From 54ff198409c04b72570f708685631afb65c6dd38 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 10 Jul 2025 08:57:08 +0900 Subject: [PATCH 001/819] Upgrade Lib/types.py from Python 3.13.5 (#5928) --- Lib/types.py | 52 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/Lib/types.py b/Lib/types.py index 4dab6ddce0b..b036a850687 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -1,6 +1,7 @@ """ Define names for built-in types that aren't directly accessible as a builtin. """ + import sys # Iterators in Python aren't a matter of type but of protocol. A large @@ -52,17 +53,14 @@ def _m(self): pass try: raise TypeError -except TypeError: - tb = sys.exc_info()[2] - TracebackType = type(tb) - FrameType = type(tb.tb_frame) - tb = None; del tb +except TypeError as exc: + TracebackType = type(exc.__traceback__) + FrameType = type(exc.__traceback__.tb_frame) -# For Jython, the following two types are identical GetSetDescriptorType = type(FunctionType.__code__) MemberDescriptorType = type(FunctionType.__globals__) -del sys, _f, _g, _C, _c, _ag # Not for export +del sys, _f, _g, _C, _c, _ag, _cell_factory # Not for export # Provide a PEP 3115 compliant mechanism for class creation @@ -82,7 +80,7 @@ def resolve_bases(bases): updated = False shift = 0 for i, base in enumerate(bases): - if isinstance(base, type) and not isinstance(base, GenericAlias): + if isinstance(base, type): continue if not hasattr(base, "__mro_entries__"): continue @@ -146,6 +144,35 @@ def _calculate_meta(meta, bases): "of the metaclasses of all its bases") return winner + +def get_original_bases(cls, /): + """Return the class's "original" bases prior to modification by `__mro_entries__`. + + Examples:: + + from typing import TypeVar, Generic, NamedTuple, TypedDict + + T = TypeVar("T") + class Foo(Generic[T]): ... + class Bar(Foo[int], float): ... + class Baz(list[str]): ... + Eggs = NamedTuple("Eggs", [("a", int), ("b", str)]) + Spam = TypedDict("Spam", {"a": int, "b": str}) + + assert get_original_bases(Bar) == (Foo[int], float) + assert get_original_bases(Baz) == (list[str],) + assert get_original_bases(Eggs) == (NamedTuple,) + assert get_original_bases(Spam) == (TypedDict,) + assert get_original_bases(int) == (object,) + """ + try: + return cls.__dict__.get("__orig_bases__", cls.__bases__) + except AttributeError: + raise TypeError( + f"Expected an instance of type, not {type(cls).__name__!r}" + ) from None + + class DynamicClassAttribute: """Route attribute access on a class to __getattr__. @@ -158,7 +185,7 @@ class DynamicClassAttribute: attributes on the class with the same name. (Enum used this between Python versions 3.4 - 3.9 .) - Subclass from this to use a different method of accessing virtual atributes + Subclass from this to use a different method of accessing virtual attributes and still be treated properly by the inspect module. (Enum uses this since Python 3.10 .) @@ -305,4 +332,11 @@ def wrapped(*args, **kwargs): NoneType = type(None) NotImplementedType = type(NotImplemented) +def __getattr__(name): + if name == 'CapsuleType': + import _socket + return type(_socket.CAPI) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + __all__ = [n for n in globals() if n[:1] != '_'] +__all__ += ['CapsuleType'] From f608df4a23050434de60f407891fc452f7e39ebd Mon Sep 17 00:00:00 2001 From: yt2b <76801443+yt2b@users.noreply.github.com> Date: Thu, 10 Jul 2025 09:10:52 +0900 Subject: [PATCH 002/819] Formatting with width and separator doesn't work correctly (#5927) * Fix add_magnitude_separators * Add extra tests --- common/src/format.rs | 8 ++++++-- extra_tests/snippets/builtin_format.py | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/common/src/format.rs b/common/src/format.rs index 92ab99a5713..819061f86ba 100644 --- a/common/src/format.rs +++ b/common/src/format.rs @@ -436,8 +436,12 @@ impl FormatSpec { let sep = char::from(fg); let inter = self.get_separator_interval().try_into().unwrap(); let magnitude_len = magnitude_str.len(); - let width = self.width.unwrap_or(magnitude_len) as i32 - prefix.len() as i32; - let disp_digit_cnt = cmp::max(width, magnitude_len as i32); + let disp_digit_cnt = if self.fill == Some('0'.into()) { + let width = self.width.unwrap_or(magnitude_len) as i32 - prefix.len() as i32; + cmp::max(width, magnitude_len as i32) + } else { + magnitude_len as i32 + }; Self::add_magnitude_separators_for_char(magnitude_str, inter, sep, disp_digit_cnt) } None => magnitude_str, diff --git a/extra_tests/snippets/builtin_format.py b/extra_tests/snippets/builtin_format.py index 457adaa1368..ac7afb769ab 100644 --- a/extra_tests/snippets/builtin_format.py +++ b/extra_tests/snippets/builtin_format.py @@ -81,6 +81,9 @@ def test_zero_padding(): assert f"{123.456:+011,}" == "+00,123.456" assert f"{1234:.3g}" == "1.23e+03" assert f"{1234567:.6G}" == "1.23457E+06" +assert f"{1234:10}" == " 1234" +assert f"{1234:10,}" == " 1,234" +assert f"{1234:010,}" == "00,001,234" assert f"{'🐍':4}" == "🐍 " assert_raises( ValueError, "{:,o}".format, 1, _msg="ValueError: Cannot specify ',' with 'o'." @@ -165,6 +168,9 @@ def test_zero_padding(): assert f"{3.1415:#.2}" == "3.1" assert f"{3.1415:#.3}" == "3.14" assert f"{3.1415:#.4}" == "3.142" +assert f"{1234.5:10}" == " 1234.5" +assert f"{1234.5:10,}" == " 1,234.5" +assert f"{1234.5:010,}" == "0,001,234.5" assert f"{12.34 + 5.6j}" == "(12.34+5.6j)" assert f"{12.34 - 5.6j: }" == "( 12.34-5.6j)" assert f"{12.34 + 5.6j:20}" == " (12.34+5.6j)" From 18d7c1baf12e5c5480d92dd8e1e394ccaac42f80 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 10 Jul 2025 10:27:03 +0900 Subject: [PATCH 003/819] codeobj.qualname (#5929) --- Lib/test/test_code.py | 2 -- compiler/codegen/src/compile.rs | 26 ++++++++++++++++++++++++-- compiler/codegen/src/ir.rs | 5 ++++- compiler/core/src/bytecode.rs | 4 ++++ compiler/core/src/marshal.rs | 5 +++++ vm/src/builtins/code.rs | 5 +++++ 6 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 1aceff4efcd..6b0dc09e286 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -249,8 +249,6 @@ def func(): pass co.co_freevars, co.co_cellvars) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_qualname(self): self.assertEqual( CodeTest.test_qualname.__code__.co_qualname, diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 4180b797125..ab9b469ad89 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -310,8 +310,8 @@ impl<'src> Compiler<'src> { kwonlyarg_count: 0, source_path: source_code.path.to_owned(), first_line_number: OneIndexed::MIN, - obj_name: code_name, - + obj_name: code_name.clone(), + qualname: Some(code_name), blocks: vec![ir::Block::default()], current_block: ir::BlockIdx(0), constants: IndexSet::default(), @@ -402,6 +402,13 @@ impl Compiler<'_> { .map(|(var, _)| var.clone()) .collect(); + // Calculate qualname based on the current qualified path + let qualname = if self.qualified_path.is_empty() { + Some(obj_name.clone()) + } else { + Some(self.qualified_path.join(".")) + }; + let info = ir::CodeInfo { flags, posonlyarg_count, @@ -410,6 +417,7 @@ impl Compiler<'_> { source_path, first_line_number, obj_name, + qualname, blocks: vec![ir::Block::default()], current_block: ir::BlockIdx(0), @@ -1496,6 +1504,10 @@ impl Compiler<'_> { self.push_qualified_path(name); let qualified_name = self.qualified_path.join("."); + + // Update the qualname in the current code info + self.code_stack.last_mut().unwrap().qualname = Some(qualified_name.clone()); + self.push_qualified_path(""); let (doc_str, body) = split_doc(body, &self.opts); @@ -1720,6 +1732,9 @@ impl Compiler<'_> { self.push_output(bytecode::CodeFlags::empty(), 0, 0, 0, name.to_owned()); + // Update the qualname in the current code info + self.code_stack.last_mut().unwrap().qualname = Some(qualified_name.clone()); + let (doc_str, body) = split_doc(body, &self.opts); let dunder_name = self.name("__name__"); @@ -3495,6 +3510,9 @@ impl Compiler<'_> { let mut func_flags = self .enter_function(&name, parameters.as_deref().unwrap_or(&Default::default()))?; + // Lambda qualname should be + self.code_stack.last_mut().unwrap().qualname = Some(name.clone()); + self.ctx = CompileContext { loop_data: Option::None, in_class: prev_ctx.in_class, @@ -3956,6 +3974,10 @@ impl Compiler<'_> { // Create magnificent function : self.push_output(flags, 1, 1, 0, name.to_owned()); + + // Set qualname for comprehension + self.code_stack.last_mut().unwrap().qualname = Some(name.to_owned()); + let arg0 = self.varname(".0")?; let return_none = init_collection.is_none(); diff --git a/compiler/codegen/src/ir.rs b/compiler/codegen/src/ir.rs index 7acd9d7f6ae..5e115d9deec 100644 --- a/compiler/codegen/src/ir.rs +++ b/compiler/codegen/src/ir.rs @@ -73,6 +73,7 @@ pub struct CodeInfo { pub source_path: String, pub first_line_number: OneIndexed, pub obj_name: String, // Name of the object that created this code object + pub qualname: Option, // Qualified name of the object pub blocks: Vec, pub current_block: BlockIdx, @@ -99,6 +100,7 @@ impl CodeInfo { source_path, first_line_number, obj_name, + qualname, mut blocks, current_block: _, @@ -162,7 +164,8 @@ impl CodeInfo { kwonlyarg_count, source_path, first_line_number: Some(first_line_number), - obj_name, + obj_name: obj_name.clone(), + qualname: qualname.unwrap_or(obj_name), max_stackdepth, instructions: instructions.into_boxed_slice(), diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index be55fe35023..3fe9356004b 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -115,6 +115,8 @@ pub struct CodeObject { pub max_stackdepth: u32, pub obj_name: C::Name, // Name of the object that created this code object + pub qualname: C::Name, + // Qualified name of the object (like CPython's co_qualname) pub cell2arg: Option>, pub constants: Box<[C]>, pub names: Box<[C::Name]>, @@ -1140,6 +1142,7 @@ impl CodeObject { freevars: map_names(self.freevars), source_path: bag.make_name(self.source_path.as_ref()), obj_name: bag.make_name(self.obj_name.as_ref()), + qualname: bag.make_name(self.qualname.as_ref()), instructions: self.instructions, locations: self.locations, @@ -1169,6 +1172,7 @@ impl CodeObject { freevars: map_names(&self.freevars), source_path: bag.make_name(self.source_path.as_ref()), obj_name: bag.make_name(self.obj_name.as_ref()), + qualname: bag.make_name(self.qualname.as_ref()), instructions: self.instructions.clone(), locations: self.locations.clone(), diff --git a/compiler/core/src/marshal.rs b/compiler/core/src/marshal.rs index 700bb48230a..fdbae7ec30b 100644 --- a/compiler/core/src/marshal.rs +++ b/compiler/core/src/marshal.rs @@ -210,6 +210,9 @@ pub fn deserialize_code( let len = rdr.read_u32()?; let obj_name = bag.make_name(rdr.read_str(len)?); + let len = rdr.read_u32()?; + let qualname = bag.make_name(rdr.read_str(len)?); + let len = rdr.read_u32()?; let cell2arg = (len != 0) .then(|| { @@ -250,6 +253,7 @@ pub fn deserialize_code( first_line_number, max_stackdepth, obj_name, + qualname, cell2arg, constants, names, @@ -609,6 +613,7 @@ pub fn serialize_code(buf: &mut W, code: &CodeObject) buf.write_u32(code.max_stackdepth); write_vec(buf, code.obj_name.as_ref().as_bytes()); + write_vec(buf, code.qualname.as_ref().as_bytes()); let cell2arg = code.cell2arg.as_deref().unwrap_or(&[]); write_len(buf, cell2arg.len()); diff --git a/vm/src/builtins/code.rs b/vm/src/builtins/code.rs index 0058dbf5556..37c883043ac 100644 --- a/vm/src/builtins/code.rs +++ b/vm/src/builtins/code.rs @@ -298,6 +298,10 @@ impl PyCode { fn co_name(&self) -> PyStrRef { self.code.obj_name.to_owned() } + #[pygetset] + fn co_qualname(&self) -> PyStrRef { + self.code.qualname.to_owned() + } #[pygetset] fn co_names(&self, vm: &VirtualMachine) -> PyTupleRef { @@ -401,6 +405,7 @@ impl PyCode { source_path: source_path.as_object().as_interned_str(vm).unwrap(), first_line_number, obj_name: obj_name.as_object().as_interned_str(vm).unwrap(), + qualname: self.code.qualname, max_stackdepth: self.code.max_stackdepth, instructions: self.code.instructions.clone(), From 8c4c63673e5dabcb031768e5e775e222162cf2f5 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI Date: Thu, 10 Jul 2025 18:11:24 +0900 Subject: [PATCH 004/819] fix(itertools): add re-entrancy guard to tee object (#5931) * fix(itertools): add re-entrancy guard to tee object * apply feedback PyRwLock -> PyMutex & remove AtomicCell lock field --- Lib/test/test_itertools.py | 2 -- vm/src/stdlib/itertools.rs | 17 +++++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 8709948b929..072279ea3a5 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -1761,8 +1761,6 @@ def test_tee_del_backward(self): del forward, backward raise - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_tee_reenter(self): class I: first = True diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs index a018fe382d1..63b2d390ca8 100644 --- a/vm/src/stdlib/itertools.rs +++ b/vm/src/stdlib/itertools.rs @@ -1184,23 +1184,28 @@ mod decl { #[derive(Debug)] struct PyItertoolsTeeData { iterable: PyIter, - values: PyRwLock>, + values: PyMutex>, } impl PyItertoolsTeeData { fn new(iterable: PyIter, _vm: &VirtualMachine) -> PyResult> { Ok(PyRc::new(Self { iterable, - values: PyRwLock::new(vec![]), + values: PyMutex::new(vec![]), })) } fn get_item(&self, vm: &VirtualMachine, index: usize) -> PyResult { - if self.values.read().len() == index { - let result = raise_if_stop!(self.iterable.next(vm)?); - self.values.write().push(result); + let Some(mut values) = self.values.try_lock() else { + return Err(vm.new_runtime_error("cannot re-enter the tee iterator")); + }; + + if values.len() == index { + let obj = raise_if_stop!(self.iterable.next(vm)?); + values.push(obj); } - Ok(PyIterReturn::Return(self.values.read()[index].clone())) + + Ok(PyIterReturn::Return(values[index].clone())) } } From b2013cddc9a83c7a72847df6d08e1febb7f67927 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 10 Jul 2025 13:55:38 +0300 Subject: [PATCH 005/819] Add "take" comment command (#5932) --- .github/workflows/comment-commands.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/comment-commands.yml diff --git a/.github/workflows/comment-commands.yml b/.github/workflows/comment-commands.yml new file mode 100644 index 00000000000..2b2a1caefe8 --- /dev/null +++ b/.github/workflows/comment-commands.yml @@ -0,0 +1,24 @@ +name: Comment Commands + +on: + issue_comment: + types: created + +jobs: + issue_assign: + if: (!github.event.issue.pull_request) && github.event.comment.body == 'take' + runs-on: ubuntu-latest + + concurrency: + group: ${{ github.actor }}-issue-assign + + permissions: + issues: write + + steps: + - run: gh issue edit "${{ env.ISSUE_NUMBER }}" --add-assignee "${{ env.USER_LOGIN }}" + env: + ISSUE_NUMBER: ${{ github.event.issue.number }} + USER_LOGIN: ${{ github.event.comment.user.login }} + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} From ef385a9efa3d976e528015ddb8b47d7b6f589ced Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:46:29 +0300 Subject: [PATCH 006/819] Add missing `@` for the "take" comment command (#5933) --- .github/workflows/comment-commands.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/comment-commands.yml b/.github/workflows/comment-commands.yml index 2b2a1caefe8..0d209fd676f 100644 --- a/.github/workflows/comment-commands.yml +++ b/.github/workflows/comment-commands.yml @@ -16,7 +16,7 @@ jobs: issues: write steps: - - run: gh issue edit "${{ env.ISSUE_NUMBER }}" --add-assignee "${{ env.USER_LOGIN }}" + - run: gh issue edit "${{ env.ISSUE_NUMBER }}" --add-assignee "@${{ env.USER_LOGIN }}" env: ISSUE_NUMBER: ${{ github.event.issue.number }} USER_LOGIN: ${{ github.event.comment.user.login }} From 4c7523080ab6bbd2b7386554ba56562168d5c26b Mon Sep 17 00:00:00 2001 From: Jiseok CHOI Date: Thu, 10 Jul 2025 22:47:24 +0900 Subject: [PATCH 007/819] fix(format): isolate special grouping rule to sign-aware zero-padding (#5924) --- common/src/format.rs | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/common/src/format.rs b/common/src/format.rs index 819061f86ba..9b2a37d450d 100644 --- a/common/src/format.rs +++ b/common/src/format.rs @@ -436,7 +436,9 @@ impl FormatSpec { let sep = char::from(fg); let inter = self.get_separator_interval().try_into().unwrap(); let magnitude_len = magnitude_str.len(); - let disp_digit_cnt = if self.fill == Some('0'.into()) { + let disp_digit_cnt = if self.fill == Some('0'.into()) + && self.align == Some(FormatAlign::AfterSign) + { let width = self.width.unwrap_or(magnitude_len) as i32 - prefix.len() as i32; cmp::max(width, magnitude_len as i32) } else { @@ -1323,6 +1325,45 @@ mod tests { ); } + #[test] + fn test_format_int_width_and_grouping() { + // issue #5922: width + comma grouping should pad left, not inside the number + let spec = FormatSpec::parse("10,").unwrap(); + let result = spec.format_int(&BigInt::from(1234)).unwrap(); + assert_eq!(result, " 1,234"); // CPython 3.13.5 + } + + #[test] + fn test_format_int_padding_with_grouping() { + // CPython behavior: f'{1234:010,}' results in "00,001,234" + let spec1 = FormatSpec::parse("010,").unwrap(); + let result1 = spec1.format_int(&BigInt::from(1234)).unwrap(); + assert_eq!(result1, "00,001,234"); + + // CPython behavior: f'{-1234:010,}' results in "-0,001,234" + let spec2 = FormatSpec::parse("010,").unwrap(); + let result2 = spec2.format_int(&BigInt::from(-1234)).unwrap(); + assert_eq!(result2, "-0,001,234"); + + // CPython behavior: f'{-1234:=10,}' results in "- 1,234" + let spec3 = FormatSpec::parse("=10,").unwrap(); + let result3 = spec3.format_int(&BigInt::from(-1234)).unwrap(); + assert_eq!(result3, "- 1,234"); + + // CPython behavior: f'{1234:=10,}' results in " 1,234" (same as right-align for positive numbers) + let spec4 = FormatSpec::parse("=10,").unwrap(); + let result4 = spec4.format_int(&BigInt::from(1234)).unwrap(); + assert_eq!(result4, " 1,234"); + } + + #[test] + fn test_format_int_non_aftersign_zero_padding() { + // CPython behavior: f'{1234:0>10,}' results in "000001,234" + let spec = FormatSpec::parse("0>10,").unwrap(); + let result = spec.format_int(&BigInt::from(1234)).unwrap(); + assert_eq!(result, "000001,234"); + } + #[test] fn test_format_parse() { let expected = Ok(FormatString { From 089c39f741e143e057769f6916895896240acf42 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:47:55 +0300 Subject: [PATCH 008/819] Update `test_string_literals.py` from 3.13.5 (#5934) --- Lib/test/test_string_literals.py | 121 ++++++++++++++++++++++++++++--- 1 file changed, 111 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_string_literals.py b/Lib/test/test_string_literals.py index 537c8fc5c80..098e8d3984e 100644 --- a/Lib/test/test_string_literals.py +++ b/Lib/test/test_string_literals.py @@ -111,26 +111,92 @@ def test_eval_str_invalid_escape(self): for b in range(1, 128): if b in b"""\n\r"'01234567NU\\abfnrtuvx""": continue - with self.assertWarns(DeprecationWarning): + with self.assertWarns(SyntaxWarning): self.assertEqual(eval(r"'\%c'" % b), '\\' + chr(b)) with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always', category=DeprecationWarning) + warnings.simplefilter('always', category=SyntaxWarning) eval("'''\n\\z'''") self.assertEqual(len(w), 1) + self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'") self.assertEqual(w[0].filename, '') - self.assertEqual(w[0].lineno, 1) + self.assertEqual(w[0].lineno, 2) with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('error', category=DeprecationWarning) + warnings.simplefilter('error', category=SyntaxWarning) with self.assertRaises(SyntaxError) as cm: eval("'''\n\\z'''") exc = cm.exception self.assertEqual(w, []) + self.assertEqual(exc.msg, r"invalid escape sequence '\z'") self.assertEqual(exc.filename, '') - self.assertEqual(exc.lineno, 1) + self.assertEqual(exc.lineno, 2) self.assertEqual(exc.offset, 1) + # Check that the warning is raised only once if there are syntax errors + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', category=SyntaxWarning) + with self.assertRaises(SyntaxError) as cm: + eval("'\\e' $") + exc = cm.exception + self.assertEqual(len(w), 1) + self.assertEqual(w[0].category, SyntaxWarning) + self.assertRegex(str(w[0].message), 'invalid escape sequence') + self.assertEqual(w[0].filename, '') + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_eval_str_invalid_octal_escape(self): + for i in range(0o400, 0o1000): + with self.assertWarns(SyntaxWarning): + self.assertEqual(eval(r"'\%o'" % i), chr(i)) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', category=SyntaxWarning) + eval("'''\n\\407'''") + self.assertEqual(len(w), 1) + self.assertEqual(str(w[0].message), + r"invalid octal escape sequence '\407'") + self.assertEqual(w[0].filename, '') + self.assertEqual(w[0].lineno, 2) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('error', category=SyntaxWarning) + with self.assertRaises(SyntaxError) as cm: + eval("'''\n\\407'''") + exc = cm.exception + self.assertEqual(w, []) + self.assertEqual(exc.msg, r"invalid octal escape sequence '\407'") + self.assertEqual(exc.filename, '') + self.assertEqual(exc.lineno, 2) + self.assertEqual(exc.offset, 1) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_invalid_escape_locations_with_offset(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('error', category=SyntaxWarning) + with self.assertRaises(SyntaxError) as cm: + eval("\"'''''''''''''''''''''invalid\\ Escape\"") + exc = cm.exception + self.assertEqual(w, []) + self.assertEqual(exc.msg, r"invalid escape sequence '\ '") + self.assertEqual(exc.filename, '') + self.assertEqual(exc.lineno, 1) + self.assertEqual(exc.offset, 30) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('error', category=SyntaxWarning) + with self.assertRaises(SyntaxError) as cm: + eval("\"''Incorrect \\ logic?\"") + exc = cm.exception + self.assertEqual(w, []) + self.assertEqual(exc.msg, r"invalid escape sequence '\ '") + self.assertEqual(exc.filename, '') + self.assertEqual(exc.lineno, 1) + self.assertEqual(exc.offset, 14) + def test_eval_str_raw(self): self.assertEqual(eval(""" r'x' """), 'x') self.assertEqual(eval(r""" r'\x01' """), '\\' + 'x01') @@ -163,24 +229,52 @@ def test_eval_bytes_invalid_escape(self): for b in range(1, 128): if b in b"""\n\r"'01234567\\abfnrtvx""": continue - with self.assertWarns(DeprecationWarning): + with self.assertWarns(SyntaxWarning): self.assertEqual(eval(r"b'\%c'" % b), b'\\' + bytes([b])) with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always', category=DeprecationWarning) + warnings.simplefilter('always', category=SyntaxWarning) eval("b'''\n\\z'''") self.assertEqual(len(w), 1) + self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'") self.assertEqual(w[0].filename, '') - self.assertEqual(w[0].lineno, 1) + self.assertEqual(w[0].lineno, 2) with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('error', category=DeprecationWarning) + warnings.simplefilter('error', category=SyntaxWarning) with self.assertRaises(SyntaxError) as cm: eval("b'''\n\\z'''") exc = cm.exception self.assertEqual(w, []) + self.assertEqual(exc.msg, r"invalid escape sequence '\z'") self.assertEqual(exc.filename, '') - self.assertEqual(exc.lineno, 1) + self.assertEqual(exc.lineno, 2) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_eval_bytes_invalid_octal_escape(self): + for i in range(0o400, 0o1000): + with self.assertWarns(SyntaxWarning): + self.assertEqual(eval(r"b'\%o'" % i), bytes([i & 0o377])) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', category=SyntaxWarning) + eval("b'''\n\\407'''") + self.assertEqual(len(w), 1) + self.assertEqual(str(w[0].message), + r"invalid octal escape sequence '\407'") + self.assertEqual(w[0].filename, '') + self.assertEqual(w[0].lineno, 2) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('error', category=SyntaxWarning) + with self.assertRaises(SyntaxError) as cm: + eval("b'''\n\\407'''") + exc = cm.exception + self.assertEqual(w, []) + self.assertEqual(exc.msg, r"invalid octal escape sequence '\407'") + self.assertEqual(exc.filename, '') + self.assertEqual(exc.lineno, 2) def test_eval_bytes_raw(self): self.assertEqual(eval(""" br'x' """), b'x') @@ -217,6 +311,13 @@ def test_eval_str_u(self): self.assertRaises(SyntaxError, eval, """ bu'' """) self.assertRaises(SyntaxError, eval, """ ub'' """) + def test_uppercase_prefixes(self): + self.assertEqual(eval(""" B'x' """), b'x') + self.assertEqual(eval(r""" R'\x01' """), r'\x01') + self.assertEqual(eval(r""" BR'\x01' """), br'\x01') + self.assertEqual(eval(""" F'{1+1}' """), f'{1+1}') + self.assertEqual(eval(r""" U'\U0001d120' """), u'\U0001d120') + def check_encoding(self, encoding, extra=""): modname = "xx_" + encoding.replace("-", "_") fn = os.path.join(self.tmpdir, modname + ".py") From 38837e587b9cd9674e86ca6282368d07843cd541 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 11 Jul 2025 02:35:21 +0300 Subject: [PATCH 009/819] Make `take` issue comment to use curl (#5937) * Revert "Add missing `@` for the "take" comment command (#5933)" This reverts commit ef385a9efa3d976e528015ddb8b47d7b6f589ced. * Fix `take` --- .github/workflows/comment-commands.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/comment-commands.yml b/.github/workflows/comment-commands.yml index 0d209fd676f..0a5d48e9035 100644 --- a/.github/workflows/comment-commands.yml +++ b/.github/workflows/comment-commands.yml @@ -13,12 +13,11 @@ jobs: group: ${{ github.actor }}-issue-assign permissions: - issues: write + issues: write steps: - - run: gh issue edit "${{ env.ISSUE_NUMBER }}" --add-assignee "@${{ env.USER_LOGIN }}" - env: - ISSUE_NUMBER: ${{ github.event.issue.number }} - USER_LOGIN: ${{ github.event.comment.user.login }} - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} + # Using REST API and not `gh issue edit`. https://github.com/cli/cli/issues/6235#issuecomment-1243487651 + - run: curl \ + -H "Authorization: token ${{ github.token }}" \ + -d '{"assignees": ["${{ github.event.comment.user.login }}"]}' \ + https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees From 01f15065fa033092e78db1b04f211f7f76bf46c5 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 11 Jul 2025 02:36:08 +0300 Subject: [PATCH 010/819] Use `raise_if_stop!` macro where possible (#5938) --- vm/src/builtins/enumerate.rs | 7 +++---- vm/src/builtins/filter.rs | 19 +++++++++---------- vm/src/builtins/map.rs | 7 +++---- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/vm/src/builtins/enumerate.rs b/vm/src/builtins/enumerate.rs index 64cd4e774e0..db3d45b2487 100644 --- a/vm/src/builtins/enumerate.rs +++ b/vm/src/builtins/enumerate.rs @@ -8,6 +8,7 @@ use crate::{ convert::ToPyObject, function::OptionalArg, protocol::{PyIter, PyIterReturn}, + raise_if_stop, types::{Constructor, IterNext, Iterable, SelfIter}, }; use malachite_bigint::BigInt; @@ -73,12 +74,10 @@ impl Py { } impl SelfIter for PyEnumerate {} + impl IterNext for PyEnumerate { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { - let next_obj = match zelf.iterator.next(vm)? { - PyIterReturn::StopIteration(v) => return Ok(PyIterReturn::StopIteration(v)), - PyIterReturn::Return(obj) => obj, - }; + let next_obj = raise_if_stop!(zelf.iterator.next(vm)?); let mut counter = zelf.counter.write(); let position = counter.clone(); *counter += 1; diff --git a/vm/src/builtins/filter.rs b/vm/src/builtins/filter.rs index 5dd0162f83f..661fbd02286 100644 --- a/vm/src/builtins/filter.rs +++ b/vm/src/builtins/filter.rs @@ -3,6 +3,7 @@ use crate::{ Context, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, class::PyClassImpl, protocol::{PyIter, PyIterReturn}, + raise_if_stop, types::{Constructor, IterNext, Iterable, SelfIter}, }; @@ -45,24 +46,22 @@ impl PyFilter { } impl SelfIter for PyFilter {} + impl IterNext for PyFilter { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { let predicate = &zelf.predicate; loop { - let next_obj = match zelf.iterator.next(vm)? { - PyIterReturn::Return(obj) => obj, - PyIterReturn::StopIteration(v) => return Ok(PyIterReturn::StopIteration(v)), - }; + let next_obj = raise_if_stop!(zelf.iterator.next(vm)?); let predicate_value = if vm.is_none(predicate) { next_obj.clone() } else { - // the predicate itself can raise StopIteration which does stop the filter - // iteration - match PyIterReturn::from_pyresult(predicate.call((next_obj.clone(),), vm), vm)? { - PyIterReturn::Return(obj) => obj, - PyIterReturn::StopIteration(v) => return Ok(PyIterReturn::StopIteration(v)), - } + // the predicate itself can raise StopIteration which does stop the filter iteration + raise_if_stop!(PyIterReturn::from_pyresult( + predicate.call((next_obj.clone(),), vm), + vm + )?) }; + if predicate_value.try_to_bool(vm)? { return Ok(PyIterReturn::Return(next_obj)); } diff --git a/vm/src/builtins/map.rs b/vm/src/builtins/map.rs index 004028c2cbc..06a533f8bcd 100644 --- a/vm/src/builtins/map.rs +++ b/vm/src/builtins/map.rs @@ -5,6 +5,7 @@ use crate::{ class::PyClassImpl, function::PosArgs, protocol::{PyIter, PyIterReturn}, + raise_if_stop, types::{Constructor, IterNext, Iterable, SelfIter}, }; @@ -53,14 +54,12 @@ impl PyMap { } impl SelfIter for PyMap {} + impl IterNext for PyMap { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { let mut next_objs = Vec::new(); for iterator in &zelf.iterators { - let item = match iterator.next(vm)? { - PyIterReturn::Return(obj) => obj, - PyIterReturn::StopIteration(v) => return Ok(PyIterReturn::StopIteration(v)), - }; + let item = raise_if_stop!(iterator.next(vm)?); next_objs.push(item); } From 2c30e01ae2860c7052cbaa57f9ff07b83b78c3d3 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 11 Jul 2025 02:36:34 +0300 Subject: [PATCH 011/819] Update test_deque from 3.13.5 (#5939) --- Lib/test/test_deque.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_deque.py b/Lib/test/test_deque.py index 2b0144eb068..9f00e12edd1 100644 --- a/Lib/test/test_deque.py +++ b/Lib/test/test_deque.py @@ -166,7 +166,7 @@ def test_contains(self): with self.assertRaises(RuntimeError): n in d - def test_contains_count_stop_crashes(self): + def test_contains_count_index_stop_crashes(self): class A: def __eq__(self, other): d.clear() @@ -178,6 +178,10 @@ def __eq__(self, other): with self.assertRaises(RuntimeError): _ = d.count(3) + d = deque([A()]) + with self.assertRaises(RuntimeError): + d.index(0) + def test_extend(self): d = deque('a') self.assertRaises(TypeError, d.extend, 1) From 2f94a63958115d2680d8160a080039ae7128b873 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:24:20 +0900 Subject: [PATCH 012/819] Add SymbolUsage::TypeParams (#5941) --- Lib/test/test_typing.py | 4 ---- compiler/codegen/src/compile.rs | 7 ++++++- compiler/codegen/src/symboltable.rs | 20 +++++++++++++++++--- vm/src/frame.rs | 6 +++++- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index a048c39cc9a..1c74e1adac8 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3888,8 +3888,6 @@ def test_pep695_generic_class_with_future_annotations(self): # should not have changed as a result of the get_type_hints() calls! self.assertEqual(ann_module695.__dict__, original_globals) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_pep695_generic_class_with_future_annotations_and_local_shadowing(self): hints_for_B = get_type_hints(ann_module695.B) self.assertEqual(hints_for_B, {"x": int, "y": str, "z": bytes}) @@ -3935,8 +3933,6 @@ def test_pep_695_generic_method_with_future_annotations_name_clash_with_global_v set(ann_module695.D.generic_method_2.__type_params__) ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_pep_695_generics_with_future_annotations_nested_in_function(self): results = ann_module695.nested() diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index ab9b469ad89..61e459500ac 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -638,7 +638,11 @@ impl Compiler<'_> { cache = &mut info.cellvar_cache; NameOpType::Deref } // TODO: is this right? - // SymbolScope::Unknown => NameOpType::Global, + SymbolScope::TypeParams => { + // Type parameters are always cell variables + cache = &mut info.cellvar_cache; + NameOpType::Deref + } // SymbolScope::Unknown => NameOpType::Global, }; if NameUsage::Load == usage && name == "__debug__" { @@ -1630,6 +1634,7 @@ impl Compiler<'_> { let vars = match symbol.scope { SymbolScope::Free => &parent_code.freevar_cache, SymbolScope::Cell => &parent_code.cellvar_cache, + SymbolScope::TypeParams => &parent_code.cellvar_cache, _ if symbol.flags.contains(SymbolFlags::FREE_CLASS) => &parent_code.freevar_cache, x => unreachable!( "var {} in a {:?} should be free or cell but it's {:?}", diff --git a/compiler/codegen/src/symboltable.rs b/compiler/codegen/src/symboltable.rs index 2949f39a9f2..f215ce38f43 100644 --- a/compiler/codegen/src/symboltable.rs +++ b/compiler/codegen/src/symboltable.rs @@ -113,6 +113,7 @@ pub enum SymbolScope { GlobalImplicit, Free, Cell, + TypeParams, } bitflags! { @@ -359,6 +360,10 @@ impl SymbolTableAnalyzer { SymbolScope::Local | SymbolScope::Cell => { // all is well } + SymbolScope::TypeParams => { + // Type parameters are always cell variables in their scope + symbol.scope = SymbolScope::Cell; + } SymbolScope::Unknown => { // Try hard to figure out what the scope of this symbol is. let scope = if symbol.is_bound() { @@ -557,6 +562,7 @@ enum SymbolUsage { AnnotationParameter, AssignedNamedExprInComprehension, Iter, + TypeParam, } struct SymbolTableBuilder<'src> { @@ -1267,6 +1273,9 @@ impl SymbolTableBuilder<'_> { } fn scan_type_params(&mut self, type_params: &TypeParams) -> SymbolTableResult { + // Register .type_params as a type parameter (automatically becomes cell variable) + self.register_name(".type_params", SymbolUsage::TypeParam, type_params.range)?; + // First register all type parameters for type_param in &type_params.type_params { match type_param { @@ -1276,7 +1285,7 @@ impl SymbolTableBuilder<'_> { range: type_var_range, .. }) => { - self.register_name(name.as_str(), SymbolUsage::Assigned, *type_var_range)?; + self.register_name(name.as_str(), SymbolUsage::TypeParam, *type_var_range)?; if let Some(binding) = bound { self.scan_expression(binding, ExpressionContext::Load)?; } @@ -1286,14 +1295,14 @@ impl SymbolTableBuilder<'_> { range: param_spec_range, .. }) => { - self.register_name(name, SymbolUsage::Assigned, *param_spec_range)?; + self.register_name(name, SymbolUsage::TypeParam, *param_spec_range)?; } TypeParam::TypeVarTuple(TypeParamTypeVarTuple { name, range: type_var_tuple_range, .. }) => { - self.register_name(name, SymbolUsage::Assigned, *type_var_tuple_range)?; + self.register_name(name, SymbolUsage::TypeParam, *type_var_tuple_range)?; } } } @@ -1544,6 +1553,11 @@ impl SymbolTableBuilder<'_> { SymbolUsage::Iter => { flags.insert(SymbolFlags::ITER); } + SymbolUsage::TypeParam => { + // Type parameters are always cell variables in their scope + symbol.scope = SymbolScope::Cell; + flags.insert(SymbolFlags::ASSIGNED); + } } // and even more checking diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 3b69de8fd34..2bcfeebf544 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -596,7 +596,11 @@ impl ExecutingFrame<'_> { } bytecode::Instruction::LoadClassDeref(i) => { let i = i.get(arg) as usize; - let name = self.code.freevars[i - self.code.cellvars.len()]; + let name = if i < self.code.cellvars.len() { + self.code.cellvars[i] + } else { + self.code.freevars[i - self.code.cellvars.len()] + }; let value = self.locals.mapping().subscript(name, vm).ok(); self.push_value(match value { Some(v) => v, From 9b133b856021b6cae681a7aeb30a023c9e4cd41c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:11:15 +0900 Subject: [PATCH 013/819] CodeInfo::private (#5943) --- compiler/codegen/src/compile.rs | 26 ++++++++++++++++++++------ compiler/codegen/src/ir.rs | 2 ++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 61e459500ac..c6f01ecc827 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -78,7 +78,6 @@ struct Compiler<'src> { done_with_future_stmts: DoneWithFuture, future_annotations: bool, ctx: CompileContext, - class_name: Option, opts: CompileOpts, in_annotation: bool, } @@ -312,6 +311,7 @@ impl<'src> Compiler<'src> { first_line_number: OneIndexed::MIN, obj_name: code_name.clone(), qualname: Some(code_name), + private: None, blocks: vec![ir::Block::default()], current_block: ir::BlockIdx(0), constants: IndexSet::default(), @@ -334,7 +334,6 @@ impl<'src> Compiler<'src> { in_class: false, func: FunctionContext::NoFunction, }, - class_name: None, opts, in_annotation: false, } @@ -409,6 +408,9 @@ impl Compiler<'_> { Some(self.qualified_path.join(".")) }; + // Get the private name from current scope if exists + let private = self.code_stack.last().and_then(|info| info.private.clone()); + let info = ir::CodeInfo { flags, posonlyarg_count, @@ -418,6 +420,7 @@ impl Compiler<'_> { first_line_number, obj_name, qualname, + private, blocks: vec![ir::Block::default()], current_block: ir::BlockIdx(0), @@ -587,7 +590,12 @@ impl Compiler<'_> { } fn mangle<'a>(&self, name: &'a str) -> Cow<'a, str> { - symboltable::mangle_name(self.class_name.as_deref(), name) + // Use u_private from current code unit for name mangling + let private = self + .code_stack + .last() + .and_then(|info| info.private.as_deref()); + symboltable::mangle_name(private, name) } fn check_forbidden_name(&mut self, name: &str, usage: NameUsage) -> CompileResult<()> { @@ -1709,8 +1717,6 @@ impl Compiler<'_> { loop_data: None, }; - let prev_class_name = self.class_name.replace(name.to_owned()); - // Check if the class is declared global let symbol_table = self.symbol_table_stack.last().unwrap(); let symbol = unwrap_internal( @@ -1729,8 +1735,14 @@ impl Compiler<'_> { // If there are type params, we need to push a special symbol table just for them if let Some(type_params) = type_params { self.push_symbol_table(); + // Save current private name to restore later + let saved_private = self.code_stack.last().and_then(|info| info.private.clone()); // Compile type parameters and store as .type_params self.compile_type_params(type_params)?; + // Restore private name after type param scope + if let Some(private) = saved_private { + self.code_stack.last_mut().unwrap().private = Some(private); + } let dot_type_params = self.name(".type_params"); emit!(self, Instruction::StoreLocal(dot_type_params)); } @@ -1740,6 +1752,9 @@ impl Compiler<'_> { // Update the qualname in the current code info self.code_stack.last_mut().unwrap().qualname = Some(qualified_name.clone()); + // For class scopes, set u_private to the class name for name mangling + self.code_stack.last_mut().unwrap().private = Some(name.to_owned()); + let (doc_str, body) = split_doc(body, &self.opts); let dunder_name = self.name("__name__"); @@ -1793,7 +1808,6 @@ impl Compiler<'_> { let code = self.pop_code_object(); - self.class_name = prev_class_name; self.qualified_path.pop(); self.qualified_path.append(global_path_prefix.as_mut()); self.ctx = prev_ctx; diff --git a/compiler/codegen/src/ir.rs b/compiler/codegen/src/ir.rs index 5e115d9deec..852051777ec 100644 --- a/compiler/codegen/src/ir.rs +++ b/compiler/codegen/src/ir.rs @@ -74,6 +74,7 @@ pub struct CodeInfo { pub first_line_number: OneIndexed, pub obj_name: String, // Name of the object that created this code object pub qualname: Option, // Qualified name of the object + pub private: Option, // For private name mangling, mostly for class pub blocks: Vec, pub current_block: BlockIdx, @@ -101,6 +102,7 @@ impl CodeInfo { first_line_number, obj_name, qualname, + private: _, // private is only used during compilation mut blocks, current_block: _, From 8b6c78c884caa7fe8ef49e672fd15aa38a54e17f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:35:52 +0900 Subject: [PATCH 014/819] SymbolTableType::Lambda (#5942) --- compiler/codegen/src/symboltable.rs | 6 ++++-- compiler/core/src/bytecode.rs | 2 +- vm/src/frame.rs | 6 ++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/compiler/codegen/src/symboltable.rs b/compiler/codegen/src/symboltable.rs index f215ce38f43..66dbff326ad 100644 --- a/compiler/codegen/src/symboltable.rs +++ b/compiler/codegen/src/symboltable.rs @@ -80,6 +80,7 @@ pub enum SymbolTableType { Module, Class, Function, + Lambda, Comprehension, TypeParams, } @@ -90,6 +91,7 @@ impl fmt::Display for SymbolTableType { Self::Module => write!(f, "module"), Self::Class => write!(f, "class"), Self::Function => write!(f, "function"), + Self::Lambda => write!(f, "lambda"), Self::Comprehension => write!(f, "comprehension"), Self::TypeParams => write!(f, "type parameter"), // TODO missing types from the C implementation @@ -493,7 +495,7 @@ impl SymbolTableAnalyzer { location: None, }); } - SymbolTableType::Function => { + SymbolTableType::Function | SymbolTableType::Lambda => { if let Some(parent_symbol) = symbols.get_mut(&symbol.name) { if let SymbolScope::Unknown = parent_symbol.scope { // this information is new, as the assignment is done in inner scope @@ -1140,7 +1142,7 @@ impl SymbolTableBuilder<'_> { } else { self.enter_scope( "lambda", - SymbolTableType::Function, + SymbolTableType::Lambda, self.line_index_start(expression.range()), ); } diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index 3fe9356004b..a27174e8598 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -414,7 +414,7 @@ op_arg_enum!( // PrepReraiseS tar = 1, // TypeVarWithBound = 2, // TypeVarWithConstraints = 3, - // SetFunctionTypeParams = 4, + SetFunctionTypeParams = 4, /// Set default value for type parameter (PEP 695) SetTypeparamDefault = 5, } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 2bcfeebf544..6c9181c2c1f 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -2286,6 +2286,12 @@ impl ExecutingFrame<'_> { bytecode::IntrinsicFunction2::SetTypeparamDefault => { crate::stdlib::typing::set_typeparam_default(arg1, arg2, vm) } + bytecode::IntrinsicFunction2::SetFunctionTypeParams => { + // arg1 is the function, arg2 is the type params tuple + // Set __type_params__ attribute on the function + arg1.set_attr("__type_params__", arg2, vm)?; + Ok(arg1) + } } } From 0ae6b4575c9b31b29db487c5fcef4965b1b45dea Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:16:01 +0900 Subject: [PATCH 015/819] typing TypeAlias (#5945) --- Lib/test/test_typing.py | 2 - compiler/codegen/src/compile.rs | 62 +++++++++++----- compiler/core/src/bytecode.rs | 46 ++---------- vm/src/frame.rs | 126 ++++++++++++++++---------------- vm/src/stdlib/typing.rs | 21 ++++++ 5 files changed, 134 insertions(+), 123 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 1c74e1adac8..d0fe1b0188c 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6649,8 +6649,6 @@ def manager(): self.assertIsInstance(cm, typing.ContextManager) self.assertNotIsInstance(42, typing.ContextManager) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_contextmanager_type_params(self): cm1 = typing.ContextManager[int] self.assertEqual(get_args(cm1), (int, bool | None)) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index c6f01ecc827..d3d412e9c52 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -1067,33 +1067,41 @@ impl Compiler<'_> { // For PEP 695 syntax, we need to compile type_params first // so that they're available when compiling the value expression + // Push name first + self.emit_load_const(ConstantData::Str { + value: name_string.clone().into(), + }); + if let Some(type_params) = type_params { self.push_symbol_table(); - // Compile type params first to define T1, T2, etc. + // Compile type params and push to stack self.compile_type_params(type_params)?; - // Stack now has type_params tuple at top + // Stack now has [name, type_params_tuple] // Compile value expression (can now see T1, T2) self.compile_expression(value)?; - // Stack: [type_params_tuple, value] - - // We need [value, type_params_tuple] for TypeAlias instruction - emit!(self, Instruction::Rotate2); + // Stack: [name, type_params_tuple, value] self.pop_symbol_table(); } else { - // No type params - push value first, then None (not empty tuple) - self.compile_expression(value)?; // Push None for type_params (matching CPython) self.emit_load_const(ConstantData::None); + // Stack: [name, None] + + // Compile value expression + self.compile_expression(value)?; + // Stack: [name, None, value] } - // Push name last - self.emit_load_const(ConstantData::Str { - value: name_string.clone().into(), - }); - emit!(self, Instruction::TypeAlias); + // Build tuple of 3 elements and call intrinsic + emit!(self, Instruction::BuildTuple { size: 3 }); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::TypeAlias + } + ); self.store_name(&name_string)?; } Stmt::IpyEscapeCommand(_) => todo!(), @@ -1246,12 +1254,22 @@ impl Compiler<'_> { self.emit_load_const(ConstantData::Str { value: name.as_str().into(), }); - emit!(self, Instruction::TypeVarWithBound); + emit!( + self, + Instruction::CallIntrinsic2 { + func: bytecode::IntrinsicFunction2::TypeVarWithBound + } + ); } else { self.emit_load_const(ConstantData::Str { value: name.as_str().into(), }); - emit!(self, Instruction::TypeVar); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::TypeVar + } + ); } // Handle default value if present (PEP 695) @@ -1274,7 +1292,12 @@ impl Compiler<'_> { self.emit_load_const(ConstantData::Str { value: name.as_str().into(), }); - emit!(self, Instruction::ParamSpec); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::ParamSpec + } + ); // Handle default value if present (PEP 695) if let Some(default_expr) = default { @@ -1296,7 +1319,12 @@ impl Compiler<'_> { self.emit_load_const(ConstantData::Str { value: name.as_str().into(), }); - emit!(self, Instruction::TypeVarTuple); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::TypeVarTuple + } + ); // Handle default value if present (PEP 695) if let Some(default_expr) = default { diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index a27174e8598..cef332bfbc6 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -382,27 +382,13 @@ op_arg_enum!( #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] pub enum IntrinsicFunction1 { - /// Import * special case - // ImportStar = 0, - /// Set stop iteration value - // StopAsyncIteration = 1, - /// Unary operators - // UnaryPositive = 2, - // UnaryNegative = 3, - // UnaryNot = 4, - // UnaryInvert = 5, - /// Exit init subclass - // ExitInitCheck = 6, - /// Create a new list from an iterator - // ListToTupleForCall = 7, /// Type parameter related - // TypeVar = 8, - // TypeVarTuple = 9, - // ParamSpec = 10, + TypeVar = 7, + ParamSpec = 8, + TypeVarTuple = 9, /// Generic subscript for PEP 695 SubscriptGeneric = 10, - // TypeAlias = 12, - // TypeParams = 13, + TypeAlias = 11, } ); @@ -412,8 +398,8 @@ op_arg_enum!( #[repr(u8)] pub enum IntrinsicFunction2 { // PrepReraiseS tar = 1, - // TypeVarWithBound = 2, - // TypeVarWithConstraints = 3, + TypeVarWithBound = 2, + TypeVarWithConstraint = 3, SetFunctionTypeParams = 4, /// Set default value for type parameter (PEP 695) SetTypeparamDefault = 5, @@ -668,16 +654,10 @@ pub enum Instruction { MatchKeys, MatchClass(Arg), ExtendedArg, - TypeVar, - TypeVarWithBound, - TypeVarWithConstraint, - TypeAlias, - TypeVarTuple, - ParamSpec, // If you add a new instruction here, be sure to keep LAST_INSTRUCTION updated } // This must be kept up to date to avoid marshaling errors -const LAST_INSTRUCTION: Instruction = Instruction::ParamSpec; +const LAST_INSTRUCTION: Instruction = Instruction::ExtendedArg; const _: () = assert!(mem::size_of::() == 1); impl From for u8 { @@ -1380,12 +1360,6 @@ impl Instruction { MatchKeys => -1, MatchClass(_) => -2, ExtendedArg => 0, - TypeVar => 0, - TypeVarWithBound => -1, - TypeVarWithConstraint => -1, - TypeAlias => -2, - ParamSpec => 0, - TypeVarTuple => 0, } } @@ -1565,12 +1539,6 @@ impl Instruction { MatchKeys => w!(MatchKeys), MatchClass(arg) => w!(MatchClass, arg), ExtendedArg => w!(ExtendedArg, Arg::::marker()), - TypeVar => w!(TypeVar), - TypeVarWithBound => w!(TypeVarWithBound), - TypeVarWithConstraint => w!(TypeVarWithConstraint), - TypeAlias => w!(TypeAlias), - ParamSpec => w!(ParamSpec), - TypeVarTuple => w!(TypeVarTuple), } } } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 6c9181c2c1f..7c935b814b3 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1255,71 +1255,6 @@ impl ExecutingFrame<'_> { *extend_arg = true; Ok(None) } - bytecode::Instruction::TypeVar => { - let type_name = self.pop_value(); - let type_var: PyObjectRef = - typing::TypeVar::new(vm, type_name.clone(), vm.ctx.none(), vm.ctx.none()) - .into_ref(&vm.ctx) - .into(); - self.push_value(type_var); - Ok(None) - } - bytecode::Instruction::TypeVarWithBound => { - let type_name = self.pop_value(); - let bound = self.pop_value(); - let type_var: PyObjectRef = - typing::TypeVar::new(vm, type_name.clone(), bound, vm.ctx.none()) - .into_ref(&vm.ctx) - .into(); - self.push_value(type_var); - Ok(None) - } - bytecode::Instruction::TypeVarWithConstraint => { - let type_name = self.pop_value(); - let constraint = self.pop_value(); - let type_var: PyObjectRef = - typing::TypeVar::new(vm, type_name.clone(), vm.ctx.none(), constraint) - .into_ref(&vm.ctx) - .into(); - self.push_value(type_var); - Ok(None) - } - bytecode::Instruction::TypeAlias => { - let name = self.pop_value(); - let type_params_obj = self.pop_value(); - - // CPython allows None or tuple for type_params - let type_params: PyTupleRef = if vm.is_none(&type_params_obj) { - // If None, use empty tuple (matching CPython's behavior) - vm.ctx.empty_tuple.clone() - } else { - type_params_obj - .downcast() - .map_err(|_| vm.new_type_error("Type params must be a tuple."))? - }; - - let value = self.pop_value(); - let type_alias = typing::TypeAliasType::new(name, type_params, value); - self.push_value(type_alias.into_ref(&vm.ctx).into()); - Ok(None) - } - bytecode::Instruction::ParamSpec => { - let param_spec_name = self.pop_value(); - let param_spec: PyObjectRef = typing::ParamSpec::new(param_spec_name.clone(), vm) - .into_ref(&vm.ctx) - .into(); - self.push_value(param_spec); - Ok(None) - } - bytecode::Instruction::TypeVarTuple => { - let type_var_tuple_name = self.pop_value(); - let type_var_tuple: PyObjectRef = - typing::TypeVarTuple::new(type_var_tuple_name.clone(), vm) - .into_ref(&vm.ctx) - .into(); - self.push_value(type_var_tuple); - Ok(None) - } bytecode::Instruction::MatchMapping => { // Pop the subject from stack let subject = self.pop_value(); @@ -2272,6 +2207,53 @@ impl ExecutingFrame<'_> { // Used for PEP 695: Generic[*type_params] crate::builtins::genericalias::subscript_generic(arg, vm) } + bytecode::IntrinsicFunction1::TypeVar => { + let type_var: PyObjectRef = + typing::TypeVar::new(vm, arg.clone(), vm.ctx.none(), vm.ctx.none()) + .into_ref(&vm.ctx) + .into(); + Ok(type_var) + } + bytecode::IntrinsicFunction1::ParamSpec => { + let param_spec: PyObjectRef = typing::ParamSpec::new(arg.clone(), vm) + .into_ref(&vm.ctx) + .into(); + Ok(param_spec) + } + bytecode::IntrinsicFunction1::TypeVarTuple => { + let type_var_tuple: PyObjectRef = typing::TypeVarTuple::new(arg.clone(), vm) + .into_ref(&vm.ctx) + .into(); + Ok(type_var_tuple) + } + bytecode::IntrinsicFunction1::TypeAlias => { + // TypeAlias receives a tuple of (name, type_params, value) + let tuple: PyTupleRef = arg + .downcast() + .map_err(|_| vm.new_type_error("TypeAlias expects a tuple argument"))?; + + if tuple.len() != 3 { + return Err(vm.new_type_error(format!( + "TypeAlias expects exactly 3 arguments, got {}", + tuple.len() + ))); + } + + let name = tuple.as_slice()[0].clone(); + let type_params_obj = tuple.as_slice()[1].clone(); + let value = tuple.as_slice()[2].clone(); + + let type_params: PyTupleRef = if vm.is_none(&type_params_obj) { + vm.ctx.empty_tuple.clone() + } else { + type_params_obj + .downcast() + .map_err(|_| vm.new_type_error("Type params must be a tuple."))? + }; + + let type_alias = typing::TypeAliasType::new(name, type_params, value); + Ok(type_alias.into_ref(&vm.ctx).into()) + } } } @@ -2292,6 +2274,20 @@ impl ExecutingFrame<'_> { arg1.set_attr("__type_params__", arg2, vm)?; Ok(arg1) } + bytecode::IntrinsicFunction2::TypeVarWithBound => { + let type_var: PyObjectRef = + typing::TypeVar::new(vm, arg1.clone(), arg2, vm.ctx.none()) + .into_ref(&vm.ctx) + .into(); + Ok(type_var) + } + bytecode::IntrinsicFunction2::TypeVarWithConstraint => { + let type_var: PyObjectRef = + typing::TypeVar::new(vm, arg1.clone(), vm.ctx.none(), arg2) + .into_ref(&vm.ctx) + .into(); + Ok(type_var) + } } } diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index 331206b214d..77feee44c03 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -113,6 +113,27 @@ pub(crate) mod decl { value, } } + + #[pygetset] + fn __name__(&self) -> PyObjectRef { + self.name.clone() + } + + #[pygetset] + fn __value__(&self) -> PyObjectRef { + self.value.clone() + } + + #[pygetset] + fn __type_params__(&self) -> PyTupleRef { + self.type_params.clone() + } + + #[pymethod(name = "__repr__")] + fn repr(&self, vm: &VirtualMachine) -> PyResult { + let name = self.name.str(vm)?; + Ok(name.as_str().to_owned()) + } } // impl AsMapping for Generic { From 392f9c26c5bc8f604fb3a7123720389b7f4ca65f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:25:57 +0900 Subject: [PATCH 016/819] Instruction::Resume (#5944) * ImportStar * Instruction::Resume --- compiler/codegen/src/compile.rs | 69 +++++++++++- ...pile__tests__nested_double_async_with.snap | 100 +++++++++--------- compiler/core/src/bytecode.rs | 24 ++++- jit/src/instructions.rs | 4 + vm/src/frame.rs | 22 +++- 5 files changed, 161 insertions(+), 58 deletions(-) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index d3d412e9c52..98b70dfbb20 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -784,7 +784,12 @@ impl Compiler<'_> { if import_star { // from .... import * - emit!(self, Instruction::ImportStar); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::ImportStar + } + ); } else { // from mod import a, b as c @@ -1556,6 +1561,14 @@ impl Compiler<'_> { .constants .insert_full(ConstantData::None); + // Emit RESUME instruction at function start + emit!( + self, + Instruction::Resume { + arg: bytecode::ResumeType::AtFuncStart as u32 + } + ); + self.compile_statements(body)?; // Emit None at end: @@ -1971,6 +1984,12 @@ impl Compiler<'_> { emit!(self, Instruction::GetAwaitable); self.emit_load_const(ConstantData::None); emit!(self, Instruction::YieldFrom); + emit!( + self, + Instruction::Resume { + arg: bytecode::ResumeType::AfterAwait as u32 + } + ); emit!(self, Instruction::SetupAsyncWith { end: final_block }); } else { emit!(self, Instruction::SetupWith { end: final_block }); @@ -2012,6 +2031,12 @@ impl Compiler<'_> { emit!(self, Instruction::GetAwaitable); self.emit_load_const(ConstantData::None); emit!(self, Instruction::YieldFrom); + emit!( + self, + Instruction::Resume { + arg: bytecode::ResumeType::AfterAwait as u32 + } + ); } emit!(self, Instruction::WithCleanupFinish); @@ -2050,6 +2075,12 @@ impl Compiler<'_> { emit!(self, Instruction::GetANext); self.emit_load_const(ConstantData::None); emit!(self, Instruction::YieldFrom); + emit!( + self, + Instruction::Resume { + arg: bytecode::ResumeType::AfterAwait as u32 + } + ); self.compile_store(target)?; emit!(self, Instruction::PopBlock); } else { @@ -3521,6 +3552,12 @@ impl Compiler<'_> { Option::None => self.emit_load_const(ConstantData::None), }; emit!(self, Instruction::YieldValue); + emit!( + self, + Instruction::Resume { + arg: bytecode::ResumeType::AfterYield as u32 + } + ); } Expr::Await(ExprAwait { value, .. }) => { if self.ctx.func != FunctionContext::AsyncFunction { @@ -3530,6 +3567,12 @@ impl Compiler<'_> { emit!(self, Instruction::GetAwaitable); self.emit_load_const(ConstantData::None); emit!(self, Instruction::YieldFrom); + emit!( + self, + Instruction::Resume { + arg: bytecode::ResumeType::AfterAwait as u32 + } + ); } Expr::YieldFrom(ExprYieldFrom { value, .. }) => { match self.ctx.func { @@ -3546,6 +3589,12 @@ impl Compiler<'_> { emit!(self, Instruction::GetIter); self.emit_load_const(ConstantData::None); emit!(self, Instruction::YieldFrom); + emit!( + self, + Instruction::Resume { + arg: bytecode::ResumeType::AfterYieldFrom as u32 + } + ); } Expr::Name(ExprName { id, .. }) => self.load_name(id.as_str())?, Expr::Lambda(ExprLambda { @@ -3672,6 +3721,12 @@ impl Compiler<'_> { compiler.compile_comprehension_element(elt)?; compiler.mark_generator(); emit!(compiler, Instruction::YieldValue); + emit!( + compiler, + Instruction::Resume { + arg: bytecode::ResumeType::AfterYield as u32 + } + ); emit!(compiler, Instruction::Pop); Ok(()) @@ -4067,6 +4122,12 @@ impl Compiler<'_> { emit!(self, Instruction::GetANext); self.emit_load_const(ConstantData::None); emit!(self, Instruction::YieldFrom); + emit!( + self, + Instruction::Resume { + arg: bytecode::ResumeType::AfterAwait as u32 + } + ); self.compile_store(&generator.target)?; emit!(self, Instruction::PopBlock); } else { @@ -4145,6 +4206,12 @@ impl Compiler<'_> { emit!(self, Instruction::GetAwaitable); self.emit_load_const(ConstantData::None); emit!(self, Instruction::YieldFrom); + emit!( + self, + Instruction::Resume { + arg: bytecode::ResumeType::AfterAwait as u32 + } + ); } Ok(()) diff --git a/compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index 36b00c567d7..9165a6cfbf1 100644 --- a/compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap +++ b/compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap @@ -11,7 +11,7 @@ expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyn 6 CallFunctionPositional(1) 7 BuildTuple (2) 8 GetIter - >> 9 ForIter (71) + >> 9 ForIter (73) 10 StoreLocal (2, stop_exc) 2 11 LoadNameAny (3, self) @@ -21,10 +21,10 @@ expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyn 15 CallFunctionPositional(1) 16 LoadConst (("type")) 17 CallMethodKeyword (1) - 18 SetupWith (68) + 18 SetupWith (70) 19 Pop - 3 20 SetupExcept (40) + 3 20 SetupExcept (42) 4 21 LoadNameAny (6, egg) 22 CallFunctionPositional(0) @@ -32,55 +32,57 @@ expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyn 24 GetAwaitable 25 LoadConst (None) 26 YieldFrom - 27 SetupAsyncWith (33) - 28 Pop + 27 Resume (3) + 28 SetupAsyncWith (34) + 29 Pop - 5 29 LoadNameAny (2, stop_exc) - 30 Raise (Raise) + 5 30 LoadNameAny (2, stop_exc) + 31 Raise (Raise) - 4 31 PopBlock - 32 EnterFinally - >> 33 WithCleanupStart - 34 GetAwaitable - 35 LoadConst (None) - 36 YieldFrom - 37 WithCleanupFinish - 38 PopBlock - 39 Jump (57) - >> 40 Duplicate + 4 32 PopBlock + 33 EnterFinally + >> 34 WithCleanupStart + 35 GetAwaitable + 36 LoadConst (None) + 37 YieldFrom + 38 Resume (3) + 39 WithCleanupFinish + 40 PopBlock + 41 Jump (59) + >> 42 Duplicate - 6 41 LoadNameAny (7, Exception) - 42 TestOperation (ExceptionMatch) - 43 JumpIfFalse (56) - 44 StoreLocal (8, ex) + 6 43 LoadNameAny (7, Exception) + 44 TestOperation (ExceptionMatch) + 45 JumpIfFalse (58) + 46 StoreLocal (8, ex) - 7 45 LoadNameAny (3, self) - 46 LoadMethod (9, assertIs) - 47 LoadNameAny (8, ex) - 48 LoadNameAny (2, stop_exc) - 49 CallMethodPositional (2) - 50 Pop - 51 PopException - 52 LoadConst (None) - 53 StoreLocal (8, ex) - 54 DeleteLocal (8, ex) - 55 Jump (66) - >> 56 Raise (Reraise) + 7 47 LoadNameAny (3, self) + 48 LoadMethod (9, assertIs) + 49 LoadNameAny (8, ex) + 50 LoadNameAny (2, stop_exc) + 51 CallMethodPositional (2) + 52 Pop + 53 PopException + 54 LoadConst (None) + 55 StoreLocal (8, ex) + 56 DeleteLocal (8, ex) + 57 Jump (68) + >> 58 Raise (Reraise) - 9 >> 57 LoadNameAny (3, self) - 58 LoadMethod (10, fail) - 59 LoadConst ("") - 60 LoadNameAny (2, stop_exc) - 61 FormatValue (None) - 62 LoadConst (" was suppressed") - 63 BuildString (2) - 64 CallMethodPositional (1) - 65 Pop + 9 >> 59 LoadNameAny (3, self) + 60 LoadMethod (10, fail) + 61 LoadConst ("") + 62 LoadNameAny (2, stop_exc) + 63 FormatValue (None) + 64 LoadConst (" was suppressed") + 65 BuildString (2) + 66 CallMethodPositional (1) + 67 Pop - 2 >> 66 PopBlock - 67 EnterFinally - >> 68 WithCleanupStart - 69 WithCleanupFinish - 70 Jump (9) - >> 71 PopBlock - 72 ReturnConst (None) + 2 >> 68 PopBlock + 69 EnterFinally + >> 70 WithCleanupStart + 71 WithCleanupFinish + 72 Jump (9) + >> 73 PopBlock + 74 ReturnConst (None) diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index cef332bfbc6..3e74fe62738 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -24,6 +24,16 @@ pub enum ConversionFlag { Repr = b'r' as i8, } +/// Resume type for the RESUME instruction +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +#[repr(u32)] +pub enum ResumeType { + AtFuncStart = 0, + AfterYield = 1, + AfterYieldFrom = 2, + AfterAwait = 3, +} + pub trait Constant: Sized { type Name: AsRef; @@ -382,6 +392,8 @@ op_arg_enum!( #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] pub enum IntrinsicFunction1 { + /// Import * operation + ImportStar = 2, /// Type parameter related TypeVar = 7, ParamSpec = 8, @@ -419,8 +431,6 @@ pub enum Instruction { }, /// Importing without name ImportNameless, - /// Import * - ImportStar, /// from ... import ... ImportFrom { idx: Arg, @@ -549,6 +559,12 @@ pub enum Instruction { }, YieldValue, YieldFrom, + + /// Resume execution (e.g., at function start, after yield, etc.) + Resume { + arg: Arg, + }, + SetupAnnotation, SetupLoop, @@ -1240,7 +1256,6 @@ impl Instruction { match self { Nop => 0, ImportName { .. } | ImportNameless => -1, - ImportStar => -1, ImportFrom { .. } => 1, LoadFast(_) | LoadNameAny(_) | LoadGlobal(_) | LoadDeref(_) | LoadClassDeref(_) => 1, StoreFast(_) | StoreLocal(_) | StoreGlobal(_) | StoreDeref(_) => -1, @@ -1305,6 +1320,7 @@ impl Instruction { } ReturnValue => -1, ReturnConst { .. } => 0, + Resume { .. } => 0, YieldValue => 0, YieldFrom => -1, SetupAnnotation | SetupLoop | SetupFinally { .. } | EnterFinally | EndFinally => 0, @@ -1433,7 +1449,6 @@ impl Instruction { Nop => w!(Nop), ImportName { idx } => w!(ImportName, name = idx), ImportNameless => w!(ImportNameless), - ImportStar => w!(ImportStar), ImportFrom { idx } => w!(ImportFrom, name = idx), LoadFast(idx) => w!(LoadFast, varname = idx), LoadNameAny(idx) => w!(LoadNameAny, name = idx), @@ -1493,6 +1508,7 @@ impl Instruction { ForIter { target } => w!(ForIter, target), ReturnValue => w!(ReturnValue), ReturnConst { idx } => fmt_const("ReturnConst", arg, f, idx), + Resume { arg } => w!(Resume, arg), YieldValue => w!(YieldValue), YieldFrom => w!(YieldFrom), SetupAnnotation => w!(SetupAnnotation), diff --git a/jit/src/instructions.rs b/jit/src/instructions.rs index 9ec0a4385ee..5f0123d22ba 100644 --- a/jit/src/instructions.rs +++ b/jit/src/instructions.rs @@ -612,6 +612,10 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { self.stack.pop(); Ok(()) } + Instruction::Resume { arg: _resume_arg } => { + // TODO: Implement the resume instruction + Ok(()) + } _ => Err(JitCompileError::NotSupported), } } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 7c935b814b3..a3e31c5c2bb 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -541,10 +541,6 @@ impl ExecutingFrame<'_> { self.import(vm, None)?; Ok(None) } - bytecode::Instruction::ImportStar => { - self.import_star(vm)?; - Ok(None) - } bytecode::Instruction::ImportFrom { idx } => { let obj = self.import_from(vm, idx.get(arg))?; self.push_value(obj); @@ -893,6 +889,18 @@ impl ExecutingFrame<'_> { Ok(Some(ExecutionResult::Yield(value))) } bytecode::Instruction::YieldFrom => self.execute_yield_from(vm), + bytecode::Instruction::Resume { arg: resume_arg } => { + // Resume execution after yield, await, or at function start + // In CPython, this checks instrumentation and eval breaker + // For now, we just check for signals/interrupts + let _resume_type = resume_arg.get(arg); + + // Check for interrupts if not resuming from yield_from + // if resume_type < bytecode::ResumeType::AfterYieldFrom as u32 { + // vm.check_signals()?; + // } + Ok(None) + } bytecode::Instruction::SetupAnnotation => self.setup_annotations(vm), bytecode::Instruction::SetupLoop => { self.push_block(BlockType::Loop); @@ -2203,6 +2211,12 @@ impl ExecutingFrame<'_> { vm: &VirtualMachine, ) -> PyResult { match func { + bytecode::IntrinsicFunction1::ImportStar => { + // arg is the module object + self.push_value(arg); // Push module back on stack for import_star + self.import_star(vm)?; + Ok(vm.ctx.none()) + } bytecode::IntrinsicFunction1::SubscriptGeneric => { // Used for PEP 695: Generic[*type_params] crate::builtins::genericalias::subscript_generic(arg, vm) From 50c241fd71982039159fd2508c228b8e59265a18 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:44:46 +0300 Subject: [PATCH 017/819] Fix yaml error in `take` issue command (#5946) --- .github/workflows/comment-commands.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/comment-commands.yml b/.github/workflows/comment-commands.yml index 0a5d48e9035..d1a457c73e6 100644 --- a/.github/workflows/comment-commands.yml +++ b/.github/workflows/comment-commands.yml @@ -17,7 +17,5 @@ jobs: steps: # Using REST API and not `gh issue edit`. https://github.com/cli/cli/issues/6235#issuecomment-1243487651 - - run: curl \ - -H "Authorization: token ${{ github.token }}" \ - -d '{"assignees": ["${{ github.event.comment.user.login }}"]}' \ - https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees + - run: | + curl -H "Authorization: token ${{ github.token }}" -d '{"assignees": ["${{ github.event.comment.user.login }}"]}' https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees From 59c7fcbb9862da38281a6605c5dda177f7a2552c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 11 Jul 2025 18:21:51 +0900 Subject: [PATCH 018/819] compiler set_qualname (#5930) * set_qualname * remove qualified_path --- compiler/codegen/src/compile.rs | 153 ++++++++++++++++++++++---------- 1 file changed, 105 insertions(+), 48 deletions(-) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 98b70dfbb20..5aef2020d09 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -74,7 +74,6 @@ struct Compiler<'src> { source_code: SourceCode<'src>, // current_source_location: SourceLocation, current_source_range: TextRange, - qualified_path: Vec, done_with_future_stmts: DoneWithFuture, future_annotations: bool, ctx: CompileContext, @@ -326,7 +325,6 @@ impl<'src> Compiler<'src> { source_code, // current_source_location: SourceLocation::default(), current_source_range: TextRange::default(), - qualified_path: Vec::new(), done_with_future_stmts: DoneWithFuture::No, future_annotations: false, ctx: CompileContext { @@ -401,12 +399,8 @@ impl Compiler<'_> { .map(|(var, _)| var.clone()) .collect(); - // Calculate qualname based on the current qualified path - let qualname = if self.qualified_path.is_empty() { - Some(obj_name.clone()) - } else { - Some(self.qualified_path.join(".")) - }; + // Qualname will be set later by set_qualname + let qualname = None; // Get the private name from current scope if exists let private = self.code_stack.last().and_then(|info| info.private.clone()); @@ -467,6 +461,98 @@ impl Compiler<'_> { .to_u32() } + /// Set the qualified name for the current code object, based on CPython's compiler_set_qualname + fn set_qualname(&mut self) -> String { + let qualname = self.make_qualname(); + self.current_code_info().qualname = Some(qualname.clone()); + qualname + } + fn make_qualname(&mut self) -> String { + let stack_size = self.code_stack.len(); + assert!(stack_size >= 1); + + let current_obj_name = self.current_code_info().obj_name.clone(); + + // If we're at the module level (stack_size == 1), qualname is just the name + if stack_size <= 1 { + return current_obj_name; + } + + // Check parent scope + let mut parent_idx = stack_size - 2; + let mut parent = &self.code_stack[parent_idx]; + + // If parent is a type parameter scope, look at grandparent + if parent.obj_name.starts_with(" self.symbol_table_stack.len() { + // We might be in a situation where symbol table isn't pushed yet + // In this case, check the parent symbol table + if let Some(parent_table) = self.symbol_table_stack.last() { + if let Some(symbol) = parent_table.lookup(¤t_obj_name) { + if symbol.scope == SymbolScope::GlobalExplicit { + force_global = true; + } + } + } + } else if let Some(_current_table) = self.symbol_table_stack.last() { + // Mangle the name if necessary (for private names in classes) + let mangled_name = self.mangle(¤t_obj_name); + + // Look up in parent symbol table to check scope + if self.symbol_table_stack.len() >= 2 { + let parent_table = &self.symbol_table_stack[self.symbol_table_stack.len() - 2]; + if let Some(symbol) = parent_table.lookup(&mangled_name) { + if symbol.scope == SymbolScope::GlobalExplicit { + force_global = true; + } + } + } + } + + // Build the qualified name + if force_global { + // For global symbols, qualname is just the name + current_obj_name + } else { + // Check parent scope type + let parent_obj_name = &parent.obj_name; + + // Determine if parent is a function-like scope + let is_function_parent = parent.flags.contains(bytecode::CodeFlags::IS_OPTIMIZED) + && !parent_obj_name.starts_with("<") // Not a special scope like , , etc. + && parent_obj_name != ""; // Not the module scope + + if is_function_parent { + // For functions, append . to parent qualname + // Use parent's qualname if available, otherwise use parent_obj_name + let parent_qualname = parent.qualname.as_ref().unwrap_or(parent_obj_name); + format!("{parent_qualname}..{current_obj_name}") + } else { + // For classes and other scopes, use parent's qualname directly + // Use parent's qualname if available, otherwise use parent_obj_name + let parent_qualname = parent.qualname.as_ref().unwrap_or(parent_obj_name); + if parent_qualname == "" { + // Module level, just use the name + current_obj_name + } else { + // Concatenate parent qualname with current name + format!("{parent_qualname}.{current_obj_name}") + } + } + } + } + fn compile_program( &mut self, body: &ModModule, @@ -1547,13 +1633,8 @@ impl Compiler<'_> { }, }; - self.push_qualified_path(name); - let qualified_name = self.qualified_path.join("."); - - // Update the qualname in the current code info - self.code_stack.last_mut().unwrap().qualname = Some(qualified_name.clone()); - - self.push_qualified_path(""); + // Set qualname using the new method + let qualname = self.set_qualname(); let (doc_str, body) = split_doc(body, &self.opts); @@ -1582,8 +1663,6 @@ impl Compiler<'_> { } let code = self.pop_code_object(); - self.qualified_path.pop(); - self.qualified_path.pop(); self.ctx = prev_ctx; // Prepare generic type parameters: @@ -1646,7 +1725,7 @@ impl Compiler<'_> { code: Box::new(code), }); self.emit_load_const(ConstantData::Str { - value: qualified_name.into(), + value: qualname.into(), }); // Turn code object into function object: @@ -1758,21 +1837,6 @@ impl Compiler<'_> { loop_data: None, }; - // Check if the class is declared global - let symbol_table = self.symbol_table_stack.last().unwrap(); - let symbol = unwrap_internal( - self, - symbol_table - .lookup(name.as_ref()) - .ok_or_else(|| InternalError::MissingSymbol(name.to_owned())), - ); - let mut global_path_prefix = Vec::new(); - if symbol.scope == SymbolScope::GlobalExplicit { - global_path_prefix.append(&mut self.qualified_path); - } - self.push_qualified_path(name); - let qualified_name = self.qualified_path.join("."); - // If there are type params, we need to push a special symbol table just for them if let Some(type_params) = type_params { self.push_symbol_table(); @@ -1790,8 +1854,8 @@ impl Compiler<'_> { self.push_output(bytecode::CodeFlags::empty(), 0, 0, 0, name.to_owned()); - // Update the qualname in the current code info - self.code_stack.last_mut().unwrap().qualname = Some(qualified_name.clone()); + // Set qualname using the new method + let qualname = self.set_qualname(); // For class scopes, set u_private to the class name for name mangling self.code_stack.last_mut().unwrap().private = Some(name.to_owned()); @@ -1803,10 +1867,10 @@ impl Compiler<'_> { let dunder_module = self.name("__module__"); emit!(self, Instruction::StoreLocal(dunder_module)); self.emit_load_const(ConstantData::Str { - value: qualified_name.into(), + value: qualname.into(), }); - let qualname = self.name("__qualname__"); - emit!(self, Instruction::StoreLocal(qualname)); + let qualname_name = self.name("__qualname__"); + emit!(self, Instruction::StoreLocal(qualname_name)); self.load_docstring(doc_str); let doc = self.name("__doc__"); emit!(self, Instruction::StoreLocal(doc)); @@ -1848,9 +1912,6 @@ impl Compiler<'_> { self.emit_return_value(); let code = self.pop_code_object(); - - self.qualified_path.pop(); - self.qualified_path.append(global_path_prefix.as_mut()); self.ctx = prev_ctx; emit!(self, Instruction::LoadBuildClass); @@ -3606,8 +3667,8 @@ impl Compiler<'_> { let mut func_flags = self .enter_function(&name, parameters.as_deref().unwrap_or(&Default::default()))?; - // Lambda qualname should be - self.code_stack.last_mut().unwrap().qualname = Some(name.clone()); + // Set qualname for lambda + self.set_qualname(); self.ctx = CompileContext { loop_data: Option::None, @@ -4078,7 +4139,7 @@ impl Compiler<'_> { self.push_output(flags, 1, 1, 0, name.to_owned()); // Set qualname for comprehension - self.code_stack.last_mut().unwrap().qualname = Some(name.to_owned()); + self.set_qualname(); let arg0 = self.varname(".0")?; @@ -4336,10 +4397,6 @@ impl Compiler<'_> { .line_index(self.current_source_range.start()) } - fn push_qualified_path(&mut self, name: &str) { - self.qualified_path.push(name.to_owned()); - } - fn mark_generator(&mut self) { self.current_code_info().flags |= bytecode::CodeFlags::IS_GENERATOR } From c3967bf8495958844b91c7f45960a2c01f4f4375 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:18:23 +0300 Subject: [PATCH 019/819] Set timeout for CI (#5947) --- .github/workflows/ci.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fec9d4d8388..c5e9344956c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -113,6 +113,7 @@ jobs: RUST_BACKTRACE: full name: Run rust tests runs-on: ${{ matrix.os }} + timeout-minutes: ${{ contains(matrix.os, 'windows') && 40 || 30 }} strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] @@ -175,6 +176,7 @@ jobs: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} name: Ensure compilation on various targets runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -237,6 +239,7 @@ jobs: RUST_BACKTRACE: full name: Run snippets and cpython tests runs-on: ${{ matrix.os }} + timeout-minutes: ${{ contains(matrix.os, 'windows') && 40 || 30 }} strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] @@ -344,6 +347,7 @@ jobs: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} name: Run tests under miri runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master @@ -361,6 +365,7 @@ jobs: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} name: Check the WASM package and demo runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -421,6 +426,7 @@ jobs: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} name: Run snippets and cpython tests on wasm-wasi runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable From c4234c169207e31af61219fc8bba8271a6d8a16a Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 11 Jul 2025 22:43:08 +0900 Subject: [PATCH 020/819] SymbolTable::varnames, fblock (#5948) * SymbolTable::varnames * varname_cache copies it * fasthidden & static attributes * metadata * fblock --- compiler/codegen/src/compile.rs | 277 +++++++++++++++++++++------- compiler/codegen/src/ir.rs | 95 ++++++---- compiler/codegen/src/symboltable.rs | 40 +++- 3 files changed, 310 insertions(+), 102 deletions(-) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 5aef2020d09..6b8007c1519 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -8,12 +8,39 @@ #![deny(clippy::cast_possible_truncation)] use crate::{ - IndexSet, ToPythonName, + IndexMap, IndexSet, ToPythonName, error::{CodegenError, CodegenErrorType, PatternUnreachableReason}, ir::{self, BlockIdx}, - symboltable::{self, SymbolFlags, SymbolScope, SymbolTable}, + symboltable::{self, SymbolFlags, SymbolScope, SymbolTable, SymbolTableType}, unparse::unparse_expr, }; + +const MAXBLOCKS: usize = 20; + +#[derive(Debug, Clone, Copy)] +pub enum FBlockType { + WhileLoop, + ForLoop, + TryExcept, + FinallyTry, + FinallyEnd, + With, + AsyncWith, + HandlerCleanup, + PopValue, + ExceptionHandler, + ExceptionGroupHandler, + AsyncComprehensionGenerator, + StopIteration, +} + +#[derive(Debug, Clone)] +pub struct FBlockInfo { + pub fb_type: FBlockType, + pub fb_block: BlockIdx, + pub fb_exit: BlockIdx, + // fb_datum is not needed in RustPython +} use itertools::Itertools; use malachite_bigint::BigInt; use num_complex::Complex; @@ -303,21 +330,27 @@ impl<'src> Compiler<'src> { fn new(opts: CompileOpts, source_code: SourceCode<'src>, code_name: String) -> Self { let module_code = ir::CodeInfo { flags: bytecode::CodeFlags::NEW_LOCALS, - posonlyarg_count: 0, - arg_count: 0, - kwonlyarg_count: 0, source_path: source_code.path.to_owned(), - first_line_number: OneIndexed::MIN, - obj_name: code_name.clone(), - qualname: Some(code_name), private: None, blocks: vec![ir::Block::default()], current_block: ir::BlockIdx(0), - constants: IndexSet::default(), - name_cache: IndexSet::default(), - varname_cache: IndexSet::default(), - cellvar_cache: IndexSet::default(), - freevar_cache: IndexSet::default(), + metadata: ir::CodeUnitMetadata { + name: code_name.clone(), + qualname: Some(code_name), + consts: IndexSet::default(), + names: IndexSet::default(), + varnames: IndexSet::default(), + cellvars: IndexSet::default(), + freevars: IndexSet::default(), + fast_hidden: IndexMap::default(), + argcount: 0, + posonlyargcount: 0, + kwonlyargcount: 0, + firstlineno: OneIndexed::MIN, + }, + static_attributes: None, + in_inlined_comp: false, + fblock: Vec::with_capacity(MAXBLOCKS), }; Compiler { code_stack: vec![module_code], @@ -382,6 +415,9 @@ impl Compiler<'_> { let source_path = self.source_code.path.to_owned(); let first_line_number = self.get_source_line_number(); + // Get the private name from current scope if exists + let private = self.code_stack.last().and_then(|info| info.private.clone()); + let table = self.push_symbol_table(); let cellvar_cache = table @@ -399,30 +435,42 @@ impl Compiler<'_> { .map(|(var, _)| var.clone()) .collect(); + // Initialize varname_cache from SymbolTable::varnames + let varname_cache: IndexSet = table.varnames.iter().cloned().collect(); + // Qualname will be set later by set_qualname let qualname = None; - // Get the private name from current scope if exists - let private = self.code_stack.last().and_then(|info| info.private.clone()); + // Check if this is a class scope + let is_class_scope = table.typ == SymbolTableType::Class; let info = ir::CodeInfo { flags, - posonlyarg_count, - arg_count, - kwonlyarg_count, source_path, - first_line_number, - obj_name, - qualname, private, - blocks: vec![ir::Block::default()], current_block: ir::BlockIdx(0), - constants: IndexSet::default(), - name_cache: IndexSet::default(), - varname_cache: IndexSet::default(), - cellvar_cache, - freevar_cache, + metadata: ir::CodeUnitMetadata { + name: obj_name, + qualname, + consts: IndexSet::default(), + names: IndexSet::default(), + varnames: varname_cache, + cellvars: cellvar_cache, + freevars: freevar_cache, + fast_hidden: IndexMap::default(), + argcount: arg_count, + posonlyargcount: posonlyarg_count, + kwonlyargcount: kwonlyarg_count, + firstlineno: first_line_number, + }, + static_attributes: if is_class_scope { + Some(IndexSet::default()) + } else { + None + }, + in_inlined_comp: false, + fblock: Vec::with_capacity(MAXBLOCKS), }; self.code_stack.push(info); } @@ -435,10 +483,41 @@ impl Compiler<'_> { unwrap_internal(self, stack_top.finalize_code(self.opts.optimize)) } + /// Push a new fblock + // = compiler_push_fblock + fn push_fblock( + &mut self, + fb_type: FBlockType, + fb_block: BlockIdx, + fb_exit: BlockIdx, + ) -> CompileResult<()> { + let code = self.current_code_info(); + if code.fblock.len() >= MAXBLOCKS { + return Err(self.error(CodegenErrorType::SyntaxError( + "too many statically nested blocks".to_owned(), + ))); + } + code.fblock.push(FBlockInfo { + fb_type, + fb_block, + fb_exit, + }); + Ok(()) + } + + /// Pop an fblock + // = compiler_pop_fblock + fn pop_fblock(&mut self, _expected_type: FBlockType) -> FBlockInfo { + let code = self.current_code_info(); + // TODO: Add assertion to check expected type matches + // assert!(matches!(fblock.fb_type, expected_type)); + code.fblock.pop().expect("fblock stack underflow") + } + // could take impl Into>, but everything is borrowed from ast structs; we never // actually have a `String` to pass fn name(&mut self, name: &str) -> bytecode::NameIdx { - self._name_inner(name, |i| &mut i.name_cache) + self._name_inner(name, |i| &mut i.metadata.names) } fn varname(&mut self, name: &str) -> CompileResult { if Compiler::is_forbidden_arg_name(name) { @@ -446,7 +525,7 @@ impl Compiler<'_> { "cannot assign to {name}", )))); } - Ok(self._name_inner(name, |i| &mut i.varname_cache)) + Ok(self._name_inner(name, |i| &mut i.metadata.varnames)) } fn _name_inner( &mut self, @@ -464,14 +543,14 @@ impl Compiler<'_> { /// Set the qualified name for the current code object, based on CPython's compiler_set_qualname fn set_qualname(&mut self) -> String { let qualname = self.make_qualname(); - self.current_code_info().qualname = Some(qualname.clone()); + self.current_code_info().metadata.qualname = Some(qualname.clone()); qualname } fn make_qualname(&mut self) -> String { let stack_size = self.code_stack.len(); assert!(stack_size >= 1); - let current_obj_name = self.current_code_info().obj_name.clone(); + let current_obj_name = self.current_code_info().metadata.name.clone(); // If we're at the module level (stack_size == 1), qualname is just the name if stack_size <= 1 { @@ -483,7 +562,7 @@ impl Compiler<'_> { let mut parent = &self.code_stack[parent_idx]; // If parent is a type parameter scope, look at grandparent - if parent.obj_name.starts_with(" { current_obj_name } else { // Check parent scope type - let parent_obj_name = &parent.obj_name; + let parent_obj_name = &parent.metadata.name; // Determine if parent is a function-like scope let is_function_parent = parent.flags.contains(bytecode::CodeFlags::IS_OPTIMIZED) @@ -536,12 +615,12 @@ impl Compiler<'_> { if is_function_parent { // For functions, append . to parent qualname // Use parent's qualname if available, otherwise use parent_obj_name - let parent_qualname = parent.qualname.as_ref().unwrap_or(parent_obj_name); + let parent_qualname = parent.metadata.qualname.as_ref().unwrap_or(parent_obj_name); format!("{parent_qualname}..{current_obj_name}") } else { // For classes and other scopes, use parent's qualname directly // Use parent's qualname if available, otherwise use parent_obj_name - let parent_qualname = parent.qualname.as_ref().unwrap_or(parent_obj_name); + let parent_qualname = parent.metadata.qualname.as_ref().unwrap_or(parent_obj_name); if parent_qualname == "" { // Module level, just use the name current_obj_name @@ -706,7 +785,7 @@ impl Compiler<'_> { .ok_or_else(|| InternalError::MissingSymbol(name.to_string())), ); let info = self.code_stack.last_mut().unwrap(); - let mut cache = &mut info.name_cache; + let mut cache = &mut info.metadata.names; enum NameOpType { Fast, Global, @@ -715,7 +794,7 @@ impl Compiler<'_> { } let op_typ = match symbol.scope { SymbolScope::Local if self.ctx.in_func() => { - cache = &mut info.varname_cache; + cache = &mut info.metadata.varnames; NameOpType::Fast } SymbolScope::GlobalExplicit => NameOpType::Global, @@ -725,16 +804,16 @@ impl Compiler<'_> { SymbolScope::GlobalImplicit | SymbolScope::Unknown => NameOpType::Local, SymbolScope::Local => NameOpType::Local, SymbolScope::Free => { - cache = &mut info.freevar_cache; + cache = &mut info.metadata.freevars; NameOpType::Deref } SymbolScope::Cell => { - cache = &mut info.cellvar_cache; + cache = &mut info.metadata.cellvars; NameOpType::Deref } // TODO: is this right? SymbolScope::TypeParams => { // Type parameters are always cell variables - cache = &mut info.cellvar_cache; + cache = &mut info.metadata.cellvars; NameOpType::Deref } // SymbolScope::Unknown => NameOpType::Global, }; @@ -750,7 +829,7 @@ impl Compiler<'_> { .get_index_of(name.as_ref()) .unwrap_or_else(|| cache.insert_full(name.into_owned()).0); if let SymbolScope::Free = symbol.scope { - idx += info.cellvar_cache.len(); + idx += info.metadata.cellvars.len(); } let op = match op_typ { NameOpType::Fast => match usage { @@ -1067,26 +1146,62 @@ impl Compiler<'_> { self.switch_to_block(after_block); } } - Stmt::Break(_) => match self.ctx.loop_data { - Some((_, end)) => { - emit!(self, Instruction::Break { target: end }); - } - None => { - return Err( - self.error_ranged(CodegenErrorType::InvalidBreak, statement.range()) - ); - } - }, - Stmt::Continue(_) => match self.ctx.loop_data { - Some((start, _)) => { - emit!(self, Instruction::Continue { target: start }); + Stmt::Break(_) => { + // Find the innermost loop in fblock stack + let found_loop = { + let code = self.current_code_info(); + let mut result = None; + for i in (0..code.fblock.len()).rev() { + match code.fblock[i].fb_type { + FBlockType::WhileLoop | FBlockType::ForLoop => { + result = Some(code.fblock[i].fb_exit); + break; + } + _ => continue, + } + } + result + }; + + match found_loop { + Some(exit_block) => { + emit!(self, Instruction::Break { target: exit_block }); + } + None => { + return Err( + self.error_ranged(CodegenErrorType::InvalidBreak, statement.range()) + ); + } } - None => { - return Err( - self.error_ranged(CodegenErrorType::InvalidContinue, statement.range()) - ); + } + Stmt::Continue(_) => { + // Find the innermost loop in fblock stack + let found_loop = { + let code = self.current_code_info(); + let mut result = None; + for i in (0..code.fblock.len()).rev() { + match code.fblock[i].fb_type { + FBlockType::WhileLoop | FBlockType::ForLoop => { + result = Some(code.fblock[i].fb_block); + break; + } + _ => continue, + } + } + result + }; + + match found_loop { + Some(loop_block) => { + emit!(self, Instruction::Continue { target: loop_block }); + } + None => { + return Err( + self.error_ranged(CodegenErrorType::InvalidContinue, statement.range()) + ); + } } - }, + } Stmt::Return(StmtReturn { value, .. }) => { if !self.ctx.in_func() { return Err( @@ -1639,7 +1754,8 @@ impl Compiler<'_> { let (doc_str, body) = split_doc(body, &self.opts); self.current_code_info() - .constants + .metadata + .consts .insert_full(ConstantData::None); // Emit RESUME instruction at function start @@ -1760,10 +1876,12 @@ impl Compiler<'_> { ); let parent_code = self.code_stack.last().unwrap(); let vars = match symbol.scope { - SymbolScope::Free => &parent_code.freevar_cache, - SymbolScope::Cell => &parent_code.cellvar_cache, - SymbolScope::TypeParams => &parent_code.cellvar_cache, - _ if symbol.flags.contains(SymbolFlags::FREE_CLASS) => &parent_code.freevar_cache, + SymbolScope::Free => &parent_code.metadata.freevars, + SymbolScope::Cell => &parent_code.metadata.cellvars, + SymbolScope::TypeParams => &parent_code.metadata.cellvars, + _ if symbol.flags.contains(SymbolFlags::FREE_CLASS) => { + &parent_code.metadata.freevars + } x => unreachable!( "var {} in a {:?} should be free or cell but it's {:?}", var, table.typ, x @@ -1771,7 +1889,7 @@ impl Compiler<'_> { }; let mut idx = vars.get_index_of(var).unwrap(); if let SymbolScope::Free = symbol.scope { - idx += parent_code.cellvar_cache.len(); + idx += parent_code.metadata.cellvars.len(); } emit!(self, Instruction::LoadClosure(idx.to_u32())) } @@ -1896,7 +2014,8 @@ impl Compiler<'_> { .code_stack .last_mut() .unwrap() - .cellvar_cache + .metadata + .cellvars .iter() .position(|var| *var == "__class__"); @@ -2005,6 +2124,9 @@ impl Compiler<'_> { emit!(self, Instruction::SetupLoop); self.switch_to_block(while_block); + // Push fblock for while loop + self.push_fblock(FBlockType::WhileLoop, while_block, after_block)?; + self.compile_jump_if(test, false, else_block)?; let was_in_loop = self.ctx.loop_data.replace((while_block, after_block)); @@ -2017,6 +2139,9 @@ impl Compiler<'_> { } ); self.switch_to_block(else_block); + + // Pop fblock + self.pop_fblock(FBlockType::WhileLoop); emit!(self, Instruction::PopBlock); self.compile_statements(orelse)?; self.switch_to_block(after_block); @@ -2127,6 +2252,10 @@ impl Compiler<'_> { emit!(self, Instruction::GetAIter); self.switch_to_block(for_block); + + // Push fblock for async for loop + self.push_fblock(FBlockType::ForLoop, for_block, after_block)?; + emit!( self, Instruction::SetupExcept { @@ -2149,6 +2278,10 @@ impl Compiler<'_> { emit!(self, Instruction::GetIter); self.switch_to_block(for_block); + + // Push fblock for for loop + self.push_fblock(FBlockType::ForLoop, for_block, after_block)?; + emit!(self, Instruction::ForIter { target: else_block }); // Start of loop iteration, set targets: @@ -2161,6 +2294,10 @@ impl Compiler<'_> { emit!(self, Instruction::Jump { target: for_block }); self.switch_to_block(else_block); + + // Pop fblock + self.pop_fblock(FBlockType::ForLoop); + if is_async { emit!(self, Instruction::EndAsyncFor); } @@ -3677,7 +3814,8 @@ impl Compiler<'_> { }; self.current_code_info() - .constants + .metadata + .consts .insert_full(ConstantData::None); self.compile_expression(body)?; @@ -4138,6 +4276,9 @@ impl Compiler<'_> { // Create magnificent function : self.push_output(flags, 1, 1, 0, name.to_owned()); + // Mark that we're in an inlined comprehension + self.current_code_info().in_inlined_comp = true; + // Set qualname for comprehension self.set_qualname(); @@ -4330,7 +4471,7 @@ impl Compiler<'_> { fn arg_constant(&mut self, constant: ConstantData) -> u32 { let info = self.current_code_info(); - info.constants.insert_full(constant).0.to_u32() + info.metadata.consts.insert_full(constant).0.to_u32() } fn emit_load_const(&mut self, constant: ConstantData) { diff --git a/compiler/codegen/src/ir.rs b/compiler/codegen/src/ir.rs index 852051777ec..f2299892b38 100644 --- a/compiler/codegen/src/ir.rs +++ b/compiler/codegen/src/ir.rs @@ -1,11 +1,29 @@ use std::ops; -use crate::IndexSet; use crate::error::InternalError; +use crate::{IndexMap, IndexSet}; use ruff_source_file::{OneIndexed, SourceLocation}; use rustpython_compiler_core::bytecode::{ CodeFlags, CodeObject, CodeUnit, ConstantData, InstrDisplayContext, Instruction, Label, OpArg, }; + +/// Metadata for a code unit +// = _PyCompile_CodeUnitMetadata +#[derive(Clone, Debug)] +pub struct CodeUnitMetadata { + pub name: String, // u_name (obj_name) + pub qualname: Option, // u_qualname + pub consts: IndexSet, // u_consts + pub names: IndexSet, // u_names + pub varnames: IndexSet, // u_varnames + pub cellvars: IndexSet, // u_cellvars + pub freevars: IndexSet, // u_freevars + pub fast_hidden: IndexMap, // u_fast_hidden + pub argcount: u32, // u_argcount + pub posonlyargcount: u32, // u_posonlyargcount + pub kwonlyargcount: u32, // u_kwonlyargcount + pub firstlineno: OneIndexed, // u_firstlineno +} // use rustpython_parser_core::source_code::{LineNumber, SourceLocation}; #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -67,22 +85,22 @@ impl Default for Block { pub struct CodeInfo { pub flags: CodeFlags, - pub posonlyarg_count: u32, // Number of positional-only arguments - pub arg_count: u32, - pub kwonlyarg_count: u32, pub source_path: String, - pub first_line_number: OneIndexed, - pub obj_name: String, // Name of the object that created this code object - pub qualname: Option, // Qualified name of the object pub private: Option, // For private name mangling, mostly for class pub blocks: Vec, pub current_block: BlockIdx, - pub constants: IndexSet, - pub name_cache: IndexSet, - pub varname_cache: IndexSet, - pub cellvar_cache: IndexSet, - pub freevar_cache: IndexSet, + + pub metadata: CodeUnitMetadata, + + // For class scopes: attributes accessed via self.X + pub static_attributes: Option>, + + // True if compiling an inlined comprehension + pub in_inlined_comp: bool, + + // Block stack for tracking nested control structures + pub fblock: Vec, } impl CodeInfo { pub fn finalize_code(mut self, optimize: u8) -> crate::InternalResult { @@ -95,24 +113,32 @@ impl CodeInfo { let Self { flags, - posonlyarg_count, - arg_count, - kwonlyarg_count, source_path, - first_line_number, - obj_name, - qualname, private: _, // private is only used during compilation mut blocks, current_block: _, - constants, - name_cache, - varname_cache, - cellvar_cache, - freevar_cache, + metadata, + static_attributes: _, + in_inlined_comp: _, + fblock: _, } = self; + let CodeUnitMetadata { + name: obj_name, + qualname, + consts: constants, + names: name_cache, + varnames: varname_cache, + cellvars: cellvar_cache, + freevars: freevar_cache, + fast_hidden: _, + argcount: arg_count, + posonlyargcount: posonlyarg_count, + kwonlyargcount: kwonlyarg_count, + firstlineno: first_line_number, + } = metadata; + let mut instructions = Vec::new(); let mut locations = Vec::new(); @@ -182,21 +208,23 @@ impl CodeInfo { } fn cell2arg(&self) -> Option> { - if self.cellvar_cache.is_empty() { + if self.metadata.cellvars.is_empty() { return None; } - let total_args = self.arg_count - + self.kwonlyarg_count + let total_args = self.metadata.argcount + + self.metadata.kwonlyargcount + self.flags.contains(CodeFlags::HAS_VARARGS) as u32 + self.flags.contains(CodeFlags::HAS_VARKEYWORDS) as u32; let mut found_cellarg = false; let cell2arg = self - .cellvar_cache + .metadata + .cellvars .iter() .map(|var| { - self.varname_cache + self.metadata + .varnames .get_index_of(var) // check that it's actually an arg .filter(|i| *i < total_args as usize) @@ -302,18 +330,19 @@ impl CodeInfo { impl InstrDisplayContext for CodeInfo { type Constant = ConstantData; fn get_constant(&self, i: usize) -> &ConstantData { - &self.constants[i] + &self.metadata.consts[i] } fn get_name(&self, i: usize) -> &str { - self.name_cache[i].as_ref() + self.metadata.names[i].as_ref() } fn get_varname(&self, i: usize) -> &str { - self.varname_cache[i].as_ref() + self.metadata.varnames[i].as_ref() } fn get_cell_name(&self, i: usize) -> &str { - self.cellvar_cache + self.metadata + .cellvars .get_index(i) - .unwrap_or_else(|| &self.freevar_cache[i - self.cellvar_cache.len()]) + .unwrap_or_else(|| &self.metadata.freevars[i - self.metadata.cellvars.len()]) .as_ref() } } diff --git a/compiler/codegen/src/symboltable.rs b/compiler/codegen/src/symboltable.rs index 66dbff326ad..52b6bae6449 100644 --- a/compiler/codegen/src/symboltable.rs +++ b/compiler/codegen/src/symboltable.rs @@ -45,6 +45,9 @@ pub struct SymbolTable { /// A list of sub-scopes in the order as found in the /// AST nodes. pub sub_tables: Vec, + + /// Variable names in definition order (parameters first, then locals) + pub varnames: Vec, } impl SymbolTable { @@ -56,6 +59,7 @@ impl SymbolTable { is_nested, symbols: IndexMap::default(), sub_tables: vec![], + varnames: Vec::new(), } } @@ -573,6 +577,8 @@ struct SymbolTableBuilder<'src> { tables: Vec, future_annotations: bool, source_code: SourceCode<'src>, + // Current scope's varnames being collected (temporary storage) + current_varnames: Vec, } /// Enum to indicate in what mode an expression @@ -595,6 +601,7 @@ impl<'src> SymbolTableBuilder<'src> { tables: vec![], future_annotations: false, source_code, + current_varnames: Vec::new(), }; this.enter_scope("top", SymbolTableType::Module, 0); this @@ -605,6 +612,8 @@ impl SymbolTableBuilder<'_> { fn finish(mut self) -> Result { assert_eq!(self.tables.len(), 1); let mut symbol_table = self.tables.pop().unwrap(); + // Save varnames for the top-level module scope + symbol_table.varnames = self.current_varnames; analyze_symbol_table(&mut symbol_table)?; Ok(symbol_table) } @@ -617,11 +626,15 @@ impl SymbolTableBuilder<'_> { .unwrap_or(false); let table = SymbolTable::new(name.to_owned(), typ, line_number, is_nested); self.tables.push(table); + // Clear current_varnames for the new scope + self.current_varnames.clear(); } /// Pop symbol table and add to sub table of parent table. fn leave_scope(&mut self) { - let table = self.tables.pop().unwrap(); + let mut table = self.tables.pop().unwrap(); + // Save the collected varnames to the symbol table + table.varnames = std::mem::take(&mut self.current_varnames); self.tables.last_mut().unwrap().sub_tables.push(table); } @@ -1533,18 +1546,43 @@ impl SymbolTableBuilder<'_> { } SymbolUsage::Parameter => { flags.insert(SymbolFlags::PARAMETER); + // Parameters are always added to varnames first + let name_str = symbol.name.clone(); + if !self.current_varnames.contains(&name_str) { + self.current_varnames.push(name_str); + } } SymbolUsage::AnnotationParameter => { flags.insert(SymbolFlags::PARAMETER | SymbolFlags::ANNOTATED); + // Annotated parameters are also added to varnames + let name_str = symbol.name.clone(); + if !self.current_varnames.contains(&name_str) { + self.current_varnames.push(name_str); + } } SymbolUsage::AnnotationAssigned => { flags.insert(SymbolFlags::ASSIGNED | SymbolFlags::ANNOTATED); } SymbolUsage::Assigned => { flags.insert(SymbolFlags::ASSIGNED); + // Local variables (assigned) are added to varnames if they are local scope + // and not already in varnames + if symbol.scope == SymbolScope::Local { + let name_str = symbol.name.clone(); + if !self.current_varnames.contains(&name_str) { + self.current_varnames.push(name_str); + } + } } SymbolUsage::AssignedNamedExprInComprehension => { flags.insert(SymbolFlags::ASSIGNED | SymbolFlags::ASSIGNED_IN_COMPREHENSION); + // Named expressions in comprehensions might also be locals + if symbol.scope == SymbolScope::Local { + let name_str = symbol.name.clone(); + if !self.current_varnames.contains(&name_str) { + self.current_varnames.push(name_str); + } + } } SymbolUsage::Global => { symbol.scope = SymbolScope::GlobalExplicit; From f19478edecba5355e827a7bf1fc40da6b85180f8 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 11 Jul 2025 19:10:30 +0300 Subject: [PATCH 021/819] Update operator from 3.13.5 (#5935) --- Lib/operator.py | 10 ++--- Lib/test/test_operator.py | 88 +++++++++++++++++++++++++++++++++++++++ vm/src/stdlib/operator.rs | 9 ++-- 3 files changed, 98 insertions(+), 9 deletions(-) diff --git a/Lib/operator.py b/Lib/operator.py index 30116c1189a..02ccdaa13dd 100644 --- a/Lib/operator.py +++ b/Lib/operator.py @@ -239,7 +239,7 @@ class attrgetter: """ __slots__ = ('_attrs', '_call') - def __init__(self, attr, *attrs): + def __init__(self, attr, /, *attrs): if not attrs: if not isinstance(attr, str): raise TypeError('attribute name must be a string') @@ -257,7 +257,7 @@ def func(obj): return tuple(getter(obj) for getter in getters) self._call = func - def __call__(self, obj): + def __call__(self, obj, /): return self._call(obj) def __repr__(self): @@ -276,7 +276,7 @@ class itemgetter: """ __slots__ = ('_items', '_call') - def __init__(self, item, *items): + def __init__(self, item, /, *items): if not items: self._items = (item,) def func(obj): @@ -288,7 +288,7 @@ def func(obj): return tuple(obj[i] for i in items) self._call = func - def __call__(self, obj): + def __call__(self, obj, /): return self._call(obj) def __repr__(self): @@ -315,7 +315,7 @@ def __init__(self, name, /, *args, **kwargs): self._args = args self._kwargs = kwargs - def __call__(self, obj): + def __call__(self, obj, /): return getattr(obj, self._name)(*self._args, **self._kwargs) def __repr__(self): diff --git a/Lib/test/test_operator.py b/Lib/test/test_operator.py index 1db738d228b..05b7a7462db 100644 --- a/Lib/test/test_operator.py +++ b/Lib/test/test_operator.py @@ -1,6 +1,9 @@ import unittest +import inspect import pickle import sys +from decimal import Decimal +from fractions import Fraction from test import support from test.support import import_helper @@ -508,6 +511,44 @@ def __getitem__(self, other): return 5 # so that C is a sequence self.assertEqual(operator.ixor (c, 5), "ixor") self.assertEqual(operator.iconcat (c, c), "iadd") + def test_iconcat_without_getitem(self): + operator = self.module + + msg = "'int' object can't be concatenated" + with self.assertRaisesRegex(TypeError, msg): + operator.iconcat(1, 0.5) + + def test_index(self): + operator = self.module + class X: + def __index__(self): + return 1 + + self.assertEqual(operator.index(X()), 1) + self.assertEqual(operator.index(0), 0) + self.assertEqual(operator.index(1), 1) + self.assertEqual(operator.index(2), 2) + with self.assertRaises((AttributeError, TypeError)): + operator.index(1.5) + with self.assertRaises((AttributeError, TypeError)): + operator.index(Fraction(3, 7)) + with self.assertRaises((AttributeError, TypeError)): + operator.index(Decimal(1)) + with self.assertRaises((AttributeError, TypeError)): + operator.index(None) + + def test_not_(self): + operator = self.module + class C: + def __bool__(self): + raise SyntaxError + self.assertRaises(TypeError, operator.not_) + self.assertRaises(SyntaxError, operator.not_, C()) + self.assertFalse(operator.not_(5)) + self.assertFalse(operator.not_([0])) + self.assertTrue(operator.not_(0)) + self.assertTrue(operator.not_([])) + def test_length_hint(self): operator = self.module class X(object): @@ -533,6 +574,13 @@ def __length_hint__(self): with self.assertRaises(LookupError): operator.length_hint(X(LookupError)) + class Y: pass + + msg = "'str' object cannot be interpreted as an integer" + with self.assertRaisesRegex(TypeError, msg): + operator.length_hint(X(2), "abc") + self.assertEqual(operator.length_hint(Y(), 10), 10) + def test_call(self): operator = self.module @@ -555,6 +603,31 @@ def test_dunder_is_original(self): if dunder: self.assertIs(dunder, orig) + @support.requires_docstrings + def test_attrgetter_signature(self): + operator = self.module + sig = inspect.signature(operator.attrgetter) + self.assertEqual(str(sig), '(attr, /, *attrs)') + sig = inspect.signature(operator.attrgetter('x', 'z', 'y')) + self.assertEqual(str(sig), '(obj, /)') + + @support.requires_docstrings + def test_itemgetter_signature(self): + operator = self.module + sig = inspect.signature(operator.itemgetter) + self.assertEqual(str(sig), '(item, /, *items)') + sig = inspect.signature(operator.itemgetter(2, 3, 5)) + self.assertEqual(str(sig), '(obj, /)') + + @support.requires_docstrings + def test_methodcaller_signature(self): + operator = self.module + sig = inspect.signature(operator.methodcaller) + self.assertEqual(str(sig), '(name, /, *args, **kwargs)') + sig = inspect.signature(operator.methodcaller('foo', 2, y=3)) + self.assertEqual(str(sig), '(obj, /)') + + class PyOperatorTestCase(OperatorTestCase, unittest.TestCase): module = py_operator @@ -562,6 +635,21 @@ class PyOperatorTestCase(OperatorTestCase, unittest.TestCase): class COperatorTestCase(OperatorTestCase, unittest.TestCase): module = c_operator + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_attrgetter_signature(self): + super().test_attrgetter_signature() + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_itemgetter_signature(self): + super().test_itemgetter_signature() + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_methodcaller_signature(self): + super().test_methodcaller_signature() + class OperatorPickleTestCase: def copy(self, obj, proto): diff --git a/vm/src/stdlib/operator.rs b/vm/src/stdlib/operator.rs index c33b9a47b0b..0d5a3092014 100644 --- a/vm/src/stdlib/operator.rs +++ b/vm/src/stdlib/operator.rs @@ -225,7 +225,7 @@ mod _operator { .map(|v| { if !v.fast_isinstance(vm.ctx.types.int_type) { return Err(vm.new_type_error(format!( - "'{}' type cannot be interpreted as an integer", + "'{}' object cannot be interpreted as an integer", v.class().name() ))); } @@ -253,9 +253,10 @@ mod _operator { if !a.class().has_attr(identifier!(vm, __getitem__)) || a.fast_isinstance(vm.ctx.types.dict_type) { - return Err( - vm.new_type_error(format!("{} object can't be concatenated", a.class().name())) - ); + return Err(vm.new_type_error(format!( + "'{}' object can't be concatenated", + a.class().name() + ))); } vm._iadd(&a, &b) } From 3f9a5fddbbe2c29b843e57557812cefd5272180f Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 12 Jul 2025 13:18:08 +0300 Subject: [PATCH 022/819] Don't skip non hanging test (#5951) --- Lib/test/test_contains.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_contains.py b/Lib/test/test_contains.py index c533311572e..471d04a76ca 100644 --- a/Lib/test/test_contains.py +++ b/Lib/test/test_contains.py @@ -36,7 +36,6 @@ def test_common_tests(self): self.assertRaises(TypeError, lambda: None in 'abc') - @unittest.skip("TODO: RUSTPYTHON, hangs") def test_builtin_sequence_types(self): # a collection of tests on builtin sequence types a = range(10) From 1303ace453192996a6cfe649ee34c63cd6129474 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 12 Jul 2025 13:18:31 +0300 Subject: [PATCH 023/819] Update textwrap from 3.13.5 (#5952) --- Lib/textwrap.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 841de9baecf..7ca393d1c37 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -63,10 +63,7 @@ class TextWrapper: Append to the last line of truncated text. """ - unicode_whitespace_trans = {} - uspace = ord(' ') - for x in _whitespace: - unicode_whitespace_trans[ord(x)] = uspace + unicode_whitespace_trans = dict.fromkeys(map(ord, _whitespace), ord(' ')) # This funky little regex is just the trick for splitting # text up into word-wrappable chunks. E.g. @@ -479,13 +476,19 @@ def indent(text, prefix, predicate=None): consist solely of whitespace characters. """ if predicate is None: - def predicate(line): - return line.strip() - - def prefixed_lines(): - for line in text.splitlines(True): - yield (prefix + line if predicate(line) else line) - return ''.join(prefixed_lines()) + # str.splitlines(True) doesn't produce empty string. + # ''.splitlines(True) => [] + # 'foo\n'.splitlines(True) => ['foo\n'] + # So we can use just `not s.isspace()` here. + predicate = lambda s: not s.isspace() + + prefixed_lines = [] + for line in text.splitlines(True): + if predicate(line): + prefixed_lines.append(prefix) + prefixed_lines.append(line) + + return ''.join(prefixed_lines) if __name__ == "__main__": From 3ef0cfc50c3b6fa5a1508f69a3a5408984d46db9 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 12 Jul 2025 19:28:22 +0900 Subject: [PATCH 024/819] compiler enter_scope (#5950) * enter_scope * drop_class_free * push_output based on enter_scope --- compiler/codegen/src/compile.rs | 231 ++++++++++++++++++++++------ compiler/codegen/src/symboltable.rs | 38 +++++ 2 files changed, 219 insertions(+), 50 deletions(-) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 6b8007c1519..14495499c5e 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -176,7 +176,7 @@ pub fn compile_program( .map_err(|e| e.into_codegen_error(source_code.path.to_owned()))?; let mut compiler = Compiler::new(opts, source_code, "".to_owned()); compiler.compile_program(ast, symbol_table)?; - let code = compiler.pop_code_object(); + let code = compiler.exit_scope(); trace!("Compilation completed: {code:?}"); Ok(code) } @@ -191,7 +191,7 @@ pub fn compile_program_single( .map_err(|e| e.into_codegen_error(source_code.path.to_owned()))?; let mut compiler = Compiler::new(opts, source_code, "".to_owned()); compiler.compile_program_single(&ast.body, symbol_table)?; - let code = compiler.pop_code_object(); + let code = compiler.exit_scope(); trace!("Compilation completed: {code:?}"); Ok(code) } @@ -205,7 +205,7 @@ pub fn compile_block_expression( .map_err(|e| e.into_codegen_error(source_code.path.to_owned()))?; let mut compiler = Compiler::new(opts, source_code, "".to_owned()); compiler.compile_block_expr(&ast.body, symbol_table)?; - let code = compiler.pop_code_object(); + let code = compiler.exit_scope(); trace!("Compilation completed: {code:?}"); Ok(code) } @@ -219,7 +219,7 @@ pub fn compile_expression( .map_err(|e| e.into_codegen_error(source_code.path.to_owned()))?; let mut compiler = Compiler::new(opts, source_code, "".to_owned()); compiler.compile_eval(ast, symbol_table)?; - let code = compiler.pop_code_object(); + let code = compiler.exit_scope(); Ok(code) } @@ -404,55 +404,121 @@ impl Compiler<'_> { self.symbol_table_stack.pop().expect("compiler bug") } - fn push_output( + /// Enter a new scope + // = compiler_enter_scope + fn enter_scope( &mut self, - flags: bytecode::CodeFlags, - posonlyarg_count: u32, - arg_count: u32, - kwonlyarg_count: u32, - obj_name: String, - ) { + name: &str, + scope_type: SymbolTableType, + key: usize, // In RustPython, we use the index in symbol_table_stack as key + lineno: u32, + ) -> CompileResult<()> { + // Create location + let location = ruff_source_file::SourceLocation { + row: OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN), + column: OneIndexed::new(1).unwrap(), + }; + + // Allocate a new compiler unit + + // In Rust, we'll create the structure directly let source_path = self.source_code.path.to_owned(); - let first_line_number = self.get_source_line_number(); - // Get the private name from current scope if exists - let private = self.code_stack.last().and_then(|info| info.private.clone()); + // Lookup symbol table entry using key (_PySymtable_Lookup) + let ste = if key < self.symbol_table_stack.len() { + &self.symbol_table_stack[key] + } else { + return Err(self.error(CodegenErrorType::SyntaxError( + "unknown symbol table entry".to_owned(), + ))); + }; - let table = self.push_symbol_table(); + // Use varnames from symbol table (already collected in definition order) + let varname_cache: IndexSet = ste.varnames.iter().cloned().collect(); - let cellvar_cache = table + // Build cellvars using dictbytype (CELL scope, sorted) + let mut cellvar_cache = IndexSet::default(); + let mut cell_names: Vec<_> = ste .symbols .iter() .filter(|(_, s)| s.scope == SymbolScope::Cell) - .map(|(var, _)| var.clone()) + .map(|(name, _)| name.clone()) .collect(); - let freevar_cache = table + cell_names.sort(); + for name in cell_names { + cellvar_cache.insert(name); + } + + // Handle implicit __class__ cell if needed + if ste.needs_class_closure { + // Cook up an implicit __class__ cell + debug_assert_eq!(scope_type, SymbolTableType::Class); + cellvar_cache.insert("__class__".to_string()); + } + + // Handle implicit __classdict__ cell if needed + if ste.needs_classdict { + // Cook up an implicit __classdict__ cell + debug_assert_eq!(scope_type, SymbolTableType::Class); + cellvar_cache.insert("__classdict__".to_string()); + } + + // Build freevars using dictbytype (FREE scope, offset by cellvars size) + let mut freevar_cache = IndexSet::default(); + let mut free_names: Vec<_> = ste .symbols .iter() .filter(|(_, s)| { s.scope == SymbolScope::Free || s.flags.contains(SymbolFlags::FREE_CLASS) }) - .map(|(var, _)| var.clone()) + .map(|(name, _)| name.clone()) .collect(); + free_names.sort(); + for name in free_names { + freevar_cache.insert(name); + } + + // Initialize u_metadata fields + let (flags, posonlyarg_count, arg_count, kwonlyarg_count) = match scope_type { + SymbolTableType::Module => (bytecode::CodeFlags::empty(), 0, 0, 0), + SymbolTableType::Class => (bytecode::CodeFlags::empty(), 0, 0, 0), + SymbolTableType::Function | SymbolTableType::Lambda => ( + bytecode::CodeFlags::NEW_LOCALS | bytecode::CodeFlags::IS_OPTIMIZED, + 0, // Will be set later in enter_function + 0, // Will be set later in enter_function + 0, // Will be set later in enter_function + ), + SymbolTableType::Comprehension => ( + bytecode::CodeFlags::NEW_LOCALS | bytecode::CodeFlags::IS_OPTIMIZED, + 0, + 1, // comprehensions take one argument (.0) + 0, + ), + SymbolTableType::TypeParams => ( + bytecode::CodeFlags::NEW_LOCALS | bytecode::CodeFlags::IS_OPTIMIZED, + 0, + 0, + 0, + ), + }; - // Initialize varname_cache from SymbolTable::varnames - let varname_cache: IndexSet = table.varnames.iter().cloned().collect(); - - // Qualname will be set later by set_qualname - let qualname = None; - - // Check if this is a class scope - let is_class_scope = table.typ == SymbolTableType::Class; + // Get private name from parent scope + let private = if !self.code_stack.is_empty() { + self.code_stack.last().unwrap().private.clone() + } else { + None + }; - let info = ir::CodeInfo { + // Create the new compilation unit + let code_info = ir::CodeInfo { flags, - source_path, + source_path: source_path.clone(), private, blocks: vec![ir::Block::default()], - current_block: ir::BlockIdx(0), + current_block: BlockIdx(0), metadata: ir::CodeUnitMetadata { - name: obj_name, - qualname, + name: name.to_owned(), + qualname: None, // Will be set below consts: IndexSet::default(), names: IndexSet::default(), varnames: varname_cache, @@ -462,9 +528,9 @@ impl Compiler<'_> { argcount: arg_count, posonlyargcount: posonlyarg_count, kwonlyargcount: kwonlyarg_count, - firstlineno: first_line_number, + firstlineno: OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN), }, - static_attributes: if is_class_scope { + static_attributes: if scope_type == SymbolTableType::Class { Some(IndexSet::default()) } else { None @@ -472,10 +538,83 @@ impl Compiler<'_> { in_inlined_comp: false, fblock: Vec::with_capacity(MAXBLOCKS), }; - self.code_stack.push(info); + + // Push the old compiler unit on the stack (like PyCapsule) + // This happens before setting qualname + self.code_stack.push(code_info); + + // Set qualname after pushing (uses compiler_set_qualname logic) + if scope_type != SymbolTableType::Module { + self.set_qualname(); + } + + // Emit RESUME instruction + let _resume_loc = if scope_type == SymbolTableType::Module { + // Module scope starts with lineno 0 + ruff_source_file::SourceLocation { + row: OneIndexed::MIN, + column: OneIndexed::MIN, + } + } else { + location + }; + + // Set the source range for the RESUME instruction + // For now, just use an empty range at the beginning + self.current_source_range = TextRange::default(); + emit!( + self, + Instruction::Resume { + arg: bytecode::ResumeType::AtFuncStart as u32 + } + ); + + if scope_type == SymbolTableType::Module { + // This would be loc.lineno = -1 in CPython + // We handle this differently in RustPython + } + + Ok(()) + } + + fn push_output( + &mut self, + flags: bytecode::CodeFlags, + posonlyarg_count: u32, + arg_count: u32, + kwonlyarg_count: u32, + obj_name: String, + ) { + // First push the symbol table + let table = self.push_symbol_table(); + let scope_type = table.typ; + + // The key is the current position in the symbol table stack + let key = self.symbol_table_stack.len() - 1; + + // Get the line number + let lineno = self.get_source_line_number().get(); + + // Call enter_scope which does most of the work + if let Err(e) = self.enter_scope(&obj_name, scope_type, key, lineno.to_u32()) { + // In the current implementation, push_output doesn't return an error, + // so we panic here. This maintains the same behavior. + panic!("enter_scope failed: {e:?}"); + } + + // Override the values that push_output sets explicitly + // enter_scope sets default values based on scope_type, but push_output + // allows callers to specify exact values + if let Some(info) = self.code_stack.last_mut() { + info.flags = flags; + info.metadata.argcount = arg_count; + info.metadata.posonlyargcount = posonlyarg_count; + info.metadata.kwonlyargcount = kwonlyarg_count; + } } - fn pop_code_object(&mut self) -> CodeObject { + // compiler_exit_scope + fn exit_scope(&mut self) -> CodeObject { let table = self.pop_symbol_table(); assert!(table.sub_tables.is_empty()); let pop = self.code_stack.pop(); @@ -755,7 +894,7 @@ impl Compiler<'_> { } fn mangle<'a>(&self, name: &'a str) -> Cow<'a, str> { - // Use u_private from current code unit for name mangling + // Use private from current code unit for name mangling let private = self .code_stack .last() @@ -1758,14 +1897,6 @@ impl Compiler<'_> { .consts .insert_full(ConstantData::None); - // Emit RESUME instruction at function start - emit!( - self, - Instruction::Resume { - arg: bytecode::ResumeType::AtFuncStart as u32 - } - ); - self.compile_statements(body)?; // Emit None at end: @@ -1778,7 +1909,7 @@ impl Compiler<'_> { } } - let code = self.pop_code_object(); + let code = self.exit_scope(); self.ctx = prev_ctx; // Prepare generic type parameters: @@ -2030,7 +2161,7 @@ impl Compiler<'_> { self.emit_return_value(); - let code = self.pop_code_object(); + let code = self.exit_scope(); self.ctx = prev_ctx; emit!(self, Instruction::LoadBuildClass); @@ -3820,7 +3951,7 @@ impl Compiler<'_> { self.compile_expression(body)?; self.emit_return_value(); - let code = self.pop_code_object(); + let code = self.exit_scope(); if self.build_closure(&code) { func_flags |= bytecode::MakeFunctionFlags::CLOSURE; } @@ -4369,7 +4500,7 @@ impl Compiler<'_> { self.emit_return_value(); // Fetch code for listcomp function: - let code = self.pop_code_object(); + let code = self.exit_scope(); self.ctx = prev_ctx; @@ -5076,7 +5207,7 @@ mod tests { .unwrap(); let mut compiler = Compiler::new(opts, source_code, "".to_owned()); compiler.compile_program(&ast, symbol_table).unwrap(); - compiler.pop_code_object() + compiler.exit_scope() } macro_rules! assert_dis_snapshot { diff --git a/compiler/codegen/src/symboltable.rs b/compiler/codegen/src/symboltable.rs index 52b6bae6449..16a65bca116 100644 --- a/compiler/codegen/src/symboltable.rs +++ b/compiler/codegen/src/symboltable.rs @@ -48,6 +48,12 @@ pub struct SymbolTable { /// Variable names in definition order (parameters first, then locals) pub varnames: Vec, + + /// Whether this class scope needs an implicit __class__ cell + pub needs_class_closure: bool, + + /// Whether this class scope needs an implicit __classdict__ cell + pub needs_classdict: bool, } impl SymbolTable { @@ -60,6 +66,8 @@ impl SymbolTable { symbols: IndexMap::default(), sub_tables: vec![], varnames: Vec::new(), + needs_class_closure: false, + needs_classdict: false, } } @@ -228,6 +236,30 @@ fn analyze_symbol_table(symbol_table: &mut SymbolTable) -> SymbolTableResult { analyzer.analyze_symbol_table(symbol_table) } +/* Drop __class__ and __classdict__ from free variables in class scope + and set the appropriate flags. Equivalent to CPython's drop_class_free(). + See: https://github.com/python/cpython/blob/main/Python/symtable.c#L884 +*/ +fn drop_class_free(symbol_table: &mut SymbolTable) { + // Check if __class__ is used as a free variable + if let Some(class_symbol) = symbol_table.symbols.get("__class__") { + if class_symbol.scope == SymbolScope::Free { + symbol_table.needs_class_closure = true; + // Note: In CPython, the symbol is removed from the free set, + // but in RustPython we handle this differently during code generation + } + } + + // Check if __classdict__ is used as a free variable + if let Some(classdict_symbol) = symbol_table.symbols.get("__classdict__") { + if classdict_symbol.scope == SymbolScope::Free { + symbol_table.needs_classdict = true; + // Note: In CPython, the symbol is removed from the free set, + // but in RustPython we handle this differently during code generation + } + } +} + type SymbolMap = IndexMap; mod stack { @@ -314,6 +346,12 @@ impl SymbolTableAnalyzer { for symbol in symbol_table.symbols.values_mut() { self.analyze_symbol(symbol, symbol_table.typ, sub_tables)?; } + + // Handle class-specific implicit cells (like CPython) + if symbol_table.typ == SymbolTableType::Class { + drop_class_free(symbol_table); + } + Ok(()) } From fef660e6b3d035e8463b6d66de9bb37ddd184184 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 12 Jul 2025 20:42:57 +0900 Subject: [PATCH 025/819] more PEP695 (#5917) * compile_class_body * type.__orig_bases__ regression of test_all_exported_names * rework type_params scope * refactor compile_class_def --- Lib/test/test_descr.py | 2 - Lib/test/test_typing.py | 4 - compiler/codegen/src/compile.rs | 248 ++++++++++++++++++++++---------- vm/src/builtins/genericalias.rs | 10 +- 4 files changed, 177 insertions(+), 87 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index d1c83cc337a..b0414d5b00d 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -5124,8 +5124,6 @@ def test_iter_keys(self): self.assertEqual(keys, ['__dict__', '__doc__', '__module__', '__weakref__', 'meth']) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 'trace function introduces __local__') def test_iter_values(self): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index d0fe1b0188c..96fddfce030 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6934,8 +6934,6 @@ class Y(Generic[T], NamedTuple): with self.assertRaises(TypeError): G[int, str] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_generic_pep695(self): class X[T](NamedTuple): x: T @@ -7560,8 +7558,6 @@ class FooBarGeneric(BarGeneric[int]): {'a': typing.Optional[T], 'b': int, 'c': str} ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_pep695_generic_typeddict(self): class A[T](TypedDict): a: T diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 14495499c5e..0bfe4adb416 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -2069,39 +2069,20 @@ impl Compiler<'_> { false } - fn compile_class_def( + /// Compile the class body into a code object + /// This is similar to CPython's compiler_class_body + fn compile_class_body( &mut self, name: &str, body: &[Stmt], - decorator_list: &[Decorator], type_params: Option<&TypeParams>, - arguments: Option<&Arguments>, - ) -> CompileResult<()> { - self.prepare_decorators(decorator_list)?; - - let prev_ctx = self.ctx; - self.ctx = CompileContext { - func: FunctionContext::NoFunction, - in_class: true, - loop_data: None, - }; - - // If there are type params, we need to push a special symbol table just for them - if let Some(type_params) = type_params { - self.push_symbol_table(); - // Save current private name to restore later - let saved_private = self.code_stack.last().and_then(|info| info.private.clone()); - // Compile type parameters and store as .type_params - self.compile_type_params(type_params)?; - // Restore private name after type param scope - if let Some(private) = saved_private { - self.code_stack.last_mut().unwrap().private = Some(private); - } - let dot_type_params = self.name(".type_params"); - emit!(self, Instruction::StoreLocal(dot_type_params)); - } - - self.push_output(bytecode::CodeFlags::empty(), 0, 0, 0, name.to_owned()); + firstlineno: u32, + ) -> CompileResult { + // 1. Enter class scope + // Use enter_scope instead of push_output to match CPython + let key = self.symbol_table_stack.len(); + self.push_symbol_table(); + self.enter_scope(name, SymbolTableType::Class, key, firstlineno)?; // Set qualname using the new method let qualname = self.set_qualname(); @@ -2109,26 +2090,35 @@ impl Compiler<'_> { // For class scopes, set u_private to the class name for name mangling self.code_stack.last_mut().unwrap().private = Some(name.to_owned()); + // 2. Set up class namespace let (doc_str, body) = split_doc(body, &self.opts); + // Load (global) __name__ and store as __module__ let dunder_name = self.name("__name__"); emit!(self, Instruction::LoadGlobal(dunder_name)); let dunder_module = self.name("__module__"); emit!(self, Instruction::StoreLocal(dunder_module)); + + // Store __qualname__ self.emit_load_const(ConstantData::Str { value: qualname.into(), }); let qualname_name = self.name("__qualname__"); emit!(self, Instruction::StoreLocal(qualname_name)); + + // Store __doc__ self.load_docstring(doc_str); let doc = self.name("__doc__"); emit!(self, Instruction::StoreLocal(doc)); - // setup annotations - if Self::find_ann(body) { - emit!(self, Instruction::SetupAnnotation); - } - // Set __type_params__ from .type_params if we have type parameters (PEP 695) + // Store __firstlineno__ (new in Python 3.12+) + self.emit_load_const(ConstantData::Integer { + value: BigInt::from(firstlineno), + }); + let firstlineno_name = self.name("__firstlineno__"); + emit!(self, Instruction::StoreLocal(firstlineno_name)); + + // Set __type_params__ if we have type parameters if type_params.is_some() { // Load .type_params from enclosing scope let dot_type_params = self.name(".type_params"); @@ -2139,8 +2129,15 @@ impl Compiler<'_> { emit!(self, Instruction::StoreLocal(dunder_type_params)); } + // Setup annotations if needed + if Self::find_ann(body) { + emit!(self, Instruction::SetupAnnotation); + } + + // 3. Compile the class body self.compile_statements(body)?; + // 4. Handle __classcell__ if needed let classcell_idx = self .code_stack .last_mut() @@ -2159,65 +2156,167 @@ impl Compiler<'_> { self.emit_load_const(ConstantData::None); } + // Return the class namespace self.emit_return_value(); - let code = self.exit_scope(); - self.ctx = prev_ctx; + // Exit scope and return the code object + Ok(self.exit_scope()) + } + + fn compile_class_def( + &mut self, + name: &str, + body: &[Stmt], + decorator_list: &[Decorator], + type_params: Option<&TypeParams>, + arguments: Option<&Arguments>, + ) -> CompileResult<()> { + self.prepare_decorators(decorator_list)?; - emit!(self, Instruction::LoadBuildClass); + let is_generic = type_params.is_some(); + let firstlineno = self.get_source_line_number().get().to_u32(); - let mut func_flags = bytecode::MakeFunctionFlags::empty(); + // Step 1: If generic, enter type params scope and compile type params + if is_generic { + let type_params_name = format!(""); + self.push_output( + bytecode::CodeFlags::IS_OPTIMIZED | bytecode::CodeFlags::NEW_LOCALS, + 0, + 0, + 0, + type_params_name, + ); - // Prepare generic type parameters: - if type_params.is_some() { - // Load .type_params from the type params scope + // Set private name for name mangling + self.code_stack.last_mut().unwrap().private = Some(name.to_owned()); + + // Compile type parameters and store as .type_params + self.compile_type_params(type_params.unwrap())?; let dot_type_params = self.name(".type_params"); - emit!(self, Instruction::LoadNameAny(dot_type_params)); - func_flags |= bytecode::MakeFunctionFlags::TYPE_PARAMS; + emit!(self, Instruction::StoreLocal(dot_type_params)); } - if self.build_closure(&code) { - func_flags |= bytecode::MakeFunctionFlags::CLOSURE; - } + // Step 2: Compile class body (always done, whether generic or not) + let prev_ctx = self.ctx; + self.ctx = CompileContext { + func: FunctionContext::NoFunction, + in_class: true, + loop_data: None, + }; + let class_code = self.compile_class_body(name, body, type_params, firstlineno)?; + self.ctx = prev_ctx; - self.emit_load_const(ConstantData::Code { - code: Box::new(code), - }); - self.emit_load_const(ConstantData::Str { value: name.into() }); + // Step 3: Generate the rest of the code for the call + if is_generic { + // Still in type params scope + let dot_type_params = self.name(".type_params"); + let dot_generic_base = self.name(".generic_base"); - // Turn code object into function object: - emit!(self, Instruction::MakeFunction(func_flags)); + // Create .generic_base + emit!(self, Instruction::LoadNameAny(dot_type_params)); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::SubscriptGeneric + } + ); + emit!(self, Instruction::StoreLocal(dot_generic_base)); - self.emit_load_const(ConstantData::Str { value: name.into() }); + // Generate class creation code + emit!(self, Instruction::LoadBuildClass); - // For PEP 695 classes: handle Generic base creation - if type_params.is_some() { - if let Some(arguments) = arguments { - // Has explicit bases - use them as is, don't add Generic - // CPython doesn't add Generic when explicit bases are present - let call = self.compile_call_inner(2, arguments)?; - self.compile_normal_call(call); + // Set up the class function with type params + let mut func_flags = bytecode::MakeFunctionFlags::empty(); + emit!(self, Instruction::LoadNameAny(dot_type_params)); + func_flags |= bytecode::MakeFunctionFlags::TYPE_PARAMS; + + if self.build_closure(&class_code) { + func_flags |= bytecode::MakeFunctionFlags::CLOSURE; + } + + self.emit_load_const(ConstantData::Code { + code: Box::new(class_code), + }); + self.emit_load_const(ConstantData::Str { value: name.into() }); + emit!(self, Instruction::MakeFunction(func_flags)); + self.emit_load_const(ConstantData::Str { value: name.into() }); + + // Compile original bases + let base_count = if let Some(arguments) = arguments { + for arg in &arguments.args { + self.compile_expression(arg)?; + } + arguments.args.len() } else { - // No explicit bases, add Generic[*type_params] as the only base - // Stack currently: [function, class_name] + 0 + }; + + // Load .generic_base as the last base + emit!(self, Instruction::LoadNameAny(dot_generic_base)); - // Load .type_params for creating Generic base - let dot_type_params = self.name(".type_params"); - emit!(self, Instruction::LoadNameAny(dot_type_params)); + let nargs = 2 + u32::try_from(base_count).expect("too many base classes") + 1; // function, name, bases..., generic_base - // Call INTRINSIC_SUBSCRIPT_GENERIC to create Generic[*type_params] + // Handle keyword arguments + if let Some(arguments) = arguments + && !arguments.keywords.is_empty() + { + for keyword in &arguments.keywords { + if let Some(name) = &keyword.arg { + self.emit_load_const(ConstantData::Str { + value: name.as_str().into(), + }); + } + self.compile_expression(&keyword.value)?; + } emit!( self, - Instruction::CallIntrinsic1 { - func: bytecode::IntrinsicFunction1::SubscriptGeneric + Instruction::CallFunctionKeyword { + nargs: nargs + + u32::try_from(arguments.keywords.len()) + .expect("too many keyword arguments") } ); + } else { + emit!(self, Instruction::CallFunctionPositional { nargs }); + } + + // Return the created class + self.emit_return_value(); - // Call __build_class__ with 3 positional args: function, class_name, Generic[T] - emit!(self, Instruction::CallFunctionPositional { nargs: 3 }); + // Exit type params scope and wrap in function + let type_params_code = self.exit_scope(); + + // Execute the type params function + if self.build_closure(&type_params_code) { + // Should not need closure } + self.emit_load_const(ConstantData::Code { + code: Box::new(type_params_code), + }); + self.emit_load_const(ConstantData::Str { + value: format!("").into(), + }); + emit!( + self, + Instruction::MakeFunction(bytecode::MakeFunctionFlags::empty()) + ); + emit!(self, Instruction::CallFunctionPositional { nargs: 0 }); } else { - // No type params, normal compilation + // Non-generic class: standard path + emit!(self, Instruction::LoadBuildClass); + + let mut func_flags = bytecode::MakeFunctionFlags::empty(); + if self.build_closure(&class_code) { + func_flags |= bytecode::MakeFunctionFlags::CLOSURE; + } + + self.emit_load_const(ConstantData::Code { + code: Box::new(class_code), + }); + self.emit_load_const(ConstantData::Str { value: name.into() }); + emit!(self, Instruction::MakeFunction(func_flags)); + self.emit_load_const(ConstantData::Str { value: name.into() }); + let call = if let Some(arguments) = arguments { self.compile_call_inner(2, arguments)? } else { @@ -2226,13 +2325,8 @@ impl Compiler<'_> { self.compile_normal_call(call); } - // Pop the special type params symbol table - if type_params.is_some() { - self.pop_symbol_table(); - } - + // Step 4: Apply decorators and store (common to both paths) self.apply_decorators(decorator_list); - self.store_name(name) } diff --git a/vm/src/builtins/genericalias.rs b/vm/src/builtins/genericalias.rs index fc666190bfa..00bd65583d8 100644 --- a/vm/src/builtins/genericalias.rs +++ b/vm/src/builtins/genericalias.rs @@ -617,19 +617,21 @@ impl Iterable for PyGenericAlias { /// This is used for PEP 695 classes to create Generic[T] from type parameters // _Py_subscript_generic pub fn subscript_generic(type_params: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // Get typing.Generic type + // Get typing module and _GenericAlias let typing_module = vm.import("typing", 0)?; let generic_type = typing_module.get_attr("Generic", vm)?; - let generic_type = PyTypeRef::try_from_object(vm, generic_type)?; - // Create GenericAlias: Generic[type_params] + // Call typing._GenericAlias(Generic, type_params) + let generic_alias_class = typing_module.get_attr("_GenericAlias", vm)?; + let args = if let Ok(tuple) = type_params.try_to_ref::(vm) { tuple.to_owned() } else { PyTuple::new_ref(vec![type_params], &vm.ctx) }; - Ok(PyGenericAlias::new(generic_type, args, false, vm).into_pyobject(vm)) + // Create _GenericAlias instance + generic_alias_class.call((generic_type, args.to_pyobject(vm)), vm) } pub fn init(context: &Context) { From e75aebb967266c02c2b10f9294afe2b8df878efc Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 12 Jul 2025 14:44:34 +0300 Subject: [PATCH 026/819] Update str related tests from 3.13.5 (#5953) * Update str related tests from 3.13.5 * Apply RustPython patches * Mark new failing tests --- Lib/test/string_tests.py | 113 +++++++--- Lib/test/test_bytes.py | 239 ++++++++++++++++------ Lib/test/{test_unicode.py => test_str.py} | 192 ++++++++++++----- Lib/test/test_unicode_file.py | 2 +- Lib/test/test_unicode_file_functions.py | 14 +- Lib/test/test_unicode_identifiers.py | 2 +- Lib/test/test_unicodedata.py | 62 +++++- Lib/test/test_userstring.py | 3 +- 8 files changed, 464 insertions(+), 163 deletions(-) rename Lib/test/{test_unicode.py => test_str.py} (94%) diff --git a/Lib/test/string_tests.py b/Lib/test/string_tests.py index 6f402513fd4..3f82b515bb0 100644 --- a/Lib/test/string_tests.py +++ b/Lib/test/string_tests.py @@ -8,18 +8,12 @@ from collections import UserList import random + class Sequence: def __init__(self, seq='wxyz'): self.seq = seq def __len__(self): return len(self.seq) def __getitem__(self, i): return self.seq[i] -class BadSeq1(Sequence): - def __init__(self): self.seq = [7, 'hello', 123] - def __str__(self): return '{0} {1} {2}'.format(*self.seq) - -class BadSeq2(Sequence): - def __init__(self): self.seq = ['a', 'b', 'c'] - def __len__(self): return 8 class BaseTest: # These tests are for buffers of values (bytes) and not @@ -27,7 +21,7 @@ class BaseTest: # and various string implementations # The type to be tested - # Change in subclasses to change the behaviour of fixtesttype() + # Change in subclasses to change the behaviour of fixtype() type2test = None # Whether the "contained items" of the container are integers in @@ -36,7 +30,7 @@ class BaseTest: contains_bytes = False # All tests pass their arguments to the testing methods - # as str objects. fixtesttype() can be used to propagate + # as str objects. fixtype() can be used to propagate # these arguments to the appropriate type def fixtype(self, obj): if isinstance(obj, str): @@ -160,6 +154,14 @@ def test_count(self): self.assertEqual(rem, 0, '%s != 0 for %s' % (rem, i)) self.assertEqual(r1, r2, '%s != %s for %s' % (r1, r2, i)) + # TODO: RUSTPYTHON; TypeError: Unexpected keyword argument count + @unittest.expectedFailure + def test_count_keyword(self): + self.assertEqual('aa'.replace('a', 'b', 0), 'aa'.replace('a', 'b', count=0)) + self.assertEqual('aa'.replace('a', 'b', 1), 'aa'.replace('a', 'b', count=1)) + self.assertEqual('aa'.replace('a', 'b', 2), 'aa'.replace('a', 'b', count=2)) + self.assertEqual('aa'.replace('a', 'b', 3), 'aa'.replace('a', 'b', count=3)) + def test_find(self): self.checkequal(0, 'abcdefghiabc', 'find', 'abc') self.checkequal(9, 'abcdefghiabc', 'find', 'abc', 1) @@ -327,11 +329,12 @@ def reference_find(p, s): for i in range(len(s)): if s.startswith(p, i): return i + if p == '' and s == '': + return 0 return -1 - rr = random.randrange - choices = random.choices - for _ in range(1000): + def check_pattern(rr): + choices = random.choices p0 = ''.join(choices('abcde', k=rr(10))) * rr(10, 20) p = p0[:len(p0) - rr(10)] # pop off some characters left = ''.join(choices('abcdef', k=rr(2000))) @@ -341,6 +344,49 @@ def reference_find(p, s): self.checkequal(reference_find(p, text), text, 'find', p) + rr = random.randrange + for _ in range(1000): + check_pattern(rr) + + # Test that empty string always work: + check_pattern(lambda *args: 0) + + def test_find_many_lengths(self): + haystack_repeats = [a * 10**e for e in range(6) for a in (1,2,5)] + haystacks = [(n, self.fixtype("abcab"*n + "da")) for n in haystack_repeats] + + needle_repeats = [a * 10**e for e in range(6) for a in (1, 3)] + needles = [(m, self.fixtype("abcab"*m + "da")) for m in needle_repeats] + + for n, haystack1 in haystacks: + haystack2 = haystack1[:-1] + for m, needle in needles: + answer1 = 5 * (n - m) if m <= n else -1 + self.assertEqual(haystack1.find(needle), answer1, msg=(n,m)) + self.assertEqual(haystack2.find(needle), -1, msg=(n,m)) + + def test_adaptive_find(self): + # This would be very slow for the naive algorithm, + # but str.find() should be O(n + m). + for N in 1000, 10_000, 100_000, 1_000_000: + A, B = 'a' * N, 'b' * N + haystack = A + A + B + A + A + needle = A + B + B + A + self.checkequal(-1, haystack, 'find', needle) + self.checkequal(0, haystack, 'count', needle) + self.checkequal(len(haystack), haystack + needle, 'find', needle) + self.checkequal(1, haystack + needle, 'count', needle) + + def test_find_with_memory(self): + # Test the "Skip with memory" path in the two-way algorithm. + for N in 1000, 3000, 10_000, 30_000: + needle = 'ab' * N + haystack = ('ab'*(N-1) + 'b') * 2 + self.checkequal(-1, haystack, 'find', needle) + self.checkequal(0, haystack, 'count', needle) + self.checkequal(len(haystack), haystack + needle, 'find', needle) + self.checkequal(1, haystack + needle, 'count', needle) + def test_find_shift_table_overflow(self): """When the table of 8-bit shifts overflows.""" N = 2**8 + 100 @@ -724,6 +770,18 @@ def test_replace(self): self.checkraises(TypeError, 'hello', 'replace', 42, 'h') self.checkraises(TypeError, 'hello', 'replace', 'h', 42) + def test_replace_uses_two_way_maxcount(self): + # Test that maxcount works in _two_way_count in fastsearch.h + A, B = "A"*1000, "B"*1000 + AABAA = A + A + B + A + A + ABBA = A + B + B + A + self.checkequal(AABAA + ABBA, + AABAA + ABBA, 'replace', ABBA, "ccc", 0) + self.checkequal(AABAA + "ccc", + AABAA + ABBA, 'replace', ABBA, "ccc", 1) + self.checkequal(AABAA + "ccc", + AABAA + ABBA, 'replace', ABBA, "ccc", 2) + @unittest.skip("TODO: RUSTPYTHON, may only apply to 32-bit platforms") @unittest.skipIf(sys.maxsize > (1 << 32) or struct.calcsize('P') != 4, 'only applies to 32-bit platforms') @@ -734,8 +792,6 @@ def test_replace_overflow(self): self.checkraises(OverflowError, A2_16, "replace", "A", A2_16) self.checkraises(OverflowError, A2_16, "replace", "AA", A2_16+A2_16) - - # Python 3.9 def test_removeprefix(self): self.checkequal('am', 'spam', 'removeprefix', 'sp') self.checkequal('spamspam', 'spamspamspam', 'removeprefix', 'spam') @@ -754,7 +810,6 @@ def test_removeprefix(self): self.checkraises(TypeError, 'hello', 'removeprefix', 'h', 42) self.checkraises(TypeError, 'hello', 'removeprefix', ("he", "l")) - # Python 3.9 def test_removesuffix(self): self.checkequal('sp', 'spam', 'removesuffix', 'am') self.checkequal('spamspam', 'spamspamspam', 'removesuffix', 'spam') @@ -1053,7 +1108,7 @@ def test_splitlines(self): self.checkraises(TypeError, 'abc', 'splitlines', 42, 42) -class CommonTest(BaseTest): +class StringLikeTest(BaseTest): # This testcase contains tests that can be used in all # stringlike classes. Currently this is str and UserString. @@ -1084,11 +1139,6 @@ def test_capitalize_nonascii(self): self.checkequal('\u019b\u1d00\u1d86\u0221\u1fb7', '\u019b\u1d00\u1d86\u0221\u1fb7', 'capitalize') - -class MixinStrUnicodeUserStringTest: - # additional tests that only work for - # stringlike objects, i.e. str, UserString - def test_startswith(self): self.checkequal(True, 'hello', 'startswith', 'he') self.checkequal(True, 'hello', 'startswith', 'hello') @@ -1273,8 +1323,11 @@ def test_join(self): self.checkequal(((('a' * i) + '-') * i)[:-1], '-', 'join', ('a' * i,) * i) - #self.checkequal(str(BadSeq1()), ' ', 'join', BadSeq1()) - self.checkequal('a b c', ' ', 'join', BadSeq2()) + class LiesAboutLengthSeq(Sequence): + def __init__(self): self.seq = ['a', 'b', 'c'] + def __len__(self): return 8 + + self.checkequal('a b c', ' ', 'join', LiesAboutLengthSeq()) self.checkraises(TypeError, ' ', 'join') self.checkraises(TypeError, ' ', 'join', None) @@ -1459,19 +1512,19 @@ def test_find_etc_raise_correct_error_messages(self): # issue 11828 s = 'hello' x = 'x' - self.assertRaisesRegex(TypeError, r'^find\(', s.find, + self.assertRaisesRegex(TypeError, r'^find\b', s.find, x, None, None, None) - self.assertRaisesRegex(TypeError, r'^rfind\(', s.rfind, + self.assertRaisesRegex(TypeError, r'^rfind\b', s.rfind, x, None, None, None) - self.assertRaisesRegex(TypeError, r'^index\(', s.index, + self.assertRaisesRegex(TypeError, r'^index\b', s.index, x, None, None, None) - self.assertRaisesRegex(TypeError, r'^rindex\(', s.rindex, + self.assertRaisesRegex(TypeError, r'^rindex\b', s.rindex, x, None, None, None) - self.assertRaisesRegex(TypeError, r'^count\(', s.count, + self.assertRaisesRegex(TypeError, r'^count\b', s.count, x, None, None, None) - self.assertRaisesRegex(TypeError, r'^startswith\(', s.startswith, + self.assertRaisesRegex(TypeError, r'^startswith\b', s.startswith, x, None, None, None) - self.assertRaisesRegex(TypeError, r'^endswith\(', s.endswith, + self.assertRaisesRegex(TypeError, r'^endswith\b', s.endswith, x, None, None, None) # issue #15534 diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 3c634b6cac2..e84df546a88 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -10,6 +10,7 @@ import sys import copy import functools +import operator import pickle import tempfile import textwrap @@ -46,6 +47,10 @@ def __index__(self): class BaseBytesTest: + def assertTypedEqual(self, actual, expected): + self.assertIs(type(actual), type(expected)) + self.assertEqual(actual, expected) + def test_basics(self): b = self.type2test() self.assertEqual(type(b), self.type2test) @@ -737,6 +742,37 @@ def check(fmt, vals, result): check(b'%i%b %*.*b', (10, b'3', 5, 3, b'abc',), b'103 abc') check(b'%c', b'a', b'a') + class PseudoFloat: + def __init__(self, value): + self.value = float(value) + def __int__(self): + return int(self.value) + + pi = PseudoFloat(3.1415) + + exceptions_params = [ + ('%x format: an integer is required, not float', b'%x', 3.14), + ('%X format: an integer is required, not float', b'%X', 2.11), + ('%o format: an integer is required, not float', b'%o', 1.79), + ('%x format: an integer is required, not PseudoFloat', b'%x', pi), + ('%x format: an integer is required, not complex', b'%x', 3j), + ('%X format: an integer is required, not complex', b'%X', 2j), + ('%o format: an integer is required, not complex', b'%o', 1j), + ('%u format: a real number is required, not complex', b'%u', 3j), + # See https://github.com/python/cpython/issues/130928 as for why + # the exception message contains '%d' instead of '%i'. + ('%d format: a real number is required, not complex', b'%i', 2j), + ('%d format: a real number is required, not complex', b'%d', 2j), + ( + r'%c requires an integer in range\(256\) or a single byte', + b'%c', pi + ), + ] + + for msg, format_bytes, value in exceptions_params: + with self.assertRaisesRegex(TypeError, msg): + operator.mod(format_bytes, value) + def test_imod(self): b = self.type2test(b'hello, %b!') orig = b @@ -995,13 +1031,13 @@ def test_translate(self): self.assertEqual(c, b'hllo') def test_sq_item(self): - _testcapi = import_helper.import_module('_testcapi') + _testlimitedcapi = import_helper.import_module('_testlimitedcapi') obj = self.type2test((42,)) with self.assertRaises(IndexError): - _testcapi.sequence_getitem(obj, -2) + _testlimitedcapi.sequence_getitem(obj, -2) with self.assertRaises(IndexError): - _testcapi.sequence_getitem(obj, 1) - self.assertEqual(_testcapi.sequence_getitem(obj, 0), 42) + _testlimitedcapi.sequence_getitem(obj, 1) + self.assertEqual(_testlimitedcapi.sequence_getitem(obj, 0), 42) class BytesTest(BaseBytesTest, unittest.TestCase): @@ -1031,36 +1067,63 @@ def test_buffer_is_readonly(self): self.assertRaises(TypeError, f.readinto, b"") def test_custom(self): - class A: - def __bytes__(self): - return b'abc' - self.assertEqual(bytes(A()), b'abc') - class A: pass - self.assertRaises(TypeError, bytes, A()) - class A: - def __bytes__(self): - return None - self.assertRaises(TypeError, bytes, A()) - class A: + self.assertEqual(bytes(BytesSubclass(b'abc')), b'abc') + self.assertEqual(BytesSubclass(OtherBytesSubclass(b'abc')), + BytesSubclass(b'abc')) + self.assertEqual(bytes(WithBytes(b'abc')), b'abc') + self.assertEqual(BytesSubclass(WithBytes(b'abc')), BytesSubclass(b'abc')) + + class NoBytes: pass + self.assertRaises(TypeError, bytes, NoBytes()) + self.assertRaises(TypeError, bytes, WithBytes('abc')) + self.assertRaises(TypeError, bytes, WithBytes(None)) + class IndexWithBytes: def __bytes__(self): return b'a' def __index__(self): return 42 - self.assertEqual(bytes(A()), b'a') + self.assertEqual(bytes(IndexWithBytes()), b'a') # Issue #25766 - class A(str): + class StrWithBytes(str): + def __new__(cls, value): + self = str.__new__(cls, '\u20ac') + self.value = value + return self def __bytes__(self): - return b'abc' - self.assertEqual(bytes(A('\u20ac')), b'abc') - self.assertEqual(bytes(A('\u20ac'), 'iso8859-15'), b'\xa4') + return self.value + self.assertEqual(bytes(StrWithBytes(b'abc')), b'abc') + self.assertEqual(bytes(StrWithBytes(b'abc'), 'iso8859-15'), b'\xa4') + self.assertEqual(bytes(StrWithBytes(BytesSubclass(b'abc'))), b'abc') + self.assertEqual(BytesSubclass(StrWithBytes(b'abc')), BytesSubclass(b'abc')) + self.assertEqual(BytesSubclass(StrWithBytes(b'abc'), 'iso8859-15'), + BytesSubclass(b'\xa4')) + self.assertEqual(BytesSubclass(StrWithBytes(BytesSubclass(b'abc'))), + BytesSubclass(b'abc')) + self.assertEqual(BytesSubclass(StrWithBytes(OtherBytesSubclass(b'abc'))), + BytesSubclass(b'abc')) # Issue #24731 - class A: + self.assertTypedEqual(bytes(WithBytes(BytesSubclass(b'abc'))), BytesSubclass(b'abc')) + self.assertTypedEqual(BytesSubclass(WithBytes(BytesSubclass(b'abc'))), + BytesSubclass(b'abc')) + self.assertTypedEqual(BytesSubclass(WithBytes(OtherBytesSubclass(b'abc'))), + BytesSubclass(b'abc')) + + class BytesWithBytes(bytes): + def __new__(cls, value): + self = bytes.__new__(cls, b'\xa4') + self.value = value + return self def __bytes__(self): - return OtherBytesSubclass(b'abc') - self.assertEqual(bytes(A()), b'abc') - self.assertIs(type(bytes(A())), OtherBytesSubclass) - self.assertEqual(BytesSubclass(A()), b'abc') - self.assertIs(type(BytesSubclass(A())), BytesSubclass) + return self.value + self.assertTypedEqual(bytes(BytesWithBytes(b'abc')), b'abc') + self.assertTypedEqual(BytesSubclass(BytesWithBytes(b'abc')), + BytesSubclass(b'abc')) + self.assertTypedEqual(bytes(BytesWithBytes(BytesSubclass(b'abc'))), + BytesSubclass(b'abc')) + self.assertTypedEqual(BytesSubclass(BytesWithBytes(BytesSubclass(b'abc'))), + BytesSubclass(b'abc')) + self.assertTypedEqual(BytesSubclass(BytesWithBytes(OtherBytesSubclass(b'abc'))), + BytesSubclass(b'abc')) # Test PyBytes_FromFormat() def test_from_format(self): @@ -1233,6 +1296,8 @@ class SubBytes(bytes): class ByteArrayTest(BaseBytesTest, unittest.TestCase): type2test = bytearray + _testlimitedcapi = import_helper.import_module('_testlimitedcapi') + def test_getitem_error(self): b = bytearray(b'python') msg = "bytearray indices must be integers or slices" @@ -1325,47 +1390,73 @@ def by(s): self.assertEqual(re.findall(br"\w+", b), [by("Hello"), by("world")]) def test_setitem(self): - b = bytearray([1, 2, 3]) - b[1] = 100 - self.assertEqual(b, bytearray([1, 100, 3])) - b[-1] = 200 - self.assertEqual(b, bytearray([1, 100, 200])) - b[0] = Indexable(10) - self.assertEqual(b, bytearray([10, 100, 200])) - try: - b[3] = 0 - self.fail("Didn't raise IndexError") - except IndexError: - pass - try: - b[-10] = 0 - self.fail("Didn't raise IndexError") - except IndexError: - pass - try: - b[0] = 256 - self.fail("Didn't raise ValueError") - except ValueError: - pass - try: - b[0] = Indexable(-1) - self.fail("Didn't raise ValueError") - except ValueError: - pass - try: - b[0] = None - self.fail("Didn't raise TypeError") - except TypeError: - pass + def setitem_as_mapping(b, i, val): + b[i] = val + + def setitem_as_sequence(b, i, val): + self._testlimitedcapi.sequence_setitem(b, i, val) + + def do_tests(setitem): + b = bytearray([1, 2, 3]) + setitem(b, 1, 100) + self.assertEqual(b, bytearray([1, 100, 3])) + setitem(b, -1, 200) + self.assertEqual(b, bytearray([1, 100, 200])) + setitem(b, 0, Indexable(10)) + self.assertEqual(b, bytearray([10, 100, 200])) + try: + setitem(b, 3, 0) + self.fail("Didn't raise IndexError") + except IndexError: + pass + try: + setitem(b, -10, 0) + self.fail("Didn't raise IndexError") + except IndexError: + pass + try: + setitem(b, 0, 256) + self.fail("Didn't raise ValueError") + except ValueError: + pass + try: + setitem(b, 0, Indexable(-1)) + self.fail("Didn't raise ValueError") + except ValueError: + pass + try: + setitem(b, 0, object()) + self.fail("Didn't raise TypeError") + except TypeError: + pass + + with self.subTest("tp_as_mapping"): + do_tests(setitem_as_mapping) + + with self.subTest("tp_as_sequence"): + do_tests(setitem_as_sequence) def test_delitem(self): - b = bytearray(range(10)) - del b[0] - self.assertEqual(b, bytearray(range(1, 10))) - del b[-1] - self.assertEqual(b, bytearray(range(1, 9))) - del b[4] - self.assertEqual(b, bytearray([1, 2, 3, 4, 6, 7, 8])) + def del_as_mapping(b, i): + del b[i] + + def del_as_sequence(b, i): + self._testlimitedcapi.sequence_delitem(b, i) + + def do_tests(delete): + b = bytearray(range(10)) + delete(b, 0) + self.assertEqual(b, bytearray(range(1, 10))) + delete(b, -1) + self.assertEqual(b, bytearray(range(1, 9))) + delete(b, 4) + self.assertEqual(b, bytearray([1, 2, 3, 4, 6, 7, 8])) + + with self.subTest("tp_as_mapping"): + do_tests(del_as_mapping) + + with self.subTest("tp_as_sequence"): + do_tests(del_as_sequence) def test_setslice(self): b = bytearray(range(10)) @@ -1558,6 +1649,13 @@ def test_extend(self): a = bytearray(b'') a.extend([Indexable(ord('a'))]) self.assertEqual(a, b'a') + a = bytearray(b'abc') + self.assertRaisesRegex(TypeError, # Override for string. + "expected iterable of integers; got: 'str'", + a.extend, 'def') + self.assertRaisesRegex(TypeError, # But not for others. + "can't extend bytearray with float", + a.extend, 1.0) def test_remove(self): b = bytearray(b'hello') @@ -1747,6 +1845,8 @@ def test_repeat_after_setslice(self): self.assertEqual(b3, b'xcxcxc') def test_mutating_index(self): + # See gh-91153 + class Boom: def __index__(self): b.clear() @@ -1758,10 +1858,9 @@ def __index__(self): b[0] = Boom() with self.subTest("tp_as_sequence"): - _testcapi = import_helper.import_module('_testcapi') b = bytearray(b'Now you see me...') with self.assertRaises(IndexError): - _testcapi.sequence_setitem(b, 0, Boom()) + self._testlimitedcapi.sequence_setitem(b, 0, Boom()) class AssortedBytesTest(unittest.TestCase): @@ -2060,6 +2159,12 @@ class BytesSubclass(bytes): class OtherBytesSubclass(bytes): pass +class WithBytes: + def __init__(self, value): + self.value = value + def __bytes__(self): + return self.value + class ByteArraySubclassTest(SubclassTest, unittest.TestCase): basetype = bytearray type2test = ByteArraySubclass diff --git a/Lib/test/test_unicode.py b/Lib/test/test_str.py similarity index 94% rename from Lib/test/test_unicode.py rename to Lib/test/test_str.py index 1a8a8f7ee93..ef2d211a612 100644 --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_str.py @@ -7,6 +7,7 @@ """ import _string import codecs +import datetime import itertools import operator import pickle @@ -55,8 +56,22 @@ def duplicate_string(text): class StrSubclass(str): pass -class UnicodeTest(string_tests.CommonTest, - string_tests.MixinStrUnicodeUserStringTest, +class OtherStrSubclass(str): + pass + +class WithStr: + def __init__(self, value): + self.value = value + def __str__(self): + return self.value + +class WithRepr: + def __init__(self, value): + self.value = value + def __repr__(self): + return self.value + +class StrTest(string_tests.StringLikeTest, string_tests.MixinStrUnicodeTest, unittest.TestCase): @@ -84,6 +99,10 @@ def __repr__(self): self.assertEqual(realresult, result) self.assertTrue(object is not realresult) + def assertTypedEqual(self, actual, expected): + self.assertIs(type(actual), type(expected)) + self.assertEqual(actual, expected) + def test_literals(self): self.assertEqual('\xff', '\u00ff') self.assertEqual('\uffff', '\U0000ffff') @@ -93,6 +112,8 @@ def test_literals(self): # raw strings should not have unicode escapes self.assertNotEqual(r"\u0020", " ") + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_ascii(self): self.assertEqual(ascii('abc'), "'abc'") self.assertEqual(ascii('ab\\c'), "'ab\\\\c'") @@ -128,10 +149,13 @@ def test_ascii(self): self.assertEqual(ascii("\U00010000" * 39 + "\uffff" * 4096), ascii("\U00010000" * 39 + "\uffff" * 4096)) - class WrongRepr: - def __repr__(self): - return b'byte-repr' - self.assertRaises(TypeError, ascii, WrongRepr()) + self.assertTypedEqual(ascii('\U0001f40d'), r"'\U0001f40d'") + self.assertTypedEqual(ascii(StrSubclass('abc')), "'abc'") + self.assertTypedEqual(ascii(WithRepr('')), '') + self.assertTypedEqual(ascii(WithRepr(StrSubclass(''))), StrSubclass('')) + self.assertTypedEqual(ascii(WithRepr('<\U0001f40d>')), r'<\U0001f40d>') + self.assertTypedEqual(ascii(WithRepr(StrSubclass('<\U0001f40d>'))), r'<\U0001f40d>') + self.assertRaises(TypeError, ascii, WithRepr(b'byte-repr')) def test_repr(self): # Test basic sanity of repr() @@ -169,10 +193,13 @@ def test_repr(self): self.assertEqual(repr("\U00010000" * 39 + "\uffff" * 4096), repr("\U00010000" * 39 + "\uffff" * 4096)) - class WrongRepr: - def __repr__(self): - return b'byte-repr' - self.assertRaises(TypeError, repr, WrongRepr()) + self.assertTypedEqual(repr('\U0001f40d'), "'\U0001f40d'") + self.assertTypedEqual(repr(StrSubclass('abc')), "'abc'") + self.assertTypedEqual(repr(WithRepr('')), '') + self.assertTypedEqual(repr(WithRepr(StrSubclass(''))), StrSubclass('')) + self.assertTypedEqual(repr(WithRepr('<\U0001f40d>')), '<\U0001f40d>') + self.assertTypedEqual(repr(WithRepr(StrSubclass('<\U0001f40d>'))), StrSubclass('<\U0001f40d>')) + self.assertRaises(TypeError, repr, WithRepr(b'byte-repr')) def test_iterators(self): # Make sure unicode objects have an __iter__ method @@ -213,7 +240,7 @@ def test_pickle_iterator(self): self.assertEqual(case, pickled) def test_count(self): - string_tests.CommonTest.test_count(self) + string_tests.StringLikeTest.test_count(self) # check mixed argument types self.checkequalnofix(3, 'aaa', 'count', 'a') self.checkequalnofix(0, 'aaa', 'count', 'b') @@ -243,7 +270,7 @@ class MyStr(str): self.checkequal(3, MyStr('aaa'), 'count', 'a') def test_find(self): - string_tests.CommonTest.test_find(self) + string_tests.StringLikeTest.test_find(self) # test implementation details of the memchr fast path self.checkequal(100, 'a' * 100 + '\u0102', 'find', '\u0102') self.checkequal(-1, 'a' * 100 + '\u0102', 'find', '\u0201') @@ -288,7 +315,7 @@ def test_find(self): self.checkequal(-1, '\u0102' * 100, 'find', '\u0102\U00100304') def test_rfind(self): - string_tests.CommonTest.test_rfind(self) + string_tests.StringLikeTest.test_rfind(self) # test implementation details of the memrchr fast path self.checkequal(0, '\u0102' + 'a' * 100 , 'rfind', '\u0102') self.checkequal(-1, '\u0102' + 'a' * 100 , 'rfind', '\u0201') @@ -329,7 +356,7 @@ def test_rfind(self): self.checkequal(-1, '\u0102' * 100, 'rfind', '\U00100304\u0102') def test_index(self): - string_tests.CommonTest.test_index(self) + string_tests.StringLikeTest.test_index(self) self.checkequalnofix(0, 'abcdefghiabc', 'index', '') self.checkequalnofix(3, 'abcdefghiabc', 'index', 'def') self.checkequalnofix(0, 'abcdefghiabc', 'index', 'abc') @@ -353,7 +380,7 @@ def test_index(self): self.assertRaises(ValueError, ('\u0102' * 100).index, '\u0102\U00100304') def test_rindex(self): - string_tests.CommonTest.test_rindex(self) + string_tests.StringLikeTest.test_rindex(self) self.checkequalnofix(12, 'abcdefghiabc', 'rindex', '') self.checkequalnofix(3, 'abcdefghiabc', 'rindex', 'def') self.checkequalnofix(9, 'abcdefghiabc', 'rindex', 'abc') @@ -449,7 +476,7 @@ def test_maketrans_translate(self): self.assertRaises(TypeError, 'abababc'.translate, 'abc', 'xyz') def test_split(self): - string_tests.CommonTest.test_split(self) + string_tests.StringLikeTest.test_split(self) # test mixed kinds for left, right in ('ba', '\u0101\u0100', '\U00010301\U00010300'): @@ -466,7 +493,7 @@ def test_split(self): left + delim * 2 + right, 'split', delim *2) def test_rsplit(self): - string_tests.CommonTest.test_rsplit(self) + string_tests.StringLikeTest.test_rsplit(self) # test mixed kinds for left, right in ('ba', 'юё', '\u0101\u0100', '\U00010301\U00010300'): left *= 9 @@ -486,7 +513,7 @@ def test_rsplit(self): left + right, 'rsplit', None) def test_partition(self): - string_tests.MixinStrUnicodeUserStringTest.test_partition(self) + string_tests.StringLikeTest.test_partition(self) # test mixed kinds self.checkequal(('ABCDEFGH', '', ''), 'ABCDEFGH', 'partition', '\u4200') for left, right in ('ba', '\u0101\u0100', '\U00010301\U00010300'): @@ -503,7 +530,7 @@ def test_partition(self): left + delim * 2 + right, 'partition', delim * 2) def test_rpartition(self): - string_tests.MixinStrUnicodeUserStringTest.test_rpartition(self) + string_tests.StringLikeTest.test_rpartition(self) # test mixed kinds self.checkequal(('', '', 'ABCDEFGH'), 'ABCDEFGH', 'rpartition', '\u4200') for left, right in ('ba', '\u0101\u0100', '\U00010301\U00010300'): @@ -520,7 +547,7 @@ def test_rpartition(self): left + delim * 2 + right, 'rpartition', delim * 2) def test_join(self): - string_tests.MixinStrUnicodeUserStringTest.test_join(self) + string_tests.StringLikeTest.test_join(self) class MyWrapper: def __init__(self, sval): self.sval = sval @@ -548,7 +575,7 @@ def test_join_overflow(self): self.assertRaises(OverflowError, ''.join, seq) def test_replace(self): - string_tests.CommonTest.test_replace(self) + string_tests.StringLikeTest.test_replace(self) # method call forwarded from str implementation because of unicode argument self.checkequalnofix('one@two!three!', 'one!two!three!', 'replace', '!', '@', 1) @@ -831,6 +858,15 @@ def test_isprintable(self): self.assertTrue('\U0001F46F'.isprintable()) self.assertFalse('\U000E0020'.isprintable()) + @support.requires_resource('cpu') + def test_isprintable_invariant(self): + for codepoint in range(sys.maxunicode + 1): + char = chr(codepoint) + category = unicodedata.category(char) + self.assertEqual(char.isprintable(), + category[0] not in ('C', 'Z') + or char == ' ') + def test_surrogates(self): for s in ('a\uD800b\uDFFF', 'a\uDFFFb\uD800', 'a\uD800b\uDFFFa', 'a\uDFFFb\uD800a'): @@ -859,7 +895,7 @@ def test_surrogates(self): def test_lower(self): - string_tests.CommonTest.test_lower(self) + string_tests.StringLikeTest.test_lower(self) self.assertEqual('\U00010427'.lower(), '\U0001044F') self.assertEqual('\U00010427\U00010427'.lower(), '\U0001044F\U0001044F') @@ -890,7 +926,7 @@ def test_casefold(self): self.assertEqual('\u00b5'.casefold(), '\u03bc') def test_upper(self): - string_tests.CommonTest.test_upper(self) + string_tests.StringLikeTest.test_upper(self) self.assertEqual('\U0001044F'.upper(), '\U00010427') self.assertEqual('\U0001044F\U0001044F'.upper(), '\U00010427\U00010427') @@ -909,7 +945,7 @@ def test_upper(self): # TODO: RUSTPYTHON @unittest.expectedFailure def test_capitalize(self): - string_tests.CommonTest.test_capitalize(self) + string_tests.StringLikeTest.test_capitalize(self) self.assertEqual('\U0001044F'.capitalize(), '\U00010427') self.assertEqual('\U0001044F\U0001044F'.capitalize(), '\U00010427\U0001044F') @@ -947,7 +983,7 @@ def test_title(self): # TODO: RUSTPYTHON @unittest.expectedFailure def test_swapcase(self): - string_tests.CommonTest.test_swapcase(self) + string_tests.StringLikeTest.test_swapcase(self) self.assertEqual('\U0001044F'.swapcase(), '\U00010427') self.assertEqual('\U00010427'.swapcase(), '\U0001044F') self.assertEqual('\U0001044F\U0001044F'.swapcase(), @@ -973,7 +1009,7 @@ def test_swapcase(self): self.assertEqual('\u1fd2'.swapcase(), '\u0399\u0308\u0300') def test_center(self): - string_tests.CommonTest.test_center(self) + string_tests.StringLikeTest.test_center(self) self.assertEqual('x'.center(2, '\U0010FFFF'), 'x\U0010FFFF') self.assertEqual('x'.center(3, '\U0010FFFF'), @@ -1483,7 +1519,7 @@ def __format__(self, spec): # TODO: RUSTPYTHON @unittest.expectedFailure def test_formatting(self): - string_tests.MixinStrUnicodeUserStringTest.test_formatting(self) + string_tests.StringLikeTest.test_formatting(self) # Testing Unicode formatting strings... self.assertEqual("%s, %s" % ("abc", "abc"), 'abc, abc') self.assertEqual("%s, %s, %i, %f, %5.2f" % ("abc", "abc", 1, 2, 3), 'abc, abc, 1, 2.000000, 3.00') @@ -1659,7 +1695,7 @@ def test_startswith_endswith_errors(self): self.assertIn('str', exc) self.assertIn('tuple', exc) - @support.run_with_locale('LC_ALL', 'de_DE', 'fr_FR') + @support.run_with_locale('LC_ALL', 'de_DE', 'fr_FR', '') def test_format_float(self): # should not format with a comma, but always with C locale self.assertEqual('1.0', '%.1f' % 1.0) @@ -1730,8 +1766,6 @@ def __str__(self): 'character buffers are decoded to unicode' ) - self.assertRaises(TypeError, str, 42, 42, 42) - # TODO: RUSTPYTHON @unittest.expectedFailure def test_constructor_keyword_args(self): @@ -1910,6 +1944,12 @@ def test_utf8_decode_invalid_sequences(self): self.assertRaises(UnicodeDecodeError, (b'\xF4'+cb+b'\xBF\xBF').decode, 'utf-8') + def test_issue127903(self): + # gh-127903: ``_copy_characters`` crashes on DEBUG builds when + # there is nothing to copy. + d = datetime.datetime(2013, 11, 10, 14, 20, 59) + self.assertEqual(d.strftime('%z'), '') + def test_issue8271(self): # Issue #8271: during the decoding of an invalid UTF-8 byte sequence, # only the start byte and the continuation byte(s) are now considered @@ -2396,28 +2436,37 @@ def test_ucs4(self): @unittest.expectedFailure def test_conversion(self): # Make sure __str__() works properly - class ObjectToStr: - def __str__(self): - return "foo" - - class StrSubclassToStr(str): - def __str__(self): - return "foo" - - class StrSubclassToStrSubclass(str): - def __new__(cls, content=""): - return str.__new__(cls, 2*content) - def __str__(self): + class StrWithStr(str): + def __new__(cls, value): + self = str.__new__(cls, "") + self.value = value return self + def __str__(self): + return self.value - self.assertEqual(str(ObjectToStr()), "foo") - self.assertEqual(str(StrSubclassToStr("bar")), "foo") - s = str(StrSubclassToStrSubclass("foo")) - self.assertEqual(s, "foofoo") - self.assertIs(type(s), StrSubclassToStrSubclass) - s = StrSubclass(StrSubclassToStrSubclass("foo")) - self.assertEqual(s, "foofoo") - self.assertIs(type(s), StrSubclass) + self.assertTypedEqual(str(WithStr('abc')), 'abc') + self.assertTypedEqual(str(WithStr(StrSubclass('abc'))), StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(WithStr('abc')), StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(WithStr(StrSubclass('abc'))), + StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(WithStr(OtherStrSubclass('abc'))), + StrSubclass('abc')) + + self.assertTypedEqual(str(StrWithStr('abc')), 'abc') + self.assertTypedEqual(str(StrWithStr(StrSubclass('abc'))), StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(StrWithStr('abc')), StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(StrWithStr(StrSubclass('abc'))), + StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(StrWithStr(OtherStrSubclass('abc'))), + StrSubclass('abc')) + + self.assertTypedEqual(str(WithRepr('')), '') + self.assertTypedEqual(str(WithRepr(StrSubclass(''))), StrSubclass('')) + self.assertTypedEqual(StrSubclass(WithRepr('')), StrSubclass('')) + self.assertTypedEqual(StrSubclass(WithRepr(StrSubclass(''))), + StrSubclass('')) + self.assertTypedEqual(StrSubclass(WithRepr(OtherStrSubclass(''))), + StrSubclass('')) def test_unicode_repr(self): class s1: @@ -2652,6 +2701,49 @@ def test_check_encoding_errors(self): proc = assert_python_failure('-X', 'dev', '-c', code) self.assertEqual(proc.rc, 10, proc) + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_str_invalid_call(self): + # too many args + with self.assertRaisesRegex(TypeError, r"str expected at most 3 arguments, got 4"): + str("too", "many", "argu", "ments") + with self.assertRaisesRegex(TypeError, r"str expected at most 3 arguments, got 4"): + str(1, "", "", 1) + + # no such kw arg + with self.assertRaisesRegex(TypeError, r"str\(\) got an unexpected keyword argument 'test'"): + str(test=1) + + # 'encoding' must be str + with self.assertRaisesRegex(TypeError, r"str\(\) argument 'encoding' must be str, not int"): + str(1, 1) + with self.assertRaisesRegex(TypeError, r"str\(\) argument 'encoding' must be str, not int"): + str(1, encoding=1) + with self.assertRaisesRegex(TypeError, r"str\(\) argument 'encoding' must be str, not bytes"): + str(b"x", b"ascii") + with self.assertRaisesRegex(TypeError, r"str\(\) argument 'encoding' must be str, not bytes"): + str(b"x", encoding=b"ascii") + + # 'errors' must be str + with self.assertRaisesRegex(TypeError, r"str\(\) argument 'encoding' must be str, not int"): + str(1, 1, 1) + with self.assertRaisesRegex(TypeError, r"str\(\) argument 'errors' must be str, not int"): + str(1, errors=1) + with self.assertRaisesRegex(TypeError, r"str\(\) argument 'errors' must be str, not int"): + str(1, "", errors=1) + with self.assertRaisesRegex(TypeError, r"str\(\) argument 'errors' must be str, not bytes"): + str(b"x", "ascii", b"strict") + with self.assertRaisesRegex(TypeError, r"str\(\) argument 'errors' must be str, not bytes"): + str(b"x", "ascii", errors=b"strict") + + # both positional and kwarg + with self.assertRaisesRegex(TypeError, r"argument for str\(\) given by name \('encoding'\) and position \(2\)"): + str(b"x", "utf-8", encoding="ascii") + with self.assertRaisesRegex(TypeError, r"str\(\) takes at most 3 arguments \(4 given\)"): + str(b"x", "utf-8", "ignore", encoding="ascii") + with self.assertRaisesRegex(TypeError, r"str\(\) takes at most 3 arguments \(4 given\)"): + str(b"x", "utf-8", "strict", errors="ignore") + class StringModuleTest(unittest.TestCase): def test_formatter_parser(self): diff --git a/Lib/test/test_unicode_file.py b/Lib/test/test_unicode_file.py index 80c22c6cdd1..fe25bfe9f88 100644 --- a/Lib/test/test_unicode_file.py +++ b/Lib/test/test_unicode_file.py @@ -110,7 +110,7 @@ def _test_single(self, filename): os.unlink(filename) self.assertTrue(not os.path.exists(filename)) # and again with os.open. - f = os.open(filename, os.O_CREAT) + f = os.open(filename, os.O_CREAT | os.O_WRONLY) os.close(f) try: self._do_single(filename) diff --git a/Lib/test/test_unicode_file_functions.py b/Lib/test/test_unicode_file_functions.py index 47619c8807b..25c16e3a0b7 100644 --- a/Lib/test/test_unicode_file_functions.py +++ b/Lib/test/test_unicode_file_functions.py @@ -5,7 +5,7 @@ import unittest import warnings from unicodedata import normalize -from test.support import os_helper +from test.support import is_apple, os_helper from test import support @@ -23,13 +23,13 @@ '10_\u1fee\u1ffd', ] -# Mac OS X decomposes Unicode names, using Normal Form D. +# Apple platforms decompose Unicode names, using Normal Form D. # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html # "However, most volume formats do not follow the exact specification for # these normal forms. For example, HFS Plus uses a variant of Normal Form D # in which U+2000 through U+2FFF, U+F900 through U+FAFF, and U+2F800 through # U+2FAFF are not decomposed." -if sys.platform != 'darwin': +if not is_apple: filenames.extend([ # Specific code points: NFC(fn), NFD(fn), NFKC(fn) and NFKD(fn) all different '11_\u0385\u03d3\u03d4', @@ -119,11 +119,11 @@ def test_open(self): os.stat(name) self._apply_failure(os.listdir, name, self._listdir_failure) - # Skip the test on darwin, because darwin does normalize the filename to + # Skip the test on Apple platforms, because they don't normalize the filename to # NFD (a variant of Unicode NFD form). Normalize the filename to NFC, NFKC, # NFKD in Python is useless, because darwin will normalize it later and so # open(), os.stat(), etc. don't raise any exception. - @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') + @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') @unittest.skipIf( support.is_emscripten or support.is_wasi, "test fails on Emscripten/WASI when host platform is macOS." @@ -142,10 +142,10 @@ def test_normalize(self): self._apply_failure(os.remove, name) self._apply_failure(os.listdir, name) - # Skip the test on darwin, because darwin uses a normalization different + # Skip the test on Apple platforms, because they use a normalization different # than Python NFD normalization: filenames are different even if we use # Python NFD normalization. - @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') + @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') def test_listdir(self): sf0 = set(self.files) with warnings.catch_warnings(): diff --git a/Lib/test/test_unicode_identifiers.py b/Lib/test/test_unicode_identifiers.py index d7a0ece2536..60cfdaabe82 100644 --- a/Lib/test/test_unicode_identifiers.py +++ b/Lib/test/test_unicode_identifiers.py @@ -21,7 +21,7 @@ def test_non_bmp_normalized(self): @unittest.expectedFailure def test_invalid(self): try: - from test import badsyntax_3131 + from test.tokenizedata import badsyntax_3131 except SyntaxError as err: self.assertEqual(str(err), "invalid character '€' (U+20AC) (badsyntax_3131.py, line 2)") diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py index 29da4a25a3f..7f49c1690f5 100644 --- a/Lib/test/test_unicodedata.py +++ b/Lib/test/test_unicodedata.py @@ -11,15 +11,20 @@ import sys import unicodedata import unittest -from test.support import (open_urlresource, requires_resource, script_helper, - cpython_only, check_disallow_instantiation, - ResourceDenied) +from test.support import ( + open_urlresource, + requires_resource, + script_helper, + cpython_only, + check_disallow_instantiation, + force_not_colorized, +) class UnicodeMethodsTest(unittest.TestCase): # update this, if the database changes - expectedchecksum = '4739770dd4d0e5f1b1677accfc3552ed3c8ef326' + expectedchecksum = '63aa77dcb36b0e1df082ee2a6071caeda7f0955e' # TODO: RUSTPYTHON @unittest.expectedFailure @@ -74,7 +79,8 @@ class UnicodeFunctionsTest(UnicodeDatabaseTest): # Update this if the database changes. Make sure to do a full rebuild # (e.g. 'make distclean && make') to get the correct checksum. - expectedchecksum = '98d602e1f69d5c5bb8a5910c40bbbad4e18e8370' + expectedchecksum = '232affd2a50ec4bd69d2482aa0291385cbdefaba' + # TODO: RUSTPYTHON @unittest.expectedFailure @requires_resource('cpu') @@ -94,6 +100,8 @@ def test_function_checksum(self): self.db.decomposition(char), str(self.db.mirrored(char)), str(self.db.combining(char)), + unicodedata.east_asian_width(char), + self.db.name(char, ""), ] h.update(''.join(data).encode("ascii")) result = h.hexdigest() @@ -106,6 +114,28 @@ def test_name_inverse_lookup(self): if looked_name := self.db.name(char, None): self.assertEqual(self.db.lookup(looked_name), char) + def test_no_names_in_pua(self): + puas = [*range(0xe000, 0xf8ff), + *range(0xf0000, 0xfffff), + *range(0x100000, 0x10ffff)] + for i in puas: + char = chr(i) + self.assertRaises(ValueError, self.db.name, char) + + # TODO: RUSTPYTHON; LookupError: undefined character name 'LATIN SMLL LETR A' + @unittest.expectedFailure + def test_lookup_nonexistant(self): + # just make sure that lookup can fail + for nonexistant in [ + "LATIN SMLL LETR A", + "OPEN HANDS SIGHS", + "DREGS", + "HANDBUG", + "MODIFIER LETTER CYRILLIC SMALL QUESTION MARK", + "???", + ]: + self.assertRaises(KeyError, self.db.lookup, nonexistant) + # TODO: RUSTPYTHON @unittest.expectedFailure def test_digit(self): @@ -245,6 +275,25 @@ def test_east_asian_width(self): self.assertEqual(eaw('\u2010'), 'A') self.assertEqual(eaw('\U00020000'), 'W') + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_east_asian_width_unassigned(self): + eaw = self.db.east_asian_width + # unassigned + for char in '\u0530\u0ecf\u10c6\u20fc\uaaca\U000107bd\U000115f2': + self.assertEqual(eaw(char), 'N') + self.assertIs(self.db.name(char, None), None) + + # unassigned but reserved for CJK + for char in '\uFA6E\uFADA\U0002A6E0\U0002FA20\U0003134B\U0003FFFD': + self.assertEqual(eaw(char), 'W') + self.assertIs(self.db.name(char, None), None) + + # private use areas + for char in '\uE000\uF800\U000F0000\U000FFFEE\U00100000\U0010FFF0': + self.assertEqual(eaw(char), 'A') + self.assertIs(self.db.name(char, None), None) + # TODO: RUSTPYTHON @unittest.expectedFailure def test_east_asian_width_9_0_changes(self): @@ -260,6 +309,7 @@ def test_disallow_instantiation(self): # TODO: RUSTPYTHON @unittest.expectedFailure + @force_not_colorized def test_failed_import_during_compiling(self): # Issue 4367 # Decoding \N escapes requires the unicodedata module. If it can't be @@ -322,6 +372,7 @@ def test_ucd_510(self): self.assertTrue("\u1d79".upper()=='\ua77d') self.assertTrue(".".upper()=='.') + @requires_resource('cpu') def test_bug_5828(self): self.assertEqual("\u1d79".lower(), "\u1d79") # Only U+0000 should have U+0000 as its upper/lower/titlecase variant @@ -364,6 +415,7 @@ def unistr(data): return "".join([chr(x) for x in data]) @requires_resource('network') + @requires_resource('cpu') def test_normalization(self): TESTDATAFILE = "NormalizationTest.txt" TESTDATAURL = f"http://www.pythontest.net/unicode/{unicodedata.unidata_version}/{TESTDATAFILE}" diff --git a/Lib/test/test_userstring.py b/Lib/test/test_userstring.py index 51b4f6041e4..74df52f5412 100644 --- a/Lib/test/test_userstring.py +++ b/Lib/test/test_userstring.py @@ -7,8 +7,7 @@ from collections import UserString class UserStringTest( - string_tests.CommonTest, - string_tests.MixinStrUnicodeUserStringTest, + string_tests.StringLikeTest, unittest.TestCase ): From ac20b00e26ab4721aa277a092db186bd1d5754c8 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 12 Jul 2025 16:43:47 +0300 Subject: [PATCH 027/819] `str.replace` support count as keyword arg (#5954) --- Lib/test/string_tests.py | 2 -- vm/src/builtins/str.rs | 35 +++++++++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Lib/test/string_tests.py b/Lib/test/string_tests.py index 3f82b515bb0..c5831c47fcf 100644 --- a/Lib/test/string_tests.py +++ b/Lib/test/string_tests.py @@ -154,8 +154,6 @@ def test_count(self): self.assertEqual(rem, 0, '%s != 0 for %s' % (rem, i)) self.assertEqual(r1, r2, '%s != %s for %s' % (r1, r2, i)) - # TODO: RUSTPYTHON; TypeError: Unexpected keyword argument count - @unittest.expectedFailure def test_count_keyword(self): self.assertEqual('aa'.replace('a', 'b', 0), 'aa'.replace('a', 'b', count=0)) self.assertEqual('aa'.replace('a', 'b', 1), 'aa'.replace('a', 'b', count=1)) diff --git a/vm/src/builtins/str.rs b/vm/src/builtins/str.rs index f822a124ed9..9f86da3da0d 100644 --- a/vm/src/builtins/str.rs +++ b/vm/src/builtins/str.rs @@ -1018,20 +1018,27 @@ impl PyStr { } #[pymethod] - fn replace(&self, old: PyStrRef, new: PyStrRef, count: OptionalArg) -> Wtf8Buf { + fn replace(&self, args: ReplaceArgs) -> Wtf8Buf { + use std::cmp::Ordering; + let s = self.as_wtf8(); - match count { - OptionalArg::Present(max_count) if max_count >= 0 => { - if max_count == 0 || (s.is_empty() && !old.is_empty()) { - // nothing to do; return the original bytes + let ReplaceArgs { old, new, count } = args; + + match count.cmp(&0) { + Ordering::Less => s.replace(old.as_wtf8(), new.as_wtf8()), + Ordering::Equal => s.to_owned(), + Ordering::Greater => { + let s_is_empty = s.is_empty(); + let old_is_empty = old.is_empty(); + + if s_is_empty && !old_is_empty { s.to_owned() - } else if s.is_empty() && old.is_empty() { + } else if s_is_empty && old_is_empty { new.as_wtf8().to_owned() } else { - s.replacen(old.as_wtf8(), new.as_wtf8(), max_count as usize) + s.replacen(old.as_wtf8(), new.as_wtf8(), count as usize) } } - _ => s.replace(old.as_wtf8(), new.as_wtf8()), } } @@ -1685,6 +1692,18 @@ impl FindArgs { } } +#[derive(FromArgs)] +struct ReplaceArgs { + #[pyarg(positional)] + old: PyStrRef, + + #[pyarg(positional)] + new: PyStrRef, + + #[pyarg(any, default = -1)] + count: isize, +} + pub fn init(ctx: &Context) { PyStr::extend_class(ctx, ctx.types.str_type); From e21ec550d4cfa65e85f1ebe2696c1998b8865790 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 13 Jul 2025 00:22:41 +0900 Subject: [PATCH 028/819] Fix set___name__ and set___qualname__ deadlock (#5956) --- Lib/test/test_descr.py | 1 - vm/src/builtins/type.rs | 18 ++++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index b0414d5b00d..7698c340c8c 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4261,7 +4261,6 @@ class C(object): C.__name__ = 'D.E' self.assertEqual((C.__module__, C.__name__), (mod, 'D.E')) - @unittest.skip("TODO: RUSTPYTHON, rustpython hang") def test_evil_type_name(self): # A badly placed Py_DECREF in type_set_name led to arbitrary code # execution while the type structure was not in a sane state, and a diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index 806d32f906b..ded0ae4b3a3 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -680,7 +680,15 @@ impl PyType { .heaptype_ext .as_ref() .expect("HEAPTYPE should have heaptype_ext"); - *heap_type.qualname.write() = str_value; + + // Use std::mem::replace to swap the new value in and get the old value out, + // then drop the old value after releasing the lock + let _old_qualname = { + let mut qualname_guard = heap_type.qualname.write(); + std::mem::replace(&mut *qualname_guard, str_value) + }; + // old_qualname is dropped here, outside the lock scope + Ok(()) } @@ -837,7 +845,13 @@ impl PyType { return Err(vm.new_value_error("type name must not contain null characters")); } - *self.heaptype_ext.as_ref().unwrap().name.write() = name; + // Use std::mem::replace to swap the new value in and get the old value out, + // then drop the old value after releasing the lock (similar to CPython's Py_SETREF) + let _old_name = { + let mut name_guard = self.heaptype_ext.as_ref().unwrap().name.write(); + std::mem::replace(&mut *name_guard, name) + }; + // old_name is dropped here, outside the lock scope Ok(()) } From 52d46326de2de4da49d503d954382c2e3343790d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 13 Jul 2025 01:00:15 +0900 Subject: [PATCH 029/819] make_closure (#5955) --- compiler/codegen/src/compile.rs | 200 +++++++++++++++----------------- 1 file changed, 95 insertions(+), 105 deletions(-) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 0bfe4adb416..96f6f9a3c53 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -1959,24 +1959,13 @@ impl Compiler<'_> { ); } - if self.build_closure(&code) { - func_flags |= bytecode::MakeFunctionFlags::CLOSURE; - } - // Pop the special type params symbol table if type_params.is_some() { self.pop_symbol_table(); } - self.emit_load_const(ConstantData::Code { - code: Box::new(code), - }); - self.emit_load_const(ConstantData::Str { - value: qualname.into(), - }); - - // Turn code object into function object: - emit!(self, Instruction::MakeFunction(func_flags)); + // Create function with closure + self.make_closure(code, &qualname, func_flags)?; if let Some(value) = doc_str { emit!(self, Instruction::Duplicate); @@ -1993,44 +1982,86 @@ impl Compiler<'_> { self.store_name(name) } - fn build_closure(&mut self, code: &CodeObject) -> bool { - if code.freevars.is_empty() { - return false; - } - for var in &*code.freevars { - let table = self.symbol_table_stack.last().unwrap(); - let symbol = unwrap_internal( + /// Loads closure variables if needed and creates a function object + // = compiler_make_closure + fn make_closure( + &mut self, + code: CodeObject, + qualname: &str, + mut flags: bytecode::MakeFunctionFlags, + ) -> CompileResult<()> { + // Handle free variables (closure) + if !code.freevars.is_empty() { + // Build closure tuple by loading free variables + for var in &code.freevars { + let table = self.symbol_table_stack.last().unwrap(); + let symbol = match table.lookup(var) { + Some(s) => s, + None => { + return Err(self.error(CodegenErrorType::SyntaxError(format!( + "compiler_make_closure: cannot find symbol '{var}'", + )))); + } + }; + + let parent_code = self.code_stack.last().unwrap(); + let vars = match symbol.scope { + SymbolScope::Free => &parent_code.metadata.freevars, + SymbolScope::Cell => &parent_code.metadata.cellvars, + SymbolScope::TypeParams => &parent_code.metadata.cellvars, + _ if symbol.flags.contains(SymbolFlags::FREE_CLASS) => { + &parent_code.metadata.freevars + } + _ => { + return Err(self.error(CodegenErrorType::SyntaxError(format!( + "compiler_make_closure: invalid scope for '{var}'", + )))); + } + }; + + let idx = match vars.get_index_of(var) { + Some(i) => i, + None => { + return Err(self.error(CodegenErrorType::SyntaxError(format!( + "compiler_make_closure: cannot find '{var}' in parent vars", + )))); + } + }; + + let idx = if let SymbolScope::Free = symbol.scope { + idx + parent_code.metadata.cellvars.len() + } else { + idx + }; + + emit!(self, Instruction::LoadClosure(idx.to_u32())); + } + + // Build tuple of closure variables + emit!( self, - table - .lookup(var) - .ok_or_else(|| InternalError::MissingSymbol(var.to_owned())), - ); - let parent_code = self.code_stack.last().unwrap(); - let vars = match symbol.scope { - SymbolScope::Free => &parent_code.metadata.freevars, - SymbolScope::Cell => &parent_code.metadata.cellvars, - SymbolScope::TypeParams => &parent_code.metadata.cellvars, - _ if symbol.flags.contains(SymbolFlags::FREE_CLASS) => { - &parent_code.metadata.freevars + Instruction::BuildTuple { + size: code.freevars.len().to_u32(), } - x => unreachable!( - "var {} in a {:?} should be free or cell but it's {:?}", - var, table.typ, x - ), - }; - let mut idx = vars.get_index_of(var).unwrap(); - if let SymbolScope::Free = symbol.scope { - idx += parent_code.metadata.cellvars.len(); - } - emit!(self, Instruction::LoadClosure(idx.to_u32())) + ); + + flags |= bytecode::MakeFunctionFlags::CLOSURE; } - emit!( - self, - Instruction::BuildTuple { - size: code.freevars.len().to_u32(), - } - ); - true + + // Load code object + self.emit_load_const(ConstantData::Code { + code: Box::new(code), + }); + + // Load qualified name + self.emit_load_const(ConstantData::Str { + value: qualname.into(), + }); + + // Make function with proper flags + emit!(self, Instruction::MakeFunction(flags)); + + Ok(()) } // Python/compile.c find_ann @@ -2230,15 +2261,8 @@ impl Compiler<'_> { emit!(self, Instruction::LoadNameAny(dot_type_params)); func_flags |= bytecode::MakeFunctionFlags::TYPE_PARAMS; - if self.build_closure(&class_code) { - func_flags |= bytecode::MakeFunctionFlags::CLOSURE; - } - - self.emit_load_const(ConstantData::Code { - code: Box::new(class_code), - }); - self.emit_load_const(ConstantData::Str { value: name.into() }); - emit!(self, Instruction::MakeFunction(func_flags)); + // Create class function with closure + self.make_closure(class_code, name, func_flags)?; self.emit_load_const(ConstantData::Str { value: name.into() }); // Compile original bases @@ -2287,34 +2311,19 @@ impl Compiler<'_> { let type_params_code = self.exit_scope(); // Execute the type params function - if self.build_closure(&type_params_code) { - // Should not need closure - } - self.emit_load_const(ConstantData::Code { - code: Box::new(type_params_code), - }); - self.emit_load_const(ConstantData::Str { - value: format!("").into(), - }); - emit!( - self, - Instruction::MakeFunction(bytecode::MakeFunctionFlags::empty()) - ); + let type_params_name = format!(""); + self.make_closure( + type_params_code, + &type_params_name, + bytecode::MakeFunctionFlags::empty(), + )?; emit!(self, Instruction::CallFunctionPositional { nargs: 0 }); } else { // Non-generic class: standard path emit!(self, Instruction::LoadBuildClass); - let mut func_flags = bytecode::MakeFunctionFlags::empty(); - if self.build_closure(&class_code) { - func_flags |= bytecode::MakeFunctionFlags::CLOSURE; - } - - self.emit_load_const(ConstantData::Code { - code: Box::new(class_code), - }); - self.emit_load_const(ConstantData::Str { value: name.into() }); - emit!(self, Instruction::MakeFunction(func_flags)); + // Create class function with closure + self.make_closure(class_code, name, bytecode::MakeFunctionFlags::empty())?; self.emit_load_const(ConstantData::Str { value: name.into() }); let call = if let Some(arguments) = arguments { @@ -4026,7 +4035,7 @@ impl Compiler<'_> { let prev_ctx = self.ctx; let name = "".to_owned(); - let mut func_flags = self + let func_flags = self .enter_function(&name, parameters.as_deref().unwrap_or(&Default::default()))?; // Set qualname for lambda @@ -4046,15 +4055,9 @@ impl Compiler<'_> { self.compile_expression(body)?; self.emit_return_value(); let code = self.exit_scope(); - if self.build_closure(&code) { - func_flags |= bytecode::MakeFunctionFlags::CLOSURE; - } - self.emit_load_const(ConstantData::Code { - code: Box::new(code), - }); - self.emit_load_const(ConstantData::Str { value: name.into() }); - // Turn code object into function object: - emit!(self, Instruction::MakeFunction(func_flags)); + + // Create lambda function with closure + self.make_closure(code, &name, func_flags)?; self.ctx = prev_ctx; } @@ -4598,21 +4601,8 @@ impl Compiler<'_> { self.ctx = prev_ctx; - let mut func_flags = bytecode::MakeFunctionFlags::empty(); - if self.build_closure(&code) { - func_flags |= bytecode::MakeFunctionFlags::CLOSURE; - } - - // List comprehension code: - self.emit_load_const(ConstantData::Code { - code: Box::new(code), - }); - - // List comprehension function name: - self.emit_load_const(ConstantData::Str { value: name.into() }); - - // Turn code object into function object: - emit!(self, Instruction::MakeFunction(func_flags)); + // Create comprehension function with closure + self.make_closure(code, name, bytecode::MakeFunctionFlags::empty())?; // Evaluate iterated item: self.compile_expression(&generators[0].iter)?; From 16aaad7aebf83ed08aa1735bb96f3aca2b3e84d5 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 13 Jul 2025 10:20:07 +0900 Subject: [PATCH 030/819] PyTraceback Constructor (#5958) --- Lib/test/test_raise.py | 2 -- vm/src/builtins/traceback.rs | 19 ++++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py index 94f42c84f1a..3ada08f7dcf 100644 --- a/Lib/test/test_raise.py +++ b/Lib/test/test_raise.py @@ -270,8 +270,6 @@ def test_attrs(self): tb.tb_next = new_tb self.assertIs(tb.tb_next, new_tb) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_constructor(self): other_tb = get_tb() frame = sys._getframe() diff --git a/vm/src/builtins/traceback.rs b/vm/src/builtins/traceback.rs index 33063132c63..05e9944e099 100644 --- a/vm/src/builtins/traceback.rs +++ b/vm/src/builtins/traceback.rs @@ -1,8 +1,9 @@ use rustpython_common::lock::PyMutex; -use super::PyType; +use super::{PyType, PyTypeRef}; use crate::{ - Context, Py, PyPayload, PyRef, class::PyClassImpl, frame::FrameRef, source::LineNumber, + Context, Py, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, frame::FrameRef, + source::LineNumber, types::Constructor, }; #[pyclass(module = false, name = "traceback", traverse)] @@ -25,7 +26,7 @@ impl PyPayload for PyTraceback { } } -#[pyclass] +#[pyclass(with(Constructor))] impl PyTraceback { pub const fn new( next: Option>, @@ -67,6 +68,18 @@ impl PyTraceback { } } +impl Constructor for PyTraceback { + type Args = (Option>, FrameRef, u32, usize); + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + let (next, frame, lasti, lineno) = args; + let lineno = LineNumber::new(lineno) + .ok_or_else(|| vm.new_value_error("lineno must be positive".to_owned()))?; + let tb = PyTraceback::new(next, frame, lasti, lineno); + tb.into_ref_with_type(vm, cls).map(Into::into) + } +} + impl PyTracebackRef { pub fn iter(&self) -> impl Iterator { std::iter::successors(Some(self.clone()), |tb| tb.next.lock().clone()) From 8ab7aa2c6b5b40bb5bc907ec5c8071e18f705804 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 13 Jul 2025 13:12:03 +0900 Subject: [PATCH 031/819] type.__dict__ (#5957) --- extra_tests/snippets/builtin_type.py | 12 ++++++++++ vm/src/builtins/type.rs | 33 ++++++++++++++++------------ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/extra_tests/snippets/builtin_type.py b/extra_tests/snippets/builtin_type.py index 923028f2cdc..820ee366155 100644 --- a/extra_tests/snippets/builtin_type.py +++ b/extra_tests/snippets/builtin_type.py @@ -595,3 +595,15 @@ def my_repr_func(): # https://github.com/RustPython/RustPython/issues/3100 assert issubclass(types.BuiltinMethodType, types.BuiltinFunctionType) + +assert type.__dict__["__dict__"].__objclass__ is type +assert ( + type(type(type.__dict__["__dict__"]).__objclass__).__name__ == "member_descriptor" +) + + +class A(type): + pass + + +assert "__dict__" not in A.__dict__ diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index ded0ae4b3a3..1e18d6fd631 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -936,9 +936,9 @@ impl Constructor for PyType { return Err(vm.new_value_error("type name must not contain null characters")); } - let (metatype, base, bases) = if bases.is_empty() { + let (metatype, base, bases, base_is_type) = if bases.is_empty() { let base = vm.ctx.types.object_type.to_owned(); - (metatype, base.clone(), vec![base]) + (metatype, base.clone(), vec![base], false) } else { let bases = bases .iter() @@ -972,8 +972,9 @@ impl Constructor for PyType { }; let base = best_base(&bases, vm)?; + let base_is_type = base.is(vm.ctx.types.type_type); - (metatype, base.to_owned(), bases) + (metatype, base.to_owned(), bases, base_is_type) }; let qualname = dict @@ -1021,17 +1022,21 @@ impl Constructor for PyType { // All *classes* should have a dict. Exceptions are *instances* of // classes that define __slots__ and instances of built-in classes // (with exceptions, e.g function) - let __dict__ = identifier!(vm, __dict__); - attributes.entry(__dict__).or_insert_with(|| { - vm.ctx - .new_static_getset( - "__dict__", - vm.ctx.types.type_type, - subtype_get_dict, - subtype_set_dict, - ) - .into() - }); + // Also, type subclasses don't need their own __dict__ descriptor + // since they inherit it from type + if !base_is_type { + let __dict__ = identifier!(vm, __dict__); + attributes.entry(__dict__).or_insert_with(|| { + vm.ctx + .new_static_getset( + "__dict__", + vm.ctx.types.type_type, + subtype_get_dict, + subtype_set_dict, + ) + .into() + }); + } // TODO: Flags is currently initialized with HAS_DICT. Should be // updated when __slots__ are supported (toggling the flag off if From 04d8d69a8c040a956c130d62a8f4595c18f958c5 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Sun, 13 Jul 2025 22:19:33 -0700 Subject: [PATCH 032/819] upgrade parts of test.support (#5686) --- Lib/test/support/hypothesis_helper.py | 7 --- Lib/test/support/smtpd.py | 46 +++++++++--------- Lib/test/support/venv.py | 70 +++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 30 deletions(-) create mode 100644 Lib/test/support/venv.py diff --git a/Lib/test/support/hypothesis_helper.py b/Lib/test/support/hypothesis_helper.py index 40f58a2f59c..db93eea5e91 100644 --- a/Lib/test/support/hypothesis_helper.py +++ b/Lib/test/support/hypothesis_helper.py @@ -5,13 +5,6 @@ except ImportError: from . import _hypothesis_stubs as hypothesis else: - # Regrtest changes to use a tempdir as the working directory, so we have - # to tell Hypothesis to use the original in order to persist the database. - from .os_helper import SAVEDCWD - from hypothesis.configuration import set_hypothesis_home_dir - - set_hypothesis_home_dir(os.path.join(SAVEDCWD, ".hypothesis")) - # When using the real Hypothesis, we'll configure it to ignore occasional # slow tests (avoiding flakiness from random VM slowness in CI). hypothesis.settings.register_profile( diff --git a/Lib/test/support/smtpd.py b/Lib/test/support/smtpd.py index ec4e7d2f4ce..6052232ec2b 100644 --- a/Lib/test/support/smtpd.py +++ b/Lib/test/support/smtpd.py @@ -180,122 +180,122 @@ def _set_rset_state(self): @property def __server(self): warn("Access to __server attribute on SMTPChannel is deprecated, " - "use 'smtp_server' instead", DeprecationWarning, 2) + "use 'smtp_server' instead", DeprecationWarning, 2) return self.smtp_server @__server.setter def __server(self, value): warn("Setting __server attribute on SMTPChannel is deprecated, " - "set 'smtp_server' instead", DeprecationWarning, 2) + "set 'smtp_server' instead", DeprecationWarning, 2) self.smtp_server = value @property def __line(self): warn("Access to __line attribute on SMTPChannel is deprecated, " - "use 'received_lines' instead", DeprecationWarning, 2) + "use 'received_lines' instead", DeprecationWarning, 2) return self.received_lines @__line.setter def __line(self, value): warn("Setting __line attribute on SMTPChannel is deprecated, " - "set 'received_lines' instead", DeprecationWarning, 2) + "set 'received_lines' instead", DeprecationWarning, 2) self.received_lines = value @property def __state(self): warn("Access to __state attribute on SMTPChannel is deprecated, " - "use 'smtp_state' instead", DeprecationWarning, 2) + "use 'smtp_state' instead", DeprecationWarning, 2) return self.smtp_state @__state.setter def __state(self, value): warn("Setting __state attribute on SMTPChannel is deprecated, " - "set 'smtp_state' instead", DeprecationWarning, 2) + "set 'smtp_state' instead", DeprecationWarning, 2) self.smtp_state = value @property def __greeting(self): warn("Access to __greeting attribute on SMTPChannel is deprecated, " - "use 'seen_greeting' instead", DeprecationWarning, 2) + "use 'seen_greeting' instead", DeprecationWarning, 2) return self.seen_greeting @__greeting.setter def __greeting(self, value): warn("Setting __greeting attribute on SMTPChannel is deprecated, " - "set 'seen_greeting' instead", DeprecationWarning, 2) + "set 'seen_greeting' instead", DeprecationWarning, 2) self.seen_greeting = value @property def __mailfrom(self): warn("Access to __mailfrom attribute on SMTPChannel is deprecated, " - "use 'mailfrom' instead", DeprecationWarning, 2) + "use 'mailfrom' instead", DeprecationWarning, 2) return self.mailfrom @__mailfrom.setter def __mailfrom(self, value): warn("Setting __mailfrom attribute on SMTPChannel is deprecated, " - "set 'mailfrom' instead", DeprecationWarning, 2) + "set 'mailfrom' instead", DeprecationWarning, 2) self.mailfrom = value @property def __rcpttos(self): warn("Access to __rcpttos attribute on SMTPChannel is deprecated, " - "use 'rcpttos' instead", DeprecationWarning, 2) + "use 'rcpttos' instead", DeprecationWarning, 2) return self.rcpttos @__rcpttos.setter def __rcpttos(self, value): warn("Setting __rcpttos attribute on SMTPChannel is deprecated, " - "set 'rcpttos' instead", DeprecationWarning, 2) + "set 'rcpttos' instead", DeprecationWarning, 2) self.rcpttos = value @property def __data(self): warn("Access to __data attribute on SMTPChannel is deprecated, " - "use 'received_data' instead", DeprecationWarning, 2) + "use 'received_data' instead", DeprecationWarning, 2) return self.received_data @__data.setter def __data(self, value): warn("Setting __data attribute on SMTPChannel is deprecated, " - "set 'received_data' instead", DeprecationWarning, 2) + "set 'received_data' instead", DeprecationWarning, 2) self.received_data = value @property def __fqdn(self): warn("Access to __fqdn attribute on SMTPChannel is deprecated, " - "use 'fqdn' instead", DeprecationWarning, 2) + "use 'fqdn' instead", DeprecationWarning, 2) return self.fqdn @__fqdn.setter def __fqdn(self, value): warn("Setting __fqdn attribute on SMTPChannel is deprecated, " - "set 'fqdn' instead", DeprecationWarning, 2) + "set 'fqdn' instead", DeprecationWarning, 2) self.fqdn = value @property def __peer(self): warn("Access to __peer attribute on SMTPChannel is deprecated, " - "use 'peer' instead", DeprecationWarning, 2) + "use 'peer' instead", DeprecationWarning, 2) return self.peer @__peer.setter def __peer(self, value): warn("Setting __peer attribute on SMTPChannel is deprecated, " - "set 'peer' instead", DeprecationWarning, 2) + "set 'peer' instead", DeprecationWarning, 2) self.peer = value @property def __conn(self): warn("Access to __conn attribute on SMTPChannel is deprecated, " - "use 'conn' instead", DeprecationWarning, 2) + "use 'conn' instead", DeprecationWarning, 2) return self.conn @__conn.setter def __conn(self, value): warn("Setting __conn attribute on SMTPChannel is deprecated, " - "set 'conn' instead", DeprecationWarning, 2) + "set 'conn' instead", DeprecationWarning, 2) self.conn = value @property def __addr(self): warn("Access to __addr attribute on SMTPChannel is deprecated, " - "use 'addr' instead", DeprecationWarning, 2) + "use 'addr' instead", DeprecationWarning, 2) return self.addr @__addr.setter def __addr(self, value): warn("Setting __addr attribute on SMTPChannel is deprecated, " - "set 'addr' instead", DeprecationWarning, 2) + "set 'addr' instead", DeprecationWarning, 2) self.addr = value # Overrides base class for convenience. @@ -339,7 +339,7 @@ def found_terminator(self): command = line[:i].upper() arg = line[i+1:].strip() max_sz = (self.command_size_limits[command] - if self.extended_smtp else self.command_size_limit) + if self.extended_smtp else self.command_size_limit) if sz > max_sz: self.push('500 Error: line too long') return diff --git a/Lib/test/support/venv.py b/Lib/test/support/venv.py new file mode 100644 index 00000000000..78e6a51ec18 --- /dev/null +++ b/Lib/test/support/venv.py @@ -0,0 +1,70 @@ +import contextlib +import logging +import os +import subprocess +import shlex +import sys +import sysconfig +import tempfile +import venv + + +class VirtualEnvironment: + def __init__(self, prefix, **venv_create_args): + self._logger = logging.getLogger(self.__class__.__name__) + venv.create(prefix, **venv_create_args) + self._prefix = prefix + self._paths = sysconfig.get_paths( + scheme='venv', + vars={'base': self.prefix}, + expand=True, + ) + + @classmethod + @contextlib.contextmanager + def from_tmpdir(cls, *, prefix=None, dir=None, **venv_create_args): + delete = not bool(os.environ.get('PYTHON_TESTS_KEEP_VENV')) + with tempfile.TemporaryDirectory(prefix=prefix, dir=dir, delete=delete) as tmpdir: + yield cls(tmpdir, **venv_create_args) + + @property + def prefix(self): + return self._prefix + + @property + def paths(self): + return self._paths + + @property + def interpreter(self): + return os.path.join(self.paths['scripts'], os.path.basename(sys.executable)) + + def _format_output(self, name, data, indent='\t'): + if not data: + return indent + f'{name}: (none)' + if len(data.splitlines()) == 1: + return indent + f'{name}: {data}' + else: + prefixed_lines = '\n'.join(indent + '> ' + line for line in data.splitlines()) + return indent + f'{name}:\n' + prefixed_lines + + def run(self, *args, **subprocess_args): + if subprocess_args.get('shell'): + raise ValueError('Running the subprocess in shell mode is not supported.') + default_args = { + 'capture_output': True, + 'check': True, + } + try: + result = subprocess.run([self.interpreter, *args], **default_args | subprocess_args) + except subprocess.CalledProcessError as e: + if e.returncode != 0: + self._logger.error( + f'Interpreter returned non-zero exit status {e.returncode}.\n' + + self._format_output('COMMAND', shlex.join(e.cmd)) + '\n' + + self._format_output('STDOUT', e.stdout.decode()) + '\n' + + self._format_output('STDERR', e.stderr.decode()) + '\n' + ) + raise + else: + return result From ed8d7157d9bf9827329306a39ecb77481ea2f749 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 14 Jul 2025 08:20:28 +0300 Subject: [PATCH 033/819] Update `test_{complex,float}.py` from 3.13.5 (#5961) --- Lib/test/support/numbers.py | 80 +++++++ Lib/test/test_complex.py | 409 +++++++++++++++++++----------------- Lib/test/test_float.py | 8 +- 3 files changed, 298 insertions(+), 199 deletions(-) create mode 100644 Lib/test/support/numbers.py diff --git a/Lib/test/support/numbers.py b/Lib/test/support/numbers.py new file mode 100644 index 00000000000..d5dbb41aceb --- /dev/null +++ b/Lib/test/support/numbers.py @@ -0,0 +1,80 @@ +# These are shared with test_tokenize and other test modules. +# +# Note: since several test cases filter out floats by looking for "e" and ".", +# don't add hexadecimal literals that contain "e" or "E". +VALID_UNDERSCORE_LITERALS = [ + '0_0_0', + '4_2', + '1_0000_0000', + '0b1001_0100', + '0xffff_ffff', + '0o5_7_7', + '1_00_00.5', + '1_00_00.5e5', + '1_00_00e5_1', + '1e1_0', + '.1_4', + '.1_4e1', + '0b_0', + '0x_f', + '0o_5', + '1_00_00j', + '1_00_00.5j', + '1_00_00e5_1j', + '.1_4j', + '(1_2.5+3_3j)', + '(.5_6j)', +] +INVALID_UNDERSCORE_LITERALS = [ + # Trailing underscores: + '0_', + '42_', + '1.4j_', + '0x_', + '0b1_', + '0xf_', + '0o5_', + '0 if 1_Else 1', + # Underscores in the base selector: + '0_b0', + '0_xf', + '0_o5', + # Old-style octal, still disallowed: + '0_7', + '09_99', + # Multiple consecutive underscores: + '4_______2', + '0.1__4', + '0.1__4j', + '0b1001__0100', + '0xffff__ffff', + '0x___', + '0o5__77', + '1e1__0', + '1e1__0j', + # Underscore right before a dot: + '1_.4', + '1_.4j', + # Underscore right after a dot: + '1._4', + '1._4j', + '._5', + '._5j', + # Underscore right after a sign: + '1.0e+_1', + '1.0e+_1j', + # Underscore right before j: + '1.4_j', + '1.4e5_j', + # Underscore right before e: + '1_e1', + '1.4_e1', + '1.4_e1j', + # Underscore right after e: + '1e_1', + '1.4e_1', + '1.4e_1j', + # Complex cases with parens: + '(1+1.5_j_)', + '(1+1.5_j)', +] diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index dd3c4f281a3..86d075de8ce 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -1,15 +1,19 @@ import unittest import sys from test import support -from test.test_grammar import (VALID_UNDERSCORE_LITERALS, - INVALID_UNDERSCORE_LITERALS) +from test.support.testcase import ComplexesAreIdenticalMixin +from test.support.numbers import ( + VALID_UNDERSCORE_LITERALS, + INVALID_UNDERSCORE_LITERALS, +) from random import random -from math import atan2, isnan, copysign +from math import isnan, copysign import operator INF = float("inf") NAN = float("nan") +DBL_MAX = sys.float_info.max # These tests ensure that complex math does the right thing ZERO_DIVISION = ( @@ -20,7 +24,28 @@ (1, 0+0j), ) -class ComplexTest(unittest.TestCase): +class WithIndex: + def __init__(self, value): + self.value = value + def __index__(self): + return self.value + +class WithFloat: + def __init__(self, value): + self.value = value + def __float__(self): + return self.value + +class ComplexSubclass(complex): + pass + +class WithComplex: + def __init__(self, value): + self.value = value + def __complex__(self): + return self.value + +class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase): def assertAlmostEqual(self, a, b): if isinstance(a, complex): @@ -49,29 +74,6 @@ def assertCloseAbs(self, x, y, eps=1e-9): # check that relative difference < eps self.assertTrue(abs((x-y)/y) < eps) - def assertFloatsAreIdentical(self, x, y): - """assert that floats x and y are identical, in the sense that: - (1) both x and y are nans, or - (2) both x and y are infinities, with the same sign, or - (3) both x and y are zeros, with the same sign, or - (4) x and y are both finite and nonzero, and x == y - - """ - msg = 'floats {!r} and {!r} are not identical' - - if isnan(x) or isnan(y): - if isnan(x) and isnan(y): - return - elif x == y: - if x != 0.0: - return - # both zero; check that signs match - elif copysign(1.0, x) == copysign(1.0, y): - return - else: - msg += ': zeros have different signs' - self.fail(msg.format(x, y)) - def assertClose(self, x, y, eps=1e-9): """Return true iff complexes x and y "are close".""" self.assertCloseAbs(x.real, y.real, eps) @@ -303,6 +305,11 @@ def test_pow(self): except OverflowError: pass + # gh-113841: possible undefined division by 0 in _Py_c_pow() + x, y = 9j, 33j**3 + with self.assertRaises(OverflowError): + x**y + def test_pow_with_small_integer_exponents(self): # Check that small integer exponents are handled identically # regardless of their type. @@ -340,138 +347,93 @@ def test_boolcontext(self): def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_constructor(self): - class NS: - def __init__(self, value): self.value = value - def __complex__(self): return self.value - self.assertEqual(complex(NS(1+10j)), 1+10j) - self.assertRaises(TypeError, complex, NS(None)) - self.assertRaises(TypeError, complex, {}) - self.assertRaises(TypeError, complex, NS(1.5)) - self.assertRaises(TypeError, complex, NS(1)) - self.assertRaises(TypeError, complex, object()) - self.assertRaises(TypeError, complex, NS(4.25+0.5j), object()) - - self.assertAlmostEqual(complex("1+10j"), 1+10j) - self.assertAlmostEqual(complex(10), 10+0j) - self.assertAlmostEqual(complex(10.0), 10+0j) - self.assertAlmostEqual(complex(10), 10+0j) - self.assertAlmostEqual(complex(10+0j), 10+0j) - self.assertAlmostEqual(complex(1,10), 1+10j) - self.assertAlmostEqual(complex(1,10), 1+10j) - self.assertAlmostEqual(complex(1,10.0), 1+10j) - self.assertAlmostEqual(complex(1,10), 1+10j) - self.assertAlmostEqual(complex(1,10), 1+10j) - self.assertAlmostEqual(complex(1,10.0), 1+10j) - self.assertAlmostEqual(complex(1.0,10), 1+10j) - self.assertAlmostEqual(complex(1.0,10), 1+10j) - self.assertAlmostEqual(complex(1.0,10.0), 1+10j) - self.assertAlmostEqual(complex(3.14+0j), 3.14+0j) - self.assertAlmostEqual(complex(3.14), 3.14+0j) - self.assertAlmostEqual(complex(314), 314.0+0j) - self.assertAlmostEqual(complex(314), 314.0+0j) - self.assertAlmostEqual(complex(3.14+0j, 0j), 3.14+0j) - self.assertAlmostEqual(complex(3.14, 0.0), 3.14+0j) - self.assertAlmostEqual(complex(314, 0), 314.0+0j) - self.assertAlmostEqual(complex(314, 0), 314.0+0j) - self.assertAlmostEqual(complex(0j, 3.14j), -3.14+0j) - self.assertAlmostEqual(complex(0.0, 3.14j), -3.14+0j) - self.assertAlmostEqual(complex(0j, 3.14), 3.14j) - self.assertAlmostEqual(complex(0.0, 3.14), 3.14j) - self.assertAlmostEqual(complex("1"), 1+0j) - self.assertAlmostEqual(complex("1j"), 1j) - self.assertAlmostEqual(complex(), 0) - self.assertAlmostEqual(complex("-1"), -1) - self.assertAlmostEqual(complex("+1"), +1) - self.assertAlmostEqual(complex("(1+2j)"), 1+2j) - self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j) - self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j) - self.assertAlmostEqual(complex(" ( +3.14-6J )"), 3.14-6j) - self.assertAlmostEqual(complex(" ( +3.14-J )"), 3.14-1j) - self.assertAlmostEqual(complex(" ( +3.14+j )"), 3.14+1j) - self.assertAlmostEqual(complex("J"), 1j) - self.assertAlmostEqual(complex("( j )"), 1j) - self.assertAlmostEqual(complex("+J"), 1j) - self.assertAlmostEqual(complex("( -j)"), -1j) - self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j) - self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j) - self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j) - self.assertEqual(complex('1-1j'), 1.0 - 1j) - self.assertEqual(complex('1J'), 1j) - - class complex2(complex): pass - self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j) - self.assertAlmostEqual(complex(real=17, imag=23), 17+23j) - self.assertAlmostEqual(complex(real=17+23j), 17+23j) - self.assertAlmostEqual(complex(real=17+23j, imag=23), 17+46j) - self.assertAlmostEqual(complex(real=1+2j, imag=3+4j), -3+5j) + def check(z, x, y): + self.assertIs(type(z), complex) + self.assertFloatsAreIdentical(z.real, x) + self.assertFloatsAreIdentical(z.imag, y) + + check(complex(), 0.0, 0.0) + check(complex(10), 10.0, 0.0) + check(complex(4.25), 4.25, 0.0) + check(complex(4.25+0j), 4.25, 0.0) + check(complex(4.25+0.5j), 4.25, 0.5) + check(complex(ComplexSubclass(4.25+0.5j)), 4.25, 0.5) + check(complex(WithComplex(4.25+0.5j)), 4.25, 0.5) + + check(complex(1, 10), 1.0, 10.0) + check(complex(1, 10.0), 1.0, 10.0) + check(complex(1, 4.25), 1.0, 4.25) + check(complex(1.0, 10), 1.0, 10.0) + check(complex(4.25, 10), 4.25, 10.0) + check(complex(1.0, 10.0), 1.0, 10.0) + check(complex(4.25, 0.5), 4.25, 0.5) + + check(complex(4.25+0j, 0), 4.25, 0.0) + check(complex(ComplexSubclass(4.25+0j), 0), 4.25, 0.0) + check(complex(WithComplex(4.25+0j), 0), 4.25, 0.0) + check(complex(4.25j, 0), 0.0, 4.25) + check(complex(0j, 4.25), 0.0, 4.25) + check(complex(0, 4.25+0j), 0.0, 4.25) + check(complex(0, ComplexSubclass(4.25+0j)), 0.0, 4.25) + with self.assertRaisesRegex(TypeError, + "second argument must be a number, not 'WithComplex'"): + complex(0, WithComplex(4.25+0j)) + check(complex(0.0, 4.25j), -4.25, 0.0) + check(complex(4.25+0j, 0j), 4.25, 0.0) + check(complex(4.25j, 0j), 0.0, 4.25) + check(complex(0j, 4.25+0j), 0.0, 4.25) + check(complex(0j, 4.25j), -4.25, 0.0) + + check(complex(real=4.25), 4.25, 0.0) + check(complex(real=4.25+0j), 4.25, 0.0) + check(complex(real=4.25+1.5j), 4.25, 1.5) + check(complex(imag=1.5), 0.0, 1.5) + check(complex(real=4.25, imag=1.5), 4.25, 1.5) + check(complex(4.25, imag=1.5), 4.25, 1.5) # check that the sign of a zero in the real or imaginary part - # is preserved when constructing from two floats. (These checks - # are harmless on systems without support for signed zeros.) - def split_zeros(x): - """Function that produces different results for 0. and -0.""" - return atan2(x, -1.) - - self.assertEqual(split_zeros(complex(1., 0.).imag), split_zeros(0.)) - self.assertEqual(split_zeros(complex(1., -0.).imag), split_zeros(-0.)) - self.assertEqual(split_zeros(complex(0., 1.).real), split_zeros(0.)) - self.assertEqual(split_zeros(complex(-0., 1.).real), split_zeros(-0.)) - - c = 3.14 + 1j - self.assertTrue(complex(c) is c) - del c - - self.assertRaises(TypeError, complex, "1", "1") - self.assertRaises(TypeError, complex, 1, "1") - - # SF bug 543840: complex(string) accepts strings with \0 - # Fixed in 2.3. - self.assertRaises(ValueError, complex, '1+1j\0j') - - self.assertRaises(TypeError, int, 5+3j) - self.assertRaises(TypeError, int, 5+3j) - self.assertRaises(TypeError, float, 5+3j) - self.assertRaises(ValueError, complex, "") - self.assertRaises(TypeError, complex, None) - self.assertRaisesRegex(TypeError, "not 'NoneType'", complex, None) - self.assertRaises(ValueError, complex, "\0") - self.assertRaises(ValueError, complex, "3\09") - self.assertRaises(TypeError, complex, "1", "2") - self.assertRaises(TypeError, complex, "1", 42) - self.assertRaises(TypeError, complex, 1, "2") - self.assertRaises(ValueError, complex, "1+") - self.assertRaises(ValueError, complex, "1+1j+1j") - self.assertRaises(ValueError, complex, "--") - self.assertRaises(ValueError, complex, "(1+2j") - self.assertRaises(ValueError, complex, "1+2j)") - self.assertRaises(ValueError, complex, "1+(2j)") - self.assertRaises(ValueError, complex, "(1+2j)123") - self.assertRaises(ValueError, complex, "x") - self.assertRaises(ValueError, complex, "1j+2") - self.assertRaises(ValueError, complex, "1e1ej") - self.assertRaises(ValueError, complex, "1e++1ej") - self.assertRaises(ValueError, complex, ")1+2j(") - self.assertRaisesRegex( - TypeError, + # is preserved when constructing from two floats. + for x in 1.0, -1.0: + for y in 0.0, -0.0: + check(complex(x, y), x, y) + check(complex(y, x), y, x) + + c = complex(4.25, 1.5) + self.assertIs(complex(c), c) + c2 = ComplexSubclass(c) + self.assertEqual(c2, c) + self.assertIs(type(c2), ComplexSubclass) + del c, c2 + + self.assertRaisesRegex(TypeError, "first argument must be a string or a number, not 'dict'", - complex, {1:2}, 1) - self.assertRaisesRegex( - TypeError, + complex, {}) + self.assertRaisesRegex(TypeError, + "first argument must be a string or a number, not 'NoneType'", + complex, None) + self.assertRaisesRegex(TypeError, + "first argument must be a string or a number, not 'dict'", + complex, {1:2}, 0) + self.assertRaisesRegex(TypeError, + "can't take second arg if first is a string", + complex, '1', 0) + self.assertRaisesRegex(TypeError, "second argument must be a number, not 'dict'", - complex, 1, {1:2}) - # the following three are accepted by Python 2.6 - self.assertRaises(ValueError, complex, "1..1j") - self.assertRaises(ValueError, complex, "1.11.1j") - self.assertRaises(ValueError, complex, "1e1.1j") - - # check that complex accepts long unicode strings - self.assertEqual(type(complex("1"*500)), complex) - # check whitespace processing - self.assertEqual(complex('\N{EM SPACE}(\N{EN SPACE}1+1j ) '), 1+1j) - # Invalid unicode string - # See bpo-34087 - self.assertRaises(ValueError, complex, '\u3053\u3093\u306b\u3061\u306f') + complex, 0, {1:2}) + self.assertRaisesRegex(TypeError, + "second arg can't be a string", + complex, 0, '1') + + self.assertRaises(TypeError, complex, WithComplex(1.5)) + self.assertRaises(TypeError, complex, WithComplex(1)) + self.assertRaises(TypeError, complex, WithComplex(None)) + self.assertRaises(TypeError, complex, WithComplex(4.25+0j), object()) + self.assertRaises(TypeError, complex, WithComplex(1.5), object()) + self.assertRaises(TypeError, complex, WithComplex(1), object()) + self.assertRaises(TypeError, complex, WithComplex(None), object()) class EvilExc(Exception): pass @@ -482,33 +444,33 @@ def __complex__(self): self.assertRaises(EvilExc, complex, evilcomplex()) - class float2: - def __init__(self, value): - self.value = value - def __float__(self): - return self.value - - self.assertAlmostEqual(complex(float2(42.)), 42) - self.assertAlmostEqual(complex(real=float2(17.), imag=float2(23.)), 17+23j) - self.assertRaises(TypeError, complex, float2(None)) - - class MyIndex: - def __init__(self, value): - self.value = value - def __index__(self): - return self.value - - self.assertAlmostEqual(complex(MyIndex(42)), 42.0+0.0j) - self.assertAlmostEqual(complex(123, MyIndex(42)), 123.0+42.0j) - self.assertRaises(OverflowError, complex, MyIndex(2**2000)) - self.assertRaises(OverflowError, complex, 123, MyIndex(2**2000)) + check(complex(WithFloat(4.25)), 4.25, 0.0) + check(complex(WithFloat(4.25), 1.5), 4.25, 1.5) + check(complex(1.5, WithFloat(4.25)), 1.5, 4.25) + self.assertRaises(TypeError, complex, WithFloat(42)) + self.assertRaises(TypeError, complex, WithFloat(42), 1.5) + self.assertRaises(TypeError, complex, 1.5, WithFloat(42)) + self.assertRaises(TypeError, complex, WithFloat(None)) + self.assertRaises(TypeError, complex, WithFloat(None), 1.5) + self.assertRaises(TypeError, complex, 1.5, WithFloat(None)) + + check(complex(WithIndex(42)), 42.0, 0.0) + check(complex(WithIndex(42), 1.5), 42.0, 1.5) + check(complex(1.5, WithIndex(42)), 1.5, 42.0) + self.assertRaises(OverflowError, complex, WithIndex(2**2000)) + self.assertRaises(OverflowError, complex, WithIndex(2**2000), 1.5) + self.assertRaises(OverflowError, complex, 1.5, WithIndex(2**2000)) + self.assertRaises(TypeError, complex, WithIndex(None)) + self.assertRaises(TypeError, complex, WithIndex(None), 1.5) + self.assertRaises(TypeError, complex, 1.5, WithIndex(None)) class MyInt: def __int__(self): return 42 self.assertRaises(TypeError, complex, MyInt()) - self.assertRaises(TypeError, complex, 123, MyInt()) + self.assertRaises(TypeError, complex, MyInt(), 1.5) + self.assertRaises(TypeError, complex, 1.5, MyInt()) class complex0(complex): """Test usage of __complex__() when inheriting from 'complex'""" @@ -528,9 +490,9 @@ class complex2(complex): def __complex__(self): return None - self.assertEqual(complex(complex0(1j)), 42j) + check(complex(complex0(1j)), 0.0, 42.0) with self.assertWarns(DeprecationWarning): - self.assertEqual(complex(complex1(1j)), 2j) + check(complex(complex1(1j)), 0.0, 2.0) self.assertRaises(TypeError, complex, complex2(1j)) def test___complex__(self): @@ -538,36 +500,93 @@ def test___complex__(self): self.assertEqual(z.__complex__(), z) self.assertEqual(type(z.__complex__()), complex) - class complex_subclass(complex): - pass - - z = complex_subclass(3 + 4j) + z = ComplexSubclass(3 + 4j) self.assertEqual(z.__complex__(), 3 + 4j) self.assertEqual(type(z.__complex__()), complex) @support.requires_IEEE_754 def test_constructor_special_numbers(self): - class complex2(complex): - pass for x in 0.0, -0.0, INF, -INF, NAN: for y in 0.0, -0.0, INF, -INF, NAN: with self.subTest(x=x, y=y): z = complex(x, y) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) - z = complex2(x, y) - self.assertIs(type(z), complex2) + z = ComplexSubclass(x, y) + self.assertIs(type(z), ComplexSubclass) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) - z = complex(complex2(x, y)) + z = complex(ComplexSubclass(x, y)) self.assertIs(type(z), complex) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) - z = complex2(complex(x, y)) - self.assertIs(type(z), complex2) + z = ComplexSubclass(complex(x, y)) + self.assertIs(type(z), ComplexSubclass) self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) + def test_constructor_from_string(self): + def check(z, x, y): + self.assertIs(type(z), complex) + self.assertFloatsAreIdentical(z.real, x) + self.assertFloatsAreIdentical(z.imag, y) + + check(complex("1"), 1.0, 0.0) + check(complex("1j"), 0.0, 1.0) + check(complex("-1"), -1.0, 0.0) + check(complex("+1"), 1.0, 0.0) + check(complex("1+2j"), 1.0, 2.0) + check(complex("(1+2j)"), 1.0, 2.0) + check(complex("(1.5+4.25j)"), 1.5, 4.25) + check(complex("4.25+1J"), 4.25, 1.0) + check(complex(" ( +4.25-6J )"), 4.25, -6.0) + check(complex(" ( +4.25-J )"), 4.25, -1.0) + check(complex(" ( +4.25+j )"), 4.25, 1.0) + check(complex("J"), 0.0, 1.0) + check(complex("( j )"), 0.0, 1.0) + check(complex("+J"), 0.0, 1.0) + check(complex("( -j)"), 0.0, -1.0) + check(complex('1-1j'), 1.0, -1.0) + check(complex('1J'), 0.0, 1.0) + + check(complex('1e-500'), 0.0, 0.0) + check(complex('-1e-500j'), 0.0, -0.0) + check(complex('1e-500+1e-500j'), 0.0, 0.0) + check(complex('-1e-500+1e-500j'), -0.0, 0.0) + check(complex('1e-500-1e-500j'), 0.0, -0.0) + check(complex('-1e-500-1e-500j'), -0.0, -0.0) + + # SF bug 543840: complex(string) accepts strings with \0 + # Fixed in 2.3. + self.assertRaises(ValueError, complex, '1+1j\0j') + self.assertRaises(ValueError, complex, "") + self.assertRaises(ValueError, complex, "\0") + self.assertRaises(ValueError, complex, "3\09") + self.assertRaises(ValueError, complex, "1+") + self.assertRaises(ValueError, complex, "1+1j+1j") + self.assertRaises(ValueError, complex, "--") + self.assertRaises(ValueError, complex, "(1+2j") + self.assertRaises(ValueError, complex, "1+2j)") + self.assertRaises(ValueError, complex, "1+(2j)") + self.assertRaises(ValueError, complex, "(1+2j)123") + self.assertRaises(ValueError, complex, "x") + self.assertRaises(ValueError, complex, "1j+2") + self.assertRaises(ValueError, complex, "1e1ej") + self.assertRaises(ValueError, complex, "1e++1ej") + self.assertRaises(ValueError, complex, ")1+2j(") + # the following three are accepted by Python 2.6 + self.assertRaises(ValueError, complex, "1..1j") + self.assertRaises(ValueError, complex, "1.11.1j") + self.assertRaises(ValueError, complex, "1e1.1j") + + # check that complex accepts long unicode strings + self.assertIs(type(complex("1"*500)), complex) + # check whitespace processing + self.assertEqual(complex('\N{EM SPACE}(\N{EN SPACE}1+1j ) '), 1+1j) + # Invalid unicode string + # See bpo-34087 + self.assertRaises(ValueError, complex, '\u3053\u3093\u306b\u3061\u306f') + def test_constructor_negative_nans_from_string(self): self.assertEqual(copysign(1., complex("-nan").real), -1.) self.assertEqual(copysign(1., complex("-nanj").imag), -1.) @@ -589,7 +608,7 @@ def test_underscores(self): def test_hash(self): for x in range(-30, 30): self.assertEqual(hash(x), hash(complex(x, 0))) - x /= 3.0 # now check against floating point + x /= 3.0 # now check against floating-point self.assertEqual(hash(x), hash(complex(x, 0.))) self.assertNotEqual(hash(2000005 - 1j), -1) @@ -599,6 +618,8 @@ def test_abs(self): for num in nums: self.assertAlmostEqual((num.real**2 + num.imag**2) ** 0.5, abs(num)) + self.assertRaises(OverflowError, abs, complex(DBL_MAX, DBL_MAX)) + def test_repr_str(self): def test(v, expected, test_fn=self.assertEqual): test_fn(repr(v), expected) @@ -644,9 +665,6 @@ def test(v, expected, test_fn=self.assertEqual): test(complex(-0., -0.), "(-0-0j)") def test_pos(self): - class ComplexSubclass(complex): - pass - self.assertEqual(+(1+6j), 1+6j) self.assertEqual(+ComplexSubclass(1, 6), 1+6j) self.assertIs(type(+ComplexSubclass(1, 6)), complex) @@ -666,8 +684,8 @@ def test_getnewargs(self): def test_plus_minus_0j(self): # test that -0j and 0j literals are not identified z1, z2 = 0j, -0j - self.assertEqual(atan2(z1.imag, -1.), atan2(0., -1.)) - self.assertEqual(atan2(z2.imag, -1.), atan2(-0., -1.)) + self.assertFloatsAreIdentical(z1.imag, 0.0) + self.assertFloatsAreIdentical(z2.imag, -0.0) @support.requires_IEEE_754 def test_negated_imaginary_literal(self): @@ -702,8 +720,7 @@ def test_repr_roundtrip(self): for y in vals: z = complex(x, y) roundtrip = complex(repr(z)) - self.assertFloatsAreIdentical(z.real, roundtrip.real) - self.assertFloatsAreIdentical(z.imag, roundtrip.imag) + self.assertComplexesAreIdentical(z, roundtrip) # if we predefine some constants, then eval(repr(z)) should # also work, except that it might change the sign of zeros diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py index d94a2bdadd8..2ccad19e03a 100644 --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -9,8 +9,10 @@ from test import support from test.support.testcase import FloatsAreIdenticalMixin -from test.test_grammar import (VALID_UNDERSCORE_LITERALS, - INVALID_UNDERSCORE_LITERALS) +from test.support.numbers import ( + VALID_UNDERSCORE_LITERALS, + INVALID_UNDERSCORE_LITERALS, +) from math import isinf, isnan, copysign, ldexp import math @@ -1513,4 +1515,4 @@ def __init__(self, value): if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() From d42e8f00425630fcd0effcc74d89a119c197cdaa Mon Sep 17 00:00:00 2001 From: Jiseok CHOI Date: Mon, 14 Jul 2025 14:21:36 +0900 Subject: [PATCH 034/819] fix(sqlite): produce correct error for surrogate characters (#5962) --- Lib/test/test_sqlite3/test_userfunctions.py | 2 -- stdlib/src/sqlite.rs | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_sqlite3/test_userfunctions.py b/Lib/test/test_sqlite3/test_userfunctions.py index 7b092365d4b..e8b98a66a57 100644 --- a/Lib/test/test_sqlite3/test_userfunctions.py +++ b/Lib/test/test_sqlite3/test_userfunctions.py @@ -354,8 +354,6 @@ def test_return_non_contiguous_blob(self): cur = self.con.execute("select return_noncont_blob()") cur.fetchone() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_param_surrogates(self): self.assertRaisesRegex(UnicodeEncodeError, "surrogates not allowed", self.con.execute, "select spam(?)", diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs index cec1f04ed92..073975f8fe3 100644 --- a/stdlib/src/sqlite.rs +++ b/stdlib/src/sqlite.rs @@ -2965,12 +2965,10 @@ mod _sqlite { } fn str_to_ptr_len(s: &PyStr, vm: &VirtualMachine) -> PyResult<(*const libc::c_char, i32)> { - let s = s - .to_str() - .ok_or_else(|| vm.new_unicode_encode_error("surrogates not allowed"))?; - let len = c_int::try_from(s.len()) + let s_str = s.try_to_str(vm)?; + let len = c_int::try_from(s_str.len()) .map_err(|_| vm.new_overflow_error("TEXT longer than INT_MAX bytes"))?; - let ptr = s.as_ptr().cast(); + let ptr = s_str.as_ptr().cast(); Ok((ptr, len)) } From 97e85b220e48a71cb1ef1c7b05b48cd70d2327b4 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 14 Jul 2025 08:21:59 +0300 Subject: [PATCH 035/819] Update `test_{dict,weakref}.py` from 3.13.5 (#5963) * Update test_dict.py from 3.13.5 * Update `test_weakref.py` from 3.13.5 --- Lib/test/support/__init__.py | 16 +++ Lib/test/test_dict.py | 64 +++++++++-- Lib/test/test_weakref.py | 209 ++++++++++++++++++++++++++--------- 3 files changed, 226 insertions(+), 63 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 948bad1ca80..6c7e7997980 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2589,6 +2589,22 @@ def adjust_int_max_str_digits(max_digits): finally: sys.set_int_max_str_digits(current) + +# From CPython 3.13.5 +def get_c_recursion_limit(): + try: + import _testcapi + return _testcapi.Py_C_RECURSION_LIMIT + except ImportError: + raise unittest.SkipTest('requires _testcapi') + + +# From CPython 3.13.5 +def exceeds_recursion_limit(): + """For recursion tests, easily exceeds default recursion limit.""" + return get_c_recursion_limit() * 3 + + #For recursion tests, easily exceeds default recursion limit EXCEEDS_RECURSION_LIMIT = 5000 diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 4aa6f1089a4..9598a7ab962 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -8,7 +8,7 @@ import unittest import weakref from test import support -from test.support import import_helper, C_RECURSION_LIMIT +from test.support import import_helper, get_c_recursion_limit class DictTest(unittest.TestCase): @@ -312,17 +312,34 @@ def __setitem__(self, key, value): self.assertRaises(Exc, baddict2.fromkeys, [1]) # test fast path for dictionary inputs + res = dict(zip(range(6), [0]*6)) d = dict(zip(range(6), range(6))) - self.assertEqual(dict.fromkeys(d, 0), dict(zip(range(6), [0]*6))) - + self.assertEqual(dict.fromkeys(d, 0), res) + # test fast path for set inputs + d = set(range(6)) + self.assertEqual(dict.fromkeys(d, 0), res) + # test slow path for other iterable inputs + d = list(range(6)) + self.assertEqual(dict.fromkeys(d, 0), res) + + # test fast path when object's constructor returns large non-empty dict class baddict3(dict): def __new__(cls): return d - d = {i : i for i in range(10)} + d = {i : i for i in range(1000)} res = d.copy() res.update(a=None, b=None, c=None) self.assertEqual(baddict3.fromkeys({"a", "b", "c"}), res) + # test slow path when object is a proper subclass of dict + class baddict4(dict): + def __init__(self): + dict.__init__(self, d) + d = {i : i for i in range(1000)} + res = d.copy() + res.update(a=None, b=None, c=None) + self.assertEqual(baddict4.fromkeys({"a", "b", "c"}), res) + def test_copy(self): d = {1: 1, 2: 2, 3: 3} self.assertIsNot(d.copy(), d) @@ -596,10 +613,9 @@ def __repr__(self): d = {1: BadRepr()} self.assertRaises(Exc, repr, d) - @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON Windows') def test_repr_deep(self): d = {} - for i in range(C_RECURSION_LIMIT + 1): + for i in range(get_c_recursion_limit() + 1): d = {1: d} self.assertRaises(RecursionError, repr, d) @@ -994,6 +1010,18 @@ class MyDict(dict): pass self._tracked(MyDict()) + @support.cpython_only + def test_track_lazy_instance_dicts(self): + class C: + pass + o = C() + d = o.__dict__ + self._not_tracked(d) + o.untracked = 42 + self._not_tracked(d) + o.tracked = [] + self._tracked(d) + def make_shared_key_dict(self, n): class C: pass @@ -1108,10 +1136,8 @@ class C: a = C() a.x = 1 d = a.__dict__ - before_resize = sys.getsizeof(d) d[2] = 2 # split table is resized to a generic combined table - self.assertGreater(sys.getsizeof(d), before_resize) self.assertEqual(list(d), ['x', 2]) def test_iterator_pickling(self): @@ -1485,6 +1511,24 @@ def test_dict_items_result_gc_reversed(self): gc.collect() self.assertTrue(gc.is_tracked(next(it))) + def test_store_evilattr(self): + class EvilAttr: + def __init__(self, d): + self.d = d + + def __del__(self): + if 'attr' in self.d: + del self.d['attr'] + gc.collect() + + class Obj: + pass + + obj = Obj() + obj.__dict__ = {} + for _ in range(10): + obj.attr = EvilAttr(obj.__dict__) + def test_str_nonstr(self): # cpython uses a different lookup function if the dict only contains # `str` keys. Make sure the unoptimized path is used when a non-`str` @@ -1591,8 +1635,8 @@ class CAPITest(unittest.TestCase): # Test _PyDict_GetItem_KnownHash() @support.cpython_only def test_getitem_knownhash(self): - _testcapi = import_helper.import_module('_testcapi') - dict_getitem_knownhash = _testcapi.dict_getitem_knownhash + _testinternalcapi = import_helper.import_module('_testinternalcapi') + dict_getitem_knownhash = _testinternalcapi.dict_getitem_knownhash d = {'x': 1, 'y': 2, 'z': 3} self.assertEqual(dict_getitem_knownhash(d, 'x', hash('x')), 1) diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 7d204f3c4c6..242c076f9b3 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -1,5 +1,6 @@ import gc import sys +import doctest import unittest import collections import weakref @@ -9,10 +10,14 @@ import threading import time import random +import textwrap from test import support -from test.support import script_helper, ALWAYS_EQ +from test.support import script_helper, ALWAYS_EQ, suppress_immortalization from test.support import gc_collect +from test.support import import_helper +from test.support import threading_helper +from test.support import is_wasi, Py_DEBUG # Used in ReferencesTestCase.test_ref_created_during_del() . ref_from_del = None @@ -77,7 +82,7 @@ def callback(self, ref): @contextlib.contextmanager -def collect_in_thread(period=0.0001): +def collect_in_thread(period=0.005): """ Ensure GC collections happen in a different thread, at a high frequency. """ @@ -114,6 +119,49 @@ def test_basic_ref(self): del o repr(wr) + @support.cpython_only + def test_ref_repr(self): + obj = C() + ref = weakref.ref(obj) + regex = ( + rf"" + ) + self.assertRegex(repr(ref), regex) + + obj = None + gc_collect() + self.assertRegex(repr(ref), + rf'') + + # test type with __name__ + class WithName: + @property + def __name__(self): + return "custom_name" + + obj2 = WithName() + ref2 = weakref.ref(obj2) + regex = ( + rf"" + ) + self.assertRegex(repr(ref2), regex) + + def test_repr_failure_gh99184(self): + class MyConfig(dict): + def __getattr__(self, x): + return self[x] + + obj = MyConfig(offset=5) + obj_weakref = weakref.ref(obj) + + self.assertIn('MyConfig', repr(obj_weakref)) + self.assertIn('MyConfig', str(obj_weakref)) + def test_basic_callback(self): self.check_basic_callback(C) self.check_basic_callback(create_function) @@ -121,7 +169,7 @@ def test_basic_callback(self): @support.cpython_only def test_cfunction(self): - import _testcapi + _testcapi = import_helper.import_module("_testcapi") create_cfunction = _testcapi.create_cfunction f = create_cfunction() wr = weakref.ref(f) @@ -182,6 +230,22 @@ def check(proxy): self.assertRaises(ReferenceError, bool, ref3) self.assertEqual(self.cbcalled, 2) + @support.cpython_only + def test_proxy_repr(self): + obj = C() + ref = weakref.proxy(obj, self.callback) + regex = ( + rf"" + ) + self.assertRegex(repr(ref), regex) + + obj = None + gc_collect() + self.assertRegex(repr(ref), + rf'') + def check_basic_ref(self, factory): o = factory() ref = weakref.ref(o) @@ -613,7 +677,8 @@ class C(object): # deallocation of c2. del c2 - def test_callback_in_cycle_1(self): + @suppress_immortalization() + def test_callback_in_cycle(self): import gc class J(object): @@ -653,40 +718,11 @@ def acallback(self, ignore): del I, J, II gc.collect() - def test_callback_in_cycle_2(self): + def test_callback_reachable_one_way(self): import gc - # This is just like test_callback_in_cycle_1, except that II is an - # old-style class. The symptom is different then: an instance of an - # old-style class looks in its own __dict__ first. 'J' happens to - # get cleared from I.__dict__ before 'wr', and 'J' was never in II's - # __dict__, so the attribute isn't found. The difference is that - # the old-style II doesn't have a NULL __mro__ (it doesn't have any - # __mro__), so no segfault occurs. Instead it got: - # test_callback_in_cycle_2 (__main__.ReferencesTestCase) ... - # Exception exceptions.AttributeError: - # "II instance has no attribute 'J'" in > ignored - - class J(object): - pass - - class II: - def acallback(self, ignore): - self.J - - I = II() - I.J = J - I.wr = weakref.ref(J, I.acallback) - - del I, J, II - gc.collect() - - def test_callback_in_cycle_3(self): - import gc - - # This one broke the first patch that fixed the last two. In this - # case, the objects reachable from the callback aren't also reachable + # This one broke the first patch that fixed the previous test. In this case, + # the objects reachable from the callback aren't also reachable # from the object (c1) *triggering* the callback: you can get to # c1 from c2, but not vice-versa. The result was that c2's __dict__ # got tp_clear'ed by the time the c2.cb callback got invoked. @@ -706,10 +742,10 @@ def cb(self, ignore): del c1, c2 gc.collect() - def test_callback_in_cycle_4(self): + def test_callback_different_classes(self): import gc - # Like test_callback_in_cycle_3, except c2 and c1 have different + # Like test_callback_reachable_one_way, except c2 and c1 have different # classes. c2's class (C) isn't reachable from c1 then, so protecting # objects reachable from the dying object (c1) isn't enough to stop # c2's class (C) from getting tp_clear'ed before c2.cb is invoked. @@ -736,6 +772,7 @@ class D: # TODO: RUSTPYTHON @unittest.expectedFailure + @suppress_immortalization() def test_callback_in_cycle_resurrection(self): import gc @@ -879,6 +916,7 @@ def test_init(self): # No exception should be raised here gc.collect() + @suppress_immortalization() def test_classes(self): # Check that classes are weakrefable. class A(object): @@ -958,6 +996,7 @@ def test_hashing(self): self.assertEqual(hash(a), hash(42)) self.assertRaises(TypeError, hash, b) + @unittest.skipIf(is_wasi and Py_DEBUG, "requires deep stack") def test_trashcan_16602(self): # Issue #16602: when a weakref's target was part of a long # deallocation chain, the trashcan mechanism could delay clearing @@ -1015,6 +1054,31 @@ def __del__(self): pass del x support.gc_collect() + @support.cpython_only + def test_no_memory_when_clearing(self): + # gh-118331: Make sure we do not raise an exception from the destructor + # when clearing weakrefs if allocating the intermediate tuple fails. + code = textwrap.dedent(""" + import _testcapi + import weakref + + class TestObj: + pass + + def callback(obj): + pass + + obj = TestObj() + # The choice of 50 is arbitrary, but must be large enough to ensure + # the allocation won't be serviced by the free list. + wrs = [weakref.ref(obj, callback) for _ in range(50)] + _testcapi.set_nomemory(0) + del obj + """).strip() + res, _ = script_helper.run_python_until_end("-c", code) + stderr = res.err.decode("ascii", "backslashreplace") + self.assertNotRegex(stderr, "_Py_Dealloc: Deallocator of type 'TestObj'") + class SubclassableWeakrefTestCase(TestBase): @@ -1267,6 +1331,12 @@ class MappingTestCase(TestBase): COUNT = 10 + if support.check_sanitizer(thread=True) and support.Py_GIL_DISABLED: + # Reduce iteration count to get acceptable latency + NUM_THREADED_ITERATIONS = 1000 + else: + NUM_THREADED_ITERATIONS = 100000 + def check_len_cycles(self, dict_type, cons): N = 20 items = [RefCycle() for i in range(N)] @@ -1898,34 +1968,56 @@ def test_make_weak_keyed_dict_repr(self): dict = weakref.WeakKeyDictionary() self.assertRegex(repr(dict), '') + @threading_helper.requires_working_threading() def test_threaded_weak_valued_setdefault(self): d = weakref.WeakValueDictionary() with collect_in_thread(): - for i in range(100000): + for i in range(self.NUM_THREADED_ITERATIONS): x = d.setdefault(10, RefCycle()) self.assertIsNot(x, None) # we never put None in there! del x + @threading_helper.requires_working_threading() def test_threaded_weak_valued_pop(self): d = weakref.WeakValueDictionary() with collect_in_thread(): - for i in range(100000): + for i in range(self.NUM_THREADED_ITERATIONS): d[10] = RefCycle() x = d.pop(10, 10) self.assertIsNot(x, None) # we never put None in there! + @threading_helper.requires_working_threading() def test_threaded_weak_valued_consistency(self): # Issue #28427: old keys should not remove new values from # WeakValueDictionary when collecting from another thread. d = weakref.WeakValueDictionary() with collect_in_thread(): - for i in range(200000): + for i in range(2 * self.NUM_THREADED_ITERATIONS): o = RefCycle() d[10] = o # o is still alive, so the dict can't be empty self.assertEqual(len(d), 1) o = None # lose ref + @support.cpython_only + def test_weak_valued_consistency(self): + # A single-threaded, deterministic repro for issue #28427: old keys + # should not remove new values from WeakValueDictionary. This relies on + # an implementation detail of CPython's WeakValueDictionary (its + # underlying dictionary of KeyedRefs) to reproduce the issue. + d = weakref.WeakValueDictionary() + with support.disable_gc(): + d[10] = RefCycle() + # Keep the KeyedRef alive after it's replaced so that GC will invoke + # the callback. + wr = d.data[10] + # Replace the value with something that isn't cyclic garbage + o = RefCycle() + d[10] = o + # Trigger GC, which will invoke the callback for `wr` + gc.collect() + self.assertEqual(len(d), 1) + def check_threaded_weak_dict_copy(self, type_, deepcopy): # `type_` should be either WeakKeyDictionary or WeakValueDictionary. # `deepcopy` should be either True or False. @@ -1987,22 +2079,28 @@ def pop_and_collect(lst): if exc: raise exc[0] + @threading_helper.requires_working_threading() def test_threaded_weak_key_dict_copy(self): # Issue #35615: Weakref keys or values getting GC'ed during dict # copying should not result in a crash. self.check_threaded_weak_dict_copy(weakref.WeakKeyDictionary, False) + @threading_helper.requires_working_threading() + @support.requires_resource('cpu') def test_threaded_weak_key_dict_deepcopy(self): # Issue #35615: Weakref keys or values getting GC'ed during dict # copying should not result in a crash. self.check_threaded_weak_dict_copy(weakref.WeakKeyDictionary, True) @unittest.skip("TODO: RUSTPYTHON; occasionally crash (Exit code -6)") + @threading_helper.requires_working_threading() def test_threaded_weak_value_dict_copy(self): # Issue #35615: Weakref keys or values getting GC'ed during dict # copying should not result in a crash. self.check_threaded_weak_dict_copy(weakref.WeakValueDictionary, False) + @threading_helper.requires_working_threading() + @support.requires_resource('cpu') def test_threaded_weak_value_dict_deepcopy(self): # Issue #35615: Weakref keys or values getting GC'ed during dict # copying should not result in a crash. @@ -2195,6 +2293,19 @@ def test_atexit(self): self.assertTrue(b'ZeroDivisionError' in err) +class ModuleTestCase(unittest.TestCase): + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_names(self): + for name in ('ReferenceType', 'ProxyType', 'CallableProxyType', + 'WeakMethod', 'WeakSet', 'WeakKeyDictionary', 'WeakValueDictionary'): + obj = getattr(weakref, name) + if name != 'WeakSet': + self.assertEqual(obj.__module__, 'weakref') + self.assertEqual(obj.__name__, name) + self.assertEqual(obj.__qualname__, name) + + libreftest = """ Doctest for examples in the library reference: weakref.rst >>> from test.support import gc_collect @@ -2283,19 +2394,11 @@ def test_atexit(self): __test__ = {'libreftest' : libreftest} -def test_main(): - support.run_unittest( - ReferencesTestCase, - WeakMethodTestCase, - MappingTestCase, - WeakValueDictionaryTestCase, - WeakKeyDictionaryTestCase, - SubclassableWeakrefTestCase, - FinalizeTestCase, - ) +def load_tests(loader, tests, pattern): # TODO: RUSTPYTHON - # support.run_doctest(sys.modules[__name__]) + # tests.addTest(doctest.DocTestSuite()) + return tests if __name__ == "__main__": - test_main() + unittest.main() From 5ab64b7002a42b38ac29c365652ba1bc3441d232 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI Date: Mon, 14 Jul 2025 14:22:52 +0900 Subject: [PATCH 036/819] fix(sqlite): align adaptation protocol with CPython (#5964) --- Lib/test/test_sqlite3/test_types.py | 4 ---- stdlib/src/sqlite.rs | 12 ++++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_sqlite3/test_types.py b/Lib/test/test_sqlite3/test_types.py index 6cbf99d6ea3..53df08e9995 100644 --- a/Lib/test/test_sqlite3/test_types.py +++ b/Lib/test/test_sqlite3/test_types.py @@ -439,8 +439,6 @@ def test_missing_protocol(self): with self.assertRaises(sqlite.ProgrammingError): sqlite.adapt(1, None) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_defect_proto(self): class DefectProto(): def __adapt__(self): @@ -448,8 +446,6 @@ def __adapt__(self): with self.assertRaises(sqlite.ProgrammingError): sqlite.adapt(1., DefectProto) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_defect_self_adapt(self): class DefectSelfAdapt(float): def __conform__(self, _): diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs index 073975f8fe3..ce84ac29884 100644 --- a/stdlib/src/sqlite.rs +++ b/stdlib/src/sqlite.rs @@ -695,7 +695,11 @@ mod _sqlite { } if let Ok(adapter) = proto.get_attr("__adapt__", vm) { match adapter.call((obj,), vm) { - Ok(val) => return Ok(val), + Ok(val) => { + if !vm.is_none(&val) { + return Ok(val); + } + } Err(exc) => { if !exc.fast_isinstance(vm.ctx.exceptions.type_error) { return Err(exc); @@ -705,7 +709,11 @@ mod _sqlite { } if let Ok(adapter) = obj.get_attr("__conform__", vm) { match adapter.call((proto,), vm) { - Ok(val) => return Ok(val), + Ok(val) => { + if !vm.is_none(&val) { + return Ok(val); + } + } Err(exc) => { if !exc.fast_isinstance(vm.ctx.exceptions.type_error) { return Err(exc); From 4fe4ff4f998cccdb923852642485dd5cc41ef7cd Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 14 Jul 2025 08:24:00 +0300 Subject: [PATCH 037/819] Update `test_{list,listcomps}.py` from 3.13.5 (#5965) --- Lib/test/test_list.py | 55 ++++++++++++++++++++++++++++++++++++-- Lib/test/test_listcomps.py | 13 ++++++--- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index c82bf5067d6..42d8dcbbe14 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -1,6 +1,8 @@ import sys +import textwrap from test import list_tests from test.support import cpython_only +from test.support.script_helper import assert_python_ok import pickle import unittest @@ -98,8 +100,13 @@ def imul(a, b): a *= b self.assertRaises((MemoryError, OverflowError), mul, lst, n) self.assertRaises((MemoryError, OverflowError), imul, lst, n) + def test_empty_slice(self): + x = [] + x[:] = x + self.assertEqual(x, []) + # TODO: RUSTPYTHON - @unittest.skip("Crashes on windows debug build") + @unittest.skip("TODO: RUSTPYTHON crash") def test_list_resize_overflow(self): # gh-97616: test new_allocated * sizeof(PyObject*) overflow # check in list_resize() @@ -113,13 +120,28 @@ def test_list_resize_overflow(self): with self.assertRaises((MemoryError, OverflowError)): lst *= size + # TODO: RUSTPYTHON + @unittest.skip("TODO: RUSTPYTHON hangs") + def test_repr_mutate(self): + class Obj: + @staticmethod + def __repr__(): + try: + mylist.pop() + except IndexError: + pass + return 'obj' + + mylist = [Obj() for _ in range(5)] + self.assertEqual(repr(mylist), '[obj, obj, obj]') + def test_repr_large(self): # Check the repr of large list objects def check(n): l = [0] * n s = repr(l) self.assertEqual(s, - '[' + ', '.join(['0'] * n) + ']') + '[' + ', '.join(['0'] * n) + ']') check(10) # check our checking code check(1000000) @@ -302,6 +324,35 @@ def __eq__(self, other): lst = [X(), X()] X() in lst + def test_tier2_invalidates_iterator(self): + # GH-121012 + for _ in range(100): + a = [1, 2, 3] + it = iter(a) + for _ in it: + pass + a.append(4) + self.assertEqual(list(it), []) + + def test_deopt_from_append_list(self): + # gh-132011: it used to crash, because + # of `CALL_LIST_APPEND` specialization failure. + code = textwrap.dedent(""" + l = [] + def lappend(l, x, y): + l.append((x, y)) + for x in range(3): + lappend(l, None, None) + try: + lappend(list, None, None) + except TypeError: + pass + else: + raise AssertionError + """) + + rc, _, _ = assert_python_ok("-c", code) + self.assertEqual(rc, 0) if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py index ad1c5053a37..1380c08d28b 100644 --- a/Lib/test/test_listcomps.py +++ b/Lib/test/test_listcomps.py @@ -177,7 +177,7 @@ def test_references___class___defined(self): res = [__class__ for x in [1]] """ self._check_in_scopes( - code, outputs={"res": [2]}, scopes=["module", "function"]) + code, outputs={"res": [2]}, scopes=["module", "function"]) self._check_in_scopes(code, raises=NameError, scopes=["class"]) def test_references___class___enclosing(self): @@ -648,11 +648,18 @@ def test_exception_in_post_comp_call(self): """ self._check_in_scopes(code, {"value": [1, None]}) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_frame_locals(self): code = """ - val = [sys._getframe().f_locals for a in [0]][0]["a"] + val = "a" in [sys._getframe().f_locals for a in [0]][0] """ import sys + self._check_in_scopes(code, {"val": False}, ns={"sys": sys}) + + code = """ + val = [sys._getframe().f_locals["a"] for a in [0]][0] + """ self._check_in_scopes(code, {"val": 0}, ns={"sys": sys}) def _recursive_replace(self, maybe_code): @@ -736,7 +743,7 @@ def iter_raises(): for func, expected in [(init_raises, "BrokenIter(init_raises=True)"), (next_raises, "BrokenIter(next_raises=True)"), (iter_raises, "BrokenIter(iter_raises=True)"), - ]: + ]: with self.subTest(func): exc = func() f = traceback.extract_tb(exc.__traceback__)[0] From 36f4d30e0155314972425cf6211d90736aeb6b31 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 14 Jul 2025 08:26:08 +0300 Subject: [PATCH 038/819] Update `test_tuple.py` from 3.13.5 (#5966) --- Lib/test/test_tuple.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Lib/test/test_tuple.py b/Lib/test/test_tuple.py index d2a2ed310b0..153df0e52dd 100644 --- a/Lib/test/test_tuple.py +++ b/Lib/test/test_tuple.py @@ -42,6 +42,35 @@ def test_keyword_args(self): with self.assertRaisesRegex(TypeError, 'keyword argument'): tuple(sequence=()) + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_keywords_in_subclass(self): + class subclass(tuple): + pass + u = subclass([1, 2]) + self.assertIs(type(u), subclass) + self.assertEqual(list(u), [1, 2]) + with self.assertRaises(TypeError): + subclass(sequence=()) + + class subclass_with_init(tuple): + def __init__(self, arg, newarg=None): + self.newarg = newarg + u = subclass_with_init([1, 2], newarg=3) + self.assertIs(type(u), subclass_with_init) + self.assertEqual(list(u), [1, 2]) + self.assertEqual(u.newarg, 3) + + class subclass_with_new(tuple): + def __new__(cls, arg, newarg=None): + self = super().__new__(cls, arg) + self.newarg = newarg + return self + u = subclass_with_new([1, 2], newarg=3) + self.assertIs(type(u), subclass_with_new) + self.assertEqual(list(u), [1, 2]) + self.assertEqual(u.newarg, 3) + def test_truth(self): super().test_truth() self.assertTrue(not ()) From 635b4afff1ca1ff4e488995e8a04f24cbbc9e19a Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 13 Jul 2025 12:56:59 +0900 Subject: [PATCH 039/819] Fix derive(Traverse) --- derive-impl/src/pytraverse.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/derive-impl/src/pytraverse.rs b/derive-impl/src/pytraverse.rs index 728722b83ac..c5c4bbd2704 100644 --- a/derive-impl/src/pytraverse.rs +++ b/derive-impl/src/pytraverse.rs @@ -105,8 +105,19 @@ pub(crate) fn impl_pytraverse(mut item: DeriveInput) -> Result { let ty = &item.ident; + // Add Traverse bound to all type parameters + for param in &mut item.generics.params { + if let syn::GenericParam::Type(type_param) = param { + type_param + .bounds + .push(syn::parse_quote!(::rustpython_vm::object::Traverse)); + } + } + + let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl(); + let ret = quote! { - unsafe impl ::rustpython_vm::object::Traverse for #ty { + unsafe impl #impl_generics ::rustpython_vm::object::Traverse for #ty #ty_generics #where_clause { fn traverse(&self, tracer_fn: &mut ::rustpython_vm::object::TraverseFn) { #trace_code } From 09489712e6d801722e9c1cb30a99701da15009ae Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 13 Jul 2025 14:34:46 +0900 Subject: [PATCH 040/819] PyPayload::payload_type_of --- vm/src/object/core.rs | 2 +- vm/src/object/payload.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/vm/src/object/core.rs b/vm/src/object/core.rs index bb057f4906f..b4b9557f2ae 100644 --- a/vm/src/object/core.rs +++ b/vm/src/object/core.rs @@ -655,7 +655,7 @@ impl PyObject { #[inline(always)] pub fn payload_is(&self) -> bool { - self.0.typeid == TypeId::of::() + self.0.typeid == T::payload_type_id() } /// Force to return payload as T. diff --git a/vm/src/object/payload.rs b/vm/src/object/payload.rs index 6413d6ae062..f223af6e968 100644 --- a/vm/src/object/payload.rs +++ b/vm/src/object/payload.rs @@ -19,6 +19,10 @@ cfg_if::cfg_if! { pub trait PyPayload: std::fmt::Debug + MaybeTraverse + PyThreadingConstraint + Sized + 'static { + #[inline] + fn payload_type_id() -> std::any::TypeId { + std::any::TypeId::of::() + } fn class(ctx: &Context) -> &'static Py; #[inline] @@ -75,7 +79,7 @@ pub trait PyPayload: } pub trait PyObjectPayload: - std::any::Any + std::fmt::Debug + MaybeTraverse + PyThreadingConstraint + 'static + PyPayload + std::any::Any + std::fmt::Debug + MaybeTraverse + PyThreadingConstraint + 'static { } From 14ce76e6c81b59191f14f811621ada0bba599cbb Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 13 Jul 2025 14:39:42 +0900 Subject: [PATCH 041/819] PyTupleTyped as alias of PyTuple --- vm/src/builtins/function.rs | 15 ++-- vm/src/builtins/tuple.rs | 120 ++++++++++++++++++------------- vm/src/builtins/type.rs | 22 +++--- vm/src/convert/try_from.rs | 8 +-- vm/src/frame.rs | 14 ++-- vm/src/object/core.rs | 44 +++++------- vm/src/object/traverse_object.rs | 5 +- vm/src/vm/context.rs | 7 ++ vm/src/vm/mod.rs | 8 +-- 9 files changed, 134 insertions(+), 109 deletions(-) diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index a29a077e509..45917adcf23 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -8,7 +8,6 @@ use super::{ #[cfg(feature = "jit")] use crate::common::lock::OnceCell; use crate::common::lock::PyMutex; -use crate::convert::{ToPyObject, TryFromObject}; use crate::function::ArgMapping; use crate::object::{Traverse, TraverseFn}; use crate::{ @@ -32,7 +31,7 @@ pub struct PyFunction { code: PyRef, globals: PyDictRef, builtins: PyObjectRef, - closure: Option>, + closure: Option>>, defaults_and_kwdefaults: PyMutex<(Option, Option)>, name: PyMutex, qualname: PyMutex, @@ -47,7 +46,9 @@ pub struct PyFunction { unsafe impl Traverse for PyFunction { fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { self.globals.traverse(tracer_fn); - self.closure.traverse(tracer_fn); + if let Some(closure) = self.closure.as_ref() { + closure.as_untyped().traverse(tracer_fn); + } self.defaults_and_kwdefaults.traverse(tracer_fn); } } @@ -58,7 +59,7 @@ impl PyFunction { pub(crate) fn new( code: PyRef, globals: PyDictRef, - closure: Option>, + closure: Option>>, defaults: Option, kw_only_defaults: Option, qualname: PyStrRef, @@ -326,6 +327,7 @@ impl Py { ) -> PyResult { #[cfg(feature = "jit")] if let Some(jitted_code) = self.jitted_code.get() { + use crate::convert::ToPyObject; match jit::get_jit_args(self, &func_args, jitted_code, vm) { Ok(args) => { return Ok(args.invoke().to_pyobject(vm)); @@ -427,7 +429,7 @@ impl PyFunction { #[pymember] fn __closure__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult { let zelf = Self::_as_pyref(&zelf, vm)?; - Ok(vm.unwrap_or_none(zelf.closure.clone().map(|x| x.to_pyobject(vm)))) + Ok(vm.unwrap_or_none(zelf.closure.clone().map(|x| x.into()))) } #[pymember] @@ -612,8 +614,7 @@ impl Constructor for PyFunction { } // Validate that all items are cells and create typed tuple - let typed_closure = - PyTupleTyped::::try_from_object(vm, closure_tuple.into())?; + let typed_closure = closure_tuple.try_into_typed::(vm)?; Some(typed_closure) } else if !args.code.freevars.is_empty() { return Err(vm.new_type_error("arg 5 (closure) must be tuple")); diff --git a/vm/src/builtins/tuple.rs b/vm/src/builtins/tuple.rs index 2ee8497dda5..9f589547f0c 100644 --- a/vm/src/builtins/tuple.rs +++ b/vm/src/builtins/tuple.rs @@ -3,7 +3,7 @@ use crate::common::{ hash::{PyHash, PyUHash}, lock::PyMutex, }; -use crate::object::{Traverse, TraverseFn}; +use crate::object::{MaybeTraverse, Traverse, TraverseFn}; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, atomic_func, @@ -449,6 +449,24 @@ impl Representable for PyTuple { } } +impl PyRef { + pub fn try_into_typed( + self, + vm: &VirtualMachine, + ) -> PyResult>>> { + PyRef::>>::try_from_untyped(self, vm) + } + /// # Safety + /// + /// The caller must ensure that all elements in the tuple are valid instances + /// of type `T` before calling this method. This is typically verified by + /// calling `try_into_typed` first. + unsafe fn into_typed_unchecked(self) -> PyRef>> { + let obj: PyObjectRef = self.into(); + unsafe { obj.downcast_unchecked::>>() } + } +} + #[pyclass(module = false, name = "tuple_iterator", traverse)] #[derive(Debug)] pub(crate) struct PyTupleIterator { @@ -500,53 +518,75 @@ pub(crate) fn init(context: &Context) { PyTupleIterator::extend_class(context, context.types.tuple_iterator_type); } -pub struct PyTupleTyped { +#[repr(transparent)] +pub struct PyTupleTyped { // SAFETY INVARIANT: T must be repr(transparent) over PyObjectRef, and the // elements must be logically valid when transmuted to T - tuple: PyTupleRef, - _marker: PhantomData>, + tuple: PyTuple, + _marker: PhantomData, } -unsafe impl Traverse for PyTupleTyped +unsafe impl Traverse for PyTupleTyped where - T: TransmuteFromObject + Traverse, + R: TransmuteFromObject, { fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { self.tuple.traverse(tracer_fn); } } -impl TryFromObject for PyTupleTyped { - fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - let tuple = PyTupleRef::try_from_object(vm, obj)?; - for elem in &*tuple { - T::check(vm, elem)? - } - // SAFETY: the contract of TransmuteFromObject upholds the variant on `tuple` - Ok(Self { - tuple, - _marker: PhantomData, - }) +impl MaybeTraverse for PyTupleTyped { + const IS_TRACE: bool = true; + fn try_traverse(&self, tracer_fn: &mut TraverseFn<'_>) { + self.traverse(tracer_fn); } } -impl AsRef<[T]> for PyTupleTyped { - fn as_ref(&self) -> &[T] { - self.as_slice() +impl PyTupleTyped> { + pub fn new_ref(elements: Vec>, ctx: &Context) -> PyRef { + // SAFETY: PyRef has the same layout as PyObjectRef + unsafe { + let elements: Vec = + std::mem::transmute::>, Vec>(elements); + let tuple = PyTuple::new_ref(elements, ctx); + tuple.into_typed_unchecked::() + } } } -impl PyTupleTyped { - pub fn empty(vm: &VirtualMachine) -> Self { - Self { - tuple: vm.ctx.empty_tuple.clone(), - _marker: PhantomData, +impl PyRef>> { + pub fn into_untyped(self) -> PyRef { + // SAFETY: PyTupleTyped is transparent over PyTuple + unsafe { std::mem::transmute::>>, PyRef>(self) } + } + + pub fn try_from_untyped(tuple: PyTupleRef, vm: &VirtualMachine) -> PyResult { + // Check that all elements are of the correct type + for elem in tuple.as_slice() { + as TransmuteFromObject>::check(vm, elem)?; } + // SAFETY: We just verified all elements are of type T, and PyTupleTyped has the same layout as PyTuple + Ok(unsafe { std::mem::transmute::, PyRef>>>(tuple) }) } +} +impl Py>> { + pub fn as_untyped(&self) -> &Py { + // SAFETY: PyTupleTyped is transparent over PyTuple + unsafe { std::mem::transmute::<&Py>>, &Py>(self) } + } +} + +impl AsRef<[PyRef]> for PyTupleTyped> { + fn as_ref(&self) -> &[PyRef] { + self.as_slice() + } +} + +impl PyTupleTyped> { #[inline] - pub fn as_slice(&self) -> &[T] { - unsafe { &*(self.tuple.as_slice() as *const [PyObjectRef] as *const [T]) } + pub fn as_slice(&self) -> &[PyRef] { + unsafe { &*(self.tuple.as_slice() as *const [PyObjectRef] as *const [PyRef]) } } #[inline] @@ -560,32 +600,16 @@ impl PyTupleTyped { } } -impl Clone for PyTupleTyped { - fn clone(&self) -> Self { - Self { - tuple: self.tuple.clone(), - _marker: PhantomData, - } - } -} - -impl fmt::Debug for PyTupleTyped { +impl fmt::Debug for PyTupleTyped { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_slice().fmt(f) - } -} - -impl From> for PyTupleRef { - #[inline] - fn from(tup: PyTupleTyped) -> Self { - tup.tuple + self.tuple.as_slice().fmt(f) } } -impl ToPyObject for PyTupleTyped { +impl From>>> for PyTupleRef { #[inline] - fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef { - self.tuple.into() + fn from(tup: PyRef>>) -> Self { + tup.into_untyped() } } diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index 1e18d6fd631..5a8f853bf1f 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -62,7 +62,7 @@ unsafe impl crate::object::Traverse for PyType { pub struct HeapTypeExt { pub name: PyRwLock, pub qualname: PyRwLock, - pub slots: Option>, + pub slots: Option>>, pub sequence_methods: PySequenceMethods, pub mapping_methods: PyMappingMethods, } @@ -1041,15 +1041,13 @@ impl Constructor for PyType { // TODO: Flags is currently initialized with HAS_DICT. Should be // updated when __slots__ are supported (toggling the flag off if // a class has __slots__ defined). - let heaptype_slots: Option> = + let heaptype_slots: Option>> = if let Some(x) = attributes.get(identifier!(vm, __slots__)) { - Some(if x.to_owned().class().is(vm.ctx.types.str_type) { - PyTupleTyped::::try_from_object( - vm, - vec![x.to_owned()].into_pytuple(vm).into(), - )? + let slots = if x.class().is(vm.ctx.types.str_type) { + let x = unsafe { x.downcast_unchecked_ref::() }; + PyTupleTyped::new_ref(vec![x.to_owned()], &vm.ctx) } else { - let iter = x.to_owned().get_iter(vm)?; + let iter = x.get_iter(vm)?; let elements = { let mut elements = Vec::new(); while let PyIterReturn::Return(element) = iter.next(vm)? { @@ -1057,8 +1055,10 @@ impl Constructor for PyType { } elements }; - PyTupleTyped::::try_from_object(vm, elements.into_pytuple(vm).into())? - }) + let tuple = elements.into_pytuple(vm); + tuple.try_into_typed(vm)? + }; + Some(slots) } else { None }; @@ -1082,7 +1082,7 @@ impl Constructor for PyType { let heaptype_ext = HeapTypeExt { name: PyRwLock::new(name), qualname: PyRwLock::new(qualname), - slots: heaptype_slots.to_owned(), + slots: heaptype_slots.clone(), sequence_methods: PySequenceMethods::default(), mapping_methods: PyMappingMethods::default(), }; diff --git a/vm/src/convert/try_from.rs b/vm/src/convert/try_from.rs index d2d83b36e78..a875ffa231e 100644 --- a/vm/src/convert/try_from.rs +++ b/vm/src/convert/try_from.rs @@ -78,12 +78,12 @@ where #[inline] fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { let class = T::class(&vm.ctx); - if obj.fast_isinstance(class) { + let result = if obj.fast_isinstance(class) { obj.downcast() - .map_err(|obj| vm.new_downcast_runtime_error(class, &obj)) } else { - Err(vm.new_downcast_type_error(class, &obj)) - } + Err(obj) + }; + result.map_err(|obj| vm.new_downcast_type_error(class, &obj)) } } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index a3e31c5c2bb..460ba4392ef 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -7,7 +7,7 @@ use crate::{ PySlice, PyStr, PyStrInterned, PyStrRef, PyTraceback, PyType, asyncgenerator::PyAsyncGenWrappedValue, function::{PyCell, PyCellRef, PyFunction}, - tuple::{PyTuple, PyTupleRef, PyTupleTyped}, + tuple::{PyTuple, PyTupleRef}, }, bytecode, convert::{IntoObject, ToPyResult}, @@ -1346,11 +1346,14 @@ impl ExecutingFrame<'_> { #[cfg_attr(feature = "flame-it", flame("Frame"))] fn import(&mut self, vm: &VirtualMachine, module_name: Option<&Py>) -> PyResult<()> { let module_name = module_name.unwrap_or(vm.ctx.empty_str); - let from_list = >>::try_from_object(vm, self.pop_value())? - .unwrap_or_else(|| PyTupleTyped::empty(vm)); + let top = self.pop_value(); + let from_list = match >::try_from_object(vm, top)? { + Some(from_list) => from_list.try_into_typed::(vm)?, + None => vm.ctx.empty_tuple_typed().to_owned(), + }; let level = usize::try_from_object(vm, self.pop_value())?; - let module = vm.import_from(module_name, from_list, level)?; + let module = vm.import_from(module_name, &from_list, level)?; self.push_value(module); Ok(()) @@ -1839,7 +1842,8 @@ impl ExecutingFrame<'_> { .expect("Second to top value on the stack must be a code object"); let closure = if flags.contains(bytecode::MakeFunctionFlags::CLOSURE) { - Some(PyTupleTyped::try_from_object(vm, self.pop_value()).unwrap()) + let tuple = PyTupleRef::try_from_object(vm, self.pop_value()).unwrap(); + Some(tuple.try_into_typed(vm).expect("This is a compiler bug")) } else { None }; diff --git a/vm/src/object/core.rs b/vm/src/object/core.rs index b4b9557f2ae..5012855133a 100644 --- a/vm/src/object/core.rs +++ b/vm/src/object/core.rs @@ -15,7 +15,7 @@ use super::{ ext::{AsObject, PyRefExact, PyResult}, payload::PyObjectPayload, }; -use crate::object::traverse::{Traverse, TraverseFn}; +use crate::object::traverse::{MaybeTraverse, Traverse, TraverseFn}; use crate::object::traverse_object::PyObjVTable; use crate::{ builtins::{PyDictRef, PyType, PyTypeRef}, @@ -121,7 +121,7 @@ impl fmt::Debug for PyInner { } } -unsafe impl Traverse for Py { +unsafe impl Traverse for Py { /// DO notice that call `trace` on `Py` means apply `tracer_fn` on `Py`'s children, /// not like call `trace` on `PyRef` which apply `tracer_fn` on `PyRef` itself fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { @@ -557,7 +557,7 @@ impl PyObjectRef { /// # Safety /// T must be the exact payload type #[inline(always)] - pub unsafe fn downcast_unchecked(self) -> PyRef { + pub unsafe fn downcast_unchecked(self) -> PyRef { // PyRef::from_obj_unchecked(self) // manual impl to avoid assertion let obj = ManuallyDrop::new(self); @@ -893,7 +893,7 @@ impl fmt::Debug for PyObjectRef { } #[repr(transparent)] -pub struct Py(PyInner); +pub struct Py(PyInner); impl Py { pub fn downgrade( @@ -908,7 +908,7 @@ impl Py { } } -impl ToOwned for Py { +impl ToOwned for Py { type Owned = PyRef; #[inline(always)] @@ -920,7 +920,7 @@ impl ToOwned for Py { } } -impl Deref for Py { +impl Deref for Py { type Target = T; #[inline(always)] @@ -984,24 +984,24 @@ impl fmt::Debug for Py { /// situations (such as when implementing in-place methods such as `__iadd__`) /// where a reference to the same object must be returned. #[repr(transparent)] -pub struct PyRef { +pub struct PyRef { ptr: NonNull>, } cfg_if::cfg_if! { if #[cfg(feature = "threading")] { - unsafe impl Send for PyRef {} - unsafe impl Sync for PyRef {} + unsafe impl Send for PyRef {} + unsafe impl Sync for PyRef {} } } -impl fmt::Debug for PyRef { +impl fmt::Debug for PyRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { (**self).fmt(f) } } -impl Drop for PyRef { +impl Drop for PyRef { #[inline] fn drop(&mut self) { if self.0.ref_count.dec() { @@ -1010,7 +1010,7 @@ impl Drop for PyRef { } } -impl Clone for PyRef { +impl Clone for PyRef { #[inline(always)] fn clone(&self) -> Self { (**self).to_owned() @@ -1070,10 +1070,7 @@ where } } -impl From> for PyObjectRef -where - T: PyObjectPayload, -{ +impl From> for PyObjectRef { #[inline] fn from(value: PyRef) -> Self { let me = ManuallyDrop::new(value); @@ -1081,30 +1078,21 @@ where } } -impl Borrow> for PyRef -where - T: PyObjectPayload, -{ +impl Borrow> for PyRef { #[inline(always)] fn borrow(&self) -> &Py { self } } -impl AsRef> for PyRef -where - T: PyObjectPayload, -{ +impl AsRef> for PyRef { #[inline(always)] fn as_ref(&self) -> &Py { self } } -impl Deref for PyRef -where - T: PyObjectPayload, -{ +impl Deref for PyRef { type Target = Py; #[inline(always)] diff --git a/vm/src/object/traverse_object.rs b/vm/src/object/traverse_object.rs index ee327859506..281b0e56eb5 100644 --- a/vm/src/object/traverse_object.rs +++ b/vm/src/object/traverse_object.rs @@ -3,7 +3,8 @@ use std::fmt; use crate::{ PyObject, object::{ - Erased, InstanceDict, PyInner, PyObjectPayload, debug_obj, drop_dealloc_obj, try_trace_obj, + Erased, InstanceDict, MaybeTraverse, PyInner, PyObjectPayload, debug_obj, drop_dealloc_obj, + try_trace_obj, }, }; @@ -56,7 +57,7 @@ unsafe impl Traverse for PyInner { } } -unsafe impl Traverse for PyInner { +unsafe impl Traverse for PyInner { /// Type is known, so we can call `try_trace` directly instead of using erased type vtable fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { // 1. trace `dict` and `slots` field(`typ` can't trace for it's a AtomicRef while is leaked by design) diff --git a/vm/src/vm/context.rs b/vm/src/vm/context.rs index d35b5b7f7e4..4c673831e06 100644 --- a/vm/src/vm/context.rs +++ b/vm/src/vm/context.rs @@ -11,6 +11,7 @@ use crate::{ }, getset::PyGetSet, object, pystr, + tuple::PyTupleTyped, type_::PyAttributes, }, class::{PyClassImpl, StaticType}, @@ -373,6 +374,12 @@ impl Context { self.not_implemented.clone().into() } + #[inline] + pub fn empty_tuple_typed(&self) -> &Py> { + let py: &Py = &self.empty_tuple; + unsafe { std::mem::transmute(py) } + } + // universal pyref constructor pub fn new_pyref(&self, value: T) -> PyRef

where diff --git a/vm/src/vm/mod.rs b/vm/src/vm/mod.rs index 4a319c96352..dbfa2147b39 100644 --- a/vm/src/vm/mod.rs +++ b/vm/src/vm/mod.rs @@ -599,7 +599,7 @@ impl VirtualMachine { #[inline] pub fn import<'a>(&self, module_name: impl AsPyStr<'a>, level: usize) -> PyResult { let module_name = module_name.as_pystr(&self.ctx); - let from_list = PyTupleTyped::empty(self); + let from_list = self.ctx.empty_tuple_typed(); self.import_inner(module_name, from_list, level) } @@ -609,7 +609,7 @@ impl VirtualMachine { pub fn import_from<'a>( &self, module_name: impl AsPyStr<'a>, - from_list: PyTupleTyped, + from_list: &Py>, level: usize, ) -> PyResult { let module_name = module_name.as_pystr(&self.ctx); @@ -619,7 +619,7 @@ impl VirtualMachine { fn import_inner( &self, module: &Py, - from_list: PyTupleTyped, + from_list: &Py>, level: usize, ) -> PyResult { // if the import inputs seem weird, e.g a package import or something, rather than just @@ -657,7 +657,7 @@ impl VirtualMachine { } else { (None, None) }; - let from_list = from_list.to_pyobject(self); + let from_list: PyObjectRef = from_list.to_owned().into(); import_func .call((module.to_owned(), globals, locals, from_list, level), self) .inspect_err(|exc| import::remove_importlib_frames(self, exc)) From 6342ad4fa7ec073aa85967e7f2a4c6bcfd2df103 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 14 Jul 2025 14:27:31 +0900 Subject: [PATCH 042/819] Fully integrate PyTupleTyped into PyTuple --- vm/src/builtins/function.rs | 8 +- vm/src/builtins/tuple.rs | 213 +++++++++++++----------------------- vm/src/builtins/type.rs | 10 +- vm/src/vm/context.rs | 3 +- vm/src/vm/mod.rs | 9 +- 5 files changed, 92 insertions(+), 151 deletions(-) diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index 45917adcf23..16cb3e420f3 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -2,8 +2,8 @@ mod jit; use super::{ - PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyStr, PyStrRef, PyTupleRef, PyType, - PyTypeRef, tuple::PyTupleTyped, + PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyStr, PyStrRef, PyTuple, PyTupleRef, + PyType, PyTypeRef, }; #[cfg(feature = "jit")] use crate::common::lock::OnceCell; @@ -31,7 +31,7 @@ pub struct PyFunction { code: PyRef, globals: PyDictRef, builtins: PyObjectRef, - closure: Option>>, + closure: Option>>, defaults_and_kwdefaults: PyMutex<(Option, Option)>, name: PyMutex, qualname: PyMutex, @@ -59,7 +59,7 @@ impl PyFunction { pub(crate) fn new( code: PyRef, globals: PyDictRef, - closure: Option>>, + closure: Option>>, defaults: Option, kw_only_defaults: Option, qualname: PyStrRef, diff --git a/vm/src/builtins/tuple.rs b/vm/src/builtins/tuple.rs index 9f589547f0c..2c3255b2490 100644 --- a/vm/src/builtins/tuple.rs +++ b/vm/src/builtins/tuple.rs @@ -3,7 +3,6 @@ use crate::common::{ hash::{PyHash, PyUHash}, lock::PyMutex, }; -use crate::object::{MaybeTraverse, Traverse, TraverseFn}; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, atomic_func, @@ -22,14 +21,14 @@ use crate::{ utils::collection_repr, vm::VirtualMachine, }; -use std::{fmt, marker::PhantomData, sync::LazyLock}; +use std::{fmt, sync::LazyLock}; #[pyclass(module = false, name = "tuple", traverse)] -pub struct PyTuple { - elements: Box<[PyObjectRef]>, +pub struct PyTuple { + elements: Box<[R]>, } -impl fmt::Debug for PyTuple { +impl fmt::Debug for PyTuple { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // TODO: implement more informational, non-recursive Debug formatter f.write_str("tuple") @@ -140,39 +139,60 @@ impl Constructor for PyTuple { } } -impl AsRef<[PyObjectRef]> for PyTuple { - fn as_ref(&self) -> &[PyObjectRef] { - self.as_slice() +impl AsRef<[R]> for PyTuple { + fn as_ref(&self) -> &[R] { + &self.elements } } -impl std::ops::Deref for PyTuple { - type Target = [PyObjectRef]; +impl std::ops::Deref for PyTuple { + type Target = [R]; - fn deref(&self) -> &[PyObjectRef] { - self.as_slice() + fn deref(&self) -> &[R] { + &self.elements } } -impl<'a> std::iter::IntoIterator for &'a PyTuple { - type Item = &'a PyObjectRef; - type IntoIter = std::slice::Iter<'a, PyObjectRef>; +impl<'a, R> std::iter::IntoIterator for &'a PyTuple { + type Item = &'a R; + type IntoIter = std::slice::Iter<'a, R>; fn into_iter(self) -> Self::IntoIter { self.iter() } } -impl<'a> std::iter::IntoIterator for &'a Py { - type Item = &'a PyObjectRef; - type IntoIter = std::slice::Iter<'a, PyObjectRef>; +impl<'a, R> std::iter::IntoIterator for &'a Py> { + type Item = &'a R; + type IntoIter = std::slice::Iter<'a, R>; fn into_iter(self) -> Self::IntoIter { self.iter() } } -impl PyTuple { +impl PyTuple { + pub const fn as_slice(&self) -> &[R] { + &self.elements + } + + #[inline] + pub fn len(&self) -> usize { + self.elements.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.elements.is_empty() + } + + #[inline] + pub fn iter(&self) -> std::slice::Iter<'_, R> { + self.elements.iter() + } +} + +impl PyTuple { pub fn new_ref(elements: Vec, ctx: &Context) -> PyRef { if elements.is_empty() { ctx.empty_tuple.clone() @@ -189,10 +209,6 @@ impl PyTuple { Self { elements } } - pub const fn as_slice(&self) -> &[PyObjectRef] { - &self.elements - } - fn repeat(zelf: PyRef, value: isize, vm: &VirtualMachine) -> PyResult> { Ok(if zelf.elements.is_empty() || value == 0 { vm.ctx.empty_tuple.clone() @@ -214,6 +230,18 @@ impl PyTuple { } } +impl PyTuple> { + pub fn new_ref_typed(elements: Vec>, ctx: &Context) -> PyRef>> { + // SAFETY: PyRef has the same layout as PyObjectRef + unsafe { + let elements: Vec = + std::mem::transmute::>, Vec>(elements); + let tuple = PyTuple::::new_ref(elements, ctx); + std::mem::transmute::, PyRef>>>(tuple) + } + } +} + #[pyclass( flags(BASETYPE), with( @@ -272,11 +300,6 @@ impl PyTuple { self.elements.len() } - #[inline] - pub const fn is_empty(&self) -> bool { - self.elements.is_empty() - } - #[pymethod(name = "__rmul__")] #[pymethod] fn __mul__(zelf: PyRef, value: ArgSize, vm: &VirtualMachine) -> PyResult> { @@ -449,21 +472,38 @@ impl Representable for PyTuple { } } -impl PyRef { +impl PyRef> { pub fn try_into_typed( self, vm: &VirtualMachine, - ) -> PyResult>>> { - PyRef::>>::try_from_untyped(self, vm) + ) -> PyResult>>> { + // Check that all elements are of the correct type + for elem in self.as_slice() { + as TransmuteFromObject>::check(vm, elem)?; + } + // SAFETY: We just verified all elements are of type T + Ok(unsafe { std::mem::transmute::, PyRef>>>(self) }) + } +} + +impl PyRef>> { + pub fn into_untyped(self) -> PyRef { + // SAFETY: PyTuple> has the same layout as PyTuple + unsafe { std::mem::transmute::>>, PyRef>(self) } } - /// # Safety - /// - /// The caller must ensure that all elements in the tuple are valid instances - /// of type `T` before calling this method. This is typically verified by - /// calling `try_into_typed` first. - unsafe fn into_typed_unchecked(self) -> PyRef>> { - let obj: PyObjectRef = self.into(); - unsafe { obj.downcast_unchecked::>>() } +} + +impl Py>> { + pub fn as_untyped(&self) -> &Py { + // SAFETY: PyTuple> has the same layout as PyTuple + unsafe { std::mem::transmute::<&Py>>, &Py>(self) } + } +} + +impl From>>> for PyTupleRef { + #[inline] + fn from(tup: PyRef>>) -> Self { + tup.into_untyped() } } @@ -518,101 +558,6 @@ pub(crate) fn init(context: &Context) { PyTupleIterator::extend_class(context, context.types.tuple_iterator_type); } -#[repr(transparent)] -pub struct PyTupleTyped { - // SAFETY INVARIANT: T must be repr(transparent) over PyObjectRef, and the - // elements must be logically valid when transmuted to T - tuple: PyTuple, - _marker: PhantomData, -} - -unsafe impl Traverse for PyTupleTyped -where - R: TransmuteFromObject, -{ - fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { - self.tuple.traverse(tracer_fn); - } -} - -impl MaybeTraverse for PyTupleTyped { - const IS_TRACE: bool = true; - fn try_traverse(&self, tracer_fn: &mut TraverseFn<'_>) { - self.traverse(tracer_fn); - } -} - -impl PyTupleTyped> { - pub fn new_ref(elements: Vec>, ctx: &Context) -> PyRef { - // SAFETY: PyRef has the same layout as PyObjectRef - unsafe { - let elements: Vec = - std::mem::transmute::>, Vec>(elements); - let tuple = PyTuple::new_ref(elements, ctx); - tuple.into_typed_unchecked::() - } - } -} - -impl PyRef>> { - pub fn into_untyped(self) -> PyRef { - // SAFETY: PyTupleTyped is transparent over PyTuple - unsafe { std::mem::transmute::>>, PyRef>(self) } - } - - pub fn try_from_untyped(tuple: PyTupleRef, vm: &VirtualMachine) -> PyResult { - // Check that all elements are of the correct type - for elem in tuple.as_slice() { - as TransmuteFromObject>::check(vm, elem)?; - } - // SAFETY: We just verified all elements are of type T, and PyTupleTyped has the same layout as PyTuple - Ok(unsafe { std::mem::transmute::, PyRef>>>(tuple) }) - } -} - -impl Py>> { - pub fn as_untyped(&self) -> &Py { - // SAFETY: PyTupleTyped is transparent over PyTuple - unsafe { std::mem::transmute::<&Py>>, &Py>(self) } - } -} - -impl AsRef<[PyRef]> for PyTupleTyped> { - fn as_ref(&self) -> &[PyRef] { - self.as_slice() - } -} - -impl PyTupleTyped> { - #[inline] - pub fn as_slice(&self) -> &[PyRef] { - unsafe { &*(self.tuple.as_slice() as *const [PyObjectRef] as *const [PyRef]) } - } - - #[inline] - pub fn len(&self) -> usize { - self.tuple.len() - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.tuple.is_empty() - } -} - -impl fmt::Debug for PyTupleTyped { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.tuple.as_slice().fmt(f) - } -} - -impl From>>> for PyTupleRef { - #[inline] - fn from(tup: PyRef>>) -> Self { - tup.into_untyped() - } -} - pub(super) fn tuple_hash(elements: &[PyObjectRef], vm: &VirtualMachine) -> PyResult { #[cfg(target_pointer_width = "64")] const PRIME1: PyUHash = 11400714785074694791; diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index 5a8f853bf1f..f2a4fde3b9b 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -1,5 +1,5 @@ use super::{ - PyClassMethod, PyDictRef, PyList, PyStr, PyStrInterned, PyStrRef, PyTuple, PyTupleRef, PyWeak, + PyClassMethod, PyDictRef, PyList, PyStr, PyStrInterned, PyStrRef, PyTupleRef, PyWeak, mappingproxy::PyMappingProxy, object, union_, }; use crate::{ @@ -12,7 +12,7 @@ use crate::{ PyMemberDescriptor, }, function::PyCellRef, - tuple::{IntoPyTuple, PyTupleTyped}, + tuple::{IntoPyTuple, PyTuple}, }, class::{PyClassImpl, StaticType}, common::{ @@ -62,7 +62,7 @@ unsafe impl crate::object::Traverse for PyType { pub struct HeapTypeExt { pub name: PyRwLock, pub qualname: PyRwLock, - pub slots: Option>>, + pub slots: Option>>, pub sequence_methods: PySequenceMethods, pub mapping_methods: PyMappingMethods, } @@ -1041,11 +1041,11 @@ impl Constructor for PyType { // TODO: Flags is currently initialized with HAS_DICT. Should be // updated when __slots__ are supported (toggling the flag off if // a class has __slots__ defined). - let heaptype_slots: Option>> = + let heaptype_slots: Option>> = if let Some(x) = attributes.get(identifier!(vm, __slots__)) { let slots = if x.class().is(vm.ctx.types.str_type) { let x = unsafe { x.downcast_unchecked_ref::() }; - PyTupleTyped::new_ref(vec![x.to_owned()], &vm.ctx) + PyTuple::new_ref_typed(vec![x.to_owned()], &vm.ctx) } else { let iter = x.get_iter(vm)?; let elements = { diff --git a/vm/src/vm/context.rs b/vm/src/vm/context.rs index 4c673831e06..67072881519 100644 --- a/vm/src/vm/context.rs +++ b/vm/src/vm/context.rs @@ -11,7 +11,6 @@ use crate::{ }, getset::PyGetSet, object, pystr, - tuple::PyTupleTyped, type_::PyAttributes, }, class::{PyClassImpl, StaticType}, @@ -375,7 +374,7 @@ impl Context { } #[inline] - pub fn empty_tuple_typed(&self) -> &Py> { + pub fn empty_tuple_typed(&self) -> &Py> { let py: &Py = &self.empty_tuple; unsafe { std::mem::transmute(py) } } diff --git a/vm/src/vm/mod.rs b/vm/src/vm/mod.rs index dbfa2147b39..498c7e39d11 100644 --- a/vm/src/vm/mod.rs +++ b/vm/src/vm/mod.rs @@ -20,10 +20,7 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, builtins::{ PyBaseExceptionRef, PyDictRef, PyInt, PyList, PyModule, PyStr, PyStrInterned, PyStrRef, - PyTypeRef, - code::PyCode, - pystr::AsPyStr, - tuple::{PyTuple, PyTupleTyped}, + PyTypeRef, code::PyCode, pystr::AsPyStr, tuple::PyTuple, }, codecs::CodecsRegistry, common::{hash::HashSecret, lock::PyMutex, rc::PyRc}, @@ -609,7 +606,7 @@ impl VirtualMachine { pub fn import_from<'a>( &self, module_name: impl AsPyStr<'a>, - from_list: &Py>, + from_list: &Py>, level: usize, ) -> PyResult { let module_name = module_name.as_pystr(&self.ctx); @@ -619,7 +616,7 @@ impl VirtualMachine { fn import_inner( &self, module: &Py, - from_list: &Py>, + from_list: &Py>, level: usize, ) -> PyResult { // if the import inputs seem weird, e.g a package import or something, rather than just From 406be9cd15b36e1952ca8eb56408e83281b2aa09 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 14 Jul 2025 20:12:22 +0900 Subject: [PATCH 043/819] Upgrade radium to 1.1.1 --- Cargo.lock | 5 +++-- Cargo.toml | 3 +-- example_projects/barebone/Cargo.toml | 1 - example_projects/frozen_stdlib/Cargo.toml | 1 - wasm/wasm-unknown-test/Cargo.toml | 1 - 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6dee806c7f..50ec28b1ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1958,8 +1958,9 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "radium" -version = "1.1.0" -source = "git+https://github.com/youknowone/ferrilab?branch=fix-nightly#4a301c3a223e096626a2773d1a1eed1fc4e21140" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1775bc532a9bfde46e26eba441ca1171b91608d14a3bae71fea371f18a00cffe" dependencies = [ "cfg-if", ] diff --git a/Cargo.toml b/Cargo.toml index 1fdc77d2614..440855aba59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,7 +82,6 @@ opt-level = 3 lto = "thin" [patch.crates-io] -radium = { version = "1.1.0", git = "https://github.com/youknowone/ferrilab", branch = "fix-nightly" } # REDOX START, Uncomment when you want to compile/check with redoxer # REDOX END @@ -190,7 +189,7 @@ paste = "1.0.15" proc-macro2 = "1.0.93" pymath = "0.0.2" quote = "1.0.38" -radium = "1.1" +radium = "1.1.1" rand = "0.9" rand_core = { version = "0.9", features = ["os_rng"] } rustix = { version = "1.0", features = ["event"] } diff --git a/example_projects/barebone/Cargo.toml b/example_projects/barebone/Cargo.toml index a993277f318..8bc49c237f3 100644 --- a/example_projects/barebone/Cargo.toml +++ b/example_projects/barebone/Cargo.toml @@ -9,4 +9,3 @@ rustpython-vm = { path = "../../vm", default-features = false } [workspace] [patch.crates-io] -radium = { version = "1.1.0", git = "https://github.com/youknowone/ferrilab", branch = "fix-nightly" } diff --git a/example_projects/frozen_stdlib/Cargo.toml b/example_projects/frozen_stdlib/Cargo.toml index be1b1eb16c9..78a88988d8a 100644 --- a/example_projects/frozen_stdlib/Cargo.toml +++ b/example_projects/frozen_stdlib/Cargo.toml @@ -11,4 +11,3 @@ rustpython-pylib = { path = "../../pylib", default-features = false, features = [workspace] [patch.crates-io] -radium = { version = "1.1.0", git = "https://github.com/youknowone/ferrilab", branch = "fix-nightly" } diff --git a/wasm/wasm-unknown-test/Cargo.toml b/wasm/wasm-unknown-test/Cargo.toml index 5945f69006f..ed8c9fcb02e 100644 --- a/wasm/wasm-unknown-test/Cargo.toml +++ b/wasm/wasm-unknown-test/Cargo.toml @@ -13,4 +13,3 @@ rustpython-vm = { path = "../../vm", default-features = false, features = ["comp [workspace] [patch.crates-io] -radium = { version = "1.1.0", git = "https://github.com/youknowone/ferrilab", branch = "fix-nightly" } From dd4f0c3a9f48a3ad28b46a11c266112465c39d98 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 14 Jul 2025 20:21:31 +0900 Subject: [PATCH 044/819] fix lint --- vm/src/vm/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vm/src/vm/mod.rs b/vm/src/vm/mod.rs index 498c7e39d11..69938975983 100644 --- a/vm/src/vm/mod.rs +++ b/vm/src/vm/mod.rs @@ -14,8 +14,6 @@ mod vm_new; mod vm_object; mod vm_ops; -#[cfg(not(feature = "stdio"))] -use crate::builtins::PyNone; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, builtins::{ @@ -337,7 +335,8 @@ impl VirtualMachine { Ok(stdio) }; #[cfg(not(feature = "stdio"))] - let make_stdio = |_name, _fd, _write| Ok(PyNone.into_pyobject(self)); + let make_stdio = + |_name, _fd, _write| Ok(crate::builtins::PyNone.into_pyobject(self)); let set_stdio = |name, fd, write| { let stdio = make_stdio(name, fd, write)?; From fd35c7a70634b78f85f2b3278f8da57b52ef6734 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 14 Jul 2025 22:54:44 +0900 Subject: [PATCH 045/819] Impl Drop for PyAtomicRef (#5970) --- vm/src/object/ext.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vm/src/object/ext.rs b/vm/src/object/ext.rs index 2815d2b20e7..1e2b78d9a9e 100644 --- a/vm/src/object/ext.rs +++ b/vm/src/object/ext.rs @@ -245,6 +245,19 @@ pub struct PyAtomicRef { _phantom: PhantomData, } +impl Drop for PyAtomicRef { + fn drop(&mut self) { + // SAFETY: We are dropping the atomic reference, so we can safely + // release the pointer. + unsafe { + let ptr = Radium::swap(&self.inner, null_mut(), Ordering::Relaxed); + if let Some(ptr) = NonNull::::new(ptr.cast()) { + let _: PyObjectRef = PyObjectRef::from_raw(ptr); + } + } + } +} + cfg_if::cfg_if! { if #[cfg(feature = "threading")] { unsafe impl Send for PyAtomicRef {} From ed433837b309d00f307a1e453fd8d60efce441bd Mon Sep 17 00:00:00 2001 From: Jiseok CHOI Date: Tue, 15 Jul 2025 00:54:42 +0900 Subject: [PATCH 046/819] Introduce PyUtf8Str and fix(sqlite): validate surrogates in SQL statements (#5969) * fix(sqlite): validate surrogates in SQL statements * Add `PyUtf8Str` wrapper for safe conversion --- Lib/test/test_sqlite3/test_regression.py | 2 - stdlib/src/sqlite.rs | 9 +++-- vm/src/builtins/str.rs | 49 +++++++++++++++++++++--- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_sqlite3/test_regression.py b/Lib/test/test_sqlite3/test_regression.py index dfcf3b11f57..870958ceee5 100644 --- a/Lib/test/test_sqlite3/test_regression.py +++ b/Lib/test/test_sqlite3/test_regression.py @@ -343,8 +343,6 @@ def test_null_character(self): self.assertRaisesRegex(sqlite.ProgrammingError, "null char", cur.execute, query) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_surrogates(self): con = sqlite.connect(":memory:") self.assertRaises(UnicodeEncodeError, con, "select '\ud8ff'") diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs index ce84ac29884..4e9620eeabd 100644 --- a/stdlib/src/sqlite.rs +++ b/stdlib/src/sqlite.rs @@ -844,7 +844,7 @@ mod _sqlite { type Args = (PyStrRef,); fn call(zelf: &Py, args: Self::Args, vm: &VirtualMachine) -> PyResult { - if let Some(stmt) = Statement::new(zelf, &args.0, vm)? { + if let Some(stmt) = Statement::new(zelf, args.0, vm)? { Ok(stmt.into_ref(&vm.ctx).into()) } else { Ok(vm.ctx.none()) @@ -1480,7 +1480,7 @@ mod _sqlite { stmt.lock().reset(); } - let Some(stmt) = Statement::new(&zelf.connection, &sql, vm)? else { + let Some(stmt) = Statement::new(&zelf.connection, sql, vm)? else { drop(inner); return Ok(zelf); }; @@ -1552,7 +1552,7 @@ mod _sqlite { stmt.lock().reset(); } - let Some(stmt) = Statement::new(&zelf.connection, &sql, vm)? else { + let Some(stmt) = Statement::new(&zelf.connection, sql, vm)? else { drop(inner); return Ok(zelf); }; @@ -2291,9 +2291,10 @@ mod _sqlite { impl Statement { fn new( connection: &Connection, - sql: &PyStr, + sql: PyStrRef, vm: &VirtualMachine, ) -> PyResult> { + let sql = sql.try_into_utf8(vm)?; let sql_cstr = sql.to_cstring(vm)?; let sql_len = sql.byte_len() + 1; diff --git a/vm/src/builtins/str.rs b/vm/src/builtins/str.rs index 9f86da3da0d..73349c61419 100644 --- a/vm/src/builtins/str.rs +++ b/vm/src/builtins/str.rs @@ -37,8 +37,8 @@ use rustpython_common::{ str::DeduceStrKind, wtf8::{CodePoint, Wtf8, Wtf8Buf, Wtf8Chunk}, }; -use std::sync::LazyLock; use std::{borrow::Cow, char, fmt, ops::Range}; +use std::{mem, sync::LazyLock}; use unic_ucd_bidi::BidiClass; use unic_ucd_category::GeneralCategory; use unic_ucd_ident::{is_xid_continue, is_xid_start}; @@ -80,6 +80,30 @@ impl fmt::Debug for PyStr { } } +#[repr(transparent)] +#[derive(Debug)] +pub struct PyUtf8Str(PyStr); + +// TODO: Remove this Deref which may hide missing optimized methods of PyUtf8Str +impl std::ops::Deref for PyUtf8Str { + type Target = PyStr; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PyUtf8Str { + /// Returns the underlying string slice. + pub fn as_str(&self) -> &str { + debug_assert!( + self.0.is_utf8(), + "PyUtf8Str invariant violated: inner string is not valid UTF-8" + ); + // Safety: This is safe because the type invariant guarantees UTF-8 validity. + unsafe { self.0.to_str().unwrap_unchecked() } + } +} + impl AsRef for PyStr { #[track_caller] // <- can remove this once it doesn't panic fn as_ref(&self) -> &str { @@ -433,21 +457,29 @@ impl PyStr { self.data.as_str() } - pub fn try_to_str(&self, vm: &VirtualMachine) -> PyResult<&str> { - self.to_str().ok_or_else(|| { + fn ensure_valid_utf8(&self, vm: &VirtualMachine) -> PyResult<()> { + if self.is_utf8() { + Ok(()) + } else { let start = self .as_wtf8() .code_points() .position(|c| c.to_char().is_none()) .unwrap(); - vm.new_unicode_encode_error_real( + Err(vm.new_unicode_encode_error_real( identifier!(vm, utf_8).to_owned(), vm.ctx.new_str(self.data.clone()), start, start + 1, vm.ctx.new_str("surrogates not allowed"), - ) - }) + )) + } + } + + pub fn try_to_str(&self, vm: &VirtualMachine) -> PyResult<&str> { + self.ensure_valid_utf8(vm)?; + // SAFETY: ensure_valid_utf8 passed, so unwrap is safe. + Ok(unsafe { self.to_str().unwrap_unchecked() }) } pub fn to_string_lossy(&self) -> Cow<'_, str> { @@ -1486,6 +1518,11 @@ impl PyStrRef { s.push_wtf8(other); *self = PyStr::from(s).into_ref(&vm.ctx); } + + pub fn try_into_utf8(self, vm: &VirtualMachine) -> PyResult> { + self.ensure_valid_utf8(vm)?; + Ok(unsafe { mem::transmute::, PyRef>(self) }) + } } impl Representable for PyStr { From d4f85cf0737974cbae15f38d73cc0245ab59f0f9 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI Date: Tue, 15 Jul 2025 01:45:42 +0900 Subject: [PATCH 047/819] Provide detailed error for circular `from` imports (#5972) --- Lib/test/test_import/__init__.py | 2 -- vm/src/frame.rs | 48 ++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 89e5ec1534a..44e7da1033d 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1380,8 +1380,6 @@ def test_crossreference2(self): self.assertIn('partially initialized module', errmsg) self.assertIn('circular import', errmsg) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_circular_from_import(self): with self.assertRaises(ImportError) as cm: import test.test_import.data.circular_imports.from_cycle1 diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 460ba4392ef..28a6ece4da0 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1363,19 +1363,38 @@ impl ExecutingFrame<'_> { fn import_from(&mut self, vm: &VirtualMachine, idx: bytecode::NameIdx) -> PyResult { let module = self.top_value(); let name = self.code.names[idx as usize]; - let err = || vm.new_import_error(format!("cannot import name '{name}'"), name.to_owned()); + // Load attribute, and transform any error into import error. if let Some(obj) = vm.get_attribute_opt(module.to_owned(), name)? { return Ok(obj); } // fallback to importing '{module.__name__}.{name}' from sys.modules - let mod_name = module - .get_attr(identifier!(vm, __name__), vm) - .map_err(|_| err())?; - let mod_name = mod_name.downcast::().map_err(|_| err())?; - let full_mod_name = format!("{mod_name}.{name}"); - let sys_modules = vm.sys_module.get_attr("modules", vm).map_err(|_| err())?; - sys_modules.get_item(&full_mod_name, vm).map_err(|_| err()) + let fallback_module = (|| { + let mod_name = module.get_attr(identifier!(vm, __name__), vm).ok()?; + let mod_name = mod_name.downcast_ref::()?; + let full_mod_name = format!("{mod_name}.{name}"); + let sys_modules = vm.sys_module.get_attr("modules", vm).ok()?; + sys_modules.get_item(&full_mod_name, vm).ok() + })(); + + if let Some(sub_module) = fallback_module { + return Ok(sub_module); + } + + if is_module_initializing(module, vm) { + let module_name = module + .get_attr(identifier!(vm, __name__), vm) + .ok() + .and_then(|n| n.downcast_ref::().map(|s| s.as_str().to_owned())) + .unwrap_or_else(|| "".to_owned()); + + let msg = format!( + "cannot import name '{name}' from partially initialized module '{module_name}' (most likely due to a circular import)", + ); + Err(vm.new_import_error(msg, name.to_owned())) + } else { + Err(vm.new_import_error(format!("cannot import name '{name}'"), name.to_owned())) + } } #[cfg_attr(feature = "flame-it", flame("Frame"))] @@ -2372,3 +2391,16 @@ impl fmt::Debug for Frame { ) } } + +fn is_module_initializing(module: &PyObject, vm: &VirtualMachine) -> bool { + let Ok(spec) = module.get_attr(&vm.ctx.new_str("__spec__"), vm) else { + return false; + }; + if vm.is_none(&spec) { + return false; + } + let Ok(initializing_attr) = spec.get_attr(&vm.ctx.new_str("_initializing"), vm) else { + return false; + }; + initializing_attr.try_to_bool(vm).unwrap_or(false) +} From 1d3603419efb83088dcf96bcebebe91ad53ea6a5 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 15 Jul 2025 03:12:23 +0900 Subject: [PATCH 048/819] SetFunctionAttribute (#5968) * PyRef::into_non_null * SetFunctionAttribute * set_function_attribute * frame helper in PyFuncion * remove closure lock * cleanup unused args --- Lib/test/test_funcattrs.py | 2 - Lib/test/test_reprlib.py | 2 - Lib/test/test_typing.py | 2 - compiler/codegen/src/compile.rs | 347 +++++++++++++++++++--------- compiler/codegen/src/symboltable.rs | 1 + compiler/core/src/bytecode.rs | 22 +- jit/tests/common.rs | 45 +++- vm/src/builtins/function.rs | 146 ++++++++---- vm/src/frame.rs | 100 +++----- vm/src/object/core.rs | 23 +- vm/src/vm/mod.rs | 14 +- 11 files changed, 440 insertions(+), 264 deletions(-) diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index 5fd268fd902..3d5378092b4 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -176,8 +176,6 @@ def test___name__(self): self.assertEqual(self.fi.a.__name__, 'a') self.cannot_set_attr(self.fi.a, "__name__", 'a', AttributeError) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test___qualname__(self): # PEP 3155 self.assertEqual(self.b.__qualname__, 'FuncAttrsTest.setUp..b') diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index 396be4b1044..738b48f5623 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -176,8 +176,6 @@ def test_instance(self): self.assertTrue(s.endswith(">")) self.assertIn(s.find("..."), [12, 13]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_lambda(self): r = repr(lambda x: x) self.assertTrue(r.startswith(". { Ok(()) } - fn enter_function( - &mut self, - name: &str, - parameters: &Parameters, - ) -> CompileResult { - let defaults: Vec<_> = std::iter::empty() - .chain(¶meters.posonlyargs) - .chain(¶meters.args) - .filter_map(|x| x.default.as_deref()) - .collect(); - let have_defaults = !defaults.is_empty(); - if have_defaults { - // Construct a tuple: - let size = defaults.len().to_u32(); - for element in &defaults { - self.compile_expression(element)?; - } - emit!(self, Instruction::BuildTuple { size }); - } - + fn enter_function(&mut self, name: &str, parameters: &Parameters) -> CompileResult<()> { // TODO: partition_in_place let mut kw_without_defaults = vec![]; let mut kw_with_defaults = vec![]; @@ -1513,31 +1494,6 @@ impl Compiler<'_> { } } - // let (kw_without_defaults, kw_with_defaults) = args.split_kwonlyargs(); - if !kw_with_defaults.is_empty() { - let default_kw_count = kw_with_defaults.len(); - for (arg, default) in kw_with_defaults.iter() { - self.emit_load_const(ConstantData::Str { - value: arg.name.as_str().into(), - }); - self.compile_expression(default)?; - } - emit!( - self, - Instruction::BuildMap { - size: default_kw_count.to_u32(), - } - ); - } - - let mut func_flags = bytecode::MakeFunctionFlags::empty(); - if have_defaults { - func_flags |= bytecode::MakeFunctionFlags::DEFAULTS; - } - if !kw_with_defaults.is_empty() { - func_flags |= bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS; - } - self.push_output( bytecode::CodeFlags::NEW_LOCALS | bytecode::CodeFlags::IS_OPTIMIZED, parameters.posonlyargs.len().to_u32(), @@ -1565,7 +1521,7 @@ impl Compiler<'_> { self.varname(name.name.as_str())?; } - Ok(func_flags) + Ok(()) } fn prepare_decorators(&mut self, decorator_list: &[Decorator]) -> CompileResult<()> { @@ -1869,7 +1825,57 @@ impl Compiler<'_> { self.push_symbol_table(); } - let mut func_flags = self.enter_function(name, parameters)?; + // Prepare defaults and kwdefaults before entering function + let defaults: Vec<_> = std::iter::empty() + .chain(¶meters.posonlyargs) + .chain(¶meters.args) + .filter_map(|x| x.default.as_deref()) + .collect(); + let have_defaults = !defaults.is_empty(); + + // Compile defaults before entering function scope + if have_defaults { + // Construct a tuple: + let size = defaults.len().to_u32(); + for element in &defaults { + self.compile_expression(element)?; + } + emit!(self, Instruction::BuildTuple { size }); + } + + // Prepare keyword-only defaults + let mut kw_with_defaults = vec![]; + for kwonlyarg in ¶meters.kwonlyargs { + if let Some(default) = &kwonlyarg.default { + kw_with_defaults.push((&kwonlyarg.parameter, default)); + } + } + + let have_kwdefaults = !kw_with_defaults.is_empty(); + if have_kwdefaults { + let default_kw_count = kw_with_defaults.len(); + for (arg, default) in kw_with_defaults.iter() { + self.emit_load_const(ConstantData::Str { + value: arg.name.as_str().into(), + }); + self.compile_expression(default)?; + } + emit!( + self, + Instruction::BuildMap { + size: default_kw_count.to_u32(), + } + ); + } + + self.enter_function(name, parameters)?; + let mut func_flags = bytecode::MakeFunctionFlags::empty(); + if have_defaults { + func_flags |= bytecode::MakeFunctionFlags::DEFAULTS; + } + if have_kwdefaults { + func_flags |= bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS; + } self.current_code_info() .flags .set(bytecode::CodeFlags::IS_COROUTINE, is_async); @@ -1888,7 +1894,7 @@ impl Compiler<'_> { }; // Set qualname using the new method - let qualname = self.set_qualname(); + self.set_qualname(); let (doc_str, body) = split_doc(body, &self.opts); @@ -1965,7 +1971,7 @@ impl Compiler<'_> { } // Create function with closure - self.make_closure(code, &qualname, func_flags)?; + self.make_closure(code, func_flags)?; if let Some(value) = doc_str { emit!(self, Instruction::Duplicate); @@ -1982,58 +1988,92 @@ impl Compiler<'_> { self.store_name(name) } + /// Determines if a variable should be CELL or FREE type + // = get_ref_type + fn get_ref_type(&self, name: &str) -> Result { + // Special handling for __class__ and __classdict__ in class scope + if self.ctx.in_class && (name == "__class__" || name == "__classdict__") { + return Ok(SymbolScope::Cell); + } + + let table = self.symbol_table_stack.last().unwrap(); + match table.lookup(name) { + Some(symbol) => match symbol.scope { + SymbolScope::Cell | SymbolScope::TypeParams => Ok(SymbolScope::Cell), + SymbolScope::Free => Ok(SymbolScope::Free), + _ if symbol.flags.contains(SymbolFlags::FREE_CLASS) => Ok(SymbolScope::Free), + _ => Err(CodegenErrorType::SyntaxError(format!( + "get_ref_type: invalid scope for '{name}'" + ))), + }, + None => Err(CodegenErrorType::SyntaxError(format!( + "get_ref_type: cannot find symbol '{name}'" + ))), + } + } + /// Loads closure variables if needed and creates a function object // = compiler_make_closure fn make_closure( &mut self, code: CodeObject, - qualname: &str, - mut flags: bytecode::MakeFunctionFlags, + flags: bytecode::MakeFunctionFlags, ) -> CompileResult<()> { // Handle free variables (closure) - if !code.freevars.is_empty() { + let has_freevars = !code.freevars.is_empty(); + if has_freevars { // Build closure tuple by loading free variables + for var in &code.freevars { - let table = self.symbol_table_stack.last().unwrap(); - let symbol = match table.lookup(var) { - Some(s) => s, - None => { - return Err(self.error(CodegenErrorType::SyntaxError(format!( - "compiler_make_closure: cannot find symbol '{var}'", - )))); - } - }; + // Special case: If a class contains a method with a + // free variable that has the same name as a method, + // the name will be considered free *and* local in the + // class. It should be handled by the closure, as + // well as by the normal name lookup logic. + + // Get reference type using our get_ref_type function + let ref_type = self.get_ref_type(var).map_err(|e| self.error(e))?; + // Get parent code info let parent_code = self.code_stack.last().unwrap(); - let vars = match symbol.scope { - SymbolScope::Free => &parent_code.metadata.freevars, - SymbolScope::Cell => &parent_code.metadata.cellvars, - SymbolScope::TypeParams => &parent_code.metadata.cellvars, - _ if symbol.flags.contains(SymbolFlags::FREE_CLASS) => { - &parent_code.metadata.freevars - } + let cellvars_len = parent_code.metadata.cellvars.len(); + + // Look up the variable index based on reference type + let idx = match ref_type { + SymbolScope::Cell => parent_code + .metadata + .cellvars + .get_index_of(var) + .or_else(|| { + parent_code + .metadata + .freevars + .get_index_of(var) + .map(|i| i + cellvars_len) + }) + .ok_or_else(|| { + self.error(CodegenErrorType::SyntaxError(format!( + "compiler_make_closure: cannot find '{var}' in parent vars", + ))) + })?, + SymbolScope::Free => parent_code + .metadata + .freevars + .get_index_of(var) + .map(|i| i + cellvars_len) + .or_else(|| parent_code.metadata.cellvars.get_index_of(var)) + .ok_or_else(|| { + self.error(CodegenErrorType::SyntaxError(format!( + "compiler_make_closure: cannot find '{var}' in parent vars", + ))) + })?, _ => { return Err(self.error(CodegenErrorType::SyntaxError(format!( - "compiler_make_closure: invalid scope for '{var}'", + "compiler_make_closure: unexpected ref_type {ref_type:?} for '{var}'", )))); } }; - let idx = match vars.get_index_of(var) { - Some(i) => i, - None => { - return Err(self.error(CodegenErrorType::SyntaxError(format!( - "compiler_make_closure: cannot find '{var}' in parent vars", - )))); - } - }; - - let idx = if let SymbolScope::Free = symbol.scope { - idx + parent_code.metadata.cellvars.len() - } else { - idx - }; - emit!(self, Instruction::LoadClosure(idx.to_u32())); } @@ -2044,22 +2084,73 @@ impl Compiler<'_> { size: code.freevars.len().to_u32(), } ); - - flags |= bytecode::MakeFunctionFlags::CLOSURE; } - // Load code object + // load code object and create function self.emit_load_const(ConstantData::Code { code: Box::new(code), }); - // Load qualified name - self.emit_load_const(ConstantData::Str { - value: qualname.into(), - }); + // Create function with no flags + emit!(self, Instruction::MakeFunction); + + // Now set attributes one by one using SET_FUNCTION_ATTRIBUTE + // Note: The order matters! Values must be on stack before calling SET_FUNCTION_ATTRIBUTE + + // Set closure if needed + if has_freevars { + // Closure tuple is already on stack + emit!( + self, + Instruction::SetFunctionAttribute { + attr: bytecode::MakeFunctionFlags::CLOSURE + } + ); + } + + // Set annotations if present + if flags.contains(bytecode::MakeFunctionFlags::ANNOTATIONS) { + // Annotations dict is already on stack + emit!( + self, + Instruction::SetFunctionAttribute { + attr: bytecode::MakeFunctionFlags::ANNOTATIONS + } + ); + } - // Make function with proper flags - emit!(self, Instruction::MakeFunction(flags)); + // Set kwdefaults if present + if flags.contains(bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS) { + // kwdefaults dict is already on stack + emit!( + self, + Instruction::SetFunctionAttribute { + attr: bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS + } + ); + } + + // Set defaults if present + if flags.contains(bytecode::MakeFunctionFlags::DEFAULTS) { + // defaults tuple is already on stack + emit!( + self, + Instruction::SetFunctionAttribute { + attr: bytecode::MakeFunctionFlags::DEFAULTS + } + ); + } + + // Set type_params if present + if flags.contains(bytecode::MakeFunctionFlags::TYPE_PARAMS) { + // type_params tuple is already on stack + emit!( + self, + Instruction::SetFunctionAttribute { + attr: bytecode::MakeFunctionFlags::TYPE_PARAMS + } + ); + } Ok(()) } @@ -2262,7 +2353,7 @@ impl Compiler<'_> { func_flags |= bytecode::MakeFunctionFlags::TYPE_PARAMS; // Create class function with closure - self.make_closure(class_code, name, func_flags)?; + self.make_closure(class_code, func_flags)?; self.emit_load_const(ConstantData::Str { value: name.into() }); // Compile original bases @@ -2311,19 +2402,14 @@ impl Compiler<'_> { let type_params_code = self.exit_scope(); // Execute the type params function - let type_params_name = format!(""); - self.make_closure( - type_params_code, - &type_params_name, - bytecode::MakeFunctionFlags::empty(), - )?; + self.make_closure(type_params_code, bytecode::MakeFunctionFlags::empty())?; emit!(self, Instruction::CallFunctionPositional { nargs: 0 }); } else { // Non-generic class: standard path emit!(self, Instruction::LoadBuildClass); // Create class function with closure - self.make_closure(class_code, name, bytecode::MakeFunctionFlags::empty())?; + self.make_closure(class_code, bytecode::MakeFunctionFlags::empty())?; self.emit_load_const(ConstantData::Str { value: name.into() }); let call = if let Some(arguments) = arguments { @@ -4033,10 +4119,59 @@ impl Compiler<'_> { parameters, body, .. }) => { let prev_ctx = self.ctx; - let name = "".to_owned(); - let func_flags = self - .enter_function(&name, parameters.as_deref().unwrap_or(&Default::default()))?; + let default_params = Default::default(); + let params = parameters.as_deref().unwrap_or(&default_params); + + // Prepare defaults before entering function + let defaults: Vec<_> = std::iter::empty() + .chain(¶ms.posonlyargs) + .chain(¶ms.args) + .filter_map(|x| x.default.as_deref()) + .collect(); + let have_defaults = !defaults.is_empty(); + + if have_defaults { + let size = defaults.len().to_u32(); + for element in &defaults { + self.compile_expression(element)?; + } + emit!(self, Instruction::BuildTuple { size }); + } + + // Prepare keyword-only defaults + let mut kw_with_defaults = vec![]; + for kwonlyarg in ¶ms.kwonlyargs { + if let Some(default) = &kwonlyarg.default { + kw_with_defaults.push((&kwonlyarg.parameter, default)); + } + } + + let have_kwdefaults = !kw_with_defaults.is_empty(); + if have_kwdefaults { + let default_kw_count = kw_with_defaults.len(); + for (arg, default) in kw_with_defaults.iter() { + self.emit_load_const(ConstantData::Str { + value: arg.name.as_str().into(), + }); + self.compile_expression(default)?; + } + emit!( + self, + Instruction::BuildMap { + size: default_kw_count.to_u32(), + } + ); + } + + self.enter_function(&name, params)?; + let mut func_flags = bytecode::MakeFunctionFlags::empty(); + if have_defaults { + func_flags |= bytecode::MakeFunctionFlags::DEFAULTS; + } + if have_kwdefaults { + func_flags |= bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS; + } // Set qualname for lambda self.set_qualname(); @@ -4057,7 +4192,7 @@ impl Compiler<'_> { let code = self.exit_scope(); // Create lambda function with closure - self.make_closure(code, &name, func_flags)?; + self.make_closure(code, func_flags)?; self.ctx = prev_ctx; } @@ -4602,7 +4737,7 @@ impl Compiler<'_> { self.ctx = prev_ctx; // Create comprehension function with closure - self.make_closure(code, name, bytecode::MakeFunctionFlags::empty())?; + self.make_closure(code, bytecode::MakeFunctionFlags::empty())?; // Evaluate iterated item: self.compile_expression(&generators[0].iter)?; diff --git a/compiler/codegen/src/symboltable.rs b/compiler/codegen/src/symboltable.rs index 16a65bca116..7f7355bd734 100644 --- a/compiler/codegen/src/symboltable.rs +++ b/compiler/codegen/src/symboltable.rs @@ -127,6 +127,7 @@ pub enum SymbolScope { GlobalImplicit, Free, Cell, + // TODO: wrong place. not a symbol scope, but a COMPILER_SCOPE_TYPEPARAMS TypeParams, } diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index 3e74fe62738..0a6f3bf20d6 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -528,7 +528,10 @@ pub enum Instruction { JumpIfFalseOrPop { target: Arg

+ ''""" + s = f'' + self._run_check(s, [("starttag", "script", []), + ("data", content), + ("endtag", "script")]) + + @support.subTests('content', [ + 'a::before { content: ""; }', + 'a::before { content: "¬-an-entity-ref;"; }', + 'a::before { content: ""; }', + 'a::before { content: "\u2603"; }', + 'a::before { content: "< /style>"; }', + 'a::before { content: ""; }', + 'a::before { content: ""; }', + 'a::before { content: ""; }', + 'a::before { content: ""; }', + 'a::before { content: ""; }', + ]) + def test_style_content(self, content): + s = f'' + self._run_check(s, [("starttag", "style", []), + ("data", content), + ("endtag", "style")]) + + @support.subTests('content', [ + '', + "", + '', + '', + '', + '\u2603', + '< /title>', + '', + '', + '', + '', + '', + ]) + def test_title_content(self, content): + source = f"{content}" + self._run_check(source, [ + ("starttag", "title", []), + ("data", content), + ("endtag", "title"), + ]) + + @support.subTests('content', [ + '', + "", + '', + '', + '', + '\u2603', + '< /textarea>', + '', + '', + '', + '', + ]) + def test_textarea_content(self, content): + source = f"" + self._run_check(source, [ + ("starttag", "textarea", []), + ("data", content), + ("endtag", "textarea"), + ]) + + @support.subTests('endtag', ['script', 'SCRIPT', 'script ', 'script\n', + 'script/', 'script foo=bar', 'script foo=">"']) + def test_script_closing_tag(self, endtag): # see issue #13358 # make sure that HTMLParser calls handle_data only once for each CDATA. - # The normal event collector normalizes the events in get_events, - # so we override it to return the original list of events. - class Collector(EventCollector): - def get_events(self): - return self.events - content = """ ¬-an-entity-ref;

''""" - for element in [' script', 'script ', ' script ', - '\nscript', 'script\n', '\nscript\n']: - element_lower = element.lower().strip() - s = '{content}{tail}' + self._run_check(s, [("starttag", "script", []), + ("data", content if end else content + tail)], + collector=EventCollectorNoNormalize(convert_charrefs=False)) + + @support.subTests('tail,end', [ + ('', False), + ('<', False), + ('', True), + ]) + def test_eof_in_title(self, tail, end): + s = f'Egg & Spam{tail}' + self._run_check(s, [("starttag", "title", []), + ("data", "Egg & Spam" + ('' if end else tail))], + collector=EventCollectorNoNormalize(convert_charrefs=True)) + self._run_check(s, [("starttag", "title", []), + ('data', 'Egg '), + ('entityref', 'amp'), + ('data', ' Spam' + ('' if end else tail))], + collector=EventCollectorNoNormalize(convert_charrefs=False)) def test_comments(self): html = ("<!-- I'm a valid comment -->" '<!--me too!-->' '<!------>' + '<!----->' '<!---->' + # abrupt-closing-of-empty-comment + '<!--->' + '<!-->' '<!----I have many hyphens---->' '<!-- I have a > in the middle -->' - '<!-- and I have -- in the middle! -->') + '<!-- and I have -- in the middle! -->' + '<!--incorrectly-closed-comment--!>' + '<!----!>' + '<!----!-->' + '<!---- >-->' + '<!---!>-->' + '<!--!>-->' + # nested-comment + '<!-- <!-- nested --> -->' + '<!--<!-->' + '<!--<!--!>' + ) expected = [('comment', " I'm a valid comment "), ('comment', 'me too!'), ('comment', '--'), + ('comment', '-'), + ('comment', ''), + ('comment', ''), ('comment', ''), ('comment', '--I have many hyphens--'), ('comment', ' I have a > in the middle '), - ('comment', ' and I have -- in the middle! ')] + ('comment', ' and I have -- in the middle! '), + ('comment', 'incorrectly-closed-comment'), + ('comment', ''), + ('comment', '--!'), + ('comment', '-- >'), + ('comment', '-!>'), + ('comment', '!>'), + ('comment', ' <!-- nested '), ('data', ' -->'), + ('comment', '<!'), + ('comment', '<!'), + ] self._run_check(html, expected) def test_condcoms(self): @@ -430,28 +590,34 @@ def test_tolerant_parsing(self): ('data', '<'), ('starttag', 'bc<', [('a', None)]), ('endtag', 'html'), - ('data', '\n<img src="URL>'), - ('comment', '/img'), - ('endtag', 'html<')]) + ('data', '\n')]) def test_starttag_junk_chars(self): + self._run_check("<", [('data', '<')]) + self._run_check("<>", [('data', '<>')]) + self._run_check("< >", [('data', '< >')]) + self._run_check("< ", [('data', '< ')]) self._run_check("</>", []) + self._run_check("<$>", [('data', '<$>')]) self._run_check("</$>", [('comment', '$')]) self._run_check("</", [('data', '</')]) - self._run_check("</a", [('data', '</a')]) + self._run_check("</a", []) + self._run_check("</ a>", [('comment', ' a')]) + self._run_check("</ a", [('comment', ' a')]) self._run_check("<a<a>", [('starttag', 'a<a', [])]) self._run_check("</a<a>", [('endtag', 'a<a')]) - self._run_check("<!", [('data', '<!')]) - self._run_check("<a", [('data', '<a')]) - self._run_check("<a foo='bar'", [('data', "<a foo='bar'")]) - self._run_check("<a foo='bar", [('data', "<a foo='bar")]) - self._run_check("<a foo='>'", [('data', "<a foo='>'")]) - self._run_check("<a foo='>", [('data', "<a foo='>")]) + self._run_check("<!", [('comment', '')]) + self._run_check("<a", []) + self._run_check("<a foo='bar'", []) + self._run_check("<a foo='bar", []) + self._run_check("<a foo='>'", []) + self._run_check("<a foo='>", []) self._run_check("<a$>", [('starttag', 'a$', [])]) self._run_check("<a$b>", [('starttag', 'a$b', [])]) self._run_check("<a$b/>", [('startendtag', 'a$b', [])]) self._run_check("<a$b >", [('starttag', 'a$b', [])]) self._run_check("<a$b />", [('startendtag', 'a$b', [])]) + self._run_check("</a$b>", [('endtag', 'a$b')]) def test_slashes_in_starttag(self): self._run_check('<a foo="var"/>', [('startendtag', 'a', [('foo', 'var')])]) @@ -484,6 +650,10 @@ def test_slashes_in_starttag(self): ] self._run_check(html, expected) + def test_slashes_in_endtag(self): + self._run_check('</a/>', [('endtag', 'a')]) + self._run_check('</a foo="var"/>', [('endtag', 'a')]) + def test_declaration_junk_chars(self): self._run_check("<!DOCTYPE foo $ >", [('decl', 'DOCTYPE foo $ ')]) @@ -518,15 +688,11 @@ def test_invalid_end_tags(self): self._run_check(html, expected) def test_broken_invalid_end_tag(self): - # This is technically wrong (the "> shouldn't be included in the 'data') - # but is probably not worth fixing it (in addition to all the cases of - # the previous test, it would require a full attribute parsing). - # see #13993 html = '<b>This</b attr=">"> confuses the parser' expected = [('starttag', 'b', []), ('data', 'This'), ('endtag', 'b'), - ('data', '"> confuses the parser')] + ('data', ' confuses the parser')] self._run_check(html, expected) def test_correct_detection_of_start_tags(self): @@ -576,21 +742,50 @@ def test_EOF_in_charref(self): for html, expected in data: self._run_check(html, expected) - def test_EOF_in_comments_or_decls(self): + def test_eof_in_comments(self): data = [ - ('<!', [('data', '<!')]), - ('<!-', [('data', '<!-')]), - ('<!--', [('data', '<!--')]), - ('<![', [('data', '<![')]), - ('<![CDATA[', [('data', '<![CDATA[')]), - ('<![CDATA[x', [('data', '<![CDATA[x')]), - ('<!DOCTYPE', [('data', '<!DOCTYPE')]), - ('<!DOCTYPE HTML', [('data', '<!DOCTYPE HTML')]), + ('<!--', [('comment', '')]), + ('<!---', [('comment', '')]), + ('<!----', [('comment', '')]), + ('<!-----', [('comment', '-')]), + ('<!------', [('comment', '--')]), + ('<!----!', [('comment', '')]), + ('<!---!', [('comment', '-!')]), + ('<!---!>', [('comment', '-!>')]), + ('<!--foo', [('comment', 'foo')]), + ('<!--foo-', [('comment', 'foo')]), + ('<!--foo--', [('comment', 'foo')]), + ('<!--foo--!', [('comment', 'foo')]), + ('<!--<!--', [('comment', '<!')]), + ('<!--<!--!', [('comment', '<!')]), ] for html, expected in data: self._run_check(html, expected) + + def test_eof_in_declarations(self): + data = [ + ('<!', [('comment', '')]), + ('<!-', [('comment', '-')]), + ('<![', [('comment', '[')]), + ('<![CDATA[', [('unknown decl', 'CDATA[')]), + ('<![CDATA[x', [('unknown decl', 'CDATA[x')]), + ('<![CDATA[x]', [('unknown decl', 'CDATA[x]')]), + ('<![CDATA[x]]', [('unknown decl', 'CDATA[x]]')]), + ('<!DOCTYPE', [('decl', 'DOCTYPE')]), + ('<!DOCTYPE ', [('decl', 'DOCTYPE ')]), + ('<!DOCTYPE html', [('decl', 'DOCTYPE html')]), + ('<!DOCTYPE html ', [('decl', 'DOCTYPE html ')]), + ('<!DOCTYPE html PUBLIC', [('decl', 'DOCTYPE html PUBLIC')]), + ('<!DOCTYPE html PUBLIC "foo', [('decl', 'DOCTYPE html PUBLIC "foo')]), + ('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "foo', + [('decl', 'DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "foo')]), + ] + for html, expected in data: + self._run_check(html, expected) + def test_bogus_comments(self): - html = ('<! not really a comment >' + html = ('<!ELEMENT br EMPTY>' + '<! not really a comment >' '<! not a comment either -->' '<! -- close enough -->' '<!><!<-- this was an empty comment>' @@ -604,6 +799,7 @@ def test_bogus_comments(self): '<![CDATA]]>' # required '[' after CDATA ) expected = [ + ('comment', 'ELEMENT br EMPTY'), ('comment', ' not really a comment '), ('comment', ' not a comment either --'), ('comment', ' -- close enough --'), @@ -684,6 +880,26 @@ def test_convert_charrefs_dropped_text(self): ('endtag', 'a'), ('data', ' bar & baz')] ) + @support.requires_resource('cpu') + def test_eof_no_quadratic_complexity(self): + # Each of these examples used to take about an hour. + # Now they take a fraction of a second. + def check(source): + parser = html.parser.HTMLParser() + parser.feed(source) + parser.close() + n = 120_000 + check("<a " * n) + check("<a a=" * n) + check("</a " * 14 * n) + check("</a a=" * 11 * n) + check("<!--" * 4 * n) + check("<!" * 60 * n) + check("<?" * 19 * n) + check("</$" * 15 * n) + check("<![CDATA[" * 9 * n) + check("<!doctype" * 35 * n) + class AttributesTestCase(TestCaseBase): @@ -692,9 +908,15 @@ def test_attr_syntax(self): ("starttag", "a", [("b", "v"), ("c", "v"), ("d", "v"), ("e", None)]) ] self._run_check("""<a b='v' c="v" d=v e>""", output) - self._run_check("""<a b = 'v' c = "v" d = v e>""", output) - self._run_check("""<a\nb\n=\n'v'\nc\n=\n"v"\nd\n=\nv\ne>""", output) - self._run_check("""<a\tb\t=\t'v'\tc\t=\t"v"\td\t=\tv\te>""", output) + self._run_check("<a foo==bar>", [('starttag', 'a', [('foo', '=bar')])]) + self._run_check("<a foo =bar>", [('starttag', 'a', [('foo', 'bar')])]) + self._run_check("<a foo\t=bar>", [('starttag', 'a', [('foo', 'bar')])]) + self._run_check("<a foo\v=bar>", [('starttag', 'a', [('foo\v', 'bar')])]) + self._run_check("<a foo\xa0=bar>", [('starttag', 'a', [('foo\xa0', 'bar')])]) + self._run_check("<a foo= bar>", [('starttag', 'a', [('foo', 'bar')])]) + self._run_check("<a foo=\tbar>", [('starttag', 'a', [('foo', 'bar')])]) + self._run_check("<a foo=\vbar>", [('starttag', 'a', [('foo', '\vbar')])]) + self._run_check("<a foo=\xa0bar>", [('starttag', 'a', [('foo', '\xa0bar')])]) def test_attr_values(self): self._run_check("""<a b='xxx\n\txxx' c="yyy\t\nyyy" d='\txyz\n'>""", @@ -703,6 +925,10 @@ def test_attr_values(self): ("d", "\txyz\n")])]) self._run_check("""<a b='' c="">""", [("starttag", "a", [("b", ""), ("c", "")])]) + self._run_check("<a b=\tx c=\ny>", + [('starttag', 'a', [('b', 'x'), ('c', 'y')])]) + self._run_check("<a b=\v c=\xa0>", + [("starttag", "a", [("b", "\v"), ("c", "\xa0")])]) # Regression test for SF patch #669683. self._run_check("<e a=rgb(1,2,3)>", [("starttag", "e", [("a", "rgb(1,2,3)")])]) @@ -769,13 +995,17 @@ def test_malformed_attributes(self): ) expected = [ ('starttag', 'a', [('href', "test'style='color:red;bad1'")]), - ('data', 'test - bad1'), ('endtag', 'a'), + ('data', 'test - bad1'), + ('endtag', 'a'), ('starttag', 'a', [('href', "test'+style='color:red;ba2'")]), - ('data', 'test - bad2'), ('endtag', 'a'), + ('data', 'test - bad2'), + ('endtag', 'a'), ('starttag', 'a', [('href', "test'\xa0style='color:red;bad3'")]), - ('data', 'test - bad3'), ('endtag', 'a'), + ('data', 'test - bad3'), + ('endtag', 'a'), ('starttag', 'a', [('href', "test'\xa0style='color:red;bad4'")]), - ('data', 'test - bad4'), ('endtag', 'a') + ('data', 'test - bad4'), + ('endtag', 'a'), ] self._run_check(html, expected) From 59d71be85f761fca2fde375652d3b11f43e77284 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 7 Sep 2025 11:08:35 +0300 Subject: [PATCH 253/819] Update `test_collections.py` from 3.13.7 (#6136) --- Lib/test/test_collections.py | 38 +++++++++++++----------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 964bcc7288f..7b11601fc46 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -262,8 +262,7 @@ def __contains__(self, key): d = c.new_child(b=20, c=30) self.assertEqual(d.maps, [{'b': 20, 'c': 30}, {'a': 1, 'b': 2}]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_union_operators(self): cm1 = ChainMap(dict(a=1, b=2), dict(c=3, d=4)) cm2 = ChainMap(dict(a=10, e=5), dict(b=20, d=4)) @@ -470,8 +469,7 @@ def test_module_parameter(self): NT = namedtuple('NT', ['x', 'y'], module=collections) self.assertEqual(NT.__module__, collections) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_instance(self): Point = namedtuple('Point', 'x y') p = Point(11, 22) @@ -740,7 +738,7 @@ def validate_abstract_methods(self, abc, *names): stubs = methodstubs.copy() del stubs[name] C = type('C', (abc,), stubs) - self.assertRaises(TypeError, C, name) + self.assertRaises(TypeError, C) def validate_isinstance(self, abc, name): stub = lambda s, *args: 0 @@ -790,8 +788,7 @@ def _test_gen(): class TestOneTrickPonyABCs(ABCTestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_Awaitable(self): def gen(): yield @@ -844,8 +841,7 @@ class CoroLike: pass CoroLike = None support.gc_collect() # Kill CoroLike to clean-up ABCMeta cache - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_Coroutine(self): def gen(): yield @@ -971,7 +967,7 @@ class AnextOnly: async def __anext__(self): raise StopAsyncIteration self.assertNotIsInstance(AnextOnly(), AsyncIterator) - self.validate_abstract_methods(AsyncIterator, '__anext__', '__aiter__') + self.validate_abstract_methods(AsyncIterator, '__anext__') def test_Iterable(self): # Check some non-iterables @@ -1168,7 +1164,7 @@ def test_Iterator(self): for x in samples: self.assertIsInstance(x, Iterator) self.assertTrue(issubclass(type(x), Iterator), repr(type(x))) - self.validate_abstract_methods(Iterator, '__next__', '__iter__') + self.validate_abstract_methods(Iterator, '__next__') # Issue 10565 class NextOnly: @@ -1852,8 +1848,7 @@ def test_Mapping(self): for sample in [dict]: self.assertIsInstance(sample(), Mapping) self.assertTrue(issubclass(sample, Mapping)) - self.validate_abstract_methods(Mapping, '__contains__', '__iter__', '__len__', - '__getitem__') + self.validate_abstract_methods(Mapping, '__iter__', '__len__', '__getitem__') class MyMapping(Mapping): def __len__(self): return 0 @@ -1868,7 +1863,7 @@ def test_MutableMapping(self): for sample in [dict]: self.assertIsInstance(sample(), MutableMapping) self.assertTrue(issubclass(sample, MutableMapping)) - self.validate_abstract_methods(MutableMapping, '__contains__', '__iter__', '__len__', + self.validate_abstract_methods(MutableMapping, '__iter__', '__len__', '__getitem__', '__setitem__', '__delitem__') def test_MutableMapping_subclass(self): @@ -1907,8 +1902,7 @@ def test_Sequence(self): self.assertIsInstance(memoryview(b""), Sequence) self.assertTrue(issubclass(memoryview, Sequence)) self.assertTrue(issubclass(str, Sequence)) - self.validate_abstract_methods(Sequence, '__contains__', '__iter__', '__len__', - '__getitem__') + self.validate_abstract_methods(Sequence, '__len__', '__getitem__') def test_Sequence_mixins(self): class SequenceSubclass(Sequence): @@ -1967,10 +1961,7 @@ class X(ByteString): pass # No metaclass conflict class Z(ByteString, Awaitable): pass - # TODO: RUSTPYTHON - # Need to implement __buffer__ and __release_buffer__ - # https://docs.python.org/3.13/reference/datamodel.html#emulating-buffer-types - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; Need to implement __buffer__ and __release_buffer__ (https://docs.python.org/3.13/reference/datamodel.html#emulating-buffer-types) def test_Buffer(self): for sample in [bytes, bytearray, memoryview]: self.assertIsInstance(sample(b"x"), Buffer) @@ -1989,8 +1980,8 @@ def test_MutableSequence(self): self.assertTrue(issubclass(sample, MutableSequence)) self.assertTrue(issubclass(array.array, MutableSequence)) self.assertFalse(issubclass(str, MutableSequence)) - self.validate_abstract_methods(MutableSequence, '__contains__', '__iter__', - '__len__', '__getitem__', '__setitem__', '__delitem__', 'insert') + self.validate_abstract_methods(MutableSequence, '__len__', '__getitem__', + '__setitem__', '__delitem__', 'insert') def test_MutableSequence_mixins(self): # Test the mixins of MutableSequence by creating a minimal concrete @@ -2042,8 +2033,7 @@ def insert(self, index, value): self.assertEqual(len(mss), len(mss2)) self.assertEqual(list(mss), list(mss2)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_illegal_patma_flags(self): with self.assertRaises(TypeError): class Both(Collection): From 74c2d490ac86fc677515c038cb6c05e846e86f95 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 7 Sep 2025 11:09:55 +0300 Subject: [PATCH 254/819] Update `zoneinfo` and `_strptime` from 3.13.7 (#6139) --- Lib/_strptime.py | 373 +++++++++++++++++++++++++++++++------- Lib/zoneinfo/_common.py | 11 +- Lib/zoneinfo/_zoneinfo.py | 6 +- 3 files changed, 315 insertions(+), 75 deletions(-) diff --git a/Lib/_strptime.py b/Lib/_strptime.py index 798cf9f9d3f..a07eb5f9230 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -10,10 +10,13 @@ strptime -- Calculates the time struct represented by the passed-in string """ +import os import time import locale import calendar +import re from re import compile as re_compile +from re import sub as re_sub from re import IGNORECASE from re import escape as re_escape from datetime import (date as datetime_date, @@ -27,6 +30,41 @@ def _getlang(): # Figure out what the current language is set to. return locale.getlocale(locale.LC_TIME) +def _findall(haystack, needle): + # Find all positions of needle in haystack. + if not needle: + return + i = 0 + while True: + i = haystack.find(needle, i) + if i < 0: + break + yield i + i += len(needle) + +def _fixmonths(months): + yield from months + # The lower case of 'İ' ('\u0130') is 'i\u0307'. + # The re module only supports 1-to-1 character matching in + # case-insensitive mode. + for s in months: + if 'i\u0307' in s: + yield s.replace('i\u0307', '\u0130') + +lzh_TW_alt_digits = ( + # 〇:一:二:三:四:五:六:七:八:九 + '\u3007', '\u4e00', '\u4e8c', '\u4e09', '\u56db', + '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', + # 十:十一:十二:十三:十四:十五:十六:十七:十八:十九 + '\u5341', '\u5341\u4e00', '\u5341\u4e8c', '\u5341\u4e09', '\u5341\u56db', + '\u5341\u4e94', '\u5341\u516d', '\u5341\u4e03', '\u5341\u516b', '\u5341\u4e5d', + # 廿:廿一:廿二:廿三:廿四:廿五:廿六:廿七:廿八:廿九 + '\u5eff', '\u5eff\u4e00', '\u5eff\u4e8c', '\u5eff\u4e09', '\u5eff\u56db', + '\u5eff\u4e94', '\u5eff\u516d', '\u5eff\u4e03', '\u5eff\u516b', '\u5eff\u4e5d', + # 卅:卅一 + '\u5345', '\u5345\u4e00') + + class LocaleTime(object): """Stores and handles locale-specific information related to time. @@ -70,6 +108,7 @@ def __init__(self): self.__calc_weekday() self.__calc_month() self.__calc_am_pm() + self.__calc_alt_digits() self.__calc_timezone() self.__calc_date_time() if _getlang() != self.lang: @@ -101,53 +140,184 @@ def __calc_am_pm(self): 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()) + # br_FR has AM/PM info (' ',' '). + am_pm.append(time.strftime("%p", time_tuple).lower().strip()) self.am_pm = am_pm + def __calc_alt_digits(self): + # Set self.LC_alt_digits by using time.strftime(). + + # The magic data should contain all decimal digits. + time_tuple = time.struct_time((1998, 1, 27, 10, 43, 56, 1, 27, 0)) + s = time.strftime("%x%X", time_tuple) + if s.isascii(): + # Fast path -- all digits are ASCII. + self.LC_alt_digits = () + return + + digits = ''.join(sorted(set(re.findall(r'\d', s)))) + if len(digits) == 10 and ord(digits[-1]) == ord(digits[0]) + 9: + # All 10 decimal digits from the same set. + if digits.isascii(): + # All digits are ASCII. + self.LC_alt_digits = () + return + + self.LC_alt_digits = [a + b for a in digits for b in digits] + # Test whether the numbers contain leading zero. + time_tuple2 = time.struct_time((2000, 1, 1, 1, 1, 1, 5, 1, 0)) + if self.LC_alt_digits[1] not in time.strftime("%x %X", time_tuple2): + self.LC_alt_digits[:10] = digits + return + + # Either non-Gregorian calendar or non-decimal numbers. + if {'\u4e00', '\u4e03', '\u4e5d', '\u5341', '\u5eff'}.issubset(s): + # lzh_TW + self.LC_alt_digits = lzh_TW_alt_digits + return + + self.LC_alt_digits = None + def __calc_date_time(self): - # Set self.date_time, self.date, & self.time by using - # time.strftime(). + # Set self.LC_date_time, self.LC_date, self.LC_time and + # self.LC_time_ampm 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: + time_tuple2 = time.struct_time((1999,1,3,1,1,1,6,3,0)) + replacement_pairs = [] + + # Non-ASCII digits + if self.LC_alt_digits or self.LC_alt_digits is None: + for n, d in [(19, '%OC'), (99, '%Oy'), (22, '%OH'), + (44, '%OM'), (55, '%OS'), (17, '%Od'), + (3, '%Om'), (2, '%Ow'), (10, '%OI')]: + if self.LC_alt_digits is None: + s = chr(0x660 + n // 10) + chr(0x660 + n % 10) + replacement_pairs.append((s, d)) + if n < 10: + replacement_pairs.append((s[1], d)) + elif len(self.LC_alt_digits) > n: + replacement_pairs.append((self.LC_alt_digits[n], d)) + else: + replacement_pairs.append((time.strftime(d, time_tuple), d)) + replacement_pairs += [ + ('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'), + ] + + date_time = [] + for directive in ('%c', '%x', '%X', '%r'): + current_format = time.strftime(directive, time_tuple).lower() + current_format = current_format.replace('%', '%%') + # The month and the day of the week formats are treated specially + # because of a possible ambiguity in some locales where the full + # and abbreviated names are equal or names of different types + # are equal. See doc of __find_month_format for more details. + lst, fmt = self.__find_weekday_format(directive) + if lst: + current_format = current_format.replace(lst[2], fmt, 1) + lst, fmt = self.__find_month_format(directive) + if lst: + current_format = current_format.replace(lst[3], fmt, 1) + if self.am_pm[1]: # 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) + current_format = current_format.replace(self.am_pm[1], '%p') + for tz_values in self.timezone: + for tz in tz_values: + if tz: + current_format = current_format.replace(tz, "%Z") + # Transform all non-ASCII digits to digits in range U+0660 to U+0669. + if not current_format.isascii() and self.LC_alt_digits is None: + current_format = re_sub(r'\d(?<![0-9])', + lambda m: chr(0x0660 + int(m[0])), + current_format) + for old, new in replacement_pairs: + 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): + if '00' in time.strftime(directive, time_tuple2): U_W = '%W' else: U_W = '%U' - date_time[offset] = current_format.replace('11', U_W) + current_format = current_format.replace('11', U_W) + date_time.append(current_format) self.LC_date_time = date_time[0] self.LC_date = date_time[1] self.LC_time = date_time[2] + self.LC_time_ampm = date_time[3] + + def __find_month_format(self, directive): + """Find the month format appropriate for the current locale. + + In some locales (for example French and Hebrew), the default month + used in __calc_date_time has the same name in full and abbreviated + form. Also, the month name can by accident match other part of the + representation: the day of the week name (for example in Morisyen) + or the month number (for example in Japanese). Thus, cycle months + of the year and find all positions that match the month name for + each month, If no common positions are found, the representation + does not use the month name. + """ + full_indices = abbr_indices = None + for m in range(1, 13): + time_tuple = time.struct_time((1999, m, 17, 22, 44, 55, 2, 76, 0)) + datetime = time.strftime(directive, time_tuple).lower() + indices = set(_findall(datetime, self.f_month[m])) + if full_indices is None: + full_indices = indices + else: + full_indices &= indices + indices = set(_findall(datetime, self.a_month[m])) + if abbr_indices is None: + abbr_indices = set(indices) + else: + abbr_indices &= indices + if not full_indices and not abbr_indices: + return None, None + if full_indices: + return self.f_month, '%B' + if abbr_indices: + return self.a_month, '%b' + return None, None + + def __find_weekday_format(self, directive): + """Find the day of the week format appropriate for the current locale. + + Similar to __find_month_format(). + """ + full_indices = abbr_indices = None + for wd in range(7): + time_tuple = time.struct_time((1999, 3, 17, 22, 44, 55, wd, 76, 0)) + datetime = time.strftime(directive, time_tuple).lower() + indices = set(_findall(datetime, self.f_weekday[wd])) + if full_indices is None: + full_indices = indices + else: + full_indices &= indices + if self.f_weekday[wd] != self.a_weekday[wd]: + indices = set(_findall(datetime, self.a_weekday[wd])) + if abbr_indices is None: + abbr_indices = set(indices) + else: + abbr_indices &= indices + if not full_indices and not abbr_indices: + return None, None + if full_indices: + return self.f_weekday, '%A' + if abbr_indices: + return self.a_weekday, '%a' + return None, None def __calc_timezone(self): # Set self.timezone by using time.tzname. @@ -181,12 +351,14 @@ def __init__(self, locale_time=None): else: self.locale_time = LocaleTime() base = super() - base.__init__({ + mapping = { # 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])", + 'H': r"(?P<H>2[0-3]|[0-1]\d|\d| \d)", + 'k': r"(?P<H>2[0-3]|[0-1]\d|\d| \d)", + 'I': r"(?P<I>1[0-2]|0[1-9]|[1-9]| [1-9])", + 'l': r"(?P<I>1[0-2]|0[1-9]|[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])", @@ -198,25 +370,60 @@ def __init__(self, locale_time=None): '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'), + 'B': self.__seqToRE(_fixmonths(self.locale_time.f_month[1:]), 'B'), + 'b': self.__seqToRE(_fixmonths(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)) + '%': '%'} + if self.locale_time.LC_alt_digits is None: + for d in 'dmyCHIMS': + mapping['O' + d] = r'(?P<%s>\d\d|\d| \d)' % d + mapping['Ow'] = r'(?P<w>\d)' + else: + mapping.update({ + 'Od': self.__seqToRE(self.locale_time.LC_alt_digits[1:32], 'd', + '3[0-1]|[1-2][0-9]|0[1-9]|[1-9]'), + 'Om': self.__seqToRE(self.locale_time.LC_alt_digits[1:13], 'm', + '1[0-2]|0[1-9]|[1-9]'), + 'Ow': self.__seqToRE(self.locale_time.LC_alt_digits[:7], 'w', + '[0-6]'), + 'Oy': self.__seqToRE(self.locale_time.LC_alt_digits, 'y', + '[0-9][0-9]'), + 'OC': self.__seqToRE(self.locale_time.LC_alt_digits, 'C', + '[0-9][0-9]'), + 'OH': self.__seqToRE(self.locale_time.LC_alt_digits[:24], 'H', + '2[0-3]|[0-1][0-9]|[0-9]'), + 'OI': self.__seqToRE(self.locale_time.LC_alt_digits[1:13], 'I', + '1[0-2]|0[1-9]|[1-9]'), + 'OM': self.__seqToRE(self.locale_time.LC_alt_digits[:60], 'M', + '[0-5][0-9]|[0-9]'), + 'OS': self.__seqToRE(self.locale_time.LC_alt_digits[:62], 'S', + '6[0-1]|[0-5][0-9]|[0-9]'), + }) + mapping.update({ + 'e': mapping['d'], + 'Oe': mapping['Od'], + 'P': mapping['p'], + 'Op': mapping['p'], + 'W': mapping['U'].replace('U', 'W'), + }) + mapping['W'] = mapping['U'].replace('U', 'W') + + base.__init__(mapping) + base.__setitem__('T', self.pattern('%H:%M:%S')) + base.__setitem__('R', self.pattern('%H:%M')) + base.__setitem__('r', self.pattern(self.locale_time.LC_time_ampm)) base.__setitem__('X', self.pattern(self.locale_time.LC_time)) + base.__setitem__('x', self.pattern(self.locale_time.LC_date)) + base.__setitem__('c', self.pattern(self.locale_time.LC_date_time)) - def __seqToRE(self, to_convert, directive): + def __seqToRE(self, to_convert, directive, altregex=None): """Convert a list to a regex string for matching a directive. Want possible matching values to be from longest to shortest. This @@ -232,8 +439,9 @@ def __seqToRE(self, to_convert, directive): else: return '' regex = '|'.join(re_escape(stuff) for stuff in to_convert) - regex = '(?P<%s>%s' % (directive, regex) - return '%s)' % regex + if altregex is not None: + regex += '|' + altregex + return '(?P<%s>%s)' % (directive, regex) def pattern(self, format): """Return regex pattern for the format string. @@ -242,21 +450,36 @@ def pattern(self, format): 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) + format = re_sub(r"([\\.^$*+?\(\){}\[\]|])", r"\\\1", format) + format = re_sub(r'\s+', r'\\s+', format) + format = re_sub(r"'", "['\u02bc]", format) # needed for br_FR + year_in_format = False + day_of_month_in_format = False + def repl(m): + format_char = m[1] + match format_char: + case 'Y' | 'y' | 'G': + nonlocal year_in_format + year_in_format = True + case 'd': + nonlocal day_of_month_in_format + day_of_month_in_format = True + return self[format_char] + format = re_sub(r'%[-_0^#]*[0-9]*([OE]?\\?.?)', repl, format) + if day_of_month_in_format and not year_in_format: + import warnings + warnings.warn("""\ +Parsing dates involving a day of month without a year specified is ambiguious +and fails to parse leap day. The default behavior will change in Python 3.15 +to either always raise an exception or to use a different default year (TBD). +To avoid trouble, add a specific year to the input & format. +See https://github.com/python/cpython/issues/70647.""", + DeprecationWarning, + skip_file_prefixes=(os.path.dirname(__file__),)) + return format def compile(self, format): """Return a compiled re object for the format string.""" @@ -319,14 +542,13 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): # \\, 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 + bad_directive = bad_directive.replace('\\s', '') + if not bad_directive: + raise ValueError("stray %% in format '%s'" % format) from None + bad_directive = bad_directive.replace('\\', '', 1) 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: @@ -348,6 +570,15 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): # values weekday = julian = None found_dict = found.groupdict() + if locale_time.LC_alt_digits: + def parse_int(s): + try: + return locale_time.LC_alt_digits.index(s) + except ValueError: + return int(s) + else: + parse_int = int + for group_key in found_dict.keys(): # Directives not explicitly handled below: # c, x, X @@ -355,30 +586,34 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): # 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 + year = parse_int(found_dict['y']) + if 'C' in found_dict: + century = parse_int(found_dict['C']) + year += century * 100 else: - year += 1900 + # 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']) + month = parse_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']) + day = parse_int(found_dict['d']) elif group_key == 'H': - hour = int(found_dict['H']) + hour = parse_int(found_dict['H']) elif group_key == 'I': - hour = int(found_dict['I']) + hour = parse_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]): @@ -394,9 +629,9 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): if hour != 12: hour += 12 elif group_key == 'M': - minute = int(found_dict['M']) + minute = parse_int(found_dict['M']) elif group_key == 'S': - second = int(found_dict['S']) + second = parse_int(found_dict['S']) elif group_key == 'f': s = found_dict['f'] # Pad to always return microseconds. diff --git a/Lib/zoneinfo/_common.py b/Lib/zoneinfo/_common.py index 98cdfe37ca6..03cc42149f9 100644 --- a/Lib/zoneinfo/_common.py +++ b/Lib/zoneinfo/_common.py @@ -9,9 +9,13 @@ def load_tzdata(key): resource_name = components[-1] try: - return resources.files(package_name).joinpath(resource_name).open("rb") - except (ImportError, FileNotFoundError, UnicodeEncodeError): - # There are three types of exception that can be raised that all amount + path = resources.files(package_name).joinpath(resource_name) + # gh-85702: Prevent PermissionError on Windows + if path.is_dir(): + raise IsADirectoryError + return path.open("rb") + except (ImportError, FileNotFoundError, UnicodeEncodeError, IsADirectoryError): + # There are four types of exception that can be raised that all amount # to "we cannot find this key": # # ImportError: If package_name doesn't exist (e.g. if tzdata is not @@ -21,6 +25,7 @@ def load_tzdata(key): # (e.g. Europe/Krasnoy) # UnicodeEncodeError: If package_name or resource_name are not UTF-8, # such as keys containing a surrogate character. + # IsADirectoryError: If package_name without a resource_name specified. raise ZoneInfoNotFoundError(f"No time zone found with key {key}") diff --git a/Lib/zoneinfo/_zoneinfo.py b/Lib/zoneinfo/_zoneinfo.py index b77dc0ed391..3ffdb4c8371 100644 --- a/Lib/zoneinfo/_zoneinfo.py +++ b/Lib/zoneinfo/_zoneinfo.py @@ -75,12 +75,12 @@ def _new_instance(cls, key): return obj @classmethod - def from_file(cls, fobj, /, key=None): + def from_file(cls, file_obj, /, key=None): obj = super().__new__(cls) obj._key = key obj._file_path = None - obj._load_file(fobj) - obj._file_repr = repr(fobj) + obj._load_file(file_obj) + obj._file_repr = repr(file_obj) # Disable pickling for objects created from files obj.__reduce__ = obj._file_reduce From 7044d43dc8534636b1b846554a2c12eb3e1af35a Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 8 Sep 2025 07:48:51 +0300 Subject: [PATCH 255/819] Update `{site,sysconfig}.py` from 3.13.7 (#6132) * Update `{site,sysconfig}.py` from 3.13.7 * Update vm/src/stdlib/sysconfig.rs --- Lib/site.py | 229 +++++--- Lib/{sysconfig.py => sysconfig/__init__.py} | 554 ++++++++------------ Lib/sysconfig/__main__.py | 248 +++++++++ Lib/test/test_faulthandler.py | 1 + Lib/test/test_site.py | 60 ++- Lib/test/test_sysconfig.py | 256 +++++++-- vm/src/stdlib/mod.rs | 2 + vm/src/stdlib/sysconfig.rs | 14 + 8 files changed, 902 insertions(+), 462 deletions(-) rename Lib/{sysconfig.py => sysconfig/__init__.py} (53%) create mode 100644 Lib/sysconfig/__main__.py create mode 100644 vm/src/stdlib/sysconfig.rs diff --git a/Lib/site.py b/Lib/site.py index acc8481b136..2983ca71544 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -75,6 +75,7 @@ import _sitebuiltins import io import stat +import errno # Prefixes for site-packages; add additional prefixes like /usr/local here PREFIXES = [sys.prefix, sys.exec_prefix] @@ -179,35 +180,46 @@ def addpackage(sitedir, name, known_paths): return _trace(f"Processing .pth file: {fullname!r}") try: - # locale encoding is not ideal especially on Windows. But we have used - # it for a long time. setuptools uses the locale encoding too. - f = io.TextIOWrapper(io.open_code(fullname), encoding="locale") + with io.open_code(fullname) as f: + pth_content = f.read() except OSError: return - with f: - for n, line in enumerate(f): - if line.startswith("#"): - continue - if line.strip() == "": + + try: + # Accept BOM markers in .pth files as we do in source files + # (Windows PowerShell 5.1 makes it hard to emit UTF-8 files without a BOM) + pth_content = pth_content.decode("utf-8-sig") + except UnicodeDecodeError: + # Fallback to locale encoding for backward compatibility. + # We will deprecate this fallback in the future. + import locale + pth_content = pth_content.decode(locale.getencoding()) + _trace(f"Cannot read {fullname!r} as UTF-8. " + f"Using fallback encoding {locale.getencoding()!r}") + + for n, line in enumerate(pth_content.splitlines(), 1): + if line.startswith("#"): + continue + if line.strip() == "": + continue + try: + if line.startswith(("import ", "import\t")): + exec(line) continue - try: - if line.startswith(("import ", "import\t")): - exec(line) - continue - line = line.rstrip() - dir, dircase = makepath(sitedir, line) - if not dircase in known_paths and os.path.exists(dir): - sys.path.append(dir) - known_paths.add(dircase) - except Exception as exc: - print("Error processing line {:d} of {}:\n".format(n+1, fullname), - file=sys.stderr) - import traceback - for record in traceback.format_exception(exc): - for line in record.splitlines(): - print(' '+line, file=sys.stderr) - print("\nRemainder of file ignored", file=sys.stderr) - break + line = line.rstrip() + dir, dircase = makepath(sitedir, line) + if dircase not in known_paths and os.path.exists(dir): + sys.path.append(dir) + known_paths.add(dircase) + except Exception as exc: + print(f"Error processing line {n:d} of {fullname}:\n", + file=sys.stderr) + import traceback + for record in traceback.format_exception(exc): + for line in record.splitlines(): + print(' '+line, file=sys.stderr) + print("\nRemainder of file ignored", file=sys.stderr) + break if reset: known_paths = None return known_paths @@ -270,14 +282,18 @@ def check_enableusersite(): # # See https://bugs.python.org/issue29585 +# Copy of sysconfig._get_implementation() +def _get_implementation(): + return 'RustPython' # XXX: RustPython; for site-packages + # Copy of sysconfig._getuserbase() def _getuserbase(): env_base = os.environ.get("PYTHONUSERBASE", None) if env_base: return env_base - # Emscripten, VxWorks, and WASI have no home directories - if sys.platform in {"emscripten", "vxworks", "wasi"}: + # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories + if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}: return None def joinuser(*args): @@ -285,8 +301,7 @@ def joinuser(*args): if os.name == "nt": base = os.environ.get("APPDATA") or "~" - # XXX: RUSTPYTHON; please keep this change for site-packages - return joinuser(base, "RustPython") + return joinuser(base, _get_implementation()) if sys.platform == "darwin" and sys._framework: return joinuser("~", "Library", sys._framework, @@ -298,15 +313,22 @@ def joinuser(*args): # Same to sysconfig.get_path('purelib', os.name+'_user') def _get_path(userbase): version = sys.version_info + if hasattr(sys, 'abiflags') and 't' in sys.abiflags: + abi_thread = 't' + else: + abi_thread = '' + implementation = _get_implementation() + implementation_lower = implementation.lower() if os.name == 'nt': ver_nodot = sys.winver.replace('.', '') - return f'{userbase}\\RustPython{ver_nodot}\\site-packages' + return f'{userbase}\\{implementation}{ver_nodot}\\site-packages' if sys.platform == 'darwin' and sys._framework: - return f'{userbase}/lib/rustpython/site-packages' + return f'{userbase}/lib/{implementation_lower}/site-packages' - return f'{userbase}/lib/rustpython{version[0]}.{version[1]}/site-packages' + # XXX: RUSTPYTHON + return f'{userbase}/lib/rustpython{version[0]}.{version[1]}{abi_thread}/site-packages' def getuserbase(): @@ -372,6 +394,12 @@ def getsitepackages(prefixes=None): continue seen.add(prefix) + implementation = _get_implementation().lower() + ver = sys.version_info + if hasattr(sys, 'abiflags') and 't' in sys.abiflags: + abi_thread = 't' + else: + abi_thread = '' if os.sep == '/': libdirs = [sys.platlibdir] if sys.platlibdir != "lib": @@ -379,8 +407,7 @@ def getsitepackages(prefixes=None): for libdir in libdirs: path = os.path.join(prefix, libdir, - # XXX: RUSTPYTHON; please keep this change for site-packages - "rustpython%d.%d" % sys.version_info[:2], + f"{implementation}{ver[0]}.{ver[1]}{abi_thread}", "site-packages") sitepackages.append(path) else: @@ -417,8 +444,9 @@ def setcopyright(): """Set 'copyright' and 'credits' in builtins""" builtins.copyright = _sitebuiltins._Printer("copyright", sys.copyright) builtins.credits = _sitebuiltins._Printer("credits", """\ - Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands - for supporting Python development. See www.python.org for more information.""") + Thanks to CWI, CNRI, BeOpen, Zope Corporation, the Python Software + Foundation, and a cast of thousands for supporting Python + development. See www.python.org for more information.""") files, dirs = [], [] # Not all modules are required to have a __file__ attribute. See # PEP 420 for more details. @@ -437,27 +465,76 @@ def setcopyright(): def sethelper(): builtins.help = _sitebuiltins._Helper() + +def gethistoryfile(): + """Check if the PYTHON_HISTORY environment variable is set and define + it as the .python_history file. If PYTHON_HISTORY is not set, use the + default .python_history file. + """ + if not sys.flags.ignore_environment: + history = os.environ.get("PYTHON_HISTORY") + if history: + return history + return os.path.join(os.path.expanduser('~'), + '.python_history') + + def enablerlcompleter(): """Enable default readline configuration on interactive prompts, by registering a sys.__interactivehook__. + """ + sys.__interactivehook__ = register_readline + + +def register_readline(): + """Configure readline completion on interactive prompts. If the readline module can be imported, the hook will set the Tab key as completion key and register ~/.python_history as history file. This can be overridden in the sitecustomize or usercustomize module, or in a PYTHONSTARTUP file. """ - def register_readline(): - import atexit + if not sys.flags.ignore_environment: + PYTHON_BASIC_REPL = os.getenv("PYTHON_BASIC_REPL") + else: + PYTHON_BASIC_REPL = False + + import atexit + + try: try: import readline - import rlcompleter except ImportError: - return + readline = None + else: + import rlcompleter # noqa: F401 + except ImportError: + return + try: + if PYTHON_BASIC_REPL: + CAN_USE_PYREPL = False + else: + original_path = sys.path + sys.path = [p for p in original_path if p != ''] + try: + import _pyrepl.readline + if os.name == "nt": + import _pyrepl.windows_console + console_errors = (_pyrepl.windows_console._error,) + else: + import _pyrepl.unix_console + console_errors = _pyrepl.unix_console._error + from _pyrepl.main import CAN_USE_PYREPL + finally: + sys.path = original_path + except ImportError: + return + + if readline is not None: # Reading the initialization (config) file may not be enough to set a # completion key, so we set one first and then read the file. - readline_doc = getattr(readline, '__doc__', '') - if readline_doc is not None and 'libedit' in readline_doc: + if readline.backend == 'editline': readline.parse_and_bind('bind ^I rl_complete') else: readline.parse_and_bind('tab: complete') @@ -471,30 +548,44 @@ def register_readline(): # want to ignore the exception. pass - if readline.get_current_history_length() == 0: - # If no history was loaded, default to .python_history. - # The guard is necessary to avoid doubling history size at - # each interpreter exit when readline was already configured - # through a PYTHONSTARTUP hook, see: - # http://bugs.python.org/issue5845#msg198636 - history = os.path.join(os.path.expanduser('~'), - '.python_history') + if readline is None or readline.get_current_history_length() == 0: + # If no history was loaded, default to .python_history, + # or PYTHON_HISTORY. + # The guard is necessary to avoid doubling history size at + # each interpreter exit when readline was already configured + # through a PYTHONSTARTUP hook, see: + # http://bugs.python.org/issue5845#msg198636 + history = gethistoryfile() + + if CAN_USE_PYREPL: + readline_module = _pyrepl.readline + exceptions = (OSError, *console_errors) + else: + if readline is None: + return + readline_module = readline + exceptions = OSError + + try: + readline_module.read_history_file(history) + except exceptions: + pass + + def write_history(): try: - readline.read_history_file(history) - except OSError: + readline_module.write_history_file(history) + except (FileNotFoundError, PermissionError): + # home directory does not exist or is not writable + # https://bugs.python.org/issue19891 pass + except OSError: + if errno.EROFS: + pass # gh-128066: read-only file system + else: + raise - def write_history(): - try: - readline.write_history_file(history) - except OSError: - # bpo-19891, bpo-41193: Home directory does not exist - # or is not writable, or the filesystem is read-only. - pass - - atexit.register(write_history) + atexit.register(write_history) - sys.__interactivehook__ = register_readline def venv(known_paths): global PREFIXES, ENABLE_USER_SITE @@ -679,17 +770,5 @@ def exists(path): print(textwrap.dedent(help % (sys.argv[0], os.pathsep))) sys.exit(10) -def gethistoryfile(): - """Check if the PYTHON_HISTORY environment variable is set and define - it as the .python_history file. If PYTHON_HISTORY is not set, use the - default .python_history file. - """ - if not sys.flags.ignore_environment: - history = os.environ.get("PYTHON_HISTORY") - if history: - return history - return os.path.join(os.path.expanduser('~'), - '.python_history') - if __name__ == '__main__': _script() diff --git a/Lib/sysconfig.py b/Lib/sysconfig/__init__.py similarity index 53% rename from Lib/sysconfig.py rename to Lib/sysconfig/__init__.py index 9999d6bbd5d..3ad9df603f7 100644 --- a/Lib/sysconfig.py +++ b/Lib/sysconfig/__init__.py @@ -1,10 +1,9 @@ -# XXX: RUSTPYTHON; Trick to make sysconfig work as RustPython -exec(r''' """Access to Python's configuration information.""" import os import sys -from os.path import pardir, realpath +import threading +from os.path import realpath __all__ = [ 'get_config_h_filename', @@ -22,29 +21,30 @@ # Keys for get_config_var() that are never converted to Python integers. _ALWAYS_STR = { + 'IPHONEOS_DEPLOYMENT_TARGET', 'MACOSX_DEPLOYMENT_TARGET', } _INSTALL_SCHEMES = { 'posix_prefix': { - 'stdlib': '{installed_base}/{platlibdir}/python{py_version_short}', - 'platstdlib': '{platbase}/{platlibdir}/python{py_version_short}', - 'purelib': '{base}/lib/python{py_version_short}/site-packages', - 'platlib': '{platbase}/{platlibdir}/python{py_version_short}/site-packages', + 'stdlib': '{installed_base}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'platstdlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'purelib': '{base}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages', + 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}/site-packages', 'include': - '{installed_base}/include/python{py_version_short}{abiflags}', + '{installed_base}/include/{implementation_lower}{py_version_short}{abiflags}', 'platinclude': - '{installed_platbase}/include/python{py_version_short}{abiflags}', + '{installed_platbase}/include/{implementation_lower}{py_version_short}{abiflags}', 'scripts': '{base}/bin', 'data': '{base}', }, 'posix_home': { - 'stdlib': '{installed_base}/lib/python', - 'platstdlib': '{base}/lib/python', - 'purelib': '{base}/lib/python', - 'platlib': '{base}/lib/python', - 'include': '{installed_base}/include/python', - 'platinclude': '{installed_base}/include/python', + 'stdlib': '{installed_base}/lib/{implementation_lower}', + 'platstdlib': '{base}/lib/{implementation_lower}', + 'purelib': '{base}/lib/{implementation_lower}', + 'platlib': '{base}/lib/{implementation_lower}', + 'include': '{installed_base}/include/{implementation_lower}', + 'platinclude': '{installed_base}/include/{implementation_lower}', 'scripts': '{base}/bin', 'data': '{base}', }, @@ -58,6 +58,7 @@ 'scripts': '{base}/Scripts', 'data': '{base}', }, + # Downstream distributors can overwrite the default install scheme. # This is done to support downstream modifications where distributors change # the installation layout (eg. different site-packages directory). @@ -76,14 +77,14 @@ # Downstream distributors who patch posix_prefix/nt scheme are encouraged to # leave the following schemes unchanged 'posix_venv': { - 'stdlib': '{installed_base}/{platlibdir}/python{py_version_short}', - 'platstdlib': '{platbase}/{platlibdir}/python{py_version_short}', - 'purelib': '{base}/lib/python{py_version_short}/site-packages', - 'platlib': '{platbase}/{platlibdir}/python{py_version_short}/site-packages', + 'stdlib': '{installed_base}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'platstdlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'purelib': '{base}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages', + 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}/site-packages', 'include': - '{installed_base}/include/python{py_version_short}{abiflags}', + '{installed_base}/include/{implementation_lower}{py_version_short}{abiflags}', 'platinclude': - '{installed_platbase}/include/python{py_version_short}{abiflags}', + '{installed_platbase}/include/{implementation_lower}{py_version_short}{abiflags}', 'scripts': '{base}/bin', 'data': '{base}', }, @@ -105,6 +106,8 @@ else: _INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv'] +def _get_implementation(): + return 'RustPython' # XXX: For site-packages # NOTE: site.py has copy of this function. # Sync it when modify this function. @@ -113,8 +116,8 @@ def _getuserbase(): if env_base: return env_base - # Emscripten, VxWorks, and WASI have no home directories - if sys.platform in {"emscripten", "vxworks", "wasi"}: + # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories + if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}: return None def joinuser(*args): @@ -122,7 +125,7 @@ def joinuser(*args): if os.name == "nt": base = os.environ.get("APPDATA") or "~" - return joinuser(base, "Python") + return joinuser(base, _get_implementation()) if sys.platform == "darwin" and sys._framework: return joinuser("~", "Library", sys._framework, @@ -136,29 +139,29 @@ def joinuser(*args): _INSTALL_SCHEMES |= { # NOTE: When modifying "purelib" scheme, update site._get_path() too. 'nt_user': { - 'stdlib': '{userbase}/Python{py_version_nodot_plat}', - 'platstdlib': '{userbase}/Python{py_version_nodot_plat}', - 'purelib': '{userbase}/Python{py_version_nodot_plat}/site-packages', - 'platlib': '{userbase}/Python{py_version_nodot_plat}/site-packages', - 'include': '{userbase}/Python{py_version_nodot_plat}/Include', - 'scripts': '{userbase}/Python{py_version_nodot_plat}/Scripts', + 'stdlib': '{userbase}/{implementation}{py_version_nodot_plat}', + 'platstdlib': '{userbase}/{implementation}{py_version_nodot_plat}', + 'purelib': '{userbase}/{implementation}{py_version_nodot_plat}/site-packages', + 'platlib': '{userbase}/{implementation}{py_version_nodot_plat}/site-packages', + 'include': '{userbase}/{implementation}{py_version_nodot_plat}/Include', + 'scripts': '{userbase}/{implementation}{py_version_nodot_plat}/Scripts', 'data': '{userbase}', }, 'posix_user': { - 'stdlib': '{userbase}/{platlibdir}/python{py_version_short}', - 'platstdlib': '{userbase}/{platlibdir}/python{py_version_short}', - 'purelib': '{userbase}/lib/python{py_version_short}/site-packages', - 'platlib': '{userbase}/lib/python{py_version_short}/site-packages', - 'include': '{userbase}/include/python{py_version_short}', + 'stdlib': '{userbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'platstdlib': '{userbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'purelib': '{userbase}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages', + 'platlib': '{userbase}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages', + 'include': '{userbase}/include/{implementation_lower}{py_version_short}{abi_thread}', 'scripts': '{userbase}/bin', 'data': '{userbase}', }, 'osx_framework_user': { - 'stdlib': '{userbase}/lib/python', - 'platstdlib': '{userbase}/lib/python', - 'purelib': '{userbase}/lib/python/site-packages', - 'platlib': '{userbase}/lib/python/site-packages', - 'include': '{userbase}/include/python{py_version_short}', + 'stdlib': '{userbase}/lib/{implementation_lower}', + 'platstdlib': '{userbase}/lib/{implementation_lower}', + 'purelib': '{userbase}/lib/{implementation_lower}/site-packages', + 'platlib': '{userbase}/lib/{implementation_lower}/site-packages', + 'include': '{userbase}/include/{implementation_lower}{py_version_short}', 'scripts': '{userbase}/bin', 'data': '{userbase}', }, @@ -170,19 +173,15 @@ def joinuser(*args): _PY_VERSION = sys.version.split()[0] _PY_VERSION_SHORT = f'{sys.version_info[0]}.{sys.version_info[1]}' _PY_VERSION_SHORT_NO_DOT = f'{sys.version_info[0]}{sys.version_info[1]}' -_PREFIX = os.path.normpath(sys.prefix) _BASE_PREFIX = os.path.normpath(sys.base_prefix) -_EXEC_PREFIX = os.path.normpath(sys.exec_prefix) _BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix) +# Mutex guarding initialization of _CONFIG_VARS. +_CONFIG_VARS_LOCK = threading.RLock() _CONFIG_VARS = None +# True iff _CONFIG_VARS has been fully initialized. +_CONFIG_VARS_INITIALIZED = False _USER_BASE = None -# Regexes needed for parsing Makefile (and similar syntaxes, -# like old-style Setup files). -_variable_rx = r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)" -_findvar1_rx = r"\$\(([A-Za-z][A-Za-z0-9_]*)\)" -_findvar2_rx = r"\${([A-Za-z][A-Za-z0-9_]*)}" - def _safe_realpath(path): try: @@ -221,8 +220,15 @@ def _safe_realpath(path): def is_python_build(check_home=None): if check_home is not None: import warnings - warnings.warn("check_home argument is deprecated and ignored.", - DeprecationWarning, stacklevel=2) + warnings.warn( + ( + 'The check_home argument of sysconfig.is_python_build is ' + 'deprecated and its value is ignored. ' + 'It will be removed in Python 3.15.' + ), + DeprecationWarning, + stacklevel=2, + ) for fn in ("Setup", "Setup.local"): if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)): return True @@ -291,6 +297,7 @@ def _get_preferred_schemes(): 'home': 'posix_home', 'user': 'osx_framework_user', } + return { 'prefix': 'posix_prefix', 'home': 'posix_home', @@ -314,134 +321,6 @@ def get_default_scheme(): return get_preferred_scheme('prefix') -def _parse_makefile(filename, vars=None, keep_unresolved=True): - """Parse a Makefile-style file. - - A dictionary containing name/value pairs is returned. If an - optional dictionary is passed in as the second argument, it is - used instead of a new dictionary. - """ - import re - - if vars is None: - vars = {} - done = {} - notdone = {} - - with open(filename, encoding=sys.getfilesystemencoding(), - errors="surrogateescape") as f: - lines = f.readlines() - - for line in lines: - if line.startswith('#') or line.strip() == '': - continue - m = re.match(_variable_rx, line) - if m: - n, v = m.group(1, 2) - v = v.strip() - # `$$' is a literal `$' in make - tmpv = v.replace('$$', '') - - if "$" in tmpv: - notdone[n] = v - else: - try: - if n in _ALWAYS_STR: - raise ValueError - - v = int(v) - except ValueError: - # insert literal `$' - done[n] = v.replace('$$', '$') - else: - done[n] = v - - # do variable interpolation here - variables = list(notdone.keys()) - - # Variables with a 'PY_' prefix in the makefile. These need to - # be made available without that prefix through sysconfig. - # Special care is needed to ensure that variable expansion works, even - # if the expansion uses the name without a prefix. - renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS') - - while len(variables) > 0: - for name in tuple(variables): - value = notdone[name] - m1 = re.search(_findvar1_rx, value) - m2 = re.search(_findvar2_rx, value) - if m1 and m2: - m = m1 if m1.start() < m2.start() else m2 - else: - m = m1 if m1 else m2 - if m is not None: - n = m.group(1) - found = True - if n in done: - item = str(done[n]) - elif n in notdone: - # get it on a subsequent round - found = False - elif n in os.environ: - # do it like make: fall back to environment - item = os.environ[n] - - elif n in renamed_variables: - if (name.startswith('PY_') and - name[3:] in renamed_variables): - item = "" - - elif 'PY_' + n in notdone: - found = False - - else: - item = str(done['PY_' + n]) - - else: - done[n] = item = "" - - if found: - after = value[m.end():] - value = value[:m.start()] + item + after - if "$" in after: - notdone[name] = value - else: - try: - if name in _ALWAYS_STR: - raise ValueError - value = int(value) - except ValueError: - done[name] = value.strip() - else: - done[name] = value - variables.remove(name) - - if name.startswith('PY_') \ - and name[3:] in renamed_variables: - - name = name[3:] - if name not in done: - done[name] = value - - else: - # Adds unresolved variables to the done dict. - # This is disabled when called from distutils.sysconfig - if keep_unresolved: - done[name] = value - # bogus variable reference (e.g. "prefix=$/opt/python"); - # just drop it since we can't deal - variables.remove(name) - - # strip spurious spaces - for k, v in done.items(): - if isinstance(v, str): - done[k] = v.strip() - - # save the results in the global dictionary - vars.update(done) - return vars - - def get_makefile_filename(): """Return the path of the Makefile.""" if _PYTHON_BUILD: @@ -462,91 +341,44 @@ def _get_sysconfigdata_name(): f'_sysconfigdata_{sys.abiflags}_{sys.platform}_{multiarch}', ) - -def _generate_posix_vars(): - """Generate the Python module containing build-time variables.""" - import pprint - vars = {} - # load the installed Makefile: - makefile = get_makefile_filename() - try: - _parse_makefile(makefile, vars) - except OSError as e: - msg = f"invalid Python installation: unable to open {makefile}" - if hasattr(e, "strerror"): - msg = f"{msg} ({e.strerror})" - raise OSError(msg) - # load the installed pyconfig.h: - config_h = get_config_h_filename() - try: - with open(config_h, encoding="utf-8") as f: - parse_config_h(f, vars) - except OSError as e: - msg = f"invalid Python installation: unable to open {config_h}" - if hasattr(e, "strerror"): - msg = f"{msg} ({e.strerror})" - raise OSError(msg) - # On AIX, there are wrong paths to the linker scripts in the Makefile - # -- these paths are relative to the Python source, but when installed - # the scripts are in another directory. - if _PYTHON_BUILD: - vars['BLDSHARED'] = vars['LDSHARED'] - - # There's a chicken-and-egg situation on OS X with regards to the - # _sysconfigdata module after the changes introduced by #15298: - # get_config_vars() is called by get_platform() as part of the - # `make pybuilddir.txt` target -- which is a precursor to the - # _sysconfigdata.py module being constructed. Unfortunately, - # get_config_vars() eventually calls _init_posix(), which attempts - # to import _sysconfigdata, which we won't have built yet. In order - # for _init_posix() to work, if we're on Darwin, just mock up the - # _sysconfigdata module manually and populate it with the build vars. - # This is more than sufficient for ensuring the subsequent call to - # get_platform() succeeds. - name = _get_sysconfigdata_name() - if 'darwin' in sys.platform: - import types - module = types.ModuleType(name) - module.build_time_vars = vars - sys.modules[name] = module - - pybuilddir = f'build/lib.{get_platform()}-{_PY_VERSION_SHORT}' - if hasattr(sys, "gettotalrefcount"): - pybuilddir += '-pydebug' - os.makedirs(pybuilddir, exist_ok=True) - destfile = os.path.join(pybuilddir, name + '.py') - - with open(destfile, 'w', encoding='utf8') as f: - f.write('# system configuration generated and used by' - ' the sysconfig module\n') - f.write('build_time_vars = ') - pprint.pprint(vars, stream=f) - - # Create file used for sys.path fixup -- see Modules/getpath.c - with open('pybuilddir.txt', 'w', encoding='utf8') as f: - f.write(pybuilddir) - def _init_posix(vars): """Initialize the module as appropriate for POSIX systems.""" # _sysconfigdata is generated at build time, see _generate_posix_vars() name = _get_sysconfigdata_name() - _temp = __import__(name, globals(), locals(), ['build_time_vars'], 0) + + # For cross builds, the path to the target's sysconfigdata must be specified + # so it can be imported. It cannot be in PYTHONPATH, as foreign modules in + # sys.path can cause crashes when loaded by the host interpreter. + # Rely on truthiness as a valueless env variable is still an empty string. + # See OS X note in _generate_posix_vars re _sysconfigdata. + if (path := os.environ.get('_PYTHON_SYSCONFIGDATA_PATH')): + from importlib.machinery import FileFinder, SourceFileLoader, SOURCE_SUFFIXES + from importlib.util import module_from_spec + spec = FileFinder(path, (SourceFileLoader, SOURCE_SUFFIXES)).find_spec(name) + _temp = module_from_spec(spec) + spec.loader.exec_module(_temp) + else: + _temp = __import__(name, globals(), locals(), ['build_time_vars'], 0) build_time_vars = _temp.build_time_vars vars.update(build_time_vars) def _init_non_posix(vars): """Initialize the module as appropriate for NT""" # set basic install directories - import _imp + import _winapi + import _sysconfig vars['LIBDEST'] = get_path('stdlib') vars['BINLIBDEST'] = get_path('platstdlib') vars['INCLUDEPY'] = get_path('include') - try: - # GH-99201: _imp.extension_suffixes may be empty when - # HAVE_DYNAMIC_LOADING is not set. In this case, don't set EXT_SUFFIX. - vars['EXT_SUFFIX'] = _imp.extension_suffixes()[0] - except IndexError: - pass + + # Add EXT_SUFFIX, SOABI, and Py_GIL_DISABLED + vars.update(_sysconfig.config_vars()) + + vars['LIBDIR'] = _safe_realpath(os.path.join(get_config_var('installed_base'), 'libs')) + if hasattr(sys, 'dllhandle'): + dllhandle = _winapi.GetModuleFileName(sys.dllhandle) + vars['LIBRARY'] = os.path.basename(_safe_realpath(dllhandle)) + vars['LDLIBRARY'] = vars['LIBRARY'] vars['EXE'] = '.exe' vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable)) @@ -595,7 +427,7 @@ def get_config_h_filename(): """Return the path of pyconfig.h.""" if _PYTHON_BUILD: if os.name == "nt": - inc_dir = os.path.join(_PROJECT_BASE, "PC") + inc_dir = os.path.dirname(sys._base_executable) else: inc_dir = _PROJECT_BASE else: @@ -633,6 +465,78 @@ def get_path(name, scheme=get_default_scheme(), vars=None, expand=True): return get_paths(scheme, vars, expand)[name] +def _init_config_vars(): + global _CONFIG_VARS + _CONFIG_VARS = {} + # Normalized versions of prefix and exec_prefix are handy to have; + # in fact, these are the standard versions used most places in the + # Distutils. + _PREFIX = os.path.normpath(sys.prefix) + _EXEC_PREFIX = os.path.normpath(sys.exec_prefix) + _CONFIG_VARS['prefix'] = _PREFIX # FIXME: This gets overwriten by _init_posix. + _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX # FIXME: This gets overwriten by _init_posix. + _CONFIG_VARS['py_version'] = _PY_VERSION + _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT + _CONFIG_VARS['py_version_nodot'] = _PY_VERSION_SHORT_NO_DOT + _CONFIG_VARS['installed_base'] = _BASE_PREFIX + _CONFIG_VARS['base'] = _PREFIX + _CONFIG_VARS['installed_platbase'] = _BASE_EXEC_PREFIX + _CONFIG_VARS['platbase'] = _EXEC_PREFIX + _CONFIG_VARS['projectbase'] = _PROJECT_BASE + _CONFIG_VARS['platlibdir'] = sys.platlibdir + _CONFIG_VARS['implementation'] = _get_implementation() + _CONFIG_VARS['implementation_lower'] = _get_implementation().lower() + try: + _CONFIG_VARS['abiflags'] = sys.abiflags + except AttributeError: + # sys.abiflags may not be defined on all platforms. + _CONFIG_VARS['abiflags'] = '' + try: + _CONFIG_VARS['py_version_nodot_plat'] = sys.winver.replace('.', '') + except AttributeError: + _CONFIG_VARS['py_version_nodot_plat'] = '' + + if os.name == 'nt': + _init_non_posix(_CONFIG_VARS) + _CONFIG_VARS['VPATH'] = sys._vpath + if os.name == 'posix': + _init_posix(_CONFIG_VARS) + if _HAS_USER_BASE: + # Setting 'userbase' is done below the call to the + # init function to enable using 'get_config_var' in + # the init-function. + _CONFIG_VARS['userbase'] = _getuserbase() + + # e.g., 't' for free-threaded or '' for default build + _CONFIG_VARS['abi_thread'] = 't' if _CONFIG_VARS.get('Py_GIL_DISABLED') else '' + + # Always convert srcdir to an absolute path + srcdir = _CONFIG_VARS.get('srcdir', _PROJECT_BASE) + if os.name == 'posix': + if _PYTHON_BUILD: + # If srcdir is a relative path (typically '.' or '..') + # then it should be interpreted relative to the directory + # containing Makefile. + base = os.path.dirname(get_makefile_filename()) + srcdir = os.path.join(base, srcdir) + else: + # srcdir is not meaningful since the installation is + # spread about the filesystem. We choose the + # directory containing the Makefile since we know it + # exists. + srcdir = os.path.dirname(get_makefile_filename()) + _CONFIG_VARS['srcdir'] = _safe_realpath(srcdir) + + # OS X platforms require special customization to handle + # multi-architecture, multi-os-version installers + if sys.platform == 'darwin': + import _osx_support + _osx_support.customize_config_vars(_CONFIG_VARS) + + global _CONFIG_VARS_INITIALIZED + _CONFIG_VARS_INITIALIZED = True + + def get_config_vars(*args): """With no arguments, return a dictionary of all configuration variables relevant for the current platform. @@ -643,66 +547,26 @@ def get_config_vars(*args): With arguments, return a list of values that result from looking up each argument in the configuration variable dictionary. """ - global _CONFIG_VARS - if _CONFIG_VARS is None: - _CONFIG_VARS = {} - # Normalized versions of prefix and exec_prefix are handy to have; - # in fact, these are the standard versions used most places in the - # Distutils. - _CONFIG_VARS['prefix'] = _PREFIX - _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX - _CONFIG_VARS['py_version'] = _PY_VERSION - _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT - _CONFIG_VARS['py_version_nodot'] = _PY_VERSION_SHORT_NO_DOT - _CONFIG_VARS['installed_base'] = _BASE_PREFIX - _CONFIG_VARS['base'] = _PREFIX - _CONFIG_VARS['installed_platbase'] = _BASE_EXEC_PREFIX - _CONFIG_VARS['platbase'] = _EXEC_PREFIX - _CONFIG_VARS['projectbase'] = _PROJECT_BASE - _CONFIG_VARS['platlibdir'] = sys.platlibdir - try: - _CONFIG_VARS['abiflags'] = sys.abiflags - except AttributeError: - # sys.abiflags may not be defined on all platforms. - _CONFIG_VARS['abiflags'] = '' - try: - _CONFIG_VARS['py_version_nodot_plat'] = sys.winver.replace('.', '') - except AttributeError: - _CONFIG_VARS['py_version_nodot_plat'] = '' - - if os.name == 'nt': - _init_non_posix(_CONFIG_VARS) - _CONFIG_VARS['VPATH'] = sys._vpath - if os.name == 'posix': - _init_posix(_CONFIG_VARS) - if _HAS_USER_BASE: - # Setting 'userbase' is done below the call to the - # init function to enable using 'get_config_var' in - # the init-function. - _CONFIG_VARS['userbase'] = _getuserbase() - - # Always convert srcdir to an absolute path - srcdir = _CONFIG_VARS.get('srcdir', _PROJECT_BASE) - if os.name == 'posix': - if _PYTHON_BUILD: - # If srcdir is a relative path (typically '.' or '..') - # then it should be interpreted relative to the directory - # containing Makefile. - base = os.path.dirname(get_makefile_filename()) - srcdir = os.path.join(base, srcdir) - else: - # srcdir is not meaningful since the installation is - # spread about the filesystem. We choose the - # directory containing the Makefile since we know it - # exists. - srcdir = os.path.dirname(get_makefile_filename()) - _CONFIG_VARS['srcdir'] = _safe_realpath(srcdir) - - # OS X platforms require special customization to handle - # multi-architecture, multi-os-version installers - if sys.platform == 'darwin': - import _osx_support - _osx_support.customize_config_vars(_CONFIG_VARS) + global _CONFIG_VARS_INITIALIZED + + # Avoid claiming the lock once initialization is complete. + if not _CONFIG_VARS_INITIALIZED: + with _CONFIG_VARS_LOCK: + # Test again with the lock held to avoid races. Note that + # we test _CONFIG_VARS here, not _CONFIG_VARS_INITIALIZED, + # to ensure that recursive calls to get_config_vars() + # don't re-enter init_config_vars(). + if _CONFIG_VARS is None: + _init_config_vars() + else: + # If the site module initialization happened after _CONFIG_VARS was + # initialized, a virtual environment might have been activated, resulting in + # variables like sys.prefix changing their value, so we need to re-init the + # config vars (see GH-126789). + if _CONFIG_VARS['base'] != os.path.normpath(sys.prefix): + with _CONFIG_VARS_LOCK: + _CONFIG_VARS_INITIALIZED = False + _init_config_vars() if args: vals = [] @@ -737,7 +601,8 @@ def get_platform(): solaris-2.6-sun4u Windows will return one of: - win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) + win-amd64 (64-bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) + win-arm64 (64-bit Windows on ARM64 (aka AArch64) win32 (all others - specifically, sys.platform is returned) For other non-POSIX platforms, currently just returns 'sys.platform'. @@ -770,10 +635,22 @@ def get_platform(): machine = machine.replace('/', '-') if osname[:5] == "linux": - # At least on Linux/Intel, 'machine' is the processor -- - # i386, etc. - # XXX what about Alpha, SPARC, etc? - return f"{osname}-{machine}" + if sys.platform == "android": + osname = "android" + release = get_config_var("ANDROID_API_LEVEL") + + # Wheel tags use the ABI names from Android's own tools. + machine = { + "x86_64": "x86_64", + "i686": "x86", + "aarch64": "arm64_v8a", + "armv7l": "armeabi_v7a", + }[machine] + else: + # At least on Linux/Intel, 'machine' is the processor -- + # i386, etc. + # XXX what about Alpha, SPARC, etc? + return f"{osname}-{machine}" elif osname[:5] == "sunos": if release[0] >= "5": # SunOS 5 == Solaris 2 osname = "solaris" @@ -795,10 +672,15 @@ def get_platform(): if m: release = m.group() elif osname[:6] == "darwin": - import _osx_support - osname, release, machine = _osx_support.get_platform_osx( - get_config_vars(), - osname, release, machine) + if sys.platform == "ios": + release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "13.0") + osname = sys.platform + machine = sys.implementation._multiarch + else: + import _osx_support + osname, release, machine = _osx_support.get_platform_osx( + get_config_vars(), + osname, release, machine) return f"{osname}-{release}-{machine}" @@ -807,6 +689,10 @@ def get_python_version(): return _PY_VERSION_SHORT +def _get_python_version_abi(): + return _PY_VERSION_SHORT + get_config_var("abi_thread") + + def expand_makefile_vars(s, vars): """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in 'string' according to 'vars' (a dictionary mapping variable names to @@ -817,6 +703,9 @@ def expand_makefile_vars(s, vars): """ import re + _findvar1_rx = r"\$\(([A-Za-z][A-Za-z0-9_]*)\)" + _findvar2_rx = r"\${([A-Za-z][A-Za-z0-9_]*)}" + # This algorithm does multiple expansion, so if vars['foo'] contains # "${bar}", it will expand ${foo} to ${bar}, and then expand # ${bar}... and so forth. This is fine as long as 'vars' comes from @@ -831,28 +720,3 @@ def expand_makefile_vars(s, vars): else: break return s - - -def _print_dict(title, data): - for index, (key, value) in enumerate(sorted(data.items())): - if index == 0: - print(f'{title}: ') - print(f'\t{key} = "{value}"') - - -def _main(): - """Display all information sysconfig detains.""" - if '--generate-posix-vars' in sys.argv: - _generate_posix_vars() - return - print(f'Platform: "{get_platform()}"') - print(f'Python version: "{get_python_version()}"') - print(f'Current installation scheme: "{get_default_scheme()}"') - print() - _print_dict('Paths', get_paths()) - print() - _print_dict('Variables', get_config_vars()) - -if __name__ == '__main__': - _main() -'''.replace("Python", "RustPython").replace("/python", "/rustpython")) diff --git a/Lib/sysconfig/__main__.py b/Lib/sysconfig/__main__.py new file mode 100644 index 00000000000..d7257b9d2d0 --- /dev/null +++ b/Lib/sysconfig/__main__.py @@ -0,0 +1,248 @@ +import os +import sys +from sysconfig import ( + _ALWAYS_STR, + _PYTHON_BUILD, + _get_sysconfigdata_name, + get_config_h_filename, + get_config_vars, + get_default_scheme, + get_makefile_filename, + get_paths, + get_platform, + get_python_version, + parse_config_h, +) + + +# Regexes needed for parsing Makefile (and similar syntaxes, +# like old-style Setup files). +_variable_rx = r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)" +_findvar1_rx = r"\$\(([A-Za-z][A-Za-z0-9_]*)\)" +_findvar2_rx = r"\${([A-Za-z][A-Za-z0-9_]*)}" + + +def _parse_makefile(filename, vars=None, keep_unresolved=True): + """Parse a Makefile-style file. + + A dictionary containing name/value pairs is returned. If an + optional dictionary is passed in as the second argument, it is + used instead of a new dictionary. + """ + import re + + if vars is None: + vars = {} + done = {} + notdone = {} + + with open(filename, encoding=sys.getfilesystemencoding(), + errors="surrogateescape") as f: + lines = f.readlines() + + for line in lines: + if line.startswith('#') or line.strip() == '': + continue + m = re.match(_variable_rx, line) + if m: + n, v = m.group(1, 2) + v = v.strip() + # `$$' is a literal `$' in make + tmpv = v.replace('$$', '') + + if "$" in tmpv: + notdone[n] = v + else: + try: + if n in _ALWAYS_STR: + raise ValueError + + v = int(v) + except ValueError: + # insert literal `$' + done[n] = v.replace('$$', '$') + else: + done[n] = v + + # do variable interpolation here + variables = list(notdone.keys()) + + # Variables with a 'PY_' prefix in the makefile. These need to + # be made available without that prefix through sysconfig. + # Special care is needed to ensure that variable expansion works, even + # if the expansion uses the name without a prefix. + renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS') + + while len(variables) > 0: + for name in tuple(variables): + value = notdone[name] + m1 = re.search(_findvar1_rx, value) + m2 = re.search(_findvar2_rx, value) + if m1 and m2: + m = m1 if m1.start() < m2.start() else m2 + else: + m = m1 if m1 else m2 + if m is not None: + n = m.group(1) + found = True + if n in done: + item = str(done[n]) + elif n in notdone: + # get it on a subsequent round + found = False + elif n in os.environ: + # do it like make: fall back to environment + item = os.environ[n] + + elif n in renamed_variables: + if (name.startswith('PY_') and + name[3:] in renamed_variables): + item = "" + + elif 'PY_' + n in notdone: + found = False + + else: + item = str(done['PY_' + n]) + + else: + done[n] = item = "" + + if found: + after = value[m.end():] + value = value[:m.start()] + item + after + if "$" in after: + notdone[name] = value + else: + try: + if name in _ALWAYS_STR: + raise ValueError + value = int(value) + except ValueError: + done[name] = value.strip() + else: + done[name] = value + variables.remove(name) + + if name.startswith('PY_') \ + and name[3:] in renamed_variables: + + name = name[3:] + if name not in done: + done[name] = value + + else: + # Adds unresolved variables to the done dict. + # This is disabled when called from distutils.sysconfig + if keep_unresolved: + done[name] = value + # bogus variable reference (e.g. "prefix=$/opt/python"); + # just drop it since we can't deal + variables.remove(name) + + # strip spurious spaces + for k, v in done.items(): + if isinstance(v, str): + done[k] = v.strip() + + # save the results in the global dictionary + vars.update(done) + return vars + + +def _print_config_dict(d, stream): + print ("{", file=stream) + for k, v in sorted(d.items()): + print(f" {k!r}: {v!r},", file=stream) + print ("}", file=stream) + + +def _generate_posix_vars(): + """Generate the Python module containing build-time variables.""" + vars = {} + # load the installed Makefile: + makefile = get_makefile_filename() + try: + _parse_makefile(makefile, vars) + except OSError as e: + msg = f"invalid Python installation: unable to open {makefile}" + if hasattr(e, "strerror"): + msg = f"{msg} ({e.strerror})" + raise OSError(msg) + # load the installed pyconfig.h: + config_h = get_config_h_filename() + try: + with open(config_h, encoding="utf-8") as f: + parse_config_h(f, vars) + except OSError as e: + msg = f"invalid Python installation: unable to open {config_h}" + if hasattr(e, "strerror"): + msg = f"{msg} ({e.strerror})" + raise OSError(msg) + # On AIX, there are wrong paths to the linker scripts in the Makefile + # -- these paths are relative to the Python source, but when installed + # the scripts are in another directory. + if _PYTHON_BUILD: + vars['BLDSHARED'] = vars['LDSHARED'] + + # There's a chicken-and-egg situation on OS X with regards to the + # _sysconfigdata module after the changes introduced by #15298: + # get_config_vars() is called by get_platform() as part of the + # `make pybuilddir.txt` target -- which is a precursor to the + # _sysconfigdata.py module being constructed. Unfortunately, + # get_config_vars() eventually calls _init_posix(), which attempts + # to import _sysconfigdata, which we won't have built yet. In order + # for _init_posix() to work, if we're on Darwin, just mock up the + # _sysconfigdata module manually and populate it with the build vars. + # This is more than sufficient for ensuring the subsequent call to + # get_platform() succeeds. + name = _get_sysconfigdata_name() + if 'darwin' in sys.platform: + import types + module = types.ModuleType(name) + module.build_time_vars = vars + sys.modules[name] = module + + pybuilddir = f'build/lib.{get_platform()}-{get_python_version()}' + if hasattr(sys, "gettotalrefcount"): + pybuilddir += '-pydebug' + os.makedirs(pybuilddir, exist_ok=True) + destfile = os.path.join(pybuilddir, name + '.py') + + with open(destfile, 'w', encoding='utf8') as f: + f.write('# system configuration generated and used by' + ' the sysconfig module\n') + f.write('build_time_vars = ') + _print_config_dict(vars, stream=f) + + # Create file used for sys.path fixup -- see Modules/getpath.c + with open('pybuilddir.txt', 'w', encoding='utf8') as f: + f.write(pybuilddir) + + +def _print_dict(title, data): + for index, (key, value) in enumerate(sorted(data.items())): + if index == 0: + print(f'{title}: ') + print(f'\t{key} = "{value}"') + + +def _main(): + """Display all information sysconfig detains.""" + if '--generate-posix-vars' in sys.argv: + _generate_posix_vars() + return + print(f'Platform: "{get_platform()}"') + print(f'Python version: "{get_python_version()}"') + print(f'Current installation scheme: "{get_default_scheme()}"') + print() + _print_dict('Paths', get_paths()) + print() + _print_dict('Variables', get_config_vars()) + + +if __name__ == '__main__': + try: + _main() + except BrokenPipeError: + pass diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index d7e2c6a1dec..1225db997e3 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -979,6 +979,7 @@ def test_cancel_later_without_dump_traceback_later(self): self.assertEqual(output, []) self.assertEqual(exitcode, 0) + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AttributeError: module 'msvcrt' has no attribute 'GetErrorMode'") @threading_helper.requires_working_threading() @unittest.skipUnless(support.Py_GIL_DISABLED, "only meaningful if the GIL is disabled") def test_free_threaded_dump_traceback(self): diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 4ff187674ef..f5b8d42dba3 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -7,6 +7,7 @@ import unittest import test.support from test import support +from test.support.script_helper import assert_python_ok from test.support import os_helper from test.support import socket_helper from test.support import captured_stderr @@ -330,14 +331,14 @@ def test_getsitepackages(self): self.assertEqual(len(dirs), 2) wanted = os.path.join('xoxo', sys.platlibdir, # XXX: RUSTPYTHON - 'rustpython%d.%d' % sys.version_info[:2], + f'rustpython{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[0], wanted) else: self.assertEqual(len(dirs), 1) wanted = os.path.join('xoxo', 'lib', # XXX: RUSTPYTHON - 'rustpython%d.%d' % sys.version_info[:2], + f'rustpython{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[-1], wanted) else: @@ -358,9 +359,7 @@ def test_no_home_directory(self): with EnvironmentVarGuard() as environ, \ mock.patch('os.path.expanduser', lambda path: path): - - del environ['PYTHONUSERBASE'] - del environ['APPDATA'] + environ.unset('PYTHONUSERBASE', 'APPDATA') user_base = site.getuserbase() self.assertTrue(user_base.startswith('~' + os.sep), @@ -382,6 +381,19 @@ def test_no_home_directory(self): mock_addsitedir.assert_not_called() self.assertFalse(known_paths) + def test_gethistoryfile(self): + filename = 'file' + rc, out, err = assert_python_ok('-c', + f'import site; assert site.gethistoryfile() == "{filename}"', + PYTHON_HISTORY=filename) + self.assertEqual(rc, 0) + + # Check that PYTHON_HISTORY is ignored in isolated mode. + rc, out, err = assert_python_ok('-I', '-c', + f'import site; assert site.gethistoryfile() != "{filename}"', + PYTHON_HISTORY=filename) + self.assertEqual(rc, 0) + def test_trace(self): message = "bla-bla-bla" for verbose, out in (True, message + "\n"), (False, ""): @@ -509,6 +521,44 @@ def test_sitecustomize_executed(self): else: self.fail("sitecustomize not imported automatically") + @support.requires_subprocess() + def test_customization_modules_on_startup(self): + mod_names = [ + 'sitecustomize' + ] + + if site.ENABLE_USER_SITE: + mod_names.append('usercustomize') + + temp_dir = tempfile.mkdtemp() + self.addCleanup(os_helper.rmtree, temp_dir) + + with EnvironmentVarGuard() as environ: + environ['PYTHONPATH'] = temp_dir + + for module_name in mod_names: + os_helper.rmtree(temp_dir) + os.mkdir(temp_dir) + + customize_path = os.path.join(temp_dir, f'{module_name}.py') + eyecatcher = f'EXECUTED_{module_name}' + + with open(customize_path, 'w') as f: + f.write(f'print("{eyecatcher}")') + + output = subprocess.check_output([sys.executable, '-c', '""']) + self.assertIn(eyecatcher, output.decode('utf-8')) + + # -S blocks any site-packages + output = subprocess.check_output([sys.executable, '-S', '-c', '""']) + self.assertNotIn(eyecatcher, output.decode('utf-8')) + + # -s blocks user site-packages + if 'usercustomize' == module_name: + output = subprocess.check_output([sys.executable, '-s', '-c', '""']) + self.assertNotIn(eyecatcher, output.decode('utf-8')) + + @unittest.skipUnless(hasattr(urllib.request, "HTTPSHandler"), 'need SSL support to download license') @test.support.requires_resource('network') diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 6db1442980a..da242c7df71 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -1,24 +1,35 @@ +import platform +import re import unittest import sys import os import subprocess import shutil +import json +import textwrap from copy import copy from test.support import ( - captured_stdout, PythonSymlink, requires_subprocess, is_wasi + captured_stdout, + is_apple_mobile, + is_wasi, + PythonSymlink, + requires_subprocess, ) from test.support.import_helper import import_module from test.support.os_helper import (TESTFN, unlink, skip_unless_symlink, change_cwd) -from test.support.warnings_helper import check_warnings +from test.support.venv import VirtualEnvironment import sysconfig from sysconfig import (get_paths, get_platform, get_config_vars, get_path, get_path_names, _INSTALL_SCHEMES, get_default_scheme, get_scheme_names, get_config_var, - _expand_vars, _get_preferred_schemes, _main) + _expand_vars, _get_preferred_schemes) +from sysconfig.__main__ import _main, _parse_makefile +import _imp import _osx_support +import _sysconfig HAS_USER_BASE = sysconfig._HAS_USER_BASE @@ -41,6 +52,7 @@ def setUp(self): self.name = os.name self.platform = sys.platform self.version = sys.version + self._framework = sys._framework self.sep = os.sep self.join = os.path.join self.isabs = os.path.isabs @@ -64,6 +76,7 @@ def tearDown(self): os.name = self.name sys.platform = self.platform sys.version = self.version + sys._framework = self._framework os.sep = self.sep os.path.join = self.join os.path.isabs = self.isabs @@ -91,6 +104,12 @@ def _cleanup_testfn(self): elif os.path.isdir(path): shutil.rmtree(path) + def venv(self, **venv_create_args): + return VirtualEnvironment.from_tmpdir( + prefix=f'{self.id()}-venv-', + **venv_create_args, + ) + def test_get_path_names(self): self.assertEqual(get_path_names(), sysconfig._SCHEME_KEYS) @@ -137,35 +156,37 @@ def test_get_preferred_schemes(self): # Mac, framework build. os.name = 'posix' sys.platform = 'darwin' - sys._framework = True + sys._framework = "MyPython" self.assertIsInstance(schemes, dict) self.assertEqual(set(schemes), expected_schemes) - # NOTE: RUSTPYTHON this is hardcoded to 'python', we're set up for failure. - @unittest.expectedFailure def test_posix_venv_scheme(self): # The following directories were hardcoded in the venv module # before bpo-45413, here we assert the posix_venv scheme does not regress binpath = 'bin' incpath = 'include' libpath = os.path.join('lib', - 'python%d.%d' % sys.version_info[:2], + # XXX: RUSTPYTHON + f'rustpython{sysconfig._get_python_version_abi()}', 'site-packages') - # Resolve the paths in prefix - binpath = os.path.join(sys.prefix, binpath) - incpath = os.path.join(sys.prefix, incpath) - libpath = os.path.join(sys.prefix, libpath) + # Resolve the paths in an imaginary venv/ directory + binpath = os.path.join('venv', binpath) + incpath = os.path.join('venv', incpath) + libpath = os.path.join('venv', libpath) + + # Mimic the venv module, set all bases to the venv directory + bases = ('base', 'platbase', 'installed_base', 'installed_platbase') + vars = {base: 'venv' for base in bases} - self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='posix_venv')) - self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='posix_venv')) + self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='posix_venv', vars=vars)) + self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='posix_venv', vars=vars)) # The include directory on POSIX isn't exactly the same as before, # but it is "within" - sysconfig_includedir = sysconfig.get_path('include', scheme='posix_venv') + sysconfig_includedir = sysconfig.get_path('include', scheme='posix_venv', vars=vars) self.assertTrue(sysconfig_includedir.startswith(incpath + os.sep)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_nt_venv_scheme(self): # The following directories were hardcoded in the venv module # before bpo-45413, here we assert the posix_venv scheme does not regress @@ -173,14 +194,19 @@ def test_nt_venv_scheme(self): incpath = 'Include' libpath = os.path.join('Lib', 'site-packages') - # Resolve the paths in prefix - binpath = os.path.join(sys.prefix, binpath) - incpath = os.path.join(sys.prefix, incpath) - libpath = os.path.join(sys.prefix, libpath) + # Resolve the paths in an imaginary venv\ directory + venv = 'venv' + binpath = os.path.join(venv, binpath) + incpath = os.path.join(venv, incpath) + libpath = os.path.join(venv, libpath) - self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='nt_venv')) - self.assertEqual(incpath, sysconfig.get_path('include', scheme='nt_venv')) - self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='nt_venv')) + # Mimic the venv module, set all bases to the venv directory + bases = ('base', 'platbase', 'installed_base', 'installed_platbase') + vars = {base: 'venv' for base in bases} + + self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='nt_venv', vars=vars)) + self.assertEqual(incpath, sysconfig.get_path('include', scheme='nt_venv', vars=vars)) + self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='nt_venv', vars=vars)) def test_venv_scheme(self): if sys.platform == 'win32': @@ -216,6 +242,11 @@ def test_get_config_vars(self): self.assertTrue(cvars) def test_get_platform(self): + # Check the actual platform returns something reasonable. + actual_platform = get_platform() + self.assertIsInstance(actual_platform, str) + self.assertTrue(actual_platform) + # windows XP, 32bits os.name = 'nt' sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) ' @@ -331,11 +362,28 @@ def test_get_platform(self): self.assertEqual(get_platform(), 'linux-i686') + # Android + os.name = 'posix' + sys.platform = 'android' + get_config_vars()['ANDROID_API_LEVEL'] = 9 + for machine, abi in { + 'x86_64': 'x86_64', + 'i686': 'x86', + 'aarch64': 'arm64_v8a', + 'armv7l': 'armeabi_v7a', + }.items(): + with self.subTest(machine): + self._set_uname(('Linux', 'localhost', '3.18.91+', + '#1 Tue Jan 9 20:35:43 UTC 2018', machine)) + self.assertEqual(get_platform(), f'android-9-{abi}') + # XXX more platforms to tests here # TODO: RUSTPYTHON @unittest.expectedFailure @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") + @unittest.skipIf(is_apple_mobile, + f"{sys.platform} doesn't distribute header files in the runtime environment") def test_get_config_h_filename(self): config_h = sysconfig.get_config_h_filename() self.assertTrue(os.path.isfile(config_h), config_h) @@ -381,8 +429,8 @@ def test_user_similar(self): if name == 'platlib': # Replace "/lib64/python3.11/site-packages" suffix # with "/lib/python3.11/site-packages". - py_version_short = sysconfig.get_python_version() - suffix = f'python{py_version_short}/site-packages' + py_version_abi = sysconfig._get_python_version_abi() + suffix = f'python{py_version_abi}/site-packages' expected = expected.replace(f'/{sys.platlibdir}/{suffix}', f'/lib/{suffix}') self.assertEqual(user_path, expected) @@ -402,6 +450,32 @@ def test_ldshared_value(self): self.assertIn(ldflags, ldshared) + @unittest.skipIf(not _imp.extension_suffixes(), "stub loader has no suffixes") + def test_soabi(self): + soabi = sysconfig.get_config_var('SOABI') + self.assertIn(soabi, _imp.extension_suffixes()[0]) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_library(self): + library = sysconfig.get_config_var('LIBRARY') + ldlibrary = sysconfig.get_config_var('LDLIBRARY') + major, minor = sys.version_info[:2] + if sys.platform == 'win32': + self.assertTrue(library.startswith(f'python{major}{minor}')) + self.assertTrue(library.endswith('.dll')) + self.assertEqual(library, ldlibrary) + elif is_apple_mobile: + framework = sysconfig.get_config_var('PYTHONFRAMEWORK') + self.assertEqual(ldlibrary, f"{framework}.framework/{framework}") + else: + self.assertTrue(library.startswith(f'libpython{major}.{minor}')) + self.assertTrue(library.endswith('.a')) + if sys.platform == 'darwin' and sys._framework: + self.skipTest('gh-110824: skip LDLIBRARY test for framework build') + else: + self.assertTrue(ldlibrary.startswith(f'libpython{major}.{minor}')) + @unittest.skipUnless(sys.platform == "darwin", "test only relevant on MacOSX") @requires_subprocess() def test_platform_in_subprocess(self): @@ -448,6 +522,8 @@ def test_platform_in_subprocess(self): @unittest.expectedFailureIf(sys.platform != "win32", "TODO: RUSTPYTHON") @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") + @unittest.skipIf(is_apple_mobile, + f"{sys.platform} doesn't include config folder at runtime") def test_srcdir(self): # See Issues #15322, #15364. srcdir = sysconfig.get_config_var('srcdir') @@ -460,11 +536,15 @@ def test_srcdir(self): # should be a full source checkout. Python_h = os.path.join(srcdir, 'Include', 'Python.h') self.assertTrue(os.path.exists(Python_h), Python_h) - # <srcdir>/PC/pyconfig.h always exists even if unused on POSIX. - pyconfig_h = os.path.join(srcdir, 'PC', 'pyconfig.h') + # <srcdir>/PC/pyconfig.h.in always exists even if unused + pyconfig_h = os.path.join(srcdir, 'PC', 'pyconfig.h.in') self.assertTrue(os.path.exists(pyconfig_h), pyconfig_h) pyconfig_h_in = os.path.join(srcdir, 'pyconfig.h.in') self.assertTrue(os.path.exists(pyconfig_h_in), pyconfig_h_in) + if os.name == 'nt': + # <executable dir>/pyconfig.h exists on Windows in a build tree + pyconfig_h = os.path.join(sys.executable, '..', 'pyconfig.h') + self.assertTrue(os.path.exists(pyconfig_h), pyconfig_h) elif os.name == 'posix': makefile_dir = os.path.dirname(sysconfig.get_makefile_filename()) # Issue #19340: srcdir has been realpath'ed already @@ -479,23 +559,16 @@ def test_srcdir_independent_of_cwd(self): srcdir2 = sysconfig.get_config_var('srcdir') self.assertEqual(srcdir, srcdir2) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(sysconfig.get_config_var('EXT_SUFFIX') is None, 'EXT_SUFFIX required for this test') + @unittest.skipIf(not _imp.extension_suffixes(), "stub loader has no suffixes") def test_EXT_SUFFIX_in_vars(self): - import _imp - if not _imp.extension_suffixes(): - self.skipTest("stub loader has no suffixes") vars = sysconfig.get_config_vars() self.assertEqual(vars['EXT_SUFFIX'], _imp.extension_suffixes()[0]) - @unittest.skipUnless(sys.platform == 'linux' and - hasattr(sys.implementation, '_multiarch'), - 'multiarch-specific test') - def test_triplet_in_ext_suffix(self): + @unittest.skipUnless(sys.platform == 'linux', 'Linux-specific test') + def test_linux_ext_suffix(self): ctypes = import_module('ctypes') - import platform, re machine = platform.machine() suffix = sysconfig.get_config_var('EXT_SUFFIX') if re.match('(aarch64|arm|mips|ppc|powerpc|s390|sparc)', machine): @@ -510,11 +583,97 @@ def test_triplet_in_ext_suffix(self): # TODO: RUSTPYTHON @unittest.expectedFailure + @unittest.skipUnless(sys.platform == 'android', 'Android-specific test') + def test_android_ext_suffix(self): + machine = platform.machine() + suffix = sysconfig.get_config_var('EXT_SUFFIX') + expected_triplet = { + "x86_64": "x86_64-linux-android", + "i686": "i686-linux-android", + "aarch64": "aarch64-linux-android", + "armv7l": "arm-linux-androideabi", + }[machine] + self.assertTrue(suffix.endswith(f"-{expected_triplet}.so"), + f"{machine=}, {suffix=}") + + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(sys.platform == 'darwin', 'OS X-specific test') def test_osx_ext_suffix(self): suffix = sysconfig.get_config_var('EXT_SUFFIX') self.assertTrue(suffix.endswith('-darwin.so'), suffix) + # TODO: RUSTPYTHON + @unittest.expectedFailure + @requires_subprocess() + def test_config_vars_depend_on_site_initialization(self): + script = textwrap.dedent(""" + import sysconfig + + config_vars = sysconfig.get_config_vars() + + import json + print(json.dumps(config_vars, indent=2)) + """) + + with self.venv() as venv: + site_config_vars = json.loads(venv.run('-c', script).stdout) + no_site_config_vars = json.loads(venv.run('-S', '-c', script).stdout) + + self.assertNotEqual(site_config_vars, no_site_config_vars) + # With the site initialization, the virtual environment should be enabled. + self.assertEqual(site_config_vars['base'], venv.prefix) + self.assertEqual(site_config_vars['platbase'], venv.prefix) + #self.assertEqual(site_config_vars['prefix'], venv.prefix) # # FIXME: prefix gets overwriten by _init_posix + # Without the site initialization, the virtual environment should be disabled. + self.assertEqual(no_site_config_vars['base'], site_config_vars['installed_base']) + self.assertEqual(no_site_config_vars['platbase'], site_config_vars['installed_platbase']) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + @requires_subprocess() + def test_config_vars_recalculation_after_site_initialization(self): + script = textwrap.dedent(""" + import sysconfig + + before = sysconfig.get_config_vars() + + import site + site.main() + + after = sysconfig.get_config_vars() + + import json + print(json.dumps({'before': before, 'after': after}, indent=2)) + """) + + with self.venv() as venv: + config_vars = json.loads(venv.run('-S', '-c', script).stdout) + + self.assertNotEqual(config_vars['before'], config_vars['after']) + self.assertEqual(config_vars['after']['base'], venv.prefix) + #self.assertEqual(config_vars['after']['prefix'], venv.prefix) # FIXME: prefix gets overwriten by _init_posix + #self.assertEqual(config_vars['after']['exec_prefix'], venv.prefix) # FIXME: exec_prefix gets overwriten by _init_posix + + # TODO: RUSTPYTHON + @unittest.expectedFailure + @requires_subprocess() + def test_paths_depend_on_site_initialization(self): + script = textwrap.dedent(""" + import sysconfig + + paths = sysconfig.get_paths() + + import json + print(json.dumps(paths, indent=2)) + """) + + with self.venv() as venv: + site_paths = json.loads(venv.run('-c', script).stdout) + no_site_paths = json.loads(venv.run('-S', '-c', script).stdout) + + self.assertNotEqual(site_paths, no_site_paths) + + class MakefileTests(unittest.TestCase): # TODO: RUSTPYTHON @@ -522,6 +681,8 @@ class MakefileTests(unittest.TestCase): @unittest.skipIf(sys.platform.startswith('win'), 'Test is not Windows compatible') @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") + @unittest.skipIf(is_apple_mobile, + f"{sys.platform} doesn't include config folder at runtime") def test_get_makefile_filename(self): makefile = sysconfig.get_makefile_filename() self.assertTrue(os.path.isfile(makefile), makefile) @@ -536,7 +697,7 @@ def test_parse_makefile(self): print("var5=dollar$$5", file=makefile) print("var6=${var3}/lib/python3.5/config-$(VAR2)$(var5)" "-x86_64-linux-gnu", file=makefile) - vars = sysconfig._parse_makefile(TESTFN) + vars = _parse_makefile(TESTFN) self.assertEqual(vars, { 'var1': 'ab42', 'VAR2': 'b42', @@ -547,5 +708,26 @@ def test_parse_makefile(self): }) +class DeprecationTests(unittest.TestCase): + def deprecated(self, removal_version, deprecation_msg=None, error=Exception, error_msg=None): + if sys.version_info >= removal_version: + return self.assertRaises(error, msg=error_msg) + else: + return self.assertWarns(DeprecationWarning, msg=deprecation_msg) + + def test_is_python_build_check_home(self): + with self.deprecated( + removal_version=(3, 15), + deprecation_msg=( + 'The check_home argument of sysconfig.is_python_build is ' + 'deprecated and its value is ignored. ' + 'It will be removed in Python 3.15.' + ), + error=TypeError, + error_msg="is_python_build() takes 0 positional arguments but 1 were given", + ): + sysconfig.is_python_build('foo') + + if __name__ == "__main__": unittest.main() diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 2c4442900bd..9fae516fe04 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -18,6 +18,7 @@ mod stat; mod string; #[cfg(feature = "compiler")] mod symtable; +mod sysconfig; mod sysconfigdata; #[cfg(feature = "threading")] pub mod thread; @@ -97,6 +98,7 @@ pub fn get_module_inits() -> StdlibMap { "_signal" => signal::make_module, "_sre" => sre::make_module, "_stat" => stat::make_module, + "_sysconfig" => sysconfig::make_module, "_string" => string::make_module, "time" => time::make_module, "_typing" => typing::make_module, diff --git a/vm/src/stdlib/sysconfig.rs b/vm/src/stdlib/sysconfig.rs new file mode 100644 index 00000000000..2e0a8a51c7a --- /dev/null +++ b/vm/src/stdlib/sysconfig.rs @@ -0,0 +1,14 @@ +pub(crate) use sysconfig::make_module; + +#[pymodule(name = "_sysconfig")] +pub(crate) mod sysconfig { + use crate::{VirtualMachine, builtins::PyDictRef, convert::ToPyObject}; + + #[pyfunction] + fn config_vars(vm: &VirtualMachine) -> PyDictRef { + let vars = vm.ctx.new_dict(); + vars.set_item("Py_GIL_DISABLED", true.to_pyobject(vm), vm) + .unwrap(); + vars + } +} From 63de4387e7aecaf4195fc5b9423676cb2c9bc96e Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 11 Sep 2025 07:05:04 +0200 Subject: [PATCH 256/819] Fix broken CI on windows (#6143) * Fix `test_dtrace.py` * Fix `test_genericpath.py` * fix `test_ntpath.py` * Fix `test_py_compile.py` * Fix `test_shutil.py` * fix `test_stat.py` * Fix `test_tarfile.py` * Mark failing tests --- Lib/test/test_dtrace.py | 32 ++++++++++++++++++++++++++++++++ Lib/test/test_genericpath.py | 8 ++++++++ Lib/test/test_ntpath.py | 8 ++++++++ Lib/test/test_py_compile.py | 1 - Lib/test/test_shutil.py | 7 +------ Lib/test/test_stat.py | 11 ----------- Lib/test/test_tarfile.py | 3 --- 7 files changed, 49 insertions(+), 21 deletions(-) diff --git a/Lib/test/test_dtrace.py b/Lib/test/test_dtrace.py index e1adf8e9748..a63978fd1bd 100644 --- a/Lib/test/test_dtrace.py +++ b/Lib/test/test_dtrace.py @@ -159,11 +159,43 @@ class DTraceNormalTests(TraceTests, unittest.TestCase): backend = DTraceBackend() optimize_python = 0 + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_function_entry_return(self): + return super().test_function_entry_return() + + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_verify_call_opcodes(self): + return super().test_verify_call_opcodes() + + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_gc(self): + return super().test_gc() + + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_line(self): + return super().test_line() + class DTraceOptimizedTests(TraceTests, unittest.TestCase): backend = DTraceBackend() optimize_python = 2 + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_function_entry_return(self): + return super().test_function_entry_return() + + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_verify_call_opcodes(self): + return super().test_verify_call_opcodes() + + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_gc(self): + return super().test_gc() + + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_line(self): + return super().test_line() + class SystemTapNormalTests(TraceTests, unittest.TestCase): backend = SystemTapBackend() diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py index 2e28d3cfb7e..3475c026bb8 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py @@ -352,6 +352,14 @@ def test_invalid_paths(self): with self.assertRaisesRegex(ValueError, 'embedded null'): func(b'/tmp\x00abcds') + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_samestat_on_symlink(self): + return super().test_samestat_on_symlink() + + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_samefile_on_symlink(self): + return super().test_samefile_on_symlink() + # Following TestCase is not supposed to be run from test_genericpath. # It is inherited by other test modules (ntpath, posixpath). diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index f2cafd97b35..96c20fe8934 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -1527,6 +1527,14 @@ def test_expandvars(self): def test_expandvars_nonascii(self): return super().test_expandvars_nonascii() + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_samefile_on_symlink(self): + return super().test_samefile_on_symlink() + + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + def test_samestat_on_symlink(self): + return super().test_samestat_on_symlink() + class PathLikeTests(NtpathTestCase): diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py index 750afc1de71..6d10099c614 100644 --- a/Lib/test/test_py_compile.py +++ b/Lib/test/test_py_compile.py @@ -78,7 +78,6 @@ def test_absolute_path(self): self.assertTrue(os.path.exists(self.pyc_path)) self.assertFalse(os.path.exists(self.cache_path)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_do_not_overwrite_symlinks(self): # In the face of a cfile argument being a symlink, bail out. # Issue #17222 diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index b64ccb37a52..8a426e338a7 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -196,7 +196,6 @@ def test_rmtree_works_on_bytes(self): self.assertIsInstance(victim, bytes) shutil.rmtree(victim) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_rmtree_fails_on_symlink_onerror(self): tmp = self.mkdtemp() @@ -216,7 +215,6 @@ def onerror(*args): self.assertEqual(errors[0][1], link) self.assertIsInstance(errors[0][2][1], OSError) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_rmtree_fails_on_symlink_onexc(self): tmp = self.mkdtemp() @@ -1087,7 +1085,6 @@ def test_copymode_follow_symlinks(self): shutil.copymode(src_link, dst_link) self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @unittest.skipUnless(hasattr(os, 'lchmod') or os.name == 'nt', 'requires os.lchmod') @os_helper.skip_unless_symlink def test_copymode_symlink_to_symlink(self): @@ -1317,7 +1314,6 @@ def test_copy(self): self.assertTrue(os.path.exists(file2)) self.assertEqual(os.stat(file1).st_mode, os.stat(file2).st_mode) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_copy_symlinks(self): tmp_dir = self.mkdtemp() @@ -1360,7 +1356,6 @@ def test_copy2(self): self.assertEqual(getattr(file1_stat, 'st_flags'), getattr(file2_stat, 'st_flags')) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_copy2_symlinks(self): tmp_dir = self.mkdtemp() @@ -1445,7 +1440,6 @@ def _test_copy_dir(self, copy_func): ### shutil.copyfile - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_copyfile_symlinks(self): tmp_dir = self.mkdtemp() @@ -1483,6 +1477,7 @@ def test_dont_copy_file_onto_link_to_itself(self): finally: shutil.rmtree(TESTFN, ignore_errors=True) + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') @os_helper.skip_unless_symlink def test_dont_copy_file_onto_symlink_to_itself(self): # bug 851123. diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index ec4cca45280..49013a4bcd8 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -325,21 +325,10 @@ def test_macosx_attribute_values(self): class TestFilemodeCStat(TestFilemode, unittest.TestCase): statmod = c_stat - # TODO: RUSTPYTHON - if sys.platform == "win32": - @unittest.expectedFailure - def test_link(self): - super().test_link() class TestFilemodePyStat(TestFilemode, unittest.TestCase): statmod = py_stat - # TODO: RUSTPYTHON - if sys.platform == "win32": - @unittest.expectedFailure - def test_link(self): - super().test_link() - if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 0fed89c7735..61929e537ff 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -1276,7 +1276,6 @@ def test_gettarinfo_pathlike_name(self): self.assertEqual(tarinfo.name, tarinfo2.name) self.assertEqual(tarinfo.size, 3) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @unittest.skipUnless(hasattr(os, "link"), "Missing hardlink implementation") def test_link_size(self): @@ -1301,7 +1300,6 @@ def test_link_size(self): os_helper.unlink(target) os_helper.unlink(link) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_symlink_size(self): path = os.path.join(TEMPDIR, "symlink") @@ -1849,7 +1847,6 @@ def test_add_twice(self): self.assertEqual(tarinfo.type, tarfile.REGTYPE, "add file as regular failed") - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_add_hardlink(self): tarinfo = self.tar.gettarinfo(self.bar) self.assertEqual(tarinfo.type, tarfile.LNKTYPE, From 0c3d14affc4bcdceded66d7d4df751fa162e8151 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 11 Sep 2025 07:05:59 +0200 Subject: [PATCH 257/819] Fix docs link in copilot (#6145) --- .github/copilot-instructions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e175cd51840..46fc16c5ed8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -206,7 +206,7 @@ cargo run --features ssl ## Documentation -- Check the [architecture document](architecture/architecture.md) for a high-level overview -- Read the [development guide](DEVELOPMENT.md) for detailed setup instructions +- Check the [architecture document](/architecture/architecture.md) for a high-level overview +- Read the [development guide](/DEVELOPMENT.md) for detailed setup instructions - Generate documentation with `cargo doc --no-deps --all` -- Online documentation is available at [docs.rs/rustpython](https://docs.rs/rustpython/) \ No newline at end of file +- Online documentation is available at [docs.rs/rustpython](https://docs.rs/rustpython/) From f429ac4939d2de91a84a2768159cef4db0e63f2d Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 11 Sep 2025 15:42:19 +0200 Subject: [PATCH 258/819] Use `ast.unparse` for generating patches with `lib_updater.py` (#6142) * Use `ast.unparse` for decorator generation and every ut_method * Ensure ut_method type for external patches * use textwrap * Apply patches to `test_os.py` * Apoly on `test_xml_etree.py` * Run on some test files * Update `test_str.py` * Update `test_logging.py` from 3.13.7 --- Lib/test/test_ast/test_ast.py | 329 ++++++++++------------------ Lib/test/test_csv.py | 84 +++----- Lib/test/test_logging.py | 121 ++--------- Lib/test/test_os.py | 64 +++--- Lib/test/test_str.py | 67 +++--- Lib/test/test_xml_etree.py | 390 ++++++++++++---------------------- scripts/lib_updater.py | 139 ++++++------ 7 files changed, 415 insertions(+), 779 deletions(-) diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index 10319e36fa7..09d9444d5d9 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -72,8 +72,7 @@ def test_AST_objects(self): # "ast.AST constructor takes 0 positional arguments" ast.AST(2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_AST_fields_NULL_check(self): # See: https://github.com/python/cpython/issues/126105 old_value = ast.AST._fields @@ -91,8 +90,7 @@ def cleanup(): with self.assertRaisesRegex(AttributeError, msg): ast.AST() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_AST_garbage_collection(self): class X: pass @@ -105,8 +103,7 @@ class X: support.gc_collect() self.assertIsNone(ref()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_snippets(self): for input, output, kind in ( (exec_tests, exec_results, "exec"), @@ -121,8 +118,7 @@ def test_snippets(self): with self.subTest(action="compiling", input=i, kind=kind): compile(ast_tree, "?", kind) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_ast_validation(self): # compile() is the only function that calls PyAST_Validate snippets_to_validate = exec_tests + single_tests + eval_tests @@ -130,8 +126,7 @@ def test_ast_validation(self): tree = ast.parse(snippet) compile(tree, "<string>", "exec") - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_optimization_levels__debug__(self): cases = [(-1, "__debug__"), (0, "__debug__"), (1, False), (2, False)] for optval, expected in cases: @@ -147,8 +142,7 @@ def test_optimization_levels__debug__(self): self.assertIsInstance(res.body[0].value, ast.Name) self.assertEqual(res.body[0].value.id, expected) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_optimization_levels_const_folding(self): folded = ("Expr", (1, 0, 1, 5), ("Constant", (1, 0, 1, 5), 3, None)) not_folded = ( @@ -172,8 +166,7 @@ def test_optimization_levels_const_folding(self): res = to_tuple(tree.body[0]) self.assertEqual(res, expected) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_invalid_position_information(self): invalid_linenos = [(10, 1), (-10, -11), (10, -11), (-5, -2), (-5, 1)] @@ -198,8 +191,7 @@ def test_invalid_position_information(self): with self.assertRaises(ValueError): compile(tree, "<string>", "exec") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_compilation_of_ast_nodes_with_default_end_position_values(self): tree = ast.Module( body=[ @@ -220,8 +212,7 @@ def test_compilation_of_ast_nodes_with_default_end_position_values(self): # Check that compilation doesn't crash. Note: this may crash explicitly only on debug mode. compile(tree, "<string>", "exec") - # TODO: RUSTPYTHON; TypeError: required field "end_lineno" missing from alias - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: required field "end_lineno" missing from alias def test_negative_locations_for_compile(self): # See https://github.com/python/cpython/issues/130775 alias = ast.alias(name='traceback', lineno=0, col_offset=0) @@ -328,8 +319,7 @@ def test_field_attr_existence_deprecated(self): if isinstance(x, ast.AST): self.assertIs(type(x._fields), tuple) - # TODO: RUSTPYTHON; type object 'Module' has no attribute '__annotations__' - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; type object 'Module' has no attribute '__annotations__' def test_field_attr_existence(self): for name, item in ast.__dict__.items(): # These emit DeprecationWarnings @@ -356,8 +346,7 @@ def _construct_ast_class(self, cls): kwargs[name] = self._construct_ast_class(typ) return cls(**kwargs) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_arguments(self): x = ast.arguments() self.assertEqual( @@ -406,8 +395,7 @@ def test_field_attr_writable(self): x._fields = 666 self.assertEqual(x._fields, 666) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_classattrs_deprecated(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore", "", DeprecationWarning) @@ -499,8 +487,7 @@ def test_classattrs_deprecated(self): ], ) - # TODO: RUSTPYTHON; DeprecationWarning not triggered - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered def test_classattrs(self): with self.assertWarns(DeprecationWarning): x = ast.Constant() @@ -706,8 +693,7 @@ class S(str): with assertNumDeprecated(): self.assertNotIsInstance(Constant(S("42")), Num) - # TODO: RUSTPYTHON; will be removed in Python 3.14 - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; will be removed in Python 3.14 def test_constant_subclasses_deprecated(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore", "", DeprecationWarning) @@ -774,8 +760,7 @@ def test_module(self): x = ast.Module(body, []) self.assertEqual(x.body, body) - # TODO: RUSTPYTHON; DeprecationWarning not triggered - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered def test_nodeclasses(self): # Zero arguments constructor explicitly allowed (but deprecated) with self.assertWarns(DeprecationWarning): @@ -827,8 +812,7 @@ def test_no_fields(self): x = ast.Sub() self.assertEqual(x._fields, ()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_sum(self): pos = dict(lineno=2, col_offset=3) m = ast.Module([ast.Expr(ast.expr(**pos), **pos)], []) @@ -836,8 +820,7 @@ def test_invalid_sum(self): compile(m, "<test>", "exec") self.assertIn("but got <ast.expr", str(cm.exception)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_identifier(self): m = ast.Module([ast.Expr(ast.Name(42, ast.Load()))], []) ast.fix_missing_locations(m) @@ -852,8 +835,7 @@ def test_invalid_constant(self): with self.assertRaisesRegex(TypeError, "invalid type in Constant: type"): compile(e, "<test>", "eval") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_empty_yield_from(self): # Issue 16546: yield from value is not optional. empty_yield_from = ast.parse("def f():\n yield from g()") @@ -910,8 +892,7 @@ def test_issue39579_dotted_name_end_col_offset(self): attr_b = tree.body[0].decorator_list[0].value self.assertEqual(attr_b.end_col_offset, 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_ast_asdl_signature(self): self.assertEqual( ast.withitem.__doc__, "withitem(expr context_expr, expr? optional_vars)" @@ -926,8 +907,7 @@ def test_ast_asdl_signature(self): expressions[0] = f"expr = {ast.expr.__subclasses__()[0].__doc__}" self.assertCountEqual(ast.expr.__doc__.split("\n"), expressions) - # TODO: RUSTPYTHON; SyntaxError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised def test_positional_only_feature_version(self): ast.parse("def foo(x, /): ...", feature_version=(3, 8)) ast.parse("def bar(x=1, /): ...", feature_version=(3, 8)) @@ -943,8 +923,7 @@ def test_positional_only_feature_version(self): with self.assertRaises(SyntaxError): ast.parse("lambda x=1, /: ...", feature_version=(3, 7)) - # TODO: RUSTPYTHON; SyntaxError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised def test_assignment_expression_feature_version(self): ast.parse("(x := 0)", feature_version=(3, 8)) with self.assertRaises(SyntaxError): @@ -954,8 +933,7 @@ def test_conditional_context_managers_parse_with_low_feature_version(self): # regression test for gh-115881 ast.parse("with (x() if y else z()): ...", feature_version=(3, 8)) - # TODO: RUSTPYTHON; SyntaxError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised def test_exception_groups_feature_version(self): code = dedent(""" try: ... @@ -965,8 +943,7 @@ def test_exception_groups_feature_version(self): with self.assertRaises(SyntaxError): ast.parse(code, feature_version=(3, 10)) - # TODO: RUSTPYTHON; SyntaxError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised def test_type_params_feature_version(self): samples = [ "type X = int", @@ -979,8 +956,7 @@ def test_type_params_feature_version(self): with self.assertRaises(SyntaxError): ast.parse(sample, feature_version=(3, 11)) - # TODO: RUSTPYTHON; SyntaxError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised def test_type_params_default_feature_version(self): samples = [ "type X[*Ts=int] = int", @@ -999,8 +975,7 @@ def test_invalid_major_feature_version(self): with self.assertRaises(ValueError): ast.parse("pass", feature_version=(4, 0)) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_constant_as_name(self): for constant in "True", "False", "None": expr = ast.Expression(ast.Name(constant, ast.Load())) @@ -1010,8 +985,7 @@ def test_constant_as_name(self): ): compile(expr, "<test>", "eval") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_constant_as_unicode_name(self): constants = [ ("True", b"Tru\xe1\xb5\x89"), @@ -1023,8 +997,7 @@ def test_constant_as_unicode_name(self): f"identifier field can't represent '{constant[0]}' constant"): ast.parse(constant[1], mode="eval") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_precedence_enum(self): class _Precedence(enum.IntEnum): """Precedence table that originated from python grammar.""" @@ -1101,8 +1074,7 @@ def assert_none_check(self, node: type[ast.AST], attr: str, source: str) -> None with self.assertRaisesRegex(ValueError, f"^{e}$"): compile(tree, "<test>", "exec") - # TODO: RUSTPYTHON; TypeError: expected some sort of expr, but got None - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: expected some sort of expr, but got None def test_none_checks(self) -> None: tests = [ (ast.alias, "name", "import spam as SPAM"), @@ -1120,8 +1092,7 @@ def test_none_checks(self) -> None: class CopyTests(unittest.TestCase): """Test copying and pickling AST nodes.""" - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickling(self): import pickle @@ -1202,8 +1173,7 @@ def test_copy_with_parents(self): class ASTHelpers_Test(unittest.TestCase): maxDiff = None - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse(self): a = ast.parse("foo(1 + 1)") b = compile("foo(1 + 1)", "<unknown>", "exec", ast.PyCF_ONLY_AST) @@ -1217,8 +1187,7 @@ def test_parse_in_error(self): ast.literal_eval(r"'\U'") self.assertIsNotNone(e.exception.__context__) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump(self): node = ast.parse('spam(eggs, "and cheese")') self.assertEqual( @@ -1242,8 +1211,7 @@ def test_dump(self): "lineno=1, col_offset=0, end_lineno=1, end_col_offset=24)])", ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_indent(self): node = ast.parse('spam(eggs, "and cheese")') self.assertEqual( @@ -1308,8 +1276,7 @@ def test_dump_indent(self): end_col_offset=24)])""", ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_incomplete(self): node = ast.Raise(lineno=3, col_offset=4) self.assertEqual(ast.dump(node), "Raise()") @@ -1377,8 +1344,7 @@ def test_dump_incomplete(self): "ClassDef('T', [], [keyword('a', Constant(None))], [], [Name('dataclass', Load())])", ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_show_empty(self): def check_node(node, empty, full, **kwargs): with self.subTest(show_empty=False): @@ -1469,8 +1435,7 @@ def check_text(code, empty, full, **kwargs): full="Module(body=[Import(names=[alias(name='_ast', asname='ast')]), ImportFrom(module='module', names=[alias(name='sub')], level=0)], type_ignores=[])", ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_copy_location(self): src = ast.parse("1 + 1", mode="eval") src.body.right = ast.copy_location(ast.Constant(2), src.body.right) @@ -1491,8 +1456,7 @@ def test_copy_location(self): self.assertEqual(new.lineno, 1) self.assertEqual(new.col_offset, 1) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_fix_missing_locations(self): src = ast.parse('write("spam")') src.body.append( @@ -1514,8 +1478,7 @@ def test_fix_missing_locations(self): "end_col_offset=0), lineno=1, col_offset=0, end_lineno=1, end_col_offset=0)])", ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_increment_lineno(self): src = ast.parse("1 + 1", mode="eval") self.assertEqual(ast.increment_lineno(src, n=3), src) @@ -1542,8 +1505,7 @@ def test_increment_lineno(self): self.assertEqual(ast.increment_lineno(src).lineno, 2) self.assertIsNone(ast.increment_lineno(src).end_lineno) - # TODO: RUSTPYTHON; IndexError: index out of range - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; IndexError: index out of range def test_increment_lineno_on_module(self): src = ast.parse( dedent("""\ @@ -1565,8 +1527,7 @@ def test_iter_fields(self): self.assertEqual(d.pop("func").id, "foo") self.assertEqual(d, {"keywords": [], "args": []}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_iter_child_nodes(self): node = ast.parse("spam(23, 42, eggs='leek')", mode="eval") self.assertEqual(len(list(ast.iter_child_nodes(node.body))), 4) @@ -1681,8 +1642,7 @@ def test_literal_eval(self): self.assertRaises(ValueError, ast.literal_eval, "+True") self.assertRaises(ValueError, ast.literal_eval, "2+3") - # TODO: RUSTPYTHON; SyntaxError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised def test_literal_eval_str_int_limit(self): with support.adjust_int_max_str_digits(4000): ast.literal_eval("3" * 4000) # no error @@ -1722,8 +1682,7 @@ def test_literal_eval_malformed_dict_nodes(self): ) self.assertRaises(ValueError, ast.literal_eval, malformed) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_literal_eval_trailing_ws(self): self.assertEqual(ast.literal_eval(" -1"), -1) self.assertEqual(ast.literal_eval("\t\t-1"), -1) @@ -1741,8 +1700,7 @@ def test_literal_eval_malformed_lineno(self): with self.assertRaisesRegex(ValueError, msg): ast.literal_eval(node) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_literal_eval_syntax_errors(self): with self.assertRaisesRegex(SyntaxError, "unexpected indent"): ast.literal_eval(r""" @@ -1750,8 +1708,7 @@ def test_literal_eval_syntax_errors(self): (\ \ """) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bad_integer(self): # issue13436: Bad error message with invalid numeric values body = [ @@ -1768,8 +1725,7 @@ def test_bad_integer(self): compile(mod, "test", "exec") self.assertIn("invalid integer value: None", str(cm.exception)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_level_as_none(self): body = [ ast.ImportFrom( @@ -1786,8 +1742,7 @@ def test_level_as_none(self): exec(code, ns) self.assertIn("sleep", ns) - # TODO: RUSTPYTHON - @unittest.skip("TODO: RUSTPYTHON; crash") + @unittest.skip('TODO: RUSTPYTHON; crash') def test_recursion_direct(self): e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) e.operand = e @@ -1795,8 +1750,7 @@ def test_recursion_direct(self): with support.infinite_recursion(): compile(ast.Expression(e), "<test>", "eval") - # TODO: RUSTPYTHON - @unittest.skip("TODO: RUSTPYTHON; crash") + @unittest.skip('TODO: RUSTPYTHON; crash') def test_recursion_indirect(self): e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) f = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) @@ -1826,8 +1780,7 @@ def stmt(self, stmt, msg=None): mod = ast.Module([stmt], []) self.mod(mod, msg) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_module(self): m = ast.Interactive([ast.Expr(ast.Name("x", ast.Store()))]) self.mod(m, "must have Load context", "single") @@ -1884,8 +1837,7 @@ def arguments( "must have Load context", ) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_funcdef(self): a = ast.arguments([], [], None, [], [], None, []) f = ast.FunctionDef("x", a, [], [], None, None, []) @@ -1906,8 +1858,7 @@ def fac(args): self._check_arguments(fac, self.stmt) - # TODO: RUSTPYTHON; called `Result::unwrap()` on an `Err` value: StackUnderflow - ''' + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: class pattern defines no positional sub-patterns (__match_args__ missing) def test_funcdef_pattern_matching(self): # gh-104799: New fields on FunctionDef should be added at the end def matcher(node): @@ -1932,10 +1883,8 @@ def foo(bar) -> pacarana: funcdef = source.body[0] self.assertIsInstance(funcdef, ast.FunctionDef) self.assertTrue(matcher(funcdef)) - ''' - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_classdef(self): def cls( bases=None, keywords=None, body=None, decorator_list=None, type_params=None @@ -1965,15 +1914,13 @@ def cls( cls(decorator_list=[ast.Name("x", ast.Store())]), "must have Load context" ) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_delete(self): self.stmt(ast.Delete([]), "empty targets on Delete") self.stmt(ast.Delete([None]), "None disallowed") self.stmt(ast.Delete([ast.Name("x", ast.Load())]), "must have Del context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_assign(self): self.stmt(ast.Assign([], ast.Constant(3)), "empty targets on Assign") self.stmt(ast.Assign([None], ast.Constant(3)), "None disallowed") @@ -1986,8 +1933,7 @@ def test_assign(self): "must have Load context", ) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_augassign(self): aug = ast.AugAssign( ast.Name("x", ast.Load()), ast.Add(), ast.Name("y", ast.Load()) @@ -1998,8 +1944,7 @@ def test_augassign(self): ) self.stmt(aug, "must have Load context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_for(self): x = ast.Name("x", ast.Store()) y = ast.Name("y", ast.Load()) @@ -2015,8 +1960,7 @@ def test_for(self): self.stmt(ast.For(x, y, [e], []), "must have Load context") self.stmt(ast.For(x, y, [p], [e]), "must have Load context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_while(self): self.stmt(ast.While(ast.Constant(3), [], []), "empty body on While") self.stmt( @@ -2030,8 +1974,7 @@ def test_while(self): "must have Load context", ) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_if(self): self.stmt(ast.If(ast.Constant(3), [], []), "empty body on If") i = ast.If(ast.Name("x", ast.Store()), [ast.Pass()], []) @@ -2043,8 +1986,7 @@ def test_if(self): ) self.stmt(i, "must have Load context") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_with(self): p = ast.Pass() self.stmt(ast.With([], [p]), "empty items on With") @@ -2055,8 +1997,7 @@ def test_with(self): i = ast.withitem(ast.Constant(3), ast.Name("x", ast.Load())) self.stmt(ast.With([i], [p]), "must have Store context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_raise(self): r = ast.Raise(None, ast.Constant(3)) self.stmt(r, "Raise with cause but no exception") @@ -2065,8 +2006,7 @@ def test_raise(self): r = ast.Raise(ast.Constant(4), ast.Name("x", ast.Store())) self.stmt(r, "must have Load context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_try(self): p = ast.Pass() t = ast.Try([], [], [], [p]) @@ -2087,8 +2027,7 @@ def test_try(self): t = ast.Try([p], e, [p], [ast.Expr(ast.Name("x", ast.Store()))]) self.stmt(t, "must have Load context") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_try_star(self): p = ast.Pass() t = ast.TryStar([], [], [], [p]) @@ -2109,8 +2048,7 @@ def test_try_star(self): t = ast.TryStar([p], e, [p], [ast.Expr(ast.Name("x", ast.Store()))]) self.stmt(t, "must have Load context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_assert(self): self.stmt( ast.Assert(ast.Name("x", ast.Store()), None), "must have Load context" @@ -2118,36 +2056,30 @@ def test_assert(self): assrt = ast.Assert(ast.Name("x", ast.Load()), ast.Name("y", ast.Store())) self.stmt(assrt, "must have Load context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_import(self): self.stmt(ast.Import([]), "empty names on Import") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_importfrom(self): imp = ast.ImportFrom(None, [ast.alias("x", None)], -42) self.stmt(imp, "Negative ImportFrom level") self.stmt(ast.ImportFrom(None, [], 0), "empty names on ImportFrom") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_global(self): self.stmt(ast.Global([]), "empty names on Global") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_nonlocal(self): self.stmt(ast.Nonlocal([]), "empty names on Nonlocal") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_expr(self): e = ast.Expr(ast.Name("x", ast.Store())) self.stmt(e, "must have Load context") - # TODO: RUSTPYTHON - @unittest.skip("TODO: RUSTPYTHON; called `Option::unwrap()` on a `None` value") + @unittest.skip('TODO: RUSTPYTHON; called `Option::unwrap()` on a `None` value') def test_boolop(self): b = ast.BoolOp(ast.And(), []) self.expr(b, "less than 2 values") @@ -2158,14 +2090,12 @@ def test_boolop(self): b = ast.BoolOp(ast.And(), [ast.Constant(4), ast.Name("x", ast.Store())]) self.expr(b, "must have Load context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_unaryop(self): u = ast.UnaryOp(ast.Not(), ast.Name("x", ast.Store())) self.expr(u, "must have Load context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_lambda(self): a = ast.arguments([], [], None, [], [], None, []) self.expr(ast.Lambda(a, ast.Name("x", ast.Store())), "must have Load context") @@ -2175,24 +2105,21 @@ def fac(args): self._check_arguments(fac, self.expr) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_ifexp(self): l = ast.Name("x", ast.Load()) s = ast.Name("y", ast.Store()) for args in (s, l, l), (l, s, l), (l, l, s): self.expr(ast.IfExp(*args), "must have Load context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_dict(self): d = ast.Dict([], [ast.Name("x", ast.Load())]) self.expr(d, "same number of keys as values") d = ast.Dict([ast.Name("x", ast.Load())], [None]) self.expr(d, "None disallowed") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_set(self): self.expr(ast.Set([None]), "None disallowed") s = ast.Set([ast.Name("x", ast.Store())]) @@ -2226,23 +2153,19 @@ def wrap(gens): self._check_comprehension(wrap) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_listcomp(self): self._simple_comp(ast.ListComp) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_setcomp(self): self._simple_comp(ast.SetComp) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_generatorexp(self): self._simple_comp(ast.GeneratorExp) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_dictcomp(self): g = ast.comprehension( ast.Name("y", ast.Store()), ast.Name("p", ast.Load()), [], 0 @@ -2259,13 +2182,11 @@ def factory(comps): self._check_comprehension(factory) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_yield(self): self.expr(ast.Yield(ast.Name("x", ast.Store())), "must have Load") self.expr(ast.YieldFrom(ast.Name("x", ast.Store())), "must have Load") - # TODO: RUSTPYTHON @unittest.skip("TODO: RUSTPYTHON; thread 'main' panicked") def test_compare(self): left = ast.Name("x", ast.Load()) @@ -2278,8 +2199,7 @@ def test_compare(self): comp = ast.Compare(left, [ast.In()], [ast.Constant("blah")]) self.expr(comp) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_call(self): func = ast.Name("x", ast.Load()) args = [ast.Name("y", ast.Load())] @@ -2325,14 +2245,12 @@ class subcomplex(complex): ], ) - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_attribute(self): attr = ast.Attribute(ast.Name("x", ast.Store()), "y", ast.Load()) self.expr(attr, "must have Load context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_subscript(self): sub = ast.Subscript(ast.Name("x", ast.Store()), ast.Constant(3), ast.Load()) self.expr(sub, "must have Load context") @@ -2348,8 +2266,7 @@ def test_subscript(self): sl = ast.Tuple([s], ast.Load()) self.expr(ast.Subscript(x, sl, ast.Load()), "must have Load context") - # TODO: RUSTPYTHON; ValueError not raised - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_starred(self): left = ast.List( [ast.Starred(ast.Name("x", ast.Load()), ast.Store())], ast.Store() @@ -2363,13 +2280,11 @@ def _sequence(self, fac): fac([ast.Name("x", ast.Store())], ast.Load()), "must have Load context" ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_list(self): self._sequence(ast.List) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tuple(self): self._sequence(ast.Tuple) @@ -2389,8 +2304,7 @@ def test_nameconstant(self): ], ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_resource("cpu") def test_stdlib_validates(self): stdlib = os.path.dirname(ast.__file__) @@ -2486,7 +2400,6 @@ def test_stdlib_validates(self): ast.MatchMapping([], [], rest="_"), ] - # TODO: RUSTPYTHON @unittest.skip("TODO: RUSTPYTHON; thread 'main' panicked") def test_match_validation_pattern(self): name_x = ast.Name("x", ast.Load()) @@ -2524,16 +2437,14 @@ def test_validation(self): self.compile_constant([1, 2, 3]) self.assertEqual(str(cm.exception), "got an invalid type in Constant: list") - # TODO: RUSTPYTHON; b'' is not b'' - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; b'' is not b'' def test_singletons(self): for const in (None, False, True, Ellipsis, b"", frozenset()): with self.subTest(const=const): value = self.compile_constant(const) self.assertIs(value, const) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_values(self): nested_tuple = (1,) nested_frozenset = frozenset({1}) @@ -2556,8 +2467,7 @@ def test_values(self): result = self.compile_constant(value) self.assertEqual(result, value) - # TODO: RUSTPYTHON; SyntaxError: cannot assign to literal - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: cannot assign to literal def test_assign_to_constant(self): tree = ast.parse("x = 1") @@ -3079,8 +2989,7 @@ def assertASTTransformation(self, tranformer_class, initial_code, expected_code) self.assertASTEqual(result_ast, expected_ast) - # TODO: RUSTPYTHON; <class 'object'> is not <class 'NoneType'> - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; <class 'object'> is not <class 'NoneType'> def test_node_remove_single(self): code = "def func(arg) -> SomeType: ..." expected = "def func(arg): ..." @@ -3118,8 +3027,7 @@ def visit_Expr(self, node: ast.Expr): self.assertASTTransformation(YieldRemover, code, expected) - # TODO: RUSTPYTHON; <class 'object'> is not <class 'NoneType'> - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; <class 'object'> is not <class 'NoneType'> def test_node_return_list(self): code = """ class DSL(Base, kw1=True): ... @@ -3160,8 +3068,7 @@ def visit_Call(self, node: ast.Call): self.assertASTTransformation(PrintToLog, code, expected) - # TODO: RUSTPYTHON; <class 'object'> is not <class 'NoneType'> - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; <class 'object'> is not <class 'NoneType'> def test_node_replace(self): code = """ def func(arg): @@ -3193,8 +3100,7 @@ def visit_Call(self, node: ast.Call): class ASTConstructorTests(unittest.TestCase): """Test the autogenerated constructors for AST nodes.""" - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_FunctionDef(self): args = ast.arguments() self.assertEqual(args.args, []) @@ -3210,8 +3116,7 @@ def test_FunctionDef(self): self.assertEqual(node.name, "foo") self.assertEqual(node.decorator_list, []) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_expr_context(self): name = ast.Name("x") self.assertEqual(name.id, "x") @@ -3260,8 +3165,7 @@ class FieldsAndTypes(ast.AST): obj = FieldsAndTypes(a=1) self.assertEqual(obj.a, 1) - # TODO: RUSTPYTHON; DeprecationWarning not triggered - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered def test_custom_attributes(self): class MyAttrs(ast.AST): _attributes = ("a", "b") @@ -3276,8 +3180,7 @@ class MyAttrs(ast.AST): ): obj = MyAttrs(c=3) - # TODO: RUSTPYTHON; DeprecationWarning not triggered - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered def test_fields_and_types_no_default(self): class FieldsAndTypesNoDefault(ast.AST): _fields = ("a",) @@ -3293,8 +3196,7 @@ class FieldsAndTypesNoDefault(ast.AST): obj = FieldsAndTypesNoDefault(a=1) self.assertEqual(obj.a, 1) - # TODO: RUSTPYTHON; DeprecationWarning not triggered - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered def test_incomplete_field_types(self): class MoreFieldsThanTypes(ast.AST): _fields = ("a", "b") @@ -3314,8 +3216,7 @@ class MoreFieldsThanTypes(ast.AST): self.assertEqual(obj.a, 1) self.assertEqual(obj.b, 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_complete_field_types(self): class _AllFieldTypes(ast.AST): _fields = ("a", "b") @@ -3416,8 +3317,7 @@ def test_subinterpreter(self): class ASTMainTests(unittest.TestCase): # Tests `ast.main()` function. - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cli_file_input(self): code = "print(1, 2, 3)" expected = ast.dump(ast.parse(code), indent=3) @@ -3435,7 +3335,7 @@ def test_cli_file_input(self): def compare(left, right): return ast.dump(left) == ast.dump(right) -class ASTOptimiziationTests(unittest.TestCase): +class ASTOptimizationTests(unittest.TestCase): binop = { "+": ast.Add(), "-": ast.Sub(), @@ -3492,8 +3392,7 @@ def assert_ast(self, code, non_optimized_target, optimized_target): def create_binop(self, operand, left=ast.Constant(1), right=ast.Constant(1)): return ast.BinOp(left=left, op=self.binop[operand], right=right) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_folding_binop(self): code = "1 %s 1" operators = self.binop.keys() @@ -3517,8 +3416,7 @@ def test_folding_binop(self): self.assert_ast(code, non_optimized_target, optimized_target) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_folding_unaryop(self): code = "%s1" operators = self.unaryop.keys() @@ -3538,8 +3436,7 @@ def create_unaryop(operand): ): self.assert_ast(result_code, non_optimized_target, optimized_target) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_folding_not(self): code = "not (1 %s (1,))" operators = { @@ -3572,8 +3469,7 @@ def create_notop(operand): ): self.assert_ast(result_code, non_optimized_target, optimized_target) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_folding_format(self): code = "'%s' % (a,)" @@ -3594,8 +3490,7 @@ def test_folding_format(self): self.assert_ast(code, non_optimized_target, optimized_target) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_folding_tuple(self): code = "(1,)" @@ -3604,8 +3499,7 @@ def test_folding_tuple(self): self.assert_ast(code, non_optimized_target, optimized_target) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_folding_comparator(self): code = "1 %s %s1%s" operators = [("in", ast.In()), ("not in", ast.NotIn())] @@ -3625,8 +3519,7 @@ def test_folding_comparator(self): )) self.assert_ast(code % (op, left, right), non_optimized_target, optimized_target) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_folding_iter(self): code = "for _ in %s1%s: pass" braces = [ @@ -3648,8 +3541,7 @@ def test_folding_iter(self): self.assert_ast(code % (left, right), non_optimized_target, optimized_target) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_folding_subscript(self): code = "(1,)[0]" @@ -3660,8 +3552,7 @@ def test_folding_subscript(self): self.assert_ast(code, non_optimized_target, optimized_target) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_folding_type_param_in_function_def(self): code = "def foo[%s = 1 + 1](): pass" @@ -3692,8 +3583,7 @@ def test_folding_type_param_in_function_def(self): ) self.assert_ast(result_code, non_optimized_target, optimized_target) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_folding_type_param_in_class_def(self): code = "class foo[%s = 1 + 1]: pass" @@ -3722,8 +3612,7 @@ def test_folding_type_param_in_class_def(self): ) self.assert_ast(result_code, non_optimized_target, optimized_target) - # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: compile() unrecognized flags def test_folding_type_param_in_type_alias(self): code = "type foo[%s = 1 + 1] = 1" diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 95cf51bf08d..b7f93d1bac9 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -86,14 +86,12 @@ def _test_arg_valid(self, ctor, arg): self.assertRaises(ValueError, ctor, arg, quotechar='\x85', lineterminator='\x85') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_reader_arg_valid(self): self._test_arg_valid(csv.reader, []) self.assertRaises(OSError, csv.reader, BadIterable()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_writer_arg_valid(self): self._test_arg_valid(csv.writer, StringIO()) class BadWriter: @@ -214,8 +212,7 @@ def test_write_bigfield(self): self._write_test([bigstring,bigstring], '%s,%s' % \ (bigstring, bigstring)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_quoting(self): self._write_test(['a',1,'p,q'], 'a,1,"p,q"') self._write_error_test(csv.Error, ['a',1,'p,q'], @@ -233,8 +230,7 @@ def test_write_quoting(self): self._write_test(['a','',None,1], '"a","",,"1"', quoting = csv.QUOTE_NOTNULL) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_escape(self): self._write_test(['a',1,'p,q'], 'a,1,"p,q"', escapechar='\\') @@ -266,8 +262,7 @@ def test_write_escape(self): self._write_test(['C\\', '6', '7', 'X"'], 'C\\\\,6,7,"X"""', escapechar='\\', quoting=csv.QUOTE_MINIMAL) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_lineterminator(self): for lineterminator in '\r\n', '\n', '\r', '!@#', '\0': with self.subTest(lineterminator=lineterminator): @@ -281,8 +276,7 @@ def test_write_lineterminator(self): f'1,2{lineterminator}' f'"\r","\n"{lineterminator}') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_iterable(self): self._write_test(iter(['a', 1, 'p,q']), 'a,1,"p,q"') self._write_test(iter(['a', 1, None]), 'a,1,') @@ -325,8 +319,7 @@ def test_writerows_with_none(self): self.assertEqual(fileobj.read(), 'a\r\n""\r\n') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_empty_fields(self): self._write_test((), '') self._write_test([''], '""') @@ -340,8 +333,7 @@ def test_write_empty_fields(self): self._write_test(['', ''], ',') self._write_test([None, None], ',') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_empty_fields_space_delimiter(self): self._write_test([''], '""', delimiter=' ', skipinitialspace=False) self._write_test([''], '""', delimiter=' ', skipinitialspace=True) @@ -382,8 +374,7 @@ def _read_test(self, input, expect, **kwargs): result = list(reader) self.assertEqual(result, expect) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_oddinputs(self): self._read_test([], []) self._read_test([''], [[]]) @@ -394,8 +385,7 @@ def test_read_oddinputs(self): self.assertRaises(csv.Error, self._read_test, [b'abc'], None) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_eol(self): self._read_test(['a,b', 'c,d'], [['a','b'], ['c','d']]) self._read_test(['a,b\n', 'c,d\n'], [['a','b'], ['c','d']]) @@ -410,8 +400,7 @@ def test_read_eol(self): with self.assertRaisesRegex(csv.Error, errmsg): next(csv.reader(['a,b\r\nc,d'])) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_eof(self): self._read_test(['a,"'], [['a', '']]) self._read_test(['"a'], [['a']]) @@ -421,8 +410,7 @@ def test_read_eof(self): self.assertRaises(csv.Error, self._read_test, ['^'], [], escapechar='^', strict=True) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_nul(self): self._read_test(['\0'], [['\0']]) self._read_test(['a,\0b,c'], [['a', '\0b', 'c']]) @@ -435,8 +423,7 @@ def test_read_delimiter(self): self._read_test(['a;b;c'], [['a', 'b', 'c']], delimiter=';') self._read_test(['a\0b\0c'], [['a', 'b', 'c']], delimiter='\0') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_escape(self): self._read_test(['a,\\b,c'], [['a', 'b', 'c']], escapechar='\\') self._read_test(['a,b\\,c'], [['a', 'b,c']], escapechar='\\') @@ -449,8 +436,7 @@ def test_read_escape(self): self._read_test(['a,\\b,c'], [['a', '\\b', 'c']], escapechar=None) self._read_test(['a,\\b,c'], [['a', '\\b', 'c']]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_quoting(self): self._read_test(['1,",3,",5'], [['1', ',3,', '5']]) self._read_test(['1,",3,",5'], [['1', '"', '3', '"', '5']], @@ -487,8 +473,7 @@ def test_read_quoting(self): self._read_test(['1\\.5,\\.5,"\\.5"'], [[1.5, 0.5, ".5"]], quoting=csv.QUOTE_STRINGS, escapechar='\\') - # TODO: RUSTPYTHON; panic - @unittest.skip("TODO: RUSTPYTHON; slice index starts at 1 but ends at 0") + @unittest.skip('TODO: RUSTPYTHON; slice index starts at 1 but ends at 0') def test_read_skipinitialspace(self): self._read_test(['no space, space, spaces,\ttab'], [['no space', 'space', 'spaces', '\ttab']], @@ -503,8 +488,7 @@ def test_read_skipinitialspace(self): [[None, None, None]], skipinitialspace=True, quoting=csv.QUOTE_STRINGS) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_space_delimiter(self): self._read_test(['a b', ' a ', ' ', ''], [['a', '', '', 'b'], ['', '', 'a', '', ''], ['', '', ''], []], @@ -544,8 +528,7 @@ def test_read_linenum(self): self.assertRaises(StopIteration, next, r) self.assertEqual(r.line_num, 3) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_roundtrip_quoteed_newlines(self): rows = [ ['\na', 'b\nc', 'd\n'], @@ -564,8 +547,7 @@ def test_roundtrip_quoteed_newlines(self): for i, row in enumerate(csv.reader(fileobj)): self.assertEqual(row, rows[i]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_roundtrip_escaped_unquoted_newlines(self): rows = [ ['\na', 'b\nc', 'd\n'], @@ -680,8 +662,7 @@ def compare_dialect_123(self, expected, *writeargs, **kwwriteargs): fileobj.seek(0) self.assertEqual(fileobj.read(), expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dialect_apply(self): class testA(csv.excel): delimiter = "\t" @@ -717,8 +698,7 @@ def test_copy(self): dialect = csv.get_dialect(name) self.assertRaises(TypeError, copy.copy, dialect) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickle(self): for name in csv.list_dialects(): dialect = csv.get_dialect(name) @@ -805,8 +785,7 @@ def test_quoted_quote(self): '"I see," said the blind man', 'as he picked up his hammer and saw']]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_quoted_nl(self): input = '''\ 1,2,3,"""I see,"" @@ -847,21 +826,18 @@ class EscapedExcel(csv.excel): class TestEscapedExcel(TestCsvBase): dialect = EscapedExcel() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_escape_fieldsep(self): self.writerAssertEqual([['abc,def']], 'abc\\,def\r\n') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_escape_fieldsep(self): self.readerAssertEqual('abc\\,def\r\n', [['abc,def']]) class TestDialectUnix(TestCsvBase): dialect = 'unix' - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_simple_writer(self): self.writerAssertEqual([[1, 'abc def', 'abc']], '"1","abc def","abc"\n') @@ -878,8 +854,7 @@ class TestQuotedEscapedExcel(TestCsvBase): def test_write_escape_fieldsep(self): self.writerAssertEqual([['abc,def']], '"abc,def"\r\n') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_escape_fieldsep(self): self.readerAssertEqual('"abc\\,def"\r\n', [['abc,def']]) @@ -1076,8 +1051,7 @@ def test_read_multi(self): "s1": 'abc', "s2": 'def'}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_with_blanks(self): reader = csv.DictReader(["1,2,abc,4,5,6\r\n","\r\n", "1,2,abc,4,5,6\r\n"], @@ -1129,8 +1103,7 @@ def test_float_write(self): fileobj.seek(0) self.assertEqual(fileobj.read(), expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_char_write(self): import array, string a = array.array('w', string.ascii_letters) @@ -1278,8 +1251,7 @@ class mydialect(csv.Dialect): self.assertEqual(str(cm.exception), '"lineterminator" must be a string') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_chars(self): def create_invalid(field_name, value, **kwargs): class mydialect(csv.Dialect): diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 0039d34b5ef..84a659ebe4b 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -1115,8 +1115,7 @@ class SMTPHandlerTest(BaseTest): # bpo-14314, bpo-19665, bpo-34092: don't wait forever TIMEOUT = support.LONG_TIMEOUT - # TODO: RUSTPYTHON - @unittest.skip(reason="RUSTPYTHON hangs") + @unittest.skip("TODO: RUSTPYTHON; hangs") def test_basic(self): sockmap = {} server = TestSMTPServer((socket_helper.HOST, 0), self.process_message, 0.001, @@ -2154,8 +2153,7 @@ def handle_request(self, request): request.end_headers() self.handled.set() - # TODO: RUSTPYTHON - @unittest.skip("TODO: RUSTPYTHON; flaky test") + @unittest.skip('TODO: RUSTPYTHON; flaky test') def test_output(self): # The log message sent to the HTTPHandler is properly received. logger = logging.getLogger("http") @@ -4060,8 +4058,7 @@ def _mpinit_issue121723(qspec, message_to_log): # log a message (this creates a record put in the queue) logging.getLogger().info(message_to_log) - # TODO: RUSTPYTHON; ImportError: cannot import name 'SemLock' - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ImportError: cannot import name 'SemLock' @skip_if_tsan_fork @support.requires_subprocess() def test_multiprocessing_queues(self): @@ -4121,8 +4118,7 @@ def test_90195(self): # Logger should be enabled, since explicitly mentioned self.assertFalse(logger.disabled) - # TODO: RUSTPYTHON; ImportError: cannot import name 'SemLock' - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ImportError: cannot import name 'SemLock' def test_111615(self): # See gh-111615 import_helper.import_module('_multiprocessing') # see gh-113692 @@ -4171,91 +4167,6 @@ def __init__(self, *args, **kwargs): handler = logging.getHandlerByName('custom') self.assertEqual(handler.custom_kwargs, custom_kwargs) - # TODO: RUSTPYTHON; ImportError: cannot import name 'SemLock' - @unittest.expectedFailure - # See gh-91555 and gh-90321 - @support.requires_subprocess() - def test_deadlock_in_queue(self): - queue = multiprocessing.Queue() - handler = logging.handlers.QueueHandler(queue) - logger = multiprocessing.get_logger() - level = logger.level - try: - logger.setLevel(logging.DEBUG) - logger.addHandler(handler) - logger.debug("deadlock") - finally: - logger.setLevel(level) - logger.removeHandler(handler) - - def test_recursion_in_custom_handler(self): - class BadHandler(logging.Handler): - def __init__(self): - super().__init__() - def emit(self, record): - logger.debug("recurse") - logger = logging.getLogger("test_recursion_in_custom_handler") - logger.addHandler(BadHandler()) - logger.setLevel(logging.DEBUG) - logger.debug("boom") - - @threading_helper.requires_working_threading() - def test_thread_supression_noninterference(self): - lock = threading.Lock() - logger = logging.getLogger("test_thread_supression_noninterference") - - # Block on the first call, allow others through - # - # NOTE: We need to bypass the base class's lock, otherwise that will - # block multiple calls to the same handler itself. - class BlockOnceHandler(TestHandler): - def __init__(self, barrier): - super().__init__(support.Matcher()) - self.barrier = barrier - - def createLock(self): - self.lock = None - - def handle(self, record): - self.emit(record) - - def emit(self, record): - if self.barrier: - barrier = self.barrier - self.barrier = None - barrier.wait() - with lock: - pass - super().emit(record) - logger.info("blow up if not supressed") - - barrier = threading.Barrier(2) - handler = BlockOnceHandler(barrier) - logger.addHandler(handler) - logger.setLevel(logging.DEBUG) - - t1 = threading.Thread(target=logger.debug, args=("1",)) - with lock: - - # Ensure first thread is blocked in the handler, hence supressing logging... - t1.start() - barrier.wait() - - # ...but the second thread should still be able to log... - t2 = threading.Thread(target=logger.debug, args=("2",)) - t2.start() - t2.join(timeout=3) - - self.assertEqual(len(handler.buffer), 1) - self.assertTrue(handler.matches(levelno=logging.DEBUG, message='2')) - - # The first thread should still be blocked here - self.assertTrue(t1.is_alive()) - - # Now the lock has been released the first thread should complete - t1.join() - self.assertEqual(len(handler.buffer), 2) - self.assertTrue(handler.matches(levelno=logging.DEBUG, message='1')) class ManagerTest(BaseTest): def test_manager_loggerclass(self): @@ -4663,8 +4574,7 @@ def test_dollars(self): f = logging.Formatter('${asctime}--', style='$') self.assertTrue(f.usesTime()) - # TODO: RUSTPYTHON; ValueError: Unexpected error parsing format string - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: Unexpected error parsing format string def test_format_validate(self): # Check correct formatting # Percentage style @@ -4838,8 +4748,7 @@ def test_defaults_parameter(self): def test_invalid_style(self): self.assertRaises(ValueError, logging.Formatter, None, None, 'x') - # TODO: RUSTPYTHON; AttributeError: 'struct_time' object has no attribute 'tm_gmtoff' - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'struct_time' object has no attribute 'tm_gmtoff' def test_time(self): r = self.get_record() dt = datetime.datetime(1993, 4, 21, 8, 3, 0, 0, utc) @@ -4854,8 +4763,7 @@ def test_time(self): f.format(r) self.assertEqual(r.asctime, '1993-04-21 08:03:00,123') - # TODO: RUSTPYTHON; AttributeError: 'struct_time' object has no attribute 'tm_gmtoff' - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'struct_time' object has no attribute 'tm_gmtoff' def test_default_msec_format_none(self): class NoMsecFormatter(logging.Formatter): default_msec_format = None @@ -5257,8 +5165,7 @@ def __init__(self, name='MyLogger', level=logging.NOTSET): h.close() logging.setLoggerClass(logging.Logger) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_logging_at_shutdown(self): # bpo-20037: Doing text I/O late at interpreter shutdown must not crash code = textwrap.dedent(""" @@ -5278,8 +5185,7 @@ def __del__(self): self.assertIn("exception in __del__", err) self.assertIn("ValueError: some error", err) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_logging_at_shutdown_open(self): # bpo-26789: FileHandler keeps a reference to the builtin open() # function to be able to open or reopen the file during Python @@ -6480,8 +6386,7 @@ def rotator(source, dest): rh.close() class TimedRotatingFileHandlerTest(BaseFileTest): - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') @unittest.skipIf(support.is_wasi, "WASI does not have /dev/null.") def test_should_not_rollover(self): # See bpo-45401. Should only ever rollover regular files @@ -6535,8 +6440,7 @@ def test_rollover(self): print(tf.read()) self.assertTrue(found, msg=msg) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') def test_rollover_at_midnight(self, weekly=False): os_helper.unlink(self.fn) now = datetime.datetime.now() @@ -6580,8 +6484,7 @@ def test_rollover_at_midnight(self, weekly=False): for i, line in enumerate(f): self.assertIn(f'testing1 {i}', line) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') def test_rollover_at_weekday(self): self.test_rollover_at_midnight(weekly=True) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 442336fce82..c86d910eef6 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -187,7 +187,7 @@ def test_access(self): os.close(f) self.assertTrue(os.access(os_helper.TESTFN, os.W_OK)) - @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; , BrokenPipeError: (32, 'The process cannot access the file because it is being used by another process. (os error 32)')") + @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; BrokenPipeError: (32, 'The process cannot access the file because it is being used by another process. (os error 32)')") @unittest.skipIf( support.is_emscripten, "Test is unstable under Emscripten." ) @@ -714,7 +714,7 @@ def check_file_attributes(self, result): self.assertTrue(isinstance(result.st_file_attributes, int)) self.assertTrue(0 <= result.st_file_attributes <= 0xFFFFFFFF) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.stat return value doesnt have st_file_attributes attribute") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.stat return value doesnt have st_file_attributes attribute') @unittest.skipUnless(sys.platform == "win32", "st_file_attributes is Win32 specific") def test_file_attributes(self): @@ -736,7 +736,7 @@ def test_file_attributes(self): result.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY, stat.FILE_ATTRIBUTE_DIRECTORY) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.stat (PermissionError: [Errno 5] Access is denied.)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.stat (PermissionError: [Errno 5] Access is denied.)') @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") def test_access_denied(self): # Default to FindFirstFile WIN32_FIND_DATA when access is @@ -759,7 +759,7 @@ def test_access_denied(self): self.assertNotEqual(result.st_size, 0) self.assertTrue(os.path.isfile(fname)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.stat (PermissionError: [Errno 1] Incorrect function.)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.stat (PermissionError: [Errno 1] Incorrect function.)') @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") def test_stat_block_device(self): # bpo-38030: os.stat fails for block devices @@ -817,7 +817,7 @@ def _test_utime(self, set_time, filename=None): self.assertEqual(st.st_atime_ns, atime_ns) self.assertEqual(st.st_mtime_ns, mtime_ns) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference))") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference))') def test_utime(self): def set_time(filename, ns): # test the ns keyword parameter @@ -883,7 +883,7 @@ def set_time(filename, ns): os.utime(name, dir_fd=dirfd, ns=ns) self._test_utime(set_time) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference))") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference))') def test_utime_directory(self): def set_time(filename, ns): # test calling os.utime() on a directory @@ -912,14 +912,14 @@ def _test_utime_current(self, set_time): self.assertAlmostEqual(st.st_mtime, current, delta=delta, msg=msg) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (AssertionError: 3359485824.516508 != 1679742912.516503 within 0.05 delta (1679742912.000005 difference) : st_time=3359485824.516508, current=1679742912.516503, dt=1679742912.000005)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: 3359485824.516508 != 1679742912.516503 within 0.05 delta (1679742912.000005 difference) : st_time=3359485824.516508, current=1679742912.516503, dt=1679742912.000005)') def test_utime_current(self): def set_time(filename): # Set to the current time in the new way os.utime(self.fname) self._test_utime_current(set_time) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (AssertionError: 3359485824.5186944 != 1679742912.5186892 within 0.05 delta (1679742912.0000052 difference) : st_time=3359485824.5186944, current=1679742912.5186892, dt=1679742912.0000052)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: 3359485824.5186944 != 1679742912.5186892 within 0.05 delta (1679742912.0000052 difference) : st_time=3359485824.5186944, current=1679742912.5186892, dt=1679742912.0000052)') def test_utime_current_old(self): def set_time(filename): # Set to the current time in the old explicit way. @@ -958,7 +958,7 @@ def test_large_time(self): os.utime(self.fname, (large, large)) self.assertEqual(os.stat(self.fname).st_mtime, large) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (AssertionError: NotImplementedError not raised)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: NotImplementedError not raised)') def test_utime_invalid_arguments(self): # seconds and nanoseconds parameters are mutually exclusive with self.assertRaises(ValueError): @@ -1161,7 +1161,7 @@ def test_putenv_unsetenv(self): self.assertEqual(proc.stdout.rstrip(), repr(None)) # On OS X < 10.6, unsetenv() doesn't return a value (bpo-13415). - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (AssertionError: ValueError not raised by putenv)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: ValueError not raised by putenv)') @support.requires_mac_ver(10, 6) def test_putenv_unsetenv_error(self): # Empty variable name is invalid. @@ -1796,7 +1796,7 @@ def test_mode(self): self.assertEqual(os.stat(path).st_mode & 0o777, 0o555) self.assertEqual(os.stat(parent).st_mode & 0o777, 0o775) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.umask not implemented yet for all platforms") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.umask not implemented yet for all platforms') @unittest.skipIf( support.is_emscripten or support.is_wasi, "Emscripten's/WASI's umask is a stub." @@ -1815,7 +1815,7 @@ def test_exist_ok_existing_directory(self): # Issue #25583: A drive root could raise PermissionError on Windows os.makedirs(os.path.abspath('/'), exist_ok=True) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.umask not implemented yet for all platforms") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.umask not implemented yet for all platforms') @unittest.skipIf( support.is_emscripten or support.is_wasi, "Emscripten's/WASI's umask is a stub." @@ -2205,7 +2205,7 @@ def test_execv_with_bad_arglist(self): self.assertRaises(ValueError, os.execv, 'notepad', ('',)) self.assertRaises(ValueError, os.execv, 'notepad', ['']) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.execve not implemented yet for all platforms") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.execve not implemented yet for all platforms') def test_execvpe_with_bad_arglist(self): self.assertRaises(ValueError, os.execvpe, 'notepad', [], None) self.assertRaises(ValueError, os.execvpe, 'notepad', [], {}) @@ -2265,7 +2265,7 @@ def test_internal_execvpe_str(self): if os.name != "nt": self._test_internal_execvpe(bytes) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.execve not implemented yet for all platforms") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.execve not implemented yet for all platforms') def test_execve_invalid_env(self): args = [sys.executable, '-c', 'pass'] @@ -2287,7 +2287,7 @@ def test_execve_invalid_env(self): with self.assertRaises(ValueError): os.execve(args[0], args, newenv) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.execve not implemented yet for all platforms") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.execve not implemented yet for all platforms') @unittest.skipUnless(sys.platform == "win32", "Win32-specific test") def test_execve_with_empty_path(self): # bpo-32890: Check GetLastError() misuse @@ -2445,12 +2445,12 @@ def test_ftruncate(self): self.check(os.ftruncate, 0) self.check_bool(os.truncate, 0) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (OSError: [Errno 18] There are no more files.)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (OSError: [Errno 18] There are no more files.)') @unittest.skipUnless(hasattr(os, 'lseek'), 'test needs os.lseek()') def test_lseek(self): self.check(os.lseek, 0, 0) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (OSError: [Errno 18] There are no more files.)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (OSError: [Errno 18] There are no more files.)') @unittest.skipUnless(hasattr(os, 'read'), 'test needs os.read()') def test_read(self): self.check(os.read, 1) @@ -2464,7 +2464,7 @@ def test_readv(self): def test_tcsetpgrpt(self): self.check(os.tcsetpgrp, 0) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (OSError: [Errno 18] There are no more files.)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (OSError: [Errno 18] There are no more files.)') @unittest.skipUnless(hasattr(os, 'write'), 'test needs os.write()') def test_write(self): self.check(os.write, b" ") @@ -2473,7 +2473,7 @@ def test_write(self): def test_writev(self): self.check(os.writev, [b'abc']) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms') @support.requires_subprocess() def test_inheritable(self): self.check(os.get_inheritable) @@ -3225,7 +3225,7 @@ def test_getfinalpathname_handles(self): self.assertEqual(0, handle_delta) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.stat (PermissionError: [Errno 5] Access is denied.)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.stat (PermissionError: [Errno 5] Access is denied.)') @support.requires_subprocess() def test_stat_unlink_race(self): # bpo-46785: the implementation of os.stat() falls back to reading @@ -3435,7 +3435,7 @@ def test_waitstatus_to_exitcode(self): with self.assertRaises(TypeError): os.waitstatus_to_exitcode(0.0) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.spawnv not implemented yet for all platforms") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.spawnv not implemented yet for all platforms') @unittest.skipUnless(sys.platform == 'win32', 'win32-specific test') def test_waitpid_windows(self): # bpo-40138: test os.waitpid() and os.waitstatus_to_exitcode() @@ -3444,7 +3444,7 @@ def test_waitpid_windows(self): code = f'import _winapi; _winapi.ExitProcess({STATUS_CONTROL_C_EXIT})' self.check_waitpid(code, exitcode=STATUS_CONTROL_C_EXIT) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (OverflowError: Python int too large to convert to Rust i32)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (OverflowError: Python int too large to convert to Rust i32)') @unittest.skipUnless(sys.platform == 'win32', 'win32-specific test') def test_waitstatus_to_exitcode_windows(self): max_exitcode = 2 ** 32 - 1 @@ -4621,7 +4621,7 @@ def test_process_cpu_count_affinity(self): # FD inheritance check is only useful for systems with process support. @support.requires_subprocess() class FDInheritanceTests(unittest.TestCase): - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms') def test_get_set_inheritable(self): fd = os.open(__file__, os.O_RDONLY) self.addCleanup(os.close, fd) @@ -4666,7 +4666,7 @@ def test_get_set_inheritable_o_path(self): os.set_inheritable(fd, False) self.assertEqual(os.get_inheritable(fd), False) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms') def test_get_set_inheritable_badf(self): fd = os_helper.make_bad_fd() @@ -4682,7 +4682,7 @@ def test_get_set_inheritable_badf(self): os.set_inheritable(fd, False) self.assertEqual(ctx.exception.errno, errno.EBADF) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms') def test_open(self): fd = os.open(__file__, os.O_RDONLY) self.addCleanup(os.close, fd) @@ -4696,7 +4696,7 @@ def test_pipe(self): self.assertEqual(os.get_inheritable(rfd), False) self.assertEqual(os.get_inheritable(wfd), False) - @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; os.dup on windows") + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; os.dup on windows') def test_dup(self): fd1 = os.open(__file__, os.O_RDONLY) self.addCleanup(os.close, fd1) @@ -4705,13 +4705,13 @@ def test_dup(self): self.addCleanup(os.close, fd2) self.assertEqual(os.get_inheritable(fd2), False) - @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; os.dup on windows") + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; os.dup on windows') def test_dup_standard_stream(self): fd = os.dup(1) self.addCleanup(os.close, fd) self.assertGreater(fd, 0) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.dup not implemented yet for all platforms") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.dup not implemented yet for all platforms') @unittest.skipUnless(sys.platform == 'win32', 'win32-specific test') def test_dup_nul(self): # os.dup() was creating inheritable fds for character files. @@ -5028,7 +5028,7 @@ def check_entry(self, entry, name, is_dir, is_file, is_symlink): entry_lstat, os.name == 'nt') - @unittest.skipIf(sys.platform == 'linux', "TODO: RUSTPYTHON; , flaky test") + @unittest.skipIf(sys.platform == 'linux', 'TODO: RUSTPYTHON; flaky test') def test_attributes(self): link = os_helper.can_hardlink() symlink = os_helper.can_symlink() @@ -5128,7 +5128,7 @@ def test_fspath_protocol_bytes(self): self.assertEqual(fspath, os.path.join(os.fsencode(self.path),bytes_filename)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; entry.is_dir() is False") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; entry.is_dir() is False') def test_removed_dir(self): path = os.path.join(self.path, 'dir') @@ -5151,7 +5151,7 @@ def test_removed_dir(self): self.assertRaises(FileNotFoundError, entry.stat) self.assertRaises(FileNotFoundError, entry.stat, follow_symlinks=False) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; entry.is_file() is False") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; entry.is_file() is False') def test_removed_file(self): entry = self.create_file_entry() os.unlink(entry.path) @@ -5239,7 +5239,7 @@ def test_fd(self): st = os.stat(entry.name, dir_fd=fd, follow_symlinks=False) self.assertEqual(entry.stat(follow_symlinks=False), st) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (AssertionError: FileNotFoundError not raised by scandir)") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: FileNotFoundError not raised by scandir)') @unittest.skipIf(support.is_wasi, "WASI maps '' to cwd") def test_empty_path(self): self.assertRaises(FileNotFoundError, os.scandir, '') diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index ef2d211a612..9d43a33cd9e 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -112,8 +112,7 @@ def test_literals(self): # raw strings should not have unicode escapes self.assertNotEqual(r"\u0020", " ") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_ascii(self): self.assertEqual(ascii('abc'), "'abc'") self.assertEqual(ascii('ab\\c'), "'ab\\\\c'") @@ -566,7 +565,7 @@ def __str__(self): return self.sval self.checkraises(TypeError, ' ', 'join', [1, 2, 3]) self.checkraises(TypeError, ' ', 'join', ['1', '2', 3]) - @unittest.skip("TODO: RUSTPYTHON, oom handling") + @unittest.skip('TODO: RUSTPYTHON; oom handling') @unittest.skipIf(sys.maxsize > 2**32, 'needs too much memory on a 64-bit platform') def test_join_overflow(self): @@ -795,8 +794,7 @@ def test_isdecimal(self): for ch in ['\U0001D7F6', '\U00011066', '\U000104A0']: self.assertTrue(ch.isdecimal(), '{!a} is decimal.'.format(ch)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_isdigit(self): super().test_isdigit() self.checkequalnofix(True, '\u2460', 'isdigit') @@ -942,8 +940,7 @@ def test_upper(self): self.assertEqual('\U0008fffe'.upper(), '\U0008fffe') self.assertEqual('\u2177'.upper(), '\u2167') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_capitalize(self): string_tests.StringLikeTest.test_capitalize(self) self.assertEqual('\U0001044F'.capitalize(), '\U00010427') @@ -961,8 +958,7 @@ def test_capitalize(self): self.assertEqual('finnish'.capitalize(), 'Finnish') self.assertEqual('A\u0345\u03a3'.capitalize(), 'A\u0345\u03c2') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_title(self): super().test_title() self.assertEqual('\U0001044F'.title(), '\U00010427') @@ -980,8 +976,7 @@ def test_title(self): self.assertEqual('A\u03a3 \u1fa1xy'.title(), 'A\u03c2 \u1fa9xy') self.assertEqual('A\u03a3A'.title(), 'A\u03c3a') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_swapcase(self): string_tests.StringLikeTest.test_swapcase(self) self.assertEqual('\U0001044F'.swapcase(), '\U00010427') @@ -1081,8 +1076,7 @@ def test_issue18183(self): '\U00100000'.ljust(3, '\U00010000') '\U00100000'.rjust(3, '\U00010000') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_format(self): self.assertEqual(''.format(), '') self.assertEqual('a'.format(), 'a') @@ -1244,10 +1238,10 @@ def __repr__(self): self.assertEqual('{0:\x00^6}'.format(3), '\x00\x003\x00\x00\x00') self.assertEqual('{0:<6}'.format(3), '3 ') - self.assertEqual('{0:\x00<6}'.format(3.14), '3.14\x00\x00') - self.assertEqual('{0:\x01<6}'.format(3.14), '3.14\x01\x01') - self.assertEqual('{0:\x00^6}'.format(3.14), '\x003.14\x00') - self.assertEqual('{0:^6}'.format(3.14), ' 3.14 ') + self.assertEqual('{0:\x00<6}'.format(3.25), '3.25\x00\x00') + self.assertEqual('{0:\x01<6}'.format(3.25), '3.25\x01\x01') + self.assertEqual('{0:\x00^6}'.format(3.25), '\x003.25\x00') + self.assertEqual('{0:^6}'.format(3.25), ' 3.25 ') self.assertEqual('{0:\x00<12}'.format(3+2.0j), '(3+2j)\x00\x00\x00\x00\x00\x00') self.assertEqual('{0:\x01<12}'.format(3+2.0j), '(3+2j)\x01\x01\x01\x01\x01\x01') @@ -1466,21 +1460,19 @@ def __getitem__(self, key): self.assertRaises(TypeError, '{a}'.format_map, []) self.assertRaises(ZeroDivisionError, '{a}'.format_map, BadMapping()) - @unittest.skip("TODO: RUSTPYTHON, killed for chewing up RAM") + @unittest.skip('TODO: RUSTPYTHON; killed for chewing up RAM') def test_format_huge_precision(self): format_string = ".{}f".format(sys.maxsize + 1) with self.assertRaises(ValueError): result = format(2.34, format_string) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_format_huge_width(self): format_string = "{}f".format(sys.maxsize + 1) with self.assertRaises(ValueError): result = format(2.34, format_string) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_format_huge_item_number(self): format_string = "{{{}:.6f}}".format(sys.maxsize + 1) with self.assertRaises(ValueError): @@ -1516,8 +1508,7 @@ def __format__(self, spec): self.assertEqual('{:{f}}{g}{}'.format(1, 3, g='g', f=2), ' 1g3') self.assertEqual('{f:{}}{}{g}'.format(2, 4, f=1, g='g'), ' 14g') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_formatting(self): string_tests.StringLikeTest.test_formatting(self) # Testing Unicode formatting strings... @@ -1766,8 +1757,7 @@ def __str__(self): 'character buffers are decoded to unicode' ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_constructor_keyword_args(self): """Pass various keyword argument combinations to the constructor.""" # The object argument can be passed as a keyword. @@ -1777,8 +1767,7 @@ def test_constructor_keyword_args(self): self.assertEqual(str(b'foo', errors='strict'), 'foo') # not "b'foo'" self.assertEqual(str(object=b'foo', errors='strict'), 'foo') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_constructor_defaults(self): """Check the constructor argument defaults.""" # The object argument defaults to '' or b''. @@ -1790,8 +1779,7 @@ def test_constructor_defaults(self): # The errors argument defaults to strict. self.assertRaises(UnicodeDecodeError, str, utf8_cent, encoding='ascii') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_codecs_utf7(self): utfTests = [ ('A\u2262\u0391.', b'A+ImIDkQ.'), # RFC2152 example @@ -2301,8 +2289,7 @@ def test_codecs_errors(self): self.assertRaises(ValueError, complex, "\ud800") self.assertRaises(ValueError, complex, "\udf00") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_codecs(self): # Encoding self.assertEqual('hello'.encode('ascii'), b'hello') @@ -2432,8 +2419,7 @@ def test_ucs4(self): else: self.fail("Should have raised UnicodeDecodeError") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_conversion(self): # Make sure __str__() works properly class StrWithStr(str): @@ -2482,7 +2468,7 @@ def test_printable_repr(self): # This test only affects 32-bit platforms because expandtabs can only take # an int as the max value, not a 64-bit C long. If expandtabs is changed # to take a 64-bit long, this test should apply to all platforms. - @unittest.skip("TODO: RUSTPYTHON, oom handling") + @unittest.skip('TODO: RUSTPYTHON; oom handling') @unittest.skipIf(sys.maxsize > (1 << 32) or struct.calcsize('P') != 4, 'only applies to 32-bit platforms') def test_expandtabs_overflows_gracefully(self): @@ -2493,7 +2479,7 @@ def test_expandtabs_optimization(self): s = 'abc' self.assertIs(s.expandtabs(), s) - @unittest.skip("TODO: RUSTPYTHON, aborted: memory allocation of 9223372036854775759 bytes failed") + @unittest.skip('TODO: RUSTPYTHON; aborted: memory allocation of 9223372036854775759 bytes failed') def test_raiseMemError(self): asciifields = "nnb" compactfields = asciifields + "nP" @@ -2633,14 +2619,12 @@ def test_compare(self): self.assertTrue(astral >= bmp2) self.assertFalse(astral >= astral2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_free_after_iterating(self): support.check_free_after_iterating(self, iter, str) support.check_free_after_iterating(self, reversed, str) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_check_encoding_errors(self): # bpo-37388: str(bytes) and str.decode() must check encoding and errors # arguments in dev mode @@ -2701,8 +2685,7 @@ def test_check_encoding_errors(self): proc = assert_python_failure('-X', 'dev', '-c', code) self.assertEqual(proc.rc, 10, proc) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_str_invalid_call(self): # too many args with self.assertRaisesRegex(TypeError, r"str expected at most 3 arguments, got 4"): diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 59b55155291..57b082fc584 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -339,8 +339,7 @@ def test_set_attribute(self): element.attrib = {'A': 'B', 'C': 'D'} self.assertEqual(element.attrib, {'A': 'B', 'C': 'D'}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_simpleops(self): # Basic method sanity checks. @@ -385,8 +384,7 @@ def test_simpleops(self): self.serialize_check(element, '<tag key="value"><subtag /><subtag /></tag>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cdata(self): # Test CDATA handling (etc). @@ -397,8 +395,7 @@ def test_cdata(self): self.serialize_check(ET.XML("<tag><![CDATA[hello]]></tag>"), '<tag>hello</tag>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_file_init(self): stringfile = io.BytesIO(SAMPLE_XML.encode("utf-8")) tree = ET.ElementTree(file=stringfile) @@ -410,8 +407,7 @@ def test_file_init(self): self.assertEqual(tree.find("element/../empty-element").tag, 'empty-element') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_path_cache(self): # Check that the path cache behaves sanely. @@ -428,8 +424,7 @@ def test_path_cache(self): for i in range(600): ET.ElementTree(elem).find('./'+str(i)) self.assertLess(len(ElementPath._cache), 500) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_copy(self): # Test copy handling (etc). @@ -442,8 +437,7 @@ def test_copy(self): self.serialize_check(e2, '<tag>hello<bar /></tag>') self.serialize_check(e3, '<tag>hello<foo /></tag>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_attrib(self): # Test attribute handling. @@ -520,8 +514,7 @@ def test_makeelement(self): elem[:] = tuple([subelem]) self.serialize_check(elem, '<tag><subtag key="value" /></tag>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parsefile(self): # Test parsing from file. @@ -567,8 +560,7 @@ def test_parsefile(self): ' <empty-element />\n' '</root>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parseliteral(self): element = ET.XML("<html><body>text</body></html>") self.assertEqual(ET.tostring(element, encoding='unicode'), @@ -591,8 +583,7 @@ def test_parseliteral(self): self.assertEqual(len(ids), 1) self.assertEqual(ids["body"].tag, 'body') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_iterparse(self): # Test iterparse interface. @@ -744,8 +735,7 @@ def test_iterparse(self): with self.assertRaises(FileNotFoundError): iterparse("nonexistent") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_iterparse_close(self): iterparse = ET.iterparse @@ -814,8 +804,7 @@ def test_writefile(self): elem[0] = ET.PI("key", "value") self.serialize_check(elem, 'text<?key value?><subtag>subtext</subtag>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_custom_builder(self): # Test parser w. custom builder. @@ -877,8 +866,7 @@ def end_ns(self, prefix): ('end-ns', ''), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_custom_builder_only_end_ns(self): class Builder(list): def end_ns(self, prefix): @@ -901,8 +889,7 @@ def end_ns(self, prefix): ('end-ns', ''), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_initialize_parser_without_target(self): # Explicit None parser = ET.XMLParser(target=None) @@ -912,8 +899,7 @@ def test_initialize_parser_without_target(self): parser2 = ET.XMLParser() self.assertIsInstance(parser2.target, ET.TreeBuilder) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_children(self): # Test Element children iteration @@ -951,16 +937,14 @@ def test_children(self): elem.clear() self.assertEqual(list(elem), []) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_writestring(self): elem = ET.XML("<html><body>text</body></html>") self.assertEqual(ET.tostring(elem), b'<html><body>text</body></html>') elem = ET.fromstring("<html><body>text</body></html>") self.assertEqual(ET.tostring(elem), b'<html><body>text</body></html>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_indent(self): elem = ET.XML("<root></root>") ET.indent(elem) @@ -1005,8 +989,7 @@ def test_indent(self): b'</html>' ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_indent_space(self): elem = ET.XML("<html><body><p>pre<br/>post</p><p>text</p></body></html>") ET.indent(elem, space='\t') @@ -1032,8 +1015,7 @@ def test_indent_space(self): b'</html>' ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_indent_space_caching(self): elem = ET.XML("<html><body><p>par</p><p>text</p><p><br/></p><p /></body></html>") ET.indent(elem) @@ -1050,8 +1032,7 @@ def test_indent_space_caching(self): len({id(el.tail) for el in elem.iter()}), ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_indent_level(self): elem = ET.XML("<html><body><p>pre<br/>post</p><p>text</p></body></html>") with self.assertRaises(ValueError): @@ -1084,8 +1065,7 @@ def test_indent_level(self): b' </html>' ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tostring_default_namespace(self): elem = ET.XML('<body xmlns="http://effbot.org/ns"><tag/></body>') self.assertEqual( @@ -1097,8 +1077,7 @@ def test_tostring_default_namespace(self): '<body xmlns="http://effbot.org/ns"><tag /></body>' ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tostring_default_namespace_different_namespace(self): elem = ET.XML('<body xmlns="http://effbot.org/ns"><tag/></body>') self.assertEqual( @@ -1106,16 +1085,14 @@ def test_tostring_default_namespace_different_namespace(self): '<ns1:body xmlns="foobar" xmlns:ns1="http://effbot.org/ns"><ns1:tag /></ns1:body>' ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tostring_default_namespace_original_no_namespace(self): elem = ET.XML('<body><tag/></body>') EXPECTED_MSG = '^cannot use non-qualified names with default_namespace option$' with self.assertRaisesRegex(ValueError, EXPECTED_MSG): ET.tostring(elem, encoding='unicode', default_namespace='foobar') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tostring_no_xml_declaration(self): elem = ET.XML('<body><tag/></body>') self.assertEqual( @@ -1123,8 +1100,7 @@ def test_tostring_no_xml_declaration(self): '<body><tag /></body>' ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tostring_xml_declaration(self): elem = ET.XML('<body><tag/></body>') self.assertEqual( @@ -1132,8 +1108,7 @@ def test_tostring_xml_declaration(self): b"<?xml version='1.0' encoding='utf8'?>\n<body><tag /></body>" ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tostring_xml_declaration_unicode_encoding(self): elem = ET.XML('<body><tag/></body>') self.assertEqual( @@ -1141,8 +1116,7 @@ def test_tostring_xml_declaration_unicode_encoding(self): "<?xml version='1.0' encoding='utf-8'?>\n<body><tag /></body>" ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tostring_xml_declaration_cases(self): elem = ET.XML('<body><tag>ø</tag></body>') TESTCASES = [ @@ -1187,8 +1161,7 @@ def test_tostring_xml_declaration_cases(self): expected_retval ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tostringlist_default_namespace(self): elem = ET.XML('<body xmlns="http://effbot.org/ns"><tag/></body>') self.assertEqual( @@ -1200,8 +1173,7 @@ def test_tostringlist_default_namespace(self): '<body xmlns="http://effbot.org/ns"><tag /></body>' ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tostringlist_xml_declaration(self): elem = ET.XML('<body><tag/></body>') self.assertEqual( @@ -1221,8 +1193,7 @@ def test_tostringlist_xml_declaration(self): self.assertRegex(stringlist[0], r"^<\?xml version='1.0' encoding='.+'?>") self.assertEqual(['<body', '>', '<tag', ' />', '</body>'], stringlist[1:]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_encoding(self): def check(encoding, body=''): xml = ("<?xml version='1.0' encoding='%s'?><xml>%s</xml>" % @@ -1282,8 +1253,7 @@ def bxml(encoding): self.assertRaises(ValueError, ET.XML, xml('undefined').encode('ascii')) self.assertRaises(LookupError, ET.XML, xml('xxx').encode('ascii')) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_methods(self): # Test serialization methods. @@ -1299,8 +1269,7 @@ def test_methods(self): '<html><link><script>1 < 2</script></html>\n') self.assertEqual(serialize(e, method="text"), '1 < 2\n') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_issue18347(self): e = ET.XML('<html><CamelCase>text</CamelCase></html>') self.assertEqual(serialize(e), @@ -1308,8 +1277,7 @@ def test_issue18347(self): self.assertEqual(serialize(e, method="html"), '<html><CamelCase>text</CamelCase></html>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_entity(self): # Test entity handling. @@ -1347,8 +1315,7 @@ def test_entity(self): self.assertEqual(str(cm.exception), 'undefined entity &entity;: line 4, column 10') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_namespace(self): # Test namespace issues. @@ -1447,8 +1414,7 @@ def test_qname(self): self.assertNotEqual(q1, 'ns:tag') self.assertEqual(q1, '{ns}tag') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_doctype_public(self): # Test PUBLIC doctype. @@ -1515,8 +1481,7 @@ def check(p, expected, namespaces=None): {'': 'http://www.w3.org/2001/XMLSchema', 'ns': 'http://www.w3.org/2001/XMLSchema'}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_processinginstruction(self): # Test ProcessingInstruction directly @@ -1533,8 +1498,7 @@ def test_processinginstruction(self): b"<?xml version='1.0' encoding='latin-1'?>\n" b"<?test <testing&>\xe3?>") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_html_empty_elems_serialization(self): # issue 15970 # from http://www.w3.org/TR/html401/index/elements.html @@ -1565,8 +1529,7 @@ def test_tree_write_attribute_order(self): self.assertEqual(serialize(root, method='html'), '<cirriculum status="public" company="example"></cirriculum>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_attlist_default(self): # Test default attribute values; See BPO 42151. root = ET.fromstring(ATTLIST_XML) @@ -1601,8 +1564,7 @@ def assert_event_tags(self, parser, expected, max_events=None): self.assertEqual([(action, elem.tag) for action, elem in events], expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_simple_xml(self, chunk_size=None, flush=False): parser = ET.XMLPullParser() self.assert_event_tags(parser, []) @@ -1624,23 +1586,19 @@ def test_simple_xml(self, chunk_size=None, flush=False): self.assert_event_tags(parser, [('end', 'root')]) self.assertIsNone(parser.close()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_simple_xml_chunk_1(self): self.test_simple_xml(chunk_size=1, flush=True) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_simple_xml_chunk_5(self): self.test_simple_xml(chunk_size=5, flush=True) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_simple_xml_chunk_22(self): self.test_simple_xml(chunk_size=22) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_feed_while_iterating(self): parser = ET.XMLPullParser() it = parser.read_events() @@ -1653,8 +1611,7 @@ def test_feed_while_iterating(self): with self.assertRaises(StopIteration): next(it) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_simple_xml_with_ns(self): parser = ET.XMLPullParser() self.assert_event_tags(parser, []) @@ -1676,8 +1633,7 @@ def test_simple_xml_with_ns(self): self.assert_event_tags(parser, [('end', '{namespace}root')]) self.assertIsNone(parser.close()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_ns_events(self): parser = ET.XMLPullParser(events=('start-ns', 'end-ns')) self._feed(parser, "<!-- comment -->\n") @@ -1693,8 +1649,7 @@ def test_ns_events(self): self.assertEqual(list(parser.read_events()), [('end-ns', None)]) self.assertIsNone(parser.close()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_ns_events_start(self): parser = ET.XMLPullParser(events=('start-ns', 'start', 'end')) self._feed(parser, "<tag xmlns='abc' xmlns:p='xyz'>\n") @@ -1718,8 +1673,7 @@ def test_ns_events_start(self): ('end', '{abc}tag'), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_ns_events_start_end(self): parser = ET.XMLPullParser(events=('start-ns', 'start', 'end', 'end-ns')) self._feed(parser, "<tag xmlns='abc' xmlns:p='xyz'>\n") @@ -1747,8 +1701,7 @@ def test_ns_events_start_end(self): ('end-ns', None), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_events(self): parser = ET.XMLPullParser(events=()) self._feed(parser, "<root/>\n") @@ -1795,8 +1748,7 @@ def test_events(self): self._feed(parser, "</root>") self.assertIsNone(parser.close()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_events_comment(self): parser = ET.XMLPullParser(events=('start', 'comment', 'end')) self._feed(parser, "<!-- text here -->\n") @@ -1816,8 +1768,7 @@ def test_events_comment(self): self._feed(parser, "<!-- text here -->\n") self.assert_events(parser, [('comment', (ET.Comment, ' text here '))]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_events_pi(self): parser = ET.XMLPullParser(events=('start', 'pi', 'end')) self._feed(parser, "<?pitarget?>\n") @@ -1826,8 +1777,7 @@ def test_events_pi(self): self._feed(parser, "<?pitarget some text ?>\n") self.assert_events(parser, [('pi', (ET.PI, 'pitarget some text '))]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_events_sequence(self): # Test that events can be some sequence that's not just a tuple or list eventset = {'end', 'start'} @@ -1847,14 +1797,12 @@ def __next__(self): self._feed(parser, "<foo>bar</foo>") self.assert_event_tags(parser, [('start', 'foo'), ('end', 'foo')]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unknown_event(self): with self.assertRaises(ValueError): ET.XMLPullParser(events=('start', 'end', 'bogus')) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(pyexpat.version_info < (2, 6, 0), f'Expat {pyexpat.version_info} does not ' 'support reparse deferral') @@ -1879,8 +1827,7 @@ def test_flush_reparse_deferral_enabled(self): self.assert_event_tags(parser, [('end', 'doc')]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_flush_reparse_deferral_disabled(self): parser = ET.XMLPullParser(events=('start', 'end')) @@ -2063,8 +2010,7 @@ def _my_loader(self, href, parse): else: return None - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_xinclude_default(self): from xml.etree import ElementInclude doc = self.xinclude_loader('default.xml') @@ -2079,8 +2025,7 @@ def test_xinclude_default(self): '</root>\n' '</document>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_xinclude(self): from xml.etree import ElementInclude @@ -2145,8 +2090,7 @@ def test_xinclude(self): ' </ns0:include>\n' '</div>') # C5 - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_xinclude_repeated(self): from xml.etree import ElementInclude @@ -2154,8 +2098,7 @@ def test_xinclude_repeated(self): ElementInclude.include(document, self.xinclude_loader) self.assertEqual(1+4*2, len(document.findall(".//p"))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_xinclude_failures(self): from xml.etree import ElementInclude @@ -2260,8 +2203,7 @@ def check(elem): elem.set("123", 123) check(elem) # attribute value - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_xmltoolkit25(self): # typo in ElementTree.findtext @@ -2270,8 +2212,7 @@ def test_bug_xmltoolkit25(self): self.assertEqual(tree.findtext("tag"), 'text') self.assertEqual(tree.findtext("section/tag"), 'subtext') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_xmltoolkit28(self): # .//tag causes exceptions @@ -2279,8 +2220,7 @@ def test_bug_xmltoolkit28(self): self.assertEqual(summarize_list(tree.findall(".//thead")), []) self.assertEqual(summarize_list(tree.findall(".//tbody")), ['tbody']) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_xmltoolkitX1(self): # dump() doesn't flush the output buffer @@ -2289,8 +2229,7 @@ def test_bug_xmltoolkitX1(self): ET.dump(tree) self.assertEqual(stdout.getvalue(), '<doc><table><tbody /></table></doc>\n') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_xmltoolkit39(self): # non-ascii element and attribute names doesn't work @@ -2316,8 +2255,7 @@ def test_bug_xmltoolkit39(self): self.assertEqual(ET.tostring(tree, "utf-8"), b'<tag \xc3\xa4ttr="v\xc3\xa4lue" />') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_xmltoolkit54(self): # problems handling internally defined entities @@ -2327,8 +2265,7 @@ def test_bug_xmltoolkit54(self): b'<doc>舰</doc>') self.assertEqual(serialize(e), '<doc>\u8230</doc>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_xmltoolkit55(self): # make sure we're reporting the first error, not the last @@ -2338,8 +2275,7 @@ def test_bug_xmltoolkit55(self): self.assertEqual(str(cm.exception), 'undefined entity &ldots;: line 1, column 36') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_xmltoolkit60(self): # Handle crash in stream source. @@ -2349,8 +2285,7 @@ def read(self, x): self.assertRaises(OSError, ET.parse, ExceptionFile()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_xmltoolkit62(self): # Don't crash when using custom entities. @@ -2368,8 +2303,7 @@ def test_bug_xmltoolkit62(self): self.assertEqual(t.find('.//paragraph').text, 'A new cultivar of Begonia plant named \u2018BCT9801BEG\u2019.') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.gettrace(), "Skips under coverage.") def test_bug_xmltoolkit63(self): # Check reference leak. @@ -2385,8 +2319,7 @@ def xmltoolkit63(): xmltoolkit63() self.assertEqual(sys.getrefcount(None), count) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_200708_newline(self): # Preserve newlines in attributes. @@ -2398,8 +2331,7 @@ def test_bug_200708_newline(self): self.assertEqual(ET.tostring(ET.XML(ET.tostring(e))), b'<SomeTag text="def _f(): return 3 " />') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_200708_close(self): # Test default builder. parser = ET.XMLParser() # default @@ -2437,8 +2369,7 @@ def test_bug_200709_default_namespace(self): self.assertEqual(str(cm.exception), 'cannot use non-qualified names with default_namespace option') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_200709_register_namespace(self): e = ET.Element("{http://namespace.invalid/does/not/exist/}title") self.assertEqual(ET.tostring(e), @@ -2494,8 +2425,7 @@ def test_bug_1534630(self): e = bob.close() self.assertEqual(serialize(e), '<tag />') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_issue6233(self): e = ET.XML(b"<?xml version='1.0' encoding='utf-8'?>" b'<body>t\xc3\xa3g</body>') @@ -2508,8 +2438,7 @@ def test_issue6233(self): b"<?xml version='1.0' encoding='ascii'?>\n" b'<body>tãg</body>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_issue6565(self): elem = ET.XML("<body><tag/></body>") self.assertEqual(summarize_list(elem), ['tag']) @@ -2555,8 +2484,7 @@ def __bool__(self): self.assertIsInstance(e[0].tail, str) self.assertEqual(e[0].tail, 'changed') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_lost_elem(self): # Issue #25902: Borrowed element can disappear class Tag: @@ -2582,8 +2510,7 @@ def check_expat224_utf8_bug(self, text): root = ET.XML(xml) self.assertEqual(root.get('b'), text.decode('utf-8')) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_expat224_utf8_bug(self): # bpo-31170: Expat 2.2.3 had a bug in its UTF-8 decoder. # Check that Expat 2.2.4 fixed the bug. @@ -2596,8 +2523,7 @@ def test_expat224_utf8_bug(self): text = b'x' + b'\xc3\xa0' * 1024 self.check_expat224_utf8_bug(text) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_expat224_utf8_bug_file(self): with open(UTF8_BUG_XMLFILE, 'rb') as fp: raw = fp.read() @@ -2747,8 +2673,7 @@ def __deepcopy__(self, memo): e[:] = [E('bar')] self.assertRaises(TypeError, copy.deepcopy, e) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cyclic_gc(self): class Dummy: pass @@ -2821,8 +2746,7 @@ def test_pickle(self): self.assertEqual(len(e2), 2) self.assertEqualElements(e, e2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickle_issue18997(self): for proto in range(2, pickle.HIGHEST_PROTOCOL + 1): for dumper, loader in product(self.modules, repeat=2): @@ -3346,8 +3270,7 @@ class MyElement(ET.Element): class ElementFindTest(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_find_simple(self): e = ET.XML(SAMPLE_XML) self.assertEqual(e.find('tag').tag, 'tag') @@ -3371,8 +3294,7 @@ def test_find_simple(self): # Issue #16922 self.assertEqual(ET.XML('<tag><empty /></tag>').findtext('empty'), '') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_find_xpath(self): LINEAR_XML = ''' <body> @@ -3395,8 +3317,7 @@ def test_find_xpath(self): self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()-0]') self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()+1]') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_findall(self): e = ET.XML(SAMPLE_XML) e[2] = ET.XML(SAMPLE_SECTION) @@ -3509,8 +3430,7 @@ def test_findall(self): self.assertEqual(summarize_list(e.findall(".//tag[. = 'subtext']")), ['tag', 'tag']) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_test_find_with_ns(self): e = ET.XML(SAMPLE_XML_NS) self.assertEqual(summarize_list(e.findall('tag')), []) @@ -3521,8 +3441,7 @@ def test_test_find_with_ns(self): summarize_list(e.findall(".//{http://effbot.org/ns}tag")), ['{http://effbot.org/ns}tag'] * 3) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_findall_different_nsmaps(self): root = ET.XML(''' <a xmlns:x="X" xmlns:y="Y"> @@ -3540,8 +3459,7 @@ def test_findall_different_nsmaps(self): self.assertEqual(len(root.findall(".//xx:b", namespaces=nsmap)), 2) self.assertEqual(len(root.findall(".//b", namespaces=nsmap)), 1) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_findall_wildcard(self): root = ET.XML(''' <a xmlns:x="X" xmlns:y="Y"> @@ -3586,15 +3504,13 @@ def test_findall_wildcard(self): self.assertEqual(summarize_list(root.findall(".//{}b")), summarize_list(root.findall(".//b"))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bad_find(self): e = ET.XML(SAMPLE_XML) with self.assertRaisesRegex(SyntaxError, 'cannot use absolute path'): e.findall('/tag') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_find_through_ElementTree(self): e = ET.XML(SAMPLE_XML) self.assertEqual(ET.ElementTree(e).find('tag').tag, 'tag') @@ -3614,8 +3530,7 @@ class ElementIterTest(unittest.TestCase): def _ilist(self, elem, tag=None): return summarize_list(elem.iter(tag)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_basic(self): doc = ET.XML("<html><body>this is a <i>paragraph</i>.</body>..</html>") self.assertEqual(self._ilist(doc), ['html', 'body', 'i']) @@ -3664,8 +3579,7 @@ def test_corners(self): del a[1] self.assertEqual(self._ilist(a), ['a', 'd']) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_iter_by_tag(self): doc = ET.XML(''' <document> @@ -3730,8 +3644,7 @@ def _check_sample1_element(self, e): self.assertEqual(child.tail, 'tail') self.assertEqual(child.attrib, {}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dummy_builder(self): class BaseDummyBuilder: def close(self): @@ -3779,8 +3692,7 @@ def test_treebuilder_pi(self): self.assertEqual(b.pi('target'), (len('target'), None)) self.assertEqual(b.pi('pitarget', ' text '), (len('pitarget'), ' text ')) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_late_tail(self): # Issue #37399: The tail of an ignored comment could overwrite the text before it. class TreeBuilderSubclass(ET.TreeBuilder): @@ -3805,8 +3717,7 @@ class TreeBuilderSubclass(ET.TreeBuilder): a = parser.close() self.assertEqual(a.text, "texttail") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_late_tail_mix_pi_comments(self): # Issue #37399: The tail of an ignored comment could overwrite the text before it. # Test appending tails to comments/pis. @@ -3843,16 +3754,14 @@ class TreeBuilderSubclass(ET.TreeBuilder): self.assertEqual(a[0].tail, 'tail') self.assertEqual(a.text, "text\n") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_treebuilder_elementfactory_none(self): parser = ET.XMLParser(target=ET.TreeBuilder(element_factory=None)) parser.feed(self.sample1) e = parser.close() self._check_sample1_element(e) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_subclass(self): class MyTreeBuilder(ET.TreeBuilder): def foobar(self, x): @@ -3867,8 +3776,7 @@ def foobar(self, x): e = parser.close() self._check_sample1_element(e) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_subclass_comment_pi(self): class MyTreeBuilder(ET.TreeBuilder): def foobar(self, x): @@ -3884,8 +3792,7 @@ def foobar(self, x): e = parser.close() self._check_sample1_element(e) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_element_factory(self): lst = [] def myfactory(tag, attrib): @@ -3909,15 +3816,13 @@ def _check_element_factory_class(self, cls): self.assertIsInstance(e, cls) self._check_sample1_element(e) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_element_factory_subclass(self): class MyElement(ET.Element): pass self._check_element_factory_class(MyElement) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_element_factory_pure_python_subclass(self): # Mimic SimpleTAL's behaviour (issue #16089): both versions of # TreeBuilder should be able to cope with a subclass of the @@ -3931,8 +3836,7 @@ class MyElement(base, ValueError): pass self._check_element_factory_class(MyElement) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_doctype(self): class DoctypeParser: _doctype = None @@ -3950,8 +3854,7 @@ def close(self): ('html', '-//W3C//DTD XHTML 1.0 Transitional//EN', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd')) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_builder_lookup_errors(self): class RaisingBuilder: def __init__(self, raise_in=None, what=ValueError): @@ -3992,16 +3895,14 @@ def _check_sample_element(self, e): self.assertEqual(e[0].tag, 'line') self.assertEqual(e[0].text, '22') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_constructor_args(self): parser2 = ET.XMLParser(encoding='utf-8', target=ET.TreeBuilder()) parser2.feed(self.sample1) self._check_sample_element(parser2.close()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_subclass(self): class MyParser(ET.XMLParser): pass @@ -4009,8 +3910,7 @@ class MyParser(ET.XMLParser): parser.feed(self.sample1) self._check_sample_element(parser.close()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_doctype_warning(self): with warnings.catch_warnings(): warnings.simplefilter('error', DeprecationWarning) @@ -4018,8 +3918,7 @@ def test_doctype_warning(self): parser.feed(self.sample2) parser.close() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_subclass_doctype(self): _doctype = None class MyParserWithDoctype(ET.XMLParser): @@ -4050,8 +3949,7 @@ def doctype(self, name, pubid, system): ('html', '-//W3C//DTD XHTML 1.0 Transitional//EN', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd')) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_inherited_doctype(self): '''Ensure that ordinary usage is not deprecated (Issue 19176)''' with warnings.catch_warnings(): @@ -4063,8 +3961,7 @@ class MyParserWithoutDoctype(ET.XMLParser): parser.feed(self.sample2) parser.close() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_string(self): parser = ET.XMLParser(target=ET.TreeBuilder()) parser.feed(self.sample3) @@ -4075,8 +3972,7 @@ def test_parse_string(self): class NamespaceParseTest(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_find_with_namespace(self): nsmap = {'h': 'hello', 'f': 'foo'} doc = ET.fromstring(SAMPLE_XML_NS_ELEMS) @@ -4253,8 +4149,7 @@ def f(): e[:1] = (f() for i in range(2)) class IOTest(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_encoding(self): # Test encoding issues. elem = ET.Element("tag") @@ -4324,8 +4219,7 @@ def test_encoding(self): ("<?xml version='1.0' encoding='%s'?>\n" "<tag key=\"åöö<>\" />" % enc).encode(enc)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_filename(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''<site>\xf8</site>''')) @@ -4333,8 +4227,7 @@ def test_write_to_filename(self): with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b'''<site>ø</site>''') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_filename_with_encoding(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''<site>\xf8</site>''')) @@ -4348,8 +4241,7 @@ def test_write_to_filename_with_encoding(self): b'''<?xml version='1.0' encoding='ISO-8859-1'?>\n''' b'''<site>\xf8</site>''')) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_filename_as_unicode(self): self.addCleanup(os_helper.unlink, TESTFN) with open(TESTFN, 'w') as f: @@ -4361,8 +4253,7 @@ def test_write_to_filename_as_unicode(self): with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b"<site>\xc3\xb8</site>") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_text_file(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''<site>\xf8</site>''')) @@ -4384,8 +4275,7 @@ def test_write_to_text_file(self): with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b'''<site>\xf8</site>''') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_binary_file(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''<site>\xf8</site>''')) @@ -4395,8 +4285,7 @@ def test_write_to_binary_file(self): with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b'''<site>ø</site>''') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_binary_file_with_encoding(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''<site>\xf8</site>''')) @@ -4414,8 +4303,7 @@ def test_write_to_binary_file_with_encoding(self): b'''<?xml version='1.0' encoding='ISO-8859-1'?>\n''' b'''<site>\xf8</site>''') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_binary_file_with_bom(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''<site>\xf8</site>''')) @@ -4436,32 +4324,28 @@ def test_write_to_binary_file_with_bom(self): '''<?xml version='1.0' encoding='utf-16'?>\n''' '''<site>\xf8</site>'''.encode("utf-16")) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_from_stringio(self): tree = ET.ElementTree() stream = io.StringIO('''<?xml version="1.0"?><site></site>''') tree.parse(stream) self.assertEqual(tree.getroot().tag, 'site') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_stringio(self): tree = ET.ElementTree(ET.XML('''<site>\xf8</site>''')) stream = io.StringIO() tree.write(stream, encoding='unicode') self.assertEqual(stream.getvalue(), '''<site>\xf8</site>''') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_from_bytesio(self): tree = ET.ElementTree() raw = io.BytesIO(b'''<?xml version="1.0"?><site></site>''') tree.parse(raw) self.assertEqual(tree.getroot().tag, 'site') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_bytesio(self): tree = ET.ElementTree(ET.XML('''<site>\xf8</site>''')) raw = io.BytesIO() @@ -4471,8 +4355,7 @@ def test_write_to_bytesio(self): class dummy: pass - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_from_user_text_reader(self): stream = io.StringIO('''<?xml version="1.0"?><site></site>''') reader = self.dummy() @@ -4481,8 +4364,7 @@ def test_read_from_user_text_reader(self): tree.parse(reader) self.assertEqual(tree.getroot().tag, 'site') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_user_text_writer(self): tree = ET.ElementTree(ET.XML('''<site>\xf8</site>''')) stream = io.StringIO() @@ -4491,8 +4373,7 @@ def test_write_to_user_text_writer(self): tree.write(writer, encoding='unicode') self.assertEqual(stream.getvalue(), '''<site>\xf8</site>''') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_from_user_binary_reader(self): raw = io.BytesIO(b'''<?xml version="1.0"?><site></site>''') reader = self.dummy() @@ -4502,8 +4383,7 @@ def test_read_from_user_binary_reader(self): self.assertEqual(tree.getroot().tag, 'site') tree = ET.ElementTree() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_user_binary_writer(self): tree = ET.ElementTree(ET.XML('''<site>\xf8</site>''')) raw = io.BytesIO() @@ -4512,8 +4392,7 @@ def test_write_to_user_binary_writer(self): tree.write(writer) self.assertEqual(raw.getvalue(), b'''<site>ø</site>''') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_to_user_binary_writer_with_bom(self): tree = ET.ElementTree(ET.XML('''<site />''')) raw = io.BytesIO() @@ -4526,8 +4405,7 @@ def test_write_to_user_binary_writer_with_bom(self): '''<?xml version='1.0' encoding='utf-16'?>\n''' '''<site />'''.encode("utf-16")) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tostringlist_invariant(self): root = ET.fromstring('<tag>foo</tag>') self.assertEqual( @@ -4537,8 +4415,7 @@ def test_tostringlist_invariant(self): ET.tostring(root, 'utf-16'), b''.join(ET.tostringlist(root, 'utf-16'))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_short_empty_elements(self): root = ET.fromstring('<tag>a<x />b<y></y>c</tag>') self.assertEqual( @@ -4562,15 +4439,13 @@ def _get_error(self, s): except ET.ParseError as e: return e - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_error_position(self): self.assertEqual(self._get_error('foo').position, (1, 0)) self.assertEqual(self._get_error('<tag>&foo;</tag>').position, (1, 5)) self.assertEqual(self._get_error('foobar<').position, (1, 6)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_error_code(self): import xml.parsers.expat.errors as ERRORS self.assertEqual(self._get_error('foo').code, @@ -4580,8 +4455,7 @@ def test_error_code(self): class KeywordArgsTest(unittest.TestCase): # Test various issues with keyword arguments passed to ET.Element # constructor and methods - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_issue14818(self): x = ET.XML("<a>foo</a>") self.assertEqual(x.find('a', None), @@ -4632,8 +4506,7 @@ def test_correct_import_pyET(self): # -------------------------------------------------------------------- class BoolTest(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_warning(self): e = ET.fromstring('<a style="new"></a>') msg = ( @@ -4663,8 +4536,7 @@ class C14NTest(unittest.TestCase): # # simple roundtrip tests (from c14n.py) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_simple_roundtrip(self): # Basics self.assertEqual(c14n_roundtrip("<doc/>"), '<doc></doc>') @@ -4705,8 +4577,7 @@ def test_simple_roundtrip(self): xml = '<X xmlns="http://nps/a"><Y xmlns:b="http://nsp/b" b:targets="abc,xyz"></Y></X>' self.assertEqual(c14n_roundtrip(xml), xml) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_c14n_exclusion(self): xml = textwrap.dedent("""\ <root xmlns:x="http://example.com/x"> @@ -4787,8 +4658,7 @@ def test_c14n_exclusion(self): # note that this uses generated C14N versions of the standard ET.write # output, not roundtripped C14N (see above). - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_xml_c14n2(self): datadir = findfile("c14n-20", subdir="xmltestdata") full_path = partial(os.path.join, datadir) diff --git a/scripts/lib_updater.py b/scripts/lib_updater.py index b019521982d..8573705dd15 100755 --- a/scripts/lib_updater.py +++ b/scripts/lib_updater.py @@ -32,6 +32,7 @@ import pathlib import re import sys +import textwrap import typing if typing.TYPE_CHECKING: @@ -39,10 +40,9 @@ type Patches = dict[str, dict[str, list["PatchSpec"]]] -COL_OFFSET = 4 -INDENT1 = " " * COL_OFFSET -INDENT2 = INDENT1 * 2 +DEFAULT_INDENT = " " * 4 COMMENT = "TODO: RUSTPYTHON" +UT = "unittest" @enum.unique @@ -54,6 +54,9 @@ class UtMethod(enum.StrEnum): def _generate_next_value_(name, start, count, last_values) -> str: return name[0].lower() + name[1:] + def has_args(self) -> bool: + return self != self.ExpectedFailure + def has_cond(self) -> bool: return self.endswith(("If", "Unless")) @@ -81,17 +84,32 @@ class PatchSpec(typing.NamedTuple): cond: str | None = None reason: str = "" - def fmt(self) -> str: - prefix = f"@unittest.{self.ut_method}" - match self.ut_method: - case UtMethod.ExpectedFailure: - line = f"{prefix} # {COMMENT}; {self.reason}" - case UtMethod.ExpectedFailureIfWindows | UtMethod.Skip: - line = f'{prefix}("{COMMENT}; {self.reason}")' - case UtMethod.SkipIf | UtMethod.SkipUnless | UtMethod.ExpectedFailureIf: - line = f'{prefix}({self.cond}, "{COMMENT}; {self.reason}")' + @property + def _reason(self) -> str: + return f"{COMMENT}; {self.reason}".strip(" ;") + + @property + def _attr_node(self) -> ast.Attribute: + return ast.Attribute(value=ast.Name(id=UT), attr=self.ut_method) + + def as_ast_node(self) -> ast.Attribute | ast.Call: + if not self.ut_method.has_args(): + return self._attr_node + + args = [] + if self.cond: + args.append(ast.parse(self.cond).body[0].value) + args.append(ast.Constant(value=self._reason)) + + return ast.Call(func=self._attr_node, args=args, keywords=[]) - return line.strip().rstrip(";").strip() + def as_decorator(self) -> str: + unparsed = ast.unparse(self.as_ast_node()) + + if not self.ut_method.has_args(): + unparsed = f"{unparsed} # {self._reason}" + + return f"@{unparsed}" class PatchEntry(typing.NamedTuple): @@ -128,7 +146,7 @@ def iter_patch_entires( if ( isinstance(attr_node, ast.Name) - or getattr(attr_node.value, "id", None) != "unittest" + or getattr(attr_node.value, "id", None) != UT ): continue @@ -138,41 +156,39 @@ def iter_patch_entires( except ValueError: continue - match ut_method: - case UtMethod.ExpectedFailure: - # Search first on decorator line, then in the line before - for line in lines[ - dec_node.lineno - 1 : dec_node.lineno - 3 : -1 - ]: - if COMMENT not in line: - continue - reason = "".join(re.findall(rf"{COMMENT}.?(.*)", line)) + # If our ut_method has args then, + # we need to search for a constant that contains our `COMMENT`. + # Otherwise we need to search it in the raw source code :/ + if ut_method.has_args(): + reason = next( + ( + node.value + for node in ast.walk(dec_node) + if isinstance(node, ast.Constant) + and isinstance(node.value, str) + and COMMENT in node.value + ), + None, + ) + + # If we didn't find a constant containing <COMMENT>, + # then we didn't put this decorator + if not reason: + continue + + if ut_method.has_cond(): + cond = ast.unparse(dec_node.args[0]) + else: + # Search first on decorator line, then in the line before + for line in lines[dec_node.lineno - 1 : dec_node.lineno - 3 : -1]: + if found := re.search(rf"{COMMENT}.?(.*)", line): + reason = found.group() break - else: - continue - case _: - reason = next( - ( - node.value - for node in ast.walk(dec_node) - if isinstance(node, ast.Constant) - and isinstance(node.value, str) - and COMMENT in node.value - ), - None, - ) - - # If we didn't find a constant containing <COMMENT>, - # then we didn't put this decorator - if not reason: - continue - - if ut_method.has_cond(): - cond = ast.unparse(dec_node.args[0]) - - reason = ( - reason.replace(COMMENT, "").strip().lstrip(";").lstrip(":").strip() - ) + else: + # Didn't find our `COMMENT` :) + continue + + reason = reason.removeprefix(COMMENT).strip(";:, ") spec = PatchSpec(ut_method, cond, reason) yield cls(parent_class, fn_node.name, spec) @@ -210,7 +226,7 @@ def build_patch_dict(it: "Iterator[PatchEntry]") -> Patches: def iter_patch_lines(tree: ast.Module, patches: Patches) -> "Iterator[tuple[int, str]]": - cache = {} # Used in phase 2 + cache = {} # Used in phase 2. Stores the end line location of a class name. # Phase 1: Iterate and mark existing tests for cls_node, fn_node in iter_tests(tree): @@ -224,7 +240,8 @@ def iter_patch_lines(tree: ast.Module, patches: Patches) -> "Iterator[tuple[int, default=fn_node.lineno, ) indent = " " * fn_node.col_offset - yield (lineno - 1, "\n".join(f"{indent}{spec.fmt()}" for spec in specs)) + patch_lines = "\n".join(spec.as_decorator() for spec in specs) + yield (lineno - 1, textwrap.indent(patch_lines, indent)) # Phase 2: Iterate and mark inhereted tests for cls_name, tests in patches.items(): @@ -232,16 +249,15 @@ def iter_patch_lines(tree: ast.Module, patches: Patches) -> "Iterator[tuple[int, if not lineno: print(f"WARNING: {cls_name} does not exist in remote file", file=sys.stderr) continue + for test_name, specs in tests.items(): - patch_lines = "\n".join(f"{INDENT1}{spec.fmt()}" for spec in specs) - yield ( - lineno, - f""" -{patch_lines} -{INDENT1}def {test_name}(self): -{INDENT2}return super().{test_name}() -""".rstrip(), - ) + decorators = "\n".join(spec.as_decorator() for spec in specs) + patch_lines = f""" +{decorators} +def {test_name}(self): +{DEFAULT_INDENT}return super().{test_name}() +""".rstrip() + yield (lineno, textwrap.indent(patch_lines, DEFAULT_INDENT)) def apply_patches(contents: str, patches: Patches) -> str: @@ -309,7 +325,10 @@ def build_argparse() -> argparse.ArgumentParser: if args.patches: patches = { cls_name: { - test_name: [PatchSpec(**spec) for spec in specs] + test_name: [ + PatchSpec(**spec)._replace(ut_method=UtMethod(spec["ut_method"])) + for spec in specs + ] for test_name, specs in tests.items() } for cls_name, tests in json.loads(args.patches.read_text()).items() From cc4ebe62569b921c88a52d17c8490fd043899fd7 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 11 Sep 2025 15:43:11 +0200 Subject: [PATCH 259/819] Update`{runpy,numbers}.py` from 3.13.7 (#6141) * Update number.py from 3.13.7 * Update `runpy.py` from 3.13.7 --- Lib/numbers.py | 11 ++++++- Lib/runpy.py | 28 ++++++++--------- Lib/test/test_runpy.py | 68 +++++++++++++++++++++++++----------------- 3 files changed, 63 insertions(+), 44 deletions(-) diff --git a/Lib/numbers.py b/Lib/numbers.py index a2913e32cfa..37fddb89177 100644 --- a/Lib/numbers.py +++ b/Lib/numbers.py @@ -290,18 +290,27 @@ def conjugate(self): class Rational(Real): - """.numerator and .denominator should be in lowest terms.""" + """To Real, Rational adds numerator and denominator properties. + + The numerator and denominator values should be in lowest terms, + with a positive denominator. + """ __slots__ = () @property @abstractmethod def numerator(self): + """The numerator of a rational number in lowest terms.""" raise NotImplementedError @property @abstractmethod def denominator(self): + """The denominator of a rational number in lowest terms. + + This denominator should be positive. + """ raise NotImplementedError # Concrete implementation of Real's conversion to float. diff --git a/Lib/runpy.py b/Lib/runpy.py index c7d3d8caad1..ef54d3282ee 100644 --- a/Lib/runpy.py +++ b/Lib/runpy.py @@ -14,18 +14,20 @@ import importlib.machinery # importlib first so we can test #15386 via -m import importlib.util import io -import types import os __all__ = [ "run_module", "run_path", ] +# avoid 'import types' just for ModuleType +ModuleType = type(sys) + class _TempModule(object): """Temporarily replace a module in sys.modules with an empty namespace""" def __init__(self, mod_name): self.mod_name = mod_name - self.module = types.ModuleType(mod_name) + self.module = ModuleType(mod_name) self._saved_module = [] def __enter__(self): @@ -245,17 +247,17 @@ def _get_main_module_details(error=ImportError): sys.modules[main_name] = saved_main -def _get_code_from_file(run_name, fname): +def _get_code_from_file(fname): # Check for a compiled file first from pkgutil import read_code - decoded_path = os.path.abspath(os.fsdecode(fname)) - with io.open_code(decoded_path) as f: + code_path = os.path.abspath(fname) + with io.open_code(code_path) as f: code = read_code(f) if code is None: # That didn't work, so try it as normal source code - with io.open_code(decoded_path) as f: + with io.open_code(code_path) as f: code = compile(f.read(), fname, 'exec') - return code, fname + return code def run_path(path_name, init_globals=None, run_name=None): """Execute code located at the specified filesystem location. @@ -277,17 +279,13 @@ def run_path(path_name, init_globals=None, run_name=None): pkg_name = run_name.rpartition(".")[0] from pkgutil import get_importer importer = get_importer(path_name) - # Trying to avoid importing imp so as to not consume the deprecation warning. - is_NullImporter = False - if type(importer).__module__ == 'imp': - if type(importer).__name__ == 'NullImporter': - is_NullImporter = True - if isinstance(importer, type(None)) or is_NullImporter: + path_name = os.fsdecode(path_name) + if isinstance(importer, type(None)): # Not a valid sys.path entry, so run the code directly # execfile() doesn't help as we want to allow compiled files - code, fname = _get_code_from_file(run_name, path_name) + code = _get_code_from_file(path_name) return _run_module_code(code, init_globals, run_name, - pkg_name=pkg_name, script_name=fname) + pkg_name=pkg_name, script_name=path_name) else: # Finder is defined for path, so add it to # the start of sys.path diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index b2fee6207be..2492abff019 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -12,9 +12,16 @@ import textwrap import unittest import warnings -from test.support import no_tracing, verbose +from test.support import ( + force_not_colorized_test_class, + infinite_recursion, + no_tracing, + requires_resource, + requires_subprocess, + verbose, +) from test.support.import_helper import forget, make_legacy_pyc, unload -from test.support.os_helper import create_empty_file, temp_dir +from test.support.os_helper import create_empty_file, temp_dir, FakePath from test.support.script_helper import make_script, make_zip_script @@ -656,13 +663,14 @@ def test_basic_script(self): self._check_script(script_name, "<run_path>", script_name, script_name, expect_spec=False) - def test_basic_script_with_path_object(self): + def test_basic_script_with_pathlike_object(self): with temp_dir() as script_dir: mod_name = 'script' - script_name = pathlib.Path(self._make_test_script(script_dir, - mod_name)) - self._check_script(script_name, "<run_path>", script_name, - script_name, expect_spec=False) + script_name = self._make_test_script(script_dir, mod_name) + self._check_script(FakePath(script_name), "<run_path>", + script_name, + script_name, + expect_spec=False) def test_basic_script_no_suffix(self): with temp_dir() as script_dir: @@ -734,6 +742,7 @@ def test_zipfile_error(self): self._check_import_error(zip_name, msg) @no_tracing + @requires_resource('cpu') def test_main_recursion_error(self): with temp_dir() as script_dir, temp_dir() as dummy_dir: mod_name = '__main__' @@ -741,10 +750,10 @@ def test_main_recursion_error(self): "runpy.run_path(%r)\n") % dummy_dir script_name = self._make_test_script(script_dir, mod_name, source) zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name) - self.assertRaises(RecursionError, run_path, zip_name) + with infinite_recursion(25): + self.assertRaises(RecursionError, run_path, zip_name) - # TODO: RUSTPYTHON, detect encoding comments in files - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; detect encoding comments in files def test_encoding(self): with temp_dir() as script_dir: filename = os.path.join(script_dir, 'script.py') @@ -757,6 +766,7 @@ def test_encoding(self): self.assertEqual(result['s'], "non-ASCII: h\xe9") +@force_not_colorized_test_class class TestExit(unittest.TestCase): STATUS_CONTROL_C_EXIT = 0xC000013A EXPECTED_CODE = ( @@ -783,16 +793,19 @@ def run(self, *args, **kwargs): ) super().run(*args, **kwargs) - def assertSigInt(self, *args, **kwargs): - proc = subprocess.run(*args, **kwargs, text=True, stderr=subprocess.PIPE) - self.assertTrue(proc.stderr.endswith("\nKeyboardInterrupt\n")) + @requires_subprocess() + def assertSigInt(self, cmd, *args, **kwargs): + # Use -E to ignore PYTHONSAFEPATH + cmd = [sys.executable, '-E', *cmd] + proc = subprocess.run(cmd, *args, **kwargs, text=True, stderr=subprocess.PIPE) + self.assertTrue(proc.stderr.endswith("\nKeyboardInterrupt\n"), proc.stderr) self.assertEqual(proc.returncode, self.EXPECTED_CODE) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_file(self): - self.assertSigInt([sys.executable, self.ham]) + self.assertSigInt([self.ham]) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_file_runpy_run_module(self): tmp = self.ham.parent run_module = tmp / "run_module.py" @@ -804,9 +817,9 @@ def test_pymain_run_file_runpy_run_module(self): """ ) ) - self.assertSigInt([sys.executable, run_module], cwd=tmp) + self.assertSigInt([run_module], cwd=tmp) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_file_runpy_run_module_as_main(self): tmp = self.ham.parent run_module_as_main = tmp / "run_module_as_main.py" @@ -818,28 +831,27 @@ def test_pymain_run_file_runpy_run_module_as_main(self): """ ) ) - self.assertSigInt([sys.executable, run_module_as_main], cwd=tmp) + self.assertSigInt([run_module_as_main], cwd=tmp) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_command_run_module(self): self.assertSigInt( - [sys.executable, "-c", "import runpy; runpy.run_module('ham')"], + ["-c", "import runpy; runpy.run_module('ham')"], cwd=self.ham.parent, ) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_command(self): - self.assertSigInt([sys.executable, "-c", "import ham"], cwd=self.ham.parent) + self.assertSigInt(["-c", "import ham"], cwd=self.ham.parent) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pymain_run_stdin(self): - self.assertSigInt([sys.executable], input="import ham", cwd=self.ham.parent) + self.assertSigInt([], input="import ham", cwd=self.ham.parent) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_module(self): ham = self.ham - self.assertSigInt([sys.executable, "-m", ham.stem], cwd=ham.parent) + self.assertSigInt(["-m", ham.stem], cwd=ham.parent) if __name__ == "__main__": From 43d643ad09179ae971894fcf690b2da3f36d2511 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 13 Sep 2025 14:24:10 +0200 Subject: [PATCH 260/819] Pin CI image to `windows-2025` (#6148) For more info see see: https://github.blog/changelog/2025-07-31-github-actions-new-apis-and-windows-latest-migration-notice/ --- .github/workflows/ci.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b0ff575f1e8..0df47990549 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -116,7 +116,10 @@ jobs: timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 30 }} strategy: matrix: - os: [macos-latest, ubuntu-latest, windows-latest] + os: + - macos-latest + - ubuntu-latest + - windows-2025 # TODO: Switch to `windows-latest` on 2025/09/30 fail-fast: false steps: - uses: actions/checkout@v4 @@ -242,7 +245,10 @@ jobs: timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 30 }} strategy: matrix: - os: [macos-latest, ubuntu-latest, windows-latest] + os: + - macos-latest + - ubuntu-latest + - windows-2025 # TODO: Switch to `windows-latest` on 2025/09/30 fail-fast: false steps: - uses: actions/checkout@v4 From ca953662192d94104b4b197cb5a550d7e89b63bb Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 15 Sep 2025 14:36:11 +0200 Subject: [PATCH 261/819] Update `fnmatch` from 3.13.7 (#6149) --- Lib/fnmatch.py | 37 +++++++++++++++---------------------- Lib/test/test_fnmatch.py | 14 +++----------- Lib/test/test_shutil.py | 3 ++- 3 files changed, 20 insertions(+), 34 deletions(-) diff --git a/Lib/fnmatch.py b/Lib/fnmatch.py index fee59bf73ff..73acb1fe8d4 100644 --- a/Lib/fnmatch.py +++ b/Lib/fnmatch.py @@ -16,12 +16,6 @@ __all__ = ["filter", "fnmatch", "fnmatchcase", "translate"] -# Build a thread-safe incrementing counter to help create unique regexp group -# names across calls. -from itertools import count -_nextgroupnum = count().__next__ -del count - def fnmatch(name, pat): """Test whether FILENAME matches PATTERN. @@ -41,7 +35,7 @@ def fnmatch(name, pat): pat = os.path.normcase(pat) return fnmatchcase(name, pat) -@functools.lru_cache(maxsize=256, typed=True) +@functools.lru_cache(maxsize=32768, typed=True) def _compile_pattern(pat): if isinstance(pat, bytes): pat_str = str(pat, 'ISO-8859-1') @@ -84,6 +78,11 @@ def translate(pat): """ STAR = object() + parts = _translate(pat, STAR, '.') + return _join_translated_parts(parts, STAR) + + +def _translate(pat, STAR, QUESTION_MARK): res = [] add = res.append i, n = 0, len(pat) @@ -95,7 +94,7 @@ def translate(pat): if (not res) or res[-1] is not STAR: add(STAR) elif c == '?': - add('.') + add(QUESTION_MARK) elif c == '[': j = i if j < n and pat[j] == '!': @@ -152,9 +151,11 @@ def translate(pat): else: add(re.escape(c)) assert i == n + return res + +def _join_translated_parts(inp, STAR): # Deal with STARs. - inp = res res = [] add = res.append i, n = 0, len(inp) @@ -165,17 +166,10 @@ def translate(pat): # Now deal with STAR fixed STAR fixed ... # For an interior `STAR fixed` pairing, we want to do a minimal # .*? match followed by `fixed`, with no possibility of backtracking. - # We can't spell that directly, but can trick it into working by matching - # .*?fixed - # in a lookahead assertion, save the matched part in a group, then - # consume that group via a backreference. If the overall match fails, - # the lookahead assertion won't try alternatives. So the translation is: - # (?=(?P<name>.*?fixed))(?P=name) - # Group names are created as needed: g0, g1, g2, ... - # The numbers are obtained from _nextgroupnum() to ensure they're unique - # across calls and across threads. This is because people rely on the - # undocumented ability to join multiple translate() results together via - # "|" to build large regexps matching "one of many" shell patterns. + # Atomic groups ("(?>...)") allow us to spell that directly. + # Note: people rely on the undocumented ability to join multiple + # translate() results together via "|" to build large regexps matching + # "one of many" shell patterns. while i < n: assert inp[i] is STAR i += 1 @@ -192,8 +186,7 @@ def translate(pat): add(".*") add(fixed) else: - groupnum = _nextgroupnum() - add(f"(?=(?P<g{groupnum}>.*?{fixed}))(?P=g{groupnum})") + add(f"(?>.*?{fixed})") assert i == n res = "".join(res) return fr'(?s:{res})\Z' diff --git a/Lib/test/test_fnmatch.py b/Lib/test/test_fnmatch.py index 092cd562850..b977b8f8eb0 100644 --- a/Lib/test/test_fnmatch.py +++ b/Lib/test/test_fnmatch.py @@ -71,7 +71,7 @@ def test_fnmatchcase(self): check('usr/bin', 'usr\\bin', False, fnmatchcase) check('usr\\bin', 'usr\\bin', True, fnmatchcase) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') def test_bytes(self): self.check_match(b'test', b'te*') self.check_match(b'test\xff', b'te*\xff') @@ -239,17 +239,9 @@ def test_translate(self): self.assertEqual(translate('A*********?[?]?'), r'(?s:A.*.[?].)\Z') # fancy translation to prevent exponential-time match failure t = translate('**a*a****a') - digits = re.findall(r'\d+', t) - self.assertEqual(len(digits), 4) - self.assertEqual(digits[0], digits[1]) - self.assertEqual(digits[2], digits[3]) - g1 = f"g{digits[0]}" # e.g., group name "g4" - g2 = f"g{digits[2]}" # e.g., group name "g5" - self.assertEqual(t, - fr'(?s:(?=(?P<{g1}>.*?a))(?P={g1})(?=(?P<{g2}>.*?a))(?P={g2}).*a)\Z') + self.assertEqual(t, r'(?s:(?>.*?a)(?>.*?a).*a)\Z') # and try pasting multiple translate results - it's an undocumented - # feature that this works; all the pain of generating unique group - # names across calls exists to support this + # feature that this works r1 = translate('**a**a**a*') r2 = translate('**b**b**b*') r3 = translate('*c*c*c*') diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 8a426e338a7..1fe2aef39de 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -196,6 +196,7 @@ def test_rmtree_works_on_bytes(self): self.assertIsInstance(victim, bytes) shutil.rmtree(victim) + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; flaky') @os_helper.skip_unless_symlink def test_rmtree_fails_on_symlink_onerror(self): tmp = self.mkdtemp() @@ -1477,7 +1478,7 @@ def test_dont_copy_file_onto_link_to_itself(self): finally: shutil.rmtree(TESTFN, ignore_errors=True) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; AssertionError: SameFileError not raised for copyfile') @os_helper.skip_unless_symlink def test_dont_copy_file_onto_symlink_to_itself(self): # bug 851123. From 6ead82154e06a4490c0a87b966db5528eee13070 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 16 Sep 2025 02:35:08 +0200 Subject: [PATCH 262/819] Update `glob` from 3.13.7 (#6152) --- Lib/glob.py | 301 +++++++++++++++++++++++++++++++++++++++++- Lib/test/test_glob.py | 194 +++++++++++++++++++++++---- 2 files changed, 462 insertions(+), 33 deletions(-) diff --git a/Lib/glob.py b/Lib/glob.py index 50beef37f45..c506e0e2157 100644 --- a/Lib/glob.py +++ b/Lib/glob.py @@ -4,11 +4,14 @@ import os import re import fnmatch +import functools import itertools +import operator import stat import sys -__all__ = ["glob", "iglob", "escape"] + +__all__ = ["glob", "iglob", "escape", "translate"] def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False, include_hidden=False): @@ -104,8 +107,8 @@ def _iglob(pathname, root_dir, dir_fd, recursive, dironly, def _glob1(dirname, pattern, dir_fd, dironly, include_hidden=False): names = _listdir(dirname, dir_fd, dironly) - if include_hidden or not _ishidden(pattern): - names = (x for x in names if include_hidden or not _ishidden(x)) + if not (include_hidden or _ishidden(pattern)): + names = (x for x in names if not _ishidden(x)) return fnmatch.filter(names, pattern) def _glob0(dirname, basename, dir_fd, dironly, include_hidden=False): @@ -119,12 +122,19 @@ def _glob0(dirname, basename, dir_fd, dironly, include_hidden=False): return [basename] return [] -# Following functions are not public but can be used by third-party code. +_deprecated_function_message = ( + "{name} is deprecated and will be removed in Python {remove}. Use " + "glob.glob and pass a directory to its root_dir argument instead." +) def glob0(dirname, pattern): + import warnings + warnings._deprecated("glob.glob0", _deprecated_function_message, remove=(3, 15)) return _glob0(dirname, pattern, None, False) def glob1(dirname, pattern): + import warnings + warnings._deprecated("glob.glob1", _deprecated_function_message, remove=(3, 15)) return _glob1(dirname, pattern, None, False) # This helper function recursively yields relative pathnames inside a literal @@ -249,4 +259,287 @@ def escape(pathname): return drive + pathname +_special_parts = ('', '.', '..') _dir_open_flags = os.O_RDONLY | getattr(os, 'O_DIRECTORY', 0) +_no_recurse_symlinks = object() + + +def translate(pat, *, recursive=False, include_hidden=False, seps=None): + """Translate a pathname with shell wildcards to a regular expression. + + If `recursive` is true, the pattern segment '**' will match any number of + path segments. + + If `include_hidden` is true, wildcards can match path segments beginning + with a dot ('.'). + + If a sequence of separator characters is given to `seps`, they will be + used to split the pattern into segments and match path separators. If not + given, os.path.sep and os.path.altsep (where available) are used. + """ + if not seps: + if os.path.altsep: + seps = (os.path.sep, os.path.altsep) + else: + seps = os.path.sep + escaped_seps = ''.join(map(re.escape, seps)) + any_sep = f'[{escaped_seps}]' if len(seps) > 1 else escaped_seps + not_sep = f'[^{escaped_seps}]' + if include_hidden: + one_last_segment = f'{not_sep}+' + one_segment = f'{one_last_segment}{any_sep}' + any_segments = f'(?:.+{any_sep})?' + any_last_segments = '.*' + else: + one_last_segment = f'[^{escaped_seps}.]{not_sep}*' + one_segment = f'{one_last_segment}{any_sep}' + any_segments = f'(?:{one_segment})*' + any_last_segments = f'{any_segments}(?:{one_last_segment})?' + + results = [] + parts = re.split(any_sep, pat) + last_part_idx = len(parts) - 1 + for idx, part in enumerate(parts): + if part == '*': + results.append(one_segment if idx < last_part_idx else one_last_segment) + elif recursive and part == '**': + if idx < last_part_idx: + if parts[idx + 1] != '**': + results.append(any_segments) + else: + results.append(any_last_segments) + else: + if part: + if not include_hidden and part[0] in '*?': + results.append(r'(?!\.)') + results.extend(fnmatch._translate(part, f'{not_sep}*', not_sep)) + if idx < last_part_idx: + results.append(any_sep) + res = ''.join(results) + return fr'(?s:{res})\Z' + + +@functools.lru_cache(maxsize=512) +def _compile_pattern(pat, sep, case_sensitive, recursive=True): + """Compile given glob pattern to a re.Pattern object (observing case + sensitivity).""" + flags = re.NOFLAG if case_sensitive else re.IGNORECASE + regex = translate(pat, recursive=recursive, include_hidden=True, seps=sep) + return re.compile(regex, flags=flags).match + + +class _Globber: + """Class providing shell-style pattern matching and globbing. + """ + + def __init__(self, sep, case_sensitive, case_pedantic=False, recursive=False): + self.sep = sep + self.case_sensitive = case_sensitive + self.case_pedantic = case_pedantic + self.recursive = recursive + + # Low-level methods + + lstat = operator.methodcaller('lstat') + add_slash = operator.methodcaller('joinpath', '') + + @staticmethod + def scandir(path): + """Emulates os.scandir(), which returns an object that can be used as + a context manager. This method is called by walk() and glob(). + """ + return contextlib.nullcontext(path.iterdir()) + + @staticmethod + def concat_path(path, text): + """Appends text to the given path. + """ + return path.with_segments(path._raw_path + text) + + @staticmethod + def parse_entry(entry): + """Returns the path of an entry yielded from scandir(). + """ + return entry + + # High-level methods + + def compile(self, pat): + return _compile_pattern(pat, self.sep, self.case_sensitive, self.recursive) + + def selector(self, parts): + """Returns a function that selects from a given path, walking and + filtering according to the glob-style pattern parts in *parts*. + """ + if not parts: + return self.select_exists + part = parts.pop() + if self.recursive and part == '**': + selector = self.recursive_selector + elif part in _special_parts: + selector = self.special_selector + elif not self.case_pedantic and magic_check.search(part) is None: + selector = self.literal_selector + else: + selector = self.wildcard_selector + return selector(part, parts) + + def special_selector(self, part, parts): + """Returns a function that selects special children of the given path. + """ + select_next = self.selector(parts) + + def select_special(path, exists=False): + path = self.concat_path(self.add_slash(path), part) + return select_next(path, exists) + return select_special + + def literal_selector(self, part, parts): + """Returns a function that selects a literal descendant of a path. + """ + + # Optimization: consume and join any subsequent literal parts here, + # rather than leaving them for the next selector. This reduces the + # number of string concatenation operations and calls to add_slash(). + while parts and magic_check.search(parts[-1]) is None: + part += self.sep + parts.pop() + + select_next = self.selector(parts) + + def select_literal(path, exists=False): + path = self.concat_path(self.add_slash(path), part) + return select_next(path, exists=False) + return select_literal + + def wildcard_selector(self, part, parts): + """Returns a function that selects direct children of a given path, + filtering by pattern. + """ + + match = None if part == '*' else self.compile(part) + dir_only = bool(parts) + if dir_only: + select_next = self.selector(parts) + + def select_wildcard(path, exists=False): + try: + # We must close the scandir() object before proceeding to + # avoid exhausting file descriptors when globbing deep trees. + with self.scandir(path) as scandir_it: + entries = list(scandir_it) + except OSError: + pass + else: + for entry in entries: + if match is None or match(entry.name): + if dir_only: + try: + if not entry.is_dir(): + continue + except OSError: + continue + entry_path = self.parse_entry(entry) + if dir_only: + yield from select_next(entry_path, exists=True) + else: + yield entry_path + return select_wildcard + + def recursive_selector(self, part, parts): + """Returns a function that selects a given path and all its children, + recursively, filtering by pattern. + """ + # Optimization: consume following '**' parts, which have no effect. + while parts and parts[-1] == '**': + parts.pop() + + # Optimization: consume and join any following non-special parts here, + # rather than leaving them for the next selector. They're used to + # build a regular expression, which we use to filter the results of + # the recursive walk. As a result, non-special pattern segments + # following a '**' wildcard don't require additional filesystem access + # to expand. + follow_symlinks = self.recursive is not _no_recurse_symlinks + if follow_symlinks: + while parts and parts[-1] not in _special_parts: + part += self.sep + parts.pop() + + match = None if part == '**' else self.compile(part) + dir_only = bool(parts) + select_next = self.selector(parts) + + def select_recursive(path, exists=False): + path = self.add_slash(path) + match_pos = len(str(path)) + if match is None or match(str(path), match_pos): + yield from select_next(path, exists) + stack = [path] + while stack: + yield from select_recursive_step(stack, match_pos) + + def select_recursive_step(stack, match_pos): + path = stack.pop() + try: + # We must close the scandir() object before proceeding to + # avoid exhausting file descriptors when globbing deep trees. + with self.scandir(path) as scandir_it: + entries = list(scandir_it) + except OSError: + pass + else: + for entry in entries: + is_dir = False + try: + if entry.is_dir(follow_symlinks=follow_symlinks): + is_dir = True + except OSError: + pass + + if is_dir or not dir_only: + entry_path = self.parse_entry(entry) + if match is None or match(str(entry_path), match_pos): + if dir_only: + yield from select_next(entry_path, exists=True) + else: + # Optimization: directly yield the path if this is + # last pattern part. + yield entry_path + if is_dir: + stack.append(entry_path) + + return select_recursive + + def select_exists(self, path, exists=False): + """Yields the given path, if it exists. + """ + if exists: + # Optimization: this path is already known to exist, e.g. because + # it was returned from os.scandir(), so we skip calling lstat(). + yield path + else: + try: + self.lstat(path) + yield path + except OSError: + pass + + +class _StringGlobber(_Globber): + lstat = staticmethod(os.lstat) + scandir = staticmethod(os.scandir) + parse_entry = operator.attrgetter('path') + concat_path = operator.add + + if os.name == 'nt': + @staticmethod + def add_slash(pathname): + tail = os.path.splitroot(pathname)[2] + if not tail or tail[-1] in '\\/': + return pathname + return f'{pathname}\\' + else: + @staticmethod + def add_slash(pathname): + if not pathname or pathname[-1] == '/': + return pathname + return f'{pathname}/' diff --git a/Lib/test/test_glob.py b/Lib/test/test_glob.py index e11ea81a7d4..c3fb8939a69 100644 --- a/Lib/test/test_glob.py +++ b/Lib/test/test_glob.py @@ -1,9 +1,12 @@ import glob import os +import re import shutil import sys import unittest +import warnings +from test.support import is_wasi, Py_DEBUG from test.support.os_helper import (TESTFN, skip_unless_symlink, can_symlink, create_empty_file, change_cwd) @@ -167,37 +170,45 @@ def test_glob_directory_names(self): self.norm('aab', 'F')]) def test_glob_directory_with_trailing_slash(self): - # Patterns ending with a slash shouldn't match non-dirs - res = glob.glob(self.norm('Z*Z') + os.sep) - self.assertEqual(res, []) - res = glob.glob(self.norm('ZZZ') + os.sep) - self.assertEqual(res, []) - # When there is a wildcard pattern which ends with os.sep, glob() - # doesn't blow up. - res = glob.glob(self.norm('aa*') + os.sep) - self.assertEqual(len(res), 2) - # either of these results is reasonable - self.assertIn(set(res), [ - {self.norm('aaa'), self.norm('aab')}, - {self.norm('aaa') + os.sep, self.norm('aab') + os.sep}, - ]) + seps = (os.sep, os.altsep) if os.altsep else (os.sep,) + for sep in seps: + # Patterns ending with a slash shouldn't match non-dirs + self.assertEqual(glob.glob(self.norm('Z*Z') + sep), []) + self.assertEqual(glob.glob(self.norm('ZZZ') + sep), []) + self.assertEqual(glob.glob(self.norm('aaa') + sep), + [self.norm('aaa') + sep]) + # Preserving the redundant separators is an implementation detail. + self.assertEqual(glob.glob(self.norm('aaa') + sep*2), + [self.norm('aaa') + sep*2]) + # When there is a wildcard pattern which ends with a pathname + # separator, glob() doesn't blow. + # The result should end with the pathname separator. + # Normalizing the trailing separator is an implementation detail. + eq = self.assertSequencesEqual_noorder + eq(glob.glob(self.norm('aa*') + sep), + [self.norm('aaa') + os.sep, self.norm('aab') + os.sep]) + # Stripping the redundant separators is an implementation detail. + eq(glob.glob(self.norm('aa*') + sep*2), + [self.norm('aaa') + os.sep, self.norm('aab') + os.sep]) def test_glob_bytes_directory_with_trailing_slash(self): # Same as test_glob_directory_with_trailing_slash, but with a # bytes argument. - res = glob.glob(os.fsencode(self.norm('Z*Z') + os.sep)) - self.assertEqual(res, []) - res = glob.glob(os.fsencode(self.norm('ZZZ') + os.sep)) - self.assertEqual(res, []) - res = glob.glob(os.fsencode(self.norm('aa*') + os.sep)) - self.assertEqual(len(res), 2) - # either of these results is reasonable - self.assertIn(set(res), [ - {os.fsencode(self.norm('aaa')), - os.fsencode(self.norm('aab'))}, - {os.fsencode(self.norm('aaa') + os.sep), - os.fsencode(self.norm('aab') + os.sep)}, - ]) + seps = (os.sep, os.altsep) if os.altsep else (os.sep,) + for sep in seps: + self.assertEqual(glob.glob(os.fsencode(self.norm('Z*Z') + sep)), []) + self.assertEqual(glob.glob(os.fsencode(self.norm('ZZZ') + sep)), []) + self.assertEqual(glob.glob(os.fsencode(self.norm('aaa') + sep)), + [os.fsencode(self.norm('aaa') + sep)]) + self.assertEqual(glob.glob(os.fsencode(self.norm('aaa') + sep*2)), + [os.fsencode(self.norm('aaa') + sep*2)]) + eq = self.assertSequencesEqual_noorder + eq(glob.glob(os.fsencode(self.norm('aa*') + sep)), + [os.fsencode(self.norm('aaa') + os.sep), + os.fsencode(self.norm('aab') + os.sep)]) + eq(glob.glob(os.fsencode(self.norm('aa*') + sep*2)), + [os.fsencode(self.norm('aaa') + os.sep), + os.fsencode(self.norm('aab') + os.sep)]) @skip_unless_symlink def test_glob_symlinks(self): @@ -205,8 +216,7 @@ def test_glob_symlinks(self): eq(self.glob('sym3'), [self.norm('sym3')]) eq(self.glob('sym3', '*'), [self.norm('sym3', 'EF'), self.norm('sym3', 'efg')]) - self.assertIn(self.glob('sym3' + os.sep), - [[self.norm('sym3')], [self.norm('sym3') + os.sep]]) + eq(self.glob('sym3' + os.sep), [self.norm('sym3') + os.sep]) eq(self.glob('*', '*F'), [self.norm('aaa', 'zzzF'), self.norm('aab', 'F'), self.norm('sym3', 'EF')]) @@ -364,6 +374,8 @@ def test_glob_named_pipe(self): self.assertEqual(self.rglob('mypipe', 'sub'), []) self.assertEqual(self.rglob('mypipe', '*'), []) + + @unittest.skipIf(is_wasi and Py_DEBUG, "requires too much stack") def test_glob_many_open_files(self): depth = 30 base = os.path.join(self.tempdir, 'deep') @@ -381,10 +393,134 @@ def test_glob_many_open_files(self): for it in iters: self.assertEqual(next(it), p) + def test_glob0(self): + with self.assertWarns(DeprecationWarning): + glob.glob0(self.tempdir, 'a') + + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + eq = self.assertSequencesEqual_noorder + eq(glob.glob0(self.tempdir, 'a'), ['a']) + eq(glob.glob0(self.tempdir, '.bb'), ['.bb']) + eq(glob.glob0(self.tempdir, '.b*'), []) + eq(glob.glob0(self.tempdir, 'b'), []) + eq(glob.glob0(self.tempdir, '?'), []) + eq(glob.glob0(self.tempdir, '*a'), []) + eq(glob.glob0(self.tempdir, 'a*'), []) + + def test_glob1(self): + with self.assertWarns(DeprecationWarning): + glob.glob1(self.tempdir, 'a') + + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + eq = self.assertSequencesEqual_noorder + eq(glob.glob1(self.tempdir, 'a'), ['a']) + eq(glob.glob1(self.tempdir, '.bb'), ['.bb']) + eq(glob.glob1(self.tempdir, '.b*'), ['.bb']) + eq(glob.glob1(self.tempdir, 'b'), []) + eq(glob.glob1(self.tempdir, '?'), ['a']) + eq(glob.glob1(self.tempdir, '*a'), ['a', 'aaa']) + eq(glob.glob1(self.tempdir, 'a*'), ['a', 'aaa', 'aab']) + + def test_translate_matching(self): + match = re.compile(glob.translate('*')).match + self.assertIsNotNone(match('foo')) + self.assertIsNotNone(match('foo.bar')) + self.assertIsNone(match('.foo')) + match = re.compile(glob.translate('.*')).match + self.assertIsNotNone(match('.foo')) + match = re.compile(glob.translate('**', recursive=True)).match + self.assertIsNotNone(match('foo')) + self.assertIsNone(match('.foo')) + self.assertIsNotNone(match(os.path.join('foo', 'bar'))) + self.assertIsNone(match(os.path.join('foo', '.bar'))) + self.assertIsNone(match(os.path.join('.foo', 'bar'))) + self.assertIsNone(match(os.path.join('.foo', '.bar'))) + match = re.compile(glob.translate('**/*', recursive=True)).match + self.assertIsNotNone(match(os.path.join('foo', 'bar'))) + self.assertIsNone(match(os.path.join('foo', '.bar'))) + self.assertIsNone(match(os.path.join('.foo', 'bar'))) + self.assertIsNone(match(os.path.join('.foo', '.bar'))) + match = re.compile(glob.translate('*/**', recursive=True)).match + self.assertIsNotNone(match(os.path.join('foo', 'bar'))) + self.assertIsNone(match(os.path.join('foo', '.bar'))) + self.assertIsNone(match(os.path.join('.foo', 'bar'))) + self.assertIsNone(match(os.path.join('.foo', '.bar'))) + match = re.compile(glob.translate('**/.bar', recursive=True)).match + self.assertIsNotNone(match(os.path.join('foo', '.bar'))) + self.assertIsNone(match(os.path.join('.foo', '.bar'))) + match = re.compile(glob.translate('**/*.*', recursive=True)).match + self.assertIsNone(match(os.path.join('foo', 'bar'))) + self.assertIsNone(match(os.path.join('foo', '.bar'))) + self.assertIsNotNone(match(os.path.join('foo', 'bar.txt'))) + self.assertIsNone(match(os.path.join('foo', '.bar.txt'))) + + def test_translate(self): + def fn(pat): + return glob.translate(pat, seps='/') + self.assertEqual(fn('foo'), r'(?s:foo)\Z') + self.assertEqual(fn('foo/bar'), r'(?s:foo/bar)\Z') + self.assertEqual(fn('*'), r'(?s:[^/.][^/]*)\Z') + self.assertEqual(fn('?'), r'(?s:(?!\.)[^/])\Z') + self.assertEqual(fn('a*'), r'(?s:a[^/]*)\Z') + self.assertEqual(fn('*a'), r'(?s:(?!\.)[^/]*a)\Z') + self.assertEqual(fn('.*'), r'(?s:\.[^/]*)\Z') + self.assertEqual(fn('?aa'), r'(?s:(?!\.)[^/]aa)\Z') + self.assertEqual(fn('aa?'), r'(?s:aa[^/])\Z') + self.assertEqual(fn('aa[ab]'), r'(?s:aa[ab])\Z') + self.assertEqual(fn('**'), r'(?s:(?!\.)[^/]*)\Z') + self.assertEqual(fn('***'), r'(?s:(?!\.)[^/]*)\Z') + self.assertEqual(fn('a**'), r'(?s:a[^/]*)\Z') + self.assertEqual(fn('**b'), r'(?s:(?!\.)[^/]*b)\Z') + self.assertEqual(fn('/**/*/*.*/**'), + r'(?s:/(?!\.)[^/]*/[^/.][^/]*/(?!\.)[^/]*\.[^/]*/(?!\.)[^/]*)\Z') + + def test_translate_include_hidden(self): + def fn(pat): + return glob.translate(pat, include_hidden=True, seps='/') + self.assertEqual(fn('foo'), r'(?s:foo)\Z') + self.assertEqual(fn('foo/bar'), r'(?s:foo/bar)\Z') + self.assertEqual(fn('*'), r'(?s:[^/]+)\Z') + self.assertEqual(fn('?'), r'(?s:[^/])\Z') + self.assertEqual(fn('a*'), r'(?s:a[^/]*)\Z') + self.assertEqual(fn('*a'), r'(?s:[^/]*a)\Z') + self.assertEqual(fn('.*'), r'(?s:\.[^/]*)\Z') + self.assertEqual(fn('?aa'), r'(?s:[^/]aa)\Z') + self.assertEqual(fn('aa?'), r'(?s:aa[^/])\Z') + self.assertEqual(fn('aa[ab]'), r'(?s:aa[ab])\Z') + self.assertEqual(fn('**'), r'(?s:[^/]*)\Z') + self.assertEqual(fn('***'), r'(?s:[^/]*)\Z') + self.assertEqual(fn('a**'), r'(?s:a[^/]*)\Z') + self.assertEqual(fn('**b'), r'(?s:[^/]*b)\Z') + self.assertEqual(fn('/**/*/*.*/**'), r'(?s:/[^/]*/[^/]+/[^/]*\.[^/]*/[^/]*)\Z') + + def test_translate_recursive(self): + def fn(pat): + return glob.translate(pat, recursive=True, include_hidden=True, seps='/') + self.assertEqual(fn('*'), r'(?s:[^/]+)\Z') + self.assertEqual(fn('?'), r'(?s:[^/])\Z') + self.assertEqual(fn('**'), r'(?s:.*)\Z') + self.assertEqual(fn('**/**'), r'(?s:.*)\Z') + self.assertEqual(fn('***'), r'(?s:[^/]*)\Z') + self.assertEqual(fn('a**'), r'(?s:a[^/]*)\Z') + self.assertEqual(fn('**b'), r'(?s:[^/]*b)\Z') + self.assertEqual(fn('/**/*/*.*/**'), r'(?s:/(?:.+/)?[^/]+/[^/]*\.[^/]*/.*)\Z') + + def test_translate_seps(self): + def fn(pat): + return glob.translate(pat, recursive=True, include_hidden=True, seps=['/', '\\']) + self.assertEqual(fn('foo/bar\\baz'), r'(?s:foo[/\\]bar[/\\]baz)\Z') + self.assertEqual(fn('**/*'), r'(?s:(?:.+[/\\])?[^/\\]+)\Z') + @skip_unless_symlink class SymlinkLoopGlobTests(unittest.TestCase): + # gh-109959: On Linux, glob._isdir() and glob._lexists() can return False + # randomly when checking the "link/" symbolic link. + # https://github.com/python/cpython/issues/109959#issuecomment-2577550700 + @unittest.skip("flaky test") def test_selflink(self): tempdir = TESTFN + "_dir" os.makedirs(tempdir) From b666c52df96d41cd246a99eaabd4b46077bf6088 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 16 Sep 2025 21:49:54 +0900 Subject: [PATCH 263/819] code object linetable (#6150) * Code.replace * implement linetable --- Lib/test/test_code.py | 12 - Lib/test/test_compile.py | 2 - compiler/codegen/src/ir.rs | 139 +++++++++- compiler/core/src/bytecode.rs | 77 ++++++ compiler/core/src/marshal.rs | 16 ++ vm/src/builtins/code.rs | 473 +++++++++++++++++++++++++++++++++- 6 files changed, 699 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 6b0dc09e286..b7e5784b48c 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -347,8 +347,6 @@ def func(arg): newcode = code.replace(co_name="func") # Should not raise SystemError self.assertEqual(code, newcode) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_empty_linetable(self): def func(): pass @@ -468,8 +466,6 @@ def f(): # co_positions behavior when info is missing. - # TODO: RUSTPYTHON - @unittest.expectedFailure # @requires_debug_ranges() def test_co_positions_empty_linetable(self): def func(): @@ -480,8 +476,6 @@ def func(): self.assertIsNone(line) self.assertEqual(end_line, new_code.co_firstlineno + 1) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_code_equality(self): def f(): try: @@ -522,8 +516,6 @@ def test_code_hash_uses_order(self): self.assertNotEqual(c, swapped) self.assertNotEqual(hash(c), hash(swapped)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_code_hash_uses_bytecode(self): c = (lambda x, y: x + y).__code__ d = (lambda x, y: x * y).__code__ @@ -735,8 +727,6 @@ def check_positions(self, func): self.assertEqual(l1, l2) self.assertEqual(len(pos1), len(pos2)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_positions(self): self.check_positions(parse_location_table) self.check_positions(misshappen) @@ -751,8 +741,6 @@ def check_lines(self, func): self.assertEqual(l1, l2) self.assertEqual(len(lines1), len(lines2)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_lines(self): self.check_lines(parse_location_table) self.check_lines(misshappen) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 5b07b3c85b7..27bbe0b64ab 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -885,8 +885,6 @@ def foo(x): self.assertIn('LOAD_ATTR', instructions) self.assertIn('PRECALL', instructions) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_lineno_procedure_call(self): def call(): ( diff --git a/compiler/codegen/src/ir.rs b/compiler/codegen/src/ir.rs index 1cc59dd656a..31c89260913 100644 --- a/compiler/codegen/src/ir.rs +++ b/compiler/codegen/src/ir.rs @@ -5,7 +5,7 @@ use rustpython_compiler_core::{ OneIndexed, SourceLocation, bytecode::{ CodeFlags, CodeObject, CodeUnit, ConstantData, InstrDisplayContext, Instruction, Label, - OpArg, + OpArg, PyCodeLocationInfoKind, }, }; @@ -72,6 +72,7 @@ pub struct InstructionInfo { pub target: BlockIdx, // pub range: TextRange, pub location: SourceLocation, + // TODO: end_location for debug ranges } // spell-checker:ignore petgraph @@ -199,6 +200,9 @@ impl CodeInfo { locations.clear() } + // Generate linetable from locations + let linetable = generate_linetable(&locations, first_line_number.get() as i32); + Ok(CodeObject { flags, posonlyarg_count, @@ -218,6 +222,8 @@ impl CodeInfo { cellvars: cellvar_cache.into_iter().collect(), freevars: freevar_cache.into_iter().collect(), cell2arg, + linetable, + exceptiontable: Box::new([]), // TODO: Generate actual exception table }) } @@ -388,3 +394,134 @@ fn iter_blocks(blocks: &[Block]) -> impl Iterator<Item = (BlockIdx, &Block)> + ' Some((idx, b)) }) } + +/// Generate CPython 3.11+ format linetable from source locations +fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8]> { + if locations.is_empty() { + return Box::new([]); + } + + let mut linetable = Vec::new(); + // Initialize prev_line to first_line + // The first entry's delta is relative to co_firstlineno + let mut prev_line = first_line; + let mut i = 0; + + while i < locations.len() { + let loc = &locations[i]; + + // Count consecutive instructions with the same location + let mut length = 1; + while i + length < locations.len() && locations[i + length] == locations[i] { + length += 1; + } + + // Process in chunks of up to 8 instructions + while length > 0 { + let entry_length = length.min(8); + + // Get line and column information + // SourceLocation always has row and column (both are OneIndexed) + let line = loc.row.get() as i32; + let col = (loc.column.get() as i32) - 1; // Convert 1-based to 0-based + + let line_delta = line - prev_line; + + // Choose the appropriate encoding based on line delta and column info + // Note: SourceLocation always has valid column, so we never get NO_COLUMNS case + if line_delta == 0 { + let end_col = col; // Use same column for end (no range info available) + + if col < 80 && end_col - col < 16 && end_col >= col { + // Short form (codes 0-9) for common cases + let code = (col / 8).min(9) as u8; // Short0 to Short9 + linetable.push(0x80 | (code << 3) | ((entry_length - 1) as u8)); + let col_byte = (((col % 8) as u8) << 4) | ((end_col - col) as u8 & 0xf); + linetable.push(col_byte); + } else if col < 128 && end_col < 128 { + // One-line form (code 10) for same line + linetable.push( + 0x80 | ((PyCodeLocationInfoKind::OneLine0 as u8) << 3) + | ((entry_length - 1) as u8), + ); + linetable.push(col as u8); + linetable.push(end_col as u8); + } else { + // Long form for columns >= 128 + linetable.push( + 0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3) + | ((entry_length - 1) as u8), + ); + write_signed_varint(&mut linetable, 0); // line_delta = 0 + write_varint(&mut linetable, 0); // end_line delta = 0 + write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding + write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1 + } + } else if line_delta > 0 && line_delta < 3 + /* && column.is_some() */ + { + // One-line form (codes 11-12) for line deltas 1-2 + let end_col = col; // Use same column for end + + if col < 128 && end_col < 128 { + let code = (PyCodeLocationInfoKind::OneLine0 as u8) + (line_delta as u8); // 11 for delta=1, 12 for delta=2 + linetable.push(0x80 | (code << 3) | ((entry_length - 1) as u8)); + linetable.push(col as u8); + linetable.push(end_col as u8); + } else { + // Long form for columns >= 128 or negative line delta + linetable.push( + 0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3) + | ((entry_length - 1) as u8), + ); + write_signed_varint(&mut linetable, line_delta); + write_varint(&mut linetable, 0); // end_line delta = 0 + write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding + write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1 + } + } else { + // Long form (code 14) for all other cases + // This handles: line_delta < 0, line_delta >= 3, or columns >= 128 + let end_col = col; // Use same column for end + linetable.push( + 0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3) | ((entry_length - 1) as u8), + ); + write_signed_varint(&mut linetable, line_delta); + write_varint(&mut linetable, 0); // end_line delta = 0 + write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding + write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1 + } + + prev_line = line; + length -= entry_length; + i += entry_length; + } + } + + linetable.into_boxed_slice() +} + +/// Write a variable-length unsigned integer (6-bit chunks) +/// Returns the number of bytes written +fn write_varint(buf: &mut Vec<u8>, mut val: u32) -> usize { + let start_len = buf.len(); + while val >= 64 { + buf.push(0x40 | (val & 0x3f) as u8); + val >>= 6; + } + buf.push(val as u8); + buf.len() - start_len +} + +/// Write a variable-length signed integer +/// Returns the number of bytes written +fn write_signed_varint(buf: &mut Vec<u8>, val: i32) -> usize { + let uval = if val < 0 { + // (unsigned int)(-val) has an undefined behavior for INT_MIN + // So we use (0 - val as u32) to handle it correctly + ((0u32.wrapping_sub(val as u32)) << 1) | 1 + } else { + (val as u32) << 1 + }; + write_varint(buf, uval) +} diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index b38c5995085..c2ce4e52c07 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -33,6 +33,75 @@ pub enum ResumeType { AfterAwait = 3, } +/// CPython 3.11+ linetable location info codes +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum PyCodeLocationInfoKind { + // Short forms are 0 to 9 + Short0 = 0, + Short1 = 1, + Short2 = 2, + Short3 = 3, + Short4 = 4, + Short5 = 5, + Short6 = 6, + Short7 = 7, + Short8 = 8, + Short9 = 9, + // One line forms are 10 to 12 + OneLine0 = 10, + OneLine1 = 11, + OneLine2 = 12, + NoColumns = 13, + Long = 14, + None = 15, +} + +impl PyCodeLocationInfoKind { + pub fn from_code(code: u8) -> Option<Self> { + match code { + 0 => Some(Self::Short0), + 1 => Some(Self::Short1), + 2 => Some(Self::Short2), + 3 => Some(Self::Short3), + 4 => Some(Self::Short4), + 5 => Some(Self::Short5), + 6 => Some(Self::Short6), + 7 => Some(Self::Short7), + 8 => Some(Self::Short8), + 9 => Some(Self::Short9), + 10 => Some(Self::OneLine0), + 11 => Some(Self::OneLine1), + 12 => Some(Self::OneLine2), + 13 => Some(Self::NoColumns), + 14 => Some(Self::Long), + 15 => Some(Self::None), + _ => Option::None, + } + } + + pub fn is_short(&self) -> bool { + (*self as u8) <= 9 + } + + pub fn short_column_group(&self) -> Option<u8> { + if self.is_short() { + Some(*self as u8) + } else { + Option::None + } + } + + pub fn one_line_delta(&self) -> Option<i32> { + match self { + Self::OneLine0 => Some(0), + Self::OneLine1 => Some(1), + Self::OneLine2 => Some(2), + _ => Option::None, + } + } +} + pub trait Constant: Sized { type Name: AsRef<str>; @@ -146,6 +215,10 @@ pub struct CodeObject<C: Constant = ConstantData> { pub varnames: Box<[C::Name]>, pub cellvars: Box<[C::Name]>, pub freevars: Box<[C::Name]>, + pub linetable: Box<[u8]>, + // Line number table (CPython 3.11+ format) + pub exceptiontable: Box<[u8]>, + // Exception handling table } bitflags! { @@ -1202,6 +1275,8 @@ impl<C: Constant> CodeObject<C> { first_line_number: self.first_line_number, max_stackdepth: self.max_stackdepth, cell2arg: self.cell2arg, + linetable: self.linetable, + exceptiontable: self.exceptiontable, } } @@ -1232,6 +1307,8 @@ impl<C: Constant> CodeObject<C> { first_line_number: self.first_line_number, max_stackdepth: self.max_stackdepth, cell2arg: self.cell2arg.clone(), + linetable: self.linetable.clone(), + exceptiontable: self.exceptiontable.clone(), } } } diff --git a/compiler/core/src/marshal.rs b/compiler/core/src/marshal.rs index ff82340c0ee..b8044a1ab95 100644 --- a/compiler/core/src/marshal.rs +++ b/compiler/core/src/marshal.rs @@ -251,6 +251,16 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>( let cellvars = read_names()?; let freevars = read_names()?; + // Read linetable and exceptiontable + let linetable_len = rdr.read_u32()?; + let linetable = rdr.read_slice(linetable_len)?.to_vec().into_boxed_slice(); + + let exceptiontable_len = rdr.read_u32()?; + let exceptiontable = rdr + .read_slice(exceptiontable_len)? + .to_vec() + .into_boxed_slice(); + Ok(CodeObject { instructions, locations, @@ -269,6 +279,8 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>( varnames, cellvars, freevars, + linetable, + exceptiontable, }) } @@ -684,4 +696,8 @@ pub fn serialize_code<W: Write, C: Constant>(buf: &mut W, code: &CodeObject<C>) write_names(&code.varnames); write_names(&code.cellvars); write_names(&code.freevars); + + // Serialize linetable and exceptiontable + write_vec(buf, &code.linetable); + write_vec(buf, &code.exceptiontable); } diff --git a/vm/src/builtins/code.rs b/vm/src/builtins/code.rs index 1ce0f3b3e03..b49f76caa02 100644 --- a/vm/src/builtins/code.rs +++ b/vm/src/builtins/code.rs @@ -16,8 +16,125 @@ use crate::{ use malachite_bigint::BigInt; use num_traits::Zero; use rustpython_compiler_core::OneIndexed; +use rustpython_compiler_core::bytecode::PyCodeLocationInfoKind; use std::{borrow::Borrow, fmt, ops::Deref}; +/// State for iterating through code address ranges +struct PyCodeAddressRange<'a> { + ar_start: i32, + ar_end: i32, + ar_line: i32, + computed_line: i32, + reader: LineTableReader<'a>, +} + +impl<'a> PyCodeAddressRange<'a> { + fn new(linetable: &'a [u8], first_line: i32) -> Self { + PyCodeAddressRange { + ar_start: 0, + ar_end: 0, + ar_line: -1, + computed_line: first_line, + reader: LineTableReader::new(linetable), + } + } + + /// Check if this is a NO_LINE marker (code 15) + fn is_no_line_marker(byte: u8) -> bool { + (byte >> 3) == 0x1f + } + + /// Advance to next address range + fn advance(&mut self) -> bool { + if self.reader.at_end() { + return false; + } + + let first_byte = match self.reader.read_byte() { + Some(b) => b, + None => return false, + }; + + if (first_byte & 0x80) == 0 { + return false; // Invalid linetable + } + + let code = (first_byte >> 3) & 0x0f; + let length = ((first_byte & 0x07) + 1) as i32; + + // Get line delta for this entry + let line_delta = self.get_line_delta(code); + + // Update computed line + self.computed_line += line_delta; + + // Check for NO_LINE marker + if Self::is_no_line_marker(first_byte) { + self.ar_line = -1; + } else { + self.ar_line = self.computed_line; + } + + // Update address range + self.ar_start = self.ar_end; + self.ar_end += length * 2; // sizeof(_Py_CODEUNIT) = 2 + + // Skip remaining bytes for this entry + while !self.reader.at_end() { + if let Some(b) = self.reader.peek_byte() { + if (b & 0x80) != 0 { + break; + } + self.reader.read_byte(); + } else { + break; + } + } + + true + } + + fn get_line_delta(&mut self, code: u8) -> i32 { + let kind = match PyCodeLocationInfoKind::from_code(code) { + Some(k) => k, + None => return 0, + }; + + match kind { + PyCodeLocationInfoKind::None => 0, // NO_LINE marker + PyCodeLocationInfoKind::Long => { + let delta = self.reader.read_signed_varint(); + // Skip end_line, col, end_col + self.reader.read_varint(); + self.reader.read_varint(); + self.reader.read_varint(); + delta + } + PyCodeLocationInfoKind::NoColumns => self.reader.read_signed_varint(), + PyCodeLocationInfoKind::OneLine0 => { + self.reader.read_byte(); // Skip column + self.reader.read_byte(); // Skip end column + 0 + } + PyCodeLocationInfoKind::OneLine1 => { + self.reader.read_byte(); // Skip column + self.reader.read_byte(); // Skip end column + 1 + } + PyCodeLocationInfoKind::OneLine2 => { + self.reader.read_byte(); // Skip column + self.reader.read_byte(); // Skip end column + 2 + } + _ if kind.is_short() => { + self.reader.read_byte(); // Skip column byte + 0 + } + _ => 0, + } + } +} + #[derive(FromArgs)] pub struct ReplaceArgs { #[pyarg(named, optional)] @@ -40,6 +157,22 @@ pub struct ReplaceArgs { co_flags: OptionalArg<u16>, #[pyarg(named, optional)] co_varnames: OptionalArg<Vec<PyObjectRef>>, + #[pyarg(named, optional)] + co_nlocals: OptionalArg<u32>, + #[pyarg(named, optional)] + co_stacksize: OptionalArg<u32>, + #[pyarg(named, optional)] + co_code: OptionalArg<crate::builtins::PyBytesRef>, + #[pyarg(named, optional)] + co_linetable: OptionalArg<crate::builtins::PyBytesRef>, + #[pyarg(named, optional)] + co_exceptiontable: OptionalArg<crate::builtins::PyBytesRef>, + #[pyarg(named, optional)] + co_freevars: OptionalArg<Vec<PyObjectRef>>, + #[pyarg(named, optional)] + co_cellvars: OptionalArg<Vec<PyObjectRef>>, + #[pyarg(named, optional)] + co_qualname: OptionalArg<PyStrRef>, } #[derive(Clone)] @@ -350,6 +483,211 @@ impl PyCode { vm.ctx.new_tuple(names) } + #[pygetset] + pub fn co_linetable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef { + // Return the actual linetable from the code object + vm.ctx.new_bytes(self.code.linetable.to_vec()) + } + + #[pygetset] + pub fn co_exceptiontable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef { + // Return the actual exception table from the code object + vm.ctx.new_bytes(self.code.exceptiontable.to_vec()) + } + + #[pymethod] + pub fn co_lines(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // TODO: Implement lazy iterator (lineiterator) like CPython for better performance + // Currently returns eager list for simplicity + + // Return an iterator over (start_offset, end_offset, lineno) tuples + let linetable = self.code.linetable.as_ref(); + let mut lines = Vec::new(); + + if !linetable.is_empty() { + let first_line = self.code.first_line_number.map_or(0, |n| n.get() as i32); + let mut range = PyCodeAddressRange::new(linetable, first_line); + + // Process all address ranges and merge consecutive entries with same line + let mut pending_entry: Option<(i32, i32, i32)> = None; + + while range.advance() { + let start = range.ar_start; + let end = range.ar_end; + let line = range.ar_line; + + if let Some((prev_start, _, prev_line)) = pending_entry { + if prev_line == line { + // Same line, extend the range + pending_entry = Some((prev_start, end, prev_line)); + } else { + // Different line, emit the previous entry + let tuple = if prev_line == -1 { + vm.ctx.new_tuple(vec![ + vm.ctx.new_int(prev_start).into(), + vm.ctx.new_int(start).into(), + vm.ctx.none(), + ]) + } else { + vm.ctx.new_tuple(vec![ + vm.ctx.new_int(prev_start).into(), + vm.ctx.new_int(start).into(), + vm.ctx.new_int(prev_line).into(), + ]) + }; + lines.push(tuple.into()); + pending_entry = Some((start, end, line)); + } + } else { + // First entry + pending_entry = Some((start, end, line)); + } + } + + // Emit the last pending entry + if let Some((start, end, line)) = pending_entry { + let tuple = if line == -1 { + vm.ctx.new_tuple(vec![ + vm.ctx.new_int(start).into(), + vm.ctx.new_int(end).into(), + vm.ctx.none(), + ]) + } else { + vm.ctx.new_tuple(vec![ + vm.ctx.new_int(start).into(), + vm.ctx.new_int(end).into(), + vm.ctx.new_int(line).into(), + ]) + }; + lines.push(tuple.into()); + } + } + + let list = vm.ctx.new_list(lines); + vm.call_method(list.as_object(), "__iter__", ()) + } + + #[pymethod] + pub fn co_positions(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // Return an iterator over (line, end_line, column, end_column) tuples for each instruction + let linetable = self.code.linetable.as_ref(); + let mut positions = Vec::new(); + + if !linetable.is_empty() { + let mut reader = LineTableReader::new(linetable); + let mut line = self.code.first_line_number.map_or(0, |n| n.get() as i32); + + while !reader.at_end() { + let first_byte = match reader.read_byte() { + Some(b) => b, + None => break, + }; + + if (first_byte & 0x80) == 0 { + break; // Invalid linetable + } + + let code = (first_byte >> 3) & 0x0f; + let length = ((first_byte & 0x07) + 1) as i32; + + let kind = match PyCodeLocationInfoKind::from_code(code) { + Some(k) => k, + None => break, // Invalid code + }; + + let (line_delta, end_line_delta, column, end_column): ( + i32, + i32, + Option<i32>, + Option<i32>, + ) = match kind { + PyCodeLocationInfoKind::None => { + // No location - all values are None + (0, 0, None, None) + } + PyCodeLocationInfoKind::Long => { + // Long form + let delta = reader.read_signed_varint(); + let end_line_delta = reader.read_varint() as i32; + + let col = reader.read_varint(); + let column = if col == 0 { + None + } else { + Some((col - 1) as i32) + }; + + let end_col = reader.read_varint(); + let end_column = if end_col == 0 { + None + } else { + Some((end_col - 1) as i32) + }; + + // endline = line + end_line_delta (will be computed after line update) + (delta, end_line_delta, column, end_column) + } + PyCodeLocationInfoKind::NoColumns => { + // No column form + let delta = reader.read_signed_varint(); + (delta, 0, None, None) // endline will be same as line (delta = 0) + } + PyCodeLocationInfoKind::OneLine0 + | PyCodeLocationInfoKind::OneLine1 + | PyCodeLocationInfoKind::OneLine2 => { + // One-line form - endline = line + let col = reader.read_byte().unwrap_or(0) as i32; + let end_col = reader.read_byte().unwrap_or(0) as i32; + let delta = kind.one_line_delta().unwrap_or(0); + (delta, 0, Some(col), Some(end_col)) // endline = line (delta = 0) + } + _ if kind.is_short() => { + // Short form - endline = line + let col_data = reader.read_byte().unwrap_or(0); + let col_group = kind.short_column_group().unwrap_or(0); + let col = ((col_group as i32) << 3) | ((col_data >> 4) as i32); + let end_col = col + (col_data & 0x0f) as i32; + (0, 0, Some(col), Some(end_col)) // endline = line (delta = 0) + } + _ => (0, 0, None, None), + }; + + // Update line number + line += line_delta; + + // Generate position tuples for each instruction covered by this entry + for _ in 0..length { + // Handle special case for no location (code 15) + let final_line = if kind == PyCodeLocationInfoKind::None { + None + } else { + Some(line) + }; + + let final_endline = if kind == PyCodeLocationInfoKind::None { + None + } else { + Some(line + end_line_delta) + }; + + // Convert Option to PyObject (None or int) + let line_obj = final_line.to_pyobject(vm); + let end_line_obj = final_endline.to_pyobject(vm); + let column_obj = column.to_pyobject(vm); + let end_column_obj = end_column.to_pyobject(vm); + + let tuple = + vm.ctx + .new_tuple(vec![line_obj, end_line_obj, column_obj, end_column_obj]); + positions.push(tuple.into()); + } + } + } + + let list = vm.ctx.new_list(positions); + vm.call_method(list.as_object(), "__iter__", ()) + } + #[pymethod] pub fn replace(&self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<Self> { let posonlyarg_count = match args.co_posonlyargcount { @@ -408,6 +746,66 @@ impl PyCode { OptionalArg::Missing => self.code.varnames.iter().map(|s| s.to_object()).collect(), }; + let qualname = match args.co_qualname { + OptionalArg::Present(qualname) => qualname, + OptionalArg::Missing => self.code.qualname.to_owned(), + }; + + let max_stackdepth = match args.co_stacksize { + OptionalArg::Present(stacksize) => stacksize, + OptionalArg::Missing => self.code.max_stackdepth, + }; + + let instructions = match args.co_code { + OptionalArg::Present(_code_bytes) => { + // Convert bytes back to instructions + // For now, keep the original instructions + // TODO: Properly parse bytecode from bytes + self.code.instructions.clone() + } + OptionalArg::Missing => self.code.instructions.clone(), + }; + + let cellvars = match args.co_cellvars { + OptionalArg::Present(cellvars) => cellvars + .into_iter() + .map(|o| o.as_interned_str(vm).unwrap()) + .collect(), + OptionalArg::Missing => self.code.cellvars.clone(), + }; + + let freevars = match args.co_freevars { + OptionalArg::Present(freevars) => freevars + .into_iter() + .map(|o| o.as_interned_str(vm).unwrap()) + .collect(), + OptionalArg::Missing => self.code.freevars.clone(), + }; + + // Validate co_nlocals if provided + if let OptionalArg::Present(nlocals) = args.co_nlocals + && nlocals as usize != varnames.len() + { + return Err(vm.new_value_error(format!( + "co_nlocals ({}) != len(co_varnames) ({})", + nlocals, + varnames.len() + ))); + } + + // Handle linetable and exceptiontable + let linetable = match args.co_linetable { + OptionalArg::Present(linetable) => linetable.as_bytes().to_vec().into_boxed_slice(), + OptionalArg::Missing => self.code.linetable.clone(), + }; + + let exceptiontable = match args.co_exceptiontable { + OptionalArg::Present(exceptiontable) => { + exceptiontable.as_bytes().to_vec().into_boxed_slice() + } + OptionalArg::Missing => self.code.exceptiontable.clone(), + }; + Ok(Self { code: CodeObject { flags: CodeFlags::from_bits_truncate(flags), @@ -417,10 +815,10 @@ impl PyCode { source_path: source_path.as_object().as_interned_str(vm).unwrap(), first_line_number, obj_name: obj_name.as_object().as_interned_str(vm).unwrap(), - qualname: self.code.qualname, + qualname: qualname.as_object().as_interned_str(vm).unwrap(), - max_stackdepth: self.code.max_stackdepth, - instructions: self.code.instructions.clone(), + max_stackdepth, + instructions, locations: self.code.locations.clone(), constants: constants.into_iter().map(Literal).collect(), names: names @@ -431,9 +829,11 @@ impl PyCode { .into_iter() .map(|o| o.as_interned_str(vm).unwrap()) .collect(), - cellvars: self.code.cellvars.clone(), - freevars: self.code.freevars.clone(), + cellvars, + freevars, cell2arg: self.code.cell2arg.clone(), + linetable, + exceptiontable, }, }) } @@ -457,6 +857,69 @@ impl ToPyObject for bytecode::CodeObject { } } +// Helper struct for reading linetable +struct LineTableReader<'a> { + data: &'a [u8], + pos: usize, +} + +impl<'a> LineTableReader<'a> { + fn new(data: &'a [u8]) -> Self { + Self { data, pos: 0 } + } + + fn read_byte(&mut self) -> Option<u8> { + if self.pos < self.data.len() { + let byte = self.data[self.pos]; + self.pos += 1; + Some(byte) + } else { + None + } + } + + fn peek_byte(&self) -> Option<u8> { + if self.pos < self.data.len() { + Some(self.data[self.pos]) + } else { + None + } + } + + fn read_varint(&mut self) -> u32 { + if let Some(first) = self.read_byte() { + let mut val = (first & 0x3f) as u32; + let mut shift = 0; + let mut byte = first; + while (byte & 0x40) != 0 { + if let Some(next) = self.read_byte() { + shift += 6; + val |= ((next & 0x3f) as u32) << shift; + byte = next; + } else { + break; + } + } + val + } else { + 0 + } + } + + fn read_signed_varint(&mut self) -> i32 { + let uval = self.read_varint(); + if uval & 1 != 0 { + -((uval >> 1) as i32) + } else { + (uval >> 1) as i32 + } + } + + fn at_end(&self) -> bool { + self.pos >= self.data.len() + } +} + pub fn init(ctx: &Context) { PyCode::extend_class(ctx, ctx.types.code_type); } From 67958ec7913c389d6ad0cf80c97c1d27373b5865 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:53:25 +0200 Subject: [PATCH 264/819] Update `{io,encodings}` from 3.13.7 (#6153) * Update `io` from 3.13.7 * Patch test & upsate `encodings` from 3.13.7 * Unmark passing tests --- Lib/encodings/aliases.py | 1 + Lib/encodings/idna.py | 166 ++++++++--- Lib/encodings/palmos.py | 2 +- Lib/encodings/punycode.py | 42 ++- Lib/encodings/undefined.py | 2 +- Lib/encodings/utf_16.py | 4 +- Lib/encodings/utf_32.py | 6 +- Lib/io.py | 15 +- Lib/test/test_codecs.py | 9 - Lib/test/test_io.py | 577 ++++++++++++++++++++----------------- 10 files changed, 467 insertions(+), 357 deletions(-) diff --git a/Lib/encodings/aliases.py b/Lib/encodings/aliases.py index d85afd6d5cf..6a5ca046b5e 100644 --- a/Lib/encodings/aliases.py +++ b/Lib/encodings/aliases.py @@ -209,6 +209,7 @@ 'ms932' : 'cp932', 'mskanji' : 'cp932', 'ms_kanji' : 'cp932', + 'windows_31j' : 'cp932', # cp949 codec '949' : 'cp949', diff --git a/Lib/encodings/idna.py b/Lib/encodings/idna.py index 5396047a7fb..0c90b4c9fe1 100644 --- a/Lib/encodings/idna.py +++ b/Lib/encodings/idna.py @@ -11,7 +11,7 @@ sace_prefix = "xn--" # This assumes query strings, so AllowUnassigned is true -def nameprep(label): +def nameprep(label): # type: (str) -> str # Map newlabel = [] for c in label: @@ -25,7 +25,7 @@ def nameprep(label): label = unicodedata.normalize("NFKC", label) # Prohibit - for c in label: + for i, c in enumerate(label): if stringprep.in_table_c12(c) or \ stringprep.in_table_c22(c) or \ stringprep.in_table_c3(c) or \ @@ -35,7 +35,7 @@ def nameprep(label): stringprep.in_table_c7(c) or \ stringprep.in_table_c8(c) or \ stringprep.in_table_c9(c): - raise UnicodeError("Invalid character %r" % c) + raise UnicodeEncodeError("idna", label, i, i+1, f"Invalid character {c!r}") # Check bidi RandAL = [stringprep.in_table_d1(x) for x in label] @@ -46,29 +46,38 @@ def nameprep(label): # This is table C.8, which was already checked # 2) If a string contains any RandALCat character, the string # MUST NOT contain any LCat character. - if any(stringprep.in_table_d2(x) for x in label): - raise UnicodeError("Violation of BIDI requirement 2") + for i, x in enumerate(label): + if stringprep.in_table_d2(x): + raise UnicodeEncodeError("idna", label, i, i+1, + "Violation of BIDI requirement 2") # 3) If a string contains any RandALCat character, a # RandALCat character MUST be the first character of the # string, and a RandALCat character MUST be the last # character of the string. - if not RandAL[0] or not RandAL[-1]: - raise UnicodeError("Violation of BIDI requirement 3") + if not RandAL[0]: + raise UnicodeEncodeError("idna", label, 0, 1, + "Violation of BIDI requirement 3") + if not RandAL[-1]: + raise UnicodeEncodeError("idna", label, len(label)-1, len(label), + "Violation of BIDI requirement 3") return label -def ToASCII(label): +def ToASCII(label): # type: (str) -> bytes try: # Step 1: try ASCII - label = label.encode("ascii") - except UnicodeError: + label_ascii = label.encode("ascii") + except UnicodeEncodeError: pass else: # Skip to step 3: UseSTD3ASCIIRules is false, so # Skip to step 8. - if 0 < len(label) < 64: - return label - raise UnicodeError("label empty or too long") + if 0 < len(label_ascii) < 64: + return label_ascii + if len(label) == 0: + raise UnicodeEncodeError("idna", label, 0, 1, "label empty") + else: + raise UnicodeEncodeError("idna", label, 0, len(label), "label too long") # Step 2: nameprep label = nameprep(label) @@ -76,29 +85,34 @@ def ToASCII(label): # Step 3: UseSTD3ASCIIRules is false # Step 4: try ASCII try: - label = label.encode("ascii") - except UnicodeError: + label_ascii = label.encode("ascii") + except UnicodeEncodeError: pass else: # Skip to step 8. if 0 < len(label) < 64: - return label - raise UnicodeError("label empty or too long") + return label_ascii + if len(label) == 0: + raise UnicodeEncodeError("idna", label, 0, 1, "label empty") + else: + raise UnicodeEncodeError("idna", label, 0, len(label), "label too long") # Step 5: Check ACE prefix - if label.startswith(sace_prefix): - raise UnicodeError("Label starts with ACE prefix") + if label.lower().startswith(sace_prefix): + raise UnicodeEncodeError( + "idna", label, 0, len(sace_prefix), "Label starts with ACE prefix") # Step 6: Encode with PUNYCODE - label = label.encode("punycode") + label_ascii = label.encode("punycode") # Step 7: Prepend ACE prefix - label = ace_prefix + label + label_ascii = ace_prefix + label_ascii # Step 8: Check size - if 0 < len(label) < 64: - return label - raise UnicodeError("label empty or too long") + # do not check for empty as we prepend ace_prefix. + if len(label_ascii) < 64: + return label_ascii + raise UnicodeEncodeError("idna", label, 0, len(label), "label too long") def ToUnicode(label): if len(label) > 1024: @@ -110,7 +124,9 @@ def ToUnicode(label): # 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") + if isinstance(label, str): + label = label.encode("utf-8", errors="backslashreplace") + raise UnicodeDecodeError("idna", label, 0, len(label), "label way too long") # Step 1: Check for ASCII if isinstance(label, bytes): pure_ascii = True @@ -118,25 +134,32 @@ def ToUnicode(label): try: label = label.encode("ascii") pure_ascii = True - except UnicodeError: + except UnicodeEncodeError: pure_ascii = False if not pure_ascii: + assert isinstance(label, str) # Step 2: Perform nameprep label = nameprep(label) # It doesn't say this, but apparently, it should be ASCII now try: label = label.encode("ascii") - except UnicodeError: - raise UnicodeError("Invalid character in IDN label") + except UnicodeEncodeError as exc: + raise UnicodeEncodeError("idna", label, exc.start, exc.end, + "Invalid character in IDN label") # Step 3: Check for ACE prefix - if not label.startswith(ace_prefix): + assert isinstance(label, bytes) + if not label.lower().startswith(ace_prefix): return str(label, "ascii") # Step 4: Remove ACE prefix label1 = label[len(ace_prefix):] # Step 5: Decode using PUNYCODE - result = label1.decode("punycode") + try: + result = label1.decode("punycode") + except UnicodeDecodeError as exc: + offset = len(ace_prefix) + raise UnicodeDecodeError("idna", label, offset+exc.start, offset+exc.end, exc.reason) # Step 6: Apply ToASCII label2 = ToASCII(result) @@ -144,7 +167,8 @@ def ToUnicode(label): # Step 7: Compare the result of step 6 with the one of step 3 # label2 will already be in lower case. if str(label, "ascii").lower() != str(label2, "ascii"): - raise UnicodeError("IDNA does not round-trip", label, label2) + raise UnicodeDecodeError("idna", label, 0, len(label), + f"IDNA does not round-trip, '{label!r}' != '{label2!r}'") # Step 8: return the result of step 5 return result @@ -156,7 +180,7 @@ def encode(self, input, errors='strict'): if errors != 'strict': # IDNA is quite clear that implementations must be strict - raise UnicodeError("unsupported error handling "+errors) + raise UnicodeError(f"Unsupported error handling: {errors}") if not input: return b'', 0 @@ -168,11 +192,16 @@ def encode(self, input, errors='strict'): else: # ASCII name: fast path labels = result.split(b'.') - for label in labels[:-1]: - if not (0 < len(label) < 64): - raise UnicodeError("label empty or too long") - if len(labels[-1]) >= 64: - raise UnicodeError("label too long") + for i, label in enumerate(labels[:-1]): + if len(label) == 0: + offset = sum(len(l) for l in labels[:i]) + i + raise UnicodeEncodeError("idna", input, offset, offset+1, + "label empty") + for i, label in enumerate(labels): + if len(label) >= 64: + offset = sum(len(l) for l in labels[:i]) + i + raise UnicodeEncodeError("idna", input, offset, offset+len(label), + "label too long") return result, len(input) result = bytearray() @@ -182,17 +211,27 @@ def encode(self, input, errors='strict'): del labels[-1] else: trailing_dot = b'' - for label in labels: + for i, label in enumerate(labels): if result: # Join with U+002E result.extend(b'.') - result.extend(ToASCII(label)) + try: + result.extend(ToASCII(label)) + except (UnicodeEncodeError, UnicodeDecodeError) as exc: + offset = sum(len(l) for l in labels[:i]) + i + raise UnicodeEncodeError( + "idna", + input, + offset + exc.start, + offset + exc.end, + exc.reason, + ) return bytes(result+trailing_dot), len(input) def decode(self, input, errors='strict'): if errors != 'strict': - raise UnicodeError("Unsupported error handling "+errors) + raise UnicodeError(f"Unsupported error handling: {errors}") if not input: return "", 0 @@ -202,7 +241,7 @@ def decode(self, input, errors='strict'): # XXX obviously wrong, see #3232 input = bytes(input) - if ace_prefix not in input: + if ace_prefix not in input.lower(): # Fast path try: return input.decode('ascii'), len(input) @@ -218,8 +257,15 @@ def decode(self, input, errors='strict'): trailing_dot = '' result = [] - for label in labels: - result.append(ToUnicode(label)) + for i, label in enumerate(labels): + try: + u_label = ToUnicode(label) + except (UnicodeEncodeError, UnicodeDecodeError) as exc: + offset = sum(len(x) for x in labels[:i]) + len(labels[:i]) + raise UnicodeDecodeError( + "idna", input, offset+exc.start, offset+exc.end, exc.reason) + else: + result.append(u_label) return ".".join(result)+trailing_dot, len(input) @@ -227,7 +273,7 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder): def _buffer_encode(self, input, errors, final): if errors != 'strict': # IDNA is quite clear that implementations must be strict - raise UnicodeError("unsupported error handling "+errors) + raise UnicodeError(f"Unsupported error handling: {errors}") if not input: return (b'', 0) @@ -251,7 +297,16 @@ def _buffer_encode(self, input, errors, final): # Join with U+002E result.extend(b'.') size += 1 - result.extend(ToASCII(label)) + try: + result.extend(ToASCII(label)) + except (UnicodeEncodeError, UnicodeDecodeError) as exc: + raise UnicodeEncodeError( + "idna", + input, + size + exc.start, + size + exc.end, + exc.reason, + ) size += len(label) result += trailing_dot @@ -261,7 +316,7 @@ def _buffer_encode(self, input, errors, final): class IncrementalDecoder(codecs.BufferedIncrementalDecoder): def _buffer_decode(self, input, errors, final): if errors != 'strict': - raise UnicodeError("Unsupported error handling "+errors) + raise UnicodeError(f"Unsupported error handling: {errors}") if not input: return ("", 0) @@ -271,7 +326,11 @@ def _buffer_decode(self, input, errors, final): labels = dots.split(input) else: # Must be ASCII string - input = str(input, "ascii") + try: + input = str(input, "ascii") + except (UnicodeEncodeError, UnicodeDecodeError) as exc: + raise UnicodeDecodeError("idna", input, + exc.start, exc.end, exc.reason) labels = input.split(".") trailing_dot = '' @@ -288,7 +347,18 @@ def _buffer_decode(self, input, errors, final): result = [] size = 0 for label in labels: - result.append(ToUnicode(label)) + try: + u_label = ToUnicode(label) + except (UnicodeEncodeError, UnicodeDecodeError) as exc: + raise UnicodeDecodeError( + "idna", + input.encode("ascii", errors="backslashreplace"), + size + exc.start, + size + exc.end, + exc.reason, + ) + else: + result.append(u_label) if size: size += 1 size += len(label) diff --git a/Lib/encodings/palmos.py b/Lib/encodings/palmos.py index c506d654523..df164ca5b95 100644 --- a/Lib/encodings/palmos.py +++ b/Lib/encodings/palmos.py @@ -201,7 +201,7 @@ def getregentry(): '\u02dc' # 0x98 -> SMALL TILDE '\u2122' # 0x99 -> TRADE MARK SIGN '\u0161' # 0x9A -> LATIN SMALL LETTER S WITH CARON - '\x9b' # 0x9B -> <control> + '\u203a' # 0x9B -> SINGLE RIGHT-POINTING ANGLE QUOTATION MARK '\u0153' # 0x9C -> LATIN SMALL LIGATURE OE '\x9d' # 0x9D -> <control> '\x9e' # 0x9E -> <control> diff --git a/Lib/encodings/punycode.py b/Lib/encodings/punycode.py index 1c572644707..4622fc8c920 100644 --- a/Lib/encodings/punycode.py +++ b/Lib/encodings/punycode.py @@ -1,4 +1,4 @@ -""" Codec for the Punicode encoding, as specified in RFC 3492 +""" Codec for the Punycode encoding, as specified in RFC 3492 Written by Martin v. Löwis. """ @@ -131,10 +131,11 @@ def decode_generalized_number(extended, extpos, bias, errors): j = 0 while 1: try: - char = ord(extended[extpos]) + char = extended[extpos] except IndexError: if errors == "strict": - raise UnicodeError("incomplete punicode string") + raise UnicodeDecodeError("punycode", extended, extpos, extpos+1, + "incomplete punycode string") return extpos + 1, None extpos += 1 if 0x41 <= char <= 0x5A: # A-Z @@ -142,8 +143,8 @@ def decode_generalized_number(extended, extpos, bias, errors): elif 0x30 <= char <= 0x39: digit = char - 22 # 0x30-26 elif errors == "strict": - raise UnicodeError("Invalid extended code point '%s'" - % extended[extpos-1]) + raise UnicodeDecodeError("punycode", extended, extpos-1, extpos, + f"Invalid extended code point '{extended[extpos-1]}'") else: return extpos, None t = T(j, bias) @@ -155,11 +156,14 @@ def decode_generalized_number(extended, extpos, bias, errors): def insertion_sort(base, extended, errors): - """3.2 Insertion unsort coding""" + """3.2 Insertion sort coding""" + # This function raises UnicodeDecodeError with position in the extended. + # Caller should add the offset. char = 0x80 pos = -1 bias = 72 extpos = 0 + while extpos < len(extended): newpos, delta = decode_generalized_number(extended, extpos, bias, errors) @@ -171,7 +175,9 @@ def insertion_sort(base, extended, errors): char += pos // (len(base) + 1) if char > 0x10FFFF: if errors == "strict": - raise UnicodeError("Invalid character U+%x" % char) + raise UnicodeDecodeError( + "punycode", extended, pos-1, pos, + f"Invalid character U+{char:x}") char = ord('?') pos = pos % (len(base) + 1) base = base[:pos] + chr(char) + base[pos:] @@ -187,11 +193,21 @@ def punycode_decode(text, errors): pos = text.rfind(b"-") if pos == -1: base = "" - extended = str(text, "ascii").upper() + extended = text.upper() else: - base = str(text[:pos], "ascii", errors) - extended = str(text[pos+1:], "ascii").upper() - return insertion_sort(base, extended, errors) + try: + base = str(text[:pos], "ascii", errors) + except UnicodeDecodeError as exc: + raise UnicodeDecodeError("ascii", text, exc.start, exc.end, + exc.reason) from None + extended = text[pos+1:].upper() + try: + return insertion_sort(base, extended, errors) + except UnicodeDecodeError as exc: + offset = pos + 1 + raise UnicodeDecodeError("punycode", text, + offset+exc.start, offset+exc.end, + exc.reason) from None ### Codec APIs @@ -203,7 +219,7 @@ def encode(self, input, errors='strict'): def decode(self, input, errors='strict'): if errors not in ('strict', 'replace', 'ignore'): - raise UnicodeError("Unsupported error handling "+errors) + raise UnicodeError(f"Unsupported error handling: {errors}") res = punycode_decode(input, errors) return res, len(input) @@ -214,7 +230,7 @@ def encode(self, input, final=False): class IncrementalDecoder(codecs.IncrementalDecoder): def decode(self, input, final=False): if self.errors not in ('strict', 'replace', 'ignore'): - raise UnicodeError("Unsupported error handling "+self.errors) + raise UnicodeError(f"Unsupported error handling: {self.errors}") return punycode_decode(input, self.errors) class StreamWriter(Codec,codecs.StreamWriter): diff --git a/Lib/encodings/undefined.py b/Lib/encodings/undefined.py index 4690288355c..082771e1c86 100644 --- a/Lib/encodings/undefined.py +++ b/Lib/encodings/undefined.py @@ -1,6 +1,6 @@ """ Python 'undefined' Codec - This codec will always raise a ValueError exception when being + This codec will always raise a UnicodeError exception when being used. It is intended for use by the site.py file to switch off automatic string to Unicode coercion. diff --git a/Lib/encodings/utf_16.py b/Lib/encodings/utf_16.py index c61248242be..d3b99800266 100644 --- a/Lib/encodings/utf_16.py +++ b/Lib/encodings/utf_16.py @@ -64,7 +64,7 @@ def _buffer_decode(self, input, errors, final): elif byteorder == 1: self.decoder = codecs.utf_16_be_decode elif consumed >= 2: - raise UnicodeError("UTF-16 stream does not start with BOM") + raise UnicodeDecodeError("utf-16", input, 0, 2, "Stream does not start with BOM") return (output, consumed) return self.decoder(input, self.errors, final) @@ -138,7 +138,7 @@ def decode(self, input, errors='strict'): elif byteorder == 1: self.decode = codecs.utf_16_be_decode elif consumed>=2: - raise UnicodeError("UTF-16 stream does not start with BOM") + raise UnicodeDecodeError("utf-16", input, 0, 2, "Stream does not start with BOM") return (object, consumed) ### encodings module API diff --git a/Lib/encodings/utf_32.py b/Lib/encodings/utf_32.py index cdf84d14129..1924bedbb74 100644 --- a/Lib/encodings/utf_32.py +++ b/Lib/encodings/utf_32.py @@ -59,7 +59,7 @@ def _buffer_decode(self, input, errors, final): elif byteorder == 1: self.decoder = codecs.utf_32_be_decode elif consumed >= 4: - raise UnicodeError("UTF-32 stream does not start with BOM") + raise UnicodeDecodeError("utf-32", input, 0, 4, "Stream does not start with BOM") return (output, consumed) return self.decoder(input, self.errors, final) @@ -132,8 +132,8 @@ def decode(self, input, errors='strict'): self.decode = codecs.utf_32_le_decode elif byteorder == 1: self.decode = codecs.utf_32_be_decode - elif consumed>=4: - raise UnicodeError("UTF-32 stream does not start with BOM") + elif consumed >= 4: + raise UnicodeDecodeError("utf-32", input, 0, 4, "Stream does not start with BOM") return (object, consumed) ### encodings module API diff --git a/Lib/io.py b/Lib/io.py index c2812876d3e..f0e2fa15d5a 100644 --- a/Lib/io.py +++ b/Lib/io.py @@ -46,23 +46,17 @@ "BufferedReader", "BufferedWriter", "BufferedRWPair", "BufferedRandom", "TextIOBase", "TextIOWrapper", "UnsupportedOperation", "SEEK_SET", "SEEK_CUR", "SEEK_END", - "DEFAULT_BUFFER_SIZE", "text_encoding", - "IncrementalNewlineDecoder" - ] + "DEFAULT_BUFFER_SIZE", "text_encoding", "IncrementalNewlineDecoder"] import _io import abc from _io import (DEFAULT_BUFFER_SIZE, BlockingIOError, UnsupportedOperation, - open, open_code, BytesIO, StringIO, BufferedReader, + open, open_code, FileIO, BytesIO, StringIO, BufferedReader, BufferedWriter, BufferedRWPair, BufferedRandom, IncrementalNewlineDecoder, text_encoding, TextIOWrapper) -try: - from _io import FileIO -except ImportError: - pass # Pretend this exception was created here. UnsupportedOperation.__module__ = "io" @@ -87,10 +81,7 @@ class BufferedIOBase(_io._BufferedIOBase, IOBase): class TextIOBase(_io._TextIOBase, IOBase): __doc__ = _io._TextIOBase.__doc__ -try: - RawIOBase.register(FileIO) -except NameError: - pass +RawIOBase.register(FileIO) for klass in (BytesIO, BufferedReader, BufferedWriter, BufferedRandom, BufferedRWPair): diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index f986d85c6d6..0d3d8c9e2d6 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -762,7 +762,6 @@ def test_only_one_bom(self): f = reader(s) self.assertEqual(f.read(), "spamspam") - @unittest.expectedFailure # TODO: RUSTPYTHON;; UTF-16 stream does not start with BOM def test_badbom(self): s = io.BytesIO(b"\xff\xff") f = codecs.getreader(self.encoding)(s) @@ -1509,7 +1508,6 @@ def test_decode(self): puny = puny.decode("ascii").encode("ascii") self.assertEqual(uni, puny.decode("punycode")) - @unittest.expectedFailure # TODO: RUSTPYTHON; b'Pro\xffprostnemluvesky' != b'Pro\xffprostnemluvesky-uyb24dma41a' def test_decode_invalid(self): testcases = [ (b"xn--w&", "strict", UnicodeDecodeError("punycode", b"", 5, 6, "")), @@ -1694,7 +1692,6 @@ def test_decode_invalid(self): class NameprepTest(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON; UnicodeError: Invalid character '\u1680' def test_nameprep(self): from encodings.idna import nameprep for pos, (orig, prepped) in enumerate(nameprep_tests): @@ -1732,7 +1729,6 @@ class IDNACodecTest(unittest.TestCase): ("あさ.\u034f", UnicodeEncodeError("idna", "あさ.\u034f", 3, 4, "")), ] - @unittest.expectedFailure # TODO: RUSTPYTHON; 'XN--pythn-mua.org.' != 'pythön.org.' def test_builtin_decode(self): self.assertEqual(str(b"python.org", "idna"), "python.org") self.assertEqual(str(b"python.org.", "idna"), "python.org.") @@ -1746,7 +1742,6 @@ def test_builtin_decode(self): self.assertEqual(str(b"bugs.XN--pythn-mua.org.", "idna"), "bugs.pyth\xf6n.org.") - @unittest.expectedFailure # TODO: RUSTPYTHON; 'ascii' != 'idna' def test_builtin_decode_invalid(self): for case, expected in self.invalid_decode_testcases: with self.subTest(case=case, expected=expected): @@ -1764,7 +1759,6 @@ def test_builtin_encode(self): self.assertEqual("pyth\xf6n.org".encode("idna"), b"xn--pythn-mua.org") self.assertEqual("pyth\xf6n.org.".encode("idna"), b"xn--pythn-mua.org.") - @unittest.expectedFailure # TODO: RUSTPYTHON; UnicodeError: label empty or too long def test_builtin_encode_invalid(self): for case, expected in self.invalid_encode_testcases: with self.subTest(case=case, expected=expected): @@ -1776,7 +1770,6 @@ def test_builtin_encode_invalid(self): self.assertEqual(exc.start, expected.start) self.assertEqual(exc.end, expected.end) - @unittest.expectedFailure # TODO: RUSTPYTHON; UnicodeError: label empty or too long def test_builtin_decode_length_limit(self): with self.assertRaisesRegex(UnicodeDecodeError, "way too long"): (b"xn--016c"+b"a"*1100).decode("idna") @@ -1818,7 +1811,6 @@ def test_incremental_decode(self): self.assertEqual(decoder.decode(b"rg."), "org.") self.assertEqual(decoder.decode(b"", True), "") - @unittest.expectedFailure # TODO: RUSTPYTHON; 'ascii' != 'idna' def test_incremental_decode_invalid(self): iterdecode_testcases = [ (b"\xFFpython.org", UnicodeDecodeError("idna", b"\xFF", 0, 1, "")), @@ -1880,7 +1872,6 @@ def test_incremental_encode(self): self.assertEqual(encoder.encode("ample.org."), b"xn--xample-9ta.org.") self.assertEqual(encoder.encode("", True), b"") - @unittest.expectedFailure # TODO: RUSTPYTHON; UnicodeError: label empty or too long def test_incremental_encode_invalid(self): iterencode_testcases = [ (f"foo.{'\xff'*60}", UnicodeEncodeError("idna", f"{'\xff'*60}", 0, 60, "")), diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index e91d454b723..272098782d6 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -39,11 +39,9 @@ from test import support from test.support.script_helper import ( assert_python_ok, assert_python_failure, run_python_until_end) -from test.support import import_helper -from test.support import os_helper -from test.support import threading_helper -from test.support import warnings_helper -from test.support import skip_if_sanitizer +from test.support import ( + import_helper, is_apple, os_helper, threading_helper, warnings_helper, +) from test.support.os_helper import FakePath import codecs @@ -66,10 +64,6 @@ def byteslike(*pos, **kw): class EmptyStruct(ctypes.Structure): pass -# Does io.IOBase finalizer log the exception if the close() method fails? -# The exception is ignored silently by default in release build. -IOBASE_EMITS_UNRAISABLE = (support.Py_DEBUG or sys.flags.dev_mode) - def _default_chunk_size(): """Get the default TextIOWrapper chunk size""" @@ -631,10 +625,10 @@ def test_raw_bytes_io(self): self.read_ops(f, True) def test_large_file_ops(self): - # On Windows and Mac OSX this test consumes large resources; It takes - # a long time to build the >2 GiB file and takes >2 GiB of disk space - # therefore the resource must be enabled to run this test. - if sys.platform[:3] == 'win' or sys.platform == 'darwin': + # On Windows and Apple platforms this test consumes large resources; It + # takes a long time to build the >2 GiB file and takes >2 GiB of disk + # space therefore the resource must be enabled to run this test. + if sys.platform[:3] == 'win' or is_apple: support.requires( 'largefile', 'test requires %s bytes and a long time to run' % self.LARGE) @@ -645,11 +639,9 @@ def test_large_file_ops(self): def test_with_open(self): for bufsize in (0, 100): - f = None with self.open(os_helper.TESTFN, "wb", bufsize) as f: f.write(b"xxx") self.assertEqual(f.closed, True) - f = None try: with self.open(os_helper.TESTFN, "wb", bufsize) as f: 1/0 @@ -788,8 +780,7 @@ def test_closefd_attr(self): file = self.open(f.fileno(), "r", encoding="utf-8", closefd=False) self.assertEqual(file.buffer.raw.closefd, False) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): # FileIO objects are collected, and collecting them flushes # all data to disk. @@ -904,7 +895,7 @@ def test_bad_opener_negative_1(self): def badopener(fname, flags): return -1 with self.assertRaises(ValueError) as cm: - open('non-existent', 'r', opener=badopener) + self.open('non-existent', 'r', opener=badopener) self.assertEqual(str(cm.exception), 'opener returned -1') def test_bad_opener_other_negative(self): @@ -912,7 +903,7 @@ def test_bad_opener_other_negative(self): def badopener(fname, flags): return -2 with self.assertRaises(ValueError) as cm: - open('non-existent', 'r', opener=badopener) + self.open('non-existent', 'r', opener=badopener) self.assertEqual(str(cm.exception), 'opener returned -2') def test_opener_invalid_fd(self): @@ -1048,11 +1039,41 @@ def flush(self): # Silence destructor error R.flush = lambda self: None + @threading_helper.requires_working_threading() + def test_write_readline_races(self): + # gh-134908: Concurrent iteration over a file caused races + thread_count = 2 + write_count = 100 + read_count = 100 + + def writer(file, barrier): + barrier.wait() + for _ in range(write_count): + file.write("x") + + def reader(file, barrier): + barrier.wait() + for _ in range(read_count): + for line in file: + self.assertEqual(line, "") + + with self.open(os_helper.TESTFN, "w+") as f: + barrier = threading.Barrier(thread_count + 1) + reader = threading.Thread(target=reader, args=(f, barrier)) + writers = [threading.Thread(target=writer, args=(f, barrier)) + for _ in range(thread_count)] + with threading_helper.catch_threading_exception() as cm: + with threading_helper.start_threads(writers + [reader]): + pass + self.assertIsNone(cm.exc_type) + + self.assertEqual(os.stat(os_helper.TESTFN).st_size, + write_count * thread_count) + class CIOTest(IOTest): - # TODO: RUSTPYTHON, cyclic gc - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; cyclic gc def test_IOBase_finalize(self): # Issue #12149: segmentation fault on _PyIOBase_finalize when both a # class which inherits IOBase and an object of this class are caught @@ -1071,10 +1092,9 @@ def close(self): support.gc_collect() self.assertIsNone(wr(), wr) - # TODO: RUSTPYTHON, AssertionError: filter ('', ResourceWarning) did not catch any warning - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: filter ('', ResourceWarning) did not catch any warning def test_destructor(self): - super().test_destructor(self) + return super().test_destructor() @support.cpython_only class TestIOCTypes(unittest.TestCase): @@ -1165,9 +1185,32 @@ def test_disallow_instantiation(self): _io = self._io support.check_disallow_instantiation(self, _io._BytesIOBuffer) + def test_stringio_setstate(self): + # gh-127182: Calling __setstate__() with invalid arguments must not crash + obj = self._io.StringIO() + with self.assertRaisesRegex( + TypeError, + 'initial_value must be str or None, not int', + ): + obj.__setstate__((1, '', 0, {})) + + obj.__setstate__((None, '', 0, {})) # should not crash + self.assertEqual(obj.getvalue(), '') + + obj.__setstate__(('', '', 0, {})) + self.assertEqual(obj.getvalue(), '') + class PyIOTest(IOTest): pass + @unittest.expectedFailure # TODO: RUSTPYTHON; OSError: Negative file descriptor + def test_bad_opener_negative_1(): + return super().test_bad_opener_negative_1() + + @unittest.expectedFailure # TODO: RUSTPYTHON; OSError: Negative file descriptor + def test_bad_opener_other_negative(): + return super().test_bad_opener_other_negative() + @support.cpython_only class APIMismatchTest(unittest.TestCase): @@ -1175,7 +1218,7 @@ class APIMismatchTest(unittest.TestCase): def test_RawIOBase_io_in_pyio_match(self): """Test that pyio RawIOBase class has all c RawIOBase methods""" mismatch = support.detect_api_mismatch(pyio.RawIOBase, io.RawIOBase, - ignore=('__weakref__',)) + ignore=('__weakref__', '__static_attributes__')) self.assertEqual(mismatch, set(), msg='Python RawIOBase does not have all C RawIOBase methods') def test_RawIOBase_pyio_in_io_match(self): @@ -1244,6 +1287,7 @@ def _with(): # a ValueError. self.assertRaises(ValueError, _with) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_error_through_destructor(self): # Test that the exception state is not modified by a destructor, # even if close() fails. @@ -1252,10 +1296,7 @@ def test_error_through_destructor(self): with self.assertRaises(AttributeError): self.tp(rawio).xyzzy - if not IOBASE_EMITS_UNRAISABLE: - self.assertIsNone(cm.unraisable) - elif cm.unraisable is not None: - self.assertEqual(cm.unraisable.exc_type, OSError) + self.assertEqual(cm.unraisable.exc_type, OSError) def test_repr(self): raw = self.MockRawIO() @@ -1271,11 +1312,9 @@ def test_recursive_repr(self): # Issue #25455 raw = self.MockRawIO() b = self.tp(raw) - with support.swap_attr(raw, 'name', b): - try: + with support.swap_attr(raw, 'name', b), support.infinite_recursion(25): + with self.assertRaises(RuntimeError): repr(b) # Should not crash - except RuntimeError: - pass def test_flush_error_on_close(self): # Test that buffered file is closed despite failed flush @@ -1356,6 +1395,28 @@ def test_readonly_attributes(self): with self.assertRaises(AttributeError): buf.raw = x + def test_pickling_subclass(self): + global MyBufferedIO + class MyBufferedIO(self.tp): + def __init__(self, raw, tag): + super().__init__(raw) + self.tag = tag + def __getstate__(self): + return self.tag, self.raw.getvalue() + def __setstate__(slf, state): + tag, value = state + slf.__init__(self.BytesIO(value), tag) + + raw = self.BytesIO(b'data') + buf = MyBufferedIO(raw, tag='ham') + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=proto): + pickled = pickle.dumps(buf, proto) + newbuf = pickle.loads(pickled) + self.assertEqual(newbuf.raw.getvalue(), b'data') + self.assertEqual(newbuf.tag, 'ham') + del MyBufferedIO + class SizeofTest: @@ -1717,20 +1778,6 @@ def test_seek_character_device_file(self): class CBufferedReaderTest(BufferedReaderTest, SizeofTest): tp = io.BufferedReader - @unittest.skip("TODO: RUSTPYTHON, fallible allocation") - @skip_if_sanitizer(memory=True, address=True, thread=True, - reason="sanitizer defaults to crashing " - "instead of returning NULL for malloc failure.") - def test_constructor(self): - BufferedReaderTest.test_constructor(self) - # The allocation can succeed on 32-bit builds, e.g. with more - # than 2 GiB RAM and a 64-bit kernel. - if sys.maxsize > 0x7FFFFFFF: - rawio = self.MockRawIO() - bufio = self.tp(rawio) - self.assertRaises((OverflowError, MemoryError, ValueError), - bufio.__init__, rawio, sys.maxsize) - def test_initialization(self): rawio = self.MockRawIO([b"abc"]) bufio = self.tp(rawio) @@ -1748,8 +1795,7 @@ def test_misbehaved_io_read(self): # checking this is not so easy. self.assertRaises(OSError, bufio.read, 10) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): # C BufferedReader objects are collected. # The Python version has __del__, so it ends into gc.garbage instead @@ -1766,40 +1812,44 @@ def test_garbage_collection(self): def test_args_error(self): # Issue #17275 with self.assertRaisesRegex(TypeError, "BufferedReader"): - self.tp(io.BytesIO(), 1024, 1024, 1024) + self.tp(self.BytesIO(), 1024, 1024, 1024) def test_bad_readinto_value(self): - rawio = io.BufferedReader(io.BytesIO(b"12")) + rawio = self.tp(self.BytesIO(b"12")) rawio.readinto = lambda buf: -1 bufio = self.tp(rawio) with self.assertRaises(OSError) as cm: bufio.readline() self.assertIsNone(cm.exception.__cause__) - # TODO: RUSTPYTHON, TypeError: 'bytes' object cannot be interpreted as an integer") - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: 'bytes' object cannot be interpreted as an integer") def test_bad_readinto_type(self): - rawio = io.BufferedReader(io.BytesIO(b"12")) + rawio = self.tp(self.BytesIO(b"12")) rawio.readinto = lambda buf: b'' bufio = self.tp(rawio) with self.assertRaises(OSError) as cm: bufio.readline() self.assertIsInstance(cm.exception.__cause__, TypeError) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_flush_error_on_close(self): - super().test_flush_error_on_close() - - # TODO: RUSTPYTHON, AssertionError: UnsupportedOperation not raised by truncate - @unittest.expectedFailure - def test_truncate_on_read_only(self): # TODO: RUSTPYTHON, remove when this passes - super().test_truncate_on_read_only() # TODO: RUSTPYTHON, remove when this passes + return super().test_flush_error_on_close() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_seek_character_device_file(self): - super().test_seek_character_device_file() + return super().test_seek_character_device_file() + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: UnsupportedOperation not raised by truncate + def test_truncate_on_read_only(self): + return super().test_truncate_on_read_only() + + @unittest.skip('TODO: RUSTPYTHON; fallible allocation') + def test_constructor(self): + return super().test_constructor() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_pickling_subclass(self): + return super().test_pickling_subclass() class PyBufferedReaderTest(BufferedReaderTest): @@ -1909,8 +1959,7 @@ def _seekrel(bufio): def test_writes_and_truncates(self): self.check_writes(lambda bufio: bufio.truncate(bufio.tell())) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_non_blocking(self): raw = self.MockNonBlockWriterIO() bufio = self.tp(raw, 8) @@ -2107,20 +2156,6 @@ def test_slow_close_from_thread(self): class CBufferedWriterTest(BufferedWriterTest, SizeofTest): tp = io.BufferedWriter - @unittest.skip("TODO: RUSTPYTHON, fallible allocation") - @skip_if_sanitizer(memory=True, address=True, thread=True, - reason="sanitizer defaults to crashing " - "instead of returning NULL for malloc failure.") - def test_constructor(self): - BufferedWriterTest.test_constructor(self) - # The allocation can succeed on 32-bit builds, e.g. with more - # than 2 GiB RAM and a 64-bit kernel. - if sys.maxsize > 0x7FFFFFFF: - rawio = self.MockRawIO() - bufio = self.tp(rawio) - self.assertRaises((OverflowError, MemoryError, ValueError), - bufio.__init__, rawio, sys.maxsize) - def test_initialization(self): rawio = self.MockRawIO() bufio = self.tp(rawio) @@ -2131,8 +2166,7 @@ def test_initialization(self): self.assertRaises(ValueError, bufio.__init__, rawio, buffer_size=-1) self.assertRaises(ValueError, bufio.write, b"def") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): # C BufferedWriter objects are collected, and collecting them flushes # all data to disk. @@ -2155,11 +2189,17 @@ def test_args_error(self): with self.assertRaisesRegex(TypeError, "BufferedWriter"): self.tp(self.BytesIO(), 1024, 1024, 1024) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_flush_error_on_close(self): - super().test_flush_error_on_close() + return super().test_flush_error_on_close() + + @unittest.skip('TODO: RUSTPYTHON; fallible allocation') + def test_constructor(self): + return super().test_constructor() + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_pickling_subclass(self): + return super().test_pickling_subclass() class PyBufferedWriterTest(BufferedWriterTest): tp = pyio.BufferedWriter @@ -2637,22 +2677,7 @@ def test_interleaved_readline_write(self): class CBufferedRandomTest(BufferedRandomTest, SizeofTest): tp = io.BufferedRandom - @unittest.skip("TODO: RUSTPYTHON, fallible allocation") - @skip_if_sanitizer(memory=True, address=True, thread=True, - reason="sanitizer defaults to crashing " - "instead of returning NULL for malloc failure.") - def test_constructor(self): - BufferedRandomTest.test_constructor(self) - # The allocation can succeed on 32-bit builds, e.g. with more - # than 2 GiB RAM and a 64-bit kernel. - if sys.maxsize > 0x7FFFFFFF: - rawio = self.MockRawIO() - bufio = self.tp(rawio) - self.assertRaises((OverflowError, MemoryError, ValueError), - bufio.__init__, rawio, sys.maxsize) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): CBufferedReaderTest.test_garbage_collection(self) CBufferedWriterTest.test_garbage_collection(self) @@ -2662,20 +2687,25 @@ def test_args_error(self): with self.assertRaisesRegex(TypeError, "BufferedRandom"): self.tp(self.BytesIO(), 1024, 1024, 1024) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_flush_error_on_close(self): - super().test_flush_error_on_close() + return super().test_flush_error_on_close() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_seek_character_device_file(self): - super().test_seek_character_device_file() + return super().test_seek_character_device_file() - # TODO: RUSTPYTHON; f.read1(1) returns b'a' - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; f.read1(1) returns b'a' def test_read1_after_write(self): - super().test_read1_after_write() + return super().test_read1_after_write() + + @unittest.skip('TODO: RUSTPYTHON; fallible allocation') + def test_constructor(self): + return super().test_constructor() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_pickling_subclass(self): + return super().test_pickling_subclass() class PyBufferedRandomTest(BufferedRandomTest): @@ -2935,11 +2965,16 @@ def test_recursive_repr(self): # Issue #25455 raw = self.BytesIO() t = self.TextIOWrapper(raw, encoding="utf-8") - with support.swap_attr(raw, 'name', t): - try: + with support.swap_attr(raw, 'name', t), support.infinite_recursion(25): + with self.assertRaises(RuntimeError): repr(t) # Should not crash - except RuntimeError: - pass + + def test_subclass_repr(self): + class TestSubclass(self.TextIOWrapper): + pass + + f = TestSubclass(self.StringIO()) + self.assertIn(TestSubclass.__name__, repr(f)) def test_line_buffering(self): r = self.BytesIO() @@ -3166,6 +3201,7 @@ def flush(self): support.gc_collect() self.assertEqual(record, [1, 2, 3]) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_error_through_destructor(self): # Test that the exception state is not modified by a destructor, # even if close() fails. @@ -3174,10 +3210,7 @@ def test_error_through_destructor(self): with self.assertRaises(AttributeError): self.TextIOWrapper(rawio, encoding="utf-8").xyzzy - if not IOBASE_EMITS_UNRAISABLE: - self.assertIsNone(cm.unraisable) - elif cm.unraisable is not None: - self.assertEqual(cm.unraisable.exc_type, OSError) + self.assertEqual(cm.unraisable.exc_type, OSError) # Systematic tests of the text I/O API @@ -3327,8 +3360,7 @@ def test_seek_and_tell_with_data(data, min_pos=0): finally: StatefulIncrementalDecoder.codecEnabled = 0 - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_multibyte_seek_and_tell(self): f = self.open(os_helper.TESTFN, "w", encoding="euc_jp") f.write("AB\n\u3046\u3048\n") @@ -3344,8 +3376,7 @@ def test_multibyte_seek_and_tell(self): self.assertEqual(f.tell(), p1) f.close() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_seek_with_encoder_state(self): f = self.open(os_helper.TESTFN, "w", encoding="euc_jis_2004") f.write("\u00e6\u0300") @@ -3359,8 +3390,7 @@ def test_seek_with_encoder_state(self): self.assertEqual(f.readline(), "\u00e6\u0300\u0300") f.close() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_encoded_writes(self): data = "1234567890" tests = ("utf-16", @@ -3499,8 +3529,7 @@ def test_issue2282(self): self.assertEqual(buffer.seekable(), txt.seekable()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_append_bom(self): # The BOM is not written again when appending to a non-empty file filename = os_helper.TESTFN @@ -3516,8 +3545,7 @@ def test_append_bom(self): with self.open(filename, 'rb') as f: self.assertEqual(f.read(), 'aaaxxx'.encode(charset)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_seek_bom(self): # Same test, but when seeking manually filename = os_helper.TESTFN @@ -3533,8 +3561,7 @@ def test_seek_bom(self): with self.open(filename, 'rb') as f: self.assertEqual(f.read(), 'bbbzzz'.encode(charset)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_seek_append_bom(self): # Same test, but first seek to the start and then to the end filename = os_helper.TESTFN @@ -3795,17 +3822,14 @@ def _check_create_at_shutdown(self, **kwargs): codecs.lookup('utf-8') class C: - def __init__(self): - self.buf = io.BytesIO() def __del__(self): - io.TextIOWrapper(self.buf, **{kwargs}) + io.TextIOWrapper(io.BytesIO(), **{kwargs}) print("ok") c = C() """.format(iomod=iomod, kwargs=kwargs) return assert_python_ok("-c", code) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_create_at_shutdown_without_encoding(self): rc, out, err = self._check_create_at_shutdown() if err: @@ -3815,8 +3839,7 @@ def test_create_at_shutdown_without_encoding(self): else: self.assertEqual("ok", out.decode().strip()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_create_at_shutdown_with_encoding(self): rc, out, err = self._check_create_at_shutdown(encoding='utf-8', errors='strict') @@ -4042,6 +4065,28 @@ def test_issue35928(self): f.write(res) self.assertEqual(res + f.readline(), 'foo\nbar\n') + def test_pickling_subclass(self): + global MyTextIO + class MyTextIO(self.TextIOWrapper): + def __init__(self, raw, tag): + super().__init__(raw) + self.tag = tag + def __getstate__(self): + return self.tag, self.buffer.getvalue() + def __setstate__(slf, state): + tag, value = state + slf.__init__(self.BytesIO(value), tag) + + raw = self.BytesIO(b'data') + txt = MyTextIO(raw, 'ham') + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=proto): + pickled = pickle.dumps(txt, proto) + newtxt = pickle.loads(pickled) + self.assertEqual(newtxt.buffer.getvalue(), b'data') + self.assertEqual(newtxt.tag, 'ham') + del MyTextIO + class MemviewBytesIO(io.BytesIO): '''A BytesIO object whose read method returns memoryviews @@ -4066,98 +4111,7 @@ class CTextIOWrapperTest(TextIOWrapperTest): io = io shutdown_error = "LookupError: unknown encoding: ascii" - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_constructor(self): - super().test_constructor() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_detach(self): - super().test_detach() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_reconfigure_encoding_read(self): - super().test_reconfigure_encoding_read() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_reconfigure_line_buffering(self): - super().test_reconfigure_line_buffering() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_basic_io(self): - super().test_basic_io() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_telling(self): - super().test_telling() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_uninitialized(self): - super().test_uninitialized() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_non_text_encoding_codecs_are_rejected(self): - super().test_non_text_encoding_codecs_are_rejected() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_repr(self): - super().test_repr() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_newlines(self): - super().test_newlines() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_newlines_input(self): - super().test_newlines_input() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_reconfigure_write_through(self): - super().test_reconfigure_write_through() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_reconfigure_write_fromascii(self): - super().test_reconfigure_write_fromascii() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_reconfigure_write(self): - super().test_reconfigure_write() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_reconfigure_defaults(self): - super().test_reconfigure_defaults() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_reconfigure_newline(self): - super().test_reconfigure_newline() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_reconfigure_errors(self): - super().test_reconfigure_errors() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_reconfigure_locale(self): - super().test_reconfigure_locale() - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_initialization(self): r = self.BytesIO(b"\xc3\xa9\n\n") b = self.BufferedReader(r, 1000) @@ -4168,8 +4122,7 @@ def test_initialization(self): t = self.TextIOWrapper.__new__(self.TextIOWrapper) self.assertRaises(Exception, repr, t) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): # C TextIOWrapper objects are collected, and collecting them flushes # all data to disk. @@ -4233,20 +4186,121 @@ def write(self, data): t.write("x"*chunk_size) self.assertEqual([b"abcdef", b"ghi", b"x"*chunk_size], buf._write_stack) + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_issue119506(self): + chunk_size = 8192 + + class MockIO(self.MockRawIO): + written = False + def write(self, data): + if not self.written: + self.written = True + t.write("middle") + return super().write(data) + + buf = MockIO() + t = self.TextIOWrapper(buf) + t.write("abc") + t.write("def") + # writing data which size >= chunk_size cause flushing buffer before write. + t.write("g" * chunk_size) + t.flush() + + self.assertEqual([b"abcdef", b"middle", b"g"*chunk_size], + buf._write_stack) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_basic_io(self): + return super().test_basic_io() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_constructor(self): + return super().test_constructor() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_detach(self): + return super().test_detach() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_newlines(self): + return super().test_newlines() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_newlines_input(self): + return super().test_newlines_input() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_non_text_encoding_codecs_are_rejected(self): + return super().test_non_text_encoding_codecs_are_rejected() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_reconfigure_defaults(self): + return super().test_reconfigure_defaults() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_reconfigure_encoding_read(self): + return super().test_reconfigure_encoding_read() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_reconfigure_errors(self): + return super().test_reconfigure_errors() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_reconfigure_line_buffering(self): + return super().test_reconfigure_line_buffering() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_reconfigure_locale(self): + return super().test_reconfigure_locale() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_reconfigure_newline(self): + return super().test_reconfigure_newline() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_reconfigure_write(self): + return super().test_reconfigure_write() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_reconfigure_write_fromascii(self): + return super().test_reconfigure_write_fromascii() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_reconfigure_write_through(self): + return super().test_reconfigure_write_through() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_repr(self): + return super().test_repr() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_telling(self): + return super().test_telling() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_uninitialized(self): + return super().test_uninitialized() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_recursive_repr(self): + return super().test_recursive_repr() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_pickling_subclass(self): + return super().test_pickling_subclass() + class PyTextIOWrapperTest(TextIOWrapperTest): io = pyio shutdown_error = "LookupError: unknown encoding: ascii" - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ValueError not raised def test_constructor(self): - super().test_constructor() + return super().test_constructor() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_newlines(self): - super().test_newlines() + return super().test_newlines() class IncrementalNewlineDecoderTest(unittest.TestCase): @@ -4326,8 +4380,7 @@ def _decode_bytewise(s): self.assertEqual(decoder.decode(input), "abc") self.assertEqual(decoder.newlines, None) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_newline_decoder(self): encodings = ( # None meaning the IncrementalNewlineDecoder takes unicode input @@ -4489,8 +4542,7 @@ def test_io_after_close(self): self.assertRaises(ValueError, f.writelines, []) self.assertRaises(ValueError, next, f) - # TODO: RUSTPYTHON, cyclic gc - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; cyclic gc def test_blockingioerror(self): # Various BlockingIOError issues class C(str): @@ -4538,15 +4590,14 @@ def test_abc_inheritance_official(self): self._check_abc_inheritance(io) def _check_warn_on_dealloc(self, *args, **kwargs): - f = open(*args, **kwargs) + f = self.open(*args, **kwargs) r = repr(f) with self.assertWarns(ResourceWarning) as cm: f = None support.gc_collect() self.assertIn(r, str(cm.warning.args[0])) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_warn_on_dealloc(self): self._check_warn_on_dealloc(os_helper.TESTFN, "wb", buffering=0) self._check_warn_on_dealloc(os_helper.TESTFN, "wb") @@ -4569,10 +4620,9 @@ def cleanup_fds(): r, w = os.pipe() fds += r, w with warnings_helper.check_no_resource_warning(self): - open(r, *args, closefd=False, **kwargs) + self.open(r, *args, closefd=False, **kwargs) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") def test_warn_on_dealloc_fd(self): self._check_warn_on_dealloc_fd("rb", buffering=0) @@ -4602,16 +4652,14 @@ def test_pickling(self): with self.assertRaisesRegex(TypeError, msg): pickle.dumps(f, protocol) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf( support.is_emscripten, "fstat() of a pipe fd is not supported" ) def test_nonblock_pipe_write_bigbuf(self): self._test_nonblock_pipe_write(16*1024) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf( support.is_emscripten, "fstat() of a pipe fd is not supported" ) @@ -4733,8 +4781,7 @@ def test_check_encoding_errors(self): proc = assert_python_failure('-X', 'dev', '-c', code) self.assertEqual(proc.rc, 10, proc) - # TODO: RUSTPYTHON, AssertionError: 0 != 2 - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 2 def test_check_encoding_warning(self): # PEP 597: Raise warning when encoding is not specified # and sys.flags.warn_default_encoding is set. @@ -4758,8 +4805,7 @@ def test_check_encoding_warning(self): self.assertTrue( warnings[1].startswith(b"<string>:8: EncodingWarning: ")) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_text_encoding(self): # PEP 597, bpo-47000. io.text_encoding() returns "locale" or "utf-8" # based on sys.flags.utf8_mode @@ -4837,10 +4883,9 @@ def test_daemon_threads_shutdown_stdout_deadlock(self): def test_daemon_threads_shutdown_stderr_deadlock(self): self.check_daemon_threads_shutdown_deadlock('stderr') - # TODO: RUSTPYTHON, AssertionError: 22 != 10 : _PythonRunResult(rc=22, out=b'', err=b'') - @unittest.expectedFailure - def test_check_encoding_errors(self): # TODO: RUSTPYTHON, remove when this passes - super().test_check_encoding_errors() # TODO: RUSTPYTHON, remove when this passes + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 22 != 10 : _PythonRunResult(rc=22, out=b'', err=b'') + def test_check_encoding_errors(self): + return super().test_check_encoding_errors() class PyMiscIOTest(MiscIOTest): @@ -5014,16 +5059,14 @@ def alarm_handler(sig, frame): os.close(w) os.close(r) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @requires_alarm @support.requires_resource('walltime') def test_interrupted_read_retry_buffered(self): self.check_interrupted_read_retry(lambda x: x.decode('latin1'), mode="rb") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @requires_alarm @support.requires_resource('walltime') def test_interrupted_read_retry_text(self): @@ -5098,15 +5141,13 @@ def alarm2(sig, frame): if e.errno != errno.EBADF: raise - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @requires_alarm @support.requires_resource('walltime') def test_interrupted_write_retry_buffered(self): self.check_interrupted_write_retry(b"x", mode="wb") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @requires_alarm @support.requires_resource('walltime') def test_interrupted_write_retry_text(self): From b7d9d7d9ae33cad222ff47859a695f25adfd432f Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:57:36 +0200 Subject: [PATCH 265/819] Update `test/test_fstring.py` from 3.13.7 (#6154) * Update `test_fstring.py` from 3.13.7 * Patch failing tests --- Lib/test/test_fstring.py | 1925 ++++++++++++++++++-------------------- 1 file changed, 932 insertions(+), 993 deletions(-) diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index 4996eedc1c0..cc9f066b141 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -9,6 +9,7 @@ import ast import datetime +import dis import os import re import types @@ -19,7 +20,7 @@ from test.support.os_helper import temp_cwd from test.support.script_helper import assert_python_failure, assert_python_ok -a_global = "global variable" +a_global = 'global variable' # You could argue that I'm too strict in looking for specific error # values with assertRaisesRegex, but without it it's way too easy to @@ -28,7 +29,6 @@ # worthwhile tradeoff. When I switched to this method, I found many # examples where I wasn't testing what I thought I was. - class TestCase(unittest.TestCase): def assertAllRaise(self, exception_type, regex, error_strings): for str in error_strings: @@ -40,45 +40,43 @@ def test__format__lookup(self): # Make sure __format__ is looked up on the type, not the instance. class X: def __format__(self, spec): - return "class" + return 'class' x = X() # Add a bound __format__ method to the 'y' instance, but not # the 'x' instance. y = X() - y.__format__ = types.MethodType(lambda self, spec: "instance", y) + y.__format__ = types.MethodType(lambda self, spec: 'instance', y) - self.assertEqual(f"{y}", format(y)) - self.assertEqual(f"{y}", "class") + self.assertEqual(f'{y}', format(y)) + self.assertEqual(f'{y}', 'class') self.assertEqual(format(x), format(y)) # __format__ is not called this way, but still make sure it # returns what we expect (so we can make sure we're bypassing # it). - self.assertEqual(x.__format__(""), "class") - self.assertEqual(y.__format__(""), "instance") + self.assertEqual(x.__format__(''), 'class') + self.assertEqual(y.__format__(''), 'instance') # This is how __format__ is actually called. - self.assertEqual(type(x).__format__(x, ""), "class") - self.assertEqual(type(y).__format__(y, ""), "class") + self.assertEqual(type(x).__format__(x, ''), 'class') + self.assertEqual(type(y).__format__(y, ''), 'class') def test_ast(self): # Inspired by http://bugs.python.org/issue24975 class X: def __init__(self): self.called = False - def __call__(self): self.called = True return 4 - x = X() expr = """ a = 10 f'{a * x()}'""" t = ast.parse(expr) - c = compile(t, "", "exec") + c = compile(t, '', 'exec') # Make sure x was not called. self.assertFalse(x.called) @@ -284,6 +282,7 @@ def test_ast_line_numbers_duplicate_expression(self): self.assertEqual(binop.right.col_offset, 27) def test_ast_numbers_fstring_with_formatting(self): + t = ast.parse('f"Here is that pesky {xxx:.3f} again"') self.assertEqual(len(t.body), 1) self.assertEqual(t.body[0].lineno, 1) @@ -384,8 +383,7 @@ def test_ast_line_numbers_multiline_fstring(self): self.assertEqual(t.body[0].value.values[1].value.col_offset, 11) self.assertEqual(t.body[0].value.values[1].value.end_col_offset, 16) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_ast_line_numbers_with_parentheses(self): expr = """ x = ( @@ -441,12 +439,24 @@ def test_ast_line_numbers_with_parentheses(self): x, y = t.body # Check the single quoted string offsets first. - offsets = [(elt.col_offset, elt.end_col_offset) for elt in x.value.elts] - self.assertTrue(all(offset == (4, 10) for offset in offsets)) + offsets = [ + (elt.col_offset, elt.end_col_offset) + for elt in x.value.elts + ] + self.assertTrue(all( + offset == (4, 10) + for offset in offsets + )) # Check the triple quoted string offsets. - offsets = [(elt.col_offset, elt.end_col_offset) for elt in y.value.elts] - self.assertTrue(all(offset == (4, 14) for offset in offsets)) + offsets = [ + (elt.col_offset, elt.end_col_offset) + for elt in y.value.elts + ] + self.assertTrue(all( + offset == (4, 14) + for offset in offsets + )) expr = """ x = ( @@ -507,612 +517,530 @@ def test_ast_fstring_empty_format_spec(self): self.assertEqual(type(format_spec), ast.JoinedStr) self.assertEqual(len(format_spec.values), 0) + def test_ast_fstring_format_spec(self): + expr = "f'{1:{name}}'" + + mod = ast.parse(expr) + self.assertEqual(type(mod), ast.Module) + self.assertEqual(len(mod.body), 1) + + fstring = mod.body[0].value + self.assertEqual(type(fstring), ast.JoinedStr) + self.assertEqual(len(fstring.values), 1) + + fv = fstring.values[0] + self.assertEqual(type(fv), ast.FormattedValue) + + format_spec = fv.format_spec + self.assertEqual(type(format_spec), ast.JoinedStr) + self.assertEqual(len(format_spec.values), 1) + + format_spec_value = format_spec.values[0] + self.assertEqual(type(format_spec_value), ast.FormattedValue) + self.assertEqual(format_spec_value.value.id, 'name') + + expr = "f'{1:{name1}{name2}}'" + + mod = ast.parse(expr) + self.assertEqual(type(mod), ast.Module) + self.assertEqual(len(mod.body), 1) + + fstring = mod.body[0].value + self.assertEqual(type(fstring), ast.JoinedStr) + self.assertEqual(len(fstring.values), 1) + + fv = fstring.values[0] + self.assertEqual(type(fv), ast.FormattedValue) + + format_spec = fv.format_spec + self.assertEqual(type(format_spec), ast.JoinedStr) + self.assertEqual(len(format_spec.values), 2) + + format_spec_value = format_spec.values[0] + self.assertEqual(type(format_spec_value), ast.FormattedValue) + self.assertEqual(format_spec_value.value.id, 'name1') + + format_spec_value = format_spec.values[1] + self.assertEqual(type(format_spec_value), ast.FormattedValue) + self.assertEqual(format_spec_value.value.id, 'name2') + + def test_docstring(self): def f(): - f"""Not a docstring""" - + f'''Not a docstring''' self.assertIsNone(f.__doc__) - def g(): - """Not a docstring""" f"" - + '''Not a docstring''' \ + f'' self.assertIsNone(g.__doc__) def test_literal_eval(self): - with self.assertRaisesRegex(ValueError, "malformed node or string"): + with self.assertRaisesRegex(ValueError, 'malformed node or string'): ast.literal_eval("f'x'") def test_ast_compile_time_concat(self): - x = [""] + x = [''] expr = """x[0] = 'foo' f'{3}'""" t = ast.parse(expr) - c = compile(t, "", "exec") + c = compile(t, '', 'exec') exec(c) - self.assertEqual(x[0], "foo3") + self.assertEqual(x[0], 'foo3') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_compile_time_concat_errors(self): - self.assertAllRaise( - SyntaxError, - "cannot mix bytes and nonbytes literals", - [ - r"""f'' b''""", - r"""b'' f''""", - ], - ) + self.assertAllRaise(SyntaxError, + 'cannot mix bytes and nonbytes literals', + [r"""f'' b''""", + r"""b'' f''""", + ]) def test_literal(self): - self.assertEqual(f"", "") - self.assertEqual(f"a", "a") - self.assertEqual(f" ", " ") + self.assertEqual(f'', '') + self.assertEqual(f'a', 'a') + self.assertEqual(f' ', ' ') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unterminated_string(self): - self.assertAllRaise( - SyntaxError, - "unterminated string", - [ - r"""f'{"x'""", - r"""f'{"x}'""", - r"""f'{("x'""", - r"""f'{("x}'""", - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertAllRaise(SyntaxError, 'unterminated string', + [r"""f'{"x'""", + r"""f'{"x}'""", + r"""f'{("x'""", + r"""f'{("x}'""", + ]) + + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") def test_mismatched_parens(self): - self.assertAllRaise( - SyntaxError, - r"closing parenthesis '\}' " r"does not match opening parenthesis '\('", - [ - "f'{((}'", - ], - ) - self.assertAllRaise( - SyntaxError, - r"closing parenthesis '\)' " r"does not match opening parenthesis '\['", - [ - "f'{a[4)}'", - ], - ) - self.assertAllRaise( - SyntaxError, - r"closing parenthesis '\]' " r"does not match opening parenthesis '\('", - [ - "f'{a(4]}'", - ], - ) - self.assertAllRaise( - SyntaxError, - r"closing parenthesis '\}' " r"does not match opening parenthesis '\['", - [ - "f'{a[4}'", - ], - ) - self.assertAllRaise( - SyntaxError, - r"closing parenthesis '\}' " r"does not match opening parenthesis '\('", - [ - "f'{a(4}'", - ], - ) - self.assertRaises(SyntaxError, eval, "f'{" + "(" * 500 + "}'") - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' " + r"does not match opening parenthesis '\('", + ["f'{((}'", + ]) + self.assertAllRaise(SyntaxError, r"closing parenthesis '\)' " + r"does not match opening parenthesis '\['", + ["f'{a[4)}'", + ]) + self.assertAllRaise(SyntaxError, r"closing parenthesis '\]' " + r"does not match opening parenthesis '\('", + ["f'{a(4]}'", + ]) + self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' " + r"does not match opening parenthesis '\['", + ["f'{a[4}'", + ]) + self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' " + r"does not match opening parenthesis '\('", + ["f'{a(4}'", + ]) + self.assertRaises(SyntaxError, eval, "f'{" + "("*500 + "}'") + + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") def test_fstring_nested_too_deeply(self): - self.assertAllRaise( - SyntaxError, - "f-string: expressions nested too deeply", - ['f"{1+2:{1+2:{1+1:{1}}}}"'], - ) + self.assertAllRaise(SyntaxError, + "f-string: expressions nested too deeply", + ['f"{1+2:{1+2:{1+1:{1}}}}"']) def create_nested_fstring(n): if n == 0: return "1+1" - prev = create_nested_fstring(n - 1) + prev = create_nested_fstring(n-1) return f'f"{{{prev}}}"' - self.assertAllRaise( - SyntaxError, "too many nested f-strings", [create_nested_fstring(160)] - ) + self.assertAllRaise(SyntaxError, + "too many nested f-strings", + [create_nested_fstring(160)]) def test_syntax_error_in_nested_fstring(self): # See gh-104016 for more information on this crash - self.assertAllRaise( - SyntaxError, "invalid syntax", ['f"{1 1:' + ('{f"1:' * 199)] - ) + self.assertAllRaise(SyntaxError, + "invalid syntax", + ['f"{1 1:' + ('{f"1:' * 199)]) def test_double_braces(self): - self.assertEqual(f"{{", "{") - self.assertEqual(f"a{{", "a{") - self.assertEqual(f"{{b", "{b") - self.assertEqual(f"a{{b", "a{b") - self.assertEqual(f"}}", "}") - self.assertEqual(f"a}}", "a}") - self.assertEqual(f"}}b", "}b") - self.assertEqual(f"a}}b", "a}b") - self.assertEqual(f"{{}}", "{}") - self.assertEqual(f"a{{}}", "a{}") - self.assertEqual(f"{{b}}", "{b}") - self.assertEqual(f"{{}}c", "{}c") - self.assertEqual(f"a{{b}}", "a{b}") - self.assertEqual(f"a{{}}c", "a{}c") - self.assertEqual(f"{{b}}c", "{b}c") - self.assertEqual(f"a{{b}}c", "a{b}c") - - self.assertEqual(f"{{{10}", "{10") - self.assertEqual(f"}}{10}", "}10") - self.assertEqual(f"}}{{{10}", "}{10") - self.assertEqual(f"}}a{{{10}", "}a{10") - - self.assertEqual(f"{10}{{", "10{") - self.assertEqual(f"{10}}}", "10}") - self.assertEqual(f"{10}}}{{", "10}{") - self.assertEqual(f"{10}}}a{{" "}", "10}a{}") + self.assertEqual(f'{{', '{') + self.assertEqual(f'a{{', 'a{') + self.assertEqual(f'{{b', '{b') + self.assertEqual(f'a{{b', 'a{b') + self.assertEqual(f'}}', '}') + self.assertEqual(f'a}}', 'a}') + self.assertEqual(f'}}b', '}b') + self.assertEqual(f'a}}b', 'a}b') + self.assertEqual(f'{{}}', '{}') + self.assertEqual(f'a{{}}', 'a{}') + self.assertEqual(f'{{b}}', '{b}') + self.assertEqual(f'{{}}c', '{}c') + self.assertEqual(f'a{{b}}', 'a{b}') + self.assertEqual(f'a{{}}c', 'a{}c') + self.assertEqual(f'{{b}}c', '{b}c') + self.assertEqual(f'a{{b}}c', 'a{b}c') + + self.assertEqual(f'{{{10}', '{10') + self.assertEqual(f'}}{10}', '}10') + self.assertEqual(f'}}{{{10}', '}{10') + self.assertEqual(f'}}a{{{10}', '}a{10') + + self.assertEqual(f'{10}{{', '10{') + self.assertEqual(f'{10}}}', '10}') + self.assertEqual(f'{10}}}{{', '10}{') + self.assertEqual(f'{10}}}a{{' '}', '10}a{}') # Inside of strings, don't interpret doubled brackets. - self.assertEqual(f'{"{{}}"}', "{{}}") - - self.assertAllRaise( - TypeError, - "unhashable type", - [ - "f'{ {{}} }'", # dict in a set - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertEqual(f'{"{{}}"}', '{{}}') + + self.assertAllRaise(TypeError, 'unhashable type', + ["f'{ {{}} }'", # dict in a set + ]) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_compile_time_concat(self): - x = "def" - self.assertEqual("abc" f"## {x}ghi", "abc## defghi") - self.assertEqual("abc" f"{x}" "ghi", "abcdefghi") - self.assertEqual("abc" f"{x}" "gh" f"i{x:4}", "abcdefghidef ") - self.assertEqual("{x}" f"{x}", "{x}def") - self.assertEqual("{x" f"{x}", "{xdef") - self.assertEqual("{x}" f"{x}", "{x}def") - self.assertEqual("{{x}}" f"{x}", "{{x}}def") - self.assertEqual("{{x" f"{x}", "{{xdef") - self.assertEqual("x}}" f"{x}", "x}}def") - self.assertEqual(f"{x}" "x}}", "defx}}") - self.assertEqual(f"{x}" "", "def") - self.assertEqual("" f"{x}" "", "def") - self.assertEqual("" f"{x}", "def") - self.assertEqual(f"{x}" "2", "def2") - self.assertEqual("1" f"{x}" "2", "1def2") - self.assertEqual("1" f"{x}", "1def") - self.assertEqual(f"{x}" f"-{x}", "def-def") - self.assertEqual("" f"", "") - self.assertEqual("" f"" "", "") - self.assertEqual("" f"" "" f"", "") - self.assertEqual(f"", "") - self.assertEqual(f"" "", "") - self.assertEqual(f"" "" f"", "") - self.assertEqual(f"" "" f"" "", "") + x = 'def' + self.assertEqual('abc' f'## {x}ghi', 'abc## defghi') + self.assertEqual('abc' f'{x}' 'ghi', 'abcdefghi') + self.assertEqual('abc' f'{x}' 'gh' f'i{x:4}', 'abcdefghidef ') + self.assertEqual('{x}' f'{x}', '{x}def') + self.assertEqual('{x' f'{x}', '{xdef') + self.assertEqual('{x}' f'{x}', '{x}def') + self.assertEqual('{{x}}' f'{x}', '{{x}}def') + self.assertEqual('{{x' f'{x}', '{{xdef') + self.assertEqual('x}}' f'{x}', 'x}}def') + self.assertEqual(f'{x}' 'x}}', 'defx}}') + self.assertEqual(f'{x}' '', 'def') + self.assertEqual('' f'{x}' '', 'def') + self.assertEqual('' f'{x}', 'def') + self.assertEqual(f'{x}' '2', 'def2') + self.assertEqual('1' f'{x}' '2', '1def2') + self.assertEqual('1' f'{x}', '1def') + self.assertEqual(f'{x}' f'-{x}', 'def-def') + self.assertEqual('' f'', '') + self.assertEqual('' f'' '', '') + self.assertEqual('' f'' '' f'', '') + self.assertEqual(f'', '') + self.assertEqual(f'' '', '') + self.assertEqual(f'' '' f'', '') + self.assertEqual(f'' '' f'' '', '') # This is not really [f'{'] + [f'}'] since we treat the inside # of braces as a purely new context, so it is actually f'{ and # then eval(' f') (a valid expression) and then }' which would # constitute a valid f-string. - # TODO: RUSTPYTHON SyntaxError - # self.assertEqual(f'{' f'}', " f") - - self.assertAllRaise( - SyntaxError, - "expecting '}'", - [ - '''f'{3' f"}"''', # can't concat to get a valid f-string - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertEqual(f'{' f'}', ' f') + + self.assertAllRaise(SyntaxError, "expecting '}'", + ['''f'{3' f"}"''', # can't concat to get a valid f-string + ]) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_comments(self): # These aren't comments, since they're in strings. - d = {"#": "hash"} - self.assertEqual(f'{"#"}', "#") - self.assertEqual(f'{d["#"]}', "hash") - - self.assertAllRaise( - SyntaxError, - "'{' was never closed", - [ - "f'{1#}'", # error because everything after '#' is a comment - "f'{#}'", - "f'one: {1#}'", - "f'{1# one} {2 this is a comment still#}'", - ], - ) - self.assertAllRaise( - SyntaxError, - r"f-string: unmatched '\)'", - [ - "f'{)#}'", # When wrapped in parens, this becomes - # '()#)'. Make sure that doesn't compile. - ], - ) - self.assertEqual( - f"""A complex trick: { + d = {'#': 'hash'} + self.assertEqual(f'{"#"}', '#') + self.assertEqual(f'{d["#"]}', 'hash') + + self.assertAllRaise(SyntaxError, "'{' was never closed", + ["f'{1#}'", # error because everything after '#' is a comment + "f'{#}'", + "f'one: {1#}'", + "f'{1# one} {2 this is a comment still#}'", + ]) + self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'", + ["f'{)#}'", # When wrapped in parens, this becomes + # '()#)'. Make sure that doesn't compile. + ]) + self.assertEqual(f'''A complex trick: { 2 # two -}""", - "A complex trick: 2", - ) - self.assertEqual( - f""" +}''', 'A complex trick: 2') + self.assertEqual(f''' { 40 # forty + # plus 2 # two -}""", - "\n42", - ) - self.assertEqual( - f""" +}''', '\n42') + self.assertEqual(f''' { 40 # forty + # plus 2 # two -}""", - "\n42", - ) -# TODO: RUSTPYTHON SyntaxError -# self.assertEqual( -# f""" -# # this is not a comment -# { # the following operation it's -# 3 # this is a number -# * 2}""", -# "\n# this is not a comment\n6", -# ) - self.assertEqual( - f""" +}''', '\n42') + + self.assertEqual(f''' +# this is not a comment +{ # the following operation it's +3 # this is a number +* 2}''', '\n# this is not a comment\n6') + self.assertEqual(f''' {# f'a {comment}' 86 # constant # nothing more -}""", - "\n86", - ) - - self.assertAllRaise( - SyntaxError, - r"f-string: valid expression required before '}'", - [ - """f''' +}''', '\n86') + + self.assertAllRaise(SyntaxError, r"f-string: valid expression required before '}'", + ["""f''' { # only a comment }''' -""", # this is equivalent to f'{}' - ], - ) +""", # this is equivalent to f'{}' + ]) def test_many_expressions(self): # Create a string with many expressions in it. Note that # because we have a space in here as a literal, we're actually # going to use twice as many ast nodes: one for each literal # plus one for each expression. - def build_fstr(n, extra=""): - return "f'" + ("{x} " * n) + extra + "'" + def build_fstr(n, extra=''): + return "f'" + ('{x} ' * n) + extra + "'" - x = "X" + x = 'X' width = 1 # Test around 256. for i in range(250, 260): - self.assertEqual(eval(build_fstr(i)), (x + " ") * i) + self.assertEqual(eval(build_fstr(i)), (x+' ')*i) # Test concatenating 2 largs fstrings. - self.assertEqual(eval(build_fstr(255) * 256), (x + " ") * (255 * 256)) + self.assertEqual(eval(build_fstr(255)*256), (x+' ')*(255*256)) - s = build_fstr(253, "{x:{width}} ") - self.assertEqual(eval(s), (x + " ") * 254) + s = build_fstr(253, '{x:{width}} ') + self.assertEqual(eval(s), (x+' ')*254) # Test lots of expressions and constants, concatenated. s = "f'{1}' 'x' 'y'" * 1024 - self.assertEqual(eval(s), "1xy" * 1024) + self.assertEqual(eval(s), '1xy' * 1024) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_format_specifier_expressions(self): width = 10 precision = 4 - value = decimal.Decimal("12.34567") - self.assertEqual(f"result: {value:{width}.{precision}}", "result: 12.35") - self.assertEqual(f"result: {value:{width!r}.{precision}}", "result: 12.35") - self.assertEqual( - f"result: {value:{width:0}.{precision:1}}", "result: 12.35" - ) - self.assertEqual( - f"result: {value:{1}{0:0}.{precision:1}}", "result: 12.35" - ) - self.assertEqual( - f"result: {value:{ 1}{ 0:0}.{ precision:1}}", "result: 12.35" - ) - self.assertEqual(f"{10:#{1}0x}", " 0xa") - self.assertEqual(f'{10:{"#"}1{0}{"x"}}', " 0xa") - self.assertEqual(f'{-10:-{"#"}1{0}x}', " -0xa") - self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', " -0xa") - self.assertEqual(f"{10:#{3 != {4:5} and width}x}", " 0xa") - - # TODO: RUSTPYTHON SyntaxError - # self.assertEqual( - # f"result: {value:{width:{0}}.{precision:1}}", "result: 12.35" - # ) - - - self.assertAllRaise( - SyntaxError, - "f-string: expecting ':' or '}'", - [ - """f'{"s"!r{":10"}}'""", - # This looks like a nested format spec. - ], - ) - - - self.assertAllRaise( - SyntaxError, - "f-string: expecting a valid expression after '{'", - [ # Invalid syntax inside a nested spec. - "f'{4:{/5}}'", - ], - ) - - self.assertAllRaise( - SyntaxError, - "f-string: invalid conversion character", - [ # No expansion inside conversion or for - # the : or ! itself. - """f'{"s"!{"r"}}'""", - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + value = decimal.Decimal('12.34567') + self.assertEqual(f'result: {value:{width}.{precision}}', 'result: 12.35') + self.assertEqual(f'result: {value:{width!r}.{precision}}', 'result: 12.35') + self.assertEqual(f'result: {value:{width:0}.{precision:1}}', 'result: 12.35') + self.assertEqual(f'result: {value:{1}{0:0}.{precision:1}}', 'result: 12.35') + self.assertEqual(f'result: {value:{ 1}{ 0:0}.{ precision:1}}', 'result: 12.35') + self.assertEqual(f'{10:#{1}0x}', ' 0xa') + self.assertEqual(f'{10:{"#"}1{0}{"x"}}', ' 0xa') + self.assertEqual(f'{-10:-{"#"}1{0}x}', ' -0xa') + self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', ' -0xa') + self.assertEqual(f'{10:#{3 != {4:5} and width}x}', ' 0xa') + self.assertEqual(f'result: {value:{width:{0}}.{precision:1}}', 'result: 12.35') + + self.assertAllRaise(SyntaxError, "f-string: expecting ':' or '}'", + ["""f'{"s"!r{":10"}}'""", + # This looks like a nested format spec. + ]) + + self.assertAllRaise(SyntaxError, + "f-string: expecting a valid expression after '{'", + [# Invalid syntax inside a nested spec. + "f'{4:{/5}}'", + ]) + + self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character', + [# No expansion inside conversion or for + # the : or ! itself. + """f'{"s"!{"r"}}'""", + ]) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_custom_format_specifier(self): class CustomFormat: def __format__(self, format_spec): return format_spec - self.assertEqual(f"{CustomFormat():\n}", "\n") - self.assertEqual(f"{CustomFormat():\u2603}", "☃") + self.assertEqual(f'{CustomFormat():\n}', '\n') + self.assertEqual(f'{CustomFormat():\u2603}', '☃') with self.assertWarns(SyntaxWarning): - exec(r'f"{F():¯\_(ツ)_/¯}"', {"F": CustomFormat}) + exec(r'f"{F():¯\_(ツ)_/¯}"', {'F': CustomFormat}) def test_side_effect_order(self): class X: def __init__(self): self.i = 0 - def __format__(self, spec): self.i += 1 return str(self.i) x = X() - self.assertEqual(f"{x} {x}", "1 2") + self.assertEqual(f'{x} {x}', '1 2') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_missing_expression(self): - self.assertAllRaise( - SyntaxError, - "f-string: valid expression required before '}'", - [ - "f'{}'", - "f'{ }'" "f' {} '", - "f'{10:{ }}'", - "f' { } '", - # The Python parser ignores also the following - # whitespace characters in additional to a space. - "f'''{\t\f\r\n}'''", - ], - ) - - self.assertAllRaise( - SyntaxError, - "f-string: valid expression required before '!'", - [ - "f'{!r}'", - "f'{ !r}'", - "f'{!}'", - "f'''{\t\f\r\n!a}'''", - # Catch empty expression before the - # missing closing brace. - "f'{!'", - "f'{!s:'", - # Catch empty expression before the - # invalid conversion. - "f'{!x}'", - "f'{ !xr}'", - "f'{!x:}'", - "f'{!x:a}'", - "f'{ !xr:}'", - "f'{ !xr:a}'", - ], - ) - - self.assertAllRaise( - SyntaxError, - "f-string: valid expression required before ':'", - [ - "f'{:}'", - "f'{ :!}'", - "f'{:2}'", - "f'''{\t\f\r\n:a}'''", - "f'{:'", - "F'{[F'{:'}[F'{:'}]]]", - ], - ) - - self.assertAllRaise( - SyntaxError, - "f-string: valid expression required before '='", - [ - "f'{=}'", - "f'{ =}'", - "f'{ =:}'", - "f'{ =!}'", - "f'''{\t\f\r\n=}'''", - "f'{='", - ], - ) + self.assertAllRaise(SyntaxError, + "f-string: valid expression required before '}'", + ["f'{}'", + "f'{ }'" + "f' {} '", + "f'{10:{ }}'", + "f' { } '", + + # The Python parser ignores also the following + # whitespace characters in additional to a space. + "f'''{\t\f\r\n}'''", + ]) + + self.assertAllRaise(SyntaxError, + "f-string: valid expression required before '!'", + ["f'{!r}'", + "f'{ !r}'", + "f'{!}'", + "f'''{\t\f\r\n!a}'''", + + # Catch empty expression before the + # missing closing brace. + "f'{!'", + "f'{!s:'", + + # Catch empty expression before the + # invalid conversion. + "f'{!x}'", + "f'{ !xr}'", + "f'{!x:}'", + "f'{!x:a}'", + "f'{ !xr:}'", + "f'{ !xr:a}'", + ]) + + self.assertAllRaise(SyntaxError, + "f-string: valid expression required before ':'", + ["f'{:}'", + "f'{ :!}'", + "f'{:2}'", + "f'''{\t\f\r\n:a}'''", + "f'{:'", + "F'{[F'{:'}[F'{:'}]]]", + ]) + + self.assertAllRaise(SyntaxError, + "f-string: valid expression required before '='", + ["f'{=}'", + "f'{ =}'", + "f'{ =:}'", + "f'{ =!}'", + "f'''{\t\f\r\n=}'''", + "f'{='", + ]) # Different error message is raised for other whitespace characters. - self.assertAllRaise( - SyntaxError, - r"invalid non-printable character U\+00A0", - [ - "f'''{\xa0}'''", - "\xa0", - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertAllRaise(SyntaxError, r"invalid non-printable character U\+00A0", + ["f'''{\xa0}'''", + "\xa0", + ]) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parens_in_expressions(self): - self.assertEqual(f"{3,}", "(3,)") - - self.assertAllRaise( - SyntaxError, - "f-string: expecting a valid expression after '{'", - [ - "f'{,}'", - ], - ) - - self.assertAllRaise( - SyntaxError, - r"f-string: unmatched '\)'", - [ - "f'{3)+(4}'", - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertEqual(f'{3,}', '(3,)') + + self.assertAllRaise(SyntaxError, + "f-string: expecting a valid expression after '{'", + ["f'{,}'", + ]) + + self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'", + ["f'{3)+(4}'", + ]) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_newlines_before_syntax_error(self): - self.assertAllRaise( - SyntaxError, - "f-string: expecting a valid expression after '{'", - ["f'{.}'", "\nf'{.}'", "\n\nf'{.}'"], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertAllRaise(SyntaxError, + "f-string: expecting a valid expression after '{'", + ["f'{.}'", "\nf'{.}'", "\n\nf'{.}'"]) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_backslashes_in_string_part(self): - self.assertEqual(f"\t", "\t") - self.assertEqual(r"\t", "\\t") - self.assertEqual(rf"\t", "\\t") - self.assertEqual(f"{2}\t", "2\t") - self.assertEqual(f"{2}\t{3}", "2\t3") - self.assertEqual(f"\t{3}", "\t3") - - self.assertEqual(f"\u0394", "\u0394") - self.assertEqual(r"\u0394", "\\u0394") - self.assertEqual(rf"\u0394", "\\u0394") - self.assertEqual(f"{2}\u0394", "2\u0394") - self.assertEqual(f"{2}\u0394{3}", "2\u03943") - self.assertEqual(f"\u0394{3}", "\u03943") - - self.assertEqual(f"\U00000394", "\u0394") - self.assertEqual(r"\U00000394", "\\U00000394") - self.assertEqual(rf"\U00000394", "\\U00000394") - self.assertEqual(f"{2}\U00000394", "2\u0394") - self.assertEqual(f"{2}\U00000394{3}", "2\u03943") - self.assertEqual(f"\U00000394{3}", "\u03943") - - self.assertEqual(f"\N{GREEK CAPITAL LETTER DELTA}", "\u0394") - self.assertEqual(f"{2}\N{GREEK CAPITAL LETTER DELTA}", "2\u0394") - self.assertEqual(f"{2}\N{GREEK CAPITAL LETTER DELTA}{3}", "2\u03943") - self.assertEqual(f"\N{GREEK CAPITAL LETTER DELTA}{3}", "\u03943") - self.assertEqual(f"2\N{GREEK CAPITAL LETTER DELTA}", "2\u0394") - self.assertEqual(f"2\N{GREEK CAPITAL LETTER DELTA}3", "2\u03943") - self.assertEqual(f"\N{GREEK CAPITAL LETTER DELTA}3", "\u03943") - - self.assertEqual(f"\x20", " ") - self.assertEqual(r"\x20", "\\x20") - self.assertEqual(rf"\x20", "\\x20") - self.assertEqual(f"{2}\x20", "2 ") - self.assertEqual(f"{2}\x20{3}", "2 3") - self.assertEqual(f"\x20{3}", " 3") - - self.assertEqual(f"2\x20", "2 ") - self.assertEqual(f"2\x203", "2 3") - self.assertEqual(f"\x203", " 3") + self.assertEqual(f'\t', '\t') + self.assertEqual(r'\t', '\\t') + self.assertEqual(rf'\t', '\\t') + self.assertEqual(f'{2}\t', '2\t') + self.assertEqual(f'{2}\t{3}', '2\t3') + self.assertEqual(f'\t{3}', '\t3') + + self.assertEqual(f'\u0394', '\u0394') + self.assertEqual(r'\u0394', '\\u0394') + self.assertEqual(rf'\u0394', '\\u0394') + self.assertEqual(f'{2}\u0394', '2\u0394') + self.assertEqual(f'{2}\u0394{3}', '2\u03943') + self.assertEqual(f'\u0394{3}', '\u03943') + + self.assertEqual(f'\U00000394', '\u0394') + self.assertEqual(r'\U00000394', '\\U00000394') + self.assertEqual(rf'\U00000394', '\\U00000394') + self.assertEqual(f'{2}\U00000394', '2\u0394') + self.assertEqual(f'{2}\U00000394{3}', '2\u03943') + self.assertEqual(f'\U00000394{3}', '\u03943') + + self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}', '\u0394') + self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}', '2\u0394') + self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}', '2\u03943') + self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}{3}', '\u03943') + self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}', '2\u0394') + self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}3', '2\u03943') + self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}3', '\u03943') + + self.assertEqual(f'\x20', ' ') + self.assertEqual(r'\x20', '\\x20') + self.assertEqual(rf'\x20', '\\x20') + self.assertEqual(f'{2}\x20', '2 ') + self.assertEqual(f'{2}\x20{3}', '2 3') + self.assertEqual(f'\x20{3}', ' 3') + + self.assertEqual(f'2\x20', '2 ') + self.assertEqual(f'2\x203', '2 3') + self.assertEqual(f'\x203', ' 3') with self.assertWarns(SyntaxWarning): # invalid escape sequence value = eval(r"f'\{6*7}'") - self.assertEqual(value, "\\42") + self.assertEqual(value, '\\42') with self.assertWarns(SyntaxWarning): # invalid escape sequence value = eval(r"f'\g'") - self.assertEqual(value, "\\g") - self.assertEqual(f"\\{6*7}", "\\42") - self.assertEqual(rf"\{6*7}", "\\42") + self.assertEqual(value, '\\g') + self.assertEqual(f'\\{6*7}', '\\42') + self.assertEqual(fr'\{6*7}', '\\42') - AMPERSAND = "spam" + AMPERSAND = 'spam' # Get the right unicode character (&), or pick up local variable # depending on the number of backslashes. - self.assertEqual(f"\N{AMPERSAND}", "&") - self.assertEqual(f"\\N{AMPERSAND}", "\\Nspam") - self.assertEqual(rf"\N{AMPERSAND}", "\\Nspam") - self.assertEqual(f"\\\N{AMPERSAND}", "\\&") + self.assertEqual(f'\N{AMPERSAND}', '&') + self.assertEqual(f'\\N{AMPERSAND}', '\\Nspam') + self.assertEqual(fr'\N{AMPERSAND}', '\\Nspam') + self.assertEqual(f'\\\N{AMPERSAND}', '\\&') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_misformed_unicode_character_name(self): # These test are needed because unicode names are parsed # differently inside f-strings. - self.assertAllRaise( - SyntaxError, - r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape", - [ - r"f'\N'", - r"f'\N '", - r"f'\N '", # See bpo-46503. - r"f'\N{'", - r"f'\N{GREEK CAPITAL LETTER DELTA'", - # Here are the non-f-string versions, - # which should give the same errors. - r"'\N'", - r"'\N '", - r"'\N '", - r"'\N{'", - r"'\N{GREEK CAPITAL LETTER DELTA'", - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertAllRaise(SyntaxError, r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape", + [r"f'\N'", + r"f'\N '", + r"f'\N '", # See bpo-46503. + r"f'\N{'", + r"f'\N{GREEK CAPITAL LETTER DELTA'", + + # Here are the non-f-string versions, + # which should give the same errors. + r"'\N'", + r"'\N '", + r"'\N '", + r"'\N{'", + r"'\N{GREEK CAPITAL LETTER DELTA'", + ]) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_backslashes_in_expression_part(self): - # TODO: RUSTPYTHON SyntaxError - # self.assertEqual( - # f"{( - # 1 + - # 2 - # )}", - # "3", - # ) - - self.assertEqual("\N{LEFT CURLY BRACKET}", "{") - self.assertEqual(f'{"\N{LEFT CURLY BRACKET}"}', "{") - self.assertEqual(rf'{"\N{LEFT CURLY BRACKET}"}', "{") - - self.assertAllRaise( - SyntaxError, - "f-string: valid expression required before '}'", - [ - "f'{\n}'", - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertEqual(f"{( + 1 + + 2 + )}", "3") + + self.assertEqual("\N{LEFT CURLY BRACKET}", '{') + self.assertEqual(f'{"\N{LEFT CURLY BRACKET}"}', '{') + self.assertEqual(rf'{"\N{LEFT CURLY BRACKET}"}', '{') + + self.assertAllRaise(SyntaxError, + "f-string: valid expression required before '}'", + ["f'{\n}'", + ]) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_backslashes_inside_fstring_context(self): # All of these variations are invalid python syntax, # so they are also invalid in f-strings as well. @@ -1129,30 +1057,25 @@ def test_invalid_backslashes_inside_fstring_context(self): r"\\"[0], ] ] - self.assertAllRaise( - SyntaxError, "unexpected character after line continuation", cases - ) + self.assertAllRaise(SyntaxError, 'unexpected character after line continuation', + cases) def test_no_escapes_for_braces(self): """ Only literal curly braces begin an expression. """ # \x7b is '{'. - self.assertEqual(f"\x7b1+1}}", "{1+1}") - self.assertEqual(f"\x7b1+1", "{1+1") - self.assertEqual(f"\u007b1+1", "{1+1") - self.assertEqual(f"\N{LEFT CURLY BRACKET}1+1\N{RIGHT CURLY BRACKET}", "{1+1}") + self.assertEqual(f'\x7b1+1}}', '{1+1}') + self.assertEqual(f'\x7b1+1', '{1+1') + self.assertEqual(f'\u007b1+1', '{1+1') + self.assertEqual(f'\N{LEFT CURLY BRACKET}1+1\N{RIGHT CURLY BRACKET}', '{1+1}') def test_newlines_in_expressions(self): - self.assertEqual(f"{0}", "0") - self.assertEqual( - rf"""{3+ -4}""", - "7", - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertEqual(f'{0}', '0') + self.assertEqual(rf'''{3+ +4}''', '7') + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_lambda(self): x = 5 self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'") @@ -1162,67 +1085,60 @@ def test_lambda(self): # lambda doesn't work without parens, because the colon # makes the parser think it's a format_spec # emit warning if we can match a format_spec - self.assertAllRaise( - SyntaxError, - "f-string: lambda expressions are not allowed " "without parentheses", - [ - "f'{lambda x:x}'", - "f'{lambda :x}'", - "f'{lambda *arg, :x}'", - "f'{1, lambda:x}'", - "f'{lambda x:}'", - "f'{lambda :}'", - ], - ) + self.assertAllRaise(SyntaxError, + "f-string: lambda expressions are not allowed " + "without parentheses", + ["f'{lambda x:x}'", + "f'{lambda :x}'", + "f'{lambda *arg, :x}'", + "f'{1, lambda:x}'", + "f'{lambda x:}'", + "f'{lambda :}'", + ]) # Ensure the detection of invalid lambdas doesn't trigger detection # for valid lambdas in the second error pass with self.assertRaisesRegex(SyntaxError, "invalid syntax"): compile("lambda name_3=f'{name_4}': {name_3}\n1 $ 1", "<string>", "exec") # but don't emit the paren warning in general cases - with self.assertRaisesRegex( - SyntaxError, "f-string: expecting a valid expression after '{'" - ): + with self.assertRaisesRegex(SyntaxError, "f-string: expecting a valid expression after '{'"): eval("f'{+ lambda:None}'") def test_valid_prefixes(self): - self.assertEqual(f"{1}", "1") - self.assertEqual(Rf"{2}", "2") - self.assertEqual(Rf"{3}", "3") + self.assertEqual(F'{1}', "1") + self.assertEqual(FR'{2}', "2") + self.assertEqual(fR'{3}', "3") def test_roundtrip_raw_quotes(self): - self.assertEqual(rf"\'", "\\'") - self.assertEqual(rf"\"", '\\"') - self.assertEqual(rf"\"\'", "\\\"\\'") - self.assertEqual(rf"\'\"", "\\'\\\"") - self.assertEqual(rf"\"\'\"", '\\"\\\'\\"') - self.assertEqual(rf"\'\"\'", "\\'\\\"\\'") - self.assertEqual(rf"\"\'\"\'", "\\\"\\'\\\"\\'") - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertEqual(fr"\'", "\\'") + self.assertEqual(fr'\"', '\\"') + self.assertEqual(fr'\"\'', '\\"\\\'') + self.assertEqual(fr'\'\"', '\\\'\\"') + self.assertEqual(fr'\"\'\"', '\\"\\\'\\"') + self.assertEqual(fr'\'\"\'', '\\\'\\"\\\'') + self.assertEqual(fr'\"\'\"\'', '\\"\\\'\\"\\\'') + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_fstring_backslash_before_double_bracket(self): deprecated_cases = [ - (r"f'\{{\}}'", "\\{\\}"), - (r"f'\{{'", "\\{"), - (r"f'\{{{1+1}'", "\\{2"), - (r"f'\}}{1+1}'", "\\}2"), - (r"f'{1+1}\}}'", "2\\}"), + (r"f'\{{\}}'", '\\{\\}'), + (r"f'\{{'", '\\{'), + (r"f'\{{{1+1}'", '\\{2'), + (r"f'\}}{1+1}'", '\\}2'), + (r"f'{1+1}\}}'", '2\\}') ] - for case, expected_result in deprecated_cases: with self.subTest(case=case, expected_result=expected_result): with self.assertWarns(SyntaxWarning): result = eval(case) self.assertEqual(result, expected_result) - self.assertEqual(rf"\{{\}}", "\\{\\}") - self.assertEqual(rf"\{{", "\\{") - self.assertEqual(rf"\{{{1+1}", "\\{2") - self.assertEqual(rf"\}}{1+1}", "\\}2") - self.assertEqual(rf"{1+1}\}}", "2\\}") - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertEqual(fr'\{{\}}', '\\{\\}') + self.assertEqual(fr'\{{', '\\{') + self.assertEqual(fr'\{{{1+1}', '\\{2') + self.assertEqual(fr'\}}{1+1}', '\\}2') + self.assertEqual(fr'{1+1}\}}', '2\\}') + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_fstring_backslash_before_double_bracket_warns_once(self): with self.assertWarns(SyntaxWarning) as w: eval(r"f'\{{'") @@ -1230,18 +1146,18 @@ def test_fstring_backslash_before_double_bracket_warns_once(self): self.assertEqual(w.warnings[0].category, SyntaxWarning) def test_fstring_backslash_prefix_raw(self): - self.assertEqual(f"\\", "\\") - self.assertEqual(f"\\\\", "\\\\") - self.assertEqual(rf"\\", r"\\") - self.assertEqual(rf"\\\\", r"\\\\") - self.assertEqual(rf"\\", r"\\") - self.assertEqual(rf"\\\\", r"\\\\") - self.assertEqual(Rf"\\", R"\\") - self.assertEqual(Rf"\\\\", R"\\\\") - self.assertEqual(Rf"\\", R"\\") - self.assertEqual(Rf"\\\\", R"\\\\") - self.assertEqual(Rf"\\", R"\\") - self.assertEqual(Rf"\\\\", R"\\\\") + self.assertEqual(f'\\', '\\') + self.assertEqual(f'\\\\', '\\\\') + self.assertEqual(fr'\\', r'\\') + self.assertEqual(fr'\\\\', r'\\\\') + self.assertEqual(rf'\\', r'\\') + self.assertEqual(rf'\\\\', r'\\\\') + self.assertEqual(Rf'\\', R'\\') + self.assertEqual(Rf'\\\\', R'\\\\') + self.assertEqual(fR'\\', R'\\') + self.assertEqual(fR'\\\\', R'\\\\') + self.assertEqual(FR'\\', R'\\') + self.assertEqual(FR'\\\\', R'\\\\') def test_fstring_format_spec_greedy_matching(self): self.assertEqual(f"{1:}}}", "1}") @@ -1251,8 +1167,8 @@ def test_yield(self): # Not terribly useful, but make sure the yield turns # a function into a generator def fn(y): - f"y:{yield y*2}" - f"{yield}" + f'y:{yield y*2}' + f'{yield}' g = fn(4) self.assertEqual(next(g), 8) @@ -1260,331 +1176,287 @@ def fn(y): def test_yield_send(self): def fn(x): - yield f"x:{yield (lambda i: x * i)}" + yield f'x:{yield (lambda i: x * i)}' g = fn(10) the_lambda = next(g) self.assertEqual(the_lambda(4), 40) - self.assertEqual(g.send("string"), "x:string") + self.assertEqual(g.send('string'), 'x:string') - # TODO: RUSTPYTHON SyntaxError - # def test_expressions_with_triple_quoted_strings(self): - # self.assertEqual(f"{'''x'''}", 'x') - # self.assertEqual(f"{'''eric's'''}", "eric's") + def test_expressions_with_triple_quoted_strings(self): + self.assertEqual(f"{'''x'''}", 'x') + self.assertEqual(f"{'''eric's'''}", "eric's") - # # Test concatenation within an expression - # self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy') - # self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s') - # self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy') - # self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy') - # self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy') - # self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy') + # Test concatenation within an expression + self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy') + self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s') + self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy') + self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy') + self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy') + self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy') def test_multiple_vars(self): x = 98 - y = "abc" - self.assertEqual(f"{x}{y}", "98abc") + y = 'abc' + self.assertEqual(f'{x}{y}', '98abc') - self.assertEqual(f"X{x}{y}", "X98abc") - self.assertEqual(f"{x}X{y}", "98Xabc") - self.assertEqual(f"{x}{y}X", "98abcX") + self.assertEqual(f'X{x}{y}', 'X98abc') + self.assertEqual(f'{x}X{y}', '98Xabc') + self.assertEqual(f'{x}{y}X', '98abcX') - self.assertEqual(f"X{x}Y{y}", "X98Yabc") - self.assertEqual(f"X{x}{y}Y", "X98abcY") - self.assertEqual(f"{x}X{y}Y", "98XabcY") + self.assertEqual(f'X{x}Y{y}', 'X98Yabc') + self.assertEqual(f'X{x}{y}Y', 'X98abcY') + self.assertEqual(f'{x}X{y}Y', '98XabcY') - self.assertEqual(f"X{x}Y{y}Z", "X98YabcZ") + self.assertEqual(f'X{x}Y{y}Z', 'X98YabcZ') def test_closure(self): def outer(x): def inner(): - return f"x:{x}" - + return f'x:{x}' return inner - self.assertEqual(outer("987")(), "x:987") - self.assertEqual(outer(7)(), "x:7") + self.assertEqual(outer('987')(), 'x:987') + self.assertEqual(outer(7)(), 'x:7') def test_arguments(self): y = 2 - def f(x, width): - return f"x={x*y:{width}}" + return f'x={x*y:{width}}' - self.assertEqual(f("foo", 10), "x=foofoo ") - x = "bar" - self.assertEqual(f(10, 10), "x= 20") + self.assertEqual(f('foo', 10), 'x=foofoo ') + x = 'bar' + self.assertEqual(f(10, 10), 'x= 20') def test_locals(self): value = 123 - self.assertEqual(f"v:{value}", "v:123") + self.assertEqual(f'v:{value}', 'v:123') def test_missing_variable(self): with self.assertRaises(NameError): - f"v:{value}" + f'v:{value}' def test_missing_format_spec(self): class O: def __format__(self, spec): if not spec: - return "*" + return '*' return spec - self.assertEqual(f"{O():x}", "x") - self.assertEqual(f"{O()}", "*") - self.assertEqual(f"{O():}", "*") + self.assertEqual(f'{O():x}', 'x') + self.assertEqual(f'{O()}', '*') + self.assertEqual(f'{O():}', '*') - self.assertEqual(f"{3:}", "3") - self.assertEqual(f"{3!s:}", "3") + self.assertEqual(f'{3:}', '3') + self.assertEqual(f'{3!s:}', '3') def test_global(self): - self.assertEqual(f"g:{a_global}", "g:global variable") - self.assertEqual(f"g:{a_global!r}", "g:'global variable'") + self.assertEqual(f'g:{a_global}', 'g:global variable') + self.assertEqual(f'g:{a_global!r}', "g:'global variable'") - a_local = "local variable" - self.assertEqual( - f"g:{a_global} l:{a_local}", "g:global variable l:local variable" - ) - self.assertEqual(f"g:{a_global!r}", "g:'global variable'") - self.assertEqual( - f"g:{a_global} l:{a_local!r}", "g:global variable l:'local variable'" - ) + a_local = 'local variable' + self.assertEqual(f'g:{a_global} l:{a_local}', + 'g:global variable l:local variable') + self.assertEqual(f'g:{a_global!r}', + "g:'global variable'") + self.assertEqual(f'g:{a_global} l:{a_local!r}', + "g:global variable l:'local variable'") - self.assertIn("module 'unittest' from", f"{unittest}") + self.assertIn("module 'unittest' from", f'{unittest}') def test_shadowed_global(self): - a_global = "really a local" - self.assertEqual(f"g:{a_global}", "g:really a local") - self.assertEqual(f"g:{a_global!r}", "g:'really a local'") - - a_local = "local variable" - self.assertEqual( - f"g:{a_global} l:{a_local}", "g:really a local l:local variable" - ) - self.assertEqual(f"g:{a_global!r}", "g:'really a local'") - self.assertEqual( - f"g:{a_global} l:{a_local!r}", "g:really a local l:'local variable'" - ) + a_global = 'really a local' + self.assertEqual(f'g:{a_global}', 'g:really a local') + self.assertEqual(f'g:{a_global!r}', "g:'really a local'") + + a_local = 'local variable' + self.assertEqual(f'g:{a_global} l:{a_local}', + 'g:really a local l:local variable') + self.assertEqual(f'g:{a_global!r}', + "g:'really a local'") + self.assertEqual(f'g:{a_global} l:{a_local!r}', + "g:really a local l:'local variable'") def test_call(self): def foo(x): - return "x=" + str(x) + return 'x=' + str(x) - self.assertEqual(f"{foo(10)}", "x=10") + self.assertEqual(f'{foo(10)}', 'x=10') def test_nested_fstrings(self): y = 5 - self.assertEqual(f'{f"{0}"*3}', "000") - self.assertEqual(f'{f"{y}"*3}', "555") + self.assertEqual(f'{f"{0}"*3}', '000') + self.assertEqual(f'{f"{y}"*3}', '555') def test_invalid_string_prefixes(self): - single_quote_cases = [ - "fu''", - "uf''", - "Fu''", - "fU''", - "Uf''", - "uF''", - "ufr''", - "urf''", - "fur''", - "fru''", - "rfu''", - "ruf''", - "FUR''", - "Fur''", - "fb''", - "fB''", - "Fb''", - "FB''", - "bf''", - "bF''", - "Bf''", - "BF''", - ] + single_quote_cases = ["fu''", + "uf''", + "Fu''", + "fU''", + "Uf''", + "uF''", + "ufr''", + "urf''", + "fur''", + "fru''", + "rfu''", + "ruf''", + "FUR''", + "Fur''", + "fb''", + "fB''", + "Fb''", + "FB''", + "bf''", + "bF''", + "Bf''", + "BF''",] double_quote_cases = [case.replace("'", '"') for case in single_quote_cases] - self.assertAllRaise( - SyntaxError, "invalid syntax", single_quote_cases + double_quote_cases - ) + self.assertAllRaise(SyntaxError, 'invalid syntax', + single_quote_cases + double_quote_cases) def test_leading_trailing_spaces(self): - self.assertEqual(f"{ 3}", "3") - self.assertEqual(f"{ 3}", "3") - self.assertEqual(f"{3 }", "3") - self.assertEqual(f"{3 }", "3") + self.assertEqual(f'{ 3}', '3') + self.assertEqual(f'{ 3}', '3') + self.assertEqual(f'{3 }', '3') + self.assertEqual(f'{3 }', '3') - self.assertEqual(f"expr={ {x: y for x, y in [(1, 2), ]}}", "expr={1: 2}") - self.assertEqual(f"expr={ {x: y for x, y in [(1, 2), ]} }", "expr={1: 2}") + self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}', + 'expr={1: 2}') + self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }', + 'expr={1: 2}') def test_not_equal(self): # There's a special test for this because there's a special # case in the f-string parser to look for != as not ending an # expression. Normally it would, while looking for !s or !r. - self.assertEqual(f"{3!=4}", "True") - self.assertEqual(f"{3!=4:}", "True") - self.assertEqual(f"{3!=4!s}", "True") - self.assertEqual(f"{3!=4!s:.3}", "Tru") + self.assertEqual(f'{3!=4}', 'True') + self.assertEqual(f'{3!=4:}', 'True') + self.assertEqual(f'{3!=4!s}', 'True') + self.assertEqual(f'{3!=4!s:.3}', 'Tru') def test_equal_equal(self): # Because an expression ending in = has special meaning, # there's a special test for ==. Make sure it works. - self.assertEqual(f"{0==1}", "False") + self.assertEqual(f'{0==1}', 'False') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_conversions(self): - self.assertEqual(f"{3.14:10.10}", " 3.14") - self.assertEqual(f"{3.14!s:10.10}", "3.14 ") - self.assertEqual(f"{3.14!r:10.10}", "3.14 ") - self.assertEqual(f"{3.14!a:10.10}", "3.14 ") + self.assertEqual(f'{3.14:10.10}', ' 3.14') + self.assertEqual(f'{1.25!s:10.10}', '1.25 ') + self.assertEqual(f'{1.25!r:10.10}', '1.25 ') + self.assertEqual(f'{1.25!a:10.10}', '1.25 ') - self.assertEqual(f'{"a"}', "a") + self.assertEqual(f'{"a"}', 'a') self.assertEqual(f'{"a"!r}', "'a'") self.assertEqual(f'{"a"!a}', "'a'") # Conversions can have trailing whitespace after them since it # does not provide any significance - # TODO: RUSTPYTHON SyntaxError - # self.assertEqual(f"{3!s }", "3") - # self.assertEqual(f"{3.14!s :10.10}", "3.14 ") + self.assertEqual(f"{3!s }", "3") + self.assertEqual(f'{1.25!s :10.10}', '1.25 ') # Not a conversion. self.assertEqual(f'{"a!r"}', "a!r") # Not a conversion, but show that ! is allowed in a format spec. - self.assertEqual(f"{3.14:!<10.10}", "3.14!!!!!!") - - self.assertAllRaise( - SyntaxError, - "f-string: expecting '}'", - [ - "f'{3!'", - "f'{3!s'", - "f'{3!g'", - ], - ) - - self.assertAllRaise( - SyntaxError, - "f-string: missing conversion character", - [ - "f'{3!}'", - "f'{3!:'", - "f'{3!:}'", - ], - ) - - for conv_identifier in "g", "A", "G", "ä", "ɐ": - self.assertAllRaise( - SyntaxError, - "f-string: invalid conversion character %r: " - "expected 's', 'r', or 'a'" % conv_identifier, - ["f'{3!" + conv_identifier + "}'"], - ) - - for conv_non_identifier in "3", "!": - self.assertAllRaise( - SyntaxError, - "f-string: invalid conversion character", - ["f'{3!" + conv_non_identifier + "}'"], - ) - - for conv in " s", " s ": - self.assertAllRaise( - SyntaxError, - "f-string: conversion type must come right after the" - " exclamanation mark", - ["f'{3!" + conv + "}'"], - ) - - self.assertAllRaise( - SyntaxError, - "f-string: invalid conversion character 'ss': " "expected 's', 'r', or 'a'", - [ - "f'{3!ss}'", - "f'{3!ss:}'", - "f'{3!ss:s}'", - ], - ) + self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!') + + self.assertAllRaise(SyntaxError, "f-string: expecting '}'", + ["f'{3!'", + "f'{3!s'", + "f'{3!g'", + ]) + + self.assertAllRaise(SyntaxError, 'f-string: missing conversion character', + ["f'{3!}'", + "f'{3!:'", + "f'{3!:}'", + ]) + + for conv_identifier in 'g', 'A', 'G', 'ä', 'ɐ': + self.assertAllRaise(SyntaxError, + "f-string: invalid conversion character %r: " + "expected 's', 'r', or 'a'" % conv_identifier, + ["f'{3!" + conv_identifier + "}'"]) + + for conv_non_identifier in '3', '!': + self.assertAllRaise(SyntaxError, + "f-string: invalid conversion character", + ["f'{3!" + conv_non_identifier + "}'"]) + + for conv in ' s', ' s ': + self.assertAllRaise(SyntaxError, + "f-string: conversion type must come right after the" + " exclamation mark", + ["f'{3!" + conv + "}'"]) + + self.assertAllRaise(SyntaxError, + "f-string: invalid conversion character 'ss': " + "expected 's', 'r', or 'a'", + ["f'{3!ss}'", + "f'{3!ss:}'", + "f'{3!ss:s}'", + ]) def test_assignment(self): - self.assertAllRaise( - SyntaxError, - r"invalid syntax", - [ - "f'' = 3", - "f'{0}' = x", - "f'{x}' = x", - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertAllRaise(SyntaxError, r'invalid syntax', + ["f'' = 3", + "f'{0}' = x", + "f'{x}' = x", + ]) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_del(self): - self.assertAllRaise( - SyntaxError, - "invalid syntax", - [ - "del f''", - "del '' f''", - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertAllRaise(SyntaxError, 'invalid syntax', + ["del f''", + "del '' f''", + ]) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_mismatched_braces(self): - self.assertAllRaise( - SyntaxError, - "f-string: single '}' is not allowed", - [ - "f'{{}'", - "f'{{}}}'", - "f'}'", - "f'x}'", - "f'x}x'", - r"f'\u007b}'", - # Can't have { or } in a format spec. - "f'{3:}>10}'", - "f'{3:}}>10}'", - ], - ) - - self.assertAllRaise( - SyntaxError, - "f-string: expecting '}'", - [ - "f'{3'", - "f'{3!'", - "f'{3:'", - "f'{3!s'", - "f'{3!s:'", - "f'{3!s:3'", - "f'x{'", - "f'x{x'", - "f'{x'", - "f'{3:s'", - "f'{{{'", - "f'{{}}{'", - "f'{'", - "f'{i='", # See gh-93418. - ], - ) - - self.assertAllRaise( - SyntaxError, - "f-string: expecting a valid expression after '{'", - [ - "f'{3:{{>10}'", - ], - ) + self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed", + ["f'{{}'", + "f'{{}}}'", + "f'}'", + "f'x}'", + "f'x}x'", + r"f'\u007b}'", + + # Can't have { or } in a format spec. + "f'{3:}>10}'", + "f'{3:}}>10}'", + ]) + + self.assertAllRaise(SyntaxError, "f-string: expecting '}'", + ["f'{3'", + "f'{3!'", + "f'{3:'", + "f'{3!s'", + "f'{3!s:'", + "f'{3!s:3'", + "f'x{'", + "f'x{x'", + "f'{x'", + "f'{3:s'", + "f'{{{'", + "f'{{}}{'", + "f'{'", + "f'{i='", # See gh-93418. + ]) + + self.assertAllRaise(SyntaxError, + "f-string: expecting a valid expression after '{'", + ["f'{3:{{>10}'", + ]) # But these are just normal strings. - self.assertEqual(f'{"{"}', "{") - self.assertEqual(f'{"}"}', "}") - self.assertEqual(f'{3:{"}"}>10}', "}}}}}}}}}3") - self.assertEqual(f'{2:{"{"}>10}', "{{{{{{{{{2") + self.assertEqual(f'{"{"}', '{') + self.assertEqual(f'{"}"}', '}') + self.assertEqual(f'{3:{"}"}>10}', '}}}}}}}}}3') + self.assertEqual(f'{2:{"{"}>10}', '{{{{{{{{{2') def test_if_conditional(self): # There's special logic in compile.c to test if the @@ -1593,7 +1465,7 @@ def test_if_conditional(self): def test_fstring(x, expected): flag = 0 - if f"{x}": + if f'{x}': flag = 1 else: flag = 2 @@ -1601,7 +1473,7 @@ def test_fstring(x, expected): def test_concat_empty(x, expected): flag = 0 - if "" f"{x}": + if '' f'{x}': flag = 1 else: flag = 2 @@ -1609,151 +1481,139 @@ def test_concat_empty(x, expected): def test_concat_non_empty(x, expected): flag = 0 - if " " f"{x}": + if ' ' f'{x}': flag = 1 else: flag = 2 self.assertEqual(flag, expected) - test_fstring("", 2) - test_fstring(" ", 1) + test_fstring('', 2) + test_fstring(' ', 1) - test_concat_empty("", 2) - test_concat_empty(" ", 1) + test_concat_empty('', 2) + test_concat_empty(' ', 1) - test_concat_non_empty("", 1) - test_concat_non_empty(" ", 1) + test_concat_non_empty('', 1) + test_concat_non_empty(' ', 1) def test_empty_format_specifier(self): - x = "test" - self.assertEqual(f"{x}", "test") - self.assertEqual(f"{x:}", "test") - self.assertEqual(f"{x!s:}", "test") - self.assertEqual(f"{x!r:}", "'test'") + x = 'test' + self.assertEqual(f'{x}', 'test') + self.assertEqual(f'{x:}', 'test') + self.assertEqual(f'{x!s:}', 'test') + self.assertEqual(f'{x!r:}', "'test'") def test_str_format_differences(self): - d = { - "a": "string", - 0: "integer", - } + d = {'a': 'string', + 0: 'integer', + } a = 0 - self.assertEqual(f"{d[0]}", "integer") - self.assertEqual(f'{d["a"]}', "string") - self.assertEqual(f"{d[a]}", "integer") - self.assertEqual("{d[a]}".format(d=d), "string") - self.assertEqual("{d[0]}".format(d=d), "integer") - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertEqual(f'{d[0]}', 'integer') + self.assertEqual(f'{d["a"]}', 'string') + self.assertEqual(f'{d[a]}', 'integer') + self.assertEqual('{d[a]}'.format(d=d), 'string') + self.assertEqual('{d[0]}'.format(d=d), 'integer') + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_errors(self): # see issue 26287 - self.assertAllRaise( - TypeError, - "unsupported", - [ - r"f'{(lambda: 0):x}'", - r"f'{(0,):x}'", - ], - ) - self.assertAllRaise( - ValueError, - "Unknown format code", - [ - r"f'{1000:j}'", - r"f'{1000:j}'", - ], - ) + self.assertAllRaise(TypeError, 'unsupported', + [r"f'{(lambda: 0):x}'", + r"f'{(0,):x}'", + ]) + self.assertAllRaise(ValueError, 'Unknown format code', + [r"f'{1000:j}'", + r"f'{1000:j}'", + ]) def test_filename_in_syntaxerror(self): # see issue 38964 with temp_cwd() as cwd: - file_path = os.path.join(cwd, "t.py") - with open(file_path, "w", encoding="utf-8") as f: - f.write('f"{a b}"') # This generates a SyntaxError - _, _, stderr = assert_python_failure(file_path, PYTHONIOENCODING="ascii") - self.assertIn(file_path.encode("ascii", "backslashreplace"), stderr) + file_path = os.path.join(cwd, 't.py') + with open(file_path, 'w', encoding="utf-8") as f: + f.write('f"{a b}"') # This generates a SyntaxError + _, _, stderr = assert_python_failure(file_path, + PYTHONIOENCODING='ascii') + self.assertIn(file_path.encode('ascii', 'backslashreplace'), stderr) def test_loop(self): for i in range(1000): - self.assertEqual(f"i:{i}", "i:" + str(i)) + self.assertEqual(f'i:{i}', 'i:' + str(i)) def test_dict(self): - d = { - '"': "dquote", - "'": "squote", - "foo": "bar", - } - self.assertEqual(f"""{d["'"]}""", "squote") - self.assertEqual(f"""{d['"']}""", "dquote") + d = {'"': 'dquote', + "'": 'squote', + 'foo': 'bar', + } + self.assertEqual(f'''{d["'"]}''', 'squote') + self.assertEqual(f"""{d['"']}""", 'dquote') - self.assertEqual(f'{d["foo"]}', "bar") - self.assertEqual(f"{d['foo']}", "bar") + self.assertEqual(f'{d["foo"]}', 'bar') + self.assertEqual(f"{d['foo']}", 'bar') def test_backslash_char(self): # Check eval of a backslash followed by a control char. # See bpo-30682: this used to raise an assert in pydebug mode. - self.assertEqual(eval('f"\\\n"'), "") - self.assertEqual(eval('f"\\\r"'), "") + self.assertEqual(eval('f"\\\n"'), '') + self.assertEqual(eval('f"\\\r"'), '') + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: '1+2 = # my comment\n 3' != '1+2 = \n 3' def test_debug_conversion(self): - x = "A string" - self.assertEqual(f"{x=}", "x=" + repr(x)) - self.assertEqual(f"{x =}", "x =" + repr(x)) - self.assertEqual(f"{x=!s}", "x=" + str(x)) - self.assertEqual(f"{x=!r}", "x=" + repr(x)) - self.assertEqual(f"{x=!a}", "x=" + ascii(x)) + x = 'A string' + self.assertEqual(f'{x=}', 'x=' + repr(x)) + self.assertEqual(f'{x =}', 'x =' + repr(x)) + self.assertEqual(f'{x=!s}', 'x=' + str(x)) + self.assertEqual(f'{x=!r}', 'x=' + repr(x)) + self.assertEqual(f'{x=!a}', 'x=' + ascii(x)) x = 2.71828 - self.assertEqual(f"{x=:.2f}", "x=" + format(x, ".2f")) - self.assertEqual(f"{x=:}", "x=" + format(x, "")) - self.assertEqual(f"{x=!r:^20}", "x=" + format(repr(x), "^20")) - self.assertEqual(f"{x=!s:^20}", "x=" + format(str(x), "^20")) - self.assertEqual(f"{x=!a:^20}", "x=" + format(ascii(x), "^20")) + self.assertEqual(f'{x=:.2f}', 'x=' + format(x, '.2f')) + self.assertEqual(f'{x=:}', 'x=' + format(x, '')) + self.assertEqual(f'{x=!r:^20}', 'x=' + format(repr(x), '^20')) + self.assertEqual(f'{x=!s:^20}', 'x=' + format(str(x), '^20')) + self.assertEqual(f'{x=!a:^20}', 'x=' + format(ascii(x), '^20')) x = 9 - self.assertEqual(f"{3*x+15=}", "3*x+15=42") + self.assertEqual(f'{3*x+15=}', '3*x+15=42') # There is code in ast.c that deals with non-ascii expression values. So, # use a unicode identifier to trigger that. tenπ = 31.4 - self.assertEqual(f"{tenπ=:.2f}", "tenπ=31.40") + self.assertEqual(f'{tenπ=:.2f}', 'tenπ=31.40') # Also test with Unicode in non-identifiers. - self.assertEqual(f'{"Σ"=}', "\"Σ\"='Σ'") + self.assertEqual(f'{"Σ"=}', '"Σ"=\'Σ\'') # Make sure nested fstrings still work. - self.assertEqual(f'{f"{3.1415=:.1f}":*^20}', "*****3.1415=3.1*****") + self.assertEqual(f'{f"{3.1415=:.1f}":*^20}', '*****3.1415=3.1*****') # Make sure text before and after an expression with = works # correctly. - pi = "π" - self.assertEqual(f"alpha α {pi=} ω omega", "alpha α pi='π' ω omega") + pi = 'π' + self.assertEqual(f'alpha α {pi=} ω omega', "alpha α pi='π' ω omega") # Check multi-line expressions. - self.assertEqual( - f"""{ + self.assertEqual(f'''{ 3 -=}""", - "\n3\n=3", - ) +=}''', '\n3\n=3') # Since = is handled specially, make sure all existing uses of # it still work. - self.assertEqual(f"{0==1}", "False") - self.assertEqual(f"{0!=1}", "True") - self.assertEqual(f"{0<=1}", "True") - self.assertEqual(f"{0>=1}", "False") - self.assertEqual(f'{(x:="5")}', "5") - self.assertEqual(x, "5") - self.assertEqual(f"{(x:=5)}", "5") + self.assertEqual(f'{0==1}', 'False') + self.assertEqual(f'{0!=1}', 'True') + self.assertEqual(f'{0<=1}', 'True') + self.assertEqual(f'{0>=1}', 'False') + self.assertEqual(f'{(x:="5")}', '5') + self.assertEqual(x, '5') + self.assertEqual(f'{(x:=5)}', '5') self.assertEqual(x, 5) - self.assertEqual(f'{"="}', "=") + self.assertEqual(f'{"="}', '=') x = 20 # This isn't an assignment expression, it's 'x', with a format # spec of '=10'. See test_walrus: you need to use parens. - self.assertEqual(f"{x:=10}", " 20") + self.assertEqual(f'{x:=10}', ' 20') # Test named function parameters, to make sure '=' parsing works # there. @@ -1762,54 +1622,60 @@ def f(a): oldx = x x = a return oldx - x = 0 - self.assertEqual(f'{f(a="3=")}', "0") - self.assertEqual(x, "3=") - self.assertEqual(f"{f(a=4)}", "3=") + self.assertEqual(f'{f(a="3=")}', '0') + self.assertEqual(x, '3=') + self.assertEqual(f'{f(a=4)}', '3=') self.assertEqual(x, 4) # Check debug expressions in format spec y = 20 self.assertEqual(f"{2:{y=}}", "yyyyyyyyyyyyyyyyyyy2") - self.assertEqual( - f"{datetime.datetime.now():h1{y=}h2{y=}h3{y=}}", "h1y=20h2y=20h3y=20" - ) + self.assertEqual(f"{datetime.datetime.now():h1{y=}h2{y=}h3{y=}}", + 'h1y=20h2y=20h3y=20') # Make sure __format__ is being called. class C: def __format__(self, s): - return f"FORMAT-{s}" - + return f'FORMAT-{s}' def __repr__(self): - return "REPR" + return 'REPR' - self.assertEqual(f"{C()=}", "C()=REPR") - self.assertEqual(f"{C()=!r}", "C()=REPR") - self.assertEqual(f"{C()=:}", "C()=FORMAT-") - self.assertEqual(f"{C()=: }", "C()=FORMAT- ") - self.assertEqual(f"{C()=:x}", "C()=FORMAT-x") - self.assertEqual(f"{C()=!r:*^20}", "C()=********REPR********") - self.assertEqual(f"{C():{20=}}", "FORMAT-20=20") + self.assertEqual(f'{C()=}', 'C()=REPR') + self.assertEqual(f'{C()=!r}', 'C()=REPR') + self.assertEqual(f'{C()=:}', 'C()=FORMAT-') + self.assertEqual(f'{C()=: }', 'C()=FORMAT- ') + self.assertEqual(f'{C()=:x}', 'C()=FORMAT-x') + self.assertEqual(f'{C()=!r:*^20}', 'C()=********REPR********') + self.assertEqual(f"{C():{20=}}", 'FORMAT-20=20') self.assertRaises(SyntaxError, eval, "f'{C=]'") + # Make sure leading and following text works. - x = "foo" - self.assertEqual(f"X{x=}Y", "Xx=" + repr(x) + "Y") + x = 'foo' + self.assertEqual(f'X{x=}Y', 'Xx='+repr(x)+'Y') # Make sure whitespace around the = works. - self.assertEqual(f"X{x =}Y", "Xx =" + repr(x) + "Y") - self.assertEqual(f"X{x= }Y", "Xx= " + repr(x) + "Y") - self.assertEqual(f"X{x = }Y", "Xx = " + repr(x) + "Y") + self.assertEqual(f'X{x =}Y', 'Xx ='+repr(x)+'Y') + self.assertEqual(f'X{x= }Y', 'Xx= '+repr(x)+'Y') + self.assertEqual(f'X{x = }Y', 'Xx = '+repr(x)+'Y') self.assertEqual(f"sadsd {1 + 1 = :{1 + 1:1d}f}", "sadsd 1 + 1 = 2.000000") -# TODO: RUSTPYTHON SyntaxError -# self.assertEqual( -# f"{1+2 = # my comment -# }", -# "1+2 = \n 3", -# ) + self.assertEqual(f"{1+2 = # my comment + }", '1+2 = \n 3') + + self.assertEqual(f'{""" # booo + """=}', '""" # booo\n """=\' # booo\\n \'') + + self.assertEqual(f'{" # nooo "=}', '" # nooo "=\' # nooo \'') + self.assertEqual(f'{" \" # nooo \" "=}', '" \\" # nooo \\" "=\' " # nooo " \'') + + self.assertEqual(f'{ # some comment goes here + """hello"""=}', ' \n """hello"""=\'hello\'') + self.assertEqual(f'{"""# this is not a comment + a""" # this is a comment + }', '# this is not a comment\n a') # These next lines contains tabs. Backslash escapes don't # work in f-strings. @@ -1817,65 +1683,68 @@ def __repr__(self): # this will be to dynamically created and exec the f-strings. But # that's such a hassle I'll save it for another day. For now, convert # the tabs to spaces just to shut up patchcheck. - # self.assertEqual(f'X{x =}Y', 'Xx\t='+repr(x)+'Y') - # self.assertEqual(f'X{x = }Y', 'Xx\t=\t'+repr(x)+'Y') + #self.assertEqual(f'X{x =}Y', 'Xx\t='+repr(x)+'Y') + #self.assertEqual(f'X{x = }Y', 'Xx\t=\t'+repr(x)+'Y') + + def test_debug_expressions_are_raw_strings(self): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SyntaxWarning) + self.assertEqual(eval("""f'{b"\\N{OX}"=}'"""), 'b"\\N{OX}"=b\'\\\\N{OX}\'') + self.assertEqual(f'{r"\xff"=}', 'r"\\xff"=\'\\\\xff\'') + self.assertEqual(f'{r"\n"=}', 'r"\\n"=\'\\\\n\'') + self.assertEqual(f"{'\''=}", "'\\''=\"'\"") + self.assertEqual(f'{'\xc5'=}', r"'\xc5'='Å'") def test_walrus(self): x = 20 # This isn't an assignment expression, it's 'x', with a format # spec of '=10'. - self.assertEqual(f"{x:=10}", " 20") + self.assertEqual(f'{x:=10}', ' 20') # This is an assignment expression, which requires parens. - self.assertEqual(f"{(x:=10)}", "10") + self.assertEqual(f'{(x:=10)}', '10') self.assertEqual(x, 10) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_syntax_error_message(self): - with self.assertRaisesRegex( - SyntaxError, "f-string: expecting '=', or '!', or ':', or '}'" - ): + with self.assertRaisesRegex(SyntaxError, + "f-string: expecting '=', or '!', or ':', or '}'"): compile("f'{a $ b}'", "?", "exec") def test_with_two_commas_in_format_specifier(self): error_msg = re.escape("Cannot specify ',' with ','.") with self.assertRaisesRegex(ValueError, error_msg): - f"{1:,,}" + f'{1:,,}' def test_with_two_underscore_in_format_specifier(self): error_msg = re.escape("Cannot specify '_' with '_'.") with self.assertRaisesRegex(ValueError, error_msg): - f"{1:__}" + f'{1:__}' def test_with_a_commas_and_an_underscore_in_format_specifier(self): error_msg = re.escape("Cannot specify both ',' and '_'.") with self.assertRaisesRegex(ValueError, error_msg): - f"{1:,_}" + f'{1:,_}' def test_with_an_underscore_and_a_comma_in_format_specifier(self): error_msg = re.escape("Cannot specify both ',' and '_'.") with self.assertRaisesRegex(ValueError, error_msg): - f"{1:_,}" + f'{1:_,}' - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_syntax_error_for_starred_expressions(self): with self.assertRaisesRegex(SyntaxError, "can't use starred expression here"): compile("f'{*a}'", "?", "exec") - with self.assertRaisesRegex( - SyntaxError, "f-string: expecting a valid expression after '{'" - ): + with self.assertRaisesRegex(SyntaxError, + "f-string: expecting a valid expression after '{'"): compile("f'{**a}'", "?", "exec") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_not_closing_quotes(self): self.assertAllRaise(SyntaxError, "unterminated f-string literal", ['f"', "f'"]) - self.assertAllRaise( - SyntaxError, "unterminated triple-quoted f-string literal", ['f"""', "f'''"] - ) + self.assertAllRaise(SyntaxError, "unterminated triple-quoted f-string literal", + ['f"""', "f'''"]) # Ensure that the errors are reported at the correct line number. data = '''\ x = 1 + 1 @@ -1891,56 +1760,126 @@ def test_not_closing_quotes(self): except SyntaxError as e: self.assertEqual(e.text, 'z = f"""') self.assertEqual(e.lineno, 3) - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_syntax_error_after_debug(self): - self.assertAllRaise( - SyntaxError, - "f-string: expecting a valid expression after '{'", - [ - "f'{1=}{;'", - "f'{1=}{+;'", - "f'{1=}{2}{;'", - "f'{1=}{3}{;'", - ], - ) - self.assertAllRaise( - SyntaxError, - "f-string: expecting '=', or '!', or ':', or '}'", - [ - "f'{1=}{1;'", - "f'{1=}{1;}'", - ], - ) + self.assertAllRaise(SyntaxError, "f-string: expecting a valid expression after '{'", + [ + "f'{1=}{;'", + "f'{1=}{+;'", + "f'{1=}{2}{;'", + "f'{1=}{3}{;'", + ]) + self.assertAllRaise(SyntaxError, "f-string: expecting '=', or '!', or ':', or '}'", + [ + "f'{1=}{1;'", + "f'{1=}{1;}'", + ]) def test_debug_in_file(self): with temp_cwd(): - script = "script.py" - with open("script.py", "w") as f: + script = 'script.py' + with open('script.py', 'w') as f: f.write(f"""\ print(f'''{{ 3 =}}''')""") _, stdout, _ = assert_python_ok(script) - self.assertEqual( - stdout.decode("utf-8").strip().replace("\r\n", "\n").replace("\r", "\n"), - "3\n=3", - ) + self.assertEqual(stdout.decode('utf-8').strip().replace('\r\n', '\n').replace('\r', '\n'), + "3\n=3") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_syntax_warning_infinite_recursion_in_file(self): with temp_cwd(): - script = "script.py" - with open(script, "w") as f: + script = 'script.py' + with open(script, 'w') as f: f.write(r"print(f'\{1}')") _, stdout, stderr = assert_python_ok(script) - self.assertIn(rb"\1", stdout) + self.assertIn(rb'\1', stdout) self.assertEqual(len(stderr.strip().splitlines()), 2) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'dis' has no attribute 'get_instructions' + def test_fstring_without_formatting_bytecode(self): + # f-string without any formatting should emit the same bytecode + # as a normal string. See gh-99606. + def get_code(s): + return [(i.opname, i.oparg) for i in dis.get_instructions(s)] + + for s in ["", "some string"]: + self.assertEqual(get_code(f"'{s}'"), get_code(f"f'{s}'")) + + def test_gh129093(self): + self.assertEqual(f'{1==2=}', '1==2=False') + self.assertEqual(f'{1 == 2=}', '1 == 2=False') + self.assertEqual(f'{1!=2=}', '1!=2=True') + self.assertEqual(f'{1 != 2=}', '1 != 2=True') + + self.assertEqual(f'{(1) != 2=}', '(1) != 2=True') + self.assertEqual(f'{(1*2) != (3)=}', '(1*2) != (3)=True') + + self.assertEqual(f'{1 != 2 == 3 != 4=}', '1 != 2 == 3 != 4=False') + self.assertEqual(f'{1 == 2 != 3 == 4=}', '1 == 2 != 3 == 4=False') + + self.assertEqual(f'{f'{1==2=}'=}', "f'{1==2=}'='1==2=False'") + self.assertEqual(f'{f'{1 == 2=}'=}', "f'{1 == 2=}'='1 == 2=False'") + self.assertEqual(f'{f'{1!=2=}'=}', "f'{1!=2=}'='1!=2=True'") + self.assertEqual(f'{f'{1 != 2=}'=}', "f'{1 != 2=}'='1 != 2=True'") + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "f-string: newlines are not allowed in format specifiers" does not match "'unexpected EOF while parsing' (<string>, line 2)" + def test_newlines_in_format_specifiers(self): + cases = [ + """f'{1:d\n}'""", + """f'__{ + 1:d + }__'""", + '''f"{value:. + {'2f'}}"''', + '''f"{value: + {'.2f'}f}"''', + '''f"{value: + #{'x'}}"''', + ] + self.assertAllRaise(SyntaxError, "f-string: newlines are not allowed in format specifiers", cases) + + valid_cases = [ + """f'''__{ + 1:d + }__'''""", + """f'''{1:d\n}'''""", + ] + + for case in valid_cases: + compile(case, "<string>", "exec") + + def test_raw_fstring_format_spec(self): + # Test raw f-string format spec behavior (Issue #137314). + # + # Raw f-strings should preserve literal backslashes in format specifications, + # not interpret them as escape sequences. + class UnchangedFormat: + """Test helper that returns the format spec unchanged.""" + def __format__(self, format): + return format + + # Test basic escape sequences + self.assertEqual(f"{UnchangedFormat():\xFF}", 'ÿ') + self.assertEqual(rf"{UnchangedFormat():\xFF}", '\\xFF') + + # Test nested expressions with raw/non-raw combinations + self.assertEqual(rf"{UnchangedFormat():{'\xFF'}}", 'ÿ') + self.assertEqual(f"{UnchangedFormat():{r'\xFF'}}", '\\xFF') + self.assertEqual(rf"{UnchangedFormat():{r'\xFF'}}", '\\xFF') + + # Test continuation character in format specs + self.assertEqual(f"""{UnchangedFormat():{'a'\ + 'b'}}""", 'ab') + self.assertEqual(rf"""{UnchangedFormat():{'a'\ + 'b'}}""", 'ab') + + # Test multiple format specs in same raw f-string + self.assertEqual(rf"{UnchangedFormat():\xFF} {UnchangedFormat():\n}", '\\xFF \\n') + -if __name__ == "__main__": +if __name__ == '__main__': unittest.main() From 62067aefd3222db5bfb0185cc28398459f30fab2 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:58:36 +0200 Subject: [PATCH 266/819] Update `uuid` from 3.13.7 (#6155) --- Lib/test/test_uuid.py | 31 +++++++++++++++++++++++++++++-- Lib/uuid.py | 20 ++++++++++++-------- stdlib/src/uuid.rs | 3 +++ 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 069221ae475..4aa15f69932 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -1,6 +1,7 @@ import unittest from test import support from test.support import import_helper +from test.support.script_helper import assert_python_ok import builtins import contextlib import copy @@ -32,8 +33,7 @@ def get_command_stdout(command, args): class BaseTestUUID: uuid = None - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_safe_uuid_enum(self): class CheckedSafeUUID(enum.Enum): safe = 0 @@ -775,10 +775,37 @@ def test_cli_uuid5_ouputted_with_valid_namespace_and_name(self): class TestUUIDWithoutExtModule(BaseTestUUID, unittest.TestCase): uuid = py_uuid + @unittest.skipUnless(c_uuid, 'requires the C _uuid module') class TestUUIDWithExtModule(BaseTestUUID, unittest.TestCase): uuid = c_uuid + def check_has_stable_libuuid_extractable_node(self): + if not self.uuid._has_stable_extractable_node: + self.skipTest("libuuid cannot deduce MAC address") + + @unittest.skipUnless(os.name == 'posix', 'POSIX only') + def test_unix_getnode_from_libuuid(self): + self.check_has_stable_libuuid_extractable_node() + script = 'import uuid; print(uuid._unix_getnode())' + _, n_a, _ = assert_python_ok('-c', script) + _, n_b, _ = assert_python_ok('-c', script) + n_a, n_b = n_a.decode().strip(), n_b.decode().strip() + self.assertTrue(n_a.isdigit()) + self.assertTrue(n_b.isdigit()) + self.assertEqual(n_a, n_b) + + @unittest.skipUnless(os.name == 'nt', 'Windows only') + def test_windows_getnode_from_libuuid(self): + self.check_has_stable_libuuid_extractable_node() + script = 'import uuid; print(uuid._windll_getnode())' + _, n_a, _ = assert_python_ok('-c', script) + _, n_b, _ = assert_python_ok('-c', script) + n_a, n_b = n_a.decode().strip(), n_b.decode().strip() + self.assertTrue(n_a.isdigit()) + self.assertTrue(n_b.isdigit()) + self.assertEqual(n_a, n_b) + class BaseTestInternals: _uuid = py_uuid diff --git a/Lib/uuid.py b/Lib/uuid.py index c286eac38e1..55f46eb5106 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -572,39 +572,43 @@ def _netstat_getnode(): try: import _uuid _generate_time_safe = getattr(_uuid, "generate_time_safe", None) + _has_stable_extractable_node = getattr(_uuid, "has_stable_extractable_node", False) _UuidCreate = getattr(_uuid, "UuidCreate", None) except ImportError: _uuid = None _generate_time_safe = None + _has_stable_extractable_node = False _UuidCreate = None def _unix_getnode(): """Get the hardware address on Unix using the _uuid extension module.""" - if _generate_time_safe: + if _generate_time_safe and _has_stable_extractable_node: uuid_time, _ = _generate_time_safe() return UUID(bytes=uuid_time).node def _windll_getnode(): """Get the hardware address on Windows using the _uuid extension module.""" - if _UuidCreate: + if _UuidCreate and _has_stable_extractable_node: uuid_bytes = _UuidCreate() return UUID(bytes_le=uuid_bytes).node def _random_getnode(): """Get a random node ID.""" - # RFC 4122, $4.1.6 says "For systems with no IEEE address, a randomly or - # pseudo-randomly generated value may be used; see Section 4.5. The - # multicast bit must be set in such addresses, in order that they will - # never conflict with addresses obtained from network cards." + # RFC 9562, §6.10-3 says that + # + # Implementations MAY elect to obtain a 48-bit cryptographic-quality + # random number as per Section 6.9 to use as the Node ID. [...] [and] + # implementations MUST set the least significant bit of the first octet + # of the Node ID to 1. This bit is the unicast or multicast bit, which + # will never be set in IEEE 802 addresses obtained from network cards. # # The "multicast bit" of a MAC address is defined to be "the least # significant bit of the first octet". This works out to be the 41st bit # counting from 1 being the least significant bit, or 1<<40. # # See https://en.wikipedia.org/w/index.php?title=MAC_address&oldid=1128764812#Universal_vs._local_(U/L_bit) - import random - return random.getrandbits(48) | (1 << 40) + return int.from_bytes(os.urandom(6)) | (1 << 40) # _OS_GETTERS, when known, are targeted for a specific OS or platform. diff --git a/stdlib/src/uuid.rs b/stdlib/src/uuid.rs index 9b0e23a81c6..3f75db402c8 100644 --- a/stdlib/src/uuid.rs +++ b/stdlib/src/uuid.rs @@ -30,4 +30,7 @@ mod _uuid { fn has_uuid_generate_time_safe(_vm: &VirtualMachine) -> u32 { 0 } + + #[pyattr(name = "has_stable_extractable_node")] + const HAS_STABLE_EXTRACTABLE_NODE: bool = false; } From 141ed726930d1b1188725a82c913698b51332325 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 17 Sep 2025 02:10:35 +0200 Subject: [PATCH 267/819] Dependencies cleanup (#6151) * Update deps * Remove some unused deps * Update lockfile --- Cargo.lock | 439 +++++++++++++++++++------------------ Cargo.toml | 30 +-- benches/execution.rs | 12 +- benches/microbenchmarks.rs | 2 +- common/Cargo.toml | 2 - compiler/Cargo.toml | 1 - compiler/core/Cargo.toml | 3 +- derive/Cargo.toml | 3 +- stdlib/Cargo.toml | 1 - vm/Cargo.toml | 7 +- 10 files changed, 250 insertions(+), 250 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6aff74336cc..1bd7b0e577d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,12 +42,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -167,7 +161,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cexpr", "clang-sys", "itertools 0.13.0", @@ -178,7 +172,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.106", + "syn", ] [[package]] @@ -189,9 +183,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.3" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "blake2" @@ -272,10 +266,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.34" +version = "1.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" dependencies = [ + "find-msvc-tools", "shlex", ] @@ -302,16 +297,15 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -354,18 +348,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.46" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.46" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" dependencies = [ "anstyle", "clap_lex", @@ -640,25 +634,22 @@ dependencies = [ [[package]] name = "criterion" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", - "is-terminal", - "itertools 0.10.5", + "itertools 0.13.0", "num-traits", - "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", - "serde_derive", "serde_json", "tinytemplate", "walkdir", @@ -666,12 +657,12 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" dependencies = [ "cast", - "itertools 0.10.5", + "itertools 0.13.0", ] [[package]] @@ -829,12 +820,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -866,6 +857,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + [[package]] name = "flame" version = "0.2.2" @@ -881,13 +878,13 @@ dependencies = [ [[package]] name = "flamer" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36b732da54fd4ea34452f2431cf464ac7be94ca4b339c9cd3d3d12eb06fe7aab" +checksum = "7693d9dd1ec1c54f52195dfe255b627f7cec7da33b679cd56de949e662b3db10" dependencies = [ "flame", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -990,7 +987,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.3+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", "wasm-bindgen", ] @@ -1065,9 +1062,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1075,7 +1072,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.0", ] [[package]] @@ -1089,9 +1086,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.0" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" dependencies = [ "equivalent", "hashbrown", @@ -1105,9 +1102,9 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "insta" -version = "1.43.1" +version = "1.43.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371" +checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" dependencies = [ "console", "once_cell", @@ -1123,18 +1120,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", -] - -[[package]] -name = "is-terminal" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.59.0", + "syn", ] [[package]] @@ -1143,15 +1129,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -1197,14 +1174,14 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" dependencies = [ "once_cell", "wasm-bindgen", @@ -1212,12 +1189,12 @@ dependencies = [ [[package]] name = "junction" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72bbdfd737a243da3dfc1f99ee8d6e166480f17ab4ac84d7c34aacd73fc7bd16" +checksum = "c52f6e1bf39a7894f618c9d378904a11dbd7e10fe3ec20d1173600e79b1408d8" dependencies = [ "scopeguard", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -1320,11 +1297,11 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "libc", ] @@ -1341,18 +1318,18 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" +checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd" dependencies = [ "zlib-rs", ] [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" @@ -1366,9 +1343,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lz4_flex" @@ -1542,7 +1519,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cfg-if", "cfg_aliases", "libc", @@ -1555,7 +1532,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cfg-if", "cfg_aliases", "libc", @@ -1627,7 +1604,7 @@ checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1654,7 +1631,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cfg-if", "foreign-types", "libc", @@ -1671,7 +1648,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1827,7 +1804,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1861,7 +1838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.106", + "syn", ] [[package]] @@ -1884,11 +1861,10 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" +checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383" dependencies = [ - "cfg-if", "indoc", "libc", "memoffset", @@ -1902,19 +1878,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" +checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" +checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105" dependencies = [ "libc", "pyo3-build-config", @@ -1922,27 +1897,27 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" +checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.106", + "syn", ] [[package]] name = "pyo3-macros-backend" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" +checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -2070,7 +2045,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", ] [[package]] @@ -2141,23 +2116,23 @@ dependencies = [ [[package]] name = "result-like" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf7172fef6a7d056b5c26bf6c826570267562d51697f4982ff3ba4aec68a9df" +checksum = "bffa194499266bd8a1ac7da6ac7355aa0f81ffa1a5db2baaf20dd13854fd6f4e" dependencies = [ "result-like-derive", ] [[package]] name = "result-like-derive" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d6574c02e894d66370cfc681e5d68fedbc9a548fb55b30a96b3f0ae22d0fe5" +checksum = "01d3b03471c9700a3a6bd166550daaa6124cb4a146ea139fb028e4edaa8f4277" dependencies = [ "pmutil", "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -2166,7 +2141,7 @@ version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" dependencies = [ "aho-corasick", - "bitflags 2.9.3", + "bitflags 2.9.4", "compact_str", "is-macro", "itertools 0.14.0", @@ -2182,7 +2157,7 @@ name = "ruff_python_parser" version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "bstr", "compact_str", "memchr", @@ -2193,7 +2168,7 @@ dependencies = [ "static_assertions", "unicode-ident", "unicode-normalization", - "unicode_names2", + "unicode_names2 1.3.0", ] [[package]] @@ -2229,15 +2204,15 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -2268,7 +2243,7 @@ name = "rustpython-codegen" version = "0.4.0" dependencies = [ "ahash", - "bitflags 2.9.3", + "bitflags 2.9.4", "indexmap", "insta", "itertools 0.14.0", @@ -2284,7 +2259,7 @@ dependencies = [ "rustpython-literal", "rustpython-wtf8", "thiserror 2.0.16", - "unicode_names2", + "unicode_names2 2.0.0", ] [[package]] @@ -2292,8 +2267,7 @@ name = "rustpython-common" version = "0.4.0" dependencies = [ "ascii", - "bitflags 2.9.3", - "bstr", + "bitflags 2.9.4", "cfg-if", "getrandom 0.3.3", "itertools 0.14.0", @@ -2302,7 +2276,6 @@ dependencies = [ "malachite-base", "malachite-bigint", "malachite-q", - "memchr", "num-complex", "num-traits", "once_cell", @@ -2311,7 +2284,7 @@ dependencies = [ "rustpython-literal", "rustpython-wtf8", "siphasher", - "unicode_names2", + "unicode_names2 2.0.0", "widestring", "windows-sys 0.59.0", ] @@ -2320,7 +2293,6 @@ dependencies = [ name = "rustpython-compiler" version = "0.4.0" dependencies = [ - "rand 0.9.2", "ruff_python_ast", "ruff_python_parser", "ruff_source_file", @@ -2334,24 +2306,22 @@ dependencies = [ name = "rustpython-compiler-core" version = "0.4.0" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "itertools 0.14.0", "lz4_flex", "malachite-bigint", "num-complex", "ruff_source_file", "rustpython-wtf8", - "serde", ] [[package]] name = "rustpython-derive" version = "0.4.0" dependencies = [ - "proc-macro2", "rustpython-compiler", "rustpython-derive-impl", - "syn 2.0.106", + "syn", ] [[package]] @@ -2364,7 +2334,7 @@ dependencies = [ "quote", "rustpython-compiler-core", "rustpython-doc", - "syn 2.0.106", + "syn", "syn-ext", "textwrap", ] @@ -2418,7 +2388,7 @@ dependencies = [ name = "rustpython-sre_engine" version = "0.4.0" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "criterion", "num_enum", "optional", @@ -2448,7 +2418,6 @@ dependencies = [ "hex", "indexmap", "itertools 0.14.0", - "junction", "libc", "libsqlite3-sys", "libz-rs-sys", @@ -2494,7 +2463,7 @@ dependencies = [ "unic-ucd-ident", "unicode-bidi-mirroring", "unicode-casing", - "unicode_names2", + "unicode_names2 2.0.0", "uuid", "widestring", "windows-sys 0.59.0", @@ -2508,7 +2477,7 @@ version = "0.4.0" dependencies = [ "ahash", "ascii", - "bitflags 2.9.3", + "bitflags 2.9.4", "bstr", "caseless", "cfg-if", @@ -2533,7 +2502,6 @@ dependencies = [ "log", "malachite-bigint", "memchr", - "memoffset", "nix 0.30.1", "num-complex", "num-integer", @@ -2558,7 +2526,6 @@ dependencies = [ "rustpython-literal", "rustpython-sre_engine", "rustyline", - "schannel", "scoped-tls", "scopeguard", "serde", @@ -2573,7 +2540,6 @@ dependencies = [ "unic-ucd-category", "unic-ucd-ident", "unicode-casing", - "unicode_names2", "wasm-bindgen", "which", "widestring", @@ -2622,7 +2588,7 @@ version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6614df0b6d4cfb20d1d5e295332921793ce499af3ebc011bf1e393380e1e492" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cfg-if", "clipboard-win", "fd-lock", @@ -2664,11 +2630,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -2685,10 +2651,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" dependencies = [ + "serde_core", "serde_derive", ] @@ -2704,27 +2671,37 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_core" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -2837,7 +2814,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -2846,17 +2823,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.106" @@ -2876,7 +2842,7 @@ checksum = "b126de4ef6c2a628a68609dd00733766c3b015894698a438ebdf374933fc31d1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -2885,7 +2851,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "core-foundation", "system-configuration-sys", ] @@ -2902,9 +2868,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tcl-sys" @@ -2956,7 +2922,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -2967,7 +2933,7 @@ checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -3073,9 +3039,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "twox-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b907da542cbced5261bd3256de1b3a1bf340a3d37f93425a07362a1d687de56" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" [[package]] name = "typenum" @@ -3205,21 +3171,21 @@ dependencies = [ [[package]] name = "unicode-bidi-mirroring" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" [[package]] name = "unicode-casing" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "623f59e6af2a98bdafeb93fa277ac8e1e40440973001ca15cf4ae1541cd16d56" +checksum = "061dbb8cc7f108532b6087a0065eff575e892a4bcb503dc57323a197457cc202" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-normalization" @@ -3249,7 +3215,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1673eca9782c84de5f81b82e4109dcfb3611c8ba0d52930ec4a9478f547b2dd" dependencies = [ "phf", - "unicode_names2_generator", + "unicode_names2_generator 1.3.0", +] + +[[package]] +name = "unicode_names2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d189085656ca1203291e965444e7f6a2723fbdd1dd9f34f8482e79bafd8338a0" +dependencies = [ + "phf", + "unicode_names2_generator 2.0.0", ] [[package]] @@ -3264,6 +3240,16 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "unicode_names2_generator" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1262662dc96937c71115228ce2e1d30f41db71a7a45d3459e98783ef94052214" +dependencies = [ + "phf_codegen", + "rand 0.8.5", +] + [[package]] name = "unindent" version = "0.2.4" @@ -3278,9 +3264,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "atomic", "js-sys", @@ -3317,44 +3303,54 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.3+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.106", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe" dependencies = [ "cfg-if", "js-sys", @@ -3365,9 +3361,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3375,22 +3371,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" dependencies = [ "unicode-ident", ] @@ -3409,9 +3405,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" dependencies = [ "js-sys", "wasm-bindgen", @@ -3462,11 +3458,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -3496,13 +3492,13 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.2.0", "windows-result", "windows-strings", ] @@ -3515,7 +3511,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -3526,7 +3522,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -3535,22 +3531,28 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" dependencies = [ - "windows-link", + "windows-link 0.2.0", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" dependencies = [ - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -3580,6 +3582,15 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -3602,7 +3613,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -3746,9 +3757,9 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "wit-bindgen" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "xml" @@ -3767,26 +3778,26 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] name = "zlib-rs" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" +checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" diff --git a/Cargo.toml b/Cargo.toml index d5bfc072047..4412fb9354a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ rustyline = { workspace = true } [dev-dependencies] criterion = { workspace = true } -pyo3 = { version = "0.24", features = ["auto-initialize"] } +pyo3 = { version = "0.26", features = ["auto-initialize"] } [[bench]] name = "execution" @@ -163,27 +163,27 @@ ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0 ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } -ahash = "0.8.11" +ahash = "0.8.12" ascii = "1.1" -bitflags = "2.9.1" +bitflags = "2.9.4" bstr = "1" cfg-if = "1.0" -chrono = "0.4.39" +chrono = "0.4.42" constant_time_eq = "0.4" -criterion = { version = "0.5", features = ["html_reports"] } +criterion = { version = "0.7", features = ["html_reports"] } crossbeam-utils = "0.8.21" flame = "0.2.2" getrandom = { version = "0.3", features = ["std"] } glob = "0.3" hex = "0.4.3" -indexmap = { version = "2.10.0", features = ["std"] } +indexmap = { version = "2.11.3", features = ["std"] } insta = "1.42" itertools = "0.14.0" is-macro = "0.3.7" -junction = "1.2.0" +junction = "1.3.0" libc = "0.2.169" libffi = "4.1" -log = "0.4.27" +log = "0.4.28" nix = { version = "0.30", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] } malachite-bigint = "0.6" malachite-q = "0.6" @@ -204,9 +204,9 @@ radium = "1.1.1" rand = "0.9" rand_core = { version = "0.9", features = ["os_rng"] } rustix = { version = "1.0", features = ["event"] } -rustyline = "17.0.0" -serde = { version = "1.0.133", default-features = false } -schannel = "0.1.27" +rustyline = "17.0.1" +serde = { version = "1.0.225", default-features = false } +schannel = "0.1.28" scoped-tls = "1" scopeguard = "1" static_assertions = "1.1" @@ -214,16 +214,16 @@ strum = "0.27" strum_macros = "0.27" syn = "2" thiserror = "2.0" -thread_local = "1.1.8" -unicode-casing = "0.1.0" +thread_local = "1.1.9" +unicode-casing = "0.1.1" unic-char-property = "0.9.0" unic-normal = "0.9.0" unic-ucd-age = "0.9.0" unic-ucd-bidi = "0.9.0" unic-ucd-category = "0.9.0" unic-ucd-ident = "0.9.0" -unicode_names2 = "1.3.0" -unicode-bidi-mirroring = "0.2" +unicode_names2 = "2.0.0" +unicode-bidi-mirroring = "0.4" widestring = "1.2.0" windows-sys = "0.59.0" wasm-bindgen = "0.2.100" diff --git a/benches/execution.rs b/benches/execution.rs index 7a7ba247e5c..c2239b59d12 100644 --- a/benches/execution.rs +++ b/benches/execution.rs @@ -1,17 +1,15 @@ -use criterion::measurement::WallTime; use criterion::{ - Bencher, BenchmarkGroup, BenchmarkId, Criterion, Throughput, black_box, criterion_group, - criterion_main, + Bencher, BenchmarkGroup, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main, + measurement::WallTime, }; use rustpython_compiler::Mode; use rustpython_vm::{Interpreter, PyResult, Settings}; -use std::collections::HashMap; -use std::path::Path; +use std::{collections::HashMap, hint::black_box, path::Path}; fn bench_cpython_code(b: &mut Bencher, source: &str) { let c_str_source_head = std::ffi::CString::new(source).unwrap(); let c_str_source = c_str_source_head.as_c_str(); - pyo3::Python::with_gil(|py| { + pyo3::Python::attach(|py| { b.iter(|| { let module = pyo3::types::PyModule::from_code(py, c_str_source, c"", c"") .expect("Error running source"); @@ -54,7 +52,7 @@ pub fn benchmark_file_parsing(group: &mut BenchmarkGroup<WallTime>, name: &str, }); group.bench_function(BenchmarkId::new("cpython", name), |b| { use pyo3::types::PyAnyMethods; - pyo3::Python::with_gil(|py| { + pyo3::Python::attach(|py| { let builtins = pyo3::types::PyModule::import(py, "builtins").expect("Failed to import builtins"); let compile = builtins.getattr("compile").expect("no compile in builtins"); diff --git a/benches/microbenchmarks.rs b/benches/microbenchmarks.rs index 5f04f4bbf87..f70be595228 100644 --- a/benches/microbenchmarks.rs +++ b/benches/microbenchmarks.rs @@ -37,7 +37,7 @@ pub struct MicroBenchmark { } fn bench_cpython_code(group: &mut BenchmarkGroup<WallTime>, bench: &MicroBenchmark) { - pyo3::Python::with_gil(|py| { + pyo3::Python::attach(|py| { let setup_name = format!("{}_setup", bench.name); let setup_code = cpy_compile_code(py, &bench.setup, &setup_name).unwrap(); diff --git a/common/Cargo.toml b/common/Cargo.toml index 94704d18c99..fd6f31d4b64 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -17,7 +17,6 @@ rustpython-wtf8 = { workspace = true } ascii = { workspace = true } bitflags = { workspace = true } -bstr = { workspace = true } cfg-if = { workspace = true } getrandom = { workspace = true } itertools = { workspace = true } @@ -25,7 +24,6 @@ libc = { workspace = true } malachite-bigint = { workspace = true } malachite-q = { workspace = true } malachite-base = { workspace = true } -memchr = { workspace = true } num-traits = { workspace = true } once_cell = { workspace = true } parking_lot = { workspace = true, optional = true } diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 2ecc06d59a4..5c793241110 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -18,7 +18,6 @@ ruff_text_size = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -rand = { workspace = true } [lints] workspace = true diff --git a/compiler/core/Cargo.toml b/compiler/core/Cargo.toml index 837f9e4866d..e49c73eb14a 100644 --- a/compiler/core/Cargo.toml +++ b/compiler/core/Cargo.toml @@ -17,9 +17,8 @@ bitflags = { workspace = true } itertools = { workspace = true } malachite-bigint = { workspace = true } num-complex = { workspace = true } -serde = { workspace = true, optional = true, default-features = false, features = ["derive"] } lz4_flex = "0.11" [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 17c0810884f..5ecd490cf94 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -14,8 +14,7 @@ proc-macro = true [dependencies] rustpython-compiler = { workspace = true } rustpython-derive-impl = { workspace = true } -proc-macro2 = { workspace = true } syn = { workspace = true } [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml index 5f8182ea152..c65582ba72f 100644 --- a/stdlib/Cargo.toml +++ b/stdlib/Cargo.toml @@ -117,7 +117,6 @@ lzma-sys = "0.1" xz2 = "0.1" [target.'cfg(windows)'.dependencies] -junction = { workspace = true } paste = { workspace = true } schannel = { workspace = true } widestring = { workspace = true } diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 803eae014e7..eb848f17ab8 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -56,7 +56,6 @@ itertools = { workspace = true } is-macro = { workspace = true } libc = { workspace = true } log = { workspace = true } -nix = { workspace = true } malachite-bigint = { workspace = true } num-complex = { workspace = true } num-integer = { workspace = true } @@ -76,15 +75,13 @@ thread_local = { workspace = true } memchr = { workspace = true } caseless = "0.2.2" -flamer = { version = "0.4", optional = true } +flamer = { version = "0.5", optional = true } half = "2" -memoffset = "0.9.1" optional = { workspace = true } result-like = "0.5.0" timsort = "0.1.2" ## unicode stuff -unicode_names2 = { workspace = true } # TODO: use unic for this; needed for title case: # https://github.com/RustPython/RustPython/pull/832#discussion_r275428939 unicode-casing = { workspace = true } @@ -95,6 +92,7 @@ unic-ucd-ident = { workspace = true } [target.'cfg(unix)'.dependencies] rustix = { workspace = true } +nix = { workspace = true } exitcode = "1.1.2" uname = "0.1.1" @@ -113,7 +111,6 @@ num_cpus = "1.17.0" [target.'cfg(windows)'.dependencies] junction = { workspace = true } -schannel = { workspace = true } winreg = "0.55" [target.'cfg(windows)'.dependencies.windows] From aa0eb4bedf7143334b686a90cf4182a1ebb429e6 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 17 Sep 2025 09:11:13 +0900 Subject: [PATCH 268/819] rustpython-common wasm_js feature (#6116) --- .cargo/config.toml | 3 +++ common/Cargo.toml | 1 + vm/Cargo.toml | 2 +- wasm/lib/.cargo/config.toml | 3 --- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index a590c044d81..635229119f8 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,3 +3,6 @@ rustflags = "-C link-arg=/STACK:8000000" [target.'cfg(all(target_os = "windows", not(target_env = "msvc")))'] rustflags = "-C link-args=-Wl,--stack,8000000" + +[target.wasm32-unknown-unknown] +rustflags = ["--cfg=getrandom_backend=\"wasm_js\""] diff --git a/common/Cargo.toml b/common/Cargo.toml index fd6f31d4b64..c75f33a7b7e 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true [features] threading = ["parking_lot"] +wasm_js = ["getrandom/wasm_js"] [dependencies] rustpython-literal = { workspace = true } diff --git a/vm/Cargo.toml b/vm/Cargo.toml index eb848f17ab8..ffd58361789 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -24,7 +24,7 @@ ast = ["ruff_python_ast", "ruff_text_size"] codegen = ["rustpython-codegen", "ast"] parser = ["ast"] serde = ["dep:serde"] -wasmbind = ["chrono/wasmbind", "getrandom/wasm_js", "wasm-bindgen"] +wasmbind = ["rustpython-common/wasm_js", "chrono/wasmbind", "wasm-bindgen"] [dependencies] rustpython-compiler = { workspace = true, optional = true } diff --git a/wasm/lib/.cargo/config.toml b/wasm/lib/.cargo/config.toml index ce1e7c694ae..f4e8c002fc2 100644 --- a/wasm/lib/.cargo/config.toml +++ b/wasm/lib/.cargo/config.toml @@ -1,5 +1,2 @@ [build] target = "wasm32-unknown-unknown" - -[target.wasm32-unknown-unknown] -rustflags = ["--cfg=getrandom_backend=\"wasm_js\""] From 11e1330758dd37475034fc12966372d0c05c4792 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 20 Sep 2025 15:46:02 +0200 Subject: [PATCH 269/819] Reconfigure dependabot (#6158) * Add `cargo` to dependabot * Remove noisy comments * Don't group updates --- .github/dependabot.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index be006de9a1a..c7ecf5eaac9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,13 +1,10 @@ -# 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: cargo + directory: / + schedule: + interval: weekly - package-ecosystem: github-actions directory: / - groups: - github-actions: - patterns: - - "*" # Group all Actions updates into a single larger pull request schedule: interval: weekly From fdae128cec7fcee6138e95a09d79033351ddb2a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 23:41:28 +0900 Subject: [PATCH 270/819] Bump actions/setup-node from 4 to 5 (#6160) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 5. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0df47990549..dfbdd53cf33 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -395,7 +395,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - run: python -m pip install -r requirements.txt working-directory: ./wasm/tests - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: cache: "npm" cache-dependency-path: "wasm/demo/package-lock.json" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6a1ad32093..c95d2be267c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -108,7 +108,7 @@ jobs: - name: install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 - uses: mwilliamson/setup-wabt-action@v3 with: { wabt-version: "1.0.30" } - name: build demo From 4b91e985ac03db5a233d0853d1bf43953f6c35f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 23:42:02 +0900 Subject: [PATCH 271/819] Bump ruff_python_ast from 0.11.0 to 0.13.1 (#6166) Bumps [ruff_python_ast](https://github.com/astral-sh/ruff) from 0.11.0 to 0.13.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/2cd25ef6410fb5fca96af1578728a3d828d2d53a...706be0a6e7e09936511198f2ff8982915520d138) --- updated-dependencies: - dependency-name: ruff_python_ast dependency-version: 706be0a6e7e09936511198f2ff8982915520d138 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 95 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1bd7b0e577d..88146885d85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,6 +400,20 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "compact_str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "console" version = "0.15.11" @@ -2142,16 +2156,34 @@ source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca dependencies = [ "aho-corasick", "bitflags 2.9.4", - "compact_str", + "compact_str 0.8.1", "is-macro", "itertools 0.14.0", "memchr", - "ruff_python_trivia", - "ruff_source_file", - "ruff_text_size", + "ruff_python_trivia 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", "rustc-hash", ] +[[package]] +name = "ruff_python_ast" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.13.1#706be0a6e7e09936511198f2ff8982915520d138" +dependencies = [ + "aho-corasick", + "bitflags 2.9.4", + "compact_str 0.9.0", + "is-macro", + "itertools 0.14.0", + "memchr", + "ruff_python_trivia 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", + "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", + "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", + "rustc-hash", + "thiserror 2.0.16", +] + [[package]] name = "ruff_python_parser" version = "0.0.0" @@ -2159,11 +2191,11 @@ source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca dependencies = [ "bitflags 2.9.4", "bstr", - "compact_str", + "compact_str 0.8.1", "memchr", - "ruff_python_ast", - "ruff_python_trivia", - "ruff_text_size", + "ruff_python_ast 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_python_trivia 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", "rustc-hash", "static_assertions", "unicode-ident", @@ -2177,8 +2209,19 @@ version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" dependencies = [ "itertools 0.14.0", - "ruff_source_file", - "ruff_text_size", + "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "unicode-ident", +] + +[[package]] +name = "ruff_python_trivia" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.13.1#706be0a6e7e09936511198f2ff8982915520d138" +dependencies = [ + "itertools 0.14.0", + "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", + "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", "unicode-ident", ] @@ -2188,7 +2231,16 @@ version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" dependencies = [ "memchr", - "ruff_text_size", + "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", +] + +[[package]] +name = "ruff_source_file" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.13.1#706be0a6e7e09936511198f2ff8982915520d138" +dependencies = [ + "memchr", + "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", ] [[package]] @@ -2196,6 +2248,11 @@ name = "ruff_text_size" version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" +[[package]] +name = "ruff_text_size" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.13.1#706be0a6e7e09936511198f2ff8982915520d138" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -2252,9 +2309,9 @@ dependencies = [ "memchr", "num-complex", "num-traits", - "ruff_python_ast", + "ruff_python_ast 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", "ruff_python_parser", - "ruff_text_size", + "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", "rustpython-compiler-core", "rustpython-literal", "rustpython-wtf8", @@ -2293,10 +2350,10 @@ dependencies = [ name = "rustpython-compiler" version = "0.4.0" dependencies = [ - "ruff_python_ast", + "ruff_python_ast 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", "ruff_python_parser", - "ruff_source_file", - "ruff_text_size", + "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", "rustpython-codegen", "rustpython-compiler-core", "thiserror 2.0.16", @@ -2311,7 +2368,7 @@ dependencies = [ "lz4_flex", "malachite-bigint", "num-complex", - "ruff_source_file", + "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", "rustpython-wtf8", ] @@ -2513,9 +2570,9 @@ dependencies = [ "parking_lot", "paste", "result-like", - "ruff_python_ast", + "ruff_python_ast 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", "ruff_python_parser", - "ruff_text_size", + "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", "rustix", "rustpython-codegen", "rustpython-common", From 150e8ef43d4f1ca5ce5ba69b0c4ff477d136cc36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Sep 2025 00:01:06 +0900 Subject: [PATCH 272/819] Bump actions/download-artifact from 4 to 5 (#6162) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c95d2be267c..1820cdcbd49 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -139,7 +139,7 @@ jobs: needs: [build, build-wasm] steps: - name: Download Binary Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: path: bin pattern: rustpython-* From 30cbc4129870820a27956f1e15ba17656ec748bd Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 20 Sep 2025 18:53:00 +0200 Subject: [PATCH 273/819] Update github actions in CI (#6169) * Update `setup-python` to v6 * Update `checkout` to v5 --- .github/workflows/ci.yaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index dfbdd53cf33..6f891eb05dc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -122,7 +122,7 @@ jobs: - windows-2025 # TODO: Switch to `windows-latest` on 2025/09/30 fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: components: clippy @@ -181,7 +181,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: target: i686-unknown-linux-gnu @@ -251,10 +251,10 @@ jobs: - windows-2025 # TODO: Switch to `windows-latest` on 2025/09/30 fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} - name: Set up the Windows environment @@ -273,7 +273,7 @@ jobs: - name: build rustpython run: cargo build --release --verbose --features=threading ${{ env.CARGO_ARGS }},jit if: runner.os != 'macOS' - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} - name: run snippets @@ -316,7 +316,7 @@ jobs: name: Check Rust code with rustfmt and clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy @@ -324,7 +324,7 @@ jobs: run: cargo fmt --check - name: run clippy on wasm run: cargo clippy --manifest-path=wasm/lib/Cargo.toml -- -Dwarnings - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} - name: install ruff @@ -357,7 +357,7 @@ jobs: env: NIGHTLY_CHANNEL: nightly steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@master with: @@ -379,7 +379,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 @@ -390,7 +390,7 @@ jobs: wget https://github.com/mozilla/geckodriver/releases/download/v0.36.0/geckodriver-v0.36.0-linux64.tar.gz mkdir geckodriver tar -xzf geckodriver-v0.36.0-linux64.tar.gz -C geckodriver - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} - run: python -m pip install -r requirements.txt @@ -440,7 +440,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: target: wasm32-wasip1 From 24f4fbad8260c2e81ac7e8848a18337d3320985a Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:05:32 +0200 Subject: [PATCH 274/819] Run scheduled CI jobs only on upstream repo (#6157) * Run scheduled CI jobs only on upstream repo * Only disable if scheduling on forks --- .github/workflows/cron-ci.yaml | 8 ++++++++ .github/workflows/release.yml | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/cron-ci.yaml b/.github/workflows/cron-ci.yaml index 6389fee1cb1..868675ca1cc 100644 --- a/.github/workflows/cron-ci.yaml +++ b/.github/workflows/cron-ci.yaml @@ -18,6 +18,8 @@ jobs: codecov: name: Collect code coverage data runs-on: ubuntu-latest + # Disable this scheduled job when running on a fork. + if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -44,6 +46,8 @@ jobs: testdata: name: Collect regression test data runs-on: ubuntu-latest + # Disable this scheduled job when running on a fork. + if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -73,6 +77,8 @@ jobs: whatsleft: name: Collect what is left data runs-on: ubuntu-latest + # Disable this scheduled job when running on a fork. + if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -111,6 +117,8 @@ jobs: benchmark: name: Collect benchmark data runs-on: ubuntu-latest + # Disable this scheduled job when running on a fork. + if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1820cdcbd49..5d78a663393 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,8 @@ env: jobs: build: runs-on: ${{ matrix.platform.runner }} + # Disable this scheduled job when running on a fork. + if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} strategy: matrix: platform: @@ -88,6 +90,8 @@ jobs: build-wasm: runs-on: ubuntu-latest + # Disable this scheduled job when running on a fork. + if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -136,6 +140,8 @@ jobs: release: runs-on: ubuntu-latest + # Disable this scheduled job when running on a fork. + if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} needs: [build, build-wasm] steps: - name: Download Binary Artifacts From 3c01be29c4c008a0b69c140e3a064191a43fb023 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 21 Sep 2025 12:33:41 +0200 Subject: [PATCH 275/819] Update some tests to 3.13.7 (#6171) * Update `test_call.py` from 3.13.7 * Update `test_yield_from.py` from 3.13.7 * Update more tests to 3.13.7 * More tests to 3.13.7 * Remove patch from passing test --- Lib/test/test_call.py | 884 ++++++++++++++++++++++++------- Lib/test/test_file.py | 5 +- Lib/test/test_raise.py | 18 +- Lib/test/test_scope.py | 80 ++- Lib/test/test_slice.py | 3 +- Lib/test/test_string_literals.py | 15 +- Lib/test/test_strtod.py | 17 +- Lib/test/test_sundry.py | 29 +- Lib/test/test_yield_from.py | 552 ++++++++++++++++++- 9 files changed, 1332 insertions(+), 271 deletions(-) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 3cb9659acb2..86ba0aa4b63 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -1,14 +1,29 @@ -import datetime import unittest -from test.support import cpython_only +from test.support import (cpython_only, is_wasi, requires_limited_api, Py_DEBUG, + set_recursion_limit, skip_on_s390x, import_helper) try: import _testcapi except ImportError: _testcapi = None +try: + import _testlimitedcapi +except ImportError: + _testlimitedcapi = None import struct import collections import itertools import gc +import contextlib +import sys +import types + + +class BadStr(str): + def __eq__(self, other): + return True + def __hash__(self): + # Guaranteed different hash + return str.__hash__(self) ^ 3 class FunctionCalls(unittest.TestCase): @@ -26,124 +41,22 @@ def fn(**kw): self.assertIsInstance(res, dict) self.assertEqual(list(res.items()), expected) - -# The test cases here cover several paths through the function calling -# code. They depend on the METH_XXX flag that is used to define a C -# function, which can't be verified from Python. If the METH_XXX decl -# for a C function changes, these tests may not cover the right paths. - -class CFunctionCalls(unittest.TestCase): - - def test_varargs0(self): - self.assertRaises(TypeError, {}.__contains__) - - def test_varargs1(self): - {}.__contains__(0) - - def test_varargs2(self): - self.assertRaises(TypeError, {}.__contains__, 0, 1) - - def test_varargs0_ext(self): - try: - {}.__contains__(*()) - except TypeError: - pass - - def test_varargs1_ext(self): - {}.__contains__(*(0,)) - - def test_varargs2_ext(self): - try: - {}.__contains__(*(1, 2)) - except TypeError: - pass - else: - raise RuntimeError - - def test_varargs1_kw(self): - self.assertRaises(TypeError, {}.__contains__, x=2) - - def test_varargs2_kw(self): - self.assertRaises(TypeError, {}.__contains__, x=2, y=2) - - def test_oldargs0_0(self): - {}.keys() - - def test_oldargs0_1(self): - self.assertRaises(TypeError, {}.keys, 0) - - def test_oldargs0_2(self): - self.assertRaises(TypeError, {}.keys, 0, 1) - - def test_oldargs0_0_ext(self): - {}.keys(*()) - - def test_oldargs0_1_ext(self): - try: - {}.keys(*(0,)) - except TypeError: + def test_frames_are_popped_after_failed_calls(self): + # GH-93252: stuff blows up if we don't pop the new frame after + # recovering from failed calls: + def f(): pass - else: - raise RuntimeError - - def test_oldargs0_2_ext(self): - try: - {}.keys(*(1, 2)) - except TypeError: - pass - else: - raise RuntimeError - - def test_oldargs0_0_kw(self): - try: - {}.keys(x=2) - except TypeError: - pass - else: - raise RuntimeError - - def test_oldargs0_1_kw(self): - self.assertRaises(TypeError, {}.keys, x=2) - - def test_oldargs0_2_kw(self): - self.assertRaises(TypeError, {}.keys, x=2, y=2) - - def test_oldargs1_0(self): - self.assertRaises(TypeError, [].count) - - def test_oldargs1_1(self): - [].count(1) - - def test_oldargs1_2(self): - self.assertRaises(TypeError, [].count, 1, 2) - - def test_oldargs1_0_ext(self): - try: - [].count(*()) - except TypeError: - pass - else: - raise RuntimeError - - def test_oldargs1_1_ext(self): - [].count(*(1,)) - - def test_oldargs1_2_ext(self): - try: - [].count(*(1, 2)) - except TypeError: - pass - else: - raise RuntimeError - - def test_oldargs1_0_kw(self): - self.assertRaises(TypeError, [].count, x=2) - - def test_oldargs1_1_kw(self): - self.assertRaises(TypeError, [].count, {}, x=2) - - def test_oldargs1_2_kw(self): - self.assertRaises(TypeError, [].count, x=2, y=2) + class C: + def m(self): + pass + callables = [f, C.m, [].__len__] + for c in callables: + for _ in range(1000): + try: + c(None) + except TypeError: + pass + # BOOM! @cpython_only @@ -158,11 +71,12 @@ def test_varargs2(self): self.assertRaisesRegex(TypeError, msg, {}.__contains__, 0, 1) def test_varargs3(self): - msg = r"^from_bytes\(\) takes exactly 2 positional arguments \(3 given\)" + msg = r"^from_bytes\(\) takes at most 2 positional arguments \(3 given\)" self.assertRaisesRegex(TypeError, msg, int.from_bytes, b'a', 'little', False) def test_varargs1min(self): - msg = r"get expected at least 1 argument, got 0" + msg = (r"get\(\) takes at least 1 argument \(0 given\)|" + r"get expected at least 1 argument, got 0") self.assertRaisesRegex(TypeError, msg, {}.get) msg = r"expected 1 argument, got 0" @@ -173,11 +87,13 @@ def test_varargs2min(self): self.assertRaisesRegex(TypeError, msg, getattr) def test_varargs1max(self): - msg = r"input expected at most 1 argument, got 2" + msg = (r"input\(\) takes at most 1 argument \(2 given\)|" + r"input expected at most 1 argument, got 2") self.assertRaisesRegex(TypeError, msg, input, 1, 2) def test_varargs2max(self): - msg = r"get expected at most 2 arguments, got 3" + msg = (r"get\(\) takes at most 2 arguments \(3 given\)|" + r"get expected at most 2 arguments, got 3") self.assertRaisesRegex(TypeError, msg, {}.get, 1, 2, 3) def test_varargs1_kw(self): @@ -193,7 +109,7 @@ def test_varargs3_kw(self): self.assertRaisesRegex(TypeError, msg, bool, x=2) def test_varargs4_kw(self): - msg = r"^index\(\) takes no keyword arguments$" + msg = r"^(list[.])?index\(\) takes no keyword arguments$" self.assertRaisesRegex(TypeError, msg, [].index, x=2) def test_varargs5_kw(self): @@ -209,19 +125,19 @@ def test_varargs7_kw(self): self.assertRaisesRegex(TypeError, msg, next, x=2) def test_varargs8_kw(self): - msg = r"^pack\(\) takes no keyword arguments$" + msg = r"^_struct[.]pack\(\) takes no keyword arguments$" self.assertRaisesRegex(TypeError, msg, struct.pack, x=2) def test_varargs9_kw(self): - msg = r"^pack_into\(\) takes no keyword arguments$" + msg = r"^_struct[.]pack_into\(\) takes no keyword arguments$" self.assertRaisesRegex(TypeError, msg, struct.pack_into, x=2) def test_varargs10_kw(self): - msg = r"^index\(\) takes no keyword arguments$" + msg = r"^deque[.]index\(\) takes no keyword arguments$" self.assertRaisesRegex(TypeError, msg, collections.deque().index, x=2) def test_varargs11_kw(self): - msg = r"^pack\(\) takes no keyword arguments$" + msg = r"^Struct[.]pack\(\) takes no keyword arguments$" self.assertRaisesRegex(TypeError, msg, struct.Struct.pack, struct.Struct(""), x=2) def test_varargs12_kw(self): @@ -238,9 +154,9 @@ def test_varargs14_kw(self): itertools.product, 0, repeat=1, foo=2) def test_varargs15_kw(self): - msg = r"^ImportError\(\) takes at most 2 keyword arguments \(3 given\)$" + msg = r"^ImportError\(\) takes at most 3 keyword arguments \(4 given\)$" self.assertRaisesRegex(TypeError, msg, - ImportError, 0, name=1, path=2, foo=3) + ImportError, 0, name=1, path=2, name_from=3, foo=3) def test_varargs16_kw(self): msg = r"^min\(\) takes at most 2 keyword arguments \(3 given\)$" @@ -248,10 +164,22 @@ def test_varargs16_kw(self): min, 0, default=1, key=2, foo=3) def test_varargs17_kw(self): - msg = r"^print\(\) takes at most 4 keyword arguments \(5 given\)$" + msg = r"print\(\) got an unexpected keyword argument 'foo'$" self.assertRaisesRegex(TypeError, msg, print, 0, sep=1, end=2, file=3, flush=4, foo=5) + def test_varargs18_kw(self): + # _PyArg_UnpackKeywordsWithVararg() + msg = r"invalid keyword argument for print\(\)$" + with self.assertRaisesRegex(TypeError, msg): + print(0, 1, **{BadStr('foo'): ','}) + + def test_varargs19_kw(self): + # _PyArg_UnpackKeywords() + msg = r"invalid keyword argument for round\(\)$" + with self.assertRaisesRegex(TypeError, msg): + round(1.75, **{BadStr('foo'): 1}) + def test_oldargs0_1(self): msg = r"keys\(\) takes no arguments \(1 given\)" self.assertRaisesRegex(TypeError, msg, {}.keys, 0) @@ -288,6 +216,208 @@ def test_oldargs1_2_kw(self): msg = r"count\(\) takes no keyword arguments" self.assertRaisesRegex(TypeError, msg, [].count, x=2, y=2) + def test_object_not_callable(self): + msg = r"^'object' object is not callable$" + self.assertRaisesRegex(TypeError, msg, object()) + + def test_module_not_callable_no_suggestion_0(self): + msg = r"^'module' object is not callable$" + self.assertRaisesRegex(TypeError, msg, types.ModuleType("mod")) + + def test_module_not_callable_no_suggestion_1(self): + msg = r"^'module' object is not callable$" + mod = types.ModuleType("mod") + mod.mod = 42 + self.assertRaisesRegex(TypeError, msg, mod) + + def test_module_not_callable_no_suggestion_2(self): + msg = r"^'module' object is not callable$" + mod = types.ModuleType("mod") + del mod.__name__ + self.assertRaisesRegex(TypeError, msg, mod) + + def test_module_not_callable_no_suggestion_3(self): + msg = r"^'module' object is not callable$" + mod = types.ModuleType("mod") + mod.__name__ = 42 + self.assertRaisesRegex(TypeError, msg, mod) + + def test_module_not_callable_suggestion(self): + msg = r"^'module' object is not callable\. Did you mean: 'mod\.mod\(\.\.\.\)'\?$" + mod = types.ModuleType("mod") + mod.mod = lambda: ... + self.assertRaisesRegex(TypeError, msg, mod) + + +@unittest.skipIf(_testcapi is None, "requires _testcapi") +class TestCallingConventions(unittest.TestCase): + """Test calling using various C calling conventions (METH_*) from Python + + Subclasses test several kinds of functions (module-level, methods, + class methods static methods) using these attributes: + obj: the object that contains tested functions (as attributes) + expected_self: expected "self" argument to the C function + + The base class tests module-level functions. + """ + + def setUp(self): + self.obj = self.expected_self = _testcapi + + def test_varargs(self): + self.assertEqual( + self.obj.meth_varargs(1, 2, 3), + (self.expected_self, (1, 2, 3)), + ) + + def test_varargs_ext(self): + self.assertEqual( + self.obj.meth_varargs(*(1, 2, 3)), + (self.expected_self, (1, 2, 3)), + ) + + def test_varargs_error_kw(self): + msg = r"meth_varargs\(\) takes no keyword arguments" + self.assertRaisesRegex( + TypeError, msg, lambda: self.obj.meth_varargs(k=1), + ) + + def test_varargs_keywords(self): + self.assertEqual( + self.obj.meth_varargs_keywords(1, 2, a=3, b=4), + (self.expected_self, (1, 2), {'a': 3, 'b': 4}) + ) + + def test_varargs_keywords_ext(self): + self.assertEqual( + self.obj.meth_varargs_keywords(*[1, 2], **{'a': 3, 'b': 4}), + (self.expected_self, (1, 2), {'a': 3, 'b': 4}) + ) + + def test_o(self): + self.assertEqual(self.obj.meth_o(1), (self.expected_self, 1)) + + def test_o_ext(self): + self.assertEqual(self.obj.meth_o(*[1]), (self.expected_self, 1)) + + def test_o_error_no_arg(self): + msg = r"meth_o\(\) takes exactly one argument \(0 given\)" + self.assertRaisesRegex(TypeError, msg, self.obj.meth_o) + + def test_o_error_two_args(self): + msg = r"meth_o\(\) takes exactly one argument \(2 given\)" + self.assertRaisesRegex( + TypeError, msg, lambda: self.obj.meth_o(1, 2), + ) + + def test_o_error_ext(self): + msg = r"meth_o\(\) takes exactly one argument \(3 given\)" + self.assertRaisesRegex( + TypeError, msg, lambda: self.obj.meth_o(*(1, 2, 3)), + ) + + def test_o_error_kw(self): + msg = r"meth_o\(\) takes no keyword arguments" + self.assertRaisesRegex( + TypeError, msg, lambda: self.obj.meth_o(k=1), + ) + + def test_o_error_arg_kw(self): + msg = r"meth_o\(\) takes no keyword arguments" + self.assertRaisesRegex( + TypeError, msg, lambda: self.obj.meth_o(k=1), + ) + + def test_noargs(self): + self.assertEqual(self.obj.meth_noargs(), self.expected_self) + + def test_noargs_ext(self): + self.assertEqual(self.obj.meth_noargs(*[]), self.expected_self) + + def test_noargs_error_arg(self): + msg = r"meth_noargs\(\) takes no arguments \(1 given\)" + self.assertRaisesRegex( + TypeError, msg, lambda: self.obj.meth_noargs(1), + ) + + def test_noargs_error_arg2(self): + msg = r"meth_noargs\(\) takes no arguments \(2 given\)" + self.assertRaisesRegex( + TypeError, msg, lambda: self.obj.meth_noargs(1, 2), + ) + + def test_noargs_error_ext(self): + msg = r"meth_noargs\(\) takes no arguments \(3 given\)" + self.assertRaisesRegex( + TypeError, msg, lambda: self.obj.meth_noargs(*(1, 2, 3)), + ) + + def test_noargs_error_kw(self): + msg = r"meth_noargs\(\) takes no keyword arguments" + self.assertRaisesRegex( + TypeError, msg, lambda: self.obj.meth_noargs(k=1), + ) + + def test_fastcall(self): + self.assertEqual( + self.obj.meth_fastcall(1, 2, 3), + (self.expected_self, (1, 2, 3)), + ) + + def test_fastcall_ext(self): + self.assertEqual( + self.obj.meth_fastcall(*(1, 2, 3)), + (self.expected_self, (1, 2, 3)), + ) + + def test_fastcall_error_kw(self): + msg = r"meth_fastcall\(\) takes no keyword arguments" + self.assertRaisesRegex( + TypeError, msg, lambda: self.obj.meth_fastcall(k=1), + ) + + def test_fastcall_keywords(self): + self.assertEqual( + self.obj.meth_fastcall_keywords(1, 2, a=3, b=4), + (self.expected_self, (1, 2), {'a': 3, 'b': 4}) + ) + + def test_fastcall_keywords_ext(self): + self.assertEqual( + self.obj.meth_fastcall_keywords(*(1, 2), **{'a': 3, 'b': 4}), + (self.expected_self, (1, 2), {'a': 3, 'b': 4}) + ) + + +class TestCallingConventionsInstance(TestCallingConventions): + """Test calling instance methods using various calling conventions""" + + def setUp(self): + self.obj = self.expected_self = _testcapi.MethInstance() + + +class TestCallingConventionsClass(TestCallingConventions): + """Test calling class methods using various calling conventions""" + + def setUp(self): + self.obj = self.expected_self = _testcapi.MethClass + + +class TestCallingConventionsClassInstance(TestCallingConventions): + """Test calling class methods on instance""" + + def setUp(self): + self.obj = _testcapi.MethClass() + self.expected_self = _testcapi.MethClass + + +class TestCallingConventionsStatic(TestCallingConventions): + """Test calling static methods using various calling conventions""" + + def setUp(self): + self.obj = _testcapi.MethStatic() + self.expected_self = None + def pyfunc(arg1, arg2): return [arg1, arg2] @@ -315,14 +445,15 @@ def static_method(): PYTHON_INSTANCE = PythonClass() - -IGNORE_RESULT = object() +NULL_OR_EMPTY = object() -@cpython_only class FastCallTests(unittest.TestCase): + """Test calling using various callables from C + """ + # Test calls with positional arguments - CALLS_POSARGS = ( + CALLS_POSARGS = [ # (func, args: tuple, result) # Python function with 2 arguments @@ -341,31 +472,11 @@ class FastCallTests(unittest.TestCase): (PYTHON_INSTANCE.class_method, (), "classmethod"), (PYTHON_INSTANCE.static_method, (), "staticmethod"), - # C function: METH_NOARGS - (globals, (), IGNORE_RESULT), - - # C function: METH_O - (id, ("hello",), IGNORE_RESULT), - - # C function: METH_VARARGS - (dir, (1,), IGNORE_RESULT), - - # C function: METH_VARARGS | METH_KEYWORDS - (min, (5, 9), 5), - - # C function: METH_FASTCALL - (divmod, (1000, 33), (30, 10)), - - # C type static method: METH_FASTCALL | METH_CLASS - (int.from_bytes, (b'\x01\x00', 'little'), 1), - - # bpo-30524: Test that calling a C type static method with no argument - # doesn't crash (ignore the result): METH_FASTCALL | METH_CLASS - (datetime.datetime.now, (), IGNORE_RESULT), - ) + # C callables are added later + ] # Test calls with positional and keyword arguments - CALLS_KWARGS = ( + CALLS_KWARGS = [ # (func, args: tuple, kwargs: dict, result) # Python function with 2 arguments @@ -376,34 +487,57 @@ class FastCallTests(unittest.TestCase): (PYTHON_INSTANCE.method, (1,), {'arg2': 2}, [1, 2]), (PYTHON_INSTANCE.method, (), {'arg1': 1, 'arg2': 2}, [1, 2]), - # C function: METH_VARARGS | METH_KEYWORDS - (max, ([],), {'default': 9}, 9), - - # C type static method: METH_FASTCALL | METH_CLASS - (int.from_bytes, (b'\x01\x00',), {'byteorder': 'little'}, 1), - (int.from_bytes, (), {'bytes': b'\x01\x00', 'byteorder': 'little'}, 1), - ) + # C callables are added later + ] + + # Add all the calling conventions and variants of C callables + if _testcapi: + _instance = _testcapi.MethInstance() + for obj, expected_self in ( + (_testcapi, _testcapi), # module-level function + (_instance, _instance), # bound method + (_testcapi.MethClass, _testcapi.MethClass), # class method on class + (_testcapi.MethClass(), _testcapi.MethClass), # class method on inst. + (_testcapi.MethStatic, None), # static method + ): + CALLS_POSARGS.extend([ + (obj.meth_varargs, (1, 2), (expected_self, (1, 2))), + (obj.meth_varargs_keywords, + (1, 2), (expected_self, (1, 2), NULL_OR_EMPTY)), + (obj.meth_fastcall, (1, 2), (expected_self, (1, 2))), + (obj.meth_fastcall, (), (expected_self, ())), + (obj.meth_fastcall_keywords, + (1, 2), (expected_self, (1, 2), NULL_OR_EMPTY)), + (obj.meth_fastcall_keywords, + (), (expected_self, (), NULL_OR_EMPTY)), + (obj.meth_noargs, (), expected_self), + (obj.meth_o, (123, ), (expected_self, 123)), + ]) + + CALLS_KWARGS.extend([ + (obj.meth_varargs_keywords, + (1, 2), {'x': 'y'}, (expected_self, (1, 2), {'x': 'y'})), + (obj.meth_varargs_keywords, + (), {'x': 'y'}, (expected_self, (), {'x': 'y'})), + (obj.meth_varargs_keywords, + (1, 2), {}, (expected_self, (1, 2), NULL_OR_EMPTY)), + (obj.meth_fastcall_keywords, + (1, 2), {'x': 'y'}, (expected_self, (1, 2), {'x': 'y'})), + (obj.meth_fastcall_keywords, + (), {'x': 'y'}, (expected_self, (), {'x': 'y'})), + (obj.meth_fastcall_keywords, + (1, 2), {}, (expected_self, (1, 2), NULL_OR_EMPTY)), + ]) def check_result(self, result, expected): - if expected is IGNORE_RESULT: - return + if isinstance(expected, tuple) and expected[-1] is NULL_OR_EMPTY: + if result[-1] in ({}, None): + expected = (*expected[:-1], result[-1]) self.assertEqual(result, expected) - def test_fastcall(self): - # Test _PyObject_FastCall() - - for func, args, expected in self.CALLS_POSARGS: - with self.subTest(func=func, args=args): - result = _testcapi.pyobject_fastcall(func, args) - self.check_result(result, expected) - - if not args: - # args=NULL, nargs=0 - result = _testcapi.pyobject_fastcall(func, None) - self.check_result(result, expected) - + @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_vectorcall_dict(self): - # Test _PyObject_FastCallDict() + # Test PyObject_VectorcallDict() for func, args, expected in self.CALLS_POSARGS: with self.subTest(func=func, args=args): @@ -411,26 +545,19 @@ def test_vectorcall_dict(self): result = _testcapi.pyobject_fastcalldict(func, args, None) self.check_result(result, expected) - # kwargs={} - result = _testcapi.pyobject_fastcalldict(func, args, {}) - self.check_result(result, expected) - if not args: # args=NULL, nargs=0, kwargs=NULL result = _testcapi.pyobject_fastcalldict(func, None, None) self.check_result(result, expected) - # args=NULL, nargs=0, kwargs={} - result = _testcapi.pyobject_fastcalldict(func, None, {}) - self.check_result(result, expected) - for func, args, kwargs, expected in self.CALLS_KWARGS: with self.subTest(func=func, args=args, kwargs=kwargs): result = _testcapi.pyobject_fastcalldict(func, args, kwargs) self.check_result(result, expected) + @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_vectorcall(self): - # Test _PyObject_Vectorcall() + # Test PyObject_Vectorcall() for func, args, expected in self.CALLS_POSARGS: with self.subTest(func=func, args=args): @@ -458,6 +585,7 @@ def test_vectorcall(self): result = _testcapi.pyobject_vectorcall(func, args, kwnames) self.check_result(result, expected) + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Expected type 'int' but 'IntWithDict' found. def test_fastcall_clearing_dict(self): # Test bpo-36907: the point of the test is just checking that this # does not crash. @@ -469,7 +597,7 @@ def __index__(self): self.kwargs.clear() gc.collect() return 0 - x = IntWithDict(dont_inherit=IntWithDict()) + x = IntWithDict(optimize=IntWithDict()) # We test the argument handling of "compile" here, the compilation # itself is not relevant. When we pass flags=x below, x.__index__() is # called, which changes the keywords dict. @@ -490,10 +618,12 @@ def testfunction_kw(self, *, kw): return self +ADAPTIVE_WARMUP_DELAY = 2 + + +@unittest.skipIf(_testcapi is None, "requires _testcapi") class TestPEP590(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_method_descriptor_flag(self): import functools cached = functools.lru_cache(1)(testfunction) @@ -508,26 +638,32 @@ def test_method_descriptor_flag(self): self.assertTrue(_testcapi.MethodDescriptorDerived.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) self.assertFalse(_testcapi.MethodDescriptorNopGet.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) - # Heap type should not inherit Py_TPFLAGS_METHOD_DESCRIPTOR + # Mutable heap types should not inherit Py_TPFLAGS_METHOD_DESCRIPTOR class MethodDescriptorHeap(_testcapi.MethodDescriptorBase): pass self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_vectorcall_flag(self): self.assertTrue(_testcapi.MethodDescriptorBase.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL) self.assertTrue(_testcapi.MethodDescriptorDerived.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL) self.assertFalse(_testcapi.MethodDescriptorNopGet.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL) self.assertTrue(_testcapi.MethodDescriptor2.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL) - # Heap type should not inherit Py_TPFLAGS_HAVE_VECTORCALL + # Mutable heap types should inherit Py_TPFLAGS_HAVE_VECTORCALL, + # but should lose it when __call__ is overridden class MethodDescriptorHeap(_testcapi.MethodDescriptorBase): pass + self.assertTrue(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL) + MethodDescriptorHeap.__call__ = print + self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL) + + # Mutable heap types should not inherit Py_TPFLAGS_HAVE_VECTORCALL if + # they define __call__ directly + class MethodDescriptorHeap(_testcapi.MethodDescriptorBase): + def __call__(self): + pass self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_vectorcall_override(self): # Check that tp_call can correctly override vectorcall. # MethodDescriptorNopGet implements tp_call but it inherits from @@ -538,14 +674,64 @@ def test_vectorcall_override(self): f = _testcapi.MethodDescriptorNopGet() self.assertIs(f(*args), args) - # TODO: RUSTPYTHON - @unittest.expectedFailure + def test_vectorcall_override_on_mutable_class(self): + """Setting __call__ should disable vectorcall""" + TestType = _testcapi.make_vectorcall_class() + instance = TestType() + self.assertEqual(instance(), "tp_call") + instance.set_vectorcall(TestType) + self.assertEqual(instance(), "vectorcall") # assume vectorcall is used + TestType.__call__ = lambda self: "custom" + self.assertEqual(instance(), "custom") + + def test_vectorcall_override_with_subclass(self): + """Setting __call__ on a superclass should disable vectorcall""" + SuperType = _testcapi.make_vectorcall_class() + class DerivedType(SuperType): + pass + + instance = DerivedType() + + # Derived types with its own vectorcall should be unaffected + UnaffectedType1 = _testcapi.make_vectorcall_class(DerivedType) + UnaffectedType2 = _testcapi.make_vectorcall_class(SuperType) + + # Aside: Quickly check that the C helper actually made derived types + self.assertTrue(issubclass(UnaffectedType1, DerivedType)) + self.assertTrue(issubclass(UnaffectedType2, SuperType)) + + # Initial state: tp_call + self.assertEqual(instance(), "tp_call") + self.assertEqual(_testcapi.has_vectorcall_flag(SuperType), True) + self.assertEqual(_testcapi.has_vectorcall_flag(DerivedType), True) + self.assertEqual(_testcapi.has_vectorcall_flag(UnaffectedType1), True) + self.assertEqual(_testcapi.has_vectorcall_flag(UnaffectedType2), True) + + # Setting the vectorcall function + instance.set_vectorcall(SuperType) + + self.assertEqual(instance(), "vectorcall") + self.assertEqual(_testcapi.has_vectorcall_flag(SuperType), True) + self.assertEqual(_testcapi.has_vectorcall_flag(DerivedType), True) + self.assertEqual(_testcapi.has_vectorcall_flag(UnaffectedType1), True) + self.assertEqual(_testcapi.has_vectorcall_flag(UnaffectedType2), True) + + # Setting __call__ should remove vectorcall from all subclasses + SuperType.__call__ = lambda self: "custom" + + self.assertEqual(instance(), "custom") + self.assertEqual(_testcapi.has_vectorcall_flag(SuperType), False) + self.assertEqual(_testcapi.has_vectorcall_flag(DerivedType), False) + self.assertEqual(_testcapi.has_vectorcall_flag(UnaffectedType1), True) + self.assertEqual(_testcapi.has_vectorcall_flag(UnaffectedType2), True) + + def test_vectorcall(self): # Test a bunch of different ways to call objects: # 1. vectorcall using PyVectorcall_Call() # (only for objects that support vectorcall directly) # 2. normal call - # 3. vectorcall using _PyObject_Vectorcall() + # 3. vectorcall using PyObject_Vectorcall() # 4. call as bound method # 5. call using functools.partial @@ -616,6 +802,300 @@ def __call__(self, *args): self.assertEqual(expected, meth(*args1, **kwargs)) self.assertEqual(expected, wrapped(*args, **kwargs)) + def test_setvectorcall(self): + from _testcapi import function_setvectorcall + def f(num): return num + 1 + assert_equal = self.assertEqual + num = 10 + assert_equal(11, f(num)) + function_setvectorcall(f) + # make sure specializer is triggered by running > 50 times + for _ in range(10 * ADAPTIVE_WARMUP_DELAY): + assert_equal("overridden", f(num)) + + def test_setvectorcall_load_attr_specialization_skip(self): + from _testcapi import function_setvectorcall + + class X: + def __getattribute__(self, attr): + return attr + + assert_equal = self.assertEqual + x = X() + assert_equal("a", x.a) + function_setvectorcall(X.__getattribute__) + # make sure specialization doesn't trigger + # when vectorcall is overridden + for _ in range(ADAPTIVE_WARMUP_DELAY): + assert_equal("overridden", x.a) + + def test_setvectorcall_load_attr_specialization_deopt(self): + from _testcapi import function_setvectorcall + + class X: + def __getattribute__(self, attr): + return attr + + def get_a(x): + return x.a + + assert_equal = self.assertEqual + x = X() + # trigger LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN specialization + for _ in range(ADAPTIVE_WARMUP_DELAY): + assert_equal("a", get_a(x)) + function_setvectorcall(X.__getattribute__) + # make sure specialized LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN + # gets deopted due to overridden vectorcall + for _ in range(ADAPTIVE_WARMUP_DELAY): + assert_equal("overridden", get_a(x)) + + @requires_limited_api + def test_vectorcall_limited_incoming(self): + from _testcapi import pyobject_vectorcall + obj = _testlimitedcapi.LimitedVectorCallClass() + self.assertEqual(pyobject_vectorcall(obj, (), ()), "vectorcall called") + + @requires_limited_api + def test_vectorcall_limited_outgoing(self): + from _testlimitedcapi import call_vectorcall + + args_captured = [] + kwargs_captured = [] + + def f(*args, **kwargs): + args_captured.append(args) + kwargs_captured.append(kwargs) + return "success" + + self.assertEqual(call_vectorcall(f), "success") + self.assertEqual(args_captured, [("foo",)]) + self.assertEqual(kwargs_captured, [{"baz": "bar"}]) + + @requires_limited_api + def test_vectorcall_limited_outgoing_method(self): + from _testlimitedcapi import call_vectorcall_method + + args_captured = [] + kwargs_captured = [] + + class TestInstance: + def f(self, *args, **kwargs): + args_captured.append(args) + kwargs_captured.append(kwargs) + return "success" + + self.assertEqual(call_vectorcall_method(TestInstance()), "success") + self.assertEqual(args_captured, [("foo",)]) + self.assertEqual(kwargs_captured, [{"baz": "bar"}]) + +class A: + def method_two_args(self, x, y): + pass + + @staticmethod + def static_no_args(): + pass + + @staticmethod + def positional_only(arg, /): + pass + +@cpython_only +class TestErrorMessagesUseQualifiedName(unittest.TestCase): + + @contextlib.contextmanager + def check_raises_type_error(self, message): + with self.assertRaises(TypeError) as cm: + yield + self.assertEqual(str(cm.exception), message) + + def test_missing_arguments(self): + msg = "A.method_two_args() missing 1 required positional argument: 'y'" + with self.check_raises_type_error(msg): + A().method_two_args("x") + + def test_too_many_positional(self): + msg = "A.static_no_args() takes 0 positional arguments but 1 was given" + with self.check_raises_type_error(msg): + A.static_no_args("oops it's an arg") + + def test_positional_only_passed_as_keyword(self): + msg = "A.positional_only() got some positional-only arguments passed as keyword arguments: 'arg'" + with self.check_raises_type_error(msg): + A.positional_only(arg="x") + + def test_unexpected_keyword(self): + msg = "A.method_two_args() got an unexpected keyword argument 'bad'" + with self.check_raises_type_error(msg): + A().method_two_args(bad="x") + + def test_multiple_values(self): + msg = "A.method_two_args() got multiple values for argument 'x'" + with self.check_raises_type_error(msg): + A().method_two_args("x", "y", x="oops") + +@cpython_only +class TestErrorMessagesSuggestions(unittest.TestCase): + @contextlib.contextmanager + def check_suggestion_includes(self, message): + with self.assertRaises(TypeError) as cm: + yield + self.assertIn(f"Did you mean '{message}'?", str(cm.exception)) + + @contextlib.contextmanager + def check_suggestion_not_present(self): + with self.assertRaises(TypeError) as cm: + yield + self.assertNotIn("Did you mean", str(cm.exception)) + + def test_unexpected_keyword_suggestion_valid_positions(self): + def foo(blech=None, /, aaa=None, *args, late1=None): + pass + + cases = [ + ("blach", None), + ("aa", "aaa"), + ("orgs", None), + ("late11", "late1"), + ] + + for keyword, suggestion in cases: + with self.subTest(keyword): + ctx = self.check_suggestion_includes(suggestion) if suggestion else self.check_suggestion_not_present() + with ctx: + foo(**{keyword:None}) + + def test_unexpected_keyword_suggestion_kinds(self): + + def substitution(noise=None, more_noise=None, a = None, blech = None): + pass + + def elimination(noise = None, more_noise = None, a = None, blch = None): + pass + + def addition(noise = None, more_noise = None, a = None, bluchin = None): + pass + + def substitution_over_elimination(blach = None, bluc = None): + pass + + def substitution_over_addition(blach = None, bluchi = None): + pass + + def elimination_over_addition(bluc = None, blucha = None): + pass + + def case_change_over_substitution(BLuch=None, Luch = None, fluch = None): + pass + + for func, suggestion in [ + (addition, "bluchin"), + (substitution, "blech"), + (elimination, "blch"), + (addition, "bluchin"), + (substitution_over_elimination, "blach"), + (substitution_over_addition, "blach"), + (elimination_over_addition, "bluc"), + (case_change_over_substitution, "BLuch"), + ]: + with self.subTest(suggestion): + with self.check_suggestion_includes(suggestion): + func(bluch=None) + + def test_unexpected_keyword_suggestion_via_getargs(self): + with self.check_suggestion_includes("maxsplit"): + "foo".split(maxsplt=1) + + self.assertRaisesRegex( + TypeError, r"split\(\) got an unexpected keyword argument 'blech'$", + "foo".split, blech=1 + ) + with self.check_suggestion_not_present(): + "foo".split(blech=1) + with self.check_suggestion_not_present(): + "foo".split(more_noise=1, maxsplt=1) + + # Also test the vgetargskeywords path + with self.check_suggestion_includes("name"): + ImportError(namez="oops") + + self.assertRaisesRegex( + TypeError, r"ImportError\(\) got an unexpected keyword argument 'blech'$", + ImportError, blech=1 + ) + with self.check_suggestion_not_present(): + ImportError(blech=1) + with self.check_suggestion_not_present(): + ImportError(blech=1, namez="oops") + +@cpython_only +class TestRecursion(unittest.TestCase): + + @skip_on_s390x + @unittest.skipIf(is_wasi and Py_DEBUG, "requires deep stack") + @unittest.skipIf(_testcapi is None, "requires _testcapi") + def test_super_deep(self): + + def recurse(n): + if n: + recurse(n-1) + + def py_recurse(n, m): + if n: + py_recurse(n-1, m) + else: + c_py_recurse(m-1) + + def c_recurse(n): + if n: + _testcapi.pyobject_vectorcall(c_recurse, (n-1,), ()) + + def c_py_recurse(m): + if m: + _testcapi.pyobject_vectorcall(py_recurse, (1000, m), ()) + + with set_recursion_limit(100_000): + recurse(90_000) + with self.assertRaises(RecursionError): + recurse(101_000) + c_recurse(100) + with self.assertRaises(RecursionError): + c_recurse(90_000) + c_py_recurse(90) + with self.assertRaises(RecursionError): + c_py_recurse(100_000) + + +class TestFunctionWithManyArgs(unittest.TestCase): + def test_function_with_many_args(self): + for N in (10, 500, 1000): + with self.subTest(N=N): + args = ",".join([f"a{i}" for i in range(N)]) + src = f"def f({args}) : return a{N//2}" + l = {} + exec(src, {}, l) + self.assertEqual(l['f'](*range(N)), N//2) + + +@unittest.skipIf(_testcapi is None, 'need _testcapi') +class TestCAPI(unittest.TestCase): + def test_cfunction_call(self): + def func(*args, **kwargs): + return (args, kwargs) + + # PyCFunction_Call() was removed in Python 3.13 API, but was kept in + # the stable ABI. + def PyCFunction_Call(func, *args, **kwargs): + if kwargs: + return _testcapi.pycfunction_call(func, args, kwargs) + else: + return _testcapi.pycfunction_call(func, args) + + self.assertEqual(PyCFunction_Call(func), ((), {})) + self.assertEqual(PyCFunction_Call(func, 1, 2, 3), ((1, 2, 3), {})) + self.assertEqual(PyCFunction_Call(func, "arg", num=5), (("arg",), {'num': 5})) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_file.py b/Lib/test/test_file.py index d998af936ae..d64f3f797d8 100644 --- a/Lib/test/test_file.py +++ b/Lib/test/test_file.py @@ -344,10 +344,9 @@ def testIteration(self): class COtherFileTests(OtherFileTests, unittest.TestCase): open = io.open - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testSetBufferSize(self): - super().testSetBufferSize() + return super().testSetBufferSize() class PyOtherFileTests(OtherFileTests, unittest.TestCase): open = staticmethod(pyio.open) diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py index 3ada08f7dcf..d7f01509078 100644 --- a/Lib/test/test_raise.py +++ b/Lib/test/test_raise.py @@ -185,6 +185,21 @@ def test_class_cause(self): else: self.fail("No exception raised") + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: 'classmethod' object is not callable + def test_class_cause_nonexception_result(self): + class ConstructsNone(BaseException): + @classmethod + def __new__(*args, **kwargs): + return None + try: + raise IndexError from ConstructsNone + except TypeError as e: + self.assertIn("should have returned an instance of BaseException", str(e)) + except IndexError: + self.fail("Wrong kind of exception raised") + else: + self.fail("No exception raised") + def test_instance_cause(self): cause = KeyError() try: @@ -233,8 +248,7 @@ class TestTracebackType(unittest.TestCase): def raiser(self): raise ValueError - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_attrs(self): try: self.raiser() diff --git a/Lib/test/test_scope.py b/Lib/test/test_scope.py index 29a4ac3c162..662d8eefbc0 100644 --- a/Lib/test/test_scope.py +++ b/Lib/test/test_scope.py @@ -177,6 +177,57 @@ def bar(): self.assertEqual(foo(a=42), 50) self.assertEqual(foo(), 25) + def testCellIsArgAndEscapes(self): + # We need to be sure that a cell passed in as an arg still + # gets wrapped in a new cell if the arg escapes into an + # inner function (closure). + + def external(): + value = 42 + def inner(): + return value + cell, = inner.__closure__ + return cell + cell_ext = external() + + def spam(arg): + def eggs(): + return arg + return eggs + + eggs = spam(cell_ext) + cell_closure, = eggs.__closure__ + cell_eggs = eggs() + + self.assertIs(cell_eggs, cell_ext) + self.assertIsNot(cell_eggs, cell_closure) + + def testCellIsLocalAndEscapes(self): + # We need to be sure that a cell bound to a local still + # gets wrapped in a new cell if the local escapes into an + # inner function (closure). + + def external(): + value = 42 + def inner(): + return value + cell, = inner.__closure__ + return cell + cell_ext = external() + + def spam(arg): + cell = arg + def eggs(): + return cell + return eggs + + eggs = spam(cell_ext) + cell_closure, = eggs.__closure__ + cell_eggs = eggs() + + self.assertIs(cell_eggs, cell_ext) + self.assertIsNot(cell_eggs, cell_closure) + def testRecursion(self): def f(x): @@ -641,10 +692,7 @@ def dec(self): self.assertEqual(c.dec(), 1) self.assertEqual(c.dec(), 0) - # TODO: RUSTPYTHON, figure out how to communicate that `y = 9` should be - # stored as a global rather than a STORE_NAME, even when - # the `global y` is in a nested subscope - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; figure out how to communicate that `y = 9` should be stored as a global rather than a STORE_NAME, even when the `global y` is in a nested subscope def testGlobalInParallelNestedFunctions(self): # A symbol table bug leaked the global statement from one # function to other nested functions in the same block. @@ -763,6 +811,30 @@ def dig(self): gc_collect() # For PyPy or other GCs. self.assertIsNone(ref()) + def test_multiple_nesting(self): + # Regression test for https://github.com/python/cpython/issues/121863 + class MultiplyNested: + def f1(self): + __arg = 1 + class D: + def g(self, __arg): + return __arg + return D().g(_MultiplyNested__arg=2) + + def f2(self): + __arg = 1 + class D: + def g(self, __arg): + return __arg + return D().g + + inst = MultiplyNested() + with self.assertRaises(TypeError): + inst.f1() + + closure = inst.f2() + with self.assertRaises(TypeError): + closure(_MultiplyNested__arg=2) if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_slice.py b/Lib/test/test_slice.py index 53d4c776160..6de7e73c399 100644 --- a/Lib/test/test_slice.py +++ b/Lib/test/test_slice.py @@ -286,8 +286,7 @@ def test_deepcopy(self): self.assertIsNot(s.stop, c.stop) self.assertIsNot(s.step, c.step) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cycle(self): class myobj(): pass o = myobj() diff --git a/Lib/test/test_string_literals.py b/Lib/test/test_string_literals.py index 098e8d3984e..c6b2ffb9de8 100644 --- a/Lib/test/test_string_literals.py +++ b/Lib/test/test_string_literals.py @@ -105,8 +105,7 @@ def test_eval_str_incomplete(self): self.assertRaises(SyntaxError, eval, r""" '\U000000' """) self.assertRaises(SyntaxError, eval, r""" '\U0000000' """) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_eval_str_invalid_escape(self): for b in range(1, 128): if b in b"""\n\r"'01234567NU\\abfnrtuvx""": @@ -145,8 +144,7 @@ def test_eval_str_invalid_escape(self): self.assertRegex(str(w[0].message), 'invalid escape sequence') self.assertEqual(w[0].filename, '<string>') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_eval_str_invalid_octal_escape(self): for i in range(0o400, 0o1000): with self.assertWarns(SyntaxWarning): @@ -172,8 +170,7 @@ def test_eval_str_invalid_octal_escape(self): self.assertEqual(exc.lineno, 2) self.assertEqual(exc.offset, 1) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_escape_locations_with_offset(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter('error', category=SyntaxWarning) @@ -223,8 +220,7 @@ def test_eval_bytes_incomplete(self): self.assertRaises(SyntaxError, eval, r""" b'\x' """) self.assertRaises(SyntaxError, eval, r""" b'\x0' """) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_eval_bytes_invalid_escape(self): for b in range(1, 128): if b in b"""\n\r"'01234567\\abfnrtvx""": @@ -250,8 +246,7 @@ def test_eval_bytes_invalid_escape(self): self.assertEqual(exc.filename, '<string>') self.assertEqual(exc.lineno, 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_eval_bytes_invalid_octal_escape(self): for i in range(0o400, 0o1000): with self.assertWarns(SyntaxWarning): diff --git a/Lib/test/test_strtod.py b/Lib/test/test_strtod.py index b8b7a9d5026..45fdcca92e4 100644 --- a/Lib/test/test_strtod.py +++ b/Lib/test/test_strtod.py @@ -146,7 +146,7 @@ def test_short_halfway_cases(self): digits *= 5 exponent -= 1 - @unittest.skip("TODO: RUSTPYTHON, fails on debug mode, flaky in release mode") + @unittest.skip('TODO: RUSTPYTHON; fails on debug mode, flaky in release mode') def test_halfway_cases(self): # test halfway cases for the round-half-to-even rule for i in range(100 * TEST_SIZE): @@ -173,8 +173,7 @@ def test_halfway_cases(self): s = '{}e{}'.format(digits, exponent) self.check_strtod(s) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_boundaries(self): # boundaries expressed as triples (n, e, u), where # n*10**e is an approximation to the boundary value and @@ -195,8 +194,7 @@ def test_boundaries(self): u *= 10 e -= 1 - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_underflow_boundary(self): # test values close to 2**-1075, the underflow boundary; similar # to boundary_tests, except that the random error doesn't scale @@ -208,8 +206,7 @@ def test_underflow_boundary(self): s = '{}e{}'.format(digits, exponent) self.check_strtod(s) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bigcomp(self): for ndigs in 5, 10, 14, 15, 16, 17, 18, 19, 20, 40, 41, 50: dig10 = 10**ndigs @@ -219,8 +216,7 @@ def test_bigcomp(self): s = '{}e{}'.format(digits, exponent) self.check_strtod(s) - # TODO: RUSTPYTHON, Incorrectly rounded str->float conversion for -07e-321 - @unittest.skip("TODO: RUSTPYTHON; flaky test") + @unittest.skip('TODO: RUSTPYTHON; flaky test') def test_parsing(self): # make '0' more likely to be chosen than other digits digits = '000000123456789' @@ -288,8 +284,7 @@ def negative_exp(n): self.assertEqual(float(negative_exp(20000)), 1.0) self.assertEqual(float(negative_exp(30000)), 1.0) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_particular(self): # inputs that produced crashes or incorrectly rounded results with # previous versions of dtoa.c, for various reasons diff --git a/Lib/test/test_sundry.py b/Lib/test/test_sundry.py index 90af9da8f9f..f4a8d434ed1 100644 --- a/Lib/test/test_sundry.py +++ b/Lib/test/test_sundry.py @@ -1,14 +1,11 @@ """Do a minimal test of all the modules that aren't otherwise tested.""" import importlib -import platform -import sys from test import support from test.support import import_helper from test.support import warnings_helper import unittest class TestUntestedModules(unittest.TestCase): - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_untested_modules_can_be_imported(self): untested = ('encodings',) with warnings_helper.check_warnings(quiet=True): @@ -21,31 +18,6 @@ def test_untested_modules_can_be_imported(self): self.fail('{} has tests even though test_sundry claims ' 'otherwise'.format(name)) - import distutils.bcppcompiler - import distutils.ccompiler - import distutils.cygwinccompiler - import distutils.filelist - import distutils.text_file - import distutils.unixccompiler - - import distutils.command.bdist_dumb - if sys.platform.startswith('win') and not platform.win32_is_iot(): - import distutils.command.bdist_msi - import distutils.command.bdist - import distutils.command.bdist_rpm - import distutils.command.build_clib - import distutils.command.build_ext - import distutils.command.build - import distutils.command.clean - import distutils.command.config - import distutils.command.install_data - import distutils.command.install_egg_info - import distutils.command.install_headers - import distutils.command.install_lib - import distutils.command.register - import distutils.command.sdist - import distutils.command.upload - import html.entities try: @@ -54,5 +26,6 @@ def test_untested_modules_can_be_imported(self): if support.verbose: print("skipping tty") + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_yield_from.py b/Lib/test/test_yield_from.py index 97acfd54139..88fa1b88c90 100644 --- a/Lib/test/test_yield_from.py +++ b/Lib/test/test_yield_from.py @@ -538,8 +538,7 @@ def g(): "finishing g", ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_broken_getattr_handling(self): """ Test subiterator with a broken getattr implementation @@ -787,8 +786,7 @@ def outer(): repr(value), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_throwing_GeneratorExit_into_subgen_that_returns(self): """ Test throwing GeneratorExit into a subgenerator that @@ -819,8 +817,7 @@ def g(): "Enter f", ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_throwing_GeneratorExit_into_subgenerator_that_yields(self): """ Test throwing GeneratorExit into a subgenerator that @@ -887,8 +884,7 @@ def g(): yield from () self.assertRaises(StopIteration, next, g()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_delegating_generators_claim_to_be_running(self): # Check with basic iteration def one(): @@ -904,6 +900,7 @@ def two(): yield 2 g1 = one() self.assertEqual(list(g1), [0, 1, 2, 3]) + # Check with send g1 = one() res = [next(g1)] @@ -913,6 +910,9 @@ def two(): except StopIteration: pass self.assertEqual(res, [0, 1, 2, 3]) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Lists differ: [0, 1, 2] != [0, 1, 2, 3] + def test_delegating_generators_claim_to_be_running_with_throw(self): # Check with throw class MyErr(Exception): pass @@ -949,8 +949,10 @@ def two(): except: self.assertEqual(res, [0, 1, 2, 3]) raise + + def test_delegating_generators_claim_to_be_running_with_close(self): # Check with close - class MyIt(object): + class MyIt: def __iter__(self): return self def __next__(self): @@ -1057,6 +1059,538 @@ def outer(): g.send((1, 2, 3, 4)) self.assertEqual(v, (1, 2, 3, 4)) +class TestInterestingEdgeCases(unittest.TestCase): + + def assert_stop_iteration(self, iterator): + with self.assertRaises(StopIteration) as caught: + next(iterator) + self.assertIsNone(caught.exception.value) + self.assertIsNone(caught.exception.__context__) + + def assert_generator_raised_stop_iteration(self): + return self.assertRaisesRegex(RuntimeError, r"^generator raised StopIteration$") + + def assert_generator_ignored_generator_exit(self): + return self.assertRaisesRegex(RuntimeError, r"^generator ignored GeneratorExit$") + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_close_and_throw_work(self): + + yielded_first = object() + yielded_second = object() + returned = object() + + def inner(): + yield yielded_first + yield yielded_second + return returned + + def outer(): + return (yield from inner()) + + with self.subTest("close"): + g = outer() + self.assertIs(next(g), yielded_first) + g.close() + self.assert_stop_iteration(g) + + with self.subTest("throw GeneratorExit"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = GeneratorExit() + with self.assertRaises(GeneratorExit) as caught: + g.throw(thrown) + self.assertIs(caught.exception, thrown) + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw StopIteration"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = StopIteration() + # PEP 479: + with self.assert_generator_raised_stop_iteration() as caught: + g.throw(thrown) + self.assertIs(caught.exception.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw BaseException"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = BaseException() + with self.assertRaises(BaseException) as caught: + g.throw(thrown) + self.assertIs(caught.exception, thrown) + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw Exception"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = Exception() + with self.assertRaises(Exception) as caught: + g.throw(thrown) + self.assertIs(caught.exception, thrown) + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: GeneratorExit() is not GeneratorExit() + def test_close_and_throw_raise_generator_exit(self): + + yielded_first = object() + yielded_second = object() + returned = object() + + def inner(): + try: + yield yielded_first + yield yielded_second + return returned + finally: + raise raised + + def outer(): + return (yield from inner()) + + with self.subTest("close"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = GeneratorExit() + # GeneratorExit is suppressed. This is consistent with PEP 342: + # https://peps.python.org/pep-0342/#new-generator-method-close + g.close() + self.assert_stop_iteration(g) + + with self.subTest("throw GeneratorExit"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = GeneratorExit() + thrown = GeneratorExit() + with self.assertRaises(GeneratorExit) as caught: + g.throw(thrown) + # The raised GeneratorExit is suppressed, but the thrown one + # propagates. This is consistent with PEP 380: + # https://peps.python.org/pep-0380/#proposal + self.assertIs(caught.exception, thrown) + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw StopIteration"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = GeneratorExit() + thrown = StopIteration() + with self.assertRaises(GeneratorExit) as caught: + g.throw(thrown) + self.assertIs(caught.exception, raised) + self.assertIs(caught.exception.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw BaseException"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = GeneratorExit() + thrown = BaseException() + with self.assertRaises(GeneratorExit) as caught: + g.throw(thrown) + self.assertIs(caught.exception, raised) + self.assertIs(caught.exception.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw Exception"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = GeneratorExit() + thrown = Exception() + with self.assertRaises(GeneratorExit) as caught: + g.throw(thrown) + self.assertIs(caught.exception, raised) + self.assertIs(caught.exception.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: RuntimeError not raised + def test_close_and_throw_raise_stop_iteration(self): + + yielded_first = object() + yielded_second = object() + returned = object() + + def inner(): + try: + yield yielded_first + yield yielded_second + return returned + finally: + raise raised + + def outer(): + return (yield from inner()) + + with self.subTest("close"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = StopIteration() + # PEP 479: + with self.assert_generator_raised_stop_iteration() as caught: + g.close() + self.assertIs(caught.exception.__context__, raised) + self.assertIsInstance(caught.exception.__context__.__context__, GeneratorExit) + self.assertIsNone(caught.exception.__context__.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw GeneratorExit"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = StopIteration() + thrown = GeneratorExit() + # PEP 479: + with self.assert_generator_raised_stop_iteration() as caught: + g.throw(thrown) + self.assertIs(caught.exception.__context__, raised) + # This isn't the same GeneratorExit as thrown! It's the one created + # by calling inner.close(): + self.assertIsInstance(caught.exception.__context__.__context__, GeneratorExit) + self.assertIsNone(caught.exception.__context__.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw StopIteration"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = StopIteration() + thrown = StopIteration() + # PEP 479: + with self.assert_generator_raised_stop_iteration() as caught: + g.throw(thrown) + self.assertIs(caught.exception.__context__, raised) + self.assertIs(caught.exception.__context__.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw BaseException"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = StopIteration() + thrown = BaseException() + # PEP 479: + with self.assert_generator_raised_stop_iteration() as caught: + g.throw(thrown) + self.assertIs(caught.exception.__context__, raised) + self.assertIs(caught.exception.__context__.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw Exception"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = StopIteration() + thrown = Exception() + # PEP 479: + with self.assert_generator_raised_stop_iteration() as caught: + g.throw(thrown) + self.assertIs(caught.exception.__context__, raised) + self.assertIs(caught.exception.__context__.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__.__context__) + self.assert_stop_iteration(g) + + def test_close_and_throw_raise_base_exception(self): + + yielded_first = object() + yielded_second = object() + returned = object() + + def inner(): + try: + yield yielded_first + yield yielded_second + return returned + finally: + raise raised + + def outer(): + return (yield from inner()) + + with self.subTest("close"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = BaseException() + with self.assertRaises(BaseException) as caught: + g.close() + self.assertIs(caught.exception, raised) + self.assertIsInstance(caught.exception.__context__, GeneratorExit) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw GeneratorExit"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = BaseException() + thrown = GeneratorExit() + with self.assertRaises(BaseException) as caught: + g.throw(thrown) + self.assertIs(caught.exception, raised) + # This isn't the same GeneratorExit as thrown! It's the one created + # by calling inner.close(): + self.assertIsInstance(caught.exception.__context__, GeneratorExit) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw StopIteration"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = BaseException() + thrown = StopIteration() + with self.assertRaises(BaseException) as caught: + g.throw(thrown) + self.assertIs(caught.exception, raised) + self.assertIs(caught.exception.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw BaseException"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = BaseException() + thrown = BaseException() + with self.assertRaises(BaseException) as caught: + g.throw(thrown) + self.assertIs(caught.exception, raised) + self.assertIs(caught.exception.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw Exception"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = BaseException() + thrown = Exception() + with self.assertRaises(BaseException) as caught: + g.throw(thrown) + self.assertIs(caught.exception, raised) + self.assertIs(caught.exception.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + def test_close_and_throw_raise_exception(self): + + yielded_first = object() + yielded_second = object() + returned = object() + + def inner(): + try: + yield yielded_first + yield yielded_second + return returned + finally: + raise raised + + def outer(): + return (yield from inner()) + + with self.subTest("close"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = Exception() + with self.assertRaises(Exception) as caught: + g.close() + self.assertIs(caught.exception, raised) + self.assertIsInstance(caught.exception.__context__, GeneratorExit) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw GeneratorExit"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = Exception() + thrown = GeneratorExit() + with self.assertRaises(Exception) as caught: + g.throw(thrown) + self.assertIs(caught.exception, raised) + # This isn't the same GeneratorExit as thrown! It's the one created + # by calling inner.close(): + self.assertIsInstance(caught.exception.__context__, GeneratorExit) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw StopIteration"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = Exception() + thrown = StopIteration() + with self.assertRaises(Exception) as caught: + g.throw(thrown) + self.assertIs(caught.exception, raised) + self.assertIs(caught.exception.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw BaseException"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = Exception() + thrown = BaseException() + with self.assertRaises(Exception) as caught: + g.throw(thrown) + self.assertIs(caught.exception, raised) + self.assertIs(caught.exception.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw Exception"): + g = outer() + self.assertIs(next(g), yielded_first) + raised = Exception() + thrown = Exception() + with self.assertRaises(Exception) as caught: + g.throw(thrown) + self.assertIs(caught.exception, raised) + self.assertIs(caught.exception.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: None is not StopIteration() + def test_close_and_throw_yield(self): + + yielded_first = object() + yielded_second = object() + returned = object() + + def inner(): + try: + yield yielded_first + finally: + yield yielded_second + return returned + + def outer(): + return (yield from inner()) + + with self.subTest("close"): + g = outer() + self.assertIs(next(g), yielded_first) + # No chaining happens. This is consistent with PEP 342: + # https://peps.python.org/pep-0342/#new-generator-method-close + with self.assert_generator_ignored_generator_exit() as caught: + g.close() + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw GeneratorExit"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = GeneratorExit() + # No chaining happens. This is consistent with PEP 342: + # https://peps.python.org/pep-0342/#new-generator-method-close + with self.assert_generator_ignored_generator_exit() as caught: + g.throw(thrown) + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw StopIteration"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = StopIteration() + self.assertEqual(g.throw(thrown), yielded_second) + # PEP 479: + with self.assert_generator_raised_stop_iteration() as caught: + next(g) + self.assertIs(caught.exception.__context__, thrown) + self.assertIsNone(caught.exception.__context__.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw BaseException"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = BaseException() + self.assertEqual(g.throw(thrown), yielded_second) + with self.assertRaises(BaseException) as caught: + next(g) + self.assertIs(caught.exception, thrown) + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw Exception"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = Exception() + self.assertEqual(g.throw(thrown), yielded_second) + with self.assertRaises(Exception) as caught: + next(g) + self.assertIs(caught.exception, thrown) + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_close_and_throw_return(self): + + yielded_first = object() + yielded_second = object() + returned = object() + + def inner(): + try: + yield yielded_first + yield yielded_second + finally: + return returned + + def outer(): + return (yield from inner()) + + with self.subTest("close"): + g = outer() + self.assertIs(next(g), yielded_first) + # StopIteration is suppressed. This is consistent with PEP 342: + # https://peps.python.org/pep-0342/#new-generator-method-close + g.close() + self.assert_stop_iteration(g) + + with self.subTest("throw GeneratorExit"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = GeneratorExit() + # StopIteration is suppressed. This is consistent with PEP 342: + # https://peps.python.org/pep-0342/#new-generator-method-close + with self.assertRaises(GeneratorExit) as caught: + g.throw(thrown) + self.assertIs(caught.exception, thrown) + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw StopIteration"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = StopIteration() + with self.assertRaises(StopIteration) as caught: + g.throw(thrown) + self.assertIs(caught.exception.value, returned) + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw BaseException"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = BaseException() + with self.assertRaises(StopIteration) as caught: + g.throw(thrown) + self.assertIs(caught.exception.value, returned) + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + + with self.subTest("throw Exception"): + g = outer() + self.assertIs(next(g), yielded_first) + thrown = Exception() + with self.assertRaises(StopIteration) as caught: + g.throw(thrown) + self.assertIs(caught.exception.value, returned) + self.assertIsNone(caught.exception.__context__) + self.assert_stop_iteration(g) + if __name__ == '__main__': unittest.main() From 1aea1467da5269922feb6cdc099f2112b3ac90db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Sep 2025 19:34:03 +0900 Subject: [PATCH 276/819] Bump on-headers, serve and compression in /wasm/demo (#6168) Bumps [on-headers](https://github.com/jshttp/on-headers) to 1.1.0 and updates ancestor dependencies [on-headers](https://github.com/jshttp/on-headers), [serve](https://github.com/vercel/serve) and [compression](https://github.com/expressjs/compression). These dependencies need to be updated together. Updates `on-headers` from 1.0.2 to 1.1.0 - [Release notes](https://github.com/jshttp/on-headers/releases) - [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md) - [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0) Updates `serve` from 14.2.4 to 14.2.5 - [Release notes](https://github.com/vercel/serve/releases) - [Commits](https://github.com/vercel/serve/compare/14.2.4...v14.2.5) Updates `compression` from 1.7.4 to 1.8.1 - [Release notes](https://github.com/expressjs/compression/releases) - [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/compression/compare/1.7.4...v1.8.1) --- updated-dependencies: - dependency-name: on-headers dependency-version: 1.1.0 dependency-type: indirect - dependency-name: serve dependency-version: 14.2.5 dependency-type: direct:development - dependency-name: compression dependency-version: 1.8.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- wasm/demo/package-lock.json | 73 +++++++++++++++++++++++++++++-------- wasm/demo/package.json | 2 +- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/wasm/demo/package-lock.json b/wasm/demo/package-lock.json index ce1a1219c06..e3cf79298ac 100644 --- a/wasm/demo/package-lock.json +++ b/wasm/demo/package-lock.json @@ -21,7 +21,7 @@ "css-loader": "^7.1.2", "html-webpack-plugin": "^5.6.3", "mini-css-extract-plugin": "^2.9.2", - "serve": "^14.2.4", + "serve": "^14.2.5", "webpack": "^5.97.1", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.1" @@ -1542,24 +1542,65 @@ } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/compression/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3540,9 +3581,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", "engines": { @@ -4310,9 +4351,9 @@ } }, "node_modules/serve": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz", - "integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.5.tgz", + "integrity": "sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==", "dev": true, "license": "MIT", "dependencies": { @@ -4323,7 +4364,7 @@ "chalk": "5.0.1", "chalk-template": "0.4.0", "clipboardy": "3.0.0", - "compression": "1.7.4", + "compression": "1.8.1", "is-port-reachable": "4.0.0", "serve-handler": "6.1.6", "update-check": "1.5.4" diff --git a/wasm/demo/package.json b/wasm/demo/package.json index 22c82e1557d..2c08e5c416f 100644 --- a/wasm/demo/package.json +++ b/wasm/demo/package.json @@ -16,7 +16,7 @@ "css-loader": "^7.1.2", "html-webpack-plugin": "^5.6.3", "mini-css-extract-plugin": "^2.9.2", - "serve": "^14.2.4", + "serve": "^14.2.5", "webpack": "^5.97.1", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.1" From 3a6fda4daf21678f5f3c2f5d57e4be5fe0a46e40 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 5 Oct 2025 05:14:33 +0300 Subject: [PATCH 277/819] Update `opcode` from 3.13.7 (#6156) * Update `opcode` from 3.13.7 * Base `_opcode` * Add `test__opcode.py` from 3.13.7 * Impl `has_*` methods * Add more methods * Update `dis.py` from 3.13.7 * Update `support/bytecode_helper.py` from 3.13.7 * correct is_valid * Patch failing tests * Unpatch `support/__init__.py` * clippy * Make comments to doc * impl `_varname_from_oparg` for code * Unmark passing tests * Revert changes to `dis` * Mark failing tests --- Lib/_opcode_metadata.py | 343 +++++++++++++++++++++ Lib/opcode.py | 449 ++++------------------------ Lib/test/support/__init__.py | 2 +- Lib/test/support/bytecode_helper.py | 109 ++++--- Lib/test/test__opcode.py | 143 +++++++++ compiler/core/src/bytecode.rs | 16 +- stdlib/Cargo.toml | 2 +- stdlib/src/lib.rs | 2 + stdlib/src/opcode.rs | 282 +++++++++++++++++ vm/src/builtins/code.rs | 9 + 10 files changed, 912 insertions(+), 445 deletions(-) create mode 100644 Lib/_opcode_metadata.py create mode 100644 Lib/test/test__opcode.py create mode 100644 stdlib/src/opcode.rs diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py new file mode 100644 index 00000000000..b3d7b8103e8 --- /dev/null +++ b/Lib/_opcode_metadata.py @@ -0,0 +1,343 @@ +# This file is generated by Tools/cases_generator/py_metadata_generator.py +# from: +# Python/bytecodes.c +# Do not edit! +_specializations = { + "RESUME": [ + "RESUME_CHECK", + ], + "TO_BOOL": [ + "TO_BOOL_ALWAYS_TRUE", + "TO_BOOL_BOOL", + "TO_BOOL_INT", + "TO_BOOL_LIST", + "TO_BOOL_NONE", + "TO_BOOL_STR", + ], + "BINARY_OP": [ + "BINARY_OP_MULTIPLY_INT", + "BINARY_OP_ADD_INT", + "BINARY_OP_SUBTRACT_INT", + "BINARY_OP_MULTIPLY_FLOAT", + "BINARY_OP_ADD_FLOAT", + "BINARY_OP_SUBTRACT_FLOAT", + "BINARY_OP_ADD_UNICODE", + "BINARY_OP_INPLACE_ADD_UNICODE", + ], + "BINARY_SUBSCR": [ + "BINARY_SUBSCR_DICT", + "BINARY_SUBSCR_GETITEM", + "BINARY_SUBSCR_LIST_INT", + "BINARY_SUBSCR_STR_INT", + "BINARY_SUBSCR_TUPLE_INT", + ], + "STORE_SUBSCR": [ + "STORE_SUBSCR_DICT", + "STORE_SUBSCR_LIST_INT", + ], + "SEND": [ + "SEND_GEN", + ], + "UNPACK_SEQUENCE": [ + "UNPACK_SEQUENCE_TWO_TUPLE", + "UNPACK_SEQUENCE_TUPLE", + "UNPACK_SEQUENCE_LIST", + ], + "STORE_ATTR": [ + "STORE_ATTR_INSTANCE_VALUE", + "STORE_ATTR_SLOT", + "STORE_ATTR_WITH_HINT", + ], + "LOAD_GLOBAL": [ + "LOAD_GLOBAL_MODULE", + "LOAD_GLOBAL_BUILTIN", + ], + "LOAD_SUPER_ATTR": [ + "LOAD_SUPER_ATTR_ATTR", + "LOAD_SUPER_ATTR_METHOD", + ], + "LOAD_ATTR": [ + "LOAD_ATTR_INSTANCE_VALUE", + "LOAD_ATTR_MODULE", + "LOAD_ATTR_WITH_HINT", + "LOAD_ATTR_SLOT", + "LOAD_ATTR_CLASS", + "LOAD_ATTR_PROPERTY", + "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", + "LOAD_ATTR_METHOD_WITH_VALUES", + "LOAD_ATTR_METHOD_NO_DICT", + "LOAD_ATTR_METHOD_LAZY_DICT", + "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", + "LOAD_ATTR_NONDESCRIPTOR_NO_DICT", + ], + "COMPARE_OP": [ + "COMPARE_OP_FLOAT", + "COMPARE_OP_INT", + "COMPARE_OP_STR", + ], + "CONTAINS_OP": [ + "CONTAINS_OP_SET", + "CONTAINS_OP_DICT", + ], + "FOR_ITER": [ + "FOR_ITER_LIST", + "FOR_ITER_TUPLE", + "FOR_ITER_RANGE", + "FOR_ITER_GEN", + ], + "CALL": [ + "CALL_BOUND_METHOD_EXACT_ARGS", + "CALL_PY_EXACT_ARGS", + "CALL_TYPE_1", + "CALL_STR_1", + "CALL_TUPLE_1", + "CALL_BUILTIN_CLASS", + "CALL_BUILTIN_O", + "CALL_BUILTIN_FAST", + "CALL_BUILTIN_FAST_WITH_KEYWORDS", + "CALL_LEN", + "CALL_ISINSTANCE", + "CALL_LIST_APPEND", + "CALL_METHOD_DESCRIPTOR_O", + "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", + "CALL_METHOD_DESCRIPTOR_NOARGS", + "CALL_METHOD_DESCRIPTOR_FAST", + "CALL_ALLOC_AND_ENTER_INIT", + "CALL_PY_GENERAL", + "CALL_BOUND_METHOD_GENERAL", + "CALL_NON_PY_GENERAL", + ], +} + +_specialized_opmap = { + 'BINARY_OP_ADD_FLOAT': 150, + 'BINARY_OP_ADD_INT': 151, + 'BINARY_OP_ADD_UNICODE': 152, + 'BINARY_OP_INPLACE_ADD_UNICODE': 3, + 'BINARY_OP_MULTIPLY_FLOAT': 153, + 'BINARY_OP_MULTIPLY_INT': 154, + 'BINARY_OP_SUBTRACT_FLOAT': 155, + 'BINARY_OP_SUBTRACT_INT': 156, + 'BINARY_SUBSCR_DICT': 157, + 'BINARY_SUBSCR_GETITEM': 158, + 'BINARY_SUBSCR_LIST_INT': 159, + 'BINARY_SUBSCR_STR_INT': 160, + 'BINARY_SUBSCR_TUPLE_INT': 161, + 'CALL_ALLOC_AND_ENTER_INIT': 162, + 'CALL_BOUND_METHOD_EXACT_ARGS': 163, + 'CALL_BOUND_METHOD_GENERAL': 164, + 'CALL_BUILTIN_CLASS': 165, + 'CALL_BUILTIN_FAST': 166, + 'CALL_BUILTIN_FAST_WITH_KEYWORDS': 167, + 'CALL_BUILTIN_O': 168, + 'CALL_ISINSTANCE': 169, + 'CALL_LEN': 170, + 'CALL_LIST_APPEND': 171, + 'CALL_METHOD_DESCRIPTOR_FAST': 172, + 'CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS': 173, + 'CALL_METHOD_DESCRIPTOR_NOARGS': 174, + 'CALL_METHOD_DESCRIPTOR_O': 175, + 'CALL_NON_PY_GENERAL': 176, + 'CALL_PY_EXACT_ARGS': 177, + 'CALL_PY_GENERAL': 178, + 'CALL_STR_1': 179, + 'CALL_TUPLE_1': 180, + 'CALL_TYPE_1': 181, + 'COMPARE_OP_FLOAT': 182, + 'COMPARE_OP_INT': 183, + 'COMPARE_OP_STR': 184, + 'CONTAINS_OP_DICT': 185, + 'CONTAINS_OP_SET': 186, + 'FOR_ITER_GEN': 187, + 'FOR_ITER_LIST': 188, + 'FOR_ITER_RANGE': 189, + 'FOR_ITER_TUPLE': 190, + 'LOAD_ATTR_CLASS': 191, + 'LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN': 192, + 'LOAD_ATTR_INSTANCE_VALUE': 193, + 'LOAD_ATTR_METHOD_LAZY_DICT': 194, + 'LOAD_ATTR_METHOD_NO_DICT': 195, + 'LOAD_ATTR_METHOD_WITH_VALUES': 196, + 'LOAD_ATTR_MODULE': 197, + 'LOAD_ATTR_NONDESCRIPTOR_NO_DICT': 198, + 'LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES': 199, + 'LOAD_ATTR_PROPERTY': 200, + 'LOAD_ATTR_SLOT': 201, + 'LOAD_ATTR_WITH_HINT': 202, + 'LOAD_GLOBAL_BUILTIN': 203, + 'LOAD_GLOBAL_MODULE': 204, + 'LOAD_SUPER_ATTR_ATTR': 205, + 'LOAD_SUPER_ATTR_METHOD': 206, + 'RESUME_CHECK': 207, + 'SEND_GEN': 208, + 'STORE_ATTR_INSTANCE_VALUE': 209, + 'STORE_ATTR_SLOT': 210, + 'STORE_ATTR_WITH_HINT': 211, + 'STORE_SUBSCR_DICT': 212, + 'STORE_SUBSCR_LIST_INT': 213, + 'TO_BOOL_ALWAYS_TRUE': 214, + 'TO_BOOL_BOOL': 215, + 'TO_BOOL_INT': 216, + 'TO_BOOL_LIST': 217, + 'TO_BOOL_NONE': 218, + 'TO_BOOL_STR': 219, + 'UNPACK_SEQUENCE_LIST': 220, + 'UNPACK_SEQUENCE_TUPLE': 221, + 'UNPACK_SEQUENCE_TWO_TUPLE': 222, +} + +opmap = { + 'CACHE': 0, + 'RESERVED': 17, + 'RESUME': 149, + 'INSTRUMENTED_LINE': 254, + 'BEFORE_ASYNC_WITH': 1, + 'BEFORE_WITH': 2, + 'BINARY_SLICE': 4, + 'BINARY_SUBSCR': 5, + 'CHECK_EG_MATCH': 6, + 'CHECK_EXC_MATCH': 7, + 'CLEANUP_THROW': 8, + 'DELETE_SUBSCR': 9, + 'END_ASYNC_FOR': 10, + 'END_FOR': 11, + 'END_SEND': 12, + 'EXIT_INIT_CHECK': 13, + 'FORMAT_SIMPLE': 14, + 'FORMAT_WITH_SPEC': 15, + 'GET_AITER': 16, + 'GET_ANEXT': 18, + 'GET_ITER': 19, + 'GET_LEN': 20, + 'GET_YIELD_FROM_ITER': 21, + 'INTERPRETER_EXIT': 22, + 'LOAD_ASSERTION_ERROR': 23, + 'LOAD_BUILD_CLASS': 24, + 'LOAD_LOCALS': 25, + 'MAKE_FUNCTION': 26, + 'MATCH_KEYS': 27, + 'MATCH_MAPPING': 28, + 'MATCH_SEQUENCE': 29, + 'NOP': 30, + 'POP_EXCEPT': 31, + 'POP_TOP': 32, + 'PUSH_EXC_INFO': 33, + 'PUSH_NULL': 34, + 'RETURN_GENERATOR': 35, + 'RETURN_VALUE': 36, + 'SETUP_ANNOTATIONS': 37, + 'STORE_SLICE': 38, + 'STORE_SUBSCR': 39, + 'TO_BOOL': 40, + 'UNARY_INVERT': 41, + 'UNARY_NEGATIVE': 42, + 'UNARY_NOT': 43, + 'WITH_EXCEPT_START': 44, + 'BINARY_OP': 45, + 'BUILD_CONST_KEY_MAP': 46, + 'BUILD_LIST': 47, + 'BUILD_MAP': 48, + 'BUILD_SET': 49, + 'BUILD_SLICE': 50, + 'BUILD_STRING': 51, + 'BUILD_TUPLE': 52, + 'CALL': 53, + 'CALL_FUNCTION_EX': 54, + 'CALL_INTRINSIC_1': 55, + 'CALL_INTRINSIC_2': 56, + 'CALL_KW': 57, + 'COMPARE_OP': 58, + 'CONTAINS_OP': 59, + 'CONVERT_VALUE': 60, + 'COPY': 61, + 'COPY_FREE_VARS': 62, + 'DELETE_ATTR': 63, + 'DELETE_DEREF': 64, + 'DELETE_FAST': 65, + 'DELETE_GLOBAL': 66, + 'DELETE_NAME': 67, + 'DICT_MERGE': 68, + 'DICT_UPDATE': 69, + 'ENTER_EXECUTOR': 70, + 'EXTENDED_ARG': 71, + 'FOR_ITER': 72, + 'GET_AWAITABLE': 73, + 'IMPORT_FROM': 74, + 'IMPORT_NAME': 75, + 'IS_OP': 76, + 'JUMP_BACKWARD': 77, + 'JUMP_BACKWARD_NO_INTERRUPT': 78, + 'JUMP_FORWARD': 79, + 'LIST_APPEND': 80, + 'LIST_EXTEND': 81, + 'LOAD_ATTR': 82, + 'LOAD_CONST': 83, + 'LOAD_DEREF': 84, + 'LOAD_FAST': 85, + 'LOAD_FAST_AND_CLEAR': 86, + 'LOAD_FAST_CHECK': 87, + 'LOAD_FAST_LOAD_FAST': 88, + 'LOAD_FROM_DICT_OR_DEREF': 89, + 'LOAD_FROM_DICT_OR_GLOBALS': 90, + 'LOAD_GLOBAL': 91, + 'LOAD_NAME': 92, + 'LOAD_SUPER_ATTR': 93, + 'MAKE_CELL': 94, + 'MAP_ADD': 95, + 'MATCH_CLASS': 96, + 'POP_JUMP_IF_FALSE': 97, + 'POP_JUMP_IF_NONE': 98, + 'POP_JUMP_IF_NOT_NONE': 99, + 'POP_JUMP_IF_TRUE': 100, + 'RAISE_VARARGS': 101, + 'RERAISE': 102, + 'RETURN_CONST': 103, + 'SEND': 104, + 'SET_ADD': 105, + 'SET_FUNCTION_ATTRIBUTE': 106, + 'SET_UPDATE': 107, + 'STORE_ATTR': 108, + 'STORE_DEREF': 109, + 'STORE_FAST': 110, + 'STORE_FAST_LOAD_FAST': 111, + 'STORE_FAST_STORE_FAST': 112, + 'STORE_GLOBAL': 113, + 'STORE_NAME': 114, + 'SWAP': 115, + 'UNPACK_EX': 116, + 'UNPACK_SEQUENCE': 117, + 'YIELD_VALUE': 118, + 'INSTRUMENTED_RESUME': 236, + 'INSTRUMENTED_END_FOR': 237, + 'INSTRUMENTED_END_SEND': 238, + 'INSTRUMENTED_RETURN_VALUE': 239, + 'INSTRUMENTED_RETURN_CONST': 240, + 'INSTRUMENTED_YIELD_VALUE': 241, + 'INSTRUMENTED_LOAD_SUPER_ATTR': 242, + 'INSTRUMENTED_FOR_ITER': 243, + 'INSTRUMENTED_CALL': 244, + 'INSTRUMENTED_CALL_KW': 245, + 'INSTRUMENTED_CALL_FUNCTION_EX': 246, + 'INSTRUMENTED_INSTRUCTION': 247, + 'INSTRUMENTED_JUMP_FORWARD': 248, + 'INSTRUMENTED_JUMP_BACKWARD': 249, + 'INSTRUMENTED_POP_JUMP_IF_TRUE': 250, + 'INSTRUMENTED_POP_JUMP_IF_FALSE': 251, + 'INSTRUMENTED_POP_JUMP_IF_NONE': 252, + 'INSTRUMENTED_POP_JUMP_IF_NOT_NONE': 253, + 'JUMP': 256, + 'JUMP_NO_INTERRUPT': 257, + 'LOAD_CLOSURE': 258, + 'LOAD_METHOD': 259, + 'LOAD_SUPER_METHOD': 260, + 'LOAD_ZERO_SUPER_ATTR': 261, + 'LOAD_ZERO_SUPER_METHOD': 262, + 'POP_BLOCK': 263, + 'SETUP_CLEANUP': 264, + 'SETUP_FINALLY': 265, + 'SETUP_WITH': 266, + 'STORE_FAST_MAYBE_NULL': 267, +} + +HAVE_ARGUMENT = 44 +MIN_INSTRUMENTED_OPCODE = 236 diff --git a/Lib/opcode.py b/Lib/opcode.py index ab6b765b4b7..5735686fa7f 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -4,404 +4,47 @@ operate on bytecodes (e.g. peephole optimizers). """ -__all__ = ["cmp_op", "hasarg", "hasconst", "hasname", "hasjrel", "hasjabs", - "haslocal", "hascompare", "hasfree", "hasexc", "opname", "opmap", - "HAVE_ARGUMENT", "EXTENDED_ARG"] -# It's a chicken-and-egg I'm afraid: -# We're imported before _opcode's made. -# With exception unheeded -# (stack_effect is not needed) -# Both our chickens and eggs are allayed. -# --Larry Hastings, 2013/11/23 +__all__ = ["cmp_op", "stack_effect", "hascompare", "opname", "opmap", + "HAVE_ARGUMENT", "EXTENDED_ARG", "hasarg", "hasconst", "hasname", + "hasjump", "hasjrel", "hasjabs", "hasfree", "haslocal", "hasexc"] -try: - from _opcode import stack_effect - __all__.append('stack_effect') -except ImportError: - pass +import _opcode +from _opcode import stack_effect -cmp_op = ('<', '<=', '==', '!=', '>', '>=') - -hasarg = [] -hasconst = [] -hasname = [] -hasjrel = [] -hasjabs = [] -haslocal = [] -hascompare = [] -hasfree = [] -hasexc = [] - -def is_pseudo(op): - return op >= MIN_PSEUDO_OPCODE and op <= MAX_PSEUDO_OPCODE - -oplists = [hasarg, hasconst, hasname, hasjrel, hasjabs, - haslocal, hascompare, hasfree, hasexc] - -opmap = {} - -## pseudo opcodes (used in the compiler) mapped to the values -## they can become in the actual code. -_pseudo_ops = {} - -def def_op(name, op): - opmap[name] = op - -def name_op(name, op): - def_op(name, op) - hasname.append(op) - -def jrel_op(name, op): - def_op(name, op) - hasjrel.append(op) - -def jabs_op(name, op): - def_op(name, op) - hasjabs.append(op) - -def pseudo_op(name, op, real_ops): - def_op(name, op) - _pseudo_ops[name] = real_ops - # add the pseudo opcode to the lists its targets are in - for oplist in oplists: - res = [opmap[rop] in oplist for rop in real_ops] - if any(res): - assert all(res) - oplist.append(op) - - -# Instruction opcodes for compiled code -# Blank lines correspond to available opcodes - -def_op('CACHE', 0) -def_op('POP_TOP', 1) -def_op('PUSH_NULL', 2) - -def_op('NOP', 9) -def_op('UNARY_POSITIVE', 10) -def_op('UNARY_NEGATIVE', 11) -def_op('UNARY_NOT', 12) - -def_op('UNARY_INVERT', 15) - -def_op('BINARY_SUBSCR', 25) -def_op('BINARY_SLICE', 26) -def_op('STORE_SLICE', 27) - -def_op('GET_LEN', 30) -def_op('MATCH_MAPPING', 31) -def_op('MATCH_SEQUENCE', 32) -def_op('MATCH_KEYS', 33) - -def_op('PUSH_EXC_INFO', 35) -def_op('CHECK_EXC_MATCH', 36) -def_op('CHECK_EG_MATCH', 37) - -def_op('WITH_EXCEPT_START', 49) -def_op('GET_AITER', 50) -def_op('GET_ANEXT', 51) -def_op('BEFORE_ASYNC_WITH', 52) -def_op('BEFORE_WITH', 53) -def_op('END_ASYNC_FOR', 54) -def_op('CLEANUP_THROW', 55) - -def_op('STORE_SUBSCR', 60) -def_op('DELETE_SUBSCR', 61) - -# TODO: RUSTPYTHON -# Delete below def_op after updating coroutines.py -def_op('YIELD_FROM', 72) - -def_op('GET_ITER', 68) -def_op('GET_YIELD_FROM_ITER', 69) -def_op('PRINT_EXPR', 70) -def_op('LOAD_BUILD_CLASS', 71) - -def_op('LOAD_ASSERTION_ERROR', 74) -def_op('RETURN_GENERATOR', 75) - -def_op('LIST_TO_TUPLE', 82) -def_op('RETURN_VALUE', 83) -def_op('IMPORT_STAR', 84) -def_op('SETUP_ANNOTATIONS', 85) - -def_op('ASYNC_GEN_WRAP', 87) -def_op('PREP_RERAISE_STAR', 88) -def_op('POP_EXCEPT', 89) - -HAVE_ARGUMENT = 90 # real opcodes from here have an argument: - -name_op('STORE_NAME', 90) # Index in name list -name_op('DELETE_NAME', 91) # "" -def_op('UNPACK_SEQUENCE', 92) # Number of tuple items -jrel_op('FOR_ITER', 93) -def_op('UNPACK_EX', 94) -name_op('STORE_ATTR', 95) # Index in name list -name_op('DELETE_ATTR', 96) # "" -name_op('STORE_GLOBAL', 97) # "" -name_op('DELETE_GLOBAL', 98) # "" -def_op('SWAP', 99) -def_op('LOAD_CONST', 100) # Index in const list -hasconst.append(100) -name_op('LOAD_NAME', 101) # Index in name list -def_op('BUILD_TUPLE', 102) # Number of tuple items -def_op('BUILD_LIST', 103) # Number of list items -def_op('BUILD_SET', 104) # Number of set items -def_op('BUILD_MAP', 105) # Number of dict entries -name_op('LOAD_ATTR', 106) # Index in name list -def_op('COMPARE_OP', 107) # Comparison operator -hascompare.append(107) -name_op('IMPORT_NAME', 108) # Index in name list -name_op('IMPORT_FROM', 109) # Index in name list -jrel_op('JUMP_FORWARD', 110) # Number of words to skip -jrel_op('JUMP_IF_FALSE_OR_POP', 111) # Number of words to skip -jrel_op('JUMP_IF_TRUE_OR_POP', 112) # "" -jrel_op('POP_JUMP_IF_FALSE', 114) -jrel_op('POP_JUMP_IF_TRUE', 115) -name_op('LOAD_GLOBAL', 116) # Index in name list -def_op('IS_OP', 117) -def_op('CONTAINS_OP', 118) -def_op('RERAISE', 119) -def_op('COPY', 120) -def_op('BINARY_OP', 122) -jrel_op('SEND', 123) # Number of bytes to skip -def_op('LOAD_FAST', 124) # Local variable number, no null check -haslocal.append(124) -def_op('STORE_FAST', 125) # Local variable number -haslocal.append(125) -def_op('DELETE_FAST', 126) # Local variable number -haslocal.append(126) -def_op('LOAD_FAST_CHECK', 127) # Local variable number -haslocal.append(127) -jrel_op('POP_JUMP_IF_NOT_NONE', 128) -jrel_op('POP_JUMP_IF_NONE', 129) -def_op('RAISE_VARARGS', 130) # Number of raise arguments (1, 2, or 3) -def_op('GET_AWAITABLE', 131) -def_op('MAKE_FUNCTION', 132) # Flags -def_op('BUILD_SLICE', 133) # Number of items -jrel_op('JUMP_BACKWARD_NO_INTERRUPT', 134) # Number of words to skip (backwards) -def_op('MAKE_CELL', 135) -hasfree.append(135) -def_op('LOAD_CLOSURE', 136) -hasfree.append(136) -def_op('LOAD_DEREF', 137) -hasfree.append(137) -def_op('STORE_DEREF', 138) -hasfree.append(138) -def_op('DELETE_DEREF', 139) -hasfree.append(139) -jrel_op('JUMP_BACKWARD', 140) # Number of words to skip (backwards) - -def_op('CALL_FUNCTION_EX', 142) # Flags - -def_op('EXTENDED_ARG', 144) -EXTENDED_ARG = 144 -def_op('LIST_APPEND', 145) -def_op('SET_ADD', 146) -def_op('MAP_ADD', 147) -def_op('LOAD_CLASSDEREF', 148) -hasfree.append(148) -def_op('COPY_FREE_VARS', 149) -def_op('YIELD_VALUE', 150) -def_op('RESUME', 151) # This must be kept in sync with deepfreeze.py -def_op('MATCH_CLASS', 152) - -def_op('FORMAT_VALUE', 155) -def_op('BUILD_CONST_KEY_MAP', 156) -def_op('BUILD_STRING', 157) - -def_op('LIST_EXTEND', 162) -def_op('SET_UPDATE', 163) -def_op('DICT_MERGE', 164) -def_op('DICT_UPDATE', 165) - -def_op('CALL', 171) -def_op('KW_NAMES', 172) -hasconst.append(172) +from _opcode_metadata import (_specializations, _specialized_opmap, opmap, + HAVE_ARGUMENT, MIN_INSTRUMENTED_OPCODE) +EXTENDED_ARG = opmap['EXTENDED_ARG'] - -hasarg.extend([op for op in opmap.values() if op >= HAVE_ARGUMENT]) - -MIN_PSEUDO_OPCODE = 256 - -pseudo_op('SETUP_FINALLY', 256, ['NOP']) -hasexc.append(256) -pseudo_op('SETUP_CLEANUP', 257, ['NOP']) -hasexc.append(257) -pseudo_op('SETUP_WITH', 258, ['NOP']) -hasexc.append(258) -pseudo_op('POP_BLOCK', 259, ['NOP']) - -pseudo_op('JUMP', 260, ['JUMP_FORWARD', 'JUMP_BACKWARD']) -pseudo_op('JUMP_NO_INTERRUPT', 261, ['JUMP_FORWARD', 'JUMP_BACKWARD_NO_INTERRUPT']) - -pseudo_op('LOAD_METHOD', 262, ['LOAD_ATTR']) - -MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1 - -del def_op, name_op, jrel_op, jabs_op, pseudo_op - -opname = ['<%r>' % (op,) for op in range(MAX_PSEUDO_OPCODE + 1)] +opname = ['<%r>' % (op,) for op in range(max(opmap.values()) + 1)] for op, i in opmap.items(): opname[i] = op +cmp_op = ('<', '<=', '==', '!=', '>', '>=') -_nb_ops = [ - ("NB_ADD", "+"), - ("NB_AND", "&"), - ("NB_FLOOR_DIVIDE", "//"), - ("NB_LSHIFT", "<<"), - ("NB_MATRIX_MULTIPLY", "@"), - ("NB_MULTIPLY", "*"), - ("NB_REMAINDER", "%"), - ("NB_OR", "|"), - ("NB_POWER", "**"), - ("NB_RSHIFT", ">>"), - ("NB_SUBTRACT", "-"), - ("NB_TRUE_DIVIDE", "/"), - ("NB_XOR", "^"), - ("NB_INPLACE_ADD", "+="), - ("NB_INPLACE_AND", "&="), - ("NB_INPLACE_FLOOR_DIVIDE", "//="), - ("NB_INPLACE_LSHIFT", "<<="), - ("NB_INPLACE_MATRIX_MULTIPLY", "@="), - ("NB_INPLACE_MULTIPLY", "*="), - ("NB_INPLACE_REMAINDER", "%="), - ("NB_INPLACE_OR", "|="), - ("NB_INPLACE_POWER", "**="), - ("NB_INPLACE_RSHIFT", ">>="), - ("NB_INPLACE_SUBTRACT", "-="), - ("NB_INPLACE_TRUE_DIVIDE", "/="), - ("NB_INPLACE_XOR", "^="), -] +# These lists are documented as part of the dis module's API +hasarg = [op for op in opmap.values() if _opcode.has_arg(op)] +hasconst = [op for op in opmap.values() if _opcode.has_const(op)] +hasname = [op for op in opmap.values() if _opcode.has_name(op)] +hasjump = [op for op in opmap.values() if _opcode.has_jump(op)] +hasjrel = hasjump # for backward compatibility +hasjabs = [] +hasfree = [op for op in opmap.values() if _opcode.has_free(op)] +haslocal = [op for op in opmap.values() if _opcode.has_local(op)] +hasexc = [op for op in opmap.values() if _opcode.has_exc(op)] -_specializations = { - "BINARY_OP": [ - "BINARY_OP_ADAPTIVE", - "BINARY_OP_ADD_FLOAT", - "BINARY_OP_ADD_INT", - "BINARY_OP_ADD_UNICODE", - "BINARY_OP_INPLACE_ADD_UNICODE", - "BINARY_OP_MULTIPLY_FLOAT", - "BINARY_OP_MULTIPLY_INT", - "BINARY_OP_SUBTRACT_FLOAT", - "BINARY_OP_SUBTRACT_INT", - ], - "BINARY_SUBSCR": [ - "BINARY_SUBSCR_ADAPTIVE", - "BINARY_SUBSCR_DICT", - "BINARY_SUBSCR_GETITEM", - "BINARY_SUBSCR_LIST_INT", - "BINARY_SUBSCR_TUPLE_INT", - ], - "CALL": [ - "CALL_ADAPTIVE", - "CALL_PY_EXACT_ARGS", - "CALL_PY_WITH_DEFAULTS", - "CALL_BOUND_METHOD_EXACT_ARGS", - "CALL_BUILTIN_CLASS", - "CALL_BUILTIN_FAST_WITH_KEYWORDS", - "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", - "CALL_NO_KW_BUILTIN_FAST", - "CALL_NO_KW_BUILTIN_O", - "CALL_NO_KW_ISINSTANCE", - "CALL_NO_KW_LEN", - "CALL_NO_KW_LIST_APPEND", - "CALL_NO_KW_METHOD_DESCRIPTOR_FAST", - "CALL_NO_KW_METHOD_DESCRIPTOR_NOARGS", - "CALL_NO_KW_METHOD_DESCRIPTOR_O", - "CALL_NO_KW_STR_1", - "CALL_NO_KW_TUPLE_1", - "CALL_NO_KW_TYPE_1", - ], - "COMPARE_OP": [ - "COMPARE_OP_ADAPTIVE", - "COMPARE_OP_FLOAT_JUMP", - "COMPARE_OP_INT_JUMP", - "COMPARE_OP_STR_JUMP", - ], - "EXTENDED_ARG": [ - "EXTENDED_ARG_QUICK", - ], - "FOR_ITER": [ - "FOR_ITER_ADAPTIVE", - "FOR_ITER_LIST", - "FOR_ITER_RANGE", - ], - "JUMP_BACKWARD": [ - "JUMP_BACKWARD_QUICK", - ], - "LOAD_ATTR": [ - "LOAD_ATTR_ADAPTIVE", - # These potentially push [NULL, bound method] onto the stack. - "LOAD_ATTR_CLASS", - "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", - "LOAD_ATTR_INSTANCE_VALUE", - "LOAD_ATTR_MODULE", - "LOAD_ATTR_PROPERTY", - "LOAD_ATTR_SLOT", - "LOAD_ATTR_WITH_HINT", - # These will always push [unbound method, self] onto the stack. - "LOAD_ATTR_METHOD_LAZY_DICT", - "LOAD_ATTR_METHOD_NO_DICT", - "LOAD_ATTR_METHOD_WITH_DICT", - "LOAD_ATTR_METHOD_WITH_VALUES", - ], - "LOAD_CONST": [ - "LOAD_CONST__LOAD_FAST", - ], - "LOAD_FAST": [ - "LOAD_FAST__LOAD_CONST", - "LOAD_FAST__LOAD_FAST", - ], - "LOAD_GLOBAL": [ - "LOAD_GLOBAL_ADAPTIVE", - "LOAD_GLOBAL_BUILTIN", - "LOAD_GLOBAL_MODULE", - ], - "RESUME": [ - "RESUME_QUICK", - ], - "STORE_ATTR": [ - "STORE_ATTR_ADAPTIVE", - "STORE_ATTR_INSTANCE_VALUE", - "STORE_ATTR_SLOT", - "STORE_ATTR_WITH_HINT", - ], - "STORE_FAST": [ - "STORE_FAST__LOAD_FAST", - "STORE_FAST__STORE_FAST", - ], - "STORE_SUBSCR": [ - "STORE_SUBSCR_ADAPTIVE", - "STORE_SUBSCR_DICT", - "STORE_SUBSCR_LIST_INT", - ], - "UNPACK_SEQUENCE": [ - "UNPACK_SEQUENCE_ADAPTIVE", - "UNPACK_SEQUENCE_LIST", - "UNPACK_SEQUENCE_TUPLE", - "UNPACK_SEQUENCE_TWO_TUPLE", - ], -} -_specialized_instructions = [ - opcode for family in _specializations.values() for opcode in family -] -_specialization_stats = [ - "success", - "failure", - "hit", - "deferred", - "miss", - "deopt", -] + +_intrinsic_1_descs = _opcode.get_intrinsic1_descs() +_intrinsic_2_descs = _opcode.get_intrinsic2_descs() +_nb_ops = _opcode.get_nb_ops() + +hascompare = [opmap["COMPARE_OP"]] _cache_format = { "LOAD_GLOBAL": { "counter": 1, "index": 1, - "module_keys_version": 2, + "module_keys_version": 1, "builtin_keys_version": 1, }, "BINARY_OP": { @@ -412,16 +55,19 @@ def pseudo_op(name, op, real_ops): }, "COMPARE_OP": { "counter": 1, - "mask": 1, + }, + "CONTAINS_OP": { + "counter": 1, }, "BINARY_SUBSCR": { "counter": 1, - "type_version": 2, - "func_version": 1, }, "FOR_ITER": { "counter": 1, }, + "LOAD_SUPER_ATTR": { + "counter": 1, + }, "LOAD_ATTR": { "counter": 1, "version": 2, @@ -436,13 +82,34 @@ def pseudo_op(name, op, real_ops): "CALL": { "counter": 1, "func_version": 2, - "min_args": 1, }, "STORE_SUBSCR": { "counter": 1, }, + "SEND": { + "counter": 1, + }, + "JUMP_BACKWARD": { + "counter": 1, + }, + "TO_BOOL": { + "counter": 1, + "version": 2, + }, + "POP_JUMP_IF_TRUE": { + "counter": 1, + }, + "POP_JUMP_IF_FALSE": { + "counter": 1, + }, + "POP_JUMP_IF_NONE": { + "counter": 1, + }, + "POP_JUMP_IF_NOT_NONE": { + "counter": 1, + }, } -_inline_cache_entries = [ - sum(_cache_format.get(opname[opcode], {}).values()) for opcode in range(256) -] +_inline_cache_entries = { + name : sum(value.values()) for (name, value) in _cache_format.items() +} diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 652a8cd92b3..88369e25c14 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -7,7 +7,7 @@ import dataclasses import functools import logging -# import _opcode # TODO: RUSTPYTHON +import _opcode import os import re import stat diff --git a/Lib/test/support/bytecode_helper.py b/Lib/test/support/bytecode_helper.py index 388d1266773..85bcd1f0f1c 100644 --- a/Lib/test/support/bytecode_helper.py +++ b/Lib/test/support/bytecode_helper.py @@ -3,10 +3,26 @@ import unittest import dis import io -from _testinternalcapi import compiler_codegen, optimize_cfg, assemble_code_object +import opcode +try: + import _testinternalcapi +except ImportError: + _testinternalcapi = None _UNSPECIFIED = object() +def instructions_with_positions(instrs, co_positions): + # Return (instr, positions) pairs from the instrs list and co_positions + # iterator. The latter contains items for cache lines and the former + # doesn't, so those need to be skipped. + + co_positions = co_positions or iter(()) + for instr in instrs: + yield instr, next(co_positions, ()) + for _, size, _ in (instr.cache_info or ()): + for i in range(size): + next(co_positions, ()) + class BytecodeTestCase(unittest.TestCase): """Custom assertion methods for inspecting bytecode.""" @@ -53,16 +69,14 @@ class CompilationStepTestCase(unittest.TestCase): class Label: pass - def assertInstructionsMatch(self, actual_, expected_): - # get two lists where each entry is a label or - # an instruction tuple. Normalize the labels to the - # instruction count of the target, and compare the lists. - - self.assertIsInstance(actual_, list) - self.assertIsInstance(expected_, list) + def assertInstructionsMatch(self, actual_seq, expected): + # get an InstructionSequence and an expected list, where each + # entry is a label or an instruction tuple. Construct an expcted + # instruction sequence and compare with the one given. - actual = self.normalize_insts(actual_) - expected = self.normalize_insts(expected_) + self.assertIsInstance(expected, list) + actual = actual_seq.get_instructions() + expected = self.seq_from_insts(expected).get_instructions() self.assertEqual(len(actual), len(expected)) # compare instructions @@ -72,10 +86,8 @@ def assertInstructionsMatch(self, actual_, expected_): continue self.assertIsInstance(exp, tuple) self.assertIsInstance(act, tuple) - # crop comparison to the provided expected values - if len(act) > len(exp): - act = act[:len(exp)] - self.assertEqual(exp, act) + idx = max([p[0] for p in enumerate(exp) if p[1] != -1]) + self.assertEqual(exp[:idx], act[:idx]) def resolveAndRemoveLabels(self, insts): idx = 0 @@ -90,54 +102,57 @@ def resolveAndRemoveLabels(self, insts): return res - def normalize_insts(self, insts): - """ Map labels to instruction index. - Map opcodes to opnames. - """ - insts = self.resolveAndRemoveLabels(insts) - res = [] - for item in insts: - assert isinstance(item, tuple) - opcode, oparg, *loc = item - opcode = dis.opmap.get(opcode, opcode) - if isinstance(oparg, self.Label): - arg = oparg.value - else: - arg = oparg if opcode in self.HAS_ARG else None - opcode = dis.opname[opcode] - res.append((opcode, arg, *loc)) - return res + def seq_from_insts(self, insts): + labels = {item for item in insts if isinstance(item, self.Label)} + for i, lbl in enumerate(labels): + lbl.value = i - def complete_insts_info(self, insts): - # fill in omitted fields in location, and oparg 0 for ops with no arg. - res = [] + seq = _testinternalcapi.new_instruction_sequence() for item in insts: - assert isinstance(item, tuple) - inst = list(item) - opcode = dis.opmap[inst[0]] - oparg = inst[1] - loc = inst[2:] + [-1] * (6 - len(inst)) - res.append((opcode, oparg, *loc)) - return res + if isinstance(item, self.Label): + seq.use_label(item.value) + else: + op = item[0] + if isinstance(op, str): + op = opcode.opmap[op] + arg, *loc = item[1:] + if isinstance(arg, self.Label): + arg = arg.value + loc = loc + [-1] * (4 - len(loc)) + seq.addop(op, arg or 0, *loc) + return seq + + def check_instructions(self, insts): + for inst in insts: + if isinstance(inst, self.Label): + continue + op, arg, *loc = inst + if isinstance(op, str): + op = opcode.opmap[op] + self.assertEqual(op in opcode.hasarg, + arg is not None, + f"{opcode.opname[op]=} {arg=}") + self.assertTrue(all(isinstance(l, int) for l in loc)) +@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") class CodegenTestCase(CompilationStepTestCase): def generate_code(self, ast): - insts, _ = compiler_codegen(ast, "my_file.py", 0) + insts, _ = _testinternalcapi.compiler_codegen(ast, "my_file.py", 0) return insts +@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") class CfgOptimizationTestCase(CompilationStepTestCase): - def get_optimized(self, insts, consts, nlocals=0): - insts = self.normalize_insts(insts) - insts = self.complete_insts_info(insts) - insts = optimize_cfg(insts, consts, nlocals) + def get_optimized(self, seq, consts, nlocals=0): + insts = _testinternalcapi.optimize_cfg(seq, consts, nlocals) return insts, consts +@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") class AssemblerTestCase(CompilationStepTestCase): def get_code_object(self, filename, insts, metadata): - co = assemble_code_object(filename, insts, metadata) + co = _testinternalcapi.assemble_code_object(filename, insts, metadata) return co diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py new file mode 100644 index 00000000000..b1e38b43dc8 --- /dev/null +++ b/Lib/test/test__opcode.py @@ -0,0 +1,143 @@ +import dis +from test.support.import_helper import import_module +import unittest +import opcode + +_opcode = import_module("_opcode") +from _opcode import stack_effect + + +class OpListTests(unittest.TestCase): + def check_bool_function_result(self, func, ops, expected): + for op in ops: + if isinstance(op, str): + op = dis.opmap[op] + with self.subTest(opcode=op, func=func): + self.assertIsInstance(func(op), bool) + self.assertEqual(func(op), expected) + + def test_invalid_opcodes(self): + invalid = [-100, -1, 255, 512, 513, 1000] + self.check_bool_function_result(_opcode.is_valid, invalid, False) + self.check_bool_function_result(_opcode.has_arg, invalid, False) + self.check_bool_function_result(_opcode.has_const, invalid, False) + self.check_bool_function_result(_opcode.has_name, invalid, False) + self.check_bool_function_result(_opcode.has_jump, invalid, False) + self.check_bool_function_result(_opcode.has_free, invalid, False) + self.check_bool_function_result(_opcode.has_local, invalid, False) + self.check_bool_function_result(_opcode.has_exc, invalid, False) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'dis' has no attribute 'opmap' + def test_is_valid(self): + names = [ + 'CACHE', + 'POP_TOP', + 'IMPORT_NAME', + 'JUMP', + 'INSTRUMENTED_RETURN_VALUE', + ] + opcodes = [dis.opmap[opname] for opname in names] + self.check_bool_function_result(_opcode.is_valid, opcodes, True) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'dis' has no attribute 'hasarg' + def test_oplists(self): + def check_function(self, func, expected): + for op in [-10, 520]: + with self.subTest(opcode=op, func=func): + res = func(op) + self.assertIsInstance(res, bool) + self.assertEqual(res, op in expected) + + check_function(self, _opcode.has_arg, dis.hasarg) + check_function(self, _opcode.has_const, dis.hasconst) + check_function(self, _opcode.has_name, dis.hasname) + check_function(self, _opcode.has_jump, dis.hasjump) + check_function(self, _opcode.has_free, dis.hasfree) + check_function(self, _opcode.has_local, dis.haslocal) + check_function(self, _opcode.has_exc, dis.hasexc) + + +class StackEffectTests(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_stack_effect(self): + self.assertEqual(stack_effect(dis.opmap['POP_TOP']), -1) + self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 0), -1) + self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 1), -1) + self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 3), -2) + self.assertRaises(ValueError, stack_effect, 30000) + # All defined opcodes + has_arg = dis.hasarg + for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()): + if code >= opcode.MIN_INSTRUMENTED_OPCODE: + continue + with self.subTest(opname=name): + stack_effect(code) + stack_effect(code, 0) + # All not defined opcodes + for code in set(range(256)) - set(dis.opmap.values()): + with self.subTest(opcode=code): + self.assertRaises(ValueError, stack_effect, code) + self.assertRaises(ValueError, stack_effect, code, 0) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_stack_effect_jump(self): + FOR_ITER = dis.opmap['FOR_ITER'] + self.assertEqual(stack_effect(FOR_ITER, 0), 1) + self.assertEqual(stack_effect(FOR_ITER, 0, jump=True), 1) + self.assertEqual(stack_effect(FOR_ITER, 0, jump=False), 1) + JUMP_FORWARD = dis.opmap['JUMP_FORWARD'] + self.assertEqual(stack_effect(JUMP_FORWARD, 0), 0) + self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=True), 0) + self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=False), 0) + # All defined opcodes + has_arg = dis.hasarg + has_exc = dis.hasexc + has_jump = dis.hasjabs + dis.hasjrel + for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()): + if code >= opcode.MIN_INSTRUMENTED_OPCODE: + continue + with self.subTest(opname=name): + if code not in has_arg: + common = stack_effect(code) + jump = stack_effect(code, jump=True) + nojump = stack_effect(code, jump=False) + else: + common = stack_effect(code, 0) + jump = stack_effect(code, 0, jump=True) + nojump = stack_effect(code, 0, jump=False) + if code in has_jump or code in has_exc: + self.assertEqual(common, max(jump, nojump)) + else: + self.assertEqual(jump, common) + self.assertEqual(nojump, common) + + +class SpecializationStatsTests(unittest.TestCase): + def test_specialization_stats(self): + stat_names = ["success", "failure", "hit", "deferred", "miss", "deopt"] + specialized_opcodes = [ + op.lower() + for op in opcode._specializations + if opcode._inline_cache_entries.get(op, 0) + ] + self.assertIn('load_attr', specialized_opcodes) + self.assertIn('binary_subscr', specialized_opcodes) + + stats = _opcode.get_specialization_stats() + if stats is not None: + self.assertIsInstance(stats, dict) + self.assertCountEqual(stats.keys(), specialized_opcodes) + self.assertCountEqual( + stats['load_attr'].keys(), + stat_names + ['failure_kinds']) + for sn in stat_names: + self.assertIsInstance(stats['load_attr'][sn], int) + self.assertIsInstance( + stats['load_attr']['failure_kinds'], + tuple) + for v in stats['load_attr']['failure_kinds']: + self.assertIsInstance(v, int) + + +if __name__ == "__main__": + unittest.main() diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index c2ce4e52c07..1088dd98487 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -198,27 +198,27 @@ pub struct CodeObject<C: Constant = ConstantData> { pub instructions: Box<[CodeUnit]>, pub locations: Box<[SourceLocation]>, pub flags: CodeFlags, + /// Number of positional-only arguments pub posonlyarg_count: u32, - // Number of positional-only arguments pub arg_count: u32, pub kwonlyarg_count: u32, pub source_path: C::Name, pub first_line_number: Option<OneIndexed>, pub max_stackdepth: u32, + /// Name of the object that created this code object pub obj_name: C::Name, - // Name of the object that created this code object + /// Qualified name of the object (like CPython's co_qualname) pub qualname: C::Name, - // Qualified name of the object (like CPython's co_qualname) pub cell2arg: Option<Box<[i32]>>, pub constants: Box<[C]>, pub names: Box<[C::Name]>, pub varnames: Box<[C::Name]>, pub cellvars: Box<[C::Name]>, pub freevars: Box<[C::Name]>, + /// Line number table (CPython 3.11+ format) pub linetable: Box<[u8]>, - // Line number table (CPython 3.11+ format) + /// Exception handling table pub exceptiontable: Box<[u8]>, - // Exception handling table } bitflags! { @@ -294,6 +294,12 @@ impl OpArg { } } +impl From<u32> for OpArg { + fn from(raw: u32) -> Self { + Self(raw) + } +} + #[derive(Default, Copy, Clone)] #[repr(transparent)] pub struct OpArgState { diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml index c65582ba72f..fd6d3e8a598 100644 --- a/stdlib/Cargo.toml +++ b/stdlib/Cargo.toml @@ -22,7 +22,7 @@ tkinter = ["dep:tk-sys", "dep:tcl-sys"] [dependencies] # rustpython crates rustpython-derive = { workspace = true } -rustpython-vm = { workspace = true, default-features = false } +rustpython-vm = { workspace = true, default-features = false, features = ["compiler"]} rustpython-common = { workspace = true } ahash = { workspace = true } diff --git a/stdlib/src/lib.rs b/stdlib/src/lib.rs index c22a95d16c6..706ce0ef210 100644 --- a/stdlib/src/lib.rs +++ b/stdlib/src/lib.rs @@ -38,6 +38,7 @@ mod locale; mod math; #[cfg(unix)] mod mmap; +mod opcode; mod pyexpat; mod pystruct; mod random; @@ -135,6 +136,7 @@ pub fn get_module_inits() -> impl Iterator<Item = (Cow<'static, str>, StdlibInit "_json" => json::make_module, "math" => math::make_module, "pyexpat" => pyexpat::make_module, + "_opcode" => opcode::make_module, "_random" => random::make_module, "_statistics" => statistics::make_module, "_struct" => pystruct::make_module, diff --git a/stdlib/src/opcode.rs b/stdlib/src/opcode.rs new file mode 100644 index 00000000000..c355b59df91 --- /dev/null +++ b/stdlib/src/opcode.rs @@ -0,0 +1,282 @@ +pub(crate) use opcode::make_module; + +#[pymodule] +mod opcode { + use crate::vm::{ + AsObject, PyObjectRef, PyResult, VirtualMachine, + builtins::{PyBool, PyInt, PyIntRef, PyNone}, + bytecode::Instruction, + match_class, + }; + use std::ops::Deref; + + struct Opcode(Instruction); + + impl Deref for Opcode { + type Target = Instruction; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl Opcode { + // https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/opcode_ids.h#L238 + const HAVE_ARGUMENT: i32 = 44; + + pub fn try_from_pyint(raw: PyIntRef, vm: &VirtualMachine) -> PyResult<Self> { + let instruction = raw + .try_to_primitive::<u8>(vm) + .and_then(|v| { + Instruction::try_from(v).map_err(|_| { + vm.new_exception_empty(vm.ctx.exceptions.value_error.to_owned()) + }) + }) + .map_err(|_| vm.new_value_error("invalid opcode or oparg"))?; + + Ok(Self(instruction)) + } + + /// https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/internal/pycore_opcode_metadata.h#L914-L916 + #[must_use] + pub const fn is_valid(opcode: i32) -> bool { + opcode >= 0 && opcode < 268 && opcode != 255 + } + + // All `has_*` methods below mimics + // https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/internal/pycore_opcode_metadata.h#L966-L1190 + + #[must_use] + pub const fn has_arg(opcode: i32) -> bool { + Self::is_valid(opcode) && opcode > Self::HAVE_ARGUMENT + } + + #[must_use] + pub const fn has_const(opcode: i32) -> bool { + Self::is_valid(opcode) && matches!(opcode, 83 | 103 | 240) + } + + #[must_use] + pub const fn has_name(opcode: i32) -> bool { + Self::is_valid(opcode) + && matches!( + opcode, + 63 | 66 + | 67 + | 74 + | 75 + | 82 + | 90 + | 91 + | 92 + | 93 + | 108 + | 113 + | 114 + | 259 + | 260 + | 261 + | 262 + ) + } + + #[must_use] + pub const fn has_jump(opcode: i32) -> bool { + Self::is_valid(opcode) + && matches!( + opcode, + 72 | 77 | 78 | 79 | 97 | 98 | 99 | 100 | 104 | 256 | 257 + ) + } + + #[must_use] + pub const fn has_free(opcode: i32) -> bool { + Self::is_valid(opcode) && matches!(opcode, 64 | 84 | 89 | 94 | 109) + } + + #[must_use] + pub const fn has_local(opcode: i32) -> bool { + Self::is_valid(opcode) + && matches!(opcode, 65 | 85 | 86 | 87 | 88 | 110 | 111 | 112 | 258 | 267) + } + + #[must_use] + pub const fn has_exc(opcode: i32) -> bool { + Self::is_valid(opcode) && matches!(opcode, 264..=266) + } + } + + #[pyattr] + const ENABLE_SPECIALIZATION: i8 = 1; + + #[derive(FromArgs)] + struct StackEffectArgs { + #[pyarg(positional)] + opcode: PyIntRef, + #[pyarg(positional, optional)] + oparg: Option<PyObjectRef>, + #[pyarg(named, optional)] + jump: Option<PyObjectRef>, + } + + #[pyfunction] + fn stack_effect(args: StackEffectArgs, vm: &VirtualMachine) -> PyResult<i32> { + let oparg = args + .oparg + .map(|v| { + if !v.fast_isinstance(vm.ctx.types.int_type) { + return Err(vm.new_type_error(format!( + "'{}' object cannot be interpreted as an integer", + v.class().name() + ))); + } + v.downcast_ref::<PyInt>() + .ok_or_else(|| vm.new_type_error(""))? + .try_to_primitive::<u32>(vm) + }) + .unwrap_or(Ok(0))?; + + let jump = args + .jump + .map(|v| { + match_class!(match v { + b @ PyBool => Ok(b.is(&vm.ctx.true_value)), + _n @ PyNone => Ok(false), + _ => { + Err(vm.new_value_error("stack_effect: jump must be False, True or None")) + } + }) + }) + .unwrap_or(Ok(false))?; + + let opcode = Opcode::try_from_pyint(args.opcode, vm)?; + + Ok(opcode.stack_effect(oparg.into(), jump)) + } + + #[pyfunction] + fn is_valid(opcode: i32) -> bool { + Opcode::is_valid(opcode) + } + + #[pyfunction] + fn has_arg(opcode: i32) -> bool { + Opcode::has_arg(opcode) + } + + #[pyfunction] + fn has_const(opcode: i32) -> bool { + Opcode::has_const(opcode) + } + + #[pyfunction] + fn has_name(opcode: i32) -> bool { + Opcode::has_name(opcode) + } + + #[pyfunction] + fn has_jump(opcode: i32) -> bool { + Opcode::has_jump(opcode) + } + + #[pyfunction] + fn has_free(opcode: i32) -> bool { + Opcode::has_free(opcode) + } + + #[pyfunction] + fn has_local(opcode: i32) -> bool { + Opcode::has_local(opcode) + } + + #[pyfunction] + fn has_exc(opcode: i32) -> bool { + Opcode::has_exc(opcode) + } + + #[pyfunction] + fn get_intrinsic1_descs(vm: &VirtualMachine) -> Vec<PyObjectRef> { + [ + "INTRINSIC_1_INVALID", + "INTRINSIC_PRINT", + "INTRINSIC_IMPORT_STAR", + "INTRINSIC_STOPITERATION_ERROR", + "INTRINSIC_ASYNC_GEN_WRAP", + "INTRINSIC_UNARY_POSITIVE", + "INTRINSIC_LIST_TO_TUPLE", + "INTRINSIC_TYPEVAR", + "INTRINSIC_PARAMSPEC", + "INTRINSIC_TYPEVARTUPLE", + "INTRINSIC_SUBSCRIPT_GENERIC", + "INTRINSIC_TYPEALIAS", + ] + .into_iter() + .map(|x| vm.ctx.new_str(x).into()) + .collect() + } + + #[pyfunction] + fn get_intrinsic2_descs(vm: &VirtualMachine) -> Vec<PyObjectRef> { + [ + "INTRINSIC_2_INVALID", + "INTRINSIC_PREP_RERAISE_STAR", + "INTRINSIC_TYPEVAR_WITH_BOUND", + "INTRINSIC_TYPEVAR_WITH_CONSTRAINTS", + "INTRINSIC_SET_FUNCTION_TYPE_PARAMS", + "INTRINSIC_SET_TYPEPARAM_DEFAULT", + ] + .into_iter() + .map(|x| vm.ctx.new_str(x).into()) + .collect() + } + + #[pyfunction] + fn get_nb_ops(vm: &VirtualMachine) -> Vec<PyObjectRef> { + [ + ("NB_ADD", "+"), + ("NB_AND", "&"), + ("NB_FLOOR_DIVIDE", "//"), + ("NB_LSHIFT", "<<"), + ("NB_MATRIX_MULTIPLY", "@"), + ("NB_MULTIPLY", "*"), + ("NB_REMAINDER", "%"), + ("NB_OR", "|"), + ("NB_POWER", "**"), + ("NB_RSHIFT", ">>"), + ("NB_SUBTRACT", "-"), + ("NB_TRUE_DIVIDE", "/"), + ("NB_XOR", "^"), + ("NB_INPLACE_ADD", "+="), + ("NB_INPLACE_AND", "&="), + ("NB_INPLACE_FLOOR_DIVIDE", "//="), + ("NB_INPLACE_LSHIFT", "<<="), + ("NB_INPLACE_MATRIX_MULTIPLY", "@="), + ("NB_INPLACE_MULTIPLY", "*="), + ("NB_INPLACE_REMAINDER", "%="), + ("NB_INPLACE_OR", "|="), + ("NB_INPLACE_POWER", "**="), + ("NB_INPLACE_RSHIFT", ">>="), + ("NB_INPLACE_SUBTRACT", "-="), + ("NB_INPLACE_TRUE_DIVIDE", "/="), + ("NB_INPLACE_XOR", "^="), + ] + .into_iter() + .map(|(a, b)| { + vm.ctx + .new_tuple(vec![vm.ctx.new_str(a).into(), vm.ctx.new_str(b).into()]) + .into() + }) + .collect() + } + + #[pyfunction] + fn get_executor(_code: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // TODO + Ok(vm.ctx.none()) + } + + #[pyfunction] + fn get_specialization_stats(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.none() + } +} diff --git a/vm/src/builtins/code.rs b/vm/src/builtins/code.rs index b49f76caa02..79ad896aaf8 100644 --- a/vm/src/builtins/code.rs +++ b/vm/src/builtins/code.rs @@ -837,6 +837,15 @@ impl PyCode { }, }) } + + #[pymethod] + fn _varname_from_oparg(&self, opcode: i32, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + let idx_err = |vm: &VirtualMachine| vm.new_index_error("tuple index out of range"); + + let idx = usize::try_from(opcode).map_err(|_| idx_err(vm))?; + let name = self.code.varnames.get(idx).ok_or_else(|| idx_err(vm))?; + Ok(name.to_object()) + } } impl fmt::Display for PyCode { From c979059eebe9cdca4e660b8e7604a3bf21c6ae9b Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 12 Oct 2025 06:55:23 +0300 Subject: [PATCH 278/819] Configure dependabot to ignore ruff updates (#6185) * Make dependabot ignore ruff updates * Regenrate Cargo.lock * Fix clippy * Fix typo --- .github/dependabot.yml | 5 + Cargo.lock | 408 +++++++++++++++------------------- compiler/literal/Cargo.toml | 2 +- compiler/literal/src/float.rs | 2 +- 4 files changed, 183 insertions(+), 234 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c7ecf5eaac9..b3b7b446e4a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,6 +4,11 @@ updates: directory: / schedule: interval: weekly + ignore: + # TODO: Remove when we use ruff from crates.io + # for some reason dependabot only updates the Cargo.lock file when dealing + # with git dependencies. i.e. not updating the version in Cargo.toml + - dependency-name: "ruff_*" - package-ecosystem: github-actions directory: / schedule: diff --git a/Cargo.lock b/Cargo.lock index 88146885d85..1d5b745a1bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,9 +59,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -74,9 +74,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -109,9 +109,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "approx" @@ -227,9 +227,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "bzip2" @@ -266,9 +266,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.37" +version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "shlex", @@ -305,7 +305,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -348,18 +348,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstyle", "clap_lex", @@ -400,20 +400,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "compact_str" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", -] - [[package]] name = "console" version = "0.15.11" @@ -527,7 +513,7 @@ dependencies = [ "cranelift-entity", "cranelift-isle", "gimli", - "hashbrown", + "hashbrown 0.15.5", "log", "regalloc2", "rustc-hash", @@ -839,7 +825,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -873,9 +859,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "flame" @@ -915,9 +901,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "libz-rs-sys", @@ -1041,6 +1027,12 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "heck" version = "0.5.0" @@ -1086,7 +1078,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.0", + "windows-core 0.62.2", ] [[package]] @@ -1100,12 +1092,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.3" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.0", ] [[package]] @@ -1193,9 +1185,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.78" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -1228,33 +1220,28 @@ checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" [[package]] name = "lexical-parse-float" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" dependencies = [ "lexical-parse-integer", "lexical-util", - "static_assertions", ] [[package]] name = "lexical-parse-integer" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" dependencies = [ "lexical-util", - "static_assertions", ] [[package]] name = "lexical-util" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3" -dependencies = [ - "static_assertions", -] +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" [[package]] name = "lexopt" @@ -1270,9 +1257,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libffi" @@ -1295,12 +1282,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link", ] [[package]] @@ -1347,11 +1334,10 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -1406,7 +1392,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c738d3789301e957a8f7519318fcbb1b92bb95863b28f6938ae5a05be6259f34" dependencies = [ - "hashbrown", + "hashbrown 0.15.5", "itertools 0.14.0", "libm", "ryu", @@ -1472,9 +1458,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" @@ -1507,6 +1493,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -1673,9 +1660,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.2+3.5.2" +version = "300.5.3+3.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4" +checksum = "dc6bad8cd0233b63971e232cc9c5e83039375b8586d2312f31fda85db8f888c2" dependencies = [ "cc", ] @@ -1711,9 +1698,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1721,15 +1708,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.17", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -1936,9 +1923,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -2055,9 +2042,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.4", ] @@ -2081,7 +2068,7 @@ checksum = "dc06e6b318142614e4a48bc725abbf08ff166694835c43c9dae5a9009704639a" dependencies = [ "allocator-api2", "bumpalo", - "hashbrown", + "hashbrown 0.15.5", "log", "rustc-hash", "smallvec", @@ -2089,9 +2076,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", @@ -2101,9 +2088,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -2156,34 +2143,16 @@ source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca dependencies = [ "aho-corasick", "bitflags 2.9.4", - "compact_str 0.8.1", + "compact_str", "is-macro", "itertools 0.14.0", "memchr", - "ruff_python_trivia 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", - "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", - "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_python_trivia", + "ruff_source_file", + "ruff_text_size", "rustc-hash", ] -[[package]] -name = "ruff_python_ast" -version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.13.1#706be0a6e7e09936511198f2ff8982915520d138" -dependencies = [ - "aho-corasick", - "bitflags 2.9.4", - "compact_str 0.9.0", - "is-macro", - "itertools 0.14.0", - "memchr", - "ruff_python_trivia 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", - "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", - "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", - "rustc-hash", - "thiserror 2.0.16", -] - [[package]] name = "ruff_python_parser" version = "0.0.0" @@ -2191,11 +2160,11 @@ source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca dependencies = [ "bitflags 2.9.4", "bstr", - "compact_str 0.8.1", + "compact_str", "memchr", - "ruff_python_ast 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", - "ruff_python_trivia 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", - "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_python_ast", + "ruff_python_trivia", + "ruff_text_size", "rustc-hash", "static_assertions", "unicode-ident", @@ -2209,19 +2178,8 @@ version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" dependencies = [ "itertools 0.14.0", - "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", - "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", - "unicode-ident", -] - -[[package]] -name = "ruff_python_trivia" -version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.13.1#706be0a6e7e09936511198f2ff8982915520d138" -dependencies = [ - "itertools 0.14.0", - "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", - "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", + "ruff_source_file", + "ruff_text_size", "unicode-ident", ] @@ -2231,16 +2189,7 @@ version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" dependencies = [ "memchr", - "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", -] - -[[package]] -name = "ruff_source_file" -version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.13.1#706be0a6e7e09936511198f2ff8982915520d138" -dependencies = [ - "memchr", - "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", + "ruff_text_size", ] [[package]] @@ -2248,11 +2197,6 @@ name = "ruff_text_size" version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" -[[package]] -name = "ruff_text_size" -version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.13.1#706be0a6e7e09936511198f2ff8982915520d138" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -2269,7 +2213,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -2309,13 +2253,13 @@ dependencies = [ "memchr", "num-complex", "num-traits", - "ruff_python_ast 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", + "ruff_python_ast", "ruff_python_parser", - "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_text_size", "rustpython-compiler-core", "rustpython-literal", "rustpython-wtf8", - "thiserror 2.0.16", + "thiserror 2.0.17", "unicode_names2 2.0.0", ] @@ -2350,13 +2294,13 @@ dependencies = [ name = "rustpython-compiler" version = "0.4.0" dependencies = [ - "ruff_python_ast 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", + "ruff_python_ast", "ruff_python_parser", - "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", - "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_source_file", + "ruff_text_size", "rustpython-codegen", "rustpython-compiler-core", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -2368,7 +2312,7 @@ dependencies = [ "lz4_flex", "malachite-bigint", "num-complex", - "ruff_source_file 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_source_file", "rustpython-wtf8", ] @@ -2416,7 +2360,7 @@ dependencies = [ "num-traits", "rustpython-compiler-core", "rustpython-derive", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -2570,9 +2514,9 @@ dependencies = [ "parking_lot", "paste", "result-like", - "ruff_python_ast 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.13.1)", + "ruff_python_ast", "ruff_python_parser", - "ruff_text_size 0.0.0 (git+https://github.com/astral-sh/ruff.git?tag=0.11.0)", + "ruff_text_size", "rustix", "rustpython-codegen", "rustpython-common", @@ -2589,7 +2533,7 @@ dependencies = [ "static_assertions", "strum", "strum_macros", - "thiserror 2.0.16", + "thiserror 2.0.17", "thread_local", "timsort", "uname", @@ -2641,9 +2585,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rustyline" -version = "17.0.1" +version = "17.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6614df0b6d4cfb20d1d5e295332921793ce499af3ebc011bf1e393380e1e492" +checksum = "e902948a25149d50edc1a8e0141aad50f54e22ba83ff988cf8f7c9ef07f50564" dependencies = [ "bitflags 2.9.4", "cfg-if", @@ -2691,7 +2635,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -2708,9 +2652,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -2730,18 +2674,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2816,6 +2760,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "similar" version = "2.7.0" @@ -2964,11 +2914,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -2984,9 +2934,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -3102,9 +3052,9 @@ checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd" @@ -3261,9 +3211,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode_names2" @@ -3378,9 +3328,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -3391,9 +3341,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -3405,9 +3355,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.51" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -3418,9 +3368,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3428,9 +3378,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -3441,9 +3391,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] @@ -3462,9 +3412,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.78" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -3519,7 +3469,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -3549,22 +3499,22 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.62.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.0", + "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -3573,9 +3523,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -3584,32 +3534,26 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -3636,16 +3580,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -3666,19 +3610,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -3689,9 +3633,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -3701,9 +3645,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -3713,9 +3657,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -3725,9 +3669,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -3737,9 +3681,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -3749,9 +3693,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -3761,9 +3705,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -3773,9 +3717,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" diff --git a/compiler/literal/Cargo.toml b/compiler/literal/Cargo.toml index da55d107b39..bd6a2699742 100644 --- a/compiler/literal/Cargo.toml +++ b/compiler/literal/Cargo.toml @@ -13,7 +13,7 @@ rustpython-wtf8 = { workspace = true } hexf-parse = "0.2.1" is-macro.workspace = true -lexical-parse-float = { version = "1.0.4", features = ["format"] } +lexical-parse-float = { version = "1.0.6", features = ["format"] } num-traits = { workspace = true } unic-ucd-category = { workspace = true } diff --git a/compiler/literal/src/float.rs b/compiler/literal/src/float.rs index b1fc4607b59..e2bc54a8f1b 100644 --- a/compiler/literal/src/float.rs +++ b/compiler/literal/src/float.rs @@ -18,7 +18,7 @@ fn parse_inner(literal: &[u8]) -> Option<f64> { // lexical-core's format::PYTHON_STRING is inaccurate const PYTHON_STRING: u128 = NumberFormatBuilder::rebuild(PYTHON3_LITERAL) .no_special(false) - .build(); + .build_unchecked(); f64::from_lexical_with_options::<PYTHON_STRING>(literal, &Options::new()).ok() } From 7986fee56fa48d8f310d97df4830e9d8c6c90330 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 12 Oct 2025 06:55:46 +0300 Subject: [PATCH 279/819] Revert "Pin CI image to `windows-2025` (#6148)" (#6182) This reverts commit 43d643ad09179ae971894fcf690b2da3f36d2511. --- .github/workflows/ci.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6f891eb05dc..45050db2da8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -116,10 +116,7 @@ jobs: timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 30 }} strategy: matrix: - os: - - macos-latest - - ubuntu-latest - - windows-2025 # TODO: Switch to `windows-latest` on 2025/09/30 + os: [macos-latest, ubuntu-latest, windows-latest] fail-fast: false steps: - uses: actions/checkout@v5 @@ -245,10 +242,7 @@ jobs: timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 30 }} strategy: matrix: - os: - - macos-latest - - ubuntu-latest - - windows-2025 # TODO: Switch to `windows-latest` on 2025/09/30 + os: [macos-latest, ubuntu-latest, windows-latest] fail-fast: false steps: - uses: actions/checkout@v5 From b56e469a5f229589fb5d9525a855fd41a08423fb Mon Sep 17 00:00:00 2001 From: Anton <tosha.fedotov.2000@gmail.com> Date: Mon, 13 Oct 2025 06:18:51 +0500 Subject: [PATCH 280/819] Handle OsError in REPL (#6187) --- src/shell.rs | 7 +++++++ vm/src/readline.rs | 4 ++++ vm/src/stdlib/builtins.rs | 2 ++ 3 files changed, 13 insertions(+) diff --git a/src/shell.rs b/src/shell.rs index e38b0a50def..c0ae508b018 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -209,6 +209,13 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> { ReadlineResult::Eof => { break; } + #[cfg(unix)] + ReadlineResult::OsError(num) => { + let os_error = + vm.new_exception_msg(vm.ctx.exceptions.os_error.to_owned(), format!("{num:?}")); + vm.print_exception(os_error); + break; + } ReadlineResult::Other(err) => { eprintln!("Readline error: {err:?}"); break; diff --git a/vm/src/readline.rs b/vm/src/readline.rs index 86c8fc4fdd7..839ac980034 100644 --- a/vm/src/readline.rs +++ b/vm/src/readline.rs @@ -13,6 +13,8 @@ pub enum ReadlineResult { Eof, Interrupt, Io(std::io::Error), + #[cfg(unix)] + OsError(nix::Error), Other(OtherError), } @@ -118,6 +120,8 @@ mod rustyline_readline { Err(ReadlineError::Eof) => ReadlineResult::Eof, Err(ReadlineError::Io(e)) => ReadlineResult::Io(e), Err(ReadlineError::Signal(_)) => continue, + #[cfg(unix)] + Err(ReadlineError::Errno(num)) => ReadlineResult::OsError(num), Err(e) => ReadlineResult::Other(e.into()), }; } diff --git a/vm/src/stdlib/builtins.rs b/vm/src/stdlib/builtins.rs index f723ed801b2..8cf99770e77 100644 --- a/vm/src/stdlib/builtins.rs +++ b/vm/src/stdlib/builtins.rs @@ -489,6 +489,8 @@ mod builtins { Err(vm.new_exception_empty(vm.ctx.exceptions.keyboard_interrupt.to_owned())) } ReadlineResult::Io(e) => Err(vm.new_os_error(e.to_string())), + #[cfg(unix)] + ReadlineResult::OsError(num) => Err(vm.new_os_error(num.to_string())), ReadlineResult::Other(e) => Err(vm.new_runtime_error(e.to_string())), } } else { From 3b48dcc7c1549f598bd9a96d2ee97b1cb83f910b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:03:49 +0900 Subject: [PATCH 281/819] Bump serde-wasm-bindgen from 0.3.1 to 0.6.5 (#6188) Bumps [serde-wasm-bindgen](https://github.com/RReverser/serde-wasm-bindgen) from 0.3.1 to 0.6.5. - [Commits](https://github.com/RReverser/serde-wasm-bindgen/commits/v0.6.5) --- updated-dependencies: - dependency-name: serde-wasm-bindgen dependency-version: 0.6.5 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 11 ++--------- wasm/lib/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d5b745a1bf..cef898addd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -910,12 +910,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foldhash" version = "0.1.5" @@ -2662,11 +2656,10 @@ dependencies = [ [[package]] name = "serde-wasm-bindgen" -version = "0.3.1" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618365e8e586c22123d692b72a7d791d5ee697817b65a218cdf12a98870af0f7" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" dependencies = [ - "fnv", "js-sys", "serde", "wasm-bindgen", diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index 768a1e671f9..a2bb1a9f948 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -30,7 +30,7 @@ wasm-bindgen = { workspace = true } console_error_panic_hook = "0.1" js-sys = "0.3" -serde-wasm-bindgen = "0.3.1" +serde-wasm-bindgen = "0.6.5" wasm-bindgen-futures = "0.4" web-sys = { version = "0.3", features = [ "console", From 3473d824a827f6b7d08832f7d90d313f921a3e47 Mon Sep 17 00:00:00 2001 From: fanninpm <fanninpm@miamioh.edu> Date: Mon, 20 Oct 2025 04:32:05 -0400 Subject: [PATCH 282/819] Replace skips in test_importlib/source/test_file_loader with expectedFailures (#6194) * Replace skips with expectedFailure markings for SimpleTest * Uncomment Source-PEP451 tests and apply similar monkey-patches as before * Uncomment Source-PEP302 tests and apply similar monkey-patches as before * Uncomment Sourceless-PEP451 tests and apply similar monkey-patches as before * Uncomment Sourceless-PEP302 tests and apply similar monkey-patches as before --- .../test_importlib/source/test_file_loader.py | 171 +++++++++++++++--- 1 file changed, 141 insertions(+), 30 deletions(-) diff --git a/Lib/test/test_importlib/source/test_file_loader.py b/Lib/test/test_importlib/source/test_file_loader.py index 9ade197a23f..d487fc9b82d 100644 --- a/Lib/test/test_importlib/source/test_file_loader.py +++ b/Lib/test/test_importlib/source/test_file_loader.py @@ -236,7 +236,6 @@ def test_unloadable(self): warnings.simplefilter('ignore', DeprecationWarning) loader.load_module('bad name') - @unittest.skip("TODO: RUSTPYTHON; successful only for Frozen") @util.writes_bytecode_files def test_checked_hash_based_pyc(self): with util.create_modules('_temp') as mapping: @@ -293,7 +292,6 @@ def test_overridden_checked_hash_based_pyc(self): loader.exec_module(mod) self.assertEqual(mod.state, 'old') - @unittest.skip("TODO: RUSTPYTHON; successful only for Frozen") @util.writes_bytecode_files def test_unchecked_hash_based_pyc(self): with util.create_modules('_temp') as mapping: @@ -324,7 +322,6 @@ def test_unchecked_hash_based_pyc(self): data[8:16], ) - @unittest.skip("TODO: RUSTPYTHON; successful only for Frozen") @util.writes_bytecode_files def test_overridden_unchecked_hash_based_pyc(self): with util.create_modules('_temp') as mapping, \ @@ -362,6 +359,23 @@ def test_overridden_unchecked_hash_based_pyc(self): ) = util.test_both(SimpleTest, importlib=importlib, machinery=machinery, abc=importlib_abc, util=importlib_util) +# TODO: RUSTPYTHON, get rid of this entire class when all of the following tests are fixed +class Source_SimpleTest(Source_SimpleTest): + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_checked_hash_based_pyc(self): + super().test_checked_hash_based_pyc() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_unchecked_hash_based_pyc(self): + super().test_unchecked_hash_based_pyc() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_overridden_unchecked_hash_based_pyc(self): + super().test_overridden_unchecked_hash_based_pyc() + class SourceDateEpochTestMeta(SourceDateEpochTestMeta, type(Source_SimpleTest)): @@ -677,24 +691,57 @@ class SourceLoaderBadBytecodeTestPEP451( pass -# (Frozen_SourceBadBytecodePEP451, -# Source_SourceBadBytecodePEP451 -# ) = util.test_both(SourceLoaderBadBytecodeTestPEP451, importlib=importlib, -# machinery=machinery, abc=importlib_abc, -# util=importlib_util) +(Frozen_SourceBadBytecodePEP451, + Source_SourceBadBytecodePEP451 + ) = util.test_both(SourceLoaderBadBytecodeTestPEP451, importlib=importlib, + machinery=machinery, abc=importlib_abc, + util=importlib_util) + +# TODO: RUSTPYTHON, get rid of this entire class when all of the following tests are fixed +class Source_SourceBadBytecodePEP451(Source_SourceBadBytecodePEP451): + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_bad_marshal(self): + super().test_bad_marshal() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_no_marshal(self): + super().test_no_marshal() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_non_code_marshal(self): + super().test_non_code_marshal() + +class SourceLoaderBadBytecodeTestPEP302( + SourceLoaderBadBytecodeTest, BadBytecodeTestPEP302): + pass -# TODO: RUSTPYTHON -# class SourceLoaderBadBytecodeTestPEP302( -# SourceLoaderBadBytecodeTest, BadBytecodeTestPEP302): -# pass +(Frozen_SourceBadBytecodePEP302, + Source_SourceBadBytecodePEP302 + ) = util.test_both(SourceLoaderBadBytecodeTestPEP302, importlib=importlib, + machinery=machinery, abc=importlib_abc, + util=importlib_util) -# (Frozen_SourceBadBytecodePEP302, -# Source_SourceBadBytecodePEP302 -# ) = util.test_both(SourceLoaderBadBytecodeTestPEP302, importlib=importlib, -# machinery=machinery, abc=importlib_abc, -# util=importlib_util) +# TODO: RUSTPYTHON, get rid of this entire class when all of the following tests are fixed +class Source_SourceBadBytecodePEP302(Source_SourceBadBytecodePEP302): + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_bad_marshal(self): + super().test_bad_marshal() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_no_marshal(self): + super().test_no_marshal() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_non_code_marshal(self): + super().test_non_code_marshal() class SourcelessLoaderBadBytecodeTest: @@ -776,23 +823,87 @@ class SourcelessLoaderBadBytecodeTestPEP451(SourcelessLoaderBadBytecodeTest, pass -# (Frozen_SourcelessBadBytecodePEP451, -# Source_SourcelessBadBytecodePEP451 -# ) = util.test_both(SourcelessLoaderBadBytecodeTestPEP451, importlib=importlib, -# machinery=machinery, abc=importlib_abc, -# util=importlib_util) +(Frozen_SourcelessBadBytecodePEP451, + Source_SourcelessBadBytecodePEP451 + ) = util.test_both(SourcelessLoaderBadBytecodeTestPEP451, importlib=importlib, + machinery=machinery, abc=importlib_abc, + util=importlib_util) + +# TODO: RUSTPYTHON, get rid of this entire class when all of the following tests are fixed +class Source_SourcelessBadBytecodePEP451(Source_SourcelessBadBytecodePEP451): + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_magic_only(self): + super().test_magic_only() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_no_marshal(self): + super().test_no_marshal() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_partial_flags(self): + super().test_partial_flags() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_partial_hash(self): + super().test_partial_hash() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_partial_size(self): + super().test_partial_size() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_partial_timestamp(self): + super().test_partial_timestamp() + + +class SourcelessLoaderBadBytecodeTestPEP302(SourcelessLoaderBadBytecodeTest, + BadBytecodeTestPEP302): + pass + + +(Frozen_SourcelessBadBytecodePEP302, + Source_SourcelessBadBytecodePEP302 + ) = util.test_both(SourcelessLoaderBadBytecodeTestPEP302, importlib=importlib, + machinery=machinery, abc=importlib_abc, + util=importlib_util) +# TODO: RUSTPYTHON, get rid of this entire class when all of the following tests are fixed +class Source_SourcelessBadBytecodePEP302(Source_SourcelessBadBytecodePEP302): + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_magic_only(self): + super().test_magic_only() -# class SourcelessLoaderBadBytecodeTestPEP302(SourcelessLoaderBadBytecodeTest, -# BadBytecodeTestPEP302): -# pass + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_no_marshal(self): + super().test_no_marshal() + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_partial_flags(self): + super().test_partial_flags() -# (Frozen_SourcelessBadBytecodePEP302, -# Source_SourcelessBadBytecodePEP302 -# ) = util.test_both(SourcelessLoaderBadBytecodeTestPEP302, importlib=importlib, -# machinery=machinery, abc=importlib_abc, -# util=importlib_util) + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_partial_hash(self): + super().test_partial_hash() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_partial_size(self): + super().test_partial_size() + + # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed + @unittest.expectedFailure + def test_partial_timestamp(self): + super().test_partial_timestamp() if __name__ == '__main__': From 9a5d5d61947dba0a613e088e8f0db301fac1e2c7 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:40:38 +0300 Subject: [PATCH 283/819] Fix CI (#6196) * Update `vcpkg` to 2025.09.17 * Pin selenium version * Use `localhost` instead of `0.0.0.0` --- Cargo.toml | 2 +- wasm/tests/conftest.py | 4 ++-- wasm/tests/requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4412fb9354a..ab055f6a36d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,7 +90,7 @@ lto = "thin" git = "https://github.com/microsoft/vcpkg" # The revision of the vcpkg repository to use # https://github.com/microsoft/vcpkg/tags -rev = "2024.02.14" +rev = "2025.09.17" [package.metadata.vcpkg.target] x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md", dev-dependencies = ["openssl" ] } diff --git a/wasm/tests/conftest.py b/wasm/tests/conftest.py index 84a25305759..e01f5eddc5f 100644 --- a/wasm/tests/conftest.py +++ b/wasm/tests/conftest.py @@ -41,7 +41,7 @@ def pytest_sessionfinish(session): # From https://gist.github.com/butla/2d9a4c0f35ea47b7452156c96a4e7b12 -def wait_for_port(port, host="0.0.0.0", timeout=5.0): +def wait_for_port(port, host="localhost", timeout=5.0): """Wait until a port starts accepting TCP connections. Args: port (int): Port number. @@ -94,7 +94,7 @@ def wdriver(request): options.add_argument("-headless") driver = Driver(options=options) try: - driver.get(f"http://0.0.0.0:{PORT}") + driver.get(f"http://localhost:{PORT}") WebDriverWait(driver, 5).until( EC.presence_of_element_located((By.ID, "rp_loaded")) ) diff --git a/wasm/tests/requirements.txt b/wasm/tests/requirements.txt index 52dc2039123..a474c5f74b7 100644 --- a/wasm/tests/requirements.txt +++ b/wasm/tests/requirements.txt @@ -1,3 +1,3 @@ pytest -selenium +selenium==4.36.0 certifi From fcf196935e0eff00a19538c1e29b458e91031fdb Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:46:46 +0300 Subject: [PATCH 284/819] Update ruff 0.14.1 (#6195) * Update ruff to 0.14.1 * Fix test regression in `test_compile` * Unmark passing test * Update `test_syntax` from 3.13.9 --------- Co-authored-by: Noa <coolreader18@gmail.com> --- Cargo.lock | 278 +++++++--- Cargo.toml | 8 +- Lib/test/test_fstring.py | 1 - Lib/test/test_syntax.py | 710 ++++++++++++++++++++++---- compiler/codegen/src/compile.rs | 225 +++++--- compiler/codegen/src/ir.rs | 6 +- compiler/codegen/src/lib.rs | 1 + compiler/codegen/src/symboltable.rs | 59 ++- compiler/codegen/src/unparse.rs | 38 +- compiler/core/src/bytecode.rs | 6 +- compiler/core/src/lib.rs | 4 +- compiler/core/src/marshal.rs | 8 +- compiler/src/lib.rs | 26 +- src/shell.rs | 4 +- stdlib/src/faulthandler.rs | 2 +- vm/src/builtins/frame.rs | 2 +- vm/src/frame.rs | 14 +- vm/src/stdlib/ast.rs | 22 +- vm/src/stdlib/ast/argument.rs | 4 + vm/src/stdlib/ast/constant.rs | 65 ++- vm/src/stdlib/ast/elif_else_clause.rs | 11 +- vm/src/stdlib/ast/exception.rs | 2 + vm/src/stdlib/ast/expression.rs | 106 +++- vm/src/stdlib/ast/module.rs | 9 +- vm/src/stdlib/ast/other.rs | 10 +- vm/src/stdlib/ast/parameter.rs | 9 + vm/src/stdlib/ast/pattern.rs | 19 + vm/src/stdlib/ast/statement.rs | 60 ++- vm/src/stdlib/ast/string.rs | 117 +++-- vm/src/stdlib/ast/type_parameters.rs | 12 +- vm/src/vm/vm_new.rs | 4 +- wasm/lib/src/convert.rs | 4 +- 32 files changed, 1440 insertions(+), 406 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cef898addd7..53b4f969223 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,7 +21,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -143,6 +143,36 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "attribute-derive" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05832cdddc8f2650cc2cc187cc2e952b8c133a48eb055f35211f61ee81502d77" +dependencies = [ + "attribute-derive-macro", + "derive-where", + "manyhow", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7cdbbd4bd005c5d3e2e9c885e6fa575db4f4a3572335b974d8db853b6beb61" +dependencies = [ + "collection_literals", + "interpolator", + "manyhow", + "proc-macro-utils", + "proc-macro2", + "quote", + "quote-use", + "syn", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -233,9 +263,9 @@ checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "bzip2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" dependencies = [ "libbz2-rs-sys", ] @@ -266,9 +296,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.40" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "shlex", @@ -285,9 +315,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -348,18 +378,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" dependencies = [ "anstyle", "clap_lex", @@ -367,9 +397,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "clipboard-win" @@ -380,6 +410,12 @@ dependencies = [ "error-code", ] +[[package]] +name = "collection_literals" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" + [[package]] name = "colorchoice" version = "1.0.4" @@ -388,9 +424,9 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "compact_str" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", @@ -708,13 +744,24 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] +[[package]] +name = "derive-where" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -785,9 +832,9 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -859,9 +906,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "flame" @@ -933,22 +980,45 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", ] +[[package]] +name = "get-size-derive2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3814abc7da8ab18d2fd820f5b540b5e39b6af0a32de1bdd7c47576693074843" +dependencies = [ + "attribute-derive", + "quote", + "syn", +] + +[[package]] +name = "get-size2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfe2cec5b5ce8fb94dcdb16a1708baa4d0609cc3ce305ca5d3f6f2ffb59baed" +dependencies = [ + "compact_str", + "get-size-derive2", + "hashbrown 0.16.0", + "smallvec", +] + [[package]] name = "gethostname" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ "rustix", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -968,20 +1038,20 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] @@ -1004,12 +1074,13 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -1086,9 +1157,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", @@ -1111,6 +1182,12 @@ dependencies = [ "similar", ] +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + [[package]] name = "is-macro" version = "0.3.7" @@ -1251,9 +1328,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libffi" @@ -1428,6 +1505,29 @@ dependencies = [ "malachite-nz", ] +[[package]] +name = "manyhow" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "manyhow-macros" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", +] + [[package]] name = "maplit" version = "1.0.2" @@ -1622,9 +1722,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" dependencies = [ "bitflags 2.9.4", "cfg-if", @@ -1663,9 +1763,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" dependencies = [ "cc", "libc", @@ -1836,6 +1936,17 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -1924,6 +2035,28 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quote-use" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9619db1197b497a36178cfc736dc96b271fe918875fbf1344c436a7e93d0321e" +dependencies = [ + "quote", + "quote-use-macros", +] + +[[package]] +name = "quote-use-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "r-efi" version = "5.3.0" @@ -2005,7 +2138,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -2070,9 +2203,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.3" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -2082,9 +2215,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -2093,9 +2226,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "region" @@ -2133,11 +2266,12 @@ dependencies = [ [[package]] name = "ruff_python_ast" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" dependencies = [ "aho-corasick", "bitflags 2.9.4", "compact_str", + "get-size2", "is-macro", "itertools 0.14.0", "memchr", @@ -2145,16 +2279,18 @@ dependencies = [ "ruff_source_file", "ruff_text_size", "rustc-hash", + "thiserror 2.0.17", ] [[package]] name = "ruff_python_parser" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" dependencies = [ "bitflags 2.9.4", "bstr", "compact_str", + "get-size2", "memchr", "ruff_python_ast", "ruff_python_trivia", @@ -2169,7 +2305,7 @@ dependencies = [ [[package]] name = "ruff_python_trivia" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" dependencies = [ "itertools 0.14.0", "ruff_source_file", @@ -2180,7 +2316,7 @@ dependencies = [ [[package]] name = "ruff_source_file" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" dependencies = [ "memchr", "ruff_text_size", @@ -2189,7 +2325,10 @@ dependencies = [ [[package]] name = "ruff_text_size" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" +dependencies = [ + "get-size2", +] [[package]] name = "rustc-hash" @@ -2264,7 +2403,7 @@ dependencies = [ "ascii", "bitflags 2.9.4", "cfg-if", - "getrandom 0.3.3", + "getrandom 0.3.4", "itertools 0.14.0", "libc", "lock_api", @@ -2483,7 +2622,7 @@ dependencies = [ "exitcode", "flame", "flamer", - "getrandom 0.3.3", + "getrandom 0.3.4", "glob", "half", "hex", @@ -2779,19 +2918,19 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -2825,9 +2964,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" dependencies = [ "proc-macro2", "quote", @@ -3301,15 +3440,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -3436,9 +3566,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "winapi" @@ -3757,9 +3887,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "xml" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e6e0a83ae73d886ab66fc2f82b598fbbb8f373357d5f2f9f783e50e4d06435" +checksum = "58a4274c410d957424a1502b21126915b45d9956b2f80a88d4f6f906af29facc" [[package]] name = "xz2" diff --git a/Cargo.toml b/Cargo.toml index ab055f6a36d..3cdc471dc3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,10 +158,10 @@ rustpython-sre_engine = { path = "vm/sre_engine", version = "0.4.0" } rustpython-wtf8 = { path = "wtf8", version = "0.4.0" } rustpython-doc = { git = "https://github.com/RustPython/__doc__", tag = "0.3.0", version = "0.3.0" } -ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } -ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } -ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } -ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } +ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } +ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } +ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } +ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ahash = "0.8.12" ascii = "1.1" diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index cc9f066b141..229f16393fe 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -1826,7 +1826,6 @@ def test_gh129093(self): self.assertEqual(f'{f'{1!=2=}'=}', "f'{1!=2=}'='1!=2=True'") self.assertEqual(f'{f'{1 != 2=}'=}', "f'{1 != 2=}'='1 != 2=True'") - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "f-string: newlines are not allowed in format specifiers" does not match "'unexpected EOF while parsing' (<string>, line 2)" def test_newlines_in_format_specifiers(self): cases = [ """f'{1:d\n}'""", diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 7e46773047e..29dd04995ec 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -259,6 +259,36 @@ Traceback (most recent call last): SyntaxError: invalid syntax +Comprehensions without 'in' keyword: + +>>> [x for x if range(1)] +Traceback (most recent call last): +SyntaxError: 'in' expected after for-loop variables + +>>> tuple(x for x if range(1)) +Traceback (most recent call last): +SyntaxError: 'in' expected after for-loop variables + +>>> [x for x() in a] +Traceback (most recent call last): +SyntaxError: cannot assign to function call + +>>> [x for a, b, (c + 1, d()) in y] +Traceback (most recent call last): +SyntaxError: cannot assign to expression + +>>> [x for a, b, (c + 1, d()) if y] +Traceback (most recent call last): +SyntaxError: 'in' expected after for-loop variables + +>>> [x for x+1 in y] +Traceback (most recent call last): +SyntaxError: cannot assign to expression + +>>> [x for x+1, x() in y] +Traceback (most recent call last): +SyntaxError: cannot assign to expression + Comprehensions creating tuples without parentheses should produce a specialized error message: @@ -322,6 +352,13 @@ Traceback (most recent call last): SyntaxError: invalid syntax +# But prefixes of soft keywords should +# still raise specialized errors + +>>> (mat x) +Traceback (most recent call last): +SyntaxError: invalid syntax. Perhaps you forgot a comma? + From compiler_complex_args(): >>> def f(None=1): @@ -334,7 +371,12 @@ >>> def f(x, y=1, z): ... pass Traceback (most recent call last): -SyntaxError: non-default argument follows default argument +SyntaxError: parameter without a default follows parameter with a default + +>>> def f(x, /, y=1, z): +... pass +Traceback (most recent call last): +SyntaxError: parameter without a default follows parameter with a default >>> def f(x, None): ... pass @@ -555,8 +597,16 @@ Traceback (most recent call last): SyntaxError: expected default value expression -# TODO: RUSTPYTHON NameError: name 'PyCF_TYPE_COMMENTS' is not defined ->>> import ast; ast.parse(''' # doctest: +SKIP +>>> lambda a,d=3,c: None +Traceback (most recent call last): +SyntaxError: parameter without a default follows parameter with a default + +>>> lambda a,/,d=3,c: None +Traceback (most recent call last): +SyntaxError: parameter without a default follows parameter with a default + +>>> # TODO: RUSTPYTHON +>>> import ast; ast.parse(''' # doctest: +SKIP ... def f( ... *, # type: int ... a, # type: int @@ -582,46 +632,31 @@ >>> L = range(10) >>> f(x for x in L) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - -# TODO: RUSTPYTHON does not raise. ->>> f(x for x in L, 1) # doctest: +SKIP +>>> f(x for x in L, 1) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized - -# TODO: RUSTPYTHON does not raise. ->>> f(x for x in L, y=1) # doctest: +SKIP +>>> f(x for x in L, y=1) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized - -# TODO: RUSTPYTHON does not raise. ->>> f(x for x in L, *[]) # doctest: +SKIP +>>> f(x for x in L, *[]) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized - -# TODO: RUSTPYTHON does not raise. ->>> f(x for x in L, **{}) # doctest: +SKIP +>>> f(x for x in L, **{}) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized - -# TODO: RUSTPYTHON does not raise. ->>> f(L, x for x in L) # doctest: +SKIP +>>> f(L, x for x in L) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized - -# TODO: RUSTPYTHON does not raise. ->>> f(x for x in L, y for y in L) # doctest: +SKIP +>>> f(x for x in L, y for y in L) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized - -# TODO: RUSTPYTHON does not raise. ->>> f(x for x in L,) # doctest: +SKIP +>>> f(x for x in L,) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized >>> f((x for x in L), 1) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - -# TODO: RUSTPYTHON TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases ->>> class C(x for x in L): # doctest: +SKIP +>>> # TODO: RUSTPYTHON +>>> class C(x for x in L): # doctest: +SKIP ... pass Traceback (most recent call last): SyntaxError: invalid syntax @@ -742,8 +777,8 @@ >>> f(x.y=1) Traceback (most recent call last): SyntaxError: expression cannot contain assignment, perhaps you meant "=="? -# TODO: RUSTPYTHON ->>> f((x)=2) # doctest: +SKIP +>>> # TODO: RUSTPYTHON +>>> f((x)=2) # doctest: +SKIP Traceback (most recent call last): SyntaxError: expression cannot contain assignment, perhaps you meant "=="? >>> f(True=1) @@ -758,11 +793,31 @@ >>> f(__debug__=1) Traceback (most recent call last): SyntaxError: cannot assign to __debug__ - -# TODO: RUSTPYTHON NameError: name '__annotations__' is not defined ->>> __debug__: int # doctest: +SKIP +>>> # TODO: RUSTPYTHON +>>> __debug__: int # doctest: +SKIP Traceback (most recent call last): SyntaxError: cannot assign to __debug__ +>>> f(a=) +Traceback (most recent call last): +SyntaxError: expected argument value expression +>>> f(a, b, c=) +Traceback (most recent call last): +SyntaxError: expected argument value expression +>>> f(a, b, c=, d) +Traceback (most recent call last): +SyntaxError: expected argument value expression +>>> f(*args=[0]) +Traceback (most recent call last): +SyntaxError: cannot assign to iterable argument unpacking +>>> f(a, b, *args=[0]) +Traceback (most recent call last): +SyntaxError: cannot assign to iterable argument unpacking +>>> f(**kwargs={'a': 1}) +Traceback (most recent call last): +SyntaxError: cannot assign to keyword argument unpacking +>>> f(a, b, *args, **kwargs={'a': 1}) +Traceback (most recent call last): +SyntaxError: cannot assign to keyword argument unpacking More set_context(): @@ -990,11 +1045,26 @@ Traceback (most recent call last): SyntaxError: expected ':' + >>> def f[T]() + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + >>> class A ... pass Traceback (most recent call last): SyntaxError: expected ':' + >>> class A[T] + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> class A[T]() + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + >>> class R&D: ... pass Traceback (most recent call last): @@ -1154,6 +1224,22 @@ Traceback (most recent call last): SyntaxError: expected '(' + >>> def f -> int: + Traceback (most recent call last): + SyntaxError: expected '(' + + >>> async def f -> int: # type: int + Traceback (most recent call last): + SyntaxError: expected '(' + + >>> async def f[T]: + Traceback (most recent call last): + SyntaxError: expected '(' + + >>> def f[T] -> str: + Traceback (most recent call last): + SyntaxError: expected '(' + Parenthesized arguments in function definitions >>> def f(x, (y, z), w): @@ -1432,11 +1518,21 @@ Traceback (most recent call last): IndentationError: expected an indented block after function definition on line 1 + >>> def foo[T](x, /, y, *, z=2): + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after function definition on line 1 + >>> class Blech(A): ... pass Traceback (most recent call last): IndentationError: expected an indented block after class definition on line 1 + >>> class Blech[T](A): + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after class definition on line 1 + >>> match something: ... pass Traceback (most recent call last): @@ -1469,14 +1565,16 @@ Check that an multiple exception types with missing parentheses raise a custom exception - >>> try: + >>> # TODO: RUSTPYTHON + >>> try: # doctest: +SKIP ... pass ... except A, B: ... pass Traceback (most recent call last): SyntaxError: multiple exception types must be parenthesized - >>> try: + >>> # TODO: RUSTPYTHON + >>> try: # doctest: +SKIP ... pass ... except A, B, C: ... pass @@ -1591,30 +1689,113 @@ Traceback (most recent call last): SyntaxError: trailing comma not allowed without surrounding parentheses +>>> import a from b +Traceback (most recent call last): +SyntaxError: Did you mean to use 'from ... import ...' instead? + +>>> import a.y.z from b.y.z +Traceback (most recent call last): +SyntaxError: Did you mean to use 'from ... import ...' instead? + +>>> import a from b as bar +Traceback (most recent call last): +SyntaxError: Did you mean to use 'from ... import ...' instead? + +>>> import a.y.z from b.y.z as bar +Traceback (most recent call last): +SyntaxError: Did you mean to use 'from ... import ...' instead? + +>>> import a, b,c from b +Traceback (most recent call last): +SyntaxError: Did you mean to use 'from ... import ...' instead? + +>>> import a.y.z, b.y.z, c.y.z from b.y.z +Traceback (most recent call last): +SyntaxError: Did you mean to use 'from ... import ...' instead? + +>>> import a,b,c from b as bar +Traceback (most recent call last): +SyntaxError: Did you mean to use 'from ... import ...' instead? + +>>> import a.y.z, b.y.z, c.y.z from b.y.z as bar +Traceback (most recent call last): +SyntaxError: Did you mean to use 'from ... import ...' instead? + # Check that we dont raise the "trailing comma" error if there is more # input to the left of the valid part that we parsed. ->>> from t import x,y, and 3 +>>> from t import x,y, and 3 Traceback (most recent call last): SyntaxError: invalid syntax -# TODO: RUSTPYTHON nothing raised. ->>> (): int # doctest: +SKIP +>>> from i import +Traceback (most recent call last): +SyntaxError: Expected one or more names after 'import' + +>>> from .. import +Traceback (most recent call last): +SyntaxError: Expected one or more names after 'import' + +>>> import +Traceback (most recent call last): +SyntaxError: Expected one or more names after 'import' + +>>> (): int Traceback (most recent call last): SyntaxError: only single target (not tuple) can be annotated -# TODO: RUSTPYTHON nothing raised. ->>> []: int # doctest: +SKIP +>>> []: int Traceback (most recent call last): SyntaxError: only single target (not list) can be annotated -# TODO: RUSTPYTHON nothing raised. ->>> (()): int # doctest: +SKIP +>>> (()): int Traceback (most recent call last): SyntaxError: only single target (not tuple) can be annotated -# TODO: RUSTPYTHON nothing raised. ->>> ([]): int # doctest: +SKIP +>>> ([]): int Traceback (most recent call last): SyntaxError: only single target (not list) can be annotated +# 'not' after operators: + +>>> 3 + not 3 +Traceback (most recent call last): +SyntaxError: 'not' after an operator must be parenthesized + +>>> 3 * not 3 +Traceback (most recent call last): +SyntaxError: 'not' after an operator must be parenthesized + +>>> + not 3 +Traceback (most recent call last): +SyntaxError: 'not' after an operator must be parenthesized + +>>> - not 3 +Traceback (most recent call last): +SyntaxError: 'not' after an operator must be parenthesized + +>>> ~ not 3 +Traceback (most recent call last): +SyntaxError: 'not' after an operator must be parenthesized + +>>> 3 + - not 3 +Traceback (most recent call last): +SyntaxError: 'not' after an operator must be parenthesized + +>>> 3 + not -1 +Traceback (most recent call last): +SyntaxError: 'not' after an operator must be parenthesized + +# Check that we don't introduce misleading errors +>>> not 1 */ 2 +Traceback (most recent call last): +SyntaxError: invalid syntax + +>>> not 1 + +Traceback (most recent call last): +SyntaxError: invalid syntax + +>>> not + 1 + +Traceback (most recent call last): +SyntaxError: invalid syntax + Corner-cases that used to fail to raise the correct error: >>> def f(*, x=lambda __debug__:0): pass @@ -1635,8 +1816,7 @@ Corner-cases that used to crash: - # TODO: RUSTPYTHON nothing raised. - >>> def f(**__debug__): pass # doctest: +SKIP + >>> def f(**__debug__): pass Traceback (most recent call last): SyntaxError: cannot assign to __debug__ @@ -1650,8 +1830,8 @@ Invalid pattern matching constructs: - # TODO: RUSTPYTHON nothing raised. - >>> match ...: # doctest: +SKIP + >>> # TODO: RUSTPYTHON + >>> match ...: # doctest: +SKIP ... case 42 as _: ... ... Traceback (most recent call last): @@ -1751,22 +1931,22 @@ >>> A[*(1:2)] Traceback (most recent call last): ... - SyntaxError: invalid syntax + SyntaxError: Invalid star expression >>> A[*(1:2)] = 1 Traceback (most recent call last): ... - SyntaxError: invalid syntax + SyntaxError: Invalid star expression >>> del A[*(1:2)] Traceback (most recent call last): ... - SyntaxError: invalid syntax + SyntaxError: Invalid star expression A[*:] and A[:*] >>> A[*:] Traceback (most recent call last): ... - SyntaxError: invalid syntax + SyntaxError: Invalid star expression >>> A[:*] Traceback (most recent call last): ... @@ -1777,7 +1957,7 @@ >>> A[*] Traceback (most recent call last): ... - SyntaxError: invalid syntax + SyntaxError: Invalid star expression A[**] @@ -1829,10 +2009,246 @@ def f(x: *b) Traceback (most recent call last): ... SyntaxError: invalid syntax + +Invalid bytes literals: + + >>> b"Ā" + Traceback (most recent call last): + ... + b"Ā" + ^^^ + SyntaxError: bytes can only contain ASCII literal characters + + >>> b"абвгде" + Traceback (most recent call last): + ... + b"абвгде" + ^^^^^^^^ + SyntaxError: bytes can only contain ASCII literal characters + + >>> b"abc ъющый" # first 3 letters are ascii + Traceback (most recent call last): + ... + b"abc ъющый" + ^^^^^^^^^^^ + SyntaxError: bytes can only contain ASCII literal characters + +Invalid expressions in type scopes: + + >>> type A[] = int + Traceback (most recent call last): + ... + SyntaxError: Type parameter list cannot be empty + + >>> class A[]: ... + Traceback (most recent call last): + ... + SyntaxError: Type parameter list cannot be empty + + >>> def some[](): ... + Traceback (most recent call last): + ... + SyntaxError: Type parameter list cannot be empty + + >>> def some[]() + Traceback (most recent call last): + ... + SyntaxError: Type parameter list cannot be empty + + >>> async def some[]: # type: int + Traceback (most recent call last): + ... + SyntaxError: Type parameter list cannot be empty + + >>> def f[T: (x:=3)](): pass + Traceback (most recent call last): + ... + SyntaxError: named expression cannot be used within a TypeVar bound + + >>> def f[T: ((x:= 3), int)](): pass + Traceback (most recent call last): + ... + SyntaxError: named expression cannot be used within a TypeVar constraint + + >>> def f[T = ((x:=3))](): pass + Traceback (most recent call last): + ... + SyntaxError: named expression cannot be used within a TypeVar default + + >>> async def f[T: (x:=3)](): pass + Traceback (most recent call last): + ... + SyntaxError: named expression cannot be used within a TypeVar bound + + >>> async def f[T: ((x:= 3), int)](): pass + Traceback (most recent call last): + ... + SyntaxError: named expression cannot be used within a TypeVar constraint + + >>> async def f[T = ((x:=3))](): pass + Traceback (most recent call last): + ... + SyntaxError: named expression cannot be used within a TypeVar default + + >>> type A[T: (x:=3)] = int + Traceback (most recent call last): + ... + SyntaxError: named expression cannot be used within a TypeVar bound + + >>> type A[T: ((x:= 3), int)] = int + Traceback (most recent call last): + ... + SyntaxError: named expression cannot be used within a TypeVar constraint + + >>> type A[T = ((x:=3))] = int + Traceback (most recent call last): + ... + SyntaxError: named expression cannot be used within a TypeVar default + + >>> def f[T: (yield)](): pass + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVar bound + + >>> def f[T: (int, (yield))](): pass + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVar constraint + + >>> def f[T = (yield)](): pass + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVar default + + >>> def f[*Ts = (yield)](): pass + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVarTuple default + + >>> def f[**P = [(yield), int]](): pass + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a ParamSpec default + + >>> type A[T: (yield 3)] = int + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVar bound + + >>> type A[T: (int, (yield 3))] = int + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVar constraint + + >>> type A[T = (yield 3)] = int + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVar default + + >>> type A[T: (await 3)] = int + Traceback (most recent call last): + ... + SyntaxError: await expression cannot be used within a TypeVar bound + + >>> type A[T: (yield from [])] = int + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVar bound + + >>> class A[T: (yield 3)]: pass + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVar bound + + >>> class A[T: (int, (yield 3))]: pass + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVar constraint + + >>> class A[T = (yield)]: pass + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVar default + + >>> class A[*Ts = (yield)]: pass + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a TypeVarTuple default + + >>> class A[**P = [(yield), int]]: pass + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a ParamSpec default + + >>> # TODO: RUSTPYTHON + >>> type A = (x := 3) # doctest: +SKIP + Traceback (most recent call last): + ... + SyntaxError: named expression cannot be used within a type alias + + >>> type A = (yield 3) + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a type alias + + >>> type A = (await 3) + Traceback (most recent call last): + ... + SyntaxError: await expression cannot be used within a type alias + + >>> type A = (yield from []) + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within a type alias + + >>> class A[T]((x := 3)): ... + Traceback (most recent call last): + ... + SyntaxError: named expression cannot be used within the definition of a generic + + >>> class A[T]((yield 3)): ... + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within the definition of a generic + + >>> class A[T]((await 3)): ... + Traceback (most recent call last): + ... + SyntaxError: await expression cannot be used within the definition of a generic + + >>> class A[T]((yield from [])): ... + Traceback (most recent call last): + ... + SyntaxError: yield expression cannot be used within the definition of a generic + + >>> f(**x, *y) + Traceback (most recent call last): + SyntaxError: iterable argument unpacking follows keyword argument unpacking + + >>> f(**x, *) + Traceback (most recent call last): + SyntaxError: Invalid star expression + + >>> f(x, *:) + Traceback (most recent call last): + SyntaxError: Invalid star expression + + >>> f(x, *) + Traceback (most recent call last): + SyntaxError: Invalid star expression + + >>> f(x = 5, *) + Traceback (most recent call last): + SyntaxError: Invalid star expression + + >>> f(x = 5, *:) + Traceback (most recent call last): + SyntaxError: Invalid star expression """ import re import doctest +import textwrap import unittest from test import support @@ -1869,8 +2285,7 @@ def _check_error(self, code, errtext, else: self.fail("compile() did not raise SyntaxError") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_expression_with_assignment(self): self._check_error( "print(end1 + end2 = ' ')", @@ -1878,16 +2293,14 @@ def test_expression_with_assignment(self): offset=7 ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_curly_brace_after_primary_raises_immediately(self): self._check_error("f{}", "invalid syntax", mode="single") def test_assign_call(self): self._check_error("f() = 1", "assign") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_assign_del(self): self._check_error("del (,)", "invalid syntax") self._check_error("del 1", "cannot delete literal") @@ -1939,9 +2352,6 @@ def error2(): """ self._check_error(source, "parameter and nonlocal", lineno=3) - def test_break_outside_loop(self): - self._check_error("break", "outside loop") - def test_yield_outside_function(self): self._check_error("if 0: yield", "outside function") self._check_error("if 0: yield\nelse: x=1", "outside function") @@ -1970,22 +2380,28 @@ def test_return_outside_function(self): "outside function") def test_break_outside_loop(self): - self._check_error("if 0: break", "outside loop") - self._check_error("if 0: break\nelse: x=1", "outside loop") - self._check_error("if 1: pass\nelse: break", "outside loop") - self._check_error("class C:\n if 0: break", "outside loop") + msg = "outside loop" + self._check_error("break", msg, lineno=1) + self._check_error("if 0: break", msg, lineno=1) + self._check_error("if 0: break\nelse: x=1", msg, lineno=1) + self._check_error("if 1: pass\nelse: break", msg, lineno=2) + self._check_error("class C:\n if 0: break", msg, lineno=2) self._check_error("class C:\n if 1: pass\n else: break", - "outside loop") + msg, lineno=3) + self._check_error("with object() as obj:\n break", + msg, lineno=2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_continue_outside_loop(self): - self._check_error("if 0: continue", "not properly in loop") - self._check_error("if 0: continue\nelse: x=1", "not properly in loop") - self._check_error("if 1: pass\nelse: continue", "not properly in loop") - self._check_error("class C:\n if 0: continue", "not properly in loop") + msg = "not properly in loop" + self._check_error("if 0: continue", msg, lineno=1) + self._check_error("if 0: continue\nelse: x=1", msg, lineno=1) + self._check_error("if 1: pass\nelse: continue", msg, lineno=2) + self._check_error("class C:\n if 0: continue", msg, lineno=2) self._check_error("class C:\n if 1: pass\n else: continue", - "not properly in loop") + msg, lineno=3) + self._check_error("with object() as obj:\n continue", + msg, lineno=2) def test_unexpected_indent(self): self._check_error("foo()\n bar()\n", "unexpected indent", @@ -2000,35 +2416,42 @@ def test_bad_outdent(self): "unindent does not match .* level", subclass=IndentationError) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_kwargs_last(self): self._check_error("int(base=10, '2')", "positional argument follows keyword argument") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_kwargs_last2(self): self._check_error("int(**{'base': 10}, '2')", "positional argument follows " "keyword argument unpacking") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_kwargs_last3(self): self._check_error("int(**{'base': 10}, *['2'])", "iterable argument unpacking follows " "keyword argument unpacking") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_generator_in_function_call(self): self._check_error("foo(x, y for y in range(3) for z in range(2) if z , p)", "Generator expression must be parenthesized", lineno=1, end_lineno=1, offset=11, end_offset=53) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_except_then_except_star(self): + self._check_error("try: pass\nexcept ValueError: pass\nexcept* TypeError: pass", + r"cannot have both 'except' and 'except\*' on the same 'try'", + lineno=3, end_lineno=3, offset=1, end_offset=8) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_except_star_then_except(self): + self._check_error("try: pass\nexcept* ValueError: pass\nexcept TypeError: pass", + r"cannot have both 'except' and 'except\*' on the same 'try'", + lineno=3, end_lineno=3, offset=1, end_offset=7) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_empty_line_after_linecont(self): # See issue-40847 s = r"""\ @@ -2073,6 +2496,25 @@ def test_continuation_bad_indentation(self): self.assertRaises(IndentationError, exec, code) + @support.cpython_only + def test_disallowed_type_param_names(self): + # See gh-128632 + + self._check_error(f"class A[__classdict__]: pass", + f"reserved name '__classdict__' cannot be used for type parameter") + self._check_error(f"def f[__classdict__](): pass", + f"reserved name '__classdict__' cannot be used for type parameter") + self._check_error(f"type T[__classdict__] = tuple[__classdict__]", + f"reserved name '__classdict__' cannot be used for type parameter") + + # These compilations are here to make sure __class__, __classcell__ and __classdictcell__ + # don't break in the future like __classdict__ did in this case. + for name in ('__class__', '__classcell__', '__classdictcell__'): + compile(f""" +class A: + class B[{name}]: pass + """, "<testcase>", mode="exec") + @support.cpython_only def test_nested_named_except_blocks(self): code = "" @@ -2083,6 +2525,58 @@ def test_nested_named_except_blocks(self): code += f"{' '*4*12}pass" self._check_error(code, "too many statically nested blocks") + @support.cpython_only + def test_with_statement_many_context_managers(self): + # See gh-113297 + + def get_code(n): + code = textwrap.dedent(""" + def bug(): + with ( + a + """) + for i in range(n): + code += f" as a{i}, a\n" + code += "): yield a" + return code + + CO_MAXBLOCKS = 21 # static nesting limit of the compiler + MAX_MANAGERS = CO_MAXBLOCKS - 1 # One for the StopIteration block + + for n in range(MAX_MANAGERS): + with self.subTest(f"within range: {n=}"): + compile(get_code(n), "<string>", "exec") + + for n in range(MAX_MANAGERS, MAX_MANAGERS + 5): + with self.subTest(f"out of range: {n=}"): + self._check_error(get_code(n), "too many statically nested blocks") + + @support.cpython_only + def test_async_with_statement_many_context_managers(self): + # See gh-116767 + + def get_code(n): + code = [ textwrap.dedent(""" + async def bug(): + async with ( + a + """) ] + for i in range(n): + code.append(f" as a{i}, a\n") + code.append("): yield a") + return "".join(code) + + CO_MAXBLOCKS = 21 # static nesting limit of the compiler + MAX_MANAGERS = CO_MAXBLOCKS - 1 # One for the StopIteration block + + for n in range(MAX_MANAGERS): + with self.subTest(f"within range: {n=}"): + compile(get_code(n), "<string>", "exec") + + for n in range(MAX_MANAGERS, MAX_MANAGERS + 5): + with self.subTest(f"out of range: {n=}"): + self._check_error(get_code(n), "too many statically nested blocks") + def test_barry_as_flufl_with_syntax_errors(self): # The "barry_as_flufl" rule can produce some "bugs-at-a-distance" if # is reading the wrong token in the presence of syntax errors later @@ -2100,8 +2594,7 @@ def func2(): """ self._check_error(code, "expected ':'") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_line_continuation_error_position(self): self._check_error(r"a = 3 \ 4", "unexpected character after line continuation character", @@ -2113,8 +2606,7 @@ def test_invalid_line_continuation_error_position(self): "unexpected character after line continuation character", lineno=3, offset=4) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_line_continuation_left_recursive(self): # Check bpo-42218: SyntaxErrors following left-recursive rules # (t_primary_raw in this case) need to be tested explicitly @@ -2123,8 +2615,7 @@ def test_invalid_line_continuation_left_recursive(self): self._check_error("A.\u03bc\\\n", "unexpected EOF while parsing") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_error_parenthesis(self): for paren in "([{": self._check_error(paren + "1 + 2", f"\\{paren}' was never closed") @@ -2135,10 +2626,39 @@ def test_error_parenthesis(self): for paren in ")]}": self._check_error(paren + "1 + 2", f"unmatched '\\{paren}'") - # TODO: RUSTPYTHON - @unittest.expectedFailure + # Some more complex examples: + code = """\ +func( + a=["unclosed], # Need a quote in this comment: " + b=2, +) +""" + self._check_error(code, "parenthesis '\\)' does not match opening parenthesis '\\['") + + self._check_error("match y:\n case e(e=v,v,", " was never closed") + + # Examples with dencodings + s = b'# coding=latin\n(aaaaaaaaaaaaaaaaa\naaaaaaaaaaa\xb5' + self._check_error(s, r"'\(' was never closed") + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_error_string_literal(self): + + self._check_error("'blech", r"unterminated string literal \(.*\)$") + self._check_error('"blech', r"unterminated string literal \(.*\)$") + self._check_error( + r'"blech\"', r"unterminated string literal \(.*\); perhaps you escaped the end quote" + ) + self._check_error( + r'r"blech\"', r"unterminated string literal \(.*\); perhaps you escaped the end quote" + ) + self._check_error("'''blech", "unterminated triple-quoted string literal") + self._check_error('"""blech', "unterminated triple-quoted string literal") + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invisible_characters(self): self._check_error('print\x17("Hello")', "invalid non-printable character") + self._check_error(b"with(0,,):\n\x01", "invalid non-printable character") def test_match_call_does_not_raise_syntax_error(self): code = """ @@ -2158,8 +2678,7 @@ def case(x): """ compile(code, "<string>", "exec") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_multiline_compiler_error_points_to_the_end(self): self._check_error( "call(\na=1,\na=1\n)", @@ -2198,7 +2717,8 @@ def test_syntax_error_on_deeply_nested_blocks(self): while 20: while 21: while 22: - break + while 23: + break """ self._check_error(source, "too many statically nested blocks") @@ -2207,7 +2727,7 @@ def test_error_on_parser_stack_overflow(self): source = "-" * 100000 + "4" for mode in ["exec", "eval", "single"]: with self.subTest(mode=mode): - with self.assertRaises(MemoryError): + with self.assertRaisesRegex(MemoryError, r"too complex"): compile(source, "<string>", mode) @support.cpython_only diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index c509052a56a..5f8caebf272 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -24,23 +24,23 @@ use ruff_python_ast::{ Alias, Arguments, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText, Decorator, DictItem, ExceptHandler, ExceptHandlerExceptHandler, Expr, ExprAttribute, ExprBoolOp, ExprContext, ExprFString, ExprList, ExprName, ExprSlice, ExprStarred, ExprSubscript, ExprTuple, ExprUnaryOp, - FString, FStringElement, FStringElements, FStringFlags, FStringPart, Identifier, Int, Keyword, - MatchCase, ModExpression, ModModule, Operator, Parameters, Pattern, PatternMatchAs, - PatternMatchClass, PatternMatchMapping, PatternMatchOr, PatternMatchSequence, - PatternMatchSingleton, PatternMatchStar, PatternMatchValue, Singleton, Stmt, StmtExpr, - TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, - WithItem, + FString, FStringFlags, FStringPart, Identifier, Int, InterpolatedElement, + InterpolatedStringElement, InterpolatedStringElements, Keyword, MatchCase, ModExpression, + ModModule, Operator, Parameters, Pattern, PatternMatchAs, PatternMatchClass, + PatternMatchMapping, PatternMatchOr, PatternMatchSequence, PatternMatchSingleton, + PatternMatchStar, PatternMatchValue, Singleton, Stmt, StmtExpr, TypeParam, TypeParamParamSpec, + TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem, }; use ruff_text_size::{Ranged, TextRange}; use rustpython_compiler_core::{ - Mode, OneIndexed, SourceFile, SourceLocation, + Mode, OneIndexed, PositionEncoding, SourceFile, SourceLocation, bytecode::{ self, Arg as OpArgMarker, BinaryOperator, CodeObject, ComparisonOperator, ConstantData, Instruction, OpArg, OpArgType, UnpackExArgs, }, }; use rustpython_wtf8::Wtf8Buf; -use std::borrow::Cow; +use std::{borrow::Cow, collections::HashSet}; const MAXBLOCKS: usize = 20; @@ -147,6 +147,20 @@ enum ComprehensionType { Dict, } +fn validate_duplicate_params(params: &Parameters) -> Result<(), CodegenErrorType> { + let mut seen_params = HashSet::new(); + for param in params { + let param_name = param.name().as_str(); + if !seen_params.insert(param_name) { + return Err(CodegenErrorType::SyntaxError(format!( + r#"Duplicate parameter "{param_name}""# + ))); + } + } + + Ok(()) +} + /// Compile an Mod produced from ruff parser pub fn compile_top( ast: ruff_python_ast::Mod, @@ -240,18 +254,18 @@ fn eprint_location(zelf: &Compiler) { let start = zelf .source_file .to_source_code() - .source_location(zelf.current_source_range.start()); + .source_location(zelf.current_source_range.start(), PositionEncoding::Utf8); let end = zelf .source_file .to_source_code() - .source_location(zelf.current_source_range.end()); + .source_location(zelf.current_source_range.end(), PositionEncoding::Utf8); eprintln!( "LOCATION: {} from {}:{} to {}:{}", zelf.source_file.name(), - start.row, - start.column, - end.row, - end.column + start.line, + start.character_offset, + end.line, + end.character_offset ); } @@ -531,7 +545,7 @@ impl Compiler { let location = self .source_file .to_source_code() - .source_location(range.start()); + .source_location(range.start(), PositionEncoding::Utf8); CodegenError { error, location: Some(location), @@ -631,8 +645,8 @@ impl Compiler { ) -> CompileResult<()> { // Create location let location = SourceLocation { - row: OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN), - column: OneIndexed::new(1).unwrap(), + line: OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN), + character_offset: OneIndexed::MIN, }; // Allocate a new compiler unit @@ -769,8 +783,8 @@ impl Compiler { let _resume_loc = if scope_type == CompilerScope::Module { // Module scope starts with lineno 0 SourceLocation { - row: OneIndexed::MIN, - column: OneIndexed::MIN, + line: OneIndexed::MIN, + character_offset: OneIndexed::MIN, } } else { location @@ -1500,15 +1514,19 @@ impl Compiler { type_params, is_async, .. - }) => self.compile_function_def( - name.as_str(), - parameters, - body, - decorator_list, - returns.as_deref(), - *is_async, - type_params.as_deref(), - )?, + }) => { + validate_duplicate_params(parameters).map_err(|e| self.error(e))?; + + self.compile_function_def( + name.as_str(), + parameters, + body, + decorator_list, + returns.as_deref(), + *is_async, + type_params.as_deref(), + )? + } Stmt::ClassDef(StmtClassDef { name, body, @@ -3579,7 +3597,7 @@ impl Compiler { // Step 2: If we have keys to match if size > 0 { // Validate and compile keys - let mut seen = std::collections::HashSet::new(); + let mut seen = HashSet::new(); for key in keys { let is_attribute = matches!(key, Expr::Attribute(_)); let is_literal = matches!( @@ -4656,10 +4674,12 @@ impl Compiler { Expr::Lambda(ExprLambda { parameters, body, .. }) => { - let prev_ctx = self.ctx; - let name = "<lambda>".to_owned(); let default_params = Parameters::default(); let params = parameters.as_deref().unwrap_or(&default_params); + validate_duplicate_params(params).map_err(|e| self.error(e))?; + + let prev_ctx = self.ctx; + let name = "<lambda>".to_owned(); // Prepare defaults before entering function let defaults: Vec<_> = std::iter::empty() @@ -4872,6 +4892,7 @@ impl Compiler { Expr::Named(ExprNamed { target, value, + node_index: _, range: _, }) => { self.compile_expression(value)?; @@ -4881,6 +4902,9 @@ impl Compiler { Expr::FString(fstring) => { self.compile_expr_fstring(fstring)?; } + Expr::TString(_) => { + return Err(self.error(CodegenErrorType::NotImplementedYet)); + } Expr::StringLiteral(string) => { let value = string.value.to_str(); if value.contains(char::REPLACEMENT_CHARACTER) { @@ -5334,7 +5358,7 @@ impl Compiler { let location = self .source_file .to_source_code() - .source_location(range.start()); + .source_location(range.start(), PositionEncoding::Utf8); // TODO: insert source filename self.current_block().instructions.push(ir::InstructionInfo { instr, @@ -5524,27 +5548,14 @@ impl Compiler { Expr::Named(ExprNamed { target, value, + node_index: _, range: _, }) => Self::contains_await(target) || Self::contains_await(value), - Expr::FString(ExprFString { value, range: _ }) => { - fn expr_element_contains_await<F: Copy + Fn(&Expr) -> bool>( - expr_element: &FStringExpressionElement, - contains_await: F, - ) -> bool { - contains_await(&expr_element.expression) - || expr_element - .format_spec - .iter() - .flat_map(|spec| spec.elements.expressions()) - .any(|element| expr_element_contains_await(element, contains_await)) - } - - value.elements().any(|element| match element { - FStringElement::Expression(expr_element) => { - expr_element_contains_await(expr_element, Self::contains_await) - } - FStringElement::Literal(_) => false, - }) + Expr::FString(fstring) => { + Self::interpolated_string_contains_await(fstring.value.elements()) + } + Expr::TString(tstring) => { + Self::interpolated_string_contains_await(tstring.value.elements()) } Expr::StringLiteral(_) | Expr::BytesLiteral(_) @@ -5556,6 +5567,29 @@ impl Compiler { } } + fn interpolated_string_contains_await<'a>( + mut elements: impl Iterator<Item = &'a InterpolatedStringElement>, + ) -> bool { + fn interpolated_element_contains_await<F: Copy + Fn(&Expr) -> bool>( + expr_element: &InterpolatedElement, + contains_await: F, + ) -> bool { + contains_await(&expr_element.expression) + || expr_element + .format_spec + .iter() + .flat_map(|spec| spec.elements.interpolations()) + .any(|element| interpolated_element_contains_await(element, contains_await)) + } + + elements.any(|element| match element { + InterpolatedStringElement::Interpolation(expr_element) => { + interpolated_element_contains_await(expr_element, Self::contains_await) + } + InterpolatedStringElement::Literal(_) => false, + }) + } + fn compile_expr_fstring(&mut self, fstring: &ExprFString) -> CompileResult<()> { let fstring = &fstring.value; for part in fstring { @@ -5602,13 +5636,13 @@ impl Compiler { fn compile_fstring_elements( &mut self, flags: FStringFlags, - fstring_elements: &FStringElements, + fstring_elements: &InterpolatedStringElements, ) -> CompileResult<()> { let mut element_count = 0; for element in fstring_elements { element_count += 1; match element { - FStringElement::Literal(string) => { + InterpolatedStringElement::Literal(string) => { if string.value.contains(char::REPLACEMENT_CHARACTER) { // might have a surrogate literal; should reparse to be sure let source = self.source_file.slice(string.range); @@ -5625,7 +5659,7 @@ impl Compiler { }); } } - FStringElement::Expression(fstring_expr) => { + InterpolatedStringElement::Interpolation(fstring_expr) => { let mut conversion = fstring_expr.conversion; if let Some(DebugText { leading, trailing }) = &fstring_expr.debug_text { @@ -5858,21 +5892,27 @@ mod ruff_tests { // f'{x}' let expr_x = Expr::Name(ExprName { + node_index: AtomicNodeIndex::NONE, range, id: Name::new("x"), ctx: ExprContext::Load, }); let not_present = &Expr::FString(ExprFString { + node_index: AtomicNodeIndex::NONE, range, value: FStringValue::single(FString { + node_index: AtomicNodeIndex::NONE, range, - elements: vec![FStringElement::Expression(FStringExpressionElement { - range, - expression: Box::new(expr_x), - debug_text: None, - conversion: ConversionFlag::None, - format_spec: None, - })] + elements: vec![InterpolatedStringElement::Interpolation( + InterpolatedElement { + node_index: AtomicNodeIndex::NONE, + range, + expression: Box::new(expr_x), + debug_text: None, + conversion: ConversionFlag::None, + format_spec: None, + }, + )] .into(), flags, }), @@ -5881,24 +5921,31 @@ mod ruff_tests { // f'{await x}' let expr_await_x = Expr::Await(ExprAwait { + node_index: AtomicNodeIndex::NONE, range, value: Box::new(Expr::Name(ExprName { + node_index: AtomicNodeIndex::NONE, range, id: Name::new("x"), ctx: ExprContext::Load, })), }); let present = &Expr::FString(ExprFString { + node_index: AtomicNodeIndex::NONE, range, value: FStringValue::single(FString { + node_index: AtomicNodeIndex::NONE, range, - elements: vec![FStringElement::Expression(FStringExpressionElement { - range, - expression: Box::new(expr_await_x), - debug_text: None, - conversion: ConversionFlag::None, - format_spec: None, - })] + elements: vec![InterpolatedStringElement::Interpolation( + InterpolatedElement { + node_index: AtomicNodeIndex::NONE, + range, + expression: Box::new(expr_await_x), + debug_text: None, + conversion: ConversionFlag::None, + format_spec: None, + }, + )] .into(), flags, }), @@ -5907,39 +5954,51 @@ mod ruff_tests { // f'{x:{await y}}' let expr_x = Expr::Name(ExprName { + node_index: AtomicNodeIndex::NONE, range, id: Name::new("x"), ctx: ExprContext::Load, }); let expr_await_y = Expr::Await(ExprAwait { + node_index: AtomicNodeIndex::NONE, range, value: Box::new(Expr::Name(ExprName { + node_index: AtomicNodeIndex::NONE, range, id: Name::new("y"), ctx: ExprContext::Load, })), }); let present = &Expr::FString(ExprFString { + node_index: AtomicNodeIndex::NONE, range, value: FStringValue::single(FString { + node_index: AtomicNodeIndex::NONE, range, - elements: vec![FStringElement::Expression(FStringExpressionElement { - range, - expression: Box::new(expr_x), - debug_text: None, - conversion: ConversionFlag::None, - format_spec: Some(Box::new(FStringFormatSpec { + elements: vec![InterpolatedStringElement::Interpolation( + InterpolatedElement { + node_index: AtomicNodeIndex::NONE, range, - elements: vec![FStringElement::Expression(FStringExpressionElement { + expression: Box::new(expr_x), + debug_text: None, + conversion: ConversionFlag::None, + format_spec: Some(Box::new(InterpolatedStringFormatSpec { + node_index: AtomicNodeIndex::NONE, range, - expression: Box::new(expr_await_y), - debug_text: None, - conversion: ConversionFlag::None, - format_spec: None, - })] - .into(), - })), - })] + elements: vec![InterpolatedStringElement::Interpolation( + InterpolatedElement { + node_index: AtomicNodeIndex::NONE, + range, + expression: Box::new(expr_await_y), + debug_text: None, + conversion: ConversionFlag::None, + format_spec: None, + }, + )] + .into(), + })), + }, + )] .into(), flags, }), diff --git a/compiler/codegen/src/ir.rs b/compiler/codegen/src/ir.rs index 31c89260913..7cd173c821d 100644 --- a/compiler/codegen/src/ir.rs +++ b/compiler/codegen/src/ir.rs @@ -182,7 +182,7 @@ impl CodeInfo { *arg = new_arg; } let (extras, lo_arg) = arg.split(); - locations.extend(std::iter::repeat_n(info.location.clone(), arg.instr_size())); + locations.extend(std::iter::repeat_n(info.location, arg.instr_size())); instructions.extend( extras .map(|byte| CodeUnit::new(Instruction::ExtendedArg, byte)) @@ -422,8 +422,8 @@ fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8] // Get line and column information // SourceLocation always has row and column (both are OneIndexed) - let line = loc.row.get() as i32; - let col = (loc.column.get() as i32) - 1; // Convert 1-based to 0-based + let line = loc.line.get() as i32; + let col = loc.character_offset.to_zero_indexed() as i32; let line_delta = line - prev_line; diff --git a/compiler/codegen/src/lib.rs b/compiler/codegen/src/lib.rs index 9b444de994d..291b57d7f67 100644 --- a/compiler/codegen/src/lib.rs +++ b/compiler/codegen/src/lib.rs @@ -56,6 +56,7 @@ impl ToPythonName for Expr { Self::Starred { .. } => "starred", Self::Slice { .. } => "slice", Self::FString { .. } => "f-string expression", + Self::TString { .. } => "t-string expression", Self::Name { .. } => "name", Self::Lambda { .. } => "lambda", Self::If { .. } => "conditional expression", diff --git a/compiler/codegen/src/symboltable.rs b/compiler/codegen/src/symboltable.rs index 16e88ad6908..0464e09c138 100644 --- a/compiler/codegen/src/symboltable.rs +++ b/compiler/codegen/src/symboltable.rs @@ -19,7 +19,7 @@ use ruff_python_ast::{ Stmt, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, }; use ruff_text_size::{Ranged, TextRange}; -use rustpython_compiler_core::{SourceFile, SourceLocation}; +use rustpython_compiler_core::{PositionEncoding, SourceFile, SourceLocation}; use std::{borrow::Cow, fmt}; /// Captures all symbols in the current scope, and has a list of sub-scopes in this scope. @@ -793,6 +793,7 @@ impl SymbolTableBuilder { decorator_list, type_params, range, + node_index: _, }) => { if let Some(type_params) = type_params { self.enter_type_param_block( @@ -910,6 +911,7 @@ impl SymbolTableBuilder { value, simple, range, + node_index: _, }) => { // https://github.com/python/cpython/blob/main/Python/symtable.c#L1233 match &**target { @@ -1046,7 +1048,7 @@ impl SymbolTableBuilder { location: Some( self.source_file .to_source_code() - .source_location(expression.range().start()), + .source_location(expression.range().start(), PositionEncoding::Utf8), ), }); } @@ -1089,7 +1091,11 @@ impl SymbolTableBuilder { }) => { self.scan_expression(value, ExpressionContext::Load)?; } - Expr::Dict(ExprDict { items, range: _ }) => { + Expr::Dict(ExprDict { + items, + node_index: _, + range: _, + }) => { for item in items { if let Some(key) = &item.key { self.scan_expression(key, context)?; @@ -1097,15 +1103,27 @@ impl SymbolTableBuilder { self.scan_expression(&item.value, context)?; } } - Expr::Await(ExprAwait { value, range: _ }) => { + Expr::Await(ExprAwait { + value, + node_index: _, + range: _, + }) => { self.scan_expression(value, context)?; } - Expr::Yield(ExprYield { value, range: _ }) => { + Expr::Yield(ExprYield { + value, + node_index: _, + range: _, + }) => { if let Some(expression) = value { self.scan_expression(expression, context)?; } } - Expr::YieldFrom(ExprYieldFrom { value, range: _ }) => { + Expr::YieldFrom(ExprYieldFrom { + value, + node_index: _, + range: _, + }) => { self.scan_expression(value, context)?; } Expr::UnaryOp(ExprUnaryOp { @@ -1127,6 +1145,7 @@ impl SymbolTableBuilder { lower, upper, step, + node_index: _, range: _, }) => { if let Some(lower) = lower { @@ -1151,6 +1170,7 @@ impl SymbolTableBuilder { elt, generators, range, + node_index: _, }) => { self.scan_comprehension("genexpr", elt, None, generators, *range)?; } @@ -1158,6 +1178,7 @@ impl SymbolTableBuilder { elt, generators, range, + node_index: _, }) => { self.scan_comprehension("genexpr", elt, None, generators, *range)?; } @@ -1166,12 +1187,14 @@ impl SymbolTableBuilder { value, generators, range, + node_index: _, }) => { self.scan_comprehension("genexpr", key, Some(value), generators, *range)?; } Expr::Call(ExprCall { func, arguments, + node_index: _, range: _, }) => { match context { @@ -1218,6 +1241,7 @@ impl SymbolTableBuilder { Expr::Lambda(ExprLambda { body, parameters, + node_index: _, range: _, }) => { if let Some(parameters) = parameters { @@ -1244,15 +1268,25 @@ impl SymbolTableBuilder { self.leave_scope(); } Expr::FString(ExprFString { value, .. }) => { - for expr in value.elements().filter_map(|x| x.as_expression()) { + for expr in value.elements().filter_map(|x| x.as_interpolation()) { self.scan_expression(&expr.expression, ExpressionContext::Load)?; if let Some(format_spec) = &expr.format_spec { - for element in format_spec.elements.expressions() { + for element in format_spec.elements.interpolations() { self.scan_expression(&element.expression, ExpressionContext::Load)? } } } } + Expr::TString(tstring) => { + return Err(SymbolTableError { + error: "not yet implemented".into(), + location: Some( + self.source_file + .to_source_code() + .source_location(tstring.range.start(), PositionEncoding::Utf8), + ), + }); + } // Constants Expr::StringLiteral(_) | Expr::BytesLiteral(_) @@ -1265,6 +1299,7 @@ impl SymbolTableBuilder { test, body, orelse, + node_index: _, range: _, }) => { self.scan_expression(test, ExpressionContext::Load)?; @@ -1276,13 +1311,14 @@ impl SymbolTableBuilder { target, value, range, + node_index: _, }) => { // named expressions are not allowed in the definition of // comprehension iterator definitions if let ExpressionContext::IterDefinitionExp = context { return Err(SymbolTableError { error: "assignment expression cannot be used in a comprehension iterable expression".to_string(), - location: Some(self.source_file.to_source_code().source_location(target.range().start())), + location: Some(self.source_file.to_source_code().source_location(target.range().start(), PositionEncoding::Utf8)), }); } @@ -1393,6 +1429,7 @@ impl SymbolTableBuilder { bound, range: type_var_range, default, + node_index: _, }) => { self.register_name(name.as_str(), SymbolUsage::TypeParam, *type_var_range)?; @@ -1416,6 +1453,7 @@ impl SymbolTableBuilder { name, range: param_spec_range, default, + node_index: _, }) => { self.register_name(name, SymbolUsage::TypeParam, *param_spec_range)?; @@ -1429,6 +1467,7 @@ impl SymbolTableBuilder { name, range: type_var_tuple_range, default, + node_index: _, }) => { self.register_name(name, SymbolUsage::TypeParam, *type_var_tuple_range)?; @@ -1565,7 +1604,7 @@ impl SymbolTableBuilder { let location = self .source_file .to_source_code() - .source_location(range.start()); + .source_location(range.start(), PositionEncoding::Utf8); let location = Some(location); let scope_depth = self.tables.len(); let table = self.tables.last_mut().unwrap(); diff --git a/compiler/codegen/src/unparse.rs b/compiler/codegen/src/unparse.rs index f2fb86ed4ef..7f2bcf16b47 100644 --- a/compiler/codegen/src/unparse.rs +++ b/compiler/codegen/src/unparse.rs @@ -85,6 +85,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { Expr::BoolOp(ruff::ExprBoolOp { op, values, + node_index: _, range: _range, }) => { let (op, prec) = op_prec!(bin, op, BoolOp, And("and", AND), Or("or", OR)); @@ -99,6 +100,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { Expr::Named(ruff::ExprNamed { target, value, + node_index: _, range: _range, }) => { group_if!(precedence::TUPLE, { @@ -111,6 +113,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { left, op, right, + node_index: _, range: _range, }) => { let right_associative = matches!(op, Operator::Pow); @@ -141,6 +144,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { Expr::UnaryOp(ruff::ExprUnaryOp { op, operand, + node_index: _, range: _range, }) => { let (op, prec) = op_prec!( @@ -160,6 +164,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { Expr::Lambda(ruff::ExprLambda { parameters, body, + node_index: _, range: _range, }) => { group_if!(precedence::TEST, { @@ -176,6 +181,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { test, body, orelse, + node_index: _, range: _range, }) => { group_if!(precedence::TEST, { @@ -188,6 +194,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { } Expr::Dict(ruff::ExprDict { items, + node_index: _, range: _range, }) => { self.p("{")?; @@ -205,6 +212,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { } Expr::Set(ruff::ExprSet { elts, + node_index: _, range: _range, }) => { self.p("{")?; @@ -218,6 +226,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { Expr::ListComp(ruff::ExprListComp { elt, generators, + node_index: _, range: _range, }) => { self.p("[")?; @@ -228,6 +237,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { Expr::SetComp(ruff::ExprSetComp { elt, generators, + node_index: _, range: _range, }) => { self.p("{")?; @@ -239,6 +249,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { key, value, generators, + node_index: _, range: _range, }) => { self.p("{")?; @@ -252,6 +263,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { parenthesized: _, elt, generators, + node_index: _, range: _range, }) => { self.p("(")?; @@ -261,6 +273,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { } Expr::Await(ruff::ExprAwait { value, + node_index: _, range: _range, }) => { group_if!(precedence::AWAIT, { @@ -270,6 +283,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { } Expr::Yield(ruff::ExprYield { value, + node_index: _, range: _range, }) => { if let Some(value) = value { @@ -280,6 +294,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { } Expr::YieldFrom(ruff::ExprYieldFrom { value, + node_index: _, range: _range, }) => { write!( @@ -292,6 +307,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { left, ops, comparators, + node_index: _, range: _range, }) => { group_if!(precedence::CMP, { @@ -308,6 +324,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { Expr::Call(ruff::ExprCall { func, arguments: Arguments { args, keywords, .. }, + node_index: _, range: _range, }) => { self.unparse_expr(func, precedence::ATOM)?; @@ -317,6 +334,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { Expr::Generator(ruff::ExprGenerator { elt, generators, + node_index: _, range: _range, .. }), @@ -347,6 +365,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { self.p(")")?; } Expr::FString(ruff::ExprFString { value, .. }) => self.unparse_fstring(value)?, + Expr::TString(_) => self.p("t\"\"")?, Expr::StringLiteral(ruff::ExprStringLiteral { value, .. }) => { if value.is_unicode() { self.p("u")? @@ -435,6 +454,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { lower, upper, step, + node_index: _, range: _range, }) => { if let Some(lower) = lower { @@ -513,7 +533,10 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { Ok(()) } - fn unparse_fstring_body(&mut self, elements: &[ruff::FStringElement]) -> fmt::Result { + fn unparse_fstring_body( + &mut self, + elements: &[ruff::InterpolatedStringElement], + ) -> fmt::Result { for elem in elements { self.unparse_fstring_elem(elem)?; } @@ -525,7 +548,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { val: &Expr, debug_text: Option<&ruff::DebugText>, conversion: ConversionFlag, - spec: Option<&ruff::FStringFormatSpec>, + spec: Option<&ruff::InterpolatedStringFormatSpec>, ) -> fmt::Result { let buffered = to_string_fmt(|f| { Unparser::new(f, self.source).unparse_expr(val, precedence::TEST + 1) @@ -562,9 +585,9 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { Ok(()) } - fn unparse_fstring_elem(&mut self, elem: &ruff::FStringElement) -> fmt::Result { + fn unparse_fstring_elem(&mut self, elem: &ruff::InterpolatedStringElement) -> fmt::Result { match elem { - ruff::FStringElement::Expression(ruff::FStringExpressionElement { + ruff::InterpolatedStringElement::Interpolation(ruff::InterpolatedElement { expression, debug_text, conversion, @@ -576,9 +599,10 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { *conversion, format_spec.as_deref(), ), - ruff::FStringElement::Literal(ruff::FStringLiteralElement { value, .. }) => { - self.unparse_fstring_str(value) - } + ruff::InterpolatedStringElement::Literal(ruff::InterpolatedStringLiteralElement { + value, + .. + }) => self.unparse_fstring_str(value), } } diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index 1088dd98487..4ab666da99e 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -1196,14 +1196,14 @@ impl<C: Constant> CodeObject<C> { level: usize, ) -> fmt::Result { let label_targets = self.label_targets(); - let line_digits = (3).max(self.locations.last().unwrap().row.to_string().len()); - let offset_digits = (4).max(self.instructions.len().to_string().len()); + let line_digits = (3).max(self.locations.last().unwrap().line.digits().get()); + let offset_digits = (4).max(1 + self.instructions.len().ilog10() as usize); let mut last_line = OneIndexed::MAX; let mut arg_state = OpArgState::default(); for (offset, &instruction) in self.instructions.iter().enumerate() { let (instruction, arg) = arg_state.get(instruction); // optional line number - let line = self.locations[offset].row; + let line = self.locations[offset].line; if line != last_line { if last_line != OneIndexed::MAX { writeln!(f)?; diff --git a/compiler/core/src/lib.rs b/compiler/core/src/lib.rs index 0ce4a9defb1..08cdc0ec21f 100644 --- a/compiler/core/src/lib.rs +++ b/compiler/core/src/lib.rs @@ -8,4 +8,6 @@ mod mode; pub use mode::Mode; -pub use ruff_source_file::{LineIndex, OneIndexed, SourceFile, SourceFileBuilder, SourceLocation}; +pub use ruff_source_file::{ + LineIndex, OneIndexed, PositionEncoding, SourceFile, SourceFileBuilder, SourceLocation, +}; diff --git a/compiler/core/src/marshal.rs b/compiler/core/src/marshal.rs index b8044a1ab95..5e16e59102a 100644 --- a/compiler/core/src/marshal.rs +++ b/compiler/core/src/marshal.rs @@ -198,8 +198,8 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>( let locations = (0..len) .map(|_| { Ok(SourceLocation { - row: OneIndexed::new(rdr.read_u32()? as _).ok_or(MarshalError::InvalidLocation)?, - column: OneIndexed::from_zero_indexed(rdr.read_u32()? as _), + line: OneIndexed::new(rdr.read_u32()? as _).ok_or(MarshalError::InvalidLocation)?, + character_offset: OneIndexed::from_zero_indexed(rdr.read_u32()? as _), }) }) .collect::<Result<Box<[SourceLocation]>>>()?; @@ -656,8 +656,8 @@ pub fn serialize_code<W: Write, C: Constant>(buf: &mut W, code: &CodeObject<C>) write_len(buf, code.locations.len()); for loc in &*code.locations { - buf.write_u32(loc.row.get() as _); - buf.write_u32(loc.column.to_zero_indexed() as _); + buf.write_u32(loc.line.get() as _); + buf.write_u32(loc.character_offset.to_zero_indexed() as _); } buf.write_u16(code.flags.bits()); diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 111b9aec2e7..84e64f3c27f 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -1,4 +1,4 @@ -use ruff_source_file::{SourceFile, SourceFileBuilder, SourceLocation}; +use ruff_source_file::{PositionEncoding, SourceFile, SourceFileBuilder, SourceLocation}; use rustpython_codegen::{compile, symboltable}; pub use rustpython_codegen::compile::CompileOpts; @@ -46,7 +46,7 @@ impl CompileError { pub fn from_ruff_parse_error(error: parser::ParseError, source_file: &SourceFile) -> Self { let location = source_file .to_source_code() - .source_location(error.location.start()); + .source_location(error.location.start(), PositionEncoding::Utf8); Self::Parse(ParseError { error: error.error, raw_location: error.location, @@ -55,26 +55,18 @@ impl CompileError { }) } - pub fn location(&self) -> Option<SourceLocation> { + pub const fn location(&self) -> Option<SourceLocation> { match self { - Self::Codegen(codegen_error) => codegen_error.location.clone(), - Self::Parse(parse_error) => Some(parse_error.location.clone()), + Self::Codegen(codegen_error) => codegen_error.location, + Self::Parse(parse_error) => Some(parse_error.location), } } pub const fn python_location(&self) -> (usize, usize) { - match self { - Self::Codegen(codegen_error) => { - if let Some(location) = &codegen_error.location { - (location.row.get(), location.column.get()) - } else { - (0, 0) - } - } - Self::Parse(parse_error) => ( - parse_error.location.row.get(), - parse_error.location.column.get(), - ), + if let Some(location) = self.location() { + (location.line.get(), location.character_offset.get()) + } else { + (0, 0) } } diff --git a/src/shell.rs b/src/shell.rs index c0ae508b018..d2e54c490a8 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -1,7 +1,7 @@ mod helper; use rustpython_compiler::{ - CompileError, ParseError, parser::FStringErrorType, parser::LexicalErrorType, + CompileError, ParseError, parser::InterpolatedStringErrorType, parser::LexicalErrorType, parser::ParseErrorType, }; use rustpython_vm::{ @@ -52,7 +52,7 @@ fn shell_exec( Err(CompileError::Parse(ParseError { error: ParseErrorType::Lexical(LexicalErrorType::FStringError( - FStringErrorType::UnterminatedTripleQuotedString, + InterpolatedStringErrorType::UnterminatedTripleQuotedString, )), .. })) => ShellExecResult::ContinueLine, diff --git a/stdlib/src/faulthandler.rs b/stdlib/src/faulthandler.rs index f358129c873..5c9196ad33f 100644 --- a/stdlib/src/faulthandler.rs +++ b/stdlib/src/faulthandler.rs @@ -10,7 +10,7 @@ mod decl { stderr, " File \"{}\", line {} in {}", frame.code.source_path, - frame.current_location().row, + frame.current_location().line, frame.code.obj_name ) } diff --git a/vm/src/builtins/frame.rs b/vm/src/builtins/frame.rs index 65ac3e798d7..17dc88ac042 100644 --- a/vm/src/builtins/frame.rs +++ b/vm/src/builtins/frame.rs @@ -60,7 +60,7 @@ impl Frame { #[pygetset] pub fn f_lineno(&self) -> usize { - self.current_location().row.get() + self.current_location().line.get() } #[pygetset] diff --git a/vm/src/frame.rs b/vm/src/frame.rs index ba9abffc6e7..fa5860244f1 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -171,7 +171,7 @@ impl Frame { } pub fn current_location(&self) -> SourceLocation { - self.code.locations[self.lasti() as usize - 1].clone() + self.code.locations[self.lasti() as usize - 1] } pub fn lasti(&self) -> u32 { @@ -388,11 +388,15 @@ impl ExecutingFrame<'_> { // 2. Add new entry with current execution position (filename, lineno, code_object) to traceback. // 3. Unwind block stack till appropriate handler is found. - let loc = frame.code.locations[idx].clone(); + let loc = frame.code.locations[idx]; let next = exception.__traceback__(); - let new_traceback = - PyTraceback::new(next, frame.object.to_owned(), frame.lasti(), loc.row); - vm_trace!("Adding to traceback: {:?} {:?}", new_traceback, loc.row); + let new_traceback = PyTraceback::new( + next, + frame.object.to_owned(), + frame.lasti(), + loc.line, + ); + vm_trace!("Adding to traceback: {:?} {:?}", new_traceback, loc.line); exception.set_traceback_typed(Some(new_traceback.into_ref(&vm.ctx))); vm.contextualize_exception(&exception); diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 1533f903c5c..00aad0213f3 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -23,7 +23,7 @@ use node::Node; use ruff_python_ast as ruff; use ruff_text_size::{Ranged, TextRange, TextSize}; use rustpython_compiler_core::{ - LineIndex, OneIndexed, SourceFile, SourceFileBuilder, SourceLocation, + LineIndex, OneIndexed, PositionEncoding, SourceFile, SourceFileBuilder, SourceLocation, }; #[cfg(feature = "parser")] @@ -92,8 +92,8 @@ pub struct PySourceLocation { impl PySourceLocation { const fn to_source_location(&self) -> SourceLocation { SourceLocation { - row: self.row.get_one_indexed(), - column: self.column.get_one_indexed(), + line: self.row.get_one_indexed(), + character_offset: self.column.get_one_indexed(), } } } @@ -194,14 +194,14 @@ fn source_range_to_text_range(source_file: &SourceFile, location: PySourceRange) } let start = index.offset( - location.start.row.get_one_indexed(), - location.start.column.get_one_indexed(), + location.start.to_source_location(), source, + PositionEncoding::Utf8, ); let end = index.offset( - location.end.row.get_one_indexed(), - location.end.column.get_one_indexed(), + location.end.to_source_location(), source, + PositionEncoding::Utf8, ); TextRange::new(start, end) @@ -273,9 +273,11 @@ pub(crate) fn compile( let ast: Mod = Node::ast_from_object(vm, &source_file, object)?; let ast = match ast { Mod::Module(m) => ruff::Mod::Module(m), - Mod::Interactive(ModInteractive { range, body }) => { - ruff::Mod::Module(ruff::ModModule { range, body }) - } + Mod::Interactive(ModInteractive { range, body }) => ruff::Mod::Module(ruff::ModModule { + node_index: Default::default(), + range, + body, + }), Mod::Expression(e) => ruff::Mod::Expression(e), Mod::FunctionType(_) => todo!(), }; diff --git a/vm/src/stdlib/ast/argument.rs b/vm/src/stdlib/ast/argument.rs index a1207f2c054..a13200e6502 100644 --- a/vm/src/stdlib/ast/argument.rs +++ b/vm/src/stdlib/ast/argument.rs @@ -57,6 +57,7 @@ pub(super) fn merge_function_call_arguments( let range = pos_args.range.cover(key_args.range); ruff::Arguments { + node_index: Default::default(), range, args: pos_args.args, keywords: key_args.keywords, @@ -67,6 +68,7 @@ pub(super) fn split_function_call_arguments( args: ruff::Arguments, ) -> (PositionalArguments, KeywordArguments) { let ruff::Arguments { + node_index: _, range: _, args, keywords, @@ -105,6 +107,7 @@ pub(super) fn split_class_def_args( Some(args) => *args, }; let ruff::Arguments { + node_index: _, range: _, args, keywords, @@ -155,6 +158,7 @@ pub(super) fn merge_class_def_args( }; Some(Box::new(ruff::Arguments { + node_index: Default::default(), range: Default::default(), // TODO args, keywords, diff --git a/vm/src/stdlib/ast/constant.rs b/vm/src/stdlib/ast/constant.rs index b6c3e9596ba..83b2a7f7015 100644 --- a/vm/src/stdlib/ast/constant.rs +++ b/vm/src/stdlib/ast/constant.rs @@ -247,14 +247,21 @@ impl Node for ConstantLiteral { fn constant_to_ruff_expr(value: Constant) -> ruff::Expr { let Constant { value, range } = value; match value { - ConstantLiteral::None => ruff::Expr::NoneLiteral(ruff::ExprNoneLiteral { range }), - ConstantLiteral::Bool(value) => { - ruff::Expr::BooleanLiteral(ruff::ExprBooleanLiteral { range, value }) - } + ConstantLiteral::None => ruff::Expr::NoneLiteral(ruff::ExprNoneLiteral { + node_index: Default::default(), + range, + }), + ConstantLiteral::Bool(value) => ruff::Expr::BooleanLiteral(ruff::ExprBooleanLiteral { + node_index: Default::default(), + range, + value, + }), ConstantLiteral::Str { value, prefix } => { ruff::Expr::StringLiteral(ruff::ExprStringLiteral { + node_index: Default::default(), range, value: ruff::StringLiteralValue::single(ruff::StringLiteral { + node_index: Default::default(), range, value, flags: ruff::StringLiteralFlags::empty().with_prefix(prefix), @@ -263,8 +270,10 @@ fn constant_to_ruff_expr(value: Constant) -> ruff::Expr { } ConstantLiteral::Bytes(value) => { ruff::Expr::BytesLiteral(ruff::ExprBytesLiteral { + node_index: Default::default(), range, value: ruff::BytesLiteralValue::single(ruff::BytesLiteral { + node_index: Default::default(), range, value, flags: ruff::BytesLiteralFlags::empty(), // TODO @@ -272,10 +281,12 @@ fn constant_to_ruff_expr(value: Constant) -> ruff::Expr { }) } ConstantLiteral::Int(value) => ruff::Expr::NumberLiteral(ruff::ExprNumberLiteral { + node_index: Default::default(), range, value: ruff::Number::Int(value), }), ConstantLiteral::Tuple(value) => ruff::Expr::Tuple(ruff::ExprTuple { + node_index: Default::default(), range, elts: value .into_iter() @@ -291,14 +302,17 @@ fn constant_to_ruff_expr(value: Constant) -> ruff::Expr { parenthesized: true, }), ConstantLiteral::FrozenSet(value) => ruff::Expr::Call(ruff::ExprCall { + node_index: Default::default(), range, // idk lol func: Box::new(ruff::Expr::Name(ruff::ExprName { + node_index: Default::default(), range: TextRange::default(), id: ruff::name::Name::new_static("frozenset"), ctx: ruff::ExprContext::Load, })), arguments: ruff::Arguments { + node_index: Default::default(), range, args: value .into_iter() @@ -313,18 +327,21 @@ fn constant_to_ruff_expr(value: Constant) -> ruff::Expr { }, }), ConstantLiteral::Float(value) => ruff::Expr::NumberLiteral(ruff::ExprNumberLiteral { + node_index: Default::default(), range, value: ruff::Number::Float(value), }), ConstantLiteral::Complex { real, imag } => { ruff::Expr::NumberLiteral(ruff::ExprNumberLiteral { + node_index: Default::default(), range, value: ruff::Number::Complex { real, imag }, }) } - ConstantLiteral::Ellipsis => { - ruff::Expr::EllipsisLiteral(ruff::ExprEllipsisLiteral { range }) - } + ConstantLiteral::Ellipsis => ruff::Expr::EllipsisLiteral(ruff::ExprEllipsisLiteral { + node_index: Default::default(), + range, + }), } } @@ -333,7 +350,11 @@ pub(super) fn number_literal_to_object( source_file: &SourceFile, constant: ruff::ExprNumberLiteral, ) -> PyObjectRef { - let ruff::ExprNumberLiteral { range, value } = constant; + let ruff::ExprNumberLiteral { + node_index: _, + range, + value, + } = constant; let c = match value { ruff::Number::Int(n) => Constant::new_int(n, range), ruff::Number::Float(n) => Constant::new_float(n, range), @@ -347,7 +368,11 @@ pub(super) fn string_literal_to_object( source_file: &SourceFile, constant: ruff::ExprStringLiteral, ) -> PyObjectRef { - let ruff::ExprStringLiteral { range, value } = constant; + let ruff::ExprStringLiteral { + node_index: _, + range, + value, + } = constant; let prefix = value .iter() .next() @@ -361,7 +386,11 @@ pub(super) fn bytes_literal_to_object( source_file: &SourceFile, constant: ruff::ExprBytesLiteral, ) -> PyObjectRef { - let ruff::ExprBytesLiteral { range, value } = constant; + let ruff::ExprBytesLiteral { + node_index: _, + range, + value, + } = constant; let bytes = value.as_slice().iter().flat_map(|b| b.value.iter()); let c = Constant::new_bytes(bytes.copied().collect(), range); c.ast_to_object(vm, source_file) @@ -372,7 +401,11 @@ pub(super) fn boolean_literal_to_object( source_file: &SourceFile, constant: ruff::ExprBooleanLiteral, ) -> PyObjectRef { - let ruff::ExprBooleanLiteral { range, value } = constant; + let ruff::ExprBooleanLiteral { + node_index: _, + range, + value, + } = constant; let c = Constant::new_bool(value, range); c.ast_to_object(vm, source_file) } @@ -382,7 +415,10 @@ pub(super) fn none_literal_to_object( source_file: &SourceFile, constant: ruff::ExprNoneLiteral, ) -> PyObjectRef { - let ruff::ExprNoneLiteral { range } = constant; + let ruff::ExprNoneLiteral { + node_index: _, + range, + } = constant; let c = Constant::new_none(range); c.ast_to_object(vm, source_file) } @@ -392,7 +428,10 @@ pub(super) fn ellipsis_literal_to_object( source_file: &SourceFile, constant: ruff::ExprEllipsisLiteral, ) -> PyObjectRef { - let ruff::ExprEllipsisLiteral { range } = constant; + let ruff::ExprEllipsisLiteral { + node_index: _, + range, + } = constant; let c = Constant::new_ellipsis(range); c.ast_to_object(vm, source_file) } diff --git a/vm/src/stdlib/ast/elif_else_clause.rs b/vm/src/stdlib/ast/elif_else_clause.rs index 2b427b1ec13..581fc499b8a 100644 --- a/vm/src/stdlib/ast/elif_else_clause.rs +++ b/vm/src/stdlib/ast/elif_else_clause.rs @@ -7,7 +7,12 @@ pub(super) fn ast_to_object( vm: &VirtualMachine, source_file: &SourceFile, ) -> PyObjectRef { - let ruff::ElifElseClause { range, test, body } = clause; + let ruff::ElifElseClause { + node_index: _, + range, + test, + body, + } = clause; let Some(test) = test else { assert!(rest.len() == 0); return body.ast_to_object(vm, source_file); @@ -55,6 +60,7 @@ pub(super) fn ast_from_object( let elif_else_clauses = if let [ruff::Stmt::If(_)] = &*orelse { let Some(ruff::Stmt::If(ruff::StmtIf { + node_index: _, range, test, body, @@ -66,6 +72,7 @@ pub(super) fn ast_from_object( elif_else_clauses.insert( 0, ruff::ElifElseClause { + node_index: Default::default(), range, test: Some(*test), body, @@ -74,6 +81,7 @@ pub(super) fn ast_from_object( elif_else_clauses } else { vec![ruff::ElifElseClause { + node_index: Default::default(), range, test: None, body: orelse, @@ -81,6 +89,7 @@ pub(super) fn ast_from_object( }; Ok(ruff::StmtIf { + node_index: Default::default(), test, body, elif_else_clauses, diff --git a/vm/src/stdlib/ast/exception.rs b/vm/src/stdlib/ast/exception.rs index df6b391aa1d..b5b3ca2709a 100644 --- a/vm/src/stdlib/ast/exception.rs +++ b/vm/src/stdlib/ast/exception.rs @@ -35,6 +35,7 @@ impl Node for ruff::ExceptHandler { impl Node for ruff::ExceptHandlerExceptHandler { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, type_, name, body, @@ -63,6 +64,7 @@ impl Node for ruff::ExceptHandlerExceptHandler { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), type_: get_node_field_opt(_vm, &_object, "type")? .map(|obj| Node::ast_from_object(_vm, source_file, obj)) .transpose()?, diff --git a/vm/src/stdlib/ast/expression.rs b/vm/src/stdlib/ast/expression.rs index 31b65bd13a9..83d77374380 100644 --- a/vm/src/stdlib/ast/expression.rs +++ b/vm/src/stdlib/ast/expression.rs @@ -36,6 +36,7 @@ impl Node for ruff::Expr { Self::NumberLiteral(cons) => constant::number_literal_to_object(vm, source_file, cons), Self::StringLiteral(cons) => constant::string_literal_to_object(vm, source_file, cons), Self::FString(cons) => string::fstring_to_object(vm, source_file, cons), + Self::TString(_) => unimplemented!(), Self::BytesLiteral(cons) => constant::bytes_literal_to_object(vm, source_file, cons), Self::BooleanLiteral(cons) => { constant::boolean_literal_to_object(vm, source_file, cons) @@ -145,7 +146,12 @@ impl Node for ruff::Expr { // constructor impl Node for ruff::ExprBoolOp { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { op, values, range } = self; + let Self { + node_index: _, + op, + values, + range, + } = self; let node = NodeAst .into_ref_with_type(vm, pyast::NodeExprBoolOp::static_type().to_owned()) .unwrap(); @@ -164,6 +170,7 @@ impl Node for ruff::ExprBoolOp { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), op: Node::ast_from_object( vm, source_file, @@ -183,6 +190,7 @@ impl Node for ruff::ExprBoolOp { impl Node for ruff::ExprNamed { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, target, value, range, @@ -205,6 +213,7 @@ impl Node for ruff::ExprNamed { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), target: Node::ast_from_object( vm, source_file, @@ -224,6 +233,7 @@ impl Node for ruff::ExprNamed { impl Node for ruff::ExprBinOp { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, left, op, right, @@ -249,6 +259,7 @@ impl Node for ruff::ExprBinOp { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), left: Node::ast_from_object( vm, source_file, @@ -272,7 +283,12 @@ impl Node for ruff::ExprBinOp { // constructor impl Node for ruff::ExprUnaryOp { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { op, operand, range } = self; + let Self { + node_index: _, + op, + operand, + range, + } = self; let node = NodeAst .into_ref_with_type(vm, pyast::NodeExprUnaryOp::static_type().to_owned()) .unwrap(); @@ -290,6 +306,7 @@ impl Node for ruff::ExprUnaryOp { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), op: Node::ast_from_object( vm, source_file, @@ -309,6 +326,7 @@ impl Node for ruff::ExprUnaryOp { impl Node for ruff::ExprLambda { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, parameters, body, range: _range, @@ -331,6 +349,7 @@ impl Node for ruff::ExprLambda { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), parameters: Node::ast_from_object( vm, source_file, @@ -350,6 +369,7 @@ impl Node for ruff::ExprLambda { impl Node for ruff::ExprIf { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, test, body, orelse, @@ -375,6 +395,7 @@ impl Node for ruff::ExprIf { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), test: Node::ast_from_object( vm, source_file, @@ -398,7 +419,11 @@ impl Node for ruff::ExprIf { // constructor impl Node for ruff::ExprDict { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { items, range } = self; + let Self { + node_index: _, + items, + range, + } = self; let (keys, values) = items .into_iter() @@ -440,6 +465,7 @@ impl Node for ruff::ExprDict { .map(|(key, value)| ruff::DictItem { key, value }) .collect(); Ok(Self { + node_index: Default::default(), items, range: range_from_object(vm, source_file, object, "Dict")?, }) @@ -449,7 +475,11 @@ impl Node for ruff::ExprDict { // constructor impl Node for ruff::ExprSet { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { elts, range } = self; + let Self { + node_index: _, + elts, + range, + } = self; let node = NodeAst .into_ref_with_type(vm, pyast::NodeExprSet::static_type().to_owned()) .unwrap(); @@ -465,6 +495,7 @@ impl Node for ruff::ExprSet { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), elts: Node::ast_from_object( vm, source_file, @@ -479,6 +510,7 @@ impl Node for ruff::ExprSet { impl Node for ruff::ExprListComp { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, elt, generators, range, @@ -501,6 +533,7 @@ impl Node for ruff::ExprListComp { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), elt: Node::ast_from_object( vm, source_file, @@ -520,6 +553,7 @@ impl Node for ruff::ExprListComp { impl Node for ruff::ExprSetComp { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, elt, generators, range, @@ -542,6 +576,7 @@ impl Node for ruff::ExprSetComp { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), elt: Node::ast_from_object( vm, source_file, @@ -561,6 +596,7 @@ impl Node for ruff::ExprSetComp { impl Node for ruff::ExprDictComp { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, key, value, generators, @@ -586,6 +622,7 @@ impl Node for ruff::ExprDictComp { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), key: Node::ast_from_object( vm, source_file, @@ -610,6 +647,7 @@ impl Node for ruff::ExprDictComp { impl Node for ruff::ExprGenerator { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, elt, generators, range, @@ -633,6 +671,7 @@ impl Node for ruff::ExprGenerator { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), elt: Node::ast_from_object( vm, source_file, @@ -653,7 +692,11 @@ impl Node for ruff::ExprGenerator { // constructor impl Node for ruff::ExprAwait { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { value, range } = self; + let Self { + node_index: _, + value, + range, + } = self; let node = NodeAst .into_ref_with_type(vm, pyast::NodeExprAwait::static_type().to_owned()) .unwrap(); @@ -669,6 +712,7 @@ impl Node for ruff::ExprAwait { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), value: Node::ast_from_object( vm, source_file, @@ -682,7 +726,11 @@ impl Node for ruff::ExprAwait { // constructor impl Node for ruff::ExprYield { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { value, range } = self; + let Self { + node_index: _, + value, + range, + } = self; let node = NodeAst .into_ref_with_type(vm, pyast::NodeExprYield::static_type().to_owned()) .unwrap(); @@ -699,6 +747,7 @@ impl Node for ruff::ExprYield { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), value: get_node_field_opt(vm, &object, "value")? .map(|obj| Node::ast_from_object(vm, source_file, obj)) .transpose()?, @@ -710,7 +759,11 @@ impl Node for ruff::ExprYield { // constructor impl Node for ruff::ExprYieldFrom { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { value, range } = self; + let Self { + node_index: _, + value, + range, + } = self; let node = NodeAst .into_ref_with_type(vm, pyast::NodeExprYieldFrom::static_type().to_owned()) .unwrap(); @@ -727,6 +780,7 @@ impl Node for ruff::ExprYieldFrom { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), value: Node::ast_from_object( vm, source_file, @@ -741,6 +795,7 @@ impl Node for ruff::ExprYieldFrom { impl Node for ruff::ExprCompare { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, left, ops, comparators, @@ -770,6 +825,7 @@ impl Node for ruff::ExprCompare { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), left: Node::ast_from_object( vm, source_file, @@ -800,6 +856,7 @@ impl Node for ruff::ExprCompare { impl Node for ruff::ExprCall { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, func, arguments, range, @@ -833,6 +890,7 @@ impl Node for ruff::ExprCall { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), func: Node::ast_from_object( vm, source_file, @@ -859,6 +917,7 @@ impl Node for ruff::ExprCall { impl Node for ruff::ExprAttribute { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, value, attr, ctx, @@ -884,6 +943,7 @@ impl Node for ruff::ExprAttribute { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), value: Node::ast_from_object( vm, source_file, @@ -908,6 +968,7 @@ impl Node for ruff::ExprAttribute { impl Node for ruff::ExprSubscript { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, value, slice, ctx, @@ -932,6 +993,7 @@ impl Node for ruff::ExprSubscript { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), value: Node::ast_from_object( vm, source_file, @@ -955,7 +1017,12 @@ impl Node for ruff::ExprSubscript { // constructor impl Node for ruff::ExprStarred { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { value, ctx, range } = self; + let Self { + node_index: _, + value, + ctx, + range, + } = self; let node = NodeAst .into_ref_with_type(vm, pyast::NodeExprStarred::static_type().to_owned()) .unwrap(); @@ -973,6 +1040,7 @@ impl Node for ruff::ExprStarred { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), value: Node::ast_from_object( vm, source_file, @@ -991,7 +1059,12 @@ impl Node for ruff::ExprStarred { // constructor impl Node for ruff::ExprName { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { id, ctx, range } = self; + let Self { + node_index: _, + id, + ctx, + range, + } = self; let node = NodeAst .into_ref_with_type(vm, pyast::NodeExprName::static_type().to_owned()) .unwrap(); @@ -1009,6 +1082,7 @@ impl Node for ruff::ExprName { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), id: Node::ast_from_object(vm, source_file, get_node_field(vm, &object, "id", "Name")?)?, ctx: Node::ast_from_object( vm, @@ -1023,7 +1097,12 @@ impl Node for ruff::ExprName { // constructor impl Node for ruff::ExprList { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { elts, ctx, range } = self; + let Self { + node_index: _, + elts, + ctx, + range, + } = self; let node = NodeAst .into_ref_with_type(vm, pyast::NodeExprList::static_type().to_owned()) .unwrap(); @@ -1042,6 +1121,7 @@ impl Node for ruff::ExprList { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), elts: Node::ast_from_object( vm, source_file, @@ -1061,6 +1141,7 @@ impl Node for ruff::ExprList { impl Node for ruff::ExprTuple { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, elts, ctx, range: _range, @@ -1084,6 +1165,7 @@ impl Node for ruff::ExprTuple { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), elts: Node::ast_from_object( vm, source_file, @@ -1104,6 +1186,7 @@ impl Node for ruff::ExprTuple { impl Node for ruff::ExprSlice { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, lower, upper, step, @@ -1129,6 +1212,7 @@ impl Node for ruff::ExprSlice { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), lower: get_node_field_opt(vm, &object, "lower")? .map(|obj| Node::ast_from_object(vm, source_file, obj)) .transpose()?, @@ -1185,6 +1269,7 @@ impl Node for ruff::ExprContext { impl Node for ruff::Comprehension { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, target, iter, ifs, @@ -1212,6 +1297,7 @@ impl Node for ruff::Comprehension { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), target: Node::ast_from_object( vm, source_file, diff --git a/vm/src/stdlib/ast/module.rs b/vm/src/stdlib/ast/module.rs index 33ee4c567b1..6fae8f10a33 100644 --- a/vm/src/stdlib/ast/module.rs +++ b/vm/src/stdlib/ast/module.rs @@ -66,6 +66,7 @@ impl Node for Mod { impl Node for ruff::ModModule { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, body, // type_ignores, range, @@ -95,6 +96,7 @@ impl Node for ruff::ModModule { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), body: Node::ast_from_object( vm, source_file, @@ -147,7 +149,11 @@ impl Node for ModInteractive { // constructor impl Node for ruff::ModExpression { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { body, range } = self; + let Self { + node_index: _, + body, + range, + } = self; let node = NodeAst .into_ref_with_type(vm, pyast::NodeModExpression::static_type().to_owned()) .unwrap(); @@ -164,6 +170,7 @@ impl Node for ruff::ModExpression { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), body: Node::ast_from_object( vm, source_file, diff --git a/vm/src/stdlib/ast/other.rs b/vm/src/stdlib/ast/other.rs index eddbef97482..780d7cc7124 100644 --- a/vm/src/stdlib/ast/other.rs +++ b/vm/src/stdlib/ast/other.rs @@ -55,7 +55,11 @@ impl Node for ruff::Decorator { ) -> PyResult<Self> { let expression = ruff::Expr::ast_from_object(vm, source_file, object)?; let range = expression.range(); - Ok(Self { expression, range }) + Ok(Self { + node_index: Default::default(), + expression, + range, + }) } } @@ -63,6 +67,7 @@ impl Node for ruff::Decorator { impl Node for ruff::Alias { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, name, asname, range: _range, @@ -85,6 +90,7 @@ impl Node for ruff::Alias { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), name: Node::ast_from_object( vm, source_file, @@ -102,6 +108,7 @@ impl Node for ruff::Alias { impl Node for ruff::WithItem { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, context_expr, optional_vars, range: _range, @@ -131,6 +138,7 @@ impl Node for ruff::WithItem { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), context_expr: Node::ast_from_object( vm, source_file, diff --git a/vm/src/stdlib/ast/parameter.rs b/vm/src/stdlib/ast/parameter.rs index 347b3b4ec0c..87fa736687b 100644 --- a/vm/src/stdlib/ast/parameter.rs +++ b/vm/src/stdlib/ast/parameter.rs @@ -5,6 +5,7 @@ use rustpython_compiler_core::SourceFile; impl Node for ruff::Parameters { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, posonlyargs, args, vararg, @@ -80,6 +81,7 @@ impl Node for ruff::Parameters { let (posonlyargs, args) = merge_positional_parameter_defaults(posonlyargs, args, defaults); Ok(Self { + node_index: Default::default(), posonlyargs, args, vararg: get_node_field_opt(vm, &object, "vararg")? @@ -102,6 +104,7 @@ impl Node for ruff::Parameters { impl Node for ruff::Parameter { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, name, annotation, // type_comment, @@ -135,6 +138,7 @@ impl Node for ruff::Parameter { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), name: Node::ast_from_object( _vm, source_file, @@ -155,6 +159,7 @@ impl Node for ruff::Parameter { impl Node for ruff::Keyword { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, arg, value, range: _range, @@ -176,6 +181,7 @@ impl Node for ruff::Keyword { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), arg: get_node_field_opt(_vm, &_object, "arg")? .map(|obj| Node::ast_from_object(_vm, source_file, obj)) .transpose()?, @@ -328,6 +334,7 @@ fn merge_positional_parameter_defaults( let mut posonlyargs: Vec<_> = <Box<[_]> as IntoIterator>::into_iter(posonlyargs) .map(|parameter| ruff::ParameterWithDefault { + node_index: Default::default(), range: Default::default(), parameter, default: None, @@ -335,6 +342,7 @@ fn merge_positional_parameter_defaults( .collect(); let mut args: Vec<_> = <Box<[_]> as IntoIterator>::into_iter(args) .map(|parameter| ruff::ParameterWithDefault { + node_index: Default::default(), range: Default::default(), parameter, default: None, @@ -397,6 +405,7 @@ fn merge_keyword_parameter_defaults( ) -> Vec<ruff::ParameterWithDefault> { std::iter::zip(kw_only_args.keywords, defaults.defaults) .map(|(parameter, default)| ruff::ParameterWithDefault { + node_index: Default::default(), parameter, default, range: Default::default(), diff --git a/vm/src/stdlib/ast/pattern.rs b/vm/src/stdlib/ast/pattern.rs index 0c484d6787a..9567ed38d41 100644 --- a/vm/src/stdlib/ast/pattern.rs +++ b/vm/src/stdlib/ast/pattern.rs @@ -5,6 +5,7 @@ use rustpython_compiler_core::SourceFile; impl Node for ruff::MatchCase { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, pattern, guard, body, @@ -29,6 +30,7 @@ impl Node for ruff::MatchCase { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), pattern: Node::ast_from_object( _vm, source_file, @@ -127,6 +129,7 @@ impl Node for ruff::Pattern { impl Node for ruff::PatternMatchValue { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, value, range: _range, } = self; @@ -146,6 +149,7 @@ impl Node for ruff::PatternMatchValue { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), value: Node::ast_from_object( _vm, source_file, @@ -160,6 +164,7 @@ impl Node for ruff::PatternMatchValue { impl Node for ruff::PatternMatchSingleton { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, value, range: _range, } = self; @@ -182,6 +187,7 @@ impl Node for ruff::PatternMatchSingleton { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), value: Node::ast_from_object( _vm, source_file, @@ -210,6 +216,7 @@ impl Node for ruff::Singleton { impl Node for ruff::PatternMatchSequence { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, patterns, range: _range, } = self; @@ -232,6 +239,7 @@ impl Node for ruff::PatternMatchSequence { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), patterns: Node::ast_from_object( _vm, source_file, @@ -246,6 +254,7 @@ impl Node for ruff::PatternMatchSequence { impl Node for ruff::PatternMatchMapping { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, keys, patterns, rest, @@ -274,6 +283,7 @@ impl Node for ruff::PatternMatchMapping { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), keys: Node::ast_from_object( _vm, source_file, @@ -296,6 +306,7 @@ impl Node for ruff::PatternMatchMapping { impl Node for ruff::PatternMatchClass { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, cls, arguments, range: _range, @@ -344,6 +355,7 @@ impl Node for ruff::PatternMatchClass { let (patterns, keywords) = merge_pattern_match_class(patterns, kwd_attrs, kwd_patterns); Ok(Self { + node_index: Default::default(), cls: Node::ast_from_object( vm, source_file, @@ -351,6 +363,7 @@ impl Node for ruff::PatternMatchClass { )?, range: range_from_object(vm, source_file, object, "MatchClass")?, arguments: ruff::PatternArguments { + node_index: Default::default(), range: Default::default(), patterns, keywords, @@ -416,6 +429,7 @@ impl Node for PatternMatchClassKeywordPatterns { impl Node for ruff::PatternMatchStar { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, name, range: _range, } = self; @@ -435,6 +449,7 @@ impl Node for ruff::PatternMatchStar { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), name: get_node_field_opt(_vm, &_object, "name")? .map(|obj| Node::ast_from_object(_vm, source_file, obj)) .transpose()?, @@ -447,6 +462,7 @@ impl Node for ruff::PatternMatchStar { impl Node for ruff::PatternMatchAs { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, pattern, name, range: _range, @@ -469,6 +485,7 @@ impl Node for ruff::PatternMatchAs { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), pattern: get_node_field_opt(_vm, &_object, "pattern")? .map(|obj| Node::ast_from_object(_vm, source_file, obj)) .transpose()?, @@ -484,6 +501,7 @@ impl Node for ruff::PatternMatchAs { impl Node for ruff::PatternMatchOr { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, patterns, range: _range, } = self; @@ -502,6 +520,7 @@ impl Node for ruff::PatternMatchOr { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), patterns: Node::ast_from_object( _vm, source_file, diff --git a/vm/src/stdlib/ast/statement.rs b/vm/src/stdlib/ast/statement.rs index fab7ea76961..5925ca1fc2a 100644 --- a/vm/src/stdlib/ast/statement.rs +++ b/vm/src/stdlib/ast/statement.rs @@ -172,6 +172,7 @@ impl Node for ruff::Stmt { impl Node for ruff::StmtFunctionDef { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, name, parameters, body, @@ -225,6 +226,7 @@ impl Node for ruff::StmtFunctionDef { let _cls = _object.class(); let is_async = _cls.is(pyast::NodeStmtAsyncFunctionDef::static_type()); Ok(Self { + node_index: Default::default(), name: Node::ast_from_object( _vm, source_file, @@ -267,6 +269,7 @@ impl Node for ruff::StmtFunctionDef { impl Node for ruff::StmtClassDef { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, name, arguments, body, @@ -318,6 +321,7 @@ impl Node for ruff::StmtClassDef { get_node_field(_vm, &_object, "keywords", "ClassDef")?, )?; Ok(Self { + node_index: Default::default(), name: Node::ast_from_object( _vm, source_file, @@ -347,6 +351,7 @@ impl Node for ruff::StmtClassDef { impl Node for ruff::StmtReturn { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, value, range: _range, } = self; @@ -365,6 +370,7 @@ impl Node for ruff::StmtReturn { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), value: get_node_field_opt(_vm, &_object, "value")? .map(|obj| Node::ast_from_object(_vm, source_file, obj)) .transpose()?, @@ -376,6 +382,7 @@ impl Node for ruff::StmtReturn { impl Node for ruff::StmtDelete { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, targets, range: _range, } = self; @@ -394,6 +401,7 @@ impl Node for ruff::StmtDelete { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), targets: Node::ast_from_object( _vm, source_file, @@ -408,6 +416,7 @@ impl Node for ruff::StmtDelete { impl Node for ruff::StmtAssign { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, targets, value, // type_comment, @@ -432,6 +441,7 @@ impl Node for ruff::StmtAssign { object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), targets: Node::ast_from_object( vm, source_file, @@ -454,6 +464,7 @@ impl Node for ruff::StmtAssign { impl Node for ruff::StmtTypeAlias { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, name, type_params, value, @@ -483,6 +494,7 @@ impl Node for ruff::StmtTypeAlias { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), name: Node::ast_from_object( _vm, source_file, @@ -507,6 +519,7 @@ impl Node for ruff::StmtTypeAlias { impl Node for ruff::StmtAugAssign { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, target, op, value, @@ -531,6 +544,7 @@ impl Node for ruff::StmtAugAssign { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), target: Node::ast_from_object( _vm, source_file, @@ -555,6 +569,7 @@ impl Node for ruff::StmtAugAssign { impl Node for ruff::StmtAnnAssign { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, target, annotation, value, @@ -586,6 +601,7 @@ impl Node for ruff::StmtAnnAssign { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), target: Node::ast_from_object( _vm, source_file, @@ -613,6 +629,7 @@ impl Node for ruff::StmtAnnAssign { impl Node for ruff::StmtFor { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, is_async, target, iter, @@ -656,6 +673,7 @@ impl Node for ruff::StmtFor { ); let is_async = _cls.is(pyast::NodeStmtAsyncFor::static_type()); Ok(Self { + node_index: Default::default(), target: Node::ast_from_object( _vm, source_file, @@ -689,6 +707,7 @@ impl Node for ruff::StmtFor { impl Node for ruff::StmtWhile { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, test, body, orelse, @@ -714,6 +733,7 @@ impl Node for ruff::StmtWhile { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), test: Node::ast_from_object( _vm, source_file, @@ -737,6 +757,7 @@ impl Node for ruff::StmtWhile { impl Node for ruff::StmtIf { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, test, body, range, @@ -744,6 +765,7 @@ impl Node for ruff::StmtIf { } = self; elif_else_clause::ast_to_object( ruff::ElifElseClause { + node_index: Default::default(), range, test: Some(*test), body, @@ -765,6 +787,7 @@ impl Node for ruff::StmtIf { impl Node for ruff::StmtWith { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, is_async, items, body, @@ -801,6 +824,7 @@ impl Node for ruff::StmtWith { ); let is_async = _cls.is(pyast::NodeStmtAsyncWith::static_type()); Ok(Self { + node_index: Default::default(), items: Node::ast_from_object( _vm, source_file, @@ -823,6 +847,7 @@ impl Node for ruff::StmtWith { impl Node for ruff::StmtMatch { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, subject, cases, range: _range, @@ -844,6 +869,7 @@ impl Node for ruff::StmtMatch { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), subject: Node::ast_from_object( _vm, source_file, @@ -862,6 +888,7 @@ impl Node for ruff::StmtMatch { impl Node for ruff::StmtRaise { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, exc, cause, range: _range, @@ -883,6 +910,7 @@ impl Node for ruff::StmtRaise { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), exc: get_node_field_opt(_vm, &_object, "exc")? .map(|obj| Node::ast_from_object(_vm, source_file, obj)) .transpose()?, @@ -897,6 +925,7 @@ impl Node for ruff::StmtRaise { impl Node for ruff::StmtTry { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, body, handlers, orelse, @@ -940,6 +969,7 @@ impl Node for ruff::StmtTry { ); Ok(Self { + node_index: Default::default(), body: Node::ast_from_object( _vm, source_file, @@ -969,6 +999,7 @@ impl Node for ruff::StmtTry { impl Node for ruff::StmtAssert { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, test, msg, range: _range, @@ -990,6 +1021,7 @@ impl Node for ruff::StmtAssert { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), test: Node::ast_from_object( _vm, source_file, @@ -1006,6 +1038,7 @@ impl Node for ruff::StmtAssert { impl Node for ruff::StmtImport { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, names, range: _range, } = self; @@ -1024,6 +1057,7 @@ impl Node for ruff::StmtImport { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), names: Node::ast_from_object( _vm, source_file, @@ -1037,6 +1071,7 @@ impl Node for ruff::StmtImport { impl Node for ruff::StmtImportFrom { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, module, names, level, @@ -1061,6 +1096,7 @@ impl Node for ruff::StmtImportFrom { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), module: get_node_field_opt(vm, &_object, "module")? .map(|obj| Node::ast_from_object(vm, source_file, obj)) .transpose()?, @@ -1081,6 +1117,7 @@ impl Node for ruff::StmtImportFrom { impl Node for ruff::StmtGlobal { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, names, range: _range, } = self; @@ -1099,6 +1136,7 @@ impl Node for ruff::StmtGlobal { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), names: Node::ast_from_object( _vm, source_file, @@ -1112,6 +1150,7 @@ impl Node for ruff::StmtGlobal { impl Node for ruff::StmtNonlocal { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, names, range: _range, } = self; @@ -1130,6 +1169,7 @@ impl Node for ruff::StmtNonlocal { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), names: Node::ast_from_object( _vm, source_file, @@ -1143,6 +1183,7 @@ impl Node for ruff::StmtNonlocal { impl Node for ruff::StmtExpr { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, value, range: _range, } = self; @@ -1161,6 +1202,7 @@ impl Node for ruff::StmtExpr { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), value: Node::ast_from_object( _vm, source_file, @@ -1173,7 +1215,10 @@ impl Node for ruff::StmtExpr { // constructor impl Node for ruff::StmtPass { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { range: _range } = self; + let Self { + node_index: _, + range: _range, + } = self; let node = NodeAst .into_ref_with_type(_vm, pyast::NodeStmtPass::static_type().to_owned()) .unwrap(); @@ -1187,6 +1232,7 @@ impl Node for ruff::StmtPass { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), range: range_from_object(_vm, source_file, _object, "Pass")?, }) } @@ -1194,7 +1240,10 @@ impl Node for ruff::StmtPass { // constructor impl Node for ruff::StmtBreak { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { range: _range } = self; + let Self { + node_index: _, + range: _range, + } = self; let node = NodeAst .into_ref_with_type(_vm, pyast::NodeStmtBreak::static_type().to_owned()) .unwrap(); @@ -1209,6 +1258,7 @@ impl Node for ruff::StmtBreak { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), range: range_from_object(_vm, source_file, _object, "Break")?, }) } @@ -1217,7 +1267,10 @@ impl Node for ruff::StmtBreak { // constructor impl Node for ruff::StmtContinue { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { - let Self { range: _range } = self; + let Self { + node_index: _, + range: _range, + } = self; let node = NodeAst .into_ref_with_type(_vm, pyast::NodeStmtContinue::static_type().to_owned()) .unwrap(); @@ -1231,6 +1284,7 @@ impl Node for ruff::StmtContinue { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), range: range_from_object(_vm, source_file, _object, "Continue")?, }) } diff --git a/vm/src/stdlib/ast/string.rs b/vm/src/stdlib/ast/string.rs index 5d8654270d4..f3df8d99262 100644 --- a/vm/src/stdlib/ast/string.rs +++ b/vm/src/stdlib/ast/string.rs @@ -5,24 +5,26 @@ fn ruff_fstring_value_into_iter( mut fstring_value: ruff::FStringValue, ) -> impl Iterator<Item = ruff::FStringPart> + 'static { let default = ruff::FStringPart::FString(ruff::FString { + node_index: Default::default(), range: Default::default(), elements: Default::default(), flags: ruff::FStringFlags::empty(), }); (0..fstring_value.as_slice().len()).map(move |i| { - let fstring_value = &mut fstring_value; - let tmp = fstring_value.into_iter().nth(i).unwrap(); + let tmp = fstring_value.iter_mut().nth(i).unwrap(); std::mem::replace(tmp, default.clone()) }) } fn ruff_fstring_element_into_iter( - mut fstring_element: ruff::FStringElements, -) -> impl Iterator<Item = ruff::FStringElement> + 'static { - let default = ruff::FStringElement::Literal(ruff::FStringLiteralElement { - range: Default::default(), - value: Default::default(), - }); + mut fstring_element: ruff::InterpolatedStringElements, +) -> impl Iterator<Item = ruff::InterpolatedStringElement> + 'static { + let default = + ruff::InterpolatedStringElement::Literal(ruff::InterpolatedStringLiteralElement { + node_index: Default::default(), + range: Default::default(), + value: Default::default(), + }); (0..fstring_element.into_iter().len()).map(move |i| { let fstring_element = &mut fstring_element; let tmp = fstring_element.into_iter().nth(i).unwrap(); @@ -36,6 +38,7 @@ fn fstring_part_to_joined_str_part(fstring_part: ruff::FStringPart) -> Vec<Joine range, value, flags, + node_index: _, }) => { vec![JoinedStrPart::Constant(Constant::new_str( value, @@ -47,27 +50,33 @@ fn fstring_part_to_joined_str_part(fstring_part: ruff::FStringPart) -> Vec<Joine range: _, elements, flags: _, // TODO + node_index: _, }) => ruff_fstring_element_into_iter(elements) .map(ruff_fstring_element_to_joined_str_part) .collect(), } } -fn ruff_fstring_element_to_joined_str_part(element: ruff::FStringElement) -> JoinedStrPart { +fn ruff_fstring_element_to_joined_str_part( + element: ruff::InterpolatedStringElement, +) -> JoinedStrPart { match element { - ruff::FStringElement::Literal(ruff::FStringLiteralElement { range, value }) => { - JoinedStrPart::Constant(Constant::new_str( - value, - ruff::str_prefix::StringLiteralPrefix::Empty, - range, - )) - } - ruff::FStringElement::Expression(ruff::FStringExpressionElement { + ruff::InterpolatedStringElement::Literal(ruff::InterpolatedStringLiteralElement { + range, + value, + node_index: _, + }) => JoinedStrPart::Constant(Constant::new_str( + value, + ruff::str_prefix::StringLiteralPrefix::Empty, + range, + )), + ruff::InterpolatedStringElement::Interpolation(ruff::InterpolatedElement { range, expression, debug_text: _, // TODO: What is this? conversion, format_spec, + node_index: _, }) => JoinedStrPart::FormattedValue(FormattedValue { value: expression, conversion, @@ -78,12 +87,16 @@ fn ruff_fstring_element_to_joined_str_part(element: ruff::FStringElement) -> Joi } fn ruff_format_spec_to_joined_str( - format_spec: Option<Box<ruff::FStringFormatSpec>>, + format_spec: Option<Box<ruff::InterpolatedStringFormatSpec>>, ) -> Option<Box<JoinedStr>> { match format_spec { None => None, Some(format_spec) => { - let ruff::FStringFormatSpec { range, elements } = *format_spec; + let ruff::InterpolatedStringFormatSpec { + range, + elements, + node_index: _, + } = *format_spec; let values: Vec<_> = ruff_fstring_element_into_iter(elements) .map(ruff_fstring_element_to_joined_str_part) .collect(); @@ -93,45 +106,37 @@ fn ruff_format_spec_to_joined_str( } } -fn ruff_fstring_element_to_ruff_fstring_part(element: ruff::FStringElement) -> ruff::FStringPart { +fn ruff_fstring_element_to_ruff_fstring_part( + element: ruff::InterpolatedStringElement, +) -> ruff::FStringPart { match element { - ruff::FStringElement::Literal(value) => { - let ruff::FStringLiteralElement { range, value } = value; - ruff::FStringPart::Literal(ruff::StringLiteral { + ruff::InterpolatedStringElement::Literal(value) => { + let ruff::InterpolatedStringLiteralElement { + node_index, range, value, - flags: ruff::StringLiteralFlags::empty(), - }) - } - ruff::FStringElement::Expression(value) => { - let ruff::FStringExpressionElement { - range, - expression, - debug_text, - conversion, - format_spec, } = value; - ruff::FStringPart::FString(ruff::FString { + ruff::FStringPart::Literal(ruff::StringLiteral { + node_index, range, - elements: vec![ruff::FStringElement::Expression( - ruff::FStringExpressionElement { - range, - expression, - debug_text, - conversion, - format_spec, - }, - )] - .into(), - flags: ruff::FStringFlags::empty(), + value, + flags: ruff::StringLiteralFlags::empty(), }) } + ruff::InterpolatedStringElement::Interpolation(ruff::InterpolatedElement { + range, .. + }) => ruff::FStringPart::FString(ruff::FString { + node_index: Default::default(), + range, + elements: vec![element].into(), + flags: ruff::FStringFlags::empty(), + }), } } fn joined_str_to_ruff_format_spec( joined_str: Option<Box<JoinedStr>>, -) -> Option<Box<ruff::FStringFormatSpec>> { +) -> Option<Box<ruff::InterpolatedStringFormatSpec>> { match joined_str { None => None, Some(joined_str) => { @@ -139,7 +144,8 @@ fn joined_str_to_ruff_format_spec( let elements: Vec<_> = Box::into_iter(values) .map(joined_str_part_to_ruff_fstring_element) .collect(); - let format_spec = ruff::FStringFormatSpec { + let format_spec = ruff::InterpolatedStringFormatSpec { + node_index: Default::default(), range, elements: elements.into(), }; @@ -158,10 +164,12 @@ impl JoinedStr { pub(super) fn into_expr(self) -> ruff::Expr { let Self { range, values } = self; ruff::Expr::FString(ruff::ExprFString { + node_index: Default::default(), range: Default::default(), value: match values.len() { // ruff represents an empty fstring like this: 0 => ruff::FStringValue::single(ruff::FString { + node_index: Default::default(), range, elements: vec![].into(), flags: ruff::FStringFlags::empty(), @@ -170,6 +178,7 @@ impl JoinedStr { Box::<[_]>::into_iter(values) .map(joined_str_part_to_ruff_fstring_element) .map(|element| ruff::FString { + node_index: Default::default(), range, elements: vec![element].into(), flags: ruff::FStringFlags::empty(), @@ -188,10 +197,11 @@ impl JoinedStr { } } -fn joined_str_part_to_ruff_fstring_element(part: JoinedStrPart) -> ruff::FStringElement { +fn joined_str_part_to_ruff_fstring_element(part: JoinedStrPart) -> ruff::InterpolatedStringElement { match part { JoinedStrPart::FormattedValue(value) => { - ruff::FStringElement::Expression(ruff::FStringExpressionElement { + ruff::InterpolatedStringElement::Interpolation(ruff::InterpolatedElement { + node_index: Default::default(), range: value.range, expression: value.value.clone(), debug_text: None, // TODO: What is this? @@ -200,7 +210,8 @@ fn joined_str_part_to_ruff_fstring_element(part: JoinedStrPart) -> ruff::FString }) } JoinedStrPart::Constant(value) => { - ruff::FStringElement::Literal(ruff::FStringLiteralElement { + ruff::InterpolatedStringElement::Literal(ruff::InterpolatedStringLiteralElement { + node_index: Default::default(), range: value.range, value: match value.value { ConstantLiteral::Str { value, .. } => value, @@ -344,7 +355,11 @@ pub(super) fn fstring_to_object( source_file: &SourceFile, expression: ruff::ExprFString, ) -> PyObjectRef { - let ruff::ExprFString { range, value } = expression; + let ruff::ExprFString { + range, + value, + node_index: _, + } = expression; let values: Vec<_> = ruff_fstring_value_into_iter(value) .flat_map(fstring_part_to_joined_str_part) .collect(); diff --git a/vm/src/stdlib/ast/type_parameters.rs b/vm/src/stdlib/ast/type_parameters.rs index 505cd04d284..017470f7e64 100644 --- a/vm/src/stdlib/ast/type_parameters.rs +++ b/vm/src/stdlib/ast/type_parameters.rs @@ -15,7 +15,11 @@ impl Node for ruff::TypeParams { let range = Option::zip(type_params.first(), type_params.last()) .map(|(first, last)| first.range().cover(last.range())) .unwrap_or_default(); - Ok(Self { type_params, range }) + Ok(Self { + node_index: Default::default(), + type_params, + range, + }) } fn is_none(&self) -> bool { @@ -70,6 +74,7 @@ impl Node for ruff::TypeParam { impl Node for ruff::TypeParamTypeVar { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, name, bound, range: _range, @@ -93,6 +98,7 @@ impl Node for ruff::TypeParamTypeVar { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), name: Node::ast_from_object( _vm, source_file, @@ -115,6 +121,7 @@ impl Node for ruff::TypeParamTypeVar { impl Node for ruff::TypeParamParamSpec { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, name, range: _range, default, @@ -141,6 +148,7 @@ impl Node for ruff::TypeParamParamSpec { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), name: Node::ast_from_object( _vm, source_file, @@ -160,6 +168,7 @@ impl Node for ruff::TypeParamParamSpec { impl Node for ruff::TypeParamTypeVarTuple { fn ast_to_object(self, _vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { let Self { + node_index: _, name, range: _range, default, @@ -189,6 +198,7 @@ impl Node for ruff::TypeParamTypeVarTuple { _object: PyObjectRef, ) -> PyResult<Self> { Ok(Self { + node_index: Default::default(), name: Node::ast_from_object( _vm, source_file, diff --git a/vm/src/vm/vm_new.rs b/vm/src/vm/vm_new.rs index cf8d682ec24..d0b78cfe5bd 100644 --- a/vm/src/vm/vm_new.rs +++ b/vm/src/vm/vm_new.rs @@ -321,7 +321,7 @@ impl VirtualMachine { error: ruff_python_parser::ParseErrorType::Lexical( ruff_python_parser::LexicalErrorType::FStringError( - ruff_python_parser::FStringErrorType::UnterminatedTripleQuotedString, + ruff_python_parser::InterpolatedStringErrorType::UnterminatedTripleQuotedString, ), ), .. @@ -412,7 +412,7 @@ impl VirtualMachine { fn get_statement(source: &str, loc: Option<SourceLocation>) -> Option<String> { let line = source .split('\n') - .nth(loc?.row.to_zero_indexed())? + .nth(loc?.line.to_zero_indexed())? .to_owned(); Some(line + "\n") } diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 9f4add07082..d1821f2e733 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -251,12 +251,12 @@ pub fn syntax_err(err: CompileError) -> SyntaxError { let _ = Reflect::set( &js_err, &"row".into(), - &(err.location().unwrap().row.get()).into(), + &(err.location().unwrap().line.get()).into(), ); let _ = Reflect::set( &js_err, &"col".into(), - &(err.location().unwrap().column.get()).into(), + &(err.location().unwrap().character_offset.get()).into(), ); // | ParseErrorType::UnrecognizedToken(Token::Dedent, _) let can_continue = matches!( From 13329f0a48444788ba0b9db36b6804ebbc209ccb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 09:32:19 +0900 Subject: [PATCH 285/819] Bump actions/setup-node from 5 to 6 (#6197) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 45050db2da8..977f27f3762 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -389,7 +389,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - run: python -m pip install -r requirements.txt working-directory: ./wasm/tests - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: cache: "npm" cache-dependency-path: "wasm/demo/package-lock.json" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d78a663393..c85614369b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,7 +112,7 @@ jobs: - name: install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 - uses: mwilliamson/setup-wabt-action@v3 with: { wabt-version: "1.0.30" } - name: build demo From 25a464eeae11634008844d17351e53f4e522261c Mon Sep 17 00:00:00 2001 From: Jiseok CHOI <jiseok.dev@gmail.com> Date: Tue, 21 Oct 2025 09:33:55 +0900 Subject: [PATCH 286/819] Fix sqlite3 Cursor initialization check (#6198) Add proper __init__ validation for sqlite3.Cursor to ensure base class __init__ is called before using cursor methods. This fixes the test_cursor_constructor_call_check test case. Changes: - Modified Cursor to initialize with inner=None in py_new - Added explicit __init__ method that sets up CursorInner - Updated close() method to check for uninitialized state - Changed error message to match CPython: 'Base Cursor.__init__ not called.' This ensures CPython compatibility where attempting to use a Cursor instance without calling the base __init__ raises ProgrammingError. --- Lib/test/test_sqlite3/test_regression.py | 2 - stdlib/src/sqlite.rs | 67 ++++++++++++++++++++---- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_sqlite3/test_regression.py b/Lib/test/test_sqlite3/test_regression.py index a658ff1f3ce..56be8b50e2a 100644 --- a/Lib/test/test_sqlite3/test_regression.py +++ b/Lib/test/test_sqlite3/test_regression.py @@ -195,8 +195,6 @@ def __del__(self): con.isolation_level = value self.assertEqual(con.isolation_level, "DEFERRED") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_cursor_constructor_call_check(self): """ Verifies that cursor methods check whether base class __init__ was diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs index aba410c42bb..52d658e6e91 100644 --- a/stdlib/src/sqlite.rs +++ b/stdlib/src/sqlite.rs @@ -1459,6 +1459,8 @@ mod _sqlite { #[pytraverse(skip)] rowcount: i64, statement: Option<PyRef<Statement>>, + #[pytraverse(skip)] + closed: bool, } #[derive(FromArgs)] @@ -1484,20 +1486,54 @@ mod _sqlite { lastrowid: -1, rowcount: -1, statement: None, + closed: false, })), } } + fn new_uninitialized(connection: PyRef<Connection>, _vm: &VirtualMachine) -> Self { + Self { + connection, + arraysize: Radium::new(1), + row_factory: PyAtomicRef::from(None), + inner: PyMutex::from(None), + } + } + + #[pymethod] + fn __init__(&self, _connection: PyRef<Connection>, _vm: &VirtualMachine) -> PyResult<()> { + let mut guard = self.inner.lock(); + if guard.is_some() { + // Already initialized (e.g., from a call to super().__init__) + return Ok(()); + } + *guard = Some(CursorInner { + description: None, + row_cast_map: vec![], + lastrowid: -1, + rowcount: -1, + statement: None, + closed: false, + }); + Ok(()) + } + fn inner(&self, vm: &VirtualMachine) -> PyResult<PyMappedMutexGuard<'_, CursorInner>> { let guard = self.inner.lock(); if guard.is_some() { - Ok(PyMutexGuard::map(guard, |x| unsafe { - x.as_mut().unwrap_unchecked() - })) + let inner_guard = + PyMutexGuard::map(guard, |x| unsafe { x.as_mut().unwrap_unchecked() }); + if inner_guard.closed { + return Err(new_programming_error( + vm, + "Cannot operate on a closed cursor.".to_owned(), + )); + } + Ok(inner_guard) } else { Err(new_programming_error( vm, - "Cannot operate on a closed cursor.".to_owned(), + "Base Cursor.__init__ not called.".to_owned(), )) } } @@ -1717,12 +1753,23 @@ mod _sqlite { } #[pymethod] - fn close(&self) { - if let Some(inner) = self.inner.lock().take() - && let Some(stmt) = inner.statement - { - stmt.lock().reset(); + fn close(&self, vm: &VirtualMachine) -> PyResult<()> { + // Check if __init__ was called + let mut guard = self.inner.lock(); + if guard.is_none() { + return Err(new_programming_error( + vm, + "Base Cursor.__init__ not called.".to_owned(), + )); } + + if let Some(inner) = guard.as_mut() { + if let Some(stmt) = &inner.statement { + stmt.lock().reset(); + } + inner.closed = true; + } + Ok(()) } #[pymethod] @@ -1809,7 +1856,7 @@ mod _sqlite { type Args = (PyRef<Connection>,); fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - Self::new(args.0, None, vm) + Self::new_uninitialized(args.0, vm) .into_ref_with_type(vm, cls) .map(Into::into) } From 2faa05dcfb9e592a50bb17ca1fcd24c880397a89 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI <jiseok.dev@gmail.com> Date: Tue, 21 Oct 2025 11:11:31 +0900 Subject: [PATCH 287/819] Fix sqlite Connection initialization check (#6199) * Fix sqlite3 Connection initialization check Add proper __init__ validation for sqlite3.Connection to ensure base class __init__ is called before using connection methods. This fixes the test_connection_constructor_call_check test case. Changes: - Modified Connection.py_new to detect subclassing - For base Connection class, initialization happens immediately in py_new - For subclassed Connection, db is initialized as None - Added __init__ method that performs actual database initialization - Updated _db_lock error message to match CPython: 'Base Connection.__init__ not called.' This ensures CPython compatibility where attempting to use a Connection subclass instance without calling the base __init__ raises ProgrammingError. * use Initializer trait --- Lib/test/test_sqlite3/test_regression.py | 2 - stdlib/src/sqlite.rs | 64 +++++++++++++++++------- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_sqlite3/test_regression.py b/Lib/test/test_sqlite3/test_regression.py index 56be8b50e2a..d746be647c6 100644 --- a/Lib/test/test_sqlite3/test_regression.py +++ b/Lib/test/test_sqlite3/test_regression.py @@ -219,8 +219,6 @@ def test_str_subclass(self): class MyStr(str): pass self.con.execute("select ?", (MyStr("abc"),)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_connection_constructor_call_check(self): """ Verifies that connection methods check whether base class __init__ was diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs index 52d658e6e91..e19aca1f6d5 100644 --- a/stdlib/src/sqlite.rs +++ b/stdlib/src/sqlite.rs @@ -74,8 +74,8 @@ mod _sqlite { }, sliceable::{SaturatedSliceIter, SliceableSequenceOp}, types::{ - AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable, IterNext, - Iterable, PyComparisonOp, SelfIter, Unconstructible, + AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable, + Initializer, IterNext, Iterable, PyComparisonOp, SelfIter, Unconstructible, }, utils::ToCString, }; @@ -851,7 +851,31 @@ mod _sqlite { type Args = ConnectArgs; fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - Ok(Self::new(args, vm)?.into_ref_with_type(vm, cls)?.into()) + let text_factory = PyStr::class(&vm.ctx).to_owned().into_object(); + + // For non-subclassed Connection, initialize in __new__ + // For subclassed Connection, leave db as None and require __init__ to be called + let is_base_class = cls.is(Connection::class(&vm.ctx).as_object()); + + let db = if is_base_class { + // Initialize immediately for base class + Some(Connection::initialize_db(&args, vm)?) + } else { + // For subclasses, require __init__ to be called + None + }; + + let conn = Self { + db: PyMutex::new(db), + detect_types: args.detect_types, + isolation_level: PyAtomicRef::from(args.isolation_level), + check_same_thread: args.check_same_thread, + thread_ident: std::thread::current().id(), + row_factory: PyAtomicRef::from(None), + text_factory: PyAtomicRef::from(text_factory), + }; + + Ok(conn.into_ref_with_type(vm, cls)?.into()) } } @@ -871,9 +895,25 @@ mod _sqlite { } } - #[pyclass(with(Constructor, Callable), flags(BASETYPE))] + impl Initializer for Connection { + type Args = ConnectArgs; + + fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + let mut guard = zelf.db.lock(); + if guard.is_some() { + // Already initialized + return Ok(()); + } + + let db = Self::initialize_db(&args, vm)?; + *guard = Some(db); + Ok(()) + } + } + + #[pyclass(with(Constructor, Callable, Initializer), flags(BASETYPE))] impl Connection { - fn new(args: ConnectArgs, vm: &VirtualMachine) -> PyResult<Self> { + fn initialize_db(args: &ConnectArgs, vm: &VirtualMachine) -> PyResult<Sqlite> { let path = args.database.to_cstring(vm)?; let db = Sqlite::from(SqliteRaw::open(path.as_ptr(), args.uri, vm)?); let timeout = (args.timeout * 1000.0) as c_int; @@ -881,17 +921,7 @@ mod _sqlite { if let Some(isolation_level) = &args.isolation_level { begin_statement_ptr_from_isolation_level(isolation_level, vm)?; } - let text_factory = PyStr::class(&vm.ctx).to_owned().into_object(); - - Ok(Self { - db: PyMutex::new(Some(db)), - detect_types: args.detect_types, - isolation_level: PyAtomicRef::from(args.isolation_level), - check_same_thread: args.check_same_thread, - thread_ident: std::thread::current().id(), - row_factory: PyAtomicRef::from(None), - text_factory: PyAtomicRef::from(text_factory), - }) + Ok(db) } fn db_lock(&self, vm: &VirtualMachine) -> PyResult<PyMappedMutexGuard<'_, Sqlite>> { @@ -908,7 +938,7 @@ mod _sqlite { } else { Err(new_programming_error( vm, - "Cannot operate on a closed database.".to_owned(), + "Base Connection.__init__ not called.".to_owned(), )) } } From b15e537692a5fb4653c83856b8d489c05d1d488e Mon Sep 17 00:00:00 2001 From: winlogon <walker84837@gmail.com> Date: Tue, 21 Oct 2025 05:14:57 +0200 Subject: [PATCH 288/819] Use PyStrRef for TypeAliasType name (#6203) * fix(PyStrRef): fix TODO in typing.rs where PyObjectRef was used * chore(fmt): apply rustfmt to code --- vm/src/frame.rs | 3 +++ vm/src/stdlib/typing.rs | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index fa5860244f1..89d19f15c8a 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -2453,6 +2453,9 @@ impl ExecutingFrame<'_> { .map_err(|_| vm.new_type_error("Type params must be a tuple."))? }; + let name = name.downcast::<crate::builtins::PyStr>().map_err(|_| { + vm.new_type_error("TypeAliasType name must be a string".to_owned()) + })?; let type_alias = typing::TypeAliasType::new(name, type_params, value); Ok(type_alias.into_ref(&vm.ctx).into()) } diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index 2c4517fc7b6..c014266935c 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -31,7 +31,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { pub(crate) mod decl { use crate::{ Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr}, + builtins::{PyStrRef, PyTupleRef, PyTypeRef, pystr::AsPyStr}, function::{FuncArgs, IntoFuncArgs}, types::{Constructor, Representable}, }; @@ -98,7 +98,7 @@ pub(crate) mod decl { #[derive(Debug, PyPayload)] #[allow(dead_code)] pub(crate) struct TypeAliasType { - name: PyObjectRef, // TODO PyStrRef? + name: PyStrRef, type_params: PyTupleRef, value: PyObjectRef, // compute_value: PyObjectRef, @@ -106,7 +106,7 @@ pub(crate) mod decl { } #[pyclass(with(Constructor, Representable), flags(BASETYPE))] impl TypeAliasType { - pub const fn new(name: PyObjectRef, type_params: PyTupleRef, value: PyObjectRef) -> Self { + pub const fn new(name: PyStrRef, type_params: PyTupleRef, value: PyObjectRef) -> Self { Self { name, type_params, @@ -116,7 +116,7 @@ pub(crate) mod decl { #[pygetset] fn __name__(&self) -> PyObjectRef { - self.name.clone() + self.name.clone().into() } #[pygetset] @@ -154,7 +154,10 @@ pub(crate) mod decl { ))); } - let name = args.args[0].clone(); + let name = args.args[0] + .clone() + .downcast::<crate::builtins::PyStr>() + .map_err(|_| vm.new_type_error("TypeAliasType name must be a string".to_owned()))?; let value = args.args[1].clone(); let type_params = if let Some(tp) = args.kwargs.get("type_params") { @@ -171,9 +174,8 @@ pub(crate) mod decl { } impl Representable for TypeAliasType { - fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> { - let name = zelf.name.str(vm)?; - Ok(name.as_str().to_owned()) + fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { + Ok(zelf.name.as_str().to_owned()) } } From 19b6241ef9fce0f5c48dd87b5940da2663b27f0f Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 10:53:43 +0300 Subject: [PATCH 289/819] Update changed files from 3.13.7 -> 3.13.8 --- Lib/_android_support.py | 16 +- Lib/_collections_abc.py | 16 +- Lib/html/parser.py | 24 +- Lib/multiprocessing/forkserver.py | 15 +- Lib/multiprocessing/popen_spawn_posix.py | 4 + Lib/sysconfig/__init__.py | 24 +- Lib/test/_test_multiprocessing.py | 41 ++++ Lib/test/list_tests.py | 27 ++- Lib/test/mp_preload_main.py | 14 ++ Lib/test/support/__init__.py | 269 +++------------------ Lib/test/test_bytes.py | 126 ++++++---- Lib/test/test_difflib.py | 39 ++++ Lib/test/test_exceptions.py | 121 +++++----- Lib/test/test_genericalias.py | 228 +++++++++++++++--- Lib/test/test_htmlparser.py | 86 +++++-- Lib/test/test_locale.py | 5 +- Lib/test/test_long.py | 25 +- Lib/test/test_posix.py | 142 +++++++++--- Lib/test/test_pyexpat.py | 282 ++++++++++++++--------- Lib/test/test_site.py | 128 ++++++++-- Lib/test/test_sysconfig.py | 36 ++- Lib/test/test_typing.py | 82 +++---- 22 files changed, 1109 insertions(+), 641 deletions(-) create mode 100644 Lib/test/mp_preload_main.py diff --git a/Lib/_android_support.py b/Lib/_android_support.py index ae506f6a4b5..a439d03a144 100644 --- a/Lib/_android_support.py +++ b/Lib/_android_support.py @@ -29,15 +29,19 @@ def init_streams(android_log_write, stdout_prio, stderr_prio): 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()) + sys.stdout = TextLogStream(stdout_prio, "python.stdout", sys.stdout) + sys.stderr = TextLogStream(stderr_prio, "python.stderr", sys.stderr) class TextLogStream(io.TextIOWrapper): - def __init__(self, prio, tag, fileno=None, **kwargs): + def __init__(self, prio, tag, original=None, **kwargs): + # Respect the -u option. + if original: + kwargs.setdefault("write_through", original.write_through) + fileno = original.fileno() + else: + fileno = None + # The default is surrogateescape for stdout and backslashreplace for # stderr, but in the context of an Android log, readability is more # important than reversibility. diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index de624f2e54f..6e224d36001 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -512,10 +512,6 @@ def __getitem__(self, item): new_args = (t_args, t_result) return _CallableGenericAlias(Callable, tuple(new_args)) - # TODO: RUSTPYTHON patch for common call - def __or__(self, other): - super().__or__(other) - def _is_param_expr(obj): """Checks if obj matches either a list of types, ``...``, ``ParamSpec`` or ``_ConcatenateGenericAlias`` from typing.py @@ -1087,7 +1083,7 @@ def __new__(cls, name, bases, namespace, **kwargs): warnings._deprecated( "collections.abc.ByteString", - remove=(3, 14), + remove=(3, 17), ) return super().__new__(cls, name, bases, namespace, **kwargs) @@ -1096,14 +1092,18 @@ def __instancecheck__(cls, instance): warnings._deprecated( "collections.abc.ByteString", - remove=(3, 14), + remove=(3, 17), ) return super().__instancecheck__(instance) class ByteString(Sequence, metaclass=_DeprecateByteStringMeta): - """This unifies bytes and bytearray. + """Deprecated ABC serving as a common supertype of ``bytes`` and ``bytearray``. - XXX Should add all their methods. + This ABC is scheduled for removal in Python 3.17. + Use ``isinstance(obj, collections.abc.Buffer)`` to test if ``obj`` + implements the buffer protocol at runtime. For use in type annotations, + either use ``Buffer`` or a union that explicitly specifies the types your + code supports (e.g., ``bytes | bytearray | memoryview``). """ __slots__ = () diff --git a/Lib/html/parser.py b/Lib/html/parser.py index 5d03c98df5c..5d7050dad23 100644 --- a/Lib/html/parser.py +++ b/Lib/html/parser.py @@ -146,6 +146,7 @@ def reset(self): self.lasttag = '???' self.interesting = interesting_normal self.cdata_elem = None + self._support_cdata = True self._escapable = True super().reset() @@ -183,6 +184,19 @@ def clear_cdata_mode(self): self.cdata_elem = None self._escapable = True + def _set_support_cdata(self, flag=True): + """Enable or disable support of the CDATA sections. + If enabled, "<[CDATA[" starts a CDATA section which ends with "]]>". + If disabled, "<[CDATA[" starts a bogus comments which ends with ">". + + This method is not called by default. Its purpose is to be called + in custom handle_starttag() and handle_endtag() methods, with + value that depends on the adjusted current node. + See https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state + for details. + """ + self._support_cdata = flag + # Internal -- handle data as far as reasonable. May leave state # and data to be processed by a subsequent call. If 'end' is # true, force handling all data as if followed by EOF marker. @@ -257,7 +271,7 @@ def goahead(self, end): j -= len(suffix) break self.handle_comment(rawdata[i+4:j]) - elif startswith("<![CDATA[", i): + elif startswith("<![CDATA[", i) and self._support_cdata: self.unknown_decl(rawdata[i+3:]) elif rawdata[i:i+9].lower() == '<!doctype': self.handle_decl(rawdata[i+2:]) @@ -333,8 +347,12 @@ def parse_html_declaration(self, i): if rawdata[i:i+4] == '<!--': # this case is actually already handled in goahead() return self.parse_comment(i) - elif rawdata[i:i+9] == '<![CDATA[': - return self.parse_marked_section(i) + elif rawdata[i:i+9] == '<![CDATA[' and self._support_cdata: + j = rawdata.find(']]>', i+9) + if j < 0: + return -1 + self.unknown_decl(rawdata[i+3: j]) + return j + 3 elif rawdata[i:i+9].lower() == '<!doctype': # find the closing > gtpos = rawdata.find('>', i+9) diff --git a/Lib/multiprocessing/forkserver.py b/Lib/multiprocessing/forkserver.py index bff7fb91d97..e243442e7a1 100644 --- a/Lib/multiprocessing/forkserver.py +++ b/Lib/multiprocessing/forkserver.py @@ -127,12 +127,13 @@ def ensure_running(self): cmd = ('from multiprocessing.forkserver import main; ' + 'main(%d, %d, %r, **%r)') + main_kws = {} if self._preload_modules: - desired_keys = {'main_path', 'sys_path'} data = spawn.get_preparation_data('ignore') - data = {x: y for x, y in data.items() if x in desired_keys} - else: - data = {} + if 'sys_path' in data: + main_kws['sys_path'] = data['sys_path'] + if 'init_main_from_path' in data: + main_kws['main_path'] = data['init_main_from_path'] with socket.socket(socket.AF_UNIX) as listener: address = connection.arbitrary_address('AF_UNIX') @@ -147,7 +148,7 @@ def ensure_running(self): try: fds_to_pass = [listener.fileno(), alive_r] cmd %= (listener.fileno(), alive_r, self._preload_modules, - data) + main_kws) exe = spawn.get_executable() args = [exe] + util._args_from_interpreter_flags() args += ['-c', cmd] @@ -182,6 +183,10 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None): except ImportError: pass + # gh-135335: flush stdout/stderr in case any of the preloaded modules + # wrote to them, otherwise children might inherit buffered data + util._flush_std_streams() + util._close_stdin() sig_r, sig_w = os.pipe() diff --git a/Lib/multiprocessing/popen_spawn_posix.py b/Lib/multiprocessing/popen_spawn_posix.py index 24b8634523e..cccd659ae77 100644 --- a/Lib/multiprocessing/popen_spawn_posix.py +++ b/Lib/multiprocessing/popen_spawn_posix.py @@ -57,6 +57,10 @@ def _launch(self, process_obj): self._fds.extend([child_r, child_w]) self.pid = util.spawnv_passfds(spawn.get_executable(), cmd, self._fds) + os.close(child_r) + child_r = None + os.close(child_w) + child_w = None self.sentinel = parent_r with open(parent_w, 'wb', closefd=False) as f: f.write(fp.getbuffer()) diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 3ad9df603f7..f7bd675bb3b 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -107,7 +107,7 @@ _INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv'] def _get_implementation(): - return 'RustPython' # XXX: For site-packages + return 'Python' # NOTE: site.py has copy of this function. # Sync it when modify this function. @@ -596,18 +596,22 @@ def get_platform(): isn't particularly important. Examples of returned values: - linux-i586 - linux-alpha (?) - solaris-2.6-sun4u - Windows will return one of: - win-amd64 (64-bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) - win-arm64 (64-bit Windows on ARM64 (aka AArch64) - win32 (all others - specifically, sys.platform is returned) - For other non-POSIX platforms, currently just returns 'sys.platform'. + Windows: - """ + - win-amd64 (64-bit Windows on AMD64, aka x86_64, Intel64, and EM64T) + - win-arm64 (64-bit Windows on ARM64, aka AArch64) + - win32 (all others - specifically, sys.platform is returned) + + POSIX based OS: + + - linux-x86_64 + - macosx-15.5-arm64 + - macosx-26.0-universal2 (macOS on Apple Silicon or Intel) + - android-24-arm64_v8a + + For other non-POSIX platforms, currently just returns :data:`sys.platform`.""" if os.name == 'nt': if 'amd64' in sys.version.lower(): return 'win-amd64' diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 0b8de96f1b0..c22ce769c48 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -6451,6 +6451,35 @@ def test_child_sys_path(self): self.assertEqual(child_sys_path[1:], sys.path[1:]) self.assertIsNone(import_error, msg=f"child could not import {self._mod_name}") + def test_std_streams_flushed_after_preload(self): + # gh-135335: Check fork server flushes standard streams after + # preloading modules + if multiprocessing.get_start_method() != "forkserver": + self.skipTest("forkserver specific test") + + # Create a test module in the temporary directory on the child's path + # TODO: This can all be simplified once gh-126631 is fixed and we can + # use __main__ instead of a module. + dirname = os.path.join(self._temp_dir, 'preloaded_module') + init_name = os.path.join(dirname, '__init__.py') + os.mkdir(dirname) + with open(init_name, "w") as f: + cmd = '''if 1: + import sys + print('stderr', end='', file=sys.stderr) + print('stdout', end='', file=sys.stdout) + ''' + f.write(cmd) + + name = os.path.join(os.path.dirname(__file__), 'mp_preload_flush.py') + env = {'PYTHONPATH': self._temp_dir} + _, out, err = test.support.script_helper.assert_python_ok(name, **env) + + # Check stderr first, as it is more likely to be useful to see in the + # event of a failure. + self.assertEqual(err.decode().rstrip(), 'stderr') + self.assertEqual(out.decode().rstrip(), 'stdout') + class MiscTestCase(unittest.TestCase): def test__all__(self): @@ -6516,6 +6545,18 @@ def child(): self.assertEqual(q.get_nowait(), "done") close_queue(q) + def test_preload_main(self): + # gh-126631: Check that __main__ can be pre-loaded + if multiprocessing.get_start_method() != "forkserver": + self.skipTest("forkserver specific test") + + name = os.path.join(os.path.dirname(__file__), 'mp_preload_main.py') + _, out, err = test.support.script_helper.assert_python_ok(name) + self.assertEqual(err, b'') + + # The trailing empty string comes from split() on output ending with \n + out = out.decode().split("\n") + self.assertEqual(out, ['__main__', '__mp_main__', 'f', 'f', '']) # # Mixins diff --git a/Lib/test/list_tests.py b/Lib/test/list_tests.py index 0e11af6f36e..65dfa41b26e 100644 --- a/Lib/test/list_tests.py +++ b/Lib/test/list_tests.py @@ -2,13 +2,11 @@ Tests common to list and UserList.UserList """ -import unittest import sys -import os from functools import cmp_to_key -from test import support, seq_tests -from test.support import ALWAYS_EQ, NEVER_EQ +from test import seq_tests +from test.support import ALWAYS_EQ, NEVER_EQ, get_c_recursion_limit class CommonTest(seq_tests.CommonTest): @@ -33,13 +31,13 @@ def test_init(self): self.assertEqual(a, b) def test_getitem_error(self): - a = [] + a = self.type2test([]) msg = "list indices must be integers or slices" with self.assertRaisesRegex(TypeError, msg): a['a'] def test_setitem_error(self): - a = [] + a = self.type2test([]) msg = "list indices must be integers or slices" with self.assertRaisesRegex(TypeError, msg): a['a'] = "python" @@ -63,7 +61,7 @@ def test_repr(self): def test_repr_deep(self): a = self.type2test([]) - for i in range(sys.getrecursionlimit() + 100): + for i in range(get_c_recursion_limit() + 1): a = self.type2test([a]) self.assertRaises(RecursionError, repr, a) @@ -193,6 +191,14 @@ def test_setslice(self): self.assertRaises(TypeError, a.__setitem__) + def test_slice_assign_iterator(self): + x = self.type2test(range(5)) + x[0:3] = reversed(range(3)) + self.assertEqual(x, self.type2test([2, 1, 0, 3, 4])) + + x[:] = reversed(range(3)) + self.assertEqual(x, self.type2test([2, 1, 0])) + def test_delslice(self): a = self.type2test([0, 1]) del a[1:2] @@ -552,7 +558,7 @@ def test_constructor_exception_handling(self): class F(object): def __iter__(self): raise KeyboardInterrupt - self.assertRaises(KeyboardInterrupt, list, F()) + self.assertRaises(KeyboardInterrupt, self.type2test, F()) def test_exhausted_iterator(self): a = self.type2test([1, 2, 3]) @@ -564,3 +570,8 @@ def test_exhausted_iterator(self): self.assertEqual(list(exhit), []) self.assertEqual(list(empit), [9]) self.assertEqual(a, self.type2test([1, 2, 3, 9])) + + # gh-115733: Crash when iterating over exhausted iterator + exhit = iter(self.type2test([1, 2, 3])) + for _ in exhit: + next(exhit, 1) diff --git a/Lib/test/mp_preload_main.py b/Lib/test/mp_preload_main.py new file mode 100644 index 00000000000..acb342822ec --- /dev/null +++ b/Lib/test/mp_preload_main.py @@ -0,0 +1,14 @@ +import multiprocessing + +print(f"{__name__}") + +def f(): + print("f") + +if __name__ == "__main__": + ctx = multiprocessing.get_context("forkserver") + ctx.set_forkserver_preload(['__main__']) + for _ in range(2): + p = ctx.Process(target=f) + p.start() + p.join() diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 88369e25c14..4605938b875 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -305,6 +305,16 @@ def requires(resource, msg=None): if resource == 'gui' and not _is_gui_available(): raise ResourceDenied(_is_gui_available.reason) +def _get_kernel_version(sysname="Linux"): + import platform + if platform.system() != sysname: + return None + version_txt = platform.release().split('-', 1)[0] + try: + return tuple(map(int, version_txt.split('.'))) + except ValueError: + return None + def _requires_unix_version(sysname, min_version): """Decorator raising SkipTest if the OS is `sysname` and the version is less than `min_version`. @@ -501,8 +511,6 @@ def requires_lzma(reason='requires lzma'): import lzma except ImportError: lzma = None - # XXX: RUSTPYTHON; xz is not supported yet - lzma = None return unittest.skipUnless(lzma, reason) def has_no_debug_ranges(): @@ -521,25 +529,43 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'): reason = e.args[0] if e.args else reason return unittest.skipIf(skip, reason) -@contextlib.contextmanager -def suppress_immortalization(suppress=True): - """Suppress immortalization of deferred objects.""" + +def can_use_suppress_immortalization(suppress=True): + """Check if suppress_immortalization(suppress) can be used. + + Use this helper in code where SkipTest must be eagerly handled. + """ + if not suppress: + return True try: import _testinternalcapi except ImportError: - yield - return + return False + return True + +@contextlib.contextmanager +def suppress_immortalization(suppress=True): + """Suppress immortalization of deferred objects. + + If _testinternalcapi is not available, the decorated test or class + is skipped. Use can_use_suppress_immortalization() outside test cases + to check if this decorator can be used. + """ if not suppress: - yield + yield # no-op return + from .import_helper import import_module + + _testinternalcapi = import_module("_testinternalcapi") _testinternalcapi.suppress_immortalization(True) try: yield finally: _testinternalcapi.suppress_immortalization(False) + def skip_if_suppress_immortalization(): try: import _testinternalcapi @@ -815,8 +841,6 @@ def gc_collect(): longer than expected. This function tries its best to force all garbage objects to disappear. """ - return # TODO: RUSTPYTHON - import gc gc.collect() gc.collect() @@ -824,13 +848,6 @@ def gc_collect(): @contextlib.contextmanager def disable_gc(): - # TODO: RUSTPYTHON; GC is not supported yet - try: - yield - finally: - pass - return - import gc have_gc = gc.isenabled() gc.disable() @@ -842,13 +859,6 @@ def disable_gc(): @contextlib.contextmanager def gc_threshold(*args): - # TODO: RUSTPYTHON; GC is not supported yet - try: - yield - finally: - pass - return - import gc old_threshold = gc.get_threshold() gc.set_threshold(*args) @@ -1645,7 +1655,7 @@ def check__all__(test_case, module, name_of_module=None, extra=(), 'module'. The 'name_of_module' argument can specify (as a string or tuple thereof) - what module(s) an API could be defined in in order to be detected as a + what module(s) an API could be defined in order to be detected as a public API. One case for this is when 'module' imports part of its public API from other modules, possibly a C backend (like 'csv' and its '_csv'). @@ -1911,10 +1921,6 @@ def _check_tracemalloc(): def check_free_after_iterating(test, iter, cls, args=()): - # TODO: RUSTPYTHON; GC is not supported yet - test.assertTrue(False) - return - done = False def wrapper(): class A(cls): @@ -2296,6 +2302,7 @@ def check_disallow_instantiation(testcase, tp, *args, **kwds): qualname = f"{name}" msg = f"cannot create '{re.escape(qualname)}' instances" testcase.assertRaisesRegex(TypeError, msg, tp, *args, **kwds) + testcase.assertRaisesRegex(TypeError, msg, tp.__new__, tp, *args, **kwds) def get_recursion_depth(): """Get the recursion depth of the caller function. @@ -2757,7 +2764,7 @@ def no_color(): from .os_helper import EnvironmentVarGuard with ( - swap_attr(_colorize, "can_colorize", lambda file=None: False), + swap_attr(_colorize, "can_colorize", lambda *, file=None: False), EnvironmentVarGuard() as env, ): env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS") @@ -2838,205 +2845,3 @@ def linked_to_musl(): except (OSError, subprocess.CalledProcessError): return False return ('musl' in stdout) - - -# TODO: RUSTPYTHON -# Every line of code below allowed us to update `Lib/test/support/__init__.py` without -# needing to update `libregtest` and its dependencies. -# Ideally we want to remove all code below and update `libregtest`. -# -# Code below was copied from: https://github.com/RustPython/RustPython/blob/9499d39f55b73535e2405bf208d5380241f79ada/Lib/test/support/__init__.py - -from .testresult import get_test_runner - -def _filter_suite(suite, pred): - """Recursively filter test cases in a suite based on a predicate.""" - newtests = [] - for test in suite._tests: - if isinstance(test, unittest.TestSuite): - _filter_suite(test, pred) - newtests.append(test) - else: - if pred(test): - newtests.append(test) - suite._tests = newtests - -# By default, don't filter tests -_match_test_func = None - -_accept_test_patterns = None -_ignore_test_patterns = None - -def match_test(test): - # Function used by support.run_unittest() and regrtest --list-cases - if _match_test_func is None: - return True - else: - return _match_test_func(test.id()) - -def _is_full_match_test(pattern): - # If a pattern contains at least one dot, it's considered - # as a full test identifier. - # Example: 'test.test_os.FileTests.test_access'. - # - # ignore patterns which contain fnmatch patterns: '*', '?', '[...]' - # or '[!...]'. For example, ignore 'test_access*'. - return ('.' in pattern) and (not re.search(r'[?*\[\]]', pattern)) - -def set_match_tests(accept_patterns=None, ignore_patterns=None): - global _match_test_func, _accept_test_patterns, _ignore_test_patterns - - if accept_patterns is None: - accept_patterns = () - if ignore_patterns is None: - ignore_patterns = () - - accept_func = ignore_func = None - - if accept_patterns != _accept_test_patterns: - accept_patterns, accept_func = _compile_match_function(accept_patterns) - if ignore_patterns != _ignore_test_patterns: - ignore_patterns, ignore_func = _compile_match_function(ignore_patterns) - - # Create a copy since patterns can be mutable and so modified later - _accept_test_patterns = tuple(accept_patterns) - _ignore_test_patterns = tuple(ignore_patterns) - - if accept_func is not None or ignore_func is not None: - def match_function(test_id): - accept = True - ignore = False - if accept_func: - accept = accept_func(test_id) - if ignore_func: - ignore = ignore_func(test_id) - return accept and not ignore - - _match_test_func = match_function - -def _compile_match_function(patterns): - if not patterns: - func = None - # set_match_tests(None) behaves as set_match_tests(()) - patterns = () - elif all(map(_is_full_match_test, patterns)): - # Simple case: all patterns are full test identifier. - # The test.bisect_cmd utility only uses such full test identifiers. - func = set(patterns).__contains__ - else: - import fnmatch - regex = '|'.join(map(fnmatch.translate, patterns)) - # The search *is* case sensitive on purpose: - # don't use flags=re.IGNORECASE - regex_match = re.compile(regex).match - - def match_test_regex(test_id): - if regex_match(test_id): - # The regex matches the whole identifier, for example - # 'test.test_os.FileTests.test_access'. - return True - else: - # Try to match parts of the test identifier. - # For example, split 'test.test_os.FileTests.test_access' - # into: 'test', 'test_os', 'FileTests' and 'test_access'. - return any(map(regex_match, test_id.split("."))) - - func = match_test_regex - - return patterns, func - -def run_unittest(*classes): - """Run tests from unittest.TestCase-derived classes.""" - valid_types = (unittest.TestSuite, unittest.TestCase) - loader = unittest.TestLoader() - suite = unittest.TestSuite() - for cls in classes: - if isinstance(cls, str): - if cls in sys.modules: - suite.addTest(loader.loadTestsFromModule(sys.modules[cls])) - else: - raise ValueError("str arguments must be keys in sys.modules") - elif isinstance(cls, valid_types): - suite.addTest(cls) - else: - suite.addTest(loader.loadTestsFromTestCase(cls)) - _filter_suite(suite, match_test) - return _run_suite(suite) - -def _run_suite(suite): - """Run tests from a unittest.TestSuite-derived class.""" - runner = get_test_runner(sys.stdout, - verbosity=verbose, - capture_output=(junit_xml_list is not None)) - - result = runner.run(suite) - - if junit_xml_list is not None: - junit_xml_list.append(result.get_xml_element()) - - if not result.testsRun and not result.skipped and not result.errors: - raise TestDidNotRun - if not result.wasSuccessful(): - stats = TestStats.from_unittest(result) - if len(result.errors) == 1 and not result.failures: - err = result.errors[0][1] - elif len(result.failures) == 1 and not result.errors: - err = result.failures[0][1] - else: - err = "multiple errors occurred" - if not verbose: err += "; run in verbose mode for details" - errors = [(str(tc), exc_str) for tc, exc_str in result.errors] - failures = [(str(tc), exc_str) for tc, exc_str in result.failures] - raise TestFailedWithDetails(err, errors, failures, stats=stats) - return result - -@dataclasses.dataclass(slots=True) -class TestStats: - tests_run: int = 0 - failures: int = 0 - skipped: int = 0 - - @staticmethod - def from_unittest(result): - return TestStats(result.testsRun, - len(result.failures), - len(result.skipped)) - - @staticmethod - def from_doctest(results): - return TestStats(results.attempted, - results.failed) - - def accumulate(self, stats): - self.tests_run += stats.tests_run - self.failures += stats.failures - self.skipped += stats.skipped - - -def run_doctest(module, verbosity=None, optionflags=0): - """Run doctest on the given module. Return (#failures, #tests). - - If optional argument verbosity is not specified (or is None), pass - support's belief about verbosity on to doctest. Else doctest's - usual behavior is used (it searches sys.argv for -v). - """ - - import doctest - - if verbosity is None: - verbosity = verbose - else: - verbosity = None - - results = doctest.testmod(module, - verbose=verbosity, - optionflags=optionflags) - if results.failed: - stats = TestStats.from_doctest(results) - raise TestFailed(f"{results.failed} of {results.attempted} " - f"doctests failed", - stats=stats) - if verbose: - print('doctest (%s) ... %d tests with zero failures' % - (module.__name__, results.attempted)) - return results diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index e84df546a88..0847afe016b 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -201,8 +201,7 @@ def test_constructor_value_errors(self): self.assertRaises(ValueError, self.type2test, [sys.maxsize+1]) self.assertRaises(ValueError, self.type2test, [10**100]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @bigaddrspacetest def test_constructor_overflow(self): size = MAX_Py_ssize_t @@ -326,8 +325,7 @@ def test_decode(self): # Default encoding is utf-8 self.assertEqual(self.type2test(b'\xe2\x98\x83').decode(), '\u2603') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_check_encoding_errors(self): # bpo-37388: bytes(str) and bytes.encode() must check encoding # and errors arguments in dev mode @@ -972,8 +970,7 @@ def test_integer_arguments_out_of_byte_range(self): self.assertRaises(ValueError, method, 256) self.assertRaises(ValueError, method, 9999) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_find_etc_raise_correct_error_messages(self): # issue 11828 b = self.type2test(b'hello') @@ -993,8 +990,7 @@ def test_find_etc_raise_correct_error_messages(self): self.assertRaisesRegex(TypeError, r'\bendswith\b', b.endswith, x, None, None, None) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_free_after_iterating(self): test.support.check_free_after_iterating(self, iter, self.type2test) test.support.check_free_after_iterating(self, reversed, self.type2test) @@ -1583,11 +1579,6 @@ def test_irepeat_1char(self): self.assertEqual(b, b1) self.assertIs(b, b1) - # NOTE: RUSTPYTHON: - # - # The second instance of self.assertGreater was replaced with - # self.assertGreaterEqual since, in RustPython, the underlying storage - # is a Vec which doesn't require trailing null byte. def test_alloc(self): b = bytearray() alloc = b.__alloc__() @@ -1596,15 +1587,10 @@ def test_alloc(self): for i in range(100): b += b"x" alloc = b.__alloc__() - self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched + self.assertGreater(alloc, len(b)) # including trailing null byte if alloc not in seq: seq.append(alloc) - # NOTE: RUSTPYTHON: - # - # The usages of self.assertGreater were replaced with - # self.assertGreaterEqual since, in RustPython, the underlying storage - # is a Vec which doesn't require trailing null byte. def test_init_alloc(self): b = bytearray() def g(): @@ -1615,12 +1601,12 @@ def g(): self.assertEqual(len(b), len(a)) self.assertLessEqual(len(b), i) alloc = b.__alloc__() - self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched + self.assertGreater(alloc, len(b)) # including trailing null byte b.__init__(g()) self.assertEqual(list(b), list(range(1, 100))) self.assertEqual(len(b), 99) alloc = b.__alloc__() - self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched + self.assertGreater(alloc, len(b)) def test_extend(self): orig = b'hello' @@ -1845,6 +1831,8 @@ def test_repeat_after_setslice(self): self.assertEqual(b3, b'xcxcxc') def test_mutating_index(self): + # bytearray slice assignment can call into python code + # that reallocates the internal buffer # See gh-91153 class Boom: @@ -1862,22 +1850,82 @@ def __index__(self): with self.assertRaises(IndexError): self._testlimitedcapi.sequence_setitem(b, 0, Boom()) + def test_mutating_index_inbounds(self): + # gh-91153 continued + # Ensure buffer is not broken even if length is correct + + class MutatesOnIndex: + def __init__(self): + self.ba = bytearray(0x180) + + def __index__(self): + self.ba.clear() + self.new_ba = bytearray(0x180) # to catch out-of-bounds writes + self.ba.extend([0] * 0x180) # to check bounds checks + return 0 + + with self.subTest("skip_bounds_safety"): + instance = MutatesOnIndex() + instance.ba[instance] = ord("?") + self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered") + self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered") + + with self.subTest("skip_bounds_safety_capi"): + instance = MutatesOnIndex() + instance.ba[instance] = ord("?") + self._testlimitedcapi.sequence_setitem(instance.ba, instance, ord("?")) + self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered") + self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered") + + with self.subTest("skip_bounds_safety_slice"): + instance = MutatesOnIndex() + instance.ba[instance:1] = [ord("?")] + self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered") + self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered") + class AssortedBytesTest(unittest.TestCase): # # Test various combinations of bytes and bytearray # + def test_bytes_repr(self, f=repr): + self.assertEqual(f(b''), "b''") + self.assertEqual(f(b"abc"), "b'abc'") + self.assertEqual(f(bytes([92])), r"b'\\'") + self.assertEqual(f(bytes([0, 1, 254, 255])), r"b'\x00\x01\xfe\xff'") + self.assertEqual(f(b'\a\b\t\n\v\f\r'), r"b'\x07\x08\t\n\x0b\x0c\r'") + self.assertEqual(f(b'"'), """b'"'""") # '"' + self.assertEqual(f(b"'"), '''b"'"''') # "'" + self.assertEqual(f(b"'\""), r"""b'\'"'""") # '\'"' + self.assertEqual(f(b"\"'\""), r"""b'"\'"'""") # '"\'"' + self.assertEqual(f(b"'\"'"), r"""b'\'"\''""") # '\'"\'' + self.assertEqual(f(BytesSubclass(b"abc")), "b'abc'") + + def test_bytearray_repr(self, f=repr): + self.assertEqual(f(bytearray()), "bytearray(b'')") + self.assertEqual(f(bytearray(b'abc')), "bytearray(b'abc')") + self.assertEqual(f(bytearray([92])), r"bytearray(b'\\')") + self.assertEqual(f(bytearray([0, 1, 254, 255])), + r"bytearray(b'\x00\x01\xfe\xff')") + self.assertEqual(f(bytearray([7, 8, 9, 10, 11, 12, 13])), + r"bytearray(b'\x07\x08\t\n\x0b\x0c\r')") + self.assertEqual(f(bytearray(b'"')), """bytearray(b'"')""") # '"' + self.assertEqual(f(bytearray(b"'")), r'''bytearray(b"\'")''') # "\'" + self.assertEqual(f(bytearray(b"'\"")), r"""bytearray(b'\'"')""") # '\'"' + self.assertEqual(f(bytearray(b"\"'\"")), r"""bytearray(b'"\'"')""") # '"\'"' + self.assertEqual(f(bytearray(b'\'"\'')), r"""bytearray(b'\'"\'')""") # '\'"\'' + self.assertEqual(f(ByteArraySubclass(b"abc")), "ByteArraySubclass(b'abc')") + self.assertEqual(f(ByteArraySubclass.Nested(b"abc")), "Nested(b'abc')") + self.assertEqual(f(ByteArraySubclass.Ŭñıçöđë(b"abc")), "Ŭñıçöđë(b'abc')") + + @check_bytes_warnings + def test_bytes_str(self): + self.test_bytes_repr(str) + @check_bytes_warnings - def test_repr_str(self): - for f in str, repr: - self.assertEqual(f(bytearray()), "bytearray(b'')") - self.assertEqual(f(bytearray([0])), "bytearray(b'\\x00')") - self.assertEqual(f(bytearray([0, 1, 254, 255])), - "bytearray(b'\\x00\\x01\\xfe\\xff')") - self.assertEqual(f(b"abc"), "b'abc'") - self.assertEqual(f(b"'"), '''b"'"''') # ''' - self.assertEqual(f(b"'\""), r"""b'\'"'""") # ' + def test_bytearray_str(self): + self.test_bytearray_repr(str) @check_bytes_warnings def test_format(self): @@ -1930,15 +1978,6 @@ def test_from_bytearray(self): b = bytearray(buf) self.assertEqual(b, bytearray(sample)) - @check_bytes_warnings - def test_to_str(self): - self.assertEqual(str(b''), "b''") - self.assertEqual(str(b'x'), "b'x'") - self.assertEqual(str(b'\x80'), "b'\\x80'") - self.assertEqual(str(bytearray(b'')), "bytearray(b'')") - self.assertEqual(str(bytearray(b'x')), "bytearray(b'x')") - self.assertEqual(str(bytearray(b'\x80')), "bytearray(b'\\x80')") - def test_literal(self): tests = [ (b"Wonderful spam", "Wonderful spam"), @@ -2089,7 +2128,7 @@ def test_join(self): s3 = s1.join([b"abcd"]) self.assertIs(type(s3), self.basetype) - @unittest.skip("TODO: RUSTPYTHON, Fails on ByteArraySubclassWithSlotsTest") + @unittest.skip('TODO: RUSTPYTHON; Fails on ByteArraySubclassWithSlotsTest') def test_pickle(self): a = self.type2test(b"abcd") a.x = 10 @@ -2104,7 +2143,7 @@ def test_pickle(self): self.assertEqual(type(a.z), type(b.z)) self.assertFalse(hasattr(b, 'y')) - @unittest.skip("TODO: RUSTPYTHON, Fails on ByteArraySubclassWithSlotsTest") + @unittest.skip('TODO: RUSTPYTHON; Fails on ByteArraySubclassWithSlotsTest') def test_copy(self): a = self.type2test(b"abcd") a.x = 10 @@ -2148,7 +2187,10 @@ def __init__(me, *args, **kwargs): class ByteArraySubclass(bytearray): - pass + class Nested(bytearray): + pass + class Ŭñıçöđë(bytearray): + pass class ByteArraySubclassWithSlots(bytearray): __slots__ = ('x', 'y', '__dict__') diff --git a/Lib/test/test_difflib.py b/Lib/test/test_difflib.py index 6afd90af844..943d7a659b1 100644 --- a/Lib/test/test_difflib.py +++ b/Lib/test/test_difflib.py @@ -29,6 +29,16 @@ def test_one_delete(self): ('delete', 40, 41, 40, 40), ('equal', 41, 81, 40, 80)]) + def test_opcode_caching(self): + sm = difflib.SequenceMatcher(None, 'b' * 100, 'a' + 'b' * 100) + opcode = sm.get_opcodes() + self.assertEqual(opcode, + [ ('insert', 0, 0, 0, 1), + ('equal', 0, 100, 1, 101)]) + # Implementation detail: opcodes are cached; + # `get_opcodes()` returns the same object + self.assertIs(opcode, sm.get_opcodes()) + def test_bjunk(self): sm = difflib.SequenceMatcher(isjunk=lambda x: x == ' ', a='a' * 40 + 'b' * 40, b='a' * 44 + 'b' * 40) @@ -273,6 +283,15 @@ def test_make_file_usascii_charset_with_nonascii_input(self): self.assertIn('ımplıcıt', output) + def test_one_insert(self): + m = difflib.Differ().compare('b' * 2, 'a' + 'b' * 2) + self.assertEqual(list(m), ['+ a', ' b', ' b']) + + def test_one_delete(self): + m = difflib.Differ().compare('a' + 'b' * 2, 'b' * 2) + self.assertEqual(list(m), ['- a', ' b', ' b']) + + class TestOutputFormat(unittest.TestCase): def test_tab_delimiter(self): args = ['one', 'two', 'Original', 'Current', @@ -547,6 +566,26 @@ def test_longest_match_with_popular_chars(self): self.assertFalse(self.longer_match_exists(a, b, match.size)) +class TestCloseMatches(unittest.TestCase): + # Happy paths are tested in the doctests of `difflib.get_close_matches`. + + def test_invalid_inputs(self): + self.assertRaises(ValueError, difflib.get_close_matches, "spam", ['egg'], n=0) + self.assertRaises(ValueError, difflib.get_close_matches, "spam", ['egg'], n=-1) + self.assertRaises(ValueError, difflib.get_close_matches, "spam", ['egg'], cutoff=1.1) + self.assertRaises(ValueError, difflib.get_close_matches, "spam", ['egg'], cutoff=-0.1) + + +class TestRestore(unittest.TestCase): + # Happy paths are tested in the doctests of `difflib.restore`. + + def test_invalid_input(self): + with self.assertRaises(ValueError): + ''.join(difflib.restore([], 0)) + with self.assertRaises(ValueError): + ''.join(difflib.restore([], 3)) + + def setUpModule(): difflib.HtmlDiff._default_prefix = 0 diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 57afb6ec6f1..eee8c13386e 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -60,8 +60,7 @@ def raise_catch(self, exc, excname): self.assertEqual(buf1, buf2) self.assertEqual(exc.__name__, excname) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testRaising(self): self.raise_catch(AttributeError, "AttributeError") self.assertRaises(AttributeError, getattr, sys, "undefined_attribute") @@ -146,8 +145,7 @@ def testRaising(self): self.raise_catch(StopAsyncIteration, "StopAsyncIteration") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testSyntaxErrorMessage(self): # make sure the right exception message is raised for each of # these code fragments @@ -172,8 +170,7 @@ def ckmsg(src, msg): ckmsg("continue\n", "'continue' not properly in loop") ckmsg("f'{6 0}'", "invalid syntax. Perhaps you forgot a comma?") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testSyntaxErrorMissingParens(self): def ckmsg(src, msg, exception=SyntaxError): try: @@ -232,14 +229,12 @@ def check(self, src, lineno, offset, end_lineno=None, end_offset=None, encoding= line = src.split('\n')[lineno-1] self.assertIn(line, cm.exception.text) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_error_offset_continuation_characters(self): check = self.check check('"\\\n"(1 for c in I,\\\n\\', 2, 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testSyntaxErrorOffset(self): check = self.check check('def fact(x):\n\treturn x!\n', 2, 10) @@ -443,8 +438,7 @@ def test_windows_message(self): with self.assertRaisesRegex(OSError, 'Windows Error 0x%x' % code): ctypes.pythonapi.PyErr_SetFromWindowsErr(code) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testAttributes(self): # test that exception attributes are happy @@ -607,8 +601,7 @@ def test_invalid_setstate(self): with self.assertRaisesRegex(TypeError, "state is not a dictionary"): e.__setstate__(42) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_notes(self): for e in [BaseException(1), Exception(2), ValueError(3)]: with self.subTest(e=e): @@ -665,8 +658,7 @@ def testInvalidTraceback(self): else: self.fail("No exception raised") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_setattr(self): TE = TypeError exc = Exception() @@ -679,8 +671,7 @@ def test_invalid_setattr(self): msg = "exception context must be None or derive from BaseException" self.assertRaisesRegex(TE, msg, setattr, exc, '__context__', 1) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_delattr(self): TE = TypeError try: @@ -752,8 +743,8 @@ def __init__(self, fancy_arg): x = DerivedException(fancy_arg=42) self.assertEqual(x.fancy_arg, 42) + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; Windows') @no_tracing - @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON Windows') def testInfiniteRecursion(self): def f(): return f() @@ -1161,8 +1152,6 @@ class C(Exception): self.assertIs(c.__context__, b) self.assertIsNone(b.__context__) - # TODO: RUSTPYTHON - @unittest.skip("Infinite loop") def test_no_hang_on_context_chain_cycle1(self): # See issue 25782. Cycle in context chain. @@ -1218,8 +1207,6 @@ class C(Exception): self.assertIs(b.__context__, a) self.assertIs(a.__context__, c) - # TODO: RUSTPYTHON - @unittest.skip("Infinite loop") def test_no_hang_on_context_chain_cycle3(self): # See issue 25782. Longer context chain with cycle. @@ -1317,8 +1304,7 @@ def test_context_of_exception_in_else_and_finally(self): self.assertIs(exc, oe) self.assertIs(exc.__context__, ve) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unicode_change_attributes(self): # See issue 7309. This was a crasher. @@ -1362,8 +1348,7 @@ def test_unicode_errors_no_object(self): for klass in klasses: self.assertEqual(str(klass.__new__(klass)), "") - # TODO: RUSTPYTHON; OverflowError: Python int too large to convert to Rust usize - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; OverflowError: Python int too large to convert to Rust usize def test_unicode_error_str_does_not_crash(self): # Test that str(UnicodeError(...)) does not crash. # See https://github.com/python/cpython/issues/123378. @@ -1387,8 +1372,8 @@ def test_unicode_error_str_does_not_crash(self): exc = UnicodeDecodeError('utf-8', encoded, start, end, '') self.assertIsInstance(str(exc), str) + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; Windows') @no_tracing - @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON Windows') def test_badisinstance(self): # Bug #2542: if issubclass(e, MyException) raises an exception, # it should be ignored @@ -1669,7 +1654,7 @@ def inner(): gc_collect() # For PyPy or other GCs. self.assertEqual(wr(), None) - @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON Windows') + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; Windows') @no_tracing def test_recursion_error_cleanup(self): # Same test as above, but with "recursion exceeded" errors @@ -1691,7 +1676,6 @@ def inner(): gc_collect() # For PyPy or other GCs. self.assertEqual(wr(), None) - @unittest.skipIf(sys.platform == 'win32', 'error specific to cpython') def test_errno_ENOTDIR(self): # Issue #12802: "not a directory" errors are ENOTDIR even on Windows with self.assertRaises(OSError) as cm: @@ -1714,8 +1698,7 @@ def __del__(self): self.assertEqual(cm.unraisable.object, BrokenDel.__del__) self.assertIsNotNone(cm.unraisable.exc_traceback) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unhandled(self): # Check for sensible reporting of unhandled exceptions for exc_type in (ValueError, BrokenStrException): @@ -1814,8 +1797,7 @@ def g(): next(i) next(i) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(__debug__, "Won't work if __debug__ is False") def test_assert_shadowing(self): # Shadowing AssertionError would cause the assert statement to @@ -1877,6 +1859,38 @@ def test_memory_error_in_subinterp(self): rc, _, err = script_helper.assert_python_ok("-c", code) self.assertIn(b'MemoryError', err) + @cpython_only + # Python built with Py_TRACE_REFS fail with a fatal error in + # _PyRefchain_Trace() on memory allocation error. + @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') + def test_exec_set_nomemory_hang(self): + import_module("_testcapi") + # gh-134163: A MemoryError inside code that was wrapped by a try/except + # block would lead to an infinite loop. + + # The frame_lasti needs to be greater than 257 to prevent + # PyLong_FromLong() from returning cached integers, which + # don't require a memory allocation. Prepend some dummy code + # to artificially increase the instruction index. + warmup_code = "a = list(range(0, 1))\n" * 20 + user_input = warmup_code + dedent(""" + try: + import _testcapi + _testcapi.set_nomemory(0) + b = list(range(1000, 2000)) + except Exception as e: + import traceback + traceback.print_exc() + """) + with SuppressCrashReport(): + with script_helper.spawn_python('-c', user_input) as p: + p.wait() + output = p.stdout.read() + + self.assertIn(p.returncode, (0, 1)) + self.assertGreater(len(output), 0) # At minimum, should not hang + self.assertIn(b"MemoryError", output) + class NameErrorTests(unittest.TestCase): def test_name_error_has_name(self): @@ -1973,8 +1987,7 @@ def blech(self): class ImportErrorTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_attributes(self): # Setting 'name' and 'path' should not be a problem. exc = ImportError('test') @@ -2064,8 +2077,7 @@ class AssertionErrorTests(unittest.TestCase): def tearDown(self): unlink(TESTFN) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @force_not_colorized def test_assertion_error_location(self): cases = [ @@ -2164,8 +2176,7 @@ def test_assertion_error_location(self): result = run_script(source) self.assertEqual(result[-3:], expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @force_not_colorized def test_multiline_not_highlighted(self): cases = [ @@ -2202,8 +2213,7 @@ def test_multiline_not_highlighted(self): class SyntaxErrorTests(unittest.TestCase): maxDiff = None - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @force_not_colorized def test_range_of_offsets(self): cases = [ @@ -2310,8 +2320,7 @@ class MySyntaxError(SyntaxError): ^^^^^ """, err.getvalue()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_encodings(self): self.addCleanup(unlink, TESTFN) source = ( @@ -2328,16 +2337,14 @@ def test_encodings(self): self.assertEqual(err[-3], ' (') self.assertEqual(err[-2], ' ^') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_non_utf8(self): # Check non utf-8 characters self.addCleanup(unlink, TESTFN) err = run_script(b"\x89") self.assertIn("SyntaxError: Non-UTF-8 code starting with '\\x89' in file", err[-1]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_string_source(self): def try_compile(source): with self.assertRaises(SyntaxError) as cm: @@ -2380,8 +2387,7 @@ def try_compile(source): self.assertEqual(exc.offset, 1) self.assertEqual(exc.end_offset, 12) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_file_source(self): self.addCleanup(unlink, TESTFN) err = run_script('return "ä"') @@ -2444,8 +2450,7 @@ def test_attributes_old_constructor(self): self.assertEqual(error, the_exception.text) self.assertEqual("bad bad", the_exception.msg) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_incorrect_constructor(self): args = ("bad.py", 1, 2) self.assertRaises(TypeError, SyntaxError, "bad bad", args) @@ -2507,8 +2512,7 @@ def in_except(): pass self.lineno_after_raise(in_except, 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_lineno_after_other_except(self): def other_except(): try: @@ -2526,8 +2530,7 @@ def in_named_except(): pass self.lineno_after_raise(in_named_except, 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_lineno_in_try(self): def in_try(): try: @@ -2554,8 +2557,7 @@ def in_finally_except(): pass self.lineno_after_raise(in_finally_except, 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_lineno_after_with(self): class Noop: def __enter__(self): @@ -2568,8 +2570,7 @@ def after_with(): pass self.lineno_after_raise(after_with, 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_missing_lineno_shows_as_none(self): def f(): 1/0 diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 0daaff099a8..4a87e2bf7a1 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -2,6 +2,7 @@ import unittest import pickle +from array import array import copy from collections import ( defaultdict, deque, OrderedDict, Counter, UserDict, UserList @@ -11,8 +12,11 @@ from concurrent.futures.thread import _WorkItem from contextlib import AbstractContextManager, AbstractAsyncContextManager from contextvars import ContextVar, Token +from csv import DictReader, DictWriter from dataclasses import Field from functools import partial, partialmethod, cached_property +from graphlib import TopologicalSorter +from logging import LoggerAdapter, StreamHandler from mailbox import Mailbox, _PartialFile try: import ctypes @@ -23,29 +27,73 @@ from fileinput import FileInput from itertools import chain from http.cookies import Morsel -from multiprocessing.managers import ValueProxy -from multiprocessing.pool import ApplyResult +try: + from multiprocessing.managers import ValueProxy, DictProxy, ListProxy + from multiprocessing.pool import ApplyResult + from multiprocessing.queues import SimpleQueue as MPSimpleQueue + from multiprocessing.queues import Queue as MPQueue + from multiprocessing.queues import JoinableQueue as MPJoinableQueue +except ImportError: + # _multiprocessing module is optional + ValueProxy = None + DictProxy = None + ListProxy = None + ApplyResult = None + MPSimpleQueue = None + MPQueue = None + MPJoinableQueue = None try: from multiprocessing.shared_memory import ShareableList except ImportError: # multiprocessing.shared_memory is not available on e.g. Android ShareableList = None -from multiprocessing.queues import SimpleQueue as MPSimpleQueue from os import DirEntry from re import Pattern, Match -from types import GenericAlias, MappingProxyType, AsyncGeneratorType +from types import GenericAlias, MappingProxyType, AsyncGeneratorType, CoroutineType, GeneratorType from tempfile import TemporaryDirectory, SpooledTemporaryFile from urllib.parse import SplitResult, ParseResult from unittest.case import _AssertRaisesContext from queue import Queue, SimpleQueue from weakref import WeakSet, ReferenceType, ref import typing +from typing import Unpack from typing import TypeVar T = TypeVar('T') K = TypeVar('K') V = TypeVar('V') +_UNPACKED_TUPLES = [ + # Unpacked tuple using `*` + (*tuple[int],)[0], + (*tuple[T],)[0], + (*tuple[int, str],)[0], + (*tuple[int, ...],)[0], + (*tuple[T, ...],)[0], + tuple[*tuple[int, ...]], + tuple[*tuple[T, ...]], + tuple[str, *tuple[int, ...]], + tuple[*tuple[int, ...], str], + tuple[float, *tuple[int, ...], str], + tuple[*tuple[*tuple[int, ...]]], + # Unpacked tuple using `Unpack` + Unpack[tuple[int]], + Unpack[tuple[T]], + Unpack[tuple[int, str]], + Unpack[tuple[int, ...]], + Unpack[tuple[T, ...]], + tuple[Unpack[tuple[int, ...]]], + tuple[Unpack[tuple[T, ...]]], + tuple[str, Unpack[tuple[int, ...]]], + tuple[Unpack[tuple[int, ...]], str], + tuple[float, Unpack[tuple[int, ...]], str], + tuple[Unpack[tuple[Unpack[tuple[int, ...]]]]], + # Unpacked tuple using `*` AND `Unpack` + tuple[Unpack[tuple[*tuple[int, ...]]]], + tuple[*tuple[Unpack[tuple[int, ...]]]], +] + + class BaseTest(unittest.TestCase): """Test basics.""" generic_types = [type, tuple, list, dict, set, frozenset, enumerate, @@ -56,6 +104,7 @@ class BaseTest(unittest.TestCase): OrderedDict, Counter, UserDict, UserList, Pattern, Match, partial, partialmethod, cached_property, + TopologicalSorter, AbstractContextManager, AbstractAsyncContextManager, Awaitable, Coroutine, AsyncIterable, AsyncIterator, @@ -71,19 +120,25 @@ class BaseTest(unittest.TestCase): KeysView, ItemsView, ValuesView, Sequence, MutableSequence, MappingProxyType, AsyncGeneratorType, + GeneratorType, CoroutineType, DirEntry, chain, + LoggerAdapter, StreamHandler, TemporaryDirectory, SpooledTemporaryFile, Queue, SimpleQueue, _AssertRaisesContext, SplitResult, ParseResult, - ValueProxy, ApplyResult, WeakSet, ReferenceType, ref, - ShareableList, MPSimpleQueue, + ShareableList, Future, _WorkItem, - Morsel] + Morsel, + DictReader, DictWriter, + array] if ctypes is not None: generic_types.extend((ctypes.Array, ctypes.LibraryLoader)) + if ValueProxy is not None: + generic_types.extend((ValueProxy, DictProxy, ListProxy, ApplyResult, + MPSimpleQueue, MPQueue, MPJoinableQueue)) def test_subscriptable(self): for t in self.generic_types: @@ -100,7 +155,7 @@ def test_unsubscriptable(self): for t in int, str, float, Sized, Hashable: tname = t.__name__ with self.subTest(f"Testing {tname}"): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, tname): t[int] def test_instantiate(self): @@ -157,12 +212,26 @@ class MyList(list): def test_repr(self): class MyList(list): pass + class MyGeneric: + __class_getitem__ = classmethod(GenericAlias) + self.assertEqual(repr(list[str]), 'list[str]') self.assertEqual(repr(list[()]), 'list[()]') self.assertEqual(repr(tuple[int, ...]), 'tuple[int, ...]') + x1 = tuple[*tuple[int]] + self.assertEqual(repr(x1), 'tuple[*tuple[int]]') + x2 = tuple[*tuple[int, str]] + self.assertEqual(repr(x2), 'tuple[*tuple[int, str]]') + x3 = tuple[*tuple[int, ...]] + self.assertEqual(repr(x3), 'tuple[*tuple[int, ...]]') self.assertTrue(repr(MyList[int]).endswith('.BaseTest.test_repr.<locals>.MyList[int]')) self.assertEqual(repr(list[str]()), '[]') # instances should keep their normal repr + # gh-105488 + self.assertTrue(repr(MyGeneric[int]).endswith('MyGeneric[int]')) + self.assertTrue(repr(MyGeneric[[]]).endswith('MyGeneric[[]]')) + self.assertTrue(repr(MyGeneric[[int, str]]).endswith('MyGeneric[[int, str]]')) + def test_exposed_type(self): import types a = types.GenericAlias(list, int) @@ -173,6 +242,7 @@ def test_exposed_type(self): def test_parameters(self): from typing import List, Dict, Callable + D0 = dict[str, int] self.assertEqual(D0.__args__, (str, int)) self.assertEqual(D0.__parameters__, ()) @@ -188,6 +258,7 @@ def test_parameters(self): D2b = dict[T, T] self.assertEqual(D2b.__args__, (T, T)) self.assertEqual(D2b.__parameters__, (T,)) + L0 = list[str] self.assertEqual(L0.__args__, (str,)) self.assertEqual(L0.__parameters__, ()) @@ -210,6 +281,27 @@ def test_parameters(self): self.assertEqual(L5.__args__, (Callable[[K, V], K],)) self.assertEqual(L5.__parameters__, (K, V)) + T1 = tuple[*tuple[int]] + self.assertEqual( + T1.__args__, + (*tuple[int],), + ) + self.assertEqual(T1.__parameters__, ()) + + T2 = tuple[*tuple[T]] + self.assertEqual( + T2.__args__, + (*tuple[T],), + ) + self.assertEqual(T2.__parameters__, (T,)) + + T4 = tuple[*tuple[int, str]] + self.assertEqual( + T4.__args__, + (*tuple[int, str],), + ) + self.assertEqual(T4.__parameters__, ()) + def test_parameter_chaining(self): from typing import List, Dict, Union, Callable self.assertEqual(list[T][int], list[int]) @@ -233,16 +325,23 @@ def test_parameter_chaining(self): with self.assertRaises(TypeError): list[int][int] + with self.assertRaises(TypeError): dict[T, int][str, int] + with self.assertRaises(TypeError): dict[str, T][str, int] + with self.assertRaises(TypeError): dict[T, T][str, int] def test_equality(self): self.assertEqual(list[int], list[int]) self.assertEqual(dict[str, int], dict[str, int]) + self.assertEqual((*tuple[int],)[0], (*tuple[int],)[0]) + self.assertEqual(tuple[*tuple[int]], tuple[*tuple[int]]) self.assertNotEqual(dict[str, int], dict[str, str]) self.assertNotEqual(list, list[int]) self.assertNotEqual(list[int], list) + self.assertNotEqual(list[int], tuple[int]) + self.assertNotEqual((*tuple[int],)[0], tuple[int]) def test_isinstance(self): self.assertTrue(isinstance([], list)) @@ -266,17 +365,20 @@ def test_type_generic(self): def test_type_subclass_generic(self): class MyType(type): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, 'MyType'): MyType[int] def test_pickle(self): - alias = GenericAlias(list, T) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - s = pickle.dumps(alias, proto) - loaded = pickle.loads(s) - self.assertEqual(loaded.__origin__, alias.__origin__) - self.assertEqual(loaded.__args__, alias.__args__) - self.assertEqual(loaded.__parameters__, alias.__parameters__) + aliases = [GenericAlias(list, T)] + _UNPACKED_TUPLES + for alias in aliases: + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(alias=alias, proto=proto): + s = pickle.dumps(alias, proto) + loaded = pickle.loads(s) + self.assertEqual(loaded.__origin__, alias.__origin__) + self.assertEqual(loaded.__args__, alias.__args__) + self.assertEqual(loaded.__parameters__, alias.__parameters__) + self.assertEqual(type(loaded), type(alias)) def test_copy(self): class X(list): @@ -285,16 +387,30 @@ def __copy__(self): def __deepcopy__(self, memo): return self - for origin in list, deque, X: - alias = GenericAlias(origin, T) - copied = copy.copy(alias) - self.assertEqual(copied.__origin__, alias.__origin__) - self.assertEqual(copied.__args__, alias.__args__) - self.assertEqual(copied.__parameters__, alias.__parameters__) - copied = copy.deepcopy(alias) - self.assertEqual(copied.__origin__, alias.__origin__) - self.assertEqual(copied.__args__, alias.__args__) - self.assertEqual(copied.__parameters__, alias.__parameters__) + aliases = [ + GenericAlias(list, T), + GenericAlias(deque, T), + GenericAlias(X, T), + X[T], + list[T], + deque[T], + ] + _UNPACKED_TUPLES + for alias in aliases: + with self.subTest(alias=alias): + copied = copy.copy(alias) + self.assertEqual(copied.__origin__, alias.__origin__) + self.assertEqual(copied.__args__, alias.__args__) + self.assertEqual(copied.__parameters__, alias.__parameters__) + copied = copy.deepcopy(alias) + self.assertEqual(copied.__origin__, alias.__origin__) + self.assertEqual(copied.__args__, alias.__args__) + self.assertEqual(copied.__parameters__, alias.__parameters__) + + def test_unpack(self): + alias = tuple[str, ...] + self.assertIs(alias.__unpacked__, False) + unpacked = (*alias,)[0] + self.assertIs(unpacked.__unpacked__, True) def test_union(self): a = typing.Union[list[int], list[str]] @@ -307,10 +423,26 @@ def test_union_generic(self): self.assertEqual(a.__parameters__, (T,)) def test_dir(self): - dir_of_gen_alias = set(dir(list[int])) + ga = list[int] + dir_of_gen_alias = set(dir(ga)) self.assertTrue(dir_of_gen_alias.issuperset(dir(list))) - for generic_alias_property in ("__origin__", "__args__", "__parameters__"): - self.assertIn(generic_alias_property, dir_of_gen_alias) + for generic_alias_property in ( + "__origin__", "__args__", "__parameters__", + "__unpacked__", + ): + with self.subTest(generic_alias_property=generic_alias_property): + self.assertIn(generic_alias_property, dir_of_gen_alias) + for blocked in ( + "__bases__", + "__copy__", + "__deepcopy__", + ): + with self.subTest(blocked=blocked): + self.assertNotIn(blocked, dir_of_gen_alias) + + for entry in dir_of_gen_alias: + with self.subTest(entry=entry): + getattr(ga, entry) # must not raise `AttributeError` def test_weakref(self): for t in self.generic_types: @@ -337,6 +469,44 @@ def __new__(cls, *args, **kwargs): with self.assertRaises(TypeError): Bad(list, int, bad=int) + def test_iter_creates_starred_tuple(self): + t = tuple[int, str] + iter_t = iter(t) + x = next(iter_t) + self.assertEqual(repr(x), '*tuple[int, str]') + + def test_calling_next_twice_raises_stopiteration(self): + t = tuple[int, str] + iter_t = iter(t) + next(iter_t) + with self.assertRaises(StopIteration): + next(iter_t) + + def test_del_iter(self): + t = tuple[int, str] + iter_x = iter(t) + del iter_x + + +class TypeIterationTests(unittest.TestCase): + _UNITERABLE_TYPES = (list, tuple) + + def test_cannot_iterate(self): + for test_type in self._UNITERABLE_TYPES: + with self.subTest(type=test_type): + expected_error_regex = "object is not iterable" + with self.assertRaisesRegex(TypeError, expected_error_regex): + iter(test_type) + with self.assertRaisesRegex(TypeError, expected_error_regex): + list(test_type) + with self.assertRaisesRegex(TypeError, expected_error_regex): + for _ in test_type: + pass + + def test_is_not_instance_of_iterable(self): + for type_to_test in self._UNITERABLE_TYPES: + self.assertNotIsInstance(type_to_test, Iterable) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_htmlparser.py b/Lib/test/test_htmlparser.py index 380bbe40177..6a1d69335a0 100644 --- a/Lib/test/test_htmlparser.py +++ b/Lib/test/test_htmlparser.py @@ -10,10 +10,13 @@ class EventCollector(html.parser.HTMLParser): - def __init__(self, *args, **kw): + def __init__(self, *args, autocdata=False, **kw): + self.autocdata = autocdata self.events = [] self.append = self.events.append html.parser.HTMLParser.__init__(self, *args, **kw) + if autocdata: + self._set_support_cdata(False) def get_events(self): # Normalize the list of events so that buffer artefacts don't @@ -34,12 +37,16 @@ def get_events(self): def handle_starttag(self, tag, attrs): self.append(("starttag", tag, attrs)) + if self.autocdata and tag == 'svg': + self._set_support_cdata(True) def handle_startendtag(self, tag, attrs): self.append(("startendtag", tag, attrs)) def handle_endtag(self, tag): self.append(("endtag", tag)) + if self.autocdata and tag == 'svg': + self._set_support_cdata(False) # all other markup @@ -767,10 +774,6 @@ def test_eof_in_declarations(self): ('<!', [('comment', '')]), ('<!-', [('comment', '-')]), ('<![', [('comment', '[')]), - ('<![CDATA[', [('unknown decl', 'CDATA[')]), - ('<![CDATA[x', [('unknown decl', 'CDATA[x')]), - ('<![CDATA[x]', [('unknown decl', 'CDATA[x]')]), - ('<![CDATA[x]]', [('unknown decl', 'CDATA[x]]')]), ('<!DOCTYPE', [('decl', 'DOCTYPE')]), ('<!DOCTYPE ', [('decl', 'DOCTYPE ')]), ('<!DOCTYPE html', [('decl', 'DOCTYPE html')]), @@ -783,6 +786,18 @@ def test_eof_in_declarations(self): for html, expected in data: self._run_check(html, expected) + @support.subTests('content', ['', 'x', 'x]', 'x]]']) + def test_eof_in_cdata(self, content): + self._run_check('<![CDATA[' + content, + [('unknown decl', 'CDATA[' + content)]) + self._run_check('<![CDATA[' + content, + [('comment', '[CDATA[' + content)], + collector=EventCollector(autocdata=True)) + self._run_check('<svg><text y="100"><![CDATA[' + content, + [('starttag', 'svg', []), + ('starttag', 'text', [('y', '100')]), + ('unknown decl', 'CDATA[' + content)]) + def test_bogus_comments(self): html = ('<!ELEMENT br EMPTY>' '<! not really a comment >' @@ -845,28 +860,53 @@ def test_broken_condcoms(self): ] self._run_check(html, expected) - def test_cdata_declarations(self): - # More tests should be added. See also "8.2.4.42. Markup - # declaration open state", "8.2.4.69. CDATA section state", - # and issue 32876 - html = ('<![CDATA[just some plain text]]>') - expected = [('unknown decl', 'CDATA[just some plain text')] + @support.subTests('content', [ + 'just some plain text', + '<!-- not a comment -->', + '¬-an-entity-ref;', + "<not a='start tag'>", + '', + '[[I have many brackets]]', + 'I have a > in the middle', + 'I have a ]] in the middle', + '] ]>', + ']] >', + ('\n' + ' if (a < b && a > b) {\n' + ' printf("[<marquee>How?</marquee>]");\n' + ' }\n'), + ]) + def test_cdata_section_content(self, content): + # See "13.2.5.42 Markup declaration open state", + # "13.2.5.69 CDATA section state", and issue bpo-32876. + html = f'<svg><text y="100"><![CDATA[{content}]]></text></svg>' + expected = [ + ('starttag', 'svg', []), + ('starttag', 'text', [('y', '100')]), + ('unknown decl', 'CDATA[' + content), + ('endtag', 'text'), + ('endtag', 'svg'), + ] self._run_check(html, expected) + self._run_check(html, expected, collector=EventCollector(autocdata=True)) - def test_cdata_declarations_multiline(self): - html = ('<code><![CDATA[' - ' if (a < b && a > b) {' - ' printf("[<marquee>How?</marquee>]");' - ' }' - ']]></code>') + def test_cdata_section(self): + # See "13.2.5.42 Markup declaration open state". + html = ('<![CDATA[foo<br>bar]]>' + '<svg><text y="100"><![CDATA[foo<br>bar]]></text></svg>' + '<![CDATA[foo<br>bar]]>') expected = [ - ('starttag', 'code', []), - ('unknown decl', - 'CDATA[ if (a < b && a > b) { ' - 'printf("[<marquee>How?</marquee>]"); }'), - ('endtag', 'code') + ('comment', '[CDATA[foo<br'), + ('data', 'bar]]>'), + ('starttag', 'svg', []), + ('starttag', 'text', [('y', '100')]), + ('unknown decl', 'CDATA[foo<br>bar'), + ('endtag', 'text'), + ('endtag', 'svg'), + ('comment', '[CDATA[foo<br'), + ('data', 'bar]]>'), ] - self._run_check(html, expected) + self._run_check(html, expected, collector=EventCollector(autocdata=True)) def test_convert_charrefs_dropped_text(self): # #23144: make sure that all the events are triggered when diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py index 7e368f115c1..9e1f46f6444 100644 --- a/Lib/test/test_locale.py +++ b/Lib/test/test_locale.py @@ -345,8 +345,7 @@ def setUp(self): enc = codecs.lookup(locale.getencoding() or 'ascii').name if enc not in ('utf-8', 'iso8859-1', 'cp1252'): raise unittest.SkipTest('encoding not suitable') - if enc != 'iso8859-1' and (sys.platform == 'darwin' or is_android or - sys.platform.startswith('freebsd')): + if enc != 'iso8859-1' and is_android: raise unittest.SkipTest('wcscoll/wcsxfrm have known bugs') BaseLocalizedTest.setUp(self) @@ -512,7 +511,7 @@ def test_getsetlocale_issue1813(self): self.skipTest(f"setlocale(LC_CTYPE, {loc!r}) failed: {exc!r}") self.assertEqual(loc, locale.getlocale(locale.LC_CTYPE)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; Error not raised") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; Error not raised') @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_setlocale_long_encoding(self): with self.assertRaises(locale.Error): diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index 3b8690367ce..dbcc85bd69e 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1324,17 +1324,22 @@ def equivalent_python(n, length, byteorder, signed=False): check(tests4, 'little', signed=False) self.assertRaises(OverflowError, (256).to_bytes, 1, 'big', signed=False) - self.assertRaises(OverflowError, (256).to_bytes, 1, 'big', signed=True) self.assertRaises(OverflowError, (256).to_bytes, 1, 'little', signed=False) - self.assertRaises(OverflowError, (256).to_bytes, 1, 'little', signed=True) + self.assertRaises(OverflowError, (128).to_bytes, 1, 'big', signed=True) + self.assertRaises(OverflowError, (128).to_bytes, 1, 'little', signed=True) + self.assertRaises(OverflowError, (-129).to_bytes, 1, 'big', signed=True) + self.assertRaises(OverflowError, (-129).to_bytes, 1, 'little', signed=True) self.assertRaises(OverflowError, (-1).to_bytes, 2, 'big', signed=False) self.assertRaises(OverflowError, (-1).to_bytes, 2, 'little', signed=False) self.assertEqual((0).to_bytes(0, 'big'), b'') + self.assertEqual((0).to_bytes(0, 'big', signed=True), b'') self.assertEqual((1).to_bytes(5, 'big'), b'\x00\x00\x00\x00\x01') self.assertEqual((0).to_bytes(5, 'big'), b'\x00\x00\x00\x00\x00') self.assertEqual((-1).to_bytes(5, 'big', signed=True), b'\xff\xff\xff\xff\xff') self.assertRaises(OverflowError, (1).to_bytes, 0, 'big') + self.assertRaises(OverflowError, (-1).to_bytes, 0, 'big', signed=True) + self.assertRaises(OverflowError, (-1).to_bytes, 0, 'little', signed=True) # gh-98783 class SubStr(str): @@ -1646,5 +1651,21 @@ class MyInt(int): # GH-117195 -- This shouldn't crash object.__sizeof__(1) + def test_hash(self): + # gh-136599 + self.assertEqual(hash(-1), -2) + self.assertEqual(hash(0), 0) + self.assertEqual(hash(10), 10) + + self.assertEqual(hash(sys.hash_info.modulus - 2), sys.hash_info.modulus - 2) + self.assertEqual(hash(sys.hash_info.modulus - 1), sys.hash_info.modulus - 1) + self.assertEqual(hash(sys.hash_info.modulus), 0) + self.assertEqual(hash(sys.hash_info.modulus + 1), 1) + + self.assertEqual(hash(-sys.hash_info.modulus - 2), -2) + self.assertEqual(hash(-sys.hash_info.modulus - 1), -2) + self.assertEqual(hash(-sys.hash_info.modulus), 0) + self.assertEqual(hash(-sys.hash_info.modulus + 1), -sys.hash_info.modulus + 1) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index dc0c6fe51e6..c327d2add2f 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1,17 +1,19 @@ "Test posix functions" from test import support -from test.support import import_helper +from test.support import is_apple from test.support import os_helper from test.support import warnings_helper from test.support.script_helper import assert_python_ok +import copy import errno import sys import signal import time import os import platform +import pickle import stat import tempfile import unittest @@ -411,8 +413,10 @@ def test_posix_fallocate(self): # issue33655: Also ignore EINVAL on *BSD since ZFS is also # often used there. if inst.errno == errno.EINVAL and sys.platform.startswith( - ('sunos', 'freebsd', 'netbsd', 'openbsd', 'gnukfreebsd')): + ('sunos', 'freebsd', 'openbsd', 'gnukfreebsd')): raise unittest.SkipTest("test may fail on ZFS filesystems") + elif inst.errno == errno.EOPNOTSUPP and sys.platform.startswith("netbsd"): + raise unittest.SkipTest("test may fail on FFS filesystems") else: raise finally: @@ -565,8 +569,37 @@ def test_dup(self): @unittest.skipUnless(hasattr(posix, 'confstr'), 'test needs posix.confstr()') def test_confstr(self): - self.assertRaises(ValueError, posix.confstr, "CS_garbage") - self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True) + with self.assertRaisesRegex( + ValueError, "unrecognized configuration name" + ): + posix.confstr("CS_garbage") + + with self.assertRaisesRegex( + TypeError, "configuration names must be strings or integers" + ): + posix.confstr(1.23) + + path = posix.confstr("CS_PATH") + self.assertGreater(len(path), 0) + self.assertEqual(posix.confstr(posix.confstr_names["CS_PATH"]), path) + + @unittest.skipUnless(hasattr(posix, 'sysconf'), + 'test needs posix.sysconf()') + def test_sysconf(self): + with self.assertRaisesRegex( + ValueError, "unrecognized configuration name" + ): + posix.sysconf("SC_garbage") + + with self.assertRaisesRegex( + TypeError, "configuration names must be strings or integers" + ): + posix.sysconf(1.23) + + arg_max = posix.sysconf("SC_ARG_MAX") + self.assertGreater(arg_max, 0) + self.assertEqual( + posix.sysconf(posix.sysconf_names["SC_ARG_MAX"]), arg_max) @unittest.skipUnless(hasattr(posix, 'dup2'), 'test needs posix.dup2()') @@ -703,7 +736,8 @@ def test_makedev(self): self.assertEqual(posix.major(dev), major) self.assertRaises(TypeError, posix.major, float(dev)) self.assertRaises(TypeError, posix.major) - self.assertRaises((ValueError, OverflowError), posix.major, -1) + for x in -2, 2**64, -2**63-1: + self.assertRaises((ValueError, OverflowError), posix.major, x) minor = posix.minor(dev) self.assertIsInstance(minor, int) @@ -711,13 +745,23 @@ def test_makedev(self): self.assertEqual(posix.minor(dev), minor) self.assertRaises(TypeError, posix.minor, float(dev)) self.assertRaises(TypeError, posix.minor) - self.assertRaises((ValueError, OverflowError), posix.minor, -1) + for x in -2, 2**64, -2**63-1: + self.assertRaises((ValueError, OverflowError), posix.minor, x) self.assertEqual(posix.makedev(major, minor), dev) self.assertRaises(TypeError, posix.makedev, float(major), minor) self.assertRaises(TypeError, posix.makedev, major, float(minor)) self.assertRaises(TypeError, posix.makedev, major) self.assertRaises(TypeError, posix.makedev) + for x in -2, 2**32, 2**64, -2**63-1: + self.assertRaises((ValueError, OverflowError), posix.makedev, x, minor) + self.assertRaises((ValueError, OverflowError), posix.makedev, major, x) + + if sys.platform == 'linux' and not support.linked_to_musl(): + NODEV = -1 + self.assertEqual(posix.major(NODEV), NODEV) + self.assertEqual(posix.minor(NODEV), NODEV) + self.assertEqual(posix.makedev(NODEV, NODEV), NODEV) def _test_all_chown_common(self, chown_func, first_param, stat_func): """Common code for chown, fchown and lchown tests.""" @@ -781,9 +825,10 @@ def check_stat(uid, gid): check_stat(uid, gid) self.assertRaises(OSError, chown_func, first_param, 0, -1) check_stat(uid, gid) - if 0 not in os.getgroups(): - self.assertRaises(OSError, chown_func, first_param, -1, 0) - check_stat(uid, gid) + if hasattr(os, 'getgroups'): + if 0 not in os.getgroups(): + self.assertRaises(OSError, chown_func, first_param, -1, 0) + check_stat(uid, gid) # test illegal types for t in str, float: self.assertRaises(TypeError, chown_func, first_param, t(uid), gid) @@ -936,6 +981,7 @@ def test_utime(self): posix.utime(os_helper.TESTFN, (now, now)) def check_chmod(self, chmod_func, target, **kwargs): + closefd = not isinstance(target, int) mode = os.stat(target).st_mode try: new_mode = mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) @@ -943,7 +989,7 @@ def check_chmod(self, chmod_func, target, **kwargs): self.assertEqual(os.stat(target).st_mode, new_mode) if stat.S_ISREG(mode): try: - with open(target, 'wb+'): + with open(target, 'wb+', closefd=closefd): pass except PermissionError: pass @@ -951,10 +997,10 @@ def check_chmod(self, chmod_func, target, **kwargs): chmod_func(target, new_mode, **kwargs) self.assertEqual(os.stat(target).st_mode, new_mode) if stat.S_ISREG(mode): - with open(target, 'wb+'): + with open(target, 'wb+', closefd=closefd): pass finally: - posix.chmod(target, mode) + chmod_func(target, mode) @os_helper.skip_unless_working_chmod def test_chmod_file(self): @@ -971,6 +1017,12 @@ def test_chmod_dir(self): target = self.tempdir() self.check_chmod(posix.chmod, target) + @os_helper.skip_unless_working_chmod + def test_fchmod_file(self): + with open(os_helper.TESTFN, 'wb+') as f: + self.check_chmod(posix.fchmod, f.fileno()) + self.check_chmod(posix.chmod, f.fileno()) + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') def test_lchmod_file(self): self.check_chmod(posix.lchmod, os_helper.TESTFN) @@ -1019,7 +1071,7 @@ def test_chmod_file_symlink(self): self.check_lchmod_link(posix.chmod, target, link) else: self.check_chmod_link(posix.chmod, target, link) - self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) @os_helper.skip_unless_symlink def test_chmod_dir_symlink(self): @@ -1031,7 +1083,7 @@ def test_chmod_dir_symlink(self): self.check_lchmod_link(posix.chmod, target, link) else: self.check_chmod_link(posix.chmod, target, link) - self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') @os_helper.skip_unless_symlink @@ -1249,8 +1301,8 @@ def test_sched_priority(self): self.assertIsInstance(lo, int) self.assertIsInstance(hi, int) self.assertGreaterEqual(hi, lo) - # OSX evidently just returns 15 without checking the argument. - if sys.platform != "darwin": + # Apple platforms return 15 without checking the argument. + if not is_apple: self.assertRaises(OSError, posix.sched_get_priority_min, -23) self.assertRaises(OSError, posix.sched_get_priority_max, -23) @@ -1262,9 +1314,10 @@ def test_get_and_set_scheduler_and_param(self): self.assertIn(mine, possible_schedulers) try: parent = posix.sched_getscheduler(os.getppid()) - except OSError as e: - if e.errno != errno.EPERM: - raise + except PermissionError: + # POSIX specifies EPERM, but Android returns EACCES. Both errno + # values are mapped to PermissionError. + pass else: self.assertIn(parent, possible_schedulers) self.assertRaises(OSError, posix.sched_getscheduler, -1) @@ -1279,9 +1332,8 @@ def test_get_and_set_scheduler_and_param(self): try: posix.sched_setscheduler(0, mine, param) posix.sched_setparam(0, param) - except OSError as e: - if e.errno != errno.EPERM: - raise + except PermissionError: + pass self.assertRaises(OSError, posix.sched_setparam, -1, param) self.assertRaises(OSError, posix.sched_setscheduler, -1, mine, param) @@ -1295,6 +1347,25 @@ def test_get_and_set_scheduler_and_param(self): param = posix.sched_param(sched_priority=-large) self.assertRaises(OverflowError, posix.sched_setparam, 0, param) + @requires_sched + def test_sched_param(self): + param = posix.sched_param(1) + for proto in range(pickle.HIGHEST_PROTOCOL+1): + newparam = pickle.loads(pickle.dumps(param, proto)) + self.assertEqual(newparam, param) + newparam = copy.copy(param) + self.assertIsNot(newparam, param) + self.assertEqual(newparam, param) + newparam = copy.deepcopy(param) + self.assertIsNot(newparam, param) + self.assertEqual(newparam, param) + newparam = copy.replace(param) + self.assertIsNot(newparam, param) + self.assertEqual(newparam, param) + newparam = copy.replace(param, sched_priority=0) + self.assertNotEqual(newparam, param) + self.assertEqual(newparam.sched_priority, 0) + @unittest.skipUnless(hasattr(posix, "sched_rr_get_interval"), "no function") def test_sched_rr_get_interval(self): try: @@ -1515,6 +1586,13 @@ def test_stat_dir_fd(self): self.assertRaises(OverflowError, posix.stat, name, dir_fd=10**20) + for fd in False, True: + with self.assertWarnsRegex(RuntimeWarning, + 'bool is used as a file descriptor') as cm: + with self.assertRaises(OSError): + posix.stat('nonexisting', dir_fd=fd) + self.assertEqual(cm.filename, __file__) + @unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()") def test_utime_dir_fd(self): with self.prepare_file() as (dir_fd, name, fullname): @@ -1890,7 +1968,6 @@ def test_setsigdef_wrong_type(self): [sys.executable, "-c", "pass"], os.environ, setsigdef=[signal.NSIG, signal.NSIG+1]) - @unittest.expectedFailure @requires_sched @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), "bpo-34685: test can fail on BSD") @@ -1911,10 +1988,14 @@ def test_setscheduler_only_param(self): ) support.wait_process(pid, exitcode=0) - @unittest.expectedFailure @requires_sched @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), "bpo-34685: test can fail on BSD") + @unittest.skipIf(platform.libc_ver()[0] == 'glibc' and + os.sched_getscheduler(0) in [ + os.SCHED_BATCH, + os.SCHED_IDLE], + "Skip test due to glibc posix_spawn policy") def test_setscheduler_with_policy(self): policy = os.sched_getscheduler(0) priority = os.sched_get_priority_min(policy) @@ -1993,10 +2074,6 @@ def test_open_file(self): with open(outfile, encoding="utf-8") as f: self.assertEqual(f.read(), 'hello') - # TODO: RUSTPYTHON: the rust runtime reopens closed stdio fds at startup, - # so this test fails, even though POSIX_SPAWN_CLOSE does - # actually have an effect - @unittest.expectedFailure def test_close_file(self): closefile = os_helper.TESTFN self.addCleanup(os_helper.unlink, closefile) @@ -2036,11 +2113,13 @@ def test_dup2(self): @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn") +@support.requires_subprocess() class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin): spawn_func = getattr(posix, 'posix_spawn', None) @unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp") +@support.requires_subprocess() class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin): spawn_func = getattr(posix, 'posix_spawnp', None) @@ -2117,6 +2196,13 @@ def test_stat(self): with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): os.stat("file", dir_fd=0) + def test_ptsname_r(self): + self._verify_available("HAVE_PTSNAME_R") + if self.mac_ver >= (10, 13, 4): + self.assertIn("HAVE_PTSNAME_R", posix._have_functions) + else: + self.assertNotIn("HAVE_PTSNAME_R", posix._have_functions) + def test_access(self): self._verify_available("HAVE_FACCESSAT") if self.mac_ver >= (10, 10): diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index 33e1ffb836c..9747cf66218 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -1,13 +1,14 @@ # XXX TypeErrors on calling handlers, or on bad return values from a # handler, are obscure and unhelpful. -from io import BytesIO import os -import platform import sys import sysconfig import unittest import traceback +from io import BytesIO +from test import support +from test.support import os_helper from xml.parsers import expat from xml.parsers.expat import errors @@ -19,32 +20,28 @@ class SetAttributeTest(unittest.TestCase): def setUp(self): self.parser = expat.ParserCreate(namespace_separator='!') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_buffer_text(self): self.assertIs(self.parser.buffer_text, False) for x in 0, 1, 2, 0: self.parser.buffer_text = x self.assertIs(self.parser.buffer_text, bool(x)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_namespace_prefixes(self): self.assertIs(self.parser.namespace_prefixes, False) for x in 0, 1, 2, 0: self.parser.namespace_prefixes = x self.assertIs(self.parser.namespace_prefixes, bool(x)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_ordered_attributes(self): self.assertIs(self.parser.ordered_attributes, False) for x in 0, 1, 2, 0: self.parser.ordered_attributes = x self.assertIs(self.parser.ordered_attributes, bool(x)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_specified_attributes(self): self.assertIs(self.parser.specified_attributes, False) for x in 0, 1, 2, 0: @@ -234,8 +231,7 @@ def _verify_parse_output(self, operations): for operation, expected_operation in zip(operations, expected_operations): self.assertEqual(operation, expected_operation) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_bytes(self): out = self.Outputter() parser = expat.ParserCreate(namespace_separator='!') @@ -248,8 +244,7 @@ def test_parse_bytes(self): # Issue #6697. self.assertRaises(AttributeError, getattr, parser, '\uD800') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_str(self): out = self.Outputter() parser = expat.ParserCreate(namespace_separator='!') @@ -260,8 +255,7 @@ def test_parse_str(self): operations = out.out self._verify_parse_output(operations) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_file(self): # Try parsing a file out = self.Outputter() @@ -274,8 +268,7 @@ def test_parse_file(self): operations = out.out self._verify_parse_output(operations) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_again(self): parser = expat.ParserCreate() file = BytesIO(data) @@ -289,8 +282,7 @@ def test_parse_again(self): expat.errors.XML_ERROR_FINISHED) class NamespaceSeparatorTest(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_legal(self): # Tests that make sure we get errors when the namespace_separator value # is illegal, and that we don't for good values: @@ -298,15 +290,12 @@ def test_legal(self): expat.ParserCreate(namespace_separator=None) expat.ParserCreate(namespace_separator=' ') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_illegal(self): - try: + with self.assertRaisesRegex(TypeError, + r"ParserCreate\(\) argument (2|'namespace_separator') " + r"must be str or None, not int"): expat.ParserCreate(namespace_separator=42) - self.fail() - except TypeError as e: - self.assertEqual(str(e), - "ParserCreate() argument 'namespace_separator' must be str or None, not int") try: expat.ParserCreate(namespace_separator='too long') @@ -328,9 +317,7 @@ def test_zero_length(self): class InterningTest(unittest.TestCase): - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test(self): # Test the interning machinery. p = expat.ParserCreate() @@ -346,8 +333,7 @@ def collector(name, *args): # L should have the same string repeated over and over. self.assertTrue(tag is entry) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_issue9402(self): # create an ExternalEntityParserCreate with buffer text class ExternalOutputter: @@ -405,8 +391,7 @@ def test_default_to_disabled(self): parser = expat.ParserCreate() self.assertFalse(parser.buffer_text) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_buffering_enabled(self): # Make sure buffering is turned on self.assertTrue(self.parser.buffer_text) @@ -414,8 +399,7 @@ def test_buffering_enabled(self): self.assertEqual(self.stuff, ['123'], "buffered text not properly collapsed") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test1(self): # XXX This test exposes more detail of Expat's text chunking than we # XXX like, but it tests what we need to concisely. @@ -425,23 +409,20 @@ def test1(self): ["<a>", "1", "<b>", "2", "\n", "3", "<c>", "4\n5"], "buffering control not reacting as expected") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test2(self): self.parser.Parse(b"<a>1<b/><2><c/> \n 3</a>", True) self.assertEqual(self.stuff, ["1<2> \n 3"], "buffered text not properly collapsed") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test3(self): self.setHandlers(["StartElementHandler"]) self.parser.Parse(b"<a>1<b/>2<c/>3</a>", True) self.assertEqual(self.stuff, ["<a>", "1", "<b>", "2", "<c>", "3"], "buffered text not properly split") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test4(self): self.setHandlers(["StartElementHandler", "EndElementHandler"]) self.parser.CharacterDataHandler = None @@ -449,16 +430,14 @@ def test4(self): self.assertEqual(self.stuff, ["<a>", "<b>", "</b>", "<c>", "</c>", "</a>"]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test5(self): self.setHandlers(["StartElementHandler", "EndElementHandler"]) self.parser.Parse(b"<a>1<b></b>2<c/>3</a>", True) self.assertEqual(self.stuff, ["<a>", "1", "<b>", "</b>", "2", "<c>", "</c>", "3", "</a>"]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test6(self): self.setHandlers(["CommentHandler", "EndElementHandler", "StartElementHandler"]) @@ -467,8 +446,7 @@ def test6(self): ["<a>", "1", "<b>", "</b>", "2", "<c>", "</c>", "345", "</a>"], "buffered text not properly split") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test7(self): self.setHandlers(["CommentHandler", "EndElementHandler", "StartElementHandler"]) @@ -482,35 +460,60 @@ def test7(self): # Test handling of exception from callback: class HandlerExceptionTest(unittest.TestCase): def StartElementHandler(self, name, attrs): - raise RuntimeError(name) + raise RuntimeError(f'StartElementHandler: <{name}>') def check_traceback_entry(self, entry, filename, funcname): - self.assertEqual(os.path.basename(entry[0]), filename) - self.assertEqual(entry[2], funcname) + self.assertEqual(os.path.basename(entry.filename), filename) + self.assertEqual(entry.name, funcname) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.cpython_only def test_exception(self): + # gh-66652: test _PyTraceback_Add() used by pyexpat.c to inject frames + + # Change the current directory to the Python source code directory + # if it is available. + src_dir = sysconfig.get_config_var('abs_builddir') + if src_dir: + have_source = os.path.isdir(src_dir) + else: + have_source = False + if have_source: + with os_helper.change_cwd(src_dir): + self._test_exception(have_source) + else: + self._test_exception(have_source) + + def _test_exception(self, have_source): + # Use path relative to the current directory which should be the Python + # source code directory (if it is available). + PYEXPAT_C = os.path.join('Modules', 'pyexpat.c') + parser = expat.ParserCreate() parser.StartElementHandler = self.StartElementHandler try: parser.Parse(b"<a><b><c/></b></a>", True) - self.fail() - except RuntimeError as e: - self.assertEqual(e.args[0], 'a', - "Expected RuntimeError for element 'a', but" + \ - " found %r" % e.args[0]) - # Check that the traceback contains the relevant line in pyexpat.c - entries = traceback.extract_tb(e.__traceback__) - self.assertEqual(len(entries), 3) - self.check_traceback_entry(entries[0], - "test_pyexpat.py", "test_exception") - self.check_traceback_entry(entries[1], - "pyexpat.c", "StartElement") - self.check_traceback_entry(entries[2], - "test_pyexpat.py", "StartElementHandler") - if sysconfig.is_python_build() and not (sys.platform == 'win32' and platform.machine() == 'ARM'): - self.assertIn('call_with_frame("StartElement"', entries[1][3]) + + self.fail("the parser did not raise RuntimeError") + except RuntimeError as exc: + self.assertEqual(exc.args[0], 'StartElementHandler: <a>', exc) + entries = traceback.extract_tb(exc.__traceback__) + + self.assertEqual(len(entries), 3, entries) + self.check_traceback_entry(entries[0], + "test_pyexpat.py", "_test_exception") + self.check_traceback_entry(entries[1], + os.path.basename(PYEXPAT_C), + "StartElement") + self.check_traceback_entry(entries[2], + "test_pyexpat.py", "StartElementHandler") + + # Check that the traceback contains the relevant line in + # Modules/pyexpat.c. Skip the test if Modules/pyexpat.c is not + # available. + if have_source and os.path.exists(PYEXPAT_C): + self.assertIn('call_with_frame("StartElement"', + entries[1].line) # Test Current* members: @@ -533,8 +536,7 @@ def check_pos(self, event): 'Expected position %s, got position %s' %(pos, expected)) self.upto += 1 - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test(self): self.parser = expat.ParserCreate() self.parser.StartElementHandler = self.StartElementHandler @@ -548,9 +550,8 @@ def test(self): class sf1296433Test(unittest.TestCase): - def test_parse_only_xml_data(self): - # http://python.org/sf/1296433 + # https://bugs.python.org/issue1296433 # xml = "<?xml version='1.0' encoding='iso8859'?><s>%s</s>" % ('a' * 1025) # this one doesn't crash @@ -565,25 +566,22 @@ def handler(text): parser = expat.ParserCreate() parser.CharacterDataHandler = handler - self.assertRaises(Exception, parser.Parse, xml.encode('iso8859')) + self.assertRaises(SpecificException, parser.Parse, xml.encode('iso8859')) class ChardataBufferTest(unittest.TestCase): """ test setting of chardata buffer size """ - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_1025_bytes(self): self.assertEqual(self.small_buffer_test(1025), 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_1000_bytes(self): self.assertEqual(self.small_buffer_test(1000), 1) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_wrong_size(self): parser = expat.ParserCreate() parser.buffer_text = 1 @@ -596,8 +594,7 @@ def test_wrong_size(self): with self.assertRaises(TypeError): parser.buffer_size = 512.0 - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unchanged_size(self): xml1 = b"<?xml version='1.0' encoding='iso8859'?><s>" + b'a' * 512 xml2 = b'a'*512 + b'</s>' @@ -620,8 +617,8 @@ def test_unchanged_size(self): parser.Parse(xml2) self.assertEqual(self.n, 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_disabling_buffer(self): xml1 = b"<?xml version='1.0' encoding='iso8859'?><a>" + b'a' * 512 xml2 = b'b' * 1024 @@ -666,8 +663,7 @@ def small_buffer_test(self, buffer_len): parser.Parse(xml) return self.n - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_change_size_1(self): xml1 = b"<?xml version='1.0' encoding='iso8859'?><a><s>" + b'a' * 1024 xml2 = b'aaa</s><s>' + b'a' * 1025 + b'</s></a>' @@ -684,8 +680,7 @@ def test_change_size_1(self): parser.Parse(xml2, True) self.assertEqual(self.n, 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_change_size_2(self): xml1 = b"<?xml version='1.0' encoding='iso8859'?><a>a<s>" + b'a' * 1023 xml2 = b'aaa</s><s>' + b'a' * 1025 + b'</s></a>' @@ -703,9 +698,7 @@ def test_change_size_2(self): self.assertEqual(self.n, 4) class MalformedInputTest(unittest.TestCase): - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test1(self): xml = b"\0\r\n" parser = expat.ParserCreate() @@ -715,8 +708,7 @@ def test1(self): except expat.ExpatError as e: self.assertEqual(str(e), 'unclosed token: line 2, column 0') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test2(self): # \xc2\x85 is UTF-8 encoded U+0085 (NEXT LINE) xml = b"<?xml version\xc2\x85='1.0'?>\r\n" @@ -726,16 +718,13 @@ def test2(self): parser.Parse(xml, True) class ErrorMessageTest(unittest.TestCase): - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_codes(self): # verify mapping of errors.codes and errors.messages self.assertEqual(errors.XML_ERROR_SYNTAX, errors.messages[errors.codes[errors.XML_ERROR_SYNTAX]]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_expaterror(self): xml = b'<' parser = expat.ParserCreate() @@ -751,9 +740,7 @@ class ForeignDTDTests(unittest.TestCase): """ Tests for the UseForeignDTD method of expat parser objects. """ - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_use_foreign_dtd(self): """ If UseForeignDTD is passed True and a document without an external @@ -782,8 +769,7 @@ def resolve_entity(context, base, system_id, public_id): parser.Parse(b"<?xml version='1.0'?><element/>") self.assertEqual(handler_call_args, [(None, None)]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_ignore_use_foreign_dtd(self): """ If UseForeignDTD is passed True and a document with an external @@ -804,5 +790,95 @@ def resolve_entity(context, base, system_id, public_id): self.assertEqual(handler_call_args, [("bar", "baz")]) +class ParentParserLifetimeTest(unittest.TestCase): + """ + Subparsers make use of their parent XML_Parser inside of Expat. + As a result, parent parsers need to outlive subparsers. + + See https://github.com/python/cpython/issues/139400. + """ + + def test_parent_parser_outlives_its_subparsers__single(self): + parser = expat.ParserCreate() + subparser = parser.ExternalEntityParserCreate(None) + + # Now try to cause garbage collection of the parent parser + # while it's still being referenced by a related subparser. + del parser + + def test_parent_parser_outlives_its_subparsers__multiple(self): + parser = expat.ParserCreate() + subparser_one = parser.ExternalEntityParserCreate(None) + subparser_two = parser.ExternalEntityParserCreate(None) + + # Now try to cause garbage collection of the parent parser + # while it's still being referenced by a related subparser. + del parser + + def test_parent_parser_outlives_its_subparsers__chain(self): + parser = expat.ParserCreate() + subparser = parser.ExternalEntityParserCreate(None) + subsubparser = subparser.ExternalEntityParserCreate(None) + + # Now try to cause garbage collection of the parent parsers + # while they are still being referenced by a related subparser. + del parser + del subparser + + +class ReparseDeferralTest(unittest.TestCase): + def test_getter_setter_round_trip(self): + parser = expat.ParserCreate() + enabled = (expat.version_info >= (2, 6, 0)) + + self.assertIs(parser.GetReparseDeferralEnabled(), enabled) + parser.SetReparseDeferralEnabled(False) + self.assertIs(parser.GetReparseDeferralEnabled(), False) + parser.SetReparseDeferralEnabled(True) + self.assertIs(parser.GetReparseDeferralEnabled(), enabled) + + def test_reparse_deferral_enabled(self): + if expat.version_info < (2, 6, 0): + self.skipTest(f'Expat {expat.version_info} does not ' + 'support reparse deferral') + + started = [] + + def start_element(name, _): + started.append(name) + + parser = expat.ParserCreate() + parser.StartElementHandler = start_element + self.assertTrue(parser.GetReparseDeferralEnabled()) + + for chunk in (b'<doc', b'/>'): + parser.Parse(chunk, False) + + # The key test: Have handlers already fired? Expecting: no. + self.assertEqual(started, []) + + parser.Parse(b'', True) + + self.assertEqual(started, ['doc']) + + def test_reparse_deferral_disabled(self): + started = [] + + def start_element(name, _): + started.append(name) + + parser = expat.ParserCreate() + parser.StartElementHandler = start_element + if expat.version_info >= (2, 6, 0): + parser.SetReparseDeferralEnabled(False) + self.assertFalse(parser.GetReparseDeferralEnabled()) + + for chunk in (b'<doc', b'/>'): + parser.Parse(chunk, False) + + # The key test: Have handlers already fired? Expecting: yes. + self.assertEqual(started, ['doc']) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index f5b8d42dba3..9001b817047 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -12,6 +12,7 @@ from test.support import socket_helper from test.support import captured_stderr from test.support.os_helper import TESTFN, EnvironmentVarGuard +from test.support.script_helper import spawn_python, kill_python import ast import builtins import glob @@ -24,6 +25,7 @@ import sys import sysconfig import tempfile +from textwrap import dedent import urllib.error import urllib.request from unittest import mock @@ -221,8 +223,7 @@ def test_addsitedir_hidden_flags(self): finally: pth_file.cleanup() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(sys.platform == 'win32', 'test needs Windows') @support.requires_subprocess() def test_addsitedir_hidden_file_attribute(self): @@ -330,15 +331,13 @@ def test_getsitepackages(self): if sys.platlibdir != "lib": self.assertEqual(len(dirs), 2) wanted = os.path.join('xoxo', sys.platlibdir, - # XXX: RUSTPYTHON - f'rustpython{sysconfig._get_python_version_abi()}', + f'python{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[0], wanted) else: self.assertEqual(len(dirs), 1) wanted = os.path.join('xoxo', 'lib', - # XXX: RUSTPYTHON - f'rustpython{sysconfig._get_python_version_abi()}', + f'python{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[-1], wanted) else: @@ -581,8 +580,7 @@ def test_license_exists_at_url(self): class StartupImportTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_startup_imports(self): # Get sys.path in isolated mode (python3 -I) @@ -715,8 +713,7 @@ def _get_pth_lines(self, libpath: str, *, import_site: bool): pth_lines.append('import site') return pth_lines - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_underpth_basic(self): pth_lines = ['#.', '# ..', *sys.path, '.', '..'] @@ -736,8 +733,7 @@ def test_underpth_basic(self): "sys.path is incorrect" ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_underpth_nosite_file(self): libpath = test.support.STDLIB_DIR @@ -762,8 +758,7 @@ def test_underpth_nosite_file(self): "sys.path is incorrect" ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_underpth_file(self): libpath = test.support.STDLIB_DIR @@ -784,8 +779,7 @@ def test_underpth_file(self): )], env=env) self.assertTrue(rc, "sys.path is incorrect") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_underpth_dll_file(self): libpath = test.support.STDLIB_DIR @@ -807,5 +801,107 @@ def test_underpth_dll_file(self): self.assertTrue(rc, "sys.path is incorrect") +class CommandLineTests(unittest.TestCase): + def exists(self, path): + if path is not None and os.path.isdir(path): + return "exists" + else: + return "doesn't exist" + + def get_excepted_output(self, *args): + if len(args) == 0: + user_base = site.getuserbase() + user_site = site.getusersitepackages() + output = io.StringIO() + output.write("sys.path = [\n") + for dir in sys.path: + output.write(" %r,\n" % (dir,)) + output.write("]\n") + output.write(f"USER_BASE: {user_base} ({self.exists(user_base)})\n") + output.write(f"USER_SITE: {user_site} ({self.exists(user_site)})\n") + output.write(f"ENABLE_USER_SITE: {site.ENABLE_USER_SITE}\n") + return 0, dedent(output.getvalue()).strip() + + buffer = [] + if '--user-base' in args: + buffer.append(site.getuserbase()) + if '--user-site' in args: + buffer.append(site.getusersitepackages()) + + if buffer: + return_code = 3 + if site.ENABLE_USER_SITE: + return_code = 0 + elif site.ENABLE_USER_SITE is False: + return_code = 1 + elif site.ENABLE_USER_SITE is None: + return_code = 2 + output = os.pathsep.join(buffer) + return return_code, os.path.normpath(dedent(output).strip()) + else: + return 10, None + + def invoke_command_line(self, *args): + args = ["-m", "site", *args] + + with EnvironmentVarGuard() as env: + env["PYTHONUTF8"] = "1" + env["PYTHONIOENCODING"] = "utf-8" + proc = spawn_python(*args, text=True, env=env, + encoding='utf-8', errors='replace') + + output = kill_python(proc) + return_code = proc.returncode + return return_code, os.path.normpath(dedent(output).strip()) + + @support.requires_subprocess() + def test_no_args(self): + return_code, output = self.invoke_command_line() + excepted_return_code, _ = self.get_excepted_output() + self.assertEqual(return_code, excepted_return_code) + lines = output.splitlines() + self.assertEqual(lines[0], "sys.path = [") + self.assertEqual(lines[-4], "]") + excepted_base = f"USER_BASE: '{site.getuserbase()}'" +\ + f" ({self.exists(site.getuserbase())})" + self.assertEqual(lines[-3], excepted_base) + excepted_site = f"USER_SITE: '{site.getusersitepackages()}'" +\ + f" ({self.exists(site.getusersitepackages())})" + self.assertEqual(lines[-2], excepted_site) + self.assertEqual(lines[-1], f"ENABLE_USER_SITE: {site.ENABLE_USER_SITE}") + + @support.requires_subprocess() + def test_unknown_args(self): + return_code, output = self.invoke_command_line("--unknown-arg") + excepted_return_code, _ = self.get_excepted_output("--unknown-arg") + self.assertEqual(return_code, excepted_return_code) + self.assertIn('[--user-base] [--user-site]', output) + + @support.requires_subprocess() + def test_base_arg(self): + return_code, output = self.invoke_command_line("--user-base") + excepted = self.get_excepted_output("--user-base") + excepted_return_code, excepted_output = excepted + self.assertEqual(return_code, excepted_return_code) + self.assertEqual(output, excepted_output) + + @support.requires_subprocess() + def test_site_arg(self): + return_code, output = self.invoke_command_line("--user-site") + excepted = self.get_excepted_output("--user-site") + excepted_return_code, excepted_output = excepted + self.assertEqual(return_code, excepted_return_code) + self.assertEqual(output, excepted_output) + + @support.requires_subprocess() + def test_both_args(self): + return_code, output = self.invoke_command_line("--user-base", + "--user-site") + excepted = self.get_excepted_output("--user-base", "--user-site") + excepted_return_code, excepted_output = excepted + self.assertEqual(return_code, excepted_return_code) + self.assertEqual(output, excepted_output) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index da242c7df71..dc970166191 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -166,8 +166,7 @@ def test_posix_venv_scheme(self): binpath = 'bin' incpath = 'include' libpath = os.path.join('lib', - # XXX: RUSTPYTHON - f'rustpython{sysconfig._get_python_version_abi()}', + f'python{sysconfig._get_python_version_abi()}', 'site-packages') # Resolve the paths in an imaginary venv/ directory @@ -352,6 +351,13 @@ def test_get_platform(self): self.assertEqual(get_platform(), 'macosx-10.4-%s' % arch) + for macver in range(11, 16): + _osx_support._remove_original_values(get_config_vars()) + get_config_vars()['CFLAGS'] = ('-fno-strict-overflow -Wsign-compare -Wunreachable-code' + '-arch arm64 -fno-common -dynamic -DNDEBUG -g -O3 -Wall') + get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = f"{macver}.0" + self.assertEqual(get_platform(), 'macosx-%d.0-arm64' % macver) + # linux debian sarge os.name = 'posix' sys.version = ('2.3.5 (#1, Jul 4 2007, 17:28:59) ' @@ -379,8 +385,7 @@ def test_get_platform(self): # XXX more platforms to tests here - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") @unittest.skipIf(is_apple_mobile, f"{sys.platform} doesn't distribute header files in the runtime environment") @@ -441,8 +446,7 @@ def test_main(self): _main() self.assertTrue(len(output.getvalue().split('\n')) > 0) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.platform == "win32", "Does not apply to Windows") def test_ldshared_value(self): ldflags = sysconfig.get_config_var('LDFLAGS') @@ -455,8 +459,7 @@ def test_soabi(self): soabi = sysconfig.get_config_var('SOABI') self.assertIn(soabi, _imp.extension_suffixes()[0]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_library(self): library = sysconfig.get_config_var('LIBRARY') ldlibrary = sysconfig.get_config_var('LDLIBRARY') @@ -520,7 +523,7 @@ def test_platform_in_subprocess(self): self.assertEqual(status, 0) self.assertEqual(my_platform, test_platform) - @unittest.expectedFailureIf(sys.platform != "win32", "TODO: RUSTPYTHON") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON') @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") @unittest.skipIf(is_apple_mobile, f"{sys.platform} doesn't include config folder at runtime") @@ -581,8 +584,7 @@ def test_linux_ext_suffix(self): self.assertTrue(suffix.endswith(expected_suffixes), f'unexpected suffix {suffix!r}') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(sys.platform == 'android', 'Android-specific test') def test_android_ext_suffix(self): machine = platform.machine() @@ -602,8 +604,7 @@ def test_osx_ext_suffix(self): suffix = sysconfig.get_config_var('EXT_SUFFIX') self.assertTrue(suffix.endswith('-darwin.so'), suffix) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @requires_subprocess() def test_config_vars_depend_on_site_initialization(self): script = textwrap.dedent(""" @@ -628,8 +629,7 @@ def test_config_vars_depend_on_site_initialization(self): self.assertEqual(no_site_config_vars['base'], site_config_vars['installed_base']) self.assertEqual(no_site_config_vars['platbase'], site_config_vars['installed_platbase']) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @requires_subprocess() def test_config_vars_recalculation_after_site_initialization(self): script = textwrap.dedent(""" @@ -654,8 +654,7 @@ def test_config_vars_recalculation_after_site_initialization(self): #self.assertEqual(config_vars['after']['prefix'], venv.prefix) # FIXME: prefix gets overwriten by _init_posix #self.assertEqual(config_vars['after']['exec_prefix'], venv.prefix) # FIXME: exec_prefix gets overwriten by _init_posix - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @requires_subprocess() def test_paths_depend_on_site_initialization(self): script = textwrap.dedent(""" @@ -676,8 +675,7 @@ def test_paths_depend_on_site_initialization(self): class MakefileTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.platform.startswith('win'), 'Test is not Windows compatible') @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 318c088fbc2..e42d1f9e49d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -49,8 +49,6 @@ from test.support.testcase import ExtraAssertions from test.typinganndata import ann_module695, mod_generics_cache, _typed_dict_helper -# TODO: RUSTPYTHON -import unittest CANNOT_SUBCLASS_TYPE = 'Cannot subclass special typing classes' NOT_A_BASE_TYPE = "type 'typing.%s' is not an acceptable base type" @@ -966,8 +964,7 @@ class C(Generic[T]): pass ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_two_parameters(self): T1 = TypeVar('T1') T2 = TypeVar('T2') @@ -1065,8 +1062,7 @@ class C(Generic[T1, T2, T3]): pass eval(expected_str) ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_variadic_parameters(self): T1 = TypeVar('T1') T2 = TypeVar('T2') @@ -3231,8 +3227,7 @@ def meth2(self, x, y): return True self.assertIsSubclass(NotAProtocolButAnImplicitSubclass2, CallableMembersProto) self.assertIsSubclass(NotAProtocolButAnImplicitSubclass3, CallableMembersProto) - # TODO: RUSTPYTHON - @unittest.skip("TODO: RUSTPYTHON (no gc)") + @unittest.skip('TODO: RUSTPYTHON; (no gc)') def test_isinstance_checks_not_at_whim_of_gc(self): self.addCleanup(gc.enable) gc.disable() @@ -4175,8 +4170,7 @@ class P(Protocol): Alias2 = typing.Union[P, typing.Iterable] self.assertEqual(Alias, Alias2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_protocols_pickleable(self): global P, CP # pickle wants to reference the class by name T = TypeVar('T') @@ -5151,8 +5145,7 @@ def test_all_repr_eq_any(self): self.assertNotEqual(repr(base), '') self.assertEqual(base, base) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickle(self): global C # pickle wants to reference the class by name T = TypeVar('T') @@ -5274,8 +5267,7 @@ def test_weakref_all(self): for t in things: self.assertEqual(weakref.ref(t)(), t) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parameterized_slots(self): T = TypeVar('T') class C(Generic[T]): @@ -5295,8 +5287,7 @@ def foo(x: C['C']): ... self.assertEqual(get_type_hints(foo, globals(), locals())['x'], C[C]) self.assertEqual(copy(C[int]), deepcopy(C[int])) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parameterized_slots_dict(self): T = TypeVar('T') class D(Generic[T]): @@ -5722,6 +5713,23 @@ class A: with self.assertRaises(TypeError): a[int] + def test_return_non_tuple_while_unpacking(self): + # GH-138497: GenericAlias objects didn't ensure that __typing_subst__ actually + # returned a tuple + class EvilTypeVar: + __typing_is_unpacked_typevartuple__ = True + def __typing_prepare_subst__(*_): + return None # any value + def __typing_subst__(*_): + return 42 # not tuple + + evil = EvilTypeVar() + # Create a dummy TypeAlias that will be given the evil generic from + # above. + type type_alias[*_] = 0 + with self.assertRaisesRegex(TypeError, ".+__typing_subst__.+tuple.+int.*"): + type_alias[evil][0] + class ClassVarTests(BaseTestCase): @@ -5824,8 +5832,7 @@ def test_final_unmodified(self): def func(x): ... self.assertIs(func, final(func)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dunder_final(self): @final def func(): ... @@ -6844,8 +6851,7 @@ def test_get_type_hints_from_various_objects(self): with self.assertRaises(TypeError): gth(None) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_get_type_hints_modules(self): ann_module_type_hints = {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str, 'u': int | float} self.assertEqual(gth(ann_module), ann_module_type_hints) @@ -7096,8 +7102,7 @@ def test_top_level_class_var(self): ): get_type_hints(ann_module6) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_get_type_hints_typeddict(self): self.assertEqual(get_type_hints(TotalMovie), {'title': str, 'year': int}) self.assertEqual(get_type_hints(TotalMovie, include_extras=True), { @@ -8138,8 +8143,7 @@ class CNT(NamedTuple): self.assertEqual(struct.__annotations__, {}) self.assertIsInstance(struct(), struct) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_namedtuple_errors(self): with self.assertRaises(TypeError): NamedTuple.__new__() @@ -8227,8 +8231,7 @@ class Bar(NamedTuple): self.assertIsInstance(bar.attr, Vanilla) self.assertEqual(bar.attr.name, "attr") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_setname_raises_the_same_as_on_other_classes(self): class CustomException(BaseException): pass @@ -8644,16 +8647,14 @@ class NewGeneric[T](TypedDict): # The TypedDict constructor is not itself a TypedDict self.assertIs(is_typeddict(TypedDict), False) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_get_type_hints(self): self.assertEqual( get_type_hints(Bar), {'a': typing.Optional[int], 'b': int} ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_get_type_hints_generic(self): self.assertEqual( get_type_hints(BarGeneric), @@ -8786,8 +8787,7 @@ class WithImplicitAny(B): with self.assertRaises(TypeError): WithImplicitAny[str] - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_non_generic_subscript(self): # For backward compatibility, subscription works # on arbitrary TypedDict types. @@ -8915,8 +8915,7 @@ class Child(Base): self.assertEqual(Child.__readonly_keys__, frozenset()) self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_combine_qualifiers(self): class AllTheThings(TypedDict): a: Annotated[Required[ReadOnly[int]], "why not"] @@ -9110,8 +9109,7 @@ def test_repr(self): self.assertEqual(repr(Match[str]), 'typing.Match[str]') self.assertEqual(repr(Match[bytes]), 'typing.Match[bytes]') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cannot_subclass(self): with self.assertRaisesRegex( TypeError, @@ -10330,8 +10328,7 @@ def test_special_attrs(self): TypeName = typing.NewType('SpecialAttrsTests.TypeName', Any) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_special_attrs2(self): # Forward refs provide a different introspection API. __name__ and # __qualname__ make little sense for forward refs as they can store @@ -10498,8 +10495,7 @@ class CustomerModel(ModelBase, init=False): class NoDefaultTests(BaseTestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickling(self): for proto in range(pickle.HIGHEST_PROTOCOL + 1): s = pickle.dumps(NoDefault, proto) @@ -10525,8 +10521,7 @@ def test_no_call(self): with self.assertRaises(TypeError): NoDefault() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_no_attributes(self): with self.assertRaises(AttributeError): NoDefault.foo = 3 @@ -10602,8 +10597,7 @@ class TypeIterationTests(BaseTestCase): Annotated[T, ''], ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cannot_iterate(self): expected_error_regex = "object is not iterable" for test_type in self._UNITERABLE_TYPES: From 9b400a9b6f32823136845d74d2a0db12bfed84ac Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:12:08 +0300 Subject: [PATCH 290/819] Reapply some patches --- Lib/_collections_abc.py | 4 + Lib/sysconfig/__init__.py | 2 +- Lib/test/support/__init__.py | 223 +++++++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+), 1 deletion(-) diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 6e224d36001..e02fc227384 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -512,6 +512,10 @@ def __getitem__(self, item): new_args = (t_args, t_result) return _CallableGenericAlias(Callable, tuple(new_args)) + # TODO: RUSTPYTHON; patch for common call + def __or__(self, other): + super().__or__(other) + def _is_param_expr(obj): """Checks if obj matches either a list of types, ``...``, ``ParamSpec`` or ``_ConcatenateGenericAlias`` from typing.py diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index f7bd675bb3b..8365236f61c 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -107,7 +107,7 @@ _INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv'] def _get_implementation(): - return 'Python' + return 'RustPython' # XXX: For site-packages # NOTE: site.py has copy of this function. # Sync it when modify this function. diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 4605938b875..444ca2219cf 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -511,6 +511,7 @@ def requires_lzma(reason='requires lzma'): import lzma except ImportError: lzma = None + lzma = None # XXX: RUSTPYTHON; xz is not supported yet return unittest.skipUnless(lzma, reason) def has_no_debug_ranges(): @@ -841,6 +842,8 @@ def gc_collect(): longer than expected. This function tries its best to force all garbage objects to disappear. """ + return # TODO: RUSTPYTHON + import gc gc.collect() gc.collect() @@ -848,6 +851,13 @@ def gc_collect(): @contextlib.contextmanager def disable_gc(): + # TODO: RUSTPYTHON; GC is not supported yet + try: + yield + finally: + pass + return + import gc have_gc = gc.isenabled() gc.disable() @@ -859,6 +869,13 @@ def disable_gc(): @contextlib.contextmanager def gc_threshold(*args): + # TODO: RUSTPYTHON; GC is not supported yet + try: + yield + finally: + pass + return + import gc old_threshold = gc.get_threshold() gc.set_threshold(*args) @@ -1921,6 +1938,10 @@ def _check_tracemalloc(): def check_free_after_iterating(test, iter, cls, args=()): + # TODO: RUSTPYTHON; GC is not supported yet + test.assertTrue(False) + return + done = False def wrapper(): class A(cls): @@ -2845,3 +2866,205 @@ def linked_to_musl(): except (OSError, subprocess.CalledProcessError): return False return ('musl' in stdout) + + +# TODO: RUSTPYTHON +# Every line of code below allowed us to update `Lib/test/support/__init__.py` without +# needing to update `libregtest` and its dependencies. +# Ideally we want to remove all code below and update `libregtest`. +# +# Code below was copied from: https://github.com/RustPython/RustPython/blob/9499d39f55b73535e2405bf208d5380241f79ada/Lib/test/support/__init__.py + +from .testresult import get_test_runner + +def _filter_suite(suite, pred): + """Recursively filter test cases in a suite based on a predicate.""" + newtests = [] + for test in suite._tests: + if isinstance(test, unittest.TestSuite): + _filter_suite(test, pred) + newtests.append(test) + else: + if pred(test): + newtests.append(test) + suite._tests = newtests + +# By default, don't filter tests +_match_test_func = None + +_accept_test_patterns = None +_ignore_test_patterns = None + +def match_test(test): + # Function used by support.run_unittest() and regrtest --list-cases + if _match_test_func is None: + return True + else: + return _match_test_func(test.id()) + +def _is_full_match_test(pattern): + # If a pattern contains at least one dot, it's considered + # as a full test identifier. + # Example: 'test.test_os.FileTests.test_access'. + # + # ignore patterns which contain fnmatch patterns: '*', '?', '[...]' + # or '[!...]'. For example, ignore 'test_access*'. + return ('.' in pattern) and (not re.search(r'[?*\[\]]', pattern)) + +def set_match_tests(accept_patterns=None, ignore_patterns=None): + global _match_test_func, _accept_test_patterns, _ignore_test_patterns + + if accept_patterns is None: + accept_patterns = () + if ignore_patterns is None: + ignore_patterns = () + + accept_func = ignore_func = None + + if accept_patterns != _accept_test_patterns: + accept_patterns, accept_func = _compile_match_function(accept_patterns) + if ignore_patterns != _ignore_test_patterns: + ignore_patterns, ignore_func = _compile_match_function(ignore_patterns) + + # Create a copy since patterns can be mutable and so modified later + _accept_test_patterns = tuple(accept_patterns) + _ignore_test_patterns = tuple(ignore_patterns) + + if accept_func is not None or ignore_func is not None: + def match_function(test_id): + accept = True + ignore = False + if accept_func: + accept = accept_func(test_id) + if ignore_func: + ignore = ignore_func(test_id) + return accept and not ignore + + _match_test_func = match_function + +def _compile_match_function(patterns): + if not patterns: + func = None + # set_match_tests(None) behaves as set_match_tests(()) + patterns = () + elif all(map(_is_full_match_test, patterns)): + # Simple case: all patterns are full test identifier. + # The test.bisect_cmd utility only uses such full test identifiers. + func = set(patterns).__contains__ + else: + import fnmatch + regex = '|'.join(map(fnmatch.translate, patterns)) + # The search *is* case sensitive on purpose: + # don't use flags=re.IGNORECASE + regex_match = re.compile(regex).match + + def match_test_regex(test_id): + if regex_match(test_id): + # The regex matches the whole identifier, for example + # 'test.test_os.FileTests.test_access'. + return True + else: + # Try to match parts of the test identifier. + # For example, split 'test.test_os.FileTests.test_access' + # into: 'test', 'test_os', 'FileTests' and 'test_access'. + return any(map(regex_match, test_id.split("."))) + + func = match_test_regex + + return patterns, func + +def run_unittest(*classes): + """Run tests from unittest.TestCase-derived classes.""" + valid_types = (unittest.TestSuite, unittest.TestCase) + loader = unittest.TestLoader() + suite = unittest.TestSuite() + for cls in classes: + if isinstance(cls, str): + if cls in sys.modules: + suite.addTest(loader.loadTestsFromModule(sys.modules[cls])) + else: + raise ValueError("str arguments must be keys in sys.modules") + elif isinstance(cls, valid_types): + suite.addTest(cls) + else: + suite.addTest(loader.loadTestsFromTestCase(cls)) + _filter_suite(suite, match_test) + return _run_suite(suite) + +def _run_suite(suite): + """Run tests from a unittest.TestSuite-derived class.""" + runner = get_test_runner(sys.stdout, + verbosity=verbose, + capture_output=(junit_xml_list is not None)) + + result = runner.run(suite) + + if junit_xml_list is not None: + junit_xml_list.append(result.get_xml_element()) + + if not result.testsRun and not result.skipped and not result.errors: + raise TestDidNotRun + if not result.wasSuccessful(): + stats = TestStats.from_unittest(result) + if len(result.errors) == 1 and not result.failures: + err = result.errors[0][1] + elif len(result.failures) == 1 and not result.errors: + err = result.failures[0][1] + else: + err = "multiple errors occurred" + if not verbose: err += "; run in verbose mode for details" + errors = [(str(tc), exc_str) for tc, exc_str in result.errors] + failures = [(str(tc), exc_str) for tc, exc_str in result.failures] + raise TestFailedWithDetails(err, errors, failures, stats=stats) + return result + +@dataclasses.dataclass(slots=True) +class TestStats: + tests_run: int = 0 + failures: int = 0 + skipped: int = 0 + + @staticmethod + def from_unittest(result): + return TestStats(result.testsRun, + len(result.failures), + len(result.skipped)) + + @staticmethod + def from_doctest(results): + return TestStats(results.attempted, + results.failed) + + def accumulate(self, stats): + self.tests_run += stats.tests_run + self.failures += stats.failures + self.skipped += stats.skipped + + +def run_doctest(module, verbosity=None, optionflags=0): + """Run doctest on the given module. Return (#failures, #tests). + + If optional argument verbosity is not specified (or is None), pass + support's belief about verbosity on to doctest. Else doctest's + usual behavior is used (it searches sys.argv for -v). + """ + + import doctest + + if verbosity is None: + verbosity = verbose + else: + verbosity = None + + results = doctest.testmod(module, + verbose=verbosity, + optionflags=optionflags) + if results.failed: + stats = TestStats.from_doctest(results) + raise TestFailed(f"{results.failed} of {results.attempted} " + f"doctests failed", + stats=stats) + if verbose: + print('doctest (%s) ... %d tests with zero failures' % + (module.__name__, results.attempted)) + return results From 360f8caead1281770adfc9361fb01645ad244d6f Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:18:15 +0300 Subject: [PATCH 291/819] Reaaply patches to `test_bytes.py` --- Lib/test/test_bytes.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 0847afe016b..8f01f890309 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1579,6 +1579,11 @@ def test_irepeat_1char(self): self.assertEqual(b, b1) self.assertIs(b, b1) + # NOTE: RUSTPYTHON: + # + # The second instance of self.assertGreater was replaced with + # self.assertGreaterEqual since, in RustPython, the underlying storage + # is a Vec which doesn't require trailing null byte. def test_alloc(self): b = bytearray() alloc = b.__alloc__() @@ -1587,10 +1592,15 @@ def test_alloc(self): for i in range(100): b += b"x" alloc = b.__alloc__() - self.assertGreater(alloc, len(b)) # including trailing null byte + self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched if alloc not in seq: seq.append(alloc) + # NOTE: RUSTPYTHON: + # + # The usages of self.assertGreater were replaced with + # self.assertGreaterEqual since, in RustPython, the underlying storage + # is a Vec which doesn't require trailing null byte. def test_init_alloc(self): b = bytearray() def g(): @@ -1601,12 +1611,12 @@ def g(): self.assertEqual(len(b), len(a)) self.assertLessEqual(len(b), i) alloc = b.__alloc__() - self.assertGreater(alloc, len(b)) # including trailing null byte + self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched b.__init__(g()) self.assertEqual(list(b), list(range(1, 100))) self.assertEqual(len(b), 99) alloc = b.__alloc__() - self.assertGreater(alloc, len(b)) + self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched def test_extend(self): orig = b'hello' From e18354b990b6535a7f3517bfedd194b5c1b3c0d8 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:23:24 +0300 Subject: [PATCH 292/819] fix test markers in `test_exceptions.py` --- Lib/test/test_exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index eee8c13386e..efee74d023c 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1152,6 +1152,7 @@ class C(Exception): self.assertIs(c.__context__, b) self.assertIsNone(b.__context__) + @unittest.skip("TODO: RUSTPYTHON; Infinite loop") def test_no_hang_on_context_chain_cycle1(self): # See issue 25782. Cycle in context chain. @@ -1207,6 +1208,7 @@ class C(Exception): self.assertIs(b.__context__, a) self.assertIs(a.__context__, c) + @unittest.skip("TODO: RUSTPYTHON; Infinite loop") def test_no_hang_on_context_chain_cycle3(self): # See issue 25782. Longer context chain with cycle. From 84b254209fe27ca8d7d2985e02a47019bb9bb232 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:25:51 +0300 Subject: [PATCH 293/819] Patch `test_posix.py` --- Lib/test/test_posix.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index c327d2add2f..00da4c2c459 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2074,6 +2074,7 @@ def test_open_file(self): with open(outfile, encoding="utf-8") as f: self.assertEqual(f.read(), 'hello') + @unittest.expectedFailure # TODO: RUSTPYTHON; the rust runtime reopens closed stdio fds at startup, so this test fails, even though POSIX_SPAWN_CLOSE does actually have an effect def test_close_file(self): closefile = os_helper.TESTFN self.addCleanup(os_helper.unlink, closefile) From aa56ebb0574c72bd155498b5cc1adb70c2c66b9e Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:32:26 +0300 Subject: [PATCH 294/819] Patched `test_{pyexpat,site,sysconfig}.py` --- Lib/test/test_pyexpat.py | 1 - Lib/test/test_site.py | 6 ++++-- Lib/test/test_sysconfig.py | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index 9747cf66218..914a00269ee 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -466,7 +466,6 @@ def check_traceback_entry(self, entry, filename, funcname): self.assertEqual(os.path.basename(entry.filename), filename) self.assertEqual(entry.name, funcname) - @unittest.expectedFailure # TODO: RUSTPYTHON @support.cpython_only def test_exception(self): # gh-66652: test _PyTraceback_Add() used by pyexpat.c to inject frames diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 9001b817047..88ca66ac7ad 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -331,13 +331,15 @@ def test_getsitepackages(self): if sys.platlibdir != "lib": self.assertEqual(len(dirs), 2) wanted = os.path.join('xoxo', sys.platlibdir, - f'python{sysconfig._get_python_version_abi()}', + # XXX: RUSTPYTHON + f'rustpython{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[0], wanted) else: self.assertEqual(len(dirs), 1) wanted = os.path.join('xoxo', 'lib', - f'python{sysconfig._get_python_version_abi()}', + # XXX: RUSTPYTHON + f'rustpython{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[-1], wanted) else: diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index dc970166191..35e62d54635 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -166,7 +166,8 @@ def test_posix_venv_scheme(self): binpath = 'bin' incpath = 'include' libpath = os.path.join('lib', - f'python{sysconfig._get_python_version_abi()}', + # XXX: RUSTPYTHON + f'rustpython{sysconfig._get_python_version_abi()}', 'site-packages') # Resolve the paths in an imaginary venv/ directory From 9557acb1c338f505a585a5d9c4f6ddf03ad90359 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:34:15 +0300 Subject: [PATCH 295/819] Patched `test_typing.py` --- Lib/test/test_typing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index e42d1f9e49d..ea5c1482f57 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -49,6 +49,8 @@ from test.support.testcase import ExtraAssertions from test.typinganndata import ann_module695, mod_generics_cache, _typed_dict_helper +import unittest # XXX: RUSTPYTHON + CANNOT_SUBCLASS_TYPE = 'Cannot subclass special typing classes' NOT_A_BASE_TYPE = "type 'typing.%s' is not an acceptable base type" From 3f7deb49c827f560cf28549730701c810ed19395 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:40:38 +0300 Subject: [PATCH 296/819] Patch failing tests in `test_typing.py` --- Lib/test/test_typing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index ea5c1482f57..3f268c62384 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -5715,6 +5715,7 @@ class A: with self.assertRaises(TypeError): a[int] + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ".+__typing_subst__.+tuple.+int.*" does not match "'TypeAliasType' object is not subscriptable" def test_return_non_tuple_while_unpacking(self): # GH-138497: GenericAlias objects didn't ensure that __typing_subst__ actually # returned a tuple From 624a561145c0c2042e7560bd13f20fdb585fd498 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:42:16 +0300 Subject: [PATCH 297/819] Update `seq_tests` from 3.13.8 --- Lib/test/seq_tests.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/seq_tests.py b/Lib/test/seq_tests.py index 59db2664a02..54eb5e65ac2 100644 --- a/Lib/test/seq_tests.py +++ b/Lib/test/seq_tests.py @@ -145,6 +145,9 @@ def __getitem__(self, i): self.assertEqual(self.type2test(LyingTuple((2,))), self.type2test((1,))) self.assertEqual(self.type2test(LyingList([2])), self.type2test([1])) + with self.assertRaises(TypeError): + self.type2test(unsupported_arg=[]) + def test_truth(self): self.assertFalse(self.type2test()) self.assertTrue(self.type2test([42])) @@ -423,8 +426,8 @@ def test_pickle(self): self.assertEqual(lst2, lst) self.assertNotEqual(id(lst2), id(lst)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.suppress_immortalization() def test_free_after_iterating(self): support.check_free_after_iterating(self, iter, self.type2test) support.check_free_after_iterating(self, reversed, self.type2test) From 296da56190781f229e98e0e6f802e3d96e3656f0 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 12:01:33 +0300 Subject: [PATCH 298/819] Mark failing tests in `test_genericalias.py` --- Lib/test/test_genericalias.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 4a87e2bf7a1..c1c49dc29d8 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -151,6 +151,7 @@ def test_subscriptable(self): self.assertEqual(alias.__args__, (int,)) self.assertEqual(alias.__parameters__, ()) + @unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message def test_unsubscriptable(self): for t in int, str, float, Sized, Hashable: tname = t.__name__ @@ -209,6 +210,7 @@ class MyList(list): self.assertEqual(t.__args__, (int,)) self.assertEqual(t.__parameters__, ()) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_repr(self): class MyList(list): pass @@ -332,6 +334,7 @@ def test_parameter_chaining(self): with self.assertRaises(TypeError): dict[T, T][str, int] + @unittest.expectedFailure # TODO: RUSTPYTHON def test_equality(self): self.assertEqual(list[int], list[int]) self.assertEqual(dict[str, int], dict[str, int]) @@ -362,6 +365,7 @@ def test_type_generic(self): self.assertEqual(t(test), Test) self.assertEqual(t(0), int) + @unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message def test_type_subclass_generic(self): class MyType(type): pass @@ -422,6 +426,7 @@ def test_union_generic(self): self.assertEqual(a.__args__, (list[T], tuple[T, ...])) self.assertEqual(a.__parameters__, (T,)) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dir(self): ga = list[int] dir_of_gen_alias = set(dir(ga)) From e069244f890f8054d8a5256ddaf8004b15706e2f Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 12:07:03 +0300 Subject: [PATCH 299/819] mark failing tests in `test_pyexpat.py` --- Lib/test/test_pyexpat.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index 914a00269ee..80485cc74b9 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -549,6 +549,7 @@ def test(self): class sf1296433Test(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Expected type 'str' but 'bytes' found. def test_parse_only_xml_data(self): # https://bugs.python.org/issue1296433 # @@ -797,6 +798,7 @@ class ParentParserLifetimeTest(unittest.TestCase): See https://github.com/python/cpython/issues/139400. """ + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate' def test_parent_parser_outlives_its_subparsers__single(self): parser = expat.ParserCreate() subparser = parser.ExternalEntityParserCreate(None) @@ -805,6 +807,7 @@ def test_parent_parser_outlives_its_subparsers__single(self): # while it's still being referenced by a related subparser. del parser + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate' def test_parent_parser_outlives_its_subparsers__multiple(self): parser = expat.ParserCreate() subparser_one = parser.ExternalEntityParserCreate(None) @@ -814,6 +817,7 @@ def test_parent_parser_outlives_its_subparsers__multiple(self): # while it's still being referenced by a related subparser. del parser + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate' def test_parent_parser_outlives_its_subparsers__chain(self): parser = expat.ParserCreate() subparser = parser.ExternalEntityParserCreate(None) @@ -826,6 +830,7 @@ def test_parent_parser_outlives_its_subparsers__chain(self): class ReparseDeferralTest(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'GetReparseDeferralEnabled' def test_getter_setter_round_trip(self): parser = expat.ParserCreate() enabled = (expat.version_info >= (2, 6, 0)) @@ -836,6 +841,7 @@ def test_getter_setter_round_trip(self): parser.SetReparseDeferralEnabled(True) self.assertIs(parser.GetReparseDeferralEnabled(), enabled) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'GetReparseDeferralEnabled' def test_reparse_deferral_enabled(self): if expat.version_info < (2, 6, 0): self.skipTest(f'Expat {expat.version_info} does not ' @@ -860,6 +866,7 @@ def start_element(name, _): self.assertEqual(started, ['doc']) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetReparseDeferralEnabled' def test_reparse_deferral_disabled(self): started = [] From 604b708741578ae75d5645dab9923fad3e129e94 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:04:49 +0300 Subject: [PATCH 300/819] Mark failing tests in `test_posix.py` --- Lib/test/test_posix.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 00da4c2c459..d4020ed9788 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -583,6 +583,7 @@ def test_confstr(self): self.assertGreater(len(path), 0) self.assertEqual(posix.confstr(posix.confstr_names["CS_PATH"]), path) + @unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), '''TODO: RUSTPYTHON; AssertionError: "configuration names must be strings or integers" does not match "Expected type 'str' but 'float' found."''') @unittest.skipUnless(hasattr(posix, 'sysconf'), 'test needs posix.sysconf()') def test_sysconf(self): @@ -1017,6 +1018,7 @@ def test_chmod_dir(self): target = self.tempdir() self.check_chmod(posix.chmod, target) + @unittest.skipIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; crash') @os_helper.skip_unless_working_chmod def test_fchmod_file(self): with open(os_helper.TESTFN, 'wb+') as f: @@ -1073,6 +1075,7 @@ def test_chmod_file_symlink(self): self.check_chmod_link(posix.chmod, target, link) self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') @os_helper.skip_unless_symlink def test_chmod_dir_symlink(self): target = self.tempdir() @@ -1566,6 +1569,7 @@ def test_chown_dir_fd(self): with self.prepare_file() as (dir_fd, name, fullname): posix.chown(name, os.getuid(), os.getgid(), dir_fd=dir_fd) + @unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; AssertionError: RuntimeWarning not triggered') @unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()") def test_stat_dir_fd(self): with self.prepare() as (dir_fd, name, fullname): @@ -1968,6 +1972,7 @@ def test_setsigdef_wrong_type(self): [sys.executable, "-c", "pass"], os.environ, setsigdef=[signal.NSIG, signal.NSIG+1]) + @unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented') @requires_sched @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), "bpo-34685: test can fail on BSD") @@ -1988,6 +1993,7 @@ def test_setscheduler_only_param(self): ) support.wait_process(pid, exitcode=0) + @unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented') @requires_sched @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), "bpo-34685: test can fail on BSD") From 68e7310d221708718daab2520228a8cd37925ab0 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:02:36 +0300 Subject: [PATCH 301/819] reapply patch --- Lib/test/test_exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index efee74d023c..6148bd3a6fc 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1678,6 +1678,7 @@ def inner(): gc_collect() # For PyPy or other GCs. self.assertEqual(wr(), None) + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; error specific to cpython') def test_errno_ENOTDIR(self): # Issue #12802: "not a directory" errors are ENOTDIR even on Windows with self.assertRaises(OSError) as cm: From 715529bef1d0e1df314689173d270c5bb856c7d1 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:36:40 +0300 Subject: [PATCH 302/819] mark failing tests --- Lib/test/test_posix.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index d4020ed9788..bb6e4b126fe 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1063,6 +1063,7 @@ def check_lchmod_link(self, chmod_func, target, link, **kwargs): self.assertEqual(os.stat(target).st_mode, target_mode) self.assertEqual(os.lstat(link).st_mode, new_mode) + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') @os_helper.skip_unless_symlink def test_chmod_file_symlink(self): target = os_helper.TESTFN @@ -1350,6 +1351,7 @@ def test_get_and_set_scheduler_and_param(self): param = posix.sched_param(sched_priority=-large) self.assertRaises(OverflowError, posix.sched_setparam, 0, param) + @unittest.expectedFailureIf(sys.platform == 'linux', "TODO: RUSTPYTHON; TypeError: cannot pickle 'sched_param' object") @requires_sched def test_sched_param(self): param = posix.sched_param(1) From a50cc9b915df0ef4d79179979ab73841872d044f Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:04:01 +0300 Subject: [PATCH 303/819] skip flaky test --- Lib/test/test_posix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index bb6e4b126fe..de3184a3736 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1076,7 +1076,7 @@ def test_chmod_file_symlink(self): self.check_chmod_link(posix.chmod, target, link) self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; flaky') @os_helper.skip_unless_symlink def test_chmod_dir_symlink(self): target = self.tempdir() From 5d9e62390c90d27a2e9c79a895e720f903e0d1e7 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:26:19 +0300 Subject: [PATCH 304/819] Update functools from 3.13.9 (#6205) * Update `functools.py` from 3.13.9 * mark/unmark tests --- Lib/functools.py | 93 +++++--- Lib/test/test_functools.py | 421 ++++++++++++++++++++++++++++++------- 2 files changed, 410 insertions(+), 104 deletions(-) diff --git a/Lib/functools.py b/Lib/functools.py index 2ae4290f983..4c1175b815d 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -19,8 +19,9 @@ # import types, weakref # Deferred to single_dispatch() from reprlib import recursive_repr from _thread import RLock -from types import GenericAlias +# Avoid importing types, so we can speedup import time +GenericAlias = type(list[int]) ################################################################################ ### update_wrapper() and wraps() decorator @@ -236,14 +237,16 @@ def __ge__(self, other): def reduce(function, sequence, initial=_initial_missing): """ - reduce(function, iterable[, initial]) -> value - - Apply a function of two arguments cumulatively to the items of a sequence - or iterable, from left to right, so as to reduce the iterable to a single - 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 - of the iterable in the calculation, and serves as a default when the - iterable is empty. + reduce(function, iterable[, initial], /) -> value + + Apply a function of two arguments cumulatively to the items of an iterable, from left to right. + + This effectively reduces the iterable to a single value. If initial is present, + it is placed before the items of the iterable in the calculation, and serves as + a default when the iterable is empty. + + For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) + calculates ((((1 + 2) + 3) + 4) + 5). """ it = iter(sequence) @@ -284,7 +287,7 @@ def __new__(cls, func, /, *args, **keywords): if not callable(func): raise TypeError("the first argument must be callable") - if hasattr(func, "func"): + if isinstance(func, partial): args = func.args + args keywords = {**func.keywords, **keywords} func = func.func @@ -302,13 +305,23 @@ def __call__(self, /, *args, **keywords): @recursive_repr() def __repr__(self): - qualname = type(self).__qualname__ + cls = type(self) + qualname = cls.__qualname__ + module = cls.__module__ args = [repr(self.func)] args.extend(repr(x) for x in self.args) args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items()) - if type(self).__module__ == "functools": - return f"functools.{qualname}({', '.join(args)})" - return f"{qualname}({', '.join(args)})" + return f"{module}.{qualname}({', '.join(args)})" + + def __get__(self, obj, objtype=None): + if obj is None: + return self + import warnings + warnings.warn('functools.partial will be a method descriptor in ' + 'future Python versions; wrap it in staticmethod() ' + 'if you want to preserve the old behavior', + FutureWarning, 2) + return self def __reduce__(self): return type(self), (self.func,), (self.func, self.args, @@ -338,6 +351,9 @@ def __setstate__(self, state): self.args = args self.keywords = kwds + __class_getitem__ = classmethod(GenericAlias) + + try: from _functools import partial except ImportError: @@ -372,28 +388,26 @@ def __init__(self, func, /, *args, **keywords): self.keywords = keywords def __repr__(self): - args = ", ".join(map(repr, self.args)) - keywords = ", ".join("{}={!r}".format(k, v) - for k, v in self.keywords.items()) - format_string = "{module}.{cls}({func}, {args}, {keywords})" - return format_string.format(module=self.__class__.__module__, - cls=self.__class__.__qualname__, - func=self.func, - args=args, - keywords=keywords) + cls = type(self) + module = cls.__module__ + qualname = cls.__qualname__ + args = [repr(self.func)] + args.extend(map(repr, self.args)) + args.extend(f"{k}={v!r}" for k, v in self.keywords.items()) + return f"{module}.{qualname}({', '.join(args)})" def _make_unbound_method(self): def _method(cls_or_self, /, *args, **keywords): keywords = {**self.keywords, **keywords} return self.func(cls_or_self, *self.args, *args, **keywords) _method.__isabstractmethod__ = self.__isabstractmethod__ - _method._partialmethod = self + _method.__partialmethod__ = self return _method def __get__(self, obj, cls=None): get = getattr(self.func, "__get__", None) result = None - if get is not None: + if get is not None and not isinstance(self.func, partial): new_func = get(obj, cls) if new_func is not self.func: # Assume __get__ returning something new indicates the @@ -423,6 +437,17 @@ def _unwrap_partial(func): func = func.func return func +def _unwrap_partialmethod(func): + prev = None + while func is not prev: + prev = func + while isinstance(getattr(func, "__partialmethod__", None), partialmethod): + func = func.__partialmethod__ + while isinstance(func, partialmethod): + func = getattr(func, 'func') + func = _unwrap_partial(func) + return func + ################################################################################ ### LRU Cache function decorator ################################################################################ @@ -483,8 +508,9 @@ def lru_cache(maxsize=128, typed=False): can grow without bound. If *typed* is True, arguments of different types will be cached separately. - For example, f(3.0) and f(3) will be treated as distinct calls with - distinct results. + For example, f(decimal.Decimal("3.0")) and f(3.0) will be treated as + distinct calls with distinct results. Some types such as str and int may + be cached separately even when typed is false. Arguments to the cached function must be hashable. @@ -660,7 +686,7 @@ def cache(user_function, /): def _c3_merge(sequences): """Merges MROs in *sequences* to a single MRO using the C3 algorithm. - Adapted from https://www.python.org/download/releases/2.3/mro/. + Adapted from https://docs.python.org/3/howto/mro.html. """ result = [] @@ -905,7 +931,6 @@ def wrapper(*args, **kw): if not args: raise TypeError(f'{funcname} requires at least ' '1 positional argument') - return dispatch(args[0].__class__)(*args, **kw) funcname = getattr(func, '__name__', 'singledispatch function') @@ -941,13 +966,18 @@ def register(self, cls, method=None): return self.dispatcher.register(cls, func=method) def __get__(self, obj, cls=None): + dispatch = self.dispatcher.dispatch + funcname = getattr(self.func, '__name__', 'singledispatchmethod method') def _method(*args, **kwargs): - method = self.dispatcher.dispatch(args[0].__class__) - return method.__get__(obj, cls)(*args, **kwargs) + if not args: + raise TypeError(f'{funcname} requires at least ' + '1 positional argument') + return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs) _method.__isabstractmethod__ = self.__isabstractmethod__ _method.register = self.register update_wrapper(_method, self.func) + return _method @property @@ -966,6 +996,7 @@ def __init__(self, func): self.func = func self.attrname = None self.__doc__ = func.__doc__ + self.__module__ = func.__module__ def __set_name__(self, owner, name): if self.attrname is None: diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 32b442b0c07..8050c4c8973 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -31,10 +31,6 @@ decimal = import_helper.import_fresh_module('decimal', fresh=['_decimal']) -_partial_types = [py_functools.partial] -if c_functools: - _partial_types.append(c_functools.partial) - @contextlib.contextmanager def replaced_module(name, replacement): @@ -189,6 +185,19 @@ def test_nested_optimization(self): flat = partial(signature, 'asdf', bar=True) self.assertEqual(signature(nested), signature(flat)) + def test_nested_optimization_bug(self): + partial = self.partial + class Builder: + def __call__(self, tag, *children, **attrib): + return (tag, children, attrib) + + def __getattr__(self, tag): + return partial(self, tag) + + B = Builder() + m = B.m + assert m(1, 2, a=2) == ('m', (1, 2), dict(a=2)) + def test_nested_partial_with_attribute(self): # see issue 25137 partial = self.partial @@ -207,10 +216,7 @@ def test_repr(self): kwargs = {'a': object(), 'b': object()} kwargs_reprs = ['a={a!r}, b={b!r}'.format_map(kwargs), 'b={b!r}, a={a!r}'.format_map(kwargs)] - if self.partial in _partial_types: - name = 'functools.partial' - else: - name = self.partial.__name__ + name = f"{self.partial.__module__}.{self.partial.__qualname__}" f = self.partial(capture) self.assertEqual(f'{name}({capture!r})', repr(f)) @@ -229,10 +235,7 @@ def test_repr(self): for kwargs_repr in kwargs_reprs]) def test_recursive_repr(self): - if self.partial in _partial_types: - name = 'functools.partial' - else: - name = self.partial.__name__ + name = f"{self.partial.__module__}.{self.partial.__qualname__}" f = self.partial(capture) f.__setstate__((f, (), {}, {})) @@ -344,8 +347,10 @@ def test_recursive_pickle(self): f.__setstate__((f, (), {}, {})) try: for proto in range(pickle.HIGHEST_PROTOCOL + 1): - with self.assertRaises(RecursionError): - pickle.dumps(f, proto) + # gh-117008: Small limit since pickle uses C stack memory + with support.infinite_recursion(100): + with self.assertRaises(RecursionError): + pickle.dumps(f, proto) finally: f.__setstate__((capture, (), {}, {})) @@ -390,6 +395,30 @@ def __getitem__(self, key): f = self.partial(object) self.assertRaises(TypeError, f.__setstate__, BadSequence()) + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_partial_as_method(self): + class A: + meth = self.partial(capture, 1, a=2) + cmeth = classmethod(self.partial(capture, 1, a=2)) + smeth = staticmethod(self.partial(capture, 1, a=2)) + + a = A() + self.assertEqual(A.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) + self.assertEqual(A.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4})) + self.assertEqual(A.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) + with self.assertWarns(FutureWarning) as w: + self.assertEqual(a.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) + self.assertEqual(w.filename, __file__) + self.assertEqual(a.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4})) + self.assertEqual(a.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) + + def test_partial_genericalias(self): + alias = self.partial[int] + self.assertIs(alias.__origin__, self.partial) + self.assertEqual(alias.__args__, (int,)) + self.assertEqual(alias.__parameters__, ()) + + @unittest.skipUnless(c_functools, 'requires the C _functools module') class TestPartialC(TestPartial, unittest.TestCase): if c_functools: @@ -436,6 +465,14 @@ def __str__(self): self.assertIn('astr', r) self.assertIn("['sth']", r) + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_repr(self): + return super().test_repr() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_recursive_repr(self): + return super().test_recursive_repr() + class TestPartialPy(TestPartial, unittest.TestCase): module = py_functools @@ -454,14 +491,6 @@ class TestPartialCSubclass(TestPartialC): if c_functools: partial = CPartialSubclass - # TODO: RUSTPYTHON - def test_pickle(self): - TestPartial.test_pickle(self) - - # TODO: RUSTPYTHON - def test_recursive_pickle(self): - TestPartial.test_recursive_pickle(self) - # partial subclasses are not optimized for nested calls test_nested_optimization = None @@ -572,6 +601,14 @@ class B: method = functools.partialmethod(func=capture, a=1) def test_repr(self): + self.assertEqual(repr(vars(self.A)['nothing']), + 'functools.partialmethod({})'.format(capture)) + self.assertEqual(repr(vars(self.A)['positional']), + 'functools.partialmethod({}, 1)'.format(capture)) + self.assertEqual(repr(vars(self.A)['keywords']), + 'functools.partialmethod({}, a=2)'.format(capture)) + self.assertEqual(repr(vars(self.A)['spec_keywords']), + 'functools.partialmethod({}, self=1, func=2)'.format(capture)) self.assertEqual(repr(vars(self.A)['both']), 'functools.partialmethod({}, 3, b=4)'.format(capture)) @@ -620,9 +657,7 @@ def check_wrapper(self, wrapper, wrapped, def _default_update(self): - # XXX: RUSTPYTHON; f[T] is not supported yet - # def f[T](a:'This is a new annotation'): - def f(a:'This is a new annotation'): + def f[T](a:'This is a new annotation'): """This is a test""" pass f.attr = 'This is also a test' @@ -632,8 +667,6 @@ def wrapper(b:'This is the prior annotation'): functools.update_wrapper(wrapper, f) return wrapper, f - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_default_update(self): wrapper, f = self._default_update() self.check_wrapper(wrapper, f) @@ -717,6 +750,14 @@ def wrapper(): self.assertTrue(wrapper.__doc__.startswith('max(')) self.assertEqual(wrapper.__annotations__, {}) + def test_update_type_wrapper(self): + def wrapper(*args): pass + + functools.update_wrapper(wrapper, type) + self.assertEqual(wrapper.__name__, 'type') + self.assertEqual(wrapper.__annotations__, {}) + self.assertEqual(wrapper.__type_params__, ()) + class TestWraps(TestUpdateWrapper): @@ -951,9 +992,16 @@ def mycmp(x, y): self.assertRaises(TypeError, hash, k) self.assertNotIsInstance(k, collections.abc.Hashable) + @unittest.skipIf(support.MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") def test_cmp_to_signature(self): - self.assertEqual(str(Signature.from_callable(self.cmp_to_key)), - '(mycmp)') + sig = Signature.from_callable(self.cmp_to_key) + self.assertEqual(str(sig), '(mycmp)') + def mycmp(x, y): + return y - x + sig = Signature.from_callable(self.cmp_to_key(mycmp)) + self.assertEqual(str(sig), '(obj)') + @unittest.skipUnless(c_functools, 'requires the C _functools module') @@ -961,52 +1009,44 @@ class TestCmpToKeyC(TestCmpToKey, unittest.TestCase): if c_functools: cmp_to_key = c_functools.cmp_to_key - # TODO: RUSTPYTHON - @unittest.expectedFailure + @support.cpython_only + def test_disallow_instantiation(self): + # Ensure that the type disallows instantiation (bpo-43916) + support.check_disallow_instantiation( + self, type(c_functools.cmp_to_key(None)) + ) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bad_cmp(self): - super().test_bad_cmp() + return super().test_bad_cmp() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cmp_to_key(self): - super().test_cmp_to_key() + return super().test_cmp_to_key() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cmp_to_key_arguments(self): - super().test_cmp_to_key_arguments() + return super().test_cmp_to_key_arguments() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_cmp_to_signature(self): + return super().test_cmp_to_signature() + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_hash(self): - super().test_hash() + return super().test_hash() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_obj_field(self): - super().test_obj_field() + return super().test_obj_field() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_sort_int(self): - super().test_sort_int() + return super().test_sort_int() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_sort_int_str(self): - super().test_sort_int_str() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_cmp_to_signature(self): - super().test_cmp_to_signature() - - @support.cpython_only - def test_disallow_instantiation(self): - # Ensure that the type disallows instantiation (bpo-43916) - support.check_disallow_instantiation( - self, type(c_functools.cmp_to_key(None)) - ) + return super().test_sort_int_str() class TestCmpToKeyPy(TestCmpToKey, unittest.TestCase): @@ -1340,6 +1380,16 @@ def fib(n): self.module._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0)) +class TestCachePy(TestCache, unittest.TestCase): + module = py_functools + + +@unittest.skipUnless(c_functools, 'requires the C _functools module') +class TestCacheC(TestCache, unittest.TestCase): + if c_functools: + module = c_functools + + class TestLRU: def test_lru(self): @@ -1773,8 +1823,7 @@ def f(x): time.sleep(.01) return 3 * x def test(i, x): - with self.subTest(thread=i): - self.assertEqual(f(x), 3 * x, i) + self.assertEqual(f(x), 3 * x, i) threads = [threading.Thread(target=test, args=(i, v)) for i, v in enumerate([1, 2, 2, 3, 2])] with threading_helper.start_threads(threads): @@ -1878,6 +1927,7 @@ def f(): return 1 self.assertEqual(f.cache_parameters(), {'maxsize': 1000, "typed": True}) + @support.suppress_immortalization() def test_lru_cache_weakrefable(self): @self.module.lru_cache def test_function(x): @@ -1908,12 +1958,33 @@ def test_staticmethod(x): self.assertIsNone(ref()) def test_common_signatures(self): - def orig(): ... + def orig(a, /, b, c=True): ... lru = self.module.lru_cache(1)(orig) + self.assertEqual(str(Signature.from_callable(lru)), '(a, /, b, c=True)') self.assertEqual(str(Signature.from_callable(lru.cache_info)), '()') self.assertEqual(str(Signature.from_callable(lru.cache_clear)), '()') + @support.skip_on_s390x + @unittest.skipIf(support.is_wasi, "WASI has limited C stack") + def test_lru_recursion(self): + + @self.module.lru_cache + def fib(n): + if n <= 1: + return n + return fib(n-1) + fib(n-2) + + if not support.Py_DEBUG: + depth = support.get_c_recursion_limit()*2//7 + with support.infinite_recursion(): + fib(depth) + if self.module == c_functools: + fib.cache_clear() + with support.infinite_recursion(): + with self.assertRaises(RecursionError): + fib(10000) + @py_functools.lru_cache() def py_cached_func(x, y): @@ -2524,6 +2595,74 @@ def _(arg): self.assertTrue(A.t('')) self.assertEqual(A.t(0.0), 0.0) + def test_slotted_class(self): + class Slot: + __slots__ = ('a', 'b') + @functools.singledispatchmethod + def go(self, item, arg): + pass + + @go.register + def _(self, item: int, arg): + return item + arg + + s = Slot() + self.assertEqual(s.go(1, 1), 2) + + def test_classmethod_slotted_class(self): + class Slot: + __slots__ = ('a', 'b') + @functools.singledispatchmethod + @classmethod + def go(cls, item, arg): + pass + + @go.register + @classmethod + def _(cls, item: int, arg): + return item + arg + + s = Slot() + self.assertEqual(s.go(1, 1), 2) + self.assertEqual(Slot.go(1, 1), 2) + + def test_staticmethod_slotted_class(self): + class A: + __slots__ = ['a'] + @functools.singledispatchmethod + @staticmethod + def t(arg): + return arg + @t.register(int) + @staticmethod + def _(arg): + return isinstance(arg, int) + @t.register(str) + @staticmethod + def _(arg): + return isinstance(arg, str) + a = A() + + self.assertTrue(A.t(0)) + self.assertTrue(A.t('')) + self.assertEqual(A.t(0.0), 0.0) + self.assertTrue(a.t(0)) + self.assertTrue(a.t('')) + self.assertEqual(a.t(0.0), 0.0) + + def test_assignment_behavior(self): + # see gh-106448 + class A: + @functools.singledispatchmethod + def t(arg): + return arg + + a = A() + a.t.foo = 'bar' + a2 = A() + with self.assertRaises(AttributeError): + a2.t.foo + def test_classmethod_register(self): class A: def __init__(self, arg): @@ -2659,6 +2798,7 @@ def static_func(arg: int) -> str: """My function docstring""" return str(arg) + prefix = A.__qualname__ + '.' for meth in ( A.func, A().func, @@ -2668,7 +2808,11 @@ def static_func(arg: int) -> str: A().static_func ): with self.subTest(meth=meth): - self.assertEqual(meth.__doc__, 'My function docstring') + self.assertEqual(meth.__qualname__, prefix + meth.__name__) + self.assertEqual(meth.__doc__, + ('My function docstring' + if support.HAVE_PY_DOCSTRINGS + else None)) self.assertEqual(meth.__annotations__['arg'], int) self.assertEqual(A.func.__name__, 'func') @@ -2757,7 +2901,10 @@ def decorated_classmethod(cls, arg: int) -> str: WithSingleDispatch().decorated_classmethod ): with self.subTest(meth=meth): - self.assertEqual(meth.__doc__, 'My function docstring') + self.assertEqual(meth.__doc__, + ('My function docstring' + if support.HAVE_PY_DOCSTRINGS + else None)) self.assertEqual(meth.__annotations__['arg'], int) self.assertEqual( @@ -2829,11 +2976,26 @@ def _(arg: typing.Union[int, typing.Iterable[str]]): def test_invalid_positional_argument(self): @functools.singledispatch - def f(*args): + def f(*args, **kwargs): pass msg = 'f requires at least 1 positional argument' with self.assertRaisesRegex(TypeError, msg): f() + msg = 'f requires at least 1 positional argument' + with self.assertRaisesRegex(TypeError, msg): + f(a=1) + + def test_invalid_positional_argument_singledispatchmethod(self): + class A: + @functools.singledispatchmethod + def t(self, *args, **kwargs): + pass + msg = 't requires at least 1 positional argument' + with self.assertRaisesRegex(TypeError, msg): + A().t() + msg = 't requires at least 1 positional argument' + with self.assertRaisesRegex(TypeError, msg): + A().t(a=1) def test_union(self): @functools.singledispatch @@ -2957,6 +3119,115 @@ def _(arg: typing.List[float] | bytes): self.assertEqual(f(""), "default") self.assertEqual(f(b""), "default") + def test_method_equal_instances(self): + # gh-127750: Reference to self was cached + class A: + def __eq__(self, other): + return True + def __hash__(self): + return 1 + @functools.singledispatchmethod + def t(self, arg): + return self + + a = A() + b = A() + self.assertIs(a.t(1), a) + self.assertIs(b.t(2), b) + + def test_method_bad_hash(self): + class A: + def __eq__(self, other): + raise AssertionError + def __hash__(self): + raise AssertionError + @functools.singledispatchmethod + def t(self, arg): + pass + + # Should not raise + A().t(1) + hash(A().t) + A().t == A().t + + def test_method_no_reference_loops(self): + # gh-127750: Created a strong reference to self + class A: + @functools.singledispatchmethod + def t(self, arg): + return weakref.ref(self) + + a = A() + r = a.t(1) + self.assertIsNotNone(r()) + del a # delete a after a.t + if not support.check_impl_detail(cpython=True): + support.gc_collect() + self.assertIsNone(r()) + + a = A() + t = a.t + del a # delete a before a.t + support.gc_collect() + r = t(1) + self.assertIsNotNone(r()) + del t + if not support.check_impl_detail(cpython=True): + support.gc_collect() + self.assertIsNone(r()) + + def test_signatures(self): + @functools.singledispatch + def func(item, arg: int) -> str: + return str(item) + @func.register + def _(item: int, arg: bytes) -> str: + return str(item) + + self.assertEqual(str(Signature.from_callable(func)), + '(item, arg: int) -> str') + + def test_method_signatures(self): + class A: + def m(self, item, arg: int) -> str: + return str(item) + @classmethod + def cm(cls, item, arg: int) -> str: + return str(item) + @functools.singledispatchmethod + def func(self, item, arg: int) -> str: + return str(item) + @func.register + def _(self, item, arg: bytes) -> str: + return str(item) + + @functools.singledispatchmethod + @classmethod + def cls_func(cls, item, arg: int) -> str: + return str(arg) + @func.register + @classmethod + def _(cls, item, arg: bytes) -> str: + return str(item) + + @functools.singledispatchmethod + @staticmethod + def static_func(item, arg: int) -> str: + return str(arg) + @func.register + @staticmethod + def _(item, arg: bytes) -> str: + return str(item) + + self.assertEqual(str(Signature.from_callable(A.func)), + '(self, item, arg: int) -> str') + self.assertEqual(str(Signature.from_callable(A().func)), + '(self, item, arg: int) -> str') + self.assertEqual(str(Signature.from_callable(A.cls_func)), + '(cls, item, arg: int) -> str') + self.assertEqual(str(Signature.from_callable(A.static_func)), + '(item, arg: int) -> str') + class CachedCostItem: _cost = 1 @@ -3007,8 +3278,7 @@ def test_cached_attribute_name_differs_from_func_name(self): self.assertEqual(item.get_cost(), 4) self.assertEqual(item.cached_cost, 3) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_object_with_slots(self): item = CachedCostItemWithSlots() with self.assertRaisesRegex( @@ -3032,8 +3302,7 @@ class MyClass(metaclass=MyMeta): ): MyClass.prop - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_reuse_different_names(self): """Disallow this case because decorated function a would not be cached.""" with self.assertRaises(TypeError) as ctx: @@ -3089,7 +3358,13 @@ def test_access_from_class(self): self.assertIsInstance(CachedCostItem.cost, py_functools.cached_property) def test_doc(self): - self.assertEqual(CachedCostItem.cost.__doc__, "The cost of the item.") + self.assertEqual(CachedCostItem.cost.__doc__, + ("The cost of the item." + if support.HAVE_PY_DOCSTRINGS + else None)) + + def test_module(self): + self.assertEqual(CachedCostItem.cost.__module__, CachedCostItem.__module__) def test_subclass_with___set__(self): """Caching still works for a subclass defining __set__.""" From 0fb7d0fae2a0701edec27a2bdb52a3527289c425 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:29:50 +0300 Subject: [PATCH 305/819] Use ruff for `Expr` unparsing (#6124) * Use ruff for unparse backend * Update `test_future_stmt/*.py` from 3.13.7 * Mark failing tests * Mark failing test * Merge remote-tracking branch 'upstream/main' into ruff-unparse * Reapply ruff code * remove git symbols * Unmark passing test --- Cargo.lock | 25 +++ Cargo.toml | 1 + ...syntax_future10.py => badsyntax_future.py} | 0 .../test_future_stmt/badsyntax_future3.py | 10 - .../test_future_stmt/badsyntax_future4.py | 10 - .../test_future_stmt/badsyntax_future5.py | 12 - .../test_future_stmt/badsyntax_future6.py | 10 - .../test_future_stmt/badsyntax_future7.py | 11 - .../test_future_stmt/badsyntax_future8.py | 10 - .../test_future_stmt/badsyntax_future9.py | 10 - ..._test1.py => import_nested_scope_twice.py} | 0 .../{future_test2.py => nested_scope.py} | 0 Lib/test/test_future_stmt/test_future.py | 206 ++++++++++++------ compiler/codegen/Cargo.toml | 3 + compiler/codegen/src/compile.rs | 17 +- compiler/codegen/src/lib.rs | 1 - 16 files changed, 179 insertions(+), 147 deletions(-) rename Lib/test/test_future_stmt/{badsyntax_future10.py => badsyntax_future.py} (100%) delete mode 100644 Lib/test/test_future_stmt/badsyntax_future3.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future4.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future5.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future6.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future7.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future8.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future9.py rename Lib/test/test_future_stmt/{future_test1.py => import_nested_scope_twice.py} (100%) rename Lib/test/test_future_stmt/{future_test2.py => nested_scope.py} (100%) diff --git a/Cargo.lock b/Cargo.lock index 53b4f969223..cc0f080fd10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2282,6 +2282,29 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "ruff_python_codegen" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" +dependencies = [ + "ruff_python_ast", + "ruff_python_literal", + "ruff_python_parser", + "ruff_source_file", + "ruff_text_size", +] + +[[package]] +name = "ruff_python_literal" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" +dependencies = [ + "bitflags 2.9.4", + "itertools 0.14.0", + "ruff_python_ast", + "unic-ucd-category", +] + [[package]] name = "ruff_python_parser" version = "0.0.0" @@ -2387,7 +2410,9 @@ dependencies = [ "num-complex", "num-traits", "ruff_python_ast", + "ruff_python_codegen", "ruff_python_parser", + "ruff_source_file", "ruff_text_size", "rustpython-compiler-core", "rustpython-literal", diff --git a/Cargo.toml b/Cargo.toml index 3cdc471dc3d..77bba61a3b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -159,6 +159,7 @@ rustpython-wtf8 = { path = "wtf8", version = "0.4.0" } rustpython-doc = { git = "https://github.com/RustPython/__doc__", tag = "0.3.0", version = "0.3.0" } ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } +ruff_python_codegen = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } diff --git a/Lib/test/test_future_stmt/badsyntax_future10.py b/Lib/test/test_future_stmt/badsyntax_future.py similarity index 100% rename from Lib/test/test_future_stmt/badsyntax_future10.py rename to Lib/test/test_future_stmt/badsyntax_future.py diff --git a/Lib/test/test_future_stmt/badsyntax_future3.py b/Lib/test/test_future_stmt/badsyntax_future3.py deleted file mode 100644 index f1c8417edaa..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future3.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This is a test""" -from __future__ import nested_scopes -from __future__ import rested_snopes - -def f(x): - def g(y): - return x + y - return g - -result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future4.py b/Lib/test/test_future_stmt/badsyntax_future4.py deleted file mode 100644 index b5f4c98e922..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future4.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This is a test""" -import __future__ -from __future__ import nested_scopes - -def f(x): - def g(y): - return x + y - return g - -result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future5.py b/Lib/test/test_future_stmt/badsyntax_future5.py deleted file mode 100644 index 8a7e5fcb70f..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future5.py +++ /dev/null @@ -1,12 +0,0 @@ -"""This is a test""" -from __future__ import nested_scopes -import foo -from __future__ import nested_scopes - - -def f(x): - def g(y): - return x + y - return g - -result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future6.py b/Lib/test/test_future_stmt/badsyntax_future6.py deleted file mode 100644 index 5a8b55a02c4..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future6.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This is a test""" -"this isn't a doc string" -from __future__ import nested_scopes - -def f(x): - def g(y): - return x + y - return g - -result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future7.py b/Lib/test/test_future_stmt/badsyntax_future7.py deleted file mode 100644 index 131db2c2164..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future7.py +++ /dev/null @@ -1,11 +0,0 @@ -"""This is a test""" - -from __future__ import nested_scopes; import string; from __future__ import \ - nested_scopes - -def f(x): - def g(y): - return x + y - return g - -result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future8.py b/Lib/test/test_future_stmt/badsyntax_future8.py deleted file mode 100644 index ca45289e2e5..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future8.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This is a test""" - -from __future__ import * - -def f(x): - def g(y): - return x + y - return g - -print(f(2)(4)) diff --git a/Lib/test/test_future_stmt/badsyntax_future9.py b/Lib/test/test_future_stmt/badsyntax_future9.py deleted file mode 100644 index 916de06ab71..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future9.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This is a test""" - -from __future__ import nested_scopes, braces - -def f(x): - def g(y): - return x + y - return g - -print(f(2)(4)) diff --git a/Lib/test/test_future_stmt/future_test1.py b/Lib/test/test_future_stmt/import_nested_scope_twice.py similarity index 100% rename from Lib/test/test_future_stmt/future_test1.py rename to Lib/test/test_future_stmt/import_nested_scope_twice.py diff --git a/Lib/test/test_future_stmt/future_test2.py b/Lib/test/test_future_stmt/nested_scope.py similarity index 100% rename from Lib/test/test_future_stmt/future_test2.py rename to Lib/test/test_future_stmt/nested_scope.py diff --git a/Lib/test/test_future_stmt/test_future.py b/Lib/test/test_future_stmt/test_future.py index 9c30054963b..e6e6201a76b 100644 --- a/Lib/test/test_future_stmt/test_future.py +++ b/Lib/test/test_future_stmt/test_future.py @@ -10,6 +10,8 @@ import re import sys +TOP_LEVEL_MSG = 'from __future__ imports must occur at the beginning of the file' + rx = re.compile(r'\((\S+).py, line (\d+)') def get_error_location(msg): @@ -18,21 +20,48 @@ def get_error_location(msg): class FutureTest(unittest.TestCase): - def check_syntax_error(self, err, basename, lineno, offset=1): - self.assertIn('%s.py, line %d' % (basename, lineno), str(err)) - self.assertEqual(os.path.basename(err.filename), basename + '.py') + def check_syntax_error(self, err, basename, + *, + lineno, + message=TOP_LEVEL_MSG, offset=1): + if basename != '<string>': + basename += '.py' + + self.assertEqual(f'{message} ({basename}, line {lineno})', str(err)) + self.assertEqual(os.path.basename(err.filename), basename) self.assertEqual(err.lineno, lineno) self.assertEqual(err.offset, offset) - def test_future1(self): - with import_helper.CleanImport('test.test_future_stmt.future_test1'): - from test.test_future_stmt import future_test1 - self.assertEqual(future_test1.result, 6) + def assertSyntaxError(self, code, + *, + lineno=1, + message=TOP_LEVEL_MSG, offset=1, + parametrize_docstring=True): + code = dedent(code.lstrip('\n')) + for add_docstring in ([False, True] if parametrize_docstring else [False]): + with self.subTest(code=code, add_docstring=add_docstring): + if add_docstring: + code = '"""Docstring"""\n' + code + lineno += 1 + with self.assertRaises(SyntaxError) as cm: + exec(code) + self.check_syntax_error(cm.exception, "<string>", + lineno=lineno, + message=message, + offset=offset) + + def test_import_nested_scope_twice(self): + # Import the name nested_scopes twice to trigger SF bug #407394 + with import_helper.CleanImport( + 'test.test_future_stmt.import_nested_scope_twice', + ): + from test.test_future_stmt import import_nested_scope_twice + self.assertEqual(import_nested_scope_twice.result, 6) - def test_future2(self): - with import_helper.CleanImport('test.test_future_stmt.future_test2'): - from test.test_future_stmt import future_test2 - self.assertEqual(future_test2.result, 6) + def test_nested_scope(self): + with import_helper.CleanImport('test.test_future_stmt.nested_scope'): + from test.test_future_stmt import nested_scope + self.assertEqual(nested_scope.result, 6) def test_future_single_import(self): with import_helper.CleanImport( @@ -52,47 +81,87 @@ def test_future_multiple_features(self): ): from test.test_future_stmt import test_future_multiple_features - def test_badfuture3(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future3 - self.check_syntax_error(cm.exception, "badsyntax_future3", 3) + @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message + def test_unknown_future_flag(self): + code = """ + from __future__ import nested_scopes + from __future__ import rested_snopes # typo error here: nested => rested + """ + self.assertSyntaxError( + code, lineno=2, + message='future feature rested_snopes is not defined', offset=24, + ) - def test_badfuture4(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future4 - self.check_syntax_error(cm.exception, "badsyntax_future4", 3) + @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message + def test_future_import_not_on_top(self): + code = """ + import some_module + from __future__ import annotations + """ + self.assertSyntaxError(code, lineno=2) - def test_badfuture5(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future5 - self.check_syntax_error(cm.exception, "badsyntax_future5", 4) + code = """ + import __future__ + from __future__ import annotations + """ + self.assertSyntaxError(code, lineno=2) - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_badfuture6(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future6 - self.check_syntax_error(cm.exception, "badsyntax_future6", 3) + code = """ + from __future__ import absolute_import + "spam, bar, blah" + from __future__ import print_function + """ + self.assertSyntaxError(code, lineno=3) + + @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message + def test_future_import_with_extra_string(self): + code = """ + '''Docstring''' + "this isn't a doc string" + from __future__ import nested_scopes + """ + self.assertSyntaxError(code, lineno=3, parametrize_docstring=False) + + @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message + def test_multiple_import_statements_on_same_line(self): + # With `\`: + code = """ + from __future__ import nested_scopes; import string; from __future__ import \ + nested_scopes + """ + self.assertSyntaxError(code, offset=54) - def test_badfuture7(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future7 - self.check_syntax_error(cm.exception, "badsyntax_future7", 3, 54) + # Without `\`: + code = """ + from __future__ import nested_scopes; import string; from __future__ import nested_scopes + """ + self.assertSyntaxError(code, offset=54) - def test_badfuture8(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future8 - self.check_syntax_error(cm.exception, "badsyntax_future8", 3) + @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message + def test_future_import_star(self): + code = """ + from __future__ import * + """ + self.assertSyntaxError(code, message='future feature * is not defined', offset=24) - def test_badfuture9(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future9 - self.check_syntax_error(cm.exception, "badsyntax_future9", 3) + @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message + def test_future_import_braces(self): + code = """ + from __future__ import braces + """ + # Congrats, you found an easter egg! + self.assertSyntaxError(code, message='not a chance', offset=24) + + code = """ + from __future__ import nested_scopes, braces + """ + self.assertSyntaxError(code, message='not a chance', offset=39) - def test_badfuture10(self): + @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message + def test_module_with_future_import_not_on_top(self): with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future10 - self.check_syntax_error(cm.exception, "badsyntax_future10", 3) + from test.test_future_stmt import badsyntax_future + self.check_syntax_error(cm.exception, "badsyntax_future", lineno=3) def test_ensure_flags_dont_clash(self): # bpo-39562: test that future flags and compiler flags doesn't clash @@ -109,26 +178,6 @@ def test_ensure_flags_dont_clash(self): } self.assertCountEqual(set(flags.values()), flags.values()) - def test_parserhack(self): - # test that the parser.c::future_hack function works as expected - # Note: although this test must pass, it's not testing the original - # bug as of 2.6 since the with statement is not optional and - # the parser hack disabled. If a new keyword is introduced in - # 2.6, change this to refer to the new future import. - try: - exec("from __future__ import print_function; print 0") - except SyntaxError: - pass - else: - self.fail("syntax error didn't occur") - - try: - exec("from __future__ import (print_function); print 0") - except SyntaxError: - pass - else: - self.fail("syntax error didn't occur") - def test_unicode_literals_exec(self): scope = {} exec("from __future__ import unicode_literals; x = ''", {}, scope) @@ -141,6 +190,26 @@ def test_syntactical_future_repl(self): out = kill_python(p) self.assertNotIn(b'SyntaxError: invalid syntax', out) + @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: future feature spam is not defined + def test_future_dotted_import(self): + with self.assertRaises(ImportError): + exec("from .__future__ import spam") + + code = dedent( + """ + from __future__ import print_function + from ...__future__ import ham + """ + ) + with self.assertRaises(ImportError): + exec(code) + + code = """ + from .__future__ import nested_scopes + from __future__ import barry_as_FLUFL + """ + self.assertSyntaxError(code, lineno=2) + class AnnotationsFutureTestCase(unittest.TestCase): template = dedent( """ @@ -198,6 +267,7 @@ def _exec_future(self, code): ) return scope + @unittest.expectedFailure # TODO: RUSTPYTHON; 'a,' != '(a,)' def test_annotations(self): eq = self.assertAnnotationEqual eq('...') @@ -362,6 +432,7 @@ def test_annotations(self): eq('(((a, b)))', '(a, b)') eq("1 + 2 + 3") + @unittest.expectedFailure # TODO: RUSTPYTHON; "f'{x=!r}'" != "f'x={x!r}'" def test_fstring_debug_annotations(self): # f-strings with '=' don't round trip very well, so set the expected # result explicitly. @@ -372,6 +443,7 @@ def test_fstring_debug_annotations(self): self.assertAnnotationEqual("f'{x=!a}'", expected="f'x={x!a}'") self.assertAnnotationEqual("f'{x=!s:*^20}'", expected="f'x={x!s:*^20}'") + @unittest.expectedFailure # TODO: RUSTPYTHON; '1e309, 1e309j' != '(1e309, 1e309j)' def test_infinity_numbers(self): inf = "1e" + repr(sys.float_info.max_10_exp + 1) infj = f"{inf}j" @@ -384,8 +456,7 @@ def test_infinity_numbers(self): self.assertAnnotationEqual("('inf', 1e1000, 'infxxx', 1e1000j)", expected=f"('inf', {inf}, 'infxxx', {infj})") self.assertAnnotationEqual("(1e1000, (1e1000j,))", expected=f"({inf}, ({infj},))") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised def test_annotation_with_complex_target(self): with self.assertRaises(SyntaxError): exec( @@ -409,8 +480,7 @@ def bar(): self.assertEqual(foo.__code__.co_cellvars, ()) self.assertEqual(foo().__code__.co_freevars, ()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; "f'{x=!r}'" != "f'x={x!r}'" def test_annotations_forbidden(self): with self.assertRaises(SyntaxError): self._exec_future("test: (yield)") diff --git a/compiler/codegen/Cargo.toml b/compiler/codegen/Cargo.toml index ce7e8d74f59..e2bfec6c3c3 100644 --- a/compiler/codegen/Cargo.toml +++ b/compiler/codegen/Cargo.toml @@ -13,6 +13,9 @@ rustpython-compiler-core = { workspace = true } rustpython-literal = {workspace = true } rustpython-wtf8 = { workspace = true } ruff_python_ast = { workspace = true } +ruff_python_parser = { workspace = true } +ruff_python_codegen = { workspace = true } +ruff_source_file = { workspace = true } ruff_text_size = { workspace = true } ahash = { workspace = true } diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 5f8caebf272..132998fe838 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -14,7 +14,6 @@ use crate::{ error::{CodegenError, CodegenErrorType, InternalError, PatternUnreachableReason}, ir::{self, BlockIdx}, symboltable::{self, CompilerScope, SymbolFlags, SymbolScope, SymbolTable}, - unparse::UnparseExpr, }; use itertools::Itertools; use malachite_bigint::BigInt; @@ -31,6 +30,7 @@ use ruff_python_ast::{ PatternMatchStar, PatternMatchValue, Singleton, Stmt, StmtExpr, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem, }; +use ruff_source_file::LineEnding; use ruff_text_size::{Ranged, TextRange}; use rustpython_compiler_core::{ Mode, OneIndexed, PositionEncoding, SourceFile, SourceLocation, @@ -147,6 +147,15 @@ enum ComprehensionType { Dict, } +fn unparse_expr(expr: &Expr) -> String { + use ruff_python_ast::str::Quote; + use ruff_python_codegen::{Generator, Indentation}; + + Generator::new(&Indentation::default(), LineEnding::default()) + .with_preferred_quote(Some(Quote::Single)) + .expr(expr) +} + fn validate_duplicate_params(params: &Parameters) -> Result<(), CodegenErrorType> { let mut seen_params = HashSet::new(); for param in params { @@ -3609,7 +3618,7 @@ impl Compiler { | Expr::NoneLiteral(_) ); let key_repr = if is_literal { - UnparseExpr::new(key, &self.source_file).to_string() + unparse_expr(key) } else if is_attribute { String::new() } else { @@ -4163,9 +4172,7 @@ impl Compiler { fn compile_annotation(&mut self, annotation: &Expr) -> CompileResult<()> { if self.future_annotations { self.emit_load_const(ConstantData::Str { - value: UnparseExpr::new(annotation, &self.source_file) - .to_string() - .into(), + value: unparse_expr(annotation).into(), }); } else { let was_in_annotation = self.in_annotation; diff --git a/compiler/codegen/src/lib.rs b/compiler/codegen/src/lib.rs index 291b57d7f67..e7944079988 100644 --- a/compiler/codegen/src/lib.rs +++ b/compiler/codegen/src/lib.rs @@ -13,7 +13,6 @@ pub mod error; pub mod ir; mod string_parser; pub mod symboltable; -mod unparse; pub use compile::CompileOpts; use ruff_python_ast::Expr; From f22aed26146a891b9c245e1dc1f8b83207477aed Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:09:42 +0900 Subject: [PATCH 306/819] Fix PyCode constructor/replace (#6193) * Fix PyCode constructor * Reuse MarshalError --- Lib/test/test_code.py | 6 - Lib/test/test_funcattrs.py | 2 - compiler/core/src/marshal.rs | 22 ++- vm/src/builtins/code.rs | 314 +++++++++++++++++++++++++------- vm/src/builtins/function.rs | 29 ++- vm/src/builtins/function/jit.rs | 22 ++- 6 files changed, 296 insertions(+), 99 deletions(-) diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index b7e5784b48c..804cce1dba4 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -222,8 +222,6 @@ class List(list): obj = List([1, 2, 3]) self.assertEqual(obj[0], "Foreign getitem: 1") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_constructor(self): def func(): pass co = func.__code__ @@ -255,8 +253,6 @@ def test_qualname(self): CodeTest.test_qualname.__qualname__ ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_replace(self): def func(): x = 1 @@ -297,8 +293,6 @@ def func2(): self.assertEqual(new_code.co_varnames, code2.co_varnames) self.assertEqual(new_code.co_nlocals, code2.co_nlocals) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_nlocals_mismatch(self): def func(): x = 1 diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index 3d5378092b4..e06e9f7f4a9 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -65,8 +65,6 @@ def duplicate(): return 3 self.assertNotEqual(self.b, duplicate) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_copying___code__(self): def test(): pass self.assertEqual(test(), None) diff --git a/compiler/core/src/marshal.rs b/compiler/core/src/marshal.rs index 5e16e59102a..f803317772d 100644 --- a/compiler/core/src/marshal.rs +++ b/compiler/core/src/marshal.rs @@ -165,6 +165,19 @@ impl<'a> ReadBorrowed<'a> for &'a [u8] { } } +/// Parses bytecode bytes into CodeUnit instructions. +/// Each instruction is 2 bytes: opcode and argument. +pub fn parse_instructions_from_bytes(bytes: &[u8]) -> Result<Box<[CodeUnit]>> { + bytes + .chunks_exact(2) + .map(|cu| { + let op = Instruction::try_from(cu[0])?; + let arg = OpArgByte(cu[1]); + Ok(CodeUnit { op, arg }) + }) + .collect() +} + pub struct Cursor<B> { pub data: B, pub position: usize, @@ -185,14 +198,7 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>( ) -> Result<CodeObject<Bag::Constant>> { let len = rdr.read_u32()?; let instructions = rdr.read_slice(len * 2)?; - let instructions = instructions - .chunks_exact(2) - .map(|cu| { - let op = Instruction::try_from(cu[0])?; - let arg = OpArgByte(cu[1]); - Ok(CodeUnit { op, arg }) - }) - .collect::<Result<Box<[CodeUnit]>>>()?; + let instructions = parse_instructions_from_bytes(instructions)?; let len = rdr.read_u32()?; let locations = (0..len) diff --git a/vm/src/builtins/code.rs b/vm/src/builtins/code.rs index 79ad896aaf8..2a22993a9e7 100644 --- a/vm/src/builtins/code.rs +++ b/vm/src/builtins/code.rs @@ -2,21 +2,24 @@ */ -use super::{PyStrRef, PyTupleRef, PyType, PyTypeRef}; +use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType, PyTypeRef}; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::PyStrInterned, - bytecode::{self, AsBag, BorrowedConstant, CodeFlags, Constant, ConstantBag}, + bytecode::{self, AsBag, BorrowedConstant, CodeFlags, CodeUnit, Constant, ConstantBag}, class::{PyClassImpl, StaticType}, convert::ToPyObject, frozen, - function::{FuncArgs, OptionalArg}, - types::Representable, + function::OptionalArg, + types::{Constructor, Representable}, }; use malachite_bigint::BigInt; use num_traits::Zero; -use rustpython_compiler_core::OneIndexed; -use rustpython_compiler_core::bytecode::PyCodeLocationInfoKind; +use rustpython_compiler_core::{ + OneIndexed, + bytecode::PyCodeLocationInfoKind, + marshal::{MarshalError, parse_instructions_from_bytes}, +}; use std::{borrow::Borrow, fmt, ops::Deref}; /// State for iterating through code address ranges @@ -367,13 +370,158 @@ impl Representable for PyCode { } } -#[pyclass(with(Representable))] -impl PyCode { - #[pyslot] - fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("Cannot directly create code object")) +// Arguments for code object constructor +#[derive(FromArgs)] +pub struct PyCodeNewArgs { + argcount: u32, + posonlyargcount: u32, + kwonlyargcount: u32, + nlocals: u32, + stacksize: u32, + flags: u16, + co_code: PyBytesRef, + consts: PyTupleRef, + names: PyTupleRef, + varnames: PyTupleRef, + filename: PyStrRef, + name: PyStrRef, + qualname: PyStrRef, + firstlineno: i32, + linetable: PyBytesRef, + exceptiontable: PyBytesRef, + freevars: PyTupleRef, + cellvars: PyTupleRef, +} + +impl Constructor for PyCode { + type Args = PyCodeNewArgs; + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Convert names tuple to vector of interned strings + let names: Box<[&'static PyStrInterned]> = args + .names + .iter() + .map(|obj| { + let s = obj.downcast_ref::<super::pystr::PyStr>().ok_or_else(|| { + vm.new_type_error("names must be tuple of strings".to_owned()) + })?; + Ok(vm.ctx.intern_str(s.as_str())) + }) + .collect::<PyResult<Vec<_>>>()? + .into_boxed_slice(); + + let varnames: Box<[&'static PyStrInterned]> = args + .varnames + .iter() + .map(|obj| { + let s = obj.downcast_ref::<super::pystr::PyStr>().ok_or_else(|| { + vm.new_type_error("varnames must be tuple of strings".to_owned()) + })?; + Ok(vm.ctx.intern_str(s.as_str())) + }) + .collect::<PyResult<Vec<_>>>()? + .into_boxed_slice(); + + let cellvars: Box<[&'static PyStrInterned]> = args + .cellvars + .iter() + .map(|obj| { + let s = obj.downcast_ref::<super::pystr::PyStr>().ok_or_else(|| { + vm.new_type_error("cellvars must be tuple of strings".to_owned()) + })?; + Ok(vm.ctx.intern_str(s.as_str())) + }) + .collect::<PyResult<Vec<_>>>()? + .into_boxed_slice(); + + let freevars: Box<[&'static PyStrInterned]> = args + .freevars + .iter() + .map(|obj| { + let s = obj.downcast_ref::<super::pystr::PyStr>().ok_or_else(|| { + vm.new_type_error("freevars must be tuple of strings".to_owned()) + })?; + Ok(vm.ctx.intern_str(s.as_str())) + }) + .collect::<PyResult<Vec<_>>>()? + .into_boxed_slice(); + + // Check nlocals matches varnames length + if args.nlocals as usize != varnames.len() { + return Err(vm.new_value_error(format!( + "nlocals ({}) != len(varnames) ({})", + args.nlocals, + varnames.len() + ))); + } + + // Parse and validate bytecode from bytes + let bytecode_bytes = args.co_code.as_bytes(); + let instructions = parse_bytecode(bytecode_bytes) + .map_err(|e| vm.new_value_error(format!("invalid bytecode: {}", e)))?; + + // Convert constants + let constants: Box<[Literal]> = args + .consts + .iter() + .map(|obj| { + // Convert PyObject to Literal constant + // For now, just wrap it + Literal(obj.clone()) + }) + .collect::<Vec<_>>() + .into_boxed_slice(); + + // Create locations + let row = if args.firstlineno > 0 { + OneIndexed::new(args.firstlineno as usize).unwrap_or(OneIndexed::MIN) + } else { + OneIndexed::MIN + }; + let locations: Box<[rustpython_compiler_core::SourceLocation]> = vec![ + rustpython_compiler_core::SourceLocation { + line: row, + character_offset: OneIndexed::from_zero_indexed(0), + }; + instructions.len() + ] + .into_boxed_slice(); + + // Build the CodeObject + let code = CodeObject { + instructions, + locations, + flags: CodeFlags::from_bits_truncate(args.flags), + posonlyarg_count: args.posonlyargcount, + arg_count: args.argcount, + kwonlyarg_count: args.kwonlyargcount, + source_path: vm.ctx.intern_str(args.filename.as_str()), + first_line_number: if args.firstlineno > 0 { + OneIndexed::new(args.firstlineno as usize) + } else { + None + }, + max_stackdepth: args.stacksize, + obj_name: vm.ctx.intern_str(args.name.as_str()), + qualname: vm.ctx.intern_str(args.qualname.as_str()), + cell2arg: None, // TODO: reuse `fn cell2arg` + constants, + names, + varnames, + cellvars, + freevars, + linetable: args.linetable.as_bytes().to_vec().into_boxed_slice(), + exceptiontable: args.exceptiontable.as_bytes().to_vec().into_boxed_slice(), + }; + + Ok(PyCode::new(code) + .into_ref_with_type(vm, cls)? + .to_pyobject(vm)) } +} +#[pyclass(with(Representable, Constructor))] +impl PyCode { #[pygetset] const fn co_posonlyargcount(&self) -> usize { self.code.posonlyarg_count as usize @@ -397,9 +545,7 @@ impl PyCode { #[pygetset] pub fn co_cellvars(&self, vm: &VirtualMachine) -> PyTupleRef { let cellvars = self - .code .cellvars - .deref() .iter() .map(|name| name.to_pyobject(vm)) .collect(); @@ -408,7 +554,7 @@ impl PyCode { #[pygetset] fn co_nlocals(&self) -> usize { - self.varnames.len() + self.code.varnames.len() } #[pygetset] @@ -690,42 +836,62 @@ impl PyCode { #[pymethod] pub fn replace(&self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<Self> { - let posonlyarg_count = match args.co_posonlyargcount { + let ReplaceArgs { + co_posonlyargcount, + co_argcount, + co_kwonlyargcount, + co_filename, + co_firstlineno, + co_consts, + co_name, + co_names, + co_flags, + co_varnames, + co_nlocals, + co_stacksize, + co_code, + co_linetable, + co_exceptiontable, + co_freevars, + co_cellvars, + co_qualname, + } = args; + let posonlyarg_count = match co_posonlyargcount { OptionalArg::Present(posonlyarg_count) => posonlyarg_count, OptionalArg::Missing => self.code.posonlyarg_count, }; - let arg_count = match args.co_argcount { + let arg_count = match co_argcount { OptionalArg::Present(arg_count) => arg_count, OptionalArg::Missing => self.code.arg_count, }; - let source_path = match args.co_filename { + let source_path = match co_filename { OptionalArg::Present(source_path) => source_path, OptionalArg::Missing => self.code.source_path.to_owned(), }; - let first_line_number = match args.co_firstlineno { + let first_line_number = match co_firstlineno { OptionalArg::Present(first_line_number) => OneIndexed::new(first_line_number as _), OptionalArg::Missing => self.code.first_line_number, }; - let kwonlyarg_count = match args.co_kwonlyargcount { + let kwonlyarg_count = match co_kwonlyargcount { OptionalArg::Present(kwonlyarg_count) => kwonlyarg_count, OptionalArg::Missing => self.code.kwonlyarg_count, }; - let constants = match args.co_consts { + let constants = match co_consts { OptionalArg::Present(constants) => constants, OptionalArg::Missing => self.code.constants.iter().map(|x| x.0.clone()).collect(), }; - let obj_name = match args.co_name { + let obj_name = match co_name { OptionalArg::Present(obj_name) => obj_name, OptionalArg::Missing => self.code.obj_name.to_owned(), }; - let names = match args.co_names { + let names = match co_names { OptionalArg::Present(names) => names, OptionalArg::Missing => self .code @@ -736,37 +902,36 @@ impl PyCode { .collect(), }; - let flags = match args.co_flags { + let flags = match co_flags { OptionalArg::Present(flags) => flags, OptionalArg::Missing => self.code.flags.bits(), }; - let varnames = match args.co_varnames { + let varnames = match co_varnames { OptionalArg::Present(varnames) => varnames, OptionalArg::Missing => self.code.varnames.iter().map(|s| s.to_object()).collect(), }; - let qualname = match args.co_qualname { + let qualname = match co_qualname { OptionalArg::Present(qualname) => qualname, OptionalArg::Missing => self.code.qualname.to_owned(), }; - let max_stackdepth = match args.co_stacksize { + let max_stackdepth = match co_stacksize { OptionalArg::Present(stacksize) => stacksize, OptionalArg::Missing => self.code.max_stackdepth, }; - let instructions = match args.co_code { - OptionalArg::Present(_code_bytes) => { - // Convert bytes back to instructions - // For now, keep the original instructions - // TODO: Properly parse bytecode from bytes - self.code.instructions.clone() + let instructions = match co_code { + OptionalArg::Present(code_bytes) => { + // Parse and validate bytecode from bytes + parse_bytecode(code_bytes.as_bytes()) + .map_err(|e| vm.new_value_error(format!("invalid bytecode: {}", e)))? } OptionalArg::Missing => self.code.instructions.clone(), }; - let cellvars = match args.co_cellvars { + let cellvars = match co_cellvars { OptionalArg::Present(cellvars) => cellvars .into_iter() .map(|o| o.as_interned_str(vm).unwrap()) @@ -774,7 +939,7 @@ impl PyCode { OptionalArg::Missing => self.code.cellvars.clone(), }; - let freevars = match args.co_freevars { + let freevars = match co_freevars { OptionalArg::Present(freevars) => freevars .into_iter() .map(|o| o.as_interned_str(vm).unwrap()) @@ -783,7 +948,7 @@ impl PyCode { }; // Validate co_nlocals if provided - if let OptionalArg::Present(nlocals) = args.co_nlocals + if let OptionalArg::Present(nlocals) = co_nlocals && nlocals as usize != varnames.len() { return Err(vm.new_value_error(format!( @@ -794,48 +959,50 @@ impl PyCode { } // Handle linetable and exceptiontable - let linetable = match args.co_linetable { + let linetable = match co_linetable { OptionalArg::Present(linetable) => linetable.as_bytes().to_vec().into_boxed_slice(), OptionalArg::Missing => self.code.linetable.clone(), }; - let exceptiontable = match args.co_exceptiontable { + let exceptiontable = match co_exceptiontable { OptionalArg::Present(exceptiontable) => { exceptiontable.as_bytes().to_vec().into_boxed_slice() } OptionalArg::Missing => self.code.exceptiontable.clone(), }; - Ok(Self { - code: CodeObject { - flags: CodeFlags::from_bits_truncate(flags), - posonlyarg_count, - arg_count, - kwonlyarg_count, - source_path: source_path.as_object().as_interned_str(vm).unwrap(), - first_line_number, - obj_name: obj_name.as_object().as_interned_str(vm).unwrap(), - qualname: qualname.as_object().as_interned_str(vm).unwrap(), - - max_stackdepth, - instructions, - locations: self.code.locations.clone(), - constants: constants.into_iter().map(Literal).collect(), - names: names - .into_iter() - .map(|o| o.as_interned_str(vm).unwrap()) - .collect(), - varnames: varnames - .into_iter() - .map(|o| o.as_interned_str(vm).unwrap()) - .collect(), - cellvars, - freevars, - cell2arg: self.code.cell2arg.clone(), - linetable, - exceptiontable, - }, - }) + let new_code = CodeObject { + flags: CodeFlags::from_bits_truncate(flags), + posonlyarg_count, + arg_count, + kwonlyarg_count, + source_path: source_path.as_object().as_interned_str(vm).unwrap(), + first_line_number, + obj_name: obj_name.as_object().as_interned_str(vm).unwrap(), + qualname: qualname.as_object().as_interned_str(vm).unwrap(), + + max_stackdepth, + instructions, + // FIXME: invalid locations. Actually locations is a duplication of linetable. + // It can be removed once we move every other code to use linetable only. + locations: self.code.locations.clone(), + constants: constants.into_iter().map(Literal).collect(), + names: names + .into_iter() + .map(|o| o.as_interned_str(vm).unwrap()) + .collect(), + varnames: varnames + .into_iter() + .map(|o| o.as_interned_str(vm).unwrap()) + .collect(), + cellvars, + freevars, + cell2arg: self.code.cell2arg.clone(), + linetable, + exceptiontable, + }; + + Ok(PyCode::new(new_code)) } #[pymethod] @@ -866,6 +1033,19 @@ impl ToPyObject for bytecode::CodeObject { } } +/// Validates and parses bytecode bytes into CodeUnit instructions. +/// Returns MarshalError if bytecode is invalid (odd length or contains invalid opcodes). +/// Note: Returning MarshalError is not necessary at this point because this is not a part of marshalling API. +/// However, we (temporarily) reuse MarshalError for simplicity. +fn parse_bytecode(bytecode_bytes: &[u8]) -> Result<Box<[CodeUnit]>, MarshalError> { + // Bytecode must have even length (each instruction is 2 bytes) + if !bytecode_bytes.len().is_multiple_of(2) { + return Err(MarshalError::InvalidBytecode); + } + + parse_instructions_from_bytes(bytecode_bytes) +} + // Helper struct for reading linetable struct LineTableReader<'a> { data: &'a [u8], diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index 02e983f2ffa..19daf885fb1 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -28,7 +28,7 @@ use rustpython_jit::CompiledCode; #[pyclass(module = false, name = "function", traverse = "manual")] #[derive(Debug)] pub struct PyFunction { - code: PyRef<PyCode>, + code: PyMutex<PyRef<PyCode>>, globals: PyDictRef, builtins: PyObjectRef, closure: Option<PyRef<PyTuple<PyCellRef>>>, @@ -73,7 +73,7 @@ impl PyFunction { let qualname = vm.ctx.new_str(code.qualname.as_str()); let func = Self { - code: code.clone(), + code: PyMutex::new(code.clone()), globals, builtins, closure: None, @@ -96,7 +96,7 @@ impl PyFunction { func_args: FuncArgs, vm: &VirtualMachine, ) -> PyResult<()> { - let code = &*self.code; + let code = &*self.code.lock(); let nargs = func_args.args.len(); let n_expected_args = code.arg_count as usize; let total_args = code.arg_count as usize + code.kwonlyarg_count as usize; @@ -392,14 +392,15 @@ impl Py<PyFunction> { Err(err) => info!( "jit: function `{}` is falling back to being interpreted because of the \ error: {}", - self.code.obj_name, err + self.code.lock().obj_name, + err ), } } - let code = &self.code; + let code = self.code.lock().clone(); - let locals = if self.code.flags.contains(bytecode::CodeFlags::NEW_LOCALS) { + let locals = if code.flags.contains(bytecode::CodeFlags::NEW_LOCALS) { ArgMapping::from_dict_exact(vm.ctx.new_dict()) } else if let Some(locals) = locals { locals @@ -451,7 +452,18 @@ impl PyPayload for PyFunction { impl PyFunction { #[pygetset] fn __code__(&self) -> PyRef<PyCode> { - self.code.clone() + self.code.lock().clone() + } + + #[pygetset(setter)] + fn set___code__(&self, code: PyRef<PyCode>) { + *self.code.lock() = code; + // TODO: jit support + // #[cfg(feature = "jit")] + // { + // // If available, clear cached compiled code. + // let _ = self.jitted_code.take(); + // } } #[pygetset] @@ -595,7 +607,8 @@ impl PyFunction { .get_or_try_init(|| { let arg_types = jit::get_jit_arg_types(&zelf, vm)?; let ret_type = jit::jit_ret_type(&zelf, vm)?; - rustpython_jit::compile(&zelf.code.code, &arg_types, ret_type) + let code = zelf.code.lock(); + rustpython_jit::compile(&code.code, &arg_types, ret_type) .map_err(|err| jit::new_jit_error(err.to_string(), vm)) }) .map(drop) diff --git a/vm/src/builtins/function/jit.rs b/vm/src/builtins/function/jit.rs index c528c9bb31e..21d8c9c0abf 100644 --- a/vm/src/builtins/function/jit.rs +++ b/vm/src/builtins/function/jit.rs @@ -65,10 +65,10 @@ fn get_jit_arg_type(dict: &PyDictRef, name: &str, vm: &VirtualMachine) -> PyResu } pub fn get_jit_arg_types(func: &Py<PyFunction>, vm: &VirtualMachine) -> PyResult<Vec<JitType>> { - let arg_names = func.code.arg_names(); + let code = func.code.lock(); + let arg_names = code.arg_names(); - if func - .code + if code .flags .intersects(CodeFlags::HAS_VARARGS | CodeFlags::HAS_VARKEYWORDS) { @@ -157,9 +157,13 @@ pub(crate) fn get_jit_args<'a>( ) -> Result<Args<'a>, ArgsError> { let mut jit_args = jitted_code.args_builder(); let nargs = func_args.args.len(); - let arg_names = func.code.arg_names(); - if nargs > func.code.arg_count as usize || nargs < func.code.posonlyarg_count as usize { + let code = func.code.lock(); + let arg_names = code.arg_names(); + let arg_count = code.arg_count; + let posonlyarg_count = code.posonlyarg_count; + + if nargs > arg_count as usize || nargs < posonlyarg_count as usize { return Err(ArgsError::WrongNumberOfArgs); } @@ -178,7 +182,7 @@ pub(crate) fn get_jit_args<'a>( } jit_args.set(arg_idx, get_jit_value(vm, value)?)?; } else if let Some(kwarg_idx) = arg_pos(arg_names.kwonlyargs, name) { - let arg_idx = kwarg_idx + func.code.arg_count as usize; + let arg_idx = kwarg_idx + arg_count as usize; if jit_args.is_set(arg_idx) { return Err(ArgsError::ArgPassedMultipleTimes); } @@ -193,7 +197,7 @@ pub(crate) fn get_jit_args<'a>( // fill in positional defaults if let Some(defaults) = defaults { for (i, default) in defaults.iter().enumerate() { - let arg_idx = i + func.code.arg_count as usize - defaults.len(); + let arg_idx = i + arg_count as usize - defaults.len(); if !jit_args.is_set(arg_idx) { jit_args.set(arg_idx, get_jit_value(vm, default)?)?; } @@ -203,7 +207,7 @@ pub(crate) fn get_jit_args<'a>( // fill in keyword only defaults if let Some(kw_only_defaults) = kwdefaults { for (i, name) in arg_names.kwonlyargs.iter().enumerate() { - let arg_idx = i + func.code.arg_count as usize; + let arg_idx = i + arg_count as usize; if !jit_args.is_set(arg_idx) { let default = kw_only_defaults .get_item(&**name, vm) @@ -214,5 +218,7 @@ pub(crate) fn get_jit_args<'a>( } } + drop(code); + jit_args.into_args().ok_or(ArgsError::NotAllArgsPassed) } From 153d0eef51ea109d8a322fd351678847b2fb8fe2 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:15:16 +0300 Subject: [PATCH 307/819] Revert "Use ruff for `Expr` unparsing (#6124)" This reverts commit 0fb7d0fae2a0701edec27a2bdb52a3527289c425. --- Cargo.lock | 25 --- Cargo.toml | 1 - ...syntax_future.py => badsyntax_future10.py} | 0 .../test_future_stmt/badsyntax_future3.py | 10 + .../test_future_stmt/badsyntax_future4.py | 10 + .../test_future_stmt/badsyntax_future5.py | 12 + .../test_future_stmt/badsyntax_future6.py | 10 + .../test_future_stmt/badsyntax_future7.py | 11 + .../test_future_stmt/badsyntax_future8.py | 10 + .../test_future_stmt/badsyntax_future9.py | 10 + ..._nested_scope_twice.py => future_test1.py} | 0 .../{nested_scope.py => future_test2.py} | 0 Lib/test/test_future_stmt/test_future.py | 206 ++++++------------ compiler/codegen/Cargo.toml | 3 - compiler/codegen/src/compile.rs | 17 +- compiler/codegen/src/lib.rs | 1 + 16 files changed, 147 insertions(+), 179 deletions(-) rename Lib/test/test_future_stmt/{badsyntax_future.py => badsyntax_future10.py} (100%) create mode 100644 Lib/test/test_future_stmt/badsyntax_future3.py create mode 100644 Lib/test/test_future_stmt/badsyntax_future4.py create mode 100644 Lib/test/test_future_stmt/badsyntax_future5.py create mode 100644 Lib/test/test_future_stmt/badsyntax_future6.py create mode 100644 Lib/test/test_future_stmt/badsyntax_future7.py create mode 100644 Lib/test/test_future_stmt/badsyntax_future8.py create mode 100644 Lib/test/test_future_stmt/badsyntax_future9.py rename Lib/test/test_future_stmt/{import_nested_scope_twice.py => future_test1.py} (100%) rename Lib/test/test_future_stmt/{nested_scope.py => future_test2.py} (100%) diff --git a/Cargo.lock b/Cargo.lock index cc0f080fd10..53b4f969223 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2282,29 +2282,6 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "ruff_python_codegen" -version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" -dependencies = [ - "ruff_python_ast", - "ruff_python_literal", - "ruff_python_parser", - "ruff_source_file", - "ruff_text_size", -] - -[[package]] -name = "ruff_python_literal" -version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" -dependencies = [ - "bitflags 2.9.4", - "itertools 0.14.0", - "ruff_python_ast", - "unic-ucd-category", -] - [[package]] name = "ruff_python_parser" version = "0.0.0" @@ -2410,9 +2387,7 @@ dependencies = [ "num-complex", "num-traits", "ruff_python_ast", - "ruff_python_codegen", "ruff_python_parser", - "ruff_source_file", "ruff_text_size", "rustpython-compiler-core", "rustpython-literal", diff --git a/Cargo.toml b/Cargo.toml index 77bba61a3b0..3cdc471dc3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -159,7 +159,6 @@ rustpython-wtf8 = { path = "wtf8", version = "0.4.0" } rustpython-doc = { git = "https://github.com/RustPython/__doc__", tag = "0.3.0", version = "0.3.0" } ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } -ruff_python_codegen = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } diff --git a/Lib/test/test_future_stmt/badsyntax_future.py b/Lib/test/test_future_stmt/badsyntax_future10.py similarity index 100% rename from Lib/test/test_future_stmt/badsyntax_future.py rename to Lib/test/test_future_stmt/badsyntax_future10.py diff --git a/Lib/test/test_future_stmt/badsyntax_future3.py b/Lib/test/test_future_stmt/badsyntax_future3.py new file mode 100644 index 00000000000..f1c8417edaa --- /dev/null +++ b/Lib/test/test_future_stmt/badsyntax_future3.py @@ -0,0 +1,10 @@ +"""This is a test""" +from __future__ import nested_scopes +from __future__ import rested_snopes + +def f(x): + def g(y): + return x + y + return g + +result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future4.py b/Lib/test/test_future_stmt/badsyntax_future4.py new file mode 100644 index 00000000000..b5f4c98e922 --- /dev/null +++ b/Lib/test/test_future_stmt/badsyntax_future4.py @@ -0,0 +1,10 @@ +"""This is a test""" +import __future__ +from __future__ import nested_scopes + +def f(x): + def g(y): + return x + y + return g + +result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future5.py b/Lib/test/test_future_stmt/badsyntax_future5.py new file mode 100644 index 00000000000..8a7e5fcb70f --- /dev/null +++ b/Lib/test/test_future_stmt/badsyntax_future5.py @@ -0,0 +1,12 @@ +"""This is a test""" +from __future__ import nested_scopes +import foo +from __future__ import nested_scopes + + +def f(x): + def g(y): + return x + y + return g + +result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future6.py b/Lib/test/test_future_stmt/badsyntax_future6.py new file mode 100644 index 00000000000..5a8b55a02c4 --- /dev/null +++ b/Lib/test/test_future_stmt/badsyntax_future6.py @@ -0,0 +1,10 @@ +"""This is a test""" +"this isn't a doc string" +from __future__ import nested_scopes + +def f(x): + def g(y): + return x + y + return g + +result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future7.py b/Lib/test/test_future_stmt/badsyntax_future7.py new file mode 100644 index 00000000000..131db2c2164 --- /dev/null +++ b/Lib/test/test_future_stmt/badsyntax_future7.py @@ -0,0 +1,11 @@ +"""This is a test""" + +from __future__ import nested_scopes; import string; from __future__ import \ + nested_scopes + +def f(x): + def g(y): + return x + y + return g + +result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future8.py b/Lib/test/test_future_stmt/badsyntax_future8.py new file mode 100644 index 00000000000..ca45289e2e5 --- /dev/null +++ b/Lib/test/test_future_stmt/badsyntax_future8.py @@ -0,0 +1,10 @@ +"""This is a test""" + +from __future__ import * + +def f(x): + def g(y): + return x + y + return g + +print(f(2)(4)) diff --git a/Lib/test/test_future_stmt/badsyntax_future9.py b/Lib/test/test_future_stmt/badsyntax_future9.py new file mode 100644 index 00000000000..916de06ab71 --- /dev/null +++ b/Lib/test/test_future_stmt/badsyntax_future9.py @@ -0,0 +1,10 @@ +"""This is a test""" + +from __future__ import nested_scopes, braces + +def f(x): + def g(y): + return x + y + return g + +print(f(2)(4)) diff --git a/Lib/test/test_future_stmt/import_nested_scope_twice.py b/Lib/test/test_future_stmt/future_test1.py similarity index 100% rename from Lib/test/test_future_stmt/import_nested_scope_twice.py rename to Lib/test/test_future_stmt/future_test1.py diff --git a/Lib/test/test_future_stmt/nested_scope.py b/Lib/test/test_future_stmt/future_test2.py similarity index 100% rename from Lib/test/test_future_stmt/nested_scope.py rename to Lib/test/test_future_stmt/future_test2.py diff --git a/Lib/test/test_future_stmt/test_future.py b/Lib/test/test_future_stmt/test_future.py index e6e6201a76b..9c30054963b 100644 --- a/Lib/test/test_future_stmt/test_future.py +++ b/Lib/test/test_future_stmt/test_future.py @@ -10,8 +10,6 @@ import re import sys -TOP_LEVEL_MSG = 'from __future__ imports must occur at the beginning of the file' - rx = re.compile(r'\((\S+).py, line (\d+)') def get_error_location(msg): @@ -20,48 +18,21 @@ def get_error_location(msg): class FutureTest(unittest.TestCase): - def check_syntax_error(self, err, basename, - *, - lineno, - message=TOP_LEVEL_MSG, offset=1): - if basename != '<string>': - basename += '.py' - - self.assertEqual(f'{message} ({basename}, line {lineno})', str(err)) - self.assertEqual(os.path.basename(err.filename), basename) + def check_syntax_error(self, err, basename, lineno, offset=1): + self.assertIn('%s.py, line %d' % (basename, lineno), str(err)) + self.assertEqual(os.path.basename(err.filename), basename + '.py') self.assertEqual(err.lineno, lineno) self.assertEqual(err.offset, offset) - def assertSyntaxError(self, code, - *, - lineno=1, - message=TOP_LEVEL_MSG, offset=1, - parametrize_docstring=True): - code = dedent(code.lstrip('\n')) - for add_docstring in ([False, True] if parametrize_docstring else [False]): - with self.subTest(code=code, add_docstring=add_docstring): - if add_docstring: - code = '"""Docstring"""\n' + code - lineno += 1 - with self.assertRaises(SyntaxError) as cm: - exec(code) - self.check_syntax_error(cm.exception, "<string>", - lineno=lineno, - message=message, - offset=offset) - - def test_import_nested_scope_twice(self): - # Import the name nested_scopes twice to trigger SF bug #407394 - with import_helper.CleanImport( - 'test.test_future_stmt.import_nested_scope_twice', - ): - from test.test_future_stmt import import_nested_scope_twice - self.assertEqual(import_nested_scope_twice.result, 6) + def test_future1(self): + with import_helper.CleanImport('test.test_future_stmt.future_test1'): + from test.test_future_stmt import future_test1 + self.assertEqual(future_test1.result, 6) - def test_nested_scope(self): - with import_helper.CleanImport('test.test_future_stmt.nested_scope'): - from test.test_future_stmt import nested_scope - self.assertEqual(nested_scope.result, 6) + def test_future2(self): + with import_helper.CleanImport('test.test_future_stmt.future_test2'): + from test.test_future_stmt import future_test2 + self.assertEqual(future_test2.result, 6) def test_future_single_import(self): with import_helper.CleanImport( @@ -81,87 +52,47 @@ def test_future_multiple_features(self): ): from test.test_future_stmt import test_future_multiple_features - @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message - def test_unknown_future_flag(self): - code = """ - from __future__ import nested_scopes - from __future__ import rested_snopes # typo error here: nested => rested - """ - self.assertSyntaxError( - code, lineno=2, - message='future feature rested_snopes is not defined', offset=24, - ) - - @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message - def test_future_import_not_on_top(self): - code = """ - import some_module - from __future__ import annotations - """ - self.assertSyntaxError(code, lineno=2) + def test_badfuture3(self): + with self.assertRaises(SyntaxError) as cm: + from test.test_future_stmt import badsyntax_future3 + self.check_syntax_error(cm.exception, "badsyntax_future3", 3) - code = """ - import __future__ - from __future__ import annotations - """ - self.assertSyntaxError(code, lineno=2) + def test_badfuture4(self): + with self.assertRaises(SyntaxError) as cm: + from test.test_future_stmt import badsyntax_future4 + self.check_syntax_error(cm.exception, "badsyntax_future4", 3) - code = """ - from __future__ import absolute_import - "spam, bar, blah" - from __future__ import print_function - """ - self.assertSyntaxError(code, lineno=3) - - @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message - def test_future_import_with_extra_string(self): - code = """ - '''Docstring''' - "this isn't a doc string" - from __future__ import nested_scopes - """ - self.assertSyntaxError(code, lineno=3, parametrize_docstring=False) - - @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message - def test_multiple_import_statements_on_same_line(self): - # With `\`: - code = """ - from __future__ import nested_scopes; import string; from __future__ import \ - nested_scopes - """ - self.assertSyntaxError(code, offset=54) + def test_badfuture5(self): + with self.assertRaises(SyntaxError) as cm: + from test.test_future_stmt import badsyntax_future5 + self.check_syntax_error(cm.exception, "badsyntax_future5", 4) - # Without `\`: - code = """ - from __future__ import nested_scopes; import string; from __future__ import nested_scopes - """ - self.assertSyntaxError(code, offset=54) + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_badfuture6(self): + with self.assertRaises(SyntaxError) as cm: + from test.test_future_stmt import badsyntax_future6 + self.check_syntax_error(cm.exception, "badsyntax_future6", 3) - @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message - def test_future_import_star(self): - code = """ - from __future__ import * - """ - self.assertSyntaxError(code, message='future feature * is not defined', offset=24) + def test_badfuture7(self): + with self.assertRaises(SyntaxError) as cm: + from test.test_future_stmt import badsyntax_future7 + self.check_syntax_error(cm.exception, "badsyntax_future7", 3, 54) - @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message - def test_future_import_braces(self): - code = """ - from __future__ import braces - """ - # Congrats, you found an easter egg! - self.assertSyntaxError(code, message='not a chance', offset=24) + def test_badfuture8(self): + with self.assertRaises(SyntaxError) as cm: + from test.test_future_stmt import badsyntax_future8 + self.check_syntax_error(cm.exception, "badsyntax_future8", 3) - code = """ - from __future__ import nested_scopes, braces - """ - self.assertSyntaxError(code, message='not a chance', offset=39) + def test_badfuture9(self): + with self.assertRaises(SyntaxError) as cm: + from test.test_future_stmt import badsyntax_future9 + self.check_syntax_error(cm.exception, "badsyntax_future9", 3) - @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message - def test_module_with_future_import_not_on_top(self): + def test_badfuture10(self): with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future - self.check_syntax_error(cm.exception, "badsyntax_future", lineno=3) + from test.test_future_stmt import badsyntax_future10 + self.check_syntax_error(cm.exception, "badsyntax_future10", 3) def test_ensure_flags_dont_clash(self): # bpo-39562: test that future flags and compiler flags doesn't clash @@ -178,6 +109,26 @@ def test_ensure_flags_dont_clash(self): } self.assertCountEqual(set(flags.values()), flags.values()) + def test_parserhack(self): + # test that the parser.c::future_hack function works as expected + # Note: although this test must pass, it's not testing the original + # bug as of 2.6 since the with statement is not optional and + # the parser hack disabled. If a new keyword is introduced in + # 2.6, change this to refer to the new future import. + try: + exec("from __future__ import print_function; print 0") + except SyntaxError: + pass + else: + self.fail("syntax error didn't occur") + + try: + exec("from __future__ import (print_function); print 0") + except SyntaxError: + pass + else: + self.fail("syntax error didn't occur") + def test_unicode_literals_exec(self): scope = {} exec("from __future__ import unicode_literals; x = ''", {}, scope) @@ -190,26 +141,6 @@ def test_syntactical_future_repl(self): out = kill_python(p) self.assertNotIn(b'SyntaxError: invalid syntax', out) - @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: future feature spam is not defined - def test_future_dotted_import(self): - with self.assertRaises(ImportError): - exec("from .__future__ import spam") - - code = dedent( - """ - from __future__ import print_function - from ...__future__ import ham - """ - ) - with self.assertRaises(ImportError): - exec(code) - - code = """ - from .__future__ import nested_scopes - from __future__ import barry_as_FLUFL - """ - self.assertSyntaxError(code, lineno=2) - class AnnotationsFutureTestCase(unittest.TestCase): template = dedent( """ @@ -267,7 +198,6 @@ def _exec_future(self, code): ) return scope - @unittest.expectedFailure # TODO: RUSTPYTHON; 'a,' != '(a,)' def test_annotations(self): eq = self.assertAnnotationEqual eq('...') @@ -432,7 +362,6 @@ def test_annotations(self): eq('(((a, b)))', '(a, b)') eq("1 + 2 + 3") - @unittest.expectedFailure # TODO: RUSTPYTHON; "f'{x=!r}'" != "f'x={x!r}'" def test_fstring_debug_annotations(self): # f-strings with '=' don't round trip very well, so set the expected # result explicitly. @@ -443,7 +372,6 @@ def test_fstring_debug_annotations(self): self.assertAnnotationEqual("f'{x=!a}'", expected="f'x={x!a}'") self.assertAnnotationEqual("f'{x=!s:*^20}'", expected="f'x={x!s:*^20}'") - @unittest.expectedFailure # TODO: RUSTPYTHON; '1e309, 1e309j' != '(1e309, 1e309j)' def test_infinity_numbers(self): inf = "1e" + repr(sys.float_info.max_10_exp + 1) infj = f"{inf}j" @@ -456,7 +384,8 @@ def test_infinity_numbers(self): self.assertAnnotationEqual("('inf', 1e1000, 'infxxx', 1e1000j)", expected=f"('inf', {inf}, 'infxxx', {infj})") self.assertAnnotationEqual("(1e1000, (1e1000j,))", expected=f"({inf}, ({infj},))") - @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_annotation_with_complex_target(self): with self.assertRaises(SyntaxError): exec( @@ -480,7 +409,8 @@ def bar(): self.assertEqual(foo.__code__.co_cellvars, ()) self.assertEqual(foo().__code__.co_freevars, ()) - @unittest.expectedFailure # TODO: RUSTPYTHON; "f'{x=!r}'" != "f'x={x!r}'" + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_annotations_forbidden(self): with self.assertRaises(SyntaxError): self._exec_future("test: (yield)") diff --git a/compiler/codegen/Cargo.toml b/compiler/codegen/Cargo.toml index e2bfec6c3c3..ce7e8d74f59 100644 --- a/compiler/codegen/Cargo.toml +++ b/compiler/codegen/Cargo.toml @@ -13,9 +13,6 @@ rustpython-compiler-core = { workspace = true } rustpython-literal = {workspace = true } rustpython-wtf8 = { workspace = true } ruff_python_ast = { workspace = true } -ruff_python_parser = { workspace = true } -ruff_python_codegen = { workspace = true } -ruff_source_file = { workspace = true } ruff_text_size = { workspace = true } ahash = { workspace = true } diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 132998fe838..5f8caebf272 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -14,6 +14,7 @@ use crate::{ error::{CodegenError, CodegenErrorType, InternalError, PatternUnreachableReason}, ir::{self, BlockIdx}, symboltable::{self, CompilerScope, SymbolFlags, SymbolScope, SymbolTable}, + unparse::UnparseExpr, }; use itertools::Itertools; use malachite_bigint::BigInt; @@ -30,7 +31,6 @@ use ruff_python_ast::{ PatternMatchStar, PatternMatchValue, Singleton, Stmt, StmtExpr, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem, }; -use ruff_source_file::LineEnding; use ruff_text_size::{Ranged, TextRange}; use rustpython_compiler_core::{ Mode, OneIndexed, PositionEncoding, SourceFile, SourceLocation, @@ -147,15 +147,6 @@ enum ComprehensionType { Dict, } -fn unparse_expr(expr: &Expr) -> String { - use ruff_python_ast::str::Quote; - use ruff_python_codegen::{Generator, Indentation}; - - Generator::new(&Indentation::default(), LineEnding::default()) - .with_preferred_quote(Some(Quote::Single)) - .expr(expr) -} - fn validate_duplicate_params(params: &Parameters) -> Result<(), CodegenErrorType> { let mut seen_params = HashSet::new(); for param in params { @@ -3618,7 +3609,7 @@ impl Compiler { | Expr::NoneLiteral(_) ); let key_repr = if is_literal { - unparse_expr(key) + UnparseExpr::new(key, &self.source_file).to_string() } else if is_attribute { String::new() } else { @@ -4172,7 +4163,9 @@ impl Compiler { fn compile_annotation(&mut self, annotation: &Expr) -> CompileResult<()> { if self.future_annotations { self.emit_load_const(ConstantData::Str { - value: unparse_expr(annotation).into(), + value: UnparseExpr::new(annotation, &self.source_file) + .to_string() + .into(), }); } else { let was_in_annotation = self.in_annotation; diff --git a/compiler/codegen/src/lib.rs b/compiler/codegen/src/lib.rs index e7944079988..291b57d7f67 100644 --- a/compiler/codegen/src/lib.rs +++ b/compiler/codegen/src/lib.rs @@ -13,6 +13,7 @@ pub mod error; pub mod ir; mod string_parser; pub mod symboltable; +mod unparse; pub use compile::CompileOpts; use ruff_python_ast::Expr; From 2463bdff0e266b781d9c63c39146f25e1ba2ad88 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:28:53 +0900 Subject: [PATCH 308/819] Fix time.strptime (#6208) --- Lib/test/test_time.py | 2 -- Lib/test/test_zipfile.py | 6 ------ vm/src/stdlib/time.rs | 19 ++++++++++--------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 3886aae9341..31a2a920d9e 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -284,8 +284,6 @@ def test_strptime_bytes(self): self.assertRaises(TypeError, time.strptime, b'2009', "%Y") self.assertRaises(TypeError, time.strptime, '2009', b'%Y') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_strptime_exception_context(self): # check that this doesn't chain exceptions needlessly (see #17572) with self.assertRaises(ValueError) as e: diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py index 43178ca26b0..0a50f036046 100644 --- a/Lib/test/test_zipfile.py +++ b/Lib/test/test_zipfile.py @@ -123,8 +123,6 @@ def zip_test(self, f, compression, compresslevel=None): # Check that testzip doesn't raise an exception zipfp.testzip() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basic(self): for f in get_files(self): self.zip_test(f, self.compression) @@ -394,8 +392,6 @@ def test_repr(self): self.assertIn('[closed]', repr(zipopen)) self.assertIn('[closed]', repr(zipfp)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_compresslevel_basic(self): for f in get_files(self): self.zip_test(f, self.compression, compresslevel=9) @@ -751,8 +747,6 @@ def zip_test(self, f, compression): # Check that testzip doesn't raise an exception zipfp.testzip() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basic(self): for f in get_files(self): self.zip_test(f, self.compression) diff --git a/vm/src/stdlib/time.rs b/vm/src/stdlib/time.rs index 4ad29d0fe17..fb64e28b906 100644 --- a/vm/src/stdlib/time.rs +++ b/vm/src/stdlib/time.rs @@ -364,15 +364,16 @@ mod decl { } #[pyfunction] - fn strptime( - string: PyStrRef, - format: OptionalArg<PyStrRef>, - vm: &VirtualMachine, - ) -> PyResult<PyStructTime> { - let format = format.as_ref().map_or("%a %b %H:%M:%S %Y", |s| s.as_str()); - let instant = NaiveDateTime::parse_from_str(string.as_str(), format) - .map_err(|e| vm.new_value_error(format!("Parse error: {e:?}")))?; - Ok(PyStructTime::new(vm, instant, -1)) + fn strptime(string: PyStrRef, format: OptionalArg<PyStrRef>, vm: &VirtualMachine) -> PyResult { + // Call _strptime._strptime_time like CPython does + let strptime_module = vm.import("_strptime", 0)?; + let strptime_func = strptime_module.get_attr("_strptime_time", vm)?; + + // Call with positional arguments + match format.into_option() { + Some(fmt) => strptime_func.call((string, fmt), vm), + None => strptime_func.call((string,), vm), + } } #[cfg(not(any( From 3ec905e08aa77301873d9bf27cb2333eb935986f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 23 Oct 2025 18:37:40 +0900 Subject: [PATCH 309/819] ssl.{SSLSession,MemoryBIO} (#6209) * SSLSession * get_unverified_chain * SSL MemoryBIO --- Lib/ssl.py | 4 +- stdlib/src/ssl.rs | 404 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 398 insertions(+), 10 deletions(-) diff --git a/Lib/ssl.py b/Lib/ssl.py index 1200d7d9939..751d79fb5e3 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -98,7 +98,7 @@ import _ssl # if we can't import it, let the error propagate from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION -from _ssl import _SSLContext#, MemoryBIO, SSLSession +from _ssl import _SSLContext, SSLSession, MemoryBIO from _ssl import ( SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError, SSLSyscallError, SSLEOFError, SSLCertVerificationError @@ -114,7 +114,7 @@ from _ssl import ( HAS_SNI, HAS_ECDH, HAS_NPN, HAS_ALPN, HAS_SSLv2, HAS_SSLv3, HAS_TLSv1, - HAS_TLSv1_1, HAS_TLSv1_2, HAS_TLSv1_3 + HAS_TLSv1_1, HAS_TLSv1_2, HAS_TLSv1_3, HAS_PSK ) from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION diff --git a/stdlib/src/ssl.rs b/stdlib/src/ssl.rs index 0e9de9c0dca..c9a9e15f8e3 100644 --- a/stdlib/src/ssl.rs +++ b/stdlib/src/ssl.rs @@ -38,15 +38,16 @@ mod _ssl { }, socket::{self, PySocket}, vm::{ - PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyBaseExceptionRef, PyStrRef, PyType, PyTypeRef, PyWeak}, + class_or_notimplemented, convert::{ToPyException, ToPyObject}, exceptions, function::{ ArgBytesLike, ArgCallable, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath, - OptionalArg, + OptionalArg, PyComparisonValue, }, - types::Constructor, + types::{Comparable, Constructor, PyComparisonOp}, utils::ToCString, }, }; @@ -162,6 +163,8 @@ mod _ssl { const HAS_TLSv1_2: bool = true; #[pyattr] const HAS_TLSv1_3: bool = cfg!(ossl111); + #[pyattr] + const HAS_PSK: bool = true; // the openssl version from the API headers @@ -816,19 +819,48 @@ mod _ssl { let stream = ssl::SslStream::new(ssl, SocketStream(args.sock.clone())) .map_err(|e| convert_openssl_error(vm, e))?; - // TODO: use this - let _ = args.session; - - Ok(PySslSocket { + let py_ssl_socket = PySslSocket { ctx: zelf, stream: PyRwLock::new(stream), socket_type, server_hostname: args.server_hostname, owner: PyRwLock::new(args.owner.map(|o| o.downgrade(None, vm)).transpose()?), - }) + }; + + // Set session if provided + if let Some(session) = args.session + && !vm.is_none(&session) + { + py_ssl_socket.set_session(session, vm)?; + } + + Ok(py_ssl_socket) + } + + #[pymethod] + fn _wrap_bio(_zelf: PyRef<Self>, _args: WrapBioArgs, vm: &VirtualMachine) -> PyResult { + // TODO: Implement BIO-based SSL wrapping + // This requires refactoring PySslSocket to support both socket and BIO modes + Err(vm.new_not_implemented_error( + "_wrap_bio is not yet implemented in RustPython".to_owned(), + )) } } + #[derive(FromArgs)] + #[allow(dead_code)] // Fields will be used when _wrap_bio is fully implemented + struct WrapBioArgs { + incoming: PyRef<PySslMemoryBio>, + outgoing: PyRef<PySslMemoryBio>, + server_side: bool, + #[pyarg(any, default)] + server_hostname: Option<PyStrRef>, + #[pyarg(named, default)] + owner: Option<PyObjectRef>, + #[pyarg(named, default)] + session: Option<PyObjectRef>, + } + #[derive(FromArgs)] struct WrapSocketArgs { sock: PyRef<PySocket>, @@ -996,6 +1028,19 @@ mod _ssl { .transpose() } + #[pymethod] + fn get_unverified_chain(&self, vm: &VirtualMachine) -> Option<PyObjectRef> { + let stream = self.stream.read(); + let chain = stream.ssl().peer_cert_chain()?; + + let certs: Vec<PyObjectRef> = chain + .iter() + .filter_map(|cert| cert.to_der().ok().map(|der| vm.ctx.new_bytes(der).into())) + .collect(); + + Some(vm.ctx.new_list(certs).into()) + } + #[pymethod] fn version(&self) -> Option<&'static str> { let v = self.stream.read().ssl().version_str(); @@ -1103,6 +1148,73 @@ mod _ssl { } } + #[pygetset] + fn session(&self, _vm: &VirtualMachine) -> PyResult<Option<PySslSession>> { + let stream = self.stream.read(); + unsafe { + let session_ptr = sys::SSL_get_session(stream.ssl().as_ptr()); + if session_ptr.is_null() { + Ok(None) + } else { + // Increment reference count since SSL_get_session returns a borrowed reference + #[cfg(ossl110)] + let _session = sys::SSL_SESSION_up_ref(session_ptr); + + Ok(Some(PySslSession { + session: session_ptr, + ctx: self.ctx.clone(), + })) + } + } + } + + #[pygetset(setter)] + fn set_session(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // Check if value is SSLSession type + let session = value + .downcast_ref::<PySslSession>() + .ok_or_else(|| vm.new_type_error("Value is not a SSLSession.".to_owned()))?; + + // Check if session refers to the same SSLContext + if !std::ptr::eq( + self.ctx.ctx.read().as_ptr(), + session.ctx.ctx.read().as_ptr(), + ) { + return Err( + vm.new_value_error("Session refers to a different SSLContext.".to_owned()) + ); + } + + // Check if this is a client socket + if self.socket_type != SslServerOrClient::Client { + return Err( + vm.new_value_error("Cannot set session for server-side SSLSocket.".to_owned()) + ); + } + + // Check if handshake is not finished + let stream = self.stream.read(); + unsafe { + if sys::SSL_is_init_finished(stream.ssl().as_ptr()) != 0 { + return Err( + vm.new_value_error("Cannot set session after handshake.".to_owned()) + ); + } + + if sys::SSL_set_session(stream.ssl().as_ptr(), session.session) == 0 { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + } + + Ok(()) + } + + #[pygetset] + fn session_reused(&self) -> bool { + let stream = self.stream.read(); + unsafe { sys::SSL_session_reused(stream.ssl().as_ptr()) != 0 } + } + #[pymethod] fn read( &self, @@ -1164,6 +1276,282 @@ mod _ssl { } } + #[pyattr] + #[pyclass(module = "ssl", name = "SSLSession")] + #[derive(PyPayload)] + struct PySslSession { + session: *mut sys::SSL_SESSION, + ctx: PyRef<PySslContext>, + } + + impl fmt::Debug for PySslSession { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("SSLSession") + } + } + + impl Drop for PySslSession { + fn drop(&mut self) { + if !self.session.is_null() { + unsafe { + sys::SSL_SESSION_free(self.session); + } + } + } + } + + unsafe impl Send for PySslSession {} + unsafe impl Sync for PySslSession {} + + impl Comparable for PySslSession { + fn cmp( + zelf: &Py<Self>, + other: &crate::vm::PyObject, + op: PyComparisonOp, + _vm: &VirtualMachine, + ) -> PyResult<PyComparisonValue> { + let other = class_or_notimplemented!(Self, other); + + if !matches!(op, PyComparisonOp::Eq | PyComparisonOp::Ne) { + return Ok(PyComparisonValue::NotImplemented); + } + let mut eq = unsafe { + let mut self_len: libc::c_uint = 0; + let mut other_len: libc::c_uint = 0; + let self_id = sys::SSL_SESSION_get_id(zelf.session, &mut self_len); + let other_id = sys::SSL_SESSION_get_id(other.session, &mut other_len); + + if self_len != other_len { + false + } else { + let self_slice = std::slice::from_raw_parts(self_id, self_len as usize); + let other_slice = std::slice::from_raw_parts(other_id, other_len as usize); + self_slice == other_slice + } + }; + if matches!(op, PyComparisonOp::Ne) { + eq = !eq; + } + Ok(PyComparisonValue::Implemented(eq)) + } + } + + #[pyattr] + #[pyclass(module = "ssl", name = "MemoryBIO")] + #[derive(PyPayload)] + struct PySslMemoryBio { + bio: *mut sys::BIO, + eof_written: AtomicCell<bool>, + } + + impl fmt::Debug for PySslMemoryBio { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("MemoryBIO") + } + } + + impl Drop for PySslMemoryBio { + fn drop(&mut self) { + if !self.bio.is_null() { + unsafe { + sys::BIO_free_all(self.bio); + } + } + } + } + + unsafe impl Send for PySslMemoryBio {} + unsafe impl Sync for PySslMemoryBio {} + + // OpenSSL BIO helper functions + // These are typically macros in OpenSSL, implemented via BIO_ctrl + const BIO_CTRL_PENDING: libc::c_int = 10; + const BIO_CTRL_SET_EOF: libc::c_int = 2; + + #[allow(non_snake_case)] + unsafe fn BIO_ctrl_pending(bio: *mut sys::BIO) -> usize { + unsafe { sys::BIO_ctrl(bio, BIO_CTRL_PENDING, 0, std::ptr::null_mut()) as usize } + } + + #[allow(non_snake_case)] + unsafe fn BIO_set_mem_eof_return(bio: *mut sys::BIO, eof: libc::c_int) -> libc::c_int { + unsafe { + sys::BIO_ctrl( + bio, + BIO_CTRL_SET_EOF, + eof as libc::c_long, + std::ptr::null_mut(), + ) as libc::c_int + } + } + + #[allow(non_snake_case)] + unsafe fn BIO_clear_retry_flags(bio: *mut sys::BIO) { + unsafe { + sys::BIO_clear_flags(bio, sys::BIO_FLAGS_RWS | sys::BIO_FLAGS_SHOULD_RETRY); + } + } + + impl Constructor for PySslMemoryBio { + type Args = (); + + fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + unsafe { + let bio = sys::BIO_new(sys::BIO_s_mem()); + if bio.is_null() { + return Err(vm.new_memory_error("failed to allocate BIO".to_owned())); + } + + sys::BIO_set_retry_read(bio); + BIO_set_mem_eof_return(bio, -1); + + PySslMemoryBio { + bio, + eof_written: AtomicCell::new(false), + } + .into_ref_with_type(vm, cls) + .map(Into::into) + } + } + } + + #[pyclass(with(Constructor))] + impl PySslMemoryBio { + #[pygetset] + fn pending(&self) -> usize { + unsafe { BIO_ctrl_pending(self.bio) } + } + + #[pygetset] + fn eof(&self) -> bool { + let pending = unsafe { BIO_ctrl_pending(self.bio) }; + pending == 0 && self.eof_written.load() + } + + #[pymethod] + fn read(&self, size: OptionalArg<i32>, vm: &VirtualMachine) -> PyResult<Vec<u8>> { + unsafe { + let avail = BIO_ctrl_pending(self.bio).min(i32::MAX as usize) as i32; + let len = size.unwrap_or(-1); + let len = if len < 0 || len > avail { avail } else { len }; + + if len == 0 { + return Ok(Vec::new()); + } + + let mut buf = vec![0u8; len as usize]; + let nbytes = sys::BIO_read(self.bio, buf.as_mut_ptr() as *mut _, len); + + if nbytes < 0 { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + + buf.truncate(nbytes as usize); + Ok(buf) + } + } + + #[pymethod] + fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<i32> { + if self.eof_written.load() { + return Err(vm.new_exception_msg( + ssl_error(vm), + "cannot write() after write_eof()".to_owned(), + )); + } + + data.with_ref(|buf| unsafe { + if buf.len() > i32::MAX as usize { + return Err( + vm.new_overflow_error(format!("string longer than {} bytes", i32::MAX)) + ); + } + + let nbytes = sys::BIO_write(self.bio, buf.as_ptr() as *const _, buf.len() as i32); + if nbytes < 0 { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + + Ok(nbytes) + }) + } + + #[pymethod] + fn write_eof(&self) { + self.eof_written.store(true); + unsafe { + BIO_clear_retry_flags(self.bio); + BIO_set_mem_eof_return(self.bio, 0); + } + } + } + + #[pyclass(with(Comparable))] + impl PySslSession { + #[pygetset] + fn time(&self) -> i64 { + unsafe { + #[cfg(ossl330)] + { + sys::SSL_SESSION_get_time(self.session) as i64 + } + #[cfg(not(ossl330))] + { + sys::SSL_SESSION_get_time(self.session) as i64 + } + } + } + + #[pygetset] + fn timeout(&self) -> i64 { + unsafe { sys::SSL_SESSION_get_timeout(self.session) as i64 } + } + + #[pygetset] + fn ticket_lifetime_hint(&self) -> u64 { + // SSL_SESSION_get_ticket_lifetime_hint may not be available in older OpenSSL + // Return 0 as default if not available + #[cfg(ossl110)] + { + // For now, return 0 as this function may not be in openssl-sys + let _ = self.session; + 0 + } + #[cfg(not(ossl110))] + { + let _ = self.session; + 0 + } + } + + #[pygetset] + fn id(&self, vm: &VirtualMachine) -> PyObjectRef { + unsafe { + let mut len: libc::c_uint = 0; + let id_ptr = sys::SSL_SESSION_get_id(self.session, &mut len); + let id_slice = std::slice::from_raw_parts(id_ptr, len as usize); + vm.ctx.new_bytes(id_slice.to_vec()).into() + } + } + + #[pygetset] + fn has_ticket(&self) -> bool { + // SSL_SESSION_has_ticket may not be available in older OpenSSL + // Return false as default + #[cfg(ossl110)] + { + // For now, return false as this function may not be in openssl-sys + let _ = self.session; + false + } + #[cfg(not(ossl110))] + { + let _ = self.session; + false + } + } + } + #[track_caller] fn convert_openssl_error(vm: &VirtualMachine, err: ErrorStack) -> PyBaseExceptionRef { let cls = ssl_error(vm); From 79dcba8fe7a146b8fff07c4106480d3a43c79958 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 19:05:37 +0900 Subject: [PATCH 310/819] Bump actions/setup-python from 5 to 6 (#6159) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cron-ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cron-ci.yaml b/.github/workflows/cron-ci.yaml index 868675ca1cc..239733ca306 100644 --- a/.github/workflows/cron-ci.yaml +++ b/.github/workflows/cron-ci.yaml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-llvm-cov - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} - run: sudo apt-get update && sudo apt-get -y install lcov @@ -82,7 +82,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} - name: build rustpython @@ -122,7 +122,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: 3.9 - run: cargo install cargo-criterion From 9825d8a3769373935166d35d0c816dd628591f2f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 23 Oct 2025 19:53:58 +0900 Subject: [PATCH 311/819] ensurepip from Python 3.13.9 (#5740) --- Lib/ensurepip/__init__.py | 131 +++++++----------- .../_bundled/pip-23.2.1-py3-none-any.whl | Bin 2086091 -> 0 bytes .../_bundled/pip-25.2-py3-none-any.whl | Bin 0 -> 1752557 bytes Lib/test/test_ensurepip.py | 46 +++--- 4 files changed, 73 insertions(+), 104 deletions(-) delete mode 100644 Lib/ensurepip/_bundled/pip-23.2.1-py3-none-any.whl create mode 100644 Lib/ensurepip/_bundled/pip-25.2-py3-none-any.whl diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py index 1fb1d505cfd..ab6d32478e4 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -1,78 +1,64 @@ -import collections import os -import os.path import subprocess import sys import sysconfig import tempfile +from contextlib import nullcontext from importlib import resources +from pathlib import Path +from shutil import copy2 __all__ = ["version", "bootstrap"] -_PACKAGE_NAMES = ('pip',) -_PIP_VERSION = "23.2.1" -_PROJECTS = [ - ("pip", _PIP_VERSION, "py3"), -] - -# Packages bundled in ensurepip._bundled have wheel_name set. -# Packages from WHEEL_PKG_DIR have wheel_path set. -_Package = collections.namedtuple('Package', - ('version', 'wheel_name', 'wheel_path')) +_PIP_VERSION = "25.2" # Directory of system wheel packages. Some Linux distribution packaging # policies recommend against bundling dependencies. For example, Fedora # installs wheel packages in the /usr/share/python-wheels/ directory and don't # install the ensurepip._bundled package. -_WHEEL_PKG_DIR = sysconfig.get_config_var('WHEEL_PKG_DIR') +if (_pkg_dir := sysconfig.get_config_var('WHEEL_PKG_DIR')) is not None: + _WHEEL_PKG_DIR = Path(_pkg_dir).resolve() +else: + _WHEEL_PKG_DIR = None + +def _find_wheel_pkg_dir_pip(): + if _WHEEL_PKG_DIR is None: + # NOTE: The compile-time `WHEEL_PKG_DIR` is unset so there is no place + # NOTE: for looking up the wheels. + return None -def _find_packages(path): - packages = {} + dist_matching_wheels = _WHEEL_PKG_DIR.glob('pip-*.whl') try: - filenames = os.listdir(path) - except OSError: - # Ignore: path doesn't exist or permission error - filenames = () - # Make the code deterministic if a directory contains multiple wheel files - # of the same package, but don't attempt to implement correct version - # comparison since this case should not happen. - filenames = sorted(filenames) - for filename in filenames: - # filename is like 'pip-21.2.4-py3-none-any.whl' - if not filename.endswith(".whl"): - continue - for name in _PACKAGE_NAMES: - prefix = name + '-' - if filename.startswith(prefix): - break - else: - continue - - # Extract '21.2.4' from 'pip-21.2.4-py3-none-any.whl' - version = filename.removeprefix(prefix).partition('-')[0] - wheel_path = os.path.join(path, filename) - packages[name] = _Package(version, None, wheel_path) - return packages - - -def _get_packages(): - global _PACKAGES, _WHEEL_PKG_DIR - if _PACKAGES is not None: - return _PACKAGES - - packages = {} - for name, version, py_tag in _PROJECTS: - wheel_name = f"{name}-{version}-{py_tag}-none-any.whl" - packages[name] = _Package(version, wheel_name, None) - if _WHEEL_PKG_DIR: - dir_packages = _find_packages(_WHEEL_PKG_DIR) - # only used the wheel package directory if all packages are found there - if all(name in dir_packages for name in _PACKAGE_NAMES): - packages = dir_packages - _PACKAGES = packages - return packages -_PACKAGES = None + last_matching_dist_wheel = sorted(dist_matching_wheels)[-1] + except IndexError: + # NOTE: `WHEEL_PKG_DIR` does not contain any wheel files for `pip`. + return None + + return nullcontext(last_matching_dist_wheel) + + +def _get_pip_whl_path_ctx(): + # Prefer pip from the wheel package directory, if present. + if (alternative_pip_wheel_path := _find_wheel_pkg_dir_pip()) is not None: + return alternative_pip_wheel_path + + return resources.as_file( + resources.files('ensurepip') + / '_bundled' + / f'pip-{_PIP_VERSION}-py3-none-any.whl' + ) + + +def _get_pip_version(): + with _get_pip_whl_path_ctx() as bundled_wheel_path: + wheel_name = bundled_wheel_path.name + return ( + # Extract '21.2.4' from 'pip-21.2.4-py3-none-any.whl' + wheel_name. + removeprefix('pip-'). + partition('-')[0] + ) def _run_pip(args, additional_paths=None): @@ -105,7 +91,7 @@ def version(): """ Returns a string specifying the bundled version of pip. """ - return _get_packages()['pip'].version + return _get_pip_version() def _disable_pip_configuration_settings(): @@ -167,24 +153,10 @@ def _bootstrap(*, root=None, upgrade=False, user=False, with tempfile.TemporaryDirectory() as tmpdir: # Put our bundled wheels into a temporary directory and construct the # additional paths that need added to sys.path - additional_paths = [] - for name, package in _get_packages().items(): - if package.wheel_name: - # Use bundled wheel package - wheel_name = package.wheel_name - wheel_path = resources.files("ensurepip") / "_bundled" / wheel_name - whl = wheel_path.read_bytes() - else: - # Use the wheel package directory - with open(package.wheel_path, "rb") as fp: - whl = fp.read() - wheel_name = os.path.basename(package.wheel_path) - - filename = os.path.join(tmpdir, wheel_name) - with open(filename, "wb") as fp: - fp.write(whl) - - additional_paths.append(filename) + tmpdir_path = Path(tmpdir) + with _get_pip_whl_path_ctx() as bundled_wheel_path: + tmp_wheel_path = tmpdir_path / bundled_wheel_path.name + copy2(bundled_wheel_path, tmp_wheel_path) # Construct the arguments to be passed to the pip command args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir] @@ -197,7 +169,8 @@ def _bootstrap(*, root=None, upgrade=False, user=False, if verbosity: args += ["-" + "v" * verbosity] - return _run_pip([*args, *_PACKAGE_NAMES], additional_paths) + return _run_pip([*args, "pip"], [os.fsdecode(tmp_wheel_path)]) + def _uninstall_helper(*, verbosity=0): """Helper to support a clean default uninstall process on Windows @@ -227,7 +200,7 @@ def _uninstall_helper(*, verbosity=0): if verbosity: args += ["-" + "v" * verbosity] - return _run_pip([*args, *reversed(_PACKAGE_NAMES)]) + return _run_pip([*args, "pip"]) def _main(argv=None): diff --git a/Lib/ensurepip/_bundled/pip-23.2.1-py3-none-any.whl b/Lib/ensurepip/_bundled/pip-23.2.1-py3-none-any.whl deleted file mode 100644 index ba28ef02e265f032560e28e40b2be0bd6e2e964f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2086091 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%0Dqs6AboN5T-Z;E@If|~MZp?e=Z#<JMW_vNFy*MM} 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%0D4F5!n&3ws?;9}bK2%B;d8D6aJ@?mQVD2Ocyf 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μ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 yY4)7^r-548 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≠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%0DU93 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� z>3Vn>{V^kL5l@gsu*-X+l%Q1NiCn|^Pjcc*M`aa-($60w1<)zf5Jul$?<24o4XIH} zijGw7M^vVN5m|z3A2>>f9-qfo1LMAE|02zZ(OjxxONj4J_$V&Vj$R<(?D9QxWtu`s zUNbA~b$x*Udccu*Gc{+2IyEybQM4{_ejS)784IV-D%CamC@(A21cHe<@Iv3R+{jG4 zBsfYycHC?qu3JBr403D963?3?nCEBkInL=OKuztSL-<H!^Lh7n_sicI?dV$cDdPXy z({!G1N1L*l@^1KgHi44aAYQF5)y37xE%1pAif-$V@#AD@pTZ*>B;g=V2^lBbc&}L) zmn<nNc-pGpXpbb!ZCH`iSrz1e3{5$?Vu)v&aW%+J$*f@2rYhPgeeEJb_?)X>tOh27 z(23~ob!jiCTVu~EFwDDJqX8|<mnWnn9o^u<P<AK~tt62who=`yV$W1MYoe*^#@dRV zz8Xn<>fikm7bj;`?$EdWhI`_Zo1M=xPr3zvApB-DNSZgCSzXu7fpwGOsi4!gfwB_O zS(oz275qqMn}UPi0zYnyO<lC@V^^jaK+J<dzEBmVTALO^QIb6LWK#G!p>vS38gU%^ z>oi;6g9)}0#a(a$w*A4cr$_l?$F={Gdb4F>73B$s1l14At3rE++)dFvK*tMM-7<A2 zB7}=3Z_%lu4;xo~$JeDjc^&Jrv7wT1^I=H|=fluC<nW>Qs`;nz>s2;zck<&zd(U2B zya~7S2}4B74uZ!5!`Tr}6D+G@J05R8uHe^nAyh|!9+TM<c01Zj7qyE8g6hbF>X-Is z3BQA<U=IAaY4rVx$#PNVk0huMD=DmV(}*350G_c)Z>CzZc=htod04#5g8tH0(m7t; zmmRVp%_+Q+g%<RgcZAmGoUhg^X%lPLht1%*+mJf0DuPF($14dq+wcXG_b#D=m?4!X z5A%_U;PajTy|i{lybeS_4+J!51O$Zp|5(|!Fn9c~G<4lqoRRp0oi{~dd}_Tu8fjsn zPaH$ET%&Qp8ftTKXppPOS;7nz!}P68lLej2lAY%)>KL?I@YN;3@~eZX%JRuXL^2tn zWz(FGSKot*5?{AR?U%>371<uon?|pPiznT$r`w$O$A-w6@d@4Pi6;a$-?p}nb{gHC zo^D#UtBIV;ucxVt3uPalw?{zkcSXg>Tj=fS<KgRNUFZ0Ym-|iGSwlz0NXGfg+gXp! z&UR0S5BZbr_+Cv^TF=+z>&-_+h3m@KN-f)y>($-fK*t+|_o)8_!V|*Vn<~PJYV1nQ z#0(zGA3mMaQ~$bc?@7szTQ#4%hohn8bdJHA&-aPj{l{kRqn4|QyR)Yi1m7;7uAKd| zw~mp}j2ymw!_UKyqp!@Jq}!5;P+uy(&CSn)-9zh_{CynWUn6g~10y54msitAGiB{v zFE6{NyF?9-hr6%sojtyH&n=yGJIS$6c;gin9k+IX%<d0dUa~)RT3cP8C2yhUUyq*; zQyH;d54)Gnyzs6cZXef=A0-^`1Fn3(Y-@IovVQSt?P&AuFT7vx9s!OWUwZ-kTXDSt zB%;6F#$0WG-9DwghG4ndZ;QPg@`kRQe^zVl?Dsfc^yu()&e%ryn&(uz#JH(DRQWnS zjD8$hbQkO$xi8>NSXU1Zlw|9Eo+7`0i0h_gaKXJvPJ4gGgB%8a(&#JubkpK(>-N0Z zd8QHV+fMZGdcM%@d_C85zg*57=;m<ye(raq_<m}CvYK*!v8>p}I^8{L3tT&om3Xwa zd%mZ#WrMt*oZQuXk$7^yoUXOZ=zOMid>yV$TxEB=$#=hey1Og*xZS=3PH^^pe;m8J z9f+Oref}I;pp$gB_x>=CNSoBn*#AoX!^W|{@s>HZvJ$HbNHl*c_~J-;^4|V^adtbg zq+8>drmMX}oIkE?6Yq-m)w6v4_<2d^v3-8~1offa=B{{oiAOiFgKLAt72fTuwuOCg zb>^9t>-%tEZ2pU{z5T6aLn{Dn?IR<DjpLrJ<>?6_<)Gs2DfazdR<{0X*JK0@H8lBl zFY^76@2!UW<MQb%R^*@|68m`wB{Es|;c)lUy6rVB_<m0|{OapLZTnl{X2#VIq8b~% zLhsh8BT1NR0y2&kQJLQxu6RHjdk+=5mzTCzU$^p(pCc8ns;(>4V*?L8zOCJ!$!<Nb zFRTon4>pEGN5@BRUaM%j?>B2O(GT|{H9gOJ2pd}7Nn>s~n(ljdp9D`^DVsGf)CZAt zpPC%q*n4U$J-5qz-YgrnL_HRVIo%rD1#>souO}7pKg350re$@jFpD*PA73#I@6CN{ zpRyaw4tjhrE8i7gqP^{Xp1FJ2zUuX-vX2b+i9Zc@I^Gdml5o5q9ycy~vTiokdPeE^ zR(hKsmvnu+QVt(is(sUDUI3XV4Lf{2t)t>^CkHuz5&w{A>-+lAfJT?Y_jWpxGSmHa zS#l(M>&s!}<xa1y`{nS^t3`(T_3;LQLARWzb%~e_@C*w%I^1tLvJIlqyeGGZ7khk1 z&fCXmZH75t_qT63-R__FzVG)RcRPIEfP>kmgee|cr8X>?Kk8TStuUu1eXo~jx4ias z8?=9=x?p&z?v<w4-@B{7&Cv~bbVQk~fe6}s5b~`W!yl&b)|-A>Xr))RIz9QU#n@kO zO-{ge+&8@zxlc{(8^74xBzBgp-Qsz)@Yp;DySskU*w${0=NxtkSF@`=b+)kImRoAC zv$uYX?zGKddnu=n1uCU?CKhDxExX6#z0b#VDzL6m^c1KwCzIxMi_Gr-6qQLY<8sv* z#^x5}&10LA-q?He*mW_BxsCDAS@np?Xu)GnE}k<J0iWue99qz&>Np5$CITE>8QXTt z&wR;EduvG}e01VI3Rpcol+rX7kT%@c@R+#d<@q9r@iN()7Tv#C^di2Z5)HTCzB)xK zw&l(t!7EsRy(G$MBqhHsX~WeUbUd=J_<>CAGvX+nZF#Gec$BWam(KB2!8Ko_wl!36 z&{VI_kH+&4G1VW}9mm6@|K>D{8h&c!rMnfpmM$`9&N7*m!t`K_Ho2FIQi`?|VJpa( zn>sczjMcP^v2<c<=i17;ggUZ1KDl@D_+~1X3dfU)%O9I7It#K%Z!_cEkZU^2bc}Hi z>jBO~^)Ff}zc2lVfJDH#F<kGlTjm<(n9wLwqAsKVC&9S1JGa->RW1~)@LY~N8=9I5 z&dB^j{4)TOrMK2w50Oi?TwJxy!r^g*cf@6zeosZ?5Z0r=nm5^w@^8Ra0kNlX352T- zrs+jB9%w@H9Nt8Cn`|288G2x;UWo+tF*JoaP4x#=k+J>easc-$S@Q5f55~2*qS;P{ zpB6-uz4dO$we7lX9}8iPVB{hy(!C6-?tN*TCr-6gpw!<B<OTfpZiSg!kdSvP{iY+? zg)ww8bnPx<$Iun^O-$2Ff9P9a$=tK|Nb4aicr#TBO$U>PUS#?KDKve$<x1_(4c_PL z$oDQ-u^3kL0z*8JZ55vp?fN^Zg)U~fsh9rAUqO!a80PTUiGg7E)+^rAk-fZy98wj% zA=E{4sno8Qj|ohvf_hvB%nmrWBMubNSCqc`vKT2Z2AlUJXllFf8i5&~{g7lnlGM>y z=+-vHv!H08#aW(IGA)~YCj|SWtkeCmFqNQ!x6TW?oAUb1?m53zBDHcuCVSHNAo#&I z#^`Oq&JTWM?TM_96>Clz)1BW8dJbS1&p^KaI-GJdalT5YoGKy>v^<DHH|Y+teiylm z3>X^cV+q_WX9_V-c!9VGDYA$2owU(Yxl=hFX6<uHx7rMcIR5D~njHh@Y*ktUH$`ej zDP5pn%rfTSA879wctEQ!(kdCHv<r>tdf3ikqw%Y>HuL!<;DoBcF-2LBaiP-KwEI@q zApjlQG8<tu%TnZUSe-|Wty80RFm=1pCMw=B?RrjNcMKRSgrSaBbSi3JY6SRkTyLHe zE$ZUvj^Q^~C66R<;^@N>gp=*hsOY$?){{-N7onGwNk40DMuc<{oA!t3P^VrFgnUW0 ze0rYbpM@nyTsA#s<*O{M5!it0)%~4&>vCVS+TbxQc$?6P<hlrI7hPN;%L)`ssfIPu zUwm<Dp7Usvn#;-up7Cj>@yy?FBbS)6579l$#F;&JgBGK>-&_xjK<b#+a~vb;#GSu~ zh!cD4CiHZ%L)kfa6BT2MJm<(w1WS$O5g<3hBrsLK8{j6i%$N}awkX;@<lZwTc?+^- zAB%orbsXb)?-UEKFV2kh1Y<dN1nvGq)(upN@yoz2swwQyRH|He{|cf}9yCEyY(%5a z*cgLK5$gx^2x(QZcHmN|i;@ae=>G6^_crS^X6GOl-w-etJi5UukT5GZCj4p>BfH*m zcla7-@eUV{U1!YRgU>cyC+J=}9FKi(wdeo<ylUUOT4r?|j^(^rYRKMe#(JN6pX#}V z*$Jw#i66H%`{U?fmZj1rP{%}2c8XF4L}CjP9Fw`R4s?p8$MP-#oDND#M$aK&EHH>Q zAQW8E#%?m?%9b#$wRiXri%Ac|(b_DCT+qeG@^2DgLl`@Z-;BM+ev(>0S?`0&z}#T3 z(SI}a8v2a@#RB8*H6mcd?zjGfm;g!vR)oRFpl<?-07eEO2bY7*MW=7_D+80Fk;#16 zCwcMDPR`aN(X#*+`4$C=4n>coN8Bss7Y{^)-eh$QERuiad^RSDNy-Rlf@Hr!u>Xkw z7Ww@S$GT(pxHfBe?tB*Q3_>;NDV%bq9Itg*pT;-Uvq%hKfD<rvPA&2sw<xeL>42w* zBUos#*%U{oe$|5No`sD`Sfy=A$nZjgKNtNcgP#G=&|f_nEY_~YEZ?T01a5u&jey30 z5^6)Nj-iMHr8WEwz#>q{RdONGCX45s()k-Mrp0s;@BlZd;)3b`b&IcFhtWC$KqFJh zD!Ru(BU8y${;YdF<RWqx_sJ!TtNdC2Z44TKCeF78b&(c|Ri6sM{aN>I=pSTB5BlFe z#p7=TmVhFLbZRUp8xZ<$9^ZslomlUI0OFUMKi>aK>VFuTRQ#LAf0z-oTR-fdf#@jw zWhPhp*8r-x%G4?VJCh27W=gG&fOu#TO7t?l!fD4X(lc-sN;UskQ$bmJg+4JbB9tg4 zKmjtTn}vS9R}6#*E~+uHW(Lq=c{kD}7|e+w*?OQ$1$jcuVP1)JiMr-`h5qs2BADpr zWHs6U*w7~dLIu~b)C=jv3~0Q6-2;FSOC<qT5YargPyN&UdPV-pA!`=@_4r>1q{?3i zTog0?{JKs{S|>5z?2k(3+If1-f3E#iM~G_8js~C^<RAr>hhO~iQ@gaAI6&b4f&B{) z$te0ShK)?!sn-eMDwt{nVdDS`0J2uSSnomqCF?KWe0Yb)5B3!}o%ucq2r8UPr*m-# zqGJO%&du32OeDF)+`#mC<eVSbEJKC{>T+nq+Q%dSh9742F^o|-QBL&pePR$qLO%Z} z_qX`ZHN%>XfD|V{Uf^Grp8r7pg@sh%`>Q>?2q*f99w?3EUcV%#+KFCKU~&x4iC$qK zqM6NK?fwn^ru)~5k3~Gx&ceWCcohJ2HSu5176$&C$$uAeZifCP6q;XLFmnQ6tq>;m zsR|LZt<X&LXiK<~%fFQUhzk8Le*7Vm!)BTPybDg415M^<L84&k8C|qe5e2KUJTw8i zP=R<d_KpG({d4306r!q#@(Yey^^E<JfmOgCjr`()MZnQ9^!_QxAviM4|NE&@=U5mz zjQ`+3yNF%sdHVi;m>!SdqUkX9jssDFRKlJ^<(UW`jsooX-;4ng3a;$``gMSd_+N5C zSvm*`nOEfkAz8xYGzb6{T{rC<n1cTkFsy1BYsvnt4Jgr%C@FfzK1fV?*688|*z_(N zaJn=-<A7u+71BqefOseoQuH*ve;4?}CPHxkerR(S2Y+s}5gP(H*Gj2#C<_q{B|ty_ zU*Lb9YI6_A=rsC=n_AjxB9us2T|*w(e{=jd>%&75lj{U1mALPKyZ<c~^n(_w8tlI{ z_zz5u0yDzkZ|At)0SxW`zW;AkIc5NsO4Ty<je}DC@MP>8{g0J^LLt+<I1aPO9y5Q# z%G53iOeJ+~eeZaGHZ^^H@8~xon5h3%fNVEy@9VOGr&HH6_D=>=L45>8VF8S2#9Jh| zqT0TDEZCmD>kRbIZ$#u3*48=({{v{`k0|ihbBds%tLqv0$AkT+(BvM@(P{J#H#POu z#D84}?PL4vDE<osCbE1;a(JBp5UB=`tuGsw^tayrH>ZElcUo+2W8bL$F{GC4|LX5v zA~If<_sR-dTS#)$PC_?^Fw7?N<8Ry#aNtbl$7$#%Le^`u1IPOJ^d43^1{g6%ts=4o zC36cWf8%LRo~&#c*&_`Zgp$w&qGQDS)y_di>>2yOZ2p=5imO#T0937fBL8oya}PIW z&)!q<VEw;Ty>vWx@-M1$7f){>8o9VX|2JXZ|C_WdXAS>DcSsX<+#^}{ng55hv@GWg z|3g`4&1x;eGs3d90kCUxZJg;UqSL3N>RHqpqI>5?bDZjpF{E3F6kO_!He_^T>@WF= zVrRtjnMHPoRsD(+)1icONPp5Op8Q^;-Jw83WlpOSJ*dPGOiFww09e8|cB8pne1DV^ zBiU~ws<~DE9%^B5u$f?$)Q|~TG#HSwF}TXsM_JT+`;h@y_EE7v8c>Zu$ZYJsJqtt$ zb$X5+PD+y5WL`Ir+m^$I=UX4ABbJ+O?vsFJpMoZWpD;8s5+%S~NGMo{_{m*6kq(8A zPNo(KKx&2&N1NIyfktO^4$^bPL}=J?humv?Uybrss+9n%zM!W}D(;-XW|Q00!nhT% zz`dASBgJHM5AQ30O~Ke_gPXvSTgO+RxwZ?GLczA{8-mbk$>m1<?AwKmK{he%-oSLn z&P<7qqK=~@Dn22ug=T1A*@A!)fQ|kEj@Fv`1BOdw5pfxNL=N$T+2%H(*N@$^7cjGe z*d<TSdRMJoqI}IILd4Q2)t42d;Q2Ezpi%#Q3`MGX7;cFIC>VPnycdp!tX47Dug(rH zM39NPSf2-W#Uu!Ni#1_vOPq3t(@kgE5!Y^7nuBjxSl-FV7s5c~aw#kuhY0<;RbX;A zl|RKWMfGgF$mWCXhL!Fca4Az^K_HgrW^MsIZM56=^&HrU;dcR1Z#p@3GB<`&NdnMX z=RedOXzn~bz--ztEydoOqblE;#bcV|u?i!Avij|2g28Ac%@J-ZN^#-sjW%U)L*q7* zwS^m)GUIp2qh}iv<z1bKxRL<I%-%`}ok{cuXBP)%EL;*7(`Lff8A`*o)qW3VhcXo0 zVyvGlR(B@Zgg?-0^h_=>tf^0g<m|fSR^LW+$9BlsAFC{121dD)Yo@pw$Z!yOW6Vm2 zB*l39Nw2B*RU|&=RFv4_Jz6601o{c?BkoO*-m1_K8Wn=YvM+5Z(Od5Z$|3UIbY6Qb z2YFw##ZlKNGxHIcDHI7)x<hJ1LS%@6t{TTU_xmDi{e-{-tEw7;PZYe)Dhr_P#dlFN z)BV1U2uOYtO2z&a-xei^;S5fQg1A08coehcDAm|Tt$F^jTt>&QPt&e!@brzvaUaN3 zB~k$gl2`_uX%L0Hm3-e=D;%se4Ux?~)Bfl1*sAldC?~kn(!P~wugE2BAdp%;8Y4+A zAo@Sjhj9Tw)+T*6`F2;%6VeX5<NK5wvLpg0SedI3mzl%?9i6)Ee+04#Mor@lC&Zad zPU4t^@N2MoFvxV-JxHZeFQnm9t%VgTa<edr`=$H2uuLG&BZHJiX@KiC`C{<p#9aOk z6Zr3Ntd>E2!4vGlkRct8%WF_+-w-GC;egQ`3s2WuqGI!gTxR~xfsjN$%fm!Tbp;eA z)T{(I?Sw9W+h?&88!f7_g(fQ>2O(Bt;TGrfnge()8=ui@ilKzOYK4hHBc>;YGS7F* z_{quPM@Q8lGG$P9K<dL)>;Bm<OMFEVORgZK**}5TQsMOuUS_XXjN77TWC`K6RXSXp za;4V`D-_|JZoETJc}1eM?L0A2`Vr!dkLoh{Q|TqLivtL?O}GEr&ps*70aiKaocda) zMci9l5xB2^^T@}M*lfcJ<}SK`me?k0u{`nF$>~~Rz9<fffm<{VA<S@vqR`cJD^*!b zk|0G*2_dlJx_BxGMev$j3;|5MLkfeKkq>fO1}7r^fhtS^6HP~e+!|6+L_5y8E?>Kb zAx+oHdto?uTe-~<MQe1OWG1d>og91PZ+~*h8WRaBCGgBo;6dtZHLcEqG`=Xyv#Y|A zC?aS}$A-eJ=yU11c<t7TmDs|6^oK;29vd6MR0y}Nb}-wk&xKIJPvH^3%{0K`Fb*IS z?WQF+|Lb`W^*Gmhjx#JRRM0y3H3`3fZ4AAcSRQA-uZ>ztTg#w`2R|-y&_0lq>;H^p zj8lr+hc#3mrATdN`IR3gRI2jBpFv11E<5_?lv<PxnNOm;7P<=$bBbky(`8sbI`fr) z%BWZt`fEcP{s1kRqVVrPnn9O-<oIqDn_Hv-JP6KKd2|)=A<mcC04y4#j&He2N=LOk z#9x%z>r2rJLeLu1oW+@`<GiFB)4Zfq$g2SY?WKxFrsUXQ(cALHR^xuR_}|UBlzIwo zX9Q7xr0-~r-P=ya|L{X*mKW`6z$rcMD0dF@-^D=u6Qv|c(s4f6L1I**9^ZC`oRIS! zLhhi<EsU}$!unCLTYO(~%HMd;9;}y=mkU290xqN$;zDf%H>F)QffKj@QWzzVkXZ~g zm?w$YUX)!pUO7(cbs*dpIzhhg&0Tf1<!%dETDSGF(krk$ONp5bf_gc8*{bjmq{qDc z{M)$*G_#i@rcL{J>F{5U26Me;T>XCb(nO~FLfJ9%hRRJHC?)*-cqblYH7m;UVE!~> z4!9qgJNqO)pSXfVVK<XOaUzpivoCOK6s-Y*qu*#6!^Rb4PQ;MGd=Gpw?=m`xi@%`| zN(|a5)`Ileow7(dDKVfZ6eQ9!uxHl^Mn-Yn<`DIAtQu;a>VQBEe=m?esNK(nW9b8U z4{y8sKNf$W)%x3Mpn>9?Mdo|i)HgV3^o4G5>_jT@h^M1zb|V<$>7Ws7<9qBVS27)b z2x3uSrl>8^7X@PmGPd;Co$3T`X(G>!i8@G^OwS4w{aWwRYZ~)XmBNS=4XCGPdA`Uy zEpw0As7n+q%0AztkaeVwwvrC4xxxp5foXq@iOQ8bUlSMuMxhYVy}O{xY+ngjwuDZc zm9#oPE*40jr(;e6PNf%Ko+<+jqiSU0-oEQ-w;#&E41ivQkgR&s8bMe3X{S0^U{1^) zSp`Zh7x@G%<K|jWJ}VA}h8SGuY^=IPML{-#!J*8xO`P5fQ!LOTI9Y873Iz@+TiRcy zy%LHn*Q`gW@*~*<;AxOIfXGOd63HB-IXHrmhaMMt!x6whlA=j8`WyRdQ;~h+J&dN= ze9Andr}$7r4TReTP*1a;D@J>ktetNN7<tp(<ouAVXOjqZB@W_;rF9e#<6&hjlq5LB zXiK4DHe91Ro@j>k3~Oa0*v5#Gi<v;^z2;kE9OqicB(+c@7yae)f8T0eqbt!*R!;t! zW};`CPbfxBli&kol4?xUaoq7r0Q1YKGgAHx;2QZ|#%O1KivOJ`n1Fq9sved!#%zA@ zC)7~w>s}+*Vi%{(qLNJ%zE~ZY=P#V0U-%@}LaOk6<yPn=-5m|T@)%nvwxAb&vdRNy zaYEnntod(8Qbn$y1C=W~=uf1;2Px9Od85pi{g!qZCAg%7Z?qTLvILgtCiWFifGIQK zT+6*fgAO#V!zT<vRH8;UdWi_8gBAguae1B6ei~m$jDkhRPT{wUho|1iBTtcz=++4l zKotWm-a!D<8|_#uNx!-ye1<!9suL1YwKF42!B84TrbLxM;|~=UK<!ZCZ9^{Kj)HP7 zl}XDuyYiOWs%W6o3So93p+)+!fs$w;_?Dd!56z0at+)ZT6JE9XU3)AP$aJ{qa_ImW z5je_9ag-^)FHHdaG$1M}Mm|lUeK3Lquit?vR-WD{^t#m@`Je+$1}tZH2ew@)-kJ() zvLeC)18E1Vbvfqx&-(Jbk+_g5(6LfHN@)+E6_KF7Ad3bL2G?>5!J-G@kQii%9z^-# zsSCcD|LH6`^>()lS3UY5rDL#&hpwcC=Q%+tt7L|278T|LyW>><xlA`|VW5@jZ5CnB zL+NgH#dfNb#aP%c3wL@}IO}b;Lp6<=?kxnQj=+u6xjyHg8SXl$LA0mo6d6U;B_uzT zz?3i%An_-bB7qS{Q9+;H-$i~lveP{D5upHTY!NWlFECwqcXBcRJQcSaVDK{%&q}%u zH8T^Gi8w?;#th<z`y_qMR0UkTS!>ey6XNDOBr`ykG_oMpec!f@EK=afEzsOUcGAA~ zliE#-#eraVt~o5%H5Djpu>T>s3`O#d-bq81f_*r29`P3}O^*bfdQ|a57?O3j;8ulV zg-+=LOEN}Wa!Eqqk>Pl;64M+#*kHnID!yQr^U!lTv&#i1Ah8*E9SOxDvaf;@-6SDm zXwx23jyGaiYH8g$hijpgu-1;=y+QT~5IE~0_uVU%OT!IKi1j2<WaQzaR3M1g7hh_! z8A#p+LBa(`1S`01FP3>WQGAgulyKVOusc@b24APX%#P8<sISiJH~l6Klfpe$>$^{w zZ@BA}*|LNJJG<tkL=>yu<9?yA@vj{zF6U4Avj=VuLe9AHQ|dC!LA~V%!eSTZQb5S7 zmGVv5B@$ynnBSmb-%Sz;!!SzgIHzRyu=`nNI_&W>qJ1zL@=$JA?`Rv%ScS5vzRRdI zHjH!cW$P`l8j0zVy4gZxA<jCLC%{Fpkd_a_zs~xGKac>)6QUlI6h~LQ1v{m=katvm z2g6{k_+2ER&$35KM}b`ayeW>B74+SnKx23Al-JImMlAsS^qRmPzLrki(HhF%Z7-@_ zRX0FQtZ3Gsm;GMp7nsMGwY>Z(Gi#D)LY9t3Juudsx`f#JSVfs94K0Vu4CM@AyOBZE zCgP+1sToYXbD~US-wH4^5`hedC&48v-vzOAO$_Asup@rT9&*a%veKed7Un#m@6tyN z<cA11GVky6Cp1-*9gq7<i(MfyS!0yUIOF;#S`LviA{P8(_G`;4dBhw|ebsdYF_P4Q zO-z&mfO!X!Ep#d}q(NhefmL|=s0S%fZ)rIa1CG0*or;{3*e(!_*6!k)S6E8!fLKsm z0kIgZQUt12rT#8Ve(_3IBWQ`T6{xEpUIHxFBUFduRziwX3<PSa>DmhjBw4$FTm*U& zVfwrz@=;KyY@azb$t@&Gsxr~*P5WK2%vOcCelOvO9HwG>=H!z<Ho=@wJQTPRMGWQk z$Q)aOE~{}ICgDt(rQFkL0#|8R1vEV(l(g~DqCUDaRZNIMs7ry7j=0qZFbMLTrcfFC z4ZX9B+Gjq6@<>76aT@C?IF>9CN<;?1w1De?e1CTH6Y!sJ2fQo0i(S9U6DKC6%t{<2 zWGPB0z)CFNc8%1&HOEjeI;81%9P2f*qNYhkZ*Y7y8_K&uo$H?N<^?M>ZW$AjWEkvS zFCYDiYe_P|56(D#iP|akjIRD}nn))A)R<H@uv*?ygXE<zfnL}p7r{8x)TSJ92756T zqsW$j$)fZWOTeO<)JEJA2BPok6cWQhCaiLRD)&>mL%)0<h``&D*A!cFi)u{lJV_-a z$7U^jPXk{jNfw<0+3`3IJ$_nFLVg;H6CRPOd!~TPxu%#p!67Ykhaswh%E+IxKz-LO z&JaTZ^_P`X8uSq4pQNreqXO$U$*qcT<DXQM$%&j!F!WHxD(#3mOYF1<LZIBF3dl3U zoBc)FMdPMf1&U7=g5}8+h*IT(N;3ZVpbBNu61i~dgJm1llq1etF}KOm@&&DTIsjJ~ zS<lEQui@#HM6uJ=68;N^Oq9Y_9yAK7j%f}-R-{X)kJ9=m6c85(Xs3<g-K8k%FO!g7 zdi|Q@&PNV~fFnKE2ELirHT1m^<3vz*#l~9f@>sEVQ6wkE9YBZ+C`KYk!pr;3wPcl< zYDh%hOB_XzNcdoSZWs<&tm_KuEIaXYIHsn2&f}a6SE;<RCx`<HXezMoWcj{#Z$)nW zv%b$b865yjoogI?Bd<BkE5im>tg;GA@V5dD^ZhySEx|<5A<U$m;uq&t3O9VPN6`g# z5N=qRGR8z+P*M_PC+MlHs(=q=4)}{w<`Br4dT18leJhp++74Gw6O)iE?~${>qrJqE zZi8b8t6WQ70aZ-WDah*NR66CTb)Qf~P3?HnF$m5s2wS#Cw;p(Gu;kJnpAHGwxLheY zMt5N*m1wXr19^-L@p~u9KNORqfYuucUZK>&eo0Y&R^R|vBWrsrm-BlwJ(p~n2ec3~ z?NN|6Y@#1rgYnxeMKM7Vd9O=wwn&UdagwGm9R;UIb?1*5YA47v`>{Q}#%Kq%fjmT_ z-IiI*bS+^BajRY0JCO*%Nl9agO3GlxkWaZL5)8C1qjNTe-+wadKSpUZaADi5x};*T z7pF@z2Km!zU4VV#L%Plo$Hujkpt2ky#(O9NDMgi&Sw!%hC>RMMBt%3FHu5CUvh~RY zrJviT--PK1^fkp`I7jFzEPnmICb$z}2+@}nsOjk4gV5?@b`wKZyysSKt~~<8(mz5E zr@*s@=e}<ih6ScGQ3<s#ps1PuFxQI0^NDNUurt;<<*2@8*Ve#a4?-S|o3ARzahjoM zkL}pQ82#1mz&f8&EOf_%@SKi=<{&|PgU?mQ?tN#6p~awoP)^c9o>ma}hn=@;xWF>> z_ghndV)}Nr?x{|fr6vK7Sd!}0;q3VL$cn_EpJ=#6R0Udi4tR0V?J%R*NcMnlyMmYk zgtfxvdC5vFxf|?fd!@AaMawHvd0{<|yI?f<$t_g<BUCsa0byVJZHrr9?yu@vMq2g> zvw~)D(|K&R)AeOL21jvvWyhFA<6`eSi|r7BO&V)~^!fZm6|H6!DP*F~WNDNZdAU|g zqy5gb=MNnJnHUhrOQy^0@ta5`izguG6s{wnI%Kl&C+Toel)S#|{eWJKM1ANXCHsNa z#S}Y-0l||G5vL+4TJB_5C-%Xf@h629c|k(@V&I~k6rfTMvWq8eS-@}tUz13=B+nKT zLrvYAZY3aQVE{FdJfNFJlqTRC3#nJq4S=e{fdF-`0UNnIU2{U#4ma~7U+6{YtN^OF zV1$IKyDqGd*y6xf5bNwJzIe*NR=~Jam1ywFVa8-&kz%>?o7T0S5G-xgUedYkzmMxs z=21^%VLt-y3JP+Qe~r4XQF3N1(iQ4kN(A+btQ^Ov@4n_;>u+*Y@dzI~Bw6f2eaw)j zvs#2z&P>76j8GK`j;?_H&M!iKtV}D>7;&7uSe&OCYKw~kc*FC+fulyaDGimUV3xwi zxc|;H<)zq&c&-ye2-FhY7!FP9A?{;+YF+H2l;#!+r~t+P?B^xFwUn?cuncnYq#jzd z_vrEq7gI;_7wWOUMLN|nv=IUBMRBKvT8zV84x1fI@25zy3-j{#<!S65htR_$zj(C2 zGz9p#TieWzCX)GrFr3POr&+2na$|C(EcK;$uLI+2)%G7UB&ie^cI(ckI@)7`&_A?> z>c{8pU#m+c)b55mzDpGaXCgx$<iiWssDVMjjo&Tfm<FHfv_x>yBbAq5t`~ZAgx>Qa z@}N-ERhc)MK2XLL3Dm@SRlRN9{oJyIqxdCgya;n_;9TUHz$cRC3TjFQGbdk0g`k@E zf^)M}cSaD){`8jWHx`_+EyB|H=R4Z)1)+HV;xOIs!NNq1=%NI=Aeyc>sH*7))N-F! zfXlY#dgQkX4+z9YTs+@c-G?EqWYhjlr&Jm}Ij1aCSed~LC84;tYOgQ%WU&t&A(32` zK}$(-Nr>~~;%9gpWeEXYUOJ?)m*cPE{j%yV2|tHhrBfZas!G^3XV_E;<kdokMy%Re z<pVxNd{X?9TOAg21$%9h_JFN$h@~!xXVj(?Sc<I>i{@D>uEJM`+p!Ft$psc&?s-~Y zg=;$F<ihhdz?+mMCiq#&uk1X!K1drJO|u8)T-hBeRMp*oJ8>atQ*jIht2Y^mZ-gYa zp>Zzs5b{_I2%6+eoh>)e4OuN%&Mu-DMUs(igyEi$O$Jnh>xsd+rdg?v?2fR%qF3Y+ zJ}|PSdej`ai`uL{1n<5JUcuL>L;1t|pXvBvakmD|W2IKc`^5g15bfMINp>aq*mT&{ z&~0{Q<B|snO7ng!({fK|akoEio$_Fv9pj-CS!Lzna&0+E2wBtkl~n}c(wf?w{?MSZ zzEWG3=IbNh$q=0)z&bA?%s5=2a2ch~lV{D2y=kF-EYxh47GFitDLu6l@98zmA!zd> z4+$B;_yycvB9&5%?SBuUmxgZ-a8z8YMryY_+7WQBQN9AQst3at40u&i>a+0!DN9{& zf!2jUf=o!RRcZ61Ex13}{ETi_6%3Cq@<8?%OV<90qk|)smaIu5*hE*2>1B{ZTZ@P< zHlRP+d=?$hzQU-iB_bZUeIy)rZgwBjy?0D+Ou?vtQQ4}Oi1DX@*qTByGowx$;Rj#{ zo}8|Rhy!yw6hSKk4yt#?R|ZTO$m2~*pdUi;tbcIG-sW=EpaG()GM3~d@q>_UH4$2t zW&`NjlH*Xb>hWiG&Y&Rk-wf%G(80}hXVCy-=0%kF2~kU!7(JliN$!PfxD>F*DC*gj za!X#;JAg&!0S8Q~zWJ1#N+RpvDL|9(90S2-2V(REw&h#js0*-_K$9KP{?UIM!yY)E zcC=t{#uZh8<)eS}{N?m^jiVgogdtfE7IfH|Gdx2`lf-})uX+Mz#_@#ZTqA6}V=Df= z{^am%z4cEjC#GK<!-EP*BeC__th8Y@ISfbP)uJPA$SGM-H;^C2SN=vQr&+N(=DxII z+IyWn2ui91GewmATEMdY)gDEcQF)SzP^_l5ge8pCpw21;dWB>Yk>8UxSFI6BBMH>v z;GnCvFd%phEjN2%JIHdORN~kw3s8%aUC195J&W>Br3aGhHm0zd!@_^!)%in`|5&cC z2-ms&C~z)F|2uTKD$<&=G2R2pe!v^wCWzARp}V5_P6j*vXD9)Z>apyfnsOt#1LkTi zJoyZGe)3pc59Q2x;xL9q!ee1+%E`FZ;nqJS8$1MKH&n?}F*M&QT45hV4?8T!@KJ%9 zDR}xWixNgc7DE=Y<)0*&Cko1ZM2XdmQ7I>t8>@(r=6RHZ!}!-3$@v+Ti>-Q&CBSO# ze0E8Wy+auBZUrN7i!LaP=H!Ly{NnH!Ph88*P(Vl!1;J1|DkSAzqvi#t<@G_8Z3{6c zt8;ZID3!#Eu1cxN!xX?d7_yvysOv}!wmOt4P#OZhq}7L29ad~Y{O-R>N$I$tOj0rl z5hHr=T`dA%pi0{jJ`sPL+Byp`;j+geL{N8B_82XxSk&Q_CI7x4%CYO0*fD6_CxhZ1 zF^T@WvZ1Wp6RaZ76~<=iDemYuihhvMzM*vrX805pL6NmWvdFAG%~AnK?361dAb4&o z31Tk&sNlRU>W4vkswT1?&n@p<PEK2D4J#}*qPt;O5<}D^D+Tm_5)KimC1srAbC!Jj zxizemM-rN^@3;Ciii@g@p%!(Z40GpABihH~3O}^eF_ucoBUD6DhYD&RB$Z7)zHi}I zQ0h_HC^WIZ9Ze?6ucth!216xGJN*<QJg>GG=An!y!^<b~);3r2dX0mu)Bq}lp>)`A zzL_LB(sWXQE}_njxDsY@9NQ|F*2zc@Z#z>C>`%Krwjx{<6bfdaNmN$EH6`<{1)?3P zUQvb43=2|58>A_H#AfM`Xd!doP(sPIhof!XyKFzxQ8%@2YI~v%>7n9@h>wFvKCC9v zjeu(}FA1Y-FXsj>*Wqs8@l}z2Afe1i$RWdwvi@@71lH_5p;#(PDOyU95A>xPSEc}A zGf|*CbMHAO@ff(GWk+|YOc2vADTh1BkDfRA{!N)Df)fU=saPPb3O%>|{54c!%aYuB z=*>e)N6lP3D^n1r3R-wJPrHSZyHrGTglh1tX+kKzE9Y+$hoe@Xk_`3FR}fqn6%>pm z!L89!bOs3&Q*uPT&GRdD*{8GYytGJcRZyvs0c$AlkaY_V>5~u6zd$H+;%S~OwQ+Xc zXxxihPJ4wLS2=3AK^=Kz$}K$gh2<rH0|UL+iU`)DZQGW}*d-AY4LD7UjfrIytUKQ* zUwIDAqkVfWzx7#~R+ux&JhAVLMF=jWoDl3Jo5L&bMR{A1si!%z?kITbQeZ^l#3T{+ zpp1$~?%8ufij|ldcX3u`zOk^QP=k&r<OqH|{kv$ac#-Fjl#0FK6i|Y8pitGmeOflJ zl$uo*fJiV+!h%$V?iX?A&6>$E)(Qqu>eO`gEZ!uS0HWtYdF@0)1chqsu9YZq-E($C z@@~1yHhtf%YEchcx2Oy(`5V&!B)?Q*#=&P)uWpMN%|-ireVt<>^dIE7oV%aiP`oU& z4sKmUHvE?&b8yL`w;7zXQ$-Z`O!ibUg(2fU?c+y<#UmUxIYnMBo)4e$HLdUOd^%vh zt~b8_=a@SQCB(}d1|Xns#Xvy#|F7f%TQhxw|D+dW$;a*wCl~EJP$6AM!kIN<#2dvC z7~;7FN@+6H6E_mWz8f{ROR7|9ZmpmG>RKQAe2Qi-X?*v)0qO|A)0#`(7axv(uD`{_ z{XAs*dhe0M_I*B&oH)v9+Ka4-mGt^FeruWVz3u3EIc7_-^>J(UdbxjI8EK*0(OU0V zK}Z>SO5y8z%Zat!?mXxFdfuC{#nbJ2zrH-VJ)PPc*g!~8{cz=dIbKQed3?MXF!Vk6 zynYI0yYlgHYwvvfyxq;gZut5{L%`ea@pxGPfq#3xym<TETadlVz8B=XKB#b&#r1u- zzrGz9IGb?oIS%`}QKHKkuJd^<$~g^1NZD!L(N?Ik<QwGsc#x&t$Y4uB+jHH~)#2_0 zoWAxpVA+%SczF?8GIHf}X?xdl<@;og*QWJ(e|ut^QiHqm{%~?LGSQ>e-j9%ia7C;8 zal0GpYMZm;+oNQQ+vDl+azD{(vBSs1t+n0r_Bp`UUb~~~<NkDaur$$<QWClnd*%J? zL%^%0)9wACyUp{k^7VZAu=msxm6x;AeQ9f2`gwhQzxSqFvy0dL{CN8^@K$|)8yebz zAp5c9^>F8Ei>v#9=KJ}4x_j{!Ix+E-k~6&ehBr;+j3?0TgX+-Ma~8U?^zm`9Tle+G z{<MRaz5Ns~`APiY(R@VY<8|Wrv5|ro^2wXQ7mfB=9r<~1TRbzoP*d~1pQ96Sm(|_P z-Mysmd%V*9mE+U<De^TpaeBKoaB%d5u=DkPxaSk}3CJ?|I?v$qdAVRqxrpS`{q**{ zF?jov!S~huWz#eNq5Jm!A{NW{xhQ%4P}EaVl48sEJ|Fx9$UbnJ`&`wn8#2xDeVob( zeSI_C@v`^&csTexh@5bFvGBZ^GQHTCX+cY=>Gr-^O3}s3(W2Gm<9_!D9~pW#MSJ<u z`E&I~;On(gvws$fK!>-}`K0;fn6v$){X1hN1yASmBTx3OB6LJYJ|)FF{QZ97YX!U8 z{pqpBqvwO<2n<juH*XVwjsLve?|Ub8D>60RU-TUx<VWv5n~|UYA7^hJ7v<CbkJ8K1 zAq~<Yp)^P>EJ!ISt-#WyfJjK!5-QyxCEX>6G>C{ugLJ7#hjhc4_49e2@8@~W`F&sK zoImd2y5IAjYp!c%?t8A8Wp}=DGMX-r`LF$&X=_<~_}k6@^m6yyZ}mLgV<%IlwX5lv zvAgohZlkhgIicb@&T+!^gU`7e<LTg46TRu?b#!{KlZ@9q-I>?P4h+`pd-yf~tM^pl z_2tL(9i_wwkKL<Z(cJT4S*Dj?7cyl|>ypl{uVw~!&RITOoa_~5-8{EC{k_*D=C#vy zwXAe~_KWfT$yYJct6wMemou;1d``__CKvgG*DIfYwV7N^DEV%gR9ssa-#<HWxjeZL z@b^+Vd3JRWxBK&K?N=z{Mqx4(=9pz-eL<aN(tG-IMC0A!=b6G}3YdD93Af+oidsxv z!j+IK-~DTv&ZOJCa#JIT?8=WCldzuN-+(=wo%auItb45%^FHb;hVMqh?V<Na`&O%^ zmxnZG_Wti_&emT@AAGyI68tN9`t!xmdpBvCv-)RJt22$6bO-mYk3j;vB0MufCDSLq zs}6wV5wQ@~n!E8qZb86=*!MFR{i>wmFWH6R6`P<|Z`c)<@3G${$7<8@PmLjuoe3uZ z`umd~%$=Wdt~N#Q%Pq7_d;}a5;{D=Eb>qP|zn{ad-|Rm3hk^TnpU0dRvxkF1em5)c zoP5~GqC(vjwo2%&J>GrZHjjO#3iAtWt#-sehzxm@?Nm04;cM}Xa+R*MR(8Q48vCr< zi+4zD+yYSM0lHxa1V5Bu0|XP4U<HJmD8cM6Vdm`I6zGYjIIFMl2x%=JyvFl=wgr<n zJ=m`tqOuSR2AcE${b@++suxOd3TYi*&<Fuqcn%^D4RR{oE-B8A{F?E6y(hrgrHr40 zTU$0tfe}Dkbta0FUO^&GY1;_@ZV<bM_tHY8V59gModdTqCqabTk%Wcp4KC^-JB^H) zGK(_mn5b8Q&j_+D>F)Ccvd<Vig7QzjGQr5VsAF5Sr2EPm$eI2EJNpR+Zg|a0CfVNl zb#~20qEK}18G{>8ovHDs1hQ)qRX7z-4kd81T?4oRd1nj^p|(@qBdUn6dY|;T%E@r@ zEe_cit?0hq3A{67;13O$>JGZk)JK^s6Oi(hHx{e=iJDE|h%qE&M7|9@Kg?Bjt!M^o z=n41Z7;Cyb71{m*RUC)<rl@D!gcP@J$x9wCa_AH;sugvky%}2JCE?U@l~s@6lMK9@ z!;R%??lZ5NtsY@4g)W&Rf$afz!~2btQ4W;K>BRPe#}ghQIp}^v)kIgxE#9lOYuFxj zWKo|##RxEeQUt|%Rh`Sh#09qr)TSU{x{)&qw~Ns5Y}XtW4v<$HMI)(0AHgCM>BrX` z6DCAx(Pcd+M*t~XYg8Cb3FO}t@omM9Nn6HNus_4XXA|*P2+`i+56~VZ!sHft%GItt zx{Y>QWQz#xArJ)Xc2z{s)zoINC&zU710Fu?2vAr1g(fA!XZgF>ju}&Pi@H;`s34J0 zL8SZ%e@SFwMn{pTo;yzNv~kvt99LPMV6CU9<UB8UfSNk1tE0$wL{20ByezqlddNwK zAfh%v>%3@`Kym9#k|1LG_0^(y;`VDlOjRWnD}H-MiUd&Ln_;$&zg2)%(^Vf@uKQ&M zy5iPM43XlNg`*I4UD(x)ppohtlthG=pA!sUGnk@@n3e|}RZuItc7l}%H27HdBF0*y z@v#)Qe#&d&<Q{1y0S+<44OLx6#8}Q5S}su{Cbipxk!Re}Isg%n`Dn?%uIU?~6}FBU zAt9UyAkkm~MTWE26cG{!oIKQS5rafDKvd$Tc3Z79;9nogss@_c9(5F1__+4~krp$T zh=PFH4S7Wws*TcaThmb_A5W;r@N?e<Czq&S1}M?!0ZO`+mVoLKVo>AV0IknCz-(|8 zA29)~H?b7wbomiu#?SE;=ODR))NT{jXo_=R8)O021hg_n0M~qJfZjQ(4EIm%ns|zH zbsqz?jysA7{*pY@M5*15k0~`v2(cp?OX=H-nvOtS6_=P1jSkmnx!c?+@mhx^4qk#K z!~_hSxo7R-t6z#_vYm7|2qIKvXud&0N8+`75p<aNINgWhG`f|IJt)>w9}Ud>pDYU% zniBQ9GZd<yb=!<{Tz!t+n&4}1@AzlC_I=&L%m2Aify%pj2GB{Eqdu4MAt6~p)aB2l zY}Y4L@3HbNni|--$|Z0TqKL^+x7Xly&Q+6(fx`8^X5(76X30*gU_umLqZQZTdsEGl z%38sM_46`<{LjKT**Xi(R$S7qMqCUFc5{LWH&G9{@j|2A+9ZRc7#Bm9=KrKvjdR(q zK5lN{`@^GowKoVN#5{n*tGqlo-QITrh8Wwj)M2Dn)lfV{_q9k7;7`bmSjUuK`%;wl z3XCiZ-FXf!J_)XbC=rkJY}a9dY#ko^P;I_!8r8Z0YcAt@0knK0LZO6`6B{mF!vW2b znm0K*R(Iu#dtE|DcEyIuWsBF}$*xVVIby3GiZ}*p>$(Jv>~ap4%i2Y_Z~?cx@DtXX z<}vcmYTnDPF?|Qd^m7-Z+MWI<%C66#;cK~9QKHi}NQVT}^9>K5p<H;hOo8d8@~K@M zG&gq`g(~<=O|!(sAV;UR4PJcINujDs#FG$J>FCIlVE_Daj!ybdy!>hUj-s-D(um$^ zd?38V%-uW5bH&W9S*8TgWP4HBQ+y!Q13?WM(R;Sd5YZSpmDOG}GpWhJzb?AAartoV z>G*vPg4JvxoNlor#8~+%J3fwU5(Q>16b#U@XpCIP;2v`D;n=zma2tjyg=Y=GT*SXc z%yy7ZH<u5MXe^h&2Ys9dD&R^1w@=ptKp`{0tY4!Bqkvjd!X1T&pn4)KwY})kYWCky zBm>aOp>~5>#O`f-QE`#CkQw3)c0^itSSd6j4fW{e6=MMcJGDq6GXcPDW>NSQz`x)C zT$Eab(CY!Xn}XzWfQM2fGTM|Jq#BPvS`3~RyG?$SB-37`a)_>|ru#Wyq&f!_&~YQ+ zDz}dVV-478uRR>7HHvc6o|Z8Jm74YejxfsVG{qe!V4}!1&@cxBSy>6F0x*(@w0E}x zwc;g$K<$)F)FN}A?K+B7)Bt6?8%MDp)eoK{hIlxxLl4)=GKeD5>P7>#_7`Wd6#IY1 zkAM`_d4~6aQ*{CDMTQ%|YuyIKSXn1_uC*>RwMgkIK!2eDUN8{6NvK7lEB!cu_5xJd z1fcz38dx%Z8yno_14<KU#b{%DoZRJI>|p}Meo_lG@Gtx}{Ps7qg01=O9l=w2(+ipq zrSVgUT6c^SC-)WYCxnhxA$0_|Jw>qAy%ux+k|IL_MGCjzj-ncSM7QisOl(Y0s$v8^ z|F4GYL+BLSd&z?lVa;C>5tQ0dBq);uz+qiTZf}K!Z1HVDYF(lXO7O}M2-dQCf&*&m zw&wp^tg{7ue2*Mkk<W20OO*=rdR>tUu_9j@6~8?V+nbIetFjrI2qT;mU@Z;4Cr)ls z#|Lmv>4~51s`&)m-UIVO^%3w$t0O?`wKY+MQMYy$D8_pTX#W)9nH@#!mcaodLvlGf z*qDlZMqtE{No*DC5Ealw#D&@6<Sv<~b#T!nTl1?f=K)9>1t|l_Fb+VOD5%&LK->VD zdIg}?w*aa}LA)r|YXHropbseMHj3p5Aiqoi<=6mdF%__008bc5vO2g@y+CFKDzhAw zxdAe1ysi0r)fxDE^%(d|h{6XE3H_3YggJ0+f$I`ngi?qE25|9#OG*kmqUI$|L|kcp zfL2Z24Pa0bhT@kV?RKuaYIxM_4l3xmOANOV34J%QBjSW|1GEIq0qH0dkmfbpxu)m< zNlYG)bh!`-&3J%Bo&z*t0TNLFhGOw|({?VA3w-MQP=EB?vEO$PW1{wD0b1+xRQ#%Q z=NO71mwG%nq2KW$w2xZ@wCu+L+<ykj8G>?$%lOn3jXemRRhD3_90xqbd#G9e9z?M? z1LV5x0Ik-(Kx^3@L_*pjc7%2<O2sFjqWlD?*f(h}vL2l-em7MI=D;Cz#o}QTV9Ycq zrs$2HJAL9^5uo+-Ld3MaNc89s6rccF>wbW2Js+U;70_z53$*h5ht~ffrE$<j*r(m) zFR@z&nm7JsP&z2;wGWCqfuigEz@U~>pkM57pT6PX;r*BC(-0K24ZJxD3<6Y?gLV5r zpSvG=uKPC$>ijDpj4gont1yE6LD(U(pxM#55Mz$X90?`P9Yw~i5BTkqNy#Ioe|2Hy zTGAS{bEV=?@~ajEf^Jbl05&OrJh1?D^WPAfL3@$!h4g!1=nl{>qiPT8Y5CK>f4T^G zag3@OKt&cw6Nj$?b1);cB|&dSy-l9w=U2y9WUx4*;$Js7AmcK2rQ}M4Q2kGBMIY-Q zb1^iHf#E_{g&G&Uhy-4Ad}?+`JMi**+h4UNNPxa{i{P_CHT$1V_|u2j5fa;|Hw&Q^ zz|%qf=Xs;IsfPsg2v*+}jx>QNtg#flf&V3N8Dap>k^+FU>?mh}XIzKJK-1+VaKaxo z{uWS5WPko$^>Ifp2!3XY2=k{Iz<5UkrHN3bG5=NiZ|Ilh7zd=?MbJJT6LFWO4w(H0 zL;(_t9qlj9Idettq9w&^rPHH*{Zgbx6!1^7VUEsd6}-5>EJR!FBVK-5C<uBu6RbkC z%P+x<e5B9yZMsskq|P))#{taB<9?_N5FL^CYJ&+`E@Xt;LMEXE!Tsl&ZX(VXoG{G+ z0n!Fg<6dp@w}^^ZKWEhfa0eBMMDzYjBD7M|lyH<cB>w{f<ND7IEyoJ1FTDiqAJ+Ow z15XUwG9#wP%1kvBpW2UC0$)TnCV($#ha3N(r{%cvW{b$L8OS<>j#NvH6Xc^}aVqdS z{&O*axSYLGjB8F9Ek74APM6>~N)2YiUu9xkx+-(oI>*7Y**aoAAn`yY?@@`>UAbb% zy%Z38!3{aN7$T3{f<}x#izk-sx1tR4L>aW+{KuealtHvxz#tj^0ni5zSA0>H5C0+B z{;~XBrBH%>jSbhgzJ5)Kqina(5!AGTx`u5{K?q@N&!;J|9cutYZ?<u;IAnYTMBgU> zJ*ut)F-1K9b-@Zwg3={WJMkArK6fLws;(<SM^7bC%a9#gvAC@*2s|H^h=j9q9`Gz) z;VBk-14vAl1U$8u|GcwC-HD;-{eUj91L%t90K#q!(y|W(kkKMoDbOvb0MY9Q$jv+f zxtt(({pH_~8y%y8KEJBk9t8-njviuz_~=rngUhWSuq!;VbH_v~L8Q?wU4uxNUZsf8 zrfUJJb3uux2S8P%Ne5Tm@IR;tjmv-(-yEbh`W7rBO2Bexl7O2!pYjDzh4IHCkdF`Y z?E`@lzTYToO*)F~$~IA!p(GtM?A!v>I#)~XpY<8<1A+sn?|)2V1NzdI{;tnHP9GK7 zXoAWzQI(PZagZV+jg!HmyB#doid8^ikuShl4wPU2QMgv`#y{LG8^Ou7P86liZ}kF1 zeb4}9;5|5I3pCPhf$DNmZSW3MHy_H+ubOcT3>*GeLx`e^C<5l6)<QMJAH!}k8Un-q zqhfCETl}j3&tB;aZw70vr-26gS3U?*{B{0n^u*oM+#oG0pR1z(GDkJ?2v3n>OB)0@ z785w&?N$jwBd~;A?=R$G%3t<j3>^Q+N>VCWTd|mMw(pDOlC?t=YO-*Fufx}RPE}^) z^ZpK^?!?`6$bR>-wf)=MbMMN<#OXcXrLnSBAI1-63Og7*+BU7xebmEK=zMPT=eWXm zTDNG@G9IHl7T0A~pe1{4_+q8G;s2so)fQ@rmaW}RZN@NJuRJET9XF2GU%bz7B!ki4 z=(L9yP@F%nlzrFLo$1M;2-Zh>3K#4yJyUs2)LnN1-iVTVe`zJRZ2ehDkmaB!lWZ!v zTm>RkNUkCv^`Ue{cItEKiuTla>59cvuivOD7nUd#9#XZUs(jNNWVikiRb@-OzNT8n zi7WQFo<4p<eWMXt{kMIhnsSpu*_m}Wym}x6q-)<j!xi@ah2dmBjVg0Fq>tu!*mA`* zyM9R2rCaI2U*VO3;qpxcr&o7^nqS|TZ}G8P9h&R6Zx|!G`uLgvKo}n;LN-e(rEK+E z)B~G<=6T&gq5%CCZn@@l5!@x|LgoO^0rmzAuXK@MuPB-Z4DSJ&h9GYu+6D|CBFcs! zpD1VphHn&hLy+$Pb_0fAE9n*9S;)$bll_bx{ojKgji%_b3sZjB=)Roi6u1ZFoBI4i zVg;t?ato<`*x0@fXB5;2<*WM6L+>+L`^@)SR+MWeefIDE;1yl)jgorZFP6+YWWGQ5 zOZnX@YrG<!qR$dGYpJ4M1eZSs{gcK+#%jv+9k^1(r{$R;(kIoYGOlcop0tuO3<Fp} zss7XQqhV6V>QfWfJOI-$>;hO(sTHt?Nu8@t{ao1|KWSxVAf-y*QC8{(>=9Cz>eEcu zJOJNf5CE{M(g0wOkh)eE(`3fDR-dZ7DHuQL|As*%s*f)|`Iv7J@w_e|xcpRnX!e5J z)=@@Mc*l=Ict=K1_%$|e%cPH(wDuj*1@-93*D`N=u^_%-(m{95!nw)m57dssTUS|2 z2%W1484chkp9;49;b)%;j{M;lp9-%0;a8tHFlFIr-5d~EIEEtQ@P~FePc$+?e$iAk zGC05J2Q)Hme$gp3GIM^>Q#3Lceo-29vLOEYIYXhvMVb3T;ti61IP|kEP65G9$Ar$# z$G6dmw&%&)JLWbR06gFEUqaT$C)raDG$1PpWI2!k0q1W)%nk?QQK<@alwd(Td_@(d zy6e3&f`XeSZizSiTL_CL=r{SWm|cTIrY%@M_it4^R6v!>zjXcp29!bp;tm7{2)$JE zZQvIJ8HSK(Dgzmrkmv^nG8Q4xDF!k@A<<I?GB`--$h3vdl*g4j!cFTCT{dCMpFRg# z>x#bxSxjU#LiHaF0~fR3kU-aH(#uGoyEN&|B+yeDUj+f>#l>CvA*}wWM;H*|6~4gW zFwErkj*l<zFD`1iSvvnNdgX-yiQeV(hsSht*xZB1b#pk}gC}%zINgIMbr<|bBKafL z)}T;3+VmgfWIu#Oe{r%c*&UF!;5%#9M<Nv>)mkHw%8_c_k;n&;Y6Fo-%}BNJNTg1r zngAZOnl^ow9{PbceUl#AL7RR-5B)-$PQU;irk$A+AtstP-d|jtqaY&ct%)V6te*RG z`)?d29I6o0(9O)1qR}^dQv?KiRAM;)-qu9}CHWS!n`xjbbm@IGP%S!N1wctKWo3S1 z(G;fL(=KNyPWD4o)Ki>nPgFEjoD4%u^n*AVnb=vl{nZlUv2TV3QTMlr(<KCBs<5`e z+vged5v7$EUpgAjB(hNA{+A9-33>Q|;vZ^gA2swl-Lu21TG`s;EbO$FYhLgZzt$nv zJEA<o-tP@_46`C*J4h#1e8g;z+)Q$&csxR^D`zRvT3qyPk9v(kJ`U+8QvJ$bc>N|{ zDbnuo<UQ;9<^ZZ^tNaxo9NSk3lIA20BE>yj>ATclN*6&+*c32oNw_IK*jK@`mJ`By zcX4|O(IX#2(nl&t%EtB_`4|g2Odm@h3@Te$G|u^8$SmyeSy4)<M>Qs9ko1e4Cp2y6 zD&t}8CN`9mf!SXUuBg<5h#?svZF~=7&Szb^s~o<DYluhYA_4acKxRk9Ff%b1vs$7l zhp*#`T#(mDz%MV5RZ*JqFy?aBv38B{jqpu^j|BbdY<)wUJXtwY6Tfl#jj($*ap%5d zl1!6|wH?O?=Xg(%n@hq8_mQ(vnxCbZ8(3YBz2GiNv)z0H&|!w0dM~)U(rVNZ@^@6j zX|Kl6=7yA9ZRI{zf9n9b@Hwse&}N+w_@4CuR=>$Da$#@d2Sb}~0`j%10rb%g(h$YW zG)~i%_+WukR6l+B(}Tm!S@fH%GJ8R!b~1OV?ejR7JiC^r?%t)2w^><2Jjl$z=to`V zVsL4=SPo~N7m{`pT75Hz$Qxdv#ArtwXgw$?F7Ej$FgTtEo&L>Z=;Gp%DjI~dQruay z{21vIt+^x3yvWLddiq`Np;&l;r3F~8Fd&6$I}G5?0^HGJLO3U=#l=5#w|A)c6SHjO zMsEV;J9HrLl7{}x!An|CMuDvnOd|F9r|lhUcNcGfkrWU-nzKPb)O$ZB=8RO3oUP06 zfb^S?I2Y)y-%1Y#RIwqPVfjJ9qrM}!!jdow`Zrw+dHzTrT<9(%Gb<c^rNlTTjM0Bq z4}mfXNTyNHCm1s`W3pKn)&&LE{xs^4qq#w5E(<3LLDI*>++$|OWwTUKR>pWa^`UZc zadT1M*;#Lit5X&(6M__uiP2(aCd!Mp1@EN~tM&oGweECmLC6b2Xe^Vj!ag=p?_LZE z9@(B0TV4?I0Wq|iiFraEep{I__NKG*IHws~FtUskI?TlEqyQIHW*qj0KvvfE<(|9Q z-VxSkgrwD7GcmDQHYqFPLQij#vB2RnuaOZkF~cm(Y<bZ|f*o=p^lV*7q&5+m3*3C% zR0S6gdRLSz4h~O>L>9%w*swD5uvtzk<Kja@7}@edkUsR#XUx6|J2&VP>R6fi*=_{H z#88v%Z~v6tcMKUSOR+|VBBfZM#ekF6AvdK+<|2O<egm}2=$np?DM>PMaqsu&5Stw^ zf$f5sfpUlJFCl3~bLHXl|A79T_m_}~2G~%*_JTlOPe_UzG&gp1{HP-!q6C9Pb07sN z9?NA?2=XPcA-_UYly5*&&B$2py?wzK9Q+B)3cMy?7qbZjpw%o|&0wnetE824)nEA} z^t?vFxKvO{o0!`pN|ceZwR-e>_rTj9SJ>ebvsqrWPNeK#UP=!&pKlJ8eM6?iRyjfL zvYIUr1u1s6GO$G|d^i^$;{Czuz;>fPmPC}y{haA3+vbtntTJPD<||SL2>rWj$V*nU zYho6qv+#p}V0SELSGG-D__{LVZmdTC5iYX_+a@XexAG(_aw)b-33r&)cd3HZ)}32Y z+;)OwM5(iaxhuNs2g2Rid05DiM0P~U$KBS19!lKrw?`=65dIaHd5yJ6F?;IP@5RNp zhf(x#GH+{xgYO@fW`8)-9oiK4bRKf#LdGKJG?iJQO*te=BT5JA9dhTnB+6v3abGLM zRaxN0vYCCpA*j+Bbqg8S_2ZC;Xj(qRAg(G5?*p5!!UPTdyD+>jY-Yq1Z&f;Pa$Ck9 zkY?c1Kfbg@a~_i6>551C>h*9!&&x<O$&L(}bJ#X(;R-5~HX#vl23d?WX#Eur{_qk| zyjv)q4vJTd;_0AxWpP!5cxLQoSrj9xoj_qCipYZ^8jzK4kkCIC1EbnFbuoL+5SQ;I zh$)FG3NXbR(y$j7?I}T%dNO}t+uVb@s!aY0*^H|y#Q)BoQ<Q`}h^zXFzsp|jl?8!p zox=lA*;w&I&y&bl9(1<eu}nek8IZvrzyo=qw>WYXl91H#nr!#sQ7T;V?94rEH+th@ zEXj^in0wha1>}=dRt;64W*j+%tn7KINMoCDA!yS;j17t~gd)6ES-q<ajpZ<#pb=E- ztPnz`0g|!^AdxtbebX32gKWvmSE)>fg<8fJ`4!=a+uG39sJ%U_rd>o3W-+84GT{DD zgI5bdPdRdIXxXveAmePw$kgG3w^YN$q0+@%%s<%<%HASl;xz-*;Jqp*Yx3cDp)fz( zyPRg98G_V12kGD=Dkp*R;Sx~TGh9PX@RRY(n`{?-%y}6|MhRp?d{q;%CnxAm2>n5D zc+3kjcQlqgwoNpJWtB<v@S*rBOp;<wGYFHpM&}?Ud|Tz@I~-RA+JqJBL*|!8@>Zj> zRn9UC=^Km-gTi3AYn<N;(V4H<E)E!%vXEIv!DtD;fBMA)konCMvuJh_U&!I8p1g$N z%0Xd!kXu~eZLXMb*f-r2h*VFCVYqN8><FU5MOs1WJmjZrnS*4+M)D_A$&=Y}nT4_a z(Cj2qS;|2&#NjGHVOx-wT;D%mGLf-wzE$8*m4Yom=zaX!iN#MYkc`g1Pf1~mi~B-; z?Hwy&;E3!&jZ<_;7UMo9kqm4FLVs~62OBDLvLcNp^E)Em)9So>fR%^5OjTBfHVKf% zf`{qoRqU=hY21j?#haXm`}?c`K#>hvVDKO@ItFB`ML}D2()(pmLe(gl;VrX5_D=0i z_4|4G$c&`$`%p=~*km$yBNl%4&36jss*`pvdr-tw6cPTgvsE>(0C^b`t_E$Qj7=wV z=VK9P-&9s`QJwS;UrnfbPcn7O%!#@4Vdp$NuMpW160QOD#*fV+b0=ewW#9ZIAEY|@ zH4Hbgs*Q+-+l-1HuG4v2Jg*2T?G&yB^`4K(C3A0JR%YLHmXA}N{2t1cSoM+6kUPg_ zjSX3hWN=h|2$h^ADI`0pW!7Lnkjg;HCRSw;zT`Gzq-D|Vlo7TpLHgR_>O#+lVoJ!4 za+vklH|6CkR40!^5Q$Yg_+Plq7O39pq8^k|q_GLEKD4Phrh*JSY$oiRYw%Xp$&8SI z#41UAD9^JO?(K5kMKRT6?gGq~?3*I+URCfqOG&I^#l6d8rbH5?_v5Op${aQH-m&5u z@_^T2r(UNcV_rFOPA2>j)Z07e{fLqtcmvDmvOLS5+6wMTw0=)4sGrs%jcaVD>Y}1- zYzJPg?x?KUM0a05`xzuDqi1)x_3tG_?KNbIN9`FO^gB=g6=Pnd@VP%p+sNj%+?6WG zLgxh_TNMiI7^#^52qH=1%Arl8@}W)DqM=RC%AvBh5Yn!Zgub|#u91V)L?l;|btP$y zL0XF<u7%wZEpuf4lmk*61@0N7W!%HHv~#Cqj?KUImYq2cmGuCX1vdnIWh*;XE0u67 zJ9kFr<oqdLBqFH*k%Y8NDzHpK+9nm)CLy0B6>udXos$Z9laQ`S1)GV;7fDGM3LOOf zeh5UUHH?X0{`ujDY|s7DY(sAhxKY}rma?6lyF7D2{?s}0O_KEvd5}>Wiw;?wQQDkl zxIHSX6qQw#Q~*g~%c~RqRm9d+CoGu}GcmI13P;?p9fiK+>sRMgs7HZQBQ-Xl&Aw)K z#0U2~a&^_eYksjb_Qd~2&?6&1zfM+R80-gW8u*!fnzYLpRFZ&7Qb1x16%uYDv$myd zi&-4mOo1csgBdJmYNJKAR^KDgx^f$%-((I1?Tz@t-fmR3M@BMtCml0EsJgZ63HuKb zysI<s*M7whyxo8M4qKyeQ!~lh2mlN2*9PO~-0mm3o!2OAmCx4I_%5B18jol?BLtCb z?Lir5oHl0|?rx{5snX+acjUl4n;-oIgnpHd&O=1Emzsnn8%TbSY%an(QHn_K07aI~ zC~$9Na{%6ZzjhE$;7-2`&r-86m|G4;HcQ|m_iHWiwC?mr-Lh;EmMkDSLU}fg0-vG4 zr9a@wNWqFtIlPv!9@ruf(wA)g`3BrH&G><`FKQ?p<xd$RXOpdMNP|ql*L#)y?2bs8 z&+?~Ok(<fZ(IlOwX$*48{&ryIzsjE?MP4LZI}u|bz|d9>usb?p!YG)U2_{GZAp8#) zhyrm?AUz5ULZtahDhJseZ86~$OvMNDrdVqbbt2Lj#Fc~X+!vXM3Z?>srBbZ#5Mr36 zUGga-?e_O`U*vf1f5<*PU26tWOQcSUwJDytS(@*yo)EjEd?xCGsgNMc6l*HHw`OU^ zTs@(7?m0~KD9|;<8V3DgHhpoSY-Z~Yg7Bk2Cgy^vpMl}P6dXbGv^i4Tmv-(-OzZ_y z41p;r)*e_c=4lzkxUcNoWteUiOq~W4rC6_E*8Blq+a29y;wzY131~>M{*19_p2mQW z8)0`u!6Z;Hr614(%t9BmNb|+UjYNSWC~y)5y8Hp7P~cq@xC(4Vudzro#=?!Zb0=kz zF8FXBeUh@g4ci0f0bb+A*ll02$`yQ=h{jD_Hh~FRrb(XT#@cPaWW87LfisFabvaYq z#WKzN2X36*wj--b!H1?uuGHmi(Ha0w;l|r-KV(%a_@EIfp1Ld}vS*p*HALen=xaIJ zQ*0=`8X=WhaMM*W^T9)63094QVn{|T=jd4QKu^5gy132K7h$(ILZFPq04Srzu!5pT zJx9?$-$l`XzCqES{Wtxr^ACL<MJIAX(Zj@0^pEK%`uBdIy)-M7h^WGkUP8P<21d+~ zb}8%X?0iT*+|jYF^foy7+n@pjQp)&>h^XXGp^~ile~XM6{}TUFwf@_B>C3xyu#zg> zfY6tLdAG_7r$g@09%Z>#9m<rJX_P4gPf(`Vh@wmpLYY!hicMdZ`#Bb6l^d{Xja7n3 z+`t&D#EvdB&*>B|N*?Yn^AHm`w(c!1{w0(EK^F>;fxiWl#D5ARxI~Vba?Y9=pFxH7 zr?f%A>6O&t4W7C{&}aW!m|y||AwVXm#HlkGocX0B7ZEQ+0)xMQ!y*b@!KWt}q!KSO zu>{uQS28$jMvHy{E%rTwk)A;Ml-5&_uX#T>_|NUm#Ui2sga^)G^X$7lI^-jWA47pt z1o{}_)L?;tK&=M8gGEK?grIm)rZvic4Iz~Og{df~Gx|^+tq$7n;Lns3{g_`{qz1tz z>|(FyA&BBh{ufW!jT6<Zab|)bJxfG2?qx5kas8}N%G>@FGW}Di@{e+Tpj`Nn2UX~w zbjuV}p>33p5C7Mi8skxQ%zgPU`mGf<VBY_p7L%s|C*3E&CSH>s*g31iBiaV>y0eEB z*mMK2_pEW);^KA*Cd5Pk2B3g;oIAt>6tK<b@iZWK{o5bY|7RFI{cH1&9q^j4#EbqK zPOp@K3zDc6|94Lagm{oh`~?UZIzHB765Y(icmDkwEDLX<#?N=K^tfrV19tXAK-*1! zKLvYcy59JJu7L*1-2#GoN5@A=G$4Ql-ADbKU{?<_AUF&x4qv=z3I1Df3l8=G!{f!V zPaxRKdI<Ki-0+CzTev$qn!o^iaomi`2POUpU@7@~8*D`ERA4!4&Ml$Z(9s3e<YIpw z-}FC^uiBr-mjm^*TPazhdMg?9miuAUf2VG*0CLuo6YCSTdd8$-t-mAA`tP*xk8Jw? zCvEdm7ZN^~u(6f7kR0OGV*xs0;C=D0)LlU_v#$d69{JDI&k=?Ee@rtUr*cA$mwMl1 zPbJ=!tL1qfWIgd6tH0tS2m-={gkKl-#81WZJP)<jUnclB+O%$M>wXD#tfb9Ct#_ax z5a7IFJO2&`L<7lOLz~8LWotcNV)R$cWAx{g@C?oRSlT*{JkB=sw?cV=db3(<H2Fi) zJ~=LZ+5F;Q1E%#1pv0Zh*1iAE&}JI!ZiCGP*gBMX%bG<$TkCMOw3unU5A58;LuKio zeCd#G>eL|Go}%z`aUQGyyI0=s(jd}XH_n;*_9$m6(J*Jq8*C;{*#Ehfbt!anrkZTQ z?qVv~L6q0Cbq~?E)sqHxOvJ;$=sPM~znA%d(GYC=^tWO5m&gOV*6YCtI3Uf!PxD}; zNh1cAt{Z5KZ@Gj2@6t8a@mlk)&&AYWD@W7#9E>$%;@jZGHTCWD9}WMVuCdAx32%6l zp{hPaRUNxaco&R2w`<h6`@f}U%C=Z(`oO9`+d#KtIBR(h*}*FyTA5h<#VEIIX5Z}{ zIWQppO8xd5(I8GHREvxK8yNHnl}y0(<UeVf=N19c)lT#k@qeBik-hDf48uf2n*?Vf zv;1#g?)b-%$i_~zk|e$>`VpP@%9c*Hy2UM8rA;Ktwx=Vvi+afgn`#l?99U=4ZO=D$ z1Q>G|6^`Ar5Y_{`sX`WF>B+El!fmC8w9#k5b`sh1FUGIom1ClGYDP~|t@Rx9C|{j% z*u+Jt?t>$dSuDg7m?=fn^I7>?6n-(lss$zME%ElK#5G}7JA%~~$ljU}L2?t?t=|*d z5p&n*0{xNFT|!?wpQ~?p4W689b>dVQN9NXN8eNLI!F*m2zk2<P^pmO<nLPvhUq>S6 zIlX)DV(e-0sNzzLMS<L`ZTK<xH(GXM@*7@zq04_Ai9F8rp}upxBfnEHUMR#rwCYib z+P%~v%e%?jvc#-X(2Q%Vzm7x>)69SVj$FsPK3~%F1<V*6iJUMZ?NGmJZBm}thj#O8 zM4bg$lsofsy-i`LoU+#qxIE@rOVj%<vQy_H@<(B~EiW4jN24Mb1e1(9A(tu3x6x)u zjN?0nH=#7f?-pn6)U|i)-v7P{lMPqQs#bhwT|!TlxE_FBci{H!MG7C)4GqRMhc|4y zuDdT@8;M8qE_)#2eyOXF<nr*mt7N9fikBPlM`PKJ?9TmUp$VhsD+m_O|IV9}viz)u z`Y`#Yc+RO{>fnf+LV{oxFOjv0;OVCy7zz1-@!$tX5+&bVh3`t>>fC+$iQJ-(=Khl# z(8saHmr6o9icUwD7%rj*NtSZcCq&YZUe0wKU5Xl4y;+&*7_rd`n|IS<I}i&Y%?lv5 zLSN4j>~=4*c|)8ZPd%9~Hn-jHcD$vQY@5omH?0)BHh1l#kQS+Erlz7*PjO_$+*xB5 z@SV~(Q@X3AiIGfT{^j)dNbw)ecMiHe%O2gTDIfEJA04L(TXKZ;k!n|bG>f_%NE0cO z8Q0+cWmS1Jt0Za#Td*0VUg^zJC?EM^FW5vEK^Li5fP5bI8-2X7FLVW?65BmD`<v>I zVZ(BxDBa<L4Gm$DG~(0WAMge>OBq_m>IQEyMMYLJt^W+sdjz|GfqkAk%tK0WNd&&( z;doRT_NY1hW{2+q;|Xp#I1qVjbq94I@@3%BZ04T>k+b^s$|_A*wc$?O)u(sj?ld^V zO+~J-|MT%HozELxyMcyQX@`c!@PB&zoGlz(U)*-~+%kUpd`2<us=`!&%3pjfYTVp9 zps8yrPd%3Xk>Mj36_;O+Z&Tp2$Uz-m4WVaKY*j9x%L-?%-5lz*^7VGQjl_WnNnh?A zj1npbW=Z2;Yrz;=B<|`usS`!3--#O2a2g=G&Qn~Lnei;R?Ih%;w=8zbZCvuvJhQl2 z+v#=N(pRhX)`hup{ZroZ*V_F_p7kZ*UqPl8){-;J_9Q%bOs%tOt)68imKYRCFpc)Z z`q%A~=53fn@8~6-8_iQ$oqvMr?Yyd?%StpfvU%=yIjNfPDBp^8<7)V++oRICkJ9;V z=i84sS;T$8sbI<L&8d%y_se>F2dA2Am#PfDS^2fNTW{-m-<b{9b{gG__e#Fw)O(>W zt*`#k=v#=_vB3gmY2@XA?$?XP2VXyTJG>VBs-w%h4k?^GUavMNHEh(j5*;j6Z+;Gj z!?z3Kdu!th6#G8AlLY1Z&u!Fm-7*qAbay`L7w&vZFLOyN(Vybx$q?BYpZMytOxgCu zW~!uD$Ef(+x~!2w%59d*rCLjYflA$HV{S(`scEBsKC8CIg|&Bc!|#1$*Gre`d9@8i zf2#g->+hKl&$RfAg?s&e`3+$Q=RZ%ZG^&0-aJ=0Gbq$pfHz?kCP+t0^+C=>gmYDvf zi_7WR=4HNw_+_@=U2zyp9DHxBC4EC`tD^D5EpD&L8`AN?MK5u!#LxAWB)U`d0I_Kk z1q7DSeD}k}kgdOui>H_Cvz(~Gfp39c9?pKAE+<$09Ur`ouP)}MJ`J~OQyGpoH9WKK z`LgsQgxphKTw|bCqCZ*J<_tnL-+Qa!P3g?R^W{#duP$L+iH$ct_+O|AJf<v7A2)e# z>AZT(ZCa$Or7<R-D!D0s@N!k+mb?!q(LwFyOVxMj2^BJY#<;g<_kQ|(S75yJ`t6&d ztXCzD1+CJ~4y?Iu2ajizc@D2SBO@-glnU=;DRU3HyHi>{D_sA4KAZaM?Ree*U0B4+ z#>WeH7&_-G-_%P=`^`Q}PhL}NR891hX;QndYjs^cpeJPW+-r{4cGIMMry_$YL(Nd4 zNw>C7uDEaee)1Qd<YCV_YedubFuO^{+>KwkGZxSE+nk3DHZM-+AJDChrrtl^oG`k~ zcK3K(P&#M(w(;Wu=QVcNRj*(hm+6k0#D2!NXObhnM%Pl?(T-_bkf)Mx)3w<nA(1|h z6_slWSiGO)j0XRu{#=&9wZd_-`LkQ0Ozs$KOqeQ9X49_^J9q0oUV0N@E_)29EXL0! z9t;;E*nNcs5~v>%+^$f&$X-MctTcv^blkx}xN_zNto^E59UWJ>Rv~XBr@Xv=4oMl@ zV^AUsUrXu{MC)>9!(fJDv9B2hiM|RD;IFi}*2dswhx<c&iUxfddM_(|FFd|pDlf^r z_FhuzKTxspf`D&rEZGyv$qjmN;&S59scO#^@@G&}zx2ABN4Bovh8}C@%`E@zq-ptT zfsugan8x3}?T3T-=bP<kE0oqXq^i<Spj-_CKA&7FeNCC)JZQUL<z)6wK3QVr;kJYS zU2=9i!r`w)#5QkVS8WOSNt;u=Iucow?PVx=IX%IQzELE@((zU+9Sgbo^%bl;Yt{bC z5$~Bmp9UJ-w&u{r2#s#b7LB@Z{{j1$)NW#gSGDm$IkBH2)1+HhtCn60{kc#i<1Tgk z9Ug0zB!?J;EcrMwXX|N1;2NUkVDTycLcn)gnP;YH1%dSZO@yn8SPt1^Mlc^-#28;s z%$_?hjvAL?iXJtoYsO)J@}k^tp@o`Np;bYCp}t2wO8AB<%2nC0eAsu**L;~6S6Dc~ z@1+!})nz!7&3Pv+g9saw&4LG>ao7zg%iv;eYy{X=Us<{&Q=qXn@y`DKp{pQ{V-y_f zdiy2mQxybXApfokv^XM#@nPs8EXA;|bi7S9N5f}SRR7+A;UZVLri?^Zr=^@O@2CLN zXIvP<YWS*k`n^tO{3)$eFg4=k7YW6r^QCHKVOX`si7U%Xu}~eWmoN1ko@JE=LJVed z<+1}wx15-Bov_iI;X)_xX5Y6Gk+zdPB(PgJW5s2|aNnE|5vI}N4jxct9>4Lkl6^Yo zvmqs$!EV)S>DBhtuk;gWe7v*JCy|?7$^jynlmm6qEJhW+cOnH~GMg*`)tiW6n~qN! z8xx~kOZJMq4fLagKdH}yxw05K<n{>U8Mp%iKdR+w4pv@bF^Bbp8B^a<Y#aY9YT-xL zIl#Wp+D^TpePDPh%fy~jnQ&(qcVB2c(r2`ag435joqwF*x9iQo5?T&hewsiw=D<&1 z;TScA+STIB5l?-%`fM&p<WlQf;|<$h5irpSS@z&6fZwv~vt_;;|0?askbUydaAAKb zscVpxweNkW&sOPEU$&~%pY#vp)93^9r=xGsQk-+xl7#aG?q)=A3yjy0+%al0l+iv; zB`<5!8j|f|7NwqaiLs-$N~)!Ntl)=VFRS3Iml8eCp;_(T88vaKoDxkF*53ZciZ_z~ z<7v2wrBnUA%}2CS5m~fy<0H)dtXZe53*OMayc_aWYd71w*j`TJ)~4*D8Cx;n3lM~= z1V68uc`4g)XwlUu^HT_Ri)o{^k7?tpO<7UL{k~Jhfbk&z9-k>5oM!`7a;#hm_UaN{ z&bN9lCJ%FpCjFU$VUwLVtn)iMIeF+Qwd48=BOk!voT~zQJ7u<O(2vj_1*6fo+xJK4 zxG*#oy6vy&D_Lla7Vb^5R@+o9L4J@EGDj(dv!*!t_E-PTaf;R6e6h`Hff(ceG<0gf zKmt0dPoaQXF@BdIPM&~B0N#-C+x(Tig{8`A^|`N9RS!<Qmi#LfC~vZ%B`bAT{MM~O zN?Mq<_GXH)(b(mpB}U<qfT(*^hbn@?+6v3VERNr5?KAn@V60-K=Pe(Vixl~*ssMBq zGA0`EVd`$5a<t+**~*X}a~8zg@0ZDWG8;~2zN0_B*iL+Y{Q2$RO@<yn*1eXn$Wm}7 zYMB{d1p(uISso({95zdwg7nN6u%Gw6$^;pI)S5G#y?jbS+});%@se2Jc8!9$M?bt0 z?0aVM!Ln3+7`W2hm%<*`_)N;^lD?^)WZ8Wh0bf?C;-_@&?<>R=B%qEpl-vK}H1B&m zL+aGSJ5JN*mj%hD(fhHwNeIR18>1Tp0<UGJMfAqil+p862f8iuwsJDF#@ZvpW5~sA zPPz!pi8@x}Etd?s$+{PUGBmZUiCw@L5O`rNdpNF?kyrA>YpRfRvOPN{j;-rN|L4sW zXala6y$>w=HUG1e)9^<J7n;Kg+pIVQnpuai)JW^jj>^kLb04d1a+jFi$-qGQ^;(2X z&}h&~?0zhX<@egoFaw+SQje-)a7k?)5F{#nCnbsqs_mQT4mn=Da<&fcA00kXT6p-J zG+;iuBPjQJv%A+335h8favGB|@aGRIEh@yxea9ad?pwB<dl7ETfbRce-v%QkLXK%3 zDk#coX}(^qqb4hk*=EgknESG?Eq|NSTm`FD4ZaNd8rJ7EEkFrb7jl1YV<z~#%=JcU zyW@c-`bKRMcK;faFj=f)+X80*`wx>DT!PQ7=Dc+RKD_<x6Ex0#zp;wZcJMV@lwUj= ze|Y*RV~k=kP_3c)vg=u@xiW4<SRM7Oi_>|QBrb*Am+nHD?koeVn0s~MRKJ=6>1dv~ z8Jsd1#X#@jn)PNotzpIXp@kX=C^95PNNvfz=`k<#9i+ZSCZd`9r471t;ZdoCCTCf; zJ`t?boat<!8ZW%mE<MzLnkpnZ;A#=tlW>|55RJv%DJ1V^dM;7BPIK|@3cg8hkR4lQ zFcbWk7Ag|7CL5POpcP$8Jrpuxa1h%>Eoa!{FcL{h%Mr4>Qspx?#37KyM2N#|c6nb} zq)&gu$54}P|LcXh>F0nFX2Q=ztn}w%T|Sxxp$$$De-bHD<RBV>K>_~@r7IWAOr>=T z&%mdaSTXHA_KX(15E?07v;ar?-)uKO{4&dbv%U7tRFQD==6UDZo7m@^jQ*-Y?;fyP zOd^7-op{$H0$*P?#CXM>2B$~W32VqX#k^?4*$jq+vJS;XDlk4WWlb49d1mt-Nl;DO z{MjJQ(VayA{gQH%ZMPzZvPN0OB>(>03iYADO<Bh7E370}Q8uiVY^`q22QD{iz3_Fa z1{Jb@VBtgLvxQBQqH$Ti642krTT_`rGFjJCNv_2#xGqM`3b78JO}5nfw1=#?Zm5b? zy$A~@b|UY|?J~W6w->L_li~iIH@fskFJEby5bM9c^1@`HWSpip)%neIxx0(IPc5`? zBDRbR6@@Dx2ek~3XiUov?4=xKHlAAs7MR;0@SFYRUi=(?F_30L>AQrbjgtyK$z8U% zMG(~Mkn;XY+d-2@5dEAm=|<6`AA|3G@dF##`5k>34C)1?7L7$iox+_YgQ9(6yOyrM zgb=hN4Pa@L&2~d?4x-Utq@1&ntH>XFX@@CZlG!=RJ^%TUOze|pH_L4SWxDh06MpO= zlL$nW*+)TSk1&7OFKn0CkT0}oq2X4f3R)>tv6Df+M0k8`Wn}i??&haA6)+?gPPRK2 zS#%7@%#<Rf7@^c%tt3g!IA{Uy$=EE**6!`^#^LN>N8dR@>ma*MIJ!D!L>Htv6@Zw_ z$|u+jp;gtPNeR}6`0~AAT2<k7R_v0%_)@0zj`=PrQ!ie5$|-3C=TD;?`XpO9SZ-=H zEGtfH{Orz6y@-gMPZ4)wHN2}fJ`!}M-FwofnIlgkh!%(CiV!9X6vjMfdG}8ChD+{Y zWPa((p3Cp+J(Xhov;<cPcprE|JC!cFX_*Pt80-_n_py9R&F+p!{(NL@Ofae}$Q-jA zap@I&2s1?gWUroOa7>6Lc63<Sq40vh35%tzhz3{q2ixwTMcXVQuPTZ_`s4U&JZ&B~ z?ahuTqXyi$xD)O*nU`b&fibdP=4B#M&pPXq#s&MQoH#TO!rnrk65lttc;ExGXeDp9 zSP;UZJI$rXw60lb&90<&$a{u)uDB@1g4gwwpsA~MfNmw?;Nm6Y<>YZKX$9h-rzoQH z^W(78QgnDtmo3b>n@ZX4o&>8r%LKDncl@>g)8S-BR|j;&Tam5ctnjGzk7;Jz_Y!b> zQgMFV>#gDlBjPR|C%(<hjFZTp%c^yf`2y>a$mUZ6NvhyD5Fa&>K4Hk3KUI(!`@&pg zu({DL;vaNs^n}#4bMgV5_8{ow6P96Vc1m|Pme@(<n|qN@YtC4eXk_iMhH5lF#Sj>V z$$pPH?0iLP?y|{Vgx-FtbDY22(WtSV=(Z3lWA$A=H*fg~t@mQ^Q;i?`Hc$8sd^==F zUNdY{1bVanrt@BwHJl3Yk2bOSFg-Pu$~Rh~4J$le<1VfFxSgK_DRn(H`1S4eI9OmV zs1HUJhCz~*DhCE&MT}!@?|$AHRKHW`FCl&YDN<@;YiM|F>Ri9Fa7D?fAh~id`nE|{ zi|1O!I+y*OHtCb&p}PK0@uTtY9ZQoT$&mSo7lkb{gOv6X{raBL8QXeOF4cvZ*G1~D zb;paRF6=^&3*EE|zuKkp&HV^Bex5tZ-O^-geP?>&rC*=^VPg96?kly&R#USoX?j_E z=i8B4rH96{+54t*#&>#`v747(oAu7Ft#(I`S|y$)ryWO)H+csK&b5!0KU`0>hlIY* zINFKHw;Ei%S75JKW*qR<ex&7UY$|h3JY(j&JxOfkv{=r_xyI+mlP7$Q+Sg;o2=Tzn z{Sf=vyb8t*y^|-#N&ODll@qV;?Te0=c*;LL_Zv4es<t%=+PFv?c+*WDa}-m|A>Lp5 z1XlhE;<j<s<S@@STUMI(ox{3crgRy*e2&Ba<Ep5HaAo<n;+xE4tE0Pj#&t_?b9vRQ zntW<9C=iQ`z5OoIJ^7bJy6_JAfOS8<$Bh0>F<ce>=TtAhJ^A3cP~wxW$Fw2wRd1lz zv!3t~R&Iz;<-OrNx88RpU#aHspTp|a*F)3wnP4VrX~{p-XTDPrvS747-=%Y>3r&Yv z>4BqJiEO7EtjP_iis`5fY!Fl<HJ+y)qFL_>;I0{F(IqZ7#<0qL@4NVJv^Mk)VV3vV zn`Vfk<?D(r=0B6uDmlwxLb{2=UzkbUAbjCKg$xrle$3=cQg^aWGCsI9FQY~q{H{BR z(pjCcP#4lUQ`UEsc*gFqFfZN2_o~GAc+dL%S$%e~NeW{c71{fFyIT!HF25fRTia|5 zcZ4r*P3X=)RvA_{8f(4Nq*B#%@UUQe{PymO=g(A*2|BihZ|dU~4)^vOtzt%gB)^>Q zbDZC7;dR_IEUl#Dc|DlgIM{nnOG0Cy<`Nx7l=EmWYFT=AIZDUnP$?iUtX}jNM>%{n z|9*JnWO~{5-t+fL=bs<n<uCQuTecsJU+nhFKD<f{X*&5iIUwI1sijnrN&H%A+>wv6 zKdUdldUmq7eunQP%=(kb#_n8Ik7H<p0GGfGk`+9LH_A|0-(@u&DZ5njXw^(;Hth&q zhNrRhS|J}TF~!QyB7!UP?P$5<$G)3ac&|R-Nm}x{ZY^N0NED=l6UTg?OJ)?hN!-}o zcxympvX1+_uO$ZuuijbQW0`NyGrsX7(QB*N@jFsP+a@osRt`gNGSYX5K1sT5v$0HZ zVNdcCw@#1v-f8jrWTEdvX^tIn${>bEMKT8n1_i0)oXCo!F~$=Uxi+npwWfmHfQn5u zr48ea>LZ3dwH+}#)%ir+!Tsg>i0PlNvovlSI5bO+2s#PwsPwN-$Nt)$m+7ZVDR~YL z7M&1!puc7u4_|oPEOWQ@jf8RQ+UAv*(g$^gOXWvDUYX4loQ3U%?SJ+?{X}Wrbic_z z01-phskE|#-u`-4;PF1O*g?5@?i$ST4EOV_*dbrP$<i+lk&O5vGzI-45YcVCH@e(~ zyxDBM*XHNZJaP4iVz^RCYxQiG`KNY~bm&C1u};3><yPC#NHlRKo3GSK*iLKhGF=)k zJ~mfDHRH^S2D??sWqUm1$ZG_KL@zX(rE!JK_&i;w!r!nzr1tO|)nhZ3+G3-~uXI1P zv{znheq(t@6rc58i!Lo7Lvx#mO6-=;!PV~-k2dMBYq5eLv^6c_kzm9tdPZq>?3(;| zJ_W|;ig(u!b2evhkS9q-tMJYgTAa$lD8Bmt*B<F}Rl8YJusQ0If{u3U|FB2;#LUsg z-0g{tnTws7<5MR~t0#Z<OV`rq@3~)A7INWxqtT`GmgSdPeywMt#(DJB0ORL9_olqt z3WVhq?jBIKuP5Y8NoM?#A0-Gj`q~jkzUey~oIPnuL-FFQ-0I!sMFf&gHVRhmZtKLW zT-@A_-~+>lEA!#a78Wn+>tzhu%uctjrhA=#hK_n1iRzu@(ur!kyq?*N9v2HOJfo}Y zm^%F4UF2?F<!)W)|GTrupX+Lc{Bk(<yQ;t2U}mX(q;FjoffyvWWA5_A>3i$+_YULB zZ}_{{kaPR@<>fz|4t?u#FNLJ190xmQh!%VnPxv5Fw;5%e50-zies*kLbAIZ-^&`{h zqL9JEvs$mI1bmQW4@D!W5E{02FfEsn)5zQ@U-h@e`T|qXAQK<^{Sa$>PC+Ano{rWp z^qtAb@7g@KhaU&+8t$E4VcV%mcxJY{5w^)ntl6L36C@LCPgdr9O4#<m$V2!7FHmK} zG)}#TpjFi<9{uW7yJbM7oj9B6sf3+c!8#!s`)RM(7iDEmC(;z3Uo&~+f}Cv}{4~c- zxDOQf9%00n?h_dzEdzGQQVuR<){qZ2A+#L6KFUMtHLbbP!n5C2KU*4Y&|V=ANNrCZ z3eM18MjXg34yrKn5&Fv+ZOHkn4AnbcP^__z{hq+$$h`_}S0nVl&!L;U5c|4fct?eb zEos5w#Vxtd$-*U^Uf{c12DB|%u4NOGC+KAFTX}ze#p1}2QbtI7{tUw{s~kTu!-&^{ z!4V9nCpMM=UO7>W?87mYJ`sI40~t9pRzKW?$w>?!&!zl+{K&)QSB?2Q@5=aVQdb{= zkjR;{%s!R*hF%;`%mKxD+pXZ{VGq$g#f>LRk>gW6bmfG5I8S245W&sp=S)rqE!0bd zFVdO=X_p8mnVi~3mk7teA5>X|`GEhSYQ&2T+3n%j$A~S0Kc^4=x=o90MyFdM6sPmV z6z!p_lA90k1%$^-d<Mrq2-c>HC9AZ=wl|)PF%&!$Gol;&zxev<xTe0paS;TmA&sQ8 zATb2#PLY<BbRs1k8{IKNS^+`2yFo|FDCw3OEy8FCX@7U0&-Zzr-}BG&+Fs|Ld(Qiu zcgJ?Odt+yCq1eJDbeS|jcdmraMHsET3>+)#buiP!vpG(k|3X;UX^R-!<p0-cjU4KH zz;r6My||RF1nBqLp>f}713VzGevPBHSjnOJwQ{H+Vzg5MfJjSMgw`g{$wNQ`1>uHb zXBIi-;6fD=X)#DW0P+wEoKT5zRERnMNX?G0o(NwNu(rO`i4mILNP^xhb?W>I(8f1y z=7Vl3acTevW)%R46x0zB{T_<eY*s%E_tq1+J6ebsR|QSS8a*xD_rMJEl+l^J)kR4T zc1Qg~i1M3%NF=qAMr-nrzsTP&Sty3Q(LqTfaWaPz0n(qUdzH~;Zsa+|EI8&maYo=@ zjwqvOJUP&so-I-@Mbx1A@eWgtt}VP%nftK(BOxgE@CNKk7o}N3XYnZpz|Mz;t;0CI zUks!!mC~uX#7)I&ZkGHruFNUj>O=^|&a%v#$V2c>4gW^es{dx=mU`JtIK3Bwm?Zy5 zh2AW2O8GIYi;`}0Qp>B8oZ?M8S|bwhDF$w#C9>i$WyUz|D4@<`E;0&U#Yjg&K>c%x z6JA7A&yIB=7w;6YsBSF27Sx1@+#^!~Dgjo9J%&o?pltV)pd#dG4x%r_=(A5n4JA%l z7$hk|?7e%e8sCo*gULmTJEnFoV$iU}BE@y}vITjR?Po5HDE`kD-Z|_Vb~>nSy8#b$ zQvug89mnfxhY0+YwLxqw8#UcaJrwUAl)TJc1@-0R9}bUOF@iH59S385F0aGv)JF3W zMTP9PNGG!}@=7Oji4$TU+`(o2sg07XZn-lJNIDA=(tcd(<e2tA^9i@cwjwmOZwFAj zd3w?>=rIvFA=E_A6U**J3L0S3ti*|WDwh952hw$f2)2Hz4ikVL>owD%t2n1L`Uic@ zn#s|8IaL`pX?M=#P?|*Ki@|fPfUu}vrxh5c<?N_W5oe7W{K%{V7Uw(p<->12SP851 zPy&3J0(?#E7kkK~LW=lCYCg1}K2RZXT#hj8PJRHk%0f43H<Kf~RnsT(f9!Pu9KsQk z`pw~p!8d6I<xbk8EC;~Nj-9fnP-8v8VR22l2+uOeT76gd8uWPL!_QJMS3KmdX!4KY zGO*mJcgez=G^2?RjBQSMP*ep0xr5;xaBYqUBYjP7^+g(vy)Xp+fy4S8ZcdCH8T~N3 z0|-K-x(hJpvu%uKCDEFOs0yJ%LNwp7(*Q#WLd}A{A5aPph=R!7HR5%2+0L0=8ig!i z;oZNKy*SGNjJsn(g)+$Qdr*ne9c#xh&m06p0hgK<>SyVWlkK!d+FP5?kS;ad-z6ET zMLbf_V>E*Bxvq~Mim!xD>VO9g%R#)F8rDI*u3meh?gdE3!5rWp>zAPV$@k!}r!Sh= z`{Yqn)5Ach=rK(DV50_#N4U~aTR@UDQL<8>=_9LRq-!&p**Q@=xZ=gh*sC#Dvq{*Z zKOS<}nk^+`LtP)OMsRDqVnlW)6>aD=_kiVg#GJzZlwzp#j!^Y6`*YpqG^Em`U@@uh zDGv2j(>2xupjLI7m7w{j^phWg+O(VbKx}N<6i}0i50c1&J80#dwNbEZQqhTB+K`Nf zm}j-G&3=)GENVMKRUB&4>QobG2RIyX*L8&dOV5(Y4;xO)%HjA`uENy|QINICohEHh zEc52%HCfJxC?$-0W!t>~Y_QiGrdveEar_6t8-c&0PsoGDZM|79=D`4ApVhL%RZyBE zJFtAj=@IZyXFhI?sE@Da|M51DH)*1|PBG{+RTNKkh7w9PrV8x(Chbm^Vfs@Pc#e{i zLvnhLO=E5X`1SK&zBOep^Ypqq)pQoy>qN;D&SA~kBK#Vk_A&h3>WMh3Y5Js3$fH_? z3d0eTlOMKwf>RJq9*KpbU?~l|BLvpykv^ehHQ*3{u>~FLr`aOI7E9QD)j+SA=vD{o z+3{SCTDS!E*NDlfr-tW~>b1>9%Bv*Ip!H1~E=1%p=uI6@?^eJ^*CG(m^rXc^C5FoZ zOw7wDMqp9@4G|=SS_Vzit#SrN&uYA)MO&BdyJ`Q?e5Ov2v!5aj;PPRYsBV!gTZsA* z+g|^WBgnlJ2!w`Z3w<VTUQ@$B%mo4$9NoKBreI;eAfWD7#ASl0%2>2y_Z2Awe7SQ6 z2KWh(08Y#9O{;nnC0}%8Bo!mt9&&(6T29IUzH-R<%S;T@5;DHGpv}R6x}y=4t(K*} zw`B|WpvT<m6U5{>J?-p$oKQ=z7<tqsuh>b|D_L|C4TmNsGGOy%A+QXmc>jKQtP$0t z{g6XK0b@!cUCn?swn=?%d-qK>Jc?Z-YUXP+HW1^d9mSB{Hm*5^RSPfaZA&q>H@YU< z$yh?SwOB%DyIO7h`XR@58J*)VB~*}_8Y(D;ALL+Pow10w6rWvJ;$$9<ACKTITfm1( z908fYQ3nXcfa*N!f#@P)&8-`bzjMw-2;>@dyBAsLGeB=88J1~xhV^~`Ugc5uq_m}w z#Gjq8{qZyqAiN`@w9v3T%rP9{ZQ{m_9%*EU7Xwa8EJ_V6b`H~8PO{A1KS{>+<bdXr zGfnN)i)vr7zqMM32ji#Z(HXWk7>LAw#k#6NWP(LjUU+ET&Wh;f5wT60gk#YS0VyRI ze?{GyvWXsYclNbY=Uf~ZOLBNoR?v!TG9IkDj7K6gNy?EjS$qco#sJ{DV+*Iz)qGnF z@&}7tj`S`12(rIIE{OUMuwdK^E3YMQKT`!&5sD44PKv4!wBT(F-p<SLy!@c#QLmZ( zqS$GWlw&&pAC8dI{R#ppibcm$cbSv`02H`ep@>uS7j;I>%m{CoYR{sTwUcQ>xb?nA z3y#@acf_l_g|0=OaYu+#))uUDaT^weJwE(|xB+0yArC0DHgTy_Q@$!>7O)LYWKNnz z?K3<_wa9xE>xvv4a#8aer*&*;YQ6L!M{UEku!cDh-`-1`Hw4W{?DhYUH9@_-(GO5n zLd9sJa!~34fRon)5gbs{MX6KV^ssnkMm~a9^#vo4jYJL>Wr1K@JY7iwW+rr`<7_t= zG86C=`?OHU)Mt@2QO5-TSkT~5_}MpUg3kT;I_WnXm-m&c-&4<$(hQ!`0e;{PS&)(i z7Q|#}#;??x3a1#0yR*nAZbnL|ic2zQC6p%Y+i}td$&Ro;S@$~4uagYqQE&J3Fx0gq zBcxJreJc20(NCKNS#%A)6c;<1{{F<RE9~oc7FR=yE=#9D*yrEiW-Ey}t<5HfkU!rS zTk_fqxY2y9?x{=suD{&`MFOIhR8><Hex~Z>VF;;X;*?U|&T9|zJkj$>N@~Vml1HV8 zY`DC@eifj4X-{P|MO-y_`T~-z`XXz3Z<>F+EW_0EARx+$+z)FiHc@Y?Qn}7$YM7gN zV>-3KIpo(X6dzg*wh}<k830%<;0oV;xia;jExAr+(;9p(IuWXch~t;dKd+Ob5p6Ua z?EJ)UGcCIE_D_h#KzcyB;Vo;qQx2rbt?nGBhO;_C^A77^bJcb<-^!ijXvC9gDNeLC zgVeLXJcWgw-FXNGhkn&R7+IbQd;L>2!O-R}9HGf-2Ggk@vc9@r=?_(kyVf)Hy@9s$ zzce24J$;PeHE3?G{BWz-%&ulfifZL4)EeJQ{@S?7L0vo_uj{i}CFE(anRONLS*eWw z)1Tbn!fJl5(SXmg2Yt+`xnw9xC_fRjKIud2B^kBqixjhvUdG*|lVk(~8`rPE099|{ zZzhV($$9%5g&#cHpKMm8`Hp{T;mPw=VYKM-?hEOoXV>yDNdqiE&Dnz!4?6j&GOgv! zzLL%US0>4y^6shOU&UammyG8VhN(fyYW5zSCM(<Y7$f_WCn%!k-tu~X!XcRdSPmuG zTp+1}OVzn3_dD*MxXHBg03aZAZ$rw~kZdPmM*fClfd?=XV-}+6xA8c0w-^5zG}*3H z&+{Kc-xhn3Gw1f<yelc|VLvw!PP!`8l74}IyJ_RGw+*hici&xS*216XWI!r=ftm+1 z9~qVb?u}9fDuCE5!H=njar~~3G^4Xl3DNd$Id4_O^zak@BUPXzUH@WOJ(WpsA&*^& z!3CJDn)$<iT~tLfP*HiRYN9vwaeo7?H>!2rT&~wV&X<@B%|WU^Wv26IzW~UcFks(_ z4}3s^`mv`4btfW!kc2l)ESiDF-9=)`<AxCx6rwDHs4Z_l8bO_j7#B@OTR+5PYwxMy zIUHvl)aNqbV*)00PVG&CYfGE}dUivKEh|d?W#ONo{Hkn$6;mQ$K6JRmResnm%TPc` z>jM=BumG?nF(9o$$bh;DSGSPs3EG?nja$7}vL;J}*jkgtlW;Soz!NXsX`;Y0hHV&u zz*ucS&8Y^IsM9DQxBsrBgc?q@fv1RtlF_*7D$r2aafwsTu`UV}jV&i7Mno>eR|%xw zW-VkvX=#Rsg$^JoLIps$ne8M?Z_;XDy2d3L(3Nr}=<!I_n?kNhVsbx79Ja+PC`xjA zSi*MzV2+XlC|o&vg5}%{MD~isj~es#!!fl7td=y(D$R&n-Dpb66WJ|~S~5>S<~t3p z>H?obC;@tPf9o}Cp`ErTi?{H&rvb5t=r+qBakj3B$UD7>$a5yAh^M|n?oPoWcfWXH zHV8Uyd&DWAo)nnq(u1P(G+eAKYFyioZsEk{A@*Euu^H7rBI?$4S(tzEH}0EpU?J>s zWJ&qpq@<(v6&-4hZrT9%Nn_(`$&8H5Ed4aDedD;YbB(Z(gf89bX#B>nbuh`PurO|$ z;0+VwLHvkgg{o+KX9o317H!o`)hn51n2$%52rbc%Dt#2_Dx+>$_hveG%fYC{=1VP$ z%wt@WmiC#Cfl)TsV=j&jq@MY3j$Z%!f#nR_mDMp=sCkLT`3~#jd4FFgF;|DLlXlQl zlR-RZ3BTju^=5iegx^*lUL60jkHf|65uLNR`GQ5u<6^mMTBpWC$+91u$>i$!-*AYF z$6Z2%i^h9#95wTY@#oW(O~@OhVw*U(CWH_|auSoaHX$%B@s|SqW9bB@x;dnCT=(d5 zGYwoqVYRuktU{*_B=2YUfu~KoU~zbfBV>nNzzpU&MWS1p$aitb?HKA3RZy`d(G(AS zQULc|I&yo5y2Kk)oKG~R2%pr!HM>Wrb4KBySZXaHl&4qEK&!*;I0eqrJ4XG}B<OH+ z<`&i9e!G1R(FoboH;Q>}$W@_HBV#a2_U%>?!Vq#J3o(n`Yx6nSXfH{I{V4Hee>B@j zkneh3+GR^0SOli+g92x!)j)?BGpr4H3i_E+(lftHrd~N^koo%Er>46hrO&uYM6qzG zY#*psju<4rerMNYjHA0#H_ee|{GpOPq?B?l?}ue}SO4jk%TdZdV>?IG1o=NK|4v8s zYSnb;(X=rJ7J_}GZjB+X^lED4TR|afU<5kRLtD<30=17LUht?ET2+;fDyb2TWT)KG zi00}k`ii*Ril^Y9uR~l;WKeQ^?nk(l2UOIEMzEibn^3U5?}|gz-zq~~?=zf!@6b<h zX-B_fgHuxV)l6l{s!&k$jp<YP=*eNGzgb4lDBTZ^KH(%FXZFx@4lD*gl(z|CFf^i< zH;mm23t4-RGL^bi_9$`xzFt8y7Y`^mKmDdHq+4Hm*}DZe+@GSp4($qynbVoPibpfE zS!y4jX%XbBZD&&|r%rrNCMdAqwxCe{G10;OeraKlApg%<2NzFVer0do>=(MeaRc(4 zv@cJnlGCA*$7oj?$7k42I@iGSXu`wR@;*qz^v6zQ72o?KS+jY;J3T&S<yy=5;JEy9 zT2N3FWq9Ln-Jq_?$|r|>L2q<Qw+w=#^b33askE*{sZ0wMvASz{O3TG?I8yX;q+3DS zIHArLo<XkQFCvfbcjtGMuPxwkIJuk-KVW-mNK+E=>rV7^IHjkEY;Eh|k#CMp_a(aQ zp$v)kB3n)@;hi1=Z0Bn{Swt7#-2$~;8LWwCFn(D?|1IGo|B%l>VFG;s>;EX}-HtuY zI{eq^_t?JdnGpId*&BL<O8X(h@q=~Z73NRTG|qRO6Xqh1s17+kCD1ryQzy<O^Vr$| zznY+0q*`ZKVOjDab1o({m2^alFEP3tb(ijVF^d!ul<au99;mWw|2SH^O@5bCwcaE3 zm{QKHFXke(?9ubJy*IaoPwyvrwWI}>f<oAq_%<!L!yZL71Jm5h|4zUB&otG4rX&8H zF8j~4+kd7R<r(;a#gW>Wt+`pIj)0w$?n7IFjXC5QobxV}T65z>NN=DwUjSy|+o@eF zdXo=OczQT=WvY?;c-->D(-ev%cRmJ};YadeQ$mDab4uRVlg^o5$%6+)KcIV#BTb}- zGb_t@NSedaiu*DOE#miRLwQy^nN*5EuY0Dm`G&qEvNAZ537fWWM%|j2!}d{_%50~i zBuEbz9_4knq~Db#niDJfsWZ+Ig=}I&_m?bO8kHHv*8`7<!|toL-@XcRHPyhmqVE*t zxjqY0qP@>B(cPDgN7FYYv^y?qn@GjC7IQ~Qk!C{e&U$2eT6-#1bR?L$`VPgmQ-fZS z?ISi-^7<2Q*+jH3(YS165^lr^t!ypsky14_g_w<cKOM04JWm9xm-N}uQS+VX$U`M( z?Dwo)mN-<lkGfR{!Lkaunujs;XEu*FR0#$Df@E{Gzy+QFk|fbUJRwkN>}Us`c16p^ z#0|-qXi?MZ=LmV=Co6xL?y&tb_q(q?pnQ*eREUD*9_uAdn%{%RhvY#o3m+fq2GuVd z$=m7BNQldOdyvlTQr}Uf>`Lv#QFxSIeJ9%XeR`et00okXxKRBY<t0_lg9L&rkP$s- z-k(HWvNQIEF82W!8r?}r9ImWGj;|#|L8f%?zKYNXcC2@>Rd~ci&>gRbIoHq<*`C~7 zA*T0t!?cY_lrQ8S$Kt*JO&av(YbniYaD@E(l}~thLKH9F;xFI22eCW?ZZ5C=?!n^J z`d?gH_>_>aN0@xTN-1H65XF3cr7?$-?c<Ls6W?@Ym9$sBA5F?y>D}LXnB!{1Yno(( zHIQ-NRSHip(o#r6`O$ipZ8jb^AEgoW`mVNXr7W*yPfo7h!<CdHHQAq9;4fYL*dJxO zz%kW#*k#z8WyN%7y3h4<^w{DpY1=%t5C=XRv#}x!ReRN$G!>bz_Ly41Xu_Q{#Xyg@ zUnVTtl+QRSP{X&j-j$wB9f8h^!>4%}J0`xI_`TCh^Pk+?1dn7GB%6JTngzBhO9M%n zOc{w&;dD=KVt>}Ro!<QI_#=V(_4}2;`y0gGDz=Jq7Lm_{iwDHt1<{OXm0qNOkL&p- zRU|C2ts_988Rgh5v-&o_<q~+l!qVpUx7Xf{GGvUWw@jx7B3H?QdzDqoa}v``d&_iE z52Yxj7^KLh=%lD`SbU^?n0*9%xO~KX*e|V4-kp?hHEx}5#cmC6Rc`HV{TO%7TUI_$ zJWxJRI#6L!WKw2QVp7Rg%vR1;%2u&Zv{1HCvQSx8TvlFIT2|>`vY=fSX%D>aJpX<E z?MPuA-WUHqP&mNjdiMP2i2TUI3@A_bn(5n)X7@N#)-6Qke!hQF<JSAc#`MicL36S7 zyXBvc@D0S)$xGaNk7g@AyWTSxIoB8aeAIoRF(iEvgvZHlo$c4Yp3?4ypOxU(9y)b+ ztY*9x_0#t!*%0GJSRb#?PqJ3Zi-bP6RyX{Jc)yW#anr9jgDR3}>{(L7{Xmi$4|s;{ z4fAU|5YjHl{xQ>q(Q`vS|9jsbXF?P~f;!TF$n$ZUa{YdXmXO8hNMDgx<J{)>U2@7y zK(3;G{eAf~w`M);t}Q2|GoG9_Ct3ZTLsQX-T+&6@I7gVQ$Tm%tk=pMive%|%2D0zx zo}{h_y^zZpipWPcw#!?2(e(W+uz$!D5NbI@-XXDlVc$8i->YR`ksmb|n>};i<qdSb z_!&=M)OhSOyh-i(#V9oyM|VyTtDk8p`Rlngo0IO%tY=w;!G$49A<)ND@^?fwNx=T$ zueVAP9U=bB>>_26M{4F@7Y{#^{WU`@zME+J;9p|uv{>RY&&k)EUSGM4^)a_JYPM_d zC=jtob9g0Nyr?|;TV9n1LhBNU&o!>Y+bn(eF;mYM%hzXwQlu*&22N#Yrs(s)Ag!fb z(AbQPY$<lbshBqUj-x+TgX(Zr!LmX0G8@Wik~hi8^9`1xKeo{D-J}4Z%Ptnb|KT1^ zOS^;>*0=Q3l;%(J58hdFgg@eS77TxM(yLA&cR%hEMMy*I@P8U@25SO2<0WeX$Qy4u zE*v}WjQ=#-UO?}k#tzf_pXUFY_;28UmThBBx4lOT9)#Wbq{@8%-qU3H2Lx|9!ojul zK`h~q*!tB8m<wdCk|;vP9tm>c<jPsUy#IF8HAz0f_cK6G!Xtnb?gs#&<j2+o%)28+ z4+_q}y7!+lI2S#jeOoW~^8QO`8i2wVz>_NqEPm+>oaPD-QG|H3QASXN2(@9hc?fJ1 zNd#=e7V$~mR`?|VXOYGg7S)y`TkxQu<8{#kjwt9ofFK53L4Y;{C2&h03Mybxe>5PP z2b6+bVN{f@sswV+G3VM8Lg6me(@H<D7XJ{<7)1PWFy|MakC@Et8Adgl|M`3OfB%z3 zRl#I6oBGb3h9Imv?ElAAS{pYjEBpVxLYrxyvC!)s(E;4_v#_boM2%TC1g~*nCWWG> z5mfs2hg69b7cWn(Id8M+KneoN-;+qW0R3?JVKy%Q^y?`z>`mV<(;op_%^`okbrjGX z`bSbfSfbFqyOAJqLAEn)8{~Os(d+y*_>1#chtb@wBjc9jigU#&9^Q!A7HH;rHMp`K z-O+Ttx|tI=!=&d!EpinK`RU=jevJt1`<NT}_<CpZ_RuSE*uvt~oJlD|g<nU<G~F5F zAyVcm-jhC`KUX`3U4LO9gZ(+9zU(r*C!E*Es1nq`xUNdv;<Om-Dk?v7os)z~%-~g} zL$zMl-)CLc3m*rT2BZTm*knDN)ylIxYu4UROY52>HzAEGpK&;P1`LaXJZr^MLU(FS zox6YMSM2!k$b7rB&XoLaEO@~+<H?(nZT-~#hG-|l@%O741nr=VWrm04h1d+@;ozv? zr_-CeJ7Ie(qjOmeZ!VN(R3%a@-eiI=tY+|&Eg*H&gVlBIa~u|ym9NCA1wZZmp{pRD z`IF&*H^&?h^L_feH(BfO+otcHmUEEbzUtOjHvv2?yIX#p#`O%>3EyikNq0htSNhbg ze<Arl4T)b+(v=$Bgq1q{k+!<}W?cWI&&v1aAzg{l&F9l^XX}I<^_GY1$*%+~YVDsH zbXfZi(*`2rd@61^4A#HVnZ-_@+r|cTp0Vt|IdS3XQ}H`d5T>p5iG_aKP%*M>lEY;X zVcZ&zH)5|K?`28dTe5UWu2774EW+429{*{m?5jWu6N2|rre|hP$e8ZPs2IF!Ok_fo zY)TQqY(k`R1$hwR8fyB8NP;kWl?pucsB9-9Q=Q0=H-a~UQ^JTM!Yl?NhytfMq7MIJ zBcoFnMAlz6Ag9$qhP-fI_;^$!vLzX5J-d4EXU*O9GY!)Q`X=BNcbOnY^fz@4e2vJZ z@7*7cMn>W%(ZhJ*$@EoP*I>MypEbbCWlBXt*)STJKzxdvZL<?K9oz(8_9Gz=JPAP9 zKUG2bF@E&QI}~vHA{V1vrz?($S0EGMrQ*+H!pH1F<CcWfE<Qxg(c`{%bEPq36C(=K z{0SdFT@*8oamSBn2>%Jz{7eC7E*5=nk)rEL<6kUFB-R7z=K?8cy3)7-cy;NO#V$nO zYrdq9_`C+*_y_hMkil=Ob5{`h6}xLNj}sq=dIcUl$Og|X5+&l<Tm^fjtbu(-e!<68 zAJX@79AB)aA>W&Ay3-WPKMbHL_60URzJN4f01!kn?{kgGWO#;BgD@CO>6O>XuE7Yt zZzTCU-oKDrVx#Wj86?HWtMg$<qL;uPD+7IyZzO~O-=(5F=Mayl3ZVQ0KYFU`8b<g% z86FoSuNdsi53BI;KTlm~iW+f1)Cp=vpkM1QyYPpxL%(Vg)F{^A_dp6+(Z~>{2!|W+ zcO_RNjF4sq*&9TOj5gQ7uxcETg1Ldr`>eqg@FgIMQPSg|WC^+#F4v#qAbozIou6dD zTGL((X}>nX#=y4^HG&_RlsP9|X#6+Qn<FfB<)wKx$Y!Njk0HDun_tK-Km1^%dp|t* zmtTNa-4~o+F&FeR$4JrO!8Cqm$zp|_|K>mv4`kW8=NEPKyr-=6n7!`i@NWA|MXFlR zNB2>73e_g(yHOYPe8*yD%wNMgA?xn=rarnq$=p|J4nJ$gAZr3+K9cOXerYxBA_Ap; zQrmJhvTqNQ?>*D#HZlTX-?_L31n}Ye>Qnb9c~R3yCgo+_VUs7oZo+UvZ_U(9HJWvJ zqiDrXI23psAwW1;O`V{hCR`)s=ri94{2?t>3eqRQC{4y8j;R+kGDfm9oZ1P}{D{%V zvegE1a09;A+Ner>e1x3c5E|WX^;)8}?BrW@`Sdkx$q)Y<$tW|R=TQ>EuZqkVl`UOE zf{}wl&cuJpy?>DvUabCHb7^9#(nx9BpuXyIqxqeO(TzNx&V4xW^WwYzD!8BhM3m_3 z`#+-ABdcZ-&b+?u-A*et=6vH>E@@`dzb}yy?p``KHJuRmYoKZK`|?2M{Y=bHIN_(} zlwnATwVT&W0L^cr<5d>X-5z@2Z_O70G<CHH&lIv`;HOK)f=eXSU6B641hup-NXg(J zU~LLn?=rK~kh|h$Mj*mt_LHUc2birPw}<pVSCq*6_Q3W2F?-k_QGhb<{bRU|w1s>> zdkvFvRkJ~-yLj6l(brZsqJc*IOj&-JaTW=i?p~m;IL4!gVyL{4)Ujiv&yL#t3$VB< z;C5A}dEFOe?3+k-U}g>cVrU&6emX;Ni1fJt8*81K*PHVNPyB0(X5NFn02MBuj%Nmj z;J?6uM)61=fE0SF%}UBOc)b{);gL<3sDRlIrd{t3Ch>R7sgl(0)AMzc^c}d?s5#65 zF0?A<V${!$14<RMkY546wfvDfw(dfD%>HO}ge3n|C;*roSa&&dN9>yB-2oBqbOPq} zK&GN^b@u|<=yeNjNqGyh39R;(69cStC>Zc6Vd>Pz8*Q6&qfEk^QgLMNfa%X&)KI~p zZupgcG_lkUOX-~u=G4ez2y8s<0M49Rds_nrkOL+R^4DqvUDIa)q^fOQKpfo4Oz&1w z?}}xveho8_uIPc39B0Z9d4Jnf^E&dBpPPnA!%t^|e;gwd295B{S8BvC<l+Djx5qQl zD~E4JOrCU;q^797y;{91S|MWb21HLa(-!u5&82mpy@d!S0*?*Ugs;WkFsd*I7<p-O zZjiO40wz971fs@}<nk#1P#WN=NABXV_KZ<T|FK1&(F%CuX}gx;bH3a@z?1MTufbDR zBY>2^Q(V6x{kj`SA9`Wy8wlqnSfVy&wZ@(ZetLaKFIw>n;Po4%zeOPRf#`cY?Thw4 zEU_Pc`29eLoniNj$9AhaaFyk|_8m@0rjJMm9JdqF@SjL*^9!B{2wNgFb(jPb<A1GS zM3&ZEbZW*HjI@;`27E)(9b_|_T0=k50};7p#FNkma9fHwh)K?UySGxa0%z7*hmZH^ zeN`j{3`ETq2>3h+Ot|vLt~8Z4X=>faGqYlLH#I*G`8Hgb1HMF>%6r8lFIT_YZn+s5 zS;)sDZOw4uoHW*)*|Bim2x=mvPqnM9f7RDWFQYU*=GQb@j_wd6qfMJlnvpC%0ed3C z>dEaMG9Q@H!7~tZN@S{uW$~v*WD2s#&FGvb31E$H6)JurpdrcAO=b+h@?!J50F<1F z17>}FA4cQ$aB}8E^z)5zJ)IVl^{-?gAs&VR4k8V*xq?W62m=7%rq(bnjIwsxdYR9G zSR&7Z<(>W`9VMFjnllcQLrdwY%-7ST;LLOb7)J)8IuMt`&w=#{BR#8LADLhgvtF}r zX7z{&VKkM+QSk8Q?HBLiF=>s~(G2FdUPhfY57h3{P#KuS*i_cKf18I4LUhF{n&*v4 z%gt$>;2N*Y<oiGhyh|WU<8GooAS!qy#UM3z;bMH1OilE{ACL(enNpu`(m?)0{%0<v z0Y)}wE@x;Y?94@+G0ujhKHnY?IY1^K8)Mc58rj>8Oe0N=LDF#0k)$$^^wC(uYtxig zsHZ`MF(f8Lbiu3g)@pATAXrGHZsgsFa14Q5Fe96$?uzx*r+YPw5%(lM$3i%>c)x+t zFw#~VUL~^ulx;K$3Y{19RW}S`n2Bch*sWQz6c0xFFe!15gCvev4tM*F{!={EOlKUo z^#soxZ-Qxoym_XQNU#D#W!$aJVTk1$e9nNFP^J*GVMqp$SG6t^+<~yJAS}cfA*sO! z!GY&)`o<`vHF3CnNidb)aYW35>T8?o*?E9+DPMXZmOVNW$TTnDGlD)Zio;h~tV!~1 zboX3%n~)BAp3Mw^#6BTmO<|c**CwK-o6XxefNFaZZyXVqI7o-{3Wg>cVM=5|vGF?R z0x0$TsTniPOdSv@Q~I+cTd(;i@$IkgR7|$2i@bUlOY#AVq(T<GnLW{av*~2S|M3A> za$Qfp)8#C6yJ4}$oLgt}Eq*0?00)!en!+0a2jE42s<V%mXnhdTXudk;h7@<72_p%! zhLg=P-RGSorVfF$eI-lQ3!!$_#mATdPXSrN9z6YtI{`d*#M2JH-zp55pypNhRqPwM zd}1CF1JM<zXzm)_Ev|Uz1aE9t12~>2su-50=bHo(5_W2VhtP=v|CXDrCNHB{7G0Qi zXX;QjL6BAAxL1~<3&e!0F5Wc4x5RO2NQco%jC?*K!)GCtT=q%@NFPEH!exblCS=!# zMzUWoWS1PDsBQU^s6J=Mk)W3W9?NA5c)tTTJavGCuoeg=l_KfmP-!3=s)E)Nv!Vzq zw=W>8g2PsqkhX&f4JyXXt2e-c*4OlW0*p(<^dsi4<PVXA{g4v~nn30h@F}`Rpmqf) z@cD{EpLz2@rEqtSZ0^A2tvd~xf*x1i=A0SCoGQChN);3jc*_!|0@nBm?oW4~Nk5ch zYZ!TEUm58~W0rcz43YxbOBGN4Mru=YVd86o+DZ+1hk75P_FoJMHX^=87&F3Dv0r@E zPe1cXq^8}ti&I#xNeuhY&-iK@vcOT_U)r~}VFCl+22j}C5BSpk5#G3|mJ{z?&08CJ z45R`e=;u8E^;Nqr)&M&%O=HTAn!A$5zkXx5XcCQO;uA>iKSR!a4P$1yPr0ckn`O^; zG&V4>ba);C|D6^4>~H&0M2CTxXRgFn7TslN<-no5r_rmON`2u=WOU%y=4TZr!rVx? z=H~GQQHzI{sqpa*63;_zPw*CW^lDk`Cka2XFpF4)+s;5w{mtnBZ$_%{2r)kt-gett zIpw DOOZQ~gKZ=4K%D=9}>e$+W26nc@66Qx`c}JJ3?}eoK_6#@-+1>r%JYmanC- zt^NHu=--q|6OyV$-(OKk&I2iwYggMAy%cn#-z-yMx#dRh*M4$laxsWWOdt5bs1lm9 zzn3M#m?oH7K7BMMsyh9!-03B>?;UUHOz%6X(wV-pRK2#^`R8S^we#v_!?p7-$|`H; zH@Q)S`8F%&ncc)>x*s#y#q{UJlI&Y!u%b#QwuJWz-nGPUymbZPioVKJVBKXKbhnMn zguN^hfF<LN=E}j6pNR_%IMpe`Bjz+1W#GMY8V1{AvMt}aGljpGa<^P@P{9X<=AFu3 z&^n6f|D?^dDCM?mwZ}C1B}i6z{paNZd!EnB1szH37$?t?W?s)9B+a~?KS-Q;Gk=gg z6W1tpb@skGhTGng(Ci6l(uqCx#}Qt^k@#(bka?dY&^b%)-4T_gtzMTXP=bzChHB3E zWY~EW`REso+o8qO^WCT1Kj9Kgr77DK8`{ujrjMJrk8QP{(5mMrPuM?=9eEzl6dzhp zyIy+`A1QV1buD0b?NyP~8{e0mfNB2eB~s0d=b>6M*`Bf2y$~soc&-~CP^OrnP!Mj_ zpenBZ(nzW0SxVdk?vH{c6AY&<1*J?ynwAv|>>5%%l@nQh@j_63*w*rrYWn5<=jCEE zx1WVhbqwGQEzb$dZFN2So)g5_jxRBAs(!iykJ!MmgOKtfytK&XkSytYwI}{bd9)?X z9gld+f^WCECc0>i8-gd<!r6svoewy;%7So@87HP(U2Mw(RgI10GlX6ERncJW@(vc& zxk+xjK#gW*D82U0azd|GQ3rc$wz|zoLjPz{2WxD$MxYjL2STFYsQilQIpLt~#5ZQf zX5`a!j|tM41+;8JSc~~Ho_r;nZrX;L_sYNFc3vFe`SLb2PM*^>(ZYNE-nI}*UfNQ^ zu`X^}p_fv&=+|Ew5(C3Z;&2)IuX0PppPRso2y!#OMrQaMJr5O@6u1Ln{^EF&8y+rX zXCs`e^yA*MYtBe?1q(4u7IjY7u*z!-VP}Nim&kLv(0J#GpN<eJW0SrkAYN}v#1tl{ z(--!lok+usQJ<_@R__N~q3<S}`M0}09gED@+j}fBEsP=kTGfH!{RfOTd?K#6Z2SAm z($|b{VV*MM%ZJ}zA7xxM8(IV%tcm>j8$eK&VSD{_!&p9m&`pZDgJ4drzgo`e?Y_C- z_&K%h;()`VrqC@ttTui6%l=#7z7)oTdOM%q)wTYrA&(8amfU#j&o8e@-&M{pr%83r z1aALsc`o#plUW8^tX`4dm8e<+F;X$EHgkTD)`n5O`ctVjqa6P+y@pYkU}{UvVWLLK zD*TdF8+l24{waWgrQ;T64j<s}%oNG_{lw*$M#(kV;y1CSl^-i33))}*%z5K+d&)>q z?1^d^n3}pJFE_w$2^}vS?eVCao<119eCy$YwB?Aty2H)Czfe9Zuc-$gtoT&2&sq|N zmX1g-i3?#?@g`{23?r(N)6n~aKcxuGme*I6#-epC%O6t8R4gcptE1MSKks`K5&q0I zS`s9n^CMLoi8wSN|5QHt;)4<-f&Ah<H_9S}PJ4Q<v!^%U?lT0_Bj!ubk}{s%-Q)+z zI?2eOsyaHTN3^}kMXb2-+JHese#Bjuw;zAn=Ks`Uj?kah&8;do#Z#ALF*BTt`+nrh z_{QiZ1Dy}n8zWL&0!UT)XBFc06Bb2%(yw=MzE%N`^X^V%E{lHDepe^l7KcgkOO+cF z)FE=8YhN_Dgrk~Q#OGyblDUd5WjW+vIfy1juP<dOtOP_h($@Sag5A3m{6n9kAROX@ z_r@p*UfDrd+w<P=FkAa0iL2}G{kv69S<W8@vCYPQzCRGil!wT9SwFe>_)<ZGNnWo* z#B>+UpX1}YbU`X~kAOZgp1Ev?>mz<kBC)E}C-)0_Lc~653%WA3d*ATkgX_0)RJ(CE zcv0DMdcPR#vK4esUoTk5>it~l^v4&tWO%-2_yFlMc4%*lu24WaKPsz_1Mco(W+h@9 zlyHq0a2Okx+uz|X4`xz$?r-)}Xls_`7`n47F#F85EM=<r*<!!tYr!APWtPe5S1a6s zI$Bb$?yR1{3@pGC*-eBVNrMD<id8M8FGV8D7%6UX6rsz7xr(&(J&k;2tytq~C_E<P z?*uyW*^+)6Mplh(h-TLSRLA<jm?77$U7%NM$7c01*=ga+dEUU;xh?Y(!#|R<3r0IK zUm7i-bvD-<&TTEOw=e(rou{hcwFk<SqO+k9H=2Ppn<V1YEg9v14I?vlE8TVWy3k)x zwF_GG3<tYk#t#Rq4@^DV>gSfx#J(iwnZW~jhUIwkyv>wPW!`yo95}-eCck{zMi4T3 z0>^02mK~GZbLW<w3~Gc8@W0ip6`w=fL!(2lLq!+XbJfGQhkAz|hw6t8hm$Kd-`u|) ze<S=h8cH3S8j>1n7-AUuBjiV@NQg*ib4YWjXNYI$pO8PHBq1cB@geb{7PuV*()iNE z0StarevE#keh>X9{TTenudO|xC!yoWe5SanSjLBDOgFYCTU)i`<oQhTPGLys&cHxs zu@=mjw;E+P@3qjoKuPDkfH|ty9|_wDFEt&V8Py$~nbSE_(>Z5<T?|=QMJ<`sNS0pt z5@vBiL!GB{IQ`YjK~i>ajTui0lI@=5a8|09<4bK@8S`$b3QoRL(3)Nd&nlxfs0SKD zGbw%ks#c#eb5s9Qy*8S^K6OjNy&YK<&9O(-oI8$|*E`>R7XH`ppQO_!fm?G7+_Kut zLhrdR1|pDOpC34%jyd}C^7PGh8?YBJbkKcJjLxLFm9+D^UXgnk1Eb{5lGx}+u5+qH z$zCtOWx^yw75$d~uB^&`ixQ2Qk5%pFykKXvYN%thnw4F|U9%j=T}yn2eN=0UeN;%& z?Mz^Hx_(#_b5j;`qcIG=ZpLeG5heI*@xQ;}Hx+$ZI8Sxw&WAv(I~@PVH~h9%Z``ar z|K}_Ixzxp8@1Esf)mz%CkJ-s$<qy<3qP`e;4D~P<f(h3(ld%&Sh+F!kG!jn~#oi|+ z+t}AO4$iYb5sP9wsO8NTl;-3Q;9R;$r3t^~JIsxIiI}=4G^TsZa#Uv&TTO7jI-*2* z`F(rC$I_<3{q3O#cqv^y-t?>RXQzAhtSzZO|L)<|XKrt}fgRsh9jF)8_lA$3qhT() zXFQ^eH>csRVb*n&4$cmeW44TrewK9->9JX$Jedm>x?&Zrdkf9^f2mdIwB9!#aUR&N zJGek;nioW&zh^I!squ=Lq^@RHhQ5+>YKaKb`O?L16*Dw0*b6b*(lau~-HTj8md!`$ z2^dy`_1Vw{Wlh(L#6}&%iVOmc?hUJ>(~d<=WYD3@Jxh@;X3Y+$}9yJ>K$VfkS> zi=!{Aen#cSBp-^_XBT_nJMawn$WxTsPm4OIiO{9Llsg3(1KIU6+bhdAVU3y+U$3~X zo+l1u*J`Gh4uNVu1#F<01ivN^$UvU%Tosq@*0Wwbw9kzDj>@i2Jk`CRu1NMM+5esU z;^(2otB-cetM#Ry8K0kL>RFjd_DO>oW)3g(sO+{^33o-4CYk*bPZOJN_;e@h*{&IP zK7Hq}ovBDpeYK<KAO6f5+A%;Yc=1g5Md_uu{SYD`bXd)JHDech@|fdVdMA|O#6;bi z{7^Xg73V=s`<!;ipmF_$Pkf`M*7Tu2NBukL*O?xZZqn*jGkq!*t^{?5c)J8@bb&)7 zVGSp~p0f`pH1RzTKpAxgsf{+1W9XVQ2gZ4+nXFjHLCd|;2^+?^zmuwI!=j6t3;f*< z&UV%@rq;e+hp2#e+#Fuw+?O-@`WS0rW5blW1nM`!8-;0^TkL`+L38iF_*nbiyV&)^ zthiDWTyP&Ur?!^JzWMoy7^>k1a~;y0eMOwjyNd8+G9mVF>JX~es7v(A&diQISG#gp zP4fqTBAym#@v>gOH4?PGDu*<6Z!P&#Og~+)n78r8m6ph!<B<0Cwzl_gav6dR#p(C$ zKKnC=ubX-@7uEi#p4<!q!yDRc6*`%_7&>$Rv`2Tod+orsYy(g~GO8R#4A8QdCS}M_ z&rnbOrinKjb+`WE!#!|Ii6pRTi%mk@+{yZHhdzf(0h_bvI#T>^ZC5l#q}Ke|HkgGP zOj~|Umcq}fT1GWzV!`u`lN<9s!Y{+2tZ{Rgm1zKo%<0kGcfseq&m?y0W8O5GVDSBQ z1!yhOe79Op-pk*=ThP|E%Qk9_^CW5RV)W4<O4o9&9%U@kw_8Bc2jFfn&m7%nlg_9u z<MxKNkDd{wWp>Q)XU}CF14u+c0Lbonaftm*deo>v$y=)@1vWvBqjwgRc>fIcH67HL zSvg#No$ak7xwnu&>7Zc#cKg2Gzg>eGVzC&<4|Oz6<0@1O+SFab(k`WYI~XXwHzvf! z{3ipXC4_BmIPc^Ze(*v}CTtF*&$pJzKav3Z&u3vZNMTny-gO-H-h&1U^VQeQT-oI~ zBJDHeB$n;z<mh54Z57N3=06_ID~}1E%s&Zf>K<D1|L}^*))zDnnm;CKAWxvU?^seO z=1AP|Op~sT@cCw?A>N*K-E)aha=g8ZFfm(0hIwrg(=tb_dF>rs$9%81#JRlRYCV}m z=o{?1=o=Uso)9?p@9Vr>{2uH!|J}YpFvw*rOKTP@q^SnW(W4HHm#7U;VUR;CM(9}b z%2q)?hxk>n@Y{Ry3Dg4(%umK5K8FkI-J1vJ4oXHU+5^KFnu+Ftzg;zq8wU$-@HFId z{{uAjPN5~UoAomeXkG|X9wNN*>$9-F-B!b}LA;U6eEKx2vL>BB4|%+8PUb;FWd&m? zV$!@1FY!AEEa1C@?PM6kqvAG$u^CZ-)#FUqw%V=GSVZePTl>#VAE#NZfm2NhnMq-{ z`BS6cKh$AA7R?6P1D}OAQ5MH)Mn|fQ`Ec3hlV6pqICQfrkIi5jCtA6M$Be<P4;q;H zDdW>BCxOwe?jF`xwwxp^7fQQ18sxB`&rP`V2{A!U#Kn#u?C8YyuGJTY?ChU9vf5&r z;!G09;UpPB3>6%c$~!$6aOHB98PfUhf0z!-r{yv@hU6CHT7+yfq#_&c>`{t33fks7 z<L&V@OsZ#?rC?;y7^hMpYeF7>-Wb*t4zO|*IgU<jj8+i>6dI$F6fl&#S6r?#d;05N zf`m5!ER?nj{S7gp!ZgO6NiuYrE!<;GWbvZ50D&7Q)@yn9u_AnTjI8=kjf>5EBwwze z6U$@)$|Y<~f|^<j&h}gK{?-3*j#+@@-9`iF3#43Xlcq$VyGA1adpGBcCtyZZbDAE0 z20X{~8hyDII~>PzlY8@9iowE3#Q&%{pZvcMqMbcC#D|Z^dhh9{Opd^tI2dbJ8wlU4 zyoV{i%h@xC2MlBwXa1Vj6`;$6vH`23@UyV^D+0iV1{<CoMLulc6?H5*Tb9ZT7N$*Y zW+ih|T9$gV*}&Z58P9Wf&mhU}IOZE5N(Y8q;t)Pu$9lZPpEmOs8TLYB!t=dO8x7CK z_`r+;Ax%1xdBGUucOC1!y6?}Yd$BRipGK-A^{`%7cn%d^T;?4_Qd|s0m?{<!zvLc@ zX!Xf=WM>d~0}Buj31=dxXMO^V^{<hZ{M)Sn)BToza{CAv{cI(y7e1FzKNL|0rmO36 z=m#M9Q{r+Ofo@D8t8FEi&Mjq5o6=fg<SE@dxgn~EA<cQ4Pni*{yVt^2KxobSV}_S) z#$e4#Wf3v=Wnd<CE?sdUa_+)jCHgI(QZh&4`e!EgFKl34Bk#ZqTINFoqi<~h7j3^k zA26Lutj9+cXWKMmq}uRo^}aaQu&up;9W~zG-R7B%Y6h-lTYIW&Aaq#S$BtX}v&6#g zPKV@F;;SguMP+8!VT^1i&DOBbZXpP!^tow{1IB7gSIJO|<v7f}$C6v=I-jm+Qvc$r zvw-#cliTyJ+Y_Ye)%;KAX{}8Fe7>{HscE+MGgq%(iPtF)@qrn}Y4)Nyfm!=3c>~Gu znL<`2I!Br8x$nKS0aQRFrxH#x+tBT&te~$Yiwio831{USHxHXT^&=>SG|lNg@7K42 zjZ2&K?Hb&5gtb>nG*pJMPJ0uT&Kc#aHk8*Zw}|Ff{?k7enW2%RGov^(HlR~p*b*q1 zqdH>|nW3PG+c=1*nC+$25M_C3TTea+*ha-0dliF(xQc0?4yX^&tB;B%>kZHB8-^Jb z46OG0%NP~VGjRn+_a#CxcHgd8V0O`$V6aBoU_M}{U=EeM+d1nA*h_(j?Jo((VX8B< zWW<!>JZ&+tH?!?8X9)kfKml)YCOE^t%2Igvhq&@QEnqnl(lu%-rUiQ}j_F}!EPEb! z5)xdP?ccR6k+_lapHIrtRT|!$CfK$N*{l5atjTx8l9<iqs}E&h`Z8v(G6btpsG6g$ zRj6F2uHAVLBXAVH`X?YnoDr}%)_WA3^spAH%nMH>`iSqbG}kOVSE*2(S5lA||LCd2 zK4g6(VLj<ri56h9H4w3G@p#K@Z79J|`<rIsMV{Hmz68pJF20H98J-(CQrR{7QV}>2 zPcq_U%`bj1Z4|R=?{zGbyi@A9l}(PDI=KlDZmO|gI!MiqldbG`dahBBx&PWm@K>6~ zv3|VL{D^IJ{rJr8>uN92JOM!OhKsg_JmY7Ryt@u@7=8^J!%+|Sp2os%ePmekV?&>; z3^G4)YIr7LFQmLMFhbj3_;4?}l7V!a#qo>;vtZG(-^U5=q1lqB*^-rbR`wZ|_hd}7 z<2EQ35h|=<)YGJ39Nc7W6Z^1X7&eoWtl#mmQPkF+We<p9y^Jto%xGFA!?@_)+F1=X zk)z2d^Q2c8aW$}ZHq~VwP;2Mt9;BGN9OM{_iR!{RW=B%H*8Gg6+l*(Fatm5aigm6Y zm{_DQx%PDL=Tpaq#=o)c2EftW+CEmNC{~(a);Rxr!#KkHldw<KbICmS`E8NdS}aUp zHR;0y>HEe%o=ncPu4%0|#Q64DG_I2l#{dRsCsCY9=%`V|3%5RKPyA23%bct#n~XrD zO-7gwYwT*PdCVsaE_7G9&fA)9S3`dEux>BW3jdQAceWa0{LJ+9G3kWQiM(Yl^f_Ji z`Cz=tK;VPYU|1rMza%4v;4lq?_yWz0X*7@$n&Qe;K!*~y(V?(l;G%x6Stfhn&kk=k z*RG$^k?Nm@qunrpkHz}xVyLOktNZ<&?3$dZWZmsx12a>n)qDcUyMoFElXdp7P58k- zmeS^}Sid$pWSgH{*nUUun0~_HQ`4vM-jDgRbU%FAbIo$b`55Mv_$Gxzn>L480qZoD zHL+}%{83l8?g5~xYmQ=YvqC0fw=&h8G_op3!Fv8EFydJ62-jvruogu%_1cOgYwXUk zmfdeux>r)^4|-KFi3>-qk(Rl&nM;Mt6t?-`_<v|tH#tU=delw*qZA+~0<4QU>b~Pd ztTjAr{K!v7jypy5^}pU~zK)4%jxwq90ep?&`LLT3;>Pq0`$wrw8zf~{OX=q-f7_lV z>^$Ey5Gic>&3^x4imLD>P-78dzl$+_N2sy~&4t}cpG~wiSr2FT+ACCj{Gq%v;NwVW z!eTGKr}<(J`-B%*1jAP)E_GyO7C4&MCOn*{EQUc_n_|P*mhBZPg&P;zKFsg$Rm9Do z<_qgJb4^Gx2OEpLQ)tRgsEC~ZuG@F7tts`T$sFY?C;22T$K>L~K~A=~`DX>N_8(1h zj&%6n#l0Oh<lltPTjNKOHkN-#FfyvR8S3rD9=9W9*dbr9T=_mOI{Oaui4jyi6teAP z33C*+cZhAGUH@J|x=6b>LYwbuDa;Vpn}0Oj%k$+~%wK<xGI}P>;{4U=l_$SK=VG@- zQ}Ux0+Wc>Rzil~M8HqjmI2XI>AoNg15v3V|`m#Lu?Mx9SY`6GXy?HJ0F<$=B^L(bi z%;|aS$wv;f&}7DR$^G?n-Sr_C8x_W}b>!f}x6l?62Gp1H#(K^XvsUl}9oJA@&iG6j zZ&#P=sr+Af@!52rB?8wrE_$sk`5Ettf%k-Kc1cr|3@F{FN6T(a9j&J~O&zVKXG|S! zrq4|G-cFC0?paUoo9<anFPZMSf81AO;j2_)L7j!YoU=;$II)#N-(e-V9kL%G&lT(R zUA}ZrHL1P_pU9<2T#l8e?k7I&oAJ>Bq22)4Zye&f>`I-n$5(T-_lWDWGiBt~cWufk zgvJ7zUW{|TDouJHSFNX-zxPCc!uIOL$Bm%$7axU|GzwqP9$1^Q^IF&^{MYpW=81WA z-=GH2WI%QU-{fsj1LNdvLPJ0qgSg*gfhg4$ylrDrb>B$f!T?iZ@6Ng9gckz|aCN{d z3>YB=PAr&tt3r)bIql0wg~W9i7$>)c90`lY#pcg|D+EHHlx=KUdW6KEt(*A)R|p(D z)08=_S_Xy0Sr`1I<C1ze*AKHF_@4V+bJ_Vx3#+alAK4TfU7Wj507LiebQm&kO?^HT zl)RTleTz$^&>t@}=u?f3-WtEz#ZlEE_UArj3x3L-tMd1-oK1=Hq{V$(ASv(e<<t+Z zq`dWmlg)`A&y$)|#)5Cy?S7l{Z3%X08hczzC21zJ#b*=9D7^Hz_D3j=CQ&d1#uvDU zoot=Ac+f0AQWY1?2CY{=k0IECZccauJ0hmgcU5(;7|O0)lQxcgJ!-TPIEcek_HNS~ zN`e@_-Lj|xpOGhSBZTZeNO7MyZ{M<NB;?fH#t8*b-ALJ8^B_E;6a#2)u<fpeE;;P3 z>lhPy`F8@@c+2uLhw-m;7|ggsA1%fYY`t|?SXHv}sOtzM;OEX)3Wb|wQhyvTEba6k zNp_dJF$?G543$2kZ73n2KfG@&Nhjp@7e|0QUyC?9glc-ch-4{(gD^MHm%;O*<iTQd zvP1_@7+*4eLrDVYv>99D7Qs@I<Sh4|+s-EN3Dxho<vi@ga4P+YJdy~K#6Opy#uh9M z^SP<z$+0|ngvX1eF#afl?a4-0LSwz>sRZrB8n>FOI!I@Q4bB-x#Q^IYLB)V~QrL;v zCV%fHE(`LZ&~Gk(N=f}czTPq*sxNvM1_1$SQ0eYax=}*98<Z4~t|5k!Mwmf5q(M?b zKvZC8fpGvqkgg$zZjjFRjKBYV-}l}R_k(+%y>_qttY@urM$Xv^s#yp+?O@Va8L9UJ z3laAkt8FZkH~Xy{!JY*C^dIZFdq!epDJ&tmHy1$RWoWLUWfRp}-58|15W&;`S1M^C znvVb*JLHN{_su)Yksi*ZRidBURq4H#ie6N*F-mDT<jSLn!(hwuNV0`ZB?F^XBw0Wv z4`-r8_gHzWrBF6MimGU9rjP$wtz3Mii*I2&u_#YXdfX7L?kaxP@d5GM8l*^4Ilo-G zAD!l19%Uh-UazO)=ck{2=|3><f<{2*<|f=vLv9|#2aWx`E2_(t9yXxuZ=%>@`)T>( z%+N?XuXZr`7)wjymaU8&BoL!;3cB~R>_^Z0`?@NvauDL(gWDF})x7cUtfQTtq}7Ve zQPZ6c5+mc7DknzHMjyP0`x^(hF}kRrheUsFRdi7;4+B414ZQpQVEE^AlhD89h5?oe zb9XxBAP^Td<@aO4Hg}=tFb&m(KRtri;&7N4-Ts|i{OiOJP=MMQQ%d3WLubuLaf1%J zsH|>`yCg#=)`KeyFU8^a8r|VHRF!<yP}l*d?rLkmRKcl1`O#-rg&S(amozmI!RDuR zRUavK4?I#BR@9~y$vVP#_$9{<G$3BAlnEb3w3vS@(H5UeD!!!B9=ox6kv^N6)@q;{ zI$Wlm41VAw@M<a{Iy{}YSf$5@62<f6egebcrzD1AwVtR01J#V-5~$KKcgfI?1LpJN z(%~PUnNE*OM;%A7S&om}{3{<WUyQi=r0rK6mCpWn8BR5JwhZl(zj@#+qo*qyL!<Ms z@>0}LEs^7dc0Qb^KSFh>lHX0`J&)Y?t)65o4$*7z@~=;k9bGyf)wgV4b?FrT>RRmV z8v6L_*P?6}qe47yF-;eve0q9)S__o%6VVf2SJanq_xlwz4#111Du2zqdS(>wvR&o8 zqby^TzWA}YbZR=o9s9)<-S=}#t>wR@z|Vk>$t12q_S>Jle?D6Q{W6`Xg=ZlL$-eRT zdk)WFLa?zNzJc(Khv2;0saAdasHinu{w-0_F|?$O_z6N9>LC|bDgVwxA$6Q9hvLTH zMVR>L3FIps;r=XXsfl3&`(@q258O5az4>nf6#MdN>-5@R&X^I6w&b#5Tm%aJB+S38 zlvJ<qUcLRslDucseoRf(kXZP$!+`tDwZMT;Go1pP!MvGFkW-HTD0llyhd+5}r>iG& zSeUj+Z!622Q#;??RqSaT!lW_Mw$UM+(3T(jn{aBi^;IWKn0jLG{<ltl|7TM&KHehp z*+^ut;;EP8EzC1WqCL>oU-YuypmWv9Gu7^5|Ngf=YoWOZWs%$6dB5+(2Y#Q5!=94Z zq|)Sv9PilvMddYS&&^X?P`i_WIa-RRH#V&W(kqV3{7M#F0+OvDw9g)4OGhs4r4KuP zp&zhYY`qv?I&~Mgn_W5`tY2ESZV2GI);>2se|xTQZgW0&5VWMR)W5{LRJG)?w7o>T zl(Tf!-PqOG?bqekeb#l>P0~fuo!G?~#TWy87&E#+Y4!Q{L+Sth>G%s<qikGS42-|; z|L<=pbM$rpKi*Q7=aE6z^YZ7ywgI7#HLdHX%D~$P*u5#N+{wnuUv2QB-;~g6P*mH0 zm`mnU%4Ed_K9F<o5QGOsk<#7k<Ya^QbYh|oY#i3pJKjCVx0pLIG$CmO$yRLU=V#4} zH`J)t-x&JcT@F@nWoMlAG|~ojw>dQI6Dv!jZX9YBcJ}uPIfm%WNALUuGIw?pYl%*2 zyJv$?H)^O~o<6pI2Rk=?i$3R*Cihd{qw+fU>TDB5BPin6dvLq^4)&O?Q>BJs0<EG` zZH%`aGQ%*bnNnFOMSOK<e8|||R8y6igO0`XP-mB`u8Qh!vy}XK0ecE4J5*<#@_BJH zCk+Wrd=E`xL7)t|>O-C1RlmQOP*)9cI9EB1k800OK%-;Fg`qTDv!{}uVoDZtq@NwT zk~risG&k%@s%XvR`&|;b&JIbq%{J^h<SlSi2X;E;jDd&sQfC@+8KfF^*;TYcX8$J8 zn4Wwc^(e9gjU^aac{*!^%o=@Ku=9Do{@v~6i66bubcpPJZa~oT6_hL0y;f3d*0^o& zyB4uukDl9T_^7;ocfa=N3Qg0}cdeS_9O%rq<v&i@J_qj<wTQQkSvWWyCMCmGM3CU< ziP&OAP50Egzrf!=b*vxrd~V*)#SSXR9i(nthHr{ecS&p4_TeFYf2k`k=8rbGC7!ky zN8jA9yHr|-r7VaA|E7*A+qJoTvwkL7-LdSGLO67+rLxC<G?}NZ$kMUeE6>ugOV{Ad zwlCquo4nwwJhpiiGngq=z2Il}X`dul+m0o4<2(4*MK5*bigtF}(!+w>sOg}(>n(%f zP!S8pfo`Kn(||}*1yWu-8%%jVCI}98on07E*7#RuhCITq3uF2~3MY>+4U9CEBPGDL z!TFzda!kbk+J`Yc#)I?7LU8WYMWHY6g`3Kg_Fw<IjF13oN#XY*fFTKpA!>uTHYw|J znn!dHwR|kWK5r&tDmdFvc&1d=zY<3P@xS7VB&i0WP0A?K1YNK|qFy@TQkQ%jy&x|9 zHX#$sW(R`PO{Q@lQpdU$W-35e$43UABJ-9T@Pym+p=Toxm*^z{THVO=y~hG2CuD}K z0`r~TJ0zzAy_eg;NZp3KYh*<XNAW#^M_?`+dBgj0LGlSxl<+PJcnYSCmC9#h61bAo zGK;)<2`hqW!u72{BI1a?E>w8{${|5r7s0e<2TW<g%o_bax=|nva)5`|vPKU_DKb6f zV6|7Ico1$XBxmlWoQr8A^v;rL&=XG0^i+s-5CasR0DB{BgBvl;^q5eEuT>sBDAQ>o z#}{C(H3^%gU5MncVit9sO_=|x@UjhEU8qR7d@Cf*AD?MW(uM)T?f8_Rb)q#$AhG3$ zbNjj&x)nhsz3f$tpT0nr@nE@Bg(3&X#)t~S%cx}+29p|&vS?oma}xx{UM@1$!o#!a z#1OO;?acbjE6#cfhIgum!>CmFydx#7!%PDsVC8XCk(ruHfXXbNLxhciF*0BAGXnd? zogkCO;~gRMF2YY>t=W^ZNnky-5)G#Z5c1R;odPP2V2Y-$1GK*oXUalKnM(E#S8>8N z+9J%9ix5h3tpho8J7x3RN_?#<+`nIb-4`5$+!O=jS=Kw=leW>T1aweS4AnQ$f|N=x z$2y>ofsoXI(~=r=9}+OEm?O7iUBN>QKw-58Xlhx%>QW{85|<vdh;O4Tf=PdPBn&KT zO_4&$9u9B%)&8dwOR)YFvyS6WWB`T{4qQozf(oANSY%F`(frn}euAuaq!PL+y^LcA zC%^;%B1i(KrUogCBqx)7D}w%Lf4TnAUXb5sv9H+|48NrYH6>6*WD38G5ndLO-`R$M zM3NWCJdk!4tMcY~Cb`0Ln#ur#Pk&3{^1KJZD-Bi5=i;Uq(Xg$1Tm8|9qEu>GLC9v` zv1^hH<rsmrtYzA6%mxWa^=)-5dcg!f&`THJj=cxEv7fucvbi&&AP@h~7V+iz$RO<4 zf^i%V?*V5AObQq%zC59@4OA}9M9Wi-j~P<+)j8tTWJ@hOQ$wmRG1mk=oKA)Bfft+# zy=fy0(Qx^J%+G&);a?S1av9-DlFP9w6x3*3V)m0cg!wZkNU&xvO4_u%{Vm^RuRU?0 zLgD{4fkMmh2bl@@;uSL!fN70_&t~$#X{7!E)36PCHc!m-T<$;;`uU+2`a#zg#9 zKCT!V8)3TU2N%e;4x>_`xRB?gX8JJCgvh!5yk$pUZ+UBH5zUG=bTL-2`(GM(nOis$ za9;JSmINxmCaH%N^{P17aN$*#vBD(qR$usg3{Zc>R8!%0+|>6c!Q<HLj~oC1v$8(* z{Yo53)v-)yg2wdADAo^n@Q5YI-$*KHn<XNfK|A`@!V?qrEbRyRpTFhYN;Ez_d*q;( z#0oG(5t9lZ4+cOHf{+{fIyolG7_HC;S)a`!(a=vPqufLv2|*u3ST2`L>mlCq2Md)Q zWJwj3*5?@3rw;&A;KjDtB(m{P8Nd((!&j9kQkEdW07KRB;lbfWzwF`Ls}QqOS}Ei# zh_!>K0vR_Yo6W_=dWtLP1IGzB{eWvjrDaVjEM!w}lK_)q;%I`zFA38L^0V59PkBu> z9U6L}o?H`1VXu4%qg!L6xaVamM-2Sm&0I~q-q)Ctd`vMo=$gk#-S@nxRNxV>A+LqP zCF$gdag(|+mj$#r9<B)7mX%<Ds5+MDn22#9-Iyz$VG_V4%8n(Bj+iuN$uH)9Qunz= zE>`bre91Vg&)x4i{K*9ST#n`nSG`SVAX9BU&ZIqV;$(0M76g<1!1$7QE;}|r@E<PZ zKa)a&tv(yz!Tqc7b2a5iU%ZH0-x7dG_C-Z8S;Ecd$mMbmpX2sjoWA=saq(CyDc9q# z=sS77I_3uc2}wT+N?=Gp6WSckq?8YAS_6srvvuD@WG+FLitzDl7L+LHNV_SwKe8e` zBv9inw@k)W_%`$A7WW}lD9G-X=z4nvJpeW+6C%M>kgksBKHB%Z7U{&OQ(yw<w~_TJ zSN0Epir}#knd^YZ0scK5BAcCq%t@E5c_P6Sl5s>Jc`^`&q`3;5@I{>DXDT|&VTeRz zH!lqtuL}U?LNu6x@uR6YDdx!p^Q(9{SUy}3)p-apA4LMRhJwJ#?G!#gf(R3$=^|PJ z3rI3^J)XaCrPP9D1QRX8+=y&$Qg<OB#Q{~0U*;m1srWmPNzp7kCWtsoR{6Dg9uuiF zr9Y9)Xv6)w+a)QRWJWY}smTO$tIV2N2RmY6<w_J1*;9BbxnyJxJ8cLofRj4C;Ln!7 z1aVK&CCpCHO~?{JA%Zb&tTxvn!kQfFaggy5B0)=~xK!v#EF@rG+<|I24w4D{g_RIo zEX%WI25oA<vbrsV0bQMC<6pnB5H%s|{^|0E6mXt`fRpxTO~Ge?2(#Fv5I`WihI!t> z6Ley3F2vi6?h59ASZtI9mRumrF}&HNy!T^30(E2_-Uku*^M>^Gr%PXgv$~rePUdxw z<aB$DO2pk997RIk)LL}YAvzUrGbFdNq<Cu&XqLmeUpsZ=b~kMS{8Z08ulRz{VT+2% z9-G3S40LOKufu4aQS<Y-nJ>Xhp}M3jDN6x)UhcI977k+RNt55NM;q3@+;V%C)qE^< zxroH4BfWZR!8=Ndvc5Mo_WDJurLZr-lE4tD12)CyQWtOw<C%V?BwgbhFVh;V7>(eH z!JO`XOF?GpkSTGdwN}GMOkNJ5QwvmeX8E9ouUMvMO*UFdZjcilrG~3RA`Ys3uk)Hr z>#!;5%*BDR@y;kK0lgIs-%|_X^~-X3*O>{X3E4+#9B&)1US1kn2x@RhB=}P_WG#mK zGiyxx_hD;8c1_qMO%(pu!f#gXSNmU|Ytyp=Usor-qFs((JW%+c$rsf<MLKKU^=wl# z2Gt-MbD|T@%OTNnlLqr~`zDq(1!0G49CTA_OnzZB2H$C{Ur#WJp-q!Kf~uAouujeK zrKn6YO+)X>8mbon-L7elS34?bIzF+v)3{8(Ah&Vet!HkXd($PEDhkn>)xriyD>oyD zAiL6bC$wp2F60XUxml9@%5E2q>r|)~7@nS0=`w4<k?U<7IrV)`k<?aYr)@VIhz$Ql zfUW&eTtom13d;e6ec0eKMMiKR9XXn6kvi~M0ue&0c@%3yt+=$qr~gdp={Omsh{<}( z+z?OO@6JAec1=XG%Lixa-uqW+&%{khlAzZ`1Hb%q96V_pGK_7zP#-uPwQ2Y0vDU-$ zR~7*O(;vE$YpB}btMO5@eMo{oNHJCTuN>!ht(OERPcao#<zt3EVylZ_`#?ypaUaUg zQWwUyW=HXdoIuqEQ*8hbsuV)r?HchKio;T8oirt$Lz)D?orbe0*j&b0u|U=GS%HX@ zjUyld&c^>N$8jc)16lYa1)j15bs_IzGZuyRvx9EMw<XvH_o0$(ALiNYOW{$-)u81Q z7LfEIe2~icCk3!XZK-gzxKaE*rn+N}x+t~}MknxlDW-Dd1XkoIAB7dpIytt-5IBo0 zlseyZ<qU4~Oa`Q7qeI@fQzN@)U9pl-!vw8bgo-D?O9O#r6MEa+hf?!$VzUUEOX_J# zF{_`z`N${pVNB$+_3*v&w0$T@s*_o;hr$++X6`~Y*z0OJ?USeifqr$I(VJ0z+U7z~ z7aem#=qz}FvGLkhhw~SEov9VMiD+Kf`zfl;D4=}_#iPtR3Q;JpoJ|WV%8WAuUUUlI z*0#yjwkD?(v0dMWGDF|1asFZh?LsFb;6g8@mmV6{asn^}Jp(p*I!|bzKpx}~4L!@{ z@(|X<S2G{Tr^wx15`?m|9mdvhmceISg3bxa4YuF1l}%W(HMG2LR<TT-`d+Q}ITSYg z1vorvw&>X}M&4%yA4$n=b$0Gz=K_;6SID0ovZ&IrN!A5HM4_mL9JGErr${M2QP7bK zkKjX92_1IcT7eOp*p||Owi@raGvK_JU;hS{Qt%wY_v*&j5@y|5ppsUW>$}jl#yl<r z#X2V_Ycnc}ZLM{;wKLzJc@w)&>1}{T3WapXcMdf5vmx@SQmJZfBCRGm58;{ckx>fD z6m4s?3b%b&l$KA9;J`c9@@9jymSMXbDOBt5`OT=iIC?ff6Vbw8fxI7V(c93r1@NGb zO{=8fDg4%j(^w?0TwA*}$%iH2TlZ!eFppWP>7D07U1a`LD1x|*A`{-8Pn9wy%^B?h z7HEL><WI@wnd*@{_%A<V2aUQ@>^Ib^0gS^oA&aySY*UMyq!5-7WD`W@Pfb#wa$!`! z=}$$%TXQp2mX%iEPg-<jZj{E*D|FMcx%rkPF8x8rCR1Bre#g3m;$8}@(gXaN5~^m& ztaH5u+`zw6p<oysA0Av>U2^;~3JdwxvC#3HdWTVj19)`@V$k!GAdL=9WKegcnyrMh z=IK@Uu%}P4AT=^a@$R|I-ssKE&FeeFqCp03s`OQ{DzVDJ;_a>rY7RjW<flBz>35mB z<bPbU36_z7T(*f#(QgkT;!O}&(!;W2>>~N@$3?PH;{^<f99OT!`wBNv&$U9G&3E>9 zH?aZ>L*4ui;GL<Q32pu%+XJPPwXPcD<g@EF?s?CAFRm-9cSqN?E#^a(Movrn9r9a{ z64U;qL{X<a)y6Us{Bsm63=CPuZ$4Lkue1?Wt&;yNHtH;FZ01z|sA~87Ov1SKt6jLW zcs1Cze!X(-euArMm6IYCsiaAkd{r**?=PaSRIFPB@RGetsbRmGW(Y0bG)t&q<<@9t zlYQ7cDxtQS*66p7GrC++6Yk})C>AOHJR+(tJirrtTSD!SUZbBKZoJ9!W~kTOD0@9K zyJ{q^;eRDEzapA=e*5DBHHXASJn`cmR8Ks=TM4`x9ZsOFUUf{QuHJR*aQvQ7e3LTV zoN(H&yv;sy*pt|E_Re$W+3USWn~pj(zpGIuacdKYRm$Gm{=XdE>2+uU5p{8s@7rIF zdh}~cc26f9V=PAo(r3sM?4OQ)@DcHjZ%8X9!?~v-Y~oP0>9{uHT2J@eJYzT`B+35i zFXNu!fk)F`Lp}J#lIy5LE8!i`P){xG?`Mph@oV{s3j*0e&qGEX_b$$#6mLp3bGs}M z_dAva2TOmBFtybTmgXy(R+qeN$S*PGmR`Wua4c<m%lY}^v<CG>Lc+5aZpww>R8(8q zyZxvb9Tku3AeEuD?*?ma#fi^vw81Lt1;5kW_BYartJ56zqf{1lN(h6*WiIWdS0`GQ zhW#|-JKRbRHgeWraY6fA#o{S-dlvLX*?aLG`_U>3LMei~7Qx5%Y;m^eqL|8EQF2$Y zM~-oaEzk)nffzeGp2~S4n$W&(`K-f1<ZLTolby9WV`!iDciY?BAC7ywt!^S`;M*6) z?42=79y+1X0-{u>r^nB%qjU1T6lU#&m8DB=3Uo;HH&`@<1Do-8b4GbJOa6#vZXa?u zy(U^yr%fr*P^~>mW#Q|*jy7Z4{7b6Q!sdjPZ)wDGGCH3tcU4ZJA@DO%ZG^pK0i1lv zydc(boTxhb*Ru>eaAzC0Rq_Itt$y7kqj#e-U)#g(SZ~lUG~@f1=!VU`M?%c6iQI%) z&QwR&JI6u`L~dU5-q^zo_kJFSDV{lB+LF)pZyJ%$ZExzR`V^4x&u#xvO9c12=3x0B zmxi*wEO_jFZH5~DVagQ@s@LL7WN23!<f*KZTrf3J?mpUBlGU{hPOqP3yCdkk<QuLC zIzC`jn_xWHm<T>Tpieo$9RhpYif;{-)62()YR3CQ=?#_#PCxUq2|Wuux3a@4zUoPn zD1J~J%qz8)og18JXz{woP~wWZqWMPxx2OHp_=&HSw+h|v!f@Sca3sz4k<_!44z;f} zwJzI1IV)e<OA<X&E4Q~tpuy)BtlJL3v!~Y_70vB6^l`Pxp9yyEd*jl(oezsf9&Y%~ zeyjPSQ7rGEE_?Q7GJlUmZQ>F5_hx?Z`>}F4gI_xZYVo}e2?3|FC0mz)$9hSZ<B#P| z*(}zAzitelX&-BbGv46<Bj-y_GmdV!@G=GYe6?aV%9s>hr5|DcxEr>Ub(CPqX=E}f zJPrtIv@k6wV-`3Cm!aGj2UcqFvBK<(*(A&QIauuFcFl#~Wp%sK-kxLWi_*X2K2LIY z*(Fc=@^E96qKM-4JW#=^FKU6EH+;t-%9l`6^`g1w`y1Ve*q}s1H)IrbOyVY=3+f|w z-YP$JJ?$eE_PZ!U3%cQ}16kGapJbvWvP?#lDkNXJ3sNdIia7_CLV-NBY_V9D)hN|K zzjJ|EG>}l&O@wg9fGco>Vfc5g+q<Hk;~pP=XNSU#3Xr0Sr`^+yC8;Zjp#|+TUwa=; z=g{Gz*6gQhZUW86<Juw#$=x3v4wH>e)QZ#IJss_}`@?z+Q7w&+))x^8)vkQ1nN^sT zO{?nESlAmucOzOcQ<qK5wWgnpV(JGS6X~BwvdTWY;RSL75lfF`8e?T0p4dLa6`6aL zJ07az={RM($wt0!>|lxQ>XkKd1M53F@igqjb`cJSwGGTqq-Mm`;^Od#eov~78%O2e zJ8t<->6F#X)ncGGBUkhG@~lMp9T~GzD~pE0MCd82etm6r|7{wW{a7G3>&j56A?j&X zquLlHOR#@tNRFY!<KUXC;0(jyu8qH4$I>NRyvg^s_f^MSOZpe-$-BR9Bum!kZB=Ib z*lav>bWup$-8@qztSZ7CPcCeawZXYm1u1fB%#sJD=yd4V7(TL?^LeCH7L~ANLGG)k zEOW^LeXVzFmwy|k80<G}aqxuv<WqUmA44a88}lD11F7Jd*gxM6Hay~~-Vq#!A8r83 zsVGV`{Wb{zrpRTLUoGCX<icV<mFlsQvi&6md3TZHxx=Z+CPC7x5IGy4vJK*M?utu9 z(NMWCO{IkMM}m!o_(~$YxvYm+Doc*<D+3c1Eglh_sIVz34R_p8SGX?=Tyg4K_+5ki zO=}tY9~kmmO8rH?L-Zlimo^Z4i_=%B;o5I%2{|Zxq$B4zi*9|Kg9I{N6E7QaITJgC z=yv0*B>i9c`N-rGAa@hpk(cSp#);rQFzFS0_R#dn*hcCF9m7pQkU7WVe@1}JvQ zg67(*NmlJfPSFqT55s$iIM2?aC$Cz7p9Lb9?EL+{wAFfVr7mY#exrzDkaG5M5ty3Y zV>D+ZZuG;YpqDRr`}%H>>9+rNZ~pgfedo%`p}|o8^S{VXL;2EoSwogEnD6FAzlkO6 zvT5k7)?$IW^(|`cfS?iVk%$Po0)h&^4vAp5%hgSjG7s&J=fN(Qr^k7lEu)Ou3k4wp zH=dWnJ5}Gr?Pc@ZC=<F<dlK?GyqsEQ-A)>e^4`$a4SwC`lOEaPYZ?4nZ|pEVKKNDZ z%=d$Z!?f=QdYANHUpG-F8fVQbPRjyjrg@rLbPls$zG&2N41P-=#~Mke2#pC$J!b0b zsGnzP+hkiToabm;RRl8Twp}|QV{b!60U2vsPRZh{{nUAHuLE69VQPn2wKkuEpPOKP z{y7&=d)}CaxOuK8=gG)<4|QX8qY8@g6S85RAwHDOdp0mg<W_HRuI}X2@wzxTL;63t z??3slABm4!+cTOw!x{Q5kz03UwJ&hJh`z3XOOH8S8)N}*vOoW%u&?{+Q0M1-p*E`E zLHmn2sX*~dVqcfdzflWns%?u0IV^t+&)pn!iw<(6tfLmd?HnrSZk+b|OO1IEeUCzB zopN^$a%$x0LS_X5I+q&b;|+isDYbVC;0RWrrGv8<&`F&GXxW!Rw{+%Ow<HcNpbMeU z-a3%h7iYdM<Fw^o67O6~?e-w|j#@A@;<@HvSi$RXa#$=7zb?}@SQ0OttT@QIyk0rT z(e~xJF3W6*JIHw&Mnu^0`u101d$)W3#GFiZNW$V(s_>%k`?fpE|NYmrbye>#{+t*X ztjCxbK!)Mz<jHRZe&cTE;l*$3_}a_vjSoNin?GK@-roQJFaG4YWYC%TMt2O=ZcM4U za!&ae>G<jJ>XE+``}mO{*%J?%+NbKgf?rJi#@<y!FHN)oN424IDh`{<X3xZ1(8OER z1zLa9lwBau!R8Cj^DEF8@AO4}x-9FaJkV$=16$0`-0TE{gYAO0ZMH`)OJj@+Xx#G` zW<oZQ6fU#tF)bv!xsUE#9AH6CLEAf?E%hOXBNM0Y+XI(t@8XxbO~5fKfA6jaJkQ`0 z7fXndk+$l=!U^04@PyxF0nOT78iO;v)!l|CO8vGY^+k1$nD~Lt?u+WwZUUF@b0eEQ zgSnF6m6feSR?dZ%z~wR4L<SFfcMA*W)w|7OlKPs4S7!}-{m+9LxpimvdHVYMV_rei ztR@=vxGo;KZRe-R7WY9@&O`&0`Wn-a<6tkd)cU-)#<OabD?{8(lg3Bu^{9!{o63gN z@RW{^Zq@XQl%kTdDTcr9s`_Rs0<}_G)NfNxIYlq}XC_`rSPeH!bX`<}wZB()l=aPw zUo>;LgdWjme^$EC(wewkX%uOyUGZ>PH@iw*7+x`)Gpcp*Dq7ih(sMprJgb+ju*hR{ zGy{6X%upL{(W-0JyPUGJbGotLnWo~Z-$FV3(yk5M7PiVm%PMAe54^P|w6}9!2{<!$ z{1KaoxF`kFy%Olq_X?qx*1xH509A_J#)I|KFU{O+eW^U0DjN>>(>`mDIO<dcmp(7H z0F^$k+Vy|gH{_I|JNwsn*VR$_B9o)`KtAz#P2biuRj@*;5nnNFwv4WHQ$-%TuZ8lE zOHcJpAEn64xsGDWde1R-#$f%c)R~Eb5%7q?k;c2XyB5b*UGHl@RGS7dJwYK#>H1-H zsey?2>L;jZ^C4JW{TpObkC1!sNgQqH*L;wzOFxWxQrO+qjau9exHLl$O#`(bKrb5N zXmKTgyygv3fEpNt<o~U`RvLx9&inwfWdy3l;%QZYzG6W0uTIFRm^a9&DG~SbG7#%S zpc9B56}_K{AgcfWW<rn6&<_J4KY+RmffY^w1ECj@gLtBO=G^Bky`?1ywBD{0+T)0q zRz>VOAPst9IU?>V=*uKd2VjE#R#=k`x}p|86-)r34xFhQbr8D(?geoz6ig^El{nw; ze~oMxfZ@0Q+rM&>Ep;O+sz%Ixsm4TO?NUX^9haw*Uep8e>|NaK5R8%!MI50{had{b zjhlUqOo(flffWuqBH~H4P|-BPdtv49*GOW+<z5(lIooU0gt-V64~VJ}D}7PBst5Kd z31rJjp~wz&Q$wBFBIR|}BB{k&Kjnj@sT#WhB@4OJsXjr_$LH`D`XQd-pb*KL#OZh) zt*^dn+{H;h=Ir?mtD``l#tU};qAlvP*2kP}akR9FZ;+Nh)nY&~T-r91OL}VcAd~<f z3V|pCb-&*FNFMX=q=MYLfJI@i%_xG%k0hx5ObW?=Q{D*gaepM2MV-E2PvXo3y+|lm zRG#MJ0SUP86f>Y|o@!rG`+1jRJ68j<<RId^%hN$7v}bd1>eq3!od71@sf%E_Hdn3G z*ds&A?K**d8S1;C6(Ha)YbZ1uN6S1Rw70dqQW8(wk|~Y?qvwh?CrK}HMP`p`;isPq z?oG?HYdz+S{7CLPhYje;FSQ)|Du4L7yDX|^kEc^lr>P2Cy5^nwy<?Dc+-$h|^(E{^ z7k}lY5_{KZ&4(wy+b}+<8Aeq1{DLv-bVxv}dlc9cmRBB9o#hsh%OZB=0A-_5ClQLW zL+aAXu_r|qlS>RxQH!qx^ur>l{vGySf(<g;Pjs$_D8QcDj}(QV&TV|Y_fyEdqlO$l zFK5yR*Mm;p%iUVG3#&;X<>jPD(3eO|v+;z>7@GjfCdg=hCC2T60^6Z??(pCF-^5Ad z@pQHW%aI3AO5Mna-Ww@m_nmsg=UktMz_|r2|5U?8oeJ)$IUxP&LDI3Q<8dv;Gnl^O z4}QXO24D_7upB$2eB5jzuyZuZ1ps`<S94DG%zXp}y+KNBaZ_@(q1DSa_HK^{|Et$X zGSJe@H+`dF)Dy954d4quAqW7%gm4R>B^0rKE@292C=L`<m_qtZU4hCE13~`-)+Xdm zwHRTC^xUX3)|QGhBgz`NDP7tO6PV1h2#dtZy}g$^-3OxwFmFt`C|5mcv}}#^mZbmV zvdYF6*gT5)THn?;-FiFln>yVnW?gF56K(J`OqA6r5OZ+nBY7`{`O$M66mb_UN0B`w zZnhtmlS@2B$CK5w%r>$r1z_tLfR;Tb^r?rS_qYS+(dI!-(09kH1B)UyB)2nGBQ+$C z0!cTkmxTegM~|H@0jN|Df91X|lodt%&kQ9|`7aEGYU5@FC&rUOSA{9gsJ(I=iEPn< ziO(sfxqP(Z3_FfUuWdP73wi?2FDDYV7HzuF0zehy1vnB3HROzj`gtH$X+~ZYSOBp3 za2bFUI7b<UN*^yeG6<Azh7s5!g$sa@0dvwRl%QrooeJ+kDLHQdLx>wDreZ>$WY!Su zhU#pkMZFo=0mJF^uYXv@d3m(wfW!;=92d0qK9v|a4<f#RvtO{fI)DlNb4j?ZCsVVD zH+lgsJXmIXTL+Ds)ywq(EC_YVs~v3`Hfm(`V>o}SOtwTon%XaEnQcYFPCbAB<ts_* z_o=C`jx61f<8ibcqTc|N0ld~|h1lgnALJv>`W`>^a_pl48owkoU7=aMBTky;j13ry z7ZE?}Rf)C`&*q<E?kb>>A=vmYwOz@!pczbnD9m&OP{cOqJxf#hMWWlGSN`4_soT>F zE7W%GyK1FrECwtt=R7HbN86diPQ;xbZ39z*pI-uP^A$i}`_MbdeG5Q`EKXVuMBaio zPrNZ{B=i@tn}IfGe$?JKkc-Z|g$FVuE(omK*Gl6TN!?fg8d68Zqa3Xn{6u*Ud9;li zu`a33b~JGTyZL9b#Nq0Jl@b-tZ2S^IFWm3_!$UmnQ7bjNHzN-|U<b|WG$MOjakN_{ zLhcd+77&23q!kf!2D@Q1-&XFJaXP?1IDjt4vzYXVXMcCYlD4TEi|IY>h1|<ih26^u z4w!Mu4_`c}ektrOO0OH_7(tZfWrqw2Mrr|67Z>!v0@&gy+HrCS?Qv5z7AMkriR>AR z>?w)wz4-~N|HNvF_C-@t_5kRjh4+}y<|B*-7V^hEx){JQqNv1g7fU|00%~x;B;HV| zUM|5(KY#M$5TIm%JyOIR`qUXB%P1E$J7M$yU*Us+eY$txNEisXU-xZ>@=f&va=>r= zOPF;AfD6B{WjhQsZyVDBM=lKk4Y?qyV^c40p7#LYo5hU$n8HDQHbCo#ho)#16E!?E zC-2zi1|Y`~yN<YV2sbn(M$1$os^6Nm)aBgvwsbc}vLOu|d7MTX>G8!^RMl4|+ULSD z^X)Fg?D`v-&Dx2YCgQkzKX|;TIl-hu=VHOMm8-K!+iz7mhlwEZ+i|GqSMSp#o`+%G zx4jT+Xn0He6{vo)5bb&)wt}C)(^>Ex5N*y@RBAal9>~x(GIHamc)mzLJQ+IUqi(d7 zu#OZiqeJXw3L52kUIjOOrUAgyLIb!Wbe(V4t}QY_%)Ns@<02RX*Q<?WQ6i4kY?79E z<BT51)uJCpQSYhWVzSSGB2EFRpe%h*;I~Z!fT1(F1BCG!X-Vyefy(zm?3Qau#nF1T zErkI-!whh$Nj;Yd5#2B&H~kjwVsuYW6!D1RGC;kpIK40sm2cdvOJx;cAf$lKPII<m z*+M_-v7@c*TCE@U2sN?S?uZ<QQ6kYt6bX_R+9NG538Xu7bwG~u&O5N9sU`*_tqUy{ zo3=x{hOuU~U9tD=^ua-TZ-bt@q$ZFVq}FI@XQxSUr1P1&2|+yg0ecZQ6xnk`zPCY! z;5Qai3t%tj`@4-)y`QbBfID1hiE@Yg#ix6AQ}(d1PxT#l9PTl(Cp|$K*Q@OgLV?&K zs53t3i2CevQVIsOII*ohgcdv<1-fG1<H%8Obx!43ns86%j=!I4vXEeb9BvF)AQ=Bi zuhusJ233uefQ8{v(j5Qhj#O7jBJX&O9LE<=D8|%ZHpnKFQ*Q+b7##v7<ddrif9!_c zsHXxBjDHD%`RW<F^F=&({GT2lqE21z``95Zx92!@)LXMLgGv1`^o4{ikRkmZbS1z5 zT4GqntnMdmYP(0u3kmIQi|xCOmk4?F)<8`95<5^JX4QI{;Z+0U5||C>!;zy2i#)y; zn4F?0iseixMB|6nP|{opoOcP#my_EwE)Kx^g?hG#d&iqp`TLp|=^XTs*(#CgHwzOr zrpLP*v%`(dLoOO>VU}_+9^%Td$nyp(r;%h&=?^X4W}gZ?zj?S!a$JIsca;uzFU;Rk zn5mzW2R5di8*!|EFJ;riZv;OoLL&s&S(T;@BkJw<f!D|>*aYW8`xc?iZ!>M3g6s!B z1F+bc17QKJX(7$U9x4l#o(rcr`Xj(ksvuo*rX>bOPh3p)5PZL+rm&FqT@3bws9BeK z=};Ga**fo~*<RWrkA83R;1<CJch9;jnVKwD!ZeZ1S$Qe-2+fQKnCgK$voqYe1Pcil zz&g3F8caB?BZ=6*4dZX1X1|T<E}LY!jx@F%?M6|y&~oykb%Z0hckx+M3UN=ta!lvL zMRiuIG*9Zd3x{;9r;7tSx`mS99#gtnPqv!tv)x~IVTmm-h~^(`7-b#1aEJ3xTq%FC z{cV>-!nKI8T@A>Vz;QT+<%$Hd{{|O#cSZiN@$2AbXV!&gfN<s5pD$z+zDCNozooFN zYblPIO$LddujZtgv}}WvQ3;5)MFjmy>Kn`V9JBy;TgvT7zD38J-)89Bg$J43cWgfg z)J!!4?hznFM0{S{8)WN@1ql~=vX)U8y*FmvHV7~#3F>?4{-Rj-&m$D-K_<oIQ+ima zQo4VRLCEqK;2UBACyc(+2{?%lSs8{EgBJ484k-v7`nwOpIOmL(f#AGZJ>p{u6WSZ$ z%CiIBhTaAZDgt-FXL|vT2Dl3XZer?%-2Ll-n;LLj9BsQTjrdp~nmUYfqmXd%et@z} z#ehg*uM#Y`Yx+O<h<-H$V@|Y1hWzYvMn%tr{kuP^bVBO7P>a*q6QZJ9(Y}HYv3nkc zcB`Qx|D(%}=X<0Co~@6yr7mH0+iE9hI#D}A6Jg(Tz$@$ncJ?p)tM;EfV~7Wwyba(2 z^pMQP+JR4gfb7ux-V4Lx`%z(!#B48I>_tZ^4>{7$sr?|yH8!#)8ZA(Sg&WfUH2Yjr z0Bi#|oVqw#X(&SL?r@(6^pLYE9=OpMCEfrc>h#p{-7w2wAnE`D^0z?T8jO@T*1q&j zm(W#9{iz1TEoNcGLhf!?j!40OCSi8Ouq@mOI7}coAHsf-p9YYgn(a4Cz+olTYF+;3 zaq{YG0UX&k^~!i@Jzh?$ScJt68_gLD`p}#_B^G4{dGM1!G1f?63|r(kTO{DF{jxw- zX0MvXWlygc8E0i=uX``o4&H8~A7{9%gJWi;tLHc;z-H@{LycmMV6*Q2ZRwWkliw42 zr!$vpz*CKhy@R8tH&+1?Dw7S3lao&g7lH#}S1YF+eHU&G-mA;g)$tQI8Tm+=#;cW? zy~~L;EU!khZu*)1=_SO6scGZw&FbC1$NaH9EhpA4a(77!GAfN?evQC;v|mv41V6kR zld1!6rbv7wsmYt8n(z4gqp;MLq`-9Z#8^ibyf#qB%4w3=zTdk!PLluXneSz?icTYO zW6cOYX`gnZS#DYnf8*>=CpAq`?y)h$QH9-1=TEvBTE0!XEoC!L#_}spAJltP-Y8fI zj;AZoZ*0}Xg)5kSST1{5-8-m0{a`mYP5Sa_L#g9Cv9zy`W>xl3X>zF)xz@tXiup@$ zLjL4W64aSrWqK+C4#@*j41^Ji`Kx1bg2|sGMQg2ChP<7XvwA7=vh_HyxXtux)TU$g z^LNMoOO9GwJ3e!*UhJ9b{rWps$W^-Vy<}qeyKM5z*Rna*`kJyi-unEqn~Jf^>{ADQ zGoU78M$YLB>8EpO{=I&8FJnl($caQ%D-p$CQr2^rp+Ac9&lo$FU`Wem=y`!IHI8u+ zZ>AjCTQE3gw;Hs#X{9zgi4TheB!UgJA_;s%wC3|fyh*cLMO!SiQqSHresVf0+c_20 znoqX438YKO20z=o;@y09d2;Sju{nwQGIqRuD6T(R+q0mq)xi+TDH>k%VfBzcIs2Tf zj;}Gu)cM}SCmJo1Jfh)c8LMu$kTJN%+a?poMD@mpvadf}y*p*7fHJ=C=Y4l-Spi+> z>W@?iDj0(&PSw0kWJt~KWo#BQIuELVN_gfk@Yl;Ew9BlZ^vPc@KFm#e#h`4Z4ANI; zd62oyy}8HoKxT|FDH~G6TYQ6C0p)5Ini<tE=Cb?lP@=5Y>nO`5N~WZve3dx{AK&P6 zywh`LiOY{WeW6cbQq1|{iW8V3P$+?<|123&%!xX8sMz!y?@-n1sA32;kG*s|sns9# zs0vNZzRj#v*9slDvv3Yz3Z~8xSqq00^V_D@$n>H%`bOi<tjyrU87<Nzf+W~=ORJNQ zDY4HmErWMulG>3d9LMF@MQ#6kHVpWKi&7#WwF8ekTt$%9<xz9!!y;H?Ndx8+lKY1i z@T!7{WO2N8EEcMONLTs($jv@_%LtCWns(%YEc05Ybxbd@B14#P1C|#2D~pF@3Fr5+ zw@Q{7oXoMND^>vyvSFRZb^<!AugN+;_jY(w7x^0+uRvSx>6vFg3P9Wu6on*OyenU@ zyCc%;=&z9<^Z0fbUf!Y2(1Gl6lUw+c_K5<6jf_9Yl>R`4#qxyy8XSmY!|-r`c!QO! z_To+0mtaVM?iXq6M8(#PC5A8tsQ+j;GE9d2urfvi%b#u8V&R)7(D9Uh=dTfjg(YA! zOp5%ld4Dmf9Z&DN_xpqQQ2!-APX)_Ju8_z58o2Oeh{^MLJPbOh|GJ;&fmJvc%hyF^ zmibEo(8qYi)>F_Xh8X$bd<->~3)}Kfk_?<NRd`N^3o}r6xipMH1NA@amJ1gqKRnt; zS=~KVeEmRTaFThAtn*W+IQijiPZfy|D;YjUE~6_e88wDwGZU?v4(h EH8xI2lDU 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%0D@F}hTN(p3mLl8iJrNWT_@D)p{FNa z9>1OdQMjZB*R*{Zq!S!ccY)~yQuP`1<}j5{kY^iY2j;*r9FiiaXowxXtY@*#&|D&q z)k>H>4hPd{?G|oqVWw;eP^=HBZeub96mXaf*}uMVsY0`*_D4+#MIo|@62(!dN%zaI z)SCZl>o(QqPAF0rqAd%5S~Ma{b#>)%p+AyqnH*_@@f3|D2>O7hPX5j8&Sro9ue@l* z_3F^b-$3{cNB{ur|MUzDY@IBAp{8cl{=^M-gzir&WfpKDn#r|2;uHx7fWxVH`gouc z((<cRDI+#@EFv$G`rzK()T@4s1LYG0Jb>p#uWj4zmUcqlR6LMP;z;i?b`{9uqgbev zfq*CM)?JdK<MgMWC#8-genm9Ev^fGKe`zvbdMz;HX~i?m78|X~V2Ot5105)1U~CW- z{Bir1?0E8mKv5`=hz5)xN}^vpnW3j&JQEUG(0h_y#$;6!Xe(a0{Ojc7kGtD!Pd&Dw zXlKTn_T_T(3ao%AbC`ToCK5GX5^VT)+TyKWbM^azpYFGfR*}A?D$1({LzN6o!Gxer zj`g|9=Ln%Yq&!!D1lnhiI0+Pt-=<b)zNCDC;aXYhd)1?}=GLZ{*<o7$Jyv6yhEhIZ zast2}jWs%qr;J7GtOeg%=~lACo63opPosh)NiIm*)atdJiQEr=v1H8(tk6gX)L?3` zv(Ct~i-<Ci@J((s5Uw`Nn-l;AO1WYn&-D|-EixL3u#k7F4^V)adIa|oCRwzU$o*Kk zVcSNKY&4tV2obx&W%gJG&>5QSQ5o}m)StVgE70kA3gcO2Ls!fnRNj82PdD)Ni?Ze! zkbUx4k@)n8$IVrAE#zE^#>%J=D3WI9Nx19An2(RR5-$EE8~*c-Xx&l!O?0p0;oO-F zq8QCi9lpMt_>4C0{xG>&LEzWoI%p&N!^i?3;@H!;#x!{`)aP&Sdv@(;y@4017xObe zZ%7wy1-|JtlE=@!>-fRhb8sQH!Lqd7>4zF#^jjvt>O=oz%OZ+YcSl3u1vi*Fh>_+Z z;XanWYeEQ9mwYpSx_)O)zbe3Q#!(@A(qqg$Uw5o2)j_4b6u>XK!F9~TW<cCq(#m|e zE{aJVuu9Im6~-}C4HADjR~~b#31QE^ercubH}2Wsz&4{$NqGL)6|(h?bTRC;^vJgK z`}3n0TN6)Iu)LTrtx}*mHIX#{ThMe}9L#Pga!(TkkdIOh+(1QWz*?XQT??21HPn1B z6$V{3|MO*r_pD5GQRa^qXx+Qau00lyeyfMT)g#c_acJEn^e$35cNyI~-&%C-(s7uY z#Urp)i-w>}=J$S=OqhL6nOOTAQmHoCZDc7Ialbo%b#udV6T-vs@A~>-o~e_l&;6$8 zrwS&BFr{y|p?d1?D~7v!fu)uv@Hhseu@)0c_gGZW=IR3<d9cYeq0JAx|5?YBy{gQ( zf4Mt%s{aoP@W1QWf4#%bc5W87OpO1<-f7x6ZMGr)diw)%`NAbhhFv*xR@7uDsXC%= zvyVnzmSt?xfR*Bhp+)LIFvPld{BCc6hy~&cT~<9wiF&Sh*L0|Jb^JMuoic15-yEvf z-58HOr<Gn(PdO2_TpgXiEC*(4cIwm)JqDmj)RDvpRSbuc5v~rrd))n+^()yClY-on z#<d%@lhrIMC#1^Z;8feU>LBG*rmhw-wf%^k_y;SQaD5on)I78Lgg%{G+Uh#w94bV5 zD3(Pg@Q@IFR}4}o#mP}i<A_|5OIPuQ(Va5JvTGnJLp@v+1N){KB}CsQ8`+lx&x3-3 z9E&Nu=#=cuO^(&8)G1kbczE>h)TA~7f(XvUAl0C-$3S<tj<_d<amT{)gp0#OZRj!P z=z5f+nM4Yy<wcrIwfnyn(LC7=xfk}(cn_pc5xS#K3728C|23FUWiaIugA6y%9EJ%( z&-I(r!xd&G%wiR+I*x<Mc~pteWBd1BVu%8{r4k~>X-APPCXN%5Rkcg=MPXwbIaDyq z5vA*`Uj0?Cr;lD#4>QOYZUEwUbmA~nD7VrX?iB*N=`)Ura;9)Z?Js5mRMhGgz12UW z4)eN$CTc~4I;dEsKE`TGrUEa0K*}wZt`*_1#}q_9k@y?9A~R5zoSFL2<IjQx#96Jw zZr|mD@&~*YTl5ec-Kijo+eDSp9M0;m4ts!yQb70Kdr@E>dS32b1tMok1o?>sM?^$& zUSu?nHPaXaM3B+?<VB_+N@$ZTPFeFx2B*(E(h2hiasQ|aVIFD1R@avrU<Tf#ayK{c z`(b4g-}gH_UEa#(y&_b~MZE|P-}u)eMyV-v&(H?o%BA4^lGVKk**);)-6(qlsSxFW z_hCnWRg3`|@90+ZCcGh&dlIL*Z~7m=?*rq*8}}RRfwI?_{NT_&1MO>a=Tm*HITLI^ zlq1CMSI04tHH%b%GM4EMmLS96O(WHFE`|nT72qK#!tQ7AW+~GPks?p{F1EM*!>{YB z2c5T<!R6<JX#fboHE+U&SqIw1KNFE!!eOMCa%p{5%T8rb^ojdy5J*+6v2KK6>$t4* zIpjL0B00;PyR4wFy5`y?8hxJ;?X|?iY2wE6K@-CbV@?@t0axa`cf!kqkZtEERxns= zYd@%rxMRDqQA~&=iV?@PIT5KzF#=>@OB!)9t@aD?ltgeO*pgi(TwJ6y_khWlghi2W z`0<Ha;eLk_2J>|nSHW6F<&t}j?V#idfs6dCR_af3R&7B?VId<Agew1(L1c==X5t5B zPbpw045MNOo4fiqc62Of*)#}Q`-s&gD<#z%D3=EjS5W-bC&)r*08zZ}C}-BLzLz<o zR=C3)sih;7fyj}eiWpJRi2SW=h2P<*02jEZnp%!wBU^Vg#XFT1KpS|M8?1@*sWeb6 zf|+6JWssJsJIsaI!~D{q#gn!1?abypL4!JriVj!Z#A<c7x3|IiFBdyIkNtXZdoaLS zy}xeP-|$|A;@LfTFczc*9ah_=*HWJSa}K{7!mG@fXTmByZrNY^ula7@c%mIfah^VI z<9+;xPEq_Ts~7ezwQX4uaMBRGw8AMKM#rQBx=%pmvjGTm7b>im#xGr&5Q-Uh`H5SF zX+oXD9j9>Srs^b$uUY$PhO9J*<0%6ZAoWR`(x6Jwv-d>;GnxlVA~dl~GuE&Q9sowl zYPJ;!>$IUI$rq0jNH9dtW~?<eelo{U!<av3C@X|Hp2Znq^Q0?+pyWAS$*nm}f|TL0 zoowlAQ$~_Slk{DteA&&`Ao`Fz5D;yk>)eDOAz)KKJW)CL?`#JXkkt#y{+g_@3vK3L zz}<V4;Z6fW4jnlRoNS8G=jdpC?}I2}92Ep2kPLg~V>F4=GnoE5;97MOVhpApQl$zm zdghPv1Di246(pg_EZgRCY=W5PX<BqSFj@Mq6)ABXCjFp4BEBmmI}Q&$Yb+Q-`WgwY zRu5(gi+PrlDQk>yi#=B1DAZ{#6IQX8WKx7}$YQJ|2*|caRXnH&6bVudaH=Z2UI@e3 znT1QAw=1Xib>nO<Q|>lwRHK241-khv3q@;7Xq*Tn%~dhOS0V{XKDG^t+9^4<nb6j^ z8MzQW2R9T;7k3^^HVvbVTicssyB%dlQjuB|qkmm2IVvqFDx*it)>r{h`pe{TSPHDf zd=sLeAM0}-*f86O36QzMGJ2u*dTas=5@#Ne8jN)|;M0>G#_8@#en+qJCOJcV*03-p z1QhZ?0O<1F&=p`9O6n0;-ovWuVxr=}C1Q*g$84s`Z43(jJ#Utv+n_cV_+Do*OvB`e zR4NFE6HucuKxhP;1Zrr<@k|b}tU5*G!5`#`W%+L}1vh-<Hw1$vj5u1UCK>}Q&L&v- zM}#u=k8}|d{#ujm#}Y7?+!1`s@-7quB{7*S%)o6h2X^8cSAq0Ob&6b*JFqRCZWK-k zQP$nT6I``>`@tU+M>3Q(mguXAw2q!;a&Jynv+_CQGPLMwQJ40v4h|DQQ49oDg0W)! zg9>ps>&Ne{cC|PnCbFdyjU-faQTHcbI`Dm;@M*WE=-mLKN3Di?3Y$k)hO9q30_yYc zS{|yVy07PYv^!TTp=mxHySlV!w{BL8m%QI`QBhIwY@L8ppT4(#+D{xmhlO&V+gF>z zpO@>l0912f3_t9bkbphci#;A*zT1ZY!goeLNBuzN<*ydn`ie@5!BM?@NvuSM+h+6l z5}x&Y-?`$+^xf*j^<<gjxw`B`4P>~ci%bHa4)tWIGF7_lRE=bWrkZrvP*)Km8t+@& z-dCW5gFk3&Jagy)-E9$1SzAXIw<n|v&+X%jH;1GPb`DA4bCWVFpI~gw*}LO1#oH&O z@cDUJ5QB%m=ddjC&C?QB5I2XUoGwx}%xB&hovo!5)>$$=Hn!#@bGc$T+Y8BZp81Zp za;E;5AuA^(v5xDA<#Czq@m4a!tSnAN2f3=>Ys9By>-4_B&|BsE?Q50Z8*FS^dRro% zT}w<?f#vH^Y27?GaQD&#ua(T)8kg7~cPW}Ap_*Ma-xfwHC~h(GE#uA`P0!uGn|ifw z0ftC-pFaEbnK0`2%$c3HK|o%KfdbWXu$$n>a$&<)Upi@<fOk0JZdgyjRLtYvFb+M? zBRAqocG`=08jHVi<yxI7_pH6V;9@6ju&&;#be#RQb5XKlOnpRE@Q^-7L`7=Ov`!R! zC_`Rkv5-+DQ9Uez!alKT?hos>7dTF5l2EPL37|aoh9j^Sxxv|lBf(Wc7!)w&f@YG3 z&IW5(WQByGq(P$RiGr32R5fGFF#4gHhKy*Vq(KKu+>d$H5A*I`;Bvy3-d1GM#7bvU z5)NBh5$-rXp?lV>j&~9VD8hGd>%*-M{|GRI&O^eL;QtE?AIt8z?UanM%^=f9_kDzz zUhFeu5k1Q+^-S>kQ?rlT!qDv$9D1$_wCHr_%CctAP1_(0QfC@y9j?h<YyZrpjv!oe zmCnc`gkeTLwnn_tzjqmsCNFo%jtDl)4#ecO@Q>d^-3Q9Nx6-;1z1OV&zAWybGsk1Z zdUm}8{g~@=00qQ12VVE9_tTfj9Gb;iEYMB@VYZXMvGG02EP-CrSBBMY`&j-|*G$xf z4B7YfPN#ssNEYX6#$TE7AB9CLpF2MKLitA1Uk54mSFUINzlyy)7paYN&0F^tt@%8g zS(!a0oZ&gFvnHEKYJV30^&%Z_t~~jkhO}4@3(urLs3b>GQuWh(!F*;jR1@^v;t)nC z-SffuYYW-IO<p8Qpe~MXFdV{5l7^t=km@%YD-S3RZ#-ndFj$#{5wwe+HA)V~Hxc6S zS-`EsBu82g&Fr~R<Sig(4Wb_LPEk;2ve^g0Itg7)hVPRV<$*KR0i&^Fl@20eWv_E} zWE{fg<Ltw3U`3zbq-kAF`j}N&XTLp^3Z4UiUqmT%LKzAzu8&`zSxvd!{0Qk8by8Iv zl@>d~!^pL+0dBd`do}W_?U{%jGPMq>m_<cxD;rA*5<`<?==pr3-hbUQ9c|E(maUgN zL6A5{|6mdN1h25HM7;CyGDrhjHw3@C5-3?4;AH5ikFuzS)Fn^D_t$Uy^*PX3pil(` zYsgcbn;(zW4D!3FAel;Ji_PhoQpL;GC4-c4I{8`6t$Dfb{h!UpR8VrL=&yM2`?moh z{7(SFe@PE546H4jOn!Th6tzv;LskTzRW<2V_<CS1n<OA;5c)Zr%tDIDE1{OGuoeV! zD`O|A1R#m(!rt5X4uytnV_=Ykq5TZ#*=z$e`^reo$HDmlJV{0`V<ca*4XCR9i!i)e zqqpOGwYHrtDAhk8{jcP(HU0Pld9ST*@NsVO4gn5BkAfD^QwL0~+nc%)o~?nm9dsqE z3dt5JLQ}>2sw&#LdIFsv0lYqnayLpSA{3_a%3XcI$kD0N#3PA8#7dm?=2aRswuJe4 zLGoOr{0gMTi4_YqpXf^)n;=njA>bqqPZ?7kvjoO}H5BwWiokAh?$(HhaIt3l*^VU_ zY{l+mPkI>)MqV)eNVzLCDkziWJz|iiXVy~~LYG~XXkQxv%2_J_asgZA8Hnhp&AIJ` z{*E-YS`kJ{tyj~q{Aq$jHaw2G^N0h*$!X5Km5g%Jq{8{2u=G+!s<9nAq|k=Ji<2)v zP}T}gF3p#O<@$}d9VJg^puzJz-JVza0<`kSdNA}Uu>14+I+6?#^~$&Hzv0v0%@Xf5 zaBE-d_*8;|=Dy>K3p{4RpRI!ZW=C|8xJg1){UD{KciU|APi@Kda;{vbj8)|GO1%EN zSgIJL14eu7hKoL9!LIwO6a1<`Gucl~$cE)`DLF5LJgb6^j=K%M-)6E*k77G=vq%Wk zB(5yrJm}T0{zJ8-cg2*+NAw$)jgD!1h4w|O94V-qOVTger{fe0mSVr3wr?%h_M^1* zlHNjwXfmy+YeYp)$R6Kz))J{s>g=z7&_6ybdn2cY^Jo9@8(6^JVlEo+pjkF0Q1;cI z(uU))Wmd7s9-dHL*mcC0i?4mq6(#?cJu|?}jhMA_q7TJCS@G(!e;pYmPeNF^p9JZp zg2=;eTkdA$>fHp)3Tc|Jrp)?hFy$byj>Olc*BP_|K)Dm^JT#DplA1^h;$vj1y9VO- zxC&TWrWg;R94E9W*Z^QG3Z>b?kYWH8qrCqZFR{Oo3mA$R3MMmUBLSuKD<$o5D$lY@ zWFHa}pdt-Sdn^#!iolYu$C9FOSTLM_^!E~aySsORE1~@p?EA~)Z=1~k3<D$Q5Tqfh zh&za^B7&4DEZx80y}p@PrqP=I=RExdqweCf#!=a#Yi_Mwo^TsO3)&iM+yt~;vK~_? z`5K>K-x(ub59xc{`)t|BKW1~<jMIP~X3#>M7g@FpViy<gnios<(2+h`x66lF@$S(c zK6-OBTf&w9TFbFEc!An1LQ7qD!J80HNB5svd#NlZwpXcCX=`R?k@#kK@nL`6l7xa^ zLOS{4dym(maYlegmaxo;HL_0G!*t#%sBiMPGepi^F(6J%YAl_R*k*aB%?~q-Kd%E$ zRuLtrLx65tzs~UKb<PV%QuPl4?8ic;JSy5<n#0Af|1ahFbXFp5+)9HT$Gj$b>E2m` z=C#I3se?CFR##jsIa>E~mf(LwARRpH<6FPsh9nsOA7k(S8v^-n@MBv2zk>}0iqEKB zBKL|vQK<2-M=f;7UG>|gq6!d<BO=?z3k4MGyY+gDh^3Y)vz&n`K}4OqT2I~GxG!Kc z=dJ@1O6BwAm|aEAM$U>Rg@fVG9G&lXgxOwbJpFhhwt|gN5aCC$vBHg_tJaM<TT99D z5m~{5*vR#rgItMqJF&C&`*gQyd28obID6aT=@yR}ecG{MN*?cHSX}<4)2QW*rZ#5M z-kLeRof@>KOWSy{{s3ebeVa3X*w7<f*|YH9#ZRX)25>v98RjY~My-E3EE8ItGz5w0 z;58;gaz7M7ps1!cNl$qb-&dQQ>s;`&Gljk4?m!jCgy<XRKyaHxGXhU5_B@I7R%~Sb zHs~l=QIIhlF?Ue6A_aizgl>&5I!`n0l<fjL+el*iw3_VpL-#6at(U-q0Rh(iNOHt5 ztP@UMbD(T^XpJ1f9`Ta0Xi>*BXS{(-ImwAMo(Yp2byL@%)(JcsvZYR^Cy(3P&gWAn zZqU_pxrZsU>+6_u<KaV!)nZ0W%mJGA?123VvpDphW&+7^t|zzF5AeK9jJ?i9_1qE| zrB?xtgb*=Ym~JA*9Cs*txSQ@AK{2oAvL;Qpqp8Q#rAaDL@UJ(y)`>Et%lo&j2Yh(o zV-EPlu|*Eppps=rgY-jmqC(fzc2ED#g`UtXP&MAgQ%uoVA1f6+z8CaqS1JvZL9C#2 zD3bx)>ah<kYtC9^8|?8fny@HAwwKDK))P7cm|q0@7qlw}<{PQqc=ZjSe~O@+BO4X{ z3t4_fs7K~MrId7Rq0(u+CZOb|PY4riO(sk%?yE)81x$eRH2v{LAGNGBJ5pPrH;phx z*(4D3S)+hAZp^_SDNM-tjcHTHbKDTBJDJX0#&klpfb1L9)j7UyY&AxA`qwK@LdvLg ziUVt<eqIAB(RAoKD6M)6(GR4w)WK^q4}A6Ah$9`_MF?9W()-#EJ%Jh>pj&Wnk}h<^ z>G=S?Dx1(CS+*;$mQik<9zy0Xq#kWglqP(+*MDxqySr>vK}r@!|JlYjG(qP=L_E`b zG^tI-3eGY=d1=0MdtJC;(?AJj%~5eV(cE26voHU2*Wd8#x8m3Nfe0)=zU<|>a^rL) zpKCxbKB{JPbN7(qZ!&FNfYQlv!#hFdpORO`nr842VGXiaNYgzZGwo(Bh)=p){@tzE zj2vu_$HHr(WW)hYE<hN$9!Up@Q^jO&x*{#XDia9h%BZ2TeZmhhK<mT`ds}VqSeU$H z7g2deNBWp;0!-8dx?vxd+J!v=O6Y~Mv8}gKo{POPMcc@(!0oS4+sp=A_@@VnzDku^ zR^CGc9@QjgbsL+!(933yhL3oVm|-uB9ANIBj>Q*X#Vf*qN)VAZB^V77;{T5N4aepJ z(ykugQ(W8v9cSpC&6z{$z*_NWKz2nX0+$PoT+P59EPDDN1w3Skj<gnkzm+QifR4Fd zC8?7Maof6vtf;t&1>Q}VW`Cx#(Z6>2R~t?JiYrE)@@{rMq`)xDO(#aVr1(z^)R$SH z+rtCzQMgyMs6s(C;UK2^?eAb1@+dB4k+uL3?4Rd9&J$J}kTY}~rCr?=1UiPf*7U^S zn{9iHMGdM1Ek6<&OA9?^NgYm-)6TPomdnO?LWJ0S5ePNW111M9J}^%JdaPQO52Hi% z;AF`q%-K`Xo~nOGvr^}!df3<5xkQ8e9FjbWQ!ldz{K*B?`rwT@zXD8twEw&;8cRB9 zc%9@Ak8(Z<u+vPn#BMNkw6rdzj^TV(9r!hFQ%0RMVv)3&O>%D9y{5v34f67kPoYOQ z-B#++=eJ(#EBF%ni%4eVEhh)~-W$`u8TF8GuDOduwk=2WVE;;7ACS$)>#@BCHZlo7 z_iiPVT9DOaS2N>#rxD8-LQkV@8l$jLvj%rdFie-na?;sQRK&0q<8;sO?FDiFT8-vP zv5Ow3ojftsuaqH{*Xv@tc0!lmSF+}Pg7}WD|8KvdP_@|7H5dTE4i*3a-hcWz{rxOB zn>hZTd8is%c7McBd{5P~!6-t5<?9xiBZY~vki%9&A?%U)s#CZ7tsAfMGc=4SD|@@m zXgjB<(S?xIe8}mpuChOmvzX*VS##(#oVif%uw=X$o{`_qm=Kdlu-P)nT-dR42gfU2 zuS&D|u|bLVi@~xH+OAr-x(&cd6{Ign`crQu{qrD;n9}b%{UO6mog(#Gh24|o<lQln zklNwLwE5MpT`-EInL>>Z6ji$_b`tPT#LW5q$}lKrMHj<`hLVT?qsSr81Am^XG$~V( zUpz}NT4iIU{x+~esR7DdEGi;=W)Gf{TP$LP%-`n`BZ#i9A6tC7ec1{OR@HLlugEm_ zU^sK~H1n!nzD!%l-R6}&=R3i9ers0O%F=Om&r{U+DO_gF9Gm<!ZuupRcg{cc-q1Uf zr((d5&guHuyLdv*fXDRwKZLzglx9JcE|}(@wr$(Cot3t2+qP}nwry70sx&Jx)oX6g z-0og;=i$7>(^)I9W5*|Z)&hVbE2z*PF*&~2ul*~!VoPMJbcmhk_+q#m`$XrO>H{RR zTR^c3)4BfZ9NeS!uUaHyrurN#BzEohq_f?}B2whMUk9Xl8ky<SUE!#H*sv?5Nm~7c zMRF+e#>@arqDf8eOvE)g@|Gg#u;Rz>=L#+T9GJl(M`|c{F{c_ff>)2xMa7+(jci46 z#cn?;4DIuhX|7xyo5CaJQm#U+lN8f)azRZ=Oeh$Ww6VSfI0}-zcMzo{G+#J$urX9` zBVZ%xrWI;SSDD`lhiwe%#2}jQxDE5T_)t`&s)C5i_n56H0vRA;lQm{iBDq0Q9FE3e z@H4()FQYOe;59niittZy4dBrsgwQeRcfg>pkC}L>O|~Q4!Bsx29Hc#Z5hgX#vg}pC z1l2iPcS535$TZ$}OVUU^0jD@VDve3N2yI!H(&5@1Y#onBDK)FrR2Z$$^NpQ5wkl+3 zy+7Z4J#}^kQ&yzO%98~Jc8AYEp&aVQTI4K6F>%Jm%yNpX>KN9~7ENY<{bGqKL3RSs z#CEne#c`6zL`(ol@l$EoltXKf0%>P6Q;N?+O{`0wO-7`9Rw8~mdaXDf&Ph4B%q~%1 zZ4o1^)eD8o6&9gQ>uUDip9|DwloVv=i*93QbZ_1`XIdcYj#K$HPt!&$fobuOkXmS0 zV}d*e<|O;4P6XYt{WJ?N(2rkC?Iph6ZyeBEj3`xwa?~r$lcRHY)~Fkp{;!CRacOjl zO@_BXk95*EA%8ykauh?da~HJ7BGH2C5k=#J-xuo@+_XLFHqm<K*0BIiFptdnB;uSM z$IZfGkJymaf1p{)d6ALiWC5&|Cw6W9^9@EBR|iG8%<`KQ!`7{nSkNm^Uz+*v9+-y~ zhzBk5cR&(A);-vNb3$IA>Ec(4{Of!OU!8FWI9b`;eP=&DFg!BldguP)y!LWwHQxxg zQkITJm`?k`P#~O+Z4F9YSd>Ymr7$H$cvPAvQ+BGMD2>H^k((11#m9sMS*Cdj<)n7^ z;5%Pc!#BEp>enfAM0;kMk!z(|3EzhMm+52-?5!Jg((Ec64bNp{K}<xp_L4r))<x-h zFM)Q8AM|-yw5++9)XNI03y}h{hmWeP<pAcK(QVk>MH*occT{pmh>(_tEWXvrs};_~ z;a;7@8>KEq_^lA$CiZ{GjAZSKNfSr=)D{|PBx%r*rY)>t=*&3XwVP;WwH_;hIropw zfg)OThCr14NH1h}l^323{w2=}?h_L_&YYl4g2@Dd#H6;Qnn?w(%Oz{D3U38r^2U4< zYp5{fJj)3^y44yNkx$81L!b3PU1;@^vqN?6fVGFHf!gf88&Qa&Q?)q!rU#5^h~;q$ zsKd19@X&~WKda(MrU~!s**=VBHPFgr@z5@kE&O?fJVU##5qBk^d0FuMEOtH3yUie; z*6{H#j21Q>t3xTK_2-FR=TV*?Jlas`djM(i6@*x(_)?uwS)7w*gls3_tU|l4*R0$2 zO<4%#EoDopK<7HNI$Z2e4<G=uTIq;QkKb(M=RzCr{EFGi<KxX%F%;Ps&OL=DB7(-7 z;!^+~;j~a+AK;Heen?SdIQao8V2Ij;f22S0hn>zxtit8=eWtYVqtUPpycpj<Mzn?x zXuNF_AHVds_&d08suLkf`Y_$>)iz*qBVJ{}rdFw{jF|d%l;PpdDaeA2IJX!)ManAN zVAcY7e`E~X)5!!f6xB<7;Pc1#!~|2){0UVB&SB^Jk&*Cwcya$J%G3~AKj{hs{`KZn zdmCLxT?FT$;=D@*hY;4e%){4x?cwhNS$wrDW50r><NA;PW4U1O9yV-uYcy(;<WP~S ziNL?7ebNml2TYQ7X+|Zot*`>%(wd9>pplWQ%7Doe56U39u|US3{Qd4A7F>V2X8Fxt zozXvFMT4}R^oH<XpK-p%6p-ZpRY;kCANl`RrF1cMc5(jSRZJ@C4s#3@v{SP4luCC~ z^D?xPk~1oIlC;#5Qnjj#%#5sSDh@DiN{nl+b5gW)@@f;4vhuSu@~cXe@^Wf-_bzVF zux@IS(=$@5M<D-Al$s)zS9AMM*opxQ1Vr(F{-OVXw)Owx-~I#GR@b%9W<>Qp`=wKf zAXNeo8s=_QCU_X!vSCe4Qq^shnvlyGqZ!0vpM866mIIL=8%P~fF`DcA?LE&G!GgJD zo0gEy7S6yb?h|X&AqE}q0C5(#-E7E+BXm2=C`>hsm<S$+^#^1aGw6OHf5t~65o#NK z)5dZr&~!$N%mvg^mUBF2_<Ky}6Q6i;hY(qtYobkQW&)U5FnXQdrz{d^)&t!TZ{#9w zr1ufDjUn#zNeN#Q{YWP3At6d=U#rdFjN$=8J}DXJaHOe5ECh3UI$~}s{Du2h8u+2s zVJc?QztkRp1dSui-zxwna<OTH#?j~%+OYyiTbX4`h&e!#4$OksOj7cbxexN=6NRCp zb`{fkBaspWTOyOx@(4&17)imx-5?gz2IW)SVhYSIz2F9&A@Hs!$z~2cct1pe^3}hT z((2JBdiw;KTesJ1JcD}8MVgL%m`PgP1tXun+^0&%y>g05aAgh{(v`NC{b&i)mwxRv zC#v6{uk@PNUVG?9Xo0}*M))dx`O5c}D_i}f0^9I19;Ra((4YD7T|$_{fs*%0*x^r$ z*=sgN=y3<zvmVobwfC0n*<aveQwGTYI6=gieH-7t@43E6o>Vvg?jh8X4m$l?2!Ok5 z^+}CLm&drXk2@-5e8!Q-EM#iNxr98*-tPQoW8ROK=WI#XP7E{aniY5*Ro1c|3GqN< z^anWC(jfRN0-<k?r6Kt6hObZs&Ze+p35R=r06(gnw+JOwE)Iom@8(vrC4_)tt2$i+ zsYTTLd5QW>si@aW3hjV%QLi`UFVfV%L1gMrsegkPsA38&zZf!y2*|do6`uU6wTgOw zvMkj4nHTGf3*5y@bjJnXrewS00zs=pZnz=TE2LYmFg*?l8r|?fomEe^Tmd@DST^gG zl5HQ#-V!ZnyZEuU@M>4<dBYU1%iVs8jIxx$mAHi#eD*p%+M$Lq?3{pu4{R+%%6{?T zhExvPpBA3A|3(Bsw|d>>_($Yw`==nJ|Nq}J|BDXtpIc{DOVfFq6WMRQUT_nkqzN|- z-W$GJrxjn~$byES`au>S7X!A4Fxon4A2}gDYs!BQLQt6$N_mB|a%WLQ7<MQ2PrX6_ zx9AzGr(AG2o5W8M)2TIXF#_iak*8Skpu=^M{@Lx~TlgcQJ*JT1esTs9B$M#M_~!eQ z6~9<DA*r2x>Jk(<D!7ucO57<g++p};Yp~rUY`(6)ykk;=YNG97fb)=tybtXBnZ`t{ z1LCX4hu4zGUE&CTELQ|nu$Vl*FZ5fE@lT_Ko==hkYx17La!Og$JtA4XkDM_p^CZ?P zxTW_gOc&BKf%B!zC*oWBU1;!1KlJH$ml7mZ1!#ZJh^ylah<tvqht9b>g#OOb{2`45 zp|peQ;G!$d@p5&uzJPqMfLXqszrf$~9eDxWxw`#>)8*%P7yf)*T@xD|{1Do+BHA&e zi)Syt2&`&55?tf6-HZIE5<{6LVUQdwo^Gj&>FQa@j!T;2QHhP3k^&XW${mKayi^^H zifdzgPh1x<<E9hDAzj6ol27&>L>`QTGETjviQ$}bN`q2e8k(=Fxmzhod`4bhc~JS6 zMV*@5<u~Z3gpVKaVQONlnm^5|lUm7)CF(3dXjD;?&yJX0oW2I|P0rBrdqVzxHypqY zbxVU_LeZJBSd=Q=HogPvSqHN17=~f$lWr85BvNM2X62hR&mvP3%FPnu`)IQ@og++s zh}rG$YziW?otk$Z_`ph)XPvYf$niDmBWD9{5R0g^>^T#tdnAlttYpUg2LVWCzec4T z3KGEXqtua@3Ab^Tg{9Hu)TrnT)rFQ-FuiT$I1ZRk^{88(Ep%und|iDs<7$amSF)V+ z!hH<8qXwP?aB#OenWX6~m=TF{Z8ecA-miAnRJqf8TWig7kl%J{RlvJeP`9R8o*12p zX)nUoLDB3v*+_lh#VxhHX~q(5s0>5$qsq~XnQKyPWy(vY?aGW$TJ=va{HGwSwE5vI zqeyh@3X52TrA5`Tu5|&N(7eESjG>%bv`k)SxhCi7g=mEdGZ`8kallDrzGmDj%#LdI zt+g52UsmF6o<*R4up9Mg(5z%DDow@8KI}P7j=fqhi+qGN@0zsz(Q3+-AKDwmqO4fN z*&QvBP>Nn7U*@>ghTEhPs;ck-_<8kHjwGe5qN*5os`Hd%ZFfWSGR`>UnaZU^f=?2l z>1lykRa<X_>>LOP$hWU44K!6C7q{VEBlT-JFlMcv>h&3SH>X%T`Q1H5As%ABs+rdX zHfK(rX{Iv^CsTEb8>`vwfeJ4rC=+<R3++fh!UzSFdDfiOS-ogY?IhF==vt=ml!b;c z*Ui)srZsIiDTQ+boIC6h@FQBj*MwcyBVQe4t${lR-aRv8lJ6AV&<BKm)|>>_DD17G z4T&yzR+C^Y`qbxVJhK@!93ck;oPe}4tJYuGP;@Ji-RP$>GfY0-zb;ILml=ZZ-{?WU zXvcflEwpi4o!aW#7zkSb@N6{lVzo`@;kG{zAdnvomiWduw|y~ap@%0UopXG=i=>|R zd1x)FzFDIb5~jsf$FYw=skV|@VXw$G*0ovW*s1nhjkoY1owx^kSC)97bGMy^U_iv| z@vuo*WK*?~HJ+ynBIe^wK0<Y}#3Fxl$AfNcC}Q+0J+!Pu^Qy1@3|AC-R6zU&pw1xP zjOBqMB}4dxZN5Sb-v>;tQi6o;ns_Yoo{Y2$&?<(iTst!)N}rfe9b~h<<T-Q@m8WSn zTRopEI7b;E!BdRBB2qdWQ-!b|ubMPbHgB#Kfi4f!+kl(b%k#jICr1Y@w$XDlsx5tk zW$<TY_N`q71W(8a`@kzDsexexkr=t{X<)VS|Ftt6Y*0(QRm78qxoXEBU0C*s6j|OM zG<G`NJtH4y&~-{Z(z|EfeOq~N56-N$W?+8JrrC7?Cxh?Qr}k5P1%({@3p}Och|lk^ zAT)zeP~m1yr%Q)>{0nxoSNJQhj%8-6&>z;`O_d$7in+6NCXd^T2nG|>#@RmNuj|q1 z;2xw(*Bgh4QmKVMhiZ9MO!eABI(AfNS^8zi#|EfGePVK?xM=~_MbLGk5WH-vsUgI_ z7E(>B0RuaWG?pT{_=teT@nu8kE?u4@q=`Q(W<0sLc=Eon;%c}sSeyhh7JN7{I4-_l z$<dxuOntlFHj+k+s-r)(rc(=mvJ~3SGQ_X^(IMp$9-SFGGWLtMOmSlJiX~a&MbzG) zbpaDZYm~1nc(?blC)vn&pt1h1-O3ar_A2IBE#Sy8^q{)#Z(Aposu)y&v;R9du8q9Z z@QhD*AptsQvx8%w|KAWFibILfCEwae<x`Piy(F!?z5}P~j+DheuqKn&PRX@;w-o|a zh1R@2QSrF>w~+dJ5CXt`WAj8)Q(c_4HpIxzYoumPMW~`*Hvi-mc`7i%b!TS=<+xcm zqsQt~hd1{D;iT(#h?Q-@d6pj=ga#Y*f)_+~SzFJTy=n-sU!a#6p}U0y@gv6ISt9Q! z(1LCv3kT=t<xw%0RR5wqq1YcSReh-2^@Ci-pEAHBY079V)1<-k?TqN1WTIa%ANM08 zWKnNHcx^h!6NR)sb%xRkCq|0VBM6xr8-vlq$Z0QhpWWF0GKJM1>M;s>1tjKFTvuRw zMA+?jh#Zm~qS8SXF-_^|sE&u)sfbhLrQH`L{>bGl8HR?KVoJMOeg=E^t`@n>eF%zf zYSj|$izwT~6yPIB92GBN)&X~o;V#Nm`Bf&RHn4g80te^r&YDNuCOs@C&Vpxy<k)l; z;>>Bnf}128`6F~wR#bBJC%-|pv$|u31}9U$hHaRCePw0D60vD5-Q9@J+gIEBLa_)6 z6R=os_fA{Chmuk;ico7*rGntPt{2Sl>n$<Dk0eC`(9ewe?-b4V_@fHRr1eKb^I+K| z36nuEDJCrjNvNcdOpNKodXXP$;~aZ#pw(FRD_0-w{8LzaXWOIbX@fW{7k;sEy?cbd zWR!MKsxCwxKmXc~?xgOV1qQkuQ@ZKcn_Qi`8Y>Pd)GQgT^|xc(khc_cG~GcTMNuRx zY2`N4)^iCh*}=YlrrM7Hk+DbWZlKez0o4+#`z2WQiAT};2VNJiDB5`xfruww8CR8i zNT476|Jp|xdyrV3{sW@auz`T+{?Avi{}&Yh&l}i|=KtfV<BU&>P_;(BgAanKk!Ge5 z^$eNi;z<b;n0AptBWFdLI`Z=smtvySWWl2w3`Y`0jC334PH@YQpGm@*KC0EYT^hKq zjMz*H;zu&&&=q!c#vrf!c5;c9!^?A4vVzJvUAe#tEJyX}=jnI<atVSSv&?E#EldsS zRa+dIL_-PA5@rSZJi_+vBVY3;iZKwqui>zGU-gQT*~%9iI4;htE9pI5yqAz%pZpF& zK<tUAJTy>-!?`d;HP215@_|||a$#n|n_NB)r;(<ZQgeq=8C8|d3D9d0!V=Sk-mH)O z*1STl-4G6tn>p#7bE0ZF#>DWWvKR{~lt#?66amblE1R-sV5hdQ#$1gRtpwF5sw@U2 z=a@CSqDuXSTgjbX8dw?1RPVPTuTZ`L`$iU1nu}a>i2T+}IfA&Hv^;ir?;Pc}gDaCo zF<!eT4OIGzmH|Nohd9u=IC3$60mAXyht^2$-AFmmb{xqfD7ubFUpb`X-KJFeDT4Y@ zJqu>tlQ)um3_y07+JoWi-HjAOaRXD^uX2ZY1*@vzInm0m`pq`?jC)Vqv4R==&(Wwp zeW1flgu!(3jIr23KNDBsMN7mVd9QOS>-k;KdcCTIX<3#~nYbK}fk%o@iv7|p#@qzH z^=K93QVEt*A3<|qY>9qhcy~Hh_l_W@S8n_3jR5eS)>)N?TRw86<5W2j<-was9i^6p zG<Vh7Zo>CR>fT?OF@5t0j8k*=6s&Jve$dD-fFI3d%+k%gNq7=hCOdMwu23I(-Q^H1 zmpzS`_VgpSqw;)ODE{|Jq5u3$Q(2h@>C8lS(=fz!su9AzUaJ6~P9UYeR8{EXX>Nx` zDsHC9w<u9Kr`>j9dlRVlM4+(M%N;Af4*dcocZZiZ)Tp}I&x3@62oZ&m&A<zUuY`dV z+9=GF?%6{7ZgYz9k)w>OoV377swAiKj8AS@QJW35&lc?Aa;oSyC}nP{cvsHIVTILk z%;ReYmj_5~pWC>kvCG!lbAUe&`+Vt~(ZSgwkre8Lk&L-Yy%~P4Oe;@1aXJmHMi{9} z@Ris8jlm-V7_9!dKcb%UdIwvn4|j+<NM1XUaOL?yRMuk^LU$R$=?vzmS!p4W*}cX& zK<ZR$SA*t=K6WF4U&DM?`|QlsAo7ZFzRq4gj$ob$(rkECpX`9CoOrh;^;9wiOR1e( zq#cgkcHDbKq$*#}r|oA9M}I6Ig>xXcp!ZD3dk^H(c^vRkb2a>9@$I+u<?-J7hUb6u z#ktu_z^C{vH6d`LbUU+!^z?obFyF0t*+}z=sb}b)XY_hE0C1yix3F5KwRc*b^{b$u zQ@MNQS*N#0Zp(Z^ZVvbFzj|rap>^4XaVM|lpYnT{39aoe2%qKPm|XW5|4QXs8J`K$ z=>9bg5ja!(W3P82ye@^|a-)|XBmUQGM#-D8wRGe9m5}#qbaTj4Z{-Wrha)sm+ic~e ztav{<$Q_o{1ILp*I?hOvPG|X`dj**K>#uUPFjIaPlV$<>F>Gs*V|;XV##LI+pVzW% zjPQ~^Hu?KAU^bHKatKD5pkKdWJUC~NDl<jD=b0GTj+LphaQbTdCYv~RlrQGWxzath ziZf_Z5>%f(f?Ca`$(ne!7p_$rZ%u?>)bwnk|Dq{j3@6N15f2USe*zC`R2~@E9EP)o z8FRspD)(!8x^4q%;1D;&cNb^Uh#$~>och$w*UoN^9nF2!#<ZQ?cu%`{Ee5)Eyz*=# z;g4IRJ4CoekZnzE?5pu40!Cs^3J16+FIZ&e<2)e#X%}uG2mtQT_I$+f&;%2Vxf76? zW6q8mXp>99UYHyE$Z?rH17YbKW|aj8-zd7{ZU)n=Vh*SKZBu3yJ8<b>5U&gTRmgq{ zYhDSH+4ue%lc4VBlhS8SO6~jw(uIbDDZBR;{bv!jV}jMcV}8!_JnhI3y$CjIFWGA! z4Ui2p%`$sUOtECk8RwY)w&&2|{0cSo7rOoL31Ax-HWpX^1ZdKbK>ySD=RcVX|M$b2 zrL&=ni|2n9i(0iot0P9l-YXgiuL`)zO<TKA+^v>rH_a_!Kq*Bb`BAK3S`2CX+bzwc z8!<;LhpiQ(<XR7hy&3BfN=9|n;^*GhzqgA_T^8v6%r+1l&Yyedtijbo>@subL91ZG zH{{YmXiU*JZ=U{6?caif;ASD|sY~dx#*&3<(5=U7MU4x1$98<?_}a|$Qr0P^ve<U3 zH0jHayH18|O%F=VRl<CMGUkcZ684LMR@0#uwbf>wW?eY}W6}*|OJ`=?>h!GinNS4U zyS)CF>F&~9ctj?l;0_{Kt!+}WoH7hh9TLU0t+>*cwB-AD8D_s35oDk>Vax!(!+H@o zE{xqw0%!l;a~00n8Y!aYV*GFdX4<gv^!*RvSjJ<vPP@iTidT9s)~-{xUGrl}iTMMJ zDCNUide+bSmNbmBd*V&MjfO<!xzEp9|D0gKyx1V%ZSt(@OGkANXExHTa3}dd39q`z z=1i}DMH`>c@b-KRLmgV<N4bmUl9<{uy+;1EzmQ;kVf#CV{kLtK!nJ1ifHnzz>vdRj z5g%^xs0ozt3(O#6(($|@WswYPTA8;=eY{1C)gQ92*$rTKy0@!tNwQbE3FgnZ9G6#d z8%KVB(<dVg#Sox8DP4kU#==>;@-slmXS;mLQHQqs-(E%qCz$U8{;e2}f2_Oz;rRDo zSB&dFd)@yqiON;hRtRSN4`7q^Z74}9$D4&oX>-QRg=J{k2yn5_n3a;&B6hO3?S!;( z%K8%M&^LYYp5+|Io0lGRtsIKaIZQnYpleV>m3HxD%IrFy%P+j>lyD>hRze0&W)w#T z;-l|<pUN)|QFN#ky#t8lP`#<9*dR?h(W^$AWE|i{Xo-1De@G*?Y1h@3rIp(Fcec>d z6lq8ogXDk@j#)kz)4*P!hy+E9NT<euWwBOpfv6H;!iWM7)6>j-FQ4P9RPyXxk23Mo zW6cdP<&TX&2Nzk(bwJb<e&`qPDM|&5d6zii_lpV2JAxgkD`<dU?4U`b{iFN!`w6tu zLy$r(CU#X!$Xj<*nG@La;_c4zIJ`h`w!A`c;!Q(A4?iKi50wFi37Es=Hlf1uhHG*^ zRd<ELjWZ!_{bqC1m7>H>Tc}E5uXCa1^lr{#+dF(cVKA%(GUl&2cINUnQezy^FY#6< z3^FCq-i1`R^pnMTu?c*S({9*%r*S~}xDucLo943a88Q+6pXSmR9teo~|9nUMU)c12 z?)j5nw)R_$h~IhohK&fMII#g$3{@d5!kW`77|_aUe^EORl#95bTXU6JZ~AYq6A}ce zT8h<5Pe3`{&$_+yc=8WmFGh~25dMfKea4VbORy4~r^rC4EY)zh*)HMdB<$qE7e@jO zfglhAvk?PLc-AlX;<`i<FgC;z;*RSeGogjeA0?0~5t2bknZZD=<5j(cu|_Nm`!e>$ zVL};VCMm=Nf)p&v@y2$2+rB{7*_g|gaIo%<2Q`xN)?B#aJq=&0<Sf<O_@IfQd1qwp zf+|p&GuBtE2+ILaQ$i@ijJ)EZ6T4_2EWw6&Slp-XH#;?6Q_yghr~1CP?A2-P*g*=Y zMfk)l;Eerc%RNfu>RH`hDc2g(@><-^%UVktD9>2G$<N_x;U_qn6#A0J`03$g*0$Px zVVY1Rh|L)9I0aYaeF!`l9*nbtWj&7hmD7N_rihQ_S*2<L-b5Iw{jn`TPCp5XW>y__ z1kNRY3~G1i)YvxlttpnYaJ5+S&c2L{;x~=O$-)4(M2)(mqzNR=K{>x_uqz!rBthW2 zoQ6G!sZW>*X<(`rN-6L9!8%K%h>H692-PF<kgS2o-XA4z1-n$sgDcm6fzN)de6R8e z^mlp2&WF~@Aep_NnPH5V@5r2axJKwC|D_zGvBWSk&ZZAryy;F+5!Z90ym9My_d4}4 zk+1B*-0>PtybT_(Gq%Yd?+dJC6S&++hYdE+VQ}ruv8yz6$DVKBOyZ?sqn^U9t8k`r z`ZaMlda=CW@WHCj<g3R{duQmKK@G4TIe_jNUik<_T$iumK(Fo->_yJ@&R*(!jnuF* z4s4%O=0BKrBHIlqZMon_k@2ds%`JP6ZEsMMux4LpVdHU|ZH08f`&Ey%80@2E=PbD{ z19AK6OdHH*Vw1N$wbaWgEz^F?d&MOH&pU}*a=KIcDW6jRYnj%@Rlcq+cWWs$hVJ9< zJnnG6+6@fD1(Cxyfht4XT?A!Kt#Gry4qf-xH+d^7NPdT%sEC8^l2u~WRn?DAowgiJ zksjjdf9t#g#4$tl{vC^A|3IVv&h;>{bat_^H2UA6t5BsuyCFtE?->oPV?pj(l<N|! z+9C58D=8R?X}5Zzt4tevrB3tU-aMV@A-ld&Kd*oQ1qqZL$b^oa4c2gA^nRnrie#+8 zX%<SR)BOIdPXxKU5~5rY6o1Srh2&GYrEnpgTv*#E?gsEJ(yn}uJ38{OyLj>t4EKgk zGm;Ea$pX_nQ|4|^o=R!qnnkDbRKg)ArtJD8&j|?#Tjz}0U)!^X7rx!Qq<ECPrk7(# zA}3SIF*K=Vdy=&=k}4TP9>}}l2*#yN((QD-*=aP**#s;$+&H`Xr{gfB*oIW2F5Kzh zogc(-!zHVQReGaxp(PFW;bQ(Q6i%qaP}obRwZ;wdYbYGOE1Lsq%^c@pB7B_Z#N!|@ ze3z{nXzhX-TvK0f*SA?=tq1;6ScPUfUDws+Fr_?0(m<ZYsm&FtXX1V)UYZ=ViRpZ& zrIo8}Wt9R6!ntSZWOW!1zR$4#wJ9znALSJPCr+g){GaU1|C}0Qds_#?|3L6=e6PH4 zIFio&`M}e#8M5J5+paTpT86JVn|K%Fj6LbNChLj}wXVxHl$x_IqD|sB_S)+k>)rE? zSHJ~9Em);^vTv_*?4O6pHw6n7)hcjc&V3#4?gVF|IA`tSL{;Vy_{a5MUS3~D9UHaM zFJ_C-2k#|$J$>GufWF^leX>s%wQ?i`N~;mGdS%U}^vD`3V3in<@4a*xqI7aF9zXc0 zQSOT@9yqI1-r)F2OCt<=dgzz0%INpE<uXa9N$Zi;Jd+~Q%f~6|=M;uMbm@wC(Z|i} z>34Jdo?i~HXSu$iZeC7J8&Q`pdMdM-Gv&p)bkN9H`|F}=DwC=F_qLP!F;YKN$uCt} z>t0?ioqSs;_7%P~OuZ+&HJhqNslJ?))5JVUNBT5U7iXEdkv)OW_sGaxgI|n@_0T*) zjAxO|pyH`Bu2^T!Mo*4t?j`P9`UPf>a4iz|a`LE1SKRRf`(&@4sVfaCt}3M8%9;4; zlHJ+(Ld@mzKuD6&8Qqf<^-DCJ0^y3KXAW4Nna}uh^*Aim%efX%wk8--H;+3e&Ms!J zDryp9(H@Zc>d(D-Y*fsik}pg9;_p?G+w=XYmvZ=3t7&Ubj6ZCEr<bh3sRQawK^_(U z0FBJcDJ}byJwXE<Uy5Y%X^K4)$EQJ~Oud$C@dln6=jgy0c_^Wt!H&K+61pU)7V`z6 zP&ngrRESGpGvD#o257jZ%<oTX+z(j@&w$aZ873VS8%S2$axT#s7|CT$PXfL1B|piA zQ_vgcSxogv`Pm7wTKpNM1|I73eN>h|yo!!q|7=W0UpcL=kT##s^15fE?V(NN)0a?l z)la$S;Mo4q-q|H})FN-z`r6n-6Qj!`UXq$T9%xlHrMvfG8Dp9wb4XT>?t;kE%@*j@ zHaoF8{d}vJTS|EPj3Lk(^7Fl)m7zNHB7KhLn_~v`l)7S@0AomI?h1G9x3b!A{{Ag2 ze^pWwO}uQ)92%<esC=lfy**c4BfSOxlnM^c>WI7(Xa+==qfSh%bQ{H0vC-2(5Y39w z425Az_dps+gV<h0l_<0Uo~J&_jP$yw1(MCqxTmK1n-Y9np!jn+YhZk^sKQEQ>~++D z(IOxIes8)MZA!uCnZgFJXTbbxIFXa{O?d+@%$SLFI|OJ3$Tt~nU1{ymlo!O&axt`% zr0->F8>1Gu!Jp$yll-7YqfU?qVz?jTV0bbAE`Ja1MEMwgx+0IRpz#mp5`!!(4waWf z2|BwktbG7O^}YjbUPi1bil`4Q;?k}jNY0KC*OC!NHn4v87>M7~U}pe%x%pn|qE+ch zlQp$ZTcI&*ij*#^YgVThO$)7P!>DM3`t2{mCv)nbIH5+=&S;s*s^yU|whB{QS@7;) zM<xHX#Lcabbt^;A83}6R_g5kA%~AsiwrpDzR#7t<%MvZ;ljf1SFa)7o5gb&d_#yX& zAn~%VmYN{o32SoPs{pP1P<O~j#3WxyLp0<poSRX0ZNp1%tI;y!BHHkS8Dt<AIn!Ut zyeccyq)msIg27ywVy;6i&z(JdUgl|mxNd}Cd*YhI1aQs)UcU9N*=3G;L?9|_>5g+* zagJKzw`H${PR$iD)suz5;57pr8^E)wtl5LeggCz<I2hta%fl7Efvj<AF-I`+!}<~O z+q$s9kIolKh>bJIpLuDgvLz<MB~E<8bYD&9LH}3+EM<$t$-F#GxxGD=qB+~;VpI<p zw4!HrWG)HLh%5^XFtO?N(`K$r1Eou4)0CtS7DN6DUNffU!^hWY1!PGsD7r#cuO0(^ z6ar&6*kt=zF0Z%7maBo+EqouC>-&7aNs79QVS|o8pq3HCXuL>yLJH+|=z(TEmI;3_ zM8}cUVkprM1Y9_RZzXU^BZavO^P=oK{wA{yfu3gWn)_FWN8mHm5=)y~BhtVD&VJv> zrJTi3i9r)Ew9<|U2jra0gkbCY04|^@(yA!j0t%xxN}&6NQ<apUlM6`)2@-fgy8xT_ ztpXBmC0mL6&(%)Bdp=~)d+3;MYN0BLw06bXflcIDn6w3Cvl0G{ui_%`%PBR4^b(Ii zTvNs`X3WS*(RzaCY%Ztyhk*K7HjDI9PC)D$24bg-oX7X`=63r0Xmv9z(&~bPScq9W z1xRgz>H-WFVlj7ic?GnyW51+1EGS_aNDJW%x)Ro`qJE*j8VIN$Srl!TJhPJEbW$kf z-vU{J0>#Ip1=9E=_Z)7hZkn+@x+GkG2@J-ipm3w?HMOMKaY`0e1Z@6r*Z5}b=#Gl! zk}6yWEvMQ&C4!K1{1ZpUo=<C!=g7wMq?rL`Fr@Gk@BFoBgEx8sPM~@W+Wb?^*G8Vg zT3R1$ZazOW96XKJf%KC2c)>#x&)g9v!2zlOFd)!;JjuMShTSSiXPdO40aN*DuBoMd z;D`>vXNl;_{NMX|6sxpaWP!x@6QsVY_wVzHEq_#=$dqVVubgL{E@<M#kat54zrVi^ z!eij!$Ec$T4;jJ*ct`HQPk>VzTH%X1$%&i7Bz7vnszCyKCWR7VWTsp2)dw$8($m`z zBzOoTa%D*iQt@_x`sTwbn!*G^h7ohGfrw(?%KZt7_+S)%J}<9lC-0jdljY+;uyK?S zoNP@ny|(wD6ph=;cH6G^$W6KRk1p#+MQq*_!5FtW2T`HL>VW{g6!@5aU3ypF2Do$4 zo3_f~98z#zZK6Pf7A07*CjJ!=eo*L87%R#12m<9JMOwNy$60_<&^IOq=9P+j%uj>6 zXgv}X!$H*trYcJZu>o@<+*u*5s%~<KbpTdAW=%<kgykAg>L^yoE*wy`ATHv8XY#U^ zyM^9C#W`3g`IpX7AgaSPL}vGz04zk!BPD}@R&(PpM<#j1$UxBgJ5E7tT?7IYYQLc$ z28fh0fLKau&_3A7=i$`I>*eF{>3X#}9V)`l!}+1!USThXm!O51vR87Ps#>sC)`FW7 zFo?){tWdy`Z~9DQ0RX|n>;lnr%dQzegr5|ijGwqa4ai#v0>Ei_j;~BW#EHlZ^aR=6 z2X}0GmY9{IvjF3CUaQsWh=p6}&taC<Of<8dUxIlc7h_XQ!#HIZAx6HfaxIAlKv1v4 z6Ox9Cab0mO!Qs<zOcb#uckq}a)Mi@?SiX$D?)mRM-#ZRIED>%a;I9J2(1%1HD!4V; z_S4{6AQ2h^*)Y7eF>TBsV@pQk^K5>Ubbk5s{r=^@T?!;TA=ru?sPBKSeS>%AR)Fyg zx+jf0BBp#Ia)Dck2HW-_v}*e)2xhwxCLVt?&a4G~4jlhXDqgHK&cd7sS@eUxAwb!_ zU&9yxWnKKU5DrI6WT2oKKoBh?NW)Rl%6q;E3v>8P3fk+HpO9<$;K6tacDpt=%LOjD z(XJKP9Jq$aLb?x6`n}*Qb6xiV0e)bLWAgQ$*)jMVp{6<WQqVecWwL4V5;fsaOgFJI zVKIYwMXw+QA|nVxRiPZX`xt>>cUR&>?!+#2VAz2>b{UHmE?W&y0&g;HR~PIQ6Qze6 z=>ui>(wK>4%&gj(_^wJ0f#?o`H<PxHNjoT%iK_PYZ;~<Y2|)OL5%R}UZnF*W*UPT* zmw5`bMVCP4AYTcR36LY#=jr>_@+w>!759i}DH?`YX*k7Fw`_ZfI-mf#FkAlM2F9pj zq9AHvm4649Ees9q*6)7#iBbw2@{$-Z#4cp&Q~HUPT&rL2sNbm#gX}2n%dhKdp>u6U z0w1zrBAKx;t`Sm8{xF9k5}PXkSAfFcyl0A*22#XX>|5Hy;udEbLoz3=40u*D5kJ(J zBnW2&xem%wF3!XR<c~T|Aw@Fh0iGqmUP+b_#`i_7l!Bz97gnNf2N)Rs(14~`1!W4z z1}%07B8Z0thQAc;OQ?9E&N!#VCvt!z0+A9+J<!))!f3@y#ytVq`qvi@wsE3?lh*?2 zhZu#@l@s1fHmD+_OBtH*TVgAQS<yBXb&T$JX--NA4lxT4HZy}DjWIc!aBI6Kc?W9{ zaWYexX_{%GP$VgCw8=7SWVlDBu!EihUK<Fv*gAv@b>1qnQZK4fCtT_%0JdC=nq$Rr z>*mhF46gCI>DK||{Pd1W0l*pOXFEAX6evGISV!}OL>pyC@Q3kwNQ*db5FT0FXM|)# zRVHjriX$K3P!`rJj23y2ua@6WlLKeAZs+uu=jfw&edG;A=8=u8!s!#rpFmJg68Q~0 zD50Jir)XiW@#D4#pw?qhQmzkQ;B-r1n;1mGp*mx}5riH9mCq7L55ccNgJYsBK5q5e zhCDX3giKq?H_HcjpI<Rpkz|Z6NyfF3tFwm#qJNfYQIN(8_xle`s=#lWOHXQ~tlNfp ze;bIU!8{2uI|vOjhmD;RF$o*P!(07ECwdm=i3^oZ8OXwjmZWcycM+WJ`W=FGD6AwU zNoYD_NtaRh;V3!>s;upV!~anf%6c>bzptyQ(EXjct>+_r*L~Cy&?8l(Q%0D2{L4s} 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�DhcUQ)IKlO@_GKvw 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%0A02ZQKOV&gIbD@yx^Lp}_e&~Y6 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%0DVA$zpUF5@vi%?Qbv57bH4Jg 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�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�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%0Dt#~?XEW^!K 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 ~`anvF)h zYSi2HTW~a>m3m(lw&?q2^z(KnmYbeeS*5OALp{b@MB0?oJ8XV9X8;q{PM01iBu?UN zRH$Bir#B>@f81$DHKuNbB6V>A(L(;05ke<y?H44$s}!{!i$%;ue#0$nmUVgEY-^;3 zhctfSF3~X(+Ivh5D{hYE9#cLx<V5t2gxD(}w*(s2l4<LTW0oC0zCjI&(x3Q+pLjY7 zLY;mC)5U%S_EJJD%i~o8fwB%tOow2$r#|%QuX9i2{;p>(#DGTQajbg6=mc1{GKl*) zn1Z<)i*6Ec@CWN`z;tl3(9$lI1ywv7Ldp)>Z5)I)e%^lNeJTJVjQY#<sXkFEP)?w_ zrTS|fm9&{YfXwL*KmzV%NQ&vb7~z2~b9Pi%EN~SiU~`>iKZwf+E>zEy?&21tL!wTS z0KmkdAoB4j9Roa6xt6Z+Jy`uiX4gwuS}|MFrT#j+QtVKcCmS5=d()?tvfoGdkdD{& zDJyU(i=6iaatalO7Jx8y5hGg1i}i}EQx~8ev#7{q@J$9pQQt8~2N1Yxk$*QMc1h3x zC{EA`LZ?2f1m50{0=5xv0ZnhgDTaSVfkV}DGuk2o3IU#8Huc4k26o9<vPMrT7uI>$ z@Co(-jd?S9gg>h24__X}O;k8c6i1SNxutib=qNu`JB)@zxA>QuS8(T=Lzh#xSyI$t zlNlj}DDw8#$RB5f!=ER6Tv?M=YmC@ZWdc>AI6}dtsXrRW?I3lS(Y|iwdhi|5qQ#2Q zg-2#fkTp-*qvS+HQgmC<GFiZ;2?PCVju%p~MnG@aTYFMm;?J$(g_bHRxg92=i^uF^ zqA4=3`3xdXbp#~<R)WZ}Xvc*JhQd6stA3lqAg@k@IPZH(Ugoc;i0E#*m$97J9w+DC z$hp;Mo04$H4nZ|SEr``*ZWKuI!q54$#qk+zPr{}N@|I%kvVhWsv+;T0_8AUZ$pMUn z6!kqbi-L(E{AnPCu`Q$SsNy?*u?6EP(Yba3SX~+^n6whfD&5jw*$wv}t5sW+5ky&a z<>z2%5QA=WPs?la>hz{47H>sJ4tU4)`^6H20fY32xD)Gq7E1KP^SUY<Z0!qGUh_iQ z&^l)U?&j~5g!K&4u9B;!XLv!Puw9zTjX;~lG3Z7X*3xfGF_#Zc!s1AcRpV8LD=hNJ z_y1;iEt72_e69?2@|`+FSVH&E)?a0_Yax6aEZc3$%&l14Qw7qcPGOumr!QG=W)YTw z9f!jq+b7p3@Pm4;Msh90Mn+Ris%>BL1wNj%_X=rdhG&*kHV()70#SMTXtpQ?ncQ^V z*FcRBeZWvVBkk@7sQK}8&3@Gv3;$dbk}8U0A}Mj;#Haq}%*n*6?>eVL@W!`JE&;$C z(H=Pm;4VS#L(YTL{g6%EB&{E6P&*jTaB_~aeR5stcMf7w?|zPxmoE_N%-nWI1Fl0_ zI$}TwKhlhB8jRor&Mb6Fq>?80-T{{DKSqigs3rkdG0BdgWPWTT08m4!Cavw7p?Gux zecrlZL(;hHb=EW(a!PEtImc#ie5>T8^i?>lypenc>W8F6vN8NS=%tcr4bU_QZ@fz; zR@jn0(34K<O*l=mi0k?uhY)bNbr|EIXx@sn^DgvEnju*1k8ZRV@gd9v^>gM&QhmWO zjv(tsjv+=T)M7m51xqAvhKRw_6kU~UGBNezqlO*4Z^KOqa@gvYR4QreW4(tlJ$9S5 zG{U0cAM|R5nqd|Xi$;`I4{oojI<`NntSWIQ`tOxWiD0~iIAn{Fh#XauKrT`?pOhs< zn~G_duwCazN~nX{yfKE!kyK+msTw{AM2F)IM#hxAmD*Fc5_7Q_Vig#w`}~R`FC9y1 zsG3IWH~b#_+&2SmTW~>#9cN}zlV74#`B5xD%bGf#r`%P~MmOd<I!q$YLdkk#oNlyj z3@i*2bx2ZnDD+Vm7E7)ONDvPdKie@mXHvd?v3*|L_$D+0^rQ$jC85>RayyQukpU8c z<SH{1i*r^b*wS(2Ouf?=S(od=wy_NPGD;7)4OSF9`%#Z81>n#nH$19q8<YV3$8*{C zCI)-fv?mUgE+FQCcI^Z(%TeG{12@Z72h;gNzYz}|9gI$i)UW@(?;dHfp|eIXD8}V^ zDxV~`!<>2AYVAfEELgMLSr=t^+TNNKPU@|yYKri_J^Ke4JN~)Z8uJH*(c!Z)zW^-w zw&#F90U^n;3n*b%haTq<(}I?(h(N}KJ@mj(F|P%pIpL6^fL=QgQqADtO}AR&HiH;u z&{*w@Ijy?r{79G6m+#YW0)<sPCgTMEzF>n^FQ;<nib5VRov<HJo`D`uPpcbR)GUs$ zrMG=9l##D51-=hNH6##mbff4PZ-O{_5?f7ZS<8$4Cd-n{aiHnU-2$&^oeXi*rYG`2 zI_{5v<BlBpNUdaY>UJC|%+Hwq<pJT&%F^{Pc+9-FI-lE5x4P3g)h=}x8z=5;TER+9 zrPd=zO2Bz9p3cC)k;1|NcN4~9SeP9l3k?O2l>wGRX&)-c1ywI6dZByWNMw2m=i+<Y z=#`muSZFVEu|vW9f|SZO6GA}dkSyUBpjbc~HeMcwv-?XxP%+~zsgOSZi98$IPPb8u zOx{=UIGvCt6~iLhmL$f?B@~z-9T6X5DS3_@<8*MX1VML99TZ>2031T&{RFFyp)Q{) z2@>4LROEGi4ERn5>NPz(9`CEOvh49xSE$k#Vzqk&6B%wln;xBEdrGeio1+F0EjYD2 z*j}h~s=)WpleE>N7&RgA^slWM))p};U{SrTqz0of;p%+^$!)MjJi?6s=*44i6_P4o zSa81pj9o^m-^w~&45N4lt8TpT@d=LLn|IC39yer)0SZPTXlxwdNWtWoIbHrON-#FV zZ(Zf;R5$RQg%Exob^>T8Tv*pzMz~USDlzwwBt^t75r3k_h5}5Gw&>r-P8WGJyKL|V z*~HE=zOXEhcGIUNN=O4Q0Q;(yVc}#qQON$Ie!XcM>aC4t9&}YXB^{V;@%|ko#TEu6 z9LaMML;DEI01?n$X|?V7VbB7&&590=m?)n|ovNjky&NtVh(#1Jf-F#%a(}*KF8u^2 z$oXLPps^PdN_2|v^(3@4N<BVPiSL@%9t7sN;HF9Np{+V>08V~HILpQ|tS6knTu!nD zD17@)Mje#0IEc4_V>%Muq8#Q)2oY$C#3^~do+Q*%pgpJFVIJ_fa6Cse%XwD-sM1Ol zI_-0CqrqJP_U-KAKT~RS6izykPp52v@||7)sDXNm`{Z-DIBBtMM_2$zdyj8kam0O@ zCsWdp@;<7KTbOw|`!f{Y^pUzi(^9qcSL4{fXLv>=<n`IX5pa_(L+!^>QW%*Cu0+Kj zo)XrIH@L7*(sp_c|IfG}lI9MB)U=kV<O82iuKpauDH1RDl>-Ipi10#hzS!S5@4=ah zK=zL#iQmYAn-gJlaLt$}O}`yoR8#4tX)xb8*y2!NJee>1(7b&T?Aq!n(l&dBHKVtr zBhrWT%7)=NDX%L{!6%O$=}CeNK>K7nMl_3PB4{7c5*6!wJl1hPPiNX1;Oam0vhzqm z;O2Fda^_dqb)cppl4C4v=ZLUb1H)L(6P!Mqg==XzLi${(=pP&$oj?~h8-K<~D-iTa zg!5x3%)}z}QFvM*Riu#MSMu`ys1)fa{b|~4_R~DZINcBcFYu`J3S4g9f4@hkkqzX; zj+%vN5{p)WdYgcJ0u8-{H{82|s&o+Z&wK(l)y$NP=3?s*Ep=e)B+H#icr*?%*b10( zhB-ki-9$RF-;pTIH)l{r(H;b1El+_;{nTtghs3N;g)j!U)u0pMyzW&tfGiWKm<2%X zI`FP@k4P8=zXm%S*JsRdw`3J+gvKTM!<!Lp-`XTVZ?>)67=$XN#1X7C4Ul+s?koe2 z&fpA0uEnt{o|x5jx>S#xALQAYq~$9;8CUe<gmmcNX@@{8XX)*4E6G$cgllXIk7BPb z3V9kfN=P;s6@<qA4Ts`XkgxdjoIw?14e_px_|Go<p&{((9l9|T#uY2SII_&r0J;)F zZGq9s6a#X*Sl%!~ciEgvvzofDzLU@dyS4$&Wlijv!RB3)ct~ONC34JcdV+)BO`zZn zR@~V*yY})E-!Z;D$N5ie-R!-)s>{LXo=#JNRbFy<D*KGCkhH!IAZKlHDx*sY&_Z(! zu9JL&vzv58mzVOGRX9*hJqa1!MFZirnhYGCIUOE?Q18KnH}XOmVO$S%Z|2ppYQA$% z<7#)N=1}C9nc*M}OR{8-*724P-u@b=p*!k1+#t>m^>*Z(l~1h}na(VWj960$gMbq} zbDPksL5mMLbnbnb=p=7@5b)|_yDT@KzeD(;Pb#1^szQ37U_aS8@#yRze~q{#cj$_u z{<K5uX}@gaLfi@R^XBT*G!qRr-`b27F`Ai|NVo%BB-BS~dlnNTDdHV_u^cu4WrC+b zl3@wgdLc_z<S97-!QnK(VLg^4<wk73^_XPoqgE9DG`69aHxoY7ij?rT6~Z;LQw@nj zF6IkQ^s>S{f=Q3;@53{;re<NsNV(R9aF=!~X#*|&Z!%OX*-9Nhw?~`C)!DRH2BVdW z{sJcZ-wH;C_45SXFB5l}EfaX`N0j(uPTiALb&P*G+%j<4tDRsM`fyvO=3%G<9S?rh zkYMyy#g_u(J>H(m&;lLq&Tb4|3pk{LsSd|xU%LwVXS^WKky4+S8JnSE8qlv^5-dL0 zXzH(t<0vrELD0!=U%&WZ<~jsN^0TE3o5{FN&O$~O<9slrickC1vt|818|QxA40C-R ziER8u1|~_E``kSyL*ND^)6*t&cM68f*<xN7PJzw`#B)vJ)wPgnZUor?j<dbQT596p zqQqD6+pe8JwK$gi;Y-i%dE@qHa&{*hJ>Ow1pC<qL`SO#MhK%!C+OyNZ4Jjm?OTrcA z67hV0B5GNzGOf%4jb3Xo%%f7`v*x;+tS!bjYip3tj=sOA3`l!hr*n&c`-ED@nF-@B zfu@)i9px;sESq}I1bJQ=MB}|;0g&~WA}ZXm=OWU#D>aG)G(>&dPnHo{8Kb$oomKX$ z_t%r()#55;cU=4g!Ul=@c{^Ok#Z04MJHcOVWQT0I93;wL`K*ljwvspJLlTlvyd?|A z&~l1S#t-_V4w}%fW3gOdA~E}naso`1TyD%*hRg0N2uxY=>~giXm=z_fYQ~VD%JAr= zFzpURk^=QBw?xApF~qt|MoqR<<)Hu_`7PdN;@D)jrk2n;By(A3LtzR_L~)#Fl&(Nh zrOfBCqBYVuUde8T!YfD1Kj4>jClX|HvmXajOJ=6DZ!_-$8_AC72fl?$LH>AY3MuiW zY?eO<9lX6PmVHe?(^bn-K*?3$5%iIOw_<|jcUG*aMSvsWEH0<M%~xd=3}G4syP(&s zuSSWn6D9&9>2orZ+*vMcX>d|~5B2Usu-KAOiIP2q+}+~(=OMa9znodqBQyPmqiSCf zq;HR}7D6{d6@>T|C`%c!ens4U;*c0$X@|7y`OxQcEgNcf{F<~JxgVVE;K-jk<UV`j z-{7=#eHNTlSLM}GKT4T4NMHnst&d>{v?^hGz3dS-BY??HO@X*ueq0<0f#<!#$4lZk zK+cPUndP^FyAnbB>iNS|SyYyGw26`mA{C9V(YA_rL<sgI5>F3x**5;otx6FAl)4aT zMC8rZ-!<g}L1lXsnaHIrjbSdDe%l^g$1pcq_#W9Jy(ishJ17}z32m-cE>1=Muet;_ zR&8qY#EsAzKUr^MhwXs16_;JEtGbJpjq58;`6=ya?%Ay_l-VFzP56daCy=!8O64C- zoraD4z<7lE^CCf;wTZv_t>9q3PEsaj&>_`_<WALsb>OG|zGhbKj?~ab5xn;8f@l>x zWPd8kN0i4p1awi6(E5xvU#L`x^f%wUhenA>voyz{d+Q4#PMeWut%p+b16w%xlMNR? zc0_fA*QEj#Nl)<RXVD)m3I>@}7K_Jpds%YlJIv-{z_YePSQ!nsBZxRwpfL|x>ZjUJ zhsXgG`fMUwL_@7<85nqCBoHf5kXwsuX6TdFP%TV3B;a^S^a^1S9%MRU6j%M&Erx97 zYA!`YbO1lL^PHEq#FMJ}g>4i2{9fzi@v`-=un8Q&gC!sg(U<a}l=?vw9i&77L-swf zWDQNxMuykM&t-vS;R28)l%iq}YY2Ui6v73?#MIQtEh{;Pfne)q(uPeT6x*_DSA-^{ zqa63o$eXt&w8bQRCergRgXXcC5$wnAZX6yI9+%3n6DXB1-a(h|r5r&G4sC`=W!|CZ z)`vDCtQr5n_<M&+Buyb}({#)baAtyu9hZ_uf<43>!5x};O>v6beDpD3o%t7bGrY9> zcv)9GJJ>--vL!o7p$Vhn^%$s;JH+5ieth?FR#R`so52@X*>fYVuGE*K55S`{^Z#7{ z?HIU%FKhx<^`ljip<F;(qu+;Sssb>pqvk(8LL34i;w`gRxFQVYu3Njl(uS2DF#9h6 zZa|U0iQVj7$L1!EZ=K3Jp5xP9ayt3Zar!Jp422%{sbP5BVkD_SG>w$!#2>0qtv6q1 zw5C<ZHLVY{80)HJQQeTa|AFjB^ooe@o1;_m-iwCk)Eho|@@ddZ4uI*)ZlY~8Gm$s1 zi_DH#DfN9wm7EC#lsBICEdqsNN4(QTB}PLJDbnUlyf9uCB<1KT`eWy;jTcmo#RS9p z$I5uqiFc4!4B>?-<{>A678D3n>U+14!RQi0aM(j+2^T(|*E3z#8h?<<5ceDQFQJr1 zmDxhB7ZThHSa^W!zxeh{d)S*uXf>a#zSt1c-Ho^vXQSJWKz#W=A(H?OX;T)!&<9=| zLhXB;!}Nf|D0$08kd1cSIe&CIyC)NWIOrY2OZ5yemzLpiX(K|*(t!l#E*6E&@fhaT z=o8}nG~fijH+MJwSazI8;z;E(3P~M|aaS6!cWKEJ>#m3<Yt+Q}VnkNY8=lW<b?x(= zJmgO^x^RIp{k%qG)$4^dB6vdMC||6%>z3W3tY@WnD~I8n;xiTBFN_h1IMOKPt>|)D zQR_QEa|)DegQ}C*s$&QD$pgHj$<A*mF@B^%j!BLmFW~HZA31fmUsZnedQ-N=Di5oZ zN}*=!55~HAx?vNS)0(#cwNB?1QZ#zo@)&Xs)^lf>G#dt0o$G^jQxnHw$!6>6v}}r2 zc`4CrlpZR8!aykP#?<rlML2YEIPXn1w+QImE)0XH>f)j(v*nZ)!UGfWtdoFU^38OR z$iNTtH_Pf3)K<B9?Yi^du|cuQPD;mEQ-gbN)C9!TXhP<a#;P+Rf*UN-g7QFX_ckw& zt`xl!S)aR~R`t~5A?}hsILPixaKDLU8}bSs#5)|PJCYaoCLsRBQ|L)gTsqUgOH}+z zC$uwJa%4(2clR%z)$IwrohW6%{L<OQ(~fs;xL~|WHQQYP$q4@Ir@uQ*_<zwF$moCV z3SgG;U%LX`8HP0wev8@SNPpFAP3G-iJY7n45$05noNE^p@oSHAJO#O%o1)!x#YEWU z?+UKY%y%(x$QWT`m^eV%Rip1^#NbimvWDYNwOjxVpNsw&e&fO&;}aD}>n=;1ttPlu zphGGM!s<CL>#02n+tqpr-(on^O$|)=BFA_pf5-KD;t#Wa(vulG@qiZ>JS5+}(&sV) z?u0|hL1LB+vH@5RkjO-0DYU%>aZ5?^LDueLcO2ojil;zTn=J~;v5Z~FZp8!Ouk!jb zTQ_Jjt<mmRZP546q~E4&7?cBAReT`!$K604I5gf;y4uUEypTj1!FbPTl&w(+z54sx zKfio2#RA<=pFI8Zu?J4`&(~R9nA{DVJCQkP9$3M`n{<-)BEmRbQVkQO`G%cE)-_;L z5yK*N)4k79!yN|UvG~fv@d$T_ZG4Uo%aSqzI5I%u3%vCUKR9qgp3oEnCs$VxIU0@Q z7tk4bjAN7uXMsX7f%k~)AsUp*>ezVdUCfvwrLAmW(z(+9k&zsyZj*0G)D)*DL;P~0 zj?()RgM)4f{wSPTt~EThxzwd!r3alaD^9-+XJy3t6I2OHOi<{K;oASRSR>HS#ySrY zdb(0B3LxKu6hICViusqkFS0ZfUH;MwQ&aq}vnJBY&>+@W<2D&=N}%rp9?!+au+dz& z*`}T#Ch3d9@obVaP9-4si>o1T9fY;9QF6`@ciHKbb-o1rT#{in?Cpy=SxOI(d_Iv> z!1%`%GqM+XiM9??n})BGO3o~%oGdr%1mxPh-r@H(Z-2>!VL-H5O?N3nFf~2?0c2#P zq5CH(xt|OVfDQ~N$vqnRj&caovn{&gO}X;G)Siox>bawFK6a9*h80ebFKc!Cv0eZK z^K!Z=o9sNF2Fu}1D*RAOVJB*MewId)e8Ih!k_6~DTFk?m1iJ=9gNQpMJ{L!JsU@sX z&{(OfYnVP~mP1@L!TKZ#W2!KB^c~VR8#wfUh7Vx#$P=ns=V)<Vv#%C9ylqNTRTZz0 z7PEw)lwYa&n>=`CR2WrR7`GrY9%UO?gc{vPVYpk=9jD1pnG{3=$=nI?Xl_cb%Ie0A zTM^)ngV~#01+C}F{ujUhlEzB09E-d~cgkKfKVY}XT01P;p)0G6D6h(DSD50T?$?vy z4Mu6!!_z=}w~rYse}Z=vW&l{{HPtqdfB-qyOG=G|V`aM-&>q>&02C*s&sS?5@ynr$ zFgoNi=z2dhagMlvm_WA*;qX!8L|m}im=7?bHMWmdSb3QaJ)$mSV~SQ(c&^ut+WlV` z4BMt}rq6$R`S#BVt-1gByVtM$MOrl%9d9Dqmg6oq>seAJ;s-QWxC6SMQ;f)FiEC_9 zRuP`EXH?_^m&YqA|0bgk!~l-d*vC&{SMi!a$6drSLDEP|i?^6viTguSdU)(jal#0M zkmrl?dE?aoavb5tyas{`O^UyaU{@yS#ZmvnkChw*cTX#MYJ`76tYqHFES68NfJ+uH zwr#{!Q^AvbSTh4KG>R7uhY(YV=u6;va#RSKDl3+6SX5>HRaoy8pAOxj;xwz}w!O3- zr`eJ(X92XRJYVe&KOoKK?6Kl_ikC0G|GQL)mcTNM{#5A+wPqGI!?np##$9k6)0TY! zU%Y(FJs=q_Z5w5OG6Bqn2^7P+(-3LTTf49(k+X3m9pRg*&g$*hWAG4+n6Ay<dI|Y0 zL+DcT!5~>i$0(|;2r*=+Ql+!S;uqYPO;5COK$Zbxiuc2i*8Y%PnVLmbX^BPuJlj0} z2t^x>3{5kuV(HpA`OoBG_2rjej)8PNtbPwa9t8MsehCdn4)f_V<;d4P(N-4{UgvM5 z&;oJh%=Z(L+s(Es>#owaBCeUEuG9`cC-+^zUAh$<ZW;@#7$5N@y5Ye#;@Z)TJ@LJ- za5SOwu@_lK?tKy7afS{Oqn}WK(D;ILw0R?Evyha{W>I1`VLFL_RvVSTuR}{rP%Szb zA{W4R2cA2+kX3WCrDir={7Tn=H>|<9d+zzB)$5POV0{zY&`?}5p5|t)eN{>(z}Pbe z*OumRsM3F?f+Q`T@#H3+4I$$d=FYu2uTF@ZV4_X-BtaY?`au{ln~9iUZ?%Rz$P~Y} z$d|d4$MpHNhVM%xsZ5w`Igg7?R%al`<bJG6UuQNYf!xOI04p(hUXKc$M=z>2Kji6% z6smM1A-~I6ws~=_$;+O4Q8r+OA9b|o9w3J~I|}PofZcM{)wy=$=$T<pVISvq=j4W% zmG!T9oIEqD%ruzgM7G5V{W9!9D5-RfO->FW_x8>#m_ora##6!D-=8tI_<Lu`u8fcT z;q3V^0ZM%BEw$^6x@x?ywpL=M6gnnB52k5R_o^V$B1pA*zA1Uld~F=%LkAR~Yj2LZ z-2uVoBKK4-4X*{}xb#3taAQ2SB2ri8j;A|ec2*$1E;pE?nND<qT+cE$ssFluDB4Tf zPn*Ld`6@W=MXQ59$Yd(%6S?3}rgPwAEoRh*<7wB613+xxhtvWzdIs{E*flxgqHS=P z<P?n}5!uLfO?dwT@k9Sqsq8!78uTG2-ZvM8jn78u$mv$CB)jIMfy}=QSZ#c8d~wkX zQg1kN1v(j!e0_)Ez=mV&-}DY0H7Ry18b~xYxSd@SBXKlToet@(sVh#(0kXvTRm7jN z!!wP)J}X+f6eRM95zOQ#@*F70ghMbe1fbq<b^|OCLWx;QxF|s;eUQ33(|iYtWWO*8 z*aUrt2gB$R)p-gR<!E^FA5RYcJv;d4!-GGjr}u{w{J^j7PV+M!$ntN11$UpS@kiyC z9uQCYwC{pg3Awt_HEeZjjR6V8WfQIR*I=-G0f=_0+x&S6EK{+NuiO}Hq>BxMk~JO; z6I`ft*zu>?y{cwBvanM?KDZRogR^&ezcnG?MAl|Ay%{r;){C|Xj#{<3V5aWtufLWW zk_K?3ui?Y=@fVx3P1$Z94P}k~?XUV}y2!5|-fuFD$oMBva@+XpPweI3PVhe!?!hz$ z_SeAt-Q)EBH<87F=fP5Z;v!t;%Ow?QSuSoLb4smKY5lc#NbZW09IJMeOkW+p`rYpy zPQQKf{r7)*^7Jn$2e?>7qsFKu!uHCq1rh3qpPmfxufeIpb#nU^5GNpgj<Cg8;@y3B zHGwpEIOzGF9+T<=^DzzFDtwp{vN!n!B_qPG#vcbM+qnkmV64g6`QQV#{>V%Df#7`{ zn1kj4l7H_;sEd;67tTUHG+QBmQWDEur{46}^U;C&s^=r0xYWaUY3G|%ePfsCVBpbk zy7^g${!4Bt2|K^z1$Ec!X*y)Xlqu~ge)bfgKrXvBJZN#}d+dPMWIB}O0b?5$#H-O( z&U>ck&bHp2l}qhJdXBattC{)!AaCZ`npvxrTNT*DZ~>e(+g}P8W^`f{&SVew0Z+)x zpx`TO%5nK4bQlP(<iFy3%!z>S*cAmxLGtCpKX^PD1Nh2xs?*&W(ja}=uvAyYz#q4F zf17Lt-U#(I{ALT{{(GC=OHqtGwW{49wjfWJMG4nj_*@2`>*eDJT3)T#U<5U^x4xN( zO((Hy$x_Y!X31GqAt!FqLK-&!$(vx1x*PB*U`W!`OH!D87*v~rcOW%xqFk<GnLCxq zrV;nR;d+a=5%fPq)z@aZYm=l0U4ck4Xrw=c+oqN_Oi0ngUKvorUi~0>GrYa7!-<db zF*~^C*2nV&NRvhZV>jw82X)wGP?`gzBp{__fc+T@5SFgtc7E~jF{Pq}p*i=@i;HQ9 zaQIAe8QN$3-EQyDiL86*C2GF~i3ELnN8AjL^_1UDkzn~g&-5iX46VE%r4Rv&fWz}c zz=p%|*!QvM^L!L7Ouy@w7f5+7Rm0TC!Y_Ne_52O*+G$axf7-Tr^Zez=WHjk`#r442 zF%IYT_i2+yiSo|Np!)ZmZ6khref`0Azo<VP=+qnpt?9nY&Km5uqM)9I0UC!VF=ZZ@ zX#9)ULZvWO{t%Hl_+=eBD;1=Q(BJ>c;p%(~pE5+&$E(&O-)o}dbfNY5z|YRB1uy`c z_Wa-v!;Y`Tk7a@HpHVA!o4%iiR+#6U-j1U{NgXCyGS4Lv)#pf4i}P*peOp(Xvt`~~ zRuxy9pi%?Cz)&F@)QH+xNbA+#Zr8bQ!-jXj(_{_2ynmDoaS$IqiiVL*`G@deXl$Zv z!ts}jR)|6oqnkWM7NOaR=@h7>+NB<ngvTiA{(h%}>pu9@qiZLl``po$y{p)lq_6Yf zP~Yw!e5YyZPk!-4{eHVg^p4b|XE2?K2j8Bk+N_<T$9}0_)O@e8BBr|DsR5kuA^+Aq z7$*1iXHi<?6H;m%xT!F66y(B<IApxx{y#8~&+3oViA~FGd7%v3=;eT>19Q|mVDZQK zBy?T}Df-62YD`J&I7+(bOQz~Xn|JlQ5_uK)e=ijGm;SErD<wbl;em|8erfmh#PLt3 zu4e;L@1$YGOlXhaGT2KJ_0V6d(+=tNc@Zg{C03gwe2YYL5$wX8&E@E+K;GHZd=id$ zf^vI4AC3b$oX<xHKw-2-Zg<UeVh@QsN8%|ZnW=7G%+Z`4odm-<jrtTV-MWy(G3m5; z*nOmtqJb-`9T770Hnc0!gSe_`xU{aPPg!3DtZ$hevu3!wjw1q7ffrT#+{illB0>qo zHn|p;9+ERT8HX{cZ%S2WSfmlWp_Lc88jm0B^*>=mVd%>LgfX3)Tma};(0^Yt6}lFc zjFUo$TMXnT^G$K?gNt5zp35(f;hy)EIT;Uz-CGaa@AQ7{8SoCrSEqH-{yz9YLtz;8 zLiCE{9$U?o3G%XS*UjOB2Mr8v-B#5SrnX&YX;D4M%LnL3-LwzZR~ODKo?f=A<vz@! zm>-yj2OKxyC=u%C(Q}S+FvCT3Uc86N!}U?;b9}B2u4zi%cEkqfaLOrz4eKI<w%0Xg zz0>N6zC8{<8x7B^>cLr7r|tVTfQp)=5s%ot@bkf0C~5lO69G}(y6)PfXh+t0lWZ5i zx&u317%cqvUpZFn<^0q75H+FsQ=EcXLTd7LM#+p=f!nk|WG*h-gAKa+Hk5ioyEm9& z)Wdi0;*0jS2BJ^aywR$nj2d%PU6FUdGV{_*rD>`Um3W{WQsuYm9A1+F-2}ZDfiCmU zT=H(OFd3+1u#wR|J^cT}c>lD_$}5(MB_N)gbZ0dD1^fP@Di6fyp}K&PL{`k#(EY+S zdSascWu5dp&r3;^?;+P@2tQyb`biX}_`wskCqGR7J74>Z3CF0&TR`HoX4Dn0F(#(% zZPCe+E@O-)Nw?3~oqX_V_z+-&SC;t6#RWR(Qb~1t9_<14KsVlqJ2P{k1hJL5#U~8r z<&_VnyzM%z#DvJ!SClt!)9T2M{T^s`uuZecr*b3t+Yjwdu!bwhnFG<pV3h-ot26o+ zuI3}XCg@@V7k)bM`QK;x8c>$_A8{#<Cs#LG&kpBRkWP?P&`Qq@Y%%!`fy6fqmr%*; z+&DE&15EqPm^#t-TYZ%s+h{!W7`r{g{27%zvVJ2DWS@9fy>;T?kU<Yw%mL<%I7kVk zkqwh`A$XQOFdre&&^8AS$)&G8JEFI4Ik^Lm!;sItXRL&oD&v9B3D%XGj3yTsW^yBM zrgl>1iSriDlIx;wH`!A8wa4>!_nAgEYc$NJww<&HSZ87ir+}%zzKt|mdM|>&Z=eyF zy)rkM<3tUD4y$LFeXij`9vB}26B)pyuJTs<!NG2=FzgeHGUgW-jid?86K#=^JXJ*1 zB+Sl{a{3A-cw!?#sG6b*LiWR8PN*-_(YO~#HL=Xl2tkGBA$;vre&pMJae*y^C8EdC z(`(ftqAxKBf;&v+A^$}B+QuN{kf${~{_eYj=P$l}IgET(SCz$1gLlu^ztG~I!ov>g zH7a0R5(g;DUnj)EN5y^-+ZU!pp%YjVc1pxP0B#w6zgvE_5KOa(A>-Fs(OCO*S0ycM zWPLWg(+%KsTY>@jA*!`_m~rY)uZ&uYg-}9=PzE&wc{E?@8c9N)*%3Kpj)re&BHscY zd{6C$lh}LoC6Bi8b55DsYr0E+PV#0>1sse$mK9^m;%vIgFwb9(fdH&AqDzHNa$weo zT|N%@B$;!;h2X@bZjlr<$J)?$rRN-uA*nfm6QbBFQ{o4+asaJ#)^gt-?M&f(LeS|9 z6W`)pwK`j5iNl#g&35m`DM6Fr@DmS^*5vmkZNe~1SSPBUZS#;|t^{w%+*1B~vHl?| zi*sCrSoxEG7Hd3&)F3(A6iZVy(XLn&wW;NZ_H~Cw<xkkkFJsvht74heMvVg9ixSp) zd)u!`2aCnX*jlLWDk=?%ai}fE9l;czBSmLUM@M>4sqt8JpeG?G6te}whGf<kh6R%d zdS}VwjG1t(w`=s_H=KK145F5j${>Zo;;}W%dz1VV9|x%lF`Ic**BrNW_}*3zhLc1l z)Kw?bmeW8mhc{+{VS?Fd*fpAHa>5|+FRDDZs|K^pYdUW!(YPspjHCez1tE%9Z2ci$ zRp>wIVXginEZ!sS&GK4QZh{&;<~c^<k#c;w%q5P1{PGE-Mnl~U4K(&-tS~>GzEN=a zy}@_(ccji#c1OU7otOhC#QVx-BX+GM3Wum?U)2QPp#~k!oqp2^t~l&ypf$emVj(#L zMrG63;utAmCgmYW84@t@^h*$JZ3ZqTD<%$I(cFPEE%kbj%pVi(qgxJ64@cT+I=7n% zv%$7fUWf1*(1safWKf+)6|y9amSd*SaB6W2-b4{~{9B6-*ei$!X0=4%6h<AyXFlet zS0tx!sHe&~>bwc?bKgZgcmfc|qht6J;{v8FgvVp!Mr@8)vpMqww0)C&+|*`?5p|W$ z2D2J*3Ja6j!%^8r(3*cqYjy?}YNoN(w2ZDylxZ6dyWi{73wpuhLO*}QF5|j!qH3kF z)DNN?tgp3t4?b_KHpna=USpp(l;4!$PP{Gme4!{Mjcg~q@88g1lVtBGOBKc`Wt0wr z-XKs4nM)O*b(3!v)xmk=%iwljJgmHL2Xlv&wE~pE+Pkf>`3#GHJ%WW6;XlNPU%l+M zAH&ZBYXv8?-P&D{qt#Q2kVO-ZN~HFR2+;Gl@@fp7zaoaVyXm0tXV4wSn4=SIIWjuE z*b^6MLOcl4K`2a8Rd1Ko{3?jvo1hCl(Bhk%Yi?i$5|T|U1cO@E4Z@DEt#F<DERU9U zJO4I%q{C|U3Ie;t3IJ};N6j8GBz^+2n?u<!L$UYx!w^l{AL~77_f_80=wwoH51-or z+2QUqh>F@UpTd1NZQ$~vX<}P_FJa8uQDWYZH#;>}Tf1E?E?3R{n?(jf;g<ftP?5LP zhwvR1j(d1N89kgN@ZZ5BjW3V>_=ksZIL|RK-S9sP?I({P1E>|6E%Ensb%p<oEac9z z*)JwEEnb}c<lE<e`{DR787hvUySPKjLs3=4^e^-G$>`DJv0KZ|UIa(hQE5I4KWB%@ z(?33Zq%qH8xJ_RULPY*P%v-K8&1-gI07MdUiz4+l#PKv>23*I_jF-(Usu#@fM^(oN zdg$>@W~IIEu1*Z<xpV6ytGRlp5W1cEcrPS}KfQkb_IUc{$5*djzJ41chtFU3kig*p z2FrOy*e-#8Z^~s+UJYzcBu5R0$<Wp@7YTFNLk=Tevf0v*H`$Fel10#xbweyd^5i=$ z8COG8)k?(9$o3Rv9fHlWjeWS)rp1Ijl$8WEB&#dbPp%`ENct~=8-uPy2^eq`VuS6l z*CUVmZ&FIfGva|njW23Vf=@A-Ab;w=+7S8RSxu6W!bJOP0L<)^PF7^TcQf(VuCw*L z+%)^!z7@p>285HLwF5^v@)ZrGId!1oS^9rDikP2$msjB|pCe&1>3;Z1FQg#gWj?>c zqG)H;bspD4Ff`1NK#Qp*-R^<~_!);~)1oc%B_{54=^p?<y^VfF<r9MZZ2hxma-f!Z z$B_H>_!m!7d5+V_ccBLoY!bmTN=tBXUKe?}0CvvLWRE`vt#_q2j1vekEm%wviL<b3 z`3OE~CB|!4Uf9$swjZk`x~}47N8s=(4gKo7#6Xg(Bsg1kXmQcScXL9nM=4(1x4IKz zjhdDQg}N2`7xgak+$vq{)b7ypKA%0-i+jngDI(hAu<fg(Td1n9%p`k>+GcAs3F^mF z6V=Cg=n5<o@P~5be-{J=h1`4~k6CwnmII<S6|IbLa_SFPv09^HNy74US)4WGT7=HB ze&6hI6eY`DFZqaHwLhMeve+)VQ5_QP5#h@8OpgRiRCthA>d+Do8LVAjFb^LixU=c2 zkp2YTKt4xOF8tcMW|=+4S59JgaX1(6k#-LXC4ci{JPqntDL{e6vFi-MSbd>AIitkM zGU-L>yMn@F`!5D~dBROkP0c|D-U}Ol&fq*;#c`tJyWZ0qqA~RZY{_~wKJ6li-8pCq z|HgJ0A3Nl5w&srVup^dx8b@g!JKnJkPgnyRmx6kX*PRiC3YgAlc0u;7a(%GL#^HfT zT^wbsODb2d`|*VQPY6%y=yUyW5Y$-h3Ze{kCcjP3yQ8DxUPx9YoZ8<|097)$Gg+d2 z><2?OJ-epU=9~A)C0y#52J)P*&2o_~u^8K{zpx(%`&00e{D@J4hbj@j^{P4Y`y4#L zxjksB2L_BugGS-&*6Ld4WN;)yGY2W@=xt+qHgpn4Xy7qJ%$d+p(IBbVDf*G?$xNJQ zoTNw(YgS0$AwiPzn3U{Xr6-rg>NQKm{AuKL2Ta~C2*>py+#Y(XQb!OUZq|ykB?r8i z?ctNdWi#fe9Qc@y;4k@BbjU9%x1+L=7Pm0ZhpB9(6Xg+)R8*;RTH&*jm-7n!=8hyF zcEZefb2Jd+?I6ZPDJ<>qPkb;b(Km4I0)-OAn6J7C5afm0+}QV`&@xc;HrYl~%o2j3 zga7aU^M8BRl};NK$>&c_iiQk6*k$_YXXRr(JbKAelrRMa{`mIWgFpDHXZFzTDG=ec z?4%^_!JywP7FVd+E0~+5Fo6?Hgsxd4LyGf@3!V0`hn(#v&+_YhS*<Cj1X?uJOXc^b z$Qyg1y+Ho~v=Ctfx31_$H)W@oP;X<PjbpUbo2daZNxI))>bTH%kGMQ^8SFUo+3&|E z4^KT9@VTyfM}1P4y1Mp}?aiU}kvk=76@M(zJ1<{^%O0$z5X$Ik9$bUSz24&g^L9=} zDsEjV=UnO6LNJe&Z{m2@^ivFSbXFV(HZp6$x}6t<+PUDW@O`N~xjgi>beR&I!>*mV z<^pax2?&$#^j*U?42G|EcuKb{x?^-fV_ayKDBi?N6&)DkO`EPI-uu8NO)>2?iL@}- z{nw|8k=6BZA{@XZ1h*>&HUd0^7u+wQ1M}V#i18A{53=p#qaix?z->15>e6FTXAnA% zK72Ip=9V+$S9Ti2B2JRX*V=tU@{P_S&1USZ5EM%%pIWSV#qtIE9`TmWypC<!a{L2D z5=Kx;N@{ZM44xabNJbt+f+sxaSaO<22S^EAF|dYH6<=oUgnaps>vKm3LE3C$W|X$T z<b-b=tkJCxJk<|36*W0>UJ_uD3doLC(pS%Bejp-_a4PA1F=s1hEzRF&X!x=Pf}?wT z;w>bKqLEgz`P7ZKJ>(&TWr9}|vNU_yfo+{&e@9hiDXb4Yvmj<>pmx>Bn{{gbbe-|p z;IW}Kg9ZllU`<}`v1uxTLf;x+1s{u<TB`Ys^~~`)1|mgbSnn6%3edmb7UG{oj(5eH z$=_F|T+i0I`Abv)w)GQdX?kPa{PS|b?is`M=5ndkSo@f-6X-_7<|QsZm<pNq)_p@& z4yYUgCq=egvZ1yj(NGKEQ)&`uu+9;VH(;&kPjyl1+rr<A!sX%c^=*#9S5xDdlO4CE z+K`^>60HTWReHI2)(l(rfJ>Z5r$v(L=8TTI;)}aA1BXLjbkPwQBY_ash!!uGNN|J9 z%6#X2hNI<Bj{sAnV*5yX8Ba`qJ~Ul9njm-eSX+Cv)kTGBQy!g3dFYT7uoPPphe|0U z4ZAuV|29hi4{;{~T-!bWC<M**gsyVCP|!&2kY)`!+<flio5^kkOjKoX+j)X|a z-6+<(i7wM%y{=51iaXQ1Tc|$8ZP_chQ>fDdN6K!qFtc5Kq3-~5mhn(+gyE-1BiH5= z$cDGWZh}!c{>bC{^=kCst7iOTaUMEa6(m0@9C4J4`oIDCUoP0!oqi=Q2~(`<;2mCp zvGJK{uz;lHzmbmH1IVXb`|fkC^R}k4F%4ZDBN>-kQ*<=F<)9J}70;5fs|j-%Q~9&= zfw_F`XNnw|Z%pR_?NDTiQTQ$$DHc^cy7gB!rhqF*(1(_(A~J{45j{0|UI{aq)C(P< zH~PkQ)X%i0KKq(G(m4*4X<oYcJM4q)Pl%-+CuaUs1Mz5wC$QRsJ*87*HBzPxvMbz! zC&olrNKdne7F%>!#2I>bWq?Nn3vb)ybxw`Yq~8lEy7u_RFo5`?dRDL(I4_h<0m{#P zrq0Ip+pqXD6=Q@=$ns#DjE1JRlu-eQ0W*+=5pXwh$j>e7TBA^6CFjV&FeW0r`9@<e zDYF;u=TGoSi!MS`7!X@|+H2M-U|kvHEHMxs$izwqfA7fq+81m{`@jm?(JNaOkF>ZL zdD=dCW{4C$3qz{Z=P%#HGW+f)&o4?0H){P-zvr$P2<h%8;&80Wuxd8h73_oMFN=Jk z5idtij<Pag7$f>CJ$~`>?X%~vNn4D6K6(1|_>KMR#mnOtZ)1V}ox9r*w4URqcTqxJ zL$#k<Y0vX0KmmD=KHT9hjZw4zF!WTRU^d6@L+h`S=Bilxa|$X5_Gr=AB&JZp(|`0? zd{le7xOT=8-^#%`T*bjSDnXY<XDsdsCNsdI+4KD9+3{bGzkm5^`t13ew-ZZHa0g3t z&!Ol)Ry;?}9<xxG)SOqA8nTW{XRGwS@#A1aI|St+z*1j$hX(PZ`1pwaNy9SE1`~?V z1H&yNzO(8fpCreo_8r}#nT=2Zw!{+TiGh%G?+BzaV#ep}PUkt+>d83)L72nbyrk;` znW^ZaOJnNNHSM`hqelrxe9o*`O^MMveXC2CE><c~*^t-hdeprSC&73;eFa70D=1{8 zhP78oFxFQW0B8w3=kwLt=)8tj4)A$@X`HI26OZX1cjbjhU~!5k@mqS``MgijXtU5w zMfb*P#@m}q+<jCl*#R0VC_4>e>d8(cQclXi90-#E;J-&D85xwXkX#m2=dftBL5avs z@1lq#C#*0llwV|0w$P7u!yoHGo~iF}X)pl92_ycpys*n<_L-DY#GFaVEb$w(YCo1p z5u;5U0YS<xY6?d|jc9=>j!FS3^gv2D*;SkvHSAW`^VY}7#T3a9c-p{G(fp>4FEH^8 zM&TvJvhtrd#dWqM*I9;?0t4u&UlqQ{0-*adU+rBz?lPakrEYe3=2NxEndz!;J8ev& z82)ogN?U8?QnI`|3CCR9iwm1$Xsyk4!Fgja4Tg@!auWN<)}9<&hW3uyT&3Xby8Ow0 zxmf>w#yKQ47!nTrBAlt;effxmBP{oLKuNL0s4$g{9m&xfcbQTGqwFXawq7oeNA2^P z+XvKKQ_dq}s7@9e%5A;>2!3oky05J*dybx+CfiaIhLFVb0uBn|X9xau-7#9FY6&8< z@z^W-F;3mszfi4*%OgUP*@WWUG~c;l__wV85$S#Sk0#|@<iT*bWHdJJ0F41TQ2-GA z@fbAe5A^aax-E4&aF9}a&OJX;%2>m$dBJHh3nl`lxpGzM1BR!yzU+;HL$U5CRNsZ6 z9u6i+rvu{<)v;`J&wpswaH`WDTF->hqX0T?nH9K95}ES(TOh!L_48gE-^q?**)k3( z|JI9<v<Y=iG3eOHrTyG7a^tro`dT4+9Vk^@7-PDCG7bg$#&hKb>GKZ0Q1wGjIG?p# z?^3jObhp(DOy5cn=2Z>!eO;9*^Jq~!^(VFU`h-`;sCyF8e=@JR)`n4)bA&5R2?TL< zWZg;JegCJbzRGGcI3n2nFCRZ73cCO0qc7rF3wk|_rPEY*>I5eynJP7748}Q-lXf&L z%5$Bk0n_4<Ew3o;)OIC-RW%_g;cK=u{&uviHhCD<Q8KXgaz=sDdfvgA*;?gIONV3W z9M&18mkucuT7h;@r^cOG!_lSBc&#J5+(ng+m$Y?gE%O`e0?f)?bu6y(?NMyW$Kj{9 zV5ez$YA=hTF-GoDaG)lnVH$^|-o8IMB-TbC#<T(*f^Hj&)<nb#OF})eV^94vVe-Z7 z&Row>H61e7-4Oy@ni6tjb0XyVz@#|LO;PwN3HZM5;6A7>KdV2EXX(4rA>+UXgHw&T z6<2kQNG8b$(_nGV9Co}TKI!qpaiXA+Lo7<IHAj2jHo94=zeb0IJ0(xHf^qXeeGce$ z1IJXdDa&66wyy&-m4T9@I}U)8SRP4qCZ1m{(V_0>@z~^uTCLM;4s1J22&39Z5mt~w z2JG_Vszt=l7y;`RkqJo(vlSrEay~@WO<Lm_rBg>FBFOB5pZQ(JFuJxi`#K*RjpVIU zp=A;_eL9T-jYdSODLdYbqbWLk0{%p~=s;s@m-h}#6b;iB?=oNRQ-)luU+NCDxJ8(C zXsE<96*vW=;p9ij^Sc0PW}@JcK#9WVS)Odx7?tJ}z>&oT8r(_=V_8eo1|U~80j6{8 zB28jU&~4xpFLa^<sZD&&)VL_%AE7WWVjhtsx*TzE!zNq+IJTH*iOC0fv7?kf34pNx zy~TRPOFv5UcEOX!eU3M^_R65MDrsZQSv6+q_nUQOakRyYrxVoI(~>RFi|eA=G|TOQ z9Hm8!f{MuNhHItqyO0vz2qQd4H<YCyCJUo}SBUO1dP}H3a!h>pB=s(xd|}z*@uzQ! zr@Xkk;9+;?X|-Bo&SW@SWn&1xT>|oIQ^_?Y^oZ2q9hcABT}U+Pl_CW|bzQ8N<!u&G zKLk&@!J%1aAfFTQ-G4e@X!Fpb;=1$q%!QZv49|Y@vZPv`me)0UtOC!l)>&b>%ucaN zCntTf?zpQ@>`^0o_@Fh(T?oA{FaeBPHNCs^(&2d67_(U}e+AllnyFpGGj29Pf6gXk zFoVRKpo|aT^$S~%oDabRbn7S2dz;v>F0ioKdQ$`Y&fe8(ggVU}mb10y3=9Zo(Kt** zH=S=UZVJ-m&()B|)`HyAfHJ16>+E8c9m2%s)pa1^KCMbFhzZQ*tCw${|1Ej^@ZlfO z86$a05GSZAoZl>x(HCQ*b8SRAUZJ6gCJbv{woRJ+*qB=GW(wIEMe&WsSo8V{?=cc_ z$?b7jtO_n$nkG+>9^$F3hz>)KLd0)KU%alXvkd(+&+?W~qA&wQ_eVVISeKzP`7N3J zbIdk$)Z}=D!W4l~IK$|drno3gqWzxTF#~tynUI(x7b*EkD6Wc^eP~=Nq&Af7Nr%R` zQbGI#4JU`)t4c<~ZBA^#B(3o@F;(h12wEptSelB>9`&Mh*du>nhOfOoBG$uad5h-v zlCll3Lk<x>WTUd$EH|f1i>@?wfN=oEQEZI&NYsUFtcK)$;rrD2%CC;wBYB%BN0Ue- zoUbOs1}4=g_Z&3R<LVCj7cQ3p)VppwXBV55ozTuC-4hkY7fi2T4a;*3bS%M5&X+hx zCaK_Rql*Z_%x!2ne5y4jwm~Cll_TPfNvqtamBtDE@bcM@-yhTINKIL<dhqV%{^(r_ z{~zD`m%%vY<PNYj(-%*E2paQNm^~SN^XlKQ)2~nd<KXoESRvs7z5;sNeM4{8TYOJp zP4N~M1`?dF)B8bQ*l+fNU*#Vd*hH`;{p<1TKfQc&oct&L@%+0NFJB)&ee&jbvS(&! z1Bg&Z_zD)gwMhGtO@DPR)_&A)U}^XTCPyq#amV*FSv>hl-4#WoRlB0+#a(QUMq??! z&A*~aU5g5WB*;ba&W#j|+e&f_q~f%(S5P)8ZxliFr(mg<e@ln>$b0pd-;Ab&whzjH zOMivA?`f*G3I7?ikvKGVopkHjv~|}J4MAaU#v_x7(avMi8M&V>#f~?SbEqEcAr`K* zC(yO+g5Vj%U?eA>H>X1w({Q5Ugi|(bu%0SnU9Z#Kz+o#^y#WU5^LmVPZ=~`_JhL!{ zkb{7Dgb8vc$-S(`Dhl`RUESbsy$Wpl#>t>Id5#;y1lju1y2})8PyHT7Vi^S$y@7u5 zUmih^m67yn9l9`hmDRAJZO#`5rGG{Z!NMvyQ{e4TpOBhyQ4Y2)pwtXVGI;09ZCDrv ze_mZ(n4G`Ou0k;u1C+wAFB=)fHdE)3m^yhg3iqjlHr$xzsIyU+M*#}?o69RhS@_6- zVvYOBiYqip2-U!qBjk^7thI$IRb6=UCIl2U>ql5t6dz$((MiHf^l)c(J2uE{$~miZ z>wK}9+iLF<b&!2C9RBoc_QJZ*lFpANz{C?z6EqteP2T&tUR~ZDK3REreZ*=svMMY= zn2L~aP5w-sB}?vNgZZ(%Dyy6F7(W?FX&=+^*jDh#JO{M_uZI_c{9WFPUkZJ@BF|ZS zkoC@!9{`@3f-+5*q53LEchupJRhs;ibBq=hB7wVf#vjfSOrCMU+D!feuh6FQph;FK zl#S>u3>7Ecu$EMYaD)B}K<tv2s6e<Wu(;#7=Ll<k(v!f0Ed)+EFfvsP2O20iktH(2 zAo>ClEf(3T$gwzEa><#iVx6F1$?KsG=+8D47LLjs=_Y;+m^}S5TI4zHxkgZgW(vbB z*ARt9zTW88r@>{5#f1AY<~QJhfrge529IVieAnC?sy{8Wm)`$o9K1V<wB_UY^~t-N z6!^Qa!67`rR(N(zoAEahFg&@h-<_oQ-knm5e;FDE9sh|ImEfC^ib^!~JkD5Q<qvFz zPDHRs%LpBmT6i;nF>{CNST$?5Wt+VRY{9Z?>}iMMvG#q#A7P!)H(e;>A^ncCPflox zk7Q3DpSn-qTH;IqC@G9%=0q({c(q_Jc<g^RJ0yZQn|a4~HsgMpQ+UBL3poi0Hpi(u z7C2av+=Q6IeDeraqm$q+C~qTW?i`<xtTqj0;V8;vNJ$iA8JKA_LmMWXb7K&ph<<%I z+E{%dk}GRU{-{(TLf-?zb>dW}gc_X5w!zfTCqw>ysFG_LPL^KdAH!26(n(EP^M=(Q z-BfhY8^g!x;&n%V@U-zyxwW`=M$w%mfV_bdYV+^p9o7{X)XV6d>J9j3nCxpu{)Dp} zzDGt__Y$x#DLxEMkl|)U6fHjs$-3<XvwJa5mVTYE2l{OXy7Z1ReHr0KMWmwFcNV6o zwUMRxQ^&sGmFgv_)86hn#|!;G=rdp2(}s(WO$=7(W*FSDPEB$_a<uI*pxxm=+n{kC zlUAse8Q4`7h=F+(V4uhv)TpVf6jt9~+`fl+5z!8E==Lq77+Ry7OlSX^U;~vChk-k9 zgB6wd42p@Sn@<ykPBsf^P3IhLOs?rN@-rX7=ps9;QImogJIRSq>qUqY%_1o+5GM&n z_bvXQ{<$bO`5ks!-B69CyvP->^;8zr-)$b>5&zp4e<717X(2uh?{*y=v$QAl{8XO< zApzaf-%Upa0F2HK7_x@aE|}O_@5BWmARLj#s+@Bj;*upcbqvCo=*S_wX{ko8aq9Xj zcL^=wm`_Nz$GW45F|;R@;b0^A)QZLq8b?6mjpYw4bF2MSg)xTm?V)kl(6ChL#h@2i zP+m$2zoFO;3eodmU?n`!W=_d(geNcII?89{aOgWiqK}wB8qqffx5j2&TnR+QzT2Jp zsRoLGy0<yim!ZnN82?wmAJ%=5$;HhB6A5LP_SIj$n^Nuwou@6Lr}K1BPG&cOSS;Z= zGGGF*{l{LCDF2nLK={GdEX~oYzfXU7^1olcRxicW(&@y8cA<(^g>?lvmGo$kuHi;- zz>t0c>p2uzbLd^~GO)P+t?k5&-*jMin*R-Sdv3m{4Yz*;jjV~uE`i^`YzoCE^RMB| z%@QFD`-k61m%OvVd>C);)x+CyyZMoNbi<jyCW#65n*_K*x*Q!bs`g@H)_kJYW|FA& zI6)Yv$-U%v7J%yr&TTCFnQ7o{F1#>gg#1CY;EYav{!Vj6$4;XY1?AM2cSumc3>acW zh>Su;LM<a+T%WWq1cZ72cwIZ@!GVNbV};9PNR|is1%M1YpG0|iua(_tf7+cu%y2G8 z-o(6Sh7y<w`Y;@&P;1~fG4#Ux;=e}3Q#l{73=V2e?TPiXy?U?u3l;U1IJ(T5%lT^E z^W`W)M_|Ag1uBVH&RN9g7##1&%d31Fc^coYkR~B59LAa$ObZ%#RoZaMy;^&GIWn2N zab>(um(Ef|EJ9BjRQWXdH*RJA_8{e-2I;6D)WxR_>gyelK5bB6?-*46b1x`ipp{%e zElj<WDUtB$v=dAC-1;eb{0BK>vMZtFt^)B`sY%r6{iOM^DIM*YQhDp@7+tXiV`9(V zUIK$Dc}-i4`Y@_k<o2_FZg~n>i#k-5Ps`d&%B=vh_umHis^x=Ci8X=QgNxa&<yPY9 z(j*xbX`W6v&q6mmdY=l5<B*Z3kT4U&X_$j%)Ymu+RInM&thZzuoa#?YF%KUo{m-r0 z<?TmsnehKRER>g2CFm1->lwLE%_21hbOaZp!5|=kg8*lvZqK%=kNS{Z4w~HSJ?*e& zYK_>`2U}uVrK0yR(hXTpBGN0Ti#Z<{|A@onLuUm)j=Lr}<@J}phpA@^O8%6Gb9UoQ zO_LEN5=D%|V1j@B{U4$qxC9oRr?<{_bUygNhM@7s<O6p1aUcdE7k#MEAhrj6c+q>| zeG;Aref<Wzc_%zlgO8$CP<;q@i%j+J$^qNLO?7bE^C@j7(B=WTkN0jVE0xbdw)^a} zcjdd%dwK7-`thLJ?SEH(ph150XGU4vDH4awU1$ix7~6$268n{izLgYCRE^~<r|=Lz z%w@(_Dhw;0eTZ$w$C(EajdVIoHl?z-u8IY_>>_nFiQv(ORMb^T1}>k?LEAxDCFdK8 z7xv3j!}KmTS)G+F6;|@Tdw!m5s|}F+g5%MRvvlO}Q$q=WFHGE!PuFz#{QNR2(dPiC zv%ssj%}s84a?L@PO^hM>-9v!3$`=J&@BbTc0#Wa?&%8%kH;F%Y3JeMk$uFQGzl37+ z(TZE+UzDJqn_5UtcF-+-<w0ECX`*y2D6b>{(iapr0jv4mPmLa@TL47@-3C8kE+jzm z;a`m^EV;}nDm*I}4`50kK6*K8;D!QfZgRXw?+&l$iJz9*{B#G@W39qH>VyTU-MQ_) z0GdLo^c_AOPptNwY}Rl@(3DR}v*6)~^GJ<7JGcGTx&$mdCYZxcIK((7r*Q`Ui9xx{ zg3A;>oiQ_CT8v?JrZI-`Gmfw}%hXr<)DD8zIbPt@2ftYLthY%a1mM=VL}uk1&eMVc zRUoAn6xoG3PpnGnChv$b0I07hBT2asO>IKn9p%;oW4|SfE9yMV#t4A;uGoUPsOll# z0MQ1sMhd1EFk2Y2${!r@hrynbYr|qio~-nK9RHm3#5^<uDW)m5<IIX>GqvsI7hIF> z@hq5;k=7&rhcwD0s-dD8>$Lf=Jt!TTrMr)qHh9)SoiDvor2nKpM)tANaqG`u?Nt1n zS}%;mlj2?2VF!m7D<wMm2p`ae#j0zkGDgFpaYixK$Qa4^Pk-tbcP6|77@zJN$hst4 z>Q!{t(4Tg9%FJYH^2DpKa~V9x8s$yvpV{w)2)@=nJ}lA(q_09;aBfUB)s<f8^F&L_ z(+(*b{2gEkJ~8uy`D2tFA3Lh+6f85<fR4D7MkPKU@`*w}`2YAcD&LvNiJkGSp6glk zVnm-?Op638W3!oG3UxWpGh!b<udOdG?ML^Dv=?ExulYJG(zly0=+sl`Nx)!|yt(wL zE8nsQdu|Kx-0^PPN9Q+FnVj<q$Eq7)?Q>mK$>fy^VDaUQp>!Flcu;SHpVigoLh_`O z0&CciLXD)S=`>)x_N8!YbQDFy6cxjXifbT_KCkRCWNTZ^x(kW5_<OF;KXdXcmvS@7 z(~A^Q8RNSo$))2dU<REut^00qFx3@msw7i<bS`e>g4V~T?<n^%JaKh^7I_QHd^+sN z?i_^}MZMk%P)5hMvoxc20qK<X(FGa1$5H1s^hVah_#0zyge3Pc(T=I_HJNu-%41Y< zNLc9=5lU5h5o4FgrCwrg{Ba@B8Z!_@by!8oMHyzmx$>29WbD1;z!}6O3hK%leF+d~ zJ&Ljc9(!7ua3C{W`F9-a!axc(xi_3yTOYc~?LqW+MFqVL8%9F3`-_AcI4<-ju0D$- zYq-*7t4x*fh?F7J^v1>&28WtU=G6;VScljRk@(MYk0Uj2DSYDq{#$P)g2TV}Dwi0& zqfrP7eRtX#Wz?-UpRVcM&wAn6fWm#3iD1ab0eZ`Mjl-kkVkFIi_eFb{ZVl>02^(I+ zqtBbMx^8>$t?TV%graWLNKdRy=l*lF!)Wt(2*wv>IHB`+=j2Nco=w;0KUP~dIN30h zVji9j!nMZ5(NRHBi}KjFQ<-a(CxU<YXW&`B%rB_&6RPM+xUs8?l4A4Mo3mvxr%y<f zB@r;~l!prF7B=_Xc&6d`W{%94kSVmP!xQUGa5uQjLQfMojb1w?Nr%CCX-9AugAf&1 z$ME~qKnn_3{3m@z&droA0e`V%&M_C}V`B!1Eyp-Kl>NRK_V|-8h6^{|?SbJe{C&BX z@uxqWc|7T6cvkOR%^K`CoD5x`jTU+ODQ}j{W=edS&Dgd_dH}??kS1g1Pvj{39Va6u zgh|9`APTiGB?u>~gkyGe)PK$*UF@tVrhtAb)Nn_;{><#cY!{w<9LRHQO0JS}?8Viv zD_AIbg3{p5JTFxcE@$-V3<*>xqDb#TZWUQnhMY!#)}@i|TJKrj<2c%lThgwdAc)bi zqC#G@lwJpuu3Vk3Db=R-1#u%ZN(Nb>h!3w?AiXlExI;1^%R~N<6ezKggoRsdvQyRy z)PJ9@M~nhM2c;mIl4jXqonri%LLA}D7%zL2OxgoD?oVg;0LMUz0@*E0Fdc0a+a<!I zyCbrS(2#V;YNzfqn$w2OW;o*6OmCA`tonGu{=GB_B`@wL-#-7_566ee&`Wu5*>Fl~ z*H#r7pqBajWc28Zv7Mvi)}5R|E_ZQZ1G_7$pR>c{=^r0H($g2w!}leGo+~W{;?MJQ zAE*^X0#bN@Ej1*Q@}@9p4$AEfX1|$$c$-LVqci|<94XUIfY}X$GD(m!l=4M#o&Z%m z!VbS1j>-#{`~&^KGqj1gJd_9}?Q}{*!Hi=@Ws;W*lVCZGtqw*N-*HK*U`dh@DQquv zbSwV=(p?r8s0&%wMOC9Il9VE9{-fA;{{xq}`vKfb)iq}o*m7Z*13OE};ks*Iiq>om z@GN}!_?zPu9ff^hUs+sJ5A8L1qVR(%8Z6{Ml?<!P4#9J`5ohsQzoplxH2FJ6Yc8}C zOXG<$vP$Nu5_#eeJ)hW|S9!Tv<rv02DucYNGDR-ubwsm>%F%+*apBqJz|ucmCdT}j za(*C@7w20>`w=DVvAe%8;Smio!97s85T}V6mLW#l29-^Ccxv9XFBG;Kx=FLEE;^HU z=dy3pc}b0o3F%M~2^9zmg2jX5m+<ld{rKYh<2P>vm{5eX9F1t0-Vkxxy7c2PJ?b0V zuaR02&i+COifLaF$jSQfG`=^Hl@gG<6+PyM77&nheq|MB^Qt^w`jLm`M-mbvjTo)z z&g!-)gGObvm5+m6+`H2R?mqZkM?#PHVPsPAgTd*gIN4<9laSi%Cu>u`qFKXE^Nn$n zo7%Ls5Z)Y%Yt6q((BqKKR<2H#g?4j^=Kt<p6}7IE$Q_zJ!NkV6AHZ~A%H*hNrBAJa zKV8ZZUz|n06vQppr_ABtZ;^v^nSIt>S{iXE2RSCOXUBUqPvi!9lnh-i-M};mWWklO zD$4x2V7;yTfB+qsj+!Jn9PQlhvRPB+mw8K~IE)}_UhGwkst9<xrGm^~J-RWxM1Vva z<7lkIwpvdqSw&0*i)5&?7Y3kYyH$$WPE)^oSc6+yMAXc_%+bZGXy_nUI51F0L(dHj zt-@;O@h`YUV%p~@>~l{os1j01kEc|nfKnk&J9<JH+<Dn!`wtVkm0l<u5*1vr^HM+E zp@+ZodNs`^H<d9WS!uY~L-Ki(++<1wSV_TSvUq;paGd_<<TXeRfPD2ulGEthkfe86 zy9rD!RP=^fX6%_q>(pJ{#I)8tl&$-yo4+W7A4_yfAby_C)d0vPD!94tsPD9Yw@Y_# zD79*&zy0Lx%V#eS4VC94Cj8BOnW5Jtr5ssr+smqaFz0{wA3glyU%z;qP_gOv=)`#I zIOHK-7K(r_0Z&Ex477-pHg>&@xllEmv!*TD4JS!2iQ{MQ#zi7|#wi<;maOTBW6Yu# zt7jD8%6wr%Q>?7zShAr<;*1iOKub1*0QIkKw0{OC*ztAy{EUvP+f4tnp&^^YNucoU z^T?9Oj1etqpZ}Y3_3A2bPZl|=nDAdq!;RAz7FNvR7Pa}_E-s#z=atq$c*<>oe}_mY zy=AXlre;pkvY>3602g!8Ho*C#RU9Og^_Il2d9mKAa|_JL{1RPFh|8{)3oo9MYe=c| zIUHAEumGT*q5l}B1BVSuRvG3qS<qRqp8!$2W?qS^5wV6^#x+SErH>w^57R|{{qX+d zFCPCf&oEHLeym<k<@Gp~02IH%GPy6~wRh%D6RT<^QbpzEUQ{=*SU@OOxK?@(c)s2W z=-FpioaKT1PJmRf1Sy2QV{oQHyDj`AlVoCZV%z4#wr$(CZQHhO+nLz5ot*5i>N}_2 zU8nZ>aaaGltE;>E;=0x{>ZKt%jO*bL^nHALrNJX>*Nud^%8uWj;&?RdnWwqy*wDYN zZnAO-nX7KU`q1vp3Qy{WLe1FS;*LyD?~pwc+YA5dm$ymLduWHP``UfEOfZqz;?bLT z`0$DTmPxg+cf7M^VjptkEpUl=z%fGnlO?$oB{LGRHDL3R7bjE)IqiA`M`cZ~lHuX< ztoVzoGs3wnNeN{(+Gd1i#HdHaF<R4TC|+sSN!+0i+~*Bbsu9fi0SqR1yCs-A+W*^% zr^7+If@cY*>ejYAwS?x_E!r;>W3o_hQI5|i^XcWWtj`Be%FEvVO-@2NkMYF&Zo^%C z0W>Blc@R>>DGW->AbrB|tI{%`#~TT!51ZjnI6?(`Zvi*a@Y4a)Momt}CQc#;j)*1+ zrhQSvmA@0}t$I9y{&{J=Bm+nBUg?C+s7{$(AGX)dhxmX%@iH#q1<O}$4HCdsAV^9R zK`c?<{KDw7yn4%~tT3IH4D2UhFsfup2{@h-r@zTn4tk95Hd4{17g@lHjWP7pNKZAO zd*{snLD%+h+&xfE{XPh!K5km|opz2h9}Z37N?VAAK>jp*=CFGYU0jTBAi;gJtGR+% zar+y2D^Z+8`P&EmhqBiLmzX$$maKFz*&mKbdtmgRd;mT++Qb>O>zyilgjabn8Jgo} zW2POF^~<E;Q-FaF9O^L+)fDid#v^&zVN+#BYQI}~AXyRYK_A!~VJFpXa>=wX(}kTo zPtu%xV`2<ASHrupIVT8(2G2$euKcMWsPf(D#ICu#l8CfVfSHAtw66t6NH>V(=YD4h z`$lfDW<uRg;uy5v6cHfuyS6QeK`cq6xStB6iZa*LWa5%9&0s#SZlYE|39tO#)Y_&q zYq69BVi;0d2;HOv+Opoa0;UGox?sV4&a#aL;2;M1N2}bD$b|;hy-L!y@NKwqLxC3W zs#y4w7<cxHH9+;)OUwB^+%QI2N760rS50BcSVW3VLWxZGH;>!<@v%)jt`3*S>%$0c zv`#0tcQQcY={RMkn_%*)T`=<R0xKTpo2!QIho_-nTwQBT+|LMhhUxG|v3BvvRwYxj zSt?`k`D-7-+E+wY-r57oVL71((h{=J6PX32B)qTBA6CRQC3^w-72h%q<(#*hAF=g8 zbv5z7b5_HBqI6Kb3X)Lp6M+aax^-nKaV5^CU!1K--)SiaZAD-Ro;I38k|&XK@8ee1 z7M12TqBRcEZgE(kW7OGTW7N)tmazugQmeT!K@BRaMhmOR*<Zq|$S)2G`vbsoC}FoF z&pCVfR0w$ztRXCcwK>?UpHV_+<k*RD{*o@x!}p!TIiCj*N7zDHF^l~CL;F(AMZDPM zr(r37O!jxtL1<V&ncf)!wtX!)Rc1rAj91!`MKfGa8#OB+rNoERSj_-Z>S5D+2PGs} zDt-m+M5~_NWdS>yQ^y)u(o=gA?d4)|0ezbdRb&sH9@>vIR*|I2F(J_50crjbh4u{1 zdG}vcSQBAp_A9qw(y#nDb4v4Lnxr&Td-Vq8rlNX{N?LMS=;mPumFhIGqV_T%=|1nk z$&h#h()JahF+|+IzXlQ!?&Q11EEwTxjOkQ}`tPom(yE<ivZGADi=4;YhNn>vf)ul+ zRIPXzb2Od_5g}#~`gEv!ad=Bm*_~8DGmLXN2t5MG{)8aCVP!{01@cniR^j$PPJ2vH zu|4A^B~DEwJSiSYJGtGLH3j}AmUDb%JtoT6l$uS66~HBP-YyRwiX+%l-yN^-%}{bq z8i+6^vLS^wR@X`k802(p7K(OopD^9-#rRvXg==epY+FU>Y*=g%q!cRsA3$W~ZqU89 zmw1f&U4V!es4SRd4*N^U`99(yy}Px7+|aaS5Mr(LdKJi-mr);4ElZ1!Z8@H+)v{>T z#+DPk)VmWY)9;oGxQcC`SECUioC-heg>K<QQleisUx9wf<`OqiM(s#6??<;QzKy&u zB6qieRO*fy)r`E*#fBrJM>EFrV1@v3L%#mgKgrsc3>Z6YX%OYOFwzHOWkiTWMpGDt z55~Od;Eqt~zmjy>1r6-~izwX9++G<fz(-cki`_jbeg;D-Rzg2n1NQF$+{~SWK@@6U z?1;#$>tZ6JH)mnvs;eVAwmHe=qx=p8a*RlhYPylE93+EA3H;f_+xkl;h|#nn%Xpty z>f<m|y;C$;N!=jZVEb)tDHtY<NlH4gV1%>+tHj0k!Q5_N!GfvuzmpH@ZjmgkHwQ`! z<X?^Gw(j#<UwiXSPGosUi~2c(jfM@<z9)n<n*Tfrxh?Pn?VN&&N1OzOfL2bB3xngw zXunVwBzH!XvX(%XEeQF6`C;W`t0dq7Am-+Lb9bQ*G`@Lj8g;jH$wAEY&#U14Pcu>x zS3g~3r{VU*u-?Sy1XfYhV^3?}pkJ`kcL}z2Yz${omL&8KAfRH1sW4Ag0vZ#<PmMue z0CgCFQf0bFpIT#GX%@Bdg5aJPlq`B*R0~d70QDu_vF|FC1HgzU85o{d<BYgA%#yHT znpn<XE-CgeQF;)63e&h;*Z23M6Eio=0NulMP3b>9A%F2u%z2`nVIVU^Q7m}%4(2Ri zBLPfHAKdjB=>*G*<|eZY+L7q}W0CMdC6m6Icpi;x!uTK(ED>mGv#J~-ExWQMs51wU zj)Q_#rU)mL6S45~Ghqw}GgITK<(BG;ul@DY==%<I<wza}v@VghWmC*6;+H&4Y5$N( zrk^=(uqjtf+RB1gwpz8j^v(9I8SF|sGWGhHjiu(*Hsu5dwN){_L{^#BeE%L#%$df? z7p;1xQJaklUO}!4bBPnahJnV~;ldJpHq_95NW}^4TIkUuo?qZ%X?^<+=@li78=PhA zpS`?BTr)~@ZgNZx)BOm<4v0t{(}*nOjDr%rrW&Dt$+3DGMq}D}Dy6jcHB-js)MDIR zzn!zaOlabJP6jUzGIQe))@DMV3ntS!HtCk)7Ao;IIr2mi!XZ!z>BK;W)BdQHT$RMo zOM9EPhWb`+#w$ps?P#rpDaaw*aL^jf>j_=nq3p_Owq&aq!hoYphi<+pMD~G}T=$hz z@|mpJzK{*c-X2Xat;Jc_je?)atlo$ej<4^zFr#N|yev0zSUSjblyIlLc)zEu0m-2y zAb0NJj^!NY@oksSPC~m-O3{w|tD5B@a-Sf2tKyjaS7xt`-Uf-JGQTMJLy2|K=fzZ2 z6otXNu6^gBprk@@f7^i7Qvc7DdwBb!+W6!7owP=}HP1aIX*%1lSL{#S4dOJ)a7h2F z!E-nPnx)Ynd3e3w{c=sUC`BjG4Ok-Wlzzh;cN`JS)31J>7q_!2_?8CKisxRmhIDnZ zns^(2O}TkHLYdFiDn6Uw%u{jDM$lQafnYa;kQ%bSizT_JgRQm9Qc6-s6JdJT%KP1~ z=`FdD?bOjeBIS&&A5>0Q-8F8*_pdtSx_Wc~ku!TlxJ1p@n0WAndxppgilE$8$pTx~ z1k?70|0YiQRg3to2?hXkNCN;w006+&%$8Qi*~r?^#-7&JQqR%Y#@@=%%>F;LNiQ`w zZPr=feO7e%T4^DYDYV|@(VYOSVk`Pu3Wem&d1Q*h`BwVzrE!Y#zdrX}5=Fxj6W3l< zKZtuqxp1zdOWtn<Z!h|Ecf69BisKReNFobDMf;J`=N-dslZTiF<Le*FMny$QnFJ9I z`$clV`D%>ijrCn!*^2Cwb>8qLU>ndS;1kGbO6&{^4gNp{E{slqW+QH6`a&R0BO-$& zXdY#P0OgAfw}MnBj`4Tha7h{Tdm=f=!6x2Is#T1k`S@_bL~m!~e1BaH*l7P~o<*jA z#jKSg(V2g|sWNIJ(D26SK>|yG`wKxc%;+02qN2tXBY_EUbJ$&CxP$Wq4MI<-Nzzg_ z<es8*(Sa2ot>@3qfnRp9@zOqrfOGR<j4Xh_$&Jg7`odir-J{X^tkMRnKx(#hme}gp zY1YQ7#%h3%n;s2J&WGCdApeQIjxQYNnoGQPLrBZzg~v!;$L;-u>qm#GYl+FmAs-k` z8Mb=iXgszS&YuH^sWyAtd)0MqkaA9a?ZvJ;AFurDgmg8{j^IYs^s60ND0SeZh?$tp zFe&@5t?N@+5$F$MvX-l8JF0T#%!7KIEf~TFa!0b}32YyZ^w{KD+CSlT$hB4n-3MJS z>^G#UR^+jX30g415w)1U)I!qd>E-GBb1#%>Qz`C@%y#FyMXgSZJgzjLw#?vUugpIz zbFP#>-ugEZsz{(mvsx4nI4%XAD)C#d{9d{}W!8TEsgsoOGr6m2xFNU>IU*_idM#5q z3Pc@5<GCBF=I-ikl<;355O)T_5%y!Eq+6_A*?CHT=n~dx2I@Y9Qn0S!-M=fTBEYp7 zz2^M1OB_5RE46<riZrA&2k6lPe^c-#9(DC=>x$X|Px!`SeUf28iZ34aKJ05wqr0$q zqi6cK1>m>$a0onvI~9md(-9?~!fP?03drE^@v?ImefLv596b&-nDpQzt{f8w%I7fv zze)!oPry>4V$|kA!zbx1I;HAGC%A7>1b8h_Ka58uN4`Zpj;&;hY<~nZ?GqE$SkjW# z9axQ4Zm5bfxtY{)O|8Z%vv<W7Ye<MLWWtz5y~~X{no5W|zfissd-E1UDm=#W@sGV= z^#zC(!jqX4f*TxXo6oNPd7_X!=VX~*`PEV2_)+?%>|#zE{m^tZ3yX1TvAH{Ga?mJV z?6uvOGh7J3tHMcus8m+u4u2^1lvGr=?jfeBh@%uKPSq0)J+ar`k*|bKO%f4aYSHjp z0Lssz6G)amX_QIp0Gp1INHfZ{Hkz-bH!YcB<~7b?dmnT&@mwxjz;hf}Fo7y2cC}+8 zq!c2b(Cw&xV_u|j-4b~42{t{v*8o<G(E#F7O070{TExLPJ;$yu0m@C_@=(0uqHwAZ zIV3EY2hGi*3>vg=D!q74|I~J%r;1?H5>egqs8GY74K|uo?wGnzI4oB<1mo)EkF6#o zxc+=8?>olQucwm{r4aHPSN^9?TLH5#!*$PCk}80ykz+CH^oJ3PS})YNvj#tzXFWDD zxZK=04?FnU+xXv_C1tg#;6Hv$@#=RqS&RBd(ze`Pf=AGW&qft@%R??1VyQRoC2P`f zn12eVyqn{OdVaM^kNMi>SHs=nNeEB>HU^`V?Aoh=oxJE?>4<67<W|>8-PFhla9=N~ zM-E^@T$DVzVy?{%t#!sCK&9V1yJS*1fBzg|>YJk1gnTh*Z*+JHkJS|(18dt+Q5;K4 z#YPe6{GP5&5xUpp3NJS=>m&DUb52!Dwz-GyQ}0G#LMLMC`J?DNuac>hWb(-6r?A5@ zZ<`(FRQ-Wb7w;?Y5{;B>Inpza)YTsXRvM{t|CGo6{eZ=Gra4u#9OP8X-&QJZ5+yp; zN~iNI1BuyGdcow*Z5<fR_0#+*LCT6nG%n2ja!YbY^|QcQC-y3z$?48<6a0rpX%kvb zE8^|qnr{cS>tlMqw5?MKQm7^1S%87k_<Z--!12<15*_Nl>dkFuiIToWj82{cVXQR2 z<|NUeUN&O0{?^-uKGFiT>R+n@1ggZVu*+$F#tBy+Q=)Jx@a9kJEReIJ+Z|`|RhYeC zm&!>Q9FKE=vGhHju4uW}z7$&OCj6}2n>?;;^&%{B2(4}PMN+b?J2wBF)y*q}5Na55 z+OJnz%5h@Nxi<YisM+%QPw8ws<$Q?-AOK(x^#AFdtn|#Rb#!QK-KG?!BG&2PyUtah z&-AQ92dra&(2|q4nqk&)p!k~(qk5N1-$oJnzdS_jlQ}Bp1=fom$2v<`-DMaNk3Xc% ze~p8<03y}`nU6DJU`gTiL-9F?K~Bc<q41*m@B)PQS38HzJ5W80+#M}A$LVr2+%G>8 z^gV{R5SsJDJr23Xf^;oCzU|&2)JxH4M+#J=9~eaW+`H9%vD5kfCe~g!m-BMJQqCbr zc*SUClC^$A+?xs~dX1@W79o*`bzPw(-ynBgK`sg*Jvi>ppC7!by+J3Rnz+Vw8%I8x z`-Z8jEzNfN!I>@^Yfrs;F{M$oXS$kI(b>x{f_uBPHa-2OGWeI55!S+{W(J#Sw9G*v z2h(Y`lYcrQ6d*Ip)CuMa3f40Q<LT|?)u2d9e+jIn%6(S*tI@3S09@smLd8iAA7wQD zNx!+g1gj$G5he4y@x$0_>YR!Ml${sK5zh_e*g<`mgXF8xZ$BX446^#{{(+>C=aXRp zO`)(K{mZ@f8y~{1A#MAI59ni|CPhbN={xOpBUEj(JEg#<j!6>kLBE*#6x90k4`f_* z9lR$Ew~osb_J3ab8<|5*<j;juVgUez|5He!XKiS2V`lhs=ZC7>{}qvV|3oCEv@Azr zV!xpS;=rV^U7%(^coTHd*Z7Tzeg>-(D8!m>Xa9POgy$C$B`a}mN_^;!?QPz6yz|I& zy?6TzBKDafzI0y~`P0fF7X?v(gb@g)u%kih4Y&4(+mmq5@a)&OBNetMP^txsLZMPl zGpY%V3L*@#X~)`YR@sjqE$K!U-HrPJjvL)Q<Mfj3>$r;fak|o6v(Zdc#SMYruA<;Z zaQtJW1$4IWsP?ys<e~hJK&9v+Akx#sPzB)~Yh@`?_UQ&$e9CM+C9d{BjU5mNJu&uf z4!^G7d%CzXg@8DQS_Jd?5eF5p7bO`6+@^03^AAxafpr}b`?wwbnz%Bf#Aj**MafLz zWGqg(AQIBC?L~^>xj!_XLK=FL#x^>qL1)5bDeo!q%ckW7A(YwtV}FU&JBE653(AMp z_b+=<ftLnG#jyzKiu>Bv@rTEQkXu962t>g{908?I@mryviR}7)6_4q;#tO{cW~R;u zl23&~!$seHOeUHu$Cdp(bQIl(Aox)R2<sMHV+KYf)F|;wUI5HS%Nn*t$!8M_LI)2Z z+)WXE*=jw-Q`8GQ+5d<U`ZO@KO(4Xp<EZAVRa9CqTBtcOAUh<_yRnM9T<;`nXPmyB zXuiX%7-Jv$7gB%W4BaUTvXE}}n&hZg(aKGkDHZ<iTt;$a)L3_NsAw7Qwr#pJ=J%lG zN4hql<!2ltnRh1Z4>mgCx6!R&I46SA#ww?kHdq&1mRYA|_!`InZT$c&x0i+%gG)n) z4rt&E0?0{24R6jer|=G-hcWWa4@d+xDj1n=ai$1H%>A^v$Bm7E>85Ij*sTHfUz%WV zG%u6nm?xaFw326V2<|{)-hBp+c8@DxU%1bwbIqGIsyJv8{fQUv#XsC$Y<5A3bkIUj zWA&qsp95Vtt#>|-VjV{61Ldd}!BpLKk^6-{sLS5xRF&VwR1~UR^`mB$F`ZrsrIRg_ zyZ!AhB|CJF<2#g6;MWm7Ok2r*V`FA)eoLgbF2Jbrv@a9<^R1-r1|7sN3YY6J9f9*u zO=PM06EGRy171t*+>cx3$*&1lRU}*AT`m2EUeC(cO%{~DB|J$_dk&VqW*08F*c?^$ zXI&hJG3=`H3l6{pAS4W0!36@XnC4!I{YlqkOoW^U$}#mF3%DVZ6_e!jl}cWLyMn$X zPD9fFvCA1P+uGFOv$6Gw50dG488^mGFEN!J#aZ-14N2Xfv;U|(P}rB`hFjb2sB~3g z7*eDr3fs<>Ly<*DMqxJjg0m<C@V3vcz&S?xWWU>e6_6Qc#<xXS<4uAH&|=4Op&_vu z$57JKBS_khuBJLpx3;_s2xPmE7bNo&8ANm1#Q*i1WNx2*gG%P*vMv*iDv`$*KOqCb zboy&LW@KA9_R}76&u(u=js7L+fga!k=qQaGpFvrslk1i#?ykRq<T|J8hADN@?O`=r zotXn86B?wMD8hW0cf)z);IM6g``c_PbXl+SY4J`)uE=__&&u4xZSPcBo!&AIsb8v` zdHa2Vz^G`T2BmDXLNZ&1w=@B~i^Cz_#X(K}yzW=mHb`1z1e=`CD$xWaf0ekTJ7u;x z&8AYY@wolX(#-1lCgtGC(ae#asitw!L8TT13}16~tom40QmA)(4cUF`F5*X@l|ESx z&aq8g5k&XQY-QnM{|#gN6S^ER@MjZEWS}L74F`*T#e5*kHz$lK9p;Cptd%WIQ>!P3 z$B!=Eb@?>koN=w*)cr|esLSC)UFu);UK&}xb*u9~^4z2~Ehy&rCQiZ4`lcb-j;Dkl zW*qg3z<L1>5`NpN^Cu3ru5PZh3mT9R!z@_I=XGV@dmtb|gz0)R;A=SIqGSf21FyTU zU;k;$A2Dis?SJyi4NL$4_kT3zwq`$uyh_#TzYO`;l@4E_&c&2cywiz%0Zek^xq9V^ zKeK=wa*(8i;-oSGb9^{q?q6>g&pC+;w(#@ZAkov^&4G@6Zb?{J*y%FkgNr>YM_XIE z@kA<7ER&=%<OqZQ2zd&DQuuk<fuh-hI3Lm`fdwH%U6W(l61o-Pxk2PfDx)(xV&;iV zTHq_-;;Vo%37YEu_X!y^pF!H~kK4PNmg{d25w-6rOTnEH11A{83$6h>$EidxZ+f&e zq7vHCm$Af5N#j5+f}(sC&@2ReF#;fwVWZE3gX1u*=)ahyo%_M$=^Ee@%pSgZ@hNhi zQ-Tyq2a&@U!*qTR4_5C5a`6gPV?xqWBucm8lrVZ&1K(lmw1tP0A&FFUD%L>0Vy9?v ziImCcWo!#9QO<hf45Gwn8-us#r%<oBu|2NOGjk35q%bB^cb03DrfQJtz0Q4vuhLr* zeWOtCCwiu!V@lkM&$T5pHRK={6Kf1Pc~!+pg`gmo!l?2`H%sa1%?CO;Iyl(aGMo1) z#%gkh!3J^Mj(LF(s?y%8Mde_X{+fHYx~!U*pYSP2cQ1=quFL=W9fNKqhD!AU5&M+S z$EphC0y2{n=klkPe-Dpt%pA>usfwxs+Fv;u#o|HVRyiui5`tcTmIO2?%cJvI5+u|U zlk-}Qyq2A<j_G)7)4Fj&T)a^w^f(bl`!>TtX~W=ahwOjSXjN5C^bQr9t_2m<+`kNX zk$RB7ig@L3g^X%fsSMTE6D*T8pEDCAPOu=R?%l;(@o!M1#*%uC!l=Y4%+HI8Uc-%$ z_n<^q%RukMlnZm2A9PRt+_zQDzhd$62GQ7qJ+%x@rT{M1Vp#ZRK#1VO7PxjS7ecni zAW5Lxe~Hf`+XZU$)nsG!sQ;<YhgISWX_)iv)*euK-&%r^eQb8}@TC@C#Tz&S4a95# z5m18Y7aE@>ryD&F?VOtR<Zyyjt{+!ef3nK7yY&3(%+)F4h{_h=8*u2Z%Jf_nF#Rwk zeAq_}gm27W#e}jY%90mYN0P>~#Pw^`G%NKb4$5MrpnIkp<N$&aCkzL}dVp+*Kf{z| zP+Fxx@D0e2AB#MeeD^AV(f_P=&E9WzqDD;4U)9vM;~P$|8=l7+WxVq=lVs=QVxm&> zA1U1li&r-$@z>`v+_Ni!<J(11den>mczQUkA)Oh<{VO;kk`Q`!cjSm8(;QfD1`R)H z>}gZ9n%v9l=*vMgn}eJ4?E~7w?m6<KRi!MEO;v|}<4BJyEeWy+8kl`xOHvol&%iV% zp@9{_0u9+;0U|+YuU*i%Czqj4h(YCxWUjg^F&^(%%OY{L)pG4ndrsFM?I>KIsl7|W zWipvxgBugzRd|qoghJ-MZCHcpzq{p0n9$?J<>$1A4BoPYdYIZ@H{`8gTV=&32O774 zB%h=jF4o`3T{BxM4{;kbK~VRlZoZidvD#G&Pj~dW6|U$JVORAFm)^Mv9ek)8CGzq! z=p#^t(4(pL57dmZzEzqo8QqKlh&NBRmzj-~Yrw^*Ku4_>7CYEJjrIHhC?OxO2ktf9 zL0tl@f(LM6w57)fMVqZ>Om{D8R*={+h0E*F%S&O-mnzF@q?Y17NrFKmN1M|8vKi^} z`f`UVCm&qim5vEN`Wwe46xh}mc0%b3So27CrgrN@iWcI8jWdVcpmzwWGJ}yhYsVv{ zaBg%I^)O>(B=yRpx^H`Wo7c=})s_XD#Ug~%PfcgC;|RSxtLvOt5Qszi(*5IC)_N7G zyy1s=uE+1K`zN1BK1}~!y%xGz!?9p{-iz*ln_pkgvY=}(jGVn)LsvHM%YQZT!Q6iz zWP6n`l-;zKg=F{nZga*YjS@?R+~2%#-}_cjf8m>uMFF-MBP_BR*b@VOU8nKMnPo#0 z>DGBX!SzI-YeRD<;e6V{dLamgS#{-|StAg5-2Xcusk})EiqK^`E4viVx~kY4IJGnl zcBwxtyUwy~|M^i&N*VUAMT)&A_)kC?r=f#KDcH?f=&7!{^0YII((IH6K@(-c@05kl zKn{P-#Mp!lY=K47cV}U;K7rR<@axfG%1>vSDk3;*Kor^aT4H;P@B2T{Je+A7(Z}fv z4wrz-i$AY8;?)Q@*lkYNQ0rxGQn=!i6>rgvYfyvE^3q2k_V)ZeghfZfby;%oUwN7S z57(pR4nNYqU%&s;UC|lUFI)iw0NSts0RI2vuB`M7Y#e?ZR+oyFO$HsZ*RhIb9S@QG z?=y=<p>uVoj78J9wGgod5E@N?W&_6c!l?=GcT0o$BHT2!l7k@K=|}G47wy`8Hfzz_ zLyoY0w(>$#yoqvY7sLi=(*?*fKU*56IYnC(A#cgjLgM!5x75H!M_yWtxun=B(4@1A z0XJkJP(ty({^B$)caAI}U2m4QE?AkoLPRKPA|X&?fMe8-tG}t-9~w_k^8zLo2u;Z0 z!7Te!B@u~!C=e0KzZM$7vf=p_vCoK_*$e9D%2V@=gUk$^MRK~iV)*Haa~fg?B*gWl zr+*f*ZXV(pG~uO{O-JS(^y-`12`9PRoHLW<D~Zhh4BQpq2bL{-J?`->F2ofs!(<$* z?Sa#b!WDEXfalxy+YiJffbBLu;$TE1K61Mmn(f8ifBch5s*$4@;KG|HY>699q6X<K z5wGxl!T#WqX|_23>a;0xQn%UA3QJldZHPCGPa`LVRAUZmagbGiGTI$5L6qXV!%#hG zW@5=DPtFaxACY!~POU^7z1KNS(h<-AG}==BUeU6EV&c97D-zMR$%cD0@$q7)wTz$S zjZ1loUyY4S#l&U4rH<Y4vIwU36YFGk;K~B-1fubd$Dp#|$;!;^Jc!Kyqq7@X9>%n_ zVVy~9ZZXN{8TRZ^+ky_+N7)XEyczI}8dy&)fPBDN^pszXy|~rnhCLVZh*4VpxAltZ zD?-yn_FaltC@N2p+F3IuYaQMxxA`m~>auW!?Pf*$ZRTyCRQ;Kznqj+0)(qC;#@h~$ zgg;{lZ)9mZNfo>N&9WS!X=Q!C1&-oDiDzd(vlV@32GU!5nuP?1@-Ui_Z`_s~2bW@K zKt$}8p0KE~u{L8vmiQN!<mh~X$O$^X@VIKmW)V2bT)+Sk)nIxw;_UCp!fnTJRKE*s zme|gq=w0hu!m^2Ii?zURH}1C_o=R3zIA!CV26lC<C(d`W=Z2bYvI%g5qsAB5?ng{- zm*%jTg6lb(|9mWxC@#<V-~fOb0RVvaKRp&FYctpXs?4Zc+3d3*epT!6nf|JTW}82{ zl89r8m>^lT_9cPUFX6^R8x0p)?G(+AF5Cz4xU}iPkiS;8beo}G#?@C#$C$piH5QH5 zUw_gX*VbHbt`fZoeDQ8nl;HEqsU_OykHeE{gGAmhYpndhNf}D=OcJ&mUI?9P;1#8S zTw?UP7T#u*SIb4E_n-o?-YGH4Zsi*uU$@I8pow=NIXEq0L}%&u+o0-^k(Lha(m&n) zHpbOJ^^*}LWGFEo<2T`j{fn4fZtw~C8<vSA8w=GPDbT<0lEfunpNQ-OKWLLuPoNtg zziw2n%1;}((+~9MXEdiOZy?_>VYFL`v0PuNh9z1y#ZC?QRS-(F|2ztP_SrvwI1yV( z7Uue+`6LHb4qE)p^_}Jk*~>>V%2vR5x4>T}iQ|zSs6(aC$cS%EL<UplDbMB5mg*5B zd<sAG=Pzs@Aw^P$tk{R$S%+bLE(mTcASBsc^Gh;F7hmES<l#ipuCJ7cvKTe6ct-Mu zDX)xR#!t7;K(dHlK*ygbK<5X}62~hG;;(>6Wq=%=q6n*pl|4-iHMPJbO12w^gX-gw z7V_JVA`|!?FQxT>!qwj*m0Pw39jab3<D)DT0f0weys@m=+IBE<I&13h2BG;hJ4q}t z1+qgQd4khkWLY-FnEZsvnnJ~FMnNxd3Y8%KG;1^Jli6-WcNXDe=x?G}y223WtFas6 z2kA+7D*8u+qofc~NgAatGRFXj9B<yhpTp>&6LAP{0D0v;f+Zxd-62J?#K+Zo(uD6~ z3{)_}C06D~`Gk=x3z0_rKGzliv73`4E&WMbP!T~BBt7!5k;z-#?&jJSTN%AcXAH+@ zBwNl~<Y!;PtOb~4Fp>4MURz{%AcmTUpJR7MO6JBpz(#+Uv6ZwY(TR?ZE+;5>B^a$d z|Am?!{BlT0f;;)jPW3wky^~ogp8V}w25G?!>MQu*R!Js-2)6d7YECM(@+F`A$PU~> z6ud$8&fD9KQ;1KO_tXSv4i&l$oyLD0`V3Pj4==fs9G|?BQFf+rf{f>nior5*D|#Z| zF?G>RbBGrHYq2D0%QblNMg9R0^FeO3^GAW2Gr^7o4E-pDGtjPyuc(Iz_1gPzY19P^ zl+HabSig6T?fPb3IFw%v_p@9HPBwb1#f+Rq=?KqottOm|=fQ|<axd$?Co1EiB2N|H zVszp`RY0Z2!pbvlbPNv1@6PRPA8fU37%+El#(xHiM>x%g?x16ztR^HsgVGtNRTZ_X zN|)(7!WAaOEd5>)H>)Ra_lGkclmpBT@6>}}OF9Yx&kSPk)dMIhvbx<B9Fj|u9uS;_ z)+uZ0A+X8=pU5leeKwIo9lgySpPrQ80jvI3SZbw<tnYHoB6r`r81phS-I^D)?-CXs z(%vACXop`<5bh4z>ZjU9;8?y~Nrqa;D`}T712k02>=?LSFM0+SVr-AsSRU$TEsOy3 z2QBa6goUxMy_))`l1yOue>pfo?&bR3Mz7?@zlm6<qXz{X@x~f4fEWDZKd8zhc$x)u z^?Qk`3CQ)z16*Kg%qpcDyfZO3MeW)w{L}mqR$M>xrYwp*BrrvxUM<5u!B!L6<-2uU zbI|s(#Lfla-`1i6gC~FR&8R+(&Mr=N+!*`(fqH5UhBnduu{HClzx8~0+dOr6+g_@D zy*s{)9No1?5-`{NNevh+g`R>GLZ1a35+ts{j*jju?Oi<NGBW$8QK@FA3Oi{L2{+(I zYSf)sk$beWbt28tBcrIn<BJR%vH^{jf4^&F9a*1iu7*=glAhQ4QICVnPOjS156!(| zUGdI$v9Eje@YywC->S`8Tvie$Dxg3Xi}uu$hE4u`c%vqI|H<(nRHeIlPp5eC_o^YF z+}xQyop?t0+|ZY+GAnow6zB&kP^H(}e{?1?*|$v~-tei}PiKiCJi@El&{{c9AJRg* zSjGf_hYXBw#dpEN>^O;G5X^w*GV@N84^jz8E=rGy+bn#sxP{zv@sJ{ZzA4^#?g&0^ zE;tuhEUSr{T^n0+s&GNsL}lgZygo8qg_vh|SvXl^QYO3*PZuz7(7A|5z`3^Xc_4@L z&mxI+pJ5WxFImEG<Cx)(mo7o7E5u}(T1$-?{@^v2@j`tqqQ7ijKWTV619=F_Yyqg} zM$F%N0cz4Qy<qvf;?4_ZT{zbd%c{3FFA8`nlf%moR}1t0J#^0vdWz6!z(5N!FUslt zBfG-1WOBrOTatH@*Q$9|y8g@6C^lbIrU-P99K2ZvDWLb1cViYPfCy^$fj*h-IkYz$ z$Scy(vIy2gk@0Wrju6(pNJ(L_E<(W^@XsQ9SzLWn)go`S>f|s_6>@DtuxzWa+o;K_ z{=B!%yS2eQF!^b@v~s{ZzXqm#)W<>>KiMX#&RqNP-Gs@Z^xgNRq?8CDqD*2qF@Dd> z?j7_toSWWelCY_N1(SC7qqgy&P3TCGXBqNOY(Al2&kjy&)zgEo>!3A<5gO8}S+&9I z#JAf{;jPcF>D;yN6STR_W!p_>Fjsp}NVC|+gfk{+eAyhbgDq&CtOQUPey^RwsNEe| zL8CWqpaKX>RMj<(g#1XTUx7Ts${a2P?>MbqFDh?K(3QA*{_6LbY`$vCc_li3a^pNO z7cWAJbKG`zJ~WusnO<R<JZhg~L!Ld}y-;?qN6|u8&^cRbm-J@fA-L$1l$(*Xkx0pU zGv!8jM&I$WxMBW*T0uMQ*B!pc5_ZSXsz|<^`BbB;!!3UU{xiI=^=eYK`w1&@f4={* z58!NM?_g$Q{S#qGMe#xQ)4>Zpvk!&P<ikT2Wmvl>;};vi3}c81_pNKxil$t5!SxI< zqYaPuGykD&^_=~uEu%mkrS9Ge8xhW0sF9d7AgyfBP6)N7siDj#Uspbaa2X7K-!pw< z@b@v`@55!$jol+7X3nrLxh`7=?SJlgWS17T2m$~wBL08u*v0GzVBzxr<qzr}3j4x{ z-`hF*(;nm(i3r|x5Yju*%p|fFy&-zf&2+?Y^Z%CdEfUk{cPyVebY=wV4@$89wUWga zU1fWJyyaosy?&U`+MY7D+`W2Q_nx4yKIsyWME+yB8^$*R-o?uWhKd2>N@0A)qKAQq z>7IKDmuW*~Oa+O~T|ACM&XKbG%}JP_^Q#99x*&vAhl9GqZQ0zLtLKBdgq#Y?eeOOw zX2`exHf-eOHiV><Ui6;H9-Jq9Ca+rttEf5%H&av)>7B>>MvNxK!E=(GAsOw+5Uq*q z6z;XfXO6o~kcoGOupb-|*^X})I0WCmuGe(ubq8GR?OZgpwytG_G*5Hxj|Xf)CT!s0 z>}|FLnCl)C)cwt&&c^M=z(|OWXf5Y%&mzU)8Q<roPse3OgZg2vc9}B8l6sN4GTKdt z=VA+W(=ezv5tbAipQA4bo5CshIEbs{Y)xL>5U57scP0Bim4MqK?NFvGfpDVH{!%0T zGm;+GN2cG3>~Ln}UR~t3v9th&gM6=4Gc%0Qq!=)bos>2gjVss}@~7N(xLqF2*AMs@ zk)-$xQ&2@XXcZBOC6vl`4=&vF8T_W7sW84aJ_n32)|5|RAKVqtlqeN<6H4g^wyVS- z5vnRcX)N&h1=q5<>~#Eg_RzWcmbbzQJAec~civPM@Tq;BV^{dl6zSR%0~tF!+S(9G zHbHQ!o=}sFF{WXj_luou7Jr<z<CxUyy>)?|y_<cSa8&y=o2)tX1z;+$*lmt8BWfUr zk4hLT*hXAa3D_f|g%lvTFrlsez32i#S&9<DXw?|;XwFh91-t@FkJO3R7SYD$dKwbn zUASyH`jRvoLu|NX_*c(|f6?lJw7n1rsOcbQNH>6r>*(g?il-Yb&03R<Fx$hletut6 z_!2oFWWMpvAQbkvZHKgN6gWqDa-IZ@NrAp4oq)WOt;TMgHm-l1md&;aC)jY#_kn?2 z4C<UrtT@^-KCzIxx_|b&6paj>*Ol1=A<rfE>$=sCgViKye1^UP3?9Zv!%vu@30t@p z&pERROva%#fmJ05>PqY@>QG-PX@_t!SDH&wDQc%(l$V4Rvr9W<4*y?)f~(sR$pVvs zbsC73ww9)*)-Xo9Ree=yMr`XZo76d!bXTjfgL+*3=(n=+Oa{&}>u__cLG6+gD)YE< zt~?7!L`o+qxhj|P%$DX$;c&Ak8#%q?hge`5_a^`kfNShGHD7_y<|A<H-vP~A>OX2V zC8++GBt7q+MNgK$&m33AB2U|@ZBtd+4^{ZJ2u&RnmBIFv)OrS;e0(teEgM0OYNqOE z^cjUe+0?*<kCv!G{gVAcK{3Kjykt)EHg`RHUUwYDf5$uIsv|0wpb8_)-)e7^sJTjW zYt#33nn=vKo@Hr*Sxk!*!*gBU!kDY6upmh~GM^V4T+;!IjU1ad({ibyiCJfmT3&y| zdO_}3y<~Xs8KN0@`_my^&7;#?6D`x80S(11VohP#L`kr{RT;hO8Ks1#!*M*7v3|ZR z+>p(cGFvHZws$G@R`{ZAGFx(=u)IWKF^qJ@ju9o-xV~<Wzp8CAK4kUW**1pE0uji% z<^Du(Y4gh9Eik-Wj_+B~N37(}C`B(@aCo*;ktF24KboCDBUNKOVa1XGA~V~ELCdz` z;HA+uDGpb{4HNt+EXagZ{>iTUfDU6QC4|?TkYVrsEk`*>+P%S;s<D9iuOZ0t#4ipw z?9mC=&)GPTXiEt59@PRd3ZGDH%r~iLx<7ua#WdH`Q7yeB4Kswutb;mkj(JwqP)r1h zZ87Qra+~UAfB{K?BOuR=aKsZl82YZI&j8?rQwACrJ-3q9XcI;?h!csBb0E5`A6X9^ zN>)q=3}C8L5GQ6<-FsDnwDTQc0Qc1w3&T*77_!GuZrC9S(r74aK<-Y&&%7Z<2BpgV zjTFV$Bu5s)f<@ItH_Y~8*38Bc`8={mt_M_AwxOepG3oB|Do8)RJpl<nLn|TN)U-C~ z{=QyrFdS{QB*t{uvPhba)~q7Zve>dH#QJv0JcoTt%`9i-K4fWAI6<VgfP;>~&(iDL zQ^sA|bI;B1ye{frG}VK_C+CM733fwr>zgG)6v_zMxlMXgOf>szx1ax*(;z9r{#e@` z`T{(d+>Rn*7c4Px7laWDJYQN7IZhzW@*}qIBkhEhDCwcQW?}0~j+m!Ke(fIN_H*=& zN=h&WTrFKRA$o*~my#XTBQ%pkd0|Eznm31zMrQ)G46!WcPRHW!l8pLNz@2rGI_GB9 zb<g%TcWGzM^)hVwW5Ahr#n)E}+%{?u5fUmqvyfdmH9qrL;fdlODnbS}6~RAwnCS=l zy4@BfiImd#Gn4FyZ@&-74uq;8IRr@NdUNu9;n7IM!^&@&GRGZceK$kdVtl<)8H-Tc zYrr8~L)rzUi!>d6Ay3vFW^$w`X2uBQjEc)>7NsbQ;d=X{`ub#v#u!4Po^vjvNP6Oq zHR^U}ds%wbWwflUR83%NC<h_xztUibnoNpb6L8F4U=X`cK^#cn@tj^3YM&E7kP}v} zBt?sqjNi)6K3L*REfO2fAivB6)|NRRNdPHQQiD-1o#O{V)fJ-LpC*J@dGU5l*_Joc zGiz*=ZS$?hjdYvG3vxQF))`>rYlQl`nU0^pQY=mgIRWRFm@8>dV%RqBNpC>A#G={q z)nbYzii~yEwHzZ&`nM$*I+=DpR|?dY0GdlOn@jL2gFLFEo1J+b;aiF+l(ubXM4CRM zKuh25+tV9HbIK8$0+@^P<aHDfDVmyuDz&#{$>XixteN?_y;GriQ-4U_U^76nB^@!d zVRo#l<*S#Qj2T8_MbE6jp?tfKD9hS`W#Xyd1ll|SI*ucZ5B2#wq-SmQX8ywXhuJsT zp<wU)K;o5V<bq4(%FodDf+qIAkAi1FPc!7_^@s~?k|gHY430R7`nV1MEqyc5#~X=u zt(|dj@n-W9q9==N***YPropvIs>pEIQc=kqprjG7^Ul7yDN>_1HWMY75Cg>C+tq1S z3}y!Q&7Hr~mm<e@{Cq_-`u3_7RsVQGoO#m#N{XH;lA@Ln<r(HRia6@;Zr7og!<boP zri6JX4JZmWTs<0(ouL#_BEN8tu3*o0y~LD<-h~TUL5$fqP?@yV+)?l+H5j86Y17s0 z)M-^7EM6i6`2D6)(!?{QDvDSL8Y36kU;k8_VuAcE!W97D_fevVyoGxH!V5X`?5W*I z1Guh(nXJ`UY@QD5zY$K7KDT}Ux0Yt`pwi!a5CEVA>i<&qax<|qvUdCrxNMiQl+C*E ze*uytvjPhJi;>VS`F!ER&KJQph(ee0dF4VW?TM^}Xwp7wCvo1^-<?wmdG-oZ@_|8Q z*EU&gjJG-uS65d<Z(e7sDV3K4aeZ8J=H?K^1pFl{DqZuVG;PE^s}wEF1-nr!inEfJ z>j}ziN3G41gabJ-XR6iel3z{GE6tS7TdBnDgSi9AB-X^0(92Z}&yqGFrhsNzJq7w- z1ge44>on5>o$Vv&0OglpvH51=X;73FMx)ky~~Rb_LQRhmr*mChU{<LCYg?U&?! z)kkWD-_)hohJA%W{K>irD@sa~6u0a6c9zPZO8hoj`(jE2$`h@{Wle|q0rW7(h!BuE z#4{5%=7G?Kkj?zbq5|cb4RqbLIhkZdR%WaI{o<lTN=wqKIJA2qd*oYLH>Y8E2#7*{ zi`?d`u<Yb2)N-T;3Y_a~>p7yHe$eR2)SJ^j-7+WIldmm-Jk#4G4>#vBVkc3R@D#pt z@RW(K1DjqlFsj+(=PqzAhA#;QNe!pqqq&eKgY|~nQV8SWm&Zklk@1U0Y*@+SZHxTR z8w*S@62AW~IEQ0qw=k&-)!RJZr7X8R2NGcrs{?P%e<hEzuJ$9vj;j_Tsa_Or$|mPY zU*^C2#C|U+^~I<yi~RvXohg`ZkDnc08=j=0Sq3;wAdhJ=ZH9dY%)Scq+zADWe#cKi zY1h=apEwLg0-3mNHm;F!6r6v>(dcx4?0(!G9=d1~$bNdOQb$nmO{HmnGxT_U-{MEn zp+hJKn>-xtj<kCFxOm-P?UIz1BI$8(1khGGYQ<H~o#fX4uE-nfc!uaQqt57fE|}B5 z9bMxcjR4h}dtvZ;^m;Ro?1|uri4TQ}O<1C7k#^8~9gF^yLuRe~;-Psp%baD)zL617 z81ab|F7Q_gq(=IiALgUa9i_x`zHy$NLv}x*W|>zbh{|HjRF+xS7&u7Q5#tlI%pbO3 z`wt2tc!!DVH&Pf22(>PiWtUKue7m!Hh@GhK1+}mqgZ)j6e01DA^STfheVXwp!rzK@ zK2)#D8CbWkIE2548>;CoFx#eD=bx)+juX-n2D|w;(5)FR{aY~<RH4)*15dMk!Yt%( zh2iQYU03t)UDp>P+V={jQ?~NebeLXQBzmmzzw^wnxC}NJXoKiK*KiElOEBkA5WRU0 z7;ZPS0YWudCH(JCj+yPbv3&^BrzVx==L*k6!wYkAt<p?#vI(l&h3{tE8t7Z%$)1nZ z%3N8m?JU%qzZ;ZBbwyQVDQb2EhF<5#5LY(j1h&D*RD}ko@fu8;7&p)DLZcN19Fjp> zAsSzuUu<e-zP>bfB}Fjua(kN|w9z)=aO^~SFN<P2J`x--TODKYJ3|?!K0YsiD25L; zB@=p~8cxM~5VTu1ws&Py8pS%rUAhApwm86XKqs>pGjySJ<~R!vG?s4vBK(f1*YHKs zMVAHQW;b0#XBF1M16blSQ4(!pz#D?hoOzisaQox%&Ld%94Q6J?LIk+gUX36fI(hm| z6|sx$MZ`ttf4~WJrKqz03n=cUTlpR2?c5Bv{JYc#^ST;rM*0P)@7sDjWMc2e|7E-p zm)z;<Q!||oLdk0H2O%7$5_b23?Q*DirL!vk%@VOpwF^U{>T^_qN=h~S@GFwj|Jfv( z-<je3%+bA$Tf~#&?Y}kE+;qpo&VG`3y&ohe-p~3UGY0?1VE%6er<P2I^#C38#v@8# z3A!oB2q=fE#XJCJ9_`FI89Sp6R25Ft5}x|=Y1}y-KXY_{GcJV)B0!bJ5t7-BCa}B+ zRiEF-CPM1Q!jRXn1F$SAWy=IxJ`Q#k6<pNbu7r|<B4l7`TYp~WJLLoAmds6cPt_A5 z060V7q2a)*T*7u0bp7~p_x`4OwPYLxYZ?JwK3wSZS+#1mIdpc<6DmL4VVBUSAW345 z?)^OzJv~@bc(@nX&K#|?&r*(EDBK!5auGSCJLE`f&Yn$7E$m4z-E=N(Hh!~_OZ?A( zEp@GlQ|e?RrU-MD!sx%DT_^W2#>dbAfFRBP3s&a;KPLk#LrXJjqn~2O64$!(`mpo$ zTR22lJ%9pFpE3$}kxng|V1y@$cqu+1v&d8qkB&&s4-f!Ebeq|%v+(yc#_5_H4ceN> zh_a-_GgfrcYMYkPgAN_~3Eflq{58HL=w`quage+u<a_DmVs4?pvJHAZRF4(41a!uP zZ`5nm72UYP31<dv2;WgrXSMJCm{#QX3|zO~0y8CQW>&?aYF|Ln2*~lpQpg0W$u+Gf zyA)yw7s^NMr|WHk<zF`sM^AUVNn^EX0&NgGf{!jB-ySqt6*cWz$rBrWEibJ^q0AE< zJ{}#=wjGZKE{*;*xFIi`%2e|xR}i|(v0<goJ5m>}sQQhS!8vTw+1Lm+2~wmOYQO+U z-39J3A+-CA93r?~I!a9gLll>DGlg#cKGzQqiI5suIG6Ns0w`Q49L{?dI0J1Pd_ri5 z8pzCr4w9xbu%_y;S*)w1PsR;$x*iSpPzaMG0}vby&~=MR5AYFXwv8{xE}U<1u*Y<S zSS%6J>tez7gBOFA%)G8yZ+?jT91#@3Qq)f75JbxeFzabrrY>qpyGy8HIkQdBA{r&m zuwNe2o|`{4rf@&-L@t@FBcd<wNge6SzAO77g6L4#U}$#N@-OG~9}7?}gxL2{9cnsP zWo|V)ZEm_yV@~wvD!4N_J>2>3d4HC90n78fo*jvcA;InF)JXfTfNJb63pG4JI)8lR zkJnohii=z&(pI`SPm*BqbvmtwhX+DV9Yk7;n6NnI1~kW^zN;uzK!|_T>h^bn_Yh;U zv2J2%$c1lCCYn*UypDVDa^#yYv6WNv@y#4v<%Zr;cn+OJ1RHgmTdGv=Fh08ZEJYa9 z%57%eDZWaew!P4Wc8&r|^r26eWtB)=&AGrqtw#6L&NjAa90v6YE+>u${RIxc*RId8 zczsGX!k5d_9vr?7%0&tuIlJ0C?1}G=3djPpWRSc{4nC010xP(0$MU%XZble$n{52a zv}N0@Jg2eBO0(%?yO4kr>pcC!{M-fH0$WJ5Sy{ke1u#B{!^z~}0T$N9e4h3|CVMUB zPW&qsrp-rNRTyieYGxFgOF#8eIT$A9&)##CoyYhjj~;Ny7<@puFSFgR%DP{eF=+zE z+h(SM{x8ix0HXUP_xbp%9hl0o`o#WtTF?TykY7*4(+AH{@p=9cS|ms}YYAdeBrLJR z9L~UNMs+U1OOYP(u3_|;zD*bBSF!VW!1oEfiv0JPIAl&PIw-nO#YTNf4~nNfM#Lyw zfM2IrU|KqO=<?&UPSN<9APtDOTsG7%`7}9VRFLs9O}!*~iBNDEzOq#Ku)nLoPz(eF zh|f={Dkj0aNAVKH2$qn({Tux0et${NA}$6r7PpX%qe%X~@@4-Tp^^^s(tklw17Wn9 zfJPkai`1RHZsk~mpVZT-GpD&=7$g-40ZNQkFk$$SoCWTsuf#uvvhBCt6d4MIW5IC- zk2t>wQ2or_L(;uW>Bj(;F%aswg#0&vg%oJ1J8Lb;A5`}u7bGf`?OQl-;=B_RPPZ}1 zt(b%t_N$M0H!gxMP9!;3AE~w~qUbZT66X8Z&}>2Y@m7H&4Q8^DJq(Xvnt7N!W-GD- z#{%wo6#y>QfK<Z4DYiJr@i;0#N>3)eDIEa?Y30JmrIi?)XUB@KzI*rA7hbA^3u5q~ zU{s#<*oPNK{l*j#&9kh44&}rE4kb90f!zGIonbfB(I9%DxVeHaU70$;a&g39!gVa- zF+Ae(34^rf2e2^u`kcJSA-<a>(4^QAqK5gVXxsRX(VLs|s(r4EPhRFkOiqLq2;;ZO zSXdExK^l84LzgTlj*VbNTx2BH0^ewi$@vCsISch=jc*uafJR^B_6{P>)AmZhVV#@T z=RzsMVT$K>j_1o{Cgb~k{HN+Kk{5)=FONnUZ$%oF>Wd?Klq_Q-`a~8RjKNbPX@%^L z#B9C;C^IXH^3+7{w^T_rZNK(6<?JN{trgOjqEiK|2CIsvzkQ+}lQ1EVFk3lp$up}l z>>xN{BU^r{v|q<P5}^!#9vq@k`$ouO{fMs&WtnpLu1<UpTUgZI@r29=_2Y}i)c+S< z=b$7?0A$&=ZQHi{wQaj!+qP}nwr$(CZQGbP8@n4ZvpfG#RhdyY&ppRwbYG2W)g6XM z_(hhlF-Ak(&uiNV#Yb>v2a+4_B)T^U^}WV_qZr%O;O;TC2*|Rwx5~B)-)ys@Wo>LO zj<^8T&B_;PnjH+xvLIq|z<$_(h=Pj1<3dc+>N{V4f-*MX3&y&ew_1JhhKXPv#++qo z*f%YN;~lU3TuMG!2AWbpoJnQT*5NBSb0r`b91LqkrU-$E2%bq5mbR(Eg%;=OXPSeB z6O3N<P>Kvemz8@q8+qeRY067wB#VQ?H^@5&tShu{s5mhkiT*C#q#ay3N3#%!twre> zregim*Jt>?rK5Q5kSH(g)GUTA5v(Wo$@pzDnV9y>87|JqsWFg^&lUFaW;~aaYuIXP z6~I=z?r`Kv{d4iwZTJdSNCI3vQ{Jgwxw21gF9H_hJzXW5J1hWMIgKpEZ0BgOW|U?3 zjilrZ-NGz(R$iX)oHo15tEzI;DMmYT)(kW>Gt(3vaBrpi51YHqmfWStXhTO*XOuL@ zZlZ~MM!6h-M2slm$4h|p+g9#ESg@{yiuXHLsQPs-TWl<qptfDgBx+1=nrs$PmdeO* zRzvUyloxjOFtZN-F6PtjIgoHPb`Up`SI4Yi5dt~S)jusiUW!%xC?Mw(a~}+T!xuCD z^Zre)!>mjY9yM%PNCB~qt?U;;E4ia}jxMjJ@T|mG{?-q*$q%&=6fd@%1>}G>oBESw zTy0>6PFWC}$V5s`%bC=`{34O@!gzUmdi?A^JZ~l*mO@i&PPMS=wtFdi5J_#{(?V0C z>V($OAr2aTey(_Bk_HN3Km)j1TlF8pVo#$P&}Tdd@qeh~0NvB6TOAZ`#ayCO98iTJ zObqb!9F~uY`lG?1QZ2F4o}p!KuPK>E+Y{7U!W}sef}bBL*Zg+u2a3XnBFUqr6pW=z z9rp^nvWSOBtB~pK$dfK8(%TIQ$COFJ7{RN%XBdZT-u73KxD5;n%1XvI84!~?krNS_ zuu~B5DQ{cY3l>ONWKaqIS#&G=2qS?89<%g!l92B^sv1*=EOG&XzAelkvmOX%JN{nI zXgH2B=3WkB;co|sHf{7)(sF)UYcy~~M3zhcL2z7^CFTx2<icwG5g~D=M<?jjj|-q3 zrZwmB`asqG>p~B=zKMw|tO64`9FiUb=bM`1*=$l6pJJ^i7ojXy#s*B%rB0v@DjG<L z@wNkLE$E`KzyPGN9$SnyQ4@jHDtkIZoHd<`r&P+feR`<X&jfPYT+H%b^Mb->K`tm7 zM!>aMP3;pEd@WX{Bq4n_$0g^3VqsG~Q5kY}H#?xbP74#u;6x=L*(fea=~FIIWDkV< zXQL^c?zE)OkT_qOw>?e~L<z$52AxO6l6WiXqS1sgHj55&dssxXhfmhDoUse+=4Wp2 z4_NSYA5$9#l0%c%1;YLQX@!ip2(gt_NLobs=o7rc&Fgmtk6vmVT?A&>xebo>c=e30 zOy5Qo_`pM-CXwMPRL7gCneC)7X6LCMQ}J(pPjdNJe)Bi~Fk)^2TXT^R6aT=6vP*y1 zdDo__^T2w*>I7m-Kr7_bwN!dh|F_ZNr#To9A8mcZU7hrDu0_TfLgPjz5IyXHx?c!N z)C0FR&g?+Zxm|x>n1hI^+umut>Y}DVI_H9h%0}g+e&Ep`^~L)I6ucwLmLXXpS)rH& zU;H9a@t$hLBFeZvV7UZtcb}P(GCauHdUni&Hc9fN;xh?pdr64PG!IG5f3!Yq41ZFp zT+!~crXJ}4SRI#GPP~!I1e_aSeLvnFat7SVuNCkeM(~DAd<#`v0v!$BC`*BZbe343 zOdzeDebSYm-gZKw9CdvZR_RJ3Ex)>dBLNvxx?h_v?LUJQoa;;@!n8vr!yMf`@O#Sn za!5BvGcMw}5TDL*YcJnt704^2OCDxU+1BI{^AiU(^kl2l@e*Wbx3_;k^Pd~>1{0O? z``mal)|6|KR+Fbwat+){6^OrkC=zV3LPiP<(*QzT%B>C4+YZd_+jmXw>&T>h8K2(9 z_qn+M`=^P#_bzuSBoV)SiFvf1#+?N(z9}I7Y4IZF*+kb>!`UEZ!A(tleAK8O@j_*c z9axh?;)MHKa@nP0E=(IC@lsxb9ZIP)J}HEr!Ly5<9J7k<J`PSU4lh3sZ{OUbj#>FU zWz-Ky0U0EPkL`iJ=_4YKZZjs%zu!qR_guqGGySdf<4kP%2>|;&*Duc3n@pK%R}B)= z>n*Y>kb){U1w^XX<-~M{k}%QG(&sWT73zcdefB~2@l`SiMa!{8`CJbb_RaYj4vftp z15d!gvrJP=Qi|1bF0<NOcgUckH3**hspeaj1gRz>lPYIj{ZJKi7^|$W1)76grM5af zj+V#V4h|4!HwI&v;#l@LXMYGvU8s3UCa92!a>%*__`@RR<PNf^Ax!4bB=&n^7j5C> zTLzwSdJadxo<6;6!Aj|3fFzXl4j`ls_!&sO`n@o54gduq=7mvt`N<LoGDOk^`9ZD7 zKmv>bG=R~|mC|6UQ#K*CB6$D$E)rU8t0Czu_cIIQuR>a*&a+_3M;2+m$fEjhpbxOX z`DBfV;E*sf;L>FI@RZ}R(;jGLR6UkyhQThV<nPvp#iA>cC)n9Y)xzoz(14Dh7z(c% zDt8z3fU64(gVK-G0;_Kra_nPFr^WaFtwaHH>`vTE_#L_Mr~9L3z6gu1Uw2?n#cyK! zH1pnQ!8|+2gOx}<7V-ouooq;ZVCS!1zzR?+fGpy4t182ETt{c>o?e2LWfu<_b4+pG z1#=?#cwDWAolv9X+^K`5S<SastkdZnmb?faWC0U|AQBfsmQ=p`*c9bFVCoiNA?Nhm zRRNdanvH!;UsJu$<Y>K1>uhf#C%ZT96Qb2~i<}jgK01uvB4B1csoH+<O(fN};Q?w6 zR;(EMub8ygBdv0ZN==PiC4xrsjpkUWF`h-GYOz3T;IeSyvF2j1rGqCib*j|z{jYv# zLc;VCUHs%}=n))F7t)L<vuPWmSe!)ORAh`dsCK?7cBmxRCSb}Ab6BP^(g$;Jf2;T4 z$BP>lfc4sLJo-@OH?9`ObLm0(Rd;YHT2v&zACUUWS~_X5x|0a~+x%6rtc|I*T5=;L z@`tS#?{ol<N2D#{*rZDR`mdAhbGl+%tC$KS41JY~eu9sJ-(SxvhSErIqegOMbiwTO z&;-s=Z_cg(Fk1bY`Upd>y?Wz2{?QX;yK_dYrTfTKJ;uW_^|)cYl>kDI9UYIw^z^YG z!{-cODpUr-u&`e+h=M@s)N(!IN}fRj|L{xh2Dr^wXQiRtaJbIWLs|<;#q&=*WrPUQ z;3UDmRBn-8e~;K<?lS;I!|r^bl~8%3WPyN#)ecd;Qh%pFc1@wasf|(<jzMocbca~w zZFEYeE92nO9uHrzWQnnFN{Ldn_b%b1+4AAx<vyuIkUiJCB?aL&DzR?tXe>36QRnxh zKfn()JOsPEE63Pvs(AnzQ;${J?QroEPbxi%s;YOwB|#10qV$sm!n~P&TYe#<uG-T| z_-b0^P<l995XMrvAg%&`Tx4T>xqWP}qmVGOpd%<&R#KX_${#Tuf4h&`tGW>^OrMAF znRwR3lr&XRxEo}F-PtoPBT`Vogb6}crqs~_Lr@10-Sq%yxzb;=aqR~-x(P^9xPS(0 zAj#5cyL@VLE0)oV23)JkU+aEARp2uG>K=omQ9tfL-h=BC9SESG0@Hr^PYUhk<5o3R zK|d@racg&T_-#C#%Oad>Xq%3nd(b3niK1SP*q9t3>nR(waw^h#bB)myA6#<i5P_!m zD<;U&6mTT(?IqV7o<8FguVVJ9LiW}_|7EQ7tz+7#K(>k8(2%756(bdYWK!caN`!9I zHnc|=VrPLESO(-($n#d3m_19@oc?7lg?9+>0pCw(4adq*IN}I$^-#X92080PJlwrr zo-SM^f*aJcKgR+`&4G7cQJHrcmryR{n9Tun8#5^_UL+hCOdG?kaYKTRFd(+?9wgLJ zY64l%yEl$s2sCPtd`9er%xu}Q<8z*s&h-EQgEUe$E~NwR_^2+zoPCC)?gmViuw}7D zb*So#M*592k_&E3$Qc2zX$<8^mMmm?WI-gEJe^0re)}QCj2dTz)126Q)1*^Prv z-KCI_DxmQ^_&fo68qs?AbSlxJkz8?cCr&g<?OK#udH#GFAAaVVAo~S2&KYpE@JLE{ z2Yc@TA@DQs-|}-BOh5wt{P-{p3V{q#KpnyL{ku7st<u@3sOO&aptmlM45v=*Wb(Je z11Q|^f`4EKw6;7cl{9p5%G2PBN0i3fN#O}B^qx$N3v}yQX-^%U7@dA}X8dW|b_u_- z0nj^v=pI#~mh5sNl0fb$4oJc4?yEIMO=}4m;rwtpS2l{T{`mDFyh1TyyJf!-ZPxj4 zrS71ni8=ebX#{PXzE^h@3Y{mA=N{Oq$XQH_U4TPE0I?xe^54k$dHDNM3n|05yYkcX z2imbKJ{CL<=CJ8d7NL;JW3wQs{9Ap;2)D|zo=c<{>mw{;F3PzW@5K(t*R#IC8!Lwh z8|3QFP1r|xu13<-T#l_x+pD>5M~LaKcY#7k6OW8&oUR<zHufkVvkY-T>wqOt0UHJV zjINo(j08j>KQj!w08zecNm*3PPk`gDN1I>La+*>mS{_1CuQ@?!?ZO)O)F>{6&~fUC zGtyiP=JY3W$tr%-N~Y<C&RO%3soeZrFd%-KGm_4YDT3@tfv}ziT`e`QOw<oQ!TpU; zBdes#4vzi_XAlO6mLy=9jMNne6H@?X#---(V#3(N&XYm`;Ub_l_VRawz`LfTJoqLZ z0{1uy5q037lGL-t(}0iO=+R9}eN>z*X<_0L4U#cl<=IkvXcp^$&;==^c2GVuG~`30 zsFW&SFv!)%h>PsE&D^NR=$39sqCxPnjfGbr96OwkkIoKcJSn9cH>GY9aF#=!Elz^3 zY3b>HJshGWZFQ(GSw5VY0m8*S$1vdY2ona+U2;)XnifdLV=y1$nPHVwF6eAwoMK`* zMmXm-Z;4dsW5|<%cUtGapQkG)(E`XZcF$K~H*9B=Zfyp!FrICy=gY!zN>+st_F6-P z7eWdyK(nV|B$`!VF$&!lGDePnwoHNBH}u3IHCsSL1dADgIVvcpUpW-B*qG`L7u9Yz z^5ds)6|{~)H(;9Of}@N~O8_g<$B=m96zH`RV64`FCal)wFjG105w|X24frkful*^E zjBKh~-v>4KWF{+@7PFv5@v!wgxcKu4%_@VO*b{KBPcmf>w7NdQTB>PWNF%e2DL=}F zv^r{B)LXgNY%}z*3Hi#@uecI=r`o?R-|LyLR!orl_JxS6*Pn>dB)mh@ykjxpckYdM z_jhkCeI9ZI)z|wBFy#ea0TE3Az8E!G_-o*cK49=dZ|GygB@n+*hw|ML3h8|LY??uJ ze(?6QVztocM!~ZyHA*@yr~6c=)oy)ls`U*5;}v4z91Y*tJM?Ojl(^jp25Hf<(1jkv z$x`zivnPtjClx8=njYPs59I$QR9Hh+M&IwW3dxH#pMM(0c(R_}SGX^}V3tiK6t2zv zaF}SK&<6UrM@<p71@ld`<st9@Ae?^K%>?AXWVFP<nkfB4`AYpeo?FTVg;6uExUF)# zzg(m^!!N!nm_ZFR#N*A1VuGw*Z~1yGlzIt?_x0SBYlNp##q3oCOA7YX!xaDY3}5xW z9ijGWp$tpn_<TR(WVv%b6F&ZG_8vEV2u_CI^5NEx%6mCR%jxA7SK2Mbm0HBzx-!CE z&^96u^10%x`Q;*CU1+)zrW(}jlsm9`*l&9nmT|$2ybyl+O!b(f`ts~mxM>mZyJBFc z9Rhe$5&m5>h#g?<#bOTK`Fk?cgCgV=8XmO2r}@|Ym)H3&PBt*8!pdY_kZU^R+ndbt zaZ^#s_I{HGuWxH3bR`elF>S{HDtNsYmqOzFWla*A+XRUzrOQ%k?ZuVm=f52h;_tfq z#{EN(^8Zn@{|_<ye^UTk8%JBK|4adjm91?T84!MQ{@ORNkm__Mv=}ZhT?0oE#vu@Z z<w`Dhj5o77Us2hsGlTTv_L7)0MT7+P9_i+s-gvpf5e@-+yBu?g<Ui=sh|qCr9@dJD zHm<BPNtFkvJEZFap#mD^y98r#Nux~aq}M--ir6VdGQbT+HrjLK^fiDq3%sctg9fwU z^2b2X_jvg{?k{f71@F^{#v6jr-|BMycDAy&-rm~ilQN6TD~vqmi#^fuOc}O;Jvj4e z$~I$7f;d{g-7khK37U+^S+EY`BsXK4;Ex?WTzoxH!7OI*l1hyyvXS5v!}^M(pWO}K z_oq`+A#Eqb_&VI76ZJ<i`{VY#1J*6gSdYGc?IrET#?edv3$<Guxv?BwkwZqbm;k&t zhrCuW3m62$vnD->Y2E#e^gg$_NQy0au`CemC4z|;6Q+1<Le?b=MmLKbERyFaWi(4$ z64aIqNtbq1NoV9&9eed9U;7?kI-(@b*0V7Hz{&RfuLD$>o0?Ue=^NU$*%MWf<<uqj zKwfCse;0AJgETi^X)NHsEiuk#h+D_OGjwLlspP@Wt`be>sOzB5sGuq$n3P4x@;dWT zw}94x!Bo?1>v7Ip8`Vb3V2w(kSqZ*5v63RPs;>g!$`m!oiOPRqmjnOOMA_E+d6uy8 z>+o{%KS=vJq!Xn<SZPp96^>b!oZH<%AV+X30dq88lqbss;<f99o~YX7OdHDYR(PWi z^5BxK(29GbdcOxyH#RX>HYU@0U`0R0)nt3IMN}QUewT&ch6#0S=8FTZO74<9n0A91 zzsUz<ikKGqiaUH5A#KP1PBHpYi7l>_BI6!ni;G-!Tr+(qpKPi;VV7+G-NpAwlm>~J zd-VBlw-VXt?~VY#000T#004OZQ}kzIZsla`@b6hpR*|yY<VWcFRD+@GcaRqr&j<I< zH^*a{D=C(p$!un6Kv&gl?2NFK{M_XJjHBTkC-l6xaLL*AV$S(@A)eQ`U2ADoHBLoH zYeE0X{s)=<t7bx#XbrZN;G$aCR&*DFRJAJ^Eg+KS>@;}erdi$A3Qlbsm2PI1FIy+; zuUF9W&gM_leLqpH5<{5X``g{~Xst2YmbikRAwwwr<mKJZ_07eJk2`Sk0?LL1p$uEP zB5xw3F_2ehvL~7YV|e`rcg!xO_*b-teJxhj>7M}>qI)xK=K=m`mqN)psy3h<KLV|u zf(Mml_X1bj5yGIcy&9+)f3K=xX0FC`d-Iz%x_Qr{gl-kPga9J{9Y}nvLKGG>@fWZh z?F_kf3K=z`$8b`C>|(}1^;m9C|ArvRO0Ey1`rh!_x>ksGgct>A;l*WFJ2WT#dsX$? zviX*d@PakSLw6)#H8H!thWRY5D4R+Z^3%)~2Y{g!)=<<a{i|(wLqQK-3#L!*XYHvS zaf;XS=v#UQ@~ZWlIwc^lE1Yb=MO*+?+qR6srgU0P6d;lHJAmu?E+>X$j|$F+aHT2A zn1-;m@rXv_)-*S42zogKxqn8Y>Nn&t4I6P*gsm+h+nk;l<U&7+sk#~>_iqDN;ly5X zhI~(#(ght135NJ=#?4~%*J}bA@FVR=%QnVVO7+omh&Vq>s3;%=q=*|YIi2+${3oPY z2AI%3ojpDpL~ckU4Gi?VaJ7X4x)<*Jk*Bxgwy8Z=ujy!Pn^bG;dC3ZQNcK;|T2iSU zrlGO-31}o+Cq~p^D&QX&OmS;FVFlIro*i=g2ch}l^+I?THB}s`q3F!XL2tpNtq^t0 zSz$kvMH=bK{Hv87rNr0nCC`zy{Ioc3En>>@6wuhV+#0}!fuCc@Xr1=`ONjLNxaR=} zvh%VxHPy>oC_jTt>>dmNLPwbc8UHdGey2@)7S0V^O>^x?6u>{ZQkqjA%R?Rhz@sUD zreB7Ax><jIz;rq!6k4;8ym&p}|JQ5sR&92Mz<)zP5#j&CqW+&j@SpvB`cD7*^-<IM zSDt|4^IA*L1->3ki-OAlGr~kLjn%}z3MUnzPXQH~LJ<iqVnLF;<auVh%NdG7N_sQ* z-d{(Vv+3I7IPcJ|&G6<_YDnGW>TYc{*^QnO)64TtbM{5*8c<wWrM%V7!?TfG!mZ(M z^%bK7%eNkRI_taE<Mwml>B7Od3-O}GGS;<s9l}Ytb{x97dh6Re9*9LMm7l#<pUU1F zbZWb+-R<G}2zA{c=|+dTrY#5nz3wvNp_(C?WK&;OZLUGy>E%oBWNejFaM`U)v-1b4 zmk*=xrR4FK@Zd`Vk{z`m@q52%wF@<J*k?9*<8~eNGff9R&L|fxQiRl^MQvp3SqDLL z{?$VaaUw&4y!lZ(^&WEQ+}bM&pW4gzS*cKA*1#cDwvVhhfRrzw#(3%>dH!_(=q$_K zkz9TP=<5!A;ck5thD?7{+t5R$K4cdfOiUHe175H1TeJ+Wp+NY&_>Y0)X4W>_0OUe{ zwVrH?qP^4x;bC#TYTL{i6sXJ5S<PF5pXxzl-MiKoT|kTx16Rsv)ay>s<93V!6p<#k zb<3;vv%%}~rM84#%_lxmvocV$m&%FUVq=2k3gHbusX5Eu<ytleFNPK%(&!&1?>;~W zo6iN{N%6sAMMGMDD~*VmLzS|6VAlqmCDBL`fpNNsA>=+@1OV$NIJY1?TmsWH{UB_~ zkVfo|gAZW++#<C;r5)<BRpYN_?bEC5`?89G0MvIzu~5;#ffhx_<gx^@*$dtBazjB% zF@@M^AZ?cJTMu|V+WVgbl@gLDHt7>0xdog$3vSXb{*q-aez*$VK)FBwMOm&l>Z}=_ zd#9w8JvSX-fpSBR#R$qN!zETV*{V>8t|w-z-6cwzGBvPtU^hmh4v5}1{0R^M@}Vw? z1=7-`_`bcTA*U45PC=p10Oe^JeA1^vSWC}S%G?at*RT)9A8<$%E>qqmZjIkO(wEWt zI{|R}2RlKpL*H^!cewD0dE|4(uVbY0rx`N<{W*>?I9(ofNQBEGl7~NJxX>1Zcvu>2 zRjhP4R>@s~*rB}yT4}N+(Q*$pw`H#}b@|^QIA^?p+nx|x6Hpn;D4JFsAm&#TEWCH4 zuG}uLoMiIhbj&k02&pBu?K^_KRc`{pq5Yr<?F6L>4N@f}LnanSA)p4Ynm_-remrw} z%WxQyiy^`2bVGej7|oMlA?Aq00~w0(9R=!YPo0|xoKfM9Y|-Hk2;GsDP{WHwOm<6H zhq#28BIhT%tYSRzv7jaQz3n{X1_(XkrjiS(__n}lN^?>lz}T^?A}fe&0eoB7pkY&M zMi531CTQR@ckD~Wj&SaN(AT7L*d#x;@)R)M{8^$|Vu`yh_<{HybR$ajlJ}le&5v)N zU|fsA9Nx%o52oSJ^T=E&8suFDEEhU1o^*(*NA9qg&A_LWO(m!BO8XcqGTa(L!CXC{ zba7XTMOa*6D@$`gGjRz@J)VAGfMWr8;9#uu92ROn98TB1XF4k+By5I_V~Y4(SSTTQ zPozHPzitJDM<-N;g;s+N>SOC3ysQ4!Xv)&;jVcbTgQW!CGS?n-7#{bandyZ>EUCO5 z*_vm>{`)vaWrHx)bP~X8j-dRfDlS)mT1`r9TLT6eG7fS>1r26ytALkv0c9?SJH)?; zR-b<u?*o#ihwwYIo_ltF(!90A6Hn3<cp5hyaR(m~l%r_y+2!9(iB|pN(KTLB3l_C4 zf^DTyOVl=$+R^p#3s~{gP2B4)J<iLU1@S?c9e{YQWq7Y*8F+!`&5B;Z$@6Uc^nq<L zp>|p7q6erZRY4w>r)Bo;+j@r)M<Z5SdwXO)rRrml3E${)R&c5J)b88<w)z?3?wG|0 zMj}9=otW8d#8Cm_l$n7;JfgJzLa76@^ogoF$VU~jW#qFBMdbkNZ=gy2mB<J%B^<2* zTE<ynhiYBh>HFI3M{1xh2%)vW$k>rH*4qEgjhvp+gwR?*D=ic%l!fC840x<2G~t~T zr2}Ol3N^#FpG%QE+u>KWy4#&7%cHn5C?b;<f3PY*&jLRSS6}SfOCd5rZZVh>#<jN8 zHxw{EP#lOj(`E$x^1xxKcVe(^E7mMjH^fVOo06)PRl>iYrnoUK;DX;cWP5DO=272) zrAB>+NE*4u+dcm2tGklcYjZ~Y8}~Suuf~jh8N2JN2@C0#)iakxx#3+aA*@{4*sk>j z{a+{8^>VFp(!W+&u228~-2Z9&vod!3U(4S$uca-vSi<f%wFfN(4i^eBCp?W?B@WVP zJL^nL;9AeL0NNN~Ddn^~$6$6S>5ai{>)-AVR`>*mgwDZzh@pIr?d5Hi<>!lwjL}$p zKE=EB8yhyR+Dh}1TW1NrZ__(>;a+caHBANN=81!fh2<ptDb-;YwG#H$XK7yMzsrk{ zkF~QoIeg!Ow5}CB%NOLBqRU)6(#plnA1~XRe7!&K(@FhC=aQ$%|9tK{UbC}*d)!{W zM%ibVJ1A5NE;*+4rbZdA#m-H%lA5WS-&L=v9-C#^KjsUaHLRA{wq;N8opVr*m*^a` zO;~9JHAgqCTQVPQmxaIi|H8f0NWWt1NS(eru+JJ|c(mE-c+It+zjx6@Iuz_&e0mh< z0*CCy_HLCl*R<SA$}N4-w)k{z-UO~KnV-lRYoM%mtpN#tQBPaGilwMNta=W-lKteJ z+T)paQq|a#@v%574fL@`*HpL{sEIV#o+<90dHR*o(C*pd?9CUx^UO57YwJ{0&PyGo z%?n5SjGVsIp1!j7ElRvyzH%+H+h?D!KOj#l|AD#Ch+p+%vdTWGtdd~y1^p_1x^!?= zLYr<GxRn9TyOZ+HRHZcw*BzA+{rA~p%74+wxO6WUTaCpVS5p@%FRe6alwrn=E|L3r z_x^)5h~y0S26*-;(={9{IywL8+3n-ot3BXriPf!}`)$tx7jg{Gsw;HZ?Bo0Mv&Hgr zaIR-*CQ_ZBYT3OMnwQyv{`b}#lfU~7D<25xB!FNaaY$GN*jg1^XBSN(Y4~3xQ2Ck8 zm74=BiWjdeFI=Qnr%{Nd?#fdrfFD`r4s9eq$9K|VuY{iYMy3PUd9_qA#&>}PGGss~ zG!wrN8GL}KKYQ#fVCP2k^4huS<Xm}hMYp5EPp5aNUEN4jvpmXSDKDQcF^8_6mNDz- z&7LckiE8Ds)q>m%0toI)!eshYWXbw)$`n9ZW`st-)DD%Y4P2n<Nqy0TsfEC#BfR3Y zTO<e~h8CWRwoj|QcuT@%sABz0r2{<Dz|m`dfK6NQ2@A2#Q3=H=6|rn3u)sTy@2qNN zpGZ$O_lh?3t`Wi)U?h%QeIdBruM50Gj<Ga;7LeNGBj0*2=J$8ixWkISro3bUa@x#U z3#E-!g3&4>V#rRzYLca<IZ(1vPB|VJce2ThsyV=4zi^ak%Sn>QC^T&v!Wyi-v5SKk zE)BLhrV$0v%L_d287tI{A05jO&*jRz1+Y0-`wn2tuNDZQ;n6NOGpNWQpvuD8##xI+ zg9v0?C5wc=4@i9>-jpqDp=8SAM40c}bG{4}V2j8!;0inql?h@2^~F&obsCDT55=WN z)B*@PLzR>wEtBUvQx*bA9g+YvC{r&6vZ_YipDZ9b4Ag%s53Ok`7zt7_Bb^3~LA_(T zad_aSn*9yhFG@ikd8z}180gn8Is~alv1kv$Bf~aRQ-tW*<idLW?0zW}_VE(nl=BtF z&Q!(S5(2sp_8Is}fE^7(ATKm9xC`i?Zb=OQSaDhg-H9Tsw+ZarG-XKxC{l-f5#ia_ zdO`8UK*g#_fLlPUgB6<@D4<J<nwT+2gkN55j1Q6l%WiD=30f$aOIMX&+4g~c<$?_K z(*Mp;=>nsg9)nWt6uJ1eHHhsj7-mx}%n_cs%F)wLZm9&2v4P};?OGf|TH|tU_ePNb zvx!)r<O77Sz^99nU|vBK^+G47fR#lcIBq3upbH>9x!(Y3PwR1OAR)&FHgY&@s$w3< zh^n#@jFcqIgdvxydZ+t)|0?#v@woxOxNP|pZApqHly2^8nR<(Zf5x;eP{Cr53gGU7 z4J=;=*er|6?|6H(SCd1dgXYlTl$J2~8uF?VIZ!vx3BA7-kdid)J0kCcg<m}AGsS}! zFvJF6G|Wq-svQ4*6@=1j)9T<01`(ZJKfli(`$D9eemPd8hBnO>6{{Ko_(%;h#WJzf z*V>N<5`uNkG{zW^IHrh-IszU{w9y%PFB=0{S+Dzi8ihXrPctq!$~p=YKN`J@P65&E zQ0H~Zs(yyuzPm%m52nh$(J>wA`)nx~0XGe1D{afKpHH8%34UJq*u0^sY0pe+j~Pb+ zgp)4C9?=#`VThbrF5qEn`>uEViv3=;U;5s+@ArpDPsS6?kSsR>vhQK*DYrTkCn}cm z6IU9<PyZiWE2xir^aGiI{wmlmpfI|4fU1QPmn6(LsT2gViGfpD0CEg`IV-`j=ip33 z%`P9uiGsm<JW~f(bunxEx0{3#Ef5)c6OAOjxeUhGNEz6)-tE`OL(7(Fiyn`VcP9hC zXrU6lHYYJyz5bab>_r2gJiU(W&Tcj)hml6`jxYS4fsuLFuSik>Eu2XYukZLI7#Ep8 zQ1=IdJ8rp!Q2^kj9rXL=G}Yx$>nRKpix`s+yg%)Pa<b7Cb#V+H2}4{jh3yUa`QX@( z6=@8z<pdE+eI|Kk1^M|X90N0l(ss(=3qj1|1hOm_o+4_}ShFy$iA_?w9Nr`JhLESr zeC@tjF9+FEc{;%wQIY?$Dad!&2~Yr9bzbLYFTX-)M>uwLfUw_U3X7eZ0EQt^)ZAJ+ ze_~DGeBy<zt`Wrw#1h2);zLV5?3K|R6D4W^soe<e?5V<R0@eN7O!KC%d1CwV5Rn$& zir>dEJgkjnF)JW1TJ*%#iA(IzMNM$n$&SL-I0#<e4Gdg3Anm!60mmFOkGS#(%E_T{ zSnyf-*Ceor@UgtyPiGX_3LpWbINbgZ!&*R<O;CJe`9ZAS^Sn5~yQ-;HBl2Meu|}lp z68rM9!Jq46iGfs&P!Y8^^)TqzZ*@5mYrxb6fH}#MzjC(1Ge0+TKp%|~SU?OR=L`Ug z1-?KASdy-gvhLl-oKAmkqi9C7B4ej+$Hc7u;;zr9a}_dHH09Yt41j!zyJe75gpgi| zT?|Lox<e<_Fm#%2Z4iQBO_@UCi2_+&O*SdoO6~tLknKN8LZ}u%7V-rPY+HCx*$kM! zPm5<$0YPa+k*s>f#DmN8Kf23Bou$R{5sCrA(l|XmnP~zyI*8vhj2H;qxu~D`W|CYH z=abCci+VIrK%km*QtRPNe>V|DVhTGLi5=V7N4A78(s>pPD$E9XzFTA2w(e~|Glbwv zMBL@(qlW4;F{a2r#xKk9OtnPU$M6*24`gJJeT8O#ixHEO1x1lgk!^H>7~d9AVQxka zzdvR1CJr9}^~#Q$OIi9Wq>~|=mTDm!RTADP<Xpl;v8B0cx7Q(3jF67&Fn~u{65G)t zV7dm(Mm?JP7iUJxGF&8G4=KD<DT5C*%6Kn-tXSB<^wDzVPiqQhx*B&?r_Dw^061S_ z0)z>r=_p`5<FtmMABXW9A@GwS*wm{VC!dyBA0o9uyU$}_hKQon^os;5A-S?Y`J)TV z63j<B^Fc5er~LZ2Fw3<omVxxSsX{Aa*!(f)HDFRXTLm0npc6)rs3-Mpd!}hTgex#i zlw|XgIVF{u`4l8ittx-P@CfK%V$NYmsG4L2GwqX&jxK#Dcxee0^I@7}fXmpQPq~ei z$8X?@8ei>{ae$}qFbV-ZwM3j>nB&N0kQ{-28tMy4+7<$|i{1ADam5^GSLiGNyyU=N zCRkj%q#aT+{d1_&NKL5^!4Am+R;{t!K-YQS&cJqhptiD>MS)IG=&Z(&iDN}!d3&Rn zO^Pa@Z_!c|MEuw_ZoLQ3z{{ADR01GUa0@6Tq2XsI?PU+T=EPIa&!pNxRvs7<27&Gq zbJ8bTYjem`%{CDkMeB+&67*~lr3yHRwHzx-;7&uPx^U816QaikTGiwfIKMN)(9TjK zH5!DbOD=MdJzhTrk9ApvUK0-U%unaKjZe$j_4voJV-=2kJ^ohBL#q;fDI$i&zVXs$ z&p0c9=zc@Aur(NH7o9yQY7fX+l-i%rxk!ZxsAUABUby9pBQ4w)7e11~UTbx8{WKRk z?i{NHOMC+=NJL@B3$BH@c>y<#oS{b7n*jH7`%>)+%|(-u7x{3e1>f-0xIUdD$u9>M z4)1e5ssK$L1m>r$ih7F=(eJ#&jQ6q6M=gD(eehYCV<rl+2%wM!xPAS3<H{8BWH}Ts z$`6cK8AT>XT3%SM%pt5+57tDrE)|jYdoeV<zX{fxLsAm^$il_`=V|hiG3JYYOakfL z5d8a}jqVZ@-C#h=XL6UIQ~=Hm-+=j60G~2iiaBuQ#Rhvp7u>hNAL0ydeh{PZby~A_ zwGi;;ZiJx4ZaSNODSLZxWSu?4+G}9?;XsyGqLMtX5j&<m((tzod_y@77Gd(0^CQg3 z#W&BmG#jy6bU2v$E?+)!%+)WT=e@t?8yLIy9ozX2j32yWo%xaLg<zvDkJyXFc@BAq zynCiN%amzq5Skcb)67~jkZIL5VE+vOI(X8mSds?IjbDT<C&P&l7%-sS4&tYE>Q0$8 z=#{izN~?7T*!myDUmnOWx}QqNKeIBQR||TqGori$M5y4Fy~k%-Q8`k`@w6-o-e5-C zet1J^ykK=%%Oqe7){?%XR*?}z^<+kq8j@o${r))h5_<$0$n1<f$Y;$?_wqWJfmpN> zc8v_%qpoaKs1Y@CQKtIrT%pa1{4MgCZ-2H4HUnw;3hC>_0HnTO(6Q0@(4L`zmB@|s z)~do)O%#NGUyMuZ#HIlEKWgH7?LF84U~-%_09wZjvx1$k47LwszxXRc;1Cf~*^QEG z4sfQ#6-0f4vi!z4HM+nGcKu}y6W#0cz8D+|<B!J(j>CEb1C<;Yf)s6StvPmc1nJRE z?tlOuuv+dffMv7b;peLx$9=lnnFWkoVu)vrPaGR@K7l*P(|eL&T6-8^t@*$~TFSxy z3F8ujfb~o7Qu||g2(S8fuNDgrX5hEKCc)NPNfHf?tmNwC``=U4YthxF`f3jGw%rUj zkjnNQ@Cp7Z@HU1T)z72$&5g;J^vyIyGW1u3NsDPPzWKrpwC(ERXxf5%=K1^qll!zL z0-u$Ym}7%W8lGGDV=qBeooH|aAbfKP2wgw?E0olf*r#2G?EI4H1CS>*Oai;~uM*VB zV7NTXLG);^z`14H*j#s3IYG6oif$2SNg*R^-HC?S=<g9E9*_m6*$Ic*=aX`>jERGz zf&3K#SKBs=U+_&w_JXzpziHs$`Hk69;AnqiN5%R@LSHcOb>wJfT8o5$_tThPcavkp z2)Mmb3SfmpxPM4k(;GSQYSoh8A?pUQu16a^y+g7;=@W>P?y?Tmv=vrUQOMwFJuL@0 z8VGw}bRjj2F)3(VW^yt5`cwFqQgLRHTd>$7VScZQmIuk`3Q|eaXgL^g{Tp=XGS<2T zuKf%-O3wbahhR5{N=p^4)rS%QFcaN;{x#7G@P0=Df(O6YbGl&o@yYomt#lL~7?=a1 zk9GE%-58jRc=+0vdLJ7g404^qs(cG@>qs>iTMYZMglA3P^fP~J?=Nr~5O#*w?bs>P zAnI(Nmu7(d;DBgBoD2GpSCrI-%BYFV2}>YGIW@Q{5EBw`Xok#EMUU;FQW9HifV?vt z8VN=lmxVP%ptm_E%3U8x6pQVEI3j2I8KCf%chvXxu|HiBNsZxz!pJeBR8b!*diOn$ z)mH8Ix{x8o)DFxrjhmo%?A$thPVD$UD=d_NP~NCrA2gn9|8;%g$K&m_a1lr&X8G78 z@&i8h=RK&}QYee|TH2bKTwV-Z+mlHfpidgZ+RW}cu2+-2pF;>bJ@*tt2Xc=sr71{e z8tgq-BFzB|vx*Iq4)Eg!&TW!&yiCV{5~>93sm!yiVm}(RgTWLc!nl!xAf5Y=84Y)K zhREZ98xsBv+|ApaS)fW_(IgpVx{~|0eHs@f-;r&9#vJisBN~0F0?{6{Vmb65V^Voa zvc6Nu98wjm+0(N)g$JYJW|DHV%Zyds2zor#Mn|F16q=pItXLIQhcY83U5N(0G`}F` zx5{}48Oa`;z;N1s{}mbLr4l_P<w^9H@eqOI0SIVOalH`2hF6Bb$6iq_qqqWht4%M! zB|jt2=Ro0^$SwMdH0|>EW9;v%>MS+s1UWsGX4BjHfC<imgT+`Vw)^)=b2LLjzOe6} z*b@FVnBU^g`1K>%!%<*sRO6b8^$K!S!aEfY;vzd2tJ{J{`Wj;N<6sctgiCav^GVn4 zor3f~GgFakW%7z~b6lCn=GhubuKZPyZ7~vK7{pX=W>v<%g*&6~{bC9PRBIjROH=jH z+H%%}O6G4Z#5<~9y<eOr?*tb%IY^OG6*pr4Vf@JhMHm6rO5g)oc4%q*B#fO>(=bdD zPeBAD&Y-xdl)zX+*?O_4(x=3wdsE^#!$`oDp&MX%Y!%D@byUY+h$dV7_2X6z!?%>~ z4tt1_<2Q-6Cs%aK5Fw0EN^GI*D@0_&iT4j>=jekhV%Y;Sf;HJ9tl%GWB&JReZmH7b zV2Xm=BGbVW?#rHGa?P)BZazGJ>KaA0@sw<~`4s<mmWS!IL%ZD+^;UB3hM_@r&sn(V zI<jpwBcU_m9*ho7VT&<8g4u%YnI3ji;E{d>!Lo}9Dmv#g9RS$}5(V8YdrOb_s?vB; zYt3>uMc*#<7S=h){Iej44=DUKpGxq;v4^-0-4>6%F1vs|KeK{8rbF+<l}=|5w@v3| z%$#kv+aZfQPi8PzXGtz#=~x`ab1&vn{?Il?Dh3>QQssNGH2DeV?TiTSsL3))a7O)Q zn?1~~l1iQbD~Hi<A4)ryKW?yPk`xv7iHMvipnSr53VN>)mRhwV`Eh3jg~_XDEk<B8 zD)qycP2##`a+_dZ7jvJ4(Pnzg!mi8dz1^v-J0_$WO3vYi4TUQeg8z|BF(D6CFii<N zHSO?-c@F?Fkb^qzT-pJuFL<(VF411TWWCT$6v_8W05m8sY$Y1<jodg?wUX9$8RUsb z>VQNFgC7x|=R?8m)_6t+m>oCn11|^{ZIcrB<H$43@=X~ZD(s;q18Fcr*8vE)itkh& z8Y)1r6Xod;xv^+ovAO@gHsbkDuh`SkB9(cqfeqot-dtI0vO1dPJ<yee752LE2+yfI zxOeqe8`M$LwSZ;ZhbcqcA~fZp&jfvxXdfq7e^y1H)T5F0Ou3Nr$2ns!$j`s@t<*O> z{m&0D4>TlYP6Wg?PJWz>@p>Pf34;E@3w-Ji9ai;%gGfL^ZU7SESFsM2=VH2r!y{2f z=XzC_!gsX070B?XTW3b;9G6bRY%NW}Jh7BkdFUJCmMymI$S6Y~$f;JA-=?57R8L7J z@wzA5tB^TTvG*rN(5YS0m<vLry+O-W8*$-uscCFfpV>|R-5%Mk&2Zo*Lw(gzpEGC| z`PL`t2ca>(l-ESGhxj2Q4;M*we~G)78k(r$qs;Q+1LKYw1Ofqox?=V}4i+mL-~oTq zeLDZ=TvKBL7Q#xP$I#BiW^)88z$H?it<Ks%+_8@Rywh8_l^yY$uop5-c5Fk=Jy;jn zF<|vDBHK>U?P>@jWLH(^T_3`~@h#8|!Ed+*<KU;e4F=Jnh(fVflnSsA0cwO-Y`fe4 z^UqZ}@SyHfduOjK%oy%dIMCpR_7p2P2DS77#fo<xT}e)Jpguvz!MdL6S4v^j5ktRg z9h&C{-}5K*cxB3&BY8n?6r8=6XWc#~2~}Q2BN*b4Q!gpU5Z%euF8wz%l9HF;DO^r= zd+d()L7a@m5sHI}LZUik;OPz#YJ6$E3Pd5heJ9B+5kK&!$QnM0!X_mYEk(><zS7_L zVJ%9Xmm;q_g}q+=6ij^3KQ;~{aKqW_<D9gVd?HaaESYkX>0H%%tf8$*nYH!p@+X5v zBV_X(G+Ji_kzLI$FHsOH!_L%T!R`1Dc2O^<i{Dkt<C8b7?6F59m8Gk>PW?7vo}FcP z8mmU&FO3zq$||zwU>rgCL!||p!Y8d2xUyE3T0AYoVUpv7sVS?<pqi`UOaizmaEBT? z`*o`~5PdaBr+%ZiFWepp=}T&fHvWQ*7O22d@z<*f)Vv%0Y}fT{8)Z$f9!SFoAO#Kt z)KAHDwoM8<L+5nJB1)iPYJ9yRDr6XX`}<{kQT3Kn82=(SN(60?-FGo*hX^Wkt+RyK z&M6zZiEx-#q=i6BBc-aq!v^;pzaF;(A*b;?5c`AO3Z(r%_w;n@;>BeQ^n{^w?;y-o z3^k|ZNs>F!qZVxEg9G`Ne(Eg!D3awz5Qz(>xXvJJhDtTT2n`X|^w4%o^;GXmQ<*7C z`C-Q!TP@;_l||i6C2VTyhBJr0>1YyLdw{4R{CPvek1`rlr;ROs1c~rDM4nTpUZwtG zfXvUgzh)9BosMKq8B@vP0Aoo%7N=f|a2Ap_?&d>?!PK3Sp(zb6*RMPG91480sY?9i z0(^fLK(<QR2TWHmlJ-uhKs!k{Vg00<b}BZNQ|;S3WLWVtIR!vKsNX$sbd!z{zCHSO zxfFIqcm36#b)H~;ywTdlDSpga=g|PrAakA;j@$m`%>r<E9-~fz<-wAnkYD+-=e)Qe z_n*3V_$~dU2%-1h5erQ}9_;|6E55iHP5p!33<NkP_2dpja*tQB@Z2}GA~Kb1OMR3o zO<CbMa1o&m6Ui<OWqMzAZ*Xz|N|YCxkv7g$3&xx^J!|W8)lDq5-I3GXclkOo$esw| zjmgt-hsOWq5J$8-Vew@l5>gYeeuE3Oh8<HXRjY1?3n^%wgO<4}I`X>xil)Ole3$<a z(n$}9S=@2k%^vgDP1`rlA@2)j-<p^W;E*M+o&usWT{6<sDiT`ay<pypIf_osSA5VA z2$BvCKV!$&423j2LX*X@H@_-K^_5q3u9-O~)^KaRgJ|^)^1dZ39Z{Z{{o#73bi;HM zb#c}M>a!JsM)(gLg`NQ`=~10onLaqoPl+*Rw#oipt`Zu&rTxG;D(u+KrXz!_L69p> z+Q_4~Nz@Y<xdqKrQ)QWS6dL?6gwg8Es#E!ks7k#L=k6Un@>F(^*f=Q|v`WDR+{h66 zQ1tdPLCWo;>^cXcI&Dsi*ezLvkqH|Vjsq&?vyb&na-r<}*;lJGg_`k7SNh!^xA%k5 zQ^h>#1Nl9(P4XH5wAF`xj~T^i^5nbui@?lJ<?cS%{?>!xf`C)S<f&v(XhXlOtijd8 z?k%#L%Lo9T5)Mfv#T<-hFRK(|wbYunq>Y2y1Q!-1wG)Kd&Ya_{y6_Cy($%M1zM`N& z;8yjukjUWj8x^aC3@KKMCiex_5qB<A6|{Xf>^}g+Zq(;c^lHC@ZVaoc&6h37NcRDG z{>Di2KqLfydO^TKN^yY_qAF=*!s-L=3hC5Y1|joZFdV)fEp;LssD=jjI#gYDq^%Ru zpiElU)##lyaRt*|rcAna12|ruTtu4DwAEf*bLH?QN2u>Mfhq+RIbEI`gighP<j!hq z$H_R%(6SL%M!sf}MxIvs-Dqi)qfg(QTPQ_F=2~;<fg>a)xdA+sf`>shbdT-;K>0$E zbei5%o4iiI+!hzc$$n(~ghB*;G81FAk=qB;=Ht~uVg96E%7##Whoj-$$q`J{VT=*@ zCi3P`?0WGWy)1#75>3tF_g-9+GJngHLtBa8$s#2%iv$_lQ%Ay4;SBFTx-2PeEXs(j zqctUpgk@hY;XRJwU^D7J2)<7)_iT>z&LY->Bpl5bU`B!SgW2;aEIJ;=ikm3^j?i-p zs)4kfgrQUQMYIBJZ)3@5j7-QrL1*gTgVdl$+TVh3#7<}+_<)Sg!q>y9S1od+;?(8y zjpzeMvO?cZICpV%wz(h@oJ>XDpdKskN{O!)Re_J5>~QFK8VYGfeo8gNtDDzSGse~8 zl6>9Hnx>w>k3YQfWr2V%Izn%!fpql845m0hq!80&A<tg5pMz6DI_om&%pμRGfn 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%0AY$%6aB$Ou}q1h!Gwt)x%7 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 <h%1ROop$P`qDp>E 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'vfNDB6%jy8t}q7)^kV8-Wf1V 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�SnsBM) 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 GH}ZduSYCku diff --git a/Lib/ensurepip/_bundled/pip-25.2-py3-none-any.whl b/Lib/ensurepip/_bundled/pip-25.2-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..e14bb3f37c0ff4cfa105c71d5e536f2f496cb160 GIT binary patch literal 1752557 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%0D1vH;B{R7rVyIOT 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 aBrJ& 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∾̳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%0AF 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Ⓢ-#o(r7q3+j>!gy&#~&Gnxfh$z+<h z_!yaIDj!%l@96lJoR(MDIUWo~21_V(zm}K?0<~q(jr!d8rc$jn^&2^MWg>6^K_O4H zn6{M!b)wNBbr}tAh<)?Wli{HC)Tvkk$_5~ow1HQ2)H`Nsp7T!O%ML2IL?1`<WrCNA zAD2o?TV}~C!|IOr;k0PMiIwiW$(8jH;9?4bH86a%s(Z9@NDIS~4dBp4TJhlL{d5VD zg_p-Jol@}-Qn^yXZBHM0jc7)V@PN~CmLGo7Adg6H>&N~4y12K~Jk8(E_y5w`6ZkQ0 z{o5`V9Y)&rW-are;xFRtb9{^8CfZEG2e<&=g>QY-JCwt1rAZJ|;Zx<IjnpSaZ1lA2 z^faVE^rnzl97UPSbRhseFkh=AO0V+Ssf`5pN+j2UZa-LOIJ#rV7&rGknvs(E-t#hB zb+jx=fk$9?Ly0D_fLnX<td45EN!HNi5cXHlIpWujLXMe(_h%ogp5+u04`ewyxMEuJ z-L;#W8lq(|1nB{=%4ebI?fi(!@>%N?<YV#4PU|WhvnFpJb8vbDuS;E;(}*z<Ev9`m znL4Hfh2US1Con#INV&xq6C7bpK6qg@HD}XQLuS3QLsK5Ky5PI6Mf3Dl9EPUv?(}qm z5Kg|o0uG6Rn4?SI2od{Y7*}PXBu{c4X{203B$=~vB%(7*D>B5YGb1KopfCPy6czP> z1vGK(SfHX-(&VhVoTOuKjnP;+x>gPjn-9172_IpIq0;)iNwM`lmEh~xYZHwx9W5}2 z4&*Q(>@asuw%ZCz!FuSiyS+Ril}Y3aaY&69LA%D8XjV{}2hOd{?2KYY58)Vtdk1)f zdZ4j6OF0o>$4Cbd#v^F<jK-&3JEtEP+Lu;4Zkx`$prj^H6z3sCF^JNb)iR@}jd%mj zMeQy316z@Y=V0|wn7vZ?-~<nUEXC|THKc?iRsv@w;~s>ICM68~(qH~C+>Vytr5i6h zX$k6-@v7^i#dTnej8{7)`~J>7jY%U0`z0$^LMdu#xMqRw$^}u~NG*E!Lb}fXJ^J+@ zh|pi3{Fmw1HzE|HAc)vBCQYH(_9u>z<n{|DNP>nTlElzG+a9OLy`6=^=$~%IZ9E2{ z-$Vy_J8csCxEKBQ)hyohD|-veIDcE4|6<%f-ZkcS9{%ovq3B(KV(Gi(<ZXVGz2)P^ zy%1zSzR@Hq-W6;7s}TF5*hkxTFDUYTt#`aMR_vQ|8>!l}=+ixfa5wqP_U@XEHGaef zQOKSKwC5|n{YL3xe-k@ndpiN-Z;#XB@6#{;7=s3i;Y}#$P~0*7`K&7#O&mS!|2F;l zL5Ok)u<yrxD?~Zu?`+5YH|Ur2H|Q68Ig}(BGmNuvsQ);r9H3hq`SRT+2&1YG!N;81 z_g00L%FY=A-e^rA(XyjOy6yG_*4_j}qTq(T>9WUa4*>$|MWXwI@*?wybgzWi&d{1! zkNjSRw_@^;R#7H7BK%=hg8np~#fbvJR)QQ}v_}a(0gr%<o#5`LI_fZns`-d^%JC#~ zqAaOv!iT_{s@T-od6q|Il&e#K)x`Pc;Awhw_1C(I(3H8Ch*aGPzVIIXwCX0?$nz}< zt3p76V1ek65LmlG5NrvsLP?|YxZWerOzW^l09CXps!fIEe3KCpjANCm$n5oaI9E1z zz`;;FU7z`zj6ZsfuGetQFR$YCFpC}=jC25Q;Q4Sv*ph^*zUI9A=<}FxogI~*y9a|@ zrA@z8bJ%v3X>eic!VFQ1wNzXNcm~IM0(ck7CbC*QxJXtlf|O`J4{HP)C(QH}5vwl; zxv38%Y!DgY4v6*e6k~aKkys;~A>teT(jNJC>#i0Q3B%>6RjxhAw@ZD*jusokBY5xe zFoXM(atxD`il0)4g&pZNPJCv>C@|}Js}zp}eHw1Fq@3?iO0pKbX&cW|dFoYZNK2;* zN$mW($K;YcDkZ+YJew}>*AwvsW-GOFWwyI3eWKWcryiF8weYK&Vxxb<ed?ZZ^mSP1 zlhN6S%2P@nTagE=6W2V-qyfN-JE2jWV4F$15}w{8XGxd3f%<RJFPmm>gQJ2sXN3_z zD+f||rE>T@huxphFDdE=4|)<2ZA!h~Rg-mTW7DvGElDBUWhDNxx?*)w??*s6U5NV8 zB6sbwy;SPTHM-l~`!`w6JiR(8^z(q<ui*+ygskaJ#J@x>)*Ib6*_ynez`+;7iE!t0 zSMYF|xDBS2_mCjRt9a@ygkubGkzXfv8X>mK!YtPGmeVRbdpK*dv^l_-x06x?ulm(E zvE2=>a#FEl5Ni(TUS&T@rjQ$8R^pk9(qe;Kl0uz6Br>b?BdRZVpf9th=vdB;+Q^s# zM%B|A$VcHAN2b;r?uo&#taOAM@)!;9a2D?-fwN9FOz1K8!_EbWg4-SR#1P9um(%gY z?Hj^9ZZ`+{$OwPsx~hAk$b_d(C(ei=3b4i0R4Z_1Ucmtm4Ay`i16KCk+BquReMPz8 zwnny-(6fQ#x0^YVd0OM5cpWmF_u9t=K6Sj)9Q5`aJV(H+%DicLAB%XeG7wEZ9*&EB zic`FP@HM_LEbR&4RiC=&jn`wu_UMw7<7r-~liMH}(A3SssLCVrXEWx9EK21<+awSR zGUVkM%o|xq=|k5Xvt{i1ar=}D2WzBY$wtOIK|;WjzzTsjuLiiR>1`7&%hkHt+D3Lc z#-7`tt;z&VM3YSw$Tp3jNwLM~DClM|;R_}W;8CK<Wvv<$e$wshcF{bZNR_UNXYpct ztKMiBdAp*wD<fTLf$SDz4NTL(hcQF*`-}YR-6naH*IoOsFA-he4AvtM0&NV6&8Kll zFZsWIc(%|c;`ePQ|CK9#=|B1H)xIWbB(iUg!PFjyNFgXfA~;S$C`Dt)_E(r7P?W-d zI^vbW`>5!?tsn0bsmMpYG<$m&z3CK+yz8F$yPBH)l%8#0FQ}c<Z6qkB-?Luuz8{6a zd&5Z<?;}?`!=m=9ZnTSjd%3?ec7E;FPQ9B(HcpkjUwt=?&-a<Gz3{qGwfNr~Dco3P zn(Pm@H%w#u{u}|lJD8z21<ZCE0yNzV5()C2x=r3W(!UQAv))tx?UxZRy}AdL)}%vI z=@7$buiOuRGSXUoKlQ&|o4ajX?DN!rZVNt~iFy?a-rmhIM8UX^(SMyACIU$yOP`a) zKIX(^FA82w{B{EPBeLs9SAh47*bIEK1eiZ80T@)@{?tA!0pO2g(yF^PZ1~-LUcV)h zHs`$nd1aa}y)w1kWTCyDk;_IF?IscYM}k1*+deat`%J7$8-cG5`v=PecAEK%@9zi8 zfVW)$`>m@x@Pl1|Mu1?GcTYOF)2~y!CacZG3LQbJRvUva$ZXUNkhZDahgpLLFlLI$ zf*Op}hRSrOLjrTPXweUax)*axTiWEIl6o#dtkhWqmM3};dMUT+-i1yXYXSvzWloe3 z4&(sK)a@h?Ke2T;^ko$YzxDDL{?IS3E~@u~L97{30Dsli{pYUkDFbMNb<#xNxsrL! zafivdoYbMS6KfJ%r8=LG`*9Lns(I3Vq;+I{z}d7$T;q}0M;l?Uz)BRVSW2@F30gGL z$C!>;h7)NOexROe-ymn{(kx?_e%@AUfpr+vT#L*rV{j-A_6<PDt3{4b^Uq-Qi;>(0 zTj+lUR{!=D{}figx!O1S7u%#Rg3vg+8wHRsjBZkxf=L>ONeZH1oJ2^R{u!($#XioO zC%fqg2JIoV8~Ty(JE`^EF-K<aS&euH#^UFtSNuJFyNTMp#E|WB7AN*X>xPEAlU=@- zKht;WSpN14h_l@hZx1#5RnZ|Y-oAV3{?sJd(R6=6?B(5@+UvtHvez~?MZ3Ku{!UFx z$X#)x9}^qU4(3#{y94YsWb!>iOYOxEiugD7d-vgCkDtZ=a0-y3m#5|xLW(?ksL5_d zk72X^baJ-(-sJ9YL+c*?D*jDq{YnWl%dR=sPtdw*thN{|x5?r&q<@5CVEZwB{WY`# zJGTB4Xazni_&<ZzuO0H<(7OH+RtEgm9nXN^?kq4AH7c?HJRM+xeOwb73&^#!Wg195 z7QCSTNpZxn^rZoPsYl`L>~1l<qKCeNyg0aClOrA}e)v%IPU7_YwE<5B;uN9}q&;CD z95YD8iKL_Fk(5nS$or<~E^{ZE)TR_*zf~_O(m^dtoF--QJ<vLKr&LVu8UuI)TMze% zHTZOuBd2)QW!4%(HT8J9TK3BeVK`TwYY;y;OgJ4XOm`;Hi!C=CpQc6t=R@1`To~ew zYA2(bKMfN;t<M(I9NI!Gw;#yum9yd62VFy5L>4^eob<ej_IeoHsRWL$-W@&RG;i9I zNvm50ArC?gku?~(F)y;~VZF<V#Yj!252~<eDHp6ykme-^wZZ!WP$8Qm*f{9TwJuc6 zhEg5fW4`5_(O;Re9+E4T(F2->yoe#p*<BnYb2Ds;_3MK~WgzXa=c-N<a<F{wQi~cm z-p2)2>(lj=g4p@G20B8dT>K#Kvj-u@>x|T{`W)+%Tzh5-P<Q2VJdrI`&jw#v<wGit zYDmgay}jm9s3|bN$Wr@2jt{Bxmsbboo9ru(iwDZ2=2F0e=T<1avQXL<VrGl_sXi<u zV;nk7Rrsn=vKcItYq2C}Wg?Q!zMRcw@=09i2}ghtpk;yv6`<U@;3wqrD7|M6mlPA{ zXbG!q1&bgocMItRfdQ84Nvly))#Es79QhJidI=nsgX@6z_$D#rx9)g9`qh1}h{t;# z0%Zk5m*|O_^1*F;AJ2S6OT6^Aen6k>fDoX^^OG4md<&-34L9N@Y+$c@jrPwV?2|j* zU3*>n2M*sZz6JEcle~*ZzEJza;6JEfF^SWyz*<o_u>x9#b$qWuEAKS(Qq!aQNR&3* z^cpOvYWNsifOgQSff(vMjX8XAgUV2XE5!Hp8n#o^JkAg2!Xsy>Z^!EOXbkGA_5lM2 zRwfb~rff`+g2)MhF2<cO(nrWT&u3rc!K=lR37d}eXr>G3C>(VTWgWfsqk8~D!-j^A zvd<-(0fG=;kZ`C%P%z-2=zP6TjCMd0usseN9n#Q@Irz#Q%^Y^ce5{S`?iA`bt3KOO z{x|@qdz}ca-w)Vf;y3hj;CNAswpomsJm1YRrjG{fF>-%AoZ<DpNz}T;)g!(7;~J~* z2)q=@W!p5GB=}Ucv(~mmDW6c^wx8*Xkvy>TlPD?_w?A4Zu|Mg>?LN1OcVL7`d^iVS zCaNcRi{tKoF6?K_k;O(X2kW%nGD($|h1527c9;j>u8zg!Hen99JkCaPyAKgc#R{Ne zFBDHwo@6&TQwd?fVrn*v6hHLI<DqJ!8wd`~UQIGN9~g`w8?>S^_zY49RX;Qp5F#Xc z3OQuH$e9nS(x{;;S*it>z3!8rbE4nql}1Y2B~IfK$K26=fxWR@A6JHAFblYiM&~gz zT3*3BCfzg4nQ@Q|pzUU^Y3SOf!~zZ@jvl2P>L?`99Sc45Czr$c)2lCAU_+PIvQNVV z1|CL9FNHvF5}atd6F39>H{9`f$<`RtHh9jF44qNm&LoE6{txr7fIDtfjlVwHxPNp- zK3uz7-2BU7zGHm<;Uzv(ygxtvYkEiEG)z!5L6Q)(J)j5@qDXvqg+geIhH)B0arCF1 zb<{gAJAQirL(n@30@_&-@y_tU-^NYE2R+&|9DW`y@s2awxE6_h3Q5~L-FA!O-I6KG zb{pgTU5nWJRj7A!$%otVuR80Jx9{=B+cuWAJs|SE>zSZ;@|dT4HywoRQ^Olw+YMLH z{gn!G7w4Z^O!8f~Z{N$tG&d!`31{LxPqK&5{-w^kd{@ooA6|J{lwQdAAX_&Iw$Aw_ zf+H<)e^AZS_o|uqRxZ_!-Dhg&+8Z1s0Y9fL-A3VtFbKYfg1MIaho>HJwr10>dC&H> z(Q?nr_KATs_Pnj?=`4Sydq-Swd|9{rqvJvm??H{}*8Fb7cLX-lWV*05TFx=FJ!$?3 zo7mLTrp7e3o;6oPKEfu_SzEQ&mt)ocZ@-1#wCai8p&Pi^MqkV0PSH^T<g~`s{z_c6 z^L09F*7=rm+Jg2BR(Tj}qGXs3PRrhL;V>qNi@DOh#*d&v)D-4ii4%}6PciBTrQ%L$ zAw3YPYRX|GRZ^|$<Zw^IBy;Oyx%jD9j=1xKpDplbGamJUXV36V-Lms`OQ{1-D*a<w z(d=1%k>$W!m&@kGLiE&>(Le@uhdk!XJ*Ea4O|W0^gD-a_@LjlfRwy<(V-2hwIP&fV zpgrCCE<<JM5~KEuQxW-2P-&^A{Ly84eg-an+lzO%-ToC{9mDM5-Zb{Su8&uYi#L;N zCiAErZ|7o4*oMYZm%PgAYCar>ifAkofY6Dp{v7gCc=yaM@1EA*Kwvl60Pl1@a`u;* zHRJ!dj1(~X|Ll*w|MTha_d@$`ocBde|8UMXMte9!5)?*L1cs70N?{m<5EO!;7=%GE zOk)&{AjnU9_L9ASZo{Mvt8ip@cHi|az59S}x;1+z?ZoKY<!O_#KaWG(l=7bM^G-Y4 zbTLZpTaV=X>^-)J+Li0}BAdp|p}paFx3+rw$P>THkVK0e;FEWJ_y*S-+<uY3$=lvw zyF~F;_eA#A>d-Eq)3^839$p#m2X~7qbRXx~cSPSQ;^^D?0RO$=LG|{C5B`WlV>qbN zB<9NxcU@jTI5WTHv-Q1v_WYG|_8qrRX*jaKeQ11$H&jnQYW63eO3PBYIeVK@0q^id zdzgh!K*YVai$HryqaInc?|e01_8B@~+S`}(4C}0f1D~+-<Dlz(4(hS~>N^8|_16CC zI|KH&_BV>g!a?hD#H!2U0uSy$m;eT)t1YkM60~=t88Pz4I`y;U23U5)AX^kgEUqx( zpEh2<<|wM9<R**{!|I~X;q~Hy_?dRK$zOcR(_V^hg&FCM=@2d53C~ijUq^+PrulTH zUYvGfW1{C1e>!KP9%Rvwf&hvoiUXS*az++0e_-e-b33TGoYNOQChYc(4EvShYeJC| zcckLu0QNG%^6u+aZ=c00AlG4dmT%cwQz#~aw=;pv6W?MG6LosPy80b|S+o0!10O3) zt)Qr`yTM)A1TnNG#ROF7wYKf5m#+uCUFvR#9n@$M96ruiTKnh0Cm+=5nv9sLfGHSg z^PxE4c|O=ndYk$J2*>G;`fjhCxGp*4IE4mIbxIDX79umr7B=6o9eHLI^DJpfr)vlC zc~fdpn)E1uP9}hKS2VV+-MDCz%`}k?KSk_SEYdVihOhEy<J?-UQ6)J{?XD@tql#ag z)*z_z#ycznIJB!3%xln@xowy8tFM+DJ>D#bkZW+AA}dsnn0N{)#~~HUhNOou#EZ1Z zx{fKsc7e_TUtSr?=7C0{N#+p-q90Kxy|B7nn8Vc1+H|1!;&5dzse;Ur!=M^+n&B7> z+2eWwYLN|{%atlhVIGXZ7i&vcW7{N{)t(o|!hE~AJt7-A_DBrLaUh-{;*p|4j7?e5 z1^}M&5sllS;sm-;FAq2Bj}T9_%;%!<ho8ZZ=8M)x3Lfwm6mE72IIqz7Yl3GT&QYjk z2qU)1M}1{7;A=g7JNp-2T#x7W5XOdW>JWQ*<c#iD=`Z~Z07h`k?XI}8k3d@0O1zZB z#knMU%1mgFdFp`12ul>;Ng7l+J#?_7!^-0tT-Ag`7ia`Vp>ZYG58<xSQ<Cm0^i-YG zB}QIEjGb64QKv-e;q1Z+u_`^X_%4QuaE!fUj3`mpCET`c+qV0*-F@5kZQHhO+qP}n zwrzhs^J6m4JKyA;N~%(+<W#b&&N_S5stu)H&BGHXM_`EaA)T1Jq&&k+@CgdU-0(KP znphK*p*eAiYZRtx;kG&yc4e%VQdfpHycJ^%dyyW%YiG@7xdw<FVv!shcQR9S+0#Fu z2Zp7@@}Bxu620dgMq6C`_}5WBDrc-3*<LP*V~DkKblG+=lvTq8fgKH6)Z+Wx3_V|B zWaaXBixYJR?2coH$ESSPdhoj0ib%X>^I7E|&=m6hbEJwUAWT-uETp8tK59r&M0>93 zGKt<+=5eT@SS~Y*B16>)fEq6Yu*(D)KBP!`+5|V<aTuk1kho&XQuue?6A%{hZ_=K8 zY(lQ$l5*L2vR*lhrnyLsFyy94OS*tYRRBvb(03fiCQ^?&4>*c|e)JeY!AtpDEcL}K z(h&p-nmLRPA>va<$gXg&rDRpY{M(EIK9oU<WlKpk^cl<Ef0o}9MTHbM6$1K3^m%Ug z8*<=HOyjd-`s>nU=Q*Ww2=EPCSV45!2B!NC7@bBitd1E`ku4d4?u51`G-=r8McV6P zMI!p#Ls&(We=AWHG%mrbEF$v&M!NdXuzA5=GkF9?xA6=DV;(T`gS;FSQ&{(>PrB*J z1j78tVjMh<W?8s>Oh)axsBXs}#@uZ=k&k$Jeyo&z`26Jflo>vEMfLgEf2;aM@MnHl zwX;hokc97HKqVlAUCM$E@(I*Q=*Rm)f)MGs$K0=d#hG1};Lwsji+0KgBiYb3U|SJ= z(4dL_Y7MsC4Q0RSnaRKz-=A+<pqMwL$5(Bq9XLSb9&4^G<gN)+k()BWY+I5?9avL$ z+SN!mCn&2moLG<6i&D2=hf(_as~eho+9KPB@1j(Q|592vF0RI6#;OC&PHER4v~>n- zR1Y7m1~Y|jK5ty)51w&oesIMM7qo%dU-+fVvf1sT`}D0rLLZ|La`qQGVa&Wf`_7IE z>Si2zXt6k8AL3Px@5@Yfp_-y|G=0YSoQ61Q`E!6P8xhQb78evIKrG(5WHLO~a?C-x zvBB3KQRO_m%&ra7Wd0>H=i0cdm=?gh9Lj<7<1e^gb9eY}&*NAn^ZiTd?{Y^Q1#XkD z!54)@TG3VX%W=-|c;ZT_{<mU2WV~+odMe^+wq2qqc0f2~CA?f?-@Vn<7xRIt%I$5- zYJFn9QHQKwh|``y0W?107aDMcWD6#RU@%TjL4eY)CS;`4Jiy!GZV^`LQq|!>8>ldK zIIm+Lnk>%G)8`E0u9%$2DPugll1+4A6gco<F21r~a1Gat%TPQW1}oc~Kur{lNIYHO zk*2J%`-rQ{ED+4<NYMVUxkx((56U7UqmQ#`>dYB3nb3#u)5b_mIyxmhkMo<9b2fVi zd!&(I%gGDiom98I3kQpiKTR8#>$C~$Y~KMw;<z8pgPvob<G_)=-5pnAv-ZgI@7^JO zioiEJ6-J8<1<qj8(=l}`eC7#+`sIPOTWYN@RvXtwCr%4>8}RvFNY_nWm~^`dZIHi) z9-o_{HwbMwWy)%2)9kae?YpC_9lv0_r%4FvDGI%^p{ieYRdE|qp^a7Xq1p43WChCA zCe`MA(~~E`D$l+&!YNUfSw(2!73bRXH%A*SO`}o`>!3UGpo|31(VZsm-HvP#=AY)o zzqb`+UgX23y6W<kHWWW*G~RTlc<6o>W@2(24>*#2>v*t7nxYk*W|oDlIsvftgR(Zz zzbx@`%r;*5>JcH7PZ7%{ZXQ+eG0f}EHFkx<^uzcg*7qgUYi#+QU@1~NOaM*iRA|yo zJgGC0Ho>2CD+iz_CaJV*O~p6WZ|BepZeUO^v9*uc{dWs8V}+O11!ivm(GMpn8x9E9 zPU7yCq4LTE#{v<B$Hc#-tlIu|%T$&J$h~8tem^J<+tKV9tBg4E`1cy5uKdepN>{(> zhT|Iex{c4)3X7+g{p21-<X4xk^=VE0dj8~BpRBdPMYD?!7Ka+p4;1|eLu!Vzj>eR_ zX$q_m?=K(jYV$b%ir4?$Fcca1Ql)x-RoL@!+@o!N>#4h!+mZnL>8!bm>h*qed9p{Y z-rTWwIVFE+&2pzN*%eZ(cjNl#@{Ob2iQAOdav*m<tt|E(h1#Pw*=!8s?qNMJ>oMZH zt-1KZY38X5mf%`5F>=fI719~y74E2e8{1+G8g--YuKvFL9`rng46?IPF#2+<*KHH} z={&I4mD;CEYx0iKP*NaxYL;V#FP;`_m=-K*j^TY>``Xi<>h-++X2__Cc^FZV|6#`X z;dtre!PxLUJq!%Lb&cNI5dfeYrc&YX$FO@}qPN;1H~(vMOFUI%F(h_G%t=<(*%)O% zvRe(%%P6|=Ll%H??#e8&w;?V()_p6UBa26bmkq-o=MVvJQOtcpE+hy{VIWb&9XPuE z<@%&}4Nd8&=tP~lu^$Mb>oeS;qNH`8u93#P^*7cQR2#vl(5B~*+kJ*DP<;z$QLb$K zjh=Pw(IQE+=^uhQxbOTxWoZGy1Tz`F?B!JUIJw4qCL^Tv+K3*Q^|>@(>;SNWeVn)< zVi(lPqe=Fosl<dpma@x%TLIzTy$~F2rNCgB^_dD!E%SKQ08{wOnF(ab;I8>vlQ4`f z=tnAJ0{;u8A>iztIBBAfGm@$7yO`D=!p`%pGr?qG#OC7$HeK+NOR5Tec<H2}MP+5V z>56;OJ4_v9y}Wnp){Zsjk3Cb`=-g3Hvk}?A&?_G!cZ|igc-4Trcx+I#0{iws3-=b{ z10?(j&`2eK;%W=lJfR3(y=C~0TZGpY^)mdXWoog!Vfy(=S7Mx=+h?@p89p9<ksA<F zGvN~zRseZhF`<+0$3C!2H3(zR{&_brj}`k<==P?tS>T*M*A<RRi3Yh6A)+@a6)sxj z!Uoa41*8Th?ft?Fm?P12Yi4dLK%fbGwkEIo1rqcTGB?rB2D<iJkrd}kTx8mN1FQg6 zx}d4wo*en%x|`tZym5Q?M$nP;f=hX=&Fw0YPbXXFy(i93J4?qgT?$!Pl@VK?-pzC1 zfYXNcsjLEslQmO_N2wAM8Wiu!6{ggw5W%HHYZ$uNs_l35vH={sh<I2Vrj@0`sMN`7 zgU3^y<8~*xP)k%TX~@?fO9TyzR!NC|YMIcmeWae{3^|EN1Tz%(O#Ibx>x@zzd=YVF z5Rc~mp$Vs#qDB6@>&!4y+EE0%3P+KsLhL8;8Kz##Pjt02sIi&ti{QlPYl-c#Dbzsq z0j<Tka0{2?&}(X${cmv(cADkSah9E#wkz(LbOEOTmt)b76J7GgeS<p6^P|~9qWxJ{ zHf9T{OK+A?p9K}gc2nUKU<k4P1l}VB_PV`mfWWa_1##t)c$ZV?8+P(^ZO*cBsz{O} zY4ehzu^q6EpO@Y{5J+}Cu#mE6?2%5b$klGRz3ffA#xgl{F662;(J}By%Dh;p@NBl@ zc=L03ZUKtfL~hRw6@glULZQO<?i~K2d-T5kL7C;1a(&P^s!Q}@??n+1XNk?(&2m#= z3=5Vk0qz5F?%_3n0er6a<HuNAyf!J>0QJOfgdwVyiPmhLT-V->6(Q+iTBd_xn9YG# zZ%tk&AQ88g#7^ZEIf+78dH#F$ldAycF9fm*HUwL3)cfE0rbTn~#yoxIX&EW{<|&+R zjRqV|ao1?{%(Zwgseb0+)qlA-fGGS_nf8vXLy9F`e*=m=;tG$*Rz}J?%iOEW6`Q8r z+$o}oAk#0lI$9n_MJg3#DYAg-tE3EFZNDLZ`hN(2-ml(#`g?9|fJ701^5t=cZLx^i z;Y4RKWROAN1~4QL7{ac!6A=6q8Kf0C9vA$%BCqW|4(KVHZHNa4W!2b%x*4+`c@NQh zV4<0fN;omz9@rk)N%OZ&m^LJ^Kl*kgWI(&Pxe<oCRpa738V+=N+%!4#?z;H}zsGB- z$zN7?vkC>dbExilYvD*={DvR5w7wNSLdLuXfW%(%<v*KEb9Xo$HYP61h;Of58bav0 zu3Fgf#1(E{+jr0xi@KcXzvqLm?^5J5&muMxTxTR*!hVB|35DewKElBF`S*0VpMbv6 zUhI30bigWTeXm7#Y-_%Dy)SEQYqHz{e*!(e6dLTib{6|+mymJ2K5Rv;47f~>!zFO( zj7reKCQIU!fLjq3obC0>XjDNCKv%m6N<4GQ#f}?R4l7GjLo7ddcFG3j6L8p+Pbs;( z41wb?@KQZ_iH5~l_DK;qx<VNhitsIXn4t6jq@(1(_n@rw(sK{@G*!B(ojCzL5xjIl zd1vR$8gsTKKR5!ue{crXM0OyA#72OzzY~-5eQlhHBg0p<w@Tt#y-15zMy&#;O0<+O zVPJp@jY#jFjngYE=z>w?r=5>BZF16a)ANH@T~4N9@%n#$x271-ic0@O6j1aSe4708 zR-XCQ>xvM{w*+yIa9q5|b6Ha%|LpTWTO{HBP$&@VYV`h`7$&}v(;oU9G|r^HOel2& z^5JY5kwz=B_$#3JS@D}ekV60!*x~fqqU2L%ew(cx^ZiBxs?cU&ZT?XYzADn^SZm?g zGpND3vEm={Hu}hVZ`=M;-*plCIlAQO;c7)7G0UxC9hykm0U`6y<@67}D!_S69Jzy| z$ouHDClB?zIf}N_YnP1L88x#fV85C9Mb$kd*VG0is;=t>$Gc+f5ZAP2W`EZv?G5gH z)3hvt7vDUgPW=An@<nOxrK>mU2hl&fdcgCL4syRQ)qP=!6m8)&1c<#6y|pOkDdb~a z;X_Mw`4ailt@lj{yOl+1()6DNhrpB7$cvNQzR^hr$Haw4<#ENbjSZU?*uvIBd6#An zt0g~}C+Db3!@xI(%0Nft2d0<$G*%CXrB|b?F5uOm=WxS6pAMaaj_)(qd%nFjI8!9* zui53VG(_MY%L^6p+rt*;u}4YEOpAohZQCjmS!MJ}GwrN{gbcKC;8tscTIBlU$_bsw zQ)i$hYBEWp5TRA`p=mp_OpN}yK)bDCSECJnF_hxZqT6hCktNPt7_kpQ;R^&Uiq>J) zTP|m%^%lExIQ;~_$H;K@=g3_Z6~5*ly!=j>LmUM<9!EEI1p!!M$SwL=)Cfq*Ho-)@ zF#n6Ed?^(gF(9VwzLQ(aw1I}=DdHZ2;OEXwBtDnr175B+-z{Bh=;9sj?6UW#u*B;s zk_hDQKmF+WQG<H|H84SpjiJF9@Nshmk`*Ia+O;tHy={Q8JXP_WdBn3cf|Gfc_W%zm z5eOUA&NSg5{_BLr$>7!Q<xQ(?0g>*10{Tw(O}u$r1<qSt9)}hG8sS-Y;T_cDvey=! z?WvJ~4TnZ>GZ?w#4}i4)HtWpp>f?}@LD%MMfVKhByZ!yK#b87O3|iB%%8_tnN&(wD z?$};ShRj=r6dxg*GAVg=)~93OGM6V^mW3&eqgUVJ;WKR&h$~;PbbLT@ujq6=t!kyD z9=42Nk=tuad$C8tWaOPnZ+FZS%j}OuF3t%cKR=VtqiY-CKIDw(KZz?u9@u7<z$__; zVYMEW)-v^6{CDyIEepBenNZXvNx~m-0kGM&Mi{uSXdO>~?Ak&~x#6;~2PuY6BvY#^ z6PkX#m6MFghS+I$MRhR}bt8zvng`!^HmN%>JH^zJZe~DN@^84UbFdf?7|0fSB7)q_ zE-QaoO?#aWT6a5b;pT@@dRW6(_6^ew%u<~TAVVN@Vi-&wonun-14@?ODX_pTB~;Z3 zW)e7R-7q|mG5Se{R^dnGXtPkZmpNd+6TH|$@mJW+&1da$$|_kXnbTrHNSe>0;Y}$0 z#o)l8ej-5k!Re#z_|ehbXz2P?kSt{S^nyw&GNXPt^q&sG?_3A-LK86t{$ct9K2;5v z6Hviz-;pN?>6tWh+nh4}SY_G*BEyffIZ%DZRcTG>GA6Mq1M*T2+*tByT9^IZ#&)#b z0t;ZiAA^9PRB?{39p#TT?8Ix`O||e4rVB<io}tyMe7MQ@#%exe!b02Ple)<ZRP81) zs8U*0aF-XlnS*g&ThmnhVrR-~gOp*<8>z4#3c{%P3ya0&wI(jj!7iQ$f<9{UQ6(Dy z=fLTZj)4m$I=u`u&+}>+Rr=(6ea<$-5j3+si`-OI9o_)~=M3Bg-Q+!%I8Q)6F|<~- z_FGGN{Wg1A@sg5_DHZt!$G1Nm?-u!@-{ui?F-Wqb(}j(vj<4ovnpq%V4vJuarQCDG z{RwL|AwfJN8f(tC{bP2V_tEkmWcVI1q#H(uMtOT_CWQW13$V*LheWC)5V@GWd>IUA zB}U5?IxkE$(S6T%C!T{*eI=nSXdHx2l1B0lTNH!)8<R}ZbnN`Yz)0KxDSW$Yo|#gs zhR|DwMO^+$`h&Q~p7bBwp>o2#I>y-&%hETrr`PURPLl(aBZZHGf&yOLbwx~iFU4o_ zH-Dk;&GP=mXjg{`&F6(mG-~p;V8@pZW3NW9`MG803G?AII0{K&wvK{i^MrIqtyV8a z34L=91=P^I^hnYFz<YiDJR5sEKK)!H?WDr>&kmmwxEea#0GdMyK}PlyKnO7!{j}L2 z#ck+Y7P!C8AZM`IhbVZQ>De58+wS;m6kGE|y`D;a{+M^flm2Fqx1CVC(_0v3Z_E0o zk`p52;sP<wG`zlp8LK#(7%x}sInfJ|5reer6XI^(+#SH-N7|2waYGmN>+W})FcNqT z-VUGEO~`rnGY72X(eC(ReT`LZBCZk&2gZ!xvV!R@uKSSa!Q=&8Bju<)X3gV8vhtxd zEL;iy#zqlrT(qLe;K8_@%y1WM`{8}QuYMhb*nW;$y~8-%%(yt?%!jzfuiIVZ#3);U zcYAbq->G*sD{XAlp)13Fn08ole4?2?vd(%Roy~k{%K3cId%R1%alV~%8bZI|%e{+a zzNs=EJpGH--Nii><SzK60_maK!425Tyz>bW;{52ibkqyU+gnkSA^S7_nELh*=jcv! z@hLFQ<Vim^HT00RVX|Y`w=)gco&D&5*7<et`Y5-sU=G#%0thnwEGu5<c-b&Mf@gTT z`;?$#kKp`4f<G+9?I2y<PgWTxJDd@vI-I>_7yxPB;=HFpkk&Z8Wpo}4AAL{2xWujp zG_afjtNdLN+HVp+Qb=qe8^GC)KU@9xPx)$aO<riPZd*vj{MPmbd&`=+s!K40bNCLW zpN!fF==c2VqYdDBGKA9l=?8om+QhDRke)ygWma`Qq5rfW>yTP+OmHWW8fm|tpkh4k zI`^WX65Mdo3Sw?G#&4;><lwZAeQs!m@FJJLe#sit^I!H-6fcy6Ad8*mC3e7vI2@yt zlGs1Vd2xi7sTcU@6E+27Kk9X#nkjEib)VNr{I+^P{REj@t@OXC2(VYAaf81dmb_v5 z>IUlY1@8)=p@lyN=zr^-f>M5_N($pwd>B3-zCk{cM&tGo=hAV-ALza7-qwT^1<Y8) zd{ckh1;a((OM;J(X$#;5rG~<*bSCWGHu$ux@Ud_e3f<v@<_h3H{LmMy_l+1Qkwmw* zuJjjp<6CV6z~hT9d!+po0te<mb@q%*pTD49-q^_-yz7nD&|472RQ0`%E`2C5W#W9o zj&-9-#f^PkIkrr-fM^z7k8X0QvEX0tOz?-ce858;!{0gks4(>8aC}$k0f`NLR&(U{ zP^6CbP^b2)Zhyx>lihAf^kf|IK^@OrzP%h`z7A)zPGzOmS<o*Bi@^t|SS)CpfVn%u zTwzJUrA;<6Q{-JMfjv<p7HoLPWdn5WS7#cX7tO6)z``vJ+0D=vJJP7VUk*^7O!hF1 z&bBZ-x-LvZw%m`;mTMY99WA8zQBR&_hICglXKO*Zmv}*_t({?XX!<Ijk@*j;YYee! zWm>NlioKiXS_i5#nmQrxNW`jzy^up@4QOD}zm-`;*cV{^=;)+WfIkeeEu0o68QQqM zjHBS0HyzoG42y|XtU-qZ+=c4u%$#L0YXS%!<VMR~7l~rwtb#IiC-D#pMl(_6JY`rx z_bOphk?{KldO7PReu1cLQDH?B-(iIJXBAQKO|^14UteXjb&;MfMu=%MoDPwbZgNoi zp&|atIIwZ0f*x)HqWm?iR@u^7hm5v_NQqtIXe9Jm;xacRUYk#cOH77oC2|NTU8<bX z2+DNqCB%O^dVr$0J4uV_>R~VSCu$z8hzr2$7$_p{fCBQ1ZQMHxCwX{|Un2ZrqJNS{ z5S7=m_;T!ftCgw@wzA9XB=#Yb;j`(8vcPx(x6fh^TDQm4_%fl!>jE%Q&R9$E3fZAA z&m9p=foGt(s`Z1h(A(9Fr5Ye*Z@A@Rceuj@<pIrhhHahkZ)xk6Gft(C$&$B9d0v@d zRT6-#7eAKCO8VUtgQ*>23g>Q{d8|spTVYWe?V>j1Th_04%>zf?KpT)Bx-2i?yTrX2 zm=%!gZpUNOXaOIGM>;Iai9%gLrLA`1xmtWUPH1?CquUtcdO@Cw8Weih1-`Q6l=x~d z*{b6&Nw*hv<K-K+>?`pd<&aqoW8tp11<oA@q!UtFI#%qI$U046M!By1Rg&5tJj}h@ zm<Pq>Ht9bQ@J-*SDu%xCJI%*AnyiQ(a1C{FdpIRFCM^CUvWxj1N(MDILR0q1U+E^w z0%ZIn`}@J$oHDHHc#TPV#j!zdBAkWvmqU>aV$1MxQuMM2G*-DCEZB7{JB$Ex?_sHD z1qczSk5jW?<L*U7Yos3YNTXt+tzX8q+k{Djg-mF*iWnGDvVg*8PYP2~h+a*iAz@^M zsi@Ak!-g88S~&(rrdK}Hciu1}Hkl-I=Ag1PWzx<*w=2Gkl%vFQmpe+3c}F_58uG|O zmvN{yF)ec}v86Y$g33tSupgGkP48Qpvi@Q^5v)-upKUW4<aMfMXkk>^TO4J6iCY5I zhQ1VWWZMz}<2#?DKBRtu8`+Vsr<FB}&B*|0+mh<Z!7W{qrp!)Ws;o5gtkat&s~;9P z^F$SA?w-zP7Ie5PxdiWllT_<#<AG9Y0=gBpGSK?kX@W8ttr7*-Agr$|C7JAx?C51u zlN85Ffg0gS-&!2SPy4ww{`1&W#~Ua}qK&0~WQyEEfV;PCWGl<Agp%CzCVz#-&a3wx zdEN7tFVbyPHEgwpp|L$fv+X*K4={zl$r1;bDg&yvNDR?1Y>J-7^40r`CSuA5zq!a~ z(1Q0)`0H6Bz8;2T0*GH83m72-=pKVV5k}|FCBo(Gz#Irz!Y%#R-C0b+*Yu(DbbEr( z=|Nf3!tx4v#=)t?A8=QpFyZ&-PbU?{Tg<@-zIZDl9<D5I#fY(i2q$pvLDR{rP(0D- zY>*(F{2L0sPP@_*{M_I8GTfQ~eop7ZIRd|2!#J@?9M4G=;yY6{E}q^^#Opx33GU0` zS>X!wfe<_O&pw|~vB4;xPZww3)*q@*CEORes?goz3A=&P=KkfjR1-L9@R=M>yzd)L zM3)!(Mk3oOJ{zo0L+bsBzWbkM)V`gc1C_vROjwmHb;ak*yDhF+e_6b&hYHS?+t?0q zNO=1^03OoP3O?4<=E{ZK?iTL#Xg#DSaUaUB)rQjZXz}@>J%nJ^zJ5<EYMxe`SveyR zA6<ZWr{@iK5OafPm~kyUiz-G=?5cf;z)*5Bo$J3o9u}nF?=eYr2hvMdm(~iEN~dT) zKy>R4;&B49n~|%ll=bqo*}{Y7B5f6@XOt9^oeh5}oH?2gI2@j(&+hT6Q<CH>=m>ok zS*!#p5;r^<l#xB%$Y06kNjOQb96bINQA32P8xt<ec{$sUQLV^;n^!xGQD;1YRL<K4 zT<1~Qi6p-O@QfF|&gP5Q?hy4kcwu&8DKi8p3wi+#tCcJ90J|qa_BDw(gWG$I7u~Y8 zrTgHeW0N4VvrlQm#7Xp=(6k{%eqco(Tp^kM4Sw5q)#(`6T&o<=k`W@PiTA{6o0?;P z*jvbmt1z&Pmy91f-rP+K;+dF=VHR?c76g+LO;o1Vt^TXu0RLTgy*p-MUR6qTCEGD4 zVu$VyqKUJ87D;eO8<d$hQA}8Rv>n#RukU3jVN!$a9BaEulH=R(<O(7^ZbsrFyyRfJ z)mawRzD%{Fw0=9eW=81dJ|YcYABl?^m(u7LcK>L>r>>_BaF!sYn}N{8`ei-(o?l<6 zvL!6sUj6x|p9Hc$YEfi-Sv(;UC!ZUP1uTZ)#B8Xi@Zftrw$E=S;`p%9&X53s;xe9< z-RL&(wvy&nqw(;w8Sd&D<1-(wcg4Opm_!Pv3^YP@ux{barA>kXT+Pgknj${QeNfDL z8y^s3`b~0J1(8O8izGpnV<XXi9b3&e$+=u9^*Pn#KTPRCJ?Q()n2n<(8alj}L}P%W zsF$m!+j!c%$x!1${vx4`6Rc!?I}S?5`c|Z=y;ICLz+wcRdr3JHY(k-PKub?))l-pS z^A|a2_B>g`McnZybKHnde?~(Nk;(ZI0R$rmal3+D5gbEe2q?gX+CMy$xy}}P{%c`{ zsgW|vmL(8FCkjHsbAx@o4sRCMK>#*};k4VnK2Q=5LGqS$6LUw&<6w6gj4dA-+COD7 zTr2%`(cYi%hT<gCu8BNffqD1_AsWxgHYZ&?hms)TtAwowO5Kfs7-fwFY=TpP5OOog zc;ePW*&~av{I|en(R463#RM+W8$l^Kc#S%?fJyhzU(N>nKu81SzkQ|FT~sHTlw8AG zg8iLZI(Yn>q{s`S{wwoyJ(``6n6g%R`v9>YEdbSt=U{>+2&MOyG;6ReLjv9IcZC>G zVgwh91MRobPBe(HUP%3|D^lb~*;&>=638CynX>F!BVKpg_Pd<EH)Mp`?8`i;xveG| z3jJykeCGNmQGZ~Y_u3q9vg&M4^g$lu55s1=$rnj(T{(C#y=3>SyFtAJ<q?K!T_aIp zCyi^}6$}02<nbMzR@#yX3T2df4VTwY@rMBDE{)m|KU{=jtfkmsbPFw89#Xujb{J3t z34We-ew+@`zHTC>`)`~ae|A4@xZ3%7Uq&#bp@_<G!xBclJBn;yNBc-1L4qhy$Oz6E zhz2QuNaaCxVs?A}E+LM5_Ca&cVo2clS%~Q=*j|>*<#?6jLifGqg%Zd4<UP{oZN<$8 z<BA4}5!kvqg7(wf4Te}>y9$Bgzm1om-x+sXgU7ty)$@n<74j4DUHS%pB%nL4a^cvc zUiRADf?whexQ&uqy#Ax{^rtI}5&1!HgK<wiAjpa0d7U7`w!?vs;h5qNavlOyojvn0 zWatxpAh=9pI14HS9o^1cWwJ44`ZO!wTOoE=rFcV>zvIGkeScOCA6DCVnJ%qJ=KQx9 zT>706KHdG@9QRBs@e2H~jRZ})vmbLm5JaXPW2AqnnSc9yv}-fst+IR;Z^AiY{8nD6 zse&CEYY(m8<wdaSodDJIvfEO0A5!G=Jb9h6^o<t?gg=>*C3a>bLSR>}c!Vi+hR5LW z;)%944*)yWxvQfdmzf0)npGOU>a<XZEYs5uG0)CG@&GNI!)Bs_LN#y`%^v4FzHS~V zrJRo;Zw6+GygF6pOvJuCn7a>c3DL;JafBQ^^Wvw&o~`#u&7-*0TfQ{~Y%5n-|HVgm zr-X5Lzgi-D<^GldyAfX@n8bFBr7TGc^p!1V{nD+yvRuqnxU25D(RVb|sI;K#L6bpa zLGO!<pIHg9q0VJV;f@JCEi#I(WlCn!R*!`&-R#~sQq?0cEt4z?P#OD9BJwryFwbec zQY|raz4gpC-mNlg#T-~aN;(%WpU-?5bZD#LreBUkkV|y53MD}0qCEdXYayWPT1hmm zzm8J=uBFwOU=SwF!u;tx99a(6B5y;A0NE<SN+)E~c>)8TC?L$lpQ;wl=v9xfDLlq; zdsX|W8EBw%)x(ZCW0M4*bmfi&w!assB|GHOSjQ*>oExI9LpC93f4{ZyH8H80ZGfC` zc7f7ITyMXLj)m;<;+{o}-S|=LUbwX%De24!YX*XGaf-e;MC~SLAP4qwZ^PJBi@rr5 zOlvP=Zar167Fazk(b#T`1UhuQYtD?$sl^W_pyvLNk+W+n%jgJ=oZ+KRNN2;FkgqB& z>A$l4Y)QGhGRI*zm&^+!f)b1Cm+;N80acq?D^8huUXSx*LQrU<%Y89Vl~y&kjwgNm z9+1_)lWzW+mF3TRl9Z1YTc^iuY(|+-TulzMs{(Fuh3|KOJkS8M($=>-<B0B{=gC;Y z?8J_8l7f0>p)=1!0VkI;4vVHSlCyKeb`ic$=YH#`|G^%IGHrD$Gx*Qt@6q+De*zaU zs2+tYt)&CA*Kb`2x?>Ju*ya@DMmn?2aXZcl;bk3VlQ<_o;xS~d0flIuGbGJarZs>% z3&z(cTa0QCa)~0Fv*=Hpk~|=Gdi2Z$0dZdGroIFfD<cgQ`j8n_;xMGeg_I*GqkCi~ zkA1!oBJ>f<?%bl${u}I@^n>rNzqY4y-TL3Z&GMq6kNU~R5&rbM=x`M*ixbAyI;sw~ zaYdx=7zq|Qw^xbZx$<vKsQP8S^z_@7GA(!{PEEA!fK<v>KE?^E=qyBo@$3`RoEnu4 z<3^T9Z!=K25y!g9xJgZ;icD$V37NcK26AENY%L3HvJBvbO8y&8y!TCq`lI{&^vF|{ z0YVn#)(@t!tNTx#RTC6@bfKrbkdui1sTdXh?OK7`lD(^rbzR-s&nj3@GqNV)U;wq} zaEa(abrWj#gzB07i6v=OYcrg!Dg>qK)}RoKcPHPpp%52v9nseM^lQ7Y5*eM<c<c!o zo-2GYN%3AqF~P00AV8>EO~+zOH61r6+94P<_Sz4Si<}PR5wy6VOztM<SccBdsU7CT z>L=&Ui2If1Z;7Vbu2V*7p(*)9RRbZeHJBfEeM(I%I<WZScjoq87G;i#E}E5rj9gx- zjDlfizlRSkjFLJ{kEg&F(=xIjZ}Uixp!E;1yc9493IG5A1b|iXw?@_{@Jbvo0DwI# z008#yRa-M#dR=E@Ya<(b`d^CeXl7zYZ)jt0OlRwMr7UA{D2mW^p^CCTV6I1_X3y-} zml||QO4AoB0Ff_NKG(RgK-(^5NFDO|{I`rWsxS-oIkUdDAEv(iV`-$KJf+x7F`v9^ z#chG~M_MkP2hY*Bph6>HH%`L_cotA_Ltq?3WL=%1I+ETRz6hzXvyYeqJ{>4=8c74n zExsP#%QBN3LK{ODbuXfrq|6rnUgb|{eqo9wFgyMpy6GJopm%u4M~GYx9~yj2&xyz! zJ{=RI&E97Rpey(yt1EPS^Yj6J)zPjsO56{#1X36J%^RfG?o3d#2RQx>!G{?%KA)v^ z!uG;;2k{_4)b<fxfQ45W;Bc;R-AXX;xQ+PoT%R`n`2Iu6$4Jh|O_v~&H(%~ZstEyp zL*fbUW70{Fo}(AR0$|>YJ2{^VZ69-gLp{hL#z<+jl$cIZq16^LiQUv}qp=Cl$PV}6 z#*LR;ezm-s(rt^is5whh-1Krh8^R{yh!zn>hgsTZG+`8^A)Jy{Qp1f|`1HXP!g^8b zDl`)^q9jpTuYUr?o8eJw)T>??kY1attO5C$#8j^Nf%AB;gj-ds^qUbD46}Q;F;X}g z_RE+**YW;l*iYX=LcNdE1_voR*6H`eNjDoH*NVNLimjNj@|G!7k)ZL0Xt~fn37gjG zgaRjw;!#*QM`nZnpH|U1eQNaKJ!LyQ-G8@-n3{~5>D1a6$it<p9h+Kyyi#vN(+81E zo4J;#HB1uY&Pt;T65l^0Mn|`6)wAy?TX&}h7Lw`9h-!n9>1uWZQg0~iVv}0%`g~1( zDr&;dh5=)~4_6Nc@f<@Ov{6y=#jAT!7Wbs9Q19#@#;vE|5`iCYyV65jqc%Vo7d_RK zF+j=Xu&w=A%%z+DdS%JdeybLT)1^r}66lH37&MU7d0sMLqLqBD9LT&0RMTs-Yr%GJ zFz}RE4bqNn)xkl@PaiZ=*jtA~kXA>YeB#3GV>y}4^XTgCIuB<i4v5ZbE}cvDOY6zU zh)~Hd`7pP+i|K6-Ym!iGV~*|w9*m6(LbyxE{j#{%+Hr(pMdd;m)3eF4?+<j!d5fOE zoj5=|i+-JMZ66J~9Siy4PfqZp?|WXmj<AV%K)O~1A#<VGYRyt?8;>aK8y%}rm&#TX zmc3g^d5gs;)W2#9o5fgYaR(8%fZ)p5Zk5Cp^HHeNko?S?qkAlu%_HTGprPQeZjwV9 z@S0I8XkClpp8rJhFL(if{>R~W!};C+|B!6!M(5~eYitAz@SkJd35B!Wf5*K4?v(#! ztdX(p?{Z*lZRn<JYH#CY>p-uoYi4ccsH^+CCOAb2LiIBsh`ewP#U=^efoYe8YSJ?# zItKO{kwuoxZfI_xu;1N$`*?0Iu=GJ1j!TCX!jP8tDlS3c;Lvc+_!+j!lVM8qd$KBB zs61lZ6f1R0?V1&mdCW?^$vpkjyx5%M9Q4CPd_RAX$=iwUuKE;qIg74X_UXd^PXlB~ z0>E>Bt*-+6e>1>J-^}`d8jzHvYqP+B(7W?MDeEmMhHQ2TDg&hmiY$ax0Q4Me<Th6H zWP7Q{jg7e9;UK%oCPU5gIX~#q^|7@xTFkl}P@AEm77~WDqkR)!*j((FxscQk)`YHu z1*<TS&wLK)BDu{H{=4i2?U)5plj6sdl+^}{if7RgctE>|9ykFx8g(fFoss>Pdr&dY zxz-)ff04CBnAI?>$?HdR3TnS>h+@^g*xkAk>wjO9Fu;b9VnAE&+Ch3&_I7&<r;UMG zR@4{FJDyfGdLq!e%&Clp+q1agVPo$pl+&e;7vw0M5UP{Y?yk?ED>UO3hdV`WpC4x< zsYwNG<iZz$yFlmyUQjS|F00m?Rr4P<C1o{1rCJk@QJywAjDR$tD-Vwtb-tb9^L+E{ zId-X#V~>=M_orS3<+WC50{v(ng4{Uebb^+=fFtMf3ahBhCdoEd-)LFpF*s|G_X~@} z5^)?yx2%Z^;gV&6a~z=(rgd=T$AUNlxA7=rCt!6|r02E>gQ7Z*p2yxenLCdFVi*CG z*Iu8_(W_#KjAv$9G$8q)FF9W=i?=>kOcWMl6zd~+X&yk%)_fm9=yXL=3<G!4P;vo5 zY4_FOvWMmOn=Gn3%PHFNCxkX@oq8eNCEO-u_~=C=z*)R9igl4R;*j;Ijx3#Tk5Yk` z_B7g8;Q^2I3SvK1a%Q4htnB&ugWV_zRv26OhSbJ!LGa_p3(AJ+^q958ZL$73x34dI z>}=)<-(TCee_sY+*{tzhCtqy<QZ&cM@(=K`!{n_X&kIwGmKVDhiO$3~LY>Kgd3Xye zdGE2EKn^nl9F<X2@2_=zyy+7Dl>AZ-rH%nJYLsRlm#(Re?8fe(@Z{VqC?ujowMalF zyjEt&4d@A?&8I7g58!`J^JVxSxP5Q{05g050EYh_r)E~Rmc~}b){gp)W;WLU!-cDB z+HA0)c)!%_c7|wV<Xgc52LoWLR1V4OQHRs6O6jde{#mg!Rv<1aR8RToc5p?eoR?j2 zTAhF)X%@+*?r|t^QN$TFX}}IqsGkbH6{bHF__q?~2dCv|UROJV#WmYrB*}P$C(*o0 z7@*NeTk-T9uCo$7LEm`e_TsM3_S9L!#$IU=Sn00OQ&gj06@M8MC*`cSm5Z}gv(PYP zFjtGa<|u^sZ{o)&BN}fN>L&}Cb<{u8E9T||8i;<ZHxh1}idCoP@c!IVsj5dS*~ks- zC$_Xy!*1fRTpY6}+2E4ZqiKE<YdN5~vf^ekoy!GH+iwtQD846N?82#SR>Nynr=TSk zCsR<9_$!m86K}FOnflADiwynnt84p8*}784M55*@kR*YSv@P#y6yiqFkczK1Km%12 zcx2^n@vztB`qixN*0Jt6$pF|jIr^$rk|X)hT>I}X=q%|3b=?)!o?rH?31&oEqEJ3` z_3}1w^04@C!$N2ice3XPTr>5Ox+181-ZvACbP&!Xmx&j@M2~v+pF$G3a&~`ixR;^% zRZvgZt3f%%YK%ABD%WVK-s`p1md;{e=;(1A(z;Rz5)AJlUU3M&99MYxZ()R%q@T{& zpD||obMnof{ECe_eNj2<ZVXwjRo@K4kGpCxZE^#{0i$TAbNjZ%8#(R+0QrU#X+bq% zgjoHUVmLAzggWBH#oiR<>}65HKWD)%o~xEB;Y}U!deeI5wkqaZl1SdNPAi0OijJ0C zAVw-vu7J@da{E`-N+y)?kec~75eUW&S;?A_Y-6QBEm{Zw={nsmM!rXYpmbl}%T&&p zT=C8^*0nHFFM|9LEi#i!tA^c`pO}CAlfj@UO=!98J5FeUy%PV~+%Bdda+b0?yzuUB z{=+|&cRic}_^3FZIE1lL!As!6mS*{bd?u-qhTOL4AOds2dzFsXskz_|pPoLrCoG9R zFi+s<Zv~lDPY9Gt!`hK88Ke@dTm?iZ%_OuZ=XiySX$^WdV&S;q2kS9MQAOsl(h{4m zW#k(@Tz=Jzx_`A=6Mm%<v0+{1_Byz32N!=vs9K*LA5g}XTS3%?!rrM5+qReZ=yLl6 ztG18ZwWT9NGYu2L3dM;RuYi#Fy)l9==e29rD6}acC&clp3fow*Mun)r<vm1$<=EWS zd0|zrJ&l+*Dp0eh1qe+Mz?DK9!7+#&W)}p*9cpwjc<w+9prjMKGe*GTxodfQY1oI| zu&-T)9XKj_)dy$}(E+tiZfnOklu}q;-gJWCh+e`9^3Psk_RHKC>Tf(Es_5%9kzw&q zqV~U@9oP5TCC2fKm{Ltq(n#rG$)$Y&4?@#a0eq*Eu=Ai|!;3-_^VQz9f3jt_zC4Qk z1>2(el)?@B_i86{7*1}`y8IT9dY6Z<^T{qLSU8eo2*i4igfJdl7~|s(i<H;NEmgzK zPrmeIOMlZW;p$BCNU{L?c3#&`f%(|@AVwWs%W^X~NA#Q|B3b;`YW=6fB#rE*Y6A{r z6xoQx#q@UzUn!!4UPpr#dgBabO*Xj@Ww%8X-J@hAgc5TU%h9t`_&Cg_kc?g`kGbKD zN6HIcjAJ;bOf-nIK=9+b446B?{X37^<1C5!tr}pBVlAbs?LIs~TIV1132mXynw<lN zB_03}^0>SaJ>zchqf{zvpjJ@CJ1@jAgoso3A2cZ$?)7@!V+4Nq-SPc&LgXvvae5Kk z4~|KF9~Of=JvDwZOL=un7qym&@kiG%&oJ1Eq8M6{?rB*9I!*EnwLdaxKO4w=@EW}T zE!$j(jH}{nx)f%NWoZHM;wYYkybMMo$g!McVRQ^n6$Xz~<%HZbWtETQ9l51$#Dd>C zDT>7~8KInyX&~dC9QPjy%X;u?bM8V{&~H=Zx^xZKm2<qwnCu;E^<o_2KyJ%3vRl;3 zMj>@DSAGGbc}zlLW|Mt%x_^gzemuGC{e8@c_=Jg*Av0U{RS&+Tun}|?I^H~^lLBJ0 za#5h!zv5icb4b2}HBpC==&Ep^xLz!$7&S#VYHH+-O4Cd>4t5oic6>3Ynvkv|00vX^ z?0~=9PfDBm#PuklO%&z!k_mc8g>R>TuU977C3;Whu=9qzEsNyM*6THJK$}iif)!!o zJd5<KEE(N-<mp~$brckQuDrUc^2Gz}2L!TcJj07=!AyZ~#9YdxW89rr+PZe@B5z?x zyugII2eWrfNp%19h1?n;SCw~_ChEpAGSK2Y_c-(fbKQl$t7!-OesFWPOOeIUO~mT+ zwVoUzU?G`dr)udDWLZXh1S5w9bSX|Ietb6Kj;$kmX%kwE*gm3S#RuoC*jm`Y5{G}e zCU!<mAR||Z)YfyUCCo*WMa(xOxT}exQ(5oRz3wn<64lX6c-19Qv-iO6+4jk<35c5# zj1nh$)EGx(nL-IW<+<Xcb;I{Sb@T`d)gAUr@x+${uX7hu1@6PR4L{1HV%^AMzU<qF zvX8<=IPljmzvEJT4=dt5_rz;);p4k|2*c^U@Uh5Q?>w^?UGnYb`MndcHoF#qWZ``3 z8l84WyUN;BGVAfbVcNIeEmq7CLYzBbhl1<lg&Gt^nC8D9?4FgP>la?%Pv@R=F*I>m zK5GEcN4)ttoV6_!Y=nSq>;10>eg?zeHgtdwn9DSMCThcr<;78b{>#@reH^F4(l0Hp z{-wnn|2<!@G_!Wp{Xh8rFK<Xv*8XM5C|)x)t@lbmRU&&;im?dN$n$|@7@g7yN2Kgk z>PXld>gY+|F4Yfng<G)%^b%hDp3`k3*7{DMLMTx-G*E!$OzZBkPE}oxk}GuK!>k>Q z^ISMS9mxhFr6>N$l9jdB7Q=KovwB&ESxqS_?8nqx+I;!A5uMeNacAS={3?M(id$j| ziH3klf1N(f5uLrYgv&B}oVK_7Y+my%0vkO@Ydv?oDv5K&Pz{mgt()=i43)1A4rXp^ zZ)B~1Nww8VEIuGTJ78TvjA~BbevPQQqqZUFdcV^}%by2a-71}iA&CK@q||tp&9}^U z*}lV9<05;A9gUN^c2`9{?~Fg%wFxUoH45h6eb(s&7f$GDxhJPg719XYyu^+oIHuzR z#&U%Ot1<O|+wN(>oi!X9NUNO7X#gc)HJjzl(-NTVN1Ic5eG6mha+jjKwlf--<HsUl zQ{3nd1q}VilZT`^nzhoG)xMCKdtjiZ4U9n668VfrVDOF^CRPp16!(2?)90uBk+-V_ zG4NeC%H4s>!Q{@=4QccIGmA6atTg!?uOJc3#Rf239cy|Ahx%gr{(Wr#W+iRMsm<#R z+zKB$H+5r!btrj5ryD^RMdRy-``Q9dZa09Cz>ZB$a*-hh_RaA~lhR4U+b~s}PEl2E zf_91_vYi?}PcB#jehdIqK6GW|RH0bkv?EBc`qd5072WE>Ieijk;!*M$|Kl0tH(J{j zS+xgbuR7!B-fs|I7v7%_bhhhz&So-{ZbfLSJdNh6)Q<sY`%Nt^S=YO_eu%{^=Gi}< znXQtO?S))(nD=}Qjj2jV*o@mwT859peEmXR`8na)wj0=;u4Y@N2fkaBxtcS8>!5Xu zWEhuR>ibAQVxZ`YM+T%`!Hs>uE!Kttt1YP<sANdR8_;fcH=_gQ`npvd!ZtufEQ+Hp zq+}rMKcWY@$Z-4tk$43EPinmcDDgw`D}+h>(reoP9xA_I*SaqD`nI;l_Wu(fI~E%Z z2;MWgcBf<4Si&tW0$>HOhVt$6P5}zx@>dS|OU*12*4XXeHysIAt7G1^Z-k143tKKb zF62qmiv-4N!OMAxxfT{$081=nrMWT(rVaX2RaN$QAwS(+4bz9GU)IJ_%cLHYB(%zE zh6`?(pV@gs#l2)Z-7dvVs;4qfzSbNl(%NI2iz-2TAoR-b)@&K5YW|B;0c~PtO)^l8 z-hfHZd&A%^8U3HMTB%^@zo~D3QjV}EZ;Y$UsoDrbG5Pej!JJh07(?A?Pg6eca<TD8 zBa6i@zu2w@mL=K8<`|W386Y9~4FuGN_W*q9z%O&ZjI#JI9FgO<biW5pYnCBSGwq-$ zoOz~Jq!U9H9^iLxJt}bDPt^kKY};V54PIoN?e8UVZa8V2f}~_uLi{_UGtR7!6Q3b+ zn)&p)+~*11WFW;nD;o+r!KNBqthegZQ|v-oU)mOui?qYxaV?PT2+J>+<779D%P9)a zzaqyX8kUaE_br|5-j%z1-e2G1S8YZo=|PJ?uq!VWwQOwrAs;hAfV&LRQlrYgcsDyU z5lD|6I*Pd7g56S?4HI~gbefda!m~t%JLPCZEkDTOJ|bU3;fSVpo7>fqwIGbLTc#1I zD8#3DFj}VEYqWtfdE<6Y5W%Mh$+dj6)V5Cj(i<xTD#gXoGF+@>(OSA<W4J7P5o|mc zO}SI!5f%G7xa0iq<<}+b;6y?4YIZA`%Ecu&`mlpyvk>OX#D+VF^`i(8dlnhW-ahAn zBb@lGbh&&Suv@fTC{cR0Nbtl8yGYT`@#&HeThuyOZYjmE3EuL{(NrT(FsfUcGelL$ za5wg*5{+FXD6@gp$x=-e*~*JE9*hBJUgyzyCK$76%M=W4n-7sfHE@!@3~D@H?s-OA za<OR`VDiZb3om!-cKl*0b4WT$&Du{a#lJRFKbD`{evsjRW=3o=pTqGt{qi-z6h|9n z1VTW0tV=l{3(B1aWFDjr=>N2fSzj5N;)-GvBm(WGRqBv|ccl2zbtT{(JX&F<9)tVV z!b|koLkdZH!xf@v7rwF;Hst&#+`z?WC`x}}h70)r1UE+;D@$EN8!KCV$6vS^M^F6^ z)hY1CE4)YtMFT%~SD`P!3os5L!wQmQtyUu3%j1Ju=%f**VMqEfZ(Q#;Uxh=6#3It$ z^wn%utaau(Da|!(h?c+1xPG^ZKLRg{=sV<JtbPF?4gnk<^=uaLa7jVDg1)AJJ!LKx z`VVWu$~kzMQEjnn!Qmp4K9E~-RuU?Qf3W-EPI}cP5G&`V(>TKLM1DlavNVKQB*MW3 zqb{SH-FWwZn*3|+{}SlPe$W4Rf$slsy7d1ysm^v_Bg^>rq1pYq^<Nd>Kks7Z;Am-P z@V^UmNeX?k{Q?MGH>%R}z*TWzkl@1mB9(E0zImrj>g#45>(^`xf_pbv$p)e9Cyfus zU1{tMc#$^4vF($q25{ihBQLo5JV$-~tmI50W4rV~sj}C_2WLiH0WOA5kOp7+>GJ=m z6Y{MXft`Cv`!B+zObMMieJq4roxwBA*!;(UAsZ(fq)mME$khcySv9WRheB&L=<=O# zf;^38*#d@4x;E(Wcbqe<HvRgQimh=N|5j5Bwkk^82Q7|=lK6pR$n>joU{V&%OSaJR zq$N`~#6(lFWx@ZvpiZKSv-BgeEu3YZ9G*)#V3<_FD?br=Q;`I9;U#;K64|qW-{ul( zFS^4mj|CfuKTS>JRUdxOa^l!C&LMY<GKAEJe4$l*qCqH$t?fP_nf9z;Rl@$r#UYsw zCrR;pY~g!@2PL5#Wm=djR4=q8?|8K0kkQ1d@*b0iEwPeGFvJRF8K)T6pu+<_{TI@u zEpbH8Obr0=k@q{j|1WRoe@*2puPf&bj`&j_eo&R{N{W`r!D(q9SFx@H=S;Jk!UM}o z$Cv7jf?|nrlMJFI5@WH1D~D&+=cnt0&36C+01#4AUWb`U-V6LN5}+U<<-Bfr0GFZf z?e5SC5^C${!TPi-pXi>;%j?VV1N*x2`iu=xkP%|L*Tda2zR!oWS2pVWB#s#GaWp~} zkF+5(g_5v;l5?BI`%mR<w9s}s)9E3ll0y;p>LdyYeKxm*RewMZP-T*|=`)bJxBi-O z$wh$aXO)z>y19GapMR2AZnF|mA=!C8pYKndsoKy_Z)YCF%vzhOBsnl<O(##)OO`0b zO`#iuPSsp6>9U<A_S{^K?o4QSl`B4#b!)S!SNBw%XJr*@;9fzzD)BGB$-dLcqDOHQ z>B^RuS4Ib}qDD})r-_N>Q|L3jn2BuVcBO`@8prjIG6!Pz7hsw2O@*twy&Xz^>hN6M z$Te_*kE5`cm_k85Wr^j|FPJomTVR}Xl`DOlNylV7Y74{ft<{hER+&Mka!Zub*YkAs z1}o5-+@`swIpoOJq^NHjfy$$=kJ2}-9k$Y#tW8~%mdBN`+M{;UpL%d#){mc&FG+df zACwr+d4HEpLcPn=-&e-PoHc;b%v7V60(7fTPV#qwBk<Et1RpaO$pPfb<ClNhBC@uK zu*jv!ck?gpBc#XJ*~3B{iKu7qP))~x<^>dDT_I$WWZkt2@^a0``n{;b4w~l^1(O>N zK^H(WBDekxG4qS@C#>q)6L0Yiqpc8<1?#;@Ei~oAX%FOFOms{5?gW|3ZA*zjf%tfy zlHv@jp{LV%7}3#?}gipZhO*r?IRJTAL@<0`)hsh8|-i{mdXx$x_fNT`%N5FHQ- zlPnI^)VQm(8(VafBHkH9SZoW_M>=da^JQH*mb*E2YYNT6|M5zrlMweFhNu1N?tVQk zN;KmA|L8i0ZBevf$!^=WZQHhO>ulS$ZQHhO+qP}ITh*%?<2BYFtVU$!jEKa~5j^wU zAPtilD2=(Qf=E0A5BgNtdSyRbLUUOyR@02DlC%NOf9@mqWivMBih5->;GdI^0GI4B zw}OEFmorv~s^!)rFwa(se9@w6;T(f4YvDw)Nm#{|dTQg~*Ff@Dj=7Ox7Nh~PX$*U! zs=E}tqx?nbN#{pL`>oa3l#^XX3>eMwq3?Gm3qYbI9iPB#!FCJ@RR<E+cYc)DU_%WU znKnWIWCOd$Coait@8fxbB3dYhz2QZJ#Vhwa9V(U>X-}XOod)k`1l!m@fPWt|_QmBK zahdOESeJ5yKZ8y-;AQx6%bUe^2e9+rh&a(B1y&UJ#E!-R2(GS5`|DsMc_~}v-tU_$ zFZV_8v!E=TykE7EXwbBFy0Ul5H$9a&smc}=PX<AJZq5#UiTV6_uWik>QPLxxv%{e) zIi^@LHtiv#3VyAk_m#sj417waSUr9@r_Pkiiuq<+C^dl_N?OsYuJ6-@Rbj#ko%3Lz zvqh=6&G`wI(=<Ca0FP8o;$cO$W(V6s5n;t6%o@tUr&=|=l0{is8Cng2c@#gpT&c_W zJ0crNEaNDuAd<x#VB`5uN9B?k&0C#$AiT4J^IQypc17M3_^{w0s`gj^F|vhXFs0Qm ziGeR@#H?|<viJhWhvib269IrA&oAujfH9INV+4mL5mFDQBMtA*`}c13t_!RFSAUl8 zTlW{Sw-JF3&+m*0jMIx|XsIhR#>YcPR%pg+x*JY;URc6dP8JHJdU_eT>WXYcl-Ban zRLAHwJIA-EFY=M`EHVr*uz%F@<}=+QC5k0a6h-5vgFgKU_*3%W;_9>lvLqJeok2UA zXaJsb0I(XZvVF}L|A>33<*u-BQg?#pdf&e%62fn%XaHkTF=a%P>#vtC@eb)7dVpBW zr?4*g=}Ta(3s~9|3Hxy_96+^#F{Kd2^n`ej_tCe@sDmM=m^x>hKG|e(7^0Zw4pYKU zNnxCPf00Hw%JyRpyRZT)9|4I3?b!t&Y6nam08}hh(m(~|MZL9w4hv?=D1fIH5%*&= zas0OdHX>65#hqO?V-G*7oB|HLNWcz}G29dbRN};Fn_LH~{iR7#p*4h@(h<F+;Nzyy z%Bj_PbrKIj9g~Nis|-lUkb8WW?M}yer+|Cu>IA!{M+7aJd;RmZtOlRz=5~9%>GX1R z!^;Cgp@~?y=@BhLnnTsb{XteXXoO`o!o#*`dRYOCl50T;dt)jo#C$^Vlt5tsQYaqJ zxn@KmX{pihu7j9*;)G`6gp+urb{#GWu4^&;+_KQ#xw&i-(0EX`sw<Yf*~BsmJm&8~ ztG%*!q)1GY%3N;zmlFT|#C{Tpn3qWe9-QP2Rmn$-rk#SN5{7fo?1fc#z=qyM{`lO> z4?J4ty5M&qZ;lU^6)YX8H!K9a0C-D&y+e@5ovm3W0)S)#NdPe1Z}c8lKws5(G7Onv z!swtjXBY8&LHkQcsrWUge@?){xoV9q5W%8Eh?8AhEtl+JMy?8n_7X*gnE4cF((&#! zNDO5u@bK_l`;4FO8_+yL4BlD258;7fQNZU1*SKJiLLw%x@?6T1Y9KV5vx8D78Cqtn z4pC|P96m0-9#}wwG$ciUG&de0_rGl*sIK0J*<%zU;}R4j{ypajH;W%!;Opn~aB}*( z`YuXh2pAK)3f5wFKyB?oC>XY;?y_C)m8!C79~{~Z<FRcc1In;TGU^Hvk^u;xMT~{y z)v9;$uZKP{^-5>mli3K&s6h~5#IWQgTzXmU^D+pu=fd&xzh^7?S~;{hX`fQy^v7yu z6bZ@B!-DmlvpyoMO4H&OrGm+Wc$1AT;1rW~{xh_nh`*8Smx$C%K9MG9<Oq6qhwQhC z2NQ8`rLm~sCaH5!c@7Xt@~yoQfZ{L<s@A>A0R>j`K<UmJwZ6WW9eqr|(75OF17=xZ zeE<wNWS{O9Qjzc!EXKTelU0b7-_?<s)6>t+?eTm?0$hN%ll%R-xyVX73sM6rLB4o5 zVYNtslm!nV{I6*98&c)8soMiIMlcsxSe0Pvc5y{xn^==V7?E=pXC7HAJ_nHaP6KPB z|G^wGU45R`7lEBSZn=6{i_CyHt*0`j>P+Ek3bQCVMYI=4H3vu@Y5CAZL(rBPg(#t$ zOFR=oL11MPFu1s$!kkyUbHMlnR5RHO$?fdA;N@{sK2!JM=WWkT*ITE)n`QhtNEo$f zTqs!rP8EC#ZQC32W<XG9FcZ4R28ON4Pi*l}T)y>>qPD6J@3yLMMhO7XBws70zlQ(5 z<~7!idp_!OVKimjFQ{1N%7|izv?YANlfM%DaDfdWkgZjCYFx|=j~2)|Y0R^-XrcUw zFGCq*^_xHQsOWOYy!1ZUz@~<JF^ri;+`vUOP&)cSggU4kuq`XNM-TMffwTN3EO~mp zQ9{6_vjgy7(rNY}^|dyAe~O@S)Camf+hW`lO!=&!WcUV{zMn6dEkg^)6^9zGgnE2u zCjAo`aJ7czvPq;#eHjiQw8cLhh8YG>`l<{$VAqeg*IT^8%Tm`j(S1XX95PK9*`X66 zz)$c-(=dbXm@<mdgxRp`jT<9o;!x9aNBE}-86JXL5U%jEeUIq@;LK!~cO%bN@&|n& zJ|FnJ(Pa8;gVi;%Yy2iJ0v!<|kX_g}{G=k}h_yMo-qqY%=L3ZTA{ui0p<)_$-Ih&@ z9>R7Am>#rEPf!EnWO)%FmqbgQ{uE0?L$Y=Ia9<G${zD$(efpUB41Ee;k>YE$Yu>ec z)gd5RmBZFmAGH)v%tF9JRt!WlMt5KWYKcFpAcPVFb&zskG{BDxv6KMvSc`p2$>mxO zG2ux%E0T!-WaS`{<E{yca0Sq>kO~!|OiUww>BMMZJkwmv%Q%QzaWaC~fsFM=pd!oy zM)bXaBYnPQFp3or<*0O%B8LEiXc%Dl8`Xih(r1cvR2l+eyI4X136Z2{T{U*rX1oM~ z3&73aXYRt9P7~JsP~K{ca}*J7Ts5g|B1(^G6kdHPw-`~qR5y$Ydh|!c_^L>S$?9jJ zV8I0#&gott$PU4DgzZ8YWj2Ri86`;+YwZ%w>cKCS$W%a<Q5$NA99TF79SpwOBP#9Y z!?c;_-~!q(&L@-Q2SS_5E0>!GB$A7Qh&bZz9XkNBPfai7QidSvYh7I9Z_HxAE$3|M z?*(c&&$w9tdF8X<QWB9IIx)0JjSPSVhoDQ4nfHA{>RdcH@7{l5w}lb63e5-qpr+X6 zVoVV_MZiD+0G>-MO+w7cpq?10tzeMyqPO(GR-+LUFOR>Vwc;ro>4k!&I!wM01n)7P z-zDMi{Xc^S#)VnDoU7#wORT7{nOzhgm!5Im2QdPWqz%rS`>EnPvycKKcuYL#h@!0q zutz7B+nPbsR3DiBtzSOc2Z3ggYJ6SvgMv<BU=?i*L&R_{TiC~is9@TNrBYo1{nOhP zb1ZN%g0)&bA)7`(kDRmsqC}oe7=T%cBX&fM-%Q-PUqJUV;T($qxSRCce3{>VJwy)N z!z}|np5Z`c%BvvhL#uLxBoXFMvJ4RPrhUAoTS*LkmJAS_Z%zt|!b<h`<jgX?GXVoi zN2X^CfIF_0en1A}G69!TA~axsWe#g*W8CcLlx1|R6;!wZz+6jmqeW$)_U2fiLhu{t zb`_|aI4HBCTO*5IE8+RY5|cz4UoE1ulbS{>6N!vQ7rlvVp#ZZ;C~5~5h*f=~vyP~! zsXl2<*4M7$uONhG25V7yovnSJvwjGyuQgS~nnZrLSb!y;g&&&Zm?NQd#_;VNYBBel zK;zMW<whm2hw25gnyOO)b;Vv$Tv7>m;2zdlqBL`_x^U*We3M08jsSIzQVOJVCBMT0 z7yO9A#Bc5cb0sEt;^N1fns0qLXvq0>I1Wq(Ax!r@1J85z>|q9my8o_No(+@DPOFF0 z$+2Cr>h~mNCFcn2x6hBB03vG^6s*KD9y|v-J&K<#&dRg+%V$<8Hy8;J0uE}}1%Ugo zhPG)ht&4*TB}ue*8RWa^kw7$`;%Ipz=BE)GrSu#CapiamRg5-SE@iV+(+MuQ(zn4V zNJ7Nx)$7rNmAq<IE!0ccPmT4OigYFl<rjThnBTkGgMhCpV@z!sae;zEEV{9ErZf<P zR?mWqTM5D~%1osWhiM(zKb#-E6Y*2|9)KAOWynd#OY@k`vO&1f8S8{o4Op^P0XE1T zN{<&=FP{lMGv3)tZOnvb-q5MAdqlJgEEoTll|9uQDw)YLD{g!3dq0Hm0j?Yd9%QBD zAOjp><N68YMlNc&4wV5pOPQu&C0`c!3{2RK-yewu+y}lx^KVzPOm<oo6Q&x0Ifc7I zJ=#f}>JxqlL{cP}NNlhG8<36shnedj^VT}PdjOHuz?G;8MASRYx@P3hZ`88$_i^`E zU=MUC3PVo9L%p_bhB-e(_h^ZS%3cl{bkuZ4enyT?%zi~!@SeH&E0-DEjaQb~!m(&V zYhwtYS=>dOmb8M7cJ$3v8#59frwBIRf?e1cj(uz8Rp!d9FhW|Xw@Zi9%2Xr5AaI=R z-KuL}jpc8s7w~tdzfhTwv;QKrkP>=DSRrK=M0Moqn5A5=5nKB@K;dijoN%l0EWx*a zio$-lkqFTZyNQq<D<kw6_=6y1D;Y=|-T^8t6tTBQO}LZn;*o~7q2Jt=Q4AZ+sao!* zz@}E|LEtYJ=;!h}^L#TBHOKkUtRqne&1ZhmER{3!1PJfx!P4<runSH1=I0Jk$A<|E z^DA!{AkUZ{;h3qcptb>9_%G|@zl^L>?D10;@vNFsosb68b;j{w5Ten51(yzM_brYp z0x%v9YiX9~clc3JZX(5Y9xjgV#fKIuLkt;4!3O^NN1<g0BCz5_uyLLDk><;95KoT~ z*;gmR!Q((c%8wsl9G0r7pE>@&9rPRamw1#zcrpufZ&>p^u;x1fAQ&$N7BDxPb|Q@_ zHSud81~<vvjt=)9OebyIw1nLQap0C@^UQYCGneGnvS=wEZMr8matb%*4(Jc?gh$XT zN6isR@|_Tl5u%s8G|iBINHl2f1JceYjDK?+Z6O1g|5a3nPdzjG4&6xs0Ge)AMPxQI zEgqLGCW!M(0E0T8E_7M<W-Di25=NCo8C-LnLmtYov=daeba5wsp?RpW>o7GVLEw6$ zRreG$2^AkQWVV+)##01XuCFu7{_~p7c;s#efMSjU(8rVN3N?4vs-C}L<yi&gXx#Q6 zovaX|SseLI8j{Z=8Zn>GL)wH0CC~R%wdyV{;R^khR9jkL$xq<W4<LSxl@<-JZ{HGq zA%fY?3<Zhi3>n-f=HSCrw_KrY%v5Os#WLs_B$gr3$>4SI7t;=<jlGV#UfY00U8$I1 zPnu0XuzZ+=`BWGU(WG;8ieFvqu`DY${8Wo?1-g{SgjSlMaWY_IIM0$3-s17pJ>fG@ zOT-RmB$et9Lhn4x^l!DzUJ;hHFWh0Lt`t=%Gi9yGazx~aEN2$|PMQ*xU5IEqS)K*l zv<!l7LSDmUs11W-YO0nfr4a}y>h^rtK%Fb;tN_K3dfu03(rnLI+%(a&h6K5!jKgTT zMGV%}Wvq;YYbX?s(4aBw;Nj-y<0TEl{>NrY2p)3{80s<_2@DlrI{<FPney-)+)GJb z?nF0B@TkqFHn#LOj_^D4FPKB&3O+v9oHa%AYkXm6ZO*KQV$!Dnb~KiE{r-nO8QJGg zeuaK09{L2xon<w?(?d=TJd~*)ZuT{7(;~fjS3VpFSc;DU;#~6Sa&c2nvRMr#p<P!N zH%t;}Xfkr<P;wWCDW$tjVBm%6nY88Npm>|EL(Z<~3kHUY!7+EFCB<m~1l!_ilrlXf zm;tjRA|lAtK|LA%rt(1OG4^4B`QBE$I-pGSSfcySG=I2wjns}9f*<t34x4b&tOn{O zbr!LYW8hg{%k)(><cBJD9I)+3N_wK8bia_Q?#Y>Mt_Hs6=kucxtZ)DutS5o@eXXza zp)QOQ*Bg~84?xdk$EUUdP5381C_2J;R^}~ND;=&gvU>Zv#?X+yvP9XOBCOz#J`Zoh z%dNJe<sq#yyz>)Y!Tya=E$(x=(-Idl5*y(`NLQS}UzWGVrnS9z;x!3w;nWQK-qrAp zV6L7I@2g>W6k{STa>3qQy}SQ2tFe7di@pCMZ{+T+59JSM2K|p{kqj=U9S`2Pf*`v| zu~%UiV#|)Htm6(YxJbAggwUM}tuMu~ji#M*zMQufe4*~sr%ZPcx7!&T+>Kbdx@ovg zQOz5fmX8d70q_;}?jEQ;MDIV=w<uOa&QeERq2vLH!XYlyOYqF0>{UJ+=Xrkp5iv(D zy=a26iVHK;@SN;EOjtW0ze(hPD37B<nvxmj{CvJ`MvPHIDO5442NaHYde*&GF5kVq zaQZ<77TMrZ-S0_lho;i9g$61kqeS;!j=FOC2za*FhV?}}Wtd31eJ0(JtJx0u*_P87 zoojocu)(28b7|TR5JsW72u}Dt?2Su_Ha*j&T2wGMqYc>tulk=h_<{h;p&rlWVqA8t zU-{+SQ7Nvth9cm4gUkY={IMc#G{_3EUYSF1ob<EkB-r+<1>kC*Ua1Rswh0|b;uDkR zsr$wh$cDJrB^s!Ox-Ki0tAiaOscAPLOp69|P*cIb0Q;mCg+d@1=)4>qVC$N-gFkGp z`Y_{rCSE+^CnX{iW@YW<+07QK$Pz7B>lBM>VP#<6r5jM)EQPk0VGUG;C;(?z^^0jt zh5zg;peBscFb(_A(X6|#L=4G8kx+Du`LLs3G(HTA1nCz=QDKgog{t<HPr#+Uht`PZ z%|h0Atl2w2e$EZuvrXwtVhss13n3-`wD8#wd|uLLnFisW@ba$?u0j5-F6}%$SV>c0 zsW_j@jlU`T@A0W;2?DTZ)E53FbBI+`83b0wS4)9+nvwIc?&XbSHK-X@a^Iwuxs94d zS`Cf?A5gk7GH}76WQ-8Bt(&eBl(%A3&M^y=%@gM)gaoxA2c98Nx!p*Tyj`Q3a(i+( z_P$ex<nMQH`D;lHsW60b31%c68gvxOT?esInJ+m|R0yb(;>)rEOM;>Ly?-Z>t`tvU z7NA3c&BcncpswN3(kL0W^Ui`jms>3#%AJSx{wQ_8AZP%1RJy<*G0h~PYmSo7U0Qu> z?dRIE5R;~Ls{sQM01c~8!j?3qYlLa=()uWpd$3OHQ;JEi#3F7e?hMHzqO_*mJqdix z)IB=(f`gBl%cwa#(F}h)L*40sL!}&OVOCOcD>B=gzThR&L^4-g`*tXPmA`cAggQFf zu*u3ZGQzzKneVeKZ!B5j#B?v3hbSd#T``y1E;7JZiPGtnbV(Ks<7}v;hQQG9P8#T$ z)tcpN6J0d<lxhKq*l7CF_T2FUs^e>i5dc0rfBT-(#NbRH{8t3<d;EYN#E_YCEf0UB z?tb@E<^1gZPj5N(Oy}l@lEBjgt0@s7gkZ=56<py?JZaIErd0GhPs@6+aVe(Dld7wa z(mvr+ikWl8JC2G1!gig2qp<b*<%|vq_U))?Q7Lh`L+Tg8Q5boeCj3G~n*C1K(Y=I7 z5O>o@yp@_Wh*N=dJNJc@FM-F()X)GVB+>m(cBbe97#2n6bZW;T2jE_O{;Mp#Gme-7 z3jGqMgL8h7ky(!8=2i7Do(<J@Ir)RR&5VjhAzbs(Yz&2mT_{PGtg<U}aOSypevD<N zP$+1Rg7tlkr;XwD`F*=Q$kfn_IS>=EM8|63uZy{9hC8f|kJgoS>xxXNDYwB}9S8#h z8wPAWqX?ZYWA$1#Ec&ssZ<%d`Jg3nZrdv!yH%h6`<GUgP-?)u{Y4(x3Bd=sRX>S!B zW7r97c|kBHDFXr;_=hnejGmHKKMOuc`y!k?dTpF$w7emO>?eJ!kYROF$ND{?S_k#g zILfKrpGruU@LJcJB#alSf)+wZt0hY^t|_F4R*U%ZRsH=hcPi&b_wGF(sRxXFiXs?A z=HB#2=vq(h_y_^G7Y{9xipbZt8HnTy_m>P${pL6$Z5JrmGNuWjxk!gSVf5sO|E+D| z>49$hhoL!a7ofZKc4bZ`MwVT`7P_k6%hkLX%rNNGUPt8F5dLz*mdu1QVf->*g&kQ) zawG<LytflOyHFR|sJTshoSiR_H;RW~<>j8M1MOL5bBntE49V^H`uytXK!$e85J*70 zS!3gm9Dgyk+pH5-<NV$>%JO_4o*N)4>6cZb^hF<V^&hyI5h28wcm;QTTZ$3X&mC9y zVh{564*2t8D>_lrK8;#5LXfWi#iF(Yvj8+nkXKxquGS09M7CF>rDF~_@f);RHt`m! zF^$7{Ee_7mt_?G6I8!Ae4Ss6d(X9ie(40N3ICJ~+uFDktGbz1<y)5`;lg1zQW{mju zj#Qm^cOcff>2aI}NTNKyOOGfxvr(A!mO68HPz)g#c3@n%0J*j^!Ezm8-d63FWh_Xj zhZZ&D+5o#p;M{k(n{)IW=Ml6CT{T8ch^b)+RVJOE1l`cX8m8kmCxF=dfw1dk<MvM3 zxz2-3nYCr=CJlMK6!2*3qI->e_ej~Lc`CPhLU;FcEo0{FY@%EXaA;;g#zXUXtZuL3 zJV;5Ny2`F>yJtd@^5gWsfFXk2v!rq-P6!XWloQ8v$0!5<r<zgz3Z?Tvk9@+xF;oDN z*vouU1mMr_vi`>-81P?;cmd7{JE{G>D<6|!u753Ab9r~fkzS>m@{48v0yfvTZi&0q z<>O0MlHJ(h%ETMG@k3eCUpv$qdBbKC)kLUCDvBzlq0Fa3_|tW1_<gFQ$oxU=5tAF| zOxT>ii@R5OEwZ&87tn~t>_ORZM}<7;x*-o2l?5$XC0J!`Q>hTln)$$S4*qeJa~%s| zb57z>0pAxEt2gzuVAQ+Dw!w`9)Ak`7Jnt<UE1A-5Rbw-=toCvS=Z*t*`d?QuP8!;p zP|j~X8N4ig9K(0}aGdeLU!iE<B{nGfnMn6v$rYe%*<*G#t;<%Jq%jiSih&Sf*TQbf z9us38*oH>6*x_5!L|UN?T~pP>WGX1n-!!FGtR7|jzK9U5Ut!32i!r-95~q@BQv?_0 z4AyW!)W4J!>m|h%u|+kq%@<QR4kDRf#yce=i6zbkZ}J@742{Qddgd;9|9kp4D^j%m zJWqUmJe?L=E&F~e)LgyO<WTdE^WY@1t<HfvG{u_ZZtp^lI&4qQy+B3lv37KgzD`HX zzic+B_$POJgmzI)vt#}<Z0!j!=%5g|%rzw}rZ*sQD*hUZ=fY`lMs=Hf3vAM;0A#L9 z`IWwiO~3-_#l+<eB!&uu;cqqUV7?KWsdRp{3+~g15f0RdmuJ)1tKS{5cWUUM+a{mG z6ikvl=~D%?Rv9t5Lerk_!wEUk4Di-zKx4<=UBMMrqpDjd&4U`z<=e-4>K{h{PjCB^ z*?OTZy|Rf;O)Q&ZnHBmzt5*5n)g~Xu78@CurDVaH^}obu;9WNgC+2gHr38M*(Si%K zh@cD<-yC}L2vJDGyR{l)+XoY2SC8eKEws#g;+Y({i<vR)%+pykgbKEx)t`-e#)?zO z)Or_Qc7kR(X+rft?)lathb(gGVppIkG>p!5V>7)W`s>7tEQSDjdU^e9O22=jY#_J- z0$@wK^wnj~PVZ*&(N*)^YSWS_8=*;wZgQA|V7Ai9UBn4;`o=IRwMm|KHd(7ypQJMj zxu;Hks$~(|f^+?#`XIvN9-HI6J)Jv&Ll~}P{iVTT9L`t7PHXJ-xlQEEhJ<a2X}ijg zH9DDy2fut}&ldUvF!D)>c9k;d$ML9(TS^VY<7P4elfS(^<rYI_;tdPTT7p#jg#yTE z0vYaYAQtpHO^zjpmzZJ|tWE?iO&i?Hrd^cOb)70uONu+i{$HV|VXNb~A@yj`wlxm_ zt;b5W#2tRJ6%K>>(4P&(V%$3eKppwphJc2At1-0`d+e@#Yv-H4cpX~1Wys(q*&f2N zp9@VJqO*KyA}y#$GT{Jw4zlK+$3w~2&qFRyyGy|R(`4=f@Z76gwMHN_O3-5qIC2Jn zJNrYKv@gdW(r*K~9%XNjA$q>KmvzY{uHc%rC-ci&W-f<9)`XI7Rsnd4sojX;2roX8 z9Zy>FAt~x71{PO;%0fDyQ0B;EK1Re<ev8?a;<Qjmo6)UsvlRGAUL4v*FNGUjNa`e% zA>)N<dg9lhq5ddAp%d8Vka?uqfvi|i;_m%RhRtdIGm+=8mr+9<Jct`kvdY-hih;;4 z&%Y|DtS}V>=v?(u_YMRadHX1pL@nbfy~9@JT%CCTH@|qZcnXBrzeZ5k#$-W^X*Rnv zX1u2tcp~Y=8vd7&sVohb{zWfJcH<BG6YKm>lWcpsNi-Wfjeml%L9Wu3y4k0wlJ(^w z$ctX_O6R@%1f`30MpvQy`kClS447B@h3X~*b><sTS*Sv_x#Vro1bP~<07h4xK==Hp zyt@X{6;IWT1lfDbrry^{W5vC~*N0Kq-i=ePR>E>GPBnfWk4KD-E3EQjalum&%^4}U zEkU=0TAJf!lxE*J_fizGbj3pN!2EV)YH1u>HS@sjKQ7ADwBuB*EaK7NNB(Bf-rT~4 zgMt}JOyWG|Kql|&s;07PN$j`R6cE30<$T!@-FJ+szsmvW6OE`D$*}1q9YOe3#cIKY zm~CmJ7W4XCy+vsmrY)yib&?HDxv9R-?zS^-wwH#-gUnK|^eRqRCPx@sE)^R-N@k}B zW2v_ZW25>8pm>|1xpH}km|<18YOf?Pe3^GW+fozTC2NQes79P^lg?wYCD5S3OEI)J zt`fHAB4D|et$23{w_U}HY%e9#1hb4+lgncd(`GEHdcP8(lM-8HjwtHRk>;aCQylJ9 zL`Sc+_c7&0mm3W$nt2CW;<O+!wwGq}sq1`sdh_uu9Tzr@mHn)e%cxZQB{F3wy#b>5 zj0>x+r$?%??I;yADV>LeO2LBoQoEN@ZAIcW*gaQjTwmE@a%{>Q%7lZ*Yga8KS6@hb zHCKabeFx!W2Al4vFb!9Ge%^NSo$<#5a+b;VEXuzPVOo0(G6{ARPTtvQr&8QoqoDl4 zLrt!4uc+DXEO9iiWt9~8<^|8B$u-S}X@K_d$gwxPvU)UH22#X8{q>r&hdy``(yE)k z-C=HbBD-}*`;Pc(2mlJg7Fe1g>nFIj&zh#?m%Gu)V~W`0;(erwB}1q?%lMr2dT6)q zdqCP_ck^<7%Tywv)-=2mSB=Vz_gO81rShpCnb~<y-V6oJXH_!yz_7U2c@}_K`?5o8 z-?B;mrTw~jQVMm~Wu!H|El6|@6c>0eTLm<ZeG07at;`*T&#V>p3X9EPaHgldnN`GR z{rS`dg-Pu92Dn_fr<>wJi7X^Rc_THAon1qE5?$j`d%TeA<SmDuvPp!CQQE|EB`mos zhmYUmKZNq|`8eiF?{LcEkA8{z`)%SEeW|KvZ`{oX(=81%NXriAzeWYLdhb7V8&teS zT7upn@lojV(7JP4aBtPf&H=%`pj!!^%6VzVeW}bvRzU7<tNQUX4Sc4tHLwZ-xU?7^ z%kn*y7%sAbwY7<b=ajUT&SIrh(CTy5G-v$|o<TWvc|$=w{Ldv1akvf=4DTiXPME~6 z14U=h!qY0Ni}Qo@#V(!sWg2vMV)i`2DXNH;&P$}b+rWeN{E*#Y^oq2V6~{j9p2;~3 zr5=VFsg-lA{D<5OofLveag{xih|=4Gg04y8k61IbvDf;7cG@Qqn__VXV6)~N-z_}5 zuj&sAw<zQDZW^)2dG#)K44B*uH^_7{y<FRu?jO@9Mz(+8!+Qgo%VOhLnaVJvuD4m4 z=@^fTbTbjJdW$4k3$n%TZa_$xY}yV>5K*r>%oSb^tZ0Xgd~XMkF{Biob_w`#=U9AM zfW|?D^bLAu#(0Q-n|$aLi#_ywLyE$D7(|vGwS-~z_jU4poo9pm*Bm(`etvC#sQ!L9 zfEr2WPU(AnFBgX2Vaz_*YCp8@^%=j~3%zN#c$r<7*od(6tu=~!%}pQbirXq;-+hr^ z_})8*c|ER8$2%r}2DnQ#t$wg(c2lK&PI%T0B9OlG97XL>$+>?nYLo$=TeM4m@*HO@ z+nz5BUOVv)Kho&*8Wk;gT%u%;*3DA>3%p&=g#h-u=2JT4BiGnkFKI_j>U)P>p8EV< z?HnFYlO5Dv?`Qk^`hR$KyK}of5r$_g<V8heaS!5rFE6I;?4L0nrH@@r>~mfL2R>W; zJl;%!e=)R2XXZace+Zvn*}a~=k3N2N(nV|V>a+G`emuI&29FV}_(+Qtjik=Ne_q_o zV6VpRB7YTT!rCBV57%JY{eHP<ed!;?cS?+NwzdQFVxmPv_2n(v-;s2lPeFXw>s$=a z&^G2qWPDMan5Z&k-@{KrxK||5Q>kw?jM?Yu?TC5T3u^;PU{)_082yP9PhToT`)Y&f zgK>U#?JaP{52(q_238&|v2v*6$uPP{)q{H2(w!!ZI^5aa+U=?IKG>mh8Nem4WZ$b^ z&DQd*9HE7_vd6`xCK$UX$<FM4)9B+z1iq-E8vC;GguO_3TV7c;J@hh$CAX=GUsKOS zo_1xl(oQRTY)wBDI?E+i>!Y6Sx8DV}%xE($`|O_OZe*auJVD!*kt3C6NZ0AMvr325 zc!q~nr#GA$xkYq^Z|y6A;I;Na;C(ErC}r<zsGV|w3Kz4niFUIE0K#+Qrbk_W045(D z+cImN(mf%tg8oQj(-z93rg)u!cDD!>sF&R4Mik9)qM!oS0wuQHT{KQD;W9zQO_}Qz zM>f-6^l>*NoZxoQlNeYTBA$KC_Z&>WTv!q8ziymkL%Rdx#A*>Ytb)?Lk1)me6RK^T zcc%n0e@aU%j5JpCk}`|WbvplzTfyr5m|W?au3p5pSr0{Mkh$`0_BwmLI}Px(<^qR7 z4HKV;4{?;hHf!*$xpbFyHLh%b_cn2w4F{=)Pw)uam9)BWpceZbOGXjm)h99VfT$FQ zu~NHETSZ@E$&{;0pnt`QtD<3B2?oFiYg1FMSBdf3Q|-6C66&VbwMWZ-L*nUqX<6g} zJQb2xg<qAuo82~faJKKsZii^_)&-?CcZ(~^U1ZHS);+g)Lyh%r#vnvw;Iy8KusO#^ zxX8rTF^*h@0_t4BhdP~Z&G3PCPsQw|9CD?eQEQXySN3m;^IyzwO3rD;I(HwEu!h(6 z(y8me^mp>uHQg}mvpb&4kx*^8bDPE%&j0GPZCV#gdaua*3m(B``zPRE<^3BY{JtRV zw{K?!*qo3_f7Tt1PS%RHMgoRVJjv6sH}>;dBjh0HY82qbelBS(V+@2w<aX6?rjnp= zAI}Q(|5|!8MV4ZME$>1E-y=(5f?e`h5d%BTz~&F<cr7~0D;NaB>c5NklqezRWr6YL zw51L^m}DS<<Bc@gPrP;SVLU}k+?0xBgm!}h*xzrDUXnP~K<9`&-8{#DV=>iX${5FS zz-(K3bf`W<QX9lqjYdRHX2;lImM>eF6*!(7T-0%vqLvo|&WQY7-}d-IC+=)hzE}rH zQ&%O~-{f-a2glV=71vXs-HNJmX^e5+^`WEI)DLMqX9pL(7@bMETCV9u$NIFO(BOI4 z2;D_T(y-$$DJctI%-(F0PBfdW>eSQfRuw@ZthE~!{bl}%3w2ML2esOMHs_MQ|DpfC zRCP|zihn&c0DuS;006=NLfbl;I@!568k;)($Bx%%dBttDBmV8>1;#kkd%sk6rCqf! zblT8cDuGBO3A0hoV}%N7H_uelKo)j)%^vi6nTNtBc)Itvu?AMt&cc|7KD{R_r|Wuq z4Xlb%J>6vEUe1i<Jxo~eU^S(QdQ=~~syCgVB>T8|ynF-wej$IgO@Acp+|xHk2VB?a zMCn8%uYgv$1^zf!yNS-)M#}okQ4i>$hw+@W1e@QBszeWYd+1KA6wvLjwIUhOKVq5g zKn(2mdVE~&%v81emSfH-zdrtS#o_OEzdt|xgkgI_I2XyPUTY>=!@U$BNe&+O(o|v{ zOEZX)WK@feFtcK11f-}K>2B#a*6%eMb%9RZ%n5DSo!q=COill)h_fr<OCR`pr<DA% zEmDoBCHYCHnmfK+8q!L!?7$SsNH?Bd<wztoQb{SUjA3p?autM4L{c*v(?5J!1ak$h z)z&mJ&i9m<D<&R$|M#!Oix3@vR=SALVfc(&YeDJ^b#L8>%sUV&BogS3q#EFbP{XWf zEn>re^Jc}z(;{Kr)lM7b5+x$6nT^Y5StnUZ-V?Arr;7_QvjBPD$T21j7w$lm>z6;! zih}k~nLy!O5F!ORywUlmGmz*^MiKP(^hX0IGLNy;$ml$JqWVyn;)KLSmoR4$WvPsp zKx1S}mB}S=`$rTFnD6~<mew;O3;W|%L6t}=Pcs<k>Kn~rR_?#=uQ!<Aeqsti=BWj@ zt$`6^=UVq8wk1Ed#vi7o^&Wb#Gn(QAc*{gH{aUg7%%}tSmI;yDG89Q?!uK>93MgwN z$ezS#<Jyqoc{uM?!2_l?%HjS=ZI1P*#kv>-u5;%~CT;W&Zp5ln2D$5*8qJ-e$Nash zuqYoHYK){hfp34x1BUdokD*{Gxw;Of@V{~>of>HM-ns-k5S%V&$UU#nP&XvNa|sX_ z(VBNBXuKSfS(x@x5}7yUDQb!M$pH=#RtYjpx)Bp83p)!=<`)_3s-0KmQdUHfq^K7A z$;#S;9~z{8FN`zG6Y(laz_J~S-bj=PU4$B9T}I&NZ#`)Pf=nvrB$DIhS5t92sprmi z{KK{Q6<Cwy<MbFN^`>6g`b1DvUrpappLd~<P#eR7)}apkE)UV_EgilX??_7@C>f7Q zVP@;lmo1H^ZoQa|%#)R2{;w1wvp30GOQ#I5yB|ucx>vvp#}<Gq*u1(W`k0*;OmA6# zP!O5xy^PuExXPe-)B7{Qa#Kpcf(ig!<A)<T<Gb+qO1l?TPgE`J1@31E6NMPgQ{W`f zB$OsGve@uHV!Gu_)0G7@KXn;PUEEb6pSKX~+$k(mwf^)T0F2|Im3sVN<0CVRDrD2G z2V7^lLBUZ!5(Kpfx6bKt@tVJs2r@H$KL&tg1rY2jf~_MdU>8mn(F7w^LEN1ssT|5U z!SXm}CcW)tkppB$5c7Y@{B(?v8Noc-I}?Zgr}tQXSM!2~9qsHuS&P5{(^eh$6k%l7 z-53Q(=h{W8UtZ7<oLdfr$kPL@H(S*j4>IP7`h2=?mum7+u|nHRl1RF{Hs+o(f>O<` zj=-*7&2bW{k5wGX7Q&mLu59O@i`A&f!2q)m<ANzR`#D$9Xg=Z|IVOghjSZ%jGChRB zAqjyPJ!rCD>auvTGTs29I%eHqsXj2O`IV@tTNaNYdk+!%>$awvfdz*Jp*66{mY{l( zWDXuW^7#Zqdx5`+tH`*bhEbg2fnjXgFRuE<Vb#i(FUs*neZfJR11^|ge~(!E^<;q^ zi6U{Nkkc|0e8<*=)?2$?#|lD)$&i6pVjj7^oaLR8D{vBQh6ZK+Q1OTjw)HUt)xy0P zU8#~)57B^DLu7$5f9{-RIk0Gqmnf*#9QI+MJL*9P+qZNEZ3mF!B*{Eg3PKYkQhn(X zrUgbQ<%~;~{5k(Pt<-TEQAz_=8;|NwIseU+;OYiOsVDKv$Puv19xo+h6yl|lfEuX^ zNd<;YT1sF94aTP~oK$OS^bNio4BACBDT7DwYp6%PW{wPgbN}mrwfaZv3Jni`3lhx$ zvu&0Q!rsWE8Y(oJ?`WfeY);tG_q$HY$|`A>f#Htg!rf4-;UR&LtUFFeVJYfzjf??t zgZ9V=JSWhB8xaS+NYn73S$|+rAnC{2v+8R_O51LOh6E+mlG$ujWSQPG2$a2zxB{Fu zbB$fiXStj?&AONrndJ@!RtQgK3kk!mQ>PN>rm40a#j@!v6lE68cI~#N;Zb0tP)UN! zg%q@ovkn!`v%(ugRJ#>Y%3Gl)9u;F-)IJ4D%{;EY-5vsFgv0(xH`J8)B9IJc5Ws-? ziG!(Na45u{c-7;tlq)vyyQmHl_v3$%ptW!R=n^C&rxJJyBVc?+*I6Ca3}6T?-2o$J zSQQUhcp)oS1%)(hswkGW1h=6GWx<r%&U{UY`4zR$XV{GGIVQB#wCf(2Phhl>tvf>3 z{8fVOGq1?9z<}y&pr7!bHB8v~yU0cWsa1Y!8ztpt{q48$Qu$MD7_dlyQC)9{w{1(X zh56fxuVoMjVJXr*w205HE)EjmaR1TQXia#2e}*ZsNlQoP@vWK~T%+1v#tYrBnD9@$ zXttrs(Am2iDC<_o%IX{|tBlZ|)m#Hpp!T2Zi0|CKwh0AxfN{ScKdz=0M2fsszXd@y z^I&6r188uR`%KPJQK*Jt+z23B&^W+L!p9FTn(r8@ps~F0@ED0BlT8p8ETMhtJlW4Z z3Es12PNV~ne%5BW;PT*l4~%XwpI^Vn0t;+H^ss)=p76X25P)aD;euA>$Ko3a;^a2F z3xF}GxMvSWl*z&7LNmI$^mWcWB>;t+^28DXkhpRP5Mtd@jbinmNR0589N5*RJCG-9 zH+08yTduBPU~Tj|Hi@sQs;Io8gV6;`1+3PK#dyhG{Gm!U+?It9^%xt@cFybuJL-3q zMV6UhaHjDAvh7`xm%d7ye3BQCa&D6_L)mE=wb++RZs0H)Y*9RV7TaMD^AGueGX6D2 zOguC^h5X?tGh;K&A$oa#)*BZMb_~v35`t}=TPQQTzhiO>^sxqAE`1(MP#(vNGLo+O zn+tc3^bVuNLD2KlV$16+Se_CT%WBd!JxOmtmg{87mOfl+i0xq=0f+4n-U1z66w|Z1 zvHG+!#oFn!K$ppAZE4ihNwQ&9F)>WXUKGmQC~I@Mh`TW1LXLVIscrb}A3W|MJI$*y zTAlJxo>m`z#SXAbmyckQ1rOLnQ%DTPjju165zl($Gq3kmxwF_2tjPWSbT>4#cwe;G z>?&jVzqs+-G5+cNI`t9t1ad6_GWF}jx}C7*G7$Hi7_MCAJ9e0+{ac)9oDVwU!qmA? zkdJR0bJ~4DZ{z5hVeQHu>AUA67bn@3sVJRHGk@v65~3Ht<CQmlo>DLs@t1O{it%xh zquBAMvFIO-t2R@RA2v-Dpu;eOg|Ryh%IVsaa2gU;t^>=olgo2S#uCg@X}72{k8X|T zH*)?zqf-{;jIH+B*+8PruI9H^jBHG@o(lUrhIwWm=)1lCu;JI*UauBA`qS%*8m|{3 zg#FqRwpY%<x&F!hY=N_X4807|oiZWm<H%7jxCu9k89`rYjj}R4LwMs{3o%_uzH!{y ztE%0*z<xLvoVRZNU2i`?a3>8<yGkq{al4QOoZ6m<#2QlU7ZWp;A2Q`5AdC!_&0nhQ z7{2YE(56FKwU6Hj@Y#qA{1cRUj=hD<xIPK^!|eWgudr9u70_gUtFy0d{EIAngSYvl zEuW&E>vwSx&{&?rw7=*--{GxGTZ$2c(&x9MQwJc)a+z1@<Yr7pUOwoywnEz}Jsxv$ zaAMQ6&Uar@eWXpaZK`cvB>bWKGfe9jXX3cuU4`wc&y6`ot>Tw%k(pm6Pp|j`R?<f; zp6C_M1>qfL5s9Zj9fWRn64{tJbWCSEfe6(u|2SINoo}S^oEZ2l(bL-{X*_%4zwYds z4_U*P8G!!&KbJP)cKaC^Isibk1ONcu|DyJtj2$iQo&T@a=dt-e=DI%@C=on<r3jG` zNqM#-8<)7G@vTh<YyPc^S?k1=A`%H9nFJi9=IiS*Mc2Cqzc&)Eq#7y!5&@;yBuke* z+XxV-kk4A70Wa%&_x{rfq2VfFwegdEQof68m)G?xlAftyE82{|&aqrxUUxqC*XIqV z*Q)R#ZdXiSG)sUXx#8?FPJM<9hK|$gZ@Jo}bX_v(4p`>d*d!voV5NTM_d#Z~LC=Qi z;p%wEZ!OboJ9iqyRnoI-lSfZ)Pfobw8WY2AgSt7nIy$ntUELgAokL-aHBGaL(1_p& z*-Vqfp3Wqmylj`}ISS1h1O8L<#4I~Z_C3c8R42{fPU<j;h6AW?^aMK5;Tf#|I2*}s z8LmrH26;A13QbUe*JhL|K5hGnGsj6|(Pch49xx71z0s%Q@lzgl<uss(!aX!4k<*a4 zrdg%j5P?!YZ3h6)C_8}G>Dy0<5o|bqz_=EVxw8-$b42yNC5q%JlC9^A@G^Fu-U}Bl z2^@pLaRo~M0P^SQJh>zLp)zF*Rv%_QK2JACRv#~CE>1jsy#bK9|47zf@2&_8nk-;` z@tvT3%e0J|*v`A!SaA9-0iAtq3{pmXmpk{}pO3QHIK$kh8JbRG2L4&`9ik}sr;kj# zLGOCRCz=UCJurclA%;B=c5vNRNaKd%j_(z{>oPcML0L2b2*NuY=leX{uo6ummf56h zimW<N$Kr8-tT%f7e(CL#@~*rgb&s^cwVLoe{wp}YOMqwbAVB5#G&hnt!%Q)wS`ZHK zPqTKD(Lc5D=wmkcbpwC*0ErVM)8hayZ@Df;f(LG)5ZtE`4>DW-FX~+GxUpe<f#cx4 z`*6|sJw^6u@&{SRnI<~w;RT#G-b=b6inDgo*CTuI^@5(~`eD3uRu+<9NOwH{+VExY zf_3Bij1N|1>7rvI^CyN#fsX@lq<@r+I4BCk*)}0ML(CXJM$kraG?zH?baZv4<Vi~p z&qgpzDGrMy0+V?jf1Pg>?%7DkH*@&8aK9+tp4WZ`_$Oum9j4Ft-Fs$FuA(MBR-UcC zU%!vfBLM(Hy||+_l&=)$(x%5bu>%z3%d!xfjW2OsF}x+<-+p8pDTsEdTb%dQ^)1Zk z%L)K!A(~cZ!picN<sOYr&2#z3&jc7g7$xpn|9%+(N<@yo^7(zd<mx~M?Lwa4eoPVP zygK%-Toji4H_tHfX*#m&BAP8&6t^`RO$40OD7m-(OTEos?w~Q_++3h0;r@mu<J6&v zf48PJ&BTWbqc)Ic6t09{>SDnuCBlv50SCmy<MDbp1eN=<nHkV1vuHU&4MOy3J*gMo z<~c|df%b+m3kitHaAG?$Fz;6z&HqviZZg0Si2PVBH}5Bt1_5q`eo(@Omfpq>lb&cA zW1vs=tms9fWQzAPKoCeVhFfX_dgUihq=9zkHIx%xviHTU<+$aM>+c`2xERb*5D_5* zsIHGhs+rqj6|W}U3JqNWfHfrm01sUklWaH0H^&SowUr+J@3d!!Bp^HtK&`VK{E!Wf zn_Nu-A1+3r(eRA%&c5gSiW&bfUNx{(!P{ImY!@dRkAD3zK&Va9K)%5>jYhEP?=wB9 znJW@f%~n|s600IFc9vwL@CAZ|INjHNFv=Lz2&`L@A)~x2Lsut9R#$o^UiattEMKl5 z#u-Qh-bL<1575AS9?<V<F=~e{es*>gC5`cE_YA;~Y4d>a%P*|1!v|hqfGHJWRH(0A zi|%hp_IK*m)#HeUpaclx-aWn$`>v}(%zMnwl`|ViKXCV5BSF&EovZJ&@(iMY_YI7p zQU^B`qEK4PIW@pePzQ;l(I|oGD?MQF%QMYciRZ5PkIy!KFEk4U3^s}fdsc)en6R2# zCtPNu9|r<%GsKQB$>B;1K*5+J97Eui3jmLUA5f;L24K7OisX<Dmil`jI4naY`8iuP zdOHK2=ZZP1u-_CrIuSw)>0r9*+*)2tFb>(0-$~(OHH{qQdZgzkyP)iKo$u*tT$RG! z-Zji6*#6{Bf4y3Zw*(S1Okyy#6i(~1&ptObG+~)*n&H}%4@x52+RMkLh^bM|Ac6=l zuq+q@(l})fA-|r89GDp2rBrr7OpFC@aPtYmo3)E1?g=fJ^C<~aXzA<9HKiVhkrhE^ z0B6vXMdm3wjNo=s^JMa&V!hcrw5UHjEo)Ah4_z7;CZMQ6UBRg|(T?F##QWc@RtCJe zcv-tojp#?_+@O0(m0|hbQbl-3w%kv)_*Czx4U`q2FQ7NYaN9F3&Nd6wfE$p*(}Jp+ zzo?Fh+fsG7_!tK03+Cv9B-h{xO)v3!ejwDl3jem?iX%h$=K-AKy}!e(og2lqTkIG4 z^S1gEMaB#TpPPQ5ar`7p!NjB?!?DizY_@c(;2#6>ctd(lp03<3c#AI3AdAKEXA#T9 zNX?p8dEVlX?mD|6nNYN}Sa{T(ar#jY{O{$96>wQ3EQ6e1fa(p-*qY9ji`A0|A7~rv zr&sqsSX0yle{aEhzCw&?Uy}#|N3Q68$&rF9%Ve2NNcF@-H4UIpzyQy1$<(_VD8OdI z-dBkKz~->=Lze&W|9ga>G!GyQPBJURAcC<-nDj@>f7@no55mG}j3n781s}Ok)d`$j z!>`+rv1Xa9eB?y0E0ZhdXxQa#QOPWlWybaqtUa%?srC_v0<a9Aip%--Ge+25#(Z{_ z^X8JJ8ram~XV&6yt?F4+=B;rHtjB=wo_C0(3_V_HK@_bQg5$d6lyNq-`3L%?GXrR( zX_WPpI`4CyB=uf#-B{+QJSo%sT{z~Lt3YJt&m|*a71w<0p_(wW!*8*Kuc{@OB2hPI zxx$O1-ZAlwymZE?hpv@}abM5*YxYAW@zkxiIFa}gyouLZUvke-e>$fb=QGB$=u~Sw zhz<@>hhV>C@L2ZFtD#D3mc2TQQ3OD4$T~<6%X3dK63G+XdrNF1%G>!R-CiH+QHWB# zqP8e#q}um$Jyex1D7=iVHqa8U>s{Saw+8XDNbhg#5S2+by-^X|^^0~p90(voF)mcd z9MmGs=~i#sMsNL;5)4`SJ{2wC+WLBgAj{mKLCw&Of~t(;prCUodS5H0pB6hil%(A^ zS^}5lrb1*?OGO4F6b(=rUI<DKAcd&{MJggx4?%aN9D_nN$o0&~@^6+*h!px+AWOUK zlwB33yZ53eMl7>Xvf+6K+*XK<+~v{xCm87M$+Z!eo=_u#KB$mt_fQ4V=%IcZ4MER* z(RGyRU!>DQ`8Sq{v}*C&3H}PV_+=q5R2yT1ROil)5-$Ej2lib%U_^K*3uGGi2Y$AL z*;d@b&!>_J@GoScUWjq(jyps@rTO)GpjpSzCdY>HCRGK$eCft1Owpm|S7A!U!ZLf> zBSVu}_$h*+Rj`pkVi5oJaHN}y{s927a3nK54Uj!+qDR?$sHCL&o9G_7>ehQv1osQ! z@KAsoBat+cj2`OkrNXJYg%7iaF<66v)Bq}f|A?zTdv;Sgeqe+_R?gDO2u3&A;?a4m zEsHX|3^j_trfMZF12kdh;F4(29y;}z7l#)kT@8qJB8*{GlX3*`O$iASbZ*Afr(FYk z1G0ZVA`eXxQ9_uI;q18vePQm4mCbzri^HWwyXJ%>;bO7>!r=**g8WFO3ifCS7H<hz zhEgVV)hMm?SR#KOX5+d)_ZYg+2!POh5QWgfZA*WUb?3IB`5X%}XO-65+ncQbpZh)V zo&5NPQ8sPXmD3ao3HF8Ek1I+w_54rCWVxhrRBL{9hcme6BBJN656mNPxenE3yfMDs z9#jBJYm0e>vYtZ1Tzck)Re4l@wYX@$!OM5u=kdGUg@lPC;MY8cxtdzZ&|1}TRzGr# zYI(8@>4=7%zg#PrZ@Fk217)^4BFQ!too|k7m5lV0s|v*gfb`@8QK!QzA124&7(P%A z9VY#IPzan1DgywbL0zK`yIF(gX4AXXOxSZexNIWw6L0TDu1sQ9311NW@5RzlyZ11G zD;kDztNG*v5<m7ld^O~VB}(|BR+!<o(l!eX75@(aaX^m0y)F^Vt$^5p2rQxpR@=?V znzw)rOS7)58$-^iQp*Bs7+7b-z#=Upzx(S@_Yl=4)T_zqo5=-Gwlz#`|2RCj2ex8D z?*RsJH|DKqeeZTk#J`IAuj7l$$=NAN+{5=*rR`r+?cL>pw!(mGv%e=({t7nj8JeB$ zS>>ctboLPzjw$p;b<h&e2JH3O+qdJ>tMMCl_U_7>)XLx(AR&y_QUoj%yuyf;vskZ4 zaSYXbP)))aY>llUz@Blmbjb$eF3j=@d5CeVT`Z@wk-dXwKLqS3f{hldHA5UQ;4RTd zja%^;7Kk8DL(B${6Yw|}cpB2T;6d<ohr{ulC5*Nq@Pr*#75zSFGeS3c1tK1@|NEc+ zVt$!|gci7yqpQg&dwq01xjH&wC*!ND@ddm5<>=y^T>??xJvkWvY1~tZiy|3i=KuTO z_rkk;dvsEV<`KB3Pv0E(6m8`;p?#xI0BmysNd-Wk-C5vD`s55xuW!N#PlNCX5BG@n zG7oGCnW#DnSgVXJ!~%(=YASbl7G+DgkPxI$NE~xKeh%q~&jK|E^~m&LNhv{!an=zB z^AY_bMj3E2Tt_#x<Ge>V?>`u_cc|p)kL6(-TIOX=!2)axBy~;)LlL1b8@r;@a5}KG zvjpJcf&&D?MuqKkCS3)(yUthC!oEktJsVT&0BQoMiSkE%JbZ<~AF>IG9eGS3ml?7W z{7;HS5Sw&<BP^arLRCz?!TZBwnplRWc8~@`8m5@06j8K$+5^m(6A5Bu$tpw|8MQHV zZb@QSF~TCB_TOok<#bGr=9V%jJbE()LCq^_VgV_XBC;7EF>0dnpl&p{y}gC~B-er~ z6*NeqDFe_m?qqd)c54Kl;8PJ?rI$^J!0Z^wIkN$5Ga)6INOjgu2h_54-Aei}q_H5- z)+#Y<)G$pIgaEY2p)N$eiv_-aL!&5{{5qd8`{eA`Fe=mx-?M4v?`J(z&~dR>szXtt z8ry4HD9t|XG$92ds|sm7B;WwRg#OH@YLGR(N%vm=TS;oIUoIq*BycU3eu3o;wI7%6 zt;OUTGYpV=(f+$R^1WQ~8c-S^k2u#x8U(3gHdEgy_tiLG&_n8ZojGT`m1S-R^s~|( zGo22;YNwL>;K}CF4M$b4&xpVTs!Y*mR=baWqpKBVR|K|1^cgnoI0JN$WxCg}qoUqR zSM3NlWJXq{Rq8RS_A^7nUxi!v6G!-FWb4Mu_4jGMMMN|sT$53+Nxr{d)tofdsFy>X zp6U@mbPF-ir}Bi;*k<<>w06j4D+RfF3qZ{hSr{N_Es;;07vG*20{-eC^R6W)tZ#+H z%{=rySwAmpO@Y1wq<-j@OK?HpicJfv@4lwsNH>uBYAoc=00e`$FUuCAp--?W9Z8e< zS_zJ>XsTn#%d>YEugB9jM_1$N`26hkFJAK;2s4yAsP-cedXHVRcr*_G3sxKapASyh zZ-D6aJ{-RI)C%W|K8)3Uw!V?NQQ4sv0N47}dZY4N6MX`>3`o?0Y(4>Z<+?ekouPG5 zHFS1^{324^d5DrgoI~p0psB4fxhCtS1U<3D-T*5nQeO#{I3VI8<#aKJ<Vh7kY6S$h zn?Y%Qqm#!`%Ln-p2u1C+$VQ@p)@ZcKm)ntp)NDBdbN3QA-dFFueJYKkbI}hQgInD! zalmR%P@JC5L=0IBJu|!bs{ANlJf{CaYTLfn9@A&CxDI8Mb}j{Z9VS!Mbx%hJ$c*Os zCVqAw_0kSpps;lnnLNaOg9FD<2_lb(gXhF1$d#D4SPA?rmKFodjQhBNA623wS>d-L zm$|RN?2rKb!1#=BK>Ey3Rm70$fK6>24>}n3ShdR$LLkpsGSmVU<H^~sslSha;74<9 z!Dj{V><GC-d5CJGwRonIk%t6Z598BL;cq2w>&S-*IDS&>HN#UZiDhgh#VFdV7`ue& zfaw9g)NUS#W!X)rscjvj2kKx$J|N#LckCDrT}UkXh_KqwsR^wyZzXDW3s*aJ>RNkw zEz*<@9T6mcp-U&!sIk*};s<L|A}*Tp`uoVLp}pxGdw}80ol^yx<$uu2eX2DlnUFHT z-iCNlISLx4Tgs+6hm0Lt!;nn_&yAZk{v{|YmUWT=eP)HdX-*x<qjpuNg{|$l3nN<J zWdlxr{|&RfvQn)nU++P~+W<B2!C$|1fl7?+RqB*%U%l<=!KY?Q);MXyW<)oa(3g{} zq+!Pan_c<A!jESYptS%GaSH}WPEVZzkYV%J6yJl|eE8IeXz)!_f;Bu(cfWXGN-0Xh z=S_`p@x?Ja>vi>dm3<zm2Ro&zibmyGK?zDF&<-Rk16H~3ZbWD(@xg#-RUMVUHl$o* zqgpC=bu!iqv4|`rSljzflFLz6jgR){CZ`qljpVeEmRdrI9nP*$Hs)AZhl|ez<>|F< zTtoO~0Bv_7+n4Y(!j{@RGz@R#_6<npytir}c6^Sp!7;}cGqYV|RbAq{A&5FLNGUwr z&zWDO1DOmkx9XMYO|NEn&=%My1xIvtqFM~qm7PdkOEDfF1G6IIbWF3z?grSoLjA~t zfm2Hh@g~k4-Zt_eZUFMIa6T_GtlTU)U(7;v`)o@EQdPZmi>wOYdrF*3G9WxIY2;<I zW`$EXSU#wxQ1U>xFE{NBRoNO?pgFupMW|dnrE>9zEvY{gfA|gChU-^@9~J@CqmX^N zEryxMCY5(81o3knn(UH;Hd(;WCX`fnaPImA^wh5qEgO?(-pA}C_9avMNw2X#n%H8z zjoLcx8Rp{}YXT9hvZP4;1L}51cT?9b1+me;+EQhq7%y++@wOt84WfXnu8ei~UkS5* z{}9WJXv&-x3v~a!XN&^c8ATdxA_hOH@YHOqX3bg!)Y-*OMX9=UP(sjU)oxS!l_E4{ z5vj<p{in@0_xa=2&Y;$Kcvd#fs~c_KrgF5u7U#H|8;x1bN%nPs(s%4k8(LiKhxn7_ zS`~HuUemeONO7-D(5pYEx}Cao%bM%t{B~69YXYJn>0c=PQWLfgug|EkR=?a+g9pT- zVdZs&zvsH<y-eR@<0_Lc3wruQHn4`BRHgDvA9$I*+L@5EVt&mfwbkC>A6*D?{G$s) z-S21M4r`72PQ_VK=UT*zmd<?oBM8AfjOMxc0^#GiYI%Q2n|txY08nV~nXVZD5nlXI zyQI)6zh?CFXYOco2tCzZQkdU`*~Vk4lehfK1-|OR--;Mt!<woY)^nj%nLMRIx(w*! zuKR>SzrJA8NFfM)f^Dw2wsd^v)WPY!Qzz|Ma)LFO`J)bK9jTu}-Oi=7VpoksXKD2t zu`MpPentmKv;|}(+8@XpD52P6=fx}%b9Qt-VatSi(W^)qvo{kV+gJN`=4^J`Hr_TY z&(5vJkweThH!i?Kbwcj0QTDv(PWTYDQ{Z(Sr*EPCqkEohh|I^U*)~>6p~A0#GFKdX z>dJvJeJ$XucA6caw9i!Qnr1?C-MkmniDc4Tc0}ir@Eu!ck}C6}+XacbQLm9utxfpa z0;fa(;Y0$jm8w4xVBHs&Yn26>CIV?4QQKTC4L`{Fhw7w8k$cqmD<r<dfm)vqP676! z4AoBe$T&M`nkyPro_0^PqM+Cnh~2e^MqX`hUYVh;QcO2RBt;svYRB%9hE2#TB{_y2 zE(G!^>h8IH{T9?<)4x}da+}3%*KxmB8v5TN4Rl}EQ9UeGxp?jfMj@KOU{s$`qxoqy zJk8Qm#i{=XP)h>@6aWAK2mo!S{#t}ec@pZp0001f0RS5S003}la4%nWWo~3|axY|Q zb98KJVlQ+vGA?C!W$e9sd{ou7IDY1l<RnaT21p>hBL<5GH8P;2Ltqdl#7b~*WQK@I zd|(`>)M7XX@Jb-@WGsinSbMKp?Jd1h>aEuHTWsZJ1t$#Vp`wVdw4g>yb&o@9C?Oe0 z$oZ|c&Y4L9wzt3E@ALis{`v7C=dsV)Ywx}G+H0@1_g-r%ZhxG!a2#iaKTYGfeVqQ! z#r@C!82sf-c_oK?IrGS6`%FuYTy|UF{<Q^b>b`$p-Cf@+xaY119{9dkaQD3hb<zU` z_digu=oW9m_rAaC-fOb6vkDCy+a5f!uFbx4TJmpYwR2iK+}{iH(_(Zloz_bC%xN#d zy*W@k?FhmCeA<sGJ==gUG4Au|e(U}B1Q1`ct{ykXEit8W=Ip=SolNWG%%%~hERO4d zm!IotF(+K?@YiKf0iVqrm&Tdsd-Bd5H4)Kf?lb}YT#W>Xu&I0MM*mjlnz*_6aHomu z_I?8~BPMQNCdWN{n&T$^6PiCQn1=Ta)bkG0KZd_Xymx~L_wgSY6~eY!hr<*=t*)tC zb(eS-$K5@{X!|wrcVajkl<T@i2jf=VfEi6X&Mec#aFJ{3*4Etv&(v0EGj|(S;u1L5 zHFfu{{yqRs7jzmBz6UOWyYU~l@c;k!|K{Iv^;F|2E6;H|nx-z`9Iq?xez~6KBzM?t z_h^zW9P01Yw15UOB7l73VSk08D<96)KX?LjvleiyWE7-DTpqS92S3_^CM}Qaa&c-! zzvBp-o6T|UQN1MVKP#zkcuAjECrj$Xk~UyT?~F7``uF^ADCwBZC`qm0{SGZ$*6$Gx zrbVt<z<HLjWuqxaC?ZbSk4b8#lb6hDB3y1#-F#7Vcu{eG{2jHmrV260r)K#4fv2I& zu)E)(1v;I8M5*sr>buw+Xpp1b*(yBI9C#KWma!?CrUlJ@P3mXsfj)SDaT3QpqqtdG z|A3~+hYOYErxbU>*;;Q=>JtE&K%npFzyT|8e9+`&|23B5ylNLK(J)VevsE0cb`?b} z?nE%{naXhe`)8g5l<ME(n-0QIvHDmMj2Fe-twK34Zu=@PE#k||sR$oXO6q3w#^PHy z6!9TcOYL$*oV~)6k-)3a3`IKSVaqYueX7Xa9ZZ8_D*^bK!E)IR*+|40YQ;%QMQ4z^ zvVv(6{O^}P;aI(1(reflKep!GF+``f36X=HfWNoH=V50rXj-j0VqZTb099(C57LyX zuJ~=LL8^z$j!2O6VAW^n2nkmRaH$EXZtH{yA^P<NB5Qcj6fK@ptJ?P?##-k{F{9iW zUHiAVMR9i_lILI!P~BaXHP!8UzuJ7kYcz>>^e9q-y*z^BDl35}Ciw!1(gix}`QWop z%j1Es;EPxmf6yP`Mj$72dw?4}>{plTR3rl5Pkfc@9FT4byAz(*>;Q?J5HA>l(lZpL zl#76VAIlp@Ea_~K-j7=xJ!OdQ1FTVv+G13<o;Qm&xLVg;3D@-Xm+uGwCqlKxH%@DS zC+$<<yfuL}@EFYWXn8g$!4Yv04?~$+-V(hP<K=`|O7C&=7D))2kQv}xE_|zs5;(#y ztHpxqz7OEv74|OY{puX`o$t1%$<0>hyLEoAFOZq*;=*Evl^p{<3eP<luC@Wo!Jy!s zpRO%Z`-%os3*;UnCp2-SoEQ*oYHM@C>|xhJFI37=tK4dJR`GRz#Fjn=I4Y}G)=)!X zupx5x5HTpPCfLX^de>|i;bCt~fM>NUZjs;C{A~GHgG9ziwfp>RPd18--@~|ptS=y! zMhZM^C%$(n!1zyP8-n9;8d^5s4q4MgyK_Kj!uiXY5Sq+zmj^JhS7<s5($U!^H2oFs z*c>~I_ohxvY!RAp7FB1vfPCIykM?2ktl9zBZXkvBAq+t_ooaIxunC)#tjom9F;6+< zsjPJNiD@NO-BO0*I3ZBo{muj=xx1yTAQwEVeJJ1C4{Vzln%a%231ntxR5Ck`JKg<~ z72%xj1U|s0(iPa-$Hg4EPZKk}+DDKL&^wNoRP~E?xxQZ$vw-y4M_w$Wq$+`l2}(?0 zA{4RG7$}XWR%Hzp)*vIjJ`k#YzY=0+frxo?k=0NPKG__|2d3YNoVkpRKCfw&mEq7S z0P+Oa97NXi1v9nT_*u)Fq4Q&od;kH~NNK=+aTxP~8GypWJ~*dojoHZATru{4aLtgs z$lQS?1os<&3oZ(WehNtZ9+rELF<0O_41-w!*&Awu7cQOSgit8eVZ-_>)QK(m(5r+_ z!UJpT*R)n^;c6=3W+*}3jQ~FOTlf&zM#-}vS=&wE&tU2<N}Y_rgapKPW0(pCZc?_t z4^PVO4!nj=z%{@?BpB87`$4q%{n~D%r(esnL-m}kQmK}=2%h}=QE)5^KiLIeZ(ZgA z83e*!%PW9y;ZPT~lX3<+P~x!b5?Huhn~fzTDK-&+wcT%@#o8UkI_E*1E7|LC1soS} zUQIJ0);R!JUTT5o_1VT3E$=p@mnpD)G$6_IAn|9|u(ibr>|$;9DJ&@udCniyjDqv> zseaV^$)rN+H_Cdpm$L3YMJ$9<_gg4=fVI@pC8u57AJzbv7p2ojYFDU7oEI+Rf%}BL zTrF_T6ge?f93SL8>}Ml&=^)v>#Lx&Vy$2bx)hb?Y;nlv_5$y9SK4=SUM4ap{9;c&F z1g33o;AtE3>U~g~@Wc7S-VFJ0J{B+<ao=d*7L&Z`4V<9L&*>%EA-9?#2ZHAy_~>L+ zzO0q!b84?RPS~4{AI{s24^Sa<ZArTHrSai2<AcaMBDG37eD$+CfrGrg9f92J1>6KJ z$gODn5@!#XoINbkLtR@dJEuxlKo)u4RIl(vBv77>wfZ@H)$u^pTFi3P3>6FtfycA4 z)!)rXsy$O`<%cIv74wq_VA|hB2<{^EHmDr7=?bjj{A3OD33HqpdQ~!U1Qeq~OJAyP zn^Fu~Q}s$iY!qh<6&uj8TAmfUxmKPtRkQ{R;%ULDa>QD>%)@T5Ci}28^ggguF7R=@ zFucYbABn~NLHLH^l-&ra>lg<+NwffiO{W4%@qMbuE4xnsOgy_ag!j)OQD$cKNqlj% zhqrgay;iZ^*rIEOEC-seb~qxT<46)N2h)}Udsc>JEXL7OslMs#Sev(XWW)Jv(bjN2 zOUzi{Y^(b^ZdOl2wjRuuPua#|wn~3E)QtnEC7mle<MRgjK<omJv0_wILFEF`eFpO^ zqC9`@*1NAs!b~7JcjLXbWINtcy!5|vk}N~KG+FwRy7BVB8bAWP;9>vSjk$y78ojgX zlNqusy+}NmGZ<yLEbweHOMtTI@D<+x9}aZ@tvo(uJKmYMA7@u+v4*vOpK3piYF|Qt z0ZuR&R)Ex?u+r-%CS+e)kp~N*|JIQr4dMZHeWWf3)Ct6(dKw5F&uQ$-&6B4}MM3+@ z@Zy5tR1dR(4UVKEV*3rof8h-BZ7y+W*jnQ;P=rG#&*|a|Foh4`i`p9Ym}^yDj+kph z?b6wzL+>WkE|qE>E{896`f?1uG{2irLPrxQFi(8rSG7$gZY7NQ^yRpY(b8rKW6q#C zEMCX^*dnZSim~PjB>(=7(+B3)h)cEISO<S#4Se<ayliGBQlr?*9)jezV!r~CJF{^# zN#--rm}@fF3ATX84n%zk^a+Qi)Sq-jK*~8kg)(6B$YmZ#Z{<LF;@ET0r;bScVStcq zr09c1@d?nYWS;(fjQ!>^G$44$na##fPB5&vcsrOnwIxoefSFS0f&*c9mnVRgVTIGE z)8acov9&|Be=`~1?TY(ks~fd=jb)<rb)RInK>jGmb5;!&N@Y~lP}x%@9>n!%_KT^2 zc1*QW%+7<Fq_)Hh0}UK1fOfD5*(gI*O9!sBLOU{;2=FQgdLN+ym84c(Zj1A^&NswS z&=bO59=o@zw!|)i*sh8Kt=1r|LQ|0tP^v>S$ROphZ%s`RY9lIXZCd<hVC8z7PMc2^ zP}$jRlu<woACu9L=s;@~w0oP6)m?#<I|+?Xy8;_O@#2;*pK7#xl{M9JG<n6VArYk3 z?82EiIj+`if_xu)>M~?<efIaTvdggK^kid~p{D0U^8-pptR2@OG>Y~fDZj+sFOFu* zAq$RRwgfR%^ea5s5lLeC)YC0xHZ1HO7Au9?m+@-(Y9&W44=78mYK}8IP_%#(N2%Eb zSAvaEzDCMmPdfoFT`jXZ)22^{k7HEGl={xIUI*f_LTZm|jakZ-%h#Bp5K}x$<pHzH ziXTB!KV;=}0-UnLeCjW8+l8jL^a&b<&v^Eef>dXG4HnQoAmrxQiwIC}3u)q~V3XyB zE4o-QYV@GHZ^-4O1x23<d#z46v7jznPTam8hIfImw+qB_7tZLAJOY!`DLDd@qE=xS z7uAB2Fu&H>B}U_OkfDXW7EmYVgO<3rOMlPsu*c>TKkIWTWP|)-7UoRDoNdBh_}^00 z+)&@oRf`#FMMA!7&4rqk2Ce1N+n{SXpY#y$6SlMw8$vukmhGC7YH3=srSW^z3R|dM z{JiKiv?%AI79E8a9ToPPE@@FSwFv5U4C{4Nj8eT06*b342aQ35oFdZ-ggwbgUeu8K zI@f>S=a);72RSL9U04VsaWY$cBgX|ateby6%?HOts~QF46ilZ<mR*ArSt6uKR|IX< zC26nD0mjJoGSec+0^IcijMO(kAi%JW(hb#vc&?XqLLrsxx08_oHV%j6%Btu-u3!Nd z-EpTM=knZY$e*zfmt5oN&X)QNwPh!M30ghuwlb{I&P9vS{*@*Ntv+Qoeq+vIAJT&3 zS5!wG$Mz%Mc-jt3R%~Xuxj;T-Z9LZi;%nnq!GUUkumgl&J=B~?Q>~$bvIU%aUjA}Q z^OxzWH3Mxv-q|8r0i{{F4zl0acy0}3*!XshBT^k@AC+@lLumujPfTMwp$^(?EQ^gs z6?;(7QTv=-byFJ$?u4?}k5}I=YHL0RR<(T@%li(;H7>PrAoVKaS#nE(Ps<a5x<f>q zoXD*oBS-8W#%@BvVrwkd;Q_>LL(@{vGMIx}?rg4`Qc4BpH{NN}Hfvf@Gc=SCwUkqK zJ@1Agc`JBCc`mnpw2_A@`QuB$-!lHGiwbRC5IMK1w-xe^<6G}2wDlgs4Fz$`)-3su zY37?^oy<()Og&ZlN@mu&QGT_r`Om!NQ@q(NpT;NDfJP|hBy+eRhxGTAQH1@An&o)& zHEm#EljWRzT64UEo%k7NGBt{w>Ua79`tq4^aa?oE4v#i?G{@U&!Z&Iwqv0C|YVdl2 zuKjRL(g*50N@jfHoR2?-D#2*K3r*IfoLx3lc?AXUxZimOrqy#3l2g?!py0-4m}s5C z$JFm2^EusoU52hIe*h#@In|X58q}3)3+QU%1eloQb2xa<;oxm9s8vW6Dan`q;)ujP z2g-Q8LCyCX+Kgb$@_<uq1CvClNI2gFnXz#K4w<WPTs}Afr@+|%g6EP7e(gvql&!?t zO^sE7Fmb-QX<@L_aco8JX*pr8yCJ+NlP^V6a{XwQjff9BqvFI??ky0%@sYq)pfgoM zQf4cM^pGPwh%>bVq;fX1PDl>b_@mUUWmrwWwkDUYx(Ov@KG1Tcmk6<XB&h^ySKP+l z8V}XEsuJd((_R(=@l&a`gzr5CMuNUjdnFiBNm+Rc#FQOH`*Kv&DtMH^IQu^U*-*Y# zR;C23kp*QN3AR7z3WCvdVRt^tC@FVP@_A^vGQTQS6yOK(X9H`q-VVf7s`_Da886r8 za$+uM4rAG<@hRziUm8j%GHg^SA7sA`R0PMd-rf>-?z+oxg~jF#f)V-jt-6KPBQ_5L ztlusCwm!GF%x1%7fch@=Q;}E8@)dV?Jfqd!jix-P(M^AXf(#&W5zMkc&Qf8^-*LL= zb9U63)r7OYU--cvAi=L)kCv{KVE?_0<F4FfHO({CCyG7=$$n*hzgpiF!$l5fRd?MT za(y=^PT7w$i{qH{kLxnk=3c2=ZH{NCQGAFN9g2^jiO1Qo-mFHQ?*8>Ff#?Yz`%jR( zm0%5cl2+rxC<s@X+=-%&E8YF^LLXak6Rzb!>a8+#k2n=NM{_5(n4oz#YoK(vyFuX% zT|pCdZtPW@eClnQh<$?H!?XLA5pwq5`ABQflDOCp5o)QeKy57o(cLTAV@-OBJ$?$5 zxW`e6i{zs*rbs7CB)fQRiIgi|RU+j}R}*#tbuvVrWgx#PKh=>xvtUQE*Yimq%h!jy zg0##Z#w{Pwl5*}4B{of0<>HiW`}veyPh|^gnDKVv!(7rgcVSiYu&T$ED%8#Ed1oJL zlu<zO^4L_RGA*QX#$Km+u%wE#&Zu}osptLdwR2d*s{WqEHkb5$Kf^T|BKZVdR2`!p zZ9bm{0^+kN&7Y-PY)|6U6;Ip00kpTC1%;cXD-m=Yg666;)pSm2?o78>!#dzT0(2EN zh`DU)V2##8*UJ?rwUVln(&c{kALCKKM12>=x6RMO9@2Ml*?Ph^>bv)jHS}HaT#8W3 z_TjE_ZHb$gK!b0G$=;;Dt_79K4VvVstHEU4Yh4hZgsN@O6n66-+|=2qYsHq!aP0^G zDr}cGw+|8FpupFTN!9%iCZZ+`beDZ_BHVcr41D9Y@ziw7u!RZGg8Vp$Gra7Tl}O&_ zI$`4Gf=j@Dgohxg;0Kp-V5?%X66&BOv*HW7NfJ6iiJkDOgiql0$qu}3e;=>AkK*;f zTWH5`don?{@F}`&Z=+l2Fx{Su(Cs&`!tJL&q-8hNZ4Fmgl`vZWtyG=~Xr_c1e!Hn( z37^D=3Y!wfdZ`t5NTIr%>c<teEx2R{%9)neMC))^ZJ4zibaL83XR{fNe*Jb_Q?KZ+ zaU5C6o<5xt$Ubd0l1<Bd9M=vd9xak>@JPg@MR!&61x*LXTDgcXXl6en?GgrG-qUEe zXn9Yd{$h-iA*eu>SgN$niwE6rq1JCXA|Cw5TJ?cs@Q&)X<7Nrll$+}3_cUB?Xpe$o zWuwKu3LeDvB$X%Oy?hYxR|FV)j6=PCq#sq}YlOY?AX_a+{84Iat@F^(g{7`qc^(G| z&5$q>ZT$~w<q}S*=ojb0#3$^X1BNE%%oFxrhnZUd_7K3Djm+QXB+QJNr1#^ru4jUG zXaY=v?*5|Y-i`^8%iaAo)v0;U=7m`ZlqP2%NQR$`9<+aupdcCU1)lc#Stj;(7s#tb zNsYDMqSRP}CNG$yl_ckYlbumvTOW==Kj>aOWRm!z#n#v~cv3UeTX?mj=#NTOch4aU zwB<vT9JwSh<S(W)f05p^Xr{FRM9RC)qigMISHrinB-6qhogdbng*+(Wr3q>@h8rGD z$2NS;{yr*M&)5&(0s_h;G`&ovTY+l)Wy`x`&yii1P^Cmsv*l28UmBDYp12*$RA9_o zsMKO8^`W2{XM_aXvqIkpbN5RH>S9npcuQM6vtdq_WYOHb3v}5ty^C#lQX%fb95f9! zt}y1Phtkk?pF>syh?ezs^%h%CGcWSJ&8YW?7Ikqx6^x}?MMMe0;Y!@o=(f1~tI=)) z$$b<h_sUTuj@>+nV-hl^RD+rC0(DG$I`ZvATsuRX9AB<N73UikKW-gmh{U_PQz8)! zaepYXq0*Vg0Y%m>BM}XjJP>IvNoZ973dwVGG-<Y6iuQ^Lbrxx|%sA0rF7oBlXm7AP z&U=Gr;}+aM%EMhHHA9)04i;5>467K4b;e4t<*1bvplqzE9@O%^j^jSR^UaMbo!L|e zuZp&TdYbB>TS6H|FPX&a<+;V0SZq{9npE_rXunD1Z~6zdkZtKO^5S`{)t;)wJGf1q zPnB3X)Up`>&i}ogYRH~Wfs-Hy3h_FH>c+Dl8ZhD@%63p&LDweQ*@$fVEZ4J$hdLio ztvpS~UD&CQ+HoC$$GyljwO8kR%PeYVtNSFLHgOy;sph5G!7L`v#buYOetaJncvgTJ zpv(qR?We|s1e3Z}#wR<v7}fBqlj0=8oirW^b9m7ln_-$ED=daOL><SW8c>ManOYG7 zI>1a0RZq188WCP(vZZXTV1B?p67?TJpPzBj??i9}8|&1~Z61ZiU3h|E1bf}7FIOq` z9kS*U!cG(dvkoBIP>;~GAF8WJovM{m(m0;>IOV5sB$ruD(n@&~&q;Hjh8q!)<M=|L z1f@;5loV-7)rq(Th=HSkO~;O_Kfy_3!__vg-fqMxPVnjtky~7GS*`C-rB1-V@Y_fM z%J4-CfXO*pkzUW{fpj2dXPTu8R6V)=q$WMII5q<hSh#`(?3U}%+)~_~FrC~Lwowj{ zw8SU{>d3}J)TTpg$F{hvoXl%2uCy%qP9FDuAyfS0<}cHf`ja{*yrU~nMt`*+<M<iQ z&q!4(lKSHD{74RTB9J*$GPt65s=(`a91nu^R~fE9sZ@2Id9kD_7Z|%WJ0&?Lp3_D8 ztF)kH$Jxu-Cr$*zGM_y=2zEB1gFOK+t?pAdQg1QnutunaHh*0va4RhrrH8HaLELE4 z#IW0#Q9asno>pLUoo7U!GrI9ct>WZ{EY;n)C0~P<j|51Mm)(HuO{(Nn{GLJIlOM;Y zKBj@{9=D@jda_nov}r)t9+8>3dT@mcEG7_+CvB;^m*{n`ih``d2J(KeAnl<Z$zCGa zq){*zqsa<$b_T8JNp|+jROXn97=CO+lX{}w9B1FUtJFCQy}rQ4EiR7zh?I<clo&U$ z2LMo>GgYc$tKdnQ6TeN(_ZxtL0o>%l-8^N<rU8T>C)b0nF_z6Sa(#<kgSqnKlVRqV z<Y=!|auCeU#@dqR2wUAtjTw|yP^pVd%d>ioss<N&)H2)iGT?b<M6z8tS==XgehoD+ zDP=Y($HzDzlgVw9@3nK4K9)Hf_@T^J*%Wz*mpl6bCO+nQlQ`;mUd(v`=PoIuwJaa! zHN9XT>%)n}!$Qk&FB3QX5}NR2b9jMPP2z423W^_(xM&V5P1T6~dyhVc%~!E=#4LHh zB&Nv&X6aAZsKtLZCN-bY$|=dy5|>PE>qi(<+oOL?O>J^%!D3wZRA;Z5xqw5BKfc}% zEO1T9>;NQ)_pySzIBw@D&<WYM@5Ht0hcel(YVbk2S<eJ@T0o2BPQ<;o26fa#e7c7P z@X^`2ZoHaaE6?3fDvk65goiy<owQU1xy7oc1FB<awL`~%7~NowcLP~gAXyr+KtyJ+ zpQ8jpJF;Il&IZu%Y5_HKY*uoG=X<zaFP)5w$_=3Wfv#s(ih-b#oj}D8kb6gHxT?|N z{6ze~%ie@agx8pYW*@tS=@iRwXwFt(yXMT~5(?XsiAy&?%lHJZ&%^MjDJng!(q&jm zE0zMXDM{$j2BCAHT2Vj#V=`{5Ww4Cd!*){qgM%uM-I|$f#Pa~Gwh$pmk50qIsZZ4p zi;m(p39g9JW|(NXMfw}==}GV7LeePAKb2N);;#-CA-|(RKjyMCe2SCG0sn~B_@bUN zn=H=pvC^5?^9lAObd-;I{|<Hsn`5G7bW+jY(c+`jvWd8#>tSDi0aU7;2v%3Ejl<<( z;@%C|O@HK#X)Z!5=1CrYXt3nB&*(gy4Mn@eY(fEq`U^lrK{5U+$-``}He4-3b3XpQ zimO#ezZ$kk3s!9{o=ia5=h;Ep#DGc4>}hC%EGAIxVT4TVaV;{5W5DEb0%*_#ZJkM{ z8KCPX`q<?9q=yK#jBXeH>t3vqST-M{tw5WpJfgS3)XVK*Qp|tN{D^U<MkY&@8jg zzCnB7X{1Mc2JMo_eFM@2_GpH#2;r`NN?D3wB}2zSUz%j-OEm`Aq7)c=20A2uOpQVn zk}BRAy%K7Ma1g2?cbdc&FKg>D$fsuL>YvTaJ}}hVxWN9No+^0A{Ae?H^=>FH3YGP; zg5Dugp$i7QSxjS37tx|A`|Qf(F2m_7aK?eg<_7j*V^5^x8c)e=7swH5gb(aYuq%H7 zblg{QJ1!ig;c;Lw4F~7-JLbZ3<33Em<K5C%`$y2?!j^nUkxt0_M7Zayd(Y8cvWvkZ z$VsIF=I}8jZ}{LuhRdau>|2FKf}Jg)gxb4t-tA|VMI@s5-~<*2^`=tGdlv2Bxwy#x zA__8wg6xYs4Je+yj!>+o+DOP`FJnT`#8hl1NRA8-+kr_tP#o@D4Hu6#`%c_bnSBRb zneB5!+;)~oC#7@`yAq(82QKkzRQD;BS2LB06P{)4r8|&rQT1M?x=#Q&=t&&H3kZMh zbW|UxXJC3~M}TNi_AyjA_9UL}99#|A$rXe4l-++VT=Nomoz#uj*<E-oJq6d=ZF%{{ zc+0lYZ4#rzw3Bq3)=9UkPr&V`KTNX&i0bBcTIp`x$rt0HNq%Q8zSBBNYnlf@aYbVX ze%xt=x7M@-&@t1>MOxGPuUSAR>}j1wUv$~Ag&t3&mN#~mLna`q6<6Nz^arkV1YSLj zhm>%gLoP)Cuz~@m6W1yOh<n)b#NgWVjsoWb&T-6(DjA++T;8oLPbkars9_6O^YaQs zJPjcy?g9fSQ72w5C)SH&*(pm(7%ljk78>q?Ir<MKNRDSaj>{GOrg-KbGEw?f^qcin z21ok>f35S7(DWfBhacMf3Lb0^wF^xXO}Iu8krQ6whyQ_FC!1wfAT;&h7-J*ETg%0T zGRqgQmRXi~nar$0Xom?6-GmT&ixkBKQcQl+9*e?##HXZX_C#Gm=r@$^qJ7j>cNc=i zo}rIsA@n$n*?#%Y1E6`xZ<<H6Li=M4q{b&WNlf7_h(92++r-;tmLs~$feuiqJej2n zp>Y7v8|5NEZ1;#i#TVncv*c{Nw_ke=y4u~1`Qz77>a@(|{mGOmMxK$4Z{kK)T-Zm; z;T@vY(H=LC=t!-~2%!_SdfdecP0!*9gn}BqUrOeQiJ-+7I1HW6_TFb`077UBR=6d7 z5D0p(RFYCz(gJTm4j=RB;|xaq2)QK-ssXxId_j;8bkOG;hJLo=XEPU{8q5fsqR;uM z&lBX99Q<tJhVY9+!@@?fR@0#V4?0qOVxWOGjqG7J9$Ew4<>Oj9wP+k~C;**XOY6i{ zwu*kG+=h#M8?4G4WdpBln5dN7X)6Mzg$|UIzr&+R$LRnF@cEIeb*mNinocKeiwfa4 za3JB9WZ1f^Bwt*KyHcp4mcM{xmllM}F31V{`cdIByN9*UAZt=+dLo%io3+cw*8Lf0 zIC@vDyLwzO*V{Zkc7&44;y+RoP=WY8!8vRV3N|&Hwp*<)IiuoIG8vOu_1#0=bfh$C zG!($T_yiE?d$bdG#V4cy(R|di&fNtiC^cv>jcL4(wmy{_t5U-&Q<R#C@-6n%EHnZ+ zx|My*;Uq+4`M7APo$Y9MT8}&q(nK7+Kt6YM-RW@oU*&Tn*XIPYSAdpeJ@Uj<TrN&q zgvMr0B_3SAyjhO$RZ+HJBFz!$`x2Kg;G&QgW!EH=`t>B6p-r&QHQL8orts_$QWCq^ zt8iC}VNwqqUEtzYX`sMCRTmZ7mDPpzF#HvDdVPKpde^!89j|+odzmIBV0apBQ{0KL zyKB`>j@nkywMxmx$I2SqQu|BQvgC%WA=dI~lS^I8J7yzJznb3S);MGQg&(%#)<Cwp z)M|OhVVlGL^Egy^sZA~8^>-ED-F%PBTC0z7wZa}=&J=IIx)OvWQz}k@HX_!9ZvxI* zsV;dmH@xUkuspj^xlMpdnX7)puqY_h6pCzcZC<Rp6KV&VS#LMgYn)i9rVp<|zEK4q zRpHx=rz&*ot&8G@8J$W3vNo37v-W$P(v@=Q9JDpWf&ku`yb6aB?w<+ja*+JDYI&G0 zALd~gK?-}qM4RU&D{SJZfKHV6)He2%o~%|RaKv3yt4IDcyjFF8qQfU@IS&oI3Ua=J zeN*DK2~D@agMs!<wg5iG#^J&?o@>3W&<<Ult1c<bUw*4+*|I>_!W0ka`w@qgMTPt- zWtzbi#p((kpl3M7scUTVz<}rk9JA!}1ESUW_jT8VXRQn0@q4>E0%|NjY~`f40w!$5 zLZ01vFURf2KE{_-@Dks(>W8TH;w~r<GYV*}y$h_MWpVouPIN$dJT%DB#-8}22HX?K z*VfpyHFn(NooDQ?5)3p$G4I1&{OEE(!>d<n4^Gq`%-0^A&hiYvLso9N&+k>g!b6UZ zV~!q&roFq8Y>=<i(`qve$=|L*;}pKO!*@;l0uQA4{6X&4z*CD{+%mT5=Nw0N_i}Z0 zA%E*KO)Si1y9W&XJeN}0eeCPMppDV6$~d^Vk-hXWfJF+NU1WL+YzMu&;QdZV#HGtt zW4BdW>h*&jJQ1`mc|P|CoPeYPFMIKFl%p9iu1AN<a)V~2j91cEC}sIdnKhoR&Q~_^ z$~>hyU#Ye>F149Q(hjj}CF<<S<yEp?zlHh)6oq)jaXwZ6ecM=T)Bc8LPd+DK!O68e zSDDNMa@8fK%Nro)cytwECTi4v+B|bMOKEbBRu$FKQ+rt(te2e4hXNhP{c1g~QG~<K z_tA-SydLPp>-JaanxN}Zx^~mGgRcAN`aWFQ5#R%sMfraU*OKjsMY@Xho`*?YpU;+n z`8H?!%aAT+si9qPm+xG20Zox^uvVRC#ACwV9LF(bJ0rBG=z18h;gfK!b-pPyy@ckr zd8Oryd;p<1Wp@N_!nS81J-nc^R!*!~F9>^^g@X;#Ib7WpkjlcrrbFfM8q8=;SZ`NC z&q5whs(w%axwOJi<uY`Ck;B+DsQ=T{3Tj)3+8m<RhKLFwY;EkzOw_)bLFE#^BQuM5 zz07!VT2T~L9E%WY!}FM7gy&<grk<^_U8!eN>=*FdNYAJ}#h!%MOd@n_D?HZ9=dO}w z$gNhrBh=0P$^ooPNE4LcAjV$ml-LSfBcQr|H<BgnZ4WQvkLq=gl_KzS*fK75T@vvX z!x48Ih({7)qCzZ<P<CI065nHA@p|JEH;H~Z5fIDd#A-1gbX+0y4xelkuBgT$JQg@_ zwKUdp#z@9Bed@E=PCnTTd+f)l=Yp6@=s~DnFHNbHH<@Qi`ErZZ$lj33z6!G>96HQQ z9p*WN!4pUZjEi7a2~9W9!~H_jHT3X(p=laDNT5j5!v>*gG(D^knzHcWIph(b0J8D2 z@NQ(bR_c2cfNUe!6|v7U3_7e5et0ct@<LMtH^WgQpMXa*Hpy&(Xa}aKgg?G0Dug}- zsVX-Yv=WOUBAv7Kmb+x?2rj%GT^DIzO4;5;JcmO;2>&~zv{DzKB#xbe*Ir`EVx(B8 zjT@vPD)WQiqzofnrO_+*X;K=Vg4qmg&_hrKL%0Ep6q<Uic-BN3v7_^9G^8+5e(n^} zWH-Kqjth_3p*dUAn?U_bhbLWp2zw&SJnZb<hIloVm1$J{5*p7NkH(JUv2F%rJ`42+ zqLAuX1Qt#Yh-)O$D^?_H2Gq^bQ<kjW?)L|UQs@^bLT=m=YU{(@bu87R(yb+g8$6u0 z>({8U(07mF3Cd>Pm0VR4l-)SE;~BLIebXWIHY&TZnz8GxxDA>bCD4#>tL0WuQXD!} zk-LT0p!+b7IO>_ALxao_JB^1lo_n8$+?RNKV333!Rse>7W$Y+|B-RK&L~BWC>O`9_ zaj&rLAf?|YJQl-U!^HjKgL2{laXNBr=;!cFCr3kT2-$|msrW8qVBp!u<?0QoDzl%_ z3aQX^Pg*itfoQ8$L&(XvsK@J%N_s1aI;wfMHOWFQy@)K-3Miqs8ONT`^h<q%*l(3v z`Ph3nCyuF=4<PJ)QkBk~WUehMq32EMy3lAO-bPJy(+%owN@=OWt!nnbM4UWBhjlXB zhjxS75zo?5Vn!%+HXaFpf|bzY_<3=^5_)2g2ejQdtb~J9bBzkoX#rnO5g6w3dbHX3 zc)s96?WlSHn<gKg*!U$1VR4c%1f^U9rB|C>XuwuhrrMbw(uP75q#zocSA%`3cL+3Y zp+0`SvGf6y9^FQtDNo|88ZVUDTA}Iwe&YJ3ZK$l^+<}KuT0;o&E$*TYM>lt&X*ECw zi&p^izJ){}P&u*;-rw#^PR+5!1Jq@96bu!J8b*3^ftaroq^B7a2jR8F{c)?XcX9tS z@zFp_Otd@$)8(;2pb4R4o5yYh1$phr-VXT~*ZZfz))^z=ap;-zYKLTYXa@D_ZA18A zyi7v5SKntJ#uI9EM5~m38O^TY-F<jsfzmaaf<lqa=?EcSty1Hkxz*$MveUS5-(0M0 z-$wkQo|n&+Y)ZF;kqBFImc|c+Lx`xTrOc=vN>LI(3W+VT2AC{EZE2WOEZ(Nk@KuT9 zbSc%s6~{|S?43-FS^fLq_>O1YTr1Dv#0)uN4jq^NUPRx~0R%C~2XHh2)t|?KLUnlF zB^JGa6ceq%tK&J(o8Vp)Pk-Jl<^~HYYnAX@gulbUuhK+cbl?<SN%bJUd)_7$#wY7D zQm{aul5SytNAE$>g|tf18qzj@qV2{!>H$>4R8qsj#3>*N5sZ1~B-x6utQ4=+AFa|A zDwPr+A1u~!tI=E76BlFuTs}a8P5Q1%0zGtm(_Jb}8N&o|QUar-_Iqw-yI1O-9-(d2 zvzrRF-M=8xQuU}fHXX{c96*4|(DBW?;@8MwDmXq(AHI5vH0s7=yC~x$H_!&8t`Kf? z7!4AyG8%LV$zJQem}K$+6hC0$YrFA*J^D7zc9d4B>Kf)=U|P~bfq$-5pL~`oL~~|a z?7QeAnbVb0bniJ1L-xSSGy#k}n|dCq_3FYG4^ma*Nvfr9;Yq5;zbbce@0Yo_^u;dj z9{Ae@f3L!y1=2^{glDwKofDTYe@%Y0AmE>D-ihby)#lVW4Vb1T7-xB%ZrfUct^FFS zrk|a!iVDB204x7=s7ErDfz~_Fxqh@S$PYcG4~EQ4xfBoRbGS!QldB&YI7VASVtNu4 zm^Kx5C@j6zgb84lT>T4}qr|B)vx;M7mMz-KMT;X6-G>nzqWebC72`%k_l?A7RRcq} zi|kO0yD-u3VU6EH%);I_wOQF@MZ;;6t@$(ii01Gn`-?BWxSuOrKz?6k6N}z{4Kr@d zHEprGZjpM!k4}QugB+#tkhWCnt*K6$J`>Z19pm!@>zv1lVVzaW+XYRgL)183_EXcg zG*MUqm$S95du!-#=TWu5go37sSRCH|^?6iu+Jrq8XWRNQMa?cx_=#-%9xS+(9VEvd z+t!69Icjc-NFPFXt;0wakO|83K-mcD37Uc&d!q^$<h+m$sTXjtBtQEhlb6-sfyfpa z0R2M&0<^=6Kr0BS5b3tfjm7jdo5Y-kCFzswTeqbl+W5$xX0w=wNd=e$h_-A?M}oyO zw)~VHu&uAfZ`H{N^9`W+FFX#{ekDDtPvSM)l!KowN5i)l$bIA1XEiLznPd+yD~M;y z=f|zL;cG#7S?3me+|t5v3!LxNwIojq2HTOxgSJ2dS0bnX6@8)6$VluU$DqY)VEWy> zniI#W>jS`?e6WE13Y|IC7UlxrXfT^i9EZDIxXrXaphzd3v&p;Y`cRKJ-lyg5(^*$& z96&A+Gkome8@Nu0K+58PO3;uEKwk#XVf%ZJsnSU(;PbIVj%e7u&@*_({4}6ZCQV1H ze_%kG+?s}uD5|x*2q2nM)i1h@!{+h1^1uaY)KHLD0VIB09_W`w;WCiRI`}bu81rVR z?gYt%mefIsQw77$oZ4u&k*%UY1&$t!oG>8es8RMxKkXyn(mpiI5h>YM3~dyeeuO2v zPs)+vQjE(}_hqBTE}-%t0%W5Wqvrr^A+&n5yu(B}p~;7v3~mOzM}q>BZh#~jq2YxS zeIE81`VStv5x1Kjwkm6FCCi!cD2|zi`o96n_fYCfUvJH6Y8RSXP+i$m1|y|C_5wEy z%}+3_LB&Zx^WFAhMd}Q{<kI^lUbMB$1cU5KVb44;CE`G`&aDkI;3L1tISH_t7CRH` zzEbPaqqSikCmn^3)7+hM3oqAqS^+~{Ol^g_wJzkzQoA2Tf4Z{J7G7u%E3*L6?X~K4 zwMwzvjC<MNkxr{;*hi=`y^O8{ODvPDE@$()9z?$h=W+BM3oo3!e*hcF^RAnubLv7X z8;SGsLYv~{;}&HtZ^$fNR6UHMD*nro+1SrQ=r;XmW=h!K)j|UyH;_LH2EvrD;A=&v z#<r#7U{LB$Fx<dWYXZ>c>_I#e?B<;dtwNI@yP^OFJ^MB~kE6GR&{PJA`*8;YhJ@O8 z<~jLta)f6ulUpzyc@jd{L8^2@TNg-;l96gzpj00x9`;l#Zq-%?ylN?q4DF+yvNdKQ z^c5<gX*fDfLI@YgVT3%BmmGo%tvl!7#Ium!IhXFXoi4aLA__2GBCXzDt1ek+uT^aB z8IbIXGx}1BOi-V_bxHojEn5n%zz;F~1CEG#5Lo#rVYY0U{w@6Wg+`+`RqfF<no|}3 zxCJXd3DdmVN68Uw#-UXVl^u;US2R9NPH0j=W1pVO%YKQuSQSf7*m0|TZzqSlo*zM4 zR4$!?i^S5X;05NxP3+b*OrR5g5yKnKr_M%7;aOb2_M!TNT^u=4T0Ho&Z|KX*Lti3; zFX0+btD8qlzVYEiA<$UNq%bJ>O@6o^wS|S&<a^YvRqzrZcpu=?5GAAz9yG#gpks&( zAFygjtpCrdRlSnJAtmV~@#>?v9Dy!TQStkwbHG{>1L~t#HAtu~$(L?dAKj<FjfJ<- z9+v((bmJb(#oF<}d8PX3%fkTvaySJrp8&Kyh>sl^Oh0rO2Y~u$M9;VgGP=}955wEl zIFvnX2LPy#;%4Y;X#5qmde~!>bkuljAY_=INr%Z9QhgK~S~?xcGNI&YtP|G8?E`7M z=Y3k^q*BA1QbgRW6LF-P0hJg5l>n~&76O&j^oyg^#n!=OJ0{DmMAKFuGvVv7K1QEb zr|yb2%cmAQd&TK~Wey9{;lmzrf{*<Tmks7|l{g7Lew986t|d>Z)@nK;qM!Fdx%11v zz(~m_4|#n8E?Wbd+a{QRCo&TzuUT!(!iBs(3%~LgoP}-dsgZ^V&}O6SuiUcQ$0{{l z+?*UX1`h%>p2DpJAst1n6<q?NT3#F6$hX(0&0bT4!}~3YasUNR&}Ns<OrHK%yHWs0 z@wKHox??pKnb5>BV8?q>klIl>8^35f))?P&U|xG4mkY89L2MW35<xz&0^9!LNlgnZ zIg5M$9f()rTg&d)#V?@sb%(ZC*_5knTGR3<E~zbSdGuzq<y*@FC_g}|JY`R|ALZ## zRbn2>NE?&v`gvN6a#6M#Pl@b6Lcz1PZZ-P@S~$rZdB`+(L3N@~EYt(gSEJH{bY3p# z$_NI-z*9lmY;2*LV23g5Q6ksUK&~01fU(shuqP{#55DcwA+^~{hR_GYas)XaOAJsf zmAp=*%~GqTq{lOT?1^Sbb@V_<Cm<ceBw@j9GlfDTKCrWi>BI-sQ6OUjl5y3a;VT`t zYD(Y?Q&xz^wTCT6WNO(A{jEfQo29=M>2GuOw@Lb&OMesew?+D!Nq;NX-_Atn?T%9H zCQSGo6M$U2kerY<1<)<eKSUAKbcye0yCXwr?$>+i9gJN8X=HP6S`B$G_p{%iyOy(* zT-tLRH5x-Uc|IorlB<Sx+++?Huc{C)+wr-kAv;s|{Z}^i8a&(Bf9TJee<-t|3v?!% zbpbWEnkY!?lXwzk%UKfHfsJ_Ixd=P%!@wGPDc4^D^g><wp<Id(oH`qa#ZDXs;uyKK zoZO1D<<ceefKJGTJKmzhFOaYUM?-5GW}JVAi&Ka@cIrb22jos;<Oog8s6Em!1aXc^ zAj#I}OysPLQ~hil$J&QFj8R#g+l<QUL(`}%mBGFkL37MKYANz#fU9;H*tCzdqdSwM zdQp;}f6Ax$xp+7~e?+`I0eN{w|HZtFv3D{^pL0a2Yjnv;-emHrT=XClntniYB*qS` zIK{@kh8%oKU7G7S?tD|q1K}Djf2~$NH*USPR`-{W-%RdjK@&NkO-9|y-%h8!(Ex-% zd%qX0Qk<ba_7*9+R#UtnyuAO`peekZztyvht<N!>T@g^UqDR`Css#F_cB83Koz(UN z(|eN40S(Iw<oD2#qm%l38iqAGnmSR;vo;j96p4)jAq9DsW+REgv&%;q5`+B^O|~u= z*BF_OUDl5AsNP$N&j;!H`%1k2l-z;xs|SxX;tnXfe5pBL*qgO?<rb?`velhR?NgQ( z(+2vu0N&Y+Zdw|^P+(9WOsZt&n?Oz7cpCb66&|$NK{Es3yv^qiJhvTE(5BsY2Yfqt zCtNDkgtKL>pk@FXGl=n4t8<|4v|Kt1%c-drCj{_`b?9LNL|c#}|DQZerDz=mf#*>e zee?+^`aC<AWrzddgp#x^sDM-8wVh4yxYcd{Ir`qq^)}GUpCvY*j>ko4VHq@`4yEd7 z=(rfrW*6dFmJd-Me_yFOfu0dxTjAN+S9dd;c}~+xGQ_{a%RKD{%*Libl9Hjlr{lw< zB#M%%6H;1<yF<)DJXJ>(cL!lrtB%HBY;~V(02(ybpWFtwwIfycNtH7Q;XY}Nf6?mh ze4GF}pD+MA2LU<>K$k}axcgIY=cFrJsnsaefE>7`hs?qbpk@HyMLcrYhckMV#Xg34 z$8e6;oos(QFw{31QZeKodnTEROBO+nDf&CU;u^-k8LyOw#dIHcHWN>p=;t-bTaUgv zB5DQiw7nh~aDgW2k`rU<{)U~XT3<gr;2QM0M4>#)qn-}i?$_<#<CV$*E0pQ%6Pi}z z(6q4AcaW>ANodN@6FTqE*Q~M)2PC2CZ>Wt8^wSdh^=L`;Yrvwp7Eu2lpNXN+K09U{ z&=NwZjZ&bYgNNXg&s)3mjadtfh*5v&Gt&{CDS@{Df!D`gNaho5NsmN(D~`8#F~_-} zR&&=~<zt_~OW5j038uCcy&bf?doFMo5ET8<>tXk<G>&vh2Li7`;z6vI&(At;Hx8*u z&uP1{$&hw9mG+~6y}-l;)<6b7TcCUP#jCyeFZK|gM(Wn`T%<)g&xmChqi}&EvemYD z%eFLr0E0zMTrBdgQap20$GqVX7T^ho@SA5#RUeaZa3>RdS{~Cwrs_uQPxq1$YhmkF z>ZBL*;N||6I@zZr{l3Nokm79G07hxWrwOEf3_9EHo0D?3)n?;M%I{Mk!IzAvh7l)I z9{EdrVfV{Mz&>>FsrIqe=z?D9Xm>X2u1x~Ghabm%x}D_hGaC;%I}feBUXG;6<$YRh zt4rgulyVC^OyROxT$jO%V@vt9>TEh`9dU&{eT^;T81!M?5wE|tq~Z&qDThw}FlV#S zgkkS6l#>-W)Dk_2kQhkylZ|HelM*^pUH{$J3gJdBNN<^p9nocm+fjH6<;ZY0uf5Kw zlYFOzYq)btgM6oh)9VM<f-I=qwVWzIl`L;VrJV299Rn$;7qRi@@pP4*9Jm%=hexJp zBE16-CHbMdwr1jLU`0<eFOBe|B72sSM;m!G4Z5{aW+h#9_iyF4lvyuv-V&P7vO?c2 zcfz_@bN4&@Ha_M!uAV^@L^eDNrHLc3?DU4E)`s|lEoIim&~Yg(*s~&@6<(;JN2?q; z>oHg}f$=nN1l?<%KUFu!G~^Uz9t9r-EUtK_oUlk%c;qjslQ;wF6VF1hR@`RO7Rr4V zVOs!wpQz8Zyi*k2v}R>>dlXN~_vn+I-_b+9D*I8URDw?St&+3|@ZY*A!0O%T2K^|5 zQRvhYEx3Twh3g1!=#B>|B&oO(^$aoL(EOKiMK8i-V7hw+t^`$LrDl}4ZQ{R(t@#pc zNw=eegix;<V6^|M7!Mdu7WP0JPO;)gpjKgk0?^NXWYN!#1}!y8eW$Y&50BR0ruNeD z%aVz^M0fJ!r{H0}w+$}|3oc4(xf`bsKj7JwJhzF5J2Q6=Io!E8zT;vaBF9H)hML&* z=G0oP*YKoL+ZsNzIF2`*GfS=)aQ#m#QOkJ0I<E+n?oBiN9v|y^ja;F|)+F6%1jyGE zc}S2uFQ6w?1_qRn-_&X#qg-o+;ZUtcR}OL50%Hj5B|L{$8Nb%YHfp2`isVf^1rKkz zpx9g=`@w6t-7lj9Qg27)gott4E0`N#NXzLTYUGo765TRQ>_)S0G7dY6i_1BwqquX# zYLz2``k)!P`%sKJj@_s%?@!WFEw}YF%i^^q%Xw*}hkZvgh&~yJ9v@dy&r7+u3jqCb zt?@~+WV%7O>1VhtT^4V0fHK@Uc#L-v6%tK$z$j8NcoTGjXi?oK*b5)%J%M|DmH2>; zU%JKW=y|OTcuAgn_SGWt{|#Hu?n4`R4vk=~SZ>Wv4PhHhPK6ua11Voq9hD0S5cii( ztD0Axgv{YPh-uJ~ASp)6o2(`=+poRnXCMC#=H4q{{%PSG2RdeeJUl^=HR#eMW+J3y z@o4W2e*dC+Fr<$*>-BxKxL~-<+5+~`&^6)rv1=A%ki6q1lW<#1%4ef6bnIkyM{jZ> zTTNjQfg!@PsKO<aMXTBx|6VYk{U;iVpw{+D0_%NmaPKYd31+bsoZ%`wSt=r0_*1^I zWBttbJ2WT{r82=_i;qy|#lieM{n8NgGdUQpcptkUi(RP=a}^KkEtBWb`qbq*kAg`$ zlKIh7N4tJ_z^H%hjymVP)UIaupzdc%E&wTa)N#%;QXU&Q2v)0u#lK6TVifmDXJWlY zsULJZHGEVxDOZu02iSe659(}ZbmKSpnC>wR^D!03$8^gb^R$29V%m5tRi6(J{7){X zzv70wm@Z0Xrf3Ch7QBChi|N5P!In8N=wiB<jX+Is&Qz(Anczv86ZepdDFHsA1GX%h zr*T6aOh0;8&ohl3fhQ$DJ~0$|XhKUmr@NQ#dl&0M?xooW{zv!HbHm(Afl{H!rV$uy zk&WkQm6TfHNoCOT5`%HP(D~E0ZWJi!7>`2Cbji(Tr`uL{dN6R8@o!mpNg-d<CVwVa znkTf6Xs(QXbsq*DHc~T^sqaIo9+U%_#mB@~a5KY(zN^na0&PEj6dOlwVJLKgx2}bL ze%>W!v&hk8yW@ZMI*vbY62~aj*5~cI{jM(M<<0>m2Lv`CLtoi8Gdu$X46GOEr0xv% z$Cr$Ay`qgBc`13OHyfiwJu*jEQ!{+*jQ|ShG8@hoexa#>*0FeXUbx((<fu#YixM{} zORdhnO_Tf-VCs_<$p{+p3c0NSN=0UWNUgBR=P=UFIgGT^EO$;(-)%mhMger592P*w zS|H{I&0g;^b}x|U@<SLf5cql;l)ANo-@?bofb8*OL>nyb`>Ene>ia>U>2!)?C<@Y& zNUIq-H|4R=J1O4(6A(Oho|<HPUKX0Z=8(KX=u5ntK~JLwP@E3A4V{g3+QIzx`}=Ty zOH=VAv$if@TgUs@?tL_M-j}Xd3FA%)P3VM8i$;&&W>J~#5owu^sr!tnr`X3f>(2vX zAy{yA@B%8Q&u}JWcf&i-suJmxQCVXUX{*ax_{iKxqznvl2Zn9qCh}&M@~|1hot!a7 zLyor8D(o>WP;%nAq+WK~<%m5KdIsiipw^(2o2V`)5fB_$p}0>e%TEGBOknrnX^#Sq zEqOb|j=+lbX&mRppt~D+Cn=J3Q&{QSI7HdyDL}Sizb+J)reP#fXqx|`Xc$|U6k9<7 zmvR}WXa3?yGIKo8h(`i8uV1QEbvxhFPf@}QErc#uF+hY(oV|5XaiKYKG_l1!ARLU8 z2d$OjxN<2+J=A*?hlKq3fU?}KR@v~!5dKY1mBz8Bj!;P(kE9skU7&OPdiBt|OsTLt zo9ir!8w04Z)9CKo0n<?z2*t_lJ4cf94!e~ed4m`aSaAg`xk~V@RnS;LS(Gy!<@Rkk zGgQIycFzK9xf&J!ZR<^G4d=4NI$CU4tyS0?>Ib+jBUB;0&}8_Mv@FU=%Mv}Ruysk! zGz4}N@cb>jD!B9<2x`_rr)5dg2=odB9Z#USNl*Z@=wMf51-WnqTL#VYs2SR<bS<yn zuv#`)aJQL-+J!COLp8U;F8p8>re&Bay2Fdotvy_W(1am9pk%YqgiDa2b}_5LnI)w) z@YcAcp`cH#>cYq`QKbr|s%~e8Aiv9zlgn1q-0?!wZ{cNbwrJa8S-1db9QzSX(B0+E zwzc`;1tyG2gQ0%lQ;Q`lE&=`H-_zXoAnmtf1S@TLt&^_6@C4LhKpk|gO}CbdxefJN zmbkyglC5TJt<bhCNl%MUb5?bMt%M(*g?`fX2xFV?rc+mxYz6p*P$7&g`bZfbtIAU| z^tQXOk>+^XRufcSjTX7PumhlxLg*-`pNheakej!TXlmb(zA*MEDKjo%PkUUD6V}b6 zq42t#n}t0iTn+m>XW;T6+d5j<^M-Ix!Pii`l+C_=1NjMq5yt;*fwN7rS~}t`Rp?oC z9W-g$!Y#L5mj?FU#>}mwRbk6*Inx%R$(MY)VqsB7d{l48GeOg><mvF7ewY_DprWd- zcNvOGuO@W`xgkNbF{E5Xnw5-<`vjf^kt<H2FAhv=xP|av&!e8^J_XWA2&rV*cgJM? zu8wuIJEOvr&6WrtQkIM<j<*0$jHs3WIa82R4bT{4Hv(Jy5eLw@Y;k0*G6LhmM$FLj zvE9Tu-E#8)SpSnTTC`k}C1ulEf<2f;0jQ9P&_!Ez+sy`-nDG}t{t=g~BraK=<dW&N za(yC8{9Q16MXh2%PDvoA;KMn1n5Jg5EK0v3D}EJnPKFK~BGwr|0x!Cg2&|`P>PVpe zNd_8^3^WeLw3uV<Nk<0CF~>7&RZa(kP63e?`*&1TseZw<YG(_~__4N><UHMm-fgiU zsw|1`38CBZ;oehR?2ZB28NrDS49{ZJ$ol{`<;}jYO&8bO+lGqkJ={g!>_0NV)(sZ< z=I{R%Z+1Pzq1!$o;)2=qI|iC-+ft_m_Ig>z-i!R*4JFLW{{37s_C<UwJKvfr+pOdH zM*sIVT!tn8_kBa-3*^FdjxI@e(N7+YX1@k(dXxt9?=dbwyqsj=7|>(uM*64%JnJmg z*8y8offMmZxiIDj!u|nZ*XFRh5t9+G;dj9?E2@X9c<OD1HuU)l9TV?&M7Ay}<eMVW zn8x#{9M$E>r3p8V@wC`SVBGrEbMkp>-F3Zxls~hGwxV~pWXP>1IcAc2)VAhM-qMUW zJKWxoKesf;%wqbP7XfjW4)+zpK>;;UxLS=6ZaoWaE}z<^W`NowX3Gyl>%knbnbPC) z<Zsd9{ib42fVOg4g-v~vB|Lc8iI?YTQjR?TDzNC^+%=zlj=?Aabn^$eeFtVkzmk#E zl^Rbqm@f+JXVZ5-Qm#<H7!r~XgXbxsm+9I?*EYH`y1qr%SLuoY`q-ggz(so<-=BbY z=*1$|^?GV9cE5xtLU&y-PK44=5*dC_H;f=1$|Y6%*y7&;FTAqB*cUTGIAZihwX1m` zjY2r?CdJaMzCrOD|8r>kMm;vvkob*e^$-Sc6haTup+4syg{J%9Y3pbs5@DDk5r+4{ zb<2}q!?o#{(DYaq$1Qa9DBCgOV99O-6t+Etu2wg8&P7M7gNH70v}#T{T2(5cZIHv+ zoN~1K#iK4x4kOuswp)n8M7P~U&)r1H-9+KtNXb|X-K-8Cy2#C{yxefJdV~Cx5T1`6 zOg&p;ze_!vV*gEUR``tE8~btUlR2isbHn+hn-xV!ME@78i*rB-uO7tMO92>Tx06p4 zKB7<5!8dfDDEc%%6CIPVWwBWX+AD^mWd_<jLQ6D=WfDSm5kkW9Hy*LvBrb0_9}pKc zoL?>GH=JKDgg)ex?I91VRs?gdmc}eJ64Aekx-0f;K3RWz?Ag?FLF@^5-fEe9y)?Pu zVG}x6Ma@RuhE(1)05jJ?5Hod%=MX|e*D4)iDMG9gn#%BgztA)f@81`iuE)EiJ6g@% zAT(Wp_Z33ZWL)gtjr?#K^=&wWBg`Z0Ipkrj*N{23HH3UuDNa?xIPSv4Z)|)8R(GLZ zEczPl!qag>Areb~5EuvrSp}W3_K;J}ilj5vE70-c5^}}@)+sE<V0h(1=ta=B8x9q; zhPGVL58ms1AM=r46*gD*tNIx?jN(7!hJKc^20y9Pt6&C7Y5NWDDjm>{Z6D+WjQ=S# z4I~|`bWej$a)0Qg_??H13dP<^`btd&Z58Uhxu{ua!q#E^<LIMASihklu^1gY^fSW7 z4~v9DIK(~7b%)^yB~EE2u|+3QM{ix+za^x{5A84#^^AJmVjBltqObtS?$lkPX6a?8 zT%r(K%zXxb+hRyl@Ml}}o{!D2rhKA0sYW(*xR2#o4WFn|@}-+*HhiKiR>LQ1FPWP6 ziyP4=>KgKi`jUL2XmsnO&?`@#QLBxfLC>f=<q&?gs&rG=^s~)Z>6-1`cwCw==<ZR- zQHFaI$;I?R8pY<Nr6^9QU3zJ{lN2T-ouoR4I!V#5kPOWgrXOiB9Q-I4Z0K2%ugE^= zJcD;UJQfd1BOA`&C*7kbQ9t=h=ox)z@ZLyrh5V{YRYH*Ts~YnqNO$6Bki+{fB)zMs zyxmxy?p>A4KFqtS;ez2^)igL#H4@k`#55{K=K@H5mOwDaaL+1i>M+kL%tfA6gl?E; zRl@<SXUefE32=#H74@g?ScUEI#+s?inbGA+80YBtRpeOp0Eh_~CQU1bI#wl_1mSe| zD&ifzA9eStnE=0)0IbVeLKvC1cuVL5%7yb28l|zEA>LKF!@R2y{r~7)l>{0eI<|QX zc~{wcJ4Szq&Q-}48e?jRbJcM6NcvV0*BQQ5L;1q+t)eM4ES=FO8I1(gP{%54w(eN< z^p%EVmChO|$0{U*?pWnMLta(MF{MyuWj77?yUDBS?S_(K(XWvNQ0dxYDt5)^-&70f z<Sx}Nohc#~d^ZuFKE%CrQ2NC)ZfckV%B72baYtuYDoH+o;}8grAqe#0hBTZF{eT-i znR;Jc9UqU|oyJ>H+=e?IBlWiey$9*M7tPv5gt*Ai;GQLnHffrgh))_i!R#uyg*{OQ z<T-$E8o3O8su5|`1E}Gt+X<Ko0WnLH(#*3-xq@C+*XWN{Kz;zriI3A!gQi-13%lVW z<bReAprGXR{t=~6WYdG{Zk-fQUm#~BEsPJIU^@N0Wax2xkz35{9-krHrRz&5RlG*q z{rJT-r94zkm3*NP7gt#+{dfFwnbbioK3>lN1H6;kM$lEX=b=}X+c?u`lz64ls7t6; z?Y@X=@&S}Y<Y<KtY|=C6Xhmt2D$Vd)En7$pXo`rV)$lf|IgXdDM`Lwozfp141$F9R zg5}5LN;Pkji!0se;=TueZ^GZ$dKWhp{sQp#G<=h%^`jLtxOVDeKY78}6|=JiuMDv* z;}w2LqkUeFmdBkowykmb9Bo_FdvO!X!>)S;5kQ6smj-PMW9;IB3#koN58AbtzcR$G z?cA?hny>F4YS%t-v0c000Q*@AjGg(#zZRu!>I?tKYV8>qVzn+B#JptxKeSqZy+3KS z245JA-2RVEw}na5Z8n&0!d|r9JZhB9JOCxQc{5mVm6Cwwn>{!m(#Idbx}yDd^qcIr z;h`+Re#<rNH)xSsZwrTCCYMk08sCn7drIineS<+9^necJA*_eHAjdpCWCAk-2eJA4 zlA$cfek<OWvfn~GkqskBkG@y?2JN@MW?>*Y=YaT*uy?FN?uNQ2vqFA_%5Dm<@g%wp z);d2FntnmiEGjMMlmqBcSVB33Z5wn8D;dn<kg%s=S7yq_QVG|0BbmaUrsAWJ9^{*u zwOV(f#@`vms4{XQL-;|qZgow3;v2$S>>5Ic6GJFNK_faTyam}^<!E=!Dryl6i<-q; znn*DsW#(=QRk3$SaElj*1h@FF;lV8)#~kz;3sDRTFpr8<iyXBkJEw#ELItIEXlx7e zWWJ4Tq>er#5QSkQ(WjCju`RAn;+-}OZ?}OrXGmxZ^YG9XZwv`-@g=V}JaLn_3`1ME z218rCH9WM%0Sav~f3-Bya&9oRMeM(a1hx3dkf0Wi@p@2$#P!m+T6wd1mNW{KhQXi~ zvF{n-Ea>Y@{c9?mMeLSjAd9kOAdAvuAd6YaKo&*GKo(Pzfh@*RAPaJ1Rmpw%)?^@y zqlT|B`#p@}%2-#1(QGiDt|RUHwWRTcapo|#97eKe7H@|?9*mICXBf%i(9lR0#MLU1 z*a#sIJ}?-_;=o`ai%`3uJT^R(#U~eqvS{Z9DR+ST=1>;oYmQ<#?KLvCqNz|8)b;Ah ze%<?CXgY_M*71K5%0d$!8<`4akwKv>P<-g2EUqfJB$UMgBb3F7q<Qu!hOz+D>`AOQ z%MhBnF_uMhGL{80<j`0aM~6tI;jt`U)?KLa_RnHj)J`4}%K}I8{}{_+a7_G5u`H7O zVZ^dH+V_pIEN+`LESAMz$m~9HNi2&mFX*u>9zeUT@Zwk&ph04}cMpkU;Y(E(dXarF zQMc4KkfnBUAPY+>kcFj{gc?=5cW59B#GjSoBHcoJ2EAB6Q5PmuZ-49q-1Hm;(&IkK zKO}U)&Ca$>yb-FPt?`^5ydwOPZeH#EBl^T5YA~;!@B4<}6^UWND=db26-@@OSeOc4 zL7koqUUA3>TtUqr7P!Jc)V$iB3|#T&q<NJJTtVpk7X_}silTjWd?>^a`)dBMz!i)2 z@#>F_8^Gptlgz;RGXv?O2d;RypIE%<r%C(jZSosNs9}LCuw>n`+IUgm3IrVzxFU?x zP|S~}RVXP>g%G|Ej%{(G{@~K-gJ#x3GP4$fnKjn<2Bb}zS_EYz|DOqFp$9bhXTdCx zAiB*}IPsf<Ss-ElrC=7-gTXAk-yF<hE(yL%gIOd8cs%nK3TBaRX$Eub%*C-Ra6tV( ziDhx3FB!`MC+cJ@i^0HR$ygRRDGdf@xZ?Mh#<IXX$ygTe_gx&z;s<Aw-FtB?3oK=L zEQ_|^T^h@R1lu>ovKS(WFOFpq%l`+lEU0>VEQ>pS_kSmrh2Ad8xW}-tE{$bD4Z4J6 zXI{EAmIX>9GO_T1y`tiDM`@KRshKK`3eZLzJxWN|MO%Q4j>aB5i#jOk3!u7FNQ?|2 zWIFqe>h47WEW!#}UE!O!->WX}_lk@A3;emZy0~rd_cZ){0)HRCx0G+aT4e{`%y2|v z=_Wc?$VuZ_`>#`K_T;BQcfgrF_63JA!ttkj0OFT}5dA++LSP76KdAxEPuJx!c@U-p zU}(wK_$AOQR6Q7@3r*zjsLT1H4~4zfnHT+bW?S5>woweSi+($^#e6gVa%MaJa%P+O zy0G`Cu(!GBQ2Yk3552AfCOdkT_LUiqKhi|j`=b7Xbb5UTef?YGb~!SDUj}~(wEE(o z0u@4djKHAHZ(dBb%A9I>6YV79uYi7f5EDuy`?_5A$ROxPPtjIk4OmD2TYe^V#_*pB zrC-+_{KaH9|8Al@^&5%knv5~5@eFalnvVX!9<>zr>hXsRJzlT&5uTuriVK2f5Bte6 z-KDf25{fk`;R$Nm^#}t(@2W5ueIE9!GF>;@kVjGcol$9z@!NZJ(7U9nU+mGOezs=| zpo+&;4Jv@YBj9(SRW}|@w(T71@rhE=wAjZ=a2SQfvgGeOec=sS7yFt07@d--R3_1n z4IG!~u<yM(uJ~*pGeJHK8eAFPU=pVW&3^XryHt7foL=Dbdsx%6$>_f~!E6?uwF!SY zN_t%*6l@ICPo9R4oP0UgnpTi<H?Le-QytalM-Eum4-g6;X;tE6(%a<cPmp_!Ps`(R zZKBK3?mUef$JNo=k{pqKhPs5862`sDThtO>VTS3a`)Km$dp6{?V&2x8mDN$N&(EHj zqHiOAG5m)r#L3U|;&^Qx&+dWBG}hXB`*5?qQc1^{mu1!=Y@l+Db13NQRq_VAMJiNX zxupeZ|1nO?E4>VFIi*wamQl*No-s*j>?8WMWef%ynDn&E2+*DMxA(ELhjmX>p=l;g zMoaI2v8y)wOY9)3BDU~;JeBHft^2yMnVy>0TJ@iErWw-1hmMh`Ig7*Te41p#Uw3gt zT(OgsVA6fWU?6$b>N}OCcY=Z}pX0&Qh16hSrMl@3c~uDyleMk!9OB>jSCvyYttx$t z<04XK>;J{xy8uL0ZU5tYV1QB4!PG+292FJA6vG#e<RFX!fubmAJ`iOP5P^mnAKesm zpcqF?Z{6PAGOg_Ky|<UyQ$fr}S!#C6%*smZ(0CJ*1QXr=_1Wi~VL&kJcE8{6@Bh6A z*6e-ubFIDCUi)$OUVB0&wraaqMy*%hSK&QBYJ>WYcIuuRb+7u4TNmAvqAJvP?4EPq z5_OOIjy*Q+AyI!;-`C*%hN!JR%6kRgJ4L;U_W*jwjuE~jGgrqyn(17b=?(yaQ}8l( z-Yn%FCsnV3pVWh7B{c=f(-d&alGw5|R&EG);D~pH&0m8#k^KkJ3fdnK>@w3Cg*IHq zZSTt|q_`terFe=^f(9@H4M3A39s5u;0G#R@mr6}_Qcv~R(nOlt`ksaEj=fwYAYE4m z#+C&84{VM7k)d>^6rr*&WB*MX)YD~CTuDJG5tXUV0M}2h>4AH&7YBEH+1g=|&8*Wp zyO203xuvfim(-XWU+JsAuW|yma*pKzB<FN%{L6jDJEsS(xk3M0b@BwMmvX$Z3lG3j z6!m{efouG^Ij~$afij@%N<X*VEbcpdw&a4r=2vo|o&9l2NxOSIXIN<?GWDf343o>T z2Ag(lJG=h`_t{DAZ-vJ}MY<D$W*NaCu8H_O1{%#LM3>w$+jbt<4!{P&-s0ZOz={c) zlF@zbT34;RBQ)X?8AJrpaHxhlfotML=03B>6G8jzXFv<ppl!OqQAa4;I<}o{V%g`m zDE;=zWD49*8m*kH?6}6C$difV0Zo*;J4)S@Aim^5Ueg07)TCmR`H?MPw+};!Awp11 zCvkazZtg6Ebr=}ec->ov+ruJzfdz>JAAFm0`Gj5Y7@V*x@72?Yo%n$yg*M`@1m}ol zRMRB-v647C*zW7JH@c=bVh2=8O2vvgsXcBbFyl~56y-XuC;=O)&hw)2u7;}beJZqi zW0iGevZUWHF2XaOwGC3D`(8>*MFRUSstLqx!aWL3ai>B=rQ28DW$lH!Dq4?3oHKqY z-%yhk8KH>Qa7DC++iTNYQ9)4lo|+m~oqDLjl$?^9>NMBM-P&&3=wzwneQ_y9BfZ_2 znu=bH+v_M4dNp*YjZ*v7@;##|iby8zY^T`Odu4g*>!zS+wjDodtsb8himy^%k3*=q zD~D2UG{9P_!_pv_HYIJxr0+~`y+tC08~4~cmQ~x@)gRo1D;3<9RFom_W&m#X#t3@@ z@75Ez)yl`PVFrtLaV2lJu7ZZT2y<;jt+C!7uzfP6><%hBYY%ko?$~)EV^zn4I@CIa z%JKS7oFkBn*M9y}!-;XunS)AFUA4|R$I9nia?WYkW8N<xjyPy*=bZELZZi*}$+r1p z@fCj#uP`4=mVStlRnwP!>Cf2fnDv*LgY4JTn}e7sfZ<5)F`Kp|iI=#{L5}0TVb!?i ztP*<#N^CK4w$M16K}vcO8)+S#xD@`Ry7%g?3Jj=fz7$*Z>fF~8ZX{rkETd#k$y&!v zZeQp0I+wX?ZqUrID%&W68wq+51UV#={i(?5b)wmxa^JAWjEf<dyBk@A){H>1h`E`s za}M>SL2pvRtl!Qzu<JQZC@M2n6-U@Ysd0o*^;{Q)d7*7u6r*J9H|;UYx;LhgMJtvu z$RaWrAI1DA;P5C$B|=em+6GCBHmVd(+Ne^vd98d05Y+)i73#49$Rg%*=Ab?1S}Zw~ z?Xg|!>Nta1O#PvLNx`N2mIWx6MbH%SnpH_qJEdKfREKL097(6N&kCj1zU@}jZZX)} z$^JTbhb=+20Qn_IiYGztc%M5lj_V^GdP019aNZ!j`UFO^!5GaxOI8=FnJ3E`S5(Lu zlh~77yGK^GWtS?<`>9EtqE>|gDmA?0lv*Ll2At`w?6=pr*B(Yyrm^*3WvRo6SwyE; z6!-Foqg*4~`jm4?-B@aT0!McA0yApoHCb99a$G&mXpjBXG;mP==f}mV{^QD3K<DiL zCB6MP=!VLE_Y?A2$RzGL3z@`q2t88z0{rOjI*1)#o?AMxp?>BXV(xvK@<bo)W&Yx} zK%bBjLrHnk#S+WKxbh@!FFsdQqA%S-+(QNAV=K({Nul&VKcO6JG+wZGmyCqds?iwj z*pkWf49zZTxY)UQgkcx+5xA~9s19~}WI0wG>0$5U+Vjv<_?(b;ClcrG6r0IMN_U<R zVaesib7oHYhf@AK&_v%5SDmZ~vg=*uWBvBn{n{l3xqaK2gNO<H?J-u_Vxz!Zwm~$4 zB%^N>F5D1rKJuHEiP5E&*>BiwzNB#nQy29*Zd(yt(4O3U+*<l&O5@d+z92La5*{Y! ziWB;>$K<WRLNN5JibA`VbzwvaDRH~8N?*2IIS{-@e^<U(`Se*rQ!MS0Fh_%*zHAnj zM7NtSMesO%=?&Ppqc59?bu#k_ehJ@xYOcX~b)U2}DvE={#lk{|owogH!ePg7<$#E> zT3@;jt7pCy<`XE#!3UI5)0Zw08)$uOxx^RxGAvcBT5;m^TRWJ22B;Q?I}`*z5F2QG z^mpZmFGO1X-Dx-`F6TZ~&0SxLlY=q_QL<ydos?4<#+Wq$qT4;uA38OnF42#@2ZgJ- z4{e~sV~PlI+8b)piLfXTX}>-#*N4LV`5O<;JFztY#ea*)Wtr^bewc~{nCJS=a#e@z zNv?>E64d7Qmv^w@n91D~)wjX;Rq+=jDu}NyyUMd7Rui&DcXQIElCO)yb!~;fzWt!8 znxM+B14_OrZvS4%ii@mVXYV7V`Spm^%I%uk5}l!$Bv~mW$udu~Rb@%8FGLBkPJc)k z>`v$1AgV~Bc=n@f9~JGk^?XP&jFd{(;M>ebEVGPsedtAnkRVQNCmOxfO#g**apYfm z<SrTq=XT95#FyoLk0{4WaUoi;u^xBO?Xh=}vL8{U{Pw%V!oW<D{WKU}p%Wo&Uq-Iz zX`=?$wSO@dVvk&3X|lcNT+!%#%nUr}+AYm)P%b_{?ybzoMRIcw4gRP{S@VccYee}+ zt~g-}ja+eDE?Ljr;tD-Y6dPQgFUdmlg!i@2QXlSpxJ~G~&<dRZ7>MhzSkX<tO#}U` z9kYx+b{!P_?euYfM~?b!?Zc{?_BgAGQkCn$G&VV%=&`<4t451k!A?hfq}&S@7p%W; zr)y`k4bq+|ZroVkz4~p@T~ku^+iv%d_$aL+Ru>FST&@*?uDC$sd22h@44v<1#tYW= z;~kZu#!sx07{s;MZ!HMEl)av6PQL49Bb6jecAU~RUGUNn@ouu`HW#NwqC$r)8#kFr zH;S@>Ubbn3*G=MfGubCu_~^t}A5%z`)VrQ~0*!jnm)D&z@}()_zOaaoR^iUHN<lfU z3sZtadq>L%z+b+NV>%>ycIXyy|F?AN7RKhNiMG5y<YsZTNnB&ZUTK<L+#eF;N)Das zN)DMSb%l`HrMv9<5L}898xk}<nF=a=OUt`ZNLGYs<+7nrp|}eLx1ey_7{{F<gSpM$ zXbTy<<fe+@OI(4kI1@txaSh6yuTbA`oqP{U>?=Aco+*}e^ts!BwnD9P5lTd5rc1o9 zTCN2S_8`}kK-bj3$SFFzpCGL|Wy)K%H_<q`gJN+ZuUmD_7_D<r;PffUGiJ`2Icuku z@q^87G}cQWKA`Tp05f`BWPFYEipNXRIXHKJdAZckZ>L7&IBuHLd_?b2pAv{|<!Sn| zk>cZ(yM)@=Tk78OA*s^Q66KIy-&E<tlICahwwHLH(R2H_s@Ir8LW;-w=u0K6%u0`y zD5w1>p$%RMm9<F7-bO+ied*&Op=n+T#d#$(wnajNFH7j0Nn2u@BV<!ut{>V-vNU6u zy3aL0Xn=ChiQthdYFCEyN!n%Cxct{7M%9(;T%DAogPo`dqv}$VrG$6I5<H=?ccO7c z?VSg+sE*tQ#{})AWgT6eu)^uoIQ)Iw9ii8WBK()oKkZoxUv0%wxHtsQ`8Fo5vWLDb z=iRW=)XTv`t>4kyOP+J>)kN%z^b4Wcul~L&R}~tRXfIXZdRxdEjiU+*Q76d`_S52G zo{G&OUWQBaCckuPrSCyUEsxmgsG`BPMzhK$xuP2>=FO!iY$4Cmn3UPNKn*Lm`M5u* zh~~&8tg5;q`jW?X(PL{=Y@X$M`KB0}Mt6&x6J!e-eFL=F>S);Jhs$tv5tW!OD2wb> zMPiXXaz<DC4RRoV{cD{ll()89XzF?&6{Ua8KUsoaM)8$>1y<+FF^qAX*R1RxnG|%V zA>yORIn+PxoJtiuB{jDlFVxCMyviymG&RDcAft$4HH@CCA?aI!7s^aunkA&1SYEHW z4Sm$Y`_yx2-pFA2%P1tA$f**C%qZzxP4nFJWn=IW=S@XBQqwB<Kjc;Lrq(C)i`7co ztk_0CJw;Nj%ZWpD`fb|i{+Nt5TlwEt?MYE5v>f4f%clMz72>i(r9G4?1-C*4MtoF% z#&rY|lFV9Hb$uqe?{)2l>3hgMk?ga{1;=<O%%Pe?pC9_+(2)-N>~-~LU9}XdD}^9o zTK}EzA!{cig<Gn#>Wvh3NxMm5<Z=D9kggG*lTAmq!1|;0M;u3)>pj<D*JsFQ?#>`~ zxtwuL50<ZpKfJOxjUZg_#g7iQ=}gA=?dKf(xNB!vRh)6>3XREW4yHML<eU(D2eD4< zGHIRW5Z8!svEUmyCp0C6I)17Ju#@&(h*heO5Lp{nEloqPk~@{0SH{R{&g*GVYaLY) zJ5CpcIr7pj(rr~#?YoHadhTKS_fy;NoGgtff@;5NTf05%i!X@1Ds@hC9p>0|k#p*7 zFQkc?AI2LPdW6V|?~bE&jc5>GDC7X5#y=6CDP%=?Vp?8CqmMx@vjL7TF!`Rvdp604 z_w<e^j`q1$G#D?e9PfqvNFZ;lT6(SYL?vO~XuM#{-V*@K9eZ@{9?8-&YfI$iJ7Ou| zIBsvCENZ=Sp?1_aB_(o>!LCh7ar<+-L7e<=NJ{}_-LOVQ6r@Q8yO?y=N!QuMTG-4A z%L(+^NsSR7NN+Bd<(SIQIxzHi1<-{MO6?XYDd{#1(vqc<V%v8}YwSH`idUZmMiE@l z)0+!=HgSOgT+mf;K~NJH^aK|W3t$bdc;(ntycP$jhkMuk+#Q$KedO(z72lDv&uwp3 zXvZbzvJ|QPJ~0D9`G68|Ku=MOouMxwMGWBY6lY2sQluY>)WSODDXfaTRtoEld=yrQ zD6AI6<Q4*>XKTfjs;Jal;EN@Ft#p1d^v3I?Gr|m%YJ?dieJ#wc(r3a9mJY!jDZM9N z4bo0whKOq9OG2fWg*jMyPMG1+lfoP>JuJ+g(w~JnT3RE_vC`_rz|h2(n53n`DOM^F zW}LK8n2A!lFq5S;VNR1K3v-4vQJAx(al)J@jS^<2G*p<`(m-M6Nxg+xC|xH^o1_=! zGATfqcS<}@M&UIqI7w&djlYHuDbjbs3a!`D=fb)c)`P-|&ok1y!iv4K(woBiAgq5E z)<<F8EUZt!`jD`0hIO5=Zh_SytU{CUGGVQRb+NFbvy$cuD`dKqCall`DJ7~XV`*Qy zMR;JoC`AY>CgW15upWc8m#{+0NY@A}zDG#`!ivF_)VK)NQ-XeB6<UVB6V_Tmzp&N` z`h~SY&@Ze)_1c@lisdos@4|{zcWJY*Lg|I{&>{t8=zfq~!XpUQ5@GEMt6f-wVa*j* zYzvgqh1CG-G+_;aHC|XlVZB9I2g4d6tl_YR3hQuKdkHHp{Fbf}*0Hb#2&)O!##~rq zVLc<Paj<@utDu~Sw<E$M8P*SkbsDTYg>?q3+k|yCtS<=bJXkl1M1tow)9qxN>GC)F z2_4k|$<oSPp>$Sj^c$<V<jF2HHa96Qx5+NyM!#&uB~G|38EzbrH$+y!JQ6O=Gt!qr zySi&apwTZ@i5DcN6lC;^Q(P|P2$!x#zeL65l<X30^h;J;j>;}QjegS<m%Xx!!RR+b zad}O42{HQ3R$MmAE~AZpg^J6cWtXu=KbzuGBD<K3e#;b>eAy+|=y#{$GFx_uGy0V% zE(yYANuqH?sY3ne9GQC0cm{=_e7&QG=I<5pY>iKQC~VrP(Lb*Zai5_wqA>%x)981N zzwp|v(Qnd*6#eL`a_lmwJHcJkb*X9i4kat^=_1{|MqS|e!Y&p#BIJwgwdFqUZ(Kix z)vnQ%2l|^z#TE8vDN5(vK1`ajrs+WRZuutl=I5ccxV*A{rx=2db@`0Z*oP2O1IA=% z?ETxHu=T2vg2qJq*t?Ts=P?<+_8=OCJ9cW@pRj-AqETh_`s*tX9d}e}s_S=(8F26# zAEK3#`JM=*NXP1TV%6TZIm?Ar3-b7oTz4b=cPagM992H<a<B4B!+4y=y{;8HSC#9K z?{4FzB|2k+e)SWS{HpU1nAV=^N$oCasgCdUj_>rwOLNohel^PnGgiMx(9*T4#>erE zuj9PVI_T_EJmfF6uVWj)oA7khXsv;c<G#Loc}SU-+<8a^d&9)mlUYiy>$}pAi6E1p z-=Q`B<pFCZXv!yQ)=coBWXmV|IClFw_K3vydlIKQ^@J6nG>+Xqjy*mI^|U9{dN~YL zIuP8Wfp58Y8sC%gF~NR<T?B6vyh`u_!Di49yY7TD_MQ`DN@Pl8o+9%UnYCorl37P) z9hnVeHjvp!W@CjGpVpagt#5-`8XQFH2)Yu4zJc#qy1D85s@DTUZ@Wyr;sT!bE)gmY zHEe}#SYySVk&Q|ngf!Jb$BSp5YNUMEzKMM6KckW!`=+2*t&{U|orIxI*1sX@1cBE- zLH1n)rwG<e@a=b~e4?-8M^SpGMd`ill{9cu!Zg*@j}%hWLb+GS^<Lp9P*a`#=<7HQ zl=Z9eCAfLoCNkm(k_l!I%p=GqC?s&aDe9&tm32=tgUJjgvn!ch$qXWsra0>Y$)w54 zIvtrhGPPuCo7at8Mvv0(W`b7<_P*&=HxqmbYp=Qx@6GGM+pSsMwa|BUb@Ix^g`f&l z1=X2~30#&`xw`BZu_!zIE}DQo^(2WU(=r-cUMqCSA9T}n#^kliCVer8dZ#1`Tb&*L zmPuJX*c5JzW=(2Shtu1p(fApQACu9eMHPeTj*0D}S!!gm)~0uzS#&PYm!xTBpmDeL z)S`WXzDc^d#;PJec>7?jiH{g-RQ0Yxo$^88T3+VSPk2NeQR+~9tF4=>I@bpuH2ccu z0BmHp$4E0b;u8@C#PLl`rO^Rz2Z|f<<DSl!&UPJ^=7`-@g*unNdy1>tA?a;5J0yQw zpE%n!(!o;Cj<x=UG{t^nJ8;^FANRG}LFh*OxZ7_BsT=XL$9zJb6bQtf=Hn5Sjupo> z`qEFt1njsyAlFwK{tV}l<`d|czIqWV?y*(EaY1KmPZZhw92d0q>s^!QIg>tiPMk;X z_FG(&Go48X_e{*hm=Z37T^&tSoX(_Uu8y%}@fEL;Nyk=ncO@O80(GU$cE)zraq}_v z7t*zli$lX+E8<9_H(#2W!lxgf>F!R2=~{8bvEl?&F*B(UY96YEUvsoNyUZ$1{x0)D z=fup2N@-N7s<9bN5ZA2MHzhW`imi6*#VsI;LTXtZH56Po(iE!Jcvim}dz4(4!Y))y z)H)ipR(pzc|3Gp%Lhmz;M~dQJ7s_vs6#2NSeXEVtR(&$|Cro}`(dT__MNAO6-XdHh z6<6EPD(;h9G5mFfYenYNHll>a_)newj@{Z+{^=%xj9Dk{q)_%W<Z5#b(o@d~t%|j& zUhzfXvU7I7+~65=s~rOvwzlO4X4isGkFSOXfG6<<vQaFbpy0hPqEIy@Hd4T9>C-3G z0z}wtT>i#Ue`IDHdVFR67SjBZWMVCsIgqBSl**{bWGyw0sAMW_;yTH`BKy^LLOHb9 zA%Z)3FnV@H3AO%P+KHQKu$Cb7unEoi$^4V-2jQ^^iSSSHUMk+FdET?d`waDc(>$`_ zh(UI;oFd$2qbcCWl_bg4ZLG3QjO>W8wsFa*bXZ*t&_`azZG&mb8!GlscCSb}HYFEJ zR8w*_$^0;`bvtKn(Ae9pN(y4kz}yiVfs4^B*#;%c3kv+8(|ihXH_lLe^s<i*pUvUP zM+kc079=LRC-7wqp5ok^=l-KPp1HEDkI|!hv75BFL><1}kSFg#Adbc@b6E7jx%@9& zpJX}4Fq=P(k|x{hh^p_y4&l3xQ`?>fN%;Ij1G^t1Dk-_06hT^2(loStP8~Cf1B0?6 z$dBwnk<*1DNbz09dn{@6AhThS7)yR3?^w=q`nV5zVddlg*tHY4>ITyQ^Nfs4Ck2^- zgp9kd>rB{B`fY*!Sx#kdtj&M2&G%Nj8=FbR0%{r8mn|17kVq)Vl@yqpoaKy7xHO{% z*M3nZ(hJ2NN@_5TX}a%k*$s~KJ}Uzq=Y8#6H`We;7!ZcDqx>S>UhEOCyK@zNn4Wvb z?H%xW?b><NZst@g8?8GE{b6&2#B?X6xx>YEfvM!0%ham^2cJ=HvzQy{hsy$Orc^0n zv#bucm*dj$CGV$7{qQdRw6aMFDr!{vdL?#csh>hOtVEObcYh=8Yv#UOc6aS@zo*cO zU1Y}hmo!~(7*wQ}?(<6B9j*U+Qc!A|>rCVvovj0?sOl>&%L49_j>~%(iEX+Xzbpz0 zJDZxze7{PO+HDgHKscVOs@Ht|L=P&ipl;3YOqZ`>qn@#B_(_~*7%hIo={IskkXV`g zn?H7<+Ivg-l~4jI{k+0c_Lv)?e{}gP+5%mdQYw1NCw6@u-}kQi-hWNv=n$WM2hTen z3!ya2E5^}p$$1}}U&(omeIPMi{UPiI>?OUlLOut4e^c`AY|d+XQQGHXM{5l>JuXVX zH5p60iZ3Cl(wdX<#TBIwK_`Xpx!3@0iN16#t`zI^Foq?05&I(Jx>l4KD4_nns$`ch zA0FU3!@CW~mactJdp?gs8_(DRM+?;SrO7@*;Tm}1F;OHUGWD}%gHb5vApKqa{BUl? zbP|dc5UI3MU$&5B<eCnytsx?C$@%u!4pr9kBtDA!?r|&|&1~J)Bo8m2URR#<vt!<9 z2JL5c$?jYA+pChL4<wn*#<(hwXW09`NkKG<GVayi^_199QBf9q5*jI_DUzsIv3KCl zTE=3yK*W80t|mnelq}uk6$Y9PG1klVt4OLN;aE|DFjJEGR*<E{mX}cF?Z{D3jmRnl zwaKZ#(NzYbT#SlNR+^6zZ;w-WBC@Lj+~Qm1ECcUEZbd}>+H*K9_YUaC89L^bCbrcy zCA!;O*Rff`FNuf<snM@}5o?BJNS}+;Yp_#itj4At>tpYu-xd+E&vA)Lx(cf31TsF( zt)S0Yft;0qQ@L=H-s(*aC(9@bNnaL^d_<x?3ZAw>xgBRDJB7cru36xo0*}!G4`4%c z@JNxLe)ai_gq9B47dx%#B8MrTrXTL=<c!sit~pKI6tNGiIU20FkzPyQ0nadJg`6~2 z=VVTcOd{U82u^-K;#`<ugo?5_`ZXp66%AO^B_`VW7%Hvgm-Zy$1RlFtN7IDzAk&0Z zmv|SNBLBlxD@O^dMR|wHbtqg~uf`Z;C&Fn2>c-vrwMMvAhY5jTgmyeE-_`W@pzfnD z`=B|}aX4N36v~5emoW#+BHOt@K1RQ-i|?1dAIkVU2>%|Mt-wFG<E$)auuC}m?$&HY z67p}BGst)Ms&`<$1j=6jt(4_xE~hL*(}J>zpzKx=Qf}*mRg&yND+gW`!o0{juaISQ z{Y5bQwrezk>wP~2X`N)E+9YQS2{dbE#JSYe?CBkGHn-MI4YHyF$%a)4JwwT^s^nBf z^`mH+M~q+UOCL~Lk)WN(*qvNaIs%bfD*#)d?^Xmfep&p!J5cU{WY#8^*)Bn7m_qbu zwKzK9Yv4G-1u>y{ly5}{K76z%*=}-~I-ZIZOf2FyPv0T>5N6MFyd%QcZp!tW8!4Kg zy{oissps4Oj-}9;=pLsu&+4T()G|z^1;X4!^F4uM$><*96G@1)RK)Lz_{B?QWe$4@ z0hd4|Of-sF@(rzXrM)TQPLH7MpsqBszH6zMa*7x^t`xfTipnW^D#0{OSf|dvI^`5) zS#2q&$ZzmcPVvkVPs)E^(x!3>{Zdsq#Ro`MD3SzCFE8=X)J2-T#G96l&1tztifYQ~ z&lyC+(Kc!D7c_jBB2`(uXz(W*hNVccaHC1;=rdwBXa}Y0i5RWJa)OD8V0Y<TELJ9+ zaO$Mpb`Msq5H@m&N?0Aztv(kWXri(3avigU@ObUA4zAASKGYMuR_PAZZ^!xdKW$dF zGuVCG`B!uQtJZ+{!o`*v5Vu^sk_JSfF;UilSV@c|U9<@e8W$A}8b8`R<CK_rI8yk! zs>tQli%l9dGQ2b(n&<u-H6WgYgitgf3Vk)st+GBNSs@|x*)-`ve7U{QIpwVd(b-%J zVsi~06-Y$Z(4q6z&;e&?=s1nNoB=bvG$VfWsjQ<z){Q9iCaNX6*Q<<cJ!M?;eUom) zCLJAdaf*(PeL_bEg8#vq5ow;{{LPvX9Zw16Uo{bJt42h+UA<Hz>e^O~_|G;}BmSeg zYQ)`bt44g@OEu!-UaArAy$aQc1;45qF-cL4XjW7sMk%ThM<}WhZ&Fkv_E%IR_7ZA3 zgmx>(Q$q1mg-~uJD@T0p3o+DfT{+^#3(%P4r5y1uEtMmFjzjftc_~NSCbTq6{}ttk zTU6zU;6yLwh$;U)$`LPIg>uAws`ibZUdj<)QIsP}zf(CPZqV~6N8EhzYLz1{zf3ve zSXsx$h^tbLI9$urs;>!csU7jh&#(=+v#K3&q|ndOSJjTlRV^E@Fj+g|GHBVjQE1ut zwxxDNS;qZ}W<<+pLS;^A!mn#aR14_pH6wljy&IQY6B}G<jWz9BYDQF)1h%3e5gN+= zFx7|`{^F$?@rjGgRU;y&t5=OE+9?-$O)poC_~6Cgsv1!wcO}(`YJgu?jkrrtBk-v} z$E~PEv?*#4XQ^sCwtJ~XM7TE8B90Jh5fPwmwTPX))FNK@`_&@0rl+HAy@*Qb{YJ%z zWml~jvG@oP0wo<XV|bKwpeTM_G2((I#fUR5Q;cYe7rHkT&4@q!e$9wzhyO;+h!0-8 zf@Z`!eg+1uXhy`yuQknx!JFICjEH=iYewwVBJnF}Mue6_p&3yqLsT^*LTRbcj2Q3d zr5O>f(2Q8#64NHlh_`P-)=2Ae&4`a)Y^fPBW>Z_55d~3~m0E0Fi&Aq46^cS_2P~3r z943vz?Q+eCdbz<yoLh6SCi7>HZcJ$HfF(d*R#DgL5W9u0#81tN){ff_m{{5eG%X=G zNigg~6N@5n5NsvC%kTfuhV8w+vi@Mdy{=vTSuG7~_gdS#s%GBjVoq&H^-T~0Wz1cp zZ2VK!O`YZjmpLdcl_nh0;2DI{0Y1~X$KF{wl&wyBTzg!fx)a1k;>IcBe5}y8hz*x} z>|NB`4s7j`rRr?Ve-E^+YLPOVPK55moomUM{k@tkA27r{tjCp&_dTWTY<bR_)PzH| z>##f@Y}$q2LozAh`g_7jchEe?eMp>WsTeC}8LnDoUP!Zo#9>;;*EF|HmYz*(F}Lm6 z?AoSJ<=w+h^D*c2<L>TK^}?nr3l_?!AO*_JCxgda(~nC72$^c6aWc(wbcXvKr`{nO zRUif3gWWhuI%hPFfOK@k2kQ1ji)(Y-o#{ne5#5!%$O!<YKw7^7+!d{JWN4XFGp9OB zf4Xu?3NA9jb-=~0osMsctE2$iC}qoemcCd+5FLac1AfBsW5Q1|e&*q)u)>KhF3x_i z>?7MK*G`%R+4OE-VmQRFh!2XhpL2YpxAk=GjQG6j2Opcx*}WoOn_zEn9QU>Pmv@hj zv;Q({Mz!lm{pXJAps>Rcl~Y}Z9pAuD6P;lHrOJ&sl#=#Vd&6xt)2VcF+w*Q?iDNcl z?~AHGqG<;^O>cvYXYHMh7nXFC_on+egCi>4f0AE(9Xh^>V}QD(i)765eE*puzW=0h zdoVV39*<Qwxa-TX-!RK%?)nC<W6CmCEg5M%YtvKm9nw-#rI-FH)-I%4i@NPxI%&aj zjlBEZOYsZE8eqJz)F8cU!Dk-idOhWeaAnFab#bd}xxccdUOw_FcDaw`@=<n#2}gI9 z6gVX%Ej3qDk<@icigQk&*ux$qPN3PkM$QS;mx&KZNjl^Ews)Kw_ZxD<ax~nbFT;r; z<5_)a5f@kN-i6J0AmzKq1SvJM)K5<>E+4yBaB<hBU2a{=&aLc-dnu$x@0Gvmy39S5 z8_9yDa^Y5X&ej84A>fqibuXETaCKkn`Y>#--7nW?hPy*VW!XOcLzNY=Ow+WHPUuB( zhop%+VRpW*o;nS3`Rli7mqDw`4fqDvU1*0Pk562?!~77Zs==rJ2-0bKch}0YD$UhE zGx!5#+?E&_U~A{7(wZER%wC^r%GD-Ierts~0UXdTJ7?GBwv+ttMhnBi`fg#DlBK<v zW#B8fOFIf*$^YpXfHVBqRibhtF6hHOk9Ee~R1Tjk@T`l3O_o0oq0OYYg43LH8jWAt zEV)rBQgpQZ;mUZ)Hr@D@ZAwICR^(D2afG~KBPcNX*>F)|dub$9&nDrr+rDfg)MZ54 z18ot;-L|39r!@82Bs9*Pc^%`O<!Mreu-GO^lRS7p{Q+)YiL^IR<HvFH-S+EqwbCw} zN8FSxlC~XnhDVCxw|610XN8xXt8Irf+$A*S5c02t;p|}JA?{pJD^0j0h%jE#uf2#p z8WH+ZC}gP^{uUo8T@x)d$zbQaIK@hxl(Q2gmnPyIC|H{7Gb;sK4erC;E56mq((*N) zGP#XB;I~ZiOOv|H<mzqCCxy7Ijo4SAotWj+QKR#%uK(=%ef5X>?VXawyJgGgwDpdp zIxbC|uQt>)sn|rRQ7PCuA?@2u;?Vg_qRw1ba}=~p(oS@CBJ?Bu?PrD57^inkf(+88 zDLQ)!)~INdj!-M6CX8&9ter4l;Zy%r#Y7+D9{t)fN-;`K12<|X=W3NS!sIltaFl^u zr#DsvzV7Voyj_dp878Os8E%KhBi)-wi4eY3<5YYjDr>?nDUG&iclz}vaW*B;l~lK8 zde_ovnqKxB#3eD;G&`-*&m5#^IQEUGG_`tcMJo=j<TB}D-uxI$a7rq!mDFG>zs@Nx z1(JTK$0dN0IM?i)ehN!=GjSgtn(&A9P_@|aJ=}?Rp4+0a$8kkaSnc({=*xE#KE*l` zOm|^Z_FL$}QgTUB%o7_bx)q8=)kfT+DLLkOt}$CS55iUm*lf%|kxMrX&Kgm}DdTsx zFV-5*TD7ifw~uRgvfv_}bn$ETd!TLPL&$xZfAL{z?gv~rWzm2jxcdwDDK_FNdIg0% zoLu-^T#qGnp?n>q{p=m2bLr|Se#eKL8om1+<Avhe@1$&t7Ns;v&%@_9S1$w9m&Ssb z$TeWHym*{RNy{5m;vRCEYy0hVPOrm~?jY#_Px8{^=}jNco&JPtKYN|y5~T+1&M88- zfrLb}n`QNxxNb@Ck9?P|vJx!DRTW+3)$aE4sK)ohHPInHm1+9dmTUFfE9D97p@vl# z#HF{%WRinzJefF{KLDl=v)uqw%j{7l@!Aqs`-;S2A!|C9e6y<LJxzIM_rT_^t4rSV zaZ4o`+G2OdCZCc%C1udbVh<3SJmyrRF#5Af#{7_V(<UgY|8A0FZj=7Ecy!@2C_u+d zBh9CDo?FC~J1LJ(^uCj_yEJjG_nnmD;9yOZM!A*p#<`wbDZk#Lv0aA?25~1Pwk5W> zlM+`CLX}FckK_31Sski9mn#mIOyjucV#?vd?zxz<=3V)w)__!L7%pmUx|q_zJQq{? zC>K+Pz3-eva|iWi%GmFe&m@iRNy^2JQKFOo4L4JEOTmtTDbjm&O)NswVaj)oF3TD0 z?uD=LjmXY*K{(B6l=h!g^TidF9X!S64scZqH39NfUOv*XVhmB!T)V}fQeTGCQ?5gf zuZydG2(Ve){uPhNBg+DRf=M*8EJ&r1Wx-sGECUE8W3ZWq!Dc!+6=cz1vml=an*~c~ zuvxIOkUSP*09t?n=u!+oiwi5n*^!DJ_)V~HD%)p^C-Q&5)mGPe=T{t1u_UOXI4Hq> z+VQpC7UHVHRaG5b*E+AOSfZ^c*2de<;@g9-tsQptPM8>Pub(kDeNOe8SRM&G81~*2 z*G@ISwdL1|098`6B;xF6XV&18p|s``(dp`Pua(x4b>=z0C#X}V>3FUtyBlMsTwiI0 zS(GZ-ZeOYDw-TFmx=EX7dD#2>1Z6W%vyC~kn=W;o;)r^YlD=g3lW^(hM!Z09$VXzS zS{UY87%1wdkerDXAE|8L;K-{Y-BThykDQ|uUlXHK(;z>(yhtO=LQSqus`NmlM>&YC zH~FQeNe@p^hhex5IZ)P3z|G=LH|5~F*uS3QN($<*&z1yHvm$U?^eeZ$X28DM0qsw$ z39t=pUuo;-*sXEw(Tw@t>Obb&r9M92joCxCYO?JfbKKqmcZ?m1*f-|5tsPdBxz(rs zGgs9tjI_iC4qy7uCYnvzgWIE2pnzc$=&x2w!4`;bMFGjO@`k2ezvAe5Vn|Sjy*RyX zZ-=YN>c8F@#8T?NR*qd}hII9nZD0Eyo<&}~{}xK)|Hh-eR>Q6#2qCzcAdz4$!4iUX z1kVw?P4E$cM4-J+!+H=5CKyYQOfZk2kf4NM9l_HC+X*K0)Ue?MAq4FSf`iFU5Ka(B zFpt1Wu#Vsbf=Yse1jh;L2z1>kEWvn!83dUGR)UoT_Y?e$;B|ss1fLLmLr_a_i9pvw z!+H`7CKyjJjbI*um0%6Qp9!8M_!~hb!BK)bg7(*In1NsnK?1=%f;$Mx2_7VPn&35p zy#&Vy8VIK11Qk8CMDtOC#_Kefm9w+2iG4N0#8S4K*keacEd4=~$FK4`6C2adbXgqr z@#*;Ed|?tK5ggZ38BssnK&+oUG@31-zany<&Ze;m>?Sta%RY>aWQoiy(wWXCv3M54 zCbL-K5=Vby*kbw{L-C75m`vqwFf&kI@}I%uvRwIN!hNW4AF8-8mMpMiELt&RnT0U3 zQ2O)f*DBm5u>!WFh1+BbWg~ahC56mFa`6sDlM}X(porY@B#=E%$rtu~3Mc1XNa1tH z4>)F!eF5}pP+3?gEh~kDn;I{J;x)O*_^LRnc&YfPIH-B6`O5h$_vEeStLCZZgLE<| zJv;ejDlrfS*fzaSBWw|($jdE_(#aGQHn~otP~PuJgmb=EoD_kZ_j`t*Mb1ynM@?Hz zPfbUSj}LHT7B3!CB$y+Up23DBdJM2Er-vma4b99Px?E(EOw}Gu5NlarSv=n&DtA)B z5|4fI0$TyBDV9Q;>KJWbXfLv%L(dawB-rysMMf7|b3`3xSQm);v&^^JGpur$jC^6G ze2_lUMVd$p=^zb?w;X98KH?${OUba=t(h4jjS1EqVVjn1w-zaGX_m}<i}EtfF58j{ z@`Xc+T`@Ckh>Lg#k8lVBe|9Uis;R_1W@>Q}>}EEEI4Y6;M1FZi6RZ{s%oK|)H6zb1 z>=V80(Rq0V3)D8R@$uFA`3LCQweJwvv6DWi^EF+%cDwev;O;%H@7e2y-iAJX`-Su$ zFfeq`jbVdt8ZtC|*v%2cM~oabI&zG0Y}75|Owr?GV$BmK#>FSxnwT_ca`Kd^DbuE> zrp=gn+pO7h=BCffn7<&?lC>~9CwEca;{1Zb+pR@5`;w*0mf!KGJ6EhMW-$d;^RgUU zvbA7=1+~AJcx?gkhLyO)LX>4v3qbu?1)tc6rxugDL9|w@2xky<bY$%nzD}g}ltO>r zVPxMFl1h`<#8!OJCMOe5MvD-w`A;SJ7EYxdAzW^vFwumKEIS}W3_`LPgygUYnTFgF z$p&gNg<F<LbFm1UO_UUoEkj5|Spq@MKnB`H+sP*xV4&X&LG==mLQ`7s&k;Tu6fcL} zp@c)LYA;%yoPLgIojDXULtrcCX`tNfB7OKn-WG^l;a5n1d7c<@ibKdJUQj8gx|m#x z#5+PRqI6~4K^L&Iiku+55T<}AH;DA|M0lC943V>1PB#hKP?nIXkQD|NN-}=|y;<2H zQF`Ej1s<Hd%QK_ZlFAo2f^%fKZxy&$o5hS3I6;!;2s&HV_syRA&ZihQ!5jIav=$Pr z2Fjt3;x16?S57^TOqp7>revSRUM1QxpJBhkSC`kf#jCtyD4l6ay;wayatRlRQbSw% zcko3^I>J44s5q(R)*1zwE#luyV+u9CUDW7&r4+AJ3Md2Mr>5nd-l*SMe@g|0S8ivm z<u~$o<OeRz{GIuY@Z?u0Y6|Htq3~8Q0zewAwH&oAHl?VhH~e?tpDt3huw?@Cp$bRI zZ>Tx77B9zt7>m4OeHTz}76oIuJr^kckb+ADH@2Q`b3Ot#g<{lYQ)G+EGp)&b?{r4} zN;-=bDVHN!oK>Wn>5&18el`8y!!ONq$`s|Dp`@;++tdzGrUil@LWLa77JZIEjCd{0 zxZ(2l817X+SC1b;$UKNTTP)sf&F~BNs-LS*7o%N_&D9i(F5};DFL|9M>OgKia*tC; z?>Qo-LG(_A!o@1yRV?H<na#=q59UPkE}5_?i|NS3YTt_&O(H}#nOM>ZBC|8x8%^vQ zxRcoh?qot0?GqPFtUH;>WcDEQ_%9}QJ(-1MVz;${%p2f--o$#78B8WdH!n7rSRXPg z&zXq-C>=5}b}S|H4&*~7^mfc66JHcfWG0EWVHN!}`d`qNDrCPZ6=GNz{TZm_nvbj! z$%YwB)BL5qQVv7JA7f+1{G))%6rmO?eXkrFX)O|^xs-lyQR=374rWh_=p)ijZkE~@ zL35~>F@Y-wi8Mm<?Rj}*^~b-4%9Q!~Gk>2Tt=Vk$!!shB(m_DO=;RpzIyNdiJS{nZ z3e|urW4d|S^XOr0S>ZBzbQFRGFrAJJ9sLF9*nb{8hRgm!J%5+`UH%tGHFKrE(pLVO zhgjwHr@6{M@A#{|{(P?TPmOE%pDzCY1<&Pmn*HC+0h*@2t9_brfTros=PLi2IN)FX z`!)LU*OmUf-@N^<nq-CU=I>WaZxh`uUBnF-tYp<{M`_tzcb7ZYxYkyzyJ!7|Ki_-b zU+#b4!G|7x<k83e`uGzYpWL+hsi&XW^6YcZzwlpw`}>P8z5L3nTVLDu`WtV)_4Yg4 zcT`sGtlstR?mc_=z4!k94?a9_@X$wxKmNxjpC0+_=;z12`0}gcUw`xMiId-bFV+0u zKK0KZPuHIL>1^H4=jt2I|8k-6;wAnsFHE$(@NiWN6aUln|4--tZ(kT{+y8$>`9qg+ z2Sp<GBD+?x-|uC|O+4zjVV#%#9xwZPFZ%{B`=7n+_j=iJJCADL>Sc!_pLTM3Vwb{1 zi@Ii(mXn`Zur$S%VaqAVhx_y*i*@q+T+0Gme12Aeb+PbBlj9_26xpH|*m9Owk_!s$ zg~CmKmy=;hlw!t0%lLw2X>2-65i5-?*BJ5mmz3O9vZ{mzV!n5k+_k!-tDhf@#LCLb zsNllGOOi{<X;c+DaCCB735~O8FqWJQ-Ft@6lJXLY#b`K|ot>@I1uQEpTv}MzY#fF! z2a^c!Z2>z6Cnz(VJl1^OPhO+QBOiHwn=O1Xe(X=<TzSkoA;XrDXRuhU1y(~&z9BEe zp1&a5VjW}XUnBxp#mYIt<>Xm14YmS9L7^qzU|D8aV7Fz=&%+||^n6(0mqmqS$jCQj z6jGo}3XfG|D!d_HVXT&nOhcBnU@?Uj(OJJf_+}AJ)WlJF&LER9qf(9u1@jFHC<^Mx zkR^~1ZaGa=XSm&Nu`bWaU)Yv^k;P{7@@9)O7U8wXmPr)n<P*6$f)q7ClYfET)>;7O zod%U%8)5KFZ)MiLK$)G|gqu89SRmGzikQ4Ys?Nv;QcdO2`#`1E^2FDn1qJywYeAkN zJ0m|c&tkodzNWfbXt7aM<=6}h3NkH*f-FO!x{f?V%+=*@HA6+7Rx;;{oQEniR6OQo zh15{w)a2Twv=%R>oDEdN$d$@);1J@6fv^>2TjpnwV_r_ag?K2hsMR!<X6M)}MTHp) zEQX9M8&NQDsJ+NKbbe0$P)q)jfojASeAUEr$cX~xSPSwOTk>t5jGE<Tu`bTZ&kzMe z^+>U9y4_x2qxvCEZ=O_ZNRPd&-K2{(C@am`W|RLkujJ))m*&{A!7oL&46B#()n(^Z zXM$EGOe<xl(j~W7@3fkQ&9sOr_x3l~i-6YjX%lW5eR;YA`wcW?DA<Ejnx)(d9s~Of zB---w3YH>K#BPbs<#F0BhgSH^y3mfwEi!~!mXO5B$ud|L7uuE&x^h1AiKP^#$-sA< z45|W^VMQS<wk$5NE@yp}*3{S2t_-cX;oI!*e{d2wJSbZ9O;b~1Q#SUSse69gKVx57 zWIpLIrD4w;iy1Q;{ZY~ERI1eFv#HNQ$5b@CC}(kDo+VwC$FnnYifprO5jPJh%oGCo zU^HX3G1QlAX+3XjYKQr;Vyf>8Z!lfaKcn}R{X+*`*?-E7SN6{wdS(BDVa@&bw(uVw zVe-&ec(bX^bjFRmay~DQy0U-W=;r<hTlh~K)7*c*m;dud6Wd|Da{SLsSI+<AX>F$u z^XVB^j(_9q=Ke=o_)nkHRCbwO_LkwUOK<Y)=Vh;K5$^Q`SEkpIdu4h$=E?q|Y!<a$ zuj?28O1&(<vVZ&XE8{Wv>9))5^Jh%#$7kC1Pkzh9roGkNzhyZ^y>sPydvp7h@qgyM zEBlwce`WgCe`aD^x@tUSy&rpIYKh;wHMs=_YgpnL*6Q=0eM<kneM;Rm-k<vK*3jG| zM((;2spm>#y|gJ745*VcY}sk-c6#%k!O*Bzp4G4|ECwTHi#h=!Nr|v=Vz}rPgRx(< zX&E`T2?f>^>csOba(weLb}fr8D#}@yucR=opeb=SS2IB*pv;6Q^{;>_P>-P@hi9ik zS08dsD#*0wStiifW>Ustgni0)3P$U#;=Rx}$)0D+8Nb|SnO2aNlWB>`&akotNGAou z5_=({rLi)&Orz0&ToCLp3ca3`sBg@aV`a)jGv?!?%<jyX>M(XUO2o_mENB3p)0P)n z*iG<_DabD>$g{-Qta)-MD@zjYcB_>JCb2nI%8JGkX;{IJ$tx(bD2d%_o;t~#NPTr? zULIpE1~jMAuw#iRh!h&Eq%pH5#b%}dc#1tngrKUPFRGcP#3iOhC&!EE2Yliz8HHlY z3VUDJ<?SgyGIKr+1!HK|k+UEpFNIJMH5%!gXvtV&xy(IBE)Of!Gbp574r{09%QJ{f z^Rfk&LWCpyY}gL;6zFW*+c&-_)-vC|aG}MTY_$|oVXz+vAB;GY3vz%Ldk?#UyxuFd z>WI0CLn;M_%cZG0e(MvTPpD_)<=oMdi0J&xl){|+m;yWHcUOzn5zP*Z(rMl%7z@@; zwdGCEr$K5a`#^+{d4*=SB86mxW~V7v!jcMhktH)epC+6bdNStrb|->V_iV(Cj!9;0 z6v}n7ISKamR4%E6d8TLw)Hc|=+G(EsM0;TO>@(VnH_r~F-FOp~9Ng4uWNxCGDne~| z@$S1`<u)xF^XYh+^_hK?HIL}bOsgp0{(fmxf`aK-L6ZcK3y>Y68p^ZSE@PFKwJCWP zOCh_-H!&uO3RLh6VNvNjm2wh^u`Hwpt6IG*Lj&~9O_MS?9toeLLOQhK!Ma>7A*khc z_EVewv;vPjV9&^MBiUjVm7BjnCWErRS4e}L1&b8AsxId$DxT$n|CblpEQ_b*P(DSX zt&@%2qeu@pq_J_ni3J(NQS+@CqGf+1cxI|auEd9Bu9M{g)%Xiu??ecdOqymwp1mj= ziH)~sWzoo<`b^>r1(O2n1PgY*S=4ru$%u&yGprU*Ij5m)y~-;!!<vIBO&Uv)eKIF! z$+eK+)3nM3ENL_$w*hK9jD^UxuV9zfdUyVx=YPpF57&q*rbgrC5p;Q2d~4TS?%d4Z z=MTi~KhOXFAMg_{sD7#Yj#%{_$6-`n@>NW%?PIS7u1v!TIVg_7x_bbV*WGoB*-kOr zD}Eik{H|5v!qhA21}SdXA99TXaPOnIk5JyDmG>CsJz0q}RdG*I%rqtanM&Syiu+>5 z#8+(WBgdW+tAeY)623&id6i<WR{R}`f2oqr1_JE7_^a~1QNd@klHW6m`!)r?4;BAU z760G>xh&K&`}aOUZ9cNUq0Q&tj@p0A)Aq+q*}v_NZU3`)|B=uCbskWj|JPX|gvJLx zTHR(6$n#b&U)@J>e)=of-1wAiu6q2qxXaeGvP76$D16nXqniwEB)jU8Y?>5v>s5x2 zR?OEZ{DCJ=JlTVtd{TOHR@>>X+$d~Qr~{dDbna0DOFx=>^ybUMA3pNs@$-xgTPerH z`W0k;+|MQ!OJE`xOE8*XI6*i;D1m_>m>`gV5j519*eL>u-~_>Of@1_n2tFn_NU)z^ zFTpN?N`kiuUM1K<@C3nwO8E5z<pd=JHiA5Yc?8o4k_i$COay}o3<T}8P2;p+dJiJd z5io*=vnEzcAQ2oP*h}y_!DfQ>1SJG{1epZ038oPw62uaWB^XXHm>`6pCqY+&K!V1f zC{2Q61P6bT!|x*Vb%HGf_Yss56cWrPm`0FDFq$BUpz(}J;CrR<<-hWT42u3k-!<%V zTyfQy`fSlevvB`t_F_OZ3#GX0Z;WPKd0z09?IrEmzw~HYn7`G%E&tydzAgX%JpcOh zYx~T6T0FkWqsBMU$Tge*{P-O4ox$(k?%wGSKViBmP@f?RgcGla8h;&%m1j7GA8c$J z&~`LR+TMKBaz3ekPw+gq`uYDM1^s{g(JH@SoN$_sbRu&ZnHZ$4Clhn8E%G$QD|~>c zt4eZ5Jslwv@oUM1zfQwSFkdr}iAowxCgx<xnwIHk#Ey{cR@}4bx1<$!<ojSN?s#7G zVjyq#$~N2&w&8xf4fonM+`XCA+dZfacS9TQ;jOr%PdAZ!@_Y@G$ULEJb{lQcFx=0n zzR%0jur0c#cU^&oW&1U~7uv|bf75&L!}K21^j`TKh1WK{hyGn7&QGiF>**agKB(^x zzN}%ldF3C<<aw@1V6E=RYyBBAWC)u$aUx4gOJg}XIm~Xi%N1HuLjNV~!3Q5?&ph)C zd*h8aSY>4;`}EUK*`-UDnEJ_41f~~Uo=Owid-j(EGX0*VOAm<G1N?MjNnqpBrFY(m z0H^u>#shqRJ$dh;cOAW)rnvOJbSd7Y1N-;y7w_nr8E%(3%^S0X|I#~ab*CsY->tka zJ)mnq{H&}-`Mnt7_aDg0I<QB1Z<PIUe?%kRDg8$B$3tO?<@DwEym}FS|NaBJWruo^ z{#VFfey997cOAe-Rk#7a#slZSqPKJK7x=GkT(iG%|2d?A_e)b}$5VjDJW8M5{odZ! zb<m^x4-g4iC?DOC|60}Mv^*ZcHM6>lWjA+D+<$;VtVMC@8lQc5*l0O`cyIXNz~;U$ z>=$7H<bEo8EN`IbVl9F2|8Uc^{SA!=N-9P81Ds!+zN4X0cq2TOZ_T3(TzHG}M-r0k zjqpNW#}V0Eq%Uv_RQR}Dd1P<a*t@d$xNpfgVu9XX;7gz>VX~lV12jrF?|9?dfMNn( z!ZZZF1OWtkg5Df_9;ER4ukrct;lo){QW8r^Nnv-~aYr*AU%!4md+xdC*sHI;DtP>p zPd;HMPoDJfc_Ef<UZi&W;<f<P#GeZP#9zzW!HRdB-jTJafE7~}W$oa+c!xWkIAKxo zk3XlsMwa5go}9(`g{&3(PEMabeMeT}@aM@)UzBkBWX`vy^OeqlJ7!H!XT^Nej;+&? z-W%(Xelh3&tgnBO2`>H<V=H_2=0O3yIrCM3T>;cE0Sf5lazYuIL^)k~#i@V){wypk zjD?4Xv#6*j79AbU;^X5**-e=;g{7vZvY9hyvN?0+u$7q;*usSiS#EAFTfBHNvs$e} zeymurf^D>qW6$3a#a>xyWR8VqwsuhryRRUcJz_Pnw^of||5Xyt9$qz{ZF{hg?Y`$$ zc5u@o7P_6Y!8<t{wwtqYyEq&19%o}e<ZSi_oW*?1*{w%8oAMQBMFi8o;cV9ToGm!c z+5A(Sty;B;Ih{_nVZ#RYzylAkhaP%}J^JXQ?1?9yU{5{ul&IeqUwn~m-MUrCvbW!U zn;ouxk*z+%+16Ul-hKC7wr}4)_Q3}qutSFqv5!Cgm>oTOlzs8V7wq)OBkb@`oPGWE z*Mcu<YHC>B=^xm)=Q*pbt!4G~^@3OM_zb1~BAGFNVj3NF+U<#NI#6TpL=D)$uVGPq z7@NhXu;u)A_8{NDw(^(Q$NZ2gPgz%r-<RTtQ~W53pGfg%Qv8J!e;M!0)=>OMDgNIm zekH~Kh~l5{ia&tjn<)Nm6yHYi*HQduDgI81e~98Aq4-}?{BJ3K4aGlA@y~h1A3A{g zH=05f(lma(jr#F*G`D}2`rB%1gdcTg{KzoIznH@KiQ5_fVFTl}FEM`3D}Gms-<RUk z0A~Fy6hDdLQ(Cn-6n{CzUqkU9qxgTP_*E4DBZ`00D?asW$<&k!X|S@s9mS{l*#VM2 zwKN#whHDtVh2qbq_;*nJ2Ppn)6#rwd_^cC4X4g@h?L%XxFlx3V*#YAGS{BcE=i3+` zR>=62yBNRyQN}mC%=k-t89(%uS9}A-A4%~ODgJzlzl`FqqxhRC{woxJJH_8a@hQ&@ z$0+_uieKxAKbTUul~Tx|6z--Jo}v_XQ3_vE3bma%`)L?wbyGMycROeG8#p`v5@#0< zaZmi~DgI3qe;mc1O7Ry^{3R5>oZ>%3@t>ynuTuP-6#oFlKSuH0Uh#EG33Q|Q*HipH z6n_B4A58IYq4+Z>{_PZhEyaJD;_smNN4?@Vf70d0+bJeyeDwHe@tuGr-Wb|Hq;KB= z6Ft7s;}hcJV`CFy#*K@PO}{C0;K2SN6DRglQ;3ZxZ~BiXOKfa<*dPR$IMJZ^CnUv~ z6UI^U=9uVl<D=6DBS8QDefuc>@kyrWgy@*q7>W<Ph71l1yCuB|$%Lc?^_g&e@O9}! zhR7*|^zCElHxcnE{e*=0aS7?Yx^xdFe+m$)1Q?J`@yA6Ya!f+{^<BDjm;D8fA$<*m zWV|Q+^ln|dbh$N31PBuW`WO&DCKkTY@bB8C*R2X_i8mq#il06%I%!fuf;l0*2NLKW zeBE`|1_lQ9OhgXwPZAHM-=kT8bQA;pW6VkM2?<H&q~6T}kpI9NMf&5DViU|su}Kqd z=+R9H0RKUuHzhJT{TPvjT59QI5kM(Fk^V4a5*2ZhIUz|9A#za5pYTtQ9cBUw36w^U z2@|?>=|TZK{^=wA(@jIol))tOzhS}zfkID@e~jKwKe|)6IUzQQN_j$WK}4?!3VVzj z6CHiCuih_wTzXP^QoLEgGD+pG_?Y42V}@!q?I$T7D69Bb&x%icdgRcccK#Yo=QzR; zC?+M0BSvcSj~+E5JffYi?wWY=j2Tb7JbZXeOp||l`uOO{#!dl#Bgq<*j@090oAOVO zL6FJDjy?e<_{WSRc1>VS{wN<}FjI%lohGNpB#ko?uQBGGzWl@mb&gDmrgDt;@<#fx z!kgSjMZ_56n|sUrL+Qs7BgUIYjA`NRl|B_seDs(W-YlW{lhneS^UC4|eaX1vG?1wL zW5#pEtw5_|xe_+6#dz+@;}mr~H+AY%F&<fIndljNJhSW;R-R*K4_af`tE<iI#fMk2 zQG01jL2@qcQ_fPqk;e?n$h`ml``IIpJi;D(>@l`+<3_f5^JXztc;%H>#CY({H{TRv zg@05&!ycjW#)~vo*tKgH`|!gL#n|A)H(#-DzWIioIB|lx-EMa3)G7AUnbTry@bk|< zvx^rmvJLf|y+rSaE_lh=p*Z+5iaI)Km+Kc$N4K0hx_hXjdzy9Tud-o$JDbAyvfKG# zwt*jGFY%Mqf7g1&rw%bWnBr4syM8dmA4%~|6hDFD&!YIZQ~bLr{$mvXWs1L#;y3kk z{~4$JXPol?>Nur`A_IE#=pjCVbr-9tJ%alW9Xb@6rh6EA^a$?J=f>VaojTn>eAc~h zX#f5L`w#6Lq#x9g-h1@vH*g?@?Hts5XxPv&*0b-8p+kGMAKIA$bn4jghMs-;hYsz~ zKJ*5~!C(lzai~rg7)o&lUDK&!kKpTW9NIyrYv0b#FL01v-$U!)<HlYcbnW3VG^lsC z08K#Op?G?=^F#XA2K#iOw+{W<w`&(h?W<4!>wSBYFGcA^@05R7&#nXeg$*4VhU;P| z{~N=?Lc+rOgbDh3(t~fYefr2>{EGFAjm09KuL5l?`fI$xAUrl)^p@9$i0#=t5yWss zIeQh4x;4b#3;dK^|2v-F^T`AFVk<_s-o1OHPyskjIiOv;b|hJZPGTx4p@M`Yh1kxa zmiQm__$uiJUS)WcE4oLpzv1!6A5Wux=dIJHPk;C0k3W7-ea|=l{O3PUlKE9#UEOJ# zm!3Fw?AS|t_Uv(KG@7unW5*6vXbM)+!PXX(!*3}+)HOQhYX%G$(2q=%Ri~?r8$4^) ztZ^ghA^Y{8e){P>G>_&xcI@EafB!wF{*-_6$tPH#;$MCB6{q<(hd<H4sh|BqlB61< z_3Mv5`slA!RaM2~$B&O-ay?-y&vi`RY6DzQuk96pUENxQEj+ytcJboH5e*FuxIljJ zZ@|AhmG=Q^gTs-=s8OQ^06)S@;Q!ux@9{U@c!Lvu{Povg7kHmMd6Iwk-FN)p!GrwJ zp+h1Z;?NiY?k6bD<1fAR(u!@{wymHco7v%TOfD=ej9s^GT{4ZUfg83Ng<W^ubrDXd z^R}CBz8Pf~92*-OigbS~{%6mg<usPr(63*=5mbije)!=BURhb`Ddz(R4)D)D`%K_T z^&@bH{rK_Y{PWL07w@1+1sd}RlX$M~%$YL{#CI1cP3f0ke&Ij;^wZgY{No>QeEaRU zUr}D?5sv7*m)hG1!eJfa{#N{HJcYbG0Q^+|4Jx2cQ~>@e9jaZ0mb9oqID3HV+ymb3 zfZv0Eb#?Vxs>i;>6Q!VG=gyr1fAGiAqen#@eEs#;;vG1mO;8&a=HbJK1)y!9K47BW z&~^~#{rBG&_@fS~{d3gArAwE%_e1X&$h{H%)P`^VE%={1caBpxB=~>y=+Ohf#{l5} z!3Q7kx88b-Q=M}fGmAEi@<)B_+qX{uaNoOkuV^brjvNto&;S_#enUG!9Rau3e&pP8 zl=E>Paz1<?=R@Dc?Zli9spNd>dz?RalJlR>ojXizrRQ(KfBW|B8$v=tM$j039mz}7 zhoAxYqRdgQpa*pU++j!EfBEH?0$@j(qr8DH_yDv3Z<Igk_{me8$9}@O=_AgK2RI+K zpYw>lM8hu5ZzLK9yu*3lH#uK-jPu6E#z%h({v_)?0Q^-@>q3R!(@xM1z%OZEaz610 z=Q01F@&^r62P5C-{AQvd|5MJ-HFBPHi1YC6o=+23g@1j0J$QL-L_|bi;)PPsK=p7L zEx-kQ1RPtV1Lck~M;(E0!2giDG-neCO(y(r{amJD{KuT%Lb#408b%P`6dsZB&pFI_ z$lG$8xLW+F&)};32me!l!~reqMCEgoE9wG#1>R8UKpO|2f$z~SR1@t2Ps-PvPa+x; zi2o;iD$@{kkn_k7WE#S$EezhtdFYPcjz7ub4gLG~A3^o74($xGr8PRh>uvED${+sd z7Z49}#`{S<L2VQ?e8G7<(O@PTUi>G&n&%9`!)@Vz{``4P{Y*0e4JxQ~v{omr(Q)Cz z1%Bv_$M{osCh&)EAII-6FbO1X`;PPJ-*P_XIOj>A;WMQ@6VHrQ+w(rTJwql9*e<u{ zo8Fe&G)dh3ZQ)P#ymt8T;eAP-mjZwANlQAw_u%W6bf~m|*TF|$?SGX&vve%qWRK#1 zExJX}fM-q(=Q9-=lE30Sk#LIp%&R?v2Bkd@flMNq+Mi@nAEKf6>#}U6XH{$X_wC!) z!~fLBqo2R5KG1&AAE<SK_JDVl2H;O}k5@jB$)8_7mOs5Tia$v-JVrF&$sqYTn`oFp zcuf07ra^7bph0cVkV(V#P`OugJ_s~W`5WG#_P<vo^I>cFle`!FPi<`-#zSaxDjg~< zpaK0sTf7DQAt$%4ispa&laW71G(1H#Y_!Sv7oFyOo||ZpI8Qq%w`a(tgwLDWGiXrT zGiXrTGh|ZeTOyhImiVKcp?{%qmIpwC3N7hqt&OO(;MrCj$zQ&64FB65M*i$FqG3rC z|GD-D{^>6=4e3O~tnY~iulAgvwCCu<vP>HNf!sf<GHD>u(C@8Q<&wak#`kMSj2O|E z#wex0A94{Ucm_0J%%jo*H}tjYxD0;iZ!u=T^JYmTe{E$Xe~D=LFQQ=!(eU;|1-$Nb z%~c7T8lrPC)yZhJJu733f!kZhAM!%w|1o363_x9|fc6T!fg5mFX@TsAyvMu-aua<O z`Zhe<SC8gz5Di;bj^QufY2+^u4K3UAUq~ixSRBpQE*j6vb7Ht-VGJ+MjO8x-ZT!nm z4+<K*#~4GDF-BYXle`!FPx!0@-oOhca01?F55QZk6Ucb)@iF>e&<OsoF29ApLo~cj zG`zZkXrQ)$_6!=HRA_iep<z8}$Q{q`&L$eNV)@EUv!DSFWYQR~F~*2@Tjzhs4D?|b zM*y~L*}|WH{&|jZkqX{*a;0{1@!~~(@UI!X$}x(+y^3g{w(ttk@OPy>Lndvs-@+fW z-ohU&9LMj=H}QM&qPdG`xQl35O*E_^8t}9nW6V9=y8HqD#0!G|iLXmR%UyTf#b0>g z1wqf%(4o?TxYD6l_|DQ%ypm{mi)aw-nP`AaQrq(rB$FPsj^hs$n)tm$!#bj24bf0W ze6-3U?!^)^DcWm{@rt{3`49Y6{x=$p1AqhYD=#ni(9n{OE75ZCVk7^$>S6xw+8DmW zF`B=nwC9(-+Vf_GhDQ_{{!DzdZc#LM=EyYQ5n~MMpA!il`(E_F@3YIF?x=I>e>Y5- zG9@8DKmR@8iuQ;$uyW-}L4!Ac{aT&CM4M_k$7=2Qbl2ZJV+^$C3Z*@_c&<490sg4_ zxVSjf1Aq3}XT{hHV;QH@$(Jly;-TS6bX=)U@IWTLy7DU8GttnBoN4Rnj&fSReti|m zPCjtpK%SnS&YybfDUN=*sHlkBY&O1j?OM^Uzxd({A$KqqYK<1)jlKc03S%LRM=>A3 zgZ6yjX*+NHh2|TZmU!B;I>vZNY0vAaE&Lk(Z@>Mvp2j-URQzwc>86ocSy}m+nVD{q zqoN+3eDX;?dGchQl9IwJDk}K>_utR&yYD`M59as47c`(RKtG|<fjJClfII~sqAx@q zyIisSqi2BsIsV?}B~4?DCGr>p?HMxZ;bloc|5xW<W5<qlCM6}U0{;-NZ=iYqh^<?< zzCdl5lOJEUY#FCE!SR0Ut+$FX!n^OjEBFDp16S0M%2#TAfIjdZ<~bPGp)SxK(56Lu zrh4#h&(F4K&-=GMj&a~4SHnD!k&*X3^w2|M9RztRZJC#pl;EC5-mhOjK4{P&PGf6< z3-CbO@CMMJLQ7fz;3+k2$kq=Yo6p}aGg1F6k1^0cKffY|Z@YIP|NQ;e__?2dK1}s} z)$0!rKKLLnEiFB}VZ#Q#diCm`Z@cX_o|l&=%AMwwf|o!8+6CwYZlI$jEm(s<KMcM{ zAC0zx^$5%(;f661>JGHD@(}swzeE}R*7YAmrndV;VPWA{fBMs(E&<Q9v@`)|(<%)& z-gqNlx^$_i2lN^0+Kx&G@CE+rnlI)CFwq{+M$jHwws-W|-g6JkpV@C*e?!{>{}V55 zT(xS|$7rVs2?_kpJMR?r02+Y*`0?ZUj2Sb8JL&*5r~n$&c`xQ-7+-_$fhSDx5NPw} zEsUuUo@B|NLqbAEzWVB`_h1h3d+-Nu0>DdHZ-yK2!kV+)ZWrx0CMJfPOeQ{L$PiHv z@JF1MbhO4>YTHmv@Dbzz_zLnEb*HXx5Kj2qcD3Ix1pY+#+L0qi_N6i`MSFhdop%Hc zC?BjFQu`1zWMpLU#Kc6wTbnm;7WkvAQRc7%Z?pr*AGl*|3jV=Z8|?sX0Br*4LIxA= zmnfam;NV~r)%%mXcI}dw{LLreYVk+D-s2y&TmhIb0w1bdPGbw~Di-a5>YEe42_8Tn zg)&y-sOyQ4qbO6z|Ce8Wng8u?e-nM+-~ax15f5ce<s^}8n?rr%orL>Na=V+!-#xDO z`;8dCVf+L6PwjCX-qGG*0vFUb+AQ&@7)vZ(yjaLqYU`qZgRB96t9*!dfOd&G0iU1_ zAd|hvL$D)V)Dh|f{W;N#lZud=epiXR2Y+u~_2yx3-Ue=<3p|9jaQEGJbCOx2pZ0!$ zn+k{nIRjaQ2mFIM5dgS@uE!sL9BYhE`1ttzR@^=KtK)wv?^4j9%Ei{`KzTz3QQs}* z3E(O8wXLk_3;a<x>KFpwb-?%FC6qbZ(1X~_|J%#G8UEh<4;j(2k8Mc{Osuibo;{mC z@W2CtzrD*{1%v}Hf!FblJ^;9@-$!U%``EvLyTIRj{!4w&I`Ad>Akd+%_d|X_)}cT0 zrUPaB+H0@zhaY}e@Uz;60iau5o5#2b@8B8r`vJ+!XMBBq{}tRl_<Q$@-eVZ=vCWn0 z1n|TYPl))S3*kW<@C9!{c7hfacN(8Q{X6;nGW>xHwJ*W{)PI+%^RFw>qON(W-|tX2 za6>()-{eqdkVW{GfNxNs1-Mflzemno5r6OXA9XHur8>d*9^)Up<GUOHyajXj?%hJ} zq72Yp!SBEy?SS}k^B;)2z~6iQljMYF{RcFl@5J~8@(+A~{s?_1${2G_ls)P}UEf7L z;2km_vKDfT<`m+)2GRUS<1X+A?_P<|+v0a`Sp<J|-2&wd8o}$tN0(@fAW>bRU+wV+ z<BkVvO|Jbn1M<MWhd)FG5HF$5WyjK`jVyp)8n<-mamL2g$|l+VVAEGIHHyjpAXC#K zeXo2=6tnV=z`s~Axom0_(^oMA6jQI5y%lquViqeVmrV`K=I3?{>~}oa-xHvKAG^X> z5$k;f`v}gpm@ncTUsPJI2e$PfuV5PchEt!PV6j-H(0D(VWIncGW7`?V+})TLpCauq z8aQ9_4d-#6$luto7L0ESH&=5$`$Nt@`AOh%B^*F&Pd`7uQ8c%^o!WE*<Qa`Qe<pcb zLvra9$)%$-K7W$Nwz)J0#u<iT1bu0|aeCni`8yfbLh+r?sI29Fe3tVAXE-0XOXUCK z74q*zGJF%|+2H*>UEOP<uAeJo{!0XBN#-6Vx%nm%#y3-ZZ^U;ld|$zuIM!nSQOEhg zpRVd#Z*R!y)vH%$+;`u7|6hAo9v?-K?;8O*WLJEOx^HpGLsVF<KBkZAuI?rpg=oYq zffy8oBqSt|i%j4F5<o!-ckn_U_l<xMK#pAj!H2*qx?Hj@iwh!8S(HogM(6#$HPbLe z5cF?~Pt%i`?yBlv{qApdRXX|r-wE*p>?8C#Y!P%dd|CJ@_zj;?9zS{kF9gN|yc0QX z;5*3417o{V`-lN31}&m^p-&LsBhE#;@S{PKAm)MJ6kmXsJ@#o7?*mr$+Nn#WE-or3 ztMP+qKYs2*^*Ilq3GCPL|Jxr~I3iu-F(<@wfLX^QksimQxM|wqDDE4~ABg8i;6uO} zIi26Xm?xXSrcnHvNq%m6{5%YdKYU>H1@`ClDTN{jhI}w`@<IDJo;Vi8J%P6ge*7Sc zwE^P+KLVoy)(M=j>pSAs?Lqsn;}q`~m6w<IiO0JGe->x>*2{+8Cvu6%=OT|7-#)Nc z;Kz?v<HvR}j0RXAFedP0%MDTe5VQ~fCmt)Uj=!TXC{;0hV(x%wKKAU%W09v$U)%@q zV|q1y^kbN*7URciIGt#p;-t(eQ>LWH_XBN%&v8Zp-iDki@CD?A*Gw+CB4&8(yGW#z z`r_`2Vr`HK;GOa9!><@LXi(9xVZ-{wW3T7~@+P1K3i9i~nqH0JeZZNJ4+TF0V*<7@ zB8D#w$&MZ`J)JN5Aoy|T1?;<e`_-}F>KHJ_fAc6`;9bCqkkdqNJm5#<#euH?n-CaR zto`y{4~Vwf$MK`P<Kpp$Pd0k==*(%;rlms`5%0r4$8)1EpaIUi=jIFC5O@{xs!L+I z?U^xr0Wtx61X$tloTT$KK#AAUz;5IDAF?%g@ZciS|9t{{0C53gJJ1BW2edeQ^dFJ8 zpHCGy0rJyTvHbeHIGF&>G&Y8l4U6>wMerlF+l`I`UtcW$L3{=|gwG$3v&3U}pwpEw zqdjjf7Z?k$x{=g}p)ss6uKYy*q5ZLA$7ar$F(Vz=5d3lUBZ#Ymd4uQ&XaRkM-%x<x zE*pG7CN9N~alCd({SO~|$dDmLqehJ?1RW+$oGA1j^e${4^gnn3^Z+lS?Rd=T#NoY> z<A?W=OjP5?g2eM!+0V5Pn}R$Ca=M@~;wzHH^SSiuSXeMN7>l<irAAI2*&8|e@xBZE zSayLQrw;9k<}Ydgss8JN_S4hTKk|KF<XwSH=jZ1~#*7&w;yd&y&;^06#Q1<VXO8ZV zyf?dV<j*4##aw`ZALk5uAhPnslE`OA4n!iC@qawe)7rIbHwe!$bm-9HtgNhUvMfjX z_U$Y3z!N4+5V?yW_5sat2K|G%ji5($n~Z`w>AKy-TR-dnwQk+I3(38R17Yid%K%#< zKP&S1<ByByzFe-k`q%_^JeXTa)nnfC&%fYL^hev|e;fmrh`e!fa<a(3q7VK0^%Hc5 zJ_s<9fCqvca=;H{C(acV6!a!u4wH|B7rPPjUn!<)0$LI6EA#U5wgTfpj=oEmE)k3c z@HMi_=kWs2J>bJ&j!1AOTlF2$^GT{}CdKE+rRXagS1L_{96#!SZwGoKmkS<5E*-cA z<RczOz!*R;;6C&Pw1D1*&O|@oc;k(U($!%ULjK2%8<#nI_U!Z^=EgXq550T$j<jsq zGSabQN71(+?-$@hxDI?1=nKqCT)A>3mey=^6>W?5(I)iIh!G?Dpg!c}Ve_GLfGu?I z-d$kKkPBRk0vZH4UiizvRN%MMGfevhZJ%$ydJZc-Cjom@T3RY}1Lmp*c}0vDXpQHF zPTsR;&w^jrwrC&ikggH>|5E)3eBc!;Rz#*xpDuF6(2dAB1(@QmY+JOCb^`kgUc&PR zJ~(7Az$MWK@E9--<e%2AT^r03{IzYLfBvh<fkMwf_hFo?s;a<$$B7oBrD*=-*A~2M z((qRvkgqH0x677uO@{l2YFza0EsiU_vm5{J?)m$q=uv(Bm0$4QZo*~zN%pYG<$fc6 zyl>{rnXk>6Gv_PPQ77ilpa0x~1q&|HJ4=@?JxO&GFJ8P@d{5WZnmc!H@~m02Hop4m zt71F@8-{Tvncf$FKk&58n>QoR`aRWgi1_a;<Pfnj_;ne@AZh=M&V%yFzn*$nRJAs^ zAJ>AX;ll;~OdwOxWj+2KjhWz|?k2Y;wCy7QSb_~kTadLNCW`k3Gml4O;I&`;#0-B) z#~SD+$PxM)yx*~N{k+Je;;3(!9gCe1KO%lbtbkZWrdVkT^$S=lVsi8y{Ev2D8JaA7 zoHG=IO+6c}1F<+_FT{R``w(|p6nDahflq~gLvNuVhJnAh>cxJdj`;G~_tBgV@(768 zKtGHVY)~)<_4Q}(7ycIfJ@|U?HR0<b-dH+qw5S6yM+fQ$#;<GFt^((V4g(zyZJ8Ze zGu##NBz$-H?T8x?JC*0O7j2{+j6@a@U8YW*D*Qpr5dp4-u>`+V_r-T^t`dHKdG>jq z8xOl0^dq|<^cFBV%pVABDawkWUgX%JJ%XNyp`OVX?Vk<CAG`-$4?e|McIwng&=h(a zWBIQ)=R{UM-CoT9oIdcO$lHHei2GLu_ix;|QTS@Xd>rU)_*@td=+pSV;4^G_JpbeR zVEltQNvI2U679ntAx=Qu(92}|hsECyJ&k^&9?$|<5uOP)8~G&Mi#Qv$d&`zB7u}08 zw9eARhIB|ZV~6xJM{rG@*l{DiuOZcu5)vAU)t;~dTMeviEdIj!Yu8CwuNW%_VD+_{ z5>_|HUs!vsmL$tj?Sz%rYR9fj5bLg8AG<O^th!c5s#_E8Ub>Vf^`QT;%V(ODEcV^( zLFbOr1N0eXL#>lPM%TfsAryFU_G8ami~dzlxaNvpr%7c}X>6y*2k49)B9rN_T&b9@ zxmR2xOBNlBj*yfk5~mN6X{;gLN7oceB~m6`KZtr0-CZ6fP3$6H65DSwrP>~b2~rm+ zEIy$;A^o1}3ffK*J5XkeK9z{w=!@yjtEuyTv6EyreOq>ZuS*nFsux#(y0|(VSFa+q z5r-_!A@Q9|V^u@Yp-fy~B=$AE@PuWt-ByF$EBi=y()B6yxwp6ylqn&a;3;}j56dL! z65nCh_WPt3bp9puvP9n=iPe)I>s#=AMVIYkJM3C5rHXw%dr|$_bj8(=83LbrKe@j; zH7`ZWB*BmARA1>OI1EX6xEr16SMX6-?06YJ?)7R_)#91qa86-X!JveJg#|@nw`u>< zB0HR&ms6M-Ze5t4U0NJ2&Mj-5U0i5qh6~#aP?{zbW)|h==7h`Ab4tVc#YJvYrH$Ow z`%V3X1YTC!KP>j=xystK=zVl&I48S*X@1$D*x&R`X->cX)JjgT)YAL``2{(>bHe9; zjQ{z5vFRjrBqe7+PC-He{&$;ZhLej16!*<3ZJN+OKQS9ysJKmYGYi5wO+DWBGB;mn zyzS*%=WQ?QD{p)Lq3LhlHlU}MnwXlHl$6}DeMSa-fb(>OXkK4VlpmAJ<R$VJ`H1|r z+(c=iBr0{)Jav%zj@m$LthLb6v>sZqwo2QeZP#{bhqWKIwxMw$SMR6q)c?!iMhD|z zBikr2N{z9`WMh%B%vfc7V4N@-nXSxw%~Er`IoVugzGdz;_nU{zde$GTyR4R$Y<X6a z^{|y@^|W%WzSb~ny!D**iS@13oIS^;vyJQki?9qk*B)<gw71#s*&o|K+W+AsJB3cU z^QQBq<M56=lb7=6`C9%V@9U0s7r1L&%{%Os`y>1peYl0g=^1a7Z<AZerktVFP-WFn z?^j2vW7K)-Ds_{(Lp`8=tbU>XsNSHp)e^Nwv|d_~_JX!e+ov7WYKGKMBYmOOiEU-? zvXks9_LM!vK4_n|cRBxbjykvU#{3@Mia*RdQ%`#HZ`=Z}fj`ur<j?W9`B0#EDeF49 zk=$B-Se_+smp_t!kpHMWs$?mZ$_!<p@~QHzqN|Uoz14~8X7#UX9c^xCM`)*>YGfGs zMt@_x@u@Mvx|OA|?yNtX!FIB5Sgt+Re%YRHudp}R|7*W*e`p`I+d3h~a|RM+Mml4h zh*O*2z;EKW@jG}k-jb`F@pimD(JP%l!E=dbPx9gXSzgJf@Oiw7ujX&@xA{)KkAKe3 z0A@g$zwqnaH@wqcL%*&6oWI)tyMNdhN=59Ge7oFS?j&c+CGvmEqvR>_4EYQBw~C@9 zDgBfQ%3)=_x`inEwc1hZuRW!W3!Ml_`UZW0vD(;a9A~mKm~ZCC`Hk)oPZFDHG$3Qu zRq3u|DmhBNQb@J*S4I+T_9+LHPn0Lsb?V<#Ka?686Pg^F9a<h*7kV@FRj7u3gWgPU zqbKS)`ZRr)enjtV^fUfqv@koE8_d6&2h0=1XE#~DBQ8s}a;ze&%qq8@wkBEYtdFcO ztm|1PHk<vMxwhoAaTYk6oLx?RZu3MM$u!=B=kOw4#>@F=K9Nu6)A=iWIbYAW@*PB> zZ}@Lq%?-PQ+_~;6?sE4n_d~a)SMJU778B<k^E99NkNBPaZhn@Z?-%<6{2~5Gf2?2W zzwFQUU-eh|YyA!WX5x<>{%)e~C;mCmx2IS)mw2VV41R4aH<Me)vh2%A@`G}k9F_;k zljKw-UFo6ZDiz8PN^SLa^-i_9YO6_VvYMk7tIw#F>SA@Zx<P$c-J>2+@6hhi9@aV& zKlj&WY4?V@gnEaTg|>!vhCU0O34I%?qdR&By}Ld^U!s4l-({p2U5y^bXk$5z)<L70 z+0txhjxr~i>q&CIFmJUUurjP?tyiq&)<3Mn)(Pt_)`r<E%!aTE_7;1O?Pq7$E%qID zlKm$;-R@=kP8P{(rE}1^o44jBPv$)ZT_^JyB(d-CU3@?B!cFd<-1$Vs>xtjq!O|`Q zdzKYbxnJ!@l6j}LDD;G$r#CiQ8gr;`Bh3kB8>_&YVQsY}d%NA&8SN~0ZsdO;9`EQw z;IW5plKg@4d$m33v6Rp}(n(8=D&sX{ow457WNbBlFzTCsG?UB$<_xpK3bV0nIa|li zxu!qZU*K=YcvMQ#5IU_;8ix`>%|gvXtwJ(6@m45X=SD|kgYlhF&rC5NH8V-Wm77z{ z_f6=rKeO#jvg_Lq5e0hM{p=w&C!XrUAE#%}<-7R@{48(m=DB6=2)BaxXPLX#{o1YN zHS})x61-Mk7q7b~&5G_HQ(qn<e<{~h?opVMqC7$RVx#hWXf?@HBVE^hJzalNpP@fw zWSHH}V$!m+&4p%T>nYNswOJF^oaM7|YysOwe6HG0+N~Vbv78Q0n)3|F_;0;Oy;I&M zA8U-(BEGZb?($diN5mJk_1p9o`XhR#9@d}HC+n~1uj}vX2ldl>O{0-<kFl7f>#X^` zS<|X(HLx05%<4h1HPWiE##xot3)ailG;5Z1%sOSAwZ5^=Sy`-*_+uh_fxXP8u?_4~ z_B%VlZe};PTiLQ5vMrn2&)VbcO8W)UYMRr>DRKHcBb*9nyz`thha~gwf`eL;RQBQh z_yCg2r%7|2=HGFNXgJwjNK{<o-tD#YLf(B|uGiNq@kWs>*7h6vxB7ScEq(4M`45sz zKIZrG`}k#kxnJRr_h0g7`AbOtu#k0za0dTVo}tWFmXMvOqu#7OsAj3B)l}__c5|qv z-ioMk#(3A9ZoOw6A>L17AF-Ql$6ihLAY$u|@4W34^09mcZ{)Ui(_E@=XLP-rzH*jw zMm4l7ZG@Jq?>6_59sHMh*!;viMtZ8)8e+X@t+Gy9w~<boZ7;CbI`2E#d<@^n-{Y0; zOYR(ZiM!GL!L3I;(#-ptcen4*Q%v-wL(wGhc=-jnp>ms2s?1c5D{IuZ$x5f`|D(^+ zm(kem*Q<;*#_PsrW4CduiAB00A9+fFQlgY814-LfDC?C?$`)^jx7R!19q~T*&U)v( zT7Con7QeCI%x~p~1O-!sR_o;#h+w0Jn1sF8|G<|T))4f|lPly!WaEw!&o);=N-DKb zsZ=RDl%2{cr6JkgRJBAMqP|SFcawTZm9z$0bFG7xr&Vb4v_;w$@-L2RbxGDbg!1rU z2{k0Kb*x-hZcbi7D<-oLvlwUXi25B^3QJ|3SqAIIda_<Dj}@>IR>lUBJ{rzO63>le zl`*cH#pba^Y$>Z^ud#J(J=?^#keu!yYkG(sVMp2L>=Zjomh~LTZY{g6-N0^W-(ufx zH~tBywj=G<!A`ML?ap?F-Hl|tmz`%9*d=zEJ&>e*xINOYAbb3cUDvtYY3`&r8BRB+ zr_;;HbIP1U?h*H>`?)K5wY<7s1Csh%XiOWE<Tvvyk9+OBB(H;);-z|>y$r9Lm**7_ zFPC`(y&>LkZ=_e@RgZ74m22f%xmK=~Yvq@e{|8V@0|XQR000O8ZKnQOoCtaWPCfwu z0LlUY9{>OVaA|NaUv_0~WN&gWWNCABY-wUIbT%|CVRCIQWq4)my$yU+SD8P4?wy(B z&P)P%nY{4UnFMGj1JnXZNNQ_tLSGW7?UJO`t-DJCRVRSj6j0JyGXY<QrfP?zqQN!L zTFuN_rEQ`eZ6(0E8=BQEP;1py5@_9o&=v}mOxrNO?{m(*nVZQ3Q0xBxzt3;;VYqMS zJm)#j+j*Yz`r*4DVJ60y8NX<hv1gh1Q`zPJ6p68E&Yw<W-PT{`JuBVz%e-1|!$#M= z_kHG*_tk&OwYL7#pZ?4y*P4&J?%VunSHq`WH{DU=`qXFc`S`V|skV6<jAy@n=KlA_ zIt%pQ&^NyAzR0f~?$7Y+F82jof9UUUALG|u?$`PC+wSxH`*++Cp7%Qi-@>)ZztcU& zuXP1u{Jl~jp8uJ@uWnfDrE=oy@^?2gc26jY9iREe8vWgAme1_+v^nfqRMai;cO`}R zFHMk$zoC1Xu_XGB#jea{5=6zHiPg2}ne@x#=Lxs0@*iY&1KsSES#B+^yKzl5BuF!{ zo03q>3Ypbjf=51lFOdu{UX)m`JazhOH+_7|CR`W(L=p&5-OUrzxEQOu_P%@SH`O!t zoks;s_6=O0lP087@$XuZjFrsCfA^x`6kMz2_;kJ3-YXLFI-;&@8m|8|DV=)leH-sv zi+fHJ(1zWG>xM+>KEB~INJumyS}_IJ1C!EKOihXZH~y}Z*o$d03#7?v#L3b|${DNe zHLZSm?17&L$4o4se_xF2;(U>AF@C3Sm4a0YYiI03FA<#RHg?$bUEh(lOCEpggoy<Q z?NSi$0tAbGdU<DSei}UgF)4UqtsIQ3HwE9_Xbz4&U<r;bmjWCAP7QAUXnF8+brr!T z@ABXS_udq&cdHSVl>widk@hC!S&qCFfKeViTk~&S1p99RQ^(iDr2b|mj~ukI+Jh47 z&SSpFY8N|$I)?J0dTUr(vC9=rt#j>5UBi@O7sEZHdsdv!)KXkajqu|CDtzpW$--*s zy4K8U^IX121(VJkG_l&U6!k5rL~S#!brP#}xsKg755GLs7s;#hMe1Cgx7~wZ1(VO@ zN29fC<m-x|vJRJ7eKJ3#{K<KaiYKq0z5K~MCI`mznAwwSQluyEamY`8Vz%kYwfIK) z*IYz?c3m-*L2%bFcd=_X?swC@tJrlf?(e1hT)I!eeac^3clrZvt$C+AM~S8;+;0U< z9|PRHG<J9`elAxX;JZ>E1N=M%@9=Xmykjm=&U>p%*1YE|nW`M3>j{m%MCZKIyGDt= zCi6u4_JY27OhS3;ZFHSS*V8*k^G>&ouElemi`_=odvGOsJqCJB2d#nsVWKh78nir2 zbSL`jd5OkEYoa;PUZ?eApf#1VRx6X>KL(oT`A|pvT)XjZH@$Ne=c#yypX*+{yO-YO z7U#L}&c)w>7cAv3rCIc#ba?Cm334w({!P#WGxWg%y+{h?F*P!1Vv)0u^>ZdEblxO~ z-Zq&+$MG{qqdNyonV~_G1J7BZ6Q=A?4Rq~J=-Nl1Ykv=2`xtcXAE9dux<)WAUexkT zS0UrRBWJbecQ@XGGH*uNHvxy`z@-BETOI^%fy>E{Y*x3dNnwSr0lsQx%l2rx^4LOk z$Lij)Gyeg3+Pz7=P3mg=bUwTKs(gtRKB2O}6Q@4E!|5{pdiSPd<KG^OMvL(6@P3(n zn|-tHb~ZcjcGh+8r)7MrP+0YD#`;yWua$5jo(v|p^glLF-Ex4j!aOrO^8@JMsc1BZ z-W?}cpgZMZ@0GVvo@96j7Br@ib4D#C;S8BrRtDLh3weB8lS3W%hw+a1e!^txH`YOZ zH;VGM^*_N(t(12;>Nxkk<!#2g9Ed~<VNcG~1E)MilW)k&ph@a~!YuV0)2<nf7NVJS z>|v~%aIbhL%InjGyhi*vF3H<?o%16i4U0r;shkZ+`v=q=9>)<Q&%mFOt#-j5;*Bcm z_ZHPJ1vYN?y;QHoqFx5x?7sKd_<Nl%^_(v*jW5>;zNE0~Ctx=SPb&8bv!MAnk5J#o z9BN5LG&;_sVc<D^T55{jW>u1tEM^l5Cj83ONqpdVZZPt}fbUk~12YLes5&2RYBS<a zv>fMyZf7*So<Q4TOncnO2jKizWFjBNc>p~4_fhfPHyY!?ALDosUCs{kwi|8snYD}i z&m^;mkw5h9bh6f_uf49{DW{LXS*=Aru6v{-1E#bPJSPXGCtERwZ*Pr7cao0A>*p#P z;B5TB$mykzbP%lNnm>{Dt4%u=C#i?aP)?bgHnIn}()*L3yDLSyU7Ef7b{Rhteyd@t ze~`p#CH9H$_~E@-8`hL4R%<JhoG*K_GiJ}oN>81ach#(1M_Nk3)p37u_CamzOi}ap zrK))>mC74`mRu8_%O%lPiD%<CLq0>ESbCO1*Glk3k!-E3k==%Ow<WS$O0TbaHB&}T zLI$edySc5v!p^9BI&b?A(4rzu-LZ8v+H*O3SDC851zD(7%Gu!|lhR+olzz3*e_Pdi zx3m$j;(xr1Jgp3ex})OVXgAS!!TD${l}+v9{&RvCiOO3Ix!#b=YL{Qd(!Aa^C2JL> zzm7R${*ilsmwVp+E~jJ~k!G{n6{)?yE5J{ctRn--oio`y-`xb;^hI^ljUS~O0RC%J zdVi-NT^;B#A>B>tvu^yx(<w-2O^^=q@5XP^`}eyX*g7|U&GeqqnUT)&|2;la;i2pP zpIh#f<!NxH%zvfj{I&GCOuheHIDaWWx$el^pFC`oec0jJ)sXo-v}wlo^(OJYUgSBm z4!$3*0n)2ay@q?V$wu4<VH@y{+j7@A{ynf(o~V0M>DW|tHtMtgSC4dm{*{iOZ`$w= zE53aEyM3p0yCShi*nY>jzje0eXTkG!7CAm$4!)XW3jWDSG1WdE6P|<3K1=@KL9_$# z17T$&Cdl1!%^!Rf{@@WOYae@Hf7f}t8aZ3@i>?^lpL4i6tn=KrGy$GDbWDTwC%~}) z-m8FhqS<#u_W6zs0_GwYYgd>r!^vP}pYqL&!9UP70dI;0Gg%}a@3%ES_ALC^^C<Iz zNeUboFEfXic>-l0N4bOWXQ>`(3Cg^XsYXPs_=~PTmtNnSf?nRgfL?8vq1V@)m#5d* z1J4DIPnUqR44h58jI(IlqbAqk0f`m8y3G|h0o(`hK7jI6=Br+u0=V!SBq<|s9Pc;d zeIH||l@F^C7rry8>}BK+409jhU%Nzq!sK#(%gagA%1Ik9=b%<j&UiVQyqxbREGI4S zpN4YI);vGn7ht5>+kjmnvAsE&L!~D{n*$l_a3&)<<#+~6wvg;)?Zl^c#Q0nJdq;?6 zx&ta=l!ua7pi8#(Tczw)qGf(MJKQF>b)12(Mez%liShn}gA?({B;GyG@u0fMEt!ER z_*sDayp{qklLPRla!}?6QD1`3>%1p3Fp$$Z^J$8+>^xqomhk&D<dL44jkU{oc6?rK zqv!1z=2c}9Yh1dgbLOBa(}Os*{RG}`b@`6u0rmjo`73i-`yiEv`yUgHQCDT28XQ^6 z0=6_Y?48eo-Y=+asAvDWbQWGJvDSIy_if#MqzieiTX$D)m#r(Q9HJ?eTgc0EcnFtU zwK8R-7iDIJ){U2$75Wy+<aNt)1SlSCmf0DyQ%!j9y;|V0E<+7dTEfN8ammtf$!z^V zK5HNP{!Il`7M1Om6#K>scR$tFouIxeQFa!}+jGDX0A0@v?ChNRPbkZQcP&e@0u@@i ze~0|kF;;W|_gA654WK2#r+Vh0epFss0{BY-pJ0#I1@Ld7y2#nBnHF|98L+4fDvRXg zNt6TMt#D%!JKQX1k5Hct`PCQjjmnGI!CTm&u?G$sY|Pa1wBO>Zj!tJ0;)Me^{?@|U zi&3}T;49aYbTvYD;a}w?0h7;HEi+*k$VVXE={2*$LgXVJbJ;A<3Jh*z0g}h7^T0RY zfVz1Iw`cKOV`4>gpNX{fTeAYk!LMx4MU`3iZ&2P)h8lsMhKywz%R_rxM0qGLy-P;k zx{J{sqCug?(;uUpPMJ0KAwNs+>_|ilVYh^CI~CUGBY)VzRyi%mhxXGgvD0jOMWhaN zpnDm1suJ>T%J5a^Nxo`pQ$_}-k5!FSrLi#KM)rjCc--zzcaG|1@U(YwT7)RA4{<7$ z%lnR!)VCy*$<t)1k$k|(qq=~nl$PqI!#<l_KB~iy+4p_pc8cID4DIk>R$$03>=D8F zchJelMwfjOI!|c`CT!P^0obTNa+wh}=xmJv{{(grG_O7Y7$+d3Br}B9&+$y{E|o<# z_osONJ)ZX^v8SjkqU}D}G4kDvns<CDthN*H>0PIsHS$Hg8?X}%#Jgs^yC<Xe9o4SU zV-D=J<jWvB=+C26PTac-)DNP)BYQ}AeiL{Up>BlZqqtv!`+agQ%KYd%gG_^8u0~)J zkLYFO;(ni;gESwRltwQzho{L#n!6^Y(Q(MeeKX1$FnzP58Su`Tc6N}yDrR>0tmzva z&Q$i4Tj%+Wu*G^^;_&f9&na!CAuaJNKD~~cH(~nBN$GVw6SX^J8_w29cqa><zlSz< zxpLTc1FKn`&Kl>r)pvHoCtbkQ@TLdVHvcq7t2G_|D16Bi@KN2p3tFv^K^J5&6Yp;U z-9L*oo7RYSv=ek#1N<^gz9arGtD_3+oHLz0<pnM4E-r7o2|mZhd={W|CfGN!<;2H- z(A(|;-lrJY?Go)bXu8UoYHsY(d2ut<8F*9MzFY~s@-AfcV-=`Vd5~<uB>9Ftj4sbV ztoKWB7*#^1h*rAo0l!|%-m^F$+SaT<?_K5yZxhh|kRL*JA`^Ck=TB85y?5Odc@?&h zYzFyhWH$&7+3%sbYMA8GSuQuKjE3KQ)xuB74*VNnc{7BqAsBjItHrTIw>b?N!shTi zE!lxE@(eu=TlOFe4)CiKbUrBKXTr~npC#yA%pyF$B;;-AlWN#$$+@q^JhiT!PoQh( z47zq!YnOj<QEQi_oWM86s87}r>eb)1<OBj^qM!C>`n$Z`z{7+ya5#T7Ie7L~7Ez?! zB}1QNVXDW_$5@b;w@Qs9gZ@eQS$I3+t0uo!v_m=L?SfBcMLd0xL6;Wuw#38pq8)j4 zxy;)W!PBkP<Mi8~8#sY)#|6CV+r~6G%Tpu!Wx+>EJNQA!)o;|M67oT~jm-!gLOQoa zjpS**q^R4>z@0h_=wEM=0jE+t1Ewr|u^3(dUc9Haa~#f|nSteFF@JRcGQ(*;p&d80 zFMnSiAs;mdc?T<I@t8s9(MGhdzebw647F)DwLN~;Nd2f1<jMxyNbPFZ-Q^L=SC=nv zpnPp;TiY1xCVe~)o|b6sywox)uvH=-#t}N_)P3yP<Rdjw>_dL11Gqm5o!>31O}&tL zD(g)v+H2gC?2G<DP44sN1SnrJ@;qaMuY9e#rBBN<U{XAT$fri5MP9%<3wawdeEXe% zS>#0?FVfT@4Sjn_D+_*A(?-A~Io&s=%js3z7VU%2#qos>55dm)k=MDMHMW2@R2F@s zXCWt7aew-cNT<VW$FIjG{Bmi~891)d<VT<he1}5+hXg#92wsaTK;^ywcy$IEeJ>7g z%4e+aceL+%eUHj4LUo%MS0=D?Xvd?bIk0mqCNndHY${CPNn^Jx4fX@~E6lJt_?1iB zXTooT|G;_(@9aQUzy{uBD}WJ)+vhag`ZV0gW;zg~@UrG}-?y5_MVj6cy{(^Dk5YNa zS9<~9On|);^)9fo!-@_5>HP&;Jczq0)7_;5kEaEG`kuza{dp5&fGv2xF2fvXmTe<h zfIkF#QvteLvwBO)idZA)TU%Bjd_AXR8(FUT&RJ-GNDhP?=5wBW{5?(Y7O9cP<;<1% zUiu~A{wjP=vd2_k*Ylk*ogd66Ts**~k@DY({N67#w>k5aGh5SH?J)8=8w$3Z#dT=u zM4EZx_(}Y>Qf#+pZov6fz=O<{1D*xA>Uri&m8X7Q!1*PSr!X#0T3nt%v~?6ml3gCr z?Q;42alN2=%ms{5_|!sYt`0a&;{6<r2TYTB^3mVAinTK{{B!WI1u@RRHYM=u*{mIO zd{McxJVMVzuP*4sF!B)3_kcg;kg?rcS>UHKw<CK=&vd@hxFD4$X*y?>eMdOFRppV# zlB}(RfH`o`!Qr0AwM*`88m8C}^>i=l-7<uFDyH71MTh}UnsWL3O6~m+(%vTJ_7B7E z8`JmU{Q@Zm>9Z%KuS8619V>71SXx`lAzv#k54Ucze6`gF+xUSb4R`692{L)~T~4RH zron3h{|$K5NAw!-p#GYz$k&q3cwD#kUC}=N9`*^k)lPbw!N+05_zV1*2>I4^>_ac< z<0|7mxkPQE==<U0u_8TUU^Q$^Z;VIC{@#U{;71Z`|MooA9+Y@o;Axo8+A|sBz6l!1 zc4aoJ=`yjx8N?fE^Y(iJcOz{lejdbGy=ZsQp0xi&;`X7Cr`eMiXaMY2z_Z`rw*%jD z0GF-=uM+8QM%?=_p2PUTA1Y#T@y!69_ez2eI?j*q=U9xTzkdPmoAA4OevAea`@McX zUgs}qb#mKTA#6nTzU8d+9=yxMyF!UQ;;*<pOfg`cjqEimawCnuVp<sO{EK<zDP+f9 zWG(gp#qFd^15RIc1$2zwOZ9g02VQjL*#pz@y;93d_N)l^Jtzx2_Pm1f8U?*W(A!8C zV$4nrFZk_+8}aT<$O4!1`tpc~0ewfx$;TkyT`qh1<A(f5*Zp-ZKglcFl86O7eJNkW zf4+IdQ#Eq`Jffc(7JZYMA=rSO#Pho~z9``Ho#4eAz@7Suh+aQqJT`=hVh&1shn98- zcy=vUOTo*cKh70t#vV8@p>L3QSSFEg;j6w;5@}x7(*AZX@k;RTS19ArDD}Th<SX&N zPKK{+$_On4Zyw4k4cOspRgSIh^Gd8|<(RcENcs)jnk8j~#_TB#|IT9H_kGaxYb6@( zG0F~)r#1c3%R?LUNM(9cDfuu3_+B9SXI7<`mL3OwKat1j%Vm|yyy|N4&ab`uInqP` zb5cKmAwK6aCf{F5wDg)-5#ruP=vVD#(jVfz#Hh`Dgq4>DzBpg#27lgD8t}~*X{fAJ zluz#!q$|L+0N-th=WPWn=)uosWf3qf!sS=KzI43*K#T8p<M|b)>zB}vP*rO+?A%_o zBLTMre?YRWmn^Jy;f-q8vwvZ$J>;*hln^sW_Vwk687dbe%_5}{u~@C0S^Eo+#$BK` zDU!9-R*&{O755h3k%a}yt`PKawUjbKK4(>`8g4Ex7<nA+aHUlZTP3TVp10w7#UA*X zpdspVWF2@&btPY;a=se&ve9CCPSW~A%$onG`;jDn@pg{%`YO<te5+?PS}Kqc=U8VS z$%WD881wOTkOj(LpxLY-@{{a_<_j6FkVW4@e7}ql4)Mlaa4YZkfn9lu`g`7i9un@P zgD&tdcfRPet&^#*Dh5l3aSFP@<IK#?yC=)l^9H$6BO8<0N1amUNV$c_bT2A{rU3Qt zS;vm`4Z!zsQQS9OjgXxx*l(gfqKF&ccsvN*C10#SQeNPB_m~31c9GX*_&%_W>ZPS= znV4o6G0F(qn<4n2wifFP!^l%9Szk~j`wQ0R)g5KP%`dT!D$@22Km68VlRHGTwpz$e z9~_rY8h2ju*s(?5TEN&+?Mgh>byv{9&#yvXuIIMbEtvxX7pHjNa(y7azwfR{bmw-n z?+AQe_%&AQFN>V#F&*kBH9rko7ctVA%R&~(z3{6H?_XqlV&h%>xh3{YZ7<y)iL*1D zZsh_`#`}88UfzxPjC}AQbkHeT*XPNsR=Gh9lg`<C-K|y7yE5oqy%f_s>NBkT67VWz zjX~0-2<gbg*veQB-ZsNO;dIK2(aC6^sJ&=|92J;YF4~qb;lg8~nYd49jd{3l$XCOa zGRt*K%1Wn|HNsxhqCLnhla%$lwZ06at$AAFK1HMrad3}zKLQ<CyYW*4E=$`CrhZEr zZ?6%j=(v#Iq}1OK0Q|j0EjI)*^Y9Kor<659emyGn;-)XFqupr#`qq@A%@qA&KD3Y2 zk2n|pJs+>)@z8;ZenEUWd$e-)zklVBJSa167IKpulN;jmq8nJv>lRkjHw`}1sn737 zlX`DVDQ5v&3d=}^9Z&11yFJ}jcl&}5<=!5WbC(r-NV#1}nN!m>zpuk1DJLmbs!H$u z9r+esT&<FnJ}j3MKVsQ`Lpt(acm3W^E6C%IM!OdSj+^e`XIPPz+K$RRw#wQ#rzSZ6 zP={NxpHweKYZZL60nYZ+6|H1r-6)&V7vP=yLG|eN)XG*@IXhfMWr{ZM$St^kCRM&Y z1$B4?b+7_|YKJli|F3QQbT!JR@^1&M58yY9x==l;fX@olh48XVwuju3jn~Ph)u~Uz zWj$!#puHQ?>bV*+x!}EM?IP-Hd{AxkrTY08lUu9P3e>4mn*LCgH0>eC<VfE;(b@yl zzpueu$@8?fLMa_^<OBC=G)n`%8CN}Z`zp|m@Tvsu2>(jlqrRMWWVb8h=+XeXEC5~B zq0L+^rLM1&Iy=O8%g-jxDV`e}ce!9Pg@!(%21jU2_bSn+JpwywlV<dH$)c^ZHmN&` zq#3QlOUg?t)7jy3E64`0#=9kdhmx#@52g6XR;=5vu-eg9DM<M`Wo3Ouny~$|>dk?p zxc<1hu(dBq4X<AA9@&<JZ#HqCPxl#Wc*ufyeVWSsUKiy3gh!3!FL$>Nn(QHJGroH+ zy0gwQx{TVWPodpc5LXmTW4UA-_LFURx;#R%Rkt-Y@R`+U6UmRkJKcYMQ|o^gV;Z?3 z@?WW38s|8R_QA!=$_)8X4!u8)G$s>`;bn$i%U2_>W~-4rr13^YJM|#aQN2cxm+IV} z#L}tWTS13kh`DRrC#SKbAK-f?Xxb^Ktlyf<YEPO{JU;|IhD|n4pGDANF44g%?hl~d zB-$oPv+k?oevPj>1RwNA$n$;to~&Nb+J$`06`&>R-enc{`M4iWSHoTD!e;J)ybY(| zIaNGUpTl|!OK*lxbz&@9I}ALUrIhu9kYTDvALXS!AHe!oMZKTwza4e(qi)2Hp%pBA z2(XAJWZ(0b7qr%Z4pcvaMKmBdow9ZPNt4xcVl-MSe3OG+@6J^t6X&OkxdCK<jqTF! z*hC+f#vY$Ayc@#(I|=;TaXYG|`!Uk}fyI2IME=Si#6mtZ>mD|<T-ZZSBZ*`ySF|&G zKzqdn-;lmbR^HAiCjLFrhqlrR9t=~wIa`fL`wLo`LycskOqx$UjrUQiVZY=mw&H!E zL_gL|{!ylNJhs}a$5zx|M=}}jf9UO#OPi~v*E56sW*-a0$15rHes}CWe9msPEvFUf zg8?T~MyQ|v>ob`no^G(U6iasN`Ksp%zdXL3$_A{ynOsj7*Nb-cNQsC&(SAt*-3Q~( z`_0srdwP*xf6x1MlGszE<BytI4^KB}4(MqR^X;H?)R#*AXS1m9C=LDz?k`BZABU$W zUslia4Cu=JSQ-naJ{0E5@ZQhD=kbo_MkqRd6U*T~PZl2o&kU`Ysljd$J_-2YadEzg zKFvw(x52;WJ`L;~e44%PJ?3#55Z&1f7IvE1Vwx|j@UfW>26S8~osRcz1Kut^zQ7z7 z7VB$B6dz;)ULElQ@CU@W(_Tfg#L6dlGX=c(_`u+IoMzIE0{426myZjaYpHgLvF<_} z(o{Cw!QsG1POoa19*)N^(Rh|oW{$4ABYYG#naXpZJi;}r#S+LtY=(GYRq*1mzPjq1 zU0imM7Uiruv~GHMDPm_G|HS>uq|ZMeA9sER?blMo<&Pkq>63Yk97dc-cxF&+rt22* z{7#b?bB4aQ4?UnhP5SXm(6Pmk*MWTG$C4aV+S_>^hv#+8pN=2*K8tv<(U3o1l)d9N zz@Yp|Ggv!~s}a0Pw6Dz>jJH7&mqUl=IP(5C;C%98^e5hIHB9lL(=ziun#)Z2272B7 zrDT)8pth|e9~p7MLMb_5AG@V3HA~&$Z(2V(&FVW6%$v5$3tu<?wcgS@B&EMWgH>i> z_rcGvU2PVAG^MAsp7UyHh$+Q`rer==52Ps`bc}x=>@9r=_}0s6@J(*xrni0)bRIBG z4`q=rromUNat(ZzqU#Ja=mo9x@8Em!apcj<p?*f{A0~XL-~Y|A@}mP%a_i)I0EzsV z7~g3=T*M@YewfSJFMzf~pbydJgP_e!(8dbdcptcb)CSs6thgZ0whQ`pqzb<+ppi3~ zH5S9CAsTymjO_>!&#ds%h>jkjA?WP|9qyE@tzB$%*<IkZ5AjuS+RGgu5_4oUKIVAt z<?*zG&zG!AqA|c+kHoTQet$dBA{qD7@#8ehvebCt|0q#Yx^)h#0goCzfKy4o7JtW^ zOlh49_@tLKZb9%rO7P7hmM8hj!}oo%a%KbhLOL&IB-KzK|M+vB!rR8_Dvez-$7k|= z;TTOIOQ4BoP%X7dX~k3GA}OtUy@yj@?r=|kxx*@1`(2WwmGo<>c7E)EpN)^_Us!7b z56zr+Huw+IUA`mpaE)&KK>t~7j@u2nLQh7ikBajnJ48M|;=bTaD<69$Kh9GCzIeu} zT9szO@1@{(7WnNTe&4@&^dn>CZQJu_?#i2?M(m&&y^ElXhw!cn@5)Tpk@6(XcJ8+X z)GU?vBbfOdN*0jW*6P8zqR)bXM}i00Au9U<;>SAk=ud{=Cr05jrNDO>M%snt?$(pI z+NsTwMf*DqcKNFoc9{B~Dj*Y;5@fE3@%S13I``cwEqGVJ8uwEll!XPz&sV^MLx+Y+ zR=~f2?-pp5az^Oee)w-gD5DbfXYkLpSbIk_Sq(gYHo7yi{wC0Qc`!ETK=7e*Hp{e* znbJLgdkS<|4gR`WO1}b}U~>Z==wGKKjl8k#p$@_yao~|R;1~Gkt6QGLcfxOdR%^#T zY1#;_1*wZPABE51pghCd{2k;EP`}wQ<;m(TS)t`&4qpeKKdh~nc>1ESpO0wQTDMr2 zk!Ze;1b#EccVwko^fzyW{878^BKoLXk8F~a)@Io;;-fyy11#_n$yc2VTKxC`jX`9G z^!DsV4{LvRt8>&weePd2k9Jl6L&-jQ_LiUI&o24N68tJ=mni9TGP<f~m-HR*v{s?5 zBR|#|VWnkRtZ^-H?*dGx<QT~V{i&|i?%2Gg_&F?z{3pUclVrkx_j&FE2pP;qo7i`e z{EsXiuk_?g0h+f|&*z|IhKk+V{Mt$V2}Zj8T%RxM*=JK5*8=u`z@KwV!X7_JeTI_g ztBl8)=HF3YIn|43ybrdr1bVd$zu^Nnhxfs^UjTU7rsP21{ToLs!Dr(Cj%aje@BK?h z;eYkCO9I9x-J-4h6YT#G^78(H1C}t_`vA4m)P`C=ueR~NE$FQWdRqX!Rhrg~+QD;I zo_$w8Xwr~p35fndXTo{0)Q`>QLz_pJaaj9r4mTqfTLXRcYVAO=W*2e*zYjJbmuy~s z?k*4X9Xjxo1ljq~3~o~!CFlZth(hWYr@o|S8*8LKFPd*cI!SX;_<T5Ah0i5xzMO;m zas;3JB7$F-#Ud2LY%tJ&^<3VzhJ{a(SZ=O}F6C3vMev_^FmMfPhmV^9g$ZYp+~=?` z@v~W$8y`YF9pK|Pez$S7*u;ABz(e2fz(e@cd>mrG;91pvOL+4qX4XI^J?%)-1)AEZ zO-0@Xz=`_zy@RZj$~$=t@jT0uNVZ7ke*>962c7Jm&JZW&jO+%F{KxqiPVqGEBRND| zlo#`3={smqJpQdb_N^xqu{U&t)+_qu?0zfgqof|{$eNL9Cp=Strww@8LBCXdt8CgZ z+K`t@ZGW{JIP1^U-?a$5`cJ@te5OAGI{gIqiTqiTw;IbtSvz2mwIcA$fpW7^X4n0j zMsrZ^3;8*_s?fG>H?iVM$Q<><yC8EJf2GXnaSWA1eO@GYhftPBle=H#$K-B*ew^H0 zdOqr6npfDZ<$2BkCztS$WA%sMy#6to<8TR?T>zPFK>ZuQ=Q`A%VzPhMU_W7ieU}(t z-J!=6XKS9D;P)A0AMf!yxUZ(DVjRYq${HJpuTS}BN|Nu0_xSWMga7vY)vP_2`o%9K zN62QsMq_3dZly8L9xt^GTG~2%dm7*LbidcqdBqqItv}%Njxt*q_0yhSMm7cMXrARJ zz#+e+l*bFAy{SW6wA(FU1TWsw7Rpw499`!g9lF0^v?_1rt_7fj*9ux&*%`=xZ4+X( z9@xg0^I2fy8Y$3@xTt$9IzBEv(XSHg1PC96@cF!Y)Mh8!VB00=HnH7Vh_>&V0v_W& zLUC87hBIQb7yI76HBz_EJ31d}=)M8>A+$lR{VbqDwrJiJ;qubOXi+b|KMJ}WdpkLj zs2w8sy6&=G@|&Eo_vMq{$B&PXuQ4&no?7tds{B|S@t-2zJQ6>~d-h_qo93Q$U5pme zT%In-M4BPrS?KZZ<t&hl`aO<(+h`6EzAGkQ^(emWgDpA^n?<oW*_fpZgrD&Y-jlDf z8+p=@m#)9k){7t0<3B?@F6JRhh;3vZ2fmpX!-4$%Ufk0dww&gxru8~?GK<iBrq}Ys zn7DDgWIWza5MTb*0QVt5lZ;#~2BUbD`}#$xHSp;Q3D=Jo@G*EEzy6>=_$&8hvG!I6 z_4$kzQyz-J>ax`c^#RQRf19(#oSY*Cq79!Mk0p+~pElsOmQ9Yk$77MjDK!M2#)`ry z<89EhQKRiaF^`+$dmn%2@Vq&J-lAOV4XkD;+F4CwK1*>Ade)Xn=0@JnPI_^3dBob( zu*D^1+GxDt1Y}_N8rDSdc62Ul8eA#nW1R4ac_Tw})h33t#<5(T4}6}{SaKw>zZDy| zIuHK84gOy+nLMK=bEsmWSXYqwU*!?<`?ww$`lDYp<%Uimo;!{<k>)SO`T-~V1%!iD zLL7suckI>(Y<vcdfi7M2LPyP5v?mcP7NNCOwDpbeh>3}{6Z(8&YU>E59WYaI?E=ho zfVlxM6@}(R+E!W@_R-kP3tgzY59K80zs%QibwdVROpQdf{Sd4sF`vhWxS0vwdW^Dg zIXJItaDFDo;Jk4$F&tW#WsENg-p132DGz2Gep}<qInqnOA2nIH9wi%O^;y0K&6iOI z-ovr)E{aCG2f;rYXWoeNe<L@)6>XEA{jN=YCwf%cGL+LfGuq5bht{&vVVWyU{&o!u zLjMCpACV(8r)@ZD4o26raP(l(Xmok<Xw)P%MceRPBbP?+K$;pk^7dR7IeWqqj6P$g zemq{!M<FL@2aAD6&qgyVqA_oZIcT2RGt%g?&I3#N-1XVO^&=+M?SQ|w5qeDX14)nd z^eX&|ee2vM12q*9H)7wR6KW}yNqzh$(e_scmxrxQS7lTMO*GEo0j&bpf@TxrM$yh} z5;0c>`RqD;ih-8mU6<@=b)IpSke{E359ycT!xOF;AMVolpeEo$`uoNQ&_DUoyg2gL z^P*`IFYb45Azu7q-j(s9|INRE7fIm7pmWQg#``BHuDev&=^akQ>SU|PPTm6A`!(N- zY~Bn$-cu1-I@)}c{GMhx?aX$h^UD<x__-r^$H#Q0YyFv@KwC+D`kMt#d|r$X`5#6d zA5D@YZL%C$s?8bu$~=9JR-*N;@pz6-z;pIgc%J*h72$cq6nL&lfagD60?*Y7YoxyX z)hVp;rrg`^r?`D}p%}At&t^5&uS%;<&Y?MHwskb#Zr0`r>1~P;jw%Z{1h4WsvF@vZ z;GiwDvoE~GVU2ZG6C4hY;sS}Cq4^(E!L?pj5dj@~taVZVe(@3Nr&ktMM3k}eqckTf z#ljAIXS2+dd5mm+^{^==q?CwxC9YT0`>AhOv9MK22@6-ikD`7)k8)sU)tBU1gC?8D zy`QDq=djG?*I46MsZTm9)oz``(ih;JGLN@SrB+-EP>yvT>0UHvyJTHy{W})6;eDkA zyqy=@yJG^rE+f7bN8J{sHN|>qd<U)s-y2yC;TtsI>jAzi@t$z?;vR8J6X9>Q3cUZD z;z`NM=h_nP+P8pJ!~Jq}PSxm~iuBVs-l?HG;G-LGf2Y@g`yE=J>H_6II~u+;eHV?f zx`CGs_}_+Wr$%po)-;a)LXF-jar7Pn-od|u-h^YSbu#WoyqW1Tc!RDSZ(KVyy6boo z-EY(A?oU8>vRhr3pnD>}3;vepm@L-vE(6}=Ly(St7<lIL_6abugx~3g-|2_V<?k$f z4qEpfnbvCMc#1y2WAVkixgAky3@UxQ%=lQ>H*vL+e_&yYX&jyU2%6>W^$W(tyqn4E zrVVlHte9P!3fE5o*9d&sE5`Rjtma$Svi7pMuJwNjzL8mKBtDj$YWxoEwTITSzOCVW zUdI`>isr_PwddKPqEyySb0M4)%5<zBxv0_TQtLwgobph2DzEe}DK9QwYhJ@jTR>Cy zJT>V39t+OWVtl2gJlscp3p6ilaC*R6pavU4ESO1g1Y-Jn#KaW8DV4AvNi1T$NsTnv zSmdW3nlG$I>MK~FANMq7<VM@x#gt`=<XqyR{t2}4>(=mj*rCbevf8>wq6ct1@H(w~ z%s8yivvx>g?PWK3PvcTqvxycgAmRNX_)hD-R~{r^iPA2V2q!hNQldGAG=`-{XnbNg zDhH!0$saPqAF_;|q_u|!SyOZ)-W`=nqh;`imdlZ$Tjj{eGp1m)%^W_>gg-Q}ydv@_ ztzTaY|7E%6znCWaFJg=;%X8>j(T)QDz&BBk^Rx~-ZvA6?eZM?KeYYg2?^pkt`ffu# z|NYW+?w4Y9{^%9f`C&tyKbD}*ZkpfuAoYLsmJ)uqNxh|zvBF`K;u$hoJ!Mh~pQoeD z?k%Brv_{5mhF^<V-m9g3eHQ$nIk2Hw(yB5kbsf!F^Umolp|lNX2Rul(dOvHVF=Nlz zu@buPjYezTV*F|S!gPO!H><Y<_P3-Dc?hpXz$*nf{ct<C%Qc4%c}vP3mH2w4e*vB; zvw8ZOhTnR(1fx02#?uT+eB3rg0d5Kl(6c?w-_eCSe#%1qWW<Nw(sgL3y0WNFDV`r7 zRyc*aZP&`4I8GAl2WkT^X)Le}ZOpglh(4J%=-6iHd^7A@1^hw(G&Q2HA!4ZCNjF~) z68716Uq;W@kk5xae14C%29EL!^L-H%KHq!$G!`-XQeIh%0pAHeZw4&h-!$d?&BXZL zdnNdy&3hpZ-!*?Td?)rJ)fHDnTuH3E!>+CQNP>K!9KVHid+)o6w<QhtnZspP*0_5b zADeo_vELkMu4dupYgzan8XE_H*Ll=%1MoSSGP-O-(*9C^i#BfDAT<tAyY!&iW^G}I z7tDC@=tCJS@TrD%&l29Lg!i>*vr<#-9(-Hbu<6<LfZLqM8fgth^BmULMSaS4mgcVd zmlC&C;rphM&HRAA4nko?*>hsC<G;TvVn<_N70rQZ{z6iL_Y27d&9YS5TrHIrX)(cT z(C_PL>@q0&-0P2sbx{8ZyW+w7bs1`8$YkqBySu|9*;-fTvB3Ha7TM6U`q>S|Ec_pc z^Sn#c@JUn3%5`|QN>?puSi-_Iwm|Jj^Ytt|Q-e=BT7MjR4F6^-xL&}eHrtDQ<ZF4a zSHlHQ`DZrZc|_Zz<%_7hNyDG!wQxF&Mmg`S(6fdlsg&*u&PI!fKg64Oeh!|A*4BY< zKL*SnJp1$gzxnOR8}ahtg?!L_Q-TFP_B=N`M(0mybbgTf9OGaQjLV8+Tvpb^$x7%7 zWyPz>ibC^_QWZPNOM#?Jk{8#zQ7$iDTpQx#Mbh@LaA-2q7l{_uqwF=0q5Xa=J)l7T zuNP%Zl9NJ>&L4xEd?10G9K9IbS@2iN$xKa7X8Z+mB4mV(%g8BBM%Ei-<Qtld_}`i= zBMn9wxmNi1<{7u%zvpzcHlDA0G?_8#R`8kdb4Ol!Z77j_iI<;+2Ko8JJ3@XoCXk=q zsgrHZKal)b4YtNUx@`TI)h6Y=&T22@!Fr>Rp+0H{AwM?A$9BliZ4C`vep1G5O$wKt z+aNm|0Jjseqe5mnAv=Ctt864YKbT-^iuHAA??ekPCnv8nAt%joa<ch-mXqf9NlsQ7 z<m8W<oK&9G<wR|2_%dtaw&Wb-#5#p7d26y<JpGgBdtZKK_|%oi#rI~#<l-ZmT-={P zE_?~>%IY||SdsFV$;Ecag>F}jvf<KXgY1fEhhnlpemvQbb>Kf@UJu!lEZgO6$vsoZ z$f>daf67RSK}MEZv~`znO_q^EXALr9uqD!&$+kpFATvMEY>8stU+QcDZDdwU>&_Wn zpA<fUvq!9-ckUPC<;r!mZg0*KntvkhuUcXqzh_I5#_wk>F^%6lmsqb8b7HxF@I|%h zf3iGd{7G#S>BI-)=DhRyl+cqpv{Ui@0Q8>v0fNw>dfY1uVtp1~kLUwv@Q6MDKL6K~ zc>ZsE98B_42i@^PR(TmylzT`k_kP5-H1_7q6a5cYT6TQ7R7cVes>2AuN#b(FW4D#R zt=r<2(lc})pwFb?aT%H%^jw}MTz(Och_;YLhC%ynGh?=>kdK=QANxw^SFhEhEMYzB zC)9)1E$jZrkb<~20WNh0T+%Ori;ja8dQ&z-;`0e<4WU0JciAxcO$%9=*2Oc(7wmzf ztW@IjN+^xjp1aH{i8MDE$~!+Q%Bv-OZ&1T-#A#(gsqs`2tM%bmHXm^zjkPY8cprAz zDyeZdV3zHX8fhJnHHG%H>n(NSnbx4VsSh%RMQA<qjeuE&D`ei$Mg93VurSSMCOnGi znj+%G)xQ+;Al)=4@{ri0Y=tD&gRK6on7hos9Tw@<;hP)w#|C&*7Olmqtd~}C{}TGG zm|1OunSInfzptaq#EJ_fVK2LY3ytNMJ*Cy*u*CcF%l;s7`&>3GHPSkmKBW6K?-TY_ zAH{W8TN6B9*QX}db-TtdnvY_=A*KiVx(+FAt4lJ>x49Jm=F6Ao-%B%M{QJ`X^6!71 ze`rUH{OdCC?<*Sr>f`w5H}LNs1OKu#{@oeJKP8#f?!FBF2FLk_c1`D>HO9XWY5cpv zz`yG?{w*8lpV}1TAK*;MKYTOtFFnS;`GouV&gx3t57T&92_7o*4^86XAFq&yFKIkH zGKq)4<*AAFy%Z1s9R8iZ0{)%Ljq&e^3;)Z%|0({(&mZG42HIkqMOwA`55PH-)o#y( z@Ba6pXRR`>as2|_h<oN~{_TfNtUXU3*G^*XS84aV@Sgf}$nUmFv$?-rF7dfE9ERP( z*ONOM@ZC*oIcP1HG-HVu>1f|0n%l(J_&`3+;hyr*z8_XRd-1&?Ke=F3^S5UvX>nL8 z{2IBuv@eAfZ2;V%<+K)@HPM`Wr<IlZ)6F$qw71ucBwk*ZMeue5W5r&Jm=Ec<NZdbC z%<wsuv(g2~`zg@o<Sbux7xf?4Q~$AT-5x$ZC-A;jqxm$Zr2!o&Jz@si?eI%N5-S=8 zE=m$`Zzw3)S1<9stoB(T-_w27)EBJSebvLaQvXds$$E+jiq-IW#7N$1HQZ^r>ORC; zD+{Hpl_Ym-AEUjj93BZc^8Oh3I`KXp^?4h7ydLh;Nm+LC>8Q^4RIq@6ljWhlRf4k+ zeii3Os)^@!Tjd%W&lxW-NtAaAwpN#gUx4-m+bxYQ<Ge;5_`8MiI37cuiTfK&oR0|J zM!!U|sa@$=|Izc6{@zKDe!6zOa0yt(@>zoNS%N&dOdKudNKn4X@Ge8ke>uJLr^AkA zOs#h>n__zR89AnReoIX6o|iAJcZ<yLpWfY|#kPHRp?8(`|F`sRD`Yy69=)A(WqNd< zL62^{LOuHU73fiuCdW;2a!k)x`g<oq`sv#B!X;oC%a7NiCQT3G^BDCgzI>D6ovugm z<Da+uiSLIM@*)=3b*V1{zV9X4uu_9A{hJiirPs_cUApfwx^&X?{^?SK)|aSgZI~Zz z*m}|>6W67%P3TgSSJNeLoGz_5=#tl{OD5JtYY{hqHpB0BR{NnxwD-gwKCegMyhxKL z8ef7QuA+2S$!hyBWHv}TWD+{$Zt`sD)O4sbUWe=~K;Ku4>yVA>Q1fMUXxoLqvh6Yn zo%{DI)VWVyi0Rx8%a!Te-4`b6+|#d39-}7t-loa@wm7+`=PUialOX+c?Rw!7u#Dx$ z>)kd@Z{qV9^)9}Ali}SYz0>xW_P#b*@4T03!#*a*^lpg7^e)+;ckR-p^)BUo)Vo%b zwpXIohPmzkckA8Tae7xIWgGM^Yh0$^d^@^x6RtE*B&JW<T%WdBE~QIZT$lL%^KZv= z$$m*)66I2!!*5U2rDLy7-s78Oc8ey<TjFGyp0D)xPJ;B)wd;jTz%rH}uS;7rU5L+P z)TQ|HO@?=*OY!q|Nr%SA8mtq?8aB7A9z8Fy?(QTt(v9ny52<15H?5Nqvz`YJuV#^; ztTu5OmiT@c)zlwd@Ss#fabk$E7b~Ut0WZ>3;p(2#TO#&Eolkqg^;7?ALv<`poUlgd zuf-FxPk3hh&GC$_hoC)FxlMu$(zz1ru8-M}71C(2H>sTal;kUtKY56JN7w{Ux_{=n z>q*}k3_mL(zufuHLg#4hvvZEb`vW#14UJ<}ZnmUL52|f-Yox%YbJ3lAZ&J#)gpbwr z=<{2>*NXYA*3FhxG)B~b^lnQ<1h(ajoAypXeW#-7y)CQXs|UQ(nQRPY_m~YZ&43A8 z(j((tJz%a~V+~leZ^I@fG}m0&nvXP}%vbqbuXA=5{y6INiR-y-i}cThzLK887LlIP zyb0K@qI&SJ?rJr12zokBt2u&Jw6;;tv)z#Pn@GFvYMMjVTiV>i8ufIvj(r{KXO(7h z`qOvse64@-QN%m6*ODyyCqc7i&85<K-u2Ub)$5a_Qft%f3_b6#$rf5qwqBDBw`A-0 zYBE84<r>$w@cu?dXUtAqFLPP>fXtpGJA66$K)NfHk5gy=FOd&hOg@%hMn1|(K6F}L zMn2O1R`T&>*7Ux}N8GrS(Z(J=OM8>65pK8mp0;!r)I>h4ZCSmA_|RgYx6TI{G`@%E zP$32UPS);{HQOgkrL=b2$nS;VcOv-SmSZJ;iIobQ8IuvJmr+h8jkhJ}Z=n5*H^Y{W z%T!!hM!UTU^?Drjr}B85PkY>=+;fP}Db`(!dPQ$#rG$fvaA0agw)(<vTt#~aj}~u6 z9ZBYl^6ZdUlM(;T)qLKTp$-QUtaC}Ye?e#+aJ^uXJRb#as~53G_jFcUl}2+8LgRA| zHXUFAn&({%U(5<0&YQu<V+*aQZv*OE0NNHoPkBGE_O7U9b;(jZQ(mGI<-beIFKCAR zlX<iy3vlN1e&tDR;}&Neyllw;FF{+H8JAv9OM9oq&qvtY()lcnqfdUn`q^&)@9oI% zCjS+AA7!IIE`Yw#ocHsltbVe8v_}z_E!ZEve<XCOoX&?q8nR!{y%pX0C}f-C(KIuI z@=|+zET7H*UqGP0LeKRa=X2RJLo|1m)=+*KGDGr@dr!exel|ncIw!einEdBzv0t9| zYB8^>Y`<6sM9;P)zE{@a2FL+yY$>JX<ItK86K?;V5gRl3<{2?&kZ3KzQ(FU1J%PWs zpLpttsn!)tB|{DWD?@*_43$z_3OQ<;LWb^4AVW=;kfFH-8T!|^E+s>1(<B+Pj>}L} zA{n~NAVbLp8A`OiV{gkNd%ugn^WWY3-S~M~Q?<*mo1+ip!v@0ElYEg}fey6|ripU3 z&f^O|B%}SKwgRye(Tec%R`)jHzKORXkM)+~zEp2B^g7Hp)ZyjRu{!AO+o`wSJT;8J z(cx3W@qRqbyM8DE?ybPxZ@aa?%Jyb#L>*et2Kr>a@5bUH$v?9VG);X=_%djZXkYH0 zAnk#-e_p_kZ%c6{JokJ7d<SoN-`YTQC)IB}E&O?&7FS9~X<UQRo$xt4{ws__t{8u` zWAXUEc}C#hb%pqU1Nff;ZrM}Rf#$>QlUd_0`8hG*72w+8zxdHMJ`A~w&1YG^rCCb9 z&zscNkqN)2Q_5Q3dNEr2@Dz0x{Vm$MPDgD0f0vmRRUp=WgU&cnSURnLd>yfq{quBQ zBCB0Dc1v5IlpLTvL>jWx9Vu4dk%f6FyC~lNfn*K%U+XQUc3jUpRh|V}o_|3e?|HR! zh_PaSdQ9d@Pfwm-qvLIZA5gVN4LT+32<<^#S+ACozE(1!i&ZQ(uc#9DG%n1LCXMDV z7}5}&N-M45Vu30vi|}XAQbOBcXW|@CuOz-VNU`;L{{!VWO(@?wvHbY{>F*5C`W!9p zs4g#cTZ9dx`F_7Nv)awDTeOGN<5#O&4#3t%O)N}4BkiF^>vrCm4ZqPu=d-Bc<MQPB zT#5TjW!_(!Wauv?c+~f^M1pT$9ov7m{Jm&xHjB;kqIQGetwEaltJNzm%VH>t%E^_) zUb5tSe|#W8pJ^POYUAic=a=YoqIHRvqtjl{X}2Wkv<7&_*ZUDRvEH;FoXt>g>d&J( zQ$DJ<UXQITR*$6#>XB$4DZLJU3t~R44vQ?YI^>|t(P(Ei_n~QD4cdDG@O2-04(jqY z><+DaT!gw4%?IYHO_!U;SMOr&&bgE47m}WSj^^-b_3t@Q+)6Z%!22xi+xx}$SI74d zp1hyccwAyw2V~%PQ6hd%MWdHy#b`w990x%keU2dQrx9P*E9G~I0e|C~phW8~#ktb5 zhxTS@H?zV+)Gx<vk8Om{C4pU`^I2%UkyUcoY3>K@Kj?zp*ok(=Ddmi0E#BEdYcyZ| zLv-g0vam5$DXX;#HibW1wP&mL44D40Xc3)vN&OGJKJXoQA2iAd%8I>#uW7pS^`q}s zJsx|f`RLQedRYFJ^+>$WRm5ZtEj7`)GDhcohelB^S~E;*%ziUntT9_^Vv*4UC9V28 zv#6Xq;)8z91P*<)Rye!2BwpA0wDrUVCe}^o3%>0Xb7uFzw{w}qTC{!Bxvds!(SodT zKh5Duur|5Rur`@s4J7-jk2@2ur6%3&&~VIqSDbxD&y&}%CEo8U@jkGER<z$vrb(O^ z83C;>7-;pdMypKJhxUg3tVXM4$x3UPDQBctqu0BzW3<-Vb&a}Zsx{bECb0&))ES!t zO>|=#-5OCAi=*2Wt?35c<^g7Nf;HVbod{Pc0p0Ebyw7TMn`#eM(VjocX>~l)6de4z z6dcH5kwM^m({#i9-`B-D4Dul=qS5Z5wP<&e&B37sEHb=}1?X(d;m0k(p%b_sr2C}c z36m5!zFb24B=K{XZEw(7N`03t_i97Azj+_Z{oU&m%biNEMzwv$XfGOCUw#3y6ERsr z@0gN8%aU1l4{#$N`K2Ur{twM@-I&C>Yw@EqgQ3f{7pAfH*Gw}*4)FO^(+t`ZmiAu? zJ(dK#=^QoSLi_0Lhy6b_jm|C;YdZJ9Pa49rgZ768+}AWX*>Q06GqMi7PG@n8J$49Q zIob!>L!IXEy=;Ixi031ChW`_$H3~jyv=}m8N_l8rV?VxCn#JDYebZueZ+bnpZZeV1 z{jKTv#AWC>VxVJ3938)5pkv!*==fWrBkhj^ShPouk&a)CqoZ#!9h-DIHW}#nSpyxf zxgt7F?#HCP)3kk&JT(&QsW-sP7>7BrFR|0c8v8DaJz?jbiteN_ezE6QW@sgg?e~^{ zO6;F@dW!wZY2Q@Zi^i24+c(<!O>w3N%@vRBLGHXyN)mgJlTY&`>?Q3z&(kAb8=`YI zrrh_jx3s`Ky3C7s4Y6^NEVCkg&+{(SRW*(Ngy24WvH7UA*WKSLTl)*fnvZsBXI6Om zJ^VY!$~lugv@Tx_S|wY5ndBJZ`&}b%=K=Rf3dKi~qZRFLq#;9`ZS0mDE4>+P;@)}_ z_8*%(E=YCO`JwNnM`OnCrpEX}JfU+B)>R{(8e8ozXquf-HKzExw6orf{CGSn;&d<k z=guU?=eW}QeZ$e6<P&DGtX6t{AWQ6VKzqXTI%u?~F>CT!$IIG$)r7KWZ#$h&1$^H& ztIcWA#uwuD{-(V$z4<Jx^G?U}mT6)?ac%Fd@$-}7<M=%A`VPL&m$pwRV`t{&s_)SH z-DeE+xd(Db`yD#SFNOXiT@%ey7zTY;FxFq$(6GflpKYPJ19@h4xFs6xfo}11ds2md z5u9qkQO%Rjk^+uBDxICh)FxU_<H7Y5ct&Tc*zi1p>#)i0X@=a83~gVeZW%FIJq@%+ z0?lh!sBZCJq;vb!rjx*_60vB327~rtC;*I9JX4+_4kwkv@pCw0Uj`aOA~;^aA^e>4 z*cK<V(HR>NHC>#)qtod)@Lg}9&+JoNua8hnTaLDl{07A9k)eyx?n8!i2rsceUihhD zY8UA2T=)s#3+bM&hopm~f24;6&_UYQ{UOGRuh;hRpg8B&)Y!hOw4ay{zQpn2srJ_s zXQrUt4(ofvE=m>qw-y=TP`@rSjV|MOXnVtMmBrq$Q`ra|zZDWI9;%`E*Hl1l`4D{i z6Yv3!qwOEu$O6xcGp`-fwQ_R|<@Sx}cJkm<{cN79lW0dE<L7|~jc?MPGIMCW{<s<; zy`*=QdCRGPWf|Gma&Av5XwQvevL&S_#T+3O_r)jqS+FAmV`PIJo+WtpQ$N;V><kn( z#%1KR@mv(Xp9s>Q1fA&&hoCIZZm@wK?xyvl)L&JVm$IvDkd;2e`2L-qpQVU>G>tlt zaQ`&Jey`^Z?Q$Jx^iS!m9rf;cAtfe*4}fMzaZhdMKG>TY_@Ss%<Q!y>=C>4xb|h2? ze|H$qZmGA4^tHgs0yGB+ww3SGRBT~^a(?bXcIazE(Ve>iqZeiUC?&RMTbsB#L|dF4 zI*pjNPGUt=Cj8b2@q+NYJKeDNnjv0Iw0~vQtKdh1{?vd)i;K*x+hL|Ncj!D`$At5B z33u9?Ckyxw%dDH~cG6^xJ8Re4zc0zp_vWWDL}kn7Eza}8ev5sN9JEIlKZCX5YwGA5 zbk2n=;zXB~&JN)GI+BQAbdEgLX)dk-gFPJp-RccI%FyJRd<~W>Z1%)+k0$T8*z^17 z&e6F2NYBw(qz1U}ofP(YgBX|KdxcK>J)QBGHL{lGX2IW~`DPT`v42)0y|B#>$!gOI zvaJ_rU5H(*3;8%}^pf7RuypF@f9P6uizh1fET;7@dOg9r4C1}y@>Ms?5odH<fc?B0 zI8|B%PIFVlKDf6cAH`_<WMxE_1TM2vVr{}NPl`5y)}kp9_zdMNKzpOOS~@%E3_~YN zJL9J{IydlnZzHz>zoBEFe|q}ZO-CR4{rEW(fsJ$?)3?4=O}4NwN65@k$Ya;fc2!gC zzOh2^koHQZeJbCz$K-j`PJSotw+mkyG<waH<9QWrxL!}98P)aLIs9DvLOQQor}N^A zVt-pI_fPg%{cCYOOKaoh)z1cj1D!`jHmR<H9WF@|`|vt}_szdgye}llgU}D=FrIUz z@2O1t5|dpmgHL&Ut?;c0pU=Rq4m~dVl~39y?rUT`kBZ<^pJp`L=^u(7^HU!pf;E0_ z!|OEHKszgy;<wOzb<2Wz>Xu52w8{w?se*iXaerIO_L&X1r*~f5`y^#0+SMY+VPOUQ zfQpOJ9%H-7+l-d-uot$1bc6P*(Pen$Nz#qI8G0LfEGov?z=I=Z7Jm-2v7hMqaT`hd z^-#M?wsD&QcA*BFWb7`$-pFF{%OKidqT}mm!$+p85$`|q^N)K3kLhfXtk847#m#7K z6x*A#|LVZ*t5x_0=0-ZpF=e)TbjZd6)}{?xw$q%;?P|o)u%U$d@Eq_HNiGJ#pM$em zJHdC{z-oFgli#Z4w;J+4qvfZ4l6|t3(|Hr<jQHzt+q==8OYuCukLptA2u3tJGYUGs z3)-AT3^)oo{ylzS*o{S?>&u`k#Z!MA7JkpA&V+l0&MafT9g6jpj%JfGvR#YQc58N5 z%+s6^di;0MolD1}6Vi6Q7o8Nxz&`WyLP?jcY-_c>dDgCz$UmIP!Y6+u1>XToiYr=e ztef!E^V@lT`#Klm%+F=e*g7k`V52=>myJ{a<^|~E>gCJeBC^_g*ns0-P>*hfO@aT3 z_R|)E?sh-+N_1y3Z}(<$ygv(^hixLZ+(rHy;I>yR>!-7v?q0sE-zw#-tSn#F+MvY% zu!BcwZ`sa@f|17&|DJq6eY#V&jo2WYA8BcyNn`g_+P;@VwnH<mf3VKF0A3Sb%gtgP zI>i=xOz{cWp26vK9#M`b3;vVK!U|QyX6*-D{R(`vyAQhhhcngiNu){D<}e?gE&B3& zfcI0>jlQ)*hVD*ghv^I@Rbr(?U!tc?vbCO^4j<xtv}eFHEA(aBUp)cu3PFeO+hR8T ze%hl#k8h;eOZ50A$rxivbCww69y;GwK|9XC`wd!r`zqw`1my5I<dM#j6K!z3{{P(2 zPt5xXdEW)eEydzHQOC{rWy-7ve&JI-j=O{7o&>y`Y%x3G8JFh+lXQl!D}p~n?ZOGv z9W)P#ed4l13&kGiJU%QP;xT%N_Bh{>l?dOM4@>rHQI>WN&NS9da<fu01y)P;5t=jB z%h!CCKkJkn>wUBa6YmjQQGfK2p%m7gWl<wNxbDklXQ(gSX%Tyg(b`sbIXiqo@av3r zu2jayXgmcn^w}g9aaa&%=d;EZ$a~d(7FbX&^SNPVfKkBPVEf1(Dhu&Qm5t?oHbb1L z*ax|#{f`e>#d)1R!~@&4d)oW9O1r1M&aB$~Ia9VL&w{$7`Kk-?qj@BKxW1Go{8cZm zoA9G}qY~Fz{76syxVEQ>{ecf98OlpCl$T^EFDX_Y?djZ=?yK%j7yI=0;rdd#n4{1I znB8fj&!-aaYVo6f1^@W@qT;+B@YloOBkY#TUjA$bjmIJ`1byPuQCm)FJ_ovfPdhI( z(bzzMvEHxZ_ci>!j^88r?ZB@MziyfJ_TWeIf_8E*#esTzC!U${`4JkwAHjPsWI*3L z-m~b1j@7iMuy!^u&u3wVeePrZMTuaL4C{N)>un_2JAd4{tHyopjs66|w9%gSim$o~ z@ERoRN@d}p4n?xPAj;AAeN8;?X_WUjnnLi4=$!c@Zd}h9+V#)E1|Nmpqcb&fXl+7v zZwc=^gnUP3)_5VObLO39-ZnWDlNcYj0!AC1U-j0p64=aAI<M$a+_%dVn`U_$^TheG zlhVt*rFweU$3PL%?0gTlYOC0bXW!MVk;W(~ZSz~s60%KXN3#eo<aHxG?L|Xro%02b z{v^_SC8S8br4iciTVaL9I=O+vPCK7#7AOE71ZM$XS1n}Ke-%H2E8W>BHSUIPQr&4D z0sQ!7zed~PPwFkDcckxxvsuJ~jpsoZI(K%(gHnwLcmc*L@VEPOTK$Z5Cx73Ea$P2x zPsNIyKGtZ?t>Sx0uasFK)wd4$C=KP!GtiI9oQj6VGAP|4I-5ttsFS|$#kafg?HK6d z*4`66rkV>i@%+yBcF@_6Yh*rdx1&Pa(^#<{=qST`I@_k;^YX5t>8=pX!zjSBE0wk8 zLEf%T6}Fh#fICvf*&4>UFLsWH9-oi<o}SHiR@jv!_Wd3P{ZA;dcBTElMQmO}^8}rU z!=Vc;h)1@%#2)3ZAg=J^d6?$#1o@sZdzXe)I<KUg=I&I24{iAV>NbY(QVLQ(iXJ-? zj@16V{gv`yz81GXr?p28_-p%>345&{nSpvy{7!o%Zk?9hN-$6G_`$*3uNlaTHiPde zFf?7*hfPm0eufV16MYiTm47dVX-$;A_a)hbkBL1pJ!h$(13qXpg~vD{`u<)D@7H&b zZ)&Bp2g&ZBee+ZOzN(HEfWLjwXb+Xu0N9j=Y?n<d_Z0H$<qW6rv&^bRzrw+|aqx-# zCIln1-W;eyo44D-dMKW+w6N~m5l^UQR=5ezFPX(TzG&CFC&sr5_Yueje%b{e5B}6= zkw)(u*_9&ppr<nhw(^)U8!=-Rj~VrRb;t*Qs!;ezS)nC!#hEwHQeVo~)KUL?(H_Dr ziTh?*<Fd9lML!#7e_Z`y_M2n~F!=s!`aV;C2ia<K#m?2EvtUv*zwZO$OyC~<d>Gy~ ze2YbVUt@eO1C_TUMacXkDVI7Y#!&u_Soy@?pHETt-HFP66lL#-mAz3byD>r8Q}|CI zvM(ng6C_jAr%wGj1(xj{yx&J(r-Lh<VM^yh!9NZxO40fT!1uRN-gdy)Zl*ne9iawe zKKP<JNMCkd?CGB;&H@e1(fCe2rRe|43H{ED`k7eKuVo>xWXsEt?wsss9n$)d&Y>*C zXNV<~P>IHW_`W?<=R%F&ON4Cm{;&_E7|tS$^CJ@VpEVt${<Ep*M1A3o5UqRf(&)6_ z(%M1$?@#PUOW}Qh9@P6`i>=i!@pG6s?dtIEk9Msu&LU_>aECxU<wlliyOGr->X##U z%aC`qM#plEj{Ww@bo^r^x-*`RuOnT1Qcmk@3Ft`uEk-&Xw+lKB*kg43gZ<B=<2A=7 z_d{|!5>O+Vw4W~75Xeq4@9*~=naXBtgDz&lW^9Jd5VDgM`kGzz!NT|GUc+rhmPd#4 zDrjJ|9UGlS+u;c1<%_xn0mF+rE3h4miS?cI{c*c~K2xIp<OzP?UgcY;3sd>o8nicF zjU?)2r0v(^XxnNReK1qm0}l7m%foGogS$BqTtj<4Zo>gbr8C}=RQ(J$KHl;(>U)OF zwb2<|kU2^hzux5ji<8Gob-08BoflLGS^E#_JEQlYPa@n`#NmDmo_YF56Q}=BT>9(d z>KboPeI1DJQOC>G`3qW|?=sZc*P*SmG0<_-c)cgvh4Xx$z^s1j96Bd<`O$r{H1hVg zhdM}aK8v~_5Pq@y%jzhMd-u>9^8Fp$_xHi?Cx5M3&K{AXiTCDDbWSS8=5#(P$;CGS zdoy6eXYKA=6N{rLhN6BgN=LBA$43(Gf35G;EzTc$MjJ=Vu?xQ=nen~*!=;?&@#j=c z9%nd@*rhA~=F*`gRz!O<Q7j|Qb?@|3{{;C6h?l}anHBXhcADaxm56cRt3Bf0L+AF` zc-^Xa{M6q;v67#30&x@7O|vn)t)tk9;Ir~1zV?mIWv2B7{N0|Uz(X?RMl0_S=s|F# z!IZ#uTwV1kfrq1fd}^<hmnz;#se!N1yQEkinlIj!Zz=WLRKC}gRbr*_w&5xh*$l=2 zE0@Oh=qws~zjouN24Z^n-1s?}$G;*MtT73ElyY(AJ=4ytq`rC@e+^c(^1g1`BZ~IR zvWNWDwqAE@74RyP?CWX2t;)q}lXH>6_vfnok{WiFigQIOBYgf7A17N%=eV!s>mo~s za^Mr!Kxg4^4$n0OD^VZXH*5HDQ^ARiEO7jB_^<F|2X1A7Ceq;?z9z7c`qFhfKrm${ z>KnJN&hsmmIJa|vd|p#VNLdUzKCgy7c}fjpD^Xu5DRzb_KhrcxJ0~{N<Ki)KrU$y& zE_g1^^BmB=8ReB?X=#s}`_05VcfEi^{Xxf3KazPq-iTO>r<Do<<WF<BElI|)M>=n- zTbD6IZ0;fXqCC6x@8z+4MqLo{7V96Nc^u_h`_rQDRiHgTsO?uk-|yG@=X5<d1vvga zvABOd;(pQxig~RI`#U^{{~yxIX_3X*=LFZQ!7+}PJ!*-?O}D%%#<h}!&-jR9T_NT_ zXy-=hV*=x`Uz)ngxjF4Vid~%7sf`1gPAPfr0MqoCY3o!TFqE@UD@TV%eWzt<D#dY+ zERvD~bnfglI{&q4!)UVAcjQoBG9Tk!@LF%_9ny>pzW<v`ZFJ%4R)Is)^!rrmuVdwn zE&P1&8B6GVTGGiNV7xFtc~|9YtTdI+RTbYzrm0V$at6zw=On{7J<n?7`3&Dz_7Oh* zx}M+r`IuKTY{+)DyV{MPOY+Y&(uK>wo|p`9zK5BRsr_u?`{zX;qH(;1zpu9hCOo&q zo+rn{rch6&u*c6>S^GZHVcPGx<r?UlBZN$+={!z<KD8slS2(oQ#?Sqrn4J2T_v4xG z%aKoZoJHu|QhxR)Z2G@i$)+Ei)GlydLr=NA)A%juAaw5pYzXNXji=C<k00r1U%H|1 z%LV_*=V=_6)Sp21;qK|IUFljLUV*d)d~K`qzA9<^%;qGvn0UTX7H9qPvkq*G%iRgH zA^txh#(nu5@20E7-m#K%iPB}_bBbwx@e80y<r!;uz-0Br`!Af{Un&pB>Z8rqF^j&2 zK0{eeT3L#wGq&`kK;_tKzdC01AJY2T;hT6S@fY?iQTrM{Po>N%{OMV2{2a${omC&_ zzvA;C!;3UHO$Io(PYGx8I@=4de?u~zb*V-W({;ZB`lXoENRY2<N_in@Q6sIeMRzSC z`-5kz8o6D&|1hq{q|}i;7FOFOW%p-}EpLMyp0TRFBkq>A4y%;;FE+F9$eq&mj(u|G z$XxJMMhqmze6vF@<q1EY+P+^Xtewub-JC&vD2U-D(Lc~{Nb?-hIFecS*QRB*rttaS zj?h=9WwlZ~_NHlCs7snQ?uQXxL-503Tf5gIZaVcPHrgyHyf2>e4xx?Tj(XW(YgfR| zGTyeM&Wm2?Sc$szODsa^=sTsWLYkA*zj5uM4m-a0<*@+TV*Y)BmX^{HEGuA8+672g zsim=7L>fQRR7whm6<pNUp{KE1Sw!o%nDYGdOU?79aT^+Qr*li!YiBdlxt+fQKW{_1 z4H`ciz|XG%hdUSjO7Js@9WF~{Pu1eNGKtkj5X)Ww{YOFDU9fBI`Rp*wNu_Z%KesD0 zM&K*gT9Nl#uqWQN<&mYxtGAofX42XMDszD(%Az$?Hl(Ni@ed>IJ}GB}o>$_&PWtFO z1Mfv^FPKt8=kXgg+4;ERaczvu$L9rRg>D4z31*-0=OPRBH3xJK4`|ERGtk*VRvz<K zUuw?xAj!PuH_}*%Zg0u14Zz-VKS;A}Lo~-4eiQjgm70y4&23zaFLfHd`M#YBpMS^g zFSXm$w$r&wii-smMc`(fr!rn{vK%~qaALXq?9OXqej1fMa*@gw{k0r+i!H!?=QC#B z-o(ul;B|aO#ADni{&iIB&ognpj7a~`dlSDI?KhQ`DT#4Y`M5ZP_MSXpd;U$k{x~Au zKQ|VGGw~cR0gGtx+y8=noy}?{&POoJ!v((UF5VO&n@jzrm9V=(&F<1Z8+G{h5^(ZY zqy?y-n&hNAnfiaz0^W<}@wRuWc$nbVoxCaXedK9AX^!ycH*ShNPS0;>&rhq7PI`Wt ziRT}ykzMrs<5=6CsD9*cvhsp}#LR)CF0oH;B6{<418A)X@$vkvkxJOAWV=|Kl%$Op z>2S^@fMcxBwp7-hnW7S&-SIIK=_Jj6$=Ce$MDtYXTw=ZL`|04Md8(xo=cx{txU*P^ zck(>d(hG8I{JLu}@jTV>^Vjoue`Yt8^}#%T4*O}Ppgdg3l;S$}p_l5ArwFpt%hHy) zBv#yu_X^uvU4|Ho;v8Mx@h;<CZM^V3Tqlo<J+HN$5<fqU&Qbpy?RSB?^#k8mP<}4( z{rn(b!|r&L)}h$gDjEY)VC%?UkWLoEzimSPY?SlHbhMYrEKq3bE&UYi;3?RyKIq5x zt5}Wgw+&lfV7bLVK)cf|^_IR4o4NsMh=!d%W{pF#bIFAa|4h24zC#+@>inUIT^Axx zA@WfAA$%j;HUpo16|Auy_W6`a3Hd9uGlJ%ed1n3tY!&&y{x8Vly*B^M|4-5?KJIui z_TK+TwyH{Bi$(pkk~o*?t0=P-Wz&8@>ru9GPfm*GX#7h-+6@+VxCgisey`J>pR`Xn z%>zc7EubaQ(E~be0G;?Apn$natB)d!wbYcK%D+>aqhYiDdgjVKS+m}+deM4t+C$nY zWv!QSJto`MA7tWclUCl}Ky#lZF&6l8e%377Cz|A9nUv!}TM}?+`2H8|%S>mM()>@F zAI<CP;B%gD)av@?Ky>GCq}*jRZ>WjrmSFBs1AL7JCi=BtyLcP0n9rHY_RtxdmD*hR zArmXS#-Kl+V{92@Era+&@OMf|@s+{f%g6e!Fl`3yQ}Yq%*4w7+l~c`siq8k%jE@^7 z97mi&|ETYZd<Z)4bF-#@BNA)>wamv|!rulhKO-^6H^?R?(7(IjlYa~OY0P@5No{i5 z8J}xGFf-??Tgd12g9o%n;0FOqMSIZ&U4y-Dd=@h2{UKWwq_t_1bzO^COUCn2o@4jV zx{vT^aUk|Mz#0!u!}TO<Ttn*xAcyx-+kU*Wx>>faKc0ebTW2&ro{DQ|hDft@#;Q+J z{CHqi;|cnHa+a2N)+(Au(%dn>aW1_-Fkigyn7`^{{QWhJYwh&@8twfxtM26Q3mPA{ z(fb1JeZi_b`1=KoC;kt6?;amjb*_)EJ(J5^l0fbus6*hO$sl@w1QL764yX;p))J!C z_Iyi_W1S>=&WWHUDkeZL39&5$*tFra;nLb<)K*JSMr#S$_JC+Bps2l(K-Ed&4H3wI zVSdlE*4~-PWP*Cm`TckP$R~UDUe|ZM>s{~ruJ>Z+`x(aj8N_?MpIJXmzMpBlpSgH} zd_SvxxsC5<8SiH;{+xV2ryge=-p?`K&sjWAzAvgjp1}7-#`~hh#MQ?6CH2$zzP3cY zpI@?ghJ62-`sML_{~6=`XBN{grE%?N>vzNvKA$z<^V!8N$setiFnkXDWVxNswz{?4 zw|MWd*eYmqVE;&!*+H3qSe0QLHoT`Tj_v$^vaGVTC9R-ct7Tm!{dpE1S8F88W4)Lo z&ZG&nW$`2^+sm5oXp4D<kapA<bVJ6^S}PVyI<pou>ThB(_X7QoNhgm0hb}Vkjj<fT zYd1LTf_lcF$B6&PyBX3+)?xW_yEsVvI>5Vq#&g%4b(C*s0`H0QM~^C7IF$1)!FSeU zdB(CllGY;W<c%YA@(Ac;*5v=g&;P?u6F*rmCH2O8j%l>rQRiGJ#yNLpYL-0bh`YsF zj@zVrckqm}s9e)67Rl!p%De+wx;*FB35BOcz|+@^bMD9EoO3P48PPoEoMY^t9QXH! z!{^)`B7DxR!a0{6anAi{oO5n$`{SQ;G0Mtw?k1dbXRO)do^#Rlcs`GJ&XLD<md*6i zuIRee`r+mis}I|2>UO(?y^cH+ZH$)g67|z)|92hM>KetrY}Hhb2iyCW#njn5@OwY@ zyY6j~IqX}|jwe-kr_^|M$I_#H;1HX!HoNvof1)^Pk8j%`bEdh|HR1nrNc!Zs1Ciw( zH_9~$FU!Qrm-N?&^1^5rQlmGTZ$A`S&S+OOh08^s|L0tzd^_Dc(}myg!EiS^l;0HX z)ak$U7Hz~0JDbbV?#{TOc~UOedySv_Y%1+{@7FhF4_~`!Ki(H>iQY+>`o_uh>$>-w zLj}_&OP%t7Ma(5UX8u+B6K)*!4fytAL_a-7-wDztIm`4>bY~d8K73yGS7nb%ePFjy z{x=bA-0^p*FWg9-+c}pWjLjE~@pE;PC!##^)?bw$7VS?TifET@UZP~d4Ys{X_k7%% zDepLFYb5VX)ZNyGNS~?|XyXK9AOpYIZqIP2ss?aV`iR&*shzH-%38puT(dbmeZ1!y z?O(gzxM%p|h~I21eUzHpS<J1xQ6E_&t{2yd>%jgv?}BO0EuYEMgU1XTH&=m@f$}j1 zn|8@;?gc*Ay6C^@aQx6>X{8>&!;&?43Uj9p{1%)+U8q}HBkFr_j&vpnZ)uT`aXV`) zqU03hCFX^3X<37}$g?@z&q7V=bPf@JX(ySfWwx>oZMs-b!8~5Vl(q@f)9@~b_FA;# zCY)*i#CtLu?Kh0Mab7liQzdb2sb;I?9mc?do|2^nO1Ir9<qc7yaMHQO-7iWQn<cP7 zjjh^JbyoV_p<I%b+vPpa#$Ic#dmD8*D93(SmbT3tKYyF;@?LolV6@;&sLIJbZ}ubo z9shY=x#p7hA%t(aHC(6Kl2IY?cO1Rhe|No;M%FtiV!d(3eB62`sP#72n(MyS$@SJ7 zxZb_sCm^Rsu6G01?V3^R9qxzg-E6FPd3e1WN-nV8^P|?gI$o_eV?3F2<Gdo*J1~v$ zH6eeZU)ouBq2It|qJe#}PCwSqjXDea6%O=Y-%~OJziZwxaUy98*FOJ*C`sd7#=(g^ z11G4DWS>b<IFS^_iG~#pg%i_GN8&`%g>XXF@mQ4Y(Ub<)k_+L+$LXi(+cpFopk4A= zYn<<dvb~T#am$7BZ3FJSR0Q0~0PbY*p75TPL!|4^Po2DF;Tt_A1x~>Hxhbvbj3J3V zUn7R*oy59v{SMGBT8sA<Tr8GcPF?=~{^9~n6xHBeM=IdA@usso$mgA3TFkg@`TGO3 zXDhsewzVkJg?diZK|4j)Vr-`W8TGo%>*k@ou$Cl!&sNO8yp?i5IbdhE8~!v{=PzPC z>#&bH?BLH)j^nQZz2W_XoCDUkBU$B^8uJZny`t>$1M-g6npDBq!c(QqQnveN7|#kz zPQ`JYLvLWc8OPz6HAmV=O?jrHGAB#=(-nE5{Axb{zx5JmHT!aj^g6N1wd4T$BAu@} zrI)xt=c!w^rFP^l)NJmWQ_8ks{Qdv&=lW^mJTuSr{$X`io)`|FY0Ue&GhUwSFO8h* z@uSZ5m&QHUt%5qhv^}VUa#aV7_Fu<;>MtiNTS@nof0pNdTJ*X9r-*SpI~;lLGuK2` z<hjpS5$@%GR<R;@?muCa|7}DY|Nhr-EQ;~Z{aE|rH|D*KyAN<Y3V7XcpMIDy`%j~v zpGEYOriqNZ^TIGYiuoU#BC7To`{pXb3$V)p%og6;Fq90qIW9}yQh|9t>>3TTZyD`B zV89H_amEdoh{73{*yiPhLfz$=v_ES)6xw0w`Wp6KW1_M-Y;-Dn=1fh@U179wuhE9& z*8q#dXzN43lDby%Ew7wUoY#53w#1dnd-bNu!dcC^<fDFK3)_e@KMVh#%of4t@Jzqb zThXp*Z+#Qx20T9<J_~s_=>9w2mm71nPfOl1Ws(ST?wmLGCAbv-8Ed+a`;>chD{!p5 zU!Aev%GK|gLYy;VZw647ZB?Kj!&hN3+nj+teGc*pzwx*G9d(Y&`jin-x7H$UYh-=1 zE@39?W6v{o%1Hea5%nqWjZ<IN1x?^QQyBLy&KQH7s~z~<Q{splnk)BwUxK$6cqi>> z)<JH--lyMzoYw+T!gXW3CCD}J=L1j7vhs{uF1%~8KJ<H)_A=U7I@0?Q<cVxO$eiQ- zJLVS@#@n2!c&1H}Y!heZs5V!q{3>Rf0hi7g2=$KC8}8W$nZ6(AXNG2XMy?&_v)(>5 zw*~mj@e(%2v7df+zU=TZtKFyka<kmG8GgcLl3&tG7~{MXuz4H2LccZJH;BKGC24Ri z)<)+1t5Ik0(jpbRfO+&l#|Mvz(Y(Q;DBi%X*;{KFUl!#DamJLQ&N}eW(w>AkhA}S$ zUghK5EXTR&(X!<^S%`BoSIN9`ygPxT%<mTsQ&)x>&%+;u_xMi@I5T%3e`gr>2d-`v zdp^VPU%BriMcY`G_id+zvh{Wt_bjZ_`jOh7n<Co3`Y$G}`rXHz<I(zC6{kwhK~r`v z)W^!T{e|ApNuKCRJ%}^1JA-jI#J4IlA-{Gw^!iP|ZpkGb{mE;f69(<-ezvfcG(T(a zrraaIQ<L7a{p@WPFKN#t;6>AS^dZVFv<-3D#N(8y!TXhTf&R3JoOyxsNB8l5i+(en z^Ve>&-Rl{u$<jBv^CoR+S-W&-VWtRnqCD4!`)U{dj$m)yI2<ZsJgiu>`s4T*&LP_v z>!d#6CH@y<%v_tRz>hgF?$1nYs{A2kEsQB}k7sB<#xy6-u?1rbx`9ti<HZT;c09+( z_fDGaIVMWnw57s--ZL=n2JeKbwWc)kW4fl|Tuj0L{E(V6?M^A{xX@M$aoKop-nDI| zzVRZFc2|}Ny6g(aRe!m&{{eh-fe(@TCi(8-*~}Nq=X+3|WjH3x^)dUYFGzhze#6~E zxs!y7t(k(ojCuAADZABi`Gp9*MK8w0IGgE)9o!_~18r1q1@HPZ{LLY+gTD?Dj&%|C z6AIr86aE>1KXIhGcI6Q758r+@)Ye#AIJ83AK}|fdAQY;KdH)+_uS&bVfR%pIJ?$<{ z8(asPVY^S;Qh>H!K|fnlu90!==g8Ri!2P^LiTm_v=N*HT2P|S2zh&TC;Ixe2LAy)e zwn;eC@XR(?z8Sd6I$Wck^Pz6)@*X)CKI3A=#E697F|7Agi|Cd*m*i`Lay(^fJepza zI3)({{>nL}3-aPWWK92PxQ@0jmar|$CTuG?|E&0?L*U5{i^W-6q^;qHYbqbtl%HCs zrQDA_mD+d}&N=YLT^5^v$9V<!I0FYK%KPTAw#SkVpdZG+aiEVW*c-JN_rkn*Z$52d zHP^UtEb-x`!|404ChyJVcsb|HHSykTw1qJ(?gFf<E3>z7{`*iC<6FER<C_x-6`6Pu zE8gU|<0^|D*Z6+_l0KA8@%CZNXV+T1lfct4)(H8MA;><oUw9e&s@0+gYn5;3^HXL% z-%dJLos2!7EOs5W<O~YPw$0MFbdH>#L$i7NXJ}n@c)qtR!Ta*H`tFn9>)D?4olFpq z_aula^43KSD~Bv%O=Y>l4e2u>3|rToR<TQ-*}xIou%a2Rf6*j=&@64C?cQTXokt*7 z81}P|Y0JXzAIefb6^fs*$#yIwd|9|JGfoJ2!d-!sj8;47qkXtzpNu1bZv%K&KD>0_ zl41Kk)26POcZ?xv^DEn#gYovyDC;6jmVw@wu#@d#KS+FElK^-t{-f96Il!-WueXf# zp<Os(QmaFAJnuHfF#+Q++k6`|kpF8U?7~=H#!|o<IW_0=GmLs47q21Oo`o>9%u1I3 zwX$SsE5Bf(@HRo#zFp~SeRDKpJ`r}v)bVXa|Fq#CJVCQ^BIz3QoJ=?Tog?Xm>~EW$ z|2JrR?Oyi9y|~!uOU6`*@<%rNh_-FH(0Rz%Cy@7~?!=-wOdkce>^IK=`NryAFa^8< z+GHHWA#1#waS+d06Ma`;ER2&;Vu>8fBsrGAXY}A(*2%s%t+~E8tT~W*vwg?GPce6m z5obuo@|$8UJwh7+Gf&wdV?eX5Je#C}k#)^_#1EA(=E8A7qt<VOWtl_4bB#l-ALd0H zm5<IVn5JuEVQTt#%exyHw<R&chJ1WKAlctuqrW~0R|&@^&hz|;Uur~u8AgBUZu*ua znEmx7j<TU2&u>PppHuah=N#z|xL5hRPt)JF3-vep{8cb@j_AP^Y@J^A+k<`|Lcip5 zibnN25>r6R>hyI<H2dD0X!sJSZ)X1=7Z;585DMPT(fHe^#-c;MU1yBth8SZ}cLoX{ zM~!FM$avf)U5f38^9lFYvZT@DTaz@hznUy#_t%Z*|M&M-y1Bo~{x|kl41CzD=6Gsk z4U!Z-FeaOUhlMflka<KnKH{QP>41FIIm%Bgdi^19Jm%gzm1p$^+RK)g#d)ubYdW-4 zvklS*_U&~Kw9}Wm;@e_~a^E(CpB!)8@=jBe{!EXh<Ls}Y9usHuN}T`IkYPZayXE;# zy`LeoG_G|Gnf_TOy&WG<m5z-v#-4QWir^W41-WvC;?p*u@P?h)!oTm>^-`B6;?U3O zF9hwcuNYd0vzPK{*8~yFH)1U_b|?LOSw^5t<*V}UX;!t0t&rM@KFs=4_6lz;bN8)@ zAE8@jJ?drn?0S99P<Og6`9bEq=lC8SR(iAO80#Nn&mP=$B-B<*KRv_Fw;)b*7nm`k zF>dAz$d4CklxZjvm&WJb$y}Ls%T04V4q2z9pSn=Srqd$gOUG->Z^iiLj19@S3Nn5V zb>-}@Tr=`QXhwYU6zOB1X2<U))OqlT@@p6)PssIo%JA1P=HgPe`PU9hnRi1vWT^eL z(e3+z|Lzo9>rTkj^!wY&yG*p*iM8?3wj6kEkC<z@Hg>Mm5AGK?&5Il_<sRA*_%Q!2 z%mp%U@Zsgc`!MK8=Vc<;H3RKmiRTIU2EMd2TLgEk6W;As&AV*}Xgv1n7M#V5)kpt< z&ku91e;BiFrp{8vqcr`=Xsf(j`IAvc7YbPlXlD@1r>tAvU15E)RlMB5WZgd{wK><c zXO51;I}+>FfI(iIf<Z6yC0wC*WoXU{+A4NfL_K3UF^|H@!B89X(oz4EmMb>nTOD;t zvEnEhe4O;JABhQTw@91Za17hgab-m;xiTuh+$JOD3+Fq*phb*d%Gi!0{mgXATuah^ z8e{fmOIuiZ9}?*Y5NTgbI?+M<)~NnQ+T^}w{t)x`qy(AAhp`dv_z3$7b8_Z4r71W# z9g*{DykB$?hNJjC*-tnoGvi=#Z*M?b#2L^P#)l5dm|UcBslxBd6qK2nlSl2JNs3-| zeWYx|<sLHHmv%4ri7s1?@Y}vDysn9;-=ht=GEhIl?<j_xy1{1bsRHb&%w@{=G(TA! zd;<H6_gsu!zZ0}~3D3R=_~c{09LJVdLTz<`hX=5*Yw5FVh8@j50~Y=BJnj~&n8$K$ zC`Y+Kb3z`Zp9$pXt)SiH|8p>>&b3R2F#m3IuKaDv$^x|W4eX_zco)XM^ilZ7K31WR zO%~<<0eqG5Ib!n(qwNRi$7vU|)sZ?LKhGaqOPQa#WEt1(a(z|l8d2YaGlqEqOTVqj z`{&aAT0MQkO8=(S=S>h*bE=iSIpzKk<G<l7B@LXD2N?+OPVybvOtuMcinL8{s+=Gg z(@6PoT}?lI{9PmCA}~I1iu-Nr1lpezy1uB@?~9`khp1v4pB~7yu1A$l*7dlk7yO^9 z)$@+rRieqeOV>iJewjm5{mCMVR#+~Y%{$N)mPxavU6>{J&TR?w70aDH8^15(odW)U zBS92hO1sbm+q_zvn8WdIiW75qm;VF7b|%lh(wIvJXb*E#yB2HpjNj&J!GDhLdzu=T zUz0wpu7jG?L%aI4`aar_S+eis8M(reHJk19CwAoWY;8<PyHk_?YiYB|E4_>U7BytZ z_@v+Gvz9sg3iQn}HKOl$qmH~ww4|!GvO99gH?9!XbM1x>WrZa}ej|)aQLl4iM|-lC z+}i(PsHoEp-cX48ZmqeUcj;X3<}377E@QoaL4RAkFSm)pv@_v5n&*IPeW1Iv3yHSx zQ2bW1_ZBI~XZRjT6Pu@;4;8iK3NLNKq@Bdfa373+kvANwDzwY|d1J;P+tX6K+l}_V zJg9Vj9!tvXg<8|0G{de%D@*Yzf3FPR7e>lVm~FQ-9kQEc)}$EvhYa86QKmW=LYaxP z_oH3_Flw%?k^R!=X1&GUS{^Y@+fb<J)N)bs?1<fpxsSrJKzogQOZvcH3R>MaP51-r z^pakj&EPeQsBg&OJt1Hh_s>n0$K`!thO|rGcahjEu%DmDILl95?;ms-XY+{({_wtc zx})Os`AKgZv%m|0_Ih%KU(v_s#_)lP*R>h^mutiiGeHxZX`5gx*MkAZ^|6S}0jJ_` zOdrf>ek<C~|Kf8p_nzdn+}G$qIaZE6VyxBl|34Rt{~6yeG4Y&=!%d%|>P0eEZui&I zamFo@aX~lOPFx+N-wxx=bsBz5rXP=OqV6vT%wGWhH7*iC@+6Z!rvK=9y>)iPk3UE~ zP@cvZ5PI+@Mm&S~Pubq#vtc+*0i2pJAKORDH_7%<*f$AtE0XgmQ+`Q0=JS2jWuJkw zqx&4b_i07P>GK6V3%G>5`w0;D@DBfj^sViZKKgM55^ifQlX*S@;2{~)tp76lzc*Dn z^M(I7ea5a7^!<OyUN+s!xKqcDga6v3e9FMf1{tT)9A8D3)!&cb`PZoTHPn?gXbr4q z|F%{B2MrAV2lm799n?W<-oBlxt!>f=Zmgc);D5Rkl^)-B{aF>aeYBsH!AGLa-W`^% zPUHTS^OX5aCS(3CHLqsr_hv1y3mLlua_4jz*I4-l+I{oTes4B#zE}?yo|Q2Rf?}o~ zoMk@G(t}r+&qaE0hWR`l^ERIg^x$;!`Ez=3s`*@o`J2yuWx<Q^EPbCN{PbvNC;8mt z2A_*}SB1hpdeM3v(wZjh0p5>uPy9Gd`aQ}0!1o`wClXP96ZXUk85<?t_xsdX@nlBJ zryl9IK04ky7}WQ-Zk+F@48${T^*JS5GfoNPdor&G<jq60b748!pKUlN<D?X74T=|f zB&h1}`^(^YssH`%ITic!AimMwnKW|kIr?ykBBPx{_n%X?l(dzW^gN{M{0?>GSl7gj z(jmPp=W6go5w!8&K487k@WuGwZ!+dZIDSFhfA~xtxsLOmb80f?^fmHxkSWbMeKV-a z^ZQ|APL;--{uAHQ<lSz%?~BHqjCKw!Jg4|uHK%l+-I&u4P-myHR*~ye`oY-vNS|b# z(m%mET^hAc^JA>j#m1a&i&&?esCCMSTBn!Kt2$pDXPsiL(I|gM>4R$b`mm3P-@<E= z^D^S9${3kXhvfOc%ZM+r`7HX5_E(LhH-PQ?)dqaHPkwcl`4F1YJR2<|c{fMhcSXS4 z)g&JGkna=X1noc5wWfACue?Mr^(wr(8@cZjqwf12<9*-T?}hLCex4TNzE6z4?|baS z3*YydXHRdU)>Ve}ogzeVnqhy)^3R_L$5v2qy>R)TelWUxG~Z*!6OwuhyXbz!uIzBZ zZx!vd%b1c46(Stl`!sk*o)_+CIx6#%DZ@Kt92(Lut9F^!jQ=n5yFm7)OhmhnDUUZv z-ZuXydJyvHt`*AulQAtDrbwEvmy{=rwZ)&weTs6VwcpHD`F7d=G&^{wI8pyS>6d7& ztB|oLW$v;QZ-6IfY+=Si%LA+zCyC9hL%hyMy*<z74<6U3=jth847VwG*UK296ButR zT%T|hXwz(map<<54HcDsINCPwAoBrW{;s|_!|!}7|Hjzs+oWHr3IE@W_`@BaCK@2~ zo!}b$%%Pv<n$R}D$U7n9v&@rnfsFqx^XCQb)ql-A0E}5N`NJ@8|H2{Vw>v|wS)8vI z=TXZm>ijCensGk#P1t7SQe?dQ&;%uK?gV_7sF<XF$l>*OK*oyuko#Tt)7Hm^wX?;E zU9>ZG4O{E?TVxJ`6F<w<&vuQ9V-0%$T&V5T$uOLB{0|&A{4LU><EsB|6ueeq40p*` zlA?sNyUL3!{M4f`&KqsI-ohDd#(LvA(H5S4Gyg!ZFzm5E3Kg02Aq;z!4bCX~>I!J} zd(Vc(oyV6kkLNfK!%t=9A?45fq9I4n)@k`?#S+slel#DX`OI^jIW@?$jGXHy7W(a- z3;cLjOWOC^L=~GQnoo+^-tfn__Z!DJ?OovBIwbS5YQDY-l`D|(WcOh25{DQIoOjpE zyLjXxvE-~Z(cQhZBlm-sLC>xbsUhsC0Qy}O0jmN7R*z%dsM{<Lt~~4-cAZ!TdK=gR z{@j}D4p~Kzx`a2Z8PcZpxHZQIImWMIf~U)z0l!i)FAF)IpQ5d8N$MWr<P6BB>7skz zB_bVsZKb<r@{)=&E!8PRi7QEzY-GP;=!wpFW#7&*&<1FqMO5ZqEYcUcCNG(Lu}B?i zY&z6{_e%?|`(SbcXeZ`zFUnJgx*y+MFX|r@r*^bcmd{U6ZG98vX&b$gxsEV?8H*Ep z%(hy|2l-l0NxoIJVSiEIxG+I<TiCYNdx*NqJIvgNnznuhzI&!W+fHAx8GA))E&eZk zL-~A_YgtalgDjsY7Sl%1@;Q+%V+DV7^<mlwZXXWKy~R2-?>OK|SWSfd$GZs3v9K;d z9DHYTQ|0X#OEZ1BC()NXS>ngNr(DH6R~xl#i9;ReZ{L7OrL5A-oYsI(jizkDJtvM9 zL&iz<oNyI$>@UhalHt}8)H;0k*-+bx`Gu_koEwkE(-vCPm)A{RQkVAQc01OC?GX02 z+l7}nVApaE*sQh#rJ6Y5-qzgC{~pxqe7rf=6E8}ZZkupo3+fD7lYCCbB46Efs62Up zJAKm~UG^<DjLi->wV+I+mNdxuEcI$9Shgiem5nUN{%K>)vRB&|ops9Ca^sgPkNDk) z@sM_mN4wM=*Z4L7vEu80XpMI_J4EUr_Lu|xRcre2QY~)ql$19$oLg4kf?3~c=NaCV zAX2&C&S1YKPKIosuKVBk8uR!G_i=n*i|?*oqJA*Dqn&Fr@M=eUr^Ru=t~n2sYtB~2 zPZ3&r>o!YE`!6Pi_Yv&`-<qMTy@z)GVa;}{`@3*{wnU-g{e7~$tR_EUaijfu&$Au5 z_UYJLn2+nZf<f}0jJE*#;XY-Jmo$XAXm0-?OfN1zsOW|CwTqD_u+N$(d5~8QhuY>^ zWS&qz=iZOGyS0qgMk7aXIq;n^|2_0$G-A-^YmNh!c=}5@d^-MHdcc38{vc@lQ^eU_ z>VBmiuxXVs8AW|EZR6t67IT>Z7xIn0?MC~x7mMP37X6TY@0?awqIi4`V7=aw4w&b( z^6qcytl3`8+T2b!P6v-TB~cVrgI;`-aV2buhIC5ZL{CZSUhqe}uS?MV$?;<G9b#zi zJJ#fidlEo@y;^-8eOMAiPUq_UE%xE+!yZRZ$&A(U>9?=0a01^eZCaA^d%NtDJr0yF z5VCw{TtxX4mbb|AEbB=TCC&CE=cDnq3Qt;3iA_s(deTHmsZ+?@cs9{f;@Ye11*r$r z;)M5!J>%!X;r`eJS`sS;>_huQZM08ueS2{6ZQuCDH%7;w3iF`7(w8k>`U5iNSsU=< zYglI=V3Gqm^G)z+<$#S#%c^6n!%=I$*WFqU9A8>9r#J^R%KXOho3x)alkg>Ny4))H z)glxA<S)1ey7IBDY-7%C{H;VCc`sxkKBY(DQ~CjZCr<Smc{J|;j8>6Gqt41_^IPf1 z&D`(JvVKNX{R{&iGkn<N-meqBz_U$^-yVTy>lL2(n=M0g16ErFWAi(-8}E5}dvoqe zd6yY`Y}L;ewyO5ieP2M^Ywy=Lne=+{-V3j#%ySbJdupsbuyOxD`+d-_(=t~`ytLsU z{o<YAaqtSIt3{Xcoyv5-Cij1$`;|!s9a|ozWAwe?nvrkgn$E#~J__2EjWyqAu^-rL zQG4m`97V%G>q;(CXRfsED@_%%{%P9{_dG9spzU?*Y3rJ$;L&)@Rs6<0J@`+Uo7tE0 z^T_c13uH}63-SHZ5n4D2v@pjVS?+h|l^ivmy|Brn6=06}slq#ec{K{#U@zdZT}b(| zq)7|Qu>GV1iZ?ES9P_yAB2i5ITOKD4Hlq#lGcy}pOTL7;{X6JMzjmefVS7sJ<qdU1 zjJGmi#G4KPV?dn0$JkCf!2SZ?1>MDdo`JFf)S=vFL7ljF!@gEW$Jwh-?^F7S(@V8c zx~gUwuS4^F<+J3ovevu7uT)HbwS5Knl68zRI~6?QypCMjFTa1W*xY0lMYN&wpe%VG zH_BdzvK@Jfr=h$@KYPX#qs(o~d$k>Fc+jmm*DnRHMSF<Qv&x5UUs}a_`!lMI3h*%G z!5YD*4MIkP+?~d|7MHL|o&OxhqCvj_G4$kHv~L!1k_N00>V5e~p(o9|+tW*9+^a>{ zS&$cgCn8tiAmDh)n!uR+yY{>vmbd=nPfFebUC*crg}P_pFAjfu;?(_GXNI<z_trdT z%ki5y_62LsyhpIVn+<+g+FfGa%M94iF7gHNt#<H=j3LJNO$LwIJiOxYfHmI@c{i<E zbIooZZal03F8{#Tjss7rcldw6Y34@=OuYJV63PU?FNDOsA7rI=<nH;FR)0Hr&p3>$ zA@@#>aZd)unuz~7T0Lc~zzo_2CAm+752w6!oUzyp8b{lpUTd=N-H$?TSE1~l&00P0 zS@--%t7rV5k-F9xbrV(Hcd(ZzpKb-+V2m@;2^Z+ZF_e}1Q`CC_`y6tnU*!S-jlg?J zA1+h$fqJhgp!pUp)7cFAum<$u-?PK~4Si|fz*yqA=hvBgUg;&4qJQTnoact&P@CfM zM%%EA$}dQI2%P!J9$EIFv}4QC{h5#p<(>1aq$QnpQA9fO!tqcWknnx#s-D2UW{e`f z|HE;W%b7k_{Qc~4WwXuaJ$R;H9Al>rS`xjUhTDf68T!U#nG-X43(uSV7P~k94V>k} zM*O6BQEyLBZT;%Fvh6YZdN``DUqtlvvrpPrANsNm&E*{!@AmqpaovRXv%m{s5k z&fKX|7Wf|a`DuLbdH!TE*B~##?uO;04Y|cu=fu9Jo}o9?A#>$5r-8QH#p0GU;olFQ zNUdL{`wVCdVJqXKF;}G~8d@yMuVTHtJI{1;&C0MIXC{+(9h(m@f1)}!3MLBwLbSd1 zV)}pc-b|Df@IPoUWA}rm$ymFcGDoW(?84d=ym58Vq3ORa)f|InoagBK4;^z~`l8B( z%KLWi>#rHV%{<2o0qY;7jJEk0tFIhw5A&v(GdV#Nvh1}+o08X|4?@{xUh3J{f2r|J zi#NV4yUgpO9oLEL!*`RWUBr(tUd>j!&8qBVD!$xQ8SBnc(O2qb*B*NGY^bft%r9_K z-HmZfHpW@MQ+P2(88cDkofiI@smjli@0o9o<%1D9p2oAM;e1JlN7=PZw@bSgsRuZ1 z*tL9l9J?0u%lp+>@h;RIOPPP4kw>xoy)bPLzNm5?^8T556sdDt$}{$SsA~Q3a85;Q zL{7z6_sXY?oJ(dND4vtdJ32F>ufH38O`{*C(HG<SvM*Ej+V+y-5yg+CE^}98?#yps zJdACk{DZABe~Q#w0?w`2FVuaeM)b$Nq>YhHEWS{_!)QIG2`}D5^`)x$tGt@A=WULi z^S|vS+AAs;aGt!sc#!@##$4%_8hAH6#vcPFjJ3l#YnU@+lU*|Jam2ijq>9a?6^WVT zCDYw+LcRno_wg*8in;fq{L)L*{13kSKc9cB__<1NW|eZUkvr2(p24)meFyVe2Aaz8 z-un!F^3v<l<3wuu)=jz0Sz+kF4u$P#)7?fsU^jI-!4J(dXlzVaQ;%!|J?sbF3``RZ zW`0!CQN}qU3~YdXFZH(IiPlP=Q}LK>|D|5%MfzcTL)|LSwYf5HU<>f8rmmQDm^4uG z1N5u-i5_gxME7*8-9IXXcSPqpg))WR_f$ynm+XUZ?Y&HI2s~%Nb;4MEZ_Id~%Ku^4 zZ4v8~^P=gm7^V+Vwp?MFz;pI|C~RLBuPq^6pe<i4yT1X(Vv_I0sQ1x%GM5?j`sBqb z4?Ukr(_ZfjwcRpR_tVz!D)59htZ6mq9P?InYLe%#XACdeM9xTU&K=JVgf<}mMcuT~ zuPfel=bAXxXHTDDmupeKf77SV?p{s(-rc8sVfnnP&&Zt(8egCp`Ll6$7Z~}o%cm!8 zA<gPytbEPxwe{&G3ny}Xc8AMuk+OeyjFU&hWGZE6TRrc3B5ex^+mYXSwj~~^4_#a7 z0nKF&&sv%HSu7^~hy3o<HF@U~e8A9`HyQt3LL0nA-2qCQDgN$lQ)laF{yZ#~oHXJG z74p2<uJg{Su-c&q=X`*3Z#(3w`!(7#2EQcv1oA7Qh&H_*%joi#8Rc<i9vT`__j9}t ztai}Wu_Kr3NS>4b132$JC|i>vg4F5I-WWVi1J32_=$y{HGot_R<MAqgO_9c2_M?6C z*1r>Kd-xr7$HKi@gYoUL*avH{-jQQGe?pCM>)FP`e;Q|uU6!$9Bro=_4(7+PyQ9a| zig7VMKl<3U(C`QQGh>l4-ep=ihVq!aV4(A7CXCMcaZ=?A9kUiMofy3qm5OI;7;i0p zXO!oi^@E>cEw+Z&qJ(P^JEu^Faj%o@{<jOP$#ks8wZ@uUc7Zkdu`yQC=-N-QCVIr0 znB$Bd-?cH;=Cv4Wa|X0^l)vbO<4pLdj~zmrDer`7?4AQkhOOtmv&x+FhxTFLh8ith znLM8$-;`TL_mcRg%2SEzZu#T_&fDfY^#xH9st_e<moRoPXd}<)b&)ysY|I6=S>|4r zHg-1Ov31gZSH{;^87GSNeP18)42O1|{L1{UE=&4>{NV+gGC(WxFQ(spy3anm;Bcds zIrz@Hmi9?#GYNl>;w-BDwmvkTuGgkbYJC80Ou}CW<I{tWVotw4>m;8kZxJ3(7`fD@ zkcUD4{Qu{aj8^6zV$Ab1)?8mN*8MLS7jx5<3-)J;>d$fj>*8H#%{~yozBrCK=AnP) z=GuI*%#T+@9_E{Mg+rUCVmxR!Tgz@;GaRbQvWD}v#h>7q(tVwltZ)o_ls&a>eS5a@ zNAB+Vjy`k_XU&-e@E!QZ9JbQ`|9#WfVtl{;SUR82|IW0ZiJeDO#gylqvwT5A7ebr3 z(euxe`P~B6LSG1coE>L`sq2cRFAG>QT63h{G{?t$UR&N*epTR!yOj=O5_q2+dG~iU z^Ta2JBF&m{;5guU4CB~}`k%-9DR^JAUGL&OC}o^#i?qqx#T;u*X!qOi)8<aeC%?oq z?@aByPhKOuHQPlO<HSH#IgkN-vy1mPc3_X&m{SQfmU=(hqdsDney%cK)K4UgGkqK{ z^FH$3kL0^__wTtklQLSXr;09nQdTR!)qoCcV87rGKxcyF9qf7NJ3;O-84EJjn!NrT zb&XGl^$W|bUBPKe$MCJE!3#d;JZRdhQt!HRlDJ96a{_MKo)L?s{J%@Z8LYW<Upx5f zd1~)v_^JRmm&lO&!H)8!{VE0p^QqF#4Ewk0IOjgHe=~f{1M~>*J|o)hq<$4`qmQ`- zhU{vWaVIhkgc=jOXsaA*v~^J@0QwMQTcPEal3-$U`>o*V0*T5V!IrFYA<G<@%ME_f zI+|acigM&f7h->~4C9l;EN9pc#rj5j{J!TjsjGiySo)v@<CPw=-duy;^yYST7AQO0 zW9uwl)8=Og@Z&uX?;fJzGs}pz_;vKX?^3<1K(if~2b_{~Zj9^V{duS{zAH3G1VcNt zlIbso?J-8pf1Eo;bN+VD|9Hgwqsv9kKRw3$t(gBDt9JtZ7-!JVP}KLPKiDqsd;G@T zPaI=mi9-HfCS#uVFyBle=dAW+hVPW}b#>3d86MbCR^pL9ESbJnAYV%O?N#vW1N^wh zWlpOzp`z!f>Roow<{5xZ$l{c^Mtd;pHiuVF&Td_%Wgp<4E1RrhM94e!i1wc`+h1cd zY|d2Mf#vg68LVmddul&ok9hY!6>1AkBmG&>71jYMTBzBiGmR)bAGQZS?HtR~kiVp? z&U3$<y2S`yrr6|VECw&5jV@O-7{1%&y{4?2LYrNYVe%^Pek`w&<z8SFn;DOwAO^2; zb|BO?c~J2xnTF3A>EXU}p(@2=*v8>8vfMMRV|k2$7(B-5DEl<hCBkSK;2T<JKPbnP zr^ZB>Ro$l_-adtVg61{j2}O_lg#jtoRTe0}(nBYgSR}1Dxy0HP0<8;8(@MVf!herW zl)nEzq!ZGHSM%N*k}`7;bM#*E6z@ieKhgPd!?xn5aV|b#*osq+)Er?`U$#u@nqJD> zcA0l8^<>~*w&A>+3OFs@t#TRzPcraZ=C+%S{(e57-ZN&$jr&KpcipXV-iL9Xw4$B2 z(aw4;&f7eFZ>MMan$8a93zNC+fe(1!xOz<|^WN;myA1;>2FU#VVcU4-Oc^i7hFNBc zCT+$yMB22!{;nDm{bw07m+g42p&z9PwivlsD)76;@F%E=kSBXAkaylwcWROHg=<5K z@(0?QBK?$HFQ>?RAgQk?OZK{6OQ|QFwk!YY$Ap&bO)#JLCdpXv@_9{?H>{T(xo39w zDm$@5zXQ)pIk;4F_!*~;@%VTT4BpX?z3@_go?Yruv0rBq=SF>l>>O#IE6b&cS=%Do zAitLmoE=ZMWaeCsrROE>*>sDH<?ADU5FdW&m-ez7oPfUvxYRLU40WVw!2*2mcvcKK zwDbcVS&*@7Rulu)i#=JQnCsZ`jX19dxZ>382OKEtsi|S!^To{FL0`9My{p{cPQ`1~ z__@C?I!L`tb%O5C$Npk&!yX4?p3x3p!GZFC+~*EaXp?>l;N8)_O~y-%tKR{<bAtW_ zzNVK9?hqyYUlU&HZr?^Zcf4LgI?r;vCv9DrZ`5<t)u3MfOuaa8K2&ro`sW?mKXzb^ zF%SBb<{5l5^W5FW?|8o~tt|M)jyP|~D*SQyO`iqJ6obFMD7u_&bU9s>JBRb<-vKwq zkbD>YIq)tSW!^%&Z=%lr>H4aE@I5v2FK?}=xve;V8RI8iG&_HpC?Q_<3!Cxn8^*WE z@|%p&iZ&~1u9Wd7u@2rxu@>JOW=tB9LAi!-Vqczbs~F(qM?I^>;@bK7t^WqzYvD4+ z8@p(B`7)LB&J%Bu<?<uSsWOvi*J##Mmb(J=*msK5#V56<+!{war_=|G{vZ7)RD^Z* zo&p^@>5TRpGU;4I?#c<4uDKV9B~rHwy0^rL*EF5Jz3I%`D%`efsQ1zR;Hm3O+iv<! ziEEk;b$qD!{gXJ;Pl0~1j^!HQIqF>WWT=h0-8~liLS?u&8ggPsf|#`ibFA(U%dM2# zxJRCRCoKC!(h|nZ|I$Cym>k!{rg4C85x<LT<lWAO{0REC4eiR@pR~74SMg>tuMxri zbb}V2Sm<|SPxIbnFV5S@d`+E(&kpl7Mf;1?gMYg5Y=}0hlD7KL)<Vq^d~uY0@Sah6 z^cLR&zu6~kJb7>1yh!B?-*l}WyqNc?=+B2S^EYh^sZ-wyTDp_563(l+d(MaF9#DQt zlBUbtfrg%QIojBX`kSs5!J6|1?LHr--R$RycVwK$5ub)g9fbW&o|U8gF&=A9^HA@{ z{vJ!m8EC@V0~v|W1!Wg|<yf>yUeBeX>w%M@wqxoZEp7RHwbyyC&Gl$b%P)zJ?Pc~g z|D>dok7<3Ec^?fazoYT)YiI3N_qF6%?lxiyW~7Nijw5SLo{Z`4OlUfE!)pC-=l#wt z_L{qQmueYxy2FU;o@vB&2VLy2*y?n<K^OZJf7<kUv4nK7$p%^3C?oZ!3Bun9+TECo z|5u3yoIn0X(D24A<^R`#u`rjzC>m>(c1F_2Vr#r8yi1-BnIq@JrI-uvQdb&n)8;^n z1Ftk$FX^8QdTe1F0lg#ux;sPDOTZ7*mCM-3&Wc*h#fiVpMYY9s^kvg7ncca_&2u8P zj`BTda6V`-<-7t}uBNzj*jmq+ah;0_ikAZhJRedqZkDM=|E4?$8Q8y{@n>b;*F}C? zqP*)&W4zvP$~k0@%;D6N>WoV)%kz>Jejo4tfd7BO`ghJw*fN-GYpuoDYk3cdxo}R{ zdv(;ce-Y!_6CYPUpZwvXJLK3~NSigAJFsYGG0Q*7HADIRkbP@0rvcDi(!f7r&M9~% zZmrxd3J7P$6(!y0TyCE&ya(_t#oBb}#Wak?ow9{+BK_`a@T_W?E>;mvUc>JvS^3CR z^3G=Kld<oh$NFJtZZpRXeNaENTHRarE810iiCALW>u$B*s+R!&YKcRy(8hg&5#QqV z+Es2X(?OUMXKSPGlRwS<7RKP_8MsdABr1;>`uWc9{79btwApO__K!e+Z56KRqQ05& zeXGFpOy8FaS~Snq3Aqt8hwCYQ1`fB&dRxguy|P6<*KiLUQO{fpo55F9<Jsgf7|$b5 zT%0ENz%p&b@0Dd`-@IeNnEJm<J*&dy=y45TT;&*7vqg<-Ld1A_BgV5ZVmt-h6DFTS ze$E^l$FnL&-WhUk)NcgRzf0TY`Zv?R(_RDkegX6_#QX(Tn@^3~J~HlDaqMK7HDO*- z#uiEt-JsnKgV@{Tk@~GppY3xZUFzFr_N-F+Hm8g;O8bPmct@*8(;N2j-b>3oV7CkJ zJbXKD&2~r2m8M?<apRYujaiWWj#x;;vwTl|p!~xaW4#;a1?_Ik`t-42dy(~js_y#= z(~Wqk8B!OL?t2-ssQH`vN;$`Ec0*pz^hNrJ#kMORef~wmh+|v{d6oT6GvsgD@P2w& zWk^^R;T!K%?)gCZ{H-wBCQrIu>H(F_(CKc&|8G$0AR^{u<X?z!4vz0{5!S5+vF}lE zm;0^AyXmGhAN?E>rJRm6$?}y?5u2qB$|_F8rQdkAI$cyf4Vt?02YN6!v8UuoJO|qK zAkG-Wz9hr9LQ}abR{lT)H^+%xyHMtJ#{AhReXzQNM?-B>Qbjl0Fzc3CRo#D$s7t-V zQxilX^{Tv=xsty3KTvwroAAu~RTF@>8>Q^G%Pcn?-?na4<-UYxez&El{$5U2`Bk#5 zSqB)~-v&HXJ|8xj7xv9F$`6rzjJ@Veap@^h|4)<jd$<ofzpoE{$TP)~aUgSe!6wRl zd_Fay`M_P)p(ojHzEyNL-E-H_KcnA!z;`s`8N6P1OG;)d&cPt#z%sXs{7tzv6xtQe zOPk@lA}*YB)`d1Q&>n3Oh#Nn=r)Y?I5pxqo;VEn8fo;q`ATkH1B~qXD(Z*8nkIyEk zJhyCXaztB6XzRmx%pLu*t*q6ei!k=bi9%D)9;sU|)UpmVIVAsc=-`0jo9|M07Sszc zSBY<pL-7Kw$FVMPgjv=ByOz^RoL_1c^-E8<iu<hV+gavxj`UG1`u>|?-z>#PXZqei z`;=e!d~86*7nVA>b#IR4x&J(1^4#Oc_ll+4p8DGu_YKm2;I|`t;*7B;Zhdp~p4iDd z8qAmbf-)-cWb<zoO!m-!D-HW1LsUHl9-?ZK9^9RvH~7hCwvFBwZy7mVsy3;8(aoIe z-`9gr%3Ow``wQF`X`-8Ln03v4@mfS(?u*?M!HY!Tya#Y;`o6+>FP_<M{zTPQVMMvF z<D0%omHRH9`Taxejh9nM|FaHE0$pmBc`D7lF==FP96TMdHyRpNebT-0ps_c~jlEGb zAnnyZ_1?J68ph|FICiv`W9*F()`k0nuz4+Ne;kk4A8QlC`(sH&dsm~q?u79EV0+vn zg!fnC<sNAm*&`z~KI=dR;KhAXhB}E+`y?xBpJcxV_+g*qCxU-Vu(g)U-0gNRXls3G zpKDd0wWU4sykbn<XWvl!h4OLap2-<d_Tzlc98mJJxo>v8F?Qc14TNK&#qx29wP$X4 z#_$u5uz%oLby(R_9g4oA_gG}yvO@zZ2W)rw_w*sgvfC$fv;zO|{M&wY-dDwmZr+RS z2af%acdyd6+&;>-{07Z_U~7V+L64zcPwgBT`{;G~ykcky>fVAjwxLWd^8y1mYk-Gc z!y!4oKlLjg@+0HicU_G6-FM8S3;(<G{FX7#mBu{#!}DwynWuqA<(OwC`GEw5M|VZc z^`|lB`t4W4b8Ru^y4jfPE91=d*l$8@Wti*PVZ~Sf*0_HN`%|R(7=Ot=OAoqJ^<afv z_YaDpd6Wz87?ARS^wA*ys~h&>k@_0y63Xq1{C3eS`83+!=4;GB=<vs~C*5SjUXymC z)4kOx;0sbz{0X+b40EYY!@IQbyMN)kEWFDKzxxW`<>Fm#_+2I6U4nO)gx`IU@22A2 z)bP7<zPl9fE)Bn1z;|=;Zf^M9=lSj`yt^v=u7vMCk9VIBzboXs`FJ-!{O(G=yB6=R z4Zm~q-6FhO6n;0o--uH(PCQl#FKYz6fCr?fX8laInSgf*;dc}H&VhH1@H;2pU4eI3 zgx@9b-E6#@9e$_99M4$&P)mx|n*YAOk@S*x*r(Ptx0Cl~?ELowp|%y$pH0~cTqwS{ zoF90TKD0wSWB#u`CcMY-yaxNJ`dQ7}Yd+7ndjE!J?vv_g<7hY3R=pj39r~+&NDm(3 zf1e(FDPXj-Hr{*CZ1a``?;l6nN%S5VY2U`ZtJ<9J@cy1{R+a_#u&o=*g3s{(SIdG= z^Z(b&g8snRxH7jG_NA2d7RHH#knOr1LT_*|jw1e3{wE(GY@lcO1HCwC2fbhnHGZF> z*(#L2=&8~7?{#rq=VmCq(T<(ei;IJ2@f<Yec5ANB@R4?(v&Q-8<9X7W;P(9<{4r=m zc_PjQ6`!EKfO(xT#=O+#TvIkv{zl9fk>Q^Hk+e;5-;H`aOPG7;mHYKgosdWDWkSZj znX;y%eJf~@OH4dLncVhS=Clxl*U^Ud=zYJQFnsml2RY6aS02E+dLavRH~;v@@?0u? zPsuve-*rB{Mf%pSm@m9$tnQO&m$Hu;@AU_0NBLo-`^<6X$!{6%v*%R&3weeb@u`l{ zU&A`t@ZX-~Hsm(9As1H4xYL%UySvc0X{%a2UmUy!>sfBx?JcrK-<M>#m(rGCo$yv* zJm+SR=e)XW^xf_sM)rwAH|!Xr;#fKULk~WW{$qYyb%AdyFYv8t{BNWc$$1mKj$Wlh zD%X|{xwK^IbA26ns~1qO#i-X3QLlW>*CkJO-I}FC<CR&ohBB>KTsIsl5*zg34bl#K zte!!}(Y#*|))=(QjGsiiCeNMJ6^m}0ru(U%t-eRvmUr8P?r)|&FXYl{)V-6ry6ztR z?RDeZll+EuUNL?j#(&RR70Z<EHZ$hscl4kLS`WU!Z|mu2VL7;;&)-+ie_&nI`5pg5 zPMi%sn`6xe?C#zk=UoXp^a^#q7Q#>YtT$Okk7w6gv}4nQJMq7H?bnC)kp`|^I@Dy8 z0ge_uf^SEr3h(B%G8f3=2ZvQG$cNriv?N+5u~m~gjQ4G8X3Kj4#@})LN4(d#OGu5p zOX#)Q+$wJ8V`<~X1zi!*9(C@tf$)62%x~aI(SM#-F@)B>HJY}4%s3W*+&Fp@K?lQo zV(|s`#NrF=iNzP#6N@jfCl>!V_r&5)uqPH@Xixkad{{K!Q}T}D33;c|Y{bssoeJfr zP2dNXKu)8--B;dH`e==Dx#nBEPNDKu@!Q>R#g6Ip{L!)TsCd7?JO!lJ8G`a;`k-l3 zo+@dskT&IqvGyxNAsOdtGtNHxKk@Ff*@y#1Ig~n5eoqLgm`x^MJSx6=l%2GZXH~{6 zSGi7JX=iT4XEBBcj*X5-lwtT~u#f+jspe~*3%l;m+M;}&Gkvw0N@io+lz&UR;VkcN zw}Y?dnbBlPZ?*hd@xjqNdxTzZgRL*D*W(;Tay)I4PV^>r<eK+3eDA`0^0{N<DU6;Y z?J61Di#p7&p#8h)w@Uw#TNooeOU4NAv6LR6KM~q3<e5jlXfI&O|Fp4ZJZ|cSS4<TL zJ)j-Lx4Y2JD<kcwc<<yf80Q^r9P~`(*_$!A2JJ-qaYfUU@$A@%6ZD_F5#zoK{j$G( z<MhWd_MMN286E*&3CGm<;CON8CK@=rMA4HCvGNp9uRBGI#2_AZ&lX9~_>Fp`X2u%C zzMYXN($=H@^3-dBZq4@ml9>nES?1w4&A7kOer;yl$Z(9sdt$YBv1+dh?Sbd^x4miN zRydBL%O>i3Xg880>SeB|&#FADt|}q@a9ua4-^6kHNy$C6E#E8hgx3-Idzp~-BodB& z`O=<5KI2<td@gyXyC8h0%N%Ys#(nSpP&nq+NV}To5}MW(Gk(TM9>o#gDL3t|C!;;) zMB2GPlypoL^_TPhn=zLhm=j~RqzM_Lwj)>NN@G0Rjw^(WqucR$l@GP~MwuVHzEkP1 z+)rR^XR!WxD7)<Kk=zQql#k7G<K{2ylD5y_@$6WOR9h?g$ozQPWM$M5z6Fvmbqp>G z(XO=V(0=gYclCw!AD8xo+US!#5AddK-yX~<2j%FSU5(!#y#bz=@)Xv7k|txi73PBn zbOHySx7eP)5o>=mL7b?@{yqYkmGcac@144)(l-6&T<{okcSG*l4Z5%=K?Dge`c?mS zFw{1AqV9h>Tlk;Gb8{-jGg0{EcTL{cK4wq8M&6e@Qtho3_+111EyuHOf(SAn0Plg8 z#*5%0%<&xNz;OjEGXA0*`$>$QV>9g>eu*+{pZ9b>9aOwF>5sYJxYw3}wlgMnkC7`P z`a9Q(-#AataA;(nw&6s~Q_XMNN1>|k$oUEXG4yGU=TC3&Zl<a7w!aV$jW~BUWuG*$ zFPsaGIyzw)n%8XP*5*AIeZ%a^CNO4>=z=_zLs-{x)3{GQ>J7K|=pJ?NIVw(H41YO! z&lM;0H*Cn(=1F{5c@cTLbf<lFxpTWi<q(>?T1$Toa*yr3`wnY0-yE9xN~n!_r>e!! z+?T<-kv<)kzQY-HGkK3FRIW5T$_GG`^J_f2ZQ9U0m)Ba)v(CQyzD<irPxdCuyy}tk zZEU=Y@#ES~dPc>y6^y?RSP@1hya+48iSROE<*<mk?>{5&jW*ac$H<vy+@Hx@zU5K5 zyJ=^$rB~VN?TCOe%BM9kKL0by&NVuItw}?Af$!#;Z;Gf-I}u@xT64=MTk}QOV>0f) z)mlKB7r<VV{+N)-`c`1ig0>JRz8TS9{vOkR&-7OuU#@=fSF~-n9Hi{aoTe=pkD1dn z8{;u^U!KExxoW}G)^l@2uz#w3%TTt=YY_}wDS{rnXI$Kc)cfa!VJ&U7rwM=Wan1Yt zy~_SOl&E|!sBg>UK8DQdpp0(K863Dw_$@A#Tb1LWKb;NZ2>c?UYsNDjmG@eDN@zQ_ zRCCsSpScTBUvrA884aGHWa=L<PS4(%t^HFcY;gyBN^p)9(*9sirl_v~jSPUs?$Jbj zl9qnp#RQcblx6p6@m_aOFR||*D82&ak7&si9ed{vwr0@o)#CMNwgW6TfagJLvitI& z@HW>BNV|>lOi}zW_+Z)qF!sz$>}C4Tyv=%<J;e*PWT&)S(X#5A_ZJk`(Jl{gVc)a^ zvun=QflDXQw!ynJC`!JFw%BKAhqa42PLHOFdfImsr#9zW8ui1+k2AiECjI`eN{~MN zk5^}cK4Wk4ZmPzjmvBB=`}>RcF=yc3{K2}3jP)++Lw}0va-%K!hu@UiqS~9Ld1*7u z_AWKr>rDeM_UznN`b^8bu7K;{rH(DPpVCX3wM)|2);ijJ;9V`&WIla-)>*uEz}({k zP0?(Qf$v&MJf}oS|2j(v`P*6jp*EH+XH2x$0;{^Tp}Dlhx;sI5kAlaxB#SE9j?`ZW zuXj-SU^b<Yx6EQ(tc<A<-&@S@sMkA9_nY_TKbQ837O&EosQ84`#r^4w((hy?Nxcd9 z;CS!QmC?CVv)p|<!uIpMc2O9Z170$nK9#l#=2NZGGMyhzz&^)%?y!S*ogwPiSX6HL zz#P$FwjJ57%w4}+jI=u?5$&R_X8uRp%<afFGl3_kurCM~%BkCdqk&&s+cj{R?$>eF z(3Y@66DJsd(_!Gye&Scn$>KjUe%#)I)<NJ>E%501)%vETTGHSkXmqod;S8XC@*l}Y z8+QN?9br67C4SG)9Ib4N^?gQt#)qKH!*Zlc{Zm1g@O=?}w_x1;Xm`-<+~PT<a>;Ye z6JyZUUZ?OAj)4X9m0e6}l&&dJ4>Fe!Xp#4^UtU<(6w9t`3+h~%4|(aAO8-XMN*`~N zzJ}0$G>xSUK>S!ae`+h~EqVNTplRFVMb+u$Dn9tbSl1J4*B%;JKC6}AncJQ@(1}x} zMh;u2CQ9<+L@NE!?4SoO@MO%%n5kt|^r7D|IAO*^TCQxd2p{J43w&2E=m(5!n%x;l z6p)WI>lSEAKH70ER22)ZeNM(8qz#)M<oq%B*6LrzwtwO|Wx|ax`;q}O&;I~sa{({< z(3-G1I0ja_%x50~tA;!qU}Xoari)c3tol)(u(BEVL9t-A{T|m61+%P}Fe{DEtRvh^ zm@WSlFiQsv!tna|b2Id4sO@^xofHAP@y>E->uA6$V3j&!nTLqELrMU@sfn~<7w_j_ zzGpGNr#0pjOLq@0XH2Wf-hegN$MW-|;7Q(hGT<2r%jd|GY0d*yz;J{n`qotbW7=dd z<IbA7>swDtImEroA_{pPzd~4|j8<0I%AB^_Eg2Oi+<T4OWfI=$%pslOgxp`Z4t>>F zvMc%lbB>R>oez^&kIrLlz?yKs-hlU$kAA#9`IF?mH}otcdGBZZUFBb%^Y<`6bUAQf zXPo4}!g=rG!g=poE|m8kd?b0khNoiZwVxExPtxBb^V(;bF&#(cwSV-fa9(?1l($8+ z;s4FY=e55OUfvA2hOC<8=S~{AHD1XQg^cm=kH4w@-u_!yM*NA<-!JJ$r}(ayJ_A_f z8vLK<-g|~}0FyJ|_XeisY^i#qr=<COINlNVR@Du|iSwB2y;J1RoiD<9;;a77xn{Y4 z!@NZ{QOLIX(FSvlyKO^r3j?7x=FniAi!S<mncvZeJ5iOVUl8Br8~N6!WU4b;`aUFx z!m4-~m#)6)`T6%e0$!kDeo-s!7nBVG_{_<~%}ImIN6*~kr!Li3F;>z3iDL7fsZ;9m zaCQ%2Ju;j)gTcS=af^ETo@}L@h%$`v;VM#odKnYI8}h9Fi=`lhHi_?V$HI5urGl9* z^K{LY_>k!~bE#i69BQNO2IohZE(ARGTZZO-sHq&(GB&IO<4BZokw@xnIIrv;BHz7= zckxm0ilshV1eqfz@SO1KnbPl#ak&nW<|tSy+Isx38K=w0-^zI1(es%j{LFnpyOaUo zSb<&l)8A+z+TxxLB<g;Tm9e&Te-r%}GekXoHY@)Y#_MbTM!l|j{#U@P0Kacx{EZQu z(7~_&^IXPObk!)`%6yb*wyHWs2JYS<^W13_rW^@e_x|jW3)2$Dlr!%r8MnrlsTa@4 z(2L9CmA}WrH6qo6=j+zA<ksMs?>u;J(rgt?fXD6NSLVlwgY(aac17w_B|V9VW0V!K zum0=l@#7f%HT22i7@acW#8VbNh4#<?HQfHMjP@Ciu_`Kdks0qNQ-ov4-}+k>$H>v5 z2RpO$VtY+(aWnqg@PCRiPukOhuQ`|=3hnxI@p(q)$2w_^^X;4<q)z`}#F*DUtzsTT z=RblRn0A*%__v-9b>B)q&Xw7BmSG;uxwr>=@=a^J5nsvTK5o^>*UNiO=0f|qvYB*u zqg*HXOW@yT=18_Q`3)VxOVK(4H_qXOz_<KKt~BPTr+)Jb{!m*L`o7EXfq7N>g~Ya( zJA?lE@7G7#rk;X*F+OJGxqn)%om<KBJtYHaLv!!S5IK#O%v#bIt_$Vh_m5z|rb(X| zzg^3$Ei-aj)mSnerKx)TRg~AHjvstNnZDT7ka{Qes`+cwGj-VcTA6&NzfQXmYj}>9 z?tYK-shU1WHis)dN%CMnGUBCjyp4{DcTRzfMLWnq@;}deB|-N)L}#U4+B!B>PMRQc zIuk5u^bO;^nL|8Q$>$y^gY1^ic}b}60PLOE`*u;!c<z)D?ac82**gz1I(4dDpy{_5 zvcHN?n(b~=XJCEx^JVv(v~J3!Oh^A2!h-r`-j}+PKu^GvyR=Dlv~%bDD4T}QnryfK zuhPD(+Y<4v`>#?)e4KN>@~_HYmofdj?sqNu8+~p+C(`zZI^=ygbvBnq;41C;`j4t_ zTr0L)X0-cLquuJ^kc1_9%*cL!Xny}FRJFv)IG8;pW{x-Ng$YN(sZZJsbW}2r))Jv| zj|K6}{GyvQ#h0A|oVGn%*m?{2F}D5mup##bLsc0f!%00{Qb^r_d>Z?<pX>xY^hELA z(|bg$c-bedHXrvD<2p7ch&!1VmVDN$fREJm$E$OS^B}E$^)I2eY{0GMDU}DrF7LE* z=5_$Ts-@5QZaF_2aLXe9KUUe|$VsDLPN_Cg>Xl0yG9_M@YH<=qrJAjtxKOGk)iZYj z^%m46Fh3FV!PFS^yj07Oy7^KqM~<abyGZ{3wK1lD#h5(6YrCPV>@x0I3JhPFJ@YXa z;Od^WDnH(yXH|Z@Jr)&TbC1O${b7G<wD)CWj!Dwr<}ryQj5TAEey}R1U%9cK`9@## z(X>y*K72?kpwH1}Q7q3o`Y<w9a<{f>UU{O(>9E*pi5H{|1Frz5;}u<S<>~TnLDpF| zN!H2G^tqA-EnMX9x8}RuT9%_$6wi~n8`6ZoO2c`YrTc3+cP(Ld4f?G~;n_WLHsS7u zyk48au~g4x+$g)2Gn+A^HVcoeXV(&Coz39u)bIG&oYQ78#rPd3zsHJK9t-EhP8FA$ zco)7W3-j|e5j2(bbVR2Mno1gKvo}@F=#zd_wadW2SHu;Y{N_ggU)23FW0aEjDEJH4 z2yjcb$hE!lFKYj1DLqnUXqsqHG$`GDH_mODUjuYV*uQ!Y{gTaG*4L+~xS0ayQFxE1 zyMOW*wa4}0@ZJmG2gmY1*lpgsF;1&I*Mm6Mdu80Yc<^6wZt`jugEtt!|5K${r|EhZ zWxz#to%buVJkv#2tz85;zY~8}I7ptT8E4j+KdbqDM4!YbWsFFfn}@XP&8V_(^S8lQ zeIWVQrpkdY=>8C7h<*6ZZ+)AtEeKRFAJ_`FT@z`61-ifgYm{vu6He14&v=byb4y;a z%whOtOp-k0xw>l#{^7j3fOlRY=1Rg^_0QA&NuUAw@jOeC>X^6jq$cK?_2)K-G`4Y# zb+Vg%oOTJn)J@8Ge5zfuJ)QwAINwcNA)XLlOk9aP-)Erx8t}3_=kteE4(Rome>diR z+NCRAc$>u-ztY82TgURi#LxRsFIK<ina}#syhQ<h*Zacl9R46w^Z?qSzHlq!CEJu< zB(S`vBvZ?s&9j8(UHIL_vq?Wr;hA?3eT>KRgc`>K^zk&tvD+eMb1Xy4MM>nI;djQN zE5=yXqfUi{Q{B))`8{b%=^LVCj%J(7wl4}9Isx!O{9XB?jJ@N&4gC)+XRdRdxfe<# zUar$5UMgKyy1Ng*P5k`zu#8dQXIpp4I3hOpJ3Mb!>zf|Ln7b@SoIH#n--wgfzo4h2 z0^{k#x0*M`j`ebUYdHnDmMvN2rVbgjzzMwcXpTkAr$mX%YOf{lvqH<6OW3fTXuOyK zc+~VMKWz`-u@8KWUGgwE|G{5uikJBKlFe8z6CdO|1IPE?BhrZ5CY};UP5dN1Aa2U? z5xDd2>uS9&I40l=$3z$>VE^Jw&mON1*L6y!87pMpxV|6(84J_``eFs&9LJc>G8P!) zNv02*`HV-$Y53aHJS$ZUfqlfm5r4-OtEOnFX{OwDA^BjuJjQJQwq(%LiQ)u(?0K&B z8~j%n{uiui&SmbP8jEO%%vT(no|?Qkcx)LfUC4Y|%$GCQCb~bgX89hB7u^k5qjts@ zK>Iv9J&<#Fk08(L1ofNu4l?J81G1+k>Ze-U+nEzb%F`J0RmsM^2+k4EhHiNtYm@7` z$QvYx6AQ8C4)EnRv88es<R@FfBKJVLq_qu!gr-Wz4Ow&6Um;e>Jgs=w>=53~4&KS9 zx3c^V=R&(a#JV(FvJRYN?5yFkO?Jcn2RwQMbH?!f{jZP8fwfWUh{joa`U2iXzbX31 ze8PEexBPHq-aM~AN>cOI!N>M8&otJJKBAiw)Y$pI-^fu%e%h3a!Kdt6DtK?(bm$7& zJfJVe;^#enY4s$Te^<lrA^!ey=AEQ<ZpLBAQU0r?pPhIo&p5&}pT4ETuKIq!coEB7 zq++&|<~#4CoD>+|gY!hIV{Wlpe!rxt@@=cbm!E<21bk9U&6MK2`x2Hs5HAki4!PE& zY1F-yFdqrWNM4QqFB=2f@$J8j{B3UP)^^C8xq#m*;On34$i0*?B@)DD#v3%>KO*l0 zMO6*pbpvgpwH_&d(cg?YKa8BcMV=3q7K5iw%QS46c%R2O`-FvTYp-F0@&tHpe5b90 zU6zTHvV>WNe4<%KH|!%~`$>-V`=Bh#c>Bj+3&+9P{G^iGujX26q8sq4r(96p(~--V z)}=iKgBX9+5!6`{r}$pRZ#opw#=0lf-T54R%fg;$O3;IMqm8=~WPU;!M~!|VB2L*v z+fb%6OBBzK6N}j&Wt=^fTP;KL)__JH2hVg2H0{SmZ0>TvwF`TRu%&Kgt|rg>V%Hmh z8RG?DocVYsVf1^Iuedou<qK{BFHBy(7Gt?k9-CzeDn87Y9}3%CjEU0;crw0$w0*rq z6kf)hy!f9KFBT7CF3v<z<c`z*OXHP}%ofMoaMU^2eDqz9Z0~JN6!H5a=DQT1f7xz- zJ~w}xV>0)uEe^a{o0K{Hj0GHBH<^2Z^dnxyt$i1BYF@1$wlkm8s+!#uYn;9>3EEtK z{$-E-kL}nWi=B_AY{40QIC)KSE9=p=&NEzn*tt~{NS_wWxq`9<=(OrLc}%~J;O&~T zMAi2+XGMpWTsL4%au3QlDf%Y%U5@`(pnpD3!2hG#ggWxujE}Pv<$0&>le{+Ndr`%^ z$>a=C7y#^;x9N&iwYvj0^<0E!j*0PGo=@%hsx5O-h15%9tW~M4xi-Ad(lRQj59+$~ zNbbJ8mvgTG&$?^s%ef)&T{8A{l4@%P+VV_4lAEbzx=D9`j&V#+9-8+Y;PH@1bRHE+ zb)C$Oai4-=9LCNuGS|Vg7~8@)1=oET+kjOB*=91@{70hjzbyTv^`LM1k@iP1hNIx` z^7CG9zh1NXtjyzRjKOJ)Az4e7w#4tD&H`hN-nBp1KG3NDdfzkFEk`xSpdI6)o+YrZ z`q%q3TdRG#b<4Z>9%y8YeA^)7Gdz61e)v}?SGzi8%RBtG#_pqS73W<e?5(9*^57pq z7Z+*H0|nS4DL5~QGt4o6nFTO6@WqXJmCr;Q;NzUYhkzFEOp<tX`|kIoZ9{Thxt3f} zH@&spw(9cTAuX|P8tpsF{%MeBBk!Ew0u0dZp-$BGp<UKzY+^gwn6b*UTiR)$UVhn( zR);pB)q!@dM_;^S;uzlst=)%u>1)zke-3#60e`oi49(>}Yen4wtJ6(C6ASIjjB&eG z73}8sMMeyY1=99H_y@7KiDxy&USRA7zGplM?(b-v=2<xQ9qoR`zlS;V5dBpyKPc~g z62<Uoc^6~#oqpBY#n|BIz^gpr6q^H+sguIFlPc0&tJR%eXr0Od$8v{J<_vzHOw@y2 zIG_HKAcDkeLl&)M?#%qhn{#`2$Q*nAH7H+mcI9Elt!w<F{sDC_ff>5LQ}oOQO?%Ap zxbPkYy`$c^lzC9V_j>-Q@}S7QMEkC&JSfeEZ>8saI1h?PbIpD=LFu!oqoE#1#t=(3 zY(P^*eV;|e5VPb_j^ka}WMApapq*<)5%qzf^)halMbo@PMxIDZAK+|0?-e8UW}%+m zB=f;neqcqtv-(EbA27d(&~E#wV|eA^e(+q>=`1bKKj^oLk|ay-A;RAFqQ0@;`anD6 zoL0{9I{Gwd&AE#TL|T<*b8ZC8CieX6B{uO`+U@774+n8h1$WvDz6v<oEvqic*T91` zxb8e<ZOQEbZ8?>w`k{_z2HF{foZN9}V*T<=z<SOlyx;XxUu&~$tsH7i^anO2`Y(5i zLfQzujqmTw(Y#$Z*Y0k{A9>$0Gqw$2SVmsp`H72L@Rz(ge#vFP3$Ay9mRNy459R0m zp*<eB+mALpj45`rXE$wn-iFNn59@Td9dxifb74h`X0LF77A|GX3~S3*yELb>^hK>c zX^lv=B>=}j&v<??Z_!+QJNieh9{aKCS7`Hv1hKdSbh!yUOEdPhUFHCD)~#iH5A^$` zo0T0O?NLgN_W``O-K=OLbzL?s&aq#McQ~g1AvZtozj7CW?hc|%?ai0(=2^q`iJOBJ zx}P?x!RvKD>%D`$$+?x@oWEPeBulPiTs?k24LI{`m-l*j{t^D4nQZY7xh($I$y@&s zaQox`!7Ucto<La>ZaZl!`)BN1;9X$F^wIeGHM>8s-0pwDuJH98e18}1?gy@R8u;30 z8;!3mfF1Xh4e%p8qhV+V>`GB~;IfX~8x5GXU=HSZ7!!CuU{^|Bh4M3Vi$%a~kpUl$ z$Al+g;xXXK`Ik2|r>l1byXl|8woEv^ZNQ0e*!Laqqu>cv-XFhY?fv&Ff4iL%ygPpm zxi?Lu$#_e7`*NE>TL$p{49>iTHy7?Eo>^Ake>gB`^>0rU-MuSp-lhMXcmlF=RUgV7 z*b!=*%R3d!xjFU6xx^>pSNVlt694}*Ojds~m~;XrPviI65)}vYKLCs4uY}riaV9?4 zO<SV**kkkk<u{0&2lIyJb>82(B^PJ&&5hP2{z;;caZLANua#~AU%KYUxy&U?|EPk! zpYzVK+geQ+uXN}occthhYfs;QxY?5FBrI?Exr`Crp!}>Rc?t9F_^#RQt-3v<^)A4* ze{g}Huxe&(t@ot=P*JwL+qd{1qWptzO&Ox<o8S}e@(km?w)x({8g<@Wx7)TlX^W$* zpw)NtjNOdE*QjOJEyVcqh`VXvb1**__BikJ2T*4r_7LA?qb%3`0rbK7n|-nmtW`!R zLDY9*OlPciH_N3r>W8NSM=Tb|NWcx^2<0N;$ATTy!HNtM#@U$n`d7lSYCjVJ=Q+Vp zVI$h^O%_$JCWq$~o=^I?^Lc?XHGYSlj`s(ETYMfw|D4zIS?k;7Z#)yuw-~?wX;xb< z`TIB0T5^duq)$If6ouco>ECuIKn6Jq-f1uJy#HjDcOT`wo3Gd%K>M>WAM%RtSW_6c zsPfi$QMd<ily@ntL;8j~uA67>4u}*dWvzbHyY8mjcK6}^jGOM*{Z^bv4T<===G6~v zv6o%h+WfhM$!_p~cI=6P$s$;A)6CuP&C~tl4+BqM<JX2mRaaV6KfOt+pKTHSbjot^ z{sH_hzxj^cY;z&rUrD|d_1Vsq=R-xZKIk9qCXZ@yb=!tQRov_5J|DA&Xz$SW(QEk7 z&q8gJBG&Nvo#8bk9qFfir&DMA()UZOwu<1Jp`ufXkPESI>~~$V#GU+A=feq(iUU@e z`*MSgamul89N62A1W`yCk$eADOC(-9J`r9QXkonGZXJ!+JRbs>=aH!q?Sf8xQENKX zEZa?%?XDkbH!Iw39@+){ko(1@Xpw9e?_|56Cql#CK>LuLv(H*HE6N)?x8$IHHs<}! z2zn7}C%tI<RP<uu&n`?aYPQFw7bac(xj`=&v*yNVdV#sbhHH5KHE9v^7o+E&g|eil zt-Qy*o_9^EPwpv<Ap?B39Mfsg3znN>(2HZ5foo{Xq!;s|aLtK%-~VzL*RBMvoxW{h z!IB7CaU_yfjLapPR)pvBqNEjB5ws#p(F)X6v;x2J9HtdnpcOxqwBq-m6<G$Y_}wU4 zkyY21p!VYt@*$uVr_uiGC|dFDQM4lKQ_+fj_dNnyF{5?g<w>9wSAted7(**g8?@qj zgH|wpB>Ra)D+ckq2DF0mE@4%RZ<IYx8}wp=K`*A*!t`Pd=mppC+501Cg}Huc@4vQ< zUcW7%|0xmcx98VbKi~}UguD}Y$Prp`#$v0;ek)XD8=(_7IV%z!6|KiYW9Y<stD+NI z`$APe><f*i6Q2N|7iwXAo~p&h=hNpy*&OSK_^<F;*)D~!ACCby_-+m6$@%XPVsqbe zo40qG=KbusP}_%~cdmR<zYTa_kbrT4{?tG=qs{w@MB#lQZuI%~CD0%>=9H*0CxvN{ zb^J5iq-_(U$B1`wj3|2##>hF)=85^->4PF|6M96d2V>*70xmr`KwH;%;qMkh^C%lq zhp=H|N3O%-a0aHy97_jF%iM$1Hx$fjZZFg(OS|VH{ND;(c^5E)jGyx*+h{ubqC?yi zo6e?$=V%$9&X{!eLhDc#u?~Me8*1ZNlC`GFL91~8z>;2Z6nprs?2g>GFkjE=C$@wj zFC?Mw^{cH*%7JtC<m;Su@%D<^y&{#l*<10RJSO$aZ;6gv@|d1QGmGiNvIl%+ANE?O zCH(-~b4?c~3b2m(dz;(SwR8!SzB3`_oh+f=nDpahVn=RZovv~SEEnF!L>13CwC-~6 zW7{um&lk(Kpx+m26VJDQhI*M7UwGk#*l}B=J*>>7V%o^byp5XJb=l#tuKKmdO*^&G zI&;Q5Yf4*k7C2sI=EHk&D_{#cYR35AhOs}$c!oxM@zTyJeQ+({lX6(;$Jxi8$CW<( zt%!RQhfQoww8gRq{OyQOd#}s^l<sT$1LT#F+-13UP65sJT%j{}P-TYZtbmN`l{p}% zG9I+3cVnGu)BZp9z62nu>ihq`0b~Fd1XM(YaZAJv*Dw_aaREg{z@@TefI&uJz?nh8 zw9q!xGEGafOtj^Tv{`9si($54E-6}>nqpGnQfXOXrTovi_uMx#Pq4n<-|zeX{+Hjw z@x0Gn&OP_s<=uDhdH1p%6QPac*=cy@e7V>YIs(s1c~2LVQx{t~6z9mhU|Y~b-wpjr zyz|?{6^+@p<N77-|9;U=@mZOD+BmfIYCqKuc=BOkCvp1E2A6Zr+|PtFkj)JLP9~PE zTzr|wnS<wiYHbA@w>Jd6bY;b;m&t#n7axsYe6H6^x>he;jm4ms;(t*u2OHCi-#?+3 zQKXmQm-#t#P3y&Fy;UDX=fkz|!1Lkwx8+>^Ag69{ok-u!xEbg9jPM+0!NPCTE8_cE z7+1VnBG$84=yx^a>)9x~Jcp3)v&{GS!Eb0i#Gc6+VvU6FnT&H1U1RuJK`68H+t2si z_7BqT+aS-@JNcn&S%z`j#u3#{o8qSQhW6~^Q1>op6yo|&-)iUcx3RdFT+Rc8jH2Ct z#S0A$Yw)Z<4A0Mih37P$DTH;1$1e^Ga829EH*7$o?!_GgwC50N&ptd&`&7hzP3O+y zo-ly7DA1<^EeU0TSFQ;^+~=jSyzErZS#-MX05^ymaO3^9och1J5&eI4=&$vUXJ9s_ z{{x5qdHqlL59+_}w7dS3r2eB>%Bg=<|Iv-<zwz@;aX)J8qka=-$l-c(#jhTo@ci<M zcnM@&#LqWvQxRJx_EE=uuFzead)l^QM431<Xe;O*?MdCwJq?Vgg)nEJzr#I-PkRJM zpuNkP&l}d@GjW+NFeiIqSfCH+H=+fM0dT%vSsK#RnW%rY9`bq_erL=M{T|Eh!y{rm z@Ea&L2W|HL@X$rzIWAW`y!d(Vzj=5^?03mWT{FIaMg%{Hz8UMktR329vN;%LBM;NQ z0^Y4w|3YEVRxYP4OPvShR*L)GwlMBUfxhUmW^C|1x;8n+Hr#Gw&OR@eitkRnJ*bbP zecxlc3fpA<t!5BE+kDPM=m+Cqe2(iJ&)^!<Fyqki!*IQr4VvI(9O?&jkIpzWPQm@c z^*IrpC(QQ=fN*^jU1wNR%@Y0;rAil2dKHk~I}rg<s)!V&L_nJK-V>3~q>1#dAfR-l zx6lLxgwQ*L&_eH!goNbg{y0fy_MF|>oPB2A-JP9DFb)w4U1nROPdoHH+uKEqx%!F- zN*s)^{3n4_cXZaA<cV>vak?#l@o@4v`!`+_eBHETTrHf_cUekFXMt^X%|YOv%W zBtmv%T+RrWZB*rP6HOlHbad8h$+?o$3{8E_wcN)Fo1!j%>0tM?^oBc?exS!Ndr4~z zU;b*FcK;8mRddby_nt>!HbHk)=qn0ofl`};s)IJv=4|HT)SCM_UHNH;+UYliBYh-8 z9@Mlnv(2e3cmWG&=-mM<8+SM3HxeQ~V{Kg7TxuG9b^fTYJRpUwh|C-en%%ZF<f4}k zM627qAG>+y_!Ru#q0`m^H31jsJ2|jV*-XY5U~xIV4eGmm?i}Csq`m##;gOSb;GnJ5 zH3wiyDYkI;Ubq%SkwjAQQ{e_VEO41@_t_ONF}%KXCDT<tKMpSgXDRu?3P+uW`KofQ zc;7+!-N#;31A5(?YfkXM@10d?bj?r>7*t#30b*|U=AN}OBL`Ljq%MB49uB3sT!$tj z60d7Q72XkMC{bLSSY_*5D>pR9)a_47*CNM`Nv}Ho^;LBsC5By&8l$V4dp&;?Sog{u zc^L@-f7*jTf+EG-3vneCpe+f&@6i@cu*{VKCr@a2m^yF}m3j0}yuSzQm!<O;Q-Ano z&(@l}!~Skz*8%;f(BEaB)l&Z`<oZbXM-KjlO+Xh;@q7L;{t>FG1teaai#tlmYoDn< zP81M$tWRtjA|g-|ntGzhB3&_h<5TwsN0zl>^TmfY`t=t^2~Hiq?;bAK46xH!&0oF9 zE+u*IUCLHe8V|1oer)i9qq8|RRI3(r*KF!^o^HKtfPKPy{@i+H@%lChyqOWRwjcDO z)hUF$l~FY(0~=zak$85kqKtmHiH?e)R!zPFKh4y@EiJR{h68q3-4lKs^%0&01!Y^G zvNiO$w>5<#)HYQMQorPXPn>#|c+Lxwx$6_W^S)s5^%|#TR+*J`?xS-^Xiyc6MI+7r zf}rFxOdl;u&ovOl_y<cp>G82)gDK=$PQz~V#;&=I>jQ`rpLcd}9v$e>?&aC%6FEB0 zsnb*P<xo(Lw{O1qdkJXFL6oA5+Ty9F2vQ@oJ6!7M6Y|uOwl>6#7Kv%$L8}gzM~2~P zl-Veyi`fQ*ed5mB^w>A!oUG?GuRFbs_+yjX=4YcglmUtE(I+wZkAcHBK!pwrbHc;V z-#~%xU$A%L)u~po%{?lkO%Lh<O{BM34tWE6BuJJg2I$@8+J)BD*G!zcYOw;yyxUFS zFTd7BF9wm{xFVbuRzpv$>D<Lck;T8iTMc{P`{;0Ys}D8&^+><Pl6&;jxt#>R!E+c| zdx)E;rN+2z!k2_bdcTQD!M|?GI6Kjt;k>$)@T>owJwV_gBe*_vSwClmA}|pid@U+l z&Dt7k&3ZSSdQ2%1qRiOz{Genq^<m806IChlIkH3U>H$s~l<h5MJ&)Jg$nr0W{eneK zI`*%dN&;US?Zk%*g-NI@44K)@7tlG$L#SgEg9;cIBd(vY--Ee(9Nk?tQh_l(4mUeE z7u*i_f*XW+8l#TaL)*TuAZAGnJ9B{yPR}7v(P`1qRbk?!n}9^PO}DS4n_gcIRm6Zu ziTVJCBJse~7?0>2`!@@K{0?rcc*VTw6V_4|iR}5Kc{wzTIlMGZNFJNT9LPOwVO!L! zKnlp+gei5|<i+z34%#md4B{+P$UOjB8ym+nr&0}IBUPZhq~LgJBaNsf)4K9eVm(`+ zmQc_(-<Wn>9m`l(e}fGJ?|`#2a7z3(>Sj>No<85+S7M9_M`b?<n^jN`=s=FJC37UB zT?Uifr|T?a|C;UY6H^O7V<eLL7InVmkN@Vg6P53ep=K@DW0k(M6WZ3KguM{;_v5oy za>um`fxauSB_IXT390rIgTxA%IWSv^gGDiF^Jakyi_QbtPoZgic2<v30dyO)PcHBI zKG3AiXrn-9K#|?PaJB_(pqqbR4qKvjaYesHTCy4sU}Fv*q#t>$oxcusuHHo`-z#Og zg@aCj%=e|$`>$(~jE~(z5|r1N9)MR}m%nqA%?8(Uc-YT4%@0>ouj5wFo{b@yj(-~@ zn>%z_7>lf4)~&?0DjY%!mtJFCRKXA3Z&;5`7yiL`4Q<WQfl$~^^m_0gi#fI<mjNUn zTXdZWwNu*nIU8oUBQS9L17rASRo~0~4^lMdFsqqpdvT}gYq<dVx+K_)+D9kPwO}~f zovp*3T{F0sHKoio;Y*q3#@T^ZFn7Y^Wcj=bRSwpam}AYECf=L-aCk^<!Exqx{n^En zjr1Ifv2ZSs5~Iw8bPFQyKd9h~gr<n~(Bf~Ob!fhO>?GL5wjX?1Zg=;@Tn6M7<(;yD zR0ZFvtEht`04kjQfmFC{cBIdncq#L+ljYS1v`SpVuyW{8XvDKCHfw5}F}yEXzOpBr z&)l2+bxLq7n1sU*W_R@=j|@<VDT3Ay4!3@>aTt+Og^vrG{goqCKQ6allC)wYXR(>7 zB}Lk%pmu%jy3`Y5yoYNLt82WRNj)cQp$Exh$G-4LGuILE3*POo-X0vvk5&5|$LECC zt)5g)S$0#SrYd$nhz@}6A#avpbsZp^{onI|pRD`0uM8Nk;yb143R8SH?0cxY&Miyq zLq)>Z)W`;81&HpDN!B^@_h3SOa)tdfNSe=1lzW#3!?fy1rY7Ep7;W%!?RJ6VfS&pZ zDAt$CTE3d{yOi%A5x{uh^vjya9ylIqE74}p8r&i%_NTA5&B-iERnOL&FlUP0X`hr| zXX_Naiu1(0Oin)T>m>2^{Qm7TmxlbZCsh~l09`k)!nD<|{JF_GhK+TV&i&=zjru~U z;0$8fJ~!4n7ju28-MRi9q~q8!D;e}%FGSrB)UdDcSqb|G9~JOb`^Q95nYVYL8;QXv zJWjei<W++9Z%m-4n)zzqe8~d7WWPmKJ=`qm&N7SSq?b^<w;y~M+}HNzB{QkBO!G#& zEtS30#ZV-wcYgOH%aIC)|2coqY@YdiQfRHj<!Y^m^cW-7kDS~@wdGJ|aeF;Tp<R3M zW36_tnE4`C^U-~}rV;xw7tbIal4j*c19Fn$xpXue&l{Q#O3)b4@r3H_!z{+PY%xtc ze_lOW1MQeQ!&tfG%MRkQFzub=oZo`S><l|$g>tGLNA}ta@vh}uJu16_1>eYEbYJuo z$hzdJDeZ274sUo_vtYrr4**4ns!-1~5j8>mk2rpGt=%RU{1cP;dUMjfg456FfT2&9 z!j2bt;!iH|+|6VeD@B`LX3@y2E742an6dS&7R6AN0{UQ5blaXw>8DUrKt~d|;tmRY z<WDf8D71fQq&zsZcms1)O^eMc+P-ySxE5+Q_{lqoZC~xEL=2X}_Sya606+Y|{|cOJ z9vjH)IbrUe_(O7j7W)p|?ObS=qxlPSzFwGk*hNDOv~F~3F=6C@*$A7@eKa_c6C-!@ z5j8f0!%!SMBG=w{K#;eOQ0Y~SFo6a(r6t2W*e5u;m<@E!4|DdmbejATshWaHAzcO2 zR7X1T+w~M$Q+SS&uC3nFncy1eN8V3yLfT9n;VgewmW5Z}SXj@<AdJH@`#tlF4q4>U za-3o!X8RW7v|@Ykoh0~k4YmP)S!+#Ae|B|q`6ioR)4xFq5xGOf{J{?(YX1Fb#%<4g zkS^L~dnsIF^oQl#^_U83Pcuy2F8Xkg^IMh;ld1~trs{ju&D2v4klVAtpP$BWf7?ei zGlG(@av0z4-I5m95A`!8v)qw%n4J=dSz&t1ZcCeg+|GSbE0IgTWzN3n`0B&FSqqpi zIK+aj8<>#8X$G#A6uq-9h!wjhR^X*GUsZo}PcM?IAocs)%<IBWr&5c_JIJ}$SZwV> zZgXwdH9~CQ3m<|d<x}k5?}FJB>X$R1eZ;L0`933xhvSO?g@zB7tB9!4`2;KN<3NRR zQ{9lAf>7P^ePU|Wa0VL`nR;loA`R-*TZ39Fce#gM_MbmVirwFO$$ng%mL3Id8vnT7 z0od3%=>29%shQgGac+9Z*5u<@1e}rQ>LuAY4dbgmkLa))<qx_RY_YyU2Mx7AwS{{z z#~tHxZ<?cTn2jH>O8b78_xtBMz+pR-HAfK`eX3UDO}O1sYU5@d=jYv-WbM{c{Nd%$ zNeaBwFv4}|Z%^_lMOUzIu<SL<p_;l+-=^NG^~RJACuXNgupr28=s6T~OJ#@0dFXFy zpnCU;{Xn>q+kHr`y!Od0SdMXc7l%?u&`Gi;xcQk3>>a4-Bj}<Q7UpUu(EKWFa8ArE zd%dcG`sevsBceINR+j;e<<)<|80sZ**EeiZ_bZs(ilG;ILcNad$arR)JOQfEplHH8 zQ2~@SP$}-$@!xSzyL1-$6sD%DSxX!8U$3^%bu5)Gn^)*ZFj)lrQ-9<VN|&xhjH;*8 zRO;WGhC_=Ts+(#fNb)4lJNa>n&w*MwlhU`hXvr|(lX0EASG{iPU&aJ=f98mTTz{MI z{wP(6n_Q{!yVI-_#JVAzSRSR^EPEv^+CASE3~@^?C~zmaDCqgYVl)|Fa4{K{WaRbc zQXxVwsB5o8VgueAg2_CsmqT!rx80?pavpk0^0v<K)@r5w6Pa0_|NN8THc_F!cRTOj zyi%jm&8GGbCn3pyIQ(raqnafD^|w5b8~vKY{`l-b&5s|fXT}jNx9aMCDBIY@?mKy1 zW9yh^${s8}AYW#5hOC<+%<IdQ4NtLJfVMuTtikApl#Z@TQYFiXJTZ+iW^Shk`|mV4 zu)(#vA^o1eB=)n`H-N9yBF>3B?hcyS8ZlN)ie|lAq}FEN-1Kf)2F;f|mG2j38=g*` zG;(!z?!G`Qm`1>@*A;(L4n*4OD<wDGMT?cGW8Gf6X!gB3^c@<T|J>kOTm#u%sPHlJ zYO1)@<yw}AfHr0xW3o>)Rm%L-)kTpT@CMfcbF{*OpGSl%AVJ>e;PHvan2i|H*LKYF z`qXNEf1pep$r@X$tnOGy7WU!EvjiP1{B^8SOcRqGOxBx5IH}z%_zcC=e2n)7;;dW9 zaE+z!A7U_oTu))HVPg_yx?|G0k>aFtR;6Rpu5OurmPM1iNtY)NT%B^irA5CN`R)au zm1l#Ane?tfsas`QXn9f0UhtgueYJ25)Fkmz0sU|IlyW<?e^aO@jPRMggyYZ5(mQ=w zZtC^(V@r*(wkONUW&pOr3iQqVs}g(4;l^9nq{4Hg0<Y6uBPU<{|6?M$gTZ2&Zs6^` zFPOeI*tf*n$2yn#U9#+Z`wHUcc_E(6%elL@d4GAWu#>FGX7nm+Z@{y~fm)E>u#xm_ zXdLC0iRj>e8-E^;XM1uE&v|qUn<h1RNo={wtff5;q+6Ep@uWtHwS=adOYR<C<Dc)0 zW?pRTg`7Y9sI0P8(wu+Snx{*-e{hc6XZJhcBkmOcE_<YP-<C5ZdEfwk625==AM?lK zcZGFpJeJws<e*}LXoBm(xeDnTYP(Ph?3K)BcL!D0s&XZ~GuEx<@|=R0{Q1cC*6Ok~ z?a@@sed#_r(wjfJM=Y$h)RIRcfBXbyXuX?hZYdTDBaN)wGpkoGo3I_u<^d(uWbL1& z0nKIyMmCjBQ?9uA887>{&T9GQpM6#D`8^5k*NF`-a-`q=ViVypJJmu~q$pVF{zuz0 z;F4q9m^$Hn_1wW~PBG>WEcpRxm}Py0KdKkqEMobF%KnM>mGluJuIazw;R4BM1CY@_ z62!wDjg$6<rK}iftFUL8G1y$2qwaudO`wl(f^}k#w=1b-_8BLjK3MWdIFPX|>S0aM zDqWyP0=DROa!=5)d&g%i7t51^ed1z-&F<&(SJmfkcAjKS-@j(o6c~h2cZH{2LWypm zR%6BM$14{!w3q)OxqDCAy~ouGvafPuobHZQAo8S-sDhc7C1QTBrHIWZp}yn1#+>EW z6eX7T<EUd%PXpBgyOv+H#iXcg9Q`T}6>7y2lTmP#gbMo5ZXaoshw2>BY+Q@LH`>N$ zTKcwvXzbs?$b*;@0c0D*yhnK5@A(<nSTDed*J(_Jq?O2=SAI~-V=i;h#$5nl&g*;P zNiH`!xILQ66$1RUhMbVOeO(msmjw1pj8Cn*z2-0D&hG@jN6X47xn1#=Lale(TcT2O zd-@Z-71DZG210|xhxB*Ar=d&P<An7B7n$#bel>$&_NZe|$^Yg!_;xxSR6&`yL5MXL zWIt(ipUNYsWd(vtlVmO5b?`1gwT9Iz_g!_QO}KJwXFzvy%%T37pW`=^jdZtelT(%S z4w_pTW4!;g^G{cP%pO=Dc`_ix9(_)J&^y2gV(K+4__x}xP7dGgvxHFpI}+XFKWSy% z?W-<7m9$QM&eF&0$+^nkOHxyD6Z*TNnL@=g=+Tr-S)H9kfRs7spTkf^BQBi!qU4>+ zJzs~_;uEN$N6)7GT>og{GNtwM#pd?Q72WTg<O=Oxj1IPao#!V@_o2Xi+70ZmM(75z z=;Xwv9(c~A{PP+6ksQ`PjHO@dgDQNG6gkVYEU}Pxydk-&o8uN17@yP`-(-{;%31?0 zkAyO!-cS8IeFCt-kmJ^@p*{k*j6To1xao6U)&5mTNT*^~DNUCH+M@Cy?{TUZm*IX) z^?CkTa6EQMXMFw(%ILz)o#Jx4QU@8Ut~*kq#`1u6|FjG(8h&fwtNJ+D@_wOF7&j-B zZv;gGj~Xy#yl*x!5>=?LvjtdgA#vXe7wOCFN69h^Hat<WhC2im&~;GVJCmzTKOJvQ zkiAk(mS5_)=T+#{?S!s33p4K?o*zDCTNrlw^XAozLB6K)&+oQ+aY<EwZt|v_^Y0ZV zmaI8W?xKp|r=+X!^GjP%nQ(=Tq<YkQO>6sGhFJ4JEI>_$*)s|FXx0YZ#;{pY^m>!( z{lKxhw}C=+R~_*XU+?t}@^84c6{>jji2U+)FY1##yG^<YfX$D5<Fb?f^Y4H2*&NDE zZMKQ3U6dO=?LdDgPxfXr8Zme3>knhMapIyueof-&?b{_j5NL2&+IHpU#N?6b{@WTE zewq_G{sEnRMuhbm`<Tn0=4#bw`}CHbH7%}|<6lBm8~(-$F`5rc8e=U<H;~`9Sy<HD zo=Vvyp6{kj-VREqvJt~#S>Imq+bH7WBGG6Y(@9=X_TBZZf!cJDjsWUGKR(X|yBST3 zpyYYVQ-;1%P8bVB5lW@@3f=mgUsK|Xt$a)RfR?De)WP`c6Sirq7Z=fC*6-8#!nl)P z8gXeuBSy&Hq!dzvI(hK?o8Rxi6EM#f1|loHpJ0WcdN*q^zLALhh4+O@`L6~7;q@M0 zCqKA+nhg8J1vVG`1^UKBpb534!7;3%ekHk!X+P9>B#NbMf%V)}eY6WL?^BJ0>kFao zJpgV$eXGRX7i2w6H|JGyzxp#qAN@Ue`FH$iAhpf{2{*0pldJdW%TFyg+(%m9D4rOt zbB+jQ_BKTa;@@+CDj074HvG><uS4%^=wlG0SCXuu@uQf)YF;p)(aL&ig=E(*d-lm< zSV7^U@GRTiJ)?XA7`#$<6Ynp0-c@&`&wPblX6B(k3KvSY0FQ;-fveK(rT-I|WfG+o zjh`JMdt0~qE=0%)xCVcEsy4^AN4MSiG&Rt<u*&z;O?xslU9WTJ(n<3JSA$VhQ{HTm z-@_vZ`0b0c$2$V-7Yww!&psLAGz7C?f46l<w#S>VfSd~~mk`Y5-2|uL1L)+Jw(N_j z8`n4SB_m#3KL2!Rb}B#mZPT8SL7+K`5B*|JH=YD$N&I8nv9B=VbBNh;jPTjN8<Jb~ znkK*Xr>|koZ(EMp)NIlKio@bnoldfc#l2}`VYDR6CZXP%ee!DK*$LJ@<Zs?y%WRmA ze}4=pm3n22uBW(MTk7!3rkxVYjf0C_AYExRHmJM#qOH#&)d78}B{UJLqp#ESIKQ=j zk{ZYqZJ*c8HQI`$;sh}fBU$hBHvJww<C%Rf1o#rXHR=VhF{UZGO&v|l=B~hK4)HKL z;A#@^{%_~jIp<MN6OR!tgKTP#GWcnJVvK6@Gn?Pl!5WGu$CLjdl<W1{b2Su8PF2%` zl|}+2x54wwuJWaK4+BI_L<I6ZZ{2Jt9(~e@{~Yg??D|#YeaIU4*5W3m7i!Hm>@n`< zUR;Pl@b1cpt0~2gxGjmLDA#!}0}CpM+!J-<=+hI?q1}IG>u-t|-1D~LuAb|8C(C`4 zVG8&)Q0$j~j^UrZ?8{c{6G6Jyu~ddJXFpKk?dv)J0vP-VK6$<zxacs-(WjAH<u@iE z%;a!5pKgBaAVNJS|2ln;HO4HsP9wIjCcnH_h8h@!sx4THk-fb<7Sf{<!#hjq<xaK| zJb!gk=`ObI_G8J>h`I7uw0AwrfO>yD>GD;ZrE1<0y7Lc&xaIVta25D>f$@ke<Y7$$ zX;(LQfB-7|d%U^H{BX*gd>XHum5S+}a!nLwK=R>L2W#Y9`Bgq*?k=dD<{i5VKD*Ta zEeY%<SKVc?tAhzZ=EZg<yeQF&xLIR65k+k{1ec(IF$<>~1EqaGgawAEL-9F-H45a# zWg-6lNNV(*&ytM_YbMOj%cnim@B7b5l9!*)N~R2~vX$I&sKb)tgJutx6ix=c;rj|- zZ!JC%&ev<fJVS8Tc%O`!t&X*9+!X~cD*)-S+fGw~?q~x`Uw=4m`v|+OsUGk7O=KZS z1f6pfU}^83eFF&UAD#9NVKZi%h>6W$43FHoRV1e+!Qfu-$eE>xBzAzlGjYeQC_b*5 z`|UG7m5FezWcto07lB11qn}m{8a_Hdns{9#qyK44;F@m2Knu?E*5HEI80M!z5*xs? zn7QLsv=LXzFe(tj_5V`dw&We-qDHO#IC&VIEX0|A!dS~B&XF<S$WM4;FwTSbnqmSL zH_LDnw^OKP=}7s3oArg?{Ryq2T`hO5W4^Y$9pxezV|dICk|^S3$=U96*{?emMQ4;H zXFGa+4MQtkKUHKNR|kDm$uJKFBMP43bp$EiUMBSJ_Dks&pMF(mK<L^q;qj?#NC^o7 zM){~K%DH7iEv!eXN5`lIQmcS?l}LEot1it238(O!TYKC)O2IYrr$%5o;Y2$TpmLOS zliWVS61J??>D3E@6w99Ka0JfnJNFhqQe|;vj2YgJbQvWDr9Ig>Ckw0uD{ArawW5uq zYpBr%upB}7G60Sut(k=YD|(DvydK)Vedh%GfE$rRYg4Zly#QAXfZGQM9EsqP0Rl-P zIGyx(YArGcts{&6Nm@9*#!6j1v!+Z9{WdYDO%1K20M_(?KS-*4|8>mjriics^cl6x zWeW9b5JkbeKuE}0=#QQUi6IOj&91oQ54e3fd=HRfIlVT;D^|a-Ho{97A=$eH@#GDA z$w=Tun7slDu3R3znAh7o{t+6~_aYGUtkCZm{ME2+UnS8?ulKIt%GMk2Ni~8cd0^%> z@3O&g=<o_4&r70r%Bb~RLV2sYM!MIU+?<|9(8@^h=~)eZ3kjly6@?E{M$Afs<&JL3 znz}?Mh@D4tbq;ibI4Zs8VA{Qhxob6t@P?bzrf}j;y*uGY6#-v%*F!UC`=4LxN}lix znIhj*UJ6x1_j{P+mh#GBl74sV<AC>;2Zk}(GWGfaF(L)7V_us}Kd4Sd)8B3D*9)%{ zo#i_X`TRS8Eua3!#G>e?NqVa>`;aDAppR#Ts;bm#M6^OF)qXerp_Ikw`H$sjec$Q@ zTSvY%b!FCpxW0$`_fUM@-(_$fU7C;)3CSgWZi!V?9P?!v%j;qF@U=m$n~pfFb}dMM z8je7da5`5qQ8y@zpA<-vl7<N4nBTF-HvH$o+ug;2yY8;x!nN6}#>%okSiXazS_>4! zO>R3X>F>5~bMk{1m;I=p30%E%Nm{?{3(D|wbZNz`+19G))96LpntdP27`mB!DK`6N z%*(f~LqlZECid>MX50BX!`>(IMLL)4ZBY~3>b@^2p6Q&?3Cm%j-xEhNqWydPBGG@~ za&7%Qeye?AH_tjv!8{-nE6Ke6ZJ|2`@82HWZ7sd$Td=SvxtUG*UVU^Sx{FSmt=c%n z3g;>G>g_X-hPR1spC6?R2Wjqtv2*>Ioh}>W;N({mlZ}-T(<-K;7ViNIf<tHNwFHme z2(13?J&TwF=1L|PLxH$2Z!rz{W;lYGR}A)EZdBR{pZ=;5kt6CfcQ)ZXmI%MR?$U6s z?CLi&;gqG0pINVc0+WKf$-LK)%r@Hf^vQ}Br7_1?J#C%cV0#p39p34^GjM@s!L2N| z^DQ~bJG9A$HUeb)cI~|S9%5t+&h?r1o|&Q$d_~&fFQP#Gl8bJz+BcL|cVrWK!~SgN z90afc$1`|v%7h2Gqp$VphlIy&56w&ej&H1_uEPZ1?N~L?Eu?;<!2bklHEAI8=Deyu z7!wshl9s$+^IxN}DXaCU#YX0=wF|$#y@!uzNuorZAtNjmWRv*vO5s-wm49c)JiPto zBQ@V7Dpm6KQl7#8d=4s|#zrES)vRh0kX0#txz^xa+39q!J;GYzo}m9yCH?Y)8JpGn zuy*1PcYSE-=8T<tn3^lDEd@$Q!V;{&$6x)UU4G84__J4bS}oPPsVt%y_N|t!k%m@F zZ%u9@pcH$o@XN_2oz6#<6=91Vt#)&GjV53B#nnpho0i!@woAshtsc{OYb@1V?e(*h z#A>!nw(Dm?u8<HJ6kY>w!I<T;PB6j}VAhC#!<28tahLb!-)0egd3KZcJ3-<Im0MSS zJ;|;oeIKny>|03zMLi<2K_#Ss@}3IWpo%dLAxMm$T|d6`4@0U14K*@@YR8i|oj z<#(d;aCy>!nioyDd-W@07Na&OjK_w0AUiI;dn%4utJS3Mu7*fl^>c=Evh){6=gcCp zo-Q&7P@40QQqG(pE9$!np@=6DcCH5w#vBI@T3)rDQwUlSQ%P`u-LrR`{C?>Cu-NX= zzc3fe+TnWHy!yje&6ukQx`{-t&t<Ihx@(8s3{fLvZtuh#<qw5RA|CYCK~jYbrCZDB z@}J>6OB#HK)RN<r>TW;U&T!**r5I|9-LdoO%0w^goPIAjV?LKfB=Qf|r;P#r-WYt{ z{|^J!z&D3{R{HwR(}|;B<S^8@z(LD3mK8+T@Nq|@D4Of80pHta4beXlycounr_%(6 zR^nH~XJ_h(c&%a(!}J~2`damln+x0XwN=X3O+VDfll1@H3Zyw1+7NH81mGk<U=9G4 z)g)qU;AUmxch^I}oWRO09I^xd7$m;z@Z_h}NjB`<?iNc0TNj=nhCAaa3_t@iT}Q0f z|8|Xb{U=oVjufnNGPHg4NQf-(h8mrV`x2AbKIRk8g=yYkiPR(gds?$awz1Ue%G_dI zbJ_J-!piMS{J;;wK3dy~<WZv*DXt1<A7aR9_9SX8xzQqe=ngzM@$a-EAR+gbYArwe zH~L>JX^&}TP5%0e?tkr!>sWV)W>>1-I40(xZpjHU$iM3^&1Hw1L$_{Qe3<HFYTX8; zjtYp~^K<#7l$Xhbhxz*srft@@x!inrz16a%X|efH;Pu}P8@LF?>Z_3K3>Ba|wBMJq z=0R?7rNA7!$-BBi{?b3^NbYMyL&A`wA)<cJ{xjP5{>tF?qP>srxy*Zz)$-utKSy)b zv4gA+!(y_oeI+ezp{b!A%3>_8k?r?PJU%P+*$y>7T?`6-*#eo5E4^*{m?bceMn*8_ z?B6d8Mcrw!6pebQJtgJWGheZX{oK^lpI?nOsm9Mh{t4!AEMUTJ@sQx(wfrfqeJ#%{ zdtV!q+5%wfWte}^9Vg0Ju$VgU{Dk29uo_EcPN+t;<0WWxkDq~2a%V_RyOfc_GX;Xx zhC~2n>Sh%1aPFkgi~z<xe$#+n#U6pzdL{+*GSx0l&UDNw!~ACU204NCS8@JE>W%na zY6mt2wetgC7@;`Kx1UM^dT}OVX%l{US+%qp<cm#eUIign@{diALcl08F3^SdOo7qW z(GqxVDl(>=mJ4%mE|CNZK0wFS7#5T(-CSlbdO~lij}BVTS)c^OV0<R%8gnMmg*O9l zp#b=fkg5n_Wm=;e$JG!nVAr<T+om&54!Q@^rS9!(S3Dfb0hH7-en*pymZ;ni2r6M` zrrih*O1UAs$1WT6chp$Z^Pzm0*2VK;VKt(CmJ?Z$RuNQy<p3oI9Ru%=<(t1&C0soW zTvn2)av`)ci*{fO2!43N)Gi_Ol1t`sz(rQw#`^l8(etb<MPU~oAKksQUsI*<klb<b z6rP~7Lm<R*zVyc5tc+BVz0L!-bzzVPx2(@^0?#ULrWC#+{~H6m>eo0Cw5Gq?@|VzD zua5~B1S@ong}GYaZ{DTaC|p-FnVeu{E&EQvn)Bn-XbR2ZjMJ0FpT^l|_viF8*fV6$ zyw_$exJ?)mmhz%%?gKLvgTvOH{Ri&dh|TIJjdt?#t?0nGjUt~5raH?{SQXvX;?RnZ z73U@*SthMH1~I|5bY}q~9;RhbdwL->!AMjLvkX;yArU*;MVWE43V(wIZCT0%S+yGY zjEB0|5UW~b^l}Uayvq5`^2THnyhQ?9=5%6$k3}W|degNkVmbLk(uj&fxrin1Dfr(n zfHBF0P|*vmsaR%uF`l=&es?DtwMK{#edQ?;hU*rY{=Z?}h9se>lrZH$JZW!=R-#sF ztRtfsZ`-GxdMzM-TRu@oT}rFbYM4+~x!FO}OM0<`vc8Y>M#bOTyEA9l`FsPG{>u6e zC=1e1RJl#=$`rU+VC|erw_d^7T{iyAS+$|7Zre|O;(a8Fj&0mdo^z55m^ETY=~Q>$ z_f~Ei;$0lZcfq+nsN|Dk)FVr&f%;RnmPhNuN_~M3bMM00BFMnOx7C-qf|wRvG`ND4 z{{`<lvJFw-&gnH+tH-hmKHER~R3J!+ietn&I9`clEWFS5x|G+4jC@q+@84u%3DmP$ z6;tff-#tT_#KPdIrz9Isqn{c^5@eLdNLj|23K_dk&}L#Ah|1hGk#&{2HG6aGJAjH@ zHWSQ}Y_+snS?+Td&9)de+P}knxo?y+`~Dzb+oSKLclWF2{Y3^r_depI+?bKB+rD+F z=nm40iZ98nKb_PWc`+<L)%_oo<ylxJ0fUMJe(@BjcD@ANZm#v_Go`%KDAlO2)la{C z;(}SEBwk#88p*C{qX=&N{sVV(B&Gj4gTLtMQCub0jw~ZyA|ORD6(7=<3gW$a_&U?> zI`aAsxcSNB;FGTWUo(`Ge=uK!!_?Vp7QPj#f7$2nJ;&%<i~g0UW>?>rkAM}c+?Zfg zLwL|1Bo>Z6T*R))TE$!QYpnu*@r%xVE+-$k94m%wUE5RJwq}^my;XPm<DXJ%1^Xo^ zdWe4WvD@cR*T`b55zFYIp_o<MFeOKq5zvKzY;_p%+qw+0YFjK;u~LvRBvE~V>39p! zX9WD1qG%S#d#0G>C2#A3di%tp@mxUk&{^;Hrmuxn38ZA@lCt+ZWR4*tVA=sf4H=;b zeAE$D+rU?#0V8u^>CDHKtoR7nx-?s9TQp8R5_L~aV#pw=VC|h0Z%wLg2!%f29%NZB z7hH~!q`Ke2rJ;u4f!t8Q^@+me4NB8->MG`W$s(TjHuP}EQ>JaOc|W8Wq)Xys*Her; zaqgX>NG8e_#Ex+5ogAzCcoNjB4H2Xh=Mh~B7Dl{)nBN)p&T5%uD9~+5IFbGTyoQe` z+rgV=Mre%J_5;>3W3rbo@XEbp6rpt0JO?in<MuLH5b>LE{Q%;*2q4{+V5OiN_W|Ec zTp<_Y`I)ET{PUJ5{X*ir#|2_ZFV1~HoF00)W~-O5aAO#0_&YxJlF^IvEk~a)_qvzh z$;tF?=shGZbriMs;=Raz)yPi+o`=&N@ox#RKfcsd)E_pQ=f2czsh_4rh`$0r+I4T} zjvk#GwY2{OAadX1DNtW9g3Bh`g<cRM!$z6_2_3;Tm*6A3BoQD;9|bKpyLJ#ALa{fJ z!bngR;w6vBrC^~?;<eHZ5<)aWJbA1PgB~CbA;lEt^qeoyLbiMe<z6YsKE)uzY;!UG zS%v~JT6_mZa{87*P@bq~@9F=EOd|1#NKg<7!Qrz<sxOGQ@xNJxq&fsM7CauwJ&`36 zo~c_L`ZuYS#}HqloXBAuqYeMLk;V60&lzludQ+wgIw{Ihx@Zx%d!Z0Las|PTPqhGq zbZ-QtnU03n>P3qfA`+rWK1X+?)l%T)*?WEUx{I8RTxSGFmn7iEpQjn5Y#T-TdI6$F z?9kOCE8Y2+lefgGMkt6?<$IAM9t%q#ScPd3@vk_Qo>1`wlVcgL9ueP^rsCIH31UCD zA*tjsbcTGY30}8|H3bPshxdxSzlMwuujaoO6z0Ph6kKV$jMXW`D|2`1qcJ=J%}Dd; zK#w@we0z<F!VS`MK2uDreM&NXd#KPWZqq0zYE!30|BSE0`b>u0N&rKz{uo1Foq9?! zH-eV!o(}whhD1_e8E6EFjo!D7f-Zk1mKkN9QaFvA$aaf<MNni8pFrk@Ph@Xdi1#tB z60hp72r^^5Lr|~3v??OxVFBsSYr{<=;?S4QJCnGf(0Kvo+X7xQ7KlqxKd%=^D#q#M zJ;7vVk$!Qd2o?W^W2E8f$ghoCjhn)V{<qO29W;cRR1EPQ{T8^We+K)JQkuPbFdAkS zaT-kB@jEN(=USlwqF<Df1gK(sS^&sJuVW=_%+_56te&)d>|Of1@(lM|m_wzLZ)t(O z$XCgKJOL8f%f!%Ft3TY8-8+Jd_45%X&ICli`igH!<Ze^yf0Lp&`kSiblf@nVH#2S> zX`)IMRi6r`nXvn6&pD)2xEb&gf4A^WT2f?f<R<L9&tH>ETVA)4pw|21g=Mh;Vh<SH zUv5hqb6r2l9+Z3*qM`D`crVn5w6N<z@tD(luh0aGOEvyfY_b_VRJye>W@`3{MPz~r zQsH+?`XI6BaeNuT>=L$py7A`IXR2F+b;1xa)tewwPO4^xxIyaM7>BK0f%4O;FMNN_ zIx9Y61%Ki1s^W#@!r2OwUzqhR1zit)xL&YYRg_bm1x&;5Fu`uI7oQDY$Kqdc(d5A6 zOzINthiblT@TYUln*W14&j4az5=tW@|F*hJ4J0zl1TS5;F>J-9g(F)pUczO3Tfv{o zyF_gYPRmt&gM$rB;r8{d{;w01Q=<?6J9W>a8Z#XReIKt{-Slo1EXVkq1pi)|4!iH& zsdyU336HC@Ze3>=M=HtzdqdnS-+C-q9R-;lsTRQEKBymBd5(qf93(s{5BY~UGkm!p zOanvRX|$ei^<TInck{G5MtE+wr)SE4w$QJ@v&`!556?KK(=cMzf{o*ESBN}7!-uo% ziXM3dx4Dy%jZ?vL#Ym(t`?Qd-QMIMgXV9M^jSE4G4al}u*C%#u>Rip7huS&Jv%X_K z|2DSLe(o)3of$q7y-u}g?2N^jmW4S!Zr+7xgQa!FSSA398}ay_UC%9?6_$+5leiww z>y!CI*nFZo|0c^-C3Z`BsKm>$`ATAMLC6$deK{akFun93r;Z`yVeu^Bv?FQiqFt(d z`KgiAhfc;2pN!&hlghU=)tC+&kdu0e)r&Mf9`jF&9uO<x^1P?jcUY7vJ$5Nu2U;%x z^37+%rDuM_*<|))PH}M;Iu(~XKLEyJM%mDMKttP%f{Yq04<?wK+p+uFMrtm->kD7M z0WG8TnZV-KgJ!?4uL+`*5=g$o59&2WgMQW^lbLJ#*^6S{{D}{EYfM{a#oE3&fxi*R z5;oZWj(ET@!5H&7N7OYs7F?kR(pGA3p0Z$R)q-ts0<^1ic=198N&>%d+UV2A0DASA z68sYJ;6MOX;Tmu9_>ye%iT(5$9YU}LZ5e#+?SZhLL3WUH6$>8=e*o#fra3w#uFj>( zZ7B{5fCH)ve|ad5m2l1ni^a{I1Om-_1JZS`SvItlj6^Saky~3G>ic3R?4NvAUfAF_ z!D+P-CsMsB5Fk-;T0J9LMLx(BIas&l<B1bd^_nx<Xava+Rj<mA4hVqT>IM1^QS~~s z&e5&vpm`Cf_t98#EA3jFdXIG@yb)1(iJG4^xz740Y)Vk4Z7CN_doy50PJyh5D58Oa zr(vclsWQHHRc7kQ?SFC|0$$B+LAcVc$1B-91i^2T_ht8EO*gw<l<EYP+%vM6oeJr# zVVp%Vq3>oFIC2PP;WFu=Ki@Rp69l)4o7B7+OyDIr5f$yK5pgw;g~Ew(RkJ1bq!^+{ z(Is&C0<HPWsMpc~L=G0e)JA`sX0gRs)$myfpKGEeZCj5X&pBM)+)?KzmWll0>ID)l zD_(0a@<jd@ZEyPzb@Gu>(Rgxz$fX8afXFE$a%#R_b6fEd8SX>|8Ie(#am^iOwGDW+ zmP$0PU1=CWT#A13h2WfV@n2x0#kXt0IgO3Kz(s{==ULOT)UJk@FZ*jg`Kr0NEnm%Q z->dX%ez1!%_uS}KTZk~G=21t+i7(Mcb?FDG*kaggcyXkNVhEh*Hq5c5?8<@3)Wyi{ zH(hQ@5a~9k6>1K}VxeBPsa$QvLh}gR+7)AEp1FU5*d$OQgnE6fxDQExSZlCjMv$de zWf^SG3<L=akgc{hs~Fgk1ae3yJTd<%J!Zt;(j}AokXd(#Al2(iF@k#N(#%~pIx+-Q zFWXXXvC@GX;wO8xt9?g2*USHQ#S(!cH0s3o45)9h_~N+=WoxzQ+{C^Rj-mGwvK1Oh z6ah%SOGYj#=KO;rqCtiH5#bFH<3%)>kP8w-vjM#$0$^REMUFoyU{1s?<XC|Cv<}_a zBLW%uh(FpE691Q=-aA?SWHBY<D<Nd4uy(n&BF~G5h(flokqw$D0gp{L2`$8TspL_` z*AZRYV3`sicDYHAv~?-tK74#Zau;g3r(;jXsE_wy7e2K4VXd!-8S(bKk7$$zK)Al* zC_K6I3|g%067vm+ok#h*m`^2n2@`LrYe@a(;@6acBl<-cqPZZ=j0JivpHEC6CHYAW z`%1d_X2y&FnaQ!zB%BPz9OLQ>3&8WW6{5-ZOu6Rou-;2Ou_2B(t`VH;K|*ON;a)Z% zXAq#Nn2UpFV)$^?(<}%ig(na0j;%{3|6x}7UvU03t!*n;W<&8qp-WBtxd1qk!%z() zK?KK+Kk}17wcP@c5{|?MEX5;&wlsiMrNZm1E0u^0L~LEm1rBT1|AHS80kw>U-C3Cf z<ILFb6JlFa6nZLRE_0?@#I^d8e0njyx>sl5x5GMUaU#CeU37341TTWVn9`34hNaau zU@Q1=a)^*#5%T>Q@{SZDES&qHPGSB$oCwB^y&+WO@xcq2z4KQcgE`ZusolGKL4UP? z-D81$j+YqjTJ3Q7{h+2CV1(~9-h9ofQI%gjpf8veb2fvF>GqYyU+veb0Ak*D-4PEM zIu0uU;{bgQ(Eb^<IR}u=KX_RMuK!xEt(;a0=w>{{cy7EKb~KQlY*8jvvGJ_VF!lT1 zn}5WthL`-a?$R@;S5iifuLf;zs~m>NxHR9YXB#u!FZmPonEAXrc$zWZ|J$2A#Q}Xd zHA@@9&(uaHT+m~3%}+s9FH)>L>^977WI-Qp(0(rQ@?5Z1bF`~Fs$BhUK~0Ji^et_F zSp1LxxG3*z#Vh)HmAh^ht_CU{fLD$&K6eZ3tGC+J$*Y=Wi#VV5F6uUG#N8418Y=8| zq;7KYs$*-afLTXS$>Dvi(Ml~iL#v$(t~weAVsG}N-;{UQbqeau7%^YUI4T@e0vMc# zk%?LG^3B(|+BebD%@$<d!-BIed55nmEtL;f`>#_gx)K+wM8?dfSoZT4K3v=7kN%hi z*3mc{5AxV?OcxFF%_`+#80U*-$!ufPEf1UfuNOp3fp(oDI__LbIUt>C>~j{|=7Qp3 ziP=G)X^==}0L1HOkoe$lP^>?fQ|SHu$lwoP{@J>`-S~xdRTj4?bo|wJ()sjtrcOJ> z^WZ;iwWd`7JL|u1z;~L;w_Hl_eGgZUX1VUa3~ww~%xq4!Vp35A=dy{v+)CMh{Cn_= zMN?3mL;U^yX?au5IzxAp{bXytWQ#%XCa#El4E=odHn7rlY37V>e>fl$ZFlp;=Zl5U zmwmo!e7zjrj3sU}<P#$daS1RtFlypLA<HY;_s_=-VTYfd^-%?)PJHFAvnwVBRc*TR zTjt&0uZ{04Y1~9GI-Mi+nm(@-3evEe&%ZwK5;3Yf0{)C<3Alat115ISIB2D%Z)QcV z%g!2H{kYHq97z1zsA>UquC;yCmMgtZ6=&pmWwR0yhaU)%g%?sr{ZSsf73NjI@~n|7 z*p4r9P)U~7T@}@7f6!AF;<<LNUkVK9I9aWi2>|Y8^9*3Jm#S&buD@OPRO-|;Jj~GZ zMdE?gk~K$ak8h|P^fUbrJ@%w1;BOM!20H@9vKOm103d>wnN9kQ)~X)TU9sDto#y@3 zeA?-YlX^fv*Zw&E6PM8zM7z)uU~;d)wQ4TlxNrrRUwO+`6C1KKusqud4H=t0Jw8_7 zrlwGvY_{UUzU}|?Wo#soECYWKxE6^GLFWT&L;X>i>)yQG56))lrm3;3GAkkLQ7n#O z%V7<367`EvUm;Ao0$~Sp!sk-a|K1+Ctq1o&9h{k$daO!TFMOGMv}`PMt`+Cm-xxn2 zk(tRQN-?xcX|3t_y7%KCpdjGbsd!*6r_BxT10kFQx&y%VlM~ys!_2V2wl7!*2m#?K zg+j%wdgQ!s)LLd*KHcYs*+fG3P@2g<5(Kvof}w{Crz3sXy`?S74b93!<E4RH3J-y2 z`oE)WFbCs%1)j%UYWt_qK6wrxx*gM>mU>>W{C@tc&sJx$%?>oA_m510n{nX^*h+m~ zKuNe`CKo`suP7$zJRKJmc<%-mchx)_m=KZ{&UtZ7qOz@~0QWl_{kZT5FmAJNNDEt3 zo1tsE&T!BSE10&VTwQS~a3uz_GBB#H$(<S4n{o`VDJ1<zk;mSA-d*ZR^fQn2?8AGq zRN)QGcXv+c+BeoSdXtkpX%<hn=E~fv{!uXYA{(nPa>qC6b*X@rbBknQiLjgCWY3Yp z5WJ~6)q8gEN$*yCiS;o?5*8Q&MTXY3AMcoOm3iK14qA?YXuE=f34f8-O%l217!}&2 zCehcG=D0Z{YGLuPEBXP$t|t2++ue0jcby|xtHb`+R9f{aE)_?+fOBLr(7LsJ=~p+^ zLW<fm&+zq_EN#yNyq3bv<mQ2df`4KA2|_9EWu!C#eYLs-`fxefFjLi>UR&v|MqOwI zM)&u2q`>_1(A)UbyZgzBu`XE0bwM&@C3WSEUBB4C3t(~>LKoi=rx1?)ECGL1N=Rb~ zD=$+3gN_YKf6=Zkd0$nJ&@zuzv8`aIyd9*BLU&PG3ZWI1)q7LNrLXaz>&A)UWXmIY zzxu#tj<<dep@$<j?j4D{ecMKD8w(^P_RxZ~%(?P&Ba$+ZjGUJ_N)DJu+jWfFh(S`v z(}uSi%`0k7>m_q3pI|39utty6UuLU0Y;a`EW!$6Pk$3v{6g*(3eDwf6rhK?{LB*uk zhC|PA<*xp`IrVEVTpeY#c-3~fy|cOmw+ue(zq<Z6qQl^%aL+?2bg%Lo$6#XNy?CKv zkk6B`OfKlzh)RZj9%oIbYW-%iIgJ4Zx-5wwx^{83IqAIm?K5}YIcUilD$(FHNUL7` z6MwL?oE*dcL49k!oM|6mV-5{`idt)Bu|%h35VlWf+qYn|-tdkF)eSSk@m2M&`Y>;k zhN^&l5+)IkV`;xNlc?2m!Aez?YFLNJtUvtW%_8U}!P%D(F9ZIN-`<Y>>0o|U3EXu8 zJ7`4C-GtjUTTq5x?uNq~kT>9aDt{Bs0r*GZp(_WI#dvt-3h)w<CV)3tZMj@a$p`Bn zcLlZUV5KNHHBtI*1YWG<!TvC0Jy9Sunq%*LZ{b=zII!B1!GCVzs-Miw7w~wob|ZIP zD|x2z^Ny>0ftoa*0@(I26&|j1Fg$>}2u>vv!~Q&)wGk{UWXJDbMAum_ie{OCToHi^ zCI52f#5YDgVX%wNkOAoikF_=FnlrM%3xZsesReSQu9+homq{bx_5mQxHn%TbBeuK% z^Myggh#?=YE!GEg4hF+N6kgj-Rx-0Ky5Yl?nt+z-oDM(?2!{!G4H*q{d;huu2n4#2 zx3%t!|1u^f8a-fGaa{amo-u~o&I}$=#~fuZrDB5<vMyGHpyCL^%+~utslYtW#>*{e zqz(8HA^V1}>b$QRFYh@Ai8P(5|FAa#ERb`9>D3rj4PRPz;iZ~Hj;`qS0+YH_H%B(( z{0EU?^Q{|9h1&K5F!i^m{huIH*JTBvzrUo@)G+ym4Co}IfoIDuBlkR@u74xJ9WU=s zpQjIjX{>#rp}Bu;C_rLr@-%E9F<ccu4{4Ab9)hiJ<U2Sf$OiN*`VI0`DK`V~XKb*G za514@>j;+U0yepwQ@LED{8!Z+_|`TpbE{}qy5UqK0qc<6KZBh>R(k*QKgkDVPFwk| zX86s`>q1rac|YaWgN$lC(5(p$xUP~QQNJVy)vgU$8uHHxbX^&&a~*bI)d1eeT?w0$ z{EP5w%|PpU?{D=|`{eD-qXV8)umkKbr!Fiy&&0hwI2(-f2^x!Sb#TJo75k^#qFQ@g zatM&{oOnF`Te-((b+1&`{+sl2v)>qWEWDr{GpIJ0;c-Z~`k4h0DMW^dRV-Eqg?FxD z<%YPj0?N$U)vnftd`X&fe@j$@0#~5j3x$UvEwnX~_y~W^ZSaL3tSK{GXMlj)J%^2k zmhO)^vB04&KtL{GE%aFpyjAVw=IqZciclL?^o5G2b*T5z$1#d<vIE+L)wZ*@Jzpf8 zI>HG#-;B*QuM(>WkN1}L$U4OiX?CAhTQ4%-oKdL+APL`&TTE275}A(KSi+TRpr}|0 zL)e?8<91k>^rI!{2OxfUeu-Bd;PsTn`>>*pjPr<jexqcs+Or9~bRKDg&FRDpVax~f zv)1bIj4N@pnMU{~@X++i$VeHAP4@A$@0)RjV{0m0eNTm{_dsQZrus-uu3w0bvN&mO z(?7~n({d4`P!4uST1EBW$I(tu8$!<RKI23B<Dk`s>meAWesUn-9G~>G>)TkTN>s@x z%`()*;*tT|tLIV%MQ;|{l!=adfV@JRk8`Vrl|2(Z3&A*qX;eH%Ak7=6n_5DyNOYLu z<Bku(rz0zdXHnVzvaI)Y0ii{K2bnzZbhkZ~^|?Iy{?mAS1+cj)eyPHGmMP5Ue50%| zDE#!G?|A$7Kbe}s=V4>z5^(fImJYk=uMtAtRf;`4Wo1d{jSMue>G;Y$5@tj7PtE|( zy@|F=cDF1Ha<QKbG&lU$D%jUP7Y!1ei;uG<2pq2}h3$==k7=pZ2mbj{R>5)!x^8yj z-~ujmZnr1Z7k_YqqNjfsc7nTJUN6MAd&H~P9;aieo<&yy^5)8_gHL(`Fco5f1xKB# zG9EhvgKy^=SY^Y5+N&zSiTkHV4Y|x0YE6~3ZrVe&-3m#lH}oi%)s=$Am4Ywv2J4d= z#v$E`f2U%E3&gV4nYz;)d-Uf-({?-BW%8l4=i{`Foaz(*`V^9auOG=6b*W@I`BI&K zu=(5ZR@Bi(%v`d@jLo9nOrqWqukxmTwov+bR_>q5n&39aPRR~QBvW{<`e&$n<@(f> zS$~;z#5$u@Ihr(67<w2e1m%fgg3|zmKpbIyAddEZoU$$8({ZW)$>!N{kHrEBrA*R? zeqBx=6>I|7F)z3ujmXg@RJUx9O%Anek&WLkI*lvN#+nkntAQDB+k;5TrZ@;|vXdw# z!>-ui{HeWoPp=%*j%8j5YJO;U1^Alxe*mFCUcW6#e(ZgYdrka6#BcbJ(<Lu_%>8dm z-ox>ppFiRFw@3C7p97o*@AwpHWw&Pza{n2#4sjgQvzp_t>o_$gJb#$GFEbtC?k|OW z#_79<IAv82e9ry982Sar_2)P(8My8!$E(ME$^E<j%xTGmr;c&Hzl}c5@x=3-R{r+L z3GRMvz*jup>?52SM>$S%`bfYj9)9{OoR%ebui@^|Hco47J2@@++2=Ipw>y>7vTvW} z)Ht(FxJUN?n)AQ2nA4iGm7E&B@IJ%ihbMAcS+k1MG~Jh+);!Xwmh`}>vHn?3+0pMf zHS~%2hWlU1;nZk+iPJLO51iJF>iex+j#RJXKNl}32^$h!HA}a4mpwcB>5ob`-2Ty> z(NFZ-m)cu5JvuTa;FkHj)1y}{uv=c5VTwL`|7)+#?mjVk)BO26-dTEk^oi$geZ1?S z{OEbg;kc{b*63d^KQZpqfSl<1jz3+l?U@^WYll;DRS)<_N5;(EI`wiw^xA=M{MoN8 zBYLh+PU-XCnut$Mbn=vEmOe5qC;HDFugs|$YKqQ0pm-nt%p5(c^P$(?`7SRyvu)zq z<pp`sL;8Juc8q08^s(FP0)PG55dCYn9$RbP93K7g*zj*WU(Ao*IqHL#gPy!I`ldOF z9(8x*L?6EOwbRpf<wd7I_4#ZcuVK+&CA{@}+s-qh$K8IV`@Wf3(MfR+$LrVK7Cmp~ z*jLAWn-|^bf#2@x@Q*v9)7ETxAbWOnbmos+&K0u!=qK0yu{gkcV087)OS@ywr$yg4 z<Mj*8UYQx4J@fSu-(E_Ler`_4AF*r3Mn66+(Riw3ZuFh$$9yYR8>26rx%zlQA8YiW zM>e1R>~eba&KdixduC-u-*#cdL1Te6ddSJequ$#&D|%$n`H)waq(?{nVr$oF$!*aq zw}tKWpOg{3>U`V7r=QA;9`(WI_7PJvq7(FSzr6HyYV`a)RWrVMD2enC8U0=l-P_Sy z@}k#nd1}k;Z<?aJoZ9h1+x+<G^o}u4{QSB(dP>T#w?^koj}Cb8^ts+~$<f1$2md(q zcWZQR{;dAkf1m{++Ct(!hq#73mcP^DJb{7Xeh)UjE2x3Lc^mi}Zz1iEioUmiTX%R1 zRd+35V@(cIuEA`x;gKa)L$0aNnvrd`-D2o&=YecA!#vhx$u(yh9QlU)0<+a%E;45n z@*{1C&lD>^-v<1%EY?he$!ahaK)_6hgS(+v=9>Fz!toPOvTXTz5JRx(+L>_+QT#e! z44cl^gylN*`22K321LPKAPreu7}eck&C0js39mVYW?M0yXGCGN{&us&;o{BmOgRYc zj!ekC#R^%sh?9CKfYU#}(9u``x}4!amp2GE1xmvNI>lXh4SYur1E2LaGqDG(L9@fI zfzQE<{c#Pf2b5ke;2u<xxz6>De5=EjpKHiASu=CZw(IigEWcT1hk+lNlaZfkHsog+ z3gq77c=ctt^Qd#bl72jW8_;wvML$0CKUei1Q&eEia0t$#<biKq9!Oz;(nkqEuRZ!g zed&RB_H1*y2^@1RRx{MGTzivz=4D$P;`kVYDa!#l>d~*zZtItBvGy}t=k}11HTBL} zFEKR;XtCv6@iZhE-kFZvRTR&@$}fZ%edZMAJIws}Aa^#62|fI}wl$Nl`Pf*rx>k)> zs!KA0|2&H$8|#wYVY0b6|Gi{g%8}=n!Ze~knLp94U3p!n?@TkV3a()cg?1EeO6u4? zQH_^RkFGuNG$Eb~tVNm>t`Fb22jnp~H-8>7#@LOCb$z_1b<#xrv&|~R0=FB&&2yor zvSb;|c?FK*o`0QQI#e~POeWkj*Mz5QQDx>z$usBWi!Gj57ixW7)OKiS#7$gZhy1`h zQG5mAc3TN>7o~Ia!+AWkQJDdLGc<l^+x09s6@Xtsl>7O;Q40I_Tm@pCTYFVjH@^SB zdi}fP`ER<)?`}L_|LT>TGCF1CH|rkC8M`%ceTNRI_E!&ng#2S~nK8v~w%KP)g+^FB zBgvMJr)Ak^*e!T`)=c@Foe`5famb(<$>v<M$!?zE7&54j$(Gl*AXBdE9%fEkSG4&a z7n_=Z#&ddj@9XO@7vXHQJ`{e>DFS!P<5SZ9yA{6IhHxvw4c=HKRGq$-OnBmV<K`Yv zmfSA5Z)9Xf7W5%i62~T|cAFj>gJBM8+|co;yBn|4HEwS4JZ_!F4IM9P+|cg49^8VR zZrl{z|FBy)o<^{nTM|#ha9H8CgyhD}sv_OPO~-&y?rxl)(akND^NVwL<KdFPZ40Qd zuMKs_c>4|Zkht6Y`SUZ=ab7=ol1{|6IWoG5>@tI!+ijd*xT{;L2p2g?C-TAFL^vac zgT~*tNTeG#L&xnnsSLRZVga}XJKeZhQYpB(lr=ZK!E@@THdEb%xm*FH38TEelr8`H zz`p~Y(N3d*GT@Er7f^sLI+lcdZFXBmw@7Y+ne>{DRZUjiq*>(OzS^*~E~girIdkUV z;)vQs_}`f`omg1}x*hVYLPtR$NC?}~{xr}BYb#5X2W!t1yrZz~4Gj%0?nYPlp)At> zbk%)Ty8DG&6}Y_&#q_mqROJ%ndQ<J0?*X4SRyy_wc%~UM>b;ne)z)&GWBuuSYgr#= z^bZHO(~FKMC`UF+@}GhD-h+#~fkW^J(YIF)KzljS_K+Gt^O1v`*cW)Co&F+-Gl&%+ zZyp!>iz;Z5_d%&dL0F`IE@3;seLJ3h?U^2M--X|IsqGB+ZRox)OVV>0@9$D8c`h(A zwgP^cz!T{x2#<DUm<?~9&l0Aq)#042zLuATF|s8ydVK9W{uBM42T&!wYh7;t${%@p zlAHm=b12b3qH#ofGGi3T@9|0VJxllKs~)KG9OYf>2GG_gGWhhK(ml<v43~&9EQc}- zgS`52{X6p-H5&2>zuXT*@2=<h6Z)G9<<*Uc7o{xAtJ_4-Is6`?IM2fU^h75Ho%*+E zBwp$S(CY+exH7ok?t=TN@tpO8+XN;9fB3yf;UJ9Omt^eJNS>-v7(0^U%u_F@hrwLG zLY`8HKa9$MJl6Gt?sa`wBW_=TY-PYp>pr}Zu<0x*811r8ixPMc>%MGT8Q_=FgkNi3 zx1ISoI)$-X_|;H8d_eAIly7a=6%g(zm#_mGk*gZ=fcE9uwhi^dzhxtOM`ORBjZQsw z)}&egEMudeW6bkI;U5ir264SsxwY?Z|6uF`_=Qur8n~}_!T0j|dc8ipm_d&nM+ETY z<#+EPwLW3Yr?x=-+2WLMGcyJU@wQw|Z7TxG*XTcz%Ov6^10LssJK|B%)ISXdzex0Z zP~ccdH=2ia*URck$n!2p7v#_0g*N=|dNG239R5C3_2z&2{wg;f2R~%2<3~_#6h>Q~ zWgkO*|Jcbx>t6?cmtFi@Fn={4YWeJk%pDN-NSkB743%drD+oS)Z?*R!uA^|3XY=F0 z4}M#TXJQlKJkLNL&p5*cVE!~|uRRBky>rgCJs88>>#l>EUl=R;71}EC>95J-cJPb) z%~_|1A&+J5JnjSkLG_>u;t|Z#ahGMr@9=E@-I>lIDCgBK^&R>G{~<2r+!xwe1oaPX zG%|(5|8e*uUV$2arofm?ar$@E_$PtiLoWVd8vhdbzsiNb9b_BB>!gsaA3X<dCK^F> zFwp_LZxqjx<<K7Z^Y8vWRDx&Dj9_U{R#opKuY7EGhpumT_ozI&)MoSWhdeO#hk;)S zm&Mt>GQmweOAo1O9D_a%zau++==x{SC_c8(-7C{O3U1?h8V9*d&o&{`0BDPS8flA{ z2P-Uf2<ZGHp)VL;`1j&6ih6P{?+4w}o7zZST+mNpYa^X{{}A%Eou}u_>ppO+6wfs^ zuR|d{Z<jvr77Yd+8%|gI%BS%%DaTrnqXRs<+L4~szGD*P2Yyi>HQlx{lNDA1zv&ca z9q7Be==a>(D)zlErYda46onO0*b8varaEzurv7aOz6X<?dg!C^pFT}tN8l&>{HFSn z6DBB=bVcnW_T4}agOsNI5}T+G35|?VXWk%<Ay9vsH}Z^2ngzOmU)e{p|G=@Kc*d@W zvRvWv+<6Pi|3yvt&jz=#+|5}}pzrYacd4thJd+H#|6NnE{lSc_{h9cd3@uN7g5Pi0 zm53^I=JXBn<)tJU@Ef0|{G%X0fz%)Lg8GKv2jD&W*Ma~0EXlCt2JvkyQtui*5yIno z+vyWNF0K2F+P>RkeQ~MJN8g6`Y4Bqo3yR}&J?Xa@jinWMX#jWsX88V~T)<Zd2JaI7 z@T#c5J4fxPTJh{c_Y9(!iQ+q(y!=f%{=ObQ)9IE3Ze`U?&s@Y&*xQKOh(1KLoah@w z4-l;(dXZ@JG(o!%9Y8dW=xs!26J1Pn1<^91+llTYdW`4=qQ4VeZWi=zq6I`ViKY^r zVG?vM(T9j`BKi)|YN8j3dZr5+K(q_dzC?!;9ZxisXfDzFh;AbK7SVk~j}omVT2Hij zhM*xtBZ-b8no2aA=zO9}i9Swr6VXqIeonNO=ubrdB-%1lPy^9OqN9i=5=|#+BYGdv z<wVyJeV*t$L_a5bk*F_~Pe-DWM8}$)br-KtDv!QIN1NRJ59-F59Vs}OTv!lq&B~v~ z&U&Vp9kKaVdw#At)?v$y6Du36uNSz-JC}6E=ckLK=vj>CWL*7e)bA*8!-c3MlOuZ? z>w`Q5LzmEueW{GYH2IWog2if{#;S!}nIbTLw;5x%TV`41*<|_7B$xxkPR!3N%;jga z;o)Y}82c4nQ_Xn=B579VF|jb$VHs8IFsJ5Evt*iMvrRU(SsBa4m<UWwvoM|s<0Y8v zjxo462Q|%Wn#tmTP$?GctX#85bW9Oj!eVpt?Pd~i0_My`ULVA<g*Kbn>KJXYL6m%3 z@ihE?Jr-Bf%&Pb?c*cb5QM7dwf-uCHgc4!SQi}`BYy={V5SyQAR?n}UFeW*1Ov1nc zeKT`&@hO0r28E_`WwJ*&PB!P|&*dsgDKKYDW0RB=hYkM4Lx_~PglREJ@jUuyQJzU= z8!sYjhB-FB(CUCX5Q1q_t|;mFOcv_mnwpL4i{Qx4@)9hzJIr~hmOOKeo#z1VSh|Pv z6uXoJi`@|iQJ57qHpMCy(lW;sWta=FazHFhxyV_@Ld76+wP&&;cZzkM#R|b$e~j-^ zZci!BGx(lXT+i9tptzo{w_cHF@(onRU@kQW>oNAGM}pZjS9{<dV>3N&bQ)iPGTNML zc3c<jR?lRU#cozJ2z|O|GALjyUyTiDHVwk_LSpT&*EV~|ORj7=%l;(=#ZR!L+wdTa zd0I`4DFR)+tfPbdVlp#rJl0W7n~PyxQG=LrQoaQhfd>a(6QOu-08Bx`SzKCj0?wm7 zp!f=eqT~2IS0=l&Y3;;VGgAsIR$i;v(_X1+IhEnPT231gspYf*5oi6bM?$_Slk&@R z_q6*tjNtNuhsz0wfWHv^2_kGo;4)7c3l)dAVYZOpkG0tBJpY{Ma_*LBF5*tp*!>>y zc96u9$=e<D6>KZ&CMGtCu{Fqj(wK>Ozng0;3Hvv8%}aJPsrhQ}!=iW#cCL{Mv9P7T z$V;*@uOKWN7mukXn*|rmrm?L$=aG%5ea1FIjV`ojqu8Sgv$D)KI~s2}%W+bnqoB}% zvL2wmM&xj!>}OJ-$e{2i^3p^A{R_WOHj9Gt7t&--$`ZA<wWzhuO7U;M25dUj=ZRj7 z?Z3lNopq#vUpPub+Eg6H@b8gDXZZIlzad}UUxxY%9?W@8_y0@RU!7m3(_9{O{j=#b z&?RvR^Y!(0@qg`#SeMn-yDgW6yQ~+k_2ZwZD|*L`*G>P^{@N?4xbg6jztQ>Q^>lvu zpMSBQ-NaR96a4*2uGqhd@L#_Co4ka-p~>stF53T=tLcM1g@4lrPyT1+{&!#hzt;=R z^?!LaQFNYekH}}?Ns+d=cyVXK&(lU?8)?I<MH_1VL~6Xdgwt{ezw4P}&lsBEzv!Bv zMxxvPX80JQZ$S7@H`Q(mVc%@3+jM)A;f;51<ad+3;CJlKV+J<!*z9B1n+Gg9eDq|^ z6~_ACEn?PDzukQ4e-FKft_#H1f9bzQ`_k_kJzuZU^SBT<2Abg2jQ!8`|K|n0?7>Mo z77X+R&^VymlXNT-=m$UxfbIkO0niIT4Xqgqo~&afKn+L(7z+nl12hsS8UaQDtwcIa z#}>4Kwgfb-En{&&_3dD61GEOHC(aoMg9tz)ff|4&0gVJ&0yGM!Aq3*1jZmntCn^ZV zy^PK%2Z5%dykWw<EKImF17o>>vw_|R^q>LvLd()khYo16&g@a%m9Z9z*<*V*3}3^7 zyCXFzCEd}sIbAsl^oo+DJJ^G<ry&0Ja2RW0y1j7gP_wSA4`Y>p)B58cR3)s4(;Bvd za5>Og^dIoQ+WQi)s;agBwGWzDme_D3tLt$_O*xhWj)0&7Nus7<)&*1$ayp6wmcwzl zh2=H8w_BK+hxssLIbfQiSz&2nHjr83lxUe$4ph|o|9)$qjYq<%+xPsR?|bg5<;&iC zy=%SidWZF{wf0$Czn}0R&n~JE$$;l@-=~{?KMZg@`m>~)PKUX>5LR9T`T!>N;JYVL zT4@iS{TZcs^yHbFQCfIUy}pE=JQH%P$>GC2#|q)qi)V^fh!ViLzz^!pGdYfHMNY_l zfD}_)1OB3>A5Uxa;~8(j158BuN<W=09@I(b1K0#|<jZGwX~#9|0G<&SrBx2l>9GW` zzbZsx0MBNKHdO=8K>36~w5N^LDh9$YYp2}ogN1kk_e+C$MrVvEIYg(22V@??{Yt<S zxX%L))7jo5LYzT;K|^`wXoV;TY^hb6JcjYN@W-`cz&nSdo#8x_CrT?H&a?W)YSt0Z zFWj#jK{}6U)<>ZSxVDZ2t0N#sz&inXaM~f<PsHT98qjkz&t5#Hl{jJLlYD<6#+39o zo}qct6cnNJSL}pdkvvNdasgZo{NgAfN&!p8@yyKeChK?|nlYQ7i~D{PARp6M%@zl} z15BPML<c~}6rO>Gew?cJt79tnI-@kJmHHZ`RReC&Dn#*g9r8uV&450$bXYwbe4ECZ ztk0l70gLBDPtl%b5##_q6aG#<j%ns+by)f=KVf!Mt4!3P`wKjiH%hB`f$fenIhK*m z7*pkP=!fZ~$+CicjMXYv@a)iIn&Y2BoCmc3i~Uj|!v77uL3_5BF}|a{pjTiY+SjIp zWS-qS-eg(HdZ8CiSn?|Ph8!zbp?)pS&1((i8QH+@gr2WKukbu_tqvV_A(o+DuN2zr zSj{Ju^&iu$X*^2|_LNRlg?<38N4dxho>5$(d1O+5;0Ln!?vY8o-?D-7b?A0d4cO#S zlkmt@_HK#=Y=i5vT%NHBJIq79@DGvs@GrQ}O}Pj79I!F$Kl}~aEA;yf`V;iiMxN~v zrP%>jYR64J#XQ>-_O_Y!2zhU&e8*}YZwe8MdVIDBkpO7hN_#k_g}tTw9fuS0V7gG+ z`8Mi5`hT0=Uf6cFgMQr3>*Jca1b!LyL;|J*Chid8C0_4@TuARuo)LFct9o1SC(ACL zVOb%9-l6?gh$6smQ7-IVzV8vK+1}-ugi)ILJ-r`nfF2`Zw}2Kvk5VDtqF>w%KScR` zp!|qt|A6&JnyNqG9yR>Q9^HTV?a}#2+N1lcqCGkv9(#3vYu&5+pX9wdJr#gqXvgzI zr+fit;JTXEZYSLmJ`&>8O()$-KiBQv>mc93gCBLm1i-l{pY#Rwb(|^kkPzR)|Ac*o z_D!eUtZ;2dKzG<t^cTt{9)o^2IMu+XQixy0gqwU$>3rEvqhHDQY0?FHzoA}1f4<S% zHGfNa9@k2~)%`*E8SnwWYCB86Hdd=T%d-!ow4f^5OPrhE_bfM7EBc=D8>^Xr&|%UK zwEtt8=a207<C^0~y}bP#?ctafexCKAUBD{PlY9aGYZT<m_iO;es!30zsi>OzJywhT ziRIxBf8yCeQJUY+LWHASDPTkR@3LReKe+akwCiK0a!GkbYVJZ44%Q3k&T*OVz2MrS zX`&L)4!B!Xm|{&D&vVg|0neb_L^n<GRSmcd<tiI!Vl~S*)ZovLX^A&!N{`9`IgWWW z(nJdG7d6sUyA@98;jW39+A&j7V@-IWed{fn(wh<|^k|}qX>RD>rW*X!2~)U-CdQ*& z((M}jV2oSPotn5`o8T6EmnOn--|`ntBmjEdt%<It@uu*5G_eHNvG;09E+v5HL3i<e znv$pI{k&fxtbkvlUt<5N=r@J6&|rTtrV7Bfz^~m5{!mY8D@`m#e}=U|zu~&7ttQs9 zJumbdU~vac9O89nO*9FIoVsYD8rR|8nwX1g(N%*#jxsp_{|-7V4{9P7_x-woe!!yc zny>&S^w5-^R06gHeeOLq;fedny)=w>$4rTRG|^UzHWl^NlpnG6(}bPR5$&%BEDZoX ztS?X#<$#WX8uS_cJqY~{f9f|_gWiqR$^eIPoCsn)QJPOM<c{)H592=Sw}omd?r<Nf zi3X!V7a;vv)lkawxRw-#eg=JIfIDChNyFH$7~hApUid9QFW@BuCIW_y(3HPP1{^Zl zq~3vH++qXVjQ;j{4E>7xRgba$3N0xd{rEWa^GVbLnEW?Q_yLwiXyQ@8peXQ(@{w?z zRe%-Ix;%YiH2Qs06`+;nC!k*di=NU%I-u8NO?(djD5gN34Nl$Un6C7}<Q`9ck7!|l zr6^ZDgL()4W)fl?oW*`Qu2s(lU1Om4&uHRXluP^v<PK<At||W)1bCMQKlGvw6Ou^J z5zVp!{G$Jh{;5ODOB%+<7*jRi1E|lIO!=O4^IWN^IHMRa2=~j@Ku-XRUem-3z}U4K z{L)yHryc!@Yjc{0_yztkP1l!7z<0FcHx*^*b|^C0&T*|2upjL{OB4C<=fw{6H`~q8 zR2(95DIdfofYG2g8SqI!&vlyeFOGGzpJ-F^8=5%9_;0<Y;tR_LO%$`9jo`n**QTO3 zH5F%;Zh`*cKHp2}g=@Y`{s7`lz9$~ec?Mt}Am1HLM*O$T3Hgq3U&I%D=dc^balYq7 z$ahuwJji)H$qAF3aJ}>S$4+?8d7tm4@!H1;!<~@tUy&ZZca#kI@%@<UG5_gq!T;9O zK%GNs5yHk}84>`w2$v+(bc|;L+b%EPNUgz_;68h-9FX`J`fNP!1g_o2ci7=NZCqgR z0pxE2;413Wqp4qP#@2J6=Sth^xv$=Bt>->d(eD<-^&2HuxL<aK`xRHXUv-81zu#oi z?|WR~zWECGE%n^zcbNT5`tMaLU7q`FfK9m26S)z--em>OeLH+b!%NrJU3lL1(zWMl zTzg!)F8LYd8(g~flBU`-&zO>M?e4PslWcN>A9E>BzJz{$uqIq8swZ{n(nSm&JXk#T z*kd9tE>6sxIa3#?&4&MNA~`u(WMyTE&6_uil9Cef*=L`L-+uc|IG3<&sxU69c-2I_ zxX0E+G<|RO?0xETpFCS*Yf>|N_T0HFa8~ZA*(dj0#N+pH?T(AHz{U0K*}SgYw`b2D zb*;9b-0{y@Sren4&z@W5{w<K@yUy#```oLE9}`ofUoT+!J^Nx}_Pyu4uF;=Q!u=Xv zqy8E^XF{0;dj0zKq>HNjo;~~C)o)x>^&e*Y`Zd~@@`ZhSNE+^uzM6eM9mdrKK3DWF zs(F4-&7KRagV(<ee<BzKY9^t6TsPdh`_2xl_v`};G3+1r^8AH&oYmI=t9kwF-ydu? zWY0d7SjO&hugQM3tB+nlU03hjm)<IGk1ETI```NamDNC3`=X%#!?Z{DRM+gYm8kOj zr2KW{_Uan-nB~#Gr&d=>^;q>ktEki;v-}>Wa{aNYU(x3L5P^H5GwF{-O^cER6>c_P zs6Y#s?1rq_gzl)WkTqvHS3KV<<ORsmCNyL><VMI%ky}XWxrbBNuchbRx^)wwp`l{L zh!NuHr=Pw|$CHwhL{3hQSigR~((z9}{ZyPdaiUhwt^6q!=APD~O^qmtXROc2jWOHB zg6(Iw$4s0m7C=NX+ofEv{hJ7A!o&sNosZavKNh@pVvLl(#LU}$V&uq?+hc}w%LR;> z7&2|Blp7-C{H5);j~N*u7Ra>i8%DCe&C6N;0x8d2yjUcZ7C$4z{1z>wN23-}xH+@9 zN;b+ga^|LbKe3PeR6kvP_q=V}wxUy~PQqfbh<^S034eco5gZ(>`fk{;VPe#%QDXGy z(c;M`pA_@Q4-yk5Ob|~!^^}-Ad9s*3eY)}=^XAPHDKmXV?$iCm8}s{!#S;dKWfKF$ zOH=*Ds_A}W%fj9w-xe%hT{u>3O16r3Uwl}UrA-uG+oX74rxab^mBM$I6y4vKqVI=N zJh4}bfRCkk_@ETS4oeY_Jo2a%V@^sju0o2j-%7D?;X<)==~A&`#R~DtE3b%^D_4ru zt5*xV-7YdRGL(Faii*UB4I7j%+q!kDIPi9nSoFOV8>*yu=bd-N?%lh^-o1Op{{8#K z#~*(z4jw!xzWnk_arQ*HIPilMM~)m(dU5L1DRJ)XX>sf)DXOZf#Knsjm98+EyfK1@ z32_sY#vOz9&Cr`$VC=WSfX(uD(NA_2W8^R~M@|#Ta)sC+*NKnie#4)NJAvN{_!i*z z1O5=;j|Tn(;3vpt;(6e&27UqXOMw3o@Q=ITw*$T(@E-^M4B#&Zem3xT0)Id7%YpwD z@Q(ri6!6ai|AGs?cRP%4{?PR?7-wc+9A7TPzHE%QZ^H;bY9?fPS0TR~CgkyHLY`hB zWYsz$FSy{}3H(;TM*x%5ANZlbM_pBMz@G#B=YhWl_=UjV0sN1Ef5HXdP5*wn6~RhU zW8mKmeE6R#1Vhq%yO8~X{{--#2L3C+-w6DVUGT+iB23&3n{A1hsT0h$hu8<rufmK% zHhWyiu2vz3JuBq2)k3a#UC4E%Lhe88f^P<X58w|0{#f8A0Dn2~(}Dj6@V5c~J>a9w z6^DR-0{B(6_z$3lhfzZuYIqJcWT1v!sNo1|sA?v~4_&1=H%y8P)1<h#LW-Z(N%6~m zS&M%!@cGR#U*Hc1{y5;z0)8U!R{}p1`0Ihc6Zrdpe+c;BxZt}xd*CkM-wXVfz;6fq z2Y}xn_~F2x2K;5f&jkK<;2(6szdS|g$@Nx1zySXN{`xUfLppl3ZPTh%yTP?j{Re~u z2L}d*1o-;;2S#-EYTv$Xo56!y8#M$5<1zjZ#vg%!5uG})z~I4V=kt)zfPo>tsD5C8 zzwZG5hzD4pZQE8YozH_q{rp4x0|EnpPrAB1(5X}Zh)YC<goYSt(7m2_M|A0;*U+X_ zOLObN#7F%hA;G>O5%=G5k0+j^fS0pCy9nU>`V%=IB;wvX?zl&Pu4ru2$_yfdYwM4= z>&`pwc(|V`&`A|&X(oO^AfNj4`JH#%|FDy^AsyKQ@FRTvLmvqV85k1LoE6;TdH3Ca zY0{+0eM8s+pNFc1`kP-?AcEb%=K%vlgF`|>2Zpw|tN@<3@2Kh@5E>XVFf=f9&|jP1 z<t)JG9lSaZ5qkXrstKdlBKop`v;S26UHgQhi$e#7geoRf3r7Eg{)oV?ek36Tbu=F| z=#D$?K!Mul5j}2-@beys1|GrlzYZFtNVu=|c|g;KO?_^&3=9bjMK2F(p_sVe_%MQR zZ-4&>-I_ME_(p_Aga!|EQfYj5NdyOU8xY{#K)d-7=L7ataG-zf^N3#F9*u9(v}Qq| zkR*nN_(G8`J@@a~-SSXlH}~6v@i1Tjbh%r%fPhQSBO(U)5AAbXqlP{3XFvpN4-UN4 zenbF^4DEBPsgWO_2lzr=jnAYR{R0K_yQSG}Ln8u0efvPygmA52PeC5ddWHI<AA?;U zv;IK!825WV6woL5^2fUVp#DH8V(`H3z3V)7sUKYv?BBc2V-a$B3a#^4%8~`uO>Mpv z2qa4WoOo_QJ-Oak&L({8#B*1VQ;c|S`0(K>9+@9KxHk64O6V^V;|7Z4=>cN>qJg65 z)%l`lDPjuvxuDOa7<E*S84>_re)(mwYSk*SX3ZLrl9D3Q)6-R~@WvZ&sCe+rH{VpT z!Y3tJVin?zBE$;2cI^@$e)yq^4UQi@ERG&MDvlpNF24EZ8}aS8--;i;KdWMc^XJct zUw{2othgw}I$ZDn#l_Ehx5Pl#lLxY3pc{vQZVm>z7ctOfie_@X=qk5~VX{<AlLy2K zc}T32Coq0jx!_|!<VLNv6+hqz{2svf1AYkb#{hpC@Sg+z8sNVU{N2F6G|v4uPWf+~ z^1n4sF}~~6ym@o=O4B{+LsZQ@+j@I@^Vsj^=H|^ko44%P!sE8v{t7+2r<GURw(Z+` zH}h!P;Z|HXZ`rzidz5YF(ZaivcPDXQtBzjY_uuT@3<Ykx_13@M*Rrjb_boSj{ndHH zZ1(Et?e5;h3pgEazwOrMo_BZjzQx`B=Ee;hHtEo`Y4Zj*HSc);E$%n-hPOwHyBcYY zT6r_w-?$;`|BI*T4qV;R`sT)sJHftMw!PQwK0F1={kTT^o$kA{ed|u%-ktcHM6}<r zQ>QkaI<@Si_`45@@7lF&slUOsps5gjxrpcH%vXB(RC6iA^8CPF3w?cv=Kw~kBIc|5 z+3QGL3FGAZCYRh$T>6~S|8)8zuilbgeh%`k7A;z^Q<-@bV!OtT8}poPcn0+oRDRNn zM{4o-EThN&tmNjbx5>2%IurYq!z0z-ymsx{$1v_}IeYf(*WZ2j-ARl+N6(x&a{}=2 zxpU{vVqSXu(4j-?-h1!87c@=l)VFV6ZzoTl&U*N{Q})A+^dDu-f%*1!?b`8kpX{sK zu8vZhJjaX~<J$uXf8YDrXP>=@d9>WVeY-q)@}$IgDnI@7Q!Y@+!-o$`%)cd{g9nLm z_RGr3%2VL=$VVT2^xBRcI~EKWFo5S2QBM4f8IQ?s=A?ykz1f-h_pQ!n{CpliZE*MG z$&(+duCD%B=$~l5Ui$ArfA510@+_pgd-m+vj`V>pMgRNnzb`j$-Yh|%EG{lqbe}kJ zLVo@A*RrgvOzz*mUzH;cVg%kl4xF{?)~%bjY15{82-yZMUc7jy)oKk~zI=HY;%d^y zPa=1^`|i6RTDo-U;}1UgAp6cUFfh=I_1-A`KmPcmL@cwSb?esM(TB@VpFS;1N=j<` zdEdT$^7GF>S9C%?igx~8QBfhk_~Hw7O`eR5m`6eA+_~?+|GpY}_Y3N*{Q2jf<qtpn z@Z%?+e6so2v15nP)=w-4KK})K>kb;06Zb~xM?A&0YBT9KGI=mEWnyH~Z}4IKZREPV z7#TFzX2`rYyWVI1BlN%h_S?%K$5zmZCFEh}&Yg;W>c_!@2bBzt966${Nh56nHmu-* z0|!*5ZBQPBlpAe_I3IlQfuf%>g#Am(;kVz6w@mef>tArchR<Qc58ep<7cN|o7>1Po z`}p{@qaHJp{=Iwm$}L;ANXT3wW>z-L{-->4@7}F4X)i4;Rkl)IUatNo5A*@lH`)nh zMA|lfCuQ_ODSbbbvfFMcz2A|t(+(-Slt?-JeJOKJNO}Ikg#)ma`>sj33;o-+ZClZ% zO`GnB;g`c-QXYy2(#t+)zmgxyg0%B*%KodbzEYWgv(MSzq?dX?UPw3lpE6GUR?5In zrS$tq%0By~?72tEhf2Z2E-5>LhjwpC+3HOxmmiX{rlw}~wMoB3KYV>{CjCY>WMSk# zVkfi%>dRwaNjbP&%79PM|KtHO=<$J+4}ym&pGkS4M#`A|Qd+jvZcSVl{TDA@q%JRe z=%I&NK^K;g2gu=fypR^^5oxTC5B59zoHC-`Q2*(7F=tZ*4F&xVf1&d*;A1KKgVx^Q zp*!@(TCUR{cR<QETQC1T)V1ixm>~`SPyNSuB$?~V#L#p0D`i2wqHY*`(8j4})O*^6 z0cjUZBaTS<2zUsA{tx<0=b>Mjl)d)qJXl~05A2lEYx|AU4_~~ZZQHipA&2F(Gy0bL z_@J&|Nx#_te9m!!c=R)_Y1DDpD0%o&%3$y?5Ihu}k=K*HM}GB6^#AnJPZHzIWtlt} z+2Er-nbgO}FTeaE_itV!Gv<cKSEu>Pm#6wEA|C%*%8|#U99AJ^D0%qYY0uD^zJ@*T z*6o=-sogf+o;z>VZ5keT&z0zhoR@X$)~yx%`4ZAkJ*mqF^`3fNmk)y%>N@r4@SgQD zYj$6mHnX36Exy0vf$7OpQjT`=5O!F~A)qPfa~FFi4^DgTLZ1Ym+7>>kC3t92tov4^ zh4s<js#U96{l^&3asGGmp#5?@Fl0e{;I+X6>4)EwC9jN^xpVr;%-Q{9DtK4}9+;xw zKc4^(;h^KuqdE_UJ(CB+p6QdizK4E)TgndP0sU{@4Erxt)qGeV{qXln|6yy(84uCs z3_c8A$OFfNE9n;Lr=Q%g&|elj(?{ljhYawLGDD|7{;ZUd-++foDIYtb+cSMq$QPIF znLHTwOdbq-rcd(PqN=%AmwwtA#}~v|wV6B^xh@~|*@(dl)20Qz<m+>L%Yvu-$m|60 zFsq+DUv*l3_Os4I1b7&85<IxrbBNQP{SW9q$!D)VJ{vx%J$PunrCz_J=tq3Ntb6zF ztq`LuA^r4=gwz@Gz?jG2h4(nt8gUt)alB>B!1SiAm)tnNms|%P^1*`xJZxP#Rh~P0 z>bjiGDeyTNGVw9&*%@QB-&Q~U^cRNy_wL=h9c5u;+AHZMZKU1cg}$Hup7R>|O^#I@ z+nBa3@{yat!-o02WzpO|G7mh|wda4sC#{(5FPBXmAQR&P<l+eda>4jO`NGV{<yW7T zDIQ#7j4sX?<4W|y-z)tGJ<CZq=^`Xeq?`6Yx(%7o$GgVI9Dm6p_5ba}{_-vGPz)Z{ z&jSyz1==%tNOkhC(#b;-d3b7od@dF|#01Lu;|D4pnCO#wyTlmX->F~!=`%QnF^*t% zI2<xJH&-$)GP0{ou4X5{{`#vddo4=tSlm-?T?igv3vYmjLZ>~`C#B5nFV{@(FO#jl z@}(($^2JI1@&)klEO=N19_E1urn)i4-wxF8e@H)cLFqsAdI@=X_St7;US6K!=X&@s zcp+}({x{^#B|T*cc-R6Sls$t7`Xs}i?eIyfr~ArRtbX!e;9)s<cpg0b9eT7dI#3Zx zpXBcnW4!TA{r;cy8~Wd;PoH+Af%GLNCf4#$myfIQ^6RfP^2m-?<vYs)<o3lra*NZR zUw5(RbSDq1oILy!dbE6^zg!xp^T4EH42;i1kk_14<L?LJ_vs$W9OLhbVZ(-nOqnv} zebP#Mqz%lUKVR|Ss$bWV2_bE&?i{PW^x3Y0+8BfO{12x+*GX56e@H)N9~2ZsImqnn zY!!PkmRY)Vshl-yRxJ-#<Kt>FVWLl3KmR)H89dbEXReg)VLv4$CGCLkl<nKMmk|*W zG9x2Ha$Js&kC!uM%#h2LEmL;=<(FS7zr$FlK3+&S#|HW;#zKroIUiu6J@3n$DQkYl zd?RgEtvwqt#!9C>C&3o3MgP{VTbm-*dDNi4bLY-IVq#*Zj2}P#8~9Nrht$+mIdtey zIby^J`Hz46L%#g-%krg{UQ+aMeouPI1IGf669ylg!;lC1Q|ckdLbkE%g+TdH7U{nr z-%p=)DaM$k#~8F{`lMGALP`EL=U;vM_FWnp8oH4B16^N%d4Kl}8#d&@h9#cKgoFeM zn~=PI_~D0DjPTAo?<jpB?WC15GW5!j2l=D!ah}7tj<TRV(596=Lk_O?oL$GB_iS3r zIB?bVC{M3my<S?ma-~`aVcSkymNuJBjv6&ewr<^8cIePSBDPkvkPh00YbFmyuFDHE zb;_ulzIE@Kv2yF*{V+c3F$Txy+<5_V)4wLjFFx2PFPuMr0P?=>^@rr-WVvL?k{?&B zSRofJT6F&L#~+uICQVZPj(MfhCGtSKAfKd-eAMNIYY-fVsrMYCX)9ch;5?G|7$Z@3 z<fWbj_J8_{eRQMiKSYM@+O1aW;b)$C<~P#$*kg~WOq(`%=-9ENoIQKCk^{#KV{OOa zgY=SqW6hUy147yZZG`qv*WNj1yUsm0e-<~m{zluP{zI2i7A{=)G3_)YBt*`gJ6Fkp zJdpkY0|v<O@NjjXG9V8|CJ)BEmvb@3*VKE`Nk|<cZ?3w<n2P1$Oa9rWO`9I;*ROw( zbBI4eKXsFty2SNn-XmRHbDlYKrn29FfB@;|=O?>#>7wMo=ftVYM}4|w*oFbANAw5O zEBa%~&RE|N>d4*e-oH@vgYRWMdh}?8K3qb3e(SBb6c6klt{cKW6c15RQF6$TAxgK> z)6*6G>}&Qp|0dnE1NtA{XKYISVXRF%pbgL_STB7rX#WlMEb;X8^n=_}ckS9$DfB&` zM%PO}+jWh9jDBV2e3A4(wi2-g-zrx20C`L3o6-S}QS4&_$5>CKA7!7?|G)nF>$0Gr zK#hTgg@p=_eT{yqgl~HiW8_@Wz7zMJ6Z*c#_3q!O_>J)o{Xgt+Ij?DNgrtS?rp-c6 zRV*=i@?_;(Ve4vqqpzWU8+u4Ppj}cX)Dy~pKG`)M;@_;7GNL><o`YYWJVC$N@LELI zrQcOoU3J)1w@DlMq7Km(o_p>&37@6LY1c&Bj7%K*8Tuk7>JR5c%%q)stzEm8Ym9c2 z$#kQ%*V1pq|LE@}<iYTZ_3^>}rVql{t>y{TDUP-Etm!NIDH|h(;Jyy^p1Q<7rwt|Z zW&RuQ_si&a)qnbkx?^lzUI@9y{=^ec$X8x@Md`O|zZ;q5s7us!UULi}?Z*BH;@UO; z0qu%@*ZD8Tp5@d_jzQ$ZSnsF*ps(Y2<jM#8cH_p4^3_*gRr+k$Ff;i!*5(;E@tQhg z>>t2qX1Te!{U@~7((gJhy2dcBvCY+F!fdzO6+Zc5dGbbjsay1&<i(&J@oDBC)c4=f zPg-DKO8+r_FEQp{SL4N4^ECGFC>!3R9E?3V%8b5<dkNfwA}^#JZTt~Gb5;6X*ME$; z)YW9d_@40(uemSBOx+@U_uY4u-(?@rUa9Y-pLPI!O#c&USM<BCf5K1HuK$n+j-8BO z=>Mn(9FI75vX43EWZzQ`#`-Shz-#(^`da!i%qi5q26+CnX;<`9cdw@BSJHP^U&QCe zx&`~0JW|)8N53IPsD!LIt~URZX=l1!=<gu>PiD5k_a6R~A5Tgf2AA!#XV-{E@;Bev zvnzz~t<oX>`jerX6KYN<{v@c?so$ktn-iA&8T2o3La9T|3EiBqkrOs`!WK^G>x2uO zQ0h=4y!>}NX7L9S-|uPU%nij=Vnwd^A@4@MP-ni#YwoDjT@SocVq2bweJvRCL!zUj zhauh%gwN+|*nI7bF*iReP^+h``^#!6XC0L?=u>@<jcdW&Q+V)gDWCXI%1?h#v|Nn_ z@_Jvxh7EgSZZ{1!T}^+6nDadR?J4-BZ{e2?B0f(=Z2J^qVE%@I-yds*c;oDZ<N7`s z*Fw3^*T=b*`|*!b?)zR!-(9Nx@2=AR{qW&wXtUaNKizoO#8^Lf#{9n_{|KLZ0DkjL zR?Iz9?l*Fui~B2F6X#m&C+DOr`{BCwdRx#>FIu!H>ZO-n8bulyJ8}NN@sWO=V-bBd zV_C*2e8#_-##CO~FXSE%_dB_!&HWv&$#akGTJ3WVfH~+C%om<A=J%ZEvR%f}>=R?o z!??*calh>Gllp!i_sZ5+)wgtUSvp%sA5{CU?>^Lh=Yf5~@$1ZqgEDLJD7B8c+_?rg z_KcJfr}cf)5l8iXU*r7&SAFFE5ce7T0)BG2PPX8fg86F{;@nZL?_p5>jDblD$Db`L zC#y9ut_O2X-e{lsZ>RNrPwuxVeJt1a+PKF<edHb$_d2;xIQj#HT57b<aUApgDf8yd zd&+gc+lXfg8Cw_34^V4~T+ih?qHFuyd*%M|V|Db=&$&m#y*}<SQ6D?KrTYh?ea1ho zd!=>v-$@Hok#m0{ZI-?sd+zLMx$yhTX`nuis-us-&OOtIocdU2pH8)pc~aEMl`BWN z(!sW==Y&k$Z{wOO_ZPS(yzv$5HTMip|19Nn(9&<ezSl;Z;C`oT`;05*%$YM~!GZ-( zx$eD^2Ckd1FPON#&b_95=YAjenYbQGedHb!_cj(g_m}35)AP%V;uH-^AImQ7eb;Tj z?p|=+Jz&az$8)}Fzl(cCT+`&*xS@|+7w7&8_a@XHm$UtO;|Hm>2D|8Ezde`BpE23e zrAwn$uU<Whw#fND<8$&&TG$7KAEm{qeM9b7aa}dnxwgI5xxYZ0;Qk2r3T@GWwSB<k z>Z7@L>#F~>t*4)UdJ6piQ^x)P=LMYGu}|oG*caz2KbP<SD@^SZaDBSSxxSw6ViVkF zdcnC*w!ldPlhQ}D`vmf#i<jHKF+ZanGUj*PXK~%TV?SMM&*;F;Lbb=jy}Bi!VZL*( z(Ix%P|FQiSUU(tuwbx!7#l0cM<D|owR~zpQk`DF-{SlurasRg9=}R_IpFX<iTD|_C zG4|ZKbEiD_+;fxJ4==v>qVjw6yBz!I|EUY?59%V@cHMLO?&!zz%+XI_6Ls`)l7H=9 z+11+Tn8I}quIaLmIlqD})~==3-3v2hLs`7DB1~2t|5%<q_UR>koN-AXSIr;I=Ib5* zQNPJ(f7GZ^M|^#K)w(P9rsLw`<nzxzujY58$?ye+ucUm~H$PS$lpm%{l$#g(t9Jnm zeN3G*NWT7WtNiBpAt|pA|Mc(Q{~<p=zd7V%{`~n<$BrHQp2cF36DLko>%hyGFIQ_9 z#@vT}PRRZ@-fd)m)E$$VSSQMT1l_u7{MWg2=aH~`H4o%i&wUy0Eg{a5W5$e8d|zp; zxvp%Caol*f64mp)=d1s~AN|j^5r3TKULx0xgM)+B`YUOeHf@^fclrZkkHpXcV-4BR z2aFRnlO|1?09}qpjKm*ybIyOQIaLex75Y9YHa7NM?(uMqe&ooJlCt3b8ph??{Q~y8 zp@+shA_l}*^)veO8Pv5F^Yb%8|H;O+QVU~^pLH;{V}ElkmpaI`bna`=K3w+^C<FQh z;*l2i1^qUCCh6S1efx{z`t~T4{V!X#EGi`>Wt1`JrkqK`gb5R5r%s*ZBab|yXfxLR zjQt^&quv<)g6}21{`%`FLZ2&KPg4Gm{%7&x#ZR$5uE}%Er_bTu!jn%vsrHy@7c9%f zJ}}mJ8835Bg>gH0SbYO+*S24G4a;>+g5%Nj>C=^O;Jd2Ex+3Mpz9!%F$p;P`$h^U} zRr_oQzDD`~`sgrX;C1WP$u(=%sI_AHMy@#-dy0RsZPh;8F~(o&68SY^aN3@+FG(7x zW8CB5`qQROn~e7a|7hE_{9n%+DE$n5ALU$BR7CwdgT7cQ^!1ZJn)q82k3X1cU)S<) zS4=f6Y`zuRWxu^;zSeJcU4Oe<izoD4_xzO`_}#AgigaQ;Y;mP$(8qwaYuBz%O-;Q3 zAN5^EM#eugGczyqJ9&9|XHm!0oSYnWA7vV(rKJVi?e=Z?`T0to#u!F9!=^uV#piz7 zjvYI=&iV`LsDS>RqaAW?OnoiD9Av~7`g>5mh_6>2RaI>=@L85R%^1#zXAGO7FN-*( z&zTsXeuS{5+k2PA$AV)p+oG)*b0XJRFzSpx2j29<?}p*?$Q$XKXh)>g_<cv&)^xdI zsvaATbIzSOf8_j?a|O;-ESM{;1YO)~<(!<fQ~%j+_WWQK<NSy@*s61S9h{4E?!~zu z=Y5=a`e5G47=|$w>89Ue;v9zY;v4^-rs{A_-~6Jl>2Mu^b2j!L<-{?_cn5XMvjbJU z#dwdg9%D_$x}0z1tzN3?;G81}bWnbyM~_zf-1K4Whl*V(a-*%EnolxzXWY(t1Lscj zq6e!sMjV#Xfqq%FYL$uy`Hl$p)hJ8qOWjz!Y)6rb`{#|Tjk(F}2KFDu1?9K6C&%{( zjIoqy-TeOYbj1PHpPWPeJx;ZM&X7NKkG`IIN?8scK3w%F{WN9y)y`D;`l7+={m(TC z56k!8%p(2<1Ap7LZ7NnX-p8TeX3Rx-(4V@}!oN9|yXrs78}c{aNn%|bC)qy7BhC|8 zH~lik{spf1^wXr9^{_9vS45sTW^+A>xSVHm?B2C&*JZd&^Bd*)dmsxpXBJn<Mwv#= z+?4k<(MXuh9_q9wp1`K@%N6R1=dYOrzemB719<wHo8Z?gc*V2V8VHL;_f9-{t)a7| zS)IFfle45*ox0XYxVtgj8zn}F2>d%x498!=VyJio*TMLA5dLO*tHIgd^mFhu<-iT- z>*Tiq{;Rv-8l$KlA!dl_B2G*Z6Hr^6m?WZ6^Ar&yrlQOMRmLKGkb5C_<5%o)KLkUg zCIV1qvapIMl%E4?qId?bs(S=#;uF?p7DMo4Cd!NxvACX&J0nHB`itpr;$GBcv>k{X zkCM~X@4;u_->Ja7o;nAL1pGA)cW2<)SXGYcLDATy{!yxQyi2`ZMHiJV3U*WXEP^ND zvL9xs@>5XbBo_{6i-%Be7yO-|uAdUEQGN*io}fyyXRPQGaxnoE&Jd!Wdu9aF0ipxo zjr5m~qHU<No;W9M27gnoNTZ*ieue>Wsu+*@$AN<Dml-EMjemz<w>RtS%Rr@%qfp=U zdNhoS@C-K|#Z&50Jf25gCilh-iW>CwO_)5%JS%#7eB9J2{n~f!Vrg%Vo-%Ig__!$( z`n4bV=%CI%?alEsqNa?GnlyDvbiej<qT}29_P_P!zESb<(UZqcnqx)<Q{wxzpE-R> z@Az@C(UYU%J5P=qH+^dS)R-Ba$4#BwJ1Ty1mswrgn<qz2iHnJjpE1g{HB@Cb_nk3) zX1w}Xr?a+J*Qo8GRtgW8@zLXEPLG>0$9auA)1#-&L@Uwb!=}g0iklQYAv(VHq3h#; z2|(rN{f0!(ik@Vi#DDv>kBSeTGHdF@=;`gvGvoZn@hf%x+Q&pqijQt@_P*4J_mx}e z>wOtbeZ6a$W4C!5{f3LW&hx+N|86S72!?^8B(*HHGPNqTI#r~(r<v2d(ky9VY2j&+ zY1TAbT2h)ltthP|tt_n~ttw5Vd!(DwE$M#gVd;_S)^uBXQo238D7_@TEWIMVDqUoF zWSBE78GadI8Ic**3|mH0hCQPwqa>p&qavd!Lu7hnnlmk#ewksJk(t&^TV_(GJ+mmY zB(p5DBC{$}WO-znvn*MDSz%d`S=KCDR#KKdt0=1^t1PP`t11fvr^D>9IQ$%8j!1{q zVRIxo?2aNwiKEO>;iz(mY>#Ynwk6vyJ1jdg+nR05PRh1t7iE`Zmt|LES7nPFj~sK3 zCC4u(EGIHYsDoa|1Mte4laOP}Nz6&gNzSq7IC6?}N^(kb%5utcDsn1ws&d?OJ#)Qs zExA6qez`%pC3&TJWqIX!6?v6;Re9BUBHumVBi}RMobQ!y$@j_k%MZ#A%MZ^N>d@G7 zfRfzO+_K#A+=|@F+^XE_T#@IV=aJ``XU_A=v*h{Y+4CHEMR~<}*IRRBer&!qKOx_i zpO~MNpPX;ccjOo47w4Dcm*$t{m*-dHSLRpcivsroj{?sEbAeZZrNF1auOO%(tRTD~ zvLLp=T98m+D@ZIzDo8G{7dQ%v3W^I#3Q7yg3d##A3Mvb#3aSf4p?jf6p=Y7F(5ui= z=u_xd7*rTm7+y#PGKtmDpM<o;wB$5LT5(!wT6tP!T6LOxx@Wpqx=(sgdU$$ldO~_) zdUCoWy*Rxzy*#}#y*k}J!!yGx!zUvsBRnHEBOxO(BRRv7QJhf#O+d20no*unnNgkL zp6QwCmFbfilo_5Go0*WAn3<gE$Slq*%`DHX%&g9I&+^Rj%JRty$_mel%}U5h%u3F3 zWEE$XW|e1EW>sgoJ3Jj;4j)I5Bis?|NN^-Nk`X)?J4zkpj!H+h!#&$G+bi2AJ19Fm zJ2pEZJ25*s+mT(IU7B5<U71~-?VjVA<CWu+6O<F46AP_Kgho&wN}&stIn_>0@PQVD z=f>tH<R<1O=Q?tWA>VSywmR1xQuTsFgYv@jV)GL667!NF+hWMI95SuWbB8p&Aju#| zF%}X`g!CMcTq&ehnO~jn4taS&RzZ+cEM$}j`8Xh(QplweGI57IydaAp$RV~cp)j#9 zxzJHqTv%FIURYUJP1$&uL?VE@-P7)6_pt}r!|k#51bd=A+3v6x+e_``_DXxT-95!K z#Vf@pB`764B{n4?B{3yA#gS41{i#s8;{m;~Kxe|BFIMPE67-}9I#LGxsDf^IKrbxN ziLk=RLTjO|Fsaa9SX5Y2SXNk3SVcO_phJDE(8F%FTkL-JFngrkYPZ>wFnunvm)Ohf z74|B-NbyK9r&v<_Qo>RqQ>-brl%y1UN>NHlN?A%pN>z$T^++|RT2lQ|!%`ztt*N%u zq*QxqQ7U~`1$>uC^ML2Fr1`;f{onUnR`{tT_^2Xys4{q`svME)k!yx;^2-g&jm)*? z+H#X}?YTv{CGa>E@HRX^nrkKHC|i_^KddGZ1dxbctW0%>&kstAg%uaUic4U@B0UVY z5ee(C!ai)UkR)XzMX-_**hv{Ir6T+P%c%9=^e3hN2T)4`1QY-O00;nWrv6%7Wdj+L z%m4rYrU3vO0001RX>c!Jc4cm4Z*nhWX>)XJX<{#QHZ(3}cxB|hd3Y36);L_fB~2Ep zSsKD7tpo%j8YV7@4Vog|QY{@p0c8{v4MtSdOsHlV5fdvtljhnE&Ww(;>bSh4vv02i zP{;yVSOY2sP>iBbO#uePECf>DIp<b)!s5*P{QmuTNO#p;&%O8Dv)yxU7v8&8unB@- zhd;v*germm%NPFpzfSn;KYBxd;otpsURh-c?!0o&;zyTyN=l!4r1ZfjJVg&a`Q%fI z=b?u^rJ*N1k3Q+~&7AFd;;BUsUz?tul4-W_#@U6-j{ds;*!Z9F(aU3x!1uYNx5n1f z_nTv1r0=z3Pr~=f!@nK-1(g-YR#EzEX8E6+-#?@8yB;lCjO~3F?6drWu;@ogf+hXr zL-DjW;VL23nt~64@K2t$Y8rgF_>@=#^Vup04*U>$zXg|-Ch@0lT46r(%j1>eUzfgL z@F;wFtworE53gB-Gqd4)twqRB?F+G0SONfK+bqJ!|GF}L|5=1;>v!i~t313yf$u9% zngHS0?H88u2*Sc^OBX$;JSYghT;8T|Gkl%7uv|X;zm^vhe3HOtY%q3Wxwu^QwI#eD zjS<Ewq`~*JOUmV6Te`Hg2)n{Lfw>Xp!1o=OEBEk{r=Z{*L}r1nh4A_%<)(f2O#J`z zU$`msM38+Fh|G2sMhY?tBQrd5`El7P2r_g0A;%)fIa}mNMP@dn1lhI?IIn?dL543n z&5)zP%xpR0%k*gbCklclyP-_f2We4xp&WI$rdb3{93TkmaUBfz+OGv+N8OGc-_fU? z`poImr~N<n`Tk?j=ZAwY?DNmq=j@S*K1Z5;%8~h*PC24vy6%zH;~wVt7a*I=&dTgm z&cPsS3Pz`$6E`&nSd;eB5bU;54p%Fqn1fNdU6R)l5PkatMol0K3j6ZT>CG}zGIM2C zC$ss`zq-ZqKwWP?8FFM*rf04+N17|$E!`ut<Irz@ki~*g$JGENz?wZ9zY25xIbQ}N z59ewnq990YN4OgD(x8a=^BS$W$1r3DWnhffoV}WFBF<7_uGW1ee+U+}NzqN`pjg<D zMERUQr<#4NjhcvKz-p^9Et~8XZ202<+v#KVntKFHC)+8rX6*>I-hx9N4Un{AFM}~- zERa9B$OvSemu=m#AH!5`1oB{K+qFLp<o!aYKrFh$P`|CW-?t|4etoPiz&?g~)BZ3V z%kRQQS3qwtJ{a8{sW5UFCu<R3s?m<02PmL-nSCHtA-&N4=O5HP*30Sv2($$-H8c$7 zZ=>nRskZr#%UPY!=VY_bAgd41Ahn#~G?CCzI`0U3`4RLYiR=7AvDYyWl0%L{_LW|Y ztwn!9^S2kq@gcOVeK!8$7C#>rZ*gNvU(hE*qjyB-XBmawPfLf3>l{^(T`c9<l$64( z&meK>Y28-H4%3qTISX-herA4vZ3(dZvIA@>JU%T4*kS1b>3(T}G=Bl0D#xL@cO#-^ z=fEVKir#ZAV$(wH^>mn*=%nP?1cNe3T911JK$h9QoF*(YUXCou^ce2zkHg-s7Aq=X zIyd81(4PUsy8?la&dFSi+i0G4-2_}4d}Z*;TC|g}gTn_ychS3^$(s+dOPl{l$L2%B zb8s(Y;>P=+A8fpnu<-z}P`&P;PN^O3HG~@W$gGaf<Pp{Kn60o^vLCpgk5xTBUhT3f zDQcH}*<`gVY56rWOD~>@18h<};)WX{@SF~?m%_KSel4`Rp&xDS?*j}~)Qh@hJhqiT z8@IO1_8ac^2|i_v+G!0r+UsngVZ0>U?qk)g88_>FvN3jCpC&x_VH>qTE@=MmG24=( z>6&2}+Mp3sU1puy^TV+56=|$do8ZCJ7TqqZ+hL`4YsU%QFCYm!WCrAH9#DnFE;&*H zgkgn;lLhQT8|cCsjoOb4+>4!>72l(Bwicp<7VVTs2@ADFl%UC~VJHJ-Z?{-69blKe zonpl;_@H)E`e$Orb(G#BR*a+cPD*bOD~3{fomkNy9@e)*dxram$Kdf>cnUJja4*5P zGxXMKxbHuPu(fGViX6scurag;MH9eJ#v|^v2fXJMN5K1O$d$9#Z1YB_SIABVyyr0I z6UzCPaz;|l5!u_LIArf3$~i+h)?-MPptTc)q7SMbhc76163A|QVfchHGN7I}lp(Mi z4R?D?5E7j=!AoKIODu4X3hV&<<OO)2Z^sZT+m(^B+5-iEsu=En^8&o<AICU?=F4nJ zri^5LmdsXV&cg-d`?8L&VPgG=uFKK4JD~^QU2Q4EyOv*L@~&`eKioH;=j_dCGTbig zs|ohVx+G3JB93qn$UD%FQ=Pb^>C501DZ{Ldh~JwLzX9XRAl1jVvyX#-kg)0A)(ELn z^PphJ5uMqoTdCO<(5&_c-qJNtr#$_xAc~6Ah&SGd;U2Hy!nq%X;;@#>phC`GBm@hA zu`bqrT8z2yX1HfU!4Cg#gd{--J!8)DY|TUyzz4(a*Gx14j5XXhX(o+t(N>&8TJ9dp z`xr<-?baURsn_w;a~+tvT?_J5iKiZcRB^)-zkz)c`nw!?rb>=108Z$5Xodx_tO<!6 zuzguZdJ|wD;!D1)F3QXm@H1P0kMS_g4ImnD)-N3;8a2akzj{<K3(3)FD>6eOY(NTf z_WB}0q-w!T&w7-94fn*OIB!%8fZO4)0KRU?Y0~z#Bh>B>Zbx|_eG7a69v%^dV)dEV z?WKy$=Dsb5_bQLeMK=SNc^#(Gu*=?>(tCk|{7*NIR2gd5b_~I0@6QNEGkaje!RA;x zS!UhH=2WksWWXnGRu%R~8qAzr(`D0M8HeE4Ic2rM2^C5|LXM<DL)z=q?%d|}040F) zDu81&P6ZV~Se^nLwb^iQIfDJAzjW3xHt=crBc-@!IB3>OXjJJA8V=Tnm$m@Tc(u9> zmOYIpeH!45W0v6_$m?hE`q@w)h&-hK^D7{oartLSri+B2dtjg3%_-?IsC0z`C$GzJ zkJXTLd?{^m5>+M%#3!zs;VcHKpH-}OU8Rhb*gkFjAX>v=Q1=Tvf-u8rxV5A4I-wnM z_@I&+upN?FM{Ku1kTpR;sVa>c-mMk&i*xQhkRGlMvIdl^R^Y~sF3%9x^~Wv;U}wQW zrq~D#+jyO?pbpf~0$5zFx0=P%u=pqrn<leE#lF08$}n+VDyBPldI~I=6*9(zj+z<A z2h#p^7W*P&FcZZiS7%%%`lp?U1M~(v3~9qhu)m7cTdxXDC|12!@j~z0Ni0N=-o@;A zOKO7^!GjWqY4oBw`iGs=rKebZ_KK^NVZ9A~X(s}N4Lt!30faa%nsYw~a~L+Ri;yC& zgLUSKY1o^%&Q=Tq{0cVc1!S|HQ%+7a=RZ)F;Cm&23@b8vW#Q=Cxm`pzUrQ#sdF$12 zy16z9QS_<9Km@OXAr$B3DnRIZ^ki|}Zdt9ii|h6vANm#S-kf${wAhJA&@ULBG|yrY z^fAHcVyiw#Vjm*N&kP`t`v=smRxbEK*~2jQs_8gIC`}@DzWp%vGxZHfWX%e+b;B$0 zsHeiii|`<;$6#8cC7@RpOOfmEu6L}s(IU*!2EnWW$&0#=+4>5Eo^KGZRBT*kJt#1E zTb2$j%S%_pvb+=}MK)?=Z)536-3I#r>W7g6Znkl5aQL)g<X7H=ByLt#caYV=NY=;B zBc<96bZtDfin@>Ce)9{Eg?Evv@DBu_s1qA_9U3T(D63}4AotHI&V%Jp0L5VuKh&Od z(RB1fTq(}GDWm~zjiC{|X5+fXBB+fEPP2EbE56_ju;9tG;85dAs_{Fs#`pTxNWV-C zq#5~0$AB2*YoB$SbM~^$#PwE^|4*T~u_`m4?JA`C_=7ngA3(-NLgi?BsulSdXcZr7 zYaO(~RsnZA;YXqnT?iAi0cS^BI9iA&814pG+UV>-Adk<<ydpF)0Nl+Jz4xScpA{z$ zI(pJVZ~%b^K88I$cnT-*Cb_6ij=r>zSU|K4kgYIk_ZjYI4<l=M3lnmx18S2IVCO+& zjLxzapZ;5b8FI&-U{r$CCVD>y@3PSpwAI8@dM-+V*Fbc>Ga0$z1}jl7-D;v!XTJDS zJy3{UZ7@4PP;-MYhuOhsy2XYx33k^9FtI@t%7c+bpbzJz@I8MgFjA7MwWoo>?zr5) z;$m@h1?m5?H@#*IC_@9}ni^MeM~z{Gtl!khHBGMA53Vw0;a*Zc7gZ7&d-Jnau08v6 zPDpGk2<zfY)vzc-wIonI8-yXK#%*@tM8(PlB*9Q-FO^X~ffpc*6ZzXN?fUVYo&#pj z!u_1C)lR@RGEi;V3?;7(U8VhPLcCejj!oUdqfLR{)~?N|)<=WN?#Fc`t<y3V3v}YV z-kXGv4M?cwA%R+m+6T&X5A^B`0S36Z4W<2XQ^>6qKwlfNt4M~{4fx0EKo_biqfW_0 zKzeh;hFr@IeJ5)JpazR;5qiI3Z>=&wR%>7%Sa2VF4GAH;%xda#s_VEuFbif?b_OH) z8NrC`nSHmZZ*+l}6Q4`Bwqbl?E|&rvYNMggzyjt(eg-l{<}9rh@Nq4!{Lc`ImLS_j z8sQwI$-A|2pmj1(nm>lQxNV|Y(hpk*=rt-R_mkd(+SW}p7eQ7741dl|7LboYPCi6? zZK!q~UnIkQ7o-3Q{`hK`9$P|h;`0bHRz8_y^0n?85=?qT9waDk?fBKC9V)5X7w`nj zmmhJWu5@7=unP531{B;IdR7Y;AjK<i$!u*kPJd*O%qpwsX@*nwx=&<T1Z4;-YecT? z_#5z%kW*VMVLlL8$L~P@k@E_i$_SYixL73>ORvOY{j?wCQrk|<YNr;gp}{JZvDO!4 zSb*uctlx;wTKd9xSzQh-J0Q&jKsTd3A+armsC;ijKmntMYp2E~7V?jv4OfBW{A}z` zl)YE^9K~+H!9Cj99;AdI?ECYI28V8yvpU3eBX22IyQR_;_`G+yy;!aDAaPYYDXEi^ zsy!qC7(12xVvvWt#oiXBUcVlM6{No&ZG=w{fD6!zxUNM@^`SsnZ;jVV!CDTiwF3yb zxUMd%#+M6{HHx6Y%xv}dI49>xg-2FzZX=-Bi(y{!K|A7ld=6})y+O7=&vDBQFvH7X z4wN67;~(2Q{xoy^q_)%egINa(#C2(dK<=Yab<wD5JixUF7OQ6|?JT9$dID;#HDEL% z%uNXMHf4*ru2x*vl+_4`i%o*Epv}b%O6^~0_roo&cC{{F#my7pCRy}%0lGhYJsBKA z9&OO=AOv24fW9*wo(z2Yx`2e>{Od^=#BK4`K~jtnyRXS}9K9K^us;%;D{m(rn6Cdx z>&e6+Bc;;5Nj!p(qz}}(1CV$qk%*fW%>`smjN1&Njt7TZggU+_nB(X7S%i&)xonzT zg$8cj0g8OEPA+QPfI@V{@dGHLjW0x`cKBxy7YSuXX90e1kBpjwnW;Q;yqTF?H5@7I zGEySH83u`w!4_Gq%?8Tll#T5mD=<elXa@QbBq+d}po~8%+XAdH7<K<%wt&K3E!T89 z)Xzr;n7vkhylGyL4YmZ-vj#}z>RC|o3t=B8HCSOijDS&%tp|*z(EHFHZ7JeYkIWK; zvZ?cOU`-T<)>TNe{TwP<u!OM(L2Brw4Im202~=`AoxJ4WJj%ku9bJFNQCAL-vj zIctyHaZIi`3q)X>Tob!B8XVx#GVX$Hu;K9x(d;@pBRg&g`gA|=x_BQ4)ULtL3{q=c z+L1dis&RXw#-FeTu9JIx27<7y^acQDDuL6ue1f>)2T&_(TOXJ@Viy4v`|&K?hc(&I z+#+ZWyG<82lxB#~ckD6D#14(I(|KRGpVD#%D6lp)umwVA0v#=6P^+oikw4coDCNu1 zC1@dE0Dqxe(4+H%k%xg1I1Y?JA$zMF@uShE5NXwElj9B1?!2m(<MjiLDfSc!QxD8R z0inJ$2~>d`{Xl^E1tt%MeK$t};Rj@p4R{jdgWWYAg%gjqYc>c-MXi8aw`x6CLX#7D zlc%pl_85c75m0$R;+0OsD;bGbG4c`8m2yI@ATN9?pe1NWz<XxtAnaND7dD0ZKx_e^ zQMh{bBWSpo)+%?LlGRRY>AV?0KwWZq7i#s(C(BVu1kD>%;Hox*{=xdSwrSM>ot)K* zvT-&D-pYiA<Q+R;0}Rskb(19nnUmNceEKOaOM^?dp~Mz8LN0CcmGR9QyAClKX2P5T zm=JRo7NH$s70g28T|@*1k}9_k$-=h5$e8ssE#^r~iX5HUR-T7_g&f*5pa!ByR|reA zQ+tGza#%qIE68948LVLRVHet>7S#3HCqc3X;&s4eAI;iEXa%{ihs%cA(es8;wkk7M zxEvt|$_<1DHg@dKWbXTf&SBK!xq)cG_&}tfEua=@g7!C&tfRT+8H#7_-S;H)IFc4( zL3Rr5CyN2I3t3ko3WlfU$ehd)#Mj}4fG+OBaC_)#?fB>f6L>2rPWD!izG#}i(I_ml z_cBS;f)(ly>kL9qpyN!?+qi53+3#FIR-dyMO(3nax!DN{zR{;(=kk{V?2~}_x6eAJ zXQYuLrYlK4HZxOClE}z|HlKxZ_@G!Z53<ct(gtKo%KgEj<xYRNU700Tq9G~ZJ?Rst zed}ktg5D<a$H&k#$3S4;gEP8V`(yz$H(|L`V)a3w-;3oeO|~RsUWpGr76xr?a#myP z_f!q?{j4V7)k+up(Yg)8@E%*1DzREWJ1upb(t+S^^|5aQ9c{j>(|QU%dXM?V>8GK2 z#OSARRntKg^_$%V*glZOyz_gF{b=lc9H?@Z7F%X;>)V^NdGb603njg)rAnF*VEzH4 z9yAh~5!}OzaVeq9iP&evLSe$w6BGUvAhtH{3S33njW<9Mb!LV~&iWMQAC$9>GqLeJ z&7~g;u!h+19@IrHv;zbq3q4$u2(VK`ht3As=ZGu|5n1j-tiaU?T_qzQNeQyroW0sN zccSbtDuF>ou+{bJaC3d$Wf*G-H$n0(1U&<fY<6gOoacKQO5Af69~*QlN4b3Dv@*&s zvx009nR!7Ws6!bBlq}dwg}~oft*mY{w0wgEiQI(jcZu9i;@^0b+}r!e?ipIo$ONqG zY$UtKKI6<jp$7${i!v8#S0eoF(=+nL@Q+Nh%|r`dv59JDk1|#+Y9_AH<p7O0OR@;+ z7o+9J_s$F01~&pb>~|$_=mGE`T#42-P(d9@P^f)c?z5q>*vfEh#SMZ2C?F=cMot~D zrB$~}xn;^A?XF%}QxVo(25i`g2uc1Zq+Eq;hpAG`!^Ah{_o@`%j51Y<(@4Tlr5KO< z7=`6xm`f+|)GBl>5!W3k_BMu4Ve(fY$%MD1AlW;FOi=gNHHqu&vObOsa6zDG`6#Yt z`&f<Pp1BuIH85iiaUEoc>uSVx+sD+zg>ssIQry;zNQ|Sv%Jn51lSY~cL~<nkBj~EG zSErqes~7!qrmLHq@f~E2`upI78Q+eYYbQI+_N!e6$R=}(1vE65kfmJ7*(+8396P&v zCO_FLk)wW>94&OA#a<?b)^OMC!M*>MldK7I@bnKSQ~T$3t_|H}8nGE#O3?m}J20CL z=<pPp%+t^!+9(h=LAWi2n)*nYg`Eg*B+Ofmzr9U3L~k&}65vP*@cc5vedQjUMYPw8 zd$_4zHr(f$Nu+cEmrKL*qhwgpl83Q{C=OoE*`e+K8oA1EGI4eS?7r~<wlo`$EayMa zmlt`67o{s>(2#tnnedqO#cj<bblx|{a`C|!uA4;1ZySc%g=#4(TeO8JD7ydJY^KRD z1F(1?1O2R-%Uc)Om&qW(V$k#1t1Pfq`vbV;PaE$0n$c{wJ<fx7;#6N5gh>W62WpVf zeDQ$~o=F0Dc$!J>1@{|hJVBa(3*<4+An-&-Ti#0)a18#OgN3X)2<(qEWyi1t5^jQm zz+hiA6}>MJOK%7;Cw@=TKLVAn*q=8{$?&6{z~`+7B?8nNpSaFi?1#3FgGe+S4QdHP z!cN%2de+7DESIYnqI&Q&EVNrkoAWSy2QCtbqy$7?9p4oH@odr*4>PpyWh92ABYg*2 zb}tHu&z*ypr=2MCJ!4mkoC53!w!;^lX3@46A&m%Euh`3GXLQsej)qdQmO5E|Y&4$Z zdx1<nFvTLUud^O^wb$B|DJb#*GzoR08i`c9&*>}!;srXoo>G>Z60#awWzZwMw$f8T zqeif;F<5A!V(*Gm+Vex>P{!_siEpp9h!uZBmIx4rl5<YPUWb>gr(r7{FJxzIPdfuO zJq}6zYV-_dZ!3KK#5}3p=YZZ;#YbV)954!ReP|4s4|dD8#yAiS1Vl~gsg8mSbdb^u z&;eo})YJAnMBKP?AIubKY9NseXfMccDydi)b`|SXZ0zYw&=(eXEyC^4JN0Z=pxvyF zsPBgVb{e@OZkG%89#mgoI4U2KSg@PV(}L;4wW`y|C}D#q17w^tPW=X+$Tntbo|oBQ zs1b#mRy^WO{~74HOCOMny5#7jH*UuL+@T&FEyFCzj~l=leBXkoI3?K#dDW>8i59^U z8Vd}4h|#2^g3dRL4h;1)Iz`kS=DA|(4kQ&=Gcqz6Si=x)s~cg*Gf?dSJgOd~<EDnZ z1RZV^Jow{kavyp?paPsf@TTi|0OzA}5h#n3y7LguZuKz0X#kuD5zg~MyD!6OQTzaI zhN0Y|PW@MZ1o!Gb;EuQqxTyNyyPW_&4?|6}83gg5UWmtE4&t~@PreR4usl{?N;@z( zUQq|pXT@Z1dNzVSK`z<@{5+pPuSISTn*uq$%<MJoJ{Lfq1dlF#q=x=7*wWfWOIiS# z83I7|kITqjd~X-+53<97nsXpj+^{bfgbJ7;n4KBy`Xgi<QCH=p38CwwGy7?uO#>d! z--iR=$Z>6WO+@>;2vgC~GGgzTvxYAxZG8&%{x1oz-%!LEEbR!C9p*CJW!nJd8~WHd z(AJNVm&{R`){#bhNABfD49&P)zyk8OAt#C3b5PgHNYE<4h+T${9r5R!h~A$Dm|Y-o zTM5vccah#~*M{LJatzpYM{lwSP&-pK^2I2#pw8NHI~&<XD#0m%_mYkKQg#Tb+?7IY zg7o$Rr$r3^iD(K;#uPr}Y6+(Y6Rwn_GvbqTx`_Zt^+gw3e1M?ib(>LOPSOkcAi{@F zgcf0W!~H-LZyHEFz25`xx{M>jLbujKmK@0ef_W_fJ|hbNt$Kr9yFq)MT`PC!LG}eK zqn&7{c5Beq${K1~9X?wts-zx$FzD9vuq_z)$r~*~Y&0OU&wE4+BU>^WVo1?9%!B9X z?TwmtGe2U=Mt-?eF8UaHt%6#Ch`m{Lya-RAX{I&H<u!a_YL*VK&v!zV?E3tEDeA&B z*cR3Es0Dpr%~1y`#B$N@izH7V?)5;}Eg&}DcOEr*(bwZ+N3rmFbO_a+xD^GjEnF2K zy$W!qgshFGKgNZ4!2L1E$8HvK<JKU`1gAl8YVm<Oi|R|499ht;{n<~NCGfQS2P8=3 z&`!?N{vdEq5(j#V9BS}u(+C$rt?<_zC<kU|+`6n^gTDneMLoG7YR`=LTY{`m4l;i} z9=L-{EEyl6C81rJ56mxM6t=7wBr|_=Wd?pC*NFI=VNs%Un?aIzrhp6jS7IiXwO3~7 zE;DnXnP~^rMa)~!Qamj(zqm*IZC$jWhrC5V8iUnX3KH4VB$1g8WS2RECv^+<Mu5cv zD7ha1X$>e2$95E3+{oyMp=uI7L|RJh7&nlL6}!0fB35igyNtJa*(~iU7s|W0Szyb~ z1O=F7pxm?~2vQa-{CIpR!Gjo*qwYe1p|dAcq8-O0jNGfl@bft7heWi3#8B1O_L56c zx@`eU$~%3`W42d!nfB0>2-3`w2YRs8;i#g*YOdvNhpyLtB%*@v(I<whS5AO}*I}_y z+EZd&ZPin?hs4C%k7%d0zu36l_l$^Sz_?&Io=bz;FM4`qz{~JVCS{P~0F3!<HRRIT z`p0`jUi_Ha`gP8U2$WGfEunteGW4Q2Z)-!A{6BdpxZaoHuQ<3eMN$u+^F{8<(1%Oy zbrxkXd^(l>lDOe;N@;RKK_-e9Sg8wGyN@-HHQ>&B32pMlm$pXz-^Dn4b)a4$LSiil z@P$WEHzfeq+RkuEVp8~^lA?B76o=Yv4SlLDJO*-wkJX`^y<0np!bJ-{wI1U}t@)Sf zyN^v?oqG56WThE((<mhWr^E`CQ0`zTN6s5H5E7M;HsUc-BQl3t1nuL+NLp9+(~{^R zbPv?+M$0PzHUqcbZFmN_^AYn5FcnQ6IokX9?rm5$3{r~KDJ!RjM6MlZ;v;0ROF}0b z9cYzJj`~(u<K}`34D;V<%P5Q=h^|k@x%#sgnax2Mez=`a<sj{NTCb7yO%S~FWpd;S zv|icd$Y@pvl!w~|_M!RWV>$}bkPOd6mj|O5x7<Pt9uuQ=R9o{Uo<Sy|A%C#;Vp`&` z;YVq>A@$*fY%97+LDe8XHW)%(1tV8;2vcsRgH1PCPWmV}#av4KZZnB#C->*<4YDSj z+Wx59^g}8#3ijIo&E996@0}(bAWc1x)hLPU1R!H}OF*r*7NbTl8{0xLJnVB7`=M@Q zs%h%gYeBSX=%Zh%yA4wx8LYjYnn23?po7!~{kC=~Lsf3p7T-u4f^}-8i}}_#1nMhH z!fXrSj02j{fgsO7i-D4&*$?A3E*dYWMHvYp<-$GEN5+QAeKyBnZFnyn=&Jy9dK|!! zuOz7tW(azlcH2=?;=aL%OWfNbN#EB;;@;6$hc4=Zee)qX8v)_}zRVon>ny^0Tyxa` zN|9etg{PmP6mM5(RD1qHi!xMu3yQDj-mNX#D_GZCr(6N;Jde+;(Qr3ZbB`p${Zcg< zqOE|LyS3^wh+>~{H#1O#Kmd%ow_l5UFl}2Q$b!@AeZ!mrY$_C}o=oAiE3qpQ`a%Xy zk!c7+xl--!0g8M@3YSdV`^6<was&V|_hIj-0Q9nS0Xn|1Z%}^-M#tU-NvKVLCIRxn z8F5oE*QtJPQEr01Z-kMo#ng6RE{I8~kkmjrjNV^+Inh3>4rKiETUv+_+Hd=D078iZ z+O=(ch&{@l^B=gM+c1a}Wmgs6i_SofBcQk|_MC%yu3Y4BZ$_7%^u3TK+iJ>lF?1+& zczvoBi^>_HmYjoX6`EfAFYA~03I;`qFV#Vl`P!>PXx^_<rz!(2f>m*7|2s(#)?@sR zHX|#3y7=PN@Ns5rylB8Ix(174M2_Z36fMT0u_ZuSG<_1`bISS=bXGYLMa~~@CK{O! zD5LFuoSu&lfamRxnPu^yP<sYrHL4yllW1NZn`nBzY1-993X@^S%%vCt$4`Oc1|yE2 z4Mn_gEAYkAA44kq%TI9ykS7DB#tO$LKm!JR=tEhj3NpSBazgUwkg`MTfX}sjgUQ_1 z!hKZO67IkqTh>GN7Q`jF{CFifdZeEifaauUH<4YgWuS#*(|L3nIR_;}bT5Cjii@nU zqClzyMUoqQnG~jkj#M(2X3-~sv+t<ml>xjRPY^v9gTUG6q4jMc5Wk_&mVTAYDl!Rx zh+`4<88u(kY9RcMQ&^ojZYO9Q8(sKTk-3(ae-APn96yD`es-a{!SOsi@xAHzzQDT$ zS=KSvd@eEEYu?Ah+jCHkufXX$xnJ62@6!zYX)~_uTzFBZ?sJh9g_qlQCuUT<kN)u7 zQf9cH#5(0$5Lloq6k~IrGZ1+^BM@2Y!FZd#XE*tUY^Af1O=c444l8pDvpNdd=RtN% zX0f-6#qbH_Getj;1Cbexg^>afZR@=^iE25rY8%Y|{Dw4E&>8f$iO-?QkrkZ7XZ1Iv z9N7i2;-~oJwJ5`op9V8QLj5RUYcQJ8HWGon)k^s@LebJET0<aHO?~bfJpH`_M!XW9 zw0}R&o%c5I$=hkTAA)2y!_Ezc)Yfk8$s~M)rDe12!J<N%((#xuK4AMehzxM2=`W|e z2UGSGdKcCMmv`Yv)*(sC;}*KVqZrI2PtFMtk_)pM-u5WooV|HNm)#&oXF8b#W2-Jr zW)}EbmS)t-YK_Y~bZLhE6|05GV8I3#@TDZSq#;eW`9Q{od6U^8?@deR%0``BbJ#B1 zK4S%EV1`Qb_^d6R47#1-@OiV8$@t)X{G2jg^$H+_11_!r;rbsI2_2q&FtdfW2FYlS zgI@Z-RoqBUp(Geb@;H1i?HfmOp=~4sg)L9twa;cZ{BV?oy!TnmA6?D;xekEV<Y&A5 zY@eTeT5nD`v3iwuah;9Nz1sPplC5@@VsyoF;6HAAf(h<AMze{w_std?Y^8LG^+>60 zx^r_sJkr=@#r7oD%w0&uiaO-Vpju!gp}4M5?VA1UO64&rYqwaj2fK=;D$|W7<rX!T zp<J!TQj}q8mp$~nAN3T}*8Hql-R+9Kh1EBFog72#%WrSU7sC-^2>G!!RH{W^1<BUX zGWBSWdgPqC+bV82nXH#UL!pPD5h>Nsi`3X0Wu_YIuS|h1Ct}}2)vhElJRESOqd@@H z8M+aA>96PcVYRdTtWn(1-e1qcw^8(VvcEpcESUx+MO_p(98Fv5kk~%m4henSXT`7` zn)Rc_VH1iw_THI>J+O5n=)YAq!u=1lU;ltCis2VY=cr5STew*DP8Gw;c}}7M_w&?% zL*O#d{hy$cO{<v#U<}mnfFdiYh|?_M?JeShA_Md+^pB(>$!3uZDk6qccxU~gnCk*i ze0e!ym`8I3@u+hLs3fpc(IvkO&Bd>l@Nw*iB8Edsg$)hlFcQj$_(%F8A!jfWf{wPr zmka*Jo7(>pRQ?n?D9*kdew%tkcFw`*42zbNO;#Q>C3vUapKB4s=y8O<r~}CEwd2vJ zWJ;T$6wA!s)}EiChURwUX9%E$hJw-Q=haTva#wVwOS50ck5vCoV|Rax9?ER+X^@)s zeTq`ksKZyI_lx}-Yy*$in<2}42rY#e546f?h^>S{u1C9yHi+V^8Y!a9@keYblGcgx z=UnvuFzrRP>qr8)LT2edrry%JwBi=(YdQ9%W_AmDCN%IU&+WvzhI`C9<kF+&5I7^d zQH(`3XjFL-jS7wOAm6&+V~oHB-5DdfxTra=72-!SrmWBTauD#Zi(FjU9$z%y=}l3x zjOu``S?!!udO9kdP&)@KPpV2sE>m9PgJD*YV%r6Z>21|@@+%Va)!0q`e(MN2<_h%# z#{2PEj_B1cZkxpX(S7na#`6bLzUlk4U+W&;Tjm#uGN4_2F&xRD``NSnD9s*>&T=6x z14vTDnsD0tPyVmv$6L_9H>bJ~Biul>4<ZlIhX0*7Ddw3iKoxYGBl#pUCo{tb<y?V= z8js^=!*r)vW^Hd_-hLlEU6tuFMPwL~e3-P76Hjn7`vtAX0o+&B*|N8VPDux8?fWTZ zA*L)R;%hikoED*@fLs<rMIbl-DxKWF1L~A>LMV&;8?@KQ323o9hm)2eM+-dULm9DE zp&}6}$cz*?^<e>)b`DJlP@$o~iC=PL29!!IpOIl5U<StZQG!=-jfIXMb^!8zkCvxn zS_V~R2b7UA+ol~IF9?8p#z~O0i!$SA=ZgEreur(!Bf&n(rwt!qT|PD@z%+Im*pZSJ zP*=EwkVCQz&<mik@ZQyVphXr6q`QA)?9!y$VX*2}t9q*ua;x5np~35KMfOPf+8Ff6 zYkzhJvQ`_0OI&j7=ZO;((q;Pkw?zM(={ZLy!>v3x`Q>)OJQ;4baR-+Dh!88}$lNkH zS_mpib0Is0J_NG>I}{&lg|GP}BqO?z#D@Z`HsB+}&_02-L5r3n{wg$fT%&D=1e23i z9LBtZzT5?GE2%Q`zox!MzB_29M9nJ5{AK7evE47O8!xfbK0wKQKRYY2y}`)bzZZjW zAy$0R520RAMN~*ykH#Wj^!tsddj})_vT(atiLqudom-2gth!=v&2l%!J8t1=pn^GD z)z3xUCb4aDQLS9mBuCNZz)=q+#7%>(HD~S6?-I5RdH4umB}S82F{U5?-etZ&ZGIP< z-^0!C&LsK{PcT7PzX+i8)`&lG0-Rz+CORT?N@7JlIYShP6*4{e#fpE^!*sFYPxLTb ztf(ZpWS&^@Bp&;B-V2<&^FguVWy&uSD~7=X9TWywQb4VDqN@w~nk!zJ&2u8b{NCnC zI7#A$FH@Ihh?@mqTId`k{4%R{Pp6M{#&C=J#Lc-l)AM2x;tQiDVITugoNu4Rc&5EU z&`6(<I<{c6e8}ccJq#L&&j_$9$l+D=btd}uR~!~ANR|#Cgk_UO->A>g^PhbaHdc@G z<qlD<6yNPHN9V59x}Ko60a%-C)Ju5uSnu;1s}7(|jiC1B&B-hg!^?o(OYK|o#R|Wj z55*~}Y4BK#!NYW@0}pa63v>Gb36oEg5+5=FSPXc&2%uDMsw9=gzt&edC-c!@Q5jO6 zARxOLNL<*gef+oy;mZEdMhVdkdvV0yEJqhKOVn{}Hq1bSzY?E|{Wg95BRJdYj(jS@ zPK6CIau=+fv^fa}1aSIdFVR{)EIvOJIq4&W9jmvmf0Uz1g^d>XNwJ~^64pOurv8pF zp+vb~?OZBWtO8MGeaOt(hvioAmv9~AM5Vv!eprC`z;P9dEaa!4ry09Jo^dHopug6z z5o(jO>ST@yl~5H!8*qw`izFW5ZkI?~m)^&1=CAm2<uWN6@D!`ncSi5ri>@#WaY`T3 zizHIRvBVu_;nc76K!DwnKo0H6$IJ;8D~@&pA&B1V!KH8%M{d%sk(=U^99v`KlPJ-# z5^oLt{;QDn`@*eCze0}hdLE)fT4@`;U{k}WBg7UZxlr7ETc<t}C<%PIDJ`k62%hbJ zc3Wp}!E_)vE1iLk796(|nh&M$R&9P(9X3`b^FZ@dN&T`z@}8H(ndbpT74#8~{&qLw zdPmEMrZL-KE@Zab*d1<q)`2u^G#<}X2Y|YCoC>n)=rqtS@>W}I^_76|+w~90b{q|3 z3y2r64n`fUgFpP0SaI$g%A{2w^a+YH*CtlzNU)9VuuUp$u|x0@8c^)>#zK;bhrh>b z0K8q&<~L{oXLezelGj*$b$P=Agt2J1R)j%B5&z%u=75S4@*`_u9X{ZIK2+~uLnnch zR&F5QF_%mdH=UP@+M!mp;r{F|sJg=rK~El5Sm|TCO*fwEAV$f3fcWh(0PoM0A1y=B zNB9B7hCrh}17*SAQ3G?)bZ9ga=F=F4n}vo_p_P3L5go|S^@g0ZIH+}^+z)t!vj@0@ zi2EVn60D>}5v#JWO~A*6u`hX|RT)&}i6=%^e2$bt?*~JcDcJ|j%nt(FT;b2^@MU#C zjkM4}^3!6Cg={MZ4z^80ni#4wy|)14Ap?(X&^6fBN!>V=5%6gbxv9|lY|^`__CKc~ zN6mw+7y|BPtbR~t-4g3S%EPHmFmsVfePs4A@cjoL;WW2M3s<imOIkC!hb`jqCt1*3 zz;+&YQTwwS2y%S_eW|+)j7HN(6A0A%#~7$bV9xI#o{W<gZeMW~!m5nquzGr7O`4XN zS9HUiC2jN&KQK|oPp2}C+8=g+kOxSE#egaEQn4&6=XYvnK$)d>E>;TGFCn6&WDuhU zty&BZvvTs18WYjZDCIdRFVr8IH)f>8+AXyCU9l4uq#=i66xoa<saR5TZww8AuNBto zsfwM_W1G<H4l;Ah%<m;K&xo<#LuM87VCW6aH~9K!;#I}hrxLFUz9M0b%7=N%Bajl6 zJpAQe3kO=Pm_@$PASBQN(b~Y^cg9JsrsgN6GIkJ;_9XTE#LxtZlS3LbZ7|%X$RyC3 zrtUU~L7>$jnzk7yGrCG{rnG~&t=U=Z(ihuB@M444NSqM%X%HkcSggq3I3u!mv|vi? zHMC{HLo}_7+Vw%Zn63(Z)g*77a*NtIM)}_QEWT=E)=%OOg9)9zHGHM+g+`tF9Bf(- zz^6qoz(q*br@)I<zYzxEi_EP=KuhprK7Po4_5}`ugbaNkA`eX45|sA1{6=k~%tsPl zcyha8_;Tlxj9B=Z>EDecCW-<odhd;BMDq2U%xniKH006P*V~%>*Qz(53{S!H*{C8I z?)mSKIkS)lNGcNy_cTbElC*R<(4U(u7JliDv>vZnLa&&S?_78pNAH<N01pGtqP9D# z*K6O~uvFAOIfddbkATq^$l~9+cne_z7;z^?h#0NQGoZf)SoG*DCrI>$JL_$(3r<p} zK9!6n!(n*T_zt(UZ%0Y)pA9x_Q~x8rB=MI^&UG&Faf*H))EC$wL|(T0(B)Zsb}Uku zgL3p4mlU0m1j}FNi!4YBMi$tMzxk6Ko!b96MiiW1V;_yI3i#5l&^K~4Its&45mDFJ z^C~SE@@s5^jLV(GUVN{F9DREQcSbGa&ZuLK#Z`_H(m_tKfZdG5ul@sG%VXgh<P-}j zBtWT3DlJAebn2}u{dSEn^X__(7CadAaJN1|eD@xUPkgu9=dE2nM9KmVRDF9iXfHvR zy?=VN7cwdG!MY~7qB`^eyDg1eQ*o(LZOLZ0*?nv~x1xtj08z*~i+VF2%rAJ9d}H$k zD8}wigS1>Z>Muc88nVkG6)o|xx|}9Fanx>n<TAy>DA!zALn0qKvy$Zz;&~-OUQ*85 z7mWV4gd%6}7!}N_m)dO>rC(V;*BW2SVZA@RS`}AWKaaG==R90lj+3P)*OzmXyipUt z*m+}ns8PQaar2H*Fb}Uv(R`2EIc&KTnu$*H=xGwZ^@mmfc`s_isKtr?pcGWiXo=}B z`_2~bFI&d_WitY7DQtqL#|PNq1$BwDCXe<G#p`y)qY_V_1Y{m0t5wx#H^A7dSMaD$ zU$PzzE!utD&-zs!{D?E;9kuxc6r6@=KBCP?1<@etSVdQvpaZeDQE_Nv>v8uxUOs`9 zeoS?lqw<95<I`sFjlp~98o>|UozWQ{ZJsM(8%ms0l07CLL$J^fd9N0P#?`Tt&m+(3 z?g@=;a9nj>;M3y1{w+b^p(nHZhMwqtwflT%V1uLcT)d<eO6nh~-RDAs(aFNo;P}MM zWBGP{V<LV9;5Bt%I#15Oudc7Y&ZA)*-ySh{3t|&Htp(7$e~@)zR58T`gFqM?VnqdJ z(u>E@Elg&50GRrF*kN8SzcEL$?FiV8qbWx1a+Jo(4edN?q^NL|8!>wB5YS+~+i=f* zlkdMH0(bBBfMn|)XC`i?#5+c!qc~C)ZPJy1-mC4A`_p`Ri!wdpkDeF!G1a)-`I+OC zNxAbZ%6ReJT=8Am;_X`Q&e{!a-ZTmTDoe2kY##=)c7(n~jU+cC^no0`?TYn1&_J2f z8L(}kJa&h@JoYBgel(Zcl@V$zT@j5Y#SSXX@G3h7O$@3Uh+k<{b|*#=Rt<o+$Zcu9 zNU>A5MecXX(bX=Xbn8(O12RWZ3wfhvbi;tdRi^jvM;8xljtA#-&J*x4Dq9D{uld6M zJ~D@yQQt&YfHg|F8Or3`?^~2>#CHdX?@AVL^YUS^!M`_3T4R%JA4*OOrWV?Ec$=3U zM_1+CVWHh1oj40^p9Hdwhc?2zm4A&xLr9e|>eqJA^1n`3QjOiA;T!qTHu9lu<U`wt zLkni@gXsofT{EJqCZ5@lL@Q?(iz627C6M(Y=yre_rDyT%0+BTrck$tB&Ns_APs@)p z%)T+e7yE#)E=k>KXw4LH-ln-w6=A=_T?Z^`(>Zj}Y4sM^#flJ2*?JG4q1H*)skP)7 zBse}q+~;M!PaCBwD)LK^$s%679F6VJoaeC>jm+>=_ysH+jE+^m5`;FRUub}t6!7lA z{a=etA_PL4VQc>p1viV~v+&jKcotZTg&*{7LJ9zr@NEX3(iYewj;FftOwSo{JVp;` z5y!*$fc}7uuNnsW(5q)1&$#8HZQ?rL3|FxfsgTRQZvm31f5AS+{PMGi8qW-ZB5QoZ zMoz1&u#`-v0;pYhK!3vQA)s-ssThVl6HAoIP@+LU1e>{4L#UlNr1TH9_>k_Z!6A*R zqf8sXEDeRV&Qd##&^VZfr@zF4pC}mis*QuPS|}E`s_Bj#n7ps?E`tB$-$H)6Q}>H# zVI904&nmQjfcFiKI*}jdQt;~dx;DcivvQY(8=q}NP0KwOzGhtbn%(y`D$SFsl4vKn zJMjvoyjg81$hpv;21;0Ebwwx9zFE%Rq-qbwegYDfqX6Azl;JfMb>yk{3le3Uj5eR8 zUmwWq@b2sbTKqlZqWA0z-ups#ZydTb6FMxC!of?AgJ-63__oF2^I0nC9t~D=X)Jd6 zc*@z2ovxIl=?}D#OR4gN%+xsW530FBXuixChrUS}qUH&q0on}o%3usz(2^g;W?06B zB_{MKv67sD7&fX6=#%cCXEi?Z>Dbvc$jrrr@)#0V?ek4k9<7g?i5g_>)^_r=Sr-1f ziN9hq+CTX-4j%aNHF#GMu;StBdYlG?>wFQxf(5iN7LGnWZxf$r$0_7rdDGjJfie@x zLkY*?&=z1h_1R985ZFv-pPT~9Y4D)zxrr{QyTD^DHf9su0g{Mq#+8z={*)ja5cKX< zu9G7J&~Se(AbKHWjt;=fU(iaT-i-HxL>=QnrU-hQVej2c_eIvxp#Zn%^LDjQX(3zU zvdlOb2Hs_5$};`6_bLAseATnolb_m}!6?u~Sv3(mH_Ex%-TcZsUf|I|edLkGWG;`y zk90ujG{!#l8y*t``<101i{TNJp`;t`k0D)6Z-E4If{*=TU~lY@iF#)#l-%*eyC@2k zqtd57=n@(v3{e&u%5fO@R9}vYZb5x{2Yx|?k*!n#2`ZPG>IRWqrg9Q%CjJ#V<b!yV zvLhR-xndLQ4|FXTWweOO2TtSH{sA)oT3(}c;6>_6AGi(ohra?MoZh2h5Q*VF^lO@S z)Q9A#Y}wF={9<f+JW<JE=^xPDY|Vyy1C`wlsY5_^Yoj=;O_-+6a2gSto~(6lBw-2U zm*4$5A^n+PnCtulBYt5hEQ{>YCMWe-7NS8DEL=YAy>Ed9auMGg+AABe@(h{3t>JI; z_}eP}wwS*?$=^!&+d}@fg1_BGZ;W;#B}hEsHNJy>{cpbE9NGj(-<-~w@2G*aN{ma- zC>}%Ak;pHHLY?V{`UuTQTH6L#45*~J;;VRQ09B8#qLPI=X9S~4y;C299j*+ARxG%8 zy`4&b^;RL&4@;!>uIg`G{0#l)8w{hbP;JgD?hkE=NIowMffD|MmgnkUp=;29qudQ# zrsd^Fw^C|ijVH78uaB5(yziIP!W9^eyIo5|*5y9%%fuS<6TM_QXG-i~)6bEDHRCSy z6t7wAZ4@j1NV8hlsy#q}FKAVt9xQ~e7T3)M-Dq<$YDRP|N7*w#o6rc+W=i_vx>~(N zTvzDYz(YN^EgehRqIP5}IT|F1>-;W#DwcrVgTz&Q*Y4D>VKWAU*65LFoOC%~5MwGy z!+eTvR>O*l9X>DZ6lWIiB$>6K_dXfAGUD6cp{jB86;9^yI=K&iM>3Y--exA+(TsZ0 zT{LXJwhlcvw;S#cUM3nsw)@yF5nuRoZGU_=*YRPxNT3ltG5aU+75~vze%s+O(;xMK z`Tb-ueN&GysTl8Cf=QZm36WxS&5#vgt8>cU&q_~Ra^El&N7<DsKr2Jh-`I3~wi<il zHDljJS7deS=_HtNwjZc=H2o-EDEYnH_-%DK_<fl0`#VV%sKxn;rvD7SwAFMSLkO=p zWIlMm9~p6U)FV`6>SFlP`(JJV>Qsi^;ibdr&tcnJ{nVL=PRLDCbe7d{KL+U+xSICy z_rSx{j0=3({H&E7@UbrL;&^R;jL4bV|7#R*D^Ln;aGX8KNxH9c?AIspnp-jaAyMFT z&4Esa6JI8&Js$ibR=mM;YbiI4ayw18!FHMMykHtvG}W)^c;6#?drC{R2k~yZyyTEo z$7@bY9{^5nxOcon8&>mSW+d7809r1gmjB6f;C&vwzy1<(+iyaSpjT7g3$kx;*yCmX zot@~@ghl@qEXmAY(2`{T0%Og|{l*>SNmDWDO-Nen4}YnMe0E%E7=83*Xk3FMc7jg| z&*(@++v#qt?i^l92z=ns&k!xy{9X^7{5~Ey<S`yACcjO1hGnzF=JjBdTuu`jlwva| zYlmqi^0C@jUQgmK7uZ{Wuu@=FzCd>vlZz`hq7CBpK-C!aCig>W_NFNP>G~L#+Bu~3 zR1UhC$TrCFRXY<j>>^(`x%~Jnvcx+M?GxgcisxgWUD|7C7-;7tRIE6Ow(C1=+K|VP z)LOWLh6xVxoDHR`Q-8XTaA`i-0M4NfSy4qI-lX-%0qpk%+vvL0`EOx|JMqH3kD8&2 zCe+jh(~0S%S}aKS4i-mlc>g4MF#`ShDUNzo=27TpBD8SqO;oe+x`m%N<L*-2z0Udk zjA9EYbvv+tj?V5lE1gr8O~-@-Hxdx9zKO^E<UJM*`riZDJCL<i-qR^(b;|5xZ6QTX zT-xpuY!|A}c@shV5Qd@sh1`TgQjl)m!<1k1l;L>4`x`hOmj@3_cWOU;7>MCpw3HV% z>&9j;P89kk@}v`>tnDyw<o)VJzTE)Kx#S8zlkKCkgEgsArL#CMM;Tz$(tSd8*7CDw zU`k8BLbFtUe&xS2b5IH@n#r#b$c|q@h}T$SKZaZTDK`E*p#rM<oqy*BJ46;9;Qlz? z&7Et8RK;U}#?y>yryn64QHJu@!+btQsZ&dUoLE3%MC8zxz@Fni+Yg;LucE^H2mTi{ zoVwn-j9B`cZOEAZ`Xc8+E;`FY|Lb1on@jy>dgC6;kFVv6jKlgRJo7ld#~NWhuCUSV z%pez#w1!rj$E{@4*Fde|fsJfuBmhc4wZDB6zImIM`eAeRlhxZhjN*oAhLGZml-l6w zIw7Tg+GKp2?yD><fX(y{R#0o_$<f<8;_;D<rCV9L(u3!ec}nI(Vg--rLC*o)UuaYd z7I`)b_$va@#r8tmS9uv?FvGiDjQ*HLF!jD36MDp@hpc=)7Blx~>@8YJ-9(aYA4S~h z1aQq#Nsi9q0GB;<UD*XdXQ%W5IQddwo0z-dJej(k-Vat)>Pc+Wy7Nf;dcp@+uL`JB zG7V)he)T91O6`8ZqTGvL&QSjVOUpFe_y2@;kY6tP8i?#GP?encWVTiP$_!mSF;BK# zQxjnJOuTVPTH{AC%2nxi8t#^#aO>csDCGImjD~=*HK1;Bdb?Lu_}P&FJ0!EI1q9y| zuYnf{PVx3ADP=*gvn&=SBI(OS9}s7GM@tpPC68!QYrX}NF8V;AdFKU$M5Ap}h_fh@ zob$&Q2xhPNF6@JAym&KuGB%m7r>LE_kOQAV>koS40j!fvFl*0Kz-XGDPQ~R0ySIlf zK&YE18C&G?Zj9|0pO;ZkN-sHwRbTiCl5N~mh@U)6@$p2MVj_n9JMPl+u&KWUzLawU zuhLU@;LiYP*Z(u2c#H=DJQF>_bD<YD^~n?H)%05p4TIj#B&JK^^y8BEc&M?@Sqq+H z_f=^AbN_hh{%M)TuycO7J|g9WuM!|k%yV=AF<*8SGRc3ufB?M80T}fe5_#4QK%A4j z=R(#01&Hs4p`FCJ7@vTT5rC?qgtm7bY9Ro{4>%ON40jem@jDZW?-LZ?02F5=?-?`< z{O76oPQN^K5C^7l3=Nw>3=cr3It)KEE(PXGJr2z8qJ4qcp~r#wR`Pxu+SvDA1JnC7 z;e~gxPp=W3Y0)0Q3ofG%*zgF*J1UZ95uSNiF8T&&(Smb=>Cg*prL|EIm`f|zh9Reh zl4LYcn0NQ!Rg<A`HWr3b+;R5w^CaGl;EAWSvwtRE+thRD)sgv)VASb9;mTI4H6Fiy zft&0lzbPVR;G*r%v`w*wZtajVa9=1lvb?O7uCTFsk~U{OBG7CX25L5H{{y0hw;?35 zf{cplm7>~uK2ngOC#y3uJQ%FaX1LIImtU%!D@Rf<l-+xINVo}|wo!?=9)W#?L~_M} zp;{iiZvvz%ygy^R_E8nNoc4d5g7&L>naky3>S^|UESA#_OK^up`@~A|j1nHb(<<0f z(X|@2@L<tlnA{J}3c~H`lvY7md^;Q5+CB|d;(qA$4}Yf+ILE&*pQW|Z66Hwhc}r-Z zb|X6KdK;86Mm=<&sgJ;T_aRWk3adDbXG)^apkJO~?x(1Y!CyeL&Csk-7b`K$*f-5f z-Vo<4qOXUq)hwL2d5G={P0w+pI#f88!tZvuc-(ZMS#)eh3*IiBb1=Zpsn0qsp>c9i zJ*SEFax|?BRQ)8h;6&3J<p_xO{nij+9cR?xrEzL^=Bg(1$0jbg@W9+_c(pLdt!m~L zJoP?!xwGianZy!Dt^FAAu0v*7a`=QYLVen4!G#3EchefqCa3=q$uK(D*16FB_aRg& z`WW+Okb8Nm>a~QD40ke?r!u=khc6!xOun6tE3mSy3HC#yY>aKL6fU`MVXhhDw~3$i zPtE!qjhaKXPk;?Y?-->iXX0|thBn0ZZrKL1)ugTaa96DYo(6h$y}ok&cr4Q0m7u7i zwrfuxZ-9CHo&?Z}(eARHR}D=vInysH`K7do)dlYuhW69my6t*;ATs~FpeJeL-yzgX z*Y?plBUIAW%Pq=+i()YMgE%r6H3r*3pe>)F&Gy?KQgoeBc>vv+0yFek?}miA`pu6o znXCWvodg*UZX0xK+nN&&!0I|4Y`rXTA;IVIiw4%CGilKKIbD$Ou{ga3oiH!DtsvrW zNnAvb6RuuyC0;Jz<QEYnE*LPZy%!Ah&s)$kt#bZ2<Nt8Kz<2Ph>`QJJz{rb>ZWq{3 z3mp53oA1PmCVJQ_R#Xf8c7Z}|%Wjm%>2`s<N8+~&Ebe=|z{13B0F~zL0!y?PpcdUO zfC8mf25*rJeYLiHH$`u@75BYbU~%881$rApxu)JX+7SBQEN~-lH<Y7Y15bFfz=Ux1 z%JKEd0>4;b$ZlLS^J0O1yAumo`@e};)BH<s^2tV0aJhR0UhTbCz>KrT@gE4XCP_Wq zDYXmsAlsu1l32d8uxOUOqa|qDBNsIW$Lt9d)d6vI(N#sVQ?|`;wo5iqNfY0dY-($p z{7TT)7|hugU`?D|Sl%@62`^+4z_AT4AdX-#M;|wyi(_m8MIh3)u(RxxKisrB6_pFv zgEygBh0zs|c)u2_Gk;VAPs2a<u|^aLeir5w@j@0#b_0f2;wb|`b-)`?P^rUvs%lG5 zeiIMg=o7D@9(M&JgER4hHD_VY!60aN8NsaPLUGy$4+q)EvFL+a=>1^nkYcI)3!$wr z`fC9QkyLNP)KFWHL0yJpQn$DgxD0r_81T5n6OrS#w{0-#+Ux%SmN_FsE^2G9c@C)D z#GSaj6l;uZ8dNIObJ}b2m2|ED&IBHOkz=&iSd?Lrr=9IJF0q0;ix;@m&wJ#EEixkm zcs}H*tzWB+R^a;YP%y`JTJqb801&0W*7b+p;Ez-gfor@(_2^*pi4p;WKuGegh@y=< z3nh!J$6;$vvp^@TAk%1!6ga~tRuA*Btx)~Ohw#imzejEI00gmHVWshQ$1!S8k8%b5 zdP_b2-U;ZVcuNcEFioJtq}A%luz%ichH*+xk1{l8FWbfM#(GMi-xd-pzNTZL8Ll<H z%<M{wKIDDEAiTa0in6WR#s{%$?|Cu26-tWlqJ@p$3bCXYAGlm|$$GPzfZFLSoh`n* zH?ZDf{tJkI+XKn@rB}!u`_<2rm84)+jT~{ww#Gu%7*Jbp4b;SvLV5fp8D4D4fa2of zwQKRPORaI@9mHqQ_c*0;fO(s4d!0BsU%*1}KTzI<KeM<r8_P%TgdK3F3+m;=cMJTr zLVfts;I9q-Izuh`&&~KCv2FnTK@IreRp6loKd2IXYxvQIADyA*wz9NLAvWhCc_eY6 z9^GpA?`;X9@&q7cY!F&%rHr>#u>uj`?R+teM!dJJ!0;PA5h){vx4Ni}7I@^Sm8?y9 z5-tBSlK|B%cs9}SS9Bzpo+-0?Tp$&8CJRDjx-)L~q-FgTJhV^GcrNq+R(ScZT&SKx zXCL&^qg;nCtNu#&jLOj)UN{LeP}CyVbUU<S>YxR$6~Ma$g0_)|^yFZ4)V;evP+L>r z%zSRjb0LSg;i-)D@@MHHlh6q0&8fHns*dS&Q5r=viN2Z$!YLXvo#MOXP8SjN!6?J3 ztuD3N)o*%+`iQg7om+kH(FP*qqN9!3eFA%kDbJIOY9!Xu;BQ5BHnQMLr02u5;wHn$ zF(SFz)Ta1mk8Q>4Y7Jmlm2bk6*Rck4PEmb@`*{ReXE2)nuhoVTs4*Psk<l~Q^Ife^ zS_Jv=_IYc3lbvr{#EK<Qw!9k|_p+gy4O)f{K52}vYwUp}q@YJJ8uXSVTuL!~V-hh& zWsh&olF7MJ6*|-SttX`B^OCq}r?2vfg+`My81Oii)paz#ZBgD<5BCJwX?>yj{>mO* z^0r9gv=$&3Yw=ek_=V*ow4%nixTLM1M>@!8#oq)dlymmd{e3@q3{5rZpFD&0T4t~c z8WND*?ELskfbEm2P)=+2Wdpuf{2CtD1he}|H#dqEF9>8~ufQu!rON*CI+D80kZcD6 z(Yr1F=)w+Pr7ydmzv6`QP`m&%kOvJYcZLrtg_S9ZdbR_yQ43W%u}Yp)2KvLbed^qd zbw)v*VZC*Dg)XRyb#9I?OE6mai4PUew8><(H{8Z*Zt18*55A(gpMW@0xeD*;tCPLW z;*Wn|C#&k7Re;d8FQD~KYz7BiCRW^l4z*?HtzyOVR*Yp<(xtLnQqZ)L?X>HTvPId> z?m*F&06T;~h-RyoBYuqn<wu~x@*bW~qH$=-aIpe!T#|0yqNI~EX~5VT+d&l2ugOMZ z(5o#S8i@KWc>iD$#=GgOgG`IzZiSI`#zzLC-8>wc87FUSHODw&b^H#H7gy0!ao#hZ zC=bH8x}G+SzJrpL;bqUvOHoGqD#yfIUgMuv>6_e7_XfO&LaCS?a@5;W^ejLa$rKp2 z8QQ&z1pzam)i<D3MZ_KNcm*DzEf`bK`-CPwmxdSWjqclJKA$hE-Vf)3hVNsC{aHuK zo>^!S!+WfBU?EoE0jO+jZ?~0&FvKDD55e?7)kXEJ{n?=aUFkq5VAP85+NR~cCzRZ2 z5tJ{*cT=Z{n<^~$MmO<1v=y-)@8MRr8?g|yYE*k`q0ORN+18>jxNthW&{ZYiwR+aN zYQP#>d`>XcH#tu|YYSbHdNa0^)uIoA`RO=d&QQ@_jpSMXmqGT#EGg^I9?i$L7qWU3 z|8n-yFZ~4h?<;QoA<_f9NZoC?TYpHuxN%|&&|Cm_8#LyvV+&gGzWE;-#_cAs`Ql3r ze%qIkRoQavf6yy<?*`nQN_w%BH{?Ge<U&HohkFr{el1Du1f0B<9QnOl6qk8RJ7Qjp z2VwX@v99hx#2gNYX)Vrkh5A9ZJ@yy03IJ|?dMR$UaNMLzjG!eGIafbTDB0g|pIOO6 z#9xL2fS_Es!83`K7bB^cMpv9Sw?)avpQ>8pZ;AJXsJh0#zt3x4yl&;3?3?u<VzDbx zKjbKm4AO6crIH(Jkrw~~v}rFq47ioTd*8RhFsP>?dRjcKc17>+ZvkE}6rV%kM!)K^ zc%8<llusc?ZCF4=gF^4OiZcN6*$;mO%iG-}iM~%*o&J5p9b18X*a7tVU$t>gq&3nb z#`VV}yx6nziIAOrrvESC$4<zKU2`6f_VwY~tF^>qR`gb#{0>)0)GhORBLMC*@9^d3 z2fFL1@Ad8yvbSS$QI~2UZ@j%0JwzfI+LeiBV|aT(&OzjjzlA67)F+Q1Z+wwZ;AoI_ z1n7F7sY{QbedbG$1-_g&gV{S^Lk2x`!Ox?tPAc!D0Lhp6wMvI;P<hkdt4TQS-2-CA z<iCJAPgUD}9;Zba;KQG1{RndK{yx^CyR|hniP64FZNPqk{8#^imr%i^tl<q8^%fiX zc0x-_-2CS0bH;g*OcuZkhC#zU3+gS!Xy466v;oj!{K~7QKTvN|P`n%{dp{^mr(bK@ zr#0UUBiYWcGtb1knLsz+Dto(^rfGYsp+4yjn<$q45<!ORg6-2X@<IQ?D}jGVLD7bw zk2P+0u1J@ewGv38ReZNbcZ!>uv@ze1xE8Yav*}hN@k2e~y{m(lvpEG=7YvLY$X^rV zm!jpb*=cOo!l)(OYAE*b!Do;D_d8hsA7Z+1;W6FUOKbXsboZxU3)zYh-2*YAdjQY* z$F15KG{m$&yXXH9&VAW<?(7~4u4v}x!u3_=xp27A(kGs~1f@m{Z7t&g-53h%V;V+o zCt|trnt=K6H%IgT!Z0+~=6EdkYdn_Q*pv|3tKN#oa=${c+-v*9a&Jh+ZL!;EFUZIT zaq9^M^rP@w0(TJ^^9G&ekFSF=-UeknO59_DMb%Kyr{J$pG)ijgXuQ0u@bT(-g|><& zBs@Cam=8s+R50Q$Klj<t1gw1p)*eQ8$@jfrHDLRB4Uwy9xn;^Vn%oPv<vYNZSVPQ~ zY{onLG8ZGBH~80h;!)@n$7IH#`zwHJ*O8pOpi0_^Qhvaw#<^RJ(dX|Z@x!dvDf5;O z42YYy09AP2**ef7TpD%`SwBw^K=<K6=L>nz`JKr;nEZ!kQ2ABn_vdE%4)c4B`K_2i z=VP#E=uqukB>rS6snI-iToNnL`I7?9KN8G<^Klk4;CvK4%vKZ%Hs5FI8*H9#F@w$j zjARjmtj%C^JcGL~9%nvI+>AkbU!^V`_{#=BACwF*#5~q+<AHK>V#hIbi;dc|g=4`l zv!S_<Fw(bUE5&R(p#F)}0J}o+8mwVG4=u03o}h%wiDT(U4y^~{9bjPd8s1#&3*nNM zdTT0I`^9y4bONKMP$IdI)oH^XipQT1gZAcgaZ6frTPM7s-<DA6hr$c0W4Qn$q^-a- zsSGxvx)Z~@t0fF-7b90#DcgsxuCe*?*lrtz`9HFHuiE)27lA3J`|tGd6!&Syc<v&K z=T1g?)EVND6>8@SF<b`?Rb7oR%`zH`eR=rvIBJa*_(+?awO!uRrF&o^a01HfXae*U zqtTZ;KuHEg5=x!U+K%zav8VCQA~UG_L?Wnr5fADH4Q1tg`<?Z^%vsq|^v=_jzRWxI znejmG6D#xmsXEBUJZ4e*`!$K^?LVU@P5V|)-_UKE!B`1}ZtpZhx7nP`pc%BSg_03J z(v~J-x38rI!d3>uXCh#GC=b|nQCgpXZF=t;t9`<Wt)@_`m&R&;)ggJiC2?jq(9cCY zRvYtrW3_|-53$<VLLychZ@%z{b^i0y_!E?6-fXdg!WTePhs=6gcB~uE5nNK)xko|; zh$RGGo(UQ@Rc@fFvwB=_nD%?0qc0W3+`?*Nh_;S*82H|xGiMCZK2*a)v|*>8<I&kw zhFdBjm&P(5{wyU1Xb17C*?Kft!lO@SAK>pSK#3}h&^~WQXkS6G$v%p|ezgV<6uQv< z6`fl~J{tshJU)9l<?rvCU*?@eq1h+mq1koUB;sD_man`yne)Xk#?8|DO~%!7E=@+1 zn*__$zSW|*@FgpLk3&!&0I61mig<L5L6VQ+7@*k_U)CaMH0vl7iN|Tf7$x@MKRj9| z{r<B5wM3luEXa!6z^|h??X6BF13v8+R?XJl@1TfgnI>b7@<`ZNT_m$_eDqUuFIAI6 zRN>n^Zot@$zcHsBSYYmk&~<F?+n^wir@rw{UlV{R)uZ13!Nad%9{;S5mske|%ID*S zd?vIUPz=__(&`1l+E*gL%81^U`DjxRWIoYmfVLuHaCXX1s4{XX46`LxhURO|rGS5a zz61)a@gNHePw%2QTVjNTYx~C8qTql|ME`o8#|wFoc$aTM0i$o6E#OB$$%W6w$^`ga zf@Xqq%R{dc0oGrU%5s;4Qh+t$dzxMel)<Gx2cI6gMoL=4$<mY?LvE>Th2{E09QX+_ zHV+C+$>AB_OJsa2l8JZ9BC+Bo{08ERzg#s1Nb1%24Kx%l0-v%1wk3W8I}=g$LDq^` zD~a#!Aa<m!oPft_Fl||A-Y?-H+-r7dkJ9y`ASn6PTqPT|Br_4er(c=Bpy(~%Ce|SK zGmyYw>=Q#CGsZg}*}WDsn8EM<Q1N;W4DH^uB@x>F6csQ-yB~@-Gs2AQM(GUWxdWJ? z?zDHb=ptV2NP*nY3J>I-7n{H<dK3vh$G}D_JC~(g1)r0YLGYO^hLd><&REZR-c;;c z{$PpW<z_hR$+0hZk~M~xnIT9k5<%Sa{y+A<1R$zv|NjgOFtRwHpt$9zm?SPJS}0mG zGUy#05*3un5`;leglV|rQZ5kC1Fw|zY<<15<&~B7d}VHJpeAUjXq#CtX?tCoGJLNP zH}3!YJ?Gq+VKd+MTmHW{c>R9QzJ1Sr&gbBN)+FhBH8reT-wqaKd53#^_4Mx)#Tf5= zwCTwfdXEx!#yZ`R$@-MNKl8r)&QlB())^{4yTiQ=QKz`(LPJI$pP<Kl67q4a9qs9q z2s~qe`~2AGg2=xN?p*cslE{8MzjqSKiKDXf*ZYdIb-K&#u=>5Ra8;aF^?eYS9h#-D z`0OxJ9tGYb;HuUgy{7vKMSLwvn7RN0D_SN<r>)XYmYU1>>Ocl)xnCX7o`DHrB%Kf@ z!IaJX6r1dwVGlLnTo0-<7<@+IrCojGCtr6@;$DB92FxaVuRTSlUjyXdGhNx~u{h>& zui2jaISs^s?S6jMN8UOV@mJ}I_!_5QX^`SIq58G?lztbVPw98zeCnaAoljjEhpOi` z@^|1+*%=NMo4W{n?vju57`bu-$Yknk9dM`;UvmrTrANERp<37(hw4O<bRkJPjEyQz z1sDv~5PH%1_Uh83O4ZG`n5*s>;zpo4p|5D|0)guKGLAsCvmF9ejZhT~V~-la7eT1P z$`FugXRyXp)1<{6$6QnCd7ZxOC;H<1p(Na0p2?>J$sCiam8<{2=_XE-I32;J3FRpr zF{ye{Osa`0CRM#UMVO=EQ-r<h6yZ-Aj!9KRI40HI9FuA`ohCe?P7{V|aE&iN%IwB) z%Ga0Q?yq7vjrDV5IJIyLr-kmmzbV46GnUmPb&e71g=N(T<{Rd!!Ve$AHz>>;%*you zB3VHTMcYz56)`AlH=Aw5M5#ztO^dMS@xqCkBt{9{Vp+{&GY*br6+wN&VgG%2jK>_` zM_(3-4>58~tKq}C#<cpl>HD_!fvVtCCc3Fe(P?N()R4sqFc{BP@u_Sb@TsyXKGh9F zRD7x+ber!1r%IA8H?3{!j7&u_2UK_}sTsSch3(L(gr?uCGnn``4?0z?8=Wdp!)G!u z?ed^gJ;P@+nRGVuvNt*vE|$8{sebW6FqJ>L1jtgBa?I7*ofvpNj!~}&Q1$ZVP5@PG z9)pd+I}~!NDh~%XtJfet6~3krwGihun7wznmU$4W=1|+_puf1R?EtEd40ca(iv1~I ziqQ+CDgb6W0WcEJr!$>R?Lexiho-k<+fRD_dyuMnAW~IAn|^j1!W-GU0;*~^`7v}k z`SCTWv!4$r7?205N>HJy%;kRp`KM4-Vh?kCjK0j*4O3;Vh-v!eq8q2`CXQ3}2$W$$ zD}$-h&u&HEI-pd&{h@pMqc6+#MyV3KP^v!L(H%<Fq%87}qEtP(11C7Gi^-Kxs_vwg zKJd2GHA>ZPCQR5a`LVVTTg9gDFLXkyTG({qLMODUg$%9AO3|vUZnP>o59$i7icV%e zmZ!DSS&#><YN6LGDD9Q*(5kp=ZL2~b=jjfrDx)1#)y{!kLRDojsH$R^p?ILGXtZ6U zDJeaS7wwI%VeW|1;5{>-SGKVk5LhZdv1$>8tGdMlSH&Z{;2dt^VtW~Pf`L%c#|Bln zs(Bo)s<nho?)a<-x@)w?QL85Uuop}@)|IQ~S0Svz+z?g)-9cEL(QpW>Z#4|U>ZIn^ zAgp$3t^i^6n5G?s)do#F2&=m_?I5hMb!rb`wMgRyVKrByLRjG<mzk~zcZ9H_s}yqe zFSHPvj=JHiqPv2x60jG+%OO%5=m_+XMtk%sq3I-XqgM${?~^N{SJBxHt~I*RtJ0fr z3&uU^aidqI(^q!>F7&De6uoK=MX#Dp(W{IMy=oFguZmNltMa`kJYTVtu|Y*BiV}U4 z%Zlgil8QS(SG8iUl^+uQVbm7joZ1=1YM&R1RodDsqFBifyx<<O&b+Vd5vz32^=g;c zPbv5;lKa$RwKE{Dx=AMVc%rkve1pg5<|;*@gP$gTXi@yq!Zxl}p5zGk$K@N=V_k!R zy-Avdpzt$<n2Y*d+QIbE>p=SGwH6<}*6#}H_}slf(TI-X)ArT)w0(_ukUooA9aH0) zVU~`<L}ITdls@YjO<!%1AGFea(-HCm&#SRC#^*-~`m$7dQ5z(cr@ko>XCBpU)R;X7 zZS=Y{n}O4iT)J|E*Jl@TT(Cn{>9~1PNZ}hG5vy2!jBtk}H*Vzl!O)!B<%c!q1jw7V zSLb90c24#_AB$4wmF)M_WOJD41JO-pdc>?ozbjOrFUOp4{w#p`@m4{pYk&WbJqb#Y zrC?b5AzlY(H&C6WM-<SGChVl8SvmrJINnH8BIe(QU1}>>S|v!w=o35g7e}yr#b+ZB zg~BR9mwE(k)3fO8QGvm?(e=#9^yXRI3Hjw&s9tpqz355<b>7FWunL&J?l(rU!vV<; zMyzn)<@IZz;eF5bas3s})<RmMU&UVJ8|aU5nl^}TA7K+w+By|qj>N9?Q_QBSg^3(w z-uZRa49#8&@t##D2OnWCB3JqyeVyH}dJB9#@d~}QH4K58-j^T6Hmo#ZYL#6hjuEoX z(7eF2=Ev&k#Z`1gALf!i!Y<?NBlKMK9K1;-WE~W$1}36k&K=+}#is->pS~MvMCI_M zuVB+PqSnuLn5ZZ!-Jgy1+!wSWD7V~DNgt9935S@=vzd=N;G0_5UD2-Q;7Y`ex8dG2 zzes4!nKW4JMa5@Qr1PB0oQbIgPRNomm3i-D7U3}|G0aMzlEzf_9fT?Q0zpe7zP^=s zpb<LH-T69#DH7&XpNd0)(IGCjB~*U=E=4KHiad9b-o3B%MM&<-M4fuSkG>(TDT${K zV#JiLiqtxXOGd*~gclbhrVc^UbLvne0aJ<bF^$t-{%!;z^&E_b-%&*D0D6Nm<`y?j zP1kRF{#80JKl3)Wm50=8up8-sWz!Z~<cB9s5<`Wmd#X`YS`&)*=6+Ds33WwUF!#04 z7ob>YX_ZX6WD0+&!69d>pEJZ-Y0;UgjF;#!Sqr{(M(-hBOT&4YzEoWKI+D`WTAjYU z1-QMjH1tv$iPC+c)=H~St59+QgI)7o$)x)Tsg~Zv?IEYWwv=uY5q|NsWYQ)uRJBV` z`#!4u0BQ#_Z9G=$x~pW;Z@u*JPMs^KWKxxv-aV){w`9`&UV3<z&2?kRqzAq9@O=r_ z$dXBK_;~d2%!;c|$)p{i2W>V+LT}zzNzJ6+;{SAlWl|a{X-Zsym{F2N3pzq2&6lPR z+ZY^GSD1layn$Rg^}1Tj6F>RJT`0nX{`MjsS;U570A@KfgJg=knV24qgYt#Gnhm~X z)z0s{8;S4g)JEd5Ke9#wA~EO>;9GG5y=C>jtP4klr-{(;CSqHiyNM|BG!ZL?(mraI z+C=!C1dp7%z|)i$Sp!kL5?3~d8(*YFK7!QxO*`0K6Tw>0jt?@wR?b?7zr2d)T$p#C zzKYpMXg_#`H;xe!HX~U&Ymr(_r8bn}ub}3dYBd_(#|Ky|eeiZ>+>gZxL!Du9)#X1L zcMjEWu7=r*B*S<}Nurn_0ULZP0tBU2zcUU^j>;cdXz$RwqXjTEmbHijVD|8evAE43 z`osS{oKK^j9tUSA#D$Z|xE9DJec9Ip^V%T#6({s?`hio|S*nj?L%bQHvs6xr!swY4 ze}2#2h1Qgs;)I1_3r6|+vct|n<t<`Qp(;^R96!`aU#JMetSfJUbV$v|n_OYaAq?OD zV8B9%B1Rc{dl!m_IJ>;XsheUDXH4lKCPIeXpnnJE3Ie^e0;#ALdv3r3do*q6kSEg> zM7QOX{isLtK-u4P3l;|phj8mn;R*r|eIxo=N~5B{rvI-!eoWU#*)@MA`eP0-(NElg zz2DSF-%#(_W7GD@LB9<<2~I`h)9AkX5Znl_oO)^-U9iHOB~D*KI!#}TqH`y9DP5go zy^T}rEUbOF)sDZxLc-aig+i7U53ZkAislGa;b(<};|>9Vl!m{@Y7itp-dd$?_V_D! z&D@qyAU8~C<<!C3urYW)uG(BVNDsWr*YhH!$hyQuSGad$R3g$+>Ck`$t1qK9NjeE# zu|W*y53~$(wz#<;uv3i}VooBB6^>t|BO!CuEKS@Yd^I+jcDhPL&s1y_VA3ki!3sX> zfV+1zQ6H#tbOu9HIyTC8kBvT<$DY5%%ezk%;bE1ZY5)1`JdD~wR}fV_G*7MM-j8oW zf#*`J3I5J2M`#`N8<AcWFBZGQ6>0x2o!i|a<}Upz-KjmK-wT@w9W2HY;ob4ioF4sH z40e6BFnm4-?X=$}Fm|n=BY!2rcOE~rr9?E(#TAH?mblN9h=pE!!(2k|>c^`ZyXInh zvk>wT1}VFlY=O?Olfv#e9-KAdv{i40avIbhLbIIv0JzA_>Q$Qdw<hZ8__1aWd&EvB z`XWk3mM}%|t&6KJnMB{M3*O7#t(y}GHUDq>>#bR)DRi8F!s?4tdSf`u(9->9R#{18 z64Z`sq&dOXgcFWH=-hqxSrZx@dXoa<@|x(Rg!dg^VF*%<R}^pzVwGyFRUyyJ#zH?N zFJ%`}hbG6=SfmEjsi2KOwWybjQ8n`5a}A}98h#tqedj0?_bAxgq-pIB?w9<4li4`M z^hU8sIt$ab+9Wv=*L<p$Xe!s9G*+xl7b@2_CzTx$b(L#dO}j!}0p?0ut9gThSMTJ# z)7c5r>jH3@7yK%3c7~oiNQ?o{giK00Df%V_UY3qB{0zj4sx}QfWJ0D`yiGYIn&69< zjgR5+%F-Z3?(>-E=-<)x?8Vb3qHtyf6ID9z_L``grDj(@W6MFEsDmI01o@*04^SFi zzDZKEl$>F%bXJ?*rtdvAg^Hu`zhU@aKm1Q$dfw<POG;>#KGAR9=L$1P$6diD=_6Nw zN&3*`BYh$r+(YmpN7{j5H4tUIe>~lkP%TcHimR^T$f*R8L9dyLcPqqEQ}G<B7(JEv zBSss{y9PByAtHhFt*f``Ej(lD3NpQg_YN?2U+%%gR3_J(%A3VL8~hf(KuAl&(4^e0 z#Z{&fy}1sIAZ13ekA81cf0KUinb6Y1T8IkhW2*Ff{}No}Zwlh}pxXm`16+Z?hl&Bf z4+H~wU>W4AFK%BBkw1S#&EwY|!AU2Uvit(D5@~vr&Igl>W+~}_ASI{NGQ`-VdOT4a z%fN$^Ws~%=nYCp3v$4@nYNQvI$|W3v(V~?O4y#G1aVF9e+QzbGaT55UdJ2hX8Qi6Z zm>?yhua~FJ$wF3;wQ8dDCcU)eb%)9#X%FJQYMT<#Cz+n)9)>3dzjzoD65sSYx}#U8 zL<A=DaT4{$d?jy=S<xHmq<rdO%!TAeCE^=g3HZ$UqWqhOF{|(tx9bU0!h24CQ}HpQ zs|P|nXn}=!X<DNcB!?Gcr-mV&+N*}dkEug=Nr9~x90qHySfhh+TTqvgi7z&7sEraT zk{a~oI1EoJQ}mU8z;(Z}Uqq)Alq9`}*8)a-2?d!{hq=^G_J;_T#G`F786u)=P?RRT zKqdtVRcRlxIOJ5W{ZKa5)465eoYJ))_IHK~S!s1(|F=eVXY~O1j@`6;xRKpUdYw6? zQ5cHY)L5hU;Pv<NzdA#tq?5z;IkiD&T|Pn0Q0V(W+!LqbC{w%+rc*$Mg35<h2P2Rn zpwbdL)zT8@Aag}hx~uOVeAA9gOrW`9dAduB%?+!``m$~G@J=nr<l5@WwRQUPr%*$x zrII$RW-3Y;J0>`lwkCGm>A^)wdbh!WOv1M-LQSQYv@1r#|9<*%T<h1rlbmjrzQgUw z2o2kwoDQ@$sZMEw5pGYs4Td=ld4c#p*s|8EnRDoR^^V>HY8KbAcje@-7)XM?4Bw6e zNf8I2jAWLqt^4WCcZoB^TxD#-WK;ZK{jQ|Cscq0XIr~Ey%!0|de9dNeyC+?2`Vdov z@o^5OVMD7V$DZaPM6}@rkmc!gI;KXg7c+HDMP#Z~kaGRKy>P=3Qm_9_8;vbXlpjl! zzU*&Ile%u6$P(tfu?Z2cATu!5rnh}*2$rk^;8v(g^{Y#YIUpxo$8WT4#6g#DV^YEo z>rc6~SWfEIj3Dy{TRN`>#ow&&B<E%w{4Zqn?r?Zk3O*=-g|PVbHF9WmV}Q;a7H^DS z+u$_NM!tX7700U68F=$ki^8NYe+c`DvSx@O9l2qR?q#9vnc5b)n;~eF&JD)42%pvS zrR&9?C=nZH^8A>nL~IvmEA#e)iek+gV3GFIr+py;cn=RFFs(Q{Qr89r`OX@sd=A8{ z!;~GSDLb#G?5I$w4U}q|MxUpP1aA1IH86TLt-=YHI>s{6F%pjB)<oHHnVUY394TLX zz|%Fpz6!fWdLX1!@AZnZ_-W~ys8s}}N#|<TLjhw)OJHmQChyW~#CX`+V3rQUr~=2& zINFj=;h8R6@ty#VW1%Yp+e3sbJ>)gb=&A_qPIiwK@32bReYD|}^zuV%p-uS=DjTkb z>XoLYisF6^*tm#c|Hhl78eFu_NRk??FuZ^~r!s=40?iVX*$E3wS{2VdZCo^tgprVb zmj;(Gd*vkfI78?qX122F{0o@qcLm1Ouyvl&^KH%$`HM~Ju@u*|@<Xe}B!o<Wc}vl? z^tf|``Hhb4KPE%}j#;NGZ8W-i$xpeR%jqjQoMW_mZj>Ko;14pxq{HuYB~ek)WATWK zAYIV!I;$@)r|U3><u^r~H;t~|Op&Ay?O-kJM7-O%MPwH}qfzl&kzMrUH$2P4b~ike zeFuK-^0i8THf>0Zk6J&)hqZO+@K%wv(bUUiZ!eAQy&QLCFE#qIy@Yugz`Y!YiVH=| zOMF*I{;{Z?mtTNVk`b3_*^lWK-%+2pB;mb1dMCRoH7bgtXPNc8j<V-6<&hKVeU@r5 zkfLDHm%b(~j54gdQK-s~ic)6lR`n4iJ>FB(2vUH4rw*^IR|dFxLPL%Fee%{C1X@8X z+PCpdi1!k}^_p7!u7fV4kTn~RC%0A3)<KzH3;yI(j&nr_Su=1Ozm49&T?JwskW&d0 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%0AHtJ!HG0K`_z@Fig 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�LSqH&yH%L2&43T6oh_A4nW><*@cdYK;T<c5`? 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�jyLH@$t!Jq2NPpE(e)BB+GIjc3o&4qu;sZ1$ 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 cMY^YOovOTn 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°X219UJj<6Q+6M5{6+?_TANJC-WjI>c+5 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⋘)YydslED@Q`<2Iam9pTc&0 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�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%0D;8J^m@bA4QwV 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%0DjI0f>AQ0yJ|*abXA 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%0DIo_$nbp zcfE;zS_xl18{oU2YkQGDB8>=^CspqF;!FA-Gc4ljB=ait?|#)vcJjGT=qytBo7}_% zFZ4TfI{iXuS{)oGy->w=(}5DlDs{>88?VifGFW(H)1cjHEEWD1Bg{T0Q48Sj==BrN ztD9fGk$NWj<Z@`N4XW}S12^2_m3hAqJa+_C3M-i~1I@s-Rm;6tufbMA=n7<p0+Buu z_9*KCmDSj>{O5TlPY5;}IRAvOGg?}0qlr{!cl{4EJ9+CWi~j8D+3)Tu+0PGt>uO<* z4Tv>0AL^&F{P;{!ZUEf;4KsM9FCa$AL->t(n?XM8xD)TQh+!^Q{(i2*kroq}c-V?m z7^n;4g;*~+sx8G^z=5B%T<3hG_u?PWATfV;@{_JEVOW}v0aWv6TFd(aR2?=@PZLDv z`j1?hNl&<sW1Mh$RJDN)MUer7gFm92<K)RS#NJ{6hKg~ItAr<rz051*M_E|c@nnqn z@#(EKU>UIyI|^O%VUn}pZYzUd#;3)kQZpvWK~v8T$FI}PAa!3PVD?8Fl)iEiS5l}} z)W$&I?c_t7a;_VDPB6{FyMqysg6*xw`g5RR55%#WMeB%8MWz8pvttB$D#;^g0F%(% zvl$aW{DS}{5-M^62;E8D!4hN1NeQ`L1bRB~gU;>A-qJ2B<wgeW=>eIndrOooviO#U zfrOccqw1<+GZAhfEjcD;v45Fn%ndG*3{I{uUj}U;<$8ta(}=g^p?F*Zn<%y@+Gghl zy@Anb@Pj$UYi&$upUoi4n$8_$2&Q;H9oFl*$(%Ivad&RD-jJ*A9Ht~e5KnFTH*_t4 z9L1qP(ev*Ws^N~^ESu9=3L~jrO5MD%kH0x~*hRA^vLM9V*!Ckq)2W(ev{I!=la5Ux z#~b%g5Ls<Yf$-_$b}i__N^dh*h152(@*Gw9q}%S*qlm!^xpZbxj6Sn&18NKIZ@riy z?dvX)4f(h*3h<Xg1bAjOU1L|W{gl1PsMj8}SpO;{{VZ3Nhj#+zHns?Nsjvm{`x5w$ z{Cr@QAV;7~$)x5XwJK?_>ZdQ3L{&lZVFQyc;wprt)@WT&P$!ABiecC40o9_WU&LGe zdZfuT8{*C``o6W}zD~;~;9R9Q?I^jXgdfbWmKQvh^i6=n$RbC4o|!+-&fR7O+E8>M zeLS$Wj(`E|zKC!&V`D%@7rWJ~S}(5sT;;Oiags0w1x2<#yDyG~vE?fLS~6RW2F8#p zYpK<E0Kv@C?$`X}AH8zmJc#;=@KJgmtfyA-K-)+YhoYR4nW%R~VNc$YTYIKJfl$wb zKej*W!!|YXwBb8MohkPutDJ_Fo=uX}8_{Sz{)hkm*Id4M_1{mL=ARx5`+w47IT)Il z{NFgcMq|rv``;FfHFcSla0$pBiC~@zUE;tawjK^xqcYJ0ETA6go5lo+ltV5ym|yR3 z^vGg0>t<23Fv8v0?b&ICN|UXC&a3uy0Q*igs`j^|Z%fa|m*?X%{Jn31ee_*wpgy;# zwxCrIIyc=J2p<#<IV8Q2&uEZ)b<5?EN>pF%CO~#(=?`}!^l_I#sEh9}aI#gI#j8x+ zZC4wv-@w0JS{oZYlGXElZaX<fP6!`KxVaD7blb6E3x4F{Ewk74hC&^%yj$lB^#|K< z;i9f*vRrGyB%bnX)-L<lv7cVHfHXD!bR7z_#vcz<9%Kyc2fw<yq>_!5mV3d$b3)oq zSLZCUdlDa*AF;I<3LCe>So3#KsCp!3Qx~+h|NKr|cW-YPT#-bLfE>P4mz}bZCiD>9 z(~ua%X_nOcox2SzbF|CW9T&W@IKjZ0IOW{}a}u%@YC=`57;t@_j6g0fup8BRwFm>0 z;HXCBt;7WHX3<@>+9jJ|0HV^?YLwaW5If#F-`qv+4m0!gHY3!ttM?gIv_8TBv?Bd! zn<F;CV+7os{B2-oc!Odalxx(IHsy?ZHPRr)MZ3j+ud-{aXj5EDn%VBdR8cNAiXP*m z)asXqnzrPs@^ss5aRqZXLjmFA)N}xQ3cA^1f2P;0u#NS{bDk&@2SzPts3@fA+Nq@T zTzRXgnUH&c7CC$<A%#PxyuaiU_BWh?Zs}jaa}(FYL9gEt=Cu6dBl)4`fBH>r6Q!?+ zl>8AL>@d?2?3rvDwrEYKpZc+x8HP1C&vN>Y-;)a}JY$R{>zl)drRf5s%Q{>?sgr79 zmABk@M+1vu)2*sH$>Tn8CVYl2g^-!Ui;fnqD%=6V-eUZWh<V%3QBl+1B!Hv;XdaMs zUhm0o(I<9(m_LWgTxs`r={o=M-l7@@tXa)v!cX6nWLy}_(CWWpyaC@rEBGrZpbyKO zDpp6lu&e3ighks>ydgl_SLQmW!|5H0grYACbb7%s<%66REXp0VA|A{lHnm1XXF$UR zZM*6K(M5R&KGgyR#+nB<mu{C<<nIZg!feO^J4Nb)Oq|^L_>Yg6I#X|2FA2>4;g9$w z8Xdb>Bq{J?;It^gWioEem<GT>`DQ}nU8B_b=RMe;N2FI$+GT$GV?@uB(wL%|aw*4T zNs{>8bA&X6?Gj>t;HU5c$o3T$$pUZPY?3pKRbw4Rh?Y8GH2Gj<VCcqQ5vV@_e!cN3 zyx>jn+ItBLY`ws+=*D~OxlB%?Z!7JtzIBDZkH&z$O-hT4rFM6=Ju-u|p;mVvg_c}j zP|CGF5>~M|ZQ6S*)&MtYi(|P2g*ZXkWN~h5dFrD+ppwVG<d7f={ei9n(xR22?8uTP zqpeVD`e?RxEy(+%dpe&<y1jdu%leQSkR<kJ@do~@{%Tc)(VL?Q3?EeXjsgLnlM778 zCSyW-H`oHTd&rM>O$g{c=}8V2y6Mh_yDuBvi{<6vV4?9XfZB>q1K(jXF!Ms=ktD#R z@Y&&k_1BhoK|R4l-{e9MO5yU}Yk#HT#B4pWBz{mCACQ~`f5P^~x)zK66QHXTQ`>=a zP)+_8*d*&o9?XF$O5cLr#8c-KeBo53+32~Q$79`lzq9l(>A{h)7f0{XeMH**MN0pf zC>pE+V=Y}84?1pI!$RZhz6@g~x~Ai(T5rpJ7U1H7g2oOvB`WmF$`;N*oh_O2W!wsM z+RT|i8{f}{*X_m6@nbFO3*IvL$`+6J@Y^8Z(&s><ohJ7M?^WpM#7kabB6yWKb$Kjn zv?kGI`waPcL+78(gZEO=<)!PQWXknPplur66aI+V#sQj*`VjmXZRO*x#wzMEO1=0` zTKFnh>+oVxiE#UXMZsRT^crOoCU=f;5OL&ttx9ST+c=c(l(e+Sej&{g(7tuIp;V`( zMPLu4q|;FG@8$A?o#G*{_0D9d+1h=@sLw<Fa@OaY;Qp)H>Xmq3;1}@!Yr=sWMGbQP zw<C+^9|-wBu0(P$H2-h>F(+}rE|>u(?9LM^T32Ax1E5&W+B=WTm0O$=BMJ;MYDcwu zCf8KHH6G!-@}?0d-pK#uZm+nt6x@}j;0D|nv$gBHrwdtI)hW)0DW#k|Y^-2qCg?&D zLeVV>LQf@_%{5f>U%W~p=@x|`)XIj20)w2?6>~qn^k)#Isve9hLt1Km<y8S|ro00H z-0c1yVouB$Azc&>E*7KNYZU>yu?4mUbSct->o1J~zy3x)Vu6+A`ErD+A8lhhPt5Q- zc->D|r!O(dK5`i3HpOt|RXtFVZ@SOLrJoZO?`N8N+b+;PTrVGZRi++l{+}YgdAcUe z<E9k?(>G$J(g_B&Qkh1c=G-i^L;o$;(!u-6xY5~T9$fKzu3fp~veJ(371Ww1$`)$^ zQX95-iH#~X=?o9&KCLHv+BgFryB;s`2?-i$(<<uu$b<_9r%wmiRAW2jA<NUFv{ZRU z&bug&>v*=hmza&t(9njO)8{i7%hX8w|M#Rg0QmH~{(09#{^6?s%f)SD>f-WW(^ZPv zmfb(K<-dh2Fw5vviplFc*JxlUF6`^2f`M)gS!qNN5F4d5t(_i{Tn*aKHGA{$*J&;s z#dfWU`Cv$r$I}NH+{~(J3Z-P=ns!#E0{*IIi5n|6+O)E|=$#u>%c=BBrgYoA8}=qG zc5HnSb=r!wq9E>DvWED5ejnp?{$C#7OE+`u$Yn_MSA}ZZ6p=)wiao8-?sm3J2^$Ko ze1KmGA7XY!KNOX!HAbXqo;Hi3gjng^pNttYsiKvvK2nG?5M{U^1K#WHsO8KO_{y5` zbcl<LP%;uuq)hudYKsoD%@mE}(bL+{cN6{Ini&w8{_yYx+!H_5TGKgLBzk@DeQ07> ziml`-TTX<_+d!cXZmbRx;8h_Tl~(l=av$b9+}Yvv{;7YH_#^6pos{PXV!cxlzyZBP z1+#q7qv&6bP(g{^Y{9k<KCKUl3+!07^$t@tyJQm5R-~?jEZX1RPlpbWHazMh6sbgz z-c-2ObxN^`uc#)1_@NaW{0Ij7eCFX*oH}NqznCX-Hlq|dezw%GGj5UuGzahm);Y(; zMl(;Gk@*D-u0y5%U?63{-!%M0F0DjWeOR88e=dnJ4#E}{@sj=@y3Q#^v|w4oW81cE z+qP}nwr%dQZQIr!+qOM(_sM-bxi?)8olg3xlU}Q;|Gz%AD8#ezQdoRwf&k=<-q-*o z!U7zFXjV$nqn)*|w5sQz9FFok02&?{w?)to?Di?rf1H(0x)J4EqZbvTsUxMRNdc{z zd?cfifmR9|nc^qk%_fb^o8~WJlEvut?0Q-R8{esJ{WE6_YI`bNpYyLs+(U2|v|>K6 zlorcPlbB=mm5u(!azwvdIV3F86(r=%iV<C;K6Ptem~>#XBA=50&KM*Oc+{*qTe$LO zE$H?D>I_}ImjwP~dieetMhEL6TWq4lrMM#7i(H+#H9ZrxMTixt2&lYhBkEGwWNXv* z9HMrqp<>P9+S9*(?VgfYZdixonqlRt2Go_ZQM~J>=4zGAM02tV49B0r%%dkjS+oo; z$mAS%QS4`@vzeVwrW5qN8|fp2QT3vlg*eT60Lwy<2H!cS;YbxDOlBp_>ZQXshQ`w= z_BCKkXb4dp?f9I(;NtRy2;=e7S&XloxST;Eog-MSG#7pmYHRnp?j$ZzcJc#?PV<PF zBED~~xy?1AWqr1dVR)~AbS9&cofmj~%u{-<%2UG-zvx~j9I0@gqtG0L(jjK6no<4; zCkga>W*)G?pR^|0sC3dTa;mVBjTAX}UW!L^yVZ4RkKN@uTi4Ifef^_mH$8IhORa93 zbh+gzK0ZLpf_?)9pk4lD>wm!$%cG~nk+({6dnG&gEZCU=Pd&Ru%%#~O2UE1&Ah(_6 z#<mjo<1>RX4W`{6EqKj+AFBW3Y<NoGiZK{riyZ%bf$77I(3<SH+bZOS==mJXxfXH5 zr6|ujN;!~sG<if!B?6v#wTBaTBwhlYvQxzJ=p%3FrDK~i*jlpTw(+7QGr&R5!p;gi z%@afvWa)Ds(|c{$>OZ45-^R)<%9abmR&@s|!QX*+Yv5KziN+y^G1cy={nqAKei~p# ztMJ*H(F4+)UZ}xJIUD5v`_3Jq1J%_fDfC+u=6#U2&$t0VNq0v!;A3wE#bnu-7W)0{ zQRMJeUn+xP^!s1HzA23YcJY4?o2k_Q@7KxB)aKufcdezLyxEHAzgi}+9-hQjGmOV? zDJ5^S%POh8iFwMD6X!jofaD@1lZb;fxm~h&yQBL>SWn<l*5y?SPm522q(S3`-lt2G zw)K1+II$_mqAr8e*_wWJaLfNE-M)|8|1<q|*FU=nJ_p5Cc{zFn&N5w<wiZkJ6AORG zvBSQt)O&kpZ8jd?&&%J<FRh9FI`(|~t`pCOpD%^gUS^km>_+Wj=jBI}zZaQ(PzlF_ z7026-a_4*u*xqDTMt}X$Z0gfW_i-TRqVjr0`4;>;V>6o>5C%+P;V=9bl&01P{&UfF zO4~C<fBaj*Wnc7W95&X3{pbR^Ltd4>XgvbA)$Vf8fN2acQ*+#>_P^cOJqW_Hd~sAn z@}FOc@MBtZXwkB|u)r3pcU;f08Rs2IB%U)@DzjQLH`EiYzOKCWQ8k-*8=A{)V`ft! zI1V8o%rH}-2Tp#<xSd(nX&yaZ&y!BNZao$zm825A!K!x7FKw-LZGF;>GV%V~`mlEO z<YH1SQ))C+wp~A=_D4=f0rk>==21VeM)jB`%Qn?|KIB+@{lMm~n|@~3a@*Lvi-FFq z7w>k@&daV0(e0FhhjY?yqwc5~sA8=gV}A`cWNAW0Dr(Yw8l`<Rn^u|wWm-2A>__I8 zmubNl*{X%P8+hvkkhu0gTlWWKO5FOEoOzfMt<>*CVI4tCGi~zYUn7mVaaQ4%3~>+@ zjaOSA>{zeP{^6j(x2t4Y4oe0H|5?}vZ)IShIM=$?)qPt63xWg1H@^h6Ts1c9tru*x z??STlRkO9hQosW%^`Se=<v*+f(#Z2^xIhrk3;De|AF%5u*@&iJxkW1&%3<AUt?H|< zI<~G77vS<5HiLe(Y>x3V1hoBW!61;8;I*9DB6bZBmnba3E$w@RIdCVHGLj?Q0Eb-D z?-6PBt%QNjj62K+oLK7TBX5&A3VB@dWc}EsO+nx*5X{X4Dk%e7ko=(}zUtx{eOr%{ zM3Q*X)xACc;)FdRCa>{M%rLFT(tT&r^gxc&A^hd+dszZlu&S8=@jZ=w07<!0kZf=q z<_K5)S7lghG`J3G`B7Gd2a~QZILm+&OiL}X2%(u;8i-l#;z2rBrzt4loA|cqRZ{H6 zNRDW@m6RhcY)XpOW`LW?8#6kcl-CNF2TSxpDv&#n8Cr*&mJNBxIQ)>oyj*KAT7%9x z>*9RgPMzX$pgN}VT>88zRjECg2oA`LC~EM-PaQb7%F+R}{BWJkHGfAbEJ}h>%_1<K z)1y404k|852S&lmqFB%p1oyLS0&^)?Ksb-8D_9jBi}^HQEF_RKXze5dkQT6Mt2G*i zAZ5swG)zE@QA=!|?}v!p58V&i3qra`(6r#0r$au60tVi&3|2Nw=f~g{D;%Wlh{M<| z*LlvLgI^0E1bJaV=Vg1o``x?0+q>sS!bfr)mC%XkACeua#F}2|pM9y4%QNxc8gE_g z*y%R}U!+qx(}iJY0B7{P!Vbw9A_&!!+c0Fb2-cbt`WEAQ5<=hMC2b6#xRUMOtS^WM ztO%4cv=6e=xfS{jgW}Xc`2i+X&Y*CSlj355gZdWlnm#~3;m0sX@4~pH2<$Vd9m65- zJO5}s?CExmjA)dJLX;2;^*AgMhB0G51gnp#&fuB>=8#0!8@Grge|=DR&WqQ*cr!sZ z$`FHjk-1Pjp=!%Ya8~%6Y~5kmwV9G+%G^lTu|dKk1ODwWia66=*Fm<XwU1vc{j)&A zDXqc8ExMSt>bsoTY@&wHVpLMSBFr@f)t|sO2c)mecNQUZJDkDPb+bY#r~kodGUj_7 z2vC10H~5b`_c~y}1F#znLgbDElQMij_}2@c<Sc<(0TF@~Iz}K&(*~i_)8h<TlAi7J zFHR9TIkI<eSj^zkI+C{<mQHp>59!Xzq55_=Zn?{P!uny_SZNhoo-)7sBu{3KPnywm zjC^o6JO1f7f|9Cpb|+YPhJ@XfS<6Iz&6=)bMUl@%qog>lNy5kP`IbT6#(n(+sp<lT zmuI=%KRM`uDXvO9YOtPWo=}B1f-IL|inlQH98jXpBni4&4kIG|;m}$>>D@Nd^pY%) zJsja#D=@$_MMo-wI&yqhBRN8ls_hZf(vsX-Bh!dUavWr}S9Z7Jvf5~2n&%5Mw>iKt z-9y0uoKn6$BfZsI%Sq%hiOT>eVUen~K1UjnGR<W<G9rog;km>kBzUG2oJC*@Ctn0! zsY<M}mNMlq!uc|2nM)BU&Glx3AvluiBe86()H>DBVsNGFIWrRBPq~oHFvMi(74MpS z`mbyTd<~DL>Q=;pKw^9C2BEtx8|ogp0F&OA*-&@^4nenrx1I6a=qptP^pWP7dZjcm zNoQ`Q{MYDWMRHexW-G+|2azl%bg+Y3f3C`74nD{C6o^yHz#w2EK0%c1tG@n@Pb<4? zxrfj&GhWgRJS(=Apf&wIsAH={l83*IG`Sdqcy~MIywFAWE$ySaegnFGzu7Pq$K3ad zM?j`B(E~H9;LC2~D*_;q-f!Rl9#~Nq>}yGC>pC-;m>}fPFm?-fYO@}x8y~EVi!62U z1NN~out>)Vg0`oWyRUlRdbF4JD7!y)onDCGFtxj^yEk^iH1zIy)O%*qx(jqR9!?kS zuoA62*DQYVVunb>&^^LLb~BeT>vUc00GP44BQ1#`;LO+kQe((t(;)L?q_i+?Ea$>7 zQ!)u;>KrCd>CV}fzG+vwKqq?E1Z`u^68e5cS)d(z6ELoBECdDAzh3Q>Z)lS}UVyYr zVrVsLb$+@*Wq?YRCrjeciOjFoB)N?oFa~<h5CD&k0#O}1f;Q5)l82b}SmkrZZfkV! zWw`4_cfV36V8TYFAa23wZ)c`3$Qk7-s0`(+hw-4^l7P<C9zCR3dwBGlbl)sNhfLYX z7Fg<%F`3dPl>02X>zVp;U4vZTDL}3O@5Z>!uGLp~$pG9f<pJhXzM}+o-5*)OLA1{< zr3}&G#p&yiL7bT^UP2RSF3q9yYcZS+gD5z^sm#N7`+Iz4l>3-4M}i>mB#z+?`D=x3 z<rCVd4bT+kMTE2@e_GW?+WBWAS87tO_Y?h4HV)|2iJIYGOS_=PJqq}=(&^8Yvx;uj z%;Lm7S9sYtXEyN&`4PVj;V@Q8uiC8jtJ#iyF;CM=N83^pLF%V77XWXj2M&vY(yH7u zL9Zv+u6*OcN%BftXh=s%x$+X_O+5+sNnIBuRU<kTcy@=WA2JPiPT5Q2&9@X;MjEND zab%bdt9b-VK^9e(^yJBJwRGpAF<In=V_TA&%G&HMF!l*4S<f>lMau0lry48Z0CZf_ zXs3ipvl?a8V!m@-eO+D4)e!Oq()u3s9D6T?#d`*^5yn`GM}x`m({g>^&S&)JoIkx* ziKF~4E7o107T#nJ%1!PZjqWDfB<8u(KX8rj>{=vhUxl2$)NFL|gIDK55gDEm4>~;E zEmc_YrOb{hrqF)>t4lD$Bg~QQKW8X;-2Wr1b8z~XaQ}yZdd+K{yw#exd#c{Q1nzvW zAX2x*nH_U!qUo5j)+nV!<=V1d$jSUiR|>~T$f-HY=GwpSX0+;lkB3AvbuyE#jza+0 z0wC6}05I)RcJalim$s_6x=WGLpFXRS3e9(Yt=M$uG@9zRbC5(Z5LVzkFLuHrT2?#N z`MYV;wMccdT6CxNU9Mc^gK$yPnd?=usphjVC!)_)^i#Lv?06I26x@EwDAm5oPjX`F zAa7G>Wyk@AZv(l3D%uM2H%-?}$&_;$QMdHU1J7WAU3PhWt5T;N>~njR?b*0@J>~g0 zy4ADK8x|7`1c_CK3+b-=XBS<*iQM1s<EU;f*Pp+jc<foOmuI4iDo;K1>pAC&5;j08 zV5d?&`3=UQ+0-KD?79p#&Qfnezpmb*hvJDI^vSDhf%I1m_|1d`PjZR<*et7u`r@BY ze$&a3b;NU-*Ao(we*bl<5Ew+fsCPR@k<`oUvlrEqoXKZmWyVPxl^AHe_90G*n-Tdg zu8tBsJEHgx`Jv*ypQ4S|eGHY)Mysqh*)VQ{<>QpAAK3*M#HsE=Sn3aDU8j(%Q6*w0 z&4&#j%8H7WS*hkS3wBQIEvAfj9W(p!LCx&&s|}n=KGWm-;6HODrMbW{ebgip%+Gjb zp_-@q@v$oC@6ex2E(4rjn`G3z>}#{JfFC0NAB|r6%g7(p=5D$NyG^t(vg0{d#Y?~E z5$}KAvwdF{Ds?)+NlO*nMBtMo`oxOqj5o-nBd+Y(kI?8rvoF)l=VPl^;{|<I>Fh7$ z>9b0!+HaS2Wl38m)NvP(KLREtf8MJz`vO(}4Dy?|=Z+rSumh3Hx=~0bk*kBwo=Y9I zB=jF(Nw_SadFs=QxUTC%N(v_-lqrNwwu<)ITFaSu_P9uRLupbr0^9i6C<qr4Q%v~i zY&`|>$p6X08}jDdeHq~d&?7+y`@W6VV`OZw(z*&67Ih;PNL#JQYJ)hSsJ}s}HyC~g z#a0|5V%B*^a$KMPaRk-dIGv|l=$D`JGptzgh084LqhbLj7i3J;*?06_&&ga5OB1Qg z3I;TOKKoE@NMF)aOF}@d+q4aYc#j2qMcb^^kRyp#AZXV!kQxD!Kq}WWD)|um-2pW7 z_LTJLM)L*ES6wuQmqt<QH{=d%p;#mL>}wY?FgO-sVx-)#Y8WqAsMQ2^RJIkcG+o`d z25y3+sX>$1y9#)))^S3m-NKPpUJe^;v<wr>GGh_f1f!2O)9jxr1dIaCO|FBsS*g|b z$;b#nf=D+-1{Z?7leo#)K{5BM{6ssRdN<uCuEKbv(6(E>)D1hM^-kgMZ_Y$wXu-5Z z>L)|aS7{nd2wR3>&WI<_3R@zW!4qz5&6Ss`(7z#7vB)ch8GZCrMkicm04RBu2^2S! znxve_Jb5ZpC-Ef*;^gRwvyF8RnFQ0+qo<w^j~K7B)TE??_6_V!Wo6LJAEnVADmTkK zLxYt7QIw%CJ2+lM=a1-;;0behboZ3(oKImjQwZ=290X+*ni1Vjy^XSBXS9YPQ*!p$ zyO?QhTMqhZ9iX($zCKQ^@d~EGr-c`lWkCWqMnx!LcXD&A#4<B%30t>wTW`E#Q|+Cq z%{r;kslI)%(*C2_qab;}T(p!!E_FnPZq-U^Py}+8DWRj81)!m%w;~HIEwQrBmd#u! zT*ET)gd}3SZJxgAr0K#K;QE8dnl<$kwGZ4qKLS#~m0<dOOju5uu)b3n^@>eU3xiP^ z4@YaGq%&7g1DUEw^cYtjZ3KxTR19I`y@-}S8ziRI8f}|uy=)afSQM5~p;>v--8t)? zbGDO)9oWfOp3W+ek5%y8x`G0PxQ%4wq(+)q0C4l53OK0Nl$X*-Jh_cQoiC#!rJ+m! zZqeZ^MVLmlo6oT5x7ZLu>?&R&i~bH)8OXD=z6{?xif#ZpQ0U6FJkVL02IL@q8Goei zNL$(1>f<j@9$a9`6Y6Rl^;~k8nAZ*1d7;~J^1~EOA+a5tQZ*AFTv$xr6F_+DZfIRP zBFQ36Kb`3p8O#}NfASTDULWmfrXMbN9Q9{?)lfkzjrkD`yjj}$-`a(ZU}V;|KZ048 zFs~b6g8a-~d^XHd8lkI+NFZ}cF&%)+5sjqJ@o`+gpS^-#3TmaE0=ybC;QnIp^fu5B zHv+gVy{krJhQ!6x-??5L<l^Qg1)6j-+G*$+lK{jcUdpJ|erPI*s&*3vX&6u`uH4oT z#jLL)xfr8E<?^&!t)fhbid=SEF{CpYN5Q3g^1qepw~Oab2SzAX0<BI0WoXnGa4C7V zJ7^>#3+6!8MIL!YRF!{lhgk)rf-5Cw%(b2)D9d1c1vbV!1k!PoupCv&J1eF9Q6s}e z3Q>_51m}+GBYmK)h}V#zi<R~agWAhmtp$KpTfwP}6}x1lQ&cv83l$-PW<N!y<ceh5 z4o>qB6Z8DV?x8QL+pDuY^)TsbadmJ{h>MrUo{9rMD#rSAx}nP)bI4ax&R42W*H+>l zr>#Z-T`s+~ia-cBob~{z%dy_jIYm!b<c933+$2PcHC(IBAjL*=66(FJTb@XjGMLLo z@gHB0y}Hn-@D)_kqybC6y8}dFCZBxZVk)K`Tgk=n2cBW0*Ui|TmkbMr9*rg6F+xy^ zg-Y$)o(F|Nu>fc@mcD^&{{F?VQ!y(dRd*Q=Ti6<TX@fp$*PVZFp=qmA?OHK<l*}}_ z+BD?nNUuV(36Jv{aHwm$<cxq8&^s13M4(-5=W|Zch&IZl-;o>c=~>(DbDwL0F8bNZ zFn`j3MH-k77RUy*T6ZTwt09Vl*<`e-mOX5s2)&pBGC)6i>X!|!po%da$Q6l+v`wvQ zoY$i#O;E6BhGWnQp{}C0uGaZ>f`7r0v5%Eu9`RKK6;SjbBT?oQhu_dO-O&?KU5luV zn!|%-s>Sqc-;omWk#J|N8EawpiCZ#++*E$n@R6H0WqnKhs0QPgDjPWk>0!gUb%fx^ zAC3#JkVU%lbAnc-pSLk@)|i5YbY?jJqMEneSE^PT%oPsXvS3_(zjQdEvDCzXm_j2^ zdz7I<GLQ?7gZ=}R5{DO>c)+n6<n~UU!__F1$<r+{MaJwZFlH{Ozhc_vpuQcJmaP~y zPlLe;P^@PToiR%z-BqY18#0!;x7%a@WcW^ar?QM+0&?vg|LI~h7YJIWF+RQD+gB@p zpLn@Qs`*onN%G4$GqZ*?hbtonNxDaGHEn^3b&-}70a7K|)Cxmlj^=Wr_?|(l-?K%- zp)Gg{$@Zabl~;|^X2g(uM5N@CYmXHd%Xz0^Wa-P9{~6pcX&W?qlPC%c*s@Nhq{Gzi z(A=fkxh})2cE10aThT{7{!E!RMm59!ujaE~lByjLhjwb4f1`YiQs92v7o`yDW4Bai z2fQew&DI#8eN3#qi`@}RspQU|yjtm$VtQay+dU|bC3LfMhWB`SBz8g8W%u|^w&1SB zi@qw`g#r;9woq3(|1ZQdh0c&*yzi{I$*sYw@j!*VMqE2W-~?F%?h5yup)HYE6_0FH zKV<gs$fZ=iCY&+O|D`~ty*53Zt2Y1q7GnXZ4Pjc+E|HcQ`+SA6Mll66)R&FmB|}!~ z<-Tt+sn;JKG09z>JZKjk-FNRZB%{x>4vSa@__98Dqt@>2n#FdPN=>FbyIL9&jF@U+ z88Md<&~IYXn<tHdzIIIRzIec%^N%NV!G=!>HqREj*~4e_YHUVW$r}}<H!{hQ*)t=T z(}=AFJ|-7nsmDx?qn0C}+)*+p3EB@5RtpAphO}8`S$>7%)R@f9mSdo%LB^xw<RD5k zUZWJ`AW74%W|pI%hA~c~<0Ez99-A_X(Ywr3<z|0uIK&CR<u$<o%^T>P4l!4!Ynv>o z%%~fV((f#)hLH-Ft{g-F2%m#lOvM$PoLo_zaHx#-nL~${iX*ywM7E4#h|Kv0!eA*r zhqLby<S@y0io+fL5;>M_Z<;rjMzBBs>>y){FyWb@1hb2-ryaYhcLH06Z`#V&G+^qd zwogCjSCX_PFv8}bPDGE`inW}Xk%2KCSN%;$mb;5ttlbTK&Kl1J(RSQ-5TS^?Z%hH~ z62pbCOgo*0`{;4V5GM9X-BOUiDO<a*i9A*qC7_TS@d8GRElwa|VN#_}Cmn@8RqePl z7)|uEb`P5BQwS8~FUDK$)=n{9SxP-ka1f9E&MHLB;!F+uhC@rd0Te}<h%w0rN3);G zL9pKH7V(bUKPc=T!VnqoX=VzP&z@UVVYw_jw5Gv(d8}{wH4zI_F3o7`b13*x8RIRJ zATnlREauZ~0?c#EuDp^AmortN%7)SEcqO8xCWp<QSt4RKjNlAxcV?mBa!EUj5aba| zz^cgx-84SC23fv?F2wH0mb(}Ih>v1a%2?Z}L(+SVt@NoZ>Qj3!wBOi9zrGgP+G1c! z+=^STOSbA@6%FtDoU<L^hT@-#Kf}G@C@(b>Go%9_ivKIp>IetT*nx4VpUEIMdLP&b zs!>NzCd9_l<%kU@20n!cfQtpo<!K~NT(HZu_A$P$H^Jm)q!49rMmrOM8|>>~4D~$T z>-+vTv8ZnDk|jK1eMU@h8nQ?dH3xbz#U7%vDBD!_+|@Y3u|=gnm)_ArvRIs;c@Asj zD$Q)1gaFB@7ILcTQ%;seYaIl${}zc8q;~3V1W`c~mmtS<Hvcu6#aO0Kf@gT$sP^UT zTxGpz9uXG`?s}yU8qsC}m^{I4fGmq%*%+Ocz7*&`OV1I+{|A6SAnQ6hI1&mTYF`<I z^ozFMKZ;V#j@FP3Vn}>IHhtKYdouUy>FfE>d@iro2b6`ns1tQveix1V=(Ff*Y>Zxy z$NQ%TI(rgXFQ%Uy(C;NAL+{2kU9uz8`mA}8;L<0YP^Xcj-1jl(a{47sL&FU1YD%dc z`7$<DmjF!JuU7~cV}Jca0Yx>5LRX@)I1(C*^OH&KB5s9b*^(`j73({m_mj*WDi@gB zh#Q#oD`f!;C3L4vlrdDW%yHt=OqZ^Hd#iJ&baVtXSyCexvO=<ApinnOL#&C?s-v?f zMs(RllqLMhfgTp#wj&OOvWp8B9et`XaWnC2L{*b^v(W4YN+;(OGEUX0phvKDQMX}2 z8v|!kFoDXd;DeK)Ky7AZC{%N|8+1jG4Fa)(Z^qs3I4&#aRyC#}K~$oo@J#p*3;$Oj z3I{VOqU;vnTDm<KqRL#UTR9N4k*|3wT^+a=htyCw4Ewc)c3M6Vg>ef-CBu|(S)(z2 zMyOZS;vI(Z+wIsZ3@Ox6zT4!V-L8xW%D6F8iY|*JkdC3Q6pqIwD7s}}V3dKRfOrAf zSe&?I8X;?2gr!y?iclA1KqG73`i)mPwg#ZC6twY~)W~ACXG)s2_6W=Hz|R=Qhe=eZ zH;jP*KL2AHom8sXh&LnETIn9jsQ50%D0q7fagUxs78k0GSOTj+H^O2}4ne~vs$Jld zP1G-Lx7WOG+d2dL$O4o<tb^n{_9ESQ!v2A6Q!@sxP)Hw;H5Fch0EZJrmq;;;_OD@0 ze|pa!Cp*>OF*HVSG0~9z5Nd~m>H6eA^<`x`H&X_J-{6CPVe>5lqH+d~=oP}EC;CAc z3{pvjM1cA^ZGww5It`FaJjp6kmF_=LF=)#&ZpPvG?oJtYLwKR@UwS9Msm<>Bg*^Lm zWGL6;vRR%7#IaJ@hUZKTlNUQ&w&HGir>sX!R|uKMUtN0Bx!!K4`-fJzY#{}94$OG? z#BJvnCw#q)ZvVK308`>gKTFu6l<TqCdTI&@lQ{&zwt154gUif$z5wUi3^e3ToP$v= zvBmO?GxRz@nYadLOORcEK*^_`|KW$aHaqWTEq;NcxYP;Mf1?IpEK@UOD@-gwpR(XV z!M5HIP&38Rvym{xLC1l!`{xz7pmzKfDyU76z>JLvwo}4HSRi)yq-wduZ_&%kR}Y7y z<aApIg#tCQVxGZU%yyL(L365Gvl=A;hlY`C>e5%?ab%HX{Z4$2W*LMkLc2&I(XXmP zk%l}V1zLUr6>WX9YRNsSzm9N|J^uw(+bOm9%-J`bK4ZFa5@=16dGlmTFB+}%-0Omq zCRa(fSU%D)wXM^0NJ0XLMa-&&bI|MsCNwK*IUfWEcNGV}+b$C8YG!5v3t~r#d$33* zmQbw}9Hy)U$|^cMil@M5;^Tv{<dd`ESyocPnMxL|{Vt5$!c-p#yzUQ*&7n^p7ma0| zpEpcF5_{32lQlP~uxH)`^#=g^v;)^hZ=-CGg5h-R3THYnOXZ6PYmw$vb$Sc9+#jA* zn}}6juKBinGD|g^$=V#>NxmMM?uE1E57l}G_#0BR`a!aT#+@B`n0k$EMsN!9^C`<R zE|9xrucj${rf?b86BgaCduo!xc|rP671<Iy-9o1)C|`VuNDj?Q_%obX5DQUBV7FKf zh?8MI@onx^JVZ%?8=$bs-ScxN_CgrRISrFU2m&_(^NT?_$K6TKqN@}R%U^GqXs7*P z_>!ajiRfb8Y5bO(EuT}MljS;!9Ivw1qE5hKjhYvtdZBoC9cwsyk@^OkFq^KwL*|X9 zwa2_P7iqgyzdsKyKGybQSJN{dx?SSfkG|0{tI++Y_*|jHTU&)YCZeeg7a)1;OLozf zG&ib@(|)*cMxD1xC=7K9x+CH+xtZ`Ub=Sw#PK686iFSvxeXYrWuYC*v&82PI+JR#? zwXZC=s(UEAz!C*_E34j!k~D0^2CP06@nNdIJ}ONsMpzI3M-m)2*G<H@HCIf<Rc@b? zoJBde&shrC>*M5;$c~}InZp(Cy=@?_zj`ybH)vRUw1r->YJ4=d7D4ej8=VK>P*-|V zBJr&?xY{Q+#~5||ZKWrR=0^~#YrA@q!R|?bQQ4a?#{Qe;+)U*f*U6n<&HUnGl<r@3 z&iTl$LJqQ@Z*pPj$3Yj*_g^NHOw+ue>4wDVu`mXF3$EUcNR|lBeA3dmUtq{Juw_n> zW0oOkucJ(mhZ2t;<Qae%h6`*r`oxo=UVSk8_k&(g*X4A+CwLwtty{owAb2a+-cA09 zq4`A~m;OXMrf56;l1JTmVi?qzF+og~Q3rX+_l<4}$=!5vOCT|8Gw7XI)M4<tnF7L$ z0*4U&Q**S3gXou+Ds1I_z0Aw{ZHa3ywym9a8T7h;0Ci<YMVa3Sdv{2st^B+|y|I8> z_CU!hXsH7aaAA1giAhuUFm`6ETG7q+zrpp_wM?L4;=s0nlp69#AS~M)BHGp2?psxs z8I7gGkr2A@$ORr1Q3fv=<a`3&{&Q7rI&_nvom7I%4L|U_Dx^kgblZgV>Rj+ou6#(d z7Q2DVoolVQvtx~C1r<h^i**PCOLkl{N#`zEF}f@a(?D&7&$b@5fWp}R&AZ}pXl%}$ z<@8fDSpSZP)Z~Jlhi7fcr1i(bm>ql|DT{pyBL<iD_BmhEsDJ<i+sSbs4(UEB&Ca<V zbri>aAP8K7w*AnK>j`;q5R`+d6A$hdC=jv=hQV2xItB#=hX&9UyFZhr(?1y!l;kYr z-uUmE#L%!25@@ln#7+hHn9GHLK40~5V63P%AwceK7eqjqJA^Ewv>PD1&api-6n_3C z*}H2sqZl}qm7lH->v3>+eB<Tu#f<B7>DfK_7kpp#gKV2l&Fb@~?EBd(eAt0_xatOn z)bwdYd~9d}IACVwsh;Gehs3=icBUS*I0OtuJ3A3EmKCEz=h6@!Jny*mt4-*+pCyW# z4EyT69as&FU7d47k`&#Q^oMjO<qV^mj!z;TiAo_I^;0DPnMmAMt-P^LJmEzJ=0hhz zXR~~Ka;FQ#4g=@ZR`Rw*pO|K@Bkk;6nlgWSwS>+ZmJOA%7^4!za6dtxN)c0cb1L~F zx@NxQ)>`du5WpuHX7As9;L6jMGKFsrrEiDjvKJeA^0pn(zD=To%OiManwcbfIKZmN zob&YWy(0~wSPTA)`=*&B-!;FhEnNP9+2LF!zr#5`zk9(3Je`tJjbB>2E5Ls#FgPXa z2d7r&A@O#-RZumREejq@WBSIf`oWKbPOLU+2SK9r0ql`D+JZK?0*szK<?&mWx#1q7 zKMA#rE$oo)hWuYTyxFw{k?+66?)k)yNAR^nhP~gigBm4ULU(Fs^Oek1*Iz_%_2#}E zIYw-T1Ur(Dv7iZ9zGiimE()#~X&qw|+HUq-NYY1C&jer*$KuQF#HO$Hdok36_yi*H z2e)+g_*UNVmwZ`cdHuWacQ20REc?bXdY$%sq`>a&hoeuf*V~cH&)>{Q4>BU<8;DXv zJdn3yS0zp8^88Z#9fh1apbx!zV`C@$CeFqUjXU!GUy99Ku<VJ(@=H{aybq%k_Qru& zLPqOziuarYkEq^v7oS?TlLB4K-4x!Pa?Km`IZwp4D@z1CiLsBci9KuhDExZD;sXc9 zj9i`Lf!;ZVwBz2p>P7_-+^iMGl>E8i-p#^u{Y=dts#M?_+xX&a8Fp@0RC+$^H|qU= zwB!^(e8i%AmCNW{kr&_46?m5baB6)1lzR*>v*LUjN8?mg`GP&0DS+&s<JUH85Z*U@ zL`JJR*o{7_O0<ynW(V%<aCDcQfH2(871SeC>+3N(f$dZ9o+O_!;7hRJm$c!OG~ra# z;moU^K@0$P#vwQ)m~>E0_@(=fzB3HMI$0=BB~(`76MzCGCIMS6JE}}}x1Ztf9Rch7 zRiw~p5oem^KyQA=p>l~>^aGfRsh?V;C1-CH!;%Z{m2hTi;~0dT+>bYgcOPbsL4JSG zEhw-=hL`sp_XcE$D88JkOnQYkE`{+)^8&ULPM+#Y&vP5B|B6qg3siqdaK$#4$3MH| z#e7fZwa-gx!d!73&qJV-C<RX1mA+7;TO{I|;_mb{h#Avd8JgTBSE!bAw(@lw?QepA z3=I|!9OBhRz9Vicy~mbB`)P;OdG`e^%ME-t;@+e4LGgmS+P^+ym~tZUm}Y<nP<=fE z$Q*y%J;h3gcI8>WJRUpeWV69)pMV`YWK(cffdMKsvj2S#3Wdm6Dq=Jk;4>~o7}MrU zjm{bTvu1*0A|1TY$)|;1>k*2x=aJT}Yd##UfuaZyB8!Zo8}x)%L#up6$B$Q>x6g{L zC&hq*6G+xL3geFey!?*~=V~k%7>wOKR5NdGp`3k^yBd)yE+=eU$jKke?)U>V8nI<i zJ;>YoL%snN+2q^fKyZ%2tTXd6O&$QxnsLA01Q|6%(i3bm$$zb~d9kiDHJnVgi4%5b zrm6O<S@w?pr)oek&8>GI@G~B)X;df?$VkM;b!a`Z2C7b%uvWZ`U7oe5giG8%oQINp zoqT{Y4?acl1`~%2-tl2;%@Ov(XNlQ}Bom0rAU@6^79%Nb{>jvO5HA`Rl$3Ikr@fgL zhUGUl7V&jXMS1iNwd2<=&y5LjNHVEA0bgXDlW^!Ur&7pQqt*UOcErWgY5h|i(^5h% z+NDi!cD6l?acdV<H<BH?`a5|a=5&8(zf5-xE)-s8?Z=wprwnQ&UXKb71~L$T^Xt4V z8W>Ucgx1f!waCNwdqzWaA$aUFc+KCnS|8VPV?w(3_U{B=@nRvf`V#vWK?3>-zA_ms zw(!f;aLtMP6_<IsGc~{~L~I*f2U&mL1mnLyqcwK6YGQ3ib<YdkJVf%*8+`@Cy6$Fm zU9MGauC{2gzGcP(*^krf$cm<0GT4X5B7Pu@KIJVxmWSQgDQ^5b@0f28KHY=YgAph- zRz)H@##?E>virNYGA_A~$-E(tJ7j-xAKL@|AaM7*dt|t!7UJIh`kHgMLZKXfjssx| z{7+jr9f7{_RYI5P(B3VbFxItrf(5aTZ;wr9XE|;E$vE^E{sFX4n2o*VUT{7SmtpZZ zIKQ>Cvv@|_`(<M1g|<&qWuLX<E#0Pz<B^>-(1T1_6ay=5K-Vu3|7zX>d3)Q9FQ=Tm zeI{)K?hEh0X2v_)Xmq?D7b2}C-C3quUOzDGyw8KSD<^E96cLHDj9XSa;k{GK)Pxf5 z>3);~W7~&lqqpNmQc-Bf6usw}gKPb0Ai`g@bYH81cAPK~BsZ9AyM;8St(^$~roky? z0}tm*hf!yXblEOv!9;M>)f_IVkbB7sfHa2PAo5o(y=+3}*<{tlgr$!hnf}yTd(V;Z zhq>VnZnv8&a(}wYN<@GA4XgLiHvE9R(C**8g6y}ufuZx@@E!6@e8G!g@kMXDi<w)p zgT8-3PSnGX2v7EpQo{o@bbqN0Jw}9<?70Kfc{Qk_dXct3*ms5z@IKCAdw3rh*)EAj zTmD6`Wr%_FMTWBTmD8-QZDAc(_ekpp9(^OQt@k%E1jNjGlMKDB!8C#9Xt`-3v$t`@ zb_Lg(sz3{tIg721zKAm$h!Jve*KYN9>*PC9bIMlxgmtG75c%RnU*wbG_Bw>F^uhq2 z))P~8q`$f#_fM$wSV^OLcDJvz{@hP1?Pp^bQXLx!-*4{XUDv$4fs+74zRm^7nk&fF z&eN-22gZ_TkayfW*Dw0gBj_ys4_G^zJ<%IcM?_<nh!go)hN<$0uveQ|O9g8A*Bm8T zsJ?uRPk`g?sjB2txp%$<6y<(!i#*RPJ$aa93i#`&@$7=0e!+z&mH+ZqipLQD&Td{F z|JMh6-{(+pc%Qzt9HL;th7{w)#ed_-CF~AbQ0M^w-Yx+EaQ;s}o0Gk{lc}@wf1c@m zt(~#OllI(yqVPEAmgWNxEaz^(M+dDnA1pYH*P$Yr<2b>9ZGd1KW%IFb>>>uKnh{^A zZ~V7JZHE1}KgnOE>F}S&y;$&&jx*PZ2hqB^DjW4QH8cN>rcN!A!+V?M+6x~Zxu}uG z%6~A&smhs+LaOXw;OCn1C*>|ZwBr`PuBi(oz3gDj!O2=Otno$rJlMS+%S)TTbPq43 zSx&i<ta+!%LA8%^kNRqyWpYhy+0#v>6SkE<ED~m-e0f<hp4v1{Ina4!@7*&T9px^A zmsdAWWA;sC_$2;R*~>`$CyV&$p0c_}SJBly3!r44jOzO6>q`D3=094B3zaLBkKNsN z_M`twos}@9=XC&AuOqZ7+x-DfR^G~|>nNi#<{vk<dac1n&#%izZV7)B8z(#UHVip} z1E=d6C$2GlSkHmvyb}6vujuP~vHAbWVspRmpTg0BKc1R(9*5dQGnX$H@!zjC+49WQ zA9Z?EO|$!~_03G(a#p{tIcKB2Xvk>!d&Z}_>BzpEHU>2?Fm};ptMV8OXX*J(7+B4# zN@ib#{lb0s((%(dpt&wFSp?hUQ|j06&*w6&cJrhtxH!&$(`L8g@)SPM=f{{jCX>1J zXAPf9Cv*WHeK0Y*+24KKGEqz)tYFKv$n;_3<Nc)tL{1o+>FBW|J2l{_wN}=jiMS;v zp%A`o_3-atU|oAP8RxPO^Z}7;AKiEWA(nM!4itLMtLNTa6S~W*mYov5440cnY`&9L z5cVYX<UHqTiD2xbF0(nm;EQSpzM{X2nZCQVYDL5N?=GhfbPLo59Q3+bXWsB~mrqS~ zbyqzXW9pccKT<zlY)c2&8AM7zf5Y@$jk;QSeS+x=pJ`u4-TZsCQtA=zie>?T?N<PK z;Lr4f930YE+sfvW6UC~U$Mn5`GiK2ogxgS%39|?Do5}lL33j8qd4Y)v(?Ly^<4;e~ z1-wsGSM>f-uVUm0BN2@(0%GO8fn$5jascOn3w0(i)Ng6)u2X)WFvY!lc9cbtlUo15 z$WWG>GX!QXO<F{#($Dtsg?50cSZ2%x+M*jR#txeSQE)P3$Rp1&n#)5AFwm9XEWkno z<2nkFZ_=!we-N@V9nQ-AT$m}^IS{^%$hchv$WFIsxN4+utp16{fBR+ss6gif3e*yg zCCX|6<~Q@R5Hrj?h^ofTUa0Q00jtzWVP*R{y}3RBs~B@zX488cCpDdKR{uHTT94`b zuzPslhWWH>1VY<ujCVbyqt?>J5I2s%x4D2-fFq!$J(k8j^IfYf)5CUzgpW3Z!G^nW zZk>BehIIg6n*du{vc9z!YpsS>iczl|btJgpd?I)b9vm27V;f0SiglnWlT+bz;${Ol z;14!p=ktmg3vl$TK|>&vl&4$FZ6mV~P~QCE0<WzoW&uCr7LqKx>hC_jB{SYymY?o* zk0Uw4c~8FGk#+j;aNI}p$et1PW3K}LkOE4B37*8zghso~(s_yR{nbGwt(^qE!fAAB z+iC*i1kQLs9s>L$?Nn)%eKJ!_SI$e`54g<G(UVvCfkt`iuy!azdzaPdFJqpVE1b;{ zEy_!B-tO_{-D*|UkhPP1R9Sf<v3r?HDTZAUP}8c0&gBCA;vxVBeDJ#7tr*#^O>9jW z7}6C4t<VC2e%?M&fSNTeRBOEKvH>rDB^MzK+;xd?T6Oy3wUw%FuJCRy0GpldvyrL) zZ=OzlW@>IGrOIin4K_P?r!nKauoo~qTGvr=GdjaG*5OPR!N3^DV`f~uY~hxFWR-)U zy-u_nCZm>wDT4EW^(EaRMb`f)2uwAquHBsl*kJ-O-aurUvqb`Jl4vmLr-|wcht2q8 z11r#Y-n{o7=8Lhx4+uxo(M@8~)2-6NTT_SKk<d;liFK>?OY-i_rVl6{kGeIWvKKbT zJE`g)@TaQ=59`i|k(83A6cB=FO9<dgheU`!k?QX}IjNJ}xO#?QNJr>ZlMSmqw#))` zqj{B*G*d~W+Xd%PO0f=YZGMY_;^N8FXuot6SV@6OyvS})GYB886#5A~9ZMqWiNgu| z>-b>G6u%^57f!o8A?8u1Q6c(~05R2IrYft9YUyH<O<1*KE}#?zNtINYaYU<81kD13 zwIADDprW0<Q=pG&5O?q<x)!-pj0T)UEC_*Nr3i(ax-j(q0GmA!wpw%{(TzpI?h&2T zTC0WUn(Y+1oK<d4ra;mv0drP4k3tO|WtqMxByfj=c=U%G4Ovv?@6tR*neh{9Suj9R zNFd7{mPww|g<&OO0-;{)Yxxk7Mmq*hr&X1s<>iYNlX77ZoN%-)ZV;Y*9RM>iCWwYC z2#>No^yaY&G4v;Cvw4Ho{P5vF>FLgsppV_{L${0qNdXkf=kn|7aYMeURZgh%VTV*8 z4na~K;;N96IeRQb2)o1z2jZIwk!n59l(?;vwwgO0+J@+7)*7o|E%!_AQN=c~1u4K_ zw`n76QIP#knsG=YBxEf?Kmpd#mV_bA+)ILJz0N*oRMelOm)RKz2rU15yvX*ihl16< ze!8hxf^bW@S^A8&<Qj~mN+T=lX8AYZ3f*PAyTgzG;tzKOlFljHqu#LfZBu3^{h=!( z(3s(-?1tOkLY+%ESE~hc6Z~(ZfYZ81QMLRRrPQg@^~S)J?d_vujkeSgFzt5wQLTuH zPDnQ%{Hjb0k=Lq3AIvxHeu;0~kk6TMo^_F#;<%?F1K<vPQ8xeriY&&FJgr{hXZ1+9 znB!tW=1DMRlh`cHl~ha{w1=Rd^Oi$7Fxmse?@Ka`g4s!DIj{yav4i!QBZR)hN3zr9 zGb_E|r}Whd1sv?qxcT*^lxR+|3rpksz|faMUyv`gIFqa;ZVoP7u0L>&_h1-8M^(Bb zv_FLxc;bDIN%fQB1gqUaq$P%62TJKS<Ddl8{5~P25*3j=+i@1KQr`#5GaRMG6=~G1 z5_deFy?3(?;jP2|ik!J&RIbf9q3j}9>2j^H08wy_d&^iFGTK&p2?ub_B}Oryj$iYg z%vCLz7A*@9qF^B_@MNHUPRYH10`(@FEtUkvp%!?1&du^zfi}AenEGn16!kOrNl>;b zevH)u7@KZA(#KFCRP(nNGlf|vXgCq>*ae0+bP}emYrse+XjIA?JQDY>XBm`4Vr3JL zo4f-VL_jc;oZJET<v<l{n&ffdbXuk^9yr1WRi3OJ%aaNVMJUmJBvi=L2<hidn=%%f zC0S6=xpGaj4S|}v=9hwv<LIhAe%eRwz+8SJutE=_gDz$5bX8pZ^i@Nuk@}_K0ImX7 zA)Of~=%pV_e|an4D)9w?acU6ZXu8fvetPJG<<jlK9eM4uv-1@V(^1avy+`Kx{SI0e z2-c&;T!H0%d+ZQC<$XIGDohs_PRM!+k}G?#x2w@8_e7y$@f~2lJwCBF0e1+wW<{ek z+u6G={MP+?);TQQ+QeoDOCYVLE7z+hJeL%p$Gl8R)?m-?8z07E1sD-@d#&%jihw-j z00M#sg6vx-Z29tTdnFa@;=M3U#2cJ&0h3V)`bMUa{^F(Hz5|fnEHusc3!|bZN^WaG z4xD?!)Hi}l=ynA>-k*;MWCQF)A4RC!GSY9j4k`$Is7>pJ8NuT27oHqc0s#^&UNKwo zG|6-Bn{bd%yfsIYZ%h_yAtZ$AvoU2JF2-T8<sepQ;h;U;=5C|l)}`^0*5jV^p3A|w zT!K|P`S!Y~n@=%5Xz{?j9Yym=G3#2KzdF7OE?T!XHh{~*mx=bxS}KlNBJ3xK%RU`# z5aa)0H3QMeOhCk<2+SI`K`E=5OqlFAWl$k7wTqgSY(_=v)zvV>Td!bVGA+7BSD;?H zR@D6KYQ88~h|=-+<p4L5d~tE=^S2P|E=mAl@g^s0{$A`TKcuUe;1?>K3+mGwzKryL z!#g+4XYgLsVR~NjKB+8>R?C5iTCpJ~jacbxd`NR>cTjWa@uD_lgkN=geP3dKf3F`0 zhazm3X<vBicAp>k6eC}{>0h6%h&neAR9LlWka1PNqG=Yz&;Vp{8?%;a=?Oz9xq{Pr zC<5JjjJfarZM{>qmzmR^&TBwvLw)Zo=sv9(GXj#-RW<H@NP=25<qta#k?QGv$j8pw z3p3?7ras{YTjjJd$4@GY*zDUt&!10|-1QBOhFNKZ4u1GS?XBJm-n|!KyI)oi;n3|@ z`JOtR3tS}UEAee_flu(}UmA<Kx3$o(V7Mgzx(g&F|1Z=f(l;?Kc+m8nh3@JC{(mj8 zaEFe@kggpxgSY3CWE`H2a@uWZHJT)1$-s_H9udD0H^01Utg!YvM4XtkQ<IaY3bBl8 z($O;X)(Y&v3bIs|t$3m&Vw~}tb4|F<R$8?pSX`C@EvqHmv*MMmS=67I187?)9+2ur z1Ffx;MYFDR98(R*3leY5d^H}NmYdJY!81NuGa4=P@FXMGE~-b$oiw}!C^T6lMX}z} zh9miS;_eyl1lM2kZM+p)ztdG(w}~Uwv=4(n=sXp6dCPCzly)^GV@~DSu+x_!+Cvi< zu`|mA>!ZX!1W6E`nU#3r=4NA-^cjs9jSpho2EETvfDcLNTqW>;fTe;?KL*#7JW9{y z`+Uu4$?hcdFr^s`d#OtqlC#tRJ((g(CGuP1HB4@+uh3p|u2_l*iaHsKDu{RX!F4+? z$Ng!flMU;9pPg37lm<M2rltLa6iH@NHJQfTIn-+@>{Nh}Ab0)m_9&-p<@2x3m}w#Z z(Y1RAzMHp$7<~bYavjxCRB`rD5bPaWs7{m*z_DiqlMPlg>zQ4{z|2&X5jm01#E8v+ zuA3*=`^0UX!v`#hp=R67>mn6BOxxMh8Y3VJO9^(%12K9JIz6HfQ0$Obsy0%AEw2ey zvyGfTjv$Hx4#mb?6q!^N=3LbtZ=h5?`h#;)__?-)i^^ZZt`B9C1zj?x)s;!FA<%_5 zuaZ?^MKjJQFCPlXf(GG4H=81tgT9!&7KvLEo)LQ$-r>MsTlK>6Ziho!*Nr$6Wbs`I zemDo{LnKLmoEk?Ze=_R&gg4OCgtX|(K(3q(TaC=JlfT%m?p9|lLXYpu9LCtb1Pt1D z`G16VetPfq&F2zJ5@!+)(blBjXWmAmi9>eRFp{wvf3>`vm1_(X$JAhoc#yS3hiC~} z@^uU8WcQXf#QSy{Cn(dDl9ts$n|t-Dmdeq@=m6UGYxgv2Mb*-~X3?kIVy(eieCYZ* z23AD^xbMSYQZ;)7A3VlzgI|^GyBzApr>iE8#vT$>V~?U^uI?2dZ?FzEVU(My50<aE zMvlN!N#j+Pvc(w|Ly_2uX7~Tt#=N`~lm{UujRR_h&NtNSyjyCu$lBBIF9SUbXfE=~ zr5UV0GPxl63PUn$M5t~<zI%9}Aj(N9NVzWIsml$KY}iV274bquG}B$QaGzhtZ7wXZ zLpfqi2Xbcsb<C9i#S~!B`B!ldiSwf1uqyi{C$mBnlELw>!ChG;=<H{ij9~#PYit4* z7n4p=CV3a`G}wy7@?;`t^^WjrzbC4)SD#v%xEXb!oaYv!cHfEt-dG5xoxvrQKPW`= zgGsaqz*P`#VAcwee2qgz?8RayF%l3=W3;wH&PGE(*2<0nw_17R(R3nd8WL4PPtGm? z#0f5@kSxx|1Kg9M_`Crbi5dGwK%IcNX#z9R?`gS<)RuNx2)UfpCm+T+$JuX8@J*IU z-*fp)TrPa!tW>bK6AEv3HAmj!8(f<+MwO1y6uDDZIV!IXaV1!pp2Io68HV)?$2@(n zM#ap85{09^CpX%U45c32?*K{F>H+`0PKXvAQzU4xw`-lHs|0~>u+)t6xY{DY1Clrd zXIBgZ#+d+ohe)CVS^q}}nnCLtzU{U^c)c<>haJUlCj2D?!S5&xO+Bz!(LIkT0L4yO zx3@UXZooNn#G6>JR}Do<E8jB?ym*lwhM^3VXql_%>KjdKU7mBRu_@JpwPCQ2HcOq^ zE~>_8gW4T)A3=c+!zBg|clZ^-yrZG7LX?y!6bl`)Va+2~*&;pY^X71W$L4fjk1obG zfuNQ1b8pV<^=8A|zp`nYf(ZBOvJGQ*(Y|7~NcH~!GgzG-hg+XL+7jMKWS7I8?wq~y zb6}Rn!J069|HITC0e4BQh3&}`X02`6;~9(KkE~b^6+o9ha`7YjLdSdH26HaGd#BDN zl(77P=zPosfW%w|$AaDQcSd%Ro5&-pR~sf^!$ub3+GEf3K7XM(CWcB0^w#AOqXZ!D z-Q!PwHv3v(Aun#UP4?_*cT)!hoR}9ev$*_O^$;aqkMG!kyW7^J#BJ5Vb34ohUpIs` z(5;XKSMWzE>vURXS^ps(@LZE@o(ks#poIbys}7g|Atxhd&rq`PS6vw<EVU*j2JaMt z!mzVVZk%6kWxFm`c%Z^%yBOlGLnT7{Q_AEE`?AFeRZN>nC^WYsPi^@mqcv|)YTjxx z&QbIw&!mS09oKx)cW&RJo_T*p5u^K96ZkP(6&HCMzFhB*l?Th`-LA=*@MZvGeOF;_ z?MeSulnz5r6%$fKbz9p;tyaras(I=VP;zLo#tg}`QOM^8>#Vwr5!XM$UC96N^-e*e z1l!hb+qP}4wr$(CZQHhO+qP}n?p|&EYu~qh;+$Ji`Ir$^5t*}Q%{g*>8hBHv1uUa( zq!tbwJ=Bv}KgIV6q9^p=wc`Bh8;%_0SKaEr4kzIqiHee!cAK{p@>_YBH(!+#Yyv;w z0L~8<=%&hfB%;K7jrfvc1|1YO1Yw%<eQuOHIm77|k8qCNVgRB$#1{=7XkMa)s5@V( z1ooAZqK6!vePLR4slqFIYzgGX!+zT&47kJ(*QS@p&^a==zurPwB7fUs!HILGvjS8T zJ<Z-9ax&X|WBbRW>Y7jE7YXd+kZm_-{wD0DHeE3l4aDI@T>;zMYHF)QSQD{%Q$r(c ze9|^X-1vdM0c)r=X*|RZ1N0gjw$qTc8A;=qk=mOl-x^{*f;O~=pQ*du{eI0I75#qg zeO&zh#QC+w{TeL(9`XIE-3`+Ecso0jI0*0^pwhXD%<qvezflM=UiGwr%7%_L!QXX2 z<LeLRF>isL6{!#KYyNow+)a#~&16?nKEHX4+BR8cL2$dv?Q72*3Ww{+A{o)LNf~2c zDXvkkI>c+Xcdh1Ag+_0}+a?=6o)^ThW@ZxfC|n&W%4V1}!$io5$5#k`IGIXfvs*^2 z1dc6oG!o9mZWg6T%Cc0kYkr9Z6TGR_h?J`Y%*1icJsQO*qF<jH!f@I{H3qYr=}vQr zT?;1RL$6sikrG0W7XO9sdQ!I(HkuYVBXBnP2?o~YX&~MqVB_FLFLHn4Et510I=5z6 z+q>)Z;xD)*O@Mi|80(x$=n>K#=ZINRydn!c92z@oxEN1`lJ4CNxV9CKa@#n%*z*Mt zr^IZnf)>}%7iyz)E|ejdCw+7ANytaMMDgLb-E`f^=Js&ZX=baU4t^^BHmUsS$W&94 zL(>4BisfUzac*xeQ;90nZsGXX_5A=8wbJa?QC4yWk(6=!2s*}(qahR#1Tj%I&>H0* zng-;(z_UE!2|R>DxHI(5fw4!1+MQTPTY@LQQ$fD4L5_6iZ^r_uht$q4Scw0!kk1;s z3$XJ7_TGy94CSf*1G7h7)gOP*=%3><-HowK#4&Bb8_;8ATdH<0aimEG{LjLJS-!$# zP^9^+{(kTi8w*KQ*ltBQZ1PqIJen}lz)NGs*S%D;n~hPXQe}fz;ehS&;Q5b7FwQr! z@g+WG?y=UshB8W@N`$YcHM-2G{W<dY6czPWA+6($)dyHA)km}}_FZ<2<q$S6g@3C8 z(xXY_MDwIaI$3`cKv5P;EhB^3&^oDnv9ZREfmkIbhpUKs8JV7`w~^5~adGyiGTH|J zX1@6-`&VBXt5@R#g-nRgGAg#rRMKwk>1%IB+Qra#;P-Qz7ZZ0h`D|glo4d$(;7N+P zyh!d2oVDlgwv#~m5^JYpX^m3LZei6#$#u``>iTC7z>5J?VJXu#KKr{6eW_mqwPU4r zlxM>c?A)FCRwRj-8W9~<<*;ZEbHIwv<H>#1@km?Tq1fATg{Y)I*<j}{q^NR-iBhV- z<JjSrz+>YZ>%`UsIVD3*B~PdazSLo@lj=r8g3?A^Ctj|}(}ZQM`6v(ug*DK^C<N?= zRRQ}YZoz=S9%3cZ96;T?$G95$bw}~w)~}$$Muv1*W1JfA&0^d9$z8XY>`}QNkoE{4 z{repBkJG51?nB<)+nnDSpI-nUI^p<H5xw782aK&g#BU^S@5zs&TSwcr#x1p5ir40X zLPfe07nI_2ale#}g8!%oxiDT#qS0vLQ4oUDSg|XrDO7;qoasr8+@dy}4<kk|n~Pt% z&pL^eMfY=X`<pD%DZ|VOlCF{s>yokpm9;5bs5nKto<^vGf@61TlMc&|JAVYc+-}w) zK2pv7bqGQ+)if}+W2C7bl&c7M?n&3egmdbeadOv@uS=7raY+ZKKaZ+ec?`MA+jso| zVm@b{Dz)88J7=+tn8+8OSn|v|LFoF`yuFwR(CWu%2eR1FH(}+WtL1Z#)Shv;L`9Fe z7S6Cz;@=##RMtsSgknc7vC%kb%vvh~v(Y{n*k;JW3}6S2GNsZdO}-PY=a`N)9d)#H z(>&2{n2?irdp7Pa%4mG!p<Ep`jt*sXf1CGY`=@%r9m5vOhoau~t6)psjxRQz__XV| ztIw+=*vacwOHq)Lgrzfrbmo;}0#Z6i0ov9O_>A$AlKN69Nc=DspANEZb{Q<r-AzPc zk_FyW*VaO|GE0ozv)C@|cSWWZQ|L}B#S4?6lo+u7^-$l(+m+S)X`ZcqgTyta$gpd| zbAytikE_35E?VI%kKEfPg5Wm#y`YYCL5vF~pim$?jEUK)?yzhjbQuvAA$^tAz3{bL z5?-Nzgjgpp(uL%q=j1?ukQd1w*9@P$)nf8_oF)JeZ;n9RJukS%>c_~F=FhF;FqRcw zaMm-%{Hq+gvd_jtp`n5zT#C}3IrLV(w{Awru=SW|I3_kNrqKZg`}RkwdnQ+vCv|)6 zmL1WovAtsTw7%>&1zPGX`XIU%HLVzPk83(6MqVWYPTd$c+a+hN%-5_(1=HMG+A>YZ zZ*U_^A}?^p2Z)Sb-?aip8<Hz=YKz$<b$w@2_U&|8E(pqE95eM#JR6We4s9MrDdyg{ z9x9VPIyFn^2`O<hYmvvcW?l!kGMIx|@hH14I{3z_A5B>}AA0IYS?b2O)qAynCh}Za zMsP6JN8i$kw@~#{@W}E!vD<;I{lIQ0;32y*7D$KZw$2--U!0(V&V2Ve&I_zMxNk1I zIAh+&bMq#KMoRlXQ;I;&9+9%VJT9h>WJJ+}Mo$&tgSg_lVnS%E&ehw(4-2Dbeb3i$ zaZRHR5`b`nYA9#Gis2W&NB4+~Y96BX+K#$gl-3jA#<Mz~8J%fFlaz)Z5c<F?3uGLR zWf&CPgBlkqII!?USePoJb_kd?`M-}DBA0PV(M2*8Fa53iE)0eq##cdy(Ma<|*{f>; z7;+7vh^|X-tvnEjr-d_FG0ObIbA~knxjcrKLtdk8E7AREgsOscbiTgs-we%-jBz%D zi63_C{6SbGV_oflciJgFBJRkN<EwJ=w~%wb7;Zv@>AIh!B=iq)5FiNrmQ@{a@1yIl z2YiS;u}kzw_}%u)yqtd^!!I$1{q^%T!wWLKiS$%SoW&YgZIuu-?nlXDbZmHN0Ttq8 zi|FmVmRDKH#3St_<sn%0Uq_0^KwNA*802Wf$s^3fOCzt41Qp=J<J)x7dK0M15dW^J z)Uii7vk$x_{K&}NuKsc4+V_Ym2Rw@zQZg#`Q>R_?@1~D(6PfmFD3-SjBe@=LZ#@@H z4%EhN%i2WfP&6{YNM7Us2#DkwQd1n4LN%zjC|jxA;eaY{EFaU}JdbE)NA-n$%XDc0 zvg8D>;5pM*#^9mqP|jHDg}B3l&b^>zWIG-w2VG`XOpa731qlIp>G=^I^348e7?pQ{ ze3GD{>8Ey4>FJKt4Li<EFG9D1tv2Xv@gvoi<rC9h=FCHL;k6j!er6cAOm&ehnK}3Z z<_75N)?W43<zGSDz+zEbpOJw>Yh3KEeXT@!v;(qfVXau5YkvrX0M@VxI(u0=pF^Lt z!*eZXF~ZoC!D!^cG0(SyBp+9tbCco@ih@BP53jdH@n|zQik-s08YvJG;8eKOarqeO zFH_zEs{ITu8R|1k`zMoBEuQ^YNq{kHVp#Wb=5cPG5!yGe6~A|vOTmDX7OQqa$8~iE z7m(4~8HKo7n?MZ3het-Eie@pscawKn_+uge^{rF@iCgx7{OF_!vMh;_a*7@+o6p+x z+K1n6@@$BG5#W@xa*0{t@Z>3MH5PXP;aAjvJpoeh@AH+jT72ZaB;<MgY_8iqLgoYS zw{4_MFG&@P1Q+l5?4$1n(S@Z)ksw}?i$7hOU(Fjh#E9W>x07MO65Z{_#`LlVMvsk2 z(y(XvgC5HX7`n=B$(`knvPmoPnPh71qWW<zqwR8Y7AS|!9cXyYlfmYVC4L~FW62>N zI#fU{ZVssCMBoXESl7R)22=HSl96G$jU)S&&@=a_qtcOV*?=%R{9$lrg|;e48HLuB zM?yw8_NSC{z69i5l=@JdZ}E1Maq2)0-k}z*Mm*n#ka!mYZSrFJpCF!HBVK}ClPk{z zuyD}<7{B|%VdnB`Wl-R4{k)26I4P@pHX~@oJEnx+5Af7Akoy}aouHO|f@nrl0h)|o zvnr%wpDK)_(Hv^|0&<o`WD*_L60BT!(M<cDD=|S1E}OK4A+zbHV*&89AY{bI2@Sbq z!yq8{(Vg!i#G*jRd#`QxPo47z$G{X5LJ<mEXi-HvFGF#p0T#p@3s)f7TYl#ja1IA} z!vt=*RF(ns?Ah_lEoT?9!9oF@jGa)8A%>ytFU8qy`MNy2ul3!$h0osk9&RUm&nw1; z=7aS^eNuaq#o~H}%z(|xU=hSabB!nw+8iO`S-Nb|bD+b?Ys@6OlZf}wCSXei{Kg^6 zjhky$+ml6gPTAZ}Dlbco_51V}-lkht9b1bnJ}EoYG7adqalfnfNyq&7?XF^lm1?#^ zTJJm#i`QNOY3&w)oe+1+MCb*BnGQBHx80wf&vwpL*cquVDB++{?V{5<3X*hd*_Q3~ zOv{cQhDN$GPlpZ38FQ4GbZ2Hv@h&?XV>7<FxoU$*=g9-B?x~G$?ESs|AiMk>pXblL zU!FV5ubaoQtP;*5%R>!jNA(GdDVmCdLt&M-DC~L=!JI#$2E59W#nR!%ed%B!g$Bp6 z&HuWWs~yO&epJ)l^pN<laU%pHU8e*baP<&=jxDRVm;E#Q-_ggL<3cMVs4a95+cPOk zT$HA_wviJujr`~;{cU=x1pkn+V9%i&&YJh}`!gC--hlyvbo3U}^z>|F=qfXki|%Zn z<P&)jAPs?x)DvNWUTIam1&$*YJ=k!{d&sC%KZT}}vo2!BcXgTTZUdXhG5cVZvRS0d z12bRY)?&I&ESV+Ix<09f5o&|FoHs%qK{HDv5a7SA9lnvVe_3b-vb*$gtz%-&q}FB7 zsz94eVmY6w?C#9FfKdu$nW~%b;71->*CBAL0P7Iu*CTrleV{*_pj#GObS>fcx%#dx z5vs(IzG2CrH&N0*@*2?>-a@bGSW5r4Xfist&~RD{xD+B;8b~a_WlvHIZ(Bv#AT)%Z zNtyqV)?cZwnsf!E;L>-K5Tia<7OI{~hd{!<fk4)-TpTFm?a(qfb&%>1QU&8c&Xh5g zK^cH=JczRuIh<Z_)<|Gq{JNpw*!^R_@s}1d%+&OprF6Xk-0|<F@kRr7R%gCtYm?hH z{A2nPR#(twR~?tDLhp|99@|Qf8JL^j;oXCeP5eMfLG$~Cs?Fx@c+^DKpd1IzLQHJd z3qDoWrRMoMVY@1l9ljw~==n{eY57ZUWc=|(h#&qzWibL&1|#p`{%<JWKdukh%7V?2 zuWO9lHZ!Y|JyXe_)zM_XE1g_4Q&r{;@I-Zx-c6Es$Rfey6z6ghI`B}8V_)M<h3y5d zz*U9>yzO0D;`kYEzl40F=YNd7{tSdC0g-iV^&nx0TP&=lZoB5Y;k<2ER`-HG%r+W? z5NTZ_!-Uq!G(%Lu6z;Y9dN4blG4D`m5n9;$*%2C0DY{L&m49WLx7IH)N%&h92xXo! z;wZL?a)B2kxca$bfyz>V&$-7U`C|t2Y^qKZ-jjPYgziOnI|XR!F3=VQ4sDu~JXVg4 zdPkMD7@E+A`Z{p=I?;8nMBlT9Yt{6R>YilXzFc(eWq_N9NY|3>u0+<}whkJtzIb+> zjy_#nxq4&2)bdz<A0PK`AHyoP8B@IdT&;=xYB;v82H(RXt~15)REKb@B6iCC4!9Qh z5{Y-bB|ciK7Iac?nRL8w38s9sed!RdpLG3g75+Nu1=7ubJ!uE~;v<byR<=pGSRD0l zTgI-N*s_#`H^tP!c*e68cMF(i5m@#6&tnCxdN5{EOC+1`u#XMcJU2==z3a5vCVp7P zFB5_6rz<iz-`<~9QY@7c$q|b}M=PqaW%fA5(zfGB0?xNIyNyhBl=yjl(|)~IpE;lL z{CsA82+bW-0ZNgv8O}V-JW~ueHjwCIx}@wG+eKNbBo&5<3p3aih@=G%fngnftp{R3 z))QCcGT>q#JVUv}HTT)-%I!~Ba+}$Nz9CHF@@VD-rf+lw#eM#XIbIqfVUP!G7hBbi zFRBn{3Lek+yi$pvTFh&=&>29wCl@=Gr>dA{pFeh$rZ#pAB|BB$pYw({HaTa?k*}4c zi!H4ekCe^(T6fwKgWX{MN{}4}wNyJNW_VUiD3ap%<f1T*2tEK}Be}Md!1_YrnAh-~ zy}c<aofvz>?+m?fEie|@UI%Z8YCLHu><rbC%7C$K%BF-f>qsg0DORPj2h|PqoRD6Y zi*KE_axGr+%E56DIEeIo4~{y~KhFYY&_2`lYDZz)2D5D(wgX-3o820Co+Gs(er;J= z9o?3Tx_N=y!yL%Am@kdZialofJv9EXW{#=o!B_%`7BCg#5LT2QA;iThd}u!+<0q4g zs@6V$+~=R&&z!}$;JlcU>S3j$UjgbXzP-O~Im!QX%qE+6b%MQT!dBT?KIZV9DksQM zJ%)D1469^{^WK|MyD%s=ZP)@so{&7=d@y~<t%ehl&HR@xd^_`-jcIrnbn7R5XU}>( zGB6b?O@qEHgIOe<YuMEe$_>e|O^eWas&Y2C)yx@WcSf-dQr)1jKt?Hj?iK2uW*?t# zRe&-#^-8t~p1+LmA(qC+_e-4|aP=;*$GQ?sFFo}!<|PyCx#&>Iy>uqVDw{xosYv^d z8FC$H{PY^6G!B<IKUa2vA~B9tet#43P&D8QQ>?F8gqq1!aH&9os4W$Mx=hLJ>ADj; z^r(LHbz2_C_FE?u3F-U*A}u#7dAOSdbo%H)@r4rSP@;s23ysy+sLItTuS0OgtJIkY z3f*>`*zOVz$PgW5si{EFj|qu=L*R#5>^gUyT_btOl;4cy!zGsl6Jl1t^V+#PJtCz6 zo*VIL2K+q#Ts5BYRXp{1Ak&>oOW-n_Schy|0b_=lZ_e(Po%$cj&d3e|&ZpSH!8op` zDH9lkQ8Mnvk?dM%IYn$R;r29IV6<j;#Yk1E`Co7t(9Ip#(+12*E<vL*Z1-^LK27p_ z<U*W{sU#de(=YFfjTmaJpb$$|LQaqk;Yku=h0uQq(i9R9z9lBA56PPuu11GVh3@U* zN8l#*-@D_t*lWw=wM_DwSQdlzD?X9B9Oe}Hc!ZQjF7OFE+T}@ixt+YZ9^k*3ey?Zh zPVCE^Bux_IE;~08!1ECqGV*}u?#5hQzqOfet5CT<8$`9cKV?ZmJW})J&w(P{<hL)L zf6jJ`F{2-8*xB#6n!dn$NyCPv@5017Ijn{m%YpFF3O!EJsHF|k=fLGX?f{oH8>2nZ z#VE9eGj94TIvO5ab{e=+Q@ylHvv@l_?&APEB0ctSMKsTxMdhuDFna2AFT4;#2-AsJ z@VTkUP`QtxKCt2*IVN$eG!lj+S@4CnZnX!`;XG0UfjF>_lRbvW7yJ8BWnQI@D1EM) z`fY@vr1A4>W}0-xKL?173E3_KlhRHd4$j&f1-qj?2suv!B*B0`Nn)vFPE-=JkK4G1 znP{4YpVc!R7kv_CT0IEl%F!+|UUp?+r}e$*Ei@JuUQ|kJ>^;TS*&FdybS$D%lX`I! zUdQ(iu*rC7FIeW<qUCObH3r)T&Y-6xbpTqt<GEBBD-VyhVKW=-w4JGdvk1P0-r8=r zqbn@+NVf`M#<Z=nj9XayTX+!}@v6ARCE`PVQNyK9++EZ@*}oU!lCw#RzAn9A$uO&^ zv!mhrJjuWlsmC+PAd_1ennj?pAS1hL8-TJ&%0#%JFgt6)GO8rtqIzx?_zY5=P|3E^ z)?3&jou1atmlD#Zwoup%uWZ%vB3|_w%3f0EvYK00$p>x#8KccCK6KTYUK*FffU*B9 z<eVLjUWoq+PL(|&KFp42=Dm24szAMO2)gtSO0W*MY(B&sHWbr0_AvK$B|DIZwR|hZ zX~gGt%PO1mRe>#NzR0S!m^0W`V4Xca<3A|Hrjz&wZ*AmQwDZ)KV1z!vm-!!C>dlGD zKCHyb%d1?>ylKjn`ZEYFCd%&ZEy5EGj<++fkW^DBZ8jLwO^zhJQl2`lzAUa8_gL3? zC2e}**(`JHXx}gTuvneukrK~fW=-N!IPpn#S#F`BmSvz%5I8V)-g`M#%IW&&VG|QO zcW!Bc{d$h(K<X^$wC7;dN+fpkywSKL(}_Q=@jlk3JS3bWuX=uCq~OKr+=W-+uF(db zEP0F;uXv^9agkNEj{TFpHf{P<&-W`}B}0fD#@9=sSui-cROX;mc(!=JjLF0xF*85m z1KV_e0p}s_&&IGBK8K+z332T3INT)J6c?q-C$VFJFpOWDDHImI{G7d5;M39H-3Vr} zpEEh!8RmCPpEn|mzS85iMT=#no%1=%A_r_D&<c@f*1avy6OY)Xx|E%9%4UqejTWyf z``jiFWpg4OIShW8b4O4qLBPxvIB6S1_;C}R&fKFIJVy?9!O=Qb*PaPE`DTP3KPGE5 zGicDwurikW*Oeh(hBjj3fcg;K_{%j&dIwSB)oSC`PMcRLiqMXVO>|q=IkMhDc0?Kv z7ulYs8fYrk#ggkLKj#a?*&`k*v}5jG!jrX+ij^Jqpsa9lqsvd%hl9G=MTLLzo4s<< zE=esIx>&dJpZtn_ofSY;OxW?D@M>_LRn9Gajg+mgTI};GoS2Di+4+py4ysKuvIi4L z%qd4|gjmOftns)nVrl!=r)khDw<xzSyR=M_I@llBby?E^q})BHT)dQ02F25_fi-|^ zMN`ETX>y4Ji+eOw3Ji`_RG;$vR9-r2jpw;Wm+2q!>Y5?jcGpYl;q2#Zrp^ZyQOQ$@ zR^nX<-ZRBV0WL@7Z93NXgzX>}mUb;N_DAr7j=S5(C!gPw4h5<3!n-=EF10XCt6oi6 zIs}>?o<M0>`46ElRh)i~2V)8sI~yRp>hyE}X`h&`=c^#4y?5J3c3=Pez*gVpLal}# z1(}w=O@u6b5@d-$Plk+C&Ga5&$$HWM;g%P$^!NP6-f4ptKt*`cR9BDD9Y}f>uG3$k zid>1%TUt+kekPT~DRnFk3M@I#*On{cp{p7HbFFRG-jOpjZ39Di2D*sSWJ;vP*`Q$1 zJ0$-lUJ{3lZL+7S@j4sO(>cGEd&qLJsh?R}>OkPV0SL%@d;<E{HBkv-5SF0#5};xB z?CJ9M@Vb=3jufO*Z_QSz+xyrB828~wrtuFLA8Qy{QPBb4-?r0V#nazOF5RK|@fq&9 zG<UOMhx|XMH`d{P@p1enU@#(&atu$tO0V6WvJo^^(>Jg1KluM`GJkkI-v9v#0C4!v z_Kx8HPff39XyEuyQD38RXL~G;_{%G|@aD|Pb;X{9dN2f0#~#b$e16Mr(<q2QR5!Mi z%iwNZwY7U~e+YaBa2M!~lN((5vu7ihm=vBYaa~(mS^KflmwU?o*P3L^k-OS*6<>nK zWo7-9mOMt=azk0!#3kR+$2wM-9`~~r|FgDYdC|hWv*({BW^wg)D>LaHtGD#TU0B0K zSEG)^VfE!!uEk;9Js~Oo?hRDyUMKU&cxFLKk(d|x9%g6kD{TwPA;ZeA_yRZSoj;Gs z9YvmI&olj!J&?@v>fXfIU8bB&8AWXKF*4J1Us9adrUhE|?SlmJ_M})3dv3L1+gto3 zpIcc#t!iGvUD*-Z@heLO94R$*{Om;PDo^;AVY;SlAA!l5f7m`u@IpW;vEKLn&(>Dk z$PJsHr<CKPXOCu0W$SVFSN{i3QTA`Aaf4T?aTC!!vPeQRAUSCSxJY)q%>?6P?h@`s zE`(L3Q0ZD@%K97WmeT}~->5H*7SfuHUZ9!R$%gR}PH3Dn0E~+<xBSEpTQl9jEskr_ z)iWosP|Vx!tB=?NJ#u7#uDdx%B8Q79h}t*e4xDE%e>>reNgZw1m?;~c__i}|5nuRP zzt{y_RJ8Y-f28jle?JofeO~5~b25zgi?|6X1bCu5G~!8w`<8)qxrydlGnNmlURr%( z0F_Nuc_x;1QFO?u9}Nd)|Cv26v1}nNE5MU+WSgLm3*B4IPp8@UGALQ(yg@t0jEvqF z2<EB|69@#N=MJJ0o6Zot^Q$65$%cbysP4btvTkL6QR4eU=xb2=ur@#&LPnE+=|xZf zRS@~H?D1l(Jrtxhxx1`3u2d7^`VwJa)fdQlst5ZL1Fey>n3W=m{dNO|AvEOXY6X4= zHj+6g2MDoa&MdHv5D26^dlk?N96AKSae!rMZwebo0lXE^Tm}4c%uF!9fdHFY5xbZj zLUOLa{2*RXxHBXoG<I>^5)-d(V`+<U;{$iX#QIDL@fRsJZAVL=91*fA9qvB;1WCeF z(VG;~MF;ao<Fw$?8@=}-0qax$GV}+A<k<u{^dTvHbk}1A^oUrh`fWO1R<xIjCw?>Z zcDw)N1OZ*Vj3C{fKSR0;Y|+s$r=nP2@Sue6-<=1h7dIL(KF!~OEH<ihU(gW8d~0q@ zg?r@yq?25<&=6E?{gFxlgD;g0RVA(1<pTqpM1yQA!*Wo#Oe{KCD-RAaBvXhXNJKR7 z^e#tsvSUHA;E|lM(kKI#5h@I$)%q$4q|9X?jm)X$B!=tLnLTsg@2KHU3%RlWu`@<Y zR&m|oO;Twhvs7a-N~d!$2K}_oUW6(}6OM)1s#1+fqrp79TW-4_w9Tx<=6X6!B$sTB zGNB#bv1>o_>M@_RkH+ydrc-&EeED-<6n<l`$!V3D29zNZH_V7zAb3fNN|f){yx(w~ zp)30*X8)X5csdfDqd*R^Y8!{)vBs?wm_^zM^CrINkygwH17l=VtwNBf?1nx!Q=>LD z0vU-|5jj)9cy0h)qcYOoPNKS`vkhV4Y%S<iG4yWQKoYw+^dI&?x;(@jnvjy!G0$!s zFn>@}i<aaGHs1Y`LXZ3emJpl_Mi^qF-Sss9S)4_$t3jySc+|e$4vB&<<Ton~Qr~u3 zYSWuH#u32}GZZc8mQ^W96`nC=P$ydZ!`6AWkDK}Qv=AT=nu~)Ox(XoIs}#EIn`!M# zsS?K5lSvos;elA+L34eRwlX+vB>ZckIc$d06U(U-VCKD}6Sx*lGqz*YTozU>`uk{f z0KxUI&58X*ibV<2*;v|g#j&{f1&uk607b{IdtA~0Omtm#NvRP-zuQc~UdcocoBJSu zr(fpl{I7_`<XO-8zuvErDze2!eQ<)6Ui9opJhOslAh60}_!DD|ITUV+wS0$?PVY;n zNlc|}PuKq5<Y=6EQG9xW0sY&M8QArY%hnfUk7=y&x}On<W}Fbol&3I=rm5=}1;#sx z&m0s}!a4)i*C$a}NW=C$@<KL{D{LZDCdzwi`~Nh^x^x_JyO1N21;gcFR0cAY<#$Y8 znEJH<1co_MnD%ROH0!rMOQG9$6m<k-_Gdvm?_;5@UHV0TCf1qCHuq++A^KL_4)l?` z{R7!s$9oQSa9GbNKhPCY#p1%iN-GFD_=YHt8*CgRrLGabV`Z1wtxxkTU%r&uQ8)By zojzedRnwe8I<>v&lp#ow!^smq2+2o#S!G!=FoZN&iry?Ut>)PoVm$t=mc#8t)aXxh zRN=J^CHXoK@?%ML>%O0f=i{({i|MUtD^qOSU*rIBeq{xF-`eRV*8cXWO|xcRQDZW; zf^rikir8q2-~0(+St*d$&D@cvAwkGseJ5)lZHX9yuG?3Xi=~{MhoMwunAU%!7@l(e zncS3Z9qJ&oxah5x<zKn^5W9UuP8Qi~A@?d3;%n&=gg==LhWk7%`X>S6(E6_4-#`Z* zml{t=bLaU>-;&SDnZwCdR}Hw_A`|jr2_zkKnMCsRzFbGQn~=l((CBb1-|7Nb3_y`i zW)GZU_qNuD&f46}EA^XaZW<57>Raq>3X1JBZ#Hn3LC9%siyPH1e0=SA&1B~ulRq+> zmGKvqDkf-r8|bW@&n;Xp@umh2wmIShj8)IS6|kG<4m<63T4%5DOTRcvPa+^i$H%&p z$En67?qL{);1tQEv&7%|Q{O(hKqiQolum4qSgsNF-WTyYC+eSKN0qTW`aoo%0VK%9 z%Xs4XgebZNjXo`49AN_xI^xzN#x>yfYM%m|AJrp=Vae%q&|FSJ|ASoCHzVnaw8py; z{emE2-;TL5m{h@M;LVRRVzw=*X-kIB8%MHCx^$N3$q^{&+?~;+quTGsI0n?H8jD$2 z4Q~K|B<xrl#k{33oHf`REJ&+^FBLl;?DS3iB*y>t#>ocUC5hlF>=U;<K5egh<vLxj zZfz3ql3K%Sq8btU`L)4cHR6>9Eh+Li#>v~v%zfXzr#BSdEO_h=Y8hm!gAFn9t{*ER z`dx9Se~dV2mg^rPE&Eo>dum8a61>}7rHx)Ha<?Jkqe@Y`@#BAFQ7OY80ejE@0C|7@ zPvc-a8++&foJhpdaoS>i-01^4;lbH3NI52?&s5r6cF~gCB1w94bShf-g8(^EO&JKB z=*pb?^}02J4v3VP&Dm3C&X^!V^Wufx1#Jtmmteb>{G6KH6&e-WUHwY=9<m~x>>=s< z&xD$^F01yKrAE<lgx58#?!arW{#Cj7c#oyJICzgy!9g=S#|GhKT7RpNZpvflG4cZS zdkP@iD+)KCjapn1QP|MT%nqP1!7nMK0AFlqiGt%Z!p2-rMn8KDz}Yobt=T4G(}NHM z{b6J*TLLrb9Wy`<MdSrhV<{D}#cT-F;+U66U4%;&ArxX0^SVBr{z^+TWkhS0l>j#i zSCa4xm)t}rnIoSV<-StqIJWg(+b}bstvOh1$7Lh23#r%AnAprF3-gWr0dXagNg{R? zbZ{?qq@LfkKFfCFS~{>BWlQxT6u(E*&|=qQ3(_P)g^?Wn$CfsBP_Z6%qOp;#izZ4- z)kYDRy)9LG+>L&mTDK_xM{pKqCS>EV5X7u|mEy!V$hx0UeJyHJh=uVvPJc|@>&5Xt zYehdP?FRNdJ)ze`aI>>ItC!ekCj~WKbkpz;!n$C6c1jxzjB`k7fM2_heLBjU0f;fF zH*v&1qcA_*wk1Moccf2oSE-@RJkI8cAu-EvR;+k^SYojL`E<DJGnx-!q$yg^ztf0g zSz!ky5rXk;H|9COU4oeq3;vAbt%p{cm^8eY!PpQi1dF)npb=}?YIqq@nY96-A@P&X z4&TyMs{*NdPe|F>!|9>(&}ziJ)b4PVE2#_nW9w4`%o<adt=IjwfMmb<I=(R%2=!Qx zH!OEmfz=t?KRG+M$el<%LJ%+8CLx+|@gCq)!!%XqV8pq?=BP-b=BaW-&45<f99MtR z?<x8LKxC>+D%8ZuGo&#$5DFI*k>@cjpGytTpQ~>PoTbbAe|e%vN*gQJQ&lyAz?G~? zagMSn)q}8zvQBc~$S}qKrlzL+r7Q_oO^}=oO`q8GZKC8O8gnW%S=G$kGP1GQu>$o! z{}W(+N?PTNk}2dWB)L%kWy1+45LO+_Da4!uvXGshW1ulA!WkQ7gj5W!y;~@3CLVO_ z2{Wvi>yIOOMfOBv4Hvk5m<;N0ICQ0b#tG&t-^WCQ)QF49=7UVlE?&RWYZxW|9gSqC z)De(u?U>ml^rjD!Tm@^97uUfkVPz2GbSmEvu$GwVM$7_JIWG?l%(s}FmxV?=_1>@R ztKt3v4FH$ClQ%i#7e6*1>$0Xj@i&O}^d21y(8dnT>R9D-<vWM~EkMbh6M}Q#eAYm7 zWs!G`F;aQqgoseKgOPpJn)TCiklt!A4BMi9TnOfI1q7ZoKm;I%f+kS*H_}7@NgtiU zEuR-j2X2IWS)hDa_OHCOz7fd>78IJ#wQ@GwWrZbq{j@(8<l92($XrO}$~VmK;Rtji zCAI1j?xtMF7>o^`&UxWDjS;eC>wC!0K1>|t3$EDvC$#U{O0W3B-P<WT%~zT17aQ8| zk^UPIY(Kj<-VVr{KnV_<85f)&vf1K6@$Vp2mB|=X7SuB8ey}5*vHBj+x>I@|lh%m< zUNrD+UTC&QLElpT6YVs<#dr$Z2N%yFBJ{7B=X@^os~Euayt5ySzK}&lQuL~*bv^d* zCL{E73XDvIzRN(c0YAK^;7V9dihIZXq;YGjU1p_m$W^#L8&%u-_8+d)n(DAFWm@=3 zU}uEx6r?zNaWGVwdV8^gBcMJ-!Ao-*Z|~SYMkYj=?#Lsro>OHck`cD>rXL!|=Z<1_ z=?wj6RFClMR*x;x6Tf0I0XdP4HdLK%L_Fmf*jBQBpf(y!ZkqJpD9UtJaRIRAwxu>8 zc=5JkM@$#a*b7PKFXZ%B>mjm&50L#?<{ox0zM3Kd)qjd$jq`|N$IBO<(qx7;ny$ca zJD?p$1fIN!rQqx)BC)R<VS<+|HUVN!j+Jkp$!I6~eUUi7;h`>Dy`ieoApgEHoEW2- zT6@FpyGP{avgdP-J2!c^ua1D}<wAf{>1vJT#wmTYqwy-w|D+Tm5cIr#k7R2J>+<cd zznVkDMO7N}QWl#6uE4i!mj`kw3WG|LtGXHbQ3>UTAM21ml@cct|6NZl9aTDI6iU3P z@VsZ9tkdu<0#+-N&zL@STnzLS3*a8fJ3<2uVM<cO6+0-rO&34`i)Nf(^LiJOB{&e* zx*L0exs+-oiz+mTC9cyYL6(OBG&+Q%*Aq3VtdhVvikiiLWG~8>V8nG%!V}u0x?~3! zm?V>x7odA=faKZN7`aQA&q}on@(FgyN_7Z!WLbA|Y(i<aDE|e`=~l=J(?Yrmru5vT zy@ZUVb21-%3By+JVzGtVM86vs{S;GkaB}P*#OD>S#^<G2Q))UzwmlS=s_j=n)gu_s zv>*nMQdB;F3zrNv&{N<-@141(5_TWQWvubOgLC$*9!9e6A3eq`f`aj|VH>2SM;&WR zL9%hb&y&6iOiGdi7zTyCHijChg7*%rx+e`BS_$4y;2)af+vd%Sa?~<xDw;1tikX(e z;AvDN*zcpoVN@!`WeSmD+6cr~3z;GS@#Q6sB#c78E?TS!kP8e~ODaq19Y(bjhu7wH z=r&5kS9BTT#>Zx`7x6a(1g%2DkXn-$Rhic2JXmQTe%gREbOdR~UOsW-HieDV@*=bg zBQp2y{-HdP;rm&H;kbr333u88d{)wMYY3Lzp1y3sGVuADe2>xEX~S89oNO`G_ZUf` z+qmZ0b>7c1;h<R69rDY{txUrEv%)PsycAfBh7+21Xh5;D4CAd&?(z_9x^IvHJTwe` zVZhDny|os*dy;-{Fq7+@L&mH?$y;{k6o>CK*m$VU<n0#IghgWo^eP{V9m)xs)VDp$ zzOL@RVO##T!!JI=J=Q0hg_1<K>1g!Fh?>fax{m`o8<WZ)M6+okg0uhg!c*}j8cyl! z9M~n9dw+Tj(@s#_FxON%LbGhb!gG{RT;p|`9CqbQVBlN&rso+6p1yBfpz^6Ts%Eg( zqD+C`F0ORphaJ=TDE@Qb-)qiGVA^{N0kYdvU-wVFos^4`ETLEKDGHOzSnD-K*OhK~ z<B`VfCzj4uqEhwm<lT6zpyRwfEhW1o5Dz~swaUFJ+4S*#K7W6>_s=*wSu=O>t(#T8 zrORI?;~oE0*0(o6v=sZX<mCpPArMga<CTKb%{G0Th_OmwZ$<jV$0KH@RZ!>cEX8YW z9)0Aq3Ill53*_>>KgkH{D#)hduIljdp3I)4ZymA26v|~UE-&PIV`a+z(>$WUzbS4^ zR4xDLTwe4*TWJe;V%aXof9>P45&BZLP9n?{>5tpp^9bl2a`+QseHcD8Xyr1<E4f_b z@_A(U`$3|LYN~M8fZobT1fek^A6sCwD?4xwJ?%V$MLXcFw9AGdc2YA{MP>UDEnOS! zm`Gc<&?xz@aYaYoi?ZqxF0TnMToC03pl2nkL;j$eWpIl$S&L0^P8D*zaL43t;pSAm z0W6RjZgjJnFiqJGyvZLQQm38jXlu+;iNpzkP#2kWD5`4kQmVz{Z`OYEHvY8ogF677 z%i9#*YLHWUBDpW%Tze03X2*9>0$m>6#xm%e^?0^=H|kC-0{U@}=)noi@nk1m3Nbwz z3gljN>A!#K-G5#%!&essc3`F`sDFt)fgMl3ky)|lB{@X*DRSR^v<?vl9_T1|zc4$R zJ%!sMQ&OV8M&`)fGJnQP=B%a0S~@2?-n5$ZmOWFkN)HwU`r~@Q4G4nH0Cvk1ItR|n z!hctN=eJU;j)CzCl<@t+;KsFBa@?_xb+IpiqF8ivvHAHBbGz6I>$!%}JvhL6Pi#NX z5^wf%1Y)K`YucUY!4?r|%1}<tM0vfl6l&bxv>J=SpnikHJ^Nj<MH3E>%XxqDM9eMN zr`7H1bbGpaxV--uTE_;=;hRtbW1>Uhvdag|IO(dC7{9r7qBZa1eB7D4{Ec<~i#DMw zN;-b1>%*n^fm+S5@A>?}##raBr~FBS?q%-W1Us{V)B62C(A(<ipGzD-004IX@Z0~F zRf)5mk==iQb~(xemRtM?Jy+CVCqw~NLfN`7lq9th(gh&oX-JceiK45UkGG9otqXf@ zs*N-gtRbox*iv^rIj^RoAqrgfEohGa5Zx&b_as-4#@n5c-NZgVU!Gd^?^lLDM(<Cg z6x;&hRSZtJLhIFst6%{Xqvsj`&MYXj&Vz6iP9{jijf@k?&6R_)Mj~<Z_sxTGnnnoq zX(vxlhArNHAz~0i@llWAqSm)%dUs{;EB}O?a`>&PCn57<A<jv>S(CPG9s+L!5RNqE z%%-!G3aW;!CR97}4@`E<q4awMDwUShq`TWf{?Yc3v<R-<p(q6_12~IVs=EPvBTX=! zJqZXbeqW?CpvUdgqvW#voXB|!ll&93ZnN6?f}`Go(ILM8ydxfDBV_=aVaqe+a|W)C ztL^Y(6Jb6`!dS)oDE8+p-cXozRDA3@J-n<o^M%e5^W)qZEjc+dFzX8yK$k$~C*g$R z)p&xM!c|R{M0nyZJYN((M%TTL`QtREUPdE;Rab|JvunR|#zzBDxaC7|PNoTx`K0f~ zu!DQd<<7*BfVcZI1dv_3*f-LIBn|nBl5C1&it`#eT3%;Gkoj=O1S=jBVD0U3M{vDG zPdc0|QN>M2;RLR|4y1ep@_73cbbguQum4;*>qll7KX7qySkm2tJjKt;R_3s`3aC|N z3#u2sXAWu}+;Jp@+X$w`WZ8e6n!psa3qrbW_r}#M<Ly3&=;|Top3ViC;6m&CP3K0H zm;wwO`~d#Dp8v0e5Abir{eRd0j`#JTbM~+|F$Vr06Lkh_*IfQhEB!aH{tbHzdwM-r z6I)|DM|wvKBXfF36Eh1t+kY!nB#HrUhyVe^OSi!>h1tXZYMHXo8{hy~HV}9~Bf<~= z5XG9MYUiQG_*_8)h<$Al1wPBd>xLzPVU%=>D;K4G+=OmzMc-H>mCFO|vfli7S#mwe zCddycGE7v^Ek@k-$VT?2DNsa?o2=JkiQS3>PT}_(`oG_Dkpl%6?cb)7Lj(Z8_}?Ga z{y*J{7Bwlme_Y(&E4BJdLd619MU?Y_8nmoZNCakasJbmf0}LSI1=cP`N_k~houS_z zreX@MR1~w%`;lY&(=2cf_y-xv6f$jP(y8V)Wk;dZmht9f5s`Kt+KR<&v=?PNDLU0# zihGE!+F@44C@W2rzZLqfZe<yq*S8)eH<t~|+EAM7JJoc!Q`TB|HNIt&w!X;ywrP(U z6@rW0kAibq3BwFvKhxlmY$hr?=J{MZPQ=_|&^2V*blaVaR764`QaUu<lyj>}s+DN# zbo?r+^OZ#Iu0_ep12bpQm}7$x3@fkS)B+f}h#4j=iZtYTtI`OSEL#B7!3t83%1=b( z$rSvN0ucFo9Be(=w2(C|FpY(o%>h|OYfl@7h>2`=5={-7N9=fDZ)8Nu)t7;lkRE|s zplY@j3Jkypw>G;(VsNcNTZNdL^)dio1fWsl4Wp^hDqLuD=dI|=;$rlfjpoE&Hi3Cf z(o~g%{Rm^y43(G?N-)NmNKcvu;OOh|()Ne|JL9B^(3uR_TdV$QHn(EifQH)9h53df zNC2?Z+~vSoOuL$q-R`k>`s?dP96}<@N#bwv3&rn+L&8&u5jf|uT&*Z|Q!bBis?tMA z&8}m(#6*W|)4s>K(C=7Q09gpVNQw;{k-Y%`_5+W#EK=<lmHDa=i(zns6X^U0arBUN z`HB`)`-=eVL|93z*?-lsUjiYIp(PUI?ZP_X6riVf06)JR5krHfVSxU?JtYWixR;8= z*x`5Qx9S5NmP4#VDDJ0vXdTU$sbbL*i{`R5FI-C(-y3GKw8u6u-qTMV(I){#s?X>e zy)5nplzkeT)%PiS_1S&5uaL7jA9B1M<~qx1GU!kXr2fQpox)~z=9JbWIS(y5%ekT- z6a6STBDOa}zS?zduvu$D^G4!c(-rc%pL+q944s5l;kS;bQyFH-*2QZF;278WtC~^k z0UuQAfqIAx2#X#g#dhp>>ZvzxzVL8~li9L|Yh_ZSpw;=srN5s01t$UTq<^_JHV`tL z&#Fupz^V?oe_*Y?5syWtGI#0W@9oXp;^EicE{%nK!XL#sPhmE%94(?@7L77?Rl!Tc zi57gON-ycz@_s$Ae{*mWFV|ykg?We#E<i?1X_sTC`X3GV)8<Bnq2k3s{miBzMS1r} zj<yKvfOk@wo{}%dDg>*#dSZ=pO;Woud%|Y6{k5Z{*FO!!DIksj@;L`@WYT=`g(q#+ zFcwXDL7)>5msxFQq#L{jkZIiNkgKiF+KFfDm?jWHKKwlv<zr{2?Xm*ack?21O@dp` zfV*^&VPD{FS*fDsI1unE>Zo#-*9*z$F%YDWC0<T8^3KPo_I0}IK;ud%M)loso?q4n zXub`6f#K7U)v_E~^PU0;d#KBKOFZ2>6@1-cTihvd;D2=f%Zu{t$JCE6|I<YI1Mpr_ z^wqHG$2pMKLGl#w@S){^FOK0G8#BwQXN?t~fjtRIax%;t=Qh6G9-W8Krsz8Mb{lW` zXjl!rHF!o}@;B^U&eU@EeMK|%ZRT2TeA}!_1ytEjhV2d98Ei;q;A_Mep=ejr_FMur z#CPh~urdRWpMEzeIUpv6Qk9hc=r3j{X@#LPNho1WBB&Go^_r^{;<m=&xWBVELKT+z zk5_uTQ>CL;;UJtCWF9>yfRZeUmZvc9gLYlg9laO`?vPtr&}3bvrF}8BHXbb=lS<Q? z^2qPlPaoia$I?U#U>lKtvGn8rjHNEt|6z>xs4LhVGb8xU)nN<@<q*#(ILlE7D&vZS zF9s;XxwFeA*d1MCnI!3ZZS7RWzaf4J`a&9U#oiciDLxa>?+hQ=nfAV$9yTL>!!0}P z?|{A8o`(eHaqhZFES{$Xwt6phq)7QZWmeee#Z6hQwl30MWmTH!z$I2pZcF?Ga4hvE z`AlU$NO@&-;q$>j?M&j-s;e<Mj2!o>9IfuX3@I=iwECLa={*78iALcTIntBeuf4dA zae@=BEPVbb>_tOI;wF3jfccS2G}=}q+qc+ZLh6`Qf!s?j{YBDd90ZZtJCa)wqCJO| zUw;YoJLeM7LVy4&KEa)e9JcO5!L|taThhJ=7W&wx7_mKkPZzd3FR*28l56gK1?>)Q zIR)bgsMi!qB&S$cX2Eb!+on9vB1#v4u?}!oqD9OY)MNaN(mA@)Fl+z&b2ax*gCqeH zW)}+_RX?M2jy<#sGb&tt0Q^V#A{QWl%821$g>bjd-Q@zrB&8F;Yh};es;d&2Ik`x} z>`+>3wVXN3IY!d}$nP0Q=p?H~((EA)Ekr4xhCCERWrVA?GSWR!ee2jEMeR==V=}~1 zP3{T#WSd}b2<x)Q_z{tCc&G>Pg~zN=E;$JaZJdTyNKTCm4(2FL&{N&M*YVU;Dw|Rh zGQJg<-e3sZVp_g7<6B0n*GmjS55Ynu;o~%LHXv$N&#mVAO-UeakLkMHChGRRrj5&p zJTh%-TVND3(A40$bw`)(g)89{Q5@<l9=46sXqOd8-GQc-K#(OvucLlgfC1Vwq(scF z|GB?y%s2UA-1T+rTZ=ms7p7!Yjyt9KEn~c-weePSvt&e?vs8*E(u2{IhQ$-)2<<w7 z{PyO!IiSn22eR~y2)K;p=}Ao-F4OdZD_XrQmnF`?+LmQ@X%v{Fk?YAGT5HpZ<;&*1 zAgmrfuVhv3q7A+4U-=BPiHZabLGAay+^JYl(d;Bt|0=FNYj^=fb)ADv+0b+`h!c$v zAuSZG88^o9BLNaSmYtjAopj6J$GfrF<9i8Bt*uolxrqO6W5GFE35V#`PKonCsa*+? zE*9e5q#W}mS4Qd-kAYMHWnT(Hz}IVxD82Pp-H^&C94v!0*UbxD3;v8T%Fxuu*=L1& z2!jWDGJ{jX;&{`tRwFed4Qi1+DI|7;>sKE1>e^#}42WMrCFItGyu#LbJ$L7ZyH(|e z$q1woabEn3uqVuy!tRHcI6cEAJDLDGDuE9vHnu`@*)lIR5omPw2&VqrUeiL0&n!j} zmnQ+oj8Q^KU9Z;gBa6!r4~r(4Tt0Y+wY1y`x`O<ryn}#&pd-3H>%4)HY6F(gIihv9 z*iwcv&t0qJ7)TGq27$(g-K=q>zZD!92zkO7=hO2O`T0ZsDe#7(>egx8?sMGrD`(IC zgo8xwF$?XSH^D7Z&lzdAe)&D*q)+}L+QSvU<V@tz*LzF)a#+9M@MS^p-W}&MUfhDT z@K0!|`W$Vg3HUxCi5nk=Xqy`x8lYh)d=g_y5m$AnxpXTK#=cp|;kR=uJ4epG!9tJY zjV??}U1b_i8PX@&Cy&Z>qe45BjMou1U%NjP<gNQgJ`U9|tK%ZIW7diGxFTY1=R4xP zm9FVRDkaqZas~*_O-0n+Duy%^mO<>kds9Wt8R>Gr0q`O&F`kzq1xcMpd?>DANMrj| zKHQ5q4{zkGYj*Uz8Kkc2O#%aL?%EvbcVQ#3e^FEO=MK_zn_GC4ILk_W8C7$BBW z8N#fHKNrV+m%oWw%VhS-Hq<X3MN+?j|2y=;hdfg|g8%?v|7#unugSWTk)8d2VsDJ9 zjny$L!cT9Xp?sfvl$9+T$EKD&VR45pr5mh8ezFOJrq=F?N~*SmY-94zYb?oDqZ@KL zA+`onW}2BPUdL)>fFk68<WveX=1y9FmZ}AGp2e1<hx)SYSnjqb0?&_!pW3!>XV>?0 z8a-c`J+U0+*D5Epa`F9oOCarF$Mrb8Sb$&Z#lc@-tLL)z9qLwPMuzh3t~7!CjFvKm z!#&T060d>L{Ou_MeR|jJF_%j}d^y_Dkt>g{EYN-A*_7~rNHV*jE8ry5rQI(>g$AtV zJqqcHe;>vp-wwdVpjH-DFKt(n=SRCCzNs4i7Qm_-gcUf)w?YzVO)wvHzlNlZeqG9d zMzNh|#@O5vS~%P~^<=-|!M{+jK9nFy;9l8p2aB^h?6lE{LOxPfkbB<{$3TEc`$WOP zJmzvQ<s{q+GdbJ(!TbmPdFe3a#?XF;qWIG6T!)|%@N161Y#51pMqH9o9nMn&6ylrt zCREhhNVrTJ=HtXr5o21<5i~}!0=WZn)ulQ0VAYTBBEZD!@$7%hRBwx4Z<V##E5U3v zj80FrZ3;Uye0Etem&><8^SRhAp|NU43k3sJGBv)W_1u?O=BF6O@dQd?uQh6zfjjrC zUWO~AfZ?s;0P`V=Ga*u<>2{u2EgSrP<oJ6`@uOFAXyyb3z){)YGS@6mc6k46S#Vqw zMD0536pyjVf7XdxZOqsG{3gdfI+01H8Vqa4MD^2p{6D(RsY{S(Th?XUwr$(4F59+k ztIM`++g6ut+xD$}-p(0!|ABnSF>~cu5#Nl+OTNtWNN#GXU^wUTNFA_D2l5h}uW;0> zj4H&WLYJu4Ax^B#uJ7VI65$p%#zb<I+3%dmmD;=^i!&>N=MOQUZ$grCMFf($a1&Q9 zF(Dnbja5>2vZ<qa4mXkz^(yi*^^gqVFabup?jMoQ?_<e+aZvLrBaENmik?!hsP;I1 zPKzrbNbA@@Jzq0eHakcZ5T+wmB{g^yHWlQLZKn%ZN$md(b)13&ks3L^y?aY{>)J2Q zw73m1d*58}5{ou~HO-yM;J9OxsJm%|)JH@gvRVsq-v^v>vyBsw6N|dMfxF;e(hy_f zumZ2nIjM)moWc#}wyinCixu{)=RG^~Zs<|Ya!}&)>Qs(NN*q19ur%m;Xv?b95m;zV z?LBPF*f?bE+b5LYYyXMP^eX;BwU(#2kVbr#Q0Bs36@}>c140SuRD!bp!l^rly(eAv z1-^xC%D}_wa9C8YApaC_82x+^oZP7Di>g-STIk||+kvDdvl+yAHDOA(pZqIpS<Lna zTGWB$Mft^we=}y!P#W6SI{$nCkI|N0ZbJp?0;Oi&o_5d$ix2Fb8h?zOEI5!q2@Q2E z+LcQ8-_<8GHDrGi%snfajOuSC3$z$ksGH|euzNB=A`8CD=#@|DBr)ryc4u)08(4Fz zR-hY)FR{M5l#<77B^Y`nW~;TzU@#GPl~{|W-qk-Cha1PIz4tFqm#@LFh+ID-N#34D zc!vDA6`sjpIYO--Xs=Tz<Mf=8kBQfcsvQ%x6|3N9DlMJbUcG-%um8v4=4bt`QT!Da z*unmbJm6&HXkzjodB8?_BPNpp!RLfpj3++w9x%SFNvd_DV!ma4T6(EE4j8P8tHp$S zt3+?7pA94Nh%lf%i_^*UQg@PhwbIZKz*SXI>$QX?G4K{ChQDWrJ#6MBtX2;Xzo9DE zkXks58Pzq$_c^z5L6=iUdtvmh!rY!sTZ)yt^d%J8sFB9VN7FJFkdLoCd0;GDgJb0p zr>k%ii^m7~N?zGnnP0(a&((^Qe1>5izp0Djr(M{RR*~g)(^4v=H2LHW_mmX-QiA63 zuTxO%tK?I9zNfx69KxJnWigY$Ps?H&l@o=VC&NeaDO8O_#MQGs2Ih?_;Znl!5*=E$ zbew@}8e1%)T+tV=zM8}ksq$_7Gsv*O01{p?MEGjj37Yg3q5N5Jc+B%GiwwgCivv;U zJs;E?-9VaF7LyIp{8zj41GMERT_{p9ht)XBYL-dZuSUxm>O-^{+=ly`Ha%a^VH=O4 zr1AhRDWPMr9J4X(=rog{Td|~WiT|gnbG>-Z7pvKh!o?=SXmPm-&2@hT#FrgN*gmhs z;NZdInrwZ5a;%W-?H%V(AzN*Etv)!u!JtWkMnEO>t1wfx@??EUz~;bO)Y=E!Jw{FH zPgX?ckm+>h9|M((0xm;~y|P8f{PiEva!%yMsx%N8vlyvvR-tl-;Yrwt`Bv4=9VC>q z^THmHiPn(8kgyj5DF!4_=o3mMLgY;dqSMMkEQiQX(XLaTNVi66xzYCXB<<fyq|6Mu zx%oL@vi<IJi>v1dGBvR~ItOq=M{+(Af+81t3q_f%Gqa!nb6-)V#4bug0RSk~|NU|P z|Fj<`6EhnV+y8+rc&wZbM&fs#s1dfq{1JzBZ1CJh8?;lJ8>%B!9VOQ{9LH%ONJ5di z2>bzdEt40w?0S8tkTK#GiRm^VD=twPd6~bib8`A=Dhx5hy_;d?O*E8c?GxBXZ=K8n zV53y{>>`OORjgF23p{vwkvDVL3LMwbEx>Bg98{<J=C{AGDTysWX(^iK%R9r4)u` z70Qp>ET{l1l)WDQXl4&)1LRcV_wdKW^l<aNUyQG{5_>+eWwU>-Ksra8rSIyMTF~y~ z4ok;tS4%B9>atnHk7E9Gp!jJ;c++jr)K!Klen0Vnzs~&zL?U9(h?<s}4iz-TSPHNs zBesaWSj=xOZDzZt+<wR$-WNYWpT-=!IiSu2fQ42q^iG~xS+mrJB1K6hWw@ai=$cGZ zl+C~C1j8bg*y1te`_tWx{*&vlU*pJ^Bry@yMK2je{J|;bPjh;LIG#jSxU1skCOFyc z3unh6zyj7^`NtD=f#t=n5>F^BTy7|w<HCTEh5DweQS1>^0DTMt^+Q$wcYUKzpcw3t zWdbj#H0+T*91W2{#KF_aW6;J3(<pr4<KyiKRd%@$TR0dMl{jb@TNrgv`GRhC`wiRu zbN#u+`1wi<oQ;RC+gHn9&*bg)O~~cx=im`@^F6goZWlY<**n{L&^TBt|NDT2%G3MP z{Z|a|A9>Z$#7Wx)zdL0XvBz&O_lKS5%+?cp1k0c1ulvl0DEKgRySuNRukVY&!rqEd zcGtnqhab+Entz3b4+}3h=3V@r8F#!3p56}b9yr-qqNU_!W3fJDFZZuh@Ai&h9HMIE zYNd<!=m^Cq`-luUy77|Q*oo~e#yq%hJF&*}aZeOwnU?GD5(KyW{%Hf7dKTSqVTz@6 z!&i!jvgUs`$`%d&lo-+l8X~gnJ0WkDPU`?<M|wbYB!U>D8nKGt4kbfOR7j{gnP&xL zY(W;^35sUKOgjNCjCcq&>Z8woIYR;m8V0WW0@*=qE#SBymjRpoIml&3+*G4%W3}wd z?;lE~e9dLNxHNFlVm^OG7$AJamkUJ0HG&;7#gz+3P0aieey*#I9Urq3+tpbc-(zV1 zCiMa$a?mIcr2Kn1mkb*kqyOht$!yb!x6C;|yv(RCQt0;b%z6nTO77^#R*;2I0&Aob zNaogsK#zFMtsL!1JtFm&XeJ*!#hpeBnrE#yfQp2=7QIwgUSmMA2@~21<MIJ@4Ewb= zXKO`hth)v^jiU_eUPvt~@?jW6r259E1so(?X>NW4^gtI%P|Khg_$4oxqVR!mET$A^ z546KgRgTamZ~M~)=%;pBj#qOw6^a!Q5Us>C8NgZ6!79vCc3tAI)*!=oGiDLOFt5$Q zMB>>-D41w=)%D|iBt%m%Iv%~!LUk+J1$xG=(UOrQLTkCkqDFw1Gs5y%0g(0rUjkdO zNG;jMna7;0E$>>1?+UpYf5P1hIhU&HhGsaXty<`{SQ0KcWXe2PE*Ch*Xi4PcNBUT$ z;i+u$DPp9v#ujKqoz&`nKBUNl_P#IJ{h+=91P#qGz%;WR=M}>qV35EfW=Sw4tnmEB zYIfN(_~r+DMl6^7<Zzkj9S#YWQ5t$}uKNr3E;b&KIW+HyOcYAZ&r&3_QPa3K0Z9l~ z^?fnHCF=thjbasB1k4Ny^&vww;x!j&R%Khk##l~mAGuP^7fpCpa^Ue-dJhWi;YlM7 z&zGeC;)QRno&j|!+TPES!gK3a3YV?8;Q-C|89Pdbm%;d5WQC2o{UL`%lSIlpYgjV& za^G^W?r!!0QoKD1*cu(yv7(G-ntuol62zX|i-`g<ozBX6SF{F(fdOUMb-*BEtF<-` z&D&i^*F+Wwgo5d=P^DF4Mvf{N(5N4L0twCO*dNOG5V|sJpP3DyR+R;T91l3F3#+o^ zmS#}}yW)}<Z?9X>kCK<$HaIf(p)Y`|%aCb3<+L`c4(En=erBv^+MfrO4_o~_E?369 z2Jh@AIIWlfd(VS*Rvzoi{pfd++`1x0CF%SjIs~(<OU-+;IJC6+3e55@1!h#DU07Uz zU4?Rn7j&_&H-Z+cHUmwt#VCKO$lLp)8R^j>L8zCBzGi9RwT2AWa!NY^bPSKihzjQD z*)5Y8+--x?wMLu^ODML=cnD{RN)T_T2;o6M!~eM21~WHj96Xkc5GC>?IlD{caG0mU zKtx(4<tV$bj$k~T_%K|6)+LdUrj-w|%NBxa`dWNHbstN4_#h9@q#6k20Zt-JCD6q- z=AZLcNq1SSL-IrXN>(E%!)PCs8blD|6H}x~;36LDp?LM?1)KO}uOoBse6HDOHOBD4 zwtV6&q_ny?9;xSgkM03$wVDV}Kx%41$IwmOxnO#%zSj9#2=FyC%qwWM^=O>O@Q34- z5x*)kUbVWbfN^sWWPv8SctF;fbM`f`9JIx@+q|#ugJlk_ZU}HD7>*v!VtjfGc*Y(v zM+r5VN^f|8DWzrjNl*xgi0T=_v${wdvLh}N+qb(Cv(^`T!q&D$QuTsh@sSc4{3PFs z;xMmFY@e*ALD^v(%0dOcC&ntDiqUSED3m2rOt7Rw7(?0TK)VAKRw`UNO8m>I%FW8g z@`94KOEA7W5CU+LE89eyx8k69)?T>x;h<QwNPL#l(Wa9_l@FYm>`?#-P1GSlL}9YO zDeU?ki-rBUcP#qfeJ&Yf`qdyFe<*xWG;atP1Q{)lrtVn5d_yBS;=Zf}1g%{ZJ|4af z-92xpcsIci_c+_=1cmmU9*B`<2hVURxoqNt-9-Pb2jIBG%qH0N%S;z&Q6*|j)b?&h zHPvWv1vbH@ojHAk{rX71uW%!FCJDHCXm@))FIj~M-{28$M~ynI|3a7Tepbr&v%2X! zfk*|;jtMJW!z-H30+a2r8%4h6(9-91y)INX3JpCQ!ReEDxsLgLO^-%L{#qF{IFdI5 z%RcM*!t`l&fO)C?gZbC`2qg3|s%`Rktw9ac-gDm4s+uuVh)p$0mvT5^Za{FuE{%Dm z=S&HLCK~K#=Db`g&89&><b6p9LIm$Pyn4;$0%(EkGz05YT_KSnJdW^+Uf}dD$u~m? zZDJoL63xT<A~S&^#GiIxydXbJkvV9MC>q*)*NW)$Tu=c}1x-pL8)5WV?}>=m#<*}E z7pf=w{?KQ*&q;ke$N`?SrSKP{wixEnClz5<_Vm(sFah=*{;oZADr*c4cX+Q+rLjl& z@nNgrGf+ZW$HU{%N>Xv`zFvXULy1ssh3=@PkW;cour=|A3W{PMe(H@>RMB-+??)s8 zIcJ^$p<D~e2agshk)lH-uU)(=XHDDC5nP9Hh_wFRpbe=ZZ2^Cm04h)BczX0zlv@bo z56mu~)3I;WL-$bNWh`fJfgC)Hhn#Y3@U7~e)`AjRhIy%wwxF{SXFpI$6s}YbKLJeA zT`H}~P>#Jg=<XP&0;vIu+G$ar&B<wdr_@!C!P8A^T#t?ImSM4+F^w$^AwNoXY;yi; z%b1XoW4IEgYqnE9DLWOzQQk{d4nF=$V6(bYwTm&E&)kFAxn=2ik$Ee(kk#J6{xC{Y z2~1r$68U}*xZU3EYu_T$eVy1=G}uh2@_GZUv-gt&b%WS?wzlXKOyjA1?(L`$nphAv zW)B-ut7wUA1T)UiT=FJ`G0W+b<$4I034f=Wt}%$A;nO$~G251TJa)DdywDj)1+I&h z0}_&RWe{7F`cU3y$V8D)yi&*Qt5t0O8s}=*cdB~<Cjvadu6$2k)O-M#B<r^Nw&H>$ z=J6K?B4BXTN)L1a+iU(meHAy6Ql$V~rksiS66~Uk<hu-h52_)Eh~_jS&4Vu7ks4k- zl_q7pz0KI0P?W3V82PR-bhg|!)G9$fh_BC4N(r(nk4vXaF<_njtixUU2%m%yM{!=y zAi~MH@~yWt)qAz%C5dC2;eKbLn^^hDl@}la0k4kEY5=v_Z15YMC}jvOOCvt960RGC z-ubc4Dw#@v!d5H7FYz5-vl$HwgY@*?c-fw1av+Ad)5%B5wMf;4^U8*;<M^g=AAZ_O zh8l%|4etd6%Od1FGzMX06*ca}@h_hnt${Cc(}F!M8@kxQ5MQ&5Kl#tHM5D|ykYXrG zL36B02<95JpgDR_zCXk~Z#UIn-FxPl2QF%G$6L4h+&n%N8(iaa$Lt;bW4v-^sAiqb z8bnGt=k_yAj}sL=QPKuQ%F>L@E1An+?R*9os5sH+!F?B1eXCGKQ<)e>mvQV(t@OSQ z7|^hMp5=!_PLRjw8?IWjp+&pEpl;een-P!CF`wC(GxQTF|0bW5E~-OyM{+$nF?V-w z>TDQM7A2-=<j|~mm3tbFZTwekOdy~74kd-In_HkNj&^bdo+*O^3MjQKNFM%85G&~> zcW4}@jGMMf2`zoO{%h|M*)KCwL;S+}Fg!mqwPo6HfyR2A4@tzrNbuC;%sD685L7ZJ zhEoBn0%&6G-C`r}R(>l}feAnbYm;;Xpau4uNQX#v5pr#xKx*DDZ0^p~O1Ny1qT4u% zXXb+1J*qfRC)I+Z`Nr^KK%Qyf_*q>;Z%W`oTcDfEQ{wbOrK7FiD$aa{%~i7{ES5b? z!pXY0h$wP82MyYd_Q=Zwd#)C-sq!E^zvK6$U?$`u(XZmYL11EwP`7e11&k*2Hh6cG zB}y~=t_p5h!F5<&Q)$Xvp-pOhWQ)xxaNgVq=9{^%4bQ#0@wT#H>p*`p4U^EQrius6 z)`IAV5+vkjSFy%26<t7?yjq#<Rn2|gcr+%Fv3ZUOkuWPEF!^WZa(kUawX$p3<v?i@ zTcCaDnS1IG!HYM?Pfu4|Of8_Nk2uHnc}0_x{M~B#c;Comw5c-~Yc@=NS8Mx-U19d) z3fxP;K5O*N(9gUHkX!Iw-2sTZe`Mfb_nI05fY!(ykNWa+PPexMx0`wlfaM|u4Axri z{Uqh<Am6Bz^)PUij=0Q)$~;rUeo|NJUe#K@V3J-O6(){Pa`Dk660_pv=oK2)mBP$- z5Mt8v=&Dj>IWq!{OZHpoN$=ZsKzRZU?6cUeJE8$*@i$d~OUohGG+oXWh4$qxXh*~z z=cA7PTQw6P#fL@Zf}1_g%qZJ%Ot4+ZjOIdS|D>Jbj_z*($@;9;Yt`hf$uxOPH7=^z z#6RDp=pvp_Km8W9*Wu+8QcaYL+Ey-q8-2di`U{)|3=jJYn5OBG4xVij*cQk;wluR~ zR<;dgN{`^!SpJ_iqZV{S$$m6D!aj#LeWi`ic)=OYdkgHsQ6!%*ZWFtUHi|cJ5hND0 zc)3ru=Bu~tl!-Mhm-mH)r`%mP(CZbXmpap**5T{dWjf6E0hRQjXo9n_@RdJ)wEcon z+cwI0Xh-~lUhew6@79gr&rXzaLrT6ZLpNEm1nV7(ACdujjq3x2vH><N&FbZXybT5K z<YQqYUq?}4vqgguOn`O6hG?Shh=aj3105-yz@ESTYd;r=J1z4Me*Pj#R6~d2wBUb1 zu2VyJbDL*Rm~CrybN6!tJ^cUn!&j7V?I0_(*Pudc0^2w<(7xu><}Qcyk)L`c_(~z) zwaJ?vQ<p-vo=g!rcPfqAS*(A<!@OZIZG(lf``NsgthR(D+3bz=R0;XjsY`gWs}H-( zP;Oi^Zb(BL1V4cb^J&_)tsBZ-4A~^CrbjL`I{~CR^6&f0Xx-h=+D7)t|GoN)+8G_# z{dLX$1QD)A`sfpUd2!nrwCjSt%_|fLZ&fYIy(4SjV^a+g``g2nivPF{tdj(fr|)h> zy84U>^-|L{YQp%1j^~0L<#G}xSa<IT_v!aBDbWL2;F33Za=FOBn@X^2RKEH?DEBu8 zXAZhwNGA;3eC7esA%Qo=?ljDqwFG>uc=BedwE-b&n>EEyuvuG>{`4Sxq_o?=DHo6O zDOYX*y|F1(#^@xb7&RrqKg72R&j{nZo^`;Db^5^L0V1T2+qQ#pbUCt40}?MEU;}v- zt*iGW#w#d4E4_xch$yMUDUJvmL`sAAY*I0Zx{EkAYzVv36eyO6UNvs%juZ=in5r%I zjSIfRd;!F5tiZA>Ay{sHENbgeoJRr0K5)Og_%@=MY{^Gw>6t<D(FKw9&%!2jyw;(o zWlMtJbBK)vTtoCBD_RbXlmYj7<*^df(Q%2lQ&zd$Z!CdQHW7nxK4-0#8{OPO7hLu> z@e}D13C5Tg1$d-*vj@4I5pG(No&L_GrOr_P^}@xQtuP*XG7sj#OiHNRK<#kEC<=%S zHLie4?fdn4;ZZuX#)hXHEGu5Wbo(<&d~Cc=tcPaq*~oJ7UmA~5sU3@j!@^3=YnBL0 zN~EsDMG30Z!GpMWp=eggR+d6Mq_C`guC_de)bqi8&yTC5#4Ngf#uiNaTVN1COl<tP zD`weo{uO~KT~G`KtDl1vGr{Hj3s}`6IU`K6$2EAp{A^D?uYFgazgoXG?VJ*Mk#s>J zMQkbtIKf}I0&_@DJkcP??P6usyDO%gSzX;bN&9X2t;r@F@m&8fQPvVIi!XMm+Ot3W z{*pe+ZmJ8lth>tpQu@@~=P3IBy`>n=a-BEBAwRtfbOYv(FMM%qV}JwfSH@N{;dq0u zsWL*<n1S9}VeSOx9h`*~Oe9>WE$qOCS~f9)YU*NfurKr3C`&4#D|8xYSMTC(b@QrH zujd2zNnmF3u($~?djG?{8LPlnsNkNWyZydeMjgAi6@I&-=R$HCZ}1^!$4&+h=#S-8 zjH}?y7X>`rd0<l~B72XLX+^gZ3MVMk|HCI>8<?5m441%b=i@(lkzf1Z$>-hYwaOqn zpfw^0qqKJRz628pa<|Ks&4hh^p>~!uOJ~X-(cZ)PuLM}Ku1qhld6hHhBfF_Xxgje5 zx{X%S`L3=ec*vVsxrSs^4Q?{3Mk^~!rY^YI0RCngsWmo)|CnDtBcjtsG>6yUX@8CH zc$jx*9|jLp<FAAQhA57MsXHX&17E8PEOGi=-VgZyb^y_5vM9-Z;R{!U|0ZVITiDu~ z{D)<mR#W(8*<gP8i(MX=My{3A>p6dL6decZ>t)>a?Lh>P(2TJN`xt8pDX;#zA3muD zlX1yV8#nk#X&R-Xve-lMj+867$NU3BVDFV?#leLeeA~)QJzKl&t*zeaU0yY=pdGGh zEx}9}J}B=oebMBcY0JVKHK-@hL`#Od*S+iS@8`Yx)Ef*HuW_)mTHAIbp%-ixAKR|i zaT+kdkE$;(@0@9OTyFI#06*K83e*Z**sV4bHl8-_G8nuxb!Lkg4=!hGb$*6sJ7;~l zvqAIAIhv{GHR~43GR50dx+@%sghMP(41cJe!s9A$(~uR%!cDdZWam)=70m>o>uDz~ zuW99LP-pON8&K_Qf%_%48xxWjG#aY2M>HFJA{H;$d1%4by5kC~Q@}E?ZWD9Y#-(HR z##`}1@mVS8#_4cWn+)LbJu_7$5HU<)NhZ&|T}fVsc<^W1Me`esq_KS;YIXfaBWqZT zttREs=c>FC>S(g)HRj`*dk^?`;G;;;x?ZC&ElMqoKAkJWzT{Ut1B5Bz`Wu<5yFB7j z6~5p#4ZTLJib&|1N-EqfGudp>06GM+bkTTff(YSMfr!viS#*uR4tte$@$OBDq?VYs z4H#WOH0~546j^`!j^tf0AVO2Z9QK^ILZtONp%1?2?F@6(suqYN<6BtNy)-n*3!czq zu?P$Nep9%|_>qzFlV8HsRW)Rk)(`jR`x3=Q(^uNd=PbpKN_|qXh(Y48+V+Jks8pwV zCSl$|)PuIun_HP221<l2fYG2WBWh71VYZ4xq0r48(qUV*NNmL$NTLieFC6pJz4NE@ zw#b`Ta1P6`srNyPYFKtc;#=2+r%FF$tCQ(8$cVJqilw8G)hb1_nq8{X@E%NaW)8BK zdbE_LEBe#c5KsaL%PUH^3l05s{Ryu%I55vOqi;M-<wl%kh2-Q$thw!%tFpCX_UFI- zy$5kjDG9~5pI}daFCor#L=`r`F1s7vf2)i==|#&dc<{PjN==t#?1Q!woK}v4VU@!Z z$P?x;NvO!9IJS|<tzV&$msRC|BR5|tyzp(`lenw=$s6~9YQyMiRr22S?bsAza5J4r zn(`<e$^YTx==UER;pz7A8yUz$0Ym!?n3Gq42N`^e$%iGe2PdrKrYhkAp$i@%xHISH zv^DJXEqUWDLkHl?sj6K9<=9bN2AhnMLGuvc(jdITVUf)X$iV=n8`1~8kIsU{#jhcH zDq!XU*zPlNady3t6X4rnmYtkl9(0>wmb~aSah;+$TsXLx=-_6mAin_BX09=gvZ4ve zX*NuQo^ZDeD!V9*5de~a*@b|BmrKHRD6}{$S@*<yXS+Igk7(1^j@|vkF-f-83k#CV zLVPt<5wo=~%f{xX`#xH|(=&};xH`LB4G{gBd#|!bSxVr)nI@LgbIxkBtfJk;YEkZI z7A|OLA6fRBQTd6ASS@@_E;WRIO_dp`ABXJ5TS=B5%BkwH-e$rvUbsL>&dSjJJAj#! zkq~l#@?oN`Vj5gYMZwri{g@I}AqC}cm!J5HPk<^yw&bFdjo$FA$Zx9USYlk5p72xd zOt|iV?t^Ka5qQPU$e}J6f0EE;&#M=sN1e)jnHjHBuVS}R<~7L*HPE3Y1bik&54}rB z$e;?ks$4UJOON?36%Cr_`Wb>JgL&FIUU`jBuV{Itvvi4vf+|aww5WuIZlV17<{<Ys zvHXJp{M2y-v6{FkUuaV&d%`)ttM|W;S0K$}uGe3{LgJTg`~O1CPR<6-F8^r?tyZ(K z+xR6cuJr`X@u^TN;#yA80kKj_HsTDX{YgJiFo0+k*tj)NM5yY2zG7|)Xt>YV0BvaQ zuG>3LbS2n;H;5fBA;q0{8U^#h0M(3xDtqyFFDe)>nl>~8_&o(R!ZuJSA!!p-st&41 zRZqowOHSqImyG)|Q{1~QH&*dMF{^Iv-k+#>Xa7kwJ&qfE!Szm?!NoZ1-s$$Pi#j~Q zZBC+J?N(Sap4@^)#g8f(N%jcciYPpW-1iKN$1v<m!Ohi_prTm|%qU;(eqbAY!VchW z8A%GYD88=@rdW}@VSI7?<r$xA*1_6@ifqR~Sal}=&v&n}-@r?p%Gb~}8l_<V1m2G( z?FK%qA(CKVainC<<6cHw?2EOTWNT{{z<d|N;%Nz#HlRGT(QqwR3drW}A`fT%-CO}E z2qqwK2JhXuniHtZmQ_T`h_obgcQ&kA)QHmQ0UA8N7`lg1e6WB8+YMf%u+<C|g(ySA z(jFg@a+MQ;lI@zQ;W-z`2)NZ*lWv7X=rG^6-LwNh$YQ&n)yfNK8iDy1ex__>EsH8A zgQb#QS5Qc)hYxR(<)W_lUe@;gvf--D-E6)pq|9KYQhs0X5OXB4YG8om)>wv|^a+xj z(;%8>K;%@U2_TqF*UfVxf*3L%CxMq3lYvJK4PG<+qbz#*pslPbl+O#3Dy4-IlFd;6 zmOum;t!chMsaPkfPqO46^3I>WS5dB77I3U|k@BT35hR*8Fz^YBE-y(V#~(sv*oRwM zggiXRAh(O8<In#D46Nedd~}K%Ra^+^`BF$qmTRo^R~Q}19d1@+e~w5gDSk94^fkFp zW)qS9Sifn>+(~)okwx74AzC@mticD3H$G`&dYpZAbwxccb$zl~X4R}T`@uCoB8$m+ zVNJJj`Y>2Wjkdf@)v=&LMTl{@*c;aTQySE&fJy7OQp;u%JZ9f1Aj~O-hu+$JA-mM5 zY<<tx%V8nCj**c@lDpY-=sOCwbBBh(cR5G(Emd_%j756g%~o>9&T}2E$w3?oZrxCo zpyhK0PIY~%%?t2Yg;+gcqD>D-3u}J65PcCH%!7cX9GZKL7umKeSLDdF%ZX7y77a(i zXiO&it*(_>JKCsLM$?zF#F}sL@_~P3@0gSTb|Cg55z&c$Wl5V&<uF!hK{3ld9}yV2 zs@FW=X}xbe;HnA~K&B*&%7ZIJIGGtquJx#T>ck=vB$GUjBOvn87bP1{RXGWI3~+OA z-Xa#KLQ!{)tWod%;}z5C{vn^}{J;~Jif7N22tV>)9=p=+*_EA-s~Tg!MWCAU$ZS7u zK8*rkX@A3}(R#AU?>;5)Ie6FBfi^7NGe{T^WZq4(^UbwY7ip2+TQf7d6s^Gu)W_PN zDqG9vf<kJ>7`zvE=(m9j>}L@xeQg9*OZLBz9@V%X6oP3RIy<8jypjCxhZ?&nHV3Dw z?WDi^+9Wr+tQ|ek32K&lTDVsWPqd3fHL<CW)6X_$;hxk5qp((cGK(?x`fauLE0VE? zZ2A6$i!IWKh-E`tmAIcF(e|Ale#yEz?S2GvN^Mhp(_;C4pK<ZP9^Et0;;`6#0_RU^ zeIO^~vyOT}frp(GJbfK$Y`Z3)Qn`EcT{MN@I*7QE8x1Gso~y9k-$^pm)ED6E5v%U4 zxDaRBZ%uy$Soe)o5dUQ&)?5hm^Z)PjGMz6=z6=cjpwZ&L0cp-2{{sVG@me`;h$Z|w z>tvf1Ad7Q65iK~GaIO0;O`SChT$GYMmDJ**L5F`tV1Tp%nx+@dd_P=Xy1;|NvO2nz zEj7v;7#2>i&(6|)W#AL6-8>ZP@BiRcM|n>!e^_fAd1y9>JQfZ6_WVu7rh9L7P_W%s zeyVxi(BU++z^f$*B>Ja%8+$ogN>VAJ=CVq<R(<Z7jw^N<W~>ptdJ&0!M|4E&8lQCg zc~B#DPa`LO`Cb>a+=|H)v&uwQNgQBVRd-u31}*8?HMP+7hqw0DfaLoPvhDZ;f(p9^ z&Sbci7)+#bsjE##0tR%)0wlF-v$Rh(o`&7&GLE6|k`3_scq!DbJ@9+)bB2CLS9_** zhW_XIeR?wL?MZLu#}$q^ZgvI2+xZdPRRvekotnCmL+d^vAXr_}hPp?t0VE8r8r1Bq zV;@rhsQ-xUpDpLaY52BydtExhRA)n=0c@F%y_2(ZlgvSb@dH2U(qnkP9Ro%e$l+0g zgexjU*~A)c?RWDbY82^NTScUCeJzI;$r6SxM*;iE1JiV+BOkct4cKhRYdx@fh}@wo zU#wJ4jri+6&t&P?O&sq#u~(v*_UtJBz+XTRgzSc!p2S%;CVk0+3G`l>rSMZo<uG|k z?CtUZB*bFM8??Me_E7DHk=79>j05f1YGXYsJ{XE`DBY_lnJy#{Grj56@p$kUVh{&E zI}{O#9E>KBS`@J{Pa3c*_C`_}ZgW4#1-JzwWlm4|pC0^}JPSm6{!O$uyhqU|I$K$O zmQk7wJM=CB6i*Cd)$3GjBCJ&F_|i&5=?V_nl)q||jKI@Pd23|fV{1nGRH3Dmz@+?r z6O$P7yEB2b0`$y6Xx)B(I+t%=MLD%Aub(Fzv?jv+4LHxJvyA5Al7fv`o(@`AZ<F;9 zDk)KDEbn0UW8=7T!v(_z7WpclPOPT<w=OVIO(NN1L`Ko7k;i-C(P>(9K^PjrnkRYO zGxpJu+jMYh<1LuLuC9b}iL2ql&u;kv@%KcsOxu4YgPdb60rl~*7^_A2GXiwO$)<U# z`>`HJe+LuCS^(MzssI|fYJn7-*TPr^=rIn}+~_`E^s=CL-JfmS_I<`EYF72lHy#g< zDffbCyQNNIYfIg|MJCc@Jy1f>`Cx+IwY)O$bc8f1`S*Q8Qqa{MT^)&@(l!@MA4p(N zEv^!*OWTU%Zu(+*PP<A!E3$O7Oq{O{9gPTqTp`J}$S+U|Q-+_=)*3?FI{@&o14`F~ z@go>v{H;P8Z(lH7b<EI-$)-y(tv1N1(r(hf(+)O=JQ;y^<Whi<1BTnN=)0Q)<Gkee zAMBu>+W_dy*pt!yI!d@QT-02>E8dNu0tmFeX6Zn_6_BHSEWPEa&mq74T=n#YnUSwL zA;0xD<<0GXP`Y@d<ngoO^@IM+9zQX={oM5Qjd{ckjT>Yx1@+)*{M}`xn?cEZ5u#9A z`SEbz^YifWd3%?)tDULMGJx;9hbh^|mu{&Pz}DonF7dm1{!_>xwo3fd@G&s$nL1$m zpfAkw9A`kl=PirkD(bd2k0pWapM5Uh*_y9o`?^~B-Q<P6K71ULeNW#>ziI92ZnpUk z;rZj3Z@sEs>(k(=Yzv+HX-BnpU7=p@$L0l`>l!#e5E(x0C3ZUZyG6rk$5uecft#&% z@nxOwHwRO$`!yd71ZoG8_^}09W=4XXq}+tROp-iJc}0$+JTKc`i1H)0B~y5QSdOAN zDcfHPk{ghhvM^;)pC>4C4GPhwcM2{?4z^o8^{*C2FZd~TcWCNg#r?qb5&SlW?Re$y zjDJf0GBf@9YGl&OG?Xe#LU{?434{j-QxUzR+%kFL*L(4!y#gd{BHl<2lvogw(Odap z!lG7SQI-}%#DLy1x<mXyyKdN=2X49Mv<7Wp!3>5jr~;EX1B}rA1y_=|GGf|%hlyN8 zkR9H;W-j`ON^kz9^4J9Ath1wlx++}<gM6^|m4-Q3(1`%8Ocmu37c_mfsEx0;W~G27 zqAZF=2gXSHnxi<Za&kE!tIGzWHqUZ7URl`L56v@kJZf{6p5eWo(BBgH7W!%8oaGOZ z{K}G8ve17Q#j!-8(-g!M`_LrvBZNLW`p6WYG<TEWpt`L9m7yq-Yd{q(a~IqAf^sNa ztN_tGOP#Ah|4?TuHle9kJ4j7p>4(8igz>LZ3V-AP3u~Q36gTF~&u9G`Bk<%k|Ec0l z$IFIIzyCpO`JfgY@oF}y!`mDe4mdR)%2}{;_Y3RSF%E~j0heGPS{9&MB$E(&)j<z& zXGWk1lcf`jnGKrcfy#g-pqzsLETuvGJgFlddgVRx&NoP`ZR(ABEi}<gfJ}k3G71I{ z>aPqOMU~_E05j-C<lIF(@Y^BBKS(k|9j;i#fE9;HGu-IfwH}Rf(X~F1Avr@8OOEsP za$qbytqEc08HyuYboaC1tnk<-GRV)$@=v`2fFLik>Y_QwHSYOPQnUe)bpT5^QbqH$ zc79A4QSIRSJ9Xt*1yg!S8YLj0O=2G=!^Hw1&DB}b)MRcW4ZSB%9v37clk)m|k3*Iq zN}p)V$NV?y&Ihg`I41Jz*($oHvTmX2^zg3%l;F_4uiUVFr+rq64Y@VSS>m)jPw<it zrLA##19D;~?3mPA_+2dzg>i?(iqjDVo@(-siHlU0@QS$K2Ic|Q>{DH90)k+$&Wn=j z2x32@f%EJ#r1^btVV*k)2m9smNo7N4aOoLoRc8u<A6LhUfw629?<ko(RK?buXMexC z0~<;7GiMsgx5ClSV-%fOIV8lF3mK*`<@&|6b|!UBXiNR>^BEbkYxt(cTl{O}<khf^ z^73Ep#>!$2OilMKKrL7a9juMs919Yqh^72s_kB%5-z`LM{h-CLa?sW5UreP82MLs% zV69x~SZ?582u1Z;q7h<=Q4g($qE{_1a(gl0ht<Maz4Hb40*t+04!XJFyB++}pXj?= zg?<2qC4Pe-o*R^)>Y**%^N|H~uw(crIKovRVJ_4RJ{9Q-b%?U&M5SetijPK~GdDz9 z`&MtFKa?tNAy?fHVa<sUL)98vE-g?2MQV7`nxhH=TYH^45HRwk;}T)W8zBfQ#`RmG z124_?UgA@TIj9-y&#r{?OrNj`=26si?n`Oh{T4#SCc>=4s`sH}iGJQP=|OS|+cFvB zcK&qaW`{O5J`#;PC-rllWY*o)e1TRO!&>*vlwBDj`@W`kS<nG}UPE67EWcdo#74Jn zJ;0<&7FB6#zsAOUo9YN6mSH74UAZ)NDDW-4bxL;<1kMg^mdNrs-CMvy$cMJwTjaDB z#8U{EI;+bD%NaIDYAb;Oy*wvlI3pmKF=ByLVJaAx@VAwU<)%mn#*v+>@)e%(UlP(c z19P~SyNhkXM9Jbk+m*q=OHVH<Hj^cFaf9Bif=`bSH`_<fc+7ij3QFMWo-bOYvoNQO zvo+jtMNkUU%}87KhGhhum%Y>gXY|l4u^6Y_ic0{P?kHS5F3P>;xv5-A^w*RZ^4u~D z?B8rkL>2eSD5z5jO*_37EVcTXD-F&_YVi-v06Q0~v*MpGjmpSls}9Wrvm8q}V+Kv$ zt6oTYq<M7!W3m!3$5nDJz&X?qK!D?osEH=Z)?!UCUya8hQa;;%-!Wi@MdZN#>o=ZA z!?f%a?!XFx9gOqL;LXxf86fV^QX4Q6)0pqAA9sQk1$pLQ0fgS<j4Z;WqY|T@IY}&q zpfWOJ82m4O)-I!PM|q+_Nr;oaoXy=59g5xEXr9vZ?f9JaO^=jQe+ewDRhsus4PL|A zkftG`yh3Dd1(k<Di=YXpa#x?b+7N;Jt9^haW4N}z&?SOrG>=(z8||x$0RX05Pfj7a z8hv(t_sOEq6Vx^g*vum)-)E$M<+=ltY$tmYgq;?At%$y10dFklQIzd0;8H`+IM$2c z;<55JtRgmf9w81zy$X*PlXZOzAzorBq_qnf52$Ow3IP|4k0bnq*meD|_ggP^pHLM# zmGDRVEyXI?Z^Aapopk2<$dwt#sJ^@T3)U~%rBMPr>{7EO2Zt4E#`wid*#-}!%1U@_ zd))hIH>AmdA<olMR+#N@rDp}*xWMMKUFJJ#<~H2E%tkg8GYe&16i&`m#Xt=fE14Zi zQ*XNN(jh&_A%c2NA(BU-IiM`dyXz@~o?4-&7F)UjdO6P(l%5j<lAc<PT|ST_Wkiu~ z?W8zW1u3YXa>FpCI%W{7?Yol-uHO^2FB~bUm?C&mQR~}d-0hreHqAS{68P@}^VYkC zKsXr|X;W0_0n5)&RN2)ZyHpzfsTIlJU;?sON<mdkNEIJe@x!mDLgqn!KrAtH0oDfb zq(bDXRmHw&3G!VMLci#V@0MoDFsV2(=zS2JO21LI1UQa|T(>-KZ%{8xX->3dVl}#7 z^Xh8sjGMlNo!nLilMW72UQPW|;%SHqtZ0yle1>+<`KbPHpxuUkdDkL|x|r@>3`k+5 zN;a_tfMRl~YYwr@5*6zcPzTsz!}}uC1$HR0w8GUZKTP?BeD*6ZOl759_ANI|Wrgh3 zX}d5)Vg?G9d&*IXst8snb8@Mz2xe%DD0W;Go1ydhUKLD)KvEPmg;bo@`}B`3UEy9g zxf9ZUD9w0=YOac~_%3n%Ygm8uiL7q-2r~i?=kAD&D-B#WI3TnzTP7r26<g-q-yc<s zuZnDiwG#FylFE{&@dBf|5!&hezj+-io?auTZPOn0*YfJRih04)t{NE~)%s0<1JRlp zc@l2e?|**Rb7#&C5ml>P2M8wk9#PzrVPnz<v;sy}n3fnS&>*r86}RrJ_-qk|fnhHt z0AI|?TtV;mV9Gs1!BXs*fIjsRF71U^f^S{tBu;IiMJp;T3B<)icjx+mGBf*|55;wD zINIO^60~_7%cSbI#<>J9Yz`Xfnb_`*(bo*;`z`UwNsk@sRP!95SP>B?FqVbMNB%*U zvZi~a;0eh@0#S@lPnlNVAw<g&up(az+FvnLyN+S)$*N7XIgwp!2Pv<h(w}<Ts$7;A z7-H<J;B;%cIQ;{p+182Gwmoe2=sd4>P^PH!)|>u~x=E<#AkjYNu6Sp#G(4~NKd||Z zuN??;aW1DZhm~<jKfn9smPB8xm&2P%GAuV2&&M{96l_r=ny-NYY?6t9*=MvW-JPk0 zHUZi?Mb4osbASlxR4X$O)VASf_o|)J1w}gub&7A8K!~=k#}=27sU@Bo^Biaqy(0Q! z!|Ew5jccz!jluUpf~k4N30kC7b@pz(2EMiz-6`m4aDd4#)+to-BER#-dM>!1d&Y#L z(230|q?#wpfnER*r|gz*eZS03rKG#1SU^nhpE6|?dJ87vudko5r(0d7-0Mid)hvNN zT$EFH=wzw57n;d_2-}P8JpORP2d@VYCKI?!YyZins{=Gx2BDeylLLD`*EW<Cuz`S> zk2vMY;1ux?iLggf0I`eR#D;Xo#>~jOR%>^EnF&y>*&_ON>BT=L`K6NXc*j>4xRbW~ zlqJo-98|k&2dhA?b5-M5zfs3+d|tVq(_^vM9zws0t58>QdAajPxtas3+;=39;G5Vj zQ{oCUl=A(al5IyTeJO>oL<n2N)+}@IgsoTepQg<x{GbGDpn)HAU~|xbs?#jN@v%bc z&0{Y6dQ9_}ruOmIkEXmGJ-uZmBqvlg`59mnCHy7RcM|wNr|2^8UAOR%XYtR^NcmzI zilH0B_@DLUt17FdQ<R)Y<cRbsu8K0@QbGqyGom?XZ|m%^)^<gmFktud`tp`?RGw%I zAGHZ4*MH<iNo!V=2;MF(q~NNT-AxUz#!E8a(Dx92EfZ;<4ec26_<+E1TtKcqphr-h z>=09ER^ZOf0wP=5u4MDs*iO6#Ljw}GK&?T2MJ&z)3ThGw|JuN?dN!ztOtK^(;M*Z) zAp|#?X+^bY1gNq_U+6-zdu<aI*(Zo{iOZ>ss3B6X$PXIH?w=_JG}wgBx8cw5a(QO8 z;fsFU%nUH?UQa#*fqBRj@TvhE1PNaY@oqX-f<9n`NLhdeF_vOburXK_ug44dveH5k z;Fb|ec;1H+H)L6#DuuQRCR1KaKy)jIt5mW=9NY(T!J=rk5;}6i5k`sM&I$nNd}Mp^ zTLg4cH*LV_l_QX>g>6RNCcQfWU{uqfv@+lC^FCDCcTK?WI=H-sv90oC`atnKTz<*G zC>Gj*7&Ye_5&2~5V^2L4hSpFnAjv$=RG8HOg+9xx)_71}B;DjCCTrAdGPy(2L?coy z%HbX)(#h`0Ns;a8KW@~AI#A(<gn9BFw0X7ApRaCY4*A`kgM;_J0YpvLh#ljQ@ys%+ z4AA39r9&OLm^dH&EMex!rtSa%GKRty1uq2$X;a*`VjU-te6hWm+J!P$q5n2L$%$nn zKLY;UAEw_Wr&7j33jrne5amqLYat2MK9#~)mI6@mZ`&Pp&2bC&yn+S}`PD@4Osu<~ zUhQ9Vr*$)5yF7`ZqNQFRzsET&Cahc*E)|V3YLQ;BmNN4@g}@`WYD!)#ge_l5D4W7d zE`%Z6z5I!qzbLumU-~q#{2tEqi?#WIUJ<H=#a9C{rv<^0-3b}w@Ry4-Z)fwPms`2) z1O+L``4$^q+jsExd3*j`ibdzw)!YF(XuZ2<_(?LZ{8XakD=!zL;J-ob!Hk$%KvcMb z6TlZo4O7z#e8+)K$?V&!>g(_8&c56xhJYft9X5i5e}&=<-y}s&Qm?I;ndQWZ5XTtr z8wPo5m03(sUqn}591C2tEmwLCl$7pwq|ELXOl4iH@!=pHODQQ3X;bIn_v6#)>Lf0D z+2Ag6mX{^pe=p7xvl`nQaH$@8k)_g`>8dxcc*2R>Jvd&AGG^^$4mSwd>8;C&XwLQP zQEIzB(xMmX%w(wsB{`>U<T98kS5{0&F)Yk?JOLwn;)e=19#T$7PB1Hz1jJMH6o6II z+iXcX$I@Xz)SQPU(3b22vYfTm6R;1@J?__xzg9W2Kd14xx>U28e6x%)@9$JY10?r} zto_#%Xei0VnP>XJm2}XcdGulV2?>hOe@)$(-ZZn9Irx#wtRl0KPLMNNnVbb^LHg9U zu7q|Du~vS?_~B-xvjvQ!TAy4g#<Bv>aEQgDjcn)wF5`h*)-w5AsQXou+@kxNYP6B3 z_axgj-Tg4$+6P<m1DWwk_+j@0?7-@+7uF5@rTQnMn+K9$J7!&|3`q7ua&p*#x63W~ zV~>@?Zr=?8h+g_<z_Wsy^bPafFB_5Sz8Qf_&%T|TZyB}MJ%L#NcKp3Y^b4mt=AYqE z747Iz{k8>V_uL=XW*QnVot2Ne5PD>-2D}*zp-u5k(lUM!%U*^OnT0<%$Q-yMTF%@t zF)=cy6*Kdp?|vdeGioYbNh_-5aCJjwK9oh)?|Ly)kTpj?Ttt(|OaF{caC8!jy8MiV zv&<uk2ofCPIGR4O;bOokj#}}&$-xALI~pN;R5pJcX4RuQ=^2cf+eB9n#XV0YBfnVC z;8l8<Lwp2^+HaFOc6hm(#hNxIXD%(Okv1G>gMUE!{-IY4p)bN7;k|@`cydb`I@UIj zDExwIlW1SFo6*^sQ=Y+>dTGkG%!}SDWE;5xe<`<}5w-VJ8>A*ow0TIb4sQPuL7+RL z`Tb2yim5+PoRAEJ<pvMqVNY_&9RK`3cHkXU$)6=a0D$vfOYpzgfxq&$@qg?<<$l{D z2AJ*#RMlr7q)BiB1q+snj>;w1%9hlef+R>85{ZJi@}V8OKx7Ez?qnsLUNim;#%u-R zRSoF1AdF@vG~Jz?>$A6&ueX_-(J-0|o&qe+OxsZLDLoc(IvW6)t`}91t~|_aS3K40 zD0W@5=|&mn4{H~&Yhk&{!y>k2%xNaPZwu<p@?bMM`c0mPGqzRp`j<39D{wa1w1>bw zNJ8@JFiea*y2{PDsv#;Zeewl)iQApyQ19HrvOSU$T>b<G?;f<vd`UT!Ijkd)b!zYx z`aMnWzlV<n1<*JCii9D>EV-|<$&f8z4J)EjGDkR-0E&Gx-0sQ=cFAu7DvG*^AX`Cy zJc10AB*mK4u#S(4uGC3E!WIz4PLe3Iwq4OjB+;%55{iOIpYqFPVYHq$=a1-iM9{+8 z@1S9CoTSoDC*fh$hfyVQsoA?OEHQP*J}AXcy(<Q`|7st2pX;pF*j?x}<UA>e>_2o* zZnUXW*)HZ2inh>D6(>Z?5wXA`NQsk?6c>TL0<PEj8na3qf%w@s+SH?U6yVb(F(ucs z*fz2aeBty2t7J&>iQFc#*O|HR1pE9$mzS&Y#z3KzB9I#Ruy?w_nd6i0v5&m`KVHYa z$lAg;0RRAU%zqmO4_jvg_upZ-;=OX+9C7)1L!o#q>?A}>$SNv5g7lV>Zm1SXa1rUS zt*j$x<CY#60Kx{Kwmf)Mct`Ll=99b`ozm~|CpB3cKZ8VydTnENI{BCV!RltZV$&FL z@7;ajRz3DmwrQQ&5*yiJ`(8kw*}=_~aFao|h4vNgX&usW@toapH5~TcLfay4Rn^+2 zSpg;g`|<ZVt+Wm|?1X=pXfVR4Y9y;cU9+|b;J<&hRMjqhtSNx3Sai#u9$U0uzFm9J zukTkNeLfak9)73|i&gz|(|S6}q<t<adp(o+v>-n({v+QI2k6(cGtp^hMUjRx9ZNEB z#5TPX<+;>!=IYmt=(1Cs{7Rr1aslnl0kTPXYBhKANY=Z4!m6Jwvcf&*a(~nj@G~K` zn;OB@>8l5+a)il?qVuTfTY?@YrG+tv9Wo~}-s3@T=@pm5g8ex%<h9%)JK3riq1kkl zfd5VK+$!6NH6pcr#imxELJUT{Q8{ns1AR#_3Wj~Oabr87>%0LJ=ExUtQbSK2a-3Lk z+My=<W71fu7N7u1VmEK1^dhQ(4g3U@(}8$uQ?H4+`JRD~?DEhALc^6Kt1OtICflg8 z>kc2Z>Dv9-fud5pF$}IBs_A1@A=bE#>@7jG`{SA1x}t_va-9)?pL1(eVCzojEcy7M zvyyu8C7lr~`3Po-c!fQ8MHMps(s|W`um17!`P%ULhsv9+;8ehsB0TjoTsajWJCAX8 zYCJZg;|wKjv>)=PKb^whPZvd;QyV`Qk*YvA#lhMKnurWiv4y9D^W)ZontbouH4I-s zxKyjk+@STkhz8nltI5~J%zk0%Ob^f3^RS2S_fuo)3;^9Wi_1d|#X(^q_}p7`__C}D zYb!^SV+_jdQe^>hWm$P)X3vf*IXu2@>euu&)L4&48Ha6KdqqZNSY`om2P{2ed_kDn zmhh{YQlK%AX^5D7VRKtF+jNC?s&q=<T(!R#`>+%aZgvlsS5B|{_tMG<ISI&)C6I)r zjsU$chUqwDZ}#-oSpgpcx5*u?Z#ZdERzH-nifzM#EBR&Z*@de0hUuAd(<XXzg}9s} z_<qGYBskG9oQ!gjh~Rs+-d0H)m^Bn=WCO{Up_;0XhWw(0zTWjYsp4utr)@|b;9V}l zQrQT}`d(AH08g56K_H1`;F5f@=d%H23*b-}91rSbC<L<*avY3o^ZofxYXPl-8CS7p zeS@XA#T+^?{$Ky>XM&7c*RMqYq+Jh(s|F!0-JQqwaYULE+_vsMjWTLtQpSE2#RUKl zsyXP`pEiX;Y^ZOKt%zlM{kx#RFHIC`V1?9wP?y$jE=Iz5I7BLstl?cYer14)b>LnY zfdeyube{@P>7}K1-?tVffAOvi->#Lyu{3sYNn&P9kpur9U+35)3ecq4)@|FiZQHhO z+jif!ZQHhO+qUhw??lXp+1TBPiuwl?Sy`3OIb`zJNni1MZfHUQAEia{AibUPjF-K& z)(x1S=m;z;?F<sf^0&S~lc$dq?JPh%q&*{^508=zy%loQQ-W7d<;ENR{a=@nEo`&7 z-r2d|qm_?~erAPm)q{8kKvj=j0~Lpf=5_T6VZBumFWDddj`yR5YP&sO2kV)QU*l|a zJwET0YOY6@i=}e8y&sSlUsq>m=HX1#oSVQ5y#5UB2u$`tr)0sd*rR&>7#IxDX+a8r zs+5AFJb@(u_QG+$7j4sTwj?WmGyn1uau_zsDqj*8e0Gt|mzcbAYkJQp8-frc>V@NK zksfB&x6$@D&essBiLR9?+9&`W?D6yL`u9i68;TUdi@+7wXldI;B$nA0m^|Ws?FIR? z0lC<37toDt=gUg#Lmh?xWki5^m+O~<O?*j?-e<fCHQ-_rh4`WQcU2&%fs*&+rw+1z zp*M7U?5djNon2-p7=i#pU$g5E@U-%{Mne;hP?NEd2Z}6pyh|N^5S6C5z;mg0#7WkX z7;vC4UGfAH+MNl_xWCx!{x&pMe5b3hDbPL<gKm3hSBb<=Ht^czYtKd>)AaAegtG~+ zKyvL)bU`c;gSCLkG4|uiEF*TwAEi3<NTKL=n|`3&I{7*~&ALF&#Y#)&GMpidh0>hA z-PkzoZUu%#8^piU4@w_?c&86qPs%23lZpfk!k-MO&jVx}C8~B)VR5p&OM{>rMq+ie zGxEgKbH8)U11332<;TcW>6*oiruKyw8$~2Z+xHG~7rPG<a~_tcYcu4l;ok>WDbuPT zp2{L(DIk6A0H-^Qv=3;Yt8hF=*<UpZ0qwp?S=190IR#9o9~c?)L1t8$i+DXh9<`ve z`e-X|LKQ?h%)u-RcV(BxU+R~R|ESAc;@Y~DsmD>mGNKeiQwACE65FtufOVd7;pJ#9 z`D29sF>`T)6giD1^GLg4a~w=<#mN5#==m!(J_Qzqksd+bZYOCkv3Web6YwD`+zd}; z9C`V7_^agH)4N6(kZoI)nxW+^3F+JgbNaw#(f=4*4~6FU-HCg&U?tSUxU0b|xuh*& zH$zJC&6o;MlLO#8Qejg8!j=j+vX#1RGqV9vyOgemMG}+8*6v0i@Gh~>1dP=1Ryq*g z6wr9dWjW#?`&GmZ`?rTjgS+PqA`qfhkDtAM$Ya}?<l~ETMb{7;@27q4fy!*Gwm+HB z?LtODFLJ#OoeiAf!wYhR69AuYyNr;181kG&WP174aJ>}78-lUsnvMwGdA_(zEfqQH z*BTH!%u2hAK=lGq9~3(6khxeSSsZ9D?LbNyUP0!DZJDdklVL<|@%TPVGf#sy1!jx$ zSYY6Wm8DB39L{!M>`~e6PW&G;xjvzvE`LhGSSh)Ozcui-c$rH7MaV`?MyVg)Tf;vP z7aLvI3TW38D&|)g>7)EC#ispQAiq$ap9ubAsb8<%^7sPs2)mfl{|6G!Gma2x8`*cV zW56_YKNm_WA;+Kz5;XdYwTM{FfXi+wd=7cdH!3$=i|ub_FwcGJ7SP0070wxXWKkz3 zyf<4S-boA?9}`+HC~t|mQm57n?IK~bmZpMIOt+~+fTbsi;?25+*EUSsKG+fE-9!PI zyWLE*a?pLmW7JAUVQ(GUDAnu)Apj*rw_q`vVCKumeuaiBoCnIsK0fezR$4sX#@Mps z0HBP(IW_7nbZ!92UVy3l`qe(LK4lh3q@hBxPOte&<bjWZD*Cb({S<d}P6LyeZ<B5W z)<+j2m9}^m!KW5GZ)H6$v0!^~RRAcrPM!NiL4>Uf`$kPJX{>-8JR}EHh;7#&T)Fi? z0<n(q4vUUc5Z=Oc17+`$Auu&zhP3+#JTCCYr>ygHuq^*9Stq$2J@siGy~#IOx5cXp z73V*X@yDbuVKkWqCJ2B&Cd$l2r71fc(K~3%F_4Br6U+x2{G9(fQ~mgH_rNcZb!8^} z5t#{9`7y6DH7Cw^DA5Dyr?q;hzVD>q6rs2H^MFS3UK+LUX^AJCpMFOvj_jkVto%T_ zOOs|I4*eBQu!83KW0IH42gT{Kf(pXvW`t$lrZqG*549f-=Ak##axHC+!S~F3_8QTn zG)|@&Z*W8c@P;?}g-8{5CH3PTcLSyilIZL!f=K|_a*Z^?xjEvBo{rgCw`0j*)5&!i zC#nEK(2OiRNyJ(2_3C2|)Sw(SFD1V^?dD70;KPav=Vk}`EPF)APKLIqISk#jC^N-= ze)>ensT34-0`%O5Krd06Dwb%peE#V3<MOiXVybI*+*8<h*ust?)8b%UsguobCQ32+ ztSaBRt?*O$i1gF0kK-M~WHhCEP8BtB?8uPNh6X;-uNg-dnp=i?kU<E`h#}Qt%Odd* zMdc-Mf-g!33RcD{|F&es1on<e*6oD67mOf`rZI?r!WGXfkfwh6?cD~=xma@1#4*n< z{wN8fC<*;SPLhak#*(Jyi&y;QBxRj9nFFal2B5aU8FQ%6Nvp{OMWf`H&1itnu>Fw% zf+9J_i*BS!{Yz@2m4;pkBGN=`1HY!KxG6&@HpD`ih^f6Oew!*!rXV3}e6>T<99U2s zz=+l8QD`Gti9R^iIMs-BTn{XLd9U?809J8)R6ZPgxZvcgw{jex*Ml2mAQ!A+^|C3a zxnvIF`ej2?ro2G{w>#b|AW8a##XQTbkQ4$>fFj*{ci>wi-K)(!yO_n3>pyHK`$gX4 z>)wLb`Ma4$n791KOMWBJLiPF*+6usfb5%jBX|Ao8tC4;&Jn>bhtz?<N3CFdhX=KK= zFF`)uWW(>fA@=IvlXGs?=~9QdZQ92JU+2zgaxJ~p>dNn1X#p{=M;X8o1g8VW1}azS zo}faxn@st|tub3asp1Xw26UOeUJ<;QTn^e7Eg-GH-%UEZ)T#xcG2tpjiKlpo1!*-= z>570_7j~nCXqkrV!>lp6OQoaSaG`g6g<5vA2Xbc{Rzo_kIVR(?Cv$$3r&zUmJAh;{ zzZSCWy00!nD!o^lKajE<y18o%qnBnbGcHngW9d#q4Uh~(uN?U*(0DcxJPn^M!*ATx z?hej5FYIZM12NU6#0O^NT&EUP5h^j8kfxG(W)~xMeRF7_swS1h_-jl}?utg6u**5} zUKl?{OC;N><n)iE(g&;AZ-_ib^EmK@7{3ytBKoGhAbsL{#)CGDgjWswtm%jZ>5o}a zV`0{9GJ!~(N^BDD4W=Ko=~<yU&ACytxl^8!f{lcUexb2SDGlTJ$b^{EMi{B!>x#y2 zeT_JT0~KMrkrmXOB$v&kwNuFe-Fn6PQeZ9u3Rdm>oUFuSIa1*|fhKi9=Q<H6=6N|Z zhzX;2K29ntO>!dp{31(`85<pc%kr)RwRFS_U_gt{JfXIkKM=pG;r^K3lCZxji-e6G zlT>#WvwfFzbTw^+ALUfjdVcWG+cB+m8U|(hnWM#O(*`oSARCd4QUS#@XAdJOSB=4A z*>UE#T<TMyEVWUwAHeWvDtK*aU#YvirE3DUo5v2~`$#`50+px?K9rp2F?q(ZOyDo8 zE0(&^`^sH@V_-D2U0P7t=%+RiUo6AlX5#G4A?$TxK27*zs&n?R(B}hP$22;#K?HkY z^hbd`$1bK0*CS~$gf)|*G?`+lCB|^Nh#&4T6G6hy#z<2R#k9F9JxyY@4;ZjK&gF=e zKsxehF@7&Ax8{=%B6J%iADUkfC7`vGL7Oa7d^5KcFUc$8C04hG)Ve7o3T!=qXduTW zaz!q}l8bkwIXCh(Sp~zH8JX*bUhMH!kJ+USTX4%&o}hx%=;eywxuM-iuf9o~A?Ag9 zW+s53ZIA6y(AMei@-`%j4p>A4JRpC&*~A0RgcUmdh?CtUCT))oky|ATI@Ttxj<3)6 z6DNB(W5XV`yKAv<l{qaym%jyZ=y@_Gn0X_pN5->qLu)A9o@%z#oepC>C=t4vV`gz| zGiNksK@01xSrGN=N@NKgOo(WAyma~He0v>B;ZGzz>ZUiWW0%(7xBqMG(*uXNl8P~! zk`aV?*sZ_KZ5@yDL_VC9a{2w_;VPt&*Qtb%gj++9zd@I0HWL~jR*RXD(O)d-&{|iL z5M(yMCJ(!sbs!+A+liUXTIzw!hJKJ@&Vy6;pxgRA(Is%6{v@(ZPn&+nl7n4RIuY7l z03oyj+jSMtF#ec5rObb{1irJT)u*}1Gf(iwS77bKv6SNQdKgT~p$9Jt&}muF(Gw40 zWc89Y;><Hz_ILQfJvgCEuyOLApf4+4c?uOKW#?T0Mqo<(Zn$*JDxQ#OfF32$O05Eo zKXvr$?@Jw^fN)nS$b}DZm9VX6r$_l)P$e9Q`SR`)5J=*PC|JMrH@QzY(O@irO9ct& zSTbCg&l@y&&x+Ayt(nogOQ~$U9Ewn=C~OGvb=u4q_*sI)v0wKhS`Df*8^ZByYONV{ zVuZ_({&qyc(wGVz65)OXN0ni@E?9_aPWqCbR(5LF+D30c$H-!OmR)h^{uY60_7sJv z2{pWpU`RsqZK6@6pl()|sWEMezsS@$+RX6DrSOL7veGc3wuL8QHZS<9Dye;n({AJ^ z?ljd1;gnrxAs?uQsf3=NU+O^h(U__?KWv2sYA347=U9gT0_&$|JQ3KI{zdB<55vjZ zlO1?PXKF!>?ItW*zUPAZ^Q15OAcQkTGz^EkJxJwd0GzWR)c<7`S!Pt>Pe#dZYCCTI zC>XL`0RSXljN-SEOmlfBb1y4Q_gecp4+Fi=t0~+XsH4JjKsm}q>DfZx0t1fTo9G^o zQV@-2A#dmlow>4cah0Yt@I7S3sIiR&6DU=nx#ie3EsGB}3z7C`)C={uK4wrirsJi! zOjQ-{FKXYJs!Z@`+9zlqhl7HB()b|W3Z)0|;aeD=#l<VT=U|%Ba$yk7egpW{kn0Kk zc36-Y;4J7WLeTDGz#&TR;~=4Dl^Js{!TtyN6c$`TquVj-08TOFq)xOMY`kK@L%-t` zB;@#R`dL(?=v-zC=W(M2Xd#qht^?L7qQzkL^K{DJl$JNS7L=W%Jld1C^dkdEvzURD zgi*yfj%}OuU3#%yg9w20n8vgFv9UQ{$GBF-4~#<~m~FcfruPwshR#b$#zmKp;&J_@ znqyCbRp9!)({x|HZA4w}lb%+3aw!rBo_vPZ$Pi$(fQUeLAMX5Hipg%yD>nWm`-+7l zTEdIsrH@9Jf7qx{eHy-z7e!_E(NWfQ@Zgjq?LG|F%UZE#=PcA7ehs=w@k_6vU;EY; z_I8>ZUR0*<(aocSCCsgJ#*8Slipa%fNTXpXdgILKsDB!x*cLXLiXm7gG)8YK9lvz% zPRdRjl`97^1beaH*lg_%)XM%p?gq4Bg~iYA0`+ggkZw+$aoqe@2tv}Fl8k6VN@{;k z-kUY;V2Fv_&w)Df1ORlmlrTP<itiq(>wj|8)%CPH+UY9mRqqzbEC!8%b;m~BF{&Bh zKIx863wDAIC_8=GOKpgUP%=0sx>_^fZjFpy$r4@|kR_?Pr-%5tFfC|1H3cL%+i!_A z)no!*Mhl--hjx)ivEB&c=HDO+$`-?uugoThC6vB`gR#@+>@R(#CTVY9bm&+)=77TB zVZjyLl7mdy$HkZS7@%!~Qv)l`IV4ztRcgBeDn74c$~;vDen;oTyfl?mapuh*)5Yj7 zG&+&CYTK{Hyx8Rg={8F)A6)8zPI0v0-ZxeN<Vndyn<?jKnI9hO<+I1<=wm(JeZkiQ zl)-6{#;nP;uR1C2q|uWyxiDOl(*=OF$%=(7PaU9PG*h}4VpKOuko+1VK3qjYGKai! za!oUy@8e#efBg~0O*Q&@*kelMz%U_lD~8~K1;irkL)}eX75&GYQ;}R+O6zeRql%N? zNA)(*(dp+MF|G#;xNJ*bvp3hRPW+=F%jVxwX~atcHTx~hbCBZPLq|hm5%N@&2odQN z#?E6fEm8emHvI;^uRR&tL0cufd}IOX?KM8Bk6fwYllkgK49A#y>0ej4IZcI!r+`C( zC=I9!Vprq(AKAo!eC*W`w*xR^7yRDGLGHIvLdkc8*<I5gZHKa}LaPkq?A?9p?4?9E z7~N#|6-L^*XID1`Sj02bBQn%O_B;2pxi#5wrJvW>5!S{wXA;9=9Hs7HfmbAr%Vc&T z?bnj{<wv!r65mE#lN3s&AbeW=k*@`OK=DvOM`NsL&TXIUSaszFs^JPtTsNULLl*GP znhBZ)A}qUbprj9Py0UE|;Fw%$n1|<2g1p7Z(Bso8)dk3i^zwb^LQ!gp3NTXN*azC9 zDBm3d6`tEyGh}RNanhE!-#507NYK!keAJ(<;Tf(7XHsN|oFduxGNz<kT$><ABaKn7 zrJKpRBdDtc&#Qu_Dx3v*9iS>qNAu!z(C=?~I4|gko|v(nanMqNJHfp!CrFi#meWyG zJLpaz%84DGp6N}sX(KQ8WfoKXMnXC!jGRB!bM_3^)1ny-L!k?-A~^=9Q$PvT6rpcx zGkWY;zkDT2VBE1j`<(L+^L34=qqbdJsPB?*rDNnE7qw9}rNi4qp4iiDj;F2R!a#yn zH-cH}!Jl~0B^!_1ld(&&TmgU&#tE??yT!uqXTkyvs#xqUx@r)cSxM){H8IHM@2xyJ z7G<#h4ILj;beB7ts-?7HF?|y|`QeeDrgu=4zFgLah-sY9b;r{ahwY#6O!;qn>iB|J z5%Yx2NKTDLr=%VI>A|W&#`-iR_clibzwIibTZ{?lh=J1#dvHFJpQp&2Ec0e&4pd_w zvbwF4H_9vFpQo8*96jEm&vsab9gjBn-m0)PrOTy)@&EleQ^ILmX{yQ4nu7nM=7;6= zwzFP75Q*}qIEI2QJd_RFQw+IjW>WdrZBu1FGCgzAkK7km_mBQ{Q7g%&`u!!uR-hj2 z9)xH^pGM~W4_7iuKLiTkZa^Kj^0PrH7~Rs&zBN_X5~ej9&|xB?N{Ikv)2{SHh?-N> zJgYzONp(Gp$J#@{gRmX1n`?$q4sqhF?ln=IY4?bYvF(`Y8l;thK3e?2Ciu9zr<83{ zGFOO0FVja18pN;dWJrdGv2mimls%V*Q{?IV>TQF7U<`Vz!M<g|n@3UAIc=RL+4%X3 zoXz9y4yH;r`G@Ws6dxX$|4(nYkqBomU(yz`Y>0TTog@qYe9@iv7=AVud<fad7i zG+D3#BuI7B6|HVAa!djaed|x0F>aKiRb`6VcpbCEqxN;f_+vj|%FE{-IgC#H1GJoE z((cjOT0+2sEx8NT4|jnzTL?!^b@vu<$8$w{a*z2}@9#HXe7px6rZU`^0TrcVsCg6u zYpXUW7baD6;ylf<{HaT#`K%^Cg`}uHt8~{QcZ^It<Lgnh4e*EJU)SP@WifIYKf2rw z>m0?br}~VC8%e}t%{5PsB2=-f@c!&koJ(e)iII6-Zf;Wt?6h%Eu?gpqI#U<1y=u!9 zz5|XZ)4BwFWL-<e;$cXz0N~ub(3<H-cEMCwR%t;FqgtJ=bH!H-3zu!Tp=Fer+ctK{ znXG$y6kXEgaTGDQWn}IC62rIM+5y5(bUxA??v6qQvCvdfR8{1x#a>jl#x-MNA@EcI z1b6Pr&<?8AgL`^{bPYAO$0m`SjA#%T-b^PjF)G^C7{z%Mxl;6chl+CBi_js>@4elE z4yl<!9Sk>PYGU4a95zk^G0jLK?B7pTC-CdRW@(}c7qIs4xJG@{4FBcyqR@CjSvQTX zgUcY;DIW(+!@PYEigul-mOpbJivdwYl9CF^_8U;g4X7_EfdawToXznmY;omA3{@}- zT{HPk{m&3k-SHsI-RIC)xe8#iS1_1H$swL(Li$JlC_#0f(vvYGVjGk_8J_Un@M(-H zRjLaA(2qBeCi((i4H3ME!w;kveRHZ9LS@O<yxfvmn0m!}MG3y<BBDt<x~}u4U@q0? zVyFzYl_~UC^Jt<e0-^FEXv4oKg}Io&_k2d3-Foj4DXi}eA)*{CzMdqG^a2EzEri3} zrb&5Is(hp0D=H4#CU?)rt#mWF|Nc5I(s`V+@u>Ob5(XmI4(`qeSm|tLNM?Uw+XEO^ z)1%ZMK*#|jxj8VP)+VaB_4eC?a@g66d@CA_G8Fc{Pc1fOPM?X~mt3f>^>8DZ(#tez zzI8DrA_^JEleZ`U&GIFutQqxEVkaG_Jf7@_gQgw_*2e4Nr^Iph3ytgtN7|MKJ-%>D zoEk5c=d^J^fer6cOu%kyu(_Kt{oavHIe&KS@myejw3J(idkV>2f^FH8PN%hznIw+R zNtFSBH-NM#H+Y_^^-3No2+VESxJrwJp=g|_qG-`}NKO<(TDb=s$t@3?4mYs`Htjtd z>+56QQOR$i+vnfLiB6v>1DymzIsW6A!5upzj<e#o^9h1`SI=aLOO?5em6(e`YdJ0d z0JQg*DsKE@SpD0^{Sui`ZvblN)g|*OLdk1wfO|M2$z^z0EFM++fQ^H5t}@>_MxutA zeU^)y`bmY-=9baheeJ<cqz6-c#Ea?r6At*OjWUk<0_wc1ipDL!v$)6vrNY5r42`rw z*h~!tL*e<L43j=R*|tQorZZ9^6C%dk_*o^3qLAy58{3CRw;v=pWeaD%Nn8L48yDni zH}34n$rA+&=j7rq$4ajdHN?tU>@AiQH(I+-YS&~Gwd<R=<N!8jd4_5~NW|(%-;knG zzb=T9%}v*BW$dBNPxS;rD0;zcVyv#+PbBUUngpRVyJe7UICp2S63Ft%D2w%o_7CH$ z`SRgZ2?4DWvJvKsHa(@NpM~C&Ro{(e(<}x2z$i`wB)^vaOk0$0BFtnCmKZ&wRmiRH z2slj2PwQL_ch{9ImB^0kNjB$)?PLev-U@5L##lwI&<rsPTrRttGjO{`2}TIv7xUM( z@RZ}*(j&*N4`}xBjrdLJFLTw`<(Y_AH_e7@+HHqV>5NAR9lE88JcW1^&PRIf`6T{= z+C!_5zeU4a^s^D_i-T3w_D<3yR{Dntb5bC199lz2{tdcYFQ(VUyegO#VWeJej-O3e zFTyfwWa~(M7IP=s0+4QUGf0nAW@NE~Q<pkFj#T|#l-#5ATFtECM@r{z1c_4NWq(oO zkyR!BvA@|&{KgV+g=`M-K&itL)br-;woixrhRz;I<R3Z>_v|`U=9^8JQ*+>(&<!Lb z93pqTFDfj*EI=p>!-qLGU{|-{ocW~mBh!fopSZ5(_@cDbdq}rvlVXo0+{4QO8HS;1 zPE;h2Syba$X`sg(;={JpcOQ5=oS}a7$Fd4EcwPvhvqSpAiuh6Yoyeu^XouA*FSKjm zF@xN?w9Qh9rVws@)CrhS58J+qHn<xe(r7x=CQ3{)#N_?(%vC;<L~;YuZ_%r5l9Pi8 z(q(vcTm*xgmjo`y6Sh1VP3WmMv&5ZHX1pwm7%VEd7CN(T-nfa(74MbE`*YbgYmYSJ z0SxZ5_sV<sB2hbl15^{&6CP2EeBd79E!ePV2gbF2rE7rXTGL+5_yx6pPMpa>BP z{<cDEp$nkzep%;>C^ug9j;#`owK1x?S%!616-(U=M8?}kaZI5v8l_sZDvo5w$-0lF zxe~_W)ZNU<xitF7Tk-Y6{zm!(CrS*v*xp}AB0;m(j9$q;SBVnen#o9DV<NFCMqJ=o z8SLDNxRK2IL-BU2eI|)_c(p+AtfPx;bxHqMj+P|It^F%S3CmLer31QC<W)TICEw4& z&g`ie6^_DqP+t-NI-V^*ywqGPZDmM+>_l!QvN8*o@#*u?_xb;mzRdO8h0=@%0N{}I z|44{B8yNmiLNsynzuC+7&Na}I@K$OuiSBgbBJoLU?Q60UoTZ9hTI$pgHv>Qrpa9Jq zQY5|K9}KZMVEi{5vd2OH8tw4&^PBVZ|7;jFovw*h6ZzV#CcA1rigvCxY-?&74K<af z$s{_>Rud-A=Fu#FJ9WCdSNhHiL2_FCz8@<OhEr>Gzh2dLzX%G|=q``@a(gf<YPu{b ze_l#E)NXqz*p@G@eM_Z!wqaa4X|R4?N<KDwHfUAS0X}nS_)TI@V$-riPL3+R<49Ci zn{{d>BT8)CI?(yhE;}wmfi_r;zkv8G&vd%IOY`rY9F|+6X|E*pWm6YY4gb)bUsQg} zn_S4KRljITM>dq#wswrTsjw|gHh9F)_165&bS>`d*}c8t=f}K@{d&Iq5b9Qe&#bt5 z)hQ<EIjPOGtmN2Du1r2l;Coz^_H)+&wuTd2bX<42o}=9qRV(w_^X@#*RiyWEiAH7~ z!}_U!3hf-Zdj#z@{@UH_%oe3r#*@Boz2hkC1-UEJ?Ib#h;!L@u37B0mJiQN@g`yq% zLgTjf{QQai^!>+xr8fEe*nCxKwcXJA{kB?kHlFeO?YMZ2#;l`1!u?*EiV=H4Ycqez zq-oV>&FrpPc}6`^Os6i#Z?6u`^n}{|eMhyR#zi=Kyy@U$D*Ef`k6OyyFl!)RUq#zc zq@rZ#o6^-?j;!4&3;Gp!hIrFvnVbaNYvUFPl13hIiQ7Z6+oXkGK|v!m;0{eASi29C zUI{78F0$XE3&SR%X%`AWf)+rZ!e*jIYmoLTYm_ILs;x`dGbuHseG}@cv@m3G!;b-@ z6wLPAB0tnj$^k|@kOHXRy>5rhbqHOxKiRUpr&J{HOib~m@dfJ=hyQxL;*1t^nZG6I z0uL~7bCci$hxo_3vZjmVpG_3Ti0rzOlH;=CdEKOaGvMeFE-#W-d#3P<;2v}|Ymy-0 z?P2-x_D8dCH#KFoM_cU=gLP4`=M2i?WJl?Z82h}&AWT$)O6NE7&k9c!gquVpk$0!1 z-DgpG&E&BTP#k_kLw<ZN_YMD`>bB7y>c&mOTgxUCfhA1}sO6-AOtRu-s>-5&oBmV~ zXicr1{+LcPa1_Se&9Y}&=g~qUHAi-2`y8uG`(@>Tyr=;CkgkmfDn0Q4zn$e>gVpOM z3e&#ghtK+x?Ih&Yx&sg<i9T5$xU8D`a;OeO6A^fOA0>DhBvPnu((PnNe(E=D$|K0q z-7(2QcNYjY!2I@#56Q&?bbECs=n2pCNGUx^>Sqad#j<hYXXZ;wTe+k>CIaa$KZUul z%Fa9FunlFtX6rS5h-5NYnW1&G^RQG_FbRyuawB<|E8o-egh~fkd+|BDXdqN=#ePdg zi^QMGg>8WztT^OodA(}i#$9cJ&VbI!lC7S{I5&2h#eY15%x}mYJzdQgTDtCY2SQAN z%T8?~bC*|VkX4mFb{sJf=FPyK42`Qk6@g@bwkFWo^#WkwY$*XMa&r|veXM-aMBrqA zAh$(VcFjo|^L)vBh1YSHZ`=*osX08Ae~F&;KJxW}v+RWvZtBRwVE)>ty}bzCLk)aw z;^02u7eL`Q!z6ID>7$M%o+=V;2T5_`2yOtTWv&514**M*NBY&>#sKh3>H?Irq~kQ} zG2tTyoChwgK$5F5jYn_1Aq88ZCAPXMmbNC5rSa4f=vAmD`oEAcQOGBt_Vjov6#AtN z7!!jJ)amV_chuv6qCp)dW&`@Hjk;EzPcZB1RPn&_8csryVyAGW#i@Z5nvRgU8mmJD zCOZu(YhtDiqo0HK<q}qRJm?5qD5&w64)`jgmjw9*5XNzV!TNpvKf&>Np%m@r6MXe! ziAh$brpyCm4K=35elshdCFvR(y=pZyckuU);Yjpz1yN@eYoSLpl?2e#`Y3sw<!Y5B zioxqGNJU!53xF1@okSJ%WR~<Bo~ccs%1-kU2~jwmXK}Xx=JEmNAhPQk4`}E7b$8lO zJUaJZnO0_`3@1p5jZ4w5j&!g+FFNVyXkg#ylfv)&1ky=(F=l}VrD=Kc6W!?UGW8hz zAV12Ek(WKFOdHZ8a9}qj9cF(ZG5E;ct7T}6He1@RC$=>LBhc9iLn<@|-1f4Z^=JFv zMB1_$&kh@>lKYNH2s*o(mZA=F3tqijgW8Sw1!yurv5DpVH&uPuf!&+|j>s_Y)!tTK z3jE1RkD`FwPkRtYaSQbGQ!MI1bL+2f9GkXJq5vZRE^lDJHcffta(+nD9iEI$g$SBJ zp+9Y<5RmWUEj*VwX9Ta%g%SW&oD=FyBc{K;1QEWkYpmSz%x<Wm!1X=m_Q&gFVAcG} zK;;#qA2_mmru5CB?;xwKubk^W%|YDAv105A_Ww9>jO=%m^Dg!MHA(2uYIr;-889zK zjKOTYlTeF>h#;@zH<jaFEQ$bW)(w(v1_nBz6*VA-cGhvd`bwBRpffl9CmEdj*N@ik zdl%+I;bC*n0gLF-AV_&{4c-N#0B^~G4EN{fF{QmdD{@r!(YLjdcnIx5+f*VVBfX^N z7jI~E`P+)y;CtF?esc@B^%|&g=L6tktruFoxkDj@oWo&Rz{n6p1GOXim(=K38r>Uf zXb3%YMC5CgZX-uXn^bAEX#abE1tS$NE{2aZ<UMiNwJQ7$$-q8a4QQUwTRs8+tEpf( zh)e3O-xcS|MF=%Z{k_WLLX(cJ)D>jY2e&!Ldcq?r{r@aJm!l*e+H2Ux0MC1I8O3kR ztum;~57a$}RUw77B+v(S;Lmqzi0jj-Yw&%G^D4+|bCVwH*ud<t599o7cW`)BAi#nt zE)XY~N^1J*EKPmQKWWR3eyArc0OXkz>t*Q&o=F<|CTWh^X^gXm=#|tfm+>El7&*d> z{ee2(BlKl1Gr>-bZW0fM(@izT@h&3H{^618dc0R~Y;(A7zd9m0A}H?x9g=)z3g6r) zu49IluWgVqoZWEI<!N0pWw8`}hm_GnpeiYdBav~nOk%Ut1(;A3IeKmOIX@Bnbxhe# zPCSGYm(wnTTZXKWPO5CS@h*Z+mFC*;%=8kWZMD&QDOfxQK<I2FtCmLfyMZ{rv?_m` z(01H$+7)1M1&@V~B-z}*z@d0kKL~(Is&2I@!@<)IM!aME>%KFfRQ7=^gxFUr2+?tB zq*B2y7KtQ5g=a2?X&k5ewwfUw@XSsExj=PpHrx>GN0}#aWZ*GMKxMrnVKsoyykiZr z(kfv2v!;Y?;D(SY7&IKYeuL7jmfB=<U6xTgfXnX)`Mc1`SJl}~70b*WZ_c9spg~@5 zTd*~;D)3Y~-T@j_+_+59n!q!fozP}k07Nl~XbYTv?QQ|q@-K&gCqeahVlq#(mLK`y zF4=2lT1sLkl;R(ajHt}}cHUehH*crN_~|X$EVy%U4A?qE2liRtO9wi1hP>*MgL6Z% zaY%y$JKwjMjIuX6Xq~woOYktNZ1dM8jrB63QyY7eqyB~A!H(v1aQJmLpkqTM<a)I~ zk}^03Am?;8!0!K-itfL^MFVA$exS``tJrzy8Fvcd#RiJkp-U^Xa5guc(W=9AT+Kil zq)_`cOFSfa)oa@uKpg8Xw~X*GjYc&h8-;u}AAb&|URdY&HH#E=6UVSOe7%=pf$o9= z&ly^@BM&pa?(}T5vP6%HGIb_X)KZ|IH8E*tlDMK^=9yC-i61pODeDOPp2Qw~3(s^S zG6ZbPr&?!h9smrbn#%_%6XiQbt$O0ki(z5cHt&2L>gs0?X7z|?Sw(gx`?KnsKsIZ* zET$iH%;1rc^|3v~`WD7K7o{F}fl(qNa9`}RYM?o^GNe5}9tHzFhzb<e6mV%t9<^P- zsbiy*V}J_ilVK7*ZGr#SnsOOgi<JEW7RCe)F=p6xm-(C<zK*_qYh5xq_b6lh4{IMI z-9>g||HD6er^>qan(0Y(%cT52F`nW6M#Zxxf8@I}b7aX2cUqgUcyN=w0qqh*F@6)u zHQ~Ry^(l+9m#(oWjP20Coq0qI?>|sae;XJBkrck^9zHc7MU8r}Iv%d2F)nBy9$%W+ zuG1A<Fb7if5Y|$@Yq#YhNfL4PtcXzoeM@8UMy`PcNcYl-qH5$~Lcx=~)vj9qZOWHE z<I)Xu+ideOBKc=}!zt8XjfyAX)T88}nzj=eS~Z<cYgB_lpVcv+wJ^GkM<eL&&nZ;* zVl?rPR}m2wyIc-&y>Ip;ZV#MBvg9j&!&~2+%r=I9A>UJ;;h-<>rHH;}jI&rw`esl4 zM?<*&yq#+~)&s;+R2hBC7dg@-EpJkvlkBGVOfG$N<cfic-xlY*0-T6mfR}365n-nN zohVNYo6^@7+dpl5E2k#=a*ZqNE@@-mxBIm~-RNs1tk=CU);f0`lKsC?qG|`Yxgd*W z)Tgm0Gp+rxqT^gkyA%GH+BIbK*}-Q1`K+~hc4pJm01E4wkw6)1x1Q1h2u{2KuFA_J zGJ_17vT55Z=fKO~gTpJlnJvd`)_eL0PJTzch}T_iw@ooXB*)>qZpp#yjRledq|73} z_HuG^U=$kkXEbJS0SJQ#NX;i<kJwdk{ISo5b+30<Nu^e`UA0L9(yLJBY%AWfbGW<` zr9lS&NBj)a%uSv`S_<A(h)h<w$<1@pKSu{F=Q$R_U>s+?P>4cI6Mz9V8Jtf0W-<Up zlL0H15*L)s9`*-7Pdg1pl9w$S`@f>+9>!OnVMtiUPhC|Xm`F6;PT;m1Mc1r2tCltu zzX3a3lyVQF90?2uRnqg{3bF=mzOcm+V2(fCk_>9cJ4fQ37j|A}U{mP_Sa9rRKuh6B zzGB*-;mQP1mOkOdwJN}P)_ZS6BtaEhQm0QLGO=!j!6%XTR=Vz@h`e3dabWWa)CC>O zrXr{cHth>ag^+x3Y~u}W1vAyOph|k)^))qhw=y14R3e;P_LG7l>9MF(*(sI3jb(-t z|4w`bi{m!|6q)Btj;J^3$%ztbE@S(9Qiu^(X|IO$(D8*I1`P`kMC-8PbuOo=+v-jX z>8E<70I3d$nLMs>PfSh$yLGw&HT0oVO6R!^E?`;_^BZ;~I2-cBaWuO~00=9+vbhld zhO9MuUdQ~Yxqa!}H8v(w4lk7o5LtFZSoNK#{0(O(%Pw*rcY^@M@h0GtdYS>IB?ZCl znJ)~aYN`>l8cuOvb~A7$zRC7yzDUo3`MnI+TKxm$ZZ?Ttbz-Nh-2O?=@G@$#c=_oh zqS9>bRe30ai8;0?H`<_)rrnB3qL><?5hPx*$9r314wa!hI14m<2onYQ9}{oWwQp-J zi<`>loz3AC1YYF}-er#mE8p6uu2oDp&M>?Aq`Uk9g0DAh5|?;xSw#tI56si?Oc2(R zkilG3@^v=$>56M#;N8EJ!l<_MkAFRQ=WVIvI;AU;72;bb6FaYhJ8#CE3=Uk9tdaZn z4x<HmtrKTVAK{OacvI}}hX5DpLj;DGd>AAEU*qF(k1^Js+4Hi%=LsAcu><5~vj=4^ z<$a>4qUird&3d-%UPSAWrhfVOV~}y^AP=`}DU004{xV{{l%-j~u@@`}7*0@YPXiU~ z3`lATRiNn&0#Q!IN?UGdd*^sT)q2_l1rb$lw{);XaG+>%P^_MPN5T}m56mYT{*Lax zGk7t{$<d)Yn(Tc=>puz)_xUwVC#H9`NrPYom@va<Yx9RX@fKFtdP<r7c0PmBiJ^%U z{p!^b7|CQ1xhcE54f$g?dp!WV$zG8`I<sSOO3%cuM*7pZs^p(Raxj9?W?IieVjfze zExMdFb9hchab9*2>e}Xuow?Ua!4`6xoh<ZFj}gs~78X&OEidF?W(XoXYwqllB4jto z+vhng1k|87KLw$Hrz`Gj?w=G{K<j{u^CG8kk<?@C*P}MjW|)qs-?(<)*w3avw4MfP z43`2DE~dp~;GG$GQ>V;H8;n2vu<3+JteTbhI+~O;Hl1%%8#Y|3hDao>hdBp5K1jBh zR&*RpE_K1NojntT;ZYKXocannDoRABEo}w`J}e0MD6G32<t2OyTtcMp_w#N*ImM6X z&5|j-Xo&3OG^kcll_C{%Wg@bBtWJY)*lKX-?;zRFCvRScCF5@P!L}4kr!=-sbN38d zr{p-1TDv;s|4?jpKjGGk|4@TY^~onZq;%vR?LZagz*DMFLjQ$7o#S*70;%&V$fVtf zc83?=WkOgRKYpP#@ka1+)$uD+4M9;}0^m_#o^x0-R>$ddjMl0Zok~Az`mJ;;M{cK$ zH2$L(EDa17A`6agq^WT>G1ZSdD#CZh3Nx~!B(B3|;N3M&mb4f@U0$3`2!s94N=+v6 z#g_E<T;P}VXlb~HR&*q@C<LCUk`WYNXX=d-rS>4F$dRwQE%=n{Vq!!DEp#vxL*AD_ z$Jyvl(Jn8b9?+LkFM-(F-<bm1d|#p$OCcfowKmR6XKJRf-O$SEb}sW|Ehz*_<;4p5 zKZ0UUdm>@3#0M9ydu{B*OJoqbb1;$4yRN{rAMU*^3L@^SicY6gd4<AZa?b`D&)xe1 zD_$AFRI>O!9;a@U@h0Q%6rgVOc?FuqFiCUr9Mo}jx1*@6-Z7e6+Hhu+qmX!4?#p#y zk~m5wX!i_Q8rX8AXkr{ZiWJ3qP}3%k#vUG-L>uC)+1vezn<PoMH~23PiK0AwZt^G# z&Jd;hZ*dD&je|Ld9p|Ab0sxuD^o>O$51)7a+V>73x??8&4xC0r>dthl9)V}=dMYem zH_=#={a=Ed!>sS>dhsY+u}n2@ch)f#ex}!}QLLaG-ZFH{*EEDgpnb3yJ+&HO<)E)L zqpR;g`T5p$r!UCNOy*>4^2j+$@R>|b=2A}St*_M?d6x1>!YE(5$yzS^($%z+gq_l} z+cF-hPi#8fK5QM6QRj<4F^nR><>x|PXK8<vwI8w!t*6B!i?-zsGPM!BLue?6kXsl< zn}@o_J-w9Jl7oPk{e=$Ld=F|-6Cp4LQ{CZ{5x|EvSY^B>z1DY#>Aw&LL9Gd;D{^~) zx_c;Z2IGYSHK%TWiBe&RXq*s5oO(%d=E08}YA&8TF7Ad559%J6txeGN7;dDtnE8r} zg<!D2xFGvjjcPD?ZBu^zQ$qi?L5@L&Qsh5H5hO`$v36fnJm@(4SENHC_BRhS%K#lY z&8m9D4HsT}OKsC4=T|$~S6rUvDDgHp&q)UOqU(LS3rh<_jNy{Z*FMRX=@3rgKsd+i ze+`IFMCV_&oCz0mw1kr7K7ls*?b7qcBdyOTZj=PFx7=VGr2-(v>*0YG<b@0_5Tj$E z{{Z6HO;*usg{hk5ZS%sS@%b22V&!^h(ArfnebD4P&D@@ByEHq-mUtJYzP$Ri?c450 zmTpDCCfZ{V)LZr7Wy;BJ2U8!cGo)H@%Oq-YrUbV7h5M2lgk9~FpE06z&Mb~K+jtuU zOlzhJe=!XkU5LNfYhRN~wk>3lcgUq@nz!M^W8RNEqqFoE1fKwk`f|Z(2EI+rpQ85{ zYQfjor=iH>mTi>_z0Gg9>Wf?&PL`bEqQ+~fO|9N{eig8|C*_EVorR!J+h)ay86!Y9 z)O5MjfSt4xwL}!GV;$(m2eNrvM7O&%h-B@RwHNmDHJ}D_95-{zVs%YYbUAq#VCKLc zGR%d&A1r0sw<|L(Di4^^BIe_hdk`Hd2S|}t2dYX8vI;lu#e_c^P#FKio!sP&T}EOP z?{wqj5D!)j91>Gp;t)wd<hF~S*jmd}+2t_*XnT@bce%Q9*Lp-4OH_n~2ZJNGzCt;b zV~oec6W{XRKM&4B%n}!fWaz}l#fMa4F<qX*o`dyG8k}JxGi4V-u)=3!NJ;nEA;|4I zcalsA2#o(}5LD3cxi6FyZ8{;dYqrwv5b(9p@^n2?7#+4yV{}EY+H((IxVy;I94~mu zS_jJ3{*b}f)-Y2jRf|)!YVvqoazw!vX5yElj)TuaD{_``G5`Z3?V!Ng?s2A7ikFC5 zagD`gL@gbBj)B9xz~XD?ebK^x!_Gz`ibc!ad3C>!_X_?oO)R<<`#IF$$R+L_?S@^x z3H!v^cag4-Y|ArY$fS08{ssCj6?757gmCaRj3qt8IoqUMSM^qD`oWO#*%*9Dk13eY z&$r4!H$23@U?t#B34@vfq7n^C#ygtPT3S;mhv-NQiJHF&uX%IDuWPkB_?Z!>kN4&C z)(ILyw&De@|8|gW!<7<mIuO5?>w;H?_(33Y`r4|;tCX&eqHr|rbwoa8W{1}gjjAx{ zsi^a8vvYBu!r?r-q8I3daeer+?|VW-{(L$`EL*5nz%Fx6J~L=mFX8stE(<5l5FUJ1 z?TRafa+n>az=x0Gwn#C#ru<ABlbDthW#D+>UeYqtvcD&5-{j}{&o#tApJ^z6^W*yE z$n-i8H%CS^NGXLH86uN63UFdn$}#{W&qgJR8JgPu@>%Iy7TTR#85XO6>`rB5>#M-w zLTkfpG}4v6(SZ26!G3EI<<%ae3w+0Sv8|&tFh!_m6+)+^2pZC3F?uvMVTH}{2~v*P zv>8#WF@$BvZsHoCw|E$f@BS+6$*e=~C0BHs(5}z;h1O2J&`!3{&9MM_GsP4Cupy3+ zsZ`y3onk72u*D8S2l;HYa1ZaZHL+@}c)^AYp^=cz6e*UKDXxQr<{@r!2#Q^S=iwrK z$@(0G6{>=_!CLjH#0CR)F0?7?g15n5@in*dN5Y1P&2GQ&OC^-OB0Yb5{Rl4f3mmdq zp7hJYk2YM!=`@g<$0Ivc40HjzQ;~+R9O-8cYH~{_Ht;S~*YD)f90V2)=mcAR|0z1` z#)y}^Z}1=c%{Y$-B@8Yon)rT$s?lC=ej)N^y0{GyT=)4v{~bdD6AqD0o4u)@oeK}0 z$0g#((dM@--(`PP8YMSS#EHd>JC1r(>`9erJNUc;iGkaDt8yc1y>Qsq@+47POKgEa zs8if4+IV=VG5)Eq(L<uXJJ}1nYYq+}3`cAwR(4l)#g*AXi|-?=KGOs(lx4IC#r~_Q z$#-Zz76HIXTo5ehuyp7i;!_w4ekTCh6DKwElw5ZBMq2RK{frJRz+QMt5=Gjb=`6+~ zgbAwIX^f6>Bd6#^7#53@OatRAzBWL6>O=Z;3uY$K(z$Zso~0`_2<}_NLh4zcS!nG5 zcoi`O#Ts;J3<J#NX;kWhIb4xeXg->wvix6_Izdg`+Tuc-uWOrR%|G%Me%fqgu%(U* z#9Y5o^3vTHJN@Zj&UT5K@uTmqktH5R1<S4IusG%oZlCD37>%!wgCm4mEqiQ?ETgfz zdu121o7fH`&Bi=Rkk^KRC&~Cc1oZrtHn;MwqNRvYC4Gxp%R^cU_T>=d2``qdH5sjT z_hy+F7p$@44BpA03`^IzSGY*;J^U`dHta$YdgKwBYK*$6nTd|RUY~S$m!n5QU-&TK z6Sm8%x_D*j+*zpN<zv`<vraFXjpApoVebWB9w&2u4r~JvJFM}VV#22Eq3yK(Q2kv) z{UxsZQCK*t(C+ChiR=R2N|{9nl>!uos!vpT53IQ!_}bCIM8gx~fORVtPFqiJsEejI zU;*~Zt%;HYyIL^Ze0bS^=|H4<dBcc>7?t7UHn+DH76@ZL4xR&9d^JP%#35@9#m=ek z)=iCLcPXpOs!R#d4KoiLFCf6uM_yRqpr?#Ml|KEN5mv{|ws9akFz9Y$MVkOGkG00_ ztlCyvLV`&^7$}?cq^F0^?Nq1^L%*#?iG`)wJJglR>(=Tz7fp(S#77AbzEVVC2#$S_ z{ChTlh!%vq<)9_-zFiI-d?3}VH@zOrx&gp@(%9zf#6a<iq^CK^To5Jd68SS;4P1Cv zCpb*Ti`cgT1qNhtnX-Qi^%jdwdl+{p)i1YB|MrmQ;+JJ)*Mx=6VK0`nqYf^YJ}M@B z?|l#(SJ6QjLj7H^wpSy>z$Ij4u$sJ@`2byepjAL8*T#NdxI+{1N*}#pE70pv;1}7s zbG2bBzJ$6V{O548@V!4-SFQ6#>hmMMx|{s3rxD;e6k4N8;I2|ni#o-^`qpOTpq0!S zA(j}v^B7-lH^v0*yVUyg`0Dj%#|2NXQnvl9S?zu4W+mLQ>1x@f;6G&vwKs2}i39)y zU5MQJXbsPZ*-J;@`l1?peHPZ6sakNO`cCp@dXK7MNzb`C%=R84?2!M_70q^t{+#uM z6}slJb;ZQ3ev)se?y6MPDVs7;$4!MLS7ggdyG=~SM}sEmtSsZ3+eZkQ{3grS;!f>- zNuFzVmxr617VB+RZYI%NuFw5@<JHp>@#ioaS<Kd9)Km}cZGu@Y(zV_`POjZX$?ze+ zt9Td2#tm$w?>|^xu1RTbu4(S~SE>TUu$Jq@njgnT!_l&BytkXD=Y-4`(#D-%m8#1e zbKci_<JIGA&zSEsm7cuhO`W_hi~g=>@dGd6>uUPVe2ktvm+yY#b4T}%+MCdsLcK`l z-D4iaPBtC)fl|-rpIaV`&X#B+ug)6K5cb#<7q7G@(M|XX4{Svvk0X>h6T>HoxVT=Y z6AD_0;p?<+Mb02Q)!a{HWQ%8{XH(hoNsB-YV_&e5^plK~4-_2g%o60iWeV=xTo7@N zlCSr1YUEj-^i|=kYp_}!XNnj#a$i_lxf!!c&mgA+UxFt>{Z*jT(_EmlQjG_SKwr;$ zyBz?~=FH7ffkkBvUrOx_b*kTMtaIx}E~0fbL3V5#9aS^F3i2Pk|4oDDBwFnK0R{k| zgaH5`{J)u&olP8VENl&|^_<O3Y)t-<5}H*N{zK9CU9H7`Ft*DfNI)dPFb6EW$1HTv zZrV`o0|#NtM>1$J7KdK>`P`0Apows7;av(#J9B@%@A<qryQ+BLUwSpt<&OWdr=}9I zebkg0`zU*8Si2!3_TjiGKE0lE`jEewq*PLYWnC8S5;C&1j^1^E@}pY!^k%NjNyX3$ zCbolqBBk5wa&3R*zBTBrwn~_|h!X-mZ{!|J7u$1kabY3j{8KO|scZQz{^ODICw})1 zzvx~b)dk}2PHS9Hh6jhUHpdb;Ft>Iyv^Tfnq1c_cFrRpY324-H`>A{psjsH$=ojFu zgW_E#`jcHm1E<A{aiGBNuRpwn>`R@@1Wx#-KX&_a`-d&SCDXwwrJtv2ErG1~df4Wm ztso4cyUI}w^$`RHUj=b8_e8ZLVtRsA`)1FTO0+CGyA2ByB=-c)cth8iN=meBUh~#1 zDR9S>c&Q30uuJcr{zAq%?T$0|4Outjv@A`JgZFJdTYNw`M&j<y#cF!;^HY|!spms9 z3q+yb46G5?!_<M`(Rw~wdx&Oc8fIWdJtm9hm!xQPFo1>iA;Z+-^TF#oUaLh4EpK&- z+v6s6w?-8zyWchfQ%00Jx~zC-l*}K|pWr>Y#}1C;rOi3g3By(J+mRdh4$~lr7lNgn z_Tg(UGHC1R1?w%Y@!Z+l5UUSh<f!d?=jp!#?f$5ivsYZlJ!Qc106c1!ozJ1~4}reO z5|^bYvGd^k8N<nO5NmU>F5d8G#z<5Ym_Uq1b;CBdaf9Imf4G{z_mwY{Qn&HFp%v-* z0zx)NNr94utJVhO@bLkQ#I=#%!NT*x74#E@`2xV9>gGK6CDfKKk1d<qW^_|i&Q1GD zlT$_rRzSGeQEQqAtii`L*0d4F)BuLVG)!aE3FaV>HgU?K#yF02yg;7FhYLfhIYuuG zz(7-YMAHC1g+Wq&0YX8+RZh`I1}8rZSKn<94IKMO!nQ;Q?!n8ep(-Lh>&I@afMtCm zJ?9GeqnJQFO#_tGb~=wgGnuu1T{^$KABkRTz@roy+=hpBgOS^^0Y%ygcnmDC^lN}p z<JND~U%)Xm7aBx0MQO^joTKW>s2pEx-qU(Q5WBxtN9MK)?v_>~ld)IUVxL3y93`3( zR{q|I1gz@aN)5niVO&@2MMo9N0(UA1T}OC`DX8h(KIYyw4yVRm%AHs0RRij0oHF;a zYfXtINL)!`3j`w6MO;e_!OejPophc<*vER_X#N*n=M*GLutnLnt=sl(+qP}nw%xaF z+qP}nwryL}@n(Kr#G8t&->Uk`tjx2|T83hpYL8E3x2+S!0Ratyo__$dC4*|_GO_YU z^8M$3)4Y2rNh07<gEq#22X2b;DX^xiN6)9sCc|ykA+VhVc^B<2-Z_phc;P02?k!!7 zqd!3ZJ$opoDQ4!70RZ&20RW)?@7crcKazIujrd=b{Aouw(D7xkc-X#}kAqWc_}uz^ zr3tlp|9M(i6bpP1$=p^g-#@Tkn<tAV`+ctgp8=m$pRqAbWn*oC`lE?KkEyg;Og@4N z3Q9&wN?S74j`p+q;#SwW#f(dX9&Kl5?I!gxco+ADBzNW@w2LnZ4eR^sai&(SsyCA^ z%Jj|(tcTpo@G0XtXW@aFjt%X$E+*NL7lzlY?~9xq>YmRNQ{C^0hZNt;`Kpo)&F_PW z&hq<*^4*G>ape(TZt&3SZ&qW<7v19w1Ml{oG}0OuZFO7yQ>UjBY|;87=%sU7O*5u0 z*0zy=OzMjE?KJ-Voe2tC&Idcy>TQk5-kk}@-XwRog~R75XLs%D2xDa%P~(P93fh+A zJH<{nc_-B-eA{LUiV&wxHI*Tk^d{k!!iwfkR$y~w+YW31*5)r(yQT6HY3(#o>y^Zg zqRl|xm1h0AvL0IL22;1Yl}_@9s<g|Ok1gul$;MY?-EOj-Bu(==@_UyZl;R=i?oAXl zAx*9QB=g&J+Npu-=3g%Sa0K~}qelnj^%f<q*E!o*{f==5NzdjX>00x&$`i938ckl7 zt0t$P?)}l?XW!zij6SZls?H|EeaRZ7pM(4oYd!i)x5x9-LXVeY`{#nLP7cq{yRK|~ z+)F=MhG~JbecVljjS*JQ`$dCo&-d}tPHq!?OuFvkl84jHc9*dtm2I^j^}2R9#jdzw zZz*d>J;m_UX~%OPZ=@fLY=kS00YEmgb#@<=OatOJF7<VkNI}V3^}nN%Bv2oq;E9F= zR5^3cdoazev2p8-qYY*HfaRC#uewu+s^A=e+P4Q7f7;??<!P$#KhWrOfKxg*_gAa* zD~ExxGQewxQyT_ZSFUtPl76v!wdc}t&UA3sI2-+D>DM5*F2~A&V=a5v?QWvhcj|T8 zBd~iWvyo;eLPVh=-L*A{)_tcJ%4x22gBeJ~Dz3^#ejim>7?noNfzE5p6GyGo<q_h~ zw~z+Oq+!1bnCB^$j@-P+9m4{QsLuhW14{JluU{*s_`~lhxu1f7tKW(|JebNev11k+ z*;O?KClLMAc?2{9Od#1mQD{dIBJ8rIL#&9Pi*Pz9J3k`?#8w^cEbn2eMAMWb=Q`9= zIPq6to5PK@KNe1Kc62f3&-Gwqe|o-*Y);%yjXzS`(+N?f-1iv4_Az^va0jF{2+X_4 zTMRt{II~A9kZ+1+E+rxKMXt126U>5YmlAiJpc?@B7aM3g0dU`S`7HB3v6nGdK0aB5 zx-vc%j_aQg|At~4;*qXRd>vyOIeEubRo|27I=52x#vflWe;+s(_WDR7L*W<P_5$V@ z$u?Nq8`h+`-#@+JL-;4TIBOF6Pdzq9>r(X<X;xhNPljzPsMbTE%}KkvgLddE-b9PO zq|C10dnda-zKHI>o4aJGeh_lfwrAw8Zo{@=)*15YYi6sqsCNL0YcV0CN#cdb#k2*# zmit1cYA;eq8C(L>_X78EnNtRY`ie~9ocoXBoVP?<AkOY$)*2Wk>vhqnw;FebaVU&z zpF{H>+FQ=WJSvDB_qf`Td&Eu{NdpkgDJWUb^%$7Ii>P2*!cJcGzlO3qDXZqfd}ezh zV86Z&TC-y<_^3~i0E&HuFQr-a6tS*!dS+}8%ZL=D5sGELv8{V@r~V|i1T}X0@xuy! zW;0j18RdHzV<{B#b(Dukz`3-}K`oBv_w~>`D+m3=+o8XC@3QQBlnFCNbkK8TT!06` z@QW_Neg_p%x>~Qnxztv@LC_6fZH?Hi#UBg5j<C2PrRSdqBR~k|E5h}OGK3F*7a@KG zgL4SN19tZW?5GfQP0XChA>I%Vs#`Y;roOSmK1^X@k7uw#n8Q<k97mOIFajS>W#r`% zl>j0i=wHFG3nEXzu+{woUwwj;NgD#qG+7qldNjZ@s>q{Cu~v4ya)5+!KSuWmYekpd zg8f%!3vqO&87%e4t*-73EmuVgi}~2~7k0Y?*tC7(TEd39F<i}N0!~RNgg&tW_aHl0 zC;S8wv`!WtE`2K}xYH%SQRv4n;D#`~1o7b1^`D{4XMC<ySAZ5`3sI0R3D_fh)Ttsr zfIt)=LcB7OsG*PMdg<+a*Z>Q7q2M1&LKKwaJAL=a7?SP!NvQ`$yxx9WV^@gJ6<?=C zLNTJBDWa8rrUzXXAPXCn5#Px9049i^W>gf%4iZ2PBS`FH`{ltqe2!_LMhN)Wdw{E5 zi^~H0{52rlivZ-#1Rhq%MNYi-+haAsTMuv#HYb`w*E2q!7v!TqLZUY~5NvGYe-9S5 zGaQeQZ?quGV;+I-Q5GwXL8eSA<Ky~zouM$L*DwcqQ!!<YS^JU`#7hGkOOZy+t(9+V zMiPVqFszJAQIR@p0jJ_Of*gI-U1wPBOoScf>XsELd>~Afa(s0w+Xs>Q6Puk=QqiQz z%9KFAo~w;neivYeFo|xBKQE~@PauOFE8E7HcPM)L^NhJCMyB?Grs*ikNGiM>k0=ZH zMSCmkrDYz;DZ(Mo+MHv}zt|umAeto32x4kN2mrp#?#T{&r)cQ)16y%Yd7f%dcp<ce z;7jh_oQydP*ZS09Ydg>r(>d>ri`O|OPi*q*_Yf#8FE%=KR7~+Mh>IaKJY!sf2H90Q zdK)+UYF78pzv>9|PBkH$W=X|?Z^S{T#`(>Hq<{GNH<ma*ZT&f6yO=0jEMH4-5*k}9 z8SX-$i4IbZ*M4C~?Pe0TQG5mu@2^r%t)VF*$9%Tg+&XSE2XhBEu4`w?YbLBu-5LYz zyb}!%@hA%!?d}odrsjpBDq32J6-eLZHpqJ2VBxFFtbXcp8uAj%L+~H<@(d$z47B`t zA{iGA#bp_Hp>)glad3BcW1sfPtf2hnFPx1we=g!K6@6EkPhHP!sBJNK_-BeCIUew! zSpq;x>^OxoO@SAGRZs>4n2x}m9^7N$&rwj+Lm%2R!DNOEXkIp0(LflVvwgUE`)%3O z2OaGJN8}IX?Nch!ajtF)05h~3k8`?OSesIrkis3~uH%I2HyvBu$Wh?Zr=>77n~rOO z3P4GK8P>!PLvRw9NKJ}G0y17`J-j5AM^a)tOedPFnMHAGu1Nsr{(?XkjcdLr2nJ?4 z?$E@{x55tJ4QR2PKAM};Sv9eNRY)+y>kzFTMz;0K{vI^cfo23YkQc+4h^nXny#vij zV;&%+c>oL!tIt>y5c2M+ByRVn%}tjK32NwNVRi*-&wJ|CH60OiYd1TJ3Gfk!Yb9<5 z#G`yETn+G~fYoPIY#-es(0F;2^_GsD`q~U^bp(SBwD*GwYOCFy<?Dlh%<iXL_o))N zW5^0IcA}yV_c_!xYl3AmyzBNDcyPXD3pV#C8H>iFB(U|R`2Hr^&S|eph(iI#a+duW zWa6Y8C7)%1r<0y9B`uq4N#f{T*(y~@yPII}i#$|X2Ovxh(~yc;pX3NdIHlTu&xVNV zUw7*qg<!$1oWuEiOcn}?D{`hj$(&x1Qz1fF*Axw&N&vs&+X;a}{&cC~_4M6iy(H=x zKn&0KuNXxjit`vlr7rlE@T^UCRfuDj)AtioQTuRq*g-ag?=Z0Bt_s##xQI>nH!FQ- zDm<&|0e*gx0cJ3U-S=&@wi<@LJsw@0<nP4=V!DXp#P{&ve;v;ddHWgzU7kGWLG*nE z`k+Xc^@P&?O)es%w=%-`{8IKmEEy!H@D)yIjDMvnA+w<Qkx3AuWA&9%oM27yEg{%G zaa$M5x3T$79hPD=4YS2#3+p~k>z`cg9MRa1z(}C))u3Ak4%In@XG4TY#Q2#&`w<gI z6Ex|$H90xEi`N`%f^B>Mc(?K}CZ9D~xm3+UEcuSkDVVq>pfgm)%-Qk~`4<ONd&m=v z5vTpPXUgXMssv|U#qW7b1X8Jcf?dlG<EGrNfE3f!_}RGbd+2FDtP)NbqgTo14xVvP zw@Zyj2KrKgX|JXmAJ&>)nm}^l&&9t-!$clE{JB?wIFyJyaVW4f0=~jX&;B;83BcxH z&LJ?CY~11Q7{K`}aqJ5Aq*5+XS!~s}9)4ZJmF<&j#9by;Kl;=R(@!VQjQaR<We ztQ{a%+v&6!|JQK5XVC4DtCUdb$g*H&^g9VoPSA6vV+^-U))Y^syJmGyW0*dNZWK4g z==Li+e{I$^VA@A8-@43qY3R@2LEgwWpuDzfN<v$E49cQ*a*JmRPLx(I-&}=(#?q9P z&J(i<L03C>Fwq#QTK`p>Un2H#Qxo0(HISYYMh|@OaKFe-oRtkHwr$_&IgBM@_NC0z zhJ*1u0usvIm!Q)?*ZgEXWj>P3)N|7k;v>!=n!XqrY8(bX@|t6Iqm*HCctMcxT3a!4 z?(QCegyq&+pU#gEVvZw)jUDfe<PN^I(13hDi0;_pVT~xm*Y#+oZ}JZ~rzx`mWyU0& zsk<@Vi!c{r;Ns~r_9B#?%#JNH%|BStAv<5sW+addgD*k9@llv^51zOYq!+y<DXcV% zf8C{Q6=w_kiyZD6nfIHNP#(ip(PowPmqko+Zz!opb4`}tfSvr2ZzhI=z}z_K4d3F9 z>^NFo8tALYdE-vJ-*gW-7-f<!94GfBgyebwntJP~#{S3IUgv0|MKXM7Z#z}TN=+~4 zc+fzTjNU#NIJC5wC}%;3o@dAP(7JfnI>UJIo7Z-bE%|TsUvrJzy}vOJ%fhK>PaGoE zqT8N*cIJ6Cg4+gTwuIXrgFS!VlGlMF;WzbqsS~tw11|ij<ABbrF8^BNgy@kn`BbSj zKm6&|6Hg8=KhuSGgm`?tyd{@vp*yoY4TOUe`06V&U-p&INM$Fa^!%4eJ{uaMV?YR9 z$K*BX-oH*mRihY*w4DcG`7DgpMAsOSTTZBPY1vMfMz^uga5H}K@8OOtr^Q*G99h@U zJ8`y^+snQEg;8RctsG0&GifsBHF43FRk~y2D+-pKZF3r(@z<_^wDE^K9Sa{G$zKcU z3RLlKOn_(<K&&hEQnd%9%R+e(>k)ZFOzt^oFuf>0muxlqAEo-z#A)no9<CyZKx*g< zQaaF$eZFV#nFtpq@0J---PL`kAM<z4WEU4<Bfk>rP-2*RBq<4CmR0=deL4C46E&u? zCR^^$@LP}l_m9yuI26bbj$r%s=;!(C8DqB^l3<2t@8Q>^cWg@)+UG&baiV*0C$eR0 zkX$K$8>qzgp^pgl)Qjp+gt!S4IRkNx3AQm>26iQ(rSx0<uHzrasAOzm-X``sJX5#n z>z$tQmWy(;^=}v`Nx~uK;h*$B_4FL|vk*~NFLt$o4-`S~72>>}DTceXvUsxDT>9WP ziq+L7R-A^U)ux;b($2Xbr{$&Pm<90_PiFB}9KhI><ql6SJnb|Q8S1LKIx0i2iXJBm zVUYOU$Y<Spo)`M-ORt|P`)Kc41?pWzd?u@#G~=$tWCN%0M1@ER&!*d)(?5@xF(Ss4 z4x!%CRzA}#Z`9Y#6_iv-^T^>s38|(xyy%jtakJRUVXmFc4PS6Zg7iMV^zQ>7Ekjo! zb76;pMBu$qWm^Ak8<G1~mtDBtW(8-(*JHeux8sV*YfpRYw{!srqt5(kz^D{=<|Ruz z<s=z6Trh5(H(*0L#XRk+yN@Zur$zj}HY&NZ_91JBhHx26#ru6q;y=vckAuUD=#l+X zebZbeljTS-vS3e0*TgKkBKPM3YQDI2(+JeT0EJJg1A)+>dZnHWEkMvm?q7KeU7d(V zyRhW0B5MR1d)Qdue0}o#RlSCW*jMXtA`D1F;Eg*P!atd2p%yNlJ1!rWRBL3|VDp6z ztdw1Fx=fI9i}zD&TJT|_ZUt!-P{c|qf<d4nH&roHsy_8L*8Yly_`8W-c9|j9$qrM1 za7$@NFgWwksu#~Y69RiP$dVSp5LSY3+gaf$9%QD+h?u)NsoC;RnNipfi!LHYROCY& zoj_MmhvG?$#!E8pMpb-jR6j)Rg?1Kks(p>q57%bu>3MI{s+RXf*62FjV&#^ADIuR` zEUQRBU#|tslF;ZKuR(mf>gIxd1yV+_#h5q94xmnWNP#A*6QDAdZiJo1NPbQ_i#Dz` z>KkBvcTz=?MOek5Cp(Bo;lL>%(}9P$ho%Y{q)0{v9>Y}@EK?%vpoZNRw^D6C;jCmB zm}{mhb|{%7C@4Md+`8F5vHB2InjH<P3f)2J2VTZM#n5H(q)|N0nAA=qO7R3ucqavn zf56S0QKQ8oXSZ9Of6;6J{fR3#<adUx)D})EEv0}T!GpPt7fED4U>%xNsv~0hHHP9w zK>oHQwD$AWE+CLNH+D6-s*&DLp^7RxS5UI!R)L5J4YI1{?wGt;C|`1*Q9S;NIr26& zjxUzgRN&aVpE%$SA;f^S;UG)$(9U9$!ya>qG3J^hyQQIOl)FwCOS=X-#rFGIwMC;3 zl!7n#NYZYYmO&C^@KivKQnD3gKcnJF!t+I0)3o&%NKGv4w^MdtuROTmJv`CEn!cq- zm8nf_0HY41D1fX`fw%G5VIy9Q0BZ5=Ey)!2dxWMv#se^uz%(7^NpL`=(yB?lHGOKw z4~*qLOw0LY6WfD%Nzh}<W4bm_lKeC1|5g$ZhFQ!x&}C2V76Oun8`6i`8Bl3F%#uD_ z?j7V9B>yRRNvG%S9Es+Q+#i6MVsCQYWv{#1;5^+6qyBME-XsTu`E_ro9Z_dyhf@c8 zY5xg}Yyi#xgdKu*!A3dW^tz0~K3v>6WeNQ|qWKl?K}NOYJC&nF7<|5#EKsU~Vq~&` zn6zgTCpO!j5PP_`&i<6uRfkT}6iEHL^H&eJFuqWEpGZdF?*^b9AhQZR+l8T#1yyu{ z0t<qR%wP~<A{hNr^vY%aIPJz8Q8Fv;vG^uH<B6}=+xdp6E|2%u<UeTH-gMinO>Ex# zwdG0nK2MQmw?Kr5WF}x16s3KyW&2Jbo*AVp1qMy?2wjvB1Y0e}GZQ42PABIhtJl+9 zNI}@c&eke@^jvQg*kBJ}#p*apPDpkgyc;p)I8Sthb{Y+d;F1sASQKW(s6aqk0rx#- z_kPAFS=<}G0-L|(lT2Qz?oTeaCnh~c=~<>e#%t=Dhg0X{k`pTcdQ^xoO?yN6x=k!F zL5wi|w`ib3Shg&AVc^;T1dC;Qy^cU=V8|3elBfvh@HKsEe-55n<&r;@mBV3?iSCyz z>nt+X!Ep)TYf8AjjH<y=2{kai=Qf57z`+`bVWPAZ)qE6_Mhg;+LUsEihw-twYFZIB zvQi9G!YE>jqAauo*R~S!$B9}@*STdZxGQ2S>A1KeU7J`0T?MfWyF}4SCHEKis9IYY z)VwK6uPV>kTTx_b4}}NjUi#Z-;Nn$_Knx9r{l$*U7rV)&JXQ~Hl8L*0+5uL{GEqf; z+&SV$mKpIp>A79pp23nbQw#f@9jG7((e-$TTsP(hjkU|ZKANe@n~06mTTJ4QEo%>h z5QZ8lk4AhM)|H<o0^-xg9#YcCOw;w0joS<0s9JN0C*2kMtG>2YRiyitk!j(h<b@0Z z^pLkWmxf9us|B}I+nzq|A!jbyWZUwy{5kYrLRdcs9U@rrZdsl~=wbCvt8!A>efyl! z$3|1Ai4bWDgmZDkn$Z7fQo+J2r81*^uKM9&P28Py$gSBVRmuZgDHJahqTxu&n2D}M zH1(lL!J=T<TWv8Jub^rg$}-P0m1R+ZlAL!b1pLr>OS(7dWUx8b_Ck*nkAKKB8@yy= z%%`ep^1@qnu0!JE6`+ew1DL9rx~{wEDjRw)(lmd-`ef&;Z2)LS{@`qU7&6QD0Kw*% zvnsLPasP^+fDXE=(*$9Qm(8%AKM24)pUvh@y5yK=;g_I2oRre=F}LJ_xwWWI3-sdx zVorzrJQAr;ooYp5`;EIjDD_v6F7qa2_7{qz*K;Rsb4RURo~Bv!ZjQV3x>VlI%VS@W zr*Ax`9$7P{&?x+Gyjy1<36rN7NhrGmh%03(QiqO#2oKkz`d;}}Kfs9p5u_#tAp7df zXLcIYkK64Wxm&R7he&R~$k<+vDs|QzdGK4>(7qMGpN3&058k4LFKB%nbA-SK?})?? zcv{}t8ALapgZB7FQ7UjkmW@R(?M}b4R~La35S0G|%U13~Q?3dBTwlrqY6QFsDE^s# ztuI+a`sU>~`o_v0QirQcIJ+A&h-Xtr=q*Hy6?eDl;5dHFO)WeTmQb$)Zl0bEK+0B- zE35(nxQx#=z6nLW!Yk_<C`VyAF&%$Ts7D<?QGDO`_*Tj=YpD*V*XJwtq7wo=#)>i1 z>}3hW<G@s+Pz!Km;k``cVo6<|ez7xrnWrD!4{l&%dE*#wTfn&4$q;|z@b9WU6RrQK zc#IFDb+GGQbizK>k6a36`iM`v6@9nac2O*ON{^u$LnN^FI8G|LAuqy>sQ;%of5VB6 zB2i#lf+2t-XQm~QqBqmIJDn@9Rr5KU70PuJW0}#Yf|=5D`J{gV(D<bAg3ij#POOxl z!qCXG_N#bwA_UZ1IQMjfX$qPTWx+TPkh=PWQ>fK9qZ&ljX4XTtsNf(&(zTs)6H_r) z7JJsY=TGH1wn0OrEjw(*PZn;5T96xY=j-u%{oEvugH`2`&FIuC=%K-ETm!~-aE=e~ zX*CL-R(+PCtBIOI_IvyGoAD%M_h%B>aVA1$-y{VD+Uo8c6)=>YK{(-l>NL<x2L> zXN<RdP7<l0Sua90d0HJGrGw<_BVu0?^7E{)=n=8?vqE!RbUM)1B7*gQ3X@(-9W${D zyv;B0sdp-VlSgC7`j@N7cwg;%C~ku(X0GBmSfY=Q|GY$WHg!tu`m}ctl=-<C)qvdj z0dU~q!T(C;hL0Z!Tlm6U_($&R!C}HV$cyd=J*8JxYBgYvSNwAg;jd%~{MQQjPBHnL zzd@K?PCzQ;zV9RLujnck?qGF7Q*~bcoT+YUb`?h3puu#{5l=6c;!#NJ>;{JH!uu3D zY`+ymLu~;EPHqD=9m0#T!1S2t0ZqL*0q!J*Vzho@Wtn;E#ML8l8$iZ5wthiZEYd{T zmNsf{)XOMxm?N`I&%_AZfcD<?$fRfPZ^72dFd?(z%q!7(pxo2+w*rBpBEtaV(npse zb=m6O*NK+FbkK!_3k+l)e)B9Kc<VX`%<I<z!{h2Ik~1MW7^M@L&@?bGVDN#T7sHIm z(NVKacrHTs-wUHR>#g~=oNO}8sMh%l7arACSw^4TZRH(OWui0+OpjFV;oa>y-=CeI z@FC|Ozcx$Wxx+XJy2_#VU9ecaqgvcwG4=%9WH^4LN8P~N?f1f^B9_Iwe&eZon<|TX zUI8@0Bj@oDLG&K<6K0pz+p~&Q(Byrpo*T8u-7MC%u|RKQLG4OxX?z_rc)mXJj*%E> z`}yuF?V?%yBVh=D^^Q<Ty{DyxzjSB$G4Q&#Y}do7ndOHRyke3iz<tAY)K{`2VzG>k z#)$}vL=CkBW##f9U_7?#Afo7;=wl@n&+ZA|PM6qY4`S5HpJ7PcL4-9ah_UzH(9k5B z=tg99j)`C5YIIMMo`tYl+XEXlMO`7Lk2c2WWPj$YDV`p(-QQFeV81WfCq$nCSiQ35 z(l*RbZ%E&{DQ<OO?5m#dzJuG{G}?I&UPT9?+1Bv$z8O#nDG*=ISlVlk+AAWX9ElV$ zW8QQnchWA1(8tzEz!-)P9&;!n9K!i;4xMPFE+n0`(;Ja2f;Qp7Gx^OLzkasbqZ0hO zK>W|B;pjYi>V5+c#T?WE_@xPrxgFAX$EO7;9Ovs=9}ofMA&zIV-%aAM1&CYVYP>rI zjY+5|c$wiT{~a8ouDDT-(>F8~>qhk?J;SUD`*{?j1weqv8@AHT9BaN##H2(dv(ho7 zFu33}6J1kzUfEaxl~|1a0u9~E$<xP<Y0eY;KNkE?S=$}(VX5)=-V0zv$L1d$Y_@5= zjUZdF*!gMfB5R4Bbgg2gXQY+TzrI&yh%0}^in3}}IDX8qlo+`4X&7FtprL4_;*5+^ z6$cWSI*cGZMxl$l8NptCt<Ln^g~STKf*6_#=6ll*18TnP`A&LSVFqX|yZQr_xi3(C zQrlzG9Bt6_efOO$r@jy~gag7JY#<_Bf2K=C^K}$<<odWjal~|(nC!h!dDARo7Diwh zXr7VVe3eGdVx7UKj7iH=4D3;=dd)lKUN4r?A1{M}VQ)u>pHwIBOw-SqqwZUR-N8ju zPt0oK+m9oOHfOkBY}b>$&EA;W6&SJZSNCY^+`1ABzUDNAxl?(5w&Nbc&9lB`)SCP4 zC5x@=@pMm~hNshQ|B)s5Mcd;QcPDZ%sf!-f$<nL8@q1Ms7B6kU-(UheOWIYxB}YiR zE3qDE`Ie>o`|2x~YtPA<$5@X97b><;EL?is$jm5#0Q5w!BK?zrFH*nt8Pt+c0kx&G z!P{>Y(f)H+vZ>6q4>8n__{rR>3hDDM)-3Mr>=DWcXFnKo92m3bXXl0|efP9sfOQOJ z;-w1uUYh=)a-}e~7opD`K`uLTlKX4%_&I7cn(jz|*yNHojgqmDZ)(zy&SgK9%L8@{ zEeIu3s#)seWrOM*jOAth&It%g6sq6M|2pFDh?xw)owYCv<x-Z8m%{Le^h1nS={+=$ zbtpNsuJ6l(XT^mHp_f~Lio<h4io(AoDKwPv^4W(fL)lzy2_VjwbZH@>OH^cu=Ny}J z;Qe&N9(`Ay9a_ZXwbDk)KJJCwNb!~+JBwt1a=sl3ZI4e4c?73P6l4vL7jw4=gDNlY zR#&jnu3>z^3RhWt@Q5lXZ2xV{gSHd0H&dj6Thn69z^f$I7e&WA&}onl%MYsMMNkUq zaX^^j+B5T>w*Ise|Avz5Ye09Z0CSUc(vu`}1cn>1ucqG2*79kNT9JBUr$Qv>IMq}G z3r7&>XI37#TJLRbE~LF98|+hIJu-$g#9{hbi<91K)tf7fff+Jc*(w*j)oTt0&O2u< z-mgjB9X16Cez^$pJ&YRL08V&Eb0z*^BSm4ipU=^|V(7M3%?Ej6zgSH?;4YLURZ+t< zwv-t0v@b782{H4*V=T@lQj&dldB-M@FCCld>^RXWO&eyf(YA7e=CvqZA>oUbrbqsu z^Y-O4!PMhs>M?7cJo+Fj@5~rd{ikDs9)CUb*;>=U`9?)_oE7%MShcgK@H{$Mb1F?u z>v%+D{CHGMlFWyjm^m+EZnD@>HnZ$-#aO9*>cK-LM@IK$Y@dKpm*3wr6LDQUTcEh$ z_#TImQb;#u+^JH$CmQPK!;ZWW?PX%DRZ5@Doo@0n)#Q3>--p=BHbJtZgXW@=3bHql z4pvJ}-7g6g4}+Y+Ym8Ua5SZn#mhgQc=c6&W<CNEU?o(XoYQR&3Afj4gg2|j4SHcrm zKoUO<x~zCM29C=ylZK$A9y$nlEhmy2ZVxALe5O$^8A>Mh?)Kh8+DR3P@q-*2UM-Lt zyop@4pM|19Gc1}=+~fI`<_}R}8*btbhf`DFw$3fp%k4<?3k<wWxT~$A=T#N-%GH-z z)!gs;MJ@LO&?B+#+483{{C)4RJJ1`<bk7>$7e`vHXY!|pGw>LAAibQq)R!BB@HHw| z=H{w9kQ#W2yBhF~4cz%7<g$(OwgD3JmD)t-xTjY1owj>6^R)ML)+8OX?d$nlyv^u3 zL~p{*r})I-SGP!|*M8Eo^sC>$v*ao?i=Z#{WEawAVua6Wk3sRa#}ka3HRmXJ>2&VI zD#C~hy~&RcC&TlM;DsH@mXnWB8`?%K)QC~o!L@|oO1I$gDb*Vpr|G8TupgWAaC)QT z&ed1DZ==%D;VgahaLb6wN?+Bx>`(-zBjb(fc@z;bhSu^%4U1h}wL?oiJpe9BNw`}u zl7FKIO!wo&9j=AFY7vv>Rah8_2s?3h<K}c9HPe19MB~cD>ePI{mF#|9E)q(D{<!4( z?xlT#UG<l3#eHyJ%}&5TYbTn-Rt>wDeq!r9#YJ6LRzk&otA2<t=hMKN8u8&!mXOlx zo!jcpx7hhCQXmHFmOS^BUh+C)<SpSal&{VKb9^nRy!Ncb(>FVCtn<~8#q+DnDB_$i z1CrH;Ev?;60N*QkU=~R3ePq()98(d$k07(3A}%K_QlusxAgacDl9rPwC0&zCGYm$8 zIWkI7fi>X(UYeE2e51Y0**yA^Ur6p?BLSFDhdeaXwKon9M#lNOY?n^3G!r140nJ$W z1jk62p1Yb@nJ2_5lg;6YP&AHO%qYWT45v39YsR71r_vKYmciHqR^9D)o%a{$^)BIQ z0QH08aqr=B@W+5N6uZ-3##ugH4Xg$F`I1;S<@}=D>QK?Y@bT|$Mf$}DdCHib{jySa zM$T69M6-P&M1gN4C7NOU(yccOtF@s`%h(9lXD|Oc5i}CZN?LhNYVN9$f_;p7^J%zp z%V45XD!PPEN)pQQn0NcldB~?gu}&^^o6b&CW~Gq{h9)I23Lj>Z%!{%&p~KFH8M)k- z(-H-qtO1(1C#cAeY35c=r0EOk51mAkU(hhEUXI)M)I-V)_9Xmx>H20cqw^>vbn~4; z<x7QuSKB|v@ieK;M9p=iuMbCNg80u@yoMRH#)H=rb>3UIl{SE}ygu$|IMMSq1>fJY z`fy|CX39l=OL_y-d5vQ%%_jCx#?OoIH*b-M<X}})axdD2LxMJoaNSbS_rh=GqYo!a zo5k=AY$6vyz5$Y_x~QC-EV513H1Ai`y~8Wu!(KR!gOw9~Ru`@C3M}0%5$=XX#!9VR zmA~$SeP=*A5Xy0HC`~Kfgy}G8@y;N(Xd(1@x#TYvS^)V$t*uz&QR)%<>;R%KdAXtx zK1W=*8BXbZc8Qbl#r_-|c~*lblJ0wGf1I+naz9i<W#Af-t$=#bgb}4k|BpK$;sd>E zG_B@Ir$5hh<TR=Hx;AS(@;Wk0mOhWy<v#tjqzU1hO228ID0=+h0KR3_p@SAF(rl|% zWcY&+H`;>X+9ln-2y_<R3Jk0?BEv&k`MedCEZ;-e+bq;!gHkSlB<4d$H<;@mZonta zQA(EOF8pEwBX))-%a{xrJ<Or$(@>}A0+k!322s#G!IknOC!sK?ZMn8;0;rTyUqsU* zPxxR?-YFk52ByKxtw;gzz&*M&2oU{0ijbo6)o044>iQY($x>Ydn?VQhlf)yWKVEIv zS-arPF48`2W3qmbet|Vb{orc=u17Y_Al5G0tc>p@xpje~rhr3G{f5_>k`9%iC$1Z| zyJBr)<J!>Sm+jTJQqUHRFeL{P75MD-qkcyYs?C&2$<TTpi{{I>0xF3gXy^bQy*Bh{ zg<2_=kF8gA{mbmb;D2xehV@1#tfS5Oc`qN-pZ`R5`NOj67UaQy`<*4enWTT!mh5^| zinM}F6_}|MLHO>*dNq_|FtAW;AbAqT9>HOng8EQ;0~?mR1I?n^Btamt$o9%Mgv8xy ziCb?l51}SWQHx8rS66&h51gZ~uq{-Y=}W35DI3#)Ag=@0=p?(R=wHoZQ>OI}b*CB6 zh)GjChl=#_cWE4fVn^05`Ka!v2+*sUM&Nl<1wlVoW>h^d(Tuns(6?wMu_q_eVtTcg zQDTckLr?(XRP8$Vqyd(VvFjV8xTE<H99Riia{cRace;k_<F*b%Dy7ctq);X48j@St zL|9(+rL%mtvo)H|$;qF)P&^xFT4OF!dS&!t0jgqNGec+7oR0>eEzjge!@}C!>L6C! zLgda?{Sa^+CF$;X3GwVh+yWHLQz-BxJ}_t>_<{fLP-DRkG4A-UJ+K4r|A#;Oe@W;n z)t&z&p<i_EXLSmt5NB0^ag%A*<nS&34SS<+@);9z9FxQWiMKFEKHBc!h=0{5;$xMA zUx1GtY`N~Zaz(VuuM6ZIs&KJLVO9Uptg@&L5g8Rl@cG=5hDyn=CD(!u=<%%&4pwY6 zuat{dhVG%Dd`rHf%)s(2Z8cBP)%o(dyYtA}srqT}ZiiOfN^}{<=_>qg=3(-xQ$s;z z1Y73Rc{l>P4bMOQ$JHbyR;^#b1cHT>V=0ndSj*ZO7y?YS^?r16qk+-^lY*gqcggDe zlS!R92K{B$Y2W^DfnK<sUIpCuT2YQ*(g^a8+{Z4z^}u)hQmd4BEye2S?mV#m>4xXu zHpdkIBD>v+TF%(5!)ZVQS1oAhy8wkHvlUl;;$6;;DUBQVWx}I_hlON2%s4z@!`Kr6 zsEiZ)7@(SF^Okz)-~UuEy`Xz1UwFzty6YT0AP%@a@to6U_1J6EK$8{78hX*`f|-7+ zE^N2sc5FOPuT~q_kO8miEsnNdMj=)^iIewkOfoC>_dzt{`}M{s?|Oku$z%UUO!%e; zIghd#Nzr*TC)W!L6Sf)+Ei%~k-L3b}%1h=_21(CXL@I(UJx}M4L9rAYAv1sesW`^~ z7yrOt3t6`b+2G5os{v}tyVI=W7@VG_B5JQU>90fON4-3LCq(X=hTbNBRRv-Xv_(au z>>$&ywZF;ES<RN(>kJPZf&h4cypv8{=}hcO@b#9p9lMl_!-13&v4bvtC@GP^LT^D( zoCK3aV6s@$K=bG^dHaBRLT-;H<(YxpH5nL}n5oX+2KhF~Rm5{<3dZGGYMFWDW+fni zla_UgC33&?ffEfV^FV}VL4iL3emOQkx{kAY99^*?Cs;7Cyc3PfnBE3RZj0A;hZ|mb z&Gdi`YB(DUN|i8SXG<w{-5o7=!89J67^OGNFX;POXS^i!JVmrqI6d~`QM^M}JeNLZ zk6r^KvRfUEVYy?;&qpkeG~WY$jO%eYRP}rJJGGpH<|`VJV%w%}$v4eEEK2)N2qdZ1 zo<ROEH1Xb&H<=Iofs{tZrjKK7d5qjdPr-o-;NAtsooNA&l#8C1>|e`4`WD3ryISgJ zNSamVL^@==^^)PMA@210yQeM57aj#U!sA`>OaN#il4RYetI0r_@T6=n1GfDh7+c_S zF<rl(Ls^v+UDJ)a$)D)Ny{Z{}f@j;q8E2PnIU5Ewvk^~62;jXM9N9)>(ms+~<G29y z$owS4_?5<r-tkn8j0MbT<1I23bz@#B8?Gz6iPr*u?KFNjxcH9)@7#&CBntV=^gwpX zjw?EacfYLzQ*%ADr>*3sOLJ=b(&2(~aInsud!{uaZaY6urbNPHQF(!3Rq!X?gfz4| zC+}P+JabH)Ik^5jT%>LVliy4m>Uy&7XN||13$PEp1#hIG0dko<GYgjSk?D*q<TqiR zc_ttd%y{Y3MkOdL`z<@K%sBeS_$HKVKq5&NM4n2Mz*2D1#Nbk_IWT%V86&DmTC35Q zk#OG&GVD!peOlmjg#Hx1-3M%07nUsuSTgkU_caI~aULW6<WME?g!$WZ;=v5^7MUrX zd)Z3dHwfwp^vB~l(<(-;N3$TPu`XsuwaMs?_P-}UdFN+2(cd(;$?yF?6{`Pf1#$fC z1(Bp2pHZHb6qlBynj#OQr74>fr&bxAq#mWEkhimUc7%C?41<RYd$fl=mH?)t9uu3A z4l08Hv~O)~&80xKA|S*p<zQ|D`0v+K8Vw(&-~s@kMgjof{LfIigTA4$fxe;T?-yKg zYsYMk+TVOop3ViZwa;a$q=XFYl4&M8xe7SN<B#!xUw{r_JYU|s1Y;*)Urj>-WwP0_ zAP|QJ42I&}-|eRG)n);_0p05&X?AlR(YBVqcdSjNEv3Xg%ylo-@|IWp-z#FsL3znq zDRC-%YQ5`U4^4RdRQ(!#0;DpLFLQ>8t&Zvh+043C^Jsc7bKIz+c!8Ntt?)E78g<1+ zxkjmha08i1q-nD=i{XNm3n8Oy!;tv8jPSKdQJ$~D(FLd{h<L$iTxD=gxs^QbSpS_4 z$jp-iV?G{h{SaXW4Z3fK`lQD91<P*n!VOB4SWk;ZMENAz=M}-nc6t&c$&OH-=tWYc zxY}HeZIYaQBkSgYT2=TGn|J?sXZc9Q)LeX$HKR@~qe_JjPL=vXVPf($<!g;vEyJ%n z7{J=OUqb!ow`G1&{xQVLQDf<itR1>(@5Y1O_zB`2$lHqY^{KYXU#O$sdvJQ4%<^<c zG_}rgh&pJ+x=wDCsAIEg9lpXj4LUu___*A}x&txnEWccZ0o?a@Q(i0$2=JJNbX!8k zU4eFCyk>?uS20eNVbN!$)7FP9dJJtEFf8E-&_$niT+psj^D3MD<i~swq!&k&&CY}g z7EK#jhYXqDYj5w=>G$JDZ5#)F8Jgu?xgZ)_UmgV_@0^q*75Bu`9U5t(Ql+G8DL`Mu zl(u8TM2)*2F}a#(?NaP;YEAszrOOODFM?tnrwY>@H#TBb#>d;A($)2Qp4R1lvk)Tx zFW;zB!-$&hi$NmZ%uXZmMjPkyYOBrl;5{qkjC7?ZVY|)wr9c!<aPN!K<bFUD-e%_u zB7Uc*zNICl!96iQRh)gCe7q(Y-g3djnbW=&SC|is)!r6eyStdUA8_hA0EjgC)6tEM zAT^+GH%6}8epg=Q7=l&<l200hG1}<VEhZxGy>yRRI<mC7l2K(XaE%%`6X4#j>7t!! zS4^tF0(Z#OW~Yle+-9@mp-T4pdIC>1#4wCD#Gj3yCI|b24J0&hyq%(<S^#a}EtGvI zb7g=aUjZ`+R=zK|Tu+X?*({X?o(N~IG6-^_gjz{@0xI-E=B#ykoCpnib<yTgrjZxh z!VHML+^}T5l^+<)CFP|m-^%}M!o|;S=sFEd@0m{P&-zg&l%+NF1fuDTq?Qas7?V8o zjDIXk2py7Zg~Dk10-<e8l)z{L6Kk~?9lIoO(6(<*ivOCrsaLR6Y#G4?ceY@|QYl3I zm^rRW%46fSN`e^_G{>QLD*uhUiKAB5zsPL<aN3NMLdndmN-hBy_k*mlZStMj`Xln0 zB0uHtRsg6&OB4sF2+^l>XK3d9*mIiX<_}H2uzKm3Wx4J<Kg(rGwS3G`1%-fQ-@?t& zSyebhE3U(&TG0ng6*FNUOdcd21!;D+UJLM@Nlcdut6_+N0ePdY7^dT77AFW${+qe{ zbC#19vWRm77b`QZ5&TxPO0@eUTr#$JRtz#k^|m=<EIn=jdgEFW;CZT$muEI4%0P`K zIY2>kG1xq{84albG%g3Z>VEr>6H)oU@xjP#mm=1vS<Co`{{En#_~zJKMh`H_8)niT zWVtGW%*tW!z&H#0NhTq4B-3yeU9#?gv0@DeFz@gP83f~M!Z1r_0b+fpQBgc&kgF?$ zuw;J`*V?jPc377oxI+*HEctta(hz4r>_X#V&$wYjX=m%0kEr!tg62si1g!Cy!Y=@l zWAF|h-9OnlxJ7XBtJP5(kR|y{fHc4sN`QjL^_1+*{T9V2K!!ub%aDiIfUmlf8-Wbs zAR=bb064lzcXbK<qi`XKVb^2T^>^Lp02Hv`(85Jz8Tc?9-t}EUbX*I0#-_uMJTSt% zFsV}I{8%pGieW|pn9^k|pFzYxxC6$S!$|Yds4_Sf7V&TE*cT&E|5SA7{gr@<T=&qP z#`elZ#YaXO(qFe(2o8Ln{WKg!{VV^K^T$+S`EUtSG=SpYy9Ot9p3-y6Qf5yT7{|&& zGUNO*1L84}4i(an-F5*wpZ%Eq=|zl|dA{1Uups4>T#cDL(fB?(O_@{xQS<n^p#?DU znE@=@D(78nH~F6uNQ-;1T2kRA8yI`4W#rUhjOzoeRjb~?v^p(5A?C!uXe;t0Nj^eQ zm(51Ldyc+sc=<L(&8A>RObbm0d)nIK1A~fM5iyTKKE>-s9-8#Wh9`>vPa1AaG`_ia zIcPnh0?pX_5NukdCpPQoigO{NK$a*cq&GA-<zw+?HQ=nPCmq39?$kgf0G(tds6kHy zDr_K)@+ChZO~v6+$TA8PO~N1qJazHFlB~kr^|8kFzmF@^vy>+JzU#t~OEmO8oLBqd z@w2)m#%*52=PBB~Y9L4lE!w3-QwcH>wLpJz#7T!V`w(KpS;@7681?@3lU^6YUxg<R zN>s9jun1fe--l#XIj;%*ED8r}Co3mJKaQ>r#0)=@t7It2<~cQ&raMpV13lyA?pZuX z=R@Pi)E5}D^eNk`a3d<lrDqqLrGT*bzQaJXP}3L)k1Z)CYOXbEv<!lIC;$NdffAZN za<KR#4$xDV1+puPuIay<(V!urBd@ujHv=M-Mcl0Tc2@+0YFw`)l0uV}S*|)0ISdX$ zc_v!QJGRWx>fgDdO$rKR?Wlr5o_nOI@^{4z1ojHD<a8Xdk)aFa;A>tR(*=^ii8=rc zO)Y^Azv=CHV9k0RWuX(Dc+0xZT+PaCkso|k=m6|eH6NMIZatqmj8V#CP{ZgPC4_)Q z%x?yC)&W4H!b!%00SFhBlUfa{VjUxRmjeH}zV)2k;4VpW&&lqoHUJRVfr`JkpR3%G z#j;Ly$!oiI8F6z$*?&>OsAXif$qis#AF`i1>5V-6sV_8Ude)c#ZZ#;60n7#TG_0Bk z?Wp={2&7{s3BsSk`8#}JFP)lS5C6kImHY|!H)tiuzTcjggUObopmg54CV`6SGFKoI zST_RR_<lLKK%2jS?w4X_=)5LXj_Ki4(`zErb2XsJc?IMD7snJjdC4gjxk{BU$~ggt zerq1IY(Y4hZZL{v!eO1d)c~AC*}43b(-ea&nQzqu7E5Rr$&p1H4`&YyxaC#Z+n{k7 z7dUJ8r<XG}#N+Qc8ze=INvYBFDF88P658UXLAQTC%<->VuS6aqorK-0K7#aekDk%U z)6`*s=}C+bxPt0WhqRK5e%F}FYQgm7P3Z0*(dE{>SNc3x$bEzr`&7dyVrD{?aS;KD zc*!zdq_76rQO39PBM<}4GyqrEE-)RTVXqbJ#d!VeCQ7DAP3|AEd!ZyZu>q)kNDh0Y zeh()<Q|%OeK!CUeJWNzz%bw$%bQdzG$Xv<mvoQqnEq8;*d?2`TS!h1&xRi;%0Oo59 z3JAAlXl?%g*lO)G`bT6$7TjARdzZl4T3TLUolV+BeGA~_&+WX9_;*2AcgMwaRg72* zUu<Z)0HF+m;-A%;1a{|0cbM)6n(nk?r$ZddcAe|Nf}v-MmKh1N>$+pH>tsdvO4%SW zGfRBQd7avVV5U39K#Q&udp7h5sWOPNAR|aIRqlvY*K5wmIFd2Pumky&ZR0@`(cclg zR`;hR>gC7PmH&;P)UhXs?~jI=RPs|a<e<6h3rsWUacf%dasM&V@6Sfp$NHw7Gxi70 zlJc;V!)ya#$1gItMM^9T&y0sfQWhYF3fLwH-<nxBJYZvVf#(`RDvz9m)pL-8->=#E z!KY1L8BW>8*}oy5tg<|C{Vl4gmkExe`Dm*LTRKNhT%mq;@oy9wLey0)Yaxe1HX0wb z;c^!bj>t2A!7LRkYad-;hzZZ*<1S1)FY`1{aXGExM;4j&ySy|qVLjz$4n$AI{EU^B zUQVnEMNf6}Xg1Hmq&K+OTAF%3KXK6p@=q{W5FzkbY)ozt-@t^v=xJBXy|&a>Z3NaC zZ+!pQ1o}W1kJVgm_RzXqw^#-yS6K`o!RFE16(MmG;z?tVv@VfJ)ANK^i}7gOr{m_8 zz$`u<Ojq4(dic>$$f9Bf^bup`W=!yHw16EffhDwASej-Yr7}JoT1^YEn}UX@G%&{9 zNns5r%#MY#NH-<#)DCUdL3K0(d~jjqND_HMp52l3n*NCa^6J+C4cuUv0TL*g>F|5s zxY6I0`pD{p>C88XDDn4=_S^*iL}m(nXE6oN@10=7-bJ?K=%pGEkPjU&?XefI7$B+w zjUjY&q<rR|uQy(%J}z(P822YH14s)CmM1VGDr4O2@y4=@|A*r$vCEhX#PmXoG#imi z>t(CG*g8HlHcbNKgHP8Q4(b$w$cf>!%dUc)uOPr^M}^Ws1NQ}j6$FP%#U^Ok9AiB2 zSd%rx+!Q!t!8tK|h-4_T5hR5}MSs!|0B9}|cAF*Ws4XE*KLelvUtdo?&s8A{2KTHH zCh=(15|KJChxAq|Fl5z5Qb!u2-B^)BtWA$tNvQO%kn(S*@}w~DomiM%(Xgs`CIttv zf&d@&fIv2`*#jg)7gZ{ddALh}R`q#%12U=LoS19cY8i*X(x~CM(2ea8-vtj`q`gG; zlzI39W`-iC!2?Eg{@ZSu;*;f5gIQa!_bkezP@a$-=y5)X2mQDPdh%Jj@zz*GJ6LRq z#qg_T<{#$uKbtZXAXjbIFE_~eI0&BEg!N1H<teX3$&hP}d9I&~jT?=_?vcU63@1lc zDs)Z-AjV#w;6#Fp3xMtNhnQpn{XjB$-TWPge}RRGRV_qLLEKETLoEg%a10AN(z4z> z;A-#QBy@jA-4iO;C>EIu#%UYm`A1ZG9;5m(?w&a*s*+42Uz0T_iKS@Wh+shHMdAy= zVGO5p8waxHQ>BGcZ*@srRFyg3x2w~9UVM=KSoP1bHUDd;uqOiPaYh{^VxMk?cIfEO zv6UcG*mkaWDP!DDWE4csPE{5}PHfd^-?jBdMC9>K_|&NNP1HtIXjsyf*@na|hqdVM zLp~6Ba9YH@9{GiwwqlPIwvI^=BvZTB`+TfuEYDdvmNlzAbCcg#b1fdubn{#s);|>7 zhn*9oM46@aedM5>n$FA>zbB41TN^lcQ}iptC~8iZ(YJY=j}3OZ$q&CjhF#=zuW`iR z3(cSqvdY|pt41GUo8bDcsBx|+BZ(}%_#U_Sfh{!tHeH;)PBbUDEwmr2O6=ZbAhiY< z6ebc@A=rn6N;gHDsEbEyoSLdamJrWQ<5KeYSXoMFEIVt<SRt1=4a#5BuDhKF)8u|c z?qhm-d?b9l;BraWdU+_W+`jH&*P%(r*Xe)Y6_TS$E%b%zlJVoR(`ng_QI5pvY4;gx z+vRoFvyfQkh=Q!EnsR}9X&}%TOS71=!S5$ZLS6a8h2hYiXDGYZp;l*$90-l0^c9jv z6X?87MAs)rX_Hzw@m)@;ob8v?p|?9Q?4F+Rp?l>@^FdgnPcb?{cAMD2(Ba%NP9tN% z*dyV(H{y^=`U8)>2_Z@qN9uR-JBulvya!bZTj{G75X;(+Rfc`eQ43Ti&FXS4*fW5$ z^d+>D(plc^gKC4ff?+>xldbQ(d{APM0)ugGNFv7sAg<k9J9|eI8lDA{EPL6GvGbBB zEptk@*3pZ1DudpuSkHh!FAssQWaq=|2Fw20Wt(vHt}{lj95dHjK5+Tg@p@FQ+N~@2 zz$e9gb;20&R#V;C)Id?w-nkQRQQb7x-S?dC0zCtC$krZaq2s*f@`@29A=Pd^4?#9* zxW0UuF&WTZVl-!ONbjzuGnd9w<;8Q@*%q2nx2Fd#*S&KU^Z3DZHbTOo@%7%V8R_~L zrNNa~@|Q|4u>^r-RUp^j*wH94nANu~7iD+^1G+kY)Lqv0ne84Mw<O5MbYDa%v4wx{ zUzF)x=1+S7|D65d^h}ujz|?MIJ1#DVWC$s7wDK=hZ8%%JG^!BT4>N>K51(Q&U3Qg3 z!&rFB=@Fnm8Bc5kHwwMnvzqlihz-6!|6lCeC*|S5QlJc4HILsp8Ile~k+2wTp~1D{ z;hYSd-!O_@3I7*6n6(`OA9l`m(ujHCnfYPS#_r-mR|`h8z#BfVz`=yEifyGH4`Fyl zx78KDfb_*67*qB_Qr%tUeBLZalJ3CYpej1)8t;(T6NTK;Xt0cY|EtLbORJvH?O#}6 zJ8EDgEU4W7q3fN3L<_by+p=xjwr$(CZQHhO?cy%mwr$(4y8HC)hkO3+`;c!dGGb<A z#LCET<d}C<8XAy$4KE+5AqdZ^Jk=i23v=gNoYBSx<qoL73n%ih?kOiA>uQ6vfhyZd zLBjin1t0rQshI!TV7jviEBmJFJ$o&WiHlHi>w`AUo?4lXQt>{FGZTMma<YZ33;95U zk6N9<PI+IzbRtC(B|=7qq4e=hf=CnjBP5L(Ehqm7S}<7|K^BEeg%PB>@>fSOvC*Is zt5$H^M|r9v-6QLEL}zc1<6Ab<Xu0OD#;0hhbujgwcq|-~{VGs_-tk!F!~8u_ABWHq z7&9~jVA;(L!Mw4gm+l|nOiaHD@M_btNgRChfIIb>t+w@d<BxO9zheW=uWd1@C;`L+ z_O*h9mf<(3gpXoqW?((JOd=7Qf+ZUx=1PV$6OqHXYvL;w9K&Dr87n|Tvj(hn645#! zdvk-ei&|qukkt1S!vE96waZlO+*1ES0G>QJ(}E;!f&Li9((wUXl2Q#@(lg@-Fw!XW zup+fgZn1MyQJfi~jxF3d#%zg2Goz`M@|xCxpvi{Ns3V`*wm&)|ZoHYc>_Gp*lVx}3 z!w&Fp;M8pqt*SeXp=;7h@kP)zOgjoNPY+D|TU7jKPY_#2BX6?G%Q(j<1bs`|djmQS zw;|3T{0ET$Pf@S6igLVXyZsRja}Ra=_7;F|$94mh0Kd10|DaX_bVR7HV7pK0GR@pn zoMwW0rN9;sm=q^<_*7wm^SRgod1vysDx=Fo;6>c7$^z*}{-4!ySFS+C&pDDLF5+vk zy}u+le*$6Z;Hxr|!!e^0FOy_SVsd0#$B|u7UqwNa1B4<+p#AVZ4p}dYj-f4c<QNso z=DJdd5~Vg<iX)^5Fnvv7%Q*W_XVzeVHB@lqx>1dgy*FBNAY4yu>q1VHHue4TPUQ%2 z$m>cnbe%G?a9lh;if1=K+iI$IE|TxFRZG>dD|~regg0NvHnHfRxJRRL;L*@NfqeHH z2Gxv(<=454im>|&Pyb2!MN6J2(Dw#6L<GZ~Q50?-V4x?UjA9i}O$btmv-Aq-`QgHM zuG^<fWFMbo+9=Yjg@NVu*Oh<{W!;fRcy-|K#lfuZ<IxIswDp0}g{th$wyVrbCD*Bo zB3T@CRAIHO!X0GxvQA8l3+xgVOeRRvs+dDL@uVmBfmw1-6Wbf2MPe$*(e~0s(3qdf zxD<x-kv{Mx$`zkIC<Lg3ztPqL>~_&^{Ym|SFGnRQ!q-hs089Ka>8iq$m><N4uzVK} z4E4e#{W3a;xAc>mY3UMRSg`#QKT=mVpj<LW-n5ysRFlqI*2C&z^F8I4x}>Y5T@VS9 z`cM(uQ;Ckhb<j~dtR{23Du0G;dA&0)SPTC70OON8!-d1bRzbh992Sls>}I{*l;+nD z<bi+NHcM&IN*?PoeLmKwt$6#qAJ!*=AJ#{rPX=+-S)HFV2EAy?w5!}X)8HBCnvA>j z+d$$T<Bek2L5~T!Dj#Rb5U)UBq$Xkn0fdDiyu)zh-&<zXKLP^8i0d!Ew%w{`n@rL- z<M^(4Bf{oz+~VWz0#fKokU(zunzfsPgF9dGwz*s^f)E1m)OH!CZM9=lp#qnh6@k1` zMD^|VdTroQ=1vq~4YN8PV)&qPf8TIA$FyU%OmJpd_}RXqF~8W|C6n~03vNw0BI1Tm zXl&SCJZl{~lbQ{%VdG$AfWlb$O!A0z|DA<I*ym|`mad1;3P)UH-uL#syfou4Ld%_o z@@*6U5@yOif1-l==t(Ml(AfRRr)SvA6vsZpK*%z5ymn4qAQxC64EMWF1|J{{`kO1q zWL82(?DtT9*$5H}x61<an>obzwV$Cz$Te1d9d()pNa(gaM3<zC|DI&wxKV1gx5ciy zd57Tl6(|IXkyo$V=&sV-4(gE}4jn76Fl$<iodSB+6myM1UQTK3jMhR;Kl^y1_HPj- zj1N~YCNvMnshfA)J=vhQ*s7Uu^7u$y57>FI>t@W%$1bbkzWLBbg5k5!Qc$ge<<ds; z92l7s@MW(-_Y*&lb(60<g2v@}^)CROYAFCQbK%L<ESQu)ri$LmSBv`9u;RF0lK~<K z;jK?!H!kme9mi?MJjWUUgV|LUl$4sbwZ&VP%uMjCp7#Z?YJ&kc(9BY~-6FAblbIws za}Ll8F>)shys(9nQ1O}H15C4S+2SgODjql-_xf)8k*^ck2@Js$xfcO`-6mZ@c4292 z<$1jA`aeepyTj)aZZJh*F*h(e-(R+8q+sP^#@s6<;hHCJCY+nE4rjx+<#<e6DH<4m zSbXd~{1kFlzeC0}(Ij?bDQT`Jc<36`EY%$6-xfLRboh^ZbXh^&<^(3##g%6w+K38S zF{Z@9z`;;y_T-tqY5wb}%%wa=#IrRqk=EYwxVZ%O5x`E2^sCywCgSM|wdSbpMO_Rl zeb`~<v4y0Za~g_!Uu;r-BvQcRbC!^)XJi87{Ss-PYdGpT9vlg~mtbKz2r}>CsqY6$ zY+mcI#6X)K46Tn16G_3Of>|pjrm2yq$Q0gvD#|)Iam{ggX`O70#}hk)<tTM9Z*zv6 z9u>;*2|IdpRk|LR^Z7?!#7q0walM5$)aUxhy1<a(QuBGHIm;6c-v^ERk;P}=o4|&A zGA{DEz6nI>*l`SUeg$K04AbE+L&4L=p+{oCi19A&HJCXR$D-9QsDDD41M7a<KeS`% z(uvwGy1I{O!Z_ii5w#@Ui|rO<J#gK<)Omb10FuDt>$O&NHZVlbPuIj!ky)m(50iW8 z%8l_Bhuq3t_NIWG$C0N~bKWWZJSVS+g%Kbs8<WhpGpb8$#(U|C-)u$pR5yVLAT?M! ze>8K48N>_};^N)|hj+N4ejhG>TmW!X6pjW@u)VM5&1U=Wuun3~oEYvFf*V^Rp>n>t z@)4YiyQLytnGvg04n6*z%qp}xWoL^Kk=#~gf6c$n-lonCg$$}g_9y_*wmfDm)i-o2 zif6VodXmYv7R@YIZotKH3#88<tEFquoZfn|c`IiChY%S!{<G^>ms=!nt-GD@1AEQx zhqkeO(uvX)y==+tKlx$77PGC^xYStkB&@rZ^}P)U9v$fW^HLoHBbWX}TvepO;gUY; zT$}<3RZL4IA8R~SF<+C@aWD`A1@O2I=IXEu`Z{1=L%MQv0xp5VsEHa5`VBhG*q+0k z2q!dMq*S?95*D#V-tb-qQC?yaOq9-sg<M9Ye|kJq><Rw#Jx<9YXxxUv*6KqW&cwG& z$>vkPN*um0^ZlwMz&v2GRS-th3o0KX(;>&$7y%O9>Sa|=r!mt}NpWe!AJXxa$s}m0 zG5ko`AG^;ewvT>QuTUbwoK&ulH97F&0*%BqVAUFmlsXO!hFy03d))|6L8&n18d4vz zV7&F5L(=BnbB`?~<1aS^Zyp{Bm5;I9TMjbKJ597q!{B$ysCD_B?cHAZI;F!$vUQ#o zw6LZlpvx}%u0w#2<^p(x4<Ys`4Yt{3`o;vVQrSiu67No?Ai7^}W8sPN7y)asWLadH z7QS6OPIvQb#AC%nsaubnM%#mRM)#UywvVi1KNa2sE_R(H)zuBe7nT}E>?)uupj`61 zHsh<3{C9M4qOCQ#cQiV#-G%x?9r1hevva?$N+Eg2?unh2Y}<BAY70u9?XexfH+jD? zvx#%#E3f`6W7$w_aoKLR6W!k`te+igymh&wTW6YHex770rbt;QA(2?Vsj*an&~*jJ z-j?cW)rKzzz4V;}*5;18xyzed&wCP)xDJjdf9OSI{rJW&le@Xru5y{&cW}E)u$~t^ zcKX!7n?15+v(t6Fy!=I-`)W2Vj^VV&BO=~77&?uZoA%b&V0&$Iu!5FI24dz+FpI9K zNM=#+&DqAq*S{fRngHr3Y0TL(C(CTn0{zO;jAO<83p(q`&R^s?`u8In-T(V*b@umK z{r4vs{r4-HKJRxCo&WPEy4UCDK-~W;JOArE+W&nYeb)brfA=@}_xpTTIP>2Zo#?-J zkNV%2{68PFdB!*Je(wkA{6D?FU-QxUKg0GvhvmKR^SeTx62AxUv-)3qv#N)vkPE)H z#-+D?Mi-rmW<H;;da90SA7_=XP|?<EA2~6oE!EEgqs5>fUR(9I=9i)_-G4BtSo1u& z-d>0%qfHgX@y(wlRfC*PsGrX~c*~i}Gwmli+9DN;CeGaQaMk5{iZ5W9zS{6!O9XR_ z{qjY8E|(ldmesQy`edCRkPYA|6KB*#1QjiATmL)JP6ww)FM$F8xFq@i)1*0>{x7v6 z)}-Cm*gc>Ae4#VJ>XHC4{|Z(B7>@R3v%4)liR}vmCc{#_d6~LTAiOm2QNk)?ILJMb z00IHv1Nb|MzaSr>>hiV;`*>nXjqn(^iMy(6cXfyNs%c)MmwJTFporFy>9pQ*SIkA% z)B0*rmG4V!#lQYXk+U<|MEl9(SdL|eNvEMB{jUzRQX%E9Bvn?RZ4*sY@@-(gbo{>G zMC0mw+{c<Q)cPb}KI#tNz7y4C^Cdm@QMeA}lHF<f_Cw8MC?XTZRg{~<J7cpc=2;@c z>~E!jGe_llHSV~@=5PCx`Au9}x>fE+Ga>P`As$Sg>k%0R8U(5>6;^X)&-uqDOqzj7 zRBn1sH(5y@kC4HmjydKvHm(CJp5sJnz$^#s)mTSaau83Ny6Fc+m7b)EmRDEr)$03` z_WIo%oW^#ON$loNGf0W|QLL!-{>{gkEh8*dYC<8NX;&EnCs-OHi#+_ioR*SB&NNyb zeAwQ)15WgBTtLu`vaK~{om&_%<ny5N8P?u_d-?Y5m0)u~5%L=qhG9>F6}Z_8QFhie z`NtEXfjF^^KCo*9_(k%p(RZ25;NdxO7!9~PsU)pKI7oR1A%eO3=!g+$%&$bV$k7)~ zB@>!7RYZ|)HqEG#Fk1DJ8!PKk(`<qM&kzmto1DU_wHn2AyxC{yib|R)f?rItqm@Aw z6fD!`o>Q+$K=x{4?B7fVwZct&&VHqSxA-`7xAga`*-zhe2GMD)aPNoVyDj$qpKtrC zcjY~NXY{l9|8zB<FSGIZe!%`0m-HXjIJe>a&Gx@<Z-e;z^1k1y{*+y%DBLI3XkkRi zt{5G;<qsy;n}`gzr~Pgp%)2jlp6PJ{)C>0CjJ>#kDSl`0Uk)>KV;QY|&0mGq&5tHp z{%54d0C?OU6FVoE`PxD9rbFOLcHUAW+617@<x>GLGK{tixQxzKM$l7@(k^+Wuq$8& za~FMJXGK(JOLO4!gh(z?XH$5?_vB4&wZqXXMHDC(Zt=&rK@RSRtxE_FoJOYL&sQ#P zBSdJU3aw)}OxtoDSf;1}YJ~YE5PzjURYD8OH-3c~8Eo$<p@K|#PBun96ORjZk5Yt* zlb)v{^Z;O*V)Q8|4<q1n!!&F)p}zD5gcnl^ijzt`X;y+pl^3ly+-s<glIBQZb%*gc zxEYWjBn9YE-+2!Ex^Ny|G1)LoFbj-)v;M|F`Y$B}`rVISBRw_3(3tsTNH-7SlG^i} z$`%&^aCcO6!j+InCkjnxsRT(1Ef>REAuA(0pHzU&&&Mgd8drt7$M6`i-HP#5)W@q) zYTh!VHHxSG$a#9LPcDJlumDAJhrZf%RkmD*q_jU``=J{7^mkp{Q*P<hId8+(ZE_VI z<+K>%7LrM7F+|-^!nN;c3=Bt=P`3GsKPVnzNCX~wWDIgG(Ee$%&*S9}?4IvzJ+0gE zgSOZ2;{Kj6C%4xJIA@w*+xo}FB2cY!QFd~&*kkEFBxpE*VjOwRh^T$4{R+G&jk%!+ zWAp&}jO}=#wu4K)c}px1Q_jR_`}C!!)?sWydBggoWJ>6odH;KFnC_+_NCHDCK?cNT zP3-5DD7;eG<`~~G57v#e%AMpKz7xBJ5qo+theE!eZP5%}7clr$#iZ2l6l00%xuHQv zR~Tckmb9c*xu;No0uhGD-OKPbp{<3u4+TNfx*ggHjbeK4S$66^MkpHZX1j6P@u|DU z%rt}B@aa*HJ-bWoNupDFugjuE;0?8sDHZ2YW{%DZ_G8Enl1jQy)%|1z1QS{nCGg;2 z8@+m*AuQ$*SLndxr`w5(ik+)p7l6LUM{mLfe2Y;io|X%zJ__C?rRm48%#7MH0n*Nq zL8}^yJxeq=j0k!MtDY1&{P)`SGTasn4-q(jvyfnVg~-}QZrt+K5@Q+oWUYLuP$z-v zTl>kRZ9!pJI8a?y)99ts{>W|P>P9uM*|$A*0>V%6p}Fnm>W@-J_j*AI^W$1qzKshw z`UG=cZQD2{=M@|ZReT+Zd}F&3<ph4f1<M+Wqsk*8rBAJhjy*7Jrr0WBY|H>&eU+px zl3T}tL0O17HiTmhd*eog45euZw$ID6vUdSz;KnAwcTA0fh$Hp*tE_4?Yi^vWK`3Et zo_pXv!i=gUbR$}@U%k1E{n;)>C!gYQ5(w6roaS~<syIZ&aOuWK`TaU1Tx!w`vuogh z9@RP}{&uuP6yd?4)JlBSC<1UcQ+N&E3N8HENorK0L5begDDJFuSVTn)wt>rPn|x-8 zPvfqwRqEs5lS}d1()W1^IdE6?8}2aosb6JMA-7OSE4D03nAiq#GI#mLJga@mes7IF z@pdLnsZ}uZM1U@|iSM+Iv<Up616gLLFfLA<Mk?Ios)Zc>L9EIm;?;eaG`}%#9_=<n z(iGpT;6?wAyl$pXM|ZyCxzpuJe2e$mQIbHF>bE0zuZjtgt<nWNcYYgQTxE4pUl-{B zY{7P-Hi!47j0t~<a|O41CREnPB-*#X9sN4sMhky)@9stu5rsT=NW2y_H*F=mdnx^$ zldm&5d{e#Y*|S}OfrUL4=T{8xRb6Qe5On*<#2m4tivHkJhRq+z2(Wapq7Y1$<e}=+ zFAEd@hs%L!Ae=}>%46R}IIB~0j*My&CXO5#5HMr3QiPC<%p3+8Vg>QDDa=mrNdAqI zd7MU;wMSn1NG{bTA(X{CUJidfImWb4_doQ7duZ$d<%*t=*lRW2bgH}T1px>3xXE|B zd!w*@%XHPnmoI)PJ$3+f=Ua+``G?^$YEC=}!giVZm3#ym93JOL(My_MU}r2*hYqRa zvRUmjl=1?fJZ>u7*c{cmDV%M+&~+0P5O;I``uzCNSxI?AF~_nQ42-RV|1^DNb!*oR zLpiDxV{nkNW#cBowke*#yn#08oPz2%6bs}3Ovfp*tSP2nbyn0IA-+{soY}lzIk}^f zrgw>C<m2&kJ(xEM|K8v42QU|XkZm*+i;Fx%Do!Wk#G7khWEf81B6>S~_2u(sy~E@A z@yWqx=13alYtu9)8%_A}WlxjT=^MZMPa6xG1nvbB|IP;+|BfdgVngtg4a6g!vh1?t zbWgtVsgX{H(@sZDdHl6@$2mp62-3~(=n8#{I<HY=on$rH;nXD|;DL?Z$vVSE4njc1 zOItC`TT|CX_I)e$;ZwM|ArkJhZK!VL?9lBF<pHQoF@HA=ndtXpp2~U(N>6nfiJWqx z;s|D=>x7bh*zR<Pml8<{l2~NP<1y7A*r9w~clXR3;?Lv>MT{LEfasv{)NVvwFSB}A zt*-o}<}!Ex`F`d9il(K*Xuo(sfxkXC8fAacbdg-zcF)YIPDaKNd+eC;fi-2;IfcB^ zoVu)?XyBrUK#fp7$4M1vc<P6JW+yH|bD3%tZm3I}z2HHu<{K|F<T~(ji6khMNO_y> zRYx0?C`G?@gZZP^SEzGlRs6n(Lu%J@UGS<!9}3!cW6GLP&wQUzK?O{k-wN{akY%tj zKrEfbo^QbwlsL|t&?vK7<d(Y%Yv*wkvzT7Rte)?suPB1(Nh;*M#Lx$Xz67LvGkVvr z51c=tTk!jex|)}PRvIu?j{9=N|KBHD2OFfP%YTd}7vTTNYP#6l+E~)->s#7cy6Efw zV>NZ-1+9Yx5JGQ%qQdpGBuefv&IF4Hp{v2R1e}qO$;XklQypoqd+W$JpP85$ZnIy> z1lr|$(v<SYVg91PVwTfqdBn<4(0kOjc9h0m`)lgk%gx7Y6MsagDKr<V8ME))ZX{$L z)d|Dq4EJmqvA{A~+0;1J$QL)oyiJH~o1l2<{bM+_Ro{lPcQ-mPzd-Q@w^S3yLuOhE zQ`Q@Q`Nn%A!EL93?~56r)cFtyC=<}3EQwH-<Wdrd<P(SA3DX`hgvy}G8!n5{<Aafu zDh^@f{Jqdya0jaDJTo3HYFn@FX8i*>*P7sV{)c2eum!jp?jK3}Y778?^FN5z|Gi2M zhEC3=PX88Zn%7$Sa3pQ_9ToS!-_3}nZ|bVsk+zbkkE9kr7}g|NH*8_d37dK>iFh0s zZqk3(S&n~+w%KM-M(MIDn^Eoc{L#;~`j)M;i{r=llqJqX>n?Xcqui7y(v#1?e6aeD z?fsi>&Rwxh&(z_jrROmI1mNcG_O7wI;Y4!AcUN0yv~u2n1Ej^&M2xOZk8!lilrvgm zF%9jb$6t!39Ne9goLK`xe{1U`b7nM<WYP99PlS38w%LcIP|K`TLril^!ch^;q`L^e z`$5GLFgWSF@Zu2r@m^>R((Yw{T4=^jof*%<lbp~;PMD6drto7_o;l$?aoAVieB820 zK5z7L!v*ux{h0VVv`4GbPX>9zpn+L<r&==iRtz7$9FWm)b#nE=rsSS~G0pe<e&zGz zd!0Fdo)3cm%m7msm?>J!U{d;h%&MPUBZUl3Z+Y~3#Oe2+JfUHtS9CZNoe<CdT~bYr zfmfJ>9kW3ez`_Qkb@eW&M;;wC8Wa^05`2WiB7w3a9<3wLyB%<WV;1SgO$-IeGyt+= zP&gd(d7x7i8Ypy7bLRx|PK|**6jwrc<G1_ZAm^mh0*LKTOB`8Eo%+|_rw;(=r{L+d zN7Es(z89bIoAMlT(9Zbe5K&JJ8Not;%qwO|-|>zDGLCigaK)5kW096AfTRE*3DlaL z5u|~LCdn^=v5|m9lplgW*nM}FnlsV;)#@zMvz{DbryQH{P3k<psR05}56c{RG9ELF z5$J%8Tp7rLKYw#bH$^eU+_@t*NXBoV13rlJP5MBAHYgozOIYPTQH2A2eRK%I0oqCO zhQ}u7l?ZZ&igDWZfGdhE(-JB80_8qiyOt0vlv-aSLqRQugn6{53?ile3N@0D{f)6r zyPL&f!3rBSuz)<6FpDV10kRISr0nGz2h9V7hOXC{Lt=w;21(rxc7=XMUKB8hG9nzr zKuIJywa>-<!GoEV4?it<CQnx%e9Q+M=vo7`F0`_~ynC?VfQhT+%TWsseD*I)r|g_( zxgFsJvoT|oVufGS-tq0;y=?gX1G~BTt6mrG<v(MLU^mkUNd;&@8Y56CGHx`ro)3NQ z5s>+g3*1MHV4X++dSx|{Zb1Y9dIus(AqXCL<p`t%9ERqDcTzxQ01S#%pz!Z*MGtNx zP3pIqm4}!2{rryQ?f%Zm14T#A7b4D&@5@0`QA2Us4Th^LzI1i5Y1R)^UFgaOFejc; zV4*<n-yX!@(+7`0)4|oS0VqK|hDt4dH8&?eRnIPJaDD!{VtKn>D8<Di_8Uj+-+yPM z*s*W(Vb$XMyEIIE^5p?>^PZmkTWhQoi`+i~A59S#9FmR@4fT>6BlyG76D$3-xd{n? zBZQR)Ul%4(3_Gt^85Xq~IL@E{<;U}7xwa<-oKESFa2e!4t(L2UhpU$Zf-grW2Olq& z&<z&j#ep(^nfMcpe|tEZ$R6O2*4f$l`|=TE3!R-2j>s;a>b3N0gz|>}=vkGIA9Z?G zLbmriC5X&>CIJJqnb+`9p%c%AR!nr~<mfTk-xMS-|GYLMZcu>J*-JlC6sa9Ch{1Vu zohXRE!2|0ZG}h6K+P@3R&DZ|s-~{6M&B4Lv=??M1Q^1&as%vxu0-5-95-r3L%9tM# z4WeV~H#q{Gg7n1#E-!8uMmFn-XKwr?Q4u$aus4z;L?Q+&k>(Zw2_O)nfseKbv25n@ zOD%(TU@dR#mcb;4m_EQT)C<|>fZD)FW{B~PLw29hC#V;O3_psI$slKjL5gr+kD`fF zo}fxgs+NV?3jCCaP=aWP0b?B*YTiX5z{3KAhBoN%2PzhE6m?7!N^`bJC?=XP(nsK? zPXp7?0Rt{SBq1wmze3iM%uL}eIE3JIm`B=FD*(Wf<S5CCz(;p#;iYiMQYcHFKt6V{ z4JN2oEiZ^Xx#uVP3|trldIaX`-*Yy01N&)xYKWmM`XB+BV3^xugdR{{V2q6K1$(8~ zJ76#6`8a$Wt8aiJG2JLZjs$qYq9xF>r^J$-EJuM4vBQ8qJ_`}eSZ0Kwc&RDhFQZ~< zB;QpQ3Mm4sFp<i7CKbMy%vBKA#5)BNgc@KMH;j&by?oaIjCb%9CgT;rl70K34GomW zoYLE8Y=d~}+q>+7QqHg>*@+{#m+Nx2Ujd`~7N&c)Q&T^Mk^;h^6MO83*ib}1e7wk2 zNO+7_AdeC?S&vldhvw#_#Ha=~XOo(_UPySNgy&+L#3+@5S+~!zc)Xzp-bJ*fH{cfY zLrFL>hWRYuW*B??cHzsDqwA~C)+trTGkEZXk#Y;>ZZ4jRpaPWKQ-GzzbBzvJB~E)% zqLHCUakwW%gtsMiyNAR%U#7hy&M0_uo`H1o6<BGUQUl)Ss)p+#L<JbCdvkq&u-^^- z;*Z3TtNcW2@D1C`{AP(^^v^;gzHewiR9~Kyu|qoqnG;|(AAepgp^8qx<MZe~6q))E zJd9(Og}C3BwHbID9PwjT4c=!sJD0c8d7=%)K)k94H=zD#(_(%P6(yO`d*XnIOJ&pz zR4DC}NhvLf(=q(y@}${!;h|GfZk74<i19bXVfGU@BGGz~TSMpYX*5tVgS2T+Dkzi4 z0~j?x$oE!0GL|c3xHC60&6D(Xn)86VN%7pBa;Yg^!9&lqC=2?Sm^UpK=;(Oxk=MNb zUmy?#1sosBQ^s)VCjLP{?hilrI$by>>lH=6?4%&08DdpTaZM;x-sRz)QJfg0CSb#y zJzwRclq=52cgYys%96WvN_s+HZ1b8i2f0AoRF6iL%IMn!8@$9j=!9>cz^e}Gv@2%C zM2w=Fjm8`Gg=<@i*GTsV02iaEFv#$^0lxPA8R9ZHiV(bvi~MK?$w5r`pmMp+jL`4v zg;$%v0nc8F)96NTx$i>b1bK9#!%mVe+74d9NfZ_w?$}-dRo}i6I_w3pNBCP0g%D&s zj9z!!Zp0Y$0Z<nPwzQ?*AQx1V7+7Y`vS?X8D;+^0tTIqoa&>Scl5UDdQ9Q@M@oyB` zLn?iYTBq#m_p1lGyl6{=UrOVU!aa(bAPI(&fI>kQ(5()F?H-8g#uFBt9k$I^QqXsz z{Q_Pgj*tQi6&204NLU}Y4420<SuH%m#%FOwHes@5|DMB1_569&V3LEb9~3K5kjRrT zsobGGwvLR0sm56x|NbQO=Fp6J>l#S8KSxF%W{VN4q!v#9fVRs3lzB;kg5r=*uQ*es zH^G-;iib0-HGgGLdP6DUm{!*K$b7^-FLN+Gz|(-3c|0k{pFW7EzcyjVdqM010ky3E zba)=%6p;ZWhy`*OAHXlm=_AITqj|t%R-j8Y5;7mP)<>)yQTaeX3S4A!RVISw3XE9H z`PkMQSYfLLKqS|?OW+i*<Yz)(kZ$w`y?yi!R2v<!U8_LH1yT<Zj!2IH3o`(z8~sk+ zfo5RKQ1H#Zv|Z_vm)Hf?onq-vAI&WR>@uQ(b=|}vsLf%=k$TeL>aXx*VF!w<oWHSd zdSQ?KLNmu@KYGUBBc?TWKP=JQxOmEb`ow~-Gy{^7o7!|`Eh?rP>KW^Z7tb6t!uYPD zt!@+=7c@yf19aYyijH!!P#38M`4oDVr<xh-Q<@pOs0u-i9&FlkZUV=q4S1v-Ltua{ zbFsF;c@7n~d|ki!$&Xc<ZPvhLyug8rEHngxAo)}HbL69ISm<C3Wmp_QGz6P+jZbN( zNK&Z|QNCm_&O5As{0|$9GSl{h`wov@qZjw*@1oq`s9aufrZHu%Dal1{4N259?!k|E zz{ENtcI$~B-c+zDzoW7MS$|w3KmAw#=7W`f!O9J;Sl0;=;lwu!BQ?W9m=m<@A<PAE zq1%pWN`C-UZ+0s)dyi1T?yd?kxv{bUOsL?dXj(}DCy?>V-m3bZfZr0-u?96kpF=Pe zZ5@pPbuI#y=&<^6%nb)f(kurxQVK#Dd^{VN-;Ri3Uwz9xMJfd?xJ)&-)HmS#IQzOg zUq{Bu+>Q)@UrcZ^9b&CqA@_jUF1gz$Go{jHxdXFJG?ox`P$|H<yX)Hz08N4Q?Qsf? z+y-39kU$P0d>}TsJjl%W(5;f#&++gKj9^2=hX0eGqAlf1h{5%h<>ie!4oM`gL}g^q zh5Mpz*+i_yo=2qr#1io*OYbD4Ci$vaA)@zV9g6GLcT`Exz`6Y`$I!&Rh>qj(OoQ}? z{#jB>>fH?i;d)tOIrcPwZiUnKEU>ako@W1JZ3KI=K!U6#oM|OyloeKKF6V_yLiy|v zIVPH00(p2LHe|nS0L53WQczU{jxEkJKb0T}R2$Vn94%>mS-N5{C~5_Zib(K0(Yp6f zOD}TNI`-Z|i)ah@k#1jIX9#jGCHU?}h(lsG7F2UQ5^Qy~Mi|sWmXf60Q<9fO^#fXK zm%3Ob%qm&HGP+jTN9jwI=za5yvX<KowzW|f4iGxTB@h{cmO;b(O}#-_oYpZmE9Qd* zY~@l0qH*|q(Gj*6OA=<~gG8On1q76b-i3Qnm?PKBQsIiZrFc4OnD<esY@kv|c~e<< z=^>G<b*yN9U`VBHL{q#-<kAtkftzdtIAwEMDK>{5bH&G(S*qQ1Z?^y-qRY>>ZPBSN z%i{YbQxrEm(02#*R4k)fsgQ83(piwgtJ*BSR!#-kQ?2Y`amP-g<3WYi2*omwsN9S> z9tQB2U05x}*12G|Ca~UwDM7EqGzX%!h;cXR4)nzdux24<bfI^y#W|rg_#y^G(VQRF zgAFmSytg^Xt^xp9^XDXyp3P!tpz(`U;*WjQ=A+{(n8A~bZfnANfeI^EYdlc2{R6qG z$3*GW%T<~oycI1!-H7A8I9fCn-Z5u_vc1<IpmaQy#}?h2>Uw{1FqBC@5Gw@?L(K1r z6~?jotyC!@$G?9&t;Cj|yhBAcH`UlLpIUaZnYhi(=*5h5)Xkn1j|{VG>fBIBjNx;_ z3qR7zaMmI<>_aIf;ZOlVDgvs$_jZoqZT#9db{Uv3<|{`42v^;<R$l9;m^FA$yTr`L z8Bnm?6~qTJS=4d<W%XEo4B7$Eh&^c;1XR9WAcTTsWm5gF+8doOEo{Y|<s*tPqAiN6 zXg#am76N9=M?+FURE|#PJ<A~HhGy$rDo?gq-h!~kJVnJT2(N@Y0xNeA40#%!_~KoL zLCZ!R!h3zyrG^HY`F8H@%%GF`RS8{jD58*feciK%NlgiguUjR>!_-CU;WUKw(VuNO zZr#}OF#4@!rJkwkH(M?amynM(>kKfW%Ce1_ac*r~p{~SeJ%AiWmyOE>KSa7a>@M&4 zYA2^w6fgABH#R0o3v5fS+53|CNkXhF(>J1>Omv1OF{2I>Fi7r+NB8SpI#D$6C>AH5 zezNz~c4=yhwqS#I0e_`HG4pl+m6AwL_72vbcxKD)BjDVl(_t?*?%XjmsMCM_4Gr~e zjAXH;P02ss#aisKG;KGhX9Tw0R@hq`wc%-&m}E=^eEUTf<BmSs0If6f0bArlbrXE# z;M_oYXRYAFiis-7`WV_xpK}4luH{Mk#%3d=IZIbS<wW4;Sx<G~53)ZfeeQHJu8Ug6 zzx{9u1m-x{9h!n2S&rv(>(?B`P9+^{*DwS0+c?<MoDpzrP4`}eY4nW?UV~67!l;!d z+k-F7Jlj|xy1Vw1P~OV)lS29*5l)UiSOJG&!?yaalj1yyz^B8)AXf=F6Ed-%P=k-a zdT>DQs*$^#I=oUR9x%Li1?_2S%#@;&TbnO4e|8Q&#b$Q(ZpLjNYO`turu9>!))IaZ z^C>Z{+{h7A>l$NV)Z1LmZJ!l)rFuw9+!3n@f*ob<b@$Exs#;|7T^a?^c2ai-3jbj! z$)p=xfo85S94AZ=UHGHMPkU19k~7n^?Ah9OHc_Y4<TsfpkZJz8awGto`-xp8w{^gG zw{jtl$^p;iwW`&zU+;EOUdXIw`djlg7iaUd+7X}zKWi3K6W#GA%vRd-ld30G?e8$Y zm639;9E^*VMDHsNSlfm9sbjb*R)ZL|PL|>-T(93xzu6zvQ0?#RQyfW8G-uLa$7U{P zcHLIHN2^rUjR4#}|1xY4H&yRI8TmpUjTc~coBXu`tZ#3uU<~Uy;MmUfybG31<)F}q zC91deE;4FkuhGlDGT;ZmG35OeGzVjVrqh`uTcc5fLua5yRTMZVQY(B_CJ{T`cOy~; zm0hBW$q9TUgp!e?2(Ig<Mnd<G)uY%j<#alh&4Cpv@N3W0YJ<$p&0jY2c5`nPti%v2 zRX+sn7wK(2vgsS_f$h~^A%Fg=wpikb;ZPve*p@|19h5B#uUM`u&NaNSb9=m6W07KZ zLq{0o9QSQ;A-@1A+R@CoJ;<1%ts4qZ?y9d^aycH)SvCa9$7;D$j5IhZ@+{o90=ouv zRo+(%r3ACfTdQshRBV{rEq~D09{pK+>ni4Z2pImr)9%db>6#787t<rIrcMsM(W@g{ z+LElxXR)U3P|MhchN2&zcsZ~}{ft9*LBZ8DUs1ytz(W;T9Z5S-lg@p_p&gwq<GZzt zopq+_4ZA~nHE$dq4?SH<eRFJ)Qcq2eN}ru^lHILf4h}$9<j~*8?`2g2^X(G7+Gs8( z0kQy)R8)@pWG`FYQqPm67Yg+O1z38W|9nfF{}g`n>Um>-&YOb6cKwZgrLw3-8v0de zWRZ4{Ay;)PlNZ8vsAmtorDwW-p?UWGbM9CeFDEnPO6*dz!G5eyC@<d1&$at^Z3*S> z(8sm#I)?M!|C<}iY~gy{9<+|fL(X|KMnr*c1R9#FhyuMXm6P^3H0o-{*EPXdGHA!+ zz0HCex9lr$g*nZ8(K`6ylDXh(b6$YaQP;D3xLI$#YLCrs)0Pauxyd**8scv#Up1E_ zI?2R!+Xd48g^Y?!4;u~&pNxlAuC!}WU~xT*-3KwJ{%YphO?t7n1<*sgSLANb5QtN% z5#wq58FZAS^>7iCSjODKZi*RU`U=R<Lsx}f_3X=!aCIdIB^u2F#wt`mzhuVbhUkpn zjaceZ-h}yt`j+rZwMJ;9Dwc@_$FvxRo^i=sLx~RcZocmC1&076lkO8>`~*+7yeQg% zl_thdnOj$>l5E`}*YZLeMTJ|VDE_PKZ8$qNE|!0Tt)Y>9qa?NzbTNgAv>q1ti+aP2 ztG1`$spr+lK<&fX4?sf-9^;8*OlFUHmqqku>vfIVRc$!fCwSfB`4U~01%&A^uiQ<h zmdbb51XL!f$7$>fCa~m4>~Jiw?6!MS^2@u#aD3cqLo_X{U$R~7Qxu0MHhr_<6If~+ z<)5xu0=5)Si!+(~C_4x$1raO%_C0^PyB&5&(3p4Fj^@?_0Fv(IAqLlD!fVmrz`DiW z7ujDWWdx!>-9V}&Y?DXyK;S-QiPzio)-?c=J*J<uoDSKD%MUwl%uI``P>!WWHp$t8 ze~l2Vh`7*Xmo0{j!h1MZz8jZYXtWd7QztX+x-{>CV1rgrz`JH?<b*vz1-$Kw0h*tK zV4Gbankr)lVy$VdEp@OHtS5HvdFl=?Y*^DgyWg2&eX=-qUf^0K%&}SjYFrouH=7_^ z(4dN1CoEBj`_Jn2XW51ar(K7pKK;$Ru(fuFNe<c7qWu*A+F04IFv{N-6A3zQt3IT5 z!>*pzT2kOl`R>C>SHv2=3U&0hWSmBWEXmn7uf374-ZsV}^H85$U<c6IH)O~|11PW1 z5088eOM&hPHOf&8p&ZCm1n<0&BBh&tymJHdkzy88FW6wr(>rI>Kd04;?9@FcqxO`g z)_sz^W!md-8HL2(WW5?Pj+edRiS!nRJ_JJ;EsU!Bn@CoCb$4~}9bP^7SPeL&#lsOm z|1|325E@tx>ZHKqJVSXR=AjbN$RDvP=>uM9on>kadL`~My)cU^MVNDQg$XxGHIH>( zIwfL~rEYI!*Aq-e$${#yVNvh)ii1gc9H1{1ye+e70I%qX-w4p<SQ_Zkj&;sdeqb?8 zHj?f499016(}?v30@{f8vDxE=DL>~VF|w?2)$ym5umx<@<(HAn%1%?AnH|l`Mtf|d z#dF4M)T5hm9ab-mSWU%U0o-MpMWf6yzgfPQwH5iAlbQ*WmjU}*u=Q(+uIFYW0gD@O zS(?pbkZhM?SFsxyB(}X%t0?p7j?3HD#%C+%>Y0|vBCIEW^C+|Dp)=O9Src}BC9irp zyEVw&ni$rV>YX-^MFi5DQg1rb<KThD6G)+pDr>L;+)<THZ=NsiK98h`L?+!O*JPQX z;4y+QuQ?Vl3nYkYWAwAOM*B~bgbt8kdE8lx2}lB@E8~(}0g+uY8z)7rguQV-XHP%( zqhYY)f#E8Sa_2wZ+m#cXs{bA;ZazMlV-$H=na6O)CZ>CZmeCpK`S8#s$oGxq@5bG8 z$#U1#1i(D=N}dU{y8Ml-ttFNi_3c<`Z}RUA0o>awmCjC^gJ4Dyr?t(W^<w0AeeEV# z|3c&UTK8j8qix-n{I`p8oJjvJ-_GA}Z7;&I&lml=-7ZU*wara$;9up*x^C5}DTd)n z+wbx|Q2J%j<Jkv<UwT-9MMSbslfJhh2T;%S+V23nO=R-$Q2$8cE_jx5Te`b7Vmi8c z^w;D?+n*w-5*ZX5E3wtJWY~$<PRdW^_dKpy$byOUAi9R^Iaq2U<3;zR&xI>Q$gV;C zxnNuAF&N9wq$BNm`P9L0lBenm*NF3{-&_J~AfYvNA10ib4ygC!qhLL<&@HC)geWYW z7eBZg!XIduiX~js$25*DjVcmj3#e}^omEA2j&JlwR4Gl#83j5OgCS>c`Chgr+t-DR z4kvi!HTmJ~T63iqE%~flN9<~?Rp@2P-H-o~w%LsWYv2DfQyIhjpB&l$&XJt{!;zJ$ z+5SgN^;4_w=+GlkNirt4S~jpnBa2%jum~2Z4)X{jnn;;lErW%kI#m0(>rNut&2b)P z7-pC}+IhI`3?I)Ew0Zg33yyCorXd*w4?RFKO^L{;<D0=HZuXvpb`)ga5=5u&B!y=J zQ*6N!VmTopxW9UFsXhTA{Jb#_L=t|vDP6G^1R=2>#7ueSOWGdrSV~|Liya!54BZn( zGES`tnM5M6icLpS662UDHKD0_Nuwl5qKjNzc;ZL=T==b<AXH}7b|?eso^rg^Hcp); zITO9`K`D}IV%b6@M~g6IF>xK%SgOixu8mJeDILv~#~=v0(AF*|DoF>OCk3vU5i*JJ z3lvfGW`ZKF_$KD>#YCRz*Oo=Xo_yryg^Yd50JaMF6%oiC9rmb+(v^8D{^ri~?_L~~ zA5+u+AaB?|tQOB7|Dz6%mzU>d1}yHBqiwK!w{}OCD;omI%%{7*apM{cnA~YDK97&$ z^Kp*}_JMOEscB}5TP&1VBN6;h$7=5@)tZ*Gg~~@haa(zfao_ym<;d%0IhlALbL9MO z?s~L=Us%?>AKOq(mdD)4Wd*&&39xArO`=1#ip12($Ei-L$n?&IEvv#t+v!P7=e5{8 zoZ^IN*0S(*ZA*UQxK8*}O?Diw=NiFx2FprbFv91Cq+ZFSQw-@FQ@iKuyW7Db(aPE3 zY4l@!6w+*3r8L@~pC2Et_rexRtJ*59#e>huQE_?);@Ew^h=KX8+}W_=ENk+P04xRH z0SG0`Fnc-qV8W~}I|`<3sf^3{S38S%G&1*>QM;RKUC`85dL2FhIeh25Q&PdTnIBB{ zQOyLLmC7l#$fL+>@Y=K?s3dCE2Lg}HmV|D+ES@>;moj@1R@}DBe|KnIRbp=Dw8GqN zz*ok;_qcA%$9H6<q%WcLmWqjy2o>Va3DioR+73EHCt#T9K`9c5+tkW$X=pBmz|7zx zEcan-3c-Jj+dD`rC8Q|7#2QEFIxnKp&Rgg;?l0z|8l{qYYDm@#mB>8S)32+Fq+AYd zMapa>CDUu_wCQ=ayY)C=284yIhXzXbmdjjtM^CP@c~oC_i_r2sSB3V^(e~IlmL9P{ z4uK{OivhF-$azW}kz)xw5E)^+z1z|m)m!{xGwU3xgzNciA!PZQ3&fT<7%f)PX#X1C ztE)=ArosnX?0Hfxxgkm@B1PE1flIVfj_K;?drG*Z;@TD4w}}$D(|YGoPcRR2Y?U{# zNA5l6xAL5U1$J&wbJUoqS*MeznRoAD;<<!66{LKaQJI|<V0<XxqYe3M!SsSn?R*%w zE6W~0<N^_&=qk;zrh*|+oX3SQ=n^8{O=>oP22BH~M*}r;4no;dMZz6XrG_O^+&)2W zrBy%DMm!`iJrhw_gmG^<`+O7lS@3v4W)YNvkKR!QW#M^o0j(OqKvPBZIHA0YmzOJ^ zeJym!*O&5ESd2p9$+vd5RJ8;#o@F&ZSA)VzFR8E#Yhx=jHOG0wp*Gae#f&m|Ff#mw z4%?2>I82kB9;}vU$bHoC=I7%e<?Y6bt2KKCz)iT3++20}Fx<7zTu17jblAM?Fuz_u zdGwdJ1?PS3{R@M=p4_F`+(UlAVEGT|lu9~lmEZErvSPU-#ookQ7@x;wkuME=Ex~ug z>Ch#fy(BA3KCAse@SNHGZz!SZ7Rh}0U(EjVZ(#r5<8~KM2h;xutaxMltp){PL~cJ( zgLH5gPcY61+cHAf3?kq|_Xf+<bP}8YT9}Ug__EucyY5V7qvmw1#y@lW5|QiRi<uNt z7uxN6oNZsAx1!W1W~7$UU*6kkukqOC6~4v<8uV`24ULulkYn|ObSOhZ$5-qXBC?fE z8Wq)Nqd3xuH#@7RV$z5oKw88VfpO2EvhacULuv^24h7U!eBvPTG-a?O$<;n))x#dZ z>ivM9Iy2!`g_}CqXFz{2@*F^2bl~ZbSC$N&^Z@>M_0*IAZ~uV|{$uX?pZ2c*_b1`t zN%x=nO_bH{WN9YkXq6-;rzBM+Ch3(R9AFg~SecK2{)dvwnxpX`?%#Zs{|5fQ;b7@N zukU7RXJYR}|G(zccmMx_9<*@{P{9lsA+J2ciFC9Q7TeqqP_QLjPwoyMmh(acjD#B> zAFAh0I5@l8jQqLABdFr*{ysS-VcTQL20w6?jfPv!o{H^U(t81og4+mHO2}tr_zWGj z4)hl-3}wm;sQb-sNB(%Umc^Bu0<uC~%6AcLRjvkC!b;9}GY>@`l5TiNHSvVgYknr3 zatGl5y*$|}m_5vp004~tcE&%+*#Es0cPGn#eeeJ6k6cY__X9TMpP9NjM}7l$T)iYa z`<w-5jkM{7Y18bIbYIBP(KV;Jk+q>TNr>ZKcd<sBNw)QtcYz=?maI!=Vp53%RL2~G zLro#v@cLygIS$8SiW53>#}qd%zy}XU3?=!Rr>)R(jM?qyEK&EQQFqQ`8&Oom-p%1E z**j&YhwH2|R!sclH%o8e5V_Nn#nDJ+KYmRA485ErLeJAM%Io?7ix9!^zFFyw+=U1- zy7=Uz#d19D_QC@X>fSV$IT#nkqw`W(vH2CUTMZDM-1%n@F4qxub@Kw$JN`wA(ygbo z$Z;B^AxOgzQ4+^uZYw`$Bh8~atj+uJ*R<u#yj)p1ar2q%`Dd3aon4(k#o}sfS&P-S z?(3p}i9&JI4#!E@>PIsdR!2S=zGesnG0Z)<d@y(Emv=rL(fbcVNzWEfpAA0jA&hR! z-rO#3U;gjo!S(my<5B&(T6@3G?|6RxZ~Tbw!(@9s|Ih7z*c60*98R&ZtC+fau{@mE zKTE%GniGU|3tZ(gC;s<&jx0Ud%=*#6(+Tff{O7lvxsT_MBTJ7X+3I29mRfwPiVHbk z=tQe#@9u9uMxTBw^dQ+0b)!S~r^~h1-x1a%ox~$3O%Y(JZ>&WPCeS3vqQ6A6I`*XM zMD0B6k{RCOl0t<nM7gg=ARe8qtzznTd?t)_lrOe^WOpea;dUJ;Hr@`V<%j*RDKUo; zi{y04m_(q<V?-mkqcWBlYeTSuZ}Xa_gyt4I0W=33e+egju<ilBPF4yyu%$$h#^g)% z=?>)&8dH*Xw;76)Ng|OKGo)t3Paa7%O)1HtrHVMXqRw!ae!_&jDz_`7>k6YPpdu_{ z6|RKXy`-NAaZ(lVQ-rzpZ-_!Z*wvYkO^%17XB7rQ-a|&OxvefFl87bZI4D*vPMG6o z`<U4zJ(==@!#aI^={Ifw+W!JI=J@1(!Vd&D+%+w0m62PpD`av9+XA7Vw1C7t10)!= ztT|<!NdG4H_N4Vf&F%#f5Tq)qjFDwhA%YHAcN!-a=Z}lR#G$#w^Y?q@(U}RCNI{;H zgb>+hioQaKyR#_i4U{f{7~7<nCy(jQ7aFQ4QE0s4A&*p2dU&%u80BZBsoqO`6iN5F z)qh5_tzM^|7zzAzH#dq!8b0xj(lGr7c2DGhRVR?E!G&Riz^0gKWhlM@$SMHJX3FET zpFBH2D@UJ@92*xLpXzuSOj?`?gI2tRTN8qi1=`nhWT#gJ*&{}o14Tl++B#NA{I26f zJH9bzX1=08V21#978SD6FPz|wWv~i4!Ncj4n!y?=t&MGu6`zc#={35vC3-yvfTh}G zQ7ol}X_#UzDo6-}PQ`*Mhr_=OBFf2V$TE`&E7Q6pM@vkYO)AdHevTvHxH8$aOEOhm zSDqy(=>|A8mhqUyF!kPH0eWJ%6t-)EfU5^mpjKL_i=iyfr%M`4nQ^1E<j)r7FT)AZ zCX${4gbCrgUZKxcg0;3+R*l1XmrUZ0p_*TlZFRN2Koq?@^<W0a7p|diR3I}ng=|p8 zD$)8oeQ(ULDXi?z;nd5VM;^)xKL$;jpL4mi#zm|lOry+PU86kedFUpD_AYzzDy{v& z>T9SZl6kJM%7iV|NBLO#q6AQF5!4pP1X+qG>yu(<QOQ_eB~-4H3G{>jNCi>h81MHS zP%}s>!vahzbCigq%(6rnp=-4^?p{uT$J%GROkin{orKW1xi?1xy-yEI%bt8>J{V=- zHHa)-*P1$aMCco$1}RUiJjTBOekw!sv@z*T^l4@^hajw)6#<{EkeLCWop6LBlXus? z))d`?m$El<s5Xf#kaCK%&`)H3^{lqzVeszPm+UGuLH*R!&^Z}8mPL7oUab)RIDQKL zI6j!581Fm078vOUVLVitiF72eODyRe)HgDpb;M(0A1ZRr@kRfOAW5V|R?09Feuc?P zu-K&*f{F?j+VlJ|z!N%y(w?}kq*iaU#6ei<m{o3jB`&~1)kWDEU16`~DN4lJi{g<H z_D>v;=$+uq!q~ho?ik2ZY)<e{g$iIAY(;$l@O+GV_X@j>`Rqe!Mu?m4^4s^fw`9kc z?-3k^F3U>J1;dE#v6*RVRgj*hB+attropWxGliKx3Enu4OTFbZ9WvwI-TPdnyH$ll z1~woDm+>#?U7G9zr!w)_(N}uzqD52%i)A9Z)6c?NO@~+s(U=nR1#~S?Y|8L53f0Pn zQ=vd<VAox8Z$#J6Wo*?-DHiGy5{@QplcyfHL4C5mQFuG<MJ?DW^CJdKEpZY02mpus z3{jvp+cCWjy}KGLJ+b9f6)*%Cc2BEFXVr1F7rwX=aA){&e*K|Fe4z(tkgn&Fb$?0~ zNX2CB_R@D3>S7el5WFMv{9%8xEoKA<RO`%uY{l9M?KVgq=Eo;eAIe{yti4C1iIk6d z^7vZM$d%MRydcN_hq8AJ&Ma)#1!LQ`ZQHhO+cr9OI<{>)9UE_K+w3Hr$)2j2+2=c_ zcGax^>)%tiE*ZBl$L*1TAQkKY@BQSQF-N;!0x`x{6Z<LF6NbkAx>|+$g7zQBGII@| zNS=XZ)CSl2)roxiit&M~{bQzAD+;2Lts2tvbQV0pS@O{KYf1l-HG$v<<2s*_#54{i zc0gddr@E~2ty(E*fMV&20M_j~-h|@FG!QZN3SY6`qBt;zFV(57(EfEU6If?T94rtN z?m}=I^E^fl`ez`scdM41@L$1{^DA7*^ptFn9D`qtPnEf*c%)`WJ(p*JwATW9D-f;o z5bO5r6dpPLAK2@`XUIH{@9Gs9yKC%LYoQsDB!tCcQRIOW1byV|6LqeBXHZ**8f%VW zQFqpjQniG>8MwQ%WPwcQgKVn7CJV|?Z^rkW(k(xD?ln%g@fr&3GJGQ&Va(mg2BH{- zKx3p{TQqxdbz^VoT%vidoA)=wD4?w}0uoHOa|fdZd1vJo!C>k4P4deH0_XeQ)VdWv z+N?r6Pa>r0!%6_?gnGjOsVtb^d597dv|iN{=qH1r<c5Kf7_M?kXeoPG=p3~n)(soy z7lc+{i8e?&3*$(22qRu(z#-Fv^q?g5Zp7P3cLbEEBg(ggh>*vGKHgODu<qz|v529T zIc`;6w#@GdWim|-gVkq;zTIB0&h^c#M^T(i_CkzzYpILxRw^a7<T=b%{@nimVH*8E zQeS^2$N&HH=+SFOoi+#%5EVEO5XpZIaW3wzZmw>QF6RGzB#l!YRM=ud>OH0X$r>lI zFV=61glUD8sSI1jAX8$55?5f6jm6_#O9}t<AKRI+&_=RhXuQhgblk~C7Wot}*8o*J zld4rChvt7W$b@7{Ud9<j%xeI5>7~3g!}lQ;ORi{Zjvr!`LInSQcR)vc$H~=PL7S|h z^9y(54jtj+l@Ai6Oq1-0UB{f&{vG`bzi@D0wY~#P4{=!wbjptz@=}PEnX-wDJA_b5 zYXNJ|N3`lV>{LP>et88Cm5DQs9w#I_+fehEbnFySO5Aa!P(R#VnO!B5EwuSkcC^Pn z6;ZN50#V6?TsN$av9(Gllb=PF$~VOFMysxLv1u6#WVRi7aT(8#ABa}V*epM=Vv;3> z``&*nE4-b8GdQ#n?M_wMy)h&s_$<N!Ntb;BK3nunLbhi|>%iS_=r;`S#&18FcSX($ zVJ?dvQ!D#zTQf?MrWY>b@DTch$#$>FKvUc8yRQeD8H4b(2WPlSyrz2bINEhv8X8+D zL#mkvOUmvdHI8lb8}tl+KW=qja)mc9i6@CA2_NaBMS5b5=iYZLb%7er)`4=ro?y~~ z!eLb<#0Wq)EVzKP<D`*)|L}U3^Jqe}B{U+O_iidD-Hv;usM|+2Tw@f|>Q%vUV_t+t zkI1yqu^nt+&7j$<QgJ-}tP$nn8Iz~YRSI8tzM%q!G)l^&h=&cHutE1@TqdhSMovuy zlrk=#`H;i|K_WaR>ek5VyL9rOAvlg0+k1(X225UU#0<3X<u0nV9A0K;C|Oq}Ry}+a z)YlwA2I~Q&$A*5`q)*WQb-Re0YFj-2QF<QG{vWoB|MoDAoveSh3xJNU<0cnIz-v9x zVq|M$9SbXPR}dycnfy|w(GrE!A_9hR!6J&iD`^T&l8uc~;Aftw3QA?nwOa^CcI0>w z!rU=0CPC6cwjM^76#K!8W}b3y>PT6aCX2UGSD&i(m#gmF!e&kkprR`-$)<(wC_?Ow zSD{2H3#Rs0F;)W9=Sb5YErS)t?v`GTOyN2wzdgUQ`E2x!tA45sEj{cBf6S6fiBPVY zT4iondUbJj(81Kk4b;`59cy0Vdk)^$pg0X7U}4(Hm4bg`a1x8naYB>)msCzN*}qAc zzO7MiQ}H2~S`(sK2|C==r+AD3PPCT7s80drXz*CGSum~xUM*s_mEMIP#{cZW((atX zxBI_)&V*inJWx!ep+P`eM*FeLMD)TLxcBW@hEDU7);Zf@jHwdUWV;{Q?%6?;S2sXT zU%59A@DTJ*ezcOrwa88QpDczHsAeTeNzWeQ6++%tdj#D27gZm~tH30QxCEq=J6Ji} z)ESAXr{}9`{y_j6PN|)A#~Y%o+jMw3*4AF%@A?TzYP<tUNy*a)5#(MAGm8+rm(r-= zil|{muuiNlM#G@h-;5C>5@qJ2LNwG^V6q@O06!$Q1YxxaHq8+>Ko=J1LMk4+nWXCC zmK@wUtO^FU*M3-yc{&XVIIujI6+I#NIQSTI5);X9GPH9GT4@4o<k(WKxr>Pbl&and z9hvF^hBgfR&uSR>IZ`MdsZtV`F0s-R?J)GvpunwyS_;6?k1gB2fa-dC*l|`4Zlk3* zag4-oJRyAU6qpywi2><7HytamucS-%cXFP0^1o5@Y~Z@o6R_#h(fz^*&gZ5Ln@8cF zlTK9Bus}d^X5bV&;B^1a!_L?tr@-JYRg`9$=qIqiA%P%e>agWT@K|gVCJwVGZIhLR zF=FTIlZ*9!#pGO=#o?HoJ=unJt+EWFyML}v2+hbVD`XTGVZW&{ORVUe+IaZ&fFK-` z$Q+$;X-*x&<Q?`*Hyvu$8+;XC>Zl_6jjd$xlrN>8WTC@+>GH=^4~|Z?N~`;anCWxR zd6or#A6kUyI_l9G87O3p>%KTa1VUBz!Z}3hF#QVDhvy*+#c`*~wi;=PtE*#lWK?8i zJlKrMetSCjygIxl%W<<($~FQ2$vgwV;Ik^#)@4G+H0ZTX!Z8oKhdafvQbb!oqUHC6 z2RuxN58^(tMRuH{Iy4Q{!1$35yzw?51lSW<-y(*uiX3!02G%oYx`R1($lTCsdcpK{ z^0Wh5)b?;Tsgd-4zb8|oVbWEsHK+i#!$btJB>F}0L6UsBMX^RD?9!rt*lg>IU=f9N z@czO(&SuSJ6QF+ySQ6(&L{h+|Rn-xHQB_b(gg%r73)HF?GjJ$D3pp}0j^p7Da7<^v zcUXYPjV@A@czOG@0&JPkbde)=3UP2_EAo_?^f3++1CWYVTvx_LgJJ9Np;RjnRi?x& zSoN}qedK^ET`e59*2Kosm2=J<K?gbO%;d7pT4{DxG=64RknUfN!wWm|Lom-FTL&&A zGpxU~<I_9_QDB5c31G9vU>OF5E6rp+<=amlW+F<r>Vzw=MOM^GY~%^GZ2c9@ZR;?j z{;<WrEj<OeL8B)v<49J<+SZxQ9l(6=dE@`uwz=ww)5oP)_F6pIqPSsenH`Qszauk{ zEd}3h=ycY!ZJ3$?$I+9}moznRQgCYIxOFv0bsc4Y8L`20vZdh{$nE@HKW@+ysi6yg zJrGM*GA*>{tJSY9dJu{#8w{HWgm2?AKUWMPW!xL0qcjhINvEUuZS#vw%;rSL%q>F} zGhf4)m9;v0Fs}EOl+&PoJhn3c|1a3R+~o=c4oz1Gk*;XAb|i0D{ZH3x4d{c^ucL$Q zcJoHXC~d<>I*FbD*W!@XI<!H{E;8L1y5U{!h>z6BI_L^#__J0kTE+?Cck8JOK6mYU z`$|-Zc`)ejIV99F4LRyd_5wEF<Jh36jf&>#d2Q=)$9RTzS5MiGKkLsekhtTJE69oo z2QU~Dm!?qf9z{#7rU=pVudrxw!JoPlDslye=cw`IkQQi&qib`VW5fl~zDU~=cM@WM z1D3Y6-MV5VhhCm{hzDD1RlbZ+1z%+~8$o@zE<k!Q0i&}Q0oz5bvYIW?m5MwdFO<y# z5cn;|k0*|yW@Y>TwlvRFk-wDxtq39~!f<C{<Qcxzz3$JMH&og`R1+V?Z^Vu?b8UVx zoxM4=T*=Y?+h?cb7@>yBxv5MKjt7LQhE@kx3w{E0^F;-&Fufa|He>HrAHHmdSm_!2 zpjxm#K#tb^^oFPQIa5;*Vn2kiW!lXS*7+CK#1u*7Uu)XQwBA8J$D5+;=F2(D)%$G4 z_I|;c9;JqqBWp;<Qsx;KWKDTb07OKndr6+?tBuDu(-GoAk))8<ri#SV8A+aET4L{7 zq12G8)lXc2iD&*GZ&)Q*BuvfP8yuf>b$ZDKJ1V>%X&Z0G2P-H*J}#~?_))8jZ=r2q zz<2XYA@}Q@z%+&G1^q#3XAKxg;%|=+zj@_4EmnI$T}aD?RsmQ=p=U1A_D*K}M?HcB zG$J#m$h37J1H+zq%i3s%sS@M{_?XnxjoeZYI(Kvt1?)nhXWZD0<;guN+ZrR*E>q5y zM2Wspo@mx(6wQsn6Z>@jz}UbtEIgWx;qx$P=}wb%5%R0r#ZCz{#<H=bTjUbnqZ860 zjL|N+wM^e)joVLN3Kl#iD3`@SqB6R^oo8$T<(3^0dcN5{0)fI6LUh~p2O1CFFZ`UB zLXatY3QcB)PGS@#7Z0F?tdov*iA{*tiK@7(`oEzluj{RayKiM%M%P2pm+RoL-`68E zmb`t#e=r~^E7&(U8WrtKO^CY2Huh6a+c=c*OvB^Hw&jIuV}2LFVA9#QfrHZJid+X& zL5eZ_qxnS~3G>IP1=J*A?G(6?{53L&%Jm~1Dag|r(v#@4jJxipE-rPBS7`h`5Qi@< zkq`v7WpZP4x3JWJP%pywM8l*@aU%mBTA*j^#c545zDj^A76j}YbjddJhGj(_-=y|# zE#20O*xWFa5@=&2H@8C2_E-DmXF9fiE67{3_X7dpRpP;%C(CDHn=Q{lNXCnp0DT>3 zk|0?}?w>e0Z9+RjBR@eKE?zKrNeHHD@n@lkgFaYh(%Vd6++Ly^y=$_+??<-{CIU?6 zUAro4!+3lIoVpOGp9h6{W_;EPWToS62+ZO;x%!~I6ev-IL~oF($nT-Rd^ibZ1gnWA zHFpWAD6U@CT4QB~5ZYcpG?1^S2lmfzZ~)z!?D9fXXr=`z7$PdR{QV&s4b<k5k1dYl z$Cq$F@f&f<M%6eyA2X(`w87lOo0&|2K<0E0>YxCGf~4;}D=a5J?=2nsnmvQZDkzs2 zs1+#C-8U0wcR}x^-Q}Jq!VWM!g8X&cPS{?#aRe1i1LxR@C7~kYZf@WwJw$ng@Aeti zl(w5)74)s-&2aO#E*avWIVA>w(`36DniN(Mg6Q}#{raT`)_?$%j;9>!U)@*52YzH{ zK-<L{DqSi;0ISghs7e_!O>eW#aBv-!M7%N5G`Oqt)x&6jtk~6<UGS4Zeo;Bdk5gF@ zq$GcWt=Be_;aN>(l>iekgR0B&>3i`_V+gN$!vaf%C?(U@!aT0m0A{I0s9}6&9BH?$ zfFRM4y~T5&!};|JSfco|Wma56Z@HcatmV^xz}P#S?FyNwKtPyEKtM$Q!}exxZ0h)5 zAWbjcmnv@ez2`p|epb~h@6im*ES9{*bfhy=8KRM+UJsVc?8n^L`kY%mX^1VhZ(q-6 z_eKBr0_!hPlgpNBz>Q6Ri)54Dv0Nh69GE|bHOr>-)33D}D$aflYpN4&-K^c5@)O!= z*`|+N?y286zs@MX@ImsMUo|yF->(m6j-R*RYyTpHH}fgZyjmeQ8JP!=XL>!RyeFNk z_rCYN<kf6TCU~QeMg+@FSkWghC$cIwaRuQwCE9x||AdU=LK{8mg=IE`LFT|bf~}+Q zsiaSH4zeVi&nh&)k>c%3=Ov*|WhD{ZAPOY(d!-l&KH^zKKhu<cnRi?ab42{fWjUy3 zL;l*S;KYLSNan5_`p$bo920fn%NWORX4+5HlL?u_p2|Gn2sr7@rrS=;rhdoe#!HBo z&KMypR!mJwR32KT1uMEwH0vZXq?HB!+Ld4*jx_9hJ|Y1%5*cz9nY~UOc6RbYJE8*$ z7?7c4*L@p~+8EwPHd>3Kwqu<c=K6KOc5S(ztN?{+Cq-uR=aoY%+=N&X((D^i<@fT7 zZ|@FzkPFJBFv}*A7!%nele{qD1YIn_uki`dbU|~l1tcWQhQqt-1R%O3sJ2Y4y6a@m z3I5DFY-uiJHXSo*unvx+yTs|05I*H5h<I5zG6PjH28?APZZ8Y9uscMRqzKlm#f&gL z;P@-HZsrF^mt4zbfeQ4#<E>3$;YMN59tP)Wg+l7`R-hZ>#2Ug5umS@q5Pqdn7f#hm zhmM72C;xsVm0qkg#bbq6yxOg1EG%@}bZP5n<X5D1Y^Bkr&!hNScHk%OB3fe%>XrJc zj~-9RtyNtjLubTYp}){gxMHEv09+*RY4Si%k1{r$W-&V@PS{@63$wiEh!RhxZ71R1 zkeV>BgW#ZBirl_?kQN(O4x?8}h;nn<vuEOcvj<94hf3OG`3tHm)%N0q1KRB&(6h_D zOqFbZ!qdFBxw|{2P5#nB&g%>fs4>k?KhjN_F3+RdYV5CnAhEsv(NX2yB;+-x+j<iv zTp`IRZ8?cj!Eii7kaKlEdgv_@Pl*z3HzHPu-8`#q1V%|L^G1u#sf5N@n4NK>5otVe zQL>**R2n6Rg!Gv^j^yHK=w=jD|H-tAdNAeYqGB%PoA9?_&JFJMhV+Q}*y_|S3}alp z%j-T6@fRqTBXea^EBZqRDlni;_{bK;L65rk>D>6mZCE}8qo`_}$AN5ZU&GHMux=SU z)Zb5G5-dj;eq}I%M0wQAct+gV^!EWIEY_t^s;<Gd99_O|LyfAcz^eRD0hLvQZiP0k zg>31k5I5p&<syrtckzyBH9q_0YO%9)gN{DANVJR0h&2Yi%gI&v?{_0Lh!GRE9vOMq zz7Ue;+TV$x$O^2|8wh=21W^sJ@)u0h^xdTi75P>f{v+-RqG`V%;PT2gNH-M*+<cB} zYd8hmO1Bh%OwPKRjtrdpZ~((T_O_#u_M)RlTqJlE<?tYOM1{=~UzsptMSzr>3su<C zQL;8vJ)H*D!qWufu87_*La7#6cM64f*jLQ3Feo1434e%%iBl!0Hbmoq<b@7n?YYWj zxIUoL!_Vtg4=3nvAgE~7x2UBhJJ-KZ&SZa25!L7(u$cNPGks;hI~B0LG9Jaq5(<|5 zz%$7lj<gCHMjR|L+Y{l!a?)eN1&|CkjA1$}oZ38CXtH8U4O+y2kGr7Rmm3nB7-ofQ zoHb`NU-3HeUmpY5mk<^8)2`)2W9n@OfZ^r`2*GI^XHBp)Dr`%q#<b_nIJT1tDB>~9 zTANfOzQ54}@m03u$DfJ_5Jd6F(^i~jlWoWCh=zq%v7`VXz{i?w-Iy^b*am2w;EVdo zDC>PY-Ejr#=oc*o!0wrL4_P`~X)J2*J<n_J(CYj4xNM_}@;dCJ<P*m^&`9mKVyEJa zoAaKe8#OnJ29pOHZBAV}3v~a$@vOBUWu+#8KRw)XYx={69$`X@{9byn{9&y$YdMvY zM^%Z-IesWEsjEd7ei&w>q!S5KHJs~#!(9YstNyY?nLEk^jb`d8z;4=Yv4!1-K2GsN zzC4J;dMc1J+=Rtsunl+1#1U0>Ok}-T>~inzc_b|qaqh9VLh|VS^pdL&9aO~VyY2M` z^_dU}U-vL#4(t<O&oF8V(o^q^<QsrifGU=}g{`o<HN#ODhd%YRuZ=>oa2RgcM1KQD zMV-bEg!pxWspTonojFh;XS%0V+?P4Y^0tuh5N$ezK2)Mp>^QA+CGs3EI;_!5zKf3| z!SnBKN_M60k0exk+~scQ;{}5`vW2ruXpU><=ToL!F+~X#E;y>cK4%D-rBsmS7JiZ^ z-H4*^63KVJI1V>7s|dQetM`|&D*X|JIJc#D+c)FnLlPvyQi_~a7R1p}SZwZBbvJN{ zWbU!P3vJ}lS3I?dAbqk`@GBFxI(xw2Q0$bgG&m^i5zyWFjmdY0-<Hrt`aV3YgJcv4 zG-7%c6NZ?pL-ghg$mmkr==tWQy-&Vql=dd2_@Vev2T;`+j5qo*HdTLOPRqygCf8C+ ziaG*mY8)P2zY3Xp$c*T~@8b_-B$2!!_{>9?V)|~VSbYg|b<a{{q6=TjVgffSAiGMf z;mzpAV8%XcJgYh$Gg<wL2Hb&x12{*g1@QNEc&edB(b(bs{yNv-8gU4bj5HOLB}tT1 zWKRaL-1lBpQt*Q*2!x^M@@iUFoTF4aIeaCA8`So^DQulT&I+A9=Aeq{f`gzvuG15- zJV6&qL1jztd{HP_QEsdkfMm{@ZBu7O@QKyf0{%TR1uUzoT(~eC<AJP-ynp1ocKrEM zm%2X!ODxE&^9$>5r_ZH@0Z`SyJK}wPPTf!)AO#f_@ub>-1&!ys@(<rd%)*UWjXR&! zLVx@Ed$gjU+7Kz)Yp;*IehFu$<uZxISQ>Tk{y5BeR80?f4r=8MHFQf=?n=W1E2HUK z@|$fH%wRx%aHX)=9iu8t^%k6sK`LV<gkJqPDwh}+=_^|?T@=(j*rRB0kRmZ?+Mkr7 zU{bj5xKJu;3Ic|yeAGL#6dYd`P0jWv1pnZxopH_7$`4K75RuE&ntl+*DzJ~*_Cqnj zobuxHCz%PgdKRaf_;x@+aP2~f1ftig?!y@1F4py}EiCk_G`j!a;!$9X!A`JWJGqSb z?QO)WMwMZVvn8d>emgT9$29f!&So1CoUz(_`)ml;U=Q|9LjKs;;r*Kv-wGs*6!-(< z{yR!eT*3HrQKDa&XXE#ar{nJD3h*2E=>d=4A|kY51c}ZW_hEhxcRbKsz9w{WW0t$0 z*VS~_Y#P(mQZXlmU5>rbQ}Fm86j;MElabrbZwo7tBUXTqX_ZzAjH9sw%I#rdbh>=G zq$_cK7PtrysztUx_(m$kN@=esx)*}946FF_09iQTnwnIzots=p@Zih!L5i4)J?RK; zn)#33I0;nnkwDIw8g?VE^?Hf40Z99Tl}-w-X$ne~fE_K%evUS+$Oc}0wQccDJlfX` zLz@cJZGTvt0y?&DLypRZ#x1u~X<g+Oi6K2MEy^-?D)Q%rZz$@jr;Ii_z3L|Wf|F(= zL`syRb_m1R<E%n-(%e5`Qf88TB|Rg$J+4yR^ZSziWn6JiQ}KVEFj49u`bye;!F0cq z1M5&LajY3q4^wtr`esS2HJO%m$JYq$2VDyxA7bQL3^1sC<t|ZB5M!6<NuUIMyu^v# zHi#O;dJt~axK_ElICpi5+P-;AnuqQ~T46}53p2o%&#mN?VLy6XGj0iAdq7LdJ$Qwf z&4s}b2C%q33Bt6BFQ)dPrBN9KWMyB)v%6I2yOyYZ+7(m)c9q@zMA?;W!%nU_PVzbT z|2{qPi(2-W#2v7TVPG!$*fPnfIgTqorEn$XOz(6}Kci=IXspwpQe)5c?h6!9s{#L3 zGbb8NnE}9=_Vdmj)G-JV|B%J}nc05Fh!CE!SmM?;=ChD#l@&xY!Ms4^f6p%kLuAoZ zf}NES;gie2>+vF2QwY|dr!GmIBc!m4-)3r}-a9XkA$a%;0;p>jD*dao=scpJ*qG@n zw^v|)_a%+Z7k3Y+NYA#=J#PTq8ktd-kkYo6^k2cx{9I(~wDDtV+m01SK7dHS+_5bZ zVVJMmAMNtCL|4ptjy{sS@-mLj<>ER4e`JHXwPyv2E&P?clNx;+f{-wMJxhzd3Eo3p z@|OJSDLb#<P*lN@C=9}=H4`>B-jnVM$v&?>Ul=<2It2*TXoIuE%AEZb47xpL7`l)T z;MRaN5^AZ;CzmOaI4eT;_h?)V`!R!~feOu%z4L_lk5YjhWXm?=>g)%cBw-rw%#(pu zg=!jf=?Zr;2E>XRtGZ;5HlYV#?$FXLH*#d%aF_p4c8yh>L{=`VV|Cf?2|b_I#oS-$ zm-=@R=)BbN>d{3tnY;#x*5&Z?YOh<f>glVf6uFe(S@vT-P)YlQ3WL)p*S@<iuI#Li z-TXDqgdsGnWna3Y1=cv-E1&5CFzWiV?}y{kq^;n~(;<1=?ETWtuk5tb{1?Z^)1R|z zVDQP$)!hp-8z3O~0sHCe<9)5#A**0I4x6Xhk;=3&X-1e)wOp;igvHX?+XTKPdYJgP z=&dApz1BJUJ+@l3K2CS~w3Gp0=X(Za1&eUGi~|HC;JBJow4-ut6-hAwGu_|a3K~F} zuI{ms<Vx?v=08%|!6VpPF;!LP>Kye>m%e6Ia|qC}vi3_csf*P<K6aYpo<&y-&9|Yd zvb2{COGR{>KeWV56s&~%g3ghJI$fDjIieNQ;+t&<vP-KBJCIf%YXzpi5Gm8Cl!XyY z-3oh1bmwpNH*CO%ol{Rs-@-~33GF##pvFpdYv%9RP6N=7IQu0qfu2;hZ($(e8#_<k zWS(X*l7mf?IsWya0cWVb1T)f38p~8<VJv4nS-Ns?>hlD6Qa{Q_kk1(7Q9>b?=|B|H zg}6SBYCT>ziBOov(XZF`_PeX9QX}8vi4|(7Drqs2(Sk6(itUIFSPA(dSKF8cJx#2h zUeFS!k$){6@?8_myb0A(*0=buN1S^w7Ph#b(SL=!jRgBx-yP7OoekC($egQo>>p82 z3+*0X`m(df-}wG1Si4d!W7zQ#zn#XlPOutZ3}Zn#UC(23eBx-|<>D~cBvkPG-OAT5 zIM0tE3sZHQ2kwnIw36g>QIN&rE;)KH=b9zZ#e2JTWGe97881Nu{4slum7I)T9g(Vw z+-EJO<@sR~YdyxR!Me?x>=G3Z#l~$PRnY#sR=SXmMyjT&!+`B0m9^KP{xwDqkqzAb z(?DDMW&C_>YRFSFP4zr@={bA_698>z1)i<09<{=F+R0|B%t>XSVdIl2M(cODZM=IZ zI>EV8$2otx&>Ypu8k%_6T`ok#!3R*l=3eFL60Bha6_^Mi9;C0pRO7904YaDeaTk<T z;NFPfN?5_NB;MQ~p9)_P-@gq*_Twfv{#DCJ8fPSP9!3|Ybw4pU$PZ-^r(SS8_-N6_ zBOo9k<xuvq^cuJi+02%YPbb>oe?|1(0kqwjNzjKKC&?G!wJy6Xs0!7Z;YW7NrgLS0 zPnh}FYX9A)NL?M1huVg#{WS2)&D;0i4#)&vf1EE1(Tvo$lhl*_=)g}PmzAu1Mh7J* z;V)b_d=2$^sd#eu>^)iB34A#Ea<gAAYxO)iQXh(W$;-E-A-ip=3ZDCMZ55CmM@8xf z)s}9fsEggxX0?W6B7grv0Q&}%waX$Hw?$u9@Q7q=Jh-C;hZZ<n|5aqdcA@Igoj8Hk z&2oUtBuu01XHI#Kme_+Y4F@vdon2P53+o!JL-I6>TG9=Aznngxo%yDp?kiPB_*+~; zMSRZ)S)-hcOl6ri*$(7}41>$*kok`{BOuhc+##K9DPpHBeILT{7qhtUos!>=j&ZXr zbZ{So+4RpDWVC(OEh_)5|KxK1l+&}ZNMBpw+ezYe7@bf%jCtv6Fu$y2a)q4Q2-(gG z1Vah`hR5d4R##<G6+*kR&7j&4%z!@r<-#|h@o!y&8E<?59(TK*+P8D~=s%}xNZ5nB zCy19k4_`&s{*{4L{xaf`7J~{??S7k()n9A)@_$XQ?s(Xi^3BpcDnb?goo^^|{=RK` zG)Cy>1~t-vFc3g(zM4FeslQu+5OM}#a>4`@XfJWEIMqGjJY>1?-u9-P&cA_vtkC9j zlLEUUjYdc>wy*1ZVFnWjUB)oS2hnD>T0=@UR_WtxYIVp#j-x3K8lE5;eVa8GnEc|G z|Fgv?t%GNzZ#UQtD#>Dub_~Z0qS8OtO0D%)NTHR;(7Bkl=W_PPgCv!=ijS%%&>pge z!w!9jPM=aE)pN|nAe@AT%E^yZhD=nz8QH|pyN);-OyOTEL1iOV55*<lMU9~Nsl@M? zL!1nSTM2-5!N$6FMr8mF3=7Bo0s%@>d3O``wrVXb%01zwG>xBpYwF5cx!R6=>j3b@ zyen88LknB+vmxz;w@}*G_I6KyrV$*>K8BQ@LEL$dpK{6FEbZeN(~un~-^9(gmdHyr zf;wqSxsj9kFObL5)Nr#DxC^c^W!J=0m(di%9AAG83jtjoiE0-w@5PnT4FqBtZC(g- zFo0Ak+#*=vOr0gI&Ey4EX+~0ddT0|#U(HuR)#&$sP(3&$fVZDOfq>9{*tP!?DZ|mp z+`-k=?&q*KqAu@v$br;*sR@?@Vq2W7SHRCEth(Ha|3`=;W&_$sN;NoH0s}cpO`0ZZ z;icd6jd*jy=T!%vEczhZ`|(7y=emSNNy_*Rz{Z!*v76+hMUAJS!#TWo#lz61>;?gj zO3s)m49~r!6@sROyL29N?lvXAeQHg-S-Bq_WgR?u7ftC?4&N5Vp+bDbo3R>gJDeG0 zdqjUhl|9!~4VX#JYmWh#c(iW~kn@QNtzu8(Jj63kt0|8;g_o+q^?mPeVhOlY1sLv* zH1rwl3~*n6c4>1hTSb>;s6fSwZ5Z=Lhjtq6mCH6EC$<PA7{?F!ZFKx5#ob2D&yomq zungu7M;i9ufn6Hb+Bf9aB$FFVM)Ece&&nYZJJ}(Sfi-_g=X}jR_C=B%mtQw3xWf`( zf3|b&)iZlM{ySUg$@|eUy&|ikP&gwphKpCdkdL@aeo9wqF#$N-75w)hX|qe%k1bzB z_4il_##*aQu7SR);tveVM)g~pW=44Z;ZTpk+M*O^c6R4(w$OtsRg#;z67%Haa4p)K z7qirC8V55PJBH$&N^zw!@ODs6Lm%_JluEE{VWCQsyEpj<a4oW~)f=kLID&!K(m=W( z=@Smtw$=_2ei2QbkpO79BaczkIfgt2p7~sbSL+myg$)QNjCLpJaGu^q1&=wqlR}VF zgh>%1I`w%b@vbXn2;?>J2rC~e`T)=(VN<+Oy@_=wrl{B5(0KKnD)w#Cvhkj^&S{y& z=xz?RU+L@uB~@3wVM&Kp?KfrwEpQE8r#yYn9;D3HCJt1O_7ABMC)TYw%Go(*B#~D> z{HxV1-oN<X22o1%glh4AA<t8q*(@LzJfFu^VV5vh&QE$Rrb-ZC_?i$m3c7bC{|wiZ zj?(&O)T1G3Nnd%W)+ZP!f``~6{3xu7$b^$b)Z5ZzFa0cPsT>qB`2|#6g0q%1gUb4b zB!Q%Hv79X1*;dgAsklRcYi2`63+ewt26DtgBnW!4oT!+zx#MgngQFaPkSmwETk33< znl0R!8b`^B6L(S$GlN;a%Dwhrq62Qnxn~<0UdTb@tYe|V!lss%cp?>)T)p>A6%byg z2~vjz5Mq;})4BwsuoK-;1ey5!J?bsP7VTqK+oQ_k)vem=`7-K@<Upk)c6=~>mar4M z?R-&cT-Bd{)(4;`qP0Oe2cdh6^pgy%lKfUKCfU@cZ*5%GL)jy2FwOtFEO0PRKEe>0 z>7(hYvY}v4jJhry4}QtMu^@Da7oK}Wj_0K$f6DJDHzeM$cd*}=wU^n<6xpPa?sZ<G z%NW>gSo=F>&Ac<U%@HhF8;1eLdKPPMlv8-DCH~)k@MWwA^yWr>yd!OXRBcrM0~GL6 zFAPl`9bDav9o+s45=c{>ayVpy>$#-;^)e48^$85z^xNR2fw3UQAX+5Ro@b#>4jUzR zCD3rv!?sn*LwOi78^?u9bh0fM5ssP1{B(}EFW_Nbn}^CB-mtfN5VZkPfaJ{xF+DbP zG82o~$4jIFP8fZjDa5>jPaGFJYMTiBE)1+qnoBmY!Q{>#=v&Om^YwZK^^kRHLeFp` zcxAPBSma!jC71XWT7@nc4*dgR7}5@=b{2hOp!1AJy6=nqPMK^IDv+9;rZ)`=^7axw zl^@i5qc3=4)|l1=UKg3+ycRp%ilM1ZKGgV$9Gg=;u59o~{`x$EocHC}%-+RRnnA_1 zKVdPs2e=b4j6D_9>5NqJ1(;P8rgn@(r+!6-F;T9v&bMl$4!J!W(%|zd8Rn4Sh>&oX zOL6o$BV!DiT(O+M-!=Ff<(#c395pQ&kT^oS^P!b!jr`)<QPs6@TAd5x{m)P%BEX^H zdvZ26@rf``sW9dZTE?(!<{#L^Izd`hBQU=5q-1$D_jA}FM`*fjaCH(1@0NcD0`B<y z(*76L8^gB^{3c^@pZ3N`wW^uAS*D7lPufCXu{Nk)!thowf8tpH)3h<IP7U38vCn_B z59NRG5kN>lK++QbbNleL{*j${{?{Ye#<O+Ylz8Y40N)4FJTHLq$TIL9eRtx?^avYI zR!B{O5EhzWo+Ob;;&Cz8w*R~9_#%Fjq85S%sRoV{&CAMLpPQSh&%J*00OISMXZ#eq zbX1tqz-gWtE$O#KzHVx%f27M8!C3M1HvU1@ohQe43mz=|+u$MN7~DnZuXqw>`G5y6 zf!|5<z1unC-zV9i82{K;a1+BrrX4Zy4%FQj>pcEwMn&(Ysz?MFGc}WcY6VcmPWj}{ zeIonA0lgC7xEsgNRH1>?DTc~@H=aH$#i5`=8F=5m9NA1Ym#@tu?7n`iIR=V;FMOHu zj_*r;Xmpr>$L{sFuD=fkIGxyvpT}<sdvm445ToxV6ew48g4174Pospr_`_j4OuQag zYeZnp5k^-ilGt@?h_YcLP|l%u(C8C?u^fcib7!-Y`nBSB;Koz~ePF*I>pGO|$h2lL zCc8%>f4Juvy|9%3uAQC=2`=0Y*l1)EjKYhtwO&un+nelelVLFGP*dwBk>)crCdz%= zcw&`;zu;=XLl}hLE&a<*3=Baw`PGc9z>$V<(F(Lcn-YnE4()QgrR<=Df(C<ui!?wZ z&x0#mwPlbZx?ME`wn3>N*Yv_w)lL<6d!7ZuUI*`11l0rY{jvFr?qSTifPW3qc-(i; z2C@T#D=vTeM8vZ2b`?BiP=Nbm?!XVWDtLmF?^CZbnat`m)A|xRpE?QX!6|C&i2@Z4 z@VtV2c_zrjYQUf-`+1x>Yu^;pNj&n&ADH59TsRq)!2UQB@{6CdtQYwu)Lzv{;-Y;w z2`PHx?UT(pz^FN}sHKBJGf8DEN_7X*+nUBnD)$J(su46}30+W?4h5SK(?BAfo+=}E zYAjwT4R4n$m{!Wm!2PZPB#y$duhKz792R~Qb391zx(hg@u>4yVwvNhaSq9gQvx;xg z+k%{8(n9$CI(Y6N*FFR!=~W*?OC)vI2%?3*p8w|g@9D^BV{eM>A#ROBD7_CQ!fe32 zk<odiun;!Q>e^6V*qu@%OiR+jPkBGG8=nqe=2b0K^u78!Rv98GA1C;6xLuz$VCtfk z7o2eU3MKaT1T{a{5471Ys?AhTTo-D$4#^PFHam_azKVnAn~WNlbL2U@;uCa8!Tu*l zwu||Q`4Z=SDC|%d+q5MrdD<L?xdKN7S-g~E0<q$4PCy3LuVnmLrBz`b==+<w&;<4) zge932GCJJI{*5Rj?9PN4?)Z&AJ64jg575@n1NZ86I4!l3*pwW%MzAkU1fv8T^}<$k z!9tptS?6c^V!ug$dIKF|hTJ&)r_cA2A}zz!E{|O(#m%rSjbl<0w8x#U)YcfTdw^A$ zKh^w#2{B0lNL)~zj7wfrZa@P6p37wW+!|Wq^Xn(}1HPptm<_>Y66;|>JkL~@-4tl? z%@gGa&d~~dKPWb`T3y6MGBsLf5@UpeB7-!|&G+g2{89fY$L>`(>#$CW1I!#rjLWQ? zmuHl^O9E>oY_Qv}RS}<ido(cKn9f^-KDutNB5%Pa{z2cSmX%f#6RQr)=jMS(Qie2C z2(Qfz2rOLytoiG6_<eWSSZF-fVl}Z2^zHqT`$Ny|#8FcCtr&eTM96K8K<W>iHSYdw ze>S}o?*9H2g8k!x7ey#seLeBqBk@{7D0%U76)CzhaV?$&tXJSdW?cR7`{w=J{-!t9 zTa4#F61*M8-GPiVDtE$J28dn6e;gfHpFrPVP;;1;z40YE;olA%I1FRW)YXzg(Y9R^ zO4JM;IdNDd!Q6oX=D?@IMt;1d5AqcIZV=cI%bNS$`(R`pnQ)Gv+LeM`3uCGgkGIB5 z1jsQ%1fWV&K`Kd6pJv1$DRVoKuS@Mq>%K+-6{PxXObixz2*J+qx9*@jAK+rT1`xJZ z^$HpB{Q#ywn+2wf3KUh<+c*G^9IGthGB@ZY)zX(4C)E`4HB~*d%wEC`<{`PMfz#NT zT(}=PgmVjmv%$sgbY|tPZd-~G2x&bpXEQh*w3Z^U>Wzs9CIn$fX<)euV2=hIlI(Li z8hH2G&A^N8#Ea`yCal-pAaOayG842i!Atx-YqqQ}Ty)Rh?~%ACTmJ*CqzQO)q|eJ) zsb<ch=R|$~Pa8v|>S@)AFm6a6kV3A|!zTw?^;G+>TCO_Om4hSvHB<y?49qcnh?FK) z(6!Hr*IPq)4Qm+S7fdxXs;_@(U_=JazZ5LPR}4C$n$)93h_FbKcijuG*2>=&ea9bL zZo_|<Y;V679W<6C@ZTN_PW00W%d8lrn<gBpWuL)1W|@L%CCZ6g`#eQcQ7fO%fOuFT z*D}-eU`0s%{=(RoRx=Sut?kL~S(A7}u*p9|Ru`j=yVxt<ECvM+aicH33KnZw7f~cB z5eq#j3wX&egK8mW<FpF3C+7%~Uat(-(wuDxGDjHgQ;f&eu>`LEQ*slm4QI+6Wyzbm zAf|v>kE1k*Hu^pREogEddl+F;B7@9sV&p3lizlo0o(i$Z0Ag11`6;wG#L_MU0Na$A zX49EzbzUhUh>-?eEcQ7dqT3i3sbyXSa`L8!eZMA{+QW&W645+4YPvsa%M_YvS{$^K zu;Jvb@!P(152hz#P>Zxr1E-`{HD-R05yW236}h$4)e{$>Xm!^Kvt7el<r@NDshjs? z6P`!b)~~f~gKkU9sI)y^UR#uHqH4M^;)2vZXK-5`Pjc5T^^U4hE+JR|LZ2WLR9caS zw`a~}+i+;i2Ek4;JOw};ifO#HuNPY8uMs$2DZzu;EpaMd?c{AI(uc|6P6`9PDY3-9 z0;!WrUi^Zv^65nbN%e(%nGxAyaf0|!^BZ3qpc$Ci_aLnitNkemW|3>_PIV^R=ItOe zgmC{FumlA|`AoT5lubbi^y4y73pjXXwL)ayIwGGfq6dNL2>RoerFxkd7$ltxk}R2i zmz$g0=zhKrpIqU4MFHMpD%R^S-NY}or}19+6!GZH4YZ{YX&!sX9!^f;U2T@;9|??@ zJV?c>QdDFwR=^VP8uU&A(mBarg%aSyeuu%P9m|ry&>EamBlBR}SvsHoi!BFdigWHH zjMWGl6g`eq;3KI75SHV2F$R#>b^JlM-D5PMcbUblR31v}dQF#JXFsMv4uVSuI=Ch? zIZFr(NaRJeOZh@LlCO@_U;NH=nHZu1L4KoUt8p(A18JxL#$u)vpXrX4(ofvbt=gab zL1*o}{9oww8BiN7cJO4CS8B(6DTG83r@<^>g#(7^=j?>SP(24KbX3u5seRFryirw1 z^(Jh7vcH6_6qZwMD!T;K1KAP%tMV>v>q*LG#5JE8Xo~2cDSxTnM|5K%C)2yS{5m8K zpn$VAVA!uHpxXQli6_P<cEQ$k@^N?z4!DhIBi`O?!{s9Lh`Cz*sP<gA<k`r;Axt$H z59?#O-RdZWeAQD6?Z*kP;5e%4Ww}{?stE#pE{e?%%36;fjb{YP(I35>4CSBrvT^`- zyo@2ju>yxpQs3|p%eIl{)ebB)PV+30h>6;2RwsoaooztFU&HM~XOQVP`4=VF^S*H| z9;tI6r03U&%3H5qDrVbO=a5|V#~_S(<Ho5s^X@CzF1q1V@&irN*U}rTdU!_$fvtyR z&bcz_3I?tXl{5?e83IP_?Uxf2ccm1?1(jKZgLHQ}nCJsH`^J><EC)@`Jl>KE`c!}t z?!JuA3TV5R$Q<*={wUo*j11!WOL3+wSg?T1;-c?b=*l2N;zw)*ACE&3<q<zos`mm+ zQBedU2Bk#ZBb&oJTLR5t$)`rp=qK!f;+7#4jA$$vXw$Ph-w?BVLGrOZ%~Vx&!yr?9 zMM|p7j+SNQ0}5Fm`GCBqk5p4d5I9Wd4WMa_G*WmRGQCZpTX<GW^?<ZR5`bdkCfD|- zd|?xx#~C!Q3+Yd}xA3ykO}O&JLYokcwt_a*!4m6E-`+XLeQ}S}z-Cb@`Hp{<bIL0= z&v<9YC%5tT`pi_5#~+wDnu)q`9O2HPLdVsx8MOzzR*zertCQSS7@*VCT62o=7o1L7 zQuW-obf+zyRr?Z<w?!-ed-Dy)3Q>l*w$=D$zNR3Z{A!OO*=BJIJrw`wMYHGRp1;c) zwhuC`9wV|UOIW3+4wQyN$+YyfR9)gVH>PBlc#>n7-tnc5i`UO+ij8b%%fCoALurPw z<J{R*&#KIj!oxy_E`&tf4ADv`(5Z!+)UCUFu#}1~aHL%ID|EUrj*o{0q$lPewd8}0 z2-p#wBa)*(K$Iz!JjDPs(+NZ&4Wbk*_^Ks!82s*OHB~>q{(*C$m=N}Y96lg5+a`A5 zmDWkxk8i`XCV_j>WgL4}D6=3tjkefmpzpHEZRqoWOtFF7EB_%boi5mE7Fc_8+lOmh zZp+HtQm}+PVal7|?3d&H<z0jys>Mj|i)DRAA?XDl4rBlTpk!N9<RoanY6!@N5o2S* z%@!+`aX?6_TA}Bw72oOd%ov--^}%kVDFQ<;WI}2&4)7jj&37!bD_(qIJr?lQ>F`sR zyH(wa0gZSaE2!$?(u_np_JfY9JW6nRsSA0tszfKqdfWq>3Ib}Ty0_I&^n+VjCNdXI z2uyh&$-Y$ZBreN?YxUJM$^GHX?x^zmpE8wK#_TNy=Jdv{A!yLeAePO$64-Xo3p74b zN$uueE*lXj@`edY%FXf;xu7Y8)XB#tAIB!I2cLjT15gI-l|ODa-8*oS@Sb4;m!TIU z1*(@<I;JOUov?Z;!KHN;v$jGyS!HqlN)O)q;84iJ1mh3{#b6x1O!;<<P3_Gl!o$-O z_N$=M_rTtY8}EBS*mPkTxNnJj@WQKiHkV7AWZ8a^TAkT@w=z)o%E*oeDDBF*#Sa32 z(eEJm89Gulsk_kU%oYyskRekZ(^JlgW<$R38~;yveb`C-^IgH*T&2k~J}d~_A0etF zsEe=HDgX|2pvKHI-;3%gc0|b@=;d91j}K{iLkY-R9I~g9Jy*FpX;K#2^tu&uW*)!N zNRd~|)(Wd7zn+~<_MhJIZ@qo0F})*+7ecR|()QK98@uYt>L>I_-RSASNm2SPWto|g zDvbm7TF*5yX6}#u_R}R&M<yAuH)~|=t@pn977a=tcb`eA6!dll0zi)3*R@1F`lcK< zeh_7%N}xXw8GiDWM94kOvt6!Rc`a>lWxLx`yx0=ENG*|(o;7yfVnYKO5RdMIr>(Ml z7lRq($tiZy70dI!glhfYWmXu=u@weXeC|SeTQ&<le&n>wvbO40%0*WP7a7&t68iW} zU5Vu_F&pP9;g}W<-rPVyV2{}VA@OPieXo%&nd_>5aM)-!bHGZwNSA6ue-SfQXdY?C z9w{`_Cr0oJOb{EMpzRkhOu1Snn$$mCoYdrCsYRDX&4OY1z?e{`9Zg!zsXSM*e&A-@ zah`VZ=q0QqgD0Nv|2QT6T#tZ$B!U0;_rG~m{D0D`|3_>H`X3e-ilIbQHa~JgaQOe- z;=<j<&d%C|^}p*0(=;ys%VO!fuK%E*=$F2i%qsk_(ZE0tg?&g{T~fzpq%o4!xt(jF zSX#-IRp3`VqwK7y_!sd4{%yeP9)Cueb<@?f!irO?4!!VQt4UQTC?_uyZ+Ok8a5KCJ z@g)xIuyjL`%AF5yFg&JIDo4R)S<<9j0ieRDm}{l<jAGydg{G28dSX^$0_MSQDNDmR z5464pp`kUsh&{uXN7`V39*4?#Sm$T`xK6JbjBfx7#)Y<H^oBkcA;`pCUonS)EVD@l zXrnZQx{w`qdXkew4vW+NNgI>x5yD8g>l+vyC17jzOOV;r0}JM3dEk?JdT%<9cHtOv zOrVtI$>iF(7PY9))9vX|Cjr_l>CTbdy%0&y&`hP>VKRS+Q&XRSgMC=>8?k9CWdldI zJxH;XH>y1H_bkDY<!7r@;zZ*`DDHsnAzH*?kh-yr>9s`f9`=(&7pvq(XH_#6J{v_D zRvo5Iy+$@Wo+t$4#!zgxdcY?i#+b3=2pu^Ekk<$d&m6n_dl~-xI-a4Q#>BiEJ3csI z^-qD5W&mf2E)xresGRJKXiXg1{u8LGe+P0Vx>7{yEJKwjZxfeq6xU`VQx|t&OS6IU zk~7$(YhYRD;0(iSio?$@xT^x&YD*J#!DmJI?OIE%eCinR{_HNYuZUV;2t_@9gaf-t z;pK=1>IGb83zg$&YePw5`<^DhzdctUDCg%s)VkEKCPRa`##D=nhQse|hw#DdLiZs% z7c$b{Gtxgduvr3@x<*Bp0l(z&5DY7f4HTZXh01s>>;(_!XSvikSHFd~^g_`+cUF~| z4<WS3u*874L_GUS&X|kGUi(|OvTI%ZE>q`Jk<gj31fKQ=>_&Nd$Loem`XQ&44G3Mn z1iR$1OKoj1c~a%Z_zzwtd;F*T?OOjsBEV{t`W)JTs5MCQ)2+9u=Max*$B9OZRfMZ; z&i}6$MwrtgC4~qdPRmUayVV}C$kqI~Yc59Ayhf6ld8VysT$Kwix^DsHi4tECg^_Kr zcTM$ppa+q7@LEd}!EDqxZ?5Q{(ioUW9@4Lb*$L&m)taKu1XdWL>6-<k_=M4t^m&S4 zN#J-tY_aK>sonk_!VihY?#X}sA`2bcMSHeWl}<dcrXTr(wS@pJ{b$~ZD<nP_2tzgp z9b<HSg{aFFU1>ad%!Mi)-n5Q^wb8YHg1qdjKS`JkP1`EOY1Yu}CY7}l?z>Xnv$hTo ze6~EkBG@ix%z`ZnXzbz8R%qPzb$z_hL0shS7UvlK_9r?hK}FaqTgt}Xfqs5!gWs;G z3QQ6J*}NlLH<C1Kw%ZD^4CWV|5Ro})!}tt_D;+Fbuk+g(99Ok)os>Y+-1sq2!pLW& z=^VV2i3E(X;#|DWO`Oulrj{RJ+XCM+%>9F^m$W!+`8}=YurgQUCiN7Hm#k}JMB79K z*~qzXDA@^{o+#t`Z}jlGeRY7%<KzyTElwASOJyzi2wbk++*VC#<)U!#B$FoG^mdQF zt`DWI@``1|a`|4};{dxtMbGS2_!7bnv-;oXvRZbS=%3JtC4|=mk&w!U0<Mm_t<JJl zN&U6bsI*<L7S@jQp$c*%3WS=ahBUTs&W4)<Q3ly~SgEBd)H74OQiEWZwf?3i$c7Z8 ze|Duhc)HEke+jEjJDFf*d>yyO@Zw}IEA5S6#k@xSoNj}^%I^rho%KJ`I?<-rh*v+9 zWwKv?R+{<+yu`oseDP0UEn%QT_$H#7_cKp?=ew-wgs0ZdUr{gQ_~21v)I}GK-hTx? z5}XLNUPNE_Be3v)KLJhFRRKL*zh5G6LzOv1Jyr+4AOiBE#`+7Wa?Yl?-Vy&-f$iXs zAVtehiMFEy0wVpX_5a<8=>IL#rjB-Y=B94eKY+o{nYdQx#|=*c<=>5AV<Pe|8*&nb zTcsRe$lp`4xNO#;Wa7?u*iX@V>$<9Bq73$xksr6O(|w8EWc0kfOOaw2mX@dXmU)@S zWen|f(j2umwn=1i4%JN>vGv(3j%iZUyONSsJNX36ejF`C^pAQaGU?)|kM0*5y{GEr zG1cV0yEJ{X;a?w8S1QWXFB+=7b(c+fC(lUvN>J|luzCBQ?N^80EWMc<v5lrL#HTi@ zE1T-7QEComK9d0q^a&C7x(u!x7gCRkt~+cI`Rj=SnQH?oMM^~q$rZ@&77KzUis9mt zq7S)h={^%gIz=yOOuG)=I|tQO-ZU$Gy_rWIZuxNmm8{A!S7~x-<k4{_0t=fU+2dD| zXH+(_>c+GI<UCmtHxP;@pGSEzR=%V!Gb({gB%)Ph``+<Z+@`0^2_)h24`20h;|A&B z2Te%Nb`utY0slS+ujE8#84BbcT(eKL)awExc@{tfwN(|FqeNbHKc+2MzrG{8deX3; z_Euu5BOJk|8dhn3bAm`T`>k!a%GIx91kNME+tQZF%p5(qOnJtAis~7#sHFT~gndJh zD8RO4+qQArw!3fJwr$(CZQHhO+qP~0`~F1C;>ASFqSjTb6O}lbVvhVUlUxzhKBKR1 zl66{lBGndDCl$e2CjA;$lYg)oBc*Y&RJu+yO?+>@KXY!6TDBVswrdD=+x0`3klWV| ze!(90$r4zPlEzCYvHc%4ig-F#TMI(c9H=np#^g0gu}N+8p~B4JBTeIsTOS@4PBF}X zY4xC~W%mu@hoicO&?96mN4Ox-bs&u)IdjkGvTt=xV83uc%zep?_Hm&svl<giC;7S? zb0-DdOasaehZF)^ztR4_V)~8zKAQEF!^bi`M$&N!Fy+m}L-Lg}0R>yvLR0gD-7fLV zxe4GCHK;;x_!kP%5-5~KjTJW)k4--{k~2a9eJ6K?FikmxA-P7~6m{GPLox-sysgVs zw^3Gx6+xPT3!M_Q^=TL&&Jn(CYz!sTS~IP<j7u#&F|PM9LBdTvm-{A>iY*#1jyd<R zNks{}G{_s#4Z^!*7;d`P*|i<dq1`DI880_*PY`D;&+~P)V!McPkW!sYM1&!uOSBOu zRCjf#TxIM+J=p)NaLt~foH9l&X^eO=aWz>zkocGN(sDn4i89g$HWgu(HyOYKKPzo! z^Fh2p4ZjXwFXSLBwsLU=JBwX^CLP#48X8MfBv>;>AzU5>E;EV;fh1qY$ZnfH2A|a` z*ze58<|qS4`4l%~D83)dAj3|a$K$Bz1JiIy4Dip720{=GbjKhpWQ6rC)Q{KLsy6K$ zKq1e9ppbOo3_FtAk~a}9CNxzOEGSA|d0%ySFrM(o=dyPG4g<7cqp>0>sK6;%5RoAV zaEQ+fT}>Af_gpjbL*#GKM8e^aFb^>%=G7izZ1kS)vAv!sHh)8%K@|Mrpgw=v{_IeY zVyh-T<)xicSUVgrIW$ELOMB@QboPs-hM2f1k#J6R)CFp)Gcs@rk>$L4%bz;yOseoY zrlG1m&AcHQo(BsJn|j;@%?WHp#7GfJ_eecx#F=T_+FCF31}TNM2QO;KVcBBA*1TE} z69v>(<q_rBex0>le+#g>xPN+hL&=pyh~bDswDw!cYB0Z(G!!U#J?>nHQT}NAkuYVG zElX>xtxm&juJ<dm$v0#bPEfJkE+3&WhRYSX76N7+{y3$Zg&VBv@=Be;??xPFYN}3k z$jLOG#O+gjZ{@wEVU#$R<MERIf#l@lIBt{<Nk2zJQ{Y0j`6<0TiQx;FfkzfBT`t#0 zSb=;UlVrRm3IG+=5bj%0Jaqi{40)Iv$|H4W*HTv<iK)A~ii+maA^l60$hMDS#+)D& z_S@_p9Th1^<tB<wNI_FIbWw5jP}vERtW(aAx(H0Vnb|}&eVv=N+OctAmEUYGuPYgh zx}_aAx**a5bA~UH#d8C-ZBZKCx|d>#sin^BEn!His<~*_iEoy-xoaoG^LrK$zOjDr zheL7AX^{QN?(C>ju8yw@4&9R?*+7Ex)o7)T#?39YYvaPh?MEQwmo0}`Td&csmm!iL z{MRN_x1L!?@l0xD+aXKeC1A&E2SO|y#V*IlALIl*zbZC25kN^(j)tR2e{A&ECBQMc zYh(cj8)LO35V<8D$ziybsgX&7S$bZ$RkcSRu%_6IN&w(OjP3p#=!ite)}w+^>=Jrh zjkh;e$gI&m>LZr5R4|zP+df1k{aINVx*j0ngY&}U7L;k8sw(Ck3kiAPlfS(8#fM6N zDwpu+*i#GUpxLGCoet}QWmttArm<_3u5)G~P^Qol`}hr?LtFF8ll)15SwA?u7Ln6P zBJ&~3;DqL*nScw7^CMSadz@JbwdzrylgA2%1OBO*%KahWYG={Fc#3(<;!yUr(baWE zAcw?<2$c)*&GJO9_o2RjnH&eZJ4DKTRyOAf=rKrwRO>yF;#|U<1J<G!!&vzvLzbQC zaW9=StVn-KL-y0Y74k4NU<xF0@(?<t2_Cv>GX)7u3GJ8=59)*W1JBkiA%rbway>>P zIBDrn_sL@Z_?$@XG4SuZ8Dlj)8oSi%Ri0kCKz6tCF;2-iOcTrC!&xFMlC3raAka0` zFx+yTv|@#IcJ)%o&k}-tTeHhy_vowoK?g_Yb6wQ<AbD;pm<wFAHms#CF;(;H6N{d? zh)Da7_@oPM%1Zx+NTxAc24jOMH+PX38ATz+wgCnAi$Ck&<1xZ@Qfa2CUzJ_c8<JYo zLNH<rEe;_4&6OoF5SbR&RNq?Iibn%1jqD=347t8Je4X_WTY&eP8rx1cRT8WY9JhK| z!rUHNmeq8oj8#YbPANP%Z}1?`7#+YqlS3$wmUPdSE_sf>S)78u*xm)3vk6+hsqP8G zZQ@`ak2xD^56=jB4x3b9X12NL>wHPM7G`4>KH_*j_mrh=imZvwpXEo9IJ4OyngZ<R zPq&>|7??D#a2yDG++lPCM?Xyj2VoU+N(p7wxxUINBoT8eNe{vst!pd0qS&>uPb1f4 z@Cno@V18_bLX@?io<ExlgZ))CX;>3Y-PTJR_e-&=6V6VXlqmp+bT-%!64(_r1w*T% z6fik@{7XQs#l&zAtKTAi2VMD1K#+1pyWRmKaGr+lshv?S&sIb4ieD1+6#6baLe&Ay z2f^a;(ulc2w0H%gos+GZ$lq<#gRMe9-(ga-)t0#JKP^7G$xy74z+*}VJXK(-mYY92 zbR^N(prT1XY+FX7Y89yynH}f4ou<dC6Y{O$Sc~-idG0G^pgU#I0e?h!K@4^8{+kNS zFBBxeI(GoGsmN&%=)ZtrjC+O6yii|{SAcu%r*B>y>nON%j6%mNBY_|j0H*2M`L`*N zbfMg?Zr{-F+^$hAwh3>^#$#3=@h;j-#5q=BEO$Y=kpY}3$7%Sx>nyMEaelE=6gVfg zLrb0|&)KQTOl0HuOGHxCSD|`rYK_QbyP|+?wnf2nP$**(xS^AZtV3+*XFsdr4H?s+ zgYJ{p06?*3mMrpZkUg*V1@<`@4?#pek6#JslSC6xEtlk+g|21@0qaxb-K?$zJEyG` z+^WS{@ZUzFq8uVu=$FMk=+1>Oi>89X_x`gf7Q^@3$1v5%q7rZil?R{TKyUV(^ck7s z_N>eg)~_*^kw!QYnfwC!+hZRA6I+J6b`i-nv!)EMFZjNu56O+UPKk$&`>vzJ)@#kO zVgCN2>H`!S*3U;w&Bm^v(!k5kVC0^c2Kbr%WfMtBtrhbb6G|azikVa&PNVN#5*$xf zC3u=djWMl#&eM*Ba=VtLMAq%j!%JN!IC~p8{W(k2UumNAKi$9Q@tUJ1+^<}<+agbY zN8)15e-IuOTZLUB9*V+qm?JSg2MT%yN7sv%5{M-ib7M7Kd%LenwH9e!_YVw6c}o!5 zq&Pa7;3U^+q~pWw3QTYsRY^!%1=99u1rAkqyIYlSoE!FUJQnjjT<#s*h+X=7NYHkI zF{~JvoikE{A;`!h@ASFr_Q|PPlzSy(AxGRNwP+z6+eP{HV<Eex`RMLZ%y%xV68rx! zE*H5WEdkCNHudM$LdK*EGt-j9Zlg^>rsPX^_jxvT%q68nJqt;lJ!<$S59k)!mPV`` z&80ak1pj#Jy53M<)LH)4)P6{@oTtg(5wp?=o7ce3F^io<o>QcZU;^CG(ww8ixrv@K zW*r`2Mc7Tqn_rmTzhWt!p^d>2vQH;dz8wvRww~Nrh)zRu2<@(}S@Y-^1WwUBdm=D= zO0_O%7b-?3yGz!dxRS<wz7kb=;Smv)yZ`Em(ydZB_MY5Y78iDkw$^E}0f4!ecvDBJ z7>Vx`UzwHgywkB<&X%a!&<Pxg{ZXJ+Uq2Xk5aI#p-VZENsSN3;IP*ayWz5DsmU*~| zzR#SqP0iaK-$SNDPqSofY(GEudA~+(9#1!0(5ShN)HaNngC;i?R!zTJbiHTlLq4Qc zH~Ppzv|Daj*-WEJwc4)m#8@zIqI;KvkiYbynryLO_!GH_KI>QO=AR>L|3#wAJwEpo z*B=>s_;<nEB*afWFjG_AI=Y^q^h#VXIAJNns14(r(|b@5zqXzI(uBM+F;d*%mSeb} z^+hwJZ>X0w!9ra)vL55fo)EQX4kb)KZEva4%q7B(7m~yIk$`FXZs^SAg|n>2f#sWK z#rMCgL2Dg6oqYem-ha-2Q+xjx^tl*2IQ~O?{{Ww&3dHyrwaWjd+5#+q3cr(-kCT*B zKpCfpL(Tlq*8J@I)bRIf-QOFR3lFEiyWihKwLnRPc!-#Ih=56G{TDH)>KNQ3iw*#w zAqxON`0oM#FU0xJnr8EV0O!B!uerZnH(3*UFKhY!6*^cLpE$lMT%TW8v@E<WH#fE{ zbah*$l!%ELNXJw2Ys|lN_<qj703ZQSkey$8y7DSrM^Yf9VZxkX`>7cB81vUvRCXNk z_a#`zr=miq{*k?m0Dt(@%iB1tJ5X*o{_xDGT@`Ox(RIwvohXSME7a6Ex9zN(((}Wp zLa6sDjelsEl0<`Yk)C$^+E|5$9J{QzZjXZnUSFphIAhgv%>Rr$3Va+1CH2kR?O!aZ zh#-(a&SAOJr9rEeeV{&+Pd>sdfV?W14PpI7xDW8F2dAF&g(AB;ejJrW$?A|$=p}!I z_v9g^<e=xwYvBuH4tuCNz}E{=ZqFr%OZ3ttrifjqEoZ&@`1qOy2E*qujE6h>WXs>X zOHdmxS-ZKc1(p$FxQ!Z-+f19TsE|sjdwB8|YSSszZ=RUEt|xa(s!A{$pmi8JM}`A{ z%A<RTlFX9Fg3{N2xsx0WoDb^QFZIg-=Af_u*7~#nw(?*FWbKC<>>YJyFyZo1azGnl z7=x>14cAkK^W~Tvph2%gz%dFU>1(Gql&pE{GZfeFbjU!Ii98tw<tqdW%_<!k%F5pn zJu#M+;O~CBE_sno-+j1pR<Pq?DJ|w=anwP!9KVWxotA>44KvP=(1B3g(!HlYIW|X9 z*4Rl0N3MKe3?FcU83Al2F%ciR$%$dE<I;grLIfqBgXPcju#=VzoMK8<RR^RPdpZcX z&00>lUVVNLHOneSbvUkO7Qa~5QSyq+%Q%V8FTuKhb~UXHRDE1O|FV$M)tT+#$gNDd zWQf$#<mZgZuu6p6Ix<GB0zjmTWE`gy4`BpwPA?gMNLTTFr$yvf@asy_f>;NVb@vzz zgwRj^A}ht5r__mQ9s%oQqy{oqy{14)-mHsY?I%S?mB=r72iYt937z1MO5e7n2k-^> zQ`QyGd$P>I{jdNH<b@AX*bhJmDoJlS9|C+v@#!9>;>fN~hZl><1O4{!_1>rp76^WI z9b!uS&0z+d?z0rz!6!?+d`v548Kc4paxO&H6p&tf$xQ9fy+=MfKz#r(A*ly~K2`t$ z<HSb*&_^kyK;T-G55&rMU(4G-Q|C>@ci#75zc5}#E7^3tHjA${Eqj6<x#!f9Hpty% z`*h#$`SEuC$lwX|JD3%iI2k#7b#;(|S3^4+n5f!Vxd#=RycJu-@H(;I>5JogzsHOB zb2^`@`~C}I^Yf}38`}#P`^&-gllv;@#}pS~;Yl=mFJqXJ=tfnI3l|zq6^41X8ssNs z6Svs&q}&6$AL0+Lx95G6fRP600v=QgBVQq`yn;y1=&uz33=)bLX_&>YMk1+a)NE?r zc1luLy<xA`#SLl*z-N$hkIOB-N#tejVWRhjV4BvT*|U3W990Q1px{5$fbgb6v|UHw z=bc<-$(*tw4yk3Oks9J+j%JFO0cF!K(Li2pSR=K=(An-k?ibzBo?d53yUI3G=m8A_ zIj)X9rI2yf8WzDVi`&y3Qj&@0p_7UTpHw12jjA7So8~dd*FnASKt|-BUjo+N<cpQ& zGA7kbGz?Kb<9B?H#}k2b*4T*ZuEY{BYIv1K(TVq{tX7(Q<eCxq9D}JIl!@&b(sO~? z_Efh`8qS(A8ZaCVky@q<H7MLB^NvU$wz>yeF`eQb06Z`}ez|w>;N<i76T9XfS#E$@ zxMZy|!lL6S6C{Y-2nCJeUjK^3F4h;KRd`~QTD{&|M^KKK6#`DlH{C&Kpy#kluaUX^ z@XSSk#Cc=Y0xSwu4x~YPB{KqNN9gduJCEXIbP}b6H@z-E$bHUD^hJEjK)2rY8sW>$ zBDtoYQA;<+i%mC5eG$crEw?u(JNKJj)R56ev}ZMpPEy>iE%c3(=#u1pwtBq&IHx4g zz@M|5MozV#jw$xj(if)k`{sLkdd&7}qz@s+PpdC!{pB;8c_$(X9NDD;!4(~+j!LfQ z1_t7JsXtgpo0b+>t<vNs#BOiNpEZ&ov{La!y$>s*e-I#5;#>+Q9ym2T<Bu?+HqI2O z1y5dWcKro9Fd*Ha(w$EqV-H)OCqbpzh6wAQb=iVNKTb7c1HD5VV7ondAzAShC4K;n zl$w#N$vQ3Qx5wk_8Hf`kV`Ktv_wUwHWJyw6SD$$F9l4FM{j|MU4f#-vhNDrS=s4rk zuM%*Bm+MqM(!St2fISVm5Yi(^&A9lYEZN$c2N8w{#B_FutR!X255H%|{4+ufLqlIh z%%?QC_82i1t2!M3t}@SFVot4r=Pp+?R!9W^jj5;Zecx6~g6{$@0Tx&EnQ*i(Phw%X z59mHBKA_iVyL=}FSHTpgH2+*%cTl`k|K07QqYmRfcoA?D?FlnzE$d(;e1U?jVdBl` zg7LIOpx9Jjp6)x%gIUIw6HGdWNWZIvC73%7cNZmdbfG>eBP>|;Ua-9inZ@sWS?4qf zo(RV%Oo~@5#VG}A3;rI5+C9Oxbh$M0^@M<FYg`JU8*I1+02>QfJCGB1Ffkt8V`AeW z|C#n@F|*p1Ewxi5LDz=svdS-D_B)9O=7Y0o&PO2!EPf`pSIS*`KbDqtsb}C!S-q1! z1H+0CHyOGbV^Vn{ci#Y7R`5mc-fC#jQbEat5}LvXm>+p{$Y)q+FICbp8Ljh`_I_dl z(1ElaKUv{iJdl7;W8!+~iQPVhO4jkO6tz2-ziH@Niy*KaQ&vffUBeiF`F^7TAYdo4 zRzQsjf_%dISQ>~K!GOqT0V@y#mW1OeantSY;j{g{3lm~LgsRI^^fHL3B9Z|oa{zFK zP)7*`=!3RF^6Aj|2Q=#3YRF=KMC^0nAFx_K<zQt7N3RxU<CPs#MW~PmQgTqIsb*LG zes3E%zrh)x<&LVKqm0j-dLT8$h;#kduyCFF8z~qL?`Vl^@|M>GFBNv!?=;IrgblDi zzeDs-el~r!#HE1bgnMG)N|cO7&H<0rdgQ9^B?Ux6Vlr5JN&4YBZpo$QJ%$A`6+S2H zTEK&nR`gzUM;vB8jd<7`wkn)Ucr#j>dC1xpVEm-S@^4Wu5eFVcCH~Cu(Cqz4qUbWb z>qtz1&=z6^$bc%s&$Kpsb2tCqc^83wLTdEg!A1n`2bSyh1BR<`+U&Vt|kXnh2M z$Q5x5Ep9GH)AVmGD~jzd%+N&7RZuf$s4VqpD1)697MaSaE#@GXpeF^TCd<W9NmYxS z%odAzkMPFIAPS#YQkkVD6_H1ap$5!Xer{!?)>Ep__|d%PCXU(_r{0N|E8B2W(XhH9 zO2aKydSSsphKOYJql9x)@bM*8m@)KC2m^;$j@fw?fC?9shrln^If5|*Cc^zEm}{O= zs!ihIj&&esc7P~d`cM$!VUg$u>dX=oq;S(UE^op{s3dd*a@MdxRcKgAER3~6?S<0W z<<VElZhdH}rK!j-jPHF$@X7)s>iRIqk3c-aIhbvJ-<;ib5axF5<UR0{n=&&f5+%u9 zCDe7$G*y*2DCvb6e{I?e_}VOsp41oS)s)GgS~w)StN_uSi?#9(nqeeH&-APG&~vR6 ztubf8tz^y}vP92_`?qP`Q7(#r1SVU|nheMqhDZh@lh{B>fU#DX%C=<w1>ULl7fbEN zms7fh^dU-BYNuMDhfmb@<pn+q>xS^>1hF!bgpa)ZOF}%GhBLdq^AuM>F=?AoOD$Pr zosFT&kZyvM4AZ1H@>XNC6^=({Uq{KZ0hRt95u<~eKdL3%q3oJJBf7XDWz20|N(r+v z8x(*|)Xk%6fBp;-IG&ALaSQJ=!-t;U!BtzTz=>z-HC!xjSLfTG!?pHm>$_iMD)tn& zjCPD@0eo=Ff!rDc1>#;`4^;yji4_r|B)GX!@oIrN(;&Y_OG_;5lUxG*km--XizwV# z#>0zVpP&0hEKsq<M#DpZz9}8}$j+;#sTDjlW5#-&0H)pJsqC3L{8vI<caoiFn}Y3D z_=+SKOW3M(zZ7rAnsE#l7*ktcC~%IghgQ}+1Xu^(c1|ewx5IcU0pD<al&yH|o<Pj+ z1&19(6|PsR;{~Nt!H-t_nOfIoSl{9Yq83`Rw@E-S2kM41WnDXG$SQMm+=7mc+MNl* zGP0=>JnP|~8x3wxUe+fG-E)>vN{~7NCp7L<Rgk7BhiJ34TCGrI+W;Tbeu=Bmb&w6h zSwxPZ4#w})zh;+U#E(8>rZ_}u*zr|mOpvAV5(@ig5h}B!oT!Swsk~7W;aQiYsRS{j zSkIct4#rhWwXx8v6I5TZ3~P_kSO^nMB`5sN=%*&kfIIR*EhXI0{6MaWqp-u34mCSC z7-u7iWF2ekp_?F7$Nl!NxA-o0q(deOzgQ<1zu<8atnp&K4OUAbWGRnMv9(~t9NbG0 zb>1^Yijw-}vgl${t^HO(E~_l;RxG7Izi}*ynRhV7#o=JU&Vith<?0`6?MYAGasqP~ zA}R1fmbD!4BFS_$K%VObuNx;ctRbyH0PJ!h=QEH%f~^R<Syo@-qfl##VW&7Rkma5| z;sS!~24xoS;jZJJgMoWNfQ`Fvg-={FxZ69yC8_i)Ti~JLym7+VJ8##f2(B{KR~jFB zddY?id@PGNDY?G0y)6%N5;LJ0rJrYW!gjW*%H_!YGA4p!%nK&<0C+sFL=2HaS6Q-K zu5LnXf>KTrpn(RM*PMVn#OMz8r?OhTwY%Hn-A0=%eJ8@NUa*j-y18-1?COahD`k82 zO`sAH3Q}Ohrat`+=>7?7fhxDbNSy3Hv`2Z+t{h%bWsu$7y}s5~cbk95)>nV=DlaUs zx7MynRS6QAx=m#}J14}cWc963baiu)xs|0gZu-Z9F|X@nBfgE4P31dbcW_V`V&b>F zetPofutTTEQM==XqY1Ok&Ml!2imt5TD(9@&VtUg4g`TjTY*M1~CxE_zNBb&tczUjY zWj0q>KL<%rO|xZnIh{)*ukjEN9LqQoLtZu+x@vK86Yh5<XgbLh*8G-j&+ctLPTHEB z_Md+pUroRNQOok??e%J;_iHMJ(Y|W}oyU2w-!Tvn))hfEtlZzBAys!4NQq*(ewb~` z-zaNi`IL)3F`L;>N%d&rV&)dgEY9tuXXZl1`CgV^Ord)KFm^;+yfI-4H^8YJZo*O3 zsBmJ*it^EnNmwR-8T#Guttw!pRJcXjFzZ`r0gSE^R=g9P);X2RjvaRW`*`OznU1ix zhR^*uh~N2~*fmkeT^twI)PU+3wj|aF7yfQ4VPANfOS`=R#Xc<&3@>J^+W(nffXnd& zCKY~v!S~cnZ^2@axN1)A*hfOEl(7(A3Go%+9x^2Xeyue}%aL6TGgxMjY69*)8oQu$ zo0T<Hi`KvVRckv_b5i;NF%9=6OX&cd_au(F@qtXT_)vv0f~7ELhcDSba-jl*BpeF! z0!Zw=HK9SX(ZQgs_{qk6rUzgGDXREtEBXP3xYB2na1n5k74wvZd%rtXmZp|@s%!3w z^B&S8BWdfpS?G+7>!At0$!#!_3f&7)89mF3a);^&CfkeuEFgEY_T|EhbL9Y$<g|aX z8oLsXzsDf(Nbkp~h`esB+FIwMYrfuaWKmh<Tq7N%2M$)U=36CJ`}r({t?m9-tla<t zw$u?nhg8d&%IQ!i@kLl2yGT+c$8Q@6iaWy>By475@Vc(+7>vA;X<>^lPW&Z7P~q3N zzI-mA3q+ngv5iN20Z+LvA!LhRbth2<HScSGUo_K76|V|5U$7pDl)t8f)nxwMLpHk> zTf=D-8;BeTCO5#V*+G`%QyEV%fW_c<I;wG!)O<uQ6Uy6ubTl+adTD3Ko;-GO(r}ZW zUn~kE;h4hp?eS1oq~|IBqi75%#cj5u9m8Leuz3^!JoYCIi?$Xn{H82E7F~5<f~kb- z!lwKs>!UR<t$$N<%*`o5=)+oZ0YWw1FhYivg~8Q_02A2A-tV?p+FFNobi67}aRc2j z`lHK2)#}4Pejsz(BVg-i5}Ze~&Dw$Gy4`Y*B~Mup{&3T+?y}fA)x*tjBWxzSy-j<H zpn#3uPA+4zhy0b?-Hkm%TZe5FVj0L#88ix#bnWse#`I*QZ?mgwSP$k1`{>X*VfbB! zUKZ0ce`b*PG1u2vdB)0TkwbdXRMXZuedT#PcMfv9a<fv@iPM`kKaU35n*}rpS#1#o zD_o?I^N5|dn_!zE2Rn_T_7nx%>PBx9g(rlD7(rVe&H&WS1$}7!#r&WHE{m1xJjfo7 zFS|yqa(Oc}0p4{S?rF%VsL{?)H1V2y`~Aj=b;0}6CH|*Lf1t_Rs#C24&^^MbZ~;%? zOK*!@j>}{uy3B<Dqh|R1c^i3)IuHC8(u8KYWXhG=sI-f`y{U477TfT-pOLe`Ma}-N zaEi8IG3*6@zAXsykWREcWj>ACR^B3-eUSUgtY8KuTK#fXXfK9GfFsv2RSqOQaqe+o zD7sg+YB=|SL>|6*NsH;Ny-ocAl~KHQDu>X*IJO{MWewsS2Z%T6?M?YF#*Zbfz(ipv zkDnc|+J~Tr!Ylf5HVh{qtUG^(ka+S0@WT2B#@rJ?0(}%HC)NwDeuMh<p>@ZD(_Fb3 z+op8>70G$eE$;<!xaMQUURP1T1mpMsk`&Sqnc~yLN8LJ)cxI72?|e>gboK14H(~9! zLhwJdYOsT-_yp+z+|hjE6w}UtM^J?lf`j0|rmkFHh1>1~N722l$67rMb$1wzqMtN( zo(4V=39H9R5P%d4*Z~X|4LH`xIo^(6$t{qhB#)5lG#B!D#VIN%m^ftEuBOceaHADq z7>Da8126^C#gTny9X5Xi$z|MU%o<CtF}b@gJfYN9RwJsWs^tGoFt^=wM+64Q`(6Ep ziM7fxo*}>-g9c}-w6*qTJ7NW@k{!bn+{e%rEm}3Gzwd9BZMQYnZ3ZOJ%`^sXJk%RN zj%@olzf22KT?=}+BKSu1q#b~b-~>y5*{B4IG?>%BR+&n6bskAZr?j0&nUN?OyP_v_ zL9)~ARU}mNo~dW9#O?9(?7JNbS{}<Uo1pYom@h;9wBd?u{q=-F__gH-xQ=Dr%%1AN zgOXz3`vX`U^Xghi`FS%pT0OQ@_orjV4E9gbcQ83oqw^v7hWPmk{f0MCVZyww9}B!v zVF#8oaPue$-O|{ckasGoQU_nb$p-XhyY6DU<6^_oMD7%A_c;V-YRfuHq-o`eg`A}2 znhrrCn8#{X25)-2&sb&`9QCD7RMk&O-1e|q-ih1CB3@oC=oY%=qSxo4HHEed$_Bm< zr&K1^EV#CVZNkQTI1Cws-5)MC1J=dv9@p;u&u_v{9{CPz=D>Us{CenMd-dve!v#m} zXsI%zdcX-vuZc@>m32ilDj#jQ=U=kiceg)_RKhZHtG&f@;R&4a^Mpm<g2lUsc<qm0 zo^y|$bAq|M^k*C>%TL>pe~a#4MVL1bCw6PFV8~_8NrXqZxNV4NzNvUqoyRW(l=jM6 zW`qH_l>*0b!-!ZCs(jV>9Hd%z$$fx&O2>Hxao4iCS9tLOwW}s^p+dT_{8nLVP0^Qv zX)(tn6CNlj>wcjxq*s5S4B@7C^5;(~C;gv0Un^D!TcI;{3+_4fV2-c-29xbSCzqRS zoW1|dmiKn#AN@!~J4#bbul6t!wxrid31*2s(yf!*ZU?ZN2f2}qvoXx$l4z!JDY;O^ zmZUDoUh9x<M_Bl9z)bUxAK!baNk0=Ax(UQkQi+G~Udv<Zm0gJJgX(ZaH_eZnfL*9% z%iH>}7gW=}0cKqDVLE;-T7V<vHy_O1D~#J0iwV9<VvcUSjX`Ul$a0@I+Lk`Mbh6F# zwD%f6ie&2m7Y3c834A=`nOTHp6@6&b?q&%q@@f|Q-tiS7<v7m4ot)>f`P5zyCz1i| zgBz=L5M`(I_x_g@u(!2`okj%!u$Bz~K=S`(<Lzv1|C2Lu?fvPvxsrT$`$1K=<R)Z7 zG1jn1x?HtjZ%r~^h5td_o+HDG4~dwHLW!VvA2;RlkCJ!Qd4-*LVBH+|QY1Bu?;CEf zb)5agq0aQko-xx{A4BpLQlWYbR2ZgpOFwHWDreL3o|$f#K1o$zR;tVsS@C;zUl zMsKZP>TRk1@yKOC<(|Kxsn@hgQJZs4{I}38o;rJXD%bgMig(;vO;YvLjYco77tl&J zpG9<7JTvj?Xls9!HE6NaMhKI^qB|?wb8TC7rS_e|t5?#}=`I44como1Dm%+tB{|+J z_v+Z{Vqth8a$$A2yqM>+{uEEvZR0B|QbQ-h?+o2f*R(de*cYx?bg;#Xlf$+>lifE| zX(sC~IiJJ4TXmne^t0%sL{^#W8*W`XZ+3T$S0qnlSDq?YqWMwWF6(+MyWqR?zVBRm z#P5s!q$PKE%Gox^-YTzJWlP#FcW380eixK?dhAET(|SALd&3_l<GR*-d>-$vN;Z$9 zwXRE~2U|qS_GhEe7GHkMwD_yPNNBKdn|e#8>_uPLTy(KmZ@^v!J0RY6YbBC;t0n=r zsWvfr+NZERn-n^{#>3JE*<3RZ>!Z%yUX@(3Hr9k<7k}bN^YgxTsIB~trXx2)kginN zv`|a1Y%W>*Yrk24Lz-Au0x;P^M4DiW5d8wKd32iG4R&v~99v~OM`o_W?rA1h!|tU5 zpv3^^B{_%z@)nExF)xGkna{Ho_k8|KlO2~*j^tErq_tGBP@Pm)X1lDS{;4oy7SsQt z;>rOIbMnmC`&x#vxxVffl1(t;Z6|NEfs}*WjMmr-l1Yf9$&*UvCevC@k~eOR*0`Bx zc?ay2lRq6V?@79l^z^z*MAu$LS?#7?F!k^fnFG%14WVNXVvX*29yO`Cz4o?<qwlMm z`d9a}u!Bbr+D2PN7+mZQtE<eEGi~Q;+Bm+mbCs-N;=Rt_`SCI9H)7b<1`Z59L9Dvb zEeu(ftVZe2HtOZ*An=G0EfS(bw&fL$Q$(G82pH5zyJ^1g5{bPg=dl-rl{=-Ft6>QL zNL=aA+iyJg=a@%AkuOFj`%W$Hz+)PL_aeGtB$2ol^^bySJcYl$jZ&kWvg=q_I=ulY z{!VTy*50=?pdsx>k?nE=xRoa9W{0hlBD<?=1>xB;;MWyflltE+EnvsJQj_O1Xh)Q_ z$X4g#GbqHcRAcB>>h3(Qc8!2KIVEAX%xFzz+R|`>zfRr}0Hh{7c3te2R(f;?rQ=X* z^=aBsqW>(`IS?lm+_=&&Q;lo-60fp@jhrX?cUm&#guc0Nva?|kGawK@;*%eEb4+HU zlI|IGSB9oLF40n3>11+kJ>?1Wi76uXA4T!cZVOT(<B3LF)@%{@63-NA8{<;evB<4- zYeLPPY+V=4KSx)Pb0L^66T3GJ3bfi>c4dp85|A@nukivzZ?}y|EoGJYoq{_xRT~3U zoEXaS$>FG61OD@gE=F5|^x)nO=WBYvoK0>D$~RftDR725P3z8(f$j23#JC`h{=kw# zM;Bl+FkidI1Pu(;f+KDYPEL&yY(n6R9qPC=Z=Z+`9fh_HSDc?G>-8Nv+&muITKM&2 zkk_x1roHYhY@M|GKnP8kkbQ29KO(yejp)2x?7m#mN2{Z*_&U8GgO{#!zmK={SfmmR z$(WzKrT)~2bFGz`U~QNBnKa5`Yhio+<%Uux%DjN2N!5Xdp$j+*@mV=%cF}ffaPtx) zl-kAP5TqH&Z+B*l#md2^5fzoN<kC3y%7Fe*ePMoaGvDz9CY$kEd*dHdVsUYMcm+nl zYtTFdls=zJQDX}j80)FA5HSp8fG$#WOc-*|QN+Dh4K=p9=%|Fh%?29DL^Oh5mgg=C z2j9XWM(7y`i>-n{<%E)YOTse9T%0nwf4X^PVz)<s=bMLRhXU>%YbaaCy|=dpD9T_C zW#6^to>1pdwYCjFnUh5Hh>?8Lg{B(z)OQ)N)clPg=ow8%DjAJbm36jI-E-Te)I35_ znez0u8;sOf)I%eIBlGyg(x7I6A)AVyS9lmI$VX5RRZGFp7`@{Psnm8X0H>VIR}j<e z)U|j`!=;CY+MOCG<z~QRGOF}3VdxPYmiD1qx(zgzSjdwIN|y~9U@D{&(IvGca4?ub ziZ0M`qmD@}v(14J4M5Tv8wP*X-E*xGC0(Loi*bDER@O_=(z3TBTfaZBI77!fK#=2F zrf=fZ1_lN^kfEr!ZOk9QO2XMHj@BelXT&8|2oh0o<+0Pa#(sDh%+k)@8PjLO!Dde1 ztSZlhkg9V(E>B}ldH@>Wh|~ba;P?WvA}X7YvB`1zq;YKFgsk|9eLU?-zm^O8JuUfx z>!|<(6YsTHcwkB^%!Rm@-?dcW!2U8b7YM1F%F<ceX8bE#ssUVRke@IW2jJ+Oj6h0X zNMgfpM+Y8?$5i9Fn?NGq6vDO(G+i!JFm9sB&^OkYv4oBTUv_|uB{<-shGQ-e)-{=) z$p%_dq5n&qD{|2_o8|@N<XdXyZRFfV5dbl6fWh5tzJ$A=%U<CegVa51BzA3y`*#ri zZoEJviD&8v4@}}qdj=lj&|8^OFN%d)r%-=x;2^z;S-^WEX}I8QqI#FAA}QFxgQOX$ zw2oTETTn9J)T>*VA*cx!D+~|hfGb-RjY8{=nurr8GRAh`<2CfPmS3;4{F8ANLW(>o z5UR&7UM3u_KS{1i5mA2?Jf{lFwgHN{&gAdhC$+{p`W)!B?245;B{zZj+g)Ckq7C>f zXy8Zf@5cqzP~7@;H&X#O*&{%l$DpbkcDdskz~5K5kd4eTwoo1rYMTzn^#D_xWh<Mr zUzu-{AgyF!%1@k1jvs)bnV}H4&Av5z1Ld*Ait9V^uGUl4OR*Ou=s%@WqSyUKB6gH8 zgx=0C-;waOwI-^kwlh24Z4I!;cR6^Ql0yChegQ=Mrw&m8l~KNd=%#~qN2K_@dpZ2r z9S3Cq{w-aK<yq{ZTgmZ-WNv2pEo0D8Ay6L%w-F8+O<2|=4Ht^mG8u0Ym1x2;&)+D{ zk(lB1AM4@Zx-01BT(lB-z?0L8q&gn4f%}^>$C<vHK=HQyhedy~9$c%JVnNM;f>ZCn zHW<O7`w;RulB4n3N)ZQ}7|{7}J~nqlbM&n0;qPoW?|mM47?q4_t@^Im#5EhW5?A}e z<}O%sh|Tz}aZVzY5Z_eIL&F;mVa9nh%cj1*zv(ZNZ9Za9;=;MoDzSq#ECQ*UKqDrQ zDDsG6f|`_^w-d*y)}N-qlr~tOOSPXb(<hB&6HD!Z2eS)09isL^{>sr-tOq}vHs@w3 z7jgGas}ytc%{wZy^>ME-19}0~nM5>kc2@Em@>dd!DNK@TR{LKgQ%M8I;fufaFFI;2 zziO}mWyluTCLEI|nw3!XWM=%U_uLxveDo@0H6C%$ofB2h`-^QIH^l?$F3X7#+ZLMs zM4v&*`+a4l{dOixq){?I`=Ry5-d)(9XL`2;u+UQxUX^E)ila9PyaX=KC&WZm^XT7F zfLHX_N4Y8Lg_YcXY}Ph;BhRC-A}go_X^E<@fgvom6*=M6o*S-=*D8_sl_u7|4mrgg zQh^Y1t(l=PPfTUEF{61R!&2`f+-(LY-4P$35EG+QzrtPnbW<qbJFI0BT$g?GBmw{H zhBk@i)2#cZ*pWSABIZ$pMm<Ay=CEE`xQVo=rQb=;drmf_#A+XpiBWO7j`uZA#vG(Z zxkll2X^${6C2g*O?<V}|E#DMee7HB4bhJcRy;k85t0BC(Xew?6c-=0&&`Kh1v*8zU zZWgiZMk1LVpMeoPCyv>^B;yHVy10K_RFC&XpK}bpz%uzBr1sC*xJ^eP5mKg<?GFzP zMf;QjbU4%W+_!$?&@lw~&v$K)Lu@Y-$+z@W)>>abv=bm^S6YTWD4M-%sV_i9nmcPk zEGUI|0CzkK3ViO`aMaH^M&Qi7+0Ba5daeJ|z0u564$Av`meBBf7PI=<YJ;?ZPV1)% znCX)J*A*k`{;5KgARpRIW5FVs`!Gt??p)ejUkI(>Yrou8>&nTJtNtv@E1`rxnqXJ7 zowpnCqljj|*C+)_6$>1qKr`uHHLXSBo*xio<gQ9VLm5J3SSOF0iW~C!JUZ?;9zOdU z=)DN~0}3MZuJ@nsc=qATGg+}oM1S|;+ZEkk`z0ve9;725H#b-fD8N>JB&wBfPC{Ui zgMgMTA+@L~C{tv??6)SMcUtPbPVBrVV~${7_&MA$4>OUbd46*t_f(2(y6R}SY62QD zLbI>BSSe%-4tfVQD!wz+x{ofi;}**llS&+Ei@AE<5lMdt3yMz&CFx~EmeiLSgt(Yk zoZorkfr+tdB0(UKX@K=ITfXF~+Y8JOa=?vyVh+%Brk!W+(Z9n71~;_?V}~PU%{N>v zkJjLOZLHRiiU9Aps$xa_h80pHe29EmeEoRFd4PKK@Z1+hMY{_%$f$hfR!A(av5yG2 zqHrTS{DV8%Iq*>alAD3qj^`u7w~J3fL4P0Q)ozu=IP2ScZ(^u~g0QTAb)KXHA;$M1 z>zt4R^~oLEp2#faQvDy&)2HnlPAW_^9dqN`P*|wuCm^6?V^77cw&7&;1PXbaBdhGQ zX6lNeovQQ3VwXgZy)N$i66HC#&%l5h@ClTXO}qZU6~m?&+yE5;YMHO4bT}e$i|yO@ z<3z=K==~YIA~XQ)c^Dg=WEgtGpa=`?SkJa-;j`OI8^mxtbLRp*Zc#87YXrtaB$q_& z6eB8Np91L#<$?8|w_j}$au$`qmCKtjg%_iV3lss7`txNlLhn$g5u0o;p@N;-U+Q3v z^jDBJju^>c8gFQ!u$RY|AK);2cgWe73-{qDerT`+F6)TGFsk$(VmlTTR4{=?oe-Tc zZc=n1CAAL(kSEi?*^sz=>a9qRVO71RMBGG6fzzU4`6e|K=hQ?Adv1K6xIx*(P3hCm zFsiu@%UybiLXI8hP!s^4A|_0rr|L@fQ⁣46jdTNPFZI?0qh9hsV{19LGY}IP<Ko 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[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 literal 0 HcmV?d00001 diff --git a/Lib/test/test_ensurepip.py b/Lib/test/test_ensurepip.py index 69ab2a4feaa..a4b36a90d88 100644 --- a/Lib/test/test_ensurepip.py +++ b/Lib/test/test_ensurepip.py @@ -6,6 +6,8 @@ import test.support import unittest import unittest.mock +from importlib.resources.abc import Traversable +from pathlib import Path import ensurepip import ensurepip._uninstall @@ -20,41 +22,35 @@ def test_version(self): # Test version() with tempfile.TemporaryDirectory() as tmpdir: self.touch(tmpdir, "pip-1.2.3b1-py2.py3-none-any.whl") - with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None), - unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)): + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', Path(tmpdir)): self.assertEqual(ensurepip.version(), '1.2.3b1') - def test_get_packages_no_dir(self): - # Test _get_packages() without a wheel package directory - with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None), - unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None)): - packages = ensurepip._get_packages() - - # when bundled wheel packages are used, we get _PIP_VERSION + def test_version_no_dir(self): + # Test version() without a wheel package directory + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None): + # when the bundled pip wheel is used, we get _PIP_VERSION self.assertEqual(ensurepip._PIP_VERSION, ensurepip.version()) - # use bundled wheel packages - self.assertIsNotNone(packages['pip'].wheel_name) + def test_selected_wheel_path_no_dir(self): + pip_filename = f'pip-{ensurepip._PIP_VERSION}-py3-none-any.whl' + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None): + with ensurepip._get_pip_whl_path_ctx() as bundled_wheel_path: + self.assertEqual(pip_filename, bundled_wheel_path.name) - def test_get_packages_with_dir(self): - # Test _get_packages() with a wheel package directory + def test_selected_wheel_path_with_dir(self): + # Test _get_pip_whl_path_ctx() with a wheel package directory pip_filename = "pip-20.2.2-py2.py3-none-any.whl" with tempfile.TemporaryDirectory() as tmpdir: self.touch(tmpdir, pip_filename) - # not used, make sure that it's ignored + # not used, make sure that they're ignored + self.touch(tmpdir, "pip-1.2.3-py2.py3-none-any.whl") self.touch(tmpdir, "wheel-0.34.2-py2.py3-none-any.whl") + self.touch(tmpdir, "pip-script.py") - with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None), - unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)): - packages = ensurepip._get_packages() - - self.assertEqual(packages['pip'].version, '20.2.2') - self.assertEqual(packages['pip'].wheel_path, - os.path.join(tmpdir, pip_filename)) - - # wheel package is ignored - self.assertEqual(sorted(packages), ['pip']) + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', Path(tmpdir)): + with ensurepip._get_pip_whl_path_ctx() as bundled_wheel_path: + self.assertEqual(pip_filename, bundled_wheel_path.name) class EnsurepipMixin: @@ -69,7 +65,7 @@ def setUp(self): real_devnull = os.devnull os_patch = unittest.mock.patch("ensurepip.os") patched_os = os_patch.start() - # But expose os.listdir() used by _find_packages() + # But expose os.listdir() used by _find_wheel_pkg_dir_pip() patched_os.listdir = os.listdir self.addCleanup(os_patch.stop) patched_os.devnull = real_devnull From 1d23f6e8dfbecfa2a1765748e4db3fcfbe3ad2c2 Mon Sep 17 00:00:00 2001 From: yt2b <76801443+yt2b@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:41:28 +0900 Subject: [PATCH 312/819] Panic occurs when formatting with separator and some format specifier (#6213) * Fix get_separator_interval * Add extra tests * Remove FormatType::Number from match arm --- common/src/format.rs | 8 +++++++- extra_tests/snippets/builtin_format.py | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/common/src/format.rs b/common/src/format.rs index 120eefcbc3e..447ae575f48 100644 --- a/common/src/format.rs +++ b/common/src/format.rs @@ -424,7 +424,13 @@ impl FormatSpec { const fn get_separator_interval(&self) -> usize { match self.format_type { Some(FormatType::Binary | FormatType::Octal | FormatType::Hex(_)) => 4, - Some(FormatType::Decimal | FormatType::Number(_) | FormatType::FixedPoint(_)) => 3, + Some( + FormatType::Decimal + | FormatType::FixedPoint(_) + | FormatType::GeneralFormat(_) + | FormatType::Exponent(_) + | FormatType::Percentage, + ) => 3, None => 3, _ => panic!("Separators only valid for numbers!"), } diff --git a/extra_tests/snippets/builtin_format.py b/extra_tests/snippets/builtin_format.py index ac7afb769ab..a5edcc89523 100644 --- a/extra_tests/snippets/builtin_format.py +++ b/extra_tests/snippets/builtin_format.py @@ -188,6 +188,11 @@ def test_zero_padding(): assert f"{12345.6 + 7890.1j:,}" == "(12,345.6+7,890.1j)" assert f"{12345.6 + 7890.1j:_.3f}" == "12_345.600+7_890.100j" assert f"{12345.6 + 7890.1j:>+30,f}" == " +12,345.600000+7,890.100000j" +assert f"{123456:,g}" == "123,456" +assert f"{123456:,G}" == "123,456" +assert f"{123456:,e}" == "1.234560e+05" +assert f"{123456:,E}" == "1.234560E+05" +assert f"{123456:,%}" == "12,345,600.000000%" # test issue 4558 x = 123456789012345678901234567890 From c6c931aa0bd80264dac3d26d2c2b6d6bb9635993 Mon Sep 17 00:00:00 2001 From: Yash Suthar <yashsuthar983@gmail.com> Date: Sun, 26 Oct 2025 14:03:49 +0530 Subject: [PATCH 313/819] Fix ldexp to prevent math range errors (#6216) * Fix ldexp to prevent math range errors Implemented IEEE 754-compliant handling for large numbers to avoid overflow and represent results using scientific notation. Fixes #5471 Signed-off-by: Yash Suthar <yashsuthar983@gmail.com> * Fix ldexp to handle denormalized inputs correctly Detect denormalized input values below f64::MIN_POSITIVE Scale subnormal inputs by 2^54 to bring them into normalized range Signed-off-by: Yash Suthar <yashsuthar983@gmail.com> * tests(math): remove expectedFailure from testLdexp_denormal As now we have implemented IEEE 754 format , so this will pass. Signed-off-by: Yash Suthar <yashsuthar983@gmail.com> * Update Lib/test/test_math.py Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com> * fixed formate in math.rs Signed-off-by: Yash Suthar <yashsuthar983@gmail.com> --------- Signed-off-by: Yash Suthar <yashsuthar983@gmail.com> Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com> --- Lib/test/test_math.py | 1 - stdlib/src/math.rs | 70 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 0907c5465ea..1a4d257586b 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -1202,7 +1202,6 @@ def testLdexp(self): self.assertEqual(math.ldexp(NINF, n), NINF) self.assertTrue(math.isnan(math.ldexp(NAN, n))) - @unittest.expectedFailure # TODO: RUSTPYTHON @requires_IEEE_754 def testLdexp_denormal(self): # Denormal output incorrectly rounded (truncated) diff --git a/stdlib/src/math.rs b/stdlib/src/math.rs index aa0097c11eb..62b0ef73ad3 100644 --- a/stdlib/src/math.rs +++ b/stdlib/src/math.rs @@ -12,7 +12,7 @@ mod math { }; use itertools::Itertools; use malachite_bigint::BigInt; - use num_traits::{One, Signed, Zero}; + use num_traits::{One, Signed, ToPrimitive, Zero}; use rustpython_common::{float_ops, int::true_div}; use std::cmp::Ordering; @@ -563,11 +563,73 @@ mod math { if value == 0_f64 || !value.is_finite() { // NaNs, zeros and infinities are returned unchanged - Ok(value) + return Ok(value); + } + + // Using IEEE 754 bit manipulation to handle large exponents correctly. + // Direct multiplication would overflow for large i values, especially when computing + // the largest finite float (i=1024, x<1.0). By directly modifying the exponent bits, + // we avoid intermediate overflow to infinity. + + // Scale subnormals to normal range first, then adjust exponent. + let (mant, exp0) = if value.abs() < f64::MIN_POSITIVE { + let scaled = value * (1u64 << 54) as f64; // multiply by 2^54 + let (mant_scaled, exp_scaled) = float_ops::decompose_float(scaled); + (mant_scaled, exp_scaled - 54) // adjust exponent back + } else { + float_ops::decompose_float(value) + }; + + let i_big = i.as_bigint(); + let overflow_bound = BigInt::from(1024_i32 - exp0); // i > 1024 - exp0 => overflow + if i_big > &overflow_bound { + return Err(vm.new_overflow_error("math range error")); + } + if i_big == &overflow_bound && mant == 1.0 { + return Err(vm.new_overflow_error("math range error")); + } + let underflow_bound = BigInt::from(-1074_i32 - exp0); // i < -1074 - exp0 => 0.0 with sign + if i_big < &underflow_bound { + return Ok(0.0f64.copysign(value)); + } + + let i_small: i32 = i_big + .to_i32() + .expect("exponent within [-1074-exp0, 1024-exp0] must fit in i32"); + let exp = exp0 + i_small; + + const SIGN_MASK: u64 = 0x8000_0000_0000_0000; + const FRAC_MASK: u64 = 0x000F_FFFF_FFFF_FFFF; + let sign_bit: u64 = if value.is_sign_negative() { + SIGN_MASK } else { - let result = value * (2_f64).powf(try_bigint_to_f64(i.as_bigint(), vm)?); - result_or_overflow(value, result, vm) + 0 + }; + let mant_bits = mant.to_bits() & FRAC_MASK; + if exp >= -1021 { + let e_bits = (1022_i32 + exp) as u64; + let result_bits = sign_bit | (e_bits << 52) | mant_bits; + return Ok(f64::from_bits(result_bits)); } + + let full_mant: u64 = (1u64 << 52) | mant_bits; + let shift: u32 = (-exp - 1021) as u32; + let frac_shifted = full_mant >> shift; + let lost_bits = full_mant & ((1u64 << shift) - 1); + + let half = 1u64 << (shift - 1); + let frac = if (lost_bits > half) || (lost_bits == half && (frac_shifted & 1) == 1) { + frac_shifted + 1 + } else { + frac_shifted + }; + + let result_bits = if frac >= (1u64 << 52) { + sign_bit | (1u64 << 52) + } else { + sign_bit | frac + }; + Ok(f64::from_bits(result_bits)) } fn math_perf_arb_len_int_op<F>(args: PosArgs<ArgIndex>, op: F, default: BigInt) -> BigInt From 2e7a8b4735dc81f4ca97f8f4ca5e0ba24c555a01 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 26 Oct 2025 19:19:24 +0900 Subject: [PATCH 314/819] Implement more SSL methods (#6210) * openssl-sys version * selected_alpn_protocol * get_ciphers * get_channel_binding * Add Tls1_1 and Tls1_2 * consts * fix ssl truncate bug * verify_flags * shared ciphers * verify_client_post_handshake * shutdown * get_verified_chain * fix lints * fix _wrap_socket * Fix convert_openssl_error * clean up ssl * X509_check_ca * set default verify flag * Fix version * fix import * consts * fix httplib --- Lib/test/test_httplib.py | 2 - stdlib/Cargo.toml | 2 +- stdlib/src/ssl.rs | 529 ++++++++++++++++++++++++++++++++++----- 3 files changed, 472 insertions(+), 61 deletions(-) diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 5a59f372ad3..9b631fb397a 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -1780,8 +1780,6 @@ def test_networked_bad_cert(self): h.request('GET', '/') self.assertEqual(exc_info.exception.reason, 'CERTIFICATE_VERIFY_FAILED') - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(sys.platform == 'darwin', 'Occasionally success on macOS') def test_local_unknown_cert(self): # The custom cert isn't known to the default trust bundle diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml index fd6d3e8a598..cc6f76a1a64 100644 --- a/stdlib/Cargo.toml +++ b/stdlib/Cargo.toml @@ -107,7 +107,7 @@ gethostname = "1.0.2" socket2 = { version = "0.6.0", features = ["all"] } dns-lookup = "3.0" openssl = { version = "0.10.72", optional = true } -openssl-sys = { version = "0.9.80", optional = true } +openssl-sys = { version = "0.9.110", optional = true } openssl-probe = { version = "0.1.5", optional = true } foreign-types-shared = { version = "0.1.1", optional = true } diff --git a/stdlib/src/ssl.rs b/stdlib/src/ssl.rs index c9a9e15f8e3..bf77d6b6907 100644 --- a/stdlib/src/ssl.rs +++ b/stdlib/src/ssl.rs @@ -26,7 +26,7 @@ cfg_if::cfg_if! { } #[allow(non_upper_case_globals)] -#[pymodule(with(ossl101, windows))] +#[pymodule(with(ossl101, ossl111, windows))] mod _ssl { use super::{bio, probe}; use crate::{ @@ -39,7 +39,7 @@ mod _ssl { socket::{self, PySocket}, vm::{ Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyBaseExceptionRef, PyStrRef, PyType, PyTypeRef, PyWeak}, + builtins::{PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyTypeRef, PyWeak}, class_or_notimplemented, convert::{ToPyException, ToPyObject}, exceptions, @@ -66,7 +66,8 @@ mod _ssl { ffi::CStr, fmt, io::{Read, Write}, - path::Path, + path::{Path, PathBuf}, + sync::LazyLock, time::Instant, }; @@ -84,19 +85,24 @@ mod _ssl { SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, - // #ifdef SSL_OP_SINGLE_ECDH_USE - // SSL_OP_SINGLE_ECDH_USE as OP_SINGLE_ECDH_USE - // #endif // X509_V_FLAG_CRL_CHECK as VERIFY_CRL_CHECK_LEAF, // sys::X509_V_FLAG_CRL_CHECK|sys::X509_V_FLAG_CRL_CHECK_ALL as VERIFY_CRL_CHECK_CHAIN // X509_V_FLAG_X509_STRICT as VERIFY_X509_STRICT, SSL_ERROR_ZERO_RETURN, SSL_OP_CIPHER_SERVER_PREFERENCE as OP_CIPHER_SERVER_PREFERENCE, + SSL_OP_ENABLE_MIDDLEBOX_COMPAT as OP_ENABLE_MIDDLEBOX_COMPAT, + SSL_OP_LEGACY_SERVER_CONNECT as OP_LEGACY_SERVER_CONNECT, SSL_OP_NO_SSLv2 as OP_NO_SSLv2, SSL_OP_NO_SSLv3 as OP_NO_SSLv3, SSL_OP_NO_TICKET as OP_NO_TICKET, SSL_OP_NO_TLSv1 as OP_NO_TLSv1, SSL_OP_SINGLE_DH_USE as OP_SINGLE_DH_USE, + SSL_OP_SINGLE_ECDH_USE as OP_SINGLE_ECDH_USE, + X509_V_FLAG_ALLOW_PROXY_CERTS as VERIFY_ALLOW_PROXY_CERTS, + X509_V_FLAG_CRL_CHECK as VERIFY_CRL_CHECK_LEAF, + X509_V_FLAG_PARTIAL_CHAIN as VERIFY_X509_PARTIAL_CHAIN, + X509_V_FLAG_TRUSTED_FIRST as VERIFY_X509_TRUSTED_FIRST, + X509_V_FLAG_X509_STRICT as VERIFY_X509_STRICT, }; // taken from CPython, should probably be kept up to date with their version if it ever changes @@ -116,6 +122,10 @@ mod _ssl { #[pyattr] const PROTOCOL_TLSv1: u32 = SslVersion::Tls1 as u32; #[pyattr] + const PROTOCOL_TLSv1_1: u32 = SslVersion::Tls1_1 as u32; + #[pyattr] + const PROTOCOL_TLSv1_2: u32 = SslVersion::Tls1_2 as u32; + #[pyattr] const PROTO_MINIMUM_SUPPORTED: i32 = ProtoVersion::MinSupported as i32; #[pyattr] const PROTO_SSLv3: i32 = ProtoVersion::Ssl3 as i32; @@ -146,7 +156,7 @@ mod _ssl { #[pyattr] const HAS_SNI: bool = true; #[pyattr] - const HAS_ECDH: bool = false; + const HAS_ECDH: bool = true; #[pyattr] const HAS_NPN: bool = false; #[pyattr] @@ -183,7 +193,8 @@ mod _ssl { #[pyattr(name = "_OPENSSL_API_VERSION")] fn _openssl_api_version(_vm: &VirtualMachine) -> OpensslVersionInfo { - let openssl_api_version = i64::from_str_radix(env!("OPENSSL_API_VERSION"), 16).unwrap(); + let openssl_api_version = i64::from_str_radix(env!("OPENSSL_API_VERSION"), 16) + .expect("OPENSSL_API_VERSION is malformed"); parse_version_info(openssl_api_version) } @@ -241,7 +252,8 @@ mod _ssl { /// SSL/TLS connection terminated abruptly. #[pyattr(name = "SSLEOFError", once)] fn ssl_eof_error(vm: &VirtualMachine) -> PyTypeRef { - PyType::new_simple_heap("ssl.SSLEOFError", &ssl_error(vm), &vm.ctx).unwrap() + vm.ctx + .new_exception_type("ssl", "SSLEOFError", Some(vec![ssl_error(vm)])) } type OpensslVersionInfo = (u8, u8, u8, u8, u8); @@ -265,7 +277,8 @@ mod _ssl { Ssl3 = 1, Tls, Tls1, - // TODO: Tls1_1, Tls1_2 ? + Tls1_1, + Tls1_2, TlsClient = 0x10, TlsServer, } @@ -341,14 +354,17 @@ mod _ssl { } type PyNid = (libc::c_int, String, String, Option<String>); - fn obj2py(obj: &Asn1ObjectRef) -> PyNid { + fn obj2py(obj: &Asn1ObjectRef, vm: &VirtualMachine) -> PyResult<PyNid> { let nid = obj.nid(); - ( - nid.as_raw(), - nid.short_name().unwrap().to_owned(), - nid.long_name().unwrap().to_owned(), - obj2txt(obj, true), - ) + let short_name = nid + .short_name() + .map_err(|_| vm.new_value_error("NID has no short name".to_owned()))? + .to_owned(); + let long_name = nid + .long_name() + .map_err(|_| vm.new_value_error("NID has no long name".to_owned()))? + .to_owned(); + Ok((nid.as_raw(), short_name, long_name, obj2txt(obj, true))) } #[derive(FromArgs)] @@ -362,55 +378,81 @@ mod _ssl { fn txt2obj(args: Txt2ObjArgs, vm: &VirtualMachine) -> PyResult<PyNid> { _txt2obj(&args.txt.to_cstring(vm)?, !args.name) .as_deref() - .map(obj2py) .ok_or_else(|| vm.new_value_error(format!("unknown object '{}'", args.txt))) + .and_then(|obj| obj2py(obj, vm)) } #[pyfunction] fn nid2obj(nid: libc::c_int, vm: &VirtualMachine) -> PyResult<PyNid> { _nid2obj(Nid::from_raw(nid)) .as_deref() - .map(obj2py) .ok_or_else(|| vm.new_value_error(format!("unknown NID {nid}"))) + .and_then(|obj| obj2py(obj, vm)) } - fn get_cert_file_dir() -> (&'static Path, &'static Path) { - let probe = probe(); - // on windows, these should be utf8 strings - fn path_from_bytes(c: &CStr) -> &Path { + // Lazily compute and cache cert file/dir paths + static CERT_PATHS: LazyLock<(PathBuf, PathBuf)> = LazyLock::new(|| { + fn path_from_cstr(c: &CStr) -> PathBuf { #[cfg(unix)] { use std::os::unix::ffi::OsStrExt; - std::ffi::OsStr::from_bytes(c.to_bytes()).as_ref() + std::ffi::OsStr::from_bytes(c.to_bytes()).into() } #[cfg(windows)] { - c.to_str().unwrap().as_ref() + // Use lossy conversion for potential non-UTF8 + PathBuf::from(c.to_string_lossy().as_ref()) } } - let cert_file = probe.cert_file.as_deref().unwrap_or_else(|| { - path_from_bytes(unsafe { CStr::from_ptr(sys::X509_get_default_cert_file()) }) - }); - let cert_dir = probe.cert_dir.as_deref().unwrap_or_else(|| { - path_from_bytes(unsafe { CStr::from_ptr(sys::X509_get_default_cert_dir()) }) - }); + + let probe = probe(); + let cert_file = probe + .cert_file + .as_ref() + .map(PathBuf::from) + .unwrap_or_else(|| { + path_from_cstr(unsafe { CStr::from_ptr(sys::X509_get_default_cert_file()) }) + }); + let cert_dir = probe + .cert_dir + .as_ref() + .map(PathBuf::from) + .unwrap_or_else(|| { + path_from_cstr(unsafe { CStr::from_ptr(sys::X509_get_default_cert_dir()) }) + }); (cert_file, cert_dir) + }); + + fn get_cert_file_dir() -> (&'static Path, &'static Path) { + let (cert_file, cert_dir) = &*CERT_PATHS; + (cert_file.as_path(), cert_dir.as_path()) } + // Lazily compute and cache cert environment variable names + static CERT_ENV_NAMES: LazyLock<(String, String)> = LazyLock::new(|| { + let cert_file_env = unsafe { CStr::from_ptr(sys::X509_get_default_cert_file_env()) } + .to_string_lossy() + .into_owned(); + let cert_dir_env = unsafe { CStr::from_ptr(sys::X509_get_default_cert_dir_env()) } + .to_string_lossy() + .into_owned(); + (cert_file_env, cert_dir_env) + }); + #[pyfunction] fn get_default_verify_paths( vm: &VirtualMachine, ) -> PyResult<(&'static str, PyObjectRef, &'static str, PyObjectRef)> { - let cert_file_env = unsafe { CStr::from_ptr(sys::X509_get_default_cert_file_env()) } - .to_str() - .unwrap(); - let cert_dir_env = unsafe { CStr::from_ptr(sys::X509_get_default_cert_dir_env()) } - .to_str() - .unwrap(); + let (cert_file_env, cert_dir_env) = &*CERT_ENV_NAMES; let (cert_file, cert_dir) = get_cert_file_dir(); let cert_file = OsPath::new_str(cert_file).filename(vm); let cert_dir = OsPath::new_str(cert_dir).filename(vm); - Ok((cert_file_env, cert_file, cert_dir_env, cert_dir)) + Ok(( + cert_file_env.as_str(), + cert_file, + cert_dir_env.as_str(), + cert_dir, + )) } #[pyfunction(name = "RAND_status")] @@ -480,7 +522,9 @@ mod _ssl { let method = match proto { // SslVersion::Ssl3 => unsafe { ssl::SslMethod::from_ptr(sys::SSLv3_method()) }, SslVersion::Tls => ssl::SslMethod::tls(), - // TODO: Tls1_1, Tls1_2 ? + SslVersion::Tls1 => ssl::SslMethod::tls(), + SslVersion::Tls1_1 => ssl::SslMethod::tls(), + SslVersion::Tls1_2 => ssl::SslMethod::tls(), SslVersion::TlsClient => ssl::SslMethod::tls_client(), SslVersion::TlsServer => ssl::SslMethod::tls_server(), _ => return Err(vm.new_value_error("invalid protocol version")), @@ -509,6 +553,7 @@ mod _ssl { options |= SslOptions::CIPHER_SERVER_PREFERENCE; options |= SslOptions::SINGLE_DH_USE; options |= SslOptions::SINGLE_ECDH_USE; + options |= SslOptions::ENABLE_MIDDLEBOX_COMPAT; builder.set_options(options); let mode = ssl::SslMode::ACCEPT_MOVING_WRITE_BUFFER | ssl::SslMode::AUTO_RETRY; @@ -523,6 +568,13 @@ mod _ssl { .set_session_id_context(b"Python") .map_err(|e| convert_openssl_error(vm, e))?; + // Set default verify flags: VERIFY_X509_TRUSTED_FIRST + unsafe { + let ctx_ptr = builder.as_ptr(); + let param = sys::SSL_CTX_get0_param(ctx_ptr); + sys::X509_VERIFY_PARAM_set_flags(param, sys::X509_V_FLAG_TRUSTED_FIRST); + } + PySslContext { ctx: PyRwLock::new(builder), check_hostname: AtomicCell::new(check_hostname), @@ -569,6 +621,64 @@ mod _ssl { }) } + #[pymethod] + fn get_ciphers(&self, vm: &VirtualMachine) -> PyResult<PyListRef> { + let ctx = self.ctx(); + let ssl = ssl::Ssl::new(&ctx).map_err(|e| convert_openssl_error(vm, e))?; + + unsafe { + let ciphers_ptr = SSL_get_ciphers(ssl.as_ptr()); + if ciphers_ptr.is_null() { + return Ok(vm.ctx.new_list(vec![])); + } + + let num_ciphers = sys::OPENSSL_sk_num(ciphers_ptr as *const _); + let mut result = Vec::new(); + + for i in 0..num_ciphers { + let cipher_ptr = + sys::OPENSSL_sk_value(ciphers_ptr as *const _, i) as *const sys::SSL_CIPHER; + let cipher = ssl::SslCipherRef::from_ptr(cipher_ptr as *mut _); + + let (name, version, bits) = cipher_to_tuple(cipher); + let dict = vm.ctx.new_dict(); + dict.set_item("name", vm.ctx.new_str(name).into(), vm)?; + dict.set_item("protocol", vm.ctx.new_str(version).into(), vm)?; + dict.set_item("secret_bits", vm.ctx.new_int(bits).into(), vm)?; + result.push(dict.into()); + } + + Ok(vm.ctx.new_list(result)) + } + } + + #[pymethod] + fn set_ecdh_curve(&self, name: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { + use openssl::ec::{EcGroup, EcKey}; + + let curve_name = name.as_str(); + if curve_name.contains('\0') { + return Err(exceptions::cstring_error(vm)); + } + + // Find the NID for the curve name using OBJ_sn2nid + let name_cstr = name.to_cstring(vm)?; + let nid_raw = unsafe { sys::OBJ_sn2nid(name_cstr.as_ptr()) }; + if nid_raw == 0 { + return Err(vm.new_value_error(format!("unknown curve name: {}", curve_name))); + } + let nid = Nid::from_raw(nid_raw); + + // Create EC key from the curve + let group = EcGroup::from_curve_name(nid).map_err(|e| convert_openssl_error(vm, e))?; + let key = EcKey::from_group(&group).map_err(|e| convert_openssl_error(vm, e))?; + + // Set the temporary ECDH key + self.builder() + .set_tmp_ecdh(&key) + .map_err(|e| convert_openssl_error(vm, e)) + } + #[pygetset] fn options(&self) -> libc::c_ulong { self.ctx.read().options().bits() as _ @@ -616,6 +726,38 @@ mod _ssl { Ok(()) } #[pygetset] + fn verify_flags(&self) -> libc::c_ulong { + unsafe { + let ctx_ptr = self.ctx().as_ptr(); + let param = sys::SSL_CTX_get0_param(ctx_ptr); + sys::X509_VERIFY_PARAM_get_flags(param) + } + } + #[pygetset(setter)] + fn set_verify_flags(&self, new_flags: libc::c_ulong, vm: &VirtualMachine) -> PyResult<()> { + unsafe { + let ctx_ptr = self.ctx().as_ptr(); + let param = sys::SSL_CTX_get0_param(ctx_ptr); + let flags = sys::X509_VERIFY_PARAM_get_flags(param); + let clear = flags & !new_flags; + let set = !flags & new_flags; + + if clear != 0 && sys::X509_VERIFY_PARAM_clear_flags(param, clear) == 0 { + return Err(vm.new_exception_msg( + ssl_error(vm), + "Failed to clear verify flags".to_owned(), + )); + } + if set != 0 && sys::X509_VERIFY_PARAM_set_flags(param, set) == 0 { + return Err(vm.new_exception_msg( + ssl_error(vm), + "Failed to set verify flags".to_owned(), + )); + } + Ok(()) + } + } + #[pygetset] fn check_hostname(&self) -> bool { self.check_hostname.load() } @@ -743,8 +885,16 @@ mod _ssl { let certs = ctx.cert_store().all_certificates(); #[cfg(not(ossl300))] let certs = ctx.cert_store().objects().iter().filter_map(|x| x.x509()); + + // Filter to only include CA certificates (Basic Constraints: CA=TRUE) let certs = certs .into_iter() + .filter(|cert| { + unsafe { + // X509_check_ca() returns 1 for CA certificates + X509_check_ca(cert.as_ptr()) == 1 + } + }) .map(|ref cert| cert_to_py(vm, cert, binary_form)) .collect::<Result<Vec<_>, _>>()?; Ok(certs) @@ -781,6 +931,20 @@ mod _ssl { args: WrapSocketArgs, vm: &VirtualMachine, ) -> PyResult<PySslSocket> { + // validate socket type and context protocol + if !args.server_side && zelf.protocol == SslVersion::TlsServer { + return Err(vm.new_exception_msg( + ssl_error(vm), + "Cannot create a client socket with a PROTOCOL_TLS_SERVER context".to_owned(), + )); + } + if args.server_side && zelf.protocol == SslVersion::TlsClient { + return Err(vm.new_exception_msg( + ssl_error(vm), + "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context".to_owned(), + )); + } + let mut ssl = ssl::Ssl::new(&zelf.ctx()).map_err(|e| convert_openssl_error(vm, e))?; let socket_type = if args.server_side { @@ -1041,6 +1205,37 @@ mod _ssl { Some(vm.ctx.new_list(certs).into()) } + #[pymethod] + fn get_verified_chain(&self, vm: &VirtualMachine) -> Option<PyListRef> { + let stream = self.stream.read(); + unsafe { + let chain = sys::SSL_get0_verified_chain(stream.ssl().as_ptr()); + if chain.is_null() { + return None; + } + + let num_certs = sys::OPENSSL_sk_num(chain as *const _); + let mut certs = Vec::new(); + + for i in 0..num_certs { + let cert_ptr = sys::OPENSSL_sk_value(chain as *const _, i) as *mut sys::X509; + if cert_ptr.is_null() { + continue; + } + let cert = X509Ref::from_ptr(cert_ptr); + if let Ok(der) = cert.to_der() { + certs.push(vm.ctx.new_bytes(der).into()); + } + } + + if certs.is_empty() { + None + } else { + Some(vm.ctx.new_list(certs)) + } + } + } + #[pymethod] fn version(&self) -> Option<&'static str> { let v = self.stream.read().ssl().version_str(); @@ -1056,6 +1251,189 @@ mod _ssl { .map(cipher_to_tuple) } + #[pymethod] + fn shared_ciphers(&self, vm: &VirtualMachine) -> Option<PyListRef> { + #[cfg(ossl110)] + { + let stream = self.stream.read(); + unsafe { + let server_ciphers = SSL_get_ciphers(stream.ssl().as_ptr()); + if server_ciphers.is_null() { + return None; + } + + let client_ciphers = SSL_get_client_ciphers(stream.ssl().as_ptr()); + if client_ciphers.is_null() { + return None; + } + + let mut result = Vec::new(); + let num_server = sys::OPENSSL_sk_num(server_ciphers as *const _); + let num_client = sys::OPENSSL_sk_num(client_ciphers as *const _); + + for i in 0..num_server { + let server_cipher_ptr = sys::OPENSSL_sk_value(server_ciphers as *const _, i) + as *const sys::SSL_CIPHER; + + // Check if client supports this cipher by comparing pointers + let mut found = false; + for j in 0..num_client { + let client_cipher_ptr = + sys::OPENSSL_sk_value(client_ciphers as *const _, j) + as *const sys::SSL_CIPHER; + + if server_cipher_ptr == client_cipher_ptr { + found = true; + break; + } + } + + if found { + let cipher = ssl::SslCipherRef::from_ptr(server_cipher_ptr as *mut _); + let (name, version, bits) = cipher_to_tuple(cipher); + let tuple = vm.new_tuple(( + vm.ctx.new_str(name), + vm.ctx.new_str(version), + vm.ctx.new_int(bits), + )); + result.push(tuple.into()); + } + } + + if result.is_empty() { + None + } else { + Some(vm.ctx.new_list(result)) + } + } + } + #[cfg(not(ossl110))] + { + let _ = vm; + None + } + } + + #[pymethod] + fn selected_alpn_protocol(&self) -> Option<String> { + #[cfg(ossl102)] + { + let stream = self.stream.read(); + unsafe { + let mut out: *const libc::c_uchar = std::ptr::null(); + let mut outlen: libc::c_uint = 0; + + sys::SSL_get0_alpn_selected(stream.ssl().as_ptr(), &mut out, &mut outlen); + + if out.is_null() { + None + } else { + let slice = std::slice::from_raw_parts(out, outlen as usize); + Some(String::from_utf8_lossy(slice).into_owned()) + } + } + } + #[cfg(not(ossl102))] + { + None + } + } + + #[pymethod] + fn get_channel_binding( + &self, + cb_type: OptionalArg<PyStrRef>, + vm: &VirtualMachine, + ) -> PyResult<Option<PyBytesRef>> { + const CB_MAXLEN: usize = 512; + + let cb_type_str = cb_type.as_ref().map_or("tls-unique", |s| s.as_str()); + + if cb_type_str != "tls-unique" { + return Err(vm.new_value_error(format!( + "Unsupported channel binding type '{}'", + cb_type_str + ))); + } + + let stream = self.stream.read(); + let ssl_ptr = stream.ssl().as_ptr(); + + unsafe { + let session_reused = sys::SSL_session_reused(ssl_ptr) != 0; + let is_client = matches!(self.socket_type, SslServerOrClient::Client); + + // Use XOR logic from CPython + let use_finished = session_reused ^ is_client; + + let mut buf = vec![0u8; CB_MAXLEN]; + let len = if use_finished { + sys::SSL_get_finished(ssl_ptr, buf.as_mut_ptr() as *mut _, CB_MAXLEN) + } else { + sys::SSL_get_peer_finished(ssl_ptr, buf.as_mut_ptr() as *mut _, CB_MAXLEN) + }; + + if len == 0 { + Ok(None) + } else { + buf.truncate(len); + Ok(Some(vm.ctx.new_bytes(buf))) + } + } + } + + #[pymethod] + fn verify_client_post_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { + #[cfg(ossl111)] + { + let stream = self.stream.read(); + let result = unsafe { SSL_verify_client_post_handshake(stream.ssl().as_ptr()) }; + if result == 0 { + Err(vm.new_exception_msg( + ssl_error(vm), + "Post-handshake authentication failed".to_owned(), + )) + } else { + Ok(()) + } + } + #[cfg(not(ossl111))] + { + Err(vm.new_not_implemented_error( + "Post-handshake auth is not supported by your OpenSSL version.".to_owned(), + )) + } + } + + #[pymethod] + fn shutdown(&self, vm: &VirtualMachine) -> PyResult<PyRef<PySocket>> { + let stream = self.stream.read(); + let ssl_ptr = stream.ssl().as_ptr(); + + // Perform SSL shutdown + let ret = unsafe { sys::SSL_shutdown(ssl_ptr) }; + + if ret < 0 { + // Error occurred + let err = unsafe { sys::SSL_get_error(ssl_ptr, ret) }; + + if err == sys::SSL_ERROR_WANT_READ || err == sys::SSL_ERROR_WANT_WRITE { + // Non-blocking would block - this is okay for shutdown + // Return the underlying socket + } else { + return Err(vm.new_exception_msg( + ssl_error(vm), + format!("SSL shutdown failed: error code {}", err), + )); + } + } + + // Return the underlying socket + // Get the socket from the stream (SocketStream wraps PyRef<PySocket>) + let socket = stream.get_ref(); + Ok(socket.0.clone()) + } + #[cfg(osslconf = "OPENSSL_NO_COMP")] #[pymethod] fn compression(&self) -> Option<&'static str> { @@ -1267,7 +1645,7 @@ mod _ssl { let ret = match inner_buffer { Either::A(_buf) => vm.ctx.new_int(count).into(), Either::B(mut buf) => { - buf.truncate(n); + buf.truncate(count); buf.shrink_to_fit(); vm.ctx.new_bytes(buf).into() } @@ -1363,6 +1741,27 @@ mod _ssl { unsafe impl Send for PySslMemoryBio {} unsafe impl Sync for PySslMemoryBio {} + // OpenSSL functions not in openssl-sys + + unsafe extern "C" { + // X509_check_ca returns 1 for CA certificates, 0 otherwise + fn X509_check_ca(x: *const sys::X509) -> libc::c_int; + } + + unsafe extern "C" { + fn SSL_get_ciphers(ssl: *const sys::SSL) -> *const sys::stack_st_SSL_CIPHER; + } + + #[cfg(ossl110)] + unsafe extern "C" { + fn SSL_get_client_ciphers(ssl: *const sys::SSL) -> *const sys::stack_st_SSL_CIPHER; + } + + #[cfg(ossl111)] + unsafe extern "C" { + fn SSL_verify_client_post_handshake(ssl: *const sys::SSL) -> libc::c_int; + } + // OpenSSL BIO helper functions // These are typically macros in OpenSSL, implemented via BIO_ctrl const BIO_CTRL_PENDING: libc::c_int = 10; @@ -1525,12 +1924,12 @@ mod _ssl { } #[pygetset] - fn id(&self, vm: &VirtualMachine) -> PyObjectRef { + fn id(&self, vm: &VirtualMachine) -> PyBytesRef { unsafe { let mut len: libc::c_uint = 0; let id_ptr = sys::SSL_SESSION_get_id(self.session, &mut len); let id_slice = std::slice::from_raw_parts(id_ptr, len as usize); - vm.ctx.new_bytes(id_slice.to_vec()).into() + vm.ctx.new_bytes(id_slice.to_vec()) } } @@ -1568,23 +1967,39 @@ mod _ssl { "certificate verify failed" => "CERTIFICATE_VERIFY_FAILED", _ => default_errstr, }; - let msg = if let Some(lib) = e.library() { - // add `library` attribute - let attr_name = vm.ctx.as_ref().intern_str("library"); - cls.set_attr(attr_name, vm.ctx.new_str(lib).into()); + + // Build message + let lib_obj = e.library(); + let msg = if let Some(lib) = lib_obj { format!("[{lib}] {errstr} ({file}:{line})") } else { format!("{errstr} ({file}:{line})") }; - // add `reason` attribute - let attr_name = vm.ctx.as_ref().intern_str("reason"); - cls.set_attr(attr_name, vm.ctx.new_str(errstr).into()); + // Create exception instance let reason = sys::ERR_GET_REASON(e.code()); - vm.new_exception( + let exc = vm.new_exception( cls, vec![vm.ctx.new_int(reason).into(), vm.ctx.new_str(msg).into()], - ) + ); + + // Set attributes on instance, not class + let exc_obj: PyObjectRef = exc.into(); + + // Set reason attribute (always set, even if just the error string) + let reason_value = vm.ctx.new_str(errstr); + let _ = exc_obj.set_attr("reason", reason_value, vm); + + // Set library attribute (None if not available) + let library_value: PyObjectRef = if let Some(lib) = lib_obj { + vm.ctx.new_str(lib).into() + } else { + vm.ctx.none() + }; + let _ = exc_obj.set_attr("library", library_value, vm); + + // Convert back to PyBaseExceptionRef + exc_obj.downcast().unwrap() } None => vm.new_exception_empty(cls), } @@ -1681,7 +2096,8 @@ mod _ssl { dict.set_item("subject", name_to_py(cert.subject_name())?, vm)?; dict.set_item("issuer", name_to_py(cert.issuer_name())?, vm)?; - dict.set_item("version", vm.new_pyobj(cert.version()), vm)?; + // X.509 version: OpenSSL uses 0-based (0=v1, 1=v2, 2=v3) but Python uses 1-based (1=v1, 2=v2, 3=v3) + dict.set_item("version", vm.new_pyobj(cert.version() + 1), vm)?; let serial_num = cert .serial_number() @@ -1894,21 +2310,18 @@ mod windows { Cryptography::PKCS_7_ASN_ENCODING => vm.new_pyobj(ascii!("pkcs_7_asn")), other => vm.new_pyobj(other), }; - let usage: PyObjectRef = match c.valid_uses()? { + let usage: PyObjectRef = match c.valid_uses().map_err(|e| e.to_pyexception(vm))? { ValidUses::All => vm.ctx.new_bool(true).into(), ValidUses::Oids(oids) => PyFrozenSet::from_iter( vm, oids.into_iter().map(|oid| vm.ctx.new_str(oid).into()), - ) - .unwrap() + )? .into_ref(&vm.ctx) .into(), }; Ok(vm.new_tuple((cert, enc_type, usage)).into()) }); - let certs = certs - .collect::<Result<Vec<_>, _>>() - .map_err(|e: std::io::Error| e.to_pyexception(vm))?; + let certs: Vec<PyObjectRef> = certs.collect::<PyResult<Vec<_>>>()?; Ok(certs) } } From 37915336ea4567cecfb9262832bd65ef2355b56a Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Sun, 26 Oct 2025 23:17:40 +0900 Subject: [PATCH 315/819] Expand #[pyexception] macro working with module attr --- derive-impl/src/pyclass.rs | 8 +++++++- derive-impl/src/pymodule.rs | 10 +++++++--- derive-impl/src/util.rs | 3 ++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/derive-impl/src/pyclass.rs b/derive-impl/src/pyclass.rs index 6c589896e63..272ed7e5fa4 100644 --- a/derive-impl/src/pyclass.rs +++ b/derive-impl/src/pyclass.rs @@ -551,7 +551,13 @@ pub(crate) fn impl_pyexception(attr: PunctuatedNestedMeta, item: Item) -> Result } } } else { - quote! {} + quote! { + impl ::rustpython_vm::PyPayload for #ident { + fn class(_ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { + <Self as ::rustpython_vm::class::StaticType>::static_type() + } + } + } }; let impl_pyclass = if class_meta.has_impl()? { quote! { diff --git a/derive-impl/src/pymodule.rs b/derive-impl/src/pymodule.rs index 9e052cd1ff9..a8588a762ef 100644 --- a/derive-impl/src/pymodule.rs +++ b/derive-impl/src/pymodule.rs @@ -16,6 +16,7 @@ enum AttrName { Function, Attr, Class, + Exception, } impl std::fmt::Display for AttrName { @@ -24,6 +25,7 @@ impl std::fmt::Display for AttrName { Self::Function => "pyfunction", Self::Attr => "pyattr", Self::Class => "pyclass", + Self::Exception => "pyexception", }; s.fmt(f) } @@ -37,6 +39,7 @@ impl FromStr for AttrName { "pyfunction" => Self::Function, "pyattr" => Self::Attr, "pyclass" => Self::Class, + "pyexception" => Self::Exception, s => { return Err(s.to_owned()); } @@ -232,7 +235,8 @@ fn module_item_new( inner: ContentItemInner { index, attr_name }, py_attrs, }), - AttrName::Class => Box::new(ClassItem { + // pyexception is treated like pyclass - both define types + AttrName::Class | AttrName::Exception => Box::new(ClassItem { inner: ContentItemInner { index, attr_name }, py_attrs, }), @@ -302,13 +306,13 @@ where result.push(item_new(i, attr_name, Vec::new())); } else { match attr_name { - AttrName::Class | AttrName::Function => { + AttrName::Class | AttrName::Function | AttrName::Exception => { result.push(item_new(i, attr_name, py_attrs.clone())); } _ => { bail_span!( attr, - "#[pyclass] or #[pyfunction] only can follow #[pyattr]", + "#[pyclass], #[pyfunction], or #[pyexception] can follow #[pyattr]", ) } } diff --git a/derive-impl/src/util.rs b/derive-impl/src/util.rs index 47a0606f05d..f7f0d28fb92 100644 --- a/derive-impl/src/util.rs +++ b/derive-impl/src/util.rs @@ -446,7 +446,8 @@ impl ClassItemMeta { pub(crate) struct ExceptionItemMeta(ClassItemMeta); impl ItemMeta for ExceptionItemMeta { - const ALLOWED_NAMES: &'static [&'static str] = &["name", "base", "unhashable", "ctx", "impl"]; + const ALLOWED_NAMES: &'static [&'static str] = + &["module", "name", "base", "unhashable", "ctx", "impl"]; fn from_inner(inner: ItemMetaInner) -> Self { Self(ClassItemMeta(inner)) From 517b55b8b525009c38b8e487bed2436b355eb533 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Sun, 26 Oct 2025 23:20:27 +0900 Subject: [PATCH 316/819] pyssl errors --- derive-impl/src/pyclass.rs | 4 +- stdlib/src/ssl.rs | 156 ++++++++++++++++++++++--------------- vm/src/exceptions.rs | 6 +- 3 files changed, 99 insertions(+), 67 deletions(-) diff --git a/derive-impl/src/pyclass.rs b/derive-impl/src/pyclass.rs index 272ed7e5fa4..99cddc84623 100644 --- a/derive-impl/src/pyclass.rs +++ b/derive-impl/src/pyclass.rs @@ -619,7 +619,7 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R } else { quote! { #[pyslot] - pub(crate) fn slot_new( + pub fn slot_new( cls: ::rustpython_vm::builtins::PyTypeRef, args: ::rustpython_vm::function::FuncArgs, vm: &::rustpython_vm::VirtualMachine, @@ -640,7 +640,7 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R quote! { #[pyslot] #[pymethod(name="__init__")] - pub(crate) fn slot_init( + pub fn slot_init( zelf: ::rustpython_vm::PyObjectRef, args: ::rustpython_vm::function::FuncArgs, vm: &::rustpython_vm::VirtualMachine, diff --git a/stdlib/src/ssl.rs b/stdlib/src/ssl.rs index bf77d6b6907..c642d6e087a 100644 --- a/stdlib/src/ssl.rs +++ b/stdlib/src/ssl.rs @@ -38,8 +38,10 @@ mod _ssl { }, socket::{self, PySocket}, vm::{ - Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyTypeRef, PyWeak}, + AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + builtins::{ + PyBaseExceptionRef, PyBytesRef, PyListRef, PyOSError, PyStrRef, PyTypeRef, PyWeak, + }, class_or_notimplemented, convert::{ToPyException, ToPyObject}, exceptions, @@ -198,63 +200,84 @@ mod _ssl { parse_version_info(openssl_api_version) } + // SSL Exception Types + /// An error occurred in the SSL implementation. - #[pyattr(name = "SSLError", once)] - fn ssl_error(vm: &VirtualMachine) -> PyTypeRef { - vm.ctx.new_exception_type( - "ssl", - "SSLError", - Some(vec![vm.ctx.exceptions.os_error.to_owned()]), - ) + #[pyattr] + #[pyexception(name = "SSLError", base = "PyOSError")] + #[derive(Debug)] + pub struct PySslError {} + + #[pyexception] + impl PySslError { + // Returns strerror attribute if available, otherwise str(args) + #[pymethod] + fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { + // Try to get strerror attribute first (OSError compatibility) + if let Ok(strerror) = exc.as_object().get_attr("strerror", vm) + && !vm.is_none(&strerror) + { + return strerror.str(vm); + } + + // Otherwise return str(args) + exc.args().as_object().str(vm) + } } /// A certificate could not be verified. - #[pyattr(name = "SSLCertVerificationError", once)] - fn ssl_cert_verification_error(vm: &VirtualMachine) -> PyTypeRef { - vm.ctx.new_exception_type( - "ssl", - "SSLCertVerificationError", - Some(vec![ - ssl_error(vm), - vm.ctx.exceptions.value_error.to_owned(), - ]), - ) - } + #[pyattr] + #[pyexception(name = "SSLCertVerificationError", base = "PySslError")] + #[derive(Debug)] + pub struct PySslCertVerificationError {} + + #[pyexception] + impl PySslCertVerificationError {} /// SSL/TLS session closed cleanly. - #[pyattr(name = "SSLZeroReturnError", once)] - fn ssl_zero_return_error(vm: &VirtualMachine) -> PyTypeRef { - vm.ctx - .new_exception_type("ssl", "SSLZeroReturnError", Some(vec![ssl_error(vm)])) - } + #[pyattr] + #[pyexception(name = "SSLZeroReturnError", base = "PySslError")] + #[derive(Debug)] + pub struct PySslZeroReturnError {} - /// Non-blocking SSL socket needs to read more data before the requested operation can be completed. - #[pyattr(name = "SSLWantReadError", once)] - fn ssl_want_read_error(vm: &VirtualMachine) -> PyTypeRef { - vm.ctx - .new_exception_type("ssl", "SSLWantReadError", Some(vec![ssl_error(vm)])) - } + #[pyexception] + impl PySslZeroReturnError {} - /// Non-blocking SSL socket needs to write more data before the requested operation can be completed. - #[pyattr(name = "SSLWantWriteError", once)] - fn ssl_want_write_error(vm: &VirtualMachine) -> PyTypeRef { - vm.ctx - .new_exception_type("ssl", "SSLWantWriteError", Some(vec![ssl_error(vm)])) - } + /// Non-blocking SSL socket needs to read more data. + #[pyattr] + #[pyexception(name = "SSLWantReadError", base = "PySslError")] + #[derive(Debug)] + pub struct PySslWantReadError {} + + #[pyexception] + impl PySslWantReadError {} + + /// Non-blocking SSL socket needs to write more data. + #[pyattr] + #[pyexception(name = "SSLWantWriteError", base = "PySslError")] + #[derive(Debug)] + pub struct PySslWantWriteError {} + + #[pyexception] + impl PySslWantWriteError {} /// System error when attempting SSL operation. - #[pyattr(name = "SSLSyscallError", once)] - fn ssl_syscall_error(vm: &VirtualMachine) -> PyTypeRef { - vm.ctx - .new_exception_type("ssl", "SSLSyscallError", Some(vec![ssl_error(vm)])) - } + #[pyattr] + #[pyexception(name = "SSLSyscallError", base = "PySslError")] + #[derive(Debug)] + pub struct PySslSyscallError {} + + #[pyexception] + impl PySslSyscallError {} /// SSL/TLS connection terminated abruptly. - #[pyattr(name = "SSLEOFError", once)] - fn ssl_eof_error(vm: &VirtualMachine) -> PyTypeRef { - vm.ctx - .new_exception_type("ssl", "SSLEOFError", Some(vec![ssl_error(vm)])) - } + #[pyattr] + #[pyexception(name = "SSLEOFError", base = "PySslError")] + #[derive(Debug)] + pub struct PySslEOFError {} + + #[pyexception] + impl PySslEOFError {} type OpensslVersionInfo = (u8, u8, u8, u8, u8); const fn parse_version_info(mut n: i64) -> OpensslVersionInfo { @@ -617,7 +640,10 @@ mod _ssl { return Err(exceptions::cstring_error(vm)); } self.builder().set_cipher_list(ciphers).map_err(|_| { - vm.new_exception_msg(ssl_error(vm), "No cipher can be selected.".to_owned()) + vm.new_exception_msg( + PySslError::class(&vm.ctx).to_owned(), + "No cipher can be selected.".to_owned(), + ) }) } @@ -744,13 +770,13 @@ mod _ssl { if clear != 0 && sys::X509_VERIFY_PARAM_clear_flags(param, clear) == 0 { return Err(vm.new_exception_msg( - ssl_error(vm), + PySslError::class(&vm.ctx).to_owned(), "Failed to clear verify flags".to_owned(), )); } if set != 0 && sys::X509_VERIFY_PARAM_set_flags(param, set) == 0 { return Err(vm.new_exception_msg( - ssl_error(vm), + PySslError::class(&vm.ctx).to_owned(), "Failed to set verify flags".to_owned(), )); } @@ -934,13 +960,13 @@ mod _ssl { // validate socket type and context protocol if !args.server_side && zelf.protocol == SslVersion::TlsServer { return Err(vm.new_exception_msg( - ssl_error(vm), + PySslError::class(&vm.ctx).to_owned(), "Cannot create a client socket with a PROTOCOL_TLS_SERVER context".to_owned(), )); } if args.server_side && zelf.protocol == SslVersion::TlsClient { return Err(vm.new_exception_msg( - ssl_error(vm), + PySslError::class(&vm.ctx).to_owned(), "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context".to_owned(), )); } @@ -1124,7 +1150,7 @@ mod _ssl { fn socket_closed_error(vm: &VirtualMachine) -> PyBaseExceptionRef { vm.new_exception_msg( - ssl_error(vm), + PySslError::class(&vm.ctx).to_owned(), "Underlying socket has been closed.".to_owned(), ) } @@ -1390,7 +1416,7 @@ mod _ssl { let result = unsafe { SSL_verify_client_post_handshake(stream.ssl().as_ptr()) }; if result == 0 { Err(vm.new_exception_msg( - ssl_error(vm), + PySslError::class(&vm.ctx).to_owned(), "Post-handshake authentication failed".to_owned(), )) } else { @@ -1422,7 +1448,7 @@ mod _ssl { // Return the underlying socket } else { return Err(vm.new_exception_msg( - ssl_error(vm), + PySslError::class(&vm.ctx).to_owned(), format!("SSL shutdown failed: error code {}", err), )); } @@ -1854,7 +1880,7 @@ mod _ssl { fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<i32> { if self.eof_written.load() { return Err(vm.new_exception_msg( - ssl_error(vm), + PySslError::class(&vm.ctx).to_owned(), "cannot write() after write_eof()".to_owned(), )); } @@ -1953,7 +1979,7 @@ mod _ssl { #[track_caller] fn convert_openssl_error(vm: &VirtualMachine, err: ErrorStack) -> PyBaseExceptionRef { - let cls = ssl_error(vm); + let cls = PySslError::class(&vm.ctx).to_owned(); match err.errors().last() { Some(e) => { let caller = std::panic::Location::caller(); @@ -2012,25 +2038,31 @@ mod _ssl { let e = e.borrow(); let (cls, msg) = match e.code() { ssl::ErrorCode::WANT_READ => ( - vm.class("_ssl", "SSLWantReadError"), + PySslWantReadError::class(&vm.ctx).to_owned(), "The operation did not complete (read)", ), ssl::ErrorCode::WANT_WRITE => ( - vm.class("_ssl", "SSLWantWriteError"), + PySslWantWriteError::class(&vm.ctx).to_owned(), "The operation did not complete (write)", ), ssl::ErrorCode::SYSCALL => match e.io_error() { Some(io_err) => return io_err.to_pyexception(vm), None => ( - vm.class("_ssl", "SSLSyscallError"), + PySslSyscallError::class(&vm.ctx).to_owned(), "EOF occurred in violation of protocol", ), }, ssl::ErrorCode::SSL => match e.ssl_error() { Some(e) => return convert_openssl_error(vm, e.clone()), - None => (ssl_error(vm), "A failure in the SSL library occurred"), + None => ( + PySslError::class(&vm.ctx).to_owned(), + "A failure in the SSL library occurred", + ), }, - _ => (ssl_error(vm), "A failure in the SSL library occurred"), + _ => ( + PySslError::class(&vm.ctx).to_owned(), + "A failure in the SSL library occurred", + ), }; vm.new_exception_msg(cls, msg.to_owned()) } diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 20bf467c45b..029b12f5a47 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1450,7 +1450,7 @@ pub(super) mod types { } #[cfg(not(target_arch = "wasm32"))] #[pyslot] - fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + pub fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { // We need this method, because of how `CPython` copies `init` // from `BaseException` in `SimpleExtendsException` macro. // See: `BaseException_new` @@ -1465,12 +1465,12 @@ pub(super) mod types { } #[cfg(target_arch = "wasm32")] #[pyslot] - fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + pub fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { PyBaseException::slot_new(cls, args, vm) } #[pyslot] #[pymethod(name = "__init__")] - fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + pub fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { let len = args.args.len(); let mut new_args = args; if (3..=5).contains(&len) { From 9a2792a44b6362554b7bd76b3f91ee88362299df Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:31:59 +0900 Subject: [PATCH 317/819] PySSLCertificate (#6219) --- Lib/test/test_urllib2_localnet.py | 4 - stdlib/src/ssl.rs | 247 ++++++++++++------------------ stdlib/src/ssl/cert.rs | 234 ++++++++++++++++++++++++++++ 3 files changed, 334 insertions(+), 151 deletions(-) create mode 100644 stdlib/src/ssl/cert.rs diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py index 3d5ca2faab7..3b309b78695 100644 --- a/Lib/test/test_urllib2_localnet.py +++ b/Lib/test/test_urllib2_localnet.py @@ -568,8 +568,6 @@ def test_200_with_parameters(self): self.assertEqual(data, expected_response) self.assertEqual(handler.requests, ["/bizarre", b"get=with_feeling"]) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") def test_https(self): handler = self.start_https_server() @@ -577,8 +575,6 @@ def test_https(self): data = self.urlopen("https://localhost:%s/bizarre" % handler.port, context=context) self.assertEqual(data, b"we care a bit") - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") def test_https_with_cafile(self): handler = self.start_https_server(certfile=CERT_localhost) diff --git a/stdlib/src/ssl.rs b/stdlib/src/ssl.rs index c642d6e087a..052248a5832 100644 --- a/stdlib/src/ssl.rs +++ b/stdlib/src/ssl.rs @@ -1,5 +1,7 @@ // spell-checker:disable +mod cert; + use crate::vm::{PyRef, VirtualMachine, builtins::PyModule}; use openssl_probe::ProbeResult; @@ -26,15 +28,12 @@ cfg_if::cfg_if! { } #[allow(non_upper_case_globals)] -#[pymodule(with(ossl101, ossl111, windows))] +#[pymodule(with(cert::ssl_cert, ossl101, ossl111, windows))] mod _ssl { use super::{bio, probe}; use crate::{ - common::{ - ascii, - lock::{ - PyMappedRwLockReadGuard, PyMutex, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard, - }, + common::lock::{ + PyMappedRwLockReadGuard, PyMutex, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard, }, socket::{self, PySocket}, vm::{ @@ -43,7 +42,7 @@ mod _ssl { PyBaseExceptionRef, PyBytesRef, PyListRef, PyOSError, PyStrRef, PyTypeRef, PyWeak, }, class_or_notimplemented, - convert::{ToPyException, ToPyObject}, + convert::ToPyException, exceptions, function::{ ArgBytesLike, ArgCallable, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath, @@ -60,7 +59,7 @@ mod _ssl { error::ErrorStack, nid::Nid, ssl::{self, SslContextBuilder, SslOptions, SslVerifyMode}, - x509::{self, X509, X509Ref}, + x509::X509, }; use openssl_sys as sys; use rustpython_vm::ospath::OsPath; @@ -73,6 +72,14 @@ mod _ssl { time::Instant, }; + // Import certificate types from parent module + use super::cert::{self, cert_to_certificate, cert_to_py}; + + // Re-export PySSLCertificate to make it available in the _ssl module + // It will be automatically exposed to Python via #[pyclass] + #[allow(unused_imports)] + use super::cert::PySSLCertificate; + // Constants #[pyattr] use sys::{ @@ -178,6 +185,18 @@ mod _ssl { #[pyattr] const HAS_PSK: bool = true; + // Encoding constants for Certificate.public_bytes() + #[pyattr] + pub(crate) const ENCODING_PEM: i32 = sys::X509_FILETYPE_PEM; + #[pyattr] + pub(crate) const ENCODING_DER: i32 = sys::X509_FILETYPE_ASN1; + #[pyattr] + const ENCODING_PEM_AUX: i32 = sys::X509_FILETYPE_PEM + 0x100; + + // OpenSSL error codes for unexpected EOF detection + const ERR_LIB_SSL: i32 = 20; + const SSL_R_UNEXPECTED_EOF_WHILE_READING: i32 = 294; + // the openssl version from the API headers #[pyattr(name = "OPENSSL_VERSION")] @@ -349,32 +368,6 @@ mod _ssl { fn _nid2obj(nid: Nid) -> Option<Asn1Object> { unsafe { ptr2obj(sys::OBJ_nid2obj(nid.as_raw())) } } - fn obj2txt(obj: &Asn1ObjectRef, no_name: bool) -> Option<String> { - let no_name = i32::from(no_name); - let ptr = obj.as_ptr(); - let b = unsafe { - let buflen = sys::OBJ_obj2txt(std::ptr::null_mut(), 0, ptr, no_name); - assert!(buflen >= 0); - if buflen == 0 { - return None; - } - let buflen = buflen as usize; - let mut buf = Vec::<u8>::with_capacity(buflen + 1); - let ret = sys::OBJ_obj2txt( - buf.as_mut_ptr() as *mut libc::c_char, - buf.capacity() as _, - ptr, - no_name, - ); - assert!(ret >= 0); - // SAFETY: OBJ_obj2txt initialized the buffer successfully - buf.set_len(buflen); - buf - }; - let s = String::from_utf8(b) - .unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned()); - Some(s) - } type PyNid = (libc::c_int, String, String, Option<String>); fn obj2py(obj: &Asn1ObjectRef, vm: &VirtualMachine) -> PyResult<PyNid> { @@ -387,7 +380,12 @@ mod _ssl { .long_name() .map_err(|_| vm.new_value_error("NID has no long name".to_owned()))? .to_owned(); - Ok((nid.as_raw(), short_name, long_name, obj2txt(obj, true))) + Ok(( + nid.as_raw(), + short_name, + long_name, + cert::obj2txt(obj, true), + )) } #[derive(FromArgs)] @@ -1219,46 +1217,54 @@ mod _ssl { } #[pymethod] - fn get_unverified_chain(&self, vm: &VirtualMachine) -> Option<PyObjectRef> { + fn get_unverified_chain(&self, vm: &VirtualMachine) -> PyResult<Option<PyListRef>> { let stream = self.stream.read(); - let chain = stream.ssl().peer_cert_chain()?; + let Some(chain) = stream.ssl().peer_cert_chain() else { + return Ok(None); + }; + // Return Certificate objects let certs: Vec<PyObjectRef> = chain .iter() - .filter_map(|cert| cert.to_der().ok().map(|der| vm.ctx.new_bytes(der).into())) - .collect(); - - Some(vm.ctx.new_list(certs).into()) + .map(|cert| unsafe { + sys::X509_up_ref(cert.as_ptr()); + let owned = X509::from_ptr(cert.as_ptr()); + cert_to_certificate(vm, owned) + }) + .collect::<PyResult<_>>()?; + Ok(Some(vm.ctx.new_list(certs))) } #[pymethod] - fn get_verified_chain(&self, vm: &VirtualMachine) -> Option<PyListRef> { + fn get_verified_chain(&self, vm: &VirtualMachine) -> PyResult<Option<PyListRef>> { let stream = self.stream.read(); unsafe { let chain = sys::SSL_get0_verified_chain(stream.ssl().as_ptr()); if chain.is_null() { - return None; + return Ok(None); } let num_certs = sys::OPENSSL_sk_num(chain as *const _); - let mut certs = Vec::new(); + let mut certs = Vec::with_capacity(num_certs as usize); + // Return Certificate objects for i in 0..num_certs { let cert_ptr = sys::OPENSSL_sk_value(chain as *const _, i) as *mut sys::X509; if cert_ptr.is_null() { continue; } - let cert = X509Ref::from_ptr(cert_ptr); - if let Ok(der) = cert.to_der() { - certs.push(vm.ctx.new_bytes(der).into()); - } + // Clone the X509 certificate to create an owned copy + sys::X509_up_ref(cert_ptr); + let owned_cert = X509::from_ptr(cert_ptr); + let cert_obj = cert_to_certificate(vm, owned_cert)?; + certs.push(cert_obj); } - if certs.is_empty() { + Ok(if certs.is_empty() { None } else { Some(vm.ctx.new_list(certs)) - } + }) } } @@ -1978,7 +1984,10 @@ mod _ssl { } #[track_caller] - fn convert_openssl_error(vm: &VirtualMachine, err: ErrorStack) -> PyBaseExceptionRef { + pub(crate) fn convert_openssl_error( + vm: &VirtualMachine, + err: ErrorStack, + ) -> PyBaseExceptionRef { let cls = PySslError::class(&vm.ctx).to_owned(); match err.errors().last() { Some(e) => { @@ -2047,18 +2056,49 @@ mod _ssl { ), ssl::ErrorCode::SYSCALL => match e.io_error() { Some(io_err) => return io_err.to_pyexception(vm), - None => ( - PySslSyscallError::class(&vm.ctx).to_owned(), - "EOF occurred in violation of protocol", - ), + // When no I/O error and OpenSSL error queue is empty, + // this is an EOF in violation of protocol -> SSLEOFError + // Need to set args[0] = SSL_ERROR_EOF for suppress_ragged_eofs check + None => { + return vm.new_exception( + PySslEOFError::class(&vm.ctx).to_owned(), + vec![ + vm.ctx.new_int(SSL_ERROR_EOF).into(), + vm.ctx + .new_str("EOF occurred in violation of protocol") + .into(), + ], + ); + } }, - ssl::ErrorCode::SSL => match e.ssl_error() { - Some(e) => return convert_openssl_error(vm, e.clone()), - None => ( + ssl::ErrorCode::SSL => { + // Check for OpenSSL 3.0 SSL_R_UNEXPECTED_EOF_WHILE_READING + if let Some(ssl_err) = e.ssl_error() { + // In OpenSSL 3.0+, unexpected EOF is reported as SSL_ERROR_SSL + // with this specific reason code instead of SSL_ERROR_SYSCALL + unsafe { + let err_code = sys::ERR_peek_last_error(); + let reason = sys::ERR_GET_REASON(err_code); + let lib = sys::ERR_GET_LIB(err_code); + if lib == ERR_LIB_SSL && reason == SSL_R_UNEXPECTED_EOF_WHILE_READING { + return vm.new_exception( + vm.class("_ssl", "SSLEOFError"), + vec![ + vm.ctx.new_int(SSL_ERROR_EOF).into(), + vm.ctx + .new_str("EOF occurred in violation of protocol") + .into(), + ], + ); + } + } + return convert_openssl_error(vm, ssl_err.clone()); + } + ( PySslError::class(&vm.ctx).to_owned(), "A failure in the SSL library occurred", - ), - }, + ) + } _ => ( PySslError::class(&vm.ctx).to_owned(), "A failure in the SSL library occurred", @@ -2106,93 +2146,6 @@ mod _ssl { (cipher.name(), cipher.version(), cipher.bits().secret) } - fn cert_to_py(vm: &VirtualMachine, cert: &X509Ref, binary: bool) -> PyResult { - let r = if binary { - let b = cert.to_der().map_err(|e| convert_openssl_error(vm, e))?; - vm.ctx.new_bytes(b).into() - } else { - let dict = vm.ctx.new_dict(); - - let name_to_py = |name: &x509::X509NameRef| -> PyResult { - let list = name - .entries() - .map(|entry| { - let txt = obj2txt(entry.object(), false).to_pyobject(vm); - let data = vm.ctx.new_str(entry.data().as_utf8()?.to_owned()); - Ok(vm.new_tuple(((txt, data),)).into()) - }) - .collect::<Result<_, _>>() - .map_err(|e| convert_openssl_error(vm, e))?; - Ok(vm.ctx.new_tuple(list).into()) - }; - - dict.set_item("subject", name_to_py(cert.subject_name())?, vm)?; - dict.set_item("issuer", name_to_py(cert.issuer_name())?, vm)?; - // X.509 version: OpenSSL uses 0-based (0=v1, 1=v2, 2=v3) but Python uses 1-based (1=v1, 2=v2, 3=v3) - dict.set_item("version", vm.new_pyobj(cert.version() + 1), vm)?; - - let serial_num = cert - .serial_number() - .to_bn() - .and_then(|bn| bn.to_hex_str()) - .map_err(|e| convert_openssl_error(vm, e))?; - dict.set_item( - "serialNumber", - vm.ctx.new_str(serial_num.to_owned()).into(), - vm, - )?; - - dict.set_item( - "notBefore", - vm.ctx.new_str(cert.not_before().to_string()).into(), - vm, - )?; - dict.set_item( - "notAfter", - vm.ctx.new_str(cert.not_after().to_string()).into(), - vm, - )?; - - #[allow(clippy::manual_map)] - if let Some(names) = cert.subject_alt_names() { - let san = names - .iter() - .filter_map(|gen_name| { - if let Some(email) = gen_name.email() { - Some(vm.new_tuple((ascii!("email"), email)).into()) - } else if let Some(dnsname) = gen_name.dnsname() { - Some(vm.new_tuple((ascii!("DNS"), dnsname)).into()) - } else if let Some(ip) = gen_name.ipaddress() { - Some( - vm.new_tuple(( - ascii!("IP Address"), - String::from_utf8_lossy(ip).into_owned(), - )) - .into(), - ) - } else { - // TODO: convert every type of general name: - // https://github.com/python/cpython/blob/3.6/Modules/_ssl.c#L1092-L1231 - None - } - }) - .collect(); - dict.set_item("subjectAltName", vm.ctx.new_tuple(san).into(), vm)?; - }; - - dict.into() - }; - Ok(r) - } - - #[pyfunction] - fn _test_decode_cert(path: FsPath, vm: &VirtualMachine) -> PyResult { - let path = path.to_path_buf(vm)?; - let pem = std::fs::read(path).map_err(|e| e.to_pyexception(vm))?; - let x509 = X509::from_pem(&pem).map_err(|e| convert_openssl_error(vm, e))?; - cert_to_py(vm, &x509, false) - } - impl Read for SocketStream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { let mut socket: &PySocket = &self.0; diff --git a/stdlib/src/ssl/cert.rs b/stdlib/src/ssl/cert.rs new file mode 100644 index 00000000000..9a77dee1eaf --- /dev/null +++ b/stdlib/src/ssl/cert.rs @@ -0,0 +1,234 @@ +pub(super) use ssl_cert::{PySSLCertificate, cert_to_certificate, cert_to_py, obj2txt}; + +// Certificate type for SSL module + +#[pymodule(sub)] +pub(crate) mod ssl_cert { + use crate::{ + common::ascii, + vm::{ + PyObjectRef, PyPayload, PyResult, VirtualMachine, + convert::{ToPyException, ToPyObject}, + function::{FsPath, OptionalArg}, + }, + }; + use foreign_types_shared::ForeignTypeRef; + use openssl::{ + asn1::Asn1ObjectRef, + x509::{self, X509, X509Ref}, + }; + use openssl_sys as sys; + use std::fmt; + + // Import constants and error converter from _ssl module + use crate::ssl::_ssl::{ENCODING_DER, ENCODING_PEM, convert_openssl_error}; + + pub(crate) fn obj2txt(obj: &Asn1ObjectRef, no_name: bool) -> Option<String> { + let no_name = i32::from(no_name); + let ptr = obj.as_ptr(); + let b = unsafe { + let buflen = sys::OBJ_obj2txt(std::ptr::null_mut(), 0, ptr, no_name); + assert!(buflen >= 0); + if buflen == 0 { + return None; + } + let buflen = buflen as usize; + let mut buf = Vec::<u8>::with_capacity(buflen + 1); + let ret = sys::OBJ_obj2txt( + buf.as_mut_ptr() as *mut libc::c_char, + buf.capacity() as _, + ptr, + no_name, + ); + assert!(ret >= 0); + // SAFETY: OBJ_obj2txt initialized the buffer successfully + buf.set_len(buflen); + buf + }; + let s = String::from_utf8(b) + .unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned()); + Some(s) + } + + #[pyattr] + #[pyclass(module = "ssl", name = "Certificate")] + #[derive(PyPayload)] + pub(crate) struct PySSLCertificate { + cert: X509, + } + + impl fmt::Debug for PySSLCertificate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("Certificate") + } + } + + #[pyclass] + impl PySSLCertificate { + #[pymethod] + fn public_bytes( + &self, + format: OptionalArg<i32>, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + let format = format.unwrap_or(ENCODING_PEM); + + match format { + x if x == ENCODING_DER => { + // DER encoding + let der = self + .cert + .to_der() + .map_err(|e| convert_openssl_error(vm, e))?; + Ok(vm.ctx.new_bytes(der).into()) + } + x if x == ENCODING_PEM => { + // PEM encoding + let pem = self + .cert + .to_pem() + .map_err(|e| convert_openssl_error(vm, e))?; + Ok(vm.ctx.new_bytes(pem).into()) + } + _ => Err(vm.new_value_error("Unsupported format".to_owned())), + } + } + + #[pymethod] + fn get_info(&self, vm: &VirtualMachine) -> PyResult { + cert_to_dict(vm, &self.cert) + } + } + + fn name_to_py(vm: &VirtualMachine, name: &x509::X509NameRef) -> PyResult { + let list = name + .entries() + .map(|entry| { + let txt = obj2txt(entry.object(), false).to_pyobject(vm); + let asn1_str = entry.data(); + let data_bytes = asn1_str.as_slice(); + let data = match std::str::from_utf8(data_bytes) { + Ok(s) => vm.ctx.new_str(s.to_owned()), + Err(_) => vm + .ctx + .new_str(String::from_utf8_lossy(data_bytes).into_owned()), + }; + Ok(vm.new_tuple(((txt, data),)).into()) + }) + .collect::<Result<_, _>>()?; + Ok(vm.ctx.new_tuple(list).into()) + } + + // Helper to convert X509 to dict (for getpeercert with binary=False) + fn cert_to_dict(vm: &VirtualMachine, cert: &X509Ref) -> PyResult { + let dict = vm.ctx.new_dict(); + + dict.set_item("subject", name_to_py(vm, cert.subject_name())?, vm)?; + dict.set_item("issuer", name_to_py(vm, cert.issuer_name())?, vm)?; + // X.509 version: OpenSSL uses 0-based (0=v1, 1=v2, 2=v3) but Python uses 1-based (1=v1, 2=v2, 3=v3) + dict.set_item("version", vm.new_pyobj(cert.version() + 1), vm)?; + + let serial_num = cert + .serial_number() + .to_bn() + .and_then(|bn| bn.to_hex_str()) + .map_err(|e| convert_openssl_error(vm, e))?; + dict.set_item( + "serialNumber", + vm.ctx.new_str(serial_num.to_owned()).into(), + vm, + )?; + + dict.set_item( + "notBefore", + vm.ctx.new_str(cert.not_before().to_string()).into(), + vm, + )?; + dict.set_item( + "notAfter", + vm.ctx.new_str(cert.not_after().to_string()).into(), + vm, + )?; + + if let Some(names) = cert.subject_alt_names() { + let san: Vec<PyObjectRef> = names + .iter() + .map(|gen_name| { + if let Some(email) = gen_name.email() { + vm.new_tuple((ascii!("email"), email)).into() + } else if let Some(dnsname) = gen_name.dnsname() { + vm.new_tuple((ascii!("DNS"), dnsname)).into() + } else if let Some(ip) = gen_name.ipaddress() { + // Parse IP address properly (IPv4 or IPv6) + let ip_str = if ip.len() == 4 { + // IPv4 + format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]) + } else if ip.len() == 16 { + // IPv6 - format like: "X:X:X:X:X:X:X:X" (not compressed) + format!( + "{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}", + (ip[0] as u16) << 8 | ip[1] as u16, + (ip[2] as u16) << 8 | ip[3] as u16, + (ip[4] as u16) << 8 | ip[5] as u16, + (ip[6] as u16) << 8 | ip[7] as u16, + (ip[8] as u16) << 8 | ip[9] as u16, + (ip[10] as u16) << 8 | ip[11] as u16, + (ip[12] as u16) << 8 | ip[13] as u16, + (ip[14] as u16) << 8 | ip[15] as u16 + ) + } else { + // Fallback for unexpected length + String::from_utf8_lossy(ip).into_owned() + }; + vm.new_tuple((ascii!("IP Address"), ip_str)).into() + } else if let Some(uri) = gen_name.uri() { + vm.new_tuple((ascii!("URI"), uri)).into() + } else { + // Handle DirName, Registered ID, and othername + // Check if this is a directory name + if let Some(dirname) = gen_name.directory_name() + && let Ok(py_name) = name_to_py(vm, dirname) + { + return vm.new_tuple((ascii!("DirName"), py_name)).into(); + } + + // TODO: Handle Registered ID (GEN_RID) + // CPython implementation uses i2t_ASN1_OBJECT to convert OID + // This requires accessing GENERAL_NAME union which is complex in Rust + // For now, we return <unsupported> for unhandled types + + // For othername and other unsupported types + vm.new_tuple((ascii!("othername"), ascii!("<unsupported>"))) + .into() + } + }) + .collect(); + dict.set_item("subjectAltName", vm.ctx.new_tuple(san).into(), vm)?; + }; + + Ok(dict.into()) + } + + // Helper to create Certificate object from X509 + pub(crate) fn cert_to_certificate(vm: &VirtualMachine, cert: X509) -> PyResult { + Ok(PySSLCertificate { cert }.into_ref(&vm.ctx).into()) + } + + // For getpeercert() - returns bytes or dict depending on binary flag + pub(crate) fn cert_to_py(vm: &VirtualMachine, cert: &X509Ref, binary: bool) -> PyResult { + if binary { + let b = cert.to_der().map_err(|e| convert_openssl_error(vm, e))?; + Ok(vm.ctx.new_bytes(b).into()) + } else { + cert_to_dict(vm, cert) + } + } + + #[pyfunction] + pub(crate) fn _test_decode_cert(path: FsPath, vm: &VirtualMachine) -> PyResult { + let path = path.to_path_buf(vm)?; + let pem = std::fs::read(path).map_err(|e| e.to_pyexception(vm))?; + let x509 = X509::from_pem(&pem).map_err(|e| convert_openssl_error(vm, e))?; + cert_to_py(vm, &x509, false) + } +} From d63e44aa3ac56af08170999a0539d82be9a8fc59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:37:14 +0900 Subject: [PATCH 318/819] Bump actions/download-artifact from 5 to 6 (#6220) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c85614369b4..766f760f34e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -145,7 +145,7 @@ jobs: needs: [build, build-wasm] steps: - name: Download Binary Artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: path: bin pattern: rustpython-* From 0e15e771c356118aec1e24ce70794c2405a12192 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:37:37 +0900 Subject: [PATCH 319/819] Bump actions/upload-artifact from 4 to 5 (#6221) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 766f760f34e..43286780ef7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,7 +83,7 @@ jobs: if: runner.os == 'Windows' - name: Upload Binary Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: rustpython-release-${{ runner.os }}-${{ matrix.platform.target }} path: target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}* @@ -105,7 +105,7 @@ jobs: run: cp target/wasm32-wasip1/release/rustpython.wasm target/rustpython-release-wasm32-wasip1.wasm - name: Upload Binary Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: rustpython-release-wasm32-wasip1 path: target/rustpython-release-wasm32-wasip1.wasm From 6b25fe5c95d4b22cc6a1656a4445f04be3e68455 Mon Sep 17 00:00:00 2001 From: Christopher Gambrell <waymer147@gmail.com> Date: Mon, 27 Oct 2025 22:02:24 -0400 Subject: [PATCH 320/819] 5539 - CTRL+Z then Enter will now close shell on Windows. (#6223) * CTRL+Z then Enter will now close shell on Windows. * Additional comment. * Use EOF const * Add cfg(windows) for EOF_CHAR --------- Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com> --- vm/src/readline.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/vm/src/readline.rs b/vm/src/readline.rs index 839ac980034..77402dc6839 100644 --- a/vm/src/readline.rs +++ b/vm/src/readline.rs @@ -76,6 +76,9 @@ mod rustyline_readline { repl: rustyline::Editor<H, rustyline::history::DefaultHistory>, } + #[cfg(windows)] + const EOF_CHAR: &str = "\u{001A}"; + impl<H: Helper> Readline<H> { pub fn new(helper: H) -> Self { use rustyline::*; @@ -88,6 +91,16 @@ mod rustyline_readline { ) .expect("failed to initialize line editor"); repl.set_helper(Some(helper)); + + // Bind CTRL + Z to insert EOF character on Windows + #[cfg(windows)] + { + repl.bind_sequence( + KeyEvent::new('z', Modifiers::CTRL), + EventHandler::Simple(Cmd::Insert(1, EOF_CHAR.into())), + ); + } + Self { repl } } @@ -115,7 +128,19 @@ mod rustyline_readline { use rustyline::error::ReadlineError; loop { break match self.repl.readline(prompt) { - Ok(line) => ReadlineResult::Line(line), + Ok(line) => { + // Check for CTRL + Z on Windows + #[cfg(windows)] + { + use std::io::IsTerminal; + + let trimmed = line.trim_end_matches(&['\r', '\n'][..]); + if trimmed == EOF_CHAR && io::stdin().is_terminal() { + return ReadlineResult::Eof; + } + } + ReadlineResult::Line(line) + } Err(ReadlineError::Interrupted) => ReadlineResult::Interrupt, Err(ReadlineError::Eof) => ReadlineResult::Eof, Err(ReadlineError::Io(e)) => ReadlineResult::Io(e), From b6e8a875acbceb0db6ff07f7ac8e1c9c6221ac8a Mon Sep 17 00:00:00 2001 From: Yash Suthar <yashsuthar983@gmail.com> Date: Tue, 28 Oct 2025 08:30:22 +0530 Subject: [PATCH 321/819] =?UTF-8?q?Resolve=20number=20slots=20via=20MRO=20?= =?UTF-8?q?in=20PyNumber=20and=20operator,=20ensure=20inherit=E2=80=A6=20(?= =?UTF-8?q?#6222)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Resolve number slots via MRO in PyNumber and operator, ensure inherited and dynamically added methods are found. Use class().mro_find_map() to mimic the same behaviour as CPython. Signed-off-by: Yash Suthar <yashsuthar983@gmail.com> --- Lib/ctypes/__init__.py | 4 +- vm/src/protocol/number.rs | 197 +++++++++++++++++++++----------------- vm/src/vm/vm_ops.rs | 22 +++-- 3 files changed, 126 insertions(+), 97 deletions(-) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index b8b005061f2..a5d27daff0b 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -299,9 +299,7 @@ def create_unicode_buffer(init, size=None): return buf elif isinstance(init, int): _sys.audit("ctypes.create_unicode_buffer", None, init) - # XXX: RUSTPYTHON - # buftype = c_wchar * init - buftype = c_wchar.__mul__(init) + buftype = c_wchar * init buf = buftype() return buf raise TypeError(init) diff --git a/vm/src/protocol/number.rs b/vm/src/protocol/number.rs index 671877057df..1242ee52795 100644 --- a/vm/src/protocol/number.rs +++ b/vm/src/protocol/number.rs @@ -443,117 +443,140 @@ impl<'a> PyNumber<'a> { // PyNumber_Check pub fn check(obj: &PyObject) -> bool { - let methods = &obj.class().slots.as_number; - methods.int.load().is_some() - || methods.index.load().is_some() - || methods.float.load().is_some() - || obj.downcastable::<PyComplex>() + let cls = &obj.class(); + // TODO: when we finally have a proper slot inheritance, mro_find_map can be removed + // methods.int.load().is_some() + // || methods.index.load().is_some() + // || methods.float.load().is_some() + // || obj.downcastable::<PyComplex>() + let has_number = cls + .mro_find_map(|x| { + let methods = &x.slots.as_number; + if methods.int.load().is_some() + || methods.index.load().is_some() + || methods.float.load().is_some() + { + Some(()) + } else { + None + } + }) + .is_some(); + has_number || obj.downcastable::<PyComplex>() } } impl PyNumber<'_> { // PyIndex_Check pub fn is_index(self) -> bool { - self.class().slots.as_number.index.load().is_some() + self.class() + .mro_find_map(|x| x.slots.as_number.index.load()) + .is_some() } #[inline] pub fn int(self, vm: &VirtualMachine) -> Option<PyResult<PyIntRef>> { - self.class().slots.as_number.int.load().map(|f| { - let ret = f(self, vm)?; - - if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) { - return Ok(ret.to_owned()); - } - - let ret_class = ret.class().to_owned(); - if let Some(ret) = ret.downcast_ref::<PyInt>() { - warnings::warn( - vm.ctx.exceptions.deprecation_warning, - format!( - "__int__ returned non-int (type {ret_class}). \ + self.class() + .mro_find_map(|x| x.slots.as_number.int.load()) + .map(|f| { + let ret = f(self, vm)?; + + if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) { + return Ok(ret.to_owned()); + } + + let ret_class = ret.class().to_owned(); + if let Some(ret) = ret.downcast_ref::<PyInt>() { + warnings::warn( + vm.ctx.exceptions.deprecation_warning, + format!( + "__int__ returned non-int (type {ret_class}). \ The ability to return an instance of a strict subclass of int \ is deprecated, and may be removed in a future version of Python." - ), - 1, - vm, - )?; - - Ok(ret.to_owned()) - } else { - Err(vm.new_type_error(format!( - "{}.__int__ returned non-int(type {})", - self.class(), - ret_class - ))) - } - }) + ), + 1, + vm, + )?; + + Ok(ret.to_owned()) + } else { + Err(vm.new_type_error(format!( + "{}.__int__ returned non-int(type {})", + self.class(), + ret_class + ))) + } + }) } #[inline] pub fn index(self, vm: &VirtualMachine) -> Option<PyResult<PyIntRef>> { - self.class().slots.as_number.index.load().map(|f| { - let ret = f(self, vm)?; - - if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) { - return Ok(ret.to_owned()); - } - - let ret_class = ret.class().to_owned(); - if let Some(ret) = ret.downcast_ref::<PyInt>() { - warnings::warn( - vm.ctx.exceptions.deprecation_warning, - format!( - "__index__ returned non-int (type {ret_class}). \ + self.class() + .mro_find_map(|x| x.slots.as_number.index.load()) + .map(|f| { + let ret = f(self, vm)?; + + if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) { + return Ok(ret.to_owned()); + } + + let ret_class = ret.class().to_owned(); + if let Some(ret) = ret.downcast_ref::<PyInt>() { + warnings::warn( + vm.ctx.exceptions.deprecation_warning, + format!( + "__index__ returned non-int (type {ret_class}). \ The ability to return an instance of a strict subclass of int \ is deprecated, and may be removed in a future version of Python." - ), - 1, - vm, - )?; - - Ok(ret.to_owned()) - } else { - Err(vm.new_type_error(format!( - "{}.__index__ returned non-int(type {})", - self.class(), - ret_class - ))) - } - }) + ), + 1, + vm, + )?; + + Ok(ret.to_owned()) + } else { + Err(vm.new_type_error(format!( + "{}.__index__ returned non-int(type {})", + self.class(), + ret_class + ))) + } + }) } #[inline] pub fn float(self, vm: &VirtualMachine) -> Option<PyResult<PyRef<PyFloat>>> { - self.class().slots.as_number.float.load().map(|f| { - let ret = f(self, vm)?; - - if let Some(ret) = ret.downcast_ref_if_exact::<PyFloat>(vm) { - return Ok(ret.to_owned()); - } - - let ret_class = ret.class().to_owned(); - if let Some(ret) = ret.downcast_ref::<PyFloat>() { - warnings::warn( - vm.ctx.exceptions.deprecation_warning, - format!( - "__float__ returned non-float (type {ret_class}). \ + self.class() + .mro_find_map(|x| x.slots.as_number.float.load()) + .map(|f| { + let ret = f(self, vm)?; + + if let Some(ret) = ret.downcast_ref_if_exact::<PyFloat>(vm) { + return Ok(ret.to_owned()); + } + + let ret_class = ret.class().to_owned(); + if let Some(ret) = ret.downcast_ref::<PyFloat>() { + warnings::warn( + vm.ctx.exceptions.deprecation_warning, + format!( + "__float__ returned non-float (type {ret_class}). \ The ability to return an instance of a strict subclass of float \ is deprecated, and may be removed in a future version of Python." - ), - 1, - vm, - )?; - - Ok(ret.to_owned()) - } else { - Err(vm.new_type_error(format!( - "{}.__float__ returned non-float(type {})", - self.class(), - ret_class - ))) - } - }) + ), + 1, + vm, + )?; + + Ok(ret.to_owned()) + } else { + Err(vm.new_type_error(format!( + "{}.__float__ returned non-float(type {})", + self.class(), + ret_class + ))) + } + }) } } diff --git a/vm/src/vm/vm_ops.rs b/vm/src/vm/vm_ops.rs index 840b79c2589..a5656250c3b 100644 --- a/vm/src/vm/vm_ops.rs +++ b/vm/src/vm/vm_ops.rs @@ -160,11 +160,12 @@ impl VirtualMachine { let class_a = a.class(); let class_b = b.class(); - let slot_a = class_a.slots.as_number.left_binary_op(op_slot); + // Look up number slots across MRO for inheritance + let slot_a = class_a.mro_find_map(|x| x.slots.as_number.left_binary_op(op_slot)); let mut slot_b = None; if !class_a.is(class_b) { - let slot_bb = class_b.slots.as_number.right_binary_op(op_slot); + let slot_bb = class_b.mro_find_map(|x| x.slots.as_number.right_binary_op(op_slot)); if slot_bb.map(|x| x as usize) != slot_a.map(|x| x as usize) { slot_b = slot_bb; } @@ -230,7 +231,10 @@ impl VirtualMachine { iop_slot: PyNumberBinaryOp, op_slot: PyNumberBinaryOp, ) -> PyResult { - if let Some(slot) = a.class().slots.as_number.left_binary_op(iop_slot) { + if let Some(slot) = a + .class() + .mro_find_map(|x| x.slots.as_number.left_binary_op(iop_slot)) + { let x = slot(a, b, self)?; if !x.is(&self.ctx.not_implemented) { return Ok(x); @@ -266,11 +270,12 @@ impl VirtualMachine { let class_b = b.class(); let class_c = c.class(); - let slot_a = class_a.slots.as_number.left_ternary_op(op_slot); + // Look up number slots across MRO for inheritance + let slot_a = class_a.mro_find_map(|x| x.slots.as_number.left_ternary_op(op_slot)); let mut slot_b = None; if !class_a.is(class_b) { - let slot_bb = class_b.slots.as_number.right_ternary_op(op_slot); + let slot_bb = class_b.mro_find_map(|x| x.slots.as_number.right_ternary_op(op_slot)); if slot_bb.map(|x| x as usize) != slot_a.map(|x| x as usize) { slot_b = slot_bb; } @@ -299,7 +304,7 @@ impl VirtualMachine { } } - if let Some(slot_c) = class_c.slots.as_number.left_ternary_op(op_slot) + if let Some(slot_c) = class_c.mro_find_map(|x| x.slots.as_number.left_ternary_op(op_slot)) && slot_a.is_some_and(|slot_a| !std::ptr::fn_addr_eq(slot_a, slot_c)) && slot_b.is_some_and(|slot_b| !std::ptr::fn_addr_eq(slot_b, slot_c)) { @@ -338,7 +343,10 @@ impl VirtualMachine { op_slot: PyNumberTernaryOp, op_str: &str, ) -> PyResult { - if let Some(slot) = a.class().slots.as_number.left_ternary_op(iop_slot) { + if let Some(slot) = a + .class() + .mro_find_map(|x| x.slots.as_number.left_ternary_op(iop_slot)) + { let x = slot(a, b, c, self)?; if !x.is(&self.ctx.not_implemented) { return Ok(x); From 2acc3be6cfb53c18b3ea72cfb159bd5517c60f6f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:00:44 +0900 Subject: [PATCH 322/819] More SSL impl (#6224) * fix ipv6 formattig * consts * fspath * fix set_ecdh_curve * minimum/maximum version * Add SSL_CTX_security_level --- stdlib/src/ssl.rs | 105 +++++++++++++++++++++++++++++++---------- stdlib/src/ssl/cert.rs | 16 +++---- 2 files changed, 88 insertions(+), 33 deletions(-) diff --git a/stdlib/src/ssl.rs b/stdlib/src/ssl.rs index 052248a5832..d151c2948c6 100644 --- a/stdlib/src/ssl.rs +++ b/stdlib/src/ssl.rs @@ -94,9 +94,6 @@ mod _ssl { SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, - // X509_V_FLAG_CRL_CHECK as VERIFY_CRL_CHECK_LEAF, - // sys::X509_V_FLAG_CRL_CHECK|sys::X509_V_FLAG_CRL_CHECK_ALL as VERIFY_CRL_CHECK_CHAIN - // X509_V_FLAG_X509_STRICT as VERIFY_X509_STRICT, SSL_ERROR_ZERO_RETURN, SSL_OP_CIPHER_SERVER_PREFERENCE as OP_CIPHER_SERVER_PREFERENCE, SSL_OP_ENABLE_MIDDLEBOX_COMPAT as OP_ENABLE_MIDDLEBOX_COMPAT, @@ -114,6 +111,11 @@ mod _ssl { X509_V_FLAG_X509_STRICT as VERIFY_X509_STRICT, }; + // CRL verification constants + #[pyattr] + const VERIFY_CRL_CHECK_CHAIN: libc::c_ulong = + sys::X509_V_FLAG_CRL_CHECK | sys::X509_V_FLAG_CRL_CHECK_ALL; + // taken from CPython, should probably be kept up to date with their version if it ever changes #[pyattr] const _DEFAULT_CIPHERS: &str = @@ -631,6 +633,12 @@ mod _ssl { Ok(()) } + #[cfg(ossl110)] + #[pygetset] + fn security_level(&self) -> i32 { + unsafe { SSL_CTX_get_security_level(self.ctx().as_ptr()) } + } + #[pymethod] fn set_ciphers(&self, cipherlist: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { let ciphers = cipherlist.as_str(); @@ -677,19 +685,29 @@ mod _ssl { } #[pymethod] - fn set_ecdh_curve(&self, name: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { + fn set_ecdh_curve( + &self, + name: Either<PyStrRef, ArgBytesLike>, + vm: &VirtualMachine, + ) -> PyResult<()> { use openssl::ec::{EcGroup, EcKey}; - let curve_name = name.as_str(); - if curve_name.contains('\0') { - return Err(exceptions::cstring_error(vm)); - } + // Convert name to CString, supporting both str and bytes + let name_cstr = match name { + Either::A(s) => { + if s.as_str().contains('\0') { + return Err(exceptions::cstring_error(vm)); + } + s.to_cstring(vm)? + } + Either::B(b) => std::ffi::CString::new(b.borrow_buf().to_vec()) + .map_err(|_| exceptions::cstring_error(vm))?, + }; // Find the NID for the curve name using OBJ_sn2nid - let name_cstr = name.to_cstring(vm)?; let nid_raw = unsafe { sys::OBJ_sn2nid(name_cstr.as_ptr()) }; if nid_raw == 0 { - return Err(vm.new_value_error(format!("unknown curve name: {}", curve_name))); + return Err(vm.new_value_error("unknown curve name")); } let nid = Nid::from_raw(nid_raw); @@ -794,6 +812,47 @@ mod _ssl { self.check_hostname.store(ch); } + // PY_PROTO_MINIMUM_SUPPORTED = -2, PY_PROTO_MAXIMUM_SUPPORTED = -1 + #[pygetset] + fn minimum_version(&self) -> i32 { + let ctx = self.ctx(); + let version = unsafe { sys::SSL_CTX_get_min_proto_version(ctx.as_ptr()) }; + if version == 0 { + -2 // PY_PROTO_MINIMUM_SUPPORTED + } else { + version + } + } + #[pygetset(setter)] + fn set_minimum_version(&self, value: i32, vm: &VirtualMachine) -> PyResult<()> { + let ctx = self.builder(); + let result = unsafe { sys::SSL_CTX_set_min_proto_version(ctx.as_ptr(), value) }; + if result == 0 { + return Err(vm.new_value_error("invalid protocol version")); + } + Ok(()) + } + + #[pygetset] + fn maximum_version(&self) -> i32 { + let ctx = self.ctx(); + let version = unsafe { sys::SSL_CTX_get_max_proto_version(ctx.as_ptr()) }; + if version == 0 { + -1 // PY_PROTO_MAXIMUM_SUPPORTED + } else { + version + } + } + #[pygetset(setter)] + fn set_maximum_version(&self, value: i32, vm: &VirtualMachine) -> PyResult<()> { + let ctx = self.builder(); + let result = unsafe { sys::SSL_CTX_set_max_proto_version(ctx.as_ptr(), value) }; + if result == 0 { + return Err(vm.new_value_error("invalid protocol version")); + } + Ok(()) + } + #[pymethod] fn set_default_verify_paths(&self, vm: &VirtualMachine) -> PyResult<()> { cfg_if::cfg_if! { @@ -852,12 +911,6 @@ mod _ssl { if let (None, None, None) = (&args.cafile, &args.capath, &args.cadata) { return Err(vm.new_type_error("cafile, capath and cadata cannot be all omitted")); } - if let Some(cafile) = &args.cafile { - cafile.ensure_no_nul(vm)? - } - if let Some(capath) = &args.capath { - capath.ensure_no_nul(vm)? - } #[cold] fn invalid_cadata(vm: &VirtualMachine) -> PyBaseExceptionRef { @@ -887,11 +940,10 @@ mod _ssl { } if args.cafile.is_some() || args.capath.is_some() { - ctx.load_verify_locations( - args.cafile.as_ref().map(|s| s.as_str().as_ref()), - args.capath.as_ref().map(|s| s.as_str().as_ref()), - ) - .map_err(|e| convert_openssl_error(vm, e))?; + let cafile_path = args.cafile.map(|p| p.to_path_buf(vm)).transpose()?; + let capath_path = args.capath.map(|p| p.to_path_buf(vm)).transpose()?; + ctx.load_verify_locations(cafile_path.as_deref(), capath_path.as_deref()) + .map_err(|e| convert_openssl_error(vm, e))?; } Ok(()) @@ -1064,9 +1116,9 @@ mod _ssl { #[derive(FromArgs)] struct LoadVerifyLocationsArgs { #[pyarg(any, default)] - cafile: Option<PyStrRef>, + cafile: Option<FsPath>, #[pyarg(any, default)] - capath: Option<PyStrRef>, + capath: Option<FsPath>, #[pyarg(any, default)] cadata: Option<Either<PyStrRef, ArgBytesLike>>, } @@ -1794,6 +1846,11 @@ mod _ssl { fn SSL_verify_client_post_handshake(ssl: *const sys::SSL) -> libc::c_int; } + #[cfg(ossl110)] + unsafe extern "C" { + fn SSL_CTX_get_security_level(ctx: *const sys::SSL_CTX) -> libc::c_int; + } + // OpenSSL BIO helper functions // These are typically macros in OpenSSL, implemented via BIO_ctrl const BIO_CTRL_PENDING: libc::c_int = 10; @@ -2082,7 +2139,7 @@ mod _ssl { let lib = sys::ERR_GET_LIB(err_code); if lib == ERR_LIB_SSL && reason == SSL_R_UNEXPECTED_EOF_WHILE_READING { return vm.new_exception( - vm.class("_ssl", "SSLEOFError"), + PySslEOFError::class(&vm.ctx).to_owned(), vec![ vm.ctx.new_int(SSL_ERROR_EOF).into(), vm.ctx diff --git a/stdlib/src/ssl/cert.rs b/stdlib/src/ssl/cert.rs index 9a77dee1eaf..19dd09f3379 100644 --- a/stdlib/src/ssl/cert.rs +++ b/stdlib/src/ssl/cert.rs @@ -164,17 +164,15 @@ pub(crate) mod ssl_cert { // IPv4 format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]) } else if ip.len() == 16 { - // IPv6 - format like: "X:X:X:X:X:X:X:X" (not compressed) + // IPv6 - format with all zeros visible (not compressed) + let ip_addr = std::net::Ipv6Addr::from([ + ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], + ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15], + ]); + let s = ip_addr.segments(); format!( "{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}", - (ip[0] as u16) << 8 | ip[1] as u16, - (ip[2] as u16) << 8 | ip[3] as u16, - (ip[4] as u16) << 8 | ip[5] as u16, - (ip[6] as u16) << 8 | ip[7] as u16, - (ip[8] as u16) << 8 | ip[9] as u16, - (ip[10] as u16) << 8 | ip[11] as u16, - (ip[12] as u16) << 8 | ip[13] as u16, - (ip[14] as u16) << 8 | ip[15] as u16 + s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7] ) } else { // Fallback for unexpected length From fda9ceea54dd1baf771f606c14288bcd5f4e4e26 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:19:12 +0900 Subject: [PATCH 323/819] Update ssl.py from CPython 3.13.9 (#6217) * update ssl.py from CPython 3.13.9 * test_ssl * mark failing tests * remove test/*.pem * fix certdata path * unmark fixed tests --- Lib/ssl.py | 342 +- Lib/test/certdata/allsans.pem | 167 + Lib/test/certdata/badcert.pem | 36 + Lib/test/certdata/badkey.pem | 40 + Lib/test/certdata/capath/4e1295a3.0 | 14 + Lib/test/certdata/capath/5ed36f99.0 | 41 + Lib/test/certdata/capath/6e88d7b8.0 | 14 + Lib/test/certdata/capath/99d0fa06.0 | 41 + Lib/test/certdata/capath/b1930218.0 | 27 + Lib/test/certdata/capath/ceff1710.0 | 27 + Lib/test/certdata/cert3.pem | 34 + Lib/test/certdata/ffdh3072.pem | 41 + Lib/test/certdata/idnsans.pem | 166 + Lib/test/certdata/keycert.passwd.pem | 69 + Lib/test/certdata/keycert.pem | 67 + Lib/test/certdata/keycert2.pem | 67 + Lib/test/certdata/keycert3.pem | 161 + Lib/test/certdata/keycert4.pem | 161 + Lib/test/certdata/keycertecc.pem | 103 + Lib/test/certdata/leaf-missing-aki.ca.pem | 13 + .../certdata/leaf-missing-aki.keycert.pem | 18 + Lib/test/certdata/make_ssl_certs.py | 315 + Lib/test/certdata/nokia.pem | 31 + Lib/test/certdata/nosan.pem | 131 + Lib/test/certdata/nullbytecert.pem | 90 + Lib/test/certdata/nullcert.pem | 0 Lib/test/certdata/pycacert.pem | 102 + Lib/test/certdata/pycakey.pem | 40 + Lib/test/certdata/revocation.crl | 14 + Lib/test/certdata/secp384r1.pem | 7 + .../certdata/selfsigned_pythontestdotnet.pem | 34 + Lib/test/certdata/ssl_cert.pem | 27 + Lib/test/certdata/ssl_key.passwd.pem | 42 + Lib/test/certdata/ssl_key.pem | 40 + Lib/test/certdata/talos-2019-0758.pem | 22 + Lib/test/keycert.passwd.pem | 50 - Lib/test/keycert.pem | 48 - Lib/test/keycert2.pem | 49 - Lib/test/keycert3.pem | 132 - Lib/test/keycert4.pem | 132 - Lib/test/keycertecc.pem | 96 - Lib/test/ssl_servers.py | 2 +- Lib/test/test_httplib.py | 8 +- Lib/test/test_ssl.py | 5581 +++++++++++++++++ Lib/test/test_urllib2_localnet.py | 4 +- 45 files changed, 7980 insertions(+), 666 deletions(-) create mode 100644 Lib/test/certdata/allsans.pem create mode 100644 Lib/test/certdata/badcert.pem create mode 100644 Lib/test/certdata/badkey.pem create mode 100644 Lib/test/certdata/capath/4e1295a3.0 create mode 100644 Lib/test/certdata/capath/5ed36f99.0 create mode 100644 Lib/test/certdata/capath/6e88d7b8.0 create mode 100644 Lib/test/certdata/capath/99d0fa06.0 create mode 100644 Lib/test/certdata/capath/b1930218.0 create mode 100644 Lib/test/certdata/capath/ceff1710.0 create mode 100644 Lib/test/certdata/cert3.pem create mode 100644 Lib/test/certdata/ffdh3072.pem create mode 100644 Lib/test/certdata/idnsans.pem create mode 100644 Lib/test/certdata/keycert.passwd.pem create mode 100644 Lib/test/certdata/keycert.pem create mode 100644 Lib/test/certdata/keycert2.pem create mode 100644 Lib/test/certdata/keycert3.pem create mode 100644 Lib/test/certdata/keycert4.pem create mode 100644 Lib/test/certdata/keycertecc.pem create mode 100644 Lib/test/certdata/leaf-missing-aki.ca.pem create mode 100644 Lib/test/certdata/leaf-missing-aki.keycert.pem create mode 100644 Lib/test/certdata/make_ssl_certs.py create mode 100644 Lib/test/certdata/nokia.pem create mode 100644 Lib/test/certdata/nosan.pem create mode 100644 Lib/test/certdata/nullbytecert.pem create mode 100644 Lib/test/certdata/nullcert.pem create mode 100644 Lib/test/certdata/pycacert.pem create mode 100644 Lib/test/certdata/pycakey.pem create mode 100644 Lib/test/certdata/revocation.crl create mode 100644 Lib/test/certdata/secp384r1.pem create mode 100644 Lib/test/certdata/selfsigned_pythontestdotnet.pem create mode 100644 Lib/test/certdata/ssl_cert.pem create mode 100644 Lib/test/certdata/ssl_key.passwd.pem create mode 100644 Lib/test/certdata/ssl_key.pem create mode 100644 Lib/test/certdata/talos-2019-0758.pem delete mode 100644 Lib/test/keycert.passwd.pem delete mode 100644 Lib/test/keycert.pem delete mode 100644 Lib/test/keycert2.pem delete mode 100644 Lib/test/keycert3.pem delete mode 100644 Lib/test/keycert4.pem delete mode 100644 Lib/test/keycertecc.pem create mode 100644 Lib/test/test_ssl.py diff --git a/Lib/ssl.py b/Lib/ssl.py index 751d79fb5e3..c8703b046cf 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -18,9 +18,10 @@ seconds past the Epoch (the time values returned from time.time()) - fetch_server_certificate (HOST, PORT) -- fetch the certificate provided - by the server running on HOST at port PORT. No - validation of the certificate is performed. + get_server_certificate (addr, ssl_version, ca_certs, timeout) -- Retrieve the + certificate from the server at the specified + address and return it as a PEM-encoded string + Integer constants: @@ -94,21 +95,22 @@ import os from collections import namedtuple from enum import Enum as _Enum, IntEnum as _IntEnum, IntFlag as _IntFlag +from enum import _simple_enum import _ssl # if we can't import it, let the error propagate from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION -from _ssl import _SSLContext, SSLSession, MemoryBIO +from _ssl import _SSLContext, MemoryBIO, SSLSession from _ssl import ( SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError, SSLSyscallError, SSLEOFError, SSLCertVerificationError ) from _ssl import txt2obj as _txt2obj, nid2obj as _nid2obj -from _ssl import RAND_status, RAND_add, RAND_bytes, RAND_pseudo_bytes +from _ssl import RAND_status, RAND_add, RAND_bytes try: from _ssl import RAND_egd except ImportError: - # LibreSSL does not provide RAND_egd + # RAND_egd is not supported on some platforms pass @@ -118,7 +120,6 @@ ) from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION - _IntEnum._convert_( '_SSLMethod', __name__, lambda name: name.startswith('PROTOCOL_') and name != 'PROTOCOL_SSLv23', @@ -155,7 +156,8 @@ _SSLv2_IF_EXISTS = getattr(_SSLMethod, 'PROTOCOL_SSLv2', None) -class TLSVersion(_IntEnum): +@_simple_enum(_IntEnum) +class TLSVersion: MINIMUM_SUPPORTED = _ssl.PROTO_MINIMUM_SUPPORTED SSLv3 = _ssl.PROTO_SSLv3 TLSv1 = _ssl.PROTO_TLSv1 @@ -165,7 +167,8 @@ class TLSVersion(_IntEnum): MAXIMUM_SUPPORTED = _ssl.PROTO_MAXIMUM_SUPPORTED -class _TLSContentType(_IntEnum): +@_simple_enum(_IntEnum) +class _TLSContentType: """Content types (record layer) See RFC 8446, section B.1 @@ -179,7 +182,8 @@ class _TLSContentType(_IntEnum): INNER_CONTENT_TYPE = 0x101 -class _TLSAlertType(_IntEnum): +@_simple_enum(_IntEnum) +class _TLSAlertType: """Alert types for TLSContentType.ALERT messages See RFC 8466, section B.2 @@ -220,7 +224,8 @@ class _TLSAlertType(_IntEnum): NO_APPLICATION_PROTOCOL = 120 -class _TLSMessageType(_IntEnum): +@_simple_enum(_IntEnum) +class _TLSMessageType: """Message types (handshake protocol) See RFC 8446, section B.3 @@ -250,10 +255,10 @@ class _TLSMessageType(_IntEnum): if sys.platform == "win32": - from _ssl import enum_certificates #, enum_crls + from _ssl import enum_certificates, enum_crls -from socket import socket, AF_INET, SOCK_STREAM, create_connection -from socket import SOL_SOCKET, SO_TYPE +from socket import socket, SOCK_STREAM, create_connection +from socket import SOL_SOCKET, SO_TYPE, _GLOBAL_DEFAULT_TIMEOUT import socket as _socket import base64 # for DER-to-PEM translation import errno @@ -275,7 +280,7 @@ class _TLSMessageType(_IntEnum): def _dnsname_match(dn, hostname): """Matching according to RFC 6125, section 6.4.3 - - Hostnames are compared lower case. + - Hostnames are compared lower-case. - For IDNA, both dn and hostname must be encoded as IDN A-label (ACE). - Partial wildcards like 'www*.example.org', multiple wildcards, sole wildcard or wildcards in labels other then the left-most label are not @@ -363,68 +368,11 @@ def _ipaddress_match(cert_ipaddress, host_ip): (section 1.7.2 - "Out of Scope"). """ # OpenSSL may add a trailing newline to a subjectAltName's IP address, - # commonly woth IPv6 addresses. Strip off trailing \n. + # commonly with IPv6 addresses. Strip off trailing \n. ip = _inet_paton(cert_ipaddress.rstrip()) return ip == host_ip -def match_hostname(cert, hostname): - """Verify that *cert* (in decoded format as returned by - SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 - rules are followed. - - The function matches IP addresses rather than dNSNames if hostname is a - valid ipaddress string. IPv4 addresses are supported on all platforms. - IPv6 addresses are supported on platforms with IPv6 support (AF_INET6 - and inet_pton). - - CertificateError is raised on failure. On success, the function - returns nothing. - """ - if not cert: - raise ValueError("empty or no certificate, match_hostname needs a " - "SSL socket or SSL context with either " - "CERT_OPTIONAL or CERT_REQUIRED") - try: - host_ip = _inet_paton(hostname) - except ValueError: - # Not an IP address (common case) - host_ip = None - dnsnames = [] - san = cert.get('subjectAltName', ()) - for key, value in san: - if key == 'DNS': - if host_ip is None and _dnsname_match(value, hostname): - return - dnsnames.append(value) - elif key == 'IP Address': - if host_ip is not None and _ipaddress_match(value, host_ip): - return - dnsnames.append(value) - if not dnsnames: - # The subject is only checked when there is no dNSName entry - # in subjectAltName - for sub in cert.get('subject', ()): - for key, value in sub: - # XXX according to RFC 2818, the most specific Common Name - # must be used. - if key == 'commonName': - if _dnsname_match(value, hostname): - return - dnsnames.append(value) - if len(dnsnames) > 1: - raise CertificateError("hostname %r " - "doesn't match either of %s" - % (hostname, ', '.join(map(repr, dnsnames)))) - elif len(dnsnames) == 1: - raise CertificateError("hostname %r " - "doesn't match %r" - % (hostname, dnsnames[0])) - else: - raise CertificateError("no appropriate commonName or " - "subjectAltName fields were found") - - DefaultVerifyPaths = namedtuple("DefaultVerifyPaths", "cafile capath openssl_cafile_env openssl_cafile openssl_capath_env " "openssl_capath") @@ -479,7 +427,14 @@ class SSLContext(_SSLContext): sslsocket_class = None # SSLSocket is assigned later. sslobject_class = None # SSLObject is assigned later. - def __new__(cls, protocol=PROTOCOL_TLS, *args, **kwargs): + def __new__(cls, protocol=None, *args, **kwargs): + if protocol is None: + warnings.warn( + "ssl.SSLContext() without protocol argument is deprecated.", + category=DeprecationWarning, + stacklevel=2 + ) + protocol = PROTOCOL_TLS self = _SSLContext.__new__(cls, protocol) return self @@ -518,6 +473,11 @@ def wrap_bio(self, incoming, outgoing, server_side=False, ) def set_npn_protocols(self, npn_protocols): + warnings.warn( + "ssl NPN is deprecated, use ALPN instead", + DeprecationWarning, + stacklevel=2 + ) protos = bytearray() for protocol in npn_protocols: b = bytes(protocol, 'ascii') @@ -553,18 +513,17 @@ def set_alpn_protocols(self, alpn_protocols): self._set_alpn_protocols(protos) def _load_windows_store_certs(self, storename, purpose): - certs = bytearray() try: for cert, encoding, trust in enum_certificates(storename): # CA certs are never PKCS#7 encoded if encoding == "x509_asn": if trust is True or purpose.oid in trust: - certs.extend(cert) + try: + self.load_verify_locations(cadata=cert) + except SSLError as exc: + warnings.warn(f"Bad certificate in Windows certificate store: {exc!s}") except PermissionError: warnings.warn("unable to enumerate Windows certificate store") - if certs: - self.load_verify_locations(cadata=certs) - return certs def load_default_certs(self, purpose=Purpose.SERVER_AUTH): if not isinstance(purpose, _ASN1Object): @@ -734,12 +693,25 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, # SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION, # OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE # by default. - context = SSLContext(PROTOCOL_TLS) - if purpose == Purpose.SERVER_AUTH: # verify certs and host name in client mode + context = SSLContext(PROTOCOL_TLS_CLIENT) context.verify_mode = CERT_REQUIRED context.check_hostname = True + elif purpose == Purpose.CLIENT_AUTH: + context = SSLContext(PROTOCOL_TLS_SERVER) + else: + raise ValueError(purpose) + + # `VERIFY_X509_PARTIAL_CHAIN` makes OpenSSL's chain building behave more + # like RFC 3280 and 5280, which specify that chain building stops with the + # first trust anchor, even if that anchor is not self-signed. + # + # `VERIFY_X509_STRICT` makes OpenSSL more conservative about the + # certificates it accepts, including "disabling workarounds for + # some broken certificates." + context.verify_flags |= (_ssl.VERIFY_X509_PARTIAL_CHAIN | + _ssl.VERIFY_X509_STRICT) if cafile or capath or cadata: context.load_verify_locations(cafile, capath, cadata) @@ -755,7 +727,7 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, context.keylog_filename = keylogfile return context -def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=CERT_NONE, +def _create_unverified_context(protocol=None, *, cert_reqs=CERT_NONE, check_hostname=False, purpose=Purpose.SERVER_AUTH, certfile=None, keyfile=None, cafile=None, capath=None, cadata=None): @@ -772,10 +744,18 @@ def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=CERT_NONE, # SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION, # OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE # by default. - context = SSLContext(protocol) + if purpose == Purpose.SERVER_AUTH: + # verify certs and host name in client mode + if protocol is None: + protocol = PROTOCOL_TLS_CLIENT + elif purpose == Purpose.CLIENT_AUTH: + if protocol is None: + protocol = PROTOCOL_TLS_SERVER + else: + raise ValueError(purpose) - if not check_hostname: - context.check_hostname = False + context = SSLContext(protocol) + context.check_hostname = check_hostname if cert_reqs is not None: context.verify_mode = cert_reqs if check_hostname: @@ -905,19 +885,46 @@ def getpeercert(self, binary_form=False): """ return self._sslobj.getpeercert(binary_form) + def get_verified_chain(self): + """Returns verified certificate chain provided by the other + end of the SSL channel as a list of DER-encoded bytes. + + If certificate verification was disabled method acts the same as + ``SSLSocket.get_unverified_chain``. + """ + chain = self._sslobj.get_verified_chain() + + if chain is None: + return [] + + return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain] + + def get_unverified_chain(self): + """Returns raw certificate chain provided by the other + end of the SSL channel as a list of DER-encoded bytes. + """ + chain = self._sslobj.get_unverified_chain() + + if chain is None: + return [] + + return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain] + def selected_npn_protocol(self): """Return the currently selected NPN protocol as a string, or ``None`` if a next protocol was not negotiated or if NPN is not supported by one of the peers.""" - if _ssl.HAS_NPN: - return self._sslobj.selected_npn_protocol() + warnings.warn( + "ssl NPN is deprecated, use ALPN instead", + DeprecationWarning, + stacklevel=2 + ) def selected_alpn_protocol(self): """Return the currently selected ALPN protocol as a string, or ``None`` if a next protocol was not negotiated or if ALPN is not supported by one of the peers.""" - if _ssl.HAS_ALPN: - return self._sslobj.selected_alpn_protocol() + return self._sslobj.selected_alpn_protocol() def cipher(self): """Return the currently selected cipher as a 3-tuple ``(name, @@ -996,38 +1003,67 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True, if context.check_hostname and not server_hostname: raise ValueError("check_hostname requires server_hostname") + sock_timeout = sock.gettimeout() kwargs = dict( family=sock.family, type=sock.type, proto=sock.proto, fileno=sock.fileno() ) self = cls.__new__(cls, **kwargs) super(SSLSocket, self).__init__(**kwargs) - self.settimeout(sock.gettimeout()) sock.detach() - - self._context = context - self._session = session - self._closed = False - self._sslobj = None - self.server_side = server_side - self.server_hostname = context._encode_hostname(server_hostname) - self.do_handshake_on_connect = do_handshake_on_connect - self.suppress_ragged_eofs = suppress_ragged_eofs - - # See if we are connected + # Now SSLSocket is responsible for closing the file descriptor. try: - self.getpeername() - except OSError as e: - if e.errno != errno.ENOTCONN: - raise - connected = False - else: - connected = True + self._context = context + self._session = session + self._closed = False + self._sslobj = None + self.server_side = server_side + self.server_hostname = context._encode_hostname(server_hostname) + self.do_handshake_on_connect = do_handshake_on_connect + self.suppress_ragged_eofs = suppress_ragged_eofs - self._connected = connected - if connected: - # create the SSL object + # See if we are connected try: + self.getpeername() + except OSError as e: + if e.errno != errno.ENOTCONN: + raise + connected = False + blocking = self.getblocking() + self.setblocking(False) + try: + # We are not connected so this is not supposed to block, but + # testing revealed otherwise on macOS and Windows so we do + # the non-blocking dance regardless. Our raise when any data + # is found means consuming the data is harmless. + notconn_pre_handshake_data = self.recv(1) + except OSError as e: + # EINVAL occurs for recv(1) on non-connected on unix sockets. + if e.errno not in (errno.ENOTCONN, errno.EINVAL): + raise + notconn_pre_handshake_data = b'' + self.setblocking(blocking) + if notconn_pre_handshake_data: + # This prevents pending data sent to the socket before it was + # closed from escaping to the caller who could otherwise + # presume it came through a successful TLS connection. + reason = "Closed before TLS handshake with data in recv buffer." + notconn_pre_handshake_data_error = SSLError(e.errno, reason) + # Add the SSLError attributes that _ssl.c always adds. + notconn_pre_handshake_data_error.reason = reason + notconn_pre_handshake_data_error.library = None + try: + raise notconn_pre_handshake_data_error + finally: + # Explicitly break the reference cycle. + notconn_pre_handshake_data_error = None + else: + connected = True + + self.settimeout(sock_timeout) # Must come after setblocking() calls. + self._connected = connected + if connected: + # create the SSL object self._sslobj = self._context._wrap_socket( self, server_side, self.server_hostname, owner=self, session=self._session, @@ -1038,9 +1074,12 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True, # non-blocking raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets") self.do_handshake() - except (OSError, ValueError): + except: + try: self.close() - raise + except OSError: + pass + raise return self @property @@ -1123,13 +1162,33 @@ def getpeercert(self, binary_form=False): self._check_connected() return self._sslobj.getpeercert(binary_form) + @_sslcopydoc + def get_verified_chain(self): + chain = self._sslobj.get_verified_chain() + + if chain is None: + return [] + + return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain] + + @_sslcopydoc + def get_unverified_chain(self): + chain = self._sslobj.get_unverified_chain() + + if chain is None: + return [] + + return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain] + @_sslcopydoc def selected_npn_protocol(self): self._checkClosed() - if self._sslobj is None or not _ssl.HAS_NPN: - return None - else: - return self._sslobj.selected_npn_protocol() + warnings.warn( + "ssl NPN is deprecated, use ALPN instead", + DeprecationWarning, + stacklevel=2 + ) + return None @_sslcopydoc def selected_alpn_protocol(self): @@ -1229,10 +1288,14 @@ def recv(self, buflen=1024, flags=0): def recv_into(self, buffer, nbytes=None, flags=0): self._checkClosed() - if buffer and (nbytes is None): - nbytes = len(buffer) - elif nbytes is None: - nbytes = 1024 + if nbytes is None: + if buffer is not None: + with memoryview(buffer) as view: + nbytes = view.nbytes + if not nbytes: + nbytes = 1024 + else: + nbytes = 1024 if self._sslobj is not None: if flags != 0: raise ValueError( @@ -1382,32 +1445,6 @@ def version(self): SSLContext.sslobject_class = SSLObject -def wrap_socket(sock, keyfile=None, certfile=None, - server_side=False, cert_reqs=CERT_NONE, - ssl_version=PROTOCOL_TLS, ca_certs=None, - do_handshake_on_connect=True, - suppress_ragged_eofs=True, - ciphers=None): - - if server_side and not certfile: - raise ValueError("certfile must be specified for server-side " - "operations") - if keyfile and not certfile: - raise ValueError("certfile must be specified") - context = SSLContext(ssl_version) - context.verify_mode = cert_reqs - if ca_certs: - context.load_verify_locations(ca_certs) - if certfile: - context.load_cert_chain(certfile, keyfile) - if ciphers: - context.set_ciphers(ciphers) - return context.wrap_socket( - sock=sock, server_side=server_side, - do_handshake_on_connect=do_handshake_on_connect, - suppress_ragged_eofs=suppress_ragged_eofs - ) - # some utility functions def cert_time_to_seconds(cert_time): @@ -1466,11 +1503,14 @@ def PEM_cert_to_DER_cert(pem_cert_string): d = pem_cert_string.strip()[len(PEM_HEADER):-len(PEM_FOOTER)] return base64.decodebytes(d.encode('ASCII', 'strict')) -def get_server_certificate(addr, ssl_version=PROTOCOL_TLS, ca_certs=None): +def get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, + ca_certs=None, timeout=_GLOBAL_DEFAULT_TIMEOUT): """Retrieve the certificate from the server at the specified address, and return it as a PEM-encoded string. If 'ca_certs' is specified, validate the server cert against it. - If 'ssl_version' is specified, use it in the connection attempt.""" + If 'ssl_version' is specified, use it in the connection attempt. + If 'timeout' is specified, use it in the connection attempt. + """ host, port = addr if ca_certs is not None: @@ -1480,8 +1520,8 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_TLS, ca_certs=None): context = _create_stdlib_context(ssl_version, cert_reqs=cert_reqs, cafile=ca_certs) - with create_connection(addr) as sock: - with context.wrap_socket(sock) as sslsock: + with create_connection(addr, timeout=timeout) as sock: + with context.wrap_socket(sock, server_hostname=host) as sslsock: dercert = sslsock.getpeercert(True) return DER_cert_to_PEM_cert(dercert) diff --git a/Lib/test/certdata/allsans.pem b/Lib/test/certdata/allsans.pem new file mode 100644 index 00000000000..02f2c2e6346 --- /dev/null +++ b/Lib/test/certdata/allsans.pem @@ -0,0 +1,167 @@ +-----BEGIN PRIVATE KEY----- +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 +GPAhMKawjrL1KFmu7vIqDSw= +-----END PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + cb:2d:80:99:5a:69:52:5f + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server + Validity + Not Before: Aug 29 14:23:16 2018 GMT + Not After : Oct 28 14:23:16 2037 GMT + Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=allsans + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (3072 bit) + Modulus: + 00:9c:cc:45:6f:e4:3d:94:0e:d9:fa:0c:c9:99:fe: + e0:96:08:bc:8b:f8:ae:71:4d:9a:7f:e6:3d:dc:c8: + 84:a8:69:3b:94:d4:ab:b7:90:3f:7b:78:b8:29:1e: + 3e:05:82:4e:59:26:2d:b5:ce:c4:2a:7a:90:a3:8c: + 08:41:93:ab:63:e9:16:14:72:73:1b:6f:c3:2e:c5: + 49:10:30:40:d6:fb:92:df:4b:2a:7e:1c:26:9e:b6: + 51:e8:b1:67:61:10:3a:97:e2:4a:49:e5:a8:c1:c0: + d4:6d:d3:b9:b1:40:4c:6d:0b:89:1c:9e:8d:2e:b7: + da:c6:e5:7c:cb:37:6f:57:a4:2a:51:1e:51:45:ae: + 7b:4e:81:00:04:a2:2c:fd:fd:f7:3c:a9:a4:e0:6f: + 7e:d1:80:66:0f:42:cc:61:ec:f7:11:0d:17:b8:64: + 6c:75:97:4e:88:ae:bf:18:fb:65:b9:d3:35:82:7e: + 4c:02:4b:70:da:2c:33:8a:22:34:8e:7c:f1:cd:aa: + 67:dc:7e:c4:e9:ce:b8:fc:df:7d:1c:74:f9:25:ac: + c2:2e:88:1b:fe:88:69:25:0a:39:0f:64:98:84:87: + 1b:d8:da:4e:84:44:ce:07:03:1c:6e:3c:28:af:ac: + 7e:4c:09:e3:0d:62:f0:55:51:c2:eb:19:13:a6:ec: + 94:57:46:59:48:fd:7b:9b:c3:0a:00:68:06:fe:cb: + e7:59:b4:26:7d:02:7c:c7:21:bf:a7:c8:a8:89:c5: + 60:23:ce:34:7e:40:f2:fa:8c:7a:d5:58:15:40:59: + 37:3c:dd:83:f9:16:fc:54:5c:c7:d8:de:c5:6f:f6: + d3:52:09:5d:dc:a5:83:96:e7:e8:97:71:0c:cb:b9: + cc:d7:9c:e2:d1:62:4e:e9:f7:09:1b:d3:f8:64:f4: + ab:8a:e0:13:00:f7:47:81:86:f1:4c:08:58:07:27: + 5f:f1:26:0d:a7:16:60:47:3e:8f:f1:6d:cc:0f:1b: + f9:9a:e5:3f:cc:70:0f:26:02:51 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:allsans, othername: 1.2.3.4::some other identifier, othername: 1.3.6.1.5.2.2::<unsupported>, email:user@example.org, DNS:www.example.org, DirName:/C=XY/L=Castle Anthrax/O=Python Software Foundation/CN=dirname example, URI:https://www.python.org/, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, Registered ID:1.2.3.4.5 + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 31:5E:C0:5E:2F:47:FF:8B:92:F9:EE:3D:B1:87:D0:53:75:3B:B1:48 + X509v3 Authority Key Identifier: + keyid:F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 + DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server + serial:CB:2D:80:99:5A:69:52:5B + Authority Information Access: + CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer + OCSP - URI:http://testca.pythontest.net/testca/ocsp/ + X509v3 CRL Distribution Points: + Full Name: + URI:http://testca.pythontest.net/testca/revocation.crl + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 72:42:a6:fc:ee:3c:21:47:05:33:e8:8c:6b:27:07:4a:ed:e2: + 81:47:96:79:43:ff:0f:ef:5a:06:aa:4c:01:70:5b:21:c4:b7: + 5d:17:29:c8:10:02:c3:08:7b:8c:86:56:9e:e9:7c:6e:a8:b6: + 26:13:9e:1e:1f:93:66:85:67:63:9e:08:fb:55:39:56:82:f5: + be:0c:38:1e:eb:c4:54:b2:a7:7b:18:55:bb:00:87:43:50:50: + bb:e1:29:10:cf:3d:c9:07:c7:d2:5d:b6:45:68:1f:d6:de:00: + 96:3e:29:73:f6:22:70:21:a2:ba:68:28:94:ec:37:bc:a7:00: + 70:58:4e:d1:48:ae:ef:8d:11:a4:6e:10:2f:92:83:07:e2:76: + ac:bf:4f:bb:d6:9f:47:9e:a4:02:03:16:f8:a8:0a:3d:67:17: + 31:44:0e:68:d0:d3:24:d5:e7:bf:67:30:8f:88:97:92:0a:1e: + d7:74:df:7e:7b:4c:c6:d9:c3:84:92:2b:a0:89:11:08:4c:dd: + 32:49:df:36:23:d4:63:56:e4:f1:68:5a:6f:a0:c3:3c:e2:36: + ee:f3:46:60:78:4d:76:a5:5a:4a:61:c6:f8:ae:18:68:c2:8d: + 0e:2f:76:50:bb:be:b9:56:f1:04:5c:ac:ad:d7:d6:a4:1e:45: + 45:52:f4:10:a2:0f:9b:e3:d9:73:17:b6:52:42:a6:5b:c9:e9: + 8d:60:74:68:d0:1f:7a:ce:01:8e:9e:55:cb:cf:64:c1:cc:9a: + 72:aa:b4:5f:b5:55:13:41:10:51:a0:2c:a5:5b:43:12:ca:cc: + b7:c4:ac:f2:6f:72:fd:0d:50:6a:d6:81:c1:91:93:21:fe:de: + 9a:be:e5:3c:2a:98:95:a1:42:f8:f2:5c:75:c6:f1:fd:11:b1: + 22:26:33:5b:43:63:21:06:61:d2:cd:04:f3:30:c6:a8:3f:17: + d3:05:a3:87:45:2e:52:1e:51:88:e3:59:4c:78:51:b0:7b:b4: + 58:d9:27:22:6e:8c +-----BEGIN CERTIFICATE----- +MIIHDTCCBXWgAwIBAgIJAMstgJlaaVJfMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx +NDIzMTZaMF0xCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj +MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xEDAOBgNVBAMMB2Fs +bHNhbnMwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCczEVv5D2UDtn6 +DMmZ/uCWCLyL+K5xTZp/5j3cyISoaTuU1Ku3kD97eLgpHj4Fgk5ZJi21zsQqepCj +jAhBk6tj6RYUcnMbb8MuxUkQMEDW+5LfSyp+HCaetlHosWdhEDqX4kpJ5ajBwNRt +07mxQExtC4kcno0ut9rG5XzLN29XpCpRHlFFrntOgQAEoiz9/fc8qaTgb37RgGYP +Qsxh7PcRDRe4ZGx1l06Irr8Y+2W50zWCfkwCS3DaLDOKIjSOfPHNqmfcfsTpzrj8 +330cdPklrMIuiBv+iGklCjkPZJiEhxvY2k6ERM4HAxxuPCivrH5MCeMNYvBVUcLr +GROm7JRXRllI/XubwwoAaAb+y+dZtCZ9AnzHIb+nyKiJxWAjzjR+QPL6jHrVWBVA +WTc83YP5FvxUXMfY3sVv9tNSCV3cpYOW5+iXcQzLuczXnOLRYk7p9wkb0/hk9KuK +4BMA90eBhvFMCFgHJ1/xJg2nFmBHPo/xbcwPG/ma5T/McA8mAlECAwEAAaOCAt4w +ggLaMIIBMAYDVR0RBIIBJzCCASOCB2FsbHNhbnOgHgYDKgMEoBcMFXNvbWUgb3Ro +ZXIgaWRlbnRpZmllcqA1BgYrBgEFAgKgKzApoBAbDktFUkJFUk9TLlJFQUxNoRUw +E6ADAgEBoQwwChsIdXNlcm5hbWWBEHVzZXJAZXhhbXBsZS5vcmeCD3d3dy5leGFt +cGxlLm9yZ6RnMGUxCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJh +eDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xGDAWBgNVBAMM +D2Rpcm5hbWUgZXhhbXBsZYYXaHR0cHM6Ly93d3cucHl0aG9uLm9yZy+HBH8AAAGH +EAAAAAAAAAAAAAAAAAAAAAGIBCoDBAUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQW +MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQx +XsBeL0f/i5L57j2xh9BTdTuxSDB9BgNVHSMEdjB0gBTz7JSO8o4wxI5owr+OahnA +wZ92ZaFRpE8wTTELMAkGA1UEBhMCWFkxJjAkBgNVBAoMHVB5dGhvbiBTb2Z0d2Fy +ZSBGb3VuZGF0aW9uIENBMRYwFAYDVQQDDA1vdXItY2Etc2VydmVyggkAyy2AmVpp +UlswgYMGCCsGAQUFBwEBBHcwdTA8BggrBgEFBQcwAoYwaHR0cDovL3Rlc3RjYS5w +eXRob250ZXN0Lm5ldC90ZXN0Y2EvcHljYWNlcnQuY2VyMDUGCCsGAQUFBzABhilo +dHRwOi8vdGVzdGNhLnB5dGhvbnRlc3QubmV0L3Rlc3RjYS9vY3NwLzBDBgNVHR8E +PDA6MDigNqA0hjJodHRwOi8vdGVzdGNhLnB5dGhvbnRlc3QubmV0L3Rlc3RjYS9y +ZXZvY2F0aW9uLmNybDANBgkqhkiG9w0BAQsFAAOCAYEAckKm/O48IUcFM+iMaycH +Su3igUeWeUP/D+9aBqpMAXBbIcS3XRcpyBACwwh7jIZWnul8bqi2JhOeHh+TZoVn +Y54I+1U5VoL1vgw4HuvEVLKnexhVuwCHQ1BQu+EpEM89yQfH0l22RWgf1t4Alj4p +c/YicCGiumgolOw3vKcAcFhO0Uiu740RpG4QL5KDB+J2rL9Pu9afR56kAgMW+KgK +PWcXMUQOaNDTJNXnv2cwj4iXkgoe13TffntMxtnDhJIroIkRCEzdMknfNiPUY1bk +8Whab6DDPOI27vNGYHhNdqVaSmHG+K4YaMKNDi92ULu+uVbxBFysrdfWpB5FRVL0 +EKIPm+PZcxe2UkKmW8npjWB0aNAfes4Bjp5Vy89kwcyacqq0X7VVE0EQUaAspVtD +EsrMt8Ss8m9y/Q1QataBwZGTIf7emr7lPCqYlaFC+PJcdcbx/RGxIiYzW0NjIQZh +0s0E8zDGqD8X0wWjh0UuUh5RiONZTHhRsHu0WNknIm6M +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/badcert.pem b/Lib/test/certdata/badcert.pem new file mode 100644 index 00000000000..c4191460f9e --- /dev/null +++ b/Lib/test/certdata/badcert.pem @@ -0,0 +1,36 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXwIBAAKBgQC8ddrhm+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9L +opdJhTvbGfEj0DQs1IE8M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVH +fhi/VwovESJlaBOp+WMnfhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQAB +AoGBAK0FZpaKj6WnJZN0RqhhK+ggtBWwBnc0U/ozgKz2j1s3fsShYeiGtW6CK5nU +D1dZ5wzhbGThI7LiOXDvRucc9n7vUgi0alqPQ/PFodPxAN/eEYkmXQ7W2k7zwsDA +IUK0KUhktQbLu8qF/m8qM86ba9y9/9YkXuQbZ3COl5ahTZrhAkEA301P08RKv3KM +oXnGU2UHTuJ1MAD2hOrPxjD4/wxA/39EWG9bZczbJyggB4RHu0I3NOSFjAm3HQm0 +ANOu5QK9owJBANgOeLfNNcF4pp+UikRFqxk5hULqRAWzVxVrWe85FlPm0VVmHbb/ +loif7mqjU8o1jTd/LM7RD9f2usZyE2psaw8CQQCNLhkpX3KO5kKJmS9N7JMZSc4j +oog58yeYO8BBqKKzpug0LXuQultYv2K4veaIO04iL9VLe5z9S/Q1jaCHBBuXAkEA +z8gjGoi1AOp6PBBLZNsncCvcV/0aC+1se4HxTNo2+duKSDnbq+ljqOM+E7odU+Nq +ewvIWOG//e8fssd0mq3HywJBAJ8l/c8GVmrpFTx8r/nZ2Pyyjt3dH1widooDXYSV +q6Gbf41Llo5sYAtmxdndTLASuHKecacTgZVhy0FryZpLKrU= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +Just bad cert data +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIICXwIBAAKBgQC8ddrhm+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9L +opdJhTvbGfEj0DQs1IE8M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVH +fhi/VwovESJlaBOp+WMnfhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQAB +AoGBAK0FZpaKj6WnJZN0RqhhK+ggtBWwBnc0U/ozgKz2j1s3fsShYeiGtW6CK5nU +D1dZ5wzhbGThI7LiOXDvRucc9n7vUgi0alqPQ/PFodPxAN/eEYkmXQ7W2k7zwsDA +IUK0KUhktQbLu8qF/m8qM86ba9y9/9YkXuQbZ3COl5ahTZrhAkEA301P08RKv3KM +oXnGU2UHTuJ1MAD2hOrPxjD4/wxA/39EWG9bZczbJyggB4RHu0I3NOSFjAm3HQm0 +ANOu5QK9owJBANgOeLfNNcF4pp+UikRFqxk5hULqRAWzVxVrWe85FlPm0VVmHbb/ +loif7mqjU8o1jTd/LM7RD9f2usZyE2psaw8CQQCNLhkpX3KO5kKJmS9N7JMZSc4j +oog58yeYO8BBqKKzpug0LXuQultYv2K4veaIO04iL9VLe5z9S/Q1jaCHBBuXAkEA +z8gjGoi1AOp6PBBLZNsncCvcV/0aC+1se4HxTNo2+duKSDnbq+ljqOM+E7odU+Nq +ewvIWOG//e8fssd0mq3HywJBAJ8l/c8GVmrpFTx8r/nZ2Pyyjt3dH1widooDXYSV +q6Gbf41Llo5sYAtmxdndTLASuHKecacTgZVhy0FryZpLKrU= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +Just bad cert data +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/badkey.pem b/Lib/test/certdata/badkey.pem new file mode 100644 index 00000000000..1c8a9557193 --- /dev/null +++ b/Lib/test/certdata/badkey.pem @@ -0,0 +1,40 @@ +-----BEGIN RSA PRIVATE KEY----- +Bad Key, though the cert should be OK +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICpzCCAhCgAwIBAgIJAP+qStv1cIGNMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD +VQQGEwJVUzERMA8GA1UECBMIRGVsYXdhcmUxEzARBgNVBAcTCldpbG1pbmd0b24x +IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQwwCgYDVQQLEwNT +U0wxHzAdBgNVBAMTFnNvbWVtYWNoaW5lLnB5dGhvbi5vcmcwHhcNMDcwODI3MTY1 +NDUwWhcNMTMwMjE2MTY1NDUwWjCBiTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCERl +bGF3YXJlMRMwEQYDVQQHEwpXaWxtaW5ndG9uMSMwIQYDVQQKExpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjEMMAoGA1UECxMDU1NMMR8wHQYDVQQDExZzb21lbWFj +aGluZS5weXRob24ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ddrh +m+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9LopdJhTvbGfEj0DQs1IE8 +M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVHfhi/VwovESJlaBOp+WMn +fhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQABoxUwEzARBglghkgBhvhC +AQEEBAMCBkAwDQYJKoZIhvcNAQEFBQADgYEAF4Q5BVqmCOLv1n8je/Jw9K669VXb +08hyGzQhkemEBYQd6fzQ9A/1ZzHkJKb1P6yreOLSEh4KcxYPyrLRC1ll8nr5OlCx +CMhKkTnR6qBsdNV0XtdU2+N25hqW+Ma4ZeqsN/iiJVCGNOZGnvQuvCAGWF8+J/f/ +iHkC6gGdBJhogs4= +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +Bad Key, though the cert should be OK +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICpzCCAhCgAwIBAgIJAP+qStv1cIGNMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD +VQQGEwJVUzERMA8GA1UECBMIRGVsYXdhcmUxEzARBgNVBAcTCldpbG1pbmd0b24x +IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQwwCgYDVQQLEwNT +U0wxHzAdBgNVBAMTFnNvbWVtYWNoaW5lLnB5dGhvbi5vcmcwHhcNMDcwODI3MTY1 +NDUwWhcNMTMwMjE2MTY1NDUwWjCBiTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCERl +bGF3YXJlMRMwEQYDVQQHEwpXaWxtaW5ndG9uMSMwIQYDVQQKExpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjEMMAoGA1UECxMDU1NMMR8wHQYDVQQDExZzb21lbWFj +aGluZS5weXRob24ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ddrh +m+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9LopdJhTvbGfEj0DQs1IE8 +M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVHfhi/VwovESJlaBOp+WMn +fhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQABoxUwEzARBglghkgBhvhC +AQEEBAMCBkAwDQYJKoZIhvcNAQEFBQADgYEAF4Q5BVqmCOLv1n8je/Jw9K669VXb +08hyGzQhkemEBYQd6fzQ9A/1ZzHkJKb1P6yreOLSEh4KcxYPyrLRC1ll8nr5OlCx +CMhKkTnR6qBsdNV0XtdU2+N25hqW+Ma4ZeqsN/iiJVCGNOZGnvQuvCAGWF8+J/f/ +iHkC6gGdBJhogs4= +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/capath/4e1295a3.0 b/Lib/test/certdata/capath/4e1295a3.0 new file mode 100644 index 00000000000..9d7ac238d86 --- /dev/null +++ b/Lib/test/certdata/capath/4e1295a3.0 @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICLDCCAdYCAQAwDQYJKoZIhvcNAQEEBQAwgaAxCzAJBgNVBAYTAlBUMRMwEQYD +VQQIEwpRdWVlbnNsYW5kMQ8wDQYDVQQHEwZMaXNib2ExFzAVBgNVBAoTDk5ldXJv +bmlvLCBMZGEuMRgwFgYDVQQLEw9EZXNlbnZvbHZpbWVudG8xGzAZBgNVBAMTEmJy +dXR1cy5uZXVyb25pby5wdDEbMBkGCSqGSIb3DQEJARYMc2FtcG9AaWtpLmZpMB4X +DTk2MDkwNTAzNDI0M1oXDTk2MTAwNTAzNDI0M1owgaAxCzAJBgNVBAYTAlBUMRMw +EQYDVQQIEwpRdWVlbnNsYW5kMQ8wDQYDVQQHEwZMaXNib2ExFzAVBgNVBAoTDk5l +dXJvbmlvLCBMZGEuMRgwFgYDVQQLEw9EZXNlbnZvbHZpbWVudG8xGzAZBgNVBAMT +EmJydXR1cy5uZXVyb25pby5wdDEbMBkGCSqGSIb3DQEJARYMc2FtcG9AaWtpLmZp +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL7+aty3S1iBA/+yxjxv4q1MUTd1kjNw +L4lYKbpzzlmC5beaQXeQ2RmGMTXU+mDvuqItjVHOK3DvPK7lTcSGftUCAwEAATAN +BgkqhkiG9w0BAQQFAANBAFqPEKFjk6T6CKTHvaQeEAsX0/8YHPHqH/9AnhSjrwuX +9EBc0n6bVGhN7XaXd6sJ7dym9sbsWxb+pJdurnkxjx4= +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/capath/5ed36f99.0 b/Lib/test/certdata/capath/5ed36f99.0 new file mode 100644 index 00000000000..e7dfc82947e --- /dev/null +++ b/Lib/test/certdata/capath/5ed36f99.0 @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE----- +MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290 +IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB +IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA +Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO +BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi +MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ +ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ +8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6 +zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y +fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7 +w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc +G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k +epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q +laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ +QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU +fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826 +YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w +ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY +gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe +MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0 +IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy +dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw +czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0 +dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl +aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC +AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg +b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB +ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc +nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg +18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c +gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl +Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY +sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T +SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF +CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum +GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk +zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW +omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/capath/6e88d7b8.0 b/Lib/test/certdata/capath/6e88d7b8.0 new file mode 100644 index 00000000000..9d7ac238d86 --- /dev/null +++ b/Lib/test/certdata/capath/6e88d7b8.0 @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICLDCCAdYCAQAwDQYJKoZIhvcNAQEEBQAwgaAxCzAJBgNVBAYTAlBUMRMwEQYD +VQQIEwpRdWVlbnNsYW5kMQ8wDQYDVQQHEwZMaXNib2ExFzAVBgNVBAoTDk5ldXJv +bmlvLCBMZGEuMRgwFgYDVQQLEw9EZXNlbnZvbHZpbWVudG8xGzAZBgNVBAMTEmJy +dXR1cy5uZXVyb25pby5wdDEbMBkGCSqGSIb3DQEJARYMc2FtcG9AaWtpLmZpMB4X +DTk2MDkwNTAzNDI0M1oXDTk2MTAwNTAzNDI0M1owgaAxCzAJBgNVBAYTAlBUMRMw +EQYDVQQIEwpRdWVlbnNsYW5kMQ8wDQYDVQQHEwZMaXNib2ExFzAVBgNVBAoTDk5l +dXJvbmlvLCBMZGEuMRgwFgYDVQQLEw9EZXNlbnZvbHZpbWVudG8xGzAZBgNVBAMT +EmJydXR1cy5uZXVyb25pby5wdDEbMBkGCSqGSIb3DQEJARYMc2FtcG9AaWtpLmZp +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL7+aty3S1iBA/+yxjxv4q1MUTd1kjNw +L4lYKbpzzlmC5beaQXeQ2RmGMTXU+mDvuqItjVHOK3DvPK7lTcSGftUCAwEAATAN +BgkqhkiG9w0BAQQFAANBAFqPEKFjk6T6CKTHvaQeEAsX0/8YHPHqH/9AnhSjrwuX +9EBc0n6bVGhN7XaXd6sJ7dym9sbsWxb+pJdurnkxjx4= +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/capath/99d0fa06.0 b/Lib/test/certdata/capath/99d0fa06.0 new file mode 100644 index 00000000000..e7dfc82947e --- /dev/null +++ b/Lib/test/certdata/capath/99d0fa06.0 @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE----- +MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290 +IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB +IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA +Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO +BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi +MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ +ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ +8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6 +zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y +fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7 +w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc +G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k +epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q +laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ +QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU +fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826 +YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w +ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY +gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe +MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0 +IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy +dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw +czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0 +dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl +aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC +AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg +b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB +ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc +nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg +18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c +gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl +Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY +sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T +SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF +CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum +GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk +zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW +omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/capath/b1930218.0 b/Lib/test/certdata/capath/b1930218.0 new file mode 100644 index 00000000000..aa9dbbe294f --- /dev/null +++ b/Lib/test/certdata/capath/b1930218.0 @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEgDCCAuigAwIBAgIJAMstgJlaaVJbMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx +NDIzMTZaME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg +Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcjCCAaIwDQYJKoZI +hvcNAQEBBQADggGPADCCAYoCggGBANCgm7G5O3nuMS+4URwBde0JWUysyL9qCvh6 +CPAl4yV7avjE2KqgYAclsM9zcQVSaL8Gk64QYZa8s2mBGn0Z/CCGj5poG+3N4mxh +Z8dOVepDBiEb6bm+hF/C2uuJiOBCpkVJKtC5a4yTyUQ7yvw8lH/dcMWt2Es73B74 +VUu1J4b437CDz/cWN78TFzTUyVXtaxbJf60gTvAe2Ru/jbrNypbvHmnLUWZhSA3o +eaNZYdQQjeANOwuFttWFEt2lB8VL+iP6VDn3lwvJREceVnc8PBMBC2131hS6RPRT +NVbZPbk+NV/bM5pPWrk4RMkySf5m9h8al6rKTEr2uF5Af/sLHfhbodz4wC7QbUn1 +0kbUkFf+koE0ri04u6gXDOHlP+L3JgVUUPVksxxuRP9vqbQDlukOwojYclKQmcZB +D0aQWbg+b9Linh02gpXTWIoS8+LYDSBRI/CQLZo+fSaGsqfX+ShgA+N3x4gEyf6J +d3AQT8Ogijv0q0J74xSS2K4W1qHefQIDAQABo2MwYTAdBgNVHQ4EFgQU8+yUjvKO +MMSOaMK/jmoZwMGfdmUwHwYDVR0jBBgwFoAU8+yUjvKOMMSOaMK/jmoZwMGfdmUw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggGBAIsAVHKzjevzrzSf1mDq3oQ/jASPGaa+AmfEY8V040c3WYOUBvFFGegHL9ZO +S0+oPccHByeS9H5zT4syGZRGeiXE2cQnsBFjOmCLheFzTzQ7a6Q0jEmOzc9PsmUn +QRmw/IAxePJzapt9cTRQ/Hio2gW0nFs6mXprXe870+k7MwESZc9eB9gZr9VT6vAQ +rMS2Jjw0LnTuZN0dNnWJRACwDf0vswHMGosCzWzogILKv4LXAJ3YNhXSBzf8bHMd +2qgc6CCOMnr+bScW5Fhs6z7w/iRSKXG4lntTS0UgVUBehhvsyUaRku6sk2WRLpS2 +tqzoozSJpBoSDU1EpVLti5HuL6avpJUl+c7HW6cA05PKtDxdTfexPMxttEW+gu0Y +kMiG0XVRUARM6E/S1lCqdede/6F7Jxkca0ksbE1rY8w7cwDzmSbQgofTqTactD25 +SGiokvAnjgzNFXZChIDJP6N+tN3X+Kx2umCXPFofTt5x7gk5EN0x1WhXXRrlQroO +aOZF0w== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/capath/ceff1710.0 b/Lib/test/certdata/capath/ceff1710.0 new file mode 100644 index 00000000000..aa9dbbe294f --- /dev/null +++ b/Lib/test/certdata/capath/ceff1710.0 @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEgDCCAuigAwIBAgIJAMstgJlaaVJbMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx +NDIzMTZaME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg +Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcjCCAaIwDQYJKoZI +hvcNAQEBBQADggGPADCCAYoCggGBANCgm7G5O3nuMS+4URwBde0JWUysyL9qCvh6 +CPAl4yV7avjE2KqgYAclsM9zcQVSaL8Gk64QYZa8s2mBGn0Z/CCGj5poG+3N4mxh +Z8dOVepDBiEb6bm+hF/C2uuJiOBCpkVJKtC5a4yTyUQ7yvw8lH/dcMWt2Es73B74 +VUu1J4b437CDz/cWN78TFzTUyVXtaxbJf60gTvAe2Ru/jbrNypbvHmnLUWZhSA3o +eaNZYdQQjeANOwuFttWFEt2lB8VL+iP6VDn3lwvJREceVnc8PBMBC2131hS6RPRT +NVbZPbk+NV/bM5pPWrk4RMkySf5m9h8al6rKTEr2uF5Af/sLHfhbodz4wC7QbUn1 +0kbUkFf+koE0ri04u6gXDOHlP+L3JgVUUPVksxxuRP9vqbQDlukOwojYclKQmcZB +D0aQWbg+b9Linh02gpXTWIoS8+LYDSBRI/CQLZo+fSaGsqfX+ShgA+N3x4gEyf6J +d3AQT8Ogijv0q0J74xSS2K4W1qHefQIDAQABo2MwYTAdBgNVHQ4EFgQU8+yUjvKO +MMSOaMK/jmoZwMGfdmUwHwYDVR0jBBgwFoAU8+yUjvKOMMSOaMK/jmoZwMGfdmUw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggGBAIsAVHKzjevzrzSf1mDq3oQ/jASPGaa+AmfEY8V040c3WYOUBvFFGegHL9ZO +S0+oPccHByeS9H5zT4syGZRGeiXE2cQnsBFjOmCLheFzTzQ7a6Q0jEmOzc9PsmUn +QRmw/IAxePJzapt9cTRQ/Hio2gW0nFs6mXprXe870+k7MwESZc9eB9gZr9VT6vAQ +rMS2Jjw0LnTuZN0dNnWJRACwDf0vswHMGosCzWzogILKv4LXAJ3YNhXSBzf8bHMd +2qgc6CCOMnr+bScW5Fhs6z7w/iRSKXG4lntTS0UgVUBehhvsyUaRku6sk2WRLpS2 +tqzoozSJpBoSDU1EpVLti5HuL6avpJUl+c7HW6cA05PKtDxdTfexPMxttEW+gu0Y +kMiG0XVRUARM6E/S1lCqdede/6F7Jxkca0ksbE1rY8w7cwDzmSbQgofTqTactD25 +SGiokvAnjgzNFXZChIDJP6N+tN3X+Kx2umCXPFofTt5x7gk5EN0x1WhXXRrlQroO +aOZF0w== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/cert3.pem b/Lib/test/certdata/cert3.pem new file mode 100644 index 00000000000..4ab0f5ff133 --- /dev/null +++ b/Lib/test/certdata/cert3.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF8TCCBFmgAwIBAgIJAMstgJlaaVJcMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx +NDIzMTZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj +MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxv +Y2FsaG9zdDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKAqKHEL7aDt +3swl8hQF8VaK4zDGDRaF3E/IZTMwCN7FsQ4ejSiOe3E90f0phHCIpEpv2OebNenY +IpOGoFgkh62r/cthmnhu8Mn+FUIv17iOq7WX7B30OSqEpnr1voLX93XYkAq8LlMh +P79vsSCVhTwow3HZY7krEgl5WlfryOfj1i1TODSFPRCJePh66BsOTUvV/33GC+Qd +pVZVDGLowU1Ycmr/FdRvwT+F39Dehp03UFcxaX0/joPhH5gYpBB1kWTAQmxuqKMW +9ZZs6hrPtMXF/yfSrrXrzTdpct9paKR8RcufOcS8qju/ISK+1P/LXg2b5KJHedLo +TTIO3yCZ4d1odyuZBP7JDrI05gMJx95gz6sG685Qc+52MzLSTwr/Qg+MOjQoBy0o +8fRRVvIMEwoN0ZDb4uFEUuwZceUP1vTk/GGpNQt7ct4ropn6K4Zta3BUtovlLjZa +IIBhc1KETUqjRDvC6ACKmlcJ/5pY/dbH1lOux+IMFsh+djmaV90b3QIDAQABo4IB +wDCCAbwwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA4GA1UdDwEB/wQEAwIFoDAdBgNV +HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4E +FgQUP7HpT6C+MGY+ChjID0caTzRqD0IwfQYDVR0jBHYwdIAU8+yUjvKOMMSOaMK/ +jmoZwMGfdmWhUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcoIJAMst +gJlaaVJbMIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKGMGh0dHA6Ly90ZXN0 +Y2EucHl0aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNlcjA1BggrBgEFBQcw +AYYpaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2Evb2NzcC8wQwYD +VR0fBDwwOjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0 +Y2EvcmV2b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBAMo0usXQzycxMtYN +JzC42xfftzmnu7E7hsQx/fur22MazJCruU6rNEkMXow+cKOnay+nmiV7AVoYlkh2 ++DZ4dPq8fWh/5cqmnXvccr2jJVEXaOjp1wKGLH0WfLXcRLIK4/fJM6NRNoO81HDN +hJGfBrot0gUKZcPZVQmouAlpu5OGwrfCkHR8v/BdvA5jE4zr+g/x+uUScE0M64wu +okJCAAQP/PkfQZxjePBmk7KPLuiTHFDLLX+2uldvUmLXOQsJgqumU03MBT4Z8NTA +zqmtEM65ceSP8lo8Zbrcy+AEkCulFaZ92tyjtbe8oN4wTmTLFw06oFLSZzuiOgDV +OaphdVKf/pvA6KBpr6izox0KQFIE5z3AAJZfKzMGDDD20xhy7jjQZNMAhjfsT+k4 +SeYB/6KafNxq08uoulj7w4Z4R/EGpkXnU96ZHYHmvGN0RnxwI1cpYHCazG8AjsK/ +anN9brBi5twTGrn+D8LRBqF5Yn+2MKkD0EdXJdtIENHP+32sPQ== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/ffdh3072.pem b/Lib/test/certdata/ffdh3072.pem new file mode 100644 index 00000000000..ad69bac8d00 --- /dev/null +++ b/Lib/test/certdata/ffdh3072.pem @@ -0,0 +1,41 @@ + DH Parameters: (3072 bit) + prime: + 00:ff:ff:ff:ff:ff:ff:ff:ff:ad:f8:54:58:a2:bb: + 4a:9a:af:dc:56:20:27:3d:3c:f1:d8:b9:c5:83:ce: + 2d:36:95:a9:e1:36:41:14:64:33:fb:cc:93:9d:ce: + 24:9b:3e:f9:7d:2f:e3:63:63:0c:75:d8:f6:81:b2: + 02:ae:c4:61:7a:d3:df:1e:d5:d5:fd:65:61:24:33: + f5:1f:5f:06:6e:d0:85:63:65:55:3d:ed:1a:f3:b5: + 57:13:5e:7f:57:c9:35:98:4f:0c:70:e0:e6:8b:77: + e2:a6:89:da:f3:ef:e8:72:1d:f1:58:a1:36:ad:e7: + 35:30:ac:ca:4f:48:3a:79:7a:bc:0a:b1:82:b3:24: + fb:61:d1:08:a9:4b:b2:c8:e3:fb:b9:6a:da:b7:60: + d7:f4:68:1d:4f:42:a3:de:39:4d:f4:ae:56:ed:e7: + 63:72:bb:19:0b:07:a7:c8:ee:0a:6d:70:9e:02:fc: + e1:cd:f7:e2:ec:c0:34:04:cd:28:34:2f:61:91:72: + fe:9c:e9:85:83:ff:8e:4f:12:32:ee:f2:81:83:c3: + fe:3b:1b:4c:6f:ad:73:3b:b5:fc:bc:2e:c2:20:05: + c5:8e:f1:83:7d:16:83:b2:c6:f3:4a:26:c1:b2:ef: + fa:88:6b:42:38:61:1f:cf:dc:de:35:5b:3b:65:19: + 03:5b:bc:34:f4:de:f9:9c:02:38:61:b4:6f:c9:d6: + e6:c9:07:7a:d9:1d:26:91:f7:f7:ee:59:8c:b0:fa: + c1:86:d9:1c:ae:fe:13:09:85:13:92:70:b4:13:0c: + 93:bc:43:79:44:f4:fd:44:52:e2:d7:4d:d3:64:f2: + e2:1e:71:f5:4b:ff:5c:ae:82:ab:9c:9d:f6:9e:e8: + 6d:2b:c5:22:36:3a:0d:ab:c5:21:97:9b:0d:ea:da: + 1d:bf:9a:42:d5:c4:48:4e:0a:bc:d0:6b:fa:53:dd: + ef:3c:1b:20:ee:3f:d5:9d:7c:25:e4:1d:2b:66:c6: + 2e:37:ff:ff:ff:ff:ff:ff:ff:ff + generator: 2 (0x2) + recommended-private-length: 276 bits +-----BEGIN DH PARAMETERS----- +MIIBjAKCAYEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 +7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 +nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZsYu +N///////////AgECAgIBFA== +-----END DH PARAMETERS----- diff --git a/Lib/test/certdata/idnsans.pem b/Lib/test/certdata/idnsans.pem new file mode 100644 index 00000000000..07a42422af1 --- /dev/null +++ b/Lib/test/certdata/idnsans.pem @@ -0,0 +1,166 @@ +-----BEGIN PRIVATE KEY----- +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 +81hAHFgeD4f+C8fLols/sA== +-----END PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + cb:2d:80:99:5a:69:52:60 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server + Validity + Not Before: Aug 29 14:23:16 2018 GMT + Not After : Oct 28 14:23:16 2037 GMT + Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=idnsans + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (3072 bit) + Modulus: + 00:a9:eb:3b:78:d1:60:77:2b:bc:a3:04:6b:a0:9d: + 1b:aa:23:70:29:7e:d4:e1:65:60:c8:a6:f5:b4:71: + 59:8d:d2:7a:90:c6:c3:ba:08:29:d2:e1:d4:bb:49: + 88:46:16:22:2a:5a:33:55:73:65:86:d2:0b:fb:9c: + 2e:43:16:0b:aa:6e:77:4d:66:4f:7f:4a:35:fc:10: + ec:4a:cf:80:0d:8d:de:b5:5a:74:e4:0c:ee:53:6e: + 28:b2:77:e4:ee:10:1c:61:99:b1:05:eb:d6:c5:f5: + db:5d:d5:28:28:f2:39:8c:ee:3f:9a:6b:a0:a5:62: + 1f:f8:f5:bc:7e:6e:52:43:17:be:84:19:2a:e6:07: + 5a:be:a1:b3:1e:65:44:61:3d:73:41:7a:88:aa:29: + 1f:37:c7:91:dc:2d:32:58:32:87:2f:6f:a9:bc:b8: + 00:70:b9:29:1a:d7:71:9b:dd:01:ae:d3:aa:79:95: + d2:ba:4c:32:b7:f0:7b:28:3b:2e:8c:14:ee:45:d4: + fa:e4:6a:41:bf:78:11:ce:8d:c0:4e:18:f7:cd:99: + 3c:16:3a:ab:f0:95:c4:16:ca:4a:f8:42:ca:56:4b: + ec:93:cd:d9:20:67:e9:b1:6a:bf:f2:42:ea:bb:c2: + e6:12:35:a1:1f:18:7e:72:a1:8b:55:58:88:0a:c3: + 06:51:0c:fa:a9:49:0b:95:7b:e2:7e:31:62:cf:43: + a4:53:33:ea:d7:20:75:73:1e:d3:e4:d5:ff:03:ae: + 03:7e:85:27:a9:27:2f:ec:54:10:44:40:2c:e3:9f: + 20:7e:b1:c9:fb:e8:44:d6:72:1d:e2:c9:5a:0c:8f: + 7a:15:38:6b:34:6f:85:d3:0c:c9:94:c3:f0:e6:12: + de:53:0d:a8:7b:4e:a1:37:1b:e7:e9:04:86:e0:31: + 28:f1:33:35:10:66:5d:bb:8b:39:8c:f6:74:02:25: + 10:28:b0:da:0d:c1:7a:a5:33:be:78:2b:d5:9c:63: + a8:13:f2:49:1c:8f:50:9f:4e:63 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:idnsans, DNS:xn--knig-5qa.idn.pythontest.net, DNS:xn--knigsgsschen-lcb0w.idna2003.pythontest.net, DNS:xn--knigsgchen-b4a3dun.idna2008.pythontest.net, DNS:xn--nxasmq6b.idna2003.pythontest.net, DNS:xn--nxasmm1c.idna2008.pythontest.net + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 5B:93:42:58:B0:B4:18:CC:41:4C:15:EB:42:33:66:77:4C:71:2F:42 + X509v3 Authority Key Identifier: + keyid:F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 + DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server + serial:CB:2D:80:99:5A:69:52:5B + Authority Information Access: + CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer + OCSP - URI:http://testca.pythontest.net/testca/ocsp/ + X509v3 CRL Distribution Points: + Full Name: + URI:http://testca.pythontest.net/testca/revocation.crl + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 5f:d8:9b:dc:22:55:80:47:e1:9b:04:3e:46:53:9b:e5:a7:4a: + 8f:eb:53:01:39:d5:04:f6:cf:dc:48:84:8a:a9:c3:a5:35:22: + 2f:ab:74:77:ec:a6:fd:b1:e6:e6:74:82:38:54:0b:27:36:e6: + ec:3d:fe:92:1a:b2:7a:35:0d:a3:e5:7c:ff:e5:5b:1a:28:4b: + 29:1f:99:1b:3e:11:e9:e2:e0:d7:da:06:4f:e3:7b:8c:ad:30: + f4:39:24:e8:ad:2a:0e:71:74:ab:ed:62:e9:9f:85:7e:6a:b0: + bb:53:b4:d7:6b:b8:da:54:15:5c:9a:41:cf:61:f1:ab:67:d6: + 27:5c:0c:a3:d7:41:e7:27:3e:58:89:d6:1f:3f:2a:52:cc:13: + 0b:4b:e6:d6:ba:a0:c7:fd:e3:17:a4:b8:da:cc:cb:88:70:21: + 3b:70:df:09:40:6c:e7:02:81:08:80:b0:36:77:fb:44:c5:cf: + bf:19:54:7c:d1:4e:1f:a2:44:9e:d8:56:0e:bf:4b:0b:e0:84: + 6f:bc:f6:c6:7f:35:7a:17:ca:83:b3:82:c6:4e:d3:f3:d8:30: + 05:fd:6d:3c:8a:ab:63:55:6f:c5:18:ba:66:fe:e2:35:04:2b: + ae:76:34:f0:56:18:e8:54:db:83:b2:1b:93:0a:25:81:81:f0: + 25:ca:0a:95:be:8e:2f:05:3f:6c:e7:de:d1:7c:b8:a3:71:7c: + 6f:8a:05:c3:69:eb:6f:e6:76:8c:11:e1:59:0b:12:53:07:42: + 84:e8:89:ee:ab:7d:28:81:48:e8:79:d5:cf:a2:05:a4:fd:72: + 2c:7d:b4:1c:08:90:4e:0d:10:05:d1:9a:c0:69:4c:0a:14:39: + 17:fb:4d:5b:f6:42:bb:46:27:23:0f:5e:57:5b:b8:ae:9b:a3: + 0e:23:59:41:63:41:a4:f1:69:df:b3:a3:5c:10:d5:63:30:74: + a8:3c:0c:8e:1c:6b:10:e1:13:27:02:26:9b:fd:88:93:7e:91: + 9c:f9:c2:07:27:a4 +-----BEGIN CERTIFICATE----- +MIIGvTCCBSWgAwIBAgIJAMstgJlaaVJgMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx +NDIzMTZaMF0xCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj +MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xEDAOBgNVBAMMB2lk +bnNhbnMwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCp6zt40WB3K7yj +BGugnRuqI3ApftThZWDIpvW0cVmN0nqQxsO6CCnS4dS7SYhGFiIqWjNVc2WG0gv7 +nC5DFguqbndNZk9/SjX8EOxKz4ANjd61WnTkDO5Tbiiyd+TuEBxhmbEF69bF9dtd +1Sgo8jmM7j+aa6ClYh/49bx+blJDF76EGSrmB1q+obMeZURhPXNBeoiqKR83x5Hc +LTJYMocvb6m8uABwuSka13Gb3QGu06p5ldK6TDK38HsoOy6MFO5F1PrkakG/eBHO +jcBOGPfNmTwWOqvwlcQWykr4QspWS+yTzdkgZ+mxar/yQuq7wuYSNaEfGH5yoYtV +WIgKwwZRDPqpSQuVe+J+MWLPQ6RTM+rXIHVzHtPk1f8DrgN+hSepJy/sVBBEQCzj +nyB+scn76ETWch3iyVoMj3oVOGs0b4XTDMmUw/DmEt5TDah7TqE3G+fpBIbgMSjx +MzUQZl27izmM9nQCJRAosNoNwXqlM754K9WcY6gT8kkcj1CfTmMCAwEAAaOCAo4w +ggKKMIHhBgNVHREEgdkwgdaCB2lkbnNhbnOCH3huLS1rbmlnLTVxYS5pZG4ucHl0 +aG9udGVzdC5uZXSCLnhuLS1rbmlnc2dzc2NoZW4tbGNiMHcuaWRuYTIwMDMucHl0 +aG9udGVzdC5uZXSCLnhuLS1rbmlnc2djaGVuLWI0YTNkdW4uaWRuYTIwMDgucHl0 +aG9udGVzdC5uZXSCJHhuLS1ueGFzbXE2Yi5pZG5hMjAwMy5weXRob250ZXN0Lm5l +dIIkeG4tLW54YXNtbTFjLmlkbmEyMDA4LnB5dGhvbnRlc3QubmV0MA4GA1UdDwEB +/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUW5NCWLC0GMxBTBXrQjNmd0xxL0IwfQYDVR0jBHYwdIAU +8+yUjvKOMMSOaMK/jmoZwMGfdmWhUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQK +DB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNh +LXNlcnZlcoIJAMstgJlaaVJbMIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKG +MGh0dHA6Ly90ZXN0Y2EucHl0aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNl +cjA1BggrBgEFBQcwAYYpaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0 +Y2Evb2NzcC8wQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250 +ZXN0Lm5ldC90ZXN0Y2EvcmV2b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGB +AF/Ym9wiVYBH4ZsEPkZTm+WnSo/rUwE51QT2z9xIhIqpw6U1Ii+rdHfspv2x5uZ0 +gjhUCyc25uw9/pIasno1DaPlfP/lWxooSykfmRs+Eeni4NfaBk/je4ytMPQ5JOit +Kg5xdKvtYumfhX5qsLtTtNdruNpUFVyaQc9h8atn1idcDKPXQecnPliJ1h8/KlLM +EwtL5ta6oMf94xekuNrMy4hwITtw3wlAbOcCgQiAsDZ3+0TFz78ZVHzRTh+iRJ7Y +Vg6/SwvghG+89sZ/NXoXyoOzgsZO0/PYMAX9bTyKq2NVb8UYumb+4jUEK652NPBW +GOhU24OyG5MKJYGB8CXKCpW+ji8FP2zn3tF8uKNxfG+KBcNp62/mdowR4VkLElMH +QoToie6rfSiBSOh51c+iBaT9cix9tBwIkE4NEAXRmsBpTAoUORf7TVv2QrtGJyMP +XldbuK6bow4jWUFjQaTxad+zo1wQ1WMwdKg8DI4caxDhEycCJpv9iJN+kZz5wgcn +pA== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/keycert.passwd.pem b/Lib/test/certdata/keycert.passwd.pem new file mode 100644 index 00000000000..187021b8eeb --- /dev/null +++ b/Lib/test/certdata/keycert.passwd.pem @@ -0,0 +1,69 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIHbTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIc17oH9riZswCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDwi0Mkj59S0hplpnDSNHwPBIIH +EFGdZuO4Cwzg0bspLhE1UpBN5cBq1rKbf4PyVtCczIqJt3KjO3H5I4KdQd9zihkN +A1qzMiqVZOnQZw1eWFXMdyWuCgvNe1S/PRLWY3iZfnuZ9gZXQvyMEHy4JU7pe2Ib +GNm9mzadzJtGv0YZ05Kkza20zRlOxC/cgaNUV6TPeTSwW9CR2bylxw0lTFKBph+o +uFGcAzhqQuw9vsURYJf1f1iE7bQsnWU2kKmb9cx6kaUXiGJpkUMUraBL/rShoHa0 +eet6saiFnK3XGMCIK0mhS9s92CIQV5H9oQQPo/7s6MOoUHjC/gFoWBXoIDOcN9aR +ngybosCLtofY2m14WcHXvu4NJnfnKStx73K3dy3ZLr2iyjnsqGD1OhqGEWOVG/ho +QiZEhZ+9sOnqWI2OuMhMoQJNvrLj7AY4QbdkahdjNvLjDAQSuMI2uSUDFDNfkQdy +hqF/iiEM28PmSHCapgCpzR4+VfEfXBoyBCqs973asa9qhrorfnBVxXnvsqmKNLGH +dymtEPei9scpoftE5T9TPqQj46446bXk23Xpg8QIFa8InQC2Y+yZqqlqvzCAbN6S +Qcq1DcTSAMnbmBXVu9hPmJYIYOlBMHL8JGbsGrkVOhLiiIou4w3G+DyAvIwPj6j9 +BHLqa7HgUnUEC+zL4azVHOSMqmDsOiF3w9fkBWNSkOyNoZpe+gBjbxq7sp+GjAJv +1CemRC3LSoNzLcjRG2IEGs1jlEHSSfijvwlE4lEy3JVc+QK8BOkKXXDVhY1SQHcS +pniEnj95RFVmAujdFDBoUgySyxK/y6Ju/tHPpSTG9VMNbJTKTdBWAVWWHVJzBFhR +0Ug62VrBK7fmfUdH1b37aIxqsPND2De6WLm0RX+7r3XPDJ7hm+baKCchI5CvnG19 +ky8InhMbU4qV+9LceMETmNKKDkhKl4Zx/Y3nab7DG9s/RZfrTdCHojc9Va/t0Ykp +qlVrvdj/893CdI78SW3VjWBJGWfKMyT16hBMY3TPz6ulbFXk6Pul/KcLLWslghS+ +GKZjyBe96UwfH4C7WjuIB+zo+De3Wr8xOCdJR5zwEutBMM+L/Wul8B6wIEGS71kB +TN/CAoeIgHLQFbcw4YE80dllTnSEsqF+ahVTTcCt3iLUaOgeTUxteMbXY9+nekSX +x8aUcvkMhbU9omdEowFr5/HIMKXo4UXat4fIGgh2pG8v8fA46hZXkhWUh/PhbnQw +StXzn4fA13erqVI679kHMmOIQebv4oqdcwkImrH5fEsACNjQbkYZF5fD4z+1GHkA +e2eGqejVT+OV14I8qfx9oqs2f8aqijH8fYLU0TymE7p53DYZy4WvDwk22I4rMzoQ +sGkOZwfKUYpdBI2t6tEf1ROBjoNG0E2Onq+5iooibN08rKXKAQMWsK+2vNHNHwBW +49vRheQNnRqSuLY+b7QAjA0KuRWo9YptCbnXyF/Aw64jMfAGjggDLoaZfALGZk3n +P+ZoL9xc7rYRpIca44BeYI6AhHFcWWIOX7Sm69FvmyHlfsgTAXVgY1lQPuGy68Au +PHSkgUyydDtkrfb2W2gJuqD/+h+9X2z+o/+nETYPCZm3sH5xvTY/DTcTx9kTpXxx +YQBaFTt12eVX7wZVr5K3u9M371rg+SeXC2SzL4T6APHD52cxbA1jgM0JFh3KJTuk +fADxIzM1NdzYQ45J6i2w+/Fh4VPnXZ0oiUSwE094XTBlvhI6zHgar2Q0Qx1P51vB +odd9XzyDLULuIzei0DYjTIg0KhE+wAGq1I5qtiMhmy5TdCKKNA9WGb1Pq38zpyjU +wGmztzSzCEjfLyhChaUObVRRxEfD5ioxKer/fczOhKQe8FXmGy5u/04tVmmEyNOO +JkkDtZy+UbKuJ257QnY72wPjgtHNy+S4Iv7zHUbNJNhxk+xBlRcmRNWCEM20LBSO +Tj4S9gyan+gH2+WFxy8FaENUhM+vHFEeJcjQIBFBeWICmSmdkh/r0YK1UVJ9NLfR +l0HiKm3lKg+kNCexTAPLMt2rGZ4PAKVnhVaxtuHMYYDpl2GYmyH73B9BfcPdA/Zx +GUBmd9hwcLz9SuUg+fjHcogZRRRlcZlKhw3zUCsqHSCQXZCQm7mBlG/5C/7cM7wQ +IRtsNospLStOg51gv21ClQ+uWx30XEcwmnIfVoLl1vMaguuf1u5u3dWBD/UgmqiP +1Ym8jv0BF/AS+u/CtUpwe7ZWxFT0vbyi10xxIF7O07fwFa+5dME3ycZwcyiE95K1 +ftcHlGOIhuVBMSNZXC4I9LM+7IWy+hanUcK+v5RvwBDSJV3fnAOdfrka1L/HyEEb +x/FYKEiU/TAjXDw2NtZ2itpADTSG5KbdJSwPr01Ak7aE+QYe7TIKJhBDZXGQlqq8 +1wv77zyv7V5Xq2cxSEKgSqzB9fhYZCASe8+HWlV2T+Sd +-----END ENCRYPTED PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIEgzCCAuugAwIBAgIUU+FIM/dUbCklbdDwNPd2xemDAEwwDQYJKoZIhvcNAQEL +BQAwXzELMAkGA1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYD +VQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxo +b3N0MB4XDTIzMTEyNTA0MjEzNloXDTQzMDEyNDA0MjEzNlowXzELMAkGA1UEBhMC +WFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYDVQQKDBpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxob3N0MIIBojANBgkqhkiG +9w0BAQEFAAOCAY8AMIIBigKCAYEAzXTIl1su11AGu6sDPsoxqcRGyAX0yjxIcswF +vj+eW/fBs2GcBby95VEOKpJPKRYYB7fAEAjAKK59zFdsDX/ynxPZLqyLQocBkFVq +tclhCRZu//KZND+uQuHSx3PjGkSvK/nrGjg5T0bkM4SFeb0YdLb+0aDTKGozUC82 +oBAilNcrFz1VXpEF0qUe9QeKQhyd0MaW5T1oSn+U3RAj2MXm3TGExyZeaicpIM5O +HFlnwUxsYSDZo0jUj342MbPOZh8szZDWi042jdtSA3i8uMSplEf4O8ZPmX0JCtrz +fVjRVdaKXIjrhMNWB8K44q6AeyhqJcVHtOmPYoHDm0qIjcrurt0LZaGhmCuKimNd +njcPxW0VQmDIS/mO5+s24SK+Mpznm5q/clXEwyD8FbrtrzV5cHCE8eNkxjuQjkmi +wW9uadK1s54tDwRWMl6DRWRyxoF0an885UQWmbsgEB5aRmEx2L0JeD0/q6Iw1Nta +As8DG4AaWuYMrgZXz7XvyiMq3IxVAgMBAAGjNzA1MBQGA1UdEQQNMAuCCWxvY2Fs +aG9zdDAdBgNVHQ4EFgQUl2wd7iWE1JTZUVq2yFBKGm9N36owDQYJKoZIhvcNAQEL +BQADggGBAF0f5x6QXFbgdyLOyeAPD/1DDxNjM68fJSmNM/6vxHJeDFzK0Pja+iJo +xv54YiS9F2tiKPpejk4ujvLQgvrYrTQvliIE+7fUT0dV74wZKPdLphftT9uEo1dH +TeIld+549fqcfZCJfVPE2Ka4vfyMGij9hVfY5FoZL1Xpnq/ZGYyWZNAPbkG292p8 +KrfLZm/0fFYAhq8tG/6DX7+2btxeX4MP/49tzskcYWgOjlkknyhJ76aMG9BJ1D7F +/TIEh5ihNwRTmyt023RBz/xWiN4xBLyIlpQ6d5ECKmFNFr0qnEui6UovfCHUF6lZ +qcAQ5VFQQ2CayNlVmQ+UGmWIqANlacYWBt7Q6VqpGg24zTMec1/Pqd6X07ScSfrm +MAtywrWrU7p1aEkN5lBa4n/XKZHGYMjor/YcMdF5yjdSrZr274YYO1pafmTFwRwH +5o16c8WPc0aPvTFbkGIFT5ddxYstw+QwsBtLKE2lJ4Qfmxt0Ew/0L7xkbK1BaCOo +EGD2IF7VDQ== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/keycert.pem b/Lib/test/certdata/keycert.pem new file mode 100644 index 00000000000..a30d15ca4d6 --- /dev/null +++ b/Lib/test/certdata/keycert.pem @@ -0,0 +1,67 @@ +-----BEGIN PRIVATE KEY----- +MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQDNdMiXWy7XUAa7 +qwM+yjGpxEbIBfTKPEhyzAW+P55b98GzYZwFvL3lUQ4qkk8pFhgHt8AQCMAorn3M +V2wNf/KfE9kurItChwGQVWq1yWEJFm7/8pk0P65C4dLHc+MaRK8r+esaODlPRuQz +hIV5vRh0tv7RoNMoajNQLzagECKU1ysXPVVekQXSpR71B4pCHJ3QxpblPWhKf5Td +ECPYxebdMYTHJl5qJykgzk4cWWfBTGxhINmjSNSPfjYxs85mHyzNkNaLTjaN21ID +eLy4xKmUR/g7xk+ZfQkK2vN9WNFV1opciOuEw1YHwrjiroB7KGolxUe06Y9igcOb +SoiNyu6u3QtloaGYK4qKY12eNw/FbRVCYMhL+Y7n6zbhIr4ynOebmr9yVcTDIPwV +uu2vNXlwcITx42TGO5COSaLBb25p0rWzni0PBFYyXoNFZHLGgXRqfzzlRBaZuyAQ +HlpGYTHYvQl4PT+rojDU21oCzwMbgBpa5gyuBlfPte/KIyrcjFUCAwEAAQKCAYAO +M1r0+TCy4Z1hhceu5JdLql0RELZTbxi71IW2GVwW87gv75hy3hGLAs/1mdC+YIBP +MkBka1JqzWq0/7rgcP5CSAMsInFqqv2s7fZ286ERGXuZFbnInnkrNsQUlJo3E9W+ +tqKtGIM/i0EVHX0DRdJlqMtSjmjh43tB+M1wAUV+n6OjEtJue5wZK+AIpBmGicdP +qZY+6IBnm8tcfzPXFRCoq7ZHdIu0jxnc4l2MQJK3DdL04KoiStOkSl8xDsI+lTtq +D3qa41LE0TY8X2jJ/w6KK3cUeK7F4DQYs+kfCKWMVPpn0/5u6TbC1F7gLvkrseph +7cIgrruNNs9iKacnR1w3U72R+hNxHsNfo4RGHFa192p/Mfc+kiBd5RNR/M9oHdeq +U6T/+KM+QyF5dDOyonY0QjwfAcEx+ZsV72nj8AerjM907I6dgHo/9YZ2S1Dt/xuG +ntD+76GDzmrOvXmmpF0DsTn+Wql7AC4uzaOjv6PVziqz03pR61RpjPDemyJEWMkC +gcEA7BkGGX3enBENs3X6BYFoeXfGO/hV7/aNpA6ykLzw657dqwy2b6bWLiIaqZdZ +u0oiY6+SpOtavkZBFTq4bTVD58FHL0n73Yvvaft507kijpYBrxyDOfTJOETv+dVG +XiY8AUSAE6GjPi0ebuYIVUxoDnMeWDuRJNvTck4byn1hJ1aVlEhwXNxt/nAjq48s +5QDuR6Z9F8lqEACRYCHSMQYFm35c7c1pPsHJnElX8a7eZ9lT7HGPXHaf/ypMkOzo +dvJNAoHBAN7GhDomff/kSgQLyzmqKqQowTZlyihnReapygwr8YpNcqKDqq6VlnfH +Jl1+qtSMSVI0csmccwJWkz1WtSjDsvY+oMdv4gUK3028vQAMQZo+Sh7OElFPFET3 +UmL+Nh73ACPgpiommsdLZQPcIqpWNT5NzO+Jm5xa+U9ToVZgQ7xjrqee5NUiMutr +r7UWAz7vDWu3x7bzYRRdUJxU18NogGbFGWJ1KM0c67GUXu2E7wBQdjVdS78UWs+4 +XBxKQkG2KQKBwQCtO+M82x122BB8iGkulvhogBjlMd8klnzxTpN5HhmMWWH+uvI1 +1G29Jer4WwRNJyU6jb4E4mgPyw7AG/jssLOlniy0Jw32TlIaKpoGXwZbJvgPW9Vx +tgnbDsIiR3o9ZMKMj42GWgike4ikCIc+xzRmvdMbHIHwUJfCfEtp9TtPGPnh9pDz +og3XLsMNg52GXnt3+VI6HOCE41XH+qj2rZt5r2tSVXEOyjQ7R5mOzSeFfXJVwDFX +v/a/zHKnuB0OAdUCgcBLrxPTEaqy2eMPdtZHM/mipbnmejRw/4zu7XYYJoG7483z +SlodT/K7pKvzDYqKBVMPm4P33K/x9mm1aBTJ0ZqmL+a9etRFtEjjByEKuB89gLX7 +uzTb7MrNF10lBopqgK3KgpLRNSZWWNXrtskMJ5eVICdkpdJ5Dyst+RKR3siEYzU9 ++yxxAFpeQsqB8gWORva/RsOR8yNjIMS3J9fZqlIdGA8ktPr0nEOyo96QQR5VdACE +5rpKI2cqtM6OSegynOkCgcAnr2Xzjef6tdcrxrQrq0DjEFTMoCAxQRa6tuF/NYHV +AK70Y4hBNX84Bvym4hmfbMUEuOCJU+QHQf/iDQrHXPhtX3X2/t8M+AlIzmwLKf2o +VwCYnZ8SqiwSaWVg+GANWLh0JuKn/ZYyR8urR79dAXFfp0UK+N39vIxNoBisBf+F +G8mca7zx3UtK2eOW8WgGHz+Y20VZy0m/nkNekd1ZTXoSGhL+iN4XsTRn1YQIn69R +kNdcwhtZZ3dpChUdf+w/LIc= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIEgzCCAuugAwIBAgIUU+FIM/dUbCklbdDwNPd2xemDAEwwDQYJKoZIhvcNAQEL +BQAwXzELMAkGA1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYD +VQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxo +b3N0MB4XDTIzMTEyNTA0MjEzNloXDTQzMDEyNDA0MjEzNlowXzELMAkGA1UEBhMC +WFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYDVQQKDBpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxob3N0MIIBojANBgkqhkiG +9w0BAQEFAAOCAY8AMIIBigKCAYEAzXTIl1su11AGu6sDPsoxqcRGyAX0yjxIcswF +vj+eW/fBs2GcBby95VEOKpJPKRYYB7fAEAjAKK59zFdsDX/ynxPZLqyLQocBkFVq +tclhCRZu//KZND+uQuHSx3PjGkSvK/nrGjg5T0bkM4SFeb0YdLb+0aDTKGozUC82 +oBAilNcrFz1VXpEF0qUe9QeKQhyd0MaW5T1oSn+U3RAj2MXm3TGExyZeaicpIM5O +HFlnwUxsYSDZo0jUj342MbPOZh8szZDWi042jdtSA3i8uMSplEf4O8ZPmX0JCtrz +fVjRVdaKXIjrhMNWB8K44q6AeyhqJcVHtOmPYoHDm0qIjcrurt0LZaGhmCuKimNd +njcPxW0VQmDIS/mO5+s24SK+Mpznm5q/clXEwyD8FbrtrzV5cHCE8eNkxjuQjkmi +wW9uadK1s54tDwRWMl6DRWRyxoF0an885UQWmbsgEB5aRmEx2L0JeD0/q6Iw1Nta +As8DG4AaWuYMrgZXz7XvyiMq3IxVAgMBAAGjNzA1MBQGA1UdEQQNMAuCCWxvY2Fs +aG9zdDAdBgNVHQ4EFgQUl2wd7iWE1JTZUVq2yFBKGm9N36owDQYJKoZIhvcNAQEL +BQADggGBAF0f5x6QXFbgdyLOyeAPD/1DDxNjM68fJSmNM/6vxHJeDFzK0Pja+iJo +xv54YiS9F2tiKPpejk4ujvLQgvrYrTQvliIE+7fUT0dV74wZKPdLphftT9uEo1dH +TeIld+549fqcfZCJfVPE2Ka4vfyMGij9hVfY5FoZL1Xpnq/ZGYyWZNAPbkG292p8 +KrfLZm/0fFYAhq8tG/6DX7+2btxeX4MP/49tzskcYWgOjlkknyhJ76aMG9BJ1D7F +/TIEh5ihNwRTmyt023RBz/xWiN4xBLyIlpQ6d5ECKmFNFr0qnEui6UovfCHUF6lZ +qcAQ5VFQQ2CayNlVmQ+UGmWIqANlacYWBt7Q6VqpGg24zTMec1/Pqd6X07ScSfrm +MAtywrWrU7p1aEkN5lBa4n/XKZHGYMjor/YcMdF5yjdSrZr274YYO1pafmTFwRwH +5o16c8WPc0aPvTFbkGIFT5ddxYstw+QwsBtLKE2lJ4Qfmxt0Ew/0L7xkbK1BaCOo +EGD2IF7VDQ== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/keycert2.pem b/Lib/test/certdata/keycert2.pem new file mode 100644 index 00000000000..c7c4aa74583 --- /dev/null +++ b/Lib/test/certdata/keycert2.pem @@ -0,0 +1,67 @@ +-----BEGIN PRIVATE KEY----- +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 +M4mhHOfwBicqYzzEYbOE9Vk9 +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIEjDCCAvSgAwIBAgIUQ2S3jJ5nve5k5956sgsrWY3vw9MwDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYD +VQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEVMBMGA1UEAwwMZmFrZWhv +c3RuYW1lMB4XDTIzMTEyNTA0MjEzN1oXDTQzMDEyNDA0MjEzN1owYjELMAkGA1UE +BhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYDVQQKDBpQeXRob24g +U29mdHdhcmUgRm91bmRhdGlvbjEVMBMGA1UEAwwMZmFrZWhvc3RuYW1lMIIBojAN +BgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsgFF43M4FBJ+5pjsAzIv/vsA0cai +QCdn+xfQA8U7jpvhLXaX2K+JgZ/FM/XtyPmYXhuct0GdXXQWAPWE33FC9KJ/FFyz +jgVWQnrcbEF6EgLgzD1dCF3hILWWQ13u0k6ikkHmJOws/lbR3pWfI+6DjnMebCYK +W2g/jhiZz6AHQfJfJ0H8agBuObFuD+oHyyqCS74inLQ3uab4IiQvLb8rYPyIREin +nny2eIsdnTY49hjVGi/pIVOlBLjNZCR3D1Yz6GHPsvH6Kf6oUNIrYQYTtjryfmJj +hXQhOu0sgsFN6fMyaTfl5MGx7bYJjYODkrOx+lexOdvwrEzUwErYQR17camjPr42 +xgEa89haXP5/i8EbezBiEdIFbZ7U4Jz9B6Hh7JIQCHg7Wj62esx3Zd2g8IAHnFXm +0xAU+WD26M/l9ZVnTq0Q9iVt26l0kSDdxOKs+SDLjcd1b4aUkXhWc0ekCG8UKszv +1afdyL8vIT2eljIH0mFrOwhrbuDT+/TyX3TLAgMBAAGjOjA4MBcGA1UdEQQQMA6C +DGZha2Vob3N0bmFtZTAdBgNVHQ4EFgQU5wVOIuQD/Jxmam/97g91+igosWQwDQYJ +KoZIhvcNAQELBQADggGBAFv5gW5x4ET5NXEw6vILlOtwxwplEbU/x6eUVR/AXtEz +jtq9zIk2svX/JIzSLRQnjJmb/nCDCeNcFMkkgIiB64I3yMJT9n50fO4EhSGEaITZ +vYAw0/U6QXw+B1VS1ijNA44X2zvC+aw1q9W+0SKtvnu7l16TQ654ey0Qh9hOF1HS +AZQ46593T9gaZMeexz4CShoBZ80oFOJezfNhyT3FK6tzXNbkVoJDhlLvr/ep81GG +mABUGtKQYYMhuSSp0TDvf7jnXxtQcZI5lQOxZp0fnWUcK4gMVJqFVicwY8NiOhAG +6TlvXYP4COLAvGmqBB+xUhekIS0jVzaMyek+hKK0sT/OE+W/fR5V9YT5QlHFJCf5 +IUIfDCpBZrBpsOTwsUm8eL0krLiBjYf0HgH5oFBc7aF4w1kuUJjlsJ68bzO9mLEF +HXDaOWbe00+7BMMDnyuEyLN8KaAGiN8x0NQRX+nTAjCdPs6E0NftcXtznWBID6tA +j5m7qjsoGurj6TlDsBJb1A== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/keycert3.pem b/Lib/test/certdata/keycert3.pem new file mode 100644 index 00000000000..20d9bd14e96 --- /dev/null +++ b/Lib/test/certdata/keycert3.pem @@ -0,0 +1,161 @@ +-----BEGIN PRIVATE KEY----- +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 +gDq3K6+KpVREQFEhyOtIB2kk +-----END PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + cb:2d:80:99:5a:69:52:5c + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server + Validity + Not Before: Aug 29 14:23:16 2018 GMT + Not After : Oct 28 14:23:16 2037 GMT + Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (3072 bit) + Modulus: + 00:a0:2a:28:71:0b:ed:a0:ed:de:cc:25:f2:14:05: + f1:56:8a:e3:30:c6:0d:16:85:dc:4f:c8:65:33:30: + 08:de:c5:b1:0e:1e:8d:28:8e:7b:71:3d:d1:fd:29: + 84:70:88:a4:4a:6f:d8:e7:9b:35:e9:d8:22:93:86: + a0:58:24:87:ad:ab:fd:cb:61:9a:78:6e:f0:c9:fe: + 15:42:2f:d7:b8:8e:ab:b5:97:ec:1d:f4:39:2a:84: + a6:7a:f5:be:82:d7:f7:75:d8:90:0a:bc:2e:53:21: + 3f:bf:6f:b1:20:95:85:3c:28:c3:71:d9:63:b9:2b: + 12:09:79:5a:57:eb:c8:e7:e3:d6:2d:53:38:34:85: + 3d:10:89:78:f8:7a:e8:1b:0e:4d:4b:d5:ff:7d:c6: + 0b:e4:1d:a5:56:55:0c:62:e8:c1:4d:58:72:6a:ff: + 15:d4:6f:c1:3f:85:df:d0:de:86:9d:37:50:57:31: + 69:7d:3f:8e:83:e1:1f:98:18:a4:10:75:91:64:c0: + 42:6c:6e:a8:a3:16:f5:96:6c:ea:1a:cf:b4:c5:c5: + ff:27:d2:ae:b5:eb:cd:37:69:72:df:69:68:a4:7c: + 45:cb:9f:39:c4:bc:aa:3b:bf:21:22:be:d4:ff:cb: + 5e:0d:9b:e4:a2:47:79:d2:e8:4d:32:0e:df:20:99: + e1:dd:68:77:2b:99:04:fe:c9:0e:b2:34:e6:03:09: + c7:de:60:cf:ab:06:eb:ce:50:73:ee:76:33:32:d2: + 4f:0a:ff:42:0f:8c:3a:34:28:07:2d:28:f1:f4:51: + 56:f2:0c:13:0a:0d:d1:90:db:e2:e1:44:52:ec:19: + 71:e5:0f:d6:f4:e4:fc:61:a9:35:0b:7b:72:de:2b: + a2:99:fa:2b:86:6d:6b:70:54:b6:8b:e5:2e:36:5a: + 20:80:61:73:52:84:4d:4a:a3:44:3b:c2:e8:00:8a: + 9a:57:09:ff:9a:58:fd:d6:c7:d6:53:ae:c7:e2:0c: + 16:c8:7e:76:39:9a:57:dd:1b:dd + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:localhost + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 3F:B1:E9:4F:A0:BE:30:66:3E:0A:18:C8:0F:47:1A:4F:34:6A:0F:42 + X509v3 Authority Key Identifier: + keyid:F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 + DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server + serial:CB:2D:80:99:5A:69:52:5B + Authority Information Access: + CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer + OCSP - URI:http://testca.pythontest.net/testca/ocsp/ + X509v3 CRL Distribution Points: + Full Name: + URI:http://testca.pythontest.net/testca/revocation.crl + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + ca:34:ba:c5:d0:cf:27:31:32:d6:0d:27:30:b8:db:17:df:b7: + 39:a7:bb:b1:3b:86:c4:31:fd:fb:ab:db:63:1a:cc:90:ab:b9: + 4e:ab:34:49:0c:5e:8c:3e:70:a3:a7:6b:2f:a7:9a:25:7b:01: + 5a:18:96:48:76:f8:36:78:74:fa:bc:7d:68:7f:e5:ca:a6:9d: + 7b:dc:72:bd:a3:25:51:17:68:e8:e9:d7:02:86:2c:7d:16:7c: + b5:dc:44:b2:0a:e3:f7:c9:33:a3:51:36:83:bc:d4:70:cd:84: + 91:9f:06:ba:2d:d2:05:0a:65:c3:d9:55:09:a8:b8:09:69:bb: + 93:86:c2:b7:c2:90:74:7c:bf:f0:5d:bc:0e:63:13:8c:eb:fa: + 0f:f1:fa:e5:12:70:4d:0c:eb:8c:2e:a2:42:42:00:04:0f:fc: + f9:1f:41:9c:63:78:f0:66:93:b2:8f:2e:e8:93:1c:50:cb:2d: + 7f:b6:ba:57:6f:52:62:d7:39:0b:09:82:ab:a6:53:4d:cc:05: + 3e:19:f0:d4:c0:ce:a9:ad:10:ce:b9:71:e4:8f:f2:5a:3c:65: + ba:dc:cb:e0:04:90:2b:a5:15:a6:7d:da:dc:a3:b5:b7:bc:a0: + de:30:4e:64:cb:17:0d:3a:a0:52:d2:67:3b:a2:3a:00:d5:39: + aa:61:75:52:9f:fe:9b:c0:e8:a0:69:af:a8:b3:a3:1d:0a:40: + 52:04:e7:3d:c0:00:96:5f:2b:33:06:0c:30:f6:d3:18:72:ee: + 38:d0:64:d3:00:86:37:ec:4f:e9:38:49:e6:01:ff:a2:9a:7c: + dc:6a:d3:cb:a8:ba:58:fb:c3:86:78:47:f1:06:a6:45:e7:53: + de:99:1d:81:e6:bc:63:74:46:7c:70:23:57:29:60:70:9a:cc: + 6f:00:8e:c2:bf:6a:73:7d:6e:b0:62:e6:dc:13:1a:b9:fe:0f: + c2:d1:06:a1:79:62:7f:b6:30:a9:03:d0:47:57:25:db:48:10: + d1:cf:fb:7d:ac:3d +-----BEGIN CERTIFICATE----- +MIIF8TCCBFmgAwIBAgIJAMstgJlaaVJcMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx +NDIzMTZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj +MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxv +Y2FsaG9zdDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKAqKHEL7aDt +3swl8hQF8VaK4zDGDRaF3E/IZTMwCN7FsQ4ejSiOe3E90f0phHCIpEpv2OebNenY +IpOGoFgkh62r/cthmnhu8Mn+FUIv17iOq7WX7B30OSqEpnr1voLX93XYkAq8LlMh +P79vsSCVhTwow3HZY7krEgl5WlfryOfj1i1TODSFPRCJePh66BsOTUvV/33GC+Qd +pVZVDGLowU1Ycmr/FdRvwT+F39Dehp03UFcxaX0/joPhH5gYpBB1kWTAQmxuqKMW +9ZZs6hrPtMXF/yfSrrXrzTdpct9paKR8RcufOcS8qju/ISK+1P/LXg2b5KJHedLo +TTIO3yCZ4d1odyuZBP7JDrI05gMJx95gz6sG685Qc+52MzLSTwr/Qg+MOjQoBy0o +8fRRVvIMEwoN0ZDb4uFEUuwZceUP1vTk/GGpNQt7ct4ropn6K4Zta3BUtovlLjZa +IIBhc1KETUqjRDvC6ACKmlcJ/5pY/dbH1lOux+IMFsh+djmaV90b3QIDAQABo4IB +wDCCAbwwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA4GA1UdDwEB/wQEAwIFoDAdBgNV +HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4E +FgQUP7HpT6C+MGY+ChjID0caTzRqD0IwfQYDVR0jBHYwdIAU8+yUjvKOMMSOaMK/ +jmoZwMGfdmWhUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcoIJAMst +gJlaaVJbMIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKGMGh0dHA6Ly90ZXN0 +Y2EucHl0aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNlcjA1BggrBgEFBQcw +AYYpaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2Evb2NzcC8wQwYD +VR0fBDwwOjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0 +Y2EvcmV2b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBAMo0usXQzycxMtYN +JzC42xfftzmnu7E7hsQx/fur22MazJCruU6rNEkMXow+cKOnay+nmiV7AVoYlkh2 ++DZ4dPq8fWh/5cqmnXvccr2jJVEXaOjp1wKGLH0WfLXcRLIK4/fJM6NRNoO81HDN +hJGfBrot0gUKZcPZVQmouAlpu5OGwrfCkHR8v/BdvA5jE4zr+g/x+uUScE0M64wu +okJCAAQP/PkfQZxjePBmk7KPLuiTHFDLLX+2uldvUmLXOQsJgqumU03MBT4Z8NTA +zqmtEM65ceSP8lo8Zbrcy+AEkCulFaZ92tyjtbe8oN4wTmTLFw06oFLSZzuiOgDV +OaphdVKf/pvA6KBpr6izox0KQFIE5z3AAJZfKzMGDDD20xhy7jjQZNMAhjfsT+k4 +SeYB/6KafNxq08uoulj7w4Z4R/EGpkXnU96ZHYHmvGN0RnxwI1cpYHCazG8AjsK/ +anN9brBi5twTGrn+D8LRBqF5Yn+2MKkD0EdXJdtIENHP+32sPQ== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/keycert4.pem b/Lib/test/certdata/keycert4.pem new file mode 100644 index 00000000000..ff4dceac790 --- /dev/null +++ b/Lib/test/certdata/keycert4.pem @@ -0,0 +1,161 @@ +-----BEGIN PRIVATE KEY----- +MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQDGKA1zZDjeNPh2 +J9WHVXXMUf8h5N4/bHCM3CbIaZ1dShkCgfmFWmOtruEihgbfRYaSWZAwCmVAQGjm +gvUfgOIgsFfM8yO+zDByPhza7XvWPZfEe7mNRFe5ZlYntbeM/vuWCM4VzwDq/mqF +TFxNRmwInqE7hx0WnfCoQWe9N41hJyl1K0OjADb+SjlpJ0/UJ63hsB+dowGjaaBv +J8HduQcRqNg8s6FcyJJ8Mjss1uRMFK2j9QrmgbA61XuIPCxzc3J57mW8FN2KsR8D +2HOhe9nsTGlxp+O5Cudf/RBWB443xcoyduwRXOFTdEAU45MS4tKGP2hzezuxMFQn +LKARXVW4/gFxZk7kU8TweZUS6LAYPfYJnlfteb6z37LAbtoDvzKUKBEDf/nmoa7C +uKxSPC5HIKhLbjU/6kuPglSVEfJPJWu2bZJDAkFL85Ot3gPs10EX2lMUy0Jt3tf+ +TaQjEvFZhpKN8KAdYj3eVgOfzIBbQyjotHJjFe9Jkq4q7RoI+ncCAwEAAQKCAYAH +tRsdRh1Z7JmHOasy+tPDsvhVuWLHMaYlScvAYhJh/W65YSKd56+zFKINlX3fYcp5 +Fz67Yy+uWahXVE2QgFou3KX0u+9ucRiLFXfYheWL3xSMXJgRee0LI/T7tRe7uAHu +CnoURqKCulIqzLOO1efx1eKasXmVuhEtmjhVpcmDGv8SChSKTIjzgOjqT7QGE9Xq +eSRhq7mulpq9zWq+/369yG+0SvPs60vTxNovDIaBn/RHSW5FjeDss5QnmYMh/ukN +dggoKllQlkTzHSxHmKrIJuryZC+bsqvEPUFXN0NMUYcZRvt1lwdjzq/A+w4gDDZG +7QqAzYMYQZMw9PJeHqu4mxfUX5hJWuAwG5I2eV3kBRheoFw7MxP0tw40fPlFU+Zh +pRXbKwhMAlIHi0D8NyMn3hkVPyToWVVY3vHRknBB/52RqRq3MjqEFaAZfp0nFkiF +ytv3Dd5aeBb1vraOIREyhxIxE/qY8CtZC+6JI8CpufLmFXB412WPwl0OrVpWYfEC +gcEA486zOI46xRDgDw0jqTpOFHzh+3VZ8UoPoiqCjKzJGnrh2EeLvTsXX/GZOj0m +5zl6RHEGFjm5vKCh2C72Vj/m+AFVy7V9iJRzTYzP8So/3paaqo7ZaROTa6uStxdD +VPnY1uIgVQz9w5coN4dmr+RLBpFvvWaHp1wuC08YIWxcC9HSTQpbi1EP5eo08fOk +8reNkDEHxihDGHr1xW0z0qJqK1IVyLP7wDkmapudMZjkjqJSGJwwefV4qyGMTV2b +suW1AoHBAN6t9n6LBH553MF5iUrNJYxXh/SCom4Zft9aD6W4bZV/xL4XPpKBB4HX +aWdeI0iYZU9U+CZ88tBoQCt+JMrJ9cz03ENOvA/MBMREwbZ2hKmQgnoDZsV0vNry +6UsxeQmeNpGQFUz9foVJQVRdQCceN2YEABdehV1HZoSBbuGZkzqGJXrWwaf/ZhpB +dPYGUGOsczoD2/QLuWy2M7f7v0Ews6Heww3zipWzvdxKE0IpyVs30ZwVi8CRQiWU +bEcleXP6+wKBwAi3xEwJxV39Q1XQHuk+/fXywYMp/oMpXmfKUKypgBivUy0/r61S +MZbOXBrKdE6s+GzeFmmLU/xP+WGYinzKfUBIbMwa6e7sH218UgjcoQ0Xnlugk9ld +kmqwajDvhvgdh5rRlIMsuBlgE33shJV+mxBpSGlrHw3cjTaJlFbTGsKpCO9B0jcG +pyEZUWVg+ZMASz6VYcLHj6nEKtufTjhlVsLJpWPE34F/rmSuB9n6C+UZeSLP91rz +dea2pfPf/TFfcQKBwF4DSj9Qx/vxzS7t9fXbuM+QoPitMpCTOQppRpPr0nA8uj6b +J7LIwPejj3+xsenTVWpx8DanqAgvC3CRWE05iQoYEupj0mhE9Xo7oSE81nOUbFHB +H+GbkKRLzA0P/Q7/egBouWWA3Kq/K9LHb+9UBYWPiM5U/K9OFs04rCyZHxylSCud +gbNA08Wf/xZjwgri4t8KhBF75bQtFJbHtY57Vkuv9d/tA4SCl1Tq/UiAxd86KMfi +HNeXPDsLd89t1eIOgwKBwQDJkqwZXkhwkhoNuHRdzPO/1f5FyKpQxFs+x+OBulzG +zuwVKIawsLlUR4TBtF7PChOSZSH50VZaBI5kVti79kEtfNjfAzg4kleHrY8jQ/eq +HludZ3nmiPqqlbH4MH8NWczPEjee6z4ODROsAe31pz3S8YQK7KVoEuSf0+usJ894 +FtzS5wl6POAXTo2QeSNg9zTbb6JjVYcq6KCTnflDm4YEvFKI+ARqAXQHxm05wEOe +DbKC6hxxQbDaNOvXEAda8wU= +-----END PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + cb:2d:80:99:5a:69:52:5d + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server + Validity + Not Before: Aug 29 14:23:16 2018 GMT + Not After : Oct 28 14:23:16 2037 GMT + Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=fakehostname + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (3072 bit) + Modulus: + 00:c6:28:0d:73:64:38:de:34:f8:76:27:d5:87:55: + 75:cc:51:ff:21:e4:de:3f:6c:70:8c:dc:26:c8:69: + 9d:5d:4a:19:02:81:f9:85:5a:63:ad:ae:e1:22:86: + 06:df:45:86:92:59:90:30:0a:65:40:40:68:e6:82: + f5:1f:80:e2:20:b0:57:cc:f3:23:be:cc:30:72:3e: + 1c:da:ed:7b:d6:3d:97:c4:7b:b9:8d:44:57:b9:66: + 56:27:b5:b7:8c:fe:fb:96:08:ce:15:cf:00:ea:fe: + 6a:85:4c:5c:4d:46:6c:08:9e:a1:3b:87:1d:16:9d: + f0:a8:41:67:bd:37:8d:61:27:29:75:2b:43:a3:00: + 36:fe:4a:39:69:27:4f:d4:27:ad:e1:b0:1f:9d:a3: + 01:a3:69:a0:6f:27:c1:dd:b9:07:11:a8:d8:3c:b3: + a1:5c:c8:92:7c:32:3b:2c:d6:e4:4c:14:ad:a3:f5: + 0a:e6:81:b0:3a:d5:7b:88:3c:2c:73:73:72:79:ee: + 65:bc:14:dd:8a:b1:1f:03:d8:73:a1:7b:d9:ec:4c: + 69:71:a7:e3:b9:0a:e7:5f:fd:10:56:07:8e:37:c5: + ca:32:76:ec:11:5c:e1:53:74:40:14:e3:93:12:e2: + d2:86:3f:68:73:7b:3b:b1:30:54:27:2c:a0:11:5d: + 55:b8:fe:01:71:66:4e:e4:53:c4:f0:79:95:12:e8: + b0:18:3d:f6:09:9e:57:ed:79:be:b3:df:b2:c0:6e: + da:03:bf:32:94:28:11:03:7f:f9:e6:a1:ae:c2:b8: + ac:52:3c:2e:47:20:a8:4b:6e:35:3f:ea:4b:8f:82: + 54:95:11:f2:4f:25:6b:b6:6d:92:43:02:41:4b:f3: + 93:ad:de:03:ec:d7:41:17:da:53:14:cb:42:6d:de: + d7:fe:4d:a4:23:12:f1:59:86:92:8d:f0:a0:1d:62: + 3d:de:56:03:9f:cc:80:5b:43:28:e8:b4:72:63:15: + ef:49:92:ae:2a:ed:1a:08:fa:77 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:fakehostname + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 1C:70:14:B0:20:DD:08:76:A4:3B:56:59:FA:5F:34:F8:36:66:E8:56 + X509v3 Authority Key Identifier: + keyid:F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 + DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server + serial:CB:2D:80:99:5A:69:52:5B + Authority Information Access: + CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer + OCSP - URI:http://testca.pythontest.net/testca/ocsp/ + X509v3 CRL Distribution Points: + Full Name: + URI:http://testca.pythontest.net/testca/revocation.crl + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 75:14:e5:68:45:8d:ed:6c:f1:27:1e:0e:f3:35:ae:0e:60:c1: + 65:36:62:b8:07:78:e1:b9:8d:7a:50:70:af:06:c9:d4:ee:50: + ef:d2:76:b2:a2:b6:cb:dc:a6:18:b5:3d:d2:f7:eb:0e:ec:b7: + 95:cd:2e:b1:36:6f:a8:9f:b8:4d:ff:ce:8a:c4:8e:62:37:32: + 80:3e:05:4a:4d:39:87:69:09:00:e8:40:64:d2:9d:f9:1f:9f: + ab:67:1f:f9:c6:84:ba:7e:17:6c:8b:8d:08:ee:fb:8a:d7:cd: + 06:25:72:9f:4e:1a:c2:71:e1:1b:cf:a2:d7:1c:05:12:95:d6: + 49:4b:e9:95:95:89:cf:68:18:46:a3:ea:0d:9d:8e:ca:1c:28: + 55:49:6b:c0:4b:58:f5:42:b9:0a:ec:0e:6e:21:a4:ff:60:c0: + 1b:6e:40:72:d0:a5:c5:b5:db:4e:87:67:3a:31:70:cb:32:84: + 70:a9:e2:ff:e0:f2:db:cd:03:b4:85:45:d3:07:cc:0f:c7:49: + d8:c2:17:eb:73:f7:4a:c0:d9:8c:59:ef:c0:0a:ce:13:0b:84: + c9:aa:0d:11:14:b4:e5:74:aa:ec:18:de:5f:26:18:98:4a:76: + f0:7f:cd:e6:c4:b5:58:03:03:f5:10:01:5d:8f:63:88:ba:65: + d7:b4:7f:5a:1a:51:0e:ed:e5:68:fa:18:03:72:15:a1:ec:27: + 1f:ea:ac:24:46:18:6e:f1:97:db:4a:f4:d6:a1:91:a0:8c:b0: + 2f:be:87:3b:44:b0:8d:2a:89:85:5f:f2:d9:e3:2e:66:b2:88: + 98:04:2c:96:32:38:99:19:a9:83:fd:94:0c:dd:63:d4:1b:60: + 9d:43:98:35:ac:b4:23:38:de:7f:85:52:57:a0:37:df:a5:cf: + be:54:2c:3c:50:27:2b:d4:54:a9:9d:a3:d4:a5:b3:c0:ea:3d: + 0e:e2:70:6b:fb:cb:a5:56:05:ec:64:72:f0:1a:db:64:01:cb: + 5d:27:c4:a1:c4:63 +-----BEGIN CERTIFICATE----- +MIIF9zCCBF+gAwIBAgIJAMstgJlaaVJdMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx +NDIzMTZaMGIxCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj +MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xFTATBgNVBAMMDGZh +a2Vob3N0bmFtZTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAMYoDXNk +ON40+HYn1YdVdcxR/yHk3j9scIzcJshpnV1KGQKB+YVaY62u4SKGBt9FhpJZkDAK +ZUBAaOaC9R+A4iCwV8zzI77MMHI+HNrte9Y9l8R7uY1EV7lmVie1t4z++5YIzhXP +AOr+aoVMXE1GbAieoTuHHRad8KhBZ703jWEnKXUrQ6MANv5KOWknT9QnreGwH52j +AaNpoG8nwd25BxGo2DyzoVzIknwyOyzW5EwUraP1CuaBsDrVe4g8LHNzcnnuZbwU +3YqxHwPYc6F72exMaXGn47kK51/9EFYHjjfFyjJ27BFc4VN0QBTjkxLi0oY/aHN7 +O7EwVCcsoBFdVbj+AXFmTuRTxPB5lRLosBg99gmeV+15vrPfssBu2gO/MpQoEQN/ ++eahrsK4rFI8LkcgqEtuNT/qS4+CVJUR8k8la7ZtkkMCQUvzk63eA+zXQRfaUxTL +Qm3e1/5NpCMS8VmGko3woB1iPd5WA5/MgFtDKOi0cmMV70mSrirtGgj6dwIDAQAB +o4IBwzCCAb8wFwYDVR0RBBAwDoIMZmFrZWhvc3RuYW1lMA4GA1UdDwEB/wQEAwIF +oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAd +BgNVHQ4EFgQUHHAUsCDdCHakO1ZZ+l80+DZm6FYwfQYDVR0jBHYwdIAU8+yUjvKO +MMSOaMK/jmoZwMGfdmWhUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRo +b24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZl +coIJAMstgJlaaVJbMIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKGMGh0dHA6 +Ly90ZXN0Y2EucHl0aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNlcjA1Bggr +BgEFBQcwAYYpaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2Evb2Nz +cC8wQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5l +dC90ZXN0Y2EvcmV2b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBAHUU5WhF +je1s8SceDvM1rg5gwWU2YrgHeOG5jXpQcK8GydTuUO/SdrKitsvcphi1PdL36w7s +t5XNLrE2b6ifuE3/zorEjmI3MoA+BUpNOYdpCQDoQGTSnfkfn6tnH/nGhLp+F2yL +jQju+4rXzQYlcp9OGsJx4RvPotccBRKV1klL6ZWVic9oGEaj6g2djsocKFVJa8BL +WPVCuQrsDm4hpP9gwBtuQHLQpcW1206HZzoxcMsyhHCp4v/g8tvNA7SFRdMHzA/H +SdjCF+tz90rA2YxZ78AKzhMLhMmqDREUtOV0quwY3l8mGJhKdvB/zebEtVgDA/UQ +AV2PY4i6Zde0f1oaUQ7t5Wj6GANyFaHsJx/qrCRGGG7xl9tK9NahkaCMsC++hztE +sI0qiYVf8tnjLmayiJgELJYyOJkZqYP9lAzdY9QbYJ1DmDWstCM43n+FUlegN9+l +z75ULDxQJyvUVKmdo9Sls8DqPQ7icGv7y6VWBexkcvAa22QBy10nxKHEYw== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/keycertecc.pem b/Lib/test/certdata/keycertecc.pem new file mode 100644 index 00000000000..bd109921898 --- /dev/null +++ b/Lib/test/certdata/keycertecc.pem @@ -0,0 +1,103 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDRUbCeT3hMph4Y/ahL +1sy9Qfy4DYotuAP06UetzG6syv+EoQ02kX3xvazqwiJDrEyhZANiAAQef97STEPn +4Nk6C153VEx24MNkJUcmLe771u6lr3Q8Em3J/YPaA1i9Ys7KZA3WvoKBPoWaaikn +4yLQbd/6YE6AAjMuaThlR1/cqH5QnmS3DXHUjmxnLjWy/dZl0CJG1qo= +-----END PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + cb:2d:80:99:5a:69:52:5e + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server + Validity + Not Before: Aug 29 14:23:16 2018 GMT + Not After : Oct 28 14:23:16 2037 GMT + Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=localhost-ecc + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (384 bit) + pub: + 04:1e:7f:de:d2:4c:43:e7:e0:d9:3a:0b:5e:77:54: + 4c:76:e0:c3:64:25:47:26:2d:ee:fb:d6:ee:a5:af: + 74:3c:12:6d:c9:fd:83:da:03:58:bd:62:ce:ca:64: + 0d:d6:be:82:81:3e:85:9a:6a:29:27:e3:22:d0:6d: + df:fa:60:4e:80:02:33:2e:69:38:65:47:5f:dc:a8: + 7e:50:9e:64:b7:0d:71:d4:8e:6c:67:2e:35:b2:fd: + d6:65:d0:22:46:d6:aa + ASN1 OID: secp384r1 + NIST CURVE: P-384 + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:localhost-ecc + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 45:ED:32:14:6D:51:A2:3B:B0:80:55:E0:A6:9B:74:4C:A5:56:88:B1 + X509v3 Authority Key Identifier: + keyid:F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 + DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server + serial:CB:2D:80:99:5A:69:52:5B + Authority Information Access: + CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer + OCSP - URI:http://testca.pythontest.net/testca/ocsp/ + X509v3 CRL Distribution Points: + Full Name: + URI:http://testca.pythontest.net/testca/revocation.crl + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 07:e4:91:0b:d3:ed:4b:52:7f:50:68:c7:8d:80:48:9f:b7:4a: + 13:66:bf:9d:4c:2d:18:19:68:a0:da:3b:12:85:05:16:fa:8d: + 9c:58:c6:81:b3:96:ba:11:62:65:d3:76:f1:1c:ab:95:e4:d8: + 2a:e0:1f:7b:c5:20:2e:7c:8f:de:87:7a:2b:52:54:ca:d1:41: + b0:5e:20:72:df:44:00:4a:69:1a:ef:10:63:52:13:ed:49:02: + ee:dc:9d:f3:c8:ba:c4:01:81:5a:a9:1c:15:12:b6:21:de:44: + a5:fd:7e:f9:22:d1:3e:ee:22:dd:31:55:32:4e:41:68:27:c5: + 95:1b:7e:6b:18:74:f9:22:d6:b7:b9:31:72:51:a0:5a:2c:ff: + 62:76:e9:a0:55:8d:78:33:52:4a:58:b2:f4:4b:0c:43:82:2f: + a9:84:68:05:dd:11:47:70:24:fe:5c:92:fd:17:21:63:bb:fa: + 93:fa:54:54:05:72:48:ed:81:48:ab:95:fc:6d:a8:62:96:f9: + 3b:e2:71:18:05:3e:76:bb:df:95:17:7b:81:4b:1f:7f:e1:67: + 76:c4:07:cb:65:a7:f2:cf:e6:b4:fb:75:7c:ee:df:a1:f5:34: + 20:2b:48:fd:2e:49:ff:f3:a6:3b:00:49:6c:88:79:ed:9c:16: + 2a:04:72:e2:93:e4:7e:3f:2a:dd:30:47:9a:99:84:2a:b9:c4: + 40:31:a6:68:f3:20:d1:75:f1:1e:c8:18:64:5b:f8:4c:ce:9a: + 3c:57:2c:e3:63:64:29:0a:c2:b6:8e:20:01:55:9f:fe:10:ba: + 12:42:38:0a:9b:53:01:a5:b4:08:76:ec:e8:a6:fc:69:2c:f7: + 7f:5e:0f:44:07:55:e1:7c:2e:58:e5:d6:fc:6f:c2:4d:83:65: + bd:f3:32:e3:14:48:22:8d:80:18:ea:44:f8:24:79:ff:ff:c6: + 04:c2:e9:90:34:40:d6:59:3f:59:1e:4a:9a:58:60:ce:ab:f9: + 76:0e:ef:f7:05:17 +-----BEGIN CERTIFICATE----- +MIIEyzCCAzOgAwIBAgIJAMstgJlaaVJeMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx +NDIzMTZaMGMxCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj +MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xFjAUBgNVBAMMDWxv +Y2FsaG9zdC1lY2MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQef97STEPn4Nk6C153 +VEx24MNkJUcmLe771u6lr3Q8Em3J/YPaA1i9Ys7KZA3WvoKBPoWaaikn4yLQbd/6 +YE6AAjMuaThlR1/cqH5QnmS3DXHUjmxnLjWy/dZl0CJG1qqjggHEMIIBwDAYBgNV +HREEETAPgg1sb2NhbGhvc3QtZWNjMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAU +BggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQURe0y +FG1RojuwgFXgppt0TKVWiLEwfQYDVR0jBHYwdIAU8+yUjvKOMMSOaMK/jmoZwMGf +dmWhUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg +Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcoIJAMstgJlaaVJb +MIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKGMGh0dHA6Ly90ZXN0Y2EucHl0 +aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNlcjA1BggrBgEFBQcwAYYpaHR0 +cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2Evb2NzcC8wQwYDVR0fBDww +OjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2EvcmV2 +b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBAAfkkQvT7UtSf1Box42ASJ+3 +ShNmv51MLRgZaKDaOxKFBRb6jZxYxoGzlroRYmXTdvEcq5Xk2CrgH3vFIC58j96H +eitSVMrRQbBeIHLfRABKaRrvEGNSE+1JAu7cnfPIusQBgVqpHBUStiHeRKX9fvki +0T7uIt0xVTJOQWgnxZUbfmsYdPki1re5MXJRoFos/2J26aBVjXgzUkpYsvRLDEOC +L6mEaAXdEUdwJP5ckv0XIWO7+pP6VFQFckjtgUirlfxtqGKW+TvicRgFPna735UX +e4FLH3/hZ3bEB8tlp/LP5rT7dXzu36H1NCArSP0uSf/zpjsASWyIee2cFioEcuKT +5H4/Kt0wR5qZhCq5xEAxpmjzINF18R7IGGRb+EzOmjxXLONjZCkKwraOIAFVn/4Q +uhJCOAqbUwGltAh27Oim/Gks939eD0QHVeF8Lljl1vxvwk2DZb3zMuMUSCKNgBjq +RPgkef//xgTC6ZA0QNZZP1keSppYYM6r+XYO7/cFFw== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/leaf-missing-aki.ca.pem b/Lib/test/certdata/leaf-missing-aki.ca.pem new file mode 100644 index 00000000000..36b202ae02e --- /dev/null +++ b/Lib/test/certdata/leaf-missing-aki.ca.pem @@ -0,0 +1,13 @@ +# Taken from x509-limbo's `rfc5280::aki::leaf-missing-aki` testcase. +# See: https://x509-limbo.com/testcases/rfc5280/#rfc5280akileaf-missing-aki +-----BEGIN CERTIFICATE----- +MIIBkDCCATWgAwIBAgIUGjIb/aYm9u9fBh2o4GAYRJwk5XIwCgYIKoZIzj0EAwIw +GjEYMBYGA1UEAwwPeDUwOS1saW1iby1yb290MCAXDTcwMDEwMTAwMDAwMVoYDzI5 +NjkwNTAzMDAwMDAxWjAaMRgwFgYDVQQDDA94NTA5LWxpbWJvLXJvb3QwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAARUzBhjMOkO911U65Fvs4YmL1YPNj63P9Fa+g9U +KrUqiIy8WjaDXdIe8g8Zj0TalpbU1gYCs3atteMxgIp6qxwHo1cwVTAPBgNVHRMB +Af8EBTADAQH/MAsGA1UdDwQEAwICBDAWBgNVHREEDzANggtleGFtcGxlLmNvbTAd +BgNVHQ4EFgQUcv1fyqgezMGzmo+lhmUkdUuAbIowCgYIKoZIzj0EAwIDSQAwRgIh +AIOErPSRlWpnyMub9UgtPF/lSzdvnD4Q8KjLQppHx6oPAiEA373p4L/HvUbs0xg8 +6/pLyn0RT02toKKJcMV3ChohLtM= +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/leaf-missing-aki.keycert.pem b/Lib/test/certdata/leaf-missing-aki.keycert.pem new file mode 100644 index 00000000000..0fd2ab39bf2 --- /dev/null +++ b/Lib/test/certdata/leaf-missing-aki.keycert.pem @@ -0,0 +1,18 @@ +# Taken from x509-limbo's `rfc5280::aki::leaf-missing-aki` testcase. +# See: https://x509-limbo.com/testcases/rfc5280/#rfc5280akileaf-missing-aki +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIF5Re+/FP3rg+7c1odKEQPXhb9V65kXnlZIWHDG9gKrLoAoGCCqGSM49 +AwEHoUQDQgAE1WAQMdC7ims7T9lpK9uzaCuKqHb/oNMbGjh1f10pOHv3Z+oAvsqF +Sv3hGzreu69YLy01afA6sUCf1AA/95dKkg== +-----END EC PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIBjjCCATWgAwIBAgIUVlBgclml+OXlrWzZfcgYCiNm96UwCgYIKoZIzj0EAwIw +GjEYMBYGA1UEAwwPeDUwOS1saW1iby1yb290MCAXDTcwMDEwMTAwMDAwMVoYDzI5 +NjkwNTAzMDAwMDAxWjAWMRQwEgYDVQQDDAtleGFtcGxlLmNvbTBZMBMGByqGSM49 +AgEGCCqGSM49AwEHA0IABNVgEDHQu4prO0/ZaSvbs2griqh2/6DTGxo4dX9dKTh7 +92fqAL7KhUr94Rs63ruvWC8tNWnwOrFAn9QAP/eXSpKjWzBZMB0GA1UdDgQWBBS3 +yYRQQwo3syjGVQ8Yw7/XRZHbpzALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYB +BQUHAwEwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wCgYIKoZIzj0EAwIDRwAwRAIg +BVq7lw4Y5MPEyisPhowMWd4KnERupdM5qeImDO+dD7ICIE/ksd6Wz1b8rMAfllNV +yiYst9lfwTd2SkFgdDNUDFud +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/make_ssl_certs.py b/Lib/test/certdata/make_ssl_certs.py new file mode 100644 index 00000000000..ed2037c1fdf --- /dev/null +++ b/Lib/test/certdata/make_ssl_certs.py @@ -0,0 +1,315 @@ +"""Make the custom certificate and private key files used by test_ssl +and friends.""" + +import os +import pprint +import shutil +import tempfile +from subprocess import * + +startdate = "20180829142316Z" +enddate = "20371028142316Z" + +req_template = """ + [ default ] + base_url = http://testca.pythontest.net/testca + + [req] + distinguished_name = req_distinguished_name + prompt = no + + [req_distinguished_name] + C = XY + L = Castle Anthrax + O = Python Software Foundation + CN = {hostname} + + [req_x509_extensions_nosan] + + [req_x509_extensions_simple] + subjectAltName = @san + + [req_x509_extensions_full] + subjectAltName = @san + keyUsage = critical,keyEncipherment,digitalSignature + extendedKeyUsage = serverAuth,clientAuth + basicConstraints = critical,CA:false + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid:always,issuer:always + authorityInfoAccess = @issuer_ocsp_info + crlDistributionPoints = @crl_info + + [ issuer_ocsp_info ] + caIssuers;URI.0 = $base_url/pycacert.cer + OCSP;URI.0 = $base_url/ocsp/ + + [ crl_info ] + URI.0 = $base_url/revocation.crl + + [san] + DNS.1 = {hostname} + {extra_san} + + [dir_sect] + C = XY + L = Castle Anthrax + O = Python Software Foundation + CN = dirname example + + [princ_name] + realm = EXP:0, GeneralString:KERBEROS.REALM + principal_name = EXP:1, SEQUENCE:principal_seq + + [principal_seq] + name_type = EXP:0, INTEGER:1 + name_string = EXP:1, SEQUENCE:principals + + [principals] + princ1 = GeneralString:username + + [ ca ] + default_ca = CA_default + + [ CA_default ] + dir = cadir + database = $dir/index.txt + crlnumber = $dir/crl.txt + default_md = sha256 + startdate = {startdate} + default_startdate = {startdate} + enddate = {enddate} + default_enddate = {enddate} + default_days = 7000 + default_crl_days = 7000 + certificate = pycacert.pem + private_key = pycakey.pem + serial = $dir/serial + RANDFILE = $dir/.rand + policy = policy_match + + [ policy_match ] + countryName = match + stateOrProvinceName = optional + organizationName = match + organizationalUnitName = optional + commonName = supplied + emailAddress = optional + + [ policy_anything ] + countryName = optional + stateOrProvinceName = optional + localityName = optional + organizationName = optional + organizationalUnitName = optional + commonName = supplied + emailAddress = optional + + + [ v3_ca ] + + subjectKeyIdentifier=hash + authorityKeyIdentifier=keyid:always,issuer + basicConstraints = critical, CA:true + keyUsage = critical, digitalSignature, keyCertSign, cRLSign + + """ + +here = os.path.abspath(os.path.dirname(__file__)) + + +def make_cert_key(hostname, sign=False, extra_san='', + ext='req_x509_extensions_full', key='rsa:3072'): + print("creating cert for " + hostname) + tempnames = [] + for i in range(3): + with tempfile.NamedTemporaryFile(delete=False) as f: + tempnames.append(f.name) + req_file, cert_file, key_file = tempnames + try: + req = req_template.format( + hostname=hostname, + extra_san=extra_san, + startdate=startdate, + enddate=enddate + ) + with open(req_file, 'w') as f: + f.write(req) + args = ['req', '-new', '-nodes', '-days', '7000', + '-newkey', key, '-keyout', key_file, + '-extensions', ext, + '-config', req_file] + if sign: + with tempfile.NamedTemporaryFile(delete=False) as f: + tempnames.append(f.name) + reqfile = f.name + args += ['-out', reqfile ] + + else: + args += ['-x509', '-out', cert_file ] + check_call(['openssl'] + args) + + if sign: + args = [ + 'ca', + '-config', req_file, + '-extensions', ext, + '-out', cert_file, + '-outdir', 'cadir', + '-policy', 'policy_anything', + '-batch', '-infiles', reqfile + ] + check_call(['openssl'] + args) + + + with open(cert_file, 'r') as f: + cert = f.read() + with open(key_file, 'r') as f: + key = f.read() + return cert, key + finally: + for name in tempnames: + os.remove(name) + +TMP_CADIR = 'cadir' + +def unmake_ca(): + shutil.rmtree(TMP_CADIR) + +def make_ca(): + os.mkdir(TMP_CADIR) + with open(os.path.join('cadir','index.txt'),'a+') as f: + pass # empty file + with open(os.path.join('cadir','crl.txt'),'a+') as f: + f.write("00") + with open(os.path.join('cadir','index.txt.attr'),'w+') as f: + f.write('unique_subject = no') + # random start value for serial numbers + with open(os.path.join('cadir','serial'), 'w') as f: + f.write('CB2D80995A69525B\n') + + with tempfile.NamedTemporaryFile("w") as t: + req = req_template.format( + hostname='our-ca-server', + extra_san='', + startdate=startdate, + enddate=enddate + ) + t.write(req) + t.flush() + with tempfile.NamedTemporaryFile() as f: + args = ['req', '-config', t.name, '-new', + '-nodes', + '-newkey', 'rsa:3072', + '-keyout', 'pycakey.pem', + '-out', f.name, + '-subj', '/C=XY/L=Castle Anthrax/O=Python Software Foundation CA/CN=our-ca-server'] + check_call(['openssl'] + args) + args = ['ca', '-config', t.name, + '-out', 'pycacert.pem', '-batch', '-outdir', TMP_CADIR, + '-keyfile', 'pycakey.pem', + '-selfsign', '-extensions', 'v3_ca', '-infiles', f.name ] + check_call(['openssl'] + args) + args = ['ca', '-config', t.name, '-gencrl', '-out', 'revocation.crl'] + check_call(['openssl'] + args) + + # capath hashes depend on subject! + check_call([ + 'openssl', 'x509', '-in', 'pycacert.pem', '-out', 'capath/ceff1710.0' + ]) + shutil.copy('capath/ceff1710.0', 'capath/b1930218.0') + + +def print_cert(path): + import _ssl + pprint.pprint(_ssl._test_decode_cert(path)) + + +if __name__ == '__main__': + os.chdir(here) + cert, key = make_cert_key('localhost', ext='req_x509_extensions_simple') + with open('ssl_cert.pem', 'w') as f: + f.write(cert) + with open('ssl_key.pem', 'w') as f: + f.write(key) + print("password protecting ssl_key.pem in ssl_key.passwd.pem") + check_call(['openssl','pkey','-in','ssl_key.pem','-out','ssl_key.passwd.pem','-aes256','-passout','pass:somepass']) + check_call(['openssl','pkey','-in','ssl_key.pem','-out','keycert.passwd.pem','-aes256','-passout','pass:somepass']) + + with open('keycert.pem', 'w') as f: + f.write(key) + f.write(cert) + + with open('keycert.passwd.pem', 'a+') as f: + f.write(cert) + + # For certificate matching tests + make_ca() + cert, key = make_cert_key('fakehostname', ext='req_x509_extensions_simple') + with open('keycert2.pem', 'w') as f: + f.write(key) + f.write(cert) + + cert, key = make_cert_key('localhost', sign=True) + with open('keycert3.pem', 'w') as f: + f.write(key) + f.write(cert) + + check_call(['openssl', 'x509', '-outform', 'pem', '-in', 'keycert3.pem', '-out', 'cert3.pem']) + + cert, key = make_cert_key('fakehostname', sign=True) + with open('keycert4.pem', 'w') as f: + f.write(key) + f.write(cert) + + cert, key = make_cert_key( + 'localhost-ecc', sign=True, key='param:secp384r1.pem' + ) + with open('keycertecc.pem', 'w') as f: + f.write(key) + f.write(cert) + + extra_san = [ + 'otherName.1 = 1.2.3.4;UTF8:some other identifier', + 'otherName.2 = 1.3.6.1.5.2.2;SEQUENCE:princ_name', + 'email.1 = user@example.org', + 'DNS.2 = www.example.org', + # GEN_X400 + 'dirName.1 = dir_sect', + # GEN_EDIPARTY + 'URI.1 = https://www.python.org/', + 'IP.1 = 127.0.0.1', + 'IP.2 = ::1', + 'RID.1 = 1.2.3.4.5', + ] + + cert, key = make_cert_key('allsans', sign=True, extra_san='\n'.join(extra_san)) + with open('allsans.pem', 'w') as f: + f.write(key) + f.write(cert) + + extra_san = [ + # könig (king) + 'DNS.2 = xn--knig-5qa.idn.pythontest.net', + # königsgäßchen (king's alleyway) + 'DNS.3 = xn--knigsgsschen-lcb0w.idna2003.pythontest.net', + 'DNS.4 = xn--knigsgchen-b4a3dun.idna2008.pythontest.net', + # βόλοσ (marble) + 'DNS.5 = xn--nxasmq6b.idna2003.pythontest.net', + 'DNS.6 = xn--nxasmm1c.idna2008.pythontest.net', + ] + + # IDN SANS, signed + cert, key = make_cert_key('idnsans', sign=True, extra_san='\n'.join(extra_san)) + with open('idnsans.pem', 'w') as f: + f.write(key) + f.write(cert) + + cert, key = make_cert_key('nosan', sign=True, ext='req_x509_extensions_nosan') + with open('nosan.pem', 'w') as f: + f.write(key) + f.write(cert) + + unmake_ca() + print("update Lib/test/test_ssl.py and Lib/test/test_asyncio/utils.py") + print_cert('keycert.pem') + print_cert('keycert3.pem') diff --git a/Lib/test/certdata/nokia.pem b/Lib/test/certdata/nokia.pem new file mode 100644 index 00000000000..0d044df4367 --- /dev/null +++ b/Lib/test/certdata/nokia.pem @@ -0,0 +1,31 @@ +# Certificate for projects.developer.nokia.com:443 (see issue 13034) +-----BEGIN CERTIFICATE----- +MIIFLDCCBBSgAwIBAgIQLubqdkCgdc7lAF9NfHlUmjANBgkqhkiG9w0BAQUFADCB +vDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug +YXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDE2MDQGA1UEAxMt +VmVyaVNpZ24gQ2xhc3MgMyBJbnRlcm5hdGlvbmFsIFNlcnZlciBDQSAtIEczMB4X +DTExMDkyMTAwMDAwMFoXDTEyMDkyMDIzNTk1OVowcTELMAkGA1UEBhMCRkkxDjAM +BgNVBAgTBUVzcG9vMQ4wDAYDVQQHFAVFc3BvbzEOMAwGA1UEChQFTm9raWExCzAJ +BgNVBAsUAkJJMSUwIwYDVQQDFBxwcm9qZWN0cy5kZXZlbG9wZXIubm9raWEuY29t +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCr92w1bpHYSYxUEx8N/8Iddda2 +lYi+aXNtQfV/l2Fw9Ykv3Ipw4nLeGTj18FFlAZgMdPRlgrzF/NNXGw/9l3/qKdow +CypkQf8lLaxb9Ze1E/KKmkRJa48QTOqvo6GqKuTI6HCeGlG1RxDb8YSKcQWLiytn +yj3Wp4MgRQO266xmMQIDAQABo4IB9jCCAfIwQQYDVR0RBDowOIIccHJvamVjdHMu +ZGV2ZWxvcGVyLm5va2lhLmNvbYIYcHJvamVjdHMuZm9ydW0ubm9raWEuY29tMAkG +A1UdEwQCMAAwCwYDVR0PBAQDAgWgMEEGA1UdHwQ6MDgwNqA0oDKGMGh0dHA6Ly9T +VlJJbnRsLUczLWNybC52ZXJpc2lnbi5jb20vU1ZSSW50bEczLmNybDBEBgNVHSAE +PTA7MDkGC2CGSAGG+EUBBxcDMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LnZl +cmlzaWduLmNvbS9ycGEwKAYDVR0lBCEwHwYJYIZIAYb4QgQBBggrBgEFBQcDAQYI +KwYBBQUHAwIwcgYIKwYBBQUHAQEEZjBkMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz +cC52ZXJpc2lnbi5jb20wPAYIKwYBBQUHMAKGMGh0dHA6Ly9TVlJJbnRsLUczLWFp +YS52ZXJpc2lnbi5jb20vU1ZSSW50bEczLmNlcjBuBggrBgEFBQcBDARiMGChXqBc +MFowWDBWFglpbWFnZS9naWYwITAfMAcGBSsOAwIaBBRLa7kolgYMu9BSOJsprEsH +iyEFGDAmFiRodHRwOi8vbG9nby52ZXJpc2lnbi5jb20vdnNsb2dvMS5naWYwDQYJ +KoZIhvcNAQEFBQADggEBACQuPyIJqXwUyFRWw9x5yDXgMW4zYFopQYOw/ItRY522 +O5BsySTh56BWS6mQB07XVfxmYUGAvRQDA5QHpmY8jIlNwSmN3s8RKo+fAtiNRlcL +x/mWSfuMs3D/S6ev3D6+dpEMZtjrhOdctsarMKp8n/hPbwhAbg5hVjpkW5n8vz2y +0KxvvkA1AxpLwpVv7OlK17ttzIHw8bp9HTlHBU5s8bKz4a565V/a5HI0CSEv/+0y +ko4/ghTnZc1CkmUngKKeFMSah/mT/xAh8XnE2l1AazFa8UKuYki1e+ArHaGZc4ix +UYOtiRphwfuYQhRZ7qX9q2MMkCMI65XNK/SaFrAbbG0= +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/nosan.pem b/Lib/test/certdata/nosan.pem new file mode 100644 index 00000000000..c6ff8ea31bf --- /dev/null +++ b/Lib/test/certdata/nosan.pem @@ -0,0 +1,131 @@ +-----BEGIN PRIVATE KEY----- +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 +hctFUL/gjIGg9PVqTWzVVKaI +-----END PRIVATE KEY----- +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + cb:2d:80:99:5a:69:52:61 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server + Validity + Not Before: Aug 29 14:23:16 2018 GMT + Not After : Oct 28 14:23:16 2037 GMT + Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=nosan + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (3072 bit) + Modulus: + 00:bd:f7:11:18:3d:3c:05:37:f8:e2:8b:49:66:d7: + 57:24:10:68:5c:c5:c8:92:b0:88:13:82:2a:2b:e7: + 80:8b:81:0c:bb:25:79:65:4d:89:80:58:54:d3:83: + 6c:cd:06:f0:cc:7b:11:88:d2:1f:b1:b5:9e:2a:d5: + 17:a7:82:18:2d:df:73:77:63:a0:93:de:ff:cb:ec: + 99:04:d5:a6:d0:6c:5b:de:29:6f:f2:76:5c:47:d3: + e8:cb:96:c1:0d:49:68:f2:4b:00:bc:d3:e7:fd:2f: + 76:d7:4d:c0:fc:4f:81:6e:f5:4b:72:d9:18:35:6b: + f1:32:7a:49:e0:e5:19:9f:92:62:cd:83:17:e7:5f: + 94:79:9d:68:61:d0:08:9a:ee:1d:c0:c7:6c:c2:00: + b7:f1:a9:92:26:7f:1c:1a:bc:48:07:b1:6f:f2:66: + 2f:93:f9:ea:09:c1:ed:22:ed:ae:8b:69:4a:0b:aa: + c3:d5:f5:98:5a:83:38:8d:0d:a6:a9:a4:f1:c7:e3: + 63:56:df:d5:a5:46:1c:75:52:3b:62:46:6d:89:8a: + 19:f3:c5:c7:ec:fb:dd:33:2d:74:a9:e6:9c:68:fd: + c1:3d:21:ae:1d:f2:27:aa:dd:1c:5d:fd:78:23:94: + 5e:85:5d:71:49:8f:07:cb:df:a0:f0:7a:1c:bc:9b: + 73:ca:28:0b:8e:f9:fc:8c:80:f3:cf:31:06:fb:a1: + a4:72:fc:c0:46:d0:d6:b2:46:b7:93:c3:22:88:2d: + 2d:b3:69:f2:16:fe:22:a9:80:80:9b:c6:b5:89:af: + f1:f5:6f:74:ba:3c:12:98:50:4d:ae:42:cd:3f:64: + f9:d1:8e:38:74:55:e3:67:b9:ee:c9:71:1a:0d:6d: + ed:bb:23:67:79:e8:a5:01:b1:51:0f:76:89:23:53: + 9f:6f:35:88:ba:49:ef:95:89:95:ed:51:b3:35:bb: + f2:34:92:f0:cc:a6:b0:22:a1:bc:2d:24:56:20:70: + 4f:3a:37:59:45:26:31:d2:c0:33 + Exponent: 65537 (0x10001) + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 7e:dd:64:64:92:6c:b9:41:ce:f3:e3:f8:e6:9f:c8:5b:32:39: + 8c:03:5b:5e:7e:b3:23:ca:6c:d1:99:2f:53:af:9d:3c:84:cd: + c6:ce:0a:ee:94:de:ff:a7:06:81:7e:e2:38:a5:05:39:58:22: + dc:13:83:53:e7:f8:16:cb:93:dc:cf:4b:e6:1b:9f:9e:71:ef: + ee:ba:ea:b6:68:5c:32:22:7e:54:4f:46:a6:0b:11:8f:ef:05: + 6e:d3:0b:d0:a8:be:95:23:a2:e4:e7:a8:a2:a4:7d:98:52:86: + a4:15:fb:74:7a:9a:89:23:43:20:26:3a:56:9e:a3:6e:54:02: + 76:4e:25:9c:a1:8c:03:99:e5:eb:a6:61:b4:9c:2a:b1:ed:eb: + 94:f9:14:aa:a4:c3:f0:f7:7a:03:a3:b1:f8:c0:83:79:ab:8a: + 93:7f:0a:95:08:50:ff:55:19:ac:28:a2:c8:9f:a6:77:72:a3: + da:37:a9:ff:f3:57:70:c8:65:d9:55:14:84:b4:b3:78:86:82: + da:84:2c:48:19:51:ec:9d:20:b1:4d:18:fb:82:9f:7b:a7:80: + 22:69:25:83:4d:bf:ac:31:64:f5:39:11:f1:ed:53:fb:67:ab: + 91:86:c5:4d:87:e8:6b:fe:9a:84:fe:6a:92:6b:62:c1:ae:d2: + f0:cb:06:6e:f3:50:f4:8d:6d:fa:7d:6a:1c:64:c3:98:91:da: + c9:8c:a9:79:e5:48:4c:a2:de:42:28:e8:0e:9f:52:6a:a4:e0: + c7:ac:11:9c:ba:5d:d6:84:93:56:28:f1:6d:83:aa:62:b2:b7: + 56:c6:64:d9:96:4e:97:ab:4e:8f:ba:f6:ab:b9:17:52:98:32: + 7f:b5:12:fa:39:d7:34:2a:f3:ed:40:90:6f:66:7b:b6:c1:9d: + b9:53:d0:e3:e9:69:8c:cf:7a:fd:08:0a:62:47:d4:ce:72:f7: + 6f:80:b4:1d:18:7a:ba:a2:a9:45:49:ef:9c:0b:99:89:03:ab: + 5f:7a:9d:c5:77:b7 +-----BEGIN CERTIFICATE----- +MIIEJDCCAowCCQDLLYCZWmlSYTANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQGEwJY +WTEmMCQGA1UECgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNV +BAMMDW91ci1jYS1zZXJ2ZXIwHhcNMTgwODI5MTQyMzE2WhcNMzcxMDI4MTQyMzE2 +WjBbMQswCQYDVQQGEwJYWTEXMBUGA1UEBwwOQ2FzdGxlIEFudGhyYXgxIzAhBgNV +BAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQ4wDAYDVQQDDAVub3NhbjCC +AaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAL33ERg9PAU3+OKLSWbXVyQQ +aFzFyJKwiBOCKivngIuBDLsleWVNiYBYVNODbM0G8Mx7EYjSH7G1nirVF6eCGC3f +c3djoJPe/8vsmQTVptBsW94pb/J2XEfT6MuWwQ1JaPJLALzT5/0vdtdNwPxPgW71 +S3LZGDVr8TJ6SeDlGZ+SYs2DF+dflHmdaGHQCJruHcDHbMIAt/GpkiZ/HBq8SAex +b/JmL5P56gnB7SLtrotpSguqw9X1mFqDOI0Npqmk8cfjY1bf1aVGHHVSO2JGbYmK +GfPFx+z73TMtdKnmnGj9wT0hrh3yJ6rdHF39eCOUXoVdcUmPB8vfoPB6HLybc8oo +C475/IyA888xBvuhpHL8wEbQ1rJGt5PDIogtLbNp8hb+IqmAgJvGtYmv8fVvdLo8 +EphQTa5CzT9k+dGOOHRV42e57slxGg1t7bsjZ3nopQGxUQ92iSNTn281iLpJ75WJ +le1RszW78jSS8MymsCKhvC0kViBwTzo3WUUmMdLAMwIDAQABMA0GCSqGSIb3DQEB +CwUAA4IBgQB+3WRkkmy5Qc7z4/jmn8hbMjmMA1tefrMjymzRmS9Tr508hM3Gzgru +lN7/pwaBfuI4pQU5WCLcE4NT5/gWy5Pcz0vmG5+ece/uuuq2aFwyIn5UT0amCxGP +7wVu0wvQqL6VI6Lk56iipH2YUoakFft0epqJI0MgJjpWnqNuVAJ2TiWcoYwDmeXr +pmG0nCqx7euU+RSqpMPw93oDo7H4wIN5q4qTfwqVCFD/VRmsKKLIn6Z3cqPaN6n/ +81dwyGXZVRSEtLN4hoLahCxIGVHsnSCxTRj7gp97p4AiaSWDTb+sMWT1ORHx7VP7 +Z6uRhsVNh+hr/pqE/mqSa2LBrtLwywZu81D0jW36fWocZMOYkdrJjKl55UhMot5C +KOgOn1JqpODHrBGcul3WhJNWKPFtg6pisrdWxmTZlk6Xq06PuvaruRdSmDJ/tRL6 +Odc0KvPtQJBvZnu2wZ25U9Dj6WmMz3r9CApiR9TOcvdvgLQdGHq6oqlFSe+cC5mJ +A6tfep3Fd7c= +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/nullbytecert.pem b/Lib/test/certdata/nullbytecert.pem new file mode 100644 index 00000000000..447186c9508 --- /dev/null +++ b/Lib/test/certdata/nullbytecert.pem @@ -0,0 +1,90 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Oregon, L=Beaverton, O=Python Software Foundation, OU=Python Core Development, CN=null.python.org\x00example.org/emailAddress=python-dev@python.org + Validity + Not Before: Aug 7 13:11:52 2013 GMT + Not After : Aug 7 13:12:52 2013 GMT + Subject: C=US, ST=Oregon, L=Beaverton, O=Python Software Foundation, OU=Python Core Development, CN=null.python.org\x00example.org/emailAddress=python-dev@python.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b5:ea:ed:c9:fb:46:7d:6f:3b:76:80:dd:3a:f3: + 03:94:0b:a7:a6:db:ec:1d:df:ff:23:74:08:9d:97: + 16:3f:a3:a4:7b:3e:1b:0e:96:59:25:03:a7:26:e2: + 88:a9:cf:79:cd:f7:04:56:b0:ab:79:32:6e:59:c1: + 32:30:54:eb:58:a8:cb:91:f0:42:a5:64:27:cb:d4: + 56:31:88:52:ad:cf:bd:7f:f0:06:64:1f:cc:27:b8: + a3:8b:8c:f3:d8:29:1f:25:0b:f5:46:06:1b:ca:02: + 45:ad:7b:76:0a:9c:bf:bb:b9:ae:0d:16:ab:60:75: + ae:06:3e:9c:7c:31:dc:92:2f:29:1a:e0:4b:0c:91: + 90:6c:e9:37:c5:90:d7:2a:d7:97:15:a3:80:8f:5d: + 7b:49:8f:54:30:d4:97:2c:1c:5b:37:b5:ab:69:30: + 68:43:d3:33:78:4b:02:60:f5:3c:44:80:a1:8f:e7: + f0:0f:d1:5e:87:9e:46:cf:62:fc:f9:bf:0c:65:12: + f1:93:c8:35:79:3f:c8:ec:ec:47:f5:ef:be:44:d5: + ae:82:1e:2d:9a:9f:98:5a:67:65:e1:74:70:7c:cb: + d3:c2:ce:0e:45:49:27:dc:e3:2d:d4:fb:48:0e:2f: + 9e:77:b8:14:46:c0:c4:36:ca:02:ae:6a:91:8c:da: + 2f:85 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 88:5A:55:C0:52:FF:61:CD:52:A3:35:0F:EA:5A:9C:24:38:22:F7:5C + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment + X509v3 Subject Alternative Name: + ************************************************************* + WARNING: The values for DNS, email and URI are WRONG. OpenSSL + doesn't print the text after a NULL byte. + ************************************************************* + DNS:altnull.python.org, email:null@python.org, URI:http://null.python.org, IP Address:192.0.2.1, IP Address:2001:DB8:0:0:0:0:0:1 + Signature Algorithm: sha1WithRSAEncryption + ac:4f:45:ef:7d:49:a8:21:70:8e:88:59:3e:d4:36:42:70:f5: + a3:bd:8b:d7:a8:d0:58:f6:31:4a:b1:a4:a6:dd:6f:d9:e8:44: + 3c:b6:0a:71:d6:7f:b1:08:61:9d:60:ce:75:cf:77:0c:d2:37: + 86:02:8d:5e:5d:f9:0f:71:b4:16:a8:c1:3d:23:1c:f1:11:b3: + 56:6e:ca:d0:8d:34:94:e6:87:2a:99:f2:ae:ae:cc:c2:e8:86: + de:08:a8:7f:c5:05:fa:6f:81:a7:82:e6:d0:53:9d:34:f4:ac: + 3e:40:fe:89:57:7a:29:a4:91:7e:0b:c6:51:31:e5:10:2f:a4: + 60:76:cd:95:51:1a:be:8b:a1:b0:fd:ad:52:bd:d7:1b:87:60: + d2:31:c7:17:c4:18:4f:2d:08:25:a3:a7:4f:b7:92:ca:e2:f5: + 25:f1:54:75:81:9d:b3:3d:61:a2:f7:da:ed:e1:c6:6f:2c:60: + 1f:d8:6f:c5:92:05:ab:c9:09:62:49:a9:14:ad:55:11:cc:d6: + 4a:19:94:99:97:37:1d:81:5f:8b:cf:a3:a8:96:44:51:08:3d: + 0b:05:65:12:eb:b6:70:80:88:48:72:4f:c6:c2:da:cf:cd:8e: + 5b:ba:97:2f:60:b4:96:56:49:5e:3a:43:76:63:04:be:2a:f6: + c1:ca:a9:94 +-----BEGIN CERTIFICATE----- +MIIE2DCCA8CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBxTELMAkGA1UEBhMCVVMx +DzANBgNVBAgMBk9yZWdvbjESMBAGA1UEBwwJQmVhdmVydG9uMSMwIQYDVQQKDBpQ +eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEgMB4GA1UECwwXUHl0aG9uIENvcmUg +RGV2ZWxvcG1lbnQxJDAiBgNVBAMMG251bGwucHl0aG9uLm9yZwBleGFtcGxlLm9y +ZzEkMCIGCSqGSIb3DQEJARYVcHl0aG9uLWRldkBweXRob24ub3JnMB4XDTEzMDgw +NzEzMTE1MloXDTEzMDgwNzEzMTI1MlowgcUxCzAJBgNVBAYTAlVTMQ8wDQYDVQQI +DAZPcmVnb24xEjAQBgNVBAcMCUJlYXZlcnRvbjEjMCEGA1UECgwaUHl0aG9uIFNv +ZnR3YXJlIEZvdW5kYXRpb24xIDAeBgNVBAsMF1B5dGhvbiBDb3JlIERldmVsb3Bt +ZW50MSQwIgYDVQQDDBtudWxsLnB5dGhvbi5vcmcAZXhhbXBsZS5vcmcxJDAiBgkq +hkiG9w0BCQEWFXB5dGhvbi1kZXZAcHl0aG9uLm9yZzCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBALXq7cn7Rn1vO3aA3TrzA5QLp6bb7B3f/yN0CJ2XFj+j +pHs+Gw6WWSUDpybiiKnPec33BFawq3kyblnBMjBU61ioy5HwQqVkJ8vUVjGIUq3P +vX/wBmQfzCe4o4uM89gpHyUL9UYGG8oCRa17dgqcv7u5rg0Wq2B1rgY+nHwx3JIv +KRrgSwyRkGzpN8WQ1yrXlxWjgI9de0mPVDDUlywcWze1q2kwaEPTM3hLAmD1PESA +oY/n8A/RXoeeRs9i/Pm/DGUS8ZPINXk/yOzsR/XvvkTVroIeLZqfmFpnZeF0cHzL +08LODkVJJ9zjLdT7SA4vnne4FEbAxDbKAq5qkYzaL4UCAwEAAaOB0DCBzTAMBgNV +HRMBAf8EAjAAMB0GA1UdDgQWBBSIWlXAUv9hzVKjNQ/qWpwkOCL3XDALBgNVHQ8E +BAMCBeAwgZAGA1UdEQSBiDCBhYIeYWx0bnVsbC5weXRob24ub3JnAGV4YW1wbGUu +Y29tgSBudWxsQHB5dGhvbi5vcmcAdXNlckBleGFtcGxlLm9yZ4YpaHR0cDovL251 +bGwucHl0aG9uLm9yZwBodHRwOi8vZXhhbXBsZS5vcmeHBMAAAgGHECABDbgAAAAA +AAAAAAAAAAEwDQYJKoZIhvcNAQEFBQADggEBAKxPRe99SaghcI6IWT7UNkJw9aO9 +i9eo0Fj2MUqxpKbdb9noRDy2CnHWf7EIYZ1gznXPdwzSN4YCjV5d+Q9xtBaowT0j +HPERs1ZuytCNNJTmhyqZ8q6uzMLoht4IqH/FBfpvgaeC5tBTnTT0rD5A/olXeimk +kX4LxlEx5RAvpGB2zZVRGr6LobD9rVK91xuHYNIxxxfEGE8tCCWjp0+3ksri9SXx +VHWBnbM9YaL32u3hxm8sYB/Yb8WSBavJCWJJqRStVRHM1koZlJmXNx2BX4vPo6iW +RFEIPQsFZRLrtnCAiEhyT8bC2s/Njlu6ly9gtJZWSV46Q3ZjBL4q9sHKqZQ= +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/nullcert.pem b/Lib/test/certdata/nullcert.pem new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Lib/test/certdata/pycacert.pem b/Lib/test/certdata/pycacert.pem new file mode 100644 index 00000000000..0a48bf7d235 --- /dev/null +++ b/Lib/test/certdata/pycacert.pem @@ -0,0 +1,102 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + cb:2d:80:99:5a:69:52:5b + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server + Validity + Not Before: Aug 29 14:23:16 2018 GMT + Not After : Oct 28 14:23:16 2037 GMT + Subject: C=XY, O=Python Software Foundation CA, CN=our-ca-server + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (3072 bit) + Modulus: + 00:d0:a0:9b:b1:b9:3b:79:ee:31:2f:b8:51:1c:01: + 75:ed:09:59:4c:ac:c8:bf:6a:0a:f8:7a:08:f0:25: + e3:25:7b:6a:f8:c4:d8:aa:a0:60:07:25:b0:cf:73: + 71:05:52:68:bf:06:93:ae:10:61:96:bc:b3:69:81: + 1a:7d:19:fc:20:86:8f:9a:68:1b:ed:cd:e2:6c:61: + 67:c7:4e:55:ea:43:06:21:1b:e9:b9:be:84:5f:c2: + da:eb:89:88:e0:42:a6:45:49:2a:d0:b9:6b:8c:93: + c9:44:3b:ca:fc:3c:94:7f:dd:70:c5:ad:d8:4b:3b: + dc:1e:f8:55:4b:b5:27:86:f8:df:b0:83:cf:f7:16: + 37:bf:13:17:34:d4:c9:55:ed:6b:16:c9:7f:ad:20: + 4e:f0:1e:d9:1b:bf:8d:ba:cd:ca:96:ef:1e:69:cb: + 51:66:61:48:0d:e8:79:a3:59:61:d4:10:8d:e0:0d: + 3b:0b:85:b6:d5:85:12:dd:a5:07:c5:4b:fa:23:fa: + 54:39:f7:97:0b:c9:44:47:1e:56:77:3c:3c:13:01: + 0b:6d:77:d6:14:ba:44:f4:53:35:56:d9:3d:b9:3e: + 35:5f:db:33:9a:4f:5a:b9:38:44:c9:32:49:fe:66: + f6:1f:1a:97:aa:ca:4c:4a:f6:b8:5e:40:7f:fb:0b: + 1d:f8:5b:a1:dc:f8:c0:2e:d0:6d:49:f5:d2:46:d4: + 90:57:fe:92:81:34:ae:2d:38:bb:a8:17:0c:e1:e5: + 3f:e2:f7:26:05:54:50:f5:64:b3:1c:6e:44:ff:6f: + a9:b4:03:96:e9:0e:c2:88:d8:72:52:90:99:c6:41: + 0f:46:90:59:b8:3e:6f:d2:e2:9e:1d:36:82:95:d3: + 58:8a:12:f3:e2:d8:0d:20:51:23:f0:90:2d:9a:3e: + 7d:26:86:b2:a7:d7:f9:28:60:03:e3:77:c7:88:04: + c9:fe:89:77:70:10:4f:c3:a0:8a:3b:f4:ab:42:7b: + e3:14:92:d8:ae:16:d6:a1:de:7d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 + X509v3 Authority Key Identifier: + F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 8b:00:54:72:b3:8d:eb:f3:af:34:9f:d6:60:ea:de:84:3f:8c: + 04:8f:19:a6:be:02:67:c4:63:c5:74:e3:47:37:59:83:94:06: + f1:45:19:e8:07:2f:d6:4e:4b:4f:a8:3d:c7:07:07:27:92:f4: + 7e:73:4f:8b:32:19:94:46:7a:25:c4:d9:c4:27:b0:11:63:3a: + 60:8b:85:e1:73:4f:34:3b:6b:a4:34:8c:49:8e:cd:cf:4f:b2: + 65:27:41:19:b0:fc:80:31:78:f2:73:6a:9b:7d:71:34:50:fc: + 78:a8:da:05:b4:9c:5b:3a:99:7a:6b:5d:ef:3b:d3:e9:3b:33: + 01:12:65:cf:5e:07:d8:19:af:d5:53:ea:f0:10:ac:c4:b6:26: + 3c:34:2e:74:ee:64:dd:1d:36:75:89:44:00:b0:0d:fd:2f:b3: + 01:cc:1a:8b:02:cd:6c:e8:80:82:ca:bf:82:d7:00:9d:d8:36: + 15:d2:07:37:fc:6c:73:1d:da:a8:1c:e8:20:8e:32:7a:fe:6d: + 27:16:e4:58:6c:eb:3e:f0:fe:24:52:29:71:b8:96:7b:53:4b: + 45:20:55:40:5e:86:1b:ec:c9:46:91:92:ee:ac:93:65:91:2e: + 94:b6:b6:ac:e8:a3:34:89:a4:1a:12:0d:4d:44:a5:52:ed:8b: + 91:ee:2f:a6:af:a4:95:25:f9:ce:c7:5b:a7:00:d3:93:ca:b4: + 3c:5d:4d:f7:b1:3c:cc:6d:b4:45:be:82:ed:18:90:c8:86:d1: + 75:51:50:04:4c:e8:4f:d2:d6:50:aa:75:e7:5e:ff:a1:7b:27: + 19:1c:6b:49:2c:6c:4d:6b:63:cc:3b:73:00:f3:99:26:d0:82: + 87:d3:a9:36:9c:b4:3d:b9:48:68:a8:92:f0:27:8e:0c:cd:15: + 76:42:84:80:c9:3f:a3:7e:b4:dd:d7:f8:ac:76:ba:60:97:3c: + 5a:1f:4e:de:71:ee:09:39:10:dd:31:d5:68:57:5d:1a:e5:42: + ba:0e:68:e6:45:d3 +-----BEGIN CERTIFICATE----- +MIIEgDCCAuigAwIBAgIJAMstgJlaaVJbMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx +NDIzMTZaME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg +Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcjCCAaIwDQYJKoZI +hvcNAQEBBQADggGPADCCAYoCggGBANCgm7G5O3nuMS+4URwBde0JWUysyL9qCvh6 +CPAl4yV7avjE2KqgYAclsM9zcQVSaL8Gk64QYZa8s2mBGn0Z/CCGj5poG+3N4mxh +Z8dOVepDBiEb6bm+hF/C2uuJiOBCpkVJKtC5a4yTyUQ7yvw8lH/dcMWt2Es73B74 +VUu1J4b437CDz/cWN78TFzTUyVXtaxbJf60gTvAe2Ru/jbrNypbvHmnLUWZhSA3o +eaNZYdQQjeANOwuFttWFEt2lB8VL+iP6VDn3lwvJREceVnc8PBMBC2131hS6RPRT +NVbZPbk+NV/bM5pPWrk4RMkySf5m9h8al6rKTEr2uF5Af/sLHfhbodz4wC7QbUn1 +0kbUkFf+koE0ri04u6gXDOHlP+L3JgVUUPVksxxuRP9vqbQDlukOwojYclKQmcZB +D0aQWbg+b9Linh02gpXTWIoS8+LYDSBRI/CQLZo+fSaGsqfX+ShgA+N3x4gEyf6J +d3AQT8Ogijv0q0J74xSS2K4W1qHefQIDAQABo2MwYTAdBgNVHQ4EFgQU8+yUjvKO +MMSOaMK/jmoZwMGfdmUwHwYDVR0jBBgwFoAU8+yUjvKOMMSOaMK/jmoZwMGfdmUw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggGBAIsAVHKzjevzrzSf1mDq3oQ/jASPGaa+AmfEY8V040c3WYOUBvFFGegHL9ZO +S0+oPccHByeS9H5zT4syGZRGeiXE2cQnsBFjOmCLheFzTzQ7a6Q0jEmOzc9PsmUn +QRmw/IAxePJzapt9cTRQ/Hio2gW0nFs6mXprXe870+k7MwESZc9eB9gZr9VT6vAQ +rMS2Jjw0LnTuZN0dNnWJRACwDf0vswHMGosCzWzogILKv4LXAJ3YNhXSBzf8bHMd +2qgc6CCOMnr+bScW5Fhs6z7w/iRSKXG4lntTS0UgVUBehhvsyUaRku6sk2WRLpS2 +tqzoozSJpBoSDU1EpVLti5HuL6avpJUl+c7HW6cA05PKtDxdTfexPMxttEW+gu0Y +kMiG0XVRUARM6E/S1lCqdede/6F7Jxkca0ksbE1rY8w7cwDzmSbQgofTqTactD25 +SGiokvAnjgzNFXZChIDJP6N+tN3X+Kx2umCXPFofTt5x7gk5EN0x1WhXXRrlQroO +aOZF0w== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/pycakey.pem b/Lib/test/certdata/pycakey.pem new file mode 100644 index 00000000000..a6bf7356f4f --- /dev/null +++ b/Lib/test/certdata/pycakey.pem @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +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 +E2bb0CdekSHf05HPiF9QpK1z +-----END PRIVATE KEY----- diff --git a/Lib/test/certdata/revocation.crl b/Lib/test/certdata/revocation.crl new file mode 100644 index 00000000000..431a831c4f1 --- /dev/null +++ b/Lib/test/certdata/revocation.crl @@ -0,0 +1,14 @@ +-----BEGIN X509 CRL----- +MIICJjCBjwIBATANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQGEwJYWTEmMCQGA1UE +CgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNVBAMMDW91ci1j +YS1zZXJ2ZXIXDTIzMTEyNTA0MjEzNloXDTQzMDEyNDA0MjEzNlqgDjAMMAoGA1Ud +FAQDAgEAMA0GCSqGSIb3DQEBCwUAA4IBgQDMZ4XLQlzUrqBbszEq9I/nXK3jN8/p +VZ2aScU2le0ySJqIthe0yXEYuoFu+I4ZULyNkCA79baStIl8/Lt48DOHfBVv8SVx +ZqF7/fdUZBCLJV1kuhuSSknbtNmja5NI4/lcRRXrodRWDMcOmqlKbAC6RMQz/gMG +vpewGPX1oj5AQnqqd9spKtHbeqeDiyyWYr9ZZFO/433lP7GdsoriTPggYJJMWJvs +819buE0iGwWf+rTLB51VyGluhcz2pqimej6Ra2cdnYh5IztZlDFR99HywzWhVz/A +2fwUA91GR7zATerweXVKNd59mcgF4PZWiXmQMwcE0qQOMqMmAqYPLim1mretZsAs +t1X+nDM0Ak3sKumIjteQF7I6VpSsG4NCtq23G8KpNHnBZVOt0U065lQEvx0ZmB94 +1z7SzjfSZMVXYxBjSXljwuoc1keGpNT5xCmHyrOIxaHsmizzwNESW4dGVLu7/JfK +w40uGbwH09w4Cfbwuo7w6sRWDWPnlW2mkoc= +-----END X509 CRL----- diff --git a/Lib/test/certdata/secp384r1.pem b/Lib/test/certdata/secp384r1.pem new file mode 100644 index 00000000000..eef7117af7a --- /dev/null +++ b/Lib/test/certdata/secp384r1.pem @@ -0,0 +1,7 @@ +$ openssl genpkey -genparam -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -pkeyopt ec_param_enc:named_curve -text +-----BEGIN EC PARAMETERS----- +BgUrgQQAIg== +-----END EC PARAMETERS----- +ECDSA-Parameters: (384 bit) +ASN1 OID: secp384r1 +NIST CURVE: P-384 diff --git a/Lib/test/certdata/selfsigned_pythontestdotnet.pem b/Lib/test/certdata/selfsigned_pythontestdotnet.pem new file mode 100644 index 00000000000..2b1760747bc --- /dev/null +++ b/Lib/test/certdata/selfsigned_pythontestdotnet.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF9zCCA9+gAwIBAgIUH98b4Fw/DyugC9cV7VK7ZODzHsIwDQYJKoZIhvcNAQEL +BQAwgYoxCzAJBgNVBAYTAlhZMRcwFQYDVQQIDA5DYXN0bGUgQW50aHJheDEYMBYG +A1UEBwwPQXJndW1lbnQgQ2xpbmljMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUg +Rm91bmRhdGlvbjEjMCEGA1UEAwwac2VsZi1zaWduZWQucHl0aG9udGVzdC5uZXQw +HhcNMTkwNTA4MDEwMjQzWhcNMjcwNzI0MDEwMjQzWjCBijELMAkGA1UEBhMCWFkx +FzAVBgNVBAgMDkNhc3RsZSBBbnRocmF4MRgwFgYDVQQHDA9Bcmd1bWVudCBDbGlu +aWMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMSMwIQYDVQQD +DBpzZWxmLXNpZ25lZC5weXRob250ZXN0Lm5ldDCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAMKdJlyCThkahwoBb7pl5q64Pe9Fn5jrIvzsveHTc97TpjV2 +RLfICnXKrltPk/ohkVl6K5SUZQZwMVzFubkyxE0nZPHYHlpiKWQxbsYVkYv01rix +IFdLvaxxbGYke2jwQao31s4o61AdlsfK1SdpHQUynBBMssqI3SB4XPmcA7e+wEEx +jxjVish4ixA1vuIZOx8yibu+CFCf/geEjoBMF3QPdzULzlrCSw8k/45iZCSoNbvK +DoL4TVV07PHOxpheDh8ZQmepGvU6pVqhb9m4lgmV0OGWHgozd5Ur9CbTVDmxIEz3 +TSoRtNJK7qtyZdGNqwjksQxgZTjM/d/Lm/BJG99AiOmYOjsl9gbQMZgvQmMAtUsI +aMJnQuZ6R+KEpW/TR5qSKLWZSG45z/op+tzI2m+cE6HwTRVAWbcuJxcAA55MZjqU +OOOu3BBYMjS5nf2sQ9uoXsVBFH7i0mQqoW1SLzr9opI8KsWwFxQmO2vBxWYaN+lH +OmwBZBwyODIsmI1YGXmTp09NxRYz3Qe5GCgFzYowpMrcxUC24iduIdMwwhRM7rKg +7GtIWMSrFfuI1XCLRmSlhDbhNN6fVg2f8Bo9PdH9ihiIyxSrc+FOUasUYCCJvlSZ +8hFUlLvcmrZlWuazohm0lsXuMK1JflmQr/DA/uXxP9xzFfRy+RU3jDyxJbRHAgMB +AAGjUzBRMB0GA1UdDgQWBBSQJyxiPMRK01i+0BsV9zUwDiBaHzAfBgNVHSMEGDAW +gBSQJyxiPMRK01i+0BsV9zUwDiBaHzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4ICAQCR+7a7N/m+WLkxPPIA/CB4MOr2Uf8ixTv435Nyv6rXOun0+lTP +ExSZ0uYQ+L0WylItI3cQHULldDueD+s8TGzxf5woaLKf6tqyr0NYhKs+UeNEzDnN +9PHQIhX0SZw3XyXGUgPNBfRCg2ZDdtMMdOU4XlQN/IN/9hbYTrueyY7eXq9hmtI9 +1srftAMqr9SR1JP7aHI6DVgrEsZVMTDnfT8WmLSGLlY1HmGfdEn1Ip5sbo9uSkiH +AEPgPfjYIvR5LqTOMn4KsrlZyBbFIDh9Sl99M1kZzgH6zUGVLCDg1y6Cms69fx/e +W1HoIeVkY4b4TY7Bk7JsqyNhIuqu7ARaxkdaZWhYaA2YyknwANdFfNpfH+elCLIk +BUt5S3f4i7DaUePTvKukCZiCq4Oyln7RcOn5If73wCeLB/ZM9Ei1HforyLWP1CN8 +XLfpHaoeoPSWIveI0XHUl65LsPN2UbMbul/F23hwl+h8+BLmyAS680Yhn4zEN6Ku +B7Po90HoFa1Du3bmx4jsN73UkT/dwMTi6K072FbipnC1904oGlWmLwvAHvrtxxmL +Pl3pvEaZIu8wa/PNF6Y7J7VIewikIJq6Ta6FrWeFfzMWOj2qA1ZZi6fUaDSNYvuV +J5quYKCc/O+I/yDDf8wyBbZ/gvUXzUHTMYGG+bFrn1p7XDbYYeEJ6R/xEg== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/ssl_cert.pem b/Lib/test/certdata/ssl_cert.pem new file mode 100644 index 00000000000..427948252b7 --- /dev/null +++ b/Lib/test/certdata/ssl_cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEgzCCAuugAwIBAgIUU+FIM/dUbCklbdDwNPd2xemDAEwwDQYJKoZIhvcNAQEL +BQAwXzELMAkGA1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYD +VQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxo +b3N0MB4XDTIzMTEyNTA0MjEzNloXDTQzMDEyNDA0MjEzNlowXzELMAkGA1UEBhMC +WFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYDVQQKDBpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxob3N0MIIBojANBgkqhkiG +9w0BAQEFAAOCAY8AMIIBigKCAYEAzXTIl1su11AGu6sDPsoxqcRGyAX0yjxIcswF +vj+eW/fBs2GcBby95VEOKpJPKRYYB7fAEAjAKK59zFdsDX/ynxPZLqyLQocBkFVq +tclhCRZu//KZND+uQuHSx3PjGkSvK/nrGjg5T0bkM4SFeb0YdLb+0aDTKGozUC82 +oBAilNcrFz1VXpEF0qUe9QeKQhyd0MaW5T1oSn+U3RAj2MXm3TGExyZeaicpIM5O +HFlnwUxsYSDZo0jUj342MbPOZh8szZDWi042jdtSA3i8uMSplEf4O8ZPmX0JCtrz +fVjRVdaKXIjrhMNWB8K44q6AeyhqJcVHtOmPYoHDm0qIjcrurt0LZaGhmCuKimNd +njcPxW0VQmDIS/mO5+s24SK+Mpznm5q/clXEwyD8FbrtrzV5cHCE8eNkxjuQjkmi +wW9uadK1s54tDwRWMl6DRWRyxoF0an885UQWmbsgEB5aRmEx2L0JeD0/q6Iw1Nta +As8DG4AaWuYMrgZXz7XvyiMq3IxVAgMBAAGjNzA1MBQGA1UdEQQNMAuCCWxvY2Fs +aG9zdDAdBgNVHQ4EFgQUl2wd7iWE1JTZUVq2yFBKGm9N36owDQYJKoZIhvcNAQEL +BQADggGBAF0f5x6QXFbgdyLOyeAPD/1DDxNjM68fJSmNM/6vxHJeDFzK0Pja+iJo +xv54YiS9F2tiKPpejk4ujvLQgvrYrTQvliIE+7fUT0dV74wZKPdLphftT9uEo1dH +TeIld+549fqcfZCJfVPE2Ka4vfyMGij9hVfY5FoZL1Xpnq/ZGYyWZNAPbkG292p8 +KrfLZm/0fFYAhq8tG/6DX7+2btxeX4MP/49tzskcYWgOjlkknyhJ76aMG9BJ1D7F +/TIEh5ihNwRTmyt023RBz/xWiN4xBLyIlpQ6d5ECKmFNFr0qnEui6UovfCHUF6lZ +qcAQ5VFQQ2CayNlVmQ+UGmWIqANlacYWBt7Q6VqpGg24zTMec1/Pqd6X07ScSfrm +MAtywrWrU7p1aEkN5lBa4n/XKZHGYMjor/YcMdF5yjdSrZr274YYO1pafmTFwRwH +5o16c8WPc0aPvTFbkGIFT5ddxYstw+QwsBtLKE2lJ4Qfmxt0Ew/0L7xkbK1BaCOo +EGD2IF7VDQ== +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/ssl_key.passwd.pem b/Lib/test/certdata/ssl_key.passwd.pem new file mode 100644 index 00000000000..6ab7d57d003 --- /dev/null +++ b/Lib/test/certdata/ssl_key.passwd.pem @@ -0,0 +1,42 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/Lib/test/certdata/ssl_key.pem b/Lib/test/certdata/ssl_key.pem new file mode 100644 index 00000000000..ee927210511 --- /dev/null +++ b/Lib/test/certdata/ssl_key.pem @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +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 +kNdcwhtZZ3dpChUdf+w/LIc= +-----END PRIVATE KEY----- diff --git a/Lib/test/certdata/talos-2019-0758.pem b/Lib/test/certdata/talos-2019-0758.pem new file mode 100644 index 00000000000..13b95a77fd8 --- /dev/null +++ b/Lib/test/certdata/talos-2019-0758.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDqDCCApKgAwIBAgIBAjALBgkqhkiG9w0BAQswHzELMAkGA1UEBhMCVUsxEDAO +BgNVBAMTB2NvZHktY2EwHhcNMTgwNjE4MTgwMDU4WhcNMjgwNjE0MTgwMDU4WjA7 +MQswCQYDVQQGEwJVSzEsMCoGA1UEAxMjY29kZW5vbWljb24tdm0tMi50ZXN0Lmxh +bC5jaXNjby5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC63fGB +J80A9Av1GB0bptslKRIUtJm8EeEu34HkDWbL6AJY0P8WfDtlXjlPaLqFa6sqH6ES +V48prSm1ZUbDSVL8R6BYVYpOlK8/48xk4pGTgRzv69gf5SGtQLwHy8UPBKgjSZoD +5a5k5wJXGswhKFFNqyyxqCvWmMnJWxXTt2XDCiWc4g4YAWi4O4+6SeeHVAV9rV7C +1wxqjzKovVe2uZOHjKEzJbbIU6JBPb6TRfMdRdYOw98n1VXDcKVgdX2DuuqjCzHP +WhU4Tw050M9NaK3eXp4Mh69VuiKoBGOLSOcS8reqHIU46Reg0hqeL8LIL6OhFHIF +j7HR6V1X6F+BfRS/AgMBAAGjgdYwgdMwCQYDVR0TBAIwADAdBgNVHQ4EFgQUOktp +HQjxDXXUg8prleY9jeLKeQ4wTwYDVR0jBEgwRoAUx6zgPygZ0ZErF9sPC4+5e2Io +UU+hI6QhMB8xCzAJBgNVBAYTAlVLMRAwDgYDVQQDEwdjb2R5LWNhggkA1QEAuwb7 +2s0wCQYDVR0SBAIwADAuBgNVHREEJzAlgiNjb2Rlbm9taWNvbi12bS0yLnRlc3Qu +bGFsLmNpc2NvLmNvbTAOBgNVHQ8BAf8EBAMCBaAwCwYDVR0fBAQwAjAAMAsGCSqG +SIb3DQEBCwOCAQEAvqantx2yBlM11RoFiCfi+AfSblXPdrIrHvccepV4pYc/yO6p +t1f2dxHQb8rWH3i6cWag/EgIZx+HJQvo0rgPY1BFJsX1WnYf1/znZpkUBGbVmlJr +t/dW1gSkNS6sPsM0Q+7HPgEv8CPDNK5eo7vU2seE0iWOkxSyVUuiCEY9ZVGaLVit +p0C78nZ35Pdv4I+1cosmHl28+es1WI22rrnmdBpH8J1eY6WvUw2xuZHLeNVN0TzV +Q3qq53AaCWuLOD1AjESWuUCxMZTK9DPS4JKXTK8RLyDeqOvJGjsSWp3kL0y3GaQ+ +10T1rfkKJub2+m9A9duin1fn6tHc2wSvB7m3DA== +-----END CERTIFICATE----- diff --git a/Lib/test/keycert.passwd.pem b/Lib/test/keycert.passwd.pem deleted file mode 100644 index 0ad69605519..00000000000 --- a/Lib/test/keycert.passwd.pem +++ /dev/null @@ -1,50 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,E74528136B90D2DD - -WRHVD2PJXPqjFSHg92HURIsUzvsTE4a9oi0SC5yMBFKNWA5Z933gK3XTifp6jul5 -zpNYi8jBXZ2EqJJBxCuVcefmXSxL0q7CMej25TdIC4BVAFJVveeprHPUFkNB0IM1 -go5Lg4YofYqTCg3OE3k7WvfR3Zg1cRYxksDKO+WNZgWyKBex5X4vjOiyUqDl3GKt -kQXnkg1VgPV2Vrx93S9XNdELNRTguwf+XG0fkhtYhp/zCto8uKTgy5elK2P/ulGp -7fe6uj7h/uN9L7EOC6CjRkitywfeBUER739mOcGT4imSFJ9G27TCqPzj2ea3uuaf -/v1xhkQ4M6lNY/gcRfgVpCXhW43aAQV8XXQRMJTqLmz5Y5hYTKn+Ugq5vJ/ngyRM -lu1gUJnYYaemBTb4hbm6aBvnYK9mORa891Pmf+vxU9rYuQIdVAhvvXh4KBreSEBI -1AFy6dFKXl8ZKs6Wrq5wPefmFFkRmZ8OBiiq0fp2ApCRGZw6LsjCgwrRM38JiY7d -3OdsJpKvRYufgUyuuzUE0xA+E4yMvD48M9pPq2fC8O5giuGL1uEekQWXJuq+6ZRI -XYKIeSkuQALbX3RAzCPXTUEMtCYXKm/gxrrwJ+Bet4ob2amf3MX0uvWwOuAq++Fk -J0HFSBxrwvIWOhyQXOPuJdAN8PXA7dWOXfOgOMF0hQYqZCl3T4TiVZJbwVQtg1sN -dO7oAD5ZPHiKzvveZuB6k1FlBG8j0TyAC+44ChxkPDD3jF4dd6zGe62sDf85p4/d -W80gxJeD3xnDxG0ePPns+GuKUpUaWS7WvHtDpeFW1JEhvOqf8p1Li9a7RzWVo8ML -mGTdQgBIYIf6/fk69pFKl0nKtBU75KaunZz4nAmd9bNED4naDurMBg44u5TvODbJ -vgYIYXIYjNvONbskJatVrrTS8zch2NwVIjCi8L/hecwBXbIXzo1pECpc6BU7sQT8 -+i9sDKBeJcRipzfKZNHvnO19mUZaPCY8+a/f9c21DgKXz+bgLcJbohpSaeGM8Gfc -aZd3Vp9n3OJ3g2zQR1++HO9v1vR/wLELu6MeydkvMduHLmOPCn54gZ9z51ZNPAwa -qfFIsH+mLh9ks0H74ssF59uIlstkgB9zmZHv/Q0dK9ZfG/VEH6rSgdETWhZxhoMQ -Z92jXBEFT0zhI3rrIPNY+XS7eJCQIc1wc84Ea3cRk7SP+S1og3JtAxX56ykUwtkM -LQ/Dwwa6h1aqD0l2d5x1/BSdavtTuSegISRWQ4iOmSvEdlFP7H4g6RZk/okbLzMD -Evq5gNc7vlXhVawoQU8JCanJ5ZbbWnIRZfiXxBQS4lpYPKvJt4ML9z/x+82XxcXv -Z93N2Wep7wWW5OwS2LcQcOgZRDSIPompwo/0pMFGOS+5oort0ZDRHdmmGLjvBcCb -1KQmKQ4+8brI/3rjRzts6uDLjTGNxSCieNsnqhwHUv9Mg9WDSWupcGa+x27L89x3 -rObf6+3umcFLSjIzU8wuv1hx/e/y98Kv7BDBNYpAr6kVMrLnzYjAfJbBmqlxkzkQ -IgQzgrk2QZoTdgwR+S374NAMO0AE5IlO+/qa6qp2SORGTDX64I3UNw== ------END RSA PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDWTCCAkGgAwIBAgIJAPm6B21bar2bMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV -BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u -IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODAx -MTkxOTA5MDZaFw0yODAxMTcxOTA5MDZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH -DA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5k -YXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKvvsX2gEti4shve3iYMc+jE4Se7WHs1Bol2f21H8qNboDOFdeb1 -RKHjmq3exHpajywOUEgne9nKHJY/3f2phR4Y5klqG6liLgiSpVyRlcBGbeT2qEAj -9oLiLFUXLGfGDds2mTwivQDLJBWi51j7ff5k2Pr58fN5ugYMn24T9FNyn0moT+qj -SFoBNm58l9jrdkJSlgWfqPlbiMa+mqDn/SFtrwLF2Trbfzu42Sd9UdIzMaSSrzbN -sGm53pNhCh8KndWUQ8GPP2IsLPoUU4qAtmZuTxCx2S1cXrN9EkmT69tlOH84YfSn -96Ih9bWRc7M5y5bfVdEVM+fKQl3hBRf05qMCAwEAAaMYMBYwFAYDVR0RBA0wC4IJ -bG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQAtQ8f37cCEk7/rAcbYR53ce3iK -Vpihb0U2ni1QjG9Tg9UIExkIGkwTiCm7kwQL+GEStBu9AG/QVrOjeTriRiddhWkk -ze8kRaI3AC/63t6Vh9Q1x6PESgeE4OtAO9JpJCf4GILglA789Y/b/GF8zJZQxR13 -qpB4ZwWw7gCBhdEW59u6CFeBmfDa58hM8lWvuVoRrTi7bjUeC6PAn5HVMzZSykhu -4HaUfBp6bKFjuym2+h/VvM1n8C3chjVSmutsLb6ELdD8IK0vPV/yf5+LN256zSsS -dyUZYd8XwQaioEMKdbhLvnehyzHiWfQIUR3BdhONxoIJhHv/EAo8eCkHHYIF ------END CERTIFICATE----- diff --git a/Lib/test/keycert.pem b/Lib/test/keycert.pem deleted file mode 100644 index 9545dcf4b94..00000000000 --- a/Lib/test/keycert.pem +++ /dev/null @@ -1,48 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCr77F9oBLYuLIb -3t4mDHPoxOEnu1h7NQaJdn9tR/KjW6AzhXXm9USh45qt3sR6Wo8sDlBIJ3vZyhyW -P939qYUeGOZJahupYi4IkqVckZXARm3k9qhAI/aC4ixVFyxnxg3bNpk8Ir0AyyQV -oudY+33+ZNj6+fHzeboGDJ9uE/RTcp9JqE/qo0haATZufJfY63ZCUpYFn6j5W4jG -vpqg5/0hba8Cxdk62387uNknfVHSMzGkkq82zbBpud6TYQofCp3VlEPBjz9iLCz6 -FFOKgLZmbk8QsdktXF6zfRJJk+vbZTh/OGH0p/eiIfW1kXOzOcuW31XRFTPnykJd -4QUX9OajAgMBAAECggEAHppmXDbuw9Z0FVPg9KLIysioTtsgz6VLiZIm8juZK4x2 -glUh/D7xvWL2uDXrgN+3lh7iGUW13LkFx5SMncbbo9TIwI57Z/XKvcnkVwquve+L -RfLFVc1Q5lD9lROv2rS86KTaN4LzYz3FKXi6dvMkpPAsUtfEQhMLkmISypQQq/1z -EJaqo7r85OjN7e0wKazlKZpOzJEa5FQLMVRjTRFhLFNbHXX/tAet2jw+umATKbw8 -hYgiuZ44TwSEd9JeIV/oSYWfI/3HetuYW0ru3caiztRF2NySNu8lcsWgNC7fIku9 -mcHjtSNzs91QN1Qlu7GQvvhpt6OWDirNDCW+49WGaQKBgQDg9SDhfF0jRYslgYbH -cqO4ggaFdHjrAAYpwnAgvanhFZL/zEqm5G1E7l/e2fCkJ9VOSFO0A208chvwMcr+ -dCjHE2tVdE81aQ2v/Eo83VdS1RcOV4Y75yPH48rMhxPaHvxWD/FFDbf0/P2mtPB7 -SZ3kIeZMkE1wxdaO3AKUbQoozwKBgQDDqYgg7kVtygyICE1mB8Hwp6nUxFTczG7y -4XcsDqMIrKmw+PbQluvkoHoStxeVrsTloDhkTjIrpmYLyAiazg+PUJdkd6xrfLSj -VV6X93W0S/1egEb1F1CGFxtk8v/PWH4K76EPL2vxXdxjywz3GWlrL9yDYaB2szzS -DqgwVMqx7QKBgDCD7UF0Bsoyl13RX3XoPXLvZ+SkR+e2q52Z94C4JskKVBeiwX7Y -yNAS8M4pBoMArDoj0xmBm69rlKbqtjLGbnzwrTdSzDpim7cWnBQgUFLm7gAD1Elb -AhZ8BCK0Bw4FnLoa2hfga4oEfdfUMgEE0W5/+SEOBgWKRUmuHUhRc911AoGAY2EN -YmSDYSM5wDIvVb5k9B3EtevOiqNPSw/XnsoEZtiEC/44JnQxdltIBY93bDBrk5IQ -cmoBM4h91kgQjshQwOMXMhFSwvmBKmCm/hrTbvMVytTutXfVD3ZXFKwT4DW7N0TF -ElhsxBh/YzRz7mG62JVjtFt2zDN3ld2Z8YpvtXUCgYEA4EJ4ObS5YyvcXAKHJFo6 -Fxmavyrf8LSm3MFA65uSnFvWukMVqqRMReQc5jvpxHKCis+XvnHzyOfL0gW9ZTi7 -tWGGbBi0TRJCa8BkvgngUZxOxUlMfg/7cVxOIB0TPoUSgxFd/+qVz4GZMvr0dPu7 -eAF7J/8ECVvb0wSPTUI1N3c= ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDWTCCAkGgAwIBAgIJAPm6B21bar2bMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV -BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u -IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODAx -MTkxOTA5MDZaFw0yODAxMTcxOTA5MDZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH -DA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5k -YXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKvvsX2gEti4shve3iYMc+jE4Se7WHs1Bol2f21H8qNboDOFdeb1 -RKHjmq3exHpajywOUEgne9nKHJY/3f2phR4Y5klqG6liLgiSpVyRlcBGbeT2qEAj -9oLiLFUXLGfGDds2mTwivQDLJBWi51j7ff5k2Pr58fN5ugYMn24T9FNyn0moT+qj -SFoBNm58l9jrdkJSlgWfqPlbiMa+mqDn/SFtrwLF2Trbfzu42Sd9UdIzMaSSrzbN -sGm53pNhCh8KndWUQ8GPP2IsLPoUU4qAtmZuTxCx2S1cXrN9EkmT69tlOH84YfSn -96Ih9bWRc7M5y5bfVdEVM+fKQl3hBRf05qMCAwEAAaMYMBYwFAYDVR0RBA0wC4IJ -bG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQAtQ8f37cCEk7/rAcbYR53ce3iK -Vpihb0U2ni1QjG9Tg9UIExkIGkwTiCm7kwQL+GEStBu9AG/QVrOjeTriRiddhWkk -ze8kRaI3AC/63t6Vh9Q1x6PESgeE4OtAO9JpJCf4GILglA789Y/b/GF8zJZQxR13 -qpB4ZwWw7gCBhdEW59u6CFeBmfDa58hM8lWvuVoRrTi7bjUeC6PAn5HVMzZSykhu -4HaUfBp6bKFjuym2+h/VvM1n8C3chjVSmutsLb6ELdD8IK0vPV/yf5+LN256zSsS -dyUZYd8XwQaioEMKdbhLvnehyzHiWfQIUR3BdhONxoIJhHv/EAo8eCkHHYIF ------END CERTIFICATE----- diff --git a/Lib/test/keycert2.pem b/Lib/test/keycert2.pem deleted file mode 100644 index bb5fa65a8ac..00000000000 --- a/Lib/test/keycert2.pem +++ /dev/null @@ -1,49 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3ulRNfhbOAey/ -B+wIVYx+d5az7EV4riR6yi/qE6G+bxbTvay2pqySHtDweuaYSh2cVmcasBKKIFJm -rCD1zR8UmLb5i2XFIina1t3eePCuBZMrvZZwkzlQUSM1AZtjGOO/W0I3FwO6y645 -9xA5PduKI7SMYkH/VL3zE5W1JwMovv6bvNiT+GU5l6mB9ylCTgLpmUqoQhRqz/35 -zCzVyoh+ppDvVcpWYfvXywsXsgQwbAF0QJm8SSFi0TZm5ykv4WE16afQp08yuZS0 -3U4K3MJCa4rxO58edcxBopWYfQ29K3iINM8enRfr5q+u5mAAbALAEEvyFjgLWl/u -7arxn7bJAgMBAAECggEBAJfMt8KfHzBunrDnVrk8FayYGkfmOzAOkc1yKEx6k/TH -zFB+Mqlm5MaF95P5t3S0J+r36JBAUdEWC38RUNpF9BwMYYGlDxzlsTdCuGYL/q+J -o6NMLXQt7/jQUQqGnWAvPFzqhbcGqOo5R2ZVH25sEWv9PDuRI35XAepIkDTwWsfa -P6UcJJoP+4v9B++fb3sSL4zNwp1BqS4wxR8YTR0t1zQqOxJ5BGPw1J8aBMs1sq5t -qyosAQAT63kLrdqWotHaM26QxjqEQUMlh12XMWb5GdBXUxbvyGtEabsqskGa/f8B -RdHE437J8D8l+jxb2mZLzrlaH3dq2tbFGCe1rT8qLRECgYEA5CWIvoD/YnQydLGA -OlEhCSocqURuqcotg9Ev0nt/C60jkr/NHFLGppz9lhqjIDjixt3sIMGZMFzxRtwM -pSYal3XiR7rZuHau9iM35yDhpuytEiGbYy1ADakJRzY5jq/Qa8RfPP9Atua5xAeP -q6DiSnq9vhHv9G+O4MxzHBmrw9sCgYEAziiJWFthcwvuXn3Jv9xFYKEb/06puZAx -EgQCz/3rPzv5fmGD/sKVo1U/K4z/eA82DNeKG8QRTFJCxT8TCNRxOmGV7HdCYo/B -4BTNNvbKcdi3l0j75kKoADg+nt5CD5lz6gLG0GrUEnVO1y5HVfCTb3BEAfa36C85 -9i0sfQGiwysCgYEAuus9k8cgdct5oz3iLuVVSark/JGCkT2B+OOkaLChsDFUWeEm -7TOsaclpwldkmvvAYOplkZjMJ2GelE2pVo1XcAw3LkmaI5WpVyQXoxe/iQGT8qzy -IFlsh0Scw2lb0tmcyw6CcPk4TiHOxRrkzNrtS9QwLM+JZx0XVHptPPKTVc0CgYAu -j/VFYY5G/8Dc0qhIjyWUR48dQNUQtkJ/ASzpcT46z/7vznKTjbtiYpSb74KbyUO5 -7sygrM4DYOj3x+Eys1jHiNbly6HQxQtS4x/edCsRP5NntfI+9XsgYZOzKhvdjhki -F3J0DEzNxnUCIM+311hVaRPTJbgv1srOkTFlIoNydQKBgQC6/OHGaC/OewQqRlRK -Mg5KZm01/pk4iKrpA5nG7OTAeoa70NzXNtG8J3WnaJ4mWanNwNUOyRMAMrsUAy9q -EeGqHM5mMFpY4TeVuNLL21lu/x3KYw6mKL3Ctinn+JLAoYoqEy8deZnEA5/tjYlz -YhFBchnUicjoUN1chdpM6SpV2Q== ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDYjCCAkqgAwIBAgIJALJXRr8qF6oIMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV -BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u -IFNvZnR3YXJlIEZvdW5kYXRpb24xFTATBgNVBAMMDGZha2Vob3N0bmFtZTAeFw0x -ODAxMTkxOTA5MDZaFw0yODAxMTcxOTA5MDZaMGIxCzAJBgNVBAYTAlhZMRcwFQYD -VQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZv -dW5kYXRpb24xFTATBgNVBAMMDGZha2Vob3N0bmFtZTCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBALe6VE1+Fs4B7L8H7AhVjH53lrPsRXiuJHrKL+oTob5v -FtO9rLamrJIe0PB65phKHZxWZxqwEoogUmasIPXNHxSYtvmLZcUiKdrW3d548K4F -kyu9lnCTOVBRIzUBm2MY479bQjcXA7rLrjn3EDk924ojtIxiQf9UvfMTlbUnAyi+ -/pu82JP4ZTmXqYH3KUJOAumZSqhCFGrP/fnMLNXKiH6mkO9VylZh+9fLCxeyBDBs -AXRAmbxJIWLRNmbnKS/hYTXpp9CnTzK5lLTdTgrcwkJrivE7nx51zEGilZh9Db0r -eIg0zx6dF+vmr67mYABsAsAQS/IWOAtaX+7tqvGftskCAwEAAaMbMBkwFwYDVR0R -BBAwDoIMZmFrZWhvc3RuYW1lMA0GCSqGSIb3DQEBCwUAA4IBAQCZhHhGItpkqhEq -ntMRd6Hv0GoOJixNvgeMwK4NJSRT/no3OirtUTzccn46h+SWibSa2eVssAV+pAVJ -HbzkN/DH27A1mMx1zJL1ekcOKA1AF6MXhUnrUGXMqW36YNtzHfXJLrwvpLJ13OQg -/Kxo4Nw68bGzM+PyRtKU/mpgYyfcvwR+ZSeIDh1fvUZK/IEVCf8ub42GPVs5wPfv -M+k5aHxWTxeif3K1byTRzxHupYNG2yWO4XEdnBGOuOwzzN4/iQyNcsuQKeuKHGrt -YvIlG/ri04CQ7xISZCj74yjTZ+/A2bXre2mQXAHqKPumHL7cl34+erzbUaxYxbTE -u5FcOmLQ ------END CERTIFICATE----- diff --git a/Lib/test/keycert3.pem b/Lib/test/keycert3.pem deleted file mode 100644 index 621eb08bb0c..00000000000 --- a/Lib/test/keycert3.pem +++ /dev/null @@ -1,132 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDgV4G+Zzf2DT5n -oAisIGFhn/bz7Vn5WiXUqbDsxROJOh/7BtOlduZka0pPhFylGbnxS8l1kEWHRI2T -6hOoWzumB6ItKiN+T5J30lAvSyo7iwdFoAQ/S5nPXQfhNARQe/NEOhRtpcuNdyx4 -BWdPdPuJQA1ASNJCLwcLOoRxaLbKLvb2V5T5FCAkeNPtRvPuT4gKQItMmiHfAhoV -C8MZWF/GC0RukHINys5MwqeFexam8CznmQPMYrLdhmKTj3DTivCPoh97EDIFGlgZ -SCaaYDVQA+aqlo/q2pi52PFwC1KzhNEA7EeOqSwC1NQjwjHuhcnf9WbxrgTq2zh3 -rv5YEW2ZAgMBAAECggEAPfSMtTumPcJskIumuXp7yk02EyliZrWZqwBuBwVqHsS5 -nkbFXnXWrLbgn9MrDsFrE5NdgKUmPnQVMVs8sIr5jyGejSCNCs4I4iRn1pfIgwcj -K/xEEALd6GGF0pDd/CgvB5GOoLVf4KKf2kmLvWrOKJpSzoUN5A8+v8AaYYOMr4sC -czbvfGomzEIewEG+Rw9zOVUDlmwyEKPQZ47E7PQ+EEA7oeFdR+1Zj6eT9ndegf8B -54frySYCLRUCk/sHCpWhaJBtBrcpht7Y8CfY7hiH/7x866fvuLnYPz4YALtUb0wN -7zUCNS9ol3n4LbjFFKfZtiRjKaCBRzMjK0rz6ydFcQKBgQDyLI3oGbnW73vqcDe/ -6eR0w++fiCAVhfMs3AO/gOaJi2la2JHlJ5u+cIHQIOFwEhn6Zq0AtdmnFx1TS5IQ -C0MdXI0XoQQw7rEF8EJcvfe85Z0QxENVhzydtdb8QpJfnQGfBfLyQlaaRYzRRHB6 -VdYUHF3EIPVIhbjbghal+Qep/QKBgQDtJlRPHkWwTMevu0J0fYbWN1ywtVTFUR// -k7VyORSf8yuuSnaQRop4cbcqONxmDKH6Or1fl3NYBsAxtXkkOK1E2OZNo2sfQdRa -wpA7o7mPHRhztQFpT5vflp+8P6+PEFat8D04eBOhNwrwwfhiPjD4gv5KvN4XutRW -VWv/2pnmzQKBgHPvHGg2mJ7quvm6ixXW1MWJX1eSBToIjCe3lBvDi5nhIaiZ8Q4w -7gA3QA3xD7tlDwauzLeAVxgEmsdbcCs6GQEfY3QiYy1Bt4FOSZa4YrcNfSmfq1Rw -j3Y4rRjKjeQz96i3YlzToT3tecJc7zPBj+DEy6au2H3Fdn+vQURneWHJAoGBANG7 -XES8mRVaUh/wlM1BVsaNH8SIGfiHzqzRjV7/bGYpQTBbWpAuUrhCmaMVtpXqBjav -TFwGLVRkZAWSYRjPpy2ERenT5SE3rv61o6mbGrifGsj6A82HQmtzYsGx8SmtYXtj -REF0sKebbmmOooUAS379GrguYJzL9o6D7YfRZNrhAoGAVfb/tiFU4S67DSpYpQey -ULhgfsFpDByICY6Potsg67gVFf9jIaB83NPTx3u/r6sHFgxFw7lQsuZcgSuWMu7t -glzOXVIP11Y5sl5CJ5OsfeK1/0umMZF5MWPyAQCx/qrPlZL86vXjt24Y/VaOxsAi -CZYdyJsjgOrJrWoMbo5ta54= ------END PRIVATE KEY----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 82:ed:bf:41:c8:80:91:9c - Signature Algorithm: sha1WithRSAEncryption - Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server - Validity - Not Before: Jan 19 19:09:06 2018 GMT - Not After : Nov 28 19:09:06 2027 GMT - Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=localhost - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: - 00:e0:57:81:be:67:37:f6:0d:3e:67:a0:08:ac:20: - 61:61:9f:f6:f3:ed:59:f9:5a:25:d4:a9:b0:ec:c5: - 13:89:3a:1f:fb:06:d3:a5:76:e6:64:6b:4a:4f:84: - 5c:a5:19:b9:f1:4b:c9:75:90:45:87:44:8d:93:ea: - 13:a8:5b:3b:a6:07:a2:2d:2a:23:7e:4f:92:77:d2: - 50:2f:4b:2a:3b:8b:07:45:a0:04:3f:4b:99:cf:5d: - 07:e1:34:04:50:7b:f3:44:3a:14:6d:a5:cb:8d:77: - 2c:78:05:67:4f:74:fb:89:40:0d:40:48:d2:42:2f: - 07:0b:3a:84:71:68:b6:ca:2e:f6:f6:57:94:f9:14: - 20:24:78:d3:ed:46:f3:ee:4f:88:0a:40:8b:4c:9a: - 21:df:02:1a:15:0b:c3:19:58:5f:c6:0b:44:6e:90: - 72:0d:ca:ce:4c:c2:a7:85:7b:16:a6:f0:2c:e7:99: - 03:cc:62:b2:dd:86:62:93:8f:70:d3:8a:f0:8f:a2: - 1f:7b:10:32:05:1a:58:19:48:26:9a:60:35:50:03: - e6:aa:96:8f:ea:da:98:b9:d8:f1:70:0b:52:b3:84: - d1:00:ec:47:8e:a9:2c:02:d4:d4:23:c2:31:ee:85: - c9:df:f5:66:f1:ae:04:ea:db:38:77:ae:fe:58:11: - 6d:99 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Subject Alternative Name: - DNS:localhost - X509v3 Key Usage: critical - Digital Signature, Key Encipherment - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Basic Constraints: critical - CA:FALSE - X509v3 Subject Key Identifier: - 85:11:BE:16:47:04:D1:30:EE:86:8A:18:70:BE:A8:28:6F:82:3D:CE - X509v3 Authority Key Identifier: - keyid:9A:CF:CF:6E:EB:71:3D:DB:3C:F1:AE:88:6B:56:72:03:CB:08:A7:48 - DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server - serial:82:ED:BF:41:C8:80:91:9B - - Authority Information Access: - CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer - OCSP - URI:http://testca.pythontest.net/testca/ocsp/ - - X509v3 CRL Distribution Points: - - Full Name: - URI:http://testca.pythontest.net/testca/revocation.crl - - Signature Algorithm: sha1WithRSAEncryption - 7f:a1:7e:3e:68:01:b0:32:b8:57:b8:03:68:13:13:b3:e3:f4: - 70:2f:15:e5:0f:87:b9:fd:e0:12:e3:16:f2:91:53:c7:4e:25: - af:ca:cb:a7:d9:9d:57:4d:bf:a2:80:d4:78:aa:04:31:fd:6d: - cc:6d:82:43:e9:62:16:0d:0e:26:8b:e7:f1:3d:57:5c:68:02: - 9c:2b:b6:c9:fd:62:2f:10:85:88:cc:44:a5:e7:a2:3e:89:f2: - 1f:02:6a:3f:d0:3c:6c:24:2d:bc:51:62:7a:ec:25:c5:86:87: - 77:35:8f:f9:7e:d0:17:3d:77:56:bf:1a:0c:be:09:78:ee:ea: - 73:97:65:60:94:91:35:b3:5c:46:8a:5e:6d:94:52:de:48:b7: - 1f:6c:28:79:7f:ff:08:8d:e4:7d:d0:b9:0b:7c:ae:c4:1d:2a: - a1:b3:50:11:82:03:5e:6c:e7:26:fa:05:32:39:07:83:49:b9: - a2:fa:04:da:0d:e5:ff:4c:db:97:d0:c3:a7:43:37:4c:16:de: - 3c:b5:e9:7e:82:d4:b3:10:df:d1:c1:66:72:9c:15:67:19:3b: - 7b:91:0a:82:07:67:c5:06:03:5f:80:54:08:81:8a:b1:5c:7c: - 4c:d2:07:38:92:eb:12:f5:71:ae:de:05:15:c8:e1:33:f0:e4: - 96:0f:0f:1e ------BEGIN CERTIFICATE----- -MIIE8TCCA9mgAwIBAgIJAILtv0HIgJGcMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV -BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW -MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODAxMTkxOTA5MDZaFw0yNzExMjgx -OTA5MDZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj -MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxv -Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOBXgb5nN/YN -PmegCKwgYWGf9vPtWflaJdSpsOzFE4k6H/sG06V25mRrSk+EXKUZufFLyXWQRYdE -jZPqE6hbO6YHoi0qI35PknfSUC9LKjuLB0WgBD9Lmc9dB+E0BFB780Q6FG2ly413 -LHgFZ090+4lADUBI0kIvBws6hHFotsou9vZXlPkUICR40+1G8+5PiApAi0yaId8C -GhULwxlYX8YLRG6Qcg3KzkzCp4V7FqbwLOeZA8xist2GYpOPcNOK8I+iH3sQMgUa -WBlIJppgNVAD5qqWj+ramLnY8XALUrOE0QDsR46pLALU1CPCMe6Fyd/1ZvGuBOrb -OHeu/lgRbZkCAwEAAaOCAcAwggG8MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDAOBgNV -HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud -EwEB/wQCMAAwHQYDVR0OBBYEFIURvhZHBNEw7oaKGHC+qChvgj3OMH0GA1UdIwR2 -MHSAFJrPz27rcT3bPPGuiGtWcgPLCKdIoVGkTzBNMQswCQYDVQQGEwJYWTEmMCQG -A1UECgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNVBAMMDW91 -ci1jYS1zZXJ2ZXKCCQCC7b9ByICRmzCBgwYIKwYBBQUHAQEEdzB1MDwGCCsGAQUF -BzAChjBodHRwOi8vdGVzdGNhLnB5dGhvbnRlc3QubmV0L3Rlc3RjYS9weWNhY2Vy -dC5jZXIwNQYIKwYBBQUHMAGGKWh0dHA6Ly90ZXN0Y2EucHl0aG9udGVzdC5uZXQv -dGVzdGNhL29jc3AvMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly90ZXN0Y2EucHl0 -aG9udGVzdC5uZXQvdGVzdGNhL3Jldm9jYXRpb24uY3JsMA0GCSqGSIb3DQEBBQUA -A4IBAQB/oX4+aAGwMrhXuANoExOz4/RwLxXlD4e5/eAS4xbykVPHTiWvysun2Z1X -Tb+igNR4qgQx/W3MbYJD6WIWDQ4mi+fxPVdcaAKcK7bJ/WIvEIWIzESl56I+ifIf -Amo/0DxsJC28UWJ67CXFhod3NY/5ftAXPXdWvxoMvgl47upzl2VglJE1s1xGil5t -lFLeSLcfbCh5f/8IjeR90LkLfK7EHSqhs1ARggNebOcm+gUyOQeDSbmi+gTaDeX/ -TNuX0MOnQzdMFt48tel+gtSzEN/RwWZynBVnGTt7kQqCB2fFBgNfgFQIgYqxXHxM -0gc4kusS9XGu3gUVyOEz8OSWDw8e ------END CERTIFICATE----- diff --git a/Lib/test/keycert4.pem b/Lib/test/keycert4.pem deleted file mode 100644 index b7df7f3f2c7..00000000000 --- a/Lib/test/keycert4.pem +++ /dev/null @@ -1,132 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDH/76hZAZH4cSV -CmVZa5HEqKCjCKrcPwBECs9BS+3ibwN4x9NnFNP+tCeFGgJXl7WGFoeXgg3oK+1p -FsOWpsRHuF3BdqkCnShSydmT8bLaGHwKeL0cPxJP5T/uW7ezPKW2VWXGMwmwRaRJ -9dj2VCUu20vDZWSGFr9zjnjoJczBtH3RsVUgpK7euEHuQ5pIM9QSOaCo+5FPR7s7 -1nU7YqbFWtd+NhC8Og1G497B31DQlHciF6BRm6/cNGAmHaAErKUGBFdkGtFPHBn4 -vktoEg9fwxJAZLvGpoTZWrB4HRsRwVTmFdGvK+JXK225xF23AXRXp/snhSuSFeLj -E5cpyJJ7AgMBAAECggEAQOv527X2e/sDr0XSpHZQuT/r9UBpBlnFIlFH+fBF5k0X -GWv0ae/O6U1dzs0kmX57xG0n0ry6+vTXeleTYiH8cTOd66EzN9AAOO+hG29IGZf9 -HAEZkkO/FARc/mjzdtFnEYsjIHWM3ZWdwQx3Q28JKu6w51rQiN51g3NqOCGdF/uF -rE5XPKsKndn+nLHvsNuApFgUYZEwdrozgUueEgRaPTUCNhzotcA9eWoBdA24XNhk -x8Cm/bZWabXm7gBO75zl3Cu2F21ay+EuwyOZTsx6lZi6YX9/zo1mkO81Zi3tQk50 -NMEI0feLNwsdxTbmOcVJadjOgd+QVghlFyr5HGBWMQKBgQD3AH3rhnAo6tOyNkGN -+IzIU1MhUS452O7IavykUYO9sM24BVChpRtlI9Dpev4yE/q3BAO3+oWT3cJrN7/3 -iyo1dzAkpGvI65XWfElXFM4nLjEiZzx4W9fiPN91Oucpr0ED6+BZXTtz4gVm0TP/ -TUc2xvTB6EKvIyWmKOYEi0snxQKBgQDPSOjbz9jWOrC9XY7PmtLB6QJDDz7XSGVK -wzD+gDAPpAwhk58BEokdOhBx2Lwl8zMJi0CRHgH2vNvkRyhvUQ4UFzisrqann/Tw -klp5sw3iWC6ERC8z9zL7GfHs7sK3mOVeAdK6ffowPM3JrZ2vPusVBdr0MN3oZwki -CtNXqbY1PwKBgGheQNbAW6wubX0kB9chavtKmhm937Z5v4vYCSC1gOEqUAKt3EAx -L74wwBmn6rjmUE382EVpCgBM99WuHONQXmlxD1qsTw763LlgkuzE0cckcYaD8L06 -saHa7uDuHrcyYlpx1L5t8q0ol/e19i6uTKUMtGcq6OJwC3yGU4sgAIWxAoGBAMVq -qiQXm2vFL+jafxYoXUvDMJ1PmskMsTP4HOR2j8+FrOwZnVk3HxGP6HOVOPRn4JbZ -YiAT1Uj6a+7I+rCyINdvmlGUcTK6fFzW9oZryvBkjcD483/pkktmVWwTpa2YV/Ml -h16IdsyUTGYlDUYHhXtbPUJOfDpIT4F1j/0wrFGfAoGAO82BcUsehEUQE0xvQLIn -7QaFtUI5z19WW730jVuEobiYlh9Ka4DPbKMvka8MwyOxEwhk39gZQavmfG6+wZm+ -kjERU23LhHziJGWS2Um4yIhC7myKbWaLzjHEq72dszLpQku4BzE5fT60fxI7cURD -WGm/Z3Q2weS3ZGIoMj1RNPI= ------END PRIVATE KEY----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 82:ed:bf:41:c8:80:91:9d - Signature Algorithm: sha1WithRSAEncryption - Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server - Validity - Not Before: Jan 19 19:09:06 2018 GMT - Not After : Nov 28 19:09:06 2027 GMT - Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=fakehostname - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: - 00:c7:ff:be:a1:64:06:47:e1:c4:95:0a:65:59:6b: - 91:c4:a8:a0:a3:08:aa:dc:3f:00:44:0a:cf:41:4b: - ed:e2:6f:03:78:c7:d3:67:14:d3:fe:b4:27:85:1a: - 02:57:97:b5:86:16:87:97:82:0d:e8:2b:ed:69:16: - c3:96:a6:c4:47:b8:5d:c1:76:a9:02:9d:28:52:c9: - d9:93:f1:b2:da:18:7c:0a:78:bd:1c:3f:12:4f:e5: - 3f:ee:5b:b7:b3:3c:a5:b6:55:65:c6:33:09:b0:45: - a4:49:f5:d8:f6:54:25:2e:db:4b:c3:65:64:86:16: - bf:73:8e:78:e8:25:cc:c1:b4:7d:d1:b1:55:20:a4: - ae:de:b8:41:ee:43:9a:48:33:d4:12:39:a0:a8:fb: - 91:4f:47:bb:3b:d6:75:3b:62:a6:c5:5a:d7:7e:36: - 10:bc:3a:0d:46:e3:de:c1:df:50:d0:94:77:22:17: - a0:51:9b:af:dc:34:60:26:1d:a0:04:ac:a5:06:04: - 57:64:1a:d1:4f:1c:19:f8:be:4b:68:12:0f:5f:c3: - 12:40:64:bb:c6:a6:84:d9:5a:b0:78:1d:1b:11:c1: - 54:e6:15:d1:af:2b:e2:57:2b:6d:b9:c4:5d:b7:01: - 74:57:a7:fb:27:85:2b:92:15:e2:e3:13:97:29:c8: - 92:7b - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Subject Alternative Name: - DNS:fakehostname - X509v3 Key Usage: critical - Digital Signature, Key Encipherment - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Basic Constraints: critical - CA:FALSE - X509v3 Subject Key Identifier: - F8:76:79:CB:11:85:F0:46:E5:95:E6:7E:69:CB:12:5E:4E:AA:EC:4D - X509v3 Authority Key Identifier: - keyid:9A:CF:CF:6E:EB:71:3D:DB:3C:F1:AE:88:6B:56:72:03:CB:08:A7:48 - DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server - serial:82:ED:BF:41:C8:80:91:9B - - Authority Information Access: - CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer - OCSP - URI:http://testca.pythontest.net/testca/ocsp/ - - X509v3 CRL Distribution Points: - - Full Name: - URI:http://testca.pythontest.net/testca/revocation.crl - - Signature Algorithm: sha1WithRSAEncryption - 6d:50:8d:fb:ee:4e:93:8b:eb:47:56:ba:38:cc:80:e1:9d:c7: - e1:9e:1f:9c:22:0c:d2:08:9b:ed:bf:31:d9:00:ee:af:8c:56: - 78:92:d1:7c:ba:4e:81:7f:82:1f:f4:68:99:86:91:c6:cb:57: - d3:b9:41:12:fa:75:53:fd:22:32:21:50:af:6b:4c:b1:34:36: - d1:a8:25:0a:d0:f0:f8:81:7d:69:58:6e:af:e3:d2:c4:32:87: - 79:d7:cd:ad:0c:56:f3:15:27:10:0c:f9:57:59:53:00:ed:af: - 5d:4d:07:86:7a:e5:f3:97:88:bc:86:b4:f1:17:46:33:55:28: - 66:7b:70:d3:a5:12:b9:4f:c7:ed:e6:13:20:2d:f0:9e:ec:17: - 64:cf:fd:13:14:1b:76:ba:64:ac:c5:51:b6:cd:13:0a:93:b1: - fd:43:09:a0:0b:44:6c:77:45:43:0b:e5:ed:70:b2:76:dc:08: - 4a:5b:73:5f:c1:fc:7f:63:70:f8:b9:ca:3c:98:06:5f:fd:98: - d1:e4:e6:61:5f:09:8f:6c:18:86:98:9c:cb:3f:73:7b:3f:38: - f5:a7:09:20:ee:a5:63:1c:ff:8b:a6:d1:8c:e8:f4:84:3d:99: - 38:0f:cc:e0:52:03:f9:18:05:23:76:39:de:52:ce:8e:fb:a6: - 6e:f5:4f:c3 ------BEGIN CERTIFICATE----- -MIIE9zCCA9+gAwIBAgIJAILtv0HIgJGdMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV -BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW -MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODAxMTkxOTA5MDZaFw0yNzExMjgx -OTA5MDZaMGIxCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj -MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xFTATBgNVBAMMDGZh -a2Vob3N0bmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMf/vqFk -BkfhxJUKZVlrkcSooKMIqtw/AEQKz0FL7eJvA3jH02cU0/60J4UaAleXtYYWh5eC -Degr7WkWw5amxEe4XcF2qQKdKFLJ2ZPxstoYfAp4vRw/Ek/lP+5bt7M8pbZVZcYz -CbBFpEn12PZUJS7bS8NlZIYWv3OOeOglzMG0fdGxVSCkrt64Qe5Dmkgz1BI5oKj7 -kU9HuzvWdTtipsVa1342ELw6DUbj3sHfUNCUdyIXoFGbr9w0YCYdoASspQYEV2Qa -0U8cGfi+S2gSD1/DEkBku8amhNlasHgdGxHBVOYV0a8r4lcrbbnEXbcBdFen+yeF -K5IV4uMTlynIknsCAwEAAaOCAcMwggG/MBcGA1UdEQQQMA6CDGZha2Vob3N0bmFt -ZTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC -MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPh2ecsRhfBG5ZXmfmnLEl5OquxNMH0G -A1UdIwR2MHSAFJrPz27rcT3bPPGuiGtWcgPLCKdIoVGkTzBNMQswCQYDVQQGEwJY -WTEmMCQGA1UECgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNV -BAMMDW91ci1jYS1zZXJ2ZXKCCQCC7b9ByICRmzCBgwYIKwYBBQUHAQEEdzB1MDwG -CCsGAQUFBzAChjBodHRwOi8vdGVzdGNhLnB5dGhvbnRlc3QubmV0L3Rlc3RjYS9w -eWNhY2VydC5jZXIwNQYIKwYBBQUHMAGGKWh0dHA6Ly90ZXN0Y2EucHl0aG9udGVz -dC5uZXQvdGVzdGNhL29jc3AvMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly90ZXN0 -Y2EucHl0aG9udGVzdC5uZXQvdGVzdGNhL3Jldm9jYXRpb24uY3JsMA0GCSqGSIb3 -DQEBBQUAA4IBAQBtUI377k6Ti+tHVro4zIDhncfhnh+cIgzSCJvtvzHZAO6vjFZ4 -ktF8uk6Bf4If9GiZhpHGy1fTuUES+nVT/SIyIVCva0yxNDbRqCUK0PD4gX1pWG6v -49LEMod5182tDFbzFScQDPlXWVMA7a9dTQeGeuXzl4i8hrTxF0YzVShme3DTpRK5 -T8ft5hMgLfCe7Bdkz/0TFBt2umSsxVG2zRMKk7H9QwmgC0Rsd0VDC+XtcLJ23AhK -W3Nfwfx/Y3D4uco8mAZf/ZjR5OZhXwmPbBiGmJzLP3N7Pzj1pwkg7qVjHP+LptGM -6PSEPZk4D8zgUgP5GAUjdjneUs6O+6Zu9U/D ------END CERTIFICATE----- diff --git a/Lib/test/keycertecc.pem b/Lib/test/keycertecc.pem deleted file mode 100644 index deb484f9920..00000000000 --- a/Lib/test/keycertecc.pem +++ /dev/null @@ -1,96 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDe3QWmhZX07HZbntz4 -CFqAOaoYMdYwD7Z3WPNIc2zR7p4D6BMOa7NAWjLV5A7CUw6hZANiAAQ5IVKzLLz4 -LCfcpy6fMOp+jk5KwywsU3upPtjA6E3UetxPcfnnv+gghRyDAYLN2OVqZgLMEmUo -F1j1SM1QrbhHIuNcVxI9gPPMdumcNFSz/hqxrBRtA/8Z2gywczdNLjc= ------END PRIVATE KEY----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 82:ed:bf:41:c8:80:91:9e - Signature Algorithm: sha1WithRSAEncryption - Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server - Validity - Not Before: Jan 19 19:09:06 2018 GMT - Not After : Nov 28 19:09:06 2027 GMT - Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=localhost-ecc - Subject Public Key Info: - Public Key Algorithm: id-ecPublicKey - Public-Key: (384 bit) - pub: - 04:39:21:52:b3:2c:bc:f8:2c:27:dc:a7:2e:9f:30: - ea:7e:8e:4e:4a:c3:2c:2c:53:7b:a9:3e:d8:c0:e8: - 4d:d4:7a:dc:4f:71:f9:e7:bf:e8:20:85:1c:83:01: - 82:cd:d8:e5:6a:66:02:cc:12:65:28:17:58:f5:48: - cd:50:ad:b8:47:22:e3:5c:57:12:3d:80:f3:cc:76: - e9:9c:34:54:b3:fe:1a:b1:ac:14:6d:03:ff:19:da: - 0c:b0:73:37:4d:2e:37 - ASN1 OID: secp384r1 - NIST CURVE: P-384 - X509v3 extensions: - X509v3 Subject Alternative Name: - DNS:localhost-ecc - X509v3 Key Usage: critical - Digital Signature, Key Encipherment - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Basic Constraints: critical - CA:FALSE - X509v3 Subject Key Identifier: - 33:23:0E:15:04:83:2E:3D:BF:DA:81:6D:10:38:80:C3:C2:B0:A4:74 - X509v3 Authority Key Identifier: - keyid:9A:CF:CF:6E:EB:71:3D:DB:3C:F1:AE:88:6B:56:72:03:CB:08:A7:48 - DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server - serial:82:ED:BF:41:C8:80:91:9B - - Authority Information Access: - CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer - OCSP - URI:http://testca.pythontest.net/testca/ocsp/ - - X509v3 CRL Distribution Points: - - Full Name: - URI:http://testca.pythontest.net/testca/revocation.crl - - Signature Algorithm: sha1WithRSAEncryption - 3b:6f:97:af:7e:5f:e0:14:34:ed:57:7e:de:ce:c4:85:1e:aa: - 84:52:94:7c:e5:ce:e9:9c:88:8b:ad:b5:4d:16:ac:af:81:ea: - b8:a2:e2:50:2e:cb:e9:11:bd:1b:a6:3f:0c:a2:d7:7b:67:72: - b3:43:16:ad:c6:87:ac:6e:ac:47:78:ef:2f:8c:86:e8:9b:d1: - 43:8c:c1:7a:91:30:e9:14:d6:9f:41:8b:9b:0b:24:9b:78:86: - 11:8a:fc:2b:cd:c9:13:ee:90:4f:14:33:51:a3:c4:9e:d6:06: - 48:f5:41:12:af:f0:f2:71:40:78:f5:96:c2:5d:cf:e1:38:ff: - bf:10:eb:74:2f:c2:23:21:3e:27:f5:f1:f2:af:2c:62:82:31: - 00:c8:96:1b:c3:7e:8d:71:89:e7:40:b5:67:1a:33:fb:c0:8b: - 96:0c:36:78:25:27:82:d8:27:27:52:0f:f7:69:cd:ff:2b:92: - 10:d3:d2:0a:db:65:ed:af:90:eb:db:76:f3:8a:7a:13:9e:c6: - 33:57:15:42:06:13:d6:54:49:fa:84:a7:0e:1d:14:72:ca:19: - 8e:2b:aa:a4:02:54:3c:f6:1c:23:81:7a:59:54:b0:92:65:72: - c8:e5:ba:9f:03:4e:30:f2:4d:45:85:e3:35:a8:b1:68:58:b9: - 3b:20:a3:eb ------BEGIN CERTIFICATE----- -MIIESzCCAzOgAwIBAgIJAILtv0HIgJGeMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV -BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW -MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODAxMTkxOTA5MDZaFw0yNzExMjgx -OTA5MDZaMGMxCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj -MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xFjAUBgNVBAMMDWxv -Y2FsaG9zdC1lY2MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQ5IVKzLLz4LCfcpy6f -MOp+jk5KwywsU3upPtjA6E3UetxPcfnnv+gghRyDAYLN2OVqZgLMEmUoF1j1SM1Q -rbhHIuNcVxI9gPPMdumcNFSz/hqxrBRtA/8Z2gywczdNLjejggHEMIIBwDAYBgNV -HREEETAPgg1sb2NhbGhvc3QtZWNjMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAU -BggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUMyMO -FQSDLj2/2oFtEDiAw8KwpHQwfQYDVR0jBHYwdIAUms/PbutxPds88a6Ia1ZyA8sI -p0ihUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg -Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcoIJAILtv0HIgJGb -MIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKGMGh0dHA6Ly90ZXN0Y2EucHl0 -aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNlcjA1BggrBgEFBQcwAYYpaHR0 -cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2Evb2NzcC8wQwYDVR0fBDww -OjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2EvcmV2 -b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQEFBQADggEBADtvl69+X+AUNO1Xft7OxIUe -qoRSlHzlzumciIuttU0WrK+B6rii4lAuy+kRvRumPwyi13tncrNDFq3Gh6xurEd4 -7y+Mhuib0UOMwXqRMOkU1p9Bi5sLJJt4hhGK/CvNyRPukE8UM1GjxJ7WBkj1QRKv -8PJxQHj1lsJdz+E4/78Q63QvwiMhPif18fKvLGKCMQDIlhvDfo1xiedAtWcaM/vA -i5YMNnglJ4LYJydSD/dpzf8rkhDT0grbZe2vkOvbdvOKehOexjNXFUIGE9ZUSfqE -pw4dFHLKGY4rqqQCVDz2HCOBellUsJJlcsjlup8DTjDyTUWF4zWosWhYuTsgo+s= ------END CERTIFICATE----- diff --git a/Lib/test/ssl_servers.py b/Lib/test/ssl_servers.py index a4bd7455d47..c45411f49d8 100644 --- a/Lib/test/ssl_servers.py +++ b/Lib/test/ssl_servers.py @@ -14,7 +14,7 @@ here = os.path.dirname(__file__) HOST = socket_helper.HOST -CERTFILE = os.path.join(here, 'keycert.pem') +CERTFILE = os.path.join(here, 'certdata/keycert.pem') # This one's based on HTTPServer, which is based on socketserver diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 9b631fb397a..abe8b9f54cd 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -22,11 +22,11 @@ here = os.path.dirname(__file__) # Self-signed cert file for 'localhost' -CERT_localhost = os.path.join(here, 'keycert.pem') +CERT_localhost = os.path.join(here, 'certdata/keycert.pem') # Self-signed cert file for 'fakehostname' -CERT_fakehostname = os.path.join(here, 'keycert2.pem') +CERT_fakehostname = os.path.join(here, 'certdata/keycert2.pem') # Self-signed cert file for self-signed.pythontest.net -CERT_selfsigned_pythontestdotnet = os.path.join(here, 'selfsigned_pythontestdotnet.pem') +CERT_selfsigned_pythontestdotnet = os.path.join(here, 'certdata/selfsigned_pythontestdotnet.pem') # constants for testing chunked encoding chunked_start = ( @@ -1733,8 +1733,6 @@ def test_networked_trusted_by_default_cert(self): h.close() self.assertIn('text/html', content_type) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_networked_good_cert(self): # We feed the server's cert as a validating cert import ssl diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py new file mode 100644 index 00000000000..e0b8870fa8c --- /dev/null +++ b/Lib/test/test_ssl.py @@ -0,0 +1,5581 @@ +# Test the support for SSL and sockets + +import sys +import unittest +import unittest.mock +from ast import literal_eval +from threading import Thread +from test import support +from test.support import import_helper +from test.support import os_helper +from test.support import socket_helper +from test.support import threading_helper +from test.support import warnings_helper +from test.support import asyncore +import array +import re +import socket +import select +import struct +import time +import enum +import gc +import http.client +import os +import errno +import pprint +import urllib.request +import threading +import traceback +import weakref +import platform +import sysconfig +import functools +from contextlib import nullcontext +try: + import ctypes +except ImportError: + ctypes = None + + +ssl = import_helper.import_module("ssl") +import _ssl + +from ssl import Purpose, TLSVersion, _TLSContentType, _TLSMessageType, _TLSAlertType + +Py_DEBUG_WIN32 = support.Py_DEBUG and sys.platform == 'win32' + +PROTOCOLS = sorted(ssl._PROTOCOL_NAMES) +HOST = socket_helper.HOST +IS_OPENSSL_3_0_0 = ssl.OPENSSL_VERSION_INFO >= (3, 0, 0) +PY_SSL_DEFAULT_CIPHERS = sysconfig.get_config_var('PY_SSL_DEFAULT_CIPHERS') + +PROTOCOL_TO_TLS_VERSION = {} +for proto, ver in ( + ("PROTOCOL_SSLv3", "SSLv3"), + ("PROTOCOL_TLSv1", "TLSv1"), + ("PROTOCOL_TLSv1_1", "TLSv1_1"), +): + try: + proto = getattr(ssl, proto) + ver = getattr(ssl.TLSVersion, ver) + except AttributeError: + continue + PROTOCOL_TO_TLS_VERSION[proto] = ver + +def data_file(*name): + return os.path.join(os.path.dirname(__file__), "certdata", *name) + +# The custom key and certificate files used in test_ssl are generated +# using Lib/test/certdata/make_ssl_certs.py. +# Other certificates are simply fetched from the internet servers they +# are meant to authenticate. + +CERTFILE = data_file("keycert.pem") +BYTES_CERTFILE = os.fsencode(CERTFILE) +ONLYCERT = data_file("ssl_cert.pem") +ONLYKEY = data_file("ssl_key.pem") +BYTES_ONLYCERT = os.fsencode(ONLYCERT) +BYTES_ONLYKEY = os.fsencode(ONLYKEY) +CERTFILE_PROTECTED = data_file("keycert.passwd.pem") +ONLYKEY_PROTECTED = data_file("ssl_key.passwd.pem") +KEY_PASSWORD = "somepass" +CAPATH = data_file("capath") +BYTES_CAPATH = os.fsencode(CAPATH) +CAFILE_NEURONIO = data_file("capath", "4e1295a3.0") +CAFILE_CACERT = data_file("capath", "5ed36f99.0") + +CERTFILE_INFO = { + 'issuer': ((('countryName', 'XY'),), + (('localityName', 'Castle Anthrax'),), + (('organizationName', 'Python Software Foundation'),), + (('commonName', 'localhost'),)), + 'notAfter': 'Jan 24 04:21:36 2043 GMT', + 'notBefore': 'Nov 25 04:21:36 2023 GMT', + 'serialNumber': '53E14833F7546C29256DD0F034F776C5E983004C', + 'subject': ((('countryName', 'XY'),), + (('localityName', 'Castle Anthrax'),), + (('organizationName', 'Python Software Foundation'),), + (('commonName', 'localhost'),)), + 'subjectAltName': (('DNS', 'localhost'),), + 'version': 3 +} + +# empty CRL +CRLFILE = data_file("revocation.crl") + +# Two keys and certs signed by the same CA (for SNI tests) +SIGNED_CERTFILE = data_file("keycert3.pem") +SINGED_CERTFILE_ONLY = data_file("cert3.pem") +SIGNED_CERTFILE_HOSTNAME = 'localhost' + +SIGNED_CERTFILE_INFO = { + 'OCSP': ('http://testca.pythontest.net/testca/ocsp/',), + 'caIssuers': ('http://testca.pythontest.net/testca/pycacert.cer',), + 'crlDistributionPoints': ('http://testca.pythontest.net/testca/revocation.crl',), + 'issuer': ((('countryName', 'XY'),), + (('organizationName', 'Python Software Foundation CA'),), + (('commonName', 'our-ca-server'),)), + 'notAfter': 'Oct 28 14:23:16 2037 GMT', + 'notBefore': 'Aug 29 14:23:16 2018 GMT', + 'serialNumber': 'CB2D80995A69525C', + 'subject': ((('countryName', 'XY'),), + (('localityName', 'Castle Anthrax'),), + (('organizationName', 'Python Software Foundation'),), + (('commonName', 'localhost'),)), + 'subjectAltName': (('DNS', 'localhost'),), + 'version': 3 +} + +SIGNED_CERTFILE2 = data_file("keycert4.pem") +SIGNED_CERTFILE2_HOSTNAME = 'fakehostname' +SIGNED_CERTFILE_ECC = data_file("keycertecc.pem") +SIGNED_CERTFILE_ECC_HOSTNAME = 'localhost-ecc' + +# A custom testcase, extracted from `rfc5280::aki::leaf-missing-aki` in x509-limbo: +# The leaf (server) certificate has no AKI, which is forbidden under RFC 5280. +# See: https://x509-limbo.com/testcases/rfc5280/#rfc5280akileaf-missing-aki +LEAF_MISSING_AKI_CERTFILE = data_file("leaf-missing-aki.keycert.pem") +LEAF_MISSING_AKI_CERTFILE_HOSTNAME = "example.com" +LEAF_MISSING_AKI_CA = data_file("leaf-missing-aki.ca.pem") + +# Same certificate as pycacert.pem, but without extra text in file +SIGNING_CA = data_file("capath", "ceff1710.0") +# cert with all kinds of subject alt names +ALLSANFILE = data_file("allsans.pem") +IDNSANSFILE = data_file("idnsans.pem") +NOSANFILE = data_file("nosan.pem") +NOSAN_HOSTNAME = 'localhost' + +REMOTE_HOST = "self-signed.pythontest.net" + +EMPTYCERT = data_file("nullcert.pem") +BADCERT = data_file("badcert.pem") +NONEXISTINGCERT = data_file("XXXnonexisting.pem") +BADKEY = data_file("badkey.pem") +NOKIACERT = data_file("nokia.pem") +NULLBYTECERT = data_file("nullbytecert.pem") +TALOS_INVALID_CRLDP = data_file("talos-2019-0758.pem") + +DHFILE = data_file("ffdh3072.pem") +BYTES_DHFILE = os.fsencode(DHFILE) + +# Not defined in all versions of OpenSSL +OP_NO_COMPRESSION = getattr(ssl, "OP_NO_COMPRESSION", 0) +OP_SINGLE_DH_USE = getattr(ssl, "OP_SINGLE_DH_USE", 0) +OP_SINGLE_ECDH_USE = getattr(ssl, "OP_SINGLE_ECDH_USE", 0) +OP_CIPHER_SERVER_PREFERENCE = getattr(ssl, "OP_CIPHER_SERVER_PREFERENCE", 0) +OP_ENABLE_MIDDLEBOX_COMPAT = getattr(ssl, "OP_ENABLE_MIDDLEBOX_COMPAT", 0) + +# Ubuntu has patched OpenSSL and changed behavior of security level 2 +# see https://bugs.python.org/issue41561#msg389003 +def is_ubuntu(): + try: + # Assume that any references of "ubuntu" implies Ubuntu-like distro + # The workaround is not required for 18.04, but doesn't hurt either. + with open("/etc/os-release", encoding="utf-8") as f: + return "ubuntu" in f.read() + except FileNotFoundError: + return False + +if is_ubuntu(): + def seclevel_workaround(*ctxs): + """"Lower security level to '1' and allow all ciphers for TLS 1.0/1""" + for ctx in ctxs: + if ( + hasattr(ctx, "minimum_version") and + ctx.minimum_version <= ssl.TLSVersion.TLSv1_1 and + ctx.security_level > 1 + ): + ctx.set_ciphers("@SECLEVEL=1:ALL") +else: + def seclevel_workaround(*ctxs): + pass + + +def has_tls_protocol(protocol): + """Check if a TLS protocol is available and enabled + + :param protocol: enum ssl._SSLMethod member or name + :return: bool + """ + if isinstance(protocol, str): + assert protocol.startswith('PROTOCOL_') + protocol = getattr(ssl, protocol, None) + if protocol is None: + return False + if protocol in { + ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS_SERVER, + ssl.PROTOCOL_TLS_CLIENT + }: + # auto-negotiate protocols are always available + return True + name = protocol.name + return has_tls_version(name[len('PROTOCOL_'):]) + + +@functools.lru_cache +def has_tls_version(version): + """Check if a TLS/SSL version is enabled + + :param version: TLS version name or ssl.TLSVersion member + :return: bool + """ + if isinstance(version, str): + version = ssl.TLSVersion.__members__[version] + + # check compile time flags like ssl.HAS_TLSv1_2 + if not getattr(ssl, f'HAS_{version.name}'): + return False + + if IS_OPENSSL_3_0_0 and version < ssl.TLSVersion.TLSv1_2: + # bpo43791: 3.0.0-alpha14 fails with TLSV1_ALERT_INTERNAL_ERROR + return False + + # check runtime and dynamic crypto policy settings. A TLS version may + # be compiled in but disabled by a policy or config option. + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + if ( + hasattr(ctx, 'minimum_version') and + ctx.minimum_version != ssl.TLSVersion.MINIMUM_SUPPORTED and + version < ctx.minimum_version + ): + return False + if ( + hasattr(ctx, 'maximum_version') and + ctx.maximum_version != ssl.TLSVersion.MAXIMUM_SUPPORTED and + version > ctx.maximum_version + ): + return False + + return True + + +def requires_tls_version(version): + """Decorator to skip tests when a required TLS version is not available + + :param version: TLS version name or ssl.TLSVersion member + :return: + """ + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kw): + if not has_tls_version(version): + raise unittest.SkipTest(f"{version} is not available.") + else: + return func(*args, **kw) + return wrapper + return decorator + + +def handle_error(prefix): + exc_format = ' '.join(traceback.format_exception(sys.exception())) + if support.verbose: + sys.stdout.write(prefix + exc_format) + + +def utc_offset(): #NOTE: ignore issues like #1647654 + # local time = utc time + utc offset + if time.daylight and time.localtime().tm_isdst > 0: + return -time.altzone # seconds + return -time.timezone + + +ignore_deprecation = warnings_helper.ignore_warnings( + category=DeprecationWarning +) + + +def test_wrap_socket(sock, *, + cert_reqs=ssl.CERT_NONE, ca_certs=None, + ciphers=None, certfile=None, keyfile=None, + **kwargs): + if not kwargs.get("server_side"): + kwargs["server_hostname"] = SIGNED_CERTFILE_HOSTNAME + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + else: + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + if cert_reqs is not None: + if cert_reqs == ssl.CERT_NONE: + context.check_hostname = False + context.verify_mode = cert_reqs + if ca_certs is not None: + context.load_verify_locations(ca_certs) + if certfile is not None or keyfile is not None: + context.load_cert_chain(certfile, keyfile) + if ciphers is not None: + context.set_ciphers(ciphers) + return context.wrap_socket(sock, **kwargs) + + +USE_SAME_TEST_CONTEXT = False +_TEST_CONTEXT = None + +def testing_context(server_cert=SIGNED_CERTFILE, *, server_chain=True): + """Create context + + client_context, server_context, hostname = testing_context() + """ + global _TEST_CONTEXT + if USE_SAME_TEST_CONTEXT: + if _TEST_CONTEXT is not None: + return _TEST_CONTEXT + + if server_cert == SIGNED_CERTFILE: + hostname = SIGNED_CERTFILE_HOSTNAME + elif server_cert == SIGNED_CERTFILE2: + hostname = SIGNED_CERTFILE2_HOSTNAME + elif server_cert == NOSANFILE: + hostname = NOSAN_HOSTNAME + else: + raise ValueError(server_cert) + + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + client_context.load_verify_locations(SIGNING_CA) + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + server_context.load_cert_chain(server_cert) + if server_chain: + server_context.load_verify_locations(SIGNING_CA) + + if USE_SAME_TEST_CONTEXT: + if _TEST_CONTEXT is not None: + _TEST_CONTEXT = client_context, server_context, hostname + + return client_context, server_context, hostname + + +class BasicSocketTests(unittest.TestCase): + + def test_constants(self): + ssl.CERT_NONE + ssl.CERT_OPTIONAL + ssl.CERT_REQUIRED + ssl.OP_CIPHER_SERVER_PREFERENCE + ssl.OP_SINGLE_DH_USE + ssl.OP_SINGLE_ECDH_USE + ssl.OP_NO_COMPRESSION + self.assertEqual(ssl.HAS_SNI, True) + self.assertEqual(ssl.HAS_ECDH, True) + self.assertEqual(ssl.HAS_TLSv1_2, True) + self.assertEqual(ssl.HAS_TLSv1_3, True) + ssl.OP_NO_SSLv2 + ssl.OP_NO_SSLv3 + ssl.OP_NO_TLSv1 + ssl.OP_NO_TLSv1_3 + ssl.OP_NO_TLSv1_1 + ssl.OP_NO_TLSv1_2 + self.assertEqual(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv23) + + def test_options(self): + # gh-106687: SSL options values are unsigned integer (uint64_t) + for name in dir(ssl): + if not name.startswith('OP_'): + continue + with self.subTest(option=name): + value = getattr(ssl, name) + self.assertGreaterEqual(value, 0, f"ssl.{name}") + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_ssl_types(self): + ssl_types = [ + _ssl._SSLContext, + _ssl._SSLSocket, + _ssl.MemoryBIO, + _ssl.Certificate, + _ssl.SSLSession, + _ssl.SSLError, + ] + for ssl_type in ssl_types: + with self.subTest(ssl_type=ssl_type): + with self.assertRaisesRegex(TypeError, "immutable type"): + ssl_type.value = None + support.check_disallow_instantiation(self, _ssl.Certificate) + + def test_private_init(self): + with self.assertRaisesRegex(TypeError, "public constructor"): + with socket.socket() as s: + ssl.SSLSocket(s) + + def test_str_for_enums(self): + # Make sure that the PROTOCOL_* constants have enum-like string + # reprs. + proto = ssl.PROTOCOL_TLS_CLIENT + self.assertEqual(repr(proto), '<_SSLMethod.PROTOCOL_TLS_CLIENT: %r>' % proto.value) + self.assertEqual(str(proto), str(proto.value)) + ctx = ssl.SSLContext(proto) + self.assertIs(ctx.protocol, proto) + + def test_random(self): + v = ssl.RAND_status() + if support.verbose: + sys.stdout.write("\n RAND_status is %d (%s)\n" + % (v, (v and "sufficient randomness") or + "insufficient randomness")) + + if v: + data = ssl.RAND_bytes(16) + self.assertEqual(len(data), 16) + else: + self.assertRaises(ssl.SSLError, ssl.RAND_bytes, 16) + + # negative num is invalid + self.assertRaises(ValueError, ssl.RAND_bytes, -5) + + ssl.RAND_add("this is a random string", 75.0) + ssl.RAND_add(b"this is a random bytes object", 75.0) + ssl.RAND_add(bytearray(b"this is a random bytearray object"), 75.0) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_parse_cert(self): + # note that this uses an 'unofficial' function in _ssl.c, + # provided solely for this test, to exercise the certificate + # parsing code + self.assertEqual( + ssl._ssl._test_decode_cert(CERTFILE), + CERTFILE_INFO + ) + self.assertEqual( + ssl._ssl._test_decode_cert(SIGNED_CERTFILE), + SIGNED_CERTFILE_INFO + ) + + # Issue #13034: the subjectAltName in some certificates + # (notably projects.developer.nokia.com:443) wasn't parsed + p = ssl._ssl._test_decode_cert(NOKIACERT) + if support.verbose: + sys.stdout.write("\n" + pprint.pformat(p) + "\n") + self.assertEqual(p['subjectAltName'], + (('DNS', 'projects.developer.nokia.com'), + ('DNS', 'projects.forum.nokia.com')) + ) + # extra OCSP and AIA fields + self.assertEqual(p['OCSP'], ('http://ocsp.verisign.com',)) + self.assertEqual(p['caIssuers'], + ('http://SVRIntl-G3-aia.verisign.com/SVRIntlG3.cer',)) + self.assertEqual(p['crlDistributionPoints'], + ('http://SVRIntl-G3-crl.verisign.com/SVRIntlG3.crl',)) + + def test_parse_cert_CVE_2019_5010(self): + p = ssl._ssl._test_decode_cert(TALOS_INVALID_CRLDP) + if support.verbose: + sys.stdout.write("\n" + pprint.pformat(p) + "\n") + self.assertEqual( + p, + { + 'issuer': ( + (('countryName', 'UK'),), (('commonName', 'cody-ca'),)), + 'notAfter': 'Jun 14 18:00:58 2028 GMT', + 'notBefore': 'Jun 18 18:00:58 2018 GMT', + 'serialNumber': '02', + 'subject': ((('countryName', 'UK'),), + (('commonName', + 'codenomicon-vm-2.test.lal.cisco.com'),)), + 'subjectAltName': ( + ('DNS', 'codenomicon-vm-2.test.lal.cisco.com'),), + 'version': 3 + } + ) + + def test_parse_cert_CVE_2013_4238(self): + p = ssl._ssl._test_decode_cert(NULLBYTECERT) + if support.verbose: + sys.stdout.write("\n" + pprint.pformat(p) + "\n") + subject = ((('countryName', 'US'),), + (('stateOrProvinceName', 'Oregon'),), + (('localityName', 'Beaverton'),), + (('organizationName', 'Python Software Foundation'),), + (('organizationalUnitName', 'Python Core Development'),), + (('commonName', 'null.python.org\x00example.org'),), + (('emailAddress', 'python-dev@python.org'),)) + self.assertEqual(p['subject'], subject) + self.assertEqual(p['issuer'], subject) + if ssl._OPENSSL_API_VERSION >= (0, 9, 8): + san = (('DNS', 'altnull.python.org\x00example.com'), + ('email', 'null@python.org\x00user@example.org'), + ('URI', 'http://null.python.org\x00http://example.org'), + ('IP Address', '192.0.2.1'), + ('IP Address', '2001:DB8:0:0:0:0:0:1')) + else: + # OpenSSL 0.9.7 doesn't support IPv6 addresses in subjectAltName + san = (('DNS', 'altnull.python.org\x00example.com'), + ('email', 'null@python.org\x00user@example.org'), + ('URI', 'http://null.python.org\x00http://example.org'), + ('IP Address', '192.0.2.1'), + ('IP Address', '<invalid>')) + + self.assertEqual(p['subjectAltName'], san) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_parse_all_sans(self): + p = ssl._ssl._test_decode_cert(ALLSANFILE) + self.assertEqual(p['subjectAltName'], + ( + ('DNS', 'allsans'), + ('othername', '<unsupported>'), + ('othername', '<unsupported>'), + ('email', 'user@example.org'), + ('DNS', 'www.example.org'), + ('DirName', + ((('countryName', 'XY'),), + (('localityName', 'Castle Anthrax'),), + (('organizationName', 'Python Software Foundation'),), + (('commonName', 'dirname example'),))), + ('URI', 'https://www.python.org/'), + ('IP Address', '127.0.0.1'), + ('IP Address', '0:0:0:0:0:0:0:1'), + ('Registered ID', '1.2.3.4.5') + ) + ) + + def test_DER_to_PEM(self): + with open(CAFILE_CACERT, 'r') as f: + pem = f.read() + d1 = ssl.PEM_cert_to_DER_cert(pem) + p2 = ssl.DER_cert_to_PEM_cert(d1) + d2 = ssl.PEM_cert_to_DER_cert(p2) + self.assertEqual(d1, d2) + if not p2.startswith(ssl.PEM_HEADER + '\n'): + self.fail("DER-to-PEM didn't include correct header:\n%r\n" % p2) + if not p2.endswith('\n' + ssl.PEM_FOOTER + '\n'): + self.fail("DER-to-PEM didn't include correct footer:\n%r\n" % p2) + + def test_openssl_version(self): + n = ssl.OPENSSL_VERSION_NUMBER + t = ssl.OPENSSL_VERSION_INFO + s = ssl.OPENSSL_VERSION + self.assertIsInstance(n, int) + self.assertIsInstance(t, tuple) + self.assertIsInstance(s, str) + # Some sanity checks follow + # >= 1.1.1 + self.assertGreaterEqual(n, 0x10101000) + # < 4.0 + self.assertLess(n, 0x40000000) + major, minor, fix, patch, status = t + self.assertGreaterEqual(major, 1) + self.assertLess(major, 4) + self.assertGreaterEqual(minor, 0) + self.assertLess(minor, 256) + self.assertGreaterEqual(fix, 0) + self.assertLess(fix, 256) + self.assertGreaterEqual(patch, 0) + self.assertLessEqual(patch, 63) + self.assertGreaterEqual(status, 0) + self.assertLessEqual(status, 15) + + libressl_ver = f"LibreSSL {major:d}" + if major >= 3: + # 3.x uses 0xMNN00PP0L + openssl_ver = f"OpenSSL {major:d}.{minor:d}.{patch:d}" + else: + openssl_ver = f"OpenSSL {major:d}.{minor:d}.{fix:d}" + self.assertTrue( + s.startswith((openssl_ver, libressl_ver, "AWS-LC")), + (s, t, hex(n)) + ) + + @support.cpython_only + def test_refcycle(self): + # Issue #7943: an SSL object doesn't create reference cycles with + # itself. + s = socket.socket(socket.AF_INET) + ss = test_wrap_socket(s) + wr = weakref.ref(ss) + with warnings_helper.check_warnings(("", ResourceWarning)): + del ss + self.assertEqual(wr(), None) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_wrapped_unconnected(self): + # Methods on an unconnected SSLSocket propagate the original + # OSError raise by the underlying socket object. + s = socket.socket(socket.AF_INET) + with test_wrap_socket(s) as ss: + self.assertRaises(OSError, ss.recv, 1) + self.assertRaises(OSError, ss.recv_into, bytearray(b'x')) + self.assertRaises(OSError, ss.recvfrom, 1) + self.assertRaises(OSError, ss.recvfrom_into, bytearray(b'x'), 1) + self.assertRaises(OSError, ss.send, b'x') + self.assertRaises(OSError, ss.sendto, b'x', ('0.0.0.0', 0)) + self.assertRaises(NotImplementedError, ss.dup) + self.assertRaises(NotImplementedError, ss.sendmsg, + [b'x'], (), 0, ('0.0.0.0', 0)) + self.assertRaises(NotImplementedError, ss.recvmsg, 100) + self.assertRaises(NotImplementedError, ss.recvmsg_into, + [bytearray(100)]) + + def test_timeout(self): + # Issue #8524: when creating an SSL socket, the timeout of the + # original socket should be retained. + for timeout in (None, 0.0, 5.0): + s = socket.socket(socket.AF_INET) + s.settimeout(timeout) + with test_wrap_socket(s) as ss: + self.assertEqual(timeout, ss.gettimeout()) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_openssl111_deprecations(self): + options = [ + ssl.OP_NO_TLSv1, + ssl.OP_NO_TLSv1_1, + ssl.OP_NO_TLSv1_2, + ssl.OP_NO_TLSv1_3 + ] + protocols = [ + ssl.PROTOCOL_TLSv1, + ssl.PROTOCOL_TLSv1_1, + ssl.PROTOCOL_TLSv1_2, + ssl.PROTOCOL_TLS + ] + versions = [ + ssl.TLSVersion.SSLv3, + ssl.TLSVersion.TLSv1, + ssl.TLSVersion.TLSv1_1, + ] + + for option in options: + with self.subTest(option=option): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + with self.assertWarns(DeprecationWarning) as cm: + ctx.options |= option + self.assertEqual( + 'ssl.OP_NO_SSL*/ssl.OP_NO_TLS* options are deprecated', + str(cm.warning) + ) + + for protocol in protocols: + if not has_tls_protocol(protocol): + continue + with self.subTest(protocol=protocol): + with self.assertWarns(DeprecationWarning) as cm: + ssl.SSLContext(protocol) + self.assertEqual( + f'ssl.{protocol.name} is deprecated', + str(cm.warning) + ) + + for version in versions: + if not has_tls_version(version): + continue + with self.subTest(version=version): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + with self.assertWarns(DeprecationWarning) as cm: + ctx.minimum_version = version + version_text = '%s.%s' % (version.__class__.__name__, version.name) + self.assertEqual( + f'ssl.{version_text} is deprecated', + str(cm.warning) + ) + + def bad_cert_test(self, certfile): + """Check that trying to use the given client certificate fails""" + certfile = os.path.join(os.path.dirname(__file__) or os.curdir, + "certdata", certfile) + sock = socket.socket() + self.addCleanup(sock.close) + with self.assertRaises(ssl.SSLError): + test_wrap_socket(sock, + certfile=certfile) + + def test_empty_cert(self): + """Wrapping with an empty cert file""" + self.bad_cert_test("nullcert.pem") + + def test_malformed_cert(self): + """Wrapping with a badly formatted certificate (syntax error)""" + self.bad_cert_test("badcert.pem") + + def test_malformed_key(self): + """Wrapping with a badly formatted key (syntax error)""" + self.bad_cert_test("badkey.pem") + + def test_server_side(self): + # server_hostname doesn't work for server sockets + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + with socket.socket() as sock: + self.assertRaises(ValueError, ctx.wrap_socket, sock, True, + server_hostname="some.hostname") + + def test_unknown_channel_binding(self): + # should raise ValueError for unknown type + s = socket.create_server(('127.0.0.1', 0)) + c = socket.socket(socket.AF_INET) + c.connect(s.getsockname()) + with test_wrap_socket(c, do_handshake_on_connect=False) as ss: + with self.assertRaises(ValueError): + ss.get_channel_binding("unknown-type") + s.close() + + @unittest.skipUnless("tls-unique" in ssl.CHANNEL_BINDING_TYPES, + "'tls-unique' channel binding not available") + def test_tls_unique_channel_binding(self): + # unconnected should return None for known type + s = socket.socket(socket.AF_INET) + with test_wrap_socket(s) as ss: + self.assertIsNone(ss.get_channel_binding("tls-unique")) + # the same for server-side + s = socket.socket(socket.AF_INET) + with test_wrap_socket(s, server_side=True, certfile=CERTFILE) as ss: + self.assertIsNone(ss.get_channel_binding("tls-unique")) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_dealloc_warn(self): + ss = test_wrap_socket(socket.socket(socket.AF_INET)) + r = repr(ss) + with self.assertWarns(ResourceWarning) as cm: + ss = None + support.gc_collect() + self.assertIn(r, str(cm.warning.args[0])) + + def test_get_default_verify_paths(self): + paths = ssl.get_default_verify_paths() + self.assertEqual(len(paths), 6) + self.assertIsInstance(paths, ssl.DefaultVerifyPaths) + + with os_helper.EnvironmentVarGuard() as env: + env["SSL_CERT_DIR"] = CAPATH + env["SSL_CERT_FILE"] = CERTFILE + paths = ssl.get_default_verify_paths() + self.assertEqual(paths.cafile, CERTFILE) + self.assertEqual(paths.capath, CAPATH) + + @unittest.skipUnless(sys.platform == "win32", "Windows specific") + def test_enum_certificates(self): + self.assertTrue(ssl.enum_certificates("CA")) + self.assertTrue(ssl.enum_certificates("ROOT")) + + self.assertRaises(TypeError, ssl.enum_certificates) + self.assertRaises(WindowsError, ssl.enum_certificates, "") + + trust_oids = set() + for storename in ("CA", "ROOT"): + store = ssl.enum_certificates(storename) + self.assertIsInstance(store, list) + for element in store: + self.assertIsInstance(element, tuple) + self.assertEqual(len(element), 3) + cert, enc, trust = element + self.assertIsInstance(cert, bytes) + self.assertIn(enc, {"x509_asn", "pkcs_7_asn"}) + self.assertIsInstance(trust, (frozenset, set, bool)) + if isinstance(trust, (frozenset, set)): + trust_oids.update(trust) + + serverAuth = "1.3.6.1.5.5.7.3.1" + self.assertIn(serverAuth, trust_oids) + + @unittest.skipUnless(sys.platform == "win32", "Windows specific") + def test_enum_crls(self): + self.assertTrue(ssl.enum_crls("CA")) + self.assertRaises(TypeError, ssl.enum_crls) + self.assertRaises(WindowsError, ssl.enum_crls, "") + + crls = ssl.enum_crls("CA") + self.assertIsInstance(crls, list) + for element in crls: + self.assertIsInstance(element, tuple) + self.assertEqual(len(element), 2) + self.assertIsInstance(element[0], bytes) + self.assertIn(element[1], {"x509_asn", "pkcs_7_asn"}) + + + def test_asn1object(self): + expected = (129, 'serverAuth', 'TLS Web Server Authentication', + '1.3.6.1.5.5.7.3.1') + + val = ssl._ASN1Object('1.3.6.1.5.5.7.3.1') + self.assertEqual(val, expected) + self.assertEqual(val.nid, 129) + self.assertEqual(val.shortname, 'serverAuth') + self.assertEqual(val.longname, 'TLS Web Server Authentication') + self.assertEqual(val.oid, '1.3.6.1.5.5.7.3.1') + self.assertIsInstance(val, ssl._ASN1Object) + self.assertRaises(ValueError, ssl._ASN1Object, 'serverAuth') + + val = ssl._ASN1Object.fromnid(129) + self.assertEqual(val, expected) + self.assertIsInstance(val, ssl._ASN1Object) + self.assertRaises(ValueError, ssl._ASN1Object.fromnid, -1) + with self.assertRaisesRegex(ValueError, "unknown NID 100000"): + ssl._ASN1Object.fromnid(100000) + for i in range(1000): + try: + obj = ssl._ASN1Object.fromnid(i) + except ValueError: + pass + else: + self.assertIsInstance(obj.nid, int) + self.assertIsInstance(obj.shortname, str) + self.assertIsInstance(obj.longname, str) + self.assertIsInstance(obj.oid, (str, type(None))) + + val = ssl._ASN1Object.fromname('TLS Web Server Authentication') + self.assertEqual(val, expected) + self.assertIsInstance(val, ssl._ASN1Object) + self.assertEqual(ssl._ASN1Object.fromname('serverAuth'), expected) + self.assertEqual(ssl._ASN1Object.fromname('1.3.6.1.5.5.7.3.1'), + expected) + with self.assertRaisesRegex(ValueError, "unknown object 'serverauth'"): + ssl._ASN1Object.fromname('serverauth') + + def test_purpose_enum(self): + val = ssl._ASN1Object('1.3.6.1.5.5.7.3.1') + self.assertIsInstance(ssl.Purpose.SERVER_AUTH, ssl._ASN1Object) + self.assertEqual(ssl.Purpose.SERVER_AUTH, val) + self.assertEqual(ssl.Purpose.SERVER_AUTH.nid, 129) + self.assertEqual(ssl.Purpose.SERVER_AUTH.shortname, 'serverAuth') + self.assertEqual(ssl.Purpose.SERVER_AUTH.oid, + '1.3.6.1.5.5.7.3.1') + + val = ssl._ASN1Object('1.3.6.1.5.5.7.3.2') + self.assertIsInstance(ssl.Purpose.CLIENT_AUTH, ssl._ASN1Object) + self.assertEqual(ssl.Purpose.CLIENT_AUTH, val) + self.assertEqual(ssl.Purpose.CLIENT_AUTH.nid, 130) + self.assertEqual(ssl.Purpose.CLIENT_AUTH.shortname, 'clientAuth') + self.assertEqual(ssl.Purpose.CLIENT_AUTH.oid, + '1.3.6.1.5.5.7.3.2') + + def test_unsupported_dtls(self): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.addCleanup(s.close) + with self.assertRaises(NotImplementedError) as cx: + test_wrap_socket(s, cert_reqs=ssl.CERT_NONE) + self.assertEqual(str(cx.exception), "only stream sockets are supported") + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + with self.assertRaises(NotImplementedError) as cx: + ctx.wrap_socket(s) + self.assertEqual(str(cx.exception), "only stream sockets are supported") + + def cert_time_ok(self, timestring, timestamp): + self.assertEqual(ssl.cert_time_to_seconds(timestring), timestamp) + + def cert_time_fail(self, timestring): + with self.assertRaises(ValueError): + ssl.cert_time_to_seconds(timestring) + + @unittest.skipUnless(utc_offset(), + 'local time needs to be different from UTC') + def test_cert_time_to_seconds_timezone(self): + # Issue #19940: ssl.cert_time_to_seconds() returns wrong + # results if local timezone is not UTC + self.cert_time_ok("May 9 00:00:00 2007 GMT", 1178668800.0) + self.cert_time_ok("Jan 5 09:34:43 2018 GMT", 1515144883.0) + + def test_cert_time_to_seconds(self): + timestring = "Jan 5 09:34:43 2018 GMT" + ts = 1515144883.0 + self.cert_time_ok(timestring, ts) + # accept keyword parameter, assert its name + self.assertEqual(ssl.cert_time_to_seconds(cert_time=timestring), ts) + # accept both %e and %d (space or zero generated by strftime) + self.cert_time_ok("Jan 05 09:34:43 2018 GMT", ts) + # case-insensitive + self.cert_time_ok("JaN 5 09:34:43 2018 GmT", ts) + self.cert_time_fail("Jan 5 09:34 2018 GMT") # no seconds + self.cert_time_fail("Jan 5 09:34:43 2018") # no GMT + self.cert_time_fail("Jan 5 09:34:43 2018 UTC") # not GMT timezone + self.cert_time_fail("Jan 35 09:34:43 2018 GMT") # invalid day + self.cert_time_fail("Jon 5 09:34:43 2018 GMT") # invalid month + self.cert_time_fail("Jan 5 24:00:00 2018 GMT") # invalid hour + self.cert_time_fail("Jan 5 09:60:43 2018 GMT") # invalid minute + + newyear_ts = 1230768000.0 + # leap seconds + self.cert_time_ok("Dec 31 23:59:60 2008 GMT", newyear_ts) + # same timestamp + self.cert_time_ok("Jan 1 00:00:00 2009 GMT", newyear_ts) + + self.cert_time_ok("Jan 5 09:34:59 2018 GMT", 1515144899) + # allow 60th second (even if it is not a leap second) + self.cert_time_ok("Jan 5 09:34:60 2018 GMT", 1515144900) + # allow 2nd leap second for compatibility with time.strptime() + self.cert_time_ok("Jan 5 09:34:61 2018 GMT", 1515144901) + self.cert_time_fail("Jan 5 09:34:62 2018 GMT") # invalid seconds + + # no special treatment for the special value: + # 99991231235959Z (rfc 5280) + self.cert_time_ok("Dec 31 23:59:59 9999 GMT", 253402300799.0) + + @support.run_with_locale('LC_ALL', '') + def test_cert_time_to_seconds_locale(self): + # `cert_time_to_seconds()` should be locale independent + + def local_february_name(): + return time.strftime('%b', (1, 2, 3, 4, 5, 6, 0, 0, 0)) + + if local_february_name().lower() == 'feb': + self.skipTest("locale-specific month name needs to be " + "different from C locale") + + # locale-independent + self.cert_time_ok("Feb 9 00:00:00 2007 GMT", 1170979200.0) + self.cert_time_fail(local_february_name() + " 9 00:00:00 2007 GMT") + + def test_connect_ex_error(self): + server = socket.socket(socket.AF_INET) + self.addCleanup(server.close) + port = socket_helper.bind_port(server) # Reserve port but don't listen + s = test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED) + self.addCleanup(s.close) + rc = s.connect_ex((HOST, port)) + # Issue #19919: Windows machines or VMs hosted on Windows + # machines sometimes return EWOULDBLOCK. + errors = ( + errno.ECONNREFUSED, errno.EHOSTUNREACH, errno.ETIMEDOUT, + errno.EWOULDBLOCK, + ) + self.assertIn(rc, errors) + + @unittest.skip("TODO: RUSTPYTHON; hangs") + def test_read_write_zero(self): + # empty reads and writes now work, bpo-42854, bpo-31711 + client_context, server_context, hostname = testing_context() + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + self.assertEqual(s.recv(0), b"") + self.assertEqual(s.send(b""), 0) + + +class ContextTests(unittest.TestCase): + + def test_constructor(self): + for protocol in PROTOCOLS: + if has_tls_protocol(protocol): + with warnings_helper.check_warnings(): + ctx = ssl.SSLContext(protocol) + self.assertEqual(ctx.protocol, protocol) + with warnings_helper.check_warnings(): + ctx = ssl.SSLContext() + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS) + self.assertRaises(ValueError, ssl.SSLContext, -1) + self.assertRaises(ValueError, ssl.SSLContext, 42) + + def test_ciphers(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.set_ciphers("ALL") + ctx.set_ciphers("DEFAULT") + with self.assertRaisesRegex(ssl.SSLError, "No cipher can be selected"): + ctx.set_ciphers("^$:,;?*'dorothyx") + + @unittest.skipUnless(PY_SSL_DEFAULT_CIPHERS == 1, + "Test applies only to Python default ciphers") + def test_python_ciphers(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ciphers = ctx.get_ciphers() + for suite in ciphers: + name = suite['name'] + self.assertNotIn("PSK", name) + self.assertNotIn("SRP", name) + self.assertNotIn("MD5", name) + self.assertNotIn("RC4", name) + self.assertNotIn("3DES", name) + + def test_get_ciphers(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.set_ciphers('AESGCM') + names = set(d['name'] for d in ctx.get_ciphers()) + expected = { + 'AES128-GCM-SHA256', + 'ECDHE-ECDSA-AES128-GCM-SHA256', + 'ECDHE-RSA-AES128-GCM-SHA256', + 'DHE-RSA-AES128-GCM-SHA256', + 'AES256-GCM-SHA384', + 'ECDHE-ECDSA-AES256-GCM-SHA384', + 'ECDHE-RSA-AES256-GCM-SHA384', + 'DHE-RSA-AES256-GCM-SHA384', + } + intersection = names.intersection(expected) + self.assertGreaterEqual( + len(intersection), 2, f"\ngot: {sorted(names)}\nexpected: {sorted(expected)}" + ) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_options(self): + # Test default SSLContext options + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + # OP_ALL | OP_NO_SSLv2 | OP_NO_SSLv3 is the default value + default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3) + # SSLContext also enables these by default + default |= (OP_NO_COMPRESSION | OP_CIPHER_SERVER_PREFERENCE | + OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE | + OP_ENABLE_MIDDLEBOX_COMPAT) + self.assertEqual(default, ctx.options) + + # disallow TLSv1 + with warnings_helper.check_warnings(): + ctx.options |= ssl.OP_NO_TLSv1 + self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options) + + # allow TLSv1 + with warnings_helper.check_warnings(): + ctx.options = (ctx.options & ~ssl.OP_NO_TLSv1) + self.assertEqual(default, ctx.options) + + # clear all options + ctx.options = 0 + # Ubuntu has OP_NO_SSLv3 forced on by default + self.assertEqual(0, ctx.options & ~ssl.OP_NO_SSLv3) + + # invalid options + with self.assertRaises(OverflowError): + ctx.options = -1 + with self.assertRaises(OverflowError): + ctx.options = 2 ** 100 + with self.assertRaises(TypeError): + ctx.options = "abc" + + def test_verify_mode_protocol(self): + with warnings_helper.check_warnings(): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS) + # Default value + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + ctx.verify_mode = ssl.CERT_OPTIONAL + self.assertEqual(ctx.verify_mode, ssl.CERT_OPTIONAL) + ctx.verify_mode = ssl.CERT_REQUIRED + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + ctx.verify_mode = ssl.CERT_NONE + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + with self.assertRaises(TypeError): + ctx.verify_mode = None + with self.assertRaises(ValueError): + ctx.verify_mode = 42 + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + self.assertFalse(ctx.check_hostname) + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + self.assertTrue(ctx.check_hostname) + + def test_hostname_checks_common_name(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertTrue(ctx.hostname_checks_common_name) + if ssl.HAS_NEVER_CHECK_COMMON_NAME: + ctx.hostname_checks_common_name = True + self.assertTrue(ctx.hostname_checks_common_name) + ctx.hostname_checks_common_name = False + self.assertFalse(ctx.hostname_checks_common_name) + ctx.hostname_checks_common_name = True + self.assertTrue(ctx.hostname_checks_common_name) + else: + with self.assertRaises(AttributeError): + ctx.hostname_checks_common_name = True + + @ignore_deprecation + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_min_max_version(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + # OpenSSL default is MINIMUM_SUPPORTED, however some vendors like + # Fedora override the setting to TLS 1.0. + minimum_range = { + # stock OpenSSL + ssl.TLSVersion.MINIMUM_SUPPORTED, + # Fedora 29 uses TLS 1.0 by default + ssl.TLSVersion.TLSv1, + # RHEL 8 uses TLS 1.2 by default + ssl.TLSVersion.TLSv1_2 + } + maximum_range = { + # stock OpenSSL + ssl.TLSVersion.MAXIMUM_SUPPORTED, + # Fedora 32 uses TLS 1.3 by default + ssl.TLSVersion.TLSv1_3 + } + + self.assertIn( + ctx.minimum_version, minimum_range + ) + self.assertIn( + ctx.maximum_version, maximum_range + ) + + ctx.minimum_version = ssl.TLSVersion.TLSv1_1 + ctx.maximum_version = ssl.TLSVersion.TLSv1_2 + self.assertEqual( + ctx.minimum_version, ssl.TLSVersion.TLSv1_1 + ) + self.assertEqual( + ctx.maximum_version, ssl.TLSVersion.TLSv1_2 + ) + + ctx.minimum_version = ssl.TLSVersion.MINIMUM_SUPPORTED + ctx.maximum_version = ssl.TLSVersion.TLSv1 + self.assertEqual( + ctx.minimum_version, ssl.TLSVersion.MINIMUM_SUPPORTED + ) + self.assertEqual( + ctx.maximum_version, ssl.TLSVersion.TLSv1 + ) + + ctx.maximum_version = ssl.TLSVersion.MAXIMUM_SUPPORTED + self.assertEqual( + ctx.maximum_version, ssl.TLSVersion.MAXIMUM_SUPPORTED + ) + + ctx.maximum_version = ssl.TLSVersion.MINIMUM_SUPPORTED + self.assertIn( + ctx.maximum_version, + {ssl.TLSVersion.TLSv1, ssl.TLSVersion.TLSv1_1, ssl.TLSVersion.SSLv3} + ) + + ctx.minimum_version = ssl.TLSVersion.MAXIMUM_SUPPORTED + self.assertIn( + ctx.minimum_version, + {ssl.TLSVersion.TLSv1_2, ssl.TLSVersion.TLSv1_3} + ) + + with self.assertRaises(ValueError): + ctx.minimum_version = 42 + + if has_tls_protocol(ssl.PROTOCOL_TLSv1_1): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_1) + + self.assertIn( + ctx.minimum_version, minimum_range + ) + self.assertEqual( + ctx.maximum_version, ssl.TLSVersion.MAXIMUM_SUPPORTED + ) + with self.assertRaises(ValueError): + ctx.minimum_version = ssl.TLSVersion.MINIMUM_SUPPORTED + with self.assertRaises(ValueError): + ctx.maximum_version = ssl.TLSVersion.TLSv1 + + @unittest.skipUnless( + hasattr(ssl.SSLContext, 'security_level'), + "requires OpenSSL >= 1.1.0" + ) + def test_security_level(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + # The default security callback allows for levels between 0-5 + # with OpenSSL defaulting to 1, however some vendors override the + # default value (e.g. Debian defaults to 2) + security_level_range = { + 0, + 1, # OpenSSL default + 2, # Debian + 3, + 4, + 5, + } + self.assertIn(ctx.security_level, security_level_range) + + def test_verify_flags(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + # default value + tf = getattr(ssl, "VERIFY_X509_TRUSTED_FIRST", 0) + self.assertEqual(ctx.verify_flags, ssl.VERIFY_DEFAULT | tf) + ctx.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF + self.assertEqual(ctx.verify_flags, ssl.VERIFY_CRL_CHECK_LEAF) + ctx.verify_flags = ssl.VERIFY_CRL_CHECK_CHAIN + self.assertEqual(ctx.verify_flags, ssl.VERIFY_CRL_CHECK_CHAIN) + ctx.verify_flags = ssl.VERIFY_DEFAULT + self.assertEqual(ctx.verify_flags, ssl.VERIFY_DEFAULT) + ctx.verify_flags = ssl.VERIFY_ALLOW_PROXY_CERTS + self.assertEqual(ctx.verify_flags, ssl.VERIFY_ALLOW_PROXY_CERTS) + # supports any value + ctx.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF | ssl.VERIFY_X509_STRICT + self.assertEqual(ctx.verify_flags, + ssl.VERIFY_CRL_CHECK_LEAF | ssl.VERIFY_X509_STRICT) + with self.assertRaises(TypeError): + ctx.verify_flags = None + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_load_cert_chain(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + # Combined key and cert in a single file + ctx.load_cert_chain(CERTFILE, keyfile=None) + ctx.load_cert_chain(CERTFILE, keyfile=CERTFILE) + self.assertRaises(TypeError, ctx.load_cert_chain, keyfile=CERTFILE) + with self.assertRaises(OSError) as cm: + ctx.load_cert_chain(NONEXISTINGCERT) + self.assertEqual(cm.exception.errno, errno.ENOENT) + with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"): + ctx.load_cert_chain(BADCERT) + with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"): + ctx.load_cert_chain(EMPTYCERT) + # Separate key and cert + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ctx.load_cert_chain(ONLYCERT, ONLYKEY) + ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY) + ctx.load_cert_chain(certfile=BYTES_ONLYCERT, keyfile=BYTES_ONLYKEY) + with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"): + ctx.load_cert_chain(ONLYCERT) + with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"): + ctx.load_cert_chain(ONLYKEY) + with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"): + ctx.load_cert_chain(certfile=ONLYKEY, keyfile=ONLYCERT) + # Mismatching key and cert + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + # Allow for flexible libssl error messages. + regex = re.compile(r"""( + key values mismatch # OpenSSL + | + KEY_VALUES_MISMATCH # AWS-LC + )""", re.X) + with self.assertRaisesRegex(ssl.SSLError, regex): + ctx.load_cert_chain(CAFILE_CACERT, ONLYKEY) + # Password protected key and cert + ctx.load_cert_chain(CERTFILE_PROTECTED, password=KEY_PASSWORD) + ctx.load_cert_chain(CERTFILE_PROTECTED, password=KEY_PASSWORD.encode()) + ctx.load_cert_chain(CERTFILE_PROTECTED, + password=bytearray(KEY_PASSWORD.encode())) + ctx.load_cert_chain(ONLYCERT, ONLYKEY_PROTECTED, KEY_PASSWORD) + ctx.load_cert_chain(ONLYCERT, ONLYKEY_PROTECTED, KEY_PASSWORD.encode()) + ctx.load_cert_chain(ONLYCERT, ONLYKEY_PROTECTED, + bytearray(KEY_PASSWORD.encode())) + with self.assertRaisesRegex(TypeError, "should be a string"): + ctx.load_cert_chain(CERTFILE_PROTECTED, password=True) + with self.assertRaises(ssl.SSLError): + ctx.load_cert_chain(CERTFILE_PROTECTED, password="badpass") + with self.assertRaisesRegex(ValueError, "cannot be longer"): + # openssl has a fixed limit on the password buffer. + # PEM_BUFSIZE is generally set to 1kb. + # Return a string larger than this. + ctx.load_cert_chain(CERTFILE_PROTECTED, password=b'a' * 102400) + # Password callback + def getpass_unicode(): + return KEY_PASSWORD + def getpass_bytes(): + return KEY_PASSWORD.encode() + def getpass_bytearray(): + return bytearray(KEY_PASSWORD.encode()) + def getpass_badpass(): + return "badpass" + def getpass_huge(): + return b'a' * (1024 * 1024) + def getpass_bad_type(): + return 9 + def getpass_exception(): + raise Exception('getpass error') + class GetPassCallable: + def __call__(self): + return KEY_PASSWORD + def getpass(self): + return KEY_PASSWORD + ctx.load_cert_chain(CERTFILE_PROTECTED, password=getpass_unicode) + ctx.load_cert_chain(CERTFILE_PROTECTED, password=getpass_bytes) + ctx.load_cert_chain(CERTFILE_PROTECTED, password=getpass_bytearray) + ctx.load_cert_chain(CERTFILE_PROTECTED, password=GetPassCallable()) + ctx.load_cert_chain(CERTFILE_PROTECTED, + password=GetPassCallable().getpass) + with self.assertRaises(ssl.SSLError): + ctx.load_cert_chain(CERTFILE_PROTECTED, password=getpass_badpass) + with self.assertRaisesRegex(ValueError, "cannot be longer"): + ctx.load_cert_chain(CERTFILE_PROTECTED, password=getpass_huge) + with self.assertRaisesRegex(TypeError, "must return a string"): + ctx.load_cert_chain(CERTFILE_PROTECTED, password=getpass_bad_type) + with self.assertRaisesRegex(Exception, "getpass error"): + ctx.load_cert_chain(CERTFILE_PROTECTED, password=getpass_exception) + # Make sure the password function isn't called if it isn't needed + ctx.load_cert_chain(CERTFILE, password=getpass_exception) + + @threading_helper.requires_working_threading() + def test_load_cert_chain_thread_safety(self): + # gh-134698: _ssl detaches the thread state (and as such, + # releases the GIL and critical sections) around expensive + # OpenSSL calls. Unfortunately, OpenSSL structures aren't + # thread-safe, so executing these calls concurrently led + # to crashes. + ctx = ssl.create_default_context() + + def race(): + ctx.load_cert_chain(CERTFILE) + + threads = [threading.Thread(target=race) for _ in range(8)] + with threading_helper.catch_threading_exception() as cm: + with threading_helper.start_threads(threads): + pass + + self.assertIsNone(cm.exc_value) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_load_verify_locations(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ctx.load_verify_locations(CERTFILE) + ctx.load_verify_locations(cafile=CERTFILE, capath=None) + ctx.load_verify_locations(BYTES_CERTFILE) + ctx.load_verify_locations(cafile=BYTES_CERTFILE, capath=None) + self.assertRaises(TypeError, ctx.load_verify_locations) + self.assertRaises(TypeError, ctx.load_verify_locations, None, None, None) + with self.assertRaises(OSError) as cm: + ctx.load_verify_locations(NONEXISTINGCERT) + self.assertEqual(cm.exception.errno, errno.ENOENT) + with self.assertRaisesRegex(ssl.SSLError, "PEM (lib|routines)"): + ctx.load_verify_locations(BADCERT) + ctx.load_verify_locations(CERTFILE, CAPATH) + ctx.load_verify_locations(CERTFILE, capath=BYTES_CAPATH) + + # Issue #10989: crash if the second argument type is invalid + self.assertRaises(TypeError, ctx.load_verify_locations, None, True) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_load_verify_cadata(self): + # test cadata + with open(CAFILE_CACERT) as f: + cacert_pem = f.read() + cacert_der = ssl.PEM_cert_to_DER_cert(cacert_pem) + with open(CAFILE_NEURONIO) as f: + neuronio_pem = f.read() + neuronio_der = ssl.PEM_cert_to_DER_cert(neuronio_pem) + + # test PEM + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.cert_store_stats()["x509_ca"], 0) + ctx.load_verify_locations(cadata=cacert_pem) + self.assertEqual(ctx.cert_store_stats()["x509_ca"], 1) + ctx.load_verify_locations(cadata=neuronio_pem) + self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2) + # cert already in hash table + ctx.load_verify_locations(cadata=neuronio_pem) + self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2) + + # combined + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + combined = "\n".join((cacert_pem, neuronio_pem)) + ctx.load_verify_locations(cadata=combined) + self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2) + + # with junk around the certs + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + combined = ["head", cacert_pem, "other", neuronio_pem, "again", + neuronio_pem, "tail"] + ctx.load_verify_locations(cadata="\n".join(combined)) + self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2) + + # test DER + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.load_verify_locations(cadata=cacert_der) + ctx.load_verify_locations(cadata=neuronio_der) + self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2) + # cert already in hash table + ctx.load_verify_locations(cadata=cacert_der) + self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2) + + # combined + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + combined = b"".join((cacert_der, neuronio_der)) + ctx.load_verify_locations(cadata=combined) + self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2) + + # error cases + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertRaises(TypeError, ctx.load_verify_locations, cadata=object) + + with self.assertRaisesRegex( + ssl.SSLError, + "no start line: cadata does not contain a certificate" + ): + ctx.load_verify_locations(cadata="broken") + with self.assertRaisesRegex( + ssl.SSLError, + "not enough data: cadata does not contain a certificate" + ): + ctx.load_verify_locations(cadata=b"broken") + with self.assertRaises(ssl.SSLError): + ctx.load_verify_locations(cadata=cacert_der + b"A") + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_load_dh_params(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + try: + ctx.load_dh_params(DHFILE) + except RuntimeError: + if Py_DEBUG_WIN32: + self.skipTest("not supported on Win32 debug build") + raise + if os.name != 'nt': + ctx.load_dh_params(BYTES_DHFILE) + self.assertRaises(TypeError, ctx.load_dh_params) + self.assertRaises(TypeError, ctx.load_dh_params, None) + with self.assertRaises(FileNotFoundError) as cm: + ctx.load_dh_params(NONEXISTINGCERT) + self.assertEqual(cm.exception.errno, errno.ENOENT) + with self.assertRaises(ssl.SSLError) as cm: + ctx.load_dh_params(CERTFILE) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_session_stats(self): + for proto in {ssl.PROTOCOL_TLS_CLIENT, ssl.PROTOCOL_TLS_SERVER}: + ctx = ssl.SSLContext(proto) + self.assertEqual(ctx.session_stats(), { + 'number': 0, + 'connect': 0, + 'connect_good': 0, + 'connect_renegotiate': 0, + 'accept': 0, + 'accept_good': 0, + 'accept_renegotiate': 0, + 'hits': 0, + 'misses': 0, + 'timeouts': 0, + 'cache_full': 0, + }) + + def test_set_default_verify_paths(self): + # There's not much we can do to test that it acts as expected, + # so just check it doesn't crash or raise an exception. + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.set_default_verify_paths() + + @unittest.skipUnless(ssl.HAS_ECDH, "ECDH disabled on this OpenSSL build") + def test_set_ecdh_curve(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ctx.set_ecdh_curve("prime256v1") + ctx.set_ecdh_curve(b"prime256v1") + self.assertRaises(TypeError, ctx.set_ecdh_curve) + self.assertRaises(TypeError, ctx.set_ecdh_curve, None) + self.assertRaises(ValueError, ctx.set_ecdh_curve, "foo") + self.assertRaises(ValueError, ctx.set_ecdh_curve, b"foo") + + def test_sni_callback(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + + # set_servername_callback expects a callable, or None + self.assertRaises(TypeError, ctx.set_servername_callback) + self.assertRaises(TypeError, ctx.set_servername_callback, 4) + self.assertRaises(TypeError, ctx.set_servername_callback, "") + self.assertRaises(TypeError, ctx.set_servername_callback, ctx) + + def dummycallback(sock, servername, ctx): + pass + ctx.set_servername_callback(None) + ctx.set_servername_callback(dummycallback) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_sni_callback_refcycle(self): + # Reference cycles through the servername callback are detected + # and cleared. + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + def dummycallback(sock, servername, ctx, cycle=ctx): + pass + ctx.set_servername_callback(dummycallback) + wr = weakref.ref(ctx) + del ctx, dummycallback + gc.collect() + self.assertIs(wr(), None) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_cert_store_stats(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.cert_store_stats(), + {'x509_ca': 0, 'crl': 0, 'x509': 0}) + ctx.load_cert_chain(CERTFILE) + self.assertEqual(ctx.cert_store_stats(), + {'x509_ca': 0, 'crl': 0, 'x509': 0}) + ctx.load_verify_locations(CERTFILE) + self.assertEqual(ctx.cert_store_stats(), + {'x509_ca': 0, 'crl': 0, 'x509': 1}) + ctx.load_verify_locations(CAFILE_CACERT) + self.assertEqual(ctx.cert_store_stats(), + {'x509_ca': 1, 'crl': 0, 'x509': 2}) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_get_ca_certs(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.get_ca_certs(), []) + # CERTFILE is not flagged as X509v3 Basic Constraints: CA:TRUE + ctx.load_verify_locations(CERTFILE) + self.assertEqual(ctx.get_ca_certs(), []) + # but CAFILE_CACERT is a CA cert + ctx.load_verify_locations(CAFILE_CACERT) + self.assertEqual(ctx.get_ca_certs(), + [{'issuer': ((('organizationName', 'Root CA'),), + (('organizationalUnitName', 'http://www.cacert.org'),), + (('commonName', 'CA Cert Signing Authority'),), + (('emailAddress', 'support@cacert.org'),)), + 'notAfter': 'Mar 29 12:29:49 2033 GMT', + 'notBefore': 'Mar 30 12:29:49 2003 GMT', + 'serialNumber': '00', + 'crlDistributionPoints': ('https://www.cacert.org/revoke.crl',), + 'subject': ((('organizationName', 'Root CA'),), + (('organizationalUnitName', 'http://www.cacert.org'),), + (('commonName', 'CA Cert Signing Authority'),), + (('emailAddress', 'support@cacert.org'),)), + 'version': 3}]) + + with open(CAFILE_CACERT) as f: + pem = f.read() + der = ssl.PEM_cert_to_DER_cert(pem) + self.assertEqual(ctx.get_ca_certs(True), [der]) + + def test_load_default_certs(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.load_default_certs() + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.load_default_certs(ssl.Purpose.SERVER_AUTH) + ctx.load_default_certs() + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.load_default_certs(ssl.Purpose.CLIENT_AUTH) + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertRaises(TypeError, ctx.load_default_certs, None) + self.assertRaises(TypeError, ctx.load_default_certs, 'SERVER_AUTH') + + @unittest.skipIf(sys.platform == "win32", "not-Windows specific") + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_load_default_certs_env(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + with os_helper.EnvironmentVarGuard() as env: + env["SSL_CERT_DIR"] = CAPATH + env["SSL_CERT_FILE"] = CERTFILE + ctx.load_default_certs() + self.assertEqual(ctx.cert_store_stats(), {"crl": 0, "x509": 1, "x509_ca": 0}) + + @unittest.skipUnless(sys.platform == "win32", "Windows specific") + @unittest.skipIf(support.Py_DEBUG, + "Debug build does not share environment between CRTs") + def test_load_default_certs_env_windows(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.load_default_certs() + stats = ctx.cert_store_stats() + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + with os_helper.EnvironmentVarGuard() as env: + env["SSL_CERT_DIR"] = CAPATH + env["SSL_CERT_FILE"] = CERTFILE + ctx.load_default_certs() + stats["x509"] += 1 + self.assertEqual(ctx.cert_store_stats(), stats) + + def _assert_context_options(self, ctx): + self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) + if OP_NO_COMPRESSION != 0: + self.assertEqual(ctx.options & OP_NO_COMPRESSION, + OP_NO_COMPRESSION) + if OP_SINGLE_DH_USE != 0: + self.assertEqual(ctx.options & OP_SINGLE_DH_USE, + OP_SINGLE_DH_USE) + if OP_SINGLE_ECDH_USE != 0: + self.assertEqual(ctx.options & OP_SINGLE_ECDH_USE, + OP_SINGLE_ECDH_USE) + if OP_CIPHER_SERVER_PREFERENCE != 0: + self.assertEqual(ctx.options & OP_CIPHER_SERVER_PREFERENCE, + OP_CIPHER_SERVER_PREFERENCE) + self.assertEqual(ctx.options & ssl.OP_LEGACY_SERVER_CONNECT, + 0 if IS_OPENSSL_3_0_0 else ssl.OP_LEGACY_SERVER_CONNECT) + + def test_create_default_context(self): + ctx = ssl.create_default_context() + + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + self.assertEqual(ctx.verify_flags & ssl.VERIFY_X509_PARTIAL_CHAIN, + ssl.VERIFY_X509_PARTIAL_CHAIN) + self.assertEqual(ctx.verify_flags & ssl.VERIFY_X509_STRICT, + ssl.VERIFY_X509_STRICT) + self.assertTrue(ctx.check_hostname) + self._assert_context_options(ctx) + + with open(SIGNING_CA) as f: + cadata = f.read() + ctx = ssl.create_default_context(cafile=SIGNING_CA, capath=CAPATH, + cadata=cadata) + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + self._assert_context_options(ctx) + + ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_SERVER) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + self._assert_context_options(ctx) + + def test__create_stdlib_context(self): + ctx = ssl._create_stdlib_context() + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + self.assertFalse(ctx.check_hostname) + self._assert_context_options(ctx) + + if has_tls_protocol(ssl.PROTOCOL_TLSv1): + with warnings_helper.check_warnings(): + ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1) + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + self._assert_context_options(ctx) + + with warnings_helper.check_warnings(): + ctx = ssl._create_stdlib_context( + ssl.PROTOCOL_TLSv1_2, + cert_reqs=ssl.CERT_REQUIRED, + check_hostname=True + ) + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1_2) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + self.assertTrue(ctx.check_hostname) + self._assert_context_options(ctx) + + ctx = ssl._create_stdlib_context(purpose=ssl.Purpose.CLIENT_AUTH) + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_SERVER) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + self._assert_context_options(ctx) + + def test_check_hostname(self): + with warnings_helper.check_warnings(): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS) + self.assertFalse(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + + # Auto set CERT_REQUIRED + ctx.check_hostname = True + self.assertTrue(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_REQUIRED + self.assertFalse(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + + # Changing verify_mode does not affect check_hostname + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + ctx.check_hostname = False + self.assertFalse(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + # Auto set + ctx.check_hostname = True + self.assertTrue(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_OPTIONAL + ctx.check_hostname = False + self.assertFalse(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_OPTIONAL) + # keep CERT_OPTIONAL + ctx.check_hostname = True + self.assertTrue(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_OPTIONAL) + + # Cannot set CERT_NONE with check_hostname enabled + with self.assertRaises(ValueError): + ctx.verify_mode = ssl.CERT_NONE + ctx.check_hostname = False + self.assertFalse(ctx.check_hostname) + ctx.verify_mode = ssl.CERT_NONE + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + + def test_context_client_server(self): + # PROTOCOL_TLS_CLIENT has sane defaults + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertTrue(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + + # PROTOCOL_TLS_SERVER has different but also sane defaults + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + self.assertFalse(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_context_custom_class(self): + class MySSLSocket(ssl.SSLSocket): + pass + + class MySSLObject(ssl.SSLObject): + pass + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ctx.sslsocket_class = MySSLSocket + ctx.sslobject_class = MySSLObject + + with ctx.wrap_socket(socket.socket(), server_side=True) as sock: + self.assertIsInstance(sock, MySSLSocket) + obj = ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO(), server_side=True) + self.assertIsInstance(obj, MySSLObject) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_num_tickest(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + self.assertEqual(ctx.num_tickets, 2) + ctx.num_tickets = 1 + self.assertEqual(ctx.num_tickets, 1) + ctx.num_tickets = 0 + self.assertEqual(ctx.num_tickets, 0) + with self.assertRaises(ValueError): + ctx.num_tickets = -1 + with self.assertRaises(TypeError): + ctx.num_tickets = None + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.num_tickets, 2) + with self.assertRaises(ValueError): + ctx.num_tickets = 1 + + +class SSLErrorTests(unittest.TestCase): + + def test_str(self): + # The str() of a SSLError doesn't include the errno + e = ssl.SSLError(1, "foo") + self.assertEqual(str(e), "foo") + self.assertEqual(e.errno, 1) + # Same for a subclass + e = ssl.SSLZeroReturnError(1, "foo") + self.assertEqual(str(e), "foo") + self.assertEqual(e.errno, 1) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_lib_reason(self): + # Test the library and reason attributes + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + try: + with self.assertRaises(ssl.SSLError) as cm: + ctx.load_dh_params(CERTFILE) + except RuntimeError: + if Py_DEBUG_WIN32: + self.skipTest("not supported on Win32 debug build") + raise + + self.assertEqual(cm.exception.library, 'PEM') + regex = "(NO_START_LINE|UNSUPPORTED_PUBLIC_KEY_TYPE)" + self.assertRegex(cm.exception.reason, regex) + s = str(cm.exception) + self.assertTrue("NO_START_LINE" in s, s) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_subclass(self): + # Check that the appropriate SSLError subclass is raised + # (this only tests one of them) + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + with socket.create_server(("127.0.0.1", 0)) as s: + c = socket.create_connection(s.getsockname()) + c.setblocking(False) + with ctx.wrap_socket(c, False, do_handshake_on_connect=False) as c: + with self.assertRaises(ssl.SSLWantReadError) as cm: + c.do_handshake() + s = str(cm.exception) + self.assertTrue(s.startswith("The operation did not complete (read)"), s) + # For compatibility + self.assertEqual(cm.exception.errno, ssl.SSL_ERROR_WANT_READ) + + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_bad_server_hostname(self): + ctx = ssl.create_default_context() + with self.assertRaises(ValueError): + ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO(), + server_hostname="") + with self.assertRaises(ValueError): + ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO(), + server_hostname=".example.org") + with self.assertRaises(TypeError): + ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO(), + server_hostname="example.org\x00evil.com") + + +class MemoryBIOTests(unittest.TestCase): + + def test_read_write(self): + bio = ssl.MemoryBIO() + bio.write(b'foo') + self.assertEqual(bio.read(), b'foo') + self.assertEqual(bio.read(), b'') + bio.write(b'foo') + bio.write(b'bar') + self.assertEqual(bio.read(), b'foobar') + self.assertEqual(bio.read(), b'') + bio.write(b'baz') + self.assertEqual(bio.read(2), b'ba') + self.assertEqual(bio.read(1), b'z') + self.assertEqual(bio.read(1), b'') + + def test_eof(self): + bio = ssl.MemoryBIO() + self.assertFalse(bio.eof) + self.assertEqual(bio.read(), b'') + self.assertFalse(bio.eof) + bio.write(b'foo') + self.assertFalse(bio.eof) + bio.write_eof() + self.assertFalse(bio.eof) + self.assertEqual(bio.read(2), b'fo') + self.assertFalse(bio.eof) + self.assertEqual(bio.read(1), b'o') + self.assertTrue(bio.eof) + self.assertEqual(bio.read(), b'') + self.assertTrue(bio.eof) + + def test_pending(self): + bio = ssl.MemoryBIO() + self.assertEqual(bio.pending, 0) + bio.write(b'foo') + self.assertEqual(bio.pending, 3) + for i in range(3): + bio.read(1) + self.assertEqual(bio.pending, 3-i-1) + for i in range(3): + bio.write(b'x') + self.assertEqual(bio.pending, i+1) + bio.read() + self.assertEqual(bio.pending, 0) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_buffer_types(self): + bio = ssl.MemoryBIO() + bio.write(b'foo') + self.assertEqual(bio.read(), b'foo') + bio.write(bytearray(b'bar')) + self.assertEqual(bio.read(), b'bar') + bio.write(memoryview(b'baz')) + self.assertEqual(bio.read(), b'baz') + m = memoryview(bytearray(b'noncontig')) + noncontig_writable = m[::-2] + with self.assertRaises(BufferError): + bio.write(memoryview(noncontig_writable)) + + def test_error_types(self): + bio = ssl.MemoryBIO() + self.assertRaises(TypeError, bio.write, 'foo') + self.assertRaises(TypeError, bio.write, None) + self.assertRaises(TypeError, bio.write, True) + self.assertRaises(TypeError, bio.write, 1) + + +class SSLObjectTests(unittest.TestCase): + def test_private_init(self): + bio = ssl.MemoryBIO() + with self.assertRaisesRegex(TypeError, "public constructor"): + ssl.SSLObject(bio, bio) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_unwrap(self): + client_ctx, server_ctx, hostname = testing_context() + c_in = ssl.MemoryBIO() + c_out = ssl.MemoryBIO() + s_in = ssl.MemoryBIO() + s_out = ssl.MemoryBIO() + client = client_ctx.wrap_bio(c_in, c_out, server_hostname=hostname) + server = server_ctx.wrap_bio(s_in, s_out, server_side=True) + + # Loop on the handshake for a bit to get it settled + for _ in range(5): + try: + client.do_handshake() + except ssl.SSLWantReadError: + pass + if c_out.pending: + s_in.write(c_out.read()) + try: + server.do_handshake() + except ssl.SSLWantReadError: + pass + if s_out.pending: + c_in.write(s_out.read()) + # Now the handshakes should be complete (don't raise WantReadError) + client.do_handshake() + server.do_handshake() + + # Now if we unwrap one side unilaterally, it should send close-notify + # and raise WantReadError: + with self.assertRaises(ssl.SSLWantReadError): + client.unwrap() + + # But server.unwrap() does not raise, because it reads the client's + # close-notify: + s_in.write(c_out.read()) + server.unwrap() + + # And now that the client gets the server's close-notify, it doesn't + # raise either. + c_in.write(s_out.read()) + client.unwrap() + +class SimpleBackgroundTests(unittest.TestCase): + """Tests that connect to a simple server running in the background""" + + def setUp(self): + self.server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + self.server_context.load_cert_chain(SIGNED_CERTFILE) + server = ThreadedEchoServer(context=self.server_context) + self.enterContext(server) + self.server_addr = (HOST, server.port) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_connect(self): + with test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_NONE) as s: + s.connect(self.server_addr) + self.assertEqual({}, s.getpeercert()) + self.assertFalse(s.server_side) + + # this should succeed because we specify the root cert + with test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED, + ca_certs=SIGNING_CA) as s: + s.connect(self.server_addr) + self.assertTrue(s.getpeercert()) + self.assertFalse(s.server_side) + + def test_connect_fail(self): + # This should fail because we have no verification certs. Connection + # failure crashes ThreadedEchoServer, so run this in an independent + # test method. + s = test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED) + self.addCleanup(s.close) + # Allow for flexible libssl error messages. + regex = re.compile(r"""( + certificate verify failed # OpenSSL + | + CERTIFICATE_VERIFY_FAILED # AWS-LC + )""", re.X) + self.assertRaisesRegex(ssl.SSLError, regex, + s.connect, self.server_addr) + + def test_connect_ex(self): + # Issue #11326: check connect_ex() implementation + s = test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED, + ca_certs=SIGNING_CA) + self.addCleanup(s.close) + self.assertEqual(0, s.connect_ex(self.server_addr)) + self.assertTrue(s.getpeercert()) + + def test_non_blocking_connect_ex(self): + # Issue #11326: non-blocking connect_ex() should allow handshake + # to proceed after the socket gets ready. + s = test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED, + ca_certs=SIGNING_CA, + do_handshake_on_connect=False) + self.addCleanup(s.close) + s.setblocking(False) + rc = s.connect_ex(self.server_addr) + # EWOULDBLOCK under Windows, EINPROGRESS elsewhere + self.assertIn(rc, (0, errno.EINPROGRESS, errno.EWOULDBLOCK)) + # Wait for connect to finish + select.select([], [s], [], 5.0) + # Non-blocking handshake + while True: + try: + s.do_handshake() + break + except ssl.SSLWantReadError: + select.select([s], [], [], 5.0) + except ssl.SSLWantWriteError: + select.select([], [s], [], 5.0) + # SSL established + self.assertTrue(s.getpeercert()) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_connect_with_context(self): + # Same as test_connect, but with a separately created context + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + with ctx.wrap_socket(socket.socket(socket.AF_INET)) as s: + s.connect(self.server_addr) + self.assertEqual({}, s.getpeercert()) + # Same with a server hostname + with ctx.wrap_socket(socket.socket(socket.AF_INET), + server_hostname="dummy") as s: + s.connect(self.server_addr) + ctx.verify_mode = ssl.CERT_REQUIRED + # This should succeed because we specify the root cert + ctx.load_verify_locations(SIGNING_CA) + with ctx.wrap_socket(socket.socket(socket.AF_INET)) as s: + s.connect(self.server_addr) + cert = s.getpeercert() + self.assertTrue(cert) + + def test_connect_with_context_fail(self): + # This should fail because we have no verification certs. Connection + # failure crashes ThreadedEchoServer, so run this in an independent + # test method. + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + s = ctx.wrap_socket( + socket.socket(socket.AF_INET), + server_hostname=SIGNED_CERTFILE_HOSTNAME + ) + self.addCleanup(s.close) + # Allow for flexible libssl error messages. + regex = re.compile(r"""( + certificate verify failed # OpenSSL + | + CERTIFICATE_VERIFY_FAILED # AWS-LC + )""", re.X) + self.assertRaisesRegex(ssl.SSLError, regex, + s.connect, self.server_addr) + + def test_connect_capath(self): + # Verify server certificates using the `capath` argument + # NOTE: the subject hashing algorithm has been changed between + # OpenSSL 0.9.8n and 1.0.0, as a result the capath directory must + # contain both versions of each certificate (same content, different + # filename) for this test to be portable across OpenSSL releases. + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.load_verify_locations(capath=CAPATH) + with ctx.wrap_socket(socket.socket(socket.AF_INET), + server_hostname=SIGNED_CERTFILE_HOSTNAME) as s: + s.connect(self.server_addr) + cert = s.getpeercert() + self.assertTrue(cert) + + # Same with a bytes `capath` argument + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.load_verify_locations(capath=BYTES_CAPATH) + with ctx.wrap_socket(socket.socket(socket.AF_INET), + server_hostname=SIGNED_CERTFILE_HOSTNAME) as s: + s.connect(self.server_addr) + cert = s.getpeercert() + self.assertTrue(cert) + + def test_connect_cadata(self): + with open(SIGNING_CA) as f: + pem = f.read() + der = ssl.PEM_cert_to_DER_cert(pem) + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.load_verify_locations(cadata=pem) + with ctx.wrap_socket(socket.socket(socket.AF_INET), + server_hostname=SIGNED_CERTFILE_HOSTNAME) as s: + s.connect(self.server_addr) + cert = s.getpeercert() + self.assertTrue(cert) + + # same with DER + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.load_verify_locations(cadata=der) + with ctx.wrap_socket(socket.socket(socket.AF_INET), + server_hostname=SIGNED_CERTFILE_HOSTNAME) as s: + s.connect(self.server_addr) + cert = s.getpeercert() + self.assertTrue(cert) + + @unittest.skipIf(os.name == "nt", "Can't use a socket as a file under Windows") + def test_makefile_close(self): + # Issue #5238: creating a file-like object with makefile() shouldn't + # delay closing the underlying "real socket" (here tested with its + # file descriptor, hence skipping the test under Windows). + ss = test_wrap_socket(socket.socket(socket.AF_INET)) + ss.connect(self.server_addr) + fd = ss.fileno() + f = ss.makefile() + f.close() + # The fd is still open + os.read(fd, 0) + # Closing the SSL socket should close the fd too + ss.close() + gc.collect() + with self.assertRaises(OSError) as e: + os.read(fd, 0) + self.assertEqual(e.exception.errno, errno.EBADF) + + def test_non_blocking_handshake(self): + s = socket.socket(socket.AF_INET) + s.connect(self.server_addr) + s.setblocking(False) + s = test_wrap_socket(s, + cert_reqs=ssl.CERT_NONE, + do_handshake_on_connect=False) + self.addCleanup(s.close) + count = 0 + while True: + try: + count += 1 + s.do_handshake() + break + except ssl.SSLWantReadError: + select.select([s], [], []) + except ssl.SSLWantWriteError: + select.select([], [s], []) + if support.verbose: + sys.stdout.write("\nNeeded %d calls to do_handshake() to establish session.\n" % count) + + def test_get_server_certificate(self): + _test_get_server_certificate(self, *self.server_addr, cert=SIGNING_CA) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_get_server_certificate_sni(self): + host, port = self.server_addr + server_names = [] + + # We store servername_cb arguments to make sure they match the host + def servername_cb(ssl_sock, server_name, initial_context): + server_names.append(server_name) + self.server_context.set_servername_callback(servername_cb) + + pem = ssl.get_server_certificate((host, port)) + if not pem: + self.fail("No server certificate on %s:%s!" % (host, port)) + + pem = ssl.get_server_certificate((host, port), ca_certs=SIGNING_CA) + if not pem: + self.fail("No server certificate on %s:%s!" % (host, port)) + if support.verbose: + sys.stdout.write("\nVerified certificate for %s:%s is\n%s\n" % (host, port, pem)) + + self.assertEqual(server_names, [host, host]) + + def test_get_server_certificate_fail(self): + # Connection failure crashes ThreadedEchoServer, so run this in an + # independent test method + _test_get_server_certificate_fail(self, *self.server_addr) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_get_server_certificate_timeout(self): + def servername_cb(ssl_sock, server_name, initial_context): + time.sleep(0.2) + self.server_context.set_servername_callback(servername_cb) + + with self.assertRaises(socket.timeout): + ssl.get_server_certificate(self.server_addr, ca_certs=SIGNING_CA, + timeout=0.1) + + def test_ciphers(self): + with test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_NONE, ciphers="ALL") as s: + s.connect(self.server_addr) + with test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_NONE, ciphers="DEFAULT") as s: + s.connect(self.server_addr) + # Error checking can happen at instantiation or when connecting + with self.assertRaisesRegex(ssl.SSLError, "No cipher can be selected"): + with socket.socket(socket.AF_INET) as sock: + s = test_wrap_socket(sock, + cert_reqs=ssl.CERT_NONE, ciphers="^$:,;?*'dorothyx") + s.connect(self.server_addr) + + def test_get_ca_certs_capath(self): + # capath certs are loaded on request + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.load_verify_locations(capath=CAPATH) + self.assertEqual(ctx.get_ca_certs(), []) + with ctx.wrap_socket(socket.socket(socket.AF_INET), + server_hostname='localhost') as s: + s.connect(self.server_addr) + cert = s.getpeercert() + self.assertTrue(cert) + self.assertEqual(len(ctx.get_ca_certs()), 1) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_context_setget(self): + # Check that the context of a connected socket can be replaced. + ctx1 = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx1.load_verify_locations(capath=CAPATH) + ctx2 = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx2.load_verify_locations(capath=CAPATH) + s = socket.socket(socket.AF_INET) + with ctx1.wrap_socket(s, server_hostname='localhost') as ss: + ss.connect(self.server_addr) + self.assertIs(ss.context, ctx1) + self.assertIs(ss._sslobj.context, ctx1) + ss.context = ctx2 + self.assertIs(ss.context, ctx2) + self.assertIs(ss._sslobj.context, ctx2) + + def ssl_io_loop(self, sock, incoming, outgoing, func, *args, **kwargs): + # A simple IO loop. Call func(*args) depending on the error we get + # (WANT_READ or WANT_WRITE) move data between the socket and the BIOs. + timeout = kwargs.get('timeout', support.SHORT_TIMEOUT) + count = 0 + for _ in support.busy_retry(timeout): + errno = None + count += 1 + try: + ret = func(*args) + except ssl.SSLError as e: + if e.errno not in (ssl.SSL_ERROR_WANT_READ, + ssl.SSL_ERROR_WANT_WRITE): + raise + errno = e.errno + # Get any data from the outgoing BIO irrespective of any error, and + # send it to the socket. + buf = outgoing.read() + sock.sendall(buf) + # If there's no error, we're done. For WANT_READ, we need to get + # data from the socket and put it in the incoming BIO. + if errno is None: + break + elif errno == ssl.SSL_ERROR_WANT_READ: + buf = sock.recv(32768) + if buf: + incoming.write(buf) + else: + incoming.write_eof() + if support.verbose: + sys.stdout.write("Needed %d calls to complete %s().\n" + % (count, func.__name__)) + return ret + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_bio_handshake(self): + sock = socket.socket(socket.AF_INET) + self.addCleanup(sock.close) + sock.connect(self.server_addr) + incoming = ssl.MemoryBIO() + outgoing = ssl.MemoryBIO() + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertTrue(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + ctx.load_verify_locations(SIGNING_CA) + sslobj = ctx.wrap_bio(incoming, outgoing, False, + SIGNED_CERTFILE_HOSTNAME) + self.assertIs(sslobj._sslobj.owner, sslobj) + self.assertIsNone(sslobj.cipher()) + self.assertIsNone(sslobj.version()) + self.assertIsNone(sslobj.shared_ciphers()) + self.assertRaises(ValueError, sslobj.getpeercert) + # tls-unique is not defined for TLSv1.3 + # https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5 + if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3": + self.assertIsNone(sslobj.get_channel_binding('tls-unique')) + self.ssl_io_loop(sock, incoming, outgoing, sslobj.do_handshake) + self.assertTrue(sslobj.cipher()) + self.assertIsNone(sslobj.shared_ciphers()) + self.assertIsNotNone(sslobj.version()) + self.assertTrue(sslobj.getpeercert()) + if 'tls-unique' in ssl.CHANNEL_BINDING_TYPES and sslobj.version() != "TLSv1.3": + self.assertTrue(sslobj.get_channel_binding('tls-unique')) + try: + self.ssl_io_loop(sock, incoming, outgoing, sslobj.unwrap) + except ssl.SSLSyscallError: + # If the server shuts down the TCP connection without sending a + # secure shutdown message, this is reported as SSL_ERROR_SYSCALL + pass + self.assertRaises(ssl.SSLError, sslobj.write, b'foo') + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_bio_read_write_data(self): + sock = socket.socket(socket.AF_INET) + self.addCleanup(sock.close) + sock.connect(self.server_addr) + incoming = ssl.MemoryBIO() + outgoing = ssl.MemoryBIO() + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + sslobj = ctx.wrap_bio(incoming, outgoing, False) + self.ssl_io_loop(sock, incoming, outgoing, sslobj.do_handshake) + req = b'FOO\n' + self.ssl_io_loop(sock, incoming, outgoing, sslobj.write, req) + buf = self.ssl_io_loop(sock, incoming, outgoing, sslobj.read, 1024) + self.assertEqual(buf, b'foo\n') + self.ssl_io_loop(sock, incoming, outgoing, sslobj.unwrap) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_transport_eof(self): + client_context, server_context, hostname = testing_context() + with socket.socket(socket.AF_INET) as sock: + sock.connect(self.server_addr) + incoming = ssl.MemoryBIO() + outgoing = ssl.MemoryBIO() + sslobj = client_context.wrap_bio(incoming, outgoing, + server_hostname=hostname) + self.ssl_io_loop(sock, incoming, outgoing, sslobj.do_handshake) + + # Simulate EOF from the transport. + incoming.write_eof() + self.assertRaises(ssl.SSLEOFError, sslobj.read) + + +@support.requires_resource('network') +class NetworkedTests(unittest.TestCase): + + def test_timeout_connect_ex(self): + # Issue #12065: on a timeout, connect_ex() should return the original + # errno (mimicking the behaviour of non-SSL sockets). + with socket_helper.transient_internet(REMOTE_HOST): + s = test_wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED, + do_handshake_on_connect=False) + self.addCleanup(s.close) + s.settimeout(0.0000001) + rc = s.connect_ex((REMOTE_HOST, 443)) + if rc == 0: + self.skipTest("REMOTE_HOST responded too quickly") + elif rc == errno.ENETUNREACH: + self.skipTest("Network unreachable.") + self.assertIn(rc, (errno.EAGAIN, errno.EWOULDBLOCK)) + + @unittest.skipUnless(socket_helper.IPV6_ENABLED, 'Needs IPv6') + @support.requires_resource('walltime') + def test_get_server_certificate_ipv6(self): + with socket_helper.transient_internet('ipv6.google.com'): + _test_get_server_certificate(self, 'ipv6.google.com', 443) + _test_get_server_certificate_fail(self, 'ipv6.google.com', 443) + + +def _test_get_server_certificate(test, host, port, cert=None): + pem = ssl.get_server_certificate((host, port)) + if not pem: + test.fail("No server certificate on %s:%s!" % (host, port)) + + pem = ssl.get_server_certificate((host, port), ca_certs=cert) + if not pem: + test.fail("No server certificate on %s:%s!" % (host, port)) + if support.verbose: + sys.stdout.write("\nVerified certificate for %s:%s is\n%s\n" % (host, port ,pem)) + +def _test_get_server_certificate_fail(test, host, port): + with warnings_helper.check_no_resource_warning(test): + try: + pem = ssl.get_server_certificate((host, port), ca_certs=CERTFILE) + except ssl.SSLError as x: + #should fail + if support.verbose: + sys.stdout.write("%s\n" % x) + else: + test.fail("Got server certificate %s for %s:%s!" % (pem, host, port)) + + +from test.ssl_servers import make_https_server + +class ThreadedEchoServer(threading.Thread): + + class ConnectionHandler(threading.Thread): + + """A mildly complicated class, because we want it to work both + with and without the SSL wrapper around the socket connection, so + that we can test the STARTTLS functionality.""" + + def __init__(self, server, connsock, addr): + self.server = server + self.running = False + self.sock = connsock + self.addr = addr + self.sock.setblocking(True) + self.sslconn = None + threading.Thread.__init__(self) + self.daemon = True + + def wrap_conn(self): + try: + self.sslconn = self.server.context.wrap_socket( + self.sock, server_side=True) + self.server.selected_alpn_protocols.append(self.sslconn.selected_alpn_protocol()) + except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError) as e: + # We treat ConnectionResetError as though it were an + # SSLError - OpenSSL on Ubuntu abruptly closes the + # connection when asked to use an unsupported protocol. + # + # BrokenPipeError is raised in TLS 1.3 mode, when OpenSSL + # tries to send session tickets after handshake. + # https://github.com/openssl/openssl/issues/6342 + # + # ConnectionAbortedError is raised in TLS 1.3 mode, when OpenSSL + # tries to send session tickets after handshake when using WinSock. + self.server.conn_errors.append(str(e)) + if self.server.chatty: + handle_error("\n server: bad connection attempt from " + repr(self.addr) + ":\n") + self.running = False + self.close() + return False + except (ssl.SSLError, OSError) as e: + # OSError may occur with wrong protocols, e.g. both + # sides use PROTOCOL_TLS_SERVER. + # + # XXX Various errors can have happened here, for example + # a mismatching protocol version, an invalid certificate, + # or a low-level bug. This should be made more discriminating. + # + # bpo-31323: Store the exception as string to prevent + # a reference leak: server -> conn_errors -> exception + # -> traceback -> self (ConnectionHandler) -> server + self.server.conn_errors.append(str(e)) + if self.server.chatty: + handle_error("\n server: bad connection attempt from " + repr(self.addr) + ":\n") + + # bpo-44229, bpo-43855, bpo-44237, and bpo-33450: + # Ignore spurious EPROTOTYPE returned by write() on macOS. + # See also http://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + if e.errno != errno.EPROTOTYPE and sys.platform != "darwin": + self.running = False + self.close() + return False + else: + self.server.shared_ciphers.append(self.sslconn.shared_ciphers()) + if self.server.context.verify_mode == ssl.CERT_REQUIRED: + cert = self.sslconn.getpeercert() + if support.verbose and self.server.chatty: + sys.stdout.write(" client cert is " + pprint.pformat(cert) + "\n") + cert_binary = self.sslconn.getpeercert(True) + if support.verbose and self.server.chatty: + if cert_binary is None: + sys.stdout.write(" client did not provide a cert\n") + else: + sys.stdout.write(f" cert binary is {len(cert_binary)}b\n") + cipher = self.sslconn.cipher() + if support.verbose and self.server.chatty: + sys.stdout.write(" server: connection cipher is now " + str(cipher) + "\n") + return True + + def read(self): + if self.sslconn: + return self.sslconn.read() + else: + return self.sock.recv(1024) + + def write(self, bytes): + if self.sslconn: + return self.sslconn.write(bytes) + else: + return self.sock.send(bytes) + + def close(self): + if self.sslconn: + self.sslconn.close() + else: + self.sock.close() + + def run(self): + self.running = True + if not self.server.starttls_server: + if not self.wrap_conn(): + return + while self.running: + try: + msg = self.read() + stripped = msg.strip() + if not stripped: + # eof, so quit this handler + self.running = False + try: + self.sock = self.sslconn.unwrap() + except OSError: + # Many tests shut the TCP connection down + # without an SSL shutdown. This causes + # unwrap() to raise OSError with errno=0! + pass + else: + self.sslconn = None + self.close() + elif stripped == b'over': + if support.verbose and self.server.connectionchatty: + sys.stdout.write(" server: client closed connection\n") + self.close() + return + elif (self.server.starttls_server and + stripped == b'STARTTLS'): + if support.verbose and self.server.connectionchatty: + sys.stdout.write(" server: read STARTTLS from client, sending OK...\n") + self.write(b"OK\n") + if not self.wrap_conn(): + return + elif (self.server.starttls_server and self.sslconn + and stripped == b'ENDTLS'): + if support.verbose and self.server.connectionchatty: + sys.stdout.write(" server: read ENDTLS from client, sending OK...\n") + self.write(b"OK\n") + self.sock = self.sslconn.unwrap() + self.sslconn = None + if support.verbose and self.server.connectionchatty: + sys.stdout.write(" server: connection is now unencrypted...\n") + elif stripped == b'CB tls-unique': + if support.verbose and self.server.connectionchatty: + sys.stdout.write(" server: read CB tls-unique from client, sending our CB data...\n") + data = self.sslconn.get_channel_binding("tls-unique") + self.write(repr(data).encode("us-ascii") + b"\n") + elif stripped == b'PHA': + if support.verbose and self.server.connectionchatty: + sys.stdout.write(" server: initiating post handshake auth\n") + try: + self.sslconn.verify_client_post_handshake() + except ssl.SSLError as e: + self.write(repr(e).encode("us-ascii") + b"\n") + else: + self.write(b"OK\n") + elif stripped == b'HASCERT': + if self.sslconn.getpeercert() is not None: + self.write(b'TRUE\n') + else: + self.write(b'FALSE\n') + elif stripped == b'GETCERT': + cert = self.sslconn.getpeercert() + self.write(repr(cert).encode("us-ascii") + b"\n") + elif stripped == b'VERIFIEDCHAIN': + certs = self.sslconn._sslobj.get_verified_chain() + self.write(len(certs).to_bytes(1, "big") + b"\n") + elif stripped == b'UNVERIFIEDCHAIN': + certs = self.sslconn._sslobj.get_unverified_chain() + self.write(len(certs).to_bytes(1, "big") + b"\n") + else: + if (support.verbose and + self.server.connectionchatty): + ctype = (self.sslconn and "encrypted") or "unencrypted" + sys.stdout.write(" server: read %r (%s), sending back %r (%s)...\n" + % (msg, ctype, msg.lower(), ctype)) + self.write(msg.lower()) + except OSError as e: + # handles SSLError and socket errors + if isinstance(e, ConnectionError): + # OpenSSL 1.1.1 sometimes raises + # ConnectionResetError when connection is not + # shut down gracefully. + if self.server.chatty and support.verbose: + print(f" Connection reset by peer: {self.addr}") + + self.close() + self.running = False + return + if self.server.chatty and support.verbose: + handle_error("Test server failure:\n") + try: + self.write(b"ERROR\n") + except OSError: + pass + self.close() + self.running = False + + def __init__(self, certificate=None, ssl_version=None, + certreqs=None, cacerts=None, + chatty=True, connectionchatty=False, starttls_server=False, + alpn_protocols=None, + ciphers=None, context=None): + if context: + self.context = context + else: + self.context = ssl.SSLContext(ssl_version + if ssl_version is not None + else ssl.PROTOCOL_TLS_SERVER) + self.context.verify_mode = (certreqs if certreqs is not None + else ssl.CERT_NONE) + if cacerts: + self.context.load_verify_locations(cacerts) + if certificate: + self.context.load_cert_chain(certificate) + if alpn_protocols: + self.context.set_alpn_protocols(alpn_protocols) + if ciphers: + self.context.set_ciphers(ciphers) + self.chatty = chatty + self.connectionchatty = connectionchatty + self.starttls_server = starttls_server + self.sock = socket.socket() + self.port = socket_helper.bind_port(self.sock) + self.flag = None + self.active = False + self.selected_alpn_protocols = [] + self.shared_ciphers = [] + self.conn_errors = [] + threading.Thread.__init__(self) + self.daemon = True + self._in_context = False + + def __enter__(self): + if self._in_context: + raise ValueError('Re-entering ThreadedEchoServer context') + self._in_context = True + self.start(threading.Event()) + self.flag.wait() + return self + + def __exit__(self, *args): + assert self._in_context + self._in_context = False + self.stop() + self.join() + + def start(self, flag=None): + if not self._in_context: + raise ValueError( + 'ThreadedEchoServer must be used as a context manager') + self.flag = flag + threading.Thread.start(self) + + def run(self): + if not self._in_context: + raise ValueError( + 'ThreadedEchoServer must be used as a context manager') + self.sock.settimeout(1.0) + self.sock.listen(5) + self.active = True + if self.flag: + # signal an event + self.flag.set() + while self.active: + try: + newconn, connaddr = self.sock.accept() + if support.verbose and self.chatty: + sys.stdout.write(' server: new connection from ' + + repr(connaddr) + '\n') + handler = self.ConnectionHandler(self, newconn, connaddr) + handler.start() + handler.join() + except TimeoutError as e: + if support.verbose: + sys.stdout.write(f' connection timeout {e!r}\n') + except KeyboardInterrupt: + self.stop() + except BaseException as e: + if support.verbose and self.chatty: + sys.stdout.write( + ' connection handling failed: ' + repr(e) + '\n') + + self.close() + + def close(self): + if self.sock is not None: + self.sock.close() + self.sock = None + + def stop(self): + self.active = False + +class AsyncoreEchoServer(threading.Thread): + + # this one's based on asyncore.dispatcher + + class EchoServer (asyncore.dispatcher): + + class ConnectionHandler(asyncore.dispatcher_with_send): + + def __init__(self, conn, certfile): + self.socket = test_wrap_socket(conn, server_side=True, + certfile=certfile, + do_handshake_on_connect=False) + asyncore.dispatcher_with_send.__init__(self, self.socket) + self._ssl_accepting = True + self._do_ssl_handshake() + + def readable(self): + if isinstance(self.socket, ssl.SSLSocket): + while self.socket.pending() > 0: + self.handle_read_event() + return True + + def _do_ssl_handshake(self): + try: + self.socket.do_handshake() + except (ssl.SSLWantReadError, ssl.SSLWantWriteError): + return + except ssl.SSLEOFError: + return self.handle_close() + except ssl.SSLError: + raise + except OSError as err: + if err.args[0] == errno.ECONNABORTED: + return self.handle_close() + else: + self._ssl_accepting = False + + def handle_read(self): + if self._ssl_accepting: + self._do_ssl_handshake() + else: + data = self.recv(1024) + if support.verbose: + sys.stdout.write(" server: read %s from client\n" % repr(data)) + if not data: + self.close() + else: + self.send(data.lower()) + + def handle_close(self): + self.close() + if support.verbose: + sys.stdout.write(" server: closed connection %s\n" % self.socket) + + def handle_error(self): + raise + + def __init__(self, certfile): + self.certfile = certfile + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.port = socket_helper.bind_port(sock, '') + asyncore.dispatcher.__init__(self, sock) + self.listen(5) + + def handle_accepted(self, sock_obj, addr): + if support.verbose: + sys.stdout.write(" server: new connection from %s:%s\n" %addr) + self.ConnectionHandler(sock_obj, self.certfile) + + def handle_error(self): + raise + + def __init__(self, certfile): + self.flag = None + self.active = False + self.server = self.EchoServer(certfile) + self.port = self.server.port + threading.Thread.__init__(self) + self.daemon = True + + def __str__(self): + return "<%s %s>" % (self.__class__.__name__, self.server) + + def __enter__(self): + self.start(threading.Event()) + self.flag.wait() + return self + + def __exit__(self, *args): + if support.verbose: + sys.stdout.write(" cleanup: stopping server.\n") + self.stop() + if support.verbose: + sys.stdout.write(" cleanup: joining server thread.\n") + self.join() + if support.verbose: + sys.stdout.write(" cleanup: successfully joined.\n") + # make sure that ConnectionHandler is removed from socket_map + asyncore.close_all(ignore_all=True) + + def start (self, flag=None): + self.flag = flag + threading.Thread.start(self) + + def run(self): + self.active = True + if self.flag: + self.flag.set() + while self.active: + try: + asyncore.loop(1) + except: + pass + + def stop(self): + self.active = False + self.server.close() + +def server_params_test(client_context, server_context, indata=b"FOO\n", + chatty=True, connectionchatty=False, sni_name=None, + session=None): + """ + Launch a server, connect a client to it and try various reads + and writes. + """ + stats = {} + server = ThreadedEchoServer(context=server_context, + chatty=chatty, + connectionchatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=sni_name, session=session) as s: + s.connect((HOST, server.port)) + for arg in [indata, bytearray(indata), memoryview(indata)]: + if connectionchatty: + if support.verbose: + sys.stdout.write( + " client: sending %r...\n" % indata) + s.write(arg) + outdata = s.read() + if connectionchatty: + if support.verbose: + sys.stdout.write(" client: read %r\n" % outdata) + if outdata != indata.lower(): + raise AssertionError( + "bad data <<%r>> (%d) received; expected <<%r>> (%d)\n" + % (outdata[:20], len(outdata), + indata[:20].lower(), len(indata))) + s.write(b"over\n") + if connectionchatty: + if support.verbose: + sys.stdout.write(" client: closing connection.\n") + stats.update({ + 'compression': s.compression(), + 'cipher': s.cipher(), + 'peercert': s.getpeercert(), + 'client_alpn_protocol': s.selected_alpn_protocol(), + 'version': s.version(), + 'session_reused': s.session_reused, + 'session': s.session, + }) + s.close() + stats['server_alpn_protocols'] = server.selected_alpn_protocols + stats['server_shared_ciphers'] = server.shared_ciphers + return stats + +def try_protocol_combo(server_protocol, client_protocol, expect_success, + certsreqs=None, server_options=0, client_options=0): + """ + Try to SSL-connect using *client_protocol* to *server_protocol*. + If *expect_success* is true, assert that the connection succeeds, + if it's false, assert that the connection fails. + Also, if *expect_success* is a string, assert that it is the protocol + version actually used by the connection. + """ + if certsreqs is None: + certsreqs = ssl.CERT_NONE + certtype = { + ssl.CERT_NONE: "CERT_NONE", + ssl.CERT_OPTIONAL: "CERT_OPTIONAL", + ssl.CERT_REQUIRED: "CERT_REQUIRED", + }[certsreqs] + if support.verbose: + formatstr = (expect_success and " %s->%s %s\n") or " {%s->%s} %s\n" + sys.stdout.write(formatstr % + (ssl.get_protocol_name(client_protocol), + ssl.get_protocol_name(server_protocol), + certtype)) + + with warnings_helper.check_warnings(): + # ignore Deprecation warnings + client_context = ssl.SSLContext(client_protocol) + client_context.options |= client_options + server_context = ssl.SSLContext(server_protocol) + server_context.options |= server_options + + min_version = PROTOCOL_TO_TLS_VERSION.get(client_protocol, None) + if (min_version is not None + # SSLContext.minimum_version is only available on recent OpenSSL + # (setter added in OpenSSL 1.1.0, getter added in OpenSSL 1.1.1) + and hasattr(server_context, 'minimum_version') + and server_protocol == ssl.PROTOCOL_TLS + and server_context.minimum_version > min_version + ): + # If OpenSSL configuration is strict and requires more recent TLS + # version, we have to change the minimum to test old TLS versions. + with warnings_helper.check_warnings(): + server_context.minimum_version = min_version + + # NOTE: we must enable "ALL" ciphers on the client, otherwise an + # SSLv23 client will send an SSLv3 hello (rather than SSLv2) + # starting from OpenSSL 1.0.0 (see issue #8322). + if client_context.protocol == ssl.PROTOCOL_TLS: + client_context.set_ciphers("ALL") + + seclevel_workaround(server_context, client_context) + + for ctx in (client_context, server_context): + ctx.verify_mode = certsreqs + ctx.load_cert_chain(SIGNED_CERTFILE) + ctx.load_verify_locations(SIGNING_CA) + try: + stats = server_params_test(client_context, server_context, + chatty=False, connectionchatty=False) + # Protocol mismatch can result in either an SSLError, or a + # "Connection reset by peer" error. + except ssl.SSLError: + if expect_success: + raise + except OSError as e: + if expect_success or e.errno != errno.ECONNRESET: + raise + else: + if not expect_success: + raise AssertionError( + "Client protocol %s succeeded with server protocol %s!" + % (ssl.get_protocol_name(client_protocol), + ssl.get_protocol_name(server_protocol))) + elif (expect_success is not True + and expect_success != stats['version']): + raise AssertionError("version mismatch: expected %r, got %r" + % (expect_success, stats['version'])) + + +def supports_kx_alias(ctx, aliases): + for cipher in ctx.get_ciphers(): + for alias in aliases: + if f"Kx={alias}" in cipher['description']: + return True + return False + + +class ThreadedTests(unittest.TestCase): + + @support.requires_resource('walltime') + def test_echo(self): + """Basic test of an SSL client connecting to a server""" + if support.verbose: + sys.stdout.write("\n") + + client_context, server_context, hostname = testing_context() + + with self.subTest(client=ssl.PROTOCOL_TLS_CLIENT, server=ssl.PROTOCOL_TLS_SERVER): + server_params_test(client_context=client_context, + server_context=server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + + client_context.check_hostname = False + with self.subTest(client=ssl.PROTOCOL_TLS_SERVER, server=ssl.PROTOCOL_TLS_CLIENT): + with self.assertRaises(ssl.SSLError) as e: + server_params_test(client_context=server_context, + server_context=client_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + self.assertIn( + 'Cannot create a client socket with a PROTOCOL_TLS_SERVER context', + str(e.exception) + ) + + with self.subTest(client=ssl.PROTOCOL_TLS_SERVER, server=ssl.PROTOCOL_TLS_SERVER): + with self.assertRaises(ssl.SSLError) as e: + server_params_test(client_context=server_context, + server_context=server_context, + chatty=True, connectionchatty=True) + self.assertIn( + 'Cannot create a client socket with a PROTOCOL_TLS_SERVER context', + str(e.exception) + ) + + with self.subTest(client=ssl.PROTOCOL_TLS_CLIENT, server=ssl.PROTOCOL_TLS_CLIENT): + with self.assertRaises(ssl.SSLError) as e: + server_params_test(client_context=server_context, + server_context=client_context, + chatty=True, connectionchatty=True) + self.assertIn( + 'Cannot create a client socket with a PROTOCOL_TLS_SERVER context', + str(e.exception)) + + @unittest.skipUnless(support.Py_GIL_DISABLED, "test is only useful if the GIL is disabled") + def test_ssl_in_multiple_threads(self): + # See GH-124984: OpenSSL is not thread safe. + threads = [] + + global USE_SAME_TEST_CONTEXT + USE_SAME_TEST_CONTEXT = True + try: + for func in ( + self.test_echo, + self.test_alpn_protocols, + self.test_getpeercert, + self.test_crl_check, + functools.partial( + self.test_check_hostname_idn, + warnings_filters=False, # gh-126483 + ), + self.test_wrong_cert_tls12, + self.test_wrong_cert_tls13, + ): + # Be careful with the number of threads here. + # Too many can result in failing tests. + for num in range(5): + with self.subTest(func=func, num=num): + threads.append(Thread(target=func)) + + with threading_helper.catch_threading_exception() as cm: + for thread in threads: + with self.subTest(thread=thread): + thread.start() + + for thread in threads: + with self.subTest(thread=thread): + thread.join() + if cm.exc_value is not None: + # Some threads can skip their test + if not isinstance(cm.exc_value, unittest.SkipTest): + raise cm.exc_value + finally: + USE_SAME_TEST_CONTEXT = False + + def test_getpeercert(self): + if support.verbose: + sys.stdout.write("\n") + + client_context, server_context, hostname = testing_context() + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + do_handshake_on_connect=False, + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + # getpeercert() raise ValueError while the handshake isn't + # done. + with self.assertRaises(ValueError): + s.getpeercert() + s.do_handshake() + cert = s.getpeercert() + self.assertTrue(cert, "Can't get peer certificate.") + cipher = s.cipher() + if support.verbose: + sys.stdout.write(pprint.pformat(cert) + '\n') + sys.stdout.write("Connection cipher is " + str(cipher) + '.\n') + if 'subject' not in cert: + self.fail("No subject field in certificate: %s." % + pprint.pformat(cert)) + if ((('organizationName', 'Python Software Foundation'),) + not in cert['subject']): + self.fail( + "Missing or invalid 'organizationName' field in certificate subject; " + "should be 'Python Software Foundation'.") + self.assertIn('notBefore', cert) + self.assertIn('notAfter', cert) + before = ssl.cert_time_to_seconds(cert['notBefore']) + after = ssl.cert_time_to_seconds(cert['notAfter']) + self.assertLess(before, after) + + def test_crl_check(self): + if support.verbose: + sys.stdout.write("\n") + + client_context, server_context, hostname = testing_context() + + tf = getattr(ssl, "VERIFY_X509_TRUSTED_FIRST", 0) + self.assertEqual(client_context.verify_flags, ssl.VERIFY_DEFAULT | tf) + + # VERIFY_DEFAULT should pass + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + cert = s.getpeercert() + self.assertTrue(cert, "Can't get peer certificate.") + + # VERIFY_CRL_CHECK_LEAF without a loaded CRL file fails + client_context.verify_flags |= ssl.VERIFY_CRL_CHECK_LEAF + + server = ThreadedEchoServer(context=server_context, chatty=True) + # Allow for flexible libssl error messages. + regex = re.compile(r"""( + certificate verify failed # OpenSSL + | + CERTIFICATE_VERIFY_FAILED # AWS-LC + )""", re.X) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + with self.assertRaisesRegex(ssl.SSLError, regex): + s.connect((HOST, server.port)) + + # now load a CRL file. The CRL file is signed by the CA. + client_context.load_verify_locations(CRLFILE) + + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + cert = s.getpeercert() + self.assertTrue(cert, "Can't get peer certificate.") + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_check_hostname(self): + if support.verbose: + sys.stdout.write("\n") + + client_context, server_context, hostname = testing_context() + + # correct hostname should verify + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + cert = s.getpeercert() + self.assertTrue(cert, "Can't get peer certificate.") + + # incorrect hostname should raise an exception + server = ThreadedEchoServer(context=server_context, chatty=True) + # Allow for flexible libssl error messages. + regex = re.compile(r"""( + certificate verify failed # OpenSSL + | + CERTIFICATE_VERIFY_FAILED # AWS-LC + )""", re.X) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname="invalid") as s: + with self.assertRaisesRegex(ssl.CertificateError, regex): + s.connect((HOST, server.port)) + + # missing server_hostname arg should cause an exception, too + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with socket.socket() as s: + with self.assertRaisesRegex(ValueError, + "check_hostname requires server_hostname"): + client_context.wrap_socket(s) + + @unittest.skipUnless( + ssl.HAS_NEVER_CHECK_COMMON_NAME, "test requires hostname_checks_common_name" + ) + def test_hostname_checks_common_name(self): + client_context, server_context, hostname = testing_context() + assert client_context.hostname_checks_common_name + client_context.hostname_checks_common_name = False + + # default cert has a SAN + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + + client_context, server_context, hostname = testing_context(NOSANFILE) + client_context.hostname_checks_common_name = False + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + with self.assertRaises(ssl.SSLCertVerificationError): + s.connect((HOST, server.port)) + + def test_ecc_cert(self): + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + client_context.load_verify_locations(SIGNING_CA) + client_context.set_ciphers('ECDHE:ECDSA:!NULL:!aRSA') + hostname = SIGNED_CERTFILE_ECC_HOSTNAME + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + # load ECC cert + server_context.load_cert_chain(SIGNED_CERTFILE_ECC) + + # correct hostname should verify + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + cert = s.getpeercert() + self.assertTrue(cert, "Can't get peer certificate.") + cipher = s.cipher()[0].split('-') + self.assertTrue(cipher[:2], ('ECDHE', 'ECDSA')) + + @unittest.skipUnless(IS_OPENSSL_3_0_0, + "test requires RFC 5280 check added in OpenSSL 3.0+") + def test_verify_strict(self): + # verification fails by default, since the server cert is non-conforming + client_context = ssl.create_default_context() + client_context.load_verify_locations(LEAF_MISSING_AKI_CA) + hostname = LEAF_MISSING_AKI_CERTFILE_HOSTNAME + + server_context = ssl.create_default_context(purpose=Purpose.CLIENT_AUTH) + server_context.load_cert_chain(LEAF_MISSING_AKI_CERTFILE) + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + with self.assertRaises(ssl.SSLError): + s.connect((HOST, server.port)) + + # explicitly disabling VERIFY_X509_STRICT allows it to succeed + client_context = ssl.create_default_context() + client_context.load_verify_locations(LEAF_MISSING_AKI_CA) + client_context.verify_flags &= ~ssl.VERIFY_X509_STRICT + + server_context = ssl.create_default_context(purpose=Purpose.CLIENT_AUTH) + server_context.load_cert_chain(LEAF_MISSING_AKI_CERTFILE) + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + cert = s.getpeercert() + self.assertTrue(cert, "Can't get peer certificate.") + + def test_dual_rsa_ecc(self): + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + client_context.load_verify_locations(SIGNING_CA) + # TODO: fix TLSv1.3 once SSLContext can restrict signature + # algorithms. + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + # only ECDSA certs + client_context.set_ciphers('ECDHE:ECDSA:!NULL:!aRSA') + hostname = SIGNED_CERTFILE_ECC_HOSTNAME + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + # load ECC and RSA key/cert pairs + server_context.load_cert_chain(SIGNED_CERTFILE_ECC) + server_context.load_cert_chain(SIGNED_CERTFILE) + + # correct hostname should verify + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + cert = s.getpeercert() + self.assertTrue(cert, "Can't get peer certificate.") + cipher = s.cipher()[0].split('-') + self.assertTrue(cipher[:2], ('ECDHE', 'ECDSA')) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_check_hostname_idn(self, warnings_filters=True): + if support.verbose: + sys.stdout.write("\n") + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + server_context.load_cert_chain(IDNSANSFILE) + + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.verify_mode = ssl.CERT_REQUIRED + context.check_hostname = True + context.load_verify_locations(SIGNING_CA) + + # correct hostname should verify, when specified in several + # different ways + idn_hostnames = [ + ('könig.idn.pythontest.net', + 'xn--knig-5qa.idn.pythontest.net'), + ('xn--knig-5qa.idn.pythontest.net', + 'xn--knig-5qa.idn.pythontest.net'), + (b'xn--knig-5qa.idn.pythontest.net', + 'xn--knig-5qa.idn.pythontest.net'), + + ('königsgäßchen.idna2003.pythontest.net', + 'xn--knigsgsschen-lcb0w.idna2003.pythontest.net'), + ('xn--knigsgsschen-lcb0w.idna2003.pythontest.net', + 'xn--knigsgsschen-lcb0w.idna2003.pythontest.net'), + (b'xn--knigsgsschen-lcb0w.idna2003.pythontest.net', + 'xn--knigsgsschen-lcb0w.idna2003.pythontest.net'), + + # ('königsgäßchen.idna2008.pythontest.net', + # 'xn--knigsgchen-b4a3dun.idna2008.pythontest.net'), + ('xn--knigsgchen-b4a3dun.idna2008.pythontest.net', + 'xn--knigsgchen-b4a3dun.idna2008.pythontest.net'), + (b'xn--knigsgchen-b4a3dun.idna2008.pythontest.net', + 'xn--knigsgchen-b4a3dun.idna2008.pythontest.net'), + + ] + for server_hostname, expected_hostname in idn_hostnames: + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with context.wrap_socket(socket.socket(), + server_hostname=server_hostname) as s: + self.assertEqual(s.server_hostname, expected_hostname) + s.connect((HOST, server.port)) + cert = s.getpeercert() + self.assertEqual(s.server_hostname, expected_hostname) + self.assertTrue(cert, "Can't get peer certificate.") + + # incorrect hostname should raise an exception + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with context.wrap_socket(socket.socket(), + server_hostname="python.example.org") as s: + with self.assertRaises(ssl.CertificateError): + s.connect((HOST, server.port)) + with ( + ThreadedEchoServer(context=server_context, chatty=True) as server, + ( + warnings_helper.check_no_resource_warning(self) + if warnings_filters + else nullcontext() + ), + self.assertRaises(UnicodeError), + ): + context.wrap_socket(socket.socket(), server_hostname='.pythontest.net') + + with ( + ThreadedEchoServer(context=server_context, chatty=True) as server, + ( + warnings_helper.check_no_resource_warning(self) + if warnings_filters + else nullcontext() + ), + self.assertRaises(UnicodeDecodeError), + ): + context.wrap_socket( + socket.socket(), + server_hostname=b'k\xf6nig.idn.pythontest.net', + ) + + def test_wrong_cert_tls12(self): + """Connecting when the server rejects the client's certificate + + Launch a server with CERT_REQUIRED, and check that trying to + connect to it with a wrong client certificate fails. + """ + client_context, server_context, hostname = testing_context() + # load client cert that is not signed by trusted CA + client_context.load_cert_chain(CERTFILE) + # require TLS client authentication + server_context.verify_mode = ssl.CERT_REQUIRED + # TLS 1.3 has different handshake + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + + server = ThreadedEchoServer( + context=server_context, chatty=True, connectionchatty=True, + ) + + with server, \ + client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + try: + # Expect either an SSL error about the server rejecting + # the connection, or a low-level connection reset (which + # sometimes happens on Windows) + s.connect((HOST, server.port)) + except ssl.SSLError as e: + if support.verbose: + sys.stdout.write("\nSSLError is %r\n" % e) + except OSError as e: + if e.errno != errno.ECONNRESET: + raise + if support.verbose: + sys.stdout.write("\nsocket.error is %r\n" % e) + else: + self.fail("Use of invalid cert should have failed!") + + @requires_tls_version('TLSv1_3') + def test_wrong_cert_tls13(self): + client_context, server_context, hostname = testing_context() + # load client cert that is not signed by trusted CA + client_context.load_cert_chain(CERTFILE) + server_context.verify_mode = ssl.CERT_REQUIRED + server_context.minimum_version = ssl.TLSVersion.TLSv1_3 + client_context.minimum_version = ssl.TLSVersion.TLSv1_3 + + server = ThreadedEchoServer( + context=server_context, chatty=True, connectionchatty=True, + ) + with server, \ + client_context.wrap_socket(socket.socket(), + server_hostname=hostname, + suppress_ragged_eofs=False) as s: + s.connect((HOST, server.port)) + with self.assertRaisesRegex( + OSError, + 'alert unknown ca|EOF occurred|TLSV1_ALERT_UNKNOWN_CA|' + 'closed by the remote host|Connection reset by peer|' + 'Broken pipe' + ): + # TLS 1.3 perform client cert exchange after handshake + s.write(b'data') + s.read(1000) + s.write(b'should have failed already') + s.read(1000) + + def test_rude_shutdown(self): + """A brutal shutdown of an SSL server should raise an OSError + in the client when attempting handshake. + """ + listener_ready = threading.Event() + listener_gone = threading.Event() + + s = socket.socket() + port = socket_helper.bind_port(s, HOST) + + # `listener` runs in a thread. It sits in an accept() until + # the main thread connects. Then it rudely closes the socket, + # and sets Event `listener_gone` to let the main thread know + # the socket is gone. + def listener(): + s.listen() + listener_ready.set() + newsock, addr = s.accept() + newsock.close() + s.close() + listener_gone.set() + + def connector(): + listener_ready.wait() + with socket.socket() as c: + c.connect((HOST, port)) + listener_gone.wait() + try: + ssl_sock = test_wrap_socket(c) + except OSError: + pass + else: + self.fail('connecting to closed SSL socket should have failed') + + t = threading.Thread(target=listener) + t.start() + try: + connector() + finally: + t.join() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_ssl_cert_verify_error(self): + if support.verbose: + sys.stdout.write("\n") + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + server_context.load_cert_chain(SIGNED_CERTFILE) + + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with context.wrap_socket(socket.socket(), + server_hostname=SIGNED_CERTFILE_HOSTNAME) as s: + try: + s.connect((HOST, server.port)) + self.fail("Expected connection failure") + except ssl.SSLError as e: + msg = 'unable to get local issuer certificate' + self.assertIsInstance(e, ssl.SSLCertVerificationError) + self.assertEqual(e.verify_code, 20) + self.assertEqual(e.verify_message, msg) + # Allow for flexible libssl error messages. + regex = f"({msg}|CERTIFICATE_VERIFY_FAILED)" + self.assertRegex(repr(e), regex) + regex = re.compile(r"""( + certificate verify failed # OpenSSL + | + CERTIFICATE_VERIFY_FAILED # AWS-LC + )""", re.X) + self.assertRegex(repr(e), regex) + + def test_PROTOCOL_TLS(self): + """Connecting to an SSLv23 server with various client options""" + if support.verbose: + sys.stdout.write("\n") + if has_tls_version('SSLv3'): + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False) + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True) + if has_tls_version('TLSv1'): + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1') + + if has_tls_version('SSLv3'): + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False, ssl.CERT_OPTIONAL) + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True, ssl.CERT_OPTIONAL) + if has_tls_version('TLSv1'): + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_OPTIONAL) + + if has_tls_version('SSLv3'): + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False, ssl.CERT_REQUIRED) + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True, ssl.CERT_REQUIRED) + if has_tls_version('TLSv1'): + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_REQUIRED) + + # Server with specific SSL options + if has_tls_version('SSLv3'): + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv3, False, + server_options=ssl.OP_NO_SSLv3) + # Will choose TLSv1 + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS, True, + server_options=ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3) + if has_tls_version('TLSv1'): + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1, False, + server_options=ssl.OP_NO_TLSv1) + + @requires_tls_version('SSLv3') + def test_protocol_sslv3(self): + """Connecting to an SSLv3 server with various client options""" + if support.verbose: + sys.stdout.write("\n") + try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3') + try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3', ssl.CERT_OPTIONAL) + try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3', ssl.CERT_REQUIRED) + try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_TLS, False, + client_options=ssl.OP_NO_SSLv3) + try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_TLSv1, False) + + @requires_tls_version('TLSv1') + def test_protocol_tlsv1(self): + """Connecting to a TLSv1 server with various client options""" + if support.verbose: + sys.stdout.write("\n") + try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1') + try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_OPTIONAL) + try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_REQUIRED) + if has_tls_version('SSLv3'): + try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv3, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLS, False, + client_options=ssl.OP_NO_TLSv1) + + @requires_tls_version('TLSv1_1') + def test_protocol_tlsv1_1(self): + """Connecting to a TLSv1.1 server with various client options. + Testing against older TLS versions.""" + if support.verbose: + sys.stdout.write("\n") + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_1, 'TLSv1.1') + if has_tls_version('SSLv3'): + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv3, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLS, False, + client_options=ssl.OP_NO_TLSv1_1) + + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1_1, 'TLSv1.1') + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_2, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_1, False) + + @requires_tls_version('TLSv1_2') + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_protocol_tlsv1_2(self): + """Connecting to a TLSv1.2 server with various client options. + Testing against older TLS versions.""" + if support.verbose: + sys.stdout.write("\n") + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_2, 'TLSv1.2', + server_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2, + client_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2,) + if has_tls_version('SSLv3'): + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv3, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLS, False, + client_options=ssl.OP_NO_TLSv1_2) + + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1_2, 'TLSv1.2') + if has_tls_protocol(ssl.PROTOCOL_TLSv1): + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1_2, False) + if has_tls_protocol(ssl.PROTOCOL_TLSv1_1): + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_1, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_2, False) + + def test_starttls(self): + """Switching from clear text to encrypted and back again.""" + msgs = (b"msg 1", b"MSG 2", b"STARTTLS", b"MSG 3", b"msg 4", b"ENDTLS", b"msg 5", b"msg 6") + + server = ThreadedEchoServer(CERTFILE, + starttls_server=True, + chatty=True, + connectionchatty=True) + wrapped = False + with server: + s = socket.socket() + s.setblocking(True) + s.connect((HOST, server.port)) + if support.verbose: + sys.stdout.write("\n") + for indata in msgs: + if support.verbose: + sys.stdout.write( + " client: sending %r...\n" % indata) + if wrapped: + conn.write(indata) + outdata = conn.read() + else: + s.send(indata) + outdata = s.recv(1024) + msg = outdata.strip().lower() + if indata == b"STARTTLS" and msg.startswith(b"ok"): + # STARTTLS ok, switch to secure mode + if support.verbose: + sys.stdout.write( + " client: read %r from server, starting TLS...\n" + % msg) + conn = test_wrap_socket(s) + wrapped = True + elif indata == b"ENDTLS" and msg.startswith(b"ok"): + # ENDTLS ok, switch back to clear text + if support.verbose: + sys.stdout.write( + " client: read %r from server, ending TLS...\n" + % msg) + s = conn.unwrap() + wrapped = False + else: + if support.verbose: + sys.stdout.write( + " client: read %r from server\n" % msg) + if support.verbose: + sys.stdout.write(" client: closing connection.\n") + if wrapped: + conn.write(b"over\n") + else: + s.send(b"over\n") + if wrapped: + conn.close() + else: + s.close() + + def test_socketserver(self): + """Using socketserver to create and manage SSL connections.""" + server = make_https_server(self, certfile=SIGNED_CERTFILE) + # try to connect + if support.verbose: + sys.stdout.write('\n') + # Get this test file itself: + with open(__file__, 'rb') as f: + d1 = f.read() + d2 = '' + # now fetch the same data from the HTTPS server + url = f'https://localhost:{server.port}/test_ssl.py' + context = ssl.create_default_context(cafile=SIGNING_CA) + f = urllib.request.urlopen(url, context=context) + try: + dlen = f.info().get("content-length") + if dlen and (int(dlen) > 0): + d2 = f.read(int(dlen)) + if support.verbose: + sys.stdout.write( + " client: read %d bytes from remote server '%s'\n" + % (len(d2), server)) + finally: + f.close() + self.assertEqual(d1, d2) + + @unittest.skip("TODO: RUSTPYTHON; hangs") + def test_asyncore_server(self): + """Check the example asyncore integration.""" + if support.verbose: + sys.stdout.write("\n") + + indata = b"FOO\n" + server = AsyncoreEchoServer(CERTFILE) + with server: + s = test_wrap_socket(socket.socket()) + s.connect(('127.0.0.1', server.port)) + if support.verbose: + sys.stdout.write( + " client: sending %r...\n" % indata) + s.write(indata) + outdata = s.read() + if support.verbose: + sys.stdout.write(" client: read %r\n" % outdata) + if outdata != indata.lower(): + self.fail( + "bad data <<%r>> (%d) received; expected <<%r>> (%d)\n" + % (outdata[:20], len(outdata), + indata[:20].lower(), len(indata))) + s.write(b"over\n") + if support.verbose: + sys.stdout.write(" client: closing connection.\n") + s.close() + if support.verbose: + sys.stdout.write(" client: connection closed.\n") + + @unittest.skip("TODO: RUSTPYTHON; hangs") + def test_recv_send(self): + """Test recv(), send() and friends.""" + if support.verbose: + sys.stdout.write("\n") + + server = ThreadedEchoServer(CERTFILE, + certreqs=ssl.CERT_NONE, + ssl_version=ssl.PROTOCOL_TLS_SERVER, + cacerts=CERTFILE, + chatty=True, + connectionchatty=False) + with server: + s = test_wrap_socket(socket.socket(), + server_side=False, + certfile=CERTFILE, + ca_certs=CERTFILE, + cert_reqs=ssl.CERT_NONE) + s.connect((HOST, server.port)) + # helper methods for standardising recv* method signatures + def _recv_into(): + b = bytearray(b"\0"*100) + count = s.recv_into(b) + return b[:count] + + def _recvfrom_into(): + b = bytearray(b"\0"*100) + count, addr = s.recvfrom_into(b) + return b[:count] + + # (name, method, expect success?, *args, return value func) + send_methods = [ + ('send', s.send, True, [], len), + ('sendto', s.sendto, False, ["some.address"], len), + ('sendall', s.sendall, True, [], lambda x: None), + ] + # (name, method, whether to expect success, *args) + recv_methods = [ + ('recv', s.recv, True, []), + ('recvfrom', s.recvfrom, False, ["some.address"]), + ('recv_into', _recv_into, True, []), + ('recvfrom_into', _recvfrom_into, False, []), + ] + data_prefix = "PREFIX_" + + for (meth_name, send_meth, expect_success, args, + ret_val_meth) in send_methods: + indata = (data_prefix + meth_name).encode('ascii') + try: + ret = send_meth(indata, *args) + msg = "sending with {}".format(meth_name) + self.assertEqual(ret, ret_val_meth(indata), msg=msg) + outdata = s.read() + if outdata != indata.lower(): + self.fail( + "While sending with <<{name:s}>> bad data " + "<<{outdata:r}>> ({nout:d}) received; " + "expected <<{indata:r}>> ({nin:d})\n".format( + name=meth_name, outdata=outdata[:20], + nout=len(outdata), + indata=indata[:20], nin=len(indata) + ) + ) + except ValueError as e: + if expect_success: + self.fail( + "Failed to send with method <<{name:s}>>; " + "expected to succeed.\n".format(name=meth_name) + ) + if not str(e).startswith(meth_name): + self.fail( + "Method <<{name:s}>> failed with unexpected " + "exception message: {exp:s}\n".format( + name=meth_name, exp=e + ) + ) + + for meth_name, recv_meth, expect_success, args in recv_methods: + indata = (data_prefix + meth_name).encode('ascii') + try: + s.send(indata) + outdata = recv_meth(*args) + if outdata != indata.lower(): + self.fail( + "While receiving with <<{name:s}>> bad data " + "<<{outdata:r}>> ({nout:d}) received; " + "expected <<{indata:r}>> ({nin:d})\n".format( + name=meth_name, outdata=outdata[:20], + nout=len(outdata), + indata=indata[:20], nin=len(indata) + ) + ) + except ValueError as e: + if expect_success: + self.fail( + "Failed to receive with method <<{name:s}>>; " + "expected to succeed.\n".format(name=meth_name) + ) + if not str(e).startswith(meth_name): + self.fail( + "Method <<{name:s}>> failed with unexpected " + "exception message: {exp:s}\n".format( + name=meth_name, exp=e + ) + ) + # consume data + s.read() + + # read(-1, buffer) is supported, even though read(-1) is not + data = b"data" + s.send(data) + buffer = bytearray(len(data)) + self.assertEqual(s.read(-1, buffer), len(data)) + self.assertEqual(buffer, data) + + # sendall accepts bytes-like objects + if ctypes is not None: + ubyte = ctypes.c_ubyte * len(data) + byteslike = ubyte.from_buffer_copy(data) + s.sendall(byteslike) + self.assertEqual(s.read(), data) + + # Make sure sendmsg et al are disallowed to avoid + # inadvertent disclosure of data and/or corruption + # of the encrypted data stream + self.assertRaises(NotImplementedError, s.dup) + self.assertRaises(NotImplementedError, s.sendmsg, [b"data"]) + self.assertRaises(NotImplementedError, s.recvmsg, 100) + self.assertRaises(NotImplementedError, + s.recvmsg_into, [bytearray(100)]) + s.write(b"over\n") + + self.assertRaises(ValueError, s.recv, -1) + self.assertRaises(ValueError, s.read, -1) + + s.close() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_recv_zero(self): + server = ThreadedEchoServer(CERTFILE) + self.enterContext(server) + s = socket.create_connection((HOST, server.port)) + self.addCleanup(s.close) + s = test_wrap_socket(s, suppress_ragged_eofs=False) + self.addCleanup(s.close) + + # recv/read(0) should return no data + s.send(b"data") + self.assertEqual(s.recv(0), b"") + self.assertEqual(s.read(0), b"") + self.assertEqual(s.read(), b"data") + + # Should not block if the other end sends no data + s.setblocking(False) + self.assertEqual(s.recv(0), b"") + self.assertEqual(s.recv_into(bytearray()), 0) + + def test_recv_into_buffer_protocol_len(self): + server = ThreadedEchoServer(CERTFILE) + self.enterContext(server) + s = socket.create_connection((HOST, server.port)) + self.addCleanup(s.close) + s = test_wrap_socket(s, suppress_ragged_eofs=False) + self.addCleanup(s.close) + + s.send(b"data") + buf = array.array('I', [0, 0]) + self.assertEqual(s.recv_into(buf), 4) + self.assertEqual(bytes(buf)[:4], b"data") + + class B(bytearray): + def __len__(self): + 1/0 + s.send(b"data") + buf = B(6) + self.assertEqual(s.recv_into(buf), 4) + self.assertEqual(bytes(buf), b"data\0\0") + + def test_nonblocking_send(self): + server = ThreadedEchoServer(CERTFILE, + certreqs=ssl.CERT_NONE, + ssl_version=ssl.PROTOCOL_TLS_SERVER, + cacerts=CERTFILE, + chatty=True, + connectionchatty=False) + with server: + s = test_wrap_socket(socket.socket(), + server_side=False, + certfile=CERTFILE, + ca_certs=CERTFILE, + cert_reqs=ssl.CERT_NONE) + s.connect((HOST, server.port)) + s.setblocking(False) + + # If we keep sending data, at some point the buffers + # will be full and the call will block + buf = bytearray(8192) + def fill_buffer(): + while True: + s.send(buf) + self.assertRaises((ssl.SSLWantWriteError, + ssl.SSLWantReadError), fill_buffer) + + # Now read all the output and discard it + s.setblocking(True) + s.close() + + def test_handshake_timeout(self): + # Issue #5103: SSL handshake must respect the socket timeout + server = socket.socket(socket.AF_INET) + host = "127.0.0.1" + port = socket_helper.bind_port(server) + started = threading.Event() + finish = False + + def serve(): + server.listen() + started.set() + conns = [] + while not finish: + r, w, e = select.select([server], [], [], 0.1) + if server in r: + # Let the socket hang around rather than having + # it closed by garbage collection. + conns.append(server.accept()[0]) + for sock in conns: + sock.close() + + t = threading.Thread(target=serve) + t.start() + started.wait() + + try: + try: + c = socket.socket(socket.AF_INET) + c.settimeout(0.2) + c.connect((host, port)) + # Will attempt handshake and time out + self.assertRaisesRegex(TimeoutError, "timed out", + test_wrap_socket, c) + finally: + c.close() + try: + c = socket.socket(socket.AF_INET) + c = test_wrap_socket(c) + c.settimeout(0.2) + # Will attempt handshake and time out + self.assertRaisesRegex(TimeoutError, "timed out", + c.connect, (host, port)) + finally: + c.close() + finally: + finish = True + t.join() + server.close() + + def test_server_accept(self): + # Issue #16357: accept() on a SSLSocket created through + # SSLContext.wrap_socket(). + client_ctx, server_ctx, hostname = testing_context() + server = socket.socket(socket.AF_INET) + host = "127.0.0.1" + port = socket_helper.bind_port(server) + server = server_ctx.wrap_socket(server, server_side=True) + self.assertTrue(server.server_side) + + evt = threading.Event() + remote = None + peer = None + def serve(): + nonlocal remote, peer + server.listen() + # Block on the accept and wait on the connection to close. + evt.set() + remote, peer = server.accept() + remote.send(remote.recv(4)) + + t = threading.Thread(target=serve) + t.start() + # Client wait until server setup and perform a connect. + evt.wait() + client = client_ctx.wrap_socket( + socket.socket(), server_hostname=hostname + ) + client.connect((hostname, port)) + client.send(b'data') + client.recv() + client_addr = client.getsockname() + client.close() + t.join() + remote.close() + server.close() + # Sanity checks. + self.assertIsInstance(remote, ssl.SSLSocket) + self.assertEqual(peer, client_addr) + + def test_getpeercert_enotconn(self): + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.check_hostname = False + with context.wrap_socket(socket.socket()) as sock: + with self.assertRaises(OSError) as cm: + sock.getpeercert() + self.assertEqual(cm.exception.errno, errno.ENOTCONN) + + def test_do_handshake_enotconn(self): + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.check_hostname = False + with context.wrap_socket(socket.socket()) as sock: + with self.assertRaises(OSError) as cm: + sock.do_handshake() + self.assertEqual(cm.exception.errno, errno.ENOTCONN) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_no_shared_ciphers(self): + client_context, server_context, hostname = testing_context() + # OpenSSL enables all TLS 1.3 ciphers, enforce TLS 1.2 for test + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + # Force different suites on client and server + client_context.set_ciphers("AES128") + server_context.set_ciphers("AES256") + with ThreadedEchoServer(context=server_context) as server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + with self.assertRaises(OSError): + s.connect((HOST, server.port)) + self.assertIn("NO_SHARED_CIPHER", server.conn_errors[0]) + + def test_version_basic(self): + """ + Basic tests for SSLSocket.version(). + More tests are done in the test_protocol_*() methods. + """ + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + with ThreadedEchoServer(CERTFILE, + ssl_version=ssl.PROTOCOL_TLS_SERVER, + chatty=False) as server: + with context.wrap_socket(socket.socket()) as s: + self.assertIs(s.version(), None) + self.assertIs(s._sslobj, None) + s.connect((HOST, server.port)) + self.assertEqual(s.version(), 'TLSv1.3') + self.assertIs(s._sslobj, None) + self.assertIs(s.version(), None) + + @requires_tls_version('TLSv1_3') + def test_tls1_3(self): + client_context, server_context, hostname = testing_context() + client_context.minimum_version = ssl.TLSVersion.TLSv1_3 + with ThreadedEchoServer(context=server_context) as server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + self.assertIn(s.cipher()[0], { + 'TLS_AES_256_GCM_SHA384', + 'TLS_CHACHA20_POLY1305_SHA256', + 'TLS_AES_128_GCM_SHA256', + }) + self.assertEqual(s.version(), 'TLSv1.3') + + @requires_tls_version('TLSv1_2') + @requires_tls_version('TLSv1') + @ignore_deprecation + def test_min_max_version_tlsv1_2(self): + client_context, server_context, hostname = testing_context() + # client TLSv1.0 to 1.2 + client_context.minimum_version = ssl.TLSVersion.TLSv1 + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + # server only TLSv1.2 + server_context.minimum_version = ssl.TLSVersion.TLSv1_2 + server_context.maximum_version = ssl.TLSVersion.TLSv1_2 + + with ThreadedEchoServer(context=server_context) as server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + self.assertEqual(s.version(), 'TLSv1.2') + + @requires_tls_version('TLSv1_1') + @ignore_deprecation + def test_min_max_version_tlsv1_1(self): + client_context, server_context, hostname = testing_context() + # client 1.0 to 1.2, server 1.0 to 1.1 + client_context.minimum_version = ssl.TLSVersion.TLSv1 + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + server_context.minimum_version = ssl.TLSVersion.TLSv1 + server_context.maximum_version = ssl.TLSVersion.TLSv1_1 + seclevel_workaround(client_context, server_context) + + with ThreadedEchoServer(context=server_context) as server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + self.assertEqual(s.version(), 'TLSv1.1') + + @requires_tls_version('TLSv1_2') + @requires_tls_version('TLSv1') + @ignore_deprecation + def test_min_max_version_mismatch(self): + client_context, server_context, hostname = testing_context() + # client 1.0, server 1.2 (mismatch) + server_context.maximum_version = ssl.TLSVersion.TLSv1_2 + server_context.minimum_version = ssl.TLSVersion.TLSv1_2 + client_context.maximum_version = ssl.TLSVersion.TLSv1 + client_context.minimum_version = ssl.TLSVersion.TLSv1 + seclevel_workaround(client_context, server_context) + + with ThreadedEchoServer(context=server_context) as server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + with self.assertRaises(ssl.SSLError) as e: + s.connect((HOST, server.port)) + self.assertRegex(str(e.exception), "(alert|ALERT)") + + @requires_tls_version('SSLv3') + def test_min_max_version_sslv3(self): + client_context, server_context, hostname = testing_context() + server_context.minimum_version = ssl.TLSVersion.SSLv3 + client_context.minimum_version = ssl.TLSVersion.SSLv3 + client_context.maximum_version = ssl.TLSVersion.SSLv3 + seclevel_workaround(client_context, server_context) + + with ThreadedEchoServer(context=server_context) as server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + self.assertEqual(s.version(), 'SSLv3') + + def test_default_ecdh_curve(self): + # Issue #21015: elliptic curve-based Diffie Hellman key exchange + # should be enabled by default on SSL contexts. + client_context, server_context, hostname = testing_context() + # TLSv1.3 defaults to PFS key agreement and no longer has KEA in + # cipher name. + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + # Prior to OpenSSL 1.0.0, ECDH ciphers have to be enabled + # explicitly using the 'ECCdraft' cipher alias. Otherwise, + # our default cipher list should prefer ECDH-based ciphers + # automatically. + with ThreadedEchoServer(context=server_context) as server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + self.assertIn("ECDH", s.cipher()[0]) + + @unittest.skipUnless("tls-unique" in ssl.CHANNEL_BINDING_TYPES, + "'tls-unique' channel binding not available") + def test_tls_unique_channel_binding(self): + """Test tls-unique channel binding.""" + if support.verbose: + sys.stdout.write("\n") + + client_context, server_context, hostname = testing_context() + + # tls-unique is not defined for TLSv1.3 + # https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.5 + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + + server = ThreadedEchoServer(context=server_context, + chatty=True, + connectionchatty=False) + + with server: + with client_context.wrap_socket( + socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + # get the data + cb_data = s.get_channel_binding("tls-unique") + if support.verbose: + sys.stdout.write( + " got channel binding data: {0!r}\n".format(cb_data)) + + # check if it is sane + self.assertIsNotNone(cb_data) + if s.version() == 'TLSv1.3': + self.assertEqual(len(cb_data), 48) + else: + self.assertEqual(len(cb_data), 12) # True for TLSv1 + + # and compare with the peers version + s.write(b"CB tls-unique\n") + peer_data_repr = s.read().strip() + self.assertEqual(peer_data_repr, + repr(cb_data).encode("us-ascii")) + + # now, again + with client_context.wrap_socket( + socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + new_cb_data = s.get_channel_binding("tls-unique") + if support.verbose: + sys.stdout.write( + "got another channel binding data: {0!r}\n".format( + new_cb_data) + ) + # is it really unique + self.assertNotEqual(cb_data, new_cb_data) + self.assertIsNotNone(cb_data) + if s.version() == 'TLSv1.3': + self.assertEqual(len(cb_data), 48) + else: + self.assertEqual(len(cb_data), 12) # True for TLSv1 + s.write(b"CB tls-unique\n") + peer_data_repr = s.read().strip() + self.assertEqual(peer_data_repr, + repr(new_cb_data).encode("us-ascii")) + + def test_compression(self): + client_context, server_context, hostname = testing_context() + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + if support.verbose: + sys.stdout.write(" got compression: {!r}\n".format(stats['compression'])) + self.assertIn(stats['compression'], { None, 'ZLIB', 'RLE' }) + + @unittest.skipUnless(hasattr(ssl, 'OP_NO_COMPRESSION'), + "ssl.OP_NO_COMPRESSION needed for this test") + def test_compression_disabled(self): + client_context, server_context, hostname = testing_context() + client_context.options |= ssl.OP_NO_COMPRESSION + server_context.options |= ssl.OP_NO_COMPRESSION + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + self.assertIs(stats['compression'], None) + + def test_legacy_server_connect(self): + client_context, server_context, hostname = testing_context() + client_context.options |= ssl.OP_LEGACY_SERVER_CONNECT + server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + + def test_no_legacy_server_connect(self): + client_context, server_context, hostname = testing_context() + client_context.options &= ~ssl.OP_LEGACY_SERVER_CONNECT + server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_dh_params(self): + # Check we can get a connection with ephemeral finite-field + # Diffie-Hellman (if supported). + client_context, server_context, hostname = testing_context() + dhe_aliases = {"ADH", "EDH", "DHE"} + if not (supports_kx_alias(client_context, dhe_aliases) + and supports_kx_alias(server_context, dhe_aliases)): + self.skipTest("libssl doesn't support ephemeral DH") + # test scenario needs TLS <= 1.2 + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + try: + server_context.load_dh_params(DHFILE) + except RuntimeError: + if Py_DEBUG_WIN32: + self.skipTest("not supported on Win32 debug build") + raise + server_context.set_ciphers("kEDH") + server_context.maximum_version = ssl.TLSVersion.TLSv1_2 + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + cipher = stats["cipher"][0] + parts = cipher.split("-") + if not dhe_aliases.intersection(parts): + self.fail("Non-DH key exchange: " + cipher[0]) + + def test_ecdh_curve(self): + # server secp384r1, client auto + client_context, server_context, hostname = testing_context() + + server_context.set_ecdh_curve("secp384r1") + server_context.set_ciphers("ECDHE:!eNULL:!aNULL") + server_context.minimum_version = ssl.TLSVersion.TLSv1_2 + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + + # server auto, client secp384r1 + client_context, server_context, hostname = testing_context() + client_context.set_ecdh_curve("secp384r1") + server_context.set_ciphers("ECDHE:!eNULL:!aNULL") + server_context.minimum_version = ssl.TLSVersion.TLSv1_2 + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + + # server / client curve mismatch + client_context, server_context, hostname = testing_context() + client_context.set_ecdh_curve("prime256v1") + server_context.set_ecdh_curve("secp384r1") + server_context.set_ciphers("ECDHE:!eNULL:!aNULL") + server_context.minimum_version = ssl.TLSVersion.TLSv1_2 + with self.assertRaises(ssl.SSLError): + server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + + def test_selected_alpn_protocol(self): + # selected_alpn_protocol() is None unless ALPN is used. + client_context, server_context, hostname = testing_context() + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + self.assertIs(stats['client_alpn_protocol'], None) + + def test_selected_alpn_protocol_if_server_uses_alpn(self): + # selected_alpn_protocol() is None unless ALPN is used by the client. + client_context, server_context, hostname = testing_context() + server_context.set_alpn_protocols(['foo', 'bar']) + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + self.assertIs(stats['client_alpn_protocol'], None) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_alpn_protocols(self): + server_protocols = ['foo', 'bar', 'milkshake'] + protocol_tests = [ + (['foo', 'bar'], 'foo'), + (['bar', 'foo'], 'foo'), + (['milkshake'], 'milkshake'), + (['http/3.0', 'http/4.0'], None) + ] + for client_protocols, expected in protocol_tests: + client_context, server_context, hostname = testing_context() + server_context.set_alpn_protocols(server_protocols) + client_context.set_alpn_protocols(client_protocols) + + try: + stats = server_params_test(client_context, + server_context, + chatty=True, + connectionchatty=True, + sni_name=hostname) + except ssl.SSLError as e: + stats = e + + msg = "failed trying %s (s) and %s (c).\n" \ + "was expecting %s, but got %%s from the %%s" \ + % (str(server_protocols), str(client_protocols), + str(expected)) + client_result = stats['client_alpn_protocol'] + self.assertEqual(client_result, expected, + msg % (client_result, "client")) + server_result = stats['server_alpn_protocols'][-1] \ + if len(stats['server_alpn_protocols']) else 'nothing' + self.assertEqual(server_result, expected, + msg % (server_result, "server")) + + def test_npn_protocols(self): + assert not ssl.HAS_NPN + + def sni_contexts(self): + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + server_context.load_cert_chain(SIGNED_CERTFILE) + other_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + other_context.load_cert_chain(SIGNED_CERTFILE2) + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + client_context.load_verify_locations(SIGNING_CA) + return server_context, other_context, client_context + + def check_common_name(self, stats, name): + cert = stats['peercert'] + self.assertIn((('commonName', name),), cert['subject']) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_sni_callback(self): + calls = [] + server_context, other_context, client_context = self.sni_contexts() + + client_context.check_hostname = False + + def servername_cb(ssl_sock, server_name, initial_context): + calls.append((server_name, initial_context)) + if server_name is not None: + ssl_sock.context = other_context + server_context.set_servername_callback(servername_cb) + + stats = server_params_test(client_context, server_context, + chatty=True, + sni_name='supermessage') + # The hostname was fetched properly, and the certificate was + # changed for the connection. + self.assertEqual(calls, [("supermessage", server_context)]) + # CERTFILE4 was selected + self.check_common_name(stats, 'fakehostname') + + calls = [] + # The callback is called with server_name=None + stats = server_params_test(client_context, server_context, + chatty=True, + sni_name=None) + self.assertEqual(calls, [(None, server_context)]) + self.check_common_name(stats, SIGNED_CERTFILE_HOSTNAME) + + # Check disabling the callback + calls = [] + server_context.set_servername_callback(None) + + stats = server_params_test(client_context, server_context, + chatty=True, + sni_name='notfunny') + # Certificate didn't change + self.check_common_name(stats, SIGNED_CERTFILE_HOSTNAME) + self.assertEqual(calls, []) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_sni_callback_alert(self): + # Returning a TLS alert is reflected to the connecting client + server_context, other_context, client_context = self.sni_contexts() + + def cb_returning_alert(ssl_sock, server_name, initial_context): + return ssl.ALERT_DESCRIPTION_ACCESS_DENIED + server_context.set_servername_callback(cb_returning_alert) + with self.assertRaises(ssl.SSLError) as cm: + stats = server_params_test(client_context, server_context, + chatty=False, + sni_name='supermessage') + self.assertEqual(cm.exception.reason, 'TLSV1_ALERT_ACCESS_DENIED') + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_sni_callback_raising(self): + # Raising fails the connection with a TLS handshake failure alert. + server_context, other_context, client_context = self.sni_contexts() + + def cb_raising(ssl_sock, server_name, initial_context): + 1/0 + server_context.set_servername_callback(cb_raising) + + with support.catch_unraisable_exception() as catch: + with self.assertRaises(ssl.SSLError) as cm: + stats = server_params_test(client_context, server_context, + chatty=False, + sni_name='supermessage') + + # Allow for flexible libssl error messages. + regex = "(SSLV3_ALERT_HANDSHAKE_FAILURE|NO_PRIVATE_VALUE)" + self.assertRegex(cm.exception.reason, regex) + self.assertEqual(catch.unraisable.exc_type, ZeroDivisionError) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_sni_callback_wrong_return_type(self): + # Returning the wrong return type terminates the TLS connection + # with an internal error alert. + server_context, other_context, client_context = self.sni_contexts() + + def cb_wrong_return_type(ssl_sock, server_name, initial_context): + return "foo" + server_context.set_servername_callback(cb_wrong_return_type) + + with support.catch_unraisable_exception() as catch: + with self.assertRaises(ssl.SSLError) as cm: + stats = server_params_test(client_context, server_context, + chatty=False, + sni_name='supermessage') + + + self.assertEqual(cm.exception.reason, 'TLSV1_ALERT_INTERNAL_ERROR') + self.assertEqual(catch.unraisable.exc_type, TypeError) + + def test_shared_ciphers(self): + client_context, server_context, hostname = testing_context() + client_context.set_ciphers("AES128:AES256") + server_context.set_ciphers("AES256:eNULL") + expected_algs = [ + "AES256", "AES-256", + # TLS 1.3 ciphers are always enabled + "TLS_CHACHA20", "TLS_AES", + ] + + stats = server_params_test(client_context, server_context, + sni_name=hostname) + ciphers = stats['server_shared_ciphers'][0] + self.assertGreater(len(ciphers), 0) + for name, tls_version, bits in ciphers: + if not any(alg in name for alg in expected_algs): + self.fail(name) + + def test_read_write_after_close_raises_valuerror(self): + client_context, server_context, hostname = testing_context() + server = ThreadedEchoServer(context=server_context, chatty=False) + + with server: + s = client_context.wrap_socket(socket.socket(), + server_hostname=hostname) + s.connect((HOST, server.port)) + s.close() + + self.assertRaises(ValueError, s.read, 1024) + self.assertRaises(ValueError, s.write, b'hello') + + def test_sendfile(self): + TEST_DATA = b"x" * 512 + with open(os_helper.TESTFN, 'wb') as f: + f.write(TEST_DATA) + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + client_context, server_context, hostname = testing_context() + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + with open(os_helper.TESTFN, 'rb') as file: + s.sendfile(file) + self.assertEqual(s.recv(1024), TEST_DATA) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_session(self): + client_context, server_context, hostname = testing_context() + # TODO: sessions aren't compatible with TLSv1.3 yet + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + + # first connection without session + stats = server_params_test(client_context, server_context, + sni_name=hostname) + session = stats['session'] + self.assertTrue(session.id) + self.assertGreater(session.time, 0) + self.assertGreater(session.timeout, 0) + self.assertTrue(session.has_ticket) + self.assertGreater(session.ticket_lifetime_hint, 0) + self.assertFalse(stats['session_reused']) + sess_stat = server_context.session_stats() + self.assertEqual(sess_stat['accept'], 1) + self.assertEqual(sess_stat['hits'], 0) + + # reuse session + stats = server_params_test(client_context, server_context, + session=session, sni_name=hostname) + sess_stat = server_context.session_stats() + self.assertEqual(sess_stat['accept'], 2) + self.assertEqual(sess_stat['hits'], 1) + self.assertTrue(stats['session_reused']) + session2 = stats['session'] + self.assertEqual(session2.id, session.id) + self.assertEqual(session2, session) + self.assertIsNot(session2, session) + self.assertGreaterEqual(session2.time, session.time) + self.assertGreaterEqual(session2.timeout, session.timeout) + + # another one without session + stats = server_params_test(client_context, server_context, + sni_name=hostname) + self.assertFalse(stats['session_reused']) + session3 = stats['session'] + self.assertNotEqual(session3.id, session.id) + self.assertNotEqual(session3, session) + sess_stat = server_context.session_stats() + self.assertEqual(sess_stat['accept'], 3) + self.assertEqual(sess_stat['hits'], 1) + + # reuse session again + stats = server_params_test(client_context, server_context, + session=session, sni_name=hostname) + self.assertTrue(stats['session_reused']) + session4 = stats['session'] + self.assertEqual(session4.id, session.id) + self.assertEqual(session4, session) + self.assertGreaterEqual(session4.time, session.time) + self.assertGreaterEqual(session4.timeout, session.timeout) + sess_stat = server_context.session_stats() + self.assertEqual(sess_stat['accept'], 4) + self.assertEqual(sess_stat['hits'], 2) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_session_handling(self): + client_context, server_context, hostname = testing_context() + client_context2, _, _ = testing_context() + + # TODO: session reuse does not work with TLSv1.3 + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + client_context2.maximum_version = ssl.TLSVersion.TLSv1_2 + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + # session is None before handshake + self.assertEqual(s.session, None) + self.assertEqual(s.session_reused, None) + s.connect((HOST, server.port)) + session = s.session + self.assertTrue(session) + with self.assertRaises(TypeError) as e: + s.session = object + self.assertEqual(str(e.exception), 'Value is not a SSLSession.') + + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + # cannot set session after handshake + with self.assertRaises(ValueError) as e: + s.session = session + self.assertEqual(str(e.exception), + 'Cannot set session after handshake.') + + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + # can set session before handshake and before the + # connection was established + s.session = session + s.connect((HOST, server.port)) + self.assertEqual(s.session.id, session.id) + self.assertEqual(s.session, session) + self.assertEqual(s.session_reused, True) + + with client_context2.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + # cannot re-use session with a different SSLContext + with self.assertRaises(ValueError) as e: + s.session = session + s.connect((HOST, server.port)) + self.assertEqual(str(e.exception), + 'Session refers to a different SSLContext.') + + @requires_tls_version('TLSv1_2') + @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.skipUnless(ssl.HAS_PSK, 'TLS-PSK disabled on this OpenSSL build') + def test_psk(self): + psk = bytes.fromhex('deadbeef') + + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + client_context.check_hostname = False + client_context.verify_mode = ssl.CERT_NONE + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + client_context.set_ciphers('PSK') + client_context.set_psk_client_callback(lambda hint: (None, psk)) + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + server_context.maximum_version = ssl.TLSVersion.TLSv1_2 + server_context.set_ciphers('PSK') + server_context.set_psk_server_callback(lambda identity: psk) + + # correct PSK should connect + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket()) as s: + s.connect((HOST, server.port)) + + # incorrect PSK should fail + incorrect_psk = bytes.fromhex('cafebabe') + client_context.set_psk_client_callback(lambda hint: (None, incorrect_psk)) + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket()) as s: + with self.assertRaises(ssl.SSLError): + s.connect((HOST, server.port)) + + # identity_hint and client_identity should be sent to the other side + identity_hint = 'identity-hint' + client_identity = 'client-identity' + + def client_callback(hint): + self.assertEqual(hint, identity_hint) + return client_identity, psk + + def server_callback(identity): + self.assertEqual(identity, client_identity) + return psk + + client_context.set_psk_client_callback(client_callback) + server_context.set_psk_server_callback(server_callback, identity_hint) + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket()) as s: + s.connect((HOST, server.port)) + + # adding client callback to server or vice versa raises an exception + with self.assertRaisesRegex(ssl.SSLError, 'Cannot add PSK server callback'): + client_context.set_psk_server_callback(server_callback, identity_hint) + with self.assertRaisesRegex(ssl.SSLError, 'Cannot add PSK client callback'): + server_context.set_psk_client_callback(client_callback) + + # test with UTF-8 identities + identity_hint = '身份暗示' # Translation: "Identity hint" + client_identity = '客户身份' # Translation: "Customer identity" + + client_context.set_psk_client_callback(client_callback) + server_context.set_psk_server_callback(server_callback, identity_hint) + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket()) as s: + s.connect((HOST, server.port)) + + @requires_tls_version('TLSv1_3') + @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.skipUnless(ssl.HAS_PSK, 'TLS-PSK disabled on this OpenSSL build') + def test_psk_tls1_3(self): + psk = bytes.fromhex('deadbeef') + identity_hint = 'identity-hint' + client_identity = 'client-identity' + + def client_callback(hint): + # identity_hint is not sent to the client in TLS 1.3 + self.assertIsNone(hint) + return client_identity, psk + + def server_callback(identity): + self.assertEqual(identity, client_identity) + return psk + + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + client_context.check_hostname = False + client_context.verify_mode = ssl.CERT_NONE + client_context.minimum_version = ssl.TLSVersion.TLSv1_3 + client_context.set_ciphers('PSK') + client_context.set_psk_client_callback(client_callback) + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + server_context.minimum_version = ssl.TLSVersion.TLSv1_3 + server_context.set_ciphers('PSK') + server_context.set_psk_server_callback(server_callback, identity_hint) + + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket()) as s: + s.connect((HOST, server.port)) + + +@unittest.skipUnless(has_tls_version('TLSv1_3'), "Test needs TLS 1.3") +class TestPostHandshakeAuth(unittest.TestCase): + def test_pha_setter(self): + protocols = [ + ssl.PROTOCOL_TLS_SERVER, ssl.PROTOCOL_TLS_CLIENT + ] + for protocol in protocols: + ctx = ssl.SSLContext(protocol) + self.assertEqual(ctx.post_handshake_auth, False) + + ctx.post_handshake_auth = True + self.assertEqual(ctx.post_handshake_auth, True) + + ctx.verify_mode = ssl.CERT_REQUIRED + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + self.assertEqual(ctx.post_handshake_auth, True) + + ctx.post_handshake_auth = False + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + self.assertEqual(ctx.post_handshake_auth, False) + + ctx.verify_mode = ssl.CERT_OPTIONAL + ctx.post_handshake_auth = True + self.assertEqual(ctx.verify_mode, ssl.CERT_OPTIONAL) + self.assertEqual(ctx.post_handshake_auth, True) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_pha_required(self): + client_context, server_context, hostname = testing_context() + server_context.post_handshake_auth = True + server_context.verify_mode = ssl.CERT_REQUIRED + client_context.post_handshake_auth = True + client_context.load_cert_chain(SIGNED_CERTFILE) + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + s.write(b'HASCERT') + self.assertEqual(s.recv(1024), b'FALSE\n') + s.write(b'PHA') + self.assertEqual(s.recv(1024), b'OK\n') + s.write(b'HASCERT') + self.assertEqual(s.recv(1024), b'TRUE\n') + # PHA method just returns true when cert is already available + s.write(b'PHA') + self.assertEqual(s.recv(1024), b'OK\n') + s.write(b'GETCERT') + cert_text = s.recv(4096).decode('us-ascii') + self.assertIn('Python Software Foundation CA', cert_text) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_pha_required_nocert(self): + client_context, server_context, hostname = testing_context() + server_context.post_handshake_auth = True + server_context.verify_mode = ssl.CERT_REQUIRED + client_context.post_handshake_auth = True + + def msg_cb(conn, direction, version, content_type, msg_type, data): + if support.verbose and content_type == _TLSContentType.ALERT: + info = (conn, direction, version, content_type, msg_type, data) + sys.stdout.write(f"TLS: {info!r}\n") + + server_context._msg_callback = msg_cb + client_context._msg_callback = msg_cb + + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname, + suppress_ragged_eofs=False) as s: + s.connect((HOST, server.port)) + s.write(b'PHA') + # test sometimes fails with EOF error. Test passes as long as + # server aborts connection with an error. + with self.assertRaisesRegex( + OSError, + ('certificate required' + '|EOF occurred' + '|closed by the remote host' + '|Connection reset by peer' + '|Broken pipe') + ): + # receive CertificateRequest + data = s.recv(1024) + self.assertEqual(data, b'OK\n') + + # send empty Certificate + Finish + s.write(b'HASCERT') + + # receive alert + s.recv(1024) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_pha_optional(self): + if support.verbose: + sys.stdout.write("\n") + + client_context, server_context, hostname = testing_context() + server_context.post_handshake_auth = True + server_context.verify_mode = ssl.CERT_REQUIRED + client_context.post_handshake_auth = True + client_context.load_cert_chain(SIGNED_CERTFILE) + + # check CERT_OPTIONAL + server_context.verify_mode = ssl.CERT_OPTIONAL + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + s.write(b'HASCERT') + self.assertEqual(s.recv(1024), b'FALSE\n') + s.write(b'PHA') + self.assertEqual(s.recv(1024), b'OK\n') + s.write(b'HASCERT') + self.assertEqual(s.recv(1024), b'TRUE\n') + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_pha_optional_nocert(self): + if support.verbose: + sys.stdout.write("\n") + + client_context, server_context, hostname = testing_context() + server_context.post_handshake_auth = True + server_context.verify_mode = ssl.CERT_OPTIONAL + client_context.post_handshake_auth = True + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + s.write(b'HASCERT') + self.assertEqual(s.recv(1024), b'FALSE\n') + s.write(b'PHA') + self.assertEqual(s.recv(1024), b'OK\n') + # optional doesn't fail when client does not have a cert + s.write(b'HASCERT') + self.assertEqual(s.recv(1024), b'FALSE\n') + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_pha_no_pha_client(self): + client_context, server_context, hostname = testing_context() + server_context.post_handshake_auth = True + server_context.verify_mode = ssl.CERT_REQUIRED + client_context.load_cert_chain(SIGNED_CERTFILE) + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + with self.assertRaisesRegex(ssl.SSLError, 'not server'): + s.verify_client_post_handshake() + s.write(b'PHA') + self.assertIn(b'extension not received', s.recv(1024)) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_pha_no_pha_server(self): + # server doesn't have PHA enabled, cert is requested in handshake + client_context, server_context, hostname = testing_context() + server_context.verify_mode = ssl.CERT_REQUIRED + client_context.post_handshake_auth = True + client_context.load_cert_chain(SIGNED_CERTFILE) + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + s.write(b'HASCERT') + self.assertEqual(s.recv(1024), b'TRUE\n') + # PHA doesn't fail if there is already a cert + s.write(b'PHA') + self.assertEqual(s.recv(1024), b'OK\n') + s.write(b'HASCERT') + self.assertEqual(s.recv(1024), b'TRUE\n') + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_pha_not_tls13(self): + # TLS 1.2 + client_context, server_context, hostname = testing_context() + server_context.verify_mode = ssl.CERT_REQUIRED + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + client_context.post_handshake_auth = True + client_context.load_cert_chain(SIGNED_CERTFILE) + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + # PHA fails for TLS != 1.3 + s.write(b'PHA') + self.assertIn(b'WRONG_SSL_VERSION', s.recv(1024)) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_bpo37428_pha_cert_none(self): + # verify that post_handshake_auth does not implicitly enable cert + # validation. + hostname = SIGNED_CERTFILE_HOSTNAME + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + client_context.post_handshake_auth = True + client_context.load_cert_chain(SIGNED_CERTFILE) + # no cert validation and CA on client side + client_context.check_hostname = False + client_context.verify_mode = ssl.CERT_NONE + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + server_context.load_cert_chain(SIGNED_CERTFILE) + server_context.load_verify_locations(SIGNING_CA) + server_context.post_handshake_auth = True + server_context.verify_mode = ssl.CERT_REQUIRED + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + s.write(b'HASCERT') + self.assertEqual(s.recv(1024), b'FALSE\n') + s.write(b'PHA') + self.assertEqual(s.recv(1024), b'OK\n') + s.write(b'HASCERT') + self.assertEqual(s.recv(1024), b'TRUE\n') + # server cert has not been validated + self.assertEqual(s.getpeercert(), {}) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_internal_chain_client(self): + client_context, server_context, hostname = testing_context( + server_chain=False + ) + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket( + socket.socket(), + server_hostname=hostname + ) as s: + s.connect((HOST, server.port)) + vc = s._sslobj.get_verified_chain() + self.assertEqual(len(vc), 2) + ee, ca = vc + uvc = s._sslobj.get_unverified_chain() + self.assertEqual(len(uvc), 1) + + self.assertEqual(ee, uvc[0]) + self.assertEqual(hash(ee), hash(uvc[0])) + self.assertEqual(repr(ee), repr(uvc[0])) + + self.assertNotEqual(ee, ca) + self.assertNotEqual(hash(ee), hash(ca)) + self.assertNotEqual(repr(ee), repr(ca)) + self.assertNotEqual(ee.get_info(), ca.get_info()) + self.assertIn("CN=localhost", repr(ee)) + self.assertIn("CN=our-ca-server", repr(ca)) + + pem = ee.public_bytes(_ssl.ENCODING_PEM) + der = ee.public_bytes(_ssl.ENCODING_DER) + self.assertIsInstance(pem, str) + self.assertIn("-----BEGIN CERTIFICATE-----", pem) + self.assertIsInstance(der, bytes) + self.assertEqual( + ssl.PEM_cert_to_DER_cert(pem), der + ) + + def test_certificate_chain(self): + client_context, server_context, hostname = testing_context( + server_chain=False + ) + server = ThreadedEchoServer(context=server_context, chatty=False) + + with open(SIGNING_CA) as f: + expected_ca_cert = ssl.PEM_cert_to_DER_cert(f.read()) + + with open(SINGED_CERTFILE_ONLY) as f: + expected_ee_cert = ssl.PEM_cert_to_DER_cert(f.read()) + + with server: + with client_context.wrap_socket( + socket.socket(), + server_hostname=hostname + ) as s: + s.connect((HOST, server.port)) + vc = s.get_verified_chain() + self.assertEqual(len(vc), 2) + + ee, ca = vc + self.assertIsInstance(ee, bytes) + self.assertIsInstance(ca, bytes) + self.assertEqual(expected_ca_cert, ca) + self.assertEqual(expected_ee_cert, ee) + + uvc = s.get_unverified_chain() + self.assertEqual(len(uvc), 1) + self.assertIsInstance(uvc[0], bytes) + + self.assertEqual(ee, uvc[0]) + self.assertNotEqual(ee, ca) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_internal_chain_server(self): + client_context, server_context, hostname = testing_context() + client_context.load_cert_chain(SIGNED_CERTFILE) + server_context.verify_mode = ssl.CERT_REQUIRED + server_context.maximum_version = ssl.TLSVersion.TLSv1_2 + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket( + socket.socket(), + server_hostname=hostname + ) as s: + s.connect((HOST, server.port)) + s.write(b'VERIFIEDCHAIN\n') + res = s.recv(1024) + self.assertEqual(res, b'\x02\n') + s.write(b'UNVERIFIEDCHAIN\n') + res = s.recv(1024) + self.assertEqual(res, b'\x02\n') + + +HAS_KEYLOG = hasattr(ssl.SSLContext, 'keylog_filename') +requires_keylog = unittest.skipUnless( + HAS_KEYLOG, 'test requires OpenSSL 1.1.1 with keylog callback') + +class TestSSLDebug(unittest.TestCase): + + def keylog_lines(self, fname=os_helper.TESTFN): + with open(fname) as f: + return len(list(f)) + + @requires_keylog + def test_keylog_defaults(self): + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.keylog_filename, None) + + self.assertFalse(os.path.isfile(os_helper.TESTFN)) + try: + ctx.keylog_filename = os_helper.TESTFN + except RuntimeError: + if Py_DEBUG_WIN32: + self.skipTest("not supported on Win32 debug build") + raise + self.assertEqual(ctx.keylog_filename, os_helper.TESTFN) + self.assertTrue(os.path.isfile(os_helper.TESTFN)) + self.assertEqual(self.keylog_lines(), 1) + + ctx.keylog_filename = None + self.assertEqual(ctx.keylog_filename, None) + + with self.assertRaises((IsADirectoryError, PermissionError)): + # Windows raises PermissionError + ctx.keylog_filename = os.path.dirname( + os.path.abspath(os_helper.TESTFN)) + + with self.assertRaises(TypeError): + ctx.keylog_filename = 1 + + @requires_keylog + def test_keylog_filename(self): + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + client_context, server_context, hostname = testing_context() + + try: + client_context.keylog_filename = os_helper.TESTFN + except RuntimeError: + if Py_DEBUG_WIN32: + self.skipTest("not supported on Win32 debug build") + raise + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + # header, 5 lines for TLS 1.3 + self.assertEqual(self.keylog_lines(), 6) + + client_context.keylog_filename = None + server_context.keylog_filename = os_helper.TESTFN + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + self.assertGreaterEqual(self.keylog_lines(), 11) + + client_context.keylog_filename = os_helper.TESTFN + server_context.keylog_filename = os_helper.TESTFN + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + self.assertGreaterEqual(self.keylog_lines(), 21) + + client_context.keylog_filename = None + server_context.keylog_filename = None + + @requires_keylog + @unittest.skipIf(sys.flags.ignore_environment, + "test is not compatible with ignore_environment") + def test_keylog_env(self): + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + with unittest.mock.patch.dict(os.environ): + os.environ['SSLKEYLOGFILE'] = os_helper.TESTFN + self.assertEqual(os.environ['SSLKEYLOGFILE'], os_helper.TESTFN) + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.keylog_filename, None) + + try: + ctx = ssl.create_default_context() + except RuntimeError: + if Py_DEBUG_WIN32: + self.skipTest("not supported on Win32 debug build") + raise + self.assertEqual(ctx.keylog_filename, os_helper.TESTFN) + + ctx = ssl._create_stdlib_context() + self.assertEqual(ctx.keylog_filename, os_helper.TESTFN) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_msg_callback(self): + client_context, server_context, hostname = testing_context() + + def msg_cb(conn, direction, version, content_type, msg_type, data): + pass + + self.assertIs(client_context._msg_callback, None) + client_context._msg_callback = msg_cb + self.assertIs(client_context._msg_callback, msg_cb) + with self.assertRaises(TypeError): + client_context._msg_callback = object() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_msg_callback_tls12(self): + client_context, server_context, hostname = testing_context() + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + + msg = [] + + def msg_cb(conn, direction, version, content_type, msg_type, data): + self.assertIsInstance(conn, ssl.SSLSocket) + self.assertIsInstance(data, bytes) + self.assertIn(direction, {'read', 'write'}) + msg.append((direction, version, content_type, msg_type)) + + client_context._msg_callback = msg_cb + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + + self.assertIn( + ("read", TLSVersion.TLSv1_2, _TLSContentType.HANDSHAKE, + _TLSMessageType.SERVER_KEY_EXCHANGE), + msg + ) + self.assertIn( + ("write", TLSVersion.TLSv1_2, _TLSContentType.CHANGE_CIPHER_SPEC, + _TLSMessageType.CHANGE_CIPHER_SPEC), + msg + ) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_msg_callback_deadlock_bpo43577(self): + client_context, server_context, hostname = testing_context() + server_context2 = testing_context()[1] + + def msg_cb(conn, direction, version, content_type, msg_type, data): + pass + + def sni_cb(sock, servername, ctx): + sock.context = server_context2 + + server_context._msg_callback = msg_cb + server_context.sni_callback = sni_cb + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + + +def set_socket_so_linger_on_with_zero_timeout(sock): + sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0)) + + +class TestPreHandshakeClose(unittest.TestCase): + """Verify behavior of close sockets with received data before to the handshake. + """ + + class SingleConnectionTestServerThread(threading.Thread): + + def __init__(self, *, name, call_after_accept, timeout=None): + self.call_after_accept = call_after_accept + self.received_data = b'' # set by .run() + self.wrap_error = None # set by .run() + self.listener = None # set by .start() + self.port = None # set by .start() + if timeout is None: + self.timeout = support.SHORT_TIMEOUT + else: + self.timeout = timeout + super().__init__(name=name) + + def __enter__(self): + self.start() + return self + + def __exit__(self, *args): + try: + if self.listener: + self.listener.close() + except OSError: + pass + self.join() + self.wrap_error = None # avoid dangling references + + def start(self): + self.ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + self.ssl_ctx.verify_mode = ssl.CERT_REQUIRED + self.ssl_ctx.load_verify_locations(cafile=ONLYCERT) + self.ssl_ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY) + self.listener = socket.socket() + self.port = socket_helper.bind_port(self.listener) + self.listener.settimeout(self.timeout) + self.listener.listen(1) + super().start() + + def run(self): + try: + conn, address = self.listener.accept() + except TimeoutError: + # on timeout, just close the listener + return + finally: + self.listener.close() + + with conn: + if self.call_after_accept(conn): + return + try: + tls_socket = self.ssl_ctx.wrap_socket(conn, server_side=True) + except OSError as err: # ssl.SSLError inherits from OSError + self.wrap_error = err + else: + try: + self.received_data = tls_socket.recv(400) + except OSError: + pass # closed, protocol error, etc. + + def non_linux_skip_if_other_okay_error(self, err): + if sys.platform in ("linux", "android"): + return # Expect the full test setup to always work on Linux. + if (isinstance(err, ConnectionResetError) or + (isinstance(err, OSError) and err.errno == errno.EINVAL) or + re.search('wrong.version.number', str(getattr(err, "reason", "")), re.I)): + # On Windows the TCP RST leads to a ConnectionResetError + # (ECONNRESET) which Linux doesn't appear to surface to userspace. + # If wrap_socket() winds up on the "if connected:" path and doing + # the actual wrapping... we get an SSLError from OpenSSL. Typically + # WRONG_VERSION_NUMBER. While appropriate, neither is the scenario + # we're specifically trying to test. The way this test is written + # is known to work on Linux. We'll skip it anywhere else that it + # does not present as doing so. + try: + self.skipTest(f"Could not recreate conditions on {sys.platform}:" + f" {err=}") + finally: + # gh-108342: Explicitly break the reference cycle + err = None + + # If maintaining this conditional winds up being a problem. + # just turn this into an unconditional skip anything but Linux. + # The important thing is that our CI has the logic covered. + + def test_preauth_data_to_tls_server(self): + server_accept_called = threading.Event() + ready_for_server_wrap_socket = threading.Event() + + def call_after_accept(unused): + server_accept_called.set() + if not ready_for_server_wrap_socket.wait(support.SHORT_TIMEOUT): + raise RuntimeError("wrap_socket event never set, test may fail.") + return False # Tell the server thread to continue. + + server = self.SingleConnectionTestServerThread( + call_after_accept=call_after_accept, + name="preauth_data_to_tls_server") + self.enterContext(server) # starts it & unittest.TestCase stops it. + + with socket.socket() as client: + client.connect(server.listener.getsockname()) + # This forces an immediate connection close via RST on .close(). + set_socket_so_linger_on_with_zero_timeout(client) + client.setblocking(False) + + server_accept_called.wait() + client.send(b"DELETE /data HTTP/1.0\r\n\r\n") + client.close() # RST + + ready_for_server_wrap_socket.set() + server.join() + + wrap_error = server.wrap_error + server.wrap_error = None + try: + self.assertEqual(b"", server.received_data) + self.assertIsInstance(wrap_error, OSError) # All platforms. + self.non_linux_skip_if_other_okay_error(wrap_error) + self.assertIsInstance(wrap_error, ssl.SSLError) + self.assertIn("before TLS handshake with data", wrap_error.args[1]) + self.assertIn("before TLS handshake with data", wrap_error.reason) + self.assertNotEqual(0, wrap_error.args[0]) + self.assertIsNone(wrap_error.library, msg="attr must exist") + finally: + # gh-108342: Explicitly break the reference cycle + wrap_error = None + server = None + + def test_preauth_data_to_tls_client(self): + server_can_continue_with_wrap_socket = threading.Event() + client_can_continue_with_wrap_socket = threading.Event() + + def call_after_accept(conn_to_client): + if not server_can_continue_with_wrap_socket.wait(support.SHORT_TIMEOUT): + print("ERROR: test client took too long") + + # This forces an immediate connection close via RST on .close(). + set_socket_so_linger_on_with_zero_timeout(conn_to_client) + conn_to_client.send( + b"HTTP/1.0 307 Temporary Redirect\r\n" + b"Location: https://example.com/someone-elses-server\r\n" + b"\r\n") + conn_to_client.close() # RST + client_can_continue_with_wrap_socket.set() + return True # Tell the server to stop. + + server = self.SingleConnectionTestServerThread( + call_after_accept=call_after_accept, + name="preauth_data_to_tls_client") + self.enterContext(server) # starts it & unittest.TestCase stops it. + # Redundant; call_after_accept sets SO_LINGER on the accepted conn. + set_socket_so_linger_on_with_zero_timeout(server.listener) + + with socket.socket() as client: + client.connect(server.listener.getsockname()) + server_can_continue_with_wrap_socket.set() + + if not client_can_continue_with_wrap_socket.wait(support.SHORT_TIMEOUT): + self.fail("test server took too long") + ssl_ctx = ssl.create_default_context() + try: + tls_client = ssl_ctx.wrap_socket( + client, server_hostname="localhost") + except OSError as err: # SSLError inherits from OSError + wrap_error = err + received_data = b"" + else: + wrap_error = None + received_data = tls_client.recv(400) + tls_client.close() + + server.join() + try: + self.assertEqual(b"", received_data) + self.assertIsInstance(wrap_error, OSError) # All platforms. + self.non_linux_skip_if_other_okay_error(wrap_error) + self.assertIsInstance(wrap_error, ssl.SSLError) + self.assertIn("before TLS handshake with data", wrap_error.args[1]) + self.assertIn("before TLS handshake with data", wrap_error.reason) + self.assertNotEqual(0, wrap_error.args[0]) + self.assertIsNone(wrap_error.library, msg="attr must exist") + finally: + # gh-108342: Explicitly break the reference cycle + with warnings_helper.check_no_resource_warning(self): + wrap_error = None + server = None + + def test_https_client_non_tls_response_ignored(self): + server_responding = threading.Event() + + class SynchronizedHTTPSConnection(http.client.HTTPSConnection): + def connect(self): + # Call clear text HTTP connect(), not the encrypted HTTPS (TLS) + # connect(): wrap_socket() is called manually below. + http.client.HTTPConnection.connect(self) + + # Wait for our fault injection server to have done its thing. + if not server_responding.wait(support.SHORT_TIMEOUT) and support.verbose: + sys.stdout.write("server_responding event never set.") + self.sock = self._context.wrap_socket( + self.sock, server_hostname=self.host) + + def call_after_accept(conn_to_client): + # This forces an immediate connection close via RST on .close(). + set_socket_so_linger_on_with_zero_timeout(conn_to_client) + conn_to_client.send( + b"HTTP/1.0 402 Payment Required\r\n" + b"\r\n") + conn_to_client.close() # RST + server_responding.set() + return True # Tell the server to stop. + + timeout = 2.0 + server = self.SingleConnectionTestServerThread( + call_after_accept=call_after_accept, + name="non_tls_http_RST_responder", + timeout=timeout) + self.enterContext(server) # starts it & unittest.TestCase stops it. + # Redundant; call_after_accept sets SO_LINGER on the accepted conn. + set_socket_so_linger_on_with_zero_timeout(server.listener) + + connection = SynchronizedHTTPSConnection( + server.listener.getsockname()[0], + port=server.port, + context=ssl.create_default_context(), + timeout=timeout, + ) + + # There are lots of reasons this raises as desired, long before this + # test was added. Sending the request requires a successful TLS wrapped + # socket; that fails if the connection is broken. It may seem pointless + # to test this. It serves as an illustration of something that we never + # want to happen... properly not happening. + with warnings_helper.check_no_resource_warning(self), \ + self.assertRaises(OSError): + connection.request("HEAD", "/test", headers={"Host": "localhost"}) + response = connection.getresponse() + + server.join() + + +class TestEnumerations(unittest.TestCase): + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_tlsversion(self): + class CheckedTLSVersion(enum.IntEnum): + MINIMUM_SUPPORTED = _ssl.PROTO_MINIMUM_SUPPORTED + SSLv3 = _ssl.PROTO_SSLv3 + TLSv1 = _ssl.PROTO_TLSv1 + TLSv1_1 = _ssl.PROTO_TLSv1_1 + TLSv1_2 = _ssl.PROTO_TLSv1_2 + TLSv1_3 = _ssl.PROTO_TLSv1_3 + MAXIMUM_SUPPORTED = _ssl.PROTO_MAXIMUM_SUPPORTED + enum._test_simple_enum(CheckedTLSVersion, TLSVersion) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_tlscontenttype(self): + class Checked_TLSContentType(enum.IntEnum): + """Content types (record layer) + + See RFC 8446, section B.1 + """ + CHANGE_CIPHER_SPEC = 20 + ALERT = 21 + HANDSHAKE = 22 + APPLICATION_DATA = 23 + # pseudo content types + HEADER = 0x100 + INNER_CONTENT_TYPE = 0x101 + enum._test_simple_enum(Checked_TLSContentType, _TLSContentType) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_tlsalerttype(self): + class Checked_TLSAlertType(enum.IntEnum): + """Alert types for TLSContentType.ALERT messages + + See RFC 8466, section B.2 + """ + CLOSE_NOTIFY = 0 + UNEXPECTED_MESSAGE = 10 + BAD_RECORD_MAC = 20 + DECRYPTION_FAILED = 21 + RECORD_OVERFLOW = 22 + DECOMPRESSION_FAILURE = 30 + HANDSHAKE_FAILURE = 40 + NO_CERTIFICATE = 41 + BAD_CERTIFICATE = 42 + UNSUPPORTED_CERTIFICATE = 43 + CERTIFICATE_REVOKED = 44 + CERTIFICATE_EXPIRED = 45 + CERTIFICATE_UNKNOWN = 46 + ILLEGAL_PARAMETER = 47 + UNKNOWN_CA = 48 + ACCESS_DENIED = 49 + DECODE_ERROR = 50 + DECRYPT_ERROR = 51 + EXPORT_RESTRICTION = 60 + PROTOCOL_VERSION = 70 + INSUFFICIENT_SECURITY = 71 + INTERNAL_ERROR = 80 + INAPPROPRIATE_FALLBACK = 86 + USER_CANCELED = 90 + NO_RENEGOTIATION = 100 + MISSING_EXTENSION = 109 + UNSUPPORTED_EXTENSION = 110 + CERTIFICATE_UNOBTAINABLE = 111 + UNRECOGNIZED_NAME = 112 + BAD_CERTIFICATE_STATUS_RESPONSE = 113 + BAD_CERTIFICATE_HASH_VALUE = 114 + UNKNOWN_PSK_IDENTITY = 115 + CERTIFICATE_REQUIRED = 116 + NO_APPLICATION_PROTOCOL = 120 + enum._test_simple_enum(Checked_TLSAlertType, _TLSAlertType) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_tlsmessagetype(self): + class Checked_TLSMessageType(enum.IntEnum): + """Message types (handshake protocol) + + See RFC 8446, section B.3 + """ + HELLO_REQUEST = 0 + CLIENT_HELLO = 1 + SERVER_HELLO = 2 + HELLO_VERIFY_REQUEST = 3 + NEWSESSION_TICKET = 4 + END_OF_EARLY_DATA = 5 + HELLO_RETRY_REQUEST = 6 + ENCRYPTED_EXTENSIONS = 8 + CERTIFICATE = 11 + SERVER_KEY_EXCHANGE = 12 + CERTIFICATE_REQUEST = 13 + SERVER_DONE = 14 + CERTIFICATE_VERIFY = 15 + CLIENT_KEY_EXCHANGE = 16 + FINISHED = 20 + CERTIFICATE_URL = 21 + CERTIFICATE_STATUS = 22 + SUPPLEMENTAL_DATA = 23 + KEY_UPDATE = 24 + NEXT_PROTO = 67 + MESSAGE_HASH = 254 + CHANGE_CIPHER_SPEC = 0x0101 + enum._test_simple_enum(Checked_TLSMessageType, _TLSMessageType) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_sslmethod(self): + Checked_SSLMethod = enum._old_convert_( + enum.IntEnum, '_SSLMethod', 'ssl', + lambda name: name.startswith('PROTOCOL_') and name != 'PROTOCOL_SSLv23', + source=ssl._ssl, + ) + # This member is assigned dynamically in `ssl.py`: + Checked_SSLMethod.PROTOCOL_SSLv23 = Checked_SSLMethod.PROTOCOL_TLS + enum._test_simple_enum(Checked_SSLMethod, ssl._SSLMethod) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_options(self): + CheckedOptions = enum._old_convert_( + enum.IntFlag, 'Options', 'ssl', + lambda name: name.startswith('OP_'), + source=ssl._ssl, + ) + enum._test_simple_enum(CheckedOptions, ssl.Options) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_alertdescription(self): + CheckedAlertDescription = enum._old_convert_( + enum.IntEnum, 'AlertDescription', 'ssl', + lambda name: name.startswith('ALERT_DESCRIPTION_'), + source=ssl._ssl, + ) + enum._test_simple_enum(CheckedAlertDescription, ssl.AlertDescription) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_sslerrornumber(self): + Checked_SSLErrorNumber = enum._old_convert_( + enum.IntEnum, 'SSLErrorNumber', 'ssl', + lambda name: name.startswith('SSL_ERROR_'), + source=ssl._ssl, + ) + enum._test_simple_enum(Checked_SSLErrorNumber, ssl.SSLErrorNumber) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_verifyflags(self): + CheckedVerifyFlags = enum._old_convert_( + enum.IntFlag, 'VerifyFlags', 'ssl', + lambda name: name.startswith('VERIFY_'), + source=ssl._ssl, + ) + enum._test_simple_enum(CheckedVerifyFlags, ssl.VerifyFlags) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_verifymode(self): + CheckedVerifyMode = enum._old_convert_( + enum.IntEnum, 'VerifyMode', 'ssl', + lambda name: name.startswith('CERT_'), + source=ssl._ssl, + ) + enum._test_simple_enum(CheckedVerifyMode, ssl.VerifyMode) + + +def setUpModule(): + if support.verbose: + plats = { + 'Mac': platform.mac_ver, + 'Windows': platform.win32_ver, + } + for name, func in plats.items(): + plat = func() + if plat and plat[0]: + plat = '%s %r' % (name, plat) + break + else: + plat = repr(platform.platform()) + print("test_ssl: testing with %r %r" % + (ssl.OPENSSL_VERSION, ssl.OPENSSL_VERSION_INFO)) + print(" under %s" % plat) + print(" HAS_SNI = %r" % ssl.HAS_SNI) + print(" OP_ALL = 0x%8x" % ssl.OP_ALL) + try: + print(" OP_NO_TLSv1_1 = 0x%8x" % ssl.OP_NO_TLSv1_1) + except AttributeError: + pass + + for filename in [ + CERTFILE, BYTES_CERTFILE, + ONLYCERT, ONLYKEY, BYTES_ONLYCERT, BYTES_ONLYKEY, + SIGNED_CERTFILE, SIGNED_CERTFILE2, SIGNING_CA, + BADCERT, BADKEY, EMPTYCERT]: + if not os.path.exists(filename): + raise support.TestFailed("Can't read certificate file %r" % filename) + + thread_info = threading_helper.threading_setup() + unittest.addModuleCleanup(threading_helper.threading_cleanup, *thread_info) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py index 3b309b78695..b40a2410bcc 100644 --- a/Lib/test/test_urllib2_localnet.py +++ b/Lib/test/test_urllib2_localnet.py @@ -19,9 +19,9 @@ here = os.path.dirname(__file__) # Self-signed cert file for 'localhost' -CERT_localhost = os.path.join(here, 'keycert.pem') +CERT_localhost = os.path.join(here, 'certdata', 'keycert.pem') # Self-signed cert file for 'fakehostname' -CERT_fakehostname = os.path.join(here, 'keycert2.pem') +CERT_fakehostname = os.path.join(here, 'certdata', 'keycert2.pem') # Loopback http server infrastructure From 8f048dd9fddb2360e2ce3cf4e3dbd9c48b8140bd Mon Sep 17 00:00:00 2001 From: Yash Suthar <yashsuthar983@gmail.com> Date: Wed, 29 Oct 2025 15:39:37 +0530 Subject: [PATCH 324/819] Implement minimal builtins.anext() (#6226) * Implement minimal builtins.anext() * Removed expectedFailure for builtins.anext() tests * Removed expectedFailure from test_contextlib_async tests fixed by anext --------- Signed-off-by: Yash Suthar <yashsuthar983@gmail.com> --- Lib/test/test_asyncgen.py | 6 ------ Lib/test/test_contextlib_async.py | 32 ------------------------------- vm/src/stdlib/builtins.rs | 16 ++++++++++++++++ 3 files changed, 16 insertions(+), 38 deletions(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index f97316adebb..797ce1c091c 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -603,8 +603,6 @@ async def check_anext_returning_iterator(self, aiter_class): await awaitable return "completed" - # TODO: RUSTPYTHON, NameError: name 'anext' is not defined - @unittest.expectedFailure def test_anext_return_iterator(self): class WithIterAnext: def __aiter__(self): @@ -614,8 +612,6 @@ def __anext__(self): result = self.loop.run_until_complete(self.check_anext_returning_iterator(WithIterAnext)) self.assertEqual(result, "completed") - # TODO: RUSTPYTHON, NameError: name 'anext' is not defined - @unittest.expectedFailure def test_anext_return_generator(self): class WithGenAnext: def __aiter__(self): @@ -625,8 +621,6 @@ def __anext__(self): result = self.loop.run_until_complete(self.check_anext_returning_iterator(WithGenAnext)) self.assertEqual(result, "completed") - # TODO: RUSTPYTHON, NameError: name 'anext' is not defined - @unittest.expectedFailure def test_anext_await_raises(self): class RaisingAwaitable: def __await__(self): diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 5673c1b4bc5..f7edcfe55da 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -40,8 +40,6 @@ async def __aexit__(self, *args): manager = DefaultAsyncContextManager() manager.var = 42 - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_async_gen_propagates_generator_exit(self): # A regression test for https://bugs.python.org/issue33786. @@ -94,8 +92,6 @@ class NoneAexit(ManagerFromScratch): class AsyncContextManagerTestCase(unittest.IsolatedAsyncioTestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_contextmanager_plain(self): state = [] @asynccontextmanager @@ -109,8 +105,6 @@ async def woohoo(): state.append(x) self.assertEqual(state, [1, 42, 999]) - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_contextmanager_finally(self): state = [] @asynccontextmanager @@ -185,8 +179,6 @@ class StopAsyncIterationSubclass(StopAsyncIteration): self.assertEqual(frames[0].name, 'test_contextmanager_traceback') self.assertEqual(frames[0].line, 'raise stop_exc') - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_contextmanager_no_reraise(self): @asynccontextmanager async def whee(): @@ -196,8 +188,6 @@ async def whee(): # Calling __aexit__ should not result in an exception self.assertFalse(await ctx.__aexit__(TypeError, TypeError("foo"), None)) - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_contextmanager_trap_yield_after_throw(self): @asynccontextmanager async def whoo(): @@ -213,8 +203,6 @@ async def whoo(): # The "gen" attribute is an implementation detail. self.assertFalse(ctx.gen.ag_suspended) - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_contextmanager_trap_no_yield(self): @asynccontextmanager async def whoo(): @@ -224,8 +212,6 @@ async def whoo(): with self.assertRaises(RuntimeError): await ctx.__aenter__() - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_contextmanager_trap_second_yield(self): @asynccontextmanager async def whoo(): @@ -239,8 +225,6 @@ async def whoo(): # The "gen" attribute is an implementation detail. self.assertFalse(ctx.gen.ag_suspended) - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_contextmanager_non_normalised(self): @asynccontextmanager async def whoo(): @@ -254,8 +238,6 @@ async def whoo(): with self.assertRaises(SyntaxError): await ctx.__aexit__(RuntimeError, None, None) - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_contextmanager_except(self): state = [] @asynccontextmanager @@ -301,8 +283,6 @@ class StopAsyncIterationSubclass(StopAsyncIteration): else: self.fail(f'{stop_exc} was suppressed') - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_contextmanager_wrap_runtimeerror(self): @asynccontextmanager async def woohoo(): @@ -346,8 +326,6 @@ def test_contextmanager_doc_attrib(self): baz = self._create_contextmanager_attribs() self.assertEqual(baz.__doc__, "Whee!") - # TODO: RUSTPYTHON - @unittest.expectedFailure @support.requires_docstrings async def test_instance_docstring_given_cm_docstring(self): baz = self._create_contextmanager_attribs()(None) @@ -355,8 +333,6 @@ async def test_instance_docstring_given_cm_docstring(self): async with baz: pass # suppress warning - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_keywords(self): # Ensure no keyword arguments are inhibited @asynccontextmanager @@ -365,8 +341,6 @@ async def woohoo(self, func, args, kwds): async with woohoo(self=11, func=22, args=33, kwds=44) as target: self.assertEqual(target, (11, 22, 33, 44)) - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_recursive(self): depth = 0 ncols = 0 @@ -393,8 +367,6 @@ async def recursive(): self.assertEqual(ncols, 10) self.assertEqual(depth, 0) - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_decorator(self): entered = False @@ -413,8 +385,6 @@ async def test(): await test() self.assertFalse(entered) - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_decorator_with_exception(self): entered = False @@ -437,8 +407,6 @@ async def test(): await test() self.assertFalse(entered) - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_decorating_method(self): @asynccontextmanager diff --git a/vm/src/stdlib/builtins.rs b/vm/src/stdlib/builtins.rs index 8cf99770e77..dfc3e5a5326 100644 --- a/vm/src/stdlib/builtins.rs +++ b/vm/src/stdlib/builtins.rs @@ -534,6 +534,22 @@ mod builtins { iter_target.get_aiter(vm) } + #[pyfunction] + fn anext( + aiter: PyObjectRef, + default_value: OptionalArg<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult { + let awaitable = vm.call_method(&aiter, "__anext__", ())?; + + if default_value.is_missing() { + Ok(awaitable) + } else { + // TODO: Implement CPython like PyAnextAwaitable to properly handle the default value. + Ok(awaitable) + } + } + #[pyfunction] fn len(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { obj.length(vm) From 614028f9a82abc496edfd93df4f4d586f0b9fc59 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 29 Oct 2025 23:01:04 +0900 Subject: [PATCH 325/819] more ssl impl (#6228) --- Cargo.lock | 15 + Lib/test/test_httplib.py | 2 - Lib/test/test_ssl.py | 21 - Lib/test/test_urllib2_localnet.py | 2 - common/src/fileutils.rs | 96 ++ scripts/make_ssl_data_rs.py | 168 +++ stdlib/Cargo.toml | 1 + stdlib/src/socket.rs | 13 + stdlib/src/ssl.rs | 1291 +++++++++++++++++++-- stdlib/src/ssl/ssl_data_111.rs | 1347 ++++++++++++++++++++++ stdlib/src/ssl/ssl_data_300.rs | 1759 ++++++++++++++++++++++++++++ stdlib/src/ssl/ssl_data_31.rs | 1770 +++++++++++++++++++++++++++++ 12 files changed, 6356 insertions(+), 129 deletions(-) create mode 100755 scripts/make_ssl_data_rs.py create mode 100644 stdlib/src/ssl/ssl_data_111.rs create mode 100644 stdlib/src/ssl/ssl_data_300.rs create mode 100644 stdlib/src/ssl/ssl_data_31.rs diff --git a/Cargo.lock b/Cargo.lock index 53b4f969223..9903fd19334 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1825,6 +1825,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ + "phf_macros", "phf_shared", ] @@ -1848,6 +1849,19 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "phf_shared" version = "0.11.3" @@ -2573,6 +2587,7 @@ dependencies = [ "page_size", "parking_lot", "paste", + "phf", "pymath", "rand_core 0.9.3", "rustix", diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index abe8b9f54cd..d4a6eefe322 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -1801,8 +1801,6 @@ def test_local_good_hostname(self): self.addCleanup(resp.close) self.assertEqual(resp.status, 404) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_local_bad_hostname(self): # The (valid) cert doesn't validate the HTTP hostname import ssl diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index e0b8870fa8c..55e6bb59143 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -586,7 +586,6 @@ def test_refcycle(self): del ss self.assertEqual(wr(), None) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_wrapped_unconnected(self): # Methods on an unconnected SSLSocket propagate the original # OSError raise by the underlying socket object. @@ -1400,7 +1399,6 @@ def test_load_dh_params(self): with self.assertRaises(ssl.SSLError) as cm: ctx.load_dh_params(CERTFILE) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_session_stats(self): for proto in {ssl.PROTOCOL_TLS_CLIENT, ssl.PROTOCOL_TLS_SERVER}: ctx = ssl.SSLContext(proto) @@ -1461,7 +1459,6 @@ def dummycallback(sock, servername, ctx, cycle=ctx): gc.collect() self.assertIs(wr(), None) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_cert_store_stats(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) self.assertEqual(ctx.cert_store_stats(), @@ -1521,7 +1518,6 @@ def test_load_default_certs(self): self.assertRaises(TypeError, ctx.load_default_certs, 'SERVER_AUTH') @unittest.skipIf(sys.platform == "win32", "not-Windows specific") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_load_default_certs_env(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) with os_helper.EnvironmentVarGuard() as env: @@ -1673,7 +1669,6 @@ def test_context_client_server(self): self.assertFalse(ctx.check_hostname) self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_context_custom_class(self): class MySSLSocket(ssl.SSLSocket): pass @@ -1690,7 +1685,6 @@ class MySSLObject(ssl.SSLObject): obj = ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO(), server_side=True) self.assertIsInstance(obj, MySSLObject) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_num_tickest(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) self.assertEqual(ctx.num_tickets, 2) @@ -1721,7 +1715,6 @@ def test_str(self): self.assertEqual(str(e), "foo") self.assertEqual(e.errno, 1) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_lib_reason(self): # Test the library and reason attributes ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -1899,7 +1892,6 @@ def setUp(self): self.enterContext(server) self.server_addr = (HOST, server.port) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_connect(self): with test_wrap_socket(socket.socket(socket.AF_INET), cert_reqs=ssl.CERT_NONE) as s: @@ -1966,7 +1958,6 @@ def test_non_blocking_connect_ex(self): # SSL established self.assertTrue(s.getpeercert()) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_connect_with_context(self): # Same as test_connect, but with a separately created context ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -2093,7 +2084,6 @@ def test_non_blocking_handshake(self): def test_get_server_certificate(self): _test_get_server_certificate(self, *self.server_addr, cert=SIGNING_CA) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_get_server_certificate_sni(self): host, port = self.server_addr server_names = [] @@ -2120,7 +2110,6 @@ def test_get_server_certificate_fail(self): # independent test method _test_get_server_certificate_fail(self, *self.server_addr) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_get_server_certificate_timeout(self): def servername_cb(ssl_sock, server_name, initial_context): time.sleep(0.2) @@ -2156,7 +2145,6 @@ def test_get_ca_certs_capath(self): self.assertTrue(cert) self.assertEqual(len(ctx.get_ca_certs()), 1) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_context_setget(self): # Check that the context of a connected socket can be replaced. ctx1 = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -3041,7 +3029,6 @@ def test_crl_check(self): cert = s.getpeercert() self.assertTrue(cert, "Can't get peer certificate.") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_check_hostname(self): if support.verbose: sys.stdout.write("\n") @@ -3182,7 +3169,6 @@ def test_dual_rsa_ecc(self): cipher = s.cipher()[0].split('-') self.assertTrue(cipher[:2], ('ECDHE', 'ECDSA')) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_check_hostname_idn(self, warnings_filters=True): if support.verbose: sys.stdout.write("\n") @@ -3370,7 +3356,6 @@ def connector(): finally: t.join() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_ssl_cert_verify_error(self): if support.verbose: sys.stdout.write("\n") @@ -3477,7 +3462,6 @@ def test_protocol_tlsv1_1(self): try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_1, False) @requires_tls_version('TLSv1_2') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_protocol_tlsv1_2(self): """Connecting to a TLSv1.2 server with various client options. Testing against older TLS versions.""" @@ -3924,7 +3908,6 @@ def test_do_handshake_enotconn(self): sock.do_handshake() self.assertEqual(cm.exception.errno, errno.ENOTCONN) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_no_shared_ciphers(self): client_context, server_context, hostname = testing_context() # OpenSSL enables all TLS 1.3 ciphers, enforce TLS 1.2 for test @@ -4156,7 +4139,6 @@ def test_no_legacy_server_connect(self): chatty=True, connectionchatty=True, sni_name=hostname) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_dh_params(self): # Check we can get a connection with ephemeral finite-field # Diffie-Hellman (if supported). @@ -4755,7 +4737,6 @@ def test_pha_optional(self): s.write(b'HASCERT') self.assertEqual(s.recv(1024), b'TRUE\n') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_pha_optional_nocert(self): if support.verbose: sys.stdout.write("\n") @@ -4795,7 +4776,6 @@ def test_pha_no_pha_client(self): s.write(b'PHA') self.assertIn(b'extension not received', s.recv(1024)) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_pha_no_pha_server(self): # server doesn't have PHA enabled, cert is requested in handshake client_context, server_context, hostname = testing_context() @@ -4816,7 +4796,6 @@ def test_pha_no_pha_server(self): s.write(b'HASCERT') self.assertEqual(s.recv(1024), b'TRUE\n') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_pha_not_tls13(self): # TLS 1.2 client_context, server_context, hostname = testing_context() diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py index b40a2410bcc..2c54ef85b4b 100644 --- a/Lib/test/test_urllib2_localnet.py +++ b/Lib/test/test_urllib2_localnet.py @@ -602,8 +602,6 @@ def test_https_with_cadefault(self): self.urlopen("https://localhost:%s/bizarre" % handler.port, cadefault=True) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") def test_https_sni(self): if ssl is None: diff --git a/common/src/fileutils.rs b/common/src/fileutils.rs index 6fa8e62a657..cd61d9b204a 100644 --- a/common/src/fileutils.rs +++ b/common/src/fileutils.rs @@ -438,3 +438,99 @@ pub mod windows { } } } + +// _Py_fopen_obj in cpython (Python/fileutils.c:1757-1835) +// Open a file using std::fs::File and convert to FILE* +// Automatically handles path encoding and EINTR retries +pub fn fopen(path: &std::path::Path, mode: &str) -> std::io::Result<*mut libc::FILE> { + use std::ffi::CString; + use std::fs::File; + + // Currently only supports read mode + // Can be extended to support "wb", "w+b", etc. if needed + if mode != "rb" { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("unsupported mode: {}", mode), + )); + } + + // Open file using std::fs::File (handles path encoding and EINTR automatically) + let file = File::open(path)?; + + #[cfg(windows)] + { + use std::os::windows::io::IntoRawHandle; + + // Declare Windows CRT functions + unsafe extern "C" { + fn _open_osfhandle(handle: isize, flags: libc::c_int) -> libc::c_int; + fn _fdopen(fd: libc::c_int, mode: *const libc::c_char) -> *mut libc::FILE; + } + + // Convert File handle to CRT file descriptor + let handle = file.into_raw_handle(); + let fd = unsafe { _open_osfhandle(handle as isize, libc::O_RDONLY) }; + if fd == -1 { + return Err(std::io::Error::last_os_error()); + } + + // Convert fd to FILE* + let mode_cstr = CString::new(mode).unwrap(); + let fp = unsafe { _fdopen(fd, mode_cstr.as_ptr()) }; + if fp.is_null() { + unsafe { libc::close(fd) }; + return Err(std::io::Error::last_os_error()); + } + + // Set non-inheritable (Windows needs this explicitly) + if let Err(e) = set_inheritable(fd, false) { + unsafe { libc::fclose(fp) }; + return Err(e); + } + + Ok(fp) + } + + #[cfg(not(windows))] + { + use std::os::fd::IntoRawFd; + + // Convert File to raw fd + let fd = file.into_raw_fd(); + + // Convert fd to FILE* + let mode_cstr = CString::new(mode).unwrap(); + let fp = unsafe { libc::fdopen(fd, mode_cstr.as_ptr()) }; + if fp.is_null() { + unsafe { libc::close(fd) }; + return Err(std::io::Error::last_os_error()); + } + + // Unix: O_CLOEXEC is already set by File::open, so non-inheritable is automatic + Ok(fp) + } +} + +// set_inheritable in cpython (Python/fileutils.c:1443-1570) +// Set the inheritable flag of the specified file descriptor +// Only used on Windows; Unix automatically sets O_CLOEXEC +#[cfg(windows)] +fn set_inheritable(fd: libc::c_int, inheritable: bool) -> std::io::Result<()> { + use windows_sys::Win32::Foundation::{ + HANDLE, HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, SetHandleInformation, + }; + + let handle = unsafe { libc::get_osfhandle(fd) }; + if handle == INVALID_HANDLE_VALUE as isize { + return Err(std::io::Error::last_os_error()); + } + + let flags = if inheritable { HANDLE_FLAG_INHERIT } else { 0 }; + let result = unsafe { SetHandleInformation(handle as HANDLE, HANDLE_FLAG_INHERIT, flags) }; + if result == 0 { + return Err(std::io::Error::last_os_error()); + } + + Ok(()) +} diff --git a/scripts/make_ssl_data_rs.py b/scripts/make_ssl_data_rs.py new file mode 100755 index 00000000000..b6891416a07 --- /dev/null +++ b/scripts/make_ssl_data_rs.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 + +""" +Generate Rust SSL error mapping code from OpenSSL sources. + +This is based on CPython's Tools/ssl/make_ssl_data.py but generates +Rust code instead of C headers. + +It takes two arguments: +- the path to the OpenSSL source tree (e.g. git checkout) +- the path to the Rust file to be generated (e.g. stdlib/src/ssl/ssl_data.rs) +- error codes are version specific +""" + +import argparse +import datetime +import operator +import os +import re +import sys + + +parser = argparse.ArgumentParser( + description="Generate ssl_data.rs from OpenSSL sources" +) +parser.add_argument("srcdir", help="OpenSSL source directory") +parser.add_argument("output", nargs="?", default=None) + + +def _file_search(fname, pat): + with open(fname, encoding="utf-8") as f: + for line in f: + match = pat.search(line) + if match is not None: + yield match + + +def parse_err_h(args): + """Parse err codes, e.g. ERR_LIB_X509: 11""" + pat = re.compile(r"#\s*define\W+ERR_LIB_(\w+)\s+(\d+)") + lib2errnum = {} + for match in _file_search(args.err_h, pat): + libname, num = match.groups() + lib2errnum[libname] = int(num) + + return lib2errnum + + +def parse_openssl_error_text(args): + """Parse error reasons, X509_R_AKID_MISMATCH""" + # ignore backslash line continuation for now + pat = re.compile(r"^((\w+?)_R_(\w+)):(\d+):") + for match in _file_search(args.errtxt, pat): + reason, libname, errname, num = match.groups() + if "_F_" in reason: + # ignore function codes + continue + num = int(num) + yield reason, libname, errname, num + + +def parse_extra_reasons(args): + """Parse extra reasons from openssl.ec""" + pat = re.compile(r"^R\s+((\w+)_R_(\w+))\s+(\d+)") + for match in _file_search(args.errcodes, pat): + reason, libname, errname, num = match.groups() + num = int(num) + yield reason, libname, errname, num + + +def gen_library_codes_rust(args): + """Generate Rust phf map for library codes""" + yield "// Maps lib_code -> library name" + yield '// Example: 20 -> "SSL"' + yield "pub static LIBRARY_CODES: phf::Map<u32, &'static str> = phf_map! {" + + # Deduplicate: keep the last one if there are duplicates + seen = {} + for libname in sorted(args.lib2errnum): + lib_num = args.lib2errnum[libname] + seen[lib_num] = libname + + for lib_num in sorted(seen.keys()): + libname = seen[lib_num] + yield f' {lib_num}u32 => "{libname}",' + yield "};" + yield "" + + +def gen_error_codes_rust(args): + """Generate Rust phf map for error codes""" + yield "// Maps encoded (lib, reason) -> error mnemonic" + yield '// Example: encode_error_key(20, 134) -> "CERTIFICATE_VERIFY_FAILED"' + yield "// Key encoding: (lib << 32) | reason" + yield "pub static ERROR_CODES: phf::Map<u64, &'static str> = phf_map! {" + for reason, libname, errname, num in args.reasons: + if libname not in args.lib2errnum: + continue + lib_num = args.lib2errnum[libname] + # Encode (lib, reason) as single u64 + key = (lib_num << 32) | num + yield f' {key}u64 => "{errname}",' + yield "};" + yield "" + + +def main(): + args = parser.parse_args() + + args.err_h = os.path.join(args.srcdir, "include", "openssl", "err.h") + if not os.path.isfile(args.err_h): + # Fall back to infile for OpenSSL 3.0.0 + args.err_h += ".in" + args.errcodes = os.path.join(args.srcdir, "crypto", "err", "openssl.ec") + args.errtxt = os.path.join(args.srcdir, "crypto", "err", "openssl.txt") + + if not os.path.isfile(args.errtxt): + parser.error(f"File {args.errtxt} not found in srcdir\n.") + + # {X509: 11, ...} + args.lib2errnum = parse_err_h(args) + + # [('X509_R_AKID_MISMATCH', 'X509', 'AKID_MISMATCH', 110), ...] + reasons = [] + reasons.extend(parse_openssl_error_text(args)) + reasons.extend(parse_extra_reasons(args)) + # sort by libname, numeric error code + args.reasons = sorted(reasons, key=operator.itemgetter(0, 3)) + + lines = [ + "// File generated by tools/make_ssl_data_rs.py", + f"// Generated on {datetime.datetime.now(datetime.timezone.utc).isoformat()}", + f"// Source: OpenSSL from {args.srcdir}", + "// spell-checker: disable", + "", + "use phf::phf_map;", + "", + ] + lines.extend(gen_library_codes_rust(args)) + lines.extend(gen_error_codes_rust(args)) + + # Add helper function + lines.extend( + [ + "/// Helper function to create encoded key from (lib, reason) pair", + "#[inline]", + "pub fn encode_error_key(lib: i32, reason: i32) -> u64 {", + " ((lib as u64) << 32) | (reason as u64 & 0xFFFFFFFF)", + "}", + "", + ] + ) + + if args.output is None: + for line in lines: + print(line) + else: + with open(args.output, "w") as output: + for line in lines: + print(line, file=output) + + print(f"Generated {args.output}") + print(f"Found {len(args.lib2errnum)} library codes") + print(f"Found {len(args.reasons)} error codes") + + +if __name__ == "__main__": + main() diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml index cc6f76a1a64..7f64802d352 100644 --- a/stdlib/Cargo.toml +++ b/stdlib/Cargo.toml @@ -40,6 +40,7 @@ num-integer = { workspace = true } num-traits = { workspace = true } num_enum = { workspace = true } parking_lot = { workspace = true } +phf = { version = "0.11", features = ["macros"] } memchr = { workspace = true } base64 = "0.22" diff --git a/stdlib/src/socket.rs b/stdlib/src/socket.rs index f3ae72b63fb..234d94a5263 100644 --- a/stdlib/src/socket.rs +++ b/stdlib/src/socket.rs @@ -1198,6 +1198,7 @@ mod _socket { fn recv_into( &self, buf: ArgMemoryBuffer, + nbytes: OptionalArg<isize>, flags: OptionalArg<i32>, vm: &VirtualMachine, ) -> Result<usize, IoOrPyException> { @@ -1205,6 +1206,18 @@ mod _socket { let sock = self.sock()?; let mut buf = buf.borrow_buf_mut(); let buf = &mut *buf; + + // Handle nbytes parameter + let read_len = if let OptionalArg::Present(nbytes) = nbytes { + let nbytes = nbytes.to_usize().ok_or_else(|| { + vm.new_value_error("negative buffersize in recv_into".to_owned()) + })?; + nbytes.min(buf.len()) + } else { + buf.len() + }; + + let buf = &mut buf[..read_len]; self.sock_op(vm, SelectKind::Read, || { sock.recv_with_flags(unsafe { slice_as_uninit(buf) }, flags) }) diff --git a/stdlib/src/ssl.rs b/stdlib/src/ssl.rs index d151c2948c6..9fd050a7efa 100644 --- a/stdlib/src/ssl.rs +++ b/stdlib/src/ssl.rs @@ -2,6 +2,23 @@ mod cert; +// Conditional compilation for OpenSSL version-specific error codes +cfg_if::cfg_if! { + if #[cfg(ossl310)] { + // OpenSSL 3.1.0+ + #[path = "ssl/ssl_data_31.rs"] + mod ssl_data; + } else if #[cfg(ossl300)] { + // OpenSSL 3.0.0+ + #[path = "ssl/ssl_data_300.rs"] + mod ssl_data; + } else { + // OpenSSL 1.1.1+ (fallback) + #[path = "ssl/ssl_data_111.rs"] + mod ssl_data; + } +} + use crate::vm::{PyRef, VirtualMachine, builtins::PyModule}; use openssl_probe::ProbeResult; @@ -173,9 +190,9 @@ mod _ssl { #[pyattr] const HAS_ALPN: bool = true; #[pyattr] - const HAS_SSLv2: bool = true; + const HAS_SSLv2: bool = false; #[pyattr] - const HAS_SSLv3: bool = true; + const HAS_SSLv3: bool = false; #[pyattr] const HAS_TLSv1: bool = true; #[pyattr] @@ -199,6 +216,10 @@ mod _ssl { const ERR_LIB_SSL: i32 = 20; const SSL_R_UNEXPECTED_EOF_WHILE_READING: i32 = 294; + // SSL_VERIFY constants for post-handshake authentication + #[cfg(ossl111)] + const SSL_VERIFY_POST_HANDSHAKE: libc::c_int = 0x20; + // the openssl version from the API headers #[pyattr(name = "OPENSSL_VERSION")] @@ -503,6 +524,131 @@ mod _ssl { Ok(buf) } + // Callback data stored in SSL context for SNI + struct SniCallbackData { + ssl_context: PyRef<PySslContext>, + vm_ptr: *const VirtualMachine, + } + + impl Drop for SniCallbackData { + fn drop(&mut self) { + // PyRef will handle reference counting + } + } + + // Get or create an ex_data index for SNI callback data + fn get_sni_ex_data_index() -> libc::c_int { + use std::sync::LazyLock; + static SNI_EX_DATA_IDX: LazyLock<libc::c_int> = LazyLock::new(|| unsafe { + sys::SSL_get_ex_new_index( + 0, + std::ptr::null_mut(), + None, + None, + Some(sni_callback_data_free), + ) + }); + *SNI_EX_DATA_IDX + } + + // Free function for callback data + unsafe extern "C" fn sni_callback_data_free( + _parent: *mut libc::c_void, + ptr: *mut libc::c_void, + _ad: *mut sys::CRYPTO_EX_DATA, + _idx: libc::c_int, + _argl: libc::c_long, + _argp: *mut libc::c_void, + ) { + if !ptr.is_null() { + unsafe { + let _ = Box::from_raw(ptr as *mut SniCallbackData); + } + } + } + + // SNI callback function called by OpenSSL + unsafe extern "C" fn _servername_callback( + ssl_ptr: *mut sys::SSL, + _al: *mut libc::c_int, + arg: *mut libc::c_void, + ) -> libc::c_int { + const SSL_TLSEXT_ERR_OK: libc::c_int = 0; + const SSL_TLSEXT_ERR_ALERT_FATAL: libc::c_int = 2; + const TLSEXT_NAMETYPE_host_name: libc::c_int = 0; + + if arg.is_null() { + return SSL_TLSEXT_ERR_OK; + } + + unsafe { + let ctx = &*(arg as *const PySslContext); + + // Get the callback + let callback_opt = ctx.sni_callback.lock().clone(); + let Some(callback) = callback_opt else { + return SSL_TLSEXT_ERR_OK; + }; + + // Get callback data from SSL ex_data + let idx = get_sni_ex_data_index(); + let data_ptr = sys::SSL_get_ex_data(ssl_ptr, idx); + if data_ptr.is_null() { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + let callback_data = &*(data_ptr as *const SniCallbackData); + + // SAFETY: vm_ptr is stored during wrap_socket and is valid for the lifetime + // of the SSL connection. The handshake happens synchronously in the same thread. + let vm = &*callback_data.vm_ptr; + + // Get server name + let servername = sys::SSL_get_servername(ssl_ptr, TLSEXT_NAMETYPE_host_name); + let server_name_arg = if servername.is_null() { + vm.ctx.none() + } else { + let name_cstr = std::ffi::CStr::from_ptr(servername); + match name_cstr.to_str() { + Ok(name_str) => vm.ctx.new_str(name_str).into(), + Err(_) => vm.ctx.none(), + } + }; + + // Get SSL socket from SSL ex_data (stored as PySslSocket pointer) + let ssl_socket_ptr = sys::SSL_get_ex_data(ssl_ptr, 0); // Index 0 for SSL socket + let ssl_socket_obj = if !ssl_socket_ptr.is_null() { + let ssl_socket = &*(ssl_socket_ptr as *const PySslSocket); + // Try to get owner first + ssl_socket + .owner + .read() + .as_ref() + .and_then(|weak| weak.upgrade()) + .unwrap_or_else(|| vm.ctx.none()) + } else { + vm.ctx.none() + }; + + // Call the Python callback + match callback.call( + ( + ssl_socket_obj, + server_name_arg, + callback_data.ssl_context.to_owned(), + ), + vm, + ) { + Ok(_) => SSL_TLSEXT_ERR_OK, + Err(exc) => { + // Log the exception but don't propagate it + vm.run_unraisable(exc, None, vm.ctx.none()); + SSL_TLSEXT_ERR_ALERT_FATAL + } + } + } + } + #[pyfunction(name = "RAND_pseudo_bytes")] fn rand_pseudo_bytes(n: i32, vm: &VirtualMachine) -> PyResult<(Vec<u8>, bool)> { if n < 0 { @@ -524,6 +670,7 @@ mod _ssl { check_hostname: AtomicCell<bool>, protocol: SslVersion, post_handshake_auth: PyMutex<bool>, + sni_callback: PyMutex<Option<PyObjectRef>>, } impl fmt::Debug for PySslContext { @@ -591,6 +738,28 @@ mod _ssl { .set_session_id_context(b"Python") .map_err(|e| convert_openssl_error(vm, e))?; + // Set protocol version limits based on the protocol version + unsafe { + let ctx_ptr = builder.as_ptr(); + match proto { + SslVersion::Tls1 => { + sys::SSL_CTX_set_min_proto_version(ctx_ptr, sys::TLS1_VERSION); + sys::SSL_CTX_set_max_proto_version(ctx_ptr, sys::TLS1_VERSION); + } + SslVersion::Tls1_1 => { + sys::SSL_CTX_set_min_proto_version(ctx_ptr, sys::TLS1_1_VERSION); + sys::SSL_CTX_set_max_proto_version(ctx_ptr, sys::TLS1_1_VERSION); + } + SslVersion::Tls1_2 => { + sys::SSL_CTX_set_min_proto_version(ctx_ptr, sys::TLS1_2_VERSION); + sys::SSL_CTX_set_max_proto_version(ctx_ptr, sys::TLS1_2_VERSION); + } + _ => { + // For Tls, TlsClient, TlsServer, use default (no restrictions) + } + } + } + // Set default verify flags: VERIFY_X509_TRUSTED_FIRST unsafe { let ctx_ptr = builder.as_ptr(); @@ -603,13 +772,14 @@ mod _ssl { check_hostname: AtomicCell::new(check_hostname), protocol: proto, post_handshake_auth: PyMutex::new(false), + sni_callback: PyMutex::new(None), } .into_ref_with_type(vm, cls) .map(Into::into) } } - #[pyclass(flags(BASETYPE), with(Constructor))] + #[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] impl PySslContext { fn builder(&self) -> PyRwLockWriteGuard<'_, SslContextBuilder> { self.ctx.write() @@ -677,6 +847,11 @@ mod _ssl { dict.set_item("name", vm.ctx.new_str(name).into(), vm)?; dict.set_item("protocol", vm.ctx.new_str(version).into(), vm)?; dict.set_item("secret_bits", vm.ctx.new_int(bits).into(), vm)?; + + // Add description field + let description = cipher_description(cipher_ptr); + dict.set_item("description", vm.ctx.new_str(description).into(), vm)?; + result.push(dict.into()); } @@ -825,8 +1000,22 @@ mod _ssl { } #[pygetset(setter)] fn set_minimum_version(&self, value: i32, vm: &VirtualMachine) -> PyResult<()> { + // Handle special values + let proto_version = match value { + -2 => { + // PY_PROTO_MINIMUM_SUPPORTED -> use minimum available (TLS 1.2) + sys::TLS1_2_VERSION + } + -1 => { + // PY_PROTO_MAXIMUM_SUPPORTED -> use maximum available + // For max on min_proto_version, we use the newest available + sys::TLS1_3_VERSION + } + _ => value, + }; + let ctx = self.builder(); - let result = unsafe { sys::SSL_CTX_set_min_proto_version(ctx.as_ptr(), value) }; + let result = unsafe { sys::SSL_CTX_set_min_proto_version(ctx.as_ptr(), proto_version) }; if result == 0 { return Err(vm.new_value_error("invalid protocol version")); } @@ -845,14 +1034,72 @@ mod _ssl { } #[pygetset(setter)] fn set_maximum_version(&self, value: i32, vm: &VirtualMachine) -> PyResult<()> { + // Handle special values + let proto_version = match value { + -1 => { + // PY_PROTO_MAXIMUM_SUPPORTED -> use 0 for OpenSSL (means no limit) + 0 + } + -2 => { + // PY_PROTO_MINIMUM_SUPPORTED -> use minimum available (TLS 1.2) + sys::TLS1_2_VERSION + } + _ => value, + }; + let ctx = self.builder(); - let result = unsafe { sys::SSL_CTX_set_max_proto_version(ctx.as_ptr(), value) }; + let result = unsafe { sys::SSL_CTX_set_max_proto_version(ctx.as_ptr(), proto_version) }; if result == 0 { return Err(vm.new_value_error("invalid protocol version")); } Ok(()) } + #[pygetset] + fn num_tickets(&self, _vm: &VirtualMachine) -> PyResult<usize> { + // Only supported for TLS 1.3 + #[cfg(ossl110)] + { + let ctx = self.ctx(); + let num = unsafe { sys::SSL_CTX_get_num_tickets(ctx.as_ptr()) }; + Ok(num) + } + #[cfg(not(ossl110))] + { + let _ = vm; + Ok(0) + } + } + #[pygetset(setter)] + fn set_num_tickets(&self, value: isize, vm: &VirtualMachine) -> PyResult<()> { + // Check for negative values + if value < 0 { + return Err( + vm.new_value_error("num_tickets must be a non-negative integer".to_owned()) + ); + } + + // Check that this is a server context + if self.protocol != SslVersion::TlsServer { + return Err(vm.new_value_error("SSLContext is not a server context.".to_owned())); + } + + #[cfg(ossl110)] + { + let ctx = self.builder(); + let result = unsafe { sys::SSL_CTX_set_num_tickets(ctx.as_ptr(), value as usize) }; + if result != 1 { + return Err(vm.new_value_error("failed to set num tickets.")); + } + Ok(()) + } + #[cfg(not(ossl110))] + { + let _ = (value, vm); + Ok(()) + } + } + #[pymethod] fn set_default_verify_paths(&self, vm: &VirtualMachine) -> PyResult<()> { cfg_if::cfg_if! { @@ -976,6 +1223,208 @@ mod _ssl { Ok(certs) } + #[pymethod] + fn cert_store_stats(&self, vm: &VirtualMachine) -> PyResult { + let ctx = self.ctx(); + let store_ptr = unsafe { sys::SSL_CTX_get_cert_store(ctx.as_ptr()) }; + + if store_ptr.is_null() { + return Err(vm.new_memory_error("failed to get cert store".to_owned())); + } + + let objs_ptr = unsafe { sys::X509_STORE_get0_objects(store_ptr) }; + if objs_ptr.is_null() { + return Err(vm.new_memory_error("failed to query cert store".to_owned())); + } + + let mut x509_count = 0; + let mut crl_count = 0; + let mut ca_count = 0; + + unsafe { + let num_objs = sys::OPENSSL_sk_num(objs_ptr as *const _); + for i in 0..num_objs { + let obj_ptr = + sys::OPENSSL_sk_value(objs_ptr as *const _, i) as *const sys::X509_OBJECT; + let obj_type = X509_OBJECT_get_type(obj_ptr); + + match obj_type { + X509_LU_X509 => { + x509_count += 1; + let x509_ptr = sys::X509_OBJECT_get0_X509(obj_ptr); + if !x509_ptr.is_null() && X509_check_ca(x509_ptr) == 1 { + ca_count += 1; + } + } + X509_LU_CRL => { + crl_count += 1; + } + _ => { + // Ignore unrecognized types + } + } + } + // Note: No need to free objs_ptr as X509_STORE_get0_objects returns + // a pointer to internal data that should not be freed by the caller + } + + let dict = vm.ctx.new_dict(); + dict.set_item("x509", vm.ctx.new_int(x509_count).into(), vm)?; + dict.set_item("crl", vm.ctx.new_int(crl_count).into(), vm)?; + dict.set_item("x509_ca", vm.ctx.new_int(ca_count).into(), vm)?; + Ok(dict.into()) + } + + #[pymethod] + fn session_stats(&self, vm: &VirtualMachine) -> PyResult { + let ctx = self.ctx(); + let ctx_ptr = ctx.as_ptr(); + + let dict = vm.ctx.new_dict(); + + macro_rules! add_stat { + ($key:expr, $func:ident) => { + let value = unsafe { $func(ctx_ptr) }; + dict.set_item($key, vm.ctx.new_int(value).into(), vm)?; + }; + } + + add_stat!("number", SSL_CTX_sess_number); + add_stat!("connect", SSL_CTX_sess_connect); + add_stat!("connect_good", SSL_CTX_sess_connect_good); + add_stat!("connect_renegotiate", SSL_CTX_sess_connect_renegotiate); + add_stat!("accept", SSL_CTX_sess_accept); + add_stat!("accept_good", SSL_CTX_sess_accept_good); + add_stat!("accept_renegotiate", SSL_CTX_sess_accept_renegotiate); + add_stat!("hits", SSL_CTX_sess_hits); + add_stat!("misses", SSL_CTX_sess_misses); + add_stat!("timeouts", SSL_CTX_sess_timeouts); + add_stat!("cache_full", SSL_CTX_sess_cache_full); + + Ok(dict.into()) + } + + #[pymethod] + fn load_dh_params(&self, filepath: FsPath, vm: &VirtualMachine) -> PyResult<()> { + let path = filepath.to_path_buf(vm)?; + + // Open the file using fopen (cross-platform) + let fp = + rustpython_common::fileutils::fopen(path.as_path(), "rb").map_err(|e| { + match e.kind() { + std::io::ErrorKind::NotFound => vm.new_exception_msg( + vm.ctx.exceptions.file_not_found_error.to_owned(), + e.to_string(), + ), + _ => vm.new_os_error(e.to_string()), + } + })?; + + // Read DH parameters + let dh = unsafe { + PEM_read_DHparams( + fp, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + ) + }; + unsafe { + libc::fclose(fp); + } + + if dh.is_null() { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + + // Set temporary DH parameters + let ctx = self.builder(); + let result = unsafe { sys::SSL_CTX_set_tmp_dh(ctx.as_ptr(), dh) }; + unsafe { + sys::DH_free(dh); + } + + if result != 1 { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + + Ok(()) + } + + #[pygetset] + fn sni_callback(&self) -> Option<PyObjectRef> { + self.sni_callback.lock().clone() + } + + #[pygetset(setter)] + fn set_sni_callback( + &self, + value: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Check if this is a server context + if self.protocol == SslVersion::TlsClient { + return Err(vm.new_value_error( + "sni_callback cannot be set on TLS_CLIENT context".to_owned(), + )); + } + + let mut callback_guard = self.sni_callback.lock(); + + if let Some(callback_obj) = value { + if !vm.is_none(&callback_obj) { + // Check if callable + if !callback_obj.is_callable() { + return Err(vm.new_type_error("not a callable object".to_owned())); + } + + // Set the callback + *callback_guard = Some(callback_obj); + + // Set OpenSSL callback + unsafe { + sys::SSL_CTX_set_tlsext_servername_callback__fixed_rust( + self.ctx().as_ptr(), + Some(_servername_callback), + ); + sys::SSL_CTX_set_tlsext_servername_arg( + self.ctx().as_ptr(), + self as *const _ as *mut _, + ); + } + } else { + // Clear callback + *callback_guard = None; + unsafe { + sys::SSL_CTX_set_tlsext_servername_callback__fixed_rust( + self.ctx().as_ptr(), + None, + ); + } + } + } else { + // Clear callback + *callback_guard = None; + unsafe { + sys::SSL_CTX_set_tlsext_servername_callback__fixed_rust( + self.ctx().as_ptr(), + None, + ); + } + } + + Ok(()) + } + + #[pymethod] + fn set_servername_callback( + &self, + callback: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + self.set_sni_callback(callback, vm) + } + #[pymethod] fn load_cert_chain(&self, args: LoadCertChainArgs, vm: &VirtualMachine) -> PyResult<()> { let LoadCertChainArgs { @@ -1006,7 +1455,7 @@ mod _ssl { zelf: PyRef<Self>, args: WrapSocketArgs, vm: &VirtualMachine, - ) -> PyResult<PySslSocket> { + ) -> PyResult<PyObjectRef> { // validate socket type and context protocol if !args.server_side && zelf.protocol == SslVersion::TlsServer { return Err(vm.new_exception_msg( @@ -1038,6 +1487,9 @@ mod _ssl { "server_hostname cannot be an empty string or start with a leading dot.", )); } + if hostname.contains('\0') { + return Err(vm.new_value_error("embedded null byte in server_hostname")); + } let ip = hostname.parse::<std::net::IpAddr>(); if ip.is_err() { ssl.set_hostname(hostname) @@ -1056,34 +1508,203 @@ mod _ssl { } } + // Configure post-handshake authentication (PHA) + #[cfg(ossl111)] + if *zelf.post_handshake_auth.lock() { + unsafe { + if args.server_side { + // Server socket: add SSL_VERIFY_POST_HANDSHAKE flag + // Only in combination with SSL_VERIFY_PEER + let mode = sys::SSL_get_verify_mode(ssl.as_ptr()); + if (mode & sys::SSL_VERIFY_PEER as libc::c_int) != 0 { + // Add POST_HANDSHAKE flag (keep existing flags including FAIL_IF_NO_PEER_CERT) + sys::SSL_set_verify( + ssl.as_ptr(), + mode | SSL_VERIFY_POST_HANDSHAKE, + None, + ); + } + } else { + // Client socket: call SSL_set_post_handshake_auth + SSL_set_post_handshake_auth(ssl.as_ptr(), 1); + } + } + } + let stream = ssl::SslStream::new(ssl, SocketStream(args.sock.clone())) .map_err(|e| convert_openssl_error(vm, e))?; let py_ssl_socket = PySslSocket { - ctx: zelf, - stream: PyRwLock::new(stream), + ctx: PyRwLock::new(zelf.clone()), + connection: PyRwLock::new(SslConnection::Socket(stream)), socket_type, server_hostname: args.server_hostname, owner: PyRwLock::new(args.owner.map(|o| o.downgrade(None, vm)).transpose()?), }; + // Convert to PyRef (heap allocation) to avoid use-after-free + let py_ref = + py_ssl_socket.into_ref_with_type(vm, PySslSocket::class(&vm.ctx).to_owned())?; + + // Set SNI callback data if callback is configured + if zelf.sni_callback.lock().is_some() { + unsafe { + let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); + + // Store callback data in SSL ex_data + let callback_data = Box::new(SniCallbackData { + ssl_context: zelf.clone(), + vm_ptr: vm as *const _, + }); + let idx = get_sni_ex_data_index(); + sys::SSL_set_ex_data(ssl_ptr, idx, Box::into_raw(callback_data) as *mut _); + + // Store PyRef pointer (heap-allocated) in ex_data index 0 + sys::SSL_set_ex_data(ssl_ptr, 0, &*py_ref as *const _ as *mut _); + } + } + // Set session if provided if let Some(session) = args.session && !vm.is_none(&session) { - py_ssl_socket.set_session(session, vm)?; + py_ref.set_session(session, vm)?; } - Ok(py_ssl_socket) + Ok(py_ref.into()) } #[pymethod] - fn _wrap_bio(_zelf: PyRef<Self>, _args: WrapBioArgs, vm: &VirtualMachine) -> PyResult { - // TODO: Implement BIO-based SSL wrapping - // This requires refactoring PySslSocket to support both socket and BIO modes - Err(vm.new_not_implemented_error( - "_wrap_bio is not yet implemented in RustPython".to_owned(), - )) + fn _wrap_bio( + zelf: PyRef<Self>, + args: WrapBioArgs, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + // validate socket type and context protocol + if !args.server_side && zelf.protocol == SslVersion::TlsServer { + return Err(vm.new_exception_msg( + PySslError::class(&vm.ctx).to_owned(), + "Cannot create a client socket with a PROTOCOL_TLS_SERVER context".to_owned(), + )); + } + if args.server_side && zelf.protocol == SslVersion::TlsClient { + return Err(vm.new_exception_msg( + PySslError::class(&vm.ctx).to_owned(), + "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context".to_owned(), + )); + } + + let mut ssl = ssl::Ssl::new(&zelf.ctx()).map_err(|e| convert_openssl_error(vm, e))?; + + let socket_type = if args.server_side { + ssl.set_accept_state(); + SslServerOrClient::Server + } else { + ssl.set_connect_state(); + SslServerOrClient::Client + }; + + if let Some(hostname) = &args.server_hostname { + let hostname = hostname.as_str(); + if hostname.is_empty() || hostname.starts_with('.') { + return Err(vm.new_value_error( + "server_hostname cannot be an empty string or start with a leading dot.", + )); + } + if hostname.contains('\0') { + return Err(vm.new_value_error("embedded null byte in server_hostname")); + } + let ip = hostname.parse::<std::net::IpAddr>(); + if ip.is_err() { + ssl.set_hostname(hostname) + .map_err(|e| convert_openssl_error(vm, e))?; + } + if zelf.check_hostname.load() { + if let Ok(ip) = ip { + ssl.param_mut() + .set_ip(ip) + .map_err(|e| convert_openssl_error(vm, e))?; + } else { + ssl.param_mut() + .set_host(hostname) + .map_err(|e| convert_openssl_error(vm, e))?; + } + } + } + + // Don't use SSL_set_bio - let SslStream drive I/O through BioStream Read/Write + + // Configure post-handshake authentication (PHA) + #[cfg(ossl111)] + if *zelf.post_handshake_auth.lock() { + unsafe { + if args.server_side { + // Server socket: add SSL_VERIFY_POST_HANDSHAKE flag + // Only in combination with SSL_VERIFY_PEER + let mode = sys::SSL_get_verify_mode(ssl.as_ptr()); + if (mode & sys::SSL_VERIFY_PEER as libc::c_int) != 0 { + // Add POST_HANDSHAKE flag (keep existing flags including FAIL_IF_NO_PEER_CERT) + sys::SSL_set_verify( + ssl.as_ptr(), + mode | SSL_VERIFY_POST_HANDSHAKE, + None, + ); + } + } else { + // Client socket: call SSL_set_post_handshake_auth + SSL_set_post_handshake_auth(ssl.as_ptr(), 1); + } + } + } + + // Create a BioStream wrapper (dummy, actual IO goes through BIOs) + let bio_stream = BioStream { + inbio: args.incoming, + outbio: args.outgoing, + }; + + // Create SslStream with BioStream + let stream = + ssl::SslStream::new(ssl, bio_stream).map_err(|e| convert_openssl_error(vm, e))?; + + let py_ssl_socket = PySslSocket { + ctx: PyRwLock::new(zelf.clone()), + connection: PyRwLock::new(SslConnection::Bio(stream)), + socket_type, + server_hostname: args.server_hostname, + owner: PyRwLock::new(args.owner.map(|o| o.downgrade(None, vm)).transpose()?), + }; + + // Convert to PyRef (heap allocation) to avoid use-after-free + let py_ref = + py_ssl_socket.into_ref_with_type(vm, PySslSocket::class(&vm.ctx).to_owned())?; + + // Set SNI callback data if callback is configured + if zelf.sni_callback.lock().is_some() { + unsafe { + let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); + + // Store callback data in SSL ex_data + let callback_data = Box::new(SniCallbackData { + ssl_context: zelf.clone(), + vm_ptr: vm as *const _, + }); + let idx = get_sni_ex_data_index(); + sys::SSL_set_ex_data(ssl_ptr, idx, Box::into_raw(callback_data) as *mut _); + + // Store PyRef pointer (heap-allocated) in ex_data index 0 + sys::SSL_set_ex_data(ssl_ptr, 0, &*py_ref as *const _ as *mut _); + } + } + + // Set session if provided + if let Some(session) = args.session + && !vm.is_none(&session) + { + py_ref.set_session(session, vm)?; + } + + Ok(py_ref.into()) } } @@ -1205,13 +1826,125 @@ mod _ssl { ) } + // BIO stream wrapper to implement Read/Write traits for MemoryBIO + struct BioStream { + inbio: PyRef<PySslMemoryBio>, + outbio: PyRef<PySslMemoryBio>, + } + + impl Read for BioStream { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + // Read from incoming MemoryBIO + unsafe { + let nbytes = sys::BIO_read( + self.inbio.bio, + buf.as_mut_ptr() as *mut _, + buf.len().min(i32::MAX as usize) as i32, + ); + if nbytes < 0 { + // BIO_read returns -1 on error or when no data is available + // Check if it's a retry condition (WANT_READ) + Err(std::io::Error::new( + std::io::ErrorKind::WouldBlock, + "BIO has no data available", + )) + } else { + Ok(nbytes as usize) + } + } + } + } + + impl Write for BioStream { + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { + // Write to outgoing MemoryBIO + unsafe { + let nbytes = sys::BIO_write( + self.outbio.bio, + buf.as_ptr() as *const _, + buf.len().min(i32::MAX as usize) as i32, + ); + if nbytes < 0 { + return Err(std::io::Error::other("BIO write failed")); + } + Ok(nbytes as usize) + } + } + + fn flush(&mut self) -> std::io::Result<()> { + // MemoryBIO doesn't need flushing + Ok(()) + } + } + + // Enum to represent different SSL connection modes + enum SslConnection { + Socket(ssl::SslStream<SocketStream>), + Bio(ssl::SslStream<BioStream>), + } + + impl SslConnection { + // Get a reference to the SSL object + fn ssl(&self) -> &ssl::SslRef { + match self { + SslConnection::Socket(stream) => stream.ssl(), + SslConnection::Bio(stream) => stream.ssl(), + } + } + + // Get underlying socket stream reference (only for socket mode) + fn get_ref(&self) -> Option<&SocketStream> { + match self { + SslConnection::Socket(stream) => Some(stream.get_ref()), + SslConnection::Bio(_) => None, + } + } + + // Check if this is in BIO mode + fn is_bio(&self) -> bool { + matches!(self, SslConnection::Bio(_)) + } + + // Perform SSL handshake + fn do_handshake(&mut self) -> Result<(), ssl::Error> { + match self { + SslConnection::Socket(stream) => stream.do_handshake(), + SslConnection::Bio(stream) => stream.do_handshake(), + } + } + + // Write data to SSL connection + fn ssl_write(&mut self, buf: &[u8]) -> Result<usize, ssl::Error> { + match self { + SslConnection::Socket(stream) => stream.ssl_write(buf), + SslConnection::Bio(stream) => stream.ssl_write(buf), + } + } + + // Read data from SSL connection + fn ssl_read(&mut self, buf: &mut [u8]) -> Result<usize, ssl::Error> { + match self { + SslConnection::Socket(stream) => stream.ssl_read(buf), + SslConnection::Bio(stream) => stream.ssl_read(buf), + } + } + + // Get SSL shutdown state + fn get_shutdown(&mut self) -> ssl::ShutdownState { + match self { + SslConnection::Socket(stream) => stream.get_shutdown(), + SslConnection::Bio(stream) => stream.get_shutdown(), + } + } + } + #[pyattr] #[pyclass(module = "ssl", name = "_SSLSocket", traverse)] #[derive(PyPayload)] struct PySslSocket { - ctx: PyRef<PySslContext>, + ctx: PyRwLock<PyRef<PySslContext>>, #[pytraverse(skip)] - stream: PyRwLock<ssl::SslStream<SocketStream>>, + connection: PyRwLock<SslConnection>, #[pytraverse(skip)] socket_type: SslServerOrClient, server_hostname: Option<PyStrRef>, @@ -1224,7 +1957,7 @@ mod _ssl { } } - #[pyclass] + #[pyclass(flags(IMMUTABLETYPE))] impl PySslSocket { #[pygetset] fn owner(&self) -> Option<PyObjectRef> { @@ -1243,7 +1976,24 @@ mod _ssl { } #[pygetset] fn context(&self) -> PyRef<PySslContext> { - self.ctx.clone() + self.ctx.read().clone() + } + #[pygetset(setter)] + fn set_context(&self, value: PyRef<PySslContext>, vm: &VirtualMachine) -> PyResult<()> { + // Update the SSL context in the underlying SSL object + let stream = self.connection.read(); + + // Set the new SSL_CTX on the SSL object + unsafe { + let result = SSL_set_SSL_CTX(stream.ssl().as_ptr(), value.ctx().as_ptr()); + if result.is_null() { + return Err(vm.new_runtime_error("Failed to set SSL context".to_owned())); + } + } + + // Update self.ctx to the new context + *self.ctx.write() = value; + Ok(()) } #[pygetset] fn server_hostname(&self) -> Option<PyStrRef> { @@ -1257,20 +2007,38 @@ mod _ssl { vm: &VirtualMachine, ) -> PyResult<Option<PyObjectRef>> { let binary = binary.unwrap_or(false); - let stream = self.stream.read(); + let stream = self.connection.read(); if !stream.ssl().is_init_finished() { return Err(vm.new_value_error("handshake not done yet")); } - stream - .ssl() - .peer_certificate() - .map(|cert| cert_to_py(vm, &cert, binary)) - .transpose() + + let peer_cert = stream.ssl().peer_certificate(); + let Some(cert) = peer_cert else { + return Ok(None); + }; + + if binary { + // Return DER-encoded certificate + cert_to_py(vm, &cert, true).map(Some) + } else { + // Check verify_mode + unsafe { + let ssl_ctx = sys::SSL_get_SSL_CTX(stream.ssl().as_ptr()); + let verify_mode = sys::SSL_CTX_get_verify_mode(ssl_ctx); + if (verify_mode & sys::SSL_VERIFY_PEER as libc::c_int) == 0 { + // Return empty dict when SSL_VERIFY_PEER is not set + Ok(Some(vm.ctx.new_dict().into())) + } else { + // Return decoded certificate + cert_to_py(vm, &cert, false).map(Some) + } + } + } } #[pymethod] fn get_unverified_chain(&self, vm: &VirtualMachine) -> PyResult<Option<PyListRef>> { - let stream = self.stream.read(); + let stream = self.connection.read(); let Some(chain) = stream.ssl().peer_cert_chain() else { return Ok(None); }; @@ -1289,7 +2057,7 @@ mod _ssl { #[pymethod] fn get_verified_chain(&self, vm: &VirtualMachine) -> PyResult<Option<PyListRef>> { - let stream = self.stream.read(); + let stream = self.connection.read(); unsafe { let chain = sys::SSL_get0_verified_chain(stream.ssl().as_ptr()); if chain.is_null() { @@ -1322,13 +2090,13 @@ mod _ssl { #[pymethod] fn version(&self) -> Option<&'static str> { - let v = self.stream.read().ssl().version_str(); + let v = self.connection.read().ssl().version_str(); if v == "unknown" { None } else { Some(v) } } #[pymethod] fn cipher(&self) -> Option<CipherTuple> { - self.stream + self.connection .read() .ssl() .current_cipher() @@ -1339,7 +2107,7 @@ mod _ssl { fn shared_ciphers(&self, vm: &VirtualMachine) -> Option<PyListRef> { #[cfg(ossl110)] { - let stream = self.stream.read(); + let stream = self.connection.read(); unsafe { let server_ciphers = SSL_get_ciphers(stream.ssl().as_ptr()); if server_ciphers.is_null() { @@ -1402,7 +2170,7 @@ mod _ssl { fn selected_alpn_protocol(&self) -> Option<String> { #[cfg(ossl102)] { - let stream = self.stream.read(); + let stream = self.connection.read(); unsafe { let mut out: *const libc::c_uchar = std::ptr::null(); let mut outlen: libc::c_uint = 0; @@ -1440,7 +2208,7 @@ mod _ssl { ))); } - let stream = self.stream.read(); + let stream = self.connection.read(); let ssl_ptr = stream.ssl().as_ptr(); unsafe { @@ -1470,13 +2238,10 @@ mod _ssl { fn verify_client_post_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { #[cfg(ossl111)] { - let stream = self.stream.read(); + let stream = self.connection.read(); let result = unsafe { SSL_verify_client_post_handshake(stream.ssl().as_ptr()) }; if result == 0 { - Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Post-handshake authentication failed".to_owned(), - )) + Err(convert_openssl_error(vm, openssl::error::ErrorStack::get())) } else { Ok(()) } @@ -1491,7 +2256,15 @@ mod _ssl { #[pymethod] fn shutdown(&self, vm: &VirtualMachine) -> PyResult<PyRef<PySocket>> { - let stream = self.stream.read(); + let stream = self.connection.read(); + + // BIO mode doesn't have an underlying socket + if stream.is_bio() { + return Err(vm.new_not_implemented_error( + "shutdown() is not supported for BIO-based SSL objects".to_owned(), + )); + } + let ssl_ptr = stream.ssl().as_ptr(); // Perform SSL shutdown @@ -1514,7 +2287,9 @@ mod _ssl { // Return the underlying socket // Get the socket from the stream (SocketStream wraps PyRef<PySocket>) - let socket = stream.get_ref(); + let socket = stream + .get_ref() + .expect("unwrap() called on bio mode; should only be called in socket mode"); Ok(socket.0.clone()) } @@ -1526,7 +2301,7 @@ mod _ssl { #[cfg(not(osslconf = "OPENSSL_NO_COMP"))] #[pymethod] fn compression(&self) -> Option<&'static str> { - let stream = self.stream.read(); + let stream = self.connection.read(); let comp_method = unsafe { sys::SSL_get_current_compression(stream.ssl().as_ptr()) }; if comp_method.is_null() { return None; @@ -1541,14 +2316,35 @@ mod _ssl { #[pymethod] fn do_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { - let mut stream = self.stream.write(); - let timeout = stream.get_ref().timeout_deadline(); + let mut stream = self.connection.write(); + let ssl_ptr = stream.ssl().as_ptr(); + + // BIO mode: no timeout/select logic, just do handshake + if stream.is_bio() { + return stream.do_handshake().map_err(|e| { + let exc = convert_ssl_error(vm, e); + // If it's a cert verification error, set verify info + if exc.class().is(PySslCertVerificationError::class(&vm.ctx)) { + set_verify_error_info(&exc, ssl_ptr, vm); + } + exc + }); + } + + // Socket mode: handle timeout and blocking + let timeout = stream + .get_ref() + .expect("handshake called in bio mode; should only be called in socket mode") + .timeout_deadline(); loop { let err = match stream.do_handshake() { Ok(()) => return Ok(()), Err(e) => e, }; - let (needs, state) = stream.get_ref().socket_needs(&err, &timeout); + let (needs, state) = stream + .get_ref() + .expect("handshake called in bio mode; should only be called in socket mode") + .socket_needs(&err, &timeout); match state { SelectRet::TimedOut => { return Err(socket::timeout_error_msg( @@ -1564,17 +2360,32 @@ mod _ssl { } } } - return Err(convert_ssl_error(vm, err)); + let exc = convert_ssl_error(vm, err); + // If it's a cert verification error, set verify info + if exc.class().is(PySslCertVerificationError::class(&vm.ctx)) { + set_verify_error_info(&exc, ssl_ptr, vm); + } + return Err(exc); } } #[pymethod] fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> { - let mut stream = self.stream.write(); + let mut stream = self.connection.write(); let data = data.borrow_buf(); let data = &*data; - let timeout = stream.get_ref().timeout_deadline(); - let state = stream.get_ref().select(SslNeeds::Write, &timeout); + + // BIO mode: no timeout/select logic + if stream.is_bio() { + return stream.ssl_write(data).map_err(|e| convert_ssl_error(vm, e)); + } + + // Socket mode: handle timeout and blocking + let socket_ref = stream + .get_ref() + .expect("write called in bio mode; should only be called in socket mode"); + let timeout = socket_ref.timeout_deadline(); + let state = socket_ref.select(SslNeeds::Write, &timeout); match state { SelectRet::TimedOut => { return Err(socket::timeout_error_msg( @@ -1590,7 +2401,10 @@ mod _ssl { Ok(len) => return Ok(len), Err(e) => e, }; - let (needs, state) = stream.get_ref().socket_needs(&err, &timeout); + let (needs, state) = stream + .get_ref() + .expect("write called in bio mode; should only be called in socket mode") + .socket_needs(&err, &timeout); match state { SelectRet::TimedOut => { return Err(socket::timeout_error_msg( @@ -1612,7 +2426,7 @@ mod _ssl { #[pygetset] fn session(&self, _vm: &VirtualMachine) -> PyResult<Option<PySslSession>> { - let stream = self.stream.read(); + let stream = self.connection.read(); unsafe { let session_ptr = sys::SSL_get_session(stream.ssl().as_ptr()); if session_ptr.is_null() { @@ -1624,7 +2438,7 @@ mod _ssl { Ok(Some(PySslSession { session: session_ptr, - ctx: self.ctx.clone(), + ctx: self.ctx.read().clone(), })) } } @@ -1639,7 +2453,7 @@ mod _ssl { // Check if session refers to the same SSLContext if !std::ptr::eq( - self.ctx.ctx.read().as_ptr(), + self.ctx.read().ctx.read().as_ptr(), session.ctx.ctx.read().as_ptr(), ) { return Err( @@ -1655,7 +2469,7 @@ mod _ssl { } // Check if handshake is not finished - let stream = self.stream.read(); + let stream = self.connection.read(); unsafe { if sys::SSL_is_init_finished(stream.ssl().as_ptr()) != 0 { return Err( @@ -1673,7 +2487,7 @@ mod _ssl { #[pygetset] fn session_reused(&self) -> bool { - let stream = self.stream.read(); + let stream = self.connection.read(); unsafe { sys::SSL_session_reused(stream.ssl().as_ptr()) != 0 } } @@ -1684,7 +2498,16 @@ mod _ssl { buffer: OptionalArg<ArgMemoryBuffer>, vm: &VirtualMachine, ) -> PyResult { - let mut stream = self.stream.write(); + // Special case: reading 0 bytes should return empty bytes immediately + if n == 0 { + return if buffer.is_present() { + Ok(vm.ctx.new_int(0).into()) + } else { + Ok(vm.ctx.new_bytes(vec![]).into()) + }; + } + + let mut stream = self.connection.write(); let mut inner_buffer = if let OptionalArg::Present(buffer) = &buffer { Either::A(buffer.borrow_buf_mut()) } else { @@ -1698,33 +2521,49 @@ mod _ssl { Some(b) => b, None => buf, }; - let timeout = stream.get_ref().timeout_deadline(); - let count = loop { - let err = match stream.ssl_read(buf) { - Ok(count) => break count, - Err(e) => e, - }; - if err.code() == ssl::ErrorCode::ZERO_RETURN - && stream.get_shutdown() == ssl::ShutdownState::RECEIVED - { - break 0; + + // BIO mode: no timeout/select logic + let count = if stream.is_bio() { + match stream.ssl_read(buf) { + Ok(count) => count, + Err(e) => return Err(convert_ssl_error(vm, e)), } - let (needs, state) = stream.get_ref().socket_needs(&err, &timeout); - match state { - SelectRet::TimedOut => { - return Err(socket::timeout_error_msg( - vm, - "The read operation timed out".to_owned(), - )); + } else { + // Socket mode: handle timeout and blocking + let timeout = stream + .get_ref() + .expect("read called in bio mode; should only be called in socket mode") + .timeout_deadline(); + loop { + let err = match stream.ssl_read(buf) { + Ok(count) => break count, + Err(e) => e, + }; + if err.code() == ssl::ErrorCode::ZERO_RETURN + && stream.get_shutdown() == ssl::ShutdownState::RECEIVED + { + break 0; } - SelectRet::Nonblocking => {} - _ => { - if needs.is_some() { - continue; + let (needs, state) = stream + .get_ref() + .expect("read called in bio mode; should only be called in socket mode") + .socket_needs(&err, &timeout); + match state { + SelectRet::TimedOut => { + return Err(socket::timeout_error_msg( + vm, + "The read operation timed out".to_owned(), + )); + } + SelectRet::Nonblocking => {} + _ => { + if needs.is_some() { + continue; + } } } + return Err(convert_ssl_error(vm, err)); } - return Err(convert_ssl_error(vm, err)); }; let ret = match inner_buffer { Either::A(_buf) => vm.ctx.new_int(count).into(), @@ -1844,6 +2683,7 @@ mod _ssl { #[cfg(ossl111)] unsafe extern "C" { fn SSL_verify_client_post_handshake(ssl: *const sys::SSL) -> libc::c_int; + fn SSL_set_post_handshake_auth(ssl: *mut sys::SSL, val: libc::c_int); } #[cfg(ossl110)] @@ -1851,6 +2691,152 @@ mod _ssl { fn SSL_CTX_get_security_level(ctx: *const sys::SSL_CTX) -> libc::c_int; } + unsafe extern "C" { + fn SSL_set_SSL_CTX(ssl: *mut sys::SSL, ctx: *mut sys::SSL_CTX) -> *mut sys::SSL_CTX; + } + + #[cfg(ossl110)] + unsafe extern "C" { + fn SSL_SESSION_has_ticket(session: *const sys::SSL_SESSION) -> libc::c_int; + fn SSL_SESSION_get_ticket_lifetime_hint(session: *const sys::SSL_SESSION) -> libc::c_ulong; + } + + // X509 object types + const X509_LU_X509: libc::c_int = 1; + const X509_LU_CRL: libc::c_int = 2; + + unsafe extern "C" { + fn X509_OBJECT_get_type(obj: *const sys::X509_OBJECT) -> libc::c_int; + } + + // SSL session statistics constants (used with SSL_CTX_ctrl) + const SSL_CTRL_SESS_NUMBER: libc::c_int = 20; + const SSL_CTRL_SESS_CONNECT: libc::c_int = 21; + const SSL_CTRL_SESS_CONNECT_GOOD: libc::c_int = 22; + const SSL_CTRL_SESS_CONNECT_RENEGOTIATE: libc::c_int = 23; + const SSL_CTRL_SESS_ACCEPT: libc::c_int = 24; + const SSL_CTRL_SESS_ACCEPT_GOOD: libc::c_int = 25; + const SSL_CTRL_SESS_ACCEPT_RENEGOTIATE: libc::c_int = 26; + const SSL_CTRL_SESS_HIT: libc::c_int = 27; + const SSL_CTRL_SESS_MISSES: libc::c_int = 29; + const SSL_CTRL_SESS_TIMEOUTS: libc::c_int = 30; + const SSL_CTRL_SESS_CACHE_FULL: libc::c_int = 31; + + // SSL session statistics functions (implemented as macros in OpenSSL) + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_number(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_NUMBER, 0, std::ptr::null_mut()) } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_connect(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_CONNECT, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_connect_good(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_CONNECT_GOOD, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_connect_renegotiate(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_CONNECT_RENEGOTIATE, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_accept(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_ACCEPT, 0, std::ptr::null_mut()) } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_accept_good(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_ACCEPT_GOOD, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_accept_renegotiate(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_ACCEPT_RENEGOTIATE, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_hits(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_HIT, 0, std::ptr::null_mut()) } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_misses(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_MISSES, 0, std::ptr::null_mut()) } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_timeouts(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_TIMEOUTS, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_cache_full(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_CACHE_FULL, + 0, + std::ptr::null_mut(), + ) + } + } + + // DH parameters functions + unsafe extern "C" { + fn PEM_read_DHparams( + fp: *mut libc::FILE, + x: *mut *mut sys::DH, + cb: *mut libc::c_void, + u: *mut libc::c_void, + ) -> *mut sys::DH; + } + // OpenSSL BIO helper functions // These are typically macros in OpenSSL, implemented via BIO_ctrl const BIO_CTRL_PENDING: libc::c_int = 10; @@ -1903,7 +2889,7 @@ mod _ssl { } } - #[pyclass(with(Constructor))] + #[pyclass(flags(IMMUTABLETYPE), with(Constructor))] impl PySslMemoryBio { #[pygetset] fn pending(&self) -> usize { @@ -1974,7 +2960,7 @@ mod _ssl { } } - #[pyclass(with(Comparable))] + #[pyclass(flags(IMMUTABLETYPE), with(Comparable))] impl PySslSession { #[pygetset] fn time(&self) -> i64 { @@ -1997,17 +2983,14 @@ mod _ssl { #[pygetset] fn ticket_lifetime_hint(&self) -> u64 { - // SSL_SESSION_get_ticket_lifetime_hint may not be available in older OpenSSL - // Return 0 as default if not available + // SSL_SESSION_get_ticket_lifetime_hint available in OpenSSL 1.1.0+ #[cfg(ossl110)] { - // For now, return 0 as this function may not be in openssl-sys - let _ = self.session; - 0 + unsafe { SSL_SESSION_get_ticket_lifetime_hint(self.session) as u64 } } #[cfg(not(ossl110))] { - let _ = self.session; + // Not available in older OpenSSL versions 0 } } @@ -2024,17 +3007,14 @@ mod _ssl { #[pygetset] fn has_ticket(&self) -> bool { - // SSL_SESSION_has_ticket may not be available in older OpenSSL - // Return false as default + // SSL_SESSION_has_ticket available in OpenSSL 1.1.0+ #[cfg(ossl110)] { - // For now, return false as this function may not be in openssl-sys - let _ = self.session; - false + unsafe { SSL_SESSION_has_ticket(self.session) != 0 } } #[cfg(not(ossl110))] { - let _ = self.session; + // Not available in older OpenSSL versions false } } @@ -2045,25 +3025,71 @@ mod _ssl { vm: &VirtualMachine, err: ErrorStack, ) -> PyBaseExceptionRef { - let cls = PySslError::class(&vm.ctx).to_owned(); match err.errors().last() { Some(e) => { + // Check if this is a system library error (errno-based) + // CPython: Modules/_ssl.c:667-671 + let lib = sys::ERR_GET_LIB(e.code()); + + if lib == sys::ERR_LIB_SYS { + // A system error is being reported; reason is set to errno + let reason = sys::ERR_GET_REASON(e.code()); + + // errno 2 = ENOENT = FileNotFoundError + let exc_type = if reason == 2 { + vm.ctx.exceptions.file_not_found_error.to_owned() + } else { + vm.ctx.exceptions.os_error.to_owned() + }; + let exc = vm.new_exception(exc_type, vec![vm.ctx.new_int(reason).into()]); + // Set errno attribute explicitly + let _ = exc + .as_object() + .set_attr("errno", vm.ctx.new_int(reason), vm); + return exc; + } + let caller = std::panic::Location::caller(); let (file, line) = (caller.file(), caller.line()); let file = file .rsplit_once(&['/', '\\'][..]) .map_or(file, |(_, basename)| basename); - // TODO: finish map - let default_errstr = e.reason().unwrap_or("unknown error"); - let errstr = match default_errstr { - "certificate verify failed" => "CERTIFICATE_VERIFY_FAILED", - _ => default_errstr, + + // Get error codes - same approach as CPython + let lib = sys::ERR_GET_LIB(e.code()); + let reason = sys::ERR_GET_REASON(e.code()); + + // Look up error mnemonic from our static tables + // CPython uses dict lookup: err_codes_to_names[(lib, reason)] + let key = super::ssl_data::encode_error_key(lib, reason); + let errstr = super::ssl_data::ERROR_CODES + .get(&key) + .copied() + .or_else(|| { + // Fallback: use OpenSSL's error string + e.reason() + }) + .unwrap_or("unknown error"); + + // Check if this is a certificate verification error + // ERR_LIB_SSL = 20 (from _ssl_data_300.h) + // SSL_R_CERTIFICATE_VERIFY_FAILED = 134 (from _ssl_data_300.h) + let is_cert_verify_error = lib == 20 && reason == 134; + + // Look up library name from our static table + // CPython uses: lib_codes_to_names[lib] + let lib_name = super::ssl_data::LIBRARY_CODES.get(&(lib as u32)).copied(); + + // Use SSLCertVerificationError for certificate verification failures + let cls = if is_cert_verify_error { + PySslCertVerificationError::class(&vm.ctx).to_owned() + } else { + PySslError::class(&vm.ctx).to_owned() }; // Build message - let lib_obj = e.library(); - let msg = if let Some(lib) = lib_obj { - format!("[{lib}] {errstr} ({file}:{line})") + let msg = if let Some(lib_str) = lib_name { + format!("[{lib_str}] {errstr} ({file}:{line})") } else { format!("{errstr} ({file}:{line})") }; @@ -2083,19 +3109,58 @@ mod _ssl { let _ = exc_obj.set_attr("reason", reason_value, vm); // Set library attribute (None if not available) - let library_value: PyObjectRef = if let Some(lib) = lib_obj { - vm.ctx.new_str(lib).into() + let library_value: PyObjectRef = if let Some(lib_str) = lib_name { + vm.ctx.new_str(lib_str).into() } else { vm.ctx.none() }; let _ = exc_obj.set_attr("library", library_value, vm); + // For SSLCertVerificationError, set verify_code and verify_message + // Note: These will be set to None here, and can be updated by the caller + // if they have access to the SSL object + if is_cert_verify_error { + let _ = exc_obj.set_attr("verify_code", vm.ctx.none(), vm); + let _ = exc_obj.set_attr("verify_message", vm.ctx.none(), vm); + } + // Convert back to PyBaseExceptionRef - exc_obj.downcast().unwrap() + exc_obj.downcast().expect( + "exc_obj is created as PyBaseExceptionRef and must downcast successfully", + ) + } + None => { + let cls = PySslError::class(&vm.ctx).to_owned(); + vm.new_exception_empty(cls) } - None => vm.new_exception_empty(cls), } } + + // Helper function to set verify_code and verify_message on SSLCertVerificationError + fn set_verify_error_info( + exc: &PyBaseExceptionRef, + ssl_ptr: *const sys::SSL, + vm: &VirtualMachine, + ) { + // Get verify result + let verify_code = unsafe { sys::SSL_get_verify_result(ssl_ptr) }; + let verify_code_obj = vm.ctx.new_int(verify_code); + + // Get verify message + let verify_message = unsafe { + let verify_str = sys::X509_verify_cert_error_string(verify_code); + if verify_str.is_null() { + vm.ctx.none() + } else { + let c_str = std::ffi::CStr::from_ptr(verify_str); + vm.ctx.new_str(c_str.to_string_lossy()).into() + } + }; + + let exc_obj = exc.as_object(); + let _ = exc_obj.set_attr("verify_code", verify_code_obj, vm); + let _ = exc_obj.set_attr("verify_message", verify_message, vm); + } #[track_caller] fn convert_ssl_error( vm: &VirtualMachine, @@ -2203,6 +3268,24 @@ mod _ssl { (cipher.name(), cipher.version(), cipher.bits().secret) } + fn cipher_description(cipher: *const sys::SSL_CIPHER) -> String { + unsafe { + // SSL_CIPHER_description writes up to 128 bytes + let mut buf = vec![0u8; 256]; + let result = sys::SSL_CIPHER_description( + cipher, + buf.as_mut_ptr() as *mut libc::c_char, + buf.len() as i32, + ); + if result.is_null() { + return String::from("No description available"); + } + // Find the null terminator + let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len()); + String::from_utf8_lossy(&buf[..len]).trim().to_string() + } + } + impl Read for SocketStream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { let mut socket: &PySocket = &self.0; diff --git a/stdlib/src/ssl/ssl_data_111.rs b/stdlib/src/ssl/ssl_data_111.rs new file mode 100644 index 00000000000..2d5f56f4855 --- /dev/null +++ b/stdlib/src/ssl/ssl_data_111.rs @@ -0,0 +1,1347 @@ +// File generated by tools/make_ssl_data_rs.py +// Generated on 2025-10-29T07:17:23.692784+00:00 +// Source: OpenSSL from /tmp/openssl-1.1.1 +// spell-checker: disable + +use phf::phf_map; + +// Maps lib_code -> library name +// Example: 20 -> "SSL" +pub static LIBRARY_CODES: phf::Map<u32, &'static str> = phf_map! { + 1u32 => "NONE", + 2u32 => "SYS", + 3u32 => "BN", + 4u32 => "RSA", + 5u32 => "DH", + 6u32 => "EVP", + 7u32 => "BUF", + 8u32 => "OBJ", + 9u32 => "PEM", + 10u32 => "DSA", + 11u32 => "X509", + 12u32 => "METH", + 13u32 => "ASN1", + 14u32 => "CONF", + 15u32 => "CRYPTO", + 16u32 => "EC", + 20u32 => "SSL", + 21u32 => "SSL23", + 22u32 => "SSL2", + 23u32 => "SSL3", + 30u32 => "RSAREF", + 31u32 => "PROXY", + 32u32 => "BIO", + 33u32 => "PKCS7", + 34u32 => "X509V3", + 35u32 => "PKCS12", + 36u32 => "RAND", + 37u32 => "DSO", + 38u32 => "ENGINE", + 39u32 => "OCSP", + 40u32 => "UI", + 41u32 => "COMP", + 42u32 => "ECDSA", + 43u32 => "ECDH", + 44u32 => "OSSL_STORE", + 45u32 => "FIPS", + 46u32 => "CMS", + 47u32 => "TS", + 48u32 => "HMAC", + 49u32 => "JPAKE", + 50u32 => "CT", + 51u32 => "ASYNC", + 52u32 => "KDF", + 53u32 => "SM2", + 128u32 => "USER", +}; + +// Maps encoded (lib, reason) -> error mnemonic +// Example: encode_error_key(20, 134) -> "CERTIFICATE_VERIFY_FAILED" +// Key encoding: (lib << 32) | reason +pub static ERROR_CODES: phf::Map<u64, &'static str> = phf_map! { + 55834575019u64 => "ADDING_OBJECT", + 55834575051u64 => "ASN1_PARSE_ERROR", + 55834575052u64 => "ASN1_SIG_PARSE_ERROR", + 55834574948u64 => "AUX_ERROR", + 55834574950u64 => "BAD_OBJECT_HEADER", + 55834575078u64 => "BAD_TEMPLATE", + 55834575062u64 => "BMPSTRING_IS_WRONG_LENGTH", + 55834574953u64 => "BN_LIB", + 55834574954u64 => "BOOLEAN_IS_WRONG_LENGTH", + 55834574955u64 => "BUFFER_TOO_SMALL", + 55834574956u64 => "CIPHER_HAS_NO_OBJECT_IDENTIFIER", + 55834575065u64 => "CONTEXT_NOT_INITIALISED", + 55834574957u64 => "DATA_IS_WRONG", + 55834574958u64 => "DECODE_ERROR", + 55834575022u64 => "DEPTH_EXCEEDED", + 55834575046u64 => "DIGEST_AND_KEY_TYPE_NOT_SUPPORTED", + 55834574960u64 => "ENCODE_ERROR", + 55834575021u64 => "ERROR_GETTING_TIME", + 55834575020u64 => "ERROR_LOADING_SECTION", + 55834574962u64 => "ERROR_SETTING_CIPHER_PARAMS", + 55834574963u64 => "EXPECTING_AN_INTEGER", + 55834574964u64 => "EXPECTING_AN_OBJECT", + 55834574967u64 => "EXPLICIT_LENGTH_MISMATCH", + 55834574968u64 => "EXPLICIT_TAG_NOT_CONSTRUCTED", + 55834574969u64 => "FIELD_MISSING", + 55834574970u64 => "FIRST_NUM_TOO_LARGE", + 55834574971u64 => "HEADER_TOO_LONG", + 55834575023u64 => "ILLEGAL_BITSTRING_FORMAT", + 55834575024u64 => "ILLEGAL_BOOLEAN", + 55834574972u64 => "ILLEGAL_CHARACTERS", + 55834575025u64 => "ILLEGAL_FORMAT", + 55834575026u64 => "ILLEGAL_HEX", + 55834575027u64 => "ILLEGAL_IMPLICIT_TAG", + 55834575028u64 => "ILLEGAL_INTEGER", + 55834575074u64 => "ILLEGAL_NEGATIVE_VALUE", + 55834575029u64 => "ILLEGAL_NESTED_TAGGING", + 55834574973u64 => "ILLEGAL_NULL", + 55834575030u64 => "ILLEGAL_NULL_VALUE", + 55834575031u64 => "ILLEGAL_OBJECT", + 55834574974u64 => "ILLEGAL_OPTIONAL_ANY", + 55834575018u64 => "ILLEGAL_OPTIONS_ON_ITEM_TEMPLATE", + 55834575069u64 => "ILLEGAL_PADDING", + 55834574975u64 => "ILLEGAL_TAGGED_ANY", + 55834575032u64 => "ILLEGAL_TIME_VALUE", + 55834575070u64 => "ILLEGAL_ZERO_CONTENT", + 55834575033u64 => "INTEGER_NOT_ASCII_FORMAT", + 55834574976u64 => "INTEGER_TOO_LARGE_FOR_LONG", + 55834575068u64 => "INVALID_BIT_STRING_BITS_LEFT", + 55834574977u64 => "INVALID_BMPSTRING_LENGTH", + 55834574978u64 => "INVALID_DIGIT", + 55834575053u64 => "INVALID_MIME_TYPE", + 55834575034u64 => "INVALID_MODIFIER", + 55834575035u64 => "INVALID_NUMBER", + 55834575064u64 => "INVALID_OBJECT_ENCODING", + 55834575075u64 => "INVALID_SCRYPT_PARAMETERS", + 55834574979u64 => "INVALID_SEPARATOR", + 55834575066u64 => "INVALID_STRING_TABLE_VALUE", + 55834574981u64 => "INVALID_UNIVERSALSTRING_LENGTH", + 55834574982u64 => "INVALID_UTF8STRING", + 55834575067u64 => "INVALID_VALUE", + 55834575036u64 => "LIST_ERROR", + 55834575054u64 => "MIME_NO_CONTENT_TYPE", + 55834575055u64 => "MIME_PARSE_ERROR", + 55834575056u64 => "MIME_SIG_PARSE_ERROR", + 55834574985u64 => "MISSING_EOC", + 55834574986u64 => "MISSING_SECOND_NUMBER", + 55834575037u64 => "MISSING_VALUE", + 55834574987u64 => "MSTRING_NOT_UNIVERSAL", + 55834574988u64 => "MSTRING_WRONG_TAG", + 55834575045u64 => "NESTED_ASN1_STRING", + 55834575049u64 => "NESTED_TOO_DEEP", + 55834574989u64 => "NON_HEX_CHARACTERS", + 55834575038u64 => "NOT_ASCII_FORMAT", + 55834574990u64 => "NOT_ENOUGH_DATA", + 55834575057u64 => "NO_CONTENT_TYPE", + 55834574991u64 => "NO_MATCHING_CHOICE_TYPE", + 55834575058u64 => "NO_MULTIPART_BODY_FAILURE", + 55834575059u64 => "NO_MULTIPART_BOUNDARY", + 55834575060u64 => "NO_SIG_CONTENT_TYPE", + 55834574992u64 => "NULL_IS_WRONG_LENGTH", + 55834575039u64 => "OBJECT_NOT_ASCII_FORMAT", + 55834574993u64 => "ODD_NUMBER_OF_CHARS", + 55834574995u64 => "SECOND_NUMBER_TOO_LARGE", + 55834574996u64 => "SEQUENCE_LENGTH_MISMATCH", + 55834574997u64 => "SEQUENCE_NOT_CONSTRUCTED", + 55834575040u64 => "SEQUENCE_OR_SET_NEEDS_CONFIG", + 55834574998u64 => "SHORT_LINE", + 55834575061u64 => "SIG_INVALID_MIME_TYPE", + 55834575050u64 => "STREAMING_NOT_SUPPORTED", + 55834574999u64 => "STRING_TOO_LONG", + 55834575000u64 => "STRING_TOO_SHORT", + 55834575002u64 => "THE_ASN1_OBJECT_IDENTIFIER_IS_NOT_KNOWN_FOR_THIS_MD", + 55834575041u64 => "TIME_NOT_ASCII_FORMAT", + 55834575071u64 => "TOO_LARGE", + 55834575003u64 => "TOO_LONG", + 55834575072u64 => "TOO_SMALL", + 55834575004u64 => "TYPE_NOT_CONSTRUCTED", + 55834575043u64 => "TYPE_NOT_PRIMITIVE", + 55834575007u64 => "UNEXPECTED_EOC", + 55834575063u64 => "UNIVERSALSTRING_IS_WRONG_LENGTH", + 55834575008u64 => "UNKNOWN_FORMAT", + 55834575009u64 => "UNKNOWN_MESSAGE_DIGEST_ALGORITHM", + 55834575010u64 => "UNKNOWN_OBJECT_TYPE", + 55834575011u64 => "UNKNOWN_PUBLIC_KEY_TYPE", + 55834575047u64 => "UNKNOWN_SIGNATURE_ALGORITHM", + 55834575042u64 => "UNKNOWN_TAG", + 55834575012u64 => "UNSUPPORTED_ANY_DEFINED_BY_TYPE", + 55834575076u64 => "UNSUPPORTED_CIPHER", + 55834575015u64 => "UNSUPPORTED_PUBLIC_KEY_TYPE", + 55834575044u64 => "UNSUPPORTED_TYPE", + 55834575073u64 => "WRONG_INTEGER_TYPE", + 55834575048u64 => "WRONG_PUBLIC_KEY_TYPE", + 55834575016u64 => "WRONG_TAG", + 219043332197u64 => "FAILED_TO_SET_POOL", + 219043332198u64 => "FAILED_TO_SWAP_CONTEXT", + 219043332201u64 => "INIT_FAILED", + 219043332199u64 => "INVALID_POOL_SIZE", + 137438953572u64 => "ACCEPT_ERROR", + 137438953613u64 => "ADDRINFO_ADDR_IS_NOT_AF_INET", + 137438953601u64 => "AMBIGUOUS_HOST_OR_SERVICE", + 137438953573u64 => "BAD_FOPEN_MODE", + 137438953596u64 => "BROKEN_PIPE", + 137438953575u64 => "CONNECT_ERROR", + 137438953579u64 => "GETHOSTBYNAME_ADDR_IS_NOT_AF_INET", + 137438953604u64 => "GETSOCKNAME_ERROR", + 137438953605u64 => "GETSOCKNAME_TRUNCATED_ADDRESS", + 137438953606u64 => "GETTING_SOCKTYPE", + 137438953597u64 => "INVALID_ARGUMENT", + 137438953607u64 => "INVALID_SOCKET", + 137438953595u64 => "IN_USE", + 137438953574u64 => "LENGTH_TOO_LONG", + 137438953608u64 => "LISTEN_V6_ONLY", + 137438953614u64 => "LOOKUP_RETURNED_NOTHING", + 137438953602u64 => "MALFORMED_HOST_OR_SERVICE", + 137438953582u64 => "NBIO_CONNECT_ERROR", + 137438953615u64 => "NO_ACCEPT_ADDR_OR_SERVICE_SPECIFIED", + 137438953616u64 => "NO_HOSTNAME_OR_SERVICE_SPECIFIED", + 137438953585u64 => "NO_PORT_DEFINED", + 137438953600u64 => "NO_SUCH_FILE", + 137438953587u64 => "NULL_PARAMETER", + 137438953589u64 => "UNABLE_TO_BIND_SOCKET", + 137438953590u64 => "UNABLE_TO_CREATE_SOCKET", + 137438953609u64 => "UNABLE_TO_KEEPALIVE", + 137438953591u64 => "UNABLE_TO_LISTEN_SOCKET", + 137438953610u64 => "UNABLE_TO_NODELAY", + 137438953611u64 => "UNABLE_TO_REUSEADDR", + 137438953617u64 => "UNAVAILABLE_IP_FAMILY", + 137438953592u64 => "UNINITIALIZED", + 137438953612u64 => "UNKNOWN_INFO_TYPE", + 137438953618u64 => "UNSUPPORTED_IP_FAMILY", + 137438953593u64 => "UNSUPPORTED_METHOD", + 137438953603u64 => "UNSUPPORTED_PROTOCOL_FAMILY", + 137438953598u64 => "WRITE_TO_READ_ONLY_BIO", + 137438953594u64 => "WSASTARTUP", + 12884901988u64 => "ARG2_LT_ARG3", + 12884901989u64 => "BAD_RECIPROCAL", + 12884902002u64 => "BIGNUM_TOO_LONG", + 12884902006u64 => "BITS_TOO_SMALL", + 12884901990u64 => "CALLED_WITH_EVEN_MODULUS", + 12884901991u64 => "DIV_BY_ZERO", + 12884901992u64 => "ENCODING_ERROR", + 12884901993u64 => "EXPAND_ON_STATIC_BIGNUM_DATA", + 12884901998u64 => "INPUT_NOT_REDUCED", + 12884901994u64 => "INVALID_LENGTH", + 12884902003u64 => "INVALID_RANGE", + 12884902007u64 => "INVALID_SHIFT", + 12884901999u64 => "NOT_A_SQUARE", + 12884901995u64 => "NOT_INITIALIZED", + 12884901996u64 => "NO_INVERSE", + 12884902004u64 => "NO_SOLUTION", + 12884902005u64 => "PRIVATE_KEY_TOO_LARGE", + 12884902000u64 => "P_IS_NOT_PRIME", + 12884902001u64 => "TOO_MANY_ITERATIONS", + 12884901997u64 => "TOO_MANY_TEMPORARY_VARIABLES", + 197568495715u64 => "ADD_SIGNER_ERROR", + 197568495777u64 => "ATTRIBUTE_ERROR", + 197568495791u64 => "CERTIFICATE_ALREADY_PRESENT", + 197568495776u64 => "CERTIFICATE_HAS_NO_KEYID", + 197568495716u64 => "CERTIFICATE_VERIFY_ERROR", + 197568495717u64 => "CIPHER_INITIALISATION_ERROR", + 197568495718u64 => "CIPHER_PARAMETER_INITIALISATION_ERROR", + 197568495719u64 => "CMS_DATAFINAL_ERROR", + 197568495720u64 => "CMS_LIB", + 197568495786u64 => "CONTENTIDENTIFIER_MISMATCH", + 197568495721u64 => "CONTENT_NOT_FOUND", + 197568495787u64 => "CONTENT_TYPE_MISMATCH", + 197568495722u64 => "CONTENT_TYPE_NOT_COMPRESSED_DATA", + 197568495723u64 => "CONTENT_TYPE_NOT_ENVELOPED_DATA", + 197568495724u64 => "CONTENT_TYPE_NOT_SIGNED_DATA", + 197568495725u64 => "CONTENT_VERIFY_ERROR", + 197568495726u64 => "CTRL_ERROR", + 197568495727u64 => "CTRL_FAILURE", + 197568495728u64 => "DECRYPT_ERROR", + 197568495729u64 => "ERROR_GETTING_PUBLIC_KEY", + 197568495730u64 => "ERROR_READING_MESSAGEDIGEST_ATTRIBUTE", + 197568495731u64 => "ERROR_SETTING_KEY", + 197568495732u64 => "ERROR_SETTING_RECIPIENTINFO", + 197568495733u64 => "INVALID_ENCRYPTED_KEY_LENGTH", + 197568495792u64 => "INVALID_KEY_ENCRYPTION_PARAMETER", + 197568495734u64 => "INVALID_KEY_LENGTH", + 197568495735u64 => "MD_BIO_INIT_ERROR", + 197568495736u64 => "MESSAGEDIGEST_ATTRIBUTE_WRONG_LENGTH", + 197568495737u64 => "MESSAGEDIGEST_WRONG_LENGTH", + 197568495788u64 => "MSGSIGDIGEST_ERROR", + 197568495778u64 => "MSGSIGDIGEST_VERIFICATION_FAILURE", + 197568495779u64 => "MSGSIGDIGEST_WRONG_LENGTH", + 197568495780u64 => "NEED_ONE_SIGNER", + 197568495781u64 => "NOT_A_SIGNED_RECEIPT", + 197568495738u64 => "NOT_ENCRYPTED_DATA", + 197568495739u64 => "NOT_KEK", + 197568495797u64 => "NOT_KEY_AGREEMENT", + 197568495740u64 => "NOT_KEY_TRANSPORT", + 197568495793u64 => "NOT_PWRI", + 197568495741u64 => "NOT_SUPPORTED_FOR_THIS_KEY_TYPE", + 197568495742u64 => "NO_CIPHER", + 197568495743u64 => "NO_CONTENT", + 197568495789u64 => "NO_CONTENT_TYPE", + 197568495744u64 => "NO_DEFAULT_DIGEST", + 197568495745u64 => "NO_DIGEST_SET", + 197568495746u64 => "NO_KEY", + 197568495790u64 => "NO_KEY_OR_CERT", + 197568495747u64 => "NO_MATCHING_DIGEST", + 197568495748u64 => "NO_MATCHING_RECIPIENT", + 197568495782u64 => "NO_MATCHING_SIGNATURE", + 197568495783u64 => "NO_MSGSIGDIGEST", + 197568495794u64 => "NO_PASSWORD", + 197568495749u64 => "NO_PRIVATE_KEY", + 197568495750u64 => "NO_PUBLIC_KEY", + 197568495784u64 => "NO_RECEIPT_REQUEST", + 197568495751u64 => "NO_SIGNERS", + 197568495752u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 197568495785u64 => "RECEIPT_DECODE_ERROR", + 197568495753u64 => "RECIPIENT_ERROR", + 197568495754u64 => "SIGNER_CERTIFICATE_NOT_FOUND", + 197568495755u64 => "SIGNFINAL_ERROR", + 197568495756u64 => "SMIME_TEXT_ERROR", + 197568495757u64 => "STORE_INIT_ERROR", + 197568495758u64 => "TYPE_NOT_COMPRESSED_DATA", + 197568495759u64 => "TYPE_NOT_DATA", + 197568495760u64 => "TYPE_NOT_DIGESTED_DATA", + 197568495761u64 => "TYPE_NOT_ENCRYPTED_DATA", + 197568495762u64 => "TYPE_NOT_ENVELOPED_DATA", + 197568495763u64 => "UNABLE_TO_FINALIZE_CONTEXT", + 197568495764u64 => "UNKNOWN_CIPHER", + 197568495765u64 => "UNKNOWN_DIGEST_ALGORITHM", + 197568495766u64 => "UNKNOWN_ID", + 197568495767u64 => "UNSUPPORTED_COMPRESSION_ALGORITHM", + 197568495810u64 => "UNSUPPORTED_CONTENT_ENCRYPTION_ALGORITHM", + 197568495768u64 => "UNSUPPORTED_CONTENT_TYPE", + 197568495769u64 => "UNSUPPORTED_KEK_ALGORITHM", + 197568495795u64 => "UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM", + 197568495771u64 => "UNSUPPORTED_RECIPIENTINFO_TYPE", + 197568495770u64 => "UNSUPPORTED_RECIPIENT_TYPE", + 197568495772u64 => "UNSUPPORTED_TYPE", + 197568495773u64 => "UNWRAP_ERROR", + 197568495796u64 => "UNWRAP_FAILURE", + 197568495774u64 => "VERIFICATION_FAILURE", + 197568495775u64 => "WRAP_ERROR", + 176093659235u64 => "ZLIB_DEFLATE_ERROR", + 176093659236u64 => "ZLIB_INFLATE_ERROR", + 176093659237u64 => "ZLIB_NOT_SUPPORTED", + 60129542254u64 => "ERROR_LOADING_DSO", + 60129542259u64 => "LIST_CANNOT_BE_NULL", + 60129542244u64 => "MISSING_CLOSE_SQUARE_BRACKET", + 60129542245u64 => "MISSING_EQUAL_SIGN", + 60129542256u64 => "MISSING_INIT_FUNCTION", + 60129542253u64 => "MODULE_INITIALIZATION_ERROR", + 60129542246u64 => "NO_CLOSE_BRACE", + 60129542249u64 => "NO_CONF", + 60129542250u64 => "NO_CONF_OR_ENVIRONMENT_VARIABLE", + 60129542251u64 => "NO_SECTION", + 60129542258u64 => "NO_SUCH_FILE", + 60129542252u64 => "NO_VALUE", + 60129542265u64 => "NUMBER_TOO_LARGE", + 60129542255u64 => "RECURSIVE_DIRECTORY_INCLUDE", + 60129542261u64 => "SSL_COMMAND_SECTION_EMPTY", + 60129542262u64 => "SSL_COMMAND_SECTION_NOT_FOUND", + 60129542263u64 => "SSL_SECTION_EMPTY", + 60129542264u64 => "SSL_SECTION_NOT_FOUND", + 60129542247u64 => "UNABLE_TO_CREATE_NEW_SECTION", + 60129542257u64 => "UNKNOWN_MODULE_NAME", + 60129542260u64 => "VARIABLE_EXPANSION_TOO_LONG", + 60129542248u64 => "VARIABLE_HAS_NO_VALUE", + 64424509541u64 => "FIPS_MODE_NOT_SUPPORTED", + 64424509542u64 => "ILLEGAL_HEX_DIGIT", + 64424509543u64 => "ODD_NUMBER_OF_DIGITS", + 214748364908u64 => "BASE64_DECODE_ERROR", + 214748364900u64 => "INVALID_LOG_ID_LENGTH", + 214748364909u64 => "LOG_CONF_INVALID", + 214748364910u64 => "LOG_CONF_INVALID_KEY", + 214748364911u64 => "LOG_CONF_MISSING_DESCRIPTION", + 214748364912u64 => "LOG_CONF_MISSING_KEY", + 214748364913u64 => "LOG_KEY_INVALID", + 214748364916u64 => "SCT_FUTURE_TIMESTAMP", + 214748364904u64 => "SCT_INVALID", + 214748364907u64 => "SCT_INVALID_SIGNATURE", + 214748364905u64 => "SCT_LIST_INVALID", + 214748364914u64 => "SCT_LOG_ID_MISMATCH", + 214748364906u64 => "SCT_NOT_SET", + 214748364915u64 => "SCT_UNSUPPORTED_VERSION", + 214748364901u64 => "UNRECOGNIZED_SIGNATURE_NID", + 214748364902u64 => "UNSUPPORTED_ENTRY_TYPE", + 214748364903u64 => "UNSUPPORTED_VERSION", + 21474836581u64 => "BAD_GENERATOR", + 21474836589u64 => "BN_DECODE_ERROR", + 21474836586u64 => "BN_ERROR", + 21474836595u64 => "CHECK_INVALID_J_VALUE", + 21474836596u64 => "CHECK_INVALID_Q_VALUE", + 21474836602u64 => "CHECK_PUBKEY_INVALID", + 21474836603u64 => "CHECK_PUBKEY_TOO_LARGE", + 21474836604u64 => "CHECK_PUBKEY_TOO_SMALL", + 21474836597u64 => "CHECK_P_NOT_PRIME", + 21474836598u64 => "CHECK_P_NOT_SAFE_PRIME", + 21474836599u64 => "CHECK_Q_NOT_PRIME", + 21474836584u64 => "DECODE_ERROR", + 21474836590u64 => "INVALID_PARAMETER_NAME", + 21474836594u64 => "INVALID_PARAMETER_NID", + 21474836582u64 => "INVALID_PUBKEY", + 21474836592u64 => "KDF_PARAMETER_ERROR", + 21474836588u64 => "KEYS_NOT_SET", + 21474836605u64 => "MISSING_PUBKEY", + 21474836583u64 => "MODULUS_TOO_LARGE", + 21474836600u64 => "NOT_SUITABLE_GENERATOR", + 21474836587u64 => "NO_PARAMETERS_SET", + 21474836580u64 => "NO_PRIVATE_VALUE", + 21474836585u64 => "PARAMETER_ENCODING_ERROR", + 21474836591u64 => "PEER_KEY_ERROR", + 21474836593u64 => "SHARED_INFO_ERROR", + 21474836601u64 => "UNABLE_TO_CHECK_GENERATOR", + 42949673062u64 => "BAD_Q_VALUE", + 42949673068u64 => "BN_DECODE_ERROR", + 42949673069u64 => "BN_ERROR", + 42949673064u64 => "DECODE_ERROR", + 42949673066u64 => "INVALID_DIGEST_TYPE", + 42949673072u64 => "INVALID_PARAMETERS", + 42949673061u64 => "MISSING_PARAMETERS", + 42949673071u64 => "MISSING_PRIVATE_KEY", + 42949673063u64 => "MODULUS_TOO_LARGE", + 42949673067u64 => "NO_PARAMETERS_SET", + 42949673065u64 => "PARAMETER_ENCODING_ERROR", + 42949673073u64 => "Q_NOT_PRIME", + 42949673070u64 => "SEED_LEN_SMALL", + 158913790052u64 => "CTRL_FAILED", + 158913790062u64 => "DSO_ALREADY_LOADED", + 158913790065u64 => "EMPTY_FILE_STRUCTURE", + 158913790066u64 => "FAILURE", + 158913790053u64 => "FILENAME_TOO_BIG", + 158913790054u64 => "FINISH_FAILED", + 158913790067u64 => "INCORRECT_FILE_SYNTAX", + 158913790055u64 => "LOAD_FAILED", + 158913790061u64 => "NAME_TRANSLATION_FAILED", + 158913790063u64 => "NO_FILENAME", + 158913790056u64 => "NULL_HANDLE", + 158913790064u64 => "SET_FILENAME_FAILED", + 158913790057u64 => "STACK_ERROR", + 158913790058u64 => "SYM_FAILURE", + 158913790059u64 => "UNLOAD_FAILED", + 158913790060u64 => "UNSUPPORTED", + 68719476851u64 => "ASN1_ERROR", + 68719476892u64 => "BAD_SIGNATURE", + 68719476880u64 => "BIGNUM_OUT_OF_RANGE", + 68719476836u64 => "BUFFER_TOO_SMALL", + 68719476901u64 => "CANNOT_INVERT", + 68719476882u64 => "COORDINATES_OUT_OF_RANGE", + 68719476896u64 => "CURVE_DOES_NOT_SUPPORT_ECDH", + 68719476895u64 => "CURVE_DOES_NOT_SUPPORT_SIGNING", + 68719476853u64 => "D2I_ECPKPARAMETERS_FAILURE", + 68719476878u64 => "DECODE_ERROR", + 68719476854u64 => "DISCRIMINANT_IS_ZERO", + 68719476855u64 => "EC_GROUP_NEW_BY_NAME_FAILURE", + 68719476879u64 => "FIELD_TOO_LARGE", + 68719476883u64 => "GF2M_NOT_SUPPORTED", + 68719476856u64 => "GROUP2PKPARAMETERS_FAILURE", + 68719476857u64 => "I2D_ECPKPARAMETERS_FAILURE", + 68719476837u64 => "INCOMPATIBLE_OBJECTS", + 68719476848u64 => "INVALID_ARGUMENT", + 68719476846u64 => "INVALID_COMPRESSED_POINT", + 68719476845u64 => "INVALID_COMPRESSION_BIT", + 68719476877u64 => "INVALID_CURVE", + 68719476887u64 => "INVALID_DIGEST", + 68719476874u64 => "INVALID_DIGEST_TYPE", + 68719476838u64 => "INVALID_ENCODING", + 68719476839u64 => "INVALID_FIELD", + 68719476840u64 => "INVALID_FORM", + 68719476858u64 => "INVALID_GROUP_ORDER", + 68719476852u64 => "INVALID_KEY", + 68719476897u64 => "INVALID_OUTPUT_LENGTH", + 68719476869u64 => "INVALID_PEER_KEY", + 68719476868u64 => "INVALID_PENTANOMIAL_BASIS", + 68719476859u64 => "INVALID_PRIVATE_KEY", + 68719476873u64 => "INVALID_TRINOMIAL_BASIS", + 68719476884u64 => "KDF_PARAMETER_ERROR", + 68719476876u64 => "KEYS_NOT_SET", + 68719476872u64 => "LADDER_POST_FAILURE", + 68719476889u64 => "LADDER_PRE_FAILURE", + 68719476898u64 => "LADDER_STEP_FAILURE", + 68719476903u64 => "MISSING_OID", + 68719476860u64 => "MISSING_PARAMETERS", + 68719476861u64 => "MISSING_PRIVATE_KEY", + 68719476893u64 => "NEED_NEW_SETUP_VALUES", + 68719476871u64 => "NOT_A_NIST_PRIME", + 68719476862u64 => "NOT_IMPLEMENTED", + 68719476847u64 => "NOT_INITIALIZED", + 68719476875u64 => "NO_PARAMETERS_SET", + 68719476890u64 => "NO_PRIVATE_VALUE", + 68719476888u64 => "OPERATION_NOT_SUPPORTED", + 68719476870u64 => "PASSED_NULL_PARAMETER", + 68719476885u64 => "PEER_KEY_ERROR", + 68719476863u64 => "PKPARAMETERS2GROUP_FAILURE", + 68719476891u64 => "POINT_ARITHMETIC_FAILURE", + 68719476842u64 => "POINT_AT_INFINITY", + 68719476899u64 => "POINT_COORDINATES_BLIND_FAILURE", + 68719476843u64 => "POINT_IS_NOT_ON_CURVE", + 68719476894u64 => "RANDOM_NUMBER_GENERATION_FAILED", + 68719476886u64 => "SHARED_INFO_ERROR", + 68719476844u64 => "SLOT_FULL", + 68719476849u64 => "UNDEFINED_GENERATOR", + 68719476864u64 => "UNDEFINED_ORDER", + 68719476900u64 => "UNKNOWN_COFACTOR", + 68719476865u64 => "UNKNOWN_GROUP", + 68719476850u64 => "UNKNOWN_ORDER", + 68719476867u64 => "UNSUPPORTED_FIELD", + 68719476881u64 => "WRONG_CURVE_PARAMETERS", + 68719476866u64 => "WRONG_ORDER", + 163208757348u64 => "ALREADY_LOADED", + 163208757381u64 => "ARGUMENT_IS_NOT_A_NUMBER", + 163208757382u64 => "CMD_NOT_EXECUTABLE", + 163208757383u64 => "COMMAND_TAKES_INPUT", + 163208757384u64 => "COMMAND_TAKES_NO_INPUT", + 163208757351u64 => "CONFLICTING_ENGINE_ID", + 163208757367u64 => "CTRL_COMMAND_NOT_IMPLEMENTED", + 163208757352u64 => "DSO_FAILURE", + 163208757380u64 => "DSO_NOT_FOUND", + 163208757396u64 => "ENGINES_SECTION_ERROR", + 163208757350u64 => "ENGINE_CONFIGURATION_ERROR", + 163208757353u64 => "ENGINE_IS_NOT_IN_LIST", + 163208757397u64 => "ENGINE_SECTION_ERROR", + 163208757376u64 => "FAILED_LOADING_PRIVATE_KEY", + 163208757377u64 => "FAILED_LOADING_PUBLIC_KEY", + 163208757354u64 => "FINISH_FAILED", + 163208757356u64 => "ID_OR_NAME_MISSING", + 163208757357u64 => "INIT_FAILED", + 163208757358u64 => "INTERNAL_LIST_ERROR", + 163208757391u64 => "INVALID_ARGUMENT", + 163208757385u64 => "INVALID_CMD_NAME", + 163208757386u64 => "INVALID_CMD_NUMBER", + 163208757399u64 => "INVALID_INIT_VALUE", + 163208757398u64 => "INVALID_STRING", + 163208757365u64 => "NOT_INITIALISED", + 163208757360u64 => "NOT_LOADED", + 163208757368u64 => "NO_CONTROL_FUNCTION", + 163208757392u64 => "NO_INDEX", + 163208757373u64 => "NO_LOAD_FUNCTION", + 163208757378u64 => "NO_REFERENCE", + 163208757364u64 => "NO_SUCH_ENGINE", + 163208757394u64 => "UNIMPLEMENTED_CIPHER", + 163208757395u64 => "UNIMPLEMENTED_DIGEST", + 163208757349u64 => "UNIMPLEMENTED_PUBLIC_KEY_METHOD", + 163208757393u64 => "VERSION_INCOMPATIBILITY", + 25769803919u64 => "AES_KEY_SETUP_FAILED", + 25769803952u64 => "ARIA_KEY_SETUP_FAILED", + 25769803876u64 => "BAD_DECRYPT", + 25769803971u64 => "BAD_KEY_LENGTH", + 25769803931u64 => "BUFFER_TOO_SMALL", + 25769803933u64 => "CAMELLIA_KEY_SETUP_FAILED", + 25769803898u64 => "CIPHER_PARAMETER_ERROR", + 25769803923u64 => "COMMAND_NOT_SUPPORTED", + 25769803949u64 => "COPY_ERROR", + 25769803908u64 => "CTRL_NOT_IMPLEMENTED", + 25769803909u64 => "CTRL_OPERATION_NOT_IMPLEMENTED", + 25769803914u64 => "DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH", + 25769803890u64 => "DECODE_ERROR", + 25769803877u64 => "DIFFERENT_KEY_TYPES", + 25769803929u64 => "DIFFERENT_PARAMETERS", + 25769803941u64 => "ERROR_LOADING_SECTION", + 25769803942u64 => "ERROR_SETTING_FIPS_MODE", + 25769803950u64 => "EXPECTING_AN_HMAC_KEY", + 25769803903u64 => "EXPECTING_AN_RSA_KEY", + 25769803904u64 => "EXPECTING_A_DH_KEY", + 25769803905u64 => "EXPECTING_A_DSA_KEY", + 25769803918u64 => "EXPECTING_A_EC_KEY", + 25769803940u64 => "EXPECTING_A_POLY1305_KEY", + 25769803951u64 => "EXPECTING_A_SIPHASH_KEY", + 25769803943u64 => "FIPS_MODE_NOT_SUPPORTED", + 25769803958u64 => "GET_RAW_KEY_FAILED", + 25769803947u64 => "ILLEGAL_SCRYPT_PARAMETERS", + 25769803910u64 => "INITIALIZATION_ERROR", + 25769803887u64 => "INPUT_NOT_INITIALIZED", + 25769803928u64 => "INVALID_DIGEST", + 25769803944u64 => "INVALID_FIPS_MODE", + 25769803970u64 => "INVALID_IV_LENGTH", + 25769803939u64 => "INVALID_KEY", + 25769803906u64 => "INVALID_KEY_LENGTH", + 25769803924u64 => "INVALID_OPERATION", + 25769803896u64 => "KEYGEN_FAILURE", + 25769803956u64 => "KEY_SETUP_FAILED", + 25769803948u64 => "MEMORY_LIMIT_EXCEEDED", + 25769803935u64 => "MESSAGE_DIGEST_IS_NULL", + 25769803920u64 => "METHOD_NOT_SUPPORTED", + 25769803879u64 => "MISSING_PARAMETERS", + 25769803954u64 => "NOT_XOF_OR_INVALID_LENGTH", + 25769803907u64 => "NO_CIPHER_SET", + 25769803934u64 => "NO_DEFAULT_DIGEST", + 25769803915u64 => "NO_DIGEST_SET", + 25769803930u64 => "NO_KEY_SET", + 25769803925u64 => "NO_OPERATION_SET", + 25769803953u64 => "ONLY_ONESHOT_SUPPORTED", + 25769803926u64 => "OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE", + 25769803927u64 => "OPERATON_NOT_INITIALIZED", + 25769803960u64 => "OUTPUT_WOULD_OVERFLOW", + 25769803938u64 => "PARTIALLY_OVERLAPPING", + 25769803957u64 => "PBKDF2_ERROR", + 25769803955u64 => "PKEY_APPLICATION_ASN1_METHOD_ALREADY_REGISTERED", + 25769803921u64 => "PRIVATE_KEY_DECODE_ERROR", + 25769803922u64 => "PRIVATE_KEY_ENCODE_ERROR", + 25769803882u64 => "PUBLIC_KEY_NOT_RSA", + 25769803936u64 => "UNKNOWN_CIPHER", + 25769803937u64 => "UNKNOWN_DIGEST", + 25769803945u64 => "UNKNOWN_OPTION", + 25769803897u64 => "UNKNOWN_PBE_ALGORITHM", + 25769803932u64 => "UNSUPPORTED_ALGORITHM", + 25769803883u64 => "UNSUPPORTED_CIPHER", + 25769803899u64 => "UNSUPPORTED_KEYLENGTH", + 25769803900u64 => "UNSUPPORTED_KEY_DERIVATION_FUNCTION", + 25769803884u64 => "UNSUPPORTED_KEY_SIZE", + 25769803911u64 => "UNSUPPORTED_NUMBER_OF_ROUNDS", + 25769803901u64 => "UNSUPPORTED_PRF", + 25769803894u64 => "UNSUPPORTED_PRIVATE_KEY_ALGORITHM", + 25769803902u64 => "UNSUPPORTED_SALT_TYPE", + 25769803946u64 => "WRAP_MODE_NOT_ALLOWED", + 25769803885u64 => "WRONG_FINAL_BLOCK_LENGTH", + 25769803959u64 => "XTS_DUPLICATED_KEYS", + 223338299492u64 => "INVALID_DIGEST", + 223338299501u64 => "MISSING_ITERATION_COUNT", + 223338299496u64 => "MISSING_KEY", + 223338299497u64 => "MISSING_MESSAGE_DIGEST", + 223338299493u64 => "MISSING_PARAMETER", + 223338299502u64 => "MISSING_PASS", + 223338299503u64 => "MISSING_SALT", + 223338299499u64 => "MISSING_SECRET", + 223338299498u64 => "MISSING_SEED", + 223338299495u64 => "UNKNOWN_PARAMETER_TYPE", + 223338299500u64 => "VALUE_ERROR", + 223338299494u64 => "VALUE_MISSING", + 34359738470u64 => "OID_EXISTS", + 34359738469u64 => "UNKNOWN_NID", + 167503724645u64 => "CERTIFICATE_VERIFY_ERROR", + 167503724646u64 => "DIGEST_ERR", + 167503724666u64 => "ERROR_IN_NEXTUPDATE_FIELD", + 167503724667u64 => "ERROR_IN_THISUPDATE_FIELD", + 167503724665u64 => "ERROR_PARSING_URL", + 167503724647u64 => "MISSING_OCSPSIGNING_USAGE", + 167503724668u64 => "NEXTUPDATE_BEFORE_THISUPDATE", + 167503724648u64 => "NOT_BASIC_RESPONSE", + 167503724649u64 => "NO_CERTIFICATES_IN_CHAIN", + 167503724652u64 => "NO_RESPONSE_DATA", + 167503724653u64 => "NO_REVOKED_TIME", + 167503724674u64 => "NO_SIGNER_KEY", + 167503724654u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 167503724672u64 => "REQUEST_NOT_SIGNED", + 167503724655u64 => "RESPONSE_CONTAINS_NO_REVOCATION_DATA", + 167503724656u64 => "ROOT_CA_NOT_TRUSTED", + 167503724658u64 => "SERVER_RESPONSE_ERROR", + 167503724659u64 => "SERVER_RESPONSE_PARSE_ERROR", + 167503724661u64 => "SIGNATURE_FAILURE", + 167503724662u64 => "SIGNER_CERTIFICATE_NOT_FOUND", + 167503724669u64 => "STATUS_EXPIRED", + 167503724670u64 => "STATUS_NOT_YET_VALID", + 167503724671u64 => "STATUS_TOO_OLD", + 167503724663u64 => "UNKNOWN_MESSAGE_DIGEST", + 167503724664u64 => "UNKNOWN_NID", + 167503724673u64 => "UNSUPPORTED_REQUESTORNAME_TYPE", + 188978561131u64 => "AMBIGUOUS_CONTENT_TYPE", + 188978561139u64 => "BAD_PASSWORD_READ", + 188978561137u64 => "ERROR_VERIFYING_PKCS12_MAC", + 188978561145u64 => "FINGERPRINT_SIZE_DOES_NOT_MATCH_DIGEST", + 188978561130u64 => "INVALID_SCHEME", + 188978561136u64 => "IS_NOT_A", + 188978561140u64 => "LOADER_INCOMPLETE", + 188978561141u64 => "LOADING_STARTED", + 188978561124u64 => "NOT_A_CERTIFICATE", + 188978561125u64 => "NOT_A_CRL", + 188978561126u64 => "NOT_A_KEY", + 188978561127u64 => "NOT_A_NAME", + 188978561128u64 => "NOT_PARAMETERS", + 188978561138u64 => "PASSPHRASE_CALLBACK_ERROR", + 188978561132u64 => "PATH_MUST_BE_ABSOLUTE", + 188978561143u64 => "SEARCH_ONLY_SUPPORTED_FOR_DIRECTORIES", + 188978561133u64 => "UI_PROCESS_INTERRUPTED_OR_CANCELLED", + 188978561129u64 => "UNREGISTERED_SCHEME", + 188978561134u64 => "UNSUPPORTED_CONTENT_TYPE", + 188978561142u64 => "UNSUPPORTED_OPERATION", + 188978561144u64 => "UNSUPPORTED_SEARCH_TYPE", + 188978561135u64 => "URI_AUTHORITY_UNSUPPORTED", + 38654705764u64 => "BAD_BASE64_DECODE", + 38654705765u64 => "BAD_DECRYPT", + 38654705766u64 => "BAD_END_LINE", + 38654705767u64 => "BAD_IV_CHARS", + 38654705780u64 => "BAD_MAGIC_NUMBER", + 38654705768u64 => "BAD_PASSWORD_READ", + 38654705781u64 => "BAD_VERSION_NUMBER", + 38654705782u64 => "BIO_WRITE_FAILURE", + 38654705791u64 => "CIPHER_IS_NULL", + 38654705779u64 => "ERROR_CONVERTING_PRIVATE_KEY", + 38654705783u64 => "EXPECTING_PRIVATE_KEY_BLOB", + 38654705784u64 => "EXPECTING_PUBLIC_KEY_BLOB", + 38654705792u64 => "HEADER_TOO_LONG", + 38654705785u64 => "INCONSISTENT_HEADER", + 38654705786u64 => "KEYBLOB_HEADER_PARSE_ERROR", + 38654705787u64 => "KEYBLOB_TOO_SHORT", + 38654705793u64 => "MISSING_DEK_IV", + 38654705769u64 => "NOT_DEK_INFO", + 38654705770u64 => "NOT_ENCRYPTED", + 38654705771u64 => "NOT_PROC_TYPE", + 38654705772u64 => "NO_START_LINE", + 38654705773u64 => "PROBLEMS_GETTING_PASSWORD", + 38654705788u64 => "PVK_DATA_TOO_SHORT", + 38654705789u64 => "PVK_TOO_SHORT", + 38654705775u64 => "READ_KEY", + 38654705776u64 => "SHORT_HEADER", + 38654705794u64 => "UNEXPECTED_DEK_IV", + 38654705777u64 => "UNSUPPORTED_CIPHER", + 38654705778u64 => "UNSUPPORTED_ENCRYPTION", + 38654705790u64 => "UNSUPPORTED_KEY_COMPONENTS", + 38654705774u64 => "UNSUPPORTED_PUBLIC_KEY_TYPE", + 150323855460u64 => "CANT_PACK_STRUCTURE", + 150323855481u64 => "CONTENT_TYPE_NOT_DATA", + 150323855461u64 => "DECODE_ERROR", + 150323855462u64 => "ENCODE_ERROR", + 150323855463u64 => "ENCRYPT_ERROR", + 150323855480u64 => "ERROR_SETTING_ENCRYPTED_DATA_TYPE", + 150323855464u64 => "INVALID_NULL_ARGUMENT", + 150323855465u64 => "INVALID_NULL_PKCS12_POINTER", + 150323855466u64 => "IV_GEN_ERROR", + 150323855467u64 => "KEY_GEN_ERROR", + 150323855468u64 => "MAC_ABSENT", + 150323855469u64 => "MAC_GENERATION_ERROR", + 150323855470u64 => "MAC_SETUP_ERROR", + 150323855471u64 => "MAC_STRING_SET_ERROR", + 150323855473u64 => "MAC_VERIFY_FAILURE", + 150323855474u64 => "PARSE_ERROR", + 150323855475u64 => "PKCS12_ALGOR_CIPHERINIT_ERROR", + 150323855476u64 => "PKCS12_CIPHERFINAL_ERROR", + 150323855477u64 => "PKCS12_PBE_CRYPT_ERROR", + 150323855478u64 => "UNKNOWN_DIGEST_ALGORITHM", + 150323855479u64 => "UNSUPPORTED_PKCS12_MODE", + 141733920885u64 => "CERTIFICATE_VERIFY_ERROR", + 141733920912u64 => "CIPHER_HAS_NO_OBJECT_IDENTIFIER", + 141733920884u64 => "CIPHER_NOT_INITIALIZED", + 141733920886u64 => "CONTENT_AND_DATA_PRESENT", + 141733920920u64 => "CTRL_ERROR", + 141733920887u64 => "DECRYPT_ERROR", + 141733920869u64 => "DIGEST_FAILURE", + 141733920917u64 => "ENCRYPTION_CTRL_FAILURE", + 141733920918u64 => "ENCRYPTION_NOT_SUPPORTED_FOR_THIS_KEY_TYPE", + 141733920888u64 => "ERROR_ADDING_RECIPIENT", + 141733920889u64 => "ERROR_SETTING_CIPHER", + 141733920911u64 => "INVALID_NULL_POINTER", + 141733920923u64 => "INVALID_SIGNED_DATA_TYPE", + 141733920890u64 => "NO_CONTENT", + 141733920919u64 => "NO_DEFAULT_DIGEST", + 141733920922u64 => "NO_MATCHING_DIGEST_TYPE_FOUND", + 141733920883u64 => "NO_RECIPIENT_MATCHES_CERTIFICATE", + 141733920891u64 => "NO_SIGNATURES_ON_DATA", + 141733920910u64 => "NO_SIGNERS", + 141733920872u64 => "OPERATION_NOT_SUPPORTED_ON_THIS_TYPE", + 141733920892u64 => "PKCS7_ADD_SIGNATURE_ERROR", + 141733920921u64 => "PKCS7_ADD_SIGNER_ERROR", + 141733920913u64 => "PKCS7_DATASIGN", + 141733920895u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 141733920873u64 => "SIGNATURE_FAILURE", + 141733920896u64 => "SIGNER_CERTIFICATE_NOT_FOUND", + 141733920915u64 => "SIGNING_CTRL_FAILURE", + 141733920916u64 => "SIGNING_NOT_SUPPORTED_FOR_THIS_KEY_TYPE", + 141733920897u64 => "SMIME_TEXT_ERROR", + 141733920874u64 => "UNABLE_TO_FIND_CERTIFICATE", + 141733920875u64 => "UNABLE_TO_FIND_MEM_BIO", + 141733920876u64 => "UNABLE_TO_FIND_MESSAGE_DIGEST", + 141733920877u64 => "UNKNOWN_DIGEST_TYPE", + 141733920878u64 => "UNKNOWN_OPERATION", + 141733920879u64 => "UNSUPPORTED_CIPHER_TYPE", + 141733920880u64 => "UNSUPPORTED_CONTENT_TYPE", + 141733920881u64 => "WRONG_CONTENT_TYPE", + 141733920882u64 => "WRONG_PKCS7_TYPE", + 154618822758u64 => "ADDITIONAL_INPUT_TOO_LONG", + 154618822759u64 => "ALREADY_INSTANTIATED", + 154618822761u64 => "ARGUMENT_OUT_OF_RANGE", + 154618822777u64 => "CANNOT_OPEN_FILE", + 154618822785u64 => "DRBG_ALREADY_INITIALIZED", + 154618822760u64 => "DRBG_NOT_INITIALISED", + 154618822762u64 => "ENTROPY_INPUT_TOO_LONG", + 154618822780u64 => "ENTROPY_OUT_OF_RANGE", + 154618822783u64 => "ERROR_ENTROPY_POOL_WAS_IGNORED", + 154618822763u64 => "ERROR_INITIALISING_DRBG", + 154618822764u64 => "ERROR_INSTANTIATING_DRBG", + 154618822765u64 => "ERROR_RETRIEVING_ADDITIONAL_INPUT", + 154618822766u64 => "ERROR_RETRIEVING_ENTROPY", + 154618822767u64 => "ERROR_RETRIEVING_NONCE", + 154618822782u64 => "FAILED_TO_CREATE_LOCK", + 154618822757u64 => "FUNC_NOT_IMPLEMENTED", + 154618822779u64 => "FWRITE_ERROR", + 154618822768u64 => "GENERATE_ERROR", + 154618822769u64 => "INTERNAL_ERROR", + 154618822770u64 => "IN_ERROR_STATE", + 154618822778u64 => "NOT_A_REGULAR_FILE", + 154618822771u64 => "NOT_INSTANTIATED", + 154618822784u64 => "NO_DRBG_IMPLEMENTATION_SELECTED", + 154618822786u64 => "PARENT_LOCKING_NOT_ENABLED", + 154618822787u64 => "PARENT_STRENGTH_TOO_WEAK", + 154618822772u64 => "PERSONALISATION_STRING_TOO_LONG", + 154618822789u64 => "PREDICTION_RESISTANCE_NOT_SUPPORTED", + 154618822756u64 => "PRNG_NOT_SEEDED", + 154618822781u64 => "RANDOM_POOL_OVERFLOW", + 154618822790u64 => "RANDOM_POOL_UNDERFLOW", + 154618822773u64 => "REQUEST_TOO_LARGE_FOR_DRBG", + 154618822774u64 => "RESEED_ERROR", + 154618822775u64 => "SELFTEST_FAILURE", + 154618822791u64 => "TOO_LITTLE_NONCE_REQUESTED", + 154618822792u64 => "TOO_MUCH_NONCE_REQUESTED", + 154618822788u64 => "UNSUPPORTED_DRBG_FLAGS", + 154618822776u64 => "UNSUPPORTED_DRBG_TYPE", + 17179869284u64 => "ALGORITHM_MISMATCH", + 17179869285u64 => "BAD_E_VALUE", + 17179869286u64 => "BAD_FIXED_HEADER_DECRYPT", + 17179869287u64 => "BAD_PAD_BYTE_COUNT", + 17179869288u64 => "BAD_SIGNATURE", + 17179869290u64 => "BLOCK_TYPE_IS_NOT_01", + 17179869291u64 => "BLOCK_TYPE_IS_NOT_02", + 17179869292u64 => "DATA_GREATER_THAN_MOD_LEN", + 17179869293u64 => "DATA_TOO_LARGE", + 17179869294u64 => "DATA_TOO_LARGE_FOR_KEY_SIZE", + 17179869316u64 => "DATA_TOO_LARGE_FOR_MODULUS", + 17179869295u64 => "DATA_TOO_SMALL", + 17179869306u64 => "DATA_TOO_SMALL_FOR_KEY_SIZE", + 17179869342u64 => "DIGEST_DOES_NOT_MATCH", + 17179869329u64 => "DIGEST_NOT_ALLOWED", + 17179869296u64 => "DIGEST_TOO_BIG_FOR_RSA_KEY", + 17179869308u64 => "DMP1_NOT_CONGRUENT_TO_D", + 17179869309u64 => "DMQ1_NOT_CONGRUENT_TO_D", + 17179869307u64 => "D_E_NOT_CONGRUENT_TO_1", + 17179869317u64 => "FIRST_OCTET_INVALID", + 17179869328u64 => "ILLEGAL_OR_UNSUPPORTED_PADDING_MODE", + 17179869341u64 => "INVALID_DIGEST", + 17179869327u64 => "INVALID_DIGEST_LENGTH", + 17179869321u64 => "INVALID_HEADER", + 17179869344u64 => "INVALID_LABEL", + 17179869315u64 => "INVALID_MESSAGE_LENGTH", + 17179869340u64 => "INVALID_MGF1_MD", + 17179869351u64 => "INVALID_MULTI_PRIME_KEY", + 17179869345u64 => "INVALID_OAEP_PARAMETERS", + 17179869322u64 => "INVALID_PADDING", + 17179869325u64 => "INVALID_PADDING_MODE", + 17179869333u64 => "INVALID_PSS_PARAMETERS", + 17179869330u64 => "INVALID_PSS_SALTLEN", + 17179869334u64 => "INVALID_SALT_LENGTH", + 17179869323u64 => "INVALID_TRAILER", + 17179869326u64 => "INVALID_X931_DIGEST", + 17179869310u64 => "IQMP_NOT_INVERSE_OF_Q", + 17179869349u64 => "KEY_PRIME_NUM_INVALID", + 17179869304u64 => "KEY_SIZE_TOO_SMALL", + 17179869318u64 => "LAST_OCTET_INVALID", + 17179869336u64 => "MGF1_DIGEST_NOT_ALLOWED", + 17179869363u64 => "MISSING_PRIVATE_KEY", + 17179869289u64 => "MODULUS_TOO_LARGE", + 17179869352u64 => "MP_COEFFICIENT_NOT_INVERSE_OF_R", + 17179869353u64 => "MP_EXPONENT_NOT_CONGRUENT_TO_D", + 17179869354u64 => "MP_R_NOT_PRIME", + 17179869324u64 => "NO_PUBLIC_EXPONENT", + 17179869297u64 => "NULL_BEFORE_BLOCK_MISSING", + 17179869356u64 => "N_DOES_NOT_EQUAL_PRODUCT_OF_PRIMES", + 17179869311u64 => "N_DOES_NOT_EQUAL_P_Q", + 17179869305u64 => "OAEP_DECODING_ERROR", + 17179869332u64 => "OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE", + 17179869298u64 => "PADDING_CHECK_FAILED", + 17179869343u64 => "PKCS_DECODING_ERROR", + 17179869348u64 => "PSS_SALTLEN_TOO_SMALL", + 17179869312u64 => "P_NOT_PRIME", + 17179869313u64 => "Q_NOT_PRIME", + 17179869314u64 => "RSA_OPERATIONS_NOT_SUPPORTED", + 17179869320u64 => "SLEN_CHECK_FAILED", + 17179869319u64 => "SLEN_RECOVERY_FAILED", + 17179869299u64 => "SSLV3_ROLLBACK_ATTACK", + 17179869300u64 => "THE_ASN1_OBJECT_IDENTIFIER_IS_NOT_KNOWN_FOR_THIS_MD", + 17179869301u64 => "UNKNOWN_ALGORITHM_TYPE", + 17179869350u64 => "UNKNOWN_DIGEST", + 17179869335u64 => "UNKNOWN_MASK_DIGEST", + 17179869302u64 => "UNKNOWN_PADDING_TYPE", + 17179869346u64 => "UNSUPPORTED_ENCRYPTION_TYPE", + 17179869347u64 => "UNSUPPORTED_LABEL_SOURCE", + 17179869337u64 => "UNSUPPORTED_MASK_ALGORITHM", + 17179869338u64 => "UNSUPPORTED_MASK_PARAMETER", + 17179869339u64 => "UNSUPPORTED_SIGNATURE_TYPE", + 17179869331u64 => "VALUE_MISSING", + 17179869303u64 => "WRONG_SIGNATURE_LENGTH", + 227633266788u64 => "ASN1_ERROR", + 227633266789u64 => "BAD_SIGNATURE", + 227633266795u64 => "BUFFER_TOO_SMALL", + 227633266798u64 => "DIST_ID_TOO_LARGE", + 227633266800u64 => "ID_NOT_SET", + 227633266799u64 => "ID_TOO_LARGE", + 227633266796u64 => "INVALID_CURVE", + 227633266790u64 => "INVALID_DIGEST", + 227633266791u64 => "INVALID_DIGEST_TYPE", + 227633266792u64 => "INVALID_ENCODING", + 227633266793u64 => "INVALID_FIELD", + 227633266797u64 => "NO_PARAMETERS_SET", + 227633266794u64 => "USER_ID_TOO_LARGE", + 85899346211u64 => "APPLICATION_DATA_AFTER_CLOSE_NOTIFY", + 85899346020u64 => "APP_DATA_IN_HANDSHAKE", + 85899346192u64 => "ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT", + 85899346063u64 => "AT_LEAST_TLS_1_0_NEEDED_IN_FIPS_MODE", + 85899346078u64 => "AT_LEAST_TLS_1_2_NEEDED_IN_SUITEB_MODE", + 85899346023u64 => "BAD_CHANGE_CIPHER_SPEC", + 85899346106u64 => "BAD_CIPHER", + 85899346310u64 => "BAD_DATA", + 85899346026u64 => "BAD_DATA_RETURNED_BY_CALLBACK", + 85899346027u64 => "BAD_DECOMPRESSION", + 85899346022u64 => "BAD_DH_VALUE", + 85899346031u64 => "BAD_DIGEST_LENGTH", + 85899346153u64 => "BAD_EARLY_DATA", + 85899346224u64 => "BAD_ECC_CERT", + 85899346226u64 => "BAD_ECPOINT", + 85899346030u64 => "BAD_EXTENSION", + 85899346252u64 => "BAD_HANDSHAKE_LENGTH", + 85899346156u64 => "BAD_HANDSHAKE_STATE", + 85899346025u64 => "BAD_HELLO_REQUEST", + 85899346183u64 => "BAD_HRR_VERSION", + 85899346028u64 => "BAD_KEY_SHARE", + 85899346042u64 => "BAD_KEY_UPDATE", + 85899346212u64 => "BAD_LEGACY_VERSION", + 85899346191u64 => "BAD_LENGTH", + 85899346160u64 => "BAD_PACKET", + 85899346035u64 => "BAD_PACKET_LENGTH", + 85899346036u64 => "BAD_PROTOCOL_VERSION_NUMBER", + 85899346139u64 => "BAD_PSK", + 85899346034u64 => "BAD_PSK_IDENTITY", + 85899346363u64 => "BAD_RECORD_TYPE", + 85899346039u64 => "BAD_RSA_ENCRYPT", + 85899346043u64 => "BAD_SIGNATURE", + 85899346267u64 => "BAD_SRP_A_LENGTH", + 85899346291u64 => "BAD_SRP_PARAMETERS", + 85899346272u64 => "BAD_SRTP_MKI_VALUE", + 85899346273u64 => "BAD_SRTP_PROTECTION_PROFILE_LIST", + 85899346044u64 => "BAD_SSL_FILETYPE", + 85899346304u64 => "BAD_VALUE", + 85899346047u64 => "BAD_WRITE_RETRY", + 85899346173u64 => "BINDER_DOES_NOT_VERIFY", + 85899346048u64 => "BIO_NOT_SET", + 85899346049u64 => "BLOCK_CIPHER_PAD_IS_WRONG", + 85899346050u64 => "BN_LIB", + 85899346154u64 => "CALLBACK_FAILED", + 85899346029u64 => "CANNOT_CHANGE_CIPHER", + 85899346051u64 => "CA_DN_LENGTH_MISMATCH", + 85899346317u64 => "CA_KEY_TOO_SMALL", + 85899346318u64 => "CA_MD_TOO_WEAK", + 85899346053u64 => "CCS_RECEIVED_EARLY", + 85899346054u64 => "CERTIFICATE_VERIFY_FAILED", + 85899346297u64 => "CERT_CB_ERROR", + 85899346055u64 => "CERT_LENGTH_MISMATCH", + 85899346138u64 => "CIPHERSUITE_DIGEST_HAS_CHANGED", + 85899346057u64 => "CIPHER_CODE_WRONG_LENGTH", + 85899346058u64 => "CIPHER_OR_HASH_UNAVAILABLE", + 85899346146u64 => "CLIENTHELLO_TLSEXT", + 85899346060u64 => "COMPRESSED_LENGTH_TOO_LONG", + 85899346263u64 => "COMPRESSION_DISABLED", + 85899346061u64 => "COMPRESSION_FAILURE", + 85899346227u64 => "COMPRESSION_ID_NOT_WITHIN_PRIVATE_RANGE", + 85899346062u64 => "COMPRESSION_LIBRARY_ERROR", + 85899346064u64 => "CONNECTION_TYPE_NOT_SET", + 85899346087u64 => "CONTEXT_NOT_DANE_ENABLED", + 85899346320u64 => "COOKIE_GEN_CALLBACK_FAILURE", + 85899346228u64 => "COOKIE_MISMATCH", + 85899346126u64 => "CUSTOM_EXT_HANDLER_ALREADY_INSTALLED", + 85899346092u64 => "DANE_ALREADY_ENABLED", + 85899346093u64 => "DANE_CANNOT_OVERRIDE_MTYPE_FULL", + 85899346095u64 => "DANE_NOT_ENABLED", + 85899346100u64 => "DANE_TLSA_BAD_CERTIFICATE", + 85899346104u64 => "DANE_TLSA_BAD_CERTIFICATE_USAGE", + 85899346109u64 => "DANE_TLSA_BAD_DATA_LENGTH", + 85899346112u64 => "DANE_TLSA_BAD_DIGEST_LENGTH", + 85899346120u64 => "DANE_TLSA_BAD_MATCHING_TYPE", + 85899346121u64 => "DANE_TLSA_BAD_PUBLIC_KEY", + 85899346122u64 => "DANE_TLSA_BAD_SELECTOR", + 85899346123u64 => "DANE_TLSA_NULL_DATA", + 85899346065u64 => "DATA_BETWEEN_CCS_AND_FINISHED", + 85899346066u64 => "DATA_LENGTH_TOO_LONG", + 85899346067u64 => "DECRYPTION_FAILED", + 85899346201u64 => "DECRYPTION_FAILED_OR_BAD_RECORD_MAC", + 85899346314u64 => "DH_KEY_TOO_SMALL", + 85899346068u64 => "DH_PUBLIC_VALUE_LENGTH_IS_WRONG", + 85899346069u64 => "DIGEST_CHECK_FAILED", + 85899346254u64 => "DTLS_MESSAGE_TOO_BIG", + 85899346229u64 => "DUPLICATE_COMPRESSION_ID", + 85899346238u64 => "ECC_CERT_NOT_FOR_SIGNING", + 85899346294u64 => "ECDH_REQUIRED_FOR_SUITEB_MODE", + 85899346319u64 => "EE_KEY_TOO_SMALL", + 85899346274u64 => "EMPTY_SRTP_PROTECTION_PROFILE_LIST", + 85899346070u64 => "ENCRYPTED_LENGTH_TOO_LONG", + 85899346071u64 => "ERROR_IN_RECEIVED_CIPHER_LIST", + 85899346124u64 => "ERROR_SETTING_TLSA_BASE_DOMAIN", + 85899346114u64 => "EXCEEDS_MAX_FRAGMENT_SIZE", + 85899346072u64 => "EXCESSIVE_MESSAGE_SIZE", + 85899346199u64 => "EXTENSION_NOT_RECEIVED", + 85899346073u64 => "EXTRA_DATA_IN_MESSAGE", + 85899346083u64 => "EXT_LENGTH_MISMATCH", + 85899346325u64 => "FAILED_TO_INIT_ASYNC", + 85899346321u64 => "FRAGMENTED_CLIENT_HELLO", + 85899346074u64 => "GOT_A_FIN_BEFORE_A_CCS", + 85899346075u64 => "HTTPS_PROXY_REQUEST", + 85899346076u64 => "HTTP_REQUEST", + 85899346082u64 => "ILLEGAL_POINT_COMPRESSION", + 85899346300u64 => "ILLEGAL_SUITEB_DIGEST", + 85899346293u64 => "INAPPROPRIATE_FALLBACK", + 85899346260u64 => "INCONSISTENT_COMPRESSION", + 85899346142u64 => "INCONSISTENT_EARLY_DATA_ALPN", + 85899346151u64 => "INCONSISTENT_EARLY_DATA_SNI", + 85899346024u64 => "INCONSISTENT_EXTMS", + 85899346161u64 => "INSUFFICIENT_SECURITY", + 85899346125u64 => "INVALID_ALERT", + 85899346180u64 => "INVALID_CCS_MESSAGE", + 85899346158u64 => "INVALID_CERTIFICATE_OR_ALG", + 85899346200u64 => "INVALID_COMMAND", + 85899346261u64 => "INVALID_COMPRESSION_ALGORITHM", + 85899346203u64 => "INVALID_CONFIG", + 85899346033u64 => "INVALID_CONFIGURATION_NAME", + 85899346202u64 => "INVALID_CONTEXT", + 85899346132u64 => "INVALID_CT_VALIDATION_TYPE", + 85899346040u64 => "INVALID_KEY_UPDATE_TYPE", + 85899346094u64 => "INVALID_MAX_EARLY_DATA", + 85899346305u64 => "INVALID_NULL_CMD_NAME", + 85899346322u64 => "INVALID_SEQUENCE_NUMBER", + 85899346308u64 => "INVALID_SERVERINFO_DATA", + 85899346919u64 => "INVALID_SESSION_ID", + 85899346277u64 => "INVALID_SRP_USERNAME", + 85899346248u64 => "INVALID_STATUS_RESPONSE", + 85899346245u64 => "INVALID_TICKET_KEYS_LENGTH", + 85899346079u64 => "LENGTH_MISMATCH", + 85899346324u64 => "LENGTH_TOO_LONG", + 85899346080u64 => "LENGTH_TOO_SHORT", + 85899346194u64 => "LIBRARY_BUG", + 85899346081u64 => "LIBRARY_HAS_NO_CIPHERS", + 85899346085u64 => "MISSING_DSA_SIGNING_CERT", + 85899346301u64 => "MISSING_ECDSA_SIGNING_CERT", + 85899346176u64 => "MISSING_FATAL", + 85899346210u64 => "MISSING_PARAMETERS", + 85899346230u64 => "MISSING_PSK_KEX_MODES_EXTENSION", + 85899346088u64 => "MISSING_RSA_CERTIFICATE", + 85899346089u64 => "MISSING_RSA_ENCRYPTING_CERT", + 85899346090u64 => "MISSING_RSA_SIGNING_CERT", + 85899346032u64 => "MISSING_SIGALGS_EXTENSION", + 85899346141u64 => "MISSING_SIGNING_CERT", + 85899346278u64 => "MISSING_SRP_PARAM", + 85899346129u64 => "MISSING_SUPPORTED_GROUPS_EXTENSION", + 85899346091u64 => "MISSING_TMP_DH_KEY", + 85899346231u64 => "MISSING_TMP_ECDH_KEY", + 85899346213u64 => "MIXED_HANDSHAKE_AND_NON_HANDSHAKE_DATA", + 85899346102u64 => "NOT_ON_RECORD_BOUNDARY", + 85899346209u64 => "NOT_REPLACING_CERTIFICATE", + 85899346204u64 => "NOT_SERVER", + 85899346155u64 => "NO_APPLICATION_PROTOCOL", + 85899346096u64 => "NO_CERTIFICATES_RETURNED", + 85899346097u64 => "NO_CERTIFICATE_ASSIGNED", + 85899346099u64 => "NO_CERTIFICATE_SET", + 85899346134u64 => "NO_CHANGE_FOLLOWING_HRR", + 85899346101u64 => "NO_CIPHERS_AVAILABLE", + 85899346103u64 => "NO_CIPHERS_SPECIFIED", + 85899346105u64 => "NO_CIPHER_MATCH", + 85899346251u64 => "NO_CLIENT_CERT_METHOD", + 85899346107u64 => "NO_COMPRESSION_SPECIFIED", + 85899346207u64 => "NO_COOKIE_CALLBACK_SET", + 85899346250u64 => "NO_GOST_CERTIFICATE_SENT_BY_PEER", + 85899346108u64 => "NO_METHOD_SPECIFIED", + 85899346309u64 => "NO_PEM_EXTENSIONS", + 85899346110u64 => "NO_PRIVATE_KEY_ASSIGNED", + 85899346111u64 => "NO_PROTOCOLS_AVAILABLE", + 85899346259u64 => "NO_RENEGOTIATION", + 85899346244u64 => "NO_REQUIRED_DIGEST", + 85899346113u64 => "NO_SHARED_CIPHER", + 85899346330u64 => "NO_SHARED_GROUPS", + 85899346296u64 => "NO_SHARED_SIGNATURE_ALGORITHMS", + 85899346279u64 => "NO_SRTP_PROFILES", + 85899346021u64 => "NO_SUITABLE_KEY_SHARE", + 85899346038u64 => "NO_SUITABLE_SIGNATURE_ALGORITHM", + 85899346136u64 => "NO_VALID_SCTS", + 85899346323u64 => "NO_VERIFY_COOKIE_CALLBACK", + 85899346115u64 => "NULL_SSL_CTX", + 85899346116u64 => "NULL_SSL_METHOD_PASSED", + 85899346214u64 => "OCSP_CALLBACK_FAILURE", + 85899346117u64 => "OLD_SESSION_CIPHER_NOT_RETURNED", + 85899346264u64 => "OLD_SESSION_COMPRESSION_ALGORITHM_NOT_RETURNED", + 85899346157u64 => "OVERFLOW_ERROR", + 85899346118u64 => "PACKET_LENGTH_TOO_LONG", + 85899346147u64 => "PARSE_TLSEXT", + 85899346190u64 => "PATH_TOO_LONG", + 85899346119u64 => "PEER_DID_NOT_RETURN_A_CERTIFICATE", + 85899346311u64 => "PEM_NAME_BAD_PREFIX", + 85899346312u64 => "PEM_NAME_TOO_SHORT", + 85899346326u64 => "PIPELINE_FAILURE", + 85899346198u64 => "POST_HANDSHAKE_AUTH_ENCODING_ERR", + 85899346208u64 => "PRIVATE_KEY_MISMATCH", + 85899346127u64 => "PROTOCOL_IS_SHUTDOWN", + 85899346143u64 => "PSK_IDENTITY_NOT_FOUND", + 85899346144u64 => "PSK_NO_CLIENT_CB", + 85899346145u64 => "PSK_NO_SERVER_CB", + 85899346131u64 => "READ_BIO_NOT_SET", + 85899346232u64 => "READ_TIMEOUT_EXPIRED", + 85899346133u64 => "RECORD_LENGTH_MISMATCH", + 85899346218u64 => "RECORD_TOO_SMALL", + 85899346255u64 => "RENEGOTIATE_EXT_TOO_LONG", + 85899346256u64 => "RENEGOTIATION_ENCODING_ERR", + 85899346257u64 => "RENEGOTIATION_MISMATCH", + 85899346205u64 => "REQUEST_PENDING", + 85899346206u64 => "REQUEST_SENT", + 85899346135u64 => "REQUIRED_CIPHER_MISSING", + 85899346262u64 => "REQUIRED_COMPRESSION_ALGORITHM_MISSING", + 85899346265u64 => "SCSV_RECEIVED_WHEN_RENEGOTIATING", + 85899346128u64 => "SCT_VERIFICATION_FAILED", + 85899346195u64 => "SERVERHELLO_TLSEXT", + 85899346197u64 => "SESSION_ID_CONTEXT_UNINITIALIZED", + 85899346327u64 => "SHUTDOWN_WHILE_IN_INIT", + 85899346280u64 => "SIGNATURE_ALGORITHMS_ERROR", + 85899346140u64 => "SIGNATURE_FOR_NON_SIGNING_CERTIFICATE", + 85899346281u64 => "SRP_A_CALC", + 85899346282u64 => "SRTP_COULD_NOT_ALLOCATE_PROFILES", + 85899346283u64 => "SRTP_PROTECTION_PROFILE_LIST_TOO_LONG", + 85899346284u64 => "SRTP_UNKNOWN_PROTECTION_PROFILE", + 85899346152u64 => "SSL3_EXT_INVALID_MAX_FRAGMENT_LENGTH", + 85899346239u64 => "SSL3_EXT_INVALID_SERVERNAME", + 85899346240u64 => "SSL3_EXT_INVALID_SERVERNAME_TYPE", + 85899346220u64 => "SSL3_SESSION_ID_TOO_LONG", + 85899346962u64 => "SSLV3_ALERT_BAD_CERTIFICATE", + 85899346940u64 => "SSLV3_ALERT_BAD_RECORD_MAC", + 85899346965u64 => "SSLV3_ALERT_CERTIFICATE_EXPIRED", + 85899346964u64 => "SSLV3_ALERT_CERTIFICATE_REVOKED", + 85899346966u64 => "SSLV3_ALERT_CERTIFICATE_UNKNOWN", + 85899346950u64 => "SSLV3_ALERT_DECOMPRESSION_FAILURE", + 85899346960u64 => "SSLV3_ALERT_HANDSHAKE_FAILURE", + 85899346967u64 => "SSLV3_ALERT_ILLEGAL_PARAMETER", + 85899346961u64 => "SSLV3_ALERT_NO_CERTIFICATE", + 85899346930u64 => "SSLV3_ALERT_UNEXPECTED_MESSAGE", + 85899346963u64 => "SSLV3_ALERT_UNSUPPORTED_CERTIFICATE", + 85899346037u64 => "SSL_COMMAND_SECTION_EMPTY", + 85899346045u64 => "SSL_COMMAND_SECTION_NOT_FOUND", + 85899346148u64 => "SSL_CTX_HAS_NO_DEFAULT_SSL_VERSION", + 85899346149u64 => "SSL_HANDSHAKE_FAILURE", + 85899346150u64 => "SSL_LIBRARY_HAS_NO_CIPHERS", + 85899346292u64 => "SSL_NEGATIVE_LENGTH", + 85899346046u64 => "SSL_SECTION_EMPTY", + 85899346056u64 => "SSL_SECTION_NOT_FOUND", + 85899346221u64 => "SSL_SESSION_ID_CALLBACK_FAILED", + 85899346222u64 => "SSL_SESSION_ID_CONFLICT", + 85899346193u64 => "SSL_SESSION_ID_CONTEXT_TOO_LONG", + 85899346223u64 => "SSL_SESSION_ID_HAS_BAD_LENGTH", + 85899346328u64 => "SSL_SESSION_ID_TOO_LONG", + 85899346130u64 => "SSL_SESSION_VERSION_MISMATCH", + 85899346041u64 => "STILL_IN_INIT", + 85899347036u64 => "TLSV13_ALERT_CERTIFICATE_REQUIRED", + 85899347029u64 => "TLSV13_ALERT_MISSING_EXTENSION", + 85899346969u64 => "TLSV1_ALERT_ACCESS_DENIED", + 85899346970u64 => "TLSV1_ALERT_DECODE_ERROR", + 85899346941u64 => "TLSV1_ALERT_DECRYPTION_FAILED", + 85899346971u64 => "TLSV1_ALERT_DECRYPT_ERROR", + 85899346980u64 => "TLSV1_ALERT_EXPORT_RESTRICTION", + 85899347006u64 => "TLSV1_ALERT_INAPPROPRIATE_FALLBACK", + 85899346991u64 => "TLSV1_ALERT_INSUFFICIENT_SECURITY", + 85899347000u64 => "TLSV1_ALERT_INTERNAL_ERROR", + 85899347020u64 => "TLSV1_ALERT_NO_RENEGOTIATION", + 85899346990u64 => "TLSV1_ALERT_PROTOCOL_VERSION", + 85899346942u64 => "TLSV1_ALERT_RECORD_OVERFLOW", + 85899346968u64 => "TLSV1_ALERT_UNKNOWN_CA", + 85899347010u64 => "TLSV1_ALERT_USER_CANCELLED", + 85899347034u64 => "TLSV1_BAD_CERTIFICATE_HASH_VALUE", + 85899347033u64 => "TLSV1_BAD_CERTIFICATE_STATUS_RESPONSE", + 85899347031u64 => "TLSV1_CERTIFICATE_UNOBTAINABLE", + 85899347032u64 => "TLSV1_UNRECOGNIZED_NAME", + 85899347030u64 => "TLSV1_UNSUPPORTED_EXTENSION", + 85899346285u64 => "TLS_HEARTBEAT_PEER_DOESNT_ACCEPT", + 85899346286u64 => "TLS_HEARTBEAT_PENDING", + 85899346287u64 => "TLS_ILLEGAL_EXPORTER_LABEL", + 85899346077u64 => "TLS_INVALID_ECPOINTFORMAT_LIST", + 85899346052u64 => "TOO_MANY_KEY_UPDATES", + 85899346329u64 => "TOO_MANY_WARN_ALERTS", + 85899346084u64 => "TOO_MUCH_EARLY_DATA", + 85899346234u64 => "UNABLE_TO_FIND_ECDH_PARAMETERS", + 85899346159u64 => "UNABLE_TO_FIND_PUBLIC_KEY_PARAMETERS", + 85899346162u64 => "UNABLE_TO_LOAD_SSL3_MD5_ROUTINES", + 85899346163u64 => "UNABLE_TO_LOAD_SSL3_SHA1_ROUTINES", + 85899346182u64 => "UNEXPECTED_CCS_MESSAGE", + 85899346098u64 => "UNEXPECTED_END_OF_EARLY_DATA", + 85899346164u64 => "UNEXPECTED_MESSAGE", + 85899346165u64 => "UNEXPECTED_RECORD", + 85899346196u64 => "UNINITIALIZED", + 85899346166u64 => "UNKNOWN_ALERT_TYPE", + 85899346167u64 => "UNKNOWN_CERTIFICATE_TYPE", + 85899346168u64 => "UNKNOWN_CIPHER_RETURNED", + 85899346169u64 => "UNKNOWN_CIPHER_TYPE", + 85899346306u64 => "UNKNOWN_CMD_NAME", + 85899346059u64 => "UNKNOWN_COMMAND", + 85899346288u64 => "UNKNOWN_DIGEST", + 85899346170u64 => "UNKNOWN_KEY_EXCHANGE_TYPE", + 85899346171u64 => "UNKNOWN_PKEY_TYPE", + 85899346172u64 => "UNKNOWN_PROTOCOL", + 85899346174u64 => "UNKNOWN_SSL_VERSION", + 85899346175u64 => "UNKNOWN_STATE", + 85899346258u64 => "UNSAFE_LEGACY_RENEGOTIATION_DISABLED", + 85899346137u64 => "UNSOLICITED_EXTENSION", + 85899346177u64 => "UNSUPPORTED_COMPRESSION_ALGORITHM", + 85899346235u64 => "UNSUPPORTED_ELLIPTIC_CURVE", + 85899346178u64 => "UNSUPPORTED_PROTOCOL", + 85899346179u64 => "UNSUPPORTED_SSL_VERSION", + 85899346249u64 => "UNSUPPORTED_STATUS_TYPE", + 85899346289u64 => "USE_SRTP_NOT_NEGOTIATED", + 85899346086u64 => "VERSION_TOO_HIGH", + 85899346316u64 => "VERSION_TOO_LOW", + 85899346303u64 => "WRONG_CERTIFICATE_TYPE", + 85899346181u64 => "WRONG_CIPHER_RETURNED", + 85899346298u64 => "WRONG_CURVE", + 85899346184u64 => "WRONG_SIGNATURE_LENGTH", + 85899346185u64 => "WRONG_SIGNATURE_SIZE", + 85899346290u64 => "WRONG_SIGNATURE_TYPE", + 85899346186u64 => "WRONG_SSL_VERSION", + 85899346187u64 => "WRONG_VERSION_NUMBER", + 85899346188u64 => "X509_LIB", + 85899346189u64 => "X509_VERIFICATION_SETUP_PROBLEMS", + 201863463044u64 => "BAD_PKCS7_TYPE", + 201863463045u64 => "BAD_TYPE", + 201863463049u64 => "CANNOT_LOAD_CERT", + 201863463050u64 => "CANNOT_LOAD_KEY", + 201863463012u64 => "CERTIFICATE_VERIFY_ERROR", + 201863463039u64 => "COULD_NOT_SET_ENGINE", + 201863463027u64 => "COULD_NOT_SET_TIME", + 201863463046u64 => "DETACHED_CONTENT", + 201863463028u64 => "ESS_ADD_SIGNING_CERT_ERROR", + 201863463051u64 => "ESS_ADD_SIGNING_CERT_V2_ERROR", + 201863463013u64 => "ESS_SIGNING_CERTIFICATE_ERROR", + 201863463014u64 => "INVALID_NULL_POINTER", + 201863463029u64 => "INVALID_SIGNER_CERTIFICATE_PURPOSE", + 201863463015u64 => "MESSAGE_IMPRINT_MISMATCH", + 201863463016u64 => "NONCE_MISMATCH", + 201863463017u64 => "NONCE_NOT_RETURNED", + 201863463018u64 => "NO_CONTENT", + 201863463019u64 => "NO_TIME_STAMP_TOKEN", + 201863463030u64 => "PKCS7_ADD_SIGNATURE_ERROR", + 201863463031u64 => "PKCS7_ADD_SIGNED_ATTR_ERROR", + 201863463041u64 => "PKCS7_TO_TS_TST_INFO_FAILED", + 201863463020u64 => "POLICY_MISMATCH", + 201863463032u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 201863463033u64 => "RESPONSE_SETUP_ERROR", + 201863463021u64 => "SIGNATURE_FAILURE", + 201863463022u64 => "THERE_MUST_BE_ONE_SIGNER", + 201863463034u64 => "TIME_SYSCALL_ERROR", + 201863463042u64 => "TOKEN_NOT_PRESENT", + 201863463043u64 => "TOKEN_PRESENT", + 201863463023u64 => "TSA_NAME_MISMATCH", + 201863463024u64 => "TSA_UNTRUSTED", + 201863463035u64 => "TST_INFO_SETUP_ERROR", + 201863463036u64 => "TS_DATASIGN", + 201863463037u64 => "UNACCEPTABLE_POLICY", + 201863463038u64 => "UNSUPPORTED_MD_ALGORITHM", + 201863463025u64 => "UNSUPPORTED_VERSION", + 201863463047u64 => "VAR_BAD_VALUE", + 201863463048u64 => "VAR_LOOKUP_FAILURE", + 201863463026u64 => "WRONG_CONTENT_TYPE", + 171798691944u64 => "COMMON_OK_AND_CANCEL_CHARACTERS", + 171798691942u64 => "INDEX_TOO_LARGE", + 171798691943u64 => "INDEX_TOO_SMALL", + 171798691945u64 => "NO_RESULT_BUFFER", + 171798691947u64 => "PROCESSING_ERROR", + 171798691940u64 => "RESULT_TOO_LARGE", + 171798691941u64 => "RESULT_TOO_SMALL", + 171798691949u64 => "SYSASSIGN_ERROR", + 171798691950u64 => "SYSDASSGN_ERROR", + 171798691951u64 => "SYSQIOW_ERROR", + 171798691946u64 => "UNKNOWN_CONTROL_COMMAND", + 171798691948u64 => "UNKNOWN_TTYGET_ERRNO_VALUE", + 171798691952u64 => "USER_DATA_DUPLICATION_UNSUPPORTED", + 146028888182u64 => "BAD_IP_ADDRESS", + 146028888183u64 => "BAD_OBJECT", + 146028888164u64 => "BN_DEC2BN_ERROR", + 146028888165u64 => "BN_TO_ASN1_INTEGER_ERROR", + 146028888213u64 => "DIRNAME_ERROR", + 146028888224u64 => "DISTPOINT_ALREADY_SET", + 146028888197u64 => "DUPLICATE_ZONE_ID", + 146028888195u64 => "ERROR_CONVERTING_ZONE", + 146028888208u64 => "ERROR_CREATING_EXTENSION", + 146028888192u64 => "ERROR_IN_EXTENSION", + 146028888201u64 => "EXPECTED_A_SECTION_NAME", + 146028888209u64 => "EXTENSION_EXISTS", + 146028888179u64 => "EXTENSION_NAME_ERROR", + 146028888166u64 => "EXTENSION_NOT_FOUND", + 146028888167u64 => "EXTENSION_SETTING_NOT_SUPPORTED", + 146028888180u64 => "EXTENSION_VALUE_ERROR", + 146028888215u64 => "ILLEGAL_EMPTY_EXTENSION", + 146028888216u64 => "INCORRECT_POLICY_SYNTAX_TAG", + 146028888226u64 => "INVALID_ASNUMBER", + 146028888227u64 => "INVALID_ASRANGE", + 146028888168u64 => "INVALID_BOOLEAN_STRING", + 146028888169u64 => "INVALID_EXTENSION_STRING", + 146028888229u64 => "INVALID_INHERITANCE", + 146028888230u64 => "INVALID_IPADDRESS", + 146028888225u64 => "INVALID_MULTIPLE_RDNS", + 146028888170u64 => "INVALID_NAME", + 146028888171u64 => "INVALID_NULL_ARGUMENT", + 146028888172u64 => "INVALID_NULL_NAME", + 146028888173u64 => "INVALID_NULL_VALUE", + 146028888204u64 => "INVALID_NUMBER", + 146028888205u64 => "INVALID_NUMBERS", + 146028888174u64 => "INVALID_OBJECT_IDENTIFIER", + 146028888202u64 => "INVALID_OPTION", + 146028888198u64 => "INVALID_POLICY_IDENTIFIER", + 146028888217u64 => "INVALID_PROXY_POLICY_SETTING", + 146028888210u64 => "INVALID_PURPOSE", + 146028888228u64 => "INVALID_SAFI", + 146028888199u64 => "INVALID_SECTION", + 146028888207u64 => "INVALID_SYNTAX", + 146028888190u64 => "ISSUER_DECODE_ERROR", + 146028888188u64 => "MISSING_VALUE", + 146028888206u64 => "NEED_ORGANIZATION_AND_NUMBERS", + 146028888200u64 => "NO_CONFIG_DATABASE", + 146028888185u64 => "NO_ISSUER_CERTIFICATE", + 146028888191u64 => "NO_ISSUER_DETAILS", + 146028888203u64 => "NO_POLICY_IDENTIFIER", + 146028888218u64 => "NO_PROXY_CERT_POLICY_LANGUAGE_DEFINED", + 146028888178u64 => "NO_PUBLIC_KEY", + 146028888189u64 => "NO_SUBJECT_DETAILS", + 146028888212u64 => "OPERATION_NOT_DEFINED", + 146028888211u64 => "OTHERNAME_ERROR", + 146028888219u64 => "POLICY_LANGUAGE_ALREADY_DEFINED", + 146028888220u64 => "POLICY_PATH_LENGTH", + 146028888221u64 => "POLICY_PATH_LENGTH_ALREADY_DEFINED", + 146028888223u64 => "POLICY_WHEN_PROXY_LANGUAGE_REQUIRES_NO_POLICY", + 146028888214u64 => "SECTION_NOT_FOUND", + 146028888186u64 => "UNABLE_TO_GET_ISSUER_DETAILS", + 146028888187u64 => "UNABLE_TO_GET_ISSUER_KEYID", + 146028888175u64 => "UNKNOWN_BIT_STRING_ARGUMENT", + 146028888193u64 => "UNKNOWN_EXTENSION", + 146028888194u64 => "UNKNOWN_EXTENSION_NAME", + 146028888184u64 => "UNKNOWN_OPTION", + 146028888181u64 => "UNSUPPORTED_OPTION", + 146028888231u64 => "UNSUPPORTED_TYPE", + 146028888196u64 => "USER_TOO_LONG", + 47244640366u64 => "AKID_MISMATCH", + 47244640389u64 => "BAD_SELECTOR", + 47244640356u64 => "BAD_X509_FILETYPE", + 47244640374u64 => "BASE64_DECODE_ERROR", + 47244640370u64 => "CANT_CHECK_DH_KEY", + 47244640357u64 => "CERT_ALREADY_IN_HASH_TABLE", + 47244640383u64 => "CRL_ALREADY_DELTA", + 47244640387u64 => "CRL_VERIFY_FAILURE", + 47244640384u64 => "IDP_MISMATCH", + 47244640394u64 => "INVALID_ATTRIBUTES", + 47244640369u64 => "INVALID_DIRECTORY", + 47244640375u64 => "INVALID_FIELD_NAME", + 47244640379u64 => "INVALID_TRUST", + 47244640385u64 => "ISSUER_MISMATCH", + 47244640371u64 => "KEY_TYPE_MISMATCH", + 47244640372u64 => "KEY_VALUES_MISMATCH", + 47244640359u64 => "LOADING_CERT_DIR", + 47244640360u64 => "LOADING_DEFAULTS", + 47244640380u64 => "METHOD_NOT_SUPPORTED", + 47244640390u64 => "NAME_TOO_LONG", + 47244640388u64 => "NEWER_CRL_NOT_NEWER", + 47244640391u64 => "NO_CERTIFICATE_FOUND", + 47244640392u64 => "NO_CERTIFICATE_OR_CRL_FOUND", + 47244640361u64 => "NO_CERT_SET_FOR_US_TO_VERIFY", + 47244640393u64 => "NO_CRL_FOUND", + 47244640386u64 => "NO_CRL_NUMBER", + 47244640381u64 => "PUBLIC_KEY_DECODE_ERROR", + 47244640382u64 => "PUBLIC_KEY_ENCODE_ERROR", + 47244640362u64 => "SHOULD_RETRY", + 47244640363u64 => "UNABLE_TO_FIND_PARAMETERS_IN_CHAIN", + 47244640364u64 => "UNABLE_TO_GET_CERTS_PUBLIC_KEY", + 47244640373u64 => "UNKNOWN_KEY_TYPE", + 47244640365u64 => "UNKNOWN_NID", + 47244640377u64 => "UNKNOWN_PURPOSE_ID", + 47244640376u64 => "UNKNOWN_TRUST_ID", + 47244640367u64 => "UNSUPPORTED_ALGORITHM", + 47244640368u64 => "WRONG_LOOKUP_TYPE", + 47244640378u64 => "WRONG_TYPE", +}; + +/// Helper function to create encoded key from (lib, reason) pair +#[inline] +pub fn encode_error_key(lib: i32, reason: i32) -> u64 { + ((lib as u64) << 32) | (reason as u64 & 0xFFFFFFFF) +} diff --git a/stdlib/src/ssl/ssl_data_300.rs b/stdlib/src/ssl/ssl_data_300.rs new file mode 100644 index 00000000000..0f657eb1b01 --- /dev/null +++ b/stdlib/src/ssl/ssl_data_300.rs @@ -0,0 +1,1759 @@ +// File generated by tools/make_ssl_data_rs.py +// Generated on 2025-10-29T07:17:23.737586+00:00 +// Source: OpenSSL from /tmp/openssl-3.0 +// spell-checker: disable + +use phf::phf_map; + +// Maps lib_code -> library name +// Example: 20 -> "SSL" +pub static LIBRARY_CODES: phf::Map<u32, &'static str> = phf_map! { + 0u32 => "MASK", + 1u32 => "NONE", + 2u32 => "SYS", + 3u32 => "BN", + 4u32 => "RSA", + 5u32 => "DH", + 6u32 => "EVP", + 7u32 => "BUF", + 8u32 => "OBJ", + 9u32 => "PEM", + 10u32 => "DSA", + 11u32 => "X509", + 12u32 => "METH", + 13u32 => "ASN1", + 14u32 => "CONF", + 15u32 => "CRYPTO", + 16u32 => "EC", + 20u32 => "SSL", + 21u32 => "SSL23", + 22u32 => "SSL2", + 23u32 => "SSL3", + 30u32 => "RSAREF", + 31u32 => "PROXY", + 32u32 => "BIO", + 33u32 => "PKCS7", + 34u32 => "X509V3", + 35u32 => "PKCS12", + 36u32 => "RAND", + 37u32 => "DSO", + 38u32 => "ENGINE", + 39u32 => "OCSP", + 40u32 => "UI", + 41u32 => "COMP", + 42u32 => "ECDSA", + 43u32 => "ECDH", + 44u32 => "OSSL_STORE", + 45u32 => "FIPS", + 46u32 => "CMS", + 47u32 => "TS", + 48u32 => "HMAC", + 49u32 => "JPAKE", + 50u32 => "CT", + 51u32 => "ASYNC", + 52u32 => "KDF", + 53u32 => "SM2", + 54u32 => "ESS", + 55u32 => "PROP", + 56u32 => "CRMF", + 57u32 => "PROV", + 58u32 => "CMP", + 59u32 => "OSSL_ENCODER", + 60u32 => "OSSL_DECODER", + 61u32 => "HTTP", + 128u32 => "USER", +}; + +// Maps encoded (lib, reason) -> error mnemonic +// Example: encode_error_key(20, 134) -> "CERTIFICATE_VERIFY_FAILED" +// Key encoding: (lib << 32) | reason +pub static ERROR_CODES: phf::Map<u64, &'static str> = phf_map! { + 55834575019u64 => "ADDING_OBJECT", + 55834575051u64 => "ASN1_PARSE_ERROR", + 55834575052u64 => "ASN1_SIG_PARSE_ERROR", + 55834574948u64 => "AUX_ERROR", + 55834574950u64 => "BAD_OBJECT_HEADER", + 55834575078u64 => "BAD_TEMPLATE", + 55834575062u64 => "BMPSTRING_IS_WRONG_LENGTH", + 55834574953u64 => "BN_LIB", + 55834574954u64 => "BOOLEAN_IS_WRONG_LENGTH", + 55834574955u64 => "BUFFER_TOO_SMALL", + 55834574956u64 => "CIPHER_HAS_NO_OBJECT_IDENTIFIER", + 55834575065u64 => "CONTEXT_NOT_INITIALISED", + 55834574957u64 => "DATA_IS_WRONG", + 55834574958u64 => "DECODE_ERROR", + 55834575022u64 => "DEPTH_EXCEEDED", + 55834575046u64 => "DIGEST_AND_KEY_TYPE_NOT_SUPPORTED", + 55834574960u64 => "ENCODE_ERROR", + 55834575021u64 => "ERROR_GETTING_TIME", + 55834575020u64 => "ERROR_LOADING_SECTION", + 55834574962u64 => "ERROR_SETTING_CIPHER_PARAMS", + 55834574963u64 => "EXPECTING_AN_INTEGER", + 55834574964u64 => "EXPECTING_AN_OBJECT", + 55834574967u64 => "EXPLICIT_LENGTH_MISMATCH", + 55834574968u64 => "EXPLICIT_TAG_NOT_CONSTRUCTED", + 55834574969u64 => "FIELD_MISSING", + 55834574970u64 => "FIRST_NUM_TOO_LARGE", + 55834574971u64 => "HEADER_TOO_LONG", + 55834575023u64 => "ILLEGAL_BITSTRING_FORMAT", + 55834575024u64 => "ILLEGAL_BOOLEAN", + 55834574972u64 => "ILLEGAL_CHARACTERS", + 55834575025u64 => "ILLEGAL_FORMAT", + 55834575026u64 => "ILLEGAL_HEX", + 55834575027u64 => "ILLEGAL_IMPLICIT_TAG", + 55834575028u64 => "ILLEGAL_INTEGER", + 55834575074u64 => "ILLEGAL_NEGATIVE_VALUE", + 55834575029u64 => "ILLEGAL_NESTED_TAGGING", + 55834574973u64 => "ILLEGAL_NULL", + 55834575030u64 => "ILLEGAL_NULL_VALUE", + 55834575031u64 => "ILLEGAL_OBJECT", + 55834574974u64 => "ILLEGAL_OPTIONAL_ANY", + 55834575018u64 => "ILLEGAL_OPTIONS_ON_ITEM_TEMPLATE", + 55834575069u64 => "ILLEGAL_PADDING", + 55834574975u64 => "ILLEGAL_TAGGED_ANY", + 55834575032u64 => "ILLEGAL_TIME_VALUE", + 55834575070u64 => "ILLEGAL_ZERO_CONTENT", + 55834575033u64 => "INTEGER_NOT_ASCII_FORMAT", + 55834574976u64 => "INTEGER_TOO_LARGE_FOR_LONG", + 55834575068u64 => "INVALID_BIT_STRING_BITS_LEFT", + 55834574977u64 => "INVALID_BMPSTRING_LENGTH", + 55834574978u64 => "INVALID_DIGIT", + 55834575053u64 => "INVALID_MIME_TYPE", + 55834575034u64 => "INVALID_MODIFIER", + 55834575035u64 => "INVALID_NUMBER", + 55834575064u64 => "INVALID_OBJECT_ENCODING", + 55834575075u64 => "INVALID_SCRYPT_PARAMETERS", + 55834574979u64 => "INVALID_SEPARATOR", + 55834575066u64 => "INVALID_STRING_TABLE_VALUE", + 55834574981u64 => "INVALID_UNIVERSALSTRING_LENGTH", + 55834574982u64 => "INVALID_UTF8STRING", + 55834575067u64 => "INVALID_VALUE", + 55834575079u64 => "LENGTH_TOO_LONG", + 55834575036u64 => "LIST_ERROR", + 55834575054u64 => "MIME_NO_CONTENT_TYPE", + 55834575055u64 => "MIME_PARSE_ERROR", + 55834575056u64 => "MIME_SIG_PARSE_ERROR", + 55834574985u64 => "MISSING_EOC", + 55834574986u64 => "MISSING_SECOND_NUMBER", + 55834575037u64 => "MISSING_VALUE", + 55834574987u64 => "MSTRING_NOT_UNIVERSAL", + 55834574988u64 => "MSTRING_WRONG_TAG", + 55834575045u64 => "NESTED_ASN1_STRING", + 55834575049u64 => "NESTED_TOO_DEEP", + 55834574989u64 => "NON_HEX_CHARACTERS", + 55834575038u64 => "NOT_ASCII_FORMAT", + 55834574990u64 => "NOT_ENOUGH_DATA", + 55834575057u64 => "NO_CONTENT_TYPE", + 55834574991u64 => "NO_MATCHING_CHOICE_TYPE", + 55834575058u64 => "NO_MULTIPART_BODY_FAILURE", + 55834575059u64 => "NO_MULTIPART_BOUNDARY", + 55834575060u64 => "NO_SIG_CONTENT_TYPE", + 55834574992u64 => "NULL_IS_WRONG_LENGTH", + 55834575039u64 => "OBJECT_NOT_ASCII_FORMAT", + 55834574993u64 => "ODD_NUMBER_OF_CHARS", + 55834574995u64 => "SECOND_NUMBER_TOO_LARGE", + 55834574996u64 => "SEQUENCE_LENGTH_MISMATCH", + 55834574997u64 => "SEQUENCE_NOT_CONSTRUCTED", + 55834575040u64 => "SEQUENCE_OR_SET_NEEDS_CONFIG", + 55834574998u64 => "SHORT_LINE", + 55834575061u64 => "SIG_INVALID_MIME_TYPE", + 55834575050u64 => "STREAMING_NOT_SUPPORTED", + 55834574999u64 => "STRING_TOO_LONG", + 55834575000u64 => "STRING_TOO_SHORT", + 55834575002u64 => "THE_ASN1_OBJECT_IDENTIFIER_IS_NOT_KNOWN_FOR_THIS_MD", + 55834575041u64 => "TIME_NOT_ASCII_FORMAT", + 55834575071u64 => "TOO_LARGE", + 55834575003u64 => "TOO_LONG", + 55834575072u64 => "TOO_SMALL", + 55834575004u64 => "TYPE_NOT_CONSTRUCTED", + 55834575043u64 => "TYPE_NOT_PRIMITIVE", + 55834575007u64 => "UNEXPECTED_EOC", + 55834575063u64 => "UNIVERSALSTRING_IS_WRONG_LENGTH", + 55834575077u64 => "UNKNOWN_DIGEST", + 55834575008u64 => "UNKNOWN_FORMAT", + 55834575009u64 => "UNKNOWN_MESSAGE_DIGEST_ALGORITHM", + 55834575010u64 => "UNKNOWN_OBJECT_TYPE", + 55834575011u64 => "UNKNOWN_PUBLIC_KEY_TYPE", + 55834575047u64 => "UNKNOWN_SIGNATURE_ALGORITHM", + 55834575042u64 => "UNKNOWN_TAG", + 55834575012u64 => "UNSUPPORTED_ANY_DEFINED_BY_TYPE", + 55834575076u64 => "UNSUPPORTED_CIPHER", + 55834575015u64 => "UNSUPPORTED_PUBLIC_KEY_TYPE", + 55834575044u64 => "UNSUPPORTED_TYPE", + 55834575073u64 => "WRONG_INTEGER_TYPE", + 55834575048u64 => "WRONG_PUBLIC_KEY_TYPE", + 55834575016u64 => "WRONG_TAG", + 219043332197u64 => "FAILED_TO_SET_POOL", + 219043332198u64 => "FAILED_TO_SWAP_CONTEXT", + 219043332201u64 => "INIT_FAILED", + 219043332199u64 => "INVALID_POOL_SIZE", + 137438953572u64 => "ACCEPT_ERROR", + 137438953613u64 => "ADDRINFO_ADDR_IS_NOT_AF_INET", + 137438953601u64 => "AMBIGUOUS_HOST_OR_SERVICE", + 137438953573u64 => "BAD_FOPEN_MODE", + 137438953596u64 => "BROKEN_PIPE", + 137438953575u64 => "CONNECT_ERROR", + 137438953619u64 => "CONNECT_TIMEOUT", + 137438953579u64 => "GETHOSTBYNAME_ADDR_IS_NOT_AF_INET", + 137438953604u64 => "GETSOCKNAME_ERROR", + 137438953605u64 => "GETSOCKNAME_TRUNCATED_ADDRESS", + 137438953606u64 => "GETTING_SOCKTYPE", + 137438953597u64 => "INVALID_ARGUMENT", + 137438953607u64 => "INVALID_SOCKET", + 137438953595u64 => "IN_USE", + 137438953574u64 => "LENGTH_TOO_LONG", + 137438953608u64 => "LISTEN_V6_ONLY", + 137438953614u64 => "LOOKUP_RETURNED_NOTHING", + 137438953602u64 => "MALFORMED_HOST_OR_SERVICE", + 137438953582u64 => "NBIO_CONNECT_ERROR", + 137438953615u64 => "NO_ACCEPT_ADDR_OR_SERVICE_SPECIFIED", + 137438953616u64 => "NO_HOSTNAME_OR_SERVICE_SPECIFIED", + 137438953585u64 => "NO_PORT_DEFINED", + 137438953600u64 => "NO_SUCH_FILE", + 137438953576u64 => "TRANSFER_ERROR", + 137438953577u64 => "TRANSFER_TIMEOUT", + 137438953589u64 => "UNABLE_TO_BIND_SOCKET", + 137438953590u64 => "UNABLE_TO_CREATE_SOCKET", + 137438953609u64 => "UNABLE_TO_KEEPALIVE", + 137438953591u64 => "UNABLE_TO_LISTEN_SOCKET", + 137438953610u64 => "UNABLE_TO_NODELAY", + 137438953611u64 => "UNABLE_TO_REUSEADDR", + 137438953617u64 => "UNAVAILABLE_IP_FAMILY", + 137438953592u64 => "UNINITIALIZED", + 137438953612u64 => "UNKNOWN_INFO_TYPE", + 137438953618u64 => "UNSUPPORTED_IP_FAMILY", + 137438953593u64 => "UNSUPPORTED_METHOD", + 137438953603u64 => "UNSUPPORTED_PROTOCOL_FAMILY", + 137438953598u64 => "WRITE_TO_READ_ONLY_BIO", + 137438953594u64 => "WSASTARTUP", + 12884901988u64 => "ARG2_LT_ARG3", + 12884901989u64 => "BAD_RECIPROCAL", + 12884902002u64 => "BIGNUM_TOO_LONG", + 12884902006u64 => "BITS_TOO_SMALL", + 12884901990u64 => "CALLED_WITH_EVEN_MODULUS", + 12884901991u64 => "DIV_BY_ZERO", + 12884901992u64 => "ENCODING_ERROR", + 12884901993u64 => "EXPAND_ON_STATIC_BIGNUM_DATA", + 12884901998u64 => "INPUT_NOT_REDUCED", + 12884901994u64 => "INVALID_LENGTH", + 12884902003u64 => "INVALID_RANGE", + 12884902007u64 => "INVALID_SHIFT", + 12884901999u64 => "NOT_A_SQUARE", + 12884901995u64 => "NOT_INITIALIZED", + 12884901996u64 => "NO_INVERSE", + 12884902009u64 => "NO_PRIME_CANDIDATE", + 12884902004u64 => "NO_SOLUTION", + 12884902008u64 => "NO_SUITABLE_DIGEST", + 12884902005u64 => "PRIVATE_KEY_TOO_LARGE", + 12884902000u64 => "P_IS_NOT_PRIME", + 12884902001u64 => "TOO_MANY_ITERATIONS", + 12884901997u64 => "TOO_MANY_TEMPORARY_VARIABLES", + 249108103307u64 => "ALGORITHM_NOT_SUPPORTED", + 249108103335u64 => "BAD_CHECKAFTER_IN_POLLREP", + 249108103276u64 => "BAD_REQUEST_ID", + 249108103324u64 => "CERTHASH_UNMATCHED", + 249108103277u64 => "CERTID_NOT_FOUND", + 249108103337u64 => "CERTIFICATE_NOT_ACCEPTED", + 249108103280u64 => "CERTIFICATE_NOT_FOUND", + 249108103325u64 => "CERTREQMSG_NOT_FOUND", + 249108103281u64 => "CERTRESPONSE_NOT_FOUND", + 249108103282u64 => "CERT_AND_KEY_DO_NOT_MATCH", + 249108103349u64 => "CHECKAFTER_OUT_OF_RANGE", + 249108103344u64 => "ENCOUNTERED_KEYUPDATEWARNING", + 249108103330u64 => "ENCOUNTERED_WAITING", + 249108103283u64 => "ERROR_CALCULATING_PROTECTION", + 249108103284u64 => "ERROR_CREATING_CERTCONF", + 249108103285u64 => "ERROR_CREATING_CERTREP", + 249108103331u64 => "ERROR_CREATING_CERTREQ", + 249108103286u64 => "ERROR_CREATING_ERROR", + 249108103287u64 => "ERROR_CREATING_GENM", + 249108103288u64 => "ERROR_CREATING_GENP", + 249108103290u64 => "ERROR_CREATING_PKICONF", + 249108103291u64 => "ERROR_CREATING_POLLREP", + 249108103292u64 => "ERROR_CREATING_POLLREQ", + 249108103293u64 => "ERROR_CREATING_RP", + 249108103294u64 => "ERROR_CREATING_RR", + 249108103275u64 => "ERROR_PARSING_PKISTATUS", + 249108103326u64 => "ERROR_PROCESSING_MESSAGE", + 249108103295u64 => "ERROR_PROTECTING_MESSAGE", + 249108103296u64 => "ERROR_SETTING_CERTHASH", + 249108103328u64 => "ERROR_UNEXPECTED_CERTCONF", + 249108103308u64 => "ERROR_VALIDATING_PROTECTION", + 249108103339u64 => "ERROR_VALIDATING_SIGNATURE", + 249108103332u64 => "FAILED_BUILDING_OWN_CHAIN", + 249108103309u64 => "FAILED_EXTRACTING_PUBKEY", + 249108103278u64 => "FAILURE_OBTAINING_RANDOM", + 249108103297u64 => "FAIL_INFO_OUT_OF_RANGE", + 249108103268u64 => "INVALID_ARGS", + 249108103342u64 => "INVALID_OPTION", + 249108103333u64 => "MISSING_CERTID", + 249108103298u64 => "MISSING_KEY_INPUT_FOR_CREATING_PROTECTION", + 249108103310u64 => "MISSING_KEY_USAGE_DIGITALSIGNATURE", + 249108103289u64 => "MISSING_P10CSR", + 249108103334u64 => "MISSING_PBM_SECRET", + 249108103299u64 => "MISSING_PRIVATE_KEY", + 249108103358u64 => "MISSING_PRIVATE_KEY_FOR_POPO", + 249108103311u64 => "MISSING_PROTECTION", + 249108103351u64 => "MISSING_PUBLIC_KEY", + 249108103336u64 => "MISSING_REFERENCE_CERT", + 249108103346u64 => "MISSING_SECRET", + 249108103279u64 => "MISSING_SENDER_IDENTIFICATION", + 249108103347u64 => "MISSING_TRUST_ANCHOR", + 249108103312u64 => "MISSING_TRUST_STORE", + 249108103329u64 => "MULTIPLE_REQUESTS_NOT_SUPPORTED", + 249108103338u64 => "MULTIPLE_RESPONSES_NOT_SUPPORTED", + 249108103270u64 => "MULTIPLE_SAN_SOURCES", + 249108103362u64 => "NO_STDIO", + 249108103313u64 => "NO_SUITABLE_SENDER_CERT", + 249108103271u64 => "NULL_ARGUMENT", + 249108103314u64 => "PKIBODY_ERROR", + 249108103300u64 => "PKISTATUSINFO_NOT_FOUND", + 249108103340u64 => "POLLING_FAILED", + 249108103315u64 => "POTENTIALLY_INVALID_CERTIFICATE", + 249108103348u64 => "RECEIVED_ERROR", + 249108103316u64 => "RECIPNONCE_UNMATCHED", + 249108103317u64 => "REQUEST_NOT_ACCEPTED", + 249108103350u64 => "REQUEST_REJECTED_BY_SERVER", + 249108103318u64 => "SENDER_GENERALNAME_TYPE_NOT_SUPPORTED", + 249108103319u64 => "SRVCERT_DOES_NOT_VALIDATE_MSG", + 249108103352u64 => "TOTAL_TIMEOUT", + 249108103320u64 => "TRANSACTIONID_UNMATCHED", + 249108103327u64 => "TRANSFER_ERROR", + 249108103301u64 => "UNEXPECTED_PKIBODY", + 249108103353u64 => "UNEXPECTED_PKISTATUS", + 249108103321u64 => "UNEXPECTED_PVNO", + 249108103302u64 => "UNKNOWN_ALGORITHM_ID", + 249108103303u64 => "UNKNOWN_CERT_TYPE", + 249108103354u64 => "UNKNOWN_PKISTATUS", + 249108103304u64 => "UNSUPPORTED_ALGORITHM", + 249108103305u64 => "UNSUPPORTED_KEY_TYPE", + 249108103322u64 => "UNSUPPORTED_PROTECTION_ALG_DHBASEDMAC", + 249108103343u64 => "VALUE_TOO_LARGE", + 249108103345u64 => "VALUE_TOO_SMALL", + 249108103306u64 => "WRONG_ALGORITHM_OID", + 249108103357u64 => "WRONG_CERTID", + 249108103355u64 => "WRONG_CERTID_IN_RP", + 249108103323u64 => "WRONG_PBM_VALUE", + 249108103356u64 => "WRONG_RP_COMPONENT_COUNT", + 249108103341u64 => "WRONG_SERIAL_IN_RP", + 197568495715u64 => "ADD_SIGNER_ERROR", + 197568495777u64 => "ATTRIBUTE_ERROR", + 197568495791u64 => "CERTIFICATE_ALREADY_PRESENT", + 197568495776u64 => "CERTIFICATE_HAS_NO_KEYID", + 197568495716u64 => "CERTIFICATE_VERIFY_ERROR", + 197568495800u64 => "CIPHER_AEAD_SET_TAG_ERROR", + 197568495801u64 => "CIPHER_GET_TAG", + 197568495717u64 => "CIPHER_INITIALISATION_ERROR", + 197568495718u64 => "CIPHER_PARAMETER_INITIALISATION_ERROR", + 197568495719u64 => "CMS_DATAFINAL_ERROR", + 197568495720u64 => "CMS_LIB", + 197568495786u64 => "CONTENTIDENTIFIER_MISMATCH", + 197568495721u64 => "CONTENT_NOT_FOUND", + 197568495787u64 => "CONTENT_TYPE_MISMATCH", + 197568495722u64 => "CONTENT_TYPE_NOT_COMPRESSED_DATA", + 197568495723u64 => "CONTENT_TYPE_NOT_ENVELOPED_DATA", + 197568495724u64 => "CONTENT_TYPE_NOT_SIGNED_DATA", + 197568495725u64 => "CONTENT_VERIFY_ERROR", + 197568495726u64 => "CTRL_ERROR", + 197568495727u64 => "CTRL_FAILURE", + 197568495803u64 => "DECODE_ERROR", + 197568495728u64 => "DECRYPT_ERROR", + 197568495729u64 => "ERROR_GETTING_PUBLIC_KEY", + 197568495730u64 => "ERROR_READING_MESSAGEDIGEST_ATTRIBUTE", + 197568495731u64 => "ERROR_SETTING_KEY", + 197568495732u64 => "ERROR_SETTING_RECIPIENTINFO", + 197568495799u64 => "ESS_SIGNING_CERTID_MISMATCH_ERROR", + 197568495733u64 => "INVALID_ENCRYPTED_KEY_LENGTH", + 197568495792u64 => "INVALID_KEY_ENCRYPTION_PARAMETER", + 197568495734u64 => "INVALID_KEY_LENGTH", + 197568495806u64 => "INVALID_LABEL", + 197568495807u64 => "INVALID_OAEP_PARAMETERS", + 197568495802u64 => "KDF_PARAMETER_ERROR", + 197568495735u64 => "MD_BIO_INIT_ERROR", + 197568495736u64 => "MESSAGEDIGEST_ATTRIBUTE_WRONG_LENGTH", + 197568495737u64 => "MESSAGEDIGEST_WRONG_LENGTH", + 197568495788u64 => "MSGSIGDIGEST_ERROR", + 197568495778u64 => "MSGSIGDIGEST_VERIFICATION_FAILURE", + 197568495779u64 => "MSGSIGDIGEST_WRONG_LENGTH", + 197568495780u64 => "NEED_ONE_SIGNER", + 197568495781u64 => "NOT_A_SIGNED_RECEIPT", + 197568495738u64 => "NOT_ENCRYPTED_DATA", + 197568495739u64 => "NOT_KEK", + 197568495797u64 => "NOT_KEY_AGREEMENT", + 197568495740u64 => "NOT_KEY_TRANSPORT", + 197568495793u64 => "NOT_PWRI", + 197568495741u64 => "NOT_SUPPORTED_FOR_THIS_KEY_TYPE", + 197568495742u64 => "NO_CIPHER", + 197568495743u64 => "NO_CONTENT", + 197568495789u64 => "NO_CONTENT_TYPE", + 197568495744u64 => "NO_DEFAULT_DIGEST", + 197568495745u64 => "NO_DIGEST_SET", + 197568495746u64 => "NO_KEY", + 197568495790u64 => "NO_KEY_OR_CERT", + 197568495747u64 => "NO_MATCHING_DIGEST", + 197568495748u64 => "NO_MATCHING_RECIPIENT", + 197568495782u64 => "NO_MATCHING_SIGNATURE", + 197568495783u64 => "NO_MSGSIGDIGEST", + 197568495794u64 => "NO_PASSWORD", + 197568495749u64 => "NO_PRIVATE_KEY", + 197568495750u64 => "NO_PUBLIC_KEY", + 197568495784u64 => "NO_RECEIPT_REQUEST", + 197568495751u64 => "NO_SIGNERS", + 197568495804u64 => "PEER_KEY_ERROR", + 197568495752u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 197568495785u64 => "RECEIPT_DECODE_ERROR", + 197568495753u64 => "RECIPIENT_ERROR", + 197568495805u64 => "SHARED_INFO_ERROR", + 197568495754u64 => "SIGNER_CERTIFICATE_NOT_FOUND", + 197568495755u64 => "SIGNFINAL_ERROR", + 197568495756u64 => "SMIME_TEXT_ERROR", + 197568495757u64 => "STORE_INIT_ERROR", + 197568495758u64 => "TYPE_NOT_COMPRESSED_DATA", + 197568495759u64 => "TYPE_NOT_DATA", + 197568495760u64 => "TYPE_NOT_DIGESTED_DATA", + 197568495761u64 => "TYPE_NOT_ENCRYPTED_DATA", + 197568495762u64 => "TYPE_NOT_ENVELOPED_DATA", + 197568495763u64 => "UNABLE_TO_FINALIZE_CONTEXT", + 197568495764u64 => "UNKNOWN_CIPHER", + 197568495765u64 => "UNKNOWN_DIGEST_ALGORITHM", + 197568495766u64 => "UNKNOWN_ID", + 197568495767u64 => "UNSUPPORTED_COMPRESSION_ALGORITHM", + 197568495810u64 => "UNSUPPORTED_CONTENT_ENCRYPTION_ALGORITHM", + 197568495768u64 => "UNSUPPORTED_CONTENT_TYPE", + 197568495808u64 => "UNSUPPORTED_ENCRYPTION_TYPE", + 197568495769u64 => "UNSUPPORTED_KEK_ALGORITHM", + 197568495795u64 => "UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM", + 197568495809u64 => "UNSUPPORTED_LABEL_SOURCE", + 197568495771u64 => "UNSUPPORTED_RECIPIENTINFO_TYPE", + 197568495770u64 => "UNSUPPORTED_RECIPIENT_TYPE", + 197568495811u64 => "UNSUPPORTED_SIGNATURE_ALGORITHM", + 197568495772u64 => "UNSUPPORTED_TYPE", + 197568495773u64 => "UNWRAP_ERROR", + 197568495796u64 => "UNWRAP_FAILURE", + 197568495774u64 => "VERIFICATION_FAILURE", + 197568495775u64 => "WRAP_ERROR", + 176093659235u64 => "ZLIB_DEFLATE_ERROR", + 176093659236u64 => "ZLIB_INFLATE_ERROR", + 176093659237u64 => "ZLIB_NOT_SUPPORTED", + 60129542254u64 => "ERROR_LOADING_DSO", + 60129542266u64 => "INVALID_PRAGMA", + 60129542259u64 => "LIST_CANNOT_BE_NULL", + 60129542267u64 => "MANDATORY_BRACES_IN_VARIABLE_EXPANSION", + 60129542244u64 => "MISSING_CLOSE_SQUARE_BRACKET", + 60129542245u64 => "MISSING_EQUAL_SIGN", + 60129542256u64 => "MISSING_INIT_FUNCTION", + 60129542253u64 => "MODULE_INITIALIZATION_ERROR", + 60129542246u64 => "NO_CLOSE_BRACE", + 60129542249u64 => "NO_CONF", + 60129542250u64 => "NO_CONF_OR_ENVIRONMENT_VARIABLE", + 60129542251u64 => "NO_SECTION", + 60129542258u64 => "NO_SUCH_FILE", + 60129542252u64 => "NO_VALUE", + 60129542265u64 => "NUMBER_TOO_LARGE", + 60129542268u64 => "OPENSSL_CONF_REFERENCES_MISSING_SECTION", + 60129542255u64 => "RECURSIVE_DIRECTORY_INCLUDE", + 60129542270u64 => "RECURSIVE_SECTION_REFERENCE", + 60129542269u64 => "RELATIVE_PATH", + 60129542261u64 => "SSL_COMMAND_SECTION_EMPTY", + 60129542262u64 => "SSL_COMMAND_SECTION_NOT_FOUND", + 60129542263u64 => "SSL_SECTION_EMPTY", + 60129542264u64 => "SSL_SECTION_NOT_FOUND", + 60129542247u64 => "UNABLE_TO_CREATE_NEW_SECTION", + 60129542257u64 => "UNKNOWN_MODULE_NAME", + 60129542260u64 => "VARIABLE_EXPANSION_TOO_LONG", + 60129542248u64 => "VARIABLE_HAS_NO_VALUE", + 240518168676u64 => "BAD_PBM_ITERATIONCOUNT", + 240518168678u64 => "CRMFERROR", + 240518168679u64 => "ERROR", + 240518168680u64 => "ERROR_DECODING_CERTIFICATE", + 240518168681u64 => "ERROR_DECRYPTING_CERTIFICATE", + 240518168682u64 => "ERROR_DECRYPTING_SYMMETRIC_KEY", + 240518168683u64 => "FAILURE_OBTAINING_RANDOM", + 240518168684u64 => "ITERATIONCOUNT_BELOW_100", + 240518168677u64 => "MALFORMED_IV", + 240518168685u64 => "NULL_ARGUMENT", + 240518168689u64 => "POPOSKINPUT_NOT_SUPPORTED", + 240518168693u64 => "POPO_INCONSISTENT_PUBLIC_KEY", + 240518168697u64 => "POPO_MISSING", + 240518168694u64 => "POPO_MISSING_PUBLIC_KEY", + 240518168695u64 => "POPO_MISSING_SUBJECT", + 240518168696u64 => "POPO_RAVERIFIED_NOT_ACCEPTED", + 240518168686u64 => "SETTING_MAC_ALGOR_FAILURE", + 240518168687u64 => "SETTING_OWF_ALGOR_FAILURE", + 240518168688u64 => "UNSUPPORTED_ALGORITHM", + 240518168690u64 => "UNSUPPORTED_CIPHER", + 240518168691u64 => "UNSUPPORTED_METHOD_FOR_CREATING_POPO", + 240518168692u64 => "UNSUPPORTED_POPO_METHOD", + 64424509557u64 => "BAD_ALGORITHM_NAME", + 64424509558u64 => "CONFLICTING_NAMES", + 64424509561u64 => "HEX_STRING_TOO_SHORT", + 64424509542u64 => "ILLEGAL_HEX_DIGIT", + 64424509546u64 => "INSUFFICIENT_DATA_SPACE", + 64424509547u64 => "INSUFFICIENT_PARAM_SIZE", + 64424509548u64 => "INSUFFICIENT_SECURE_DATA_SPACE", + 64424509562u64 => "INVALID_NEGATIVE_VALUE", + 64424509549u64 => "INVALID_NULL_ARGUMENT", + 64424509550u64 => "INVALID_OSSL_PARAM_TYPE", + 64424509543u64 => "ODD_NUMBER_OF_DIGITS", + 64424509544u64 => "PROVIDER_ALREADY_EXISTS", + 64424509545u64 => "PROVIDER_SECTION_ERROR", + 64424509559u64 => "RANDOM_SECTION_ERROR", + 64424509551u64 => "SECURE_MALLOC_FAILURE", + 64424509552u64 => "STRING_TOO_LONG", + 64424509553u64 => "TOO_MANY_BYTES", + 64424509554u64 => "TOO_MANY_RECORDS", + 64424509556u64 => "TOO_SMALL_BUFFER", + 64424509560u64 => "UNKNOWN_NAME_IN_RANDOM_SECTION", + 64424509555u64 => "ZERO_LENGTH_NUMBER", + 214748364908u64 => "BASE64_DECODE_ERROR", + 214748364900u64 => "INVALID_LOG_ID_LENGTH", + 214748364909u64 => "LOG_CONF_INVALID", + 214748364910u64 => "LOG_CONF_INVALID_KEY", + 214748364911u64 => "LOG_CONF_MISSING_DESCRIPTION", + 214748364912u64 => "LOG_CONF_MISSING_KEY", + 214748364913u64 => "LOG_KEY_INVALID", + 214748364916u64 => "SCT_FUTURE_TIMESTAMP", + 214748364904u64 => "SCT_INVALID", + 214748364907u64 => "SCT_INVALID_SIGNATURE", + 214748364905u64 => "SCT_LIST_INVALID", + 214748364914u64 => "SCT_LOG_ID_MISMATCH", + 214748364906u64 => "SCT_NOT_SET", + 214748364915u64 => "SCT_UNSUPPORTED_VERSION", + 214748364901u64 => "UNRECOGNIZED_SIGNATURE_NID", + 214748364902u64 => "UNSUPPORTED_ENTRY_TYPE", + 214748364903u64 => "UNSUPPORTED_VERSION", + 21474836607u64 => "BAD_FFC_PARAMETERS", + 21474836581u64 => "BAD_GENERATOR", + 21474836589u64 => "BN_DECODE_ERROR", + 21474836586u64 => "BN_ERROR", + 21474836595u64 => "CHECK_INVALID_J_VALUE", + 21474836596u64 => "CHECK_INVALID_Q_VALUE", + 21474836602u64 => "CHECK_PUBKEY_INVALID", + 21474836603u64 => "CHECK_PUBKEY_TOO_LARGE", + 21474836604u64 => "CHECK_PUBKEY_TOO_SMALL", + 21474836597u64 => "CHECK_P_NOT_PRIME", + 21474836598u64 => "CHECK_P_NOT_SAFE_PRIME", + 21474836599u64 => "CHECK_Q_NOT_PRIME", + 21474836584u64 => "DECODE_ERROR", + 21474836590u64 => "INVALID_PARAMETER_NAME", + 21474836594u64 => "INVALID_PARAMETER_NID", + 21474836582u64 => "INVALID_PUBKEY", + 21474836608u64 => "INVALID_SECRET", + 21474836592u64 => "KDF_PARAMETER_ERROR", + 21474836588u64 => "KEYS_NOT_SET", + 21474836605u64 => "MISSING_PUBKEY", + 21474836583u64 => "MODULUS_TOO_LARGE", + 21474836606u64 => "MODULUS_TOO_SMALL", + 21474836600u64 => "NOT_SUITABLE_GENERATOR", + 21474836587u64 => "NO_PARAMETERS_SET", + 21474836580u64 => "NO_PRIVATE_VALUE", + 21474836585u64 => "PARAMETER_ENCODING_ERROR", + 21474836591u64 => "PEER_KEY_ERROR", + 21474836610u64 => "Q_TOO_LARGE", + 21474836593u64 => "SHARED_INFO_ERROR", + 21474836601u64 => "UNABLE_TO_CHECK_GENERATOR", + 42949673074u64 => "BAD_FFC_PARAMETERS", + 42949673062u64 => "BAD_Q_VALUE", + 42949673068u64 => "BN_DECODE_ERROR", + 42949673069u64 => "BN_ERROR", + 42949673064u64 => "DECODE_ERROR", + 42949673066u64 => "INVALID_DIGEST_TYPE", + 42949673072u64 => "INVALID_PARAMETERS", + 42949673061u64 => "MISSING_PARAMETERS", + 42949673071u64 => "MISSING_PRIVATE_KEY", + 42949673063u64 => "MODULUS_TOO_LARGE", + 42949673067u64 => "NO_PARAMETERS_SET", + 42949673065u64 => "PARAMETER_ENCODING_ERROR", + 42949673075u64 => "P_NOT_PRIME", + 42949673073u64 => "Q_NOT_PRIME", + 42949673070u64 => "SEED_LEN_SMALL", + 42949673076u64 => "TOO_MANY_RETRIES", + 158913790052u64 => "CTRL_FAILED", + 158913790062u64 => "DSO_ALREADY_LOADED", + 158913790065u64 => "EMPTY_FILE_STRUCTURE", + 158913790066u64 => "FAILURE", + 158913790053u64 => "FILENAME_TOO_BIG", + 158913790054u64 => "FINISH_FAILED", + 158913790067u64 => "INCORRECT_FILE_SYNTAX", + 158913790055u64 => "LOAD_FAILED", + 158913790061u64 => "NAME_TRANSLATION_FAILED", + 158913790063u64 => "NO_FILENAME", + 158913790056u64 => "NULL_HANDLE", + 158913790064u64 => "SET_FILENAME_FAILED", + 158913790057u64 => "STACK_ERROR", + 158913790058u64 => "SYM_FAILURE", + 158913790059u64 => "UNLOAD_FAILED", + 158913790060u64 => "UNSUPPORTED", + 68719476851u64 => "ASN1_ERROR", + 68719476892u64 => "BAD_SIGNATURE", + 68719476880u64 => "BIGNUM_OUT_OF_RANGE", + 68719476836u64 => "BUFFER_TOO_SMALL", + 68719476901u64 => "CANNOT_INVERT", + 68719476882u64 => "COORDINATES_OUT_OF_RANGE", + 68719476896u64 => "CURVE_DOES_NOT_SUPPORT_ECDH", + 68719476906u64 => "CURVE_DOES_NOT_SUPPORT_ECDSA", + 68719476895u64 => "CURVE_DOES_NOT_SUPPORT_SIGNING", + 68719476878u64 => "DECODE_ERROR", + 68719476854u64 => "DISCRIMINANT_IS_ZERO", + 68719476855u64 => "EC_GROUP_NEW_BY_NAME_FAILURE", + 68719476863u64 => "EXPLICIT_PARAMS_NOT_SUPPORTED", + 68719476902u64 => "FAILED_MAKING_PUBLIC_KEY", + 68719476879u64 => "FIELD_TOO_LARGE", + 68719476883u64 => "GF2M_NOT_SUPPORTED", + 68719476856u64 => "GROUP2PKPARAMETERS_FAILURE", + 68719476857u64 => "I2D_ECPKPARAMETERS_FAILURE", + 68719476837u64 => "INCOMPATIBLE_OBJECTS", + 68719476904u64 => "INVALID_A", + 68719476848u64 => "INVALID_ARGUMENT", + 68719476905u64 => "INVALID_B", + 68719476907u64 => "INVALID_COFACTOR", + 68719476846u64 => "INVALID_COMPRESSED_POINT", + 68719476845u64 => "INVALID_COMPRESSION_BIT", + 68719476877u64 => "INVALID_CURVE", + 68719476887u64 => "INVALID_DIGEST", + 68719476874u64 => "INVALID_DIGEST_TYPE", + 68719476838u64 => "INVALID_ENCODING", + 68719476839u64 => "INVALID_FIELD", + 68719476840u64 => "INVALID_FORM", + 68719476909u64 => "INVALID_GENERATOR", + 68719476858u64 => "INVALID_GROUP_ORDER", + 68719476852u64 => "INVALID_KEY", + 68719476853u64 => "INVALID_LENGTH", + 68719476910u64 => "INVALID_NAMED_GROUP_CONVERSION", + 68719476897u64 => "INVALID_OUTPUT_LENGTH", + 68719476908u64 => "INVALID_P", + 68719476869u64 => "INVALID_PEER_KEY", + 68719476868u64 => "INVALID_PENTANOMIAL_BASIS", + 68719476859u64 => "INVALID_PRIVATE_KEY", + 68719476911u64 => "INVALID_SEED", + 68719476873u64 => "INVALID_TRINOMIAL_BASIS", + 68719476884u64 => "KDF_PARAMETER_ERROR", + 68719476876u64 => "KEYS_NOT_SET", + 68719476872u64 => "LADDER_POST_FAILURE", + 68719476889u64 => "LADDER_PRE_FAILURE", + 68719476898u64 => "LADDER_STEP_FAILURE", + 68719476903u64 => "MISSING_OID", + 68719476860u64 => "MISSING_PARAMETERS", + 68719476861u64 => "MISSING_PRIVATE_KEY", + 68719476893u64 => "NEED_NEW_SETUP_VALUES", + 68719476871u64 => "NOT_A_NIST_PRIME", + 68719476862u64 => "NOT_IMPLEMENTED", + 68719476847u64 => "NOT_INITIALIZED", + 68719476875u64 => "NO_PARAMETERS_SET", + 68719476890u64 => "NO_PRIVATE_VALUE", + 68719476888u64 => "OPERATION_NOT_SUPPORTED", + 68719476870u64 => "PASSED_NULL_PARAMETER", + 68719476885u64 => "PEER_KEY_ERROR", + 68719476891u64 => "POINT_ARITHMETIC_FAILURE", + 68719476842u64 => "POINT_AT_INFINITY", + 68719476899u64 => "POINT_COORDINATES_BLIND_FAILURE", + 68719476843u64 => "POINT_IS_NOT_ON_CURVE", + 68719476894u64 => "RANDOM_NUMBER_GENERATION_FAILED", + 68719476886u64 => "SHARED_INFO_ERROR", + 68719476844u64 => "SLOT_FULL", + 68719476912u64 => "TOO_MANY_RETRIES", + 68719476849u64 => "UNDEFINED_GENERATOR", + 68719476864u64 => "UNDEFINED_ORDER", + 68719476900u64 => "UNKNOWN_COFACTOR", + 68719476865u64 => "UNKNOWN_GROUP", + 68719476850u64 => "UNKNOWN_ORDER", + 68719476867u64 => "UNSUPPORTED_FIELD", + 68719476881u64 => "WRONG_CURVE_PARAMETERS", + 68719476866u64 => "WRONG_ORDER", + 163208757348u64 => "ALREADY_LOADED", + 163208757381u64 => "ARGUMENT_IS_NOT_A_NUMBER", + 163208757382u64 => "CMD_NOT_EXECUTABLE", + 163208757383u64 => "COMMAND_TAKES_INPUT", + 163208757384u64 => "COMMAND_TAKES_NO_INPUT", + 163208757351u64 => "CONFLICTING_ENGINE_ID", + 163208757367u64 => "CTRL_COMMAND_NOT_IMPLEMENTED", + 163208757352u64 => "DSO_FAILURE", + 163208757380u64 => "DSO_NOT_FOUND", + 163208757396u64 => "ENGINES_SECTION_ERROR", + 163208757350u64 => "ENGINE_CONFIGURATION_ERROR", + 163208757353u64 => "ENGINE_IS_NOT_IN_LIST", + 163208757397u64 => "ENGINE_SECTION_ERROR", + 163208757376u64 => "FAILED_LOADING_PRIVATE_KEY", + 163208757377u64 => "FAILED_LOADING_PUBLIC_KEY", + 163208757354u64 => "FINISH_FAILED", + 163208757356u64 => "ID_OR_NAME_MISSING", + 163208757357u64 => "INIT_FAILED", + 163208757358u64 => "INTERNAL_LIST_ERROR", + 163208757391u64 => "INVALID_ARGUMENT", + 163208757385u64 => "INVALID_CMD_NAME", + 163208757386u64 => "INVALID_CMD_NUMBER", + 163208757399u64 => "INVALID_INIT_VALUE", + 163208757398u64 => "INVALID_STRING", + 163208757365u64 => "NOT_INITIALISED", + 163208757360u64 => "NOT_LOADED", + 163208757368u64 => "NO_CONTROL_FUNCTION", + 163208757392u64 => "NO_INDEX", + 163208757373u64 => "NO_LOAD_FUNCTION", + 163208757378u64 => "NO_REFERENCE", + 163208757364u64 => "NO_SUCH_ENGINE", + 163208757394u64 => "UNIMPLEMENTED_CIPHER", + 163208757395u64 => "UNIMPLEMENTED_DIGEST", + 163208757349u64 => "UNIMPLEMENTED_PUBLIC_KEY_METHOD", + 163208757393u64 => "VERSION_INCOMPATIBILITY", + 231928234091u64 => "EMPTY_ESS_CERT_ID_LIST", + 231928234087u64 => "ESS_CERT_DIGEST_ERROR", + 231928234088u64 => "ESS_CERT_ID_NOT_FOUND", + 231928234089u64 => "ESS_CERT_ID_WRONG_ORDER", + 231928234090u64 => "ESS_DIGEST_ALG_UNKNOWN", + 231928234086u64 => "ESS_SIGNING_CERTIFICATE_ERROR", + 231928234084u64 => "ESS_SIGNING_CERT_ADD_ERROR", + 231928234085u64 => "ESS_SIGNING_CERT_V2_ADD_ERROR", + 231928234092u64 => "MISSING_SIGNING_CERTIFICATE_ATTRIBUTE", + 25769803919u64 => "AES_KEY_SETUP_FAILED", + 25769803952u64 => "ARIA_KEY_SETUP_FAILED", + 25769803976u64 => "BAD_ALGORITHM_NAME", + 25769803876u64 => "BAD_DECRYPT", + 25769803971u64 => "BAD_KEY_LENGTH", + 25769803931u64 => "BUFFER_TOO_SMALL", + 25769804001u64 => "CACHE_CONSTANTS_FAILED", + 25769803933u64 => "CAMELLIA_KEY_SETUP_FAILED", + 25769803973u64 => "CANNOT_GET_PARAMETERS", + 25769803974u64 => "CANNOT_SET_PARAMETERS", + 25769803960u64 => "CIPHER_NOT_GCM_MODE", + 25769803898u64 => "CIPHER_PARAMETER_ERROR", + 25769803923u64 => "COMMAND_NOT_SUPPORTED", + 25769803977u64 => "CONFLICTING_ALGORITHM_NAME", + 25769803949u64 => "COPY_ERROR", + 25769803908u64 => "CTRL_NOT_IMPLEMENTED", + 25769803909u64 => "CTRL_OPERATION_NOT_IMPLEMENTED", + 25769803914u64 => "DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH", + 25769803890u64 => "DECODE_ERROR", + 25769803986u64 => "DEFAULT_QUERY_PARSE_ERROR", + 25769803877u64 => "DIFFERENT_KEY_TYPES", + 25769803929u64 => "DIFFERENT_PARAMETERS", + 25769803941u64 => "ERROR_LOADING_SECTION", + 25769803950u64 => "EXPECTING_AN_HMAC_KEY", + 25769803903u64 => "EXPECTING_AN_RSA_KEY", + 25769803904u64 => "EXPECTING_A_DH_KEY", + 25769803905u64 => "EXPECTING_A_DSA_KEY", + 25769803995u64 => "EXPECTING_A_ECX_KEY", + 25769803918u64 => "EXPECTING_A_EC_KEY", + 25769803940u64 => "EXPECTING_A_POLY1305_KEY", + 25769803951u64 => "EXPECTING_A_SIPHASH_KEY", + 25769803964u64 => "FINAL_ERROR", + 25769803990u64 => "GENERATE_ERROR", + 25769803958u64 => "GET_RAW_KEY_FAILED", + 25769803947u64 => "ILLEGAL_SCRYPT_PARAMETERS", + 25769803980u64 => "INACCESSIBLE_DOMAIN_PARAMETERS", + 25769803979u64 => "INACCESSIBLE_KEY", + 25769803910u64 => "INITIALIZATION_ERROR", + 25769803887u64 => "INPUT_NOT_INITIALIZED", + 25769803961u64 => "INVALID_CUSTOM_LENGTH", + 25769803928u64 => "INVALID_DIGEST", + 25769803970u64 => "INVALID_IV_LENGTH", + 25769803939u64 => "INVALID_KEY", + 25769803906u64 => "INVALID_KEY_LENGTH", + 25769803997u64 => "INVALID_LENGTH", + 25769803994u64 => "INVALID_NULL_ALGORITHM", + 25769803924u64 => "INVALID_OPERATION", + 25769803969u64 => "INVALID_PROVIDER_FUNCTIONS", + 25769803962u64 => "INVALID_SALT_LENGTH", + 25769803999u64 => "INVALID_SECRET_LENGTH", + 25769803996u64 => "INVALID_SEED_LENGTH", + 25769803998u64 => "INVALID_VALUE", + 25769803981u64 => "KEYMGMT_EXPORT_FAILURE", + 25769803956u64 => "KEY_SETUP_FAILED", + 25769803989u64 => "LOCKING_NOT_SUPPORTED", + 25769803948u64 => "MEMORY_LIMIT_EXCEEDED", + 25769803935u64 => "MESSAGE_DIGEST_IS_NULL", + 25769803920u64 => "METHOD_NOT_SUPPORTED", + 25769803879u64 => "MISSING_PARAMETERS", + 25769803966u64 => "NOT_ABLE_TO_COPY_CTX", + 25769803954u64 => "NOT_XOF_OR_INVALID_LENGTH", + 25769803907u64 => "NO_CIPHER_SET", + 25769803934u64 => "NO_DEFAULT_DIGEST", + 25769803915u64 => "NO_DIGEST_SET", + 25769803982u64 => "NO_IMPORT_FUNCTION", + 25769803975u64 => "NO_KEYMGMT_AVAILABLE", + 25769803972u64 => "NO_KEYMGMT_PRESENT", + 25769803930u64 => "NO_KEY_SET", + 25769803925u64 => "NO_OPERATION_SET", + 25769803984u64 => "NULL_MAC_PKEY_CTX", + 25769803953u64 => "ONLY_ONESHOT_SUPPORTED", + 25769803927u64 => "OPERATION_NOT_INITIALIZED", + 25769803926u64 => "OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE", + 25769803978u64 => "OUTPUT_WOULD_OVERFLOW", + 25769803963u64 => "PARAMETER_TOO_LARGE", + 25769803938u64 => "PARTIALLY_OVERLAPPING", + 25769803957u64 => "PBKDF2_ERROR", + 25769803955u64 => "PKEY_APPLICATION_ASN1_METHOD_ALREADY_REGISTERED", + 25769803921u64 => "PRIVATE_KEY_DECODE_ERROR", + 25769803922u64 => "PRIVATE_KEY_ENCODE_ERROR", + 25769803882u64 => "PUBLIC_KEY_NOT_RSA", + 25769804003u64 => "SETTING_XOF_FAILED", + 25769803985u64 => "SET_DEFAULT_PROPERTY_FAILURE", + 25769803959u64 => "TOO_MANY_RECORDS", + 25769803988u64 => "UNABLE_TO_ENABLE_LOCKING", + 25769803991u64 => "UNABLE_TO_GET_MAXIMUM_REQUEST_SIZE", + 25769803992u64 => "UNABLE_TO_GET_RANDOM_STRENGTH", + 25769803987u64 => "UNABLE_TO_LOCK_CONTEXT", + 25769803993u64 => "UNABLE_TO_SET_CALLBACKS", + 25769803936u64 => "UNKNOWN_CIPHER", + 25769803937u64 => "UNKNOWN_DIGEST", + 25769803983u64 => "UNKNOWN_KEY_TYPE", + 25769803945u64 => "UNKNOWN_OPTION", + 25769803897u64 => "UNKNOWN_PBE_ALGORITHM", + 25769803932u64 => "UNSUPPORTED_ALGORITHM", + 25769803883u64 => "UNSUPPORTED_CIPHER", + 25769803899u64 => "UNSUPPORTED_KEYLENGTH", + 25769803900u64 => "UNSUPPORTED_KEY_DERIVATION_FUNCTION", + 25769803884u64 => "UNSUPPORTED_KEY_SIZE", + 25769804000u64 => "UNSUPPORTED_KEY_TYPE", + 25769803911u64 => "UNSUPPORTED_NUMBER_OF_ROUNDS", + 25769803901u64 => "UNSUPPORTED_PRF", + 25769803894u64 => "UNSUPPORTED_PRIVATE_KEY_ALGORITHM", + 25769803902u64 => "UNSUPPORTED_SALT_TYPE", + 25769803965u64 => "UPDATE_ERROR", + 25769803946u64 => "WRAP_MODE_NOT_ALLOWED", + 25769803885u64 => "WRONG_FINAL_BLOCK_LENGTH", + 25769803967u64 => "XTS_DATA_UNIT_IS_TOO_LARGE", + 25769803968u64 => "XTS_DUPLICATED_KEYS", + 261993005164u64 => "ASN1_LEN_EXCEEDS_MAX_RESP_LEN", + 261993005156u64 => "CONNECT_FAILURE", + 261993005165u64 => "ERROR_PARSING_ASN1_LENGTH", + 261993005175u64 => "ERROR_PARSING_CONTENT_LENGTH", + 261993005157u64 => "ERROR_PARSING_URL", + 261993005159u64 => "ERROR_RECEIVING", + 261993005158u64 => "ERROR_SENDING", + 261993005184u64 => "FAILED_READING_DATA", + 261993005182u64 => "HEADER_PARSE_ERROR", + 261993005176u64 => "INCONSISTENT_CONTENT_LENGTH", + 261993005179u64 => "INVALID_PORT_NUMBER", + 261993005181u64 => "INVALID_URL_PATH", + 261993005180u64 => "INVALID_URL_SCHEME", + 261993005173u64 => "MAX_RESP_LEN_EXCEEDED", + 261993005166u64 => "MISSING_ASN1_ENCODING", + 261993005177u64 => "MISSING_CONTENT_TYPE", + 261993005167u64 => "MISSING_REDIRECT_LOCATION", + 261993005161u64 => "RECEIVED_ERROR", + 261993005162u64 => "RECEIVED_WRONG_HTTP_VERSION", + 261993005168u64 => "REDIRECTION_FROM_HTTPS_TO_HTTP", + 261993005172u64 => "REDIRECTION_NOT_ENABLED", + 261993005169u64 => "RESPONSE_LINE_TOO_LONG", + 261993005160u64 => "RESPONSE_PARSE_ERROR", + 261993005185u64 => "RETRY_TIMEOUT", + 261993005183u64 => "SERVER_CANCELED_CONNECTION", + 261993005178u64 => "SOCK_NOT_SUPPORTED", + 261993005170u64 => "STATUS_CODE_UNSUPPORTED", + 261993005163u64 => "TLS_NOT_ENABLED", + 261993005171u64 => "TOO_MANY_REDIRECTIONS", + 261993005174u64 => "UNEXPECTED_CONTENT_TYPE", + 34359738470u64 => "OID_EXISTS", + 34359738469u64 => "UNKNOWN_NID", + 34359738471u64 => "UNKNOWN_OBJECT_NAME", + 167503724645u64 => "CERTIFICATE_VERIFY_ERROR", + 167503724646u64 => "DIGEST_ERR", + 167503724650u64 => "DIGEST_NAME_ERR", + 167503724651u64 => "DIGEST_SIZE_ERR", + 167503724666u64 => "ERROR_IN_NEXTUPDATE_FIELD", + 167503724667u64 => "ERROR_IN_THISUPDATE_FIELD", + 167503724647u64 => "MISSING_OCSPSIGNING_USAGE", + 167503724668u64 => "NEXTUPDATE_BEFORE_THISUPDATE", + 167503724648u64 => "NOT_BASIC_RESPONSE", + 167503724649u64 => "NO_CERTIFICATES_IN_CHAIN", + 167503724652u64 => "NO_RESPONSE_DATA", + 167503724653u64 => "NO_REVOKED_TIME", + 167503724674u64 => "NO_SIGNER_KEY", + 167503724654u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 167503724672u64 => "REQUEST_NOT_SIGNED", + 167503724655u64 => "RESPONSE_CONTAINS_NO_REVOCATION_DATA", + 167503724656u64 => "ROOT_CA_NOT_TRUSTED", + 167503724661u64 => "SIGNATURE_FAILURE", + 167503724662u64 => "SIGNER_CERTIFICATE_NOT_FOUND", + 167503724669u64 => "STATUS_EXPIRED", + 167503724670u64 => "STATUS_NOT_YET_VALID", + 167503724671u64 => "STATUS_TOO_OLD", + 167503724663u64 => "UNKNOWN_MESSAGE_DIGEST", + 167503724664u64 => "UNKNOWN_NID", + 167503724673u64 => "UNSUPPORTED_REQUESTORNAME_TYPE", + 257698037861u64 => "COULD_NOT_DECODE_OBJECT", + 257698037862u64 => "DECODER_NOT_FOUND", + 257698037860u64 => "MISSING_GET_PARAMS", + 253403070565u64 => "ENCODER_NOT_FOUND", + 253403070564u64 => "INCORRECT_PROPERTY_QUERY", + 253403070566u64 => "MISSING_GET_PARAMS", + 188978561131u64 => "AMBIGUOUS_CONTENT_TYPE", + 188978561139u64 => "BAD_PASSWORD_READ", + 188978561137u64 => "ERROR_VERIFYING_PKCS12_MAC", + 188978561145u64 => "FINGERPRINT_SIZE_DOES_NOT_MATCH_DIGEST", + 188978561130u64 => "INVALID_SCHEME", + 188978561136u64 => "IS_NOT_A", + 188978561140u64 => "LOADER_INCOMPLETE", + 188978561141u64 => "LOADING_STARTED", + 188978561124u64 => "NOT_A_CERTIFICATE", + 188978561125u64 => "NOT_A_CRL", + 188978561127u64 => "NOT_A_NAME", + 188978561126u64 => "NOT_A_PRIVATE_KEY", + 188978561146u64 => "NOT_A_PUBLIC_KEY", + 188978561128u64 => "NOT_PARAMETERS", + 188978561147u64 => "NO_LOADERS_FOUND", + 188978561138u64 => "PASSPHRASE_CALLBACK_ERROR", + 188978561132u64 => "PATH_MUST_BE_ABSOLUTE", + 188978561143u64 => "SEARCH_ONLY_SUPPORTED_FOR_DIRECTORIES", + 188978561133u64 => "UI_PROCESS_INTERRUPTED_OR_CANCELLED", + 188978561129u64 => "UNREGISTERED_SCHEME", + 188978561134u64 => "UNSUPPORTED_CONTENT_TYPE", + 188978561142u64 => "UNSUPPORTED_OPERATION", + 188978561144u64 => "UNSUPPORTED_SEARCH_TYPE", + 188978561135u64 => "URI_AUTHORITY_UNSUPPORTED", + 38654705764u64 => "BAD_BASE64_DECODE", + 38654705765u64 => "BAD_DECRYPT", + 38654705766u64 => "BAD_END_LINE", + 38654705767u64 => "BAD_IV_CHARS", + 38654705780u64 => "BAD_MAGIC_NUMBER", + 38654705768u64 => "BAD_PASSWORD_READ", + 38654705781u64 => "BAD_VERSION_NUMBER", + 38654705782u64 => "BIO_WRITE_FAILURE", + 38654705791u64 => "CIPHER_IS_NULL", + 38654705779u64 => "ERROR_CONVERTING_PRIVATE_KEY", + 38654705795u64 => "EXPECTING_DSS_KEY_BLOB", + 38654705783u64 => "EXPECTING_PRIVATE_KEY_BLOB", + 38654705784u64 => "EXPECTING_PUBLIC_KEY_BLOB", + 38654705796u64 => "EXPECTING_RSA_KEY_BLOB", + 38654705792u64 => "HEADER_TOO_LONG", + 38654705785u64 => "INCONSISTENT_HEADER", + 38654705786u64 => "KEYBLOB_HEADER_PARSE_ERROR", + 38654705787u64 => "KEYBLOB_TOO_SHORT", + 38654705793u64 => "MISSING_DEK_IV", + 38654705769u64 => "NOT_DEK_INFO", + 38654705770u64 => "NOT_ENCRYPTED", + 38654705771u64 => "NOT_PROC_TYPE", + 38654705772u64 => "NO_START_LINE", + 38654705773u64 => "PROBLEMS_GETTING_PASSWORD", + 38654705788u64 => "PVK_DATA_TOO_SHORT", + 38654705789u64 => "PVK_TOO_SHORT", + 38654705775u64 => "READ_KEY", + 38654705776u64 => "SHORT_HEADER", + 38654705794u64 => "UNEXPECTED_DEK_IV", + 38654705777u64 => "UNSUPPORTED_CIPHER", + 38654705778u64 => "UNSUPPORTED_ENCRYPTION", + 38654705790u64 => "UNSUPPORTED_KEY_COMPONENTS", + 38654705774u64 => "UNSUPPORTED_PUBLIC_KEY_TYPE", + 150323855460u64 => "CANT_PACK_STRUCTURE", + 150323855481u64 => "CONTENT_TYPE_NOT_DATA", + 150323855461u64 => "DECODE_ERROR", + 150323855462u64 => "ENCODE_ERROR", + 150323855463u64 => "ENCRYPT_ERROR", + 150323855480u64 => "ERROR_SETTING_ENCRYPTED_DATA_TYPE", + 150323855464u64 => "INVALID_NULL_ARGUMENT", + 150323855465u64 => "INVALID_NULL_PKCS12_POINTER", + 150323855472u64 => "INVALID_TYPE", + 150323855466u64 => "IV_GEN_ERROR", + 150323855467u64 => "KEY_GEN_ERROR", + 150323855468u64 => "MAC_ABSENT", + 150323855469u64 => "MAC_GENERATION_ERROR", + 150323855470u64 => "MAC_SETUP_ERROR", + 150323855471u64 => "MAC_STRING_SET_ERROR", + 150323855473u64 => "MAC_VERIFY_FAILURE", + 150323855474u64 => "PARSE_ERROR", + 150323855476u64 => "PKCS12_CIPHERFINAL_ERROR", + 150323855478u64 => "UNKNOWN_DIGEST_ALGORITHM", + 150323855479u64 => "UNSUPPORTED_PKCS12_MODE", + 141733920885u64 => "CERTIFICATE_VERIFY_ERROR", + 141733920912u64 => "CIPHER_HAS_NO_OBJECT_IDENTIFIER", + 141733920884u64 => "CIPHER_NOT_INITIALIZED", + 141733920886u64 => "CONTENT_AND_DATA_PRESENT", + 141733920920u64 => "CTRL_ERROR", + 141733920887u64 => "DECRYPT_ERROR", + 141733920869u64 => "DIGEST_FAILURE", + 141733920917u64 => "ENCRYPTION_CTRL_FAILURE", + 141733920918u64 => "ENCRYPTION_NOT_SUPPORTED_FOR_THIS_KEY_TYPE", + 141733920888u64 => "ERROR_ADDING_RECIPIENT", + 141733920889u64 => "ERROR_SETTING_CIPHER", + 141733920911u64 => "INVALID_NULL_POINTER", + 141733920923u64 => "INVALID_SIGNED_DATA_TYPE", + 141733920890u64 => "NO_CONTENT", + 141733920919u64 => "NO_DEFAULT_DIGEST", + 141733920922u64 => "NO_MATCHING_DIGEST_TYPE_FOUND", + 141733920883u64 => "NO_RECIPIENT_MATCHES_CERTIFICATE", + 141733920891u64 => "NO_SIGNATURES_ON_DATA", + 141733920910u64 => "NO_SIGNERS", + 141733920872u64 => "OPERATION_NOT_SUPPORTED_ON_THIS_TYPE", + 141733920892u64 => "PKCS7_ADD_SIGNATURE_ERROR", + 141733920921u64 => "PKCS7_ADD_SIGNER_ERROR", + 141733920913u64 => "PKCS7_DATASIGN", + 141733920895u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 141733920873u64 => "SIGNATURE_FAILURE", + 141733920896u64 => "SIGNER_CERTIFICATE_NOT_FOUND", + 141733920915u64 => "SIGNING_CTRL_FAILURE", + 141733920916u64 => "SIGNING_NOT_SUPPORTED_FOR_THIS_KEY_TYPE", + 141733920897u64 => "SMIME_TEXT_ERROR", + 141733920874u64 => "UNABLE_TO_FIND_CERTIFICATE", + 141733920875u64 => "UNABLE_TO_FIND_MEM_BIO", + 141733920876u64 => "UNABLE_TO_FIND_MESSAGE_DIGEST", + 141733920877u64 => "UNKNOWN_DIGEST_TYPE", + 141733920878u64 => "UNKNOWN_OPERATION", + 141733920879u64 => "UNSUPPORTED_CIPHER_TYPE", + 141733920880u64 => "UNSUPPORTED_CONTENT_TYPE", + 141733920881u64 => "WRONG_CONTENT_TYPE", + 141733920882u64 => "WRONG_PKCS7_TYPE", + 236223201380u64 => "NAME_TOO_LONG", + 236223201381u64 => "NOT_AN_ASCII_CHARACTER", + 236223201382u64 => "NOT_AN_HEXADECIMAL_DIGIT", + 236223201383u64 => "NOT_AN_IDENTIFIER", + 236223201384u64 => "NOT_AN_OCTAL_DIGIT", + 236223201385u64 => "NOT_A_DECIMAL_DIGIT", + 236223201386u64 => "NO_MATCHING_STRING_DELIMITER", + 236223201387u64 => "NO_VALUE", + 236223201388u64 => "PARSE_FAILED", + 236223201389u64 => "STRING_TOO_LONG", + 236223201390u64 => "TRAILING_CHARACTERS", + 244813136056u64 => "ADDITIONAL_INPUT_TOO_LONG", + 244813136045u64 => "ALGORITHM_MISMATCH", + 244813136057u64 => "ALREADY_INSTANTIATED", + 244813135972u64 => "BAD_DECRYPT", + 244813136013u64 => "BAD_ENCODING", + 244813136014u64 => "BAD_LENGTH", + 244813136033u64 => "BAD_TLS_CLIENT_VERSION", + 244813136032u64 => "BN_ERROR", + 244813135974u64 => "CIPHER_OPERATION_FAILED", + 244813136077u64 => "DERIVATION_FUNCTION_INIT_FAILED", + 244813136046u64 => "DIGEST_NOT_ALLOWED", + 244813136058u64 => "ENTROPY_SOURCE_STRENGTH_TOO_WEAK", + 244813136060u64 => "ERROR_INSTANTIATING_DRBG", + 244813136061u64 => "ERROR_RETRIEVING_ENTROPY", + 244813136062u64 => "ERROR_RETRIEVING_NONCE", + 244813136036u64 => "FAILED_DURING_DERIVATION", + 244813136052u64 => "FAILED_TO_CREATE_LOCK", + 244813136034u64 => "FAILED_TO_DECRYPT", + 244813135993u64 => "FAILED_TO_GENERATE_KEY", + 244813135975u64 => "FAILED_TO_GET_PARAMETER", + 244813135976u64 => "FAILED_TO_SET_PARAMETER", + 244813136047u64 => "FAILED_TO_SIGN", + 244813136099u64 => "FIPS_MODULE_CONDITIONAL_ERROR", + 244813136096u64 => "FIPS_MODULE_ENTERING_ERROR_STATE", + 244813136097u64 => "FIPS_MODULE_IN_ERROR_STATE", + 244813136063u64 => "GENERATE_ERROR", + 244813136037u64 => "ILLEGAL_OR_UNSUPPORTED_PADDING_MODE", + 244813136082u64 => "INDICATOR_INTEGRITY_FAILURE", + 244813136053u64 => "INSUFFICIENT_DRBG_STRENGTH", + 244813135980u64 => "INVALID_AAD", + 244813136083u64 => "INVALID_CONFIG_DATA", + 244813136029u64 => "INVALID_CONSTANT_LENGTH", + 244813136048u64 => "INVALID_CURVE", + 244813135983u64 => "INVALID_CUSTOM_LENGTH", + 244813135987u64 => "INVALID_DATA", + 244813135994u64 => "INVALID_DIGEST", + 244813136038u64 => "INVALID_DIGEST_LENGTH", + 244813136090u64 => "INVALID_DIGEST_SIZE", + 244813136102u64 => "INVALID_INPUT_LENGTH", + 244813135995u64 => "INVALID_ITERATION_COUNT", + 244813135981u64 => "INVALID_IV_LENGTH", + 244813136030u64 => "INVALID_KEY", + 244813135977u64 => "INVALID_KEY_LENGTH", + 244813136023u64 => "INVALID_MAC", + 244813136039u64 => "INVALID_MGF1_MD", + 244813135997u64 => "INVALID_MODE", + 244813136089u64 => "INVALID_OUTPUT_LENGTH", + 244813136040u64 => "INVALID_PADDING_MODE", + 244813136070u64 => "INVALID_PUBINFO", + 244813135984u64 => "INVALID_SALT_LENGTH", + 244813136026u64 => "INVALID_SEED_LENGTH", + 244813136051u64 => "INVALID_SIGNATURE_SIZE", + 244813136084u64 => "INVALID_STATE", + 244813135982u64 => "INVALID_TAG", + 244813135990u64 => "INVALID_TAG_LENGTH", + 244813136072u64 => "INVALID_UKM_LENGTH", + 244813136042u64 => "INVALID_X931_DIGEST", + 244813136064u64 => "IN_ERROR_STATE", + 244813135973u64 => "KEY_SETUP_FAILED", + 244813136043u64 => "KEY_SIZE_TOO_SMALL", + 244813136074u64 => "LENGTH_TOO_LARGE", + 244813136075u64 => "MISMATCHING_DOMAIN_PARAMETERS", + 244813136016u64 => "MISSING_CEK_ALG", + 244813136027u64 => "MISSING_CIPHER", + 244813136085u64 => "MISSING_CONFIG_DATA", + 244813136028u64 => "MISSING_CONSTANT", + 244813136000u64 => "MISSING_KEY", + 244813136022u64 => "MISSING_MAC", + 244813136001u64 => "MISSING_MESSAGE_DIGEST", + 244813136081u64 => "MISSING_OID", + 244813136002u64 => "MISSING_PASS", + 244813136003u64 => "MISSING_SALT", + 244813136004u64 => "MISSING_SECRET", + 244813136012u64 => "MISSING_SEED", + 244813136005u64 => "MISSING_SESSION_ID", + 244813136006u64 => "MISSING_TYPE", + 244813136007u64 => "MISSING_XCGHASH", + 244813136086u64 => "MODULE_INTEGRITY_FAILURE", + 244813136093u64 => "NOT_A_PRIVATE_KEY", + 244813136092u64 => "NOT_A_PUBLIC_KEY", + 244813136065u64 => "NOT_INSTANTIATED", + 244813136098u64 => "NOT_PARAMETERS", + 244813136008u64 => "NOT_SUPPORTED", + 244813135985u64 => "NOT_XOF_OR_INVALID_LENGTH", + 244813135986u64 => "NO_KEY_SET", + 244813136049u64 => "NO_PARAMETERS_SET", + 244813136050u64 => "OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE", + 244813135978u64 => "OUTPUT_BUFFER_TOO_SMALL", + 244813136100u64 => "PARENT_CANNOT_GENERATE_RANDOM_NUMBERS", + 244813136059u64 => "PARENT_CANNOT_SUPPLY_ENTROPY_SEED", + 244813136054u64 => "PARENT_LOCKING_NOT_ENABLED", + 244813136066u64 => "PARENT_STRENGTH_TOO_WEAK", + 244813136091u64 => "PATH_MUST_BE_ABSOLUTE", + 244813136067u64 => "PERSONALISATION_STRING_TOO_LONG", + 244813136044u64 => "PSS_SALTLEN_TOO_SMALL", + 244813136068u64 => "REQUEST_TOO_LARGE_FOR_DRBG", + 244813136078u64 => "REQUIRE_CTR_MODE_CIPHER", + 244813136069u64 => "RESEED_ERROR", + 244813136094u64 => "SEARCH_ONLY_SUPPORTED_FOR_DIRECTORIES", + 244813136101u64 => "SEED_SOURCES_MUST_NOT_HAVE_A_PARENT", + 244813136087u64 => "SELF_TEST_KAT_FAILURE", + 244813136088u64 => "SELF_TEST_POST_FAILURE", + 244813135992u64 => "TAG_NOT_NEEDED", + 244813135991u64 => "TAG_NOT_SET", + 244813135998u64 => "TOO_MANY_RECORDS", + 244813136079u64 => "UNABLE_TO_FIND_CIPHERS", + 244813136071u64 => "UNABLE_TO_GET_PARENT_STRENGTH", + 244813136031u64 => "UNABLE_TO_GET_PASSPHRASE", + 244813136080u64 => "UNABLE_TO_INITIALISE_CIPHERS", + 244813136019u64 => "UNABLE_TO_LOAD_SHA256", + 244813136073u64 => "UNABLE_TO_LOCK_PARENT", + 244813136076u64 => "UNABLE_TO_RESEED", + 244813136017u64 => "UNSUPPORTED_CEK_ALG", + 244813136025u64 => "UNSUPPORTED_KEY_SIZE", + 244813136009u64 => "UNSUPPORTED_MAC_TYPE", + 244813136024u64 => "UNSUPPORTED_NUMBER_OF_ROUNDS", + 244813136095u64 => "URI_AUTHORITY_UNSUPPORTED", + 244813136010u64 => "VALUE_ERROR", + 244813135979u64 => "WRONG_FINAL_BLOCK_LENGTH", + 244813136011u64 => "WRONG_OUTPUT_BUFFER_SIZE", + 244813136055u64 => "XOF_DIGESTS_NOT_ALLOWED", + 244813136020u64 => "XTS_DATA_UNIT_IS_TOO_LARGE", + 244813136021u64 => "XTS_DUPLICATED_KEYS", + 154618822758u64 => "ADDITIONAL_INPUT_TOO_LONG", + 154618822759u64 => "ALREADY_INSTANTIATED", + 154618822761u64 => "ARGUMENT_OUT_OF_RANGE", + 154618822777u64 => "CANNOT_OPEN_FILE", + 154618822785u64 => "DRBG_ALREADY_INITIALIZED", + 154618822760u64 => "DRBG_NOT_INITIALISED", + 154618822762u64 => "ENTROPY_INPUT_TOO_LONG", + 154618822780u64 => "ENTROPY_OUT_OF_RANGE", + 154618822783u64 => "ERROR_ENTROPY_POOL_WAS_IGNORED", + 154618822763u64 => "ERROR_INITIALISING_DRBG", + 154618822764u64 => "ERROR_INSTANTIATING_DRBG", + 154618822765u64 => "ERROR_RETRIEVING_ADDITIONAL_INPUT", + 154618822766u64 => "ERROR_RETRIEVING_ENTROPY", + 154618822767u64 => "ERROR_RETRIEVING_NONCE", + 154618822782u64 => "FAILED_TO_CREATE_LOCK", + 154618822757u64 => "FUNC_NOT_IMPLEMENTED", + 154618822779u64 => "FWRITE_ERROR", + 154618822768u64 => "GENERATE_ERROR", + 154618822795u64 => "INSUFFICIENT_DRBG_STRENGTH", + 154618822769u64 => "INTERNAL_ERROR", + 154618822770u64 => "IN_ERROR_STATE", + 154618822778u64 => "NOT_A_REGULAR_FILE", + 154618822771u64 => "NOT_INSTANTIATED", + 154618822784u64 => "NO_DRBG_IMPLEMENTATION_SELECTED", + 154618822786u64 => "PARENT_LOCKING_NOT_ENABLED", + 154618822787u64 => "PARENT_STRENGTH_TOO_WEAK", + 154618822772u64 => "PERSONALISATION_STRING_TOO_LONG", + 154618822789u64 => "PREDICTION_RESISTANCE_NOT_SUPPORTED", + 154618822756u64 => "PRNG_NOT_SEEDED", + 154618822781u64 => "RANDOM_POOL_OVERFLOW", + 154618822790u64 => "RANDOM_POOL_UNDERFLOW", + 154618822773u64 => "REQUEST_TOO_LARGE_FOR_DRBG", + 154618822774u64 => "RESEED_ERROR", + 154618822775u64 => "SELFTEST_FAILURE", + 154618822791u64 => "TOO_LITTLE_NONCE_REQUESTED", + 154618822792u64 => "TOO_MUCH_NONCE_REQUESTED", + 154618822799u64 => "UNABLE_TO_CREATE_DRBG", + 154618822800u64 => "UNABLE_TO_FETCH_DRBG", + 154618822797u64 => "UNABLE_TO_GET_PARENT_RESEED_PROP_COUNTER", + 154618822794u64 => "UNABLE_TO_GET_PARENT_STRENGTH", + 154618822796u64 => "UNABLE_TO_LOCK_PARENT", + 154618822788u64 => "UNSUPPORTED_DRBG_FLAGS", + 154618822776u64 => "UNSUPPORTED_DRBG_TYPE", + 17179869284u64 => "ALGORITHM_MISMATCH", + 17179869285u64 => "BAD_E_VALUE", + 17179869286u64 => "BAD_FIXED_HEADER_DECRYPT", + 17179869287u64 => "BAD_PAD_BYTE_COUNT", + 17179869288u64 => "BAD_SIGNATURE", + 17179869290u64 => "BLOCK_TYPE_IS_NOT_01", + 17179869291u64 => "BLOCK_TYPE_IS_NOT_02", + 17179869292u64 => "DATA_GREATER_THAN_MOD_LEN", + 17179869293u64 => "DATA_TOO_LARGE", + 17179869294u64 => "DATA_TOO_LARGE_FOR_KEY_SIZE", + 17179869316u64 => "DATA_TOO_LARGE_FOR_MODULUS", + 17179869295u64 => "DATA_TOO_SMALL", + 17179869306u64 => "DATA_TOO_SMALL_FOR_KEY_SIZE", + 17179869342u64 => "DIGEST_DOES_NOT_MATCH", + 17179869329u64 => "DIGEST_NOT_ALLOWED", + 17179869296u64 => "DIGEST_TOO_BIG_FOR_RSA_KEY", + 17179869308u64 => "DMP1_NOT_CONGRUENT_TO_D", + 17179869309u64 => "DMQ1_NOT_CONGRUENT_TO_D", + 17179869307u64 => "D_E_NOT_CONGRUENT_TO_1", + 17179869317u64 => "FIRST_OCTET_INVALID", + 17179869328u64 => "ILLEGAL_OR_UNSUPPORTED_PADDING_MODE", + 17179869341u64 => "INVALID_DIGEST", + 17179869327u64 => "INVALID_DIGEST_LENGTH", + 17179869321u64 => "INVALID_HEADER", + 17179869355u64 => "INVALID_KEYPAIR", + 17179869357u64 => "INVALID_KEY_LENGTH", + 17179869344u64 => "INVALID_LABEL", + 17179869365u64 => "INVALID_LENGTH", + 17179869315u64 => "INVALID_MESSAGE_LENGTH", + 17179869340u64 => "INVALID_MGF1_MD", + 17179869358u64 => "INVALID_MODULUS", + 17179869351u64 => "INVALID_MULTI_PRIME_KEY", + 17179869345u64 => "INVALID_OAEP_PARAMETERS", + 17179869322u64 => "INVALID_PADDING", + 17179869325u64 => "INVALID_PADDING_MODE", + 17179869333u64 => "INVALID_PSS_PARAMETERS", + 17179869330u64 => "INVALID_PSS_SALTLEN", + 17179869359u64 => "INVALID_REQUEST", + 17179869334u64 => "INVALID_SALT_LENGTH", + 17179869360u64 => "INVALID_STRENGTH", + 17179869323u64 => "INVALID_TRAILER", + 17179869326u64 => "INVALID_X931_DIGEST", + 17179869310u64 => "IQMP_NOT_INVERSE_OF_Q", + 17179869349u64 => "KEY_PRIME_NUM_INVALID", + 17179869304u64 => "KEY_SIZE_TOO_SMALL", + 17179869318u64 => "LAST_OCTET_INVALID", + 17179869336u64 => "MGF1_DIGEST_NOT_ALLOWED", + 17179869363u64 => "MISSING_PRIVATE_KEY", + 17179869289u64 => "MODULUS_TOO_LARGE", + 17179869352u64 => "MP_COEFFICIENT_NOT_INVERSE_OF_R", + 17179869353u64 => "MP_EXPONENT_NOT_CONGRUENT_TO_D", + 17179869354u64 => "MP_R_NOT_PRIME", + 17179869324u64 => "NO_PUBLIC_EXPONENT", + 17179869297u64 => "NULL_BEFORE_BLOCK_MISSING", + 17179869356u64 => "N_DOES_NOT_EQUAL_PRODUCT_OF_PRIMES", + 17179869311u64 => "N_DOES_NOT_EQUAL_P_Q", + 17179869305u64 => "OAEP_DECODING_ERROR", + 17179869332u64 => "OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE", + 17179869298u64 => "PADDING_CHECK_FAILED", + 17179869361u64 => "PAIRWISE_TEST_FAILURE", + 17179869343u64 => "PKCS_DECODING_ERROR", + 17179869348u64 => "PSS_SALTLEN_TOO_SMALL", + 17179869362u64 => "PUB_EXPONENT_OUT_OF_RANGE", + 17179869312u64 => "P_NOT_PRIME", + 17179869313u64 => "Q_NOT_PRIME", + 17179869364u64 => "RANDOMNESS_SOURCE_STRENGTH_INSUFFICIENT", + 17179869314u64 => "RSA_OPERATIONS_NOT_SUPPORTED", + 17179869320u64 => "SLEN_CHECK_FAILED", + 17179869319u64 => "SLEN_RECOVERY_FAILED", + 17179869299u64 => "SSLV3_ROLLBACK_ATTACK", + 17179869300u64 => "THE_ASN1_OBJECT_IDENTIFIER_IS_NOT_KNOWN_FOR_THIS_MD", + 17179869301u64 => "UNKNOWN_ALGORITHM_TYPE", + 17179869350u64 => "UNKNOWN_DIGEST", + 17179869335u64 => "UNKNOWN_MASK_DIGEST", + 17179869302u64 => "UNKNOWN_PADDING_TYPE", + 17179869346u64 => "UNSUPPORTED_ENCRYPTION_TYPE", + 17179869347u64 => "UNSUPPORTED_LABEL_SOURCE", + 17179869337u64 => "UNSUPPORTED_MASK_ALGORITHM", + 17179869338u64 => "UNSUPPORTED_MASK_PARAMETER", + 17179869339u64 => "UNSUPPORTED_SIGNATURE_TYPE", + 17179869331u64 => "VALUE_MISSING", + 17179869303u64 => "WRONG_SIGNATURE_LENGTH", + 227633266788u64 => "ASN1_ERROR", + 227633266789u64 => "BAD_SIGNATURE", + 227633266795u64 => "BUFFER_TOO_SMALL", + 227633266798u64 => "DIST_ID_TOO_LARGE", + 227633266800u64 => "ID_NOT_SET", + 227633266799u64 => "ID_TOO_LARGE", + 227633266796u64 => "INVALID_CURVE", + 227633266790u64 => "INVALID_DIGEST", + 227633266791u64 => "INVALID_DIGEST_TYPE", + 227633266792u64 => "INVALID_ENCODING", + 227633266793u64 => "INVALID_FIELD", + 227633266801u64 => "INVALID_PRIVATE_KEY", + 227633266797u64 => "NO_PARAMETERS_SET", + 227633266794u64 => "USER_ID_TOO_LARGE", + 85899346211u64 => "APPLICATION_DATA_AFTER_CLOSE_NOTIFY", + 85899346020u64 => "APP_DATA_IN_HANDSHAKE", + 85899346192u64 => "ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT", + 85899346078u64 => "AT_LEAST_TLS_1_2_NEEDED_IN_SUITEB_MODE", + 85899346023u64 => "BAD_CHANGE_CIPHER_SPEC", + 85899346106u64 => "BAD_CIPHER", + 85899346310u64 => "BAD_DATA", + 85899346026u64 => "BAD_DATA_RETURNED_BY_CALLBACK", + 85899346027u64 => "BAD_DECOMPRESSION", + 85899346022u64 => "BAD_DH_VALUE", + 85899346031u64 => "BAD_DIGEST_LENGTH", + 85899346153u64 => "BAD_EARLY_DATA", + 85899346224u64 => "BAD_ECC_CERT", + 85899346226u64 => "BAD_ECPOINT", + 85899346030u64 => "BAD_EXTENSION", + 85899346252u64 => "BAD_HANDSHAKE_LENGTH", + 85899346156u64 => "BAD_HANDSHAKE_STATE", + 85899346025u64 => "BAD_HELLO_REQUEST", + 85899346183u64 => "BAD_HRR_VERSION", + 85899346028u64 => "BAD_KEY_SHARE", + 85899346042u64 => "BAD_KEY_UPDATE", + 85899346212u64 => "BAD_LEGACY_VERSION", + 85899346191u64 => "BAD_LENGTH", + 85899346160u64 => "BAD_PACKET", + 85899346035u64 => "BAD_PACKET_LENGTH", + 85899346036u64 => "BAD_PROTOCOL_VERSION_NUMBER", + 85899346139u64 => "BAD_PSK", + 85899346034u64 => "BAD_PSK_IDENTITY", + 85899346363u64 => "BAD_RECORD_TYPE", + 85899346039u64 => "BAD_RSA_ENCRYPT", + 85899346043u64 => "BAD_SIGNATURE", + 85899346267u64 => "BAD_SRP_A_LENGTH", + 85899346291u64 => "BAD_SRP_PARAMETERS", + 85899346272u64 => "BAD_SRTP_MKI_VALUE", + 85899346273u64 => "BAD_SRTP_PROTECTION_PROFILE_LIST", + 85899346044u64 => "BAD_SSL_FILETYPE", + 85899346304u64 => "BAD_VALUE", + 85899346047u64 => "BAD_WRITE_RETRY", + 85899346173u64 => "BINDER_DOES_NOT_VERIFY", + 85899346048u64 => "BIO_NOT_SET", + 85899346049u64 => "BLOCK_CIPHER_PAD_IS_WRONG", + 85899346050u64 => "BN_LIB", + 85899346154u64 => "CALLBACK_FAILED", + 85899346029u64 => "CANNOT_CHANGE_CIPHER", + 85899346219u64 => "CANNOT_GET_GROUP_NAME", + 85899346051u64 => "CA_DN_LENGTH_MISMATCH", + 85899346317u64 => "CA_KEY_TOO_SMALL", + 85899346318u64 => "CA_MD_TOO_WEAK", + 85899346053u64 => "CCS_RECEIVED_EARLY", + 85899346054u64 => "CERTIFICATE_VERIFY_FAILED", + 85899346297u64 => "CERT_CB_ERROR", + 85899346055u64 => "CERT_LENGTH_MISMATCH", + 85899346138u64 => "CIPHERSUITE_DIGEST_HAS_CHANGED", + 85899346057u64 => "CIPHER_CODE_WRONG_LENGTH", + 85899346146u64 => "CLIENTHELLO_TLSEXT", + 85899346060u64 => "COMPRESSED_LENGTH_TOO_LONG", + 85899346263u64 => "COMPRESSION_DISABLED", + 85899346061u64 => "COMPRESSION_FAILURE", + 85899346227u64 => "COMPRESSION_ID_NOT_WITHIN_PRIVATE_RANGE", + 85899346062u64 => "COMPRESSION_LIBRARY_ERROR", + 85899346064u64 => "CONNECTION_TYPE_NOT_SET", + 85899346087u64 => "CONTEXT_NOT_DANE_ENABLED", + 85899346320u64 => "COOKIE_GEN_CALLBACK_FAILURE", + 85899346228u64 => "COOKIE_MISMATCH", + 85899346216u64 => "COPY_PARAMETERS_FAILED", + 85899346126u64 => "CUSTOM_EXT_HANDLER_ALREADY_INSTALLED", + 85899346092u64 => "DANE_ALREADY_ENABLED", + 85899346093u64 => "DANE_CANNOT_OVERRIDE_MTYPE_FULL", + 85899346095u64 => "DANE_NOT_ENABLED", + 85899346100u64 => "DANE_TLSA_BAD_CERTIFICATE", + 85899346104u64 => "DANE_TLSA_BAD_CERTIFICATE_USAGE", + 85899346109u64 => "DANE_TLSA_BAD_DATA_LENGTH", + 85899346112u64 => "DANE_TLSA_BAD_DIGEST_LENGTH", + 85899346120u64 => "DANE_TLSA_BAD_MATCHING_TYPE", + 85899346121u64 => "DANE_TLSA_BAD_PUBLIC_KEY", + 85899346122u64 => "DANE_TLSA_BAD_SELECTOR", + 85899346123u64 => "DANE_TLSA_NULL_DATA", + 85899346065u64 => "DATA_BETWEEN_CCS_AND_FINISHED", + 85899346066u64 => "DATA_LENGTH_TOO_LONG", + 85899346067u64 => "DECRYPTION_FAILED", + 85899346201u64 => "DECRYPTION_FAILED_OR_BAD_RECORD_MAC", + 85899346314u64 => "DH_KEY_TOO_SMALL", + 85899346068u64 => "DH_PUBLIC_VALUE_LENGTH_IS_WRONG", + 85899346069u64 => "DIGEST_CHECK_FAILED", + 85899346254u64 => "DTLS_MESSAGE_TOO_BIG", + 85899346229u64 => "DUPLICATE_COMPRESSION_ID", + 85899346238u64 => "ECC_CERT_NOT_FOR_SIGNING", + 85899346294u64 => "ECDH_REQUIRED_FOR_SUITEB_MODE", + 85899346319u64 => "EE_KEY_TOO_SMALL", + 85899346274u64 => "EMPTY_SRTP_PROTECTION_PROFILE_LIST", + 85899346070u64 => "ENCRYPTED_LENGTH_TOO_LONG", + 85899346071u64 => "ERROR_IN_RECEIVED_CIPHER_LIST", + 85899346124u64 => "ERROR_SETTING_TLSA_BASE_DOMAIN", + 85899346114u64 => "EXCEEDS_MAX_FRAGMENT_SIZE", + 85899346072u64 => "EXCESSIVE_MESSAGE_SIZE", + 85899346199u64 => "EXTENSION_NOT_RECEIVED", + 85899346073u64 => "EXTRA_DATA_IN_MESSAGE", + 85899346083u64 => "EXT_LENGTH_MISMATCH", + 85899346325u64 => "FAILED_TO_INIT_ASYNC", + 85899346321u64 => "FRAGMENTED_CLIENT_HELLO", + 85899346074u64 => "GOT_A_FIN_BEFORE_A_CCS", + 85899346075u64 => "HTTPS_PROXY_REQUEST", + 85899346076u64 => "HTTP_REQUEST", + 85899346082u64 => "ILLEGAL_POINT_COMPRESSION", + 85899346300u64 => "ILLEGAL_SUITEB_DIGEST", + 85899346293u64 => "INAPPROPRIATE_FALLBACK", + 85899346260u64 => "INCONSISTENT_COMPRESSION", + 85899346142u64 => "INCONSISTENT_EARLY_DATA_ALPN", + 85899346151u64 => "INCONSISTENT_EARLY_DATA_SNI", + 85899346024u64 => "INCONSISTENT_EXTMS", + 85899346161u64 => "INSUFFICIENT_SECURITY", + 85899346125u64 => "INVALID_ALERT", + 85899346180u64 => "INVALID_CCS_MESSAGE", + 85899346158u64 => "INVALID_CERTIFICATE_OR_ALG", + 85899346200u64 => "INVALID_COMMAND", + 85899346261u64 => "INVALID_COMPRESSION_ALGORITHM", + 85899346203u64 => "INVALID_CONFIG", + 85899346033u64 => "INVALID_CONFIGURATION_NAME", + 85899346202u64 => "INVALID_CONTEXT", + 85899346132u64 => "INVALID_CT_VALIDATION_TYPE", + 85899346040u64 => "INVALID_KEY_UPDATE_TYPE", + 85899346094u64 => "INVALID_MAX_EARLY_DATA", + 85899346305u64 => "INVALID_NULL_CMD_NAME", + 85899346322u64 => "INVALID_SEQUENCE_NUMBER", + 85899346308u64 => "INVALID_SERVERINFO_DATA", + 85899346919u64 => "INVALID_SESSION_ID", + 85899346277u64 => "INVALID_SRP_USERNAME", + 85899346248u64 => "INVALID_STATUS_RESPONSE", + 85899346245u64 => "INVALID_TICKET_KEYS_LENGTH", + 85899346253u64 => "LEGACY_SIGALG_DISALLOWED_OR_UNSUPPORTED", + 85899346079u64 => "LENGTH_MISMATCH", + 85899346324u64 => "LENGTH_TOO_LONG", + 85899346080u64 => "LENGTH_TOO_SHORT", + 85899346194u64 => "LIBRARY_BUG", + 85899346081u64 => "LIBRARY_HAS_NO_CIPHERS", + 85899346085u64 => "MISSING_DSA_SIGNING_CERT", + 85899346301u64 => "MISSING_ECDSA_SIGNING_CERT", + 85899346176u64 => "MISSING_FATAL", + 85899346210u64 => "MISSING_PARAMETERS", + 85899346230u64 => "MISSING_PSK_KEX_MODES_EXTENSION", + 85899346088u64 => "MISSING_RSA_CERTIFICATE", + 85899346089u64 => "MISSING_RSA_ENCRYPTING_CERT", + 85899346090u64 => "MISSING_RSA_SIGNING_CERT", + 85899346032u64 => "MISSING_SIGALGS_EXTENSION", + 85899346141u64 => "MISSING_SIGNING_CERT", + 85899346278u64 => "MISSING_SRP_PARAM", + 85899346129u64 => "MISSING_SUPPORTED_GROUPS_EXTENSION", + 85899346091u64 => "MISSING_TMP_DH_KEY", + 85899346231u64 => "MISSING_TMP_ECDH_KEY", + 85899346213u64 => "MIXED_HANDSHAKE_AND_NON_HANDSHAKE_DATA", + 85899346102u64 => "NOT_ON_RECORD_BOUNDARY", + 85899346209u64 => "NOT_REPLACING_CERTIFICATE", + 85899346204u64 => "NOT_SERVER", + 85899346155u64 => "NO_APPLICATION_PROTOCOL", + 85899346096u64 => "NO_CERTIFICATES_RETURNED", + 85899346097u64 => "NO_CERTIFICATE_ASSIGNED", + 85899346099u64 => "NO_CERTIFICATE_SET", + 85899346134u64 => "NO_CHANGE_FOLLOWING_HRR", + 85899346101u64 => "NO_CIPHERS_AVAILABLE", + 85899346103u64 => "NO_CIPHERS_SPECIFIED", + 85899346105u64 => "NO_CIPHER_MATCH", + 85899346251u64 => "NO_CLIENT_CERT_METHOD", + 85899346107u64 => "NO_COMPRESSION_SPECIFIED", + 85899346207u64 => "NO_COOKIE_CALLBACK_SET", + 85899346250u64 => "NO_GOST_CERTIFICATE_SENT_BY_PEER", + 85899346108u64 => "NO_METHOD_SPECIFIED", + 85899346309u64 => "NO_PEM_EXTENSIONS", + 85899346110u64 => "NO_PRIVATE_KEY_ASSIGNED", + 85899346111u64 => "NO_PROTOCOLS_AVAILABLE", + 85899346259u64 => "NO_RENEGOTIATION", + 85899346244u64 => "NO_REQUIRED_DIGEST", + 85899346113u64 => "NO_SHARED_CIPHER", + 85899346330u64 => "NO_SHARED_GROUPS", + 85899346296u64 => "NO_SHARED_SIGNATURE_ALGORITHMS", + 85899346279u64 => "NO_SRTP_PROFILES", + 85899346217u64 => "NO_SUITABLE_DIGEST_ALGORITHM", + 85899346215u64 => "NO_SUITABLE_GROUPS", + 85899346021u64 => "NO_SUITABLE_KEY_SHARE", + 85899346038u64 => "NO_SUITABLE_SIGNATURE_ALGORITHM", + 85899346136u64 => "NO_VALID_SCTS", + 85899346323u64 => "NO_VERIFY_COOKIE_CALLBACK", + 85899346115u64 => "NULL_SSL_CTX", + 85899346116u64 => "NULL_SSL_METHOD_PASSED", + 85899346225u64 => "OCSP_CALLBACK_FAILURE", + 85899346117u64 => "OLD_SESSION_CIPHER_NOT_RETURNED", + 85899346264u64 => "OLD_SESSION_COMPRESSION_ALGORITHM_NOT_RETURNED", + 85899346157u64 => "OVERFLOW_ERROR", + 85899346118u64 => "PACKET_LENGTH_TOO_LONG", + 85899346147u64 => "PARSE_TLSEXT", + 85899346190u64 => "PATH_TOO_LONG", + 85899346119u64 => "PEER_DID_NOT_RETURN_A_CERTIFICATE", + 85899346311u64 => "PEM_NAME_BAD_PREFIX", + 85899346312u64 => "PEM_NAME_TOO_SHORT", + 85899346326u64 => "PIPELINE_FAILURE", + 85899346198u64 => "POST_HANDSHAKE_AUTH_ENCODING_ERR", + 85899346208u64 => "PRIVATE_KEY_MISMATCH", + 85899346127u64 => "PROTOCOL_IS_SHUTDOWN", + 85899346143u64 => "PSK_IDENTITY_NOT_FOUND", + 85899346144u64 => "PSK_NO_CLIENT_CB", + 85899346145u64 => "PSK_NO_SERVER_CB", + 85899346131u64 => "READ_BIO_NOT_SET", + 85899346232u64 => "READ_TIMEOUT_EXPIRED", + 85899346133u64 => "RECORD_LENGTH_MISMATCH", + 85899346218u64 => "RECORD_TOO_SMALL", + 85899346255u64 => "RENEGOTIATE_EXT_TOO_LONG", + 85899346256u64 => "RENEGOTIATION_ENCODING_ERR", + 85899346257u64 => "RENEGOTIATION_MISMATCH", + 85899346205u64 => "REQUEST_PENDING", + 85899346206u64 => "REQUEST_SENT", + 85899346135u64 => "REQUIRED_CIPHER_MISSING", + 85899346262u64 => "REQUIRED_COMPRESSION_ALGORITHM_MISSING", + 85899346265u64 => "SCSV_RECEIVED_WHEN_RENEGOTIATING", + 85899346128u64 => "SCT_VERIFICATION_FAILED", + 85899346195u64 => "SERVERHELLO_TLSEXT", + 85899346197u64 => "SESSION_ID_CONTEXT_UNINITIALIZED", + 85899346327u64 => "SHUTDOWN_WHILE_IN_INIT", + 85899346280u64 => "SIGNATURE_ALGORITHMS_ERROR", + 85899346140u64 => "SIGNATURE_FOR_NON_SIGNING_CERTIFICATE", + 85899346281u64 => "SRP_A_CALC", + 85899346282u64 => "SRTP_COULD_NOT_ALLOCATE_PROFILES", + 85899346283u64 => "SRTP_PROTECTION_PROFILE_LIST_TOO_LONG", + 85899346284u64 => "SRTP_UNKNOWN_PROTECTION_PROFILE", + 85899346152u64 => "SSL3_EXT_INVALID_MAX_FRAGMENT_LENGTH", + 85899346239u64 => "SSL3_EXT_INVALID_SERVERNAME", + 85899346240u64 => "SSL3_EXT_INVALID_SERVERNAME_TYPE", + 85899346220u64 => "SSL3_SESSION_ID_TOO_LONG", + 85899346962u64 => "SSLV3_ALERT_BAD_CERTIFICATE", + 85899346940u64 => "SSLV3_ALERT_BAD_RECORD_MAC", + 85899346965u64 => "SSLV3_ALERT_CERTIFICATE_EXPIRED", + 85899346964u64 => "SSLV3_ALERT_CERTIFICATE_REVOKED", + 85899346966u64 => "SSLV3_ALERT_CERTIFICATE_UNKNOWN", + 85899346950u64 => "SSLV3_ALERT_DECOMPRESSION_FAILURE", + 85899346960u64 => "SSLV3_ALERT_HANDSHAKE_FAILURE", + 85899346967u64 => "SSLV3_ALERT_ILLEGAL_PARAMETER", + 85899346961u64 => "SSLV3_ALERT_NO_CERTIFICATE", + 85899346930u64 => "SSLV3_ALERT_UNEXPECTED_MESSAGE", + 85899346963u64 => "SSLV3_ALERT_UNSUPPORTED_CERTIFICATE", + 85899346037u64 => "SSL_COMMAND_SECTION_EMPTY", + 85899346045u64 => "SSL_COMMAND_SECTION_NOT_FOUND", + 85899346148u64 => "SSL_CTX_HAS_NO_DEFAULT_SSL_VERSION", + 85899346149u64 => "SSL_HANDSHAKE_FAILURE", + 85899346150u64 => "SSL_LIBRARY_HAS_NO_CIPHERS", + 85899346292u64 => "SSL_NEGATIVE_LENGTH", + 85899346046u64 => "SSL_SECTION_EMPTY", + 85899346056u64 => "SSL_SECTION_NOT_FOUND", + 85899346221u64 => "SSL_SESSION_ID_CALLBACK_FAILED", + 85899346222u64 => "SSL_SESSION_ID_CONFLICT", + 85899346193u64 => "SSL_SESSION_ID_CONTEXT_TOO_LONG", + 85899346223u64 => "SSL_SESSION_ID_HAS_BAD_LENGTH", + 85899346328u64 => "SSL_SESSION_ID_TOO_LONG", + 85899346130u64 => "SSL_SESSION_VERSION_MISMATCH", + 85899346041u64 => "STILL_IN_INIT", + 85899347036u64 => "TLSV13_ALERT_CERTIFICATE_REQUIRED", + 85899347029u64 => "TLSV13_ALERT_MISSING_EXTENSION", + 85899346969u64 => "TLSV1_ALERT_ACCESS_DENIED", + 85899346970u64 => "TLSV1_ALERT_DECODE_ERROR", + 85899346941u64 => "TLSV1_ALERT_DECRYPTION_FAILED", + 85899346971u64 => "TLSV1_ALERT_DECRYPT_ERROR", + 85899346980u64 => "TLSV1_ALERT_EXPORT_RESTRICTION", + 85899347006u64 => "TLSV1_ALERT_INAPPROPRIATE_FALLBACK", + 85899346991u64 => "TLSV1_ALERT_INSUFFICIENT_SECURITY", + 85899347000u64 => "TLSV1_ALERT_INTERNAL_ERROR", + 85899347040u64 => "TLSV1_ALERT_NO_APPLICATION_PROTOCOL", + 85899347020u64 => "TLSV1_ALERT_NO_RENEGOTIATION", + 85899346990u64 => "TLSV1_ALERT_PROTOCOL_VERSION", + 85899346942u64 => "TLSV1_ALERT_RECORD_OVERFLOW", + 85899346968u64 => "TLSV1_ALERT_UNKNOWN_CA", + 85899347035u64 => "TLSV1_ALERT_UNKNOWN_PSK_IDENTITY", + 85899347010u64 => "TLSV1_ALERT_USER_CANCELLED", + 85899347034u64 => "TLSV1_BAD_CERTIFICATE_HASH_VALUE", + 85899347033u64 => "TLSV1_BAD_CERTIFICATE_STATUS_RESPONSE", + 85899347031u64 => "TLSV1_CERTIFICATE_UNOBTAINABLE", + 85899347032u64 => "TLSV1_UNRECOGNIZED_NAME", + 85899347030u64 => "TLSV1_UNSUPPORTED_EXTENSION", + 85899346287u64 => "TLS_ILLEGAL_EXPORTER_LABEL", + 85899346077u64 => "TLS_INVALID_ECPOINTFORMAT_LIST", + 85899346052u64 => "TOO_MANY_KEY_UPDATES", + 85899346329u64 => "TOO_MANY_WARN_ALERTS", + 85899346084u64 => "TOO_MUCH_EARLY_DATA", + 85899346234u64 => "UNABLE_TO_FIND_ECDH_PARAMETERS", + 85899346159u64 => "UNABLE_TO_FIND_PUBLIC_KEY_PARAMETERS", + 85899346162u64 => "UNABLE_TO_LOAD_SSL3_MD5_ROUTINES", + 85899346163u64 => "UNABLE_TO_LOAD_SSL3_SHA1_ROUTINES", + 85899346182u64 => "UNEXPECTED_CCS_MESSAGE", + 85899346098u64 => "UNEXPECTED_END_OF_EARLY_DATA", + 85899346214u64 => "UNEXPECTED_EOF_WHILE_READING", + 85899346164u64 => "UNEXPECTED_MESSAGE", + 85899346165u64 => "UNEXPECTED_RECORD", + 85899346196u64 => "UNINITIALIZED", + 85899346166u64 => "UNKNOWN_ALERT_TYPE", + 85899346167u64 => "UNKNOWN_CERTIFICATE_TYPE", + 85899346168u64 => "UNKNOWN_CIPHER_RETURNED", + 85899346169u64 => "UNKNOWN_CIPHER_TYPE", + 85899346306u64 => "UNKNOWN_CMD_NAME", + 85899346059u64 => "UNKNOWN_COMMAND", + 85899346288u64 => "UNKNOWN_DIGEST", + 85899346170u64 => "UNKNOWN_KEY_EXCHANGE_TYPE", + 85899346171u64 => "UNKNOWN_PKEY_TYPE", + 85899346172u64 => "UNKNOWN_PROTOCOL", + 85899346174u64 => "UNKNOWN_SSL_VERSION", + 85899346175u64 => "UNKNOWN_STATE", + 85899346258u64 => "UNSAFE_LEGACY_RENEGOTIATION_DISABLED", + 85899346137u64 => "UNSOLICITED_EXTENSION", + 85899346177u64 => "UNSUPPORTED_COMPRESSION_ALGORITHM", + 85899346235u64 => "UNSUPPORTED_ELLIPTIC_CURVE", + 85899346178u64 => "UNSUPPORTED_PROTOCOL", + 85899346179u64 => "UNSUPPORTED_SSL_VERSION", + 85899346249u64 => "UNSUPPORTED_STATUS_TYPE", + 85899346289u64 => "USE_SRTP_NOT_NEGOTIATED", + 85899346086u64 => "VERSION_TOO_HIGH", + 85899346316u64 => "VERSION_TOO_LOW", + 85899346303u64 => "WRONG_CERTIFICATE_TYPE", + 85899346181u64 => "WRONG_CIPHER_RETURNED", + 85899346298u64 => "WRONG_CURVE", + 85899346184u64 => "WRONG_SIGNATURE_LENGTH", + 85899346185u64 => "WRONG_SIGNATURE_SIZE", + 85899346290u64 => "WRONG_SIGNATURE_TYPE", + 85899346186u64 => "WRONG_SSL_VERSION", + 85899346187u64 => "WRONG_VERSION_NUMBER", + 85899346188u64 => "X509_LIB", + 85899346189u64 => "X509_VERIFICATION_SETUP_PROBLEMS", + 201863463044u64 => "BAD_PKCS7_TYPE", + 201863463045u64 => "BAD_TYPE", + 201863463049u64 => "CANNOT_LOAD_CERT", + 201863463050u64 => "CANNOT_LOAD_KEY", + 201863463012u64 => "CERTIFICATE_VERIFY_ERROR", + 201863463039u64 => "COULD_NOT_SET_ENGINE", + 201863463027u64 => "COULD_NOT_SET_TIME", + 201863463046u64 => "DETACHED_CONTENT", + 201863463028u64 => "ESS_ADD_SIGNING_CERT_ERROR", + 201863463051u64 => "ESS_ADD_SIGNING_CERT_V2_ERROR", + 201863463013u64 => "ESS_SIGNING_CERTIFICATE_ERROR", + 201863463014u64 => "INVALID_NULL_POINTER", + 201863463029u64 => "INVALID_SIGNER_CERTIFICATE_PURPOSE", + 201863463015u64 => "MESSAGE_IMPRINT_MISMATCH", + 201863463016u64 => "NONCE_MISMATCH", + 201863463017u64 => "NONCE_NOT_RETURNED", + 201863463018u64 => "NO_CONTENT", + 201863463019u64 => "NO_TIME_STAMP_TOKEN", + 201863463030u64 => "PKCS7_ADD_SIGNATURE_ERROR", + 201863463031u64 => "PKCS7_ADD_SIGNED_ATTR_ERROR", + 201863463041u64 => "PKCS7_TO_TS_TST_INFO_FAILED", + 201863463020u64 => "POLICY_MISMATCH", + 201863463032u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 201863463033u64 => "RESPONSE_SETUP_ERROR", + 201863463021u64 => "SIGNATURE_FAILURE", + 201863463022u64 => "THERE_MUST_BE_ONE_SIGNER", + 201863463034u64 => "TIME_SYSCALL_ERROR", + 201863463042u64 => "TOKEN_NOT_PRESENT", + 201863463043u64 => "TOKEN_PRESENT", + 201863463023u64 => "TSA_NAME_MISMATCH", + 201863463024u64 => "TSA_UNTRUSTED", + 201863463035u64 => "TST_INFO_SETUP_ERROR", + 201863463036u64 => "TS_DATASIGN", + 201863463037u64 => "UNACCEPTABLE_POLICY", + 201863463038u64 => "UNSUPPORTED_MD_ALGORITHM", + 201863463025u64 => "UNSUPPORTED_VERSION", + 201863463047u64 => "VAR_BAD_VALUE", + 201863463048u64 => "VAR_LOOKUP_FAILURE", + 201863463026u64 => "WRONG_CONTENT_TYPE", + 171798691944u64 => "COMMON_OK_AND_CANCEL_CHARACTERS", + 171798691942u64 => "INDEX_TOO_LARGE", + 171798691943u64 => "INDEX_TOO_SMALL", + 171798691945u64 => "NO_RESULT_BUFFER", + 171798691947u64 => "PROCESSING_ERROR", + 171798691940u64 => "RESULT_TOO_LARGE", + 171798691941u64 => "RESULT_TOO_SMALL", + 171798691949u64 => "SYSASSIGN_ERROR", + 171798691950u64 => "SYSDASSGN_ERROR", + 171798691951u64 => "SYSQIOW_ERROR", + 171798691946u64 => "UNKNOWN_CONTROL_COMMAND", + 171798691948u64 => "UNKNOWN_TTYGET_ERRNO_VALUE", + 171798691952u64 => "USER_DATA_DUPLICATION_UNSUPPORTED", + 146028888182u64 => "BAD_IP_ADDRESS", + 146028888183u64 => "BAD_OBJECT", + 146028888164u64 => "BN_DEC2BN_ERROR", + 146028888165u64 => "BN_TO_ASN1_INTEGER_ERROR", + 146028888213u64 => "DIRNAME_ERROR", + 146028888224u64 => "DISTPOINT_ALREADY_SET", + 146028888197u64 => "DUPLICATE_ZONE_ID", + 146028888233u64 => "EMPTY_KEY_USAGE", + 146028888195u64 => "ERROR_CONVERTING_ZONE", + 146028888208u64 => "ERROR_CREATING_EXTENSION", + 146028888192u64 => "ERROR_IN_EXTENSION", + 146028888201u64 => "EXPECTED_A_SECTION_NAME", + 146028888209u64 => "EXTENSION_EXISTS", + 146028888179u64 => "EXTENSION_NAME_ERROR", + 146028888166u64 => "EXTENSION_NOT_FOUND", + 146028888167u64 => "EXTENSION_SETTING_NOT_SUPPORTED", + 146028888180u64 => "EXTENSION_VALUE_ERROR", + 146028888215u64 => "ILLEGAL_EMPTY_EXTENSION", + 146028888216u64 => "INCORRECT_POLICY_SYNTAX_TAG", + 146028888226u64 => "INVALID_ASNUMBER", + 146028888227u64 => "INVALID_ASRANGE", + 146028888168u64 => "INVALID_BOOLEAN_STRING", + 146028888222u64 => "INVALID_CERTIFICATE", + 146028888172u64 => "INVALID_EMPTY_NAME", + 146028888169u64 => "INVALID_EXTENSION_STRING", + 146028888229u64 => "INVALID_INHERITANCE", + 146028888230u64 => "INVALID_IPADDRESS", + 146028888225u64 => "INVALID_MULTIPLE_RDNS", + 146028888170u64 => "INVALID_NAME", + 146028888171u64 => "INVALID_NULL_ARGUMENT", + 146028888173u64 => "INVALID_NULL_VALUE", + 146028888204u64 => "INVALID_NUMBER", + 146028888205u64 => "INVALID_NUMBERS", + 146028888174u64 => "INVALID_OBJECT_IDENTIFIER", + 146028888202u64 => "INVALID_OPTION", + 146028888198u64 => "INVALID_POLICY_IDENTIFIER", + 146028888217u64 => "INVALID_PROXY_POLICY_SETTING", + 146028888210u64 => "INVALID_PURPOSE", + 146028888228u64 => "INVALID_SAFI", + 146028888199u64 => "INVALID_SECTION", + 146028888207u64 => "INVALID_SYNTAX", + 146028888190u64 => "ISSUER_DECODE_ERROR", + 146028888188u64 => "MISSING_VALUE", + 146028888206u64 => "NEED_ORGANIZATION_AND_NUMBERS", + 146028888232u64 => "NEGATIVE_PATHLEN", + 146028888200u64 => "NO_CONFIG_DATABASE", + 146028888185u64 => "NO_ISSUER_CERTIFICATE", + 146028888191u64 => "NO_ISSUER_DETAILS", + 146028888203u64 => "NO_POLICY_IDENTIFIER", + 146028888218u64 => "NO_PROXY_CERT_POLICY_LANGUAGE_DEFINED", + 146028888178u64 => "NO_PUBLIC_KEY", + 146028888189u64 => "NO_SUBJECT_DETAILS", + 146028888212u64 => "OPERATION_NOT_DEFINED", + 146028888211u64 => "OTHERNAME_ERROR", + 146028888219u64 => "POLICY_LANGUAGE_ALREADY_DEFINED", + 146028888220u64 => "POLICY_PATH_LENGTH", + 146028888221u64 => "POLICY_PATH_LENGTH_ALREADY_DEFINED", + 146028888223u64 => "POLICY_WHEN_PROXY_LANGUAGE_REQUIRES_NO_POLICY", + 146028888214u64 => "SECTION_NOT_FOUND", + 146028888186u64 => "UNABLE_TO_GET_ISSUER_DETAILS", + 146028888187u64 => "UNABLE_TO_GET_ISSUER_KEYID", + 146028888175u64 => "UNKNOWN_BIT_STRING_ARGUMENT", + 146028888193u64 => "UNKNOWN_EXTENSION", + 146028888194u64 => "UNKNOWN_EXTENSION_NAME", + 146028888184u64 => "UNKNOWN_OPTION", + 146028888181u64 => "UNSUPPORTED_OPTION", + 146028888231u64 => "UNSUPPORTED_TYPE", + 146028888196u64 => "USER_TOO_LONG", + 47244640366u64 => "AKID_MISMATCH", + 47244640389u64 => "BAD_SELECTOR", + 47244640356u64 => "BAD_X509_FILETYPE", + 47244640374u64 => "BASE64_DECODE_ERROR", + 47244640370u64 => "CANT_CHECK_DH_KEY", + 47244640395u64 => "CERTIFICATE_VERIFICATION_FAILED", + 47244640357u64 => "CERT_ALREADY_IN_HASH_TABLE", + 47244640383u64 => "CRL_ALREADY_DELTA", + 47244640387u64 => "CRL_VERIFY_FAILURE", + 47244640396u64 => "DUPLICATE_ATTRIBUTE", + 47244640397u64 => "ERROR_GETTING_MD_BY_NID", + 47244640398u64 => "ERROR_USING_SIGINF_SET", + 47244640384u64 => "IDP_MISMATCH", + 47244640394u64 => "INVALID_ATTRIBUTES", + 47244640369u64 => "INVALID_DIRECTORY", + 47244640399u64 => "INVALID_DISTPOINT", + 47244640375u64 => "INVALID_FIELD_NAME", + 47244640379u64 => "INVALID_TRUST", + 47244640385u64 => "ISSUER_MISMATCH", + 47244640371u64 => "KEY_TYPE_MISMATCH", + 47244640372u64 => "KEY_VALUES_MISMATCH", + 47244640359u64 => "LOADING_CERT_DIR", + 47244640360u64 => "LOADING_DEFAULTS", + 47244640380u64 => "METHOD_NOT_SUPPORTED", + 47244640390u64 => "NAME_TOO_LONG", + 47244640388u64 => "NEWER_CRL_NOT_NEWER", + 47244640391u64 => "NO_CERTIFICATE_FOUND", + 47244640392u64 => "NO_CERTIFICATE_OR_CRL_FOUND", + 47244640361u64 => "NO_CERT_SET_FOR_US_TO_VERIFY", + 47244640393u64 => "NO_CRL_FOUND", + 47244640386u64 => "NO_CRL_NUMBER", + 47244640381u64 => "PUBLIC_KEY_DECODE_ERROR", + 47244640382u64 => "PUBLIC_KEY_ENCODE_ERROR", + 47244640362u64 => "SHOULD_RETRY", + 47244640363u64 => "UNABLE_TO_FIND_PARAMETERS_IN_CHAIN", + 47244640364u64 => "UNABLE_TO_GET_CERTS_PUBLIC_KEY", + 47244640373u64 => "UNKNOWN_KEY_TYPE", + 47244640365u64 => "UNKNOWN_NID", + 47244640377u64 => "UNKNOWN_PURPOSE_ID", + 47244640400u64 => "UNKNOWN_SIGID_ALGS", + 47244640376u64 => "UNKNOWN_TRUST_ID", + 47244640367u64 => "UNSUPPORTED_ALGORITHM", + 47244640368u64 => "WRONG_LOOKUP_TYPE", + 47244640378u64 => "WRONG_TYPE", +}; + +/// Helper function to create encoded key from (lib, reason) pair +#[inline] +pub fn encode_error_key(lib: i32, reason: i32) -> u64 { + ((lib as u64) << 32) | (reason as u64 & 0xFFFFFFFF) +} diff --git a/stdlib/src/ssl/ssl_data_31.rs b/stdlib/src/ssl/ssl_data_31.rs new file mode 100644 index 00000000000..4b80de539ec --- /dev/null +++ b/stdlib/src/ssl/ssl_data_31.rs @@ -0,0 +1,1770 @@ +// File generated by tools/make_ssl_data_rs.py +// Generated on 2025-10-29T07:17:16.621749+00:00 +// Source: OpenSSL from /tmp/openssl-3.1 +// spell-checker: disable + +use phf::phf_map; + +// Maps lib_code -> library name +// Example: 20 -> "SSL" +pub static LIBRARY_CODES: phf::Map<u32, &'static str> = phf_map! { + 0u32 => "MASK", + 1u32 => "NONE", + 2u32 => "SYS", + 3u32 => "BN", + 4u32 => "RSA", + 5u32 => "DH", + 6u32 => "EVP", + 7u32 => "BUF", + 8u32 => "OBJ", + 9u32 => "PEM", + 10u32 => "DSA", + 11u32 => "X509", + 12u32 => "METH", + 13u32 => "ASN1", + 14u32 => "CONF", + 15u32 => "CRYPTO", + 16u32 => "EC", + 20u32 => "SSL", + 21u32 => "SSL23", + 22u32 => "SSL2", + 23u32 => "SSL3", + 30u32 => "RSAREF", + 31u32 => "PROXY", + 32u32 => "BIO", + 33u32 => "PKCS7", + 34u32 => "X509V3", + 35u32 => "PKCS12", + 36u32 => "RAND", + 37u32 => "DSO", + 38u32 => "ENGINE", + 39u32 => "OCSP", + 40u32 => "UI", + 41u32 => "COMP", + 42u32 => "ECDSA", + 43u32 => "ECDH", + 44u32 => "OSSL_STORE", + 45u32 => "FIPS", + 46u32 => "CMS", + 47u32 => "TS", + 48u32 => "HMAC", + 49u32 => "JPAKE", + 50u32 => "CT", + 51u32 => "ASYNC", + 52u32 => "KDF", + 53u32 => "SM2", + 54u32 => "ESS", + 55u32 => "PROP", + 56u32 => "CRMF", + 57u32 => "PROV", + 58u32 => "CMP", + 59u32 => "OSSL_ENCODER", + 60u32 => "OSSL_DECODER", + 61u32 => "HTTP", + 128u32 => "USER", +}; + +// Maps encoded (lib, reason) -> error mnemonic +// Example: encode_error_key(20, 134) -> "CERTIFICATE_VERIFY_FAILED" +// Key encoding: (lib << 32) | reason +pub static ERROR_CODES: phf::Map<u64, &'static str> = phf_map! { + 55834575019u64 => "ADDING_OBJECT", + 55834575051u64 => "ASN1_PARSE_ERROR", + 55834575052u64 => "ASN1_SIG_PARSE_ERROR", + 55834574948u64 => "AUX_ERROR", + 55834574950u64 => "BAD_OBJECT_HEADER", + 55834575078u64 => "BAD_TEMPLATE", + 55834575062u64 => "BMPSTRING_IS_WRONG_LENGTH", + 55834574953u64 => "BN_LIB", + 55834574954u64 => "BOOLEAN_IS_WRONG_LENGTH", + 55834574955u64 => "BUFFER_TOO_SMALL", + 55834574956u64 => "CIPHER_HAS_NO_OBJECT_IDENTIFIER", + 55834575065u64 => "CONTEXT_NOT_INITIALISED", + 55834574957u64 => "DATA_IS_WRONG", + 55834574958u64 => "DECODE_ERROR", + 55834575022u64 => "DEPTH_EXCEEDED", + 55834575046u64 => "DIGEST_AND_KEY_TYPE_NOT_SUPPORTED", + 55834574960u64 => "ENCODE_ERROR", + 55834575021u64 => "ERROR_GETTING_TIME", + 55834575020u64 => "ERROR_LOADING_SECTION", + 55834574962u64 => "ERROR_SETTING_CIPHER_PARAMS", + 55834574963u64 => "EXPECTING_AN_INTEGER", + 55834574964u64 => "EXPECTING_AN_OBJECT", + 55834574967u64 => "EXPLICIT_LENGTH_MISMATCH", + 55834574968u64 => "EXPLICIT_TAG_NOT_CONSTRUCTED", + 55834574969u64 => "FIELD_MISSING", + 55834574970u64 => "FIRST_NUM_TOO_LARGE", + 55834574971u64 => "HEADER_TOO_LONG", + 55834575023u64 => "ILLEGAL_BITSTRING_FORMAT", + 55834575024u64 => "ILLEGAL_BOOLEAN", + 55834574972u64 => "ILLEGAL_CHARACTERS", + 55834575025u64 => "ILLEGAL_FORMAT", + 55834575026u64 => "ILLEGAL_HEX", + 55834575027u64 => "ILLEGAL_IMPLICIT_TAG", + 55834575028u64 => "ILLEGAL_INTEGER", + 55834575074u64 => "ILLEGAL_NEGATIVE_VALUE", + 55834575029u64 => "ILLEGAL_NESTED_TAGGING", + 55834574973u64 => "ILLEGAL_NULL", + 55834575030u64 => "ILLEGAL_NULL_VALUE", + 55834575031u64 => "ILLEGAL_OBJECT", + 55834574974u64 => "ILLEGAL_OPTIONAL_ANY", + 55834575018u64 => "ILLEGAL_OPTIONS_ON_ITEM_TEMPLATE", + 55834575069u64 => "ILLEGAL_PADDING", + 55834574975u64 => "ILLEGAL_TAGGED_ANY", + 55834575032u64 => "ILLEGAL_TIME_VALUE", + 55834575070u64 => "ILLEGAL_ZERO_CONTENT", + 55834575033u64 => "INTEGER_NOT_ASCII_FORMAT", + 55834574976u64 => "INTEGER_TOO_LARGE_FOR_LONG", + 55834575068u64 => "INVALID_BIT_STRING_BITS_LEFT", + 55834574977u64 => "INVALID_BMPSTRING_LENGTH", + 55834574978u64 => "INVALID_DIGIT", + 55834575053u64 => "INVALID_MIME_TYPE", + 55834575034u64 => "INVALID_MODIFIER", + 55834575035u64 => "INVALID_NUMBER", + 55834575064u64 => "INVALID_OBJECT_ENCODING", + 55834575075u64 => "INVALID_SCRYPT_PARAMETERS", + 55834574979u64 => "INVALID_SEPARATOR", + 55834575066u64 => "INVALID_STRING_TABLE_VALUE", + 55834574981u64 => "INVALID_UNIVERSALSTRING_LENGTH", + 55834574982u64 => "INVALID_UTF8STRING", + 55834575067u64 => "INVALID_VALUE", + 55834575079u64 => "LENGTH_TOO_LONG", + 55834575036u64 => "LIST_ERROR", + 55834575054u64 => "MIME_NO_CONTENT_TYPE", + 55834575055u64 => "MIME_PARSE_ERROR", + 55834575056u64 => "MIME_SIG_PARSE_ERROR", + 55834574985u64 => "MISSING_EOC", + 55834574986u64 => "MISSING_SECOND_NUMBER", + 55834575037u64 => "MISSING_VALUE", + 55834574987u64 => "MSTRING_NOT_UNIVERSAL", + 55834574988u64 => "MSTRING_WRONG_TAG", + 55834575045u64 => "NESTED_ASN1_STRING", + 55834575049u64 => "NESTED_TOO_DEEP", + 55834574989u64 => "NON_HEX_CHARACTERS", + 55834575038u64 => "NOT_ASCII_FORMAT", + 55834574990u64 => "NOT_ENOUGH_DATA", + 55834575057u64 => "NO_CONTENT_TYPE", + 55834574991u64 => "NO_MATCHING_CHOICE_TYPE", + 55834575058u64 => "NO_MULTIPART_BODY_FAILURE", + 55834575059u64 => "NO_MULTIPART_BOUNDARY", + 55834575060u64 => "NO_SIG_CONTENT_TYPE", + 55834574992u64 => "NULL_IS_WRONG_LENGTH", + 55834575039u64 => "OBJECT_NOT_ASCII_FORMAT", + 55834574993u64 => "ODD_NUMBER_OF_CHARS", + 55834574995u64 => "SECOND_NUMBER_TOO_LARGE", + 55834574996u64 => "SEQUENCE_LENGTH_MISMATCH", + 55834574997u64 => "SEQUENCE_NOT_CONSTRUCTED", + 55834575040u64 => "SEQUENCE_OR_SET_NEEDS_CONFIG", + 55834574998u64 => "SHORT_LINE", + 55834575061u64 => "SIG_INVALID_MIME_TYPE", + 55834575050u64 => "STREAMING_NOT_SUPPORTED", + 55834574999u64 => "STRING_TOO_LONG", + 55834575000u64 => "STRING_TOO_SHORT", + 55834575002u64 => "THE_ASN1_OBJECT_IDENTIFIER_IS_NOT_KNOWN_FOR_THIS_MD", + 55834575041u64 => "TIME_NOT_ASCII_FORMAT", + 55834575071u64 => "TOO_LARGE", + 55834575003u64 => "TOO_LONG", + 55834575072u64 => "TOO_SMALL", + 55834575004u64 => "TYPE_NOT_CONSTRUCTED", + 55834575043u64 => "TYPE_NOT_PRIMITIVE", + 55834575007u64 => "UNEXPECTED_EOC", + 55834575063u64 => "UNIVERSALSTRING_IS_WRONG_LENGTH", + 55834575077u64 => "UNKNOWN_DIGEST", + 55834575008u64 => "UNKNOWN_FORMAT", + 55834575009u64 => "UNKNOWN_MESSAGE_DIGEST_ALGORITHM", + 55834575010u64 => "UNKNOWN_OBJECT_TYPE", + 55834575011u64 => "UNKNOWN_PUBLIC_KEY_TYPE", + 55834575047u64 => "UNKNOWN_SIGNATURE_ALGORITHM", + 55834575042u64 => "UNKNOWN_TAG", + 55834575012u64 => "UNSUPPORTED_ANY_DEFINED_BY_TYPE", + 55834575076u64 => "UNSUPPORTED_CIPHER", + 55834575015u64 => "UNSUPPORTED_PUBLIC_KEY_TYPE", + 55834575044u64 => "UNSUPPORTED_TYPE", + 55834575073u64 => "WRONG_INTEGER_TYPE", + 55834575048u64 => "WRONG_PUBLIC_KEY_TYPE", + 55834575016u64 => "WRONG_TAG", + 219043332197u64 => "FAILED_TO_SET_POOL", + 219043332198u64 => "FAILED_TO_SWAP_CONTEXT", + 219043332201u64 => "INIT_FAILED", + 219043332199u64 => "INVALID_POOL_SIZE", + 137438953572u64 => "ACCEPT_ERROR", + 137438953613u64 => "ADDRINFO_ADDR_IS_NOT_AF_INET", + 137438953601u64 => "AMBIGUOUS_HOST_OR_SERVICE", + 137438953573u64 => "BAD_FOPEN_MODE", + 137438953596u64 => "BROKEN_PIPE", + 137438953575u64 => "CONNECT_ERROR", + 137438953619u64 => "CONNECT_TIMEOUT", + 137438953579u64 => "GETHOSTBYNAME_ADDR_IS_NOT_AF_INET", + 137438953604u64 => "GETSOCKNAME_ERROR", + 137438953605u64 => "GETSOCKNAME_TRUNCATED_ADDRESS", + 137438953606u64 => "GETTING_SOCKTYPE", + 137438953597u64 => "INVALID_ARGUMENT", + 137438953607u64 => "INVALID_SOCKET", + 137438953595u64 => "IN_USE", + 137438953574u64 => "LENGTH_TOO_LONG", + 137438953608u64 => "LISTEN_V6_ONLY", + 137438953614u64 => "LOOKUP_RETURNED_NOTHING", + 137438953602u64 => "MALFORMED_HOST_OR_SERVICE", + 137438953582u64 => "NBIO_CONNECT_ERROR", + 137438953615u64 => "NO_ACCEPT_ADDR_OR_SERVICE_SPECIFIED", + 137438953616u64 => "NO_HOSTNAME_OR_SERVICE_SPECIFIED", + 137438953585u64 => "NO_PORT_DEFINED", + 137438953600u64 => "NO_SUCH_FILE", + 137438953576u64 => "TRANSFER_ERROR", + 137438953577u64 => "TRANSFER_TIMEOUT", + 137438953589u64 => "UNABLE_TO_BIND_SOCKET", + 137438953590u64 => "UNABLE_TO_CREATE_SOCKET", + 137438953609u64 => "UNABLE_TO_KEEPALIVE", + 137438953591u64 => "UNABLE_TO_LISTEN_SOCKET", + 137438953610u64 => "UNABLE_TO_NODELAY", + 137438953611u64 => "UNABLE_TO_REUSEADDR", + 137438953617u64 => "UNAVAILABLE_IP_FAMILY", + 137438953592u64 => "UNINITIALIZED", + 137438953612u64 => "UNKNOWN_INFO_TYPE", + 137438953618u64 => "UNSUPPORTED_IP_FAMILY", + 137438953593u64 => "UNSUPPORTED_METHOD", + 137438953603u64 => "UNSUPPORTED_PROTOCOL_FAMILY", + 137438953598u64 => "WRITE_TO_READ_ONLY_BIO", + 137438953594u64 => "WSASTARTUP", + 12884901988u64 => "ARG2_LT_ARG3", + 12884901989u64 => "BAD_RECIPROCAL", + 12884902002u64 => "BIGNUM_TOO_LONG", + 12884902006u64 => "BITS_TOO_SMALL", + 12884901990u64 => "CALLED_WITH_EVEN_MODULUS", + 12884901991u64 => "DIV_BY_ZERO", + 12884901992u64 => "ENCODING_ERROR", + 12884901993u64 => "EXPAND_ON_STATIC_BIGNUM_DATA", + 12884901998u64 => "INPUT_NOT_REDUCED", + 12884901994u64 => "INVALID_LENGTH", + 12884902003u64 => "INVALID_RANGE", + 12884902007u64 => "INVALID_SHIFT", + 12884901999u64 => "NOT_A_SQUARE", + 12884901995u64 => "NOT_INITIALIZED", + 12884901996u64 => "NO_INVERSE", + 12884902009u64 => "NO_PRIME_CANDIDATE", + 12884902004u64 => "NO_SOLUTION", + 12884902008u64 => "NO_SUITABLE_DIGEST", + 12884902005u64 => "PRIVATE_KEY_TOO_LARGE", + 12884902000u64 => "P_IS_NOT_PRIME", + 12884902001u64 => "TOO_MANY_ITERATIONS", + 12884901997u64 => "TOO_MANY_TEMPORARY_VARIABLES", + 249108103307u64 => "ALGORITHM_NOT_SUPPORTED", + 249108103335u64 => "BAD_CHECKAFTER_IN_POLLREP", + 249108103276u64 => "BAD_REQUEST_ID", + 249108103324u64 => "CERTHASH_UNMATCHED", + 249108103277u64 => "CERTID_NOT_FOUND", + 249108103337u64 => "CERTIFICATE_NOT_ACCEPTED", + 249108103280u64 => "CERTIFICATE_NOT_FOUND", + 249108103325u64 => "CERTREQMSG_NOT_FOUND", + 249108103281u64 => "CERTRESPONSE_NOT_FOUND", + 249108103282u64 => "CERT_AND_KEY_DO_NOT_MATCH", + 249108103349u64 => "CHECKAFTER_OUT_OF_RANGE", + 249108103344u64 => "ENCOUNTERED_KEYUPDATEWARNING", + 249108103330u64 => "ENCOUNTERED_WAITING", + 249108103283u64 => "ERROR_CALCULATING_PROTECTION", + 249108103284u64 => "ERROR_CREATING_CERTCONF", + 249108103285u64 => "ERROR_CREATING_CERTREP", + 249108103331u64 => "ERROR_CREATING_CERTREQ", + 249108103286u64 => "ERROR_CREATING_ERROR", + 249108103287u64 => "ERROR_CREATING_GENM", + 249108103288u64 => "ERROR_CREATING_GENP", + 249108103290u64 => "ERROR_CREATING_PKICONF", + 249108103291u64 => "ERROR_CREATING_POLLREP", + 249108103292u64 => "ERROR_CREATING_POLLREQ", + 249108103293u64 => "ERROR_CREATING_RP", + 249108103294u64 => "ERROR_CREATING_RR", + 249108103275u64 => "ERROR_PARSING_PKISTATUS", + 249108103326u64 => "ERROR_PROCESSING_MESSAGE", + 249108103295u64 => "ERROR_PROTECTING_MESSAGE", + 249108103296u64 => "ERROR_SETTING_CERTHASH", + 249108103328u64 => "ERROR_UNEXPECTED_CERTCONF", + 249108103308u64 => "ERROR_VALIDATING_PROTECTION", + 249108103339u64 => "ERROR_VALIDATING_SIGNATURE", + 249108103332u64 => "FAILED_BUILDING_OWN_CHAIN", + 249108103309u64 => "FAILED_EXTRACTING_PUBKEY", + 249108103278u64 => "FAILURE_OBTAINING_RANDOM", + 249108103297u64 => "FAIL_INFO_OUT_OF_RANGE", + 249108103268u64 => "INVALID_ARGS", + 249108103342u64 => "INVALID_OPTION", + 249108103333u64 => "MISSING_CERTID", + 249108103298u64 => "MISSING_KEY_INPUT_FOR_CREATING_PROTECTION", + 249108103310u64 => "MISSING_KEY_USAGE_DIGITALSIGNATURE", + 249108103289u64 => "MISSING_P10CSR", + 249108103334u64 => "MISSING_PBM_SECRET", + 249108103299u64 => "MISSING_PRIVATE_KEY", + 249108103358u64 => "MISSING_PRIVATE_KEY_FOR_POPO", + 249108103311u64 => "MISSING_PROTECTION", + 249108103351u64 => "MISSING_PUBLIC_KEY", + 249108103336u64 => "MISSING_REFERENCE_CERT", + 249108103346u64 => "MISSING_SECRET", + 249108103279u64 => "MISSING_SENDER_IDENTIFICATION", + 249108103347u64 => "MISSING_TRUST_ANCHOR", + 249108103312u64 => "MISSING_TRUST_STORE", + 249108103329u64 => "MULTIPLE_REQUESTS_NOT_SUPPORTED", + 249108103338u64 => "MULTIPLE_RESPONSES_NOT_SUPPORTED", + 249108103270u64 => "MULTIPLE_SAN_SOURCES", + 249108103362u64 => "NO_STDIO", + 249108103313u64 => "NO_SUITABLE_SENDER_CERT", + 249108103271u64 => "NULL_ARGUMENT", + 249108103314u64 => "PKIBODY_ERROR", + 249108103300u64 => "PKISTATUSINFO_NOT_FOUND", + 249108103340u64 => "POLLING_FAILED", + 249108103315u64 => "POTENTIALLY_INVALID_CERTIFICATE", + 249108103348u64 => "RECEIVED_ERROR", + 249108103316u64 => "RECIPNONCE_UNMATCHED", + 249108103317u64 => "REQUEST_NOT_ACCEPTED", + 249108103350u64 => "REQUEST_REJECTED_BY_SERVER", + 249108103318u64 => "SENDER_GENERALNAME_TYPE_NOT_SUPPORTED", + 249108103319u64 => "SRVCERT_DOES_NOT_VALIDATE_MSG", + 249108103352u64 => "TOTAL_TIMEOUT", + 249108103320u64 => "TRANSACTIONID_UNMATCHED", + 249108103327u64 => "TRANSFER_ERROR", + 249108103301u64 => "UNEXPECTED_PKIBODY", + 249108103353u64 => "UNEXPECTED_PKISTATUS", + 249108103321u64 => "UNEXPECTED_PVNO", + 249108103302u64 => "UNKNOWN_ALGORITHM_ID", + 249108103303u64 => "UNKNOWN_CERT_TYPE", + 249108103354u64 => "UNKNOWN_PKISTATUS", + 249108103304u64 => "UNSUPPORTED_ALGORITHM", + 249108103305u64 => "UNSUPPORTED_KEY_TYPE", + 249108103322u64 => "UNSUPPORTED_PROTECTION_ALG_DHBASEDMAC", + 249108103343u64 => "VALUE_TOO_LARGE", + 249108103345u64 => "VALUE_TOO_SMALL", + 249108103306u64 => "WRONG_ALGORITHM_OID", + 249108103357u64 => "WRONG_CERTID", + 249108103355u64 => "WRONG_CERTID_IN_RP", + 249108103323u64 => "WRONG_PBM_VALUE", + 249108103356u64 => "WRONG_RP_COMPONENT_COUNT", + 249108103341u64 => "WRONG_SERIAL_IN_RP", + 197568495715u64 => "ADD_SIGNER_ERROR", + 197568495777u64 => "ATTRIBUTE_ERROR", + 197568495791u64 => "CERTIFICATE_ALREADY_PRESENT", + 197568495776u64 => "CERTIFICATE_HAS_NO_KEYID", + 197568495716u64 => "CERTIFICATE_VERIFY_ERROR", + 197568495800u64 => "CIPHER_AEAD_SET_TAG_ERROR", + 197568495801u64 => "CIPHER_GET_TAG", + 197568495717u64 => "CIPHER_INITIALISATION_ERROR", + 197568495718u64 => "CIPHER_PARAMETER_INITIALISATION_ERROR", + 197568495719u64 => "CMS_DATAFINAL_ERROR", + 197568495720u64 => "CMS_LIB", + 197568495786u64 => "CONTENTIDENTIFIER_MISMATCH", + 197568495721u64 => "CONTENT_NOT_FOUND", + 197568495787u64 => "CONTENT_TYPE_MISMATCH", + 197568495722u64 => "CONTENT_TYPE_NOT_COMPRESSED_DATA", + 197568495723u64 => "CONTENT_TYPE_NOT_ENVELOPED_DATA", + 197568495724u64 => "CONTENT_TYPE_NOT_SIGNED_DATA", + 197568495725u64 => "CONTENT_VERIFY_ERROR", + 197568495726u64 => "CTRL_ERROR", + 197568495727u64 => "CTRL_FAILURE", + 197568495803u64 => "DECODE_ERROR", + 197568495728u64 => "DECRYPT_ERROR", + 197568495729u64 => "ERROR_GETTING_PUBLIC_KEY", + 197568495730u64 => "ERROR_READING_MESSAGEDIGEST_ATTRIBUTE", + 197568495731u64 => "ERROR_SETTING_KEY", + 197568495732u64 => "ERROR_SETTING_RECIPIENTINFO", + 197568495799u64 => "ESS_SIGNING_CERTID_MISMATCH_ERROR", + 197568495733u64 => "INVALID_ENCRYPTED_KEY_LENGTH", + 197568495792u64 => "INVALID_KEY_ENCRYPTION_PARAMETER", + 197568495734u64 => "INVALID_KEY_LENGTH", + 197568495806u64 => "INVALID_LABEL", + 197568495807u64 => "INVALID_OAEP_PARAMETERS", + 197568495802u64 => "KDF_PARAMETER_ERROR", + 197568495735u64 => "MD_BIO_INIT_ERROR", + 197568495736u64 => "MESSAGEDIGEST_ATTRIBUTE_WRONG_LENGTH", + 197568495737u64 => "MESSAGEDIGEST_WRONG_LENGTH", + 197568495788u64 => "MSGSIGDIGEST_ERROR", + 197568495778u64 => "MSGSIGDIGEST_VERIFICATION_FAILURE", + 197568495779u64 => "MSGSIGDIGEST_WRONG_LENGTH", + 197568495780u64 => "NEED_ONE_SIGNER", + 197568495781u64 => "NOT_A_SIGNED_RECEIPT", + 197568495738u64 => "NOT_ENCRYPTED_DATA", + 197568495739u64 => "NOT_KEK", + 197568495797u64 => "NOT_KEY_AGREEMENT", + 197568495740u64 => "NOT_KEY_TRANSPORT", + 197568495793u64 => "NOT_PWRI", + 197568495741u64 => "NOT_SUPPORTED_FOR_THIS_KEY_TYPE", + 197568495742u64 => "NO_CIPHER", + 197568495743u64 => "NO_CONTENT", + 197568495789u64 => "NO_CONTENT_TYPE", + 197568495744u64 => "NO_DEFAULT_DIGEST", + 197568495745u64 => "NO_DIGEST_SET", + 197568495746u64 => "NO_KEY", + 197568495790u64 => "NO_KEY_OR_CERT", + 197568495747u64 => "NO_MATCHING_DIGEST", + 197568495748u64 => "NO_MATCHING_RECIPIENT", + 197568495782u64 => "NO_MATCHING_SIGNATURE", + 197568495783u64 => "NO_MSGSIGDIGEST", + 197568495794u64 => "NO_PASSWORD", + 197568495749u64 => "NO_PRIVATE_KEY", + 197568495750u64 => "NO_PUBLIC_KEY", + 197568495784u64 => "NO_RECEIPT_REQUEST", + 197568495751u64 => "NO_SIGNERS", + 197568495804u64 => "PEER_KEY_ERROR", + 197568495752u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 197568495785u64 => "RECEIPT_DECODE_ERROR", + 197568495753u64 => "RECIPIENT_ERROR", + 197568495805u64 => "SHARED_INFO_ERROR", + 197568495754u64 => "SIGNER_CERTIFICATE_NOT_FOUND", + 197568495755u64 => "SIGNFINAL_ERROR", + 197568495756u64 => "SMIME_TEXT_ERROR", + 197568495757u64 => "STORE_INIT_ERROR", + 197568495758u64 => "TYPE_NOT_COMPRESSED_DATA", + 197568495759u64 => "TYPE_NOT_DATA", + 197568495760u64 => "TYPE_NOT_DIGESTED_DATA", + 197568495761u64 => "TYPE_NOT_ENCRYPTED_DATA", + 197568495762u64 => "TYPE_NOT_ENVELOPED_DATA", + 197568495763u64 => "UNABLE_TO_FINALIZE_CONTEXT", + 197568495764u64 => "UNKNOWN_CIPHER", + 197568495765u64 => "UNKNOWN_DIGEST_ALGORITHM", + 197568495766u64 => "UNKNOWN_ID", + 197568495767u64 => "UNSUPPORTED_COMPRESSION_ALGORITHM", + 197568495810u64 => "UNSUPPORTED_CONTENT_ENCRYPTION_ALGORITHM", + 197568495768u64 => "UNSUPPORTED_CONTENT_TYPE", + 197568495808u64 => "UNSUPPORTED_ENCRYPTION_TYPE", + 197568495769u64 => "UNSUPPORTED_KEK_ALGORITHM", + 197568495795u64 => "UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM", + 197568495809u64 => "UNSUPPORTED_LABEL_SOURCE", + 197568495771u64 => "UNSUPPORTED_RECIPIENTINFO_TYPE", + 197568495770u64 => "UNSUPPORTED_RECIPIENT_TYPE", + 197568495811u64 => "UNSUPPORTED_SIGNATURE_ALGORITHM", + 197568495772u64 => "UNSUPPORTED_TYPE", + 197568495773u64 => "UNWRAP_ERROR", + 197568495796u64 => "UNWRAP_FAILURE", + 197568495774u64 => "VERIFICATION_FAILURE", + 197568495775u64 => "WRAP_ERROR", + 176093659235u64 => "ZLIB_DEFLATE_ERROR", + 176093659236u64 => "ZLIB_INFLATE_ERROR", + 176093659237u64 => "ZLIB_NOT_SUPPORTED", + 60129542254u64 => "ERROR_LOADING_DSO", + 60129542266u64 => "INVALID_PRAGMA", + 60129542259u64 => "LIST_CANNOT_BE_NULL", + 60129542267u64 => "MANDATORY_BRACES_IN_VARIABLE_EXPANSION", + 60129542244u64 => "MISSING_CLOSE_SQUARE_BRACKET", + 60129542245u64 => "MISSING_EQUAL_SIGN", + 60129542256u64 => "MISSING_INIT_FUNCTION", + 60129542253u64 => "MODULE_INITIALIZATION_ERROR", + 60129542246u64 => "NO_CLOSE_BRACE", + 60129542249u64 => "NO_CONF", + 60129542250u64 => "NO_CONF_OR_ENVIRONMENT_VARIABLE", + 60129542251u64 => "NO_SECTION", + 60129542258u64 => "NO_SUCH_FILE", + 60129542252u64 => "NO_VALUE", + 60129542265u64 => "NUMBER_TOO_LARGE", + 60129542268u64 => "OPENSSL_CONF_REFERENCES_MISSING_SECTION", + 60129542255u64 => "RECURSIVE_DIRECTORY_INCLUDE", + 60129542270u64 => "RECURSIVE_SECTION_REFERENCE", + 60129542269u64 => "RELATIVE_PATH", + 60129542261u64 => "SSL_COMMAND_SECTION_EMPTY", + 60129542262u64 => "SSL_COMMAND_SECTION_NOT_FOUND", + 60129542263u64 => "SSL_SECTION_EMPTY", + 60129542264u64 => "SSL_SECTION_NOT_FOUND", + 60129542247u64 => "UNABLE_TO_CREATE_NEW_SECTION", + 60129542257u64 => "UNKNOWN_MODULE_NAME", + 60129542260u64 => "VARIABLE_EXPANSION_TOO_LONG", + 60129542248u64 => "VARIABLE_HAS_NO_VALUE", + 240518168676u64 => "BAD_PBM_ITERATIONCOUNT", + 240518168678u64 => "CRMFERROR", + 240518168679u64 => "ERROR", + 240518168680u64 => "ERROR_DECODING_CERTIFICATE", + 240518168681u64 => "ERROR_DECRYPTING_CERTIFICATE", + 240518168682u64 => "ERROR_DECRYPTING_SYMMETRIC_KEY", + 240518168683u64 => "FAILURE_OBTAINING_RANDOM", + 240518168684u64 => "ITERATIONCOUNT_BELOW_100", + 240518168677u64 => "MALFORMED_IV", + 240518168685u64 => "NULL_ARGUMENT", + 240518168689u64 => "POPOSKINPUT_NOT_SUPPORTED", + 240518168693u64 => "POPO_INCONSISTENT_PUBLIC_KEY", + 240518168697u64 => "POPO_MISSING", + 240518168694u64 => "POPO_MISSING_PUBLIC_KEY", + 240518168695u64 => "POPO_MISSING_SUBJECT", + 240518168696u64 => "POPO_RAVERIFIED_NOT_ACCEPTED", + 240518168686u64 => "SETTING_MAC_ALGOR_FAILURE", + 240518168687u64 => "SETTING_OWF_ALGOR_FAILURE", + 240518168688u64 => "UNSUPPORTED_ALGORITHM", + 240518168690u64 => "UNSUPPORTED_CIPHER", + 240518168691u64 => "UNSUPPORTED_METHOD_FOR_CREATING_POPO", + 240518168692u64 => "UNSUPPORTED_POPO_METHOD", + 64424509557u64 => "BAD_ALGORITHM_NAME", + 64424509558u64 => "CONFLICTING_NAMES", + 64424509561u64 => "HEX_STRING_TOO_SHORT", + 64424509542u64 => "ILLEGAL_HEX_DIGIT", + 64424509546u64 => "INSUFFICIENT_DATA_SPACE", + 64424509547u64 => "INSUFFICIENT_PARAM_SIZE", + 64424509548u64 => "INSUFFICIENT_SECURE_DATA_SPACE", + 64424509567u64 => "INTEGER_OVERFLOW", + 64424509562u64 => "INVALID_NEGATIVE_VALUE", + 64424509549u64 => "INVALID_NULL_ARGUMENT", + 64424509550u64 => "INVALID_OSSL_PARAM_TYPE", + 64424509571u64 => "NO_PARAMS_TO_MERGE", + 64424509568u64 => "NO_SPACE_FOR_TERMINATING_NULL", + 64424509543u64 => "ODD_NUMBER_OF_DIGITS", + 64424509563u64 => "PARAM_CANNOT_BE_REPRESENTED_EXACTLY", + 64424509564u64 => "PARAM_NOT_INTEGER_TYPE", + 64424509569u64 => "PARAM_OF_INCOMPATIBLE_TYPE", + 64424509565u64 => "PARAM_UNSIGNED_INTEGER_NEGATIVE_VALUE_UNSUPPORTED", + 64424509570u64 => "PARAM_UNSUPPORTED_FLOATING_POINT_FORMAT", + 64424509566u64 => "PARAM_VALUE_TOO_LARGE_FOR_DESTINATION", + 64424509544u64 => "PROVIDER_ALREADY_EXISTS", + 64424509545u64 => "PROVIDER_SECTION_ERROR", + 64424509559u64 => "RANDOM_SECTION_ERROR", + 64424509551u64 => "SECURE_MALLOC_FAILURE", + 64424509552u64 => "STRING_TOO_LONG", + 64424509553u64 => "TOO_MANY_BYTES", + 64424509554u64 => "TOO_MANY_RECORDS", + 64424509556u64 => "TOO_SMALL_BUFFER", + 64424509560u64 => "UNKNOWN_NAME_IN_RANDOM_SECTION", + 64424509555u64 => "ZERO_LENGTH_NUMBER", + 214748364908u64 => "BASE64_DECODE_ERROR", + 214748364900u64 => "INVALID_LOG_ID_LENGTH", + 214748364909u64 => "LOG_CONF_INVALID", + 214748364910u64 => "LOG_CONF_INVALID_KEY", + 214748364911u64 => "LOG_CONF_MISSING_DESCRIPTION", + 214748364912u64 => "LOG_CONF_MISSING_KEY", + 214748364913u64 => "LOG_KEY_INVALID", + 214748364916u64 => "SCT_FUTURE_TIMESTAMP", + 214748364904u64 => "SCT_INVALID", + 214748364907u64 => "SCT_INVALID_SIGNATURE", + 214748364905u64 => "SCT_LIST_INVALID", + 214748364914u64 => "SCT_LOG_ID_MISMATCH", + 214748364906u64 => "SCT_NOT_SET", + 214748364915u64 => "SCT_UNSUPPORTED_VERSION", + 214748364901u64 => "UNRECOGNIZED_SIGNATURE_NID", + 214748364902u64 => "UNSUPPORTED_ENTRY_TYPE", + 214748364903u64 => "UNSUPPORTED_VERSION", + 21474836607u64 => "BAD_FFC_PARAMETERS", + 21474836581u64 => "BAD_GENERATOR", + 21474836589u64 => "BN_DECODE_ERROR", + 21474836586u64 => "BN_ERROR", + 21474836595u64 => "CHECK_INVALID_J_VALUE", + 21474836596u64 => "CHECK_INVALID_Q_VALUE", + 21474836602u64 => "CHECK_PUBKEY_INVALID", + 21474836603u64 => "CHECK_PUBKEY_TOO_LARGE", + 21474836604u64 => "CHECK_PUBKEY_TOO_SMALL", + 21474836597u64 => "CHECK_P_NOT_PRIME", + 21474836598u64 => "CHECK_P_NOT_SAFE_PRIME", + 21474836599u64 => "CHECK_Q_NOT_PRIME", + 21474836584u64 => "DECODE_ERROR", + 21474836590u64 => "INVALID_PARAMETER_NAME", + 21474836594u64 => "INVALID_PARAMETER_NID", + 21474836582u64 => "INVALID_PUBKEY", + 21474836608u64 => "INVALID_SECRET", + 21474836592u64 => "KDF_PARAMETER_ERROR", + 21474836588u64 => "KEYS_NOT_SET", + 21474836605u64 => "MISSING_PUBKEY", + 21474836583u64 => "MODULUS_TOO_LARGE", + 21474836606u64 => "MODULUS_TOO_SMALL", + 21474836600u64 => "NOT_SUITABLE_GENERATOR", + 21474836587u64 => "NO_PARAMETERS_SET", + 21474836580u64 => "NO_PRIVATE_VALUE", + 21474836585u64 => "PARAMETER_ENCODING_ERROR", + 21474836591u64 => "PEER_KEY_ERROR", + 21474836610u64 => "Q_TOO_LARGE", + 21474836593u64 => "SHARED_INFO_ERROR", + 21474836601u64 => "UNABLE_TO_CHECK_GENERATOR", + 42949673074u64 => "BAD_FFC_PARAMETERS", + 42949673062u64 => "BAD_Q_VALUE", + 42949673068u64 => "BN_DECODE_ERROR", + 42949673069u64 => "BN_ERROR", + 42949673064u64 => "DECODE_ERROR", + 42949673066u64 => "INVALID_DIGEST_TYPE", + 42949673072u64 => "INVALID_PARAMETERS", + 42949673061u64 => "MISSING_PARAMETERS", + 42949673071u64 => "MISSING_PRIVATE_KEY", + 42949673063u64 => "MODULUS_TOO_LARGE", + 42949673067u64 => "NO_PARAMETERS_SET", + 42949673065u64 => "PARAMETER_ENCODING_ERROR", + 42949673075u64 => "P_NOT_PRIME", + 42949673073u64 => "Q_NOT_PRIME", + 42949673070u64 => "SEED_LEN_SMALL", + 42949673076u64 => "TOO_MANY_RETRIES", + 158913790052u64 => "CTRL_FAILED", + 158913790062u64 => "DSO_ALREADY_LOADED", + 158913790065u64 => "EMPTY_FILE_STRUCTURE", + 158913790066u64 => "FAILURE", + 158913790053u64 => "FILENAME_TOO_BIG", + 158913790054u64 => "FINISH_FAILED", + 158913790067u64 => "INCORRECT_FILE_SYNTAX", + 158913790055u64 => "LOAD_FAILED", + 158913790061u64 => "NAME_TRANSLATION_FAILED", + 158913790063u64 => "NO_FILENAME", + 158913790056u64 => "NULL_HANDLE", + 158913790064u64 => "SET_FILENAME_FAILED", + 158913790057u64 => "STACK_ERROR", + 158913790058u64 => "SYM_FAILURE", + 158913790059u64 => "UNLOAD_FAILED", + 158913790060u64 => "UNSUPPORTED", + 68719476851u64 => "ASN1_ERROR", + 68719476892u64 => "BAD_SIGNATURE", + 68719476880u64 => "BIGNUM_OUT_OF_RANGE", + 68719476836u64 => "BUFFER_TOO_SMALL", + 68719476901u64 => "CANNOT_INVERT", + 68719476882u64 => "COORDINATES_OUT_OF_RANGE", + 68719476896u64 => "CURVE_DOES_NOT_SUPPORT_ECDH", + 68719476906u64 => "CURVE_DOES_NOT_SUPPORT_ECDSA", + 68719476895u64 => "CURVE_DOES_NOT_SUPPORT_SIGNING", + 68719476878u64 => "DECODE_ERROR", + 68719476854u64 => "DISCRIMINANT_IS_ZERO", + 68719476855u64 => "EC_GROUP_NEW_BY_NAME_FAILURE", + 68719476863u64 => "EXPLICIT_PARAMS_NOT_SUPPORTED", + 68719476902u64 => "FAILED_MAKING_PUBLIC_KEY", + 68719476879u64 => "FIELD_TOO_LARGE", + 68719476883u64 => "GF2M_NOT_SUPPORTED", + 68719476856u64 => "GROUP2PKPARAMETERS_FAILURE", + 68719476857u64 => "I2D_ECPKPARAMETERS_FAILURE", + 68719476837u64 => "INCOMPATIBLE_OBJECTS", + 68719476904u64 => "INVALID_A", + 68719476848u64 => "INVALID_ARGUMENT", + 68719476905u64 => "INVALID_B", + 68719476907u64 => "INVALID_COFACTOR", + 68719476846u64 => "INVALID_COMPRESSED_POINT", + 68719476845u64 => "INVALID_COMPRESSION_BIT", + 68719476877u64 => "INVALID_CURVE", + 68719476887u64 => "INVALID_DIGEST", + 68719476874u64 => "INVALID_DIGEST_TYPE", + 68719476838u64 => "INVALID_ENCODING", + 68719476839u64 => "INVALID_FIELD", + 68719476840u64 => "INVALID_FORM", + 68719476909u64 => "INVALID_GENERATOR", + 68719476858u64 => "INVALID_GROUP_ORDER", + 68719476852u64 => "INVALID_KEY", + 68719476853u64 => "INVALID_LENGTH", + 68719476910u64 => "INVALID_NAMED_GROUP_CONVERSION", + 68719476897u64 => "INVALID_OUTPUT_LENGTH", + 68719476908u64 => "INVALID_P", + 68719476869u64 => "INVALID_PEER_KEY", + 68719476868u64 => "INVALID_PENTANOMIAL_BASIS", + 68719476859u64 => "INVALID_PRIVATE_KEY", + 68719476911u64 => "INVALID_SEED", + 68719476873u64 => "INVALID_TRINOMIAL_BASIS", + 68719476884u64 => "KDF_PARAMETER_ERROR", + 68719476876u64 => "KEYS_NOT_SET", + 68719476872u64 => "LADDER_POST_FAILURE", + 68719476889u64 => "LADDER_PRE_FAILURE", + 68719476898u64 => "LADDER_STEP_FAILURE", + 68719476903u64 => "MISSING_OID", + 68719476860u64 => "MISSING_PARAMETERS", + 68719476861u64 => "MISSING_PRIVATE_KEY", + 68719476893u64 => "NEED_NEW_SETUP_VALUES", + 68719476871u64 => "NOT_A_NIST_PRIME", + 68719476862u64 => "NOT_IMPLEMENTED", + 68719476847u64 => "NOT_INITIALIZED", + 68719476875u64 => "NO_PARAMETERS_SET", + 68719476890u64 => "NO_PRIVATE_VALUE", + 68719476888u64 => "OPERATION_NOT_SUPPORTED", + 68719476870u64 => "PASSED_NULL_PARAMETER", + 68719476885u64 => "PEER_KEY_ERROR", + 68719476891u64 => "POINT_ARITHMETIC_FAILURE", + 68719476842u64 => "POINT_AT_INFINITY", + 68719476899u64 => "POINT_COORDINATES_BLIND_FAILURE", + 68719476843u64 => "POINT_IS_NOT_ON_CURVE", + 68719476894u64 => "RANDOM_NUMBER_GENERATION_FAILED", + 68719476886u64 => "SHARED_INFO_ERROR", + 68719476844u64 => "SLOT_FULL", + 68719476912u64 => "TOO_MANY_RETRIES", + 68719476849u64 => "UNDEFINED_GENERATOR", + 68719476864u64 => "UNDEFINED_ORDER", + 68719476900u64 => "UNKNOWN_COFACTOR", + 68719476865u64 => "UNKNOWN_GROUP", + 68719476850u64 => "UNKNOWN_ORDER", + 68719476867u64 => "UNSUPPORTED_FIELD", + 68719476881u64 => "WRONG_CURVE_PARAMETERS", + 68719476866u64 => "WRONG_ORDER", + 163208757348u64 => "ALREADY_LOADED", + 163208757381u64 => "ARGUMENT_IS_NOT_A_NUMBER", + 163208757382u64 => "CMD_NOT_EXECUTABLE", + 163208757383u64 => "COMMAND_TAKES_INPUT", + 163208757384u64 => "COMMAND_TAKES_NO_INPUT", + 163208757351u64 => "CONFLICTING_ENGINE_ID", + 163208757367u64 => "CTRL_COMMAND_NOT_IMPLEMENTED", + 163208757352u64 => "DSO_FAILURE", + 163208757380u64 => "DSO_NOT_FOUND", + 163208757396u64 => "ENGINES_SECTION_ERROR", + 163208757350u64 => "ENGINE_CONFIGURATION_ERROR", + 163208757353u64 => "ENGINE_IS_NOT_IN_LIST", + 163208757397u64 => "ENGINE_SECTION_ERROR", + 163208757376u64 => "FAILED_LOADING_PRIVATE_KEY", + 163208757377u64 => "FAILED_LOADING_PUBLIC_KEY", + 163208757354u64 => "FINISH_FAILED", + 163208757356u64 => "ID_OR_NAME_MISSING", + 163208757357u64 => "INIT_FAILED", + 163208757358u64 => "INTERNAL_LIST_ERROR", + 163208757391u64 => "INVALID_ARGUMENT", + 163208757385u64 => "INVALID_CMD_NAME", + 163208757386u64 => "INVALID_CMD_NUMBER", + 163208757399u64 => "INVALID_INIT_VALUE", + 163208757398u64 => "INVALID_STRING", + 163208757365u64 => "NOT_INITIALISED", + 163208757360u64 => "NOT_LOADED", + 163208757368u64 => "NO_CONTROL_FUNCTION", + 163208757392u64 => "NO_INDEX", + 163208757373u64 => "NO_LOAD_FUNCTION", + 163208757378u64 => "NO_REFERENCE", + 163208757364u64 => "NO_SUCH_ENGINE", + 163208757394u64 => "UNIMPLEMENTED_CIPHER", + 163208757395u64 => "UNIMPLEMENTED_DIGEST", + 163208757349u64 => "UNIMPLEMENTED_PUBLIC_KEY_METHOD", + 163208757393u64 => "VERSION_INCOMPATIBILITY", + 231928234091u64 => "EMPTY_ESS_CERT_ID_LIST", + 231928234087u64 => "ESS_CERT_DIGEST_ERROR", + 231928234088u64 => "ESS_CERT_ID_NOT_FOUND", + 231928234089u64 => "ESS_CERT_ID_WRONG_ORDER", + 231928234090u64 => "ESS_DIGEST_ALG_UNKNOWN", + 231928234086u64 => "ESS_SIGNING_CERTIFICATE_ERROR", + 231928234084u64 => "ESS_SIGNING_CERT_ADD_ERROR", + 231928234085u64 => "ESS_SIGNING_CERT_V2_ADD_ERROR", + 231928234092u64 => "MISSING_SIGNING_CERTIFICATE_ATTRIBUTE", + 25769803919u64 => "AES_KEY_SETUP_FAILED", + 25769803952u64 => "ARIA_KEY_SETUP_FAILED", + 25769803976u64 => "BAD_ALGORITHM_NAME", + 25769803876u64 => "BAD_DECRYPT", + 25769803971u64 => "BAD_KEY_LENGTH", + 25769803931u64 => "BUFFER_TOO_SMALL", + 25769804001u64 => "CACHE_CONSTANTS_FAILED", + 25769803933u64 => "CAMELLIA_KEY_SETUP_FAILED", + 25769803973u64 => "CANNOT_GET_PARAMETERS", + 25769803974u64 => "CANNOT_SET_PARAMETERS", + 25769803960u64 => "CIPHER_NOT_GCM_MODE", + 25769803898u64 => "CIPHER_PARAMETER_ERROR", + 25769803923u64 => "COMMAND_NOT_SUPPORTED", + 25769803977u64 => "CONFLICTING_ALGORITHM_NAME", + 25769803949u64 => "COPY_ERROR", + 25769803908u64 => "CTRL_NOT_IMPLEMENTED", + 25769803909u64 => "CTRL_OPERATION_NOT_IMPLEMENTED", + 25769803914u64 => "DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH", + 25769803890u64 => "DECODE_ERROR", + 25769803986u64 => "DEFAULT_QUERY_PARSE_ERROR", + 25769803877u64 => "DIFFERENT_KEY_TYPES", + 25769803929u64 => "DIFFERENT_PARAMETERS", + 25769803941u64 => "ERROR_LOADING_SECTION", + 25769803950u64 => "EXPECTING_AN_HMAC_KEY", + 25769803903u64 => "EXPECTING_AN_RSA_KEY", + 25769803904u64 => "EXPECTING_A_DH_KEY", + 25769803905u64 => "EXPECTING_A_DSA_KEY", + 25769803995u64 => "EXPECTING_A_ECX_KEY", + 25769803918u64 => "EXPECTING_A_EC_KEY", + 25769803940u64 => "EXPECTING_A_POLY1305_KEY", + 25769803951u64 => "EXPECTING_A_SIPHASH_KEY", + 25769803964u64 => "FINAL_ERROR", + 25769803990u64 => "GENERATE_ERROR", + 25769803958u64 => "GET_RAW_KEY_FAILED", + 25769803947u64 => "ILLEGAL_SCRYPT_PARAMETERS", + 25769803980u64 => "INACCESSIBLE_DOMAIN_PARAMETERS", + 25769803979u64 => "INACCESSIBLE_KEY", + 25769803910u64 => "INITIALIZATION_ERROR", + 25769803887u64 => "INPUT_NOT_INITIALIZED", + 25769803961u64 => "INVALID_CUSTOM_LENGTH", + 25769803928u64 => "INVALID_DIGEST", + 25769803970u64 => "INVALID_IV_LENGTH", + 25769803939u64 => "INVALID_KEY", + 25769803906u64 => "INVALID_KEY_LENGTH", + 25769803997u64 => "INVALID_LENGTH", + 25769803994u64 => "INVALID_NULL_ALGORITHM", + 25769803924u64 => "INVALID_OPERATION", + 25769803969u64 => "INVALID_PROVIDER_FUNCTIONS", + 25769803962u64 => "INVALID_SALT_LENGTH", + 25769803999u64 => "INVALID_SECRET_LENGTH", + 25769803996u64 => "INVALID_SEED_LENGTH", + 25769803998u64 => "INVALID_VALUE", + 25769803981u64 => "KEYMGMT_EXPORT_FAILURE", + 25769803956u64 => "KEY_SETUP_FAILED", + 25769803989u64 => "LOCKING_NOT_SUPPORTED", + 25769803948u64 => "MEMORY_LIMIT_EXCEEDED", + 25769803935u64 => "MESSAGE_DIGEST_IS_NULL", + 25769803920u64 => "METHOD_NOT_SUPPORTED", + 25769803879u64 => "MISSING_PARAMETERS", + 25769803966u64 => "NOT_ABLE_TO_COPY_CTX", + 25769803954u64 => "NOT_XOF_OR_INVALID_LENGTH", + 25769803907u64 => "NO_CIPHER_SET", + 25769803934u64 => "NO_DEFAULT_DIGEST", + 25769803915u64 => "NO_DIGEST_SET", + 25769803982u64 => "NO_IMPORT_FUNCTION", + 25769803975u64 => "NO_KEYMGMT_AVAILABLE", + 25769803972u64 => "NO_KEYMGMT_PRESENT", + 25769803930u64 => "NO_KEY_SET", + 25769803925u64 => "NO_OPERATION_SET", + 25769803984u64 => "NULL_MAC_PKEY_CTX", + 25769803953u64 => "ONLY_ONESHOT_SUPPORTED", + 25769803927u64 => "OPERATION_NOT_INITIALIZED", + 25769803926u64 => "OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE", + 25769803978u64 => "OUTPUT_WOULD_OVERFLOW", + 25769803963u64 => "PARAMETER_TOO_LARGE", + 25769803938u64 => "PARTIALLY_OVERLAPPING", + 25769803957u64 => "PBKDF2_ERROR", + 25769803955u64 => "PKEY_APPLICATION_ASN1_METHOD_ALREADY_REGISTERED", + 25769803921u64 => "PRIVATE_KEY_DECODE_ERROR", + 25769803922u64 => "PRIVATE_KEY_ENCODE_ERROR", + 25769803882u64 => "PUBLIC_KEY_NOT_RSA", + 25769804003u64 => "SETTING_XOF_FAILED", + 25769803985u64 => "SET_DEFAULT_PROPERTY_FAILURE", + 25769803959u64 => "TOO_MANY_RECORDS", + 25769803988u64 => "UNABLE_TO_ENABLE_LOCKING", + 25769803991u64 => "UNABLE_TO_GET_MAXIMUM_REQUEST_SIZE", + 25769803992u64 => "UNABLE_TO_GET_RANDOM_STRENGTH", + 25769803987u64 => "UNABLE_TO_LOCK_CONTEXT", + 25769803993u64 => "UNABLE_TO_SET_CALLBACKS", + 25769803936u64 => "UNKNOWN_CIPHER", + 25769803937u64 => "UNKNOWN_DIGEST", + 25769803983u64 => "UNKNOWN_KEY_TYPE", + 25769803945u64 => "UNKNOWN_OPTION", + 25769803897u64 => "UNKNOWN_PBE_ALGORITHM", + 25769803932u64 => "UNSUPPORTED_ALGORITHM", + 25769803883u64 => "UNSUPPORTED_CIPHER", + 25769803899u64 => "UNSUPPORTED_KEYLENGTH", + 25769803900u64 => "UNSUPPORTED_KEY_DERIVATION_FUNCTION", + 25769803884u64 => "UNSUPPORTED_KEY_SIZE", + 25769804000u64 => "UNSUPPORTED_KEY_TYPE", + 25769803911u64 => "UNSUPPORTED_NUMBER_OF_ROUNDS", + 25769803901u64 => "UNSUPPORTED_PRF", + 25769803894u64 => "UNSUPPORTED_PRIVATE_KEY_ALGORITHM", + 25769803902u64 => "UNSUPPORTED_SALT_TYPE", + 25769803965u64 => "UPDATE_ERROR", + 25769803946u64 => "WRAP_MODE_NOT_ALLOWED", + 25769803885u64 => "WRONG_FINAL_BLOCK_LENGTH", + 25769803967u64 => "XTS_DATA_UNIT_IS_TOO_LARGE", + 25769803968u64 => "XTS_DUPLICATED_KEYS", + 261993005164u64 => "ASN1_LEN_EXCEEDS_MAX_RESP_LEN", + 261993005156u64 => "CONNECT_FAILURE", + 261993005165u64 => "ERROR_PARSING_ASN1_LENGTH", + 261993005175u64 => "ERROR_PARSING_CONTENT_LENGTH", + 261993005157u64 => "ERROR_PARSING_URL", + 261993005159u64 => "ERROR_RECEIVING", + 261993005158u64 => "ERROR_SENDING", + 261993005184u64 => "FAILED_READING_DATA", + 261993005182u64 => "HEADER_PARSE_ERROR", + 261993005176u64 => "INCONSISTENT_CONTENT_LENGTH", + 261993005179u64 => "INVALID_PORT_NUMBER", + 261993005181u64 => "INVALID_URL_PATH", + 261993005180u64 => "INVALID_URL_SCHEME", + 261993005173u64 => "MAX_RESP_LEN_EXCEEDED", + 261993005166u64 => "MISSING_ASN1_ENCODING", + 261993005177u64 => "MISSING_CONTENT_TYPE", + 261993005167u64 => "MISSING_REDIRECT_LOCATION", + 261993005161u64 => "RECEIVED_ERROR", + 261993005162u64 => "RECEIVED_WRONG_HTTP_VERSION", + 261993005168u64 => "REDIRECTION_FROM_HTTPS_TO_HTTP", + 261993005172u64 => "REDIRECTION_NOT_ENABLED", + 261993005169u64 => "RESPONSE_LINE_TOO_LONG", + 261993005160u64 => "RESPONSE_PARSE_ERROR", + 261993005185u64 => "RETRY_TIMEOUT", + 261993005183u64 => "SERVER_CANCELED_CONNECTION", + 261993005178u64 => "SOCK_NOT_SUPPORTED", + 261993005170u64 => "STATUS_CODE_UNSUPPORTED", + 261993005163u64 => "TLS_NOT_ENABLED", + 261993005171u64 => "TOO_MANY_REDIRECTIONS", + 261993005174u64 => "UNEXPECTED_CONTENT_TYPE", + 34359738470u64 => "OID_EXISTS", + 34359738469u64 => "UNKNOWN_NID", + 34359738471u64 => "UNKNOWN_OBJECT_NAME", + 167503724645u64 => "CERTIFICATE_VERIFY_ERROR", + 167503724646u64 => "DIGEST_ERR", + 167503724650u64 => "DIGEST_NAME_ERR", + 167503724651u64 => "DIGEST_SIZE_ERR", + 167503724666u64 => "ERROR_IN_NEXTUPDATE_FIELD", + 167503724667u64 => "ERROR_IN_THISUPDATE_FIELD", + 167503724647u64 => "MISSING_OCSPSIGNING_USAGE", + 167503724668u64 => "NEXTUPDATE_BEFORE_THISUPDATE", + 167503724648u64 => "NOT_BASIC_RESPONSE", + 167503724649u64 => "NO_CERTIFICATES_IN_CHAIN", + 167503724652u64 => "NO_RESPONSE_DATA", + 167503724653u64 => "NO_REVOKED_TIME", + 167503724674u64 => "NO_SIGNER_KEY", + 167503724654u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 167503724672u64 => "REQUEST_NOT_SIGNED", + 167503724655u64 => "RESPONSE_CONTAINS_NO_REVOCATION_DATA", + 167503724656u64 => "ROOT_CA_NOT_TRUSTED", + 167503724661u64 => "SIGNATURE_FAILURE", + 167503724662u64 => "SIGNER_CERTIFICATE_NOT_FOUND", + 167503724669u64 => "STATUS_EXPIRED", + 167503724670u64 => "STATUS_NOT_YET_VALID", + 167503724671u64 => "STATUS_TOO_OLD", + 167503724663u64 => "UNKNOWN_MESSAGE_DIGEST", + 167503724664u64 => "UNKNOWN_NID", + 167503724673u64 => "UNSUPPORTED_REQUESTORNAME_TYPE", + 257698037861u64 => "COULD_NOT_DECODE_OBJECT", + 257698037862u64 => "DECODER_NOT_FOUND", + 257698037860u64 => "MISSING_GET_PARAMS", + 253403070565u64 => "ENCODER_NOT_FOUND", + 253403070564u64 => "INCORRECT_PROPERTY_QUERY", + 253403070566u64 => "MISSING_GET_PARAMS", + 188978561131u64 => "AMBIGUOUS_CONTENT_TYPE", + 188978561139u64 => "BAD_PASSWORD_READ", + 188978561137u64 => "ERROR_VERIFYING_PKCS12_MAC", + 188978561145u64 => "FINGERPRINT_SIZE_DOES_NOT_MATCH_DIGEST", + 188978561130u64 => "INVALID_SCHEME", + 188978561136u64 => "IS_NOT_A", + 188978561140u64 => "LOADER_INCOMPLETE", + 188978561141u64 => "LOADING_STARTED", + 188978561124u64 => "NOT_A_CERTIFICATE", + 188978561125u64 => "NOT_A_CRL", + 188978561127u64 => "NOT_A_NAME", + 188978561126u64 => "NOT_A_PRIVATE_KEY", + 188978561146u64 => "NOT_A_PUBLIC_KEY", + 188978561128u64 => "NOT_PARAMETERS", + 188978561147u64 => "NO_LOADERS_FOUND", + 188978561138u64 => "PASSPHRASE_CALLBACK_ERROR", + 188978561132u64 => "PATH_MUST_BE_ABSOLUTE", + 188978561143u64 => "SEARCH_ONLY_SUPPORTED_FOR_DIRECTORIES", + 188978561133u64 => "UI_PROCESS_INTERRUPTED_OR_CANCELLED", + 188978561129u64 => "UNREGISTERED_SCHEME", + 188978561134u64 => "UNSUPPORTED_CONTENT_TYPE", + 188978561142u64 => "UNSUPPORTED_OPERATION", + 188978561144u64 => "UNSUPPORTED_SEARCH_TYPE", + 188978561135u64 => "URI_AUTHORITY_UNSUPPORTED", + 38654705764u64 => "BAD_BASE64_DECODE", + 38654705765u64 => "BAD_DECRYPT", + 38654705766u64 => "BAD_END_LINE", + 38654705767u64 => "BAD_IV_CHARS", + 38654705780u64 => "BAD_MAGIC_NUMBER", + 38654705768u64 => "BAD_PASSWORD_READ", + 38654705781u64 => "BAD_VERSION_NUMBER", + 38654705782u64 => "BIO_WRITE_FAILURE", + 38654705791u64 => "CIPHER_IS_NULL", + 38654705779u64 => "ERROR_CONVERTING_PRIVATE_KEY", + 38654705795u64 => "EXPECTING_DSS_KEY_BLOB", + 38654705783u64 => "EXPECTING_PRIVATE_KEY_BLOB", + 38654705784u64 => "EXPECTING_PUBLIC_KEY_BLOB", + 38654705796u64 => "EXPECTING_RSA_KEY_BLOB", + 38654705792u64 => "HEADER_TOO_LONG", + 38654705785u64 => "INCONSISTENT_HEADER", + 38654705786u64 => "KEYBLOB_HEADER_PARSE_ERROR", + 38654705787u64 => "KEYBLOB_TOO_SHORT", + 38654705793u64 => "MISSING_DEK_IV", + 38654705769u64 => "NOT_DEK_INFO", + 38654705770u64 => "NOT_ENCRYPTED", + 38654705771u64 => "NOT_PROC_TYPE", + 38654705772u64 => "NO_START_LINE", + 38654705773u64 => "PROBLEMS_GETTING_PASSWORD", + 38654705788u64 => "PVK_DATA_TOO_SHORT", + 38654705789u64 => "PVK_TOO_SHORT", + 38654705775u64 => "READ_KEY", + 38654705776u64 => "SHORT_HEADER", + 38654705794u64 => "UNEXPECTED_DEK_IV", + 38654705777u64 => "UNSUPPORTED_CIPHER", + 38654705778u64 => "UNSUPPORTED_ENCRYPTION", + 38654705790u64 => "UNSUPPORTED_KEY_COMPONENTS", + 38654705774u64 => "UNSUPPORTED_PUBLIC_KEY_TYPE", + 150323855460u64 => "CANT_PACK_STRUCTURE", + 150323855481u64 => "CONTENT_TYPE_NOT_DATA", + 150323855461u64 => "DECODE_ERROR", + 150323855462u64 => "ENCODE_ERROR", + 150323855463u64 => "ENCRYPT_ERROR", + 150323855480u64 => "ERROR_SETTING_ENCRYPTED_DATA_TYPE", + 150323855464u64 => "INVALID_NULL_ARGUMENT", + 150323855465u64 => "INVALID_NULL_PKCS12_POINTER", + 150323855472u64 => "INVALID_TYPE", + 150323855466u64 => "IV_GEN_ERROR", + 150323855467u64 => "KEY_GEN_ERROR", + 150323855468u64 => "MAC_ABSENT", + 150323855469u64 => "MAC_GENERATION_ERROR", + 150323855470u64 => "MAC_SETUP_ERROR", + 150323855471u64 => "MAC_STRING_SET_ERROR", + 150323855473u64 => "MAC_VERIFY_FAILURE", + 150323855474u64 => "PARSE_ERROR", + 150323855476u64 => "PKCS12_CIPHERFINAL_ERROR", + 150323855478u64 => "UNKNOWN_DIGEST_ALGORITHM", + 150323855479u64 => "UNSUPPORTED_PKCS12_MODE", + 141733920885u64 => "CERTIFICATE_VERIFY_ERROR", + 141733920912u64 => "CIPHER_HAS_NO_OBJECT_IDENTIFIER", + 141733920884u64 => "CIPHER_NOT_INITIALIZED", + 141733920886u64 => "CONTENT_AND_DATA_PRESENT", + 141733920920u64 => "CTRL_ERROR", + 141733920887u64 => "DECRYPT_ERROR", + 141733920869u64 => "DIGEST_FAILURE", + 141733920917u64 => "ENCRYPTION_CTRL_FAILURE", + 141733920918u64 => "ENCRYPTION_NOT_SUPPORTED_FOR_THIS_KEY_TYPE", + 141733920888u64 => "ERROR_ADDING_RECIPIENT", + 141733920889u64 => "ERROR_SETTING_CIPHER", + 141733920911u64 => "INVALID_NULL_POINTER", + 141733920923u64 => "INVALID_SIGNED_DATA_TYPE", + 141733920890u64 => "NO_CONTENT", + 141733920919u64 => "NO_DEFAULT_DIGEST", + 141733920922u64 => "NO_MATCHING_DIGEST_TYPE_FOUND", + 141733920883u64 => "NO_RECIPIENT_MATCHES_CERTIFICATE", + 141733920891u64 => "NO_SIGNATURES_ON_DATA", + 141733920910u64 => "NO_SIGNERS", + 141733920872u64 => "OPERATION_NOT_SUPPORTED_ON_THIS_TYPE", + 141733920892u64 => "PKCS7_ADD_SIGNATURE_ERROR", + 141733920921u64 => "PKCS7_ADD_SIGNER_ERROR", + 141733920913u64 => "PKCS7_DATASIGN", + 141733920895u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 141733920873u64 => "SIGNATURE_FAILURE", + 141733920896u64 => "SIGNER_CERTIFICATE_NOT_FOUND", + 141733920915u64 => "SIGNING_CTRL_FAILURE", + 141733920916u64 => "SIGNING_NOT_SUPPORTED_FOR_THIS_KEY_TYPE", + 141733920897u64 => "SMIME_TEXT_ERROR", + 141733920874u64 => "UNABLE_TO_FIND_CERTIFICATE", + 141733920875u64 => "UNABLE_TO_FIND_MEM_BIO", + 141733920876u64 => "UNABLE_TO_FIND_MESSAGE_DIGEST", + 141733920877u64 => "UNKNOWN_DIGEST_TYPE", + 141733920878u64 => "UNKNOWN_OPERATION", + 141733920879u64 => "UNSUPPORTED_CIPHER_TYPE", + 141733920880u64 => "UNSUPPORTED_CONTENT_TYPE", + 141733920881u64 => "WRONG_CONTENT_TYPE", + 141733920882u64 => "WRONG_PKCS7_TYPE", + 236223201380u64 => "NAME_TOO_LONG", + 236223201381u64 => "NOT_AN_ASCII_CHARACTER", + 236223201382u64 => "NOT_AN_HEXADECIMAL_DIGIT", + 236223201383u64 => "NOT_AN_IDENTIFIER", + 236223201384u64 => "NOT_AN_OCTAL_DIGIT", + 236223201385u64 => "NOT_A_DECIMAL_DIGIT", + 236223201386u64 => "NO_MATCHING_STRING_DELIMITER", + 236223201387u64 => "NO_VALUE", + 236223201388u64 => "PARSE_FAILED", + 236223201389u64 => "STRING_TOO_LONG", + 236223201390u64 => "TRAILING_CHARACTERS", + 244813136056u64 => "ADDITIONAL_INPUT_TOO_LONG", + 244813136045u64 => "ALGORITHM_MISMATCH", + 244813136057u64 => "ALREADY_INSTANTIATED", + 244813135972u64 => "BAD_DECRYPT", + 244813136013u64 => "BAD_ENCODING", + 244813136014u64 => "BAD_LENGTH", + 244813136033u64 => "BAD_TLS_CLIENT_VERSION", + 244813136032u64 => "BN_ERROR", + 244813135974u64 => "CIPHER_OPERATION_FAILED", + 244813136077u64 => "DERIVATION_FUNCTION_INIT_FAILED", + 244813136046u64 => "DIGEST_NOT_ALLOWED", + 244813136105u64 => "EMS_NOT_ENABLED", + 244813136058u64 => "ENTROPY_SOURCE_STRENGTH_TOO_WEAK", + 244813136060u64 => "ERROR_INSTANTIATING_DRBG", + 244813136061u64 => "ERROR_RETRIEVING_ENTROPY", + 244813136062u64 => "ERROR_RETRIEVING_NONCE", + 244813136036u64 => "FAILED_DURING_DERIVATION", + 244813136052u64 => "FAILED_TO_CREATE_LOCK", + 244813136034u64 => "FAILED_TO_DECRYPT", + 244813135993u64 => "FAILED_TO_GENERATE_KEY", + 244813135975u64 => "FAILED_TO_GET_PARAMETER", + 244813135976u64 => "FAILED_TO_SET_PARAMETER", + 244813136047u64 => "FAILED_TO_SIGN", + 244813136099u64 => "FIPS_MODULE_CONDITIONAL_ERROR", + 244813136096u64 => "FIPS_MODULE_ENTERING_ERROR_STATE", + 244813136097u64 => "FIPS_MODULE_IN_ERROR_STATE", + 244813136063u64 => "GENERATE_ERROR", + 244813136037u64 => "ILLEGAL_OR_UNSUPPORTED_PADDING_MODE", + 244813136082u64 => "INDICATOR_INTEGRITY_FAILURE", + 244813136053u64 => "INSUFFICIENT_DRBG_STRENGTH", + 244813135980u64 => "INVALID_AAD", + 244813136083u64 => "INVALID_CONFIG_DATA", + 244813136029u64 => "INVALID_CONSTANT_LENGTH", + 244813136048u64 => "INVALID_CURVE", + 244813135983u64 => "INVALID_CUSTOM_LENGTH", + 244813135987u64 => "INVALID_DATA", + 244813135994u64 => "INVALID_DIGEST", + 244813136038u64 => "INVALID_DIGEST_LENGTH", + 244813136090u64 => "INVALID_DIGEST_SIZE", + 244813136102u64 => "INVALID_INPUT_LENGTH", + 244813135995u64 => "INVALID_ITERATION_COUNT", + 244813135981u64 => "INVALID_IV_LENGTH", + 244813136030u64 => "INVALID_KEY", + 244813135977u64 => "INVALID_KEY_LENGTH", + 244813136023u64 => "INVALID_MAC", + 244813136039u64 => "INVALID_MGF1_MD", + 244813135997u64 => "INVALID_MODE", + 244813136089u64 => "INVALID_OUTPUT_LENGTH", + 244813136040u64 => "INVALID_PADDING_MODE", + 244813136070u64 => "INVALID_PUBINFO", + 244813135984u64 => "INVALID_SALT_LENGTH", + 244813136026u64 => "INVALID_SEED_LENGTH", + 244813136051u64 => "INVALID_SIGNATURE_SIZE", + 244813136084u64 => "INVALID_STATE", + 244813135982u64 => "INVALID_TAG", + 244813135990u64 => "INVALID_TAG_LENGTH", + 244813136072u64 => "INVALID_UKM_LENGTH", + 244813136042u64 => "INVALID_X931_DIGEST", + 244813136064u64 => "IN_ERROR_STATE", + 244813135973u64 => "KEY_SETUP_FAILED", + 244813136043u64 => "KEY_SIZE_TOO_SMALL", + 244813136074u64 => "LENGTH_TOO_LARGE", + 244813136075u64 => "MISMATCHING_DOMAIN_PARAMETERS", + 244813136016u64 => "MISSING_CEK_ALG", + 244813136027u64 => "MISSING_CIPHER", + 244813136085u64 => "MISSING_CONFIG_DATA", + 244813136028u64 => "MISSING_CONSTANT", + 244813136000u64 => "MISSING_KEY", + 244813136022u64 => "MISSING_MAC", + 244813136001u64 => "MISSING_MESSAGE_DIGEST", + 244813136081u64 => "MISSING_OID", + 244813136002u64 => "MISSING_PASS", + 244813136003u64 => "MISSING_SALT", + 244813136004u64 => "MISSING_SECRET", + 244813136012u64 => "MISSING_SEED", + 244813136005u64 => "MISSING_SESSION_ID", + 244813136006u64 => "MISSING_TYPE", + 244813136007u64 => "MISSING_XCGHASH", + 244813136086u64 => "MODULE_INTEGRITY_FAILURE", + 244813136093u64 => "NOT_A_PRIVATE_KEY", + 244813136092u64 => "NOT_A_PUBLIC_KEY", + 244813136065u64 => "NOT_INSTANTIATED", + 244813136098u64 => "NOT_PARAMETERS", + 244813136008u64 => "NOT_SUPPORTED", + 244813135985u64 => "NOT_XOF_OR_INVALID_LENGTH", + 244813135986u64 => "NO_KEY_SET", + 244813136049u64 => "NO_PARAMETERS_SET", + 244813136050u64 => "OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE", + 244813135978u64 => "OUTPUT_BUFFER_TOO_SMALL", + 244813136100u64 => "PARENT_CANNOT_GENERATE_RANDOM_NUMBERS", + 244813136059u64 => "PARENT_CANNOT_SUPPLY_ENTROPY_SEED", + 244813136054u64 => "PARENT_LOCKING_NOT_ENABLED", + 244813136066u64 => "PARENT_STRENGTH_TOO_WEAK", + 244813136091u64 => "PATH_MUST_BE_ABSOLUTE", + 244813136067u64 => "PERSONALISATION_STRING_TOO_LONG", + 244813136044u64 => "PSS_SALTLEN_TOO_SMALL", + 244813136068u64 => "REQUEST_TOO_LARGE_FOR_DRBG", + 244813136078u64 => "REQUIRE_CTR_MODE_CIPHER", + 244813136069u64 => "RESEED_ERROR", + 244813136094u64 => "SEARCH_ONLY_SUPPORTED_FOR_DIRECTORIES", + 244813136101u64 => "SEED_SOURCES_MUST_NOT_HAVE_A_PARENT", + 244813136087u64 => "SELF_TEST_KAT_FAILURE", + 244813136088u64 => "SELF_TEST_POST_FAILURE", + 244813135992u64 => "TAG_NOT_NEEDED", + 244813135991u64 => "TAG_NOT_SET", + 244813135998u64 => "TOO_MANY_RECORDS", + 244813136079u64 => "UNABLE_TO_FIND_CIPHERS", + 244813136071u64 => "UNABLE_TO_GET_PARENT_STRENGTH", + 244813136031u64 => "UNABLE_TO_GET_PASSPHRASE", + 244813136080u64 => "UNABLE_TO_INITIALISE_CIPHERS", + 244813136019u64 => "UNABLE_TO_LOAD_SHA256", + 244813136073u64 => "UNABLE_TO_LOCK_PARENT", + 244813136076u64 => "UNABLE_TO_RESEED", + 244813136017u64 => "UNSUPPORTED_CEK_ALG", + 244813136025u64 => "UNSUPPORTED_KEY_SIZE", + 244813136009u64 => "UNSUPPORTED_MAC_TYPE", + 244813136024u64 => "UNSUPPORTED_NUMBER_OF_ROUNDS", + 244813136095u64 => "URI_AUTHORITY_UNSUPPORTED", + 244813136010u64 => "VALUE_ERROR", + 244813135979u64 => "WRONG_FINAL_BLOCK_LENGTH", + 244813136011u64 => "WRONG_OUTPUT_BUFFER_SIZE", + 244813136055u64 => "XOF_DIGESTS_NOT_ALLOWED", + 244813136020u64 => "XTS_DATA_UNIT_IS_TOO_LARGE", + 244813136021u64 => "XTS_DUPLICATED_KEYS", + 154618822758u64 => "ADDITIONAL_INPUT_TOO_LONG", + 154618822759u64 => "ALREADY_INSTANTIATED", + 154618822761u64 => "ARGUMENT_OUT_OF_RANGE", + 154618822777u64 => "CANNOT_OPEN_FILE", + 154618822785u64 => "DRBG_ALREADY_INITIALIZED", + 154618822760u64 => "DRBG_NOT_INITIALISED", + 154618822762u64 => "ENTROPY_INPUT_TOO_LONG", + 154618822780u64 => "ENTROPY_OUT_OF_RANGE", + 154618822783u64 => "ERROR_ENTROPY_POOL_WAS_IGNORED", + 154618822763u64 => "ERROR_INITIALISING_DRBG", + 154618822764u64 => "ERROR_INSTANTIATING_DRBG", + 154618822765u64 => "ERROR_RETRIEVING_ADDITIONAL_INPUT", + 154618822766u64 => "ERROR_RETRIEVING_ENTROPY", + 154618822767u64 => "ERROR_RETRIEVING_NONCE", + 154618822782u64 => "FAILED_TO_CREATE_LOCK", + 154618822757u64 => "FUNC_NOT_IMPLEMENTED", + 154618822779u64 => "FWRITE_ERROR", + 154618822768u64 => "GENERATE_ERROR", + 154618822795u64 => "INSUFFICIENT_DRBG_STRENGTH", + 154618822769u64 => "INTERNAL_ERROR", + 154618822793u64 => "INVALID_PROPERTY_QUERY", + 154618822770u64 => "IN_ERROR_STATE", + 154618822778u64 => "NOT_A_REGULAR_FILE", + 154618822771u64 => "NOT_INSTANTIATED", + 154618822784u64 => "NO_DRBG_IMPLEMENTATION_SELECTED", + 154618822786u64 => "PARENT_LOCKING_NOT_ENABLED", + 154618822787u64 => "PARENT_STRENGTH_TOO_WEAK", + 154618822772u64 => "PERSONALISATION_STRING_TOO_LONG", + 154618822789u64 => "PREDICTION_RESISTANCE_NOT_SUPPORTED", + 154618822756u64 => "PRNG_NOT_SEEDED", + 154618822781u64 => "RANDOM_POOL_OVERFLOW", + 154618822790u64 => "RANDOM_POOL_UNDERFLOW", + 154618822773u64 => "REQUEST_TOO_LARGE_FOR_DRBG", + 154618822774u64 => "RESEED_ERROR", + 154618822775u64 => "SELFTEST_FAILURE", + 154618822791u64 => "TOO_LITTLE_NONCE_REQUESTED", + 154618822792u64 => "TOO_MUCH_NONCE_REQUESTED", + 154618822799u64 => "UNABLE_TO_CREATE_DRBG", + 154618822800u64 => "UNABLE_TO_FETCH_DRBG", + 154618822797u64 => "UNABLE_TO_GET_PARENT_RESEED_PROP_COUNTER", + 154618822794u64 => "UNABLE_TO_GET_PARENT_STRENGTH", + 154618822796u64 => "UNABLE_TO_LOCK_PARENT", + 154618822788u64 => "UNSUPPORTED_DRBG_FLAGS", + 154618822776u64 => "UNSUPPORTED_DRBG_TYPE", + 17179869284u64 => "ALGORITHM_MISMATCH", + 17179869285u64 => "BAD_E_VALUE", + 17179869286u64 => "BAD_FIXED_HEADER_DECRYPT", + 17179869287u64 => "BAD_PAD_BYTE_COUNT", + 17179869288u64 => "BAD_SIGNATURE", + 17179869290u64 => "BLOCK_TYPE_IS_NOT_01", + 17179869291u64 => "BLOCK_TYPE_IS_NOT_02", + 17179869292u64 => "DATA_GREATER_THAN_MOD_LEN", + 17179869293u64 => "DATA_TOO_LARGE", + 17179869294u64 => "DATA_TOO_LARGE_FOR_KEY_SIZE", + 17179869316u64 => "DATA_TOO_LARGE_FOR_MODULUS", + 17179869295u64 => "DATA_TOO_SMALL", + 17179869306u64 => "DATA_TOO_SMALL_FOR_KEY_SIZE", + 17179869342u64 => "DIGEST_DOES_NOT_MATCH", + 17179869329u64 => "DIGEST_NOT_ALLOWED", + 17179869296u64 => "DIGEST_TOO_BIG_FOR_RSA_KEY", + 17179869308u64 => "DMP1_NOT_CONGRUENT_TO_D", + 17179869309u64 => "DMQ1_NOT_CONGRUENT_TO_D", + 17179869307u64 => "D_E_NOT_CONGRUENT_TO_1", + 17179869317u64 => "FIRST_OCTET_INVALID", + 17179869328u64 => "ILLEGAL_OR_UNSUPPORTED_PADDING_MODE", + 17179869341u64 => "INVALID_DIGEST", + 17179869327u64 => "INVALID_DIGEST_LENGTH", + 17179869321u64 => "INVALID_HEADER", + 17179869355u64 => "INVALID_KEYPAIR", + 17179869357u64 => "INVALID_KEY_LENGTH", + 17179869344u64 => "INVALID_LABEL", + 17179869365u64 => "INVALID_LENGTH", + 17179869315u64 => "INVALID_MESSAGE_LENGTH", + 17179869340u64 => "INVALID_MGF1_MD", + 17179869358u64 => "INVALID_MODULUS", + 17179869351u64 => "INVALID_MULTI_PRIME_KEY", + 17179869345u64 => "INVALID_OAEP_PARAMETERS", + 17179869322u64 => "INVALID_PADDING", + 17179869325u64 => "INVALID_PADDING_MODE", + 17179869333u64 => "INVALID_PSS_PARAMETERS", + 17179869330u64 => "INVALID_PSS_SALTLEN", + 17179869359u64 => "INVALID_REQUEST", + 17179869334u64 => "INVALID_SALT_LENGTH", + 17179869360u64 => "INVALID_STRENGTH", + 17179869323u64 => "INVALID_TRAILER", + 17179869326u64 => "INVALID_X931_DIGEST", + 17179869310u64 => "IQMP_NOT_INVERSE_OF_Q", + 17179869349u64 => "KEY_PRIME_NUM_INVALID", + 17179869304u64 => "KEY_SIZE_TOO_SMALL", + 17179869318u64 => "LAST_OCTET_INVALID", + 17179869336u64 => "MGF1_DIGEST_NOT_ALLOWED", + 17179869363u64 => "MISSING_PRIVATE_KEY", + 17179869289u64 => "MODULUS_TOO_LARGE", + 17179869352u64 => "MP_COEFFICIENT_NOT_INVERSE_OF_R", + 17179869353u64 => "MP_EXPONENT_NOT_CONGRUENT_TO_D", + 17179869354u64 => "MP_R_NOT_PRIME", + 17179869324u64 => "NO_PUBLIC_EXPONENT", + 17179869297u64 => "NULL_BEFORE_BLOCK_MISSING", + 17179869356u64 => "N_DOES_NOT_EQUAL_PRODUCT_OF_PRIMES", + 17179869311u64 => "N_DOES_NOT_EQUAL_P_Q", + 17179869305u64 => "OAEP_DECODING_ERROR", + 17179869332u64 => "OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE", + 17179869298u64 => "PADDING_CHECK_FAILED", + 17179869361u64 => "PAIRWISE_TEST_FAILURE", + 17179869343u64 => "PKCS_DECODING_ERROR", + 17179869348u64 => "PSS_SALTLEN_TOO_SMALL", + 17179869362u64 => "PUB_EXPONENT_OUT_OF_RANGE", + 17179869312u64 => "P_NOT_PRIME", + 17179869313u64 => "Q_NOT_PRIME", + 17179869364u64 => "RANDOMNESS_SOURCE_STRENGTH_INSUFFICIENT", + 17179869314u64 => "RSA_OPERATIONS_NOT_SUPPORTED", + 17179869320u64 => "SLEN_CHECK_FAILED", + 17179869319u64 => "SLEN_RECOVERY_FAILED", + 17179869299u64 => "SSLV3_ROLLBACK_ATTACK", + 17179869300u64 => "THE_ASN1_OBJECT_IDENTIFIER_IS_NOT_KNOWN_FOR_THIS_MD", + 17179869301u64 => "UNKNOWN_ALGORITHM_TYPE", + 17179869350u64 => "UNKNOWN_DIGEST", + 17179869335u64 => "UNKNOWN_MASK_DIGEST", + 17179869302u64 => "UNKNOWN_PADDING_TYPE", + 17179869346u64 => "UNSUPPORTED_ENCRYPTION_TYPE", + 17179869347u64 => "UNSUPPORTED_LABEL_SOURCE", + 17179869337u64 => "UNSUPPORTED_MASK_ALGORITHM", + 17179869338u64 => "UNSUPPORTED_MASK_PARAMETER", + 17179869339u64 => "UNSUPPORTED_SIGNATURE_TYPE", + 17179869331u64 => "VALUE_MISSING", + 17179869303u64 => "WRONG_SIGNATURE_LENGTH", + 227633266788u64 => "ASN1_ERROR", + 227633266789u64 => "BAD_SIGNATURE", + 227633266795u64 => "BUFFER_TOO_SMALL", + 227633266798u64 => "DIST_ID_TOO_LARGE", + 227633266800u64 => "ID_NOT_SET", + 227633266799u64 => "ID_TOO_LARGE", + 227633266796u64 => "INVALID_CURVE", + 227633266790u64 => "INVALID_DIGEST", + 227633266791u64 => "INVALID_DIGEST_TYPE", + 227633266792u64 => "INVALID_ENCODING", + 227633266793u64 => "INVALID_FIELD", + 227633266801u64 => "INVALID_PRIVATE_KEY", + 227633266797u64 => "NO_PARAMETERS_SET", + 227633266794u64 => "USER_ID_TOO_LARGE", + 85899346211u64 => "APPLICATION_DATA_AFTER_CLOSE_NOTIFY", + 85899346020u64 => "APP_DATA_IN_HANDSHAKE", + 85899346192u64 => "ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT", + 85899346078u64 => "AT_LEAST_TLS_1_2_NEEDED_IN_SUITEB_MODE", + 85899346023u64 => "BAD_CHANGE_CIPHER_SPEC", + 85899346106u64 => "BAD_CIPHER", + 85899346310u64 => "BAD_DATA", + 85899346026u64 => "BAD_DATA_RETURNED_BY_CALLBACK", + 85899346027u64 => "BAD_DECOMPRESSION", + 85899346022u64 => "BAD_DH_VALUE", + 85899346031u64 => "BAD_DIGEST_LENGTH", + 85899346153u64 => "BAD_EARLY_DATA", + 85899346224u64 => "BAD_ECC_CERT", + 85899346226u64 => "BAD_ECPOINT", + 85899346030u64 => "BAD_EXTENSION", + 85899346252u64 => "BAD_HANDSHAKE_LENGTH", + 85899346156u64 => "BAD_HANDSHAKE_STATE", + 85899346025u64 => "BAD_HELLO_REQUEST", + 85899346183u64 => "BAD_HRR_VERSION", + 85899346028u64 => "BAD_KEY_SHARE", + 85899346042u64 => "BAD_KEY_UPDATE", + 85899346212u64 => "BAD_LEGACY_VERSION", + 85899346191u64 => "BAD_LENGTH", + 85899346160u64 => "BAD_PACKET", + 85899346035u64 => "BAD_PACKET_LENGTH", + 85899346036u64 => "BAD_PROTOCOL_VERSION_NUMBER", + 85899346139u64 => "BAD_PSK", + 85899346034u64 => "BAD_PSK_IDENTITY", + 85899346363u64 => "BAD_RECORD_TYPE", + 85899346039u64 => "BAD_RSA_ENCRYPT", + 85899346043u64 => "BAD_SIGNATURE", + 85899346267u64 => "BAD_SRP_A_LENGTH", + 85899346291u64 => "BAD_SRP_PARAMETERS", + 85899346272u64 => "BAD_SRTP_MKI_VALUE", + 85899346273u64 => "BAD_SRTP_PROTECTION_PROFILE_LIST", + 85899346044u64 => "BAD_SSL_FILETYPE", + 85899346304u64 => "BAD_VALUE", + 85899346047u64 => "BAD_WRITE_RETRY", + 85899346173u64 => "BINDER_DOES_NOT_VERIFY", + 85899346048u64 => "BIO_NOT_SET", + 85899346049u64 => "BLOCK_CIPHER_PAD_IS_WRONG", + 85899346050u64 => "BN_LIB", + 85899346154u64 => "CALLBACK_FAILED", + 85899346029u64 => "CANNOT_CHANGE_CIPHER", + 85899346219u64 => "CANNOT_GET_GROUP_NAME", + 85899346051u64 => "CA_DN_LENGTH_MISMATCH", + 85899346317u64 => "CA_KEY_TOO_SMALL", + 85899346318u64 => "CA_MD_TOO_WEAK", + 85899346053u64 => "CCS_RECEIVED_EARLY", + 85899346054u64 => "CERTIFICATE_VERIFY_FAILED", + 85899346297u64 => "CERT_CB_ERROR", + 85899346055u64 => "CERT_LENGTH_MISMATCH", + 85899346138u64 => "CIPHERSUITE_DIGEST_HAS_CHANGED", + 85899346057u64 => "CIPHER_CODE_WRONG_LENGTH", + 85899346146u64 => "CLIENTHELLO_TLSEXT", + 85899346060u64 => "COMPRESSED_LENGTH_TOO_LONG", + 85899346263u64 => "COMPRESSION_DISABLED", + 85899346061u64 => "COMPRESSION_FAILURE", + 85899346227u64 => "COMPRESSION_ID_NOT_WITHIN_PRIVATE_RANGE", + 85899346062u64 => "COMPRESSION_LIBRARY_ERROR", + 85899346064u64 => "CONNECTION_TYPE_NOT_SET", + 85899346087u64 => "CONTEXT_NOT_DANE_ENABLED", + 85899346320u64 => "COOKIE_GEN_CALLBACK_FAILURE", + 85899346228u64 => "COOKIE_MISMATCH", + 85899346216u64 => "COPY_PARAMETERS_FAILED", + 85899346126u64 => "CUSTOM_EXT_HANDLER_ALREADY_INSTALLED", + 85899346092u64 => "DANE_ALREADY_ENABLED", + 85899346093u64 => "DANE_CANNOT_OVERRIDE_MTYPE_FULL", + 85899346095u64 => "DANE_NOT_ENABLED", + 85899346100u64 => "DANE_TLSA_BAD_CERTIFICATE", + 85899346104u64 => "DANE_TLSA_BAD_CERTIFICATE_USAGE", + 85899346109u64 => "DANE_TLSA_BAD_DATA_LENGTH", + 85899346112u64 => "DANE_TLSA_BAD_DIGEST_LENGTH", + 85899346120u64 => "DANE_TLSA_BAD_MATCHING_TYPE", + 85899346121u64 => "DANE_TLSA_BAD_PUBLIC_KEY", + 85899346122u64 => "DANE_TLSA_BAD_SELECTOR", + 85899346123u64 => "DANE_TLSA_NULL_DATA", + 85899346065u64 => "DATA_BETWEEN_CCS_AND_FINISHED", + 85899346066u64 => "DATA_LENGTH_TOO_LONG", + 85899346067u64 => "DECRYPTION_FAILED", + 85899346201u64 => "DECRYPTION_FAILED_OR_BAD_RECORD_MAC", + 85899346314u64 => "DH_KEY_TOO_SMALL", + 85899346068u64 => "DH_PUBLIC_VALUE_LENGTH_IS_WRONG", + 85899346069u64 => "DIGEST_CHECK_FAILED", + 85899346254u64 => "DTLS_MESSAGE_TOO_BIG", + 85899346229u64 => "DUPLICATE_COMPRESSION_ID", + 85899346238u64 => "ECC_CERT_NOT_FOR_SIGNING", + 85899346294u64 => "ECDH_REQUIRED_FOR_SUITEB_MODE", + 85899346319u64 => "EE_KEY_TOO_SMALL", + 85899346274u64 => "EMPTY_SRTP_PROTECTION_PROFILE_LIST", + 85899346070u64 => "ENCRYPTED_LENGTH_TOO_LONG", + 85899346071u64 => "ERROR_IN_RECEIVED_CIPHER_LIST", + 85899346124u64 => "ERROR_SETTING_TLSA_BASE_DOMAIN", + 85899346114u64 => "EXCEEDS_MAX_FRAGMENT_SIZE", + 85899346072u64 => "EXCESSIVE_MESSAGE_SIZE", + 85899346199u64 => "EXTENSION_NOT_RECEIVED", + 85899346073u64 => "EXTRA_DATA_IN_MESSAGE", + 85899346083u64 => "EXT_LENGTH_MISMATCH", + 85899346325u64 => "FAILED_TO_INIT_ASYNC", + 85899346321u64 => "FRAGMENTED_CLIENT_HELLO", + 85899346074u64 => "GOT_A_FIN_BEFORE_A_CCS", + 85899346075u64 => "HTTPS_PROXY_REQUEST", + 85899346076u64 => "HTTP_REQUEST", + 85899346082u64 => "ILLEGAL_POINT_COMPRESSION", + 85899346300u64 => "ILLEGAL_SUITEB_DIGEST", + 85899346293u64 => "INAPPROPRIATE_FALLBACK", + 85899346260u64 => "INCONSISTENT_COMPRESSION", + 85899346142u64 => "INCONSISTENT_EARLY_DATA_ALPN", + 85899346151u64 => "INCONSISTENT_EARLY_DATA_SNI", + 85899346024u64 => "INCONSISTENT_EXTMS", + 85899346161u64 => "INSUFFICIENT_SECURITY", + 85899346125u64 => "INVALID_ALERT", + 85899346180u64 => "INVALID_CCS_MESSAGE", + 85899346158u64 => "INVALID_CERTIFICATE_OR_ALG", + 85899346200u64 => "INVALID_COMMAND", + 85899346261u64 => "INVALID_COMPRESSION_ALGORITHM", + 85899346203u64 => "INVALID_CONFIG", + 85899346033u64 => "INVALID_CONFIGURATION_NAME", + 85899346202u64 => "INVALID_CONTEXT", + 85899346132u64 => "INVALID_CT_VALIDATION_TYPE", + 85899346040u64 => "INVALID_KEY_UPDATE_TYPE", + 85899346094u64 => "INVALID_MAX_EARLY_DATA", + 85899346305u64 => "INVALID_NULL_CMD_NAME", + 85899346322u64 => "INVALID_SEQUENCE_NUMBER", + 85899346308u64 => "INVALID_SERVERINFO_DATA", + 85899346919u64 => "INVALID_SESSION_ID", + 85899346277u64 => "INVALID_SRP_USERNAME", + 85899346248u64 => "INVALID_STATUS_RESPONSE", + 85899346245u64 => "INVALID_TICKET_KEYS_LENGTH", + 85899346253u64 => "LEGACY_SIGALG_DISALLOWED_OR_UNSUPPORTED", + 85899346079u64 => "LENGTH_MISMATCH", + 85899346324u64 => "LENGTH_TOO_LONG", + 85899346080u64 => "LENGTH_TOO_SHORT", + 85899346194u64 => "LIBRARY_BUG", + 85899346081u64 => "LIBRARY_HAS_NO_CIPHERS", + 85899346085u64 => "MISSING_DSA_SIGNING_CERT", + 85899346301u64 => "MISSING_ECDSA_SIGNING_CERT", + 85899346176u64 => "MISSING_FATAL", + 85899346210u64 => "MISSING_PARAMETERS", + 85899346230u64 => "MISSING_PSK_KEX_MODES_EXTENSION", + 85899346088u64 => "MISSING_RSA_CERTIFICATE", + 85899346089u64 => "MISSING_RSA_ENCRYPTING_CERT", + 85899346090u64 => "MISSING_RSA_SIGNING_CERT", + 85899346032u64 => "MISSING_SIGALGS_EXTENSION", + 85899346141u64 => "MISSING_SIGNING_CERT", + 85899346278u64 => "MISSING_SRP_PARAM", + 85899346129u64 => "MISSING_SUPPORTED_GROUPS_EXTENSION", + 85899346091u64 => "MISSING_TMP_DH_KEY", + 85899346231u64 => "MISSING_TMP_ECDH_KEY", + 85899346213u64 => "MIXED_HANDSHAKE_AND_NON_HANDSHAKE_DATA", + 85899346102u64 => "NOT_ON_RECORD_BOUNDARY", + 85899346209u64 => "NOT_REPLACING_CERTIFICATE", + 85899346204u64 => "NOT_SERVER", + 85899346155u64 => "NO_APPLICATION_PROTOCOL", + 85899346096u64 => "NO_CERTIFICATES_RETURNED", + 85899346097u64 => "NO_CERTIFICATE_ASSIGNED", + 85899346099u64 => "NO_CERTIFICATE_SET", + 85899346134u64 => "NO_CHANGE_FOLLOWING_HRR", + 85899346101u64 => "NO_CIPHERS_AVAILABLE", + 85899346103u64 => "NO_CIPHERS_SPECIFIED", + 85899346105u64 => "NO_CIPHER_MATCH", + 85899346251u64 => "NO_CLIENT_CERT_METHOD", + 85899346107u64 => "NO_COMPRESSION_SPECIFIED", + 85899346207u64 => "NO_COOKIE_CALLBACK_SET", + 85899346250u64 => "NO_GOST_CERTIFICATE_SENT_BY_PEER", + 85899346108u64 => "NO_METHOD_SPECIFIED", + 85899346309u64 => "NO_PEM_EXTENSIONS", + 85899346110u64 => "NO_PRIVATE_KEY_ASSIGNED", + 85899346111u64 => "NO_PROTOCOLS_AVAILABLE", + 85899346259u64 => "NO_RENEGOTIATION", + 85899346244u64 => "NO_REQUIRED_DIGEST", + 85899346113u64 => "NO_SHARED_CIPHER", + 85899346330u64 => "NO_SHARED_GROUPS", + 85899346296u64 => "NO_SHARED_SIGNATURE_ALGORITHMS", + 85899346279u64 => "NO_SRTP_PROFILES", + 85899346217u64 => "NO_SUITABLE_DIGEST_ALGORITHM", + 85899346215u64 => "NO_SUITABLE_GROUPS", + 85899346021u64 => "NO_SUITABLE_KEY_SHARE", + 85899346038u64 => "NO_SUITABLE_SIGNATURE_ALGORITHM", + 85899346136u64 => "NO_VALID_SCTS", + 85899346323u64 => "NO_VERIFY_COOKIE_CALLBACK", + 85899346115u64 => "NULL_SSL_CTX", + 85899346116u64 => "NULL_SSL_METHOD_PASSED", + 85899346225u64 => "OCSP_CALLBACK_FAILURE", + 85899346117u64 => "OLD_SESSION_CIPHER_NOT_RETURNED", + 85899346264u64 => "OLD_SESSION_COMPRESSION_ALGORITHM_NOT_RETURNED", + 85899346157u64 => "OVERFLOW_ERROR", + 85899346118u64 => "PACKET_LENGTH_TOO_LONG", + 85899346147u64 => "PARSE_TLSEXT", + 85899346190u64 => "PATH_TOO_LONG", + 85899346119u64 => "PEER_DID_NOT_RETURN_A_CERTIFICATE", + 85899346311u64 => "PEM_NAME_BAD_PREFIX", + 85899346312u64 => "PEM_NAME_TOO_SHORT", + 85899346326u64 => "PIPELINE_FAILURE", + 85899346198u64 => "POST_HANDSHAKE_AUTH_ENCODING_ERR", + 85899346208u64 => "PRIVATE_KEY_MISMATCH", + 85899346127u64 => "PROTOCOL_IS_SHUTDOWN", + 85899346143u64 => "PSK_IDENTITY_NOT_FOUND", + 85899346144u64 => "PSK_NO_CLIENT_CB", + 85899346145u64 => "PSK_NO_SERVER_CB", + 85899346131u64 => "READ_BIO_NOT_SET", + 85899346232u64 => "READ_TIMEOUT_EXPIRED", + 85899346133u64 => "RECORD_LENGTH_MISMATCH", + 85899346218u64 => "RECORD_TOO_SMALL", + 85899346255u64 => "RENEGOTIATE_EXT_TOO_LONG", + 85899346256u64 => "RENEGOTIATION_ENCODING_ERR", + 85899346257u64 => "RENEGOTIATION_MISMATCH", + 85899346205u64 => "REQUEST_PENDING", + 85899346206u64 => "REQUEST_SENT", + 85899346135u64 => "REQUIRED_CIPHER_MISSING", + 85899346262u64 => "REQUIRED_COMPRESSION_ALGORITHM_MISSING", + 85899346265u64 => "SCSV_RECEIVED_WHEN_RENEGOTIATING", + 85899346128u64 => "SCT_VERIFICATION_FAILED", + 85899346195u64 => "SERVERHELLO_TLSEXT", + 85899346197u64 => "SESSION_ID_CONTEXT_UNINITIALIZED", + 85899346327u64 => "SHUTDOWN_WHILE_IN_INIT", + 85899346280u64 => "SIGNATURE_ALGORITHMS_ERROR", + 85899346140u64 => "SIGNATURE_FOR_NON_SIGNING_CERTIFICATE", + 85899346281u64 => "SRP_A_CALC", + 85899346282u64 => "SRTP_COULD_NOT_ALLOCATE_PROFILES", + 85899346283u64 => "SRTP_PROTECTION_PROFILE_LIST_TOO_LONG", + 85899346284u64 => "SRTP_UNKNOWN_PROTECTION_PROFILE", + 85899346152u64 => "SSL3_EXT_INVALID_MAX_FRAGMENT_LENGTH", + 85899346239u64 => "SSL3_EXT_INVALID_SERVERNAME", + 85899346240u64 => "SSL3_EXT_INVALID_SERVERNAME_TYPE", + 85899346220u64 => "SSL3_SESSION_ID_TOO_LONG", + 85899346962u64 => "SSLV3_ALERT_BAD_CERTIFICATE", + 85899346940u64 => "SSLV3_ALERT_BAD_RECORD_MAC", + 85899346965u64 => "SSLV3_ALERT_CERTIFICATE_EXPIRED", + 85899346964u64 => "SSLV3_ALERT_CERTIFICATE_REVOKED", + 85899346966u64 => "SSLV3_ALERT_CERTIFICATE_UNKNOWN", + 85899346950u64 => "SSLV3_ALERT_DECOMPRESSION_FAILURE", + 85899346960u64 => "SSLV3_ALERT_HANDSHAKE_FAILURE", + 85899346967u64 => "SSLV3_ALERT_ILLEGAL_PARAMETER", + 85899346961u64 => "SSLV3_ALERT_NO_CERTIFICATE", + 85899346930u64 => "SSLV3_ALERT_UNEXPECTED_MESSAGE", + 85899346963u64 => "SSLV3_ALERT_UNSUPPORTED_CERTIFICATE", + 85899346037u64 => "SSL_COMMAND_SECTION_EMPTY", + 85899346045u64 => "SSL_COMMAND_SECTION_NOT_FOUND", + 85899346148u64 => "SSL_CTX_HAS_NO_DEFAULT_SSL_VERSION", + 85899346149u64 => "SSL_HANDSHAKE_FAILURE", + 85899346150u64 => "SSL_LIBRARY_HAS_NO_CIPHERS", + 85899346292u64 => "SSL_NEGATIVE_LENGTH", + 85899346046u64 => "SSL_SECTION_EMPTY", + 85899346056u64 => "SSL_SECTION_NOT_FOUND", + 85899346221u64 => "SSL_SESSION_ID_CALLBACK_FAILED", + 85899346222u64 => "SSL_SESSION_ID_CONFLICT", + 85899346193u64 => "SSL_SESSION_ID_CONTEXT_TOO_LONG", + 85899346223u64 => "SSL_SESSION_ID_HAS_BAD_LENGTH", + 85899346328u64 => "SSL_SESSION_ID_TOO_LONG", + 85899346130u64 => "SSL_SESSION_VERSION_MISMATCH", + 85899346041u64 => "STILL_IN_INIT", + 85899347036u64 => "TLSV13_ALERT_CERTIFICATE_REQUIRED", + 85899347029u64 => "TLSV13_ALERT_MISSING_EXTENSION", + 85899346969u64 => "TLSV1_ALERT_ACCESS_DENIED", + 85899346970u64 => "TLSV1_ALERT_DECODE_ERROR", + 85899346941u64 => "TLSV1_ALERT_DECRYPTION_FAILED", + 85899346971u64 => "TLSV1_ALERT_DECRYPT_ERROR", + 85899346980u64 => "TLSV1_ALERT_EXPORT_RESTRICTION", + 85899347006u64 => "TLSV1_ALERT_INAPPROPRIATE_FALLBACK", + 85899346991u64 => "TLSV1_ALERT_INSUFFICIENT_SECURITY", + 85899347000u64 => "TLSV1_ALERT_INTERNAL_ERROR", + 85899347040u64 => "TLSV1_ALERT_NO_APPLICATION_PROTOCOL", + 85899347020u64 => "TLSV1_ALERT_NO_RENEGOTIATION", + 85899346990u64 => "TLSV1_ALERT_PROTOCOL_VERSION", + 85899346942u64 => "TLSV1_ALERT_RECORD_OVERFLOW", + 85899346968u64 => "TLSV1_ALERT_UNKNOWN_CA", + 85899347035u64 => "TLSV1_ALERT_UNKNOWN_PSK_IDENTITY", + 85899347010u64 => "TLSV1_ALERT_USER_CANCELLED", + 85899347034u64 => "TLSV1_BAD_CERTIFICATE_HASH_VALUE", + 85899347033u64 => "TLSV1_BAD_CERTIFICATE_STATUS_RESPONSE", + 85899347031u64 => "TLSV1_CERTIFICATE_UNOBTAINABLE", + 85899347032u64 => "TLSV1_UNRECOGNIZED_NAME", + 85899347030u64 => "TLSV1_UNSUPPORTED_EXTENSION", + 85899346287u64 => "TLS_ILLEGAL_EXPORTER_LABEL", + 85899346077u64 => "TLS_INVALID_ECPOINTFORMAT_LIST", + 85899346052u64 => "TOO_MANY_KEY_UPDATES", + 85899346329u64 => "TOO_MANY_WARN_ALERTS", + 85899346084u64 => "TOO_MUCH_EARLY_DATA", + 85899346234u64 => "UNABLE_TO_FIND_ECDH_PARAMETERS", + 85899346159u64 => "UNABLE_TO_FIND_PUBLIC_KEY_PARAMETERS", + 85899346162u64 => "UNABLE_TO_LOAD_SSL3_MD5_ROUTINES", + 85899346163u64 => "UNABLE_TO_LOAD_SSL3_SHA1_ROUTINES", + 85899346182u64 => "UNEXPECTED_CCS_MESSAGE", + 85899346098u64 => "UNEXPECTED_END_OF_EARLY_DATA", + 85899346214u64 => "UNEXPECTED_EOF_WHILE_READING", + 85899346164u64 => "UNEXPECTED_MESSAGE", + 85899346165u64 => "UNEXPECTED_RECORD", + 85899346196u64 => "UNINITIALIZED", + 85899346166u64 => "UNKNOWN_ALERT_TYPE", + 85899346167u64 => "UNKNOWN_CERTIFICATE_TYPE", + 85899346168u64 => "UNKNOWN_CIPHER_RETURNED", + 85899346169u64 => "UNKNOWN_CIPHER_TYPE", + 85899346306u64 => "UNKNOWN_CMD_NAME", + 85899346059u64 => "UNKNOWN_COMMAND", + 85899346288u64 => "UNKNOWN_DIGEST", + 85899346170u64 => "UNKNOWN_KEY_EXCHANGE_TYPE", + 85899346171u64 => "UNKNOWN_PKEY_TYPE", + 85899346172u64 => "UNKNOWN_PROTOCOL", + 85899346174u64 => "UNKNOWN_SSL_VERSION", + 85899346175u64 => "UNKNOWN_STATE", + 85899346258u64 => "UNSAFE_LEGACY_RENEGOTIATION_DISABLED", + 85899346137u64 => "UNSOLICITED_EXTENSION", + 85899346177u64 => "UNSUPPORTED_COMPRESSION_ALGORITHM", + 85899346235u64 => "UNSUPPORTED_ELLIPTIC_CURVE", + 85899346178u64 => "UNSUPPORTED_PROTOCOL", + 85899346179u64 => "UNSUPPORTED_SSL_VERSION", + 85899346249u64 => "UNSUPPORTED_STATUS_TYPE", + 85899346289u64 => "USE_SRTP_NOT_NEGOTIATED", + 85899346086u64 => "VERSION_TOO_HIGH", + 85899346316u64 => "VERSION_TOO_LOW", + 85899346303u64 => "WRONG_CERTIFICATE_TYPE", + 85899346181u64 => "WRONG_CIPHER_RETURNED", + 85899346298u64 => "WRONG_CURVE", + 85899346184u64 => "WRONG_SIGNATURE_LENGTH", + 85899346185u64 => "WRONG_SIGNATURE_SIZE", + 85899346290u64 => "WRONG_SIGNATURE_TYPE", + 85899346186u64 => "WRONG_SSL_VERSION", + 85899346187u64 => "WRONG_VERSION_NUMBER", + 85899346188u64 => "X509_LIB", + 85899346189u64 => "X509_VERIFICATION_SETUP_PROBLEMS", + 201863463044u64 => "BAD_PKCS7_TYPE", + 201863463045u64 => "BAD_TYPE", + 201863463049u64 => "CANNOT_LOAD_CERT", + 201863463050u64 => "CANNOT_LOAD_KEY", + 201863463012u64 => "CERTIFICATE_VERIFY_ERROR", + 201863463039u64 => "COULD_NOT_SET_ENGINE", + 201863463027u64 => "COULD_NOT_SET_TIME", + 201863463046u64 => "DETACHED_CONTENT", + 201863463028u64 => "ESS_ADD_SIGNING_CERT_ERROR", + 201863463051u64 => "ESS_ADD_SIGNING_CERT_V2_ERROR", + 201863463013u64 => "ESS_SIGNING_CERTIFICATE_ERROR", + 201863463014u64 => "INVALID_NULL_POINTER", + 201863463029u64 => "INVALID_SIGNER_CERTIFICATE_PURPOSE", + 201863463015u64 => "MESSAGE_IMPRINT_MISMATCH", + 201863463016u64 => "NONCE_MISMATCH", + 201863463017u64 => "NONCE_NOT_RETURNED", + 201863463018u64 => "NO_CONTENT", + 201863463019u64 => "NO_TIME_STAMP_TOKEN", + 201863463030u64 => "PKCS7_ADD_SIGNATURE_ERROR", + 201863463031u64 => "PKCS7_ADD_SIGNED_ATTR_ERROR", + 201863463041u64 => "PKCS7_TO_TS_TST_INFO_FAILED", + 201863463020u64 => "POLICY_MISMATCH", + 201863463032u64 => "PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE", + 201863463033u64 => "RESPONSE_SETUP_ERROR", + 201863463021u64 => "SIGNATURE_FAILURE", + 201863463022u64 => "THERE_MUST_BE_ONE_SIGNER", + 201863463034u64 => "TIME_SYSCALL_ERROR", + 201863463042u64 => "TOKEN_NOT_PRESENT", + 201863463043u64 => "TOKEN_PRESENT", + 201863463023u64 => "TSA_NAME_MISMATCH", + 201863463024u64 => "TSA_UNTRUSTED", + 201863463035u64 => "TST_INFO_SETUP_ERROR", + 201863463036u64 => "TS_DATASIGN", + 201863463037u64 => "UNACCEPTABLE_POLICY", + 201863463038u64 => "UNSUPPORTED_MD_ALGORITHM", + 201863463025u64 => "UNSUPPORTED_VERSION", + 201863463047u64 => "VAR_BAD_VALUE", + 201863463048u64 => "VAR_LOOKUP_FAILURE", + 201863463026u64 => "WRONG_CONTENT_TYPE", + 171798691944u64 => "COMMON_OK_AND_CANCEL_CHARACTERS", + 171798691942u64 => "INDEX_TOO_LARGE", + 171798691943u64 => "INDEX_TOO_SMALL", + 171798691945u64 => "NO_RESULT_BUFFER", + 171798691947u64 => "PROCESSING_ERROR", + 171798691940u64 => "RESULT_TOO_LARGE", + 171798691941u64 => "RESULT_TOO_SMALL", + 171798691949u64 => "SYSASSIGN_ERROR", + 171798691950u64 => "SYSDASSGN_ERROR", + 171798691951u64 => "SYSQIOW_ERROR", + 171798691946u64 => "UNKNOWN_CONTROL_COMMAND", + 171798691948u64 => "UNKNOWN_TTYGET_ERRNO_VALUE", + 171798691952u64 => "USER_DATA_DUPLICATION_UNSUPPORTED", + 146028888182u64 => "BAD_IP_ADDRESS", + 146028888183u64 => "BAD_OBJECT", + 146028888164u64 => "BN_DEC2BN_ERROR", + 146028888165u64 => "BN_TO_ASN1_INTEGER_ERROR", + 146028888213u64 => "DIRNAME_ERROR", + 146028888224u64 => "DISTPOINT_ALREADY_SET", + 146028888197u64 => "DUPLICATE_ZONE_ID", + 146028888233u64 => "EMPTY_KEY_USAGE", + 146028888195u64 => "ERROR_CONVERTING_ZONE", + 146028888208u64 => "ERROR_CREATING_EXTENSION", + 146028888192u64 => "ERROR_IN_EXTENSION", + 146028888201u64 => "EXPECTED_A_SECTION_NAME", + 146028888209u64 => "EXTENSION_EXISTS", + 146028888179u64 => "EXTENSION_NAME_ERROR", + 146028888166u64 => "EXTENSION_NOT_FOUND", + 146028888167u64 => "EXTENSION_SETTING_NOT_SUPPORTED", + 146028888180u64 => "EXTENSION_VALUE_ERROR", + 146028888215u64 => "ILLEGAL_EMPTY_EXTENSION", + 146028888216u64 => "INCORRECT_POLICY_SYNTAX_TAG", + 146028888226u64 => "INVALID_ASNUMBER", + 146028888227u64 => "INVALID_ASRANGE", + 146028888168u64 => "INVALID_BOOLEAN_STRING", + 146028888222u64 => "INVALID_CERTIFICATE", + 146028888172u64 => "INVALID_EMPTY_NAME", + 146028888169u64 => "INVALID_EXTENSION_STRING", + 146028888229u64 => "INVALID_INHERITANCE", + 146028888230u64 => "INVALID_IPADDRESS", + 146028888225u64 => "INVALID_MULTIPLE_RDNS", + 146028888170u64 => "INVALID_NAME", + 146028888171u64 => "INVALID_NULL_ARGUMENT", + 146028888173u64 => "INVALID_NULL_VALUE", + 146028888204u64 => "INVALID_NUMBER", + 146028888205u64 => "INVALID_NUMBERS", + 146028888174u64 => "INVALID_OBJECT_IDENTIFIER", + 146028888202u64 => "INVALID_OPTION", + 146028888198u64 => "INVALID_POLICY_IDENTIFIER", + 146028888217u64 => "INVALID_PROXY_POLICY_SETTING", + 146028888210u64 => "INVALID_PURPOSE", + 146028888228u64 => "INVALID_SAFI", + 146028888199u64 => "INVALID_SECTION", + 146028888207u64 => "INVALID_SYNTAX", + 146028888190u64 => "ISSUER_DECODE_ERROR", + 146028888188u64 => "MISSING_VALUE", + 146028888206u64 => "NEED_ORGANIZATION_AND_NUMBERS", + 146028888232u64 => "NEGATIVE_PATHLEN", + 146028888200u64 => "NO_CONFIG_DATABASE", + 146028888185u64 => "NO_ISSUER_CERTIFICATE", + 146028888191u64 => "NO_ISSUER_DETAILS", + 146028888203u64 => "NO_POLICY_IDENTIFIER", + 146028888218u64 => "NO_PROXY_CERT_POLICY_LANGUAGE_DEFINED", + 146028888178u64 => "NO_PUBLIC_KEY", + 146028888189u64 => "NO_SUBJECT_DETAILS", + 146028888212u64 => "OPERATION_NOT_DEFINED", + 146028888211u64 => "OTHERNAME_ERROR", + 146028888219u64 => "POLICY_LANGUAGE_ALREADY_DEFINED", + 146028888220u64 => "POLICY_PATH_LENGTH", + 146028888221u64 => "POLICY_PATH_LENGTH_ALREADY_DEFINED", + 146028888223u64 => "POLICY_WHEN_PROXY_LANGUAGE_REQUIRES_NO_POLICY", + 146028888214u64 => "SECTION_NOT_FOUND", + 146028888186u64 => "UNABLE_TO_GET_ISSUER_DETAILS", + 146028888187u64 => "UNABLE_TO_GET_ISSUER_KEYID", + 146028888175u64 => "UNKNOWN_BIT_STRING_ARGUMENT", + 146028888193u64 => "UNKNOWN_EXTENSION", + 146028888194u64 => "UNKNOWN_EXTENSION_NAME", + 146028888184u64 => "UNKNOWN_OPTION", + 146028888181u64 => "UNSUPPORTED_OPTION", + 146028888231u64 => "UNSUPPORTED_TYPE", + 146028888196u64 => "USER_TOO_LONG", + 47244640366u64 => "AKID_MISMATCH", + 47244640389u64 => "BAD_SELECTOR", + 47244640356u64 => "BAD_X509_FILETYPE", + 47244640374u64 => "BASE64_DECODE_ERROR", + 47244640370u64 => "CANT_CHECK_DH_KEY", + 47244640395u64 => "CERTIFICATE_VERIFICATION_FAILED", + 47244640357u64 => "CERT_ALREADY_IN_HASH_TABLE", + 47244640383u64 => "CRL_ALREADY_DELTA", + 47244640387u64 => "CRL_VERIFY_FAILURE", + 47244640396u64 => "DUPLICATE_ATTRIBUTE", + 47244640397u64 => "ERROR_GETTING_MD_BY_NID", + 47244640398u64 => "ERROR_USING_SIGINF_SET", + 47244640384u64 => "IDP_MISMATCH", + 47244640394u64 => "INVALID_ATTRIBUTES", + 47244640369u64 => "INVALID_DIRECTORY", + 47244640399u64 => "INVALID_DISTPOINT", + 47244640375u64 => "INVALID_FIELD_NAME", + 47244640379u64 => "INVALID_TRUST", + 47244640385u64 => "ISSUER_MISMATCH", + 47244640371u64 => "KEY_TYPE_MISMATCH", + 47244640372u64 => "KEY_VALUES_MISMATCH", + 47244640359u64 => "LOADING_CERT_DIR", + 47244640360u64 => "LOADING_DEFAULTS", + 47244640380u64 => "METHOD_NOT_SUPPORTED", + 47244640390u64 => "NAME_TOO_LONG", + 47244640388u64 => "NEWER_CRL_NOT_NEWER", + 47244640391u64 => "NO_CERTIFICATE_FOUND", + 47244640392u64 => "NO_CERTIFICATE_OR_CRL_FOUND", + 47244640361u64 => "NO_CERT_SET_FOR_US_TO_VERIFY", + 47244640393u64 => "NO_CRL_FOUND", + 47244640386u64 => "NO_CRL_NUMBER", + 47244640381u64 => "PUBLIC_KEY_DECODE_ERROR", + 47244640382u64 => "PUBLIC_KEY_ENCODE_ERROR", + 47244640362u64 => "SHOULD_RETRY", + 47244640363u64 => "UNABLE_TO_FIND_PARAMETERS_IN_CHAIN", + 47244640364u64 => "UNABLE_TO_GET_CERTS_PUBLIC_KEY", + 47244640373u64 => "UNKNOWN_KEY_TYPE", + 47244640365u64 => "UNKNOWN_NID", + 47244640377u64 => "UNKNOWN_PURPOSE_ID", + 47244640400u64 => "UNKNOWN_SIGID_ALGS", + 47244640376u64 => "UNKNOWN_TRUST_ID", + 47244640367u64 => "UNSUPPORTED_ALGORITHM", + 47244640368u64 => "WRONG_LOOKUP_TYPE", + 47244640378u64 => "WRONG_TYPE", +}; + +/// Helper function to create encoded key from (lib, reason) pair +#[inline] +pub fn encode_error_key(lib: i32, reason: i32) -> u64 { + ((lib as u64) << 32) | (reason as u64 & 0xFFFFFFFF) +} From 9e7d291b63f4f2b3746146c3ed654d1b551490cb Mon Sep 17 00:00:00 2001 From: Jiseok CHOI <jiseok.dev@gmail.com> Date: Fri, 31 Oct 2025 00:08:04 +0900 Subject: [PATCH 326/819] Add callable validation to codecs.register_error (#6229) Validate that the handler argument passed to codecs.register_error is callable, raising TypeError with message 'handler must be callable' if it is not. This matches CPython behavior. This fix enables test_badregistercall in test_codeccallbacks.py to pass. --- Lib/test/test_codeccallbacks.py | 2 -- vm/src/stdlib/codecs.rs | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_codeccallbacks.py b/Lib/test/test_codeccallbacks.py index bd1dbcd6266..9ca02cea351 100644 --- a/Lib/test/test_codeccallbacks.py +++ b/Lib/test/test_codeccallbacks.py @@ -909,8 +909,6 @@ def handle(exc): self.assertEqual(exc.object, input) self.assertEqual(exc.reason, "surrogates not allowed") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_badregistercall(self): # enhance coverage of: # Modules/_codecsmodule.c::register_error() diff --git a/vm/src/stdlib/codecs.rs b/vm/src/stdlib/codecs.rs index 468b5dda6e5..46b1608f243 100644 --- a/vm/src/stdlib/codecs.rs +++ b/vm/src/stdlib/codecs.rs @@ -67,10 +67,14 @@ mod _codecs { } #[pyfunction] - fn register_error(name: PyStrRef, handler: PyObjectRef, vm: &VirtualMachine) { + fn register_error(name: PyStrRef, handler: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + if !handler.is_callable() { + return Err(vm.new_type_error("handler must be callable".to_owned())); + } vm.state .codec_registry .register_error(name.as_str().to_owned(), handler); + Ok(()) } #[pyfunction] From e096ce7bc75c703f0a6be897d82308126ee98f40 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI <jiseok.dev@gmail.com> Date: Sat, 1 Nov 2025 18:30:03 +0900 Subject: [PATCH 327/819] Implement property.__name__ attribute (#6230) * Implement property.__name__ attribute Add getter and setter for the __name__ attribute on property objects. The getter returns the explicitly set name if available, otherwise falls back to the getter function's __name__. Raises AttributeError if no name is available, matching CPython 3.13 behavior. The implementation handles edge cases: - Returns None when explicitly set to None - Propagates non-AttributeError exceptions from getter's __getattr__ - Raises property-specific AttributeError when getter lacks __name__ This fix enables test_property_name in test_property.py to pass. * Refactor to use get_property_name in __name__ implementation Consolidate duplicate logic by making name_getter() use the existing get_property_name() helper method. This eliminates code duplication and improves maintainability. Changes: - Update get_property_name() to return PyResult<Option<PyObjectRef>> to properly handle and propagate non-AttributeError exceptions - Simplify name_getter() to delegate to get_property_name() - Update format_property_error() to handle the new return type This addresses review feedback about the relationship between get_property_name() and __name__ implementation. * style comment --- Lib/test/test_property.py | 1 - vm/src/builtins/property.rs | 45 ++++++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 65153395b7e..49dc2331e94 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -201,7 +201,6 @@ def test_gh_115618(self): self.assertIsNone(prop.fdel) self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_property_name(self): def getter(self): return 42 diff --git a/vm/src/builtins/property.rs b/vm/src/builtins/property.rs index 1568cb08d8a..a75357f7611 100644 --- a/vm/src/builtins/property.rs +++ b/vm/src/builtins/property.rs @@ -67,20 +67,30 @@ impl GetDescriptor for PyProperty { #[pyclass(with(Constructor, Initializer, GetDescriptor), flags(BASETYPE))] impl PyProperty { // Helper method to get property name - fn get_property_name(&self, vm: &VirtualMachine) -> Option<PyObjectRef> { + // Returns the name if available, None if not found, or propagates errors + fn get_property_name(&self, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> { // First check if name was set via __set_name__ if let Some(name) = self.name.read().as_ref() { - return Some(name.clone()); + return Ok(Some(name.clone())); } - // Otherwise try to get __name__ from getter - if let Some(getter) = self.getter.read().as_ref() - && let Ok(name) = getter.get_attr("__name__", vm) - { - return Some(name); - } + let getter = self.getter.read(); + let Some(getter) = getter.as_ref() else { + return Ok(None); + }; - None + match getter.get_attr("__name__", vm) { + Ok(name) => Ok(Some(name)), + Err(e) => { + // If it's an AttributeError from the getter, return None + // Otherwise, propagate the original exception (e.g., RuntimeError) + if e.class().is(vm.ctx.exceptions.attribute_error) { + Ok(None) + } else { + Err(e) + } + } + } } // Descriptor methods @@ -143,6 +153,21 @@ impl PyProperty { self.deleter.read().clone() } + #[pygetset(name = "__name__")] + fn name_getter(&self, vm: &VirtualMachine) -> PyResult { + match self.get_property_name(vm)? { + Some(name) => Ok(name), + None => Err( + vm.new_attribute_error("'property' object has no attribute '__name__'".to_owned()) + ), + } + } + + #[pygetset(name = "__name__", setter)] + fn name_setter(&self, value: PyObjectRef) { + *self.name.write() = Some(value); + } + fn doc_getter(&self) -> Option<PyObjectRef> { self.doc.read().clone() } @@ -288,7 +313,7 @@ impl PyProperty { error_type: &str, vm: &VirtualMachine, ) -> PyResult<String> { - let prop_name = self.get_property_name(vm); + let prop_name = self.get_property_name(vm)?; let obj_type = obj.class(); let qualname = obj_type.__qualname__(vm); From a7e8ac684b0d225e7de34ab9c7a03f024934f5ac Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 1 Nov 2025 17:01:02 +0200 Subject: [PATCH 328/819] Remove user defined docstrings (#6232) --- vm/src/builtins/str.rs | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/vm/src/builtins/str.rs b/vm/src/builtins/str.rs index 3ee5e3369dd..80da626f318 100644 --- a/vm/src/builtins/str.rs +++ b/vm/src/builtins/str.rs @@ -890,10 +890,6 @@ impl PyStr { ) } - /// Return a str with the given prefix string removed if present. - /// - /// If the string starts with the prefix string, return string[len(prefix):] - /// Otherwise, return a copy of the original string. #[pymethod] fn removeprefix(&self, pref: PyStrRef) -> Wtf8Buf { self.as_wtf8() @@ -901,10 +897,6 @@ impl PyStr { .to_owned() } - /// Return a str with the given suffix string removed if present. - /// - /// If the string ends with the suffix string, return string[:len(suffix)] - /// Otherwise, return a copy of the original string. #[pymethod] fn removesuffix(&self, suffix: PyStrRef) -> Wtf8Buf { self.as_wtf8() @@ -955,10 +947,6 @@ impl PyStr { format(&format_str, &args, vm) } - /// S.format_map(mapping) -> str - /// - /// Return a formatted version of S, using substitutions from mapping. - /// The substitutions are identified by braces ('{' and '}'). #[pymethod] fn format_map(&self, mapping: PyObjectRef, vm: &VirtualMachine) -> PyResult<Wtf8Buf> { let format_string = @@ -989,8 +977,6 @@ impl PyStr { Ok(vm.ctx.new_str(s)) } - /// Return a titlecased version of the string where words start with an - /// uppercase character and the remaining characters are lowercase. #[pymethod] fn title(&self) -> Wtf8Buf { let mut title = Wtf8Buf::with_capacity(self.data.len()); @@ -1066,21 +1052,6 @@ impl PyStr { } } - /// Return true if all characters in the string are printable or the string is empty, - /// false otherwise. Nonprintable characters are those characters defined in the - /// Unicode character database as `Other` or `Separator`, - /// excepting the ASCII space (0x20) which is considered printable. - /// - /// All characters except those characters defined in the Unicode character - /// database as following categories are considered printable. - /// * Cc (Other, Control) - /// * Cf (Other, Format) - /// * Cs (Other, Surrogate) - /// * Co (Other, Private Use) - /// * Cn (Other, Not Assigned) - /// * Zl Separator, Line ('\u2028', LINE SEPARATOR) - /// * Zp Separator, Paragraph ('\u2029', PARAGRAPH SEPARATOR) - /// * Zs (Separator, Space) other than ASCII space('\x20'). #[pymethod] fn isprintable(&self) -> bool { self.char_all(|c| c == '\u{0020}' || rustpython_literal::char::is_printable(c)) @@ -1246,8 +1217,6 @@ impl PyStr { .to_pyobject(vm)) } - /// Return `true` if the sequence is ASCII titlecase and the sequence is not - /// empty, `false` otherwise. #[pymethod] fn istitle(&self) -> bool { if self.data.is_empty() { From 377151a57f1451df5febd09b183d8c4f31ede32d Mon Sep 17 00:00:00 2001 From: Yash Suthar <yashsuthar983@gmail.com> Date: Tue, 4 Nov 2025 14:07:22 +0530 Subject: [PATCH 329/819] Update CI auto-format (#6233) --------- Signed-off-by: Yash Suthar <yashsuthar983@gmail.com> --- .github/workflows/auto-format.yaml | 64 ++++++++++++++++++++++++++++++ .github/workflows/ci.yaml | 6 +-- 2 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/auto-format.yaml diff --git a/.github/workflows/auto-format.yaml b/.github/workflows/auto-format.yaml new file mode 100644 index 00000000000..2acf83715a0 --- /dev/null +++ b/.github/workflows/auto-format.yaml @@ -0,0 +1,64 @@ +on: + workflow_run: + workflows: ["CI"] + types: + - completed + +name: Auto format + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: true + +jobs: + auto_format_commit: + permissions: + contents: write + pull-requests: write + name: Auto-format code + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' && !contains(github.event.workflow_run.head_commit.message, '[skip ci]') }} + concurrency: + group: fmt-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: true + + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + ref: ${{ github.event.workflow_run.head_branch }} + repository: ${{ github.event.workflow_run.head_repository.full_name }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: Run cargo fmt + run: | + echo "Running cargo fmt --all" + cargo fmt --all + + - name: Commit and push if changes + id: commit + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + if [ -n "$(git status --porcelain)" ]; then + git add -u + git commit -m "Auto-format code [skip ci]" + git push + echo "formatted=true" >> $GITHUB_OUTPUT + else + echo "formatted=false" >> $GITHUB_OUTPUT + fi + + - name: Comment on PR if formatting was applied + if: steps.commit.outputs.formatted == 'true' && github.event.workflow_run.event == 'pull_request' + uses: marocchino/sticky-pull-request-comment@v2 + with: + message: | + Code has been automatically formatted. + No action needed. + the changes were committed with `[skip ci]`. diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 977f27f3762..2ce4b475773 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -307,15 +307,13 @@ jobs: run: python -I whats_left.py lint: - name: Check Rust code with rustfmt and clippy + name: Check Rust code with clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: - components: rustfmt, clippy - - name: run rustfmt - run: cargo fmt --check + components: clippy - name: run clippy on wasm run: cargo clippy --manifest-path=wasm/lib/Cargo.toml -- -Dwarnings - uses: actions/setup-python@v6 From 5cad66cebfc62a7912197ae299ac13e8629c560d Mon Sep 17 00:00:00 2001 From: Yash Suthar <yashsuthar983@gmail.com> Date: Tue, 4 Nov 2025 23:44:27 +0530 Subject: [PATCH 330/819] Revert "Update CI auto-format (#6233)" (#6236) This reverts commit 377151a57f1451df5febd09b183d8c4f31ede32d. --- .github/workflows/auto-format.yaml | 64 ------------------------------ .github/workflows/ci.yaml | 6 ++- 2 files changed, 4 insertions(+), 66 deletions(-) delete mode 100644 .github/workflows/auto-format.yaml diff --git a/.github/workflows/auto-format.yaml b/.github/workflows/auto-format.yaml deleted file mode 100644 index 2acf83715a0..00000000000 --- a/.github/workflows/auto-format.yaml +++ /dev/null @@ -1,64 +0,0 @@ -on: - workflow_run: - workflows: ["CI"] - types: - - completed - -name: Auto format - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} - cancel-in-progress: true - -jobs: - auto_format_commit: - permissions: - contents: write - pull-requests: write - name: Auto-format code - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'success' && !contains(github.event.workflow_run.head_commit.message, '[skip ci]') }} - concurrency: - group: fmt-${{ github.event.workflow_run.head_branch }} - cancel-in-progress: true - - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - fetch-depth: 0 - ref: ${{ github.event.workflow_run.head_branch }} - repository: ${{ github.event.workflow_run.head_repository.full_name }} - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt - - - name: Run cargo fmt - run: | - echo "Running cargo fmt --all" - cargo fmt --all - - - name: Commit and push if changes - id: commit - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - if [ -n "$(git status --porcelain)" ]; then - git add -u - git commit -m "Auto-format code [skip ci]" - git push - echo "formatted=true" >> $GITHUB_OUTPUT - else - echo "formatted=false" >> $GITHUB_OUTPUT - fi - - - name: Comment on PR if formatting was applied - if: steps.commit.outputs.formatted == 'true' && github.event.workflow_run.event == 'pull_request' - uses: marocchino/sticky-pull-request-comment@v2 - with: - message: | - Code has been automatically formatted. - No action needed. - the changes were committed with `[skip ci]`. diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2ce4b475773..977f27f3762 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -307,13 +307,15 @@ jobs: run: python -I whats_left.py lint: - name: Check Rust code with clippy + name: Check Rust code with rustfmt and clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: - components: clippy + components: rustfmt, clippy + - name: run rustfmt + run: cargo fmt --check - name: run clippy on wasm run: cargo clippy --manifest-path=wasm/lib/Cargo.toml -- -Dwarnings - uses: actions/setup-python@v6 From ed9a61d956b0a94fea5c82fd896be18848b13844 Mon Sep 17 00:00:00 2001 From: fanninpm <fanninpm@miamioh.edu> Date: Sun, 9 Nov 2025 04:00:59 -0500 Subject: [PATCH 331/819] Add builtin_items updater to whats_left job (#6238) Corresponds to RustPython/rustpython.github.io#81. --- .github/workflows/cron-ci.yaml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cron-ci.yaml b/.github/workflows/cron-ci.yaml index 239733ca306..a94c8df9e23 100644 --- a/.github/workflows/cron-ci.yaml +++ b/.github/workflows/cron-ci.yaml @@ -1,6 +1,6 @@ on: schedule: - - cron: '0 0 * * 6' + - cron: "0 0 * * 6" workflow_dispatch: push: paths: @@ -34,7 +34,7 @@ jobs: run: python scripts/cargo-llvm-cov.py continue-on-error: true - name: Run cargo-llvm-cov with Python test suite. - run: cargo llvm-cov --no-report run -- -m test -u all --slowest --fail-env-changed + run: cargo llvm-cov --no-report run -- -m test -u all --slowest --fail-env-changed continue-on-error: true - name: Prepare code coverage data run: cargo llvm-cov report --lcov --output-path='codecov.lcov' @@ -109,6 +109,23 @@ jobs: 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 + awk -f - ./_data/whats_left.temp > ./_data/whats_left/builtin_items.csv <<'EOF' + BEGIN { + OFS="," + print "builtin,name,is_inherited" + } + /^# builtin items/ { in_section=1; next } + /^$/ { if (in_section) exit } + in_section { + split($1, a, ".") + rest = "" + idx = index($0, " ") + if (idx > 0) { + rest = substr($0, idx+1) + } + print a[1], $1, rest + } + EOF 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 git push From d8a4a09ec0487b2b5ee96018243de5bb89dca62e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 10 Nov 2025 09:47:57 +0900 Subject: [PATCH 332/819] Fix #[pyclass(base=...)] notation (#6242) --- derive-impl/src/pyclass.rs | 4 +- derive-impl/src/util.rs | 33 +++- derive/src/lib.rs | 2 +- stdlib/src/ssl.rs | 14 +- vm/src/builtins/bool.rs | 2 +- vm/src/builtins/builtin_func.rs | 2 +- vm/src/exceptions.rs | 136 ++++++++--------- vm/src/stdlib/ast/pyast.rs | 246 +++++++++++++++--------------- vm/src/stdlib/ctypes/array.rs | 4 +- vm/src/stdlib/ctypes/base.rs | 4 +- vm/src/stdlib/ctypes/function.rs | 2 +- vm/src/stdlib/ctypes/structure.rs | 2 +- vm/src/stdlib/ctypes/union.rs | 2 +- vm/src/stdlib/io.rs | 22 +-- 14 files changed, 251 insertions(+), 224 deletions(-) diff --git a/derive-impl/src/pyclass.rs b/derive-impl/src/pyclass.rs index 99cddc84623..0b0769cac3c 100644 --- a/derive-impl/src/pyclass.rs +++ b/derive-impl/src/pyclass.rs @@ -308,7 +308,7 @@ fn generate_class_def( ident: &Ident, name: &str, module_name: Option<&str>, - base: Option<String>, + base: Option<syn::Path>, metaclass: Option<String>, unhashable: bool, attrs: &[Attribute], @@ -358,7 +358,6 @@ fn generate_class_def( Some(quote! { rustpython_vm::builtins::PyTuple }) } else { base.as_ref().map(|typ| { - let typ = Ident::new(typ, ident.span()); quote_spanned! { ident.span() => #typ } }) } @@ -382,7 +381,6 @@ fn generate_class_def( }); let base_or_object = if let Some(base) = base { - let base = Ident::new(&base, ident.span()); quote! { #base } } else { quote! { ::rustpython_vm::builtins::PyBaseObject } diff --git a/derive-impl/src/util.rs b/derive-impl/src/util.rs index f7f0d28fb92..379adc65b57 100644 --- a/derive-impl/src/util.rs +++ b/derive-impl/src/util.rs @@ -187,6 +187,35 @@ impl ItemMetaInner { Ok(value) } + pub fn _optional_path(&self, key: &str) -> Result<Option<syn::Path>> { + let value = if let Some((_, meta)) = self.meta_map.get(key) { + let Meta::NameValue(syn::MetaNameValue { value, .. }) = meta else { + bail_span!( + meta, + "#[{}({} = ...)] must be a name-value pair", + self.meta_name(), + key + ) + }; + + // Try to parse as a Path (identifier or path like Foo or foo::Bar) + match syn::parse2::<syn::Path>(value.to_token_stream()) { + Ok(path) => Some(path), + Err(_) => { + bail_span!( + value, + "#[{}({} = ...)] must be a valid type path (e.g., PyBaseException)", + self.meta_name(), + key + ) + } + } + } else { + None + }; + Ok(value) + } + pub fn _has_key(&self, key: &str) -> Result<bool> { Ok(matches!(self.meta_map.get(key), Some((_, _)))) } @@ -384,8 +413,8 @@ impl ClassItemMeta { self.inner()._optional_str("ctx") } - pub fn base(&self) -> Result<Option<String>> { - self.inner()._optional_str("base") + pub fn base(&self) -> Result<Option<syn::Path>> { + self.inner()._optional_path("base") } pub fn unhashable(&self) -> Result<bool> { diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 3b95c594a13..6aef58a2aa3 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -34,7 +34,7 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream { /// - `IMMUTABLETYPE`: class attributes are immutable. /// - `with`: which trait implementations are to be included in the python class. /// ```rust, ignore -/// #[pyclass(module = "my_module", name = "MyClass", base = "BaseClass")] +/// #[pyclass(module = "my_module", name = "MyClass", base = BaseClass)] /// struct MyStruct { /// x: i32, /// } diff --git a/stdlib/src/ssl.rs b/stdlib/src/ssl.rs index 9fd050a7efa..9604999d7da 100644 --- a/stdlib/src/ssl.rs +++ b/stdlib/src/ssl.rs @@ -246,7 +246,7 @@ mod _ssl { /// An error occurred in the SSL implementation. #[pyattr] - #[pyexception(name = "SSLError", base = "PyOSError")] + #[pyexception(name = "SSLError", base = PyOSError)] #[derive(Debug)] pub struct PySslError {} @@ -269,7 +269,7 @@ mod _ssl { /// A certificate could not be verified. #[pyattr] - #[pyexception(name = "SSLCertVerificationError", base = "PySslError")] + #[pyexception(name = "SSLCertVerificationError", base = PySslError)] #[derive(Debug)] pub struct PySslCertVerificationError {} @@ -278,7 +278,7 @@ mod _ssl { /// SSL/TLS session closed cleanly. #[pyattr] - #[pyexception(name = "SSLZeroReturnError", base = "PySslError")] + #[pyexception(name = "SSLZeroReturnError", base = PySslError)] #[derive(Debug)] pub struct PySslZeroReturnError {} @@ -287,7 +287,7 @@ mod _ssl { /// Non-blocking SSL socket needs to read more data. #[pyattr] - #[pyexception(name = "SSLWantReadError", base = "PySslError")] + #[pyexception(name = "SSLWantReadError", base = PySslError)] #[derive(Debug)] pub struct PySslWantReadError {} @@ -296,7 +296,7 @@ mod _ssl { /// Non-blocking SSL socket needs to write more data. #[pyattr] - #[pyexception(name = "SSLWantWriteError", base = "PySslError")] + #[pyexception(name = "SSLWantWriteError", base = PySslError)] #[derive(Debug)] pub struct PySslWantWriteError {} @@ -305,7 +305,7 @@ mod _ssl { /// System error when attempting SSL operation. #[pyattr] - #[pyexception(name = "SSLSyscallError", base = "PySslError")] + #[pyexception(name = "SSLSyscallError", base = PySslError)] #[derive(Debug)] pub struct PySslSyscallError {} @@ -314,7 +314,7 @@ mod _ssl { /// SSL/TLS connection terminated abruptly. #[pyattr] - #[pyexception(name = "SSLEOFError", base = "PySslError")] + #[pyexception(name = "SSLEOFError", base = PySslError)] #[derive(Debug)] pub struct PySslEOFError {} diff --git a/vm/src/builtins/bool.rs b/vm/src/builtins/bool.rs index a20e821d2d1..47e46541e39 100644 --- a/vm/src/builtins/bool.rs +++ b/vm/src/builtins/bool.rs @@ -77,7 +77,7 @@ impl PyObjectRef { } } -#[pyclass(name = "bool", module = false, base = "PyInt")] +#[pyclass(name = "bool", module = false, base = PyInt)] pub struct PyBool; impl PyPayload for PyBool { diff --git a/vm/src/builtins/builtin_func.rs b/vm/src/builtins/builtin_func.rs index 3df93398dd9..8c160c2eb4b 100644 --- a/vm/src/builtins/builtin_func.rs +++ b/vm/src/builtins/builtin_func.rs @@ -148,7 +148,7 @@ impl Representable for PyNativeFunction { impl Unconstructible for PyNativeFunction {} // `PyCMethodObject` in CPython -#[pyclass(name = "builtin_method", module = false, base = "PyNativeFunction")] +#[pyclass(name = "builtin_method", module = false, base = PyNativeFunction)] pub struct PyNativeMethod { pub(crate) func: PyNativeFunction, pub(crate) class: &'static Py<PyType>, // TODO: the actual life is &'self diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 029b12f5a47..1a9c95a930e 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1225,11 +1225,11 @@ pub(super) mod types { pub(super) args: PyRwLock<PyTupleRef>, } - #[pyexception(name, base = "PyBaseException", ctx = "system_exit", impl)] + #[pyexception(name, base = PyBaseException, ctx = "system_exit", impl)] #[derive(Debug)] pub struct PySystemExit {} - #[pyexception(name, base = "PyBaseException", ctx = "base_exception_group")] + #[pyexception(name, base = PyBaseException, ctx = "base_exception_group")] #[derive(Debug)] pub struct PyBaseExceptionGroup {} @@ -1245,23 +1245,23 @@ pub(super) mod types { } } - #[pyexception(name, base = "PyBaseExceptionGroup", ctx = "exception_group", impl)] + #[pyexception(name, base = PyBaseExceptionGroup, ctx = "exception_group", impl)] #[derive(Debug)] pub struct PyExceptionGroup {} - #[pyexception(name, base = "PyBaseException", ctx = "generator_exit", impl)] + #[pyexception(name, base = PyBaseException, ctx = "generator_exit", impl)] #[derive(Debug)] pub struct PyGeneratorExit {} - #[pyexception(name, base = "PyBaseException", ctx = "keyboard_interrupt", impl)] + #[pyexception(name, base = PyBaseException, ctx = "keyboard_interrupt", impl)] #[derive(Debug)] pub struct PyKeyboardInterrupt {} - #[pyexception(name, base = "PyBaseException", ctx = "exception_type", impl)] + #[pyexception(name, base = PyBaseException, ctx = "exception_type", impl)] #[derive(Debug)] pub struct PyException {} - #[pyexception(name, base = "PyException", ctx = "stop_iteration")] + #[pyexception(name, base = PyException, ctx = "stop_iteration")] #[derive(Debug)] pub struct PyStopIteration {} @@ -1279,31 +1279,31 @@ pub(super) mod types { } } - #[pyexception(name, base = "PyException", ctx = "stop_async_iteration", impl)] + #[pyexception(name, base = PyException, ctx = "stop_async_iteration", impl)] #[derive(Debug)] pub struct PyStopAsyncIteration {} - #[pyexception(name, base = "PyException", ctx = "arithmetic_error", impl)] + #[pyexception(name, base = PyException, ctx = "arithmetic_error", impl)] #[derive(Debug)] pub struct PyArithmeticError {} - #[pyexception(name, base = "PyArithmeticError", ctx = "floating_point_error", impl)] + #[pyexception(name, base = PyArithmeticError, ctx = "floating_point_error", impl)] #[derive(Debug)] pub struct PyFloatingPointError {} - #[pyexception(name, base = "PyArithmeticError", ctx = "overflow_error", impl)] + #[pyexception(name, base = PyArithmeticError, ctx = "overflow_error", impl)] #[derive(Debug)] pub struct PyOverflowError {} - #[pyexception(name, base = "PyArithmeticError", ctx = "zero_division_error", impl)] + #[pyexception(name, base = PyArithmeticError, ctx = "zero_division_error", impl)] #[derive(Debug)] pub struct PyZeroDivisionError {} - #[pyexception(name, base = "PyException", ctx = "assertion_error", impl)] + #[pyexception(name, base = PyException, ctx = "assertion_error", impl)] #[derive(Debug)] pub struct PyAssertionError {} - #[pyexception(name, base = "PyException", ctx = "attribute_error")] + #[pyexception(name, base = PyException, ctx = "attribute_error")] #[derive(Debug)] pub struct PyAttributeError {} @@ -1330,15 +1330,15 @@ pub(super) mod types { } } - #[pyexception(name, base = "PyException", ctx = "buffer_error", impl)] + #[pyexception(name, base = PyException, ctx = "buffer_error", impl)] #[derive(Debug)] pub struct PyBufferError {} - #[pyexception(name, base = "PyException", ctx = "eof_error", impl)] + #[pyexception(name, base = PyException, ctx = "eof_error", impl)] #[derive(Debug)] pub struct PyEOFError {} - #[pyexception(name, base = "PyException", ctx = "import_error")] + #[pyexception(name, base = PyException, ctx = "import_error")] #[derive(Debug)] pub struct PyImportError {} @@ -1383,19 +1383,19 @@ pub(super) mod types { } } - #[pyexception(name, base = "PyImportError", ctx = "module_not_found_error", impl)] + #[pyexception(name, base = PyImportError, ctx = "module_not_found_error", impl)] #[derive(Debug)] pub struct PyModuleNotFoundError {} - #[pyexception(name, base = "PyException", ctx = "lookup_error", impl)] + #[pyexception(name, base = PyException, ctx = "lookup_error", impl)] #[derive(Debug)] pub struct PyLookupError {} - #[pyexception(name, base = "PyLookupError", ctx = "index_error", impl)] + #[pyexception(name, base = PyLookupError, ctx = "index_error", impl)] #[derive(Debug)] pub struct PyIndexError {} - #[pyexception(name, base = "PyLookupError", ctx = "key_error")] + #[pyexception(name, base = PyLookupError, ctx = "key_error")] #[derive(Debug)] pub struct PyKeyError {} @@ -1415,19 +1415,19 @@ pub(super) mod types { } } - #[pyexception(name, base = "PyException", ctx = "memory_error", impl)] + #[pyexception(name, base = PyException, ctx = "memory_error", impl)] #[derive(Debug)] pub struct PyMemoryError {} - #[pyexception(name, base = "PyException", ctx = "name_error", impl)] + #[pyexception(name, base = PyException, ctx = "name_error", impl)] #[derive(Debug)] pub struct PyNameError {} - #[pyexception(name, base = "PyNameError", ctx = "unbound_local_error", impl)] + #[pyexception(name, base = PyNameError, ctx = "unbound_local_error", impl)] #[derive(Debug)] pub struct PyUnboundLocalError {} - #[pyexception(name, base = "PyException", ctx = "os_error")] + #[pyexception(name, base = PyException, ctx = "os_error")] #[derive(Debug)] pub struct PyOSError {} @@ -1558,25 +1558,25 @@ pub(super) mod types { } } - #[pyexception(name, base = "PyOSError", ctx = "blocking_io_error", impl)] + #[pyexception(name, base = PyOSError, ctx = "blocking_io_error", impl)] #[derive(Debug)] pub struct PyBlockingIOError {} - #[pyexception(name, base = "PyOSError", ctx = "child_process_error", impl)] + #[pyexception(name, base = PyOSError, ctx = "child_process_error", impl)] #[derive(Debug)] pub struct PyChildProcessError {} - #[pyexception(name, base = "PyOSError", ctx = "connection_error", impl)] + #[pyexception(name, base = PyOSError, ctx = "connection_error", impl)] #[derive(Debug)] pub struct PyConnectionError {} - #[pyexception(name, base = "PyConnectionError", ctx = "broken_pipe_error", impl)] + #[pyexception(name, base = PyConnectionError, ctx = "broken_pipe_error", impl)] #[derive(Debug)] pub struct PyBrokenPipeError {} #[pyexception( name, - base = "PyConnectionError", + base = PyConnectionError, ctx = "connection_aborted_error", impl )] @@ -1585,66 +1585,66 @@ pub(super) mod types { #[pyexception( name, - base = "PyConnectionError", + base = PyConnectionError, ctx = "connection_refused_error", impl )] #[derive(Debug)] pub struct PyConnectionRefusedError {} - #[pyexception(name, base = "PyConnectionError", ctx = "connection_reset_error", impl)] + #[pyexception(name, base = PyConnectionError, ctx = "connection_reset_error", impl)] #[derive(Debug)] pub struct PyConnectionResetError {} - #[pyexception(name, base = "PyOSError", ctx = "file_exists_error", impl)] + #[pyexception(name, base = PyOSError, ctx = "file_exists_error", impl)] #[derive(Debug)] pub struct PyFileExistsError {} - #[pyexception(name, base = "PyOSError", ctx = "file_not_found_error", impl)] + #[pyexception(name, base = PyOSError, ctx = "file_not_found_error", impl)] #[derive(Debug)] pub struct PyFileNotFoundError {} - #[pyexception(name, base = "PyOSError", ctx = "interrupted_error", impl)] + #[pyexception(name, base = PyOSError, ctx = "interrupted_error", impl)] #[derive(Debug)] pub struct PyInterruptedError {} - #[pyexception(name, base = "PyOSError", ctx = "is_a_directory_error", impl)] + #[pyexception(name, base = PyOSError, ctx = "is_a_directory_error", impl)] #[derive(Debug)] pub struct PyIsADirectoryError {} - #[pyexception(name, base = "PyOSError", ctx = "not_a_directory_error", impl)] + #[pyexception(name, base = PyOSError, ctx = "not_a_directory_error", impl)] #[derive(Debug)] pub struct PyNotADirectoryError {} - #[pyexception(name, base = "PyOSError", ctx = "permission_error", impl)] + #[pyexception(name, base = PyOSError, ctx = "permission_error", impl)] #[derive(Debug)] pub struct PyPermissionError {} - #[pyexception(name, base = "PyOSError", ctx = "process_lookup_error", impl)] + #[pyexception(name, base = PyOSError, ctx = "process_lookup_error", impl)] #[derive(Debug)] pub struct PyProcessLookupError {} - #[pyexception(name, base = "PyOSError", ctx = "timeout_error", impl)] + #[pyexception(name, base = PyOSError, ctx = "timeout_error", impl)] #[derive(Debug)] pub struct PyTimeoutError {} - #[pyexception(name, base = "PyException", ctx = "reference_error", impl)] + #[pyexception(name, base = PyException, ctx = "reference_error", impl)] #[derive(Debug)] pub struct PyReferenceError {} - #[pyexception(name, base = "PyException", ctx = "runtime_error", impl)] + #[pyexception(name, base = PyException, ctx = "runtime_error", impl)] #[derive(Debug)] pub struct PyRuntimeError {} - #[pyexception(name, base = "PyRuntimeError", ctx = "not_implemented_error", impl)] + #[pyexception(name, base = PyRuntimeError, ctx = "not_implemented_error", impl)] #[derive(Debug)] pub struct PyNotImplementedError {} - #[pyexception(name, base = "PyRuntimeError", ctx = "recursion_error", impl)] + #[pyexception(name, base = PyRuntimeError, ctx = "recursion_error", impl)] #[derive(Debug)] pub struct PyRecursionError {} - #[pyexception(name, base = "PyException", ctx = "syntax_error")] + #[pyexception(name, base = PyException, ctx = "syntax_error")] #[derive(Debug)] pub struct PySyntaxError {} @@ -1736,7 +1736,7 @@ pub(super) mod types { #[pyexception( name = "_IncompleteInputError", - base = "PySyntaxError", + base = PySyntaxError, ctx = "incomplete_input_error" )] #[derive(Debug)] @@ -1756,31 +1756,31 @@ pub(super) mod types { } } - #[pyexception(name, base = "PySyntaxError", ctx = "indentation_error", impl)] + #[pyexception(name, base = PySyntaxError, ctx = "indentation_error", impl)] #[derive(Debug)] pub struct PyIndentationError {} - #[pyexception(name, base = "PyIndentationError", ctx = "tab_error", impl)] + #[pyexception(name, base = PyIndentationError, ctx = "tab_error", impl)] #[derive(Debug)] pub struct PyTabError {} - #[pyexception(name, base = "PyException", ctx = "system_error", impl)] + #[pyexception(name, base = PyException, ctx = "system_error", impl)] #[derive(Debug)] pub struct PySystemError {} - #[pyexception(name, base = "PyException", ctx = "type_error", impl)] + #[pyexception(name, base = PyException, ctx = "type_error", impl)] #[derive(Debug)] pub struct PyTypeError {} - #[pyexception(name, base = "PyException", ctx = "value_error", impl)] + #[pyexception(name, base = PyException, ctx = "value_error", impl)] #[derive(Debug)] pub struct PyValueError {} - #[pyexception(name, base = "PyValueError", ctx = "unicode_error", impl)] + #[pyexception(name, base = PyValueError, ctx = "unicode_error", impl)] #[derive(Debug)] pub struct PyUnicodeError {} - #[pyexception(name, base = "PyUnicodeError", ctx = "unicode_decode_error")] + #[pyexception(name, base = PyUnicodeError, ctx = "unicode_decode_error")] #[derive(Debug)] pub struct PyUnicodeDecodeError {} @@ -1831,7 +1831,7 @@ pub(super) mod types { } } - #[pyexception(name, base = "PyUnicodeError", ctx = "unicode_encode_error")] + #[pyexception(name, base = PyUnicodeError, ctx = "unicode_encode_error")] #[derive(Debug)] pub struct PyUnicodeEncodeError {} @@ -1882,7 +1882,7 @@ pub(super) mod types { } } - #[pyexception(name, base = "PyUnicodeError", ctx = "unicode_translate_error")] + #[pyexception(name, base = PyUnicodeError, ctx = "unicode_translate_error")] #[derive(Debug)] pub struct PyUnicodeTranslateError {} @@ -1930,56 +1930,56 @@ pub(super) mod types { /// JIT error. #[cfg(feature = "jit")] - #[pyexception(name, base = "PyException", ctx = "jit_error", impl)] + #[pyexception(name, base = PyException, ctx = "jit_error", impl)] #[derive(Debug)] pub struct PyJitError {} // Warnings - #[pyexception(name, base = "PyException", ctx = "warning", impl)] + #[pyexception(name, base = PyException, ctx = "warning", impl)] #[derive(Debug)] pub struct PyWarning {} - #[pyexception(name, base = "PyWarning", ctx = "deprecation_warning", impl)] + #[pyexception(name, base = PyWarning, ctx = "deprecation_warning", impl)] #[derive(Debug)] pub struct PyDeprecationWarning {} - #[pyexception(name, base = "PyWarning", ctx = "pending_deprecation_warning", impl)] + #[pyexception(name, base = PyWarning, ctx = "pending_deprecation_warning", impl)] #[derive(Debug)] pub struct PyPendingDeprecationWarning {} - #[pyexception(name, base = "PyWarning", ctx = "runtime_warning", impl)] + #[pyexception(name, base = PyWarning, ctx = "runtime_warning", impl)] #[derive(Debug)] pub struct PyRuntimeWarning {} - #[pyexception(name, base = "PyWarning", ctx = "syntax_warning", impl)] + #[pyexception(name, base = PyWarning, ctx = "syntax_warning", impl)] #[derive(Debug)] pub struct PySyntaxWarning {} - #[pyexception(name, base = "PyWarning", ctx = "user_warning", impl)] + #[pyexception(name, base = PyWarning, ctx = "user_warning", impl)] #[derive(Debug)] pub struct PyUserWarning {} - #[pyexception(name, base = "PyWarning", ctx = "future_warning", impl)] + #[pyexception(name, base = PyWarning, ctx = "future_warning", impl)] #[derive(Debug)] pub struct PyFutureWarning {} - #[pyexception(name, base = "PyWarning", ctx = "import_warning", impl)] + #[pyexception(name, base = PyWarning, ctx = "import_warning", impl)] #[derive(Debug)] pub struct PyImportWarning {} - #[pyexception(name, base = "PyWarning", ctx = "unicode_warning", impl)] + #[pyexception(name, base = PyWarning, ctx = "unicode_warning", impl)] #[derive(Debug)] pub struct PyUnicodeWarning {} - #[pyexception(name, base = "PyWarning", ctx = "bytes_warning", impl)] + #[pyexception(name, base = PyWarning, ctx = "bytes_warning", impl)] #[derive(Debug)] pub struct PyBytesWarning {} - #[pyexception(name, base = "PyWarning", ctx = "resource_warning", impl)] + #[pyexception(name, base = PyWarning, ctx = "resource_warning", impl)] #[derive(Debug)] pub struct PyResourceWarning {} - #[pyexception(name, base = "PyWarning", ctx = "encoding_warning", impl)] + #[pyexception(name, base = PyWarning, ctx = "encoding_warning", impl)] #[derive(Debug)] pub struct PyEncodingWarning {} } diff --git a/vm/src/stdlib/ast/pyast.rs b/vm/src/stdlib/ast/pyast.rs index 8aae6c72e04..b891a605dc2 100644 --- a/vm/src/stdlib/ast/pyast.rs +++ b/vm/src/stdlib/ast/pyast.rs @@ -77,774 +77,774 @@ macro_rules! impl_node { }; } -#[pyclass(module = "_ast", name = "mod", base = "NodeAst")] +#[pyclass(module = "_ast", name = "mod", base = NodeAst)] pub(crate) struct NodeMod; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeMod {} impl_node!( - #[pyclass(module = "_ast", name = "Module", base = "NodeMod")] + #[pyclass(module = "_ast", name = "Module", base = NodeMod)] pub(crate) struct NodeModModule, fields: ["body", "type_ignores"], ); impl_node!( - #[pyclass(module = "_ast", name = "Interactive", base = "NodeMod")] + #[pyclass(module = "_ast", name = "Interactive", base = NodeMod)] pub(crate) struct NodeModInteractive, fields: ["body"], ); impl_node!( - #[pyclass(module = "_ast", name = "Expression", base = "NodeMod")] + #[pyclass(module = "_ast", name = "Expression", base = NodeMod)] pub(crate) struct NodeModExpression, fields: ["body"], ); -#[pyclass(module = "_ast", name = "stmt", base = "NodeAst")] +#[pyclass(module = "_ast", name = "stmt", base = NodeAst)] pub(crate) struct NodeStmt; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeStmt {} impl_node!( - #[pyclass(module = "_ast", name = "FunctionType", base = "NodeMod")] + #[pyclass(module = "_ast", name = "FunctionType", base = NodeMod)] pub(crate) struct NodeModFunctionType, fields: ["argtypes", "returns"], ); impl_node!( - #[pyclass(module = "_ast", name = "FunctionDef", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "FunctionDef", base = NodeStmt)] pub(crate) struct NodeStmtFunctionDef, fields: ["name", "args", "body", "decorator_list", "returns", "type_comment", "type_params"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "AsyncFunctionDef", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "AsyncFunctionDef", base = NodeStmt)] pub(crate) struct NodeStmtAsyncFunctionDef, fields: ["name", "args", "body", "decorator_list", "returns", "type_comment", "type_params"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "ClassDef", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "ClassDef", base = NodeStmt)] pub(crate) struct NodeStmtClassDef, fields: ["name", "bases", "keywords", "body", "decorator_list", "type_params"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Return", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Return", base = NodeStmt)] pub(crate) struct NodeStmtReturn, fields: ["value"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Delete", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Delete", base = NodeStmt)] pub(crate) struct NodeStmtDelete, fields: ["targets"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Assign", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Assign", base = NodeStmt)] pub(crate) struct NodeStmtAssign, fields: ["targets", "value", "type_comment"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "TypeAlias", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "TypeAlias", base = NodeStmt)] pub(crate) struct NodeStmtTypeAlias, fields: ["name", "type_params", "value"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "AugAssign", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "AugAssign", base = NodeStmt)] pub(crate) struct NodeStmtAugAssign, fields: ["target", "op", "value"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "AnnAssign", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "AnnAssign", base = NodeStmt)] pub(crate) struct NodeStmtAnnAssign, fields: ["target", "annotation", "value", "simple"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "For", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "For", base = NodeStmt)] pub(crate) struct NodeStmtFor, fields: ["target", "iter", "body", "orelse", "type_comment"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "AsyncFor", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "AsyncFor", base = NodeStmt)] pub(crate) struct NodeStmtAsyncFor, fields: ["target", "iter", "body", "orelse", "type_comment"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "While", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "While", base = NodeStmt)] pub(crate) struct NodeStmtWhile, fields: ["test", "body", "orelse"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "If", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "If", base = NodeStmt)] pub(crate) struct NodeStmtIf, fields: ["test", "body", "orelse"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "With", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "With", base = NodeStmt)] pub(crate) struct NodeStmtWith, fields: ["items", "body", "type_comment"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "AsyncWith", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "AsyncWith", base = NodeStmt)] pub(crate) struct NodeStmtAsyncWith, fields: ["items", "body", "type_comment"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Match", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Match", base = NodeStmt)] pub(crate) struct NodeStmtMatch, fields: ["subject", "cases"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Raise", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Raise", base = NodeStmt)] pub(crate) struct NodeStmtRaise, fields: ["exc", "cause"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Try", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Try", base = NodeStmt)] pub(crate) struct NodeStmtTry, fields: ["body", "handlers", "orelse", "finalbody"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "TryStar", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "TryStar", base = NodeStmt)] pub(crate) struct NodeStmtTryStar, fields: ["body", "handlers", "orelse", "finalbody"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Assert", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Assert", base = NodeStmt)] pub(crate) struct NodeStmtAssert, fields: ["test", "msg"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Import", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Import", base = NodeStmt)] pub(crate) struct NodeStmtImport, fields: ["names"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "ImportFrom", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "ImportFrom", base = NodeStmt)] pub(crate) struct NodeStmtImportFrom, fields: ["module", "names", "level"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Global", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Global", base = NodeStmt)] pub(crate) struct NodeStmtGlobal, fields: ["names"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Nonlocal", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Nonlocal", base = NodeStmt)] pub(crate) struct NodeStmtNonlocal, fields: ["names"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Expr", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Expr", base = NodeStmt)] pub(crate) struct NodeStmtExpr, fields: ["value"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Pass", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Pass", base = NodeStmt)] pub(crate) struct NodeStmtPass, attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Break", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Break", base = NodeStmt)] pub(crate) struct NodeStmtBreak, attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); -#[pyclass(module = "_ast", name = "expr", base = "NodeAst")] +#[pyclass(module = "_ast", name = "expr", base = NodeAst)] pub(crate) struct NodeExpr; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeExpr {} impl_node!( - #[pyclass(module = "_ast", name = "Continue", base = "NodeStmt")] + #[pyclass(module = "_ast", name = "Continue", base = NodeStmt)] pub(crate) struct NodeStmtContinue, attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "BoolOp", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "BoolOp", base = NodeExpr)] pub(crate) struct NodeExprBoolOp, fields: ["op", "values"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "NamedExpr", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "NamedExpr", base = NodeExpr)] pub(crate) struct NodeExprNamedExpr, fields: ["target", "value"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "BinOp", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "BinOp", base = NodeExpr)] pub(crate) struct NodeExprBinOp, fields: ["left", "op", "right"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "UnaryOp", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "UnaryOp", base = NodeExpr)] pub(crate) struct NodeExprUnaryOp, fields: ["op", "operand"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Lambda", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Lambda", base = NodeExpr)] pub(crate) struct NodeExprLambda, fields: ["args", "body"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "IfExp", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "IfExp", base = NodeExpr)] pub(crate) struct NodeExprIfExp, fields: ["test", "body", "orelse"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Dict", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Dict", base = NodeExpr)] pub(crate) struct NodeExprDict, fields: ["keys", "values"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Set", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Set", base = NodeExpr)] pub(crate) struct NodeExprSet, fields: ["elts"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "ListComp", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "ListComp", base = NodeExpr)] pub(crate) struct NodeExprListComp, fields: ["elt", "generators"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "SetComp", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "SetComp", base = NodeExpr)] pub(crate) struct NodeExprSetComp, fields: ["elt", "generators"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "DictComp", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "DictComp", base = NodeExpr)] pub(crate) struct NodeExprDictComp, fields: ["key", "value", "generators"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "GeneratorExp", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "GeneratorExp", base = NodeExpr)] pub(crate) struct NodeExprGeneratorExp, fields: ["elt", "generators"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Await", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Await", base = NodeExpr)] pub(crate) struct NodeExprAwait, fields: ["value"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Yield", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Yield", base = NodeExpr)] pub(crate) struct NodeExprYield, fields: ["value"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "YieldFrom", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "YieldFrom", base = NodeExpr)] pub(crate) struct NodeExprYieldFrom, fields: ["value"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Compare", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Compare", base = NodeExpr)] pub(crate) struct NodeExprCompare, fields: ["left", "ops", "comparators"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Call", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Call", base = NodeExpr)] pub(crate) struct NodeExprCall, fields: ["func", "args", "keywords"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "FormattedValue", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "FormattedValue", base = NodeExpr)] pub(crate) struct NodeExprFormattedValue, fields: ["value", "conversion", "format_spec"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "JoinedStr", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "JoinedStr", base = NodeExpr)] pub(crate) struct NodeExprJoinedStr, fields: ["values"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Constant", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Constant", base = NodeExpr)] pub(crate) struct NodeExprConstant, fields: ["value", "kind"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Attribute", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Attribute", base = NodeExpr)] pub(crate) struct NodeExprAttribute, fields: ["value", "attr", "ctx"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Subscript", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Subscript", base = NodeExpr)] pub(crate) struct NodeExprSubscript, fields: ["value", "slice", "ctx"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Starred", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Starred", base = NodeExpr)] pub(crate) struct NodeExprStarred, fields: ["value", "ctx"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Name", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Name", base = NodeExpr)] pub(crate) struct NodeExprName, fields: ["id", "ctx"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "List", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "List", base = NodeExpr)] pub(crate) struct NodeExprList, fields: ["elts", "ctx"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Tuple", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Tuple", base = NodeExpr)] pub(crate) struct NodeExprTuple, fields: ["elts", "ctx"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); -#[pyclass(module = "_ast", name = "expr_context", base = "NodeAst")] +#[pyclass(module = "_ast", name = "expr_context", base = NodeAst)] pub(crate) struct NodeExprContext; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeExprContext {} impl_node!( - #[pyclass(module = "_ast", name = "Slice", base = "NodeExpr")] + #[pyclass(module = "_ast", name = "Slice", base = NodeExpr)] pub(crate) struct NodeExprSlice, fields: ["lower", "upper", "step"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "Load", base = "NodeExprContext")] + #[pyclass(module = "_ast", name = "Load", base = NodeExprContext)] pub(crate) struct NodeExprContextLoad, ); impl_node!( - #[pyclass(module = "_ast", name = "Store", base = "NodeExprContext")] + #[pyclass(module = "_ast", name = "Store", base = NodeExprContext)] pub(crate) struct NodeExprContextStore, ); -#[pyclass(module = "_ast", name = "boolop", base = "NodeAst")] +#[pyclass(module = "_ast", name = "boolop", base = NodeAst)] pub(crate) struct NodeBoolOp; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeBoolOp {} impl_node!( - #[pyclass(module = "_ast", name = "Del", base = "NodeExprContext")] + #[pyclass(module = "_ast", name = "Del", base = NodeExprContext)] pub(crate) struct NodeExprContextDel, ); impl_node!( - #[pyclass(module = "_ast", name = "And", base = "NodeBoolOp")] + #[pyclass(module = "_ast", name = "And", base = NodeBoolOp)] pub(crate) struct NodeBoolOpAnd, ); -#[pyclass(module = "_ast", name = "operator", base = "NodeAst")] +#[pyclass(module = "_ast", name = "operator", base = NodeAst)] pub(crate) struct NodeOperator; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeOperator {} impl_node!( - #[pyclass(module = "_ast", name = "Or", base = "NodeBoolOp")] + #[pyclass(module = "_ast", name = "Or", base = NodeBoolOp)] pub(crate) struct NodeBoolOpOr, ); impl_node!( - #[pyclass(module = "_ast", name = "Add", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "Add", base = NodeOperator)] pub(crate) struct NodeOperatorAdd, ); impl_node!( - #[pyclass(module = "_ast", name = "Sub", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "Sub", base = NodeOperator)] pub(crate) struct NodeOperatorSub, ); impl_node!( - #[pyclass(module = "_ast", name = "Mult", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "Mult", base = NodeOperator)] pub(crate) struct NodeOperatorMult, ); impl_node!( - #[pyclass(module = "_ast", name = "MatMult", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "MatMult", base = NodeOperator)] pub(crate) struct NodeOperatorMatMult, ); impl_node!( - #[pyclass(module = "_ast", name = "Div", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "Div", base = NodeOperator)] pub(crate) struct NodeOperatorDiv, ); impl_node!( - #[pyclass(module = "_ast", name = "Mod", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "Mod", base = NodeOperator)] pub(crate) struct NodeOperatorMod, ); impl_node!( - #[pyclass(module = "_ast", name = "Pow", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "Pow", base = NodeOperator)] pub(crate) struct NodeOperatorPow, ); impl_node!( - #[pyclass(module = "_ast", name = "LShift", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "LShift", base = NodeOperator)] pub(crate) struct NodeOperatorLShift, ); impl_node!( - #[pyclass(module = "_ast", name = "RShift", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "RShift", base = NodeOperator)] pub(crate) struct NodeOperatorRShift, ); impl_node!( - #[pyclass(module = "_ast", name = "BitOr", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "BitOr", base = NodeOperator)] pub(crate) struct NodeOperatorBitOr, ); impl_node!( - #[pyclass(module = "_ast", name = "BitXor", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "BitXor", base = NodeOperator)] pub(crate) struct NodeOperatorBitXor, ); impl_node!( - #[pyclass(module = "_ast", name = "BitAnd", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "BitAnd", base = NodeOperator)] pub(crate) struct NodeOperatorBitAnd, ); -#[pyclass(module = "_ast", name = "unaryop", base = "NodeAst")] +#[pyclass(module = "_ast", name = "unaryop", base = NodeAst)] pub(crate) struct NodeUnaryOp; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeUnaryOp {} impl_node!( - #[pyclass(module = "_ast", name = "FloorDiv", base = "NodeOperator")] + #[pyclass(module = "_ast", name = "FloorDiv", base = NodeOperator)] pub(crate) struct NodeOperatorFloorDiv, ); impl_node!( - #[pyclass(module = "_ast", name = "Invert", base = "NodeUnaryOp")] + #[pyclass(module = "_ast", name = "Invert", base = NodeUnaryOp)] pub(crate) struct NodeUnaryOpInvert, ); impl_node!( - #[pyclass(module = "_ast", name = "Not", base = "NodeUnaryOp")] + #[pyclass(module = "_ast", name = "Not", base = NodeUnaryOp)] pub(crate) struct NodeUnaryOpNot, ); impl_node!( - #[pyclass(module = "_ast", name = "UAdd", base = "NodeUnaryOp")] + #[pyclass(module = "_ast", name = "UAdd", base = NodeUnaryOp)] pub(crate) struct NodeUnaryOpUAdd, ); -#[pyclass(module = "_ast", name = "cmpop", base = "NodeAst")] +#[pyclass(module = "_ast", name = "cmpop", base = NodeAst)] pub(crate) struct NodeCmpOp; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeCmpOp {} impl_node!( - #[pyclass(module = "_ast", name = "USub", base = "NodeUnaryOp")] + #[pyclass(module = "_ast", name = "USub", base = NodeUnaryOp)] pub(crate) struct NodeUnaryOpUSub, ); impl_node!( - #[pyclass(module = "_ast", name = "Eq", base = "NodeCmpOp")] + #[pyclass(module = "_ast", name = "Eq", base = NodeCmpOp)] pub(crate) struct NodeCmpOpEq, ); impl_node!( - #[pyclass(module = "_ast", name = "NotEq", base = "NodeCmpOp")] + #[pyclass(module = "_ast", name = "NotEq", base = NodeCmpOp)] pub(crate) struct NodeCmpOpNotEq, ); impl_node!( - #[pyclass(module = "_ast", name = "Lt", base = "NodeCmpOp")] + #[pyclass(module = "_ast", name = "Lt", base = NodeCmpOp)] pub(crate) struct NodeCmpOpLt, ); impl_node!( - #[pyclass(module = "_ast", name = "LtE", base = "NodeCmpOp")] + #[pyclass(module = "_ast", name = "LtE", base = NodeCmpOp)] pub(crate) struct NodeCmpOpLtE, ); impl_node!( - #[pyclass(module = "_ast", name = "Gt", base = "NodeCmpOp")] + #[pyclass(module = "_ast", name = "Gt", base = NodeCmpOp)] pub(crate) struct NodeCmpOpGt, ); impl_node!( - #[pyclass(module = "_ast", name = "GtE", base = "NodeCmpOp")] + #[pyclass(module = "_ast", name = "GtE", base = NodeCmpOp)] pub(crate) struct NodeCmpOpGtE, ); impl_node!( - #[pyclass(module = "_ast", name = "Is", base = "NodeCmpOp")] + #[pyclass(module = "_ast", name = "Is", base = NodeCmpOp)] pub(crate) struct NodeCmpOpIs, ); impl_node!( - #[pyclass(module = "_ast", name = "IsNot", base = "NodeCmpOp")] + #[pyclass(module = "_ast", name = "IsNot", base = NodeCmpOp)] pub(crate) struct NodeCmpOpIsNot, ); impl_node!( - #[pyclass(module = "_ast", name = "In", base = "NodeCmpOp")] + #[pyclass(module = "_ast", name = "In", base = NodeCmpOp)] pub(crate) struct NodeCmpOpIn, ); impl_node!( - #[pyclass(module = "_ast", name = "NotIn", base = "NodeCmpOp")] + #[pyclass(module = "_ast", name = "NotIn", base = NodeCmpOp)] pub(crate) struct NodeCmpOpNotIn, ); -#[pyclass(module = "_ast", name = "excepthandler", base = "NodeAst")] +#[pyclass(module = "_ast", name = "excepthandler", base = NodeAst)] pub(crate) struct NodeExceptHandler; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeExceptHandler {} impl_node!( - #[pyclass(module = "_ast", name = "comprehension", base = "NodeAst")] + #[pyclass(module = "_ast", name = "comprehension", base = NodeAst)] pub(crate) struct NodeComprehension, fields: ["target", "iter", "ifs", "is_async"], ); impl_node!( - #[pyclass(module = "_ast", name = "ExceptHandler", base = "NodeExceptHandler")] + #[pyclass(module = "_ast", name = "ExceptHandler", base = NodeExceptHandler)] pub(crate) struct NodeExceptHandlerExceptHandler, fields: ["type", "name", "body"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "arguments", base = "NodeAst")] + #[pyclass(module = "_ast", name = "arguments", base = NodeAst)] pub(crate) struct NodeArguments, fields: ["posonlyargs", "args", "vararg", "kwonlyargs", "kw_defaults", "kwarg", "defaults"], ); impl_node!( - #[pyclass(module = "_ast", name = "arg", base = "NodeAst")] + #[pyclass(module = "_ast", name = "arg", base = NodeAst)] pub(crate) struct NodeArg, fields: ["arg", "annotation", "type_comment"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "keyword", base = "NodeAst")] + #[pyclass(module = "_ast", name = "keyword", base = NodeAst)] pub(crate) struct NodeKeyword, fields: ["arg", "value"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "alias", base = "NodeAst")] + #[pyclass(module = "_ast", name = "alias", base = NodeAst)] pub(crate) struct NodeAlias, fields: ["name", "asname"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "withitem", base = "NodeAst")] + #[pyclass(module = "_ast", name = "withitem", base = NodeAst)] pub(crate) struct NodeWithItem, fields: ["context_expr", "optional_vars"], ); -#[pyclass(module = "_ast", name = "pattern", base = "NodeAst")] +#[pyclass(module = "_ast", name = "pattern", base = NodeAst)] pub(crate) struct NodePattern; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodePattern {} impl_node!( - #[pyclass(module = "_ast", name = "match_case", base = "NodeAst")] + #[pyclass(module = "_ast", name = "match_case", base = NodeAst)] pub(crate) struct NodeMatchCase, fields: ["pattern", "guard", "body"], ); impl_node!( - #[pyclass(module = "_ast", name = "MatchValue", base = "NodePattern")] + #[pyclass(module = "_ast", name = "MatchValue", base = NodePattern)] pub(crate) struct NodePatternMatchValue, fields: ["value"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "MatchSingleton", base = "NodePattern")] + #[pyclass(module = "_ast", name = "MatchSingleton", base = NodePattern)] pub(crate) struct NodePatternMatchSingleton, fields: ["value"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "MatchSequence", base = "NodePattern")] + #[pyclass(module = "_ast", name = "MatchSequence", base = NodePattern)] pub(crate) struct NodePatternMatchSequence, fields: ["patterns"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "MatchMapping", base = "NodePattern")] + #[pyclass(module = "_ast", name = "MatchMapping", base = NodePattern)] pub(crate) struct NodePatternMatchMapping, fields: ["keys", "patterns", "rest"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "MatchClass", base = "NodePattern")] + #[pyclass(module = "_ast", name = "MatchClass", base = NodePattern)] pub(crate) struct NodePatternMatchClass, fields: ["cls", "patterns", "kwd_attrs", "kwd_patterns"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "MatchStar", base = "NodePattern")] + #[pyclass(module = "_ast", name = "MatchStar", base = NodePattern)] pub(crate) struct NodePatternMatchStar, fields: ["name"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "MatchAs", base = "NodePattern")] + #[pyclass(module = "_ast", name = "MatchAs", base = NodePattern)] pub(crate) struct NodePatternMatchAs, fields: ["pattern", "name"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); -#[pyclass(module = "_ast", name = "type_ignore", base = "NodeAst")] +#[pyclass(module = "_ast", name = "type_ignore", base = NodeAst)] pub(crate) struct NodeTypeIgnore; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeTypeIgnore {} impl_node!( - #[pyclass(module = "_ast", name = "MatchOr", base = "NodePattern")] + #[pyclass(module = "_ast", name = "MatchOr", base = NodePattern)] pub(crate) struct NodePatternMatchOr, fields: ["patterns"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); -#[pyclass(module = "_ast", name = "type_param", base = "NodeAst")] +#[pyclass(module = "_ast", name = "type_param", base = NodeAst)] pub(crate) struct NodeTypeParam; #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeTypeParam {} impl_node!( - #[pyclass(module = "_ast", name = "TypeIgnore", base = "NodeTypeIgnore")] + #[pyclass(module = "_ast", name = "TypeIgnore", base = NodeTypeIgnore)] pub(crate) struct NodeTypeIgnoreTypeIgnore, fields: ["lineno", "tag"], ); impl_node!( - #[pyclass(module = "_ast", name = "TypeVar", base = "NodeTypeParam")] + #[pyclass(module = "_ast", name = "TypeVar", base = NodeTypeParam)] pub(crate) struct NodeTypeParamTypeVar, fields: ["name", "bound"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "ParamSpec", base = "NodeTypeParam")] + #[pyclass(module = "_ast", name = "ParamSpec", base = NodeTypeParam)] pub(crate) struct NodeTypeParamParamSpec, fields: ["name"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], ); impl_node!( - #[pyclass(module = "_ast", name = "TypeVarTuple", base = "NodeTypeParam")] + #[pyclass(module = "_ast", name = "TypeVarTuple", base = NodeTypeParam)] pub(crate) struct NodeTypeParamTypeVarTuple, fields: ["name"], attributes: ["lineno", "col_offset", "end_lineno", "end_col_offset"], diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index 82306c8b0b1..5290ec42f37 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -10,7 +10,7 @@ use crossbeam_utils::atomic::AtomicCell; use rustpython_common::lock::PyRwLock; use rustpython_vm::stdlib::ctypes::base::PyCData; -#[pyclass(name = "PyCArrayType", base = "PyType", module = "_ctypes")] +#[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")] #[derive(PyPayload)] pub struct PyCArrayType { pub(super) inner: PyCArray, @@ -49,7 +49,7 @@ impl PyCArrayType {} #[pyclass( name = "Array", - base = "PyCData", + base = PyCData, metaclass = "PyCArrayType", module = "_ctypes" )] diff --git a/vm/src/stdlib/ctypes/base.rs b/vm/src/stdlib/ctypes/base.rs index 2fcac469b95..23cb505adaf 100644 --- a/vm/src/stdlib/ctypes/base.rs +++ b/vm/src/stdlib/ctypes/base.rs @@ -157,7 +157,7 @@ pub struct PyCData { #[pyclass] impl PyCData {} -#[pyclass(module = "_ctypes", name = "PyCSimpleType", base = "PyType")] +#[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)] pub struct PyCSimpleType {} #[pyclass(flags(BASETYPE))] @@ -176,7 +176,7 @@ impl PyCSimpleType { #[pyclass( module = "_ctypes", name = "_SimpleCData", - base = "PyCData", + base = PyCData, metaclass = "PyCSimpleType" )] #[derive(PyPayload)] diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 034b1bd0723..6703dcc0f52 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -122,7 +122,7 @@ impl Function { } } -#[pyclass(module = "_ctypes", name = "CFuncPtr", base = "PyCData")] +#[pyclass(module = "_ctypes", name = "CFuncPtr", base = PyCData)] #[derive(PyPayload)] pub struct PyCFuncPtr { pub name: PyRwLock<String>, diff --git a/vm/src/stdlib/ctypes/structure.rs b/vm/src/stdlib/ctypes/structure.rs index 8ca8bb51dfc..aef95fe5619 100644 --- a/vm/src/stdlib/ctypes/structure.rs +++ b/vm/src/stdlib/ctypes/structure.rs @@ -8,7 +8,7 @@ use rustpython_vm::types::Constructor; use std::collections::HashMap; use std::fmt::Debug; -#[pyclass(module = "_ctypes", name = "Structure", base = "PyCData")] +#[pyclass(module = "_ctypes", name = "Structure", base = PyCData)] #[derive(PyPayload, Debug)] pub struct PyCStructure { #[allow(dead_code)] diff --git a/vm/src/stdlib/ctypes/union.rs b/vm/src/stdlib/ctypes/union.rs index 2d76dbc9ca4..93a53b2b6db 100644 --- a/vm/src/stdlib/ctypes/union.rs +++ b/vm/src/stdlib/ctypes/union.rs @@ -1,7 +1,7 @@ use super::base::PyCData; // TODO: metaclass = "UnionType" -#[pyclass(module = "_ctypes", name = "Union", base = "PyCData")] +#[pyclass(module = "_ctypes", name = "Union", base = PyCData)] pub struct PyCUnion {} #[pyclass(flags(BASETYPE, IMMUTABLETYPE))] diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index e06560780ce..d1ffe5a9723 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -607,7 +607,7 @@ mod _io { } #[pyattr] - #[pyclass(name = "_RawIOBase", base = "_IOBase")] + #[pyclass(name = "_RawIOBase", base = _IOBase)] pub(super) struct _RawIOBase; #[pyclass(flags(BASETYPE, HAS_DICT))] @@ -665,7 +665,7 @@ mod _io { } #[pyattr] - #[pyclass(name = "_BufferedIOBase", base = "_IOBase")] + #[pyclass(name = "_BufferedIOBase", base = _IOBase)] struct _BufferedIOBase; #[pyclass(flags(BASETYPE))] @@ -728,7 +728,7 @@ mod _io { // TextIO Base has no public constructor #[pyattr] - #[pyclass(name = "_TextIOBase", base = "_IOBase")] + #[pyclass(name = "_TextIOBase", base = _IOBase)] #[derive(Debug, PyPayload)] struct _TextIOBase; @@ -1728,7 +1728,7 @@ mod _io { } #[pyattr] - #[pyclass(name = "BufferedReader", base = "_BufferedIOBase")] + #[pyclass(name = "BufferedReader", base = _BufferedIOBase)] #[derive(Debug, Default, PyPayload)] struct BufferedReader { data: PyThreadMutex<BufferedData>, @@ -1785,7 +1785,7 @@ mod _io { } #[pyattr] - #[pyclass(name = "BufferedWriter", base = "_BufferedIOBase")] + #[pyclass(name = "BufferedWriter", base = _BufferedIOBase)] #[derive(Debug, Default, PyPayload)] struct BufferedWriter { data: PyThreadMutex<BufferedData>, @@ -1818,7 +1818,7 @@ mod _io { impl DefaultConstructor for BufferedWriter {} #[pyattr] - #[pyclass(name = "BufferedRandom", base = "_BufferedIOBase")] + #[pyclass(name = "BufferedRandom", base = _BufferedIOBase)] #[derive(Debug, Default, PyPayload)] struct BufferedRandom { data: PyThreadMutex<BufferedData>, @@ -1860,7 +1860,7 @@ mod _io { impl DefaultConstructor for BufferedRandom {} #[pyattr] - #[pyclass(name = "BufferedRWPair", base = "_BufferedIOBase")] + #[pyclass(name = "BufferedRWPair", base = _BufferedIOBase)] #[derive(Debug, Default, PyPayload)] struct BufferedRWPair { read: BufferedReader, @@ -2274,7 +2274,7 @@ mod _io { } #[pyattr] - #[pyclass(name = "TextIOWrapper", base = "_TextIOBase")] + #[pyclass(name = "TextIOWrapper", base = _TextIOBase)] #[derive(Debug, Default, PyPayload)] struct TextIOWrapper { data: PyThreadMutex<Option<TextIOData>>, @@ -3460,7 +3460,7 @@ mod _io { } #[pyattr] - #[pyclass(name = "StringIO", base = "_TextIOBase")] + #[pyclass(name = "StringIO", base = _TextIOBase)] #[derive(Debug, PyPayload)] struct StringIO { buffer: PyRwLock<BufferedIO>, @@ -3605,7 +3605,7 @@ mod _io { } #[pyattr] - #[pyclass(name = "BytesIO", base = "_BufferedIOBase")] + #[pyclass(name = "BytesIO", base = _BufferedIOBase)] #[derive(Debug, PyPayload)] struct BytesIO { buffer: PyRwLock<BufferedIO>, @@ -4241,7 +4241,7 @@ mod fileio { } #[pyattr] - #[pyclass(module = "io", name, base = "_RawIOBase")] + #[pyclass(module = "io", name, base = _RawIOBase)] #[derive(Debug, PyPayload)] pub(super) struct FileIO { fd: AtomicCell<i32>, From 9792001703ae653114693a662012bab2ea8a35ae Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 10 Nov 2025 02:48:25 +0200 Subject: [PATCH 333/819] Add newtype of `CodeUnits` (#6241) --- compiler/codegen/src/ir.rs | 6 +-- compiler/core/src/bytecode.rs | 73 ++++++++++++++++++++++++++++++++--- compiler/core/src/marshal.rs | 17 +------- vm/src/builtins/code.rs | 29 +++----------- 4 files changed, 77 insertions(+), 48 deletions(-) diff --git a/compiler/codegen/src/ir.rs b/compiler/codegen/src/ir.rs index 7cd173c821d..f08b37e8887 100644 --- a/compiler/codegen/src/ir.rs +++ b/compiler/codegen/src/ir.rs @@ -4,8 +4,8 @@ use crate::{IndexMap, IndexSet, error::InternalError}; use rustpython_compiler_core::{ OneIndexed, SourceLocation, bytecode::{ - CodeFlags, CodeObject, CodeUnit, ConstantData, InstrDisplayContext, Instruction, Label, - OpArg, PyCodeLocationInfoKind, + CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, InstrDisplayContext, Instruction, + Label, OpArg, PyCodeLocationInfoKind, }, }; @@ -214,7 +214,7 @@ impl CodeInfo { qualname: qualname.unwrap_or(obj_name), max_stackdepth, - instructions: instructions.into_boxed_slice(), + instructions: CodeUnits::from(instructions), locations: locations.into_boxed_slice(), constants: constants.into_iter().collect(), names: name_cache.into_iter().collect(), diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index 4ab666da99e..2cfb70e2782 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -1,13 +1,16 @@ //! Implement python as a virtual machine with bytecode. This module //! implements bytecode structure. -use crate::{OneIndexed, SourceLocation}; +use crate::{ + marshal::MarshalError, + {OneIndexed, SourceLocation}, +}; use bitflags::bitflags; use itertools::Itertools; use malachite_bigint::BigInt; use num_complex::Complex64; use rustpython_wtf8::{Wtf8, Wtf8Buf}; -use std::{collections::BTreeSet, fmt, hash, marker::PhantomData, mem}; +use std::{collections::BTreeSet, fmt, hash, marker::PhantomData, mem, ops::Deref}; #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] #[repr(i8)] @@ -195,7 +198,7 @@ impl ConstantBag for BasicBag { /// a code object. Also a module has a code object. #[derive(Clone)] pub struct CodeObject<C: Constant = ConstantData> { - pub instructions: Box<[CodeUnit]>, + pub instructions: CodeUnits, pub locations: Box<[SourceLocation]>, pub flags: CodeFlags, /// Number of positional-only arguments @@ -257,6 +260,12 @@ impl OpArgByte { } } +impl From<u8> for OpArgByte { + fn from(raw: u8) -> Self { + Self(raw) + } +} + impl fmt::Debug for OpArgByte { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) @@ -808,14 +817,14 @@ impl From<Instruction> for u8 { } impl TryFrom<u8> for Instruction { - type Error = crate::marshal::MarshalError; + type Error = MarshalError; #[inline] - fn try_from(value: u8) -> Result<Self, crate::marshal::MarshalError> { + fn try_from(value: u8) -> Result<Self, MarshalError> { if value <= u8::from(LAST_INSTRUCTION) { Ok(unsafe { std::mem::transmute::<u8, Self>(value) }) } else { - Err(crate::marshal::MarshalError::InvalidBytecode) + Err(MarshalError::InvalidBytecode) } } } @@ -835,6 +844,58 @@ impl CodeUnit { } } +impl TryFrom<&[u8]> for CodeUnit { + type Error = MarshalError; + + fn try_from(value: &[u8]) -> Result<Self, Self::Error> { + match value.len() { + 2 => Ok(Self::new(value[0].try_into()?, value[1].into())), + _ => Err(Self::Error::InvalidBytecode), + } + } +} + +#[derive(Clone)] +pub struct CodeUnits(Box<[CodeUnit]>); + +impl TryFrom<&[u8]> for CodeUnits { + type Error = MarshalError; + + fn try_from(value: &[u8]) -> Result<Self, Self::Error> { + if !value.len().is_multiple_of(2) { + return Err(Self::Error::InvalidBytecode); + } + + value.chunks_exact(2).map(CodeUnit::try_from).collect() + } +} + +impl<const N: usize> From<[CodeUnit; N]> for CodeUnits { + fn from(value: [CodeUnit; N]) -> Self { + Self(Box::from(value)) + } +} + +impl From<Vec<CodeUnit>> for CodeUnits { + fn from(value: Vec<CodeUnit>) -> Self { + Self(value.into_boxed_slice()) + } +} + +impl FromIterator<CodeUnit> for CodeUnits { + fn from_iter<T: IntoIterator<Item = CodeUnit>>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl Deref for CodeUnits { + type Target = [CodeUnit]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + use self::Instruction::*; bitflags! { diff --git a/compiler/core/src/marshal.rs b/compiler/core/src/marshal.rs index f803317772d..39e48071678 100644 --- a/compiler/core/src/marshal.rs +++ b/compiler/core/src/marshal.rs @@ -165,19 +165,6 @@ impl<'a> ReadBorrowed<'a> for &'a [u8] { } } -/// Parses bytecode bytes into CodeUnit instructions. -/// Each instruction is 2 bytes: opcode and argument. -pub fn parse_instructions_from_bytes(bytes: &[u8]) -> Result<Box<[CodeUnit]>> { - bytes - .chunks_exact(2) - .map(|cu| { - let op = Instruction::try_from(cu[0])?; - let arg = OpArgByte(cu[1]); - Ok(CodeUnit { op, arg }) - }) - .collect() -} - pub struct Cursor<B> { pub data: B, pub position: usize, @@ -197,8 +184,8 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>( bag: Bag, ) -> Result<CodeObject<Bag::Constant>> { let len = rdr.read_u32()?; - let instructions = rdr.read_slice(len * 2)?; - let instructions = parse_instructions_from_bytes(instructions)?; + let raw_instructions = rdr.read_slice(len * 2)?; + let instructions = CodeUnits::try_from(raw_instructions)?; let len = rdr.read_u32()?; let locations = (0..len) diff --git a/vm/src/builtins/code.rs b/vm/src/builtins/code.rs index 2a22993a9e7..f9f3429e303 100644 --- a/vm/src/builtins/code.rs +++ b/vm/src/builtins/code.rs @@ -1,12 +1,10 @@ -/*! Infamous code object. The python class `code` - -*/ +//! Infamous code object. The python class `code` use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType, PyTypeRef}; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::PyStrInterned, - bytecode::{self, AsBag, BorrowedConstant, CodeFlags, CodeUnit, Constant, ConstantBag}, + bytecode::{self, AsBag, BorrowedConstant, CodeFlags, Constant, ConstantBag}, class::{PyClassImpl, StaticType}, convert::ToPyObject, frozen, @@ -15,11 +13,7 @@ use crate::{ }; use malachite_bigint::BigInt; use num_traits::Zero; -use rustpython_compiler_core::{ - OneIndexed, - bytecode::PyCodeLocationInfoKind, - marshal::{MarshalError, parse_instructions_from_bytes}, -}; +use rustpython_compiler_core::{OneIndexed, bytecode::CodeUnits, bytecode::PyCodeLocationInfoKind}; use std::{borrow::Borrow, fmt, ops::Deref}; /// State for iterating through code address ranges @@ -457,7 +451,7 @@ impl Constructor for PyCode { // Parse and validate bytecode from bytes let bytecode_bytes = args.co_code.as_bytes(); - let instructions = parse_bytecode(bytecode_bytes) + let instructions = CodeUnits::try_from(bytecode_bytes) .map_err(|e| vm.new_value_error(format!("invalid bytecode: {}", e)))?; // Convert constants @@ -925,7 +919,7 @@ impl PyCode { let instructions = match co_code { OptionalArg::Present(code_bytes) => { // Parse and validate bytecode from bytes - parse_bytecode(code_bytes.as_bytes()) + CodeUnits::try_from(code_bytes.as_bytes()) .map_err(|e| vm.new_value_error(format!("invalid bytecode: {}", e)))? } OptionalArg::Missing => self.code.instructions.clone(), @@ -1033,19 +1027,6 @@ impl ToPyObject for bytecode::CodeObject { } } -/// Validates and parses bytecode bytes into CodeUnit instructions. -/// Returns MarshalError if bytecode is invalid (odd length or contains invalid opcodes). -/// Note: Returning MarshalError is not necessary at this point because this is not a part of marshalling API. -/// However, we (temporarily) reuse MarshalError for simplicity. -fn parse_bytecode(bytecode_bytes: &[u8]) -> Result<Box<[CodeUnit]>, MarshalError> { - // Bytecode must have even length (each instruction is 2 bytes) - if !bytecode_bytes.len().is_multiple_of(2) { - return Err(MarshalError::InvalidBytecode); - } - - parse_instructions_from_bytes(bytecode_bytes) -} - // Helper struct for reading linetable struct LineTableReader<'a> { data: &'a [u8], From c9ba9560b5820ce2cf750b809810b00ac7f75f46 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:58:08 +0900 Subject: [PATCH 334/819] new_buffer_error (#6243) --- Lib/test/test_binascii.py | 1 - Lib/test/test_ssl.py | 1 - vm/src/function/buffer.rs | 6 +++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_binascii.py b/Lib/test/test_binascii.py index 02ca06a3c07..cf11ffce7f1 100644 --- a/Lib/test/test_binascii.py +++ b/Lib/test/test_binascii.py @@ -490,7 +490,6 @@ def test_base64_roundtrip(self, binary, newline): restored = binascii.a2b_base64(self.type2test(converted)) self.assertConversion(binary, converted, restored, newline=newline) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_c_contiguity(self): m = memoryview(bytearray(b'noncontig')) noncontig_writable = m[::-2] diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 55e6bb59143..fea3a2ce692 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -1811,7 +1811,6 @@ def test_pending(self): bio.read() self.assertEqual(bio.pending, 0) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_buffer_types(self): bio = ssl.MemoryBIO() bio.write(b'foo') diff --git a/vm/src/function/buffer.rs b/vm/src/function/buffer.rs index 8a2a471ac94..e8f835e1dac 100644 --- a/vm/src/function/buffer.rs +++ b/vm/src/function/buffer.rs @@ -22,7 +22,7 @@ impl PyObject { buffer .as_contiguous() .map(|x| f(&x)) - .ok_or_else(|| vm.new_type_error("non-contiguous buffer is not a bytes-like object")) + .ok_or_else(|| vm.new_buffer_error("non-contiguous buffer is not a bytes-like object")) } pub fn try_rw_bytes_like<R>( @@ -81,7 +81,7 @@ impl<'a> TryFromBorrowedObject<'a> for ArgBytesLike { if buffer.desc.is_contiguous() { Ok(Self(buffer)) } else { - Err(vm.new_type_error("non-contiguous buffer is not a bytes-like object")) + Err(vm.new_buffer_error("non-contiguous buffer is not a bytes-like object")) } } } @@ -121,7 +121,7 @@ impl<'a> TryFromBorrowedObject<'a> for ArgMemoryBuffer { fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> { let buffer = PyBuffer::try_from_borrowed_object(vm, obj)?; if !buffer.desc.is_contiguous() { - Err(vm.new_type_error("non-contiguous buffer is not a bytes-like object")) + Err(vm.new_buffer_error("non-contiguous buffer is not a bytes-like object")) } else if buffer.desc.readonly { Err(vm.new_type_error("buffer is not a read-write bytes-like object")) } else { From 2d4617236e4b7fc3ba88beac71d48a1263fa4e43 Mon Sep 17 00:00:00 2001 From: Yash Suthar <yashsuthar983@gmail.com> Date: Mon, 10 Nov 2025 20:16:14 +0530 Subject: [PATCH 335/819] Update CI auto-formate (#6237) --------- Signed-off-by: Yash Suthar <yashsuthar983@gmail.com> Co-authored-by: fanninpm <luxverdans@sbcglobal.net> --- .github/workflows/ci.yaml | 58 ++++++++++++++-- .github/workflows/pr-auto-commit.yaml | 99 +++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/pr-auto-commit.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 977f27f3762..c61d46c4dac 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -307,15 +307,13 @@ jobs: run: python -I whats_left.py lint: - name: Check Rust code with rustfmt and clippy + name: Check Rust code with clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: - components: rustfmt, clippy - - name: run rustfmt - run: cargo fmt --check + components: clippy - name: run clippy on wasm run: cargo clippy --manifest-path=wasm/lib/Cargo.toml -- -Dwarnings - uses: actions/setup-python@v6 @@ -450,3 +448,55 @@ jobs: run: wasmer run --dir `pwd` target/wasm32-wasip1/release/rustpython.wasm -- `pwd`/extra_tests/snippets/stdlib_random.py - name: run cpython unittest run: wasmer run --dir `pwd` target/wasm32-wasip1/release/rustpython.wasm -- `pwd`/Lib/test/test_int.py + + auto_format_commit: + needs: [rust_tests, exotic_targets, snippets_cpython, lint, miri, wasm, wasm-wasi] + permissions: + contents: write + pull-requests: write + name: Auto-format code + runs-on: ubuntu-latest + if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} + concurrency: + group: fmt-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + ref: ${{ github.head_ref || github.ref_name }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: Run cargo fmt + run: | + echo "Running cargo fmt --all" + cargo fmt --all + + - name: Commit and push if changes + id: commit + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + if [ -n "$(git status --porcelain)" ]; then + git add -u + git commit -m "Auto-format code [skip ci]" + git push + echo "formatted=true" >> $GITHUB_OUTPUT + else + echo "formatted=false" >> $GITHUB_OUTPUT + fi + + - name: Comment on PR if formatting was applied + if: steps.commit.outputs.formatted == 'true' && github.event_name == 'pull_request' + uses: marocchino/sticky-pull-request-comment@v2 + with: + message: | + Code has been automatically formatted. + No action needed. + the changes were committed with `[skip ci]`. diff --git a/.github/workflows/pr-auto-commit.yaml b/.github/workflows/pr-auto-commit.yaml new file mode 100644 index 00000000000..8546c2abe51 --- /dev/null +++ b/.github/workflows/pr-auto-commit.yaml @@ -0,0 +1,99 @@ +name: PR Auto-format + +# This workflow triggers when a PR is opened/updated +on: + pull_request_target: + types: [opened, synchronize, reopened] + branches: + - main + - release + +concurrency: + group: pr-fmt-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + auto_format: + if: | + !contains(github.event.pull_request.labels.*.name, 'skip:ci') && + !contains(github.event.pull_request.head.sha, '[skip ci]') + permissions: + contents: write + pull-requests: write + checks: read + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Checkout PR branch + uses: actions/checkout@v5 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + # Wait for all PR check runs to complete + - name: Wait for all checks to complete + uses: poseidon/wait-for-status-checks@v0.6.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + delay: 60 + interval: 30 + timeout: 7200 + + - name: CI completed successfully + run: echo "CI workflow completed successfully - proceeding with auto-format" + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: Run cargo fmt + run: | + echo "Running cargo fmt --all on PR #${{ github.event.pull_request.number }}" + cargo fmt --all + + - name: Check for formatting changes + id: check_changes + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + else + echo "has_changes=false" >> $GITHUB_OUTPUT + fi + + - name: Commit and push formatting changes + if: steps.check_changes.outputs.has_changes == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git add -u + git commit -m "Auto-format code [skip ci]" + + git push origin HEAD:${{ github.event.pull_request.head.ref }} + + - name: Comment on PR + if: steps.check_changes.outputs.has_changes == 'true' + uses: marocchino/sticky-pull-request-comment@v2 + with: + number: ${{ github.event.pull_request.number }} + message: | + **Code has been automatically formatted** + + The code in this PR has been formatted using `cargo fmt`. + The changes have been committed with `[skip ci]` to avoid triggering another CI run. + + **Triggered by commit:** `${{ github.event.pull_request.head.sha }}` + **Last formatted:** ${{ github.event.pull_request.updated_at }} + + You may need to pull the latest changes before pushing again: + ```bash + git pull origin ${{ github.event.pull_request.head.ref }} + ``` + + - name: No formatting needed + if: steps.check_changes.outputs.has_changes == 'false' + run: echo "Code is already properly formatted" From 0f8c0bc8a8130807983e574bffd6886d44818628 Mon Sep 17 00:00:00 2001 From: Yash Suthar <yashsuthar983@gmail.com> Date: Tue, 11 Nov 2025 04:05:08 +0530 Subject: [PATCH 336/819] remove old auto-formte implimentaion (#6251) Signed-off-by: Yash Suthar <yashsuthar983@gmail.com> --- .github/workflows/ci.yaml | 52 --------------------------------------- 1 file changed, 52 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c61d46c4dac..2ce4b475773 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -448,55 +448,3 @@ jobs: run: wasmer run --dir `pwd` target/wasm32-wasip1/release/rustpython.wasm -- `pwd`/extra_tests/snippets/stdlib_random.py - name: run cpython unittest run: wasmer run --dir `pwd` target/wasm32-wasip1/release/rustpython.wasm -- `pwd`/Lib/test/test_int.py - - auto_format_commit: - needs: [rust_tests, exotic_targets, snippets_cpython, lint, miri, wasm, wasm-wasi] - permissions: - contents: write - pull-requests: write - name: Auto-format code - runs-on: ubuntu-latest - if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} - concurrency: - group: fmt-${{ github.ref }} - cancel-in-progress: true - - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - fetch-depth: 0 - ref: ${{ github.head_ref || github.ref_name }} - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt - - - name: Run cargo fmt - run: | - echo "Running cargo fmt --all" - cargo fmt --all - - - name: Commit and push if changes - id: commit - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - if [ -n "$(git status --porcelain)" ]; then - git add -u - git commit -m "Auto-format code [skip ci]" - git push - echo "formatted=true" >> $GITHUB_OUTPUT - else - echo "formatted=false" >> $GITHUB_OUTPUT - fi - - - name: Comment on PR if formatting was applied - if: steps.commit.outputs.formatted == 'true' && github.event_name == 'pull_request' - uses: marocchino/sticky-pull-request-comment@v2 - with: - message: | - Code has been automatically formatted. - No action needed. - the changes were committed with `[skip ci]`. From 456293023301e4d024cf20ed65275ca4766048a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:24:16 +0900 Subject: [PATCH 337/819] Bump streetsidesoftware/cspell-action from 7 to 8 (#6246) Bumps [streetsidesoftware/cspell-action](https://github.com/streetsidesoftware/cspell-action) from 7 to 8. - [Release notes](https://github.com/streetsidesoftware/cspell-action/releases) - [Changelog](https://github.com/streetsidesoftware/cspell-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/streetsidesoftware/cspell-action/compare/v7...v8) --- updated-dependencies: - dependency-name: streetsidesoftware/cspell-action dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2ce4b475773..02afeff5713 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -336,7 +336,7 @@ jobs: - name: install extra dictionaries run: npm install @cspell/dict-en_us @cspell/dict-cpp @cspell/dict-python @cspell/dict-rust @cspell/dict-win32 @cspell/dict-shell - name: spell checker - uses: streetsidesoftware/cspell-action@v7 + uses: streetsidesoftware/cspell-action@v8 with: files: '**/*.rs' incremental_files_only: true From 9ce85862ce13b42461cf32e720d2d2f4caf0c65b Mon Sep 17 00:00:00 2001 From: Jiseok CHOI <jiseok.dev@gmail.com> Date: Fri, 14 Nov 2025 10:31:47 +0900 Subject: [PATCH 338/819] Add BaseException.add_note() and __notes__ attribute support (#6252) --- Lib/test/test_exceptions.py | 1 - vm/src/exceptions.rs | 25 ++++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 6148bd3a6fc..61f4156dc6d 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -601,7 +601,6 @@ def test_invalid_setstate(self): with self.assertRaisesRegex(TypeError, "state is not a dictionary"): e.__setstate__(42) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_notes(self): for e in [BaseException(1), Exception(2), ValueError(3)]: with self.subTest(e=e): diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 1a9c95a930e..3cf26c1d5e9 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -4,7 +4,7 @@ use crate::object::{Traverse, TraverseFn}; use crate::{ AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ - PyNone, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, + PyList, PyNone, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, traceback::{PyTraceback, PyTracebackRef}, }, class::{PyClassImpl, StaticType}, @@ -652,6 +652,29 @@ impl PyRef<PyBaseException> { Ok(self) } + #[pymethod] + fn add_note(self, note: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { + let dict = self + .as_object() + .dict() + .ok_or_else(|| vm.new_attribute_error("Exception object has no __dict__"))?; + + let notes = if let Ok(notes) = dict.get_item("__notes__", vm) { + notes + } else { + let new_notes = vm.ctx.new_list(vec![]); + dict.set_item("__notes__", new_notes.clone().into(), vm)?; + new_notes.into() + }; + + let notes = notes + .downcast::<PyList>() + .map_err(|_| vm.new_type_error("__notes__ must be a list"))?; + + notes.borrow_vec_mut().push(note.into()); + Ok(()) + } + #[pymethod] fn __reduce__(self, vm: &VirtualMachine) -> PyTupleRef { if let Some(dict) = self.as_object().dict().filter(|x| !x.is_empty()) { From db1adaa2c26f83f39f97ffbec6b03261f2792bd6 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:47:51 +0200 Subject: [PATCH 339/819] Move `__doc__` crate to `crates/doc` (#6234) * Add `__doc__` crate * Base auto-generate ci * Add dummy files * Update docs * Set codegen-units to 1 for doc db * Mark `*.inc.rs` as auto generated * Disable doctest * Reset docs --- .gitattributes | 1 + .github/workflows/ci.yaml | 4 +- .github/workflows/update-doc-db.yml | 95 + Cargo.lock | 73 +- Cargo.toml | 8 +- crates/doc/.gitignore | 1 + crates/doc/Cargo.toml | 17 + crates/doc/LICENSE | 277 + crates/doc/generate.py | 214 + crates/doc/src/data.inc.rs | 9007 +++++++++++++++++++++++++++ crates/doc/src/lib.rs | 12 + derive-impl/Cargo.toml | 2 +- derive-impl/src/lib.rs | 1 - derive-impl/src/pyclass.rs | 7 +- derive-impl/src/pymodule.rs | 16 +- 15 files changed, 9702 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/update-doc-db.yml create mode 100644 crates/doc/.gitignore create mode 100644 crates/doc/Cargo.toml create mode 100644 crates/doc/LICENSE create mode 100644 crates/doc/generate.py create mode 100644 crates/doc/src/data.inc.rs create mode 100644 crates/doc/src/lib.rs diff --git a/.gitattributes b/.gitattributes index f54bcd3b725..d1dd182a9b0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,4 @@ vm/src/stdlib/ast/gen.rs linguist-generated -merge Lib/*.py text working-tree-encoding=UTF-8 eol=LF **/*.rs text working-tree-encoding=UTF-8 eol=LF *.pck binary +crates/rustpython_doc_db/src/*.inc.rs linguist-generated=true diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 02afeff5713..9ab4610ed11 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -113,7 +113,7 @@ jobs: RUST_BACKTRACE: full name: Run rust tests runs-on: ${{ matrix.os }} - timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 30 }} + timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 35 }} strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] @@ -239,7 +239,7 @@ jobs: RUST_BACKTRACE: full name: Run snippets and cpython tests runs-on: ${{ matrix.os }} - timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 30 }} + timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 35 }} strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] diff --git a/.github/workflows/update-doc-db.yml b/.github/workflows/update-doc-db.yml new file mode 100644 index 00000000000..dcef94f20d2 --- /dev/null +++ b/.github/workflows/update-doc-db.yml @@ -0,0 +1,95 @@ +name: Update doc DB + +permissions: + contents: read + +on: + workflow_dispatch: + inputs: + python-version: + description: Target python version to generate doc db for + type: string + default: "3.13.9" + +defaults: + run: + shell: bash + working-directory: ./crates/rustpython-doc + +jobs: + generate: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + sparse-checkout: | + crates/rustpython-doc + + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + with: + python-version: ${{ inputs.python-version }} + + - name: Generate docs + run: python ./generate.py + + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: doc-db-${{ inputs.python-version }}-${{ matrix.os }} + path: "crates/rustpython-doc/generated/*.json" + if-no-files-found: error + retention-days: 7 + overwrite: true + + merge: + runs-on: ubuntu-latest + needs: generate + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + sparse-checkout: | + crates/rustpython-doc + + - name: Download generated doc DBs + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + pattern: "doc-db-${{ inputs.python-version }}-**" + path: crates/rustpython-doc/generated/ + merge-multiple: true + + - name: Transform JSON + run: | + # Merge all artifacts + jq -s "add" --sort-keys generated/*.json > generated/merged.json + + # Format merged json for the phf macro + jq -r 'to_entries[] | " \(.key | @json) => \(.value | @json),"' generated/merged.json > generated/raw_entries.txt + + OUTPUT_FILE='src/data.inc.rs' + + echo -n '' > $OUTPUT_FILE + + echo '// This file was auto-generated by `.github/workflows/update-doc-db.yml`.' >> $OUTPUT_FILE + echo "// CPython version: ${{ inputs.python-version }}" >> $OUTPUT_FILE + echo '// spell-checker: disable' >> $OUTPUT_FILE + + echo '' >> $OUTPUT_FILE + + echo "pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! {" >> $OUTPUT_FILE + cat generated/raw_entries.txt >> $OUTPUT_FILE + echo '};' >> $OUTPUT_FILE + + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: doc-db-${{ inputs.python-version }} + path: "crates/rustpython-doc/src/data.inc.rs" + if-no-files-found: error + retention-days: 7 + overwrite: true diff --git a/Cargo.lock b/Cargo.lock index 9903fd19334..e1b1d958d30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -893,6 +893,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fd-lock" version = "4.0.4" @@ -1825,8 +1831,18 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_macros", - "phf_shared", + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", ] [[package]] @@ -1835,8 +1851,8 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.11.3", + "phf_shared 0.11.3", ] [[package]] @@ -1845,18 +1861,41 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared", + "phf_shared 0.11.3", "rand 0.8.5", ] +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", "syn", @@ -1871,6 +1910,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -2489,10 +2537,9 @@ dependencies = [ [[package]] name = "rustpython-doc" -version = "0.3.0" -source = "git+https://github.com/RustPython/__doc__?tag=0.3.0#8b62ce5d796d68a091969c9fa5406276cb483f79" +version = "0.4.0" dependencies = [ - "once_cell", + "phf 0.13.1", ] [[package]] @@ -2587,7 +2634,7 @@ dependencies = [ "page_size", "parking_lot", "paste", - "phf", + "phf 0.11.3", "pymath", "rand_core 0.9.3", "rustix", @@ -3368,7 +3415,7 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1673eca9782c84de5f81b82e4109dcfb3611c8ba0d52930ec4a9478f547b2dd" dependencies = [ - "phf", + "phf 0.11.3", "unicode_names2_generator 1.3.0", ] @@ -3378,7 +3425,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d189085656ca1203291e965444e7f6a2723fbdd1dd9f34f8482e79bafd8338a0" dependencies = [ - "phf", + "phf 0.11.3", "unicode_names2_generator 2.0.0", ] diff --git a/Cargo.toml b/Cargo.toml index 3cdc471dc3d..b01734ae7ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,10 @@ opt-level = 3 # https://github.com/rust-lang/rust/issues/92869 # lto = "thin" +# Doesn't change often +[profile.release.package.rustpython-doc] +codegen-units = 1 + [profile.bench] lto = "thin" codegen-units = 1 @@ -132,6 +136,7 @@ members = [ "derive-impl", "wtf8", "wasm/lib", + "crates/*", ] [workspace.package] @@ -156,13 +161,14 @@ 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-wtf8 = { path = "wtf8", version = "0.4.0" } -rustpython-doc = { git = "https://github.com/RustPython/__doc__", tag = "0.3.0", version = "0.3.0" } +rustpython-doc = { path = "crates/doc", version = "0.4.0" } ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } +phf = { version = "0.13.1", default-features = false, features = ["macros"]} ahash = "0.8.12" ascii = "1.1" bitflags = "2.9.4" diff --git a/crates/doc/.gitignore b/crates/doc/.gitignore new file mode 100644 index 00000000000..9ab870da897 --- /dev/null +++ b/crates/doc/.gitignore @@ -0,0 +1 @@ +generated/ diff --git a/crates/doc/Cargo.toml b/crates/doc/Cargo.toml new file mode 100644 index 00000000000..3435fabc8b5 --- /dev/null +++ b/crates/doc/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rustpython-doc" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +license-file = "LICENSE" + +[dependencies] +phf = { workspace = true } + +[lib] +doctest = false # Crashes when true + +[lints] +workspace = true diff --git a/crates/doc/LICENSE b/crates/doc/LICENSE new file mode 100644 index 00000000000..20cf39097c6 --- /dev/null +++ b/crates/doc/LICENSE @@ -0,0 +1,277 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001 Python Software Foundation; All Rights Reserved" +are retained in Python alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/crates/doc/generate.py b/crates/doc/generate.py new file mode 100644 index 00000000000..2f553ac0a19 --- /dev/null +++ b/crates/doc/generate.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python +import argparse +import inspect +import json +import os +import pathlib +import platform +import pydoc +import re +import sys +import types +import typing +import warnings +from importlib.machinery import EXTENSION_SUFFIXES, ExtensionFileLoader + +if typing.TYPE_CHECKING: + from collections.abc import Iterable + +OUTPUT_FILE = pathlib.Path(__file__).parent / "generated" / f"{sys.platform}.json" +OUTPUT_FILE.parent.mkdir(exist_ok=True) + +UNICODE_ESCAPE = re.compile(r"\\u([0-9]+)") + +IGNORED_MODULES = {"this", "antigravity"} +IGNORED_ATTRS = { + "__annotations__", + "__class__", + "__dict__", + "__dir__", + "__doc__", + "__file__", + "__name__", + "__qualname__", +} + + +type Parts = tuple[str, ...] + + +class DocEntry(typing.NamedTuple): + parts: Parts + raw_doc: str | None + + @property + def key(self) -> str: + return ".".join(self.parts) + + @property + def doc(self) -> str: + assert self.raw_doc is not None + + return re.sub(UNICODE_ESCAPE, r"\\u{\1}", inspect.cleandoc(self.raw_doc)) + + +def is_c_extension(module: types.ModuleType) -> bool: + """ + Check whether a module was written in C. + + Returns + ------- + bool + + Notes + ----- + Adapted from: https://stackoverflow.com/a/39304199 + """ + loader = getattr(module, "__loader__", None) + if isinstance(loader, ExtensionFileLoader): + return True + + try: + inspect.getsource(module) + except (OSError, TypeError): + return True + + try: + module_filename = inspect.getfile(module) + except TypeError: + return True + + module_filetype = os.path.splitext(module_filename)[1] + return module_filetype in EXTENSION_SUFFIXES + + +def is_child_of(obj: typing.Any, module: types.ModuleType) -> bool: + """ + Whether or not an object is a child of a module. + + Returns + ------- + bool + """ + return inspect.getmodule(obj) is module + + +def iter_modules() -> "Iterable[types.ModuleType]": + """ + Yields + ------ + :class:`types.Module` + Python modules. + """ + for module_name in sys.stdlib_module_names - IGNORED_MODULES: + try: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + module = __import__(module_name) + except ImportError: + warnings.warn(f"Could not import {module_name}", category=ImportWarning) + continue + + yield module + + +def iter_c_modules() -> "Iterable[types.ModuleType]": + """ + Yields + ------ + :class:`types.Module` + Modules that are written in C. (not pure python) + """ + yield from filter(is_c_extension, iter_modules()) + + +def traverse( + obj: typing.Any, module: types.ModuleType, parts: Parts = () +) -> "typing.Iterable[DocEntry]": + if inspect.ismodule(obj): + parts += (obj.__name__,) + + if any(f(obj) for f in (inspect.ismodule, inspect.isclass, inspect.isbuiltin)): + yield DocEntry(parts, pydoc._getowndoc(obj)) + + for name, attr in inspect.getmembers(obj): + if name in IGNORED_ATTRS: + continue + + if attr == obj: + continue + + if (module is obj) and (not is_child_of(attr, module)): + continue + + # Don't recurse into modules imported by our module. i.e. `ipaddress.py` imports `re` don't traverse `re` + if (not inspect.ismodule(obj)) and inspect.ismodule(attr): + continue + + new_parts = parts + (name,) + + attr_typ = type(attr) + is_type_or_builtin = any(attr_typ is x for x in (type, type(__builtins__))) + + if is_type_or_builtin: + yield from traverse(attr, module, new_parts) + continue + + is_callable = ( + callable(attr) + or not issubclass(attr_typ, type) + or attr_typ.__name__ in ("getset_descriptor", "member_descriptor") + ) + + is_func = any( + f(attr) + for f in (inspect.isfunction, inspect.ismethod, inspect.ismethoddescriptor) + ) + + if is_callable or is_func: + yield DocEntry(new_parts, pydoc._getowndoc(attr)) + + +def find_doc_entries() -> "Iterable[DocEntry]": + yield from ( + doc_entry + for module in iter_c_modules() + for doc_entry in traverse(module, module) + ) + yield from (doc_entry for doc_entry in traverse(__builtins__, __builtins__)) + + builtin_types = [ + type(None), + type(bytearray().__iter__()), + type(bytes().__iter__()), + type(dict().__iter__()), + type(dict().items()), + type(dict().items().__iter__()), + type(dict().values()), + type(dict().values().__iter__()), + type(lambda: ...), + type(list().__iter__()), + type(memoryview(b"").__iter__()), + type(range(0).__iter__()), + type(set().__iter__()), + type(str().__iter__()), + type(tuple().__iter__()), + ] + for typ in builtin_types: + parts = ("builtins", typ.__name__) + yield DocEntry(parts, pydoc._getowndoc(typ)) + yield from traverse(typ, __builtins__, parts) + + +def main(): + docs = { + entry.key: entry.doc + for entry in find_doc_entries() + if entry.raw_doc is not None + } + dumped = json.dumps(docs, sort_keys=True, indent=4) + OUTPUT_FILE.write_text(dumped) + + +if __name__ == "__main__": + main() diff --git a/crates/doc/src/data.inc.rs b/crates/doc/src/data.inc.rs new file mode 100644 index 00000000000..ee08b59f7ba --- /dev/null +++ b/crates/doc/src/data.inc.rs @@ -0,0 +1,9007 @@ +// This file was auto-generated by `.github/workflows/update-doc-db.yml`. +// CPython version: 3.13.9 +// spell-checker: disable + +pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { + "_abc" => "Module contains faster C implementation of abc.ABCMeta", + "_abc._abc_init" => "Internal ABC helper for class set-up. Should be never used outside abc module.", + "_abc._abc_instancecheck" => "Internal ABC helper for instance checks. Should be never used outside abc module.", + "_abc._abc_register" => "Internal ABC helper for subclasss registration. Should be never used outside abc module.", + "_abc._abc_subclasscheck" => "Internal ABC helper for subclasss checks. Should be never used outside abc module.", + "_abc._get_dump" => "Internal ABC helper for cache and registry debugging.\n\nReturn shallow copies of registry, of both caches, and\nnegative cache version. Don't call this function directly,\ninstead use ABC._dump_registry() for a nice repr.", + "_abc._reset_caches" => "Internal ABC helper to reset both caches of a given class.\n\nShould be only used by refleak.py", + "_abc._reset_registry" => "Internal ABC helper to reset registry of a given class.\n\nShould be only used by refleak.py", + "_abc.get_cache_token" => "Returns the current ABC cache token.\n\nThe token is an opaque object (supporting equality testing) identifying the\ncurrent version of the ABC cache for virtual subclasses. The token changes\nwith every call to register() on any ABC.", + "_asyncio" => "Accelerator module for asyncio", + "_asyncio.Future" => "This class is *almost* compatible with concurrent.futures.Future.\n\nDifferences:\n\n- result() and exception() do not take a timeout argument and\n raise an exception when the future isn't done yet.\n\n- Callbacks registered with add_done_callback() are always called\n via the event loop's call_soon_threadsafe().\n\n- This class is not compatible with the wait() and as_completed()\n methods in the concurrent.futures package.", + "_asyncio.Future.__await__" => "Return an iterator to be used in await expression.", + "_asyncio.Future.__class_getitem__" => "See PEP 585", + "_asyncio.Future.__del__" => "Called when the instance is about to be destroyed.", + "_asyncio.Future.__delattr__" => "Implement delattr(self, name).", + "_asyncio.Future.__eq__" => "Return self==value.", + "_asyncio.Future.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_asyncio.Future.__ge__" => "Return self>=value.", + "_asyncio.Future.__getattribute__" => "Return getattr(self, name).", + "_asyncio.Future.__getstate__" => "Helper for pickle.", + "_asyncio.Future.__gt__" => "Return self>value.", + "_asyncio.Future.__hash__" => "Return hash(self).", + "_asyncio.Future.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_asyncio.Future.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_asyncio.Future.__iter__" => "Implement iter(self).", + "_asyncio.Future.__le__" => "Return self<=value.", + "_asyncio.Future.__lt__" => "Return self<value.", + "_asyncio.Future.__ne__" => "Return self!=value.", + "_asyncio.Future.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_asyncio.Future.__reduce__" => "Helper for pickle.", + "_asyncio.Future.__reduce_ex__" => "Helper for pickle.", + "_asyncio.Future.__repr__" => "Return repr(self).", + "_asyncio.Future.__setattr__" => "Implement setattr(self, name, value).", + "_asyncio.Future.__sizeof__" => "Size of object in memory, in bytes.", + "_asyncio.Future.__str__" => "Return str(self).", + "_asyncio.Future.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_asyncio.Future._make_cancelled_error" => "Create the CancelledError to raise if the Future is cancelled.\n\nThis should only be called once when handling a cancellation since\nit erases the context exception value.", + "_asyncio.Future.add_done_callback" => "Add a callback to be run when the future becomes done.\n\nThe callback is called with a single argument - the future object. If\nthe future is already done when this is called, the callback is\nscheduled with call_soon.", + "_asyncio.Future.cancel" => "Cancel the future and schedule callbacks.\n\nIf the future is already done or cancelled, return False. Otherwise,\nchange the future's state to cancelled, schedule the callbacks and\nreturn True.", + "_asyncio.Future.cancelled" => "Return True if the future was cancelled.", + "_asyncio.Future.done" => "Return True if the future is done.\n\nDone means either that a result / exception are available, or that the\nfuture was cancelled.", + "_asyncio.Future.exception" => "Return the exception that was set on this future.\n\nThe exception (or None if no exception was set) is returned only if\nthe future is done. If the future has been cancelled, raises\nCancelledError. If the future isn't done yet, raises\nInvalidStateError.", + "_asyncio.Future.get_loop" => "Return the event loop the Future is bound to.", + "_asyncio.Future.remove_done_callback" => "Remove all instances of a callback from the \"call when done\" list.\n\nReturns the number of callbacks removed.", + "_asyncio.Future.result" => "Return the result this future represents.\n\nIf the future has been cancelled, raises CancelledError. If the\nfuture's result isn't yet available, raises InvalidStateError. If\nthe future is done and has an exception set, this exception is raised.", + "_asyncio.Future.set_exception" => "Mark the future done and set an exception.\n\nIf the future is already done when this method is called, raises\nInvalidStateError.", + "_asyncio.Future.set_result" => "Mark the future done and set its result.\n\nIf the future is already done when this method is called, raises\nInvalidStateError.", + "_asyncio.Task" => "A coroutine wrapped in a Future.", + "_asyncio.Task.__await__" => "Return an iterator to be used in await expression.", + "_asyncio.Task.__class_getitem__" => "See PEP 585", + "_asyncio.Task.__del__" => "Called when the instance is about to be destroyed.", + "_asyncio.Task.__delattr__" => "Implement delattr(self, name).", + "_asyncio.Task.__eq__" => "Return self==value.", + "_asyncio.Task.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_asyncio.Task.__ge__" => "Return self>=value.", + "_asyncio.Task.__getattribute__" => "Return getattr(self, name).", + "_asyncio.Task.__getstate__" => "Helper for pickle.", + "_asyncio.Task.__gt__" => "Return self>value.", + "_asyncio.Task.__hash__" => "Return hash(self).", + "_asyncio.Task.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_asyncio.Task.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_asyncio.Task.__iter__" => "Implement iter(self).", + "_asyncio.Task.__le__" => "Return self<=value.", + "_asyncio.Task.__lt__" => "Return self<value.", + "_asyncio.Task.__ne__" => "Return self!=value.", + "_asyncio.Task.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_asyncio.Task.__reduce__" => "Helper for pickle.", + "_asyncio.Task.__reduce_ex__" => "Helper for pickle.", + "_asyncio.Task.__repr__" => "Return repr(self).", + "_asyncio.Task.__setattr__" => "Implement setattr(self, name, value).", + "_asyncio.Task.__sizeof__" => "Size of object in memory, in bytes.", + "_asyncio.Task.__str__" => "Return str(self).", + "_asyncio.Task.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_asyncio.Task._make_cancelled_error" => "Create the CancelledError to raise if the Task is cancelled.\n\nThis should only be called once when handling a cancellation since\nit erases the context exception value.", + "_asyncio.Task.add_done_callback" => "Add a callback to be run when the future becomes done.\n\nThe callback is called with a single argument - the future object. If\nthe future is already done when this is called, the callback is\nscheduled with call_soon.", + "_asyncio.Task.cancel" => "Request that this task cancel itself.\n\nThis arranges for a CancelledError to be thrown into the\nwrapped coroutine on the next cycle through the event loop.\nThe coroutine then has a chance to clean up or even deny\nthe request using try/except/finally.\n\nUnlike Future.cancel, this does not guarantee that the\ntask will be cancelled: the exception might be caught and\nacted upon, delaying cancellation of the task or preventing\ncancellation completely. The task may also return a value or\nraise a different exception.\n\nImmediately after this method is called, Task.cancelled() will\nnot return True (unless the task was already cancelled). A\ntask will be marked as cancelled when the wrapped coroutine\nterminates with a CancelledError exception (even if cancel()\nwas not called).\n\nThis also increases the task's count of cancellation requests.", + "_asyncio.Task.cancelled" => "Return True if the future was cancelled.", + "_asyncio.Task.cancelling" => "Return the count of the task's cancellation requests.\n\nThis count is incremented when .cancel() is called\nand may be decremented using .uncancel().", + "_asyncio.Task.done" => "Return True if the future is done.\n\nDone means either that a result / exception are available, or that the\nfuture was cancelled.", + "_asyncio.Task.exception" => "Return the exception that was set on this future.\n\nThe exception (or None if no exception was set) is returned only if\nthe future is done. If the future has been cancelled, raises\nCancelledError. If the future isn't done yet, raises\nInvalidStateError.", + "_asyncio.Task.get_loop" => "Return the event loop the Future is bound to.", + "_asyncio.Task.get_stack" => "Return the list of stack frames for this task's coroutine.\n\nIf the coroutine is not done, this returns the stack where it is\nsuspended. If the coroutine has completed successfully or was\ncancelled, this returns an empty list. If the coroutine was\nterminated by an exception, this returns the list of traceback\nframes.\n\nThe frames are always ordered from oldest to newest.\n\nThe optional limit gives the maximum number of frames to\nreturn; by default all available frames are returned. Its\nmeaning differs depending on whether a stack or a traceback is\nreturned: the newest frames of a stack are returned, but the\noldest frames of a traceback are returned. (This matches the\nbehavior of the traceback module.)\n\nFor reasons beyond our control, only one stack frame is\nreturned for a suspended coroutine.", + "_asyncio.Task.print_stack" => "Print the stack or traceback for this task's coroutine.\n\nThis produces output similar to that of the traceback module,\nfor the frames retrieved by get_stack(). The limit argument\nis passed to get_stack(). The file argument is an I/O stream\nto which the output is written; by default output is written\nto sys.stderr.", + "_asyncio.Task.remove_done_callback" => "Remove all instances of a callback from the \"call when done\" list.\n\nReturns the number of callbacks removed.", + "_asyncio.Task.result" => "Return the result this future represents.\n\nIf the future has been cancelled, raises CancelledError. If the\nfuture's result isn't yet available, raises InvalidStateError. If\nthe future is done and has an exception set, this exception is raised.", + "_asyncio.Task.uncancel" => "Decrement the task's count of cancellation requests.\n\nThis should be used by tasks that catch CancelledError\nand wish to continue indefinitely until they are cancelled again.\n\nReturns the remaining number of cancellation requests.", + "_asyncio._enter_task" => "Enter into task execution or resume suspended task.\n\nTask belongs to loop.\n\nReturns None.", + "_asyncio._get_running_loop" => "Return the running event loop or None.\n\nThis is a low-level function intended to be used by event loops.\nThis function is thread-specific.", + "_asyncio._leave_task" => "Leave task execution or suspend a task.\n\nTask belongs to loop.\n\nReturns None.", + "_asyncio._register_eager_task" => "Register a new task in asyncio as executed by loop.\n\nReturns None.", + "_asyncio._register_task" => "Register a new task in asyncio as executed by loop.\n\nReturns None.", + "_asyncio._set_running_loop" => "Set the running event loop.\n\nThis is a low-level function intended to be used by event loops.\nThis function is thread-specific.", + "_asyncio._swap_current_task" => "Temporarily swap in the supplied task and return the original one (or None).\n\nThis is intended for use during eager coroutine execution.", + "_asyncio._unregister_eager_task" => "Unregister a task.\n\nReturns None.", + "_asyncio._unregister_task" => "Unregister a task.\n\nReturns None.", + "_asyncio.current_task" => "Return a currently executed task.", + "_asyncio.get_event_loop" => "Return an asyncio event loop.\n\nWhen called from a coroutine or a callback (e.g. scheduled with\ncall_soon or similar API), this function will always return the\nrunning event loop.\n\nIf there is no running event loop set, the function will return\nthe result of `get_event_loop_policy().get_event_loop()` call.", + "_asyncio.get_running_loop" => "Return the running event loop. Raise a RuntimeError if there is none.\n\nThis function is thread-specific.", + "_bisect" => "Bisection algorithms.\n\nThis module provides support for maintaining a list in sorted order without\nhaving to sort the list after each insertion. For long lists of items with\nexpensive comparison operations, this can be an improvement over the more\ncommon approach.", + "_bisect.bisect_left" => "Return the index where to insert item x in list a, assuming a is sorted.\n\nThe return value i is such that all e in a[:i] have e < x, and all e in\na[i:] have e >= x. So if x already appears in the list, a.insert(i, x) will\ninsert just before the leftmost x already there.\n\nOptional args lo (default 0) and hi (default len(a)) bound the\nslice of a to be searched.\n\nA custom key function can be supplied to customize the sort order.", + "_bisect.bisect_right" => "Return the index where to insert item x in list a, assuming a is sorted.\n\nThe return value i is such that all e in a[:i] have e <= x, and all e in\na[i:] have e > x. So if x already appears in the list, a.insert(i, x) will\ninsert just after the rightmost x already there.\n\nOptional args lo (default 0) and hi (default len(a)) bound the\nslice of a to be searched.\n\nA custom key function can be supplied to customize the sort order.", + "_bisect.insort_left" => "Insert item x in list a, and keep it sorted assuming a is sorted.\n\nIf x is already in a, insert it to the left of the leftmost x.\n\nOptional args lo (default 0) and hi (default len(a)) bound the\nslice of a to be searched.\n\nA custom key function can be supplied to customize the sort order.", + "_bisect.insort_right" => "Insert item x in list a, and keep it sorted assuming a is sorted.\n\nIf x is already in a, insert it to the right of the rightmost x.\n\nOptional args lo (default 0) and hi (default len(a)) bound the\nslice of a to be searched.\n\nA custom key function can be supplied to customize the sort order.", + "_blake2" => "_blake2b provides BLAKE2b for hashlib", + "_blake2.blake2b" => "Return a new BLAKE2b hash object.", + "_blake2.blake2b.__delattr__" => "Implement delattr(self, name).", + "_blake2.blake2b.__eq__" => "Return self==value.", + "_blake2.blake2b.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_blake2.blake2b.__ge__" => "Return self>=value.", + "_blake2.blake2b.__getattribute__" => "Return getattr(self, name).", + "_blake2.blake2b.__getstate__" => "Helper for pickle.", + "_blake2.blake2b.__gt__" => "Return self>value.", + "_blake2.blake2b.__hash__" => "Return hash(self).", + "_blake2.blake2b.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_blake2.blake2b.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_blake2.blake2b.__le__" => "Return self<=value.", + "_blake2.blake2b.__lt__" => "Return self<value.", + "_blake2.blake2b.__ne__" => "Return self!=value.", + "_blake2.blake2b.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_blake2.blake2b.__reduce__" => "Helper for pickle.", + "_blake2.blake2b.__reduce_ex__" => "Helper for pickle.", + "_blake2.blake2b.__repr__" => "Return repr(self).", + "_blake2.blake2b.__setattr__" => "Implement setattr(self, name, value).", + "_blake2.blake2b.__sizeof__" => "Size of object in memory, in bytes.", + "_blake2.blake2b.__str__" => "Return str(self).", + "_blake2.blake2b.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_blake2.blake2b.copy" => "Return a copy of the hash object.", + "_blake2.blake2b.digest" => "Return the digest value as a bytes object.", + "_blake2.blake2b.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_blake2.blake2b.update" => "Update this hash object's state with the provided bytes-like object.", + "_blake2.blake2s" => "Return a new BLAKE2s hash object.", + "_blake2.blake2s.__delattr__" => "Implement delattr(self, name).", + "_blake2.blake2s.__eq__" => "Return self==value.", + "_blake2.blake2s.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_blake2.blake2s.__ge__" => "Return self>=value.", + "_blake2.blake2s.__getattribute__" => "Return getattr(self, name).", + "_blake2.blake2s.__getstate__" => "Helper for pickle.", + "_blake2.blake2s.__gt__" => "Return self>value.", + "_blake2.blake2s.__hash__" => "Return hash(self).", + "_blake2.blake2s.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_blake2.blake2s.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_blake2.blake2s.__le__" => "Return self<=value.", + "_blake2.blake2s.__lt__" => "Return self<value.", + "_blake2.blake2s.__ne__" => "Return self!=value.", + "_blake2.blake2s.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_blake2.blake2s.__reduce__" => "Helper for pickle.", + "_blake2.blake2s.__reduce_ex__" => "Helper for pickle.", + "_blake2.blake2s.__repr__" => "Return repr(self).", + "_blake2.blake2s.__setattr__" => "Implement setattr(self, name, value).", + "_blake2.blake2s.__sizeof__" => "Size of object in memory, in bytes.", + "_blake2.blake2s.__str__" => "Return str(self).", + "_blake2.blake2s.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_blake2.blake2s.copy" => "Return a copy of the hash object.", + "_blake2.blake2s.digest" => "Return the digest value as a bytes object.", + "_blake2.blake2s.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_blake2.blake2s.update" => "Update this hash object's state with the provided bytes-like object.", + "_bz2.BZ2Compressor" => "Create a compressor object for compressing data incrementally.\n\n compresslevel\n Compression level, as a number between 1 and 9.\n\nFor one-shot compression, use the compress() function instead.", + "_bz2.BZ2Compressor.__delattr__" => "Implement delattr(self, name).", + "_bz2.BZ2Compressor.__eq__" => "Return self==value.", + "_bz2.BZ2Compressor.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_bz2.BZ2Compressor.__ge__" => "Return self>=value.", + "_bz2.BZ2Compressor.__getattribute__" => "Return getattr(self, name).", + "_bz2.BZ2Compressor.__getstate__" => "Helper for pickle.", + "_bz2.BZ2Compressor.__gt__" => "Return self>value.", + "_bz2.BZ2Compressor.__hash__" => "Return hash(self).", + "_bz2.BZ2Compressor.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_bz2.BZ2Compressor.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_bz2.BZ2Compressor.__le__" => "Return self<=value.", + "_bz2.BZ2Compressor.__lt__" => "Return self<value.", + "_bz2.BZ2Compressor.__ne__" => "Return self!=value.", + "_bz2.BZ2Compressor.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_bz2.BZ2Compressor.__reduce__" => "Helper for pickle.", + "_bz2.BZ2Compressor.__reduce_ex__" => "Helper for pickle.", + "_bz2.BZ2Compressor.__repr__" => "Return repr(self).", + "_bz2.BZ2Compressor.__setattr__" => "Implement setattr(self, name, value).", + "_bz2.BZ2Compressor.__sizeof__" => "Size of object in memory, in bytes.", + "_bz2.BZ2Compressor.__str__" => "Return str(self).", + "_bz2.BZ2Compressor.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_bz2.BZ2Compressor.compress" => "Provide data to the compressor object.\n\nReturns a chunk of compressed data if possible, or b'' otherwise.\n\nWhen you have finished providing data to the compressor, call the\nflush() method to finish the compression process.", + "_bz2.BZ2Compressor.flush" => "Finish the compression process.\n\nReturns the compressed data left in internal buffers.\n\nThe compressor object may not be used after this method is called.", + "_bz2.BZ2Decompressor" => "Create a decompressor object for decompressing data incrementally.\n\nFor one-shot decompression, use the decompress() function instead.", + "_bz2.BZ2Decompressor.__delattr__" => "Implement delattr(self, name).", + "_bz2.BZ2Decompressor.__eq__" => "Return self==value.", + "_bz2.BZ2Decompressor.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_bz2.BZ2Decompressor.__ge__" => "Return self>=value.", + "_bz2.BZ2Decompressor.__getattribute__" => "Return getattr(self, name).", + "_bz2.BZ2Decompressor.__getstate__" => "Helper for pickle.", + "_bz2.BZ2Decompressor.__gt__" => "Return self>value.", + "_bz2.BZ2Decompressor.__hash__" => "Return hash(self).", + "_bz2.BZ2Decompressor.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_bz2.BZ2Decompressor.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_bz2.BZ2Decompressor.__le__" => "Return self<=value.", + "_bz2.BZ2Decompressor.__lt__" => "Return self<value.", + "_bz2.BZ2Decompressor.__ne__" => "Return self!=value.", + "_bz2.BZ2Decompressor.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_bz2.BZ2Decompressor.__reduce__" => "Helper for pickle.", + "_bz2.BZ2Decompressor.__reduce_ex__" => "Helper for pickle.", + "_bz2.BZ2Decompressor.__repr__" => "Return repr(self).", + "_bz2.BZ2Decompressor.__setattr__" => "Implement setattr(self, name, value).", + "_bz2.BZ2Decompressor.__sizeof__" => "Size of object in memory, in bytes.", + "_bz2.BZ2Decompressor.__str__" => "Return str(self).", + "_bz2.BZ2Decompressor.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_bz2.BZ2Decompressor.decompress" => "Decompress *data*, returning uncompressed data as bytes.\n\nIf *max_length* is nonnegative, returns at most *max_length* bytes of\ndecompressed data. If this limit is reached and further output can be\nproduced, *self.needs_input* will be set to ``False``. In this case, the next\ncall to *decompress()* may provide *data* as b'' to obtain more of the output.\n\nIf all of the input data was decompressed and returned (either because this\nwas less than *max_length* bytes, or because *max_length* was negative),\n*self.needs_input* will be set to True.\n\nAttempting to decompress data after the end of stream is reached raises an\nEOFError. Any data found after the end of the stream is ignored and saved in\nthe unused_data attribute.", + "_bz2.BZ2Decompressor.eof" => "True if the end-of-stream marker has been reached.", + "_bz2.BZ2Decompressor.needs_input" => "True if more input is needed before more decompressed data can be produced.", + "_bz2.BZ2Decompressor.unused_data" => "Data found after the end of the compressed stream.", + "_codecs.decode" => "Decodes obj using the codec registered for encoding.\n\nDefault encoding is 'utf-8'. errors may be given to set a\ndifferent error handling scheme. Default is 'strict' meaning that encoding\nerrors raise a ValueError. Other possible values are 'ignore', 'replace'\nand 'backslashreplace' as well as any other name registered with\ncodecs.register_error that can handle ValueErrors.", + "_codecs.encode" => "Encodes obj using the codec registered for encoding.\n\nThe default encoding is 'utf-8'. errors may be given to set a\ndifferent error handling scheme. Default is 'strict' meaning that encoding\nerrors raise a ValueError. Other possible values are 'ignore', 'replace'\nand 'backslashreplace' as well as any other name registered with\ncodecs.register_error that can handle ValueErrors.", + "_codecs.lookup" => "Looks up a codec tuple in the Python codec registry and returns a CodecInfo object.", + "_codecs.lookup_error" => "lookup_error(errors) -> handler\n\nReturn the error handler for the specified error handling name or raise a\nLookupError, if no handler exists under this name.", + "_codecs.register" => "Register a codec search function.\n\nSearch functions are expected to take one argument, the encoding name in\nall lower case letters, and either return None, or a tuple of functions\n(encoder, decoder, stream_reader, stream_writer) (or a CodecInfo object).", + "_codecs.register_error" => "Register the specified error handler under the name errors.\n\nhandler must be a callable object, that will be called with an exception\ninstance containing information about the location of the encoding/decoding\nerror and must return a (replacement, new position) tuple.", + "_codecs.unregister" => "Unregister a codec search function and clear the registry's cache.\n\nIf the search function is not registered, do nothing.", + "_collections" => "High performance data structures.\n- deque: ordered collection accessible from endpoints only\n- defaultdict: dict subclass with a default value factory", + "_collections._count_elements" => "Count elements in the iterable, updating the mapping", + "_contextvars" => "Context Variables", + "_contextvars.Context.__contains__" => "Return bool(key in self).", + "_contextvars.Context.__delattr__" => "Implement delattr(self, name).", + "_contextvars.Context.__eq__" => "Return self==value.", + "_contextvars.Context.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_contextvars.Context.__ge__" => "Return self>=value.", + "_contextvars.Context.__getattribute__" => "Return getattr(self, name).", + "_contextvars.Context.__getitem__" => "Return self[key].", + "_contextvars.Context.__getstate__" => "Helper for pickle.", + "_contextvars.Context.__gt__" => "Return self>value.", + "_contextvars.Context.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_contextvars.Context.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_contextvars.Context.__iter__" => "Implement iter(self).", + "_contextvars.Context.__le__" => "Return self<=value.", + "_contextvars.Context.__len__" => "Return len(self).", + "_contextvars.Context.__lt__" => "Return self<value.", + "_contextvars.Context.__ne__" => "Return self!=value.", + "_contextvars.Context.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_contextvars.Context.__reduce__" => "Helper for pickle.", + "_contextvars.Context.__reduce_ex__" => "Helper for pickle.", + "_contextvars.Context.__repr__" => "Return repr(self).", + "_contextvars.Context.__setattr__" => "Implement setattr(self, name, value).", + "_contextvars.Context.__sizeof__" => "Size of object in memory, in bytes.", + "_contextvars.Context.__str__" => "Return str(self).", + "_contextvars.Context.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_contextvars.Context.copy" => "Return a shallow copy of the context object.", + "_contextvars.Context.get" => "Return the value for `key` if `key` has the value in the context object.\n\nIf `key` does not exist, return `default`. If `default` is not given,\nreturn None.", + "_contextvars.Context.items" => "Return all variables and their values in the context object.\n\nThe result is returned as a list of 2-tuples (variable, value).", + "_contextvars.Context.keys" => "Return a list of all variables in the context object.", + "_contextvars.Context.values" => "Return a list of all variables' values in the context object.", + "_contextvars.ContextVar.__class_getitem__" => "See PEP 585", + "_contextvars.ContextVar.__delattr__" => "Implement delattr(self, name).", + "_contextvars.ContextVar.__eq__" => "Return self==value.", + "_contextvars.ContextVar.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_contextvars.ContextVar.__ge__" => "Return self>=value.", + "_contextvars.ContextVar.__getattribute__" => "Return getattr(self, name).", + "_contextvars.ContextVar.__getstate__" => "Helper for pickle.", + "_contextvars.ContextVar.__gt__" => "Return self>value.", + "_contextvars.ContextVar.__hash__" => "Return hash(self).", + "_contextvars.ContextVar.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_contextvars.ContextVar.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_contextvars.ContextVar.__le__" => "Return self<=value.", + "_contextvars.ContextVar.__lt__" => "Return self<value.", + "_contextvars.ContextVar.__ne__" => "Return self!=value.", + "_contextvars.ContextVar.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_contextvars.ContextVar.__reduce__" => "Helper for pickle.", + "_contextvars.ContextVar.__reduce_ex__" => "Helper for pickle.", + "_contextvars.ContextVar.__repr__" => "Return repr(self).", + "_contextvars.ContextVar.__setattr__" => "Implement setattr(self, name, value).", + "_contextvars.ContextVar.__sizeof__" => "Size of object in memory, in bytes.", + "_contextvars.ContextVar.__str__" => "Return str(self).", + "_contextvars.ContextVar.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_contextvars.ContextVar.get" => "Return a value for the context variable for the current context.\n\nIf there is no value for the variable in the current context, the method will:\n * return the value of the default argument of the method, if provided; or\n * return the default value for the context variable, if it was created\n with one; or\n * raise a LookupError.", + "_contextvars.ContextVar.reset" => "Reset the context variable.\n\nThe variable is reset to the value it had before the `ContextVar.set()` that\ncreated the token was used.", + "_contextvars.ContextVar.set" => "Call to set a new value for the context variable in the current context.\n\nThe required value argument is the new value for the context variable.\n\nReturns a Token object that can be used to restore the variable to its previous\nvalue via the `ContextVar.reset()` method.", + "_contextvars.Token.__class_getitem__" => "See PEP 585", + "_contextvars.Token.__delattr__" => "Implement delattr(self, name).", + "_contextvars.Token.__eq__" => "Return self==value.", + "_contextvars.Token.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_contextvars.Token.__ge__" => "Return self>=value.", + "_contextvars.Token.__getattribute__" => "Return getattr(self, name).", + "_contextvars.Token.__getstate__" => "Helper for pickle.", + "_contextvars.Token.__gt__" => "Return self>value.", + "_contextvars.Token.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_contextvars.Token.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_contextvars.Token.__le__" => "Return self<=value.", + "_contextvars.Token.__lt__" => "Return self<value.", + "_contextvars.Token.__ne__" => "Return self!=value.", + "_contextvars.Token.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_contextvars.Token.__reduce__" => "Helper for pickle.", + "_contextvars.Token.__reduce_ex__" => "Helper for pickle.", + "_contextvars.Token.__repr__" => "Return repr(self).", + "_contextvars.Token.__setattr__" => "Implement setattr(self, name, value).", + "_contextvars.Token.__sizeof__" => "Size of object in memory, in bytes.", + "_contextvars.Token.__str__" => "Return str(self).", + "_contextvars.Token.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_csv" => "CSV parsing and writing.", + "_csv.Dialect" => "CSV dialect\n\nThe Dialect type records CSV parsing and generation options.", + "_csv.Dialect.__delattr__" => "Implement delattr(self, name).", + "_csv.Dialect.__eq__" => "Return self==value.", + "_csv.Dialect.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_csv.Dialect.__ge__" => "Return self>=value.", + "_csv.Dialect.__getattribute__" => "Return getattr(self, name).", + "_csv.Dialect.__getstate__" => "Helper for pickle.", + "_csv.Dialect.__gt__" => "Return self>value.", + "_csv.Dialect.__hash__" => "Return hash(self).", + "_csv.Dialect.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_csv.Dialect.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_csv.Dialect.__le__" => "Return self<=value.", + "_csv.Dialect.__lt__" => "Return self<value.", + "_csv.Dialect.__ne__" => "Return self!=value.", + "_csv.Dialect.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_csv.Dialect.__reduce__" => "raises an exception to avoid pickling", + "_csv.Dialect.__reduce_ex__" => "raises an exception to avoid pickling", + "_csv.Dialect.__repr__" => "Return repr(self).", + "_csv.Dialect.__setattr__" => "Implement setattr(self, name, value).", + "_csv.Dialect.__sizeof__" => "Size of object in memory, in bytes.", + "_csv.Dialect.__str__" => "Return str(self).", + "_csv.Dialect.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_csv.Error.__cause__" => "exception cause", + "_csv.Error.__context__" => "exception context", + "_csv.Error.__delattr__" => "Implement delattr(self, name).", + "_csv.Error.__eq__" => "Return self==value.", + "_csv.Error.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_csv.Error.__ge__" => "Return self>=value.", + "_csv.Error.__getattribute__" => "Return getattr(self, name).", + "_csv.Error.__getstate__" => "Helper for pickle.", + "_csv.Error.__gt__" => "Return self>value.", + "_csv.Error.__hash__" => "Return hash(self).", + "_csv.Error.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_csv.Error.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_csv.Error.__le__" => "Return self<=value.", + "_csv.Error.__lt__" => "Return self<value.", + "_csv.Error.__ne__" => "Return self!=value.", + "_csv.Error.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_csv.Error.__reduce_ex__" => "Helper for pickle.", + "_csv.Error.__repr__" => "Return repr(self).", + "_csv.Error.__setattr__" => "Implement setattr(self, name, value).", + "_csv.Error.__sizeof__" => "Size of object in memory, in bytes.", + "_csv.Error.__str__" => "Return str(self).", + "_csv.Error.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_csv.Error.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_csv.Error.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_csv.Reader" => "CSV reader\n\nReader objects are responsible for reading and parsing tabular data\nin CSV format.", + "_csv.Reader.__delattr__" => "Implement delattr(self, name).", + "_csv.Reader.__eq__" => "Return self==value.", + "_csv.Reader.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_csv.Reader.__ge__" => "Return self>=value.", + "_csv.Reader.__getattribute__" => "Return getattr(self, name).", + "_csv.Reader.__getstate__" => "Helper for pickle.", + "_csv.Reader.__gt__" => "Return self>value.", + "_csv.Reader.__hash__" => "Return hash(self).", + "_csv.Reader.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_csv.Reader.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_csv.Reader.__iter__" => "Implement iter(self).", + "_csv.Reader.__le__" => "Return self<=value.", + "_csv.Reader.__lt__" => "Return self<value.", + "_csv.Reader.__ne__" => "Return self!=value.", + "_csv.Reader.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_csv.Reader.__next__" => "Implement next(self).", + "_csv.Reader.__reduce__" => "Helper for pickle.", + "_csv.Reader.__reduce_ex__" => "Helper for pickle.", + "_csv.Reader.__repr__" => "Return repr(self).", + "_csv.Reader.__setattr__" => "Implement setattr(self, name, value).", + "_csv.Reader.__sizeof__" => "Size of object in memory, in bytes.", + "_csv.Reader.__str__" => "Return str(self).", + "_csv.Reader.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_csv.Writer" => "CSV writer\n\nWriter objects are responsible for generating tabular data\nin CSV format from sequence input.", + "_csv.Writer.__delattr__" => "Implement delattr(self, name).", + "_csv.Writer.__eq__" => "Return self==value.", + "_csv.Writer.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_csv.Writer.__ge__" => "Return self>=value.", + "_csv.Writer.__getattribute__" => "Return getattr(self, name).", + "_csv.Writer.__getstate__" => "Helper for pickle.", + "_csv.Writer.__gt__" => "Return self>value.", + "_csv.Writer.__hash__" => "Return hash(self).", + "_csv.Writer.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_csv.Writer.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_csv.Writer.__le__" => "Return self<=value.", + "_csv.Writer.__lt__" => "Return self<value.", + "_csv.Writer.__ne__" => "Return self!=value.", + "_csv.Writer.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_csv.Writer.__reduce__" => "Helper for pickle.", + "_csv.Writer.__reduce_ex__" => "Helper for pickle.", + "_csv.Writer.__repr__" => "Return repr(self).", + "_csv.Writer.__setattr__" => "Implement setattr(self, name, value).", + "_csv.Writer.__sizeof__" => "Size of object in memory, in bytes.", + "_csv.Writer.__str__" => "Return str(self).", + "_csv.Writer.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_csv.Writer.writerow" => "Construct and write a CSV record from an iterable of fields.\n\nNon-string elements will be converted to string.", + "_csv.Writer.writerows" => "Construct and write a series of iterables to a csv file.\n\nNon-string elements will be converted to string.", + "_csv.field_size_limit" => "Sets an upper limit on parsed fields.\n\nReturns old limit. If limit is not given, no new limit is set and\nthe old limit is returned", + "_csv.get_dialect" => "Return the dialect instance associated with name.", + "_csv.list_dialects" => "Return a list of all known dialect names.", + "_csv.reader" => "Return a reader object that will process lines from the given iterable.\n\nThe \"iterable\" argument can be any object that returns a line\nof input for each iteration, such as a file object or a list. The\noptional \"dialect\" argument defines a CSV dialect. The function\nalso accepts optional keyword arguments which override settings\nprovided by the dialect.\n\nThe returned object is an iterator. Each iteration returns a row\nof the CSV file (which can span multiple input lines).", + "_csv.register_dialect" => "Create a mapping from a string name to a CVS dialect.\n\nThe optional \"dialect\" argument specifies the base dialect instance\nor the name of the registered dialect. The function also accepts\noptional keyword arguments which override settings provided by the\ndialect.", + "_csv.unregister_dialect" => "Delete the name/dialect mapping associated with a string name.", + "_csv.writer" => "Return a writer object that will write user data on the given file object.\n\nThe \"fileobj\" argument can be any object that supports the file API.\nThe optional \"dialect\" argument defines a CSV dialect. The function\nalso accepts optional keyword arguments which override settings\nprovided by the dialect.", + "_ctypes" => "Create and manipulate C compatible data types in Python.", + "_ctypes.Array" => "Abstract base class for arrays.\n\nThe recommended way to create concrete array types is by multiplying any\nctypes data type with a non-negative integer. Alternatively, you can subclass\nthis type and define _length_ and _type_ class variables. Array elements can\nbe read and written using standard subscript and slice accesses for slice\nreads, the resulting object is not itself an Array.", + "_ctypes.CFuncPtr" => "Function Pointer", + "_ctypes.COMError" => "Raised when a COM method call failed.", + "_ctypes.COMError.__cause__" => "exception cause", + "_ctypes.COMError.__context__" => "exception context", + "_ctypes.COMError.__delattr__" => "Implement delattr(self, name).", + "_ctypes.COMError.__eq__" => "Return self==value.", + "_ctypes.COMError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_ctypes.COMError.__ge__" => "Return self>=value.", + "_ctypes.COMError.__getattribute__" => "Return getattr(self, name).", + "_ctypes.COMError.__getstate__" => "Helper for pickle.", + "_ctypes.COMError.__gt__" => "Return self>value.", + "_ctypes.COMError.__hash__" => "Return hash(self).", + "_ctypes.COMError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_ctypes.COMError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_ctypes.COMError.__le__" => "Return self<=value.", + "_ctypes.COMError.__lt__" => "Return self<value.", + "_ctypes.COMError.__ne__" => "Return self!=value.", + "_ctypes.COMError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_ctypes.COMError.__reduce_ex__" => "Helper for pickle.", + "_ctypes.COMError.__repr__" => "Return repr(self).", + "_ctypes.COMError.__setattr__" => "Implement setattr(self, name, value).", + "_ctypes.COMError.__sizeof__" => "Size of object in memory, in bytes.", + "_ctypes.COMError.__str__" => "Return str(self).", + "_ctypes.COMError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_ctypes.COMError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_ctypes.COMError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_ctypes.CopyComPointer" => "CopyComPointer(src, dst) -> HRESULT value", + "_ctypes.FormatError" => "FormatError([integer]) -> string\n\nConvert a win32 error code into a string. If the error code is not\ngiven, the return value of a call to GetLastError() is used.", + "_ctypes.FreeLibrary" => "FreeLibrary(handle) -> void\n\nFree the handle of an executable previously loaded by LoadLibrary.", + "_ctypes.LoadLibrary" => "LoadLibrary(name, load_flags) -> handle\n\nLoad an executable (usually a DLL), and return a handle to it.\nThe handle may be used to locate exported functions in this\nmodule. load_flags are as defined for LoadLibraryEx in the\nWindows API.", + "_ctypes.POINTER" => "Create and return a new ctypes pointer type.\n\n type\n A ctypes type.\n\nPointer types are cached and reused internally,\nso calling this function repeatedly is cheap.", + "_ctypes.Structure" => "Structure base class", + "_ctypes.Union" => "Union base class", + "_ctypes._Pointer" => "XXX to be provided", + "_ctypes._SimpleCData" => "XXX to be provided", + "_ctypes._dyld_shared_cache_contains_path" => "check if path is in the shared cache", + "_ctypes.addressof" => "addressof(C instance) -> integer\nReturn the address of the C instance internal buffer", + "_ctypes.alignment" => "alignment(C type) -> integer\nalignment(C instance) -> integer\nReturn the alignment requirements of a C instance", + "_ctypes.buffer_info" => "Return buffer interface information", + "_ctypes.byref" => "byref(C instance[, offset=0]) -> byref-object\nReturn a pointer lookalike to a C instance, only usable\nas function argument", + "_ctypes.dlclose" => "dlclose a library", + "_ctypes.dlopen" => "dlopen(name, flag={RTLD_GLOBAL|RTLD_LOCAL}) open a shared library", + "_ctypes.dlsym" => "find symbol in shared library", + "_ctypes.pointer" => "Create a new pointer instance, pointing to 'obj'.\n\nThe returned object is of the type POINTER(type(obj)). Note that if you\njust want to pass a pointer to an object to a foreign function call, you\nshould use byref(obj) which is much faster.", + "_ctypes.resize" => "Resize the memory buffer of a ctypes instance", + "_ctypes.sizeof" => "sizeof(C type) -> integer\nsizeof(C instance) -> integer\nReturn the size in bytes of a C instance", + "_curses.baudrate" => "Return the output speed of the terminal in bits per second.", + "_curses.beep" => "Emit a short attention sound.", + "_curses.can_change_color" => "Return True if the programmer can change the colors displayed by the terminal.", + "_curses.cbreak" => "Enter cbreak mode.\n\n flag\n If false, the effect is the same as calling nocbreak().\n\nIn cbreak mode (sometimes called \"rare\" mode) normal tty line buffering is\nturned off and characters are available to be read one by one. However,\nunlike raw mode, special characters (interrupt, quit, suspend, and flow\ncontrol) retain their effects on the tty driver and calling program.\nCalling first raw() then cbreak() leaves the terminal in cbreak mode.", + "_curses.color_content" => "Return the red, green, and blue (RGB) components of the specified color.\n\n color_number\n The number of the color (0 - (COLORS-1)).\n\nA 3-tuple is returned, containing the R, G, B values for the given color,\nwhich will be between 0 (no component) and 1000 (maximum amount of component).", + "_curses.color_pair" => "Return the attribute value for displaying text in the specified color.\n\n pair_number\n The number of the color pair.\n\nThis attribute value can be combined with A_STANDOUT, A_REVERSE, and the\nother A_* attributes. pair_number() is the counterpart to this function.", + "_curses.curs_set" => "Set the cursor state.\n\n visibility\n 0 for invisible, 1 for normal visible, or 2 for very visible.\n\nIf the terminal supports the visibility requested, the previous cursor\nstate is returned; otherwise, an exception is raised. On many terminals,\nthe \"visible\" mode is an underline cursor and the \"very visible\" mode is\na block cursor.", + "_curses.def_prog_mode" => "Save the current terminal mode as the \"program\" mode.\n\nThe \"program\" mode is the mode when the running program is using curses.\n\nSubsequent calls to reset_prog_mode() will restore this mode.", + "_curses.def_shell_mode" => "Save the current terminal mode as the \"shell\" mode.\n\nThe \"shell\" mode is the mode when the running program is not using curses.\n\nSubsequent calls to reset_shell_mode() will restore this mode.", + "_curses.delay_output" => "Insert a pause in output.\n\nms\n Duration in milliseconds.", + "_curses.doupdate" => "Update the physical screen to match the virtual screen.", + "_curses.echo" => "Enter echo mode.\n\n flag\n If false, the effect is the same as calling noecho().\n\nIn echo mode, each character input is echoed to the screen as it is entered.", + "_curses.endwin" => "De-initialize the library, and return terminal to normal status.", + "_curses.erasechar" => "Return the user's current erase character.", + "_curses.error.__cause__" => "exception cause", + "_curses.error.__context__" => "exception context", + "_curses.error.__delattr__" => "Implement delattr(self, name).", + "_curses.error.__eq__" => "Return self==value.", + "_curses.error.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_curses.error.__ge__" => "Return self>=value.", + "_curses.error.__getattribute__" => "Return getattr(self, name).", + "_curses.error.__getstate__" => "Helper for pickle.", + "_curses.error.__gt__" => "Return self>value.", + "_curses.error.__hash__" => "Return hash(self).", + "_curses.error.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_curses.error.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_curses.error.__le__" => "Return self<=value.", + "_curses.error.__lt__" => "Return self<value.", + "_curses.error.__ne__" => "Return self!=value.", + "_curses.error.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_curses.error.__reduce_ex__" => "Helper for pickle.", + "_curses.error.__repr__" => "Return repr(self).", + "_curses.error.__setattr__" => "Implement setattr(self, name, value).", + "_curses.error.__sizeof__" => "Size of object in memory, in bytes.", + "_curses.error.__str__" => "Return str(self).", + "_curses.error.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_curses.error.__weakref__" => "list of weak references to the object", + "_curses.error.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_curses.error.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_curses.flash" => "Flash the screen.\n\nThat is, change it to reverse-video and then change it back in a short interval.", + "_curses.flushinp" => "Flush all input buffers.\n\nThis throws away any typeahead that has been typed by the user and has not\nyet been processed by the program.", + "_curses.get_escdelay" => "Gets the curses ESCDELAY setting.\n\nGets the number of milliseconds to wait after reading an escape character,\nto distinguish between an individual escape character entered on the\nkeyboard from escape sequences sent by cursor and function keys.", + "_curses.get_tabsize" => "Gets the curses TABSIZE setting.\n\nGets the number of columns used by the curses library when converting a tab\ncharacter to spaces as it adds the tab to a window.", + "_curses.getmouse" => "Retrieve the queued mouse event.\n\nAfter getch() returns KEY_MOUSE to signal a mouse event, this function\nreturns a 5-tuple (id, x, y, z, bstate).", + "_curses.getsyx" => "Return the current coordinates of the virtual screen cursor.\n\nReturn a (y, x) tuple. If leaveok is currently true, return (-1, -1).", + "_curses.getwin" => "Read window related data stored in the file by an earlier putwin() call.\n\nThe routine then creates and initializes a new window using that data,\nreturning the new window object.", + "_curses.halfdelay" => "Enter half-delay mode.\n\n tenths\n Maximal blocking delay in tenths of seconds (1 - 255).\n\nUse nocbreak() to leave half-delay mode.", + "_curses.has_colors" => "Return True if the terminal can display colors; otherwise, return False.", + "_curses.has_extended_color_support" => "Return True if the module supports extended colors; otherwise, return False.\n\nExtended color support allows more than 256 color-pairs for terminals\nthat support more than 16 colors (e.g. xterm-256color).", + "_curses.has_ic" => "Return True if the terminal has insert- and delete-character capabilities.", + "_curses.has_il" => "Return True if the terminal has insert- and delete-line capabilities.", + "_curses.has_key" => "Return True if the current terminal type recognizes a key with that value.\n\nkey\n Key number.", + "_curses.init_color" => "Change the definition of a color.\n\n color_number\n The number of the color to be changed (0 - (COLORS-1)).\n r\n Red component (0 - 1000).\n g\n Green component (0 - 1000).\n b\n Blue component (0 - 1000).\n\nWhen init_color() is used, all occurrences of that color on the screen\nimmediately change to the new definition. This function is a no-op on\nmost terminals; it is active only if can_change_color() returns true.", + "_curses.init_pair" => "Change the definition of a color-pair.\n\n pair_number\n The number of the color-pair to be changed (1 - (COLOR_PAIRS-1)).\n fg\n Foreground color number (-1 - (COLORS-1)).\n bg\n Background color number (-1 - (COLORS-1)).\n\nIf the color-pair was previously initialized, the screen is refreshed and\nall occurrences of that color-pair are changed to the new definition.", + "_curses.initscr" => "Initialize the library.\n\nReturn a WindowObject which represents the whole screen.", + "_curses.is_term_resized" => "Return True if resize_term() would modify the window structure, False otherwise.\n\nnlines\n Height.\nncols\n Width.", + "_curses.isendwin" => "Return True if endwin() has been called.", + "_curses.keyname" => "Return the name of specified key.\n\nkey\n Key number.", + "_curses.killchar" => "Return the user's current line kill character.", + "_curses.longname" => "Return the terminfo long name field describing the current terminal.\n\nThe maximum length of a verbose description is 128 characters. It is defined\nonly after the call to initscr().", + "_curses.meta" => "Enable/disable meta keys.\n\nIf yes is True, allow 8-bit characters to be input. If yes is False,\nallow only 7-bit characters.", + "_curses.mouseinterval" => "Set and retrieve the maximum time between press and release in a click.\n\n interval\n Time in milliseconds.\n\nSet the maximum time that can elapse between press and release events in\norder for them to be recognized as a click, and return the previous interval\nvalue.", + "_curses.mousemask" => "Set the mouse events to be reported, and return a tuple (availmask, oldmask).\n\nReturn a tuple (availmask, oldmask). availmask indicates which of the\nspecified mouse events can be reported; on complete failure it returns 0.\noldmask is the previous value of the given window's mouse event mask.\nIf this function is never called, no mouse events are ever reported.", + "_curses.napms" => "Sleep for specified time.\n\nms\n Duration in milliseconds.", + "_curses.newpad" => "Create and return a pointer to a new pad data structure.\n\nnlines\n Height.\nncols\n Width.", + "_curses.newwin" => "newwin(nlines, ncols, [begin_y=0, begin_x=0])\nReturn a new window.\n\n nlines\n Height.\n ncols\n Width.\n begin_y\n Top side y-coordinate.\n begin_x\n Left side x-coordinate.\n\nBy default, the window will extend from the specified position to the lower\nright corner of the screen.", + "_curses.nl" => "Enter newline mode.\n\n flag\n If false, the effect is the same as calling nonl().\n\nThis mode translates the return key into newline on input, and translates\nnewline into return and line-feed on output. Newline mode is initially on.", + "_curses.nocbreak" => "Leave cbreak mode.\n\nReturn to normal \"cooked\" mode with line buffering.", + "_curses.noecho" => "Leave echo mode.\n\nEchoing of input characters is turned off.", + "_curses.nonl" => "Leave newline mode.\n\nDisable translation of return into newline on input, and disable low-level\ntranslation of newline into newline/return on output.", + "_curses.noqiflush" => "Disable queue flushing.\n\nWhen queue flushing is disabled, normal flush of input and output queues\nassociated with the INTR, QUIT and SUSP characters will not be done.", + "_curses.noraw" => "Leave raw mode.\n\nReturn to normal \"cooked\" mode with line buffering.", + "_curses.pair_content" => "Return a tuple (fg, bg) containing the colors for the requested color pair.\n\npair_number\n The number of the color pair (0 - (COLOR_PAIRS-1)).", + "_curses.pair_number" => "Return the number of the color-pair set by the specified attribute value.\n\ncolor_pair() is the counterpart to this function.", + "_curses.putp" => "Emit the value of a specified terminfo capability for the current terminal.\n\nNote that the output of putp() always goes to standard output.", + "_curses.qiflush" => "Enable queue flushing.\n\n flag\n If false, the effect is the same as calling noqiflush().\n\nIf queue flushing is enabled, all output in the display driver queue\nwill be flushed when the INTR, QUIT and SUSP characters are read.", + "_curses.raw" => "Enter raw mode.\n\n flag\n If false, the effect is the same as calling noraw().\n\nIn raw mode, normal line buffering and processing of interrupt, quit,\nsuspend, and flow control keys are turned off; characters are presented to\ncurses input functions one by one.", + "_curses.reset_prog_mode" => "Restore the terminal to \"program\" mode, as previously saved by def_prog_mode().", + "_curses.reset_shell_mode" => "Restore the terminal to \"shell\" mode, as previously saved by def_shell_mode().", + "_curses.resetty" => "Restore terminal mode.", + "_curses.resize_term" => "Backend function used by resizeterm(), performing most of the work.\n\n nlines\n Height.\n ncols\n Width.\n\nWhen resizing the windows, resize_term() blank-fills the areas that are\nextended. The calling application should fill in these areas with appropriate\ndata. The resize_term() function attempts to resize all windows. However,\ndue to the calling convention of pads, it is not possible to resize these\nwithout additional interaction with the application.", + "_curses.resizeterm" => "Resize the standard and current windows to the specified dimensions.\n\n nlines\n Height.\n ncols\n Width.\n\nAdjusts other bookkeeping data used by the curses library that record the\nwindow dimensions (in particular the SIGWINCH handler).", + "_curses.savetty" => "Save terminal mode.", + "_curses.set_escdelay" => "Sets the curses ESCDELAY setting.\n\n ms\n length of the delay in milliseconds.\n\nSets the number of milliseconds to wait after reading an escape character,\nto distinguish between an individual escape character entered on the\nkeyboard from escape sequences sent by cursor and function keys.", + "_curses.set_tabsize" => "Sets the curses TABSIZE setting.\n\n size\n rendered cell width of a tab character.\n\nSets the number of columns used by the curses library when converting a tab\ncharacter to spaces as it adds the tab to a window.", + "_curses.setsyx" => "Set the virtual screen cursor.\n\n y\n Y-coordinate.\n x\n X-coordinate.\n\nIf y and x are both -1, then leaveok is set.", + "_curses.setupterm" => "Initialize the terminal.\n\nterm\n Terminal name.\n If omitted, the value of the TERM environment variable will be used.\nfd\n File descriptor to which any initialization sequences will be sent.\n If not supplied, the file descriptor for sys.stdout will be used.", + "_curses.start_color" => "Initializes eight basic colors and global variables COLORS and COLOR_PAIRS.\n\nMust be called if the programmer wants to use colors, and before any other\ncolor manipulation routine is called. It is good practice to call this\nroutine right after initscr().\n\nIt also restores the colors on the terminal to the values they had when the\nterminal was just turned on.", + "_curses.termattrs" => "Return a logical OR of all video attributes supported by the terminal.", + "_curses.termname" => "Return the value of the environment variable TERM, truncated to 14 characters.", + "_curses.tigetflag" => "Return the value of the Boolean capability.\n\n capname\n The terminfo capability name.\n\nThe value -1 is returned if capname is not a Boolean capability, or 0 if\nit is canceled or absent from the terminal description.", + "_curses.tigetnum" => "Return the value of the numeric capability.\n\n capname\n The terminfo capability name.\n\nThe value -2 is returned if capname is not a numeric capability, or -1 if\nit is canceled or absent from the terminal description.", + "_curses.tigetstr" => "Return the value of the string capability.\n\n capname\n The terminfo capability name.\n\nNone is returned if capname is not a string capability, or is canceled or\nabsent from the terminal description.", + "_curses.tparm" => "Instantiate the specified byte string with the supplied parameters.\n\nstr\n Parameterized byte string obtained from the terminfo database.", + "_curses.typeahead" => "Specify that the file descriptor fd be used for typeahead checking.\n\n fd\n File descriptor.\n\nIf fd is -1, then no typeahead checking is done.", + "_curses.unctrl" => "Return a string which is a printable representation of the character ch.\n\nControl characters are displayed as a caret followed by the character,\nfor example as ^C. Printing characters are left as they are.", + "_curses.unget_wch" => "Push ch so the next get_wch() will return it.", + "_curses.ungetch" => "Push ch so the next getch() will return it.", + "_curses.ungetmouse" => "Push a KEY_MOUSE event onto the input queue.\n\nThe following getmouse() will return the given state data.", + "_curses.use_default_colors" => "Allow use of default values for colors on terminals supporting this feature.\n\nUse this to support transparency in your application. The default color\nis assigned to the color number -1.", + "_curses.use_env" => "Use environment variables LINES and COLUMNS.\n\nIf used, this function should be called before initscr() or newterm() are\ncalled.\n\nWhen flag is False, the values of lines and columns specified in the terminfo\ndatabase will be used, even if environment variables LINES and COLUMNS (used\nby default) are set, or if curses is running in a window (in which case\ndefault behavior would be to use the window size if LINES and COLUMNS are\nnot set).", + "_curses.window.__delattr__" => "Implement delattr(self, name).", + "_curses.window.__eq__" => "Return self==value.", + "_curses.window.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_curses.window.__ge__" => "Return self>=value.", + "_curses.window.__getattribute__" => "Return getattr(self, name).", + "_curses.window.__getstate__" => "Helper for pickle.", + "_curses.window.__gt__" => "Return self>value.", + "_curses.window.__hash__" => "Return hash(self).", + "_curses.window.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_curses.window.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_curses.window.__le__" => "Return self<=value.", + "_curses.window.__lt__" => "Return self<value.", + "_curses.window.__ne__" => "Return self!=value.", + "_curses.window.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_curses.window.__reduce__" => "Helper for pickle.", + "_curses.window.__reduce_ex__" => "Helper for pickle.", + "_curses.window.__repr__" => "Return repr(self).", + "_curses.window.__setattr__" => "Implement setattr(self, name, value).", + "_curses.window.__sizeof__" => "Size of object in memory, in bytes.", + "_curses.window.__str__" => "Return str(self).", + "_curses.window.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_curses.window.addch" => "addch([y, x,] ch, [attr=_curses.A_NORMAL])\nPaint the character.\n\n y\n Y-coordinate.\n x\n X-coordinate.\n ch\n Character to add.\n attr\n Attributes for the character.\n\nPaint character ch at (y, x) with attributes attr,\noverwriting any character previously painted at that location.\nBy default, the character position and attributes are the\ncurrent settings for the window object.", + "_curses.window.addnstr" => "addnstr([y, x,] str, n, [attr])\nPaint at most n characters of the string.\n\n y\n Y-coordinate.\n x\n X-coordinate.\n str\n String to add.\n n\n Maximal number of characters.\n attr\n Attributes for characters.\n\nPaint at most n characters of the string str at (y, x) with\nattributes attr, overwriting anything previously on the display.\nBy default, the character position and attributes are the\ncurrent settings for the window object.", + "_curses.window.addstr" => "addstr([y, x,] str, [attr])\nPaint the string.\n\n y\n Y-coordinate.\n x\n X-coordinate.\n str\n String to add.\n attr\n Attributes for characters.\n\nPaint the string str at (y, x) with attributes attr,\noverwriting anything previously on the display.\nBy default, the character position and attributes are the\ncurrent settings for the window object.", + "_curses.window.attroff" => "Remove attribute attr from the \"background\" set.", + "_curses.window.attron" => "Add attribute attr from the \"background\" set.", + "_curses.window.attrset" => "Set the \"background\" set of attributes.", + "_curses.window.bkgd" => "Set the background property of the window.\n\nch\n Background character.\nattr\n Background attributes.", + "_curses.window.bkgdset" => "Set the window's background.\n\nch\n Background character.\nattr\n Background attributes.", + "_curses.window.border" => "Draw a border around the edges of the window.\n\n ls\n Left side.\n rs\n Right side.\n ts\n Top side.\n bs\n Bottom side.\n tl\n Upper-left corner.\n tr\n Upper-right corner.\n bl\n Bottom-left corner.\n br\n Bottom-right corner.\n\nEach parameter specifies the character to use for a specific part of the\nborder. The characters can be specified as integers or as one-character\nstrings. A 0 value for any parameter will cause the default character to be\nused for that parameter.", + "_curses.window.box" => "box([verch=0, horch=0])\nDraw a border around the edges of the window.\n\n verch\n Left and right side.\n horch\n Top and bottom side.\n\nSimilar to border(), but both ls and rs are verch and both ts and bs are\nhorch. The default corner characters are always used by this function.", + "_curses.window.delch" => "delch([y, x])\nDelete any character at (y, x).\n\n y\n Y-coordinate.\n x\n X-coordinate.", + "_curses.window.derwin" => "derwin([nlines=0, ncols=0,] begin_y, begin_x)\nCreate a sub-window (window-relative coordinates).\n\n nlines\n Height.\n ncols\n Width.\n begin_y\n Top side y-coordinate.\n begin_x\n Left side x-coordinate.\n\nderwin() is the same as calling subwin(), except that begin_y and begin_x\nare relative to the origin of the window, rather than relative to the entire\nscreen.", + "_curses.window.echochar" => "Add character ch with attribute attr, and refresh.\n\nch\n Character to add.\nattr\n Attributes for the character.", + "_curses.window.enclose" => "Return True if the screen-relative coordinates are enclosed by the window.\n\ny\n Y-coordinate.\nx\n X-coordinate.", + "_curses.window.encoding" => "the typecode character used to create the array", + "_curses.window.get_wch" => "get_wch([y, x])\nGet a wide character from terminal keyboard.\n\n y\n Y-coordinate.\n x\n X-coordinate.\n\nReturn a character for most keys, or an integer for function keys,\nkeypad keys, and other special keys.", + "_curses.window.getbkgd" => "Return the window's current background character/attribute pair.", + "_curses.window.getch" => "getch([y, x])\nGet a character code from terminal keyboard.\n\n y\n Y-coordinate.\n x\n X-coordinate.\n\nThe integer returned does not have to be in ASCII range: function keys,\nkeypad keys and so on return numbers higher than 256. In no-delay mode, -1\nis returned if there is no input, else getch() waits until a key is pressed.", + "_curses.window.getkey" => "getkey([y, x])\nGet a character (string) from terminal keyboard.\n\n y\n Y-coordinate.\n x\n X-coordinate.\n\nReturning a string instead of an integer, as getch() does. Function keys,\nkeypad keys and other special keys return a multibyte string containing the\nkey name. In no-delay mode, an exception is raised if there is no input.", + "_curses.window.hline" => "hline([y, x,] ch, n, [attr=_curses.A_NORMAL])\nDisplay a horizontal line.\n\n y\n Starting Y-coordinate.\n x\n Starting X-coordinate.\n ch\n Character to draw.\n n\n Line length.\n attr\n Attributes for the characters.", + "_curses.window.inch" => "inch([y, x])\nReturn the character at the given position in the window.\n\n y\n Y-coordinate.\n x\n X-coordinate.\n\nThe bottom 8 bits are the character proper, and upper bits are the attributes.", + "_curses.window.insch" => "insch([y, x,] ch, [attr=_curses.A_NORMAL])\nInsert a character before the current or specified position.\n\n y\n Y-coordinate.\n x\n X-coordinate.\n ch\n Character to insert.\n attr\n Attributes for the character.\n\nAll characters to the right of the cursor are shifted one position right, with\nthe rightmost characters on the line being lost.", + "_curses.window.insnstr" => "insnstr([y, x,] str, n, [attr])\nInsert at most n characters of the string.\n\n y\n Y-coordinate.\n x\n X-coordinate.\n str\n String to insert.\n n\n Maximal number of characters.\n attr\n Attributes for characters.\n\nInsert a character string (as many characters as will fit on the line)\nbefore the character under the cursor, up to n characters. If n is zero\nor negative, the entire string is inserted. All characters to the right\nof the cursor are shifted right, with the rightmost characters on the line\nbeing lost. The cursor position does not change (after moving to y, x, if\nspecified).", + "_curses.window.insstr" => "insstr([y, x,] str, [attr])\nInsert the string before the current or specified position.\n\n y\n Y-coordinate.\n x\n X-coordinate.\n str\n String to insert.\n attr\n Attributes for characters.\n\nInsert a character string (as many characters as will fit on the line)\nbefore the character under the cursor. All characters to the right of\nthe cursor are shifted right, with the rightmost characters on the line\nbeing lost. The cursor position does not change (after moving to y, x,\nif specified).", + "_curses.window.is_linetouched" => "Return True if the specified line was modified, otherwise return False.\n\n line\n Line number.\n\nRaise a curses.error exception if line is not valid for the given window.", + "_curses.window.noutrefresh" => "noutrefresh([pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol])\nMark for refresh but wait.\n\nThis function updates the data structure representing the desired state of the\nwindow, but does not force an update of the physical screen. To accomplish\nthat, call doupdate().", + "_curses.window.overlay" => "overlay(destwin, [sminrow, smincol, dminrow, dmincol, dmaxrow, dmaxcol])\nOverlay the window on top of destwin.\n\nThe windows need not be the same size, only the overlapping region is copied.\nThis copy is non-destructive, which means that the current background\ncharacter does not overwrite the old contents of destwin.\n\nTo get fine-grained control over the copied region, the second form of\noverlay() can be used. sminrow and smincol are the upper-left coordinates\nof the source window, and the other variables mark a rectangle in the\ndestination window.", + "_curses.window.overwrite" => "overwrite(destwin, [sminrow, smincol, dminrow, dmincol, dmaxrow,\n dmaxcol])\nOverwrite the window on top of destwin.\n\nThe windows need not be the same size, in which case only the overlapping\nregion is copied. This copy is destructive, which means that the current\nbackground character overwrites the old contents of destwin.\n\nTo get fine-grained control over the copied region, the second form of\noverwrite() can be used. sminrow and smincol are the upper-left coordinates\nof the source window, the other variables mark a rectangle in the destination\nwindow.", + "_curses.window.putwin" => "Write all data associated with the window into the provided file object.\n\nThis information can be later retrieved using the getwin() function.", + "_curses.window.redrawln" => "Mark the specified lines corrupted.\n\n beg\n Starting line number.\n num\n The number of lines.\n\nThey should be completely redrawn on the next refresh() call.", + "_curses.window.refresh" => "refresh([pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol])\nUpdate the display immediately.\n\nSynchronize actual screen with previous drawing/deleting methods.\nThe 6 optional arguments can only be specified when the window is a pad\ncreated with newpad(). The additional parameters are needed to indicate\nwhat part of the pad and screen are involved. pminrow and pmincol specify\nthe upper left-hand corner of the rectangle to be displayed in the pad.\nsminrow, smincol, smaxrow, and smaxcol specify the edges of the rectangle to\nbe displayed on the screen. The lower right-hand corner of the rectangle to\nbe displayed in the pad is calculated from the screen coordinates, since the\nrectangles must be the same size. Both rectangles must be entirely contained\nwithin their respective structures. Negative values of pminrow, pmincol,\nsminrow, or smincol are treated as if they were zero.", + "_curses.window.scroll" => "scroll([lines=1])\nScroll the screen or scrolling region.\n\n lines\n Number of lines to scroll.\n\nScroll upward if the argument is positive and downward if it is negative.", + "_curses.window.setscrreg" => "Define a software scrolling region.\n\n top\n First line number.\n bottom\n Last line number.\n\nAll scrolling actions will take place in this region.", + "_curses.window.subpad" => "subwin([nlines=0, ncols=0,] begin_y, begin_x)\nCreate a sub-window (screen-relative coordinates).\n\n nlines\n Height.\n ncols\n Width.\n begin_y\n Top side y-coordinate.\n begin_x\n Left side x-coordinate.\n\nBy default, the sub-window will extend from the specified position to the\nlower right corner of the window.", + "_curses.window.subwin" => "subwin([nlines=0, ncols=0,] begin_y, begin_x)\nCreate a sub-window (screen-relative coordinates).\n\n nlines\n Height.\n ncols\n Width.\n begin_y\n Top side y-coordinate.\n begin_x\n Left side x-coordinate.\n\nBy default, the sub-window will extend from the specified position to the\nlower right corner of the window.", + "_curses.window.touchline" => "touchline(start, count, [changed=True])\nPretend count lines have been changed, starting with line start.\n\nIf changed is supplied, it specifies whether the affected lines are marked\nas having been changed (changed=True) or unchanged (changed=False).", + "_curses.window.vline" => "vline([y, x,] ch, n, [attr=_curses.A_NORMAL])\nDisplay a vertical line.\n\n y\n Starting Y-coordinate.\n x\n Starting X-coordinate.\n ch\n Character to draw.\n n\n Line length.\n attr\n Attributes for the character.", + "_curses_panel.bottom_panel" => "Return the bottom panel in the panel stack.", + "_curses_panel.error.__cause__" => "exception cause", + "_curses_panel.error.__context__" => "exception context", + "_curses_panel.error.__delattr__" => "Implement delattr(self, name).", + "_curses_panel.error.__eq__" => "Return self==value.", + "_curses_panel.error.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_curses_panel.error.__ge__" => "Return self>=value.", + "_curses_panel.error.__getattribute__" => "Return getattr(self, name).", + "_curses_panel.error.__getstate__" => "Helper for pickle.", + "_curses_panel.error.__gt__" => "Return self>value.", + "_curses_panel.error.__hash__" => "Return hash(self).", + "_curses_panel.error.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_curses_panel.error.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_curses_panel.error.__le__" => "Return self<=value.", + "_curses_panel.error.__lt__" => "Return self<value.", + "_curses_panel.error.__ne__" => "Return self!=value.", + "_curses_panel.error.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_curses_panel.error.__reduce_ex__" => "Helper for pickle.", + "_curses_panel.error.__repr__" => "Return repr(self).", + "_curses_panel.error.__setattr__" => "Implement setattr(self, name, value).", + "_curses_panel.error.__sizeof__" => "Size of object in memory, in bytes.", + "_curses_panel.error.__str__" => "Return str(self).", + "_curses_panel.error.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_curses_panel.error.__weakref__" => "list of weak references to the object", + "_curses_panel.error.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_curses_panel.error.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_curses_panel.new_panel" => "Return a panel object, associating it with the given window win.", + "_curses_panel.panel.__delattr__" => "Implement delattr(self, name).", + "_curses_panel.panel.__eq__" => "Return self==value.", + "_curses_panel.panel.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_curses_panel.panel.__ge__" => "Return self>=value.", + "_curses_panel.panel.__getattribute__" => "Return getattr(self, name).", + "_curses_panel.panel.__getstate__" => "Helper for pickle.", + "_curses_panel.panel.__gt__" => "Return self>value.", + "_curses_panel.panel.__hash__" => "Return hash(self).", + "_curses_panel.panel.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_curses_panel.panel.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_curses_panel.panel.__le__" => "Return self<=value.", + "_curses_panel.panel.__lt__" => "Return self<value.", + "_curses_panel.panel.__ne__" => "Return self!=value.", + "_curses_panel.panel.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_curses_panel.panel.__reduce__" => "Helper for pickle.", + "_curses_panel.panel.__reduce_ex__" => "Helper for pickle.", + "_curses_panel.panel.__repr__" => "Return repr(self).", + "_curses_panel.panel.__setattr__" => "Implement setattr(self, name, value).", + "_curses_panel.panel.__sizeof__" => "Size of object in memory, in bytes.", + "_curses_panel.panel.__str__" => "Return str(self).", + "_curses_panel.panel.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_curses_panel.panel.above" => "Return the panel above the current panel.", + "_curses_panel.panel.below" => "Return the panel below the current panel.", + "_curses_panel.panel.bottom" => "Push the panel to the bottom of the stack.", + "_curses_panel.panel.hidden" => "Return True if the panel is hidden (not visible), False otherwise.", + "_curses_panel.panel.hide" => "Hide the panel.\n\nThis does not delete the object, it just makes the window on screen invisible.", + "_curses_panel.panel.move" => "Move the panel to the screen coordinates (y, x).", + "_curses_panel.panel.replace" => "Change the window associated with the panel to the window win.", + "_curses_panel.panel.set_userptr" => "Set the panel's user pointer to obj.", + "_curses_panel.panel.show" => "Display the panel (which might have been hidden).", + "_curses_panel.panel.top" => "Push panel to the top of the stack.", + "_curses_panel.panel.userptr" => "Return the user pointer for the panel.", + "_curses_panel.panel.window" => "Return the window object associated with the panel.", + "_curses_panel.top_panel" => "Return the top panel in the panel stack.", + "_curses_panel.update_panels" => "Updates the virtual screen after changes in the panel stack.\n\nThis does not call curses.doupdate(), so you'll have to do this yourself.", + "_datetime" => "Fast implementation of the datetime type.", + "_dbm.error.__cause__" => "exception cause", + "_dbm.error.__context__" => "exception context", + "_dbm.error.__delattr__" => "Implement delattr(self, name).", + "_dbm.error.__eq__" => "Return self==value.", + "_dbm.error.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_dbm.error.__ge__" => "Return self>=value.", + "_dbm.error.__getattribute__" => "Return getattr(self, name).", + "_dbm.error.__getstate__" => "Helper for pickle.", + "_dbm.error.__gt__" => "Return self>value.", + "_dbm.error.__hash__" => "Return hash(self).", + "_dbm.error.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_dbm.error.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_dbm.error.__le__" => "Return self<=value.", + "_dbm.error.__lt__" => "Return self<value.", + "_dbm.error.__ne__" => "Return self!=value.", + "_dbm.error.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_dbm.error.__reduce_ex__" => "Helper for pickle.", + "_dbm.error.__repr__" => "Return repr(self).", + "_dbm.error.__setattr__" => "Implement setattr(self, name, value).", + "_dbm.error.__sizeof__" => "Size of object in memory, in bytes.", + "_dbm.error.__str__" => "Return str(self).", + "_dbm.error.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_dbm.error.__weakref__" => "list of weak references to the object", + "_dbm.error.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_dbm.error.errno" => "POSIX exception code", + "_dbm.error.filename" => "exception filename", + "_dbm.error.filename2" => "second exception filename", + "_dbm.error.strerror" => "exception strerror", + "_dbm.error.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_dbm.open" => "Return a database object.\n\nfilename\n The filename to open.\nflags\n How to open the file. \"r\" for reading, \"w\" for writing, etc.\nmode\n If creating a new file, the mode bits for the new file\n (e.g. os.O_RDWR).", + "_decimal" => "C decimal arithmetic module", + "_decimal.getcontext" => "Get the current default context.", + "_decimal.localcontext" => "Return a context manager that will set the default context to a copy of ctx\non entry to the with-statement and restore the previous default context when\nexiting the with-statement. If no context is specified, a copy of the current\ndefault context is used.", + "_decimal.setcontext" => "Set a new default context.", + "_elementtree._set_factories" => "Change the factories used to create comments and processing instructions.\n\nFor internal use only.", + "_functools" => "Tools that operate on functions.", + "_functools.cmp_to_key" => "Convert a cmp= function into a key= function.\n\nmycmp\n Function that compares two objects.", + "_functools.reduce" => "reduce(function, iterable[, initial], /) -> value\n\nApply a function of two arguments cumulatively to the items of an iterable, from left to right.\n\nThis effectively reduces the iterable to a single value. If initial is present,\nit is placed before the items of the iterable in the calculation, and serves as\na default when the iterable is empty.\n\nFor example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])\ncalculates ((((1 + 2) + 3) + 4) + 5).", + "_gdbm" => "This module provides an interface to the GNU DBM (GDBM) library.\n\nThis module is quite similar to the dbm module, but uses GDBM instead to\nprovide some additional functionality. Please note that the file formats\ncreated by GDBM and dbm are incompatible.\n\nGDBM objects behave like mappings (dictionaries), except that keys and\nvalues are always immutable bytes-like objects or strings. Printing\na GDBM object doesn't print the keys and values, and the items() and\nvalues() methods are not supported.", + "_gdbm.error.__cause__" => "exception cause", + "_gdbm.error.__context__" => "exception context", + "_gdbm.error.__delattr__" => "Implement delattr(self, name).", + "_gdbm.error.__eq__" => "Return self==value.", + "_gdbm.error.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_gdbm.error.__ge__" => "Return self>=value.", + "_gdbm.error.__getattribute__" => "Return getattr(self, name).", + "_gdbm.error.__getstate__" => "Helper for pickle.", + "_gdbm.error.__gt__" => "Return self>value.", + "_gdbm.error.__hash__" => "Return hash(self).", + "_gdbm.error.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_gdbm.error.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_gdbm.error.__le__" => "Return self<=value.", + "_gdbm.error.__lt__" => "Return self<value.", + "_gdbm.error.__ne__" => "Return self!=value.", + "_gdbm.error.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_gdbm.error.__reduce_ex__" => "Helper for pickle.", + "_gdbm.error.__repr__" => "Return repr(self).", + "_gdbm.error.__setattr__" => "Implement setattr(self, name, value).", + "_gdbm.error.__sizeof__" => "Size of object in memory, in bytes.", + "_gdbm.error.__str__" => "Return str(self).", + "_gdbm.error.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_gdbm.error.__weakref__" => "list of weak references to the object", + "_gdbm.error.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_gdbm.error.errno" => "POSIX exception code", + "_gdbm.error.filename" => "exception filename", + "_gdbm.error.filename2" => "second exception filename", + "_gdbm.error.strerror" => "exception strerror", + "_gdbm.error.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_gdbm.open" => "Open a dbm database and return a dbm object.\n\nThe filename argument is the name of the database file.\n\nThe optional flags argument can be 'r' (to open an existing database\nfor reading only -- default), 'w' (to open an existing database for\nreading and writing), 'c' (which creates the database if it doesn't\nexist), or 'n' (which always creates a new empty database).\n\nSome versions of gdbm support additional flags which must be\nappended to one of the flags described above. The module constant\n'open_flags' is a string of valid additional flags. The 'f' flag\nopens the database in fast mode; altered data will not automatically\nbe written to the disk after every change. This results in faster\nwrites to the database, but may result in an inconsistent database\nif the program crashes while the database is still open. Use the\nsync() method to force any unwritten data to be written to the disk.\nThe 's' flag causes all database operations to be synchronized to\ndisk. The 'u' flag disables locking of the database file.\n\nThe optional mode argument is the Unix mode of the file, used only\nwhen the database has to be created. It defaults to octal 0o666.", + "_hashlib" => "OpenSSL interface for hashlib module", + "_hashlib.HASH" => "A hash is an object used to calculate a checksum of a string of information.\n\nMethods:\n\nupdate() -- updates the current digest with an additional string\ndigest() -- return the current digest value\nhexdigest() -- return the current digest as a string of hexadecimal digits\ncopy() -- return a copy of the current hash object\n\nAttributes:\n\nname -- the hash algorithm being used by this object\ndigest_size -- number of bytes in this hashes output", + "_hashlib.HASH.__delattr__" => "Implement delattr(self, name).", + "_hashlib.HASH.__eq__" => "Return self==value.", + "_hashlib.HASH.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_hashlib.HASH.__ge__" => "Return self>=value.", + "_hashlib.HASH.__getattribute__" => "Return getattr(self, name).", + "_hashlib.HASH.__getstate__" => "Helper for pickle.", + "_hashlib.HASH.__gt__" => "Return self>value.", + "_hashlib.HASH.__hash__" => "Return hash(self).", + "_hashlib.HASH.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_hashlib.HASH.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_hashlib.HASH.__le__" => "Return self<=value.", + "_hashlib.HASH.__lt__" => "Return self<value.", + "_hashlib.HASH.__ne__" => "Return self!=value.", + "_hashlib.HASH.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_hashlib.HASH.__reduce__" => "Helper for pickle.", + "_hashlib.HASH.__reduce_ex__" => "Helper for pickle.", + "_hashlib.HASH.__repr__" => "Return repr(self).", + "_hashlib.HASH.__setattr__" => "Implement setattr(self, name, value).", + "_hashlib.HASH.__sizeof__" => "Size of object in memory, in bytes.", + "_hashlib.HASH.__str__" => "Return str(self).", + "_hashlib.HASH.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_hashlib.HASH.copy" => "Return a copy of the hash object.", + "_hashlib.HASH.digest" => "Return the digest value as a bytes object.", + "_hashlib.HASH.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_hashlib.HASH.update" => "Update this hash object's state with the provided string.", + "_hashlib.HASHXOF" => "A hash is an object used to calculate a checksum of a string of information.\n\nMethods:\n\nupdate() -- updates the current digest with an additional string\ndigest(length) -- return the current digest value\nhexdigest(length) -- return the current digest as a string of hexadecimal digits\ncopy() -- return a copy of the current hash object\n\nAttributes:\n\nname -- the hash algorithm being used by this object\ndigest_size -- number of bytes in this hashes output", + "_hashlib.HASHXOF.__delattr__" => "Implement delattr(self, name).", + "_hashlib.HASHXOF.__eq__" => "Return self==value.", + "_hashlib.HASHXOF.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_hashlib.HASHXOF.__ge__" => "Return self>=value.", + "_hashlib.HASHXOF.__getattribute__" => "Return getattr(self, name).", + "_hashlib.HASHXOF.__getstate__" => "Helper for pickle.", + "_hashlib.HASHXOF.__gt__" => "Return self>value.", + "_hashlib.HASHXOF.__hash__" => "Return hash(self).", + "_hashlib.HASHXOF.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_hashlib.HASHXOF.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_hashlib.HASHXOF.__le__" => "Return self<=value.", + "_hashlib.HASHXOF.__lt__" => "Return self<value.", + "_hashlib.HASHXOF.__ne__" => "Return self!=value.", + "_hashlib.HASHXOF.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_hashlib.HASHXOF.__reduce__" => "Helper for pickle.", + "_hashlib.HASHXOF.__reduce_ex__" => "Helper for pickle.", + "_hashlib.HASHXOF.__repr__" => "Return repr(self).", + "_hashlib.HASHXOF.__setattr__" => "Implement setattr(self, name, value).", + "_hashlib.HASHXOF.__sizeof__" => "Size of object in memory, in bytes.", + "_hashlib.HASHXOF.__str__" => "Return str(self).", + "_hashlib.HASHXOF.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_hashlib.HASHXOF.copy" => "Return a copy of the hash object.", + "_hashlib.HASHXOF.digest" => "Return the digest value as a bytes object.", + "_hashlib.HASHXOF.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_hashlib.HASHXOF.update" => "Update this hash object's state with the provided string.", + "_hashlib.HMAC" => "The object used to calculate HMAC of a message.\n\nMethods:\n\nupdate() -- updates the current digest with an additional string\ndigest() -- return the current digest value\nhexdigest() -- return the current digest as a string of hexadecimal digits\ncopy() -- return a copy of the current hash object\n\nAttributes:\n\nname -- the name, including the hash algorithm used by this object\ndigest_size -- number of bytes in digest() output", + "_hashlib.HMAC.__delattr__" => "Implement delattr(self, name).", + "_hashlib.HMAC.__eq__" => "Return self==value.", + "_hashlib.HMAC.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_hashlib.HMAC.__ge__" => "Return self>=value.", + "_hashlib.HMAC.__getattribute__" => "Return getattr(self, name).", + "_hashlib.HMAC.__getstate__" => "Helper for pickle.", + "_hashlib.HMAC.__gt__" => "Return self>value.", + "_hashlib.HMAC.__hash__" => "Return hash(self).", + "_hashlib.HMAC.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_hashlib.HMAC.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_hashlib.HMAC.__le__" => "Return self<=value.", + "_hashlib.HMAC.__lt__" => "Return self<value.", + "_hashlib.HMAC.__ne__" => "Return self!=value.", + "_hashlib.HMAC.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_hashlib.HMAC.__reduce__" => "Helper for pickle.", + "_hashlib.HMAC.__reduce_ex__" => "Helper for pickle.", + "_hashlib.HMAC.__repr__" => "Return repr(self).", + "_hashlib.HMAC.__setattr__" => "Implement setattr(self, name, value).", + "_hashlib.HMAC.__sizeof__" => "Size of object in memory, in bytes.", + "_hashlib.HMAC.__str__" => "Return str(self).", + "_hashlib.HMAC.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_hashlib.HMAC.copy" => "Return a copy (\"clone\") of the HMAC object.", + "_hashlib.HMAC.digest" => "Return the digest of the bytes passed to the update() method so far.", + "_hashlib.HMAC.hexdigest" => "Return hexadecimal digest of the bytes passed to the update() method so far.\n\nThis may be used to exchange the value safely in email or other non-binary\nenvironments.", + "_hashlib.HMAC.update" => "Update the HMAC object with msg.", + "_hashlib.UnsupportedDigestmodError.__cause__" => "exception cause", + "_hashlib.UnsupportedDigestmodError.__context__" => "exception context", + "_hashlib.UnsupportedDigestmodError.__delattr__" => "Implement delattr(self, name).", + "_hashlib.UnsupportedDigestmodError.__eq__" => "Return self==value.", + "_hashlib.UnsupportedDigestmodError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_hashlib.UnsupportedDigestmodError.__ge__" => "Return self>=value.", + "_hashlib.UnsupportedDigestmodError.__getattribute__" => "Return getattr(self, name).", + "_hashlib.UnsupportedDigestmodError.__getstate__" => "Helper for pickle.", + "_hashlib.UnsupportedDigestmodError.__gt__" => "Return self>value.", + "_hashlib.UnsupportedDigestmodError.__hash__" => "Return hash(self).", + "_hashlib.UnsupportedDigestmodError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_hashlib.UnsupportedDigestmodError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_hashlib.UnsupportedDigestmodError.__le__" => "Return self<=value.", + "_hashlib.UnsupportedDigestmodError.__lt__" => "Return self<value.", + "_hashlib.UnsupportedDigestmodError.__ne__" => "Return self!=value.", + "_hashlib.UnsupportedDigestmodError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_hashlib.UnsupportedDigestmodError.__reduce_ex__" => "Helper for pickle.", + "_hashlib.UnsupportedDigestmodError.__repr__" => "Return repr(self).", + "_hashlib.UnsupportedDigestmodError.__setattr__" => "Implement setattr(self, name, value).", + "_hashlib.UnsupportedDigestmodError.__sizeof__" => "Size of object in memory, in bytes.", + "_hashlib.UnsupportedDigestmodError.__str__" => "Return str(self).", + "_hashlib.UnsupportedDigestmodError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_hashlib.UnsupportedDigestmodError.__weakref__" => "list of weak references to the object", + "_hashlib.UnsupportedDigestmodError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_hashlib.UnsupportedDigestmodError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_hashlib.compare_digest" => "Return 'a == b'.\n\nThis function uses an approach designed to prevent\ntiming analysis, making it appropriate for cryptography.\n\na and b must both be of the same type: either str (ASCII only),\nor any bytes-like object.\n\nNote: If a and b are of different lengths, or if an error occurs,\na timing attack could theoretically reveal information about the\ntypes and lengths of a and b--but not their values.", + "_hashlib.get_fips_mode" => "Determine the OpenSSL FIPS mode of operation.\n\nFor OpenSSL 3.0.0 and newer it returns the state of the default provider\nin the default OSSL context. It's not quite the same as FIPS_mode() but good\nenough for unittests.\n\nEffectively any non-zero return value indicates FIPS mode;\nvalues other than 1 may have additional significance.", + "_hashlib.hmac_digest" => "Single-shot HMAC.", + "_hashlib.hmac_new" => "Return a new hmac object.", + "_hashlib.new" => "Return a new hash object using the named algorithm.\n\nAn optional string argument may be provided and will be\nautomatically hashed.\n\nThe MD5 and SHA1 algorithms are always supported.", + "_hashlib.openssl_md5" => "Returns a md5 hash object; optionally initialized with a string", + "_hashlib.openssl_sha1" => "Returns a sha1 hash object; optionally initialized with a string", + "_hashlib.openssl_sha224" => "Returns a sha224 hash object; optionally initialized with a string", + "_hashlib.openssl_sha256" => "Returns a sha256 hash object; optionally initialized with a string", + "_hashlib.openssl_sha384" => "Returns a sha384 hash object; optionally initialized with a string", + "_hashlib.openssl_sha3_224" => "Returns a sha3-224 hash object; optionally initialized with a string", + "_hashlib.openssl_sha3_256" => "Returns a sha3-256 hash object; optionally initialized with a string", + "_hashlib.openssl_sha3_384" => "Returns a sha3-384 hash object; optionally initialized with a string", + "_hashlib.openssl_sha3_512" => "Returns a sha3-512 hash object; optionally initialized with a string", + "_hashlib.openssl_sha512" => "Returns a sha512 hash object; optionally initialized with a string", + "_hashlib.openssl_shake_128" => "Returns a shake-128 variable hash object; optionally initialized with a string", + "_hashlib.openssl_shake_256" => "Returns a shake-256 variable hash object; optionally initialized with a string", + "_hashlib.pbkdf2_hmac" => "Password based key derivation function 2 (PKCS #5 v2.0) with HMAC as pseudorandom function.", + "_hashlib.scrypt" => "scrypt password-based key derivation function.", + "_heapq" => "Heap queue algorithm (a.k.a. priority queue).\n\nHeaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for\nall k, counting elements from 0. For the sake of comparison,\nnon-existing elements are considered to be infinite. The interesting\nproperty of a heap is that a[0] is always its smallest element.\n\nUsage:\n\nheap = [] # creates an empty heap\nheappush(heap, item) # pushes a new item on the heap\nitem = heappop(heap) # pops the smallest item from the heap\nitem = heap[0] # smallest item on the heap without popping it\nheapify(x) # transforms list into a heap, in-place, in linear time\nitem = heapreplace(heap, item) # pops and returns smallest item, and adds\n # new item; the heap size is unchanged\n\nOur API differs from textbook heap algorithms as follows:\n\n- We use 0-based indexing. This makes the relationship between the\n index for a node and the indexes for its children slightly less\n obvious, but is more suitable since Python uses 0-based indexing.\n\n- Our heappop() method returns the smallest item, not the largest.\n\nThese two make it possible to view the heap as a regular Python list\nwithout surprises: heap[0] is the smallest item, and heap.sort()\nmaintains the heap invariant!", + "_heapq._heapify_max" => "Maxheap variant of heapify.", + "_heapq._heappop_max" => "Maxheap variant of heappop.", + "_heapq._heapreplace_max" => "Maxheap variant of heapreplace.", + "_heapq.heapify" => "Transform list into a heap, in-place, in O(len(heap)) time.", + "_heapq.heappop" => "Pop the smallest item off the heap, maintaining the heap invariant.", + "_heapq.heappush" => "Push item onto heap, maintaining the heap invariant.", + "_heapq.heappushpop" => "Push item on the heap, then pop and return the smallest item from the heap.\n\nThe combined action runs more efficiently than heappush() followed by\na separate call to heappop().", + "_heapq.heapreplace" => "Pop and return the current smallest value, and add the new item.\n\nThis is more efficient than heappop() followed by heappush(), and can be\nmore appropriate when using a fixed-size heap. Note that the value\nreturned may be larger than item! That constrains reasonable uses of\nthis routine unless written as part of a conditional replacement:\n\n if item > heap[0]:\n item = heapreplace(heap, item)", + "_imp" => "(Extremely) low-level import machinery bits as used by importlib.", + "_imp._fix_co_filename" => "Changes code.co_filename to specify the passed-in file path.\n\ncode\n Code object to change.\npath\n File path to use.", + "_imp._frozen_module_names" => "Returns the list of available frozen modules.", + "_imp._override_frozen_modules_for_tests" => "(internal-only) Override PyConfig.use_frozen_modules.\n\n(-1: \"off\", 1: \"on\", 0: no override)\nSee frozen_modules() in Lib/test/support/import_helper.py.", + "_imp._override_multi_interp_extensions_check" => "(internal-only) Override PyInterpreterConfig.check_multi_interp_extensions.\n\n(-1: \"never\", 1: \"always\", 0: no override)", + "_imp.acquire_lock" => "Acquires the interpreter's import lock for the current thread.\n\nThis lock should be used by import hooks to ensure thread-safety when importing\nmodules. On platforms without threads, this function does nothing.", + "_imp.create_builtin" => "Create an extension module.", + "_imp.create_dynamic" => "Create an extension module.", + "_imp.exec_builtin" => "Initialize a built-in module.", + "_imp.exec_dynamic" => "Initialize an extension module.", + "_imp.extension_suffixes" => "Returns the list of file suffixes used to identify extension modules.", + "_imp.find_frozen" => "Return info about the corresponding frozen module (if there is one) or None.\n\nThe returned info (a 2-tuple):\n\n * data the raw marshalled bytes\n * is_package whether or not it is a package\n * origname the originally frozen module's name, or None if not\n a stdlib module (this will usually be the same as\n the module's current name)", + "_imp.get_frozen_object" => "Create a code object for a frozen module.", + "_imp.init_frozen" => "Initializes a frozen module.", + "_imp.is_builtin" => "Returns True if the module name corresponds to a built-in module.", + "_imp.is_frozen" => "Returns True if the module name corresponds to a frozen module.", + "_imp.is_frozen_package" => "Returns True if the module name is of a frozen package.", + "_imp.lock_held" => "Return True if the import lock is currently held, else False.\n\nOn platforms without threads, return False.", + "_imp.release_lock" => "Release the interpreter's import lock.\n\nOn platforms without threads, this function does nothing.", + "_interpchannels" => "This module provides primitive operations to manage Python interpreters.\nThe 'interpreters' module provides a more convenient interface.", + "_interpchannels.ChannelClosedError.__cause__" => "exception cause", + "_interpchannels.ChannelClosedError.__context__" => "exception context", + "_interpchannels.ChannelClosedError.__delattr__" => "Implement delattr(self, name).", + "_interpchannels.ChannelClosedError.__eq__" => "Return self==value.", + "_interpchannels.ChannelClosedError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_interpchannels.ChannelClosedError.__ge__" => "Return self>=value.", + "_interpchannels.ChannelClosedError.__getattribute__" => "Return getattr(self, name).", + "_interpchannels.ChannelClosedError.__getstate__" => "Helper for pickle.", + "_interpchannels.ChannelClosedError.__gt__" => "Return self>value.", + "_interpchannels.ChannelClosedError.__hash__" => "Return hash(self).", + "_interpchannels.ChannelClosedError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_interpchannels.ChannelClosedError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_interpchannels.ChannelClosedError.__le__" => "Return self<=value.", + "_interpchannels.ChannelClosedError.__lt__" => "Return self<value.", + "_interpchannels.ChannelClosedError.__ne__" => "Return self!=value.", + "_interpchannels.ChannelClosedError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_interpchannels.ChannelClosedError.__reduce_ex__" => "Helper for pickle.", + "_interpchannels.ChannelClosedError.__repr__" => "Return repr(self).", + "_interpchannels.ChannelClosedError.__setattr__" => "Implement setattr(self, name, value).", + "_interpchannels.ChannelClosedError.__sizeof__" => "Size of object in memory, in bytes.", + "_interpchannels.ChannelClosedError.__str__" => "Return str(self).", + "_interpchannels.ChannelClosedError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_interpchannels.ChannelClosedError.__weakref__" => "list of weak references to the object", + "_interpchannels.ChannelClosedError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_interpchannels.ChannelClosedError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_interpchannels.ChannelEmptyError.__cause__" => "exception cause", + "_interpchannels.ChannelEmptyError.__context__" => "exception context", + "_interpchannels.ChannelEmptyError.__delattr__" => "Implement delattr(self, name).", + "_interpchannels.ChannelEmptyError.__eq__" => "Return self==value.", + "_interpchannels.ChannelEmptyError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_interpchannels.ChannelEmptyError.__ge__" => "Return self>=value.", + "_interpchannels.ChannelEmptyError.__getattribute__" => "Return getattr(self, name).", + "_interpchannels.ChannelEmptyError.__getstate__" => "Helper for pickle.", + "_interpchannels.ChannelEmptyError.__gt__" => "Return self>value.", + "_interpchannels.ChannelEmptyError.__hash__" => "Return hash(self).", + "_interpchannels.ChannelEmptyError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_interpchannels.ChannelEmptyError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_interpchannels.ChannelEmptyError.__le__" => "Return self<=value.", + "_interpchannels.ChannelEmptyError.__lt__" => "Return self<value.", + "_interpchannels.ChannelEmptyError.__ne__" => "Return self!=value.", + "_interpchannels.ChannelEmptyError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_interpchannels.ChannelEmptyError.__reduce_ex__" => "Helper for pickle.", + "_interpchannels.ChannelEmptyError.__repr__" => "Return repr(self).", + "_interpchannels.ChannelEmptyError.__setattr__" => "Implement setattr(self, name, value).", + "_interpchannels.ChannelEmptyError.__sizeof__" => "Size of object in memory, in bytes.", + "_interpchannels.ChannelEmptyError.__str__" => "Return str(self).", + "_interpchannels.ChannelEmptyError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_interpchannels.ChannelEmptyError.__weakref__" => "list of weak references to the object", + "_interpchannels.ChannelEmptyError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_interpchannels.ChannelEmptyError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_interpchannels.ChannelError.__cause__" => "exception cause", + "_interpchannels.ChannelError.__context__" => "exception context", + "_interpchannels.ChannelError.__delattr__" => "Implement delattr(self, name).", + "_interpchannels.ChannelError.__eq__" => "Return self==value.", + "_interpchannels.ChannelError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_interpchannels.ChannelError.__ge__" => "Return self>=value.", + "_interpchannels.ChannelError.__getattribute__" => "Return getattr(self, name).", + "_interpchannels.ChannelError.__getstate__" => "Helper for pickle.", + "_interpchannels.ChannelError.__gt__" => "Return self>value.", + "_interpchannels.ChannelError.__hash__" => "Return hash(self).", + "_interpchannels.ChannelError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_interpchannels.ChannelError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_interpchannels.ChannelError.__le__" => "Return self<=value.", + "_interpchannels.ChannelError.__lt__" => "Return self<value.", + "_interpchannels.ChannelError.__ne__" => "Return self!=value.", + "_interpchannels.ChannelError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_interpchannels.ChannelError.__reduce_ex__" => "Helper for pickle.", + "_interpchannels.ChannelError.__repr__" => "Return repr(self).", + "_interpchannels.ChannelError.__setattr__" => "Implement setattr(self, name, value).", + "_interpchannels.ChannelError.__sizeof__" => "Size of object in memory, in bytes.", + "_interpchannels.ChannelError.__str__" => "Return str(self).", + "_interpchannels.ChannelError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_interpchannels.ChannelError.__weakref__" => "list of weak references to the object", + "_interpchannels.ChannelError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_interpchannels.ChannelError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_interpchannels.ChannelID" => "A channel ID identifies a channel and may be used as an int.", + "_interpchannels.ChannelID.__delattr__" => "Implement delattr(self, name).", + "_interpchannels.ChannelID.__eq__" => "Return self==value.", + "_interpchannels.ChannelID.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_interpchannels.ChannelID.__ge__" => "Return self>=value.", + "_interpchannels.ChannelID.__getattribute__" => "Return getattr(self, name).", + "_interpchannels.ChannelID.__getstate__" => "Helper for pickle.", + "_interpchannels.ChannelID.__gt__" => "Return self>value.", + "_interpchannels.ChannelID.__hash__" => "Return hash(self).", + "_interpchannels.ChannelID.__index__" => "Return self converted to an integer, if self is suitable for use as an index into a list.", + "_interpchannels.ChannelID.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_interpchannels.ChannelID.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_interpchannels.ChannelID.__int__" => "int(self)", + "_interpchannels.ChannelID.__le__" => "Return self<=value.", + "_interpchannels.ChannelID.__lt__" => "Return self<value.", + "_interpchannels.ChannelID.__ne__" => "Return self!=value.", + "_interpchannels.ChannelID.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_interpchannels.ChannelID.__reduce__" => "Helper for pickle.", + "_interpchannels.ChannelID.__reduce_ex__" => "Helper for pickle.", + "_interpchannels.ChannelID.__repr__" => "Return repr(self).", + "_interpchannels.ChannelID.__setattr__" => "Implement setattr(self, name, value).", + "_interpchannels.ChannelID.__sizeof__" => "Size of object in memory, in bytes.", + "_interpchannels.ChannelID.__str__" => "Return str(self).", + "_interpchannels.ChannelID.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_interpchannels.ChannelID.end" => "'send', 'recv', or 'both'", + "_interpchannels.ChannelID.recv" => "the 'recv' end of the channel", + "_interpchannels.ChannelID.send" => "the 'send' end of the channel", + "_interpchannels.ChannelInfo" => "ChannelInfo\n\nA named tuple of a channel's state.", + "_interpchannels.ChannelInfo.__add__" => "Return self+value.", + "_interpchannels.ChannelInfo.__class_getitem__" => "See PEP 585", + "_interpchannels.ChannelInfo.__contains__" => "Return bool(key in self).", + "_interpchannels.ChannelInfo.__delattr__" => "Implement delattr(self, name).", + "_interpchannels.ChannelInfo.__eq__" => "Return self==value.", + "_interpchannels.ChannelInfo.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_interpchannels.ChannelInfo.__ge__" => "Return self>=value.", + "_interpchannels.ChannelInfo.__getattribute__" => "Return getattr(self, name).", + "_interpchannels.ChannelInfo.__getitem__" => "Return self[key].", + "_interpchannels.ChannelInfo.__getstate__" => "Helper for pickle.", + "_interpchannels.ChannelInfo.__gt__" => "Return self>value.", + "_interpchannels.ChannelInfo.__hash__" => "Return hash(self).", + "_interpchannels.ChannelInfo.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_interpchannels.ChannelInfo.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_interpchannels.ChannelInfo.__iter__" => "Implement iter(self).", + "_interpchannels.ChannelInfo.__le__" => "Return self<=value.", + "_interpchannels.ChannelInfo.__len__" => "Return len(self).", + "_interpchannels.ChannelInfo.__lt__" => "Return self<value.", + "_interpchannels.ChannelInfo.__mul__" => "Return self*value.", + "_interpchannels.ChannelInfo.__ne__" => "Return self!=value.", + "_interpchannels.ChannelInfo.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_interpchannels.ChannelInfo.__reduce_ex__" => "Helper for pickle.", + "_interpchannels.ChannelInfo.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "_interpchannels.ChannelInfo.__repr__" => "Return repr(self).", + "_interpchannels.ChannelInfo.__rmul__" => "Return value*self.", + "_interpchannels.ChannelInfo.__setattr__" => "Implement setattr(self, name, value).", + "_interpchannels.ChannelInfo.__sizeof__" => "Size of object in memory, in bytes.", + "_interpchannels.ChannelInfo.__str__" => "Return str(self).", + "_interpchannels.ChannelInfo.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_interpchannels.ChannelInfo.closed" => "both ends are closed", + "_interpchannels.ChannelInfo.closing" => "send is closed, recv is non-empty", + "_interpchannels.ChannelInfo.count" => "queued objects", + "_interpchannels.ChannelInfo.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "_interpchannels.ChannelInfo.num_interp_both" => "interpreters bound to both ends", + "_interpchannels.ChannelInfo.num_interp_both_recv_released" => "interpreters bound to both ends and released_from_the recv end", + "_interpchannels.ChannelInfo.num_interp_both_released" => "interpreters bound to both ends and released_from_both", + "_interpchannels.ChannelInfo.num_interp_both_send_released" => "interpreters bound to both ends and released_from_the send end", + "_interpchannels.ChannelInfo.num_interp_recv" => "interpreters bound to the send end", + "_interpchannels.ChannelInfo.num_interp_recv_released" => "interpreters bound to the send end and released", + "_interpchannels.ChannelInfo.num_interp_send" => "interpreters bound to the send end", + "_interpchannels.ChannelInfo.num_interp_send_released" => "interpreters bound to the send end and released", + "_interpchannels.ChannelInfo.open" => "both ends are open", + "_interpchannels.ChannelInfo.recv_associated" => "current interpreter is bound to the recv end", + "_interpchannels.ChannelInfo.recv_released" => "current interpreter *was* bound to the recv end", + "_interpchannels.ChannelInfo.send_associated" => "current interpreter is bound to the send end", + "_interpchannels.ChannelInfo.send_released" => "current interpreter *was* bound to the send end", + "_interpchannels.ChannelNotEmptyError.__cause__" => "exception cause", + "_interpchannels.ChannelNotEmptyError.__context__" => "exception context", + "_interpchannels.ChannelNotEmptyError.__delattr__" => "Implement delattr(self, name).", + "_interpchannels.ChannelNotEmptyError.__eq__" => "Return self==value.", + "_interpchannels.ChannelNotEmptyError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_interpchannels.ChannelNotEmptyError.__ge__" => "Return self>=value.", + "_interpchannels.ChannelNotEmptyError.__getattribute__" => "Return getattr(self, name).", + "_interpchannels.ChannelNotEmptyError.__getstate__" => "Helper for pickle.", + "_interpchannels.ChannelNotEmptyError.__gt__" => "Return self>value.", + "_interpchannels.ChannelNotEmptyError.__hash__" => "Return hash(self).", + "_interpchannels.ChannelNotEmptyError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_interpchannels.ChannelNotEmptyError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_interpchannels.ChannelNotEmptyError.__le__" => "Return self<=value.", + "_interpchannels.ChannelNotEmptyError.__lt__" => "Return self<value.", + "_interpchannels.ChannelNotEmptyError.__ne__" => "Return self!=value.", + "_interpchannels.ChannelNotEmptyError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_interpchannels.ChannelNotEmptyError.__reduce_ex__" => "Helper for pickle.", + "_interpchannels.ChannelNotEmptyError.__repr__" => "Return repr(self).", + "_interpchannels.ChannelNotEmptyError.__setattr__" => "Implement setattr(self, name, value).", + "_interpchannels.ChannelNotEmptyError.__sizeof__" => "Size of object in memory, in bytes.", + "_interpchannels.ChannelNotEmptyError.__str__" => "Return str(self).", + "_interpchannels.ChannelNotEmptyError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_interpchannels.ChannelNotEmptyError.__weakref__" => "list of weak references to the object", + "_interpchannels.ChannelNotEmptyError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_interpchannels.ChannelNotEmptyError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_interpchannels.ChannelNotFoundError.__cause__" => "exception cause", + "_interpchannels.ChannelNotFoundError.__context__" => "exception context", + "_interpchannels.ChannelNotFoundError.__delattr__" => "Implement delattr(self, name).", + "_interpchannels.ChannelNotFoundError.__eq__" => "Return self==value.", + "_interpchannels.ChannelNotFoundError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_interpchannels.ChannelNotFoundError.__ge__" => "Return self>=value.", + "_interpchannels.ChannelNotFoundError.__getattribute__" => "Return getattr(self, name).", + "_interpchannels.ChannelNotFoundError.__getstate__" => "Helper for pickle.", + "_interpchannels.ChannelNotFoundError.__gt__" => "Return self>value.", + "_interpchannels.ChannelNotFoundError.__hash__" => "Return hash(self).", + "_interpchannels.ChannelNotFoundError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_interpchannels.ChannelNotFoundError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_interpchannels.ChannelNotFoundError.__le__" => "Return self<=value.", + "_interpchannels.ChannelNotFoundError.__lt__" => "Return self<value.", + "_interpchannels.ChannelNotFoundError.__ne__" => "Return self!=value.", + "_interpchannels.ChannelNotFoundError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_interpchannels.ChannelNotFoundError.__reduce_ex__" => "Helper for pickle.", + "_interpchannels.ChannelNotFoundError.__repr__" => "Return repr(self).", + "_interpchannels.ChannelNotFoundError.__setattr__" => "Implement setattr(self, name, value).", + "_interpchannels.ChannelNotFoundError.__sizeof__" => "Size of object in memory, in bytes.", + "_interpchannels.ChannelNotFoundError.__str__" => "Return str(self).", + "_interpchannels.ChannelNotFoundError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_interpchannels.ChannelNotFoundError.__weakref__" => "list of weak references to the object", + "_interpchannels.ChannelNotFoundError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_interpchannels.ChannelNotFoundError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_interpchannels.close" => "channel_close(cid, *, send=None, recv=None, force=False)\n\nClose the channel for all interpreters.\n\nIf the channel is empty then the keyword args are ignored and both\nends are immediately closed. Otherwise, if 'force' is True then\nall queued items are released and both ends are immediately\nclosed.\n\nIf the channel is not empty *and* 'force' is False then following\nhappens:\n\n * recv is True (regardless of send):\n - raise ChannelNotEmptyError\n * recv is None and send is None:\n - raise ChannelNotEmptyError\n * send is True and recv is not True:\n - fully close the 'send' end\n - close the 'recv' end to interpreters not already receiving\n - fully close it once empty\n\nClosing an already closed channel results in a ChannelClosedError.\n\nOnce the channel's ID has no more ref counts in any interpreter\nthe channel will be destroyed.", + "_interpchannels.create" => "channel_create(unboundop) -> cid\n\nCreate a new cross-interpreter channel and return a unique generated ID.", + "_interpchannels.destroy" => "channel_destroy(cid)\n\nClose and finalize the channel. Afterward attempts to use the channel\nwill behave as though it never existed.", + "_interpchannels.get_channel_defaults" => "get_channel_defaults(cid)\n\nReturn the channel's default values, set when it was created.", + "_interpchannels.get_count" => "get_count(cid)\n\nReturn the number of items in the channel.", + "_interpchannels.get_info" => "get_info(cid)\n\nReturn details about the channel.", + "_interpchannels.list_all" => "channel_list_all() -> [cid]\n\nReturn the list of all IDs for active channels.", + "_interpchannels.list_interpreters" => "channel_list_interpreters(cid, *, send) -> [id]\n\nReturn the list of all interpreter IDs associated with an end of the channel.\n\nThe 'send' argument should be a boolean indicating whether to use the send or\nreceive end.", + "_interpchannels.recv" => "channel_recv(cid, [default]) -> (obj, unboundop)\n\nReturn a new object from the data at the front of the channel's queue.\n\nIf there is nothing to receive then raise ChannelEmptyError, unless\na default value is provided. In that case return it.", + "_interpchannels.release" => "channel_release(cid, *, send=None, recv=None, force=True)\n\nClose the channel for the current interpreter. 'send' and 'recv'\n(bool) may be used to indicate the ends to close. By default both\nends are closed. Closing an already closed end is a noop.", + "_interpchannels.send" => "channel_send(cid, obj, *, blocking=True, timeout=None)\n\nAdd the object's data to the channel's queue.\nBy default this waits for the object to be received.", + "_interpchannels.send_buffer" => "channel_send_buffer(cid, obj, *, blocking=True, timeout=None)\n\nAdd the object's buffer to the channel's queue.\nBy default this waits for the object to be received.", + "_interpqueues" => "This module provides primitive operations to manage Python interpreters.\nThe 'interpreters' module provides a more convenient interface.", + "_interpqueues.bind" => "bind(qid)\n\nTake a reference to the identified queue.\nThe queue is not destroyed until there are no references left.", + "_interpqueues.create" => "create(maxsize, fmt, unboundop) -> qid\n\nCreate a new cross-interpreter queue and return its unique generated ID.\nIt is a new reference as though bind() had been called on the queue.\n\nThe caller is responsible for calling destroy() for the new queue\nbefore the runtime is finalized.", + "_interpqueues.destroy" => "destroy(qid)\n\nClear and destroy the queue. Afterward attempts to use the queue\nwill behave as though it never existed.", + "_interpqueues.get" => "get(qid) -> (obj, fmt)\n\nReturn a new object from the data at the front of the queue.\nThe object's format is also returned.\n\nIf there is nothing to receive then raise QueueEmpty.", + "_interpqueues.get_count" => "get_count(qid)\n\nReturn the number of items in the queue.", + "_interpqueues.get_maxsize" => "get_maxsize(qid)\n\nReturn the maximum number of items in the queue.", + "_interpqueues.get_queue_defaults" => "get_queue_defaults(qid)\n\nReturn the queue's default values, set when it was created.", + "_interpqueues.is_full" => "is_full(qid)\n\nReturn true if the queue has a maxsize and has reached it.", + "_interpqueues.list_all" => "list_all() -> [(qid, fmt)]\n\nReturn the list of IDs for all queues.\nEach corresponding default format is also included.", + "_interpqueues.put" => "put(qid, obj, fmt)\n\nAdd the object's data to the queue.", + "_interpqueues.release" => "release(qid)\n\nRelease a reference to the queue.\nThe queue is destroyed once there are no references left.", + "_interpreters" => "This module provides primitive operations to manage Python interpreters.\nThe 'interpreters' module provides a more convenient interface.", + "_interpreters.CrossInterpreterBufferView.__buffer__" => "Return a buffer object that exposes the underlying memory of the object.", + "_interpreters.CrossInterpreterBufferView.__delattr__" => "Implement delattr(self, name).", + "_interpreters.CrossInterpreterBufferView.__eq__" => "Return self==value.", + "_interpreters.CrossInterpreterBufferView.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_interpreters.CrossInterpreterBufferView.__ge__" => "Return self>=value.", + "_interpreters.CrossInterpreterBufferView.__getattribute__" => "Return getattr(self, name).", + "_interpreters.CrossInterpreterBufferView.__getstate__" => "Helper for pickle.", + "_interpreters.CrossInterpreterBufferView.__gt__" => "Return self>value.", + "_interpreters.CrossInterpreterBufferView.__hash__" => "Return hash(self).", + "_interpreters.CrossInterpreterBufferView.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_interpreters.CrossInterpreterBufferView.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_interpreters.CrossInterpreterBufferView.__le__" => "Return self<=value.", + "_interpreters.CrossInterpreterBufferView.__lt__" => "Return self<value.", + "_interpreters.CrossInterpreterBufferView.__ne__" => "Return self!=value.", + "_interpreters.CrossInterpreterBufferView.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_interpreters.CrossInterpreterBufferView.__reduce__" => "Helper for pickle.", + "_interpreters.CrossInterpreterBufferView.__reduce_ex__" => "Helper for pickle.", + "_interpreters.CrossInterpreterBufferView.__repr__" => "Return repr(self).", + "_interpreters.CrossInterpreterBufferView.__setattr__" => "Implement setattr(self, name, value).", + "_interpreters.CrossInterpreterBufferView.__sizeof__" => "Size of object in memory, in bytes.", + "_interpreters.CrossInterpreterBufferView.__str__" => "Return str(self).", + "_interpreters.CrossInterpreterBufferView.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_interpreters.call" => "call(id, callable, args=None, kwargs=None, *, restrict=False)\n\nCall the provided object in the identified interpreter.\nPass the given args and kwargs, if possible.\n\n\"callable\" may be a plain function with no free vars that takes\nno arguments.\n\nThe function's code object is used and all its state\nis ignored, including its __globals__ dict.", + "_interpreters.capture_exception" => "capture_exception(exc=None) -> types.SimpleNamespace\n\nReturn a snapshot of an exception. If \"exc\" is None\nthen the current exception, if any, is used (but not cleared).\n\nThe returned snapshot is the same as what _interpreters.exec() returns.", + "_interpreters.create" => "create([config], *, reqrefs=False) -> ID\n\nCreate a new interpreter and return a unique generated ID.\n\nThe caller is responsible for destroying the interpreter before exiting,\ntypically by using _interpreters.destroy(). This can be managed \nautomatically by passing \"reqrefs=True\" and then using _incref() and\n_decref()` appropriately.\n\n\"config\" must be a valid interpreter config or the name of a\npredefined config (\"isolated\" or \"legacy\"). The default\nis \"isolated\".", + "_interpreters.destroy" => "destroy(id, *, restrict=False)\n\nDestroy the identified interpreter.\n\nAttempting to destroy the current interpreter raises InterpreterError.\nSo does an unrecognized ID.", + "_interpreters.exec" => "exec(id, code, shared=None, *, restrict=False)\n\nExecute the provided code in the identified interpreter.\nThis is equivalent to running the builtin exec() under the target\ninterpreter, using the __dict__ of its __main__ module as both\nglobals and locals.\n\n\"code\" may be a string containing the text of a Python script.\n\nFunctions (and code objects) are also supported, with some restrictions.\nThe code/function must not take any arguments or be a closure\n(i.e. have cell vars). Methods and other callables are not supported.\n\nIf a function is provided, its code object is used and all its state\nis ignored, including its __globals__ dict.", + "_interpreters.get_config" => "get_config(id, *, restrict=False) -> types.SimpleNamespace\n\nReturn a representation of the config used to initialize the interpreter.", + "_interpreters.get_current" => "get_current() -> (ID, whence)\n\nReturn the ID of current interpreter.", + "_interpreters.get_main" => "get_main() -> (ID, whence)\n\nReturn the ID of main interpreter.", + "_interpreters.is_running" => "is_running(id, *, restrict=False) -> bool\n\nReturn whether or not the identified interpreter is running.", + "_interpreters.is_shareable" => "is_shareable(obj) -> bool\n\nReturn True if the object's data may be shared between interpreters and\nFalse otherwise.", + "_interpreters.list_all" => "list_all() -> [(ID, whence)]\n\nReturn a list containing the ID of every existing interpreter.", + "_interpreters.new_config" => "new_config(name='isolated', /, **overrides) -> type.SimpleNamespace\n\nReturn a representation of a new PyInterpreterConfig.\n\nThe name determines the initial values of the config. Supported named\nconfigs are: default, isolated, legacy, and empty.\n\nAny keyword arguments are set on the corresponding config fields,\noverriding the initial values.", + "_interpreters.run_func" => "run_func(id, func, shared=None, *, restrict=False)\n\nExecute the body of the provided function in the identified interpreter.\nCode objects are also supported. In both cases, closures and args\nare not supported. Methods and other callables are not supported either.\n\n(See _interpreters.exec().", + "_interpreters.run_string" => "run_string(id, script, shared=None, *, restrict=False)\n\nExecute the provided string in the identified interpreter.\n\n(See _interpreters.exec().", + "_interpreters.set___main___attrs" => "set___main___attrs(id, ns, *, restrict=False)\n\nBind the given attributes in the interpreter's __main__ module.", + "_interpreters.whence" => "whence(id) -> int\n\nReturn an identifier for where the interpreter was created.", + "_io" => "The io module provides the Python interfaces to stream handling. The\nbuiltin open function is defined in this module.\n\nAt the top of the I/O hierarchy is the abstract base class IOBase. It\ndefines the basic interface to a stream. Note, however, that there is no\nseparation between reading and writing to streams; implementations are\nallowed to raise an OSError if they do not support a given operation.\n\nExtending IOBase is RawIOBase which deals simply with the reading and\nwriting of raw bytes to a stream. FileIO subclasses RawIOBase to provide\nan interface to OS files.\n\nBufferedIOBase deals with buffering on a raw byte stream (RawIOBase). Its\nsubclasses, BufferedWriter, BufferedReader, and BufferedRWPair buffer\nstreams that are readable, writable, and both respectively.\nBufferedRandom provides a buffered interface to random access\nstreams. BytesIO is a simple stream of in-memory bytes.\n\nAnother IOBase subclass, TextIOBase, deals with the encoding and decoding\nof streams into text. TextIOWrapper, which extends it, is a buffered text\ninterface to a buffered raw stream (`BufferedIOBase`). Finally, StringIO\nis an in-memory stream for text.\n\nArgument names are not part of the specification, and only the arguments\nof open() are intended to be used as keyword arguments.\n\ndata:\n\nDEFAULT_BUFFER_SIZE\n\n An int containing the default buffer size used by the module's buffered\n I/O classes. open() uses the file's blksize (as obtained by os.stat) if\n possible.", + "_io.BufferedRWPair" => "A buffered reader and writer object together.\n\nA buffered reader object and buffered writer object put together to\nform a sequential IO object that can read and write. This is typically\nused with a socket or two-way pipe.\n\nreader and writer are RawIOBase objects that are readable and\nwriteable respectively. If the buffer_size is omitted it defaults to\nDEFAULT_BUFFER_SIZE.", + "_io.BufferedRWPair.__del__" => "Called when the instance is about to be destroyed.", + "_io.BufferedRWPair.__delattr__" => "Implement delattr(self, name).", + "_io.BufferedRWPair.__eq__" => "Return self==value.", + "_io.BufferedRWPair.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io.BufferedRWPair.__ge__" => "Return self>=value.", + "_io.BufferedRWPair.__getattribute__" => "Return getattr(self, name).", + "_io.BufferedRWPair.__getstate__" => "Helper for pickle.", + "_io.BufferedRWPair.__gt__" => "Return self>value.", + "_io.BufferedRWPair.__hash__" => "Return hash(self).", + "_io.BufferedRWPair.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io.BufferedRWPair.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io.BufferedRWPair.__iter__" => "Implement iter(self).", + "_io.BufferedRWPair.__le__" => "Return self<=value.", + "_io.BufferedRWPair.__lt__" => "Return self<value.", + "_io.BufferedRWPair.__ne__" => "Return self!=value.", + "_io.BufferedRWPair.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io.BufferedRWPair.__next__" => "Implement next(self).", + "_io.BufferedRWPair.__reduce__" => "Helper for pickle.", + "_io.BufferedRWPair.__reduce_ex__" => "Helper for pickle.", + "_io.BufferedRWPair.__repr__" => "Return repr(self).", + "_io.BufferedRWPair.__setattr__" => "Implement setattr(self, name, value).", + "_io.BufferedRWPair.__sizeof__" => "Size of object in memory, in bytes.", + "_io.BufferedRWPair.__str__" => "Return str(self).", + "_io.BufferedRWPair.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io.BufferedRWPair.detach" => "Disconnect this buffer from its underlying raw stream and return it.\n\nAfter the raw stream has been detached, the buffer is in an unusable\nstate.", + "_io.BufferedRWPair.fileno" => "Return underlying file descriptor if one exists.\n\nRaise OSError if the IO object does not use a file descriptor.", + "_io.BufferedRWPair.readline" => "Read and return a line from the stream.\n\nIf size is specified, at most size bytes will be read.\n\nThe line terminator is always b'\\n' for binary files; for text\nfiles, the newlines argument to open can be used to select the line\nterminator(s) recognized.", + "_io.BufferedRWPair.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io.BufferedRWPair.seek" => "Change the stream position to the given byte offset.\n\n offset\n The stream position, relative to 'whence'.\n whence\n The relative position to seek from.\n\nThe offset is interpreted relative to the position indicated by whence.\nValues for whence are:\n\n* os.SEEK_SET or 0 -- start of stream (the default); offset should be zero or positive\n* os.SEEK_CUR or 1 -- current stream position; offset may be negative\n* os.SEEK_END or 2 -- end of stream; offset is usually negative\n\nReturn the new absolute position.", + "_io.BufferedRWPair.seekable" => "Return whether object supports random access.\n\nIf False, seek(), tell() and truncate() will raise OSError.\nThis method may need to do a test seek().", + "_io.BufferedRWPair.tell" => "Return current stream position.", + "_io.BufferedRWPair.truncate" => "Truncate file to size bytes.\n\nFile pointer is left unchanged. Size defaults to the current IO position\nas reported by tell(). Return the new size.", + "_io.BufferedRWPair.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io.BufferedRandom" => "A buffered interface to random access streams.\n\nThe constructor creates a reader and writer for a seekable stream,\nraw, given in the first argument. If the buffer_size is omitted it\ndefaults to DEFAULT_BUFFER_SIZE.", + "_io.BufferedRandom.__del__" => "Called when the instance is about to be destroyed.", + "_io.BufferedRandom.__delattr__" => "Implement delattr(self, name).", + "_io.BufferedRandom.__eq__" => "Return self==value.", + "_io.BufferedRandom.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io.BufferedRandom.__ge__" => "Return self>=value.", + "_io.BufferedRandom.__getattribute__" => "Return getattr(self, name).", + "_io.BufferedRandom.__gt__" => "Return self>value.", + "_io.BufferedRandom.__hash__" => "Return hash(self).", + "_io.BufferedRandom.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io.BufferedRandom.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io.BufferedRandom.__iter__" => "Implement iter(self).", + "_io.BufferedRandom.__le__" => "Return self<=value.", + "_io.BufferedRandom.__lt__" => "Return self<value.", + "_io.BufferedRandom.__ne__" => "Return self!=value.", + "_io.BufferedRandom.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io.BufferedRandom.__next__" => "Implement next(self).", + "_io.BufferedRandom.__reduce__" => "Helper for pickle.", + "_io.BufferedRandom.__reduce_ex__" => "Helper for pickle.", + "_io.BufferedRandom.__repr__" => "Return repr(self).", + "_io.BufferedRandom.__setattr__" => "Implement setattr(self, name, value).", + "_io.BufferedRandom.__str__" => "Return str(self).", + "_io.BufferedRandom.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io.BufferedRandom.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io.BufferedRandom.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io.BufferedReader" => "Create a new buffered reader using the given readable raw IO object.", + "_io.BufferedReader.__del__" => "Called when the instance is about to be destroyed.", + "_io.BufferedReader.__delattr__" => "Implement delattr(self, name).", + "_io.BufferedReader.__eq__" => "Return self==value.", + "_io.BufferedReader.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io.BufferedReader.__ge__" => "Return self>=value.", + "_io.BufferedReader.__getattribute__" => "Return getattr(self, name).", + "_io.BufferedReader.__gt__" => "Return self>value.", + "_io.BufferedReader.__hash__" => "Return hash(self).", + "_io.BufferedReader.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io.BufferedReader.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io.BufferedReader.__iter__" => "Implement iter(self).", + "_io.BufferedReader.__le__" => "Return self<=value.", + "_io.BufferedReader.__lt__" => "Return self<value.", + "_io.BufferedReader.__ne__" => "Return self!=value.", + "_io.BufferedReader.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io.BufferedReader.__next__" => "Implement next(self).", + "_io.BufferedReader.__reduce__" => "Helper for pickle.", + "_io.BufferedReader.__reduce_ex__" => "Helper for pickle.", + "_io.BufferedReader.__repr__" => "Return repr(self).", + "_io.BufferedReader.__setattr__" => "Implement setattr(self, name, value).", + "_io.BufferedReader.__str__" => "Return str(self).", + "_io.BufferedReader.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io.BufferedReader.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io.BufferedReader.writable" => "Return whether object was opened for writing.\n\nIf False, write() will raise OSError.", + "_io.BufferedReader.write" => "Write buffer b to the IO stream.\n\nReturn the number of bytes written, which is always\nthe length of b in bytes.\n\nRaise BlockingIOError if the buffer is full and the\nunderlying raw stream cannot accept more data at the moment.", + "_io.BufferedReader.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io.BufferedWriter" => "A buffer for a writeable sequential RawIO object.\n\nThe constructor creates a BufferedWriter for the given writeable raw\nstream. If the buffer_size is not given, it defaults to\nDEFAULT_BUFFER_SIZE.", + "_io.BufferedWriter.__del__" => "Called when the instance is about to be destroyed.", + "_io.BufferedWriter.__delattr__" => "Implement delattr(self, name).", + "_io.BufferedWriter.__eq__" => "Return self==value.", + "_io.BufferedWriter.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io.BufferedWriter.__ge__" => "Return self>=value.", + "_io.BufferedWriter.__getattribute__" => "Return getattr(self, name).", + "_io.BufferedWriter.__gt__" => "Return self>value.", + "_io.BufferedWriter.__hash__" => "Return hash(self).", + "_io.BufferedWriter.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io.BufferedWriter.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io.BufferedWriter.__iter__" => "Implement iter(self).", + "_io.BufferedWriter.__le__" => "Return self<=value.", + "_io.BufferedWriter.__lt__" => "Return self<value.", + "_io.BufferedWriter.__ne__" => "Return self!=value.", + "_io.BufferedWriter.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io.BufferedWriter.__next__" => "Implement next(self).", + "_io.BufferedWriter.__reduce__" => "Helper for pickle.", + "_io.BufferedWriter.__reduce_ex__" => "Helper for pickle.", + "_io.BufferedWriter.__repr__" => "Return repr(self).", + "_io.BufferedWriter.__setattr__" => "Implement setattr(self, name, value).", + "_io.BufferedWriter.__str__" => "Return str(self).", + "_io.BufferedWriter.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io.BufferedWriter.read" => "Read and return up to n bytes.\n\nIf the size argument is omitted, None, or negative, read and\nreturn all data until EOF.\n\nIf the size argument is positive, and the underlying raw stream is\nnot 'interactive', multiple raw reads may be issued to satisfy\nthe byte count (unless EOF is reached first).\nHowever, for interactive raw streams (as well as sockets and pipes),\nat most one raw read will be issued, and a short result does not\nimply that EOF is imminent.\n\nReturn an empty bytes object on EOF.\n\nReturn None if the underlying raw stream was open in non-blocking\nmode and no data is available at the moment.", + "_io.BufferedWriter.read1" => "Read and return up to size bytes, with at most one read() call to the underlying raw stream.\n\nReturn an empty bytes object on EOF.\nA short result does not imply that EOF is imminent.", + "_io.BufferedWriter.readable" => "Return whether object was opened for reading.\n\nIf False, read() will raise OSError.", + "_io.BufferedWriter.readline" => "Read and return a line from the stream.\n\nIf size is specified, at most size bytes will be read.\n\nThe line terminator is always b'\\n' for binary files; for text\nfiles, the newlines argument to open can be used to select the line\nterminator(s) recognized.", + "_io.BufferedWriter.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io.BufferedWriter.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io.BytesIO" => "Buffered I/O implementation using an in-memory bytes buffer.", + "_io.BytesIO.__del__" => "Called when the instance is about to be destroyed.", + "_io.BytesIO.__delattr__" => "Implement delattr(self, name).", + "_io.BytesIO.__eq__" => "Return self==value.", + "_io.BytesIO.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io.BytesIO.__ge__" => "Return self>=value.", + "_io.BytesIO.__getattribute__" => "Return getattr(self, name).", + "_io.BytesIO.__gt__" => "Return self>value.", + "_io.BytesIO.__hash__" => "Return hash(self).", + "_io.BytesIO.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io.BytesIO.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io.BytesIO.__iter__" => "Implement iter(self).", + "_io.BytesIO.__le__" => "Return self<=value.", + "_io.BytesIO.__lt__" => "Return self<value.", + "_io.BytesIO.__ne__" => "Return self!=value.", + "_io.BytesIO.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io.BytesIO.__next__" => "Implement next(self).", + "_io.BytesIO.__reduce__" => "Helper for pickle.", + "_io.BytesIO.__reduce_ex__" => "Helper for pickle.", + "_io.BytesIO.__repr__" => "Return repr(self).", + "_io.BytesIO.__setattr__" => "Implement setattr(self, name, value).", + "_io.BytesIO.__str__" => "Return str(self).", + "_io.BytesIO.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io.BytesIO.close" => "Disable all I/O operations.", + "_io.BytesIO.closed" => "True if the file is closed.", + "_io.BytesIO.detach" => "Disconnect this buffer from its underlying raw stream and return it.\n\nAfter the raw stream has been detached, the buffer is in an unusable\nstate.", + "_io.BytesIO.fileno" => "Return underlying file descriptor if one exists.\n\nRaise OSError if the IO object does not use a file descriptor.", + "_io.BytesIO.flush" => "Does nothing.", + "_io.BytesIO.getbuffer" => "Get a read-write view over the contents of the BytesIO object.", + "_io.BytesIO.getvalue" => "Retrieve the entire contents of the BytesIO object.", + "_io.BytesIO.isatty" => "Always returns False.\n\nBytesIO objects are not connected to a TTY-like device.", + "_io.BytesIO.read" => "Read at most size bytes, returned as a bytes object.\n\nIf the size argument is negative, read until EOF is reached.\nReturn an empty bytes object at EOF.", + "_io.BytesIO.read1" => "Read at most size bytes, returned as a bytes object.\n\nIf the size argument is negative or omitted, read until EOF is reached.\nReturn an empty bytes object at EOF.", + "_io.BytesIO.readable" => "Returns True if the IO object can be read.", + "_io.BytesIO.readinto" => "Read bytes into buffer.\n\nReturns number of bytes read (0 for EOF), or None if the object\nis set not to block and has no data to read.", + "_io.BytesIO.readline" => "Next line from the file, as a bytes object.\n\nRetain newline. A non-negative size argument limits the maximum\nnumber of bytes to return (an incomplete line may be returned then).\nReturn an empty bytes object at EOF.", + "_io.BytesIO.readlines" => "List of bytes objects, each a line from the file.\n\nCall readline() repeatedly and return a list of the lines so read.\nThe optional size argument, if given, is an approximate bound on the\ntotal number of bytes in the lines returned.", + "_io.BytesIO.seek" => "Change stream position.\n\nSeek to byte offset pos relative to position indicated by whence:\n 0 Start of stream (the default). pos should be >= 0;\n 1 Current position - pos may be negative;\n 2 End of stream - pos usually negative.\nReturns the new absolute position.", + "_io.BytesIO.seekable" => "Returns True if the IO object can be seeked.", + "_io.BytesIO.tell" => "Current file position, an integer.", + "_io.BytesIO.truncate" => "Truncate the file to at most size bytes.\n\nSize defaults to the current file position, as returned by tell().\nThe current file position is unchanged. Returns the new size.", + "_io.BytesIO.writable" => "Returns True if the IO object can be written.", + "_io.BytesIO.write" => "Write bytes to file.\n\nReturn the number of bytes written.", + "_io.BytesIO.writelines" => "Write lines to the file.\n\nNote that newlines are not added. lines can be any iterable object\nproducing bytes-like objects. This is equivalent to calling write() for\neach element.", + "_io.FileIO" => "Open a file.\n\nThe mode can be 'r' (default), 'w', 'x' or 'a' for reading,\nwriting, exclusive creation or appending. The file will be created if it\ndoesn't exist when opened for writing or appending; it will be truncated\nwhen opened for writing. A FileExistsError will be raised if it already\nexists when opened for creating. Opening a file for creating implies\nwriting so this mode behaves in a similar way to 'w'.Add a '+' to the mode\nto allow simultaneous reading and writing. A custom opener can be used by\npassing a callable as *opener*. The underlying file descriptor for the file\nobject is then obtained by calling opener with (*name*, *flags*).\n*opener* must return an open file descriptor (passing os.open as *opener*\nresults in functionality similar to passing None).", + "_io.FileIO.__del__" => "Called when the instance is about to be destroyed.", + "_io.FileIO.__delattr__" => "Implement delattr(self, name).", + "_io.FileIO.__eq__" => "Return self==value.", + "_io.FileIO.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io.FileIO.__ge__" => "Return self>=value.", + "_io.FileIO.__getattribute__" => "Return getattr(self, name).", + "_io.FileIO.__gt__" => "Return self>value.", + "_io.FileIO.__hash__" => "Return hash(self).", + "_io.FileIO.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io.FileIO.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io.FileIO.__iter__" => "Implement iter(self).", + "_io.FileIO.__le__" => "Return self<=value.", + "_io.FileIO.__lt__" => "Return self<value.", + "_io.FileIO.__ne__" => "Return self!=value.", + "_io.FileIO.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io.FileIO.__next__" => "Implement next(self).", + "_io.FileIO.__reduce__" => "Helper for pickle.", + "_io.FileIO.__reduce_ex__" => "Helper for pickle.", + "_io.FileIO.__repr__" => "Return repr(self).", + "_io.FileIO.__setattr__" => "Implement setattr(self, name, value).", + "_io.FileIO.__sizeof__" => "Size of object in memory, in bytes.", + "_io.FileIO.__str__" => "Return str(self).", + "_io.FileIO.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io.FileIO.close" => "Close the file.\n\nA closed file cannot be used for further I/O operations. close() may be\ncalled more than once without error.", + "_io.FileIO.closed" => "True if the file is closed", + "_io.FileIO.closefd" => "True if the file descriptor will be closed by close().", + "_io.FileIO.fileno" => "Return the underlying file descriptor (an integer).", + "_io.FileIO.flush" => "Flush write buffers, if applicable.\n\nThis is not implemented for read-only and non-blocking streams.", + "_io.FileIO.isatty" => "True if the file is connected to a TTY device.", + "_io.FileIO.mode" => "String giving the file mode", + "_io.FileIO.read" => "Read at most size bytes, returned as bytes.\n\nOnly makes one system call, so less data may be returned than requested.\nIn non-blocking mode, returns None if no data is available.\nReturn an empty bytes object at EOF.", + "_io.FileIO.readable" => "True if file was opened in a read mode.", + "_io.FileIO.readall" => "Read all data from the file, returned as bytes.\n\nIn non-blocking mode, returns as much as is immediately available,\nor None if no data is available. Return an empty bytes object at EOF.", + "_io.FileIO.readinto" => "Same as RawIOBase.readinto().", + "_io.FileIO.readline" => "Read and return a line from the stream.\n\nIf size is specified, at most size bytes will be read.\n\nThe line terminator is always b'\\n' for binary files; for text\nfiles, the newlines argument to open can be used to select the line\nterminator(s) recognized.", + "_io.FileIO.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io.FileIO.seek" => "Move to new file position and return the file position.\n\nArgument offset is a byte count. Optional argument whence defaults to\nSEEK_SET or 0 (offset from start of file, offset should be >= 0); other values\nare SEEK_CUR or 1 (move relative to current position, positive or negative),\nand SEEK_END or 2 (move relative to end of file, usually negative, although\nmany platforms allow seeking beyond the end of a file).\n\nNote that not all file objects are seekable.", + "_io.FileIO.seekable" => "True if file supports random-access.", + "_io.FileIO.tell" => "Current file position.\n\nCan raise OSError for non seekable files.", + "_io.FileIO.truncate" => "Truncate the file to at most size bytes and return the truncated size.\n\nSize defaults to the current file position, as returned by tell().\nThe current file position is changed to the value of size.", + "_io.FileIO.writable" => "True if file was opened in a write mode.", + "_io.FileIO.write" => "Write buffer b to file, return number of bytes written.\n\nOnly makes one system call, so not all of the data may be written.\nThe number of bytes actually written is returned. In non-blocking mode,\nreturns None if the write would block.", + "_io.FileIO.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io.IncrementalNewlineDecoder" => "Codec used when reading a file in universal newlines mode.\n\nIt wraps another incremental decoder, translating \\r\\n and \\r into \\n.\nIt also records the types of newlines encountered. When used with\ntranslate=False, it ensures that the newline sequence is returned in\none piece. When used with decoder=None, it expects unicode strings as\ndecode input and translates newlines without first invoking an external\ndecoder.", + "_io.IncrementalNewlineDecoder.__delattr__" => "Implement delattr(self, name).", + "_io.IncrementalNewlineDecoder.__eq__" => "Return self==value.", + "_io.IncrementalNewlineDecoder.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io.IncrementalNewlineDecoder.__ge__" => "Return self>=value.", + "_io.IncrementalNewlineDecoder.__getattribute__" => "Return getattr(self, name).", + "_io.IncrementalNewlineDecoder.__getstate__" => "Helper for pickle.", + "_io.IncrementalNewlineDecoder.__gt__" => "Return self>value.", + "_io.IncrementalNewlineDecoder.__hash__" => "Return hash(self).", + "_io.IncrementalNewlineDecoder.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io.IncrementalNewlineDecoder.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io.IncrementalNewlineDecoder.__le__" => "Return self<=value.", + "_io.IncrementalNewlineDecoder.__lt__" => "Return self<value.", + "_io.IncrementalNewlineDecoder.__ne__" => "Return self!=value.", + "_io.IncrementalNewlineDecoder.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io.IncrementalNewlineDecoder.__reduce__" => "Helper for pickle.", + "_io.IncrementalNewlineDecoder.__reduce_ex__" => "Helper for pickle.", + "_io.IncrementalNewlineDecoder.__repr__" => "Return repr(self).", + "_io.IncrementalNewlineDecoder.__setattr__" => "Implement setattr(self, name, value).", + "_io.IncrementalNewlineDecoder.__sizeof__" => "Size of object in memory, in bytes.", + "_io.IncrementalNewlineDecoder.__str__" => "Return str(self).", + "_io.IncrementalNewlineDecoder.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io.StringIO" => "Text I/O implementation using an in-memory buffer.\n\nThe initial_value argument sets the value of object. The newline\nargument is like the one of TextIOWrapper's constructor.", + "_io.StringIO.__del__" => "Called when the instance is about to be destroyed.", + "_io.StringIO.__delattr__" => "Implement delattr(self, name).", + "_io.StringIO.__eq__" => "Return self==value.", + "_io.StringIO.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io.StringIO.__ge__" => "Return self>=value.", + "_io.StringIO.__getattribute__" => "Return getattr(self, name).", + "_io.StringIO.__gt__" => "Return self>value.", + "_io.StringIO.__hash__" => "Return hash(self).", + "_io.StringIO.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io.StringIO.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io.StringIO.__iter__" => "Implement iter(self).", + "_io.StringIO.__le__" => "Return self<=value.", + "_io.StringIO.__lt__" => "Return self<value.", + "_io.StringIO.__ne__" => "Return self!=value.", + "_io.StringIO.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io.StringIO.__next__" => "Implement next(self).", + "_io.StringIO.__reduce__" => "Helper for pickle.", + "_io.StringIO.__reduce_ex__" => "Helper for pickle.", + "_io.StringIO.__repr__" => "Return repr(self).", + "_io.StringIO.__setattr__" => "Implement setattr(self, name, value).", + "_io.StringIO.__sizeof__" => "Size of object in memory, in bytes.", + "_io.StringIO.__str__" => "Return str(self).", + "_io.StringIO.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io.StringIO.close" => "Close the IO object.\n\nAttempting any further operation after the object is closed\nwill raise a ValueError.\n\nThis method has no effect if the file is already closed.", + "_io.StringIO.detach" => "Separate the underlying buffer from the TextIOBase and return it.\n\nAfter the underlying buffer has been detached, the TextIO is in an unusable state.", + "_io.StringIO.encoding" => "Encoding of the text stream.\n\nSubclasses should override.", + "_io.StringIO.errors" => "The error setting of the decoder or encoder.\n\nSubclasses should override.", + "_io.StringIO.fileno" => "Return underlying file descriptor if one exists.\n\nRaise OSError if the IO object does not use a file descriptor.", + "_io.StringIO.flush" => "Flush write buffers, if applicable.\n\nThis is not implemented for read-only and non-blocking streams.", + "_io.StringIO.getvalue" => "Retrieve the entire contents of the object.", + "_io.StringIO.isatty" => "Return whether this is an 'interactive' stream.\n\nReturn False if it can't be determined.", + "_io.StringIO.read" => "Read at most size characters, returned as a string.\n\nIf the argument is negative or omitted, read until EOF\nis reached. Return an empty string at EOF.", + "_io.StringIO.readable" => "Returns True if the IO object can be read.", + "_io.StringIO.readline" => "Read until newline or EOF.\n\nReturns an empty string if EOF is hit immediately.", + "_io.StringIO.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io.StringIO.seek" => "Change stream position.\n\nSeek to character offset pos relative to position indicated by whence:\n 0 Start of stream (the default). pos should be >= 0;\n 1 Current position - pos must be 0;\n 2 End of stream - pos must be 0.\nReturns the new absolute position.", + "_io.StringIO.seekable" => "Returns True if the IO object can be seeked.", + "_io.StringIO.tell" => "Tell the current file position.", + "_io.StringIO.truncate" => "Truncate size to pos.\n\nThe pos argument defaults to the current file position, as\nreturned by tell(). The current file position is unchanged.\nReturns the new absolute position.", + "_io.StringIO.writable" => "Returns True if the IO object can be written.", + "_io.StringIO.write" => "Write string to file.\n\nReturns the number of characters written, which is always equal to\nthe length of the string.", + "_io.StringIO.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io.TextIOWrapper" => "Character and line based layer over a BufferedIOBase object, buffer.\n\nencoding gives the name of the encoding that the stream will be\ndecoded or encoded with. It defaults to locale.getencoding().\n\nerrors determines the strictness of encoding and decoding (see\nhelp(codecs.Codec) or the documentation for codecs.register) and\ndefaults to \"strict\".\n\nnewline controls how line endings are handled. It can be None, '',\n'\\n', '\\r', and '\\r\\n'. It works as follows:\n\n* On input, if newline is None, universal newlines mode is\n enabled. Lines in the input can end in '\\n', '\\r', or '\\r\\n', and\n these are translated into '\\n' before being returned to the\n caller. If it is '', universal newline mode is enabled, but line\n endings are returned to the caller untranslated. If it has any of\n the other legal values, input lines are only terminated by the given\n string, and the line ending is returned to the caller untranslated.\n\n* On output, if newline is None, any '\\n' characters written are\n translated to the system default line separator, os.linesep. If\n newline is '' or '\\n', no translation takes place. If newline is any\n of the other legal values, any '\\n' characters written are translated\n to the given string.\n\nIf line_buffering is True, a call to flush is implied when a call to\nwrite contains a newline character.", + "_io.TextIOWrapper.__del__" => "Called when the instance is about to be destroyed.", + "_io.TextIOWrapper.__delattr__" => "Implement delattr(self, name).", + "_io.TextIOWrapper.__eq__" => "Return self==value.", + "_io.TextIOWrapper.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io.TextIOWrapper.__ge__" => "Return self>=value.", + "_io.TextIOWrapper.__getattribute__" => "Return getattr(self, name).", + "_io.TextIOWrapper.__gt__" => "Return self>value.", + "_io.TextIOWrapper.__hash__" => "Return hash(self).", + "_io.TextIOWrapper.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io.TextIOWrapper.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io.TextIOWrapper.__iter__" => "Implement iter(self).", + "_io.TextIOWrapper.__le__" => "Return self<=value.", + "_io.TextIOWrapper.__lt__" => "Return self<value.", + "_io.TextIOWrapper.__ne__" => "Return self!=value.", + "_io.TextIOWrapper.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io.TextIOWrapper.__next__" => "Implement next(self).", + "_io.TextIOWrapper.__reduce__" => "Helper for pickle.", + "_io.TextIOWrapper.__reduce_ex__" => "Helper for pickle.", + "_io.TextIOWrapper.__repr__" => "Return repr(self).", + "_io.TextIOWrapper.__setattr__" => "Implement setattr(self, name, value).", + "_io.TextIOWrapper.__sizeof__" => "Size of object in memory, in bytes.", + "_io.TextIOWrapper.__str__" => "Return str(self).", + "_io.TextIOWrapper.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io.TextIOWrapper.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io.TextIOWrapper.reconfigure" => "Reconfigure the text stream with new parameters.\n\nThis also does an implicit stream flush.", + "_io.TextIOWrapper.seek" => "Set the stream position, and return the new stream position.\n\n cookie\n Zero or an opaque number returned by tell().\n whence\n The relative position to seek from.\n\nFour operations are supported, given by the following argument\ncombinations:\n\n- seek(0, SEEK_SET): Rewind to the start of the stream.\n- seek(cookie, SEEK_SET): Restore a previous position;\n 'cookie' must be a number returned by tell().\n- seek(0, SEEK_END): Fast-forward to the end of the stream.\n- seek(0, SEEK_CUR): Leave the current stream position unchanged.\n\nAny other argument combinations are invalid,\nand may raise exceptions.", + "_io.TextIOWrapper.tell" => "Return the stream position as an opaque number.\n\nThe return value of tell() can be given as input to seek(), to restore a\nprevious stream position.", + "_io.TextIOWrapper.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io._BufferedIOBase" => "Base class for buffered IO objects.\n\nThe main difference with RawIOBase is that the read() method\nsupports omitting the size argument, and does not have a default\nimplementation that defers to readinto().\n\nIn addition, read(), readinto() and write() may raise\nBlockingIOError if the underlying raw stream is in non-blocking\nmode and not ready; unlike their raw counterparts, they will never\nreturn None.\n\nA typical implementation should not inherit from a RawIOBase\nimplementation, but wrap one.", + "_io._BufferedIOBase.__del__" => "Called when the instance is about to be destroyed.", + "_io._BufferedIOBase.__delattr__" => "Implement delattr(self, name).", + "_io._BufferedIOBase.__eq__" => "Return self==value.", + "_io._BufferedIOBase.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io._BufferedIOBase.__ge__" => "Return self>=value.", + "_io._BufferedIOBase.__getattribute__" => "Return getattr(self, name).", + "_io._BufferedIOBase.__getstate__" => "Helper for pickle.", + "_io._BufferedIOBase.__gt__" => "Return self>value.", + "_io._BufferedIOBase.__hash__" => "Return hash(self).", + "_io._BufferedIOBase.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io._BufferedIOBase.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io._BufferedIOBase.__iter__" => "Implement iter(self).", + "_io._BufferedIOBase.__le__" => "Return self<=value.", + "_io._BufferedIOBase.__lt__" => "Return self<value.", + "_io._BufferedIOBase.__ne__" => "Return self!=value.", + "_io._BufferedIOBase.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io._BufferedIOBase.__next__" => "Implement next(self).", + "_io._BufferedIOBase.__reduce__" => "Helper for pickle.", + "_io._BufferedIOBase.__reduce_ex__" => "Helper for pickle.", + "_io._BufferedIOBase.__repr__" => "Return repr(self).", + "_io._BufferedIOBase.__setattr__" => "Implement setattr(self, name, value).", + "_io._BufferedIOBase.__sizeof__" => "Size of object in memory, in bytes.", + "_io._BufferedIOBase.__str__" => "Return str(self).", + "_io._BufferedIOBase.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io._BufferedIOBase.close" => "Flush and close the IO object.\n\nThis method has no effect if the file is already closed.", + "_io._BufferedIOBase.detach" => "Disconnect this buffer from its underlying raw stream and return it.\n\nAfter the raw stream has been detached, the buffer is in an unusable\nstate.", + "_io._BufferedIOBase.fileno" => "Return underlying file descriptor if one exists.\n\nRaise OSError if the IO object does not use a file descriptor.", + "_io._BufferedIOBase.flush" => "Flush write buffers, if applicable.\n\nThis is not implemented for read-only and non-blocking streams.", + "_io._BufferedIOBase.isatty" => "Return whether this is an 'interactive' stream.\n\nReturn False if it can't be determined.", + "_io._BufferedIOBase.read" => "Read and return up to n bytes.\n\nIf the size argument is omitted, None, or negative, read and\nreturn all data until EOF.\n\nIf the size argument is positive, and the underlying raw stream is\nnot 'interactive', multiple raw reads may be issued to satisfy\nthe byte count (unless EOF is reached first).\nHowever, for interactive raw streams (as well as sockets and pipes),\nat most one raw read will be issued, and a short result does not\nimply that EOF is imminent.\n\nReturn an empty bytes object on EOF.\n\nReturn None if the underlying raw stream was open in non-blocking\nmode and no data is available at the moment.", + "_io._BufferedIOBase.read1" => "Read and return up to size bytes, with at most one read() call to the underlying raw stream.\n\nReturn an empty bytes object on EOF.\nA short result does not imply that EOF is imminent.", + "_io._BufferedIOBase.readable" => "Return whether object was opened for reading.\n\nIf False, read() will raise OSError.", + "_io._BufferedIOBase.readline" => "Read and return a line from the stream.\n\nIf size is specified, at most size bytes will be read.\n\nThe line terminator is always b'\\n' for binary files; for text\nfiles, the newlines argument to open can be used to select the line\nterminator(s) recognized.", + "_io._BufferedIOBase.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io._BufferedIOBase.seek" => "Change the stream position to the given byte offset.\n\n offset\n The stream position, relative to 'whence'.\n whence\n The relative position to seek from.\n\nThe offset is interpreted relative to the position indicated by whence.\nValues for whence are:\n\n* os.SEEK_SET or 0 -- start of stream (the default); offset should be zero or positive\n* os.SEEK_CUR or 1 -- current stream position; offset may be negative\n* os.SEEK_END or 2 -- end of stream; offset is usually negative\n\nReturn the new absolute position.", + "_io._BufferedIOBase.seekable" => "Return whether object supports random access.\n\nIf False, seek(), tell() and truncate() will raise OSError.\nThis method may need to do a test seek().", + "_io._BufferedIOBase.tell" => "Return current stream position.", + "_io._BufferedIOBase.truncate" => "Truncate file to size bytes.\n\nFile pointer is left unchanged. Size defaults to the current IO position\nas reported by tell(). Return the new size.", + "_io._BufferedIOBase.writable" => "Return whether object was opened for writing.\n\nIf False, write() will raise OSError.", + "_io._BufferedIOBase.write" => "Write buffer b to the IO stream.\n\nReturn the number of bytes written, which is always\nthe length of b in bytes.\n\nRaise BlockingIOError if the buffer is full and the\nunderlying raw stream cannot accept more data at the moment.", + "_io._BufferedIOBase.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io._BytesIOBuffer.__buffer__" => "Return a buffer object that exposes the underlying memory of the object.", + "_io._BytesIOBuffer.__delattr__" => "Implement delattr(self, name).", + "_io._BytesIOBuffer.__eq__" => "Return self==value.", + "_io._BytesIOBuffer.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io._BytesIOBuffer.__ge__" => "Return self>=value.", + "_io._BytesIOBuffer.__getattribute__" => "Return getattr(self, name).", + "_io._BytesIOBuffer.__getstate__" => "Helper for pickle.", + "_io._BytesIOBuffer.__gt__" => "Return self>value.", + "_io._BytesIOBuffer.__hash__" => "Return hash(self).", + "_io._BytesIOBuffer.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io._BytesIOBuffer.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io._BytesIOBuffer.__le__" => "Return self<=value.", + "_io._BytesIOBuffer.__lt__" => "Return self<value.", + "_io._BytesIOBuffer.__ne__" => "Return self!=value.", + "_io._BytesIOBuffer.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io._BytesIOBuffer.__reduce__" => "Helper for pickle.", + "_io._BytesIOBuffer.__reduce_ex__" => "Helper for pickle.", + "_io._BytesIOBuffer.__release_buffer__" => "Release the buffer object that exposes the underlying memory of the object.", + "_io._BytesIOBuffer.__repr__" => "Return repr(self).", + "_io._BytesIOBuffer.__setattr__" => "Implement setattr(self, name, value).", + "_io._BytesIOBuffer.__sizeof__" => "Size of object in memory, in bytes.", + "_io._BytesIOBuffer.__str__" => "Return str(self).", + "_io._BytesIOBuffer.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io._IOBase" => "The abstract base class for all I/O classes.\n\nThis class provides dummy implementations for many methods that\nderived classes can override selectively; the default implementations\nrepresent a file that cannot be read, written or seeked.\n\nEven though IOBase does not declare read, readinto, or write because\ntheir signatures will vary, implementations and clients should\nconsider those methods part of the interface. Also, implementations\nmay raise UnsupportedOperation when operations they do not support are\ncalled.\n\nThe basic type used for binary data read from or written to a file is\nbytes. Other bytes-like objects are accepted as method arguments too.\nIn some cases (such as readinto), a writable object is required. Text\nI/O classes work with str data.\n\nNote that calling any method (except additional calls to close(),\nwhich are ignored) on a closed stream should raise a ValueError.\n\nIOBase (and its subclasses) support the iterator protocol, meaning\nthat an IOBase object can be iterated over yielding the lines in a\nstream.\n\nIOBase also supports the :keyword:`with` statement. In this example,\nfp is closed after the suite of the with statement is complete:\n\nwith open('spam.txt', 'r') as fp:\n fp.write('Spam and eggs!')", + "_io._IOBase.__del__" => "Called when the instance is about to be destroyed.", + "_io._IOBase.__delattr__" => "Implement delattr(self, name).", + "_io._IOBase.__eq__" => "Return self==value.", + "_io._IOBase.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io._IOBase.__ge__" => "Return self>=value.", + "_io._IOBase.__getattribute__" => "Return getattr(self, name).", + "_io._IOBase.__getstate__" => "Helper for pickle.", + "_io._IOBase.__gt__" => "Return self>value.", + "_io._IOBase.__hash__" => "Return hash(self).", + "_io._IOBase.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io._IOBase.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io._IOBase.__iter__" => "Implement iter(self).", + "_io._IOBase.__le__" => "Return self<=value.", + "_io._IOBase.__lt__" => "Return self<value.", + "_io._IOBase.__ne__" => "Return self!=value.", + "_io._IOBase.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io._IOBase.__next__" => "Implement next(self).", + "_io._IOBase.__reduce__" => "Helper for pickle.", + "_io._IOBase.__reduce_ex__" => "Helper for pickle.", + "_io._IOBase.__repr__" => "Return repr(self).", + "_io._IOBase.__setattr__" => "Implement setattr(self, name, value).", + "_io._IOBase.__sizeof__" => "Size of object in memory, in bytes.", + "_io._IOBase.__str__" => "Return str(self).", + "_io._IOBase.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io._IOBase.close" => "Flush and close the IO object.\n\nThis method has no effect if the file is already closed.", + "_io._IOBase.fileno" => "Return underlying file descriptor if one exists.\n\nRaise OSError if the IO object does not use a file descriptor.", + "_io._IOBase.flush" => "Flush write buffers, if applicable.\n\nThis is not implemented for read-only and non-blocking streams.", + "_io._IOBase.isatty" => "Return whether this is an 'interactive' stream.\n\nReturn False if it can't be determined.", + "_io._IOBase.readable" => "Return whether object was opened for reading.\n\nIf False, read() will raise OSError.", + "_io._IOBase.readline" => "Read and return a line from the stream.\n\nIf size is specified, at most size bytes will be read.\n\nThe line terminator is always b'\\n' for binary files; for text\nfiles, the newlines argument to open can be used to select the line\nterminator(s) recognized.", + "_io._IOBase.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io._IOBase.seek" => "Change the stream position to the given byte offset.\n\n offset\n The stream position, relative to 'whence'.\n whence\n The relative position to seek from.\n\nThe offset is interpreted relative to the position indicated by whence.\nValues for whence are:\n\n* os.SEEK_SET or 0 -- start of stream (the default); offset should be zero or positive\n* os.SEEK_CUR or 1 -- current stream position; offset may be negative\n* os.SEEK_END or 2 -- end of stream; offset is usually negative\n\nReturn the new absolute position.", + "_io._IOBase.seekable" => "Return whether object supports random access.\n\nIf False, seek(), tell() and truncate() will raise OSError.\nThis method may need to do a test seek().", + "_io._IOBase.tell" => "Return current stream position.", + "_io._IOBase.truncate" => "Truncate file to size bytes.\n\nFile pointer is left unchanged. Size defaults to the current IO position\nas reported by tell(). Return the new size.", + "_io._IOBase.writable" => "Return whether object was opened for writing.\n\nIf False, write() will raise OSError.", + "_io._IOBase.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io._RawIOBase" => "Base class for raw binary I/O.", + "_io._RawIOBase.__del__" => "Called when the instance is about to be destroyed.", + "_io._RawIOBase.__delattr__" => "Implement delattr(self, name).", + "_io._RawIOBase.__eq__" => "Return self==value.", + "_io._RawIOBase.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io._RawIOBase.__ge__" => "Return self>=value.", + "_io._RawIOBase.__getattribute__" => "Return getattr(self, name).", + "_io._RawIOBase.__getstate__" => "Helper for pickle.", + "_io._RawIOBase.__gt__" => "Return self>value.", + "_io._RawIOBase.__hash__" => "Return hash(self).", + "_io._RawIOBase.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io._RawIOBase.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io._RawIOBase.__iter__" => "Implement iter(self).", + "_io._RawIOBase.__le__" => "Return self<=value.", + "_io._RawIOBase.__lt__" => "Return self<value.", + "_io._RawIOBase.__ne__" => "Return self!=value.", + "_io._RawIOBase.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io._RawIOBase.__next__" => "Implement next(self).", + "_io._RawIOBase.__reduce__" => "Helper for pickle.", + "_io._RawIOBase.__reduce_ex__" => "Helper for pickle.", + "_io._RawIOBase.__repr__" => "Return repr(self).", + "_io._RawIOBase.__setattr__" => "Implement setattr(self, name, value).", + "_io._RawIOBase.__sizeof__" => "Size of object in memory, in bytes.", + "_io._RawIOBase.__str__" => "Return str(self).", + "_io._RawIOBase.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io._RawIOBase.close" => "Flush and close the IO object.\n\nThis method has no effect if the file is already closed.", + "_io._RawIOBase.fileno" => "Return underlying file descriptor if one exists.\n\nRaise OSError if the IO object does not use a file descriptor.", + "_io._RawIOBase.flush" => "Flush write buffers, if applicable.\n\nThis is not implemented for read-only and non-blocking streams.", + "_io._RawIOBase.isatty" => "Return whether this is an 'interactive' stream.\n\nReturn False if it can't be determined.", + "_io._RawIOBase.readable" => "Return whether object was opened for reading.\n\nIf False, read() will raise OSError.", + "_io._RawIOBase.readall" => "Read until EOF, using multiple read() call.", + "_io._RawIOBase.readline" => "Read and return a line from the stream.\n\nIf size is specified, at most size bytes will be read.\n\nThe line terminator is always b'\\n' for binary files; for text\nfiles, the newlines argument to open can be used to select the line\nterminator(s) recognized.", + "_io._RawIOBase.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io._RawIOBase.seek" => "Change the stream position to the given byte offset.\n\n offset\n The stream position, relative to 'whence'.\n whence\n The relative position to seek from.\n\nThe offset is interpreted relative to the position indicated by whence.\nValues for whence are:\n\n* os.SEEK_SET or 0 -- start of stream (the default); offset should be zero or positive\n* os.SEEK_CUR or 1 -- current stream position; offset may be negative\n* os.SEEK_END or 2 -- end of stream; offset is usually negative\n\nReturn the new absolute position.", + "_io._RawIOBase.seekable" => "Return whether object supports random access.\n\nIf False, seek(), tell() and truncate() will raise OSError.\nThis method may need to do a test seek().", + "_io._RawIOBase.tell" => "Return current stream position.", + "_io._RawIOBase.truncate" => "Truncate file to size bytes.\n\nFile pointer is left unchanged. Size defaults to the current IO position\nas reported by tell(). Return the new size.", + "_io._RawIOBase.writable" => "Return whether object was opened for writing.\n\nIf False, write() will raise OSError.", + "_io._RawIOBase.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io._TextIOBase" => "Base class for text I/O.\n\nThis class provides a character and line based interface to stream\nI/O. There is no readinto method because Python's character strings\nare immutable.", + "_io._TextIOBase.__del__" => "Called when the instance is about to be destroyed.", + "_io._TextIOBase.__delattr__" => "Implement delattr(self, name).", + "_io._TextIOBase.__eq__" => "Return self==value.", + "_io._TextIOBase.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io._TextIOBase.__ge__" => "Return self>=value.", + "_io._TextIOBase.__getattribute__" => "Return getattr(self, name).", + "_io._TextIOBase.__getstate__" => "Helper for pickle.", + "_io._TextIOBase.__gt__" => "Return self>value.", + "_io._TextIOBase.__hash__" => "Return hash(self).", + "_io._TextIOBase.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io._TextIOBase.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io._TextIOBase.__iter__" => "Implement iter(self).", + "_io._TextIOBase.__le__" => "Return self<=value.", + "_io._TextIOBase.__lt__" => "Return self<value.", + "_io._TextIOBase.__ne__" => "Return self!=value.", + "_io._TextIOBase.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io._TextIOBase.__next__" => "Implement next(self).", + "_io._TextIOBase.__reduce__" => "Helper for pickle.", + "_io._TextIOBase.__reduce_ex__" => "Helper for pickle.", + "_io._TextIOBase.__repr__" => "Return repr(self).", + "_io._TextIOBase.__setattr__" => "Implement setattr(self, name, value).", + "_io._TextIOBase.__sizeof__" => "Size of object in memory, in bytes.", + "_io._TextIOBase.__str__" => "Return str(self).", + "_io._TextIOBase.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io._TextIOBase.close" => "Flush and close the IO object.\n\nThis method has no effect if the file is already closed.", + "_io._TextIOBase.detach" => "Separate the underlying buffer from the TextIOBase and return it.\n\nAfter the underlying buffer has been detached, the TextIO is in an unusable state.", + "_io._TextIOBase.encoding" => "Encoding of the text stream.\n\nSubclasses should override.", + "_io._TextIOBase.errors" => "The error setting of the decoder or encoder.\n\nSubclasses should override.", + "_io._TextIOBase.fileno" => "Return underlying file descriptor if one exists.\n\nRaise OSError if the IO object does not use a file descriptor.", + "_io._TextIOBase.flush" => "Flush write buffers, if applicable.\n\nThis is not implemented for read-only and non-blocking streams.", + "_io._TextIOBase.isatty" => "Return whether this is an 'interactive' stream.\n\nReturn False if it can't be determined.", + "_io._TextIOBase.newlines" => "Line endings translated so far.\n\nOnly line endings translated during reading are considered.\n\nSubclasses should override.", + "_io._TextIOBase.read" => "Read at most size characters from stream.\n\nRead from underlying buffer until we have size characters or we hit EOF.\nIf size is negative or omitted, read until EOF.", + "_io._TextIOBase.readable" => "Return whether object was opened for reading.\n\nIf False, read() will raise OSError.", + "_io._TextIOBase.readline" => "Read until newline or EOF.\n\nReturn an empty string if EOF is hit immediately.\nIf size is specified, at most size characters will be read.", + "_io._TextIOBase.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io._TextIOBase.seek" => "Change the stream position to the given byte offset.\n\n offset\n The stream position, relative to 'whence'.\n whence\n The relative position to seek from.\n\nThe offset is interpreted relative to the position indicated by whence.\nValues for whence are:\n\n* os.SEEK_SET or 0 -- start of stream (the default); offset should be zero or positive\n* os.SEEK_CUR or 1 -- current stream position; offset may be negative\n* os.SEEK_END or 2 -- end of stream; offset is usually negative\n\nReturn the new absolute position.", + "_io._TextIOBase.seekable" => "Return whether object supports random access.\n\nIf False, seek(), tell() and truncate() will raise OSError.\nThis method may need to do a test seek().", + "_io._TextIOBase.tell" => "Return current stream position.", + "_io._TextIOBase.truncate" => "Truncate file to size bytes.\n\nFile pointer is left unchanged. Size defaults to the current IO position\nas reported by tell(). Return the new size.", + "_io._TextIOBase.writable" => "Return whether object was opened for writing.\n\nIf False, write() will raise OSError.", + "_io._TextIOBase.write" => "Write string s to stream.\n\nReturn the number of characters written\n(which is always equal to the length of the string).", + "_io._TextIOBase.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io._WindowsConsoleIO" => "Open a console buffer by file descriptor.\n\nThe mode can be 'rb' (default), or 'wb' for reading or writing bytes. All\nother mode characters will be ignored. Mode 'b' will be assumed if it is\nomitted. The *opener* parameter is always ignored.", + "_io._WindowsConsoleIO.__del__" => "Called when the instance is about to be destroyed.", + "_io._WindowsConsoleIO.__delattr__" => "Implement delattr(self, name).", + "_io._WindowsConsoleIO.__eq__" => "Return self==value.", + "_io._WindowsConsoleIO.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_io._WindowsConsoleIO.__ge__" => "Return self>=value.", + "_io._WindowsConsoleIO.__getattribute__" => "Return getattr(self, name).", + "_io._WindowsConsoleIO.__getstate__" => "Helper for pickle.", + "_io._WindowsConsoleIO.__gt__" => "Return self>value.", + "_io._WindowsConsoleIO.__hash__" => "Return hash(self).", + "_io._WindowsConsoleIO.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_io._WindowsConsoleIO.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_io._WindowsConsoleIO.__iter__" => "Implement iter(self).", + "_io._WindowsConsoleIO.__le__" => "Return self<=value.", + "_io._WindowsConsoleIO.__lt__" => "Return self<value.", + "_io._WindowsConsoleIO.__ne__" => "Return self!=value.", + "_io._WindowsConsoleIO.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_io._WindowsConsoleIO.__next__" => "Implement next(self).", + "_io._WindowsConsoleIO.__reduce__" => "Helper for pickle.", + "_io._WindowsConsoleIO.__reduce_ex__" => "Helper for pickle.", + "_io._WindowsConsoleIO.__repr__" => "Return repr(self).", + "_io._WindowsConsoleIO.__setattr__" => "Implement setattr(self, name, value).", + "_io._WindowsConsoleIO.__sizeof__" => "Size of object in memory, in bytes.", + "_io._WindowsConsoleIO.__str__" => "Return str(self).", + "_io._WindowsConsoleIO.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_io._WindowsConsoleIO.close" => "Close the console object.\n\nA closed console object cannot be used for further I/O operations.\nclose() may be called more than once without error.", + "_io._WindowsConsoleIO.closed" => "True if the file is closed", + "_io._WindowsConsoleIO.closefd" => "True if the file descriptor will be closed by close().", + "_io._WindowsConsoleIO.fileno" => "Return the underlying file descriptor (an integer).", + "_io._WindowsConsoleIO.flush" => "Flush write buffers, if applicable.\n\nThis is not implemented for read-only and non-blocking streams.", + "_io._WindowsConsoleIO.isatty" => "Always True.", + "_io._WindowsConsoleIO.mode" => "String giving the file mode", + "_io._WindowsConsoleIO.read" => "Read at most size bytes, returned as bytes.\n\nOnly makes one system call when size is a positive integer,\nso less data may be returned than requested.\nReturn an empty bytes object at EOF.", + "_io._WindowsConsoleIO.readable" => "True if console is an input buffer.", + "_io._WindowsConsoleIO.readall" => "Read all data from the console, returned as bytes.\n\nReturn an empty bytes object at EOF.", + "_io._WindowsConsoleIO.readinto" => "Same as RawIOBase.readinto().", + "_io._WindowsConsoleIO.readline" => "Read and return a line from the stream.\n\nIf size is specified, at most size bytes will be read.\n\nThe line terminator is always b'\\n' for binary files; for text\nfiles, the newlines argument to open can be used to select the line\nterminator(s) recognized.", + "_io._WindowsConsoleIO.readlines" => "Return a list of lines from the stream.\n\nhint can be specified to control the number of lines read: no more\nlines will be read if the total size (in bytes/characters) of all\nlines so far exceeds hint.", + "_io._WindowsConsoleIO.seek" => "Change the stream position to the given byte offset.\n\n offset\n The stream position, relative to 'whence'.\n whence\n The relative position to seek from.\n\nThe offset is interpreted relative to the position indicated by whence.\nValues for whence are:\n\n* os.SEEK_SET or 0 -- start of stream (the default); offset should be zero or positive\n* os.SEEK_CUR or 1 -- current stream position; offset may be negative\n* os.SEEK_END or 2 -- end of stream; offset is usually negative\n\nReturn the new absolute position.", + "_io._WindowsConsoleIO.seekable" => "Return whether object supports random access.\n\nIf False, seek(), tell() and truncate() will raise OSError.\nThis method may need to do a test seek().", + "_io._WindowsConsoleIO.tell" => "Return current stream position.", + "_io._WindowsConsoleIO.truncate" => "Truncate file to size bytes.\n\nFile pointer is left unchanged. Size defaults to the current IO position\nas reported by tell(). Return the new size.", + "_io._WindowsConsoleIO.writable" => "True if console is an output buffer.", + "_io._WindowsConsoleIO.write" => "Write buffer b to file, return number of bytes written.\n\nOnly makes one system call, so not all of the data may be written.\nThe number of bytes actually written is returned.", + "_io._WindowsConsoleIO.writelines" => "Write a list of lines to stream.\n\nLine separators are not added, so it is usual for each of the\nlines provided to have a line separator at the end.", + "_io.open" => "Open file and return a stream. Raise OSError upon failure.\n\nfile is either a text or byte string giving the name (and the path\nif the file isn't in the current working directory) of the file to\nbe opened or an integer file descriptor of the file to be\nwrapped. (If a file descriptor is given, it is closed when the\nreturned I/O object is closed, unless closefd is set to False.)\n\nmode is an optional string that specifies the mode in which the file\nis opened. It defaults to 'r' which means open for reading in text\nmode. Other common values are 'w' for writing (truncating the file if\nit already exists), 'x' for creating and writing to a new file, and\n'a' for appending (which on some Unix systems, means that all writes\nappend to the end of the file regardless of the current seek position).\nIn text mode, if encoding is not specified the encoding used is platform\ndependent: locale.getencoding() is called to get the current locale encoding.\n(For reading and writing raw bytes use binary mode and leave encoding\nunspecified.) The available modes are:\n\n========= ===============================================================\nCharacter Meaning\n--------- ---------------------------------------------------------------\n'r' open for reading (default)\n'w' open for writing, truncating the file first\n'x' create a new file and open it for writing\n'a' open for writing, appending to the end of the file if it exists\n'b' binary mode\n't' text mode (default)\n'+' open a disk file for updating (reading and writing)\n========= ===============================================================\n\nThe default mode is 'rt' (open for reading text). For binary random\naccess, the mode 'w+b' opens and truncates the file to 0 bytes, while\n'r+b' opens the file without truncation. The 'x' mode implies 'w' and\nraises an `FileExistsError` if the file already exists.\n\nPython distinguishes between files opened in binary and text modes,\neven when the underlying operating system doesn't. Files opened in\nbinary mode (appending 'b' to the mode argument) return contents as\nbytes objects without any decoding. In text mode (the default, or when\n't' is appended to the mode argument), the contents of the file are\nreturned as strings, the bytes having been first decoded using a\nplatform-dependent encoding or using the specified encoding if given.\n\nbuffering is an optional integer used to set the buffering policy.\nPass 0 to switch buffering off (only allowed in binary mode), 1 to select\nline buffering (only usable in text mode), and an integer > 1 to indicate\nthe size of a fixed-size chunk buffer. When no buffering argument is\ngiven, the default buffering policy works as follows:\n\n* Binary files are buffered in fixed-size chunks; the size of the buffer\n is chosen using a heuristic trying to determine the underlying device's\n \"block size\" and falling back on `io.DEFAULT_BUFFER_SIZE`.\n On many systems, the buffer will typically be 4096 or 8192 bytes long.\n\n* \"Interactive\" text files (files for which isatty() returns True)\n use line buffering. Other text files use the policy described above\n for binary files.\n\nencoding is the name of the encoding used to decode or encode the\nfile. This should only be used in text mode. The default encoding is\nplatform dependent, but any encoding supported by Python can be\npassed. See the codecs module for the list of supported encodings.\n\nerrors is an optional string that specifies how encoding errors are to\nbe handled---this argument should not be used in binary mode. Pass\n'strict' to raise a ValueError exception if there is an encoding error\n(the default of None has the same effect), or pass 'ignore' to ignore\nerrors. (Note that ignoring encoding errors can lead to data loss.)\nSee the documentation for codecs.register or run 'help(codecs.Codec)'\nfor a list of the permitted encoding error strings.\n\nnewline controls how universal newlines works (it only applies to text\nmode). It can be None, '', '\\n', '\\r', and '\\r\\n'. It works as\nfollows:\n\n* On input, if newline is None, universal newlines mode is\n enabled. Lines in the input can end in '\\n', '\\r', or '\\r\\n', and\n these are translated into '\\n' before being returned to the\n caller. If it is '', universal newline mode is enabled, but line\n endings are returned to the caller untranslated. If it has any of\n the other legal values, input lines are only terminated by the given\n string, and the line ending is returned to the caller untranslated.\n\n* On output, if newline is None, any '\\n' characters written are\n translated to the system default line separator, os.linesep. If\n newline is '' or '\\n', no translation takes place. If newline is any\n of the other legal values, any '\\n' characters written are translated\n to the given string.\n\nIf closefd is False, the underlying file descriptor will be kept open\nwhen the file is closed. This does not work when a file name is given\nand must be True in that case.\n\nA custom opener can be used by passing a callable as *opener*. The\nunderlying file descriptor for the file object is then obtained by\ncalling *opener* with (*file*, *flags*). *opener* must return an open\nfile descriptor (passing os.open as *opener* results in functionality\nsimilar to passing None).\n\nopen() returns a file object whose type depends on the mode, and\nthrough which the standard file operations such as reading and writing\nare performed. When open() is used to open a file in a text mode ('w',\n'r', 'wt', 'rt', etc.), it returns a TextIOWrapper. When used to open\na file in a binary mode, the returned class varies: in read binary\nmode, it returns a BufferedReader; in write binary and append binary\nmodes, it returns a BufferedWriter, and in read/write mode, it returns\na BufferedRandom.\n\nIt is also possible to use a string or bytearray as a file for both\nreading and writing. For strings StringIO can be used like a file\nopened in a text mode, and for bytes a BytesIO can be used like a file\nopened in a binary mode.", + "_io.open_code" => "Opens the provided file with the intent to import the contents.\n\nThis may perform extra validation beyond open(), but is otherwise interchangeable\nwith calling open(path, 'rb').", + "_io.text_encoding" => "A helper function to choose the text encoding.\n\nWhen encoding is not None, this function returns it.\nOtherwise, this function returns the default text encoding\n(i.e. \"locale\" or \"utf-8\" depends on UTF-8 mode).\n\nThis function emits an EncodingWarning if encoding is None and\nsys.flags.warn_default_encoding is true.\n\nThis can be used in APIs with an encoding=None parameter.\nHowever, please consider using encoding=\"utf-8\" for new APIs.", + "_json" => "json speedups", + "_json.encode_basestring" => "encode_basestring(string) -> string\n\nReturn a JSON representation of a Python string", + "_json.encode_basestring_ascii" => "encode_basestring_ascii(string) -> string\n\nReturn an ASCII-only JSON representation of a Python string", + "_json.make_encoder" => "Encoder(markers, default, encoder, indent, key_separator, item_separator, sort_keys, skipkeys, allow_nan)", + "_json.make_encoder.__call__" => "Call self as a function.", + "_json.make_encoder.__delattr__" => "Implement delattr(self, name).", + "_json.make_encoder.__eq__" => "Return self==value.", + "_json.make_encoder.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_json.make_encoder.__ge__" => "Return self>=value.", + "_json.make_encoder.__getattribute__" => "Return getattr(self, name).", + "_json.make_encoder.__getstate__" => "Helper for pickle.", + "_json.make_encoder.__gt__" => "Return self>value.", + "_json.make_encoder.__hash__" => "Return hash(self).", + "_json.make_encoder.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_json.make_encoder.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_json.make_encoder.__le__" => "Return self<=value.", + "_json.make_encoder.__lt__" => "Return self<value.", + "_json.make_encoder.__ne__" => "Return self!=value.", + "_json.make_encoder.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_json.make_encoder.__reduce__" => "Helper for pickle.", + "_json.make_encoder.__reduce_ex__" => "Helper for pickle.", + "_json.make_encoder.__repr__" => "Return repr(self).", + "_json.make_encoder.__setattr__" => "Implement setattr(self, name, value).", + "_json.make_encoder.__sizeof__" => "Size of object in memory, in bytes.", + "_json.make_encoder.__str__" => "Return str(self).", + "_json.make_encoder.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_json.make_encoder.default" => "default", + "_json.make_encoder.encoder" => "encoder", + "_json.make_encoder.indent" => "indent", + "_json.make_encoder.item_separator" => "item_separator", + "_json.make_encoder.key_separator" => "key_separator", + "_json.make_encoder.markers" => "markers", + "_json.make_encoder.skipkeys" => "skipkeys", + "_json.make_encoder.sort_keys" => "sort_keys", + "_json.make_scanner" => "JSON scanner object", + "_json.make_scanner.__call__" => "Call self as a function.", + "_json.make_scanner.__delattr__" => "Implement delattr(self, name).", + "_json.make_scanner.__eq__" => "Return self==value.", + "_json.make_scanner.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_json.make_scanner.__ge__" => "Return self>=value.", + "_json.make_scanner.__getattribute__" => "Return getattr(self, name).", + "_json.make_scanner.__getstate__" => "Helper for pickle.", + "_json.make_scanner.__gt__" => "Return self>value.", + "_json.make_scanner.__hash__" => "Return hash(self).", + "_json.make_scanner.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_json.make_scanner.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_json.make_scanner.__le__" => "Return self<=value.", + "_json.make_scanner.__lt__" => "Return self<value.", + "_json.make_scanner.__ne__" => "Return self!=value.", + "_json.make_scanner.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_json.make_scanner.__reduce__" => "Helper for pickle.", + "_json.make_scanner.__reduce_ex__" => "Helper for pickle.", + "_json.make_scanner.__repr__" => "Return repr(self).", + "_json.make_scanner.__setattr__" => "Implement setattr(self, name, value).", + "_json.make_scanner.__sizeof__" => "Size of object in memory, in bytes.", + "_json.make_scanner.__str__" => "Return str(self).", + "_json.make_scanner.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_json.make_scanner.object_hook" => "object_hook", + "_json.make_scanner.parse_constant" => "parse_constant", + "_json.make_scanner.parse_float" => "parse_float", + "_json.make_scanner.parse_int" => "parse_int", + "_json.make_scanner.strict" => "strict", + "_json.scanstring" => "scanstring(string, end, strict=True) -> (string, end)\n\nScan the string s for a JSON string. End is the index of the\ncharacter in s after the quote that started the JSON string.\nUnescapes all valid JSON string escape sequences and raises ValueError\non attempt to decode an invalid string. If strict is False then literal\ncontrol characters are allowed in the string.\n\nReturns a tuple of the decoded string and the index of the character in s\nafter the end quote.", + "_locale" => "Support for POSIX locales.", + "_locale.bind_textdomain_codeset" => "Bind the C library's domain to codeset.", + "_locale.bindtextdomain" => "Bind the C library's domain to dir.", + "_locale.dcgettext" => "Return translation of msg in domain and category.", + "_locale.dgettext" => "dgettext(domain, msg) -> string\n\nReturn translation of msg in domain.", + "_locale.getencoding" => "Get the current locale encoding.", + "_locale.gettext" => "gettext(msg) -> string\n\nReturn translation of msg.", + "_locale.localeconv" => "Returns numeric and monetary locale-specific parameters.", + "_locale.nl_langinfo" => "Return the value for the locale information associated with key.", + "_locale.setlocale" => "Activates/queries locale processing.", + "_locale.strcoll" => "Compares two strings according to the locale.", + "_locale.strxfrm" => "Return a string that can be used as a key for locale-aware comparisons.", + "_locale.textdomain" => "Set the C library's textdmain to domain, returning the new domain.", + "_lsprof" => "Fast profiler", + "_lsprof.Profiler" => "Profiler(timer=None, timeunit=None, subcalls=True, builtins=True)\n\nBuilds a profiler object using the specified timer function.\nThe default timer is a fast built-in one based on real time.\nFor custom timer functions returning integers, timeunit can\nbe a float specifying a scale (i.e. how long each integer unit\nis, in seconds).", + "_lsprof.Profiler.__delattr__" => "Implement delattr(self, name).", + "_lsprof.Profiler.__eq__" => "Return self==value.", + "_lsprof.Profiler.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_lsprof.Profiler.__ge__" => "Return self>=value.", + "_lsprof.Profiler.__getattribute__" => "Return getattr(self, name).", + "_lsprof.Profiler.__getstate__" => "Helper for pickle.", + "_lsprof.Profiler.__gt__" => "Return self>value.", + "_lsprof.Profiler.__hash__" => "Return hash(self).", + "_lsprof.Profiler.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_lsprof.Profiler.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_lsprof.Profiler.__le__" => "Return self<=value.", + "_lsprof.Profiler.__lt__" => "Return self<value.", + "_lsprof.Profiler.__ne__" => "Return self!=value.", + "_lsprof.Profiler.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_lsprof.Profiler.__reduce__" => "Helper for pickle.", + "_lsprof.Profiler.__reduce_ex__" => "Helper for pickle.", + "_lsprof.Profiler.__repr__" => "Return repr(self).", + "_lsprof.Profiler.__setattr__" => "Implement setattr(self, name, value).", + "_lsprof.Profiler.__sizeof__" => "Size of object in memory, in bytes.", + "_lsprof.Profiler.__str__" => "Return str(self).", + "_lsprof.Profiler.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_lsprof.Profiler.clear" => "clear()\n\nClear all profiling information collected so far.", + "_lsprof.Profiler.disable" => "disable()\n\nStop collecting profiling information.", + "_lsprof.Profiler.enable" => "enable(subcalls=True, builtins=True)\n\nStart collecting profiling information.\nIf 'subcalls' is True, also records for each function\nstatistics separated according to its current caller.\nIf 'builtins' is True, records the time spent in\nbuilt-in functions separately from their caller.", + "_lsprof.Profiler.getstats" => "list of profiler_entry objects.\n\ngetstats() -> list of profiler_entry objects\n\nReturn all information collected by the profiler.\nEach profiler_entry is a tuple-like object with the\nfollowing attributes:\n\n code code object\n callcount how many times this was called\n reccallcount how many times called recursively\n totaltime total time in this entry\n inlinetime inline time in this entry (not in subcalls)\n calls details of the calls\n\nThe calls attribute is either None or a list of\nprofiler_subentry objects:\n\n code called code object\n callcount how many times this is called\n reccallcount how many times this is called recursively\n totaltime total time spent in this call\n inlinetime inline time (not in further subcalls)", + "_lsprof.profiler_entry.__add__" => "Return self+value.", + "_lsprof.profiler_entry.__class_getitem__" => "See PEP 585", + "_lsprof.profiler_entry.__contains__" => "Return bool(key in self).", + "_lsprof.profiler_entry.__delattr__" => "Implement delattr(self, name).", + "_lsprof.profiler_entry.__eq__" => "Return self==value.", + "_lsprof.profiler_entry.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_lsprof.profiler_entry.__ge__" => "Return self>=value.", + "_lsprof.profiler_entry.__getattribute__" => "Return getattr(self, name).", + "_lsprof.profiler_entry.__getitem__" => "Return self[key].", + "_lsprof.profiler_entry.__getstate__" => "Helper for pickle.", + "_lsprof.profiler_entry.__gt__" => "Return self>value.", + "_lsprof.profiler_entry.__hash__" => "Return hash(self).", + "_lsprof.profiler_entry.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_lsprof.profiler_entry.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_lsprof.profiler_entry.__iter__" => "Implement iter(self).", + "_lsprof.profiler_entry.__le__" => "Return self<=value.", + "_lsprof.profiler_entry.__len__" => "Return len(self).", + "_lsprof.profiler_entry.__lt__" => "Return self<value.", + "_lsprof.profiler_entry.__mul__" => "Return self*value.", + "_lsprof.profiler_entry.__ne__" => "Return self!=value.", + "_lsprof.profiler_entry.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_lsprof.profiler_entry.__reduce_ex__" => "Helper for pickle.", + "_lsprof.profiler_entry.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "_lsprof.profiler_entry.__repr__" => "Return repr(self).", + "_lsprof.profiler_entry.__rmul__" => "Return value*self.", + "_lsprof.profiler_entry.__setattr__" => "Implement setattr(self, name, value).", + "_lsprof.profiler_entry.__sizeof__" => "Size of object in memory, in bytes.", + "_lsprof.profiler_entry.__str__" => "Return str(self).", + "_lsprof.profiler_entry.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_lsprof.profiler_entry.callcount" => "how many times this was called", + "_lsprof.profiler_entry.calls" => "details of the calls", + "_lsprof.profiler_entry.code" => "code object or built-in function name", + "_lsprof.profiler_entry.count" => "Return number of occurrences of value.", + "_lsprof.profiler_entry.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "_lsprof.profiler_entry.inlinetime" => "inline time in this entry (not in subcalls)", + "_lsprof.profiler_entry.reccallcount" => "how many times called recursively", + "_lsprof.profiler_entry.totaltime" => "total time in this entry", + "_lsprof.profiler_subentry.__add__" => "Return self+value.", + "_lsprof.profiler_subentry.__class_getitem__" => "See PEP 585", + "_lsprof.profiler_subentry.__contains__" => "Return bool(key in self).", + "_lsprof.profiler_subentry.__delattr__" => "Implement delattr(self, name).", + "_lsprof.profiler_subentry.__eq__" => "Return self==value.", + "_lsprof.profiler_subentry.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_lsprof.profiler_subentry.__ge__" => "Return self>=value.", + "_lsprof.profiler_subentry.__getattribute__" => "Return getattr(self, name).", + "_lsprof.profiler_subentry.__getitem__" => "Return self[key].", + "_lsprof.profiler_subentry.__getstate__" => "Helper for pickle.", + "_lsprof.profiler_subentry.__gt__" => "Return self>value.", + "_lsprof.profiler_subentry.__hash__" => "Return hash(self).", + "_lsprof.profiler_subentry.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_lsprof.profiler_subentry.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_lsprof.profiler_subentry.__iter__" => "Implement iter(self).", + "_lsprof.profiler_subentry.__le__" => "Return self<=value.", + "_lsprof.profiler_subentry.__len__" => "Return len(self).", + "_lsprof.profiler_subentry.__lt__" => "Return self<value.", + "_lsprof.profiler_subentry.__mul__" => "Return self*value.", + "_lsprof.profiler_subentry.__ne__" => "Return self!=value.", + "_lsprof.profiler_subentry.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_lsprof.profiler_subentry.__reduce_ex__" => "Helper for pickle.", + "_lsprof.profiler_subentry.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "_lsprof.profiler_subentry.__repr__" => "Return repr(self).", + "_lsprof.profiler_subentry.__rmul__" => "Return value*self.", + "_lsprof.profiler_subentry.__setattr__" => "Implement setattr(self, name, value).", + "_lsprof.profiler_subentry.__sizeof__" => "Size of object in memory, in bytes.", + "_lsprof.profiler_subentry.__str__" => "Return str(self).", + "_lsprof.profiler_subentry.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_lsprof.profiler_subentry.callcount" => "how many times this is called", + "_lsprof.profiler_subentry.code" => "called code object or built-in function name", + "_lsprof.profiler_subentry.count" => "Return number of occurrences of value.", + "_lsprof.profiler_subentry.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "_lsprof.profiler_subentry.inlinetime" => "inline time (not in further subcalls)", + "_lsprof.profiler_subentry.reccallcount" => "how many times this is called recursively", + "_lsprof.profiler_subentry.totaltime" => "total time spent in this call", + "_lzma.LZMACompressor" => "LZMACompressor(format=FORMAT_XZ, check=-1, preset=None, filters=None)\n\nCreate a compressor object for compressing data incrementally.\n\nformat specifies the container format to use for the output. This can\nbe FORMAT_XZ (default), FORMAT_ALONE, or FORMAT_RAW.\n\ncheck specifies the integrity check to use. For FORMAT_XZ, the default\nis CHECK_CRC64. FORMAT_ALONE and FORMAT_RAW do not support integrity\nchecks; for these formats, check must be omitted, or be CHECK_NONE.\n\nThe settings used by the compressor can be specified either as a\npreset compression level (with the 'preset' argument), or in detail\nas a custom filter chain (with the 'filters' argument). For FORMAT_XZ\nand FORMAT_ALONE, the default is to use the PRESET_DEFAULT preset\nlevel. For FORMAT_RAW, the caller must always specify a filter chain;\nthe raw compressor does not support preset compression levels.\n\npreset (if provided) should be an integer in the range 0-9, optionally\nOR-ed with the constant PRESET_EXTREME.\n\nfilters (if provided) should be a sequence of dicts. Each dict should\nhave an entry for \"id\" indicating the ID of the filter, plus\nadditional entries for options to the filter.\n\nFor one-shot compression, use the compress() function instead.", + "_lzma.LZMACompressor.__delattr__" => "Implement delattr(self, name).", + "_lzma.LZMACompressor.__eq__" => "Return self==value.", + "_lzma.LZMACompressor.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_lzma.LZMACompressor.__ge__" => "Return self>=value.", + "_lzma.LZMACompressor.__getattribute__" => "Return getattr(self, name).", + "_lzma.LZMACompressor.__getstate__" => "Helper for pickle.", + "_lzma.LZMACompressor.__gt__" => "Return self>value.", + "_lzma.LZMACompressor.__hash__" => "Return hash(self).", + "_lzma.LZMACompressor.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_lzma.LZMACompressor.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_lzma.LZMACompressor.__le__" => "Return self<=value.", + "_lzma.LZMACompressor.__lt__" => "Return self<value.", + "_lzma.LZMACompressor.__ne__" => "Return self!=value.", + "_lzma.LZMACompressor.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_lzma.LZMACompressor.__reduce__" => "Helper for pickle.", + "_lzma.LZMACompressor.__reduce_ex__" => "Helper for pickle.", + "_lzma.LZMACompressor.__repr__" => "Return repr(self).", + "_lzma.LZMACompressor.__setattr__" => "Implement setattr(self, name, value).", + "_lzma.LZMACompressor.__sizeof__" => "Size of object in memory, in bytes.", + "_lzma.LZMACompressor.__str__" => "Return str(self).", + "_lzma.LZMACompressor.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_lzma.LZMACompressor.compress" => "Provide data to the compressor object.\n\nReturns a chunk of compressed data if possible, or b'' otherwise.\n\nWhen you have finished providing data to the compressor, call the\nflush() method to finish the compression process.", + "_lzma.LZMACompressor.flush" => "Finish the compression process.\n\nReturns the compressed data left in internal buffers.\n\nThe compressor object may not be used after this method is called.", + "_lzma.LZMADecompressor" => "Create a decompressor object for decompressing data incrementally.\n\n format\n Specifies the container format of the input stream. If this is\n FORMAT_AUTO (the default), the decompressor will automatically detect\n whether the input is FORMAT_XZ or FORMAT_ALONE. Streams created with\n FORMAT_RAW cannot be autodetected.\n memlimit\n Limit the amount of memory used by the decompressor. This will cause\n decompression to fail if the input cannot be decompressed within the\n given limit.\n filters\n A custom filter chain. This argument is required for FORMAT_RAW, and\n not accepted with any other format. When provided, this should be a\n sequence of dicts, each indicating the ID and options for a single\n filter.\n\nFor one-shot decompression, use the decompress() function instead.", + "_lzma.LZMADecompressor.__delattr__" => "Implement delattr(self, name).", + "_lzma.LZMADecompressor.__eq__" => "Return self==value.", + "_lzma.LZMADecompressor.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_lzma.LZMADecompressor.__ge__" => "Return self>=value.", + "_lzma.LZMADecompressor.__getattribute__" => "Return getattr(self, name).", + "_lzma.LZMADecompressor.__getstate__" => "Helper for pickle.", + "_lzma.LZMADecompressor.__gt__" => "Return self>value.", + "_lzma.LZMADecompressor.__hash__" => "Return hash(self).", + "_lzma.LZMADecompressor.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_lzma.LZMADecompressor.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_lzma.LZMADecompressor.__le__" => "Return self<=value.", + "_lzma.LZMADecompressor.__lt__" => "Return self<value.", + "_lzma.LZMADecompressor.__ne__" => "Return self!=value.", + "_lzma.LZMADecompressor.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_lzma.LZMADecompressor.__reduce__" => "Helper for pickle.", + "_lzma.LZMADecompressor.__reduce_ex__" => "Helper for pickle.", + "_lzma.LZMADecompressor.__repr__" => "Return repr(self).", + "_lzma.LZMADecompressor.__setattr__" => "Implement setattr(self, name, value).", + "_lzma.LZMADecompressor.__sizeof__" => "Size of object in memory, in bytes.", + "_lzma.LZMADecompressor.__str__" => "Return str(self).", + "_lzma.LZMADecompressor.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_lzma.LZMADecompressor.check" => "ID of the integrity check used by the input stream.", + "_lzma.LZMADecompressor.decompress" => "Decompress *data*, returning uncompressed data as bytes.\n\nIf *max_length* is nonnegative, returns at most *max_length* bytes of\ndecompressed data. If this limit is reached and further output can be\nproduced, *self.needs_input* will be set to ``False``. In this case, the next\ncall to *decompress()* may provide *data* as b'' to obtain more of the output.\n\nIf all of the input data was decompressed and returned (either because this\nwas less than *max_length* bytes, or because *max_length* was negative),\n*self.needs_input* will be set to True.\n\nAttempting to decompress data after the end of stream is reached raises an\nEOFError. Any data found after the end of the stream is ignored and saved in\nthe unused_data attribute.", + "_lzma.LZMADecompressor.eof" => "True if the end-of-stream marker has been reached.", + "_lzma.LZMADecompressor.needs_input" => "True if more input is needed before more decompressed data can be produced.", + "_lzma.LZMADecompressor.unused_data" => "Data found after the end of the compressed stream.", + "_lzma.LZMAError" => "Call to liblzma failed.", + "_lzma.LZMAError.__cause__" => "exception cause", + "_lzma.LZMAError.__context__" => "exception context", + "_lzma.LZMAError.__delattr__" => "Implement delattr(self, name).", + "_lzma.LZMAError.__eq__" => "Return self==value.", + "_lzma.LZMAError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_lzma.LZMAError.__ge__" => "Return self>=value.", + "_lzma.LZMAError.__getattribute__" => "Return getattr(self, name).", + "_lzma.LZMAError.__getstate__" => "Helper for pickle.", + "_lzma.LZMAError.__gt__" => "Return self>value.", + "_lzma.LZMAError.__hash__" => "Return hash(self).", + "_lzma.LZMAError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_lzma.LZMAError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_lzma.LZMAError.__le__" => "Return self<=value.", + "_lzma.LZMAError.__lt__" => "Return self<value.", + "_lzma.LZMAError.__ne__" => "Return self!=value.", + "_lzma.LZMAError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_lzma.LZMAError.__reduce_ex__" => "Helper for pickle.", + "_lzma.LZMAError.__repr__" => "Return repr(self).", + "_lzma.LZMAError.__setattr__" => "Implement setattr(self, name, value).", + "_lzma.LZMAError.__sizeof__" => "Size of object in memory, in bytes.", + "_lzma.LZMAError.__str__" => "Return str(self).", + "_lzma.LZMAError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_lzma.LZMAError.__weakref__" => "list of weak references to the object", + "_lzma.LZMAError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_lzma.LZMAError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_lzma._decode_filter_properties" => "Return a bytes object encoding the options (properties) of the filter specified by *filter* (a dict).\n\nThe result does not include the filter ID itself, only the options.", + "_lzma._encode_filter_properties" => "Return a bytes object encoding the options (properties) of the filter specified by *filter* (a dict).\n\nThe result does not include the filter ID itself, only the options.", + "_lzma.is_check_supported" => "Test whether the given integrity check is supported.\n\nAlways returns True for CHECK_NONE and CHECK_CRC32.", + "_md5.MD5Type.__delattr__" => "Implement delattr(self, name).", + "_md5.MD5Type.__eq__" => "Return self==value.", + "_md5.MD5Type.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_md5.MD5Type.__ge__" => "Return self>=value.", + "_md5.MD5Type.__getattribute__" => "Return getattr(self, name).", + "_md5.MD5Type.__getstate__" => "Helper for pickle.", + "_md5.MD5Type.__gt__" => "Return self>value.", + "_md5.MD5Type.__hash__" => "Return hash(self).", + "_md5.MD5Type.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_md5.MD5Type.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_md5.MD5Type.__le__" => "Return self<=value.", + "_md5.MD5Type.__lt__" => "Return self<value.", + "_md5.MD5Type.__ne__" => "Return self!=value.", + "_md5.MD5Type.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_md5.MD5Type.__reduce__" => "Helper for pickle.", + "_md5.MD5Type.__reduce_ex__" => "Helper for pickle.", + "_md5.MD5Type.__repr__" => "Return repr(self).", + "_md5.MD5Type.__setattr__" => "Implement setattr(self, name, value).", + "_md5.MD5Type.__sizeof__" => "Size of object in memory, in bytes.", + "_md5.MD5Type.__str__" => "Return str(self).", + "_md5.MD5Type.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_md5.MD5Type.copy" => "Return a copy of the hash object.", + "_md5.MD5Type.digest" => "Return the digest value as a bytes object.", + "_md5.MD5Type.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_md5.MD5Type.update" => "Update this hash object's state with the provided string.", + "_md5.md5" => "Return a new MD5 hash object; optionally initialized with a string.", + "_multibytecodec.MultibyteIncrementalDecoder.__delattr__" => "Implement delattr(self, name).", + "_multibytecodec.MultibyteIncrementalDecoder.__eq__" => "Return self==value.", + "_multibytecodec.MultibyteIncrementalDecoder.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_multibytecodec.MultibyteIncrementalDecoder.__ge__" => "Return self>=value.", + "_multibytecodec.MultibyteIncrementalDecoder.__getattribute__" => "Return getattr(self, name).", + "_multibytecodec.MultibyteIncrementalDecoder.__getstate__" => "Helper for pickle.", + "_multibytecodec.MultibyteIncrementalDecoder.__gt__" => "Return self>value.", + "_multibytecodec.MultibyteIncrementalDecoder.__hash__" => "Return hash(self).", + "_multibytecodec.MultibyteIncrementalDecoder.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_multibytecodec.MultibyteIncrementalDecoder.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_multibytecodec.MultibyteIncrementalDecoder.__le__" => "Return self<=value.", + "_multibytecodec.MultibyteIncrementalDecoder.__lt__" => "Return self<value.", + "_multibytecodec.MultibyteIncrementalDecoder.__ne__" => "Return self!=value.", + "_multibytecodec.MultibyteIncrementalDecoder.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_multibytecodec.MultibyteIncrementalDecoder.__reduce__" => "Helper for pickle.", + "_multibytecodec.MultibyteIncrementalDecoder.__reduce_ex__" => "Helper for pickle.", + "_multibytecodec.MultibyteIncrementalDecoder.__repr__" => "Return repr(self).", + "_multibytecodec.MultibyteIncrementalDecoder.__setattr__" => "Implement setattr(self, name, value).", + "_multibytecodec.MultibyteIncrementalDecoder.__sizeof__" => "Size of object in memory, in bytes.", + "_multibytecodec.MultibyteIncrementalDecoder.__str__" => "Return str(self).", + "_multibytecodec.MultibyteIncrementalDecoder.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_multibytecodec.MultibyteIncrementalDecoder.errors" => "how to treat errors", + "_multibytecodec.MultibyteIncrementalEncoder.__delattr__" => "Implement delattr(self, name).", + "_multibytecodec.MultibyteIncrementalEncoder.__eq__" => "Return self==value.", + "_multibytecodec.MultibyteIncrementalEncoder.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_multibytecodec.MultibyteIncrementalEncoder.__ge__" => "Return self>=value.", + "_multibytecodec.MultibyteIncrementalEncoder.__getattribute__" => "Return getattr(self, name).", + "_multibytecodec.MultibyteIncrementalEncoder.__getstate__" => "Helper for pickle.", + "_multibytecodec.MultibyteIncrementalEncoder.__gt__" => "Return self>value.", + "_multibytecodec.MultibyteIncrementalEncoder.__hash__" => "Return hash(self).", + "_multibytecodec.MultibyteIncrementalEncoder.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_multibytecodec.MultibyteIncrementalEncoder.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_multibytecodec.MultibyteIncrementalEncoder.__le__" => "Return self<=value.", + "_multibytecodec.MultibyteIncrementalEncoder.__lt__" => "Return self<value.", + "_multibytecodec.MultibyteIncrementalEncoder.__ne__" => "Return self!=value.", + "_multibytecodec.MultibyteIncrementalEncoder.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_multibytecodec.MultibyteIncrementalEncoder.__reduce__" => "Helper for pickle.", + "_multibytecodec.MultibyteIncrementalEncoder.__reduce_ex__" => "Helper for pickle.", + "_multibytecodec.MultibyteIncrementalEncoder.__repr__" => "Return repr(self).", + "_multibytecodec.MultibyteIncrementalEncoder.__setattr__" => "Implement setattr(self, name, value).", + "_multibytecodec.MultibyteIncrementalEncoder.__sizeof__" => "Size of object in memory, in bytes.", + "_multibytecodec.MultibyteIncrementalEncoder.__str__" => "Return str(self).", + "_multibytecodec.MultibyteIncrementalEncoder.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_multibytecodec.MultibyteIncrementalEncoder.errors" => "how to treat errors", + "_multibytecodec.MultibyteStreamReader.__delattr__" => "Implement delattr(self, name).", + "_multibytecodec.MultibyteStreamReader.__eq__" => "Return self==value.", + "_multibytecodec.MultibyteStreamReader.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_multibytecodec.MultibyteStreamReader.__ge__" => "Return self>=value.", + "_multibytecodec.MultibyteStreamReader.__getattribute__" => "Return getattr(self, name).", + "_multibytecodec.MultibyteStreamReader.__getstate__" => "Helper for pickle.", + "_multibytecodec.MultibyteStreamReader.__gt__" => "Return self>value.", + "_multibytecodec.MultibyteStreamReader.__hash__" => "Return hash(self).", + "_multibytecodec.MultibyteStreamReader.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_multibytecodec.MultibyteStreamReader.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_multibytecodec.MultibyteStreamReader.__le__" => "Return self<=value.", + "_multibytecodec.MultibyteStreamReader.__lt__" => "Return self<value.", + "_multibytecodec.MultibyteStreamReader.__ne__" => "Return self!=value.", + "_multibytecodec.MultibyteStreamReader.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_multibytecodec.MultibyteStreamReader.__reduce__" => "Helper for pickle.", + "_multibytecodec.MultibyteStreamReader.__reduce_ex__" => "Helper for pickle.", + "_multibytecodec.MultibyteStreamReader.__repr__" => "Return repr(self).", + "_multibytecodec.MultibyteStreamReader.__setattr__" => "Implement setattr(self, name, value).", + "_multibytecodec.MultibyteStreamReader.__sizeof__" => "Size of object in memory, in bytes.", + "_multibytecodec.MultibyteStreamReader.__str__" => "Return str(self).", + "_multibytecodec.MultibyteStreamReader.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_multibytecodec.MultibyteStreamReader.errors" => "how to treat errors", + "_multibytecodec.MultibyteStreamWriter.__delattr__" => "Implement delattr(self, name).", + "_multibytecodec.MultibyteStreamWriter.__eq__" => "Return self==value.", + "_multibytecodec.MultibyteStreamWriter.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_multibytecodec.MultibyteStreamWriter.__ge__" => "Return self>=value.", + "_multibytecodec.MultibyteStreamWriter.__getattribute__" => "Return getattr(self, name).", + "_multibytecodec.MultibyteStreamWriter.__getstate__" => "Helper for pickle.", + "_multibytecodec.MultibyteStreamWriter.__gt__" => "Return self>value.", + "_multibytecodec.MultibyteStreamWriter.__hash__" => "Return hash(self).", + "_multibytecodec.MultibyteStreamWriter.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_multibytecodec.MultibyteStreamWriter.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_multibytecodec.MultibyteStreamWriter.__le__" => "Return self<=value.", + "_multibytecodec.MultibyteStreamWriter.__lt__" => "Return self<value.", + "_multibytecodec.MultibyteStreamWriter.__ne__" => "Return self!=value.", + "_multibytecodec.MultibyteStreamWriter.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_multibytecodec.MultibyteStreamWriter.__reduce__" => "Helper for pickle.", + "_multibytecodec.MultibyteStreamWriter.__reduce_ex__" => "Helper for pickle.", + "_multibytecodec.MultibyteStreamWriter.__repr__" => "Return repr(self).", + "_multibytecodec.MultibyteStreamWriter.__setattr__" => "Implement setattr(self, name, value).", + "_multibytecodec.MultibyteStreamWriter.__sizeof__" => "Size of object in memory, in bytes.", + "_multibytecodec.MultibyteStreamWriter.__str__" => "Return str(self).", + "_multibytecodec.MultibyteStreamWriter.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_multibytecodec.MultibyteStreamWriter.errors" => "how to treat errors", + "_multiprocessing.SemLock" => "Semaphore/Mutex type", + "_multiprocessing.SemLock.__delattr__" => "Implement delattr(self, name).", + "_multiprocessing.SemLock.__enter__" => "Enter the semaphore/lock.", + "_multiprocessing.SemLock.__eq__" => "Return self==value.", + "_multiprocessing.SemLock.__exit__" => "Exit the semaphore/lock.", + "_multiprocessing.SemLock.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_multiprocessing.SemLock.__ge__" => "Return self>=value.", + "_multiprocessing.SemLock.__getattribute__" => "Return getattr(self, name).", + "_multiprocessing.SemLock.__getstate__" => "Helper for pickle.", + "_multiprocessing.SemLock.__gt__" => "Return self>value.", + "_multiprocessing.SemLock.__hash__" => "Return hash(self).", + "_multiprocessing.SemLock.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_multiprocessing.SemLock.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_multiprocessing.SemLock.__le__" => "Return self<=value.", + "_multiprocessing.SemLock.__lt__" => "Return self<value.", + "_multiprocessing.SemLock.__ne__" => "Return self!=value.", + "_multiprocessing.SemLock.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_multiprocessing.SemLock.__reduce__" => "Helper for pickle.", + "_multiprocessing.SemLock.__reduce_ex__" => "Helper for pickle.", + "_multiprocessing.SemLock.__repr__" => "Return repr(self).", + "_multiprocessing.SemLock.__setattr__" => "Implement setattr(self, name, value).", + "_multiprocessing.SemLock.__sizeof__" => "Size of object in memory, in bytes.", + "_multiprocessing.SemLock.__str__" => "Return str(self).", + "_multiprocessing.SemLock.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_multiprocessing.SemLock._after_fork" => "Rezero the net acquisition count after fork().", + "_multiprocessing.SemLock._count" => "Num of `acquire()`s minus num of `release()`s for this process.", + "_multiprocessing.SemLock._get_value" => "Get the value of the semaphore.", + "_multiprocessing.SemLock._is_mine" => "Whether the lock is owned by this thread.", + "_multiprocessing.SemLock._is_zero" => "Return whether semaphore has value zero.", + "_multiprocessing.SemLock.acquire" => "Acquire the semaphore/lock.", + "_multiprocessing.SemLock.handle" => "", + "_multiprocessing.SemLock.kind" => "", + "_multiprocessing.SemLock.maxvalue" => "", + "_multiprocessing.SemLock.name" => "", + "_multiprocessing.SemLock.release" => "Release the semaphore/lock.", + "_opcode" => "Opcode support module.", + "_opcode.get_executor" => "Return the executor object at offset in code if exists, None otherwise.", + "_opcode.get_intrinsic1_descs" => "Return a list of names of the unary intrinsics.", + "_opcode.get_intrinsic2_descs" => "Return a list of names of the binary intrinsics.", + "_opcode.get_nb_ops" => "Return array of symbols of binary ops.\n\nIndexed by the BINARY_OP oparg value.", + "_opcode.get_specialization_stats" => "Return the specialization stats", + "_opcode.has_arg" => "Return True if the opcode uses its oparg, False otherwise.", + "_opcode.has_const" => "Return True if the opcode accesses a constant, False otherwise.", + "_opcode.has_exc" => "Return True if the opcode sets an exception handler, False otherwise.", + "_opcode.has_free" => "Return True if the opcode accesses a free variable, False otherwise.\n\nNote that 'free' in this context refers to names in the current scope\nthat are referenced by inner scopes or names in outer scopes that are\nreferenced from this scope. It does not include references to global\nor builtin scopes.", + "_opcode.has_jump" => "Return True if the opcode has a jump target, False otherwise.", + "_opcode.has_local" => "Return True if the opcode accesses a local variable, False otherwise.", + "_opcode.has_name" => "Return True if the opcode accesses an attribute by name, False otherwise.", + "_opcode.is_valid" => "Return True if opcode is valid, False otherwise.", + "_opcode.stack_effect" => "Compute the stack effect of the opcode.", + "_operator" => "Operator interface.\n\nThis module exports a set of functions implemented in C corresponding\nto the intrinsic operators of Python. For example, operator.add(x, y)\nis equivalent to the expression x+y. The function names are those\nused for special methods; variants without leading and trailing\n'__' are also provided for convenience.", + "_operator._compare_digest" => "Return 'a == b'.\n\nThis function uses an approach designed to prevent\ntiming analysis, making it appropriate for cryptography.\n\na and b must both be of the same type: either str (ASCII only),\nor any bytes-like object.\n\nNote: If a and b are of different lengths, or if an error occurs,\na timing attack could theoretically reveal information about the\ntypes and lengths of a and b--but not their values.", + "_operator.abs" => "Same as abs(a).", + "_operator.add" => "Same as a + b.", + "_operator.and_" => "Same as a & b.", + "_operator.call" => "Same as obj(*args, **kwargs).", + "_operator.concat" => "Same as a + b, for a and b sequences.", + "_operator.contains" => "Same as b in a (note reversed operands).", + "_operator.countOf" => "Return the number of items in a which are, or which equal, b.", + "_operator.delitem" => "Same as del a[b].", + "_operator.eq" => "Same as a == b.", + "_operator.floordiv" => "Same as a // b.", + "_operator.ge" => "Same as a >= b.", + "_operator.getitem" => "Same as a[b].", + "_operator.gt" => "Same as a > b.", + "_operator.iadd" => "Same as a += b.", + "_operator.iand" => "Same as a &= b.", + "_operator.iconcat" => "Same as a += b, for a and b sequences.", + "_operator.ifloordiv" => "Same as a //= b.", + "_operator.ilshift" => "Same as a <<= b.", + "_operator.imatmul" => "Same as a @= b.", + "_operator.imod" => "Same as a %= b.", + "_operator.imul" => "Same as a *= b.", + "_operator.index" => "Same as a.__index__()", + "_operator.indexOf" => "Return the first index of b in a.", + "_operator.inv" => "Same as ~a.", + "_operator.invert" => "Same as ~a.", + "_operator.ior" => "Same as a |= b.", + "_operator.ipow" => "Same as a **= b.", + "_operator.irshift" => "Same as a >>= b.", + "_operator.is_" => "Same as a is b.", + "_operator.is_not" => "Same as a is not b.", + "_operator.isub" => "Same as a -= b.", + "_operator.itruediv" => "Same as a /= b.", + "_operator.ixor" => "Same as a ^= b.", + "_operator.le" => "Same as a <= b.", + "_operator.length_hint" => "Return an estimate of the number of items in obj.\n\nThis is useful for presizing containers when building from an iterable.\n\nIf the object supports len(), the result will be exact.\nOtherwise, it may over- or under-estimate by an arbitrary amount.\nThe result will be an integer >= 0.", + "_operator.lshift" => "Same as a << b.", + "_operator.lt" => "Same as a < b.", + "_operator.matmul" => "Same as a @ b.", + "_operator.mod" => "Same as a % b.", + "_operator.mul" => "Same as a * b.", + "_operator.ne" => "Same as a != b.", + "_operator.neg" => "Same as -a.", + "_operator.not_" => "Same as not a.", + "_operator.or_" => "Same as a | b.", + "_operator.pos" => "Same as +a.", + "_operator.pow" => "Same as a ** b.", + "_operator.rshift" => "Same as a >> b.", + "_operator.setitem" => "Same as a[b] = c.", + "_operator.sub" => "Same as a - b.", + "_operator.truediv" => "Same as a / b.", + "_operator.truth" => "Return True if a is true, False otherwise.", + "_operator.xor" => "Same as a ^ b.", + "_overlapped.BindLocal" => "Bind a socket handle to an arbitrary local port.\n\nfamily should be AF_INET or AF_INET6.", + "_overlapped.ConnectPipe" => "Connect to the pipe for asynchronous I/O (overlapped).", + "_overlapped.CreateEvent" => "Create an event.\n\nEventAttributes must be None.", + "_overlapped.CreateIoCompletionPort" => "Create a completion port or register a handle with a port.", + "_overlapped.FormatMessage" => "Return error message for an error code.", + "_overlapped.GetQueuedCompletionStatus" => "Get a message from completion port.\n\nWait for up to msecs milliseconds.", + "_overlapped.Overlapped" => "OVERLAPPED structure wrapper.", + "_overlapped.Overlapped.AcceptEx" => "Start overlapped wait for client to connect.", + "_overlapped.Overlapped.ConnectEx" => "Start overlapped connect.\n\nclient_handle should be unbound.", + "_overlapped.Overlapped.ConnectNamedPipe" => "Start overlapped wait for a client to connect.", + "_overlapped.Overlapped.ReadFile" => "Start overlapped read.", + "_overlapped.Overlapped.ReadFileInto" => "Start overlapped receive.", + "_overlapped.Overlapped.TransmitFile" => "Transmit file data over a connected socket.", + "_overlapped.Overlapped.WSARecv" => "Start overlapped receive.", + "_overlapped.Overlapped.WSARecvFrom" => "Start overlapped receive.", + "_overlapped.Overlapped.WSARecvFromInto" => "Start overlapped receive.", + "_overlapped.Overlapped.WSARecvInto" => "Start overlapped receive.", + "_overlapped.Overlapped.WSASend" => "Start overlapped send.", + "_overlapped.Overlapped.WSASendTo" => "Start overlapped sendto over a connectionless (UDP) socket.", + "_overlapped.Overlapped.WriteFile" => "Start overlapped write.", + "_overlapped.Overlapped.__delattr__" => "Implement delattr(self, name).", + "_overlapped.Overlapped.__eq__" => "Return self==value.", + "_overlapped.Overlapped.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_overlapped.Overlapped.__ge__" => "Return self>=value.", + "_overlapped.Overlapped.__getattribute__" => "Return getattr(self, name).", + "_overlapped.Overlapped.__getstate__" => "Helper for pickle.", + "_overlapped.Overlapped.__gt__" => "Return self>value.", + "_overlapped.Overlapped.__hash__" => "Return hash(self).", + "_overlapped.Overlapped.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_overlapped.Overlapped.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_overlapped.Overlapped.__le__" => "Return self<=value.", + "_overlapped.Overlapped.__lt__" => "Return self<value.", + "_overlapped.Overlapped.__ne__" => "Return self!=value.", + "_overlapped.Overlapped.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_overlapped.Overlapped.__reduce__" => "Helper for pickle.", + "_overlapped.Overlapped.__reduce_ex__" => "Helper for pickle.", + "_overlapped.Overlapped.__repr__" => "Return repr(self).", + "_overlapped.Overlapped.__setattr__" => "Implement setattr(self, name, value).", + "_overlapped.Overlapped.__sizeof__" => "Size of object in memory, in bytes.", + "_overlapped.Overlapped.__str__" => "Return str(self).", + "_overlapped.Overlapped.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_overlapped.Overlapped.address" => "Address of overlapped structure", + "_overlapped.Overlapped.cancel" => "Cancel overlapped operation.", + "_overlapped.Overlapped.error" => "Error from last operation", + "_overlapped.Overlapped.event" => "Overlapped event handle", + "_overlapped.Overlapped.getresult" => "Retrieve result of operation.\n\nIf wait is true then it blocks until the operation is finished. If wait\nis false and the operation is still pending then an error is raised.", + "_overlapped.Overlapped.pending" => "Whether the operation is pending", + "_overlapped.PostQueuedCompletionStatus" => "Post a message to completion port.", + "_overlapped.RegisterWaitWithQueue" => "Register wait for Object; when complete CompletionPort is notified.", + "_overlapped.ResetEvent" => "Reset event.", + "_overlapped.SetEvent" => "Set event.", + "_overlapped.UnregisterWait" => "Unregister wait handle.", + "_overlapped.UnregisterWaitEx" => "Unregister wait handle.", + "_overlapped.WSAConnect" => "Bind a remote address to a connectionless (UDP) socket.", + "_pickle" => "Optimized C implementation for the Python pickle module.", + "_pickle.PickleError.__cause__" => "exception cause", + "_pickle.PickleError.__context__" => "exception context", + "_pickle.PickleError.__delattr__" => "Implement delattr(self, name).", + "_pickle.PickleError.__eq__" => "Return self==value.", + "_pickle.PickleError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_pickle.PickleError.__ge__" => "Return self>=value.", + "_pickle.PickleError.__getattribute__" => "Return getattr(self, name).", + "_pickle.PickleError.__getstate__" => "Helper for pickle.", + "_pickle.PickleError.__gt__" => "Return self>value.", + "_pickle.PickleError.__hash__" => "Return hash(self).", + "_pickle.PickleError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_pickle.PickleError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_pickle.PickleError.__le__" => "Return self<=value.", + "_pickle.PickleError.__lt__" => "Return self<value.", + "_pickle.PickleError.__ne__" => "Return self!=value.", + "_pickle.PickleError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_pickle.PickleError.__reduce_ex__" => "Helper for pickle.", + "_pickle.PickleError.__repr__" => "Return repr(self).", + "_pickle.PickleError.__setattr__" => "Implement setattr(self, name, value).", + "_pickle.PickleError.__sizeof__" => "Size of object in memory, in bytes.", + "_pickle.PickleError.__str__" => "Return str(self).", + "_pickle.PickleError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_pickle.PickleError.__weakref__" => "list of weak references to the object", + "_pickle.PickleError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_pickle.PickleError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_pickle.Pickler" => "This takes a binary file for writing a pickle data stream.\n\nThe optional *protocol* argument tells the pickler to use the given\nprotocol; supported protocols are 0, 1, 2, 3, 4 and 5. The default\nprotocol is 4. It was introduced in Python 3.4, and is incompatible\nwith previous versions.\n\nSpecifying a negative protocol version selects the highest protocol\nversion supported. The higher the protocol used, the more recent the\nversion of Python needed to read the pickle produced.\n\nThe *file* argument must have a write() method that accepts a single\nbytes argument. It can thus be a file object opened for binary\nwriting, an io.BytesIO instance, or any other custom object that meets\nthis interface.\n\nIf *fix_imports* is True and protocol is less than 3, pickle will try\nto map the new Python 3 names to the old module names used in Python\n2, so that the pickle data stream is readable with Python 2.\n\nIf *buffer_callback* is None (the default), buffer views are\nserialized into *file* as part of the pickle stream.\n\nIf *buffer_callback* is not None, then it can be called any number\nof times with a buffer view. If the callback returns a false value\n(such as None), the given buffer is out-of-band; otherwise the\nbuffer is serialized in-band, i.e. inside the pickle stream.\n\nIt is an error if *buffer_callback* is not None and *protocol*\nis None or smaller than 5.", + "_pickle.Pickler.__delattr__" => "Implement delattr(self, name).", + "_pickle.Pickler.__eq__" => "Return self==value.", + "_pickle.Pickler.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_pickle.Pickler.__ge__" => "Return self>=value.", + "_pickle.Pickler.__getattribute__" => "Return getattr(self, name).", + "_pickle.Pickler.__getstate__" => "Helper for pickle.", + "_pickle.Pickler.__gt__" => "Return self>value.", + "_pickle.Pickler.__hash__" => "Return hash(self).", + "_pickle.Pickler.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_pickle.Pickler.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_pickle.Pickler.__le__" => "Return self<=value.", + "_pickle.Pickler.__lt__" => "Return self<value.", + "_pickle.Pickler.__ne__" => "Return self!=value.", + "_pickle.Pickler.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_pickle.Pickler.__reduce__" => "Helper for pickle.", + "_pickle.Pickler.__reduce_ex__" => "Helper for pickle.", + "_pickle.Pickler.__repr__" => "Return repr(self).", + "_pickle.Pickler.__setattr__" => "Implement setattr(self, name, value).", + "_pickle.Pickler.__sizeof__" => "Returns size in memory, in bytes.", + "_pickle.Pickler.__str__" => "Return str(self).", + "_pickle.Pickler.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_pickle.Pickler.clear_memo" => "Clears the pickler's \"memo\".\n\nThe memo is the data structure that remembers which objects the\npickler has already seen, so that shared or recursive objects are\npickled by reference and not by value. This method is useful when\nre-using picklers.", + "_pickle.Pickler.dump" => "Write a pickled representation of the given object to the open file.", + "_pickle.PicklingError.__cause__" => "exception cause", + "_pickle.PicklingError.__context__" => "exception context", + "_pickle.PicklingError.__delattr__" => "Implement delattr(self, name).", + "_pickle.PicklingError.__eq__" => "Return self==value.", + "_pickle.PicklingError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_pickle.PicklingError.__ge__" => "Return self>=value.", + "_pickle.PicklingError.__getattribute__" => "Return getattr(self, name).", + "_pickle.PicklingError.__getstate__" => "Helper for pickle.", + "_pickle.PicklingError.__gt__" => "Return self>value.", + "_pickle.PicklingError.__hash__" => "Return hash(self).", + "_pickle.PicklingError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_pickle.PicklingError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_pickle.PicklingError.__le__" => "Return self<=value.", + "_pickle.PicklingError.__lt__" => "Return self<value.", + "_pickle.PicklingError.__ne__" => "Return self!=value.", + "_pickle.PicklingError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_pickle.PicklingError.__reduce_ex__" => "Helper for pickle.", + "_pickle.PicklingError.__repr__" => "Return repr(self).", + "_pickle.PicklingError.__setattr__" => "Implement setattr(self, name, value).", + "_pickle.PicklingError.__sizeof__" => "Size of object in memory, in bytes.", + "_pickle.PicklingError.__str__" => "Return str(self).", + "_pickle.PicklingError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_pickle.PicklingError.__weakref__" => "list of weak references to the object", + "_pickle.PicklingError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_pickle.PicklingError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_pickle.Unpickler" => "This takes a binary file for reading a pickle data stream.\n\nThe protocol version of the pickle is detected automatically, so no\nprotocol argument is needed. Bytes past the pickled object's\nrepresentation are ignored.\n\nThe argument *file* must have two methods, a read() method that takes\nan integer argument, and a readline() method that requires no\narguments. Both methods should return bytes. Thus *file* can be a\nbinary file object opened for reading, an io.BytesIO object, or any\nother custom object that meets this interface.\n\nOptional keyword arguments are *fix_imports*, *encoding* and *errors*,\nwhich are used to control compatibility support for pickle stream\ngenerated by Python 2. If *fix_imports* is True, pickle will try to\nmap the old Python 2 names to the new names used in Python 3. The\n*encoding* and *errors* tell pickle how to decode 8-bit string\ninstances pickled by Python 2; these default to 'ASCII' and 'strict',\nrespectively. The *encoding* can be 'bytes' to read these 8-bit\nstring instances as bytes objects.", + "_pickle.Unpickler.__delattr__" => "Implement delattr(self, name).", + "_pickle.Unpickler.__eq__" => "Return self==value.", + "_pickle.Unpickler.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_pickle.Unpickler.__ge__" => "Return self>=value.", + "_pickle.Unpickler.__getattribute__" => "Return getattr(self, name).", + "_pickle.Unpickler.__getstate__" => "Helper for pickle.", + "_pickle.Unpickler.__gt__" => "Return self>value.", + "_pickle.Unpickler.__hash__" => "Return hash(self).", + "_pickle.Unpickler.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_pickle.Unpickler.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_pickle.Unpickler.__le__" => "Return self<=value.", + "_pickle.Unpickler.__lt__" => "Return self<value.", + "_pickle.Unpickler.__ne__" => "Return self!=value.", + "_pickle.Unpickler.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_pickle.Unpickler.__reduce__" => "Helper for pickle.", + "_pickle.Unpickler.__reduce_ex__" => "Helper for pickle.", + "_pickle.Unpickler.__repr__" => "Return repr(self).", + "_pickle.Unpickler.__setattr__" => "Implement setattr(self, name, value).", + "_pickle.Unpickler.__sizeof__" => "Returns size in memory, in bytes.", + "_pickle.Unpickler.__str__" => "Return str(self).", + "_pickle.Unpickler.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_pickle.Unpickler.find_class" => "Return an object from a specified module.\n\nIf necessary, the module will be imported. Subclasses may override\nthis method (e.g. to restrict unpickling of arbitrary classes and\nfunctions).\n\nThis method is called whenever a class or a function object is\nneeded. Both arguments passed are str objects.", + "_pickle.Unpickler.load" => "Load a pickle.\n\nRead a pickled object representation from the open file object given\nin the constructor, and return the reconstituted object hierarchy\nspecified therein.", + "_pickle.UnpicklingError.__cause__" => "exception cause", + "_pickle.UnpicklingError.__context__" => "exception context", + "_pickle.UnpicklingError.__delattr__" => "Implement delattr(self, name).", + "_pickle.UnpicklingError.__eq__" => "Return self==value.", + "_pickle.UnpicklingError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_pickle.UnpicklingError.__ge__" => "Return self>=value.", + "_pickle.UnpicklingError.__getattribute__" => "Return getattr(self, name).", + "_pickle.UnpicklingError.__getstate__" => "Helper for pickle.", + "_pickle.UnpicklingError.__gt__" => "Return self>value.", + "_pickle.UnpicklingError.__hash__" => "Return hash(self).", + "_pickle.UnpicklingError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_pickle.UnpicklingError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_pickle.UnpicklingError.__le__" => "Return self<=value.", + "_pickle.UnpicklingError.__lt__" => "Return self<value.", + "_pickle.UnpicklingError.__ne__" => "Return self!=value.", + "_pickle.UnpicklingError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_pickle.UnpicklingError.__reduce_ex__" => "Helper for pickle.", + "_pickle.UnpicklingError.__repr__" => "Return repr(self).", + "_pickle.UnpicklingError.__setattr__" => "Implement setattr(self, name, value).", + "_pickle.UnpicklingError.__sizeof__" => "Size of object in memory, in bytes.", + "_pickle.UnpicklingError.__str__" => "Return str(self).", + "_pickle.UnpicklingError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_pickle.UnpicklingError.__weakref__" => "list of weak references to the object", + "_pickle.UnpicklingError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_pickle.UnpicklingError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_pickle.dump" => "Write a pickled representation of obj to the open file object file.\n\nThis is equivalent to ``Pickler(file, protocol).dump(obj)``, but may\nbe more efficient.\n\nThe optional *protocol* argument tells the pickler to use the given\nprotocol; supported protocols are 0, 1, 2, 3, 4 and 5. The default\nprotocol is 4. It was introduced in Python 3.4, and is incompatible\nwith previous versions.\n\nSpecifying a negative protocol version selects the highest protocol\nversion supported. The higher the protocol used, the more recent the\nversion of Python needed to read the pickle produced.\n\nThe *file* argument must have a write() method that accepts a single\nbytes argument. It can thus be a file object opened for binary\nwriting, an io.BytesIO instance, or any other custom object that meets\nthis interface.\n\nIf *fix_imports* is True and protocol is less than 3, pickle will try\nto map the new Python 3 names to the old module names used in Python\n2, so that the pickle data stream is readable with Python 2.\n\nIf *buffer_callback* is None (the default), buffer views are serialized\ninto *file* as part of the pickle stream. It is an error if\n*buffer_callback* is not None and *protocol* is None or smaller than 5.", + "_pickle.dumps" => "Return the pickled representation of the object as a bytes object.\n\nThe optional *protocol* argument tells the pickler to use the given\nprotocol; supported protocols are 0, 1, 2, 3, 4 and 5. The default\nprotocol is 4. It was introduced in Python 3.4, and is incompatible\nwith previous versions.\n\nSpecifying a negative protocol version selects the highest protocol\nversion supported. The higher the protocol used, the more recent the\nversion of Python needed to read the pickle produced.\n\nIf *fix_imports* is True and *protocol* is less than 3, pickle will\ntry to map the new Python 3 names to the old module names used in\nPython 2, so that the pickle data stream is readable with Python 2.\n\nIf *buffer_callback* is None (the default), buffer views are serialized\ninto *file* as part of the pickle stream. It is an error if\n*buffer_callback* is not None and *protocol* is None or smaller than 5.", + "_pickle.load" => "Read and return an object from the pickle data stored in a file.\n\nThis is equivalent to ``Unpickler(file).load()``, but may be more\nefficient.\n\nThe protocol version of the pickle is detected automatically, so no\nprotocol argument is needed. Bytes past the pickled object's\nrepresentation are ignored.\n\nThe argument *file* must have two methods, a read() method that takes\nan integer argument, and a readline() method that requires no\narguments. Both methods should return bytes. Thus *file* can be a\nbinary file object opened for reading, an io.BytesIO object, or any\nother custom object that meets this interface.\n\nOptional keyword arguments are *fix_imports*, *encoding* and *errors*,\nwhich are used to control compatibility support for pickle stream\ngenerated by Python 2. If *fix_imports* is True, pickle will try to\nmap the old Python 2 names to the new names used in Python 3. The\n*encoding* and *errors* tell pickle how to decode 8-bit string\ninstances pickled by Python 2; these default to 'ASCII' and 'strict',\nrespectively. The *encoding* can be 'bytes' to read these 8-bit\nstring instances as bytes objects.", + "_pickle.loads" => "Read and return an object from the given pickle data.\n\nThe protocol version of the pickle is detected automatically, so no\nprotocol argument is needed. Bytes past the pickled object's\nrepresentation are ignored.\n\nOptional keyword arguments are *fix_imports*, *encoding* and *errors*,\nwhich are used to control compatibility support for pickle stream\ngenerated by Python 2. If *fix_imports* is True, pickle will try to\nmap the old Python 2 names to the new names used in Python 3. The\n*encoding* and *errors* tell pickle how to decode 8-bit string\ninstances pickled by Python 2; these default to 'ASCII' and 'strict',\nrespectively. The *encoding* can be 'bytes' to read these 8-bit\nstring instances as bytes objects.", + "_posixshmem" => "POSIX shared memory module", + "_posixshmem.shm_open" => "Open a shared memory object. Returns a file descriptor (integer).", + "_posixshmem.shm_unlink" => "Remove a shared memory object (similar to unlink()).\n\nRemove a shared memory object name, and, once all processes have unmapped\nthe object, de-allocates and destroys the contents of the associated memory\nregion.", + "_posixsubprocess" => "A POSIX helper for the subprocess module.", + "_posixsubprocess.fork_exec" => "Spawn a fresh new child process.\n\nFork a child process, close parent file descriptors as appropriate in the\nchild and duplicate the few that are needed before calling exec() in the\nchild process.\n\nIf close_fds is True, close file descriptors 3 and higher, except those listed\nin the sorted tuple pass_fds.\n\nThe preexec_fn, if supplied, will be called immediately before closing file\ndescriptors and exec.\n\nWARNING: preexec_fn is NOT SAFE if your application uses threads.\n It may trigger infrequent, difficult to debug deadlocks.\n\nIf an error occurs in the child process before the exec, it is\nserialized and written to the errpipe_write fd per subprocess.py.\n\nReturns: the child process's PID.\n\nRaises: Only on an error in the parent process.", + "_queue" => "C implementation of the Python queue module.\nThis module is an implementation detail, please do not use it directly.", + "_queue.Empty" => "Exception raised by Queue.get(block=0)/get_nowait().", + "_queue.Empty.__cause__" => "exception cause", + "_queue.Empty.__context__" => "exception context", + "_queue.Empty.__delattr__" => "Implement delattr(self, name).", + "_queue.Empty.__eq__" => "Return self==value.", + "_queue.Empty.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_queue.Empty.__ge__" => "Return self>=value.", + "_queue.Empty.__getattribute__" => "Return getattr(self, name).", + "_queue.Empty.__getstate__" => "Helper for pickle.", + "_queue.Empty.__gt__" => "Return self>value.", + "_queue.Empty.__hash__" => "Return hash(self).", + "_queue.Empty.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_queue.Empty.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_queue.Empty.__le__" => "Return self<=value.", + "_queue.Empty.__lt__" => "Return self<value.", + "_queue.Empty.__ne__" => "Return self!=value.", + "_queue.Empty.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_queue.Empty.__reduce_ex__" => "Helper for pickle.", + "_queue.Empty.__repr__" => "Return repr(self).", + "_queue.Empty.__setattr__" => "Implement setattr(self, name, value).", + "_queue.Empty.__sizeof__" => "Size of object in memory, in bytes.", + "_queue.Empty.__str__" => "Return str(self).", + "_queue.Empty.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_queue.Empty.__weakref__" => "list of weak references to the object", + "_queue.Empty.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_queue.Empty.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_queue.SimpleQueue" => "Simple, unbounded, reentrant FIFO queue.", + "_queue.SimpleQueue.__class_getitem__" => "See PEP 585", + "_queue.SimpleQueue.__delattr__" => "Implement delattr(self, name).", + "_queue.SimpleQueue.__eq__" => "Return self==value.", + "_queue.SimpleQueue.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_queue.SimpleQueue.__ge__" => "Return self>=value.", + "_queue.SimpleQueue.__getattribute__" => "Return getattr(self, name).", + "_queue.SimpleQueue.__getstate__" => "Helper for pickle.", + "_queue.SimpleQueue.__gt__" => "Return self>value.", + "_queue.SimpleQueue.__hash__" => "Return hash(self).", + "_queue.SimpleQueue.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_queue.SimpleQueue.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_queue.SimpleQueue.__le__" => "Return self<=value.", + "_queue.SimpleQueue.__lt__" => "Return self<value.", + "_queue.SimpleQueue.__ne__" => "Return self!=value.", + "_queue.SimpleQueue.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_queue.SimpleQueue.__reduce__" => "Helper for pickle.", + "_queue.SimpleQueue.__reduce_ex__" => "Helper for pickle.", + "_queue.SimpleQueue.__repr__" => "Return repr(self).", + "_queue.SimpleQueue.__setattr__" => "Implement setattr(self, name, value).", + "_queue.SimpleQueue.__sizeof__" => "Size of object in memory, in bytes.", + "_queue.SimpleQueue.__str__" => "Return str(self).", + "_queue.SimpleQueue.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_queue.SimpleQueue.empty" => "Return True if the queue is empty, False otherwise (not reliable!).", + "_queue.SimpleQueue.get" => "Remove and return an item from the queue.\n\nIf optional args 'block' is true and 'timeout' is None (the default),\nblock if necessary until an item is available. If 'timeout' is\na non-negative number, it blocks at most 'timeout' seconds and raises\nthe Empty exception if no item was available within that time.\nOtherwise ('block' is false), return an item if one is immediately\navailable, else raise the Empty exception ('timeout' is ignored\nin that case).", + "_queue.SimpleQueue.get_nowait" => "Remove and return an item from the queue without blocking.\n\nOnly get an item if one is immediately available. Otherwise\nraise the Empty exception.", + "_queue.SimpleQueue.put" => "Put the item on the queue.\n\nThe optional 'block' and 'timeout' arguments are ignored, as this method\nnever blocks. They are provided for compatibility with the Queue class.", + "_queue.SimpleQueue.put_nowait" => "Put an item into the queue without blocking.\n\nThis is exactly equivalent to `put(item)` and is only provided\nfor compatibility with the Queue class.", + "_queue.SimpleQueue.qsize" => "Return the approximate size of the queue (not reliable!).", + "_random" => "Module implements the Mersenne Twister random number generator.", + "_random.Random" => "Random() -> create a random number generator with its own internal state.", + "_random.Random.__delattr__" => "Implement delattr(self, name).", + "_random.Random.__eq__" => "Return self==value.", + "_random.Random.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_random.Random.__ge__" => "Return self>=value.", + "_random.Random.__getattribute__" => "Return getattr(self, name).", + "_random.Random.__getstate__" => "Helper for pickle.", + "_random.Random.__gt__" => "Return self>value.", + "_random.Random.__hash__" => "Return hash(self).", + "_random.Random.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_random.Random.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_random.Random.__le__" => "Return self<=value.", + "_random.Random.__lt__" => "Return self<value.", + "_random.Random.__ne__" => "Return self!=value.", + "_random.Random.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_random.Random.__reduce__" => "Helper for pickle.", + "_random.Random.__reduce_ex__" => "Helper for pickle.", + "_random.Random.__repr__" => "Return repr(self).", + "_random.Random.__setattr__" => "Implement setattr(self, name, value).", + "_random.Random.__sizeof__" => "Size of object in memory, in bytes.", + "_random.Random.__str__" => "Return str(self).", + "_random.Random.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_random.Random.getrandbits" => "getrandbits(k) -> x. Generates an int with k random bits.", + "_random.Random.getstate" => "getstate() -> tuple containing the current state.", + "_random.Random.random" => "random() -> x in the interval [0, 1).", + "_random.Random.seed" => "seed([n]) -> None.\n\nDefaults to use urandom and falls back to a combination\nof the current time and the process identifier.", + "_random.Random.setstate" => "setstate(state) -> None. Restores generator state.", + "_sha1.SHA1Type.__delattr__" => "Implement delattr(self, name).", + "_sha1.SHA1Type.__eq__" => "Return self==value.", + "_sha1.SHA1Type.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_sha1.SHA1Type.__ge__" => "Return self>=value.", + "_sha1.SHA1Type.__getattribute__" => "Return getattr(self, name).", + "_sha1.SHA1Type.__getstate__" => "Helper for pickle.", + "_sha1.SHA1Type.__gt__" => "Return self>value.", + "_sha1.SHA1Type.__hash__" => "Return hash(self).", + "_sha1.SHA1Type.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_sha1.SHA1Type.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_sha1.SHA1Type.__le__" => "Return self<=value.", + "_sha1.SHA1Type.__lt__" => "Return self<value.", + "_sha1.SHA1Type.__ne__" => "Return self!=value.", + "_sha1.SHA1Type.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_sha1.SHA1Type.__reduce__" => "Helper for pickle.", + "_sha1.SHA1Type.__reduce_ex__" => "Helper for pickle.", + "_sha1.SHA1Type.__repr__" => "Return repr(self).", + "_sha1.SHA1Type.__setattr__" => "Implement setattr(self, name, value).", + "_sha1.SHA1Type.__sizeof__" => "Size of object in memory, in bytes.", + "_sha1.SHA1Type.__str__" => "Return str(self).", + "_sha1.SHA1Type.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_sha1.SHA1Type.copy" => "Return a copy of the hash object.", + "_sha1.SHA1Type.digest" => "Return the digest value as a bytes object.", + "_sha1.SHA1Type.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_sha1.SHA1Type.update" => "Update this hash object's state with the provided string.", + "_sha1.sha1" => "Return a new SHA1 hash object; optionally initialized with a string.", + "_sha2.SHA224Type.__delattr__" => "Implement delattr(self, name).", + "_sha2.SHA224Type.__eq__" => "Return self==value.", + "_sha2.SHA224Type.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_sha2.SHA224Type.__ge__" => "Return self>=value.", + "_sha2.SHA224Type.__getattribute__" => "Return getattr(self, name).", + "_sha2.SHA224Type.__getstate__" => "Helper for pickle.", + "_sha2.SHA224Type.__gt__" => "Return self>value.", + "_sha2.SHA224Type.__hash__" => "Return hash(self).", + "_sha2.SHA224Type.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_sha2.SHA224Type.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_sha2.SHA224Type.__le__" => "Return self<=value.", + "_sha2.SHA224Type.__lt__" => "Return self<value.", + "_sha2.SHA224Type.__ne__" => "Return self!=value.", + "_sha2.SHA224Type.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_sha2.SHA224Type.__reduce__" => "Helper for pickle.", + "_sha2.SHA224Type.__reduce_ex__" => "Helper for pickle.", + "_sha2.SHA224Type.__repr__" => "Return repr(self).", + "_sha2.SHA224Type.__setattr__" => "Implement setattr(self, name, value).", + "_sha2.SHA224Type.__sizeof__" => "Size of object in memory, in bytes.", + "_sha2.SHA224Type.__str__" => "Return str(self).", + "_sha2.SHA224Type.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_sha2.SHA224Type.copy" => "Return a copy of the hash object.", + "_sha2.SHA224Type.digest" => "Return the digest value as a bytes object.", + "_sha2.SHA224Type.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_sha2.SHA224Type.update" => "Update this hash object's state with the provided string.", + "_sha2.SHA256Type.__delattr__" => "Implement delattr(self, name).", + "_sha2.SHA256Type.__eq__" => "Return self==value.", + "_sha2.SHA256Type.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_sha2.SHA256Type.__ge__" => "Return self>=value.", + "_sha2.SHA256Type.__getattribute__" => "Return getattr(self, name).", + "_sha2.SHA256Type.__getstate__" => "Helper for pickle.", + "_sha2.SHA256Type.__gt__" => "Return self>value.", + "_sha2.SHA256Type.__hash__" => "Return hash(self).", + "_sha2.SHA256Type.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_sha2.SHA256Type.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_sha2.SHA256Type.__le__" => "Return self<=value.", + "_sha2.SHA256Type.__lt__" => "Return self<value.", + "_sha2.SHA256Type.__ne__" => "Return self!=value.", + "_sha2.SHA256Type.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_sha2.SHA256Type.__reduce__" => "Helper for pickle.", + "_sha2.SHA256Type.__reduce_ex__" => "Helper for pickle.", + "_sha2.SHA256Type.__repr__" => "Return repr(self).", + "_sha2.SHA256Type.__setattr__" => "Implement setattr(self, name, value).", + "_sha2.SHA256Type.__sizeof__" => "Size of object in memory, in bytes.", + "_sha2.SHA256Type.__str__" => "Return str(self).", + "_sha2.SHA256Type.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_sha2.SHA256Type.copy" => "Return a copy of the hash object.", + "_sha2.SHA256Type.digest" => "Return the digest value as a bytes object.", + "_sha2.SHA256Type.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_sha2.SHA256Type.update" => "Update this hash object's state with the provided string.", + "_sha2.SHA384Type.__delattr__" => "Implement delattr(self, name).", + "_sha2.SHA384Type.__eq__" => "Return self==value.", + "_sha2.SHA384Type.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_sha2.SHA384Type.__ge__" => "Return self>=value.", + "_sha2.SHA384Type.__getattribute__" => "Return getattr(self, name).", + "_sha2.SHA384Type.__getstate__" => "Helper for pickle.", + "_sha2.SHA384Type.__gt__" => "Return self>value.", + "_sha2.SHA384Type.__hash__" => "Return hash(self).", + "_sha2.SHA384Type.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_sha2.SHA384Type.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_sha2.SHA384Type.__le__" => "Return self<=value.", + "_sha2.SHA384Type.__lt__" => "Return self<value.", + "_sha2.SHA384Type.__ne__" => "Return self!=value.", + "_sha2.SHA384Type.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_sha2.SHA384Type.__reduce__" => "Helper for pickle.", + "_sha2.SHA384Type.__reduce_ex__" => "Helper for pickle.", + "_sha2.SHA384Type.__repr__" => "Return repr(self).", + "_sha2.SHA384Type.__setattr__" => "Implement setattr(self, name, value).", + "_sha2.SHA384Type.__sizeof__" => "Size of object in memory, in bytes.", + "_sha2.SHA384Type.__str__" => "Return str(self).", + "_sha2.SHA384Type.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_sha2.SHA384Type.copy" => "Return a copy of the hash object.", + "_sha2.SHA384Type.digest" => "Return the digest value as a bytes object.", + "_sha2.SHA384Type.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_sha2.SHA384Type.update" => "Update this hash object's state with the provided string.", + "_sha2.SHA512Type.__delattr__" => "Implement delattr(self, name).", + "_sha2.SHA512Type.__eq__" => "Return self==value.", + "_sha2.SHA512Type.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_sha2.SHA512Type.__ge__" => "Return self>=value.", + "_sha2.SHA512Type.__getattribute__" => "Return getattr(self, name).", + "_sha2.SHA512Type.__getstate__" => "Helper for pickle.", + "_sha2.SHA512Type.__gt__" => "Return self>value.", + "_sha2.SHA512Type.__hash__" => "Return hash(self).", + "_sha2.SHA512Type.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_sha2.SHA512Type.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_sha2.SHA512Type.__le__" => "Return self<=value.", + "_sha2.SHA512Type.__lt__" => "Return self<value.", + "_sha2.SHA512Type.__ne__" => "Return self!=value.", + "_sha2.SHA512Type.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_sha2.SHA512Type.__reduce__" => "Helper for pickle.", + "_sha2.SHA512Type.__reduce_ex__" => "Helper for pickle.", + "_sha2.SHA512Type.__repr__" => "Return repr(self).", + "_sha2.SHA512Type.__setattr__" => "Implement setattr(self, name, value).", + "_sha2.SHA512Type.__sizeof__" => "Size of object in memory, in bytes.", + "_sha2.SHA512Type.__str__" => "Return str(self).", + "_sha2.SHA512Type.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_sha2.SHA512Type.copy" => "Return a copy of the hash object.", + "_sha2.SHA512Type.digest" => "Return the digest value as a bytes object.", + "_sha2.SHA512Type.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_sha2.SHA512Type.update" => "Update this hash object's state with the provided string.", + "_sha2.sha224" => "Return a new SHA-224 hash object; optionally initialized with a string.", + "_sha2.sha256" => "Return a new SHA-256 hash object; optionally initialized with a string.", + "_sha2.sha384" => "Return a new SHA-384 hash object; optionally initialized with a string.", + "_sha2.sha512" => "Return a new SHA-512 hash object; optionally initialized with a string.", + "_sha3.sha3_224" => "sha3_224([data], *, usedforsecurity=True) -> SHA3 object\n\nReturn a new SHA3 hash object with a hashbit length of 28 bytes.", + "_sha3.sha3_224.__delattr__" => "Implement delattr(self, name).", + "_sha3.sha3_224.__eq__" => "Return self==value.", + "_sha3.sha3_224.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_sha3.sha3_224.__ge__" => "Return self>=value.", + "_sha3.sha3_224.__getattribute__" => "Return getattr(self, name).", + "_sha3.sha3_224.__getstate__" => "Helper for pickle.", + "_sha3.sha3_224.__gt__" => "Return self>value.", + "_sha3.sha3_224.__hash__" => "Return hash(self).", + "_sha3.sha3_224.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_sha3.sha3_224.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_sha3.sha3_224.__le__" => "Return self<=value.", + "_sha3.sha3_224.__lt__" => "Return self<value.", + "_sha3.sha3_224.__ne__" => "Return self!=value.", + "_sha3.sha3_224.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_sha3.sha3_224.__reduce__" => "Helper for pickle.", + "_sha3.sha3_224.__reduce_ex__" => "Helper for pickle.", + "_sha3.sha3_224.__repr__" => "Return repr(self).", + "_sha3.sha3_224.__setattr__" => "Implement setattr(self, name, value).", + "_sha3.sha3_224.__sizeof__" => "Size of object in memory, in bytes.", + "_sha3.sha3_224.__str__" => "Return str(self).", + "_sha3.sha3_224.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_sha3.sha3_224.copy" => "Return a copy of the hash object.", + "_sha3.sha3_224.digest" => "Return the digest value as a bytes object.", + "_sha3.sha3_224.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_sha3.sha3_224.update" => "Update this hash object's state with the provided bytes-like object.", + "_sha3.sha3_256" => "sha3_256([data], *, usedforsecurity=True) -> SHA3 object\n\nReturn a new SHA3 hash object with a hashbit length of 32 bytes.", + "_sha3.sha3_256.__delattr__" => "Implement delattr(self, name).", + "_sha3.sha3_256.__eq__" => "Return self==value.", + "_sha3.sha3_256.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_sha3.sha3_256.__ge__" => "Return self>=value.", + "_sha3.sha3_256.__getattribute__" => "Return getattr(self, name).", + "_sha3.sha3_256.__getstate__" => "Helper for pickle.", + "_sha3.sha3_256.__gt__" => "Return self>value.", + "_sha3.sha3_256.__hash__" => "Return hash(self).", + "_sha3.sha3_256.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_sha3.sha3_256.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_sha3.sha3_256.__le__" => "Return self<=value.", + "_sha3.sha3_256.__lt__" => "Return self<value.", + "_sha3.sha3_256.__ne__" => "Return self!=value.", + "_sha3.sha3_256.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_sha3.sha3_256.__reduce__" => "Helper for pickle.", + "_sha3.sha3_256.__reduce_ex__" => "Helper for pickle.", + "_sha3.sha3_256.__repr__" => "Return repr(self).", + "_sha3.sha3_256.__setattr__" => "Implement setattr(self, name, value).", + "_sha3.sha3_256.__sizeof__" => "Size of object in memory, in bytes.", + "_sha3.sha3_256.__str__" => "Return str(self).", + "_sha3.sha3_256.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_sha3.sha3_256.copy" => "Return a copy of the hash object.", + "_sha3.sha3_256.digest" => "Return the digest value as a bytes object.", + "_sha3.sha3_256.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_sha3.sha3_256.update" => "Update this hash object's state with the provided bytes-like object.", + "_sha3.sha3_384" => "sha3_384([data], *, usedforsecurity=True) -> SHA3 object\n\nReturn a new SHA3 hash object with a hashbit length of 48 bytes.", + "_sha3.sha3_384.__delattr__" => "Implement delattr(self, name).", + "_sha3.sha3_384.__eq__" => "Return self==value.", + "_sha3.sha3_384.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_sha3.sha3_384.__ge__" => "Return self>=value.", + "_sha3.sha3_384.__getattribute__" => "Return getattr(self, name).", + "_sha3.sha3_384.__getstate__" => "Helper for pickle.", + "_sha3.sha3_384.__gt__" => "Return self>value.", + "_sha3.sha3_384.__hash__" => "Return hash(self).", + "_sha3.sha3_384.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_sha3.sha3_384.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_sha3.sha3_384.__le__" => "Return self<=value.", + "_sha3.sha3_384.__lt__" => "Return self<value.", + "_sha3.sha3_384.__ne__" => "Return self!=value.", + "_sha3.sha3_384.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_sha3.sha3_384.__reduce__" => "Helper for pickle.", + "_sha3.sha3_384.__reduce_ex__" => "Helper for pickle.", + "_sha3.sha3_384.__repr__" => "Return repr(self).", + "_sha3.sha3_384.__setattr__" => "Implement setattr(self, name, value).", + "_sha3.sha3_384.__sizeof__" => "Size of object in memory, in bytes.", + "_sha3.sha3_384.__str__" => "Return str(self).", + "_sha3.sha3_384.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_sha3.sha3_384.copy" => "Return a copy of the hash object.", + "_sha3.sha3_384.digest" => "Return the digest value as a bytes object.", + "_sha3.sha3_384.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_sha3.sha3_384.update" => "Update this hash object's state with the provided bytes-like object.", + "_sha3.sha3_512" => "sha3_512([data], *, usedforsecurity=True) -> SHA3 object\n\nReturn a new SHA3 hash object with a hashbit length of 64 bytes.", + "_sha3.sha3_512.__delattr__" => "Implement delattr(self, name).", + "_sha3.sha3_512.__eq__" => "Return self==value.", + "_sha3.sha3_512.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_sha3.sha3_512.__ge__" => "Return self>=value.", + "_sha3.sha3_512.__getattribute__" => "Return getattr(self, name).", + "_sha3.sha3_512.__getstate__" => "Helper for pickle.", + "_sha3.sha3_512.__gt__" => "Return self>value.", + "_sha3.sha3_512.__hash__" => "Return hash(self).", + "_sha3.sha3_512.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_sha3.sha3_512.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_sha3.sha3_512.__le__" => "Return self<=value.", + "_sha3.sha3_512.__lt__" => "Return self<value.", + "_sha3.sha3_512.__ne__" => "Return self!=value.", + "_sha3.sha3_512.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_sha3.sha3_512.__reduce__" => "Helper for pickle.", + "_sha3.sha3_512.__reduce_ex__" => "Helper for pickle.", + "_sha3.sha3_512.__repr__" => "Return repr(self).", + "_sha3.sha3_512.__setattr__" => "Implement setattr(self, name, value).", + "_sha3.sha3_512.__sizeof__" => "Size of object in memory, in bytes.", + "_sha3.sha3_512.__str__" => "Return str(self).", + "_sha3.sha3_512.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_sha3.sha3_512.copy" => "Return a copy of the hash object.", + "_sha3.sha3_512.digest" => "Return the digest value as a bytes object.", + "_sha3.sha3_512.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_sha3.sha3_512.update" => "Update this hash object's state with the provided bytes-like object.", + "_sha3.shake_128" => "shake_128([data], *, usedforsecurity=True) -> SHAKE object\n\nReturn a new SHAKE hash object.", + "_sha3.shake_128.__delattr__" => "Implement delattr(self, name).", + "_sha3.shake_128.__eq__" => "Return self==value.", + "_sha3.shake_128.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_sha3.shake_128.__ge__" => "Return self>=value.", + "_sha3.shake_128.__getattribute__" => "Return getattr(self, name).", + "_sha3.shake_128.__getstate__" => "Helper for pickle.", + "_sha3.shake_128.__gt__" => "Return self>value.", + "_sha3.shake_128.__hash__" => "Return hash(self).", + "_sha3.shake_128.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_sha3.shake_128.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_sha3.shake_128.__le__" => "Return self<=value.", + "_sha3.shake_128.__lt__" => "Return self<value.", + "_sha3.shake_128.__ne__" => "Return self!=value.", + "_sha3.shake_128.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_sha3.shake_128.__reduce__" => "Helper for pickle.", + "_sha3.shake_128.__reduce_ex__" => "Helper for pickle.", + "_sha3.shake_128.__repr__" => "Return repr(self).", + "_sha3.shake_128.__setattr__" => "Implement setattr(self, name, value).", + "_sha3.shake_128.__sizeof__" => "Size of object in memory, in bytes.", + "_sha3.shake_128.__str__" => "Return str(self).", + "_sha3.shake_128.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_sha3.shake_128.copy" => "Return a copy of the hash object.", + "_sha3.shake_128.digest" => "Return the digest value as a bytes object.", + "_sha3.shake_128.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_sha3.shake_128.update" => "Update this hash object's state with the provided bytes-like object.", + "_sha3.shake_256" => "shake_256([data], *, usedforsecurity=True) -> SHAKE object\n\nReturn a new SHAKE hash object.", + "_sha3.shake_256.__delattr__" => "Implement delattr(self, name).", + "_sha3.shake_256.__eq__" => "Return self==value.", + "_sha3.shake_256.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_sha3.shake_256.__ge__" => "Return self>=value.", + "_sha3.shake_256.__getattribute__" => "Return getattr(self, name).", + "_sha3.shake_256.__getstate__" => "Helper for pickle.", + "_sha3.shake_256.__gt__" => "Return self>value.", + "_sha3.shake_256.__hash__" => "Return hash(self).", + "_sha3.shake_256.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_sha3.shake_256.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_sha3.shake_256.__le__" => "Return self<=value.", + "_sha3.shake_256.__lt__" => "Return self<value.", + "_sha3.shake_256.__ne__" => "Return self!=value.", + "_sha3.shake_256.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_sha3.shake_256.__reduce__" => "Helper for pickle.", + "_sha3.shake_256.__reduce_ex__" => "Helper for pickle.", + "_sha3.shake_256.__repr__" => "Return repr(self).", + "_sha3.shake_256.__setattr__" => "Implement setattr(self, name, value).", + "_sha3.shake_256.__sizeof__" => "Size of object in memory, in bytes.", + "_sha3.shake_256.__str__" => "Return str(self).", + "_sha3.shake_256.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_sha3.shake_256.copy" => "Return a copy of the hash object.", + "_sha3.shake_256.digest" => "Return the digest value as a bytes object.", + "_sha3.shake_256.hexdigest" => "Return the digest value as a string of hexadecimal digits.", + "_sha3.shake_256.update" => "Update this hash object's state with the provided bytes-like object.", + "_signal" => "This module provides mechanisms to use signal handlers in Python.\n\nFunctions:\n\nalarm() -- cause SIGALRM after a specified time [Unix only]\nsetitimer() -- cause a signal (described below) after a specified\n float time and the timer may restart then [Unix only]\ngetitimer() -- get current value of timer [Unix only]\nsignal() -- set the action for a given signal\ngetsignal() -- get the signal action for a given signal\npause() -- wait until a signal arrives [Unix only]\ndefault_int_handler() -- default SIGINT handler\n\nsignal constants:\nSIG_DFL -- used to refer to the system default handler\nSIG_IGN -- used to ignore the signal\nNSIG -- number of defined signals\nSIGINT, SIGTERM, etc. -- signal numbers\n\nitimer constants:\nITIMER_REAL -- decrements in real time, and delivers SIGALRM upon\n expiration\nITIMER_VIRTUAL -- decrements only when the process is executing,\n and delivers SIGVTALRM upon expiration\nITIMER_PROF -- decrements both when the process is executing and\n when the system is executing on behalf of the process.\n Coupled with ITIMER_VIRTUAL, this timer is usually\n used to profile the time spent by the application\n in user and kernel space. SIGPROF is delivered upon\n expiration.\n\n\n*** IMPORTANT NOTICE ***\nA signal handler function is called with two arguments:\nthe first is the signal number, the second is the interrupted stack frame.", + "_signal.alarm" => "Arrange for SIGALRM to arrive after the given number of seconds.", + "_signal.default_int_handler" => "The default handler for SIGINT installed by Python.\n\nIt raises KeyboardInterrupt.", + "_signal.getitimer" => "Returns current value of given itimer.", + "_signal.getsignal" => "Return the current action for the given signal.\n\nThe return value can be:\n SIG_IGN -- if the signal is being ignored\n SIG_DFL -- if the default action for the signal is in effect\n None -- if an unknown handler is in effect\n anything else -- the callable Python object used as a handler", + "_signal.pause" => "Wait until a signal arrives.", + "_signal.pidfd_send_signal" => "Send a signal to a process referred to by a pid file descriptor.", + "_signal.pthread_kill" => "Send a signal to a thread.", + "_signal.pthread_sigmask" => "Fetch and/or change the signal mask of the calling thread.", + "_signal.raise_signal" => "Send a signal to the executing process.", + "_signal.set_wakeup_fd" => "Sets the fd to be written to (with the signal number) when a signal comes in.\n\nA library can use this to wakeup select or poll.\nThe previous fd or -1 is returned.\n\nThe fd must be non-blocking.", + "_signal.setitimer" => "Sets given itimer (one of ITIMER_REAL, ITIMER_VIRTUAL or ITIMER_PROF).\n\nThe timer will fire after value seconds and after that every interval seconds.\nThe itimer can be cleared by setting seconds to zero.\n\nReturns old values as a tuple: (delay, interval).", + "_signal.siginterrupt" => "Change system call restart behaviour.\n\nIf flag is False, system calls will be restarted when interrupted by\nsignal sig, else system calls will be interrupted.", + "_signal.signal" => "Set the action for the given signal.\n\nThe action can be SIG_DFL, SIG_IGN, or a callable Python object.\nThe previous action is returned. See getsignal() for possible return values.\n\n*** IMPORTANT NOTICE ***\nA signal handler function is called with two arguments:\nthe first is the signal number, the second is the interrupted stack frame.", + "_signal.sigpending" => "Examine pending signals.\n\nReturns a set of signal numbers that are pending for delivery to\nthe calling thread.", + "_signal.sigtimedwait" => "Like sigwaitinfo(), but with a timeout.\n\nThe timeout is specified in seconds, with floating-point numbers allowed.", + "_signal.sigwait" => "Wait for a signal.\n\nSuspend execution of the calling thread until the delivery of one of the\nsignals specified in the signal set sigset. The function accepts the signal\nand returns the signal number.", + "_signal.sigwaitinfo" => "Wait synchronously until one of the signals in *sigset* is delivered.\n\nReturns a struct_siginfo containing information about the signal.", + "_signal.strsignal" => "Return the system description of the given signal.\n\nReturns the description of signal *signalnum*, such as \"Interrupt\"\nfor :const:`SIGINT`. Returns :const:`None` if *signalnum* has no\ndescription. Raises :exc:`ValueError` if *signalnum* is invalid.", + "_signal.valid_signals" => "Return a set of valid signal numbers on this platform.\n\nThe signal numbers returned by this function can be safely passed to\nfunctions like `pthread_sigmask`.", + "_socket" => "Implementation module for socket operations.\n\nSee the socket module for documentation.", + "_socket.CMSG_LEN" => "CMSG_LEN(length) -> control message length\n\nReturn the total length, without trailing padding, of an ancillary\ndata item with associated data of the given length. This value can\noften be used as the buffer size for recvmsg() to receive a single\nitem of ancillary data, but RFC 3542 requires portable applications to\nuse CMSG_SPACE() and thus include space for padding, even when the\nitem will be the last in the buffer. Raises OverflowError if length\nis outside the permissible range of values.", + "_socket.CMSG_SPACE" => "CMSG_SPACE(length) -> buffer size\n\nReturn the buffer size needed for recvmsg() to receive an ancillary\ndata item with associated data of the given length, along with any\ntrailing padding. The buffer space needed to receive multiple items\nis the sum of the CMSG_SPACE() values for their associated data\nlengths. Raises OverflowError if length is outside the permissible\nrange of values.", + "_socket.SocketType" => "socket(family=AF_INET, type=SOCK_STREAM, proto=0) -> socket object\nsocket(family=-1, type=-1, proto=-1, fileno=None) -> socket object\n\nOpen a socket of the given type. The family argument specifies the\naddress family; it defaults to AF_INET. The type argument specifies\nwhether this is a stream (SOCK_STREAM, this is the default)\nor datagram (SOCK_DGRAM) socket. The protocol argument defaults to 0,\nspecifying the default protocol. Keyword arguments are accepted.\nThe socket is created as non-inheritable.\n\nWhen a fileno is passed in, family, type and proto are auto-detected,\nunless they are explicitly set.\n\nA socket object represents one endpoint of a network connection.\n\nMethods of socket objects (keyword arguments not allowed):\n\n_accept() -- accept connection, returning new socket fd and client address\nbind(addr) -- bind the socket to a local address\nclose() -- close the socket\nconnect(addr) -- connect the socket to a remote address\nconnect_ex(addr) -- connect, return an error code instead of an exception\ndup() -- return a new socket fd duplicated from fileno()\nfileno() -- return underlying file descriptor\ngetpeername() -- return remote address [*]\ngetsockname() -- return local address\ngetsockopt(level, optname[, buflen]) -- get socket options\ngettimeout() -- return timeout or None\nlisten([n]) -- start listening for incoming connections\nrecv(buflen[, flags]) -- receive data\nrecv_into(buffer[, nbytes[, flags]]) -- receive data (into a buffer)\nrecvfrom(buflen[, flags]) -- receive data and sender's address\nrecvfrom_into(buffer[, nbytes, [, flags])\n -- receive data and sender's address (into a buffer)\nsendall(data[, flags]) -- send all data\nsend(data[, flags]) -- send data, may not send all of it\nsendto(data[, flags], addr) -- send data to a given address\nsetblocking(bool) -- set or clear the blocking I/O flag\ngetblocking() -- return True if socket is blocking, False if non-blocking\nsetsockopt(level, optname, value[, optlen]) -- set socket options\nsettimeout(None | float) -- set or clear the timeout\nshutdown(how) -- shut down traffic in one or both directions\n\n [*] not available on all platforms!", + "_socket.SocketType.__del__" => "Called when the instance is about to be destroyed.", + "_socket.SocketType.__delattr__" => "Implement delattr(self, name).", + "_socket.SocketType.__eq__" => "Return self==value.", + "_socket.SocketType.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_socket.SocketType.__ge__" => "Return self>=value.", + "_socket.SocketType.__getattribute__" => "Return getattr(self, name).", + "_socket.SocketType.__getstate__" => "Helper for pickle.", + "_socket.SocketType.__gt__" => "Return self>value.", + "_socket.SocketType.__hash__" => "Return hash(self).", + "_socket.SocketType.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_socket.SocketType.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_socket.SocketType.__le__" => "Return self<=value.", + "_socket.SocketType.__lt__" => "Return self<value.", + "_socket.SocketType.__ne__" => "Return self!=value.", + "_socket.SocketType.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_socket.SocketType.__reduce__" => "Helper for pickle.", + "_socket.SocketType.__reduce_ex__" => "Helper for pickle.", + "_socket.SocketType.__repr__" => "Return repr(self).", + "_socket.SocketType.__setattr__" => "Implement setattr(self, name, value).", + "_socket.SocketType.__sizeof__" => "Size of object in memory, in bytes.", + "_socket.SocketType.__str__" => "Return str(self).", + "_socket.SocketType.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_socket.SocketType._accept" => "_accept() -> (integer, address info)\n\nWait for an incoming connection. Return a new socket file descriptor\nrepresenting the connection, and the address of the client.\nFor IP sockets, the address info is a pair (hostaddr, port).", + "_socket.SocketType.bind" => "bind(address)\n\nBind the socket to a local address. For IP sockets, the address is a\npair (host, port); the host must refer to the local host. For raw packet\nsockets the address is a tuple (ifname, proto [,pkttype [,hatype [,addr]]])", + "_socket.SocketType.close" => "close()\n\nClose the socket. It cannot be used after this call.", + "_socket.SocketType.connect" => "connect(address)\n\nConnect the socket to a remote address. For IP sockets, the address\nis a pair (host, port).", + "_socket.SocketType.connect_ex" => "connect_ex(address) -> errno\n\nThis is like connect(address), but returns an error code (the errno value)\ninstead of raising an exception when an error occurs.", + "_socket.SocketType.detach" => "detach()\n\nClose the socket object without closing the underlying file descriptor.\nThe object cannot be used after this call, but the file descriptor\ncan be reused for other purposes. The file descriptor is returned.", + "_socket.SocketType.family" => "the socket family", + "_socket.SocketType.fileno" => "fileno() -> integer\n\nReturn the integer file descriptor of the socket.", + "_socket.SocketType.getblocking" => "getblocking()\n\nReturns True if socket is in blocking mode, or False if it\nis in non-blocking mode.", + "_socket.SocketType.getpeername" => "getpeername() -> address info\n\nReturn the address of the remote endpoint. For IP sockets, the address\ninfo is a pair (hostaddr, port).", + "_socket.SocketType.getsockname" => "getsockname() -> address info\n\nReturn the address of the local endpoint. The format depends on the\naddress family. For IPv4 sockets, the address info is a pair\n(hostaddr, port). For IPv6 sockets, the address info is a 4-tuple\n(hostaddr, port, flowinfo, scope_id).", + "_socket.SocketType.getsockopt" => "getsockopt(level, option[, buffersize]) -> value\n\nGet a socket option. See the Unix manual for level and option.\nIf a nonzero buffersize argument is given, the return value is a\nstring of that length; otherwise it is an integer.", + "_socket.SocketType.gettimeout" => "gettimeout() -> timeout\n\nReturns the timeout in seconds (float) associated with socket\noperations. A timeout of None indicates that timeouts on socket\noperations are disabled.", + "_socket.SocketType.ioctl" => "ioctl(cmd, option) -> long\n\nControl the socket with WSAIoctl syscall. Currently supported 'cmd' values are\nSIO_RCVALL: 'option' must be one of the socket.RCVALL_* constants.\nSIO_KEEPALIVE_VALS: 'option' is a tuple of (onoff, timeout, interval).\nSIO_LOOPBACK_FAST_PATH: 'option' is a boolean value, and is disabled by default", + "_socket.SocketType.listen" => "listen([backlog])\n\nEnable a server to accept connections. If backlog is specified, it must be\nat least 0 (if it is lower, it is set to 0); it specifies the number of\nunaccepted connections that the system will allow before refusing new\nconnections. If not specified, a default reasonable value is chosen.", + "_socket.SocketType.proto" => "the socket protocol", + "_socket.SocketType.recv" => "recv(buffersize[, flags]) -> data\n\nReceive up to buffersize bytes from the socket. For the optional flags\nargument, see the Unix manual. When no data is available, block until\nat least one byte is available or until the remote end is closed. When\nthe remote end is closed and all data is read, return the empty string.", + "_socket.SocketType.recv_into" => "recv_into(buffer, [nbytes[, flags]]) -> nbytes_read\n\nA version of recv() that stores its data into a buffer rather than creating\na new string. Receive up to buffersize bytes from the socket. If buffersize\nis not specified (or 0), receive up to the size available in the given buffer.\n\nSee recv() for documentation about the flags.", + "_socket.SocketType.recvfrom" => "recvfrom(buffersize[, flags]) -> (data, address info)\n\nLike recv(buffersize, flags) but also return the sender's address info.", + "_socket.SocketType.recvfrom_into" => "recvfrom_into(buffer[, nbytes[, flags]]) -> (nbytes, address info)\n\nLike recv_into(buffer[, nbytes[, flags]]) but also return the sender's address info.", + "_socket.SocketType.recvmsg" => "recvmsg(bufsize[, ancbufsize[, flags]]) -> (data, ancdata, msg_flags, address)\n\nReceive normal data (up to bufsize bytes) and ancillary data from the\nsocket. The ancbufsize argument sets the size in bytes of the\ninternal buffer used to receive the ancillary data; it defaults to 0,\nmeaning that no ancillary data will be received. Appropriate buffer\nsizes for ancillary data can be calculated using CMSG_SPACE() or\nCMSG_LEN(), and items which do not fit into the buffer might be\ntruncated or discarded. The flags argument defaults to 0 and has the\nsame meaning as for recv().\n\nThe return value is a 4-tuple: (data, ancdata, msg_flags, address).\nThe data item is a bytes object holding the non-ancillary data\nreceived. The ancdata item is a list of zero or more tuples\n(cmsg_level, cmsg_type, cmsg_data) representing the ancillary data\n(control messages) received: cmsg_level and cmsg_type are integers\nspecifying the protocol level and protocol-specific type respectively,\nand cmsg_data is a bytes object holding the associated data. The\nmsg_flags item is the bitwise OR of various flags indicating\nconditions on the received message; see your system documentation for\ndetails. If the receiving socket is unconnected, address is the\naddress of the sending socket, if available; otherwise, its value is\nunspecified.\n\nIf recvmsg() raises an exception after the system call returns, it\nwill first attempt to close any file descriptors received via the\nSCM_RIGHTS mechanism.", + "_socket.SocketType.recvmsg_into" => "recvmsg_into(buffers[, ancbufsize[, flags]]) -> (nbytes, ancdata, msg_flags, address)\n\nReceive normal data and ancillary data from the socket, scattering the\nnon-ancillary data into a series of buffers. The buffers argument\nmust be an iterable of objects that export writable buffers\n(e.g. bytearray objects); these will be filled with successive chunks\nof the non-ancillary data until it has all been written or there are\nno more buffers. The ancbufsize argument sets the size in bytes of\nthe internal buffer used to receive the ancillary data; it defaults to\n0, meaning that no ancillary data will be received. Appropriate\nbuffer sizes for ancillary data can be calculated using CMSG_SPACE()\nor CMSG_LEN(), and items which do not fit into the buffer might be\ntruncated or discarded. The flags argument defaults to 0 and has the\nsame meaning as for recv().\n\nThe return value is a 4-tuple: (nbytes, ancdata, msg_flags, address).\nThe nbytes item is the total number of bytes of non-ancillary data\nwritten into the buffers. The ancdata item is a list of zero or more\ntuples (cmsg_level, cmsg_type, cmsg_data) representing the ancillary\ndata (control messages) received: cmsg_level and cmsg_type are\nintegers specifying the protocol level and protocol-specific type\nrespectively, and cmsg_data is a bytes object holding the associated\ndata. The msg_flags item is the bitwise OR of various flags\nindicating conditions on the received message; see your system\ndocumentation for details. If the receiving socket is unconnected,\naddress is the address of the sending socket, if available; otherwise,\nits value is unspecified.\n\nIf recvmsg_into() raises an exception after the system call returns,\nit will first attempt to close any file descriptors received via the\nSCM_RIGHTS mechanism.", + "_socket.SocketType.send" => "send(data[, flags]) -> count\n\nSend a data string to the socket. For the optional flags\nargument, see the Unix manual. Return the number of bytes\nsent; this may be less than len(data) if the network is busy.", + "_socket.SocketType.sendall" => "sendall(data[, flags])\n\nSend a data string to the socket. For the optional flags\nargument, see the Unix manual. This calls send() repeatedly\nuntil all data is sent. If an error occurs, it's impossible\nto tell how much data has been sent.", + "_socket.SocketType.sendmsg" => "sendmsg(buffers[, ancdata[, flags[, address]]]) -> count\n\nSend normal and ancillary data to the socket, gathering the\nnon-ancillary data from a series of buffers and concatenating it into\na single message. The buffers argument specifies the non-ancillary\ndata as an iterable of bytes-like objects (e.g. bytes objects).\nThe ancdata argument specifies the ancillary data (control messages)\nas an iterable of zero or more tuples (cmsg_level, cmsg_type,\ncmsg_data), where cmsg_level and cmsg_type are integers specifying the\nprotocol level and protocol-specific type respectively, and cmsg_data\nis a bytes-like object holding the associated data. The flags\nargument defaults to 0 and has the same meaning as for send(). If\naddress is supplied and not None, it sets a destination address for\nthe message. The return value is the number of bytes of non-ancillary\ndata sent.", + "_socket.SocketType.sendmsg_afalg" => "sendmsg_afalg([msg], *, op[, iv[, assoclen[, flags=MSG_MORE]]])\n\nSet operation mode, IV and length of associated data for an AF_ALG\noperation socket.", + "_socket.SocketType.sendto" => "sendto(data[, flags], address) -> count\n\nLike send(data, flags) but allows specifying the destination address.\nFor IP sockets, the address is a pair (hostaddr, port).", + "_socket.SocketType.setblocking" => "setblocking(flag)\n\nSet the socket to blocking (flag is true) or non-blocking (false).\nsetblocking(True) is equivalent to settimeout(None);\nsetblocking(False) is equivalent to settimeout(0.0).", + "_socket.SocketType.setsockopt" => "setsockopt(level, option, value: int)\nsetsockopt(level, option, value: buffer)\nsetsockopt(level, option, None, optlen: int)\n\nSet a socket option. See the Unix manual for level and option.\nThe value argument can either be an integer, a string buffer, or\nNone, optlen.", + "_socket.SocketType.settimeout" => "settimeout(timeout)\n\nSet a timeout on socket operations. 'timeout' can be a float,\ngiving in seconds, or None. Setting a timeout of None disables\nthe timeout feature and is equivalent to setblocking(1).\nSetting a timeout of zero is the same as setblocking(0).", + "_socket.SocketType.share" => "share(process_id) -> bytes\n\nShare the socket with another process. The target process id\nmust be provided and the resulting bytes object passed to the target\nprocess. There the shared socket can be instantiated by calling\nsocket.fromshare().", + "_socket.SocketType.shutdown" => "shutdown(flag)\n\nShut down the reading side of the socket (flag == SHUT_RD), the writing side\nof the socket (flag == SHUT_WR), or both ends (flag == SHUT_RDWR).", + "_socket.SocketType.timeout" => "the socket timeout", + "_socket.SocketType.type" => "the socket type", + "_socket.close" => "close(integer) -> None\n\nClose an integer socket file descriptor. This is like os.close(), but for\nsockets; on some platforms os.close() won't work for socket file descriptors.", + "_socket.dup" => "dup(integer) -> integer\n\nDuplicate an integer socket file descriptor. This is like os.dup(), but for\nsockets; on some platforms os.dup() won't work for socket file descriptors.", + "_socket.getaddrinfo" => "getaddrinfo(host, port [, family, type, proto, flags])\n -> list of (family, type, proto, canonname, sockaddr)\n\nResolve host and port into addrinfo struct.", + "_socket.getdefaulttimeout" => "getdefaulttimeout() -> timeout\n\nReturns the default timeout in seconds (float) for new socket objects.\nA value of None indicates that new socket objects have no timeout.\nWhen the socket module is first imported, the default is None.", + "_socket.gethostbyaddr" => "gethostbyaddr(host) -> (name, aliaslist, addresslist)\n\nReturn the true host name, a list of aliases, and a list of IP addresses,\nfor a host. The host argument is a string giving a host name or IP number.", + "_socket.gethostbyname" => "gethostbyname(host) -> address\n\nReturn the IP address (a string of the form '255.255.255.255') for a host.", + "_socket.gethostbyname_ex" => "gethostbyname_ex(host) -> (name, aliaslist, addresslist)\n\nReturn the true host name, a list of aliases, and a list of IP addresses,\nfor a host. The host argument is a string giving a host name or IP number.", + "_socket.gethostname" => "gethostname() -> string\n\nReturn the current host name.", + "_socket.getnameinfo" => "getnameinfo(sockaddr, flags) --> (host, port)\n\nGet host and port for a sockaddr.", + "_socket.getprotobyname" => "getprotobyname(name) -> integer\n\nReturn the protocol number for the named protocol. (Rarely used.)", + "_socket.getservbyname" => "getservbyname(servicename[, protocolname]) -> integer\n\nReturn a port number from a service name and protocol name.\nThe optional protocol name, if given, should be 'tcp' or 'udp',\notherwise any protocol will match.", + "_socket.getservbyport" => "getservbyport(port[, protocolname]) -> string\n\nReturn the service name from a port number and protocol name.\nThe optional protocol name, if given, should be 'tcp' or 'udp',\notherwise any protocol will match.", + "_socket.htonl" => "htonl(integer) -> integer\n\nConvert a 32-bit integer from host to network byte order.", + "_socket.htons" => "Convert a 16-bit unsigned integer from host to network byte order.", + "_socket.if_indextoname" => "if_indextoname(if_index)\n\nReturns the interface name corresponding to the interface index if_index.", + "_socket.if_nameindex" => "if_nameindex()\n\nReturns a list of network interface information (index, name) tuples.", + "_socket.if_nametoindex" => "Returns the interface index corresponding to the interface name if_name.", + "_socket.inet_aton" => "Convert an IP address in string format (123.45.67.89) to the 32-bit packed binary format used in low-level network functions.", + "_socket.inet_ntoa" => "Convert an IP address from 32-bit packed binary format to string format.", + "_socket.inet_ntop" => "inet_ntop(af, packed_ip) -> string formatted IP address\n\nConvert a packed IP address of the given family to string format.", + "_socket.inet_pton" => "inet_pton(af, ip) -> packed IP address string\n\nConvert an IP address from string format to a packed string suitable\nfor use with low-level network functions.", + "_socket.ntohl" => "ntohl(integer) -> integer\n\nConvert a 32-bit integer from network to host byte order.", + "_socket.ntohs" => "Convert a 16-bit unsigned integer from network to host byte order.", + "_socket.setdefaulttimeout" => "setdefaulttimeout(timeout)\n\nSet the default timeout in seconds (float) for new socket objects.\nA value of None indicates that new socket objects have no timeout.\nWhen the socket module is first imported, the default is None.", + "_socket.sethostname" => "sethostname(name)\n\nSets the hostname to name.", + "_socket.socket" => "socket(family=AF_INET, type=SOCK_STREAM, proto=0) -> socket object\nsocket(family=-1, type=-1, proto=-1, fileno=None) -> socket object\n\nOpen a socket of the given type. The family argument specifies the\naddress family; it defaults to AF_INET. The type argument specifies\nwhether this is a stream (SOCK_STREAM, this is the default)\nor datagram (SOCK_DGRAM) socket. The protocol argument defaults to 0,\nspecifying the default protocol. Keyword arguments are accepted.\nThe socket is created as non-inheritable.\n\nWhen a fileno is passed in, family, type and proto are auto-detected,\nunless they are explicitly set.\n\nA socket object represents one endpoint of a network connection.\n\nMethods of socket objects (keyword arguments not allowed):\n\n_accept() -- accept connection, returning new socket fd and client address\nbind(addr) -- bind the socket to a local address\nclose() -- close the socket\nconnect(addr) -- connect the socket to a remote address\nconnect_ex(addr) -- connect, return an error code instead of an exception\ndup() -- return a new socket fd duplicated from fileno()\nfileno() -- return underlying file descriptor\ngetpeername() -- return remote address [*]\ngetsockname() -- return local address\ngetsockopt(level, optname[, buflen]) -- get socket options\ngettimeout() -- return timeout or None\nlisten([n]) -- start listening for incoming connections\nrecv(buflen[, flags]) -- receive data\nrecv_into(buffer[, nbytes[, flags]]) -- receive data (into a buffer)\nrecvfrom(buflen[, flags]) -- receive data and sender's address\nrecvfrom_into(buffer[, nbytes, [, flags])\n -- receive data and sender's address (into a buffer)\nsendall(data[, flags]) -- send all data\nsend(data[, flags]) -- send data, may not send all of it\nsendto(data[, flags], addr) -- send data to a given address\nsetblocking(bool) -- set or clear the blocking I/O flag\ngetblocking() -- return True if socket is blocking, False if non-blocking\nsetsockopt(level, optname, value[, optlen]) -- set socket options\nsettimeout(None | float) -- set or clear the timeout\nshutdown(how) -- shut down traffic in one or both directions\n\n [*] not available on all platforms!", + "_socket.socket.__del__" => "Called when the instance is about to be destroyed.", + "_socket.socket.__delattr__" => "Implement delattr(self, name).", + "_socket.socket.__eq__" => "Return self==value.", + "_socket.socket.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_socket.socket.__ge__" => "Return self>=value.", + "_socket.socket.__getattribute__" => "Return getattr(self, name).", + "_socket.socket.__getstate__" => "Helper for pickle.", + "_socket.socket.__gt__" => "Return self>value.", + "_socket.socket.__hash__" => "Return hash(self).", + "_socket.socket.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_socket.socket.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_socket.socket.__le__" => "Return self<=value.", + "_socket.socket.__lt__" => "Return self<value.", + "_socket.socket.__ne__" => "Return self!=value.", + "_socket.socket.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_socket.socket.__reduce__" => "Helper for pickle.", + "_socket.socket.__reduce_ex__" => "Helper for pickle.", + "_socket.socket.__repr__" => "Return repr(self).", + "_socket.socket.__setattr__" => "Implement setattr(self, name, value).", + "_socket.socket.__sizeof__" => "Size of object in memory, in bytes.", + "_socket.socket.__str__" => "Return str(self).", + "_socket.socket.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_socket.socket._accept" => "_accept() -> (integer, address info)\n\nWait for an incoming connection. Return a new socket file descriptor\nrepresenting the connection, and the address of the client.\nFor IP sockets, the address info is a pair (hostaddr, port).", + "_socket.socket.bind" => "bind(address)\n\nBind the socket to a local address. For IP sockets, the address is a\npair (host, port); the host must refer to the local host. For raw packet\nsockets the address is a tuple (ifname, proto [,pkttype [,hatype [,addr]]])", + "_socket.socket.close" => "close()\n\nClose the socket. It cannot be used after this call.", + "_socket.socket.connect" => "connect(address)\n\nConnect the socket to a remote address. For IP sockets, the address\nis a pair (host, port).", + "_socket.socket.connect_ex" => "connect_ex(address) -> errno\n\nThis is like connect(address), but returns an error code (the errno value)\ninstead of raising an exception when an error occurs.", + "_socket.socket.detach" => "detach()\n\nClose the socket object without closing the underlying file descriptor.\nThe object cannot be used after this call, but the file descriptor\ncan be reused for other purposes. The file descriptor is returned.", + "_socket.socket.family" => "the socket family", + "_socket.socket.fileno" => "fileno() -> integer\n\nReturn the integer file descriptor of the socket.", + "_socket.socket.getblocking" => "getblocking()\n\nReturns True if socket is in blocking mode, or False if it\nis in non-blocking mode.", + "_socket.socket.getpeername" => "getpeername() -> address info\n\nReturn the address of the remote endpoint. For IP sockets, the address\ninfo is a pair (hostaddr, port).", + "_socket.socket.getsockname" => "getsockname() -> address info\n\nReturn the address of the local endpoint. The format depends on the\naddress family. For IPv4 sockets, the address info is a pair\n(hostaddr, port). For IPv6 sockets, the address info is a 4-tuple\n(hostaddr, port, flowinfo, scope_id).", + "_socket.socket.getsockopt" => "getsockopt(level, option[, buffersize]) -> value\n\nGet a socket option. See the Unix manual for level and option.\nIf a nonzero buffersize argument is given, the return value is a\nstring of that length; otherwise it is an integer.", + "_socket.socket.gettimeout" => "gettimeout() -> timeout\n\nReturns the timeout in seconds (float) associated with socket\noperations. A timeout of None indicates that timeouts on socket\noperations are disabled.", + "_socket.socket.ioctl" => "ioctl(cmd, option) -> long\n\nControl the socket with WSAIoctl syscall. Currently supported 'cmd' values are\nSIO_RCVALL: 'option' must be one of the socket.RCVALL_* constants.\nSIO_KEEPALIVE_VALS: 'option' is a tuple of (onoff, timeout, interval).\nSIO_LOOPBACK_FAST_PATH: 'option' is a boolean value, and is disabled by default", + "_socket.socket.listen" => "listen([backlog])\n\nEnable a server to accept connections. If backlog is specified, it must be\nat least 0 (if it is lower, it is set to 0); it specifies the number of\nunaccepted connections that the system will allow before refusing new\nconnections. If not specified, a default reasonable value is chosen.", + "_socket.socket.proto" => "the socket protocol", + "_socket.socket.recv" => "recv(buffersize[, flags]) -> data\n\nReceive up to buffersize bytes from the socket. For the optional flags\nargument, see the Unix manual. When no data is available, block until\nat least one byte is available or until the remote end is closed. When\nthe remote end is closed and all data is read, return the empty string.", + "_socket.socket.recv_into" => "recv_into(buffer, [nbytes[, flags]]) -> nbytes_read\n\nA version of recv() that stores its data into a buffer rather than creating\na new string. Receive up to buffersize bytes from the socket. If buffersize\nis not specified (or 0), receive up to the size available in the given buffer.\n\nSee recv() for documentation about the flags.", + "_socket.socket.recvfrom" => "recvfrom(buffersize[, flags]) -> (data, address info)\n\nLike recv(buffersize, flags) but also return the sender's address info.", + "_socket.socket.recvfrom_into" => "recvfrom_into(buffer[, nbytes[, flags]]) -> (nbytes, address info)\n\nLike recv_into(buffer[, nbytes[, flags]]) but also return the sender's address info.", + "_socket.socket.recvmsg" => "recvmsg(bufsize[, ancbufsize[, flags]]) -> (data, ancdata, msg_flags, address)\n\nReceive normal data (up to bufsize bytes) and ancillary data from the\nsocket. The ancbufsize argument sets the size in bytes of the\ninternal buffer used to receive the ancillary data; it defaults to 0,\nmeaning that no ancillary data will be received. Appropriate buffer\nsizes for ancillary data can be calculated using CMSG_SPACE() or\nCMSG_LEN(), and items which do not fit into the buffer might be\ntruncated or discarded. The flags argument defaults to 0 and has the\nsame meaning as for recv().\n\nThe return value is a 4-tuple: (data, ancdata, msg_flags, address).\nThe data item is a bytes object holding the non-ancillary data\nreceived. The ancdata item is a list of zero or more tuples\n(cmsg_level, cmsg_type, cmsg_data) representing the ancillary data\n(control messages) received: cmsg_level and cmsg_type are integers\nspecifying the protocol level and protocol-specific type respectively,\nand cmsg_data is a bytes object holding the associated data. The\nmsg_flags item is the bitwise OR of various flags indicating\nconditions on the received message; see your system documentation for\ndetails. If the receiving socket is unconnected, address is the\naddress of the sending socket, if available; otherwise, its value is\nunspecified.\n\nIf recvmsg() raises an exception after the system call returns, it\nwill first attempt to close any file descriptors received via the\nSCM_RIGHTS mechanism.", + "_socket.socket.recvmsg_into" => "recvmsg_into(buffers[, ancbufsize[, flags]]) -> (nbytes, ancdata, msg_flags, address)\n\nReceive normal data and ancillary data from the socket, scattering the\nnon-ancillary data into a series of buffers. The buffers argument\nmust be an iterable of objects that export writable buffers\n(e.g. bytearray objects); these will be filled with successive chunks\nof the non-ancillary data until it has all been written or there are\nno more buffers. The ancbufsize argument sets the size in bytes of\nthe internal buffer used to receive the ancillary data; it defaults to\n0, meaning that no ancillary data will be received. Appropriate\nbuffer sizes for ancillary data can be calculated using CMSG_SPACE()\nor CMSG_LEN(), and items which do not fit into the buffer might be\ntruncated or discarded. The flags argument defaults to 0 and has the\nsame meaning as for recv().\n\nThe return value is a 4-tuple: (nbytes, ancdata, msg_flags, address).\nThe nbytes item is the total number of bytes of non-ancillary data\nwritten into the buffers. The ancdata item is a list of zero or more\ntuples (cmsg_level, cmsg_type, cmsg_data) representing the ancillary\ndata (control messages) received: cmsg_level and cmsg_type are\nintegers specifying the protocol level and protocol-specific type\nrespectively, and cmsg_data is a bytes object holding the associated\ndata. The msg_flags item is the bitwise OR of various flags\nindicating conditions on the received message; see your system\ndocumentation for details. If the receiving socket is unconnected,\naddress is the address of the sending socket, if available; otherwise,\nits value is unspecified.\n\nIf recvmsg_into() raises an exception after the system call returns,\nit will first attempt to close any file descriptors received via the\nSCM_RIGHTS mechanism.", + "_socket.socket.send" => "send(data[, flags]) -> count\n\nSend a data string to the socket. For the optional flags\nargument, see the Unix manual. Return the number of bytes\nsent; this may be less than len(data) if the network is busy.", + "_socket.socket.sendall" => "sendall(data[, flags])\n\nSend a data string to the socket. For the optional flags\nargument, see the Unix manual. This calls send() repeatedly\nuntil all data is sent. If an error occurs, it's impossible\nto tell how much data has been sent.", + "_socket.socket.sendmsg" => "sendmsg(buffers[, ancdata[, flags[, address]]]) -> count\n\nSend normal and ancillary data to the socket, gathering the\nnon-ancillary data from a series of buffers and concatenating it into\na single message. The buffers argument specifies the non-ancillary\ndata as an iterable of bytes-like objects (e.g. bytes objects).\nThe ancdata argument specifies the ancillary data (control messages)\nas an iterable of zero or more tuples (cmsg_level, cmsg_type,\ncmsg_data), where cmsg_level and cmsg_type are integers specifying the\nprotocol level and protocol-specific type respectively, and cmsg_data\nis a bytes-like object holding the associated data. The flags\nargument defaults to 0 and has the same meaning as for send(). If\naddress is supplied and not None, it sets a destination address for\nthe message. The return value is the number of bytes of non-ancillary\ndata sent.", + "_socket.socket.sendmsg_afalg" => "sendmsg_afalg([msg], *, op[, iv[, assoclen[, flags=MSG_MORE]]])\n\nSet operation mode, IV and length of associated data for an AF_ALG\noperation socket.", + "_socket.socket.sendto" => "sendto(data[, flags], address) -> count\n\nLike send(data, flags) but allows specifying the destination address.\nFor IP sockets, the address is a pair (hostaddr, port).", + "_socket.socket.setblocking" => "setblocking(flag)\n\nSet the socket to blocking (flag is true) or non-blocking (false).\nsetblocking(True) is equivalent to settimeout(None);\nsetblocking(False) is equivalent to settimeout(0.0).", + "_socket.socket.setsockopt" => "setsockopt(level, option, value: int)\nsetsockopt(level, option, value: buffer)\nsetsockopt(level, option, None, optlen: int)\n\nSet a socket option. See the Unix manual for level and option.\nThe value argument can either be an integer, a string buffer, or\nNone, optlen.", + "_socket.socket.settimeout" => "settimeout(timeout)\n\nSet a timeout on socket operations. 'timeout' can be a float,\ngiving in seconds, or None. Setting a timeout of None disables\nthe timeout feature and is equivalent to setblocking(1).\nSetting a timeout of zero is the same as setblocking(0).", + "_socket.socket.share" => "share(process_id) -> bytes\n\nShare the socket with another process. The target process id\nmust be provided and the resulting bytes object passed to the target\nprocess. There the shared socket can be instantiated by calling\nsocket.fromshare().", + "_socket.socket.shutdown" => "shutdown(flag)\n\nShut down the reading side of the socket (flag == SHUT_RD), the writing side\nof the socket (flag == SHUT_WR), or both ends (flag == SHUT_RDWR).", + "_socket.socket.timeout" => "the socket timeout", + "_socket.socket.type" => "the socket type", + "_socket.socketpair" => "socketpair([family[, type [, proto]]]) -> (socket object, socket object)\n\nCreate a pair of socket objects from the sockets returned by the platform\nsocketpair() function.\nThe arguments are the same as for socket() except the default family is\nAF_UNIX if defined on the platform; otherwise, the default is AF_INET.", + "_sqlite3.adapt" => "Adapt given object to given protocol.", + "_sqlite3.complete_statement" => "Checks if a string contains a complete SQL statement.", + "_sqlite3.connect" => "Open a connection to the SQLite database file 'database'.\n\nYou can use \":memory:\" to open a database connection to a database that\nresides in RAM instead of on disk.\n\nNote: Passing more than 1 positional argument to _sqlite3.connect() is\ndeprecated. Parameters 'timeout', 'detect_types', 'isolation_level',\n'check_same_thread', 'factory', 'cached_statements' and 'uri' will\nbecome keyword-only parameters in Python 3.15.", + "_sqlite3.enable_callback_tracebacks" => "Enable or disable callback functions throwing errors to stderr.", + "_sqlite3.register_adapter" => "Register a function to adapt Python objects to SQLite values.", + "_sqlite3.register_converter" => "Register a function to convert SQLite values to Python objects.", + "_sre.template" => "template\n A list containing interleaved literal strings (str or bytes) and group\n indices (int), as returned by re._parser.parse_template():\n [literal1, group1, ..., literalN, groupN]", + "_ssl" => "Implementation module for SSL socket operations. See the socket module\nfor documentation.", + "_ssl.Certificate.__delattr__" => "Implement delattr(self, name).", + "_ssl.Certificate.__eq__" => "Return self==value.", + "_ssl.Certificate.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_ssl.Certificate.__ge__" => "Return self>=value.", + "_ssl.Certificate.__getattribute__" => "Return getattr(self, name).", + "_ssl.Certificate.__getstate__" => "Helper for pickle.", + "_ssl.Certificate.__gt__" => "Return self>value.", + "_ssl.Certificate.__hash__" => "Return hash(self).", + "_ssl.Certificate.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_ssl.Certificate.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_ssl.Certificate.__le__" => "Return self<=value.", + "_ssl.Certificate.__lt__" => "Return self<value.", + "_ssl.Certificate.__ne__" => "Return self!=value.", + "_ssl.Certificate.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_ssl.Certificate.__reduce__" => "Helper for pickle.", + "_ssl.Certificate.__reduce_ex__" => "Helper for pickle.", + "_ssl.Certificate.__repr__" => "Return repr(self).", + "_ssl.Certificate.__setattr__" => "Implement setattr(self, name, value).", + "_ssl.Certificate.__sizeof__" => "Size of object in memory, in bytes.", + "_ssl.Certificate.__str__" => "Return str(self).", + "_ssl.Certificate.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_ssl.MemoryBIO.__delattr__" => "Implement delattr(self, name).", + "_ssl.MemoryBIO.__eq__" => "Return self==value.", + "_ssl.MemoryBIO.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_ssl.MemoryBIO.__ge__" => "Return self>=value.", + "_ssl.MemoryBIO.__getattribute__" => "Return getattr(self, name).", + "_ssl.MemoryBIO.__getstate__" => "Helper for pickle.", + "_ssl.MemoryBIO.__gt__" => "Return self>value.", + "_ssl.MemoryBIO.__hash__" => "Return hash(self).", + "_ssl.MemoryBIO.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_ssl.MemoryBIO.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_ssl.MemoryBIO.__le__" => "Return self<=value.", + "_ssl.MemoryBIO.__lt__" => "Return self<value.", + "_ssl.MemoryBIO.__ne__" => "Return self!=value.", + "_ssl.MemoryBIO.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_ssl.MemoryBIO.__reduce__" => "Helper for pickle.", + "_ssl.MemoryBIO.__reduce_ex__" => "Helper for pickle.", + "_ssl.MemoryBIO.__repr__" => "Return repr(self).", + "_ssl.MemoryBIO.__setattr__" => "Implement setattr(self, name, value).", + "_ssl.MemoryBIO.__sizeof__" => "Size of object in memory, in bytes.", + "_ssl.MemoryBIO.__str__" => "Return str(self).", + "_ssl.MemoryBIO.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_ssl.MemoryBIO.eof" => "Whether the memory BIO is at EOF.", + "_ssl.MemoryBIO.pending" => "The number of bytes pending in the memory BIO.", + "_ssl.MemoryBIO.read" => "Read up to size bytes from the memory BIO.\n\nIf size is not specified, read the entire buffer.\nIf the return value is an empty bytes instance, this means either\nEOF or that no data is available. Use the \"eof\" property to\ndistinguish between the two.", + "_ssl.MemoryBIO.write" => "Writes the bytes b into the memory BIO.\n\nReturns the number of bytes written.", + "_ssl.MemoryBIO.write_eof" => "Write an EOF marker to the memory BIO.\n\nWhen all data has been read, the \"eof\" property will be True.", + "_ssl.RAND_add" => "Mix string into the OpenSSL PRNG state.\n\nentropy (a float) is a lower bound on the entropy contained in\nstring. See RFC 4086.", + "_ssl.RAND_bytes" => "Generate n cryptographically strong pseudo-random bytes.", + "_ssl.RAND_status" => "Returns True if the OpenSSL PRNG has been seeded with enough data and False if not.\n\nIt is necessary to seed the PRNG with RAND_add() on some platforms before\nusing the ssl() function.", + "_ssl.SSLSession.__delattr__" => "Implement delattr(self, name).", + "_ssl.SSLSession.__eq__" => "Return self==value.", + "_ssl.SSLSession.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_ssl.SSLSession.__ge__" => "Return self>=value.", + "_ssl.SSLSession.__getattribute__" => "Return getattr(self, name).", + "_ssl.SSLSession.__getstate__" => "Helper for pickle.", + "_ssl.SSLSession.__gt__" => "Return self>value.", + "_ssl.SSLSession.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_ssl.SSLSession.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_ssl.SSLSession.__le__" => "Return self<=value.", + "_ssl.SSLSession.__lt__" => "Return self<value.", + "_ssl.SSLSession.__ne__" => "Return self!=value.", + "_ssl.SSLSession.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_ssl.SSLSession.__reduce__" => "Helper for pickle.", + "_ssl.SSLSession.__reduce_ex__" => "Helper for pickle.", + "_ssl.SSLSession.__repr__" => "Return repr(self).", + "_ssl.SSLSession.__setattr__" => "Implement setattr(self, name, value).", + "_ssl.SSLSession.__sizeof__" => "Size of object in memory, in bytes.", + "_ssl.SSLSession.__str__" => "Return str(self).", + "_ssl.SSLSession.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_ssl.SSLSession.has_ticket" => "Does the session contain a ticket?", + "_ssl.SSLSession.id" => "Session ID.", + "_ssl.SSLSession.ticket_lifetime_hint" => "Ticket life time hint.", + "_ssl.SSLSession.time" => "Session creation time (seconds since epoch).", + "_ssl.SSLSession.timeout" => "Session timeout (delta in seconds).", + "_ssl._SSLContext.__delattr__" => "Implement delattr(self, name).", + "_ssl._SSLContext.__eq__" => "Return self==value.", + "_ssl._SSLContext.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_ssl._SSLContext.__ge__" => "Return self>=value.", + "_ssl._SSLContext.__getattribute__" => "Return getattr(self, name).", + "_ssl._SSLContext.__getstate__" => "Helper for pickle.", + "_ssl._SSLContext.__gt__" => "Return self>value.", + "_ssl._SSLContext.__hash__" => "Return hash(self).", + "_ssl._SSLContext.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_ssl._SSLContext.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_ssl._SSLContext.__le__" => "Return self<=value.", + "_ssl._SSLContext.__lt__" => "Return self<value.", + "_ssl._SSLContext.__ne__" => "Return self!=value.", + "_ssl._SSLContext.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_ssl._SSLContext.__reduce__" => "Helper for pickle.", + "_ssl._SSLContext.__reduce_ex__" => "Helper for pickle.", + "_ssl._SSLContext.__repr__" => "Return repr(self).", + "_ssl._SSLContext.__setattr__" => "Implement setattr(self, name, value).", + "_ssl._SSLContext.__sizeof__" => "Size of object in memory, in bytes.", + "_ssl._SSLContext.__str__" => "Return str(self).", + "_ssl._SSLContext.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_ssl._SSLContext.cert_store_stats" => "Returns quantities of loaded X.509 certificates.\n\nX.509 certificates with a CA extension and certificate revocation lists\ninside the context's cert store.\n\nNOTE: Certificates in a capath directory aren't loaded unless they have\nbeen used at least once.", + "_ssl._SSLContext.get_ca_certs" => "Returns a list of dicts with information of loaded CA certs.\n\nIf the optional argument is True, returns a DER-encoded copy of the CA\ncertificate.\n\nNOTE: Certificates in a capath directory aren't loaded unless they have\nbeen used at least once.", + "_ssl._SSLContext.num_tickets" => "Control the number of TLSv1.3 session tickets.", + "_ssl._SSLContext.security_level" => "The current security level.", + "_ssl._SSLContext.sni_callback" => "Set a callback that will be called when a server name is provided by the SSL/TLS client in the SNI extension.\n\nIf the argument is None then the callback is disabled. The method is called\nwith the SSLSocket, the server name as a string, and the SSLContext object.\n\nSee RFC 6066 for details of the SNI extension.", + "_ssl._SSLSocket.__delattr__" => "Implement delattr(self, name).", + "_ssl._SSLSocket.__eq__" => "Return self==value.", + "_ssl._SSLSocket.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_ssl._SSLSocket.__ge__" => "Return self>=value.", + "_ssl._SSLSocket.__getattribute__" => "Return getattr(self, name).", + "_ssl._SSLSocket.__getstate__" => "Helper for pickle.", + "_ssl._SSLSocket.__gt__" => "Return self>value.", + "_ssl._SSLSocket.__hash__" => "Return hash(self).", + "_ssl._SSLSocket.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_ssl._SSLSocket.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_ssl._SSLSocket.__le__" => "Return self<=value.", + "_ssl._SSLSocket.__lt__" => "Return self<value.", + "_ssl._SSLSocket.__ne__" => "Return self!=value.", + "_ssl._SSLSocket.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_ssl._SSLSocket.__reduce__" => "Helper for pickle.", + "_ssl._SSLSocket.__reduce_ex__" => "Helper for pickle.", + "_ssl._SSLSocket.__repr__" => "Return repr(self).", + "_ssl._SSLSocket.__setattr__" => "Implement setattr(self, name, value).", + "_ssl._SSLSocket.__sizeof__" => "Size of object in memory, in bytes.", + "_ssl._SSLSocket.__str__" => "Return str(self).", + "_ssl._SSLSocket.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_ssl._SSLSocket.context" => "This changes the context associated with the SSLSocket.\n\nThis is typically used from within a callback function set by the sni_callback\non the SSLContext to change the certificate information associated with the\nSSLSocket before the cryptographic exchange handshake messages.", + "_ssl._SSLSocket.get_channel_binding" => "Get channel binding data for current connection.\n\nRaise ValueError if the requested `cb_type` is not supported. Return bytes\nof the data or None if the data is not available (e.g. before the handshake).\nOnly 'tls-unique' channel binding data from RFC 5929 is supported.", + "_ssl._SSLSocket.getpeercert" => "Returns the certificate for the peer.\n\nIf no certificate was provided, returns None. If a certificate was\nprovided, but not validated, returns an empty dictionary. Otherwise\nreturns a dict containing information about the peer certificate.\n\nIf the optional argument is True, returns a DER-encoded copy of the\npeer certificate, or None if no certificate was provided. This will\nreturn the certificate even if it wasn't validated.", + "_ssl._SSLSocket.owner" => "The Python-level owner of this object.\n\nPassed as \"self\" in servername callback.", + "_ssl._SSLSocket.pending" => "Returns the number of already decrypted bytes available for read, pending on the connection.", + "_ssl._SSLSocket.read" => "read(size, [buffer])\nRead up to size bytes from the SSL socket.", + "_ssl._SSLSocket.server_hostname" => "The currently set server hostname (for SNI).", + "_ssl._SSLSocket.server_side" => "Whether this is a server-side socket.", + "_ssl._SSLSocket.session" => "The underlying SSLSession object.", + "_ssl._SSLSocket.session_reused" => "Was the client session reused during handshake?", + "_ssl._SSLSocket.shutdown" => "Does the SSL shutdown handshake with the remote end.", + "_ssl._SSLSocket.verify_client_post_handshake" => "Initiate TLS 1.3 post-handshake authentication", + "_ssl._SSLSocket.write" => "Writes the bytes-like object b into the SSL object.\n\nReturns the number of bytes written.", + "_ssl.enum_certificates" => "Retrieve certificates from Windows' cert store.\n\nstore_name may be one of 'CA', 'ROOT' or 'MY'. The system may provide\nmore cert storages, too. The function returns a list of (bytes,\nencoding_type, trust) tuples. The encoding_type flag can be interpreted\nwith X509_ASN_ENCODING or PKCS_7_ASN_ENCODING. The trust setting is either\na set of OIDs or the boolean True.", + "_ssl.enum_crls" => "Retrieve CRLs from Windows' cert store.\n\nstore_name may be one of 'CA', 'ROOT' or 'MY'. The system may provide\nmore cert storages, too. The function returns a list of (bytes,\nencoding_type) tuples. The encoding_type flag can be interpreted with\nX509_ASN_ENCODING or PKCS_7_ASN_ENCODING.", + "_ssl.get_default_verify_paths" => "Return search paths and environment vars that are used by SSLContext's set_default_verify_paths() to load default CAs.\n\nThe values are 'cert_file_env', 'cert_file', 'cert_dir_env', 'cert_dir'.", + "_ssl.nid2obj" => "Lookup NID, short name, long name and OID of an ASN1_OBJECT by NID.", + "_ssl.txt2obj" => "Lookup NID, short name, long name and OID of an ASN1_OBJECT.\n\nBy default objects are looked up by OID. With name=True short and\nlong name are also matched.", + "_stat" => "S_IFMT_: file type bits\nS_IFDIR: directory\nS_IFCHR: character device\nS_IFBLK: block device\nS_IFREG: regular file\nS_IFIFO: fifo (named pipe)\nS_IFLNK: symbolic link\nS_IFSOCK: socket file\nS_IFDOOR: door\nS_IFPORT: event port\nS_IFWHT: whiteout\n\nS_ISUID: set UID bit\nS_ISGID: set GID bit\nS_ENFMT: file locking enforcement\nS_ISVTX: sticky bit\nS_IREAD: Unix V7 synonym for S_IRUSR\nS_IWRITE: Unix V7 synonym for S_IWUSR\nS_IEXEC: Unix V7 synonym for S_IXUSR\nS_IRWXU: mask for owner permissions\nS_IRUSR: read by owner\nS_IWUSR: write by owner\nS_IXUSR: execute by owner\nS_IRWXG: mask for group permissions\nS_IRGRP: read by group\nS_IWGRP: write by group\nS_IXGRP: execute by group\nS_IRWXO: mask for others (not in group) permissions\nS_IROTH: read by others\nS_IWOTH: write by others\nS_IXOTH: execute by others\n\nUF_SETTABLE: mask of owner changable flags\nUF_NODUMP: do not dump file\nUF_IMMUTABLE: file may not be changed\nUF_APPEND: file may only be appended to\nUF_OPAQUE: directory is opaque when viewed through a union stack\nUF_NOUNLINK: file may not be renamed or deleted\nUF_COMPRESSED: macOS: file is hfs-compressed\nUF_TRACKED: used for dealing with document IDs\nUF_DATAVAULT: entitlement required for reading and writing\nUF_HIDDEN: macOS: file should not be displayed\nSF_SETTABLE: mask of super user changeable flags\nSF_ARCHIVED: file may be archived\nSF_IMMUTABLE: file may not be changed\nSF_APPEND: file may only be appended to\nSF_RESTRICTED: entitlement required for writing\nSF_NOUNLINK: file may not be renamed or deleted\nSF_SNAPSHOT: file is a snapshot file\nSF_FIRMLINK: file is a firmlink\nSF_DATALESS: file is a dataless object\n\nOn macOS:\nSF_SUPPORTED: mask of super user supported flags\nSF_SYNTHETIC: mask of read-only synthetic flags\n\nST_MODE\nST_INO\nST_DEV\nST_NLINK\nST_UID\nST_GID\nST_SIZE\nST_ATIME\nST_MTIME\nST_CTIME\n\nFILE_ATTRIBUTE_*: Windows file attribute constants\n (only present on Windows)", + "_stat.S_IFMT" => "Return the portion of the file's mode that describes the file type.", + "_stat.S_IMODE" => "Return the portion of the file's mode that can be set by os.chmod().", + "_stat.S_ISBLK" => "S_ISBLK(mode) -> bool\n\nReturn True if mode is from a block special device file.", + "_stat.S_ISCHR" => "S_ISCHR(mode) -> bool\n\nReturn True if mode is from a character special device file.", + "_stat.S_ISDIR" => "S_ISDIR(mode) -> bool\n\nReturn True if mode is from a directory.", + "_stat.S_ISDOOR" => "S_ISDOOR(mode) -> bool\n\nReturn True if mode is from a door.", + "_stat.S_ISFIFO" => "S_ISFIFO(mode) -> bool\n\nReturn True if mode is from a FIFO (named pipe).", + "_stat.S_ISLNK" => "S_ISLNK(mode) -> bool\n\nReturn True if mode is from a symbolic link.", + "_stat.S_ISPORT" => "S_ISPORT(mode) -> bool\n\nReturn True if mode is from an event port.", + "_stat.S_ISREG" => "S_ISREG(mode) -> bool\n\nReturn True if mode is from a regular file.", + "_stat.S_ISSOCK" => "S_ISSOCK(mode) -> bool\n\nReturn True if mode is from a socket.", + "_stat.S_ISWHT" => "S_ISWHT(mode) -> bool\n\nReturn True if mode is from a whiteout.", + "_stat.filemode" => "Convert a file's mode to a string of the form '-rwxrwxrwx'", + "_statistics" => "Accelerators for the statistics module.", + "_string" => "string helper module", + "_string.formatter_field_name_split" => "split the argument as a field name", + "_string.formatter_parser" => "parse the argument as a format string", + "_struct" => "Functions to convert between Python values and C structs.\nPython bytes objects are used to hold the data representing the C struct\nand also as format strings (explained below) to describe the layout of data\nin the C struct.\n\nThe optional first format char indicates byte order, size and alignment:\n @: native order, size & alignment (default)\n =: native order, std. size & alignment\n <: little-endian, std. size & alignment\n >: big-endian, std. size & alignment\n !: same as >\n\nThe remaining chars indicate types of args and must match exactly;\nthese can be preceded by a decimal repeat count:\n x: pad byte (no data); c:char; b:signed byte; B:unsigned byte;\n ?: _Bool (requires C99; if not available, char is used instead)\n h:short; H:unsigned short; i:int; I:unsigned int;\n l:long; L:unsigned long; f:float; d:double; e:half-float.\nSpecial cases (preceding decimal count indicates length):\n s:string (array of char); p: pascal string (with count byte).\nSpecial cases (only available in native format):\n n:ssize_t; N:size_t;\n P:an integer type that is wide enough to hold a pointer.\nSpecial case (not in native mode unless 'long long' in platform C):\n q:long long; Q:unsigned long long\nWhitespace between formats is ignored.\n\nThe variable struct.error is an exception raised on errors.", + "_struct.Struct" => "Struct(fmt) --> compiled struct object", + "_struct.Struct.__delattr__" => "Implement delattr(self, name).", + "_struct.Struct.__eq__" => "Return self==value.", + "_struct.Struct.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_struct.Struct.__ge__" => "Return self>=value.", + "_struct.Struct.__getattribute__" => "Return getattr(self, name).", + "_struct.Struct.__getstate__" => "Helper for pickle.", + "_struct.Struct.__gt__" => "Return self>value.", + "_struct.Struct.__hash__" => "Return hash(self).", + "_struct.Struct.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_struct.Struct.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_struct.Struct.__le__" => "Return self<=value.", + "_struct.Struct.__lt__" => "Return self<value.", + "_struct.Struct.__ne__" => "Return self!=value.", + "_struct.Struct.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_struct.Struct.__reduce__" => "Helper for pickle.", + "_struct.Struct.__reduce_ex__" => "Helper for pickle.", + "_struct.Struct.__repr__" => "Return repr(self).", + "_struct.Struct.__setattr__" => "Implement setattr(self, name, value).", + "_struct.Struct.__sizeof__" => "S.__sizeof__() -> size of S in memory, in bytes", + "_struct.Struct.__str__" => "Return str(self).", + "_struct.Struct.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_struct.Struct.format" => "struct format string", + "_struct.Struct.iter_unpack" => "Return an iterator yielding tuples.\n\nTuples are unpacked from the given bytes source, like a repeated\ninvocation of unpack_from().\n\nRequires that the bytes length be a multiple of the struct size.", + "_struct.Struct.pack" => "S.pack(v1, v2, ...) -> bytes\n\nReturn a bytes object containing values v1, v2, ... packed according\nto the format string S.format. See help(struct) for more on format\nstrings.", + "_struct.Struct.pack_into" => "S.pack_into(buffer, offset, v1, v2, ...)\n\nPack the values v1, v2, ... according to the format string S.format\nand write the packed bytes into the writable buffer buf starting at\noffset. Note that the offset is a required argument. See\nhelp(struct) for more on format strings.", + "_struct.Struct.size" => "struct size in bytes", + "_struct.Struct.unpack" => "Return a tuple containing unpacked values.\n\nUnpack according to the format string Struct.format. The buffer's size\nin bytes must be Struct.size.\n\nSee help(struct) for more on format strings.", + "_struct.Struct.unpack_from" => "Return a tuple containing unpacked values.\n\nValues are unpacked according to the format string Struct.format.\n\nThe buffer's size in bytes, starting at position offset, must be\nat least Struct.size.\n\nSee help(struct) for more on format strings.", + "_struct._clearcache" => "Clear the internal cache.", + "_struct.calcsize" => "Return size in bytes of the struct described by the format string.", + "_struct.iter_unpack" => "Return an iterator yielding tuples unpacked from the given bytes.\n\nThe bytes are unpacked according to the format string, like\na repeated invocation of unpack_from().\n\nRequires that the bytes length be a multiple of the format struct size.", + "_struct.pack" => "pack(format, v1, v2, ...) -> bytes\n\nReturn a bytes object containing the values v1, v2, ... packed according\nto the format string. See help(struct) for more on format strings.", + "_struct.pack_into" => "pack_into(format, buffer, offset, v1, v2, ...)\n\nPack the values v1, v2, ... according to the format string and write\nthe packed bytes into the writable buffer buf starting at offset. Note\nthat the offset is a required argument. See help(struct) for more\non format strings.", + "_struct.unpack" => "Return a tuple containing values unpacked according to the format string.\n\nThe buffer's size in bytes must be calcsize(format).\n\nSee help(struct) for more on format strings.", + "_struct.unpack_from" => "Return a tuple containing values unpacked according to the format string.\n\nThe buffer's size, minus offset, must be at least calcsize(format).\n\nSee help(struct) for more on format strings.", + "_suggestions._generate_suggestions" => "Returns the candidate in candidates that's closest to item", + "_symtable.symtable" => "Return symbol and scope dictionaries used internally by compiler.", + "_sysconfig" => "A helper for the sysconfig module.", + "_sysconfig.config_vars" => "Returns a dictionary containing build variables intended to be exposed by sysconfig.", + "_thread" => "This module provides primitive operations to write multi-threaded programs.\nThe 'threading' module provides a more convenient interface.", + "_thread.LockType" => "A lock object is a synchronization primitive. To create a lock,\ncall threading.Lock(). Methods are:\n\nacquire() -- lock the lock, possibly blocking until it can be obtained\nrelease() -- unlock of the lock\nlocked() -- test whether the lock is currently locked\n\nA lock is not owned by the thread that locked it; another thread may\nunlock it. A thread attempting to lock a lock that it has already locked\nwill block until another thread unlocks it. Deadlocks may ensue.", + "_thread.LockType.__delattr__" => "Implement delattr(self, name).", + "_thread.LockType.__enter__" => "Lock the lock.", + "_thread.LockType.__eq__" => "Return self==value.", + "_thread.LockType.__exit__" => "Release the lock.", + "_thread.LockType.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_thread.LockType.__ge__" => "Return self>=value.", + "_thread.LockType.__getattribute__" => "Return getattr(self, name).", + "_thread.LockType.__getstate__" => "Helper for pickle.", + "_thread.LockType.__gt__" => "Return self>value.", + "_thread.LockType.__hash__" => "Return hash(self).", + "_thread.LockType.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_thread.LockType.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_thread.LockType.__le__" => "Return self<=value.", + "_thread.LockType.__lt__" => "Return self<value.", + "_thread.LockType.__ne__" => "Return self!=value.", + "_thread.LockType.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_thread.LockType.__reduce__" => "Helper for pickle.", + "_thread.LockType.__reduce_ex__" => "Helper for pickle.", + "_thread.LockType.__repr__" => "Return repr(self).", + "_thread.LockType.__setattr__" => "Implement setattr(self, name, value).", + "_thread.LockType.__sizeof__" => "Size of object in memory, in bytes.", + "_thread.LockType.__str__" => "Return str(self).", + "_thread.LockType.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_thread.LockType.acquire" => "Lock the lock. Without argument, this blocks if the lock is already\nlocked (even by the same thread), waiting for another thread to release\nthe lock, and return True once the lock is acquired.\nWith an argument, this will only block if the argument is true,\nand the return value reflects whether the lock is acquired.\nThe blocking operation is interruptible.", + "_thread.LockType.acquire_lock" => "An obsolete synonym of acquire().", + "_thread.LockType.locked" => "Return whether the lock is in the locked state.", + "_thread.LockType.locked_lock" => "An obsolete synonym of locked().", + "_thread.LockType.release" => "Release the lock, allowing another thread that is blocked waiting for\nthe lock to acquire the lock. The lock must be in the locked state,\nbut it needn't be locked by the same thread that unlocks it.", + "_thread.LockType.release_lock" => "An obsolete synonym of release().", + "_thread.RLock.__delattr__" => "Implement delattr(self, name).", + "_thread.RLock.__enter__" => "Lock the lock.", + "_thread.RLock.__eq__" => "Return self==value.", + "_thread.RLock.__exit__" => "Release the lock.", + "_thread.RLock.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_thread.RLock.__ge__" => "Return self>=value.", + "_thread.RLock.__getattribute__" => "Return getattr(self, name).", + "_thread.RLock.__getstate__" => "Helper for pickle.", + "_thread.RLock.__gt__" => "Return self>value.", + "_thread.RLock.__hash__" => "Return hash(self).", + "_thread.RLock.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_thread.RLock.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_thread.RLock.__le__" => "Return self<=value.", + "_thread.RLock.__lt__" => "Return self<value.", + "_thread.RLock.__ne__" => "Return self!=value.", + "_thread.RLock.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_thread.RLock.__reduce__" => "Helper for pickle.", + "_thread.RLock.__reduce_ex__" => "Helper for pickle.", + "_thread.RLock.__repr__" => "Return repr(self).", + "_thread.RLock.__setattr__" => "Implement setattr(self, name, value).", + "_thread.RLock.__sizeof__" => "Size of object in memory, in bytes.", + "_thread.RLock.__str__" => "Return str(self).", + "_thread.RLock.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_thread.RLock._acquire_restore" => "For internal use by `threading.Condition`.", + "_thread.RLock._is_owned" => "For internal use by `threading.Condition`.", + "_thread.RLock._recursion_count" => "For internal use by reentrancy checks.", + "_thread.RLock._release_save" => "For internal use by `threading.Condition`.", + "_thread.RLock.acquire" => "Lock the lock. `blocking` indicates whether we should wait\nfor the lock to be available or not. If `blocking` is False\nand another thread holds the lock, the method will return False\nimmediately. If `blocking` is True and another thread holds\nthe lock, the method will wait for the lock to be released,\ntake it and then return True.\n(note: the blocking operation is interruptible.)\n\nIn all other cases, the method will return True immediately.\nPrecisely, if the current thread already holds the lock, its\ninternal counter is simply incremented. If nobody holds the lock,\nthe lock is taken and its internal counter initialized to 1.", + "_thread.RLock.release" => "Release the lock, allowing another thread that is blocked waiting for\nthe lock to acquire the lock. The lock must be in the locked state,\nand must be locked by the same thread that unlocks it; otherwise a\n`RuntimeError` is raised.\n\nDo note that if the lock was acquire()d several times in a row by the\ncurrent thread, release() needs to be called as many times for the lock\nto be available for other threads.", + "_thread._ExceptHookArgs" => "ExceptHookArgs\n\nType used to pass arguments to threading.excepthook.", + "_thread._ExceptHookArgs.__add__" => "Return self+value.", + "_thread._ExceptHookArgs.__class_getitem__" => "See PEP 585", + "_thread._ExceptHookArgs.__contains__" => "Return bool(key in self).", + "_thread._ExceptHookArgs.__delattr__" => "Implement delattr(self, name).", + "_thread._ExceptHookArgs.__eq__" => "Return self==value.", + "_thread._ExceptHookArgs.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_thread._ExceptHookArgs.__ge__" => "Return self>=value.", + "_thread._ExceptHookArgs.__getattribute__" => "Return getattr(self, name).", + "_thread._ExceptHookArgs.__getitem__" => "Return self[key].", + "_thread._ExceptHookArgs.__getstate__" => "Helper for pickle.", + "_thread._ExceptHookArgs.__gt__" => "Return self>value.", + "_thread._ExceptHookArgs.__hash__" => "Return hash(self).", + "_thread._ExceptHookArgs.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_thread._ExceptHookArgs.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_thread._ExceptHookArgs.__iter__" => "Implement iter(self).", + "_thread._ExceptHookArgs.__le__" => "Return self<=value.", + "_thread._ExceptHookArgs.__len__" => "Return len(self).", + "_thread._ExceptHookArgs.__lt__" => "Return self<value.", + "_thread._ExceptHookArgs.__mul__" => "Return self*value.", + "_thread._ExceptHookArgs.__ne__" => "Return self!=value.", + "_thread._ExceptHookArgs.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_thread._ExceptHookArgs.__reduce_ex__" => "Helper for pickle.", + "_thread._ExceptHookArgs.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "_thread._ExceptHookArgs.__repr__" => "Return repr(self).", + "_thread._ExceptHookArgs.__rmul__" => "Return value*self.", + "_thread._ExceptHookArgs.__setattr__" => "Implement setattr(self, name, value).", + "_thread._ExceptHookArgs.__sizeof__" => "Size of object in memory, in bytes.", + "_thread._ExceptHookArgs.__str__" => "Return str(self).", + "_thread._ExceptHookArgs.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_thread._ExceptHookArgs.count" => "Return number of occurrences of value.", + "_thread._ExceptHookArgs.exc_traceback" => "Exception traceback", + "_thread._ExceptHookArgs.exc_type" => "Exception type", + "_thread._ExceptHookArgs.exc_value" => "Exception value", + "_thread._ExceptHookArgs.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "_thread._ExceptHookArgs.thread" => "Thread", + "_thread._ThreadHandle.__delattr__" => "Implement delattr(self, name).", + "_thread._ThreadHandle.__eq__" => "Return self==value.", + "_thread._ThreadHandle.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_thread._ThreadHandle.__ge__" => "Return self>=value.", + "_thread._ThreadHandle.__getattribute__" => "Return getattr(self, name).", + "_thread._ThreadHandle.__getstate__" => "Helper for pickle.", + "_thread._ThreadHandle.__gt__" => "Return self>value.", + "_thread._ThreadHandle.__hash__" => "Return hash(self).", + "_thread._ThreadHandle.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_thread._ThreadHandle.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_thread._ThreadHandle.__le__" => "Return self<=value.", + "_thread._ThreadHandle.__lt__" => "Return self<value.", + "_thread._ThreadHandle.__ne__" => "Return self!=value.", + "_thread._ThreadHandle.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_thread._ThreadHandle.__reduce__" => "Helper for pickle.", + "_thread._ThreadHandle.__reduce_ex__" => "Helper for pickle.", + "_thread._ThreadHandle.__repr__" => "Return repr(self).", + "_thread._ThreadHandle.__setattr__" => "Implement setattr(self, name, value).", + "_thread._ThreadHandle.__sizeof__" => "Size of object in memory, in bytes.", + "_thread._ThreadHandle.__str__" => "Return str(self).", + "_thread._ThreadHandle.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_thread._count" => "Return the number of currently running Python threads, excluding\nthe main thread. The returned number comprises all threads created\nthrough `start_new_thread()` as well as `threading.Thread`, and not\nyet finished.\n\nThis function is meant for internal and specialized purposes only.\nIn most applications `threading.enumerate()` should be used instead.", + "_thread._excepthook" => "Handle uncaught Thread.run() exception.", + "_thread._get_main_thread_ident" => "Internal only. Return a non-zero integer that uniquely identifies the main thread\nof the main interpreter.", + "_thread._is_main_interpreter" => "Return True if the current interpreter is the main Python interpreter.", + "_thread._local" => "Thread-local data", + "_thread._local.__delattr__" => "Implement delattr(self, name).", + "_thread._local.__eq__" => "Return self==value.", + "_thread._local.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_thread._local.__ge__" => "Return self>=value.", + "_thread._local.__getattribute__" => "Return getattr(self, name).", + "_thread._local.__getstate__" => "Helper for pickle.", + "_thread._local.__gt__" => "Return self>value.", + "_thread._local.__hash__" => "Return hash(self).", + "_thread._local.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_thread._local.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_thread._local.__le__" => "Return self<=value.", + "_thread._local.__lt__" => "Return self<value.", + "_thread._local.__ne__" => "Return self!=value.", + "_thread._local.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_thread._local.__reduce__" => "Helper for pickle.", + "_thread._local.__reduce_ex__" => "Helper for pickle.", + "_thread._local.__repr__" => "Return repr(self).", + "_thread._local.__setattr__" => "Implement setattr(self, name, value).", + "_thread._local.__sizeof__" => "Size of object in memory, in bytes.", + "_thread._local.__str__" => "Return str(self).", + "_thread._local.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_thread._make_thread_handle" => "Internal only. Make a thread handle for threads not spawned\nby the _thread or threading module.", + "_thread._shutdown" => "Wait for all non-daemon threads (other than the calling thread) to stop.", + "_thread.allocate" => "An obsolete synonym of allocate_lock().", + "_thread.allocate_lock" => "Create a new lock object. See help(type(threading.Lock())) for\ninformation about locks.", + "_thread.daemon_threads_allowed" => "Return True if daemon threads are allowed in the current interpreter,\nand False otherwise.", + "_thread.exit" => "This is synonymous to ``raise SystemExit''. It will cause the current\nthread to exit silently unless the exception is caught.", + "_thread.exit_thread" => "An obsolete synonym of exit().", + "_thread.get_ident" => "Return a non-zero integer that uniquely identifies the current thread\namongst other threads that exist simultaneously.\nThis may be used to identify per-thread resources.\nEven though on some platforms threads identities may appear to be\nallocated consecutive numbers starting at 1, this behavior should not\nbe relied upon, and the number should be seen purely as a magic cookie.\nA thread's identity may be reused for another thread after it exits.", + "_thread.get_native_id" => "Return a non-negative integer identifying the thread as reported\nby the OS (kernel). This may be used to uniquely identify a\nparticular thread within a system.", + "_thread.interrupt_main" => "Simulate the arrival of the given signal in the main thread,\nwhere the corresponding signal handler will be executed.\nIf *signum* is omitted, SIGINT is assumed.\nA subthread can use this function to interrupt the main thread.\n\nNote: the default signal handler for SIGINT raises ``KeyboardInterrupt``.", + "_thread.lock" => "A lock object is a synchronization primitive. To create a lock,\ncall threading.Lock(). Methods are:\n\nacquire() -- lock the lock, possibly blocking until it can be obtained\nrelease() -- unlock of the lock\nlocked() -- test whether the lock is currently locked\n\nA lock is not owned by the thread that locked it; another thread may\nunlock it. A thread attempting to lock a lock that it has already locked\nwill block until another thread unlocks it. Deadlocks may ensue.", + "_thread.lock.__delattr__" => "Implement delattr(self, name).", + "_thread.lock.__enter__" => "Lock the lock.", + "_thread.lock.__eq__" => "Return self==value.", + "_thread.lock.__exit__" => "Release the lock.", + "_thread.lock.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_thread.lock.__ge__" => "Return self>=value.", + "_thread.lock.__getattribute__" => "Return getattr(self, name).", + "_thread.lock.__getstate__" => "Helper for pickle.", + "_thread.lock.__gt__" => "Return self>value.", + "_thread.lock.__hash__" => "Return hash(self).", + "_thread.lock.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_thread.lock.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_thread.lock.__le__" => "Return self<=value.", + "_thread.lock.__lt__" => "Return self<value.", + "_thread.lock.__ne__" => "Return self!=value.", + "_thread.lock.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_thread.lock.__reduce__" => "Helper for pickle.", + "_thread.lock.__reduce_ex__" => "Helper for pickle.", + "_thread.lock.__repr__" => "Return repr(self).", + "_thread.lock.__setattr__" => "Implement setattr(self, name, value).", + "_thread.lock.__sizeof__" => "Size of object in memory, in bytes.", + "_thread.lock.__str__" => "Return str(self).", + "_thread.lock.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_thread.lock.acquire" => "Lock the lock. Without argument, this blocks if the lock is already\nlocked (even by the same thread), waiting for another thread to release\nthe lock, and return True once the lock is acquired.\nWith an argument, this will only block if the argument is true,\nand the return value reflects whether the lock is acquired.\nThe blocking operation is interruptible.", + "_thread.lock.acquire_lock" => "An obsolete synonym of acquire().", + "_thread.lock.locked" => "Return whether the lock is in the locked state.", + "_thread.lock.locked_lock" => "An obsolete synonym of locked().", + "_thread.lock.release" => "Release the lock, allowing another thread that is blocked waiting for\nthe lock to acquire the lock. The lock must be in the locked state,\nbut it needn't be locked by the same thread that unlocks it.", + "_thread.lock.release_lock" => "An obsolete synonym of release().", + "_thread.stack_size" => "Return the thread stack size used when creating new threads. The\noptional size argument specifies the stack size (in bytes) to be used\nfor subsequently created threads, and must be 0 (use platform or\nconfigured default) or a positive integer value of at least 32,768 (32k).\nIf changing the thread stack size is unsupported, a ThreadError\nexception is raised. If the specified size is invalid, a ValueError\nexception is raised, and the stack size is unmodified. 32k bytes\n currently the minimum supported stack size value to guarantee\nsufficient stack space for the interpreter itself.\n\nNote that some platforms may have particular restrictions on values for\nthe stack size, such as requiring a minimum stack size larger than 32 KiB or\nrequiring allocation in multiples of the system memory page size\n- platform documentation should be referred to for more information\n(4 KiB pages are common; using multiples of 4096 for the stack size is\nthe suggested approach in the absence of more specific information).", + "_thread.start_joinable_thread" => "*For internal use only*: start a new thread.\n\nLike start_new_thread(), this starts a new thread calling the given function.\nUnlike start_new_thread(), this returns a handle object with methods to join\nor detach the given thread.\nThis function is not for third-party code, please use the\n`threading` module instead. During finalization the runtime will not wait for\nthe thread to exit if daemon is True. If handle is provided it must be a\nnewly created thread._ThreadHandle instance.", + "_thread.start_new" => "An obsolete synonym of start_new_thread().", + "_thread.start_new_thread" => "Start a new thread and return its identifier.\n\nThe thread will call the function with positional arguments from the\ntuple args and keyword arguments taken from the optional dictionary\nkwargs. The thread exits when the function returns; the return value\nis ignored. The thread will also exit when the function raises an\nunhandled exception; a stack trace will be printed unless the exception\nis SystemExit.", + "_tkinter.TclError.__cause__" => "exception cause", + "_tkinter.TclError.__context__" => "exception context", + "_tkinter.TclError.__delattr__" => "Implement delattr(self, name).", + "_tkinter.TclError.__eq__" => "Return self==value.", + "_tkinter.TclError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_tkinter.TclError.__ge__" => "Return self>=value.", + "_tkinter.TclError.__getattribute__" => "Return getattr(self, name).", + "_tkinter.TclError.__getstate__" => "Helper for pickle.", + "_tkinter.TclError.__gt__" => "Return self>value.", + "_tkinter.TclError.__hash__" => "Return hash(self).", + "_tkinter.TclError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_tkinter.TclError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_tkinter.TclError.__le__" => "Return self<=value.", + "_tkinter.TclError.__lt__" => "Return self<value.", + "_tkinter.TclError.__ne__" => "Return self!=value.", + "_tkinter.TclError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_tkinter.TclError.__reduce_ex__" => "Helper for pickle.", + "_tkinter.TclError.__repr__" => "Return repr(self).", + "_tkinter.TclError.__setattr__" => "Implement setattr(self, name, value).", + "_tkinter.TclError.__sizeof__" => "Size of object in memory, in bytes.", + "_tkinter.TclError.__str__" => "Return str(self).", + "_tkinter.TclError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_tkinter.TclError.__weakref__" => "list of weak references to the object", + "_tkinter.TclError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "_tkinter.TclError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "_tkinter.Tcl_Obj.__delattr__" => "Implement delattr(self, name).", + "_tkinter.Tcl_Obj.__eq__" => "Return self==value.", + "_tkinter.Tcl_Obj.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_tkinter.Tcl_Obj.__ge__" => "Return self>=value.", + "_tkinter.Tcl_Obj.__getattribute__" => "Return getattr(self, name).", + "_tkinter.Tcl_Obj.__getstate__" => "Helper for pickle.", + "_tkinter.Tcl_Obj.__gt__" => "Return self>value.", + "_tkinter.Tcl_Obj.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_tkinter.Tcl_Obj.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_tkinter.Tcl_Obj.__le__" => "Return self<=value.", + "_tkinter.Tcl_Obj.__lt__" => "Return self<value.", + "_tkinter.Tcl_Obj.__ne__" => "Return self!=value.", + "_tkinter.Tcl_Obj.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_tkinter.Tcl_Obj.__reduce__" => "Helper for pickle.", + "_tkinter.Tcl_Obj.__reduce_ex__" => "Helper for pickle.", + "_tkinter.Tcl_Obj.__repr__" => "Return repr(self).", + "_tkinter.Tcl_Obj.__setattr__" => "Implement setattr(self, name, value).", + "_tkinter.Tcl_Obj.__sizeof__" => "Size of object in memory, in bytes.", + "_tkinter.Tcl_Obj.__str__" => "Return str(self).", + "_tkinter.Tcl_Obj.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_tkinter.Tcl_Obj.string" => "the string representation of this object, either as str or bytes", + "_tkinter.Tcl_Obj.typename" => "name of the Tcl type", + "_tkinter.TkappType.__delattr__" => "Implement delattr(self, name).", + "_tkinter.TkappType.__eq__" => "Return self==value.", + "_tkinter.TkappType.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_tkinter.TkappType.__ge__" => "Return self>=value.", + "_tkinter.TkappType.__getattribute__" => "Return getattr(self, name).", + "_tkinter.TkappType.__getstate__" => "Helper for pickle.", + "_tkinter.TkappType.__gt__" => "Return self>value.", + "_tkinter.TkappType.__hash__" => "Return hash(self).", + "_tkinter.TkappType.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_tkinter.TkappType.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_tkinter.TkappType.__le__" => "Return self<=value.", + "_tkinter.TkappType.__lt__" => "Return self<value.", + "_tkinter.TkappType.__ne__" => "Return self!=value.", + "_tkinter.TkappType.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_tkinter.TkappType.__reduce__" => "Helper for pickle.", + "_tkinter.TkappType.__reduce_ex__" => "Helper for pickle.", + "_tkinter.TkappType.__repr__" => "Return repr(self).", + "_tkinter.TkappType.__setattr__" => "Implement setattr(self, name, value).", + "_tkinter.TkappType.__sizeof__" => "Size of object in memory, in bytes.", + "_tkinter.TkappType.__str__" => "Return str(self).", + "_tkinter.TkappType.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_tkinter.TkappType.gettrace" => "Get the tracing function.", + "_tkinter.TkappType.settrace" => "Set the tracing function.", + "_tkinter.TkttType.__delattr__" => "Implement delattr(self, name).", + "_tkinter.TkttType.__eq__" => "Return self==value.", + "_tkinter.TkttType.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_tkinter.TkttType.__ge__" => "Return self>=value.", + "_tkinter.TkttType.__getattribute__" => "Return getattr(self, name).", + "_tkinter.TkttType.__getstate__" => "Helper for pickle.", + "_tkinter.TkttType.__gt__" => "Return self>value.", + "_tkinter.TkttType.__hash__" => "Return hash(self).", + "_tkinter.TkttType.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_tkinter.TkttType.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_tkinter.TkttType.__le__" => "Return self<=value.", + "_tkinter.TkttType.__lt__" => "Return self<value.", + "_tkinter.TkttType.__ne__" => "Return self!=value.", + "_tkinter.TkttType.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_tkinter.TkttType.__reduce__" => "Helper for pickle.", + "_tkinter.TkttType.__reduce_ex__" => "Helper for pickle.", + "_tkinter.TkttType.__repr__" => "Return repr(self).", + "_tkinter.TkttType.__setattr__" => "Implement setattr(self, name, value).", + "_tkinter.TkttType.__sizeof__" => "Size of object in memory, in bytes.", + "_tkinter.TkttType.__str__" => "Return str(self).", + "_tkinter.TkttType.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_tkinter.create" => "wantTk\n if false, then Tk_Init() doesn't get called\nsync\n if true, then pass -sync to wish\nuse\n if not None, then pass -use to wish", + "_tkinter.getbusywaitinterval" => "Return the current busy-wait interval between successive calls to Tcl_DoOneEvent in a threaded Python interpreter.", + "_tkinter.setbusywaitinterval" => "Set the busy-wait interval in milliseconds between successive calls to Tcl_DoOneEvent in a threaded Python interpreter.\n\nIt should be set to a divisor of the maximum time between frames in an animation.", + "_tokenize.TokenizerIter.__delattr__" => "Implement delattr(self, name).", + "_tokenize.TokenizerIter.__eq__" => "Return self==value.", + "_tokenize.TokenizerIter.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_tokenize.TokenizerIter.__ge__" => "Return self>=value.", + "_tokenize.TokenizerIter.__getattribute__" => "Return getattr(self, name).", + "_tokenize.TokenizerIter.__getstate__" => "Helper for pickle.", + "_tokenize.TokenizerIter.__gt__" => "Return self>value.", + "_tokenize.TokenizerIter.__hash__" => "Return hash(self).", + "_tokenize.TokenizerIter.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_tokenize.TokenizerIter.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_tokenize.TokenizerIter.__iter__" => "Implement iter(self).", + "_tokenize.TokenizerIter.__le__" => "Return self<=value.", + "_tokenize.TokenizerIter.__lt__" => "Return self<value.", + "_tokenize.TokenizerIter.__ne__" => "Return self!=value.", + "_tokenize.TokenizerIter.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_tokenize.TokenizerIter.__next__" => "Implement next(self).", + "_tokenize.TokenizerIter.__reduce__" => "Helper for pickle.", + "_tokenize.TokenizerIter.__reduce_ex__" => "Helper for pickle.", + "_tokenize.TokenizerIter.__repr__" => "Return repr(self).", + "_tokenize.TokenizerIter.__setattr__" => "Implement setattr(self, name, value).", + "_tokenize.TokenizerIter.__sizeof__" => "Size of object in memory, in bytes.", + "_tokenize.TokenizerIter.__str__" => "Return str(self).", + "_tokenize.TokenizerIter.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_tracemalloc" => "Debug module to trace memory blocks allocated by Python.", + "_tracemalloc._get_object_traceback" => "Get the traceback where the Python object obj was allocated.\n\nReturn a tuple of (filename: str, lineno: int) tuples.\nReturn None if the tracemalloc module is disabled or did not\ntrace the allocation of the object.", + "_tracemalloc._get_traces" => "Get traces of all memory blocks allocated by Python.\n\nReturn a list of (size: int, traceback: tuple) tuples.\ntraceback is a tuple of (filename: str, lineno: int) tuples.\n\nReturn an empty list if the tracemalloc module is disabled.", + "_tracemalloc.clear_traces" => "Clear traces of memory blocks allocated by Python.", + "_tracemalloc.get_traceback_limit" => "Get the maximum number of frames stored in the traceback of a trace.\n\nBy default, a trace of an allocated memory block only stores\nthe most recent frame: the limit is 1.", + "_tracemalloc.get_traced_memory" => "Get the current size and peak size of memory blocks traced by tracemalloc.\n\nReturns a tuple: (current: int, peak: int).", + "_tracemalloc.get_tracemalloc_memory" => "Get the memory usage in bytes of the tracemalloc module.\n\nThis memory is used internally to trace memory allocations.", + "_tracemalloc.is_tracing" => "Return True if the tracemalloc module is tracing Python memory allocations.", + "_tracemalloc.reset_peak" => "Set the peak size of memory blocks traced by tracemalloc to the current size.\n\nDo nothing if the tracemalloc module is not tracing memory allocations.", + "_tracemalloc.start" => "Start tracing Python memory allocations.\n\nAlso set the maximum number of frames stored in the traceback of a\ntrace to nframe.", + "_tracemalloc.stop" => "Stop tracing Python memory allocations.\n\nAlso clear traces of memory blocks allocated by Python.", + "_typing" => "Primitives and accelerators for the typing module.", + "_warnings" => "_warnings provides basic warning filtering support.\nIt is a helper module to speed up interpreter start-up.", + "_warnings.warn" => "Issue a warning, or maybe ignore it or raise an exception.\n\nmessage\n Text of the warning message.\ncategory\n The Warning category subclass. Defaults to UserWarning.\nstacklevel\n How far up the call stack to make this warning appear. A value of 2 for\n example attributes the warning to the caller of the code calling warn().\nsource\n If supplied, the destroyed object which emitted a ResourceWarning\nskip_file_prefixes\n An optional tuple of module filename prefixes indicating frames to skip\n during stacklevel computations for stack frame attribution.", + "_warnings.warn_explicit" => "Issue a warning, or maybe ignore it or raise an exception.", + "_weakref" => "Weak-reference support module.", + "_weakref._remove_dead_weakref" => "Atomically remove key from dict if it points to a dead weakref.", + "_weakref.getweakrefcount" => "Return the number of weak references to 'object'.", + "_weakref.getweakrefs" => "Return a list of all weak reference objects pointing to 'object'.", + "_weakref.proxy" => "Create a proxy object that weakly references 'object'.\n\n'callback', if given, is called with a reference to the\nproxy when 'object' is about to be finalized.", + "_winapi.BatchedWaitForMultipleObjects" => "Supports a larger number of handles than WaitForMultipleObjects\n\nNote that the handles may be waited on other threads, which could cause\nissues for objects like mutexes that become associated with the thread\nthat was waiting for them. Objects may also be left signalled, even if\nthe wait fails.\n\nIt is recommended to use WaitForMultipleObjects whenever possible, and\nonly switch to BatchedWaitForMultipleObjects for scenarios where you\ncontrol all the handles involved, such as your own thread pool or\nfiles, and all wait objects are left unmodified by a wait (for example,\nmanual reset events, threads, and files/pipes).\n\nOverlapped handles returned from this module use manual reset events.", + "_winapi.CloseHandle" => "Close handle.", + "_winapi.CopyFile2" => "Copies a file from one name to a new name.\n\nThis is implemented using the CopyFile2 API, which preserves all stat\nand metadata information apart from security attributes.\n\nprogress_routine is reserved for future use, but is currently not\nimplemented. Its value is ignored.", + "_winapi.CreatePipe" => "Create an anonymous pipe.\n\n pipe_attrs\n Ignored internally, can be None.\n\nReturns a 2-tuple of handles, to the read and write ends of the pipe.", + "_winapi.CreateProcess" => "Create a new process and its primary thread.\n\n command_line\n Can be str or None\n proc_attrs\n Ignored internally, can be None.\n thread_attrs\n Ignored internally, can be None.\n\nThe return value is a tuple of the process handle, thread handle,\nprocess ID, and thread ID.", + "_winapi.DuplicateHandle" => "Return a duplicate handle object.\n\nThe duplicate handle refers to the same object as the original\nhandle. Therefore, any changes to the object are reflected\nthrough both handles.", + "_winapi.GetACP" => "Get the current Windows ANSI code page identifier.", + "_winapi.GetCurrentProcess" => "Return a handle object for the current process.", + "_winapi.GetExitCodeProcess" => "Return the termination status of the specified process.", + "_winapi.GetLongPathName" => "Return the long version of the provided path.\n\nIf the path is already in its long form, returns the same value.\n\nThe path must already be a 'str'. If the type is not known, use\nos.fsdecode before calling this function.", + "_winapi.GetModuleFileName" => "Return the fully-qualified path for the file that contains module.\n\nThe module must have been loaded by the current process.\n\nThe module parameter should be a handle to the loaded module\nwhose path is being requested. If this parameter is 0,\nGetModuleFileName retrieves the path of the executable file\nof the current process.", + "_winapi.GetShortPathName" => "Return the short version of the provided path.\n\nIf the path is already in its short form, returns the same value.\n\nThe path must already be a 'str'. If the type is not known, use\nos.fsdecode before calling this function.", + "_winapi.GetStdHandle" => "Return a handle to the specified standard device.\n\n std_handle\n One of STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, or STD_ERROR_HANDLE.\n\nThe integer associated with the handle object is returned.", + "_winapi.GetVersion" => "Return the version number of the current operating system.", + "_winapi.Overlapped" => "OVERLAPPED structure wrapper", + "_winapi.Overlapped.__delattr__" => "Implement delattr(self, name).", + "_winapi.Overlapped.__eq__" => "Return self==value.", + "_winapi.Overlapped.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "_winapi.Overlapped.__ge__" => "Return self>=value.", + "_winapi.Overlapped.__getattribute__" => "Return getattr(self, name).", + "_winapi.Overlapped.__getstate__" => "Helper for pickle.", + "_winapi.Overlapped.__gt__" => "Return self>value.", + "_winapi.Overlapped.__hash__" => "Return hash(self).", + "_winapi.Overlapped.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "_winapi.Overlapped.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "_winapi.Overlapped.__le__" => "Return self<=value.", + "_winapi.Overlapped.__lt__" => "Return self<value.", + "_winapi.Overlapped.__ne__" => "Return self!=value.", + "_winapi.Overlapped.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "_winapi.Overlapped.__reduce__" => "Helper for pickle.", + "_winapi.Overlapped.__reduce_ex__" => "Helper for pickle.", + "_winapi.Overlapped.__repr__" => "Return repr(self).", + "_winapi.Overlapped.__setattr__" => "Implement setattr(self, name, value).", + "_winapi.Overlapped.__sizeof__" => "Size of object in memory, in bytes.", + "_winapi.Overlapped.__str__" => "Return str(self).", + "_winapi.Overlapped.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "_winapi.Overlapped.event" => "overlapped event handle", + "_winapi.TerminateProcess" => "Terminate the specified process and all of its threads.", + "_winapi.WaitForSingleObject" => "Wait for a single object.\n\nWait until the specified object is in the signaled state or\nthe time-out interval elapses. The timeout value is specified\nin milliseconds.", + "_winapi._mimetypes_read_windows_registry" => "Optimized function for reading all known MIME types from the registry.\n\n*on_type_read* is a callable taking *type* and *ext* arguments, as for\nMimeTypes.add_type.", + "_wmi.exec_query" => "Runs a WMI query against the local machine.\n\nThis returns a single string with 'name=value' pairs in a flat array separated\nby null characters.", + "_zoneinfo" => "C implementation of the zoneinfo module", + "array" => "This module defines an object type which can efficiently represent\nan array of basic values: characters, integers, floating-point\nnumbers. Arrays are sequence types and behave very much like lists,\nexcept that the type of objects stored in them is constrained.", + "array.ArrayType" => "array(typecode [, initializer]) -> array\n\nReturn a new array whose items are restricted by typecode, and\ninitialized from the optional initializer value, which must be a list,\nstring or iterable over elements of the appropriate type.\n\nArrays represent basic values and behave very much like lists, except\nthe type of objects stored in them is constrained. The type is specified\nat object creation time by using a type code, which is a single character.\nThe following type codes are defined:\n\n Type code C Type Minimum size in bytes\n 'b' signed integer 1\n 'B' unsigned integer 1\n 'u' Unicode character 2 (see note)\n 'h' signed integer 2\n 'H' unsigned integer 2\n 'i' signed integer 2\n 'I' unsigned integer 2\n 'l' signed integer 4\n 'L' unsigned integer 4\n 'q' signed integer 8 (see note)\n 'Q' unsigned integer 8 (see note)\n 'f' floating-point 4\n 'd' floating-point 8\n\nNOTE: The 'u' typecode corresponds to Python's unicode character. On\nnarrow builds this is 2-bytes on wide builds this is 4-bytes.\n\nNOTE: The 'q' and 'Q' type codes are only available if the platform\nC compiler used to build Python supports 'long long', or, on Windows,\n'__int64'.\n\nMethods:\n\nappend() -- append a new item to the end of the array\nbuffer_info() -- return information giving the current memory info\nbyteswap() -- byteswap all the items of the array\ncount() -- return number of occurrences of an object\nextend() -- extend array by appending multiple elements from an iterable\nfromfile() -- read items from a file object\nfromlist() -- append items from the list\nfrombytes() -- append items from the string\nindex() -- return index of first occurrence of an object\ninsert() -- insert a new item into the array at a provided position\npop() -- remove and return item (default last)\nremove() -- remove first occurrence of an object\nreverse() -- reverse the order of the items in the array\ntofile() -- write all items to a file object\ntolist() -- return the array converted to an ordinary list\ntobytes() -- return the array converted to a string\n\nAttributes:\n\ntypecode -- the typecode character used to create the array\nitemsize -- the length in bytes of one array item", + "array.ArrayType.__add__" => "Return self+value.", + "array.ArrayType.__buffer__" => "Return a buffer object that exposes the underlying memory of the object.", + "array.ArrayType.__class_getitem__" => "See PEP 585", + "array.ArrayType.__contains__" => "Return bool(key in self).", + "array.ArrayType.__copy__" => "Return a copy of the array.", + "array.ArrayType.__deepcopy__" => "Return a copy of the array.", + "array.ArrayType.__delattr__" => "Implement delattr(self, name).", + "array.ArrayType.__delitem__" => "Delete self[key].", + "array.ArrayType.__eq__" => "Return self==value.", + "array.ArrayType.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "array.ArrayType.__ge__" => "Return self>=value.", + "array.ArrayType.__getattribute__" => "Return getattr(self, name).", + "array.ArrayType.__getitem__" => "Return self[key].", + "array.ArrayType.__getstate__" => "Helper for pickle.", + "array.ArrayType.__gt__" => "Return self>value.", + "array.ArrayType.__iadd__" => "Implement self+=value.", + "array.ArrayType.__imul__" => "Implement self*=value.", + "array.ArrayType.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "array.ArrayType.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "array.ArrayType.__iter__" => "Implement iter(self).", + "array.ArrayType.__le__" => "Return self<=value.", + "array.ArrayType.__len__" => "Return len(self).", + "array.ArrayType.__lt__" => "Return self<value.", + "array.ArrayType.__mul__" => "Return self*value.", + "array.ArrayType.__ne__" => "Return self!=value.", + "array.ArrayType.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "array.ArrayType.__reduce__" => "Helper for pickle.", + "array.ArrayType.__reduce_ex__" => "Return state information for pickling.", + "array.ArrayType.__release_buffer__" => "Release the buffer object that exposes the underlying memory of the object.", + "array.ArrayType.__repr__" => "Return repr(self).", + "array.ArrayType.__rmul__" => "Return value*self.", + "array.ArrayType.__setattr__" => "Implement setattr(self, name, value).", + "array.ArrayType.__setitem__" => "Set self[key] to value.", + "array.ArrayType.__sizeof__" => "Size of the array in memory, in bytes.", + "array.ArrayType.__str__" => "Return str(self).", + "array.ArrayType.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "array.ArrayType.append" => "Append new value v to the end of the array.", + "array.ArrayType.buffer_info" => "Return a tuple (address, length) giving the current memory address and the length in items of the buffer used to hold array's contents.\n\nThe length should be multiplied by the itemsize attribute to calculate\nthe buffer length in bytes.", + "array.ArrayType.byteswap" => "Byteswap all items of the array.\n\nIf the items in the array are not 1, 2, 4, or 8 bytes in size, RuntimeError is\nraised.", + "array.ArrayType.clear" => "Remove all items from the array.", + "array.ArrayType.count" => "Return number of occurrences of v in the array.", + "array.ArrayType.extend" => "Append items to the end of the array.", + "array.ArrayType.frombytes" => "Appends items from the string, interpreting it as an array of machine values, as if it had been read from a file using the fromfile() method.", + "array.ArrayType.fromfile" => "Read n objects from the file object f and append them to the end of the array.", + "array.ArrayType.fromlist" => "Append items to array from list.", + "array.ArrayType.fromunicode" => "Extends this array with data from the unicode string ustr.\n\nThe array must be a unicode type array; otherwise a ValueError is raised.\nUse array.frombytes(ustr.encode(...)) to append Unicode data to an array of\nsome other type.", + "array.ArrayType.index" => "Return index of first occurrence of v in the array.\n\nRaise ValueError if the value is not present.", + "array.ArrayType.insert" => "Insert a new item v into the array before position i.", + "array.ArrayType.itemsize" => "the size, in bytes, of one array item", + "array.ArrayType.pop" => "Return the i-th element and delete it from the array.\n\ni defaults to -1.", + "array.ArrayType.remove" => "Remove the first occurrence of v in the array.", + "array.ArrayType.reverse" => "Reverse the order of the items in the array.", + "array.ArrayType.tobytes" => "Convert the array to an array of machine values and return the bytes representation.", + "array.ArrayType.tofile" => "Write all items (as machine values) to the file object f.", + "array.ArrayType.tolist" => "Convert array to an ordinary list with the same items.", + "array.ArrayType.tounicode" => "Extends this array with data from the unicode string ustr.\n\nConvert the array to a unicode string. The array must be a unicode type array;\notherwise a ValueError is raised. Use array.tobytes().decode() to obtain a\nunicode string from an array of some other type.", + "array.ArrayType.typecode" => "the typecode character used to create the array", + "array._array_reconstructor" => "Internal. Used for pickling support.", + "array.array" => "array(typecode [, initializer]) -> array\n\nReturn a new array whose items are restricted by typecode, and\ninitialized from the optional initializer value, which must be a list,\nstring or iterable over elements of the appropriate type.\n\nArrays represent basic values and behave very much like lists, except\nthe type of objects stored in them is constrained. The type is specified\nat object creation time by using a type code, which is a single character.\nThe following type codes are defined:\n\n Type code C Type Minimum size in bytes\n 'b' signed integer 1\n 'B' unsigned integer 1\n 'u' Unicode character 2 (see note)\n 'h' signed integer 2\n 'H' unsigned integer 2\n 'i' signed integer 2\n 'I' unsigned integer 2\n 'l' signed integer 4\n 'L' unsigned integer 4\n 'q' signed integer 8 (see note)\n 'Q' unsigned integer 8 (see note)\n 'f' floating-point 4\n 'd' floating-point 8\n\nNOTE: The 'u' typecode corresponds to Python's unicode character. On\nnarrow builds this is 2-bytes on wide builds this is 4-bytes.\n\nNOTE: The 'q' and 'Q' type codes are only available if the platform\nC compiler used to build Python supports 'long long', or, on Windows,\n'__int64'.\n\nMethods:\n\nappend() -- append a new item to the end of the array\nbuffer_info() -- return information giving the current memory info\nbyteswap() -- byteswap all the items of the array\ncount() -- return number of occurrences of an object\nextend() -- extend array by appending multiple elements from an iterable\nfromfile() -- read items from a file object\nfromlist() -- append items from the list\nfrombytes() -- append items from the string\nindex() -- return index of first occurrence of an object\ninsert() -- insert a new item into the array at a provided position\npop() -- remove and return item (default last)\nremove() -- remove first occurrence of an object\nreverse() -- reverse the order of the items in the array\ntofile() -- write all items to a file object\ntolist() -- return the array converted to an ordinary list\ntobytes() -- return the array converted to a string\n\nAttributes:\n\ntypecode -- the typecode character used to create the array\nitemsize -- the length in bytes of one array item", + "array.array.__add__" => "Return self+value.", + "array.array.__buffer__" => "Return a buffer object that exposes the underlying memory of the object.", + "array.array.__class_getitem__" => "See PEP 585", + "array.array.__contains__" => "Return bool(key in self).", + "array.array.__copy__" => "Return a copy of the array.", + "array.array.__deepcopy__" => "Return a copy of the array.", + "array.array.__delattr__" => "Implement delattr(self, name).", + "array.array.__delitem__" => "Delete self[key].", + "array.array.__eq__" => "Return self==value.", + "array.array.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "array.array.__ge__" => "Return self>=value.", + "array.array.__getattribute__" => "Return getattr(self, name).", + "array.array.__getitem__" => "Return self[key].", + "array.array.__getstate__" => "Helper for pickle.", + "array.array.__gt__" => "Return self>value.", + "array.array.__iadd__" => "Implement self+=value.", + "array.array.__imul__" => "Implement self*=value.", + "array.array.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "array.array.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "array.array.__iter__" => "Implement iter(self).", + "array.array.__le__" => "Return self<=value.", + "array.array.__len__" => "Return len(self).", + "array.array.__lt__" => "Return self<value.", + "array.array.__mul__" => "Return self*value.", + "array.array.__ne__" => "Return self!=value.", + "array.array.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "array.array.__reduce__" => "Helper for pickle.", + "array.array.__reduce_ex__" => "Return state information for pickling.", + "array.array.__release_buffer__" => "Release the buffer object that exposes the underlying memory of the object.", + "array.array.__repr__" => "Return repr(self).", + "array.array.__rmul__" => "Return value*self.", + "array.array.__setattr__" => "Implement setattr(self, name, value).", + "array.array.__setitem__" => "Set self[key] to value.", + "array.array.__sizeof__" => "Size of the array in memory, in bytes.", + "array.array.__str__" => "Return str(self).", + "array.array.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "array.array.append" => "Append new value v to the end of the array.", + "array.array.buffer_info" => "Return a tuple (address, length) giving the current memory address and the length in items of the buffer used to hold array's contents.\n\nThe length should be multiplied by the itemsize attribute to calculate\nthe buffer length in bytes.", + "array.array.byteswap" => "Byteswap all items of the array.\n\nIf the items in the array are not 1, 2, 4, or 8 bytes in size, RuntimeError is\nraised.", + "array.array.clear" => "Remove all items from the array.", + "array.array.count" => "Return number of occurrences of v in the array.", + "array.array.extend" => "Append items to the end of the array.", + "array.array.frombytes" => "Appends items from the string, interpreting it as an array of machine values, as if it had been read from a file using the fromfile() method.", + "array.array.fromfile" => "Read n objects from the file object f and append them to the end of the array.", + "array.array.fromlist" => "Append items to array from list.", + "array.array.fromunicode" => "Extends this array with data from the unicode string ustr.\n\nThe array must be a unicode type array; otherwise a ValueError is raised.\nUse array.frombytes(ustr.encode(...)) to append Unicode data to an array of\nsome other type.", + "array.array.index" => "Return index of first occurrence of v in the array.\n\nRaise ValueError if the value is not present.", + "array.array.insert" => "Insert a new item v into the array before position i.", + "array.array.itemsize" => "the size, in bytes, of one array item", + "array.array.pop" => "Return the i-th element and delete it from the array.\n\ni defaults to -1.", + "array.array.remove" => "Remove the first occurrence of v in the array.", + "array.array.reverse" => "Reverse the order of the items in the array.", + "array.array.tobytes" => "Convert the array to an array of machine values and return the bytes representation.", + "array.array.tofile" => "Write all items (as machine values) to the file object f.", + "array.array.tolist" => "Convert array to an ordinary list with the same items.", + "array.array.tounicode" => "Extends this array with data from the unicode string ustr.\n\nConvert the array to a unicode string. The array must be a unicode type array;\notherwise a ValueError is raised. Use array.tobytes().decode() to obtain a\nunicode string from an array of some other type.", + "array.array.typecode" => "the typecode character used to create the array", + "atexit" => "allow programmer to define multiple exit functions to be executed\nupon normal program termination.\n\nTwo public functions, register and unregister, are defined.", + "atexit._clear" => "Clear the list of previously registered exit functions.", + "atexit._ncallbacks" => "Return the number of registered exit functions.", + "atexit._run_exitfuncs" => "Run all registered exit functions.\n\nIf a callback raises an exception, it is logged with sys.unraisablehook.", + "atexit.register" => "Register a function to be executed upon normal program termination\n\nfunc - function to be called at exit\nargs - optional arguments to pass to func\nkwargs - optional keyword arguments to pass to func\n\nfunc is returned to facilitate usage as a decorator.", + "atexit.unregister" => "Unregister an exit function which was previously registered using\natexit.register\n\n func - function to be unregistered", + "binascii" => "Conversion between binary data and ASCII", + "binascii.Error.__cause__" => "exception cause", + "binascii.Error.__context__" => "exception context", + "binascii.Error.__delattr__" => "Implement delattr(self, name).", + "binascii.Error.__eq__" => "Return self==value.", + "binascii.Error.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "binascii.Error.__ge__" => "Return self>=value.", + "binascii.Error.__getattribute__" => "Return getattr(self, name).", + "binascii.Error.__getstate__" => "Helper for pickle.", + "binascii.Error.__gt__" => "Return self>value.", + "binascii.Error.__hash__" => "Return hash(self).", + "binascii.Error.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "binascii.Error.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "binascii.Error.__le__" => "Return self<=value.", + "binascii.Error.__lt__" => "Return self<value.", + "binascii.Error.__ne__" => "Return self!=value.", + "binascii.Error.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "binascii.Error.__reduce_ex__" => "Helper for pickle.", + "binascii.Error.__repr__" => "Return repr(self).", + "binascii.Error.__setattr__" => "Implement setattr(self, name, value).", + "binascii.Error.__sizeof__" => "Size of object in memory, in bytes.", + "binascii.Error.__str__" => "Return str(self).", + "binascii.Error.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "binascii.Error.__weakref__" => "list of weak references to the object", + "binascii.Error.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "binascii.Error.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "binascii.Incomplete.__cause__" => "exception cause", + "binascii.Incomplete.__context__" => "exception context", + "binascii.Incomplete.__delattr__" => "Implement delattr(self, name).", + "binascii.Incomplete.__eq__" => "Return self==value.", + "binascii.Incomplete.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "binascii.Incomplete.__ge__" => "Return self>=value.", + "binascii.Incomplete.__getattribute__" => "Return getattr(self, name).", + "binascii.Incomplete.__getstate__" => "Helper for pickle.", + "binascii.Incomplete.__gt__" => "Return self>value.", + "binascii.Incomplete.__hash__" => "Return hash(self).", + "binascii.Incomplete.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "binascii.Incomplete.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "binascii.Incomplete.__le__" => "Return self<=value.", + "binascii.Incomplete.__lt__" => "Return self<value.", + "binascii.Incomplete.__ne__" => "Return self!=value.", + "binascii.Incomplete.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "binascii.Incomplete.__reduce_ex__" => "Helper for pickle.", + "binascii.Incomplete.__repr__" => "Return repr(self).", + "binascii.Incomplete.__setattr__" => "Implement setattr(self, name, value).", + "binascii.Incomplete.__sizeof__" => "Size of object in memory, in bytes.", + "binascii.Incomplete.__str__" => "Return str(self).", + "binascii.Incomplete.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "binascii.Incomplete.__weakref__" => "list of weak references to the object", + "binascii.Incomplete.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "binascii.Incomplete.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "binascii.a2b_base64" => "Decode a line of base64 data.\n\nstrict_mode\n When set to True, bytes that are not part of the base64 standard are not allowed.\n The same applies to excess data after padding (= / ==).", + "binascii.a2b_hex" => "Binary data of hexadecimal representation.\n\nhexstr must contain an even number of hex digits (upper or lower case).\nThis function is also available as \"unhexlify()\".", + "binascii.a2b_qp" => "Decode a string of qp-encoded data.", + "binascii.a2b_uu" => "Decode a line of uuencoded data.", + "binascii.b2a_base64" => "Base64-code line of data.", + "binascii.b2a_hex" => "Hexadecimal representation of binary data.\n\n sep\n An optional single character or byte to separate hex bytes.\n bytes_per_sep\n How many bytes between separators. Positive values count from the\n right, negative values count from the left.\n\nThe return value is a bytes object. This function is also\navailable as \"hexlify()\".\n\nExample:\n>>> binascii.b2a_hex(b'\\xb9\\x01\\xef')\nb'b901ef'\n>>> binascii.hexlify(b'\\xb9\\x01\\xef', ':')\nb'b9:01:ef'\n>>> binascii.b2a_hex(b'\\xb9\\x01\\xef', b'_', 2)\nb'b9_01ef'", + "binascii.b2a_qp" => "Encode a string using quoted-printable encoding.\n\nOn encoding, when istext is set, newlines are not encoded, and white\nspace at end of lines is. When istext is not set, \\r and \\n (CR/LF)\nare both encoded. When quotetabs is set, space and tabs are encoded.", + "binascii.b2a_uu" => "Uuencode line of data.", + "binascii.crc32" => "Compute CRC-32 incrementally.", + "binascii.crc_hqx" => "Compute CRC-CCITT incrementally.", + "binascii.hexlify" => "Hexadecimal representation of binary data.\n\n sep\n An optional single character or byte to separate hex bytes.\n bytes_per_sep\n How many bytes between separators. Positive values count from the\n right, negative values count from the left.\n\nThe return value is a bytes object. This function is also\navailable as \"b2a_hex()\".", + "binascii.unhexlify" => "Binary data of hexadecimal representation.\n\nhexstr must contain an even number of hex digits (upper or lower case).", + "builtins" => "Built-in functions, types, exceptions, and other objects.\n\nThis module provides direct access to all 'built-in'\nidentifiers of Python; for example, builtins.len is\nthe full name for the built-in function len().\n\nThis module is not normally accessed explicitly by most\napplications, but can be useful in modules that provide\nobjects with the same name as a built-in value, but in\nwhich the built-in of that name is also needed.", + "builtins.ArithmeticError" => "Base class for arithmetic errors.", + "builtins.ArithmeticError.__cause__" => "exception cause", + "builtins.ArithmeticError.__context__" => "exception context", + "builtins.ArithmeticError.__delattr__" => "Implement delattr(self, name).", + "builtins.ArithmeticError.__eq__" => "Return self==value.", + "builtins.ArithmeticError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ArithmeticError.__ge__" => "Return self>=value.", + "builtins.ArithmeticError.__getattribute__" => "Return getattr(self, name).", + "builtins.ArithmeticError.__getstate__" => "Helper for pickle.", + "builtins.ArithmeticError.__gt__" => "Return self>value.", + "builtins.ArithmeticError.__hash__" => "Return hash(self).", + "builtins.ArithmeticError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ArithmeticError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ArithmeticError.__le__" => "Return self<=value.", + "builtins.ArithmeticError.__lt__" => "Return self<value.", + "builtins.ArithmeticError.__ne__" => "Return self!=value.", + "builtins.ArithmeticError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ArithmeticError.__reduce_ex__" => "Helper for pickle.", + "builtins.ArithmeticError.__repr__" => "Return repr(self).", + "builtins.ArithmeticError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ArithmeticError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ArithmeticError.__str__" => "Return str(self).", + "builtins.ArithmeticError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ArithmeticError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ArithmeticError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.AssertionError" => "Assertion failed.", + "builtins.AssertionError.__cause__" => "exception cause", + "builtins.AssertionError.__context__" => "exception context", + "builtins.AssertionError.__delattr__" => "Implement delattr(self, name).", + "builtins.AssertionError.__eq__" => "Return self==value.", + "builtins.AssertionError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.AssertionError.__ge__" => "Return self>=value.", + "builtins.AssertionError.__getattribute__" => "Return getattr(self, name).", + "builtins.AssertionError.__getstate__" => "Helper for pickle.", + "builtins.AssertionError.__gt__" => "Return self>value.", + "builtins.AssertionError.__hash__" => "Return hash(self).", + "builtins.AssertionError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.AssertionError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.AssertionError.__le__" => "Return self<=value.", + "builtins.AssertionError.__lt__" => "Return self<value.", + "builtins.AssertionError.__ne__" => "Return self!=value.", + "builtins.AssertionError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.AssertionError.__reduce_ex__" => "Helper for pickle.", + "builtins.AssertionError.__repr__" => "Return repr(self).", + "builtins.AssertionError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.AssertionError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.AssertionError.__str__" => "Return str(self).", + "builtins.AssertionError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.AssertionError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.AssertionError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.AttributeError" => "Attribute not found.", + "builtins.AttributeError.__cause__" => "exception cause", + "builtins.AttributeError.__context__" => "exception context", + "builtins.AttributeError.__delattr__" => "Implement delattr(self, name).", + "builtins.AttributeError.__eq__" => "Return self==value.", + "builtins.AttributeError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.AttributeError.__ge__" => "Return self>=value.", + "builtins.AttributeError.__getattribute__" => "Return getattr(self, name).", + "builtins.AttributeError.__gt__" => "Return self>value.", + "builtins.AttributeError.__hash__" => "Return hash(self).", + "builtins.AttributeError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.AttributeError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.AttributeError.__le__" => "Return self<=value.", + "builtins.AttributeError.__lt__" => "Return self<value.", + "builtins.AttributeError.__ne__" => "Return self!=value.", + "builtins.AttributeError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.AttributeError.__reduce_ex__" => "Helper for pickle.", + "builtins.AttributeError.__repr__" => "Return repr(self).", + "builtins.AttributeError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.AttributeError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.AttributeError.__str__" => "Return str(self).", + "builtins.AttributeError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.AttributeError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.AttributeError.name" => "attribute name", + "builtins.AttributeError.obj" => "object", + "builtins.AttributeError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.BaseException" => "Common base class for all exceptions", + "builtins.BaseException.__cause__" => "exception cause", + "builtins.BaseException.__context__" => "exception context", + "builtins.BaseException.__delattr__" => "Implement delattr(self, name).", + "builtins.BaseException.__eq__" => "Return self==value.", + "builtins.BaseException.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.BaseException.__ge__" => "Return self>=value.", + "builtins.BaseException.__getattribute__" => "Return getattr(self, name).", + "builtins.BaseException.__getstate__" => "Helper for pickle.", + "builtins.BaseException.__gt__" => "Return self>value.", + "builtins.BaseException.__hash__" => "Return hash(self).", + "builtins.BaseException.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.BaseException.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.BaseException.__le__" => "Return self<=value.", + "builtins.BaseException.__lt__" => "Return self<value.", + "builtins.BaseException.__ne__" => "Return self!=value.", + "builtins.BaseException.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.BaseException.__reduce_ex__" => "Helper for pickle.", + "builtins.BaseException.__repr__" => "Return repr(self).", + "builtins.BaseException.__setattr__" => "Implement setattr(self, name, value).", + "builtins.BaseException.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.BaseException.__str__" => "Return str(self).", + "builtins.BaseException.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.BaseException.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.BaseException.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.BaseExceptionGroup" => "A combination of multiple unrelated exceptions.", + "builtins.BaseExceptionGroup.__cause__" => "exception cause", + "builtins.BaseExceptionGroup.__class_getitem__" => "See PEP 585", + "builtins.BaseExceptionGroup.__context__" => "exception context", + "builtins.BaseExceptionGroup.__delattr__" => "Implement delattr(self, name).", + "builtins.BaseExceptionGroup.__eq__" => "Return self==value.", + "builtins.BaseExceptionGroup.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.BaseExceptionGroup.__ge__" => "Return self>=value.", + "builtins.BaseExceptionGroup.__getattribute__" => "Return getattr(self, name).", + "builtins.BaseExceptionGroup.__getstate__" => "Helper for pickle.", + "builtins.BaseExceptionGroup.__gt__" => "Return self>value.", + "builtins.BaseExceptionGroup.__hash__" => "Return hash(self).", + "builtins.BaseExceptionGroup.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.BaseExceptionGroup.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.BaseExceptionGroup.__le__" => "Return self<=value.", + "builtins.BaseExceptionGroup.__lt__" => "Return self<value.", + "builtins.BaseExceptionGroup.__ne__" => "Return self!=value.", + "builtins.BaseExceptionGroup.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.BaseExceptionGroup.__reduce_ex__" => "Helper for pickle.", + "builtins.BaseExceptionGroup.__repr__" => "Return repr(self).", + "builtins.BaseExceptionGroup.__setattr__" => "Implement setattr(self, name, value).", + "builtins.BaseExceptionGroup.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.BaseExceptionGroup.__str__" => "Return str(self).", + "builtins.BaseExceptionGroup.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.BaseExceptionGroup.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.BaseExceptionGroup.exceptions" => "nested exceptions", + "builtins.BaseExceptionGroup.message" => "exception message", + "builtins.BaseExceptionGroup.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.BlockingIOError" => "I/O operation would block.", + "builtins.BlockingIOError.__cause__" => "exception cause", + "builtins.BlockingIOError.__context__" => "exception context", + "builtins.BlockingIOError.__delattr__" => "Implement delattr(self, name).", + "builtins.BlockingIOError.__eq__" => "Return self==value.", + "builtins.BlockingIOError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.BlockingIOError.__ge__" => "Return self>=value.", + "builtins.BlockingIOError.__getattribute__" => "Return getattr(self, name).", + "builtins.BlockingIOError.__getstate__" => "Helper for pickle.", + "builtins.BlockingIOError.__gt__" => "Return self>value.", + "builtins.BlockingIOError.__hash__" => "Return hash(self).", + "builtins.BlockingIOError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.BlockingIOError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.BlockingIOError.__le__" => "Return self<=value.", + "builtins.BlockingIOError.__lt__" => "Return self<value.", + "builtins.BlockingIOError.__ne__" => "Return self!=value.", + "builtins.BlockingIOError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.BlockingIOError.__reduce_ex__" => "Helper for pickle.", + "builtins.BlockingIOError.__repr__" => "Return repr(self).", + "builtins.BlockingIOError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.BlockingIOError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.BlockingIOError.__str__" => "Return str(self).", + "builtins.BlockingIOError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.BlockingIOError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.BlockingIOError.errno" => "POSIX exception code", + "builtins.BlockingIOError.filename" => "exception filename", + "builtins.BlockingIOError.filename2" => "second exception filename", + "builtins.BlockingIOError.strerror" => "exception strerror", + "builtins.BlockingIOError.winerror" => "Win32 exception code", + "builtins.BlockingIOError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.BrokenPipeError" => "Broken pipe.", + "builtins.BrokenPipeError.__cause__" => "exception cause", + "builtins.BrokenPipeError.__context__" => "exception context", + "builtins.BrokenPipeError.__delattr__" => "Implement delattr(self, name).", + "builtins.BrokenPipeError.__eq__" => "Return self==value.", + "builtins.BrokenPipeError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.BrokenPipeError.__ge__" => "Return self>=value.", + "builtins.BrokenPipeError.__getattribute__" => "Return getattr(self, name).", + "builtins.BrokenPipeError.__getstate__" => "Helper for pickle.", + "builtins.BrokenPipeError.__gt__" => "Return self>value.", + "builtins.BrokenPipeError.__hash__" => "Return hash(self).", + "builtins.BrokenPipeError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.BrokenPipeError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.BrokenPipeError.__le__" => "Return self<=value.", + "builtins.BrokenPipeError.__lt__" => "Return self<value.", + "builtins.BrokenPipeError.__ne__" => "Return self!=value.", + "builtins.BrokenPipeError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.BrokenPipeError.__reduce_ex__" => "Helper for pickle.", + "builtins.BrokenPipeError.__repr__" => "Return repr(self).", + "builtins.BrokenPipeError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.BrokenPipeError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.BrokenPipeError.__str__" => "Return str(self).", + "builtins.BrokenPipeError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.BrokenPipeError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.BrokenPipeError.errno" => "POSIX exception code", + "builtins.BrokenPipeError.filename" => "exception filename", + "builtins.BrokenPipeError.filename2" => "second exception filename", + "builtins.BrokenPipeError.strerror" => "exception strerror", + "builtins.BrokenPipeError.winerror" => "Win32 exception code", + "builtins.BrokenPipeError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.BufferError" => "Buffer error.", + "builtins.BufferError.__cause__" => "exception cause", + "builtins.BufferError.__context__" => "exception context", + "builtins.BufferError.__delattr__" => "Implement delattr(self, name).", + "builtins.BufferError.__eq__" => "Return self==value.", + "builtins.BufferError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.BufferError.__ge__" => "Return self>=value.", + "builtins.BufferError.__getattribute__" => "Return getattr(self, name).", + "builtins.BufferError.__getstate__" => "Helper for pickle.", + "builtins.BufferError.__gt__" => "Return self>value.", + "builtins.BufferError.__hash__" => "Return hash(self).", + "builtins.BufferError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.BufferError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.BufferError.__le__" => "Return self<=value.", + "builtins.BufferError.__lt__" => "Return self<value.", + "builtins.BufferError.__ne__" => "Return self!=value.", + "builtins.BufferError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.BufferError.__reduce_ex__" => "Helper for pickle.", + "builtins.BufferError.__repr__" => "Return repr(self).", + "builtins.BufferError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.BufferError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.BufferError.__str__" => "Return str(self).", + "builtins.BufferError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.BufferError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.BufferError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.BytesWarning" => "Base class for warnings about bytes and buffer related problems, mostly\nrelated to conversion from str or comparing to str.", + "builtins.BytesWarning.__cause__" => "exception cause", + "builtins.BytesWarning.__context__" => "exception context", + "builtins.BytesWarning.__delattr__" => "Implement delattr(self, name).", + "builtins.BytesWarning.__eq__" => "Return self==value.", + "builtins.BytesWarning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.BytesWarning.__ge__" => "Return self>=value.", + "builtins.BytesWarning.__getattribute__" => "Return getattr(self, name).", + "builtins.BytesWarning.__getstate__" => "Helper for pickle.", + "builtins.BytesWarning.__gt__" => "Return self>value.", + "builtins.BytesWarning.__hash__" => "Return hash(self).", + "builtins.BytesWarning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.BytesWarning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.BytesWarning.__le__" => "Return self<=value.", + "builtins.BytesWarning.__lt__" => "Return self<value.", + "builtins.BytesWarning.__ne__" => "Return self!=value.", + "builtins.BytesWarning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.BytesWarning.__reduce_ex__" => "Helper for pickle.", + "builtins.BytesWarning.__repr__" => "Return repr(self).", + "builtins.BytesWarning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.BytesWarning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.BytesWarning.__str__" => "Return str(self).", + "builtins.BytesWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.BytesWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.BytesWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ChildProcessError" => "Child process error.", + "builtins.ChildProcessError.__cause__" => "exception cause", + "builtins.ChildProcessError.__context__" => "exception context", + "builtins.ChildProcessError.__delattr__" => "Implement delattr(self, name).", + "builtins.ChildProcessError.__eq__" => "Return self==value.", + "builtins.ChildProcessError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ChildProcessError.__ge__" => "Return self>=value.", + "builtins.ChildProcessError.__getattribute__" => "Return getattr(self, name).", + "builtins.ChildProcessError.__getstate__" => "Helper for pickle.", + "builtins.ChildProcessError.__gt__" => "Return self>value.", + "builtins.ChildProcessError.__hash__" => "Return hash(self).", + "builtins.ChildProcessError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ChildProcessError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ChildProcessError.__le__" => "Return self<=value.", + "builtins.ChildProcessError.__lt__" => "Return self<value.", + "builtins.ChildProcessError.__ne__" => "Return self!=value.", + "builtins.ChildProcessError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ChildProcessError.__reduce_ex__" => "Helper for pickle.", + "builtins.ChildProcessError.__repr__" => "Return repr(self).", + "builtins.ChildProcessError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ChildProcessError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ChildProcessError.__str__" => "Return str(self).", + "builtins.ChildProcessError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ChildProcessError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ChildProcessError.errno" => "POSIX exception code", + "builtins.ChildProcessError.filename" => "exception filename", + "builtins.ChildProcessError.filename2" => "second exception filename", + "builtins.ChildProcessError.strerror" => "exception strerror", + "builtins.ChildProcessError.winerror" => "Win32 exception code", + "builtins.ChildProcessError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ConnectionAbortedError" => "Connection aborted.", + "builtins.ConnectionAbortedError.__cause__" => "exception cause", + "builtins.ConnectionAbortedError.__context__" => "exception context", + "builtins.ConnectionAbortedError.__delattr__" => "Implement delattr(self, name).", + "builtins.ConnectionAbortedError.__eq__" => "Return self==value.", + "builtins.ConnectionAbortedError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ConnectionAbortedError.__ge__" => "Return self>=value.", + "builtins.ConnectionAbortedError.__getattribute__" => "Return getattr(self, name).", + "builtins.ConnectionAbortedError.__getstate__" => "Helper for pickle.", + "builtins.ConnectionAbortedError.__gt__" => "Return self>value.", + "builtins.ConnectionAbortedError.__hash__" => "Return hash(self).", + "builtins.ConnectionAbortedError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ConnectionAbortedError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ConnectionAbortedError.__le__" => "Return self<=value.", + "builtins.ConnectionAbortedError.__lt__" => "Return self<value.", + "builtins.ConnectionAbortedError.__ne__" => "Return self!=value.", + "builtins.ConnectionAbortedError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ConnectionAbortedError.__reduce_ex__" => "Helper for pickle.", + "builtins.ConnectionAbortedError.__repr__" => "Return repr(self).", + "builtins.ConnectionAbortedError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ConnectionAbortedError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ConnectionAbortedError.__str__" => "Return str(self).", + "builtins.ConnectionAbortedError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ConnectionAbortedError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ConnectionAbortedError.errno" => "POSIX exception code", + "builtins.ConnectionAbortedError.filename" => "exception filename", + "builtins.ConnectionAbortedError.filename2" => "second exception filename", + "builtins.ConnectionAbortedError.strerror" => "exception strerror", + "builtins.ConnectionAbortedError.winerror" => "Win32 exception code", + "builtins.ConnectionAbortedError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ConnectionError" => "Connection error.", + "builtins.ConnectionError.__cause__" => "exception cause", + "builtins.ConnectionError.__context__" => "exception context", + "builtins.ConnectionError.__delattr__" => "Implement delattr(self, name).", + "builtins.ConnectionError.__eq__" => "Return self==value.", + "builtins.ConnectionError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ConnectionError.__ge__" => "Return self>=value.", + "builtins.ConnectionError.__getattribute__" => "Return getattr(self, name).", + "builtins.ConnectionError.__getstate__" => "Helper for pickle.", + "builtins.ConnectionError.__gt__" => "Return self>value.", + "builtins.ConnectionError.__hash__" => "Return hash(self).", + "builtins.ConnectionError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ConnectionError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ConnectionError.__le__" => "Return self<=value.", + "builtins.ConnectionError.__lt__" => "Return self<value.", + "builtins.ConnectionError.__ne__" => "Return self!=value.", + "builtins.ConnectionError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ConnectionError.__reduce_ex__" => "Helper for pickle.", + "builtins.ConnectionError.__repr__" => "Return repr(self).", + "builtins.ConnectionError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ConnectionError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ConnectionError.__str__" => "Return str(self).", + "builtins.ConnectionError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ConnectionError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ConnectionError.errno" => "POSIX exception code", + "builtins.ConnectionError.filename" => "exception filename", + "builtins.ConnectionError.filename2" => "second exception filename", + "builtins.ConnectionError.strerror" => "exception strerror", + "builtins.ConnectionError.winerror" => "Win32 exception code", + "builtins.ConnectionError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ConnectionRefusedError" => "Connection refused.", + "builtins.ConnectionRefusedError.__cause__" => "exception cause", + "builtins.ConnectionRefusedError.__context__" => "exception context", + "builtins.ConnectionRefusedError.__delattr__" => "Implement delattr(self, name).", + "builtins.ConnectionRefusedError.__eq__" => "Return self==value.", + "builtins.ConnectionRefusedError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ConnectionRefusedError.__ge__" => "Return self>=value.", + "builtins.ConnectionRefusedError.__getattribute__" => "Return getattr(self, name).", + "builtins.ConnectionRefusedError.__getstate__" => "Helper for pickle.", + "builtins.ConnectionRefusedError.__gt__" => "Return self>value.", + "builtins.ConnectionRefusedError.__hash__" => "Return hash(self).", + "builtins.ConnectionRefusedError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ConnectionRefusedError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ConnectionRefusedError.__le__" => "Return self<=value.", + "builtins.ConnectionRefusedError.__lt__" => "Return self<value.", + "builtins.ConnectionRefusedError.__ne__" => "Return self!=value.", + "builtins.ConnectionRefusedError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ConnectionRefusedError.__reduce_ex__" => "Helper for pickle.", + "builtins.ConnectionRefusedError.__repr__" => "Return repr(self).", + "builtins.ConnectionRefusedError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ConnectionRefusedError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ConnectionRefusedError.__str__" => "Return str(self).", + "builtins.ConnectionRefusedError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ConnectionRefusedError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ConnectionRefusedError.errno" => "POSIX exception code", + "builtins.ConnectionRefusedError.filename" => "exception filename", + "builtins.ConnectionRefusedError.filename2" => "second exception filename", + "builtins.ConnectionRefusedError.strerror" => "exception strerror", + "builtins.ConnectionRefusedError.winerror" => "Win32 exception code", + "builtins.ConnectionRefusedError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ConnectionResetError" => "Connection reset.", + "builtins.ConnectionResetError.__cause__" => "exception cause", + "builtins.ConnectionResetError.__context__" => "exception context", + "builtins.ConnectionResetError.__delattr__" => "Implement delattr(self, name).", + "builtins.ConnectionResetError.__eq__" => "Return self==value.", + "builtins.ConnectionResetError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ConnectionResetError.__ge__" => "Return self>=value.", + "builtins.ConnectionResetError.__getattribute__" => "Return getattr(self, name).", + "builtins.ConnectionResetError.__getstate__" => "Helper for pickle.", + "builtins.ConnectionResetError.__gt__" => "Return self>value.", + "builtins.ConnectionResetError.__hash__" => "Return hash(self).", + "builtins.ConnectionResetError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ConnectionResetError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ConnectionResetError.__le__" => "Return self<=value.", + "builtins.ConnectionResetError.__lt__" => "Return self<value.", + "builtins.ConnectionResetError.__ne__" => "Return self!=value.", + "builtins.ConnectionResetError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ConnectionResetError.__reduce_ex__" => "Helper for pickle.", + "builtins.ConnectionResetError.__repr__" => "Return repr(self).", + "builtins.ConnectionResetError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ConnectionResetError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ConnectionResetError.__str__" => "Return str(self).", + "builtins.ConnectionResetError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ConnectionResetError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ConnectionResetError.errno" => "POSIX exception code", + "builtins.ConnectionResetError.filename" => "exception filename", + "builtins.ConnectionResetError.filename2" => "second exception filename", + "builtins.ConnectionResetError.strerror" => "exception strerror", + "builtins.ConnectionResetError.winerror" => "Win32 exception code", + "builtins.ConnectionResetError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.DeprecationWarning" => "Base class for warnings about deprecated features.", + "builtins.DeprecationWarning.__cause__" => "exception cause", + "builtins.DeprecationWarning.__context__" => "exception context", + "builtins.DeprecationWarning.__delattr__" => "Implement delattr(self, name).", + "builtins.DeprecationWarning.__eq__" => "Return self==value.", + "builtins.DeprecationWarning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.DeprecationWarning.__ge__" => "Return self>=value.", + "builtins.DeprecationWarning.__getattribute__" => "Return getattr(self, name).", + "builtins.DeprecationWarning.__getstate__" => "Helper for pickle.", + "builtins.DeprecationWarning.__gt__" => "Return self>value.", + "builtins.DeprecationWarning.__hash__" => "Return hash(self).", + "builtins.DeprecationWarning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.DeprecationWarning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.DeprecationWarning.__le__" => "Return self<=value.", + "builtins.DeprecationWarning.__lt__" => "Return self<value.", + "builtins.DeprecationWarning.__ne__" => "Return self!=value.", + "builtins.DeprecationWarning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.DeprecationWarning.__reduce_ex__" => "Helper for pickle.", + "builtins.DeprecationWarning.__repr__" => "Return repr(self).", + "builtins.DeprecationWarning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.DeprecationWarning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.DeprecationWarning.__str__" => "Return str(self).", + "builtins.DeprecationWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.DeprecationWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.DeprecationWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.EOFError" => "Read beyond end of file.", + "builtins.EOFError.__cause__" => "exception cause", + "builtins.EOFError.__context__" => "exception context", + "builtins.EOFError.__delattr__" => "Implement delattr(self, name).", + "builtins.EOFError.__eq__" => "Return self==value.", + "builtins.EOFError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.EOFError.__ge__" => "Return self>=value.", + "builtins.EOFError.__getattribute__" => "Return getattr(self, name).", + "builtins.EOFError.__getstate__" => "Helper for pickle.", + "builtins.EOFError.__gt__" => "Return self>value.", + "builtins.EOFError.__hash__" => "Return hash(self).", + "builtins.EOFError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.EOFError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.EOFError.__le__" => "Return self<=value.", + "builtins.EOFError.__lt__" => "Return self<value.", + "builtins.EOFError.__ne__" => "Return self!=value.", + "builtins.EOFError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.EOFError.__reduce_ex__" => "Helper for pickle.", + "builtins.EOFError.__repr__" => "Return repr(self).", + "builtins.EOFError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.EOFError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.EOFError.__str__" => "Return str(self).", + "builtins.EOFError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.EOFError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.EOFError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.EncodingWarning" => "Base class for warnings about encodings.", + "builtins.EncodingWarning.__cause__" => "exception cause", + "builtins.EncodingWarning.__context__" => "exception context", + "builtins.EncodingWarning.__delattr__" => "Implement delattr(self, name).", + "builtins.EncodingWarning.__eq__" => "Return self==value.", + "builtins.EncodingWarning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.EncodingWarning.__ge__" => "Return self>=value.", + "builtins.EncodingWarning.__getattribute__" => "Return getattr(self, name).", + "builtins.EncodingWarning.__getstate__" => "Helper for pickle.", + "builtins.EncodingWarning.__gt__" => "Return self>value.", + "builtins.EncodingWarning.__hash__" => "Return hash(self).", + "builtins.EncodingWarning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.EncodingWarning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.EncodingWarning.__le__" => "Return self<=value.", + "builtins.EncodingWarning.__lt__" => "Return self<value.", + "builtins.EncodingWarning.__ne__" => "Return self!=value.", + "builtins.EncodingWarning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.EncodingWarning.__reduce_ex__" => "Helper for pickle.", + "builtins.EncodingWarning.__repr__" => "Return repr(self).", + "builtins.EncodingWarning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.EncodingWarning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.EncodingWarning.__str__" => "Return str(self).", + "builtins.EncodingWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.EncodingWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.EncodingWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.EnvironmentError" => "Base class for I/O related errors.", + "builtins.EnvironmentError.__cause__" => "exception cause", + "builtins.EnvironmentError.__context__" => "exception context", + "builtins.EnvironmentError.__delattr__" => "Implement delattr(self, name).", + "builtins.EnvironmentError.__eq__" => "Return self==value.", + "builtins.EnvironmentError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.EnvironmentError.__ge__" => "Return self>=value.", + "builtins.EnvironmentError.__getattribute__" => "Return getattr(self, name).", + "builtins.EnvironmentError.__getstate__" => "Helper for pickle.", + "builtins.EnvironmentError.__gt__" => "Return self>value.", + "builtins.EnvironmentError.__hash__" => "Return hash(self).", + "builtins.EnvironmentError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.EnvironmentError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.EnvironmentError.__le__" => "Return self<=value.", + "builtins.EnvironmentError.__lt__" => "Return self<value.", + "builtins.EnvironmentError.__ne__" => "Return self!=value.", + "builtins.EnvironmentError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.EnvironmentError.__reduce_ex__" => "Helper for pickle.", + "builtins.EnvironmentError.__repr__" => "Return repr(self).", + "builtins.EnvironmentError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.EnvironmentError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.EnvironmentError.__str__" => "Return str(self).", + "builtins.EnvironmentError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.EnvironmentError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.EnvironmentError.errno" => "POSIX exception code", + "builtins.EnvironmentError.filename" => "exception filename", + "builtins.EnvironmentError.filename2" => "second exception filename", + "builtins.EnvironmentError.strerror" => "exception strerror", + "builtins.EnvironmentError.winerror" => "Win32 exception code", + "builtins.EnvironmentError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.Exception" => "Common base class for all non-exit exceptions.", + "builtins.Exception.__cause__" => "exception cause", + "builtins.Exception.__context__" => "exception context", + "builtins.Exception.__delattr__" => "Implement delattr(self, name).", + "builtins.Exception.__eq__" => "Return self==value.", + "builtins.Exception.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.Exception.__ge__" => "Return self>=value.", + "builtins.Exception.__getattribute__" => "Return getattr(self, name).", + "builtins.Exception.__getstate__" => "Helper for pickle.", + "builtins.Exception.__gt__" => "Return self>value.", + "builtins.Exception.__hash__" => "Return hash(self).", + "builtins.Exception.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.Exception.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.Exception.__le__" => "Return self<=value.", + "builtins.Exception.__lt__" => "Return self<value.", + "builtins.Exception.__ne__" => "Return self!=value.", + "builtins.Exception.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.Exception.__reduce_ex__" => "Helper for pickle.", + "builtins.Exception.__repr__" => "Return repr(self).", + "builtins.Exception.__setattr__" => "Implement setattr(self, name, value).", + "builtins.Exception.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.Exception.__str__" => "Return str(self).", + "builtins.Exception.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.Exception.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.Exception.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ExceptionGroup.__cause__" => "exception cause", + "builtins.ExceptionGroup.__class_getitem__" => "See PEP 585", + "builtins.ExceptionGroup.__context__" => "exception context", + "builtins.ExceptionGroup.__delattr__" => "Implement delattr(self, name).", + "builtins.ExceptionGroup.__eq__" => "Return self==value.", + "builtins.ExceptionGroup.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ExceptionGroup.__ge__" => "Return self>=value.", + "builtins.ExceptionGroup.__getattribute__" => "Return getattr(self, name).", + "builtins.ExceptionGroup.__getstate__" => "Helper for pickle.", + "builtins.ExceptionGroup.__gt__" => "Return self>value.", + "builtins.ExceptionGroup.__hash__" => "Return hash(self).", + "builtins.ExceptionGroup.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ExceptionGroup.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ExceptionGroup.__le__" => "Return self<=value.", + "builtins.ExceptionGroup.__lt__" => "Return self<value.", + "builtins.ExceptionGroup.__ne__" => "Return self!=value.", + "builtins.ExceptionGroup.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ExceptionGroup.__reduce_ex__" => "Helper for pickle.", + "builtins.ExceptionGroup.__repr__" => "Return repr(self).", + "builtins.ExceptionGroup.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ExceptionGroup.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ExceptionGroup.__str__" => "Return str(self).", + "builtins.ExceptionGroup.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ExceptionGroup.__weakref__" => "list of weak references to the object", + "builtins.ExceptionGroup.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ExceptionGroup.exceptions" => "nested exceptions", + "builtins.ExceptionGroup.message" => "exception message", + "builtins.ExceptionGroup.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.FileExistsError" => "File already exists.", + "builtins.FileExistsError.__cause__" => "exception cause", + "builtins.FileExistsError.__context__" => "exception context", + "builtins.FileExistsError.__delattr__" => "Implement delattr(self, name).", + "builtins.FileExistsError.__eq__" => "Return self==value.", + "builtins.FileExistsError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.FileExistsError.__ge__" => "Return self>=value.", + "builtins.FileExistsError.__getattribute__" => "Return getattr(self, name).", + "builtins.FileExistsError.__getstate__" => "Helper for pickle.", + "builtins.FileExistsError.__gt__" => "Return self>value.", + "builtins.FileExistsError.__hash__" => "Return hash(self).", + "builtins.FileExistsError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.FileExistsError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.FileExistsError.__le__" => "Return self<=value.", + "builtins.FileExistsError.__lt__" => "Return self<value.", + "builtins.FileExistsError.__ne__" => "Return self!=value.", + "builtins.FileExistsError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.FileExistsError.__reduce_ex__" => "Helper for pickle.", + "builtins.FileExistsError.__repr__" => "Return repr(self).", + "builtins.FileExistsError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.FileExistsError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.FileExistsError.__str__" => "Return str(self).", + "builtins.FileExistsError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.FileExistsError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.FileExistsError.errno" => "POSIX exception code", + "builtins.FileExistsError.filename" => "exception filename", + "builtins.FileExistsError.filename2" => "second exception filename", + "builtins.FileExistsError.strerror" => "exception strerror", + "builtins.FileExistsError.winerror" => "Win32 exception code", + "builtins.FileExistsError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.FileNotFoundError" => "File not found.", + "builtins.FileNotFoundError.__cause__" => "exception cause", + "builtins.FileNotFoundError.__context__" => "exception context", + "builtins.FileNotFoundError.__delattr__" => "Implement delattr(self, name).", + "builtins.FileNotFoundError.__eq__" => "Return self==value.", + "builtins.FileNotFoundError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.FileNotFoundError.__ge__" => "Return self>=value.", + "builtins.FileNotFoundError.__getattribute__" => "Return getattr(self, name).", + "builtins.FileNotFoundError.__getstate__" => "Helper for pickle.", + "builtins.FileNotFoundError.__gt__" => "Return self>value.", + "builtins.FileNotFoundError.__hash__" => "Return hash(self).", + "builtins.FileNotFoundError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.FileNotFoundError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.FileNotFoundError.__le__" => "Return self<=value.", + "builtins.FileNotFoundError.__lt__" => "Return self<value.", + "builtins.FileNotFoundError.__ne__" => "Return self!=value.", + "builtins.FileNotFoundError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.FileNotFoundError.__reduce_ex__" => "Helper for pickle.", + "builtins.FileNotFoundError.__repr__" => "Return repr(self).", + "builtins.FileNotFoundError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.FileNotFoundError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.FileNotFoundError.__str__" => "Return str(self).", + "builtins.FileNotFoundError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.FileNotFoundError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.FileNotFoundError.errno" => "POSIX exception code", + "builtins.FileNotFoundError.filename" => "exception filename", + "builtins.FileNotFoundError.filename2" => "second exception filename", + "builtins.FileNotFoundError.strerror" => "exception strerror", + "builtins.FileNotFoundError.winerror" => "Win32 exception code", + "builtins.FileNotFoundError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.FloatingPointError" => "Floating-point operation failed.", + "builtins.FloatingPointError.__cause__" => "exception cause", + "builtins.FloatingPointError.__context__" => "exception context", + "builtins.FloatingPointError.__delattr__" => "Implement delattr(self, name).", + "builtins.FloatingPointError.__eq__" => "Return self==value.", + "builtins.FloatingPointError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.FloatingPointError.__ge__" => "Return self>=value.", + "builtins.FloatingPointError.__getattribute__" => "Return getattr(self, name).", + "builtins.FloatingPointError.__getstate__" => "Helper for pickle.", + "builtins.FloatingPointError.__gt__" => "Return self>value.", + "builtins.FloatingPointError.__hash__" => "Return hash(self).", + "builtins.FloatingPointError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.FloatingPointError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.FloatingPointError.__le__" => "Return self<=value.", + "builtins.FloatingPointError.__lt__" => "Return self<value.", + "builtins.FloatingPointError.__ne__" => "Return self!=value.", + "builtins.FloatingPointError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.FloatingPointError.__reduce_ex__" => "Helper for pickle.", + "builtins.FloatingPointError.__repr__" => "Return repr(self).", + "builtins.FloatingPointError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.FloatingPointError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.FloatingPointError.__str__" => "Return str(self).", + "builtins.FloatingPointError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.FloatingPointError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.FloatingPointError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.FutureWarning" => "Base class for warnings about constructs that will change semantically\nin the future.", + "builtins.FutureWarning.__cause__" => "exception cause", + "builtins.FutureWarning.__context__" => "exception context", + "builtins.FutureWarning.__delattr__" => "Implement delattr(self, name).", + "builtins.FutureWarning.__eq__" => "Return self==value.", + "builtins.FutureWarning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.FutureWarning.__ge__" => "Return self>=value.", + "builtins.FutureWarning.__getattribute__" => "Return getattr(self, name).", + "builtins.FutureWarning.__getstate__" => "Helper for pickle.", + "builtins.FutureWarning.__gt__" => "Return self>value.", + "builtins.FutureWarning.__hash__" => "Return hash(self).", + "builtins.FutureWarning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.FutureWarning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.FutureWarning.__le__" => "Return self<=value.", + "builtins.FutureWarning.__lt__" => "Return self<value.", + "builtins.FutureWarning.__ne__" => "Return self!=value.", + "builtins.FutureWarning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.FutureWarning.__reduce_ex__" => "Helper for pickle.", + "builtins.FutureWarning.__repr__" => "Return repr(self).", + "builtins.FutureWarning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.FutureWarning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.FutureWarning.__str__" => "Return str(self).", + "builtins.FutureWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.FutureWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.FutureWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.GeneratorExit" => "Request that a generator exit.", + "builtins.GeneratorExit.__cause__" => "exception cause", + "builtins.GeneratorExit.__context__" => "exception context", + "builtins.GeneratorExit.__delattr__" => "Implement delattr(self, name).", + "builtins.GeneratorExit.__eq__" => "Return self==value.", + "builtins.GeneratorExit.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.GeneratorExit.__ge__" => "Return self>=value.", + "builtins.GeneratorExit.__getattribute__" => "Return getattr(self, name).", + "builtins.GeneratorExit.__getstate__" => "Helper for pickle.", + "builtins.GeneratorExit.__gt__" => "Return self>value.", + "builtins.GeneratorExit.__hash__" => "Return hash(self).", + "builtins.GeneratorExit.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.GeneratorExit.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.GeneratorExit.__le__" => "Return self<=value.", + "builtins.GeneratorExit.__lt__" => "Return self<value.", + "builtins.GeneratorExit.__ne__" => "Return self!=value.", + "builtins.GeneratorExit.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.GeneratorExit.__reduce_ex__" => "Helper for pickle.", + "builtins.GeneratorExit.__repr__" => "Return repr(self).", + "builtins.GeneratorExit.__setattr__" => "Implement setattr(self, name, value).", + "builtins.GeneratorExit.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.GeneratorExit.__str__" => "Return str(self).", + "builtins.GeneratorExit.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.GeneratorExit.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.GeneratorExit.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.IOError" => "Base class for I/O related errors.", + "builtins.IOError.__cause__" => "exception cause", + "builtins.IOError.__context__" => "exception context", + "builtins.IOError.__delattr__" => "Implement delattr(self, name).", + "builtins.IOError.__eq__" => "Return self==value.", + "builtins.IOError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.IOError.__ge__" => "Return self>=value.", + "builtins.IOError.__getattribute__" => "Return getattr(self, name).", + "builtins.IOError.__getstate__" => "Helper for pickle.", + "builtins.IOError.__gt__" => "Return self>value.", + "builtins.IOError.__hash__" => "Return hash(self).", + "builtins.IOError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.IOError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.IOError.__le__" => "Return self<=value.", + "builtins.IOError.__lt__" => "Return self<value.", + "builtins.IOError.__ne__" => "Return self!=value.", + "builtins.IOError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.IOError.__reduce_ex__" => "Helper for pickle.", + "builtins.IOError.__repr__" => "Return repr(self).", + "builtins.IOError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.IOError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.IOError.__str__" => "Return str(self).", + "builtins.IOError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.IOError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.IOError.errno" => "POSIX exception code", + "builtins.IOError.filename" => "exception filename", + "builtins.IOError.filename2" => "second exception filename", + "builtins.IOError.strerror" => "exception strerror", + "builtins.IOError.winerror" => "Win32 exception code", + "builtins.IOError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ImportError" => "Import can't find module, or can't find name in module.", + "builtins.ImportError.__cause__" => "exception cause", + "builtins.ImportError.__context__" => "exception context", + "builtins.ImportError.__delattr__" => "Implement delattr(self, name).", + "builtins.ImportError.__eq__" => "Return self==value.", + "builtins.ImportError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ImportError.__ge__" => "Return self>=value.", + "builtins.ImportError.__getattribute__" => "Return getattr(self, name).", + "builtins.ImportError.__getstate__" => "Helper for pickle.", + "builtins.ImportError.__gt__" => "Return self>value.", + "builtins.ImportError.__hash__" => "Return hash(self).", + "builtins.ImportError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ImportError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ImportError.__le__" => "Return self<=value.", + "builtins.ImportError.__lt__" => "Return self<value.", + "builtins.ImportError.__ne__" => "Return self!=value.", + "builtins.ImportError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ImportError.__reduce_ex__" => "Helper for pickle.", + "builtins.ImportError.__repr__" => "Return repr(self).", + "builtins.ImportError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ImportError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ImportError.__str__" => "Return str(self).", + "builtins.ImportError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ImportError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ImportError.msg" => "exception message", + "builtins.ImportError.name" => "module name", + "builtins.ImportError.name_from" => "name imported from module", + "builtins.ImportError.path" => "module path", + "builtins.ImportError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ImportWarning" => "Base class for warnings about probable mistakes in module imports", + "builtins.ImportWarning.__cause__" => "exception cause", + "builtins.ImportWarning.__context__" => "exception context", + "builtins.ImportWarning.__delattr__" => "Implement delattr(self, name).", + "builtins.ImportWarning.__eq__" => "Return self==value.", + "builtins.ImportWarning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ImportWarning.__ge__" => "Return self>=value.", + "builtins.ImportWarning.__getattribute__" => "Return getattr(self, name).", + "builtins.ImportWarning.__getstate__" => "Helper for pickle.", + "builtins.ImportWarning.__gt__" => "Return self>value.", + "builtins.ImportWarning.__hash__" => "Return hash(self).", + "builtins.ImportWarning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ImportWarning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ImportWarning.__le__" => "Return self<=value.", + "builtins.ImportWarning.__lt__" => "Return self<value.", + "builtins.ImportWarning.__ne__" => "Return self!=value.", + "builtins.ImportWarning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ImportWarning.__reduce_ex__" => "Helper for pickle.", + "builtins.ImportWarning.__repr__" => "Return repr(self).", + "builtins.ImportWarning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ImportWarning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ImportWarning.__str__" => "Return str(self).", + "builtins.ImportWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ImportWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ImportWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.IndentationError" => "Improper indentation.", + "builtins.IndentationError.__cause__" => "exception cause", + "builtins.IndentationError.__context__" => "exception context", + "builtins.IndentationError.__delattr__" => "Implement delattr(self, name).", + "builtins.IndentationError.__eq__" => "Return self==value.", + "builtins.IndentationError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.IndentationError.__ge__" => "Return self>=value.", + "builtins.IndentationError.__getattribute__" => "Return getattr(self, name).", + "builtins.IndentationError.__getstate__" => "Helper for pickle.", + "builtins.IndentationError.__gt__" => "Return self>value.", + "builtins.IndentationError.__hash__" => "Return hash(self).", + "builtins.IndentationError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.IndentationError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.IndentationError.__le__" => "Return self<=value.", + "builtins.IndentationError.__lt__" => "Return self<value.", + "builtins.IndentationError.__ne__" => "Return self!=value.", + "builtins.IndentationError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.IndentationError.__reduce_ex__" => "Helper for pickle.", + "builtins.IndentationError.__repr__" => "Return repr(self).", + "builtins.IndentationError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.IndentationError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.IndentationError.__str__" => "Return str(self).", + "builtins.IndentationError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.IndentationError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.IndentationError.end_lineno" => "exception end lineno", + "builtins.IndentationError.end_offset" => "exception end offset", + "builtins.IndentationError.filename" => "exception filename", + "builtins.IndentationError.lineno" => "exception lineno", + "builtins.IndentationError.msg" => "exception msg", + "builtins.IndentationError.offset" => "exception offset", + "builtins.IndentationError.print_file_and_line" => "exception print_file_and_line", + "builtins.IndentationError.text" => "exception text", + "builtins.IndentationError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.IndexError" => "Sequence index out of range.", + "builtins.IndexError.__cause__" => "exception cause", + "builtins.IndexError.__context__" => "exception context", + "builtins.IndexError.__delattr__" => "Implement delattr(self, name).", + "builtins.IndexError.__eq__" => "Return self==value.", + "builtins.IndexError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.IndexError.__ge__" => "Return self>=value.", + "builtins.IndexError.__getattribute__" => "Return getattr(self, name).", + "builtins.IndexError.__getstate__" => "Helper for pickle.", + "builtins.IndexError.__gt__" => "Return self>value.", + "builtins.IndexError.__hash__" => "Return hash(self).", + "builtins.IndexError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.IndexError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.IndexError.__le__" => "Return self<=value.", + "builtins.IndexError.__lt__" => "Return self<value.", + "builtins.IndexError.__ne__" => "Return self!=value.", + "builtins.IndexError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.IndexError.__reduce_ex__" => "Helper for pickle.", + "builtins.IndexError.__repr__" => "Return repr(self).", + "builtins.IndexError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.IndexError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.IndexError.__str__" => "Return str(self).", + "builtins.IndexError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.IndexError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.IndexError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.InterruptedError" => "Interrupted by signal.", + "builtins.InterruptedError.__cause__" => "exception cause", + "builtins.InterruptedError.__context__" => "exception context", + "builtins.InterruptedError.__delattr__" => "Implement delattr(self, name).", + "builtins.InterruptedError.__eq__" => "Return self==value.", + "builtins.InterruptedError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.InterruptedError.__ge__" => "Return self>=value.", + "builtins.InterruptedError.__getattribute__" => "Return getattr(self, name).", + "builtins.InterruptedError.__getstate__" => "Helper for pickle.", + "builtins.InterruptedError.__gt__" => "Return self>value.", + "builtins.InterruptedError.__hash__" => "Return hash(self).", + "builtins.InterruptedError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.InterruptedError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.InterruptedError.__le__" => "Return self<=value.", + "builtins.InterruptedError.__lt__" => "Return self<value.", + "builtins.InterruptedError.__ne__" => "Return self!=value.", + "builtins.InterruptedError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.InterruptedError.__reduce_ex__" => "Helper for pickle.", + "builtins.InterruptedError.__repr__" => "Return repr(self).", + "builtins.InterruptedError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.InterruptedError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.InterruptedError.__str__" => "Return str(self).", + "builtins.InterruptedError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.InterruptedError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.InterruptedError.errno" => "POSIX exception code", + "builtins.InterruptedError.filename" => "exception filename", + "builtins.InterruptedError.filename2" => "second exception filename", + "builtins.InterruptedError.strerror" => "exception strerror", + "builtins.InterruptedError.winerror" => "Win32 exception code", + "builtins.InterruptedError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.IsADirectoryError" => "Operation doesn't work on directories.", + "builtins.IsADirectoryError.__cause__" => "exception cause", + "builtins.IsADirectoryError.__context__" => "exception context", + "builtins.IsADirectoryError.__delattr__" => "Implement delattr(self, name).", + "builtins.IsADirectoryError.__eq__" => "Return self==value.", + "builtins.IsADirectoryError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.IsADirectoryError.__ge__" => "Return self>=value.", + "builtins.IsADirectoryError.__getattribute__" => "Return getattr(self, name).", + "builtins.IsADirectoryError.__getstate__" => "Helper for pickle.", + "builtins.IsADirectoryError.__gt__" => "Return self>value.", + "builtins.IsADirectoryError.__hash__" => "Return hash(self).", + "builtins.IsADirectoryError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.IsADirectoryError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.IsADirectoryError.__le__" => "Return self<=value.", + "builtins.IsADirectoryError.__lt__" => "Return self<value.", + "builtins.IsADirectoryError.__ne__" => "Return self!=value.", + "builtins.IsADirectoryError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.IsADirectoryError.__reduce_ex__" => "Helper for pickle.", + "builtins.IsADirectoryError.__repr__" => "Return repr(self).", + "builtins.IsADirectoryError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.IsADirectoryError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.IsADirectoryError.__str__" => "Return str(self).", + "builtins.IsADirectoryError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.IsADirectoryError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.IsADirectoryError.errno" => "POSIX exception code", + "builtins.IsADirectoryError.filename" => "exception filename", + "builtins.IsADirectoryError.filename2" => "second exception filename", + "builtins.IsADirectoryError.strerror" => "exception strerror", + "builtins.IsADirectoryError.winerror" => "Win32 exception code", + "builtins.IsADirectoryError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.KeyError" => "Mapping key not found.", + "builtins.KeyError.__cause__" => "exception cause", + "builtins.KeyError.__context__" => "exception context", + "builtins.KeyError.__delattr__" => "Implement delattr(self, name).", + "builtins.KeyError.__eq__" => "Return self==value.", + "builtins.KeyError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.KeyError.__ge__" => "Return self>=value.", + "builtins.KeyError.__getattribute__" => "Return getattr(self, name).", + "builtins.KeyError.__getstate__" => "Helper for pickle.", + "builtins.KeyError.__gt__" => "Return self>value.", + "builtins.KeyError.__hash__" => "Return hash(self).", + "builtins.KeyError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.KeyError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.KeyError.__le__" => "Return self<=value.", + "builtins.KeyError.__lt__" => "Return self<value.", + "builtins.KeyError.__ne__" => "Return self!=value.", + "builtins.KeyError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.KeyError.__reduce_ex__" => "Helper for pickle.", + "builtins.KeyError.__repr__" => "Return repr(self).", + "builtins.KeyError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.KeyError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.KeyError.__str__" => "Return str(self).", + "builtins.KeyError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.KeyError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.KeyError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.KeyboardInterrupt" => "Program interrupted by user.", + "builtins.KeyboardInterrupt.__cause__" => "exception cause", + "builtins.KeyboardInterrupt.__context__" => "exception context", + "builtins.KeyboardInterrupt.__delattr__" => "Implement delattr(self, name).", + "builtins.KeyboardInterrupt.__eq__" => "Return self==value.", + "builtins.KeyboardInterrupt.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.KeyboardInterrupt.__ge__" => "Return self>=value.", + "builtins.KeyboardInterrupt.__getattribute__" => "Return getattr(self, name).", + "builtins.KeyboardInterrupt.__getstate__" => "Helper for pickle.", + "builtins.KeyboardInterrupt.__gt__" => "Return self>value.", + "builtins.KeyboardInterrupt.__hash__" => "Return hash(self).", + "builtins.KeyboardInterrupt.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.KeyboardInterrupt.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.KeyboardInterrupt.__le__" => "Return self<=value.", + "builtins.KeyboardInterrupt.__lt__" => "Return self<value.", + "builtins.KeyboardInterrupt.__ne__" => "Return self!=value.", + "builtins.KeyboardInterrupt.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.KeyboardInterrupt.__reduce_ex__" => "Helper for pickle.", + "builtins.KeyboardInterrupt.__repr__" => "Return repr(self).", + "builtins.KeyboardInterrupt.__setattr__" => "Implement setattr(self, name, value).", + "builtins.KeyboardInterrupt.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.KeyboardInterrupt.__str__" => "Return str(self).", + "builtins.KeyboardInterrupt.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.KeyboardInterrupt.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.KeyboardInterrupt.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.LookupError" => "Base class for lookup errors.", + "builtins.LookupError.__cause__" => "exception cause", + "builtins.LookupError.__context__" => "exception context", + "builtins.LookupError.__delattr__" => "Implement delattr(self, name).", + "builtins.LookupError.__eq__" => "Return self==value.", + "builtins.LookupError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.LookupError.__ge__" => "Return self>=value.", + "builtins.LookupError.__getattribute__" => "Return getattr(self, name).", + "builtins.LookupError.__getstate__" => "Helper for pickle.", + "builtins.LookupError.__gt__" => "Return self>value.", + "builtins.LookupError.__hash__" => "Return hash(self).", + "builtins.LookupError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.LookupError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.LookupError.__le__" => "Return self<=value.", + "builtins.LookupError.__lt__" => "Return self<value.", + "builtins.LookupError.__ne__" => "Return self!=value.", + "builtins.LookupError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.LookupError.__reduce_ex__" => "Helper for pickle.", + "builtins.LookupError.__repr__" => "Return repr(self).", + "builtins.LookupError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.LookupError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.LookupError.__str__" => "Return str(self).", + "builtins.LookupError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.LookupError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.LookupError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.MemoryError" => "Out of memory.", + "builtins.MemoryError.__cause__" => "exception cause", + "builtins.MemoryError.__context__" => "exception context", + "builtins.MemoryError.__delattr__" => "Implement delattr(self, name).", + "builtins.MemoryError.__eq__" => "Return self==value.", + "builtins.MemoryError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.MemoryError.__ge__" => "Return self>=value.", + "builtins.MemoryError.__getattribute__" => "Return getattr(self, name).", + "builtins.MemoryError.__getstate__" => "Helper for pickle.", + "builtins.MemoryError.__gt__" => "Return self>value.", + "builtins.MemoryError.__hash__" => "Return hash(self).", + "builtins.MemoryError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.MemoryError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.MemoryError.__le__" => "Return self<=value.", + "builtins.MemoryError.__lt__" => "Return self<value.", + "builtins.MemoryError.__ne__" => "Return self!=value.", + "builtins.MemoryError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.MemoryError.__reduce_ex__" => "Helper for pickle.", + "builtins.MemoryError.__repr__" => "Return repr(self).", + "builtins.MemoryError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.MemoryError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.MemoryError.__str__" => "Return str(self).", + "builtins.MemoryError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.MemoryError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.MemoryError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ModuleNotFoundError" => "Module not found.", + "builtins.ModuleNotFoundError.__cause__" => "exception cause", + "builtins.ModuleNotFoundError.__context__" => "exception context", + "builtins.ModuleNotFoundError.__delattr__" => "Implement delattr(self, name).", + "builtins.ModuleNotFoundError.__eq__" => "Return self==value.", + "builtins.ModuleNotFoundError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ModuleNotFoundError.__ge__" => "Return self>=value.", + "builtins.ModuleNotFoundError.__getattribute__" => "Return getattr(self, name).", + "builtins.ModuleNotFoundError.__getstate__" => "Helper for pickle.", + "builtins.ModuleNotFoundError.__gt__" => "Return self>value.", + "builtins.ModuleNotFoundError.__hash__" => "Return hash(self).", + "builtins.ModuleNotFoundError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ModuleNotFoundError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ModuleNotFoundError.__le__" => "Return self<=value.", + "builtins.ModuleNotFoundError.__lt__" => "Return self<value.", + "builtins.ModuleNotFoundError.__ne__" => "Return self!=value.", + "builtins.ModuleNotFoundError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ModuleNotFoundError.__reduce_ex__" => "Helper for pickle.", + "builtins.ModuleNotFoundError.__repr__" => "Return repr(self).", + "builtins.ModuleNotFoundError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ModuleNotFoundError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ModuleNotFoundError.__str__" => "Return str(self).", + "builtins.ModuleNotFoundError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ModuleNotFoundError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ModuleNotFoundError.msg" => "exception message", + "builtins.ModuleNotFoundError.name" => "module name", + "builtins.ModuleNotFoundError.name_from" => "name imported from module", + "builtins.ModuleNotFoundError.path" => "module path", + "builtins.ModuleNotFoundError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.NameError" => "Name not found globally.", + "builtins.NameError.__cause__" => "exception cause", + "builtins.NameError.__context__" => "exception context", + "builtins.NameError.__delattr__" => "Implement delattr(self, name).", + "builtins.NameError.__eq__" => "Return self==value.", + "builtins.NameError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.NameError.__ge__" => "Return self>=value.", + "builtins.NameError.__getattribute__" => "Return getattr(self, name).", + "builtins.NameError.__getstate__" => "Helper for pickle.", + "builtins.NameError.__gt__" => "Return self>value.", + "builtins.NameError.__hash__" => "Return hash(self).", + "builtins.NameError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.NameError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.NameError.__le__" => "Return self<=value.", + "builtins.NameError.__lt__" => "Return self<value.", + "builtins.NameError.__ne__" => "Return self!=value.", + "builtins.NameError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.NameError.__reduce_ex__" => "Helper for pickle.", + "builtins.NameError.__repr__" => "Return repr(self).", + "builtins.NameError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.NameError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.NameError.__str__" => "Return str(self).", + "builtins.NameError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.NameError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.NameError.name" => "name", + "builtins.NameError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.NoneType" => "The type of the None singleton.", + "builtins.NoneType.__bool__" => "True if self else False", + "builtins.NoneType.__delattr__" => "Implement delattr(self, name).", + "builtins.NoneType.__eq__" => "Return self==value.", + "builtins.NoneType.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.NoneType.__ge__" => "Return self>=value.", + "builtins.NoneType.__getattribute__" => "Return getattr(self, name).", + "builtins.NoneType.__getstate__" => "Helper for pickle.", + "builtins.NoneType.__gt__" => "Return self>value.", + "builtins.NoneType.__hash__" => "Return hash(self).", + "builtins.NoneType.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.NoneType.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.NoneType.__le__" => "Return self<=value.", + "builtins.NoneType.__lt__" => "Return self<value.", + "builtins.NoneType.__ne__" => "Return self!=value.", + "builtins.NoneType.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.NoneType.__reduce__" => "Helper for pickle.", + "builtins.NoneType.__reduce_ex__" => "Helper for pickle.", + "builtins.NoneType.__repr__" => "Return repr(self).", + "builtins.NoneType.__setattr__" => "Implement setattr(self, name, value).", + "builtins.NoneType.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.NoneType.__str__" => "Return str(self).", + "builtins.NoneType.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.NotADirectoryError" => "Operation only works on directories.", + "builtins.NotADirectoryError.__cause__" => "exception cause", + "builtins.NotADirectoryError.__context__" => "exception context", + "builtins.NotADirectoryError.__delattr__" => "Implement delattr(self, name).", + "builtins.NotADirectoryError.__eq__" => "Return self==value.", + "builtins.NotADirectoryError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.NotADirectoryError.__ge__" => "Return self>=value.", + "builtins.NotADirectoryError.__getattribute__" => "Return getattr(self, name).", + "builtins.NotADirectoryError.__getstate__" => "Helper for pickle.", + "builtins.NotADirectoryError.__gt__" => "Return self>value.", + "builtins.NotADirectoryError.__hash__" => "Return hash(self).", + "builtins.NotADirectoryError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.NotADirectoryError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.NotADirectoryError.__le__" => "Return self<=value.", + "builtins.NotADirectoryError.__lt__" => "Return self<value.", + "builtins.NotADirectoryError.__ne__" => "Return self!=value.", + "builtins.NotADirectoryError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.NotADirectoryError.__reduce_ex__" => "Helper for pickle.", + "builtins.NotADirectoryError.__repr__" => "Return repr(self).", + "builtins.NotADirectoryError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.NotADirectoryError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.NotADirectoryError.__str__" => "Return str(self).", + "builtins.NotADirectoryError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.NotADirectoryError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.NotADirectoryError.errno" => "POSIX exception code", + "builtins.NotADirectoryError.filename" => "exception filename", + "builtins.NotADirectoryError.filename2" => "second exception filename", + "builtins.NotADirectoryError.strerror" => "exception strerror", + "builtins.NotADirectoryError.winerror" => "Win32 exception code", + "builtins.NotADirectoryError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.NotImplementedError" => "Method or function hasn't been implemented yet.", + "builtins.NotImplementedError.__cause__" => "exception cause", + "builtins.NotImplementedError.__context__" => "exception context", + "builtins.NotImplementedError.__delattr__" => "Implement delattr(self, name).", + "builtins.NotImplementedError.__eq__" => "Return self==value.", + "builtins.NotImplementedError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.NotImplementedError.__ge__" => "Return self>=value.", + "builtins.NotImplementedError.__getattribute__" => "Return getattr(self, name).", + "builtins.NotImplementedError.__getstate__" => "Helper for pickle.", + "builtins.NotImplementedError.__gt__" => "Return self>value.", + "builtins.NotImplementedError.__hash__" => "Return hash(self).", + "builtins.NotImplementedError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.NotImplementedError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.NotImplementedError.__le__" => "Return self<=value.", + "builtins.NotImplementedError.__lt__" => "Return self<value.", + "builtins.NotImplementedError.__ne__" => "Return self!=value.", + "builtins.NotImplementedError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.NotImplementedError.__reduce_ex__" => "Helper for pickle.", + "builtins.NotImplementedError.__repr__" => "Return repr(self).", + "builtins.NotImplementedError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.NotImplementedError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.NotImplementedError.__str__" => "Return str(self).", + "builtins.NotImplementedError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.NotImplementedError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.NotImplementedError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.OSError" => "Base class for I/O related errors.", + "builtins.OSError.__cause__" => "exception cause", + "builtins.OSError.__context__" => "exception context", + "builtins.OSError.__delattr__" => "Implement delattr(self, name).", + "builtins.OSError.__eq__" => "Return self==value.", + "builtins.OSError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.OSError.__ge__" => "Return self>=value.", + "builtins.OSError.__getattribute__" => "Return getattr(self, name).", + "builtins.OSError.__getstate__" => "Helper for pickle.", + "builtins.OSError.__gt__" => "Return self>value.", + "builtins.OSError.__hash__" => "Return hash(self).", + "builtins.OSError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.OSError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.OSError.__le__" => "Return self<=value.", + "builtins.OSError.__lt__" => "Return self<value.", + "builtins.OSError.__ne__" => "Return self!=value.", + "builtins.OSError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.OSError.__reduce_ex__" => "Helper for pickle.", + "builtins.OSError.__repr__" => "Return repr(self).", + "builtins.OSError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.OSError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.OSError.__str__" => "Return str(self).", + "builtins.OSError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.OSError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.OSError.errno" => "POSIX exception code", + "builtins.OSError.filename" => "exception filename", + "builtins.OSError.filename2" => "second exception filename", + "builtins.OSError.strerror" => "exception strerror", + "builtins.OSError.winerror" => "Win32 exception code", + "builtins.OSError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.OverflowError" => "Result too large to be represented.", + "builtins.OverflowError.__cause__" => "exception cause", + "builtins.OverflowError.__context__" => "exception context", + "builtins.OverflowError.__delattr__" => "Implement delattr(self, name).", + "builtins.OverflowError.__eq__" => "Return self==value.", + "builtins.OverflowError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.OverflowError.__ge__" => "Return self>=value.", + "builtins.OverflowError.__getattribute__" => "Return getattr(self, name).", + "builtins.OverflowError.__getstate__" => "Helper for pickle.", + "builtins.OverflowError.__gt__" => "Return self>value.", + "builtins.OverflowError.__hash__" => "Return hash(self).", + "builtins.OverflowError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.OverflowError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.OverflowError.__le__" => "Return self<=value.", + "builtins.OverflowError.__lt__" => "Return self<value.", + "builtins.OverflowError.__ne__" => "Return self!=value.", + "builtins.OverflowError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.OverflowError.__reduce_ex__" => "Helper for pickle.", + "builtins.OverflowError.__repr__" => "Return repr(self).", + "builtins.OverflowError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.OverflowError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.OverflowError.__str__" => "Return str(self).", + "builtins.OverflowError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.OverflowError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.OverflowError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.PendingDeprecationWarning" => "Base class for warnings about features which will be deprecated\nin the future.", + "builtins.PendingDeprecationWarning.__cause__" => "exception cause", + "builtins.PendingDeprecationWarning.__context__" => "exception context", + "builtins.PendingDeprecationWarning.__delattr__" => "Implement delattr(self, name).", + "builtins.PendingDeprecationWarning.__eq__" => "Return self==value.", + "builtins.PendingDeprecationWarning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.PendingDeprecationWarning.__ge__" => "Return self>=value.", + "builtins.PendingDeprecationWarning.__getattribute__" => "Return getattr(self, name).", + "builtins.PendingDeprecationWarning.__getstate__" => "Helper for pickle.", + "builtins.PendingDeprecationWarning.__gt__" => "Return self>value.", + "builtins.PendingDeprecationWarning.__hash__" => "Return hash(self).", + "builtins.PendingDeprecationWarning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.PendingDeprecationWarning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.PendingDeprecationWarning.__le__" => "Return self<=value.", + "builtins.PendingDeprecationWarning.__lt__" => "Return self<value.", + "builtins.PendingDeprecationWarning.__ne__" => "Return self!=value.", + "builtins.PendingDeprecationWarning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.PendingDeprecationWarning.__reduce_ex__" => "Helper for pickle.", + "builtins.PendingDeprecationWarning.__repr__" => "Return repr(self).", + "builtins.PendingDeprecationWarning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.PendingDeprecationWarning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.PendingDeprecationWarning.__str__" => "Return str(self).", + "builtins.PendingDeprecationWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.PendingDeprecationWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.PendingDeprecationWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.PermissionError" => "Not enough permissions.", + "builtins.PermissionError.__cause__" => "exception cause", + "builtins.PermissionError.__context__" => "exception context", + "builtins.PermissionError.__delattr__" => "Implement delattr(self, name).", + "builtins.PermissionError.__eq__" => "Return self==value.", + "builtins.PermissionError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.PermissionError.__ge__" => "Return self>=value.", + "builtins.PermissionError.__getattribute__" => "Return getattr(self, name).", + "builtins.PermissionError.__getstate__" => "Helper for pickle.", + "builtins.PermissionError.__gt__" => "Return self>value.", + "builtins.PermissionError.__hash__" => "Return hash(self).", + "builtins.PermissionError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.PermissionError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.PermissionError.__le__" => "Return self<=value.", + "builtins.PermissionError.__lt__" => "Return self<value.", + "builtins.PermissionError.__ne__" => "Return self!=value.", + "builtins.PermissionError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.PermissionError.__reduce_ex__" => "Helper for pickle.", + "builtins.PermissionError.__repr__" => "Return repr(self).", + "builtins.PermissionError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.PermissionError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.PermissionError.__str__" => "Return str(self).", + "builtins.PermissionError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.PermissionError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.PermissionError.errno" => "POSIX exception code", + "builtins.PermissionError.filename" => "exception filename", + "builtins.PermissionError.filename2" => "second exception filename", + "builtins.PermissionError.strerror" => "exception strerror", + "builtins.PermissionError.winerror" => "Win32 exception code", + "builtins.PermissionError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ProcessLookupError" => "Process not found.", + "builtins.ProcessLookupError.__cause__" => "exception cause", + "builtins.ProcessLookupError.__context__" => "exception context", + "builtins.ProcessLookupError.__delattr__" => "Implement delattr(self, name).", + "builtins.ProcessLookupError.__eq__" => "Return self==value.", + "builtins.ProcessLookupError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ProcessLookupError.__ge__" => "Return self>=value.", + "builtins.ProcessLookupError.__getattribute__" => "Return getattr(self, name).", + "builtins.ProcessLookupError.__getstate__" => "Helper for pickle.", + "builtins.ProcessLookupError.__gt__" => "Return self>value.", + "builtins.ProcessLookupError.__hash__" => "Return hash(self).", + "builtins.ProcessLookupError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ProcessLookupError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ProcessLookupError.__le__" => "Return self<=value.", + "builtins.ProcessLookupError.__lt__" => "Return self<value.", + "builtins.ProcessLookupError.__ne__" => "Return self!=value.", + "builtins.ProcessLookupError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ProcessLookupError.__reduce_ex__" => "Helper for pickle.", + "builtins.ProcessLookupError.__repr__" => "Return repr(self).", + "builtins.ProcessLookupError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ProcessLookupError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ProcessLookupError.__str__" => "Return str(self).", + "builtins.ProcessLookupError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ProcessLookupError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ProcessLookupError.errno" => "POSIX exception code", + "builtins.ProcessLookupError.filename" => "exception filename", + "builtins.ProcessLookupError.filename2" => "second exception filename", + "builtins.ProcessLookupError.strerror" => "exception strerror", + "builtins.ProcessLookupError.winerror" => "Win32 exception code", + "builtins.ProcessLookupError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.PythonFinalizationError" => "Operation blocked during Python finalization.", + "builtins.PythonFinalizationError.__cause__" => "exception cause", + "builtins.PythonFinalizationError.__context__" => "exception context", + "builtins.PythonFinalizationError.__delattr__" => "Implement delattr(self, name).", + "builtins.PythonFinalizationError.__eq__" => "Return self==value.", + "builtins.PythonFinalizationError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.PythonFinalizationError.__ge__" => "Return self>=value.", + "builtins.PythonFinalizationError.__getattribute__" => "Return getattr(self, name).", + "builtins.PythonFinalizationError.__getstate__" => "Helper for pickle.", + "builtins.PythonFinalizationError.__gt__" => "Return self>value.", + "builtins.PythonFinalizationError.__hash__" => "Return hash(self).", + "builtins.PythonFinalizationError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.PythonFinalizationError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.PythonFinalizationError.__le__" => "Return self<=value.", + "builtins.PythonFinalizationError.__lt__" => "Return self<value.", + "builtins.PythonFinalizationError.__ne__" => "Return self!=value.", + "builtins.PythonFinalizationError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.PythonFinalizationError.__reduce_ex__" => "Helper for pickle.", + "builtins.PythonFinalizationError.__repr__" => "Return repr(self).", + "builtins.PythonFinalizationError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.PythonFinalizationError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.PythonFinalizationError.__str__" => "Return str(self).", + "builtins.PythonFinalizationError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.PythonFinalizationError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.PythonFinalizationError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.RecursionError" => "Recursion limit exceeded.", + "builtins.RecursionError.__cause__" => "exception cause", + "builtins.RecursionError.__context__" => "exception context", + "builtins.RecursionError.__delattr__" => "Implement delattr(self, name).", + "builtins.RecursionError.__eq__" => "Return self==value.", + "builtins.RecursionError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.RecursionError.__ge__" => "Return self>=value.", + "builtins.RecursionError.__getattribute__" => "Return getattr(self, name).", + "builtins.RecursionError.__getstate__" => "Helper for pickle.", + "builtins.RecursionError.__gt__" => "Return self>value.", + "builtins.RecursionError.__hash__" => "Return hash(self).", + "builtins.RecursionError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.RecursionError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.RecursionError.__le__" => "Return self<=value.", + "builtins.RecursionError.__lt__" => "Return self<value.", + "builtins.RecursionError.__ne__" => "Return self!=value.", + "builtins.RecursionError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.RecursionError.__reduce_ex__" => "Helper for pickle.", + "builtins.RecursionError.__repr__" => "Return repr(self).", + "builtins.RecursionError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.RecursionError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.RecursionError.__str__" => "Return str(self).", + "builtins.RecursionError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.RecursionError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.RecursionError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ReferenceError" => "Weak ref proxy used after referent went away.", + "builtins.ReferenceError.__cause__" => "exception cause", + "builtins.ReferenceError.__context__" => "exception context", + "builtins.ReferenceError.__delattr__" => "Implement delattr(self, name).", + "builtins.ReferenceError.__eq__" => "Return self==value.", + "builtins.ReferenceError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ReferenceError.__ge__" => "Return self>=value.", + "builtins.ReferenceError.__getattribute__" => "Return getattr(self, name).", + "builtins.ReferenceError.__getstate__" => "Helper for pickle.", + "builtins.ReferenceError.__gt__" => "Return self>value.", + "builtins.ReferenceError.__hash__" => "Return hash(self).", + "builtins.ReferenceError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ReferenceError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ReferenceError.__le__" => "Return self<=value.", + "builtins.ReferenceError.__lt__" => "Return self<value.", + "builtins.ReferenceError.__ne__" => "Return self!=value.", + "builtins.ReferenceError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ReferenceError.__reduce_ex__" => "Helper for pickle.", + "builtins.ReferenceError.__repr__" => "Return repr(self).", + "builtins.ReferenceError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ReferenceError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ReferenceError.__str__" => "Return str(self).", + "builtins.ReferenceError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ReferenceError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ReferenceError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ResourceWarning" => "Base class for warnings about resource usage.", + "builtins.ResourceWarning.__cause__" => "exception cause", + "builtins.ResourceWarning.__context__" => "exception context", + "builtins.ResourceWarning.__delattr__" => "Implement delattr(self, name).", + "builtins.ResourceWarning.__eq__" => "Return self==value.", + "builtins.ResourceWarning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ResourceWarning.__ge__" => "Return self>=value.", + "builtins.ResourceWarning.__getattribute__" => "Return getattr(self, name).", + "builtins.ResourceWarning.__getstate__" => "Helper for pickle.", + "builtins.ResourceWarning.__gt__" => "Return self>value.", + "builtins.ResourceWarning.__hash__" => "Return hash(self).", + "builtins.ResourceWarning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ResourceWarning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ResourceWarning.__le__" => "Return self<=value.", + "builtins.ResourceWarning.__lt__" => "Return self<value.", + "builtins.ResourceWarning.__ne__" => "Return self!=value.", + "builtins.ResourceWarning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ResourceWarning.__reduce_ex__" => "Helper for pickle.", + "builtins.ResourceWarning.__repr__" => "Return repr(self).", + "builtins.ResourceWarning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ResourceWarning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ResourceWarning.__str__" => "Return str(self).", + "builtins.ResourceWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ResourceWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ResourceWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.RuntimeError" => "Unspecified run-time error.", + "builtins.RuntimeError.__cause__" => "exception cause", + "builtins.RuntimeError.__context__" => "exception context", + "builtins.RuntimeError.__delattr__" => "Implement delattr(self, name).", + "builtins.RuntimeError.__eq__" => "Return self==value.", + "builtins.RuntimeError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.RuntimeError.__ge__" => "Return self>=value.", + "builtins.RuntimeError.__getattribute__" => "Return getattr(self, name).", + "builtins.RuntimeError.__getstate__" => "Helper for pickle.", + "builtins.RuntimeError.__gt__" => "Return self>value.", + "builtins.RuntimeError.__hash__" => "Return hash(self).", + "builtins.RuntimeError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.RuntimeError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.RuntimeError.__le__" => "Return self<=value.", + "builtins.RuntimeError.__lt__" => "Return self<value.", + "builtins.RuntimeError.__ne__" => "Return self!=value.", + "builtins.RuntimeError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.RuntimeError.__reduce_ex__" => "Helper for pickle.", + "builtins.RuntimeError.__repr__" => "Return repr(self).", + "builtins.RuntimeError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.RuntimeError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.RuntimeError.__str__" => "Return str(self).", + "builtins.RuntimeError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.RuntimeError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.RuntimeError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.RuntimeWarning" => "Base class for warnings about dubious runtime behavior.", + "builtins.RuntimeWarning.__cause__" => "exception cause", + "builtins.RuntimeWarning.__context__" => "exception context", + "builtins.RuntimeWarning.__delattr__" => "Implement delattr(self, name).", + "builtins.RuntimeWarning.__eq__" => "Return self==value.", + "builtins.RuntimeWarning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.RuntimeWarning.__ge__" => "Return self>=value.", + "builtins.RuntimeWarning.__getattribute__" => "Return getattr(self, name).", + "builtins.RuntimeWarning.__getstate__" => "Helper for pickle.", + "builtins.RuntimeWarning.__gt__" => "Return self>value.", + "builtins.RuntimeWarning.__hash__" => "Return hash(self).", + "builtins.RuntimeWarning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.RuntimeWarning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.RuntimeWarning.__le__" => "Return self<=value.", + "builtins.RuntimeWarning.__lt__" => "Return self<value.", + "builtins.RuntimeWarning.__ne__" => "Return self!=value.", + "builtins.RuntimeWarning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.RuntimeWarning.__reduce_ex__" => "Helper for pickle.", + "builtins.RuntimeWarning.__repr__" => "Return repr(self).", + "builtins.RuntimeWarning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.RuntimeWarning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.RuntimeWarning.__str__" => "Return str(self).", + "builtins.RuntimeWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.RuntimeWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.RuntimeWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.StopAsyncIteration" => "Signal the end from iterator.__anext__().", + "builtins.StopAsyncIteration.__cause__" => "exception cause", + "builtins.StopAsyncIteration.__context__" => "exception context", + "builtins.StopAsyncIteration.__delattr__" => "Implement delattr(self, name).", + "builtins.StopAsyncIteration.__eq__" => "Return self==value.", + "builtins.StopAsyncIteration.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.StopAsyncIteration.__ge__" => "Return self>=value.", + "builtins.StopAsyncIteration.__getattribute__" => "Return getattr(self, name).", + "builtins.StopAsyncIteration.__getstate__" => "Helper for pickle.", + "builtins.StopAsyncIteration.__gt__" => "Return self>value.", + "builtins.StopAsyncIteration.__hash__" => "Return hash(self).", + "builtins.StopAsyncIteration.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.StopAsyncIteration.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.StopAsyncIteration.__le__" => "Return self<=value.", + "builtins.StopAsyncIteration.__lt__" => "Return self<value.", + "builtins.StopAsyncIteration.__ne__" => "Return self!=value.", + "builtins.StopAsyncIteration.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.StopAsyncIteration.__reduce_ex__" => "Helper for pickle.", + "builtins.StopAsyncIteration.__repr__" => "Return repr(self).", + "builtins.StopAsyncIteration.__setattr__" => "Implement setattr(self, name, value).", + "builtins.StopAsyncIteration.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.StopAsyncIteration.__str__" => "Return str(self).", + "builtins.StopAsyncIteration.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.StopAsyncIteration.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.StopAsyncIteration.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.StopIteration" => "Signal the end from iterator.__next__().", + "builtins.StopIteration.__cause__" => "exception cause", + "builtins.StopIteration.__context__" => "exception context", + "builtins.StopIteration.__delattr__" => "Implement delattr(self, name).", + "builtins.StopIteration.__eq__" => "Return self==value.", + "builtins.StopIteration.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.StopIteration.__ge__" => "Return self>=value.", + "builtins.StopIteration.__getattribute__" => "Return getattr(self, name).", + "builtins.StopIteration.__getstate__" => "Helper for pickle.", + "builtins.StopIteration.__gt__" => "Return self>value.", + "builtins.StopIteration.__hash__" => "Return hash(self).", + "builtins.StopIteration.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.StopIteration.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.StopIteration.__le__" => "Return self<=value.", + "builtins.StopIteration.__lt__" => "Return self<value.", + "builtins.StopIteration.__ne__" => "Return self!=value.", + "builtins.StopIteration.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.StopIteration.__reduce_ex__" => "Helper for pickle.", + "builtins.StopIteration.__repr__" => "Return repr(self).", + "builtins.StopIteration.__setattr__" => "Implement setattr(self, name, value).", + "builtins.StopIteration.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.StopIteration.__str__" => "Return str(self).", + "builtins.StopIteration.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.StopIteration.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.StopIteration.value" => "generator return value", + "builtins.StopIteration.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.SyntaxError" => "Invalid syntax.", + "builtins.SyntaxError.__cause__" => "exception cause", + "builtins.SyntaxError.__context__" => "exception context", + "builtins.SyntaxError.__delattr__" => "Implement delattr(self, name).", + "builtins.SyntaxError.__eq__" => "Return self==value.", + "builtins.SyntaxError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.SyntaxError.__ge__" => "Return self>=value.", + "builtins.SyntaxError.__getattribute__" => "Return getattr(self, name).", + "builtins.SyntaxError.__getstate__" => "Helper for pickle.", + "builtins.SyntaxError.__gt__" => "Return self>value.", + "builtins.SyntaxError.__hash__" => "Return hash(self).", + "builtins.SyntaxError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.SyntaxError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.SyntaxError.__le__" => "Return self<=value.", + "builtins.SyntaxError.__lt__" => "Return self<value.", + "builtins.SyntaxError.__ne__" => "Return self!=value.", + "builtins.SyntaxError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.SyntaxError.__reduce_ex__" => "Helper for pickle.", + "builtins.SyntaxError.__repr__" => "Return repr(self).", + "builtins.SyntaxError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.SyntaxError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.SyntaxError.__str__" => "Return str(self).", + "builtins.SyntaxError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.SyntaxError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.SyntaxError.end_lineno" => "exception end lineno", + "builtins.SyntaxError.end_offset" => "exception end offset", + "builtins.SyntaxError.filename" => "exception filename", + "builtins.SyntaxError.lineno" => "exception lineno", + "builtins.SyntaxError.msg" => "exception msg", + "builtins.SyntaxError.offset" => "exception offset", + "builtins.SyntaxError.print_file_and_line" => "exception print_file_and_line", + "builtins.SyntaxError.text" => "exception text", + "builtins.SyntaxError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.SyntaxWarning" => "Base class for warnings about dubious syntax.", + "builtins.SyntaxWarning.__cause__" => "exception cause", + "builtins.SyntaxWarning.__context__" => "exception context", + "builtins.SyntaxWarning.__delattr__" => "Implement delattr(self, name).", + "builtins.SyntaxWarning.__eq__" => "Return self==value.", + "builtins.SyntaxWarning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.SyntaxWarning.__ge__" => "Return self>=value.", + "builtins.SyntaxWarning.__getattribute__" => "Return getattr(self, name).", + "builtins.SyntaxWarning.__getstate__" => "Helper for pickle.", + "builtins.SyntaxWarning.__gt__" => "Return self>value.", + "builtins.SyntaxWarning.__hash__" => "Return hash(self).", + "builtins.SyntaxWarning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.SyntaxWarning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.SyntaxWarning.__le__" => "Return self<=value.", + "builtins.SyntaxWarning.__lt__" => "Return self<value.", + "builtins.SyntaxWarning.__ne__" => "Return self!=value.", + "builtins.SyntaxWarning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.SyntaxWarning.__reduce_ex__" => "Helper for pickle.", + "builtins.SyntaxWarning.__repr__" => "Return repr(self).", + "builtins.SyntaxWarning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.SyntaxWarning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.SyntaxWarning.__str__" => "Return str(self).", + "builtins.SyntaxWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.SyntaxWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.SyntaxWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.SystemError" => "Internal error in the Python interpreter.\n\nPlease report this to the Python maintainer, along with the traceback,\nthe Python version, and the hardware/OS platform and version.", + "builtins.SystemError.__cause__" => "exception cause", + "builtins.SystemError.__context__" => "exception context", + "builtins.SystemError.__delattr__" => "Implement delattr(self, name).", + "builtins.SystemError.__eq__" => "Return self==value.", + "builtins.SystemError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.SystemError.__ge__" => "Return self>=value.", + "builtins.SystemError.__getattribute__" => "Return getattr(self, name).", + "builtins.SystemError.__getstate__" => "Helper for pickle.", + "builtins.SystemError.__gt__" => "Return self>value.", + "builtins.SystemError.__hash__" => "Return hash(self).", + "builtins.SystemError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.SystemError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.SystemError.__le__" => "Return self<=value.", + "builtins.SystemError.__lt__" => "Return self<value.", + "builtins.SystemError.__ne__" => "Return self!=value.", + "builtins.SystemError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.SystemError.__reduce_ex__" => "Helper for pickle.", + "builtins.SystemError.__repr__" => "Return repr(self).", + "builtins.SystemError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.SystemError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.SystemError.__str__" => "Return str(self).", + "builtins.SystemError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.SystemError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.SystemError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.SystemExit" => "Request to exit from the interpreter.", + "builtins.SystemExit.__cause__" => "exception cause", + "builtins.SystemExit.__context__" => "exception context", + "builtins.SystemExit.__delattr__" => "Implement delattr(self, name).", + "builtins.SystemExit.__eq__" => "Return self==value.", + "builtins.SystemExit.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.SystemExit.__ge__" => "Return self>=value.", + "builtins.SystemExit.__getattribute__" => "Return getattr(self, name).", + "builtins.SystemExit.__getstate__" => "Helper for pickle.", + "builtins.SystemExit.__gt__" => "Return self>value.", + "builtins.SystemExit.__hash__" => "Return hash(self).", + "builtins.SystemExit.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.SystemExit.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.SystemExit.__le__" => "Return self<=value.", + "builtins.SystemExit.__lt__" => "Return self<value.", + "builtins.SystemExit.__ne__" => "Return self!=value.", + "builtins.SystemExit.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.SystemExit.__reduce_ex__" => "Helper for pickle.", + "builtins.SystemExit.__repr__" => "Return repr(self).", + "builtins.SystemExit.__setattr__" => "Implement setattr(self, name, value).", + "builtins.SystemExit.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.SystemExit.__str__" => "Return str(self).", + "builtins.SystemExit.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.SystemExit.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.SystemExit.code" => "exception code", + "builtins.SystemExit.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.TabError" => "Improper mixture of spaces and tabs.", + "builtins.TabError.__cause__" => "exception cause", + "builtins.TabError.__context__" => "exception context", + "builtins.TabError.__delattr__" => "Implement delattr(self, name).", + "builtins.TabError.__eq__" => "Return self==value.", + "builtins.TabError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.TabError.__ge__" => "Return self>=value.", + "builtins.TabError.__getattribute__" => "Return getattr(self, name).", + "builtins.TabError.__getstate__" => "Helper for pickle.", + "builtins.TabError.__gt__" => "Return self>value.", + "builtins.TabError.__hash__" => "Return hash(self).", + "builtins.TabError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.TabError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.TabError.__le__" => "Return self<=value.", + "builtins.TabError.__lt__" => "Return self<value.", + "builtins.TabError.__ne__" => "Return self!=value.", + "builtins.TabError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.TabError.__reduce_ex__" => "Helper for pickle.", + "builtins.TabError.__repr__" => "Return repr(self).", + "builtins.TabError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.TabError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.TabError.__str__" => "Return str(self).", + "builtins.TabError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.TabError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.TabError.end_lineno" => "exception end lineno", + "builtins.TabError.end_offset" => "exception end offset", + "builtins.TabError.filename" => "exception filename", + "builtins.TabError.lineno" => "exception lineno", + "builtins.TabError.msg" => "exception msg", + "builtins.TabError.offset" => "exception offset", + "builtins.TabError.print_file_and_line" => "exception print_file_and_line", + "builtins.TabError.text" => "exception text", + "builtins.TabError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.TimeoutError" => "Timeout expired.", + "builtins.TimeoutError.__cause__" => "exception cause", + "builtins.TimeoutError.__context__" => "exception context", + "builtins.TimeoutError.__delattr__" => "Implement delattr(self, name).", + "builtins.TimeoutError.__eq__" => "Return self==value.", + "builtins.TimeoutError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.TimeoutError.__ge__" => "Return self>=value.", + "builtins.TimeoutError.__getattribute__" => "Return getattr(self, name).", + "builtins.TimeoutError.__getstate__" => "Helper for pickle.", + "builtins.TimeoutError.__gt__" => "Return self>value.", + "builtins.TimeoutError.__hash__" => "Return hash(self).", + "builtins.TimeoutError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.TimeoutError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.TimeoutError.__le__" => "Return self<=value.", + "builtins.TimeoutError.__lt__" => "Return self<value.", + "builtins.TimeoutError.__ne__" => "Return self!=value.", + "builtins.TimeoutError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.TimeoutError.__reduce_ex__" => "Helper for pickle.", + "builtins.TimeoutError.__repr__" => "Return repr(self).", + "builtins.TimeoutError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.TimeoutError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.TimeoutError.__str__" => "Return str(self).", + "builtins.TimeoutError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.TimeoutError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.TimeoutError.errno" => "POSIX exception code", + "builtins.TimeoutError.filename" => "exception filename", + "builtins.TimeoutError.filename2" => "second exception filename", + "builtins.TimeoutError.strerror" => "exception strerror", + "builtins.TimeoutError.winerror" => "Win32 exception code", + "builtins.TimeoutError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.TypeError" => "Inappropriate argument type.", + "builtins.TypeError.__cause__" => "exception cause", + "builtins.TypeError.__context__" => "exception context", + "builtins.TypeError.__delattr__" => "Implement delattr(self, name).", + "builtins.TypeError.__eq__" => "Return self==value.", + "builtins.TypeError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.TypeError.__ge__" => "Return self>=value.", + "builtins.TypeError.__getattribute__" => "Return getattr(self, name).", + "builtins.TypeError.__getstate__" => "Helper for pickle.", + "builtins.TypeError.__gt__" => "Return self>value.", + "builtins.TypeError.__hash__" => "Return hash(self).", + "builtins.TypeError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.TypeError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.TypeError.__le__" => "Return self<=value.", + "builtins.TypeError.__lt__" => "Return self<value.", + "builtins.TypeError.__ne__" => "Return self!=value.", + "builtins.TypeError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.TypeError.__reduce_ex__" => "Helper for pickle.", + "builtins.TypeError.__repr__" => "Return repr(self).", + "builtins.TypeError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.TypeError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.TypeError.__str__" => "Return str(self).", + "builtins.TypeError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.TypeError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.TypeError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.UnboundLocalError" => "Local name referenced but not bound to a value.", + "builtins.UnboundLocalError.__cause__" => "exception cause", + "builtins.UnboundLocalError.__context__" => "exception context", + "builtins.UnboundLocalError.__delattr__" => "Implement delattr(self, name).", + "builtins.UnboundLocalError.__eq__" => "Return self==value.", + "builtins.UnboundLocalError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.UnboundLocalError.__ge__" => "Return self>=value.", + "builtins.UnboundLocalError.__getattribute__" => "Return getattr(self, name).", + "builtins.UnboundLocalError.__getstate__" => "Helper for pickle.", + "builtins.UnboundLocalError.__gt__" => "Return self>value.", + "builtins.UnboundLocalError.__hash__" => "Return hash(self).", + "builtins.UnboundLocalError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.UnboundLocalError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.UnboundLocalError.__le__" => "Return self<=value.", + "builtins.UnboundLocalError.__lt__" => "Return self<value.", + "builtins.UnboundLocalError.__ne__" => "Return self!=value.", + "builtins.UnboundLocalError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.UnboundLocalError.__reduce_ex__" => "Helper for pickle.", + "builtins.UnboundLocalError.__repr__" => "Return repr(self).", + "builtins.UnboundLocalError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.UnboundLocalError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.UnboundLocalError.__str__" => "Return str(self).", + "builtins.UnboundLocalError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.UnboundLocalError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.UnboundLocalError.name" => "name", + "builtins.UnboundLocalError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.UnicodeDecodeError" => "Unicode decoding error.", + "builtins.UnicodeDecodeError.__cause__" => "exception cause", + "builtins.UnicodeDecodeError.__context__" => "exception context", + "builtins.UnicodeDecodeError.__delattr__" => "Implement delattr(self, name).", + "builtins.UnicodeDecodeError.__eq__" => "Return self==value.", + "builtins.UnicodeDecodeError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.UnicodeDecodeError.__ge__" => "Return self>=value.", + "builtins.UnicodeDecodeError.__getattribute__" => "Return getattr(self, name).", + "builtins.UnicodeDecodeError.__getstate__" => "Helper for pickle.", + "builtins.UnicodeDecodeError.__gt__" => "Return self>value.", + "builtins.UnicodeDecodeError.__hash__" => "Return hash(self).", + "builtins.UnicodeDecodeError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.UnicodeDecodeError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.UnicodeDecodeError.__le__" => "Return self<=value.", + "builtins.UnicodeDecodeError.__lt__" => "Return self<value.", + "builtins.UnicodeDecodeError.__ne__" => "Return self!=value.", + "builtins.UnicodeDecodeError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.UnicodeDecodeError.__reduce_ex__" => "Helper for pickle.", + "builtins.UnicodeDecodeError.__repr__" => "Return repr(self).", + "builtins.UnicodeDecodeError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.UnicodeDecodeError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.UnicodeDecodeError.__str__" => "Return str(self).", + "builtins.UnicodeDecodeError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.UnicodeDecodeError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.UnicodeDecodeError.encoding" => "exception encoding", + "builtins.UnicodeDecodeError.end" => "exception end", + "builtins.UnicodeDecodeError.object" => "exception object", + "builtins.UnicodeDecodeError.reason" => "exception reason", + "builtins.UnicodeDecodeError.start" => "exception start", + "builtins.UnicodeDecodeError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.UnicodeEncodeError" => "Unicode encoding error.", + "builtins.UnicodeEncodeError.__cause__" => "exception cause", + "builtins.UnicodeEncodeError.__context__" => "exception context", + "builtins.UnicodeEncodeError.__delattr__" => "Implement delattr(self, name).", + "builtins.UnicodeEncodeError.__eq__" => "Return self==value.", + "builtins.UnicodeEncodeError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.UnicodeEncodeError.__ge__" => "Return self>=value.", + "builtins.UnicodeEncodeError.__getattribute__" => "Return getattr(self, name).", + "builtins.UnicodeEncodeError.__getstate__" => "Helper for pickle.", + "builtins.UnicodeEncodeError.__gt__" => "Return self>value.", + "builtins.UnicodeEncodeError.__hash__" => "Return hash(self).", + "builtins.UnicodeEncodeError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.UnicodeEncodeError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.UnicodeEncodeError.__le__" => "Return self<=value.", + "builtins.UnicodeEncodeError.__lt__" => "Return self<value.", + "builtins.UnicodeEncodeError.__ne__" => "Return self!=value.", + "builtins.UnicodeEncodeError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.UnicodeEncodeError.__reduce_ex__" => "Helper for pickle.", + "builtins.UnicodeEncodeError.__repr__" => "Return repr(self).", + "builtins.UnicodeEncodeError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.UnicodeEncodeError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.UnicodeEncodeError.__str__" => "Return str(self).", + "builtins.UnicodeEncodeError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.UnicodeEncodeError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.UnicodeEncodeError.encoding" => "exception encoding", + "builtins.UnicodeEncodeError.end" => "exception end", + "builtins.UnicodeEncodeError.object" => "exception object", + "builtins.UnicodeEncodeError.reason" => "exception reason", + "builtins.UnicodeEncodeError.start" => "exception start", + "builtins.UnicodeEncodeError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.UnicodeError" => "Unicode related error.", + "builtins.UnicodeError.__cause__" => "exception cause", + "builtins.UnicodeError.__context__" => "exception context", + "builtins.UnicodeError.__delattr__" => "Implement delattr(self, name).", + "builtins.UnicodeError.__eq__" => "Return self==value.", + "builtins.UnicodeError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.UnicodeError.__ge__" => "Return self>=value.", + "builtins.UnicodeError.__getattribute__" => "Return getattr(self, name).", + "builtins.UnicodeError.__getstate__" => "Helper for pickle.", + "builtins.UnicodeError.__gt__" => "Return self>value.", + "builtins.UnicodeError.__hash__" => "Return hash(self).", + "builtins.UnicodeError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.UnicodeError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.UnicodeError.__le__" => "Return self<=value.", + "builtins.UnicodeError.__lt__" => "Return self<value.", + "builtins.UnicodeError.__ne__" => "Return self!=value.", + "builtins.UnicodeError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.UnicodeError.__reduce_ex__" => "Helper for pickle.", + "builtins.UnicodeError.__repr__" => "Return repr(self).", + "builtins.UnicodeError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.UnicodeError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.UnicodeError.__str__" => "Return str(self).", + "builtins.UnicodeError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.UnicodeError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.UnicodeError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.UnicodeTranslateError" => "Unicode translation error.", + "builtins.UnicodeTranslateError.__cause__" => "exception cause", + "builtins.UnicodeTranslateError.__context__" => "exception context", + "builtins.UnicodeTranslateError.__delattr__" => "Implement delattr(self, name).", + "builtins.UnicodeTranslateError.__eq__" => "Return self==value.", + "builtins.UnicodeTranslateError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.UnicodeTranslateError.__ge__" => "Return self>=value.", + "builtins.UnicodeTranslateError.__getattribute__" => "Return getattr(self, name).", + "builtins.UnicodeTranslateError.__getstate__" => "Helper for pickle.", + "builtins.UnicodeTranslateError.__gt__" => "Return self>value.", + "builtins.UnicodeTranslateError.__hash__" => "Return hash(self).", + "builtins.UnicodeTranslateError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.UnicodeTranslateError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.UnicodeTranslateError.__le__" => "Return self<=value.", + "builtins.UnicodeTranslateError.__lt__" => "Return self<value.", + "builtins.UnicodeTranslateError.__ne__" => "Return self!=value.", + "builtins.UnicodeTranslateError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.UnicodeTranslateError.__reduce_ex__" => "Helper for pickle.", + "builtins.UnicodeTranslateError.__repr__" => "Return repr(self).", + "builtins.UnicodeTranslateError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.UnicodeTranslateError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.UnicodeTranslateError.__str__" => "Return str(self).", + "builtins.UnicodeTranslateError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.UnicodeTranslateError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.UnicodeTranslateError.encoding" => "exception encoding", + "builtins.UnicodeTranslateError.end" => "exception end", + "builtins.UnicodeTranslateError.object" => "exception object", + "builtins.UnicodeTranslateError.reason" => "exception reason", + "builtins.UnicodeTranslateError.start" => "exception start", + "builtins.UnicodeTranslateError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.UnicodeWarning" => "Base class for warnings about Unicode related problems, mostly\nrelated to conversion problems.", + "builtins.UnicodeWarning.__cause__" => "exception cause", + "builtins.UnicodeWarning.__context__" => "exception context", + "builtins.UnicodeWarning.__delattr__" => "Implement delattr(self, name).", + "builtins.UnicodeWarning.__eq__" => "Return self==value.", + "builtins.UnicodeWarning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.UnicodeWarning.__ge__" => "Return self>=value.", + "builtins.UnicodeWarning.__getattribute__" => "Return getattr(self, name).", + "builtins.UnicodeWarning.__getstate__" => "Helper for pickle.", + "builtins.UnicodeWarning.__gt__" => "Return self>value.", + "builtins.UnicodeWarning.__hash__" => "Return hash(self).", + "builtins.UnicodeWarning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.UnicodeWarning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.UnicodeWarning.__le__" => "Return self<=value.", + "builtins.UnicodeWarning.__lt__" => "Return self<value.", + "builtins.UnicodeWarning.__ne__" => "Return self!=value.", + "builtins.UnicodeWarning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.UnicodeWarning.__reduce_ex__" => "Helper for pickle.", + "builtins.UnicodeWarning.__repr__" => "Return repr(self).", + "builtins.UnicodeWarning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.UnicodeWarning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.UnicodeWarning.__str__" => "Return str(self).", + "builtins.UnicodeWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.UnicodeWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.UnicodeWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.UserWarning" => "Base class for warnings generated by user code.", + "builtins.UserWarning.__cause__" => "exception cause", + "builtins.UserWarning.__context__" => "exception context", + "builtins.UserWarning.__delattr__" => "Implement delattr(self, name).", + "builtins.UserWarning.__eq__" => "Return self==value.", + "builtins.UserWarning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.UserWarning.__ge__" => "Return self>=value.", + "builtins.UserWarning.__getattribute__" => "Return getattr(self, name).", + "builtins.UserWarning.__getstate__" => "Helper for pickle.", + "builtins.UserWarning.__gt__" => "Return self>value.", + "builtins.UserWarning.__hash__" => "Return hash(self).", + "builtins.UserWarning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.UserWarning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.UserWarning.__le__" => "Return self<=value.", + "builtins.UserWarning.__lt__" => "Return self<value.", + "builtins.UserWarning.__ne__" => "Return self!=value.", + "builtins.UserWarning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.UserWarning.__reduce_ex__" => "Helper for pickle.", + "builtins.UserWarning.__repr__" => "Return repr(self).", + "builtins.UserWarning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.UserWarning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.UserWarning.__str__" => "Return str(self).", + "builtins.UserWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.UserWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.UserWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ValueError" => "Inappropriate argument value (of correct type).", + "builtins.ValueError.__cause__" => "exception cause", + "builtins.ValueError.__context__" => "exception context", + "builtins.ValueError.__delattr__" => "Implement delattr(self, name).", + "builtins.ValueError.__eq__" => "Return self==value.", + "builtins.ValueError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ValueError.__ge__" => "Return self>=value.", + "builtins.ValueError.__getattribute__" => "Return getattr(self, name).", + "builtins.ValueError.__getstate__" => "Helper for pickle.", + "builtins.ValueError.__gt__" => "Return self>value.", + "builtins.ValueError.__hash__" => "Return hash(self).", + "builtins.ValueError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ValueError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ValueError.__le__" => "Return self<=value.", + "builtins.ValueError.__lt__" => "Return self<value.", + "builtins.ValueError.__ne__" => "Return self!=value.", + "builtins.ValueError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ValueError.__reduce_ex__" => "Helper for pickle.", + "builtins.ValueError.__repr__" => "Return repr(self).", + "builtins.ValueError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ValueError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ValueError.__str__" => "Return str(self).", + "builtins.ValueError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ValueError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ValueError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.Warning" => "Base class for warning categories.", + "builtins.Warning.__cause__" => "exception cause", + "builtins.Warning.__context__" => "exception context", + "builtins.Warning.__delattr__" => "Implement delattr(self, name).", + "builtins.Warning.__eq__" => "Return self==value.", + "builtins.Warning.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.Warning.__ge__" => "Return self>=value.", + "builtins.Warning.__getattribute__" => "Return getattr(self, name).", + "builtins.Warning.__getstate__" => "Helper for pickle.", + "builtins.Warning.__gt__" => "Return self>value.", + "builtins.Warning.__hash__" => "Return hash(self).", + "builtins.Warning.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.Warning.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.Warning.__le__" => "Return self<=value.", + "builtins.Warning.__lt__" => "Return self<value.", + "builtins.Warning.__ne__" => "Return self!=value.", + "builtins.Warning.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.Warning.__reduce_ex__" => "Helper for pickle.", + "builtins.Warning.__repr__" => "Return repr(self).", + "builtins.Warning.__setattr__" => "Implement setattr(self, name, value).", + "builtins.Warning.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.Warning.__str__" => "Return str(self).", + "builtins.Warning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.Warning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.Warning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.WindowsError" => "Base class for I/O related errors.", + "builtins.WindowsError.__cause__" => "exception cause", + "builtins.WindowsError.__context__" => "exception context", + "builtins.WindowsError.__delattr__" => "Implement delattr(self, name).", + "builtins.WindowsError.__eq__" => "Return self==value.", + "builtins.WindowsError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.WindowsError.__ge__" => "Return self>=value.", + "builtins.WindowsError.__getattribute__" => "Return getattr(self, name).", + "builtins.WindowsError.__getstate__" => "Helper for pickle.", + "builtins.WindowsError.__gt__" => "Return self>value.", + "builtins.WindowsError.__hash__" => "Return hash(self).", + "builtins.WindowsError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.WindowsError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.WindowsError.__le__" => "Return self<=value.", + "builtins.WindowsError.__lt__" => "Return self<value.", + "builtins.WindowsError.__ne__" => "Return self!=value.", + "builtins.WindowsError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.WindowsError.__reduce_ex__" => "Helper for pickle.", + "builtins.WindowsError.__repr__" => "Return repr(self).", + "builtins.WindowsError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.WindowsError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.WindowsError.__str__" => "Return str(self).", + "builtins.WindowsError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.WindowsError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.WindowsError.errno" => "POSIX exception code", + "builtins.WindowsError.filename" => "exception filename", + "builtins.WindowsError.filename2" => "second exception filename", + "builtins.WindowsError.strerror" => "exception strerror", + "builtins.WindowsError.winerror" => "Win32 exception code", + "builtins.WindowsError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.ZeroDivisionError" => "Second argument to a division or modulo operation was zero.", + "builtins.ZeroDivisionError.__cause__" => "exception cause", + "builtins.ZeroDivisionError.__context__" => "exception context", + "builtins.ZeroDivisionError.__delattr__" => "Implement delattr(self, name).", + "builtins.ZeroDivisionError.__eq__" => "Return self==value.", + "builtins.ZeroDivisionError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ZeroDivisionError.__ge__" => "Return self>=value.", + "builtins.ZeroDivisionError.__getattribute__" => "Return getattr(self, name).", + "builtins.ZeroDivisionError.__getstate__" => "Helper for pickle.", + "builtins.ZeroDivisionError.__gt__" => "Return self>value.", + "builtins.ZeroDivisionError.__hash__" => "Return hash(self).", + "builtins.ZeroDivisionError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ZeroDivisionError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ZeroDivisionError.__le__" => "Return self<=value.", + "builtins.ZeroDivisionError.__lt__" => "Return self<value.", + "builtins.ZeroDivisionError.__ne__" => "Return self!=value.", + "builtins.ZeroDivisionError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ZeroDivisionError.__reduce_ex__" => "Helper for pickle.", + "builtins.ZeroDivisionError.__repr__" => "Return repr(self).", + "builtins.ZeroDivisionError.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ZeroDivisionError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ZeroDivisionError.__str__" => "Return str(self).", + "builtins.ZeroDivisionError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.ZeroDivisionError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins.ZeroDivisionError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins._IncompleteInputError" => "incomplete input.", + "builtins._IncompleteInputError.__cause__" => "exception cause", + "builtins._IncompleteInputError.__context__" => "exception context", + "builtins._IncompleteInputError.__delattr__" => "Implement delattr(self, name).", + "builtins._IncompleteInputError.__eq__" => "Return self==value.", + "builtins._IncompleteInputError.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins._IncompleteInputError.__ge__" => "Return self>=value.", + "builtins._IncompleteInputError.__getattribute__" => "Return getattr(self, name).", + "builtins._IncompleteInputError.__getstate__" => "Helper for pickle.", + "builtins._IncompleteInputError.__gt__" => "Return self>value.", + "builtins._IncompleteInputError.__hash__" => "Return hash(self).", + "builtins._IncompleteInputError.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins._IncompleteInputError.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins._IncompleteInputError.__le__" => "Return self<=value.", + "builtins._IncompleteInputError.__lt__" => "Return self<value.", + "builtins._IncompleteInputError.__ne__" => "Return self!=value.", + "builtins._IncompleteInputError.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins._IncompleteInputError.__reduce_ex__" => "Helper for pickle.", + "builtins._IncompleteInputError.__repr__" => "Return repr(self).", + "builtins._IncompleteInputError.__setattr__" => "Implement setattr(self, name, value).", + "builtins._IncompleteInputError.__sizeof__" => "Size of object in memory, in bytes.", + "builtins._IncompleteInputError.__str__" => "Return str(self).", + "builtins._IncompleteInputError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins._IncompleteInputError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "builtins._IncompleteInputError.end_lineno" => "exception end lineno", + "builtins._IncompleteInputError.end_offset" => "exception end offset", + "builtins._IncompleteInputError.filename" => "exception filename", + "builtins._IncompleteInputError.lineno" => "exception lineno", + "builtins._IncompleteInputError.msg" => "exception msg", + "builtins._IncompleteInputError.offset" => "exception offset", + "builtins._IncompleteInputError.print_file_and_line" => "exception print_file_and_line", + "builtins._IncompleteInputError.text" => "exception text", + "builtins._IncompleteInputError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.__build_class__" => "__build_class__(func, name, /, *bases, [metaclass], **kwds) -> class\n\nInternal helper function used by the class statement.", + "builtins.__import__" => "Import a module.\n\nBecause this function is meant for use by the Python\ninterpreter and not for general use, it is better to use\nimportlib.import_module() to programmatically import a module.\n\nThe globals argument is only used to determine the context;\nthey are not modified. The locals argument is unused. The fromlist\nshould be a list of names to emulate ``from name import ...``, or an\nempty list to emulate ``import name``.\nWhen importing a module from a package, note that __import__('A.B', ...)\nreturns package A when fromlist is empty, but its submodule B when\nfromlist is not empty. The level argument is used to determine whether to\nperform absolute or relative imports: 0 is absolute, while a positive number\nis the number of parent directories to search relative to the current module.", + "builtins.abs" => "Return the absolute value of the argument.", + "builtins.aiter" => "Return an AsyncIterator for an AsyncIterable object.", + "builtins.all" => "Return True if bool(x) is True for all values x in the iterable.\n\nIf the iterable is empty, return True.", + "builtins.anext" => "Return the next item from the async iterator.\n\nIf default is given and the async iterator is exhausted,\nit is returned instead of raising StopAsyncIteration.", + "builtins.any" => "Return True if bool(x) is True for any x in the iterable.\n\nIf the iterable is empty, return False.", + "builtins.ascii" => "Return an ASCII-only representation of an object.\n\nAs repr(), return a string containing a printable representation of an\nobject, but escape the non-ASCII characters in the string returned by\nrepr() using \\\\x, \\\\u or \\\\U escapes. This generates a string similar\nto that returned by repr() in Python 2.", + "builtins.bin" => "Return the binary representation of an integer.\n\n>>> bin(2796202)\n'0b1010101010101010101010'", + "builtins.bool" => "Returns True when the argument is true, False otherwise.\nThe builtins True and False are the only two instances of the class bool.\nThe class bool is a subclass of the class int, and cannot be subclassed.", + "builtins.bool.__abs__" => "abs(self)", + "builtins.bool.__add__" => "Return self+value.", + "builtins.bool.__and__" => "Return self&value.", + "builtins.bool.__bool__" => "True if self else False", + "builtins.bool.__ceil__" => "Ceiling of an Integral returns itself.", + "builtins.bool.__delattr__" => "Implement delattr(self, name).", + "builtins.bool.__divmod__" => "Return divmod(self, value).", + "builtins.bool.__eq__" => "Return self==value.", + "builtins.bool.__float__" => "float(self)", + "builtins.bool.__floor__" => "Flooring an Integral returns itself.", + "builtins.bool.__floordiv__" => "Return self//value.", + "builtins.bool.__format__" => "Convert to a string according to format_spec.", + "builtins.bool.__ge__" => "Return self>=value.", + "builtins.bool.__getattribute__" => "Return getattr(self, name).", + "builtins.bool.__getstate__" => "Helper for pickle.", + "builtins.bool.__gt__" => "Return self>value.", + "builtins.bool.__hash__" => "Return hash(self).", + "builtins.bool.__index__" => "Return self converted to an integer, if self is suitable for use as an index into a list.", + "builtins.bool.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.bool.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.bool.__int__" => "int(self)", + "builtins.bool.__invert__" => "~self", + "builtins.bool.__le__" => "Return self<=value.", + "builtins.bool.__lshift__" => "Return self<<value.", + "builtins.bool.__lt__" => "Return self<value.", + "builtins.bool.__mod__" => "Return self%value.", + "builtins.bool.__mul__" => "Return self*value.", + "builtins.bool.__ne__" => "Return self!=value.", + "builtins.bool.__neg__" => "-self", + "builtins.bool.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.bool.__or__" => "Return self|value.", + "builtins.bool.__pos__" => "+self", + "builtins.bool.__pow__" => "Return pow(self, value, mod).", + "builtins.bool.__radd__" => "Return value+self.", + "builtins.bool.__rand__" => "Return value&self.", + "builtins.bool.__rdivmod__" => "Return divmod(value, self).", + "builtins.bool.__reduce__" => "Helper for pickle.", + "builtins.bool.__reduce_ex__" => "Helper for pickle.", + "builtins.bool.__repr__" => "Return repr(self).", + "builtins.bool.__rfloordiv__" => "Return value//self.", + "builtins.bool.__rlshift__" => "Return value<<self.", + "builtins.bool.__rmod__" => "Return value%self.", + "builtins.bool.__rmul__" => "Return value*self.", + "builtins.bool.__ror__" => "Return value|self.", + "builtins.bool.__round__" => "Rounding an Integral returns itself.\n\nRounding with an ndigits argument also returns an integer.", + "builtins.bool.__rpow__" => "Return pow(value, self, mod).", + "builtins.bool.__rrshift__" => "Return value>>self.", + "builtins.bool.__rshift__" => "Return self>>value.", + "builtins.bool.__rsub__" => "Return value-self.", + "builtins.bool.__rtruediv__" => "Return value/self.", + "builtins.bool.__rxor__" => "Return value^self.", + "builtins.bool.__setattr__" => "Implement setattr(self, name, value).", + "builtins.bool.__sizeof__" => "Returns size in memory, in bytes.", + "builtins.bool.__str__" => "Return str(self).", + "builtins.bool.__sub__" => "Return self-value.", + "builtins.bool.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.bool.__truediv__" => "Return self/value.", + "builtins.bool.__trunc__" => "Truncating an Integral returns itself.", + "builtins.bool.__xor__" => "Return self^value.", + "builtins.bool.as_integer_ratio" => "Return a pair of integers, whose ratio is equal to the original int.\n\nThe ratio is in lowest terms and has a positive denominator.\n\n>>> (10).as_integer_ratio()\n(10, 1)\n>>> (-10).as_integer_ratio()\n(-10, 1)\n>>> (0).as_integer_ratio()\n(0, 1)", + "builtins.bool.bit_count" => "Number of ones in the binary representation of the absolute value of self.\n\nAlso known as the population count.\n\n>>> bin(13)\n'0b1101'\n>>> (13).bit_count()\n3", + "builtins.bool.bit_length" => "Number of bits necessary to represent self in binary.\n\n>>> bin(37)\n'0b100101'\n>>> (37).bit_length()\n6", + "builtins.bool.conjugate" => "Returns self, the complex conjugate of any int.", + "builtins.bool.denominator" => "the denominator of a rational number in lowest terms", + "builtins.bool.from_bytes" => "Return the integer represented by the given array of bytes.\n\nbytes\n Holds the array of bytes to convert. The argument must either\n support the buffer protocol or be an iterable object producing bytes.\n Bytes and bytearray are examples of built-in objects that support the\n buffer protocol.\nbyteorder\n The byte order used to represent the integer. If byteorder is 'big',\n the most significant byte is at the beginning of the byte array. If\n byteorder is 'little', the most significant byte is at the end of the\n byte array. To request the native byte order of the host system, use\n sys.byteorder as the byte order value. Default is to use 'big'.\nsigned\n Indicates whether two's complement is used to represent the integer.", + "builtins.bool.imag" => "the imaginary part of a complex number", + "builtins.bool.is_integer" => "Returns True. Exists for duck type compatibility with float.is_integer.", + "builtins.bool.numerator" => "the numerator of a rational number in lowest terms", + "builtins.bool.real" => "the real part of a complex number", + "builtins.bool.to_bytes" => "Return an array of bytes representing an integer.\n\nlength\n Length of bytes object to use. An OverflowError is raised if the\n integer is not representable with the given number of bytes. Default\n is length 1.\nbyteorder\n The byte order used to represent the integer. If byteorder is 'big',\n the most significant byte is at the beginning of the byte array. If\n byteorder is 'little', the most significant byte is at the end of the\n byte array. To request the native byte order of the host system, use\n sys.byteorder as the byte order value. Default is to use 'big'.\nsigned\n Determines whether two's complement is used to represent the integer.\n If signed is False and a negative integer is given, an OverflowError\n is raised.", + "builtins.breakpoint" => "Call sys.breakpointhook(*args, **kws). sys.breakpointhook() must accept\nwhatever arguments are passed.\n\nBy default, this drops you into the pdb debugger.", + "builtins.bytearray" => "bytearray(iterable_of_ints) -> bytearray\nbytearray(string, encoding[, errors]) -> bytearray\nbytearray(bytes_or_buffer) -> mutable copy of bytes_or_buffer\nbytearray(int) -> bytes array of size given by the parameter initialized with null bytes\nbytearray() -> empty bytes array\n\nConstruct a mutable bytearray object from:\n - an iterable yielding integers in range(256)\n - a text string encoded using the specified encoding\n - a bytes or a buffer object\n - any object implementing the buffer API.\n - an integer", + "builtins.bytearray.__add__" => "Return self+value.", + "builtins.bytearray.__alloc__" => "B.__alloc__() -> int\n\nReturn the number of bytes actually allocated.", + "builtins.bytearray.__buffer__" => "Return a buffer object that exposes the underlying memory of the object.", + "builtins.bytearray.__contains__" => "Return bool(key in self).", + "builtins.bytearray.__delattr__" => "Implement delattr(self, name).", + "builtins.bytearray.__delitem__" => "Delete self[key].", + "builtins.bytearray.__eq__" => "Return self==value.", + "builtins.bytearray.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.bytearray.__ge__" => "Return self>=value.", + "builtins.bytearray.__getattribute__" => "Return getattr(self, name).", + "builtins.bytearray.__getitem__" => "Return self[key].", + "builtins.bytearray.__getstate__" => "Helper for pickle.", + "builtins.bytearray.__gt__" => "Return self>value.", + "builtins.bytearray.__iadd__" => "Implement self+=value.", + "builtins.bytearray.__imul__" => "Implement self*=value.", + "builtins.bytearray.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.bytearray.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.bytearray.__iter__" => "Implement iter(self).", + "builtins.bytearray.__le__" => "Return self<=value.", + "builtins.bytearray.__len__" => "Return len(self).", + "builtins.bytearray.__lt__" => "Return self<value.", + "builtins.bytearray.__mod__" => "Return self%value.", + "builtins.bytearray.__mul__" => "Return self*value.", + "builtins.bytearray.__ne__" => "Return self!=value.", + "builtins.bytearray.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.bytearray.__reduce__" => "Return state information for pickling.", + "builtins.bytearray.__reduce_ex__" => "Return state information for pickling.", + "builtins.bytearray.__release_buffer__" => "Release the buffer object that exposes the underlying memory of the object.", + "builtins.bytearray.__repr__" => "Return repr(self).", + "builtins.bytearray.__rmod__" => "Return value%self.", + "builtins.bytearray.__rmul__" => "Return value*self.", + "builtins.bytearray.__setattr__" => "Implement setattr(self, name, value).", + "builtins.bytearray.__setitem__" => "Set self[key] to value.", + "builtins.bytearray.__sizeof__" => "Returns the size of the bytearray object in memory, in bytes.", + "builtins.bytearray.__str__" => "Return str(self).", + "builtins.bytearray.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.bytearray.append" => "Append a single item to the end of the bytearray.\n\nitem\n The item to be appended.", + "builtins.bytearray.capitalize" => "B.capitalize() -> copy of B\n\nReturn a copy of B with only its first character capitalized (ASCII)\nand the rest lower-cased.", + "builtins.bytearray.center" => "Return a centered string of length width.\n\nPadding is done using the specified fill character.", + "builtins.bytearray.clear" => "Remove all items from the bytearray.", + "builtins.bytearray.copy" => "Return a copy of B.", + "builtins.bytearray.count" => "Return the number of non-overlapping occurrences of subsection 'sub' in bytes B[start:end].\n\nstart\n Optional start position. Default: start of the bytes.\nend\n Optional stop position. Default: end of the bytes.", + "builtins.bytearray.decode" => "Decode the bytearray using the codec registered for encoding.\n\nencoding\n The encoding with which to decode the bytearray.\nerrors\n The error handling scheme to use for the handling of decoding errors.\n The default is 'strict' meaning that decoding errors raise a\n UnicodeDecodeError. Other possible values are 'ignore' and 'replace'\n as well as any other name registered with codecs.register_error that\n can handle UnicodeDecodeErrors.", + "builtins.bytearray.endswith" => "Return True if the bytearray ends with the specified suffix, False otherwise.\n\nsuffix\n A bytes or a tuple of bytes to try.\nstart\n Optional start position. Default: start of the bytearray.\nend\n Optional stop position. Default: end of the bytearray.", + "builtins.bytearray.expandtabs" => "Return a copy where all tab characters are expanded using spaces.\n\nIf tabsize is not given, a tab size of 8 characters is assumed.", + "builtins.bytearray.extend" => "Append all the items from the iterator or sequence to the end of the bytearray.\n\niterable_of_ints\n The iterable of items to append.", + "builtins.bytearray.find" => "Return the lowest index in B where subsection 'sub' is found, such that 'sub' is contained within B[start:end].\n\n start\n Optional start position. Default: start of the bytes.\n end\n Optional stop position. Default: end of the bytes.\n\nReturn -1 on failure.", + "builtins.bytearray.fromhex" => "Create a bytearray object from a string of hexadecimal numbers.\n\nSpaces between two numbers are accepted.\nExample: bytearray.fromhex('B9 01EF') -> bytearray(b'\\\\xb9\\\\x01\\\\xef')", + "builtins.bytearray.hex" => "Create a string of hexadecimal numbers from a bytearray object.\n\n sep\n An optional single character or byte to separate hex bytes.\n bytes_per_sep\n How many bytes between separators. Positive values count from the\n right, negative values count from the left.\n\nExample:\n>>> value = bytearray([0xb9, 0x01, 0xef])\n>>> value.hex()\n'b901ef'\n>>> value.hex(':')\n'b9:01:ef'\n>>> value.hex(':', 2)\n'b9:01ef'\n>>> value.hex(':', -2)\n'b901:ef'", + "builtins.bytearray.index" => "Return the lowest index in B where subsection 'sub' is found, such that 'sub' is contained within B[start:end].\n\n start\n Optional start position. Default: start of the bytes.\n end\n Optional stop position. Default: end of the bytes.\n\nRaise ValueError if the subsection is not found.", + "builtins.bytearray.insert" => "Insert a single item into the bytearray before the given index.\n\nindex\n The index where the value is to be inserted.\nitem\n The item to be inserted.", + "builtins.bytearray.isalnum" => "B.isalnum() -> bool\n\nReturn True if all characters in B are alphanumeric\nand there is at least one character in B, False otherwise.", + "builtins.bytearray.isalpha" => "B.isalpha() -> bool\n\nReturn True if all characters in B are alphabetic\nand there is at least one character in B, False otherwise.", + "builtins.bytearray.isascii" => "B.isascii() -> bool\n\nReturn True if B is empty or all characters in B are ASCII,\nFalse otherwise.", + "builtins.bytearray.isdigit" => "B.isdigit() -> bool\n\nReturn True if all characters in B are digits\nand there is at least one character in B, False otherwise.", + "builtins.bytearray.islower" => "B.islower() -> bool\n\nReturn True if all cased characters in B are lowercase and there is\nat least one cased character in B, False otherwise.", + "builtins.bytearray.isspace" => "B.isspace() -> bool\n\nReturn True if all characters in B are whitespace\nand there is at least one character in B, False otherwise.", + "builtins.bytearray.istitle" => "B.istitle() -> bool\n\nReturn True if B is a titlecased string and there is at least one\ncharacter in B, i.e. uppercase characters may only follow uncased\ncharacters and lowercase characters only cased ones. Return False\notherwise.", + "builtins.bytearray.isupper" => "B.isupper() -> bool\n\nReturn True if all cased characters in B are uppercase and there is\nat least one cased character in B, False otherwise.", + "builtins.bytearray.join" => "Concatenate any number of bytes/bytearray objects.\n\nThe bytearray whose method is called is inserted in between each pair.\n\nThe result is returned as a new bytearray object.", + "builtins.bytearray.ljust" => "Return a left-justified string of length width.\n\nPadding is done using the specified fill character.", + "builtins.bytearray.lower" => "B.lower() -> copy of B\n\nReturn a copy of B with all ASCII characters converted to lowercase.", + "builtins.bytearray.lstrip" => "Strip leading bytes contained in the argument.\n\nIf the argument is omitted or None, strip leading ASCII whitespace.", + "builtins.bytearray.maketrans" => "Return a translation table usable for the bytes or bytearray translate method.\n\nThe returned table will be one where each byte in frm is mapped to the byte at\nthe same position in to.\n\nThe bytes objects frm and to must be of the same length.", + "builtins.bytearray.partition" => "Partition the bytearray into three parts using the given separator.\n\nThis will search for the separator sep in the bytearray. If the separator is\nfound, returns a 3-tuple containing the part before the separator, the\nseparator itself, and the part after it as new bytearray objects.\n\nIf the separator is not found, returns a 3-tuple containing the copy of the\noriginal bytearray object and two empty bytearray objects.", + "builtins.bytearray.pop" => "Remove and return a single item from B.\n\n index\n The index from where to remove the item.\n -1 (the default value) means remove the last item.\n\nIf no index argument is given, will pop the last item.", + "builtins.bytearray.remove" => "Remove the first occurrence of a value in the bytearray.\n\nvalue\n The value to remove.", + "builtins.bytearray.removeprefix" => "Return a bytearray with the given prefix string removed if present.\n\nIf the bytearray starts with the prefix string, return\nbytearray[len(prefix):]. Otherwise, return a copy of the original\nbytearray.", + "builtins.bytearray.removesuffix" => "Return a bytearray with the given suffix string removed if present.\n\nIf the bytearray ends with the suffix string and that suffix is not\nempty, return bytearray[:-len(suffix)]. Otherwise, return a copy of\nthe original bytearray.", + "builtins.bytearray.replace" => "Return a copy with all occurrences of substring old replaced by new.\n\n count\n Maximum number of occurrences to replace.\n -1 (the default value) means replace all occurrences.\n\nIf the optional argument count is given, only the first count occurrences are\nreplaced.", + "builtins.bytearray.reverse" => "Reverse the order of the values in B in place.", + "builtins.bytearray.rfind" => "Return the highest index in B where subsection 'sub' is found, such that 'sub' is contained within B[start:end].\n\n start\n Optional start position. Default: start of the bytes.\n end\n Optional stop position. Default: end of the bytes.\n\nReturn -1 on failure.", + "builtins.bytearray.rindex" => "Return the highest index in B where subsection 'sub' is found, such that 'sub' is contained within B[start:end].\n\n start\n Optional start position. Default: start of the bytes.\n end\n Optional stop position. Default: end of the bytes.\n\nRaise ValueError if the subsection is not found.", + "builtins.bytearray.rjust" => "Return a right-justified string of length width.\n\nPadding is done using the specified fill character.", + "builtins.bytearray.rpartition" => "Partition the bytearray into three parts using the given separator.\n\nThis will search for the separator sep in the bytearray, starting at the end.\nIf the separator is found, returns a 3-tuple containing the part before the\nseparator, the separator itself, and the part after it as new bytearray\nobjects.\n\nIf the separator is not found, returns a 3-tuple containing two empty bytearray\nobjects and the copy of the original bytearray object.", + "builtins.bytearray.rsplit" => "Return a list of the sections in the bytearray, using sep as the delimiter.\n\n sep\n The delimiter according which to split the bytearray.\n None (the default value) means split on ASCII whitespace characters\n (space, tab, return, newline, formfeed, vertical tab).\n maxsplit\n Maximum number of splits to do.\n -1 (the default value) means no limit.\n\nSplitting is done starting at the end of the bytearray and working to the front.", + "builtins.bytearray.rstrip" => "Strip trailing bytes contained in the argument.\n\nIf the argument is omitted or None, strip trailing ASCII whitespace.", + "builtins.bytearray.split" => "Return a list of the sections in the bytearray, using sep as the delimiter.\n\nsep\n The delimiter according which to split the bytearray.\n None (the default value) means split on ASCII whitespace characters\n (space, tab, return, newline, formfeed, vertical tab).\nmaxsplit\n Maximum number of splits to do.\n -1 (the default value) means no limit.", + "builtins.bytearray.splitlines" => "Return a list of the lines in the bytearray, breaking at line boundaries.\n\nLine breaks are not included in the resulting list unless keepends is given and\ntrue.", + "builtins.bytearray.startswith" => "Return True if the bytearray starts with the specified prefix, False otherwise.\n\nprefix\n A bytes or a tuple of bytes to try.\nstart\n Optional start position. Default: start of the bytearray.\nend\n Optional stop position. Default: end of the bytearray.", + "builtins.bytearray.strip" => "Strip leading and trailing bytes contained in the argument.\n\nIf the argument is omitted or None, strip leading and trailing ASCII whitespace.", + "builtins.bytearray.swapcase" => "B.swapcase() -> copy of B\n\nReturn a copy of B with uppercase ASCII characters converted\nto lowercase ASCII and vice versa.", + "builtins.bytearray.title" => "B.title() -> copy of B\n\nReturn a titlecased version of B, i.e. ASCII words start with uppercase\ncharacters, all remaining cased characters have lowercase.", + "builtins.bytearray.translate" => "Return a copy with each character mapped by the given translation table.\n\n table\n Translation table, which must be a bytes object of length 256.\n\nAll characters occurring in the optional argument delete are removed.\nThe remaining characters are mapped through the given translation table.", + "builtins.bytearray.upper" => "B.upper() -> copy of B\n\nReturn a copy of B with all ASCII characters converted to uppercase.", + "builtins.bytearray.zfill" => "Pad a numeric string with zeros on the left, to fill a field of the given width.\n\nThe original string is never truncated.", + "builtins.bytearray_iterator.__delattr__" => "Implement delattr(self, name).", + "builtins.bytearray_iterator.__eq__" => "Return self==value.", + "builtins.bytearray_iterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.bytearray_iterator.__ge__" => "Return self>=value.", + "builtins.bytearray_iterator.__getattribute__" => "Return getattr(self, name).", + "builtins.bytearray_iterator.__getstate__" => "Helper for pickle.", + "builtins.bytearray_iterator.__gt__" => "Return self>value.", + "builtins.bytearray_iterator.__hash__" => "Return hash(self).", + "builtins.bytearray_iterator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.bytearray_iterator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.bytearray_iterator.__iter__" => "Implement iter(self).", + "builtins.bytearray_iterator.__le__" => "Return self<=value.", + "builtins.bytearray_iterator.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "builtins.bytearray_iterator.__lt__" => "Return self<value.", + "builtins.bytearray_iterator.__ne__" => "Return self!=value.", + "builtins.bytearray_iterator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.bytearray_iterator.__next__" => "Implement next(self).", + "builtins.bytearray_iterator.__reduce__" => "Return state information for pickling.", + "builtins.bytearray_iterator.__reduce_ex__" => "Helper for pickle.", + "builtins.bytearray_iterator.__repr__" => "Return repr(self).", + "builtins.bytearray_iterator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.bytearray_iterator.__setstate__" => "Set state information for unpickling.", + "builtins.bytearray_iterator.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.bytearray_iterator.__str__" => "Return str(self).", + "builtins.bytearray_iterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.bytes" => "bytes(iterable_of_ints) -> bytes\nbytes(string, encoding[, errors]) -> bytes\nbytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\nbytes(int) -> bytes object of size given by the parameter initialized with null bytes\nbytes() -> empty bytes object\n\nConstruct an immutable array of bytes from:\n - an iterable yielding integers in range(256)\n - a text string encoded using the specified encoding\n - any object implementing the buffer API.\n - an integer", + "builtins.bytes.__add__" => "Return self+value.", + "builtins.bytes.__buffer__" => "Return a buffer object that exposes the underlying memory of the object.", + "builtins.bytes.__bytes__" => "Convert this value to exact type bytes.", + "builtins.bytes.__contains__" => "Return bool(key in self).", + "builtins.bytes.__delattr__" => "Implement delattr(self, name).", + "builtins.bytes.__eq__" => "Return self==value.", + "builtins.bytes.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.bytes.__ge__" => "Return self>=value.", + "builtins.bytes.__getattribute__" => "Return getattr(self, name).", + "builtins.bytes.__getitem__" => "Return self[key].", + "builtins.bytes.__getstate__" => "Helper for pickle.", + "builtins.bytes.__gt__" => "Return self>value.", + "builtins.bytes.__hash__" => "Return hash(self).", + "builtins.bytes.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.bytes.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.bytes.__iter__" => "Implement iter(self).", + "builtins.bytes.__le__" => "Return self<=value.", + "builtins.bytes.__len__" => "Return len(self).", + "builtins.bytes.__lt__" => "Return self<value.", + "builtins.bytes.__mod__" => "Return self%value.", + "builtins.bytes.__mul__" => "Return self*value.", + "builtins.bytes.__ne__" => "Return self!=value.", + "builtins.bytes.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.bytes.__reduce__" => "Helper for pickle.", + "builtins.bytes.__reduce_ex__" => "Helper for pickle.", + "builtins.bytes.__repr__" => "Return repr(self).", + "builtins.bytes.__rmod__" => "Return value%self.", + "builtins.bytes.__rmul__" => "Return value*self.", + "builtins.bytes.__setattr__" => "Implement setattr(self, name, value).", + "builtins.bytes.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.bytes.__str__" => "Return str(self).", + "builtins.bytes.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.bytes.capitalize" => "B.capitalize() -> copy of B\n\nReturn a copy of B with only its first character capitalized (ASCII)\nand the rest lower-cased.", + "builtins.bytes.center" => "Return a centered string of length width.\n\nPadding is done using the specified fill character.", + "builtins.bytes.count" => "Return the number of non-overlapping occurrences of subsection 'sub' in bytes B[start:end].\n\nstart\n Optional start position. Default: start of the bytes.\nend\n Optional stop position. Default: end of the bytes.", + "builtins.bytes.decode" => "Decode the bytes using the codec registered for encoding.\n\nencoding\n The encoding with which to decode the bytes.\nerrors\n The error handling scheme to use for the handling of decoding errors.\n The default is 'strict' meaning that decoding errors raise a\n UnicodeDecodeError. Other possible values are 'ignore' and 'replace'\n as well as any other name registered with codecs.register_error that\n can handle UnicodeDecodeErrors.", + "builtins.bytes.endswith" => "Return True if the bytes ends with the specified suffix, False otherwise.\n\nsuffix\n A bytes or a tuple of bytes to try.\nstart\n Optional start position. Default: start of the bytes.\nend\n Optional stop position. Default: end of the bytes.", + "builtins.bytes.expandtabs" => "Return a copy where all tab characters are expanded using spaces.\n\nIf tabsize is not given, a tab size of 8 characters is assumed.", + "builtins.bytes.find" => "Return the lowest index in B where subsection 'sub' is found, such that 'sub' is contained within B[start,end].\n\n start\n Optional start position. Default: start of the bytes.\n end\n Optional stop position. Default: end of the bytes.\n\nReturn -1 on failure.", + "builtins.bytes.fromhex" => "Create a bytes object from a string of hexadecimal numbers.\n\nSpaces between two numbers are accepted.\nExample: bytes.fromhex('B9 01EF') -> b'\\\\xb9\\\\x01\\\\xef'.", + "builtins.bytes.hex" => "Create a string of hexadecimal numbers from a bytes object.\n\n sep\n An optional single character or byte to separate hex bytes.\n bytes_per_sep\n How many bytes between separators. Positive values count from the\n right, negative values count from the left.\n\nExample:\n>>> value = b'\\xb9\\x01\\xef'\n>>> value.hex()\n'b901ef'\n>>> value.hex(':')\n'b9:01:ef'\n>>> value.hex(':', 2)\n'b9:01ef'\n>>> value.hex(':', -2)\n'b901:ef'", + "builtins.bytes.index" => "Return the lowest index in B where subsection 'sub' is found, such that 'sub' is contained within B[start,end].\n\n start\n Optional start position. Default: start of the bytes.\n end\n Optional stop position. Default: end of the bytes.\n\nRaise ValueError if the subsection is not found.", + "builtins.bytes.isalnum" => "B.isalnum() -> bool\n\nReturn True if all characters in B are alphanumeric\nand there is at least one character in B, False otherwise.", + "builtins.bytes.isalpha" => "B.isalpha() -> bool\n\nReturn True if all characters in B are alphabetic\nand there is at least one character in B, False otherwise.", + "builtins.bytes.isascii" => "B.isascii() -> bool\n\nReturn True if B is empty or all characters in B are ASCII,\nFalse otherwise.", + "builtins.bytes.isdigit" => "B.isdigit() -> bool\n\nReturn True if all characters in B are digits\nand there is at least one character in B, False otherwise.", + "builtins.bytes.islower" => "B.islower() -> bool\n\nReturn True if all cased characters in B are lowercase and there is\nat least one cased character in B, False otherwise.", + "builtins.bytes.isspace" => "B.isspace() -> bool\n\nReturn True if all characters in B are whitespace\nand there is at least one character in B, False otherwise.", + "builtins.bytes.istitle" => "B.istitle() -> bool\n\nReturn True if B is a titlecased string and there is at least one\ncharacter in B, i.e. uppercase characters may only follow uncased\ncharacters and lowercase characters only cased ones. Return False\notherwise.", + "builtins.bytes.isupper" => "B.isupper() -> bool\n\nReturn True if all cased characters in B are uppercase and there is\nat least one cased character in B, False otherwise.", + "builtins.bytes.join" => "Concatenate any number of bytes objects.\n\nThe bytes whose method is called is inserted in between each pair.\n\nThe result is returned as a new bytes object.\n\nExample: b'.'.join([b'ab', b'pq', b'rs']) -> b'ab.pq.rs'.", + "builtins.bytes.ljust" => "Return a left-justified string of length width.\n\nPadding is done using the specified fill character.", + "builtins.bytes.lower" => "B.lower() -> copy of B\n\nReturn a copy of B with all ASCII characters converted to lowercase.", + "builtins.bytes.lstrip" => "Strip leading bytes contained in the argument.\n\nIf the argument is omitted or None, strip leading ASCII whitespace.", + "builtins.bytes.maketrans" => "Return a translation table usable for the bytes or bytearray translate method.\n\nThe returned table will be one where each byte in frm is mapped to the byte at\nthe same position in to.\n\nThe bytes objects frm and to must be of the same length.", + "builtins.bytes.partition" => "Partition the bytes into three parts using the given separator.\n\nThis will search for the separator sep in the bytes. If the separator is found,\nreturns a 3-tuple containing the part before the separator, the separator\nitself, and the part after it.\n\nIf the separator is not found, returns a 3-tuple containing the original bytes\nobject and two empty bytes objects.", + "builtins.bytes.removeprefix" => "Return a bytes object with the given prefix string removed if present.\n\nIf the bytes starts with the prefix string, return bytes[len(prefix):].\nOtherwise, return a copy of the original bytes.", + "builtins.bytes.removesuffix" => "Return a bytes object with the given suffix string removed if present.\n\nIf the bytes ends with the suffix string and that suffix is not empty,\nreturn bytes[:-len(prefix)]. Otherwise, return a copy of the original\nbytes.", + "builtins.bytes.replace" => "Return a copy with all occurrences of substring old replaced by new.\n\n count\n Maximum number of occurrences to replace.\n -1 (the default value) means replace all occurrences.\n\nIf the optional argument count is given, only the first count occurrences are\nreplaced.", + "builtins.bytes.rfind" => "Return the highest index in B where subsection 'sub' is found, such that 'sub' is contained within B[start,end].\n\n start\n Optional start position. Default: start of the bytes.\n end\n Optional stop position. Default: end of the bytes.\n\nReturn -1 on failure.", + "builtins.bytes.rindex" => "Return the highest index in B where subsection 'sub' is found, such that 'sub' is contained within B[start,end].\n\n start\n Optional start position. Default: start of the bytes.\n end\n Optional stop position. Default: end of the bytes.\n\nRaise ValueError if the subsection is not found.", + "builtins.bytes.rjust" => "Return a right-justified string of length width.\n\nPadding is done using the specified fill character.", + "builtins.bytes.rpartition" => "Partition the bytes into three parts using the given separator.\n\nThis will search for the separator sep in the bytes, starting at the end. If\nthe separator is found, returns a 3-tuple containing the part before the\nseparator, the separator itself, and the part after it.\n\nIf the separator is not found, returns a 3-tuple containing two empty bytes\nobjects and the original bytes object.", + "builtins.bytes.rsplit" => "Return a list of the sections in the bytes, using sep as the delimiter.\n\n sep\n The delimiter according which to split the bytes.\n None (the default value) means split on ASCII whitespace characters\n (space, tab, return, newline, formfeed, vertical tab).\n maxsplit\n Maximum number of splits to do.\n -1 (the default value) means no limit.\n\nSplitting is done starting at the end of the bytes and working to the front.", + "builtins.bytes.rstrip" => "Strip trailing bytes contained in the argument.\n\nIf the argument is omitted or None, strip trailing ASCII whitespace.", + "builtins.bytes.split" => "Return a list of the sections in the bytes, using sep as the delimiter.\n\nsep\n The delimiter according which to split the bytes.\n None (the default value) means split on ASCII whitespace characters\n (space, tab, return, newline, formfeed, vertical tab).\nmaxsplit\n Maximum number of splits to do.\n -1 (the default value) means no limit.", + "builtins.bytes.splitlines" => "Return a list of the lines in the bytes, breaking at line boundaries.\n\nLine breaks are not included in the resulting list unless keepends is given and\ntrue.", + "builtins.bytes.startswith" => "Return True if the bytes starts with the specified prefix, False otherwise.\n\nprefix\n A bytes or a tuple of bytes to try.\nstart\n Optional start position. Default: start of the bytes.\nend\n Optional stop position. Default: end of the bytes.", + "builtins.bytes.strip" => "Strip leading and trailing bytes contained in the argument.\n\nIf the argument is omitted or None, strip leading and trailing ASCII whitespace.", + "builtins.bytes.swapcase" => "B.swapcase() -> copy of B\n\nReturn a copy of B with uppercase ASCII characters converted\nto lowercase ASCII and vice versa.", + "builtins.bytes.title" => "B.title() -> copy of B\n\nReturn a titlecased version of B, i.e. ASCII words start with uppercase\ncharacters, all remaining cased characters have lowercase.", + "builtins.bytes.translate" => "Return a copy with each character mapped by the given translation table.\n\n table\n Translation table, which must be a bytes object of length 256.\n\nAll characters occurring in the optional argument delete are removed.\nThe remaining characters are mapped through the given translation table.", + "builtins.bytes.upper" => "B.upper() -> copy of B\n\nReturn a copy of B with all ASCII characters converted to uppercase.", + "builtins.bytes.zfill" => "Pad a numeric string with zeros on the left, to fill a field of the given width.\n\nThe original string is never truncated.", + "builtins.bytes_iterator.__delattr__" => "Implement delattr(self, name).", + "builtins.bytes_iterator.__eq__" => "Return self==value.", + "builtins.bytes_iterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.bytes_iterator.__ge__" => "Return self>=value.", + "builtins.bytes_iterator.__getattribute__" => "Return getattr(self, name).", + "builtins.bytes_iterator.__getstate__" => "Helper for pickle.", + "builtins.bytes_iterator.__gt__" => "Return self>value.", + "builtins.bytes_iterator.__hash__" => "Return hash(self).", + "builtins.bytes_iterator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.bytes_iterator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.bytes_iterator.__iter__" => "Implement iter(self).", + "builtins.bytes_iterator.__le__" => "Return self<=value.", + "builtins.bytes_iterator.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "builtins.bytes_iterator.__lt__" => "Return self<value.", + "builtins.bytes_iterator.__ne__" => "Return self!=value.", + "builtins.bytes_iterator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.bytes_iterator.__next__" => "Implement next(self).", + "builtins.bytes_iterator.__reduce__" => "Return state information for pickling.", + "builtins.bytes_iterator.__reduce_ex__" => "Helper for pickle.", + "builtins.bytes_iterator.__repr__" => "Return repr(self).", + "builtins.bytes_iterator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.bytes_iterator.__setstate__" => "Set state information for unpickling.", + "builtins.bytes_iterator.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.bytes_iterator.__str__" => "Return str(self).", + "builtins.bytes_iterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.callable" => "Return whether the object is callable (i.e., some kind of function).\n\nNote that classes are callable, as are instances of classes with a\n__call__() method.", + "builtins.chr" => "Return a Unicode string of one character with ordinal i; 0 <= i <= 0x10ffff.", + "builtins.classmethod" => "Convert a function to be a class method.\n\nA class method receives the class as implicit first argument,\njust like an instance method receives the instance.\nTo declare a class method, use this idiom:\n\n class C:\n @classmethod\n def f(cls, arg1, arg2, argN):\n ...\n\nIt can be called either on the class (e.g. C.f()) or on an instance\n(e.g. C().f()). The instance is ignored except for its class.\nIf a class method is called for a derived class, the derived class\nobject is passed as the implied first argument.\n\nClass methods are different than C++ or Java static methods.\nIf you want those, see the staticmethod builtin.", + "builtins.classmethod.__delattr__" => "Implement delattr(self, name).", + "builtins.classmethod.__eq__" => "Return self==value.", + "builtins.classmethod.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.classmethod.__ge__" => "Return self>=value.", + "builtins.classmethod.__get__" => "Return an attribute of instance, which is of type owner.", + "builtins.classmethod.__getattribute__" => "Return getattr(self, name).", + "builtins.classmethod.__getstate__" => "Helper for pickle.", + "builtins.classmethod.__gt__" => "Return self>value.", + "builtins.classmethod.__hash__" => "Return hash(self).", + "builtins.classmethod.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.classmethod.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.classmethod.__le__" => "Return self<=value.", + "builtins.classmethod.__lt__" => "Return self<value.", + "builtins.classmethod.__ne__" => "Return self!=value.", + "builtins.classmethod.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.classmethod.__reduce__" => "Helper for pickle.", + "builtins.classmethod.__reduce_ex__" => "Helper for pickle.", + "builtins.classmethod.__repr__" => "Return repr(self).", + "builtins.classmethod.__setattr__" => "Implement setattr(self, name, value).", + "builtins.classmethod.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.classmethod.__str__" => "Return str(self).", + "builtins.classmethod.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.compile" => "Compile source into a code object that can be executed by exec() or eval().\n\nThe source code may represent a Python module, statement or expression.\nThe filename will be used for run-time error messages.\nThe mode must be 'exec' to compile a module, 'single' to compile a\nsingle (interactive) statement, or 'eval' to compile an expression.\nThe flags argument, if present, controls which future statements influence\nthe compilation of the code.\nThe dont_inherit argument, if true, stops the compilation inheriting\nthe effects of any future statements in effect in the code calling\ncompile; if absent or false these statements do influence the compilation,\nin addition to any features explicitly specified.", + "builtins.complex" => "Create a complex number from a string or numbers.\n\nIf a string is given, parse it as a complex number.\nIf a single number is given, convert it to a complex number.\nIf the 'real' or 'imag' arguments are given, create a complex number\nwith the specified real and imaginary components.", + "builtins.complex.__abs__" => "abs(self)", + "builtins.complex.__add__" => "Return self+value.", + "builtins.complex.__bool__" => "True if self else False", + "builtins.complex.__complex__" => "Convert this value to exact type complex.", + "builtins.complex.__delattr__" => "Implement delattr(self, name).", + "builtins.complex.__eq__" => "Return self==value.", + "builtins.complex.__format__" => "Convert to a string according to format_spec.", + "builtins.complex.__ge__" => "Return self>=value.", + "builtins.complex.__getattribute__" => "Return getattr(self, name).", + "builtins.complex.__getstate__" => "Helper for pickle.", + "builtins.complex.__gt__" => "Return self>value.", + "builtins.complex.__hash__" => "Return hash(self).", + "builtins.complex.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.complex.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.complex.__le__" => "Return self<=value.", + "builtins.complex.__lt__" => "Return self<value.", + "builtins.complex.__mul__" => "Return self*value.", + "builtins.complex.__ne__" => "Return self!=value.", + "builtins.complex.__neg__" => "-self", + "builtins.complex.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.complex.__pos__" => "+self", + "builtins.complex.__pow__" => "Return pow(self, value, mod).", + "builtins.complex.__radd__" => "Return value+self.", + "builtins.complex.__reduce__" => "Helper for pickle.", + "builtins.complex.__reduce_ex__" => "Helper for pickle.", + "builtins.complex.__repr__" => "Return repr(self).", + "builtins.complex.__rmul__" => "Return value*self.", + "builtins.complex.__rpow__" => "Return pow(value, self, mod).", + "builtins.complex.__rsub__" => "Return value-self.", + "builtins.complex.__rtruediv__" => "Return value/self.", + "builtins.complex.__setattr__" => "Implement setattr(self, name, value).", + "builtins.complex.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.complex.__str__" => "Return str(self).", + "builtins.complex.__sub__" => "Return self-value.", + "builtins.complex.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.complex.__truediv__" => "Return self/value.", + "builtins.complex.conjugate" => "Return the complex conjugate of its argument. (3-4j).conjugate() == 3+4j.", + "builtins.complex.imag" => "the imaginary part of a complex number", + "builtins.complex.real" => "the real part of a complex number", + "builtins.delattr" => "Deletes the named attribute from the given object.\n\ndelattr(x, 'y') is equivalent to ``del x.y``", + "builtins.dict" => "dict() -> new empty dictionary\ndict(mapping) -> new dictionary initialized from a mapping object's\n (key, value) pairs\ndict(iterable) -> new dictionary initialized as if via:\n d = {}\n for k, v in iterable:\n d[k] = v\ndict(**kwargs) -> new dictionary initialized with the name=value pairs\n in the keyword argument list. For example: dict(one=1, two=2)", + "builtins.dict.__class_getitem__" => "See PEP 585", + "builtins.dict.__contains__" => "True if the dictionary has the specified key, else False.", + "builtins.dict.__delattr__" => "Implement delattr(self, name).", + "builtins.dict.__delitem__" => "Delete self[key].", + "builtins.dict.__eq__" => "Return self==value.", + "builtins.dict.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.dict.__ge__" => "Return self>=value.", + "builtins.dict.__getattribute__" => "Return getattr(self, name).", + "builtins.dict.__getitem__" => "Return self[key].", + "builtins.dict.__getstate__" => "Helper for pickle.", + "builtins.dict.__gt__" => "Return self>value.", + "builtins.dict.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.dict.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.dict.__ior__" => "Return self|=value.", + "builtins.dict.__iter__" => "Implement iter(self).", + "builtins.dict.__le__" => "Return self<=value.", + "builtins.dict.__len__" => "Return len(self).", + "builtins.dict.__lt__" => "Return self<value.", + "builtins.dict.__ne__" => "Return self!=value.", + "builtins.dict.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.dict.__or__" => "Return self|value.", + "builtins.dict.__reduce__" => "Helper for pickle.", + "builtins.dict.__reduce_ex__" => "Helper for pickle.", + "builtins.dict.__repr__" => "Return repr(self).", + "builtins.dict.__reversed__" => "Return a reverse iterator over the dict keys.", + "builtins.dict.__ror__" => "Return value|self.", + "builtins.dict.__setattr__" => "Implement setattr(self, name, value).", + "builtins.dict.__setitem__" => "Set self[key] to value.", + "builtins.dict.__sizeof__" => "Return the size of the dict in memory, in bytes.", + "builtins.dict.__str__" => "Return str(self).", + "builtins.dict.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.dict.clear" => "Remove all items from the dict.", + "builtins.dict.copy" => "Return a shallow copy of the dict.", + "builtins.dict.fromkeys" => "Create a new dictionary with keys from iterable and values set to value.", + "builtins.dict.get" => "Return the value for key if key is in the dictionary, else default.", + "builtins.dict.items" => "Return a set-like object providing a view on the dict's items.", + "builtins.dict.keys" => "Return a set-like object providing a view on the dict's keys.", + "builtins.dict.pop" => "D.pop(k[,d]) -> v, remove specified key and return the corresponding value.\n\nIf the key is not found, return the default if given; otherwise,\nraise a KeyError.", + "builtins.dict.popitem" => "Remove and return a (key, value) pair as a 2-tuple.\n\nPairs are returned in LIFO (last-in, first-out) order.\nRaises KeyError if the dict is empty.", + "builtins.dict.setdefault" => "Insert key with a value of default if key is not in the dictionary.\n\nReturn the value for key if key is in the dictionary, else default.", + "builtins.dict.update" => "D.update([E, ]**F) -> None. Update D from mapping/iterable E and F.\nIf E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k]\nIf E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v\nIn either case, this is followed by: for k in F: D[k] = F[k]", + "builtins.dict.values" => "Return an object providing a view on the dict's values.", + "builtins.dict_itemiterator.__delattr__" => "Implement delattr(self, name).", + "builtins.dict_itemiterator.__eq__" => "Return self==value.", + "builtins.dict_itemiterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.dict_itemiterator.__ge__" => "Return self>=value.", + "builtins.dict_itemiterator.__getattribute__" => "Return getattr(self, name).", + "builtins.dict_itemiterator.__getstate__" => "Helper for pickle.", + "builtins.dict_itemiterator.__gt__" => "Return self>value.", + "builtins.dict_itemiterator.__hash__" => "Return hash(self).", + "builtins.dict_itemiterator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.dict_itemiterator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.dict_itemiterator.__iter__" => "Implement iter(self).", + "builtins.dict_itemiterator.__le__" => "Return self<=value.", + "builtins.dict_itemiterator.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "builtins.dict_itemiterator.__lt__" => "Return self<value.", + "builtins.dict_itemiterator.__ne__" => "Return self!=value.", + "builtins.dict_itemiterator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.dict_itemiterator.__next__" => "Implement next(self).", + "builtins.dict_itemiterator.__reduce__" => "Return state information for pickling.", + "builtins.dict_itemiterator.__reduce_ex__" => "Helper for pickle.", + "builtins.dict_itemiterator.__repr__" => "Return repr(self).", + "builtins.dict_itemiterator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.dict_itemiterator.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.dict_itemiterator.__str__" => "Return str(self).", + "builtins.dict_itemiterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.dict_items.__and__" => "Return self&value.", + "builtins.dict_items.__contains__" => "Return bool(key in self).", + "builtins.dict_items.__delattr__" => "Implement delattr(self, name).", + "builtins.dict_items.__eq__" => "Return self==value.", + "builtins.dict_items.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.dict_items.__ge__" => "Return self>=value.", + "builtins.dict_items.__getattribute__" => "Return getattr(self, name).", + "builtins.dict_items.__getstate__" => "Helper for pickle.", + "builtins.dict_items.__gt__" => "Return self>value.", + "builtins.dict_items.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.dict_items.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.dict_items.__iter__" => "Implement iter(self).", + "builtins.dict_items.__le__" => "Return self<=value.", + "builtins.dict_items.__len__" => "Return len(self).", + "builtins.dict_items.__lt__" => "Return self<value.", + "builtins.dict_items.__ne__" => "Return self!=value.", + "builtins.dict_items.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.dict_items.__or__" => "Return self|value.", + "builtins.dict_items.__rand__" => "Return value&self.", + "builtins.dict_items.__reduce__" => "Helper for pickle.", + "builtins.dict_items.__reduce_ex__" => "Helper for pickle.", + "builtins.dict_items.__repr__" => "Return repr(self).", + "builtins.dict_items.__reversed__" => "Return a reverse iterator over the dict items.", + "builtins.dict_items.__ror__" => "Return value|self.", + "builtins.dict_items.__rsub__" => "Return value-self.", + "builtins.dict_items.__rxor__" => "Return value^self.", + "builtins.dict_items.__setattr__" => "Implement setattr(self, name, value).", + "builtins.dict_items.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.dict_items.__str__" => "Return str(self).", + "builtins.dict_items.__sub__" => "Return self-value.", + "builtins.dict_items.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.dict_items.__xor__" => "Return self^value.", + "builtins.dict_items.isdisjoint" => "Return True if the view and the given iterable have a null intersection.", + "builtins.dict_items.mapping" => "dictionary that this view refers to", + "builtins.dict_keyiterator.__delattr__" => "Implement delattr(self, name).", + "builtins.dict_keyiterator.__eq__" => "Return self==value.", + "builtins.dict_keyiterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.dict_keyiterator.__ge__" => "Return self>=value.", + "builtins.dict_keyiterator.__getattribute__" => "Return getattr(self, name).", + "builtins.dict_keyiterator.__getstate__" => "Helper for pickle.", + "builtins.dict_keyiterator.__gt__" => "Return self>value.", + "builtins.dict_keyiterator.__hash__" => "Return hash(self).", + "builtins.dict_keyiterator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.dict_keyiterator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.dict_keyiterator.__iter__" => "Implement iter(self).", + "builtins.dict_keyiterator.__le__" => "Return self<=value.", + "builtins.dict_keyiterator.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "builtins.dict_keyiterator.__lt__" => "Return self<value.", + "builtins.dict_keyiterator.__ne__" => "Return self!=value.", + "builtins.dict_keyiterator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.dict_keyiterator.__next__" => "Implement next(self).", + "builtins.dict_keyiterator.__reduce__" => "Return state information for pickling.", + "builtins.dict_keyiterator.__reduce_ex__" => "Helper for pickle.", + "builtins.dict_keyiterator.__repr__" => "Return repr(self).", + "builtins.dict_keyiterator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.dict_keyiterator.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.dict_keyiterator.__str__" => "Return str(self).", + "builtins.dict_keyiterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.dict_valueiterator.__delattr__" => "Implement delattr(self, name).", + "builtins.dict_valueiterator.__eq__" => "Return self==value.", + "builtins.dict_valueiterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.dict_valueiterator.__ge__" => "Return self>=value.", + "builtins.dict_valueiterator.__getattribute__" => "Return getattr(self, name).", + "builtins.dict_valueiterator.__getstate__" => "Helper for pickle.", + "builtins.dict_valueiterator.__gt__" => "Return self>value.", + "builtins.dict_valueiterator.__hash__" => "Return hash(self).", + "builtins.dict_valueiterator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.dict_valueiterator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.dict_valueiterator.__iter__" => "Implement iter(self).", + "builtins.dict_valueiterator.__le__" => "Return self<=value.", + "builtins.dict_valueiterator.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "builtins.dict_valueiterator.__lt__" => "Return self<value.", + "builtins.dict_valueiterator.__ne__" => "Return self!=value.", + "builtins.dict_valueiterator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.dict_valueiterator.__next__" => "Implement next(self).", + "builtins.dict_valueiterator.__reduce__" => "Return state information for pickling.", + "builtins.dict_valueiterator.__reduce_ex__" => "Helper for pickle.", + "builtins.dict_valueiterator.__repr__" => "Return repr(self).", + "builtins.dict_valueiterator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.dict_valueiterator.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.dict_valueiterator.__str__" => "Return str(self).", + "builtins.dict_valueiterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.dict_values.__delattr__" => "Implement delattr(self, name).", + "builtins.dict_values.__eq__" => "Return self==value.", + "builtins.dict_values.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.dict_values.__ge__" => "Return self>=value.", + "builtins.dict_values.__getattribute__" => "Return getattr(self, name).", + "builtins.dict_values.__getstate__" => "Helper for pickle.", + "builtins.dict_values.__gt__" => "Return self>value.", + "builtins.dict_values.__hash__" => "Return hash(self).", + "builtins.dict_values.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.dict_values.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.dict_values.__iter__" => "Implement iter(self).", + "builtins.dict_values.__le__" => "Return self<=value.", + "builtins.dict_values.__len__" => "Return len(self).", + "builtins.dict_values.__lt__" => "Return self<value.", + "builtins.dict_values.__ne__" => "Return self!=value.", + "builtins.dict_values.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.dict_values.__reduce__" => "Helper for pickle.", + "builtins.dict_values.__reduce_ex__" => "Helper for pickle.", + "builtins.dict_values.__repr__" => "Return repr(self).", + "builtins.dict_values.__reversed__" => "Return a reverse iterator over the dict values.", + "builtins.dict_values.__setattr__" => "Implement setattr(self, name, value).", + "builtins.dict_values.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.dict_values.__str__" => "Return str(self).", + "builtins.dict_values.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.dict_values.mapping" => "dictionary that this view refers to", + "builtins.dir" => "dir([object]) -> list of strings\n\nIf called without an argument, return the names in the current scope.\nElse, return an alphabetized list of names comprising (some of) the attributes\nof the given object, and of attributes reachable from it.\nIf the object supplies a method named __dir__, it will be used; otherwise\nthe default dir() logic is used and returns:\n for a module object: the module's attributes.\n for a class object: its attributes, and recursively the attributes\n of its bases.\n for any other object: its attributes, its class's attributes, and\n recursively the attributes of its class's base classes.", + "builtins.divmod" => "Return the tuple (x//y, x%y). Invariant: div*y + mod == x.", + "builtins.enumerate" => "Return an enumerate object.\n\n iterable\n an object supporting iteration\n\nThe enumerate object yields pairs containing a count (from start, which\ndefaults to zero) and a value yielded by the iterable argument.\n\nenumerate is useful for obtaining an indexed list:\n (0, seq[0]), (1, seq[1]), (2, seq[2]), ...", + "builtins.enumerate.__class_getitem__" => "See PEP 585", + "builtins.enumerate.__delattr__" => "Implement delattr(self, name).", + "builtins.enumerate.__eq__" => "Return self==value.", + "builtins.enumerate.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.enumerate.__ge__" => "Return self>=value.", + "builtins.enumerate.__getattribute__" => "Return getattr(self, name).", + "builtins.enumerate.__getstate__" => "Helper for pickle.", + "builtins.enumerate.__gt__" => "Return self>value.", + "builtins.enumerate.__hash__" => "Return hash(self).", + "builtins.enumerate.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.enumerate.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.enumerate.__iter__" => "Implement iter(self).", + "builtins.enumerate.__le__" => "Return self<=value.", + "builtins.enumerate.__lt__" => "Return self<value.", + "builtins.enumerate.__ne__" => "Return self!=value.", + "builtins.enumerate.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.enumerate.__next__" => "Implement next(self).", + "builtins.enumerate.__reduce__" => "Return state information for pickling.", + "builtins.enumerate.__reduce_ex__" => "Helper for pickle.", + "builtins.enumerate.__repr__" => "Return repr(self).", + "builtins.enumerate.__setattr__" => "Implement setattr(self, name, value).", + "builtins.enumerate.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.enumerate.__str__" => "Return str(self).", + "builtins.enumerate.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.eval" => "Evaluate the given source in the context of globals and locals.\n\nThe source may be a string representing a Python expression\nor a code object as returned by compile().\nThe globals must be a dictionary and locals can be any mapping,\ndefaulting to the current globals and locals.\nIf only globals is given, locals defaults to it.", + "builtins.exec" => "Execute the given source in the context of globals and locals.\n\nThe source may be a string representing one or more Python statements\nor a code object as returned by compile().\nThe globals must be a dictionary and locals can be any mapping,\ndefaulting to the current globals and locals.\nIf only globals is given, locals defaults to it.\nThe closure must be a tuple of cellvars, and can only be used\nwhen source is a code object requiring exactly that many cellvars.", + "builtins.filter" => "Return an iterator yielding those items of iterable for which function(item)\nis true. If function is None, return the items that are true.", + "builtins.filter.__delattr__" => "Implement delattr(self, name).", + "builtins.filter.__eq__" => "Return self==value.", + "builtins.filter.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.filter.__ge__" => "Return self>=value.", + "builtins.filter.__getattribute__" => "Return getattr(self, name).", + "builtins.filter.__getstate__" => "Helper for pickle.", + "builtins.filter.__gt__" => "Return self>value.", + "builtins.filter.__hash__" => "Return hash(self).", + "builtins.filter.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.filter.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.filter.__iter__" => "Implement iter(self).", + "builtins.filter.__le__" => "Return self<=value.", + "builtins.filter.__lt__" => "Return self<value.", + "builtins.filter.__ne__" => "Return self!=value.", + "builtins.filter.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.filter.__next__" => "Implement next(self).", + "builtins.filter.__reduce__" => "Return state information for pickling.", + "builtins.filter.__reduce_ex__" => "Helper for pickle.", + "builtins.filter.__repr__" => "Return repr(self).", + "builtins.filter.__setattr__" => "Implement setattr(self, name, value).", + "builtins.filter.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.filter.__str__" => "Return str(self).", + "builtins.filter.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.float" => "Convert a string or number to a floating-point number, if possible.", + "builtins.float.__abs__" => "abs(self)", + "builtins.float.__add__" => "Return self+value.", + "builtins.float.__bool__" => "True if self else False", + "builtins.float.__ceil__" => "Return the ceiling as an Integral.", + "builtins.float.__delattr__" => "Implement delattr(self, name).", + "builtins.float.__divmod__" => "Return divmod(self, value).", + "builtins.float.__eq__" => "Return self==value.", + "builtins.float.__float__" => "float(self)", + "builtins.float.__floor__" => "Return the floor as an Integral.", + "builtins.float.__floordiv__" => "Return self//value.", + "builtins.float.__format__" => "Formats the float according to format_spec.", + "builtins.float.__ge__" => "Return self>=value.", + "builtins.float.__getattribute__" => "Return getattr(self, name).", + "builtins.float.__getformat__" => "You probably don't want to use this function.\n\n typestr\n Must be 'double' or 'float'.\n\nIt exists mainly to be used in Python's test suite.\n\nThis function returns whichever of 'unknown', 'IEEE, big-endian' or 'IEEE,\nlittle-endian' best describes the format of floating-point numbers used by the\nC type named by typestr.", + "builtins.float.__getstate__" => "Helper for pickle.", + "builtins.float.__gt__" => "Return self>value.", + "builtins.float.__hash__" => "Return hash(self).", + "builtins.float.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.float.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.float.__int__" => "int(self)", + "builtins.float.__le__" => "Return self<=value.", + "builtins.float.__lt__" => "Return self<value.", + "builtins.float.__mod__" => "Return self%value.", + "builtins.float.__mul__" => "Return self*value.", + "builtins.float.__ne__" => "Return self!=value.", + "builtins.float.__neg__" => "-self", + "builtins.float.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.float.__pos__" => "+self", + "builtins.float.__pow__" => "Return pow(self, value, mod).", + "builtins.float.__radd__" => "Return value+self.", + "builtins.float.__rdivmod__" => "Return divmod(value, self).", + "builtins.float.__reduce__" => "Helper for pickle.", + "builtins.float.__reduce_ex__" => "Helper for pickle.", + "builtins.float.__repr__" => "Return repr(self).", + "builtins.float.__rfloordiv__" => "Return value//self.", + "builtins.float.__rmod__" => "Return value%self.", + "builtins.float.__rmul__" => "Return value*self.", + "builtins.float.__round__" => "Return the Integral closest to x, rounding half toward even.\n\nWhen an argument is passed, work like built-in round(x, ndigits).", + "builtins.float.__rpow__" => "Return pow(value, self, mod).", + "builtins.float.__rsub__" => "Return value-self.", + "builtins.float.__rtruediv__" => "Return value/self.", + "builtins.float.__setattr__" => "Implement setattr(self, name, value).", + "builtins.float.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.float.__str__" => "Return str(self).", + "builtins.float.__sub__" => "Return self-value.", + "builtins.float.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.float.__truediv__" => "Return self/value.", + "builtins.float.__trunc__" => "Return the Integral closest to x between 0 and x.", + "builtins.float.as_integer_ratio" => "Return a pair of integers, whose ratio is exactly equal to the original float.\n\nThe ratio is in lowest terms and has a positive denominator. Raise\nOverflowError on infinities and a ValueError on NaNs.\n\n>>> (10.0).as_integer_ratio()\n(10, 1)\n>>> (0.0).as_integer_ratio()\n(0, 1)\n>>> (-.25).as_integer_ratio()\n(-1, 4)", + "builtins.float.conjugate" => "Return self, the complex conjugate of any float.", + "builtins.float.fromhex" => "Create a floating-point number from a hexadecimal string.\n\n>>> float.fromhex('0x1.ffffp10')\n2047.984375\n>>> float.fromhex('-0x1p-1074')\n-5e-324", + "builtins.float.hex" => "Return a hexadecimal representation of a floating-point number.\n\n>>> (-0.1).hex()\n'-0x1.999999999999ap-4'\n>>> 3.14159.hex()\n'0x1.921f9f01b866ep+1'", + "builtins.float.imag" => "the imaginary part of a complex number", + "builtins.float.is_integer" => "Return True if the float is an integer.", + "builtins.float.real" => "the real part of a complex number", + "builtins.format" => "Return type(value).__format__(value, format_spec)\n\nMany built-in types implement format_spec according to the\nFormat Specification Mini-language. See help('FORMATTING').\n\nIf type(value) does not supply a method named __format__\nand format_spec is empty, then str(value) is returned.\nSee also help('SPECIALMETHODS').", + "builtins.frozenset" => "Build an immutable unordered collection of unique elements.", + "builtins.frozenset.__and__" => "Return self&value.", + "builtins.frozenset.__class_getitem__" => "See PEP 585", + "builtins.frozenset.__contains__" => "x.__contains__(y) <==> y in x.", + "builtins.frozenset.__delattr__" => "Implement delattr(self, name).", + "builtins.frozenset.__eq__" => "Return self==value.", + "builtins.frozenset.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.frozenset.__ge__" => "Return self>=value.", + "builtins.frozenset.__getattribute__" => "Return getattr(self, name).", + "builtins.frozenset.__getstate__" => "Helper for pickle.", + "builtins.frozenset.__gt__" => "Return self>value.", + "builtins.frozenset.__hash__" => "Return hash(self).", + "builtins.frozenset.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.frozenset.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.frozenset.__iter__" => "Implement iter(self).", + "builtins.frozenset.__le__" => "Return self<=value.", + "builtins.frozenset.__len__" => "Return len(self).", + "builtins.frozenset.__lt__" => "Return self<value.", + "builtins.frozenset.__ne__" => "Return self!=value.", + "builtins.frozenset.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.frozenset.__or__" => "Return self|value.", + "builtins.frozenset.__rand__" => "Return value&self.", + "builtins.frozenset.__reduce__" => "Return state information for pickling.", + "builtins.frozenset.__reduce_ex__" => "Helper for pickle.", + "builtins.frozenset.__repr__" => "Return repr(self).", + "builtins.frozenset.__ror__" => "Return value|self.", + "builtins.frozenset.__rsub__" => "Return value-self.", + "builtins.frozenset.__rxor__" => "Return value^self.", + "builtins.frozenset.__setattr__" => "Implement setattr(self, name, value).", + "builtins.frozenset.__sizeof__" => "S.__sizeof__() -> size of S in memory, in bytes.", + "builtins.frozenset.__str__" => "Return str(self).", + "builtins.frozenset.__sub__" => "Return self-value.", + "builtins.frozenset.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.frozenset.__xor__" => "Return self^value.", + "builtins.frozenset.copy" => "Return a shallow copy of a set.", + "builtins.frozenset.difference" => "Return a new set with elements in the set that are not in the others.", + "builtins.frozenset.intersection" => "Return a new set with elements common to the set and all others.", + "builtins.frozenset.isdisjoint" => "Return True if two sets have a null intersection.", + "builtins.frozenset.issubset" => "Report whether another set contains this set.", + "builtins.frozenset.issuperset" => "Report whether this set contains another set.", + "builtins.frozenset.symmetric_difference" => "Return a new set with elements in either the set or other but not both.", + "builtins.frozenset.union" => "Return a new set with elements from the set and all others.", + "builtins.function" => "Create a function object.\n\ncode\n a code object\nglobals\n the globals dictionary\nname\n a string that overrides the name from the code object\nargdefs\n a tuple that specifies the default argument values\nclosure\n a tuple that supplies the bindings for free variables\nkwdefaults\n a dictionary that specifies the default keyword argument values", + "builtins.function.__call__" => "Call self as a function.", + "builtins.function.__delattr__" => "Implement delattr(self, name).", + "builtins.function.__eq__" => "Return self==value.", + "builtins.function.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.function.__ge__" => "Return self>=value.", + "builtins.function.__get__" => "Return an attribute of instance, which is of type owner.", + "builtins.function.__getattribute__" => "Return getattr(self, name).", + "builtins.function.__getstate__" => "Helper for pickle.", + "builtins.function.__gt__" => "Return self>value.", + "builtins.function.__hash__" => "Return hash(self).", + "builtins.function.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.function.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.function.__le__" => "Return self<=value.", + "builtins.function.__lt__" => "Return self<value.", + "builtins.function.__ne__" => "Return self!=value.", + "builtins.function.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.function.__reduce__" => "Helper for pickle.", + "builtins.function.__reduce_ex__" => "Helper for pickle.", + "builtins.function.__repr__" => "Return repr(self).", + "builtins.function.__setattr__" => "Implement setattr(self, name, value).", + "builtins.function.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.function.__str__" => "Return str(self).", + "builtins.function.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.function.__type_params__" => "Get the declared type parameters for a function.", + "builtins.getattr" => "getattr(object, name[, default]) -> value\n\nGet a named attribute from an object; getattr(x, 'y') is equivalent to x.y.\nWhen a default argument is given, it is returned when the attribute doesn't\nexist; without it, an exception is raised in that case.", + "builtins.globals" => "Return the dictionary containing the current scope's global variables.\n\nNOTE: Updates to this dictionary *will* affect name lookups in the current\nglobal scope and vice-versa.", + "builtins.hasattr" => "Return whether the object has an attribute with the given name.\n\nThis is done by calling getattr(obj, name) and catching AttributeError.", + "builtins.hash" => "Return the hash value for the given object.\n\nTwo objects that compare equal must also have the same hash value, but the\nreverse is not necessarily true.", + "builtins.hex" => "Return the hexadecimal representation of an integer.\n\n>>> hex(12648430)\n'0xc0ffee'", + "builtins.id" => "Return the identity of an object.\n\nThis is guaranteed to be unique among simultaneously existing objects.\n(CPython uses the object's memory address.)", + "builtins.input" => "Read a string from standard input. The trailing newline is stripped.\n\nThe prompt string, if given, is printed to standard output without a\ntrailing newline before reading input.\n\nIf the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.\nOn *nix systems, readline is used if available.", + "builtins.int" => "int([x]) -> integer\nint(x, base=10) -> integer\n\nConvert a number or string to an integer, or return 0 if no arguments\nare given. If x is a number, return x.__int__(). For floating-point\nnumbers, this truncates towards zero.\n\nIf x is not a number or if base is given, then x must be a string,\nbytes, or bytearray instance representing an integer literal in the\ngiven base. The literal can be preceded by '+' or '-' and be surrounded\nby whitespace. The base defaults to 10. Valid bases are 0 and 2-36.\nBase 0 means to interpret the base from the string as an integer literal.\n>>> int('0b100', base=0)\n4", + "builtins.int.__abs__" => "abs(self)", + "builtins.int.__add__" => "Return self+value.", + "builtins.int.__and__" => "Return self&value.", + "builtins.int.__bool__" => "True if self else False", + "builtins.int.__ceil__" => "Ceiling of an Integral returns itself.", + "builtins.int.__delattr__" => "Implement delattr(self, name).", + "builtins.int.__divmod__" => "Return divmod(self, value).", + "builtins.int.__eq__" => "Return self==value.", + "builtins.int.__float__" => "float(self)", + "builtins.int.__floor__" => "Flooring an Integral returns itself.", + "builtins.int.__floordiv__" => "Return self//value.", + "builtins.int.__format__" => "Convert to a string according to format_spec.", + "builtins.int.__ge__" => "Return self>=value.", + "builtins.int.__getattribute__" => "Return getattr(self, name).", + "builtins.int.__getstate__" => "Helper for pickle.", + "builtins.int.__gt__" => "Return self>value.", + "builtins.int.__hash__" => "Return hash(self).", + "builtins.int.__index__" => "Return self converted to an integer, if self is suitable for use as an index into a list.", + "builtins.int.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.int.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.int.__int__" => "int(self)", + "builtins.int.__invert__" => "~self", + "builtins.int.__le__" => "Return self<=value.", + "builtins.int.__lshift__" => "Return self<<value.", + "builtins.int.__lt__" => "Return self<value.", + "builtins.int.__mod__" => "Return self%value.", + "builtins.int.__mul__" => "Return self*value.", + "builtins.int.__ne__" => "Return self!=value.", + "builtins.int.__neg__" => "-self", + "builtins.int.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.int.__or__" => "Return self|value.", + "builtins.int.__pos__" => "+self", + "builtins.int.__pow__" => "Return pow(self, value, mod).", + "builtins.int.__radd__" => "Return value+self.", + "builtins.int.__rand__" => "Return value&self.", + "builtins.int.__rdivmod__" => "Return divmod(value, self).", + "builtins.int.__reduce__" => "Helper for pickle.", + "builtins.int.__reduce_ex__" => "Helper for pickle.", + "builtins.int.__repr__" => "Return repr(self).", + "builtins.int.__rfloordiv__" => "Return value//self.", + "builtins.int.__rlshift__" => "Return value<<self.", + "builtins.int.__rmod__" => "Return value%self.", + "builtins.int.__rmul__" => "Return value*self.", + "builtins.int.__ror__" => "Return value|self.", + "builtins.int.__round__" => "Rounding an Integral returns itself.\n\nRounding with an ndigits argument also returns an integer.", + "builtins.int.__rpow__" => "Return pow(value, self, mod).", + "builtins.int.__rrshift__" => "Return value>>self.", + "builtins.int.__rshift__" => "Return self>>value.", + "builtins.int.__rsub__" => "Return value-self.", + "builtins.int.__rtruediv__" => "Return value/self.", + "builtins.int.__rxor__" => "Return value^self.", + "builtins.int.__setattr__" => "Implement setattr(self, name, value).", + "builtins.int.__sizeof__" => "Returns size in memory, in bytes.", + "builtins.int.__str__" => "Return str(self).", + "builtins.int.__sub__" => "Return self-value.", + "builtins.int.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.int.__truediv__" => "Return self/value.", + "builtins.int.__trunc__" => "Truncating an Integral returns itself.", + "builtins.int.__xor__" => "Return self^value.", + "builtins.int.as_integer_ratio" => "Return a pair of integers, whose ratio is equal to the original int.\n\nThe ratio is in lowest terms and has a positive denominator.\n\n>>> (10).as_integer_ratio()\n(10, 1)\n>>> (-10).as_integer_ratio()\n(-10, 1)\n>>> (0).as_integer_ratio()\n(0, 1)", + "builtins.int.bit_count" => "Number of ones in the binary representation of the absolute value of self.\n\nAlso known as the population count.\n\n>>> bin(13)\n'0b1101'\n>>> (13).bit_count()\n3", + "builtins.int.bit_length" => "Number of bits necessary to represent self in binary.\n\n>>> bin(37)\n'0b100101'\n>>> (37).bit_length()\n6", + "builtins.int.conjugate" => "Returns self, the complex conjugate of any int.", + "builtins.int.denominator" => "the denominator of a rational number in lowest terms", + "builtins.int.from_bytes" => "Return the integer represented by the given array of bytes.\n\nbytes\n Holds the array of bytes to convert. The argument must either\n support the buffer protocol or be an iterable object producing bytes.\n Bytes and bytearray are examples of built-in objects that support the\n buffer protocol.\nbyteorder\n The byte order used to represent the integer. If byteorder is 'big',\n the most significant byte is at the beginning of the byte array. If\n byteorder is 'little', the most significant byte is at the end of the\n byte array. To request the native byte order of the host system, use\n sys.byteorder as the byte order value. Default is to use 'big'.\nsigned\n Indicates whether two's complement is used to represent the integer.", + "builtins.int.imag" => "the imaginary part of a complex number", + "builtins.int.is_integer" => "Returns True. Exists for duck type compatibility with float.is_integer.", + "builtins.int.numerator" => "the numerator of a rational number in lowest terms", + "builtins.int.real" => "the real part of a complex number", + "builtins.int.to_bytes" => "Return an array of bytes representing an integer.\n\nlength\n Length of bytes object to use. An OverflowError is raised if the\n integer is not representable with the given number of bytes. Default\n is length 1.\nbyteorder\n The byte order used to represent the integer. If byteorder is 'big',\n the most significant byte is at the beginning of the byte array. If\n byteorder is 'little', the most significant byte is at the end of the\n byte array. To request the native byte order of the host system, use\n sys.byteorder as the byte order value. Default is to use 'big'.\nsigned\n Determines whether two's complement is used to represent the integer.\n If signed is False and a negative integer is given, an OverflowError\n is raised.", + "builtins.isinstance" => "Return whether an object is an instance of a class or of a subclass thereof.\n\nA tuple, as in ``isinstance(x, (A, B, ...))``, may be given as the target to\ncheck against. This is equivalent to ``isinstance(x, A) or isinstance(x, B)\nor ...`` etc.", + "builtins.issubclass" => "Return whether 'cls' is derived from another class or is the same class.\n\nA tuple, as in ``issubclass(x, (A, B, ...))``, may be given as the target to\ncheck against. This is equivalent to ``issubclass(x, A) or issubclass(x, B)\nor ...``.", + "builtins.iter" => "iter(iterable) -> iterator\niter(callable, sentinel) -> iterator\n\nGet an iterator from an object. In the first form, the argument must\nsupply its own iterator, or be a sequence.\nIn the second form, the callable is called until it returns the sentinel.", + "builtins.len" => "Return the number of items in a container.", + "builtins.list" => "Built-in mutable sequence.\n\nIf no argument is given, the constructor creates a new empty list.\nThe argument must be an iterable if specified.", + "builtins.list.__add__" => "Return self+value.", + "builtins.list.__class_getitem__" => "See PEP 585", + "builtins.list.__contains__" => "Return bool(key in self).", + "builtins.list.__delattr__" => "Implement delattr(self, name).", + "builtins.list.__delitem__" => "Delete self[key].", + "builtins.list.__eq__" => "Return self==value.", + "builtins.list.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.list.__ge__" => "Return self>=value.", + "builtins.list.__getattribute__" => "Return getattr(self, name).", + "builtins.list.__getitem__" => "Return self[index].", + "builtins.list.__getstate__" => "Helper for pickle.", + "builtins.list.__gt__" => "Return self>value.", + "builtins.list.__iadd__" => "Implement self+=value.", + "builtins.list.__imul__" => "Implement self*=value.", + "builtins.list.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.list.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.list.__iter__" => "Implement iter(self).", + "builtins.list.__le__" => "Return self<=value.", + "builtins.list.__len__" => "Return len(self).", + "builtins.list.__lt__" => "Return self<value.", + "builtins.list.__mul__" => "Return self*value.", + "builtins.list.__ne__" => "Return self!=value.", + "builtins.list.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.list.__reduce__" => "Helper for pickle.", + "builtins.list.__reduce_ex__" => "Helper for pickle.", + "builtins.list.__repr__" => "Return repr(self).", + "builtins.list.__reversed__" => "Return a reverse iterator over the list.", + "builtins.list.__rmul__" => "Return value*self.", + "builtins.list.__setattr__" => "Implement setattr(self, name, value).", + "builtins.list.__setitem__" => "Set self[key] to value.", + "builtins.list.__sizeof__" => "Return the size of the list in memory, in bytes.", + "builtins.list.__str__" => "Return str(self).", + "builtins.list.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.list.append" => "Append object to the end of the list.", + "builtins.list.clear" => "Remove all items from list.", + "builtins.list.copy" => "Return a shallow copy of the list.", + "builtins.list.count" => "Return number of occurrences of value.", + "builtins.list.extend" => "Extend list by appending elements from the iterable.", + "builtins.list.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "builtins.list.insert" => "Insert object before index.", + "builtins.list.pop" => "Remove and return item at index (default last).\n\nRaises IndexError if list is empty or index is out of range.", + "builtins.list.remove" => "Remove first occurrence of value.\n\nRaises ValueError if the value is not present.", + "builtins.list.reverse" => "Reverse *IN PLACE*.", + "builtins.list.sort" => "Sort the list in ascending order and return None.\n\nThe sort is in-place (i.e. the list itself is modified) and stable (i.e. the\norder of two equal elements is maintained).\n\nIf a key function is given, apply it once to each list item and sort them,\nascending or descending, according to their function values.\n\nThe reverse flag can be set to sort in descending order.", + "builtins.list_iterator.__delattr__" => "Implement delattr(self, name).", + "builtins.list_iterator.__eq__" => "Return self==value.", + "builtins.list_iterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.list_iterator.__ge__" => "Return self>=value.", + "builtins.list_iterator.__getattribute__" => "Return getattr(self, name).", + "builtins.list_iterator.__getstate__" => "Helper for pickle.", + "builtins.list_iterator.__gt__" => "Return self>value.", + "builtins.list_iterator.__hash__" => "Return hash(self).", + "builtins.list_iterator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.list_iterator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.list_iterator.__iter__" => "Implement iter(self).", + "builtins.list_iterator.__le__" => "Return self<=value.", + "builtins.list_iterator.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "builtins.list_iterator.__lt__" => "Return self<value.", + "builtins.list_iterator.__ne__" => "Return self!=value.", + "builtins.list_iterator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.list_iterator.__next__" => "Implement next(self).", + "builtins.list_iterator.__reduce__" => "Return state information for pickling.", + "builtins.list_iterator.__reduce_ex__" => "Helper for pickle.", + "builtins.list_iterator.__repr__" => "Return repr(self).", + "builtins.list_iterator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.list_iterator.__setstate__" => "Set state information for unpickling.", + "builtins.list_iterator.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.list_iterator.__str__" => "Return str(self).", + "builtins.list_iterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.locals" => "Return a dictionary containing the current scope's local variables.\n\nNOTE: Whether or not updates to this dictionary will affect name lookups in\nthe local scope and vice-versa is *implementation dependent* and not\ncovered by any backwards compatibility guarantees.", + "builtins.map" => "Make an iterator that computes the function using arguments from\neach of the iterables. Stops when the shortest iterable is exhausted.", + "builtins.map.__delattr__" => "Implement delattr(self, name).", + "builtins.map.__eq__" => "Return self==value.", + "builtins.map.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.map.__ge__" => "Return self>=value.", + "builtins.map.__getattribute__" => "Return getattr(self, name).", + "builtins.map.__getstate__" => "Helper for pickle.", + "builtins.map.__gt__" => "Return self>value.", + "builtins.map.__hash__" => "Return hash(self).", + "builtins.map.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.map.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.map.__iter__" => "Implement iter(self).", + "builtins.map.__le__" => "Return self<=value.", + "builtins.map.__lt__" => "Return self<value.", + "builtins.map.__ne__" => "Return self!=value.", + "builtins.map.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.map.__next__" => "Implement next(self).", + "builtins.map.__reduce__" => "Return state information for pickling.", + "builtins.map.__reduce_ex__" => "Helper for pickle.", + "builtins.map.__repr__" => "Return repr(self).", + "builtins.map.__setattr__" => "Implement setattr(self, name, value).", + "builtins.map.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.map.__str__" => "Return str(self).", + "builtins.map.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.max" => "max(iterable, *[, default=obj, key=func]) -> value\nmax(arg1, arg2, *args, *[, key=func]) -> value\n\nWith a single iterable argument, return its biggest item. The\ndefault keyword-only argument specifies an object to return if\nthe provided iterable is empty.\nWith two or more positional arguments, return the largest argument.", + "builtins.memory_iterator.__delattr__" => "Implement delattr(self, name).", + "builtins.memory_iterator.__eq__" => "Return self==value.", + "builtins.memory_iterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.memory_iterator.__ge__" => "Return self>=value.", + "builtins.memory_iterator.__getattribute__" => "Return getattr(self, name).", + "builtins.memory_iterator.__getstate__" => "Helper for pickle.", + "builtins.memory_iterator.__gt__" => "Return self>value.", + "builtins.memory_iterator.__hash__" => "Return hash(self).", + "builtins.memory_iterator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.memory_iterator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.memory_iterator.__iter__" => "Implement iter(self).", + "builtins.memory_iterator.__le__" => "Return self<=value.", + "builtins.memory_iterator.__lt__" => "Return self<value.", + "builtins.memory_iterator.__ne__" => "Return self!=value.", + "builtins.memory_iterator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.memory_iterator.__next__" => "Implement next(self).", + "builtins.memory_iterator.__reduce__" => "Helper for pickle.", + "builtins.memory_iterator.__reduce_ex__" => "Helper for pickle.", + "builtins.memory_iterator.__repr__" => "Return repr(self).", + "builtins.memory_iterator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.memory_iterator.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.memory_iterator.__str__" => "Return str(self).", + "builtins.memory_iterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.memoryview" => "Create a new memoryview object which references the given object.", + "builtins.memoryview.__buffer__" => "Return a buffer object that exposes the underlying memory of the object.", + "builtins.memoryview.__delattr__" => "Implement delattr(self, name).", + "builtins.memoryview.__delitem__" => "Delete self[key].", + "builtins.memoryview.__eq__" => "Return self==value.", + "builtins.memoryview.__exit__" => "Release the underlying buffer exposed by the memoryview object.", + "builtins.memoryview.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.memoryview.__ge__" => "Return self>=value.", + "builtins.memoryview.__getattribute__" => "Return getattr(self, name).", + "builtins.memoryview.__getitem__" => "Return self[key].", + "builtins.memoryview.__getstate__" => "Helper for pickle.", + "builtins.memoryview.__gt__" => "Return self>value.", + "builtins.memoryview.__hash__" => "Return hash(self).", + "builtins.memoryview.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.memoryview.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.memoryview.__iter__" => "Implement iter(self).", + "builtins.memoryview.__le__" => "Return self<=value.", + "builtins.memoryview.__len__" => "Return len(self).", + "builtins.memoryview.__lt__" => "Return self<value.", + "builtins.memoryview.__ne__" => "Return self!=value.", + "builtins.memoryview.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.memoryview.__reduce__" => "Helper for pickle.", + "builtins.memoryview.__reduce_ex__" => "Helper for pickle.", + "builtins.memoryview.__release_buffer__" => "Release the buffer object that exposes the underlying memory of the object.", + "builtins.memoryview.__repr__" => "Return repr(self).", + "builtins.memoryview.__setattr__" => "Implement setattr(self, name, value).", + "builtins.memoryview.__setitem__" => "Set self[key] to value.", + "builtins.memoryview.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.memoryview.__str__" => "Return str(self).", + "builtins.memoryview.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.memoryview._from_flags" => "Create a new memoryview object which references the given object.", + "builtins.memoryview.c_contiguous" => "A bool indicating whether the memory is C contiguous.", + "builtins.memoryview.cast" => "Cast a memoryview to a new format or shape.", + "builtins.memoryview.contiguous" => "A bool indicating whether the memory is contiguous.", + "builtins.memoryview.f_contiguous" => "A bool indicating whether the memory is Fortran contiguous.", + "builtins.memoryview.format" => "A string containing the format (in struct module style)\nfor each element in the view.", + "builtins.memoryview.hex" => "Return the data in the buffer as a str of hexadecimal numbers.\n\n sep\n An optional single character or byte to separate hex bytes.\n bytes_per_sep\n How many bytes between separators. Positive values count from the\n right, negative values count from the left.\n\nExample:\n>>> value = memoryview(b'\\xb9\\x01\\xef')\n>>> value.hex()\n'b901ef'\n>>> value.hex(':')\n'b9:01:ef'\n>>> value.hex(':', 2)\n'b9:01ef'\n>>> value.hex(':', -2)\n'b901:ef'", + "builtins.memoryview.itemsize" => "The size in bytes of each element of the memoryview.", + "builtins.memoryview.nbytes" => "The amount of space in bytes that the array would use in\na contiguous representation.", + "builtins.memoryview.ndim" => "An integer indicating how many dimensions of a multi-dimensional\narray the memory represents.", + "builtins.memoryview.obj" => "The underlying object of the memoryview.", + "builtins.memoryview.readonly" => "A bool indicating whether the memory is read only.", + "builtins.memoryview.release" => "Release the underlying buffer exposed by the memoryview object.", + "builtins.memoryview.shape" => "A tuple of ndim integers giving the shape of the memory\nas an N-dimensional array.", + "builtins.memoryview.strides" => "A tuple of ndim integers giving the size in bytes to access\neach element for each dimension of the array.", + "builtins.memoryview.suboffsets" => "A tuple of integers used internally for PIL-style arrays.", + "builtins.memoryview.tobytes" => "Return the data in the buffer as a byte string.\n\nOrder can be {'C', 'F', 'A'}. When order is 'C' or 'F', the data of the\noriginal array is converted to C or Fortran order. For contiguous views,\n'A' returns an exact copy of the physical memory. In particular, in-memory\nFortran order is preserved. For non-contiguous views, the data is converted\nto C first. order=None is the same as order='C'.", + "builtins.memoryview.tolist" => "Return the data in the buffer as a list of elements.", + "builtins.memoryview.toreadonly" => "Return a readonly version of the memoryview.", + "builtins.min" => "min(iterable, *[, default=obj, key=func]) -> value\nmin(arg1, arg2, *args, *[, key=func]) -> value\n\nWith a single iterable argument, return its smallest item. The\ndefault keyword-only argument specifies an object to return if\nthe provided iterable is empty.\nWith two or more positional arguments, return the smallest argument.", + "builtins.next" => "next(iterator[, default])\n\nReturn the next item from the iterator. If default is given and the iterator\nis exhausted, it is returned instead of raising StopIteration.", + "builtins.object" => "The base class of the class hierarchy.\n\nWhen called, it accepts no arguments and returns a new featureless\ninstance that has no instance attributes and cannot be given any.", + "builtins.object.__delattr__" => "Implement delattr(self, name).", + "builtins.object.__eq__" => "Return self==value.", + "builtins.object.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.object.__ge__" => "Return self>=value.", + "builtins.object.__getattribute__" => "Return getattr(self, name).", + "builtins.object.__getstate__" => "Helper for pickle.", + "builtins.object.__gt__" => "Return self>value.", + "builtins.object.__hash__" => "Return hash(self).", + "builtins.object.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.object.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.object.__le__" => "Return self<=value.", + "builtins.object.__lt__" => "Return self<value.", + "builtins.object.__ne__" => "Return self!=value.", + "builtins.object.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.object.__reduce__" => "Helper for pickle.", + "builtins.object.__reduce_ex__" => "Helper for pickle.", + "builtins.object.__repr__" => "Return repr(self).", + "builtins.object.__setattr__" => "Implement setattr(self, name, value).", + "builtins.object.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.object.__str__" => "Return str(self).", + "builtins.object.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.oct" => "Return the octal representation of an integer.\n\n>>> oct(342391)\n'0o1234567'", + "builtins.ord" => "Return the ordinal value of a character.\n\nIf the argument is a one-character string, return the Unicode code\npoint of that character.\n\nIf the argument is a bytes or bytearray object of length 1, return its\nsingle byte value.", + "builtins.pow" => "Equivalent to base**exp with 2 arguments or base**exp % mod with 3 arguments\n\nSome types, such as ints, are able to use a more efficient algorithm when\ninvoked using the three argument form.", + "builtins.print" => "Prints the values to a stream, or to sys.stdout by default.\n\nsep\n string inserted between values, default a space.\nend\n string appended after the last value, default a newline.\nfile\n a file-like object (stream); defaults to the current sys.stdout.\nflush\n whether to forcibly flush the stream.", + "builtins.property" => "Property attribute.\n\n fget\n function to be used for getting an attribute value\n fset\n function to be used for setting an attribute value\n fdel\n function to be used for del'ing an attribute\n doc\n docstring\n\nTypical use is to define a managed attribute x:\n\nclass C(object):\n def getx(self): return self._x\n def setx(self, value): self._x = value\n def delx(self): del self._x\n x = property(getx, setx, delx, \"I'm the 'x' property.\")\n\nDecorators make defining new properties or modifying existing ones easy:\n\nclass C(object):\n @property\n def x(self):\n \"I am the 'x' property.\"\n return self._x\n @x.setter\n def x(self, value):\n self._x = value\n @x.deleter\n def x(self):\n del self._x", + "builtins.property.__delattr__" => "Implement delattr(self, name).", + "builtins.property.__delete__" => "Delete an attribute of instance.", + "builtins.property.__eq__" => "Return self==value.", + "builtins.property.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.property.__ge__" => "Return self>=value.", + "builtins.property.__get__" => "Return an attribute of instance, which is of type owner.", + "builtins.property.__getattribute__" => "Return getattr(self, name).", + "builtins.property.__getstate__" => "Helper for pickle.", + "builtins.property.__gt__" => "Return self>value.", + "builtins.property.__hash__" => "Return hash(self).", + "builtins.property.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.property.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.property.__le__" => "Return self<=value.", + "builtins.property.__lt__" => "Return self<value.", + "builtins.property.__ne__" => "Return self!=value.", + "builtins.property.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.property.__reduce__" => "Helper for pickle.", + "builtins.property.__reduce_ex__" => "Helper for pickle.", + "builtins.property.__repr__" => "Return repr(self).", + "builtins.property.__set__" => "Set an attribute of instance to value.", + "builtins.property.__set_name__" => "Method to set name of a property.", + "builtins.property.__setattr__" => "Implement setattr(self, name, value).", + "builtins.property.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.property.__str__" => "Return str(self).", + "builtins.property.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.property.deleter" => "Descriptor to obtain a copy of the property with a different deleter.", + "builtins.property.getter" => "Descriptor to obtain a copy of the property with a different getter.", + "builtins.property.setter" => "Descriptor to obtain a copy of the property with a different setter.", + "builtins.range" => "range(stop) -> range object\nrange(start, stop[, step]) -> range object\n\nReturn an object that produces a sequence of integers from start (inclusive)\nto stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.\nstart defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3.\nThese are exactly the valid indices for a list of 4 elements.\nWhen step is given, it specifies the increment (or decrement).", + "builtins.range.__bool__" => "True if self else False", + "builtins.range.__contains__" => "Return bool(key in self).", + "builtins.range.__delattr__" => "Implement delattr(self, name).", + "builtins.range.__eq__" => "Return self==value.", + "builtins.range.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.range.__ge__" => "Return self>=value.", + "builtins.range.__getattribute__" => "Return getattr(self, name).", + "builtins.range.__getitem__" => "Return self[key].", + "builtins.range.__getstate__" => "Helper for pickle.", + "builtins.range.__gt__" => "Return self>value.", + "builtins.range.__hash__" => "Return hash(self).", + "builtins.range.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.range.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.range.__iter__" => "Implement iter(self).", + "builtins.range.__le__" => "Return self<=value.", + "builtins.range.__len__" => "Return len(self).", + "builtins.range.__lt__" => "Return self<value.", + "builtins.range.__ne__" => "Return self!=value.", + "builtins.range.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.range.__reduce_ex__" => "Helper for pickle.", + "builtins.range.__repr__" => "Return repr(self).", + "builtins.range.__reversed__" => "Return a reverse iterator.", + "builtins.range.__setattr__" => "Implement setattr(self, name, value).", + "builtins.range.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.range.__str__" => "Return str(self).", + "builtins.range.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.range.count" => "rangeobject.count(value) -> integer -- return number of occurrences of value", + "builtins.range.index" => "rangeobject.index(value) -> integer -- return index of value.\nRaise ValueError if the value is not present.", + "builtins.range_iterator.__delattr__" => "Implement delattr(self, name).", + "builtins.range_iterator.__eq__" => "Return self==value.", + "builtins.range_iterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.range_iterator.__ge__" => "Return self>=value.", + "builtins.range_iterator.__getattribute__" => "Return getattr(self, name).", + "builtins.range_iterator.__getstate__" => "Helper for pickle.", + "builtins.range_iterator.__gt__" => "Return self>value.", + "builtins.range_iterator.__hash__" => "Return hash(self).", + "builtins.range_iterator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.range_iterator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.range_iterator.__iter__" => "Implement iter(self).", + "builtins.range_iterator.__le__" => "Return self<=value.", + "builtins.range_iterator.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "builtins.range_iterator.__lt__" => "Return self<value.", + "builtins.range_iterator.__ne__" => "Return self!=value.", + "builtins.range_iterator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.range_iterator.__next__" => "Implement next(self).", + "builtins.range_iterator.__reduce__" => "Return state information for pickling.", + "builtins.range_iterator.__reduce_ex__" => "Helper for pickle.", + "builtins.range_iterator.__repr__" => "Return repr(self).", + "builtins.range_iterator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.range_iterator.__setstate__" => "Set state information for unpickling.", + "builtins.range_iterator.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.range_iterator.__str__" => "Return str(self).", + "builtins.range_iterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.repr" => "Return the canonical string representation of the object.\n\nFor many object types, including most builtins, eval(repr(obj)) == obj.", + "builtins.reversed" => "Return a reverse iterator over the values of the given sequence.", + "builtins.reversed.__delattr__" => "Implement delattr(self, name).", + "builtins.reversed.__eq__" => "Return self==value.", + "builtins.reversed.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.reversed.__ge__" => "Return self>=value.", + "builtins.reversed.__getattribute__" => "Return getattr(self, name).", + "builtins.reversed.__getstate__" => "Helper for pickle.", + "builtins.reversed.__gt__" => "Return self>value.", + "builtins.reversed.__hash__" => "Return hash(self).", + "builtins.reversed.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.reversed.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.reversed.__iter__" => "Implement iter(self).", + "builtins.reversed.__le__" => "Return self<=value.", + "builtins.reversed.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "builtins.reversed.__lt__" => "Return self<value.", + "builtins.reversed.__ne__" => "Return self!=value.", + "builtins.reversed.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.reversed.__next__" => "Implement next(self).", + "builtins.reversed.__reduce__" => "Return state information for pickling.", + "builtins.reversed.__reduce_ex__" => "Helper for pickle.", + "builtins.reversed.__repr__" => "Return repr(self).", + "builtins.reversed.__setattr__" => "Implement setattr(self, name, value).", + "builtins.reversed.__setstate__" => "Set state information for unpickling.", + "builtins.reversed.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.reversed.__str__" => "Return str(self).", + "builtins.reversed.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.round" => "Round a number to a given precision in decimal digits.\n\nThe return value is an integer if ndigits is omitted or None. Otherwise\nthe return value has the same type as the number. ndigits may be negative.", + "builtins.set" => "Build an unordered collection of unique elements.", + "builtins.set.__and__" => "Return self&value.", + "builtins.set.__class_getitem__" => "See PEP 585", + "builtins.set.__contains__" => "x.__contains__(y) <==> y in x.", + "builtins.set.__delattr__" => "Implement delattr(self, name).", + "builtins.set.__eq__" => "Return self==value.", + "builtins.set.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.set.__ge__" => "Return self>=value.", + "builtins.set.__getattribute__" => "Return getattr(self, name).", + "builtins.set.__getstate__" => "Helper for pickle.", + "builtins.set.__gt__" => "Return self>value.", + "builtins.set.__iand__" => "Return self&=value.", + "builtins.set.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.set.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.set.__ior__" => "Return self|=value.", + "builtins.set.__isub__" => "Return self-=value.", + "builtins.set.__iter__" => "Implement iter(self).", + "builtins.set.__ixor__" => "Return self^=value.", + "builtins.set.__le__" => "Return self<=value.", + "builtins.set.__len__" => "Return len(self).", + "builtins.set.__lt__" => "Return self<value.", + "builtins.set.__ne__" => "Return self!=value.", + "builtins.set.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.set.__or__" => "Return self|value.", + "builtins.set.__rand__" => "Return value&self.", + "builtins.set.__reduce__" => "Return state information for pickling.", + "builtins.set.__reduce_ex__" => "Helper for pickle.", + "builtins.set.__repr__" => "Return repr(self).", + "builtins.set.__ror__" => "Return value|self.", + "builtins.set.__rsub__" => "Return value-self.", + "builtins.set.__rxor__" => "Return value^self.", + "builtins.set.__setattr__" => "Implement setattr(self, name, value).", + "builtins.set.__sizeof__" => "S.__sizeof__() -> size of S in memory, in bytes.", + "builtins.set.__str__" => "Return str(self).", + "builtins.set.__sub__" => "Return self-value.", + "builtins.set.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.set.__xor__" => "Return self^value.", + "builtins.set.add" => "Add an element to a set.\n\nThis has no effect if the element is already present.", + "builtins.set.clear" => "Remove all elements from this set.", + "builtins.set.copy" => "Return a shallow copy of a set.", + "builtins.set.difference" => "Return a new set with elements in the set that are not in the others.", + "builtins.set.difference_update" => "Update the set, removing elements found in others.", + "builtins.set.discard" => "Remove an element from a set if it is a member.\n\nUnlike set.remove(), the discard() method does not raise\nan exception when an element is missing from the set.", + "builtins.set.intersection" => "Return a new set with elements common to the set and all others.", + "builtins.set.intersection_update" => "Update the set, keeping only elements found in it and all others.", + "builtins.set.isdisjoint" => "Return True if two sets have a null intersection.", + "builtins.set.issubset" => "Report whether another set contains this set.", + "builtins.set.issuperset" => "Report whether this set contains another set.", + "builtins.set.pop" => "Remove and return an arbitrary set element.\n\nRaises KeyError if the set is empty.", + "builtins.set.remove" => "Remove an element from a set; it must be a member.\n\nIf the element is not a member, raise a KeyError.", + "builtins.set.symmetric_difference" => "Return a new set with elements in either the set or other but not both.", + "builtins.set.symmetric_difference_update" => "Update the set, keeping only elements found in either set, but not in both.", + "builtins.set.union" => "Return a new set with elements from the set and all others.", + "builtins.set.update" => "Update the set, adding elements from all others.", + "builtins.set_iterator.__delattr__" => "Implement delattr(self, name).", + "builtins.set_iterator.__eq__" => "Return self==value.", + "builtins.set_iterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.set_iterator.__ge__" => "Return self>=value.", + "builtins.set_iterator.__getattribute__" => "Return getattr(self, name).", + "builtins.set_iterator.__getstate__" => "Helper for pickle.", + "builtins.set_iterator.__gt__" => "Return self>value.", + "builtins.set_iterator.__hash__" => "Return hash(self).", + "builtins.set_iterator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.set_iterator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.set_iterator.__iter__" => "Implement iter(self).", + "builtins.set_iterator.__le__" => "Return self<=value.", + "builtins.set_iterator.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "builtins.set_iterator.__lt__" => "Return self<value.", + "builtins.set_iterator.__ne__" => "Return self!=value.", + "builtins.set_iterator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.set_iterator.__next__" => "Implement next(self).", + "builtins.set_iterator.__reduce__" => "Return state information for pickling.", + "builtins.set_iterator.__reduce_ex__" => "Helper for pickle.", + "builtins.set_iterator.__repr__" => "Return repr(self).", + "builtins.set_iterator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.set_iterator.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.set_iterator.__str__" => "Return str(self).", + "builtins.set_iterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.setattr" => "Sets the named attribute on the given object to the specified value.\n\nsetattr(x, 'y', v) is equivalent to ``x.y = v``", + "builtins.slice" => "slice(stop)\nslice(start, stop[, step])\n\nCreate a slice object. This is used for extended slicing (e.g. a[0:10:2]).", + "builtins.slice.__delattr__" => "Implement delattr(self, name).", + "builtins.slice.__eq__" => "Return self==value.", + "builtins.slice.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.slice.__ge__" => "Return self>=value.", + "builtins.slice.__getattribute__" => "Return getattr(self, name).", + "builtins.slice.__getstate__" => "Helper for pickle.", + "builtins.slice.__gt__" => "Return self>value.", + "builtins.slice.__hash__" => "Return hash(self).", + "builtins.slice.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.slice.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.slice.__le__" => "Return self<=value.", + "builtins.slice.__lt__" => "Return self<value.", + "builtins.slice.__ne__" => "Return self!=value.", + "builtins.slice.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.slice.__reduce__" => "Return state information for pickling.", + "builtins.slice.__reduce_ex__" => "Helper for pickle.", + "builtins.slice.__repr__" => "Return repr(self).", + "builtins.slice.__setattr__" => "Implement setattr(self, name, value).", + "builtins.slice.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.slice.__str__" => "Return str(self).", + "builtins.slice.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.slice.indices" => "S.indices(len) -> (start, stop, stride)\n\nAssuming a sequence of length len, calculate the start and stop\nindices, and the stride length of the extended slice described by\nS. Out of bounds indices are clipped in a manner consistent with the\nhandling of normal slices.", + "builtins.sorted" => "Return a new list containing all items from the iterable in ascending order.\n\nA custom key function can be supplied to customize the sort order, and the\nreverse flag can be set to request the result in descending order.", + "builtins.staticmethod" => "Convert a function to be a static method.\n\nA static method does not receive an implicit first argument.\nTo declare a static method, use this idiom:\n\n class C:\n @staticmethod\n def f(arg1, arg2, argN):\n ...\n\nIt can be called either on the class (e.g. C.f()) or on an instance\n(e.g. C().f()). Both the class and the instance are ignored, and\nneither is passed implicitly as the first argument to the method.\n\nStatic methods in Python are similar to those found in Java or C++.\nFor a more advanced concept, see the classmethod builtin.", + "builtins.staticmethod.__call__" => "Call self as a function.", + "builtins.staticmethod.__delattr__" => "Implement delattr(self, name).", + "builtins.staticmethod.__eq__" => "Return self==value.", + "builtins.staticmethod.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.staticmethod.__ge__" => "Return self>=value.", + "builtins.staticmethod.__get__" => "Return an attribute of instance, which is of type owner.", + "builtins.staticmethod.__getattribute__" => "Return getattr(self, name).", + "builtins.staticmethod.__getstate__" => "Helper for pickle.", + "builtins.staticmethod.__gt__" => "Return self>value.", + "builtins.staticmethod.__hash__" => "Return hash(self).", + "builtins.staticmethod.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.staticmethod.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.staticmethod.__le__" => "Return self<=value.", + "builtins.staticmethod.__lt__" => "Return self<value.", + "builtins.staticmethod.__ne__" => "Return self!=value.", + "builtins.staticmethod.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.staticmethod.__reduce__" => "Helper for pickle.", + "builtins.staticmethod.__reduce_ex__" => "Helper for pickle.", + "builtins.staticmethod.__repr__" => "Return repr(self).", + "builtins.staticmethod.__setattr__" => "Implement setattr(self, name, value).", + "builtins.staticmethod.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.staticmethod.__str__" => "Return str(self).", + "builtins.staticmethod.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.str" => "str(object='') -> str\nstr(bytes_or_buffer[, encoding[, errors]]) -> str\n\nCreate a new string object from the given object. If encoding or\nerrors is specified, then the object must expose a data buffer\nthat will be decoded using the given encoding and error handler.\nOtherwise, returns the result of object.__str__() (if defined)\nor repr(object).\nencoding defaults to 'utf-8'.\nerrors defaults to 'strict'.", + "builtins.str.__add__" => "Return self+value.", + "builtins.str.__contains__" => "Return bool(key in self).", + "builtins.str.__delattr__" => "Implement delattr(self, name).", + "builtins.str.__eq__" => "Return self==value.", + "builtins.str.__format__" => "Return a formatted version of the string as described by format_spec.", + "builtins.str.__ge__" => "Return self>=value.", + "builtins.str.__getattribute__" => "Return getattr(self, name).", + "builtins.str.__getitem__" => "Return self[key].", + "builtins.str.__getstate__" => "Helper for pickle.", + "builtins.str.__gt__" => "Return self>value.", + "builtins.str.__hash__" => "Return hash(self).", + "builtins.str.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.str.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.str.__iter__" => "Implement iter(self).", + "builtins.str.__le__" => "Return self<=value.", + "builtins.str.__len__" => "Return len(self).", + "builtins.str.__lt__" => "Return self<value.", + "builtins.str.__mod__" => "Return self%value.", + "builtins.str.__mul__" => "Return self*value.", + "builtins.str.__ne__" => "Return self!=value.", + "builtins.str.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.str.__reduce__" => "Helper for pickle.", + "builtins.str.__reduce_ex__" => "Helper for pickle.", + "builtins.str.__repr__" => "Return repr(self).", + "builtins.str.__rmod__" => "Return value%self.", + "builtins.str.__rmul__" => "Return value*self.", + "builtins.str.__setattr__" => "Implement setattr(self, name, value).", + "builtins.str.__sizeof__" => "Return the size of the string in memory, in bytes.", + "builtins.str.__str__" => "Return str(self).", + "builtins.str.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.str.capitalize" => "Return a capitalized version of the string.\n\nMore specifically, make the first character have upper case and the rest lower\ncase.", + "builtins.str.casefold" => "Return a version of the string suitable for caseless comparisons.", + "builtins.str.center" => "Return a centered string of length width.\n\nPadding is done using the specified fill character (default is a space).", + "builtins.str.count" => "Return the number of non-overlapping occurrences of substring sub in string S[start:end].\n\nOptional arguments start and end are interpreted as in slice notation.", + "builtins.str.encode" => "Encode the string using the codec registered for encoding.\n\nencoding\n The encoding in which to encode the string.\nerrors\n The error handling scheme to use for encoding errors.\n The default is 'strict' meaning that encoding errors raise a\n UnicodeEncodeError. Other possible values are 'ignore', 'replace' and\n 'xmlcharrefreplace' as well as any other name registered with\n codecs.register_error that can handle UnicodeEncodeErrors.", + "builtins.str.endswith" => "Return True if the string ends with the specified suffix, False otherwise.\n\nsuffix\n A string or a tuple of strings to try.\nstart\n Optional start position. Default: start of the string.\nend\n Optional stop position. Default: end of the string.", + "builtins.str.expandtabs" => "Return a copy where all tab characters are expanded using spaces.\n\nIf tabsize is not given, a tab size of 8 characters is assumed.", + "builtins.str.find" => "Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end].\n\nOptional arguments start and end are interpreted as in slice notation.\nReturn -1 on failure.", + "builtins.str.format" => "Return a formatted version of the string, using substitutions from args and kwargs.\nThe substitutions are identified by braces ('{' and '}').", + "builtins.str.format_map" => "Return a formatted version of the string, using substitutions from mapping.\nThe substitutions are identified by braces ('{' and '}').", + "builtins.str.index" => "Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end].\n\nOptional arguments start and end are interpreted as in slice notation.\nRaises ValueError when the substring is not found.", + "builtins.str.isalnum" => "Return True if the string is an alpha-numeric string, False otherwise.\n\nA string is alpha-numeric if all characters in the string are alpha-numeric and\nthere is at least one character in the string.", + "builtins.str.isalpha" => "Return True if the string is an alphabetic string, False otherwise.\n\nA string is alphabetic if all characters in the string are alphabetic and there\nis at least one character in the string.", + "builtins.str.isascii" => "Return True if all characters in the string are ASCII, False otherwise.\n\nASCII characters have code points in the range U+0000-U+007F.\nEmpty string is ASCII too.", + "builtins.str.isdecimal" => "Return True if the string is a decimal string, False otherwise.\n\nA string is a decimal string if all characters in the string are decimal and\nthere is at least one character in the string.", + "builtins.str.isdigit" => "Return True if the string is a digit string, False otherwise.\n\nA string is a digit string if all characters in the string are digits and there\nis at least one character in the string.", + "builtins.str.isidentifier" => "Return True if the string is a valid Python identifier, False otherwise.\n\nCall keyword.iskeyword(s) to test whether string s is a reserved identifier,\nsuch as \"def\" or \"class\".", + "builtins.str.islower" => "Return True if the string is a lowercase string, False otherwise.\n\nA string is lowercase if all cased characters in the string are lowercase and\nthere is at least one cased character in the string.", + "builtins.str.isnumeric" => "Return True if the string is a numeric string, False otherwise.\n\nA string is numeric if all characters in the string are numeric and there is at\nleast one character in the string.", + "builtins.str.isprintable" => "Return True if all characters in the string are printable, False otherwise.\n\nA character is printable if repr() may use it in its output.", + "builtins.str.isspace" => "Return True if the string is a whitespace string, False otherwise.\n\nA string is whitespace if all characters in the string are whitespace and there\nis at least one character in the string.", + "builtins.str.istitle" => "Return True if the string is a title-cased string, False otherwise.\n\nIn a title-cased string, upper- and title-case characters may only\nfollow uncased characters and lowercase characters only cased ones.", + "builtins.str.isupper" => "Return True if the string is an uppercase string, False otherwise.\n\nA string is uppercase if all cased characters in the string are uppercase and\nthere is at least one cased character in the string.", + "builtins.str.join" => "Concatenate any number of strings.\n\nThe string whose method is called is inserted in between each given string.\nThe result is returned as a new string.\n\nExample: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'", + "builtins.str.ljust" => "Return a left-justified string of length width.\n\nPadding is done using the specified fill character (default is a space).", + "builtins.str.lower" => "Return a copy of the string converted to lowercase.", + "builtins.str.lstrip" => "Return a copy of the string with leading whitespace removed.\n\nIf chars is given and not None, remove characters in chars instead.", + "builtins.str.maketrans" => "Return a translation table usable for str.translate().\n\nIf there is only one argument, it must be a dictionary mapping Unicode\nordinals (integers) or characters to Unicode ordinals, strings or None.\nCharacter keys will be then converted to ordinals.\nIf there are two arguments, they must be strings of equal length, and\nin the resulting dictionary, each character in x will be mapped to the\ncharacter at the same position in y. If there is a third argument, it\nmust be a string, whose characters will be mapped to None in the result.", + "builtins.str.partition" => "Partition the string into three parts using the given separator.\n\nThis will search for the separator in the string. If the separator is found,\nreturns a 3-tuple containing the part before the separator, the separator\nitself, and the part after it.\n\nIf the separator is not found, returns a 3-tuple containing the original string\nand two empty strings.", + "builtins.str.removeprefix" => "Return a str with the given prefix string removed if present.\n\nIf the string starts with the prefix string, return string[len(prefix):].\nOtherwise, return a copy of the original string.", + "builtins.str.removesuffix" => "Return a str with the given suffix string removed if present.\n\nIf the string ends with the suffix string and that suffix is not empty,\nreturn string[:-len(suffix)]. Otherwise, return a copy of the original\nstring.", + "builtins.str.replace" => "Return a copy with all occurrences of substring old replaced by new.\n\n count\n Maximum number of occurrences to replace.\n -1 (the default value) means replace all occurrences.\n\nIf the optional argument count is given, only the first count occurrences are\nreplaced.", + "builtins.str.rfind" => "Return the highest index in S where substring sub is found, such that sub is contained within S[start:end].\n\nOptional arguments start and end are interpreted as in slice notation.\nReturn -1 on failure.", + "builtins.str.rindex" => "Return the highest index in S where substring sub is found, such that sub is contained within S[start:end].\n\nOptional arguments start and end are interpreted as in slice notation.\nRaises ValueError when the substring is not found.", + "builtins.str.rjust" => "Return a right-justified string of length width.\n\nPadding is done using the specified fill character (default is a space).", + "builtins.str.rpartition" => "Partition the string into three parts using the given separator.\n\nThis will search for the separator in the string, starting at the end. If\nthe separator is found, returns a 3-tuple containing the part before the\nseparator, the separator itself, and the part after it.\n\nIf the separator is not found, returns a 3-tuple containing two empty strings\nand the original string.", + "builtins.str.rsplit" => "Return a list of the substrings in the string, using sep as the separator string.\n\n sep\n The separator used to split the string.\n\n When set to None (the default value), will split on any whitespace\n character (including \\n \\r \\t \\f and spaces) and will discard\n empty strings from the result.\n maxsplit\n Maximum number of splits.\n -1 (the default value) means no limit.\n\nSplitting starts at the end of the string and works to the front.", + "builtins.str.rstrip" => "Return a copy of the string with trailing whitespace removed.\n\nIf chars is given and not None, remove characters in chars instead.", + "builtins.str.split" => "Return a list of the substrings in the string, using sep as the separator string.\n\n sep\n The separator used to split the string.\n\n When set to None (the default value), will split on any whitespace\n character (including \\n \\r \\t \\f and spaces) and will discard\n empty strings from the result.\n maxsplit\n Maximum number of splits.\n -1 (the default value) means no limit.\n\nSplitting starts at the front of the string and works to the end.\n\nNote, str.split() is mainly useful for data that has been intentionally\ndelimited. With natural text that includes punctuation, consider using\nthe regular expression module.", + "builtins.str.splitlines" => "Return a list of the lines in the string, breaking at line boundaries.\n\nLine breaks are not included in the resulting list unless keepends is given and\ntrue.", + "builtins.str.startswith" => "Return True if the string starts with the specified prefix, False otherwise.\n\nprefix\n A string or a tuple of strings to try.\nstart\n Optional start position. Default: start of the string.\nend\n Optional stop position. Default: end of the string.", + "builtins.str.strip" => "Return a copy of the string with leading and trailing whitespace removed.\n\nIf chars is given and not None, remove characters in chars instead.", + "builtins.str.swapcase" => "Convert uppercase characters to lowercase and lowercase characters to uppercase.", + "builtins.str.title" => "Return a version of the string where each word is titlecased.\n\nMore specifically, words start with uppercased characters and all remaining\ncased characters have lower case.", + "builtins.str.translate" => "Replace each character in the string using the given translation table.\n\n table\n Translation table, which must be a mapping of Unicode ordinals to\n Unicode ordinals, strings, or None.\n\nThe table must implement lookup/indexing via __getitem__, for instance a\ndictionary or list. If this operation raises LookupError, the character is\nleft untouched. Characters mapped to None are deleted.", + "builtins.str.upper" => "Return a copy of the string converted to uppercase.", + "builtins.str.zfill" => "Pad a numeric string with zeros on the left, to fill a field of the given width.\n\nThe string is never truncated.", + "builtins.str_ascii_iterator.__delattr__" => "Implement delattr(self, name).", + "builtins.str_ascii_iterator.__eq__" => "Return self==value.", + "builtins.str_ascii_iterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.str_ascii_iterator.__ge__" => "Return self>=value.", + "builtins.str_ascii_iterator.__getattribute__" => "Return getattr(self, name).", + "builtins.str_ascii_iterator.__getstate__" => "Helper for pickle.", + "builtins.str_ascii_iterator.__gt__" => "Return self>value.", + "builtins.str_ascii_iterator.__hash__" => "Return hash(self).", + "builtins.str_ascii_iterator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.str_ascii_iterator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.str_ascii_iterator.__iter__" => "Implement iter(self).", + "builtins.str_ascii_iterator.__le__" => "Return self<=value.", + "builtins.str_ascii_iterator.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "builtins.str_ascii_iterator.__lt__" => "Return self<value.", + "builtins.str_ascii_iterator.__ne__" => "Return self!=value.", + "builtins.str_ascii_iterator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.str_ascii_iterator.__next__" => "Implement next(self).", + "builtins.str_ascii_iterator.__reduce__" => "Return state information for pickling.", + "builtins.str_ascii_iterator.__reduce_ex__" => "Helper for pickle.", + "builtins.str_ascii_iterator.__repr__" => "Return repr(self).", + "builtins.str_ascii_iterator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.str_ascii_iterator.__setstate__" => "Set state information for unpickling.", + "builtins.str_ascii_iterator.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.str_ascii_iterator.__str__" => "Return str(self).", + "builtins.str_ascii_iterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.sum" => "Return the sum of a 'start' value (default: 0) plus an iterable of numbers\n\nWhen the iterable is empty, return the start value.\nThis function is intended specifically for use with numeric values and may\nreject non-numeric types.", + "builtins.super" => "super() -> same as super(__class__, <first argument>)\nsuper(type) -> unbound super object\nsuper(type, obj) -> bound super object; requires isinstance(obj, type)\nsuper(type, type2) -> bound super object; requires issubclass(type2, type)\nTypical use to call a cooperative superclass method:\nclass C(B):\n def meth(self, arg):\n super().meth(arg)\nThis works for class methods too:\nclass C(B):\n @classmethod\n def cmeth(cls, arg):\n super().cmeth(arg)", + "builtins.super.__delattr__" => "Implement delattr(self, name).", + "builtins.super.__eq__" => "Return self==value.", + "builtins.super.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.super.__ge__" => "Return self>=value.", + "builtins.super.__get__" => "Return an attribute of instance, which is of type owner.", + "builtins.super.__getattribute__" => "Return getattr(self, name).", + "builtins.super.__getstate__" => "Helper for pickle.", + "builtins.super.__gt__" => "Return self>value.", + "builtins.super.__hash__" => "Return hash(self).", + "builtins.super.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.super.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.super.__le__" => "Return self<=value.", + "builtins.super.__lt__" => "Return self<value.", + "builtins.super.__ne__" => "Return self!=value.", + "builtins.super.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.super.__reduce__" => "Helper for pickle.", + "builtins.super.__reduce_ex__" => "Helper for pickle.", + "builtins.super.__repr__" => "Return repr(self).", + "builtins.super.__self__" => "the instance invoking super(); may be None", + "builtins.super.__self_class__" => "the type of the instance invoking super(); may be None", + "builtins.super.__setattr__" => "Implement setattr(self, name, value).", + "builtins.super.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.super.__str__" => "Return str(self).", + "builtins.super.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.super.__thisclass__" => "the class invoking super()", + "builtins.tuple" => "Built-in immutable sequence.\n\nIf no argument is given, the constructor returns an empty tuple.\nIf iterable is specified the tuple is initialized from iterable's items.\n\nIf the argument is a tuple, the return value is the same object.", + "builtins.tuple.__add__" => "Return self+value.", + "builtins.tuple.__class_getitem__" => "See PEP 585", + "builtins.tuple.__contains__" => "Return bool(key in self).", + "builtins.tuple.__delattr__" => "Implement delattr(self, name).", + "builtins.tuple.__eq__" => "Return self==value.", + "builtins.tuple.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.tuple.__ge__" => "Return self>=value.", + "builtins.tuple.__getattribute__" => "Return getattr(self, name).", + "builtins.tuple.__getitem__" => "Return self[key].", + "builtins.tuple.__getstate__" => "Helper for pickle.", + "builtins.tuple.__gt__" => "Return self>value.", + "builtins.tuple.__hash__" => "Return hash(self).", + "builtins.tuple.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.tuple.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.tuple.__iter__" => "Implement iter(self).", + "builtins.tuple.__le__" => "Return self<=value.", + "builtins.tuple.__len__" => "Return len(self).", + "builtins.tuple.__lt__" => "Return self<value.", + "builtins.tuple.__mul__" => "Return self*value.", + "builtins.tuple.__ne__" => "Return self!=value.", + "builtins.tuple.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.tuple.__reduce__" => "Helper for pickle.", + "builtins.tuple.__reduce_ex__" => "Helper for pickle.", + "builtins.tuple.__repr__" => "Return repr(self).", + "builtins.tuple.__rmul__" => "Return value*self.", + "builtins.tuple.__setattr__" => "Implement setattr(self, name, value).", + "builtins.tuple.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.tuple.__str__" => "Return str(self).", + "builtins.tuple.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.tuple.count" => "Return number of occurrences of value.", + "builtins.tuple.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "builtins.tuple_iterator.__delattr__" => "Implement delattr(self, name).", + "builtins.tuple_iterator.__eq__" => "Return self==value.", + "builtins.tuple_iterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.tuple_iterator.__ge__" => "Return self>=value.", + "builtins.tuple_iterator.__getattribute__" => "Return getattr(self, name).", + "builtins.tuple_iterator.__getstate__" => "Helper for pickle.", + "builtins.tuple_iterator.__gt__" => "Return self>value.", + "builtins.tuple_iterator.__hash__" => "Return hash(self).", + "builtins.tuple_iterator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.tuple_iterator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.tuple_iterator.__iter__" => "Implement iter(self).", + "builtins.tuple_iterator.__le__" => "Return self<=value.", + "builtins.tuple_iterator.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "builtins.tuple_iterator.__lt__" => "Return self<value.", + "builtins.tuple_iterator.__ne__" => "Return self!=value.", + "builtins.tuple_iterator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.tuple_iterator.__next__" => "Implement next(self).", + "builtins.tuple_iterator.__reduce__" => "Return state information for pickling.", + "builtins.tuple_iterator.__reduce_ex__" => "Helper for pickle.", + "builtins.tuple_iterator.__repr__" => "Return repr(self).", + "builtins.tuple_iterator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.tuple_iterator.__setstate__" => "Set state information for unpickling.", + "builtins.tuple_iterator.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.tuple_iterator.__str__" => "Return str(self).", + "builtins.tuple_iterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.type" => "type(object) -> the object's type\ntype(name, bases, dict, **kwds) -> a new type", + "builtins.type.__base__" => "The base class of the class hierarchy.\n\nWhen called, it accepts no arguments and returns a new featureless\ninstance that has no instance attributes and cannot be given any.", + "builtins.type.__base__.__delattr__" => "Implement delattr(self, name).", + "builtins.type.__base__.__eq__" => "Return self==value.", + "builtins.type.__base__.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.type.__base__.__ge__" => "Return self>=value.", + "builtins.type.__base__.__getattribute__" => "Return getattr(self, name).", + "builtins.type.__base__.__getstate__" => "Helper for pickle.", + "builtins.type.__base__.__gt__" => "Return self>value.", + "builtins.type.__base__.__hash__" => "Return hash(self).", + "builtins.type.__base__.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.type.__base__.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.type.__base__.__le__" => "Return self<=value.", + "builtins.type.__base__.__lt__" => "Return self<value.", + "builtins.type.__base__.__ne__" => "Return self!=value.", + "builtins.type.__base__.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.type.__base__.__reduce__" => "Helper for pickle.", + "builtins.type.__base__.__reduce_ex__" => "Helper for pickle.", + "builtins.type.__base__.__repr__" => "Return repr(self).", + "builtins.type.__base__.__setattr__" => "Implement setattr(self, name, value).", + "builtins.type.__base__.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.type.__base__.__str__" => "Return str(self).", + "builtins.type.__base__.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.type.__call__" => "Call self as a function.", + "builtins.type.__delattr__" => "Implement delattr(self, name).", + "builtins.type.__eq__" => "Return self==value.", + "builtins.type.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.type.__ge__" => "Return self>=value.", + "builtins.type.__getattribute__" => "Return getattr(self, name).", + "builtins.type.__getstate__" => "Helper for pickle.", + "builtins.type.__gt__" => "Return self>value.", + "builtins.type.__hash__" => "Return hash(self).", + "builtins.type.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.type.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.type.__instancecheck__" => "Check if an object is an instance.", + "builtins.type.__le__" => "Return self<=value.", + "builtins.type.__lt__" => "Return self<value.", + "builtins.type.__ne__" => "Return self!=value.", + "builtins.type.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.type.__or__" => "Return self|value.", + "builtins.type.__prepare__" => "Create the namespace for the class statement", + "builtins.type.__reduce__" => "Helper for pickle.", + "builtins.type.__reduce_ex__" => "Helper for pickle.", + "builtins.type.__repr__" => "Return repr(self).", + "builtins.type.__ror__" => "Return value|self.", + "builtins.type.__setattr__" => "Implement setattr(self, name, value).", + "builtins.type.__sizeof__" => "Return memory consumption of the type object.", + "builtins.type.__str__" => "Return str(self).", + "builtins.type.__subclasscheck__" => "Check if a class is a subclass.", + "builtins.type.__subclasses__" => "Return a list of immediate subclasses.", + "builtins.type.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.type.mro" => "Return a type's method resolution order.", + "builtins.vars" => "vars([object]) -> dictionary\n\nWithout arguments, equivalent to locals().\nWith an argument, equivalent to object.__dict__.", + "builtins.zip" => "The zip object yields n-length tuples, where n is the number of iterables\npassed as positional arguments to zip(). The i-th element in every tuple\ncomes from the i-th iterable argument to zip(). This continues until the\nshortest argument is exhausted.\n\nIf strict is true and one of the arguments is exhausted before the others,\nraise a ValueError.\n\n >>> list(zip('abcdefg', range(3), range(4)))\n [('a', 0, 0), ('b', 1, 1), ('c', 2, 2)]", + "builtins.zip.__delattr__" => "Implement delattr(self, name).", + "builtins.zip.__eq__" => "Return self==value.", + "builtins.zip.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.zip.__ge__" => "Return self>=value.", + "builtins.zip.__getattribute__" => "Return getattr(self, name).", + "builtins.zip.__getstate__" => "Helper for pickle.", + "builtins.zip.__gt__" => "Return self>value.", + "builtins.zip.__hash__" => "Return hash(self).", + "builtins.zip.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.zip.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.zip.__iter__" => "Implement iter(self).", + "builtins.zip.__le__" => "Return self<=value.", + "builtins.zip.__lt__" => "Return self<value.", + "builtins.zip.__ne__" => "Return self!=value.", + "builtins.zip.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.zip.__next__" => "Implement next(self).", + "builtins.zip.__reduce__" => "Return state information for pickling.", + "builtins.zip.__reduce_ex__" => "Helper for pickle.", + "builtins.zip.__repr__" => "Return repr(self).", + "builtins.zip.__setattr__" => "Implement setattr(self, name, value).", + "builtins.zip.__setstate__" => "Set state information for unpickling.", + "builtins.zip.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.zip.__str__" => "Return str(self).", + "builtins.zip.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "cmath" => "This module provides access to mathematical functions for complex\nnumbers.", + "cmath.acos" => "Return the arc cosine of z.", + "cmath.acosh" => "Return the inverse hyperbolic cosine of z.", + "cmath.asin" => "Return the arc sine of z.", + "cmath.asinh" => "Return the inverse hyperbolic sine of z.", + "cmath.atan" => "Return the arc tangent of z.", + "cmath.atanh" => "Return the inverse hyperbolic tangent of z.", + "cmath.cos" => "Return the cosine of z.", + "cmath.cosh" => "Return the hyperbolic cosine of z.", + "cmath.exp" => "Return the exponential value e**z.", + "cmath.isclose" => "Determine whether two complex numbers are close in value.\n\n rel_tol\n maximum difference for being considered \"close\", relative to the\n magnitude of the input values\n abs_tol\n maximum difference for being considered \"close\", regardless of the\n magnitude of the input values\n\nReturn True if a is close in value to b, and False otherwise.\n\nFor the values to be considered close, the difference between them must be\nsmaller than at least one of the tolerances.\n\n-inf, inf and NaN behave similarly to the IEEE 754 Standard. That is, NaN is\nnot close to anything, even itself. inf and -inf are only close to themselves.", + "cmath.isfinite" => "Return True if both the real and imaginary parts of z are finite, else False.", + "cmath.isinf" => "Checks if the real or imaginary part of z is infinite.", + "cmath.isnan" => "Checks if the real or imaginary part of z not a number (NaN).", + "cmath.log" => "log(z[, base]) -> the logarithm of z to the given base.\n\nIf the base is not specified, returns the natural logarithm (base e) of z.", + "cmath.log10" => "Return the base-10 logarithm of z.", + "cmath.phase" => "Return argument, also known as the phase angle, of a complex.", + "cmath.polar" => "Convert a complex from rectangular coordinates to polar coordinates.\n\nr is the distance from 0 and phi the phase angle.", + "cmath.rect" => "Convert from polar coordinates to rectangular coordinates.", + "cmath.sin" => "Return the sine of z.", + "cmath.sinh" => "Return the hyperbolic sine of z.", + "cmath.sqrt" => "Return the square root of z.", + "cmath.tan" => "Return the tangent of z.", + "cmath.tanh" => "Return the hyperbolic tangent of z.", + "errno" => "This module makes available standard errno system symbols.\n\nThe value of each symbol is the corresponding integer value,\ne.g., on most systems, errno.ENOENT equals the integer 2.\n\nThe dictionary errno.errorcode maps numeric codes to symbol names,\ne.g., errno.errorcode[2] could be the string 'ENOENT'.\n\nSymbols that are not relevant to the underlying system are not defined.\n\nTo map error codes to error messages, use the function os.strerror(),\ne.g. os.strerror(2) could return 'No such file or directory'.", + "faulthandler" => "faulthandler module.", + "faulthandler._fatal_error_c_thread" => "Call Py_FatalError() in a new C thread.", + "faulthandler._raise_exception" => "Call RaiseException(code, flags).", + "faulthandler._read_null" => "Read from NULL, raise a SIGSEGV or SIGBUS signal depending on the platform.", + "faulthandler._sigabrt" => "Raise a SIGABRT signal.", + "faulthandler._sigfpe" => "Raise a SIGFPE signal.", + "faulthandler._sigsegv" => "Raise a SIGSEGV signal.", + "faulthandler._stack_overflow" => "Recursive call to raise a stack overflow.", + "faulthandler.cancel_dump_traceback_later" => "Cancel the previous call to dump_traceback_later().", + "faulthandler.disable" => "Disable the fault handler.", + "faulthandler.dump_traceback" => "Dump the traceback of the current thread, or of all threads if all_threads is True, into file.", + "faulthandler.dump_traceback_later" => "Dump the traceback of all threads in timeout seconds,\nor each timeout seconds if repeat is True. If exit is True, call _exit(1) which is not safe.", + "faulthandler.enable" => "Enable the fault handler.", + "faulthandler.is_enabled" => "Check if the handler is enabled.", + "faulthandler.register" => "Register a handler for the signal 'signum': dump the traceback of the current thread, or of all threads if all_threads is True, into file.", + "faulthandler.unregister" => "Unregister the handler of the signal 'signum' registered by register().", + "fcntl" => "This module performs file control and I/O control on file\ndescriptors. It is an interface to the fcntl() and ioctl() Unix\nroutines. File descriptors can be obtained with the fileno() method of\na file or socket object.", + "fcntl.fcntl" => "Perform the operation `cmd` on file descriptor fd.\n\nThe values used for `cmd` are operating system dependent, and are available\nas constants in the fcntl module, using the same names as used in\nthe relevant C header files. The argument arg is optional, and\ndefaults to 0; it may be an int or a string. If arg is given as a string,\nthe return value of fcntl is a string of that length, containing the\nresulting value put in the arg buffer by the operating system. The length\nof the arg string is not allowed to exceed 1024 bytes. If the arg given\nis an integer or if none is specified, the result value is an integer\ncorresponding to the return value of the fcntl call in the C code.", + "fcntl.flock" => "Perform the lock operation `operation` on file descriptor `fd`.\n\nSee the Unix manual page for flock(2) for details (On some systems, this\nfunction is emulated using fcntl()).", + "fcntl.ioctl" => "Perform the operation `request` on file descriptor `fd`.\n\nThe values used for `request` are operating system dependent, and are available\nas constants in the fcntl or termios library modules, using the same names as\nused in the relevant C header files.\n\nThe argument `arg` is optional, and defaults to 0; it may be an int or a\nbuffer containing character data (most likely a string or an array).\n\nIf the argument is a mutable buffer (such as an array) and if the\nmutate_flag argument (which is only allowed in this case) is true then the\nbuffer is (in effect) passed to the operating system and changes made by\nthe OS will be reflected in the contents of the buffer after the call has\nreturned. The return value is the integer returned by the ioctl system\ncall.\n\nIf the argument is a mutable buffer and the mutable_flag argument is false,\nthe behavior is as if a string had been passed.\n\nIf the argument is an immutable buffer (most likely a string) then a copy\nof the buffer is passed to the operating system and the return value is a\nstring of the same length containing whatever the operating system put in\nthe buffer. The length of the arg buffer in this case is not allowed to\nexceed 1024 bytes.\n\nIf the arg given is an integer or if none is specified, the result value is\nan integer corresponding to the return value of the ioctl call in the C\ncode.", + "fcntl.lockf" => "A wrapper around the fcntl() locking calls.\n\n`fd` is the file descriptor of the file to lock or unlock, and operation is one\nof the following values:\n\n LOCK_UN - unlock\n LOCK_SH - acquire a shared lock\n LOCK_EX - acquire an exclusive lock\n\nWhen operation is LOCK_SH or LOCK_EX, it can also be bitwise ORed with\nLOCK_NB to avoid blocking on lock acquisition. If LOCK_NB is used and the\nlock cannot be acquired, an OSError will be raised and the exception will\nhave an errno attribute set to EACCES or EAGAIN (depending on the operating\nsystem -- for portability, check for either value).\n\n`len` is the number of bytes to lock, with the default meaning to lock to\nEOF. `start` is the byte offset, relative to `whence`, to that the lock\nstarts. `whence` is as with fileobj.seek(), specifically:\n\n 0 - relative to the start of the file (SEEK_SET)\n 1 - relative to the current buffer position (SEEK_CUR)\n 2 - relative to the end of the file (SEEK_END)", + "gc" => "This module provides access to the garbage collector for reference cycles.\n\nenable() -- Enable automatic garbage collection.\ndisable() -- Disable automatic garbage collection.\nisenabled() -- Returns true if automatic collection is enabled.\ncollect() -- Do a full collection right now.\nget_count() -- Return the current collection counts.\nget_stats() -- Return list of dictionaries containing per-generation stats.\nset_debug() -- Set debugging flags.\nget_debug() -- Get debugging flags.\nset_threshold() -- Set the collection thresholds.\nget_threshold() -- Return the current collection thresholds.\nget_objects() -- Return a list of all objects tracked by the collector.\nis_tracked() -- Returns true if a given object is tracked.\nis_finalized() -- Returns true if a given object has been already finalized.\nget_referrers() -- Return the list of objects that refer to an object.\nget_referents() -- Return the list of objects that an object refers to.\nfreeze() -- Freeze all tracked objects and ignore them for future collections.\nunfreeze() -- Unfreeze all objects in the permanent generation.\nget_freeze_count() -- Return the number of objects in the permanent generation.", + "gc.collect" => "Run the garbage collector.\n\nWith no arguments, run a full collection. The optional argument\nmay be an integer specifying which generation to collect. A ValueError\nis raised if the generation number is invalid.\n\nThe number of unreachable objects is returned.", + "gc.disable" => "Disable automatic garbage collection.", + "gc.enable" => "Enable automatic garbage collection.", + "gc.freeze" => "Freeze all current tracked objects and ignore them for future collections.\n\nThis can be used before a POSIX fork() call to make the gc copy-on-write friendly.\nNote: collection before a POSIX fork() call may free pages for future allocation\nwhich can cause copy-on-write.", + "gc.get_count" => "Return a three-tuple of the current collection counts.", + "gc.get_debug" => "Get the garbage collection debugging flags.", + "gc.get_freeze_count" => "Return the number of objects in the permanent generation.", + "gc.get_objects" => "Return a list of objects tracked by the collector (excluding the list returned).\n\n generation\n Generation to extract the objects from.\n\nIf generation is not None, return only the objects tracked by the collector\nthat are in that generation.", + "gc.get_referents" => "Return the list of objects that are directly referred to by 'objs'.", + "gc.get_referrers" => "Return the list of objects that directly refer to any of 'objs'.", + "gc.get_stats" => "Return a list of dictionaries containing per-generation statistics.", + "gc.get_threshold" => "Return the current collection thresholds.", + "gc.is_finalized" => "Returns true if the object has been already finalized by the GC.", + "gc.is_tracked" => "Returns true if the object is tracked by the garbage collector.\n\nSimple atomic objects will return false.", + "gc.isenabled" => "Returns true if automatic garbage collection is enabled.", + "gc.set_debug" => "Set the garbage collection debugging flags.\n\n flags\n An integer that can have the following bits turned on:\n DEBUG_STATS - Print statistics during collection.\n DEBUG_COLLECTABLE - Print collectable objects found.\n DEBUG_UNCOLLECTABLE - Print unreachable but uncollectable objects\n found.\n DEBUG_SAVEALL - Save objects to gc.garbage rather than freeing them.\n DEBUG_LEAK - Debug leaking programs (everything but STATS).\n\nDebugging information is written to sys.stderr.", + "gc.set_threshold" => "set_threshold(threshold0, [threshold1, [threshold2]])\nSet the collection thresholds (the collection frequency).\n\nSetting 'threshold0' to zero disables collection.", + "gc.unfreeze" => "Unfreeze all objects in the permanent generation.\n\nPut all objects in the permanent generation back into oldest generation.", + "grp" => "Access to the Unix group database.\n\nGroup entries are reported as 4-tuples containing the following fields\nfrom the group database, in order:\n\n gr_name - name of the group\n gr_passwd - group password (encrypted); often empty\n gr_gid - numeric ID of the group\n gr_mem - list of members\n\nThe gid is an integer, name and password are strings. (Note that most\nusers are not explicitly listed as members of the groups they are in\naccording to the password database. Check both databases to get\ncomplete membership information.)", + "grp.getgrall" => "Return a list of all available group entries, in arbitrary order.\n\nAn entry whose name starts with '+' or '-' represents an instruction\nto use YP/NIS and may not be accessible via getgrnam or getgrgid.", + "grp.getgrgid" => "Return the group database entry for the given numeric group ID.\n\nIf id is not valid, raise KeyError.", + "grp.getgrnam" => "Return the group database entry for the given group name.\n\nIf name is not valid, raise KeyError.", + "grp.struct_group" => "grp.struct_group: Results from getgr*() routines.\n\nThis object may be accessed either as a tuple of\n (gr_name,gr_passwd,gr_gid,gr_mem)\nor via the object attributes as named in the above tuple.", + "grp.struct_group.__add__" => "Return self+value.", + "grp.struct_group.__class_getitem__" => "See PEP 585", + "grp.struct_group.__contains__" => "Return bool(key in self).", + "grp.struct_group.__delattr__" => "Implement delattr(self, name).", + "grp.struct_group.__eq__" => "Return self==value.", + "grp.struct_group.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "grp.struct_group.__ge__" => "Return self>=value.", + "grp.struct_group.__getattribute__" => "Return getattr(self, name).", + "grp.struct_group.__getitem__" => "Return self[key].", + "grp.struct_group.__getstate__" => "Helper for pickle.", + "grp.struct_group.__gt__" => "Return self>value.", + "grp.struct_group.__hash__" => "Return hash(self).", + "grp.struct_group.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "grp.struct_group.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "grp.struct_group.__iter__" => "Implement iter(self).", + "grp.struct_group.__le__" => "Return self<=value.", + "grp.struct_group.__len__" => "Return len(self).", + "grp.struct_group.__lt__" => "Return self<value.", + "grp.struct_group.__mul__" => "Return self*value.", + "grp.struct_group.__ne__" => "Return self!=value.", + "grp.struct_group.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "grp.struct_group.__reduce_ex__" => "Helper for pickle.", + "grp.struct_group.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "grp.struct_group.__repr__" => "Return repr(self).", + "grp.struct_group.__rmul__" => "Return value*self.", + "grp.struct_group.__setattr__" => "Implement setattr(self, name, value).", + "grp.struct_group.__sizeof__" => "Size of object in memory, in bytes.", + "grp.struct_group.__str__" => "Return str(self).", + "grp.struct_group.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "grp.struct_group.count" => "Return number of occurrences of value.", + "grp.struct_group.gr_gid" => "group id", + "grp.struct_group.gr_mem" => "group members", + "grp.struct_group.gr_name" => "group name", + "grp.struct_group.gr_passwd" => "password", + "grp.struct_group.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "itertools" => "Functional tools for creating and using iterators.\n\nInfinite iterators:\ncount(start=0, step=1) --> start, start+step, start+2*step, ...\ncycle(p) --> p0, p1, ... plast, p0, p1, ...\nrepeat(elem [,n]) --> elem, elem, elem, ... endlessly or up to n times\n\nIterators terminating on the shortest input sequence:\naccumulate(p[, func]) --> p0, p0+p1, p0+p1+p2\nbatched(p, n) --> [p0, p1, ..., p_n-1], [p_n, p_n+1, ..., p_2n-1], ...\nchain(p, q, ...) --> p0, p1, ... plast, q0, q1, ...\nchain.from_iterable([p, q, ...]) --> p0, p1, ... plast, q0, q1, ...\ncompress(data, selectors) --> (d[0] if s[0]), (d[1] if s[1]), ...\ndropwhile(predicate, seq) --> seq[n], seq[n+1], starting when predicate fails\ngroupby(iterable[, keyfunc]) --> sub-iterators grouped by value of keyfunc(v)\nfilterfalse(predicate, seq) --> elements of seq where predicate(elem) is False\nislice(seq, [start,] stop [, step]) --> elements from\n seq[start:stop:step]\npairwise(s) --> (s[0],s[1]), (s[1],s[2]), (s[2], s[3]), ...\nstarmap(fun, seq) --> fun(*seq[0]), fun(*seq[1]), ...\ntee(it, n=2) --> (it1, it2 , ... itn) splits one iterator into n\ntakewhile(predicate, seq) --> seq[0], seq[1], until predicate fails\nzip_longest(p, q, ...) --> (p[0], q[0]), (p[1], q[1]), ...\n\nCombinatoric generators:\nproduct(p, q, ... [repeat=1]) --> cartesian product\npermutations(p[, r])\ncombinations(p, r)\ncombinations_with_replacement(p, r)", + "itertools._grouper.__delattr__" => "Implement delattr(self, name).", + "itertools._grouper.__eq__" => "Return self==value.", + "itertools._grouper.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools._grouper.__ge__" => "Return self>=value.", + "itertools._grouper.__getattribute__" => "Return getattr(self, name).", + "itertools._grouper.__getstate__" => "Helper for pickle.", + "itertools._grouper.__gt__" => "Return self>value.", + "itertools._grouper.__hash__" => "Return hash(self).", + "itertools._grouper.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools._grouper.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools._grouper.__iter__" => "Implement iter(self).", + "itertools._grouper.__le__" => "Return self<=value.", + "itertools._grouper.__lt__" => "Return self<value.", + "itertools._grouper.__ne__" => "Return self!=value.", + "itertools._grouper.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools._grouper.__next__" => "Implement next(self).", + "itertools._grouper.__reduce__" => "Return state information for pickling.", + "itertools._grouper.__reduce_ex__" => "Helper for pickle.", + "itertools._grouper.__repr__" => "Return repr(self).", + "itertools._grouper.__setattr__" => "Implement setattr(self, name, value).", + "itertools._grouper.__sizeof__" => "Size of object in memory, in bytes.", + "itertools._grouper.__str__" => "Return str(self).", + "itertools._grouper.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools._tee" => "Iterator wrapped to make it copyable.", + "itertools._tee.__copy__" => "Returns an independent iterator.", + "itertools._tee.__delattr__" => "Implement delattr(self, name).", + "itertools._tee.__eq__" => "Return self==value.", + "itertools._tee.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools._tee.__ge__" => "Return self>=value.", + "itertools._tee.__getattribute__" => "Return getattr(self, name).", + "itertools._tee.__getstate__" => "Helper for pickle.", + "itertools._tee.__gt__" => "Return self>value.", + "itertools._tee.__hash__" => "Return hash(self).", + "itertools._tee.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools._tee.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools._tee.__iter__" => "Implement iter(self).", + "itertools._tee.__le__" => "Return self<=value.", + "itertools._tee.__lt__" => "Return self<value.", + "itertools._tee.__ne__" => "Return self!=value.", + "itertools._tee.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools._tee.__next__" => "Implement next(self).", + "itertools._tee.__reduce__" => "Return state information for pickling.", + "itertools._tee.__reduce_ex__" => "Helper for pickle.", + "itertools._tee.__repr__" => "Return repr(self).", + "itertools._tee.__setattr__" => "Implement setattr(self, name, value).", + "itertools._tee.__setstate__" => "Set state information for unpickling.", + "itertools._tee.__sizeof__" => "Size of object in memory, in bytes.", + "itertools._tee.__str__" => "Return str(self).", + "itertools._tee.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools._tee_dataobject" => "teedataobject(iterable, values, next, /)\n--\n\nData container common to multiple tee objects.", + "itertools._tee_dataobject.__delattr__" => "Implement delattr(self, name).", + "itertools._tee_dataobject.__eq__" => "Return self==value.", + "itertools._tee_dataobject.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools._tee_dataobject.__ge__" => "Return self>=value.", + "itertools._tee_dataobject.__getattribute__" => "Return getattr(self, name).", + "itertools._tee_dataobject.__getstate__" => "Helper for pickle.", + "itertools._tee_dataobject.__gt__" => "Return self>value.", + "itertools._tee_dataobject.__hash__" => "Return hash(self).", + "itertools._tee_dataobject.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools._tee_dataobject.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools._tee_dataobject.__le__" => "Return self<=value.", + "itertools._tee_dataobject.__lt__" => "Return self<value.", + "itertools._tee_dataobject.__ne__" => "Return self!=value.", + "itertools._tee_dataobject.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools._tee_dataobject.__reduce__" => "Return state information for pickling.", + "itertools._tee_dataobject.__reduce_ex__" => "Helper for pickle.", + "itertools._tee_dataobject.__repr__" => "Return repr(self).", + "itertools._tee_dataobject.__setattr__" => "Implement setattr(self, name, value).", + "itertools._tee_dataobject.__sizeof__" => "Size of object in memory, in bytes.", + "itertools._tee_dataobject.__str__" => "Return str(self).", + "itertools._tee_dataobject.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.accumulate" => "Return series of accumulated sums (or other binary function results).", + "itertools.accumulate.__delattr__" => "Implement delattr(self, name).", + "itertools.accumulate.__eq__" => "Return self==value.", + "itertools.accumulate.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.accumulate.__ge__" => "Return self>=value.", + "itertools.accumulate.__getattribute__" => "Return getattr(self, name).", + "itertools.accumulate.__getstate__" => "Helper for pickle.", + "itertools.accumulate.__gt__" => "Return self>value.", + "itertools.accumulate.__hash__" => "Return hash(self).", + "itertools.accumulate.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.accumulate.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.accumulate.__iter__" => "Implement iter(self).", + "itertools.accumulate.__le__" => "Return self<=value.", + "itertools.accumulate.__lt__" => "Return self<value.", + "itertools.accumulate.__ne__" => "Return self!=value.", + "itertools.accumulate.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.accumulate.__next__" => "Implement next(self).", + "itertools.accumulate.__reduce__" => "Return state information for pickling.", + "itertools.accumulate.__reduce_ex__" => "Helper for pickle.", + "itertools.accumulate.__repr__" => "Return repr(self).", + "itertools.accumulate.__setattr__" => "Implement setattr(self, name, value).", + "itertools.accumulate.__setstate__" => "Set state information for unpickling.", + "itertools.accumulate.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.accumulate.__str__" => "Return str(self).", + "itertools.accumulate.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.batched" => "Batch data into tuples of length n. The last batch may be shorter than n.\n\nLoops over the input iterable and accumulates data into tuples\nup to size n. The input is consumed lazily, just enough to\nfill a batch. The result is yielded as soon as a batch is full\nor when the input iterable is exhausted.\n\n >>> for batch in batched('ABCDEFG', 3):\n ... print(batch)\n ...\n ('A', 'B', 'C')\n ('D', 'E', 'F')\n ('G',)\n\nIf \"strict\" is True, raises a ValueError if the final batch is shorter\nthan n.", + "itertools.batched.__delattr__" => "Implement delattr(self, name).", + "itertools.batched.__eq__" => "Return self==value.", + "itertools.batched.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.batched.__ge__" => "Return self>=value.", + "itertools.batched.__getattribute__" => "Return getattr(self, name).", + "itertools.batched.__getstate__" => "Helper for pickle.", + "itertools.batched.__gt__" => "Return self>value.", + "itertools.batched.__hash__" => "Return hash(self).", + "itertools.batched.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.batched.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.batched.__iter__" => "Implement iter(self).", + "itertools.batched.__le__" => "Return self<=value.", + "itertools.batched.__lt__" => "Return self<value.", + "itertools.batched.__ne__" => "Return self!=value.", + "itertools.batched.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.batched.__next__" => "Implement next(self).", + "itertools.batched.__reduce__" => "Helper for pickle.", + "itertools.batched.__reduce_ex__" => "Helper for pickle.", + "itertools.batched.__repr__" => "Return repr(self).", + "itertools.batched.__setattr__" => "Implement setattr(self, name, value).", + "itertools.batched.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.batched.__str__" => "Return str(self).", + "itertools.batched.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.chain" => "Return a chain object whose .__next__() method returns elements from the\nfirst iterable until it is exhausted, then elements from the next\niterable, until all of the iterables are exhausted.", + "itertools.chain.__class_getitem__" => "See PEP 585", + "itertools.chain.__delattr__" => "Implement delattr(self, name).", + "itertools.chain.__eq__" => "Return self==value.", + "itertools.chain.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.chain.__ge__" => "Return self>=value.", + "itertools.chain.__getattribute__" => "Return getattr(self, name).", + "itertools.chain.__getstate__" => "Helper for pickle.", + "itertools.chain.__gt__" => "Return self>value.", + "itertools.chain.__hash__" => "Return hash(self).", + "itertools.chain.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.chain.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.chain.__iter__" => "Implement iter(self).", + "itertools.chain.__le__" => "Return self<=value.", + "itertools.chain.__lt__" => "Return self<value.", + "itertools.chain.__ne__" => "Return self!=value.", + "itertools.chain.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.chain.__next__" => "Implement next(self).", + "itertools.chain.__reduce__" => "Return state information for pickling.", + "itertools.chain.__reduce_ex__" => "Helper for pickle.", + "itertools.chain.__repr__" => "Return repr(self).", + "itertools.chain.__setattr__" => "Implement setattr(self, name, value).", + "itertools.chain.__setstate__" => "Set state information for unpickling.", + "itertools.chain.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.chain.__str__" => "Return str(self).", + "itertools.chain.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.chain.from_iterable" => "Alternative chain() constructor taking a single iterable argument that evaluates lazily.", + "itertools.combinations" => "Return successive r-length combinations of elements in the iterable.\n\ncombinations(range(4), 3) --> (0,1,2), (0,1,3), (0,2,3), (1,2,3)", + "itertools.combinations.__delattr__" => "Implement delattr(self, name).", + "itertools.combinations.__eq__" => "Return self==value.", + "itertools.combinations.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.combinations.__ge__" => "Return self>=value.", + "itertools.combinations.__getattribute__" => "Return getattr(self, name).", + "itertools.combinations.__getstate__" => "Helper for pickle.", + "itertools.combinations.__gt__" => "Return self>value.", + "itertools.combinations.__hash__" => "Return hash(self).", + "itertools.combinations.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.combinations.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.combinations.__iter__" => "Implement iter(self).", + "itertools.combinations.__le__" => "Return self<=value.", + "itertools.combinations.__lt__" => "Return self<value.", + "itertools.combinations.__ne__" => "Return self!=value.", + "itertools.combinations.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.combinations.__next__" => "Implement next(self).", + "itertools.combinations.__reduce__" => "Return state information for pickling.", + "itertools.combinations.__reduce_ex__" => "Helper for pickle.", + "itertools.combinations.__repr__" => "Return repr(self).", + "itertools.combinations.__setattr__" => "Implement setattr(self, name, value).", + "itertools.combinations.__setstate__" => "Set state information for unpickling.", + "itertools.combinations.__sizeof__" => "Returns size in memory, in bytes.", + "itertools.combinations.__str__" => "Return str(self).", + "itertools.combinations.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.combinations_with_replacement" => "Return successive r-length combinations of elements in the iterable allowing individual elements to have successive repeats.\n\ncombinations_with_replacement('ABC', 2) --> ('A','A'), ('A','B'), ('A','C'), ('B','B'), ('B','C'), ('C','C')", + "itertools.combinations_with_replacement.__delattr__" => "Implement delattr(self, name).", + "itertools.combinations_with_replacement.__eq__" => "Return self==value.", + "itertools.combinations_with_replacement.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.combinations_with_replacement.__ge__" => "Return self>=value.", + "itertools.combinations_with_replacement.__getattribute__" => "Return getattr(self, name).", + "itertools.combinations_with_replacement.__getstate__" => "Helper for pickle.", + "itertools.combinations_with_replacement.__gt__" => "Return self>value.", + "itertools.combinations_with_replacement.__hash__" => "Return hash(self).", + "itertools.combinations_with_replacement.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.combinations_with_replacement.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.combinations_with_replacement.__iter__" => "Implement iter(self).", + "itertools.combinations_with_replacement.__le__" => "Return self<=value.", + "itertools.combinations_with_replacement.__lt__" => "Return self<value.", + "itertools.combinations_with_replacement.__ne__" => "Return self!=value.", + "itertools.combinations_with_replacement.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.combinations_with_replacement.__next__" => "Implement next(self).", + "itertools.combinations_with_replacement.__reduce__" => "Return state information for pickling.", + "itertools.combinations_with_replacement.__reduce_ex__" => "Helper for pickle.", + "itertools.combinations_with_replacement.__repr__" => "Return repr(self).", + "itertools.combinations_with_replacement.__setattr__" => "Implement setattr(self, name, value).", + "itertools.combinations_with_replacement.__setstate__" => "Set state information for unpickling.", + "itertools.combinations_with_replacement.__sizeof__" => "Returns size in memory, in bytes.", + "itertools.combinations_with_replacement.__str__" => "Return str(self).", + "itertools.combinations_with_replacement.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.compress" => "Return data elements corresponding to true selector elements.\n\nForms a shorter iterator from selected data elements using the selectors to\nchoose the data elements.", + "itertools.compress.__delattr__" => "Implement delattr(self, name).", + "itertools.compress.__eq__" => "Return self==value.", + "itertools.compress.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.compress.__ge__" => "Return self>=value.", + "itertools.compress.__getattribute__" => "Return getattr(self, name).", + "itertools.compress.__getstate__" => "Helper for pickle.", + "itertools.compress.__gt__" => "Return self>value.", + "itertools.compress.__hash__" => "Return hash(self).", + "itertools.compress.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.compress.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.compress.__iter__" => "Implement iter(self).", + "itertools.compress.__le__" => "Return self<=value.", + "itertools.compress.__lt__" => "Return self<value.", + "itertools.compress.__ne__" => "Return self!=value.", + "itertools.compress.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.compress.__next__" => "Implement next(self).", + "itertools.compress.__reduce__" => "Return state information for pickling.", + "itertools.compress.__reduce_ex__" => "Helper for pickle.", + "itertools.compress.__repr__" => "Return repr(self).", + "itertools.compress.__setattr__" => "Implement setattr(self, name, value).", + "itertools.compress.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.compress.__str__" => "Return str(self).", + "itertools.compress.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.count" => "Return a count object whose .__next__() method returns consecutive values.\n\nEquivalent to:\n def count(firstval=0, step=1):\n x = firstval\n while 1:\n yield x\n x += step", + "itertools.count.__delattr__" => "Implement delattr(self, name).", + "itertools.count.__eq__" => "Return self==value.", + "itertools.count.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.count.__ge__" => "Return self>=value.", + "itertools.count.__getattribute__" => "Return getattr(self, name).", + "itertools.count.__getstate__" => "Helper for pickle.", + "itertools.count.__gt__" => "Return self>value.", + "itertools.count.__hash__" => "Return hash(self).", + "itertools.count.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.count.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.count.__iter__" => "Implement iter(self).", + "itertools.count.__le__" => "Return self<=value.", + "itertools.count.__lt__" => "Return self<value.", + "itertools.count.__ne__" => "Return self!=value.", + "itertools.count.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.count.__next__" => "Implement next(self).", + "itertools.count.__reduce__" => "Return state information for pickling.", + "itertools.count.__reduce_ex__" => "Helper for pickle.", + "itertools.count.__repr__" => "Return repr(self).", + "itertools.count.__setattr__" => "Implement setattr(self, name, value).", + "itertools.count.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.count.__str__" => "Return str(self).", + "itertools.count.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.cycle" => "Return elements from the iterable until it is exhausted. Then repeat the sequence indefinitely.", + "itertools.cycle.__delattr__" => "Implement delattr(self, name).", + "itertools.cycle.__eq__" => "Return self==value.", + "itertools.cycle.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.cycle.__ge__" => "Return self>=value.", + "itertools.cycle.__getattribute__" => "Return getattr(self, name).", + "itertools.cycle.__getstate__" => "Helper for pickle.", + "itertools.cycle.__gt__" => "Return self>value.", + "itertools.cycle.__hash__" => "Return hash(self).", + "itertools.cycle.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.cycle.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.cycle.__iter__" => "Implement iter(self).", + "itertools.cycle.__le__" => "Return self<=value.", + "itertools.cycle.__lt__" => "Return self<value.", + "itertools.cycle.__ne__" => "Return self!=value.", + "itertools.cycle.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.cycle.__next__" => "Implement next(self).", + "itertools.cycle.__reduce__" => "Return state information for pickling.", + "itertools.cycle.__reduce_ex__" => "Helper for pickle.", + "itertools.cycle.__repr__" => "Return repr(self).", + "itertools.cycle.__setattr__" => "Implement setattr(self, name, value).", + "itertools.cycle.__setstate__" => "Set state information for unpickling.", + "itertools.cycle.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.cycle.__str__" => "Return str(self).", + "itertools.cycle.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.dropwhile" => "Drop items from the iterable while predicate(item) is true.\n\nAfterwards, return every element until the iterable is exhausted.", + "itertools.dropwhile.__delattr__" => "Implement delattr(self, name).", + "itertools.dropwhile.__eq__" => "Return self==value.", + "itertools.dropwhile.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.dropwhile.__ge__" => "Return self>=value.", + "itertools.dropwhile.__getattribute__" => "Return getattr(self, name).", + "itertools.dropwhile.__getstate__" => "Helper for pickle.", + "itertools.dropwhile.__gt__" => "Return self>value.", + "itertools.dropwhile.__hash__" => "Return hash(self).", + "itertools.dropwhile.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.dropwhile.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.dropwhile.__iter__" => "Implement iter(self).", + "itertools.dropwhile.__le__" => "Return self<=value.", + "itertools.dropwhile.__lt__" => "Return self<value.", + "itertools.dropwhile.__ne__" => "Return self!=value.", + "itertools.dropwhile.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.dropwhile.__next__" => "Implement next(self).", + "itertools.dropwhile.__reduce__" => "Return state information for pickling.", + "itertools.dropwhile.__reduce_ex__" => "Helper for pickle.", + "itertools.dropwhile.__repr__" => "Return repr(self).", + "itertools.dropwhile.__setattr__" => "Implement setattr(self, name, value).", + "itertools.dropwhile.__setstate__" => "Set state information for unpickling.", + "itertools.dropwhile.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.dropwhile.__str__" => "Return str(self).", + "itertools.dropwhile.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.filterfalse" => "Return those items of iterable for which function(item) is false.\n\nIf function is None, return the items that are false.", + "itertools.filterfalse.__delattr__" => "Implement delattr(self, name).", + "itertools.filterfalse.__eq__" => "Return self==value.", + "itertools.filterfalse.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.filterfalse.__ge__" => "Return self>=value.", + "itertools.filterfalse.__getattribute__" => "Return getattr(self, name).", + "itertools.filterfalse.__getstate__" => "Helper for pickle.", + "itertools.filterfalse.__gt__" => "Return self>value.", + "itertools.filterfalse.__hash__" => "Return hash(self).", + "itertools.filterfalse.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.filterfalse.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.filterfalse.__iter__" => "Implement iter(self).", + "itertools.filterfalse.__le__" => "Return self<=value.", + "itertools.filterfalse.__lt__" => "Return self<value.", + "itertools.filterfalse.__ne__" => "Return self!=value.", + "itertools.filterfalse.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.filterfalse.__next__" => "Implement next(self).", + "itertools.filterfalse.__reduce__" => "Return state information for pickling.", + "itertools.filterfalse.__reduce_ex__" => "Helper for pickle.", + "itertools.filterfalse.__repr__" => "Return repr(self).", + "itertools.filterfalse.__setattr__" => "Implement setattr(self, name, value).", + "itertools.filterfalse.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.filterfalse.__str__" => "Return str(self).", + "itertools.filterfalse.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.groupby" => "make an iterator that returns consecutive keys and groups from the iterable\n\niterable\n Elements to divide into groups according to the key function.\nkey\n A function for computing the group category for each element.\n If the key function is not specified or is None, the element itself\n is used for grouping.", + "itertools.groupby.__delattr__" => "Implement delattr(self, name).", + "itertools.groupby.__eq__" => "Return self==value.", + "itertools.groupby.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.groupby.__ge__" => "Return self>=value.", + "itertools.groupby.__getattribute__" => "Return getattr(self, name).", + "itertools.groupby.__getstate__" => "Helper for pickle.", + "itertools.groupby.__gt__" => "Return self>value.", + "itertools.groupby.__hash__" => "Return hash(self).", + "itertools.groupby.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.groupby.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.groupby.__iter__" => "Implement iter(self).", + "itertools.groupby.__le__" => "Return self<=value.", + "itertools.groupby.__lt__" => "Return self<value.", + "itertools.groupby.__ne__" => "Return self!=value.", + "itertools.groupby.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.groupby.__next__" => "Implement next(self).", + "itertools.groupby.__reduce__" => "Return state information for pickling.", + "itertools.groupby.__reduce_ex__" => "Helper for pickle.", + "itertools.groupby.__repr__" => "Return repr(self).", + "itertools.groupby.__setattr__" => "Implement setattr(self, name, value).", + "itertools.groupby.__setstate__" => "Set state information for unpickling.", + "itertools.groupby.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.groupby.__str__" => "Return str(self).", + "itertools.groupby.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.islice" => "islice(iterable, stop) --> islice object\nislice(iterable, start, stop[, step]) --> islice object\n\nReturn an iterator whose next() method returns selected values from an\niterable. If start is specified, will skip all preceding elements;\notherwise, start defaults to zero. Step defaults to one. If\nspecified as another value, step determines how many values are\nskipped between successive calls. Works like a slice() on a list\nbut returns an iterator.", + "itertools.islice.__delattr__" => "Implement delattr(self, name).", + "itertools.islice.__eq__" => "Return self==value.", + "itertools.islice.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.islice.__ge__" => "Return self>=value.", + "itertools.islice.__getattribute__" => "Return getattr(self, name).", + "itertools.islice.__getstate__" => "Helper for pickle.", + "itertools.islice.__gt__" => "Return self>value.", + "itertools.islice.__hash__" => "Return hash(self).", + "itertools.islice.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.islice.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.islice.__iter__" => "Implement iter(self).", + "itertools.islice.__le__" => "Return self<=value.", + "itertools.islice.__lt__" => "Return self<value.", + "itertools.islice.__ne__" => "Return self!=value.", + "itertools.islice.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.islice.__next__" => "Implement next(self).", + "itertools.islice.__reduce__" => "Return state information for pickling.", + "itertools.islice.__reduce_ex__" => "Helper for pickle.", + "itertools.islice.__repr__" => "Return repr(self).", + "itertools.islice.__setattr__" => "Implement setattr(self, name, value).", + "itertools.islice.__setstate__" => "Set state information for unpickling.", + "itertools.islice.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.islice.__str__" => "Return str(self).", + "itertools.islice.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.pairwise" => "Return an iterator of overlapping pairs taken from the input iterator.\n\ns -> (s0,s1), (s1,s2), (s2, s3), ...", + "itertools.pairwise.__delattr__" => "Implement delattr(self, name).", + "itertools.pairwise.__eq__" => "Return self==value.", + "itertools.pairwise.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.pairwise.__ge__" => "Return self>=value.", + "itertools.pairwise.__getattribute__" => "Return getattr(self, name).", + "itertools.pairwise.__getstate__" => "Helper for pickle.", + "itertools.pairwise.__gt__" => "Return self>value.", + "itertools.pairwise.__hash__" => "Return hash(self).", + "itertools.pairwise.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.pairwise.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.pairwise.__iter__" => "Implement iter(self).", + "itertools.pairwise.__le__" => "Return self<=value.", + "itertools.pairwise.__lt__" => "Return self<value.", + "itertools.pairwise.__ne__" => "Return self!=value.", + "itertools.pairwise.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.pairwise.__next__" => "Implement next(self).", + "itertools.pairwise.__reduce__" => "Helper for pickle.", + "itertools.pairwise.__reduce_ex__" => "Helper for pickle.", + "itertools.pairwise.__repr__" => "Return repr(self).", + "itertools.pairwise.__setattr__" => "Implement setattr(self, name, value).", + "itertools.pairwise.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.pairwise.__str__" => "Return str(self).", + "itertools.pairwise.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.permutations" => "Return successive r-length permutations of elements in the iterable.\n\npermutations(range(3), 2) --> (0,1), (0,2), (1,0), (1,2), (2,0), (2,1)", + "itertools.permutations.__delattr__" => "Implement delattr(self, name).", + "itertools.permutations.__eq__" => "Return self==value.", + "itertools.permutations.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.permutations.__ge__" => "Return self>=value.", + "itertools.permutations.__getattribute__" => "Return getattr(self, name).", + "itertools.permutations.__getstate__" => "Helper for pickle.", + "itertools.permutations.__gt__" => "Return self>value.", + "itertools.permutations.__hash__" => "Return hash(self).", + "itertools.permutations.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.permutations.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.permutations.__iter__" => "Implement iter(self).", + "itertools.permutations.__le__" => "Return self<=value.", + "itertools.permutations.__lt__" => "Return self<value.", + "itertools.permutations.__ne__" => "Return self!=value.", + "itertools.permutations.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.permutations.__next__" => "Implement next(self).", + "itertools.permutations.__reduce__" => "Return state information for pickling.", + "itertools.permutations.__reduce_ex__" => "Helper for pickle.", + "itertools.permutations.__repr__" => "Return repr(self).", + "itertools.permutations.__setattr__" => "Implement setattr(self, name, value).", + "itertools.permutations.__setstate__" => "Set state information for unpickling.", + "itertools.permutations.__sizeof__" => "Returns size in memory, in bytes.", + "itertools.permutations.__str__" => "Return str(self).", + "itertools.permutations.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.product" => "Cartesian product of input iterables. Equivalent to nested for-loops.\n\nFor example, product(A, B) returns the same as: ((x,y) for x in A for y in B).\nThe leftmost iterators are in the outermost for-loop, so the output tuples\ncycle in a manner similar to an odometer (with the rightmost element changing\non every iteration).\n\nTo compute the product of an iterable with itself, specify the number\nof repetitions with the optional repeat keyword argument. For example,\nproduct(A, repeat=4) means the same as product(A, A, A, A).\n\nproduct('ab', range(3)) --> ('a',0) ('a',1) ('a',2) ('b',0) ('b',1) ('b',2)\nproduct((0,1), (0,1), (0,1)) --> (0,0,0) (0,0,1) (0,1,0) (0,1,1) (1,0,0) ...", + "itertools.product.__delattr__" => "Implement delattr(self, name).", + "itertools.product.__eq__" => "Return self==value.", + "itertools.product.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.product.__ge__" => "Return self>=value.", + "itertools.product.__getattribute__" => "Return getattr(self, name).", + "itertools.product.__getstate__" => "Helper for pickle.", + "itertools.product.__gt__" => "Return self>value.", + "itertools.product.__hash__" => "Return hash(self).", + "itertools.product.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.product.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.product.__iter__" => "Implement iter(self).", + "itertools.product.__le__" => "Return self<=value.", + "itertools.product.__lt__" => "Return self<value.", + "itertools.product.__ne__" => "Return self!=value.", + "itertools.product.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.product.__next__" => "Implement next(self).", + "itertools.product.__reduce__" => "Return state information for pickling.", + "itertools.product.__reduce_ex__" => "Helper for pickle.", + "itertools.product.__repr__" => "Return repr(self).", + "itertools.product.__setattr__" => "Implement setattr(self, name, value).", + "itertools.product.__setstate__" => "Set state information for unpickling.", + "itertools.product.__sizeof__" => "Returns size in memory, in bytes.", + "itertools.product.__str__" => "Return str(self).", + "itertools.product.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.repeat" => "repeat(object [,times]) -> create an iterator which returns the object\nfor the specified number of times. If not specified, returns the object\nendlessly.", + "itertools.repeat.__delattr__" => "Implement delattr(self, name).", + "itertools.repeat.__eq__" => "Return self==value.", + "itertools.repeat.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.repeat.__ge__" => "Return self>=value.", + "itertools.repeat.__getattribute__" => "Return getattr(self, name).", + "itertools.repeat.__getstate__" => "Helper for pickle.", + "itertools.repeat.__gt__" => "Return self>value.", + "itertools.repeat.__hash__" => "Return hash(self).", + "itertools.repeat.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.repeat.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.repeat.__iter__" => "Implement iter(self).", + "itertools.repeat.__le__" => "Return self<=value.", + "itertools.repeat.__length_hint__" => "Private method returning an estimate of len(list(it)).", + "itertools.repeat.__lt__" => "Return self<value.", + "itertools.repeat.__ne__" => "Return self!=value.", + "itertools.repeat.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.repeat.__next__" => "Implement next(self).", + "itertools.repeat.__reduce__" => "Return state information for pickling.", + "itertools.repeat.__reduce_ex__" => "Helper for pickle.", + "itertools.repeat.__repr__" => "Return repr(self).", + "itertools.repeat.__setattr__" => "Implement setattr(self, name, value).", + "itertools.repeat.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.repeat.__str__" => "Return str(self).", + "itertools.repeat.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.starmap" => "Return an iterator whose values are returned from the function evaluated with an argument tuple taken from the given sequence.", + "itertools.starmap.__delattr__" => "Implement delattr(self, name).", + "itertools.starmap.__eq__" => "Return self==value.", + "itertools.starmap.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.starmap.__ge__" => "Return self>=value.", + "itertools.starmap.__getattribute__" => "Return getattr(self, name).", + "itertools.starmap.__getstate__" => "Helper for pickle.", + "itertools.starmap.__gt__" => "Return self>value.", + "itertools.starmap.__hash__" => "Return hash(self).", + "itertools.starmap.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.starmap.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.starmap.__iter__" => "Implement iter(self).", + "itertools.starmap.__le__" => "Return self<=value.", + "itertools.starmap.__lt__" => "Return self<value.", + "itertools.starmap.__ne__" => "Return self!=value.", + "itertools.starmap.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.starmap.__next__" => "Implement next(self).", + "itertools.starmap.__reduce__" => "Return state information for pickling.", + "itertools.starmap.__reduce_ex__" => "Helper for pickle.", + "itertools.starmap.__repr__" => "Return repr(self).", + "itertools.starmap.__setattr__" => "Implement setattr(self, name, value).", + "itertools.starmap.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.starmap.__str__" => "Return str(self).", + "itertools.starmap.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.takewhile" => "Return successive entries from an iterable as long as the predicate evaluates to true for each entry.", + "itertools.takewhile.__delattr__" => "Implement delattr(self, name).", + "itertools.takewhile.__eq__" => "Return self==value.", + "itertools.takewhile.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.takewhile.__ge__" => "Return self>=value.", + "itertools.takewhile.__getattribute__" => "Return getattr(self, name).", + "itertools.takewhile.__getstate__" => "Helper for pickle.", + "itertools.takewhile.__gt__" => "Return self>value.", + "itertools.takewhile.__hash__" => "Return hash(self).", + "itertools.takewhile.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.takewhile.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.takewhile.__iter__" => "Implement iter(self).", + "itertools.takewhile.__le__" => "Return self<=value.", + "itertools.takewhile.__lt__" => "Return self<value.", + "itertools.takewhile.__ne__" => "Return self!=value.", + "itertools.takewhile.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.takewhile.__next__" => "Implement next(self).", + "itertools.takewhile.__reduce__" => "Return state information for pickling.", + "itertools.takewhile.__reduce_ex__" => "Helper for pickle.", + "itertools.takewhile.__repr__" => "Return repr(self).", + "itertools.takewhile.__setattr__" => "Implement setattr(self, name, value).", + "itertools.takewhile.__setstate__" => "Set state information for unpickling.", + "itertools.takewhile.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.takewhile.__str__" => "Return str(self).", + "itertools.takewhile.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "itertools.tee" => "Returns a tuple of n independent iterators.", + "itertools.zip_longest" => "Return a zip_longest object whose .__next__() method returns a tuple where\nthe i-th element comes from the i-th iterable argument. The .__next__()\nmethod continues until the longest iterable in the argument sequence\nis exhausted and then it raises StopIteration. When the shorter iterables\nare exhausted, the fillvalue is substituted in their place. The fillvalue\ndefaults to None or can be specified by a keyword argument.", + "itertools.zip_longest.__delattr__" => "Implement delattr(self, name).", + "itertools.zip_longest.__eq__" => "Return self==value.", + "itertools.zip_longest.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "itertools.zip_longest.__ge__" => "Return self>=value.", + "itertools.zip_longest.__getattribute__" => "Return getattr(self, name).", + "itertools.zip_longest.__getstate__" => "Helper for pickle.", + "itertools.zip_longest.__gt__" => "Return self>value.", + "itertools.zip_longest.__hash__" => "Return hash(self).", + "itertools.zip_longest.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "itertools.zip_longest.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "itertools.zip_longest.__iter__" => "Implement iter(self).", + "itertools.zip_longest.__le__" => "Return self<=value.", + "itertools.zip_longest.__lt__" => "Return self<value.", + "itertools.zip_longest.__ne__" => "Return self!=value.", + "itertools.zip_longest.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "itertools.zip_longest.__next__" => "Implement next(self).", + "itertools.zip_longest.__reduce__" => "Return state information for pickling.", + "itertools.zip_longest.__reduce_ex__" => "Helper for pickle.", + "itertools.zip_longest.__repr__" => "Return repr(self).", + "itertools.zip_longest.__setattr__" => "Implement setattr(self, name, value).", + "itertools.zip_longest.__setstate__" => "Set state information for unpickling.", + "itertools.zip_longest.__sizeof__" => "Size of object in memory, in bytes.", + "itertools.zip_longest.__str__" => "Return str(self).", + "itertools.zip_longest.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "marshal" => "This module contains functions that can read and write Python values in\na binary format. The format is specific to Python, but independent of\nmachine architecture issues.\n\nNot all Python object types are supported; in general, only objects\nwhose value is independent from a particular invocation of Python can be\nwritten and read by this module. The following types are supported:\nNone, integers, floating-point numbers, strings, bytes, bytearrays,\ntuples, lists, sets, dictionaries, and code objects, where it\nshould be understood that tuples, lists and dictionaries are only\nsupported as long as the values contained therein are themselves\nsupported; and recursive lists and dictionaries should not be written\n(they will cause infinite loops).\n\nVariables:\n\nversion -- indicates the format that the module uses. Version 0 is the\n historical format, version 1 shares interned strings and version 2\n uses a binary format for floating-point numbers.\n Version 3 shares common object references (New in version 3.4).\n\nFunctions:\n\ndump() -- write value to a file\nload() -- read value from a file\ndumps() -- marshal value as a bytes object\nloads() -- read value from a bytes-like object", + "marshal.dump" => "Write the value on the open file.\n\n value\n Must be a supported type.\n file\n Must be a writeable binary file.\n version\n Indicates the data format that dump should use.\n allow_code\n Allow to write code objects.\n\nIf the value has (or contains an object that has) an unsupported type, a\nValueError exception is raised - but garbage data will also be written\nto the file. The object will not be properly read back by load().", + "marshal.dumps" => "Return the bytes object that would be written to a file by dump(value, file).\n\n value\n Must be a supported type.\n version\n Indicates the data format that dumps should use.\n allow_code\n Allow to write code objects.\n\nRaise a ValueError exception if value has (or contains an object that has) an\nunsupported type.", + "marshal.load" => "Read one value from the open file and return it.\n\n file\n Must be readable binary file.\n allow_code\n Allow to load code objects.\n\nIf no valid value is read (e.g. because the data has a different Python\nversion's incompatible marshal format), raise EOFError, ValueError or\nTypeError.\n\nNote: If an object containing an unsupported type was marshalled with\ndump(), load() will substitute None for the unmarshallable type.", + "marshal.loads" => "Convert the bytes-like object to a value.\n\n allow_code\n Allow to load code objects.\n\nIf no valid value is found, raise EOFError, ValueError or TypeError. Extra\nbytes in the input are ignored.", + "math" => "This module provides access to the mathematical functions\ndefined by the C standard.", + "math.acos" => "Return the arc cosine (measured in radians) of x.\n\nThe result is between 0 and pi.", + "math.acosh" => "Return the inverse hyperbolic cosine of x.", + "math.asin" => "Return the arc sine (measured in radians) of x.\n\nThe result is between -pi/2 and pi/2.", + "math.asinh" => "Return the inverse hyperbolic sine of x.", + "math.atan" => "Return the arc tangent (measured in radians) of x.\n\nThe result is between -pi/2 and pi/2.", + "math.atan2" => "Return the arc tangent (measured in radians) of y/x.\n\nUnlike atan(y/x), the signs of both x and y are considered.", + "math.atanh" => "Return the inverse hyperbolic tangent of x.", + "math.cbrt" => "Return the cube root of x.", + "math.ceil" => "Return the ceiling of x as an Integral.\n\nThis is the smallest integer >= x.", + "math.comb" => "Number of ways to choose k items from n items without repetition and without order.\n\nEvaluates to n! / (k! * (n - k)!) when k <= n and evaluates\nto zero when k > n.\n\nAlso called the binomial coefficient because it is equivalent\nto the coefficient of k-th term in polynomial expansion of the\nexpression (1 + x)**n.\n\nRaises TypeError if either of the arguments are not integers.\nRaises ValueError if either of the arguments are negative.", + "math.copysign" => "Return a float with the magnitude (absolute value) of x but the sign of y.\n\nOn platforms that support signed zeros, copysign(1.0, -0.0)\nreturns -1.0.", + "math.cos" => "Return the cosine of x (measured in radians).", + "math.cosh" => "Return the hyperbolic cosine of x.", + "math.degrees" => "Convert angle x from radians to degrees.", + "math.dist" => "Return the Euclidean distance between two points p and q.\n\nThe points should be specified as sequences (or iterables) of\ncoordinates. Both inputs must have the same dimension.\n\nRoughly equivalent to:\n sqrt(sum((px - qx) ** 2.0 for px, qx in zip(p, q)))", + "math.erf" => "Error function at x.", + "math.erfc" => "Complementary error function at x.", + "math.exp" => "Return e raised to the power of x.", + "math.exp2" => "Return 2 raised to the power of x.", + "math.expm1" => "Return exp(x)-1.\n\nThis function avoids the loss of precision involved in the direct evaluation of exp(x)-1 for small x.", + "math.fabs" => "Return the absolute value of the float x.", + "math.factorial" => "Find n!.", + "math.floor" => "Return the floor of x as an Integral.\n\nThis is the largest integer <= x.", + "math.fma" => "Fused multiply-add operation.\n\nCompute (x * y) + z with a single round.", + "math.fmod" => "Return fmod(x, y), according to platform C.\n\nx % y may differ.", + "math.frexp" => "Return the mantissa and exponent of x, as pair (m, e).\n\nm is a float and e is an int, such that x = m * 2.**e.\nIf x is 0, m and e are both 0. Else 0.5 <= abs(m) < 1.0.", + "math.fsum" => "Return an accurate floating-point sum of values in the iterable seq.\n\nAssumes IEEE-754 floating-point arithmetic.", + "math.gamma" => "Gamma function at x.", + "math.gcd" => "Greatest Common Divisor.", + "math.hypot" => "hypot(*coordinates) -> value\n\nMultidimensional Euclidean distance from the origin to a point.\n\nRoughly equivalent to:\n sqrt(sum(x**2 for x in coordinates))\n\nFor a two dimensional point (x, y), gives the hypotenuse\nusing the Pythagorean theorem: sqrt(x*x + y*y).\n\nFor example, the hypotenuse of a 3/4/5 right triangle is:\n\n >>> hypot(3.0, 4.0)\n 5.0", + "math.isclose" => "Determine whether two floating-point numbers are close in value.\n\n rel_tol\n maximum difference for being considered \"close\", relative to the\n magnitude of the input values\n abs_tol\n maximum difference for being considered \"close\", regardless of the\n magnitude of the input values\n\nReturn True if a is close in value to b, and False otherwise.\n\nFor the values to be considered close, the difference between them\nmust be smaller than at least one of the tolerances.\n\n-inf, inf and NaN behave similarly to the IEEE 754 Standard. That\nis, NaN is not close to anything, even itself. inf and -inf are\nonly close to themselves.", + "math.isfinite" => "Return True if x is neither an infinity nor a NaN, and False otherwise.", + "math.isinf" => "Return True if x is a positive or negative infinity, and False otherwise.", + "math.isnan" => "Return True if x is a NaN (not a number), and False otherwise.", + "math.isqrt" => "Return the integer part of the square root of the input.", + "math.lcm" => "Least Common Multiple.", + "math.ldexp" => "Return x * (2**i).\n\nThis is essentially the inverse of frexp().", + "math.lgamma" => "Natural logarithm of absolute value of Gamma function at x.", + "math.log" => "log(x, [base=math.e])\nReturn the logarithm of x to the given base.\n\nIf the base is not specified, returns the natural logarithm (base e) of x.", + "math.log10" => "Return the base 10 logarithm of x.", + "math.log1p" => "Return the natural logarithm of 1+x (base e).\n\nThe result is computed in a way which is accurate for x near zero.", + "math.log2" => "Return the base 2 logarithm of x.", + "math.modf" => "Return the fractional and integer parts of x.\n\nBoth results carry the sign of x and are floats.", + "math.nextafter" => "Return the floating-point value the given number of steps after x towards y.\n\nIf steps is not specified or is None, it defaults to 1.\n\nRaises a TypeError, if x or y is not a double, or if steps is not an integer.\nRaises ValueError if steps is negative.", + "math.perm" => "Number of ways to choose k items from n items without repetition and with order.\n\nEvaluates to n! / (n - k)! when k <= n and evaluates\nto zero when k > n.\n\nIf k is not specified or is None, then k defaults to n\nand the function returns n!.\n\nRaises TypeError if either of the arguments are not integers.\nRaises ValueError if either of the arguments are negative.", + "math.pow" => "Return x**y (x to the power of y).", + "math.prod" => "Calculate the product of all the elements in the input iterable.\n\nThe default start value for the product is 1.\n\nWhen the iterable is empty, return the start value. This function is\nintended specifically for use with numeric values and may reject\nnon-numeric types.", + "math.radians" => "Convert angle x from degrees to radians.", + "math.remainder" => "Difference between x and the closest integer multiple of y.\n\nReturn x - n*y where n*y is the closest integer multiple of y.\nIn the case where x is exactly halfway between two multiples of\ny, the nearest even value of n is used. The result is always exact.", + "math.sin" => "Return the sine of x (measured in radians).", + "math.sinh" => "Return the hyperbolic sine of x.", + "math.sqrt" => "Return the square root of x.", + "math.sumprod" => "Return the sum of products of values from two iterables p and q.\n\nRoughly equivalent to:\n\n sum(itertools.starmap(operator.mul, zip(p, q, strict=True)))\n\nFor float and mixed int/float inputs, the intermediate products\nand sums are computed with extended precision.", + "math.tan" => "Return the tangent of x (measured in radians).", + "math.tanh" => "Return the hyperbolic tangent of x.", + "math.trunc" => "Truncates the Real x to the nearest Integral toward 0.\n\nUses the __trunc__ magic method.", + "math.ulp" => "Return the value of the least significant bit of the float x.", + "mmap.mmap" => "Windows: mmap(fileno, length[, tagname[, access[, offset]]])\n\nMaps length bytes from the file specified by the file handle fileno,\nand returns a mmap object. If length is larger than the current size\nof the file, the file is extended to contain length bytes. If length\nis 0, the maximum length of the map is the current size of the file,\nexcept that if the file is empty Windows raises an exception (you cannot\ncreate an empty mapping on Windows).\n\nUnix: mmap(fileno, length[, flags[, prot[, access[, offset[, trackfd]]]]])\n\nMaps length bytes from the file specified by the file descriptor fileno,\nand returns a mmap object. If length is 0, the maximum length of the map\nwill be the current size of the file when mmap is called.\nflags specifies the nature of the mapping. MAP_PRIVATE creates a\nprivate copy-on-write mapping, so changes to the contents of the mmap\nobject will be private to this process, and MAP_SHARED creates a mapping\nthat's shared with all other processes mapping the same areas of the file.\nThe default value is MAP_SHARED.\n\nTo map anonymous memory, pass -1 as the fileno (both versions).", + "mmap.mmap.__buffer__" => "Return a buffer object that exposes the underlying memory of the object.", + "mmap.mmap.__delattr__" => "Implement delattr(self, name).", + "mmap.mmap.__delitem__" => "Delete self[key].", + "mmap.mmap.__eq__" => "Return self==value.", + "mmap.mmap.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "mmap.mmap.__ge__" => "Return self>=value.", + "mmap.mmap.__getattribute__" => "Return getattr(self, name).", + "mmap.mmap.__getitem__" => "Return self[key].", + "mmap.mmap.__getstate__" => "Helper for pickle.", + "mmap.mmap.__gt__" => "Return self>value.", + "mmap.mmap.__hash__" => "Return hash(self).", + "mmap.mmap.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "mmap.mmap.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "mmap.mmap.__le__" => "Return self<=value.", + "mmap.mmap.__len__" => "Return len(self).", + "mmap.mmap.__lt__" => "Return self<value.", + "mmap.mmap.__ne__" => "Return self!=value.", + "mmap.mmap.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "mmap.mmap.__reduce__" => "Helper for pickle.", + "mmap.mmap.__reduce_ex__" => "Helper for pickle.", + "mmap.mmap.__release_buffer__" => "Release the buffer object that exposes the underlying memory of the object.", + "mmap.mmap.__repr__" => "Return repr(self).", + "mmap.mmap.__setattr__" => "Implement setattr(self, name, value).", + "mmap.mmap.__setitem__" => "Set self[key] to value.", + "mmap.mmap.__sizeof__" => "Size of object in memory, in bytes.", + "mmap.mmap.__str__" => "Return str(self).", + "mmap.mmap.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "msvcrt.GetErrorMode" => "Wrapper around GetErrorMode.", + "msvcrt.SetErrorMode" => "Wrapper around SetErrorMode.", + "msvcrt.get_osfhandle" => "Return the file handle for the file descriptor fd.\n\nRaises OSError if fd is not recognized.", + "msvcrt.getch" => "Read a keypress and return the resulting character as a byte string.\n\nNothing is echoed to the console. This call will block if a keypress is\nnot already available, but will not wait for Enter to be pressed. If the\npressed key was a special function key, this will return '\\000' or\n'\\xe0'; the next call will return the keycode. The Control-C keypress\ncannot be read with this function.", + "msvcrt.getche" => "Similar to getch(), but the keypress will be echoed if possible.", + "msvcrt.getwch" => "Wide char variant of getch(), returning a Unicode value.", + "msvcrt.getwche" => "Wide char variant of getche(), returning a Unicode value.", + "msvcrt.heapmin" => "Minimize the malloc() heap.\n\nForce the malloc() heap to clean itself up and return unused blocks\nto the operating system. On failure, this raises OSError.", + "msvcrt.kbhit" => "Returns a nonzero value if a keypress is waiting to be read. Otherwise, return 0.", + "msvcrt.locking" => "Lock part of a file based on file descriptor fd from the C runtime.\n\nRaises OSError on failure. The locked region of the file extends from\nthe current file position for nbytes bytes, and may continue beyond\nthe end of the file. mode must be one of the LK_* constants listed\nbelow. Multiple regions in a file may be locked at the same time, but\nmay not overlap. Adjacent regions are not merged; they must be unlocked\nindividually.", + "msvcrt.open_osfhandle" => "Create a C runtime file descriptor from the file handle handle.\n\nThe flags parameter should be a bitwise OR of os.O_APPEND, os.O_RDONLY,\nand os.O_TEXT. The returned file descriptor may be used as a parameter\nto os.fdopen() to create a file object.", + "msvcrt.putch" => "Print the byte string char to the console without buffering.", + "msvcrt.putwch" => "Wide char variant of putch(), accepting a Unicode value.", + "msvcrt.setmode" => "Set the line-end translation mode for the file descriptor fd.\n\nTo set it to text mode, flags should be os.O_TEXT; for binary, it\nshould be os.O_BINARY.\n\nReturn value is the previous mode.", + "msvcrt.ungetch" => "Opposite of getch.\n\nCause the byte string char to be \"pushed back\" into the\nconsole buffer; it will be the next character read by\ngetch() or getche().", + "msvcrt.ungetwch" => "Wide char variant of ungetch(), accepting a Unicode value.", + "nt" => "This module provides access to operating system functionality that is\nstandardized by the C Standard and the POSIX standard (a thinly\ndisguised Unix interface). Refer to the library manual and\ncorresponding Unix manual entries for more information on calls.", + "nt.DirEntry.__class_getitem__" => "See PEP 585", + "nt.DirEntry.__delattr__" => "Implement delattr(self, name).", + "nt.DirEntry.__eq__" => "Return self==value.", + "nt.DirEntry.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "nt.DirEntry.__fspath__" => "Returns the path for the entry.", + "nt.DirEntry.__ge__" => "Return self>=value.", + "nt.DirEntry.__getattribute__" => "Return getattr(self, name).", + "nt.DirEntry.__getstate__" => "Helper for pickle.", + "nt.DirEntry.__gt__" => "Return self>value.", + "nt.DirEntry.__hash__" => "Return hash(self).", + "nt.DirEntry.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "nt.DirEntry.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "nt.DirEntry.__le__" => "Return self<=value.", + "nt.DirEntry.__lt__" => "Return self<value.", + "nt.DirEntry.__ne__" => "Return self!=value.", + "nt.DirEntry.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "nt.DirEntry.__reduce__" => "Helper for pickle.", + "nt.DirEntry.__reduce_ex__" => "Helper for pickle.", + "nt.DirEntry.__repr__" => "Return repr(self).", + "nt.DirEntry.__setattr__" => "Implement setattr(self, name, value).", + "nt.DirEntry.__sizeof__" => "Size of object in memory, in bytes.", + "nt.DirEntry.__str__" => "Return str(self).", + "nt.DirEntry.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "nt.DirEntry.inode" => "Return inode of the entry; cached per entry.", + "nt.DirEntry.is_dir" => "Return True if the entry is a directory; cached per entry.", + "nt.DirEntry.is_file" => "Return True if the entry is a file; cached per entry.", + "nt.DirEntry.is_junction" => "Return True if the entry is a junction; cached per entry.", + "nt.DirEntry.is_symlink" => "Return True if the entry is a symbolic link; cached per entry.", + "nt.DirEntry.name" => "the entry's base filename, relative to scandir() \"path\" argument", + "nt.DirEntry.path" => "the entry's full path name; equivalent to os.path.join(scandir_path, entry.name)", + "nt.DirEntry.stat" => "Return stat_result object for the entry; cached per entry.", + "nt._add_dll_directory" => "Add a path to the DLL search path.\n\nThis search path is used when resolving dependencies for imported\nextension modules (the module itself is resolved through sys.path),\nand also by ctypes.\n\nReturns an opaque value that may be passed to os.remove_dll_directory\nto remove this directory from the search path.", + "nt._exit" => "Exit to the system with specified status, without normal exit processing.", + "nt._findfirstfile" => "A function to get the real file name without accessing the file in Windows.", + "nt._getdiskusage" => "Return disk usage statistics about the given path as a (total, free) tuple.", + "nt._getfinalpathname" => "A helper function for samepath on windows.", + "nt._getvolumepathname" => "A helper function for ismount on Win32.", + "nt._inputhook" => "Calls PyOS_CallInputHook droppong the GIL first", + "nt._is_inputhook_installed" => "Checks if PyOS_CallInputHook is set", + "nt._path_exists" => "Test whether a path exists. Returns False for broken symbolic links.", + "nt._path_isdevdrive" => "Determines whether the specified path is on a Windows Dev Drive.", + "nt._path_isdir" => "Return true if the pathname refers to an existing directory.", + "nt._path_isfile" => "Test whether a path is a regular file", + "nt._path_isjunction" => "Test whether a path is a junction", + "nt._path_islink" => "Test whether a path is a symbolic link", + "nt._path_lexists" => "Test whether a path exists. Returns True for broken symbolic links.", + "nt._path_normpath" => "Normalize path, eliminating double slashes, etc.", + "nt._path_splitroot" => "Removes everything after the root on Win32.", + "nt._path_splitroot_ex" => "Split a pathname into drive, root and tail.\n\nThe tail contains anything after the root.", + "nt._remove_dll_directory" => "Removes a path from the DLL search path.\n\nThe parameter is an opaque value that was returned from\nos.add_dll_directory. You can only remove directories that you added\nyourself.", + "nt._supports_virtual_terminal" => "Checks if virtual terminal is supported in windows", + "nt.abort" => "Abort the interpreter immediately.\n\nThis function 'dumps core' or otherwise fails in the hardest way possible\non the hosting operating system. This function never returns.", + "nt.access" => "Use the real uid/gid to test for access to a path.\n\n path\n Path to be tested; can be string, bytes, or a path-like object.\n mode\n Operating-system mode bitfield. Can be F_OK to test existence,\n or the inclusive-OR of R_OK, W_OK, and X_OK.\n dir_fd\n If not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that\n directory.\n effective_ids\n If True, access will use the effective uid/gid instead of\n the real uid/gid.\n follow_symlinks\n If False, and the last element of the path is a symbolic link,\n access will examine the symbolic link itself instead of the file\n the link points to.\n\ndir_fd, effective_ids, and follow_symlinks may not be implemented\n on your platform. If they are unavailable, using them will raise a\n NotImplementedError.\n\nNote that most operations will use the effective uid/gid, therefore this\n routine can be used in a suid/sgid environment to test if the invoking user\n has the specified access to the path.", + "nt.chdir" => "Change the current working directory to the specified path.\n\npath may always be specified as a string.\nOn some platforms, path may also be specified as an open file descriptor.\nIf this functionality is unavailable, using it raises an exception.", + "nt.chmod" => "Change the access permissions of a file.\n\n path\n Path to be modified. May always be specified as a str, bytes, or a path-like object.\n On some platforms, path may also be specified as an open file descriptor.\n If this functionality is unavailable, using it raises an exception.\n mode\n Operating-system mode bitfield.\n Be careful when using number literals for *mode*. The conventional UNIX notation for\n numeric modes uses an octal base, which needs to be indicated with a ``0o`` prefix in\n Python.\n dir_fd\n If not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that\n directory.\n follow_symlinks\n If False, and the last element of the path is a symbolic link,\n chmod will modify the symbolic link itself instead of the file\n the link points to.\n\nIt is an error to use dir_fd or follow_symlinks when specifying path as\n an open file descriptor.\ndir_fd and follow_symlinks may not be implemented on your platform.\n If they are unavailable, using them will raise a NotImplementedError.", + "nt.close" => "Close a file descriptor.", + "nt.closerange" => "Closes all file descriptors in [fd_low, fd_high), ignoring errors.", + "nt.cpu_count" => "Return the number of logical CPUs in the system.\n\nReturn None if indeterminable.", + "nt.device_encoding" => "Return a string describing the encoding of a terminal's file descriptor.\n\nThe file descriptor must be attached to a terminal.\nIf the device is not a terminal, return None.", + "nt.dup" => "Return a duplicate of a file descriptor.", + "nt.dup2" => "Duplicate file descriptor.", + "nt.execv" => "Execute an executable path with arguments, replacing current process.\n\npath\n Path of executable file.\nargv\n Tuple or list of strings.", + "nt.execve" => "Execute an executable path with arguments, replacing current process.\n\npath\n Path of executable file.\nargv\n Tuple or list of strings.\nenv\n Dictionary of strings mapping to strings.", + "nt.fchmod" => "Change the access permissions of the file given by file descriptor fd.\n\n fd\n The file descriptor of the file to be modified.\n mode\n Operating-system mode bitfield.\n Be careful when using number literals for *mode*. The conventional UNIX notation for\n numeric modes uses an octal base, which needs to be indicated with a ``0o`` prefix in\n Python.\n\nEquivalent to os.chmod(fd, mode).", + "nt.fspath" => "Return the file system path representation of the object.\n\nIf the object is str or bytes, then allow it to pass through as-is. If the\nobject defines __fspath__(), then return the result of that method. All other\ntypes raise a TypeError.", + "nt.fstat" => "Perform a stat system call on the given file descriptor.\n\nLike stat(), but for an open file descriptor.\nEquivalent to os.stat(fd).", + "nt.fsync" => "Force write of fd to disk.", + "nt.ftruncate" => "Truncate a file, specified by file descriptor, to a specific length.", + "nt.get_blocking" => "Get the blocking mode of the file descriptor.\n\nReturn False if the O_NONBLOCK flag is set, True if the flag is cleared.", + "nt.get_handle_inheritable" => "Get the close-on-exe flag of the specified file descriptor.", + "nt.get_inheritable" => "Get the close-on-exe flag of the specified file descriptor.", + "nt.get_terminal_size" => "Return the size of the terminal window as (columns, lines).\n\nThe optional argument fd (default standard output) specifies\nwhich file descriptor should be queried.\n\nIf the file descriptor is not connected to a terminal, an OSError\nis thrown.\n\nThis function will only be defined if an implementation is\navailable for this system.\n\nshutil.get_terminal_size is the high-level function which should\nnormally be used, os.get_terminal_size is the low-level implementation.", + "nt.getcwd" => "Return a unicode string representing the current working directory.", + "nt.getcwdb" => "Return a bytes string representing the current working directory.", + "nt.getlogin" => "Return the actual login name.", + "nt.getpid" => "Return the current process id.", + "nt.getppid" => "Return the parent's process id.\n\nIf the parent process has already exited, Windows machines will still\nreturn its id; others systems will return the id of the 'init' process (1).", + "nt.isatty" => "Return True if the fd is connected to a terminal.\n\nReturn True if the file descriptor is an open file descriptor\nconnected to the slave end of a terminal.", + "nt.kill" => "Kill a process with a signal.", + "nt.lchmod" => "Change the access permissions of a file, without following symbolic links.\n\nIf path is a symlink, this affects the link itself rather than the target.\nEquivalent to chmod(path, mode, follow_symlinks=False).\"", + "nt.link" => "Create a hard link to a file.\n\nIf either src_dir_fd or dst_dir_fd is not None, it should be a file\n descriptor open to a directory, and the respective path string (src or dst)\n should be relative; the path will then be relative to that directory.\nIf follow_symlinks is False, and the last element of src is a symbolic\n link, link will create a link to the symbolic link itself instead of the\n file the link points to.\nsrc_dir_fd, dst_dir_fd, and follow_symlinks may not be implemented on your\n platform. If they are unavailable, using them will raise a\n NotImplementedError.", + "nt.listdir" => "Return a list containing the names of the files in the directory.\n\npath can be specified as either str, bytes, or a path-like object. If path is bytes,\n the filenames returned will also be bytes; in all other circumstances\n the filenames returned will be str.\nIf path is None, uses the path='.'.\nOn some platforms, path may also be specified as an open file descriptor;\\\n the file descriptor must refer to a directory.\n If this functionality is unavailable, using it raises NotImplementedError.\n\nThe list is in arbitrary order. It does not include the special\nentries '.' and '..' even if they are present in the directory.", + "nt.listdrives" => "Return a list containing the names of drives in the system.\n\nA drive name typically looks like 'C:\\\\'.", + "nt.listmounts" => "Return a list containing mount points for a particular volume.\n\n'volume' should be a GUID path as returned from os.listvolumes.", + "nt.listvolumes" => "Return a list containing the volumes in the system.\n\nVolumes are typically represented as a GUID path.", + "nt.lseek" => "Set the position of a file descriptor. Return the new position.\n\n fd\n An open file descriptor, as returned by os.open().\n position\n Position, interpreted relative to 'whence'.\n whence\n The relative position to seek from. Valid values are:\n - SEEK_SET: seek from the start of the file.\n - SEEK_CUR: seek from the current file position.\n - SEEK_END: seek from the end of the file.\n\nThe return value is the number of bytes relative to the beginning of the file.", + "nt.lstat" => "Perform a stat system call on the given path, without following symbolic links.\n\nLike stat(), but do not follow symbolic links.\nEquivalent to stat(path, follow_symlinks=False).", + "nt.mkdir" => "Create a directory.\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.\n\nThe mode argument is ignored on Windows. Where it is used, the current umask\nvalue is first masked out.", + "nt.open" => "Open a file for low level IO. Returns a file descriptor (integer).\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "nt.pipe" => "Create a pipe.\n\nReturns a tuple of two file descriptors:\n (read_fd, write_fd)", + "nt.putenv" => "Change or add an environment variable.", + "nt.read" => "Read from a file descriptor. Returns a bytes object.", + "nt.readlink" => "Return a string representing the path to which the symbolic link points.\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\nand path should be relative; path will then be relative to that directory.\n\ndir_fd may not be implemented on your platform. If it is unavailable,\nusing it will raise a NotImplementedError.", + "nt.remove" => "Remove a file (same as unlink()).\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "nt.rename" => "Rename a file or directory.\n\nIf either src_dir_fd or dst_dir_fd is not None, it should be a file\n descriptor open to a directory, and the respective path string (src or dst)\n should be relative; the path will then be relative to that directory.\nsrc_dir_fd and dst_dir_fd, may not be implemented on your platform.\n If they are unavailable, using them will raise a NotImplementedError.", + "nt.replace" => "Rename a file or directory, overwriting the destination.\n\nIf either src_dir_fd or dst_dir_fd is not None, it should be a file\n descriptor open to a directory, and the respective path string (src or dst)\n should be relative; the path will then be relative to that directory.\nsrc_dir_fd and dst_dir_fd, may not be implemented on your platform.\n If they are unavailable, using them will raise a NotImplementedError.", + "nt.rmdir" => "Remove a directory.\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "nt.scandir" => "Return an iterator of DirEntry objects for given path.\n\npath can be specified as either str, bytes, or a path-like object. If path\nis bytes, the names of yielded DirEntry objects will also be bytes; in\nall other circumstances they will be str.\n\nIf path is None, uses the path='.'.", + "nt.set_blocking" => "Set the blocking mode of the specified file descriptor.\n\nSet the O_NONBLOCK flag if blocking is False,\nclear the O_NONBLOCK flag otherwise.", + "nt.set_handle_inheritable" => "Set the inheritable flag of the specified handle.", + "nt.set_inheritable" => "Set the inheritable flag of the specified file descriptor.", + "nt.spawnv" => "Execute the program specified by path in a new process.\n\nmode\n Mode of process creation.\npath\n Path of executable file.\nargv\n Tuple or list of strings.", + "nt.spawnve" => "Execute the program specified by path in a new process.\n\nmode\n Mode of process creation.\npath\n Path of executable file.\nargv\n Tuple or list of strings.\nenv\n Dictionary of strings mapping to strings.", + "nt.startfile" => "Start a file with its associated application.\n\nWhen \"operation\" is not specified or \"open\", this acts like\ndouble-clicking the file in Explorer, or giving the file name as an\nargument to the DOS \"start\" command: the file is opened with whatever\napplication (if any) its extension is associated.\nWhen another \"operation\" is given, it specifies what should be done with\nthe file. A typical operation is \"print\".\n\n\"arguments\" is passed to the application, but should be omitted if the\nfile is a document.\n\n\"cwd\" is the working directory for the operation. If \"filepath\" is\nrelative, it will be resolved against this directory. This argument\nshould usually be an absolute path.\n\n\"show_cmd\" can be used to override the recommended visibility option.\nSee the Windows ShellExecute documentation for values.\n\nstartfile returns as soon as the associated application is launched.\nThere is no option to wait for the application to close, and no way\nto retrieve the application's exit status.\n\nThe filepath is relative to the current directory. If you want to use\nan absolute path, make sure the first character is not a slash (\"/\");\nthe underlying Win32 ShellExecute function doesn't work if it is.", + "nt.stat" => "Perform a stat system call on the given path.\n\n path\n Path to be examined; can be string, bytes, a path-like object or\n open-file-descriptor int.\n dir_fd\n If not None, it should be a file descriptor open to a directory,\n and path should be a relative string; path will then be relative to\n that directory.\n follow_symlinks\n If False, and the last element of the path is a symbolic link,\n stat will examine the symbolic link itself instead of the file\n the link points to.\n\ndir_fd and follow_symlinks may not be implemented\n on your platform. If they are unavailable, using them will raise a\n NotImplementedError.\n\nIt's an error to use dir_fd or follow_symlinks when specifying path as\n an open file descriptor.", + "nt.strerror" => "Translate an error code to a message string.", + "nt.symlink" => "Create a symbolic link pointing to src named dst.\n\ntarget_is_directory is required on Windows if the target is to be\n interpreted as a directory. (On Windows, symlink requires\n Windows 6.0 or greater, and raises a NotImplementedError otherwise.)\n target_is_directory is ignored on non-Windows platforms.\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "nt.system" => "Execute the command in a subshell.", + "nt.times" => "Return a collection containing process timing information.\n\nThe object returned behaves like a named tuple with these fields:\n (utime, stime, cutime, cstime, elapsed_time)\nAll fields are floating-point numbers.", + "nt.times_result" => "times_result: Result from os.times().\n\nThis object may be accessed either as a tuple of\n (user, system, children_user, children_system, elapsed),\nor via the attributes user, system, children_user, children_system,\nand elapsed.\n\nSee os.times for more information.", + "nt.times_result.__add__" => "Return self+value.", + "nt.times_result.__class_getitem__" => "See PEP 585", + "nt.times_result.__contains__" => "Return bool(key in self).", + "nt.times_result.__delattr__" => "Implement delattr(self, name).", + "nt.times_result.__eq__" => "Return self==value.", + "nt.times_result.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "nt.times_result.__ge__" => "Return self>=value.", + "nt.times_result.__getattribute__" => "Return getattr(self, name).", + "nt.times_result.__getitem__" => "Return self[key].", + "nt.times_result.__getstate__" => "Helper for pickle.", + "nt.times_result.__gt__" => "Return self>value.", + "nt.times_result.__hash__" => "Return hash(self).", + "nt.times_result.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "nt.times_result.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "nt.times_result.__iter__" => "Implement iter(self).", + "nt.times_result.__le__" => "Return self<=value.", + "nt.times_result.__len__" => "Return len(self).", + "nt.times_result.__lt__" => "Return self<value.", + "nt.times_result.__mul__" => "Return self*value.", + "nt.times_result.__ne__" => "Return self!=value.", + "nt.times_result.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "nt.times_result.__reduce_ex__" => "Helper for pickle.", + "nt.times_result.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "nt.times_result.__repr__" => "Return repr(self).", + "nt.times_result.__rmul__" => "Return value*self.", + "nt.times_result.__setattr__" => "Implement setattr(self, name, value).", + "nt.times_result.__sizeof__" => "Size of object in memory, in bytes.", + "nt.times_result.__str__" => "Return str(self).", + "nt.times_result.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "nt.times_result.children_system" => "system time of children", + "nt.times_result.children_user" => "user time of children", + "nt.times_result.count" => "Return number of occurrences of value.", + "nt.times_result.elapsed" => "elapsed time since an arbitrary point in the past", + "nt.times_result.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "nt.times_result.system" => "system time", + "nt.times_result.user" => "user time", + "nt.truncate" => "Truncate a file, specified by path, to a specific length.\n\nOn some platforms, path may also be specified as an open file descriptor.\n If this functionality is unavailable, using it raises an exception.", + "nt.umask" => "Set the current numeric umask and return the previous umask.", + "nt.uname_result" => "uname_result: Result from os.uname().\n\nThis object may be accessed either as a tuple of\n (sysname, nodename, release, version, machine),\nor via the attributes sysname, nodename, release, version, and machine.\n\nSee os.uname for more information.", + "nt.uname_result.__add__" => "Return self+value.", + "nt.uname_result.__class_getitem__" => "See PEP 585", + "nt.uname_result.__contains__" => "Return bool(key in self).", + "nt.uname_result.__delattr__" => "Implement delattr(self, name).", + "nt.uname_result.__eq__" => "Return self==value.", + "nt.uname_result.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "nt.uname_result.__ge__" => "Return self>=value.", + "nt.uname_result.__getattribute__" => "Return getattr(self, name).", + "nt.uname_result.__getitem__" => "Return self[key].", + "nt.uname_result.__getstate__" => "Helper for pickle.", + "nt.uname_result.__gt__" => "Return self>value.", + "nt.uname_result.__hash__" => "Return hash(self).", + "nt.uname_result.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "nt.uname_result.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "nt.uname_result.__iter__" => "Implement iter(self).", + "nt.uname_result.__le__" => "Return self<=value.", + "nt.uname_result.__len__" => "Return len(self).", + "nt.uname_result.__lt__" => "Return self<value.", + "nt.uname_result.__mul__" => "Return self*value.", + "nt.uname_result.__ne__" => "Return self!=value.", + "nt.uname_result.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "nt.uname_result.__reduce_ex__" => "Helper for pickle.", + "nt.uname_result.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "nt.uname_result.__repr__" => "Return repr(self).", + "nt.uname_result.__rmul__" => "Return value*self.", + "nt.uname_result.__setattr__" => "Implement setattr(self, name, value).", + "nt.uname_result.__sizeof__" => "Size of object in memory, in bytes.", + "nt.uname_result.__str__" => "Return str(self).", + "nt.uname_result.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "nt.uname_result.count" => "Return number of occurrences of value.", + "nt.uname_result.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "nt.uname_result.machine" => "hardware identifier", + "nt.uname_result.nodename" => "name of machine on network (implementation-defined)", + "nt.uname_result.release" => "operating system release", + "nt.uname_result.sysname" => "operating system name", + "nt.uname_result.version" => "operating system version", + "nt.unlink" => "Remove a file (same as remove()).\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "nt.unsetenv" => "Delete an environment variable.", + "nt.urandom" => "Return a bytes object containing random bytes suitable for cryptographic use.", + "nt.utime" => "Set the access and modified time of path.\n\npath may always be specified as a string.\nOn some platforms, path may also be specified as an open file descriptor.\n If this functionality is unavailable, using it raises an exception.\n\nIf times is not None, it must be a tuple (atime, mtime);\n atime and mtime should be expressed as float seconds since the epoch.\nIf ns is specified, it must be a tuple (atime_ns, mtime_ns);\n atime_ns and mtime_ns should be expressed as integer nanoseconds\n since the epoch.\nIf times is None and ns is unspecified, utime uses the current time.\nSpecifying tuples for both times and ns is an error.\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\nIf follow_symlinks is False, and the last element of the path is a symbolic\n link, utime will modify the symbolic link itself instead of the file the\n link points to.\nIt is an error to use dir_fd or follow_symlinks when specifying path\n as an open file descriptor.\ndir_fd and follow_symlinks may not be available on your platform.\n If they are unavailable, using them will raise a NotImplementedError.", + "nt.waitpid" => "Wait for completion of a given process.\n\nReturns a tuple of information regarding the process:\n (pid, status << 8)\n\nThe options argument is ignored on Windows.", + "nt.waitstatus_to_exitcode" => "Convert a wait status to an exit code.\n\nOn Unix:\n\n* If WIFEXITED(status) is true, return WEXITSTATUS(status).\n* If WIFSIGNALED(status) is true, return -WTERMSIG(status).\n* Otherwise, raise a ValueError.\n\nOn Windows, return status shifted right by 8 bits.\n\nOn Unix, if the process is being traced or if waitpid() was called with\nWUNTRACED option, the caller must first check if WIFSTOPPED(status) is true.\nThis function must not be called if WIFSTOPPED(status) is true.", + "nt.write" => "Write a bytes object to a file descriptor.", + "posix" => "This module provides access to operating system functionality that is\nstandardized by the C Standard and the POSIX standard (a thinly\ndisguised Unix interface). Refer to the library manual and\ncorresponding Unix manual entries for more information on calls.", + "posix.DirEntry.__class_getitem__" => "See PEP 585", + "posix.DirEntry.__delattr__" => "Implement delattr(self, name).", + "posix.DirEntry.__eq__" => "Return self==value.", + "posix.DirEntry.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "posix.DirEntry.__fspath__" => "Returns the path for the entry.", + "posix.DirEntry.__ge__" => "Return self>=value.", + "posix.DirEntry.__getattribute__" => "Return getattr(self, name).", + "posix.DirEntry.__getstate__" => "Helper for pickle.", + "posix.DirEntry.__gt__" => "Return self>value.", + "posix.DirEntry.__hash__" => "Return hash(self).", + "posix.DirEntry.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "posix.DirEntry.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "posix.DirEntry.__le__" => "Return self<=value.", + "posix.DirEntry.__lt__" => "Return self<value.", + "posix.DirEntry.__ne__" => "Return self!=value.", + "posix.DirEntry.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "posix.DirEntry.__reduce__" => "Helper for pickle.", + "posix.DirEntry.__reduce_ex__" => "Helper for pickle.", + "posix.DirEntry.__repr__" => "Return repr(self).", + "posix.DirEntry.__setattr__" => "Implement setattr(self, name, value).", + "posix.DirEntry.__sizeof__" => "Size of object in memory, in bytes.", + "posix.DirEntry.__str__" => "Return str(self).", + "posix.DirEntry.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "posix.DirEntry.inode" => "Return inode of the entry; cached per entry.", + "posix.DirEntry.is_dir" => "Return True if the entry is a directory; cached per entry.", + "posix.DirEntry.is_file" => "Return True if the entry is a file; cached per entry.", + "posix.DirEntry.is_junction" => "Return True if the entry is a junction; cached per entry.", + "posix.DirEntry.is_symlink" => "Return True if the entry is a symbolic link; cached per entry.", + "posix.DirEntry.name" => "the entry's base filename, relative to scandir() \"path\" argument", + "posix.DirEntry.path" => "the entry's full path name; equivalent to os.path.join(scandir_path, entry.name)", + "posix.DirEntry.stat" => "Return stat_result object for the entry; cached per entry.", + "posix.WCOREDUMP" => "Return True if the process returning status was dumped to a core file.", + "posix.WEXITSTATUS" => "Return the process return code from status.", + "posix.WIFCONTINUED" => "Return True if a particular process was continued from a job control stop.\n\nReturn True if the process returning status was continued from a\njob control stop.", + "posix.WIFEXITED" => "Return True if the process returning status exited via the exit() system call.", + "posix.WIFSIGNALED" => "Return True if the process returning status was terminated by a signal.", + "posix.WIFSTOPPED" => "Return True if the process returning status was stopped.", + "posix.WSTOPSIG" => "Return the signal that stopped the process that provided the status value.", + "posix.WTERMSIG" => "Return the signal that terminated the process that provided the status value.", + "posix._exit" => "Exit to the system with specified status, without normal exit processing.", + "posix._fcopyfile" => "Efficiently copy content or metadata of 2 regular file descriptors (macOS).", + "posix._inputhook" => "Calls PyOS_CallInputHook droppong the GIL first", + "posix._is_inputhook_installed" => "Checks if PyOS_CallInputHook is set", + "posix._path_normpath" => "Normalize path, eliminating double slashes, etc.", + "posix._path_splitroot_ex" => "Split a pathname into drive, root and tail.\n\nThe tail contains anything after the root.", + "posix.abort" => "Abort the interpreter immediately.\n\nThis function 'dumps core' or otherwise fails in the hardest way possible\non the hosting operating system. This function never returns.", + "posix.access" => "Use the real uid/gid to test for access to a path.\n\n path\n Path to be tested; can be string, bytes, or a path-like object.\n mode\n Operating-system mode bitfield. Can be F_OK to test existence,\n or the inclusive-OR of R_OK, W_OK, and X_OK.\n dir_fd\n If not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that\n directory.\n effective_ids\n If True, access will use the effective uid/gid instead of\n the real uid/gid.\n follow_symlinks\n If False, and the last element of the path is a symbolic link,\n access will examine the symbolic link itself instead of the file\n the link points to.\n\ndir_fd, effective_ids, and follow_symlinks may not be implemented\n on your platform. If they are unavailable, using them will raise a\n NotImplementedError.\n\nNote that most operations will use the effective uid/gid, therefore this\n routine can be used in a suid/sgid environment to test if the invoking user\n has the specified access to the path.", + "posix.chdir" => "Change the current working directory to the specified path.\n\npath may always be specified as a string.\nOn some platforms, path may also be specified as an open file descriptor.\nIf this functionality is unavailable, using it raises an exception.", + "posix.chflags" => "Set file flags.\n\nIf follow_symlinks is False, and the last element of the path is a symbolic\n link, chflags will change flags on the symbolic link itself instead of the\n file the link points to.\nfollow_symlinks may not be implemented on your platform. If it is\nunavailable, using it will raise a NotImplementedError.", + "posix.chmod" => "Change the access permissions of a file.\n\n path\n Path to be modified. May always be specified as a str, bytes, or a path-like object.\n On some platforms, path may also be specified as an open file descriptor.\n If this functionality is unavailable, using it raises an exception.\n mode\n Operating-system mode bitfield.\n Be careful when using number literals for *mode*. The conventional UNIX notation for\n numeric modes uses an octal base, which needs to be indicated with a ``0o`` prefix in\n Python.\n dir_fd\n If not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that\n directory.\n follow_symlinks\n If False, and the last element of the path is a symbolic link,\n chmod will modify the symbolic link itself instead of the file\n the link points to.\n\nIt is an error to use dir_fd or follow_symlinks when specifying path as\n an open file descriptor.\ndir_fd and follow_symlinks may not be implemented on your platform.\n If they are unavailable, using them will raise a NotImplementedError.", + "posix.chown" => "Change the owner and group id of path to the numeric uid and gid.\\\n\n path\n Path to be examined; can be string, bytes, a path-like object, or open-file-descriptor int.\n dir_fd\n If not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that\n directory.\n follow_symlinks\n If False, and the last element of the path is a symbolic link,\n stat will examine the symbolic link itself instead of the file\n the link points to.\n\npath may always be specified as a string.\nOn some platforms, path may also be specified as an open file descriptor.\n If this functionality is unavailable, using it raises an exception.\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\nIf follow_symlinks is False, and the last element of the path is a symbolic\n link, chown will modify the symbolic link itself instead of the file the\n link points to.\nIt is an error to use dir_fd or follow_symlinks when specifying path as\n an open file descriptor.\ndir_fd and follow_symlinks may not be implemented on your platform.\n If they are unavailable, using them will raise a NotImplementedError.", + "posix.chroot" => "Change root directory to path.", + "posix.close" => "Close a file descriptor.", + "posix.closerange" => "Closes all file descriptors in [fd_low, fd_high), ignoring errors.", + "posix.confstr" => "Return a string-valued system configuration variable.", + "posix.copy_file_range" => "Copy count bytes from one file descriptor to another.\n\n src\n Source file descriptor.\n dst\n Destination file descriptor.\n count\n Number of bytes to copy.\n offset_src\n Starting offset in src.\n offset_dst\n Starting offset in dst.\n\nIf offset_src is None, then src is read from the current position;\nrespectively for offset_dst.", + "posix.cpu_count" => "Return the number of logical CPUs in the system.\n\nReturn None if indeterminable.", + "posix.ctermid" => "Return the name of the controlling terminal for this process.", + "posix.device_encoding" => "Return a string describing the encoding of a terminal's file descriptor.\n\nThe file descriptor must be attached to a terminal.\nIf the device is not a terminal, return None.", + "posix.dup" => "Return a duplicate of a file descriptor.", + "posix.dup2" => "Duplicate file descriptor.", + "posix.eventfd" => "Creates and returns an event notification file descriptor.", + "posix.eventfd_read" => "Read eventfd value", + "posix.eventfd_write" => "Write eventfd value.", + "posix.execv" => "Execute an executable path with arguments, replacing current process.\n\npath\n Path of executable file.\nargv\n Tuple or list of strings.", + "posix.execve" => "Execute an executable path with arguments, replacing current process.\n\npath\n Path of executable file.\nargv\n Tuple or list of strings.\nenv\n Dictionary of strings mapping to strings.", + "posix.fchdir" => "Change to the directory of the given file descriptor.\n\nfd must be opened on a directory, not a file.\nEquivalent to os.chdir(fd).", + "posix.fchmod" => "Change the access permissions of the file given by file descriptor fd.\n\n fd\n The file descriptor of the file to be modified.\n mode\n Operating-system mode bitfield.\n Be careful when using number literals for *mode*. The conventional UNIX notation for\n numeric modes uses an octal base, which needs to be indicated with a ``0o`` prefix in\n Python.\n\nEquivalent to os.chmod(fd, mode).", + "posix.fchown" => "Change the owner and group id of the file specified by file descriptor.\n\nEquivalent to os.chown(fd, uid, gid).", + "posix.fdatasync" => "Force write of fd to disk without forcing update of metadata.", + "posix.fork" => "Fork a child process.\n\nReturn 0 to child process and PID of child to parent process.", + "posix.forkpty" => "Fork a new process with a new pseudo-terminal as controlling tty.\n\nReturns a tuple of (pid, master_fd).\nLike fork(), return pid of 0 to the child process,\nand pid of child to the parent process.\nTo both, return fd of newly opened pseudo-terminal.", + "posix.fpathconf" => "Return the configuration limit name for the file descriptor fd.\n\nIf there is no limit, return -1.", + "posix.fspath" => "Return the file system path representation of the object.\n\nIf the object is str or bytes, then allow it to pass through as-is. If the\nobject defines __fspath__(), then return the result of that method. All other\ntypes raise a TypeError.", + "posix.fstat" => "Perform a stat system call on the given file descriptor.\n\nLike stat(), but for an open file descriptor.\nEquivalent to os.stat(fd).", + "posix.fstatvfs" => "Perform an fstatvfs system call on the given fd.\n\nEquivalent to statvfs(fd).", + "posix.fsync" => "Force write of fd to disk.", + "posix.ftruncate" => "Truncate a file, specified by file descriptor, to a specific length.", + "posix.get_blocking" => "Get the blocking mode of the file descriptor.\n\nReturn False if the O_NONBLOCK flag is set, True if the flag is cleared.", + "posix.get_inheritable" => "Get the close-on-exe flag of the specified file descriptor.", + "posix.get_terminal_size" => "Return the size of the terminal window as (columns, lines).\n\nThe optional argument fd (default standard output) specifies\nwhich file descriptor should be queried.\n\nIf the file descriptor is not connected to a terminal, an OSError\nis thrown.\n\nThis function will only be defined if an implementation is\navailable for this system.\n\nshutil.get_terminal_size is the high-level function which should\nnormally be used, os.get_terminal_size is the low-level implementation.", + "posix.getcwd" => "Return a unicode string representing the current working directory.", + "posix.getcwdb" => "Return a bytes string representing the current working directory.", + "posix.getegid" => "Return the current process's effective group id.", + "posix.geteuid" => "Return the current process's effective user id.", + "posix.getgid" => "Return the current process's group id.", + "posix.getgrouplist" => "Returns a list of groups to which a user belongs.\n\nuser\n username to lookup\ngroup\n base group id of the user", + "posix.getgroups" => "Return list of supplemental group IDs for the process.", + "posix.getloadavg" => "Return average recent system load information.\n\nReturn the number of processes in the system run queue averaged over\nthe last 1, 5, and 15 minutes as a tuple of three floats.\nRaises OSError if the load average was unobtainable.", + "posix.getlogin" => "Return the actual login name.", + "posix.getpgid" => "Call the system call getpgid(), and return the result.", + "posix.getpgrp" => "Return the current process group id.", + "posix.getpid" => "Return the current process id.", + "posix.getppid" => "Return the parent's process id.\n\nIf the parent process has already exited, Windows machines will still\nreturn its id; others systems will return the id of the 'init' process (1).", + "posix.getpriority" => "Return program scheduling priority.", + "posix.getrandom" => "Obtain a series of random bytes.", + "posix.getresgid" => "Return a tuple of the current process's real, effective, and saved group ids.", + "posix.getresuid" => "Return a tuple of the current process's real, effective, and saved user ids.", + "posix.getsid" => "Call the system call getsid(pid) and return the result.", + "posix.getuid" => "Return the current process's user id.", + "posix.getxattr" => "Return the value of extended attribute attribute on path.\n\npath may be either a string, a path-like object, or an open file descriptor.\nIf follow_symlinks is False, and the last element of the path is a symbolic\n link, getxattr will examine the symbolic link itself instead of the file\n the link points to.", + "posix.grantpt" => "Grant access to the slave pseudo-terminal device.\n\n fd\n File descriptor of a master pseudo-terminal device.\n\nPerforms a grantpt() C function call.", + "posix.initgroups" => "Initialize the group access list.\n\nCall the system initgroups() to initialize the group access list with all of\nthe groups of which the specified username is a member, plus the specified\ngroup id.", + "posix.isatty" => "Return True if the fd is connected to a terminal.\n\nReturn True if the file descriptor is an open file descriptor\nconnected to the slave end of a terminal.", + "posix.kill" => "Kill a process with a signal.", + "posix.killpg" => "Kill a process group with a signal.", + "posix.lchflags" => "Set file flags.\n\nThis function will not follow symbolic links.\nEquivalent to chflags(path, flags, follow_symlinks=False).", + "posix.lchmod" => "Change the access permissions of a file, without following symbolic links.\n\nIf path is a symlink, this affects the link itself rather than the target.\nEquivalent to chmod(path, mode, follow_symlinks=False).\"", + "posix.lchown" => "Change the owner and group id of path to the numeric uid and gid.\n\nThis function will not follow symbolic links.\nEquivalent to os.chown(path, uid, gid, follow_symlinks=False).", + "posix.link" => "Create a hard link to a file.\n\nIf either src_dir_fd or dst_dir_fd is not None, it should be a file\n descriptor open to a directory, and the respective path string (src or dst)\n should be relative; the path will then be relative to that directory.\nIf follow_symlinks is False, and the last element of src is a symbolic\n link, link will create a link to the symbolic link itself instead of the\n file the link points to.\nsrc_dir_fd, dst_dir_fd, and follow_symlinks may not be implemented on your\n platform. If they are unavailable, using them will raise a\n NotImplementedError.", + "posix.listdir" => "Return a list containing the names of the files in the directory.\n\npath can be specified as either str, bytes, or a path-like object. If path is bytes,\n the filenames returned will also be bytes; in all other circumstances\n the filenames returned will be str.\nIf path is None, uses the path='.'.\nOn some platforms, path may also be specified as an open file descriptor;\\\n the file descriptor must refer to a directory.\n If this functionality is unavailable, using it raises NotImplementedError.\n\nThe list is in arbitrary order. It does not include the special\nentries '.' and '..' even if they are present in the directory.", + "posix.listxattr" => "Return a list of extended attributes on path.\n\npath may be either None, a string, a path-like object, or an open file descriptor.\nif path is None, listxattr will examine the current directory.\nIf follow_symlinks is False, and the last element of the path is a symbolic\n link, listxattr will examine the symbolic link itself instead of the file\n the link points to.", + "posix.lockf" => "Apply, test or remove a POSIX lock on an open file descriptor.\n\nfd\n An open file descriptor.\ncommand\n One of F_LOCK, F_TLOCK, F_ULOCK or F_TEST.\nlength\n The number of bytes to lock, starting at the current position.", + "posix.login_tty" => "Prepare the tty of which fd is a file descriptor for a new login session.\n\nMake the calling process a session leader; make the tty the\ncontrolling tty, the stdin, the stdout, and the stderr of the\ncalling process; close fd.", + "posix.lseek" => "Set the position of a file descriptor. Return the new position.\n\n fd\n An open file descriptor, as returned by os.open().\n position\n Position, interpreted relative to 'whence'.\n whence\n The relative position to seek from. Valid values are:\n - SEEK_SET: seek from the start of the file.\n - SEEK_CUR: seek from the current file position.\n - SEEK_END: seek from the end of the file.\n\nThe return value is the number of bytes relative to the beginning of the file.", + "posix.lstat" => "Perform a stat system call on the given path, without following symbolic links.\n\nLike stat(), but do not follow symbolic links.\nEquivalent to stat(path, follow_symlinks=False).", + "posix.major" => "Extracts a device major number from a raw device number.", + "posix.makedev" => "Composes a raw device number from the major and minor device numbers.", + "posix.minor" => "Extracts a device minor number from a raw device number.", + "posix.mkdir" => "Create a directory.\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.\n\nThe mode argument is ignored on Windows. Where it is used, the current umask\nvalue is first masked out.", + "posix.mkfifo" => "Create a \"fifo\" (a POSIX named pipe).\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "posix.mknod" => "Create a node in the file system.\n\nCreate a node in the file system (file, device special file or named pipe)\nat path. mode specifies both the permissions to use and the\ntype of node to be created, being combined (bitwise OR) with one of\nS_IFREG, S_IFCHR, S_IFBLK, and S_IFIFO. If S_IFCHR or S_IFBLK is set on mode,\ndevice defines the newly created device special file (probably using\nos.makedev()). Otherwise device is ignored.\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "posix.nice" => "Add increment to the priority of process and return the new priority.", + "posix.open" => "Open a file for low level IO. Returns a file descriptor (integer).\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "posix.openpty" => "Open a pseudo-terminal.\n\nReturn a tuple of (master_fd, slave_fd) containing open file descriptors\nfor both the master and slave ends.", + "posix.pathconf" => "Return the configuration limit name for the file or directory path.\n\nIf there is no limit, return -1.\nOn some platforms, path may also be specified as an open file descriptor.\n If this functionality is unavailable, using it raises an exception.", + "posix.pidfd_open" => "Return a file descriptor referring to the process *pid*.\n\nThe descriptor can be used to perform process management without races and\nsignals.", + "posix.pipe" => "Create a pipe.\n\nReturns a tuple of two file descriptors:\n (read_fd, write_fd)", + "posix.pipe2" => "Create a pipe with flags set atomically.\n\nReturns a tuple of two file descriptors:\n (read_fd, write_fd)\n\nflags can be constructed by ORing together one or more of these values:\nO_NONBLOCK, O_CLOEXEC.", + "posix.posix_fadvise" => "Announce an intention to access data in a specific pattern.\n\nAnnounce an intention to access data in a specific pattern, thus allowing\nthe kernel to make optimizations.\nThe advice applies to the region of the file specified by fd starting at\noffset and continuing for length bytes.\nadvice is one of POSIX_FADV_NORMAL, POSIX_FADV_SEQUENTIAL,\nPOSIX_FADV_RANDOM, POSIX_FADV_NOREUSE, POSIX_FADV_WILLNEED, or\nPOSIX_FADV_DONTNEED.", + "posix.posix_fallocate" => "Ensure a file has allocated at least a particular number of bytes on disk.\n\nEnsure that the file specified by fd encompasses a range of bytes\nstarting at offset bytes from the beginning and continuing for length bytes.", + "posix.posix_openpt" => "Open and return a file descriptor for a master pseudo-terminal device.\n\nPerforms a posix_openpt() C function call. The oflag argument is used to\nset file status flags and file access modes as specified in the manual page\nof posix_openpt() of your system.", + "posix.posix_spawn" => "Execute the program specified by path in a new process.\n\npath\n Path of executable file.\nargv\n Tuple or list of strings.\nenv\n Dictionary of strings mapping to strings.\nfile_actions\n A sequence of file action tuples.\nsetpgroup\n The pgroup to use with the POSIX_SPAWN_SETPGROUP flag.\nresetids\n If the value is `true` the POSIX_SPAWN_RESETIDS will be activated.\nsetsid\n If the value is `true` the POSIX_SPAWN_SETSID or POSIX_SPAWN_SETSID_NP will be activated.\nsetsigmask\n The sigmask to use with the POSIX_SPAWN_SETSIGMASK flag.\nsetsigdef\n The sigmask to use with the POSIX_SPAWN_SETSIGDEF flag.\nscheduler\n A tuple with the scheduler policy (optional) and parameters.", + "posix.posix_spawnp" => "Execute the program specified by path in a new process.\n\npath\n Path of executable file.\nargv\n Tuple or list of strings.\nenv\n Dictionary of strings mapping to strings.\nfile_actions\n A sequence of file action tuples.\nsetpgroup\n The pgroup to use with the POSIX_SPAWN_SETPGROUP flag.\nresetids\n If the value is `True` the POSIX_SPAWN_RESETIDS will be activated.\nsetsid\n If the value is `True` the POSIX_SPAWN_SETSID or POSIX_SPAWN_SETSID_NP will be activated.\nsetsigmask\n The sigmask to use with the POSIX_SPAWN_SETSIGMASK flag.\nsetsigdef\n The sigmask to use with the POSIX_SPAWN_SETSIGDEF flag.\nscheduler\n A tuple with the scheduler policy (optional) and parameters.", + "posix.pread" => "Read a number of bytes from a file descriptor starting at a particular offset.\n\nRead length bytes from file descriptor fd, starting at offset bytes from\nthe beginning of the file. The file offset remains unchanged.", + "posix.preadv" => "Reads from a file descriptor into a number of mutable bytes-like objects.\n\nCombines the functionality of readv() and pread(). As readv(), it will\ntransfer data into each buffer until it is full and then move on to the next\nbuffer in the sequence to hold the rest of the data. Its fourth argument,\nspecifies the file offset at which the input operation is to be performed. It\nwill return the total number of bytes read (which can be less than the total\ncapacity of all the objects).\n\nThe flags argument contains a bitwise OR of zero or more of the following flags:\n\n- RWF_HIPRI\n- RWF_NOWAIT\n\nUsing non-zero flags requires Linux 4.6 or newer.", + "posix.ptsname" => "Return the name of the slave pseudo-terminal device.\n\n fd\n File descriptor of a master pseudo-terminal device.\n\nIf the ptsname_r() C function is available, it is called;\notherwise, performs a ptsname() C function call.", + "posix.putenv" => "Change or add an environment variable.", + "posix.pwrite" => "Write bytes to a file descriptor starting at a particular offset.\n\nWrite buffer to fd, starting at offset bytes from the beginning of\nthe file. Returns the number of bytes written. Does not change the\ncurrent file offset.", + "posix.pwritev" => "Writes the contents of bytes-like objects to a file descriptor at a given offset.\n\nCombines the functionality of writev() and pwrite(). All buffers must be a sequence\nof bytes-like objects. Buffers are processed in array order. Entire contents of first\nbuffer is written before proceeding to second, and so on. The operating system may\nset a limit (sysconf() value SC_IOV_MAX) on the number of buffers that can be used.\nThis function writes the contents of each object to the file descriptor and returns\nthe total number of bytes written.\n\nThe flags argument contains a bitwise OR of zero or more of the following flags:\n\n- RWF_DSYNC\n- RWF_SYNC\n- RWF_APPEND\n\nUsing non-zero flags requires Linux 4.7 or newer.", + "posix.read" => "Read from a file descriptor. Returns a bytes object.", + "posix.readlink" => "Return a string representing the path to which the symbolic link points.\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\nand path should be relative; path will then be relative to that directory.\n\ndir_fd may not be implemented on your platform. If it is unavailable,\nusing it will raise a NotImplementedError.", + "posix.readv" => "Read from a file descriptor fd into an iterable of buffers.\n\nThe buffers should be mutable buffers accepting bytes.\nreadv will transfer data into each buffer until it is full\nand then move on to the next buffer in the sequence to hold\nthe rest of the data.\n\nreadv returns the total number of bytes read,\nwhich may be less than the total capacity of all the buffers.", + "posix.register_at_fork" => "Register callables to be called when forking a new process.\n\n before\n A callable to be called in the parent before the fork() syscall.\n after_in_child\n A callable to be called in the child after fork().\n after_in_parent\n A callable to be called in the parent after fork().\n\n'before' callbacks are called in reverse order.\n'after_in_child' and 'after_in_parent' callbacks are called in order.", + "posix.remove" => "Remove a file (same as unlink()).\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "posix.removexattr" => "Remove extended attribute attribute on path.\n\npath may be either a string, a path-like object, or an open file descriptor.\nIf follow_symlinks is False, and the last element of the path is a symbolic\n link, removexattr will modify the symbolic link itself instead of the file\n the link points to.", + "posix.rename" => "Rename a file or directory.\n\nIf either src_dir_fd or dst_dir_fd is not None, it should be a file\n descriptor open to a directory, and the respective path string (src or dst)\n should be relative; the path will then be relative to that directory.\nsrc_dir_fd and dst_dir_fd, may not be implemented on your platform.\n If they are unavailable, using them will raise a NotImplementedError.", + "posix.replace" => "Rename a file or directory, overwriting the destination.\n\nIf either src_dir_fd or dst_dir_fd is not None, it should be a file\n descriptor open to a directory, and the respective path string (src or dst)\n should be relative; the path will then be relative to that directory.\nsrc_dir_fd and dst_dir_fd, may not be implemented on your platform.\n If they are unavailable, using them will raise a NotImplementedError.", + "posix.rmdir" => "Remove a directory.\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "posix.scandir" => "Return an iterator of DirEntry objects for given path.\n\npath can be specified as either str, bytes, or a path-like object. If path\nis bytes, the names of yielded DirEntry objects will also be bytes; in\nall other circumstances they will be str.\n\nIf path is None, uses the path='.'.", + "posix.sched_get_priority_max" => "Get the maximum scheduling priority for policy.", + "posix.sched_get_priority_min" => "Get the minimum scheduling priority for policy.", + "posix.sched_getaffinity" => "Return the affinity of the process identified by pid (or the current process if zero).\n\nThe affinity is returned as a set of CPU identifiers.", + "posix.sched_getparam" => "Returns scheduling parameters for the process identified by pid.\n\nIf pid is 0, returns parameters for the calling process.\nReturn value is an instance of sched_param.", + "posix.sched_getscheduler" => "Get the scheduling policy for the process identified by pid.\n\nPassing 0 for pid returns the scheduling policy for the calling process.", + "posix.sched_param" => "Currently has only one field: sched_priority\n\nsched_priority\n A scheduling parameter.", + "posix.sched_param.__add__" => "Return self+value.", + "posix.sched_param.__class_getitem__" => "See PEP 585", + "posix.sched_param.__contains__" => "Return bool(key in self).", + "posix.sched_param.__delattr__" => "Implement delattr(self, name).", + "posix.sched_param.__eq__" => "Return self==value.", + "posix.sched_param.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "posix.sched_param.__ge__" => "Return self>=value.", + "posix.sched_param.__getattribute__" => "Return getattr(self, name).", + "posix.sched_param.__getitem__" => "Return self[key].", + "posix.sched_param.__getstate__" => "Helper for pickle.", + "posix.sched_param.__gt__" => "Return self>value.", + "posix.sched_param.__hash__" => "Return hash(self).", + "posix.sched_param.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "posix.sched_param.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "posix.sched_param.__iter__" => "Implement iter(self).", + "posix.sched_param.__le__" => "Return self<=value.", + "posix.sched_param.__len__" => "Return len(self).", + "posix.sched_param.__lt__" => "Return self<value.", + "posix.sched_param.__mul__" => "Return self*value.", + "posix.sched_param.__ne__" => "Return self!=value.", + "posix.sched_param.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "posix.sched_param.__reduce_ex__" => "Helper for pickle.", + "posix.sched_param.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "posix.sched_param.__repr__" => "Return repr(self).", + "posix.sched_param.__rmul__" => "Return value*self.", + "posix.sched_param.__setattr__" => "Implement setattr(self, name, value).", + "posix.sched_param.__sizeof__" => "Size of object in memory, in bytes.", + "posix.sched_param.__str__" => "Return str(self).", + "posix.sched_param.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "posix.sched_param.count" => "Return number of occurrences of value.", + "posix.sched_param.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "posix.sched_param.sched_priority" => "the scheduling priority", + "posix.sched_rr_get_interval" => "Return the round-robin quantum for the process identified by pid, in seconds.\n\nValue returned is a float.", + "posix.sched_setaffinity" => "Set the CPU affinity of the process identified by pid to mask.\n\nmask should be an iterable of integers identifying CPUs.", + "posix.sched_setparam" => "Set scheduling parameters for the process identified by pid.\n\nIf pid is 0, sets parameters for the calling process.\nparam should be an instance of sched_param.", + "posix.sched_setscheduler" => "Set the scheduling policy for the process identified by pid.\n\nIf pid is 0, the calling process is changed.\nparam is an instance of sched_param.", + "posix.sched_yield" => "Voluntarily relinquish the CPU.", + "posix.sendfile" => "Copy count bytes from file descriptor in_fd to file descriptor out_fd.", + "posix.set_blocking" => "Set the blocking mode of the specified file descriptor.\n\nSet the O_NONBLOCK flag if blocking is False,\nclear the O_NONBLOCK flag otherwise.", + "posix.set_inheritable" => "Set the inheritable flag of the specified file descriptor.", + "posix.setegid" => "Set the current process's effective group id.", + "posix.seteuid" => "Set the current process's effective user id.", + "posix.setgid" => "Set the current process's group id.", + "posix.setgroups" => "Set the groups of the current process to list.", + "posix.setns" => "Move the calling thread into different namespaces.\n\nfd\n A file descriptor to a namespace.\nnstype\n Type of namespace.", + "posix.setpgid" => "Call the system call setpgid(pid, pgrp).", + "posix.setpgrp" => "Make the current process the leader of its process group.", + "posix.setpriority" => "Set program scheduling priority.", + "posix.setregid" => "Set the current process's real and effective group ids.", + "posix.setresgid" => "Set the current process's real, effective, and saved group ids.", + "posix.setresuid" => "Set the current process's real, effective, and saved user ids.", + "posix.setreuid" => "Set the current process's real and effective user ids.", + "posix.setsid" => "Call the system call setsid().", + "posix.setuid" => "Set the current process's user id.", + "posix.setxattr" => "Set extended attribute attribute on path to value.\n\npath may be either a string, a path-like object, or an open file descriptor.\nIf follow_symlinks is False, and the last element of the path is a symbolic\n link, setxattr will modify the symbolic link itself instead of the file\n the link points to.", + "posix.splice" => "Transfer count bytes from one pipe to a descriptor or vice versa.\n\n src\n Source file descriptor.\n dst\n Destination file descriptor.\n count\n Number of bytes to copy.\n offset_src\n Starting offset in src.\n offset_dst\n Starting offset in dst.\n flags\n Flags to modify the semantics of the call.\n\nIf offset_src is None, then src is read from the current position;\nrespectively for offset_dst. The offset associated to the file\ndescriptor that refers to a pipe must be None.", + "posix.stat" => "Perform a stat system call on the given path.\n\n path\n Path to be examined; can be string, bytes, a path-like object or\n open-file-descriptor int.\n dir_fd\n If not None, it should be a file descriptor open to a directory,\n and path should be a relative string; path will then be relative to\n that directory.\n follow_symlinks\n If False, and the last element of the path is a symbolic link,\n stat will examine the symbolic link itself instead of the file\n the link points to.\n\ndir_fd and follow_symlinks may not be implemented\n on your platform. If they are unavailable, using them will raise a\n NotImplementedError.\n\nIt's an error to use dir_fd or follow_symlinks when specifying path as\n an open file descriptor.", + "posix.statvfs" => "Perform a statvfs system call on the given path.\n\npath may always be specified as a string.\nOn some platforms, path may also be specified as an open file descriptor.\n If this functionality is unavailable, using it raises an exception.", + "posix.strerror" => "Translate an error code to a message string.", + "posix.symlink" => "Create a symbolic link pointing to src named dst.\n\ntarget_is_directory is required on Windows if the target is to be\n interpreted as a directory. (On Windows, symlink requires\n Windows 6.0 or greater, and raises a NotImplementedError otherwise.)\n target_is_directory is ignored on non-Windows platforms.\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "posix.sync" => "Force write of everything to disk.", + "posix.sysconf" => "Return an integer-valued system configuration variable.", + "posix.system" => "Execute the command in a subshell.", + "posix.tcgetpgrp" => "Return the process group associated with the terminal specified by fd.", + "posix.tcsetpgrp" => "Set the process group associated with the terminal specified by fd.", + "posix.timerfd_create" => "Create and return a timer file descriptor.\n\nclockid\n A valid clock ID constant as timer file descriptor.\n\n time.CLOCK_REALTIME\n time.CLOCK_MONOTONIC\n time.CLOCK_BOOTTIME\nflags\n 0 or a bit mask of os.TFD_NONBLOCK or os.TFD_CLOEXEC.\n\n os.TFD_NONBLOCK\n If *TFD_NONBLOCK* is set as a flag, read doesn't blocks.\n If *TFD_NONBLOCK* is not set as a flag, read block until the timer fires.\n\n os.TFD_CLOEXEC\n If *TFD_CLOEXEC* is set as a flag, enable the close-on-exec flag", + "posix.timerfd_gettime" => "Return a tuple of a timer file descriptor's (interval, next expiration) in float seconds.\n\nfd\n A timer file descriptor.", + "posix.timerfd_gettime_ns" => "Return a tuple of a timer file descriptor's (interval, next expiration) in nanoseconds.\n\nfd\n A timer file descriptor.", + "posix.timerfd_settime" => "Alter a timer file descriptor's internal timer in seconds.\n\nfd\n A timer file descriptor.\nflags\n 0 or a bit mask of TFD_TIMER_ABSTIME or TFD_TIMER_CANCEL_ON_SET.\ninitial\n The initial expiration time, in seconds.\ninterval\n The timer's interval, in seconds.", + "posix.timerfd_settime_ns" => "Alter a timer file descriptor's internal timer in nanoseconds.\n\nfd\n A timer file descriptor.\nflags\n 0 or a bit mask of TFD_TIMER_ABSTIME or TFD_TIMER_CANCEL_ON_SET.\ninitial\n initial expiration timing in seconds.\ninterval\n interval for the timer in seconds.", + "posix.times" => "Return a collection containing process timing information.\n\nThe object returned behaves like a named tuple with these fields:\n (utime, stime, cutime, cstime, elapsed_time)\nAll fields are floating-point numbers.", + "posix.times_result" => "times_result: Result from os.times().\n\nThis object may be accessed either as a tuple of\n (user, system, children_user, children_system, elapsed),\nor via the attributes user, system, children_user, children_system,\nand elapsed.\n\nSee os.times for more information.", + "posix.times_result.__add__" => "Return self+value.", + "posix.times_result.__class_getitem__" => "See PEP 585", + "posix.times_result.__contains__" => "Return bool(key in self).", + "posix.times_result.__delattr__" => "Implement delattr(self, name).", + "posix.times_result.__eq__" => "Return self==value.", + "posix.times_result.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "posix.times_result.__ge__" => "Return self>=value.", + "posix.times_result.__getattribute__" => "Return getattr(self, name).", + "posix.times_result.__getitem__" => "Return self[key].", + "posix.times_result.__getstate__" => "Helper for pickle.", + "posix.times_result.__gt__" => "Return self>value.", + "posix.times_result.__hash__" => "Return hash(self).", + "posix.times_result.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "posix.times_result.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "posix.times_result.__iter__" => "Implement iter(self).", + "posix.times_result.__le__" => "Return self<=value.", + "posix.times_result.__len__" => "Return len(self).", + "posix.times_result.__lt__" => "Return self<value.", + "posix.times_result.__mul__" => "Return self*value.", + "posix.times_result.__ne__" => "Return self!=value.", + "posix.times_result.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "posix.times_result.__reduce_ex__" => "Helper for pickle.", + "posix.times_result.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "posix.times_result.__repr__" => "Return repr(self).", + "posix.times_result.__rmul__" => "Return value*self.", + "posix.times_result.__setattr__" => "Implement setattr(self, name, value).", + "posix.times_result.__sizeof__" => "Size of object in memory, in bytes.", + "posix.times_result.__str__" => "Return str(self).", + "posix.times_result.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "posix.times_result.children_system" => "system time of children", + "posix.times_result.children_user" => "user time of children", + "posix.times_result.count" => "Return number of occurrences of value.", + "posix.times_result.elapsed" => "elapsed time since an arbitrary point in the past", + "posix.times_result.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "posix.times_result.system" => "system time", + "posix.times_result.user" => "user time", + "posix.truncate" => "Truncate a file, specified by path, to a specific length.\n\nOn some platforms, path may also be specified as an open file descriptor.\n If this functionality is unavailable, using it raises an exception.", + "posix.ttyname" => "Return the name of the terminal device connected to 'fd'.\n\nfd\n Integer file descriptor handle.", + "posix.umask" => "Set the current numeric umask and return the previous umask.", + "posix.uname" => "Return an object identifying the current operating system.\n\nThe object behaves like a named tuple with the following fields:\n (sysname, nodename, release, version, machine)", + "posix.uname_result" => "uname_result: Result from os.uname().\n\nThis object may be accessed either as a tuple of\n (sysname, nodename, release, version, machine),\nor via the attributes sysname, nodename, release, version, and machine.\n\nSee os.uname for more information.", + "posix.uname_result.__add__" => "Return self+value.", + "posix.uname_result.__class_getitem__" => "See PEP 585", + "posix.uname_result.__contains__" => "Return bool(key in self).", + "posix.uname_result.__delattr__" => "Implement delattr(self, name).", + "posix.uname_result.__eq__" => "Return self==value.", + "posix.uname_result.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "posix.uname_result.__ge__" => "Return self>=value.", + "posix.uname_result.__getattribute__" => "Return getattr(self, name).", + "posix.uname_result.__getitem__" => "Return self[key].", + "posix.uname_result.__getstate__" => "Helper for pickle.", + "posix.uname_result.__gt__" => "Return self>value.", + "posix.uname_result.__hash__" => "Return hash(self).", + "posix.uname_result.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "posix.uname_result.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "posix.uname_result.__iter__" => "Implement iter(self).", + "posix.uname_result.__le__" => "Return self<=value.", + "posix.uname_result.__len__" => "Return len(self).", + "posix.uname_result.__lt__" => "Return self<value.", + "posix.uname_result.__mul__" => "Return self*value.", + "posix.uname_result.__ne__" => "Return self!=value.", + "posix.uname_result.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "posix.uname_result.__reduce_ex__" => "Helper for pickle.", + "posix.uname_result.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "posix.uname_result.__repr__" => "Return repr(self).", + "posix.uname_result.__rmul__" => "Return value*self.", + "posix.uname_result.__setattr__" => "Implement setattr(self, name, value).", + "posix.uname_result.__sizeof__" => "Size of object in memory, in bytes.", + "posix.uname_result.__str__" => "Return str(self).", + "posix.uname_result.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "posix.uname_result.count" => "Return number of occurrences of value.", + "posix.uname_result.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "posix.uname_result.machine" => "hardware identifier", + "posix.uname_result.nodename" => "name of machine on network (implementation-defined)", + "posix.uname_result.release" => "operating system release", + "posix.uname_result.sysname" => "operating system name", + "posix.uname_result.version" => "operating system version", + "posix.unlink" => "Remove a file (same as remove()).\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\ndir_fd may not be implemented on your platform.\n If it is unavailable, using it will raise a NotImplementedError.", + "posix.unlockpt" => "Unlock a pseudo-terminal master/slave pair.\n\n fd\n File descriptor of a master pseudo-terminal device.\n\nPerforms an unlockpt() C function call.", + "posix.unsetenv" => "Delete an environment variable.", + "posix.unshare" => "Disassociate parts of a process (or thread) execution context.\n\nflags\n Namespaces to be unshared.", + "posix.urandom" => "Return a bytes object containing random bytes suitable for cryptographic use.", + "posix.utime" => "Set the access and modified time of path.\n\npath may always be specified as a string.\nOn some platforms, path may also be specified as an open file descriptor.\n If this functionality is unavailable, using it raises an exception.\n\nIf times is not None, it must be a tuple (atime, mtime);\n atime and mtime should be expressed as float seconds since the epoch.\nIf ns is specified, it must be a tuple (atime_ns, mtime_ns);\n atime_ns and mtime_ns should be expressed as integer nanoseconds\n since the epoch.\nIf times is None and ns is unspecified, utime uses the current time.\nSpecifying tuples for both times and ns is an error.\n\nIf dir_fd is not None, it should be a file descriptor open to a directory,\n and path should be relative; path will then be relative to that directory.\nIf follow_symlinks is False, and the last element of the path is a symbolic\n link, utime will modify the symbolic link itself instead of the file the\n link points to.\nIt is an error to use dir_fd or follow_symlinks when specifying path\n as an open file descriptor.\ndir_fd and follow_symlinks may not be available on your platform.\n If they are unavailable, using them will raise a NotImplementedError.", + "posix.wait" => "Wait for completion of a child process.\n\nReturns a tuple of information about the child process:\n (pid, status)", + "posix.wait3" => "Wait for completion of a child process.\n\nReturns a tuple of information about the child process:\n (pid, status, rusage)", + "posix.wait4" => "Wait for completion of a specific child process.\n\nReturns a tuple of information about the child process:\n (pid, status, rusage)", + "posix.waitid" => "Returns the result of waiting for a process or processes.\n\n idtype\n Must be one of be P_PID, P_PGID or P_ALL.\n id\n The id to wait on.\n options\n Constructed from the ORing of one or more of WEXITED, WSTOPPED\n or WCONTINUED and additionally may be ORed with WNOHANG or WNOWAIT.\n\nReturns either waitid_result or None if WNOHANG is specified and there are\nno children in a waitable state.", + "posix.waitid_result" => "waitid_result: Result from waitid.\n\nThis object may be accessed either as a tuple of\n (si_pid, si_uid, si_signo, si_status, si_code),\nor via the attributes si_pid, si_uid, and so on.\n\nSee os.waitid for more information.", + "posix.waitid_result.__add__" => "Return self+value.", + "posix.waitid_result.__class_getitem__" => "See PEP 585", + "posix.waitid_result.__contains__" => "Return bool(key in self).", + "posix.waitid_result.__delattr__" => "Implement delattr(self, name).", + "posix.waitid_result.__eq__" => "Return self==value.", + "posix.waitid_result.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "posix.waitid_result.__ge__" => "Return self>=value.", + "posix.waitid_result.__getattribute__" => "Return getattr(self, name).", + "posix.waitid_result.__getitem__" => "Return self[key].", + "posix.waitid_result.__getstate__" => "Helper for pickle.", + "posix.waitid_result.__gt__" => "Return self>value.", + "posix.waitid_result.__hash__" => "Return hash(self).", + "posix.waitid_result.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "posix.waitid_result.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "posix.waitid_result.__iter__" => "Implement iter(self).", + "posix.waitid_result.__le__" => "Return self<=value.", + "posix.waitid_result.__len__" => "Return len(self).", + "posix.waitid_result.__lt__" => "Return self<value.", + "posix.waitid_result.__mul__" => "Return self*value.", + "posix.waitid_result.__ne__" => "Return self!=value.", + "posix.waitid_result.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "posix.waitid_result.__reduce_ex__" => "Helper for pickle.", + "posix.waitid_result.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "posix.waitid_result.__repr__" => "Return repr(self).", + "posix.waitid_result.__rmul__" => "Return value*self.", + "posix.waitid_result.__setattr__" => "Implement setattr(self, name, value).", + "posix.waitid_result.__sizeof__" => "Size of object in memory, in bytes.", + "posix.waitid_result.__str__" => "Return str(self).", + "posix.waitid_result.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "posix.waitid_result.count" => "Return number of occurrences of value.", + "posix.waitid_result.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "posix.waitpid" => "Wait for completion of a given child process.\n\nReturns a tuple of information regarding the child process:\n (pid, status)\n\nThe options argument is ignored on Windows.", + "posix.waitstatus_to_exitcode" => "Convert a wait status to an exit code.\n\nOn Unix:\n\n* If WIFEXITED(status) is true, return WEXITSTATUS(status).\n* If WIFSIGNALED(status) is true, return -WTERMSIG(status).\n* Otherwise, raise a ValueError.\n\nOn Windows, return status shifted right by 8 bits.\n\nOn Unix, if the process is being traced or if waitpid() was called with\nWUNTRACED option, the caller must first check if WIFSTOPPED(status) is true.\nThis function must not be called if WIFSTOPPED(status) is true.", + "posix.write" => "Write a bytes object to a file descriptor.", + "posix.writev" => "Iterate over buffers, and write the contents of each to a file descriptor.\n\nReturns the total number of bytes written.\nbuffers must be a sequence of bytes-like objects.", + "pwd" => "This module provides access to the Unix password database.\nIt is available on all Unix versions.\n\nPassword database entries are reported as 7-tuples containing the following\nitems from the password database (see `<pwd.h>'), in order:\npw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir, pw_shell.\nThe uid and gid items are integers, all others are strings. An\nexception is raised if the entry asked for cannot be found.", + "pwd.getpwall" => "Return a list of all available password database entries, in arbitrary order.\n\nSee help(pwd) for more on password database entries.", + "pwd.getpwnam" => "Return the password database entry for the given user name.\n\nSee `help(pwd)` for more on password database entries.", + "pwd.getpwuid" => "Return the password database entry for the given numeric user ID.\n\nSee `help(pwd)` for more on password database entries.", + "pwd.struct_passwd" => "pwd.struct_passwd: Results from getpw*() routines.\n\nThis object may be accessed either as a tuple of\n (pw_name,pw_passwd,pw_uid,pw_gid,pw_gecos,pw_dir,pw_shell)\nor via the object attributes as named in the above tuple.", + "pwd.struct_passwd.__add__" => "Return self+value.", + "pwd.struct_passwd.__class_getitem__" => "See PEP 585", + "pwd.struct_passwd.__contains__" => "Return bool(key in self).", + "pwd.struct_passwd.__delattr__" => "Implement delattr(self, name).", + "pwd.struct_passwd.__eq__" => "Return self==value.", + "pwd.struct_passwd.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "pwd.struct_passwd.__ge__" => "Return self>=value.", + "pwd.struct_passwd.__getattribute__" => "Return getattr(self, name).", + "pwd.struct_passwd.__getitem__" => "Return self[key].", + "pwd.struct_passwd.__getstate__" => "Helper for pickle.", + "pwd.struct_passwd.__gt__" => "Return self>value.", + "pwd.struct_passwd.__hash__" => "Return hash(self).", + "pwd.struct_passwd.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "pwd.struct_passwd.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "pwd.struct_passwd.__iter__" => "Implement iter(self).", + "pwd.struct_passwd.__le__" => "Return self<=value.", + "pwd.struct_passwd.__len__" => "Return len(self).", + "pwd.struct_passwd.__lt__" => "Return self<value.", + "pwd.struct_passwd.__mul__" => "Return self*value.", + "pwd.struct_passwd.__ne__" => "Return self!=value.", + "pwd.struct_passwd.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "pwd.struct_passwd.__reduce_ex__" => "Helper for pickle.", + "pwd.struct_passwd.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "pwd.struct_passwd.__repr__" => "Return repr(self).", + "pwd.struct_passwd.__rmul__" => "Return value*self.", + "pwd.struct_passwd.__setattr__" => "Implement setattr(self, name, value).", + "pwd.struct_passwd.__sizeof__" => "Size of object in memory, in bytes.", + "pwd.struct_passwd.__str__" => "Return str(self).", + "pwd.struct_passwd.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "pwd.struct_passwd.count" => "Return number of occurrences of value.", + "pwd.struct_passwd.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "pwd.struct_passwd.pw_dir" => "home directory", + "pwd.struct_passwd.pw_gecos" => "real name", + "pwd.struct_passwd.pw_gid" => "group id", + "pwd.struct_passwd.pw_name" => "user name", + "pwd.struct_passwd.pw_passwd" => "password", + "pwd.struct_passwd.pw_shell" => "shell program", + "pwd.struct_passwd.pw_uid" => "user id", + "pyexpat" => "Python wrapper for Expat parser.", + "pyexpat.ErrorString" => "Returns string error for given number.", + "pyexpat.ParserCreate" => "Return a new XML parser object.", + "pyexpat.XMLParserType" => "XML parser", + "pyexpat.XMLParserType.ExternalEntityParserCreate" => "Create a parser for parsing an external entity based on the information passed to the ExternalEntityRefHandler.", + "pyexpat.XMLParserType.GetBase" => "Return base URL string for the parser.", + "pyexpat.XMLParserType.GetInputContext" => "Return the untranslated text of the input that caused the current event.\n\nIf the event was generated by a large amount of text (such as a start tag\nfor an element with many attributes), not all of the text may be available.", + "pyexpat.XMLParserType.GetReparseDeferralEnabled" => "Retrieve reparse deferral enabled status; always returns false with Expat <2.6.0.", + "pyexpat.XMLParserType.Parse" => "Parse XML data.\n\n`isfinal' should be true at end of input.", + "pyexpat.XMLParserType.ParseFile" => "Parse XML data from file-like object.", + "pyexpat.XMLParserType.SetBase" => "Set the base URL for the parser.", + "pyexpat.XMLParserType.SetParamEntityParsing" => "Controls parsing of parameter entities (including the external DTD subset).\n\nPossible flag values are XML_PARAM_ENTITY_PARSING_NEVER,\nXML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE and\nXML_PARAM_ENTITY_PARSING_ALWAYS. Returns true if setting the flag\nwas successful.", + "pyexpat.XMLParserType.SetReparseDeferralEnabled" => "Enable/Disable reparse deferral; enabled by default with Expat >=2.6.0.", + "pyexpat.XMLParserType.UseForeignDTD" => "Allows the application to provide an artificial external subset if one is not specified as part of the document instance.\n\nThis readily allows the use of a 'default' document type controlled by the\napplication, while still getting the advantage of providing document type\ninformation to the parser. 'flag' defaults to True if not provided.", + "pyexpat.XMLParserType.__delattr__" => "Implement delattr(self, name).", + "pyexpat.XMLParserType.__eq__" => "Return self==value.", + "pyexpat.XMLParserType.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "pyexpat.XMLParserType.__ge__" => "Return self>=value.", + "pyexpat.XMLParserType.__getattribute__" => "Return getattr(self, name).", + "pyexpat.XMLParserType.__getstate__" => "Helper for pickle.", + "pyexpat.XMLParserType.__gt__" => "Return self>value.", + "pyexpat.XMLParserType.__hash__" => "Return hash(self).", + "pyexpat.XMLParserType.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "pyexpat.XMLParserType.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "pyexpat.XMLParserType.__le__" => "Return self<=value.", + "pyexpat.XMLParserType.__lt__" => "Return self<value.", + "pyexpat.XMLParserType.__ne__" => "Return self!=value.", + "pyexpat.XMLParserType.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "pyexpat.XMLParserType.__reduce__" => "Helper for pickle.", + "pyexpat.XMLParserType.__reduce_ex__" => "Helper for pickle.", + "pyexpat.XMLParserType.__repr__" => "Return repr(self).", + "pyexpat.XMLParserType.__setattr__" => "Implement setattr(self, name, value).", + "pyexpat.XMLParserType.__sizeof__" => "Size of object in memory, in bytes.", + "pyexpat.XMLParserType.__str__" => "Return str(self).", + "pyexpat.XMLParserType.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "readline" => "Importing this module enables command line editing using GNU readline.", + "readline.add_history" => "Add an item to the history buffer.", + "readline.append_history_file" => "Append the last nelements items of the history list to file.\n\nThe default filename is ~/.history.", + "readline.clear_history" => "Clear the current readline history.", + "readline.get_begidx" => "Get the beginning index of the completion scope.", + "readline.get_completer" => "Get the current completer function.", + "readline.get_completer_delims" => "Get the word delimiters for completion.", + "readline.get_completion_type" => "Get the type of completion being attempted.", + "readline.get_current_history_length" => "Return the current (not the maximum) length of history.", + "readline.get_endidx" => "Get the ending index of the completion scope.", + "readline.get_history_item" => "Return the current contents of history item at one-based index.", + "readline.get_history_length" => "Return the maximum number of lines that will be written to the history file.", + "readline.get_line_buffer" => "Return the current contents of the line buffer.", + "readline.insert_text" => "Insert text into the line buffer at the cursor position.", + "readline.parse_and_bind" => "Execute the init line provided in the string argument.", + "readline.read_history_file" => "Load a readline history file.\n\nThe default filename is ~/.history.", + "readline.read_init_file" => "Execute a readline initialization file.\n\nThe default filename is the last filename used.", + "readline.redisplay" => "Change what's displayed on the screen to reflect contents of the line buffer.", + "readline.remove_history_item" => "Remove history item given by its zero-based position.", + "readline.replace_history_item" => "Replaces history item given by its position with contents of line.\n\npos is zero-based.", + "readline.set_auto_history" => "Enables or disables automatic history.", + "readline.set_completer" => "Set or remove the completer function.\n\nThe function is called as function(text, state),\nfor state in 0, 1, 2, ..., until it returns a non-string.\nIt should return the next possible completion starting with 'text'.", + "readline.set_completer_delims" => "Set the word delimiters for completion.", + "readline.set_completion_display_matches_hook" => "Set or remove the completion display function.\n\nThe function is called as\n function(substitution, [matches], longest_match_length)\nonce each time matches need to be displayed.", + "readline.set_history_length" => "Set the maximal number of lines which will be written to the history file.\n\nA negative length is used to inhibit history truncation.", + "readline.set_pre_input_hook" => "Set or remove the function invoked by the rl_pre_input_hook callback.\n\nThe function is called with no arguments after the first prompt\nhas been printed and just before readline starts reading input\ncharacters.", + "readline.set_startup_hook" => "Set or remove the function invoked by the rl_startup_hook callback.\n\nThe function is called with no arguments just\nbefore readline prints the first prompt.", + "readline.write_history_file" => "Save a readline history file.\n\nThe default filename is ~/.history.", + "resource.struct_rusage" => "struct_rusage: Result from getrusage.\n\nThis object may be accessed either as a tuple of\n (utime,stime,maxrss,ixrss,idrss,isrss,minflt,majflt,\n nswap,inblock,oublock,msgsnd,msgrcv,nsignals,nvcsw,nivcsw)\nor via the attributes ru_utime, ru_stime, ru_maxrss, and so on.", + "resource.struct_rusage.__add__" => "Return self+value.", + "resource.struct_rusage.__class_getitem__" => "See PEP 585", + "resource.struct_rusage.__contains__" => "Return bool(key in self).", + "resource.struct_rusage.__delattr__" => "Implement delattr(self, name).", + "resource.struct_rusage.__eq__" => "Return self==value.", + "resource.struct_rusage.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "resource.struct_rusage.__ge__" => "Return self>=value.", + "resource.struct_rusage.__getattribute__" => "Return getattr(self, name).", + "resource.struct_rusage.__getitem__" => "Return self[key].", + "resource.struct_rusage.__getstate__" => "Helper for pickle.", + "resource.struct_rusage.__gt__" => "Return self>value.", + "resource.struct_rusage.__hash__" => "Return hash(self).", + "resource.struct_rusage.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "resource.struct_rusage.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "resource.struct_rusage.__iter__" => "Implement iter(self).", + "resource.struct_rusage.__le__" => "Return self<=value.", + "resource.struct_rusage.__len__" => "Return len(self).", + "resource.struct_rusage.__lt__" => "Return self<value.", + "resource.struct_rusage.__mul__" => "Return self*value.", + "resource.struct_rusage.__ne__" => "Return self!=value.", + "resource.struct_rusage.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "resource.struct_rusage.__reduce_ex__" => "Helper for pickle.", + "resource.struct_rusage.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "resource.struct_rusage.__repr__" => "Return repr(self).", + "resource.struct_rusage.__rmul__" => "Return value*self.", + "resource.struct_rusage.__setattr__" => "Implement setattr(self, name, value).", + "resource.struct_rusage.__sizeof__" => "Size of object in memory, in bytes.", + "resource.struct_rusage.__str__" => "Return str(self).", + "resource.struct_rusage.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "resource.struct_rusage.count" => "Return number of occurrences of value.", + "resource.struct_rusage.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "resource.struct_rusage.ru_idrss" => "unshared data size", + "resource.struct_rusage.ru_inblock" => "block input operations", + "resource.struct_rusage.ru_isrss" => "unshared stack size", + "resource.struct_rusage.ru_ixrss" => "shared memory size", + "resource.struct_rusage.ru_majflt" => "page faults requiring I/O", + "resource.struct_rusage.ru_maxrss" => "max. resident set size", + "resource.struct_rusage.ru_minflt" => "page faults not requiring I/O", + "resource.struct_rusage.ru_msgrcv" => "IPC messages received", + "resource.struct_rusage.ru_msgsnd" => "IPC messages sent", + "resource.struct_rusage.ru_nivcsw" => "involuntary context switches", + "resource.struct_rusage.ru_nsignals" => "signals received", + "resource.struct_rusage.ru_nswap" => "number of swap outs", + "resource.struct_rusage.ru_nvcsw" => "voluntary context switches", + "resource.struct_rusage.ru_oublock" => "block output operations", + "resource.struct_rusage.ru_stime" => "system time used", + "resource.struct_rusage.ru_utime" => "user time used", + "select" => "This module supports asynchronous I/O on multiple file descriptors.\n\n*** IMPORTANT NOTICE ***\nOn Windows, only sockets are supported; on Unix, all file descriptors.", + "select.epoll" => "select.epoll(sizehint=-1, flags=0)\n\nReturns an epolling object\n\nsizehint must be a positive integer or -1 for the default size. The\nsizehint is used to optimize internal data structures. It doesn't limit\nthe maximum number of monitored events.", + "select.epoll.__delattr__" => "Implement delattr(self, name).", + "select.epoll.__eq__" => "Return self==value.", + "select.epoll.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "select.epoll.__ge__" => "Return self>=value.", + "select.epoll.__getattribute__" => "Return getattr(self, name).", + "select.epoll.__getstate__" => "Helper for pickle.", + "select.epoll.__gt__" => "Return self>value.", + "select.epoll.__hash__" => "Return hash(self).", + "select.epoll.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "select.epoll.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "select.epoll.__le__" => "Return self<=value.", + "select.epoll.__lt__" => "Return self<value.", + "select.epoll.__ne__" => "Return self!=value.", + "select.epoll.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "select.epoll.__reduce__" => "Helper for pickle.", + "select.epoll.__reduce_ex__" => "Helper for pickle.", + "select.epoll.__repr__" => "Return repr(self).", + "select.epoll.__setattr__" => "Implement setattr(self, name, value).", + "select.epoll.__sizeof__" => "Size of object in memory, in bytes.", + "select.epoll.__str__" => "Return str(self).", + "select.epoll.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "select.epoll.close" => "Close the epoll control file descriptor.\n\nFurther operations on the epoll object will raise an exception.", + "select.epoll.closed" => "True if the epoll handler is closed", + "select.epoll.fileno" => "Return the epoll control file descriptor.", + "select.epoll.fromfd" => "Create an epoll object from a given control fd.", + "select.epoll.modify" => "Modify event mask for a registered file descriptor.\n\nfd\n the target file descriptor of the operation\neventmask\n a bit set composed of the various EPOLL constants", + "select.epoll.poll" => "Wait for events on the epoll file descriptor.\n\n timeout\n the maximum time to wait in seconds (as float);\n a timeout of None or -1 makes poll wait indefinitely\n maxevents\n the maximum number of events returned; -1 means no limit\n\nReturns a list containing any descriptors that have events to report,\nas a list of (fd, events) 2-tuples.", + "select.epoll.register" => "Registers a new fd or raises an OSError if the fd is already registered.\n\n fd\n the target file descriptor of the operation\n eventmask\n a bit set composed of the various EPOLL constants\n\nThe epoll interface supports all file descriptors that support poll.", + "select.epoll.unregister" => "Remove a registered file descriptor from the epoll object.\n\nfd\n the target file descriptor of the operation", + "select.kevent" => "kevent(ident, filter=KQ_FILTER_READ, flags=KQ_EV_ADD, fflags=0, data=0, udata=0)\n\nThis object is the equivalent of the struct kevent for the C API.\n\nSee the kqueue manpage for more detailed information about the meaning\nof the arguments.\n\nOne minor note: while you might hope that udata could store a\nreference to a python object, it cannot, because it is impossible to\nkeep a proper reference count of the object once it's passed into the\nkernel. Therefore, I have restricted it to only storing an integer. I\nrecommend ignoring it and simply using the 'ident' field to key off\nof. You could also set up a dictionary on the python side to store a\nudata->object mapping.", + "select.kevent.__delattr__" => "Implement delattr(self, name).", + "select.kevent.__eq__" => "Return self==value.", + "select.kevent.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "select.kevent.__ge__" => "Return self>=value.", + "select.kevent.__getattribute__" => "Return getattr(self, name).", + "select.kevent.__getstate__" => "Helper for pickle.", + "select.kevent.__gt__" => "Return self>value.", + "select.kevent.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "select.kevent.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "select.kevent.__le__" => "Return self<=value.", + "select.kevent.__lt__" => "Return self<value.", + "select.kevent.__ne__" => "Return self!=value.", + "select.kevent.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "select.kevent.__reduce__" => "Helper for pickle.", + "select.kevent.__reduce_ex__" => "Helper for pickle.", + "select.kevent.__repr__" => "Return repr(self).", + "select.kevent.__setattr__" => "Implement setattr(self, name, value).", + "select.kevent.__sizeof__" => "Size of object in memory, in bytes.", + "select.kevent.__str__" => "Return str(self).", + "select.kevent.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "select.kqueue" => "Kqueue syscall wrapper.\n\nFor example, to start watching a socket for input:\n>>> kq = kqueue()\n>>> sock = socket()\n>>> sock.connect((host, port))\n>>> kq.control([kevent(sock, KQ_FILTER_WRITE, KQ_EV_ADD)], 0)\n\nTo wait one second for it to become writeable:\n>>> kq.control(None, 1, 1000)\n\nTo stop listening:\n>>> kq.control([kevent(sock, KQ_FILTER_WRITE, KQ_EV_DELETE)], 0)", + "select.kqueue.__del__" => "Called when the instance is about to be destroyed.", + "select.kqueue.__delattr__" => "Implement delattr(self, name).", + "select.kqueue.__eq__" => "Return self==value.", + "select.kqueue.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "select.kqueue.__ge__" => "Return self>=value.", + "select.kqueue.__getattribute__" => "Return getattr(self, name).", + "select.kqueue.__getstate__" => "Helper for pickle.", + "select.kqueue.__gt__" => "Return self>value.", + "select.kqueue.__hash__" => "Return hash(self).", + "select.kqueue.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "select.kqueue.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "select.kqueue.__le__" => "Return self<=value.", + "select.kqueue.__lt__" => "Return self<value.", + "select.kqueue.__ne__" => "Return self!=value.", + "select.kqueue.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "select.kqueue.__reduce__" => "Helper for pickle.", + "select.kqueue.__reduce_ex__" => "Helper for pickle.", + "select.kqueue.__repr__" => "Return repr(self).", + "select.kqueue.__setattr__" => "Implement setattr(self, name, value).", + "select.kqueue.__sizeof__" => "Size of object in memory, in bytes.", + "select.kqueue.__str__" => "Return str(self).", + "select.kqueue.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "select.kqueue.close" => "Close the kqueue control file descriptor.\n\nFurther operations on the kqueue object will raise an exception.", + "select.kqueue.closed" => "True if the kqueue handler is closed", + "select.kqueue.control" => "Calls the kernel kevent function.\n\nchangelist\n Must be an iterable of kevent objects describing the changes to be made\n to the kernel's watch list or None.\nmaxevents\n The maximum number of events that the kernel will return.\ntimeout\n The maximum time to wait in seconds, or else None to wait forever.\n This accepts floats for smaller timeouts, too.", + "select.kqueue.fileno" => "Return the kqueue control file descriptor.", + "select.kqueue.fromfd" => "Create a kqueue object from a given control fd.", + "select.poll" => "Returns a polling object.\n\nThis object supports registering and unregistering file descriptors, and then\npolling them for I/O events.", + "select.select" => "Wait until one or more file descriptors are ready for some kind of I/O.\n\nThe first three arguments are iterables of file descriptors to be waited for:\nrlist -- wait until ready for reading\nwlist -- wait until ready for writing\nxlist -- wait for an \"exceptional condition\"\nIf only one kind of condition is required, pass [] for the other lists.\n\nA file descriptor is either a socket or file object, or a small integer\ngotten from a fileno() method call on one of those.\n\nThe optional 4th argument specifies a timeout in seconds; it may be\na floating-point number to specify fractions of seconds. If it is absent\nor None, the call will never time out.\n\nThe return value is a tuple of three lists corresponding to the first three\narguments; each contains the subset of the corresponding file descriptors\nthat are ready.\n\n*** IMPORTANT NOTICE ***\nOn Windows, only sockets are supported; on Unix, all file\ndescriptors can be used.", + "sys" => "This module provides access to some objects used or maintained by the\ninterpreter and to functions that interact strongly with the interpreter.\n\nDynamic objects:\n\nargv -- command line arguments; argv[0] is the script pathname if known\npath -- module search path; path[0] is the script directory, else ''\nmodules -- dictionary of loaded modules\n\ndisplayhook -- called to show results in an interactive session\nexcepthook -- called to handle any uncaught exception other than SystemExit\n To customize printing in an interactive session or to install a custom\n top-level exception handler, assign other functions to replace these.\n\nstdin -- standard input file object; used by input()\nstdout -- standard output file object; used by print()\nstderr -- standard error object; used for error messages\n By assigning other file objects (or objects that behave like files)\n to these, it is possible to redirect all of the interpreter's I/O.\n\nlast_exc - the last uncaught exception\n Only available in an interactive session after a\n traceback has been printed.\nlast_type -- type of last uncaught exception\nlast_value -- value of last uncaught exception\nlast_traceback -- traceback of last uncaught exception\n These three are the (deprecated) legacy representation of last_exc.\n\nStatic objects:\n\nbuiltin_module_names -- tuple of module names built into this interpreter\ncopyright -- copyright notice pertaining to this interpreter\nexec_prefix -- prefix used to find the machine-specific Python library\nexecutable -- absolute path of the executable binary of the Python interpreter\nfloat_info -- a named tuple with information about the float implementation.\nfloat_repr_style -- string indicating the style of repr() output for floats\nhash_info -- a named tuple with information about the hash algorithm.\nhexversion -- version information encoded as a single integer\nimplementation -- Python implementation information.\nint_info -- a named tuple with information about the int implementation.\nmaxsize -- the largest supported length of containers.\nmaxunicode -- the value of the largest Unicode code point\nplatform -- platform identifier\nprefix -- prefix used to find the Python library\nthread_info -- a named tuple with information about the thread implementation.\nversion -- the version of this interpreter as a string\nversion_info -- version information as a named tuple\ndllhandle -- [Windows only] integer handle of the Python DLL\nwinver -- [Windows only] version number of the Python DLL\n_enablelegacywindowsfsencoding -- [Windows only]\n__stdin__ -- the original stdin; don't touch!\n__stdout__ -- the original stdout; don't touch!\n__stderr__ -- the original stderr; don't touch!\n__displayhook__ -- the original displayhook; don't touch!\n__excepthook__ -- the original excepthook; don't touch!\n\nFunctions:\n\ndisplayhook() -- print an object to the screen, and save it in builtins._\nexcepthook() -- print an exception and its traceback to sys.stderr\nexception() -- return the current thread's active exception\nexc_info() -- return information about the current thread's active exception\nexit() -- exit the interpreter by raising SystemExit\ngetdlopenflags() -- returns flags to be used for dlopen() calls\ngetprofile() -- get the global profiling function\ngetrefcount() -- return the reference count for an object (plus one :-)\ngetrecursionlimit() -- return the max recursion depth for the interpreter\ngetsizeof() -- return the size of an object in bytes\ngettrace() -- get the global debug tracing function\nsetdlopenflags() -- set the flags to be used for dlopen() calls\nsetprofile() -- set the global profiling function\nsetrecursionlimit() -- set the max recursion depth for the interpreter\nsettrace() -- set the global debug tracing function", + "sys.__breakpointhook__" => "This hook function is called by built-in breakpoint().", + "sys.__displayhook__" => "Print an object to sys.stdout and also save it in builtins._", + "sys.__excepthook__" => "Handle an exception by displaying it with a traceback on sys.stderr.", + "sys.__unraisablehook__" => "Handle an unraisable exception.\n\nThe unraisable argument has the following attributes:\n\n* exc_type: Exception type.\n* exc_value: Exception value, can be None.\n* exc_traceback: Exception traceback, can be None.\n* err_msg: Error message, can be None.\n* object: Object causing the exception, can be None.", + "sys._baserepl" => "Private function for getting the base REPL", + "sys._clear_internal_caches" => "Clear all internal performance-related caches.", + "sys._clear_type_cache" => "Clear the internal type lookup cache.", + "sys._current_exceptions" => "Return a dict mapping each thread's identifier to its current raised exception.\n\nThis function should be used for specialized purposes only.", + "sys._current_frames" => "Return a dict mapping each thread's thread id to its current stack frame.\n\nThis function should be used for specialized purposes only.", + "sys._debugmallocstats" => "Print summary info to stderr about the state of pymalloc's structures.\n\nIn Py_DEBUG mode, also perform some expensive internal consistency\nchecks.", + "sys._enablelegacywindowsfsencoding" => "Changes the default filesystem encoding to mbcs:replace.\n\nThis is done for consistency with earlier versions of Python. See PEP\n529 for more information.\n\nThis is equivalent to defining the PYTHONLEGACYWINDOWSFSENCODING\nenvironment variable before launching Python.", + "sys._get_cpu_count_config" => "Private function for getting PyConfig.cpu_count", + "sys._getframe" => "Return a frame object from the call stack.\n\nIf optional integer depth is given, return the frame object that many\ncalls below the top of the stack. If that is deeper than the call\nstack, ValueError is raised. The default for depth is zero, returning\nthe frame at the top of the call stack.\n\nThis function should be used for internal and specialized purposes\nonly.", + "sys._getframemodulename" => "Return the name of the module for a calling frame.\n\nThe default depth returns the module containing the call to this API.\nA more typical use in a library will pass a depth of 1 to get the user's\nmodule rather than the library module.\n\nIf no frame, module, or name can be found, returns None.", + "sys._is_gil_enabled" => "Return True if the GIL is currently enabled and False otherwise.", + "sys._is_interned" => "Return True if the given string is \"interned\".", + "sys._setprofileallthreads" => "Set the profiling function in all running threads belonging to the current interpreter.\n\nIt will be called on each function call and return. See the profiler\nchapter in the library manual.", + "sys._settraceallthreads" => "Set the global debug tracing function in all running threads belonging to the current interpreter.\n\nIt will be called on each function call. See the debugger chapter\nin the library manual.", + "sys.activate_stack_trampoline" => "Activate stack profiler trampoline *backend*.", + "sys.addaudithook" => "Adds a new audit hook callback.", + "sys.audit" => "Passes the event to any audit hooks that are attached.", + "sys.breakpointhook" => "This hook function is called by built-in breakpoint().", + "sys.call_tracing" => "Call func(*args), while tracing is enabled.\n\nThe tracing state is saved, and restored afterwards. This is intended\nto be called from a debugger from a checkpoint, to recursively debug\nsome other code.", + "sys.deactivate_stack_trampoline" => "Deactivate the current stack profiler trampoline backend.\n\nIf no stack profiler is activated, this function has no effect.", + "sys.displayhook" => "Print an object to sys.stdout and also save it in builtins._", + "sys.exc_info" => "Return current exception information: (type, value, traceback).\n\nReturn information about the most recent exception caught by an except\nclause in the current stack frame or in an older stack frame.", + "sys.excepthook" => "Handle an exception by displaying it with a traceback on sys.stderr.", + "sys.exception" => "Return the current exception.\n\nReturn the most recent exception caught by an except clause\nin the current stack frame or in an older stack frame, or None\nif no such exception exists.", + "sys.exit" => "Exit the interpreter by raising SystemExit(status).\n\nIf the status is omitted or None, it defaults to zero (i.e., success).\nIf the status is an integer, it will be used as the system exit status.\nIf it is another kind of object, it will be printed and the system\nexit status will be one (i.e., failure).", + "sys.get_asyncgen_hooks" => "Return the installed asynchronous generators hooks.\n\nThis returns a namedtuple of the form (firstiter, finalizer).", + "sys.get_coroutine_origin_tracking_depth" => "Check status of origin tracking for coroutine objects in this thread.", + "sys.get_int_max_str_digits" => "Return the maximum string digits limit for non-binary int<->str conversions.", + "sys.getallocatedblocks" => "Return the number of memory blocks currently allocated.", + "sys.getdefaultencoding" => "Return the current default encoding used by the Unicode implementation.", + "sys.getdlopenflags" => "Return the current value of the flags that are used for dlopen calls.\n\nThe flag constants are defined in the os module.", + "sys.getfilesystemencodeerrors" => "Return the error mode used Unicode to OS filename conversion.", + "sys.getfilesystemencoding" => "Return the encoding used to convert Unicode filenames to OS filenames.", + "sys.getprofile" => "Return the profiling function set with sys.setprofile.\n\nSee the profiler chapter in the library manual.", + "sys.getrecursionlimit" => "Return the current value of the recursion limit.\n\nThe recursion limit is the maximum depth of the Python interpreter\nstack. This limit prevents infinite recursion from causing an overflow\nof the C stack and crashing Python.", + "sys.getrefcount" => "Return the reference count of object.\n\nThe count returned is generally one higher than you might expect,\nbecause it includes the (temporary) reference as an argument to\ngetrefcount().", + "sys.getsizeof" => "getsizeof(object [, default]) -> int\n\nReturn the size of object in bytes.", + "sys.getswitchinterval" => "Return the current thread switch interval; see sys.setswitchinterval().", + "sys.gettrace" => "Return the global debug tracing function set with sys.settrace.\n\nSee the debugger chapter in the library manual.", + "sys.getunicodeinternedsize" => "Return the number of elements of the unicode interned dictionary", + "sys.getwindowsversion" => "Return info about the running version of Windows as a named tuple.\n\nThe members are named: major, minor, build, platform, service_pack,\nservice_pack_major, service_pack_minor, suite_mask, product_type and\nplatform_version. For backward compatibility, only the first 5 items\nare available by indexing. All elements are numbers, except\nservice_pack and platform_type which are strings, and platform_version\nwhich is a 3-tuple. Platform is always 2. Product_type may be 1 for a\nworkstation, 2 for a domain controller, 3 for a server.\nPlatform_version is a 3-tuple containing a version number that is\nintended for identifying the OS rather than feature detection.", + "sys.intern" => "``Intern'' the given string.\n\nThis enters the string in the (global) table of interned strings whose\npurpose is to speed up dictionary lookups. Return the string itself or\nthe previously interned string object with the same value.", + "sys.is_finalizing" => "Return True if Python is exiting.", + "sys.is_stack_trampoline_active" => "Return *True* if a stack profiler trampoline is active.", + "sys.set_asyncgen_hooks" => "set_asyncgen_hooks([firstiter] [, finalizer])\n\nSet a finalizer for async generators objects.", + "sys.set_coroutine_origin_tracking_depth" => "Enable or disable origin tracking for coroutine objects in this thread.\n\nCoroutine objects will track 'depth' frames of traceback information\nabout where they came from, available in their cr_origin attribute.\n\nSet a depth of 0 to disable.", + "sys.set_int_max_str_digits" => "Set the maximum string digits limit for non-binary int<->str conversions.", + "sys.setdlopenflags" => "Set the flags used by the interpreter for dlopen calls.\n\nThis is used, for example, when the interpreter loads extension\nmodules. Among other things, this will enable a lazy resolving of\nsymbols when importing a module, if called as sys.setdlopenflags(0).\nTo share symbols across extension modules, call as\nsys.setdlopenflags(os.RTLD_GLOBAL). Symbolic names for the flag\nmodules can be found in the os module (RTLD_xxx constants, e.g.\nos.RTLD_LAZY).", + "sys.setprofile" => "Set the profiling function.\n\nIt will be called on each function call and return. See the profiler\nchapter in the library manual.", + "sys.setrecursionlimit" => "Set the maximum depth of the Python interpreter stack to n.\n\nThis limit prevents infinite recursion from causing an overflow of the C\nstack and crashing Python. The highest possible limit is platform-\ndependent.", + "sys.setswitchinterval" => "Set the ideal thread switching delay inside the Python interpreter.\n\nThe actual frequency of switching threads can be lower if the\ninterpreter executes long sequences of uninterruptible code\n(this is implementation-specific and workload-dependent).\n\nThe parameter must represent the desired switching delay in seconds\nA typical value is 0.005 (5 milliseconds).", + "sys.settrace" => "Set the global debug tracing function.\n\nIt will be called on each function call. See the debugger chapter\nin the library manual.", + "sys.unraisablehook" => "Handle an unraisable exception.\n\nThe unraisable argument has the following attributes:\n\n* exc_type: Exception type.\n* exc_value: Exception value, can be None.\n* exc_traceback: Exception traceback, can be None.\n* err_msg: Error message, can be None.\n* object: Object causing the exception, can be None.", + "syslog.LOG_MASK" => "Calculates the mask for the individual priority pri.", + "syslog.LOG_UPTO" => "Calculates the mask for all priorities up to and including pri.", + "syslog.closelog" => "Reset the syslog module values and call the system library closelog().", + "syslog.openlog" => "Set logging options of subsequent syslog() calls.", + "syslog.setlogmask" => "Set the priority mask to maskpri and return the previous mask value.", + "syslog.syslog" => "syslog([priority=LOG_INFO,] message)\nSend the string message to the system logger.", + "termios" => "This module provides an interface to the Posix calls for tty I/O control.\nFor a complete description of these calls, see the Posix or Unix manual\npages. It is only available for those Unix versions that support Posix\ntermios style tty I/O control.\n\nAll functions in this module take a file descriptor fd as their first\nargument. This can be an integer file descriptor, such as returned by\nsys.stdin.fileno(), or a file object, such as sys.stdin itself.", + "termios.error.__cause__" => "exception cause", + "termios.error.__context__" => "exception context", + "termios.error.__delattr__" => "Implement delattr(self, name).", + "termios.error.__eq__" => "Return self==value.", + "termios.error.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "termios.error.__ge__" => "Return self>=value.", + "termios.error.__getattribute__" => "Return getattr(self, name).", + "termios.error.__getstate__" => "Helper for pickle.", + "termios.error.__gt__" => "Return self>value.", + "termios.error.__hash__" => "Return hash(self).", + "termios.error.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "termios.error.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "termios.error.__le__" => "Return self<=value.", + "termios.error.__lt__" => "Return self<value.", + "termios.error.__ne__" => "Return self!=value.", + "termios.error.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "termios.error.__reduce_ex__" => "Helper for pickle.", + "termios.error.__repr__" => "Return repr(self).", + "termios.error.__setattr__" => "Implement setattr(self, name, value).", + "termios.error.__sizeof__" => "Size of object in memory, in bytes.", + "termios.error.__str__" => "Return str(self).", + "termios.error.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "termios.error.__weakref__" => "list of weak references to the object", + "termios.error.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "termios.error.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "termios.tcdrain" => "Wait until all output written to file descriptor fd has been transmitted.", + "termios.tcflow" => "Suspend or resume input or output on file descriptor fd.\n\nThe action argument can be termios.TCOOFF to suspend output,\ntermios.TCOON to restart output, termios.TCIOFF to suspend input,\nor termios.TCION to restart input.", + "termios.tcflush" => "Discard queued data on file descriptor fd.\n\nThe queue selector specifies which queue: termios.TCIFLUSH for the input\nqueue, termios.TCOFLUSH for the output queue, or termios.TCIOFLUSH for\nboth queues.", + "termios.tcgetattr" => "Get the tty attributes for file descriptor fd.\n\nReturns a list [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]\nwhere cc is a list of the tty special characters (each a string of\nlength 1, except the items with indices VMIN and VTIME, which are\nintegers when these fields are defined). The interpretation of the\nflags and the speeds as well as the indexing in the cc array must be\ndone using the symbolic constants defined in this module.", + "termios.tcgetwinsize" => "Get the tty winsize for file descriptor fd.\n\nReturns a tuple (ws_row, ws_col).", + "termios.tcsendbreak" => "Send a break on file descriptor fd.\n\nA zero duration sends a break for 0.25-0.5 seconds; a nonzero duration\nhas a system dependent meaning.", + "termios.tcsetattr" => "Set the tty attributes for file descriptor fd.\n\nThe attributes to be set are taken from the attributes argument, which\nis a list like the one returned by tcgetattr(). The when argument\ndetermines when the attributes are changed: termios.TCSANOW to\nchange immediately, termios.TCSADRAIN to change after transmitting all\nqueued output, or termios.TCSAFLUSH to change after transmitting all\nqueued output and discarding all queued input.", + "termios.tcsetwinsize" => "Set the tty winsize for file descriptor fd.\n\nThe winsize to be set is taken from the winsize argument, which\nis a two-item tuple (ws_row, ws_col) like the one returned by tcgetwinsize().", + "time" => "This module provides various functions to manipulate time values.\n\nThere are two standard representations of time. One is the number\nof seconds since the Epoch, in UTC (a.k.a. GMT). It may be an integer\nor a floating-point number (to represent fractions of seconds).\nThe epoch is the point where the time starts, the return value of time.gmtime(0).\nIt is January 1, 1970, 00:00:00 (UTC) on all platforms.\n\nThe other representation is a tuple of 9 integers giving local time.\nThe tuple items are:\n year (including century, e.g. 1998)\n month (1-12)\n day (1-31)\n hours (0-23)\n minutes (0-59)\n seconds (0-59)\n weekday (0-6, Monday is 0)\n Julian day (day in the year, 1-366)\n DST (Daylight Savings Time) flag (-1, 0 or 1)\nIf the DST flag is 0, the time is given in the regular time zone;\nif it is 1, the time is given in the DST time zone;\nif it is -1, mktime() should guess based on the date and time.", + "time.asctime" => "asctime([tuple]) -> string\n\nConvert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'.\nWhen the time tuple is not present, current time as returned by localtime()\nis used.", + "time.clock_getres" => "clock_getres(clk_id) -> floating-point number\n\nReturn the resolution (precision) of the specified clock clk_id.", + "time.clock_gettime" => "Return the time of the specified clock clk_id as a float.", + "time.clock_gettime_ns" => "Return the time of the specified clock clk_id as nanoseconds (int).", + "time.clock_settime" => "clock_settime(clk_id, time)\n\nSet the time of the specified clock clk_id.", + "time.clock_settime_ns" => "clock_settime_ns(clk_id, time)\n\nSet the time of the specified clock clk_id with nanoseconds.", + "time.ctime" => "ctime(seconds) -> string\n\nConvert a time in seconds since the Epoch to a string in local time.\nThis is equivalent to asctime(localtime(seconds)). When the time tuple is\nnot present, current time as returned by localtime() is used.", + "time.get_clock_info" => "get_clock_info(name: str) -> dict\n\nGet information of the specified clock.", + "time.gmtime" => "gmtime([seconds]) -> (tm_year, tm_mon, tm_mday, tm_hour, tm_min,\n tm_sec, tm_wday, tm_yday, tm_isdst)\n\nConvert seconds since the Epoch to a time tuple expressing UTC (a.k.a.\nGMT). When 'seconds' is not passed in, convert the current time instead.\n\nIf the platform supports the tm_gmtoff and tm_zone, they are available as\nattributes only.", + "time.localtime" => "localtime([seconds]) -> (tm_year,tm_mon,tm_mday,tm_hour,tm_min,\n tm_sec,tm_wday,tm_yday,tm_isdst)\n\nConvert seconds since the Epoch to a time tuple expressing local time.\nWhen 'seconds' is not passed in, convert the current time instead.", + "time.mktime" => "mktime(tuple) -> floating-point number\n\nConvert a time tuple in local time to seconds since the Epoch.\nNote that mktime(gmtime(0)) will not generally return zero for most\ntime zones; instead the returned value will either be equal to that\nof the timezone or altzone attributes on the time module.", + "time.monotonic" => "monotonic() -> float\n\nMonotonic clock, cannot go backward.", + "time.monotonic_ns" => "monotonic_ns() -> int\n\nMonotonic clock, cannot go backward, as nanoseconds.", + "time.perf_counter" => "perf_counter() -> float\n\nPerformance counter for benchmarking.", + "time.perf_counter_ns" => "perf_counter_ns() -> int\n\nPerformance counter for benchmarking as nanoseconds.", + "time.process_time" => "process_time() -> float\n\nProcess time for profiling: sum of the kernel and user-space CPU time.", + "time.process_time_ns" => "process_time() -> int\n\nProcess time for profiling as nanoseconds:\nsum of the kernel and user-space CPU time.", + "time.pthread_getcpuclockid" => "pthread_getcpuclockid(thread_id) -> int\n\nReturn the clk_id of a thread's CPU time clock.", + "time.sleep" => "sleep(seconds)\n\nDelay execution for a given number of seconds. The argument may be\na floating-point number for subsecond precision.", + "time.strftime" => "strftime(format[, tuple]) -> string\n\nConvert a time tuple to a string according to a format specification.\nSee the library reference manual for formatting codes. When the time tuple\nis not present, current time as returned by localtime() is used.\n\nCommonly used format codes:\n\n%Y Year with century as a decimal number.\n%m Month as a decimal number [01,12].\n%d Day of the month as a decimal number [01,31].\n%H Hour (24-hour clock) as a decimal number [00,23].\n%M Minute as a decimal number [00,59].\n%S Second as a decimal number [00,61].\n%z Time zone offset from UTC.\n%a Locale's abbreviated weekday name.\n%A Locale's full weekday name.\n%b Locale's abbreviated month name.\n%B Locale's full month name.\n%c Locale's appropriate date and time representation.\n%I Hour (12-hour clock) as a decimal number [01,12].\n%p Locale's equivalent of either AM or PM.\n\nOther codes may be available on your platform. See documentation for\nthe C library strftime function.", + "time.strptime" => "strptime(string, format) -> struct_time\n\nParse a string to a time tuple according to a format specification.\nSee the library reference manual for formatting codes (same as\nstrftime()).\n\nCommonly used format codes:\n\n%Y Year with century as a decimal number.\n%m Month as a decimal number [01,12].\n%d Day of the month as a decimal number [01,31].\n%H Hour (24-hour clock) as a decimal number [00,23].\n%M Minute as a decimal number [00,59].\n%S Second as a decimal number [00,61].\n%z Time zone offset from UTC.\n%a Locale's abbreviated weekday name.\n%A Locale's full weekday name.\n%b Locale's abbreviated month name.\n%B Locale's full month name.\n%c Locale's appropriate date and time representation.\n%I Hour (12-hour clock) as a decimal number [01,12].\n%p Locale's equivalent of either AM or PM.\n\nOther codes may be available on your platform. See documentation for\nthe C library strftime function.", + "time.struct_time" => "The time value as returned by gmtime(), localtime(), and strptime(), and\naccepted by asctime(), mktime() and strftime(). May be considered as a\nsequence of 9 integers.\n\nNote that several fields' values are not the same as those defined by\nthe C language standard for struct tm. For example, the value of the\nfield tm_year is the actual year, not year - 1900. See individual\nfields' descriptions for details.", + "time.struct_time.__add__" => "Return self+value.", + "time.struct_time.__class_getitem__" => "See PEP 585", + "time.struct_time.__contains__" => "Return bool(key in self).", + "time.struct_time.__delattr__" => "Implement delattr(self, name).", + "time.struct_time.__eq__" => "Return self==value.", + "time.struct_time.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "time.struct_time.__ge__" => "Return self>=value.", + "time.struct_time.__getattribute__" => "Return getattr(self, name).", + "time.struct_time.__getitem__" => "Return self[key].", + "time.struct_time.__getstate__" => "Helper for pickle.", + "time.struct_time.__gt__" => "Return self>value.", + "time.struct_time.__hash__" => "Return hash(self).", + "time.struct_time.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "time.struct_time.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "time.struct_time.__iter__" => "Implement iter(self).", + "time.struct_time.__le__" => "Return self<=value.", + "time.struct_time.__len__" => "Return len(self).", + "time.struct_time.__lt__" => "Return self<value.", + "time.struct_time.__mul__" => "Return self*value.", + "time.struct_time.__ne__" => "Return self!=value.", + "time.struct_time.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "time.struct_time.__reduce_ex__" => "Helper for pickle.", + "time.struct_time.__replace__" => "Return a copy of the structure with new values for the specified fields.", + "time.struct_time.__repr__" => "Return repr(self).", + "time.struct_time.__rmul__" => "Return value*self.", + "time.struct_time.__setattr__" => "Implement setattr(self, name, value).", + "time.struct_time.__sizeof__" => "Size of object in memory, in bytes.", + "time.struct_time.__str__" => "Return str(self).", + "time.struct_time.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "time.struct_time.count" => "Return number of occurrences of value.", + "time.struct_time.index" => "Return first index of value.\n\nRaises ValueError if the value is not present.", + "time.struct_time.tm_gmtoff" => "offset from UTC in seconds", + "time.struct_time.tm_hour" => "hours, range [0, 23]", + "time.struct_time.tm_isdst" => "1 if summer time is in effect, 0 if not, and -1 if unknown", + "time.struct_time.tm_mday" => "day of month, range [1, 31]", + "time.struct_time.tm_min" => "minutes, range [0, 59]", + "time.struct_time.tm_mon" => "month of year, range [1, 12]", + "time.struct_time.tm_sec" => "seconds, range [0, 61])", + "time.struct_time.tm_wday" => "day of week, range [0, 6], Monday is 0", + "time.struct_time.tm_yday" => "day of year, range [1, 366]", + "time.struct_time.tm_year" => "year, for example, 1993", + "time.struct_time.tm_zone" => "abbreviation of timezone name", + "time.thread_time" => "thread_time() -> float\n\nThread time for profiling: sum of the kernel and user-space CPU time.", + "time.thread_time_ns" => "thread_time() -> int\n\nThread time for profiling as nanoseconds:\nsum of the kernel and user-space CPU time.", + "time.time" => "time() -> floating-point number\n\nReturn the current time in seconds since the Epoch.\nFractions of a second may be present if the system clock provides them.", + "time.time_ns" => "time_ns() -> int\n\nReturn the current time in nanoseconds since the Epoch.", + "time.tzset" => "tzset()\n\nInitialize, or reinitialize, the local timezone to the value stored in\nos.environ['TZ']. The TZ environment variable should be specified in\nstandard Unix timezone format as documented in the tzset man page\n(eg. 'US/Eastern', 'Europe/Amsterdam'). Unknown timezones will silently\nfall back to UTC. If the TZ environment variable is not set, the local\ntimezone is set to the systems best guess of wallclock time.\nChanging the TZ environment variable without calling tzset *may* change\nthe local timezone used by methods such as localtime, but this behaviour\nshould not be relied on.", + "unicodedata" => "This module provides access to the Unicode Character Database which\ndefines character properties for all Unicode characters. The data in\nthis database is based on the UnicodeData.txt file version\n15.1.0 which is publicly available from ftp://ftp.unicode.org/.\n\nThe module uses the same names and symbols as defined by the\nUnicodeData File Format 15.1.0.", + "unicodedata.UCD.__delattr__" => "Implement delattr(self, name).", + "unicodedata.UCD.__eq__" => "Return self==value.", + "unicodedata.UCD.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "unicodedata.UCD.__ge__" => "Return self>=value.", + "unicodedata.UCD.__getattribute__" => "Return getattr(self, name).", + "unicodedata.UCD.__getstate__" => "Helper for pickle.", + "unicodedata.UCD.__gt__" => "Return self>value.", + "unicodedata.UCD.__hash__" => "Return hash(self).", + "unicodedata.UCD.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "unicodedata.UCD.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "unicodedata.UCD.__le__" => "Return self<=value.", + "unicodedata.UCD.__lt__" => "Return self<value.", + "unicodedata.UCD.__ne__" => "Return self!=value.", + "unicodedata.UCD.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "unicodedata.UCD.__reduce__" => "Helper for pickle.", + "unicodedata.UCD.__reduce_ex__" => "Helper for pickle.", + "unicodedata.UCD.__repr__" => "Return repr(self).", + "unicodedata.UCD.__setattr__" => "Implement setattr(self, name, value).", + "unicodedata.UCD.__sizeof__" => "Size of object in memory, in bytes.", + "unicodedata.UCD.__str__" => "Return str(self).", + "unicodedata.UCD.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "unicodedata.UCD.bidirectional" => "Returns the bidirectional class assigned to the character chr as string.\n\nIf no such value is defined, an empty string is returned.", + "unicodedata.UCD.category" => "Returns the general category assigned to the character chr as string.", + "unicodedata.UCD.combining" => "Returns the canonical combining class assigned to the character chr as integer.\n\nReturns 0 if no combining class is defined.", + "unicodedata.UCD.decimal" => "Converts a Unicode character into its equivalent decimal value.\n\nReturns the decimal value assigned to the character chr as integer.\nIf no such value is defined, default is returned, or, if not given,\nValueError is raised.", + "unicodedata.UCD.decomposition" => "Returns the character decomposition mapping assigned to the character chr as string.\n\nAn empty string is returned in case no such mapping is defined.", + "unicodedata.UCD.digit" => "Converts a Unicode character into its equivalent digit value.\n\nReturns the digit value assigned to the character chr as integer.\nIf no such value is defined, default is returned, or, if not given,\nValueError is raised.", + "unicodedata.UCD.east_asian_width" => "Returns the east asian width assigned to the character chr as string.", + "unicodedata.UCD.is_normalized" => "Return whether the Unicode string unistr is in the normal form 'form'.\n\nValid values for form are 'NFC', 'NFKC', 'NFD', and 'NFKD'.", + "unicodedata.UCD.lookup" => "Look up character by name.\n\nIf a character with the given name is found, return the\ncorresponding character. If not found, KeyError is raised.", + "unicodedata.UCD.mirrored" => "Returns the mirrored property assigned to the character chr as integer.\n\nReturns 1 if the character has been identified as a \"mirrored\"\ncharacter in bidirectional text, 0 otherwise.", + "unicodedata.UCD.name" => "Returns the name assigned to the character chr as a string.\n\nIf no name is defined, default is returned, or, if not given,\nValueError is raised.", + "unicodedata.UCD.normalize" => "Return the normal form 'form' for the Unicode string unistr.\n\nValid values for form are 'NFC', 'NFKC', 'NFD', and 'NFKD'.", + "unicodedata.UCD.numeric" => "Converts a Unicode character into its equivalent numeric value.\n\nReturns the numeric value assigned to the character chr as float.\nIf no such value is defined, default is returned, or, if not given,\nValueError is raised.", + "unicodedata.bidirectional" => "Returns the bidirectional class assigned to the character chr as string.\n\nIf no such value is defined, an empty string is returned.", + "unicodedata.category" => "Returns the general category assigned to the character chr as string.", + "unicodedata.combining" => "Returns the canonical combining class assigned to the character chr as integer.\n\nReturns 0 if no combining class is defined.", + "unicodedata.decimal" => "Converts a Unicode character into its equivalent decimal value.\n\nReturns the decimal value assigned to the character chr as integer.\nIf no such value is defined, default is returned, or, if not given,\nValueError is raised.", + "unicodedata.decomposition" => "Returns the character decomposition mapping assigned to the character chr as string.\n\nAn empty string is returned in case no such mapping is defined.", + "unicodedata.digit" => "Converts a Unicode character into its equivalent digit value.\n\nReturns the digit value assigned to the character chr as integer.\nIf no such value is defined, default is returned, or, if not given,\nValueError is raised.", + "unicodedata.east_asian_width" => "Returns the east asian width assigned to the character chr as string.", + "unicodedata.is_normalized" => "Return whether the Unicode string unistr is in the normal form 'form'.\n\nValid values for form are 'NFC', 'NFKC', 'NFD', and 'NFKD'.", + "unicodedata.lookup" => "Look up character by name.\n\nIf a character with the given name is found, return the\ncorresponding character. If not found, KeyError is raised.", + "unicodedata.mirrored" => "Returns the mirrored property assigned to the character chr as integer.\n\nReturns 1 if the character has been identified as a \"mirrored\"\ncharacter in bidirectional text, 0 otherwise.", + "unicodedata.name" => "Returns the name assigned to the character chr as a string.\n\nIf no name is defined, default is returned, or, if not given,\nValueError is raised.", + "unicodedata.normalize" => "Return the normal form 'form' for the Unicode string unistr.\n\nValid values for form are 'NFC', 'NFKC', 'NFD', and 'NFKD'.", + "unicodedata.numeric" => "Converts a Unicode character into its equivalent numeric value.\n\nReturns the numeric value assigned to the character chr as float.\nIf no such value is defined, default is returned, or, if not given,\nValueError is raised.", + "winreg" => "This module provides access to the Windows registry API.\n\nFunctions:\n\nCloseKey() - Closes a registry key.\nConnectRegistry() - Establishes a connection to a predefined registry handle\n on another computer.\nCreateKey() - Creates the specified key, or opens it if it already exists.\nDeleteKey() - Deletes the specified key.\nDeleteValue() - Removes a named value from the specified registry key.\nEnumKey() - Enumerates subkeys of the specified open registry key.\nEnumValue() - Enumerates values of the specified open registry key.\nExpandEnvironmentStrings() - Expand the env strings in a REG_EXPAND_SZ\n string.\nFlushKey() - Writes all the attributes of the specified key to the registry.\nLoadKey() - Creates a subkey under HKEY_USER or HKEY_LOCAL_MACHINE and\n stores registration information from a specified file into that\n subkey.\nOpenKey() - Opens the specified key.\nOpenKeyEx() - Alias of OpenKey().\nQueryValue() - Retrieves the value associated with the unnamed value for a\n specified key in the registry.\nQueryValueEx() - Retrieves the type and data for a specified value name\n associated with an open registry key.\nQueryInfoKey() - Returns information about the specified key.\nSaveKey() - Saves the specified key, and all its subkeys a file.\nSetValue() - Associates a value with a specified key.\nSetValueEx() - Stores data in the value field of an open registry key.\n\nSpecial objects:\n\nHKEYType -- type object for HKEY objects\nerror -- exception raised for Win32 errors\n\nInteger constants:\nMany constants are defined - see the documentation for each function\nto see what constants are used, and where.", + "winreg.CloseKey" => "Closes a previously opened registry key.\n\n hkey\n A previously opened key.\n\nNote that if the key is not closed using this method, it will be\nclosed when the hkey object is destroyed by Python.", + "winreg.ConnectRegistry" => "Establishes a connection to the registry on another computer.\n\n computer_name\n The name of the remote computer, of the form r\"\\\\computername\". If\n None, the local computer is used.\n key\n The predefined key to connect to.\n\nThe return value is the handle of the opened key.\nIf the function fails, an OSError exception is raised.", + "winreg.CreateKey" => "Creates or opens the specified key.\n\n key\n An already open key, or one of the predefined HKEY_* constants.\n sub_key\n The name of the key this method opens or creates.\n\nIf key is one of the predefined keys, sub_key may be None. In that case,\nthe handle returned is the same key handle passed in to the function.\n\nIf the key already exists, this function opens the existing key.\n\nThe return value is the handle of the opened key.\nIf the function fails, an OSError exception is raised.", + "winreg.CreateKeyEx" => "Creates or opens the specified key.\n\n key\n An already open key, or one of the predefined HKEY_* constants.\n sub_key\n The name of the key this method opens or creates.\n reserved\n A reserved integer, and must be zero. Default is zero.\n access\n An integer that specifies an access mask that describes the\n desired security access for the key. Default is KEY_WRITE.\n\nIf key is one of the predefined keys, sub_key may be None. In that case,\nthe handle returned is the same key handle passed in to the function.\n\nIf the key already exists, this function opens the existing key\n\nThe return value is the handle of the opened key.\nIf the function fails, an OSError exception is raised.", + "winreg.DeleteKey" => "Deletes the specified key.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n sub_key\n A string that must be the name of a subkey of the key identified by\n the key parameter. This value must not be None, and the key may not\n have subkeys.\n\nThis method can not delete keys with subkeys.\n\nIf the function succeeds, the entire key, including all of its values,\nis removed. If the function fails, an OSError exception is raised.", + "winreg.DeleteKeyEx" => "Deletes the specified key (intended for 64-bit OS).\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n sub_key\n A string that must be the name of a subkey of the key identified by\n the key parameter. This value must not be None, and the key may not\n have subkeys.\n access\n An integer that specifies an access mask that describes the\n desired security access for the key. Default is KEY_WOW64_64KEY.\n reserved\n A reserved integer, and must be zero. Default is zero.\n\nWhile this function is intended to be used for 64-bit OS, it is also\n available on 32-bit systems.\n\nThis method can not delete keys with subkeys.\n\nIf the function succeeds, the entire key, including all of its values,\nis removed. If the function fails, an OSError exception is raised.\nOn unsupported Windows versions, NotImplementedError is raised.", + "winreg.DeleteValue" => "Removes a named value from a registry key.\n\nkey\n An already open key, or any one of the predefined HKEY_* constants.\nvalue\n A string that identifies the value to remove.", + "winreg.DisableReflectionKey" => "Disables registry reflection for 32bit processes running on a 64bit OS.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n\nWill generally raise NotImplementedError if executed on a 32bit OS.\n\nIf the key is not on the reflection list, the function succeeds but has\nno effect. Disabling reflection for a key does not affect reflection\nof any subkeys.", + "winreg.EnableReflectionKey" => "Restores registry reflection for the specified disabled key.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n\nWill generally raise NotImplementedError if executed on a 32bit OS.\nRestoring reflection for a key does not affect reflection of any\nsubkeys.", + "winreg.EnumKey" => "Enumerates subkeys of an open registry key.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n index\n An integer that identifies the index of the key to retrieve.\n\nThe function retrieves the name of one subkey each time it is called.\nIt is typically called repeatedly until an OSError exception is\nraised, indicating no more values are available.", + "winreg.EnumValue" => "Enumerates values of an open registry key.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n index\n An integer that identifies the index of the value to retrieve.\n\nThe function retrieves the name of one subkey each time it is called.\nIt is typically called repeatedly, until an OSError exception\nis raised, indicating no more values.\n\nThe result is a tuple of 3 items:\n value_name\n A string that identifies the value.\n value_data\n An object that holds the value data, and whose type depends\n on the underlying registry type.\n data_type\n An integer that identifies the type of the value data.", + "winreg.ExpandEnvironmentStrings" => "Expand environment vars.", + "winreg.FlushKey" => "Writes all the attributes of a key to the registry.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n\nIt is not necessary to call FlushKey to change a key. Registry changes\nare flushed to disk by the registry using its lazy flusher. Registry\nchanges are also flushed to disk at system shutdown. Unlike\nCloseKey(), the FlushKey() method returns only when all the data has\nbeen written to the registry.\n\nAn application should only call FlushKey() if it requires absolute\ncertainty that registry changes are on disk. If you don't know whether\na FlushKey() call is required, it probably isn't.", + "winreg.HKEYType" => "PyHKEY Object - A Python object, representing a win32 registry key.\n\nThis object wraps a Windows HKEY object, automatically closing it when\nthe object is destroyed. To guarantee cleanup, you can call either\nthe Close() method on the PyHKEY, or the CloseKey() method.\n\nAll functions which accept a handle object also accept an integer --\nhowever, use of the handle object is encouraged.\n\nFunctions:\nClose() - Closes the underlying handle.\nDetach() - Returns the integer Win32 handle, detaching it from the object\n\nProperties:\nhandle - The integer Win32 handle.\n\nOperations:\n__bool__ - Handles with an open object return true, otherwise false.\n__int__ - Converting a handle to an integer returns the Win32 handle.\nrich comparison - Handle objects are compared using the handle value.", + "winreg.HKEYType.Close" => "Closes the underlying Windows handle.\n\nIf the handle is already closed, no error is raised.", + "winreg.HKEYType.Detach" => "Detaches the Windows handle from the handle object.\n\nThe result is the value of the handle before it is detached. If the\nhandle is already detached, this will return zero.\n\nAfter calling this function, the handle is effectively invalidated,\nbut the handle is not closed. You would call this function when you\nneed the underlying win32 handle to exist beyond the lifetime of the\nhandle object.", + "winreg.HKEYType.__abs__" => "abs(self)", + "winreg.HKEYType.__add__" => "Return self+value.", + "winreg.HKEYType.__and__" => "Return self&value.", + "winreg.HKEYType.__bool__" => "True if self else False", + "winreg.HKEYType.__delattr__" => "Implement delattr(self, name).", + "winreg.HKEYType.__divmod__" => "Return divmod(self, value).", + "winreg.HKEYType.__eq__" => "Return self==value.", + "winreg.HKEYType.__float__" => "float(self)", + "winreg.HKEYType.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "winreg.HKEYType.__ge__" => "Return self>=value.", + "winreg.HKEYType.__getattribute__" => "Return getattr(self, name).", + "winreg.HKEYType.__getstate__" => "Helper for pickle.", + "winreg.HKEYType.__gt__" => "Return self>value.", + "winreg.HKEYType.__hash__" => "Return hash(self).", + "winreg.HKEYType.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "winreg.HKEYType.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "winreg.HKEYType.__int__" => "int(self)", + "winreg.HKEYType.__invert__" => "~self", + "winreg.HKEYType.__le__" => "Return self<=value.", + "winreg.HKEYType.__lshift__" => "Return self<<value.", + "winreg.HKEYType.__lt__" => "Return self<value.", + "winreg.HKEYType.__mod__" => "Return self%value.", + "winreg.HKEYType.__mul__" => "Return self*value.", + "winreg.HKEYType.__ne__" => "Return self!=value.", + "winreg.HKEYType.__neg__" => "-self", + "winreg.HKEYType.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "winreg.HKEYType.__or__" => "Return self|value.", + "winreg.HKEYType.__pos__" => "+self", + "winreg.HKEYType.__pow__" => "Return pow(self, value, mod).", + "winreg.HKEYType.__radd__" => "Return value+self.", + "winreg.HKEYType.__rand__" => "Return value&self.", + "winreg.HKEYType.__rdivmod__" => "Return divmod(value, self).", + "winreg.HKEYType.__reduce__" => "Helper for pickle.", + "winreg.HKEYType.__reduce_ex__" => "Helper for pickle.", + "winreg.HKEYType.__repr__" => "Return repr(self).", + "winreg.HKEYType.__rlshift__" => "Return value<<self.", + "winreg.HKEYType.__rmod__" => "Return value%self.", + "winreg.HKEYType.__rmul__" => "Return value*self.", + "winreg.HKEYType.__ror__" => "Return value|self.", + "winreg.HKEYType.__rpow__" => "Return pow(value, self, mod).", + "winreg.HKEYType.__rrshift__" => "Return value>>self.", + "winreg.HKEYType.__rshift__" => "Return self>>value.", + "winreg.HKEYType.__rsub__" => "Return value-self.", + "winreg.HKEYType.__rxor__" => "Return value^self.", + "winreg.HKEYType.__setattr__" => "Implement setattr(self, name, value).", + "winreg.HKEYType.__sizeof__" => "Size of object in memory, in bytes.", + "winreg.HKEYType.__str__" => "Return str(self).", + "winreg.HKEYType.__sub__" => "Return self-value.", + "winreg.HKEYType.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "winreg.HKEYType.__xor__" => "Return self^value.", + "winreg.LoadKey" => "Insert data into the registry from a file.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n sub_key\n A string that identifies the sub-key to load.\n file_name\n The name of the file to load registry data from. This file must\n have been created with the SaveKey() function. Under the file\n allocation table (FAT) file system, the filename may not have an\n extension.\n\nCreates a subkey under the specified key and stores registration\ninformation from a specified file into that subkey.\n\nA call to LoadKey() fails if the calling process does not have the\nSE_RESTORE_PRIVILEGE privilege.\n\nIf key is a handle returned by ConnectRegistry(), then the path\nspecified in fileName is relative to the remote computer.\n\nThe MSDN docs imply key must be in the HKEY_USER or HKEY_LOCAL_MACHINE\ntree.", + "winreg.OpenKey" => "Opens the specified key.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n sub_key\n A string that identifies the sub_key to open.\n reserved\n A reserved integer that must be zero. Default is zero.\n access\n An integer that specifies an access mask that describes the desired\n security access for the key. Default is KEY_READ.\n\nThe result is a new handle to the specified key.\nIf the function fails, an OSError exception is raised.", + "winreg.OpenKeyEx" => "Opens the specified key.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n sub_key\n A string that identifies the sub_key to open.\n reserved\n A reserved integer that must be zero. Default is zero.\n access\n An integer that specifies an access mask that describes the desired\n security access for the key. Default is KEY_READ.\n\nThe result is a new handle to the specified key.\nIf the function fails, an OSError exception is raised.", + "winreg.QueryInfoKey" => "Returns information about a key.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n\nThe result is a tuple of 3 items:\nAn integer that identifies the number of sub keys this key has.\nAn integer that identifies the number of values this key has.\nAn integer that identifies when the key was last modified (if available)\nas 100's of nanoseconds since Jan 1, 1600.", + "winreg.QueryReflectionKey" => "Returns the reflection state for the specified key as a bool.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n\nWill generally raise NotImplementedError if executed on a 32bit OS.", + "winreg.QueryValue" => "Retrieves the unnamed value for a key.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n sub_key\n A string that holds the name of the subkey with which the value\n is associated. If this parameter is None or empty, the function\n retrieves the value set by the SetValue() method for the key\n identified by key.\n\nValues in the registry have name, type, and data components. This method\nretrieves the data for a key's first value that has a NULL name.\nBut since the underlying API call doesn't return the type, you'll\nprobably be happier using QueryValueEx; this function is just here for\ncompleteness.", + "winreg.QueryValueEx" => "Retrieves the type and value of a specified sub-key.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n name\n A string indicating the value to query.\n\nBehaves mostly like QueryValue(), but also returns the type of the\nspecified value name associated with the given open registry key.\n\nThe return value is a tuple of the value and the type_id.", + "winreg.SaveKey" => "Saves the specified key, and all its subkeys to the specified file.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n file_name\n The name of the file to save registry data to. This file cannot\n already exist. If this filename includes an extension, it cannot be\n used on file allocation table (FAT) file systems by the LoadKey(),\n ReplaceKey() or RestoreKey() methods.\n\nIf key represents a key on a remote computer, the path described by\nfile_name is relative to the remote computer.\n\nThe caller of this method must possess the SeBackupPrivilege\nsecurity privilege. This function passes NULL for security_attributes\nto the API.", + "winreg.SetValue" => "Associates a value with a specified key.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n sub_key\n A string that names the subkey with which the value is associated.\n type\n An integer that specifies the type of the data. Currently this must\n be REG_SZ, meaning only strings are supported.\n value\n A string that specifies the new value.\n\nIf the key specified by the sub_key parameter does not exist, the\nSetValue function creates it.\n\nValue lengths are limited by available memory. Long values (more than\n2048 bytes) should be stored as files with the filenames stored in\nthe configuration registry to help the registry perform efficiently.\n\nThe key identified by the key parameter must have been opened with\nKEY_SET_VALUE access.", + "winreg.SetValueEx" => "Stores data in the value field of an open registry key.\n\n key\n An already open key, or any one of the predefined HKEY_* constants.\n value_name\n A string containing the name of the value to set, or None.\n reserved\n Can be anything - zero is always passed to the API.\n type\n An integer that specifies the type of the data, one of:\n REG_BINARY -- Binary data in any form.\n REG_DWORD -- A 32-bit number.\n REG_DWORD_LITTLE_ENDIAN -- A 32-bit number in little-endian format. Equivalent to REG_DWORD\n REG_DWORD_BIG_ENDIAN -- A 32-bit number in big-endian format.\n REG_EXPAND_SZ -- A null-terminated string that contains unexpanded\n references to environment variables (for example,\n %PATH%).\n REG_LINK -- A Unicode symbolic link.\n REG_MULTI_SZ -- A sequence of null-terminated strings, terminated\n by two null characters. Note that Python handles\n this termination automatically.\n REG_NONE -- No defined value type.\n REG_QWORD -- A 64-bit number.\n REG_QWORD_LITTLE_ENDIAN -- A 64-bit number in little-endian format. Equivalent to REG_QWORD.\n REG_RESOURCE_LIST -- A device-driver resource list.\n REG_SZ -- A null-terminated string.\n value\n A string that specifies the new value.\n\nThis method can also set additional value and type information for the\nspecified key. The key identified by the key parameter must have been\nopened with KEY_SET_VALUE access.\n\nTo open the key, use the CreateKeyEx() or OpenKeyEx() methods.\n\nValue lengths are limited by available memory. Long values (more than\n2048 bytes) should be stored as files with the filenames stored in\nthe configuration registry to help the registry perform efficiently.", + "winsound" => "PlaySound(sound, flags) - play a sound\nSND_FILENAME - sound is a wav file name\nSND_ALIAS - sound is a registry sound association name\nSND_LOOP - Play the sound repeatedly; must also specify SND_ASYNC\nSND_MEMORY - sound is a memory image of a wav file\nSND_PURGE - stop all instances of the specified sound\nSND_ASYNC - PlaySound returns immediately\nSND_NODEFAULT - Do not play a default beep if the sound can not be found\nSND_NOSTOP - Do not interrupt any sounds currently playing\nSND_NOWAIT - Return immediately if the sound driver is busy\nSND_APPLICATION - sound is an application-specific alias in the registry.\nBeep(frequency, duration) - Make a beep through the PC speaker.\nMessageBeep(type) - Call Windows MessageBeep.", + "winsound.Beep" => "A wrapper around the Windows Beep API.\n\nfrequency\n Frequency of the sound in hertz.\n Must be in the range 37 through 32,767.\nduration\n How long the sound should play, in milliseconds.", + "winsound.MessageBeep" => "Call Windows MessageBeep(x).\n\nx defaults to MB_OK.", + "winsound.PlaySound" => "A wrapper around the Windows PlaySound API.\n\nsound\n The sound to play; a filename, data, or None.\nflags\n Flag values, ored together. See module documentation.", + "zlib" => "The functions in this module allow compression and decompression using the\nzlib library, which is based on GNU zip.\n\nadler32(string[, start]) -- Compute an Adler-32 checksum.\ncompress(data[, level]) -- Compress data, with compression level 0-9 or -1.\ncompressobj([level[, ...]]) -- Return a compressor object.\ncrc32(string[, start]) -- Compute a CRC-32 checksum.\ndecompress(string,[wbits],[bufsize]) -- Decompresses a compressed string.\ndecompressobj([wbits[, zdict]]) -- Return a decompressor object.\n\n'wbits' is window buffer size and container format.\nCompressor objects support compress() and flush() methods; decompressor\nobjects support decompress() and flush().", + "zlib._ZlibDecompressor" => "Create a decompressor object for decompressing data incrementally.\n\nwbits = 15\nzdict\n The predefined compression dictionary. This is a sequence of bytes\n (such as a bytes object) containing subsequences that are expected\n to occur frequently in the data that is to be compressed. Those\n subsequences that are expected to be most common should come at the\n end of the dictionary. This must be the same dictionary as used by the\n compressor that produced the input data.", + "zlib._ZlibDecompressor.__delattr__" => "Implement delattr(self, name).", + "zlib._ZlibDecompressor.__eq__" => "Return self==value.", + "zlib._ZlibDecompressor.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "zlib._ZlibDecompressor.__ge__" => "Return self>=value.", + "zlib._ZlibDecompressor.__getattribute__" => "Return getattr(self, name).", + "zlib._ZlibDecompressor.__getstate__" => "Helper for pickle.", + "zlib._ZlibDecompressor.__gt__" => "Return self>value.", + "zlib._ZlibDecompressor.__hash__" => "Return hash(self).", + "zlib._ZlibDecompressor.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "zlib._ZlibDecompressor.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "zlib._ZlibDecompressor.__le__" => "Return self<=value.", + "zlib._ZlibDecompressor.__lt__" => "Return self<value.", + "zlib._ZlibDecompressor.__ne__" => "Return self!=value.", + "zlib._ZlibDecompressor.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "zlib._ZlibDecompressor.__reduce__" => "Helper for pickle.", + "zlib._ZlibDecompressor.__reduce_ex__" => "Helper for pickle.", + "zlib._ZlibDecompressor.__repr__" => "Return repr(self).", + "zlib._ZlibDecompressor.__setattr__" => "Implement setattr(self, name, value).", + "zlib._ZlibDecompressor.__sizeof__" => "Size of object in memory, in bytes.", + "zlib._ZlibDecompressor.__str__" => "Return str(self).", + "zlib._ZlibDecompressor.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "zlib._ZlibDecompressor.decompress" => "Decompress *data*, returning uncompressed data as bytes.\n\nIf *max_length* is nonnegative, returns at most *max_length* bytes of\ndecompressed data. If this limit is reached and further output can be\nproduced, *self.needs_input* will be set to ``False``. In this case, the next\ncall to *decompress()* may provide *data* as b'' to obtain more of the output.\n\nIf all of the input data was decompressed and returned (either because this\nwas less than *max_length* bytes, or because *max_length* was negative),\n*self.needs_input* will be set to True.\n\nAttempting to decompress data after the end of stream is reached raises an\nEOFError. Any data found after the end of the stream is ignored and saved in\nthe unused_data attribute.", + "zlib._ZlibDecompressor.eof" => "True if the end-of-stream marker has been reached.", + "zlib._ZlibDecompressor.needs_input" => "True if more input is needed before more decompressed data can be produced.", + "zlib._ZlibDecompressor.unused_data" => "Data found after the end of the compressed stream.", + "zlib.adler32" => "Compute an Adler-32 checksum of data.\n\n value\n Starting value of the checksum.\n\nThe returned checksum is an integer.", + "zlib.compress" => "Returns a bytes object containing compressed data.\n\ndata\n Binary data to be compressed.\nlevel\n Compression level, in 0-9 or -1.\nwbits\n The window buffer size and container format.", + "zlib.compressobj" => "Return a compressor object.\n\nlevel\n The compression level (an integer in the range 0-9 or -1; default is\n currently equivalent to 6). Higher compression levels are slower,\n but produce smaller results.\nmethod\n The compression algorithm. If given, this must be DEFLATED.\nwbits\n +9 to +15: The base-two logarithm of the window size. Include a zlib\n container.\n -9 to -15: Generate a raw stream.\n +25 to +31: Include a gzip container.\nmemLevel\n Controls the amount of memory used for internal compression state.\n Valid values range from 1 to 9. Higher values result in higher memory\n usage, faster compression, and smaller output.\nstrategy\n Used to tune the compression algorithm. Possible values are\n Z_DEFAULT_STRATEGY, Z_FILTERED, and Z_HUFFMAN_ONLY.\nzdict\n The predefined compression dictionary - a sequence of bytes\n containing subsequences that are likely to occur in the input data.", + "zlib.crc32" => "Compute a CRC-32 checksum of data.\n\n value\n Starting value of the checksum.\n\nThe returned checksum is an integer.", + "zlib.decompress" => "Returns a bytes object containing the uncompressed data.\n\ndata\n Compressed data.\nwbits\n The window buffer size and container format.\nbufsize\n The initial output buffer size.", + "zlib.decompressobj" => "Return a decompressor object.\n\nwbits\n The window buffer size and container format.\nzdict\n The predefined compression dictionary. This must be the same\n dictionary as used by the compressor that produced the input data.", + "zlib.error.__cause__" => "exception cause", + "zlib.error.__context__" => "exception context", + "zlib.error.__delattr__" => "Implement delattr(self, name).", + "zlib.error.__eq__" => "Return self==value.", + "zlib.error.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "zlib.error.__ge__" => "Return self>=value.", + "zlib.error.__getattribute__" => "Return getattr(self, name).", + "zlib.error.__getstate__" => "Helper for pickle.", + "zlib.error.__gt__" => "Return self>value.", + "zlib.error.__hash__" => "Return hash(self).", + "zlib.error.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "zlib.error.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "zlib.error.__le__" => "Return self<=value.", + "zlib.error.__lt__" => "Return self<value.", + "zlib.error.__ne__" => "Return self!=value.", + "zlib.error.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "zlib.error.__reduce_ex__" => "Helper for pickle.", + "zlib.error.__repr__" => "Return repr(self).", + "zlib.error.__setattr__" => "Implement setattr(self, name, value).", + "zlib.error.__sizeof__" => "Size of object in memory, in bytes.", + "zlib.error.__str__" => "Return str(self).", + "zlib.error.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "zlib.error.__weakref__" => "list of weak references to the object", + "zlib.error.add_note" => "Exception.add_note(note) --\nadd a note to the exception", + "zlib.error.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", +}; diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs new file mode 100644 index 00000000000..cc81f17fc55 --- /dev/null +++ b/crates/doc/src/lib.rs @@ -0,0 +1,12 @@ +include!("./data.inc.rs"); + +#[cfg(test)] +mod test { + use super::DB; + + #[test] + fn test_db() { + let doc = DB.get("array._array_reconstructor"); + assert!(doc.is_some()); + } +} diff --git a/derive-impl/Cargo.toml b/derive-impl/Cargo.toml index 66ed67cef81..a772cab1d38 100644 --- a/derive-impl/Cargo.toml +++ b/derive-impl/Cargo.toml @@ -23,4 +23,4 @@ syn-ext = { version = "0.5.0", features = ["full"] } textwrap = { version = "0.16.1", default-features = false } [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/derive-impl/src/lib.rs b/derive-impl/src/lib.rs index a1f97c96b0e..786c77e3212 100644 --- a/derive-impl/src/lib.rs +++ b/derive-impl/src/lib.rs @@ -23,7 +23,6 @@ mod pytraverse; use error::Diagnostic; use proc_macro2::TokenStream; use quote::ToTokens; -use rustpython_doc as doc; use syn::{DeriveInput, Item}; use syn_ext::types::PunctuatedNestedMeta; diff --git a/derive-impl/src/pyclass.rs b/derive-impl/src/pyclass.rs index 0b0769cac3c..84559e3574e 100644 --- a/derive-impl/src/pyclass.rs +++ b/derive-impl/src/pyclass.rs @@ -6,6 +6,7 @@ use crate::util::{ }; use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree}; use quote::{ToTokens, quote, quote_spanned}; +use rustpython_doc::DB; use std::collections::{HashMap, HashSet}; use std::str::FromStr; use syn::{Attribute, Ident, Item, Result, parse_quote, spanned::Spanned}; @@ -315,10 +316,8 @@ fn generate_class_def( ) -> Result<TokenStream> { let doc = attrs.doc().or_else(|| { let module_name = module_name.unwrap_or("builtins"); - crate::doc::Database::shared() - .try_module_item(module_name, name) - .ok() - .flatten() + DB.get(&format!("{module_name}.{name}")) + .copied() .map(str::to_owned) }); let doc = if let Some(doc) = doc { diff --git a/derive-impl/src/pymodule.rs b/derive-impl/src/pymodule.rs index a8588a762ef..9db7128b3a1 100644 --- a/derive-impl/src/pymodule.rs +++ b/derive-impl/src/pymodule.rs @@ -6,6 +6,7 @@ use crate::util::{ }; use proc_macro2::{Delimiter, Group, TokenStream, TokenTree}; use quote::{ToTokens, quote, quote_spanned}; +use rustpython_doc::DB; use std::{collections::HashSet, str::FromStr}; use syn::{Attribute, Ident, Item, Result, parse_quote, spanned::Spanned}; use syn_ext::ext::*; @@ -100,13 +101,7 @@ pub fn impl_pymodule(attr: PunctuatedNestedMeta, module_item: Item) -> Result<To let module_name = context.name.as_str(); let function_items = context.function_items.validate()?; let attribute_items = context.attribute_items.validate()?; - let doc = doc.or_else(|| { - crate::doc::Database::shared() - .try_path(module_name) - .ok() - .flatten() - .map(str::to_owned) - }); + let doc = doc.or_else(|| DB.get(module_name).copied().map(str::to_owned)); let doc = if let Some(doc) = doc { quote!(Some(#doc)) } else { @@ -463,11 +458,10 @@ impl ModuleItem for FunctionItem { let sig_doc = text_signature(func.sig(), &py_name); let module = args.module_name(); + // TODO: doc must exist at least one of code or CPython let doc = args.attrs.doc().or_else(|| { - crate::doc::Database::shared() - .try_module_item(module, &py_name) - .ok() // TODO: doc must exist at least one of code or CPython - .flatten() + DB.get(&format!("{module}.{py_name}")) + .copied() .map(str::to_owned) }); let doc = if let Some(doc) = doc { From 609d99f1e3abb8adc87c0acf1e347d7248ab321d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:55:17 +0900 Subject: [PATCH 340/819] implement sys.implementation cache_tag (#6255) --- vm/src/stdlib/sys.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vm/src/stdlib/sys.rs b/vm/src/stdlib/sys.rs index d650f72443e..12a69c23138 100644 --- a/vm/src/stdlib/sys.rs +++ b/vm/src/stdlib/sys.rs @@ -229,11 +229,13 @@ mod sys { #[pyattr] fn implementation(vm: &VirtualMachine) -> PyRef<PyNamespace> { - // TODO: Add crate version to this namespace + const NAME: &str = "rustpython"; + + let cache_tag = format!("{NAME}-{}{}", version::MAJOR, version::MINOR); let ctx = &vm.ctx; py_namespace!(vm, { - "name" => ctx.new_str(ascii!("rustpython")), - "cache_tag" => ctx.new_str(ascii!("rustpython-01")), + "name" => ctx.new_str(NAME), + "cache_tag" => ctx.new_str(cache_tag), "_multiarch" => ctx.new_str(MULTIARCH.to_owned()), "version" => version_info(vm), "hexversion" => ctx.new_int(version::VERSION_HEX), From 5eac229eae35ed1c5547771ae9375badf091e7df Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 00:05:08 +0200 Subject: [PATCH 341/819] Move `pylib` -> `crates/pylib` (#6225) * Move `pylib * clean `build.rs` a bit --- Cargo.toml | 3 +-- {pylib => crates/pylib}/Cargo.toml | 0 crates/pylib/Lib | 1 + {pylib => crates/pylib}/build.rs | 8 +++++--- {pylib => crates/pylib}/src/lib.rs | 0 example_projects/frozen_stdlib/Cargo.toml | 2 +- pylib/Lib | 1 - 7 files changed, 8 insertions(+), 7 deletions(-) rename {pylib => crates/pylib}/Cargo.toml (100%) create mode 120000 crates/pylib/Lib rename {pylib => crates/pylib}/build.rs (80%) rename {pylib => crates/pylib}/src/lib.rs (100%) delete mode 120000 pylib/Lib diff --git a/Cargo.toml b/Cargo.toml index b01734ae7ae..09be0c4595d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,7 +131,6 @@ members = [ "jit", "vm", "vm/sre_engine", - "pylib", "stdlib", "derive-impl", "wtf8", @@ -157,7 +156,7 @@ rustpython-derive-impl = { path = "derive-impl", version = "0.4.0" } rustpython-jit = { path = "jit", version = "0.4.0" } rustpython-literal = { path = "compiler/literal", version = "0.4.0" } rustpython-vm = { path = "vm", default-features = false, version = "0.4.0" } -rustpython-pylib = { path = "pylib", version = "0.4.0" } +rustpython-pylib = { path = "crates/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-wtf8 = { path = "wtf8", version = "0.4.0" } diff --git a/pylib/Cargo.toml b/crates/pylib/Cargo.toml similarity index 100% rename from pylib/Cargo.toml rename to crates/pylib/Cargo.toml diff --git a/crates/pylib/Lib b/crates/pylib/Lib new file mode 120000 index 00000000000..5665252daf7 --- /dev/null +++ b/crates/pylib/Lib @@ -0,0 +1 @@ +../../Lib/ \ No newline at end of file diff --git a/pylib/build.rs b/crates/pylib/build.rs similarity index 80% rename from pylib/build.rs rename to crates/pylib/build.rs index f85c4b5cfb8..8885870e40f 100644 --- a/pylib/build.rs +++ b/crates/pylib/build.rs @@ -1,10 +1,12 @@ +const CRATE_ROOT: &str = "../.."; + fn main() { - process_python_libs("../vm/Lib/python_builtins/*"); + process_python_libs(format!("{CRATE_ROOT}/vm/Lib/python_builtins/*").as_str()); + process_python_libs(format!("{CRATE_ROOT}/vm/Lib/core_modules/*").as_str()); - process_python_libs("../vm/Lib/core_modules/*"); #[cfg(feature = "freeze-stdlib")] if cfg!(windows) { - process_python_libs("../Lib/**/*"); + process_python_libs(format!("{CRATE_ROOT}/Lib/**/*").as_str()); } else { process_python_libs("./Lib/**/*"); } diff --git a/pylib/src/lib.rs b/crates/pylib/src/lib.rs similarity index 100% rename from pylib/src/lib.rs rename to crates/pylib/src/lib.rs diff --git a/example_projects/frozen_stdlib/Cargo.toml b/example_projects/frozen_stdlib/Cargo.toml index 78a88988d8a..6824865cbf7 100644 --- a/example_projects/frozen_stdlib/Cargo.toml +++ b/example_projects/frozen_stdlib/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] rustpython = { path = "../../", default-features = false, features = ["freeze-stdlib"] } rustpython-vm = { path = "../../vm", default-features = false, features = ["freeze-stdlib"] } -rustpython-pylib = { path = "../../pylib", default-features = false, features = ["freeze-stdlib"] } +rustpython-pylib = { path = "../../crates/pylib", default-features = false, features = ["freeze-stdlib"] } [workspace] diff --git a/pylib/Lib b/pylib/Lib deleted file mode 120000 index 47f928ff4d5..00000000000 --- a/pylib/Lib +++ /dev/null @@ -1 +0,0 @@ -../Lib \ No newline at end of file From 4b7e49a17e5f86d60e49c6aa1b37e3a5ee13f0a4 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 00:19:53 +0200 Subject: [PATCH 342/819] Move `common` -> `crates/common` (#6256) --- Cargo.toml | 3 +-- {common => crates/common}/Cargo.toml | 0 {common => crates/common}/src/atomic.rs | 0 {common => crates/common}/src/borrow.rs | 0 {common => crates/common}/src/boxvec.rs | 0 {common => crates/common}/src/cformat.rs | 0 {common => crates/common}/src/crt_fd.rs | 0 {common => crates/common}/src/encodings.rs | 0 {common => crates/common}/src/fileutils.rs | 0 {common => crates/common}/src/float_ops.rs | 0 {common => crates/common}/src/format.rs | 0 {common => crates/common}/src/hash.rs | 0 {common => crates/common}/src/int.rs | 0 {common => crates/common}/src/lib.rs | 0 {common => crates/common}/src/linked_list.rs | 0 {common => crates/common}/src/lock.rs | 0 {common => crates/common}/src/lock/cell_lock.rs | 0 {common => crates/common}/src/lock/immutable_mutex.rs | 0 {common => crates/common}/src/lock/thread_mutex.rs | 0 {common => crates/common}/src/macros.rs | 0 {common => crates/common}/src/os.rs | 0 {common => crates/common}/src/rand.rs | 0 {common => crates/common}/src/rc.rs | 0 {common => crates/common}/src/refcount.rs | 0 {common => crates/common}/src/static_cell.rs | 0 {common => crates/common}/src/str.rs | 0 {common => crates/common}/src/windows.rs | 0 27 files changed, 1 insertion(+), 2 deletions(-) rename {common => crates/common}/Cargo.toml (100%) rename {common => crates/common}/src/atomic.rs (100%) rename {common => crates/common}/src/borrow.rs (100%) rename {common => crates/common}/src/boxvec.rs (100%) rename {common => crates/common}/src/cformat.rs (100%) rename {common => crates/common}/src/crt_fd.rs (100%) rename {common => crates/common}/src/encodings.rs (100%) rename {common => crates/common}/src/fileutils.rs (100%) rename {common => crates/common}/src/float_ops.rs (100%) rename {common => crates/common}/src/format.rs (100%) rename {common => crates/common}/src/hash.rs (100%) rename {common => crates/common}/src/int.rs (100%) rename {common => crates/common}/src/lib.rs (100%) rename {common => crates/common}/src/linked_list.rs (100%) rename {common => crates/common}/src/lock.rs (100%) rename {common => crates/common}/src/lock/cell_lock.rs (100%) rename {common => crates/common}/src/lock/immutable_mutex.rs (100%) rename {common => crates/common}/src/lock/thread_mutex.rs (100%) rename {common => crates/common}/src/macros.rs (100%) rename {common => crates/common}/src/os.rs (100%) rename {common => crates/common}/src/rand.rs (100%) rename {common => crates/common}/src/rc.rs (100%) rename {common => crates/common}/src/refcount.rs (100%) rename {common => crates/common}/src/static_cell.rs (100%) rename {common => crates/common}/src/str.rs (100%) rename {common => crates/common}/src/windows.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 09be0c4595d..795cfb06571 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,7 +126,6 @@ members = [ "compiler/codegen", "compiler/literal", ".", - "common", "derive", "jit", "vm", @@ -150,7 +149,7 @@ license = "MIT" 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-common = { path = "crates/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" } diff --git a/common/Cargo.toml b/crates/common/Cargo.toml similarity index 100% rename from common/Cargo.toml rename to crates/common/Cargo.toml diff --git a/common/src/atomic.rs b/crates/common/src/atomic.rs similarity index 100% rename from common/src/atomic.rs rename to crates/common/src/atomic.rs diff --git a/common/src/borrow.rs b/crates/common/src/borrow.rs similarity index 100% rename from common/src/borrow.rs rename to crates/common/src/borrow.rs diff --git a/common/src/boxvec.rs b/crates/common/src/boxvec.rs similarity index 100% rename from common/src/boxvec.rs rename to crates/common/src/boxvec.rs diff --git a/common/src/cformat.rs b/crates/common/src/cformat.rs similarity index 100% rename from common/src/cformat.rs rename to crates/common/src/cformat.rs diff --git a/common/src/crt_fd.rs b/crates/common/src/crt_fd.rs similarity index 100% rename from common/src/crt_fd.rs rename to crates/common/src/crt_fd.rs diff --git a/common/src/encodings.rs b/crates/common/src/encodings.rs similarity index 100% rename from common/src/encodings.rs rename to crates/common/src/encodings.rs diff --git a/common/src/fileutils.rs b/crates/common/src/fileutils.rs similarity index 100% rename from common/src/fileutils.rs rename to crates/common/src/fileutils.rs diff --git a/common/src/float_ops.rs b/crates/common/src/float_ops.rs similarity index 100% rename from common/src/float_ops.rs rename to crates/common/src/float_ops.rs diff --git a/common/src/format.rs b/crates/common/src/format.rs similarity index 100% rename from common/src/format.rs rename to crates/common/src/format.rs diff --git a/common/src/hash.rs b/crates/common/src/hash.rs similarity index 100% rename from common/src/hash.rs rename to crates/common/src/hash.rs diff --git a/common/src/int.rs b/crates/common/src/int.rs similarity index 100% rename from common/src/int.rs rename to crates/common/src/int.rs diff --git a/common/src/lib.rs b/crates/common/src/lib.rs similarity index 100% rename from common/src/lib.rs rename to crates/common/src/lib.rs diff --git a/common/src/linked_list.rs b/crates/common/src/linked_list.rs similarity index 100% rename from common/src/linked_list.rs rename to crates/common/src/linked_list.rs diff --git a/common/src/lock.rs b/crates/common/src/lock.rs similarity index 100% rename from common/src/lock.rs rename to crates/common/src/lock.rs diff --git a/common/src/lock/cell_lock.rs b/crates/common/src/lock/cell_lock.rs similarity index 100% rename from common/src/lock/cell_lock.rs rename to crates/common/src/lock/cell_lock.rs diff --git a/common/src/lock/immutable_mutex.rs b/crates/common/src/lock/immutable_mutex.rs similarity index 100% rename from common/src/lock/immutable_mutex.rs rename to crates/common/src/lock/immutable_mutex.rs diff --git a/common/src/lock/thread_mutex.rs b/crates/common/src/lock/thread_mutex.rs similarity index 100% rename from common/src/lock/thread_mutex.rs rename to crates/common/src/lock/thread_mutex.rs diff --git a/common/src/macros.rs b/crates/common/src/macros.rs similarity index 100% rename from common/src/macros.rs rename to crates/common/src/macros.rs diff --git a/common/src/os.rs b/crates/common/src/os.rs similarity index 100% rename from common/src/os.rs rename to crates/common/src/os.rs diff --git a/common/src/rand.rs b/crates/common/src/rand.rs similarity index 100% rename from common/src/rand.rs rename to crates/common/src/rand.rs diff --git a/common/src/rc.rs b/crates/common/src/rc.rs similarity index 100% rename from common/src/rc.rs rename to crates/common/src/rc.rs diff --git a/common/src/refcount.rs b/crates/common/src/refcount.rs similarity index 100% rename from common/src/refcount.rs rename to crates/common/src/refcount.rs diff --git a/common/src/static_cell.rs b/crates/common/src/static_cell.rs similarity index 100% rename from common/src/static_cell.rs rename to crates/common/src/static_cell.rs diff --git a/common/src/str.rs b/crates/common/src/str.rs similarity index 100% rename from common/src/str.rs rename to crates/common/src/str.rs diff --git a/common/src/windows.rs b/crates/common/src/windows.rs similarity index 100% rename from common/src/windows.rs rename to crates/common/src/windows.rs From 3728879baf54a7f3b6c79a6c00a380bb30766ec1 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 00:21:04 +0200 Subject: [PATCH 343/819] Move `wtf8` -> `crates/wtf8` (#6257) --- Cargo.toml | 3 +-- {wtf8 => crates/wtf8}/Cargo.toml | 0 {wtf8 => crates/wtf8}/src/core_char.rs | 0 {wtf8 => crates/wtf8}/src/core_str.rs | 0 {wtf8 => crates/wtf8}/src/core_str_count.rs | 0 {wtf8 => crates/wtf8}/src/lib.rs | 0 6 files changed, 1 insertion(+), 2 deletions(-) rename {wtf8 => crates/wtf8}/Cargo.toml (100%) rename {wtf8 => crates/wtf8}/src/core_char.rs (100%) rename {wtf8 => crates/wtf8}/src/core_str.rs (100%) rename {wtf8 => crates/wtf8}/src/core_str_count.rs (100%) rename {wtf8 => crates/wtf8}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 795cfb06571..6a3f3ad739b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -132,7 +132,6 @@ members = [ "vm/sre_engine", "stdlib", "derive-impl", - "wtf8", "wasm/lib", "crates/*", ] @@ -158,7 +157,7 @@ rustpython-vm = { path = "vm", default-features = false, version = "0.4.0" } rustpython-pylib = { path = "crates/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-wtf8 = { path = "wtf8", version = "0.4.0" } +rustpython-wtf8 = { path = "crates/wtf8", version = "0.4.0" } rustpython-doc = { path = "crates/doc", version = "0.4.0" } ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } diff --git a/wtf8/Cargo.toml b/crates/wtf8/Cargo.toml similarity index 100% rename from wtf8/Cargo.toml rename to crates/wtf8/Cargo.toml diff --git a/wtf8/src/core_char.rs b/crates/wtf8/src/core_char.rs similarity index 100% rename from wtf8/src/core_char.rs rename to crates/wtf8/src/core_char.rs diff --git a/wtf8/src/core_str.rs b/crates/wtf8/src/core_str.rs similarity index 100% rename from wtf8/src/core_str.rs rename to crates/wtf8/src/core_str.rs diff --git a/wtf8/src/core_str_count.rs b/crates/wtf8/src/core_str_count.rs similarity index 100% rename from wtf8/src/core_str_count.rs rename to crates/wtf8/src/core_str_count.rs diff --git a/wtf8/src/lib.rs b/crates/wtf8/src/lib.rs similarity index 100% rename from wtf8/src/lib.rs rename to crates/wtf8/src/lib.rs From bb54c5b0e61802563535f815f9e88e29bf2b660a Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 00:21:29 +0200 Subject: [PATCH 344/819] Move `compiler-core` -> `crates/compiler-core` (#6258) --- Cargo.toml | 3 +-- {compiler/core => crates/compiler-core}/Cargo.toml | 0 {compiler/core => crates/compiler-core}/src/bytecode.rs | 0 {compiler/core => crates/compiler-core}/src/frozen.rs | 0 {compiler/core => crates/compiler-core}/src/lib.rs | 0 {compiler/core => crates/compiler-core}/src/marshal.rs | 0 {compiler/core => crates/compiler-core}/src/mode.rs | 0 7 files changed, 1 insertion(+), 2 deletions(-) rename {compiler/core => crates/compiler-core}/Cargo.toml (100%) rename {compiler/core => crates/compiler-core}/src/bytecode.rs (100%) rename {compiler/core => crates/compiler-core}/src/frozen.rs (100%) rename {compiler/core => crates/compiler-core}/src/lib.rs (100%) rename {compiler/core => crates/compiler-core}/src/marshal.rs (100%) rename {compiler/core => crates/compiler-core}/src/mode.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 6a3f3ad739b..324ea4a96f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,7 +122,6 @@ template = "installer-config/installer.wxs" resolver = "2" members = [ "compiler", - "compiler/core", "compiler/codegen", "compiler/literal", ".", @@ -145,7 +144,7 @@ repository = "https://github.com/RustPython/RustPython" license = "MIT" [workspace.dependencies] -rustpython-compiler-core = { path = "compiler/core", version = "0.4.0" } +rustpython-compiler-core = { path = "crates/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 = "crates/common", version = "0.4.0" } diff --git a/compiler/core/Cargo.toml b/crates/compiler-core/Cargo.toml similarity index 100% rename from compiler/core/Cargo.toml rename to crates/compiler-core/Cargo.toml diff --git a/compiler/core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs similarity index 100% rename from compiler/core/src/bytecode.rs rename to crates/compiler-core/src/bytecode.rs diff --git a/compiler/core/src/frozen.rs b/crates/compiler-core/src/frozen.rs similarity index 100% rename from compiler/core/src/frozen.rs rename to crates/compiler-core/src/frozen.rs diff --git a/compiler/core/src/lib.rs b/crates/compiler-core/src/lib.rs similarity index 100% rename from compiler/core/src/lib.rs rename to crates/compiler-core/src/lib.rs diff --git a/compiler/core/src/marshal.rs b/crates/compiler-core/src/marshal.rs similarity index 100% rename from compiler/core/src/marshal.rs rename to crates/compiler-core/src/marshal.rs diff --git a/compiler/core/src/mode.rs b/crates/compiler-core/src/mode.rs similarity index 100% rename from compiler/core/src/mode.rs rename to crates/compiler-core/src/mode.rs From 2071fa2e69f30b30436ceb7d9936d3fa6718d298 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 00:27:04 +0200 Subject: [PATCH 345/819] Move `compiler/literal` -> `crates/literal` (#6259) * Move `compiler/literal` -> `crates/compiler-literal` * Use correct crate name --- Cargo.toml | 3 +-- {compiler => crates}/literal/Cargo.toml | 0 {compiler => crates}/literal/src/char.rs | 0 {compiler => crates}/literal/src/complex.rs | 0 {compiler => crates}/literal/src/escape.rs | 0 {compiler => crates}/literal/src/float.rs | 0 {compiler => crates}/literal/src/format.rs | 0 {compiler => crates}/literal/src/lib.rs | 0 8 files changed, 1 insertion(+), 2 deletions(-) rename {compiler => crates}/literal/Cargo.toml (100%) rename {compiler => crates}/literal/src/char.rs (100%) rename {compiler => crates}/literal/src/complex.rs (100%) rename {compiler => crates}/literal/src/escape.rs (100%) rename {compiler => crates}/literal/src/float.rs (100%) rename {compiler => crates}/literal/src/format.rs (100%) rename {compiler => crates}/literal/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 324ea4a96f5..d3bcfc88f19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,7 +123,6 @@ resolver = "2" members = [ "compiler", "compiler/codegen", - "compiler/literal", ".", "derive", "jit", @@ -151,7 +150,7 @@ rustpython-common = { path = "crates/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-literal = { path = "compiler/literal", version = "0.4.0" } +rustpython-literal = { path = "crates/literal", version = "0.4.0" } rustpython-vm = { path = "vm", default-features = false, version = "0.4.0" } rustpython-pylib = { path = "crates/pylib", version = "0.4.0" } rustpython-stdlib = { path = "stdlib", default-features = false, version = "0.4.0" } diff --git a/compiler/literal/Cargo.toml b/crates/literal/Cargo.toml similarity index 100% rename from compiler/literal/Cargo.toml rename to crates/literal/Cargo.toml diff --git a/compiler/literal/src/char.rs b/crates/literal/src/char.rs similarity index 100% rename from compiler/literal/src/char.rs rename to crates/literal/src/char.rs diff --git a/compiler/literal/src/complex.rs b/crates/literal/src/complex.rs similarity index 100% rename from compiler/literal/src/complex.rs rename to crates/literal/src/complex.rs diff --git a/compiler/literal/src/escape.rs b/crates/literal/src/escape.rs similarity index 100% rename from compiler/literal/src/escape.rs rename to crates/literal/src/escape.rs diff --git a/compiler/literal/src/float.rs b/crates/literal/src/float.rs similarity index 100% rename from compiler/literal/src/float.rs rename to crates/literal/src/float.rs diff --git a/compiler/literal/src/format.rs b/crates/literal/src/format.rs similarity index 100% rename from compiler/literal/src/format.rs rename to crates/literal/src/format.rs diff --git a/compiler/literal/src/lib.rs b/crates/literal/src/lib.rs similarity index 100% rename from compiler/literal/src/lib.rs rename to crates/literal/src/lib.rs From 477c9b32f0218e9cea42c8c6a90d1d4720a87754 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:29:20 +0900 Subject: [PATCH 346/819] Fix OSError.__str__ to not display 'None' for filename (#6266) --- vm/src/exceptions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 3cf26c1d5e9..a8b55ae8a4d 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1522,7 +1522,7 @@ pub(super) mod types { let msg = exc.get_arg(1).unwrap().str(vm)?; let s = match obj.get_attr("filename", vm) { - Ok(filename) => match obj.get_attr("filename2", vm) { + Ok(filename) if !vm.is_none(&filename) => match obj.get_attr("filename2", vm) { Ok(filename2) if !vm.is_none(&filename2) => format!( "[Errno {}] {}: '{}' -> '{}'", errno, @@ -1532,7 +1532,7 @@ pub(super) mod types { ), _ => format!("[Errno {}] {}: '{}'", errno, msg, filename.str(vm)?), }, - Err(_) => { + _ => { format!("[Errno {errno}] {msg}") } }; From 7f45ba4c9c734fa9bcb291849d5908e8c7836a0c Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 11:25:46 +0200 Subject: [PATCH 347/819] Move `compiler/codegen` -> `crates/codegen` (#6260) --- Cargo.toml | 3 +-- {compiler => crates}/codegen/Cargo.toml | 0 {compiler => crates}/codegen/src/compile.rs | 0 {compiler => crates}/codegen/src/error.rs | 0 {compiler => crates}/codegen/src/ir.rs | 0 {compiler => crates}/codegen/src/lib.rs | 0 .../snapshots/rustpython_codegen__compile__tests__if_ands.snap | 0 .../rustpython_codegen__compile__tests__if_mixed.snap | 0 .../snapshots/rustpython_codegen__compile__tests__if_ors.snap | 0 ...thon_codegen__compile__tests__nested_double_async_with.snap | 0 .../rustpython_compiler_core__compile__tests__if_ands.snap | 0 .../rustpython_compiler_core__compile__tests__if_mixed.snap | 0 .../rustpython_compiler_core__compile__tests__if_ors.snap | 0 ...ompiler_core__compile__tests__nested_double_async_with.snap | 0 {compiler => crates}/codegen/src/string_parser.rs | 0 {compiler => crates}/codegen/src/symboltable.rs | 0 {compiler => crates}/codegen/src/unparse.rs | 0 17 files changed, 1 insertion(+), 2 deletions(-) rename {compiler => crates}/codegen/Cargo.toml (100%) rename {compiler => crates}/codegen/src/compile.rs (100%) rename {compiler => crates}/codegen/src/error.rs (100%) rename {compiler => crates}/codegen/src/ir.rs (100%) rename {compiler => crates}/codegen/src/lib.rs (100%) rename {compiler => crates}/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap (100%) rename {compiler => crates}/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap (100%) rename {compiler => crates}/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap (100%) rename {compiler => crates}/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap (100%) rename {compiler => crates}/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_ands.snap (100%) rename {compiler => crates}/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_mixed.snap (100%) rename {compiler => crates}/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_ors.snap (100%) rename {compiler => crates}/codegen/src/snapshots/rustpython_compiler_core__compile__tests__nested_double_async_with.snap (100%) rename {compiler => crates}/codegen/src/string_parser.rs (100%) rename {compiler => crates}/codegen/src/symboltable.rs (100%) rename {compiler => crates}/codegen/src/unparse.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index d3bcfc88f19..c53acbb86a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,7 +122,6 @@ template = "installer-config/installer.wxs" resolver = "2" members = [ "compiler", - "compiler/codegen", ".", "derive", "jit", @@ -145,7 +144,7 @@ license = "MIT" [workspace.dependencies] rustpython-compiler-core = { path = "crates/compiler-core", version = "0.4.0" } rustpython-compiler = { path = "compiler", version = "0.4.0" } -rustpython-codegen = { path = "compiler/codegen", version = "0.4.0" } +rustpython-codegen = { path = "crates/codegen", version = "0.4.0" } rustpython-common = { path = "crates/common", version = "0.4.0" } rustpython-derive = { path = "derive", version = "0.4.0" } rustpython-derive-impl = { path = "derive-impl", version = "0.4.0" } diff --git a/compiler/codegen/Cargo.toml b/crates/codegen/Cargo.toml similarity index 100% rename from compiler/codegen/Cargo.toml rename to crates/codegen/Cargo.toml diff --git a/compiler/codegen/src/compile.rs b/crates/codegen/src/compile.rs similarity index 100% rename from compiler/codegen/src/compile.rs rename to crates/codegen/src/compile.rs diff --git a/compiler/codegen/src/error.rs b/crates/codegen/src/error.rs similarity index 100% rename from compiler/codegen/src/error.rs rename to crates/codegen/src/error.rs diff --git a/compiler/codegen/src/ir.rs b/crates/codegen/src/ir.rs similarity index 100% rename from compiler/codegen/src/ir.rs rename to crates/codegen/src/ir.rs diff --git a/compiler/codegen/src/lib.rs b/crates/codegen/src/lib.rs similarity index 100% rename from compiler/codegen/src/lib.rs rename to crates/codegen/src/lib.rs diff --git a/compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap similarity index 100% rename from compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap rename to crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap diff --git a/compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap similarity index 100% rename from compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap rename to crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap diff --git a/compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap similarity index 100% rename from compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap rename to crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap diff --git a/compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap similarity index 100% rename from compiler/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap rename to crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap diff --git a/compiler/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_ands.snap b/crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_ands.snap similarity index 100% rename from compiler/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_ands.snap rename to crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_ands.snap diff --git a/compiler/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_mixed.snap b/crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_mixed.snap similarity index 100% rename from compiler/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_mixed.snap rename to crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_mixed.snap diff --git a/compiler/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_ors.snap b/crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_ors.snap similarity index 100% rename from compiler/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_ors.snap rename to crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__if_ors.snap diff --git a/compiler/codegen/src/snapshots/rustpython_compiler_core__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__nested_double_async_with.snap similarity index 100% rename from compiler/codegen/src/snapshots/rustpython_compiler_core__compile__tests__nested_double_async_with.snap rename to crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__nested_double_async_with.snap diff --git a/compiler/codegen/src/string_parser.rs b/crates/codegen/src/string_parser.rs similarity index 100% rename from compiler/codegen/src/string_parser.rs rename to crates/codegen/src/string_parser.rs diff --git a/compiler/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs similarity index 100% rename from compiler/codegen/src/symboltable.rs rename to crates/codegen/src/symboltable.rs diff --git a/compiler/codegen/src/unparse.rs b/crates/codegen/src/unparse.rs similarity index 100% rename from compiler/codegen/src/unparse.rs rename to crates/codegen/src/unparse.rs From cc2e84b9fc0af690f3281cd4b02e219b4752bed7 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 11:26:24 +0200 Subject: [PATCH 348/819] Move `jit` -> `crates/jit` (#6262) --- Cargo.toml | 3 +-- {jit => crates/jit}/Cargo.toml | 2 +- {jit => crates/jit}/src/instructions.rs | 0 {jit => crates/jit}/src/lib.rs | 0 {jit => crates/jit}/tests/bool_tests.rs | 0 {jit => crates/jit}/tests/common.rs | 0 {jit => crates/jit}/tests/float_tests.rs | 0 {jit => crates/jit}/tests/int_tests.rs | 0 {jit => crates/jit}/tests/lib.rs | 0 {jit => crates/jit}/tests/misc_tests.rs | 0 {jit => crates/jit}/tests/none_tests.rs | 0 11 files changed, 2 insertions(+), 3 deletions(-) rename {jit => crates/jit}/Cargo.toml (91%) rename {jit => crates/jit}/src/instructions.rs (100%) rename {jit => crates/jit}/src/lib.rs (100%) rename {jit => crates/jit}/tests/bool_tests.rs (100%) rename {jit => crates/jit}/tests/common.rs (100%) rename {jit => crates/jit}/tests/float_tests.rs (100%) rename {jit => crates/jit}/tests/int_tests.rs (100%) rename {jit => crates/jit}/tests/lib.rs (100%) rename {jit => crates/jit}/tests/misc_tests.rs (100%) rename {jit => crates/jit}/tests/none_tests.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index c53acbb86a9..c3e6102d649 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,7 +124,6 @@ members = [ "compiler", ".", "derive", - "jit", "vm", "vm/sre_engine", "stdlib", @@ -148,7 +147,7 @@ rustpython-codegen = { path = "crates/codegen", version = "0.4.0" } rustpython-common = { path = "crates/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-jit = { path = "crates/jit", version = "0.4.0" } rustpython-literal = { path = "crates/literal", version = "0.4.0" } rustpython-vm = { path = "vm", default-features = false, version = "0.4.0" } rustpython-pylib = { path = "crates/pylib", version = "0.4.0" } diff --git a/jit/Cargo.toml b/crates/jit/Cargo.toml similarity index 91% rename from jit/Cargo.toml rename to crates/jit/Cargo.toml index 5708ae367b5..2ef8c344a9d 100644 --- a/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -22,7 +22,7 @@ cranelift-jit = "0.119" cranelift-module = "0.119" [dev-dependencies] -rustpython-derive = { path = "../derive", version = "0.4.0" } +rustpython-derive = { workspace = true } approx = "0.5.1" diff --git a/jit/src/instructions.rs b/crates/jit/src/instructions.rs similarity index 100% rename from jit/src/instructions.rs rename to crates/jit/src/instructions.rs diff --git a/jit/src/lib.rs b/crates/jit/src/lib.rs similarity index 100% rename from jit/src/lib.rs rename to crates/jit/src/lib.rs diff --git a/jit/tests/bool_tests.rs b/crates/jit/tests/bool_tests.rs similarity index 100% rename from jit/tests/bool_tests.rs rename to crates/jit/tests/bool_tests.rs diff --git a/jit/tests/common.rs b/crates/jit/tests/common.rs similarity index 100% rename from jit/tests/common.rs rename to crates/jit/tests/common.rs diff --git a/jit/tests/float_tests.rs b/crates/jit/tests/float_tests.rs similarity index 100% rename from jit/tests/float_tests.rs rename to crates/jit/tests/float_tests.rs diff --git a/jit/tests/int_tests.rs b/crates/jit/tests/int_tests.rs similarity index 100% rename from jit/tests/int_tests.rs rename to crates/jit/tests/int_tests.rs diff --git a/jit/tests/lib.rs b/crates/jit/tests/lib.rs similarity index 100% rename from jit/tests/lib.rs rename to crates/jit/tests/lib.rs diff --git a/jit/tests/misc_tests.rs b/crates/jit/tests/misc_tests.rs similarity index 100% rename from jit/tests/misc_tests.rs rename to crates/jit/tests/misc_tests.rs diff --git a/jit/tests/none_tests.rs b/crates/jit/tests/none_tests.rs similarity index 100% rename from jit/tests/none_tests.rs rename to crates/jit/tests/none_tests.rs From 6479a2063caabe22be58687f861b6057c6147fc2 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:49:00 +0200 Subject: [PATCH 349/819] move `derive-impl` -> `crates/derive-impl` (#6263) --- Cargo.toml | 3 +-- {derive-impl => crates/derive-impl}/Cargo.toml | 0 {derive-impl => crates/derive-impl}/src/compile_bytecode.rs | 0 {derive-impl => crates/derive-impl}/src/error.rs | 0 {derive-impl => crates/derive-impl}/src/from_args.rs | 0 {derive-impl => crates/derive-impl}/src/lib.rs | 0 {derive-impl => crates/derive-impl}/src/pyclass.rs | 0 {derive-impl => crates/derive-impl}/src/pymodule.rs | 0 {derive-impl => crates/derive-impl}/src/pypayload.rs | 0 {derive-impl => crates/derive-impl}/src/pystructseq.rs | 0 {derive-impl => crates/derive-impl}/src/pytraverse.rs | 0 {derive-impl => crates/derive-impl}/src/util.rs | 0 12 files changed, 1 insertion(+), 2 deletions(-) rename {derive-impl => crates/derive-impl}/Cargo.toml (100%) rename {derive-impl => crates/derive-impl}/src/compile_bytecode.rs (100%) rename {derive-impl => crates/derive-impl}/src/error.rs (100%) rename {derive-impl => crates/derive-impl}/src/from_args.rs (100%) rename {derive-impl => crates/derive-impl}/src/lib.rs (100%) rename {derive-impl => crates/derive-impl}/src/pyclass.rs (100%) rename {derive-impl => crates/derive-impl}/src/pymodule.rs (100%) rename {derive-impl => crates/derive-impl}/src/pypayload.rs (100%) rename {derive-impl => crates/derive-impl}/src/pystructseq.rs (100%) rename {derive-impl => crates/derive-impl}/src/pytraverse.rs (100%) rename {derive-impl => crates/derive-impl}/src/util.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index c3e6102d649..dc4c71f9929 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,7 +127,6 @@ members = [ "vm", "vm/sre_engine", "stdlib", - "derive-impl", "wasm/lib", "crates/*", ] @@ -146,7 +145,7 @@ rustpython-compiler = { path = "compiler", version = "0.4.0" } rustpython-codegen = { path = "crates/codegen", version = "0.4.0" } rustpython-common = { path = "crates/common", version = "0.4.0" } rustpython-derive = { path = "derive", version = "0.4.0" } -rustpython-derive-impl = { path = "derive-impl", version = "0.4.0" } +rustpython-derive-impl = { path = "crates/derive-impl", version = "0.4.0" } rustpython-jit = { path = "crates/jit", version = "0.4.0" } rustpython-literal = { path = "crates/literal", version = "0.4.0" } rustpython-vm = { path = "vm", default-features = false, version = "0.4.0" } diff --git a/derive-impl/Cargo.toml b/crates/derive-impl/Cargo.toml similarity index 100% rename from derive-impl/Cargo.toml rename to crates/derive-impl/Cargo.toml diff --git a/derive-impl/src/compile_bytecode.rs b/crates/derive-impl/src/compile_bytecode.rs similarity index 100% rename from derive-impl/src/compile_bytecode.rs rename to crates/derive-impl/src/compile_bytecode.rs diff --git a/derive-impl/src/error.rs b/crates/derive-impl/src/error.rs similarity index 100% rename from derive-impl/src/error.rs rename to crates/derive-impl/src/error.rs diff --git a/derive-impl/src/from_args.rs b/crates/derive-impl/src/from_args.rs similarity index 100% rename from derive-impl/src/from_args.rs rename to crates/derive-impl/src/from_args.rs diff --git a/derive-impl/src/lib.rs b/crates/derive-impl/src/lib.rs similarity index 100% rename from derive-impl/src/lib.rs rename to crates/derive-impl/src/lib.rs diff --git a/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs similarity index 100% rename from derive-impl/src/pyclass.rs rename to crates/derive-impl/src/pyclass.rs diff --git a/derive-impl/src/pymodule.rs b/crates/derive-impl/src/pymodule.rs similarity index 100% rename from derive-impl/src/pymodule.rs rename to crates/derive-impl/src/pymodule.rs diff --git a/derive-impl/src/pypayload.rs b/crates/derive-impl/src/pypayload.rs similarity index 100% rename from derive-impl/src/pypayload.rs rename to crates/derive-impl/src/pypayload.rs diff --git a/derive-impl/src/pystructseq.rs b/crates/derive-impl/src/pystructseq.rs similarity index 100% rename from derive-impl/src/pystructseq.rs rename to crates/derive-impl/src/pystructseq.rs diff --git a/derive-impl/src/pytraverse.rs b/crates/derive-impl/src/pytraverse.rs similarity index 100% rename from derive-impl/src/pytraverse.rs rename to crates/derive-impl/src/pytraverse.rs diff --git a/derive-impl/src/util.rs b/crates/derive-impl/src/util.rs similarity index 100% rename from derive-impl/src/util.rs rename to crates/derive-impl/src/util.rs From 9e60940f1be2af30884f8b78c4fe4234ceaf2b56 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:49:48 +0200 Subject: [PATCH 350/819] Move `vm/sre_engine` -> `crates/sre_engine` (#6265) --- Cargo.toml | 3 +-- {vm => crates}/sre_engine/.gitignore | 0 {vm => crates}/sre_engine/Cargo.toml | 0 {vm => crates}/sre_engine/LICENSE | 0 {vm => crates}/sre_engine/benches/benches.rs | 0 {vm => crates}/sre_engine/generate_tests.py | 0 {vm => crates}/sre_engine/src/constants.rs | 0 {vm => crates}/sre_engine/src/engine.rs | 0 {vm => crates}/sre_engine/src/lib.rs | 0 {vm => crates}/sre_engine/src/string.rs | 0 {vm => crates}/sre_engine/tests/tests.rs | 0 11 files changed, 1 insertion(+), 2 deletions(-) rename {vm => crates}/sre_engine/.gitignore (100%) rename {vm => crates}/sre_engine/Cargo.toml (100%) rename {vm => crates}/sre_engine/LICENSE (100%) rename {vm => crates}/sre_engine/benches/benches.rs (100%) rename {vm => crates}/sre_engine/generate_tests.py (100%) rename {vm => crates}/sre_engine/src/constants.rs (100%) rename {vm => crates}/sre_engine/src/engine.rs (100%) rename {vm => crates}/sre_engine/src/lib.rs (100%) rename {vm => crates}/sre_engine/src/string.rs (100%) rename {vm => crates}/sre_engine/tests/tests.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index dc4c71f9929..31b57794e72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,7 +125,6 @@ members = [ ".", "derive", "vm", - "vm/sre_engine", "stdlib", "wasm/lib", "crates/*", @@ -151,7 +150,7 @@ rustpython-literal = { path = "crates/literal", version = "0.4.0" } rustpython-vm = { path = "vm", default-features = false, version = "0.4.0" } rustpython-pylib = { path = "crates/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-sre_engine = { path = "crates/sre_engine", version = "0.4.0" } rustpython-wtf8 = { path = "crates/wtf8", version = "0.4.0" } rustpython-doc = { path = "crates/doc", version = "0.4.0" } diff --git a/vm/sre_engine/.gitignore b/crates/sre_engine/.gitignore similarity index 100% rename from vm/sre_engine/.gitignore rename to crates/sre_engine/.gitignore diff --git a/vm/sre_engine/Cargo.toml b/crates/sre_engine/Cargo.toml similarity index 100% rename from vm/sre_engine/Cargo.toml rename to crates/sre_engine/Cargo.toml diff --git a/vm/sre_engine/LICENSE b/crates/sre_engine/LICENSE similarity index 100% rename from vm/sre_engine/LICENSE rename to crates/sre_engine/LICENSE diff --git a/vm/sre_engine/benches/benches.rs b/crates/sre_engine/benches/benches.rs similarity index 100% rename from vm/sre_engine/benches/benches.rs rename to crates/sre_engine/benches/benches.rs diff --git a/vm/sre_engine/generate_tests.py b/crates/sre_engine/generate_tests.py similarity index 100% rename from vm/sre_engine/generate_tests.py rename to crates/sre_engine/generate_tests.py diff --git a/vm/sre_engine/src/constants.rs b/crates/sre_engine/src/constants.rs similarity index 100% rename from vm/sre_engine/src/constants.rs rename to crates/sre_engine/src/constants.rs diff --git a/vm/sre_engine/src/engine.rs b/crates/sre_engine/src/engine.rs similarity index 100% rename from vm/sre_engine/src/engine.rs rename to crates/sre_engine/src/engine.rs diff --git a/vm/sre_engine/src/lib.rs b/crates/sre_engine/src/lib.rs similarity index 100% rename from vm/sre_engine/src/lib.rs rename to crates/sre_engine/src/lib.rs diff --git a/vm/sre_engine/src/string.rs b/crates/sre_engine/src/string.rs similarity index 100% rename from vm/sre_engine/src/string.rs rename to crates/sre_engine/src/string.rs diff --git a/vm/sre_engine/tests/tests.rs b/crates/sre_engine/tests/tests.rs similarity index 100% rename from vm/sre_engine/tests/tests.rs rename to crates/sre_engine/tests/tests.rs From 041dd306020bf4340d668895cabc931f4bc3bb75 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:50:19 +0200 Subject: [PATCH 351/819] Move `compiler/src` -> `crates/compiler` (#6270) --- Cargo.toml | 3 +-- {compiler => crates/compiler}/Cargo.toml | 0 {compiler => crates/compiler}/src/lib.rs | 0 3 files changed, 1 insertion(+), 2 deletions(-) rename {compiler => crates/compiler}/Cargo.toml (100%) rename {compiler => crates/compiler}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 31b57794e72..be2d7241af2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,7 +121,6 @@ template = "installer-config/installer.wxs" [workspace] resolver = "2" members = [ - "compiler", ".", "derive", "vm", @@ -140,7 +139,7 @@ license = "MIT" [workspace.dependencies] rustpython-compiler-core = { path = "crates/compiler-core", version = "0.4.0" } -rustpython-compiler = { path = "compiler", version = "0.4.0" } +rustpython-compiler = { path = "crates/compiler", version = "0.4.0" } rustpython-codegen = { path = "crates/codegen", version = "0.4.0" } rustpython-common = { path = "crates/common", version = "0.4.0" } rustpython-derive = { path = "derive", version = "0.4.0" } diff --git a/compiler/Cargo.toml b/crates/compiler/Cargo.toml similarity index 100% rename from compiler/Cargo.toml rename to crates/compiler/Cargo.toml diff --git a/compiler/src/lib.rs b/crates/compiler/src/lib.rs similarity index 100% rename from compiler/src/lib.rs rename to crates/compiler/src/lib.rs From 8968aeafb93ebd36d71de99a45a853336b2b9503 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 14:51:33 +0200 Subject: [PATCH 352/819] Move `vm` -> `crates/vm` (#6269) --- Cargo.toml | 3 +-- {vm => crates/vm}/Cargo.toml | 0 {vm => crates/vm}/Lib/README.md | 0 crates/vm/Lib/core_modules/codecs.py | 1 + crates/vm/Lib/core_modules/copyreg.py | 1 + crates/vm/Lib/core_modules/encodings_utf_8.py | 1 + crates/vm/Lib/python_builtins/__hello__.py | 1 + crates/vm/Lib/python_builtins/__phello__ | 1 + {vm => crates/vm}/Lib/python_builtins/__reducelib.py | 0 crates/vm/Lib/python_builtins/_frozen_importlib.py | 1 + crates/vm/Lib/python_builtins/_frozen_importlib_external.py | 1 + {vm => crates/vm}/Lib/python_builtins/_py_exceptiongroup.py | 0 crates/vm/Lib/python_builtins/_thread.py | 1 + {vm => crates/vm}/build.rs | 2 +- {vm => crates/vm}/src/anystr.rs | 0 {vm => crates/vm}/src/buffer.rs | 0 {vm => crates/vm}/src/builtins/asyncgenerator.rs | 0 {vm => crates/vm}/src/builtins/bool.rs | 0 {vm => crates/vm}/src/builtins/builtin_func.rs | 0 {vm => crates/vm}/src/builtins/bytearray.rs | 0 {vm => crates/vm}/src/builtins/bytes.rs | 0 {vm => crates/vm}/src/builtins/classmethod.rs | 0 {vm => crates/vm}/src/builtins/code.rs | 0 {vm => crates/vm}/src/builtins/complex.rs | 0 {vm => crates/vm}/src/builtins/coroutine.rs | 0 {vm => crates/vm}/src/builtins/descriptor.rs | 0 {vm => crates/vm}/src/builtins/dict.rs | 0 {vm => crates/vm}/src/builtins/enumerate.rs | 0 {vm => crates/vm}/src/builtins/filter.rs | 0 {vm => crates/vm}/src/builtins/float.rs | 0 {vm => crates/vm}/src/builtins/frame.rs | 0 {vm => crates/vm}/src/builtins/function.rs | 0 {vm => crates/vm}/src/builtins/function/jit.rs | 0 {vm => crates/vm}/src/builtins/generator.rs | 0 {vm => crates/vm}/src/builtins/genericalias.rs | 0 {vm => crates/vm}/src/builtins/getset.rs | 0 {vm => crates/vm}/src/builtins/int.rs | 0 {vm => crates/vm}/src/builtins/iter.rs | 0 {vm => crates/vm}/src/builtins/list.rs | 0 {vm => crates/vm}/src/builtins/map.rs | 0 {vm => crates/vm}/src/builtins/mappingproxy.rs | 0 {vm => crates/vm}/src/builtins/memory.rs | 0 {vm => crates/vm}/src/builtins/mod.rs | 0 {vm => crates/vm}/src/builtins/module.rs | 0 {vm => crates/vm}/src/builtins/namespace.rs | 0 {vm => crates/vm}/src/builtins/object.rs | 0 {vm => crates/vm}/src/builtins/property.rs | 0 {vm => crates/vm}/src/builtins/range.rs | 0 {vm => crates/vm}/src/builtins/set.rs | 0 {vm => crates/vm}/src/builtins/singletons.rs | 0 {vm => crates/vm}/src/builtins/slice.rs | 0 {vm => crates/vm}/src/builtins/staticmethod.rs | 0 {vm => crates/vm}/src/builtins/str.rs | 0 {vm => crates/vm}/src/builtins/super.rs | 0 {vm => crates/vm}/src/builtins/traceback.rs | 0 {vm => crates/vm}/src/builtins/tuple.rs | 0 {vm => crates/vm}/src/builtins/type.rs | 0 {vm => crates/vm}/src/builtins/union.rs | 0 {vm => crates/vm}/src/builtins/weakproxy.rs | 0 {vm => crates/vm}/src/builtins/weakref.rs | 0 {vm => crates/vm}/src/builtins/zip.rs | 0 {vm => crates/vm}/src/byte.rs | 0 {vm => crates/vm}/src/bytes_inner.rs | 0 {vm => crates/vm}/src/cformat.rs | 0 {vm => crates/vm}/src/class.rs | 0 {vm => crates/vm}/src/codecs.rs | 0 {vm => crates/vm}/src/compiler.rs | 0 {vm => crates/vm}/src/convert/into_object.rs | 0 {vm => crates/vm}/src/convert/mod.rs | 0 {vm => crates/vm}/src/convert/to_pyobject.rs | 0 {vm => crates/vm}/src/convert/transmute_from.rs | 0 {vm => crates/vm}/src/convert/try_from.rs | 0 {vm => crates/vm}/src/coroutine.rs | 0 {vm => crates/vm}/src/dict_inner.rs | 0 {vm => crates/vm}/src/eval.rs | 0 {vm => crates/vm}/src/exceptions.rs | 0 {vm => crates/vm}/src/format.rs | 0 {vm => crates/vm}/src/frame.rs | 0 {vm => crates/vm}/src/function/argument.rs | 0 {vm => crates/vm}/src/function/arithmetic.rs | 0 {vm => crates/vm}/src/function/buffer.rs | 0 {vm => crates/vm}/src/function/builtin.rs | 0 {vm => crates/vm}/src/function/either.rs | 0 {vm => crates/vm}/src/function/fspath.rs | 0 {vm => crates/vm}/src/function/getset.rs | 0 {vm => crates/vm}/src/function/method.rs | 0 {vm => crates/vm}/src/function/mod.rs | 0 {vm => crates/vm}/src/function/number.rs | 0 {vm => crates/vm}/src/function/protocol.rs | 0 {vm => crates/vm}/src/import.rs | 0 {vm => crates/vm}/src/intern.rs | 0 {vm => crates/vm}/src/iter.rs | 0 {vm => crates/vm}/src/lib.rs | 0 {vm => crates/vm}/src/macros.rs | 0 {vm => crates/vm}/src/object/core.rs | 0 {vm => crates/vm}/src/object/ext.rs | 0 {vm => crates/vm}/src/object/mod.rs | 0 {vm => crates/vm}/src/object/payload.rs | 0 {vm => crates/vm}/src/object/traverse.rs | 0 {vm => crates/vm}/src/object/traverse_object.rs | 0 {vm => crates/vm}/src/ospath.rs | 0 {vm => crates/vm}/src/prelude.rs | 0 {vm => crates/vm}/src/protocol/buffer.rs | 0 {vm => crates/vm}/src/protocol/callable.rs | 0 {vm => crates/vm}/src/protocol/iter.rs | 0 {vm => crates/vm}/src/protocol/mapping.rs | 0 {vm => crates/vm}/src/protocol/mod.rs | 0 {vm => crates/vm}/src/protocol/number.rs | 0 {vm => crates/vm}/src/protocol/object.rs | 0 {vm => crates/vm}/src/protocol/sequence.rs | 0 {vm => crates/vm}/src/py_io.rs | 0 {vm => crates/vm}/src/py_serde.rs | 0 {vm => crates/vm}/src/readline.rs | 0 {vm => crates/vm}/src/recursion.rs | 0 {vm => crates/vm}/src/scope.rs | 0 {vm => crates/vm}/src/sequence.rs | 0 {vm => crates/vm}/src/signal.rs | 0 {vm => crates/vm}/src/sliceable.rs | 0 {vm => crates/vm}/src/stdlib/ast.rs | 0 {vm => crates/vm}/src/stdlib/ast/argument.rs | 0 {vm => crates/vm}/src/stdlib/ast/basic.rs | 0 {vm => crates/vm}/src/stdlib/ast/constant.rs | 0 {vm => crates/vm}/src/stdlib/ast/elif_else_clause.rs | 0 {vm => crates/vm}/src/stdlib/ast/exception.rs | 0 {vm => crates/vm}/src/stdlib/ast/expression.rs | 0 {vm => crates/vm}/src/stdlib/ast/module.rs | 0 {vm => crates/vm}/src/stdlib/ast/node.rs | 0 {vm => crates/vm}/src/stdlib/ast/operator.rs | 0 {vm => crates/vm}/src/stdlib/ast/other.rs | 0 {vm => crates/vm}/src/stdlib/ast/parameter.rs | 0 {vm => crates/vm}/src/stdlib/ast/pattern.rs | 0 {vm => crates/vm}/src/stdlib/ast/pyast.rs | 0 {vm => crates/vm}/src/stdlib/ast/python.rs | 0 {vm => crates/vm}/src/stdlib/ast/statement.rs | 0 {vm => crates/vm}/src/stdlib/ast/string.rs | 0 {vm => crates/vm}/src/stdlib/ast/type_ignore.rs | 0 {vm => crates/vm}/src/stdlib/ast/type_parameters.rs | 0 {vm => crates/vm}/src/stdlib/atexit.rs | 0 {vm => crates/vm}/src/stdlib/builtins.rs | 0 {vm => crates/vm}/src/stdlib/codecs.rs | 0 {vm => crates/vm}/src/stdlib/collections.rs | 0 {vm => crates/vm}/src/stdlib/ctypes.rs | 0 {vm => crates/vm}/src/stdlib/ctypes/array.rs | 0 {vm => crates/vm}/src/stdlib/ctypes/base.rs | 0 {vm => crates/vm}/src/stdlib/ctypes/function.rs | 0 {vm => crates/vm}/src/stdlib/ctypes/library.rs | 0 {vm => crates/vm}/src/stdlib/ctypes/pointer.rs | 0 {vm => crates/vm}/src/stdlib/ctypes/structure.rs | 0 {vm => crates/vm}/src/stdlib/ctypes/union.rs | 0 {vm => crates/vm}/src/stdlib/errno.rs | 0 {vm => crates/vm}/src/stdlib/functools.rs | 0 {vm => crates/vm}/src/stdlib/imp.rs | 0 {vm => crates/vm}/src/stdlib/io.rs | 0 {vm => crates/vm}/src/stdlib/itertools.rs | 0 {vm => crates/vm}/src/stdlib/marshal.rs | 0 {vm => crates/vm}/src/stdlib/mod.rs | 0 {vm => crates/vm}/src/stdlib/msvcrt.rs | 0 {vm => crates/vm}/src/stdlib/nt.rs | 0 {vm => crates/vm}/src/stdlib/operator.rs | 0 {vm => crates/vm}/src/stdlib/os.rs | 0 {vm => crates/vm}/src/stdlib/posix.rs | 0 {vm => crates/vm}/src/stdlib/posix_compat.rs | 0 {vm => crates/vm}/src/stdlib/pwd.rs | 0 {vm => crates/vm}/src/stdlib/signal.rs | 0 {vm => crates/vm}/src/stdlib/sre.rs | 0 {vm => crates/vm}/src/stdlib/stat.rs | 0 {vm => crates/vm}/src/stdlib/string.rs | 0 {vm => crates/vm}/src/stdlib/symtable.rs | 0 {vm => crates/vm}/src/stdlib/sys.rs | 0 {vm => crates/vm}/src/stdlib/sysconfig.rs | 0 {vm => crates/vm}/src/stdlib/sysconfigdata.rs | 0 {vm => crates/vm}/src/stdlib/thread.rs | 0 {vm => crates/vm}/src/stdlib/time.rs | 0 {vm => crates/vm}/src/stdlib/typevar.rs | 0 {vm => crates/vm}/src/stdlib/typing.rs | 0 {vm => crates/vm}/src/stdlib/warnings.rs | 0 {vm => crates/vm}/src/stdlib/weakref.rs | 0 {vm => crates/vm}/src/stdlib/winapi.rs | 0 {vm => crates/vm}/src/stdlib/winreg.rs | 0 {vm => crates/vm}/src/suggestion.rs | 0 {vm => crates/vm}/src/types/mod.rs | 0 {vm => crates/vm}/src/types/slot.rs | 0 {vm => crates/vm}/src/types/structseq.rs | 0 {vm => crates/vm}/src/types/zoo.rs | 0 {vm => crates/vm}/src/utils.rs | 0 {vm => crates/vm}/src/version.rs | 0 {vm => crates/vm}/src/vm/compile.rs | 0 {vm => crates/vm}/src/vm/context.rs | 0 {vm => crates/vm}/src/vm/interpreter.rs | 0 {vm => crates/vm}/src/vm/method.rs | 0 {vm => crates/vm}/src/vm/mod.rs | 4 +++- {vm => crates/vm}/src/vm/setting.rs | 0 {vm => crates/vm}/src/vm/thread.rs | 0 {vm => crates/vm}/src/vm/vm_new.rs | 0 {vm => crates/vm}/src/vm/vm_object.rs | 0 {vm => crates/vm}/src/vm/vm_ops.rs | 0 {vm => crates/vm}/src/warn.rs | 0 {vm => crates/vm}/src/windows.rs | 0 example_projects/barebone/Cargo.toml | 2 +- example_projects/frozen_stdlib/Cargo.toml | 2 +- vm/Lib/core_modules/codecs.py | 1 - vm/Lib/core_modules/copyreg.py | 1 - vm/Lib/core_modules/encodings_utf_8.py | 1 - vm/Lib/python_builtins/__hello__.py | 1 - vm/Lib/python_builtins/__phello__ | 1 - vm/Lib/python_builtins/_frozen_importlib.py | 1 - vm/Lib/python_builtins/_frozen_importlib_external.py | 1 - vm/Lib/python_builtins/_thread.py | 1 - wasm/wasm-unknown-test/Cargo.toml | 2 +- 209 files changed, 16 insertions(+), 15 deletions(-) rename {vm => crates/vm}/Cargo.toml (100%) rename {vm => crates/vm}/Lib/README.md (100%) create mode 120000 crates/vm/Lib/core_modules/codecs.py create mode 120000 crates/vm/Lib/core_modules/copyreg.py create mode 120000 crates/vm/Lib/core_modules/encodings_utf_8.py create mode 120000 crates/vm/Lib/python_builtins/__hello__.py create mode 120000 crates/vm/Lib/python_builtins/__phello__ rename {vm => crates/vm}/Lib/python_builtins/__reducelib.py (100%) create mode 120000 crates/vm/Lib/python_builtins/_frozen_importlib.py create mode 120000 crates/vm/Lib/python_builtins/_frozen_importlib_external.py rename {vm => crates/vm}/Lib/python_builtins/_py_exceptiongroup.py (100%) create mode 120000 crates/vm/Lib/python_builtins/_thread.py rename {vm => crates/vm}/build.rs (96%) rename {vm => crates/vm}/src/anystr.rs (100%) rename {vm => crates/vm}/src/buffer.rs (100%) rename {vm => crates/vm}/src/builtins/asyncgenerator.rs (100%) rename {vm => crates/vm}/src/builtins/bool.rs (100%) rename {vm => crates/vm}/src/builtins/builtin_func.rs (100%) rename {vm => crates/vm}/src/builtins/bytearray.rs (100%) rename {vm => crates/vm}/src/builtins/bytes.rs (100%) rename {vm => crates/vm}/src/builtins/classmethod.rs (100%) rename {vm => crates/vm}/src/builtins/code.rs (100%) rename {vm => crates/vm}/src/builtins/complex.rs (100%) rename {vm => crates/vm}/src/builtins/coroutine.rs (100%) rename {vm => crates/vm}/src/builtins/descriptor.rs (100%) rename {vm => crates/vm}/src/builtins/dict.rs (100%) rename {vm => crates/vm}/src/builtins/enumerate.rs (100%) rename {vm => crates/vm}/src/builtins/filter.rs (100%) rename {vm => crates/vm}/src/builtins/float.rs (100%) rename {vm => crates/vm}/src/builtins/frame.rs (100%) rename {vm => crates/vm}/src/builtins/function.rs (100%) rename {vm => crates/vm}/src/builtins/function/jit.rs (100%) rename {vm => crates/vm}/src/builtins/generator.rs (100%) rename {vm => crates/vm}/src/builtins/genericalias.rs (100%) rename {vm => crates/vm}/src/builtins/getset.rs (100%) rename {vm => crates/vm}/src/builtins/int.rs (100%) rename {vm => crates/vm}/src/builtins/iter.rs (100%) rename {vm => crates/vm}/src/builtins/list.rs (100%) rename {vm => crates/vm}/src/builtins/map.rs (100%) rename {vm => crates/vm}/src/builtins/mappingproxy.rs (100%) rename {vm => crates/vm}/src/builtins/memory.rs (100%) rename {vm => crates/vm}/src/builtins/mod.rs (100%) rename {vm => crates/vm}/src/builtins/module.rs (100%) rename {vm => crates/vm}/src/builtins/namespace.rs (100%) rename {vm => crates/vm}/src/builtins/object.rs (100%) rename {vm => crates/vm}/src/builtins/property.rs (100%) rename {vm => crates/vm}/src/builtins/range.rs (100%) rename {vm => crates/vm}/src/builtins/set.rs (100%) rename {vm => crates/vm}/src/builtins/singletons.rs (100%) rename {vm => crates/vm}/src/builtins/slice.rs (100%) rename {vm => crates/vm}/src/builtins/staticmethod.rs (100%) rename {vm => crates/vm}/src/builtins/str.rs (100%) rename {vm => crates/vm}/src/builtins/super.rs (100%) rename {vm => crates/vm}/src/builtins/traceback.rs (100%) rename {vm => crates/vm}/src/builtins/tuple.rs (100%) rename {vm => crates/vm}/src/builtins/type.rs (100%) rename {vm => crates/vm}/src/builtins/union.rs (100%) rename {vm => crates/vm}/src/builtins/weakproxy.rs (100%) rename {vm => crates/vm}/src/builtins/weakref.rs (100%) rename {vm => crates/vm}/src/builtins/zip.rs (100%) rename {vm => crates/vm}/src/byte.rs (100%) rename {vm => crates/vm}/src/bytes_inner.rs (100%) rename {vm => crates/vm}/src/cformat.rs (100%) rename {vm => crates/vm}/src/class.rs (100%) rename {vm => crates/vm}/src/codecs.rs (100%) rename {vm => crates/vm}/src/compiler.rs (100%) rename {vm => crates/vm}/src/convert/into_object.rs (100%) rename {vm => crates/vm}/src/convert/mod.rs (100%) rename {vm => crates/vm}/src/convert/to_pyobject.rs (100%) rename {vm => crates/vm}/src/convert/transmute_from.rs (100%) rename {vm => crates/vm}/src/convert/try_from.rs (100%) rename {vm => crates/vm}/src/coroutine.rs (100%) rename {vm => crates/vm}/src/dict_inner.rs (100%) rename {vm => crates/vm}/src/eval.rs (100%) rename {vm => crates/vm}/src/exceptions.rs (100%) rename {vm => crates/vm}/src/format.rs (100%) rename {vm => crates/vm}/src/frame.rs (100%) rename {vm => crates/vm}/src/function/argument.rs (100%) rename {vm => crates/vm}/src/function/arithmetic.rs (100%) rename {vm => crates/vm}/src/function/buffer.rs (100%) rename {vm => crates/vm}/src/function/builtin.rs (100%) rename {vm => crates/vm}/src/function/either.rs (100%) rename {vm => crates/vm}/src/function/fspath.rs (100%) rename {vm => crates/vm}/src/function/getset.rs (100%) rename {vm => crates/vm}/src/function/method.rs (100%) rename {vm => crates/vm}/src/function/mod.rs (100%) rename {vm => crates/vm}/src/function/number.rs (100%) rename {vm => crates/vm}/src/function/protocol.rs (100%) rename {vm => crates/vm}/src/import.rs (100%) rename {vm => crates/vm}/src/intern.rs (100%) rename {vm => crates/vm}/src/iter.rs (100%) rename {vm => crates/vm}/src/lib.rs (100%) rename {vm => crates/vm}/src/macros.rs (100%) rename {vm => crates/vm}/src/object/core.rs (100%) rename {vm => crates/vm}/src/object/ext.rs (100%) rename {vm => crates/vm}/src/object/mod.rs (100%) rename {vm => crates/vm}/src/object/payload.rs (100%) rename {vm => crates/vm}/src/object/traverse.rs (100%) rename {vm => crates/vm}/src/object/traverse_object.rs (100%) rename {vm => crates/vm}/src/ospath.rs (100%) rename {vm => crates/vm}/src/prelude.rs (100%) rename {vm => crates/vm}/src/protocol/buffer.rs (100%) rename {vm => crates/vm}/src/protocol/callable.rs (100%) rename {vm => crates/vm}/src/protocol/iter.rs (100%) rename {vm => crates/vm}/src/protocol/mapping.rs (100%) rename {vm => crates/vm}/src/protocol/mod.rs (100%) rename {vm => crates/vm}/src/protocol/number.rs (100%) rename {vm => crates/vm}/src/protocol/object.rs (100%) rename {vm => crates/vm}/src/protocol/sequence.rs (100%) rename {vm => crates/vm}/src/py_io.rs (100%) rename {vm => crates/vm}/src/py_serde.rs (100%) rename {vm => crates/vm}/src/readline.rs (100%) rename {vm => crates/vm}/src/recursion.rs (100%) rename {vm => crates/vm}/src/scope.rs (100%) rename {vm => crates/vm}/src/sequence.rs (100%) rename {vm => crates/vm}/src/signal.rs (100%) rename {vm => crates/vm}/src/sliceable.rs (100%) rename {vm => crates/vm}/src/stdlib/ast.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/argument.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/basic.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/constant.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/elif_else_clause.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/exception.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/expression.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/module.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/node.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/operator.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/other.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/parameter.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/pattern.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/pyast.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/python.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/statement.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/string.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/type_ignore.rs (100%) rename {vm => crates/vm}/src/stdlib/ast/type_parameters.rs (100%) rename {vm => crates/vm}/src/stdlib/atexit.rs (100%) rename {vm => crates/vm}/src/stdlib/builtins.rs (100%) rename {vm => crates/vm}/src/stdlib/codecs.rs (100%) rename {vm => crates/vm}/src/stdlib/collections.rs (100%) rename {vm => crates/vm}/src/stdlib/ctypes.rs (100%) rename {vm => crates/vm}/src/stdlib/ctypes/array.rs (100%) rename {vm => crates/vm}/src/stdlib/ctypes/base.rs (100%) rename {vm => crates/vm}/src/stdlib/ctypes/function.rs (100%) rename {vm => crates/vm}/src/stdlib/ctypes/library.rs (100%) rename {vm => crates/vm}/src/stdlib/ctypes/pointer.rs (100%) rename {vm => crates/vm}/src/stdlib/ctypes/structure.rs (100%) rename {vm => crates/vm}/src/stdlib/ctypes/union.rs (100%) rename {vm => crates/vm}/src/stdlib/errno.rs (100%) rename {vm => crates/vm}/src/stdlib/functools.rs (100%) rename {vm => crates/vm}/src/stdlib/imp.rs (100%) rename {vm => crates/vm}/src/stdlib/io.rs (100%) rename {vm => crates/vm}/src/stdlib/itertools.rs (100%) rename {vm => crates/vm}/src/stdlib/marshal.rs (100%) rename {vm => crates/vm}/src/stdlib/mod.rs (100%) rename {vm => crates/vm}/src/stdlib/msvcrt.rs (100%) rename {vm => crates/vm}/src/stdlib/nt.rs (100%) rename {vm => crates/vm}/src/stdlib/operator.rs (100%) rename {vm => crates/vm}/src/stdlib/os.rs (100%) rename {vm => crates/vm}/src/stdlib/posix.rs (100%) rename {vm => crates/vm}/src/stdlib/posix_compat.rs (100%) rename {vm => crates/vm}/src/stdlib/pwd.rs (100%) rename {vm => crates/vm}/src/stdlib/signal.rs (100%) rename {vm => crates/vm}/src/stdlib/sre.rs (100%) rename {vm => crates/vm}/src/stdlib/stat.rs (100%) rename {vm => crates/vm}/src/stdlib/string.rs (100%) rename {vm => crates/vm}/src/stdlib/symtable.rs (100%) rename {vm => crates/vm}/src/stdlib/sys.rs (100%) rename {vm => crates/vm}/src/stdlib/sysconfig.rs (100%) rename {vm => crates/vm}/src/stdlib/sysconfigdata.rs (100%) rename {vm => crates/vm}/src/stdlib/thread.rs (100%) rename {vm => crates/vm}/src/stdlib/time.rs (100%) rename {vm => crates/vm}/src/stdlib/typevar.rs (100%) rename {vm => crates/vm}/src/stdlib/typing.rs (100%) rename {vm => crates/vm}/src/stdlib/warnings.rs (100%) rename {vm => crates/vm}/src/stdlib/weakref.rs (100%) rename {vm => crates/vm}/src/stdlib/winapi.rs (100%) rename {vm => crates/vm}/src/stdlib/winreg.rs (100%) rename {vm => crates/vm}/src/suggestion.rs (100%) rename {vm => crates/vm}/src/types/mod.rs (100%) rename {vm => crates/vm}/src/types/slot.rs (100%) rename {vm => crates/vm}/src/types/structseq.rs (100%) rename {vm => crates/vm}/src/types/zoo.rs (100%) rename {vm => crates/vm}/src/utils.rs (100%) rename {vm => crates/vm}/src/version.rs (100%) rename {vm => crates/vm}/src/vm/compile.rs (100%) rename {vm => crates/vm}/src/vm/context.rs (100%) rename {vm => crates/vm}/src/vm/interpreter.rs (100%) rename {vm => crates/vm}/src/vm/method.rs (100%) rename {vm => crates/vm}/src/vm/mod.rs (99%) rename {vm => crates/vm}/src/vm/setting.rs (100%) rename {vm => crates/vm}/src/vm/thread.rs (100%) rename {vm => crates/vm}/src/vm/vm_new.rs (100%) rename {vm => crates/vm}/src/vm/vm_object.rs (100%) rename {vm => crates/vm}/src/vm/vm_ops.rs (100%) rename {vm => crates/vm}/src/warn.rs (100%) rename {vm => crates/vm}/src/windows.rs (100%) delete mode 120000 vm/Lib/core_modules/codecs.py delete mode 120000 vm/Lib/core_modules/copyreg.py delete mode 120000 vm/Lib/core_modules/encodings_utf_8.py delete mode 120000 vm/Lib/python_builtins/__hello__.py delete mode 120000 vm/Lib/python_builtins/__phello__ delete mode 120000 vm/Lib/python_builtins/_frozen_importlib.py delete mode 120000 vm/Lib/python_builtins/_frozen_importlib_external.py delete mode 120000 vm/Lib/python_builtins/_thread.py diff --git a/Cargo.toml b/Cargo.toml index be2d7241af2..8d344c133c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,7 +123,6 @@ resolver = "2" members = [ ".", "derive", - "vm", "stdlib", "wasm/lib", "crates/*", @@ -146,7 +145,7 @@ rustpython-derive = { path = "derive", version = "0.4.0" } rustpython-derive-impl = { path = "crates/derive-impl", version = "0.4.0" } rustpython-jit = { path = "crates/jit", version = "0.4.0" } rustpython-literal = { path = "crates/literal", version = "0.4.0" } -rustpython-vm = { path = "vm", default-features = false, version = "0.4.0" } +rustpython-vm = { path = "crates/vm", default-features = false, version = "0.4.0" } rustpython-pylib = { path = "crates/pylib", version = "0.4.0" } rustpython-stdlib = { path = "stdlib", default-features = false, version = "0.4.0" } rustpython-sre_engine = { path = "crates/sre_engine", version = "0.4.0" } diff --git a/vm/Cargo.toml b/crates/vm/Cargo.toml similarity index 100% rename from vm/Cargo.toml rename to crates/vm/Cargo.toml diff --git a/vm/Lib/README.md b/crates/vm/Lib/README.md similarity index 100% rename from vm/Lib/README.md rename to crates/vm/Lib/README.md diff --git a/crates/vm/Lib/core_modules/codecs.py b/crates/vm/Lib/core_modules/codecs.py new file mode 120000 index 00000000000..4a96fdd182d --- /dev/null +++ b/crates/vm/Lib/core_modules/codecs.py @@ -0,0 +1 @@ +../../../../Lib/codecs.py \ No newline at end of file diff --git a/crates/vm/Lib/core_modules/copyreg.py b/crates/vm/Lib/core_modules/copyreg.py new file mode 120000 index 00000000000..6f4f0d4d445 --- /dev/null +++ b/crates/vm/Lib/core_modules/copyreg.py @@ -0,0 +1 @@ +../../../../Lib/copyreg.py \ No newline at end of file diff --git a/crates/vm/Lib/core_modules/encodings_utf_8.py b/crates/vm/Lib/core_modules/encodings_utf_8.py new file mode 120000 index 00000000000..0a82f3b4a41 --- /dev/null +++ b/crates/vm/Lib/core_modules/encodings_utf_8.py @@ -0,0 +1 @@ +../../../../Lib/encodings/utf_8.py \ No newline at end of file diff --git a/crates/vm/Lib/python_builtins/__hello__.py b/crates/vm/Lib/python_builtins/__hello__.py new file mode 120000 index 00000000000..e7dedd3d0aa --- /dev/null +++ b/crates/vm/Lib/python_builtins/__hello__.py @@ -0,0 +1 @@ +../../../../Lib/__hello__.py \ No newline at end of file diff --git a/crates/vm/Lib/python_builtins/__phello__ b/crates/vm/Lib/python_builtins/__phello__ new file mode 120000 index 00000000000..21833dd18d8 --- /dev/null +++ b/crates/vm/Lib/python_builtins/__phello__ @@ -0,0 +1 @@ +../../../../Lib/__phello__/ \ No newline at end of file diff --git a/vm/Lib/python_builtins/__reducelib.py b/crates/vm/Lib/python_builtins/__reducelib.py similarity index 100% rename from vm/Lib/python_builtins/__reducelib.py rename to crates/vm/Lib/python_builtins/__reducelib.py diff --git a/crates/vm/Lib/python_builtins/_frozen_importlib.py b/crates/vm/Lib/python_builtins/_frozen_importlib.py new file mode 120000 index 00000000000..9d752e80dec --- /dev/null +++ b/crates/vm/Lib/python_builtins/_frozen_importlib.py @@ -0,0 +1 @@ +../../../../Lib/importlib/_bootstrap.py \ No newline at end of file diff --git a/crates/vm/Lib/python_builtins/_frozen_importlib_external.py b/crates/vm/Lib/python_builtins/_frozen_importlib_external.py new file mode 120000 index 00000000000..a6b01510674 --- /dev/null +++ b/crates/vm/Lib/python_builtins/_frozen_importlib_external.py @@ -0,0 +1 @@ +../../../../Lib/importlib/_bootstrap_external.py \ No newline at end of file diff --git a/vm/Lib/python_builtins/_py_exceptiongroup.py b/crates/vm/Lib/python_builtins/_py_exceptiongroup.py similarity index 100% rename from vm/Lib/python_builtins/_py_exceptiongroup.py rename to crates/vm/Lib/python_builtins/_py_exceptiongroup.py diff --git a/crates/vm/Lib/python_builtins/_thread.py b/crates/vm/Lib/python_builtins/_thread.py new file mode 120000 index 00000000000..9079ca9fda3 --- /dev/null +++ b/crates/vm/Lib/python_builtins/_thread.py @@ -0,0 +1 @@ +../../../../Lib/_dummy_thread.py \ No newline at end of file diff --git a/vm/build.rs b/crates/vm/build.rs similarity index 96% rename from vm/build.rs rename to crates/vm/build.rs index 93d29c3a578..f76bf3f5cbd 100644 --- a/vm/build.rs +++ b/crates/vm/build.rs @@ -11,7 +11,7 @@ fn main() { let display = entry.display(); println!("cargo:rerun-if-changed={display}"); } - println!("cargo:rerun-if-changed=../Lib/importlib/_bootstrap.py"); + println!("cargo:rerun-if-changed=../../Lib/importlib/_bootstrap.py"); println!("cargo:rustc-env=RUSTPYTHON_GIT_HASH={}", git_hash()); println!( diff --git a/vm/src/anystr.rs b/crates/vm/src/anystr.rs similarity index 100% rename from vm/src/anystr.rs rename to crates/vm/src/anystr.rs diff --git a/vm/src/buffer.rs b/crates/vm/src/buffer.rs similarity index 100% rename from vm/src/buffer.rs rename to crates/vm/src/buffer.rs diff --git a/vm/src/builtins/asyncgenerator.rs b/crates/vm/src/builtins/asyncgenerator.rs similarity index 100% rename from vm/src/builtins/asyncgenerator.rs rename to crates/vm/src/builtins/asyncgenerator.rs diff --git a/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs similarity index 100% rename from vm/src/builtins/bool.rs rename to crates/vm/src/builtins/bool.rs diff --git a/vm/src/builtins/builtin_func.rs b/crates/vm/src/builtins/builtin_func.rs similarity index 100% rename from vm/src/builtins/builtin_func.rs rename to crates/vm/src/builtins/builtin_func.rs diff --git a/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs similarity index 100% rename from vm/src/builtins/bytearray.rs rename to crates/vm/src/builtins/bytearray.rs diff --git a/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs similarity index 100% rename from vm/src/builtins/bytes.rs rename to crates/vm/src/builtins/bytes.rs diff --git a/vm/src/builtins/classmethod.rs b/crates/vm/src/builtins/classmethod.rs similarity index 100% rename from vm/src/builtins/classmethod.rs rename to crates/vm/src/builtins/classmethod.rs diff --git a/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs similarity index 100% rename from vm/src/builtins/code.rs rename to crates/vm/src/builtins/code.rs diff --git a/vm/src/builtins/complex.rs b/crates/vm/src/builtins/complex.rs similarity index 100% rename from vm/src/builtins/complex.rs rename to crates/vm/src/builtins/complex.rs diff --git a/vm/src/builtins/coroutine.rs b/crates/vm/src/builtins/coroutine.rs similarity index 100% rename from vm/src/builtins/coroutine.rs rename to crates/vm/src/builtins/coroutine.rs diff --git a/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs similarity index 100% rename from vm/src/builtins/descriptor.rs rename to crates/vm/src/builtins/descriptor.rs diff --git a/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs similarity index 100% rename from vm/src/builtins/dict.rs rename to crates/vm/src/builtins/dict.rs diff --git a/vm/src/builtins/enumerate.rs b/crates/vm/src/builtins/enumerate.rs similarity index 100% rename from vm/src/builtins/enumerate.rs rename to crates/vm/src/builtins/enumerate.rs diff --git a/vm/src/builtins/filter.rs b/crates/vm/src/builtins/filter.rs similarity index 100% rename from vm/src/builtins/filter.rs rename to crates/vm/src/builtins/filter.rs diff --git a/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs similarity index 100% rename from vm/src/builtins/float.rs rename to crates/vm/src/builtins/float.rs diff --git a/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs similarity index 100% rename from vm/src/builtins/frame.rs rename to crates/vm/src/builtins/frame.rs diff --git a/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs similarity index 100% rename from vm/src/builtins/function.rs rename to crates/vm/src/builtins/function.rs diff --git a/vm/src/builtins/function/jit.rs b/crates/vm/src/builtins/function/jit.rs similarity index 100% rename from vm/src/builtins/function/jit.rs rename to crates/vm/src/builtins/function/jit.rs diff --git a/vm/src/builtins/generator.rs b/crates/vm/src/builtins/generator.rs similarity index 100% rename from vm/src/builtins/generator.rs rename to crates/vm/src/builtins/generator.rs diff --git a/vm/src/builtins/genericalias.rs b/crates/vm/src/builtins/genericalias.rs similarity index 100% rename from vm/src/builtins/genericalias.rs rename to crates/vm/src/builtins/genericalias.rs diff --git a/vm/src/builtins/getset.rs b/crates/vm/src/builtins/getset.rs similarity index 100% rename from vm/src/builtins/getset.rs rename to crates/vm/src/builtins/getset.rs diff --git a/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs similarity index 100% rename from vm/src/builtins/int.rs rename to crates/vm/src/builtins/int.rs diff --git a/vm/src/builtins/iter.rs b/crates/vm/src/builtins/iter.rs similarity index 100% rename from vm/src/builtins/iter.rs rename to crates/vm/src/builtins/iter.rs diff --git a/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs similarity index 100% rename from vm/src/builtins/list.rs rename to crates/vm/src/builtins/list.rs diff --git a/vm/src/builtins/map.rs b/crates/vm/src/builtins/map.rs similarity index 100% rename from vm/src/builtins/map.rs rename to crates/vm/src/builtins/map.rs diff --git a/vm/src/builtins/mappingproxy.rs b/crates/vm/src/builtins/mappingproxy.rs similarity index 100% rename from vm/src/builtins/mappingproxy.rs rename to crates/vm/src/builtins/mappingproxy.rs diff --git a/vm/src/builtins/memory.rs b/crates/vm/src/builtins/memory.rs similarity index 100% rename from vm/src/builtins/memory.rs rename to crates/vm/src/builtins/memory.rs diff --git a/vm/src/builtins/mod.rs b/crates/vm/src/builtins/mod.rs similarity index 100% rename from vm/src/builtins/mod.rs rename to crates/vm/src/builtins/mod.rs diff --git a/vm/src/builtins/module.rs b/crates/vm/src/builtins/module.rs similarity index 100% rename from vm/src/builtins/module.rs rename to crates/vm/src/builtins/module.rs diff --git a/vm/src/builtins/namespace.rs b/crates/vm/src/builtins/namespace.rs similarity index 100% rename from vm/src/builtins/namespace.rs rename to crates/vm/src/builtins/namespace.rs diff --git a/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs similarity index 100% rename from vm/src/builtins/object.rs rename to crates/vm/src/builtins/object.rs diff --git a/vm/src/builtins/property.rs b/crates/vm/src/builtins/property.rs similarity index 100% rename from vm/src/builtins/property.rs rename to crates/vm/src/builtins/property.rs diff --git a/vm/src/builtins/range.rs b/crates/vm/src/builtins/range.rs similarity index 100% rename from vm/src/builtins/range.rs rename to crates/vm/src/builtins/range.rs diff --git a/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs similarity index 100% rename from vm/src/builtins/set.rs rename to crates/vm/src/builtins/set.rs diff --git a/vm/src/builtins/singletons.rs b/crates/vm/src/builtins/singletons.rs similarity index 100% rename from vm/src/builtins/singletons.rs rename to crates/vm/src/builtins/singletons.rs diff --git a/vm/src/builtins/slice.rs b/crates/vm/src/builtins/slice.rs similarity index 100% rename from vm/src/builtins/slice.rs rename to crates/vm/src/builtins/slice.rs diff --git a/vm/src/builtins/staticmethod.rs b/crates/vm/src/builtins/staticmethod.rs similarity index 100% rename from vm/src/builtins/staticmethod.rs rename to crates/vm/src/builtins/staticmethod.rs diff --git a/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs similarity index 100% rename from vm/src/builtins/str.rs rename to crates/vm/src/builtins/str.rs diff --git a/vm/src/builtins/super.rs b/crates/vm/src/builtins/super.rs similarity index 100% rename from vm/src/builtins/super.rs rename to crates/vm/src/builtins/super.rs diff --git a/vm/src/builtins/traceback.rs b/crates/vm/src/builtins/traceback.rs similarity index 100% rename from vm/src/builtins/traceback.rs rename to crates/vm/src/builtins/traceback.rs diff --git a/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs similarity index 100% rename from vm/src/builtins/tuple.rs rename to crates/vm/src/builtins/tuple.rs diff --git a/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs similarity index 100% rename from vm/src/builtins/type.rs rename to crates/vm/src/builtins/type.rs diff --git a/vm/src/builtins/union.rs b/crates/vm/src/builtins/union.rs similarity index 100% rename from vm/src/builtins/union.rs rename to crates/vm/src/builtins/union.rs diff --git a/vm/src/builtins/weakproxy.rs b/crates/vm/src/builtins/weakproxy.rs similarity index 100% rename from vm/src/builtins/weakproxy.rs rename to crates/vm/src/builtins/weakproxy.rs diff --git a/vm/src/builtins/weakref.rs b/crates/vm/src/builtins/weakref.rs similarity index 100% rename from vm/src/builtins/weakref.rs rename to crates/vm/src/builtins/weakref.rs diff --git a/vm/src/builtins/zip.rs b/crates/vm/src/builtins/zip.rs similarity index 100% rename from vm/src/builtins/zip.rs rename to crates/vm/src/builtins/zip.rs diff --git a/vm/src/byte.rs b/crates/vm/src/byte.rs similarity index 100% rename from vm/src/byte.rs rename to crates/vm/src/byte.rs diff --git a/vm/src/bytes_inner.rs b/crates/vm/src/bytes_inner.rs similarity index 100% rename from vm/src/bytes_inner.rs rename to crates/vm/src/bytes_inner.rs diff --git a/vm/src/cformat.rs b/crates/vm/src/cformat.rs similarity index 100% rename from vm/src/cformat.rs rename to crates/vm/src/cformat.rs diff --git a/vm/src/class.rs b/crates/vm/src/class.rs similarity index 100% rename from vm/src/class.rs rename to crates/vm/src/class.rs diff --git a/vm/src/codecs.rs b/crates/vm/src/codecs.rs similarity index 100% rename from vm/src/codecs.rs rename to crates/vm/src/codecs.rs diff --git a/vm/src/compiler.rs b/crates/vm/src/compiler.rs similarity index 100% rename from vm/src/compiler.rs rename to crates/vm/src/compiler.rs diff --git a/vm/src/convert/into_object.rs b/crates/vm/src/convert/into_object.rs similarity index 100% rename from vm/src/convert/into_object.rs rename to crates/vm/src/convert/into_object.rs diff --git a/vm/src/convert/mod.rs b/crates/vm/src/convert/mod.rs similarity index 100% rename from vm/src/convert/mod.rs rename to crates/vm/src/convert/mod.rs diff --git a/vm/src/convert/to_pyobject.rs b/crates/vm/src/convert/to_pyobject.rs similarity index 100% rename from vm/src/convert/to_pyobject.rs rename to crates/vm/src/convert/to_pyobject.rs diff --git a/vm/src/convert/transmute_from.rs b/crates/vm/src/convert/transmute_from.rs similarity index 100% rename from vm/src/convert/transmute_from.rs rename to crates/vm/src/convert/transmute_from.rs diff --git a/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs similarity index 100% rename from vm/src/convert/try_from.rs rename to crates/vm/src/convert/try_from.rs diff --git a/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs similarity index 100% rename from vm/src/coroutine.rs rename to crates/vm/src/coroutine.rs diff --git a/vm/src/dict_inner.rs b/crates/vm/src/dict_inner.rs similarity index 100% rename from vm/src/dict_inner.rs rename to crates/vm/src/dict_inner.rs diff --git a/vm/src/eval.rs b/crates/vm/src/eval.rs similarity index 100% rename from vm/src/eval.rs rename to crates/vm/src/eval.rs diff --git a/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs similarity index 100% rename from vm/src/exceptions.rs rename to crates/vm/src/exceptions.rs diff --git a/vm/src/format.rs b/crates/vm/src/format.rs similarity index 100% rename from vm/src/format.rs rename to crates/vm/src/format.rs diff --git a/vm/src/frame.rs b/crates/vm/src/frame.rs similarity index 100% rename from vm/src/frame.rs rename to crates/vm/src/frame.rs diff --git a/vm/src/function/argument.rs b/crates/vm/src/function/argument.rs similarity index 100% rename from vm/src/function/argument.rs rename to crates/vm/src/function/argument.rs diff --git a/vm/src/function/arithmetic.rs b/crates/vm/src/function/arithmetic.rs similarity index 100% rename from vm/src/function/arithmetic.rs rename to crates/vm/src/function/arithmetic.rs diff --git a/vm/src/function/buffer.rs b/crates/vm/src/function/buffer.rs similarity index 100% rename from vm/src/function/buffer.rs rename to crates/vm/src/function/buffer.rs diff --git a/vm/src/function/builtin.rs b/crates/vm/src/function/builtin.rs similarity index 100% rename from vm/src/function/builtin.rs rename to crates/vm/src/function/builtin.rs diff --git a/vm/src/function/either.rs b/crates/vm/src/function/either.rs similarity index 100% rename from vm/src/function/either.rs rename to crates/vm/src/function/either.rs diff --git a/vm/src/function/fspath.rs b/crates/vm/src/function/fspath.rs similarity index 100% rename from vm/src/function/fspath.rs rename to crates/vm/src/function/fspath.rs diff --git a/vm/src/function/getset.rs b/crates/vm/src/function/getset.rs similarity index 100% rename from vm/src/function/getset.rs rename to crates/vm/src/function/getset.rs diff --git a/vm/src/function/method.rs b/crates/vm/src/function/method.rs similarity index 100% rename from vm/src/function/method.rs rename to crates/vm/src/function/method.rs diff --git a/vm/src/function/mod.rs b/crates/vm/src/function/mod.rs similarity index 100% rename from vm/src/function/mod.rs rename to crates/vm/src/function/mod.rs diff --git a/vm/src/function/number.rs b/crates/vm/src/function/number.rs similarity index 100% rename from vm/src/function/number.rs rename to crates/vm/src/function/number.rs diff --git a/vm/src/function/protocol.rs b/crates/vm/src/function/protocol.rs similarity index 100% rename from vm/src/function/protocol.rs rename to crates/vm/src/function/protocol.rs diff --git a/vm/src/import.rs b/crates/vm/src/import.rs similarity index 100% rename from vm/src/import.rs rename to crates/vm/src/import.rs diff --git a/vm/src/intern.rs b/crates/vm/src/intern.rs similarity index 100% rename from vm/src/intern.rs rename to crates/vm/src/intern.rs diff --git a/vm/src/iter.rs b/crates/vm/src/iter.rs similarity index 100% rename from vm/src/iter.rs rename to crates/vm/src/iter.rs diff --git a/vm/src/lib.rs b/crates/vm/src/lib.rs similarity index 100% rename from vm/src/lib.rs rename to crates/vm/src/lib.rs diff --git a/vm/src/macros.rs b/crates/vm/src/macros.rs similarity index 100% rename from vm/src/macros.rs rename to crates/vm/src/macros.rs diff --git a/vm/src/object/core.rs b/crates/vm/src/object/core.rs similarity index 100% rename from vm/src/object/core.rs rename to crates/vm/src/object/core.rs diff --git a/vm/src/object/ext.rs b/crates/vm/src/object/ext.rs similarity index 100% rename from vm/src/object/ext.rs rename to crates/vm/src/object/ext.rs diff --git a/vm/src/object/mod.rs b/crates/vm/src/object/mod.rs similarity index 100% rename from vm/src/object/mod.rs rename to crates/vm/src/object/mod.rs diff --git a/vm/src/object/payload.rs b/crates/vm/src/object/payload.rs similarity index 100% rename from vm/src/object/payload.rs rename to crates/vm/src/object/payload.rs diff --git a/vm/src/object/traverse.rs b/crates/vm/src/object/traverse.rs similarity index 100% rename from vm/src/object/traverse.rs rename to crates/vm/src/object/traverse.rs diff --git a/vm/src/object/traverse_object.rs b/crates/vm/src/object/traverse_object.rs similarity index 100% rename from vm/src/object/traverse_object.rs rename to crates/vm/src/object/traverse_object.rs diff --git a/vm/src/ospath.rs b/crates/vm/src/ospath.rs similarity index 100% rename from vm/src/ospath.rs rename to crates/vm/src/ospath.rs diff --git a/vm/src/prelude.rs b/crates/vm/src/prelude.rs similarity index 100% rename from vm/src/prelude.rs rename to crates/vm/src/prelude.rs diff --git a/vm/src/protocol/buffer.rs b/crates/vm/src/protocol/buffer.rs similarity index 100% rename from vm/src/protocol/buffer.rs rename to crates/vm/src/protocol/buffer.rs diff --git a/vm/src/protocol/callable.rs b/crates/vm/src/protocol/callable.rs similarity index 100% rename from vm/src/protocol/callable.rs rename to crates/vm/src/protocol/callable.rs diff --git a/vm/src/protocol/iter.rs b/crates/vm/src/protocol/iter.rs similarity index 100% rename from vm/src/protocol/iter.rs rename to crates/vm/src/protocol/iter.rs diff --git a/vm/src/protocol/mapping.rs b/crates/vm/src/protocol/mapping.rs similarity index 100% rename from vm/src/protocol/mapping.rs rename to crates/vm/src/protocol/mapping.rs diff --git a/vm/src/protocol/mod.rs b/crates/vm/src/protocol/mod.rs similarity index 100% rename from vm/src/protocol/mod.rs rename to crates/vm/src/protocol/mod.rs diff --git a/vm/src/protocol/number.rs b/crates/vm/src/protocol/number.rs similarity index 100% rename from vm/src/protocol/number.rs rename to crates/vm/src/protocol/number.rs diff --git a/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs similarity index 100% rename from vm/src/protocol/object.rs rename to crates/vm/src/protocol/object.rs diff --git a/vm/src/protocol/sequence.rs b/crates/vm/src/protocol/sequence.rs similarity index 100% rename from vm/src/protocol/sequence.rs rename to crates/vm/src/protocol/sequence.rs diff --git a/vm/src/py_io.rs b/crates/vm/src/py_io.rs similarity index 100% rename from vm/src/py_io.rs rename to crates/vm/src/py_io.rs diff --git a/vm/src/py_serde.rs b/crates/vm/src/py_serde.rs similarity index 100% rename from vm/src/py_serde.rs rename to crates/vm/src/py_serde.rs diff --git a/vm/src/readline.rs b/crates/vm/src/readline.rs similarity index 100% rename from vm/src/readline.rs rename to crates/vm/src/readline.rs diff --git a/vm/src/recursion.rs b/crates/vm/src/recursion.rs similarity index 100% rename from vm/src/recursion.rs rename to crates/vm/src/recursion.rs diff --git a/vm/src/scope.rs b/crates/vm/src/scope.rs similarity index 100% rename from vm/src/scope.rs rename to crates/vm/src/scope.rs diff --git a/vm/src/sequence.rs b/crates/vm/src/sequence.rs similarity index 100% rename from vm/src/sequence.rs rename to crates/vm/src/sequence.rs diff --git a/vm/src/signal.rs b/crates/vm/src/signal.rs similarity index 100% rename from vm/src/signal.rs rename to crates/vm/src/signal.rs diff --git a/vm/src/sliceable.rs b/crates/vm/src/sliceable.rs similarity index 100% rename from vm/src/sliceable.rs rename to crates/vm/src/sliceable.rs diff --git a/vm/src/stdlib/ast.rs b/crates/vm/src/stdlib/ast.rs similarity index 100% rename from vm/src/stdlib/ast.rs rename to crates/vm/src/stdlib/ast.rs diff --git a/vm/src/stdlib/ast/argument.rs b/crates/vm/src/stdlib/ast/argument.rs similarity index 100% rename from vm/src/stdlib/ast/argument.rs rename to crates/vm/src/stdlib/ast/argument.rs diff --git a/vm/src/stdlib/ast/basic.rs b/crates/vm/src/stdlib/ast/basic.rs similarity index 100% rename from vm/src/stdlib/ast/basic.rs rename to crates/vm/src/stdlib/ast/basic.rs diff --git a/vm/src/stdlib/ast/constant.rs b/crates/vm/src/stdlib/ast/constant.rs similarity index 100% rename from vm/src/stdlib/ast/constant.rs rename to crates/vm/src/stdlib/ast/constant.rs diff --git a/vm/src/stdlib/ast/elif_else_clause.rs b/crates/vm/src/stdlib/ast/elif_else_clause.rs similarity index 100% rename from vm/src/stdlib/ast/elif_else_clause.rs rename to crates/vm/src/stdlib/ast/elif_else_clause.rs diff --git a/vm/src/stdlib/ast/exception.rs b/crates/vm/src/stdlib/ast/exception.rs similarity index 100% rename from vm/src/stdlib/ast/exception.rs rename to crates/vm/src/stdlib/ast/exception.rs diff --git a/vm/src/stdlib/ast/expression.rs b/crates/vm/src/stdlib/ast/expression.rs similarity index 100% rename from vm/src/stdlib/ast/expression.rs rename to crates/vm/src/stdlib/ast/expression.rs diff --git a/vm/src/stdlib/ast/module.rs b/crates/vm/src/stdlib/ast/module.rs similarity index 100% rename from vm/src/stdlib/ast/module.rs rename to crates/vm/src/stdlib/ast/module.rs diff --git a/vm/src/stdlib/ast/node.rs b/crates/vm/src/stdlib/ast/node.rs similarity index 100% rename from vm/src/stdlib/ast/node.rs rename to crates/vm/src/stdlib/ast/node.rs diff --git a/vm/src/stdlib/ast/operator.rs b/crates/vm/src/stdlib/ast/operator.rs similarity index 100% rename from vm/src/stdlib/ast/operator.rs rename to crates/vm/src/stdlib/ast/operator.rs diff --git a/vm/src/stdlib/ast/other.rs b/crates/vm/src/stdlib/ast/other.rs similarity index 100% rename from vm/src/stdlib/ast/other.rs rename to crates/vm/src/stdlib/ast/other.rs diff --git a/vm/src/stdlib/ast/parameter.rs b/crates/vm/src/stdlib/ast/parameter.rs similarity index 100% rename from vm/src/stdlib/ast/parameter.rs rename to crates/vm/src/stdlib/ast/parameter.rs diff --git a/vm/src/stdlib/ast/pattern.rs b/crates/vm/src/stdlib/ast/pattern.rs similarity index 100% rename from vm/src/stdlib/ast/pattern.rs rename to crates/vm/src/stdlib/ast/pattern.rs diff --git a/vm/src/stdlib/ast/pyast.rs b/crates/vm/src/stdlib/ast/pyast.rs similarity index 100% rename from vm/src/stdlib/ast/pyast.rs rename to crates/vm/src/stdlib/ast/pyast.rs diff --git a/vm/src/stdlib/ast/python.rs b/crates/vm/src/stdlib/ast/python.rs similarity index 100% rename from vm/src/stdlib/ast/python.rs rename to crates/vm/src/stdlib/ast/python.rs diff --git a/vm/src/stdlib/ast/statement.rs b/crates/vm/src/stdlib/ast/statement.rs similarity index 100% rename from vm/src/stdlib/ast/statement.rs rename to crates/vm/src/stdlib/ast/statement.rs diff --git a/vm/src/stdlib/ast/string.rs b/crates/vm/src/stdlib/ast/string.rs similarity index 100% rename from vm/src/stdlib/ast/string.rs rename to crates/vm/src/stdlib/ast/string.rs diff --git a/vm/src/stdlib/ast/type_ignore.rs b/crates/vm/src/stdlib/ast/type_ignore.rs similarity index 100% rename from vm/src/stdlib/ast/type_ignore.rs rename to crates/vm/src/stdlib/ast/type_ignore.rs diff --git a/vm/src/stdlib/ast/type_parameters.rs b/crates/vm/src/stdlib/ast/type_parameters.rs similarity index 100% rename from vm/src/stdlib/ast/type_parameters.rs rename to crates/vm/src/stdlib/ast/type_parameters.rs diff --git a/vm/src/stdlib/atexit.rs b/crates/vm/src/stdlib/atexit.rs similarity index 100% rename from vm/src/stdlib/atexit.rs rename to crates/vm/src/stdlib/atexit.rs diff --git a/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs similarity index 100% rename from vm/src/stdlib/builtins.rs rename to crates/vm/src/stdlib/builtins.rs diff --git a/vm/src/stdlib/codecs.rs b/crates/vm/src/stdlib/codecs.rs similarity index 100% rename from vm/src/stdlib/codecs.rs rename to crates/vm/src/stdlib/codecs.rs diff --git a/vm/src/stdlib/collections.rs b/crates/vm/src/stdlib/collections.rs similarity index 100% rename from vm/src/stdlib/collections.rs rename to crates/vm/src/stdlib/collections.rs diff --git a/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs similarity index 100% rename from vm/src/stdlib/ctypes.rs rename to crates/vm/src/stdlib/ctypes.rs diff --git a/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs similarity index 100% rename from vm/src/stdlib/ctypes/array.rs rename to crates/vm/src/stdlib/ctypes/array.rs diff --git a/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs similarity index 100% rename from vm/src/stdlib/ctypes/base.rs rename to crates/vm/src/stdlib/ctypes/base.rs diff --git a/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs similarity index 100% rename from vm/src/stdlib/ctypes/function.rs rename to crates/vm/src/stdlib/ctypes/function.rs diff --git a/vm/src/stdlib/ctypes/library.rs b/crates/vm/src/stdlib/ctypes/library.rs similarity index 100% rename from vm/src/stdlib/ctypes/library.rs rename to crates/vm/src/stdlib/ctypes/library.rs diff --git a/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs similarity index 100% rename from vm/src/stdlib/ctypes/pointer.rs rename to crates/vm/src/stdlib/ctypes/pointer.rs diff --git a/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs similarity index 100% rename from vm/src/stdlib/ctypes/structure.rs rename to crates/vm/src/stdlib/ctypes/structure.rs diff --git a/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs similarity index 100% rename from vm/src/stdlib/ctypes/union.rs rename to crates/vm/src/stdlib/ctypes/union.rs diff --git a/vm/src/stdlib/errno.rs b/crates/vm/src/stdlib/errno.rs similarity index 100% rename from vm/src/stdlib/errno.rs rename to crates/vm/src/stdlib/errno.rs diff --git a/vm/src/stdlib/functools.rs b/crates/vm/src/stdlib/functools.rs similarity index 100% rename from vm/src/stdlib/functools.rs rename to crates/vm/src/stdlib/functools.rs diff --git a/vm/src/stdlib/imp.rs b/crates/vm/src/stdlib/imp.rs similarity index 100% rename from vm/src/stdlib/imp.rs rename to crates/vm/src/stdlib/imp.rs diff --git a/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs similarity index 100% rename from vm/src/stdlib/io.rs rename to crates/vm/src/stdlib/io.rs diff --git a/vm/src/stdlib/itertools.rs b/crates/vm/src/stdlib/itertools.rs similarity index 100% rename from vm/src/stdlib/itertools.rs rename to crates/vm/src/stdlib/itertools.rs diff --git a/vm/src/stdlib/marshal.rs b/crates/vm/src/stdlib/marshal.rs similarity index 100% rename from vm/src/stdlib/marshal.rs rename to crates/vm/src/stdlib/marshal.rs diff --git a/vm/src/stdlib/mod.rs b/crates/vm/src/stdlib/mod.rs similarity index 100% rename from vm/src/stdlib/mod.rs rename to crates/vm/src/stdlib/mod.rs diff --git a/vm/src/stdlib/msvcrt.rs b/crates/vm/src/stdlib/msvcrt.rs similarity index 100% rename from vm/src/stdlib/msvcrt.rs rename to crates/vm/src/stdlib/msvcrt.rs diff --git a/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs similarity index 100% rename from vm/src/stdlib/nt.rs rename to crates/vm/src/stdlib/nt.rs diff --git a/vm/src/stdlib/operator.rs b/crates/vm/src/stdlib/operator.rs similarity index 100% rename from vm/src/stdlib/operator.rs rename to crates/vm/src/stdlib/operator.rs diff --git a/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs similarity index 100% rename from vm/src/stdlib/os.rs rename to crates/vm/src/stdlib/os.rs diff --git a/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs similarity index 100% rename from vm/src/stdlib/posix.rs rename to crates/vm/src/stdlib/posix.rs diff --git a/vm/src/stdlib/posix_compat.rs b/crates/vm/src/stdlib/posix_compat.rs similarity index 100% rename from vm/src/stdlib/posix_compat.rs rename to crates/vm/src/stdlib/posix_compat.rs diff --git a/vm/src/stdlib/pwd.rs b/crates/vm/src/stdlib/pwd.rs similarity index 100% rename from vm/src/stdlib/pwd.rs rename to crates/vm/src/stdlib/pwd.rs diff --git a/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs similarity index 100% rename from vm/src/stdlib/signal.rs rename to crates/vm/src/stdlib/signal.rs diff --git a/vm/src/stdlib/sre.rs b/crates/vm/src/stdlib/sre.rs similarity index 100% rename from vm/src/stdlib/sre.rs rename to crates/vm/src/stdlib/sre.rs diff --git a/vm/src/stdlib/stat.rs b/crates/vm/src/stdlib/stat.rs similarity index 100% rename from vm/src/stdlib/stat.rs rename to crates/vm/src/stdlib/stat.rs diff --git a/vm/src/stdlib/string.rs b/crates/vm/src/stdlib/string.rs similarity index 100% rename from vm/src/stdlib/string.rs rename to crates/vm/src/stdlib/string.rs diff --git a/vm/src/stdlib/symtable.rs b/crates/vm/src/stdlib/symtable.rs similarity index 100% rename from vm/src/stdlib/symtable.rs rename to crates/vm/src/stdlib/symtable.rs diff --git a/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs similarity index 100% rename from vm/src/stdlib/sys.rs rename to crates/vm/src/stdlib/sys.rs diff --git a/vm/src/stdlib/sysconfig.rs b/crates/vm/src/stdlib/sysconfig.rs similarity index 100% rename from vm/src/stdlib/sysconfig.rs rename to crates/vm/src/stdlib/sysconfig.rs diff --git a/vm/src/stdlib/sysconfigdata.rs b/crates/vm/src/stdlib/sysconfigdata.rs similarity index 100% rename from vm/src/stdlib/sysconfigdata.rs rename to crates/vm/src/stdlib/sysconfigdata.rs diff --git a/vm/src/stdlib/thread.rs b/crates/vm/src/stdlib/thread.rs similarity index 100% rename from vm/src/stdlib/thread.rs rename to crates/vm/src/stdlib/thread.rs diff --git a/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs similarity index 100% rename from vm/src/stdlib/time.rs rename to crates/vm/src/stdlib/time.rs diff --git a/vm/src/stdlib/typevar.rs b/crates/vm/src/stdlib/typevar.rs similarity index 100% rename from vm/src/stdlib/typevar.rs rename to crates/vm/src/stdlib/typevar.rs diff --git a/vm/src/stdlib/typing.rs b/crates/vm/src/stdlib/typing.rs similarity index 100% rename from vm/src/stdlib/typing.rs rename to crates/vm/src/stdlib/typing.rs diff --git a/vm/src/stdlib/warnings.rs b/crates/vm/src/stdlib/warnings.rs similarity index 100% rename from vm/src/stdlib/warnings.rs rename to crates/vm/src/stdlib/warnings.rs diff --git a/vm/src/stdlib/weakref.rs b/crates/vm/src/stdlib/weakref.rs similarity index 100% rename from vm/src/stdlib/weakref.rs rename to crates/vm/src/stdlib/weakref.rs diff --git a/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs similarity index 100% rename from vm/src/stdlib/winapi.rs rename to crates/vm/src/stdlib/winapi.rs diff --git a/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs similarity index 100% rename from vm/src/stdlib/winreg.rs rename to crates/vm/src/stdlib/winreg.rs diff --git a/vm/src/suggestion.rs b/crates/vm/src/suggestion.rs similarity index 100% rename from vm/src/suggestion.rs rename to crates/vm/src/suggestion.rs diff --git a/vm/src/types/mod.rs b/crates/vm/src/types/mod.rs similarity index 100% rename from vm/src/types/mod.rs rename to crates/vm/src/types/mod.rs diff --git a/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs similarity index 100% rename from vm/src/types/slot.rs rename to crates/vm/src/types/slot.rs diff --git a/vm/src/types/structseq.rs b/crates/vm/src/types/structseq.rs similarity index 100% rename from vm/src/types/structseq.rs rename to crates/vm/src/types/structseq.rs diff --git a/vm/src/types/zoo.rs b/crates/vm/src/types/zoo.rs similarity index 100% rename from vm/src/types/zoo.rs rename to crates/vm/src/types/zoo.rs diff --git a/vm/src/utils.rs b/crates/vm/src/utils.rs similarity index 100% rename from vm/src/utils.rs rename to crates/vm/src/utils.rs diff --git a/vm/src/version.rs b/crates/vm/src/version.rs similarity index 100% rename from vm/src/version.rs rename to crates/vm/src/version.rs diff --git a/vm/src/vm/compile.rs b/crates/vm/src/vm/compile.rs similarity index 100% rename from vm/src/vm/compile.rs rename to crates/vm/src/vm/compile.rs diff --git a/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs similarity index 100% rename from vm/src/vm/context.rs rename to crates/vm/src/vm/context.rs diff --git a/vm/src/vm/interpreter.rs b/crates/vm/src/vm/interpreter.rs similarity index 100% rename from vm/src/vm/interpreter.rs rename to crates/vm/src/vm/interpreter.rs diff --git a/vm/src/vm/method.rs b/crates/vm/src/vm/method.rs similarity index 100% rename from vm/src/vm/method.rs rename to crates/vm/src/vm/method.rs diff --git a/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs similarity index 99% rename from vm/src/vm/mod.rs rename to crates/vm/src/vm/mod.rs index a973140a99a..b793a283525 100644 --- a/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -1036,7 +1036,9 @@ fn test_nested_frozen() { vm::Interpreter::with_init(Default::default(), |vm| { // vm.add_native_modules(rustpython_stdlib::get_module_inits()); - vm.add_frozen(rustpython_vm::py_freeze!(dir = "../extra_tests/snippets")); + vm.add_frozen(rustpython_vm::py_freeze!( + dir = "../../extra_tests/snippets" + )); }) .enter(|vm| { let scope = vm.new_scope_with_builtins(); diff --git a/vm/src/vm/setting.rs b/crates/vm/src/vm/setting.rs similarity index 100% rename from vm/src/vm/setting.rs rename to crates/vm/src/vm/setting.rs diff --git a/vm/src/vm/thread.rs b/crates/vm/src/vm/thread.rs similarity index 100% rename from vm/src/vm/thread.rs rename to crates/vm/src/vm/thread.rs diff --git a/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs similarity index 100% rename from vm/src/vm/vm_new.rs rename to crates/vm/src/vm/vm_new.rs diff --git a/vm/src/vm/vm_object.rs b/crates/vm/src/vm/vm_object.rs similarity index 100% rename from vm/src/vm/vm_object.rs rename to crates/vm/src/vm/vm_object.rs diff --git a/vm/src/vm/vm_ops.rs b/crates/vm/src/vm/vm_ops.rs similarity index 100% rename from vm/src/vm/vm_ops.rs rename to crates/vm/src/vm/vm_ops.rs diff --git a/vm/src/warn.rs b/crates/vm/src/warn.rs similarity index 100% rename from vm/src/warn.rs rename to crates/vm/src/warn.rs diff --git a/vm/src/windows.rs b/crates/vm/src/windows.rs similarity index 100% rename from vm/src/windows.rs rename to crates/vm/src/windows.rs diff --git a/example_projects/barebone/Cargo.toml b/example_projects/barebone/Cargo.toml index 8bc49c237f3..b69617bdaf7 100644 --- a/example_projects/barebone/Cargo.toml +++ b/example_projects/barebone/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -rustpython-vm = { path = "../../vm", default-features = false } +rustpython-vm = { path = "../../crates/vm", default-features = false } [workspace] diff --git a/example_projects/frozen_stdlib/Cargo.toml b/example_projects/frozen_stdlib/Cargo.toml index 6824865cbf7..91bb14a083b 100644 --- a/example_projects/frozen_stdlib/Cargo.toml +++ b/example_projects/frozen_stdlib/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] rustpython = { path = "../../", default-features = false, features = ["freeze-stdlib"] } -rustpython-vm = { path = "../../vm", default-features = false, features = ["freeze-stdlib"] } +rustpython-vm = { path = "../../crates/vm", default-features = false, features = ["freeze-stdlib"] } rustpython-pylib = { path = "../../crates/pylib", default-features = false, features = ["freeze-stdlib"] } [workspace] diff --git a/vm/Lib/core_modules/codecs.py b/vm/Lib/core_modules/codecs.py deleted file mode 120000 index db3231198d9..00000000000 --- a/vm/Lib/core_modules/codecs.py +++ /dev/null @@ -1 +0,0 @@ -../../../Lib/codecs.py \ No newline at end of file diff --git a/vm/Lib/core_modules/copyreg.py b/vm/Lib/core_modules/copyreg.py deleted file mode 120000 index 4ac7f40c43d..00000000000 --- a/vm/Lib/core_modules/copyreg.py +++ /dev/null @@ -1 +0,0 @@ -../../../Lib/copyreg.py \ No newline at end of file diff --git a/vm/Lib/core_modules/encodings_utf_8.py b/vm/Lib/core_modules/encodings_utf_8.py deleted file mode 120000 index a8f34066264..00000000000 --- a/vm/Lib/core_modules/encodings_utf_8.py +++ /dev/null @@ -1 +0,0 @@ -../../../Lib/encodings/utf_8.py \ No newline at end of file diff --git a/vm/Lib/python_builtins/__hello__.py b/vm/Lib/python_builtins/__hello__.py deleted file mode 120000 index f6cae8932f0..00000000000 --- a/vm/Lib/python_builtins/__hello__.py +++ /dev/null @@ -1 +0,0 @@ -../../../Lib/__hello__.py \ No newline at end of file diff --git a/vm/Lib/python_builtins/__phello__ b/vm/Lib/python_builtins/__phello__ deleted file mode 120000 index 113aeb15040..00000000000 --- a/vm/Lib/python_builtins/__phello__ +++ /dev/null @@ -1 +0,0 @@ -../../../Lib/__phello__ \ No newline at end of file diff --git a/vm/Lib/python_builtins/_frozen_importlib.py b/vm/Lib/python_builtins/_frozen_importlib.py deleted file mode 120000 index d1d4364abac..00000000000 --- a/vm/Lib/python_builtins/_frozen_importlib.py +++ /dev/null @@ -1 +0,0 @@ -../../../Lib/importlib/_bootstrap.py \ No newline at end of file diff --git a/vm/Lib/python_builtins/_frozen_importlib_external.py b/vm/Lib/python_builtins/_frozen_importlib_external.py deleted file mode 120000 index ba8aff58051..00000000000 --- a/vm/Lib/python_builtins/_frozen_importlib_external.py +++ /dev/null @@ -1 +0,0 @@ -../../../Lib/importlib/_bootstrap_external.py \ No newline at end of file diff --git a/vm/Lib/python_builtins/_thread.py b/vm/Lib/python_builtins/_thread.py deleted file mode 120000 index fa4a34c4fb9..00000000000 --- a/vm/Lib/python_builtins/_thread.py +++ /dev/null @@ -1 +0,0 @@ -../../../Lib/_dummy_thread.py \ No newline at end of file diff --git a/wasm/wasm-unknown-test/Cargo.toml b/wasm/wasm-unknown-test/Cargo.toml index ed8c9fcb02e..277a9810b99 100644 --- a/wasm/wasm-unknown-test/Cargo.toml +++ b/wasm/wasm-unknown-test/Cargo.toml @@ -8,7 +8,7 @@ crate-type = ["cdylib"] [dependencies] getrandom = "0.3" -rustpython-vm = { path = "../../vm", default-features = false, features = ["compiler"] } +rustpython-vm = { path = "../../crates/vm", default-features = false, features = ["compiler"] } [workspace] From 8715ae76f173ece3413f9be111c2fda2fbc579b2 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 15 Nov 2025 15:54:25 +0200 Subject: [PATCH 353/819] Move `derive` -> `crates/derive` (#6264) --- Cargo.toml | 3 +-- {derive => crates/derive}/Cargo.toml | 0 {derive => crates/derive}/src/lib.rs | 0 3 files changed, 1 insertion(+), 2 deletions(-) rename {derive => crates/derive}/Cargo.toml (100%) rename {derive => crates/derive}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 8d344c133c8..54e4f1b6429 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,7 +122,6 @@ template = "installer-config/installer.wxs" resolver = "2" members = [ ".", - "derive", "stdlib", "wasm/lib", "crates/*", @@ -141,7 +140,7 @@ rustpython-compiler-core = { path = "crates/compiler-core", version = "0.4.0" } rustpython-compiler = { path = "crates/compiler", version = "0.4.0" } rustpython-codegen = { path = "crates/codegen", version = "0.4.0" } rustpython-common = { path = "crates/common", version = "0.4.0" } -rustpython-derive = { path = "derive", version = "0.4.0" } +rustpython-derive = { path = "crates/derive", version = "0.4.0" } rustpython-derive-impl = { path = "crates/derive-impl", version = "0.4.0" } rustpython-jit = { path = "crates/jit", version = "0.4.0" } rustpython-literal = { path = "crates/literal", version = "0.4.0" } diff --git a/derive/Cargo.toml b/crates/derive/Cargo.toml similarity index 100% rename from derive/Cargo.toml rename to crates/derive/Cargo.toml diff --git a/derive/src/lib.rs b/crates/derive/src/lib.rs similarity index 100% rename from derive/src/lib.rs rename to crates/derive/src/lib.rs From 4f8ef16937a1311f3dd0c236cbcb4dcaa932d954 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Sat, 15 Nov 2025 20:57:33 +0900 Subject: [PATCH 354/819] Re-organize vm.run_script inner functions --- crates/vm/src/vm/compile.rs | 59 ++++++++++++++++++++++++++++++++----- crates/vm/src/vm/context.rs | 29 +++++++++--------- src/lib.rs | 8 +++-- 3 files changed, 72 insertions(+), 24 deletions(-) diff --git a/crates/vm/src/vm/compile.rs b/crates/vm/src/vm/compile.rs index ee813bf7b46..26daf993d8d 100644 --- a/crates/vm/src/vm/compile.rs +++ b/crates/vm/src/vm/compile.rs @@ -26,7 +26,9 @@ impl VirtualMachine { compiler::compile(source, mode, &source_path, opts).map(|code| self.ctx.new_code(code)) } + // pymain_run_file_obj pub fn run_script(&self, scope: Scope, path: &str) -> PyResult<()> { + // when pymain_run_module? if get_importer(path, self)?.is_some() { self.insert_sys_path(self.new_pyobj(path))?; let runpy = self.import("runpy", 0)?; @@ -35,6 +37,7 @@ impl VirtualMachine { return Ok(()); } + // TODO: check if this is proper place if !self.state.settings.safe_path { let dir = std::path::Path::new(path) .parent() @@ -44,19 +47,61 @@ impl VirtualMachine { self.insert_sys_path(self.new_pyobj(dir))?; } - match std::fs::read_to_string(path) { - Ok(source) => { - self.run_code_string(scope, &source, path.to_owned())?; + self.run_any_file(scope, path) + } + + // = _PyRun_AnyFileObject + fn run_any_file(&self, scope: Scope, path: &str) -> PyResult<()> { + let path = if path.is_empty() { "???" } else { path }; + + self.run_simple_file(scope, path) + } + + // = _PyRun_SimpleFileObject + fn run_simple_file(&self, scope: Scope, path: &str) -> PyResult<()> { + // __main__ is given by scope + let sys_modules = self.sys_module.get_attr(identifier!(self, modules), self)?; + let main_module = sys_modules.get_item(identifier!(self, __main__), self)?; + let module_dict = main_module.dict().expect("main module must have __dict__"); + if !module_dict.contains_key(identifier!(self, __file__), self) { + module_dict.set_item( + identifier!(self, __file__), + self.ctx.new_str(path).into(), + self, + )?; + } + + // Consider to use enum to distinguish `path` + // https://github.com/RustPython/RustPython/pull/6276#discussion_r2529849479 + + // TODO: check .pyc here + let pyc = false; + if pyc { + todo!("running pyc is not implemented yet"); + } else { + if path != "<stdin>" { + // TODO: set_main_loader(dict, filename, "SourceFileLoader"); } - Err(err) => { - error!("Failed reading file '{path}': {err}"); - // TODO: Need to change to ExitCode or Termination - std::process::exit(1); + // TODO: replace to something equivalent to py_run_file + match std::fs::read_to_string(path) { + Ok(source) => { + let code_obj = self + .compile(&source, compiler::Mode::Exec, path.to_owned()) + .map_err(|err| self.new_syntax_error(&err, Some(&source)))?; + // trace!("Code object: {:?}", code_obj.borrow()); + self.run_code_obj(code_obj, scope)?; + } + Err(err) => { + error!("Failed reading file '{path}': {err}"); + // TODO: Need to change to ExitCode or Termination + std::process::exit(1); + } } } Ok(()) } + // TODO: deprecate or reimplement using other primitive functions pub fn run_code_string(&self, scope: Scope, source: &str, source_path: String) -> PyResult { let code_obj = self .compile(source, compiler::Mode::Exec, source_path.clone()) diff --git a/crates/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs index 4411aa0ff67..0e5b5498434 100644 --- a/crates/vm/src/vm/context.rs +++ b/crates/vm/src/vm/context.rs @@ -102,8 +102,8 @@ declare_const_name! { __ceil__, __cformat__, __class__, - __classcell__, __class_getitem__, + __classcell__, __complex__, __contains__, __copy__, @@ -234,26 +234,27 @@ declare_const_name! { _attributes, _fields, _showwarnmsg, + backslashreplace, + close, + copy, decode, encode, - keys, - items, - values, - version, - update, - copy, flush, - close, - WarningMessage, - strict, ignore, - replace, - xmlcharrefreplace, - backslashreplace, + items, + keys, + modules, namereplace, - surrogatepass, + replace, + strict, surrogateescape, + surrogatepass, + update, utf_8: "utf-8", + values, + version, + WarningMessage, + xmlcharrefreplace, } // Basic objects: diff --git a/src/lib.rs b/src/lib.rs index 362adfba490..dafc3fcd0d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,6 +154,7 @@ fn install_pip(installer: InstallPipMode, scope: Scope, vm: &VirtualMachine) -> } } +// pymain_run_python fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { #[cfg(feature = "flame-it")] let main_guard = flame::start_guard("RustPython main"); @@ -206,9 +207,10 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { vm.run_module(&module) } RunMode::InstallPip(installer) => install_pip(installer, scope.clone(), vm), - RunMode::Script(script) => { - debug!("Running script {}", &script); - vm.run_script(scope.clone(), &script) + RunMode::Script(script_path) => { + // pymain_run_file + debug!("Running script {}", &script_path); + vm.run_script(scope.clone(), &script_path) } RunMode::Repl => Ok(()), }; From 6991a80e13c06deaa7b4a0131fcfbf139621a465 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Sat, 15 Nov 2025 20:56:16 +0900 Subject: [PATCH 355/819] Add __main__.__cached__ --- crates/vm/src/vm/compile.rs | 1 + crates/vm/src/vm/context.rs | 1 + extra_tests/snippets/builtin___main__.py | 5 +++++ 3 files changed, 7 insertions(+) create mode 100644 extra_tests/snippets/builtin___main__.py diff --git a/crates/vm/src/vm/compile.rs b/crates/vm/src/vm/compile.rs index 26daf993d8d..6f1ea734926 100644 --- a/crates/vm/src/vm/compile.rs +++ b/crates/vm/src/vm/compile.rs @@ -69,6 +69,7 @@ impl VirtualMachine { self.ctx.new_str(path).into(), self, )?; + module_dict.set_item(identifier!(self, __cached__), self.ctx.none(), self)?; } // Consider to use enum to distinguish `path` diff --git a/crates/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs index 0e5b5498434..3fc25f3b992 100644 --- a/crates/vm/src/vm/context.rs +++ b/crates/vm/src/vm/context.rs @@ -98,6 +98,7 @@ declare_const_name! { __build_class__, __builtins__, __bytes__, + __cached__, __call__, __ceil__, __cformat__, diff --git a/extra_tests/snippets/builtin___main__.py b/extra_tests/snippets/builtin___main__.py new file mode 100644 index 00000000000..97e76ce324a --- /dev/null +++ b/extra_tests/snippets/builtin___main__.py @@ -0,0 +1,5 @@ +import sys + +main_module = sys.modules["__main__"] +assert main_module.__file__.endswith("builtin___main__.py") +assert main_module.__cached__ is None From 93e4e42b53a48d316aa8cf0707b34ad6567728aa Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 16 Nov 2025 12:14:35 +0200 Subject: [PATCH 356/819] Move `compiler/source` -> `crates/compiler-source` (#6261) --- .github/workflows/ci.yaml | 6 +++--- {compiler/source => crates/compiler-source}/Cargo.toml | 0 {compiler/source => crates/compiler-source}/src/lib.rs | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename {compiler/source => crates/compiler-source}/Cargo.toml (100%) rename {compiler/source => crates/compiler-source}/src/lib.rs (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9ab4610ed11..dcae77693af 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -137,13 +137,13 @@ jobs: if: runner.os == 'macOS' - name: run clippy - run: cargo clippy ${{ env.CARGO_ARGS }} --workspace --all-targets --exclude rustpython_wasm -- -Dwarnings + run: cargo clippy ${{ env.CARGO_ARGS }} --workspace --all-targets --exclude rustpython_wasm --exclude rustpython-compiler-source -- -Dwarnings - name: run rust tests - run: cargo test --workspace --exclude rustpython_wasm --verbose --features threading ${{ env.CARGO_ARGS }} + run: cargo test --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --verbose --features threading ${{ env.CARGO_ARGS }} if: runner.os != 'macOS' - name: run rust tests - run: cargo test --workspace --exclude rustpython_wasm --exclude rustpython-jit --verbose --features threading ${{ env.CARGO_ARGS }} + run: cargo test --workspace --exclude rustpython_wasm --exclude rustpython-jit --exclude rustpython-compiler-source --verbose --features threading ${{ env.CARGO_ARGS }} if: runner.os == 'macOS' - name: check compilation without threading diff --git a/compiler/source/Cargo.toml b/crates/compiler-source/Cargo.toml similarity index 100% rename from compiler/source/Cargo.toml rename to crates/compiler-source/Cargo.toml diff --git a/compiler/source/src/lib.rs b/crates/compiler-source/src/lib.rs similarity index 100% rename from compiler/source/src/lib.rs rename to crates/compiler-source/src/lib.rs From 1568d2a7fbe7c8c70b85ac0239053c4ff6a6a7ec Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 16 Nov 2025 12:15:32 +0200 Subject: [PATCH 357/819] Remove leftovers after vm crate move (#6279) --- vm/.gitignore | 5 ----- vm/sre_engine/.vscode/launch.json | 21 --------------------- 2 files changed, 26 deletions(-) delete mode 100644 vm/.gitignore delete mode 100644 vm/sre_engine/.vscode/launch.json diff --git a/vm/.gitignore b/vm/.gitignore deleted file mode 100644 index 4931874f785..00000000000 --- a/vm/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -venv/ -tests/*.bytecode -Cargo.lock -python_compiler/target -target/ diff --git a/vm/sre_engine/.vscode/launch.json b/vm/sre_engine/.vscode/launch.json deleted file mode 100644 index 5ebfe34f05a..00000000000 --- a/vm/sre_engine/.vscode/launch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug Unit Test", - "cargo": { - "args": [ - "test", - "--no-run" - ], - "filter": { - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - } - ] -} \ No newline at end of file From 7c4c1eabf04fe3a26bb4dee4fa1ed8214b8bae7b Mon Sep 17 00:00:00 2001 From: Lee Dogeon <dev.moreal@gmail.com> Date: Sun, 16 Nov 2025 22:16:34 +0900 Subject: [PATCH 358/819] Fix wasm32-unknown-unknown target build (#6278) --- Cargo.toml | 2 +- crates/vm/Cargo.toml | 3 +-- wasm/wasm-unknown-test/src/lib.rs | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 54e4f1b6429..5f8f7a5b555 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -162,7 +162,7 @@ ascii = "1.1" bitflags = "2.9.4" bstr = "1" cfg-if = "1.0" -chrono = "0.4.42" +chrono = { version = "0.4.42", default-features = false, features = ["clock", "oldtime", "std"] } constant_time_eq = "0.4" criterion = { version = "0.7", features = ["html_reports"] } crossbeam-utils = "0.8.21" diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index ffd58361789..1e12454a6ba 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -46,7 +46,7 @@ bitflags = { workspace = true } bstr = { workspace = true } cfg-if = { workspace = true } crossbeam-utils = { workspace = true } -chrono = { workspace = true, features = ["wasmbind"] } +chrono = { workspace = true } constant_time_eq = { workspace = true } flame = { workspace = true, optional = true } getrandom = { workspace = true } @@ -150,7 +150,6 @@ features = [ [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] wasm-bindgen = { workspace = true, optional = true } -getrandom = { workspace = true } [build-dependencies] glob = { workspace = true } diff --git a/wasm/wasm-unknown-test/src/lib.rs b/wasm/wasm-unknown-test/src/lib.rs index a3beead8900..aae922864dd 100644 --- a/wasm/wasm-unknown-test/src/lib.rs +++ b/wasm/wasm-unknown-test/src/lib.rs @@ -1,5 +1,6 @@ use rustpython_vm::{Interpreter, eval}; +#[unsafe(no_mangle)] pub unsafe extern "C" fn eval(s: *const u8, l: usize) -> u32 { let src = std::slice::from_raw_parts(s, l); let src = std::str::from_utf8(src).unwrap(); From 1a783fc9ec16bd824967449bc9899661a5ee5761 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 16 Nov 2025 22:17:35 +0900 Subject: [PATCH 359/819] Replace SSL backend to rustls (#6244) --- .cspell.dict/cpython.txt | 7 +- .cspell.dict/rust-more.txt | 2 + .github/workflows/ci.yaml | 17 +- Cargo.lock | 825 ++- Cargo.toml | 8 +- Lib/test/test_ssl.py | 68 +- README.md | 17 +- crates/vm/src/frame.rs | 2 +- src/lib.rs | 10 +- stdlib/Cargo.toml | 25 +- stdlib/build.rs | 37 +- stdlib/src/lib.rs | 14 +- stdlib/src/openssl.rs | 3705 +++++++++++ stdlib/src/openssl/cert.rs | 229 + stdlib/src/{ssl => openssl}/ssl_data_111.rs | 0 stdlib/src/{ssl => openssl}/ssl_data_300.rs | 0 stdlib/src/{ssl => openssl}/ssl_data_31.rs | 0 stdlib/src/ssl.rs | 6628 +++++++++++-------- stdlib/src/ssl/cert.rs | 1932 +++++- stdlib/src/ssl/compat.rs | 1786 +++++ stdlib/src/ssl/oid.rs | 464 ++ 21 files changed, 12700 insertions(+), 3076 deletions(-) create mode 100644 stdlib/src/openssl.rs create mode 100644 stdlib/src/openssl/cert.rs rename stdlib/src/{ssl => openssl}/ssl_data_111.rs (100%) rename stdlib/src/{ssl => openssl}/ssl_data_300.rs (100%) rename stdlib/src/{ssl => openssl}/ssl_data_31.rs (100%) create mode 100644 stdlib/src/ssl/compat.rs create mode 100644 stdlib/src/ssl/oid.rs diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 8c733e343d1..5676549ec1a 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -2,11 +2,14 @@ argtypes asdl asname augassign +badcert badsyntax basetype boolop bxor cached_tsver +cadata +cafile cellarg cellvar cellvars @@ -23,8 +26,8 @@ freevars fromlist heaptype HIGHRES -Itertool IMMUTABLETYPE +Itertool kwonlyarg kwonlyargs lasti @@ -47,6 +50,7 @@ stackdepth stringlib structseq subparams +ticketer tok_oldval tvars unaryop @@ -56,6 +60,7 @@ VARKEYWORDS varkwarg wbits weakreflist +webpki withitem withs xstat diff --git a/.cspell.dict/rust-more.txt b/.cspell.dict/rust-more.txt index 6f89fdfafe1..f27e53bd6ed 100644 --- a/.cspell.dict/rust-more.txt +++ b/.cspell.dict/rust-more.txt @@ -50,6 +50,7 @@ nanos nonoverlapping objclass peekable +pemfile powc powf powi @@ -61,6 +62,7 @@ rposition rsplitn rustc rustfmt +rustls rustyline seedable seekfrom diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index dcae77693af..6963ee5f7c4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,7 +16,8 @@ concurrency: cancel-in-progress: true env: - CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl + CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls + CARGO_ARGS_NO_SSL: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite # Skip additional tests on Windows. They are checked on Linux and MacOS. # test_glob: many failing tests # test_io: many failing tests @@ -169,7 +170,7 @@ jobs: target: aarch64-apple-ios if: runner.os == 'macOS' - name: Check compilation for iOS - run: cargo check --target aarch64-apple-ios + run: cargo check --target aarch64-apple-ios ${{ env.CARGO_ARGS_NO_SSL }} if: runner.os == 'macOS' exotic_targets: @@ -186,14 +187,14 @@ jobs: - name: Install gcc-multilib and musl-tools run: sudo apt-get update && sudo apt-get install gcc-multilib musl-tools - name: Check compilation for x86 32bit - run: cargo check --target i686-unknown-linux-gnu + run: cargo check --target i686-unknown-linux-gnu ${{ env.CARGO_ARGS_NO_SSL }} - uses: dtolnay/rust-toolchain@stable with: target: aarch64-linux-android - name: Check compilation for android - run: cargo check --target aarch64-linux-android + run: cargo check --target aarch64-linux-android ${{ env.CARGO_ARGS_NO_SSL }} - uses: dtolnay/rust-toolchain@stable with: @@ -202,28 +203,28 @@ jobs: - name: Install gcc-aarch64-linux-gnu run: sudo apt install gcc-aarch64-linux-gnu - name: Check compilation for aarch64 linux gnu - run: cargo check --target aarch64-unknown-linux-gnu + run: cargo check --target aarch64-unknown-linux-gnu ${{ env.CARGO_ARGS_NO_SSL }} - uses: dtolnay/rust-toolchain@stable with: target: i686-unknown-linux-musl - name: Check compilation for musl - run: cargo check --target i686-unknown-linux-musl + run: cargo check --target i686-unknown-linux-musl ${{ env.CARGO_ARGS_NO_SSL }} - uses: dtolnay/rust-toolchain@stable with: target: x86_64-unknown-freebsd - name: Check compilation for freebsd - run: cargo check --target x86_64-unknown-freebsd + run: cargo check --target x86_64-unknown-freebsd ${{ env.CARGO_ARGS_NO_SSL }} - uses: dtolnay/rust-toolchain@stable with: target: x86_64-unknown-freebsd - name: Check compilation for freeBSD - run: cargo check --target x86_64-unknown-freebsd + run: cargo check --target x86_64-unknown-freebsd ${{ env.CARGO_ARGS_NO_SSL }} # - name: Prepare repository for redox compilation # run: bash scripts/redox/uncomment-cargo.sh diff --git a/Cargo.lock b/Cargo.lock index e1b1d958d30..cec9f7afb47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.12" @@ -134,6 +145,45 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic" version = "0.6.1" @@ -179,12 +229,57 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-fips-sys" +version = "0.13.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede71ad84efb06d748d9af3bc500b14957a96282a69a6833b1420dcacb411cc3" +dependencies = [ + "bindgen 0.72.1", + "cc", + "cmake", + "dunce", + "fs_extra", + "regex", +] + +[[package]] +name = "aws-lc-rs" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +dependencies = [ + "aws-lc-fips-sys", + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" +dependencies = [ + "bindgen 0.72.1", + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + [[package]] name = "bindgen" version = "0.71.1" @@ -205,6 +300,26 @@ dependencies = [ "syn", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -235,6 +350,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "bstr" version = "1.12.0" @@ -261,6 +385,12 @@ version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + [[package]] name = "bzip2" version = "0.6.1" @@ -294,6 +424,15 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.41" @@ -301,9 +440,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -365,6 +512,16 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -410,6 +567,15 @@ dependencies = [ "error-code", ] +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "collection_literals" version = "1.0.3" @@ -422,6 +588,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compact_str" version = "0.9.0" @@ -458,6 +634,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.4.2" @@ -474,6 +656,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -751,6 +943,59 @@ dependencies = [ "memchr", ] +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive-where" version = "1.6.0" @@ -794,6 +1039,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dns-lookup" version = "3.0.0" @@ -806,6 +1062,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -916,13 +1178,19 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +[[package]] +name = "flagset" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" + [[package]] name = "flame" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc2706461e1ee94f55cab2ed2e3d34ae9536cfa830358ef80acff1a3dacab30" dependencies = [ - "lazy_static", + "lazy_static 0.2.11", "serde", "serde_derive", "serde_json", @@ -984,6 +1252,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "generic-array" version = "0.14.9" @@ -1128,6 +1402,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.11" @@ -1177,6 +1460,16 @@ version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "insta" version = "1.43.2" @@ -1260,6 +1553,38 @@ dependencies = [ "syn", ] +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.81" @@ -1295,6 +1620,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "lexical-parse-float" version = "1.0.6" @@ -1650,6 +1981,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -1659,6 +2000,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.46" @@ -1708,6 +2055,15 @@ dependencies = [ "syn", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1825,6 +2181,25 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "phf" version = "0.11.3" @@ -1919,6 +2294,33 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "pkcs5", + "rand_core 0.6.4", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -1979,6 +2381,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2325,6 +2733,20 @@ dependencies = [ "syn", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "ruff_python_ast" version = "0.0.0" @@ -2398,6 +2820,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "1.1.2" @@ -2411,6 +2842,89 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustpython" version = "0.4.0" @@ -2597,13 +3111,16 @@ dependencies = [ "adler32", "ahash", "ascii", + "aws-lc-rs", "base64", "blake2", "bzip2", "cfg-if", + "chrono", "crc32fast", "crossbeam-utils", "csv-core", + "der", "digest", "dns-lookup", "dyn-clone", @@ -2628,16 +3145,23 @@ dependencies = [ "num-integer", "num-traits", "num_enum", + "oid-registry", "openssl", "openssl-probe", "openssl-sys", "page_size", "parking_lot", "paste", + "pem-rfc7468", "phf 0.11.3", + "pkcs8", "pymath", "rand_core 0.9.3", "rustix", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-platform-verifier", "rustpython-common", "rustpython-derive", "rustpython-vm", @@ -2661,8 +3185,11 @@ dependencies = [ "unicode-casing", "unicode_names2 2.0.0", "uuid", + "webpki-roots 0.26.11", "widestring", "windows-sys 0.59.0", + "x509-cert", + "x509-parser", "xml", "xz2", ] @@ -2815,6 +3342,15 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -2845,6 +3381,40 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.9.4", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.228" @@ -2919,6 +3489,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2945,7 +3526,7 @@ name = "shared-build" version = "0.2.0" source = "git+https://github.com/arihant2math/tkinter.git?tag=v0.2.0#198fc35b1f18f4eda401f97a641908f321b1403a" dependencies = [ - "bindgen", + "bindgen 0.71.1", ] [[package]] @@ -2954,6 +3535,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -2988,6 +3578,16 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3046,6 +3646,17 @@ dependencies = [ "syn", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "system-configuration" version = "0.6.1" @@ -3053,7 +3664,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.9.4", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3157,6 +3768,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "timsort" version = "0.1.3" @@ -3197,6 +3839,27 @@ dependencies = [ "shared-build", ] +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.8.23" @@ -3457,6 +4120,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "utf8parse" version = "0.2.2" @@ -3605,6 +4280,33 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.4", +] + +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "8.0.0" @@ -3741,6 +4443,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3777,6 +4488,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -3810,6 +4536,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3822,6 +4554,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3834,6 +4572,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3858,6 +4602,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3870,6 +4620,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3882,6 +4638,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3894,6 +4656,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3947,6 +4715,37 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der", + "sha1", + "signature", + "spki", + "tls_codec", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static 1.5.0", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "xml" version = "1.0.1" @@ -3982,6 +4781,26 @@ dependencies = [ "syn", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zlib-rs" version = "0.5.2" diff --git a/Cargo.toml b/Cargo.toml index 5f8f7a5b555..c1ee8eaa3d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository.workspace = true license.workspace = true [features] -default = ["threading", "stdlib", "stdio", "importlib"] +default = ["threading", "stdlib", "stdio", "importlib", "ssl-rustls"] importlib = ["rustpython-vm/importlib"] encodings = ["rustpython-vm/encodings"] stdio = ["rustpython-vm/stdio"] @@ -20,8 +20,10 @@ freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/fre jit = ["rustpython-vm/jit"] threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"] sqlite = ["rustpython-stdlib/sqlite"] -ssl = ["rustpython-stdlib/ssl"] -ssl-vendor = ["ssl", "rustpython-stdlib/ssl-vendor"] +ssl = [] +ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"] +ssl-openssl = ["ssl", "rustpython-stdlib/ssl-openssl"] +ssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-vendor"] tkinter = ["rustpython-stdlib/tkinter"] [build-dependencies] diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index fea3a2ce692..f073def5bc1 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -426,7 +426,6 @@ def test_random(self): ssl.RAND_add(b"this is a random bytes object", 75.0) ssl.RAND_add(bytearray(b"this is a random bytearray object"), 75.0) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_cert(self): # note that this uses an 'unofficial' function in _ssl.c, # provided solely for this test, to exercise the certificate @@ -506,7 +505,6 @@ def test_parse_cert_CVE_2013_4238(self): self.assertEqual(p['subjectAltName'], san) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_all_sans(self): p = ssl._ssl._test_decode_cert(ALLSANFILE) self.assertEqual(p['subjectAltName'], @@ -927,7 +925,6 @@ def test_connect_ex_error(self): ) self.assertIn(rc, errors) - @unittest.skip("TODO: RUSTPYTHON; hangs") def test_read_write_zero(self): # empty reads and writes now work, bpo-42854, bpo-31711 client_context, server_context, hostname = testing_context() @@ -993,7 +990,6 @@ def test_get_ciphers(self): len(intersection), 2, f"\ngot: {sorted(names)}\nexpected: {sorted(expected)}" ) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_options(self): # Test default SSLContext options ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -1066,8 +1062,8 @@ def test_hostname_checks_common_name(self): with self.assertRaises(AttributeError): ctx.hostname_checks_common_name = True - @ignore_deprecation @unittest.expectedFailure # TODO: RUSTPYTHON + @ignore_deprecation def test_min_max_version(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) # OpenSSL default is MINIMUM_SUPPORTED, however some vendors like @@ -1185,7 +1181,6 @@ def test_verify_flags(self): with self.assertRaises(TypeError): ctx.verify_flags = None - @unittest.expectedFailure # TODO: RUSTPYTHON def test_load_cert_chain(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) # Combined key and cert in a single file @@ -1294,7 +1289,6 @@ def race(): self.assertIsNone(cm.exc_value) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_load_verify_locations(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ctx.load_verify_locations(CERTFILE) @@ -1314,7 +1308,6 @@ def test_load_verify_locations(self): # Issue #10989: crash if the second argument type is invalid self.assertRaises(TypeError, ctx.load_verify_locations, None, True) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_load_verify_cadata(self): # test cadata with open(CAFILE_CACERT) as f: @@ -1380,7 +1373,6 @@ def test_load_verify_cadata(self): with self.assertRaises(ssl.SSLError): ctx.load_verify_locations(cadata=cacert_der + b"A") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_load_dh_params(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) try: @@ -1473,7 +1465,6 @@ def test_cert_store_stats(self): self.assertEqual(ctx.cert_store_stats(), {'x509_ca': 1, 'crl': 0, 'x509': 2}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_get_ca_certs(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) self.assertEqual(ctx.get_ca_certs(), []) @@ -1732,7 +1723,6 @@ def test_lib_reason(self): s = str(cm.exception) self.assertTrue("NO_START_LINE" in s, s) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_subclass(self): # Check that the appropriate SSLError subclass is raised # (this only tests one of them) @@ -1751,7 +1741,6 @@ def test_subclass(self): self.assertEqual(cm.exception.errno, ssl.SSL_ERROR_WANT_READ) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_bad_server_hostname(self): ctx = ssl.create_default_context() with self.assertRaises(ValueError): @@ -1838,7 +1827,6 @@ def test_private_init(self): with self.assertRaisesRegex(TypeError, "public constructor"): ssl.SSLObject(bio, bio) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_unwrap(self): client_ctx, server_ctx, hostname = testing_context() c_in = ssl.MemoryBIO() @@ -2193,7 +2181,6 @@ def ssl_io_loop(self, sock, incoming, outgoing, func, *args, **kwargs): % (count, func.__name__)) return ret - @unittest.expectedFailure # TODO: RUSTPYTHON def test_bio_handshake(self): sock = socket.socket(socket.AF_INET) self.addCleanup(sock.close) @@ -2230,7 +2217,6 @@ def test_bio_handshake(self): pass self.assertRaises(ssl.SSLError, sslobj.write, b'foo') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_bio_read_write_data(self): sock = socket.socket(socket.AF_INET) self.addCleanup(sock.close) @@ -2248,7 +2234,6 @@ def test_bio_read_write_data(self): self.assertEqual(buf, b'foo\n') self.ssl_io_loop(sock, incoming, outgoing, sslobj.unwrap) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_transport_eof(self): client_context, server_context, hostname = testing_context() with socket.socket(socket.AF_INET) as sock: @@ -3565,7 +3550,6 @@ def test_socketserver(self): f.close() self.assertEqual(d1, d2) - @unittest.skip("TODO: RUSTPYTHON; hangs") def test_asyncore_server(self): """Check the example asyncore integration.""" if support.verbose: @@ -3595,7 +3579,6 @@ def test_asyncore_server(self): if support.verbose: sys.stdout.write(" client: connection closed.\n") - @unittest.skip("TODO: RUSTPYTHON; hangs") def test_recv_send(self): """Test recv(), send() and friends.""" if support.verbose: @@ -3732,7 +3715,6 @@ def _recvfrom_into(): s.close() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_recv_zero(self): server = ThreadedEchoServer(CERTFILE) self.enterContext(server) @@ -4040,6 +4022,7 @@ def test_default_ecdh_curve(self): s.connect((HOST, server.port)) self.assertIn("ECDH", s.cipher()[0]) + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless("tls-unique" in ssl.CHANNEL_BINDING_TYPES, "'tls-unique' channel binding not available") def test_tls_unique_channel_binding(self): @@ -4212,7 +4195,6 @@ def test_selected_alpn_protocol_if_server_uses_alpn(self): sni_name=hostname) self.assertIs(stats['client_alpn_protocol'], None) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_alpn_protocols(self): server_protocols = ['foo', 'bar', 'milkshake'] protocol_tests = [ @@ -4263,7 +4245,6 @@ def check_common_name(self, stats, name): cert = stats['peercert'] self.assertIn((('commonName', name),), cert['subject']) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_sni_callback(self): calls = [] server_context, other_context, client_context = self.sni_contexts() @@ -4514,7 +4495,6 @@ def test_session_handling(self): 'Session refers to a different SSLContext.') @requires_tls_version('TLSv1_2') - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(ssl.HAS_PSK, 'TLS-PSK disabled on this OpenSSL build') def test_psk(self): psk = bytes.fromhex('deadbeef') @@ -4583,7 +4563,6 @@ def server_callback(identity): s.connect((HOST, server.port)) @requires_tls_version('TLSv1_3') - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(ssl.HAS_PSK, 'TLS-PSK disabled on this OpenSSL build') def test_psk_tls1_3(self): psk = bytes.fromhex('deadbeef') @@ -4616,6 +4595,43 @@ def server_callback(identity): with client_context.wrap_socket(socket.socket()) as s: s.connect((HOST, server.port)) + @unittest.skip("TODO: rustpython") + def test_thread_recv_while_main_thread_sends(self): + # GH-137583: Locking was added to calls to send() and recv() on SSL + # socket objects. This seemed fine at the surface level because those + # calls weren't re-entrant, but recv() calls would implicitly mimick + # holding a lock by blocking until it received data. This means that + # if a thread started to infinitely block until data was received, calls + # to send() would deadlock, because it would wait forever on the lock + # that the recv() call held. + data = b"1" * 1024 + event = threading.Event() + def background(sock): + event.set() + received = sock.recv(len(data)) + self.assertEqual(received, data) + + client_context, server_context, hostname = testing_context() + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as sock: + sock.connect((HOST, server.port)) + sock.settimeout(1) + sock.setblocking(1) + # Ensure that the server is ready to accept requests + sock.sendall(b"123") + self.assertEqual(sock.recv(3), b"123") + with threading_helper.catch_threading_exception() as cm: + thread = threading.Thread(target=background, + args=(sock,), daemon=True) + thread.start() + event.wait() + sock.sendall(data) + thread.join() + if cm.exc_value is not None: + raise cm.exc_value + @unittest.skipUnless(has_tls_version('TLSv1_3'), "Test needs TLS 1.3") class TestPostHandshakeAuth(unittest.TestCase): @@ -4736,6 +4752,7 @@ def test_pha_optional(self): s.write(b'HASCERT') self.assertEqual(s.recv(1024), b'TRUE\n') + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pha_optional_nocert(self): if support.verbose: sys.stdout.write("\n") @@ -4775,6 +4792,7 @@ def test_pha_no_pha_client(self): s.write(b'PHA') self.assertIn(b'extension not received', s.recv(1024)) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pha_no_pha_server(self): # server doesn't have PHA enabled, cert is requested in handshake client_context, server_context, hostname = testing_context() @@ -4844,7 +4862,6 @@ def test_bpo37428_pha_cert_none(self): # server cert has not been validated self.assertEqual(s.getpeercert(), {}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_internal_chain_client(self): client_context, server_context, hostname = testing_context( server_chain=False @@ -4916,7 +4933,6 @@ def test_certificate_chain(self): self.assertEqual(ee, uvc[0]) self.assertNotEqual(ee, ca) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_internal_chain_server(self): client_context, server_context, hostname = testing_context() client_context.load_cert_chain(SIGNED_CERTFILE) @@ -5040,7 +5056,6 @@ def test_keylog_env(self): ctx = ssl._create_stdlib_context() self.assertEqual(ctx.keylog_filename, os_helper.TESTFN) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_msg_callback(self): client_context, server_context, hostname = testing_context() @@ -5085,7 +5100,6 @@ def msg_cb(conn, direction, version, content_type, msg_type, data): msg ) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_msg_callback_deadlock_bpo43577(self): client_context, server_context, hostname = testing_context() server_context2 = testing_context()[1] diff --git a/README.md b/README.md index ce5f02bee23..86d0738ec8e 100644 --- a/README.md +++ b/README.md @@ -66,17 +66,11 @@ Welcome to the magnificent Rust Python interpreter >>>>> ``` -If you'd like to make https requests, you can enable the `ssl` feature, which -also lets you install the `pip` package manager. Note that on Windows, you may -need to install OpenSSL, or you can enable the `ssl-vendor` feature instead, -which compiles OpenSSL for you but requires a C compiler, perl, and `make`. -OpenSSL version 3 is expected and tested in CI. Older versions may not work. - -Once you've installed rustpython with SSL support, you can install pip by +You can install pip by running: ```bash -cargo install --git https://github.com/RustPython/RustPython --features ssl +cargo install --git https://github.com/RustPython/RustPython rustpython --install-pip ``` @@ -88,6 +82,13 @@ conda install rustpython -c conda-forge rustpython ``` +### SSL provider + +For HTTPS requests, `ssl-rustls` feature is enabled by default. You can replace it with `ssl-openssl` feature if your environment requires OpenSSL. +Note that to use OpenSSL on Windows, you may need to install OpenSSL, or you can enable the `ssl-vendor` feature instead, +which compiles OpenSSL for you but requires a C compiler, perl, and `make`. +OpenSSL version 3 is expected and tested in CI. Older versions may not work. + ### WASI You can compile RustPython to a standalone WebAssembly WASI module so it can run anywhere. diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 89d19f15c8a..ab90f52b2df 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -520,7 +520,7 @@ impl ExecutingFrame<'_> { trace!(" {:#?}", self); trace!( " Executing op code: {}", - instruction.display(arg, &self.code.code).to_string() + instruction.display(arg, &self.code.code) ); trace!("======="); } diff --git a/src/lib.rs b/src/lib.rs index dafc3fcd0d5..976d0bc0a34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,14 @@ pub use rustpython_vm as vm; pub use settings::{InstallPipMode, RunMode, parse_opts}; pub use shell::run_shell; +#[cfg(all( + feature = "ssl", + not(any(feature = "ssl-rustls", feature = "ssl-openssl")) +))] +compile_error!( + "Feature \"ssl\" is now enabled by either \"ssl-rustls\" or \"ssl-openssl\" to be enabled. Do not manually pass \"ssl\" feature. To enable ssl-openssl, use --no-default-features to disable ssl-rustls" +); + /// The main cli of the `rustpython` interpreter. This function will return `std::process::ExitCode` /// based on the return code of the python code ran through the cli. pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode { @@ -141,7 +149,7 @@ __import__("io").TextIOWrapper( } fn install_pip(installer: InstallPipMode, scope: Scope, vm: &VirtualMachine) -> PyResult<()> { - if cfg!(not(feature = "ssl")) { + if !cfg!(feature = "ssl") { return Err(vm.new_exception_msg( vm.ctx.exceptions.system_error.to_owned(), "install-pip requires rustpython be build with '--features=ssl'".to_owned(), diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml index 7f64802d352..e62872324ea 100644 --- a/stdlib/Cargo.toml +++ b/stdlib/Cargo.toml @@ -15,8 +15,12 @@ default = ["compiler"] compiler = ["rustpython-vm/compiler"] threading = ["rustpython-common/threading", "rustpython-vm/threading"] sqlite = ["dep:libsqlite3-sys"] -ssl = ["openssl", "openssl-sys", "foreign-types-shared", "openssl-probe"] -ssl-vendor = ["ssl", "openssl/vendored"] +# SSL backends - default to rustls +ssl = [] +ssl-rustls = ["ssl", "rustls", "rustls-native-certs", "rustls-pemfile", "rustls-platform-verifier", "x509-cert", "x509-parser", "der", "pem-rfc7468", "webpki-roots", "aws-lc-rs", "oid-registry", "pkcs8"] +ssl-rustls-fips = ["ssl-rustls", "aws-lc-rs/fips"] +ssl-openssl = ["ssl", "openssl", "openssl-sys", "foreign-types-shared", "openssl-probe"] +ssl-vendor = ["ssl-openssl", "openssl/vendored"] tkinter = ["dep:tk-sys", "dep:tcl-sys"] [dependencies] @@ -86,6 +90,7 @@ bzip2 = "0.6" # tkinter tk-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0", optional = true } tcl-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0", optional = true } +chrono.workspace = true # uuid [target.'cfg(not(any(target_os = "ios", target_os = "android", target_os = "windows", target_arch = "wasm32", target_os = "redox")))'.dependencies] @@ -107,11 +112,27 @@ rustix = { workspace = true } gethostname = "1.0.2" socket2 = { version = "0.6.0", features = ["all"] } dns-lookup = "3.0" + +# OpenSSL dependencies (optional, for ssl-openssl feature) openssl = { version = "0.10.72", optional = true } openssl-sys = { version = "0.9.110", optional = true } openssl-probe = { version = "0.1.5", optional = true } foreign-types-shared = { version = "0.1.1", optional = true } +# Rustls dependencies (optional, for ssl-rustls feature) +rustls = { version = "0.23.35", default-features = false, features = ["std", "tls12", "aws_lc_rs"], optional = true } +rustls-native-certs = { version = "0.8", optional = true } +rustls-pemfile = { version = "2.2", optional = true } +rustls-platform-verifier = { version = "0.6", optional = true } +x509-cert = { version = "0.2.5", features = ["pem", "builder"], optional = true } +x509-parser = { version = "0.16", optional = true } +der = { version = "0.7", features = ["alloc", "oid"], optional = true } +pem-rfc7468 = { version = "0.7", optional = true } +webpki-roots = { version = "0.26", optional = true } +aws-lc-rs = { version = "1.14.1", optional = true } +oid-registry = { version = "0.7", features = ["x509", "pkcs1", "nist_algs"], optional = true } +pkcs8 = { version = "0.10", features = ["encryption", "pkcs5", "pem"], optional = true } + [target.'cfg(not(any(target_os = "android", target_arch = "wasm32")))'.dependencies] libsqlite3-sys = { version = "0.28", features = ["bundled"], optional = true } lzma-sys = "0.1" diff --git a/stdlib/build.rs b/stdlib/build.rs index b7bf6307157..83ebd81ead6 100644 --- a/stdlib/build.rs +++ b/stdlib/build.rs @@ -23,25 +23,28 @@ fn main() { println!("cargo::rustc-check-cfg=cfg({cfg})"); } - #[allow(clippy::unusual_byte_groupings)] - if let Ok(v) = std::env::var("DEP_OPENSSL_VERSION_NUMBER") { - println!("cargo:rustc-env=OPENSSL_API_VERSION={v}"); - // cfg setup from openssl crate's build script - let version = u64::from_str_radix(&v, 16).unwrap(); - for (ver, cfg) in ossl_vers { - if version >= ver { - println!("cargo:rustc-cfg={cfg}"); + #[cfg(feature = "ssl-openssl")] + { + #[allow(clippy::unusual_byte_groupings)] + if let Ok(v) = std::env::var("DEP_OPENSSL_VERSION_NUMBER") { + println!("cargo:rustc-env=OPENSSL_API_VERSION={v}"); + // cfg setup from openssl crate's build script + let version = u64::from_str_radix(&v, 16).unwrap(); + for (ver, cfg) in ossl_vers { + if version >= ver { + println!("cargo:rustc-cfg={cfg}"); + } } } - } - if let Ok(v) = std::env::var("DEP_OPENSSL_CONF") { - for conf in v.split(',') { - println!("cargo:rustc-cfg=osslconf=\"{conf}\""); + if let Ok(v) = std::env::var("DEP_OPENSSL_CONF") { + for conf in v.split(',') { + println!("cargo:rustc-cfg=osslconf=\"{conf}\""); + } + } + // it's possible for openssl-sys to link against the system openssl under certain conditions, + // so let the ssl module know to only perform a probe if we're actually vendored + if std::env::var("DEP_OPENSSL_VENDORED").is_ok_and(|s| s == "1") { + println!("cargo::rustc-cfg=openssl_vendored") } - } - // it's possible for openssl-sys to link against the system openssl under certain conditions, - // so let the ssl module know to only perform a probe if we're actually vendored - if std::env::var("DEP_OPENSSL_VENDORED").is_ok_and(|s| s == "1") { - println!("cargo::rustc-cfg=openssl_vendored") } } diff --git a/stdlib/src/lib.rs b/stdlib/src/lib.rs index 706ce0ef210..01a27b76609 100644 --- a/stdlib/src/lib.rs +++ b/stdlib/src/lib.rs @@ -75,8 +75,14 @@ mod select; not(any(target_os = "android", target_arch = "wasm32")) ))] mod sqlite; -#[cfg(all(not(target_arch = "wasm32"), feature = "ssl"))] + +#[cfg(all(not(target_arch = "wasm32"), feature = "ssl-openssl"))] +mod openssl; +#[cfg(all(not(target_arch = "wasm32"), feature = "ssl-rustls"))] mod ssl; +#[cfg(all(feature = "ssl-openssl", feature = "ssl-rustls"))] +compile_error!("features \"ssl-openssl\" and \"ssl-rustls\" are mutually exclusive"); + #[cfg(all(unix, not(target_os = "redox"), not(target_os = "ios")))] mod termios; #[cfg(not(any( @@ -167,10 +173,14 @@ pub fn get_module_inits() -> impl Iterator<Item = (Cow<'static, str>, StdlibInit { "_sqlite3" => sqlite::make_module, } - #[cfg(feature = "ssl")] + #[cfg(all(not(target_arch = "wasm32"), feature = "ssl-rustls"))] { "_ssl" => ssl::make_module, } + #[cfg(all(not(target_arch = "wasm32"), feature = "ssl-openssl"))] + { + "_ssl" => openssl::make_module, + } #[cfg(windows)] { "_overlapped" => overlapped::make_module, diff --git a/stdlib/src/openssl.rs b/stdlib/src/openssl.rs new file mode 100644 index 00000000000..ea67d605f76 --- /dev/null +++ b/stdlib/src/openssl.rs @@ -0,0 +1,3705 @@ +// spell-checker:disable + +mod cert; + +// Conditional compilation for OpenSSL version-specific error codes +cfg_if::cfg_if! { + if #[cfg(ossl310)] { + // OpenSSL 3.1.0+ + mod ssl_data_31; + use ssl_data_31 as ssl_data; + } else if #[cfg(ossl300)] { + // OpenSSL 3.0.0+ + mod ssl_data_300; + use ssl_data_300 as ssl_data; + } else { + // OpenSSL 1.1.1+ (fallback) + mod ssl_data_111; + use ssl_data_111 as ssl_data; + } +} + +use crate::vm::{PyRef, VirtualMachine, builtins::PyModule}; +use openssl_probe::ProbeResult; + +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { + // if openssl is vendored, it doesn't know the locations + // of system certificates - cache the probe result now. + #[cfg(openssl_vendored)] + LazyLock::force(&PROBE); + _ssl::make_module(vm) +} + +// define our own copy of ProbeResult so we can handle the vendor case +// easily, without having to have a bunch of cfgs +cfg_if::cfg_if! { + if #[cfg(openssl_vendored)] { + use std::sync::LazyLock; + static PROBE: LazyLock<ProbeResult> = LazyLock::new(openssl_probe::probe); + fn probe() -> &'static ProbeResult { &PROBE } + } else { + fn probe() -> &'static ProbeResult { + &ProbeResult { cert_file: None, cert_dir: None } + } + } +} + +#[allow(non_upper_case_globals)] +#[pymodule(with(cert::ssl_cert, ossl101, ossl111, windows))] +mod _ssl { + use super::{bio, probe}; + use crate::{ + common::lock::{ + PyMappedRwLockReadGuard, PyMutex, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard, + }, + socket::{self, PySocket}, + vm::{ + AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + builtins::{ + PyBaseExceptionRef, PyBytesRef, PyListRef, PyOSError, PyStrRef, PyTypeRef, PyWeak, + }, + class_or_notimplemented, + convert::ToPyException, + exceptions, + function::{ + ArgBytesLike, ArgCallable, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath, + OptionalArg, PyComparisonValue, + }, + types::{Comparable, Constructor, PyComparisonOp}, + utils::ToCString, + }, + }; + use crossbeam_utils::atomic::AtomicCell; + use foreign_types_shared::{ForeignType, ForeignTypeRef}; + use openssl::{ + asn1::{Asn1Object, Asn1ObjectRef}, + error::ErrorStack, + nid::Nid, + ssl::{self, SslContextBuilder, SslOptions, SslVerifyMode}, + x509::X509, + }; + use openssl_sys as sys; + use rustpython_vm::ospath::OsPath; + use std::{ + ffi::CStr, + fmt, + io::{Read, Write}, + path::{Path, PathBuf}, + sync::LazyLock, + time::Instant, + }; + + // Import certificate types from parent module + use super::cert::{self, cert_to_certificate, cert_to_py}; + + // Re-export PySSLCertificate to make it available in the _ssl module + // It will be automatically exposed to Python via #[pyclass] + #[allow(unused_imports)] + use super::cert::PySSLCertificate; + + // Constants + #[pyattr] + use sys::{ + // SSL Alert Descriptions that are exported by openssl_sys + SSL_AD_DECODE_ERROR, + SSL_AD_ILLEGAL_PARAMETER, + SSL_AD_UNRECOGNIZED_NAME, + // SSL_ERROR_INVALID_ERROR_CODE, + SSL_ERROR_SSL, + // SSL_ERROR_WANT_X509_LOOKUP, + SSL_ERROR_SYSCALL, + SSL_ERROR_WANT_CONNECT, + SSL_ERROR_WANT_READ, + SSL_ERROR_WANT_WRITE, + SSL_ERROR_ZERO_RETURN, + SSL_OP_CIPHER_SERVER_PREFERENCE as OP_CIPHER_SERVER_PREFERENCE, + SSL_OP_ENABLE_MIDDLEBOX_COMPAT as OP_ENABLE_MIDDLEBOX_COMPAT, + SSL_OP_LEGACY_SERVER_CONNECT as OP_LEGACY_SERVER_CONNECT, + SSL_OP_NO_SSLv2 as OP_NO_SSLv2, + SSL_OP_NO_SSLv3 as OP_NO_SSLv3, + SSL_OP_NO_TICKET as OP_NO_TICKET, + SSL_OP_NO_TLSv1 as OP_NO_TLSv1, + SSL_OP_SINGLE_DH_USE as OP_SINGLE_DH_USE, + SSL_OP_SINGLE_ECDH_USE as OP_SINGLE_ECDH_USE, + X509_V_FLAG_ALLOW_PROXY_CERTS as VERIFY_ALLOW_PROXY_CERTS, + X509_V_FLAG_CRL_CHECK as VERIFY_CRL_CHECK_LEAF, + X509_V_FLAG_PARTIAL_CHAIN as VERIFY_X509_PARTIAL_CHAIN, + X509_V_FLAG_TRUSTED_FIRST as VERIFY_X509_TRUSTED_FIRST, + X509_V_FLAG_X509_STRICT as VERIFY_X509_STRICT, + }; + + // SSL Alert Descriptions (RFC 5246 and extensions) + // Hybrid approach: use openssl_sys constants where available, hardcode others + #[pyattr] + const ALERT_DESCRIPTION_CLOSE_NOTIFY: libc::c_int = 0; + #[pyattr] + const ALERT_DESCRIPTION_UNEXPECTED_MESSAGE: libc::c_int = 10; + #[pyattr] + const ALERT_DESCRIPTION_BAD_RECORD_MAC: libc::c_int = 20; + #[pyattr] + const ALERT_DESCRIPTION_RECORD_OVERFLOW: libc::c_int = 22; + #[pyattr] + const ALERT_DESCRIPTION_DECOMPRESSION_FAILURE: libc::c_int = 30; + #[pyattr] + const ALERT_DESCRIPTION_HANDSHAKE_FAILURE: libc::c_int = 40; + #[pyattr] + const ALERT_DESCRIPTION_BAD_CERTIFICATE: libc::c_int = 42; + #[pyattr] + const ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE: libc::c_int = 43; + #[pyattr] + const ALERT_DESCRIPTION_CERTIFICATE_REVOKED: libc::c_int = 44; + #[pyattr] + const ALERT_DESCRIPTION_CERTIFICATE_EXPIRED: libc::c_int = 45; + #[pyattr] + const ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN: libc::c_int = 46; + #[pyattr] + const ALERT_DESCRIPTION_ILLEGAL_PARAMETER: libc::c_int = SSL_AD_ILLEGAL_PARAMETER; + #[pyattr] + const ALERT_DESCRIPTION_UNKNOWN_CA: libc::c_int = 48; + #[pyattr] + const ALERT_DESCRIPTION_ACCESS_DENIED: libc::c_int = 49; + #[pyattr] + const ALERT_DESCRIPTION_DECODE_ERROR: libc::c_int = SSL_AD_DECODE_ERROR; + #[pyattr] + const ALERT_DESCRIPTION_DECRYPT_ERROR: libc::c_int = 51; + #[pyattr] + const ALERT_DESCRIPTION_PROTOCOL_VERSION: libc::c_int = 70; + #[pyattr] + const ALERT_DESCRIPTION_INSUFFICIENT_SECURITY: libc::c_int = 71; + #[pyattr] + const ALERT_DESCRIPTION_INTERNAL_ERROR: libc::c_int = 80; + #[pyattr] + const ALERT_DESCRIPTION_USER_CANCELLED: libc::c_int = 90; + #[pyattr] + const ALERT_DESCRIPTION_NO_RENEGOTIATION: libc::c_int = 100; + #[pyattr] + const ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION: libc::c_int = 110; + #[pyattr] + const ALERT_DESCRIPTION_CERTIFICATE_UNOBTAINABLE: libc::c_int = 111; + #[pyattr] + const ALERT_DESCRIPTION_UNRECOGNIZED_NAME: libc::c_int = SSL_AD_UNRECOGNIZED_NAME; + #[pyattr] + const ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE: libc::c_int = 113; + #[pyattr] + const ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE: libc::c_int = 114; + #[pyattr] + const ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY: libc::c_int = 115; + + // CRL verification constants + #[pyattr] + const VERIFY_CRL_CHECK_CHAIN: libc::c_ulong = + sys::X509_V_FLAG_CRL_CHECK | sys::X509_V_FLAG_CRL_CHECK_ALL; + + // taken from CPython, should probably be kept up to date with their version if it ever changes + #[pyattr] + const _DEFAULT_CIPHERS: &str = + "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK"; + // #[pyattr] PROTOCOL_SSLv2: u32 = SslVersion::Ssl2 as u32; // unsupported + // #[pyattr] PROTOCOL_SSLv3: u32 = SslVersion::Ssl3 as u32; + #[pyattr] + const PROTOCOL_SSLv23: u32 = SslVersion::Tls as u32; + #[pyattr] + const PROTOCOL_TLS: u32 = SslVersion::Tls as u32; + #[pyattr] + const PROTOCOL_TLS_CLIENT: u32 = SslVersion::TlsClient as u32; + #[pyattr] + const PROTOCOL_TLS_SERVER: u32 = SslVersion::TlsServer as u32; + #[pyattr] + const PROTOCOL_TLSv1: u32 = SslVersion::Tls1 as u32; + #[pyattr] + const PROTOCOL_TLSv1_1: u32 = SslVersion::Tls1_1 as u32; + #[pyattr] + const PROTOCOL_TLSv1_2: u32 = SslVersion::Tls1_2 as u32; + #[pyattr] + const PROTO_MINIMUM_SUPPORTED: i32 = ProtoVersion::MinSupported as i32; + #[pyattr] + const PROTO_SSLv3: i32 = ProtoVersion::Ssl3 as i32; + #[pyattr] + const PROTO_TLSv1: i32 = ProtoVersion::Tls1 as i32; + #[pyattr] + const PROTO_TLSv1_1: i32 = ProtoVersion::Tls1_1 as i32; + #[pyattr] + const PROTO_TLSv1_2: i32 = ProtoVersion::Tls1_2 as i32; + #[pyattr] + const PROTO_TLSv1_3: i32 = ProtoVersion::Tls1_3 as i32; + #[pyattr] + const PROTO_MAXIMUM_SUPPORTED: i32 = ProtoVersion::MaxSupported as i32; + #[pyattr] + const OP_ALL: libc::c_ulong = (sys::SSL_OP_ALL & !sys::SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) as _; + #[pyattr] + const HAS_TLS_UNIQUE: bool = true; + #[pyattr] + const CERT_NONE: u32 = CertRequirements::None as u32; + #[pyattr] + const CERT_OPTIONAL: u32 = CertRequirements::Optional as u32; + #[pyattr] + const CERT_REQUIRED: u32 = CertRequirements::Required as u32; + #[pyattr] + const VERIFY_DEFAULT: u32 = 0; + #[pyattr] + const SSL_ERROR_EOF: u32 = 8; // custom for python + #[pyattr] + const HAS_SNI: bool = true; + #[pyattr] + const HAS_ECDH: bool = true; + #[pyattr] + const HAS_NPN: bool = false; + #[pyattr] + const HAS_ALPN: bool = true; + #[pyattr] + const HAS_SSLv2: bool = false; + #[pyattr] + const HAS_SSLv3: bool = false; + #[pyattr] + const HAS_TLSv1: bool = true; + #[pyattr] + const HAS_TLSv1_1: bool = true; + #[pyattr] + const HAS_TLSv1_2: bool = true; + #[pyattr] + const HAS_TLSv1_3: bool = cfg!(ossl111); + #[pyattr] + const HAS_PSK: bool = true; + + // Encoding constants for Certificate.public_bytes() + #[pyattr] + pub(crate) const ENCODING_PEM: i32 = sys::X509_FILETYPE_PEM; + #[pyattr] + pub(crate) const ENCODING_DER: i32 = sys::X509_FILETYPE_ASN1; + #[pyattr] + const ENCODING_PEM_AUX: i32 = sys::X509_FILETYPE_PEM + 0x100; + + // OpenSSL error codes for unexpected EOF detection + const ERR_LIB_SSL: i32 = 20; + const SSL_R_UNEXPECTED_EOF_WHILE_READING: i32 = 294; + + // SSL_VERIFY constants for post-handshake authentication + #[cfg(ossl111)] + const SSL_VERIFY_POST_HANDSHAKE: libc::c_int = 0x20; + + // the openssl version from the API headers + + #[pyattr(name = "OPENSSL_VERSION")] + fn openssl_version(_vm: &VirtualMachine) -> &str { + openssl::version::version() + } + #[pyattr(name = "OPENSSL_VERSION_NUMBER")] + fn openssl_version_number(_vm: &VirtualMachine) -> i64 { + openssl::version::number() + } + #[pyattr(name = "OPENSSL_VERSION_INFO")] + fn openssl_version_info(_vm: &VirtualMachine) -> OpensslVersionInfo { + parse_version_info(openssl::version::number()) + } + + #[pyattr(name = "_OPENSSL_API_VERSION")] + fn _openssl_api_version(_vm: &VirtualMachine) -> OpensslVersionInfo { + let openssl_api_version = i64::from_str_radix(env!("OPENSSL_API_VERSION"), 16) + .expect("OPENSSL_API_VERSION is malformed"); + parse_version_info(openssl_api_version) + } + + // SSL Exception Types + + /// An error occurred in the SSL implementation. + #[pyattr] + #[pyexception(name = "SSLError", base = PyOSError)] + #[derive(Debug)] + pub struct PySslError {} + + #[pyexception] + impl PySslError { + // Returns strerror attribute if available, otherwise str(args) + #[pymethod] + fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { + // Try to get strerror attribute first (OSError compatibility) + if let Ok(strerror) = exc.as_object().get_attr("strerror", vm) + && !vm.is_none(&strerror) + { + return strerror.str(vm); + } + + // Otherwise return str(args) + exc.args().as_object().str(vm) + } + } + + /// A certificate could not be verified. + #[pyattr] + #[pyexception(name = "SSLCertVerificationError", base = PySslError)] + #[derive(Debug)] + pub struct PySslCertVerificationError {} + + #[pyexception] + impl PySslCertVerificationError {} + + /// SSL/TLS session closed cleanly. + #[pyattr] + #[pyexception(name = "SSLZeroReturnError", base = PySslError)] + #[derive(Debug)] + pub struct PySslZeroReturnError {} + + #[pyexception] + impl PySslZeroReturnError {} + + /// Non-blocking SSL socket needs to read more data. + #[pyattr] + #[pyexception(name = "SSLWantReadError", base = PySslError)] + #[derive(Debug)] + pub struct PySslWantReadError {} + + #[pyexception] + impl PySslWantReadError {} + + /// Non-blocking SSL socket needs to write more data. + #[pyattr] + #[pyexception(name = "SSLWantWriteError", base = PySslError)] + #[derive(Debug)] + pub struct PySslWantWriteError {} + + #[pyexception] + impl PySslWantWriteError {} + + /// System error when attempting SSL operation. + #[pyattr] + #[pyexception(name = "SSLSyscallError", base = PySslError)] + #[derive(Debug)] + pub struct PySslSyscallError {} + + #[pyexception] + impl PySslSyscallError {} + + /// SSL/TLS connection terminated abruptly. + #[pyattr] + #[pyexception(name = "SSLEOFError", base = PySslError)] + #[derive(Debug)] + pub struct PySslEOFError {} + + #[pyexception] + impl PySslEOFError {} + + type OpensslVersionInfo = (u8, u8, u8, u8, u8); + const fn parse_version_info(mut n: i64) -> OpensslVersionInfo { + let status = (n & 0xF) as u8; + n >>= 4; + let patch = (n & 0xFF) as u8; + n >>= 8; + let fix = (n & 0xFF) as u8; + n >>= 8; + let minor = (n & 0xFF) as u8; + n >>= 8; + let major = (n & 0xFF) as u8; + (major, minor, fix, patch, status) + } + + #[derive(Copy, Clone, num_enum::IntoPrimitive, num_enum::TryFromPrimitive, PartialEq)] + #[repr(i32)] + enum SslVersion { + Ssl2, + Ssl3 = 1, + Tls, + Tls1, + Tls1_1, + Tls1_2, + TlsClient = 0x10, + TlsServer, + } + + #[derive(Copy, Clone, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] + #[repr(i32)] + enum ProtoVersion { + MinSupported = -2, + Ssl3 = sys::SSL3_VERSION, + Tls1 = sys::TLS1_VERSION, + Tls1_1 = sys::TLS1_1_VERSION, + Tls1_2 = sys::TLS1_2_VERSION, + #[cfg(ossl111)] + Tls1_3 = sys::TLS1_3_VERSION, + #[cfg(not(ossl111))] + Tls1_3 = 0x304, + MaxSupported = -1, + } + + #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] + #[repr(i32)] + enum CertRequirements { + None, + Optional, + Required, + } + + #[derive(Debug, PartialEq)] + enum SslServerOrClient { + Client, + Server, + } + + unsafe fn ptr2obj(ptr: *mut sys::ASN1_OBJECT) -> Option<Asn1Object> { + if ptr.is_null() { + None + } else { + Some(unsafe { Asn1Object::from_ptr(ptr) }) + } + } + + fn _txt2obj(s: &CStr, no_name: bool) -> Option<Asn1Object> { + unsafe { ptr2obj(sys::OBJ_txt2obj(s.as_ptr(), i32::from(no_name))) } + } + fn _nid2obj(nid: Nid) -> Option<Asn1Object> { + unsafe { ptr2obj(sys::OBJ_nid2obj(nid.as_raw())) } + } + + type PyNid = (libc::c_int, String, String, Option<String>); + fn obj2py(obj: &Asn1ObjectRef, vm: &VirtualMachine) -> PyResult<PyNid> { + let nid = obj.nid(); + let short_name = nid + .short_name() + .map_err(|_| vm.new_value_error("NID has no short name".to_owned()))? + .to_owned(); + let long_name = nid + .long_name() + .map_err(|_| vm.new_value_error("NID has no long name".to_owned()))? + .to_owned(); + Ok(( + nid.as_raw(), + short_name, + long_name, + cert::obj2txt(obj, true), + )) + } + + #[derive(FromArgs)] + struct Txt2ObjArgs { + txt: PyStrRef, + #[pyarg(any, default = false)] + name: bool, + } + + #[pyfunction] + fn txt2obj(args: Txt2ObjArgs, vm: &VirtualMachine) -> PyResult<PyNid> { + _txt2obj(&args.txt.to_cstring(vm)?, !args.name) + .as_deref() + .ok_or_else(|| vm.new_value_error(format!("unknown object '{}'", args.txt))) + .and_then(|obj| obj2py(obj, vm)) + } + + #[pyfunction] + fn nid2obj(nid: libc::c_int, vm: &VirtualMachine) -> PyResult<PyNid> { + _nid2obj(Nid::from_raw(nid)) + .as_deref() + .ok_or_else(|| vm.new_value_error(format!("unknown NID {nid}"))) + .and_then(|obj| obj2py(obj, vm)) + } + + // Lazily compute and cache cert file/dir paths + static CERT_PATHS: LazyLock<(PathBuf, PathBuf)> = LazyLock::new(|| { + fn path_from_cstr(c: &CStr) -> PathBuf { + #[cfg(unix)] + { + use std::os::unix::ffi::OsStrExt; + std::ffi::OsStr::from_bytes(c.to_bytes()).into() + } + #[cfg(windows)] + { + // Use lossy conversion for potential non-UTF8 + PathBuf::from(c.to_string_lossy().as_ref()) + } + } + + let probe = probe(); + let cert_file = probe + .cert_file + .as_ref() + .map(PathBuf::from) + .unwrap_or_else(|| { + path_from_cstr(unsafe { CStr::from_ptr(sys::X509_get_default_cert_file()) }) + }); + let cert_dir = probe + .cert_dir + .as_ref() + .map(PathBuf::from) + .unwrap_or_else(|| { + path_from_cstr(unsafe { CStr::from_ptr(sys::X509_get_default_cert_dir()) }) + }); + (cert_file, cert_dir) + }); + + fn get_cert_file_dir() -> (&'static Path, &'static Path) { + let (cert_file, cert_dir) = &*CERT_PATHS; + (cert_file.as_path(), cert_dir.as_path()) + } + + // Lazily compute and cache cert environment variable names + static CERT_ENV_NAMES: LazyLock<(String, String)> = LazyLock::new(|| { + let cert_file_env = unsafe { CStr::from_ptr(sys::X509_get_default_cert_file_env()) } + .to_string_lossy() + .into_owned(); + let cert_dir_env = unsafe { CStr::from_ptr(sys::X509_get_default_cert_dir_env()) } + .to_string_lossy() + .into_owned(); + (cert_file_env, cert_dir_env) + }); + + #[pyfunction] + fn get_default_verify_paths( + vm: &VirtualMachine, + ) -> PyResult<(&'static str, PyObjectRef, &'static str, PyObjectRef)> { + let (cert_file_env, cert_dir_env) = &*CERT_ENV_NAMES; + let (cert_file, cert_dir) = get_cert_file_dir(); + let cert_file = OsPath::new_str(cert_file).filename(vm); + let cert_dir = OsPath::new_str(cert_dir).filename(vm); + Ok(( + cert_file_env.as_str(), + cert_file, + cert_dir_env.as_str(), + cert_dir, + )) + } + + #[pyfunction(name = "RAND_status")] + fn rand_status() -> i32 { + unsafe { sys::RAND_status() } + } + + #[pyfunction(name = "RAND_add")] + fn rand_add(string: ArgStrOrBytesLike, entropy: f64) { + let f = |b: &[u8]| { + for buf in b.chunks(libc::c_int::MAX as usize) { + unsafe { sys::RAND_add(buf.as_ptr() as *const _, buf.len() as _, entropy) } + } + }; + f(&string.borrow_bytes()) + } + + #[pyfunction(name = "RAND_bytes")] + fn rand_bytes(n: i32, vm: &VirtualMachine) -> PyResult<Vec<u8>> { + if n < 0 { + return Err(vm.new_value_error("num must be positive")); + } + let mut buf = vec![0; n as usize]; + openssl::rand::rand_bytes(&mut buf).map_err(|e| convert_openssl_error(vm, e))?; + Ok(buf) + } + + // Callback data stored in SSL context for SNI + struct SniCallbackData { + ssl_context: PyRef<PySslContext>, + vm_ptr: *const VirtualMachine, + } + + impl Drop for SniCallbackData { + fn drop(&mut self) { + // PyRef will handle reference counting + } + } + + // Get or create an ex_data index for SNI callback data + fn get_sni_ex_data_index() -> libc::c_int { + use std::sync::LazyLock; + static SNI_EX_DATA_IDX: LazyLock<libc::c_int> = LazyLock::new(|| unsafe { + sys::SSL_get_ex_new_index( + 0, + std::ptr::null_mut(), + None, + None, + Some(sni_callback_data_free), + ) + }); + *SNI_EX_DATA_IDX + } + + // Free function for callback data + unsafe extern "C" fn sni_callback_data_free( + _parent: *mut libc::c_void, + ptr: *mut libc::c_void, + _ad: *mut sys::CRYPTO_EX_DATA, + _idx: libc::c_int, + _argl: libc::c_long, + _argp: *mut libc::c_void, + ) { + if !ptr.is_null() { + unsafe { + let _ = Box::from_raw(ptr as *mut SniCallbackData); + } + } + } + + // SNI callback function called by OpenSSL + unsafe extern "C" fn _servername_callback( + ssl_ptr: *mut sys::SSL, + al: *mut libc::c_int, + arg: *mut libc::c_void, + ) -> libc::c_int { + const SSL_TLSEXT_ERR_OK: libc::c_int = 0; + const SSL_TLSEXT_ERR_ALERT_FATAL: libc::c_int = 2; + const SSL_AD_INTERNAL_ERROR: libc::c_int = 80; + const TLSEXT_NAMETYPE_host_name: libc::c_int = 0; + + if arg.is_null() { + return SSL_TLSEXT_ERR_OK; + } + + unsafe { + let ctx = &*(arg as *const PySslContext); + + // Get the callback + let callback_opt = ctx.sni_callback.lock().clone(); + let Some(callback) = callback_opt else { + return SSL_TLSEXT_ERR_OK; + }; + + // Get callback data from SSL ex_data + let idx = get_sni_ex_data_index(); + let data_ptr = sys::SSL_get_ex_data(ssl_ptr, idx); + if data_ptr.is_null() { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + let callback_data = &*(data_ptr as *const SniCallbackData); + + // SAFETY: vm_ptr is stored during wrap_socket and is valid for the lifetime + // of the SSL connection. The handshake happens synchronously in the same thread. + let vm = &*callback_data.vm_ptr; + + // Get server name + let servername = sys::SSL_get_servername(ssl_ptr, TLSEXT_NAMETYPE_host_name); + let server_name_arg = if servername.is_null() { + vm.ctx.none() + } else { + let name_cstr = std::ffi::CStr::from_ptr(servername); + match name_cstr.to_str() { + Ok(name_str) => vm.ctx.new_str(name_str).into(), + Err(_) => vm.ctx.none(), + } + }; + + // Get SSL socket from SSL ex_data (stored as PySslSocket pointer) + let ssl_socket_ptr = sys::SSL_get_ex_data(ssl_ptr, 0); // Index 0 for SSL socket + let ssl_socket_obj = if !ssl_socket_ptr.is_null() { + let ssl_socket = &*(ssl_socket_ptr as *const PySslSocket); + // Try to get owner first + ssl_socket + .owner + .read() + .as_ref() + .and_then(|weak| weak.upgrade()) + .unwrap_or_else(|| vm.ctx.none()) + } else { + vm.ctx.none() + }; + + // Call the Python callback + match callback.call( + ( + ssl_socket_obj, + server_name_arg, + callback_data.ssl_context.to_owned(), + ), + vm, + ) { + Ok(result) => { + // Check return value type (must be None or integer) + if vm.is_none(&result) { + // None is OK + SSL_TLSEXT_ERR_OK + } else { + // Try to convert to integer + match result.try_to_value::<i32>(vm) { + Ok(alert_code) => { + // Valid integer - use as alert code + *al = alert_code; + SSL_TLSEXT_ERR_ALERT_FATAL + } + Err(_) => { + // Type conversion failed - raise TypeError + let type_error = vm.new_type_error(format!( + "servername callback must return None or an integer, not '{}'", + result.class().name() + )); + vm.run_unraisable(type_error, None, result); + *al = SSL_AD_INTERNAL_ERROR; + SSL_TLSEXT_ERR_ALERT_FATAL + } + } + } + } + Err(exc) => { + // Log the exception but don't propagate it + vm.run_unraisable(exc, None, vm.ctx.none()); + *al = SSL_AD_INTERNAL_ERROR; + SSL_TLSEXT_ERR_ALERT_FATAL + } + } + } + } + + // Message callback function called by OpenSSL + // Based on CPython's _PySSL_msg_callback in Modules/_ssl/debughelpers.c + unsafe extern "C" fn _msg_callback( + write_p: libc::c_int, + version: libc::c_int, + content_type: libc::c_int, + buf: *const libc::c_void, + len: usize, + ssl_ptr: *mut sys::SSL, + _arg: *mut libc::c_void, + ) { + if ssl_ptr.is_null() { + return; + } + + unsafe { + // Get SSL socket from SSL_get_app_data (index 0) + let ssl_socket_ptr = sys::SSL_get_ex_data(ssl_ptr, 0); + if ssl_socket_ptr.is_null() { + return; + } + + let ssl_socket = &*(ssl_socket_ptr as *const PySslSocket); + + // Get the callback from the context + let callback_opt = ssl_socket.ctx.read().msg_callback.lock().clone(); + let Some(callback) = callback_opt else { + return; + }; + + // Get callback data from SSL ex_data (for VM) + let idx = get_sni_ex_data_index(); + let data_ptr = sys::SSL_get_ex_data(ssl_ptr, idx); + if data_ptr.is_null() { + return; + } + + let callback_data = &*(data_ptr as *const SniCallbackData); + let vm = &*callback_data.vm_ptr; + + // Get SSL socket owner object + let ssl_socket_obj = ssl_socket + .owner + .read() + .as_ref() + .and_then(|weak| weak.upgrade()) + .unwrap_or_else(|| vm.ctx.none()); + + // Create the message bytes + let buf_slice = std::slice::from_raw_parts(buf as *const u8, len); + let msg_bytes = vm.ctx.new_bytes(buf_slice.to_vec()); + + // Determine direction string + let direction_str = if write_p != 0 { "write" } else { "read" }; + + // Call the Python callback + // Signature: callback(conn, direction, version, content_type, msg_type, data) + // For simplicity, we'll pass msg_type as 0 (would need more parsing to get the actual type) + match callback.call( + ( + ssl_socket_obj, + vm.ctx.new_str(direction_str), + vm.ctx.new_int(version), + vm.ctx.new_int(content_type), + vm.ctx.new_int(0), // msg_type - would need parsing + msg_bytes, + ), + vm, + ) { + Ok(_) => {} + Err(exc) => { + // Log the exception but don't propagate it + vm.run_unraisable(exc, None, vm.ctx.none()); + } + } + } + } + + #[pyfunction(name = "RAND_pseudo_bytes")] + fn rand_pseudo_bytes(n: i32, vm: &VirtualMachine) -> PyResult<(Vec<u8>, bool)> { + if n < 0 { + return Err(vm.new_value_error("num must be positive")); + } + let mut buf = vec![0; n as usize]; + let ret = unsafe { sys::RAND_bytes(buf.as_mut_ptr(), n) }; + match ret { + 0 | 1 => Ok((buf, ret == 1)), + _ => Err(convert_openssl_error(vm, ErrorStack::get())), + } + } + + #[pyattr] + #[pyclass(module = "ssl", name = "_SSLContext")] + #[derive(PyPayload)] + struct PySslContext { + ctx: PyRwLock<SslContextBuilder>, + check_hostname: AtomicCell<bool>, + protocol: SslVersion, + post_handshake_auth: PyMutex<bool>, + sni_callback: PyMutex<Option<PyObjectRef>>, + msg_callback: PyMutex<Option<PyObjectRef>>, + } + + impl fmt::Debug for PySslContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("_SSLContext") + } + } + + fn builder_as_ctx(x: &SslContextBuilder) -> &ssl::SslContextRef { + unsafe { ssl::SslContextRef::from_ptr(x.as_ptr()) } + } + + impl Constructor for PySslContext { + type Args = i32; + + fn py_new(cls: PyTypeRef, proto_version: Self::Args, vm: &VirtualMachine) -> PyResult { + let proto = SslVersion::try_from(proto_version) + .map_err(|_| vm.new_value_error("invalid protocol version"))?; + let method = match proto { + // SslVersion::Ssl3 => unsafe { ssl::SslMethod::from_ptr(sys::SSLv3_method()) }, + SslVersion::Tls => ssl::SslMethod::tls(), + SslVersion::Tls1 => ssl::SslMethod::tls(), + SslVersion::Tls1_1 => ssl::SslMethod::tls(), + SslVersion::Tls1_2 => ssl::SslMethod::tls(), + SslVersion::TlsClient => ssl::SslMethod::tls_client(), + SslVersion::TlsServer => ssl::SslMethod::tls_server(), + _ => return Err(vm.new_value_error("invalid protocol version")), + }; + let mut builder = + SslContextBuilder::new(method).map_err(|e| convert_openssl_error(vm, e))?; + + #[cfg(target_os = "android")] + android::load_client_ca_list(vm, &mut builder)?; + + let check_hostname = proto == SslVersion::TlsClient; + builder.set_verify(if check_hostname { + SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT + } else { + SslVerifyMode::NONE + }); + + let mut options = SslOptions::ALL & !SslOptions::DONT_INSERT_EMPTY_FRAGMENTS; + if proto != SslVersion::Ssl2 { + options |= SslOptions::NO_SSLV2; + } + if proto != SslVersion::Ssl3 { + options |= SslOptions::NO_SSLV3; + } + options |= SslOptions::NO_COMPRESSION; + options |= SslOptions::CIPHER_SERVER_PREFERENCE; + options |= SslOptions::SINGLE_DH_USE; + options |= SslOptions::SINGLE_ECDH_USE; + options |= SslOptions::ENABLE_MIDDLEBOX_COMPAT; + builder.set_options(options); + + let mode = ssl::SslMode::ACCEPT_MOVING_WRITE_BUFFER | ssl::SslMode::AUTO_RETRY; + builder.set_mode(mode); + + #[cfg(ossl111)] + unsafe { + sys::SSL_CTX_set_post_handshake_auth(builder.as_ptr(), 0); + } + + // Note: Unlike some other implementations, we do NOT set session_id_context at the + // context level. CPython sets it only on individual SSL objects (server-side only). + // This matches CPython's behavior in _ssl.c where SSL_set_session_id_context is called + // in newPySSLSocket() at line 862, not during context creation. + + // Set protocol version limits based on the protocol version + unsafe { + let ctx_ptr = builder.as_ptr(); + match proto { + SslVersion::Tls1 => { + sys::SSL_CTX_set_min_proto_version(ctx_ptr, sys::TLS1_VERSION); + sys::SSL_CTX_set_max_proto_version(ctx_ptr, sys::TLS1_VERSION); + } + SslVersion::Tls1_1 => { + sys::SSL_CTX_set_min_proto_version(ctx_ptr, sys::TLS1_1_VERSION); + sys::SSL_CTX_set_max_proto_version(ctx_ptr, sys::TLS1_1_VERSION); + } + SslVersion::Tls1_2 => { + sys::SSL_CTX_set_min_proto_version(ctx_ptr, sys::TLS1_2_VERSION); + sys::SSL_CTX_set_max_proto_version(ctx_ptr, sys::TLS1_2_VERSION); + } + _ => { + // For Tls, TlsClient, TlsServer, use default (no restrictions) + } + } + } + + // Set default verify flags: VERIFY_X509_TRUSTED_FIRST + unsafe { + let ctx_ptr = builder.as_ptr(); + let param = sys::SSL_CTX_get0_param(ctx_ptr); + sys::X509_VERIFY_PARAM_set_flags(param, sys::X509_V_FLAG_TRUSTED_FIRST); + } + + PySslContext { + ctx: PyRwLock::new(builder), + check_hostname: AtomicCell::new(check_hostname), + protocol: proto, + post_handshake_auth: PyMutex::new(false), + sni_callback: PyMutex::new(None), + msg_callback: PyMutex::new(None), + } + .into_ref_with_type(vm, cls) + .map(Into::into) + } + } + + #[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] + impl PySslContext { + fn builder(&self) -> PyRwLockWriteGuard<'_, SslContextBuilder> { + self.ctx.write() + } + fn ctx(&self) -> PyMappedRwLockReadGuard<'_, ssl::SslContextRef> { + PyRwLockReadGuard::map(self.ctx.read(), builder_as_ctx) + } + + #[pygetset] + fn post_handshake_auth(&self) -> bool { + *self.post_handshake_auth.lock() + } + #[pygetset(setter)] + fn set_post_handshake_auth( + &self, + value: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + let value = value.ok_or_else(|| vm.new_attribute_error("cannot delete attribute"))?; + *self.post_handshake_auth.lock() = value.is_true(vm)?; + Ok(()) + } + + #[cfg(ossl110)] + #[pygetset] + fn security_level(&self) -> i32 { + unsafe { SSL_CTX_get_security_level(self.ctx().as_ptr()) } + } + + #[pymethod] + fn set_ciphers(&self, cipherlist: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { + let ciphers = cipherlist.as_str(); + if ciphers.contains('\0') { + return Err(exceptions::cstring_error(vm)); + } + self.builder().set_cipher_list(ciphers).map_err(|_| { + vm.new_exception_msg( + PySslError::class(&vm.ctx).to_owned(), + "No cipher can be selected.".to_owned(), + ) + }) + } + + #[pymethod] + fn get_ciphers(&self, vm: &VirtualMachine) -> PyResult<PyListRef> { + let ctx = self.ctx(); + let ssl = ssl::Ssl::new(&ctx).map_err(|e| convert_openssl_error(vm, e))?; + + unsafe { + let ciphers_ptr = SSL_get_ciphers(ssl.as_ptr()); + if ciphers_ptr.is_null() { + return Ok(vm.ctx.new_list(vec![])); + } + + let num_ciphers = sys::OPENSSL_sk_num(ciphers_ptr as *const _); + let mut result = Vec::new(); + + for i in 0..num_ciphers { + let cipher_ptr = + sys::OPENSSL_sk_value(ciphers_ptr as *const _, i) as *const sys::SSL_CIPHER; + let cipher = ssl::SslCipherRef::from_ptr(cipher_ptr as *mut _); + + let (name, version, bits) = cipher_to_tuple(cipher); + let dict = vm.ctx.new_dict(); + dict.set_item("name", vm.ctx.new_str(name).into(), vm)?; + dict.set_item("protocol", vm.ctx.new_str(version).into(), vm)?; + dict.set_item("secret_bits", vm.ctx.new_int(bits).into(), vm)?; + + // Add description field + let description = cipher_description(cipher_ptr); + dict.set_item("description", vm.ctx.new_str(description).into(), vm)?; + + result.push(dict.into()); + } + + Ok(vm.ctx.new_list(result)) + } + } + + #[pymethod] + fn set_ecdh_curve( + &self, + name: Either<PyStrRef, ArgBytesLike>, + vm: &VirtualMachine, + ) -> PyResult<()> { + use openssl::ec::{EcGroup, EcKey}; + + // Convert name to CString, supporting both str and bytes + let name_cstr = match name { + Either::A(s) => { + if s.as_str().contains('\0') { + return Err(exceptions::cstring_error(vm)); + } + s.to_cstring(vm)? + } + Either::B(b) => std::ffi::CString::new(b.borrow_buf().to_vec()) + .map_err(|_| exceptions::cstring_error(vm))?, + }; + + // Find the NID for the curve name using OBJ_sn2nid + let nid_raw = unsafe { sys::OBJ_sn2nid(name_cstr.as_ptr()) }; + if nid_raw == 0 { + return Err(vm.new_value_error("unknown curve name")); + } + let nid = Nid::from_raw(nid_raw); + + // Create EC key from the curve + let group = EcGroup::from_curve_name(nid).map_err(|e| convert_openssl_error(vm, e))?; + let key = EcKey::from_group(&group).map_err(|e| convert_openssl_error(vm, e))?; + + // Set the temporary ECDH key + self.builder() + .set_tmp_ecdh(&key) + .map_err(|e| convert_openssl_error(vm, e)) + } + + #[pygetset] + fn options(&self) -> libc::c_ulong { + self.ctx.read().options().bits() as _ + } + #[pygetset(setter)] + fn set_options(&self, opts: libc::c_ulong) { + self.builder() + .set_options(SslOptions::from_bits_truncate(opts as _)); + } + #[pygetset] + fn protocol(&self) -> i32 { + self.protocol as i32 + } + #[pygetset] + fn verify_mode(&self) -> i32 { + let mode = self.ctx().verify_mode(); + if mode == SslVerifyMode::NONE { + CertRequirements::None.into() + } else if mode == SslVerifyMode::PEER { + CertRequirements::Optional.into() + } else if mode == SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT { + CertRequirements::Required.into() + } else { + unreachable!() + } + } + #[pygetset(setter)] + fn set_verify_mode(&self, cert: i32, vm: &VirtualMachine) -> PyResult<()> { + let mut ctx = self.builder(); + let cert_req = CertRequirements::try_from(cert) + .map_err(|_| vm.new_value_error("invalid value for verify_mode"))?; + let mode = match cert_req { + CertRequirements::None if self.check_hostname.load() => { + return Err(vm.new_value_error( + "Cannot set verify_mode to CERT_NONE when check_hostname is enabled.", + )); + } + CertRequirements::None => SslVerifyMode::NONE, + CertRequirements::Optional => SslVerifyMode::PEER, + CertRequirements::Required => { + SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT + } + }; + ctx.set_verify(mode); + Ok(()) + } + #[pygetset] + fn verify_flags(&self) -> libc::c_ulong { + unsafe { + let ctx_ptr = self.ctx().as_ptr(); + let param = sys::SSL_CTX_get0_param(ctx_ptr); + sys::X509_VERIFY_PARAM_get_flags(param) + } + } + #[pygetset(setter)] + fn set_verify_flags(&self, new_flags: libc::c_ulong, vm: &VirtualMachine) -> PyResult<()> { + unsafe { + let ctx_ptr = self.ctx().as_ptr(); + let param = sys::SSL_CTX_get0_param(ctx_ptr); + let flags = sys::X509_VERIFY_PARAM_get_flags(param); + let clear = flags & !new_flags; + let set = !flags & new_flags; + + if clear != 0 && sys::X509_VERIFY_PARAM_clear_flags(param, clear) == 0 { + return Err(vm.new_exception_msg( + PySslError::class(&vm.ctx).to_owned(), + "Failed to clear verify flags".to_owned(), + )); + } + if set != 0 && sys::X509_VERIFY_PARAM_set_flags(param, set) == 0 { + return Err(vm.new_exception_msg( + PySslError::class(&vm.ctx).to_owned(), + "Failed to set verify flags".to_owned(), + )); + } + Ok(()) + } + } + #[pygetset] + fn check_hostname(&self) -> bool { + self.check_hostname.load() + } + #[pygetset(setter)] + fn set_check_hostname(&self, ch: bool) { + let mut ctx = self.builder(); + if ch && builder_as_ctx(&ctx).verify_mode() == SslVerifyMode::NONE { + ctx.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT); + } + self.check_hostname.store(ch); + } + + // PY_PROTO_MINIMUM_SUPPORTED = -2, PY_PROTO_MAXIMUM_SUPPORTED = -1 + #[pygetset] + fn minimum_version(&self) -> i32 { + let ctx = self.ctx(); + let version = unsafe { sys::SSL_CTX_get_min_proto_version(ctx.as_ptr()) }; + if version == 0 { + -2 // PY_PROTO_MINIMUM_SUPPORTED + } else { + version + } + } + #[pygetset(setter)] + fn set_minimum_version(&self, value: i32, vm: &VirtualMachine) -> PyResult<()> { + // Handle special values + let proto_version = match value { + -2 => { + // PY_PROTO_MINIMUM_SUPPORTED -> use minimum available (TLS 1.2) + sys::TLS1_2_VERSION + } + -1 => { + // PY_PROTO_MAXIMUM_SUPPORTED -> use maximum available + // For max on min_proto_version, we use the newest available + sys::TLS1_3_VERSION + } + _ => value, + }; + + let ctx = self.builder(); + let result = unsafe { sys::SSL_CTX_set_min_proto_version(ctx.as_ptr(), proto_version) }; + if result == 0 { + return Err(vm.new_value_error("invalid protocol version")); + } + Ok(()) + } + + #[pygetset] + fn maximum_version(&self) -> i32 { + let ctx = self.ctx(); + let version = unsafe { sys::SSL_CTX_get_max_proto_version(ctx.as_ptr()) }; + if version == 0 { + -1 // PY_PROTO_MAXIMUM_SUPPORTED + } else { + version + } + } + #[pygetset(setter)] + fn set_maximum_version(&self, value: i32, vm: &VirtualMachine) -> PyResult<()> { + // Handle special values + let proto_version = match value { + -1 => { + // PY_PROTO_MAXIMUM_SUPPORTED -> use 0 for OpenSSL (means no limit) + 0 + } + -2 => { + // PY_PROTO_MINIMUM_SUPPORTED -> use minimum available (TLS 1.2) + sys::TLS1_2_VERSION + } + _ => value, + }; + + let ctx = self.builder(); + let result = unsafe { sys::SSL_CTX_set_max_proto_version(ctx.as_ptr(), proto_version) }; + if result == 0 { + return Err(vm.new_value_error("invalid protocol version")); + } + Ok(()) + } + + #[pygetset] + fn num_tickets(&self, _vm: &VirtualMachine) -> PyResult<usize> { + // Only supported for TLS 1.3 + #[cfg(ossl110)] + { + let ctx = self.ctx(); + let num = unsafe { sys::SSL_CTX_get_num_tickets(ctx.as_ptr()) }; + Ok(num) + } + #[cfg(not(ossl110))] + { + Ok(0) + } + } + #[pygetset(setter)] + fn set_num_tickets(&self, value: isize, vm: &VirtualMachine) -> PyResult<()> { + // Check for negative values + if value < 0 { + return Err( + vm.new_value_error("num_tickets must be a non-negative integer".to_owned()) + ); + } + + // Check that this is a server context + if self.protocol != SslVersion::TlsServer { + return Err(vm.new_value_error("SSLContext is not a server context.".to_owned())); + } + + #[cfg(ossl110)] + { + let ctx = self.builder(); + let result = unsafe { sys::SSL_CTX_set_num_tickets(ctx.as_ptr(), value as usize) }; + if result != 1 { + return Err(vm.new_value_error("failed to set num tickets.")); + } + Ok(()) + } + #[cfg(not(ossl110))] + { + let _ = (value, vm); + Ok(()) + } + } + + #[pymethod] + fn set_default_verify_paths(&self, vm: &VirtualMachine) -> PyResult<()> { + cfg_if::cfg_if! { + if #[cfg(openssl_vendored)] { + let (cert_file, cert_dir) = get_cert_file_dir(); + self.builder() + .load_verify_locations(Some(cert_file), Some(cert_dir)) + .map_err(|e| convert_openssl_error(vm, e)) + } else { + self.builder() + .set_default_verify_paths() + .map_err(|e| convert_openssl_error(vm, e)) + } + } + } + + #[pymethod] + fn _set_alpn_protocols(&self, protos: ArgBytesLike, vm: &VirtualMachine) -> PyResult<()> { + #[cfg(ossl102)] + { + let mut ctx = self.builder(); + let server = protos.with_ref(|pbuf| { + if pbuf.len() > libc::c_uint::MAX as usize { + return Err(vm.new_overflow_error(format!( + "protocols longer than {} bytes", + libc::c_uint::MAX + ))); + } + ctx.set_alpn_protos(pbuf) + .map_err(|e| convert_openssl_error(vm, e))?; + Ok(pbuf.to_vec()) + })?; + ctx.set_alpn_select_callback(move |_, client| { + let proto = + ssl::select_next_proto(&server, client).ok_or(ssl::AlpnError::NOACK)?; + let pos = memchr::memmem::find(client, proto) + .expect("selected alpn proto should be present in client protos"); + Ok(&client[pos..proto.len()]) + }); + Ok(()) + } + #[cfg(not(ossl102))] + { + Err(vm.new_not_implemented_error( + "The NPN extension requires OpenSSL 1.0.1 or later.", + )) + } + } + + #[pymethod] + fn load_verify_locations( + &self, + args: LoadVerifyLocationsArgs, + vm: &VirtualMachine, + ) -> PyResult<()> { + if let (None, None, None) = (&args.cafile, &args.capath, &args.cadata) { + return Err(vm.new_type_error("cafile, capath and cadata cannot be all omitted")); + } + + #[cold] + fn invalid_cadata(vm: &VirtualMachine) -> PyBaseExceptionRef { + vm.new_type_error("cadata should be an ASCII string or a bytes-like object") + } + + let mut ctx = self.builder(); + + // validate cadata type and load cadata + if let Some(cadata) = args.cadata { + let certs = match cadata { + Either::A(s) => { + if !s.is_ascii() { + return Err(invalid_cadata(vm)); + } + X509::stack_from_pem(s.as_bytes()) + } + Either::B(b) => b.with_ref(x509_stack_from_der), + }; + let certs = certs.map_err(|e| convert_openssl_error(vm, e))?; + let store = ctx.cert_store_mut(); + for cert in certs { + store + .add_cert(cert) + .map_err(|e| convert_openssl_error(vm, e))?; + } + } + + if args.cafile.is_some() || args.capath.is_some() { + let cafile_path = args.cafile.map(|p| p.to_path_buf(vm)).transpose()?; + let capath_path = args.capath.map(|p| p.to_path_buf(vm)).transpose()?; + ctx.load_verify_locations(cafile_path.as_deref(), capath_path.as_deref()) + .map_err(|e| convert_openssl_error(vm, e))?; + } + + Ok(()) + } + + #[pymethod] + fn get_ca_certs( + &self, + binary_form: OptionalArg<bool>, + vm: &VirtualMachine, + ) -> PyResult<Vec<PyObjectRef>> { + let binary_form = binary_form.unwrap_or(false); + let ctx = self.ctx(); + #[cfg(ossl300)] + let certs = ctx.cert_store().all_certificates(); + #[cfg(not(ossl300))] + let certs = ctx.cert_store().objects().iter().filter_map(|x| x.x509()); + + // Filter to only include CA certificates (Basic Constraints: CA=TRUE) + let certs = certs + .into_iter() + .filter(|cert| { + unsafe { + // X509_check_ca() returns 1 for CA certificates + X509_check_ca(cert.as_ptr()) == 1 + } + }) + .map(|ref cert| cert_to_py(vm, cert, binary_form)) + .collect::<Result<Vec<_>, _>>()?; + Ok(certs) + } + + #[pymethod] + fn cert_store_stats(&self, vm: &VirtualMachine) -> PyResult { + let ctx = self.ctx(); + let store_ptr = unsafe { sys::SSL_CTX_get_cert_store(ctx.as_ptr()) }; + + if store_ptr.is_null() { + return Err(vm.new_memory_error("failed to get cert store".to_owned())); + } + + let objs_ptr = unsafe { sys::X509_STORE_get0_objects(store_ptr) }; + if objs_ptr.is_null() { + return Err(vm.new_memory_error("failed to query cert store".to_owned())); + } + + let mut x509_count = 0; + let mut crl_count = 0; + let mut ca_count = 0; + + unsafe { + let num_objs = sys::OPENSSL_sk_num(objs_ptr as *const _); + for i in 0..num_objs { + let obj_ptr = + sys::OPENSSL_sk_value(objs_ptr as *const _, i) as *const sys::X509_OBJECT; + let obj_type = X509_OBJECT_get_type(obj_ptr); + + match obj_type { + X509_LU_X509 => { + x509_count += 1; + let x509_ptr = sys::X509_OBJECT_get0_X509(obj_ptr); + if !x509_ptr.is_null() && X509_check_ca(x509_ptr) == 1 { + ca_count += 1; + } + } + X509_LU_CRL => { + crl_count += 1; + } + _ => { + // Ignore unrecognized types + } + } + } + // Note: No need to free objs_ptr as X509_STORE_get0_objects returns + // a pointer to internal data that should not be freed by the caller + } + + let dict = vm.ctx.new_dict(); + dict.set_item("x509", vm.ctx.new_int(x509_count).into(), vm)?; + dict.set_item("crl", vm.ctx.new_int(crl_count).into(), vm)?; + dict.set_item("x509_ca", vm.ctx.new_int(ca_count).into(), vm)?; + Ok(dict.into()) + } + + #[pymethod] + fn session_stats(&self, vm: &VirtualMachine) -> PyResult { + let ctx = self.ctx(); + let ctx_ptr = ctx.as_ptr(); + + let dict = vm.ctx.new_dict(); + + macro_rules! add_stat { + ($key:expr, $func:ident) => { + let value = unsafe { $func(ctx_ptr) }; + dict.set_item($key, vm.ctx.new_int(value).into(), vm)?; + }; + } + + add_stat!("number", SSL_CTX_sess_number); + add_stat!("connect", SSL_CTX_sess_connect); + add_stat!("connect_good", SSL_CTX_sess_connect_good); + add_stat!("connect_renegotiate", SSL_CTX_sess_connect_renegotiate); + add_stat!("accept", SSL_CTX_sess_accept); + add_stat!("accept_good", SSL_CTX_sess_accept_good); + add_stat!("accept_renegotiate", SSL_CTX_sess_accept_renegotiate); + add_stat!("hits", SSL_CTX_sess_hits); + add_stat!("misses", SSL_CTX_sess_misses); + add_stat!("timeouts", SSL_CTX_sess_timeouts); + add_stat!("cache_full", SSL_CTX_sess_cache_full); + + Ok(dict.into()) + } + + #[pymethod] + fn load_dh_params(&self, filepath: FsPath, vm: &VirtualMachine) -> PyResult<()> { + let path = filepath.to_path_buf(vm)?; + + // Open the file using fopen (cross-platform) + let fp = + rustpython_common::fileutils::fopen(path.as_path(), "rb").map_err(|e| { + match e.kind() { + std::io::ErrorKind::NotFound => vm.new_exception_msg( + vm.ctx.exceptions.file_not_found_error.to_owned(), + e.to_string(), + ), + _ => vm.new_os_error(e.to_string()), + } + })?; + + // Read DH parameters + let dh = unsafe { + PEM_read_DHparams( + fp, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + ) + }; + unsafe { + libc::fclose(fp); + } + + if dh.is_null() { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + + // Set temporary DH parameters + let ctx = self.builder(); + let result = unsafe { sys::SSL_CTX_set_tmp_dh(ctx.as_ptr(), dh) }; + unsafe { + sys::DH_free(dh); + } + + if result != 1 { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + + Ok(()) + } + + #[pygetset] + fn sni_callback(&self) -> Option<PyObjectRef> { + self.sni_callback.lock().clone() + } + + #[pygetset(setter)] + fn set_sni_callback( + &self, + value: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Check if this is a server context + if self.protocol == SslVersion::TlsClient { + return Err(vm.new_value_error( + "sni_callback cannot be set on TLS_CLIENT context".to_owned(), + )); + } + + let mut callback_guard = self.sni_callback.lock(); + + if let Some(callback_obj) = value { + if !vm.is_none(&callback_obj) { + // Check if callable + if !callback_obj.is_callable() { + return Err(vm.new_type_error("not a callable object".to_owned())); + } + + // Set the callback + *callback_guard = Some(callback_obj); + + // Set OpenSSL callback + unsafe { + sys::SSL_CTX_set_tlsext_servername_callback__fixed_rust( + self.ctx().as_ptr(), + Some(_servername_callback), + ); + sys::SSL_CTX_set_tlsext_servername_arg( + self.ctx().as_ptr(), + self as *const _ as *mut _, + ); + } + } else { + // Clear callback + *callback_guard = None; + unsafe { + sys::SSL_CTX_set_tlsext_servername_callback__fixed_rust( + self.ctx().as_ptr(), + None, + ); + } + } + } else { + // Clear callback + *callback_guard = None; + unsafe { + sys::SSL_CTX_set_tlsext_servername_callback__fixed_rust( + self.ctx().as_ptr(), + None, + ); + } + } + + Ok(()) + } + + #[pymethod] + fn set_servername_callback( + &self, + callback: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + self.set_sni_callback(callback, vm) + } + + #[pygetset(name = "_msg_callback")] + fn msg_callback(&self) -> Option<PyObjectRef> { + self.msg_callback.lock().clone() + } + + #[pygetset(setter, name = "_msg_callback")] + fn set_msg_callback( + &self, + value: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + let mut callback_guard = self.msg_callback.lock(); + + if let Some(callback_obj) = value { + if !vm.is_none(&callback_obj) { + // Check if callable + if !callback_obj.is_callable() { + return Err(vm.new_type_error("not a callable object".to_owned())); + } + + // Set the callback + *callback_guard = Some(callback_obj); + + // Set OpenSSL callback + unsafe { + SSL_CTX_set_msg_callback(self.ctx().as_ptr(), Some(_msg_callback)); + } + } else { + // Clear callback + *callback_guard = None; + unsafe { + SSL_CTX_set_msg_callback(self.ctx().as_ptr(), None); + } + } + } else { + // Clear callback when value is None + *callback_guard = None; + unsafe { + SSL_CTX_set_msg_callback(self.ctx().as_ptr(), None); + } + } + + Ok(()) + } + + #[pymethod] + fn load_cert_chain(&self, args: LoadCertChainArgs, vm: &VirtualMachine) -> PyResult<()> { + let LoadCertChainArgs { + certfile, + keyfile, + password, + } = args; + // TODO: requires passing a callback to C + if password.is_some() { + return Err(vm.new_not_implemented_error("password arg not yet supported")); + } + let mut ctx = self.builder(); + let key_path = keyfile.map(|path| path.to_path_buf(vm)).transpose()?; + let cert_path = certfile.to_path_buf(vm)?; + ctx.set_certificate_chain_file(&cert_path) + .and_then(|()| { + ctx.set_private_key_file( + key_path.as_ref().unwrap_or(&cert_path), + ssl::SslFiletype::PEM, + ) + }) + .and_then(|()| ctx.check_private_key()) + .map_err(|e| convert_openssl_error(vm, e)) + } + + // Helper function to create SSL socket + // = CPython's newPySSLSocket() + fn new_py_ssl_socket( + ctx_ref: PyRef<PySslContext>, + server_side: bool, + server_hostname: Option<PyStrRef>, + vm: &VirtualMachine, + ) -> PyResult<(ssl::Ssl, SslServerOrClient, Option<PyStrRef>)> { + // Validate socket type and context protocol + if server_side && ctx_ref.protocol == SslVersion::TlsClient { + return Err(vm.new_exception_msg( + PySslError::class(&vm.ctx).to_owned(), + "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context".to_owned(), + )); + } + if !server_side && ctx_ref.protocol == SslVersion::TlsServer { + return Err(vm.new_exception_msg( + PySslError::class(&vm.ctx).to_owned(), + "Cannot create a client socket with a PROTOCOL_TLS_SERVER context".to_owned(), + )); + } + + // Create SSL object + let mut ssl = + ssl::Ssl::new(&ctx_ref.ctx()).map_err(|e| convert_openssl_error(vm, e))?; + + // Set session id context for server-side sockets + let socket_type = if server_side { + unsafe { + const SID_CTX: &[u8] = b"Python"; + let ret = SSL_set_session_id_context( + ssl.as_ptr(), + SID_CTX.as_ptr(), + SID_CTX.len() as libc::c_uint, + ); + if ret == 0 { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + } + SslServerOrClient::Server + } else { + SslServerOrClient::Client + }; + + // Configure server hostname + if let Some(hostname) = &server_hostname { + let hostname_str = hostname.as_str(); + if hostname_str.is_empty() || hostname_str.starts_with('.') { + return Err(vm.new_value_error( + "server_hostname cannot be an empty string or start with a leading dot.", + )); + } + if hostname_str.contains('\0') { + return Err(vm.new_value_error("embedded null byte in server_hostname")); + } + let ip = hostname_str.parse::<std::net::IpAddr>(); + if ip.is_err() { + ssl.set_hostname(hostname_str) + .map_err(|e| convert_openssl_error(vm, e))?; + } + if ctx_ref.check_hostname.load() { + if let Ok(ip) = ip { + ssl.param_mut() + .set_ip(ip) + .map_err(|e| convert_openssl_error(vm, e))?; + } else { + ssl.param_mut() + .set_host(hostname_str) + .map_err(|e| convert_openssl_error(vm, e))?; + } + } + } + + // Configure post-handshake authentication + #[cfg(ossl111)] + if *ctx_ref.post_handshake_auth.lock() { + unsafe { + if server_side { + // Server socket: add SSL_VERIFY_POST_HANDSHAKE flag + // Only in combination with SSL_VERIFY_PEER + let mode = sys::SSL_get_verify_mode(ssl.as_ptr()); + if (mode & sys::SSL_VERIFY_PEER as libc::c_int) != 0 { + sys::SSL_set_verify( + ssl.as_ptr(), + mode | SSL_VERIFY_POST_HANDSHAKE, + None, + ); + } + } else { + // Client socket: call SSL_set_post_handshake_auth + SSL_set_post_handshake_auth(ssl.as_ptr(), 1); + } + } + } + + // Set connect/accept state + if server_side { + ssl.set_accept_state(); + } else { + ssl.set_connect_state(); + } + + Ok((ssl, socket_type, server_hostname)) + } + + #[pymethod] + fn _wrap_socket( + zelf: PyRef<Self>, + args: WrapSocketArgs, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + // Use common helper function + let (ssl, socket_type, server_hostname) = + Self::new_py_ssl_socket(zelf.clone(), args.server_side, args.server_hostname, vm)?; + + // Create SslStream with socket + let stream = ssl::SslStream::new(ssl, SocketStream(args.sock.clone())) + .map_err(|e| convert_openssl_error(vm, e))?; + + let py_ssl_socket = PySslSocket { + ctx: PyRwLock::new(zelf.clone()), + connection: PyRwLock::new(SslConnection::Socket(stream)), + socket_type, + server_hostname, + owner: PyRwLock::new(args.owner.map(|o| o.downgrade(None, vm)).transpose()?), + }; + + // Convert to PyRef (heap allocation) to avoid use-after-free + let py_ref = + py_ssl_socket.into_ref_with_type(vm, PySslSocket::class(&vm.ctx).to_owned())?; + + // Set SNI callback data if callback is configured + if zelf.sni_callback.lock().is_some() { + unsafe { + let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); + + // Store callback data in SSL ex_data + let callback_data = Box::new(SniCallbackData { + ssl_context: zelf.clone(), + vm_ptr: vm as *const _, + }); + let idx = get_sni_ex_data_index(); + sys::SSL_set_ex_data(ssl_ptr, idx, Box::into_raw(callback_data) as *mut _); + + // Store PyRef pointer (heap-allocated) in ex_data index 0 + sys::SSL_set_ex_data(ssl_ptr, 0, &*py_ref as *const _ as *mut _); + } + } + + // Set session if provided + if let Some(session) = args.session + && !vm.is_none(&session) + { + py_ref.set_session(session, vm)?; + } + + Ok(py_ref.into()) + } + + #[pymethod] + fn _wrap_bio( + zelf: PyRef<Self>, + args: WrapBioArgs, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + // Use common helper function + let (ssl, socket_type, server_hostname) = + Self::new_py_ssl_socket(zelf.clone(), args.server_side, args.server_hostname, vm)?; + + // Create BioStream wrapper + let bio_stream = BioStream { + inbio: args.incoming, + outbio: args.outgoing, + }; + + // Create SslStream with BioStream + let stream = + ssl::SslStream::new(ssl, bio_stream).map_err(|e| convert_openssl_error(vm, e))?; + + let py_ssl_socket = PySslSocket { + ctx: PyRwLock::new(zelf.clone()), + connection: PyRwLock::new(SslConnection::Bio(stream)), + socket_type, + server_hostname, + owner: PyRwLock::new(args.owner.map(|o| o.downgrade(None, vm)).transpose()?), + }; + + // Convert to PyRef (heap allocation) to avoid use-after-free + let py_ref = + py_ssl_socket.into_ref_with_type(vm, PySslSocket::class(&vm.ctx).to_owned())?; + + // Set SNI callback data if callback is configured + if zelf.sni_callback.lock().is_some() { + unsafe { + let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); + + // Store callback data in SSL ex_data + let callback_data = Box::new(SniCallbackData { + ssl_context: zelf.clone(), + vm_ptr: vm as *const _, + }); + let idx = get_sni_ex_data_index(); + sys::SSL_set_ex_data(ssl_ptr, idx, Box::into_raw(callback_data) as *mut _); + + // Store PyRef pointer (heap-allocated) in ex_data index 0 + sys::SSL_set_ex_data(ssl_ptr, 0, &*py_ref as *const _ as *mut _); + } + } + + // Set session if provided + if let Some(session) = args.session + && !vm.is_none(&session) + { + py_ref.set_session(session, vm)?; + } + + Ok(py_ref.into()) + } + } + + #[derive(FromArgs)] + #[allow(dead_code)] // Fields will be used when _wrap_bio is fully implemented + struct WrapBioArgs { + incoming: PyRef<PySslMemoryBio>, + outgoing: PyRef<PySslMemoryBio>, + server_side: bool, + #[pyarg(any, default)] + server_hostname: Option<PyStrRef>, + #[pyarg(named, default)] + owner: Option<PyObjectRef>, + #[pyarg(named, default)] + session: Option<PyObjectRef>, + } + + #[derive(FromArgs)] + struct WrapSocketArgs { + sock: PyRef<PySocket>, + server_side: bool, + #[pyarg(any, default)] + server_hostname: Option<PyStrRef>, + #[pyarg(named, default)] + owner: Option<PyObjectRef>, + #[pyarg(named, default)] + session: Option<PyObjectRef>, + } + + #[derive(FromArgs)] + struct LoadVerifyLocationsArgs { + #[pyarg(any, default)] + cafile: Option<FsPath>, + #[pyarg(any, default)] + capath: Option<FsPath>, + #[pyarg(any, default)] + cadata: Option<Either<PyStrRef, ArgBytesLike>>, + } + + #[derive(FromArgs)] + struct LoadCertChainArgs { + certfile: FsPath, + #[pyarg(any, optional)] + keyfile: Option<FsPath>, + #[pyarg(any, optional)] + password: Option<Either<PyStrRef, ArgCallable>>, + } + + // Err is true if the socket is blocking + type SocketDeadline = Result<Instant, bool>; + + enum SelectRet { + Nonblocking, + TimedOut, + IsBlocking, + Closed, + Ok, + } + + #[derive(Clone, Copy)] + enum SslNeeds { + Read, + Write, + } + + struct SocketStream(PyRef<PySocket>); + + impl SocketStream { + fn timeout_deadline(&self) -> SocketDeadline { + self.0.get_timeout().map(|d| Instant::now() + d) + } + + fn select(&self, needs: SslNeeds, deadline: &SocketDeadline) -> SelectRet { + let sock = match self.0.sock_opt() { + Some(s) => s, + None => return SelectRet::Closed, + }; + let deadline = match &deadline { + Ok(deadline) => match deadline.checked_duration_since(Instant::now()) { + Some(deadline) => deadline, + None => return SelectRet::TimedOut, + }, + Err(true) => return SelectRet::IsBlocking, + Err(false) => return SelectRet::Nonblocking, + }; + let res = socket::sock_select( + &sock, + match needs { + SslNeeds::Read => socket::SelectKind::Read, + SslNeeds::Write => socket::SelectKind::Write, + }, + Some(deadline), + ); + match res { + Ok(true) => SelectRet::TimedOut, + _ => SelectRet::Ok, + } + } + + fn socket_needs( + &self, + err: &ssl::Error, + deadline: &SocketDeadline, + ) -> (Option<SslNeeds>, SelectRet) { + let needs = match err.code() { + ssl::ErrorCode::WANT_READ => Some(SslNeeds::Read), + ssl::ErrorCode::WANT_WRITE => Some(SslNeeds::Write), + _ => None, + }; + let state = needs.map_or(SelectRet::Ok, |needs| self.select(needs, deadline)); + (needs, state) + } + } + + fn socket_closed_error(vm: &VirtualMachine) -> PyBaseExceptionRef { + vm.new_exception_msg( + PySslError::class(&vm.ctx).to_owned(), + "Underlying socket has been closed.".to_owned(), + ) + } + + // BIO stream wrapper to implement Read/Write traits for MemoryBIO + struct BioStream { + inbio: PyRef<PySslMemoryBio>, + outbio: PyRef<PySslMemoryBio>, + } + + impl Read for BioStream { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + // Read from incoming MemoryBIO + unsafe { + let nbytes = sys::BIO_read( + self.inbio.bio, + buf.as_mut_ptr() as *mut _, + buf.len().min(i32::MAX as usize) as i32, + ); + if nbytes < 0 { + // BIO_read returns -1 on error or when no data is available + // Check if it's a retry condition (WANT_READ) + Err(std::io::Error::new( + std::io::ErrorKind::WouldBlock, + "BIO has no data available", + )) + } else { + Ok(nbytes as usize) + } + } + } + } + + impl Write for BioStream { + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { + // Write to outgoing MemoryBIO + unsafe { + let nbytes = sys::BIO_write( + self.outbio.bio, + buf.as_ptr() as *const _, + buf.len().min(i32::MAX as usize) as i32, + ); + if nbytes < 0 { + return Err(std::io::Error::other("BIO write failed")); + } + Ok(nbytes as usize) + } + } + + fn flush(&mut self) -> std::io::Result<()> { + // MemoryBIO doesn't need flushing + Ok(()) + } + } + + // Enum to represent different SSL connection modes + enum SslConnection { + Socket(ssl::SslStream<SocketStream>), + Bio(ssl::SslStream<BioStream>), + } + + impl SslConnection { + // Get a reference to the SSL object + fn ssl(&self) -> &ssl::SslRef { + match self { + SslConnection::Socket(stream) => stream.ssl(), + SslConnection::Bio(stream) => stream.ssl(), + } + } + + // Get underlying socket stream reference (only for socket mode) + fn get_ref(&self) -> Option<&SocketStream> { + match self { + SslConnection::Socket(stream) => Some(stream.get_ref()), + SslConnection::Bio(_) => None, + } + } + + // Check if this is in BIO mode + fn is_bio(&self) -> bool { + matches!(self, SslConnection::Bio(_)) + } + + // Perform SSL handshake + fn do_handshake(&mut self) -> Result<(), ssl::Error> { + match self { + SslConnection::Socket(stream) => stream.do_handshake(), + SslConnection::Bio(stream) => stream.do_handshake(), + } + } + + // Write data to SSL connection + fn ssl_write(&mut self, buf: &[u8]) -> Result<usize, ssl::Error> { + match self { + SslConnection::Socket(stream) => stream.ssl_write(buf), + SslConnection::Bio(stream) => stream.ssl_write(buf), + } + } + + // Read data from SSL connection + fn ssl_read(&mut self, buf: &mut [u8]) -> Result<usize, ssl::Error> { + match self { + SslConnection::Socket(stream) => stream.ssl_read(buf), + SslConnection::Bio(stream) => stream.ssl_read(buf), + } + } + + // Get SSL shutdown state + fn get_shutdown(&mut self) -> ssl::ShutdownState { + match self { + SslConnection::Socket(stream) => stream.get_shutdown(), + SslConnection::Bio(stream) => stream.get_shutdown(), + } + } + } + + #[pyattr] + #[pyclass(module = "ssl", name = "_SSLSocket", traverse)] + #[derive(PyPayload)] + struct PySslSocket { + ctx: PyRwLock<PyRef<PySslContext>>, + #[pytraverse(skip)] + connection: PyRwLock<SslConnection>, + #[pytraverse(skip)] + socket_type: SslServerOrClient, + server_hostname: Option<PyStrRef>, + owner: PyRwLock<Option<PyRef<PyWeak>>>, + } + + impl fmt::Debug for PySslSocket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("_SSLSocket") + } + } + + #[pyclass(flags(IMMUTABLETYPE))] + impl PySslSocket { + #[pygetset] + fn owner(&self) -> Option<PyObjectRef> { + self.owner.read().as_ref().and_then(|weak| weak.upgrade()) + } + #[pygetset(setter)] + fn set_owner(&self, owner: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let mut lock = self.owner.write(); + lock.take(); + *lock = Some(owner.downgrade(None, vm)?); + Ok(()) + } + #[pygetset] + fn server_side(&self) -> bool { + self.socket_type == SslServerOrClient::Server + } + #[pygetset] + fn context(&self) -> PyRef<PySslContext> { + self.ctx.read().clone() + } + #[pygetset(setter)] + fn set_context(&self, value: PyRef<PySslContext>, vm: &VirtualMachine) -> PyResult<()> { + // Update the SSL context in the underlying SSL object + let stream = self.connection.read(); + + // Set the new SSL_CTX on the SSL object + unsafe { + let result = SSL_set_SSL_CTX(stream.ssl().as_ptr(), value.ctx().as_ptr()); + if result.is_null() { + return Err(vm.new_runtime_error("Failed to set SSL context".to_owned())); + } + } + + // Update self.ctx to the new context + *self.ctx.write() = value; + Ok(()) + } + #[pygetset] + fn server_hostname(&self) -> Option<PyStrRef> { + self.server_hostname.clone() + } + + #[pymethod] + fn getpeercert( + &self, + binary: OptionalArg<bool>, + vm: &VirtualMachine, + ) -> PyResult<Option<PyObjectRef>> { + let binary = binary.unwrap_or(false); + let stream = self.connection.read(); + if !stream.ssl().is_init_finished() { + return Err(vm.new_value_error("handshake not done yet")); + } + + let peer_cert = stream.ssl().peer_certificate(); + let Some(cert) = peer_cert else { + return Ok(None); + }; + + if binary { + // Return DER-encoded certificate + cert_to_py(vm, &cert, true).map(Some) + } else { + // Check verify_mode + unsafe { + let ssl_ctx = sys::SSL_get_SSL_CTX(stream.ssl().as_ptr()); + let verify_mode = sys::SSL_CTX_get_verify_mode(ssl_ctx); + if (verify_mode & sys::SSL_VERIFY_PEER as libc::c_int) == 0 { + // Return empty dict when SSL_VERIFY_PEER is not set + Ok(Some(vm.ctx.new_dict().into())) + } else { + // Return decoded certificate + cert_to_py(vm, &cert, false).map(Some) + } + } + } + } + + #[pymethod] + fn get_unverified_chain(&self, vm: &VirtualMachine) -> PyResult<Option<PyListRef>> { + let stream = self.connection.read(); + let Some(chain) = stream.ssl().peer_cert_chain() else { + return Ok(None); + }; + + // Return Certificate objects + let certs: Vec<PyObjectRef> = chain + .iter() + .map(|cert| unsafe { + sys::X509_up_ref(cert.as_ptr()); + let owned = X509::from_ptr(cert.as_ptr()); + cert_to_certificate(vm, owned) + }) + .collect::<PyResult<_>>()?; + Ok(Some(vm.ctx.new_list(certs))) + } + + #[pymethod] + fn get_verified_chain(&self, vm: &VirtualMachine) -> PyResult<Option<PyListRef>> { + let stream = self.connection.read(); + unsafe { + let chain = sys::SSL_get0_verified_chain(stream.ssl().as_ptr()); + if chain.is_null() { + return Ok(None); + } + + let num_certs = sys::OPENSSL_sk_num(chain as *const _); + + let mut certs = Vec::with_capacity(num_certs as usize); + // Return Certificate objects + for i in 0..num_certs { + let cert_ptr = sys::OPENSSL_sk_value(chain as *const _, i) as *mut sys::X509; + if cert_ptr.is_null() { + continue; + } + // Clone the X509 certificate to create an owned copy + sys::X509_up_ref(cert_ptr); + let owned_cert = X509::from_ptr(cert_ptr); + let cert_obj = cert_to_certificate(vm, owned_cert)?; + certs.push(cert_obj); + } + + Ok(if certs.is_empty() { + None + } else { + Some(vm.ctx.new_list(certs)) + }) + } + } + + #[pymethod] + fn version(&self) -> Option<&'static str> { + let v = self.connection.read().ssl().version_str(); + if v == "unknown" { None } else { Some(v) } + } + + #[pymethod] + fn cipher(&self) -> Option<CipherTuple> { + self.connection + .read() + .ssl() + .current_cipher() + .map(cipher_to_tuple) + } + + #[pymethod] + fn shared_ciphers(&self, vm: &VirtualMachine) -> Option<PyListRef> { + #[cfg(ossl110)] + { + let stream = self.connection.read(); + unsafe { + let server_ciphers = SSL_get_ciphers(stream.ssl().as_ptr()); + if server_ciphers.is_null() { + return None; + } + + let client_ciphers = SSL_get_client_ciphers(stream.ssl().as_ptr()); + if client_ciphers.is_null() { + return None; + } + + let mut result = Vec::new(); + let num_server = sys::OPENSSL_sk_num(server_ciphers as *const _); + let num_client = sys::OPENSSL_sk_num(client_ciphers as *const _); + + for i in 0..num_server { + let server_cipher_ptr = sys::OPENSSL_sk_value(server_ciphers as *const _, i) + as *const sys::SSL_CIPHER; + + // Check if client supports this cipher by comparing pointers + let mut found = false; + for j in 0..num_client { + let client_cipher_ptr = + sys::OPENSSL_sk_value(client_ciphers as *const _, j) + as *const sys::SSL_CIPHER; + + if server_cipher_ptr == client_cipher_ptr { + found = true; + break; + } + } + + if found { + let cipher = ssl::SslCipherRef::from_ptr(server_cipher_ptr as *mut _); + let (name, version, bits) = cipher_to_tuple(cipher); + let tuple = vm.new_tuple(( + vm.ctx.new_str(name), + vm.ctx.new_str(version), + vm.ctx.new_int(bits), + )); + result.push(tuple.into()); + } + } + + if result.is_empty() { + None + } else { + Some(vm.ctx.new_list(result)) + } + } + } + #[cfg(not(ossl110))] + { + let _ = vm; + None + } + } + + #[pymethod] + fn selected_alpn_protocol(&self) -> Option<String> { + #[cfg(ossl102)] + { + let stream = self.connection.read(); + unsafe { + let mut out: *const libc::c_uchar = std::ptr::null(); + let mut outlen: libc::c_uint = 0; + + sys::SSL_get0_alpn_selected(stream.ssl().as_ptr(), &mut out, &mut outlen); + + if out.is_null() { + None + } else { + let slice = std::slice::from_raw_parts(out, outlen as usize); + Some(String::from_utf8_lossy(slice).into_owned()) + } + } + } + #[cfg(not(ossl102))] + { + None + } + } + + #[pymethod] + fn get_channel_binding( + &self, + cb_type: OptionalArg<PyStrRef>, + vm: &VirtualMachine, + ) -> PyResult<Option<PyBytesRef>> { + const CB_MAXLEN: usize = 512; + + let cb_type_str = cb_type.as_ref().map_or("tls-unique", |s| s.as_str()); + + if cb_type_str != "tls-unique" { + return Err(vm.new_value_error(format!( + "Unsupported channel binding type '{}'", + cb_type_str + ))); + } + + let stream = self.connection.read(); + let ssl_ptr = stream.ssl().as_ptr(); + + unsafe { + let session_reused = sys::SSL_session_reused(ssl_ptr) != 0; + let is_client = matches!(self.socket_type, SslServerOrClient::Client); + + // Use XOR logic from CPython + let use_finished = session_reused ^ is_client; + + let mut buf = vec![0u8; CB_MAXLEN]; + let len = if use_finished { + sys::SSL_get_finished(ssl_ptr, buf.as_mut_ptr() as *mut _, CB_MAXLEN) + } else { + sys::SSL_get_peer_finished(ssl_ptr, buf.as_mut_ptr() as *mut _, CB_MAXLEN) + }; + + if len == 0 { + Ok(None) + } else { + buf.truncate(len); + Ok(Some(vm.ctx.new_bytes(buf))) + } + } + } + + #[pymethod] + fn verify_client_post_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { + #[cfg(ossl111)] + { + let stream = self.connection.read(); + let result = unsafe { SSL_verify_client_post_handshake(stream.ssl().as_ptr()) }; + if result == 0 { + Err(convert_openssl_error(vm, openssl::error::ErrorStack::get())) + } else { + Ok(()) + } + } + #[cfg(not(ossl111))] + { + Err(vm.new_not_implemented_error( + "Post-handshake auth is not supported by your OpenSSL version.".to_owned(), + )) + } + } + + #[pymethod] + fn shutdown(&self, vm: &VirtualMachine) -> PyResult<PyRef<PySocket>> { + let stream = self.connection.read(); + + // BIO mode doesn't have an underlying socket + if stream.is_bio() { + return Err(vm.new_not_implemented_error( + "shutdown() is not supported for BIO-based SSL objects".to_owned(), + )); + } + + let ssl_ptr = stream.ssl().as_ptr(); + + // Perform SSL shutdown + let ret = unsafe { sys::SSL_shutdown(ssl_ptr) }; + + if ret < 0 { + // Error occurred + let err = unsafe { sys::SSL_get_error(ssl_ptr, ret) }; + + if err == sys::SSL_ERROR_WANT_READ || err == sys::SSL_ERROR_WANT_WRITE { + // Non-blocking would block - this is okay for shutdown + // Return the underlying socket + } else { + return Err(vm.new_exception_msg( + PySslError::class(&vm.ctx).to_owned(), + format!("SSL shutdown failed: error code {}", err), + )); + } + } + + // Return the underlying socket + // Get the socket from the stream (SocketStream wraps PyRef<PySocket>) + let socket = stream + .get_ref() + .expect("unwrap() called on bio mode; should only be called in socket mode"); + Ok(socket.0.clone()) + } + + #[cfg(osslconf = "OPENSSL_NO_COMP")] + #[pymethod] + fn compression(&self) -> Option<&'static str> { + None + } + #[cfg(not(osslconf = "OPENSSL_NO_COMP"))] + #[pymethod] + fn compression(&self) -> Option<&'static str> { + let stream = self.connection.read(); + let comp_method = unsafe { sys::SSL_get_current_compression(stream.ssl().as_ptr()) }; + if comp_method.is_null() { + return None; + } + let typ = unsafe { sys::COMP_get_type(comp_method) }; + let nid = Nid::from_raw(typ); + if nid == Nid::UNDEF { + return None; + } + nid.short_name().ok() + } + + #[pymethod] + fn do_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { + let mut stream = self.connection.write(); + let ssl_ptr = stream.ssl().as_ptr(); + + // BIO mode: no timeout/select logic, just do handshake + if stream.is_bio() { + return stream.do_handshake().map_err(|e| { + let exc = convert_ssl_error(vm, e); + // If it's a cert verification error, set verify info + if exc.class().is(PySslCertVerificationError::class(&vm.ctx)) { + set_verify_error_info(&exc, ssl_ptr, vm); + } + exc + }); + } + + // Socket mode: handle timeout and blocking + let timeout = stream + .get_ref() + .expect("handshake called in bio mode; should only be called in socket mode") + .timeout_deadline(); + loop { + let err = match stream.do_handshake() { + Ok(()) => return Ok(()), + Err(e) => e, + }; + let (needs, state) = stream + .get_ref() + .expect("handshake called in bio mode; should only be called in socket mode") + .socket_needs(&err, &timeout); + match state { + SelectRet::TimedOut => { + return Err(socket::timeout_error_msg( + vm, + "The handshake operation timed out".to_owned(), + )); + } + SelectRet::Closed => return Err(socket_closed_error(vm)), + SelectRet::Nonblocking => {} + SelectRet::IsBlocking | SelectRet::Ok => { + // For blocking sockets, select() has completed successfully + // Continue the handshake loop (matches CPython's SOCKET_IS_BLOCKING behavior) + if needs.is_some() { + continue; + } + } + } + let exc = convert_ssl_error(vm, err); + // If it's a cert verification error, set verify info + if exc.class().is(PySslCertVerificationError::class(&vm.ctx)) { + set_verify_error_info(&exc, ssl_ptr, vm); + } + return Err(exc); + } + } + + #[pymethod] + fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> { + let mut stream = self.connection.write(); + let data = data.borrow_buf(); + let data = &*data; + + // BIO mode: no timeout/select logic + if stream.is_bio() { + return stream.ssl_write(data).map_err(|e| convert_ssl_error(vm, e)); + } + + // Socket mode: handle timeout and blocking + let socket_ref = stream + .get_ref() + .expect("write called in bio mode; should only be called in socket mode"); + let timeout = socket_ref.timeout_deadline(); + let state = socket_ref.select(SslNeeds::Write, &timeout); + match state { + SelectRet::TimedOut => { + return Err(socket::timeout_error_msg( + vm, + "The write operation timed out".to_owned(), + )); + } + SelectRet::Closed => return Err(socket_closed_error(vm)), + _ => {} + } + loop { + let err = match stream.ssl_write(data) { + Ok(len) => return Ok(len), + Err(e) => e, + }; + let (needs, state) = stream + .get_ref() + .expect("write called in bio mode; should only be called in socket mode") + .socket_needs(&err, &timeout); + match state { + SelectRet::TimedOut => { + return Err(socket::timeout_error_msg( + vm, + "The write operation timed out".to_owned(), + )); + } + SelectRet::Closed => return Err(socket_closed_error(vm)), + SelectRet::Nonblocking => {} + SelectRet::IsBlocking | SelectRet::Ok => { + // For blocking sockets, select() has completed successfully + // Continue the write loop (matches CPython's SOCKET_IS_BLOCKING behavior) + if needs.is_some() { + continue; + } + } + } + return Err(convert_ssl_error(vm, err)); + } + } + + #[pygetset] + fn session(&self, _vm: &VirtualMachine) -> PyResult<Option<PySslSession>> { + let stream = self.connection.read(); + unsafe { + // Use SSL_get1_session which returns an owned reference (ref count already incremented) + let session_ptr = SSL_get1_session(stream.ssl().as_ptr()); + if session_ptr.is_null() { + Ok(None) + } else { + Ok(Some(PySslSession { + session: session_ptr, + ctx: self.ctx.read().clone(), + })) + } + } + } + + #[pygetset(setter)] + fn set_session(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // Check if value is SSLSession type + let session = value + .downcast_ref::<PySslSession>() + .ok_or_else(|| vm.new_type_error("Value is not a SSLSession.".to_owned()))?; + + // Check if session refers to the same SSLContext + if !std::ptr::eq( + self.ctx.read().ctx.read().as_ptr(), + session.ctx.ctx.read().as_ptr(), + ) { + return Err( + vm.new_value_error("Session refers to a different SSLContext.".to_owned()) + ); + } + + // Check if this is a client socket + if self.socket_type != SslServerOrClient::Client { + return Err( + vm.new_value_error("Cannot set session for server-side SSLSocket.".to_owned()) + ); + } + + // Check if handshake is not finished + let stream = self.connection.read(); + unsafe { + if sys::SSL_is_init_finished(stream.ssl().as_ptr()) != 0 { + return Err( + vm.new_value_error("Cannot set session after handshake.".to_owned()) + ); + } + + let ret = sys::SSL_set_session(stream.ssl().as_ptr(), session.session); + if ret == 0 { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + } + + Ok(()) + } + + #[pygetset] + fn session_reused(&self) -> bool { + let stream = self.connection.read(); + unsafe { sys::SSL_session_reused(stream.ssl().as_ptr()) != 0 } + } + + #[pymethod] + fn read( + &self, + n: usize, + buffer: OptionalArg<ArgMemoryBuffer>, + vm: &VirtualMachine, + ) -> PyResult { + // Special case: reading 0 bytes should return empty bytes immediately + if n == 0 { + return if buffer.is_present() { + Ok(vm.ctx.new_int(0).into()) + } else { + Ok(vm.ctx.new_bytes(vec![]).into()) + }; + } + + let mut stream = self.connection.write(); + let mut inner_buffer = if let OptionalArg::Present(buffer) = &buffer { + Either::A(buffer.borrow_buf_mut()) + } else { + Either::B(vec![0u8; n]) + }; + let buf = match &mut inner_buffer { + Either::A(b) => &mut **b, + Either::B(b) => b.as_mut_slice(), + }; + let buf = match buf.get_mut(..n) { + Some(b) => b, + None => buf, + }; + + // BIO mode: no timeout/select logic + let count = if stream.is_bio() { + match stream.ssl_read(buf) { + Ok(count) => count, + Err(e) => return Err(convert_ssl_error(vm, e)), + } + } else { + // Socket mode: handle timeout and blocking + let timeout = stream + .get_ref() + .expect("read called in bio mode; should only be called in socket mode") + .timeout_deadline(); + loop { + let err = match stream.ssl_read(buf) { + Ok(count) => break count, + Err(e) => e, + }; + if err.code() == ssl::ErrorCode::ZERO_RETURN + && stream.get_shutdown() == ssl::ShutdownState::RECEIVED + { + break 0; + } + let (needs, state) = stream + .get_ref() + .expect("read called in bio mode; should only be called in socket mode") + .socket_needs(&err, &timeout); + match state { + SelectRet::TimedOut => { + return Err(socket::timeout_error_msg( + vm, + "The read operation timed out".to_owned(), + )); + } + SelectRet::Closed => return Err(socket_closed_error(vm)), + SelectRet::Nonblocking => {} + SelectRet::IsBlocking | SelectRet::Ok => { + // For blocking sockets, select() has completed successfully + // Continue the read loop (matches CPython's SOCKET_IS_BLOCKING behavior) + if needs.is_some() { + continue; + } + } + } + return Err(convert_ssl_error(vm, err)); + } + }; + let ret = match inner_buffer { + Either::A(_buf) => vm.ctx.new_int(count).into(), + Either::B(mut buf) => { + buf.truncate(count); + buf.shrink_to_fit(); + vm.ctx.new_bytes(buf).into() + } + }; + Ok(ret) + } + } + + #[pyattr] + #[pyclass(module = "ssl", name = "SSLSession")] + #[derive(PyPayload)] + struct PySslSession { + session: *mut sys::SSL_SESSION, + ctx: PyRef<PySslContext>, + } + + impl fmt::Debug for PySslSession { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("SSLSession") + } + } + + impl Drop for PySslSession { + fn drop(&mut self) { + if !self.session.is_null() { + unsafe { + sys::SSL_SESSION_free(self.session); + } + } + } + } + + unsafe impl Send for PySslSession {} + unsafe impl Sync for PySslSession {} + + impl Comparable for PySslSession { + fn cmp( + zelf: &Py<Self>, + other: &crate::vm::PyObject, + op: PyComparisonOp, + _vm: &VirtualMachine, + ) -> PyResult<PyComparisonValue> { + let other = class_or_notimplemented!(Self, other); + + if !matches!(op, PyComparisonOp::Eq | PyComparisonOp::Ne) { + return Ok(PyComparisonValue::NotImplemented); + } + let mut eq = unsafe { + let mut self_len: libc::c_uint = 0; + let mut other_len: libc::c_uint = 0; + let self_id = sys::SSL_SESSION_get_id(zelf.session, &mut self_len); + let other_id = sys::SSL_SESSION_get_id(other.session, &mut other_len); + + if self_len != other_len { + false + } else { + let self_slice = std::slice::from_raw_parts(self_id, self_len as usize); + let other_slice = std::slice::from_raw_parts(other_id, other_len as usize); + self_slice == other_slice + } + }; + if matches!(op, PyComparisonOp::Ne) { + eq = !eq; + } + Ok(PyComparisonValue::Implemented(eq)) + } + } + + #[pyattr] + #[pyclass(module = "ssl", name = "MemoryBIO")] + #[derive(PyPayload)] + struct PySslMemoryBio { + bio: *mut sys::BIO, + eof_written: AtomicCell<bool>, + } + + impl fmt::Debug for PySslMemoryBio { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("MemoryBIO") + } + } + + impl Drop for PySslMemoryBio { + fn drop(&mut self) { + if !self.bio.is_null() { + unsafe { + sys::BIO_free_all(self.bio); + } + } + } + } + + unsafe impl Send for PySslMemoryBio {} + unsafe impl Sync for PySslMemoryBio {} + + // OpenSSL functions not in openssl-sys + + unsafe extern "C" { + // X509_check_ca returns 1 for CA certificates, 0 otherwise + fn X509_check_ca(x: *const sys::X509) -> libc::c_int; + } + + unsafe extern "C" { + fn SSL_get_ciphers(ssl: *const sys::SSL) -> *const sys::stack_st_SSL_CIPHER; + } + + #[cfg(ossl110)] + unsafe extern "C" { + fn SSL_get_client_ciphers(ssl: *const sys::SSL) -> *const sys::stack_st_SSL_CIPHER; + } + + #[cfg(ossl111)] + unsafe extern "C" { + fn SSL_verify_client_post_handshake(ssl: *const sys::SSL) -> libc::c_int; + fn SSL_set_post_handshake_auth(ssl: *mut sys::SSL, val: libc::c_int); + } + + #[cfg(ossl110)] + unsafe extern "C" { + fn SSL_CTX_get_security_level(ctx: *const sys::SSL_CTX) -> libc::c_int; + } + + unsafe extern "C" { + fn SSL_set_SSL_CTX(ssl: *mut sys::SSL, ctx: *mut sys::SSL_CTX) -> *mut sys::SSL_CTX; + } + + // Message callback type + #[allow(non_camel_case_types)] + type SSL_CTX_msg_callback = Option< + unsafe extern "C" fn( + write_p: libc::c_int, + version: libc::c_int, + content_type: libc::c_int, + buf: *const libc::c_void, + len: usize, + ssl: *mut sys::SSL, + arg: *mut libc::c_void, + ), + >; + + unsafe extern "C" { + fn SSL_CTX_set_msg_callback(ctx: *mut sys::SSL_CTX, cb: SSL_CTX_msg_callback); + } + + #[cfg(ossl110)] + unsafe extern "C" { + fn SSL_SESSION_has_ticket(session: *const sys::SSL_SESSION) -> libc::c_int; + fn SSL_SESSION_get_ticket_lifetime_hint(session: *const sys::SSL_SESSION) -> libc::c_ulong; + } + + // X509 object types + const X509_LU_X509: libc::c_int = 1; + const X509_LU_CRL: libc::c_int = 2; + + unsafe extern "C" { + fn X509_OBJECT_get_type(obj: *const sys::X509_OBJECT) -> libc::c_int; + fn SSL_set_session_id_context( + ssl: *mut sys::SSL, + sid_ctx: *const libc::c_uchar, + sid_ctx_len: libc::c_uint, + ) -> libc::c_int; + fn SSL_get1_session(ssl: *const sys::SSL) -> *mut sys::SSL_SESSION; + } + + // SSL session statistics constants (used with SSL_CTX_ctrl) + const SSL_CTRL_SESS_NUMBER: libc::c_int = 20; + const SSL_CTRL_SESS_CONNECT: libc::c_int = 21; + const SSL_CTRL_SESS_CONNECT_GOOD: libc::c_int = 22; + const SSL_CTRL_SESS_CONNECT_RENEGOTIATE: libc::c_int = 23; + const SSL_CTRL_SESS_ACCEPT: libc::c_int = 24; + const SSL_CTRL_SESS_ACCEPT_GOOD: libc::c_int = 25; + const SSL_CTRL_SESS_ACCEPT_RENEGOTIATE: libc::c_int = 26; + const SSL_CTRL_SESS_HIT: libc::c_int = 27; + const SSL_CTRL_SESS_MISSES: libc::c_int = 29; + const SSL_CTRL_SESS_TIMEOUTS: libc::c_int = 30; + const SSL_CTRL_SESS_CACHE_FULL: libc::c_int = 31; + + // SSL session statistics functions (implemented as macros in OpenSSL) + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_number(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_NUMBER, 0, std::ptr::null_mut()) } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_connect(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_CONNECT, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_connect_good(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_CONNECT_GOOD, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_connect_renegotiate(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_CONNECT_RENEGOTIATE, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_accept(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_ACCEPT, 0, std::ptr::null_mut()) } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_accept_good(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_ACCEPT_GOOD, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_accept_renegotiate(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_ACCEPT_RENEGOTIATE, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_hits(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_HIT, 0, std::ptr::null_mut()) } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_misses(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_MISSES, 0, std::ptr::null_mut()) } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_timeouts(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_TIMEOUTS, + 0, + std::ptr::null_mut(), + ) + } + } + + #[allow(non_snake_case)] + unsafe fn SSL_CTX_sess_cache_full(ctx: *const sys::SSL_CTX) -> libc::c_long { + unsafe { + sys::SSL_CTX_ctrl( + ctx as *mut _, + SSL_CTRL_SESS_CACHE_FULL, + 0, + std::ptr::null_mut(), + ) + } + } + + // DH parameters functions + unsafe extern "C" { + fn PEM_read_DHparams( + fp: *mut libc::FILE, + x: *mut *mut sys::DH, + cb: *mut libc::c_void, + u: *mut libc::c_void, + ) -> *mut sys::DH; + } + + // OpenSSL BIO helper functions + // These are typically macros in OpenSSL, implemented via BIO_ctrl + const BIO_CTRL_PENDING: libc::c_int = 10; + const BIO_CTRL_SET_EOF: libc::c_int = 2; + + #[allow(non_snake_case)] + unsafe fn BIO_ctrl_pending(bio: *mut sys::BIO) -> usize { + unsafe { sys::BIO_ctrl(bio, BIO_CTRL_PENDING, 0, std::ptr::null_mut()) as usize } + } + + #[allow(non_snake_case)] + unsafe fn BIO_set_mem_eof_return(bio: *mut sys::BIO, eof: libc::c_int) -> libc::c_int { + unsafe { + sys::BIO_ctrl( + bio, + BIO_CTRL_SET_EOF, + eof as libc::c_long, + std::ptr::null_mut(), + ) as libc::c_int + } + } + + #[allow(non_snake_case)] + unsafe fn BIO_clear_retry_flags(bio: *mut sys::BIO) { + unsafe { + sys::BIO_clear_flags(bio, sys::BIO_FLAGS_RWS | sys::BIO_FLAGS_SHOULD_RETRY); + } + } + + impl Constructor for PySslMemoryBio { + type Args = (); + + fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + unsafe { + let bio = sys::BIO_new(sys::BIO_s_mem()); + if bio.is_null() { + return Err(vm.new_memory_error("failed to allocate BIO".to_owned())); + } + + sys::BIO_set_retry_read(bio); + BIO_set_mem_eof_return(bio, -1); + + PySslMemoryBio { + bio, + eof_written: AtomicCell::new(false), + } + .into_ref_with_type(vm, cls) + .map(Into::into) + } + } + } + + #[pyclass(flags(IMMUTABLETYPE), with(Constructor))] + impl PySslMemoryBio { + #[pygetset] + fn pending(&self) -> usize { + unsafe { BIO_ctrl_pending(self.bio) } + } + + #[pygetset] + fn eof(&self) -> bool { + let pending = unsafe { BIO_ctrl_pending(self.bio) }; + pending == 0 && self.eof_written.load() + } + + #[pymethod] + fn read(&self, size: OptionalArg<i32>, vm: &VirtualMachine) -> PyResult<Vec<u8>> { + unsafe { + let avail = BIO_ctrl_pending(self.bio).min(i32::MAX as usize) as i32; + let len = size.unwrap_or(-1); + let len = if len < 0 || len > avail { avail } else { len }; + + // Check if EOF has been written and no data available + // This matches CPython's behavior where read() returns b'' when EOF is set + if len == 0 && self.eof_written.load() { + return Ok(Vec::new()); + } + + if len == 0 { + // No data available and no EOF - would block + // Call BIO_read() to get the proper error (SSL_ERROR_WANT_READ) + let mut test_buf = [0u8; 1]; + let nbytes = sys::BIO_read(self.bio, test_buf.as_mut_ptr() as *mut _, 1); + if nbytes < 0 { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + // Shouldn't reach here, but if we do, return what we got + return Ok(test_buf[..nbytes as usize].to_vec()); + } + + let mut buf = vec![0u8; len as usize]; + let nbytes = sys::BIO_read(self.bio, buf.as_mut_ptr() as *mut _, len); + + if nbytes < 0 { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + + buf.truncate(nbytes as usize); + Ok(buf) + } + } + + #[pymethod] + fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<i32> { + if self.eof_written.load() { + return Err(vm.new_exception_msg( + PySslError::class(&vm.ctx).to_owned(), + "cannot write() after write_eof()".to_owned(), + )); + } + + data.with_ref(|buf| unsafe { + if buf.len() > i32::MAX as usize { + return Err( + vm.new_overflow_error(format!("string longer than {} bytes", i32::MAX)) + ); + } + + let nbytes = sys::BIO_write(self.bio, buf.as_ptr() as *const _, buf.len() as i32); + if nbytes < 0 { + return Err(convert_openssl_error(vm, ErrorStack::get())); + } + + Ok(nbytes) + }) + } + + #[pymethod] + fn write_eof(&self) { + self.eof_written.store(true); + unsafe { + BIO_clear_retry_flags(self.bio); + BIO_set_mem_eof_return(self.bio, 0); + } + } + } + + #[pyclass(flags(IMMUTABLETYPE), with(Comparable))] + impl PySslSession { + #[pygetset] + fn time(&self) -> i64 { + unsafe { + #[cfg(ossl330)] + { + sys::SSL_SESSION_get_time(self.session) as i64 + } + #[cfg(not(ossl330))] + { + sys::SSL_SESSION_get_time(self.session) as i64 + } + } + } + + #[pygetset] + fn timeout(&self) -> i64 { + unsafe { sys::SSL_SESSION_get_timeout(self.session) as i64 } + } + + #[pygetset] + fn ticket_lifetime_hint(&self) -> u64 { + // SSL_SESSION_get_ticket_lifetime_hint available in OpenSSL 1.1.0+ + #[cfg(ossl110)] + { + unsafe { SSL_SESSION_get_ticket_lifetime_hint(self.session) as u64 } + } + #[cfg(not(ossl110))] + { + // Not available in older OpenSSL versions + 0 + } + } + + #[pygetset] + fn id(&self, vm: &VirtualMachine) -> PyBytesRef { + unsafe { + let mut len: libc::c_uint = 0; + let id_ptr = sys::SSL_SESSION_get_id(self.session, &mut len); + let id_slice = std::slice::from_raw_parts(id_ptr, len as usize); + vm.ctx.new_bytes(id_slice.to_vec()) + } + } + + #[pygetset] + fn has_ticket(&self) -> bool { + // SSL_SESSION_has_ticket available in OpenSSL 1.1.0+ + #[cfg(ossl110)] + { + unsafe { SSL_SESSION_has_ticket(self.session) != 0 } + } + #[cfg(not(ossl110))] + { + // Not available in older OpenSSL versions + false + } + } + } + + #[track_caller] + pub(crate) fn convert_openssl_error( + vm: &VirtualMachine, + err: ErrorStack, + ) -> PyBaseExceptionRef { + match err.errors().last() { + Some(e) => { + // Check if this is a system library error (errno-based) + let lib = sys::ERR_GET_LIB(e.code()); + + if lib == sys::ERR_LIB_SYS { + // A system error is being reported; reason is set to errno + let reason = sys::ERR_GET_REASON(e.code()); + + // errno 2 = ENOENT = FileNotFoundError + let exc_type = if reason == 2 { + vm.ctx.exceptions.file_not_found_error.to_owned() + } else { + vm.ctx.exceptions.os_error.to_owned() + }; + let exc = vm.new_exception(exc_type, vec![vm.ctx.new_int(reason).into()]); + // Set errno attribute explicitly + let _ = exc + .as_object() + .set_attr("errno", vm.ctx.new_int(reason), vm); + return exc; + } + + let caller = std::panic::Location::caller(); + let (file, line) = (caller.file(), caller.line()); + let file = file + .rsplit_once(&['/', '\\'][..]) + .map_or(file, |(_, basename)| basename); + + // Get error codes - same approach as CPython + let lib = sys::ERR_GET_LIB(e.code()); + let reason = sys::ERR_GET_REASON(e.code()); + + // Look up error mnemonic from our static tables + // CPython uses dict lookup: err_codes_to_names[(lib, reason)] + let key = super::ssl_data::encode_error_key(lib, reason); + let errstr = super::ssl_data::ERROR_CODES + .get(&key) + .copied() + .or_else(|| { + // Fallback: use OpenSSL's error string + e.reason() + }) + .unwrap_or("unknown error"); + + // Check if this is a certificate verification error + // ERR_LIB_SSL = 20 (from _ssl_data_300.h) + // SSL_R_CERTIFICATE_VERIFY_FAILED = 134 (from _ssl_data_300.h) + let is_cert_verify_error = lib == 20 && reason == 134; + + // Look up library name from our static table + // CPython uses: lib_codes_to_names[lib] + let lib_name = super::ssl_data::LIBRARY_CODES.get(&(lib as u32)).copied(); + + // Use SSLCertVerificationError for certificate verification failures + let cls = if is_cert_verify_error { + PySslCertVerificationError::class(&vm.ctx).to_owned() + } else { + PySslError::class(&vm.ctx).to_owned() + }; + + // Build message + let msg = if let Some(lib_str) = lib_name { + format!("[{lib_str}] {errstr} ({file}:{line})") + } else { + format!("{errstr} ({file}:{line})") + }; + + // Create exception instance + let reason = sys::ERR_GET_REASON(e.code()); + let exc = vm.new_exception( + cls, + vec![vm.ctx.new_int(reason).into(), vm.ctx.new_str(msg).into()], + ); + + // Set attributes on instance, not class + let exc_obj: PyObjectRef = exc.into(); + + // Set reason attribute (always set, even if just the error string) + let reason_value = vm.ctx.new_str(errstr); + let _ = exc_obj.set_attr("reason", reason_value, vm); + + // Set library attribute (None if not available) + let library_value: PyObjectRef = if let Some(lib_str) = lib_name { + vm.ctx.new_str(lib_str).into() + } else { + vm.ctx.none() + }; + let _ = exc_obj.set_attr("library", library_value, vm); + + // For SSLCertVerificationError, set verify_code and verify_message + // Note: These will be set to None here, and can be updated by the caller + // if they have access to the SSL object + if is_cert_verify_error { + let _ = exc_obj.set_attr("verify_code", vm.ctx.none(), vm); + let _ = exc_obj.set_attr("verify_message", vm.ctx.none(), vm); + } + + // Convert back to PyBaseExceptionRef + exc_obj.downcast().expect( + "exc_obj is created as PyBaseExceptionRef and must downcast successfully", + ) + } + None => { + let cls = PySslError::class(&vm.ctx).to_owned(); + vm.new_exception_empty(cls) + } + } + } + + // Helper function to set verify_code and verify_message on SSLCertVerificationError + fn set_verify_error_info( + exc: &PyBaseExceptionRef, + ssl_ptr: *const sys::SSL, + vm: &VirtualMachine, + ) { + // Get verify result + let verify_code = unsafe { sys::SSL_get_verify_result(ssl_ptr) }; + let verify_code_obj = vm.ctx.new_int(verify_code); + + // Get verify message + let verify_message = unsafe { + let verify_str = sys::X509_verify_cert_error_string(verify_code); + if verify_str.is_null() { + vm.ctx.none() + } else { + let c_str = std::ffi::CStr::from_ptr(verify_str); + vm.ctx.new_str(c_str.to_string_lossy()).into() + } + }; + + let exc_obj = exc.as_object(); + let _ = exc_obj.set_attr("verify_code", verify_code_obj, vm); + let _ = exc_obj.set_attr("verify_message", verify_message, vm); + } + #[track_caller] + fn convert_ssl_error( + vm: &VirtualMachine, + e: impl std::borrow::Borrow<ssl::Error>, + ) -> PyBaseExceptionRef { + let e = e.borrow(); + let (cls, msg) = match e.code() { + ssl::ErrorCode::WANT_READ => ( + PySslWantReadError::class(&vm.ctx).to_owned(), + "The operation did not complete (read)", + ), + ssl::ErrorCode::WANT_WRITE => ( + PySslWantWriteError::class(&vm.ctx).to_owned(), + "The operation did not complete (write)", + ), + ssl::ErrorCode::SYSCALL => match e.io_error() { + Some(io_err) => return io_err.to_pyexception(vm), + // When no I/O error and OpenSSL error queue is empty, + // this is an EOF in violation of protocol -> SSLEOFError + // Need to set args[0] = SSL_ERROR_EOF for suppress_ragged_eofs check + None => { + return vm.new_exception( + PySslEOFError::class(&vm.ctx).to_owned(), + vec![ + vm.ctx.new_int(SSL_ERROR_EOF).into(), + vm.ctx + .new_str("EOF occurred in violation of protocol") + .into(), + ], + ); + } + }, + ssl::ErrorCode::SSL => { + // Check for OpenSSL 3.0 SSL_R_UNEXPECTED_EOF_WHILE_READING + if let Some(ssl_err) = e.ssl_error() { + // In OpenSSL 3.0+, unexpected EOF is reported as SSL_ERROR_SSL + // with this specific reason code instead of SSL_ERROR_SYSCALL + unsafe { + let err_code = sys::ERR_peek_last_error(); + let reason = sys::ERR_GET_REASON(err_code); + let lib = sys::ERR_GET_LIB(err_code); + if lib == ERR_LIB_SSL && reason == SSL_R_UNEXPECTED_EOF_WHILE_READING { + return vm.new_exception( + PySslEOFError::class(&vm.ctx).to_owned(), + vec![ + vm.ctx.new_int(SSL_ERROR_EOF).into(), + vm.ctx + .new_str("EOF occurred in violation of protocol") + .into(), + ], + ); + } + } + return convert_openssl_error(vm, ssl_err.clone()); + } + ( + PySslError::class(&vm.ctx).to_owned(), + "A failure in the SSL library occurred", + ) + } + _ => ( + PySslError::class(&vm.ctx).to_owned(), + "A failure in the SSL library occurred", + ), + }; + vm.new_exception_msg(cls, msg.to_owned()) + } + + // SSL_FILETYPE_ASN1 part of _add_ca_certs in CPython + fn x509_stack_from_der(der: &[u8]) -> Result<Vec<X509>, ErrorStack> { + unsafe { + openssl::init(); + let bio = bio::MemBioSlice::new(der)?; + + let mut certs = vec![]; + + loop { + let cert = sys::d2i_X509_bio(bio.as_ptr(), std::ptr::null_mut()); + if cert.is_null() { + break; + } + certs.push(X509::from_ptr(cert)); + } + + if certs.is_empty() { + // No certificates loaded at all + return Err(ErrorStack::get()); + } + + // Successfully loaded at least one certificate from DER data. + // Clear any trailing errors from EOF. + // CPython clears errors when: + // - DER: was_bio_eof is set (EOF reached) + // - PEM: PEM_R_NO_START_LINE error (normal EOF) + // Both cases mean successful completion with loaded certs. + eprintln!( + "[x509_stack_from_der] SUCCESS: Clearing errors and returning {} certs", + certs.len() + ); + sys::ERR_clear_error(); + Ok(certs) + } + } + + type CipherTuple = (&'static str, &'static str, i32); + + fn cipher_to_tuple(cipher: &ssl::SslCipherRef) -> CipherTuple { + (cipher.name(), cipher.version(), cipher.bits().secret) + } + + fn cipher_description(cipher: *const sys::SSL_CIPHER) -> String { + unsafe { + // SSL_CIPHER_description writes up to 128 bytes + let mut buf = vec![0u8; 256]; + let result = sys::SSL_CIPHER_description( + cipher, + buf.as_mut_ptr() as *mut libc::c_char, + buf.len() as i32, + ); + if result.is_null() { + return String::from("No description available"); + } + // Find the null terminator + let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len()); + String::from_utf8_lossy(&buf[..len]).trim().to_string() + } + } + + impl Read for SocketStream { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + let mut socket: &PySocket = &self.0; + socket.read(buf) + } + } + + impl Write for SocketStream { + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { + let mut socket: &PySocket = &self.0; + socket.write(buf) + } + fn flush(&mut self) -> std::io::Result<()> { + let mut socket: &PySocket = &self.0; + socket.flush() + } + } + + #[cfg(target_os = "android")] + mod android { + use super::convert_openssl_error; + use crate::vm::{VirtualMachine, builtins::PyBaseExceptionRef}; + use openssl::{ + ssl::SslContextBuilder, + x509::{X509, store::X509StoreBuilder}, + }; + use std::{ + fs::{File, read_dir}, + io::Read, + path::Path, + }; + + static CERT_DIR: &'static str = "/system/etc/security/cacerts"; + + pub(super) fn load_client_ca_list( + vm: &VirtualMachine, + b: &mut SslContextBuilder, + ) -> Result<(), PyBaseExceptionRef> { + let root = Path::new(CERT_DIR); + if !root.is_dir() { + return Err(vm.new_exception_msg( + vm.ctx.exceptions.file_not_found_error.to_owned(), + CERT_DIR.to_string(), + )); + } + + let mut combined_pem = String::new(); + let entries = read_dir(root) + .map_err(|err| vm.new_os_error(format!("read cert root: {}", err)))?; + for entry in entries { + let entry = + entry.map_err(|err| vm.new_os_error(format!("iter cert root: {}", err)))?; + + let path = entry.path(); + if !path.is_file() { + continue; + } + + File::open(&path) + .and_then(|mut file| file.read_to_string(&mut combined_pem)) + .map_err(|err| { + vm.new_os_error(format!("open cert file {}: {}", path.display(), err)) + })?; + + combined_pem.push('\n'); + } + + let mut store_b = + X509StoreBuilder::new().map_err(|err| convert_openssl_error(vm, err))?; + let x509_vec = X509::stack_from_pem(combined_pem.as_bytes()) + .map_err(|err| convert_openssl_error(vm, err))?; + for x509 in x509_vec { + store_b + .add_cert(x509) + .map_err(|err| convert_openssl_error(vm, err))?; + } + b.set_cert_store(store_b.build()); + + Ok(()) + } + } +} + +#[cfg(not(ossl101))] +#[pymodule(sub)] +mod ossl101 {} + +#[cfg(not(ossl111))] +#[pymodule(sub)] +mod ossl111 {} + +#[cfg(not(windows))] +#[pymodule(sub)] +mod windows {} + +#[allow(non_upper_case_globals)] +#[cfg(ossl101)] +#[pymodule(sub)] +mod ossl101 { + #[pyattr] + use openssl_sys::{ + SSL_OP_NO_COMPRESSION as OP_NO_COMPRESSION, SSL_OP_NO_TLSv1_1 as OP_NO_TLSv1_1, + SSL_OP_NO_TLSv1_2 as OP_NO_TLSv1_2, + }; +} + +#[allow(non_upper_case_globals)] +#[cfg(ossl111)] +#[pymodule(sub)] +mod ossl111 { + #[pyattr] + use openssl_sys::SSL_OP_NO_TLSv1_3 as OP_NO_TLSv1_3; +} + +#[cfg(windows)] +#[pymodule(sub)] +mod windows { + use crate::{ + common::ascii, + vm::{ + PyObjectRef, PyPayload, PyResult, VirtualMachine, + builtins::{PyFrozenSet, PyStrRef}, + convert::ToPyException, + }, + }; + + #[pyfunction] + fn enum_certificates(store_name: PyStrRef, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> { + use schannel::{RawPointer, cert_context::ValidUses, cert_store::CertStore}; + use windows_sys::Win32::Security::Cryptography; + + // TODO: check every store for it, not just 2 of them: + // https://github.com/python/cpython/blob/3.8/Modules/_ssl.c#L5603-L5610 + let open_fns = [CertStore::open_current_user, CertStore::open_local_machine]; + let stores = open_fns + .iter() + .filter_map(|open| open(store_name.as_str()).ok()) + .collect::<Vec<_>>(); + let certs = stores.iter().flat_map(|s| s.certs()).map(|c| { + let cert = vm.ctx.new_bytes(c.to_der().to_owned()); + let enc_type = unsafe { + let ptr = c.as_ptr() as *const Cryptography::CERT_CONTEXT; + (*ptr).dwCertEncodingType + }; + let enc_type = match enc_type { + Cryptography::X509_ASN_ENCODING => vm.new_pyobj(ascii!("x509_asn")), + Cryptography::PKCS_7_ASN_ENCODING => vm.new_pyobj(ascii!("pkcs_7_asn")), + other => vm.new_pyobj(other), + }; + let usage: PyObjectRef = match c.valid_uses().map_err(|e| e.to_pyexception(vm))? { + ValidUses::All => vm.ctx.new_bool(true).into(), + ValidUses::Oids(oids) => PyFrozenSet::from_iter( + vm, + oids.into_iter().map(|oid| vm.ctx.new_str(oid).into()), + )? + .into_ref(&vm.ctx) + .into(), + }; + Ok(vm.new_tuple((cert, enc_type, usage)).into()) + }); + let certs: Vec<PyObjectRef> = certs.collect::<PyResult<Vec<_>>>()?; + Ok(certs) + } +} + +mod bio { + //! based off rust-openssl's private `bio` module + + use libc::c_int; + use openssl::error::ErrorStack; + use openssl_sys as sys; + use std::marker::PhantomData; + + pub struct MemBioSlice<'a>(*mut sys::BIO, PhantomData<&'a [u8]>); + + impl Drop for MemBioSlice<'_> { + fn drop(&mut self) { + unsafe { + sys::BIO_free_all(self.0); + } + } + } + + impl<'a> MemBioSlice<'a> { + pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> { + openssl::init(); + + assert!(buf.len() <= c_int::MAX as usize); + let bio = unsafe { sys::BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int) }; + if bio.is_null() { + return Err(ErrorStack::get()); + } + + Ok(MemBioSlice(bio, PhantomData)) + } + + pub fn as_ptr(&self) -> *mut sys::BIO { + self.0 + } + } +} diff --git a/stdlib/src/openssl/cert.rs b/stdlib/src/openssl/cert.rs new file mode 100644 index 00000000000..1139f0e26f0 --- /dev/null +++ b/stdlib/src/openssl/cert.rs @@ -0,0 +1,229 @@ +pub(super) use ssl_cert::{PySSLCertificate, cert_to_certificate, cert_to_py, obj2txt}; + +// Certificate type for SSL module + +#[pymodule(sub)] +pub(crate) mod ssl_cert { + use crate::{ + common::ascii, + vm::{ + PyObjectRef, PyPayload, PyResult, VirtualMachine, + convert::{ToPyException, ToPyObject}, + function::{FsPath, OptionalArg}, + }, + }; + use foreign_types_shared::ForeignTypeRef; + use openssl::{ + asn1::Asn1ObjectRef, + x509::{self, X509, X509Ref}, + }; + use openssl_sys as sys; + use std::fmt; + + // Import constants and error converter from _ssl module + use crate::openssl::_ssl::{ENCODING_DER, ENCODING_PEM, convert_openssl_error}; + + pub(crate) fn obj2txt(obj: &Asn1ObjectRef, no_name: bool) -> Option<String> { + let no_name = i32::from(no_name); + let ptr = obj.as_ptr(); + let b = unsafe { + let buflen = sys::OBJ_obj2txt(std::ptr::null_mut(), 0, ptr, no_name); + assert!(buflen >= 0); + if buflen == 0 { + return None; + } + let buflen = buflen as usize; + let mut buf = Vec::<u8>::with_capacity(buflen + 1); + let ret = sys::OBJ_obj2txt( + buf.as_mut_ptr() as *mut libc::c_char, + buf.capacity() as _, + ptr, + no_name, + ); + assert!(ret >= 0); + // SAFETY: OBJ_obj2txt initialized the buffer successfully + buf.set_len(buflen); + buf + }; + let s = String::from_utf8(b) + .unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned()); + Some(s) + } + + #[pyattr] + #[pyclass(module = "ssl", name = "Certificate")] + #[derive(PyPayload)] + pub(crate) struct PySSLCertificate { + cert: X509, + } + + impl fmt::Debug for PySSLCertificate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("Certificate") + } + } + + #[pyclass] + impl PySSLCertificate { + #[pymethod] + fn public_bytes( + &self, + format: OptionalArg<i32>, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + let format = format.unwrap_or(ENCODING_PEM); + + match format { + ENCODING_DER => { + // DER encoding + let der = self + .cert + .to_der() + .map_err(|e| convert_openssl_error(vm, e))?; + Ok(vm.ctx.new_bytes(der).into()) + } + ENCODING_PEM => { + // PEM encoding + let pem = self + .cert + .to_pem() + .map_err(|e| convert_openssl_error(vm, e))?; + Ok(vm.ctx.new_bytes(pem).into()) + } + _ => Err(vm.new_value_error("Unsupported format")), + } + } + + #[pymethod] + fn get_info(&self, vm: &VirtualMachine) -> PyResult { + cert_to_dict(vm, &self.cert) + } + } + + fn name_to_py(vm: &VirtualMachine, name: &x509::X509NameRef) -> PyResult { + let list = name + .entries() + .map(|entry| { + let txt = obj2txt(entry.object(), false).to_pyobject(vm); + let asn1_str = entry.data(); + let data_bytes = asn1_str.as_slice(); + let data = match std::str::from_utf8(data_bytes) { + Ok(s) => vm.ctx.new_str(s.to_owned()), + Err(_) => vm + .ctx + .new_str(String::from_utf8_lossy(data_bytes).into_owned()), + }; + Ok(vm.new_tuple(((txt, data),)).into()) + }) + .collect::<Result<_, _>>()?; + Ok(vm.ctx.new_tuple(list).into()) + } + + // Helper to convert X509 to dict (for getpeercert with binary=False) + fn cert_to_dict(vm: &VirtualMachine, cert: &X509Ref) -> PyResult { + let dict = vm.ctx.new_dict(); + + dict.set_item("subject", name_to_py(vm, cert.subject_name())?, vm)?; + dict.set_item("issuer", name_to_py(vm, cert.issuer_name())?, vm)?; + // X.509 version: OpenSSL uses 0-based (0=v1, 1=v2, 2=v3) but Python uses 1-based (1=v1, 2=v2, 3=v3) + dict.set_item("version", vm.new_pyobj(cert.version() + 1), vm)?; + + let serial_num = cert + .serial_number() + .to_bn() + .and_then(|bn| bn.to_hex_str()) + .map_err(|e| convert_openssl_error(vm, e))?; + dict.set_item( + "serialNumber", + vm.ctx.new_str(serial_num.to_owned()).into(), + vm, + )?; + + dict.set_item( + "notBefore", + vm.ctx.new_str(cert.not_before().to_string()).into(), + vm, + )?; + dict.set_item( + "notAfter", + vm.ctx.new_str(cert.not_after().to_string()).into(), + vm, + )?; + + if let Some(names) = cert.subject_alt_names() { + let san: Vec<PyObjectRef> = names + .iter() + .map(|gen_name| { + if let Some(email) = gen_name.email() { + vm.new_tuple((ascii!("email"), email)).into() + } else if let Some(dnsname) = gen_name.dnsname() { + vm.new_tuple((ascii!("DNS"), dnsname)).into() + } else if let Some(ip) = gen_name.ipaddress() { + // Parse IP address properly (IPv4 or IPv6) + let ip_str = if ip.len() == 4 { + // IPv4 + format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]) + } else if ip.len() == 16 { + // IPv6 - format with all zeros visible (not compressed) + let ip_addr = std::net::Ipv6Addr::from(ip[0..16]); + let s = ip_addr.segments(); + format!( + "{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}", + s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7] + ) + } else { + // Fallback for unexpected length + String::from_utf8_lossy(ip).into_owned() + }; + vm.new_tuple((ascii!("IP Address"), ip_str)).into() + } else if let Some(uri) = gen_name.uri() { + vm.new_tuple((ascii!("URI"), uri)).into() + } else { + // Handle DirName, Registered ID, and othername + // Check if this is a directory name + if let Some(dirname) = gen_name.directory_name() + && let Ok(py_name) = name_to_py(vm, dirname) + { + return vm.new_tuple((ascii!("DirName"), py_name)).into(); + } + + // TODO: Handle Registered ID (GEN_RID) + // CPython implementation uses i2t_ASN1_OBJECT to convert OID + // This requires accessing GENERAL_NAME union which is complex in Rust + // For now, we return <unsupported> for unhandled types + + // For othername and other unsupported types + vm.new_tuple((ascii!("othername"), ascii!("<unsupported>"))) + .into() + } + }) + .collect(); + dict.set_item("subjectAltName", vm.ctx.new_tuple(san).into(), vm)?; + }; + + Ok(dict.into()) + } + + // Helper to create Certificate object from X509 + pub(crate) fn cert_to_certificate(vm: &VirtualMachine, cert: X509) -> PyResult { + Ok(PySSLCertificate { cert }.into_ref(&vm.ctx).into()) + } + + // For getpeercert() - returns bytes or dict depending on binary flag + pub(crate) fn cert_to_py(vm: &VirtualMachine, cert: &X509Ref, binary: bool) -> PyResult { + if binary { + let b = cert.to_der().map_err(|e| convert_openssl_error(vm, e))?; + Ok(vm.ctx.new_bytes(b).into()) + } else { + cert_to_dict(vm, cert) + } + } + + #[pyfunction] + pub(crate) fn _test_decode_cert(path: FsPath, vm: &VirtualMachine) -> PyResult { + let path = path.to_path_buf(vm)?; + let pem = std::fs::read(path).map_err(|e| e.to_pyexception(vm))?; + let x509 = X509::from_pem(&pem).map_err(|e| convert_openssl_error(vm, e))?; + cert_to_py(vm, &x509, false) + } +} diff --git a/stdlib/src/ssl/ssl_data_111.rs b/stdlib/src/openssl/ssl_data_111.rs similarity index 100% rename from stdlib/src/ssl/ssl_data_111.rs rename to stdlib/src/openssl/ssl_data_111.rs diff --git a/stdlib/src/ssl/ssl_data_300.rs b/stdlib/src/openssl/ssl_data_300.rs similarity index 100% rename from stdlib/src/ssl/ssl_data_300.rs rename to stdlib/src/openssl/ssl_data_300.rs diff --git a/stdlib/src/ssl/ssl_data_31.rs b/stdlib/src/openssl/ssl_data_31.rs similarity index 100% rename from stdlib/src/ssl/ssl_data_31.rs rename to stdlib/src/openssl/ssl_data_31.rs diff --git a/stdlib/src/ssl.rs b/stdlib/src/ssl.rs index 9604999d7da..e0019ae4750 100644 --- a/stdlib/src/ssl.rs +++ b/stdlib/src/ssl.rs @@ -1,257 +1,356 @@ -// spell-checker:disable - +// spell-checker: ignore ssleof aesccm aesgcm getblocking setblocking ENDTLS + +//! Pure Rust SSL/TLS implementation using rustls +//! +//! This module provides SSL/TLS support without requiring C dependencies. +//! It implements the Python ssl module API using: +//! - rustls: TLS protocol implementation +//! - x509-parser/x509-cert: Certificate parsing +//! - ring: Cryptographic primitives +//! - rustls-platform-verifier: Platform-native certificate verification +//! +//! DO NOT add openssl dependency here. +//! +//! Warning: This library contains AI-generated code and comments. Do not trust any code or comment without verification. Please have a qualified expert review the code and remove this notice after review. + +// OID (Object Identifier) management module +mod oid; + +// Certificate operations module (parsing, validation, conversion) mod cert; -// Conditional compilation for OpenSSL version-specific error codes -cfg_if::cfg_if! { - if #[cfg(ossl310)] { - // OpenSSL 3.1.0+ - #[path = "ssl/ssl_data_31.rs"] - mod ssl_data; - } else if #[cfg(ossl300)] { - // OpenSSL 3.0.0+ - #[path = "ssl/ssl_data_300.rs"] - mod ssl_data; - } else { - // OpenSSL 1.1.1+ (fallback) - #[path = "ssl/ssl_data_111.rs"] - mod ssl_data; - } -} - -use crate::vm::{PyRef, VirtualMachine, builtins::PyModule}; -use openssl_probe::ProbeResult; +// OpenSSL compatibility layer (abstracts rustls operations) +mod compat; -pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { - // if openssl is vendored, it doesn't know the locations - // of system certificates - cache the probe result now. - #[cfg(openssl_vendored)] - LazyLock::force(&PROBE); - _ssl::make_module(vm) -} - -// define our own copy of ProbeResult so we can handle the vendor case -// easily, without having to have a bunch of cfgs -cfg_if::cfg_if! { - if #[cfg(openssl_vendored)] { - use std::sync::LazyLock; - static PROBE: LazyLock<ProbeResult> = LazyLock::new(openssl_probe::probe); - fn probe() -> &'static ProbeResult { &PROBE } - } else { - fn probe() -> &'static ProbeResult { - &ProbeResult { cert_file: None, cert_dir: None } - } - } -} +pub(crate) use _ssl::make_module; +#[allow(non_snake_case)] #[allow(non_upper_case_globals)] -#[pymodule(with(cert::ssl_cert, ossl101, ossl111, windows))] +#[pymodule] mod _ssl { - use super::{bio, probe}; use crate::{ - common::lock::{ - PyMappedRwLockReadGuard, PyMutex, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard, + common::{ + hash::PyHash, + lock::{PyMutex, PyRwLock}, }, - socket::{self, PySocket}, + socket::{PySocket, SelectKind, sock_select, timeout_error_msg}, vm::{ - AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{ - PyBaseExceptionRef, PyBytesRef, PyListRef, PyOSError, PyStrRef, PyTypeRef, PyWeak, - }, - class_or_notimplemented, - convert::ToPyException, - exceptions, - function::{ - ArgBytesLike, ArgCallable, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath, - OptionalArg, PyComparisonValue, - }, - types::{Comparable, Constructor, PyComparisonOp}, - utils::ToCString, + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, + VirtualMachine, + builtins::{PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyTypeRef}, + convert::IntoPyException, + function::{ArgBytesLike, ArgMemoryBuffer, OptionalArg, PyComparisonValue}, + stdlib::warnings, + types::{Comparable, Constructor, Hashable, PyComparisonOp, Representable}, }, }; - use crossbeam_utils::atomic::AtomicCell; - use foreign_types_shared::{ForeignType, ForeignTypeRef}; - use openssl::{ - asn1::{Asn1Object, Asn1ObjectRef}, - error::ErrorStack, - nid::Nid, - ssl::{self, SslContextBuilder, SslOptions, SslVerifyMode}, - x509::X509, - }; - use openssl_sys as sys; - use rustpython_vm::ospath::OsPath; use std::{ - ffi::CStr, - fmt, - io::{Read, Write}, - path::{Path, PathBuf}, - sync::LazyLock, - time::Instant, + collections::HashMap, + io::Write, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, + time::{Duration, SystemTime}, + }; + + // Rustls imports + use parking_lot::{Mutex as ParkingMutex, RwLock as ParkingRwLock}; + use pem_rfc7468::{LineEnding, encode_string}; + use rustls::{ + ClientConfig, ClientConnection, RootCertStore, ServerConfig, ServerConnection, + client::{ClientSessionMemoryCache, ClientSessionStore}, + crypto::SupportedKxGroup, + pki_types::{CertificateDer, CertificateRevocationListDer, PrivateKeyDer, ServerName}, + server::{ClientHello, ResolvesServerCert}, + sign::CertifiedKey, + version::{TLS12, TLS13}, }; + use sha2::{Digest, Sha256}; + + // Import certificate operations module + use super::cert; - // Import certificate types from parent module - use super::cert::{self, cert_to_certificate, cert_to_py}; - - // Re-export PySSLCertificate to make it available in the _ssl module - // It will be automatically exposed to Python via #[pyclass] - #[allow(unused_imports)] - use super::cert::PySSLCertificate; - - // Constants - #[pyattr] - use sys::{ - // TODO: so many more of these - SSL_AD_DECODE_ERROR as ALERT_DESCRIPTION_DECODE_ERROR, - SSL_AD_ILLEGAL_PARAMETER as ALERT_DESCRIPTION_ILLEGAL_PARAMETER, - SSL_AD_UNRECOGNIZED_NAME as ALERT_DESCRIPTION_UNRECOGNIZED_NAME, - // SSL_ERROR_INVALID_ERROR_CODE, - SSL_ERROR_SSL, - // SSL_ERROR_WANT_X509_LOOKUP, - SSL_ERROR_SYSCALL, - SSL_ERROR_WANT_CONNECT, - SSL_ERROR_WANT_READ, - SSL_ERROR_WANT_WRITE, - SSL_ERROR_ZERO_RETURN, - SSL_OP_CIPHER_SERVER_PREFERENCE as OP_CIPHER_SERVER_PREFERENCE, - SSL_OP_ENABLE_MIDDLEBOX_COMPAT as OP_ENABLE_MIDDLEBOX_COMPAT, - SSL_OP_LEGACY_SERVER_CONNECT as OP_LEGACY_SERVER_CONNECT, - SSL_OP_NO_SSLv2 as OP_NO_SSLv2, - SSL_OP_NO_SSLv3 as OP_NO_SSLv3, - SSL_OP_NO_TICKET as OP_NO_TICKET, - SSL_OP_NO_TLSv1 as OP_NO_TLSv1, - SSL_OP_SINGLE_DH_USE as OP_SINGLE_DH_USE, - SSL_OP_SINGLE_ECDH_USE as OP_SINGLE_ECDH_USE, - X509_V_FLAG_ALLOW_PROXY_CERTS as VERIFY_ALLOW_PROXY_CERTS, - X509_V_FLAG_CRL_CHECK as VERIFY_CRL_CHECK_LEAF, - X509_V_FLAG_PARTIAL_CHAIN as VERIFY_X509_PARTIAL_CHAIN, - X509_V_FLAG_TRUSTED_FIRST as VERIFY_X509_TRUSTED_FIRST, - X509_V_FLAG_X509_STRICT as VERIFY_X509_STRICT, + // Import OID module + use super::oid; + + // Import compat module (OpenSSL compatibility layer) + use super::compat::{ + ClientConfigOptions, MultiCertResolver, ProtocolSettings, ServerConfigOptions, SslError, + TlsConnection, create_client_config, create_server_config, curve_name_to_kx_group, + extract_cipher_info, get_cipher_encryption_desc, is_blocking_io_error, + normalize_cipher_name, ssl_do_handshake, }; - // CRL verification constants + // Type aliases for better readability + // Additional type alias for certificate/key pairs (SessionCache and SniCertName defined below) + + /// Certificate and private key pair used in SSL contexts + type CertKeyPair = (Arc<CertifiedKey>, PrivateKeyDer<'static>); + + // Constants matching Python ssl module + + // SSL/TLS Protocol versions + #[pyattr] + const PROTOCOL_TLS: i32 = 2; // Auto-negotiate best version + #[pyattr] + const PROTOCOL_SSLv23: i32 = PROTOCOL_TLS; // Alias for PROTOCOL_TLS #[pyattr] - const VERIFY_CRL_CHECK_CHAIN: libc::c_ulong = - sys::X509_V_FLAG_CRL_CHECK | sys::X509_V_FLAG_CRL_CHECK_ALL; + const PROTOCOL_TLS_CLIENT: i32 = 16; + #[pyattr] + const PROTOCOL_TLS_SERVER: i32 = 17; - // taken from CPython, should probably be kept up to date with their version if it ever changes + // Note: rustls doesn't support TLS 1.0/1.1 for security reasons + // These are defined for API compatibility but will raise errors if used #[pyattr] - const _DEFAULT_CIPHERS: &str = - "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK"; - // #[pyattr] PROTOCOL_SSLv2: u32 = SslVersion::Ssl2 as u32; // unsupported - // #[pyattr] PROTOCOL_SSLv3: u32 = SslVersion::Ssl3 as u32; + const PROTOCOL_TLSv1: i32 = 3; #[pyattr] - const PROTOCOL_SSLv23: u32 = SslVersion::Tls as u32; + const PROTOCOL_TLSv1_1: i32 = 4; #[pyattr] - const PROTOCOL_TLS: u32 = SslVersion::Tls as u32; + const PROTOCOL_TLSv1_2: i32 = 5; #[pyattr] - const PROTOCOL_TLS_CLIENT: u32 = SslVersion::TlsClient as u32; + const PROTOCOL_TLSv1_3: i32 = 6; + + // Protocol version constants for TLSVersion enum #[pyattr] - const PROTOCOL_TLS_SERVER: u32 = SslVersion::TlsServer as u32; + const PROTO_SSLv3: i32 = 0x0300; #[pyattr] - const PROTOCOL_TLSv1: u32 = SslVersion::Tls1 as u32; + const PROTO_TLSv1: i32 = 0x0301; #[pyattr] - const PROTOCOL_TLSv1_1: u32 = SslVersion::Tls1_1 as u32; + const PROTO_TLSv1_1: i32 = 0x0302; #[pyattr] - const PROTOCOL_TLSv1_2: u32 = SslVersion::Tls1_2 as u32; + const PROTO_TLSv1_2: i32 = 0x0303; #[pyattr] - const PROTO_MINIMUM_SUPPORTED: i32 = ProtoVersion::MinSupported as i32; + const PROTO_TLSv1_3: i32 = 0x0304; + + // Minimum and maximum supported protocol versions for rustls + // Use special values -2 and -1 to avoid enum name conflicts #[pyattr] - const PROTO_SSLv3: i32 = ProtoVersion::Ssl3 as i32; + const PROTO_MINIMUM_SUPPORTED: i32 = -2; // special value #[pyattr] - const PROTO_TLSv1: i32 = ProtoVersion::Tls1 as i32; + const PROTO_MAXIMUM_SUPPORTED: i32 = -1; // special value + + // Internal constants for rustls actual supported versions + // rustls only supports TLS 1.2 and TLS 1.3 + const MINIMUM_VERSION: i32 = PROTO_TLSv1_2; // 0x0303 + const MAXIMUM_VERSION: i32 = PROTO_TLSv1_3; // 0x0304 + + // Buffer sizes and limits (OpenSSL/CPython compatibility) + const PEM_BUFSIZE: usize = 1024; + // OpenSSL: ssl/ssl_local.h + const SSL3_RT_MAX_PLAIN_LENGTH: usize = 16384; + // SSL session cache size (common practice, similar to OpenSSL defaults) + const SSL_SESSION_CACHE_SIZE: usize = 256; + + // Certificate verification modes #[pyattr] - const PROTO_TLSv1_1: i32 = ProtoVersion::Tls1_1 as i32; + const CERT_NONE: i32 = 0; #[pyattr] - const PROTO_TLSv1_2: i32 = ProtoVersion::Tls1_2 as i32; + const CERT_OPTIONAL: i32 = 1; #[pyattr] - const PROTO_TLSv1_3: i32 = ProtoVersion::Tls1_3 as i32; + const CERT_REQUIRED: i32 = 2; + + // Certificate requirements #[pyattr] - const PROTO_MAXIMUM_SUPPORTED: i32 = ProtoVersion::MaxSupported as i32; + const VERIFY_DEFAULT: i32 = 0; #[pyattr] - const OP_ALL: libc::c_ulong = (sys::SSL_OP_ALL & !sys::SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) as _; + const VERIFY_CRL_CHECK_LEAF: i32 = 4; #[pyattr] - const HAS_TLS_UNIQUE: bool = true; + const VERIFY_CRL_CHECK_CHAIN: i32 = 12; #[pyattr] - const CERT_NONE: u32 = CertRequirements::None as u32; + const VERIFY_X509_STRICT: i32 = 32; #[pyattr] - const CERT_OPTIONAL: u32 = CertRequirements::Optional as u32; + const VERIFY_ALLOW_PROXY_CERTS: i32 = 64; #[pyattr] - const CERT_REQUIRED: u32 = CertRequirements::Required as u32; + const VERIFY_X509_TRUSTED_FIRST: i32 = 32768; #[pyattr] - const VERIFY_DEFAULT: u32 = 0; + const VERIFY_X509_PARTIAL_CHAIN: i32 = 0x80000; + + // Options (OpenSSL-compatible flags, mostly no-op in rustls) #[pyattr] - const SSL_ERROR_EOF: u32 = 8; // custom for python + const OP_NO_SSLv2: i32 = 0x00000000; // Not supported anyway #[pyattr] - const HAS_SNI: bool = true; + const OP_NO_SSLv3: i32 = 0x02000000; #[pyattr] - const HAS_ECDH: bool = true; + const OP_NO_TLSv1: i32 = 0x04000000; #[pyattr] - const HAS_NPN: bool = false; + const OP_NO_TLSv1_1: i32 = 0x10000000; #[pyattr] - const HAS_ALPN: bool = true; + const OP_NO_TLSv1_2: i32 = 0x08000000; #[pyattr] - const HAS_SSLv2: bool = false; + const OP_NO_TLSv1_3: i32 = 0x20000000; #[pyattr] - const HAS_SSLv3: bool = false; + const OP_NO_COMPRESSION: i32 = 0x00020000; + #[pyattr] + const OP_CIPHER_SERVER_PREFERENCE: i32 = 0x00400000; + #[pyattr] + const OP_SINGLE_DH_USE: i32 = 0x00000000; // No-op in rustls #[pyattr] - const HAS_TLSv1: bool = true; + const OP_SINGLE_ECDH_USE: i32 = 0x00000000; // No-op in rustls #[pyattr] - const HAS_TLSv1_1: bool = true; + const OP_NO_TICKET: i32 = 0x00004000; #[pyattr] - const HAS_TLSv1_2: bool = true; + const OP_LEGACY_SERVER_CONNECT: i32 = 0x00000004; #[pyattr] - const HAS_TLSv1_3: bool = cfg!(ossl111); + const OP_NO_RENEGOTIATION: i32 = 0x40000000; #[pyattr] - const HAS_PSK: bool = true; + const OP_IGNORE_UNEXPECTED_EOF: i32 = 0x00000080; + #[pyattr] + const OP_ENABLE_MIDDLEBOX_COMPAT: i32 = 0x00100000; + #[pyattr] + const OP_ALL: i32 = 0x00000BFB; // Combined "safe" options (reduced for i32, excluding OP_LEGACY_SERVER_CONNECT for OpenSSL 3.0.0+ compatibility) - // Encoding constants for Certificate.public_bytes() + // Error types + #[pyattr] + const SSL_ERROR_NONE: i32 = 0; + #[pyattr] + const SSL_ERROR_SSL: i32 = 1; #[pyattr] - pub(crate) const ENCODING_PEM: i32 = sys::X509_FILETYPE_PEM; + const SSL_ERROR_WANT_READ: i32 = 2; #[pyattr] - pub(crate) const ENCODING_DER: i32 = sys::X509_FILETYPE_ASN1; + const SSL_ERROR_WANT_WRITE: i32 = 3; #[pyattr] - const ENCODING_PEM_AUX: i32 = sys::X509_FILETYPE_PEM + 0x100; + const SSL_ERROR_WANT_X509_LOOKUP: i32 = 4; + #[pyattr] + const SSL_ERROR_SYSCALL: i32 = 5; + #[pyattr] + const SSL_ERROR_ZERO_RETURN: i32 = 6; + #[pyattr] + const SSL_ERROR_WANT_CONNECT: i32 = 7; + #[pyattr] + const SSL_ERROR_EOF: i32 = 8; + #[pyattr] + const SSL_ERROR_INVALID_ERROR_CODE: i32 = 10; - // OpenSSL error codes for unexpected EOF detection - const ERR_LIB_SSL: i32 = 20; - const SSL_R_UNEXPECTED_EOF_WHILE_READING: i32 = 294; + // Alert types (matching _TLSAlertType enum) + #[pyattr] + const ALERT_DESCRIPTION_CLOSE_NOTIFY: i32 = 0; + #[pyattr] + const ALERT_DESCRIPTION_UNEXPECTED_MESSAGE: i32 = 10; + #[pyattr] + const ALERT_DESCRIPTION_BAD_RECORD_MAC: i32 = 20; + #[pyattr] + const ALERT_DESCRIPTION_DECRYPTION_FAILED: i32 = 21; + #[pyattr] + const ALERT_DESCRIPTION_RECORD_OVERFLOW: i32 = 22; + #[pyattr] + const ALERT_DESCRIPTION_DECOMPRESSION_FAILURE: i32 = 30; + #[pyattr] + const ALERT_DESCRIPTION_HANDSHAKE_FAILURE: i32 = 40; + #[pyattr] + const ALERT_DESCRIPTION_NO_CERTIFICATE: i32 = 41; + #[pyattr] + const ALERT_DESCRIPTION_BAD_CERTIFICATE: i32 = 42; + #[pyattr] + const ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE: i32 = 43; + #[pyattr] + const ALERT_DESCRIPTION_CERTIFICATE_REVOKED: i32 = 44; + #[pyattr] + const ALERT_DESCRIPTION_CERTIFICATE_EXPIRED: i32 = 45; + #[pyattr] + const ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN: i32 = 46; + #[pyattr] + const ALERT_DESCRIPTION_ILLEGAL_PARAMETER: i32 = 47; + #[pyattr] + const ALERT_DESCRIPTION_UNKNOWN_CA: i32 = 48; + #[pyattr] + const ALERT_DESCRIPTION_ACCESS_DENIED: i32 = 49; + #[pyattr] + const ALERT_DESCRIPTION_DECODE_ERROR: i32 = 50; + #[pyattr] + const ALERT_DESCRIPTION_DECRYPT_ERROR: i32 = 51; + #[pyattr] + const ALERT_DESCRIPTION_EXPORT_RESTRICTION: i32 = 60; + #[pyattr] + const ALERT_DESCRIPTION_PROTOCOL_VERSION: i32 = 70; + #[pyattr] + const ALERT_DESCRIPTION_INSUFFICIENT_SECURITY: i32 = 71; + #[pyattr] + const ALERT_DESCRIPTION_INTERNAL_ERROR: i32 = 80; + #[pyattr] + const ALERT_DESCRIPTION_INAPPROPRIATE_FALLBACK: i32 = 86; + #[pyattr] + const ALERT_DESCRIPTION_USER_CANCELLED: i32 = 90; + #[pyattr] + const ALERT_DESCRIPTION_NO_RENEGOTIATION: i32 = 100; + #[pyattr] + const ALERT_DESCRIPTION_MISSING_EXTENSION: i32 = 109; + #[pyattr] + const ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION: i32 = 110; + #[pyattr] + const ALERT_DESCRIPTION_CERTIFICATE_UNOBTAINABLE: i32 = 111; + #[pyattr] + const ALERT_DESCRIPTION_UNRECOGNIZED_NAME: i32 = 112; + #[pyattr] + const ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE: i32 = 113; + #[pyattr] + const ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE: i32 = 114; + #[pyattr] + const ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY: i32 = 115; + #[pyattr] + const ALERT_DESCRIPTION_CERTIFICATE_REQUIRED: i32 = 116; + #[pyattr] + const ALERT_DESCRIPTION_NO_APPLICATION_PROTOCOL: i32 = 120; - // SSL_VERIFY constants for post-handshake authentication - #[cfg(ossl111)] - const SSL_VERIFY_POST_HANDSHAKE: libc::c_int = 0x20; + // Version info - reporting as OpenSSL 3.3.0 for compatibility + #[pyattr] + const OPENSSL_VERSION_NUMBER: i32 = 0x30300000; // OpenSSL 3.3.0 (808452096) + #[pyattr] + const OPENSSL_VERSION: &str = "OpenSSL 3.3.0 (rustls/0.23)"; + #[pyattr] + const OPENSSL_VERSION_INFO: (i32, i32, i32, i32, i32) = (3, 3, 0, 0, 15); // 3.3.0 release + #[pyattr] + const _OPENSSL_API_VERSION: (i32, i32, i32, i32, i32) = (3, 3, 0, 0, 15); // 3.3.0 release - // the openssl version from the API headers + // Default cipher list for rustls - using modern secure ciphers + #[pyattr] + const _DEFAULT_CIPHERS: &str = + "TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256"; - #[pyattr(name = "OPENSSL_VERSION")] - fn openssl_version(_vm: &VirtualMachine) -> &str { - openssl::version::version() - } - #[pyattr(name = "OPENSSL_VERSION_NUMBER")] - fn openssl_version_number(_vm: &VirtualMachine) -> i64 { - openssl::version::number() - } - #[pyattr(name = "OPENSSL_VERSION_INFO")] - fn openssl_version_info(_vm: &VirtualMachine) -> OpensslVersionInfo { - parse_version_info(openssl::version::number()) - } + // Has features + #[pyattr] + const HAS_SNI: bool = true; + #[pyattr] + const HAS_TLS_UNIQUE: bool = false; // Not supported + #[pyattr] + const HAS_ECDH: bool = true; + #[pyattr] + const HAS_NPN: bool = false; // Deprecated, use ALPN + #[pyattr] + const HAS_ALPN: bool = true; + #[pyattr] + const HAS_PSK: bool = false; // PSK not supported in rustls + #[pyattr] + const HAS_SSLv2: bool = false; + #[pyattr] + const HAS_SSLv3: bool = false; + #[pyattr] + const HAS_TLSv1: bool = false; // Not supported for security + #[pyattr] + const HAS_TLSv1_1: bool = false; // Not supported for security + #[pyattr] + const HAS_TLSv1_2: bool = true; // rustls supports TLS 1.2 + #[pyattr] + const HAS_TLSv1_3: bool = true; - #[pyattr(name = "_OPENSSL_API_VERSION")] - fn _openssl_api_version(_vm: &VirtualMachine) -> OpensslVersionInfo { - let openssl_api_version = i64::from_str_radix(env!("OPENSSL_API_VERSION"), 16) - .expect("OPENSSL_API_VERSION is malformed"); - parse_version_info(openssl_api_version) - } + // Encoding constants (matching OpenSSL) + #[pyattr] + const ENCODING_PEM: i32 = 1; + #[pyattr] + const ENCODING_DER: i32 = 2; + #[pyattr] + const ENCODING_PEM_AUX: i32 = 0x101; // PEM + 0x100 - // SSL Exception Types + // Exception types + use rustpython_vm::builtins::PyOSError; - /// An error occurred in the SSL implementation. #[pyattr] #[pyexception(name = "SSLError", base = PyOSError)] #[derive(Debug)] - pub struct PySslError {} + pub struct PySSLError {} #[pyexception] - impl PySslError { + impl PySSLError { // Returns strerror attribute if available, otherwise str(args) #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { @@ -263,890 +362,973 @@ mod _ssl { } // Otherwise return str(args) - exc.args().as_object().str(vm) + let args = exc.args(); + if args.len() == 1 { + args.as_slice()[0].str(vm) + } else { + args.as_object().str(vm) + } } } - /// A certificate could not be verified. #[pyattr] - #[pyexception(name = "SSLCertVerificationError", base = PySslError)] + #[pyexception(name = "SSLZeroReturnError", base = PySSLError)] #[derive(Debug)] - pub struct PySslCertVerificationError {} + pub struct PySSLZeroReturnError {} #[pyexception] - impl PySslCertVerificationError {} + impl PySSLZeroReturnError {} - /// SSL/TLS session closed cleanly. #[pyattr] - #[pyexception(name = "SSLZeroReturnError", base = PySslError)] + #[pyexception(name = "SSLWantReadError", base = PySSLError)] #[derive(Debug)] - pub struct PySslZeroReturnError {} + pub struct PySSLWantReadError {} #[pyexception] - impl PySslZeroReturnError {} + impl PySSLWantReadError {} - /// Non-blocking SSL socket needs to read more data. #[pyattr] - #[pyexception(name = "SSLWantReadError", base = PySslError)] + #[pyexception(name = "SSLWantWriteError", base = PySSLError)] #[derive(Debug)] - pub struct PySslWantReadError {} + pub struct PySSLWantWriteError {} #[pyexception] - impl PySslWantReadError {} + impl PySSLWantWriteError {} - /// Non-blocking SSL socket needs to write more data. #[pyattr] - #[pyexception(name = "SSLWantWriteError", base = PySslError)] + #[pyexception(name = "SSLSyscallError", base = PySSLError)] #[derive(Debug)] - pub struct PySslWantWriteError {} + pub struct PySSLSyscallError {} #[pyexception] - impl PySslWantWriteError {} + impl PySSLSyscallError {} - /// System error when attempting SSL operation. #[pyattr] - #[pyexception(name = "SSLSyscallError", base = PySslError)] + #[pyexception(name = "SSLEOFError", base = PySSLError)] #[derive(Debug)] - pub struct PySslSyscallError {} + pub struct PySSLEOFError {} #[pyexception] - impl PySslSyscallError {} + impl PySSLEOFError {} - /// SSL/TLS connection terminated abruptly. #[pyattr] - #[pyexception(name = "SSLEOFError", base = PySslError)] + #[pyexception(name = "SSLCertVerificationError", base = PySSLError)] #[derive(Debug)] - pub struct PySslEOFError {} + pub struct PySSLCertVerificationError {} #[pyexception] - impl PySslEOFError {} - - type OpensslVersionInfo = (u8, u8, u8, u8, u8); - const fn parse_version_info(mut n: i64) -> OpensslVersionInfo { - let status = (n & 0xF) as u8; - n >>= 4; - let patch = (n & 0xFF) as u8; - n >>= 8; - let fix = (n & 0xFF) as u8; - n >>= 8; - let minor = (n & 0xFF) as u8; - n >>= 8; - let major = (n & 0xFF) as u8; - (major, minor, fix, patch, status) - } - - #[derive(Copy, Clone, num_enum::IntoPrimitive, num_enum::TryFromPrimitive, PartialEq)] - #[repr(i32)] - enum SslVersion { - Ssl2, - Ssl3 = 1, - Tls, - Tls1, - Tls1_1, - Tls1_2, - TlsClient = 0x10, - TlsServer, + impl PySSLCertVerificationError {} + + // Helper functions to create SSL exceptions with proper errno attribute + pub(super) fn create_ssl_want_read_error(vm: &VirtualMachine) -> PyBaseExceptionRef { + // args = (errno, message) + vm.new_exception( + PySSLWantReadError::class(&vm.ctx).to_owned(), + vec![ + vm.ctx.new_int(SSL_ERROR_WANT_READ).into(), + vm.ctx + .new_str("The operation did not complete (read)") + .into(), + ], + ) } - #[derive(Copy, Clone, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] - #[repr(i32)] - enum ProtoVersion { - MinSupported = -2, - Ssl3 = sys::SSL3_VERSION, - Tls1 = sys::TLS1_VERSION, - Tls1_1 = sys::TLS1_1_VERSION, - Tls1_2 = sys::TLS1_2_VERSION, - #[cfg(ossl111)] - Tls1_3 = sys::TLS1_3_VERSION, - #[cfg(not(ossl111))] - Tls1_3 = 0x304, - MaxSupported = -1, + pub(super) fn create_ssl_want_write_error(vm: &VirtualMachine) -> PyBaseExceptionRef { + // args = (errno, message) + vm.new_exception( + PySSLWantWriteError::class(&vm.ctx).to_owned(), + vec![ + vm.ctx.new_int(SSL_ERROR_WANT_WRITE).into(), + vm.ctx + .new_str("The operation did not complete (write)") + .into(), + ], + ) } - #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] - #[repr(i32)] - enum CertRequirements { - None, - Optional, - Required, + pub(crate) fn create_ssl_eof_error(vm: &VirtualMachine) -> PyBaseExceptionRef { + vm.new_exception_msg( + PySSLEOFError::class(&vm.ctx).to_owned(), + "EOF occurred in violation of protocol".to_owned(), + ) } - #[derive(Debug, PartialEq)] - enum SslServerOrClient { - Client, - Server, + pub(crate) fn create_ssl_zero_return_error(vm: &VirtualMachine) -> PyBaseExceptionRef { + vm.new_exception_msg( + PySSLZeroReturnError::class(&vm.ctx).to_owned(), + "TLS/SSL connection has been closed (EOF)".to_owned(), + ) } - unsafe fn ptr2obj(ptr: *mut sys::ASN1_OBJECT) -> Option<Asn1Object> { - if ptr.is_null() { - None - } else { - Some(unsafe { Asn1Object::from_ptr(ptr) }) + /// Validate server hostname for TLS SNI + /// + /// Checks that the hostname: + /// - Is not empty + /// - Does not start with a dot + /// - Is not an IP address (SNI requires DNS names) + /// - Does not contain null bytes + /// - Does not exceed 253 characters (DNS limit) + /// + /// Returns Ok(()) if validation passes, or an appropriate error. + fn validate_hostname(hostname: &str, vm: &VirtualMachine) -> PyResult<()> { + if hostname.is_empty() { + return Err(vm.new_value_error("server_hostname cannot be an empty string")); } - } - - fn _txt2obj(s: &CStr, no_name: bool) -> Option<Asn1Object> { - unsafe { ptr2obj(sys::OBJ_txt2obj(s.as_ptr(), i32::from(no_name))) } - } - fn _nid2obj(nid: Nid) -> Option<Asn1Object> { - unsafe { ptr2obj(sys::OBJ_nid2obj(nid.as_raw())) } - } - - type PyNid = (libc::c_int, String, String, Option<String>); - fn obj2py(obj: &Asn1ObjectRef, vm: &VirtualMachine) -> PyResult<PyNid> { - let nid = obj.nid(); - let short_name = nid - .short_name() - .map_err(|_| vm.new_value_error("NID has no short name".to_owned()))? - .to_owned(); - let long_name = nid - .long_name() - .map_err(|_| vm.new_value_error("NID has no long name".to_owned()))? - .to_owned(); - Ok(( - nid.as_raw(), - short_name, - long_name, - cert::obj2txt(obj, true), - )) - } - - #[derive(FromArgs)] - struct Txt2ObjArgs { - txt: PyStrRef, - #[pyarg(any, default = false)] - name: bool, - } - #[pyfunction] - fn txt2obj(args: Txt2ObjArgs, vm: &VirtualMachine) -> PyResult<PyNid> { - _txt2obj(&args.txt.to_cstring(vm)?, !args.name) - .as_deref() - .ok_or_else(|| vm.new_value_error(format!("unknown object '{}'", args.txt))) - .and_then(|obj| obj2py(obj, vm)) - } + if hostname.starts_with('.') { + return Err(vm.new_value_error("server_hostname cannot start with a dot")); + } - #[pyfunction] - fn nid2obj(nid: libc::c_int, vm: &VirtualMachine) -> PyResult<PyNid> { - _nid2obj(Nid::from_raw(nid)) - .as_deref() - .ok_or_else(|| vm.new_value_error(format!("unknown NID {nid}"))) - .and_then(|obj| obj2py(obj, vm)) - } + if hostname.parse::<std::net::IpAddr>().is_ok() { + return Err(vm.new_value_error("server_hostname cannot be an IP address")); + } - // Lazily compute and cache cert file/dir paths - static CERT_PATHS: LazyLock<(PathBuf, PathBuf)> = LazyLock::new(|| { - fn path_from_cstr(c: &CStr) -> PathBuf { - #[cfg(unix)] - { - use std::os::unix::ffi::OsStrExt; - std::ffi::OsStr::from_bytes(c.to_bytes()).into() - } - #[cfg(windows)] - { - // Use lossy conversion for potential non-UTF8 - PathBuf::from(c.to_string_lossy().as_ref()) - } + if hostname.contains('\0') { + return Err(vm.new_type_error("embedded null character")); } - let probe = probe(); - let cert_file = probe - .cert_file - .as_ref() - .map(PathBuf::from) - .unwrap_or_else(|| { - path_from_cstr(unsafe { CStr::from_ptr(sys::X509_get_default_cert_file()) }) - }); - let cert_dir = probe - .cert_dir - .as_ref() - .map(PathBuf::from) - .unwrap_or_else(|| { - path_from_cstr(unsafe { CStr::from_ptr(sys::X509_get_default_cert_dir()) }) - }); - (cert_file, cert_dir) - }); + if hostname.len() > 253 { + return Err(vm.new_value_error("server_hostname is too long (maximum 253 characters)")); + } - fn get_cert_file_dir() -> (&'static Path, &'static Path) { - let (cert_file, cert_dir) = &*CERT_PATHS; - (cert_file.as_path(), cert_dir.as_path()) + Ok(()) } - // Lazily compute and cache cert environment variable names - static CERT_ENV_NAMES: LazyLock<(String, String)> = LazyLock::new(|| { - let cert_file_env = unsafe { CStr::from_ptr(sys::X509_get_default_cert_file_env()) } - .to_string_lossy() - .into_owned(); - let cert_dir_env = unsafe { CStr::from_ptr(sys::X509_get_default_cert_dir_env()) } - .to_string_lossy() - .into_owned(); - (cert_file_env, cert_dir_env) - }); - - #[pyfunction] - fn get_default_verify_paths( - vm: &VirtualMachine, - ) -> PyResult<(&'static str, PyObjectRef, &'static str, PyObjectRef)> { - let (cert_file_env, cert_dir_env) = &*CERT_ENV_NAMES; - let (cert_file, cert_dir) = get_cert_file_dir(); - let cert_file = OsPath::new_str(cert_file).filename(vm); - let cert_dir = OsPath::new_str(cert_dir).filename(vm); - Ok(( - cert_file_env.as_str(), - cert_file, - cert_dir_env.as_str(), - cert_dir, - )) + // SNI certificate resolver that uses shared mutable state + // The Python SNI callback updates this state, and resolve() reads from it + #[derive(Debug)] + struct SniCertResolver { + // SNI state: (certificate, server_name) + sni_state: Arc<ParkingMutex<SniCertName>>, } - #[pyfunction(name = "RAND_status")] - fn rand_status() -> i32 { - unsafe { sys::RAND_status() } - } + impl ResolvesServerCert for SniCertResolver { + fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> { + let mut state = self.sni_state.lock(); - #[pyfunction(name = "RAND_add")] - fn rand_add(string: ArgStrOrBytesLike, entropy: f64) { - let f = |b: &[u8]| { - for buf in b.chunks(libc::c_int::MAX as usize) { - unsafe { sys::RAND_add(buf.as_ptr() as *const _, buf.len() as _, entropy) } + // Extract and store SNI from client hello for later use + if let Some(sni) = client_hello.server_name() { + state.1 = Some(sni.to_string()); + } else { + state.1 = None; } - }; - f(&string.borrow_bytes()) - } - #[pyfunction(name = "RAND_bytes")] - fn rand_bytes(n: i32, vm: &VirtualMachine) -> PyResult<Vec<u8>> { - if n < 0 { - return Err(vm.new_value_error("num must be positive")); + // Return the current certificate (may have been updated by Python callback) + Some(state.0.clone()) } - let mut buf = vec![0; n as usize]; - openssl::rand::rand_bytes(&mut buf).map_err(|e| convert_openssl_error(vm, e))?; - Ok(buf) } - // Callback data stored in SSL context for SNI - struct SniCallbackData { - ssl_context: PyRef<PySslContext>, - vm_ptr: *const VirtualMachine, + // Session data structure for tracking TLS sessions + #[derive(Debug, Clone)] + struct SessionData { + #[allow(dead_code)] + server_name: String, + session_id: Vec<u8>, + creation_time: SystemTime, + lifetime: u64, } - impl Drop for SniCallbackData { - fn drop(&mut self) { - // PyRef will handle reference counting - } + // Type alias to simplify complex session cache type + type SessionCache = Arc<ParkingRwLock<HashMap<Vec<u8>, Arc<ParkingMutex<SessionData>>>>>; + + // Type alias for SNI state + type SniCertName = (Arc<CertifiedKey>, Option<String>); + + // SESSION EMULATION IMPLEMENTATION + // + // IMPORTANT: This is an EMULATION of CPython's SSL session management. + // Rustls 0.23 does NOT expose session data (ticket bytes, session IDs, etc.) + // through public APIs. All session value fields are private. + // + // LIMITATIONS: + // - Session IDs are generated from metadata (server name + timestamp hash) + // NOT actual TLS session IDs + // - Ticket data is not stored (Rustls keeps it internally) + // - Session resumption works (via Rustls's automatic mechanism) + // but we can't access the actual session state + // + // This implementation provides: + // ✓ session.id - synthetic ID based on metadata + // ✓ session.time - creation timestamp + // ✓ session.timeout - default lifetime value + // ✓ session.has_ticket - always True when session exists + // ✓ session_reused - tracked via handshake_kind() + // ✗ Actual TLS session ID/ticket data - NOT ACCESSIBLE + + // Generate a synthetic session ID from server name and timestamp + // NOTE: This is NOT the actual TLS session ID, just a unique identifier + fn generate_session_id_from_metadata(server_name: &str, time: &SystemTime) -> Vec<u8> { + let mut hasher = Sha256::new(); + hasher.update(server_name.as_bytes()); + hasher.update( + time.duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + .to_le_bytes(), + ); + hasher.finalize()[..16].to_vec() } - // Get or create an ex_data index for SNI callback data - fn get_sni_ex_data_index() -> libc::c_int { - use std::sync::LazyLock; - static SNI_EX_DATA_IDX: LazyLock<libc::c_int> = LazyLock::new(|| unsafe { - sys::SSL_get_ex_new_index( - 0, - std::ptr::null_mut(), - None, - None, - Some(sni_callback_data_free), - ) - }); - *SNI_EX_DATA_IDX + // Custom ClientSessionStore that tracks session metadata for Python access + // NOTE: This wraps ClientSessionMemoryCache and records metadata when sessions are stored + #[derive(Debug)] + struct PythonClientSessionStore { + inner: Arc<ClientSessionMemoryCache>, + session_cache: SessionCache, } - // Free function for callback data - unsafe extern "C" fn sni_callback_data_free( - _parent: *mut libc::c_void, - ptr: *mut libc::c_void, - _ad: *mut sys::CRYPTO_EX_DATA, - _idx: libc::c_int, - _argl: libc::c_long, - _argp: *mut libc::c_void, - ) { - if !ptr.is_null() { - unsafe { - let _ = Box::from_raw(ptr as *mut SniCallbackData); - } + impl ClientSessionStore for PythonClientSessionStore { + fn set_kx_hint(&self, server_name: ServerName<'static>, group: rustls::NamedGroup) { + self.inner.set_kx_hint(server_name, group); } - } - // SNI callback function called by OpenSSL - unsafe extern "C" fn _servername_callback( - ssl_ptr: *mut sys::SSL, - _al: *mut libc::c_int, - arg: *mut libc::c_void, - ) -> libc::c_int { - const SSL_TLSEXT_ERR_OK: libc::c_int = 0; - const SSL_TLSEXT_ERR_ALERT_FATAL: libc::c_int = 2; - const TLSEXT_NAMETYPE_host_name: libc::c_int = 0; - - if arg.is_null() { - return SSL_TLSEXT_ERR_OK; + fn kx_hint(&self, server_name: &ServerName<'_>) -> Option<rustls::NamedGroup> { + self.inner.kx_hint(server_name) } - unsafe { - let ctx = &*(arg as *const PySslContext); - - // Get the callback - let callback_opt = ctx.sni_callback.lock().clone(); - let Some(callback) = callback_opt else { - return SSL_TLSEXT_ERR_OK; - }; - - // Get callback data from SSL ex_data - let idx = get_sni_ex_data_index(); - let data_ptr = sys::SSL_get_ex_data(ssl_ptr, idx); - if data_ptr.is_null() { - return SSL_TLSEXT_ERR_ALERT_FATAL; - } - - let callback_data = &*(data_ptr as *const SniCallbackData); - - // SAFETY: vm_ptr is stored during wrap_socket and is valid for the lifetime - // of the SSL connection. The handshake happens synchronously in the same thread. - let vm = &*callback_data.vm_ptr; - - // Get server name - let servername = sys::SSL_get_servername(ssl_ptr, TLSEXT_NAMETYPE_host_name); - let server_name_arg = if servername.is_null() { - vm.ctx.none() - } else { - let name_cstr = std::ffi::CStr::from_ptr(servername); - match name_cstr.to_str() { - Ok(name_str) => vm.ctx.new_str(name_str).into(), - Err(_) => vm.ctx.none(), - } - }; - - // Get SSL socket from SSL ex_data (stored as PySslSocket pointer) - let ssl_socket_ptr = sys::SSL_get_ex_data(ssl_ptr, 0); // Index 0 for SSL socket - let ssl_socket_obj = if !ssl_socket_ptr.is_null() { - let ssl_socket = &*(ssl_socket_ptr as *const PySslSocket); - // Try to get owner first - ssl_socket - .owner - .read() - .as_ref() - .and_then(|weak| weak.upgrade()) - .unwrap_or_else(|| vm.ctx.none()) - } else { - vm.ctx.none() + fn set_tls12_session( + &self, + server_name: ServerName<'static>, + value: rustls::client::Tls12ClientSessionValue, + ) { + // Store in inner cache for actual resumption (Rustls handles this) + self.inner.set_tls12_session(server_name.clone(), value); + + // Record metadata in Python-accessible cache + // NOTE: We can't access value.session_id or value.ticket (private fields) + // So we generate a synthetic ID from metadata + let creation_time = SystemTime::now(); + let server_name_str = server_name.to_str(); + let session_data = SessionData { + server_name: server_name_str.as_ref().to_string(), + session_id: generate_session_id_from_metadata( + server_name_str.as_ref(), + &creation_time, + ), + creation_time, + lifetime: 7200, // TLS 1.2 default session lifetime }; - // Call the Python callback - match callback.call( - ( - ssl_socket_obj, - server_name_arg, - callback_data.ssl_context.to_owned(), - ), - vm, - ) { - Ok(_) => SSL_TLSEXT_ERR_OK, - Err(exc) => { - // Log the exception but don't propagate it - vm.run_unraisable(exc, None, vm.ctx.none()); - SSL_TLSEXT_ERR_ALERT_FATAL - } - } + let key = server_name_str.as_bytes().to_vec(); + self.session_cache + .write() + .insert(key, Arc::new(ParkingMutex::new(session_data))); } - } - #[pyfunction(name = "RAND_pseudo_bytes")] - fn rand_pseudo_bytes(n: i32, vm: &VirtualMachine) -> PyResult<(Vec<u8>, bool)> { - if n < 0 { - return Err(vm.new_value_error("num must be positive")); - } - let mut buf = vec![0; n as usize]; - let ret = unsafe { sys::RAND_bytes(buf.as_mut_ptr(), n) }; - match ret { - 0 | 1 => Ok((buf, ret == 1)), - _ => Err(convert_openssl_error(vm, ErrorStack::get())), + fn tls12_session( + &self, + server_name: &ServerName<'_>, + ) -> Option<rustls::client::Tls12ClientSessionValue> { + self.inner.tls12_session(server_name) } - } - #[pyattr] - #[pyclass(module = "ssl", name = "_SSLContext")] - #[derive(PyPayload)] - struct PySslContext { - ctx: PyRwLock<SslContextBuilder>, - check_hostname: AtomicCell<bool>, - protocol: SslVersion, - post_handshake_auth: PyMutex<bool>, - sni_callback: PyMutex<Option<PyObjectRef>>, - } + fn remove_tls12_session(&self, server_name: &ServerName<'static>) { + self.inner.remove_tls12_session(server_name); - impl fmt::Debug for PySslContext { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("_SSLContext") + // Also remove from Python cache + let key = server_name.to_str().as_bytes().to_vec(); + self.session_cache.write().remove(&key); } - } - - fn builder_as_ctx(x: &SslContextBuilder) -> &ssl::SslContextRef { - unsafe { ssl::SslContextRef::from_ptr(x.as_ptr()) } - } - impl Constructor for PySslContext { - type Args = i32; - - fn py_new(cls: PyTypeRef, proto_version: Self::Args, vm: &VirtualMachine) -> PyResult { - let proto = SslVersion::try_from(proto_version) - .map_err(|_| vm.new_value_error("invalid protocol version"))?; - let method = match proto { - // SslVersion::Ssl3 => unsafe { ssl::SslMethod::from_ptr(sys::SSLv3_method()) }, - SslVersion::Tls => ssl::SslMethod::tls(), - SslVersion::Tls1 => ssl::SslMethod::tls(), - SslVersion::Tls1_1 => ssl::SslMethod::tls(), - SslVersion::Tls1_2 => ssl::SslMethod::tls(), - SslVersion::TlsClient => ssl::SslMethod::tls_client(), - SslVersion::TlsServer => ssl::SslMethod::tls_server(), - _ => return Err(vm.new_value_error("invalid protocol version")), + fn insert_tls13_ticket( + &self, + server_name: ServerName<'static>, + value: rustls::client::Tls13ClientSessionValue, + ) { + // Store in inner cache for actual resumption (Rustls handles this) + self.inner.insert_tls13_ticket(server_name.clone(), value); + + // Record metadata in Python-accessible cache + // NOTE: We can't access value.ticket or value.lifetime_secs (private fields) + // So we use default values + let creation_time = SystemTime::now(); + let server_name_str = server_name.to_str(); + let session_data = SessionData { + server_name: server_name_str.to_string(), + session_id: generate_session_id_from_metadata( + server_name_str.as_ref(), + &creation_time, + ), + creation_time, + lifetime: 7200, // Default TLS 1.3 ticket lifetime (Rustls uses this) }; - let mut builder = - SslContextBuilder::new(method).map_err(|e| convert_openssl_error(vm, e))?; - - #[cfg(target_os = "android")] - android::load_client_ca_list(vm, &mut builder)?; - - let check_hostname = proto == SslVersion::TlsClient; - builder.set_verify(if check_hostname { - SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT - } else { - SslVerifyMode::NONE - }); - let mut options = SslOptions::ALL & !SslOptions::DONT_INSERT_EMPTY_FRAGMENTS; - if proto != SslVersion::Ssl2 { - options |= SslOptions::NO_SSLV2; - } - if proto != SslVersion::Ssl3 { - options |= SslOptions::NO_SSLV3; - } - options |= SslOptions::NO_COMPRESSION; - options |= SslOptions::CIPHER_SERVER_PREFERENCE; - options |= SslOptions::SINGLE_DH_USE; - options |= SslOptions::SINGLE_ECDH_USE; - options |= SslOptions::ENABLE_MIDDLEBOX_COMPAT; - builder.set_options(options); + let key = server_name_str.as_bytes().to_vec(); + self.session_cache + .write() + .insert(key, Arc::new(ParkingMutex::new(session_data))); + } - let mode = ssl::SslMode::ACCEPT_MOVING_WRITE_BUFFER | ssl::SslMode::AUTO_RETRY; - builder.set_mode(mode); + fn take_tls13_ticket( + &self, + server_name: &ServerName<'static>, + ) -> Option<rustls::client::Tls13ClientSessionValue> { + self.inner.take_tls13_ticket(server_name) + } + } - #[cfg(ossl111)] - unsafe { - sys::SSL_CTX_set_post_handshake_auth(builder.as_ptr(), 0); + /// Parse length-prefixed ALPN protocol list + /// + /// Format: [len1, proto1..., len2, proto2..., ...] + /// + /// This is the wire format used by Python's ssl.py when calling _set_alpn_protocols(). + /// Each protocol is prefixed with a single byte indicating its length. + /// + /// # Arguments + /// * `bytes` - The length-prefixed protocol data + /// * `vm` - VirtualMachine for error creation + /// + /// # Returns + /// * `Ok(Vec<Vec<u8>>)` - List of protocol names as byte vectors + /// * `Err(PyBaseExceptionRef)` - ValueError with detailed error message + fn parse_length_prefixed_alpn(bytes: &[u8], vm: &VirtualMachine) -> PyResult<Vec<Vec<u8>>> { + let mut alpn_list = Vec::new(); + let mut offset = 0; + + while offset < bytes.len() { + // Check if we can read the length byte + if offset + 1 > bytes.len() { + return Err(vm.new_value_error(format!( + "Invalid ALPN protocol data: unexpected end at offset {offset}", + ))); } - builder - .set_session_id_context(b"Python") - .map_err(|e| convert_openssl_error(vm, e))?; + let proto_len = bytes[offset] as usize; + offset += 1; - // Set protocol version limits based on the protocol version - unsafe { - let ctx_ptr = builder.as_ptr(); - match proto { - SslVersion::Tls1 => { - sys::SSL_CTX_set_min_proto_version(ctx_ptr, sys::TLS1_VERSION); - sys::SSL_CTX_set_max_proto_version(ctx_ptr, sys::TLS1_VERSION); - } - SslVersion::Tls1_1 => { - sys::SSL_CTX_set_min_proto_version(ctx_ptr, sys::TLS1_1_VERSION); - sys::SSL_CTX_set_max_proto_version(ctx_ptr, sys::TLS1_1_VERSION); - } - SslVersion::Tls1_2 => { - sys::SSL_CTX_set_min_proto_version(ctx_ptr, sys::TLS1_2_VERSION); - sys::SSL_CTX_set_max_proto_version(ctx_ptr, sys::TLS1_2_VERSION); - } - _ => { - // For Tls, TlsClient, TlsServer, use default (no restrictions) - } - } + // Validate protocol length + if proto_len == 0 { + return Err(vm.new_value_error(format!( + "Invalid ALPN protocol data: protocol length cannot be 0 at offset {}", + offset - 1 + ))); } - // Set default verify flags: VERIFY_X509_TRUSTED_FIRST - unsafe { - let ctx_ptr = builder.as_ptr(); - let param = sys::SSL_CTX_get0_param(ctx_ptr); - sys::X509_VERIFY_PARAM_set_flags(param, sys::X509_V_FLAG_TRUSTED_FIRST); + // Check if we have enough bytes for the protocol data + if offset + proto_len > bytes.len() { + return Err(vm.new_value_error(format!( + "Invalid ALPN protocol data: expected {} bytes at offset {}, but only {} bytes remain", + proto_len, offset, bytes.len() - offset + ))); } - PySslContext { - ctx: PyRwLock::new(builder), - check_hostname: AtomicCell::new(check_hostname), - protocol: proto, - post_handshake_auth: PyMutex::new(false), - sni_callback: PyMutex::new(None), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + // Extract protocol bytes + let proto = bytes[offset..offset + proto_len].to_vec(); + alpn_list.push(proto); + offset += proto_len; } + + Ok(alpn_list) } - #[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] - impl PySslContext { - fn builder(&self) -> PyRwLockWriteGuard<'_, SslContextBuilder> { - self.ctx.write() - } - fn ctx(&self) -> PyMappedRwLockReadGuard<'_, ssl::SslContextRef> { - PyRwLockReadGuard::map(self.ctx.read(), builder_as_ctx) + /// Parse OpenSSL cipher string to rustls SupportedCipherSuite list + /// + /// Supports patterns like: + /// - "AES128" → filters for AES_128 + /// - "AES256" → filters for AES_256 + /// - "AES128:AES256" → both + /// - "ECDHE+AESGCM" → ECDHE AND AESGCM (both conditions must match) + /// - "ALL" or "DEFAULT" → all available + /// - "!MD5" → exclusion (ignored, rustls doesn't support weak ciphers anyway) + fn parse_cipher_string(cipher_str: &str) -> Result<Vec<rustls::SupportedCipherSuite>, String> { + use rustls::crypto::aws_lc_rs::ALL_CIPHER_SUITES; + + if cipher_str.is_empty() { + return Err("No cipher can be selected".to_string()); } - #[pygetset] - fn post_handshake_auth(&self) -> bool { - *self.post_handshake_auth.lock() - } - #[pygetset(setter)] - fn set_post_handshake_auth( - &self, - value: Option<PyObjectRef>, - vm: &VirtualMachine, - ) -> PyResult<()> { - let value = value.ok_or_else(|| vm.new_attribute_error("cannot delete attribute"))?; - *self.post_handshake_auth.lock() = value.is_true(vm)?; - Ok(()) - } + let all_suites = ALL_CIPHER_SUITES; + let mut selected = Vec::new(); - #[cfg(ossl110)] - #[pygetset] - fn security_level(&self) -> i32 { - unsafe { SSL_CTX_get_security_level(self.ctx().as_ptr()) } - } + for part in cipher_str.split(':') { + let part = part.trim(); - #[pymethod] - fn set_ciphers(&self, cipherlist: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { - let ciphers = cipherlist.as_str(); - if ciphers.contains('\0') { - return Err(exceptions::cstring_error(vm)); + // Skip exclusions (rustls doesn't support these) + if part.starts_with('!') { + continue; } - self.builder().set_cipher_list(ciphers).map_err(|_| { - vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "No cipher can be selected.".to_owned(), - ) - }) - } - - #[pymethod] - fn get_ciphers(&self, vm: &VirtualMachine) -> PyResult<PyListRef> { - let ctx = self.ctx(); - let ssl = ssl::Ssl::new(&ctx).map_err(|e| convert_openssl_error(vm, e))?; - - unsafe { - let ciphers_ptr = SSL_get_ciphers(ssl.as_ptr()); - if ciphers_ptr.is_null() { - return Ok(vm.ctx.new_list(vec![])); - } - - let num_ciphers = sys::OPENSSL_sk_num(ciphers_ptr as *const _); - let mut result = Vec::new(); - - for i in 0..num_ciphers { - let cipher_ptr = - sys::OPENSSL_sk_value(ciphers_ptr as *const _, i) as *const sys::SSL_CIPHER; - let cipher = ssl::SslCipherRef::from_ptr(cipher_ptr as *mut _); - - let (name, version, bits) = cipher_to_tuple(cipher); - let dict = vm.ctx.new_dict(); - dict.set_item("name", vm.ctx.new_str(name).into(), vm)?; - dict.set_item("protocol", vm.ctx.new_str(version).into(), vm)?; - dict.set_item("secret_bits", vm.ctx.new_int(bits).into(), vm)?; - // Add description field - let description = cipher_description(cipher_ptr); - dict.set_item("description", vm.ctx.new_str(description).into(), vm)?; + // Skip priority markers starting with + + if part.starts_with('+') { + continue; + } - result.push(dict.into()); + // Match pattern + match part { + "ALL" | "DEFAULT" | "HIGH" => { + // Add all available cipher suites + selected.extend_from_slice(all_suites); } + _ => { + // Check if this is a compound pattern with + (AND condition) + // e.g., "ECDHE+AESGCM" means ECDHE AND AESGCM + let patterns: Vec<&str> = part.split('+').collect(); + + let mut found_any = false; + for suite in all_suites { + let name = format!("{:?}", suite.suite()); + + // Check if all patterns match (AND condition) + let matches = patterns.iter().all(|&pattern| { + // Handle common OpenSSL pattern variations + if pattern.contains("AES128") { + name.contains("AES_128") + } else if pattern.contains("AES256") { + name.contains("AES_256") + } else if pattern == "AESGCM" { + // AESGCM: AES with GCM mode + name.contains("AES") && name.contains("GCM") + } else if pattern == "AESCCM" { + // AESCCM: AES with CCM mode + name.contains("AES") && name.contains("CCM") + } else if pattern == "CHACHA20" { + name.contains("CHACHA20") + } else if pattern == "ECDHE" { + name.contains("ECDHE") + } else if pattern == "DHE" { + // DHE but not ECDHE + name.contains("DHE") && !name.contains("ECDHE") + } else if pattern == "ECDH" { + // ECDH but not ECDHE + name.contains("ECDH") && !name.contains("ECDHE") + } else if pattern == "DH" { + // DH but not DHE or ECDH + name.contains("DH") + && !name.contains("DHE") + && !name.contains("ECDH") + } else if pattern == "RSA" { + name.contains("RSA") + } else if pattern == "AES" { + name.contains("AES") + } else if pattern == "ECDSA" { + name.contains("ECDSA") + } else { + // Direct substring match for other patterns + name.contains(pattern) + } + }); - Ok(vm.ctx.new_list(result)) - } - } - - #[pymethod] - fn set_ecdh_curve( - &self, - name: Either<PyStrRef, ArgBytesLike>, - vm: &VirtualMachine, - ) -> PyResult<()> { - use openssl::ec::{EcGroup, EcKey}; + if matches { + selected.push(*suite); + found_any = true; + } + } - // Convert name to CString, supporting both str and bytes - let name_cstr = match name { - Either::A(s) => { - if s.as_str().contains('\0') { - return Err(exceptions::cstring_error(vm)); + if !found_any { + // No matching cipher suite found - warn but continue } - s.to_cstring(vm)? } - Either::B(b) => std::ffi::CString::new(b.borrow_buf().to_vec()) - .map_err(|_| exceptions::cstring_error(vm))?, - }; - - // Find the NID for the curve name using OBJ_sn2nid - let nid_raw = unsafe { sys::OBJ_sn2nid(name_cstr.as_ptr()) }; - if nid_raw == 0 { - return Err(vm.new_value_error("unknown curve name")); } - let nid = Nid::from_raw(nid_raw); + } - // Create EC key from the curve - let group = EcGroup::from_curve_name(nid).map_err(|e| convert_openssl_error(vm, e))?; - let key = EcKey::from_group(&group).map_err(|e| convert_openssl_error(vm, e))?; + // Remove duplicates + selected.dedup_by_key(|s| s.suite()); - // Set the temporary ECDH key - self.builder() - .set_tmp_ecdh(&key) - .map_err(|e| convert_openssl_error(vm, e)) + if selected.is_empty() { + Err("No cipher can be selected".to_string()) + } else { + Ok(selected) + } + } + + // SSLContext - manages TLS configuration + #[pyattr] + #[pyclass(name = "_SSLContext", module = "ssl", traverse)] + #[derive(Debug, PyPayload)] + struct PySSLContext { + #[pytraverse(skip)] + protocol: i32, + #[pytraverse(skip)] + check_hostname: PyRwLock<bool>, + #[pytraverse(skip)] + verify_mode: PyRwLock<i32>, + #[pytraverse(skip)] + verify_flags: PyRwLock<i32>, + // Rustls configuration (built lazily) + #[allow(dead_code)] + #[pytraverse(skip)] + client_config: PyRwLock<Option<Arc<ClientConfig>>>, + #[allow(dead_code)] + #[pytraverse(skip)] + server_config: PyRwLock<Option<Arc<ServerConfig>>>, + // Certificate store + #[pytraverse(skip)] + root_certs: PyRwLock<RootCertStore>, + // Store full CA certificates for get_ca_certs() + // RootCertStore only keeps TrustAnchors, not full certificates + #[pytraverse(skip)] + ca_certs_der: PyRwLock<Vec<Vec<u8>>>, + // Store CA certificates from capath for lazy loading simulation + // (CPython only returns these in get_ca_certs() after they're used in handshake) + #[pytraverse(skip)] + capath_certs_der: PyRwLock<Vec<Vec<u8>>>, + // Certificate Revocation Lists for CRL checking + #[pytraverse(skip)] + crls: PyRwLock<Vec<CertificateRevocationListDer<'static>>>, + // Server certificate/key pairs (supports multiple for RSA+ECC dual mode) + // OpenSSL allows multiple cert/key pairs to be loaded, and selects the appropriate + // one based on client capabilities during handshake + // Stored as (CertifiedKey, PrivateKeyDer) to support both server and client usage + #[pytraverse(skip)] + cert_keys: PyRwLock<Vec<CertKeyPair>>, + // Options + #[allow(dead_code)] + #[pytraverse(skip)] + options: PyRwLock<i32>, + // ALPN protocols + #[allow(dead_code)] + #[pytraverse(skip)] + alpn_protocols: PyRwLock<Vec<Vec<u8>>>, + // ALPN strict matching flag + // When false (default), mimics OpenSSL behavior: no ALPN negotiation failure + // When true, requires ALPN match (Rustls default behavior) + #[allow(dead_code)] + #[pytraverse(skip)] + require_alpn_match: PyRwLock<bool>, + // TLS 1.3 features + #[pytraverse(skip)] + post_handshake_auth: PyRwLock<bool>, + #[pytraverse(skip)] + num_tickets: PyRwLock<i32>, + // Protocol version limits + #[pytraverse(skip)] + minimum_version: PyRwLock<i32>, + #[pytraverse(skip)] + maximum_version: PyRwLock<i32>, + // SNI callback for server-side (contains PyObjectRef - needs GC tracking) + sni_callback: PyRwLock<Option<PyObjectRef>>, + // Message callback for debugging (contains PyObjectRef - needs GC tracking) + msg_callback: PyRwLock<Option<PyObjectRef>>, + // ECDH curve name for key exchange + #[pytraverse(skip)] + ecdh_curve: PyRwLock<Option<String>>, + // Certificate statistics for cert_store_stats() + #[pytraverse(skip)] + ca_cert_count: PyRwLock<usize>, // Number of CA certificates + #[pytraverse(skip)] + x509_cert_count: PyRwLock<usize>, // Total number of certificates + // Session management + #[pytraverse(skip)] + client_session_cache: SessionCache, + // Rustls session store for actual TLS session resumption + #[pytraverse(skip)] + rustls_session_store: Arc<PythonClientSessionStore>, + // Rustls server session store for server-side session resumption + #[pytraverse(skip)] + rustls_server_session_store: Arc<rustls::server::ServerSessionMemoryCache>, + // Shared ticketer for TLS 1.2 session tickets + #[pytraverse(skip)] + server_ticketer: Arc<dyn rustls::server::ProducesTickets>, + // Server-side session statistics + #[pytraverse(skip)] + accept_count: AtomicUsize, // Total number of accepts + #[pytraverse(skip)] + session_hits: AtomicUsize, // Number of session reuses + // Cipher suite selection + /// Selected cipher suites (None = use all rustls defaults) + #[pytraverse(skip)] + selected_ciphers: PyRwLock<Option<Vec<rustls::SupportedCipherSuite>>>, + } + + #[derive(FromArgs)] + struct WrapSocketArgs { + sock: PyObjectRef, + server_side: bool, + #[pyarg(positional, optional)] + server_hostname: OptionalArg<Option<PyStrRef>>, + #[pyarg(named, optional)] + owner: OptionalArg<PyObjectRef>, + #[pyarg(named, optional)] + session: OptionalArg<PyObjectRef>, + } + + #[derive(FromArgs)] + struct WrapBioArgs { + incoming: PyRef<PyMemoryBIO>, + outgoing: PyRef<PyMemoryBIO>, + #[pyarg(named, optional)] + server_side: OptionalArg<bool>, + #[pyarg(named, optional)] + server_hostname: OptionalArg<Option<PyStrRef>>, + #[pyarg(named, optional)] + owner: OptionalArg<PyObjectRef>, + #[pyarg(named, optional)] + session: OptionalArg<PyObjectRef>, + } + + #[derive(FromArgs)] + struct LoadVerifyLocationsArgs { + #[pyarg(any, optional)] + cafile: OptionalArg<Option<PyObjectRef>>, + #[pyarg(any, optional)] + capath: OptionalArg<Option<PyObjectRef>>, + #[pyarg(any, optional)] + cadata: OptionalArg<PyObjectRef>, + } + + #[derive(FromArgs)] + struct LoadCertChainArgs { + #[pyarg(any)] + certfile: PyObjectRef, + #[pyarg(any, optional)] + keyfile: OptionalArg<Option<PyObjectRef>>, + #[pyarg(any, optional)] + password: OptionalArg<PyObjectRef>, + } + + #[pyclass(with(Constructor), flags(BASETYPE))] + impl PySSLContext { + // Helper method to convert DER certificate bytes to Python dict + fn cert_der_to_dict(&self, vm: &VirtualMachine, cert_der: &[u8]) -> PyResult<PyObjectRef> { + cert::cert_der_to_dict_helper(vm, cert_der) + } + + #[pymethod] + fn __repr__(&self) -> String { + format!("<SSLContext(protocol={})>", self.protocol) + } + + #[pygetset] + fn check_hostname(&self) -> bool { + *self.check_hostname.read() + } + + #[pygetset(setter)] + fn set_check_hostname(&self, value: bool) { + *self.check_hostname.write() = value; + // When check_hostname is enabled, ensure verify_mode is at least CERT_REQUIRED + if value { + let current_verify_mode = *self.verify_mode.read(); + if current_verify_mode == CERT_NONE { + *self.verify_mode.write() = CERT_REQUIRED; + } + } } #[pygetset] - fn options(&self) -> libc::c_ulong { - self.ctx.read().options().bits() as _ + fn verify_mode(&self) -> i32 { + *self.verify_mode.read() } + #[pygetset(setter)] - fn set_options(&self, opts: libc::c_ulong) { - self.builder() - .set_options(SslOptions::from_bits_truncate(opts as _)); + fn set_verify_mode(&self, mode: i32, vm: &VirtualMachine) -> PyResult<()> { + if !(CERT_NONE..=CERT_REQUIRED).contains(&mode) { + return Err(vm.new_value_error("invalid verify mode")); + } + // Cannot set CERT_NONE when check_hostname is enabled + if mode == CERT_NONE && *self.check_hostname.read() { + return Err(vm.new_value_error( + "Cannot set verify_mode to CERT_NONE when check_hostname is enabled", + )); + } + *self.verify_mode.write() = mode; + Ok(()) } + #[pygetset] fn protocol(&self) -> i32 { - self.protocol as i32 + self.protocol } + #[pygetset] - fn verify_mode(&self) -> i32 { - let mode = self.ctx().verify_mode(); - if mode == SslVerifyMode::NONE { - CertRequirements::None.into() - } else if mode == SslVerifyMode::PEER { - CertRequirements::Optional.into() - } else if mode == SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT { - CertRequirements::Required.into() - } else { - unreachable!() - } + fn verify_flags(&self) -> i32 { + *self.verify_flags.read() } + #[pygetset(setter)] - fn set_verify_mode(&self, cert: i32, vm: &VirtualMachine) -> PyResult<()> { - let mut ctx = self.builder(); - let cert_req = CertRequirements::try_from(cert) - .map_err(|_| vm.new_value_error("invalid value for verify_mode"))?; - let mode = match cert_req { - CertRequirements::None if self.check_hostname.load() => { - return Err(vm.new_value_error( - "Cannot set verify_mode to CERT_NONE when check_hostname is enabled.", - )); - } - CertRequirements::None => SslVerifyMode::NONE, - CertRequirements::Optional => SslVerifyMode::PEER, - CertRequirements::Required => { - SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT - } - }; - ctx.set_verify(mode); - Ok(()) + fn set_verify_flags(&self, value: i32) { + *self.verify_flags.write() = value; } + #[pygetset] - fn verify_flags(&self) -> libc::c_ulong { - unsafe { - let ctx_ptr = self.ctx().as_ptr(); - let param = sys::SSL_CTX_get0_param(ctx_ptr); - sys::X509_VERIFY_PARAM_get_flags(param) - } + fn post_handshake_auth(&self) -> bool { + *self.post_handshake_auth.read() } + #[pygetset(setter)] - fn set_verify_flags(&self, new_flags: libc::c_ulong, vm: &VirtualMachine) -> PyResult<()> { - unsafe { - let ctx_ptr = self.ctx().as_ptr(); - let param = sys::SSL_CTX_get0_param(ctx_ptr); - let flags = sys::X509_VERIFY_PARAM_get_flags(param); - let clear = flags & !new_flags; - let set = !flags & new_flags; - - if clear != 0 && sys::X509_VERIFY_PARAM_clear_flags(param, clear) == 0 { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Failed to clear verify flags".to_owned(), - )); - } - if set != 0 && sys::X509_VERIFY_PARAM_set_flags(param, set) == 0 { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Failed to set verify flags".to_owned(), - )); - } - Ok(()) + fn set_post_handshake_auth(&self, value: bool) { + *self.post_handshake_auth.write() = value; + } + + #[pygetset] + fn num_tickets(&self) -> i32 { + *self.num_tickets.read() + } + + #[pygetset(setter)] + fn set_num_tickets(&self, value: i32, vm: &VirtualMachine) -> PyResult<()> { + if value < 0 { + return Err(vm.new_value_error("num_tickets must be a non-negative integer")); } + if self.protocol != PROTOCOL_TLS_SERVER { + return Err( + vm.new_value_error("num_tickets can only be set on server-side contexts") + ); + } + *self.num_tickets.write() = value; + Ok(()) } + #[pygetset] - fn check_hostname(&self) -> bool { - self.check_hostname.load() + fn options(&self) -> i32 { + *self.options.read() } + #[pygetset(setter)] - fn set_check_hostname(&self, ch: bool) { - let mut ctx = self.builder(); - if ch && builder_as_ctx(&ctx).verify_mode() == SslVerifyMode::NONE { - ctx.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT); + fn set_options(&self, value: i32, vm: &VirtualMachine) -> PyResult<()> { + // Validate that the value is non-negative + if value < 0 { + return Err(vm.new_overflow_error("options must be non-negative".to_owned())); } - self.check_hostname.store(ch); + + // Deprecated SSL/TLS protocol version options + let opt_no = OP_NO_SSLv2 + | OP_NO_SSLv3 + | OP_NO_TLSv1 + | OP_NO_TLSv1_1 + | OP_NO_TLSv1_2 + | OP_NO_TLSv1_3; + + // Get current options and calculate newly set bits + let old_opts = *self.options.read(); + let set = !old_opts & value; // Bits being newly set + + // Warn if any deprecated options are being newly set + if (set & opt_no) != 0 { + warnings::warn( + vm.ctx.exceptions.deprecation_warning, + "ssl.OP_NO_SSL*/ssl.OP_NO_TLS* options are deprecated".to_owned(), + 2, // stack_level = 2 + vm, + )?; + } + + *self.options.write() = value; + Ok(()) } - // PY_PROTO_MINIMUM_SUPPORTED = -2, PY_PROTO_MAXIMUM_SUPPORTED = -1 #[pygetset] fn minimum_version(&self) -> i32 { - let ctx = self.ctx(); - let version = unsafe { sys::SSL_CTX_get_min_proto_version(ctx.as_ptr()) }; - if version == 0 { - -2 // PY_PROTO_MINIMUM_SUPPORTED - } else { - version - } + let v = *self.minimum_version.read(); + // return MINIMUM_SUPPORTED if value is 0 + if v == 0 { PROTO_MINIMUM_SUPPORTED } else { v } } + #[pygetset(setter)] fn set_minimum_version(&self, value: i32, vm: &VirtualMachine) -> PyResult<()> { - // Handle special values - let proto_version = match value { - -2 => { - // PY_PROTO_MINIMUM_SUPPORTED -> use minimum available (TLS 1.2) - sys::TLS1_2_VERSION - } - -1 => { - // PY_PROTO_MAXIMUM_SUPPORTED -> use maximum available - // For max on min_proto_version, we use the newest available - sys::TLS1_3_VERSION - } + // Validate that the value is a valid TLS version constant + // Valid values: 0 (default), -2 (MINIMUM_SUPPORTED), -1 (MAXIMUM_SUPPORTED), + // or 0x0300-0x0304 (SSLv3-TLSv1.3) + if value != 0 + && value != -2 + && value != -1 + && !(PROTO_SSLv3..=PROTO_TLSv1_3).contains(&value) + { + return Err(vm.new_value_error(format!("invalid protocol version: {value}"))); + } + // Convert special values to rustls actual supported versions + // MINIMUM_SUPPORTED (-2) -> 0 (auto-negotiate) + // MAXIMUM_SUPPORTED (-1) -> MAXIMUM_VERSION (TLSv1.3) + let normalized_value = match value { + PROTO_MINIMUM_SUPPORTED => 0, // Auto-negotiate + PROTO_MAXIMUM_SUPPORTED => MAXIMUM_VERSION, // TLSv1.3 _ => value, }; - - let ctx = self.builder(); - let result = unsafe { sys::SSL_CTX_set_min_proto_version(ctx.as_ptr(), proto_version) }; - if result == 0 { - return Err(vm.new_value_error("invalid protocol version")); - } + *self.minimum_version.write() = normalized_value; Ok(()) } #[pygetset] fn maximum_version(&self) -> i32 { - let ctx = self.ctx(); - let version = unsafe { sys::SSL_CTX_get_max_proto_version(ctx.as_ptr()) }; - if version == 0 { - -1 // PY_PROTO_MAXIMUM_SUPPORTED - } else { - version - } + let v = *self.maximum_version.read(); + // return MAXIMUM_SUPPORTED if value is 0 + if v == 0 { PROTO_MAXIMUM_SUPPORTED } else { v } } + #[pygetset(setter)] fn set_maximum_version(&self, value: i32, vm: &VirtualMachine) -> PyResult<()> { - // Handle special values - let proto_version = match value { - -1 => { - // PY_PROTO_MAXIMUM_SUPPORTED -> use 0 for OpenSSL (means no limit) - 0 - } - -2 => { - // PY_PROTO_MINIMUM_SUPPORTED -> use minimum available (TLS 1.2) - sys::TLS1_2_VERSION - } + // Validate that the value is a valid TLS version constant + // Valid values: 0 (default), -2 (MINIMUM_SUPPORTED), -1 (MAXIMUM_SUPPORTED), + // or 0x0300-0x0304 (SSLv3-TLSv1.3) + if value != 0 + && value != -2 + && value != -1 + && !(PROTO_SSLv3..=PROTO_TLSv1_3).contains(&value) + { + return Err(vm.new_value_error(format!("invalid protocol version: {value}"))); + } + // Convert special values to rustls actual supported versions + // MAXIMUM_SUPPORTED (-1) -> 0 (auto-negotiate) + // MINIMUM_SUPPORTED (-2) -> MINIMUM_VERSION (TLSv1.2) + let normalized_value = match value { + PROTO_MAXIMUM_SUPPORTED => 0, // Auto-negotiate + PROTO_MINIMUM_SUPPORTED => MINIMUM_VERSION, // TLSv1.2 _ => value, }; - - let ctx = self.builder(); - let result = unsafe { sys::SSL_CTX_set_max_proto_version(ctx.as_ptr(), proto_version) }; - if result == 0 { - return Err(vm.new_value_error("invalid protocol version")); - } + *self.maximum_version.write() = normalized_value; Ok(()) } - #[pygetset] - fn num_tickets(&self, _vm: &VirtualMachine) -> PyResult<usize> { - // Only supported for TLS 1.3 - #[cfg(ossl110)] - { - let ctx = self.ctx(); - let num = unsafe { sys::SSL_CTX_get_num_tickets(ctx.as_ptr()) }; - Ok(num) - } - #[cfg(not(ossl110))] + #[pymethod] + fn load_cert_chain(&self, args: LoadCertChainArgs, vm: &VirtualMachine) -> PyResult<()> { + // Parse certfile argument (str or bytes) to path + let cert_path = Self::parse_path_arg(&args.certfile, vm)?; + + // Parse keyfile argument (default to certfile if not provided) + let key_path = match args.keyfile { + OptionalArg::Present(Some(ref k)) => Self::parse_path_arg(k, vm)?, + _ => cert_path.clone(), + }; + + // Parse password argument (str, bytes-like, or callable) + // Callable passwords are NOT invoked immediately (lazy evaluation) + let (password_str, password_callable) = + Self::parse_password_argument(&args.password, vm)?; + + // Validate immediate password length (limit: PEM_BUFSIZE = 1024 bytes) + if let Some(ref pwd) = password_str + && pwd.len() > PEM_BUFSIZE { - let _ = vm; - Ok(0) - } - } - #[pygetset(setter)] - fn set_num_tickets(&self, value: isize, vm: &VirtualMachine) -> PyResult<()> { - // Check for negative values - if value < 0 { - return Err( - vm.new_value_error("num_tickets must be a non-negative integer".to_owned()) - ); + return Err(vm.new_value_error(format!( + "password cannot be longer than {PEM_BUFSIZE} bytes", + ))); } - // Check that this is a server context - if self.protocol != SslVersion::TlsServer { - return Err(vm.new_value_error("SSLContext is not a server context.".to_owned())); - } + // First attempt: Load with immediate password (or None if callable) + let mut result = + cert::load_cert_chain_from_file(&cert_path, &key_path, password_str.as_deref()); - #[cfg(ossl110)] + // If failed and callable exists, invoke it and retry + // This implements lazy evaluation: callable only invoked if password is actually needed + if result.is_err() + && let Some(callable) = password_callable { - let ctx = self.builder(); - let result = unsafe { sys::SSL_CTX_set_num_tickets(ctx.as_ptr(), value as usize) }; - if result != 1 { - return Err(vm.new_value_error("failed to set num tickets.")); + // Invoke callable - exceptions propagate naturally + let pwd_result = callable.call((), vm)?; + + // Convert callable result to string + let password_from_callable = if let Ok(pwd_str) = + PyStrRef::try_from_object(vm, pwd_result.clone()) + { + pwd_str.as_str().to_owned() + } else if let Ok(pwd_bytes_like) = ArgBytesLike::try_from_object(vm, pwd_result) { + String::from_utf8(pwd_bytes_like.borrow_buf().to_vec()).map_err(|_| { + vm.new_type_error( + "password callback returned invalid UTF-8 bytes".to_owned(), + ) + })? + } else { + return Err(vm.new_type_error( + "password callback must return a string or bytes".to_owned(), + )); + }; + + // Validate callable password length + if password_from_callable.len() > PEM_BUFSIZE { + return Err(vm.new_value_error(format!( + "password cannot be longer than {PEM_BUFSIZE} bytes", + ))); } - Ok(()) - } - #[cfg(not(ossl110))] - { - let _ = (value, vm); - Ok(()) + + // Retry with callable password + result = cert::load_cert_chain_from_file( + &cert_path, + &key_path, + Some(&password_from_callable), + ); } - } - #[pymethod] - fn set_default_verify_paths(&self, vm: &VirtualMachine) -> PyResult<()> { - cfg_if::cfg_if! { - if #[cfg(openssl_vendored)] { - let (cert_file, cert_dir) = get_cert_file_dir(); - self.builder() - .load_verify_locations(Some(cert_file), Some(cert_dir)) - .map_err(|e| convert_openssl_error(vm, e)) + // Process result + let (certs, key) = result.map_err(|e| { + // Try to downcast to io::Error to preserve errno information + if let Ok(io_err) = e.downcast::<std::io::Error>() { + match io_err.kind() { + // File access errors (NotFound, PermissionDenied) - preserve errno + std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => { + io_err.into_pyexception(vm) + } + // Other io::Error types + std::io::ErrorKind::Other => { + let msg = io_err.to_string(); + if msg.contains("Failed to decrypt") || msg.contains("wrong password") { + // Wrong password error + vm.new_exception_msg(PySSLError::class(&vm.ctx).to_owned(), msg) + } else { + // [SSL] PEM lib + super::compat::SslError::create_ssl_error_with_reason( + vm, "SSL", "", "PEM lib", + ) + } + } + // PEM parsing errors - [SSL] PEM lib + _ => super::compat::SslError::create_ssl_error_with_reason( + vm, "SSL", "", "PEM lib", + ), + } } else { - self.builder() - .set_default_verify_paths() - .map_err(|e| convert_openssl_error(vm, e)) + // Unknown error type - [SSL] PEM lib + super::compat::SslError::create_ssl_error_with_reason(vm, "SSL", "", "PEM lib") } - } - } + })?; - #[pymethod] - fn _set_alpn_protocols(&self, protos: ArgBytesLike, vm: &VirtualMachine) -> PyResult<()> { - #[cfg(ossl102)] - { - let mut ctx = self.builder(); - let server = protos.with_ref(|pbuf| { - if pbuf.len() > libc::c_uint::MAX as usize { - return Err(vm.new_overflow_error(format!( - "protocols longer than {} bytes", - libc::c_uint::MAX - ))); + // Validate certificate and key match + cert::validate_cert_key_match(&certs, &key).map_err(|e| { + vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + if e.contains("key values mismatch") { + "[SSL: KEY_VALUES_MISMATCH] key values mismatch".to_owned() + } else { + e + }, + ) + })?; + + // Auto-build certificate chain: if only leaf cert is in file, try to add CA certs + // This matches OpenSSL behavior where it automatically includes intermediate/CA certs + let mut full_chain = certs.clone(); + if full_chain.len() == 1 { + // Only have leaf cert, try to build chain from CA certs + let ca_certs_der = self.ca_certs_der.read(); + if !ca_certs_der.is_empty() { + // Use build_verified_chain to construct full chain + let chain_result = cert::build_verified_chain(&full_chain, &ca_certs_der); + if chain_result.len() > 1 { + // Successfully built a longer chain + full_chain = chain_result.into_iter().map(CertificateDer::from).collect(); } - ctx.set_alpn_protos(pbuf) - .map_err(|e| convert_openssl_error(vm, e))?; - Ok(pbuf.to_vec()) + } + } + + // Additional validation: Create CertifiedKey to ensure rustls accepts it + let signing_key = + rustls::crypto::aws_lc_rs::sign::any_supported_type(&key).map_err(|_| { + vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + "[SSL: KEY_VALUES_MISMATCH] key values mismatch".to_owned(), + ) })?; - ctx.set_alpn_select_callback(move |_, client| { - let proto = - ssl::select_next_proto(&server, client).ok_or(ssl::AlpnError::NOACK)?; - let pos = memchr::memmem::find(client, proto) - .expect("selected alpn proto should be present in client protos"); - Ok(&client[pos..proto.len()]) - }); - Ok(()) - } - #[cfg(not(ossl102))] - { - Err(vm.new_not_implemented_error( - "The NPN extension requires OpenSSL 1.0.1 or later.", - )) + + let certified_key = CertifiedKey::new(full_chain.clone(), signing_key); + if certified_key.keys_match().is_err() { + return Err(vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + "[SSL: KEY_VALUES_MISMATCH] key values mismatch".to_owned(), + )); } + + // Add cert/key pair to collection (OpenSSL allows multiple cert/key pairs) + // Store both CertifiedKey (for server) and PrivateKeyDer (for client mTLS) + let cert_der = &full_chain[0]; + let mut cert_keys = self.cert_keys.write(); + + // Remove any existing cert/key pair with the same certificate + // (This allows updating cert/key pair without duplicating) + cert_keys.retain(|(existing, _)| &existing.cert[0] != cert_der); + + // Add new cert/key pair as tuple + cert_keys.push((Arc::new(certified_key), key)); + + Ok(()) } #[pymethod] @@ -1155,264 +1337,357 @@ mod _ssl { args: LoadVerifyLocationsArgs, vm: &VirtualMachine, ) -> PyResult<()> { - if let (None, None, None) = (&args.cafile, &args.capath, &args.cadata) { - return Err(vm.new_type_error("cafile, capath and cadata cannot be all omitted")); - } + // Check that at least one argument is provided + let has_cafile = matches!(&args.cafile, OptionalArg::Present(Some(_))); + let has_capath = matches!(&args.capath, OptionalArg::Present(Some(_))); + let has_cadata = matches!(&args.cadata, OptionalArg::Present(obj) if !vm.is_none(obj)); - #[cold] - fn invalid_cadata(vm: &VirtualMachine) -> PyBaseExceptionRef { - vm.new_type_error("cadata should be an ASCII string or a bytes-like object") + if !has_cafile && !has_capath && !has_cadata { + return Err( + vm.new_type_error("cafile, capath and cadata cannot be all omitted".to_owned()) + ); } - let mut ctx = self.builder(); + // Get mutable references to store and ca_certs_der + let mut root_store = self.root_certs.write(); + let mut ca_certs_der = self.ca_certs_der.write(); - // validate cadata type and load cadata - if let Some(cadata) = args.cadata { - let certs = match cadata { - Either::A(s) => { - if !s.is_ascii() { - return Err(invalid_cadata(vm)); - } - X509::stack_from_pem(s.as_bytes()) - } - Either::B(b) => b.with_ref(x509_stack_from_der), - }; - let certs = certs.map_err(|e| convert_openssl_error(vm, e))?; - let store = ctx.cert_store_mut(); - for cert in certs { - store - .add_cert(cert) - .map_err(|e| convert_openssl_error(vm, e))?; + // Load from file + if let OptionalArg::Present(Some(ref cafile_obj)) = args.cafile { + let path = Self::parse_path_arg(cafile_obj, vm)?; + + // Try to load as CRL first + if let Some(crl) = self.load_crl_from_file(&path, vm)? { + self.crls.write().push(crl); + } else { + // Not a CRL, load as certificate + let stats = self.load_certs_from_file_helper( + &mut root_store, + &mut ca_certs_der, + &path, + vm, + )?; + self.update_cert_stats(stats); } } - if args.cafile.is_some() || args.capath.is_some() { - let cafile_path = args.cafile.map(|p| p.to_path_buf(vm)).transpose()?; - let capath_path = args.capath.map(|p| p.to_path_buf(vm)).transpose()?; - ctx.load_verify_locations(cafile_path.as_deref(), capath_path.as_deref()) - .map_err(|e| convert_openssl_error(vm, e))?; + // Load from directory (don't add to ca_certs_der) + if let OptionalArg::Present(Some(ref capath_obj)) = args.capath { + let dir_path = Self::parse_path_arg(capath_obj, vm)?; + let stats = self.load_certs_from_dir_helper(&mut root_store, &dir_path, vm)?; + self.update_cert_stats(stats); + } + + // Load from bytes or str + if let OptionalArg::Present(cadata_obj) = args.cadata + && !vm.is_none(&cadata_obj) + { + // Check if input is string or bytes + let is_string = PyStrRef::try_from_object(vm, cadata_obj.clone()).is_ok(); + let data_vec = self.parse_cadata_arg(&cadata_obj, vm)?; + let stats = self.load_certs_from_bytes_helper( + &mut root_store, + &mut ca_certs_der, + &data_vec, + is_string, // PEM only for strings + vm, + )?; + self.update_cert_stats(stats); } Ok(()) } - #[pymethod] - fn get_ca_certs( - &self, - binary_form: OptionalArg<bool>, + /// Helper: Get path from Python's os.environ + fn get_env_path( + environ: &PyObjectRef, + var_name: &str, vm: &VirtualMachine, - ) -> PyResult<Vec<PyObjectRef>> { - let binary_form = binary_form.unwrap_or(false); - let ctx = self.ctx(); - #[cfg(ossl300)] - let certs = ctx.cert_store().all_certificates(); - #[cfg(not(ossl300))] - let certs = ctx.cert_store().objects().iter().filter_map(|x| x.x509()); - - // Filter to only include CA certificates (Basic Constraints: CA=TRUE) - let certs = certs - .into_iter() - .filter(|cert| { - unsafe { - // X509_check_ca() returns 1 for CA certificates - X509_check_ca(cert.as_ptr()) == 1 - } - }) - .map(|ref cert| cert_to_py(vm, cert, binary_form)) - .collect::<Result<Vec<_>, _>>()?; - Ok(certs) + ) -> PyResult<String> { + let path_obj = environ.get_item(var_name, vm)?; + path_obj.try_into_value(vm) } - #[pymethod] - fn cert_store_stats(&self, vm: &VirtualMachine) -> PyResult { - let ctx = self.ctx(); - let store_ptr = unsafe { sys::SSL_CTX_get_cert_store(ctx.as_ptr()) }; + /// Helper: Try to load certificates from Python's os.environ variables + /// + /// Returns true if certificates were successfully loaded. + /// + /// We use Python's os.environ instead of Rust's std::env + /// because Python code can modify os.environ at runtime (e.g., + /// `os.environ['SSL_CERT_FILE'] = '/path'`), but rustls-native-certs uses + /// std::env which only sees the process environment at startup. + fn try_load_from_python_environ( + &self, + loader: &mut cert::CertLoader<'_>, + vm: &VirtualMachine, + ) -> PyResult<bool> { + use std::path::Path; - if store_ptr.is_null() { - return Err(vm.new_memory_error("failed to get cert store".to_owned())); - } + let os_module = vm.import("os", 0)?; + let environ = os_module.get_attr("environ", vm)?; - let objs_ptr = unsafe { sys::X509_STORE_get0_objects(store_ptr) }; - if objs_ptr.is_null() { - return Err(vm.new_memory_error("failed to query cert store".to_owned())); + // Try SSL_CERT_FILE first + if let Ok(cert_file) = Self::get_env_path(&environ, "SSL_CERT_FILE", vm) + && Path::new(&cert_file).exists() + && let Ok(stats) = loader.load_from_file(&cert_file) + { + self.update_cert_stats(stats); + return Ok(true); } - let mut x509_count = 0; - let mut crl_count = 0; - let mut ca_count = 0; + // Try SSL_CERT_DIR (only if SSL_CERT_FILE didn't work) + if let Ok(cert_dir) = Self::get_env_path(&environ, "SSL_CERT_DIR", vm) + && Path::new(&cert_dir).is_dir() + && let Ok(stats) = loader.load_from_dir(&cert_dir) + { + self.update_cert_stats(stats); + return Ok(true); + } - unsafe { - let num_objs = sys::OPENSSL_sk_num(objs_ptr as *const _); - for i in 0..num_objs { - let obj_ptr = - sys::OPENSSL_sk_value(objs_ptr as *const _, i) as *const sys::X509_OBJECT; - let obj_type = X509_OBJECT_get_type(obj_ptr); + Ok(false) + } - match obj_type { - X509_LU_X509 => { - x509_count += 1; - let x509_ptr = sys::X509_OBJECT_get0_X509(obj_ptr); - if !x509_ptr.is_null() && X509_check_ca(x509_ptr) == 1 { - ca_count += 1; - } - } - X509_LU_CRL => { - crl_count += 1; - } - _ => { - // Ignore unrecognized types - } + /// Helper: Load system certificates using rustls-native-certs + /// + /// This uses platform-specific methods: + /// - Linux: openssl-probe to find certificate files + /// - macOS: Keychain API + /// - Windows: System certificate store + fn load_system_certificates( + &self, + store: &mut rustls::RootCertStore, + vm: &VirtualMachine, + ) -> PyResult<()> { + let result = rustls_native_certs::load_native_certs(); + + // Load successfully found certificates + for cert in result.certs { + let is_ca = cert::is_ca_certificate(cert.as_ref()); + if store.add(cert).is_ok() { + *self.x509_cert_count.write() += 1; + if is_ca { + *self.ca_cert_count.write() += 1; } } - // Note: No need to free objs_ptr as X509_STORE_get0_objects returns - // a pointer to internal data that should not be freed by the caller } - let dict = vm.ctx.new_dict(); - dict.set_item("x509", vm.ctx.new_int(x509_count).into(), vm)?; - dict.set_item("crl", vm.ctx.new_int(crl_count).into(), vm)?; - dict.set_item("x509_ca", vm.ctx.new_int(ca_count).into(), vm)?; - Ok(dict.into()) - } - - #[pymethod] - fn session_stats(&self, vm: &VirtualMachine) -> PyResult { - let ctx = self.ctx(); - let ctx_ptr = ctx.as_ptr(); - - let dict = vm.ctx.new_dict(); - - macro_rules! add_stat { - ($key:expr, $func:ident) => { - let value = unsafe { $func(ctx_ptr) }; - dict.set_item($key, vm.ctx.new_int(value).into(), vm)?; - }; + // If there were errors but some certs loaded, just continue + // If NO certs loaded and there were errors, report the first error + if *self.x509_cert_count.read() == 0 && !result.errors.is_empty() { + return Err(vm.new_os_error(format!( + "Failed to load native certificates: {}", + result.errors[0] + ))); } - add_stat!("number", SSL_CTX_sess_number); - add_stat!("connect", SSL_CTX_sess_connect); - add_stat!("connect_good", SSL_CTX_sess_connect_good); - add_stat!("connect_renegotiate", SSL_CTX_sess_connect_renegotiate); - add_stat!("accept", SSL_CTX_sess_accept); - add_stat!("accept_good", SSL_CTX_sess_accept_good); - add_stat!("accept_renegotiate", SSL_CTX_sess_accept_renegotiate); - add_stat!("hits", SSL_CTX_sess_hits); - add_stat!("misses", SSL_CTX_sess_misses); - add_stat!("timeouts", SSL_CTX_sess_timeouts); - add_stat!("cache_full", SSL_CTX_sess_cache_full); - - Ok(dict.into()) + Ok(()) } #[pymethod] - fn load_dh_params(&self, filepath: FsPath, vm: &VirtualMachine) -> PyResult<()> { - let path = filepath.to_path_buf(vm)?; - - // Open the file using fopen (cross-platform) - let fp = - rustpython_common::fileutils::fopen(path.as_path(), "rb").map_err(|e| { - match e.kind() { - std::io::ErrorKind::NotFound => vm.new_exception_msg( - vm.ctx.exceptions.file_not_found_error.to_owned(), - e.to_string(), - ), - _ => vm.new_os_error(e.to_string()), - } - })?; + fn load_default_certs( + &self, + _purpose: OptionalArg<i32>, + vm: &VirtualMachine, + ) -> PyResult<()> { + let mut store = self.root_certs.write(); - // Read DH parameters - let dh = unsafe { - PEM_read_DHparams( - fp, - std::ptr::null_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - ) - }; - unsafe { - libc::fclose(fp); - } + // Create loader (without ca_certs_der - default certs don't go to get_ca_certs()) + let mut lazy_ca_certs = Vec::new(); + let mut loader = cert::CertLoader::new(&mut store, &mut lazy_ca_certs); - if dh.is_null() { - return Err(convert_openssl_error(vm, ErrorStack::get())); - } + // Try Python os.environ first (allows runtime env changes) + // This checks SSL_CERT_FILE and SSL_CERT_DIR from Python's os.environ + let loaded = self.try_load_from_python_environ(&mut loader, vm)?; - // Set temporary DH parameters - let ctx = self.builder(); - let result = unsafe { sys::SSL_CTX_set_tmp_dh(ctx.as_ptr(), dh) }; - unsafe { - sys::DH_free(dh); + // Fallback to system certificates if environment variables didn't provide any + if !loaded { + let _ = self.load_system_certificates(&mut store, vm); } - if result != 1 { - return Err(convert_openssl_error(vm, ErrorStack::get())); + // If no certificates were loaded from system, fallback to webpki-roots (Mozilla CA bundle) + // This ensures we always have some trusted root certificates even if system cert loading fails + if *self.x509_cert_count.read() == 0 { + use webpki_roots; + + // webpki_roots provides TLS_SERVER_ROOTS as &[TrustAnchor] + // We can use extend() to add them to the RootCertStore + let webpki_count = webpki_roots::TLS_SERVER_ROOTS.len(); + store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + + *self.x509_cert_count.write() += webpki_count; + *self.ca_cert_count.write() += webpki_count; } Ok(()) } - #[pygetset] + #[pymethod] + fn set_alpn_protocols(&self, protocols: PyListRef, vm: &VirtualMachine) -> PyResult<()> { + let mut alpn_list = Vec::new(); + for item in protocols.borrow_vec().iter() { + let bytes = ArgBytesLike::try_from_object(vm, item.clone())?; + alpn_list.push(bytes.borrow_buf().to_vec()); + } + *self.alpn_protocols.write() = alpn_list; + Ok(()) + } + + #[pymethod] + fn _set_alpn_protocols(&self, protos: ArgBytesLike, vm: &VirtualMachine) -> PyResult<()> { + let bytes = protos.borrow_buf(); + let alpn_list = parse_length_prefixed_alpn(&bytes, vm)?; + *self.alpn_protocols.write() = alpn_list; + Ok(()) + } + + #[pymethod] + fn set_ciphers(&self, ciphers: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { + let cipher_str = ciphers.as_str(); + + // Parse cipher string and store selected ciphers + let selected_ciphers = parse_cipher_string(cipher_str) + .map_err(|e| vm.new_exception_msg(PySSLError::class(&vm.ctx).to_owned(), e))?; + + // Store in context + *self.selected_ciphers.write() = Some(selected_ciphers); + + Ok(()) + } + + #[pymethod] + fn get_ciphers(&self, vm: &VirtualMachine) -> PyResult<PyListRef> { + // Dynamically generate cipher list from rustls ALL_CIPHER_SUITES + // This automatically includes all cipher suites supported by the current rustls version + use rustls::crypto::aws_lc_rs::ALL_CIPHER_SUITES; + + let cipher_list = ALL_CIPHER_SUITES + .iter() + .map(|suite| { + // Extract cipher information using unified helper + let cipher_info = extract_cipher_info(suite); + + // Convert to OpenSSL-style name + // e.g., "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" -> "ECDHE-RSA-AES128-GCM-SHA256" + let openssl_name = normalize_cipher_name(&cipher_info.name); + + // Determine key exchange and auth methods + let (kx, auth) = if cipher_info.protocol == "TLSv1.3" { + // TLS 1.3 doesn't distinguish - all use modern algos + ("any", "any") + } else if cipher_info.name.contains("ECDHE") { + // TLS 1.2 with ECDHE + let auth = if cipher_info.name.contains("ECDSA") { + "ECDSA" + } else if cipher_info.name.contains("RSA") { + "RSA" + } else { + "any" + }; + ("ECDH", auth) + } else { + ("any", "any") + }; + + // Build description string + // Format: "{name} {protocol} Kx={kx} Au={auth} Enc={enc} Mac={mac}" + let enc = get_cipher_encryption_desc(&openssl_name); + + let description = format!( + "{} {} Kx={} Au={} Enc={} Mac=AEAD", + openssl_name, cipher_info.protocol, kx, auth, enc + ); + + // Create cipher dict + let dict = vm.ctx.new_dict(); + dict.set_item("name", vm.ctx.new_str(openssl_name).into(), vm) + .unwrap(); + dict.set_item("protocol", vm.ctx.new_str(cipher_info.protocol).into(), vm) + .unwrap(); + dict.set_item("id", vm.ctx.new_int(0).into(), vm).unwrap(); // Placeholder ID + dict.set_item("strength_bits", vm.ctx.new_int(cipher_info.bits).into(), vm) + .unwrap(); + dict.set_item("alg_bits", vm.ctx.new_int(cipher_info.bits).into(), vm) + .unwrap(); + dict.set_item("description", vm.ctx.new_str(description).into(), vm) + .unwrap(); + dict.into() + }) + .collect::<Vec<_>>(); + + Ok(PyListRef::from(vm.ctx.new_list(cipher_list))) + } + + #[pymethod] + fn set_default_verify_paths(&self, vm: &VirtualMachine) -> PyResult<()> { + // Just call load_default_certs + self.load_default_certs(OptionalArg::Missing, vm) + } + + #[pymethod] + fn cert_store_stats(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // Use the certificate counters that are updated in load_verify_locations + let x509_count = *self.x509_cert_count.read() as i32; + let ca_count = *self.ca_cert_count.read() as i32; + + let dict = vm.ctx.new_dict(); + dict.set_item("x509", vm.ctx.new_int(x509_count).into(), vm)?; + dict.set_item("crl", vm.ctx.new_int(0).into(), vm)?; // CRL not supported + dict.set_item("x509_ca", vm.ctx.new_int(ca_count).into(), vm)?; + Ok(dict.into()) + } + + #[pymethod] + fn session_stats(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // Return session statistics + // NOTE: This is a partial implementation - rustls doesn't expose all OpenSSL stats + let dict = vm.ctx.new_dict(); + + // Number of sessions currently in the cache + let session_count = self.client_session_cache.read().len() as i32; + dict.set_item("number", vm.ctx.new_int(session_count).into(), vm)?; + + // Client-side statistics (not tracked separately in this implementation) + dict.set_item("connect", vm.ctx.new_int(0).into(), vm)?; + dict.set_item("connect_good", vm.ctx.new_int(0).into(), vm)?; + dict.set_item("connect_renegotiate", vm.ctx.new_int(0).into(), vm)?; // rustls doesn't support renegotiation + + // Server-side statistics + let accept_count = self.accept_count.load(Ordering::SeqCst) as i32; + dict.set_item("accept", vm.ctx.new_int(accept_count).into(), vm)?; + dict.set_item("accept_good", vm.ctx.new_int(accept_count).into(), vm)?; // Assume all accepts are good + dict.set_item("accept_renegotiate", vm.ctx.new_int(0).into(), vm)?; // rustls doesn't support renegotiation + + // Session reuse statistics + let hits = self.session_hits.load(Ordering::SeqCst) as i32; + dict.set_item("hits", vm.ctx.new_int(hits).into(), vm)?; + + // Misses, timeouts, and cache_full are not tracked in this implementation + dict.set_item("misses", vm.ctx.new_int(0).into(), vm)?; + dict.set_item("timeouts", vm.ctx.new_int(0).into(), vm)?; + dict.set_item("cache_full", vm.ctx.new_int(0).into(), vm)?; + + Ok(dict.into()) + } + + #[pygetset] fn sni_callback(&self) -> Option<PyObjectRef> { - self.sni_callback.lock().clone() + self.sni_callback.read().clone() } #[pygetset(setter)] fn set_sni_callback( &self, - value: Option<PyObjectRef>, + callback: Option<PyObjectRef>, vm: &VirtualMachine, ) -> PyResult<()> { - // Check if this is a server context - if self.protocol == SslVersion::TlsClient { - return Err(vm.new_value_error( - "sni_callback cannot be set on TLS_CLIENT context".to_owned(), - )); - } - - let mut callback_guard = self.sni_callback.lock(); - - if let Some(callback_obj) = value { - if !vm.is_none(&callback_obj) { - // Check if callable - if !callback_obj.is_callable() { - return Err(vm.new_type_error("not a callable object".to_owned())); - } - - // Set the callback - *callback_guard = Some(callback_obj); - - // Set OpenSSL callback - unsafe { - sys::SSL_CTX_set_tlsext_servername_callback__fixed_rust( - self.ctx().as_ptr(), - Some(_servername_callback), - ); - sys::SSL_CTX_set_tlsext_servername_arg( - self.ctx().as_ptr(), - self as *const _ as *mut _, - ); - } - } else { - // Clear callback - *callback_guard = None; - unsafe { - sys::SSL_CTX_set_tlsext_servername_callback__fixed_rust( - self.ctx().as_ptr(), - None, - ); - } - } - } else { - // Clear callback - *callback_guard = None; - unsafe { - sys::SSL_CTX_set_tlsext_servername_callback__fixed_rust( - self.ctx().as_ptr(), - None, - ); - } + // Validate callback is callable or None + if let Some(ref cb) = callback + && !cb.is(vm.ctx.types.none_type) + && !cb.is_callable() + { + return Err(vm.new_type_error("sni_callback must be callable or None")); } - + *self.sni_callback.write() = callback; Ok(()) } @@ -1422,156 +1697,258 @@ mod _ssl { callback: Option<PyObjectRef>, vm: &VirtualMachine, ) -> PyResult<()> { + // Alias for set_sni_callback self.set_sni_callback(callback, vm) } - #[pymethod] - fn load_cert_chain(&self, args: LoadCertChainArgs, vm: &VirtualMachine) -> PyResult<()> { - let LoadCertChainArgs { - certfile, - keyfile, - password, - } = args; - // TODO: requires passing a callback to C - if password.is_some() { - return Err(vm.new_not_implemented_error("password arg not yet supported")); - } - let mut ctx = self.builder(); - let key_path = keyfile.map(|path| path.to_path_buf(vm)).transpose()?; - let cert_path = certfile.to_path_buf(vm)?; - ctx.set_certificate_chain_file(&cert_path) - .and_then(|()| { - ctx.set_private_key_file( - key_path.as_ref().unwrap_or(&cert_path), - ssl::SslFiletype::PEM, - ) - }) - .and_then(|()| ctx.check_private_key()) - .map_err(|e| convert_openssl_error(vm, e)) + #[pygetset] + fn security_level(&self) -> i32 { + // rustls uses a fixed security level + // Return 2 which is a reasonable default (equivalent to OpenSSL 1.1.0+ level 2) + 2 + } + + #[pygetset] + fn _msg_callback(&self) -> Option<PyObjectRef> { + self.msg_callback.read().clone() + } + + #[pygetset(setter)] + fn set__msg_callback( + &self, + callback: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Validate callback is callable or None + if let Some(ref cb) = callback + && !cb.is(vm.ctx.types.none_type) + && !cb.is_callable() + { + return Err(vm.new_type_error("msg_callback must be callable or None")); + } + *self.msg_callback.write() = callback; + Ok(()) } #[pymethod] - fn _wrap_socket( - zelf: PyRef<Self>, - args: WrapSocketArgs, + fn get_ca_certs( + &self, + binary_form: OptionalArg<bool>, vm: &VirtualMachine, - ) -> PyResult<PyObjectRef> { - // validate socket type and context protocol - if !args.server_side && zelf.protocol == SslVersion::TlsServer { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Cannot create a client socket with a PROTOCOL_TLS_SERVER context".to_owned(), - )); + ) -> PyResult<PyListRef> { + let binary_form = binary_form.unwrap_or(false); + let ca_certs_der = self.ca_certs_der.read(); + + let mut certs = Vec::new(); + for cert_der in ca_certs_der.iter() { + // Parse certificate to check if it's a CA and get info + match x509_parser::parse_x509_certificate(cert_der) { + Ok((_, cert)) => { + // Check if this is a CA certificate (BasicConstraints: CA=TRUE) + let is_ca = if let Ok(Some(bc_ext)) = cert.basic_constraints() { + bc_ext.value.ca + } else { + false + }; + + // Only include CA certificates + if !is_ca { + continue; + } + + if binary_form { + // Return DER-encoded certificate as bytes + certs.push(vm.ctx.new_bytes(cert_der.clone()).into()); + } else { + // Return certificate as dict (use helper from _test_decode_cert) + let dict = self.cert_der_to_dict(vm, cert_der)?; + certs.push(dict); + } + } + Err(_) => { + // Skip invalid certificates + continue; + } + } } - if args.server_side && zelf.protocol == SslVersion::TlsClient { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context".to_owned(), + + Ok(PyListRef::from(vm.ctx.new_list(certs))) + } + + #[pymethod] + fn load_dh_params(&self, filepath: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // Validate filepath is not None + if vm.is_none(&filepath) { + return Err(vm.new_type_error("DH params filepath cannot be None".to_owned())); + } + + // Validate filepath is str or bytes + let path_str = if let Ok(s) = PyStrRef::try_from_object(vm, filepath.clone()) { + s.as_str().to_owned() + } else if let Ok(b) = ArgBytesLike::try_from_object(vm, filepath) { + String::from_utf8(b.borrow_buf().to_vec()) + .map_err(|_| vm.new_value_error("Invalid path encoding".to_owned()))? + } else { + return Err(vm.new_type_error("DH params filepath must be str or bytes".to_owned())); + }; + + // Check if file exists + if !std::path::Path::new(&path_str).exists() { + // Create FileNotFoundError with errno=ENOENT (2) using args + let exc = vm.new_exception( + vm.ctx.exceptions.file_not_found_error.to_owned(), + vec![ + vm.ctx.new_int(2).into(), // errno = ENOENT (2) + vm.ctx.new_str("No such file or directory").into(), + vm.ctx.new_str(path_str.clone()).into(), // filename + ], + ); + return Err(exc); + } + + // Validate that the file contains DH parameters + // Read the file and check for DH PARAMETERS header + let contents = + std::fs::read_to_string(&path_str).map_err(|e| vm.new_os_error(e.to_string()))?; + + if !contents.contains("BEGIN DH PARAMETERS") + && !contents.contains("BEGIN X9.42 DH PARAMETERS") + { + // File exists but doesn't contain DH parameters - raise SSLError + // [PEM: NO_START_LINE] no start line + return Err(super::compat::SslError::create_ssl_error_with_reason( + vm, + "PEM", + "NO_START_LINE", + "[PEM: NO_START_LINE] no start line", )); } - let mut ssl = ssl::Ssl::new(&zelf.ctx()).map_err(|e| convert_openssl_error(vm, e))?; + // rustls doesn't use DH parameters (it uses ECDHE for key exchange) + // This is a no-op for compatibility with OpenSSL-based code + Ok(()) + } + + #[pymethod] + fn set_ecdh_curve(&self, name: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // Validate name is not None + if vm.is_none(&name) { + return Err(vm.new_type_error("ECDH curve name cannot be None".to_owned())); + } - let socket_type = if args.server_side { - ssl.set_accept_state(); - SslServerOrClient::Server + // Validate name is str or bytes + let curve_name = if let Ok(s) = PyStrRef::try_from_object(vm, name.clone()) { + s.as_str().to_owned() + } else if let Ok(b) = ArgBytesLike::try_from_object(vm, name) { + String::from_utf8(b.borrow_buf().to_vec()) + .map_err(|_| vm.new_value_error("Invalid curve name encoding".to_owned()))? } else { - ssl.set_connect_state(); - SslServerOrClient::Client + return Err(vm.new_type_error("ECDH curve name must be str or bytes".to_owned())); }; - if let Some(hostname) = &args.server_hostname { - let hostname = hostname.as_str(); - if hostname.is_empty() || hostname.starts_with('.') { - return Err(vm.new_value_error( - "server_hostname cannot be an empty string or start with a leading dot.", - )); - } - if hostname.contains('\0') { - return Err(vm.new_value_error("embedded null byte in server_hostname")); - } - let ip = hostname.parse::<std::net::IpAddr>(); - if ip.is_err() { - ssl.set_hostname(hostname) - .map_err(|e| convert_openssl_error(vm, e))?; - } - if zelf.check_hostname.load() { - if let Ok(ip) = ip { - ssl.param_mut() - .set_ip(ip) - .map_err(|e| convert_openssl_error(vm, e))?; - } else { - ssl.param_mut() - .set_host(hostname) - .map_err(|e| convert_openssl_error(vm, e))?; - } - } + // Validate curve name (common curves for compatibility) + // rustls supports: X25519, secp256r1 (prime256v1), secp384r1 + let valid_curves = [ + "prime256v1", + "secp256r1", + "prime384v1", + "secp384r1", + "prime521v1", + "secp521r1", + "X25519", + "x25519", + "x448", // For future compatibility + ]; + + if !valid_curves.contains(&curve_name.as_str()) { + return Err(vm.new_value_error(format!("unknown curve name '{curve_name}'"))); } - // Configure post-handshake authentication (PHA) - #[cfg(ossl111)] - if *zelf.post_handshake_auth.lock() { - unsafe { - if args.server_side { - // Server socket: add SSL_VERIFY_POST_HANDSHAKE flag - // Only in combination with SSL_VERIFY_PEER - let mode = sys::SSL_get_verify_mode(ssl.as_ptr()); - if (mode & sys::SSL_VERIFY_PEER as libc::c_int) != 0 { - // Add POST_HANDSHAKE flag (keep existing flags including FAIL_IF_NO_PEER_CERT) - sys::SSL_set_verify( - ssl.as_ptr(), - mode | SSL_VERIFY_POST_HANDSHAKE, - None, - ); - } - } else { - // Client socket: call SSL_set_post_handshake_auth - SSL_set_post_handshake_auth(ssl.as_ptr(), 1); + // Store the curve name to be used during handshake + // This will limit the key exchange groups offered/accepted + *self.ecdh_curve.write() = Some(curve_name); + Ok(()) + } + + #[pymethod] + fn _wrap_socket( + zelf: PyRef<Self>, + args: WrapSocketArgs, + vm: &VirtualMachine, + ) -> PyResult<PyRef<PySSLSocket>> { + // Convert server_hostname to Option<String> + // Handle both missing argument and None value + let hostname = match args.server_hostname.into_option().flatten() { + Some(hostname_str) => { + let hostname = hostname_str.as_str(); + + // Validate hostname + if hostname.is_empty() { + return Err(vm.new_value_error("server_hostname cannot be an empty string")); } - } - } - let stream = ssl::SslStream::new(ssl, SocketStream(args.sock.clone())) - .map_err(|e| convert_openssl_error(vm, e))?; + // Check if it starts with a dot + if hostname.starts_with('.') { + return Err(vm.new_value_error("server_hostname cannot start with a dot")); + } - let py_ssl_socket = PySslSocket { - ctx: PyRwLock::new(zelf.clone()), - connection: PyRwLock::new(SslConnection::Socket(stream)), - socket_type, - server_hostname: args.server_hostname, - owner: PyRwLock::new(args.owner.map(|o| o.downgrade(None, vm)).transpose()?), - }; + // Check if it's a bare IP address (not allowed for SNI) + if hostname.parse::<std::net::IpAddr>().is_ok() { + return Err(vm.new_value_error("server_hostname cannot be an IP address")); + } + + // Check for NULL bytes + if hostname.contains('\0') { + return Err(vm.new_type_error("embedded null character")); + } - // Convert to PyRef (heap allocation) to avoid use-after-free - let py_ref = - py_ssl_socket.into_ref_with_type(vm, PySslSocket::class(&vm.ctx).to_owned())?; - - // Set SNI callback data if callback is configured - if zelf.sni_callback.lock().is_some() { - unsafe { - let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); - - // Store callback data in SSL ex_data - let callback_data = Box::new(SniCallbackData { - ssl_context: zelf.clone(), - vm_ptr: vm as *const _, - }); - let idx = get_sni_ex_data_index(); - sys::SSL_set_ex_data(ssl_ptr, idx, Box::into_raw(callback_data) as *mut _); - - // Store PyRef pointer (heap-allocated) in ex_data index 0 - sys::SSL_set_ex_data(ssl_ptr, 0, &*py_ref as *const _ as *mut _); + Some(hostname.to_string()) } - } + None => None, + }; - // Set session if provided - if let Some(session) = args.session - && !vm.is_none(&session) - { - py_ref.set_session(session, vm)?; + // Validate socket type and context protocol + if args.server_side && zelf.protocol == PROTOCOL_TLS_CLIENT { + return Err(vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context".to_owned(), + )); + } + if !args.server_side && zelf.protocol == PROTOCOL_TLS_SERVER { + return Err(vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + "Cannot create a client socket with a PROTOCOL_TLS_SERVER context".to_owned(), + )); } - Ok(py_ref.into()) + // Create _SSLSocket instance + let ssl_socket = PySSLSocket { + sock: args.sock.clone(), + context: PyRwLock::new(zelf), + server_side: args.server_side, + server_hostname: PyRwLock::new(hostname), + connection: PyMutex::new(None), + handshake_done: PyMutex::new(false), + session_was_reused: PyMutex::new(false), + owner: PyRwLock::new(args.owner.into_option()), + // Filter out Python None objects - only store actual SSLSession objects + session: PyRwLock::new(args.session.into_option().filter(|s| !vm.is_none(s))), + verified_chain: PyRwLock::new(None), + incoming_bio: None, + outgoing_bio: None, + sni_state: PyRwLock::new(None), + pending_context: PyRwLock::new(None), + client_hello_buffer: PyMutex::new(None), + shutdown_state: PyMutex::new(ShutdownState::NotStarted), + deferred_cert_error: Arc::new(ParkingRwLock::new(None)), + }; + + // Create PyRef with correct type + let ssl_socket_ref = ssl_socket + .into_ref_with_type(vm, vm.class("_ssl", "_SSLSocket")) + .map_err(|_| vm.new_type_error("Failed to create SSLSocket"))?; + + Ok(ssl_socket_ref) } #[pymethod] @@ -1579,1911 +1956,2546 @@ mod _ssl { zelf: PyRef<Self>, args: WrapBioArgs, vm: &VirtualMachine, - ) -> PyResult<PyObjectRef> { - // validate socket type and context protocol - if !args.server_side && zelf.protocol == SslVersion::TlsServer { + ) -> PyResult<PyRef<PySSLSocket>> { + // Convert server_hostname to Option<String> + // Handle both missing argument and None value + let hostname = match args.server_hostname.into_option().flatten() { + Some(hostname_str) => { + let hostname = hostname_str.as_str(); + validate_hostname(hostname, vm)?; + Some(hostname.to_string()) + } + None => None, + }; + + // Extract server_side value + let server_side = args.server_side.unwrap_or(false); + + // Validate socket type and context protocol + if server_side && zelf.protocol == PROTOCOL_TLS_CLIENT { return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Cannot create a client socket with a PROTOCOL_TLS_SERVER context".to_owned(), + PySSLError::class(&vm.ctx).to_owned(), + "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context".to_owned(), )); } - if args.server_side && zelf.protocol == SslVersion::TlsClient { + if !server_side && zelf.protocol == PROTOCOL_TLS_SERVER { return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context".to_owned(), + PySSLError::class(&vm.ctx).to_owned(), + "Cannot create a client socket with a PROTOCOL_TLS_SERVER context".to_owned(), )); } - let mut ssl = ssl::Ssl::new(&zelf.ctx()).map_err(|e| convert_openssl_error(vm, e))?; + // Create _SSLSocket instance with BIO mode + let ssl_socket = PySSLSocket { + sock: vm.ctx.none(), // No socket in BIO mode + context: PyRwLock::new(zelf), + server_side, + server_hostname: PyRwLock::new(hostname), + connection: PyMutex::new(None), + handshake_done: PyMutex::new(false), + session_was_reused: PyMutex::new(false), + owner: PyRwLock::new(args.owner.into_option()), + // Filter out Python None objects - only store actual SSLSession objects + session: PyRwLock::new(args.session.into_option().filter(|s| !vm.is_none(s))), + verified_chain: PyRwLock::new(None), + incoming_bio: Some(args.incoming), + outgoing_bio: Some(args.outgoing), + sni_state: PyRwLock::new(None), + pending_context: PyRwLock::new(None), + client_hello_buffer: PyMutex::new(None), + shutdown_state: PyMutex::new(ShutdownState::NotStarted), + deferred_cert_error: Arc::new(ParkingRwLock::new(None)), + }; + + let ssl_socket_ref = ssl_socket + .into_ref_with_type(vm, vm.class("_ssl", "_SSLSocket")) + .map_err(|_| vm.new_type_error("Failed to create SSLSocket"))?; + + Ok(ssl_socket_ref) + } + + // Helper functions (private): - let socket_type = if args.server_side { - ssl.set_accept_state(); - SslServerOrClient::Server + /// Parse path argument (str or bytes) to string + fn parse_path_arg(arg: &PyObjectRef, vm: &VirtualMachine) -> PyResult<String> { + if let Ok(s) = PyStrRef::try_from_object(vm, arg.clone()) { + Ok(s.as_str().to_owned()) + } else if let Ok(b) = ArgBytesLike::try_from_object(vm, arg.clone()) { + String::from_utf8(b.borrow_buf().to_vec()) + .map_err(|_| vm.new_value_error("path contains invalid UTF-8".to_owned())) } else { - ssl.set_connect_state(); - SslServerOrClient::Client - }; + Err(vm.new_type_error("path should be a str or bytes".to_owned())) + } + } - if let Some(hostname) = &args.server_hostname { - let hostname = hostname.as_str(); - if hostname.is_empty() || hostname.starts_with('.') { - return Err(vm.new_value_error( - "server_hostname cannot be an empty string or start with a leading dot.", - )); - } - if hostname.contains('\0') { - return Err(vm.new_value_error("embedded null byte in server_hostname")); - } - let ip = hostname.parse::<std::net::IpAddr>(); - if ip.is_err() { - ssl.set_hostname(hostname) - .map_err(|e| convert_openssl_error(vm, e))?; - } - if zelf.check_hostname.load() { - if let Ok(ip) = ip { - ssl.param_mut() - .set_ip(ip) - .map_err(|e| convert_openssl_error(vm, e))?; + /// Parse password argument (str, bytes-like, or callable) + /// + /// Returns (immediate_password, callable) where: + /// - immediate_password: Some(string) if password is str/bytes, None if callable + /// - callable: Some(PyObjectRef) if password is callable, None otherwise + fn parse_password_argument( + password: &OptionalArg<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<(Option<String>, Option<PyObjectRef>)> { + match password { + OptionalArg::Present(p) => { + // Try string first + if let Ok(pwd_str) = PyStrRef::try_from_object(vm, p.clone()) { + Ok((Some(pwd_str.as_str().to_owned()), None)) + } + // Try bytes-like + else if let Ok(pwd_bytes_like) = ArgBytesLike::try_from_object(vm, p.clone()) + { + let pwd = String::from_utf8(pwd_bytes_like.borrow_buf().to_vec()).map_err( + |_| vm.new_type_error("password bytes must be valid UTF-8".to_owned()), + )?; + Ok((Some(pwd), None)) + } + // Try callable + else if p.is_callable() { + Ok((None, Some(p.clone()))) } else { - ssl.param_mut() - .set_host(hostname) - .map_err(|e| convert_openssl_error(vm, e))?; + Err(vm.new_type_error( + "password should be a string, bytes, or callable".to_owned(), + )) } } + _ => Ok((None, None)), } + } - // Don't use SSL_set_bio - let SslStream drive I/O through BioStream Read/Write - - // Configure post-handshake authentication (PHA) - #[cfg(ossl111)] - if *zelf.post_handshake_auth.lock() { - unsafe { - if args.server_side { - // Server socket: add SSL_VERIFY_POST_HANDSHAKE flag - // Only in combination with SSL_VERIFY_PEER - let mode = sys::SSL_get_verify_mode(ssl.as_ptr()); - if (mode & sys::SSL_VERIFY_PEER as libc::c_int) != 0 { - // Add POST_HANDSHAKE flag (keep existing flags including FAIL_IF_NO_PEER_CERT) - sys::SSL_set_verify( - ssl.as_ptr(), - mode | SSL_VERIFY_POST_HANDSHAKE, - None, - ); - } - } else { - // Client socket: call SSL_set_post_handshake_auth - SSL_set_post_handshake_auth(ssl.as_ptr(), 1); + /// Helper: Load certificates from file into existing store + fn load_certs_from_file_helper( + &self, + root_store: &mut RootCertStore, + ca_certs_der: &mut Vec<Vec<u8>>, + path: &str, + vm: &VirtualMachine, + ) -> PyResult<cert::CertStats> { + let mut loader = cert::CertLoader::new(root_store, ca_certs_der); + loader.load_from_file(path).map_err(|e| { + // Preserve errno for file access errors (NotFound, PermissionDenied) + match e.kind() { + std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => { + e.into_pyexception(vm) } + // PEM parsing errors + _ => super::compat::SslError::create_ssl_error_with_reason( + vm, "X509", "", "PEM lib", + ), } - } + }) + } - // Create a BioStream wrapper (dummy, actual IO goes through BIOs) - let bio_stream = BioStream { - inbio: args.incoming, - outbio: args.outgoing, - }; + /// Helper: Load certificates from directory into existing store + fn load_certs_from_dir_helper( + &self, + root_store: &mut RootCertStore, + path: &str, + vm: &VirtualMachine, + ) -> PyResult<cert::CertStats> { + // Load certs and store them in capath_certs_der for lazy loading simulation + // (CPython only returns these in get_ca_certs() after they're used in handshake) + let mut capath_certs = Vec::new(); + let mut loader = cert::CertLoader::new(root_store, &mut capath_certs); + let stats = loader + .load_from_dir(path) + .map_err(|e| e.into_pyexception(vm))?; + + // Store loaded certs for potential tracking after handshake + *self.capath_certs_der.write() = capath_certs; + + Ok(stats) + } + + /// Helper: Load certificates from bytes into existing store + fn load_certs_from_bytes_helper( + &self, + root_store: &mut RootCertStore, + ca_certs_der: &mut Vec<Vec<u8>>, + data: &[u8], + pem_only: bool, + vm: &VirtualMachine, + ) -> PyResult<cert::CertStats> { + let mut loader = cert::CertLoader::new(root_store, ca_certs_der); + // treat_all_as_ca=true: CPython counts all certificates loaded via cadata as CA certs + // regardless of their Basic Constraints extension + // pem_only=true for string input + loader + .load_from_bytes_ex(data, true, pem_only) + .map_err(|e| { + // Preserve specific error messages from cert.rs + let err_msg = e.to_string(); + if err_msg.contains("no start line") { + // no start line: cadata does not contain a certificate + vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + "no start line: cadata does not contain a certificate".to_string(), + ) + } else if err_msg.contains("not enough data") { + // not enough data: cadata does not contain a certificate + vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + "not enough data: cadata does not contain a certificate".to_string(), + ) + } else { + // Generic PEM error + vm.new_exception_msg(PySSLError::class(&vm.ctx).to_owned(), err_msg) + } + }) + } - // Create SslStream with BioStream - let stream = - ssl::SslStream::new(ssl, bio_stream).map_err(|e| convert_openssl_error(vm, e))?; + /// Helper: Try to parse data as CRL (PEM or DER format) + fn try_parse_crl( + &self, + data: &[u8], + ) -> Result<CertificateRevocationListDer<'static>, String> { + // Try PEM format first + let mut cursor = std::io::Cursor::new(data); + let mut crl_iter = rustls_pemfile::crls(&mut cursor); + if let Some(Ok(crl)) = crl_iter.next() { + return Ok(crl); + } - let py_ssl_socket = PySslSocket { - ctx: PyRwLock::new(zelf.clone()), - connection: PyRwLock::new(SslConnection::Bio(stream)), - socket_type, - server_hostname: args.server_hostname, - owner: PyRwLock::new(args.owner.map(|o| o.downgrade(None, vm)).transpose()?), - }; + // Try DER format + // Basic validation: CRL should start with SEQUENCE tag (0x30) + if !data.is_empty() && data[0] == 0x30 { + return Ok(CertificateRevocationListDer::from(data.to_vec())); + } - // Convert to PyRef (heap allocation) to avoid use-after-free - let py_ref = - py_ssl_socket.into_ref_with_type(vm, PySslSocket::class(&vm.ctx).to_owned())?; - - // Set SNI callback data if callback is configured - if zelf.sni_callback.lock().is_some() { - unsafe { - let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); - - // Store callback data in SSL ex_data - let callback_data = Box::new(SniCallbackData { - ssl_context: zelf.clone(), - vm_ptr: vm as *const _, - }); - let idx = get_sni_ex_data_index(); - sys::SSL_set_ex_data(ssl_ptr, idx, Box::into_raw(callback_data) as *mut _); - - // Store PyRef pointer (heap-allocated) in ex_data index 0 - sys::SSL_set_ex_data(ssl_ptr, 0, &*py_ref as *const _ as *mut _); + Err("Not a valid CRL file".to_string()) + } + + /// Helper: Load CRL from file + fn load_crl_from_file( + &self, + path: &str, + vm: &VirtualMachine, + ) -> PyResult<Option<CertificateRevocationListDer<'static>>> { + let data = std::fs::read(path).map_err(|e| match e.kind() { + std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => { + e.into_pyexception(vm) } + _ => vm.new_os_error(e.to_string()), + })?; + + match self.try_parse_crl(&data) { + Ok(crl) => Ok(Some(crl)), + Err(_) => Ok(None), // Not a CRL file, might be a cert file } + } - // Set session if provided - if let Some(session) = args.session - && !vm.is_none(&session) - { - py_ref.set_session(session, vm)?; + /// Helper: Parse cadata argument (str or bytes) + fn parse_cadata_arg(&self, arg: &PyObjectRef, vm: &VirtualMachine) -> PyResult<Vec<u8>> { + if let Ok(s) = PyStrRef::try_from_object(vm, arg.clone()) { + Ok(s.as_str().as_bytes().to_vec()) + } else if let Ok(b) = ArgBytesLike::try_from_object(vm, arg.clone()) { + Ok(b.borrow_buf().to_vec()) + } else { + Err(vm.new_type_error("cadata should be a str or bytes".to_owned())) } + } - Ok(py_ref.into()) + /// Helper: Update certificate statistics + fn update_cert_stats(&self, stats: cert::CertStats) { + *self.x509_cert_count.write() += stats.total_certs; + *self.ca_cert_count.write() += stats.ca_certs; } } - #[derive(FromArgs)] - #[allow(dead_code)] // Fields will be used when _wrap_bio is fully implemented - struct WrapBioArgs { - incoming: PyRef<PySslMemoryBio>, - outgoing: PyRef<PySslMemoryBio>, - server_side: bool, - #[pyarg(any, default)] - server_hostname: Option<PyStrRef>, - #[pyarg(named, default)] - owner: Option<PyObjectRef>, - #[pyarg(named, default)] - session: Option<PyObjectRef>, - } + impl Constructor for PySSLContext { + type Args = (i32,); - #[derive(FromArgs)] - struct WrapSocketArgs { - sock: PyRef<PySocket>, - server_side: bool, - #[pyarg(any, default)] - server_hostname: Option<PyStrRef>, - #[pyarg(named, default)] - owner: Option<PyObjectRef>, - #[pyarg(named, default)] - session: Option<PyObjectRef>, - } + fn py_new(cls: PyTypeRef, (protocol,): Self::Args, vm: &VirtualMachine) -> PyResult { + // Validate protocol + match protocol { + PROTOCOL_TLS | PROTOCOL_TLS_CLIENT | PROTOCOL_TLS_SERVER | PROTOCOL_TLSv1_2 + | PROTOCOL_TLSv1_3 => { + // Valid protocols + } + PROTOCOL_TLSv1 | PROTOCOL_TLSv1_1 => { + return Err(vm.new_value_error( + "TLS 1.0 and 1.1 are not supported by rustls for security reasons", + )); + } + _ => { + return Err(vm.new_value_error(format!("invalid protocol version: {protocol}"))); + } + } - #[derive(FromArgs)] - struct LoadVerifyLocationsArgs { - #[pyarg(any, default)] - cafile: Option<FsPath>, - #[pyarg(any, default)] - capath: Option<FsPath>, - #[pyarg(any, default)] - cadata: Option<Either<PyStrRef, ArgBytesLike>>, - } + // Set default options + // OP_ALL | OP_NO_SSLv2 | OP_NO_SSLv3 | OP_NO_COMPRESSION | + // OP_CIPHER_SERVER_PREFERENCE | OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE | + // OP_ENABLE_MIDDLEBOX_COMPAT + let default_options = OP_ALL + | OP_NO_SSLv2 + | OP_NO_SSLv3 + | OP_NO_COMPRESSION + | OP_CIPHER_SERVER_PREFERENCE + | OP_SINGLE_DH_USE + | OP_SINGLE_ECDH_USE + | OP_ENABLE_MIDDLEBOX_COMPAT; + + // Set default verify_mode based on protocol + // PROTOCOL_TLS_CLIENT defaults to CERT_REQUIRED + // PROTOCOL_TLS_SERVER defaults to CERT_NONE + let default_verify_mode = if protocol == PROTOCOL_TLS_CLIENT { + CERT_REQUIRED + } else { + CERT_NONE + }; - #[derive(FromArgs)] - struct LoadCertChainArgs { - certfile: FsPath, - #[pyarg(any, optional)] - keyfile: Option<FsPath>, - #[pyarg(any, optional)] - password: Option<Either<PyStrRef, ArgCallable>>, - } + // Set default verify_flags based on protocol + // Both PROTOCOL_TLS_CLIENT and PROTOCOL_TLS_SERVER only set VERIFY_X509_TRUSTED_FIRST + // Note: VERIFY_X509_PARTIAL_CHAIN and VERIFY_X509_STRICT are NOT set here + // - they're only added by create_default_context() in Python's ssl.py + let default_verify_flags = VERIFY_DEFAULT | VERIFY_X509_TRUSTED_FIRST; + + // Set minimum and maximum protocol versions based on protocol constant + // specific protocol versions fix both min and max + let (min_version, max_version) = match protocol { + PROTOCOL_TLSv1_2 => (PROTO_TLSv1_2, PROTO_TLSv1_2), // Only TLS 1.2 + PROTOCOL_TLSv1_3 => (PROTO_TLSv1_3, PROTO_TLSv1_3), // Only TLS 1.3 + _ => (PROTO_MINIMUM_SUPPORTED, PROTO_MAXIMUM_SUPPORTED), // Auto-negotiate + }; - // Err is true if the socket is blocking - type SocketDeadline = Result<Instant, bool>; + // IMPORTANT: Create shared session cache BEFORE PySSLContext + // Both client_session_cache and PythonClientSessionStore.session_cache + // MUST point to the same HashMap to ensure Python-level and Rustls-level + // sessions are synchronized + let shared_session_cache = Arc::new(ParkingRwLock::new(HashMap::new())); + let rustls_client_store = Arc::new(PythonClientSessionStore { + inner: Arc::new(rustls::client::ClientSessionMemoryCache::new( + SSL_SESSION_CACHE_SIZE, + )), + session_cache: shared_session_cache.clone(), + }); - enum SelectRet { - Nonblocking, - TimedOut, - IsBlocking, - Closed, - Ok, + PySSLContext { + protocol, + check_hostname: PyRwLock::new(protocol == PROTOCOL_TLS_CLIENT), + verify_mode: PyRwLock::new(default_verify_mode), + verify_flags: PyRwLock::new(default_verify_flags), + client_config: PyRwLock::new(None), + server_config: PyRwLock::new(None), + root_certs: PyRwLock::new(RootCertStore::empty()), + ca_certs_der: PyRwLock::new(Vec::new()), + capath_certs_der: PyRwLock::new(Vec::new()), + crls: PyRwLock::new(Vec::new()), + cert_keys: PyRwLock::new(Vec::new()), + options: PyRwLock::new(default_options), + alpn_protocols: PyRwLock::new(Vec::new()), + require_alpn_match: PyRwLock::new(false), + post_handshake_auth: PyRwLock::new(false), + num_tickets: PyRwLock::new(2), // TLS 1.3 default + minimum_version: PyRwLock::new(min_version), + maximum_version: PyRwLock::new(max_version), + sni_callback: PyRwLock::new(None), + msg_callback: PyRwLock::new(None), + ecdh_curve: PyRwLock::new(None), + ca_cert_count: PyRwLock::new(0), + x509_cert_count: PyRwLock::new(0), + // Use the shared cache created above + client_session_cache: shared_session_cache, + rustls_session_store: rustls_client_store, + rustls_server_session_store: rustls::server::ServerSessionMemoryCache::new( + SSL_SESSION_CACHE_SIZE, + ), + server_ticketer: rustls::crypto::aws_lc_rs::Ticketer::new() + .expect("Failed to create shared ticketer for TLS 1.2 session resumption"), + accept_count: AtomicUsize::new(0), + session_hits: AtomicUsize::new(0), + selected_ciphers: PyRwLock::new(None), + } + .into_ref_with_type(vm, cls) + .map(Into::into) + } } - #[derive(Clone, Copy)] - enum SslNeeds { - Read, - Write, + // SSLSocket - represents a TLS-wrapped socket + #[pyattr] + #[pyclass(name = "_SSLSocket", module = "ssl")] + #[derive(Debug, PyPayload)] + pub(crate) struct PySSLSocket { + // Underlying socket + sock: PyObjectRef, + // SSL context + context: PyRwLock<PyRef<PySSLContext>>, + // Server-side or client-side + server_side: bool, + // Server hostname for SNI + server_hostname: PyRwLock<Option<String>>, + // TLS connection state + connection: PyMutex<Option<TlsConnection>>, + // Handshake completed flag + handshake_done: PyMutex<bool>, + // Session was reused (for session resumption tracking) + session_was_reused: PyMutex<bool>, + // Owner (SSLSocket instance that owns this _SSLSocket) + owner: PyRwLock<Option<PyObjectRef>>, + // Session for resumption + session: PyRwLock<Option<PyObjectRef>>, + // Verified certificate chain (built during verification) + #[allow(dead_code)] + verified_chain: PyRwLock<Option<Vec<CertificateDer<'static>>>>, + // MemoryBIO mode (optional) + incoming_bio: Option<PyRef<PyMemoryBIO>>, + outgoing_bio: Option<PyRef<PyMemoryBIO>>, + // SNI certificate resolver state (for server-side only) + sni_state: PyRwLock<Option<Arc<ParkingMutex<SniCertName>>>>, + // Pending context change (for SNI callback deferred handling) + pending_context: PyRwLock<Option<PyRef<PySSLContext>>>, + // Buffer to store ClientHello for connection recreation + client_hello_buffer: PyMutex<Option<Vec<u8>>>, + // Shutdown state for tracking close-notify exchange + shutdown_state: PyMutex<ShutdownState>, + // Deferred client certificate verification error (for TLS 1.3) + // Stores error message if client cert verification failed during handshake + // Error is raised on first I/O operation after handshake + // Using Arc to share with the certificate verifier + deferred_cert_error: Arc<ParkingRwLock<Option<String>>>, } - struct SocketStream(PyRef<PySocket>); + // Shutdown state for tracking close-notify exchange + #[derive(Debug, Clone, Copy, PartialEq)] + enum ShutdownState { + NotStarted, // unwrap() not called yet + SentCloseNotify, // close-notify sent, waiting for peer's response + Completed, // unwrap() completed successfully + } - impl SocketStream { - fn timeout_deadline(&self) -> SocketDeadline { - self.0.get_timeout().map(|d| Instant::now() + d) + #[pyclass(with(Constructor), flags(BASETYPE))] + impl PySSLSocket { + // Check if this is BIO mode + pub(crate) fn is_bio_mode(&self) -> bool { + self.incoming_bio.is_some() && self.outgoing_bio.is_some() } - fn select(&self, needs: SslNeeds, deadline: &SocketDeadline) -> SelectRet { - let sock = match self.0.sock_opt() { - Some(s) => s, - None => return SelectRet::Closed, - }; - let deadline = match &deadline { - Ok(deadline) => match deadline.checked_duration_since(Instant::now()) { - Some(deadline) => deadline, - None => return SelectRet::TimedOut, - }, - Err(true) => return SelectRet::IsBlocking, - Err(false) => return SelectRet::Nonblocking, - }; - let res = socket::sock_select( - &sock, - match needs { - SslNeeds::Read => socket::SelectKind::Read, - SslNeeds::Write => socket::SelectKind::Write, - }, - Some(deadline), - ); - match res { - Ok(true) => SelectRet::TimedOut, - _ => SelectRet::Ok, - } + // Get incoming BIO reference (for EOF checking) + pub(crate) fn incoming_bio(&self) -> Option<PyObjectRef> { + self.incoming_bio.as_ref().map(|bio| bio.clone().into()) } - fn socket_needs( - &self, - err: &ssl::Error, - deadline: &SocketDeadline, - ) -> (Option<SslNeeds>, SelectRet) { - let needs = match err.code() { - ssl::ErrorCode::WANT_READ => Some(SslNeeds::Read), - ssl::ErrorCode::WANT_WRITE => Some(SslNeeds::Write), - _ => None, - }; - let state = needs.map_or(SelectRet::Ok, |needs| self.select(needs, deadline)); - (needs, state) + // Check for deferred certificate verification errors (TLS 1.3) + // If an error exists, raise it and clear it from storage + fn check_deferred_cert_error(&self, vm: &VirtualMachine) -> PyResult<()> { + let error_opt = self.deferred_cert_error.read().clone(); + if let Some(error_msg) = error_opt { + // Clear the error so it's only raised once + *self.deferred_cert_error.write() = None; + // Raise OSError with the stored error message + return Err(vm.new_os_error(error_msg)); + } + Ok(()) } - } - fn socket_closed_error(vm: &VirtualMachine) -> PyBaseExceptionRef { - vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Underlying socket has been closed.".to_owned(), - ) - } + // Get socket timeout as Duration + pub(crate) fn get_socket_timeout(&self, vm: &VirtualMachine) -> PyResult<Option<Duration>> { + if self.is_bio_mode() { + return Ok(None); + } - // BIO stream wrapper to implement Read/Write traits for MemoryBIO - struct BioStream { - inbio: PyRef<PySslMemoryBio>, - outbio: PyRef<PySslMemoryBio>, - } + // Get timeout from socket + let timeout_obj = self.sock.get_attr("gettimeout", vm)?.call((), vm)?; - impl Read for BioStream { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - // Read from incoming MemoryBIO - unsafe { - let nbytes = sys::BIO_read( - self.inbio.bio, - buf.as_mut_ptr() as *mut _, - buf.len().min(i32::MAX as usize) as i32, - ); - if nbytes < 0 { - // BIO_read returns -1 on error or when no data is available - // Check if it's a retry condition (WANT_READ) - Err(std::io::Error::new( - std::io::ErrorKind::WouldBlock, - "BIO has no data available", - )) + // timeout can be None (blocking), 0.0 (non-blocking), or positive float + if vm.is_none(&timeout_obj) { + // None means blocking forever + Ok(None) + } else { + let timeout_float: f64 = timeout_obj.try_into_value(vm)?; + if timeout_float <= 0.0 { + // 0 means non-blocking + Ok(Some(Duration::from_secs(0))) } else { - Ok(nbytes as usize) + // Positive timeout + Ok(Some(Duration::from_secs_f64(timeout_float))) } } } - } - impl Write for BioStream { - fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { - // Write to outgoing MemoryBIO - unsafe { - let nbytes = sys::BIO_write( - self.outbio.bio, - buf.as_ptr() as *const _, - buf.len().min(i32::MAX as usize) as i32, - ); - if nbytes < 0 { - return Err(std::io::Error::other("BIO write failed")); + // Create and store a session object after successful handshake + fn create_session_after_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { + // Only create session for client-side connections + if self.server_side { + return Ok(()); + } + + // Check if session already exists + let session_opt = self.session.read().clone(); + if let Some(ref s) = session_opt { + if vm.is_none(s) { + } else { + return Ok(()); } - Ok(nbytes as usize) } - } - fn flush(&mut self) -> std::io::Result<()> { - // MemoryBIO doesn't need flushing + // Get server hostname + let server_name = self.server_hostname.read().clone(); + + // Try to get session data from context's session cache + // IMPORTANT: Acquire and release locks quickly to avoid deadlock + let context = self.context.read(); + let session_cache_arc = context.client_session_cache.clone(); + drop(context); // Release context lock ASAP + + let (session_id, creation_time, lifetime) = if let Some(ref name) = server_name { + let key = name.as_bytes().to_vec(); + + // Clone the data we need while holding the lock, then immediately release + let session_data_opt = { + let cache_guard = session_cache_arc.read(); + cache_guard.get(&key).cloned() // Clone Arc<PyMutex<SessionData>> + }; // Lock released here + + if let Some(session_data_arc) = session_data_opt { + let data = session_data_arc.lock(); + let result = (data.session_id.clone(), data.creation_time, data.lifetime); + drop(data); // Explicit unlock + result + } else { + // Create new session ID if not in cache + let time = std::time::SystemTime::now(); + (generate_session_id_from_metadata(name, &time), time, 7200) + } + } else { + // No server name, use defaults + let time = std::time::SystemTime::now(); + (vec![0; 16], time, 7200) + }; + + // Create a new SSLSession object with real metadata + let session = PySSLSession { + // Use dummy session data to indicate we have a ticket + // TLS 1.2+ always uses session tickets/resumption + session_data: vec![1], // Non-empty to indicate has_ticket=True + session_id, + creation_time, + lifetime, + }; + + let py_session = session.into_pyobject(vm); + + *self.session.write() = Some(py_session); + Ok(()) } - } - // Enum to represent different SSL connection modes - enum SslConnection { - Socket(ssl::SslStream<SocketStream>), - Bio(ssl::SslStream<BioStream>), - } + // Complete handshake and create session + /// Track which CA certificate from capath was used to verify peer + /// + /// This simulates lazy loading behavior: capath certificates + /// are only added to get_ca_certs() after they're actually used in a handshake. + fn track_used_ca_from_capath(&self) -> Result<(), String> { + let context = self.context.read(); + let capath_certs = context.capath_certs_der.read(); + + // No capath certs to track + if capath_certs.is_empty() { + return Ok(()); + } - impl SslConnection { - // Get a reference to the SSL object - fn ssl(&self) -> &ssl::SslRef { - match self { - SslConnection::Socket(stream) => stream.ssl(), - SslConnection::Bio(stream) => stream.ssl(), + // Get peer certificate chain + let conn_guard = self.connection.lock(); + let conn = conn_guard.as_ref().ok_or("No connection")?; + + let peer_certs = conn.peer_certificates().ok_or("No peer certificates")?; + + if peer_certs.is_empty() { + return Ok(()); } - } - // Get underlying socket stream reference (only for socket mode) - fn get_ref(&self) -> Option<&SocketStream> { - match self { - SslConnection::Socket(stream) => Some(stream.get_ref()), - SslConnection::Bio(_) => None, + // Get the top certificate in the chain (closest to root) + // Note: Server usually doesn't send the root CA, so we check the last cert's issuer + let top_cert_der = peer_certs.last().unwrap(); + let (_, top_cert) = x509_parser::parse_x509_certificate(top_cert_der) + .map_err(|e| format!("Failed to parse top cert: {e}"))?; + + let top_issuer = top_cert.issuer(); + + // Find matching CA in capath certs + for ca_der in capath_certs.iter() { + let (_, ca) = x509_parser::parse_x509_certificate(ca_der) + .map_err(|e| format!("Failed to parse CA: {e}"))?; + + // Check if this CA is self-signed and matches the issuer + if ca.subject() == ca.issuer() // Self-signed (root CA) + && ca.subject() == top_issuer + // Matches top cert's issuer + { + // Check if not already in ca_certs_der + let mut ca_certs_der = context.ca_certs_der.write(); + if !ca_certs_der.iter().any(|c| c == ca_der) { + ca_certs_der.push(ca_der.clone()); + } + break; + } } - } - // Check if this is in BIO mode - fn is_bio(&self) -> bool { - matches!(self, SslConnection::Bio(_)) + Ok(()) } - // Perform SSL handshake - fn do_handshake(&mut self) -> Result<(), ssl::Error> { - match self { - SslConnection::Socket(stream) => stream.do_handshake(), - SslConnection::Bio(stream) => stream.do_handshake(), + fn complete_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { + *self.handshake_done.lock() = true; + + // Check if session was resumed before creating session object + let conn_guard = self.connection.lock(); + if let Some(ref conn) = *conn_guard { + let was_resumed = conn.is_session_resumed(); + *self.session_was_reused.lock() = was_resumed; + + // Update context session statistics if server-side + if self.server_side { + let context = self.context.read(); + // Increment accept count for every successful server handshake + context.accept_count.fetch_add(1, Ordering::SeqCst); + // Increment hits count if session was resumed + if was_resumed { + context.session_hits.fetch_add(1, Ordering::SeqCst); + } + } } - } + drop(conn_guard); - // Write data to SSL connection - fn ssl_write(&mut self, buf: &[u8]) -> Result<usize, ssl::Error> { - match self { - SslConnection::Socket(stream) => stream.ssl_write(buf), - SslConnection::Bio(stream) => stream.ssl_write(buf), + // Track CA certificate used during handshake (client-side only) + // This simulates lazy loading behavior for capath certificates + if !self.server_side { + // Don't fail handshake if tracking fails + let _ = self.track_used_ca_from_capath(); } + + self.create_session_after_handshake(vm)?; + Ok(()) } - // Read data from SSL connection - fn ssl_read(&mut self, buf: &mut [u8]) -> Result<usize, ssl::Error> { - match self { - SslConnection::Socket(stream) => stream.ssl_read(buf), - SslConnection::Bio(stream) => stream.ssl_read(buf), + // Internal implementation with timeout control + pub(crate) fn sock_wait_for_io_impl( + &self, + kind: SelectKind, + vm: &VirtualMachine, + ) -> PyResult<bool> { + if self.is_bio_mode() { + // BIO mode doesn't use select + return Ok(false); } - } - // Get SSL shutdown state - fn get_shutdown(&mut self) -> ssl::ShutdownState { - match self { - SslConnection::Socket(stream) => stream.get_shutdown(), - SslConnection::Bio(stream) => stream.get_shutdown(), + // Get timeout + let timeout = self.get_socket_timeout(vm)?; + + // Check for non-blocking mode (timeout = 0) + if let Some(t) = timeout + && t.is_zero() + { + // Non-blocking mode - don't use select + return Ok(false); } - } - } - #[pyattr] - #[pyclass(module = "ssl", name = "_SSLSocket", traverse)] - #[derive(PyPayload)] - struct PySslSocket { - ctx: PyRwLock<PyRef<PySslContext>>, - #[pytraverse(skip)] - connection: PyRwLock<SslConnection>, - #[pytraverse(skip)] - socket_type: SslServerOrClient, - server_hostname: Option<PyStrRef>, - owner: PyRwLock<Option<PyRef<PyWeak>>>, - } + // Use select with the effective timeout + let py_socket: PyRef<PySocket> = self.sock.clone().try_into_value(vm)?; + let socket = py_socket + .sock() + .map_err(|e| vm.new_os_error(format!("Failed to get socket: {e}")))?; - impl fmt::Debug for PySslSocket { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("_SSLSocket") + let timed_out = sock_select(&socket, kind, timeout) + .map_err(|e| vm.new_os_error(format!("select failed: {e}")))?; + + Ok(timed_out) } - } - #[pyclass(flags(IMMUTABLETYPE))] - impl PySslSocket { - #[pygetset] - fn owner(&self) -> Option<PyObjectRef> { - self.owner.read().as_ref().and_then(|weak| weak.upgrade()) + // SNI (Server Name Indication) Helper Methods: + // These methods support the server-side handshake SNI callback mechanism + + /// Check if this is the first read during handshake (for SNI callback) + /// Returns true if we haven't processed ClientHello yet, regardless of SNI presence + pub(crate) fn is_first_sni_read(&self) -> bool { + self.client_hello_buffer.lock().is_none() } - #[pygetset(setter)] - fn set_owner(&self, owner: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - let mut lock = self.owner.write(); - lock.take(); - *lock = Some(owner.downgrade(None, vm)?); - Ok(()) + + /// Check if SNI callback is configured + pub(crate) fn has_sni_callback(&self) -> bool { + self.context.read().sni_callback.read().is_some() } - #[pygetset] - fn server_side(&self) -> bool { - self.socket_type == SslServerOrClient::Server + + /// Save ClientHello data from PyObjectRef for potential connection recreation + pub(crate) fn save_client_hello_from_bytes(&self, bytes_data: &[u8]) { + *self.client_hello_buffer.lock() = Some(bytes_data.to_vec()); } - #[pygetset] - fn context(&self) -> PyRef<PySslContext> { - self.ctx.read().clone() + + /// Get the extracted SNI name from resolver + pub(crate) fn get_extracted_sni_name(&self) -> Option<String> { + self.sni_state + .read() + .as_ref() + .and_then(|arc| arc.lock().1.clone()) } - #[pygetset(setter)] - fn set_context(&self, value: PyRef<PySslContext>, vm: &VirtualMachine) -> PyResult<()> { - // Update the SSL context in the underlying SSL object - let stream = self.connection.read(); - - // Set the new SSL_CTX on the SSL object - unsafe { - let result = SSL_set_SSL_CTX(stream.ssl().as_ptr(), value.ctx().as_ptr()); - if result.is_null() { - return Err(vm.new_runtime_error("Failed to set SSL context".to_owned())); + + /// Invoke the Python SNI callback + pub(crate) fn invoke_sni_callback( + &self, + sni_name: Option<&str>, + vm: &VirtualMachine, + ) -> PyResult<()> { + let callback = self + .context + .read() + .sni_callback + .read() + .clone() + .ok_or_else(|| vm.new_value_error("SNI callback not set"))?; + + let ssl_sock = self.owner.read().clone().unwrap_or(vm.ctx.none()); + let server_name_py: PyObjectRef = match sni_name { + Some(name) => vm.ctx.new_str(name.to_string()).into(), + None => vm.ctx.none(), + }; + let initial_context: PyObjectRef = self.context.read().clone().into(); + + let result = callback.call((ssl_sock, server_name_py, initial_context), vm)?; + + // Check return value type (must be None or integer) + if !vm.is_none(&result) { + // Try to convert to integer + if result.try_to_value::<i32>(vm).is_err() { + // Type conversion failed - raise TypeError as unraisable + let type_error = vm.new_type_error(format!( + "servername callback must return None or an integer, not '{}'", + result.class().name() + )); + vm.run_unraisable(type_error, None, result.clone()); + + // Return SSL error with reason set to TLSV1_ALERT_INTERNAL_ERROR + // + // RUSTLS API LIMITATION: + // We cannot send a TLS InternalError alert to the client here because: + // 1. Rustls does not provide a public API like send_fatal_alert() + // 2. This method is called AFTER dropping the connection lock (to prevent deadlock) + // 3. By the time we detect the error, the connection is no longer available + // + // CPython/OpenSSL behavior: + // - SNI callback runs inside SSL_do_handshake with connection active + // - Sets *al = SSL_AD_INTERNAL_ERROR + // - OpenSSL automatically sends alert before returning + // + // RustPython/Rustls behavior: + // - SNI callback runs after dropping connection lock (deadlock prevention) + // - Exception has _reason='TLSV1_ALERT_INTERNAL_ERROR' for error reporting + // - TCP connection closes without sending TLS alert to client + // + // If rustls adds send_fatal_alert() API in the future, we should: + // - Re-acquire connection lock after callback + // - Call: connection.send_fatal_alert(AlertDescription::InternalError) + // - Then close connection + let exc = vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + "SNI callback returned invalid type".to_owned(), + ); + let _ = exc.as_object().set_attr( + "reason", + vm.ctx.new_str("TLSV1_ALERT_INTERNAL_ERROR"), + vm, + ); + return Err(exc); } } - // Update self.ctx to the new context - *self.ctx.write() = value; Ok(()) } - #[pygetset] - fn server_hostname(&self) -> Option<PyStrRef> { - self.server_hostname.clone() + + // Helper to call socket methods, bypassing any SSL wrapper + pub(crate) fn sock_recv(&self, size: usize, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // In BIO mode, read from incoming BIO + if let Some(ref bio) = self.incoming_bio { + let bio_obj: PyObjectRef = bio.clone().into(); + let read_method = bio_obj.get_attr("read", vm)?; + return read_method.call((vm.ctx.new_int(size),), vm); + } + + // Normal socket mode + let socket_mod = vm.import("socket", 0)?; + let socket_class = socket_mod.get_attr("socket", vm)?; + + // Call socket.socket.recv(self.sock, size) + let recv_method = socket_class.get_attr("recv", vm)?; + recv_method.call((self.sock.clone(), vm.ctx.new_int(size)), vm) + } + + pub(crate) fn sock_send( + &self, + data: Vec<u8>, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + // In BIO mode, write to outgoing BIO + if let Some(ref bio) = self.outgoing_bio { + let bio_obj: PyObjectRef = bio.clone().into(); + let write_method = bio_obj.get_attr("write", vm)?; + return write_method.call((vm.ctx.new_bytes(data),), vm); + } + + // Normal socket mode + let socket_mod = vm.import("socket", 0)?; + let socket_class = socket_mod.get_attr("socket", vm)?; + + // Call socket.socket.send(self.sock, data) + let send_method = socket_class.get_attr("send", vm)?; + send_method.call((self.sock.clone(), vm.ctx.new_bytes(data)), vm) } #[pymethod] - fn getpeercert( + fn __repr__(&self) -> String { + "<SSLSocket>".to_string() + } + + // Helper function to convert Python PROTO_* constants to rustls versions + fn get_rustls_versions( + minimum: i32, + maximum: i32, + options: i32, + ) -> &'static [&'static rustls::SupportedProtocolVersion] { + // Rustls only supports TLS 1.2 and 1.3 + // PROTO_TLSv1_2 = 0x0303, PROTO_TLSv1_3 = 0x0304 + // PROTO_MINIMUM_SUPPORTED = -2, PROTO_MAXIMUM_SUPPORTED = -1 + // If minimum and maximum are 0, use default (both TLS 1.2 and 1.3) + + // Static arrays for single-version configurations + static TLS12_ONLY: &[&rustls::SupportedProtocolVersion] = &[&TLS12]; + static TLS13_ONLY: &[&rustls::SupportedProtocolVersion] = &[&TLS13]; + + // Normalize special values: -2 (MINIMUM_SUPPORTED) → TLS 1.2, -1 (MAXIMUM_SUPPORTED) → TLS 1.3 + let min = if minimum == -2 { + PROTO_TLSv1_2 + } else { + minimum + }; + let max = if maximum == -1 { + PROTO_TLSv1_3 + } else { + maximum + }; + + // Check if versions are disabled by options + let tls12_disabled = (options & OP_NO_TLSv1_2) != 0; + let tls13_disabled = (options & OP_NO_TLSv1_3) != 0; + + let want_tls12 = (min == 0 || min <= PROTO_TLSv1_2) + && (max == 0 || max >= PROTO_TLSv1_2) + && !tls12_disabled; + let want_tls13 = (min == 0 || min <= PROTO_TLSv1_3) + && (max == 0 || max >= PROTO_TLSv1_3) + && !tls13_disabled; + + match (want_tls12, want_tls13) { + (true, true) => rustls::DEFAULT_VERSIONS, // Both TLS 1.2 and 1.3 + (true, false) => TLS12_ONLY, // Only TLS 1.2 + (false, true) => TLS13_ONLY, // Only TLS 1.3 + (false, false) => rustls::DEFAULT_VERSIONS, // Fallback to default + } + } + + /// Helper: Prepare TLS versions from context settings + fn prepare_tls_versions(&self) -> &'static [&'static rustls::SupportedProtocolVersion] { + let ctx = self.context.read(); + let min_ver = *ctx.minimum_version.read(); + let max_ver = *ctx.maximum_version.read(); + let options = *ctx.options.read(); + Self::get_rustls_versions(min_ver, max_ver, options) + } + + /// Helper: Prepare KX groups (ECDH curve) from context settings + fn prepare_kx_groups( &self, - binary: OptionalArg<bool>, vm: &VirtualMachine, - ) -> PyResult<Option<PyObjectRef>> { - let binary = binary.unwrap_or(false); - let stream = self.connection.read(); - if !stream.ssl().is_init_finished() { - return Err(vm.new_value_error("handshake not done yet")); + ) -> PyResult<Option<Vec<&'static dyn SupportedKxGroup>>> { + let ctx = self.context.read(); + let ecdh_curve = ctx.ecdh_curve.read().clone(); + drop(ctx); + + if let Some(ref curve_name) = ecdh_curve { + match curve_name_to_kx_group(curve_name) { + Ok(groups) => Ok(Some(groups)), + Err(e) => Err(vm.new_value_error(format!("Failed to set ECDH curve: {e}"))), + } + } else { + Ok(None) } + } - let peer_cert = stream.ssl().peer_certificate(); - let Some(cert) = peer_cert else { - return Ok(None); + /// Helper: Prepare all common protocol settings (versions, KX groups, ciphers, ALPN) + fn prepare_protocol_settings(&self, vm: &VirtualMachine) -> PyResult<ProtocolSettings> { + let ctx = self.context.read(); + let versions = self.prepare_tls_versions(); + let kx_groups = self.prepare_kx_groups(vm)?; + let cipher_suites = ctx.selected_ciphers.read().clone(); + let alpn_protocols = ctx.alpn_protocols.read().clone(); + + Ok(ProtocolSettings { + versions, + kx_groups, + cipher_suites, + alpn_protocols, + }) + } + + /// Initialize server-side TLS connection with configuration + /// + /// This method handles all server-side setup including: + /// - Certificate and key validation + /// - Client authentication configuration + /// - SNI (Server Name Indication) setup + /// - ALPN protocol negotiation + /// - Session resumption configuration + /// + /// Returns the configured ServerConnection. + fn initialize_server_connection( + &self, + conn_guard: &mut Option<TlsConnection>, + vm: &VirtualMachine, + ) -> PyResult<()> { + let ctx = self.context.read(); + let cert_keys = ctx.cert_keys.read(); + + if cert_keys.is_empty() { + return Err(vm.new_value_error( + "Server-side connection requires certificate and key (use load_cert_chain)", + )); + } + + // Clone cert_keys for use in config + // PrivateKeyDer doesn't implement Clone, use clone_key() + let cert_keys_clone: Vec<CertKeyPair> = cert_keys + .iter() + .map(|(ck, pk)| (ck.clone(), pk.clone_key())) + .collect(); + drop(cert_keys); + + // Prepare common protocol settings (TLS versions, ECDH curve, cipher suites, ALPN) + let protocol_settings = self.prepare_protocol_settings(vm)?; + let min_ver = *ctx.minimum_version.read(); + + // Check if client certificate verification is required + let verify_mode = *ctx.verify_mode.read(); + let root_store = ctx.root_certs.read(); + let pha_enabled = *ctx.post_handshake_auth.read(); + + // Check if TLS 1.3 is being used + let is_tls13 = min_ver >= PROTO_TLSv1_3; + + // For TLS 1.3: always use deferred validation for client certificates + // For TLS 1.2: use immediate validation during handshake + let use_deferred_validation = is_tls13 + && !pha_enabled + && (verify_mode == CERT_REQUIRED || verify_mode == CERT_OPTIONAL); + + // For TLS 1.3 + PHA: if PHA is enabled, don't request cert in initial handshake + // The certificate will be requested later via verify_client_post_handshake() + let request_initial_cert = if pha_enabled { + // PHA enabled: don't request cert initially (will use PHA later) + false + } else if verify_mode == CERT_REQUIRED || verify_mode == CERT_OPTIONAL { + // PHA not enabled or TLS 1.2: request cert in initial handshake + true + } else { + // CERT_NONE + false }; - if binary { - // Return DER-encoded certificate - cert_to_py(vm, &cert, true).map(Some) + // Check if SNI callback is set + let sni_callback = ctx.sni_callback.read().clone(); + let use_sni_resolver = sni_callback.is_some(); + + // Create SNI state if needed (to be stored in PySSLSocket later) + // For SNI, use the first cert_key pair as the initial certificate + let sni_state: Option<Arc<ParkingMutex<SniCertName>>> = if use_sni_resolver { + // Use first cert_key as initial certificate for SNI + // Extract CertifiedKey from tuple + let (first_cert_key, _) = &cert_keys_clone[0]; + let first_cert_key = first_cert_key.clone(); + + // Check if we already have existing SNI state (from previous connection) + let existing_sni_state = self.sni_state.read().clone(); + + if let Some(sni_state_arc) = existing_sni_state { + // Reuse existing Arc and update its contents + // This is crucial: rustls SniCertResolver holds references to this Arc + let mut state = sni_state_arc.lock(); + state.0 = first_cert_key; + state.1 = None; // Reset SNI name for new connection + drop(state); + + // Return the existing Arc (not a new one!) + Some(sni_state_arc) + } else { + // First connection: create new SNI state + Some(Arc::new(ParkingMutex::new((first_cert_key, None)))) + } } else { - // Check verify_mode - unsafe { - let ssl_ctx = sys::SSL_get_SSL_CTX(stream.ssl().as_ptr()); - let verify_mode = sys::SSL_CTX_get_verify_mode(ssl_ctx); - if (verify_mode & sys::SSL_VERIFY_PEER as libc::c_int) == 0 { - // Return empty dict when SSL_VERIFY_PEER is not set - Ok(Some(vm.ctx.new_dict().into())) - } else { - // Return decoded certificate - cert_to_py(vm, &cert, false).map(Some) - } + None + }; + + // Determine which cert resolver to use + // Priority: SNI > Multi-cert/Single-cert via MultiCertResolver + let cert_resolver: Option<Arc<dyn ResolvesServerCert>> = if use_sni_resolver { + // SNI takes precedence - use first cert_key for initial setup + sni_state.as_ref().map(|sni_state_arc| { + Arc::new(SniCertResolver { + sni_state: sni_state_arc.clone(), + }) as Arc<dyn ResolvesServerCert> + }) + } else { + // Use MultiCertResolver for all cases (single or multiple certs) + // Extract CertifiedKey from tuples for MultiCertResolver + let cert_keys_only: Vec<Arc<CertifiedKey>> = + cert_keys_clone.iter().map(|(ck, _)| ck.clone()).collect(); + Some(Arc::new(MultiCertResolver::new(cert_keys_only))) + }; + + // Extract cert_chain and private_key from first cert_key + // + // Note: Since we always use cert_resolver now, these values won't actually be used + // by create_server_config. But we still need to provide them for the API signature. + let (first_cert_key, _) = &cert_keys_clone[0]; + let certs_clone = first_cert_key.cert.clone(); + + // Provide a dummy key since cert_resolver will handle cert selection + let key_clone = PrivateKeyDer::Pkcs8(Vec::new().into()); + + // Get shared server session storage and ticketer from context + let server_session_storage = ctx.rustls_server_session_store.clone(); + let server_ticketer = ctx.server_ticketer.clone(); + + // Build server config using compat helper + let config_options = ServerConfigOptions { + protocol_settings, + cert_chain: certs_clone, + private_key: key_clone, + root_store: if request_initial_cert { + Some(root_store.clone()) + } else { + None + }, + request_client_cert: request_initial_cert, + use_deferred_validation, + cert_resolver, + deferred_cert_error: if use_deferred_validation { + Some(self.deferred_cert_error.clone()) + } else { + None + }, + session_storage: Some(server_session_storage), + ticketer: Some(server_ticketer), + }; + + drop(root_store); + + // Check if we have a cached ServerConfig + let cached_config_arc = ctx.server_config.read().clone(); + drop(ctx); + + let config_arc = if let Some(cached) = cached_config_arc { + // Don't use cache when SNI is enabled, because each connection needs + // a fresh SniCertResolver with the correct Arc references + if use_sni_resolver { + let config = + create_server_config(config_options).map_err(|e| vm.new_value_error(e))?; + Arc::new(config) + } else { + cached } + } else { + let config = + create_server_config(config_options).map_err(|e| vm.new_value_error(e))?; + let config_arc = Arc::new(config); + + // Cache the ServerConfig for future connections + let ctx = self.context.read(); + *ctx.server_config.write() = Some(config_arc.clone()); + drop(ctx); + + config_arc + }; + + let conn = ServerConnection::new(config_arc).map_err(|e| { + vm.new_value_error(format!("Failed to create server connection: {e}")) + })?; + + *conn_guard = Some(TlsConnection::Server(conn)); + + // If ClientHello buffer exists (from SNI callback), re-inject it + if let Some(ref hello_data) = *self.client_hello_buffer.lock() + && let Some(TlsConnection::Server(ref mut server)) = *conn_guard + { + let mut cursor = std::io::Cursor::new(hello_data.as_slice()); + let _ = server.read_tls(&mut cursor); + + // Process the re-injected ClientHello + let _ = server.process_new_packets(); + + // DON'T clear buffer - keep it to prevent callback from being invoked again + // The buffer being non-empty signals that SNI callback was already processed + } + + // Store SNI state if we're using SNI resolver + if let Some(sni_state_arc) = sni_state { + *self.sni_state.write() = Some(sni_state_arc); } + + Ok(()) } #[pymethod] - fn get_unverified_chain(&self, vm: &VirtualMachine) -> PyResult<Option<PyListRef>> { - let stream = self.connection.read(); - let Some(chain) = stream.ssl().peer_cert_chain() else { - return Ok(None); - }; + fn do_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { + // Check if handshake already done + if *self.handshake_done.lock() { + return Ok(()); + } - // Return Certificate objects - let certs: Vec<PyObjectRef> = chain - .iter() - .map(|cert| unsafe { - sys::X509_up_ref(cert.as_ptr()); - let owned = X509::from_ptr(cert.as_ptr()); - cert_to_certificate(vm, owned) - }) - .collect::<PyResult<_>>()?; - Ok(Some(vm.ctx.new_list(certs))) + let mut conn_guard = self.connection.lock(); + + // Initialize connection if not already done + if conn_guard.is_none() { + // Check for pending context change (from SNI callback) + if let Some(new_ctx) = self.pending_context.write().take() { + *self.context.write() = new_ctx; + } + + if self.server_side { + // Server-side connection - delegate to helper method + self.initialize_server_connection(&mut conn_guard, vm)?; + } else { + // Client-side connection + let ctx = self.context.read(); + + // Prepare common protocol settings (TLS versions, ECDH curve, cipher suites, ALPN) + let protocol_settings = self.prepare_protocol_settings(vm)?; + + // Clone values we need before building config + let verify_mode = *ctx.verify_mode.read(); + let root_store_clone = ctx.root_certs.read().clone(); + let ca_certs_der_clone = ctx.ca_certs_der.read().clone(); + + // For client mTLS: extract cert_chain and private_key from first cert_key (if any) + // Now we store both CertifiedKey and PrivateKeyDer as tuple + let cert_keys_guard = ctx.cert_keys.read(); + let (cert_chain_clone, private_key_opt) = if !cert_keys_guard.is_empty() { + let (first_cert_key, private_key) = &cert_keys_guard[0]; + let certs = first_cert_key.cert.clone(); + (certs, Some(private_key.clone_key())) + } else { + (Vec::new(), None) + }; + drop(cert_keys_guard); + + let check_hostname = *ctx.check_hostname.read(); + let verify_flags = *ctx.verify_flags.read(); + + // Get session store before dropping ctx + let session_store = ctx.rustls_session_store.clone(); + + // Get CRLs for revocation checking + let crls_clone = ctx.crls.read().clone(); + + // Drop ctx early to avoid borrow conflicts + drop(ctx); + + // Build client config using compat helper + let config_options = ClientConfigOptions { + protocol_settings, + root_store: if verify_mode != CERT_NONE { + Some(root_store_clone) + } else { + None + }, + ca_certs_der: ca_certs_der_clone, + cert_chain: if !cert_chain_clone.is_empty() { + Some(cert_chain_clone) + } else { + None + }, + private_key: private_key_opt, + verify_server_cert: verify_mode != CERT_NONE, + check_hostname, + verify_flags, + session_store: Some(session_store), + crls: crls_clone, + }; + + let config = + create_client_config(config_options).map_err(|e| vm.new_value_error(e))?; + + // Parse server name for SNI + // Convert to ServerName + use rustls::pki_types::ServerName; + let hostname_opt = self.server_hostname.read().clone(); + + let server_name = if let Some(ref hostname) = hostname_opt { + // Use the provided hostname for SNI + ServerName::try_from(hostname.clone()).map_err(|e| { + vm.new_value_error(format!("Invalid server hostname: {e:?}")) + })? + } else { + // When server_hostname=None, use an IP address to suppress SNI + // no hostname = no SNI extension + ServerName::IpAddress( + std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)).into(), + ) + }; + + let conn = ClientConnection::new(Arc::new(config), server_name.clone()) + .map_err(|e| { + vm.new_value_error(format!("Failed to create client connection: {e}")) + })?; + + *conn_guard = Some(TlsConnection::Client(conn)); + } + } + + // Perform the actual handshake by exchanging data with the socket/BIO + match conn_guard.as_mut() { + Some(TlsConnection::Client(_conn)) => { + // CLIENT is simple - no SNI callback handling needed + ssl_do_handshake(conn_guard.as_mut().unwrap(), self, vm) + .map_err(|e| e.into_py_err(vm))?; + + drop(conn_guard); + self.complete_handshake(vm)?; + Ok(()) + } + Some(TlsConnection::Server(_conn)) => { + // Use OpenSSL-compatible handshake for server + // Handle SNI callback restart + match ssl_do_handshake(conn_guard.as_mut().unwrap(), self, vm) { + Ok(()) => { + // Handshake completed successfully + drop(conn_guard); + self.complete_handshake(vm)?; + Ok(()) + } + Err(SslError::SniCallbackRestart) => { + // SNI detected - need to call callback and recreate connection + + // CRITICAL: Drop connection lock BEFORE calling Python callback to avoid deadlock + // + // Deadlock scenario if we keep the lock: + // 1. This thread holds self.connection.lock() + // 2. Python callback invokes other SSL methods (e.g., getpeercert(), cipher()) + // 3. Those methods try to acquire self.connection.lock() again + // 4. PyMutex (parking_lot::Mutex) is not reentrant -> DEADLOCK + // + // Trade-off: By dropping the lock, we lose the ability to send TLS alerts + // because Rustls doesn't provide a send_fatal_alert() API. See detailed + // explanation in invoke_sni_callback() where we set _reason attribute. + drop(conn_guard); + + // Get the SNI name that was extracted (may be None if client didn't send SNI) + let sni_name = self.get_extracted_sni_name(); + + // Now safe to call Python callback (no locks held) + self.invoke_sni_callback(sni_name.as_deref(), vm)?; + + // Clear connection to trigger recreation + *self.connection.lock() = None; + + // Recursively call do_handshake to recreate with new context + self.do_handshake(vm) + } + Err(e) => { + // Other errors - convert to Python exception + drop(conn_guard); + Err(e.into_py_err(vm)) + } + } + } + None => unreachable!(), + } } #[pymethod] - fn get_verified_chain(&self, vm: &VirtualMachine) -> PyResult<Option<PyListRef>> { - let stream = self.connection.read(); - unsafe { - let chain = sys::SSL_get0_verified_chain(stream.ssl().as_ptr()); - if chain.is_null() { - return Ok(None); + fn read( + &self, + len: OptionalArg<isize>, + buffer: OptionalArg<ArgMemoryBuffer>, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + // Convert len to usize, defaulting to 1024 if not provided + // -1 means read all available data (treat as large buffer size) + let len_val = len.unwrap_or(PEM_BUFSIZE as isize); + let mut len = if len_val == -1 { + // -1 is only valid when a buffer is provided + match &buffer { + OptionalArg::Present(buf_arg) => buf_arg.len(), + OptionalArg::Missing => { + return Err(vm.new_value_error("negative read length")); + } } + } else if len_val < 0 { + return Err(vm.new_value_error("negative read length")); + } else { + len_val as usize + }; - let num_certs = sys::OPENSSL_sk_num(chain as *const _); + // if buffer is provided, limit len to buffer size + if let OptionalArg::Present(buf_arg) = &buffer { + let buf_len = buf_arg.len(); + if len_val <= 0 || len > buf_len { + len = buf_len; + } + } - let mut certs = Vec::with_capacity(num_certs as usize); - // Return Certificate objects - for i in 0..num_certs { - let cert_ptr = sys::OPENSSL_sk_value(chain as *const _, i) as *mut sys::X509; - if cert_ptr.is_null() { - continue; + // return empty bytes immediately for len=0 + if len == 0 { + return match buffer { + OptionalArg::Present(_) => Ok(vm.ctx.new_int(0).into()), + OptionalArg::Missing => Ok(vm.ctx.new_bytes(vec![]).into()), + }; + } + + // Ensure handshake is done + if !*self.handshake_done.lock() { + return Err(vm.new_value_error("Handshake not completed")); + } + + // Check if connection has been shut down + // After unwrap()/shutdown(), read operations should fail with SSLError + let shutdown_state = *self.shutdown_state.lock(); + if shutdown_state != ShutdownState::NotStarted { + return Err(vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + "cannot read after shutdown".to_owned(), + )); + } + + // Check for deferred certificate verification errors (TLS 1.3) + self.check_deferred_cert_error(vm)?; + + // Helper function to handle return value based on buffer presence + let return_data = |data: Vec<u8>, + buffer_arg: &OptionalArg<ArgMemoryBuffer>, + vm: &VirtualMachine| + -> PyResult<PyObjectRef> { + match buffer_arg { + OptionalArg::Present(buf_arg) => { + // Write into buffer and return number of bytes written + let n = data.len(); + if n > 0 { + let mut buf = buf_arg.borrow_buf_mut(); + let buf_slice = &mut *buf; + let copy_len = n.min(buf_slice.len()); + buf_slice[..copy_len].copy_from_slice(&data[..copy_len]); + } + Ok(vm.ctx.new_int(n).into()) } - // Clone the X509 certificate to create an owned copy - sys::X509_up_ref(cert_ptr); - let owned_cert = X509::from_ptr(cert_ptr); - let cert_obj = cert_to_certificate(vm, owned_cert)?; - certs.push(cert_obj); + OptionalArg::Missing => { + // Return bytes object + Ok(vm.ctx.new_bytes(data).into()) + } + } + }; + + let mut conn_guard = self.connection.lock(); + let conn = conn_guard + .as_mut() + .ok_or_else(|| vm.new_value_error("Connection not established"))?; + + // Use compat layer for unified read logic with proper EOF handling + // This matches CPython's SSL_read_ex() approach + let mut buf = vec![0u8; len]; + + match crate::ssl::compat::ssl_read(conn, &mut buf, self, vm) { + Ok(n) => { + buf.truncate(n); + return_data(buf, &buffer, vm) + } + Err(crate::ssl::compat::SslError::Eof) => { + // EOF occurred in violation of protocol (unexpected closure) + Err(vm.new_exception_msg( + PySSLEOFError::class(&vm.ctx).to_owned(), + "EOF occurred in violation of protocol".to_owned(), + )) + } + Err(crate::ssl::compat::SslError::ZeroReturn) => { + // Clean closure with close_notify - return empty data + return_data(vec![], &buffer, vm) + } + Err(crate::ssl::compat::SslError::WantRead) => { + // Non-blocking mode: would block + Err(create_ssl_want_read_error(vm)) + } + Err(crate::ssl::compat::SslError::WantWrite) => { + // Non-blocking mode: would block on write + Err(create_ssl_want_write_error(vm)) + } + Err(crate::ssl::compat::SslError::Timeout(msg)) => Err(timeout_error_msg(vm, msg)), + Err(crate::ssl::compat::SslError::Py(e)) => { + // Python exception - pass through + Err(e) + } + Err(e) => { + // Other SSL errors + Err(e.into_py_err(vm)) } - - Ok(if certs.is_empty() { - None - } else { - Some(vm.ctx.new_list(certs)) - }) } } #[pymethod] - fn version(&self) -> Option<&'static str> { - let v = self.connection.read().ssl().version_str(); - if v == "unknown" { None } else { Some(v) } - } + fn pending(&self) -> PyResult<usize> { + // Returns the number of already decrypted bytes available for read + // This is critical for asyncore's readable() method which checks socket.pending() > 0 + let mut conn_guard = self.connection.lock(); + let conn = match conn_guard.as_mut() { + Some(c) => c, + None => return Ok(0), // No connection established yet + }; - #[pymethod] - fn cipher(&self) -> Option<CipherTuple> { - self.connection - .read() - .ssl() - .current_cipher() - .map(cipher_to_tuple) + // Use rustls Reader's fill_buf() to check buffered plaintext + // fill_buf() returns a reference to buffered data without consuming it + // This matches OpenSSL's SSL_pending() behavior + use std::io::BufRead; + let mut reader = conn.reader(); + match reader.fill_buf() { + Ok(buf) => Ok(buf.len()), + Err(_) => { + // WouldBlock or other errors mean no data available + // Return 0 like OpenSSL does when buffer is empty + Ok(0) + } + } } #[pymethod] - fn shared_ciphers(&self, vm: &VirtualMachine) -> Option<PyListRef> { - #[cfg(ossl110)] - { - let stream = self.connection.read(); - unsafe { - let server_ciphers = SSL_get_ciphers(stream.ssl().as_ptr()); - if server_ciphers.is_null() { - return None; - } - - let client_ciphers = SSL_get_client_ciphers(stream.ssl().as_ptr()); - if client_ciphers.is_null() { - return None; - } + fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> { + let data_bytes = data.borrow_buf(); + let data_len = data_bytes.len(); - let mut result = Vec::new(); - let num_server = sys::OPENSSL_sk_num(server_ciphers as *const _); - let num_client = sys::OPENSSL_sk_num(client_ciphers as *const _); + // return 0 immediately for empty write + if data_len == 0 { + return Ok(0); + } - for i in 0..num_server { - let server_cipher_ptr = sys::OPENSSL_sk_value(server_ciphers as *const _, i) - as *const sys::SSL_CIPHER; + // Ensure handshake is done + if !*self.handshake_done.lock() { + return Err(vm.new_value_error("Handshake not completed")); + } - // Check if client supports this cipher by comparing pointers - let mut found = false; - for j in 0..num_client { - let client_cipher_ptr = - sys::OPENSSL_sk_value(client_ciphers as *const _, j) - as *const sys::SSL_CIPHER; + // Check if connection has been shut down + // After unwrap()/shutdown(), write operations should fail with SSLError + let shutdown_state = *self.shutdown_state.lock(); + if shutdown_state != ShutdownState::NotStarted { + return Err(vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + "cannot write after shutdown".to_owned(), + )); + } - if server_cipher_ptr == client_cipher_ptr { - found = true; - break; - } + // Check for deferred certificate verification errors (TLS 1.3) + self.check_deferred_cert_error(vm)?; + + let mut conn_guard = self.connection.lock(); + let conn = conn_guard + .as_mut() + .ok_or_else(|| vm.new_value_error("Connection not established"))?; + + // Unified write logic - no need to match on Client/Server anymore + let mut writer = conn.writer(); + writer + .write_all(data_bytes.as_ref()) + .map_err(|e| vm.new_os_error(format!("Write failed: {e}")))?; + + // Flush to get TLS-encrypted data (writer automatically flushed on drop) + // Send encrypted data to socket + if conn.wants_write() { + let is_bio = self.is_bio_mode(); + + if is_bio { + // BIO mode: Write ALL pending TLS data to outgoing BIO + // This prevents hangs where Python's ssl_io_loop waits for data + self.write_pending_tls(conn, vm)?; + } else { + // Socket mode: Try once and may return SSLWantWriteError + let mut buf = Vec::new(); + conn.write_tls(&mut buf) + .map_err(|e| vm.new_os_error(format!("TLS write failed: {e}")))?; + + if !buf.is_empty() { + // Wait for socket to be ready for writing + let timed_out = self.sock_wait_for_io_impl(SelectKind::Write, vm)?; + if timed_out { + return Err(vm.new_os_error("Write operation timed out")); } - if found { - let cipher = ssl::SslCipherRef::from_ptr(server_cipher_ptr as *mut _); - let (name, version, bits) = cipher_to_tuple(cipher); - let tuple = vm.new_tuple(( - vm.ctx.new_str(name), - vm.ctx.new_str(version), - vm.ctx.new_int(bits), - )); - result.push(tuple.into()); + // Send encrypted data to socket + // Convert BlockingIOError to SSLWantWriteError + match self.sock_send(buf, vm) { + Ok(_) => {} + Err(e) => { + if is_blocking_io_error(&e, vm) { + // Non-blocking socket would block - return SSLWantWriteError + return Err(create_ssl_want_write_error(vm)); + } + return Err(e); + } } } - - if result.is_empty() { - None - } else { - Some(vm.ctx.new_list(result)) - } } } - #[cfg(not(ossl110))] - { - let _ = vm; - None - } - } - - #[pymethod] - fn selected_alpn_protocol(&self) -> Option<String> { - #[cfg(ossl102)] - { - let stream = self.connection.read(); - unsafe { - let mut out: *const libc::c_uchar = std::ptr::null(); - let mut outlen: libc::c_uint = 0; - - sys::SSL_get0_alpn_selected(stream.ssl().as_ptr(), &mut out, &mut outlen); - if out.is_null() { - None - } else { - let slice = std::slice::from_raw_parts(out, outlen as usize); - Some(String::from_utf8_lossy(slice).into_owned()) - } - } - } - #[cfg(not(ossl102))] - { - None - } + Ok(data_len) } #[pymethod] - fn get_channel_binding( + fn getpeercert( &self, - cb_type: OptionalArg<PyStrRef>, + binary_form: OptionalArg<bool>, vm: &VirtualMachine, - ) -> PyResult<Option<PyBytesRef>> { - const CB_MAXLEN: usize = 512; - - let cb_type_str = cb_type.as_ref().map_or("tls-unique", |s| s.as_str()); + ) -> PyResult<Option<PyObjectRef>> { + let binary = binary_form.unwrap_or(false); - if cb_type_str != "tls-unique" { - return Err(vm.new_value_error(format!( - "Unsupported channel binding type '{}'", - cb_type_str - ))); + // Check if handshake is complete + if !*self.handshake_done.lock() { + return Err(vm.new_value_error("handshake not done yet")); } - let stream = self.connection.read(); - let ssl_ptr = stream.ssl().as_ptr(); + // Get peer certificates from TLS connection + let conn_guard = self.connection.lock(); + let conn = conn_guard + .as_ref() + .ok_or_else(|| vm.new_value_error("No TLS connection established"))?; + + let certs = conn.peer_certificates(); - unsafe { - let session_reused = sys::SSL_session_reused(ssl_ptr) != 0; - let is_client = matches!(self.socket_type, SslServerOrClient::Client); + // Return None if no peer certificate + let Some(certs) = certs else { + return Ok(None); + }; - // Use XOR logic from CPython - let use_finished = session_reused ^ is_client; + // Get first certificate (peer's certificate) + let cert_der = certs + .first() + .ok_or_else(|| vm.new_value_error("No peer certificate available"))?; - let mut buf = vec![0u8; CB_MAXLEN]; - let len = if use_finished { - sys::SSL_get_finished(ssl_ptr, buf.as_mut_ptr() as *mut _, CB_MAXLEN) - } else { - sys::SSL_get_peer_finished(ssl_ptr, buf.as_mut_ptr() as *mut _, CB_MAXLEN) - }; + if binary { + // Return DER-encoded certificate as bytes + let der_bytes = cert_der.as_ref().to_vec(); + return Ok(Some(vm.ctx.new_bytes(der_bytes).into())); + } - if len == 0 { - Ok(None) - } else { - buf.truncate(len); - Ok(Some(vm.ctx.new_bytes(buf))) - } + // Dictionary mode: check verify_mode + let verify_mode = *self.context.read().verify_mode.read(); + + if verify_mode == CERT_NONE { + // Return empty dict when CERT_NONE + return Ok(Some(vm.ctx.new_dict().into())); } + + // Parse DER certificate and convert to dict + let der_bytes = cert_der.as_ref(); + let (_, cert) = x509_parser::parse_x509_certificate(der_bytes) + .map_err(|e| vm.new_value_error(format!("Failed to parse certificate: {e}")))?; + + cert::cert_to_dict(vm, &cert).map(Some) } #[pymethod] - fn verify_client_post_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { - #[cfg(ossl111)] - { - let stream = self.connection.read(); - let result = unsafe { SSL_verify_client_post_handshake(stream.ssl().as_ptr()) }; - if result == 0 { - Err(convert_openssl_error(vm, openssl::error::ErrorStack::get())) - } else { - Ok(()) - } - } - #[cfg(not(ossl111))] - { - Err(vm.new_not_implemented_error( - "Post-handshake auth is not supported by your OpenSSL version.".to_owned(), - )) - } + fn cipher(&self) -> Option<(String, String, i32)> { + let conn_guard = self.connection.lock(); + let conn = conn_guard.as_ref()?; + + let suite = conn.negotiated_cipher_suite()?; + + // Extract cipher information using unified helper + let cipher_info = extract_cipher_info(&suite); + + // Note: returns a 3-tuple (name, protocol_version, bits) + // The 'description' field is part of get_ciphers() output, not cipher() + Some(( + cipher_info.name, + cipher_info.protocol.to_string(), + cipher_info.bits, + )) } #[pymethod] - fn shutdown(&self, vm: &VirtualMachine) -> PyResult<PyRef<PySocket>> { - let stream = self.connection.read(); + fn version(&self) -> Option<String> { + let conn_guard = self.connection.lock(); + let conn = conn_guard.as_ref()?; - // BIO mode doesn't have an underlying socket - if stream.is_bio() { - return Err(vm.new_not_implemented_error( - "shutdown() is not supported for BIO-based SSL objects".to_owned(), - )); - } + let suite = conn.negotiated_cipher_suite()?; + + let version_str = match suite.version().version { + rustls::ProtocolVersion::TLSv1_2 => "TLSv1.2", + rustls::ProtocolVersion::TLSv1_3 => "TLSv1.3", + _ => "Unknown", + }; - let ssl_ptr = stream.ssl().as_ptr(); + Some(version_str.to_string()) + } - // Perform SSL shutdown - let ret = unsafe { sys::SSL_shutdown(ssl_ptr) }; + #[pymethod] + fn selected_alpn_protocol(&self) -> Option<String> { + let conn_guard = self.connection.lock(); + let conn = conn_guard.as_ref()?; - if ret < 0 { - // Error occurred - let err = unsafe { sys::SSL_get_error(ssl_ptr, ret) }; + let alpn_bytes = conn.alpn_protocol()?; - if err == sys::SSL_ERROR_WANT_READ || err == sys::SSL_ERROR_WANT_WRITE { - // Non-blocking would block - this is okay for shutdown - // Return the underlying socket - } else { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - format!("SSL shutdown failed: error code {}", err), - )); - } + // Null byte protocol (vec![0u8]) means no actual ALPN match (fallback protocol) + if alpn_bytes.is_empty() || alpn_bytes == [0u8] { + return None; } - // Return the underlying socket - // Get the socket from the stream (SocketStream wraps PyRef<PySocket>) - let socket = stream - .get_ref() - .expect("unwrap() called on bio mode; should only be called in socket mode"); - Ok(socket.0.clone()) + // Convert bytes to string + String::from_utf8(alpn_bytes.to_vec()).ok() } - #[cfg(osslconf = "OPENSSL_NO_COMP")] #[pymethod] - fn compression(&self) -> Option<&'static str> { + fn selected_npn_protocol(&self) -> Option<String> { + // NPN (Next Protocol Negotiation) is the predecessor to ALPN + // It was deprecated in favor of ALPN (RFC 7301) + // Rustls doesn't support NPN, only ALPN + // Return None to indicate NPN is not supported None } - #[cfg(not(osslconf = "OPENSSL_NO_COMP"))] - #[pymethod] - fn compression(&self) -> Option<&'static str> { - let stream = self.connection.read(); - let comp_method = unsafe { sys::SSL_get_current_compression(stream.ssl().as_ptr()) }; - if comp_method.is_null() { - return None; - } - let typ = unsafe { sys::COMP_get_type(comp_method) }; - let nid = Nid::from_raw(typ); - if nid == Nid::UNDEF { - return None; - } - nid.short_name().ok() + + #[pygetset] + fn owner(&self) -> Option<PyObjectRef> { + self.owner.read().clone() } - #[pymethod] - fn do_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { - let mut stream = self.connection.write(); - let ssl_ptr = stream.ssl().as_ptr(); - - // BIO mode: no timeout/select logic, just do handshake - if stream.is_bio() { - return stream.do_handshake().map_err(|e| { - let exc = convert_ssl_error(vm, e); - // If it's a cert verification error, set verify info - if exc.class().is(PySslCertVerificationError::class(&vm.ctx)) { - set_verify_error_info(&exc, ssl_ptr, vm); - } - exc - }); - } + #[pygetset(setter)] + fn set_owner(&self, owner: PyObjectRef, _vm: &VirtualMachine) -> PyResult<()> { + *self.owner.write() = Some(owner); + Ok(()) + } - // Socket mode: handle timeout and blocking - let timeout = stream - .get_ref() - .expect("handshake called in bio mode; should only be called in socket mode") - .timeout_deadline(); - loop { - let err = match stream.do_handshake() { - Ok(()) => return Ok(()), - Err(e) => e, - }; - let (needs, state) = stream - .get_ref() - .expect("handshake called in bio mode; should only be called in socket mode") - .socket_needs(&err, &timeout); - match state { - SelectRet::TimedOut => { - return Err(socket::timeout_error_msg( - vm, - "The handshake operation timed out".to_owned(), - )); - } - SelectRet::Closed => return Err(socket_closed_error(vm)), - SelectRet::Nonblocking => {} - _ => { - if needs.is_some() { - continue; - } - } - } - let exc = convert_ssl_error(vm, err); - // If it's a cert verification error, set verify info - if exc.class().is(PySslCertVerificationError::class(&vm.ctx)) { - set_verify_error_info(&exc, ssl_ptr, vm); - } - return Err(exc); - } + #[pygetset] + fn server_side(&self) -> bool { + self.server_side } - #[pymethod] - fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> { - let mut stream = self.connection.write(); - let data = data.borrow_buf(); - let data = &*data; - - // BIO mode: no timeout/select logic - if stream.is_bio() { - return stream.ssl_write(data).map_err(|e| convert_ssl_error(vm, e)); - } - - // Socket mode: handle timeout and blocking - let socket_ref = stream - .get_ref() - .expect("write called in bio mode; should only be called in socket mode"); - let timeout = socket_ref.timeout_deadline(); - let state = socket_ref.select(SslNeeds::Write, &timeout); - match state { - SelectRet::TimedOut => { - return Err(socket::timeout_error_msg( - vm, - "The write operation timed out".to_owned(), - )); - } - SelectRet::Closed => return Err(socket_closed_error(vm)), - _ => {} + #[pygetset] + fn context(&self) -> PyRef<PySSLContext> { + self.context.read().clone() + } + + #[pygetset(setter)] + fn set_context(&self, value: PyRef<PySSLContext>, _vm: &VirtualMachine) -> PyResult<()> { + // Update context reference immediately + // SSL_set_SSL_CTX allows context changes at any time, + // even after handshake completion + *self.context.write() = value; + + // Clear pending context as we've applied the change + *self.pending_context.write() = None; + + Ok(()) + } + + #[pygetset] + fn server_hostname(&self) -> Option<String> { + self.server_hostname.read().clone() + } + + #[pygetset(setter)] + fn set_server_hostname( + &self, + value: Option<PyStrRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Check if handshake is already done + if *self.handshake_done.lock() { + return Err( + vm.new_value_error("Cannot set server_hostname on socket after handshake") + ); } - loop { - let err = match stream.ssl_write(data) { - Ok(len) => return Ok(len), - Err(e) => e, - }; - let (needs, state) = stream - .get_ref() - .expect("write called in bio mode; should only be called in socket mode") - .socket_needs(&err, &timeout); - match state { - SelectRet::TimedOut => { - return Err(socket::timeout_error_msg( - vm, - "The write operation timed out".to_owned(), - )); - } - SelectRet::Closed => return Err(socket_closed_error(vm)), - SelectRet::Nonblocking => {} - _ => { - if needs.is_some() { - continue; - } - } - } - return Err(convert_ssl_error(vm, err)); + + // Validate hostname + if let Some(hostname_str) = &value { + validate_hostname(hostname_str.as_str(), vm)?; } + + *self.server_hostname.write() = value.map(|s| s.as_str().to_string()); + Ok(()) } #[pygetset] - fn session(&self, _vm: &VirtualMachine) -> PyResult<Option<PySslSession>> { - let stream = self.connection.read(); - unsafe { - let session_ptr = sys::SSL_get_session(stream.ssl().as_ptr()); - if session_ptr.is_null() { - Ok(None) - } else { - // Increment reference count since SSL_get_session returns a borrowed reference - #[cfg(ossl110)] - let _session = sys::SSL_SESSION_up_ref(session_ptr); - - Ok(Some(PySslSession { - session: session_ptr, - ctx: self.ctx.read().clone(), - })) - } + fn session(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // Return the stored session object if any + let sess = self.session.read().clone(); + if let Some(s) = sess { + Ok(s) + } else { + Ok(vm.ctx.none()) } } #[pygetset(setter)] fn set_session(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - // Check if value is SSLSession type - let session = value - .downcast_ref::<PySslSession>() - .ok_or_else(|| vm.new_type_error("Value is not a SSLSession.".to_owned()))?; - - // Check if session refers to the same SSLContext - if !std::ptr::eq( - self.ctx.read().ctx.read().as_ptr(), - session.ctx.ctx.read().as_ptr(), - ) { - return Err( - vm.new_value_error("Session refers to a different SSLContext.".to_owned()) - ); + // Validate that value is an SSLSession + if !value.is(vm.ctx.types.none_type) { + // Try to downcast to SSLSession to validate + let _ = value + .downcast_ref::<PySSLSession>() + .ok_or_else(|| vm.new_type_error("Value is not a SSLSession."))?; } // Check if this is a client socket - if self.socket_type != SslServerOrClient::Client { - return Err( - vm.new_value_error("Cannot set session for server-side SSLSocket.".to_owned()) - ); + if self.server_side { + return Err(vm.new_value_error("Cannot set session for server-side SSLSocket")); } - // Check if handshake is not finished - let stream = self.connection.read(); - unsafe { - if sys::SSL_is_init_finished(stream.ssl().as_ptr()) != 0 { - return Err( - vm.new_value_error("Cannot set session after handshake.".to_owned()) - ); - } - - if sys::SSL_set_session(stream.ssl().as_ptr(), session.session) == 0 { - return Err(convert_openssl_error(vm, ErrorStack::get())); - } + // Check if handshake is already done + if *self.handshake_done.lock() { + return Err(vm.new_value_error("Cannot set session after handshake.")); } + // Store the session for potential use during handshake + *self.session.write() = if value.is(vm.ctx.types.none_type) { + None + } else { + Some(value) + }; + Ok(()) } #[pygetset] fn session_reused(&self) -> bool { - let stream = self.connection.read(); - unsafe { sys::SSL_session_reused(stream.ssl().as_ptr()) != 0 } + // Return the tracked session reuse status + *self.session_was_reused.lock() } #[pymethod] - fn read( - &self, - n: usize, - buffer: OptionalArg<ArgMemoryBuffer>, - vm: &VirtualMachine, - ) -> PyResult { - // Special case: reading 0 bytes should return empty bytes immediately - if n == 0 { - return if buffer.is_present() { - Ok(vm.ctx.new_int(0).into()) - } else { - Ok(vm.ctx.new_bytes(vec![]).into()) - }; - } + fn compression(&self) -> Option<&'static str> { + // rustls doesn't support compression + None + } - let mut stream = self.connection.write(); - let mut inner_buffer = if let OptionalArg::Present(buffer) = &buffer { - Either::A(buffer.borrow_buf_mut()) - } else { - Either::B(vec![0u8; n]) - }; - let buf = match &mut inner_buffer { - Either::A(b) => &mut **b, - Either::B(b) => b.as_mut_slice(), - }; - let buf = match buf.get_mut(..n) { - Some(b) => b, - None => buf, + #[pymethod] + fn get_unverified_chain(&self, vm: &VirtualMachine) -> PyResult<Option<PyListRef>> { + // Get peer certificates from the connection + let conn_guard = self.connection.lock(); + let conn = conn_guard + .as_ref() + .ok_or_else(|| vm.new_value_error("Handshake not completed"))?; + + let certs = conn.peer_certificates(); + + let Some(certs) = certs else { + return Ok(None); }; - // BIO mode: no timeout/select logic - let count = if stream.is_bio() { - match stream.ssl_read(buf) { - Ok(count) => count, - Err(e) => return Err(convert_ssl_error(vm, e)), - } - } else { - // Socket mode: handle timeout and blocking - let timeout = stream - .get_ref() - .expect("read called in bio mode; should only be called in socket mode") - .timeout_deadline(); - loop { - let err = match stream.ssl_read(buf) { - Ok(count) => break count, - Err(e) => e, - }; - if err.code() == ssl::ErrorCode::ZERO_RETURN - && stream.get_shutdown() == ssl::ShutdownState::RECEIVED - { - break 0; - } - let (needs, state) = stream - .get_ref() - .expect("read called in bio mode; should only be called in socket mode") - .socket_needs(&err, &timeout); - match state { - SelectRet::TimedOut => { - return Err(socket::timeout_error_msg( - vm, - "The read operation timed out".to_owned(), - )); - } - SelectRet::Nonblocking => {} - _ => { - if needs.is_some() { - continue; - } - } + // Convert to list of Certificate objects + let cert_list: Vec<PyObjectRef> = certs + .iter() + .map(|cert_der| { + let cert_bytes = cert_der.as_ref().to_vec(); + PySSLCertificate { + der_bytes: cert_bytes, } - return Err(convert_ssl_error(vm, err)); - } + .into_ref(&vm.ctx) + .into() + }) + .collect(); + + Ok(Some(vm.ctx.new_list(cert_list))) + } + + #[pymethod] + fn get_verified_chain(&self, vm: &VirtualMachine) -> PyResult<Option<PyListRef>> { + // Get peer certificates (what peer sent during handshake) + let conn_guard = self.connection.lock(); + let Some(ref conn) = *conn_guard else { + return Ok(None); }; - let ret = match inner_buffer { - Either::A(_buf) => vm.ctx.new_int(count).into(), - Either::B(mut buf) => { - buf.truncate(count); - buf.shrink_to_fit(); - vm.ctx.new_bytes(buf).into() - } + + let peer_certs = conn.peer_certificates(); + + let Some(peer_certs_slice) = peer_certs else { + return Ok(None); }; - Ok(ret) - } - } - #[pyattr] - #[pyclass(module = "ssl", name = "SSLSession")] - #[derive(PyPayload)] - struct PySslSession { - session: *mut sys::SSL_SESSION, - ctx: PyRef<PySslContext>, - } + // Build the verified chain using cert module + let ctx_guard = self.context.read(); + let ca_certs_der = ctx_guard.ca_certs_der.read(); + + let chain_der = cert::build_verified_chain(peer_certs_slice, &ca_certs_der); + + // Convert DER chain to Python list of Certificate objects + let cert_list: Vec<PyObjectRef> = chain_der + .into_iter() + .map(|der_bytes| PySSLCertificate { der_bytes }.into_ref(&vm.ctx).into()) + .collect(); - impl fmt::Debug for PySslSession { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("SSLSession") + Ok(Some(vm.ctx.new_list(cert_list))) } - } - impl Drop for PySslSession { - fn drop(&mut self) { - if !self.session.is_null() { - unsafe { - sys::SSL_SESSION_free(self.session); + #[pymethod] + fn shutdown(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // Check current shutdown state + let current_state = *self.shutdown_state.lock(); + + // If already completed, return immediately + if current_state == ShutdownState::Completed { + if self.is_bio_mode() { + return Ok(vm.ctx.none()); } + return Ok(self.sock.clone()); } - } - } - unsafe impl Send for PySslSession {} - unsafe impl Sync for PySslSession {} + // Get connection + let mut conn_guard = self.connection.lock(); + let conn = conn_guard + .as_mut() + .ok_or_else(|| vm.new_value_error("Connection not established"))?; - impl Comparable for PySslSession { - fn cmp( - zelf: &Py<Self>, - other: &crate::vm::PyObject, - op: PyComparisonOp, - _vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - let other = class_or_notimplemented!(Self, other); + // Step 1: Send our close_notify if not already sent + if current_state == ShutdownState::NotStarted { + conn.send_close_notify(); + + // Write close_notify to outgoing buffer/BIO + self.write_pending_tls(conn, vm)?; - if !matches!(op, PyComparisonOp::Eq | PyComparisonOp::Ne) { - return Ok(PyComparisonValue::NotImplemented); + // Update state + *self.shutdown_state.lock() = ShutdownState::SentCloseNotify; } - let mut eq = unsafe { - let mut self_len: libc::c_uint = 0; - let mut other_len: libc::c_uint = 0; - let self_id = sys::SSL_SESSION_get_id(zelf.session, &mut self_len); - let other_id = sys::SSL_SESSION_get_id(other.session, &mut other_len); - if self_len != other_len { - false + // Step 2: Try to read and process peer's close_notify + let is_bio = self.is_bio_mode(); + + // First check if we already have peer's close_notify + // This can happen if it was received during a previous read() call + let mut peer_closed = self.check_peer_closed(conn, vm)?; + + // If peer hasn't closed yet, try to read from socket + if !peer_closed { + // Check if socket is in blocking mode (timeout is None) + let is_blocking = if !is_bio { + // Get socket timeout + match self.sock.get_attr("gettimeout", vm) { + Ok(method) => match method.call((), vm) { + Ok(timeout) => vm.is_none(&timeout), + Err(_) => false, + }, + Err(_) => false, + } } else { - let self_slice = std::slice::from_raw_parts(self_id, self_len as usize); - let other_slice = std::slice::from_raw_parts(other_id, other_len as usize); - self_slice == other_slice + false + }; + + if is_bio { + // In BIO mode: non-blocking read attempt + let _ = self.try_read_close_notify(conn, vm); + } else if is_blocking { + // Blocking socket mode: Return immediately without waiting for peer + // + // Reasons we don't read from socket here: + // 1. STARTTLS scenario: application data may arrive before/instead of close_notify + // - Example: client sends ENDTLS, immediately sends plain "msg 5" + // - Server's unwrap() would read "msg 5" and try to parse as TLS → FAIL + // 2. CPython's SSL_shutdown() typically returns immediately without waiting + // 3. Bidirectional shutdown is the application's responsibility + // 4. Reading from socket would consume application data incorrectly + // + // Therefore: Just send our close_notify and return success immediately. + // The peer's close_notify (if any) will remain in the socket buffer. + // + // Mark shutdown as complete and return the underlying socket + drop(conn_guard); + *self.shutdown_state.lock() = ShutdownState::Completed; + *self.connection.lock() = None; + return Ok(self.sock.clone()); } - }; - if matches!(op, PyComparisonOp::Ne) { - eq = !eq; + + // Step 3: Check again if peer has sent close_notify (non-blocking/BIO mode only) + peer_closed = self.check_peer_closed(conn, vm)?; } - Ok(PyComparisonValue::Implemented(eq)) - } - } - #[pyattr] - #[pyclass(module = "ssl", name = "MemoryBIO")] - #[derive(PyPayload)] - struct PySslMemoryBio { - bio: *mut sys::BIO, - eof_written: AtomicCell<bool>, - } + drop(conn_guard); // Release lock before returning - impl fmt::Debug for PySslMemoryBio { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("MemoryBIO") - } - } + if !peer_closed { + // Still waiting for peer's close-notify + // Raise SSLWantReadError to signal app needs to transfer data + // This is correct for non-blocking sockets and BIO mode + return Err(create_ssl_want_read_error(vm)); + } + // Both close-notify exchanged, shutdown complete + *self.shutdown_state.lock() = ShutdownState::Completed; - impl Drop for PySslMemoryBio { - fn drop(&mut self) { - if !self.bio.is_null() { - unsafe { - sys::BIO_free_all(self.bio); - } + if is_bio { + return Ok(vm.ctx.none()); } + Ok(self.sock.clone()) } - } - unsafe impl Send for PySslMemoryBio {} - unsafe impl Sync for PySslMemoryBio {} + // Helper: Write all pending TLS data (including close_notify) to outgoing buffer/BIO + fn write_pending_tls(&self, conn: &mut TlsConnection, vm: &VirtualMachine) -> PyResult<()> { + loop { + if !conn.wants_write() { + break; + } + + let mut buf = vec![0u8; SSL3_RT_MAX_PLAIN_LENGTH]; + let written = conn + .write_tls(&mut buf.as_mut_slice()) + .map_err(|e| vm.new_os_error(format!("TLS write failed: {e}")))?; - // OpenSSL functions not in openssl-sys + if written == 0 { + break; + } - unsafe extern "C" { - // X509_check_ca returns 1 for CA certificates, 0 otherwise - fn X509_check_ca(x: *const sys::X509) -> libc::c_int; - } + // Send to outgoing BIO or socket + self.sock_send(buf[..written].to_vec(), vm)?; + } - unsafe extern "C" { - fn SSL_get_ciphers(ssl: *const sys::SSL) -> *const sys::stack_st_SSL_CIPHER; - } + Ok(()) + } + + // Helper: Try to read incoming data from BIO (non-blocking) + fn try_read_close_notify( + &self, + conn: &mut TlsConnection, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Try to read incoming data from BIO + // This is non-blocking in BIO mode - if no data, recv returns empty + match self.sock_recv(SSL3_RT_MAX_PLAIN_LENGTH, vm) { + Ok(bytes_obj) => { + let bytes = ArgBytesLike::try_from_object(vm, bytes_obj)?; + let data = bytes.borrow_buf(); + + if !data.is_empty() { + // Feed data to TLS connection + let data_slice: &[u8] = data.as_ref(); + let mut cursor = std::io::Cursor::new(data_slice); + let _ = conn.read_tls(&mut cursor); + + // Process packets + let _ = conn.process_new_packets(); + } + } + Err(_) => { + // No data available or error - that's OK in BIO mode + } + } - #[cfg(ossl110)] - unsafe extern "C" { - fn SSL_get_client_ciphers(ssl: *const sys::SSL) -> *const sys::stack_st_SSL_CIPHER; - } + Ok(()) + } - #[cfg(ossl111)] - unsafe extern "C" { - fn SSL_verify_client_post_handshake(ssl: *const sys::SSL) -> libc::c_int; - fn SSL_set_post_handshake_auth(ssl: *mut sys::SSL, val: libc::c_int); - } + // Helper: Check if peer has sent close_notify + fn check_peer_closed( + &self, + conn: &mut TlsConnection, + vm: &VirtualMachine, + ) -> PyResult<bool> { + // Process any remaining packets and check peer_has_closed + let io_state = conn + .process_new_packets() + .map_err(|e| vm.new_os_error(format!("Failed to process packets: {e}")))?; - #[cfg(ossl110)] - unsafe extern "C" { - fn SSL_CTX_get_security_level(ctx: *const sys::SSL_CTX) -> libc::c_int; - } + Ok(io_state.peer_has_closed()) + } - unsafe extern "C" { - fn SSL_set_SSL_CTX(ssl: *mut sys::SSL, ctx: *mut sys::SSL_CTX) -> *mut sys::SSL_CTX; - } + #[pymethod] + fn shared_ciphers(&self, vm: &VirtualMachine) -> Option<PyListRef> { + // Return None for client-side sockets + if !self.server_side { + return None; + } - #[cfg(ossl110)] - unsafe extern "C" { - fn SSL_SESSION_has_ticket(session: *const sys::SSL_SESSION) -> libc::c_int; - fn SSL_SESSION_get_ticket_lifetime_hint(session: *const sys::SSL_SESSION) -> libc::c_ulong; - } + // Check if handshake completed + if !*self.handshake_done.lock() { + return None; + } - // X509 object types - const X509_LU_X509: libc::c_int = 1; - const X509_LU_CRL: libc::c_int = 2; + // Get negotiated cipher suite from rustls + let conn_guard = self.connection.lock(); + let conn = conn_guard.as_ref()?; - unsafe extern "C" { - fn X509_OBJECT_get_type(obj: *const sys::X509_OBJECT) -> libc::c_int; - } + let suite = conn.negotiated_cipher_suite()?; - // SSL session statistics constants (used with SSL_CTX_ctrl) - const SSL_CTRL_SESS_NUMBER: libc::c_int = 20; - const SSL_CTRL_SESS_CONNECT: libc::c_int = 21; - const SSL_CTRL_SESS_CONNECT_GOOD: libc::c_int = 22; - const SSL_CTRL_SESS_CONNECT_RENEGOTIATE: libc::c_int = 23; - const SSL_CTRL_SESS_ACCEPT: libc::c_int = 24; - const SSL_CTRL_SESS_ACCEPT_GOOD: libc::c_int = 25; - const SSL_CTRL_SESS_ACCEPT_RENEGOTIATE: libc::c_int = 26; - const SSL_CTRL_SESS_HIT: libc::c_int = 27; - const SSL_CTRL_SESS_MISSES: libc::c_int = 29; - const SSL_CTRL_SESS_TIMEOUTS: libc::c_int = 30; - const SSL_CTRL_SESS_CACHE_FULL: libc::c_int = 31; - - // SSL session statistics functions (implemented as macros in OpenSSL) - #[allow(non_snake_case)] - unsafe fn SSL_CTX_sess_number(ctx: *const sys::SSL_CTX) -> libc::c_long { - unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_NUMBER, 0, std::ptr::null_mut()) } - } + // Extract cipher information using unified helper + let cipher_info = extract_cipher_info(&suite); - #[allow(non_snake_case)] - unsafe fn SSL_CTX_sess_connect(ctx: *const sys::SSL_CTX) -> libc::c_long { - unsafe { - sys::SSL_CTX_ctrl( - ctx as *mut _, - SSL_CTRL_SESS_CONNECT, - 0, - std::ptr::null_mut(), - ) + // Return as list with single tuple (name, version, bits) + let tuple = vm.ctx.new_tuple(vec![ + vm.ctx.new_str(cipher_info.name).into(), + vm.ctx.new_str(cipher_info.protocol).into(), + vm.ctx.new_int(cipher_info.bits).into(), + ]); + Some(vm.ctx.new_list(vec![tuple.into()])) } - } - #[allow(non_snake_case)] - unsafe fn SSL_CTX_sess_connect_good(ctx: *const sys::SSL_CTX) -> libc::c_long { - unsafe { - sys::SSL_CTX_ctrl( - ctx as *mut _, - SSL_CTRL_SESS_CONNECT_GOOD, - 0, - std::ptr::null_mut(), - ) - } - } + #[pymethod] + fn verify_client_post_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { + // TLS 1.3 post-handshake authentication + // This is only valid for server-side TLS 1.3 connections - #[allow(non_snake_case)] - unsafe fn SSL_CTX_sess_connect_renegotiate(ctx: *const sys::SSL_CTX) -> libc::c_long { - unsafe { - sys::SSL_CTX_ctrl( - ctx as *mut _, - SSL_CTRL_SESS_CONNECT_RENEGOTIATE, - 0, - std::ptr::null_mut(), - ) - } - } + // Check if this is a server-side socket + if !self.server_side { + return Err(vm.new_value_error( + "Cannot perform post-handshake authentication on client-side socket", + )); + } - #[allow(non_snake_case)] - unsafe fn SSL_CTX_sess_accept(ctx: *const sys::SSL_CTX) -> libc::c_long { - unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_ACCEPT, 0, std::ptr::null_mut()) } - } + // Check if handshake has been completed + if !*self.handshake_done.lock() { + return Err(vm.new_value_error( + "Handshake must be completed before post-handshake authentication", + )); + } - #[allow(non_snake_case)] - unsafe fn SSL_CTX_sess_accept_good(ctx: *const sys::SSL_CTX) -> libc::c_long { - unsafe { - sys::SSL_CTX_ctrl( - ctx as *mut _, - SSL_CTRL_SESS_ACCEPT_GOOD, - 0, - std::ptr::null_mut(), - ) - } - } + // Check connection exists and protocol version + let conn_guard = self.connection.lock(); + if let Some(conn) = conn_guard.as_ref() { + let version = match conn { + TlsConnection::Client(_) => { + return Err(vm.new_value_error( + "Post-handshake authentication requires server socket", + )); + } + TlsConnection::Server(server) => server.protocol_version(), + }; + + // Post-handshake auth is only available in TLS 1.3 + if version != Some(rustls::ProtocolVersion::TLSv1_3) { + // Get SSLError class from ssl module (not _ssl) + // ssl.py imports _ssl.SSLError as ssl.SSLError + let ssl_mod = vm.import("ssl", 0)?; + let ssl_error_class = ssl_mod.get_attr("SSLError", vm)?; + + // Create SSLError instance with message containing WRONG_SSL_VERSION + let msg = "[SSL: WRONG_SSL_VERSION] wrong ssl version"; + let args = vm.ctx.new_tuple(vec![vm.ctx.new_str(msg).into()]); + let exc = ssl_error_class.call((args,), vm)?; + + return Err(exc + .downcast() + .map_err(|_| vm.new_type_error("Failed to create SSLError"))?); + } + } else { + return Err(vm.new_value_error("No SSL connection established")); + } - #[allow(non_snake_case)] - unsafe fn SSL_CTX_sess_accept_renegotiate(ctx: *const sys::SSL_CTX) -> libc::c_long { - unsafe { - sys::SSL_CTX_ctrl( - ctx as *mut _, - SSL_CTRL_SESS_ACCEPT_RENEGOTIATE, - 0, - std::ptr::null_mut(), - ) + // rustls doesn't provide an API for post-handshake authentication. + // The rustls TLS library does not support requesting client certificates + // after the initial handshake is completed. + // Raise SSLError instead of NotImplementedError for compatibility + Err(vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + "Post-handshake authentication is not supported by the rustls backend. \ + The rustls TLS library does not provide an API to request client certificates \ + after the initial handshake. Consider requesting the client certificate \ + during the initial handshake by setting the appropriate verify_mode before \ + calling do_handshake()." + .to_owned(), + )) } - } - #[allow(non_snake_case)] - unsafe fn SSL_CTX_sess_hits(ctx: *const sys::SSL_CTX) -> libc::c_long { - unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_HIT, 0, std::ptr::null_mut()) } - } + #[pymethod] + fn get_channel_binding( + &self, + cb_type: OptionalArg<PyStrRef>, + vm: &VirtualMachine, + ) -> PyResult<Option<PyBytesRef>> { + let cb_type_str = cb_type.as_ref().map_or("tls-unique", |s| s.as_str()); - #[allow(non_snake_case)] - unsafe fn SSL_CTX_sess_misses(ctx: *const sys::SSL_CTX) -> libc::c_long { - unsafe { sys::SSL_CTX_ctrl(ctx as *mut _, SSL_CTRL_SESS_MISSES, 0, std::ptr::null_mut()) } - } + // rustls doesn't support channel binding (tls-unique, tls-server-end-point, etc.) + // This is because: + // 1. tls-unique requires access to TLS Finished messages, which rustls doesn't expose + // 2. tls-server-end-point requires the server certificate, which we don't track here + // 3. TLS 1.3 deprecated tls-unique anyway + // + // For compatibility, we'll return None (no channel binding available) + // rather than raising an error - #[allow(non_snake_case)] - unsafe fn SSL_CTX_sess_timeouts(ctx: *const sys::SSL_CTX) -> libc::c_long { - unsafe { - sys::SSL_CTX_ctrl( - ctx as *mut _, - SSL_CTRL_SESS_TIMEOUTS, - 0, - std::ptr::null_mut(), - ) - } - } + if cb_type_str != "tls-unique" { + return Err(vm.new_value_error(format!( + "Unsupported channel binding type '{cb_type_str}'", + ))); + } - #[allow(non_snake_case)] - unsafe fn SSL_CTX_sess_cache_full(ctx: *const sys::SSL_CTX) -> libc::c_long { - unsafe { - sys::SSL_CTX_ctrl( - ctx as *mut _, - SSL_CTRL_SESS_CACHE_FULL, - 0, - std::ptr::null_mut(), - ) + // Return None to indicate channel binding is not available + // This matches the behavior when the handshake hasn't completed yet + Ok(None) } } - // DH parameters functions - unsafe extern "C" { - fn PEM_read_DHparams( - fp: *mut libc::FILE, - x: *mut *mut sys::DH, - cb: *mut libc::c_void, - u: *mut libc::c_void, - ) -> *mut sys::DH; - } - - // OpenSSL BIO helper functions - // These are typically macros in OpenSSL, implemented via BIO_ctrl - const BIO_CTRL_PENDING: libc::c_int = 10; - const BIO_CTRL_SET_EOF: libc::c_int = 2; - - #[allow(non_snake_case)] - unsafe fn BIO_ctrl_pending(bio: *mut sys::BIO) -> usize { - unsafe { sys::BIO_ctrl(bio, BIO_CTRL_PENDING, 0, std::ptr::null_mut()) as usize } - } + impl Constructor for PySSLSocket { + type Args = (); - #[allow(non_snake_case)] - unsafe fn BIO_set_mem_eof_return(bio: *mut sys::BIO, eof: libc::c_int) -> libc::c_int { - unsafe { - sys::BIO_ctrl( - bio, - BIO_CTRL_SET_EOF, - eof as libc::c_long, - std::ptr::null_mut(), - ) as libc::c_int + fn py_new(_cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error( + "Cannot directly instantiate SSLSocket, use SSLContext.wrap_socket()", + )) } } - #[allow(non_snake_case)] - unsafe fn BIO_clear_retry_flags(bio: *mut sys::BIO) { - unsafe { - sys::BIO_clear_flags(bio, sys::BIO_FLAGS_RWS | sys::BIO_FLAGS_SHOULD_RETRY); - } + // MemoryBIO - provides in-memory buffer for SSL/TLS I/O + #[pyattr] + #[pyclass(name = "MemoryBIO", module = "ssl")] + #[derive(Debug, PyPayload)] + struct PyMemoryBIO { + // Internal buffer + buffer: PyMutex<Vec<u8>>, + // EOF flag + eof: PyRwLock<bool>, } - impl Constructor for PySslMemoryBio { - type Args = (); - - fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { - unsafe { - let bio = sys::BIO_new(sys::BIO_s_mem()); - if bio.is_null() { - return Err(vm.new_memory_error("failed to allocate BIO".to_owned())); - } + #[pyclass(with(Constructor), flags(BASETYPE))] + impl PyMemoryBIO { + #[pymethod] + fn read(&self, len: OptionalArg<i32>, vm: &VirtualMachine) -> PyResult<PyBytesRef> { + let mut buffer = self.buffer.lock(); - sys::BIO_set_retry_read(bio); - BIO_set_mem_eof_return(bio, -1); + if buffer.is_empty() && *self.eof.read() { + // Return empty bytes at EOF + return Ok(vm.ctx.new_bytes(vec![])); + } - PySslMemoryBio { - bio, - eof_written: AtomicCell::new(false), + let read_len = match len { + OptionalArg::Present(n) if n >= 0 => n as usize, + OptionalArg::Present(n) => { + return Err(vm.new_value_error(format!("negative read length: {n}"))); } - .into_ref_with_type(vm, cls) - .map(Into::into) - } - } - } + OptionalArg::Missing => buffer.len(), // Read all available + }; - #[pyclass(flags(IMMUTABLETYPE), with(Constructor))] - impl PySslMemoryBio { - #[pygetset] - fn pending(&self) -> usize { - unsafe { BIO_ctrl_pending(self.bio) } - } + let actual_len = read_len.min(buffer.len()); + let data = buffer.drain(..actual_len).collect::<Vec<u8>>(); - #[pygetset] - fn eof(&self) -> bool { - let pending = unsafe { BIO_ctrl_pending(self.bio) }; - pending == 0 && self.eof_written.load() + Ok(vm.ctx.new_bytes(data)) } #[pymethod] - fn read(&self, size: OptionalArg<i32>, vm: &VirtualMachine) -> PyResult<Vec<u8>> { - unsafe { - let avail = BIO_ctrl_pending(self.bio).min(i32::MAX as usize) as i32; - let len = size.unwrap_or(-1); - let len = if len < 0 || len > avail { avail } else { len }; - - if len == 0 { - return Ok(Vec::new()); + fn write(&self, buf: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { + // Check if it's a memoryview and if it's contiguous + if let Ok(mem_view) = buf.get_attr("c_contiguous", vm) { + // It's a memoryview, check if contiguous + let is_contiguous: bool = mem_view.try_to_bool(vm)?; + if !is_contiguous { + return Err(vm.new_exception_msg( + vm.ctx.exceptions.buffer_error.to_owned(), + "non-contiguous buffer is not supported".to_owned(), + )); } + } - let mut buf = vec![0u8; len as usize]; - let nbytes = sys::BIO_read(self.bio, buf.as_mut_ptr() as *mut _, len); + // Convert to bytes-like object + let bytes_like = ArgBytesLike::try_from_object(vm, buf)?; + let data = bytes_like.borrow_buf(); + let len = data.len(); - if nbytes < 0 { - return Err(convert_openssl_error(vm, ErrorStack::get())); - } + let mut buffer = self.buffer.lock(); + buffer.extend_from_slice(&data); - buf.truncate(nbytes as usize); - Ok(buf) - } + Ok(len) } #[pymethod] - fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<i32> { - if self.eof_written.load() { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "cannot write() after write_eof()".to_owned(), - )); - } + fn write_eof(&self, _vm: &VirtualMachine) -> PyResult<()> { + *self.eof.write() = true; + Ok(()) + } - data.with_ref(|buf| unsafe { - if buf.len() > i32::MAX as usize { - return Err( - vm.new_overflow_error(format!("string longer than {} bytes", i32::MAX)) - ); - } + #[pygetset] + fn pending(&self) -> i32 { + self.buffer.lock().len() as i32 + } - let nbytes = sys::BIO_write(self.bio, buf.as_ptr() as *const _, buf.len() as i32); - if nbytes < 0 { - return Err(convert_openssl_error(vm, ErrorStack::get())); - } + #[pygetset] + fn eof(&self) -> bool { + // EOF is true only when buffer is empty AND write_eof has been called + let pending = self.buffer.lock().len(); + pending == 0 && *self.eof.read() + } + } - Ok(nbytes) - }) + impl Representable for PyMemoryBIO { + #[inline] + fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { + Ok("<MemoryBIO>".to_owned()) } + } - #[pymethod] - fn write_eof(&self) { - self.eof_written.store(true); - unsafe { - BIO_clear_retry_flags(self.bio); - BIO_set_mem_eof_return(self.bio, 0); + impl Constructor for PyMemoryBIO { + type Args = (); + + fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + let obj = PyMemoryBIO { + buffer: PyMutex::new(Vec::new()), + eof: PyRwLock::new(false), } + .into_ref_with_type(vm, cls)?; + Ok(obj.into()) } } - #[pyclass(flags(IMMUTABLETYPE), with(Comparable))] - impl PySslSession { + // SSLSession - represents a cached SSL session + // NOTE: This is an EMULATION - actual session data is managed by Rustls internally + #[pyattr] + #[pyclass(name = "SSLSession", module = "ssl")] + #[derive(Debug, PyPayload)] + struct PySSLSession { + // Session data - serialized rustls session (EMULATED - kept empty) + session_data: Vec<u8>, + // Session ID - synthetic ID generated from metadata (NOT actual TLS session ID) + #[allow(dead_code)] + session_id: Vec<u8>, + // Session metadata + creation_time: std::time::SystemTime, + // Lifetime in seconds (default 7200 = 2 hours) + lifetime: u64, + } + + #[pyclass(flags(BASETYPE))] + impl PySSLSession { #[pygetset] fn time(&self) -> i64 { - unsafe { - #[cfg(ossl330)] - { - sys::SSL_SESSION_get_time(self.session) as i64 - } - #[cfg(not(ossl330))] - { - sys::SSL_SESSION_get_time(self.session) as i64 - } - } + // Return session creation time as Unix timestamp + self.creation_time + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64 } #[pygetset] fn timeout(&self) -> i64 { - unsafe { sys::SSL_SESSION_get_timeout(self.session) as i64 } + // Return session timeout/lifetime in seconds + self.lifetime as i64 } #[pygetset] - fn ticket_lifetime_hint(&self) -> u64 { - // SSL_SESSION_get_ticket_lifetime_hint available in OpenSSL 1.1.0+ - #[cfg(ossl110)] - { - unsafe { SSL_SESSION_get_ticket_lifetime_hint(self.session) as u64 } - } - #[cfg(not(ossl110))] - { - // Not available in older OpenSSL versions - 0 - } + fn ticket_lifetime_hint(&self) -> i64 { + // Return ticket lifetime hint (same as timeout for rustls) + self.lifetime as i64 } #[pygetset] fn id(&self, vm: &VirtualMachine) -> PyBytesRef { - unsafe { - let mut len: libc::c_uint = 0; - let id_ptr = sys::SSL_SESSION_get_id(self.session, &mut len); - let id_slice = std::slice::from_raw_parts(id_ptr, len as usize); - vm.ctx.new_bytes(id_slice.to_vec()) - } + // Return session ID (hash of session data for uniqueness) + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut hasher = DefaultHasher::new(); + self.session_data.hash(&mut hasher); + let hash = hasher.finish(); + + // Convert hash to bytes + vm.ctx.new_bytes(hash.to_be_bytes().to_vec()) } #[pygetset] fn has_ticket(&self) -> bool { - // SSL_SESSION_has_ticket available in OpenSSL 1.1.0+ - #[cfg(ossl110)] - { - unsafe { SSL_SESSION_has_ticket(self.session) != 0 } - } - #[cfg(not(ossl110))] - { - // Not available in older OpenSSL versions - false - } + // For rustls, if we have session data, we have a ticket + !self.session_data.is_empty() } } - #[track_caller] - pub(crate) fn convert_openssl_error( - vm: &VirtualMachine, - err: ErrorStack, - ) -> PyBaseExceptionRef { - match err.errors().last() { - Some(e) => { - // Check if this is a system library error (errno-based) - // CPython: Modules/_ssl.c:667-671 - let lib = sys::ERR_GET_LIB(e.code()); - - if lib == sys::ERR_LIB_SYS { - // A system error is being reported; reason is set to errno - let reason = sys::ERR_GET_REASON(e.code()); - - // errno 2 = ENOENT = FileNotFoundError - let exc_type = if reason == 2 { - vm.ctx.exceptions.file_not_found_error.to_owned() - } else { - vm.ctx.exceptions.os_error.to_owned() - }; - let exc = vm.new_exception(exc_type, vec![vm.ctx.new_int(reason).into()]); - // Set errno attribute explicitly - let _ = exc - .as_object() - .set_attr("errno", vm.ctx.new_int(reason), vm); - return exc; - } - - let caller = std::panic::Location::caller(); - let (file, line) = (caller.file(), caller.line()); - let file = file - .rsplit_once(&['/', '\\'][..]) - .map_or(file, |(_, basename)| basename); - - // Get error codes - same approach as CPython - let lib = sys::ERR_GET_LIB(e.code()); - let reason = sys::ERR_GET_REASON(e.code()); - - // Look up error mnemonic from our static tables - // CPython uses dict lookup: err_codes_to_names[(lib, reason)] - let key = super::ssl_data::encode_error_key(lib, reason); - let errstr = super::ssl_data::ERROR_CODES - .get(&key) - .copied() - .or_else(|| { - // Fallback: use OpenSSL's error string - e.reason() - }) - .unwrap_or("unknown error"); - - // Check if this is a certificate verification error - // ERR_LIB_SSL = 20 (from _ssl_data_300.h) - // SSL_R_CERTIFICATE_VERIFY_FAILED = 134 (from _ssl_data_300.h) - let is_cert_verify_error = lib == 20 && reason == 134; - - // Look up library name from our static table - // CPython uses: lib_codes_to_names[lib] - let lib_name = super::ssl_data::LIBRARY_CODES.get(&(lib as u32)).copied(); - - // Use SSLCertVerificationError for certificate verification failures - let cls = if is_cert_verify_error { - PySslCertVerificationError::class(&vm.ctx).to_owned() - } else { - PySslError::class(&vm.ctx).to_owned() - }; - - // Build message - let msg = if let Some(lib_str) = lib_name { - format!("[{lib_str}] {errstr} ({file}:{line})") - } else { - format!("{errstr} ({file}:{line})") - }; - - // Create exception instance - let reason = sys::ERR_GET_REASON(e.code()); - let exc = vm.new_exception( - cls, - vec![vm.ctx.new_int(reason).into(), vm.ctx.new_str(msg).into()], - ); - - // Set attributes on instance, not class - let exc_obj: PyObjectRef = exc.into(); + impl Representable for PySSLSession { + #[inline] + fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { + Ok("<SSLSession>".to_owned()) + } + } - // Set reason attribute (always set, even if just the error string) - let reason_value = vm.ctx.new_str(errstr); - let _ = exc_obj.set_attr("reason", reason_value, vm); + // Helper functions - // Set library attribute (None if not available) - let library_value: PyObjectRef = if let Some(lib_str) = lib_name { - vm.ctx.new_str(lib_str).into() - } else { - vm.ctx.none() - }; - let _ = exc_obj.set_attr("library", library_value, vm); - - // For SSLCertVerificationError, set verify_code and verify_message - // Note: These will be set to None here, and can be updated by the caller - // if they have access to the SSL object - if is_cert_verify_error { - let _ = exc_obj.set_attr("verify_code", vm.ctx.none(), vm); - let _ = exc_obj.set_attr("verify_message", vm.ctx.none(), vm); - } + // OID module already imported at top of _ssl module - // Convert back to PyBaseExceptionRef - exc_obj.downcast().expect( - "exc_obj is created as PyBaseExceptionRef and must downcast successfully", - ) - } - None => { - let cls = PySslError::class(&vm.ctx).to_owned(); - vm.new_exception_empty(cls) - } - } + #[derive(FromArgs)] + struct Txt2ObjArgs { + txt: PyStrRef, + #[pyarg(named, optional)] + name: OptionalArg<bool>, } - // Helper function to set verify_code and verify_message on SSLCertVerificationError - fn set_verify_error_info( - exc: &PyBaseExceptionRef, - ssl_ptr: *const sys::SSL, - vm: &VirtualMachine, - ) { - // Get verify result - let verify_code = unsafe { sys::SSL_get_verify_result(ssl_ptr) }; - let verify_code_obj = vm.ctx.new_int(verify_code); - - // Get verify message - let verify_message = unsafe { - let verify_str = sys::X509_verify_cert_error_string(verify_code); - if verify_str.is_null() { - vm.ctx.none() - } else { - let c_str = std::ffi::CStr::from_ptr(verify_str); - vm.ctx.new_str(c_str.to_string_lossy()).into() - } + #[pyfunction] + fn txt2obj(args: Txt2ObjArgs, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + let txt = args.txt.as_str(); + let name = args.name.unwrap_or(false); + + // If name=False (default), only accept OID strings + // If name=True, accept both names and OID strings + let entry = if txt + .chars() + .next() + .map(|c| c.is_ascii_digit()) + .unwrap_or(false) + { + // Looks like an OID string (starts with digit) + oid::find_by_oid_string(txt) + } else if name { + // name=True: allow shortname/longname lookup + oid::find_by_name(txt) + } else { + // name=False: only OID strings allowed, not names + None }; - let exc_obj = exc.as_object(); - let _ = exc_obj.set_attr("verify_code", verify_code_obj, vm); - let _ = exc_obj.set_attr("verify_message", verify_message, vm); - } - #[track_caller] - fn convert_ssl_error( - vm: &VirtualMachine, - e: impl std::borrow::Borrow<ssl::Error>, - ) -> PyBaseExceptionRef { - let e = e.borrow(); - let (cls, msg) = match e.code() { - ssl::ErrorCode::WANT_READ => ( - PySslWantReadError::class(&vm.ctx).to_owned(), - "The operation did not complete (read)", - ), - ssl::ErrorCode::WANT_WRITE => ( - PySslWantWriteError::class(&vm.ctx).to_owned(), - "The operation did not complete (write)", - ), - ssl::ErrorCode::SYSCALL => match e.io_error() { - Some(io_err) => return io_err.to_pyexception(vm), - // When no I/O error and OpenSSL error queue is empty, - // this is an EOF in violation of protocol -> SSLEOFError - // Need to set args[0] = SSL_ERROR_EOF for suppress_ragged_eofs check - None => { - return vm.new_exception( - PySslEOFError::class(&vm.ctx).to_owned(), - vec![ - vm.ctx.new_int(SSL_ERROR_EOF).into(), - vm.ctx - .new_str("EOF occurred in violation of protocol") - .into(), - ], - ); - } - }, - ssl::ErrorCode::SSL => { - // Check for OpenSSL 3.0 SSL_R_UNEXPECTED_EOF_WHILE_READING - if let Some(ssl_err) = e.ssl_error() { - // In OpenSSL 3.0+, unexpected EOF is reported as SSL_ERROR_SSL - // with this specific reason code instead of SSL_ERROR_SYSCALL - unsafe { - let err_code = sys::ERR_peek_last_error(); - let reason = sys::ERR_GET_REASON(err_code); - let lib = sys::ERR_GET_LIB(err_code); - if lib == ERR_LIB_SSL && reason == SSL_R_UNEXPECTED_EOF_WHILE_READING { - return vm.new_exception( - PySslEOFError::class(&vm.ctx).to_owned(), - vec![ - vm.ctx.new_int(SSL_ERROR_EOF).into(), - vm.ctx - .new_str("EOF occurred in violation of protocol") - .into(), - ], - ); - } - } - return convert_openssl_error(vm, ssl_err.clone()); - } - ( - PySslError::class(&vm.ctx).to_owned(), - "A failure in the SSL library occurred", - ) - } - _ => ( - PySslError::class(&vm.ctx).to_owned(), - "A failure in the SSL library occurred", - ), - }; - vm.new_exception_msg(cls, msg.to_owned()) + let entry = entry.ok_or_else(|| vm.new_value_error(format!("unknown object '{txt}'")))?; + + // Return tuple: (nid, shortname, longname, oid) + Ok(vm + .new_tuple(( + vm.ctx.new_int(entry.nid), + vm.ctx.new_str(entry.short_name), + vm.ctx.new_str(entry.long_name), + vm.ctx.new_str(entry.oid_string()), + )) + .into()) } - // SSL_FILETYPE_ASN1 part of _add_ca_certs in CPython - fn x509_stack_from_der(der: &[u8]) -> Result<Vec<X509>, ErrorStack> { - unsafe { - openssl::init(); - let bio = bio::MemBioSlice::new(der)?; - - let mut certs = vec![]; - loop { - let cert = sys::d2i_X509_bio(bio.as_ptr(), std::ptr::null_mut()); - if cert.is_null() { - break; - } - certs.push(X509::from_ptr(cert)); - } + #[pyfunction] + fn nid2obj(nid: i32, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + let entry = oid::find_by_nid(nid) + .ok_or_else(|| vm.new_value_error(format!("unknown NID {nid}")))?; + + // Return tuple: (nid, shortname, longname, oid) + Ok(vm + .new_tuple(( + vm.ctx.new_int(entry.nid), + vm.ctx.new_str(entry.short_name), + vm.ctx.new_str(entry.long_name), + vm.ctx.new_str(entry.oid_string()), + )) + .into()) + } - let err = sys::ERR_peek_last_error(); + #[pyfunction] + fn get_default_verify_paths(vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // Return default certificate paths as a tuple + // Lib/ssl.py expects: (openssl_cafile_env, openssl_cafile, openssl_capath_env, openssl_capath) + // parts[0] = environment variable name for cafile + // parts[1] = default cafile path + // parts[2] = environment variable name for capath + // parts[3] = default capath path + + // Common default paths for different platforms + // These match the first candidates that rustls-native-certs/openssl-probe checks + #[cfg(target_os = "macos")] + let (default_cafile, default_capath) = { + // macOS primarily uses Keychain API, but provides fallback paths + // for compatibility and when Keychain access fails + (Some("/etc/ssl/cert.pem"), Some("/etc/ssl/certs")) + }; - if certs.is_empty() { - // let msg = if filetype == sys::SSL_FILETYPE_PEM { - // "no start line: cadata does not contain a certificate" - // } else { - // "not enough data: cadata does not contain a certificate" - // }; - return Err(ErrorStack::get()); - } - if err != 0 { - return Err(ErrorStack::get()); - } + #[cfg(target_os = "linux")] + let (default_cafile, default_capath) = { + // Linux: matches openssl-probe's first candidate (/etc/ssl/cert.pem) + // openssl-probe checks multiple locations at runtime, but we return + // OpenSSL's compile-time default + (Some("/etc/ssl/cert.pem"), Some("/etc/ssl/certs")) + }; - Ok(certs) - } + #[cfg(not(any(target_os = "macos", target_os = "linux")))] + let (default_cafile, default_capath): (Option<&str>, Option<&str>) = (None, None); + + let tuple = vm.ctx.new_tuple(vec![ + vm.ctx.new_str("SSL_CERT_FILE").into(), // openssl_cafile_env + default_cafile + .map(|s| vm.ctx.new_str(s).into()) + .unwrap_or_else(|| vm.ctx.none()), // openssl_cafile + vm.ctx.new_str("SSL_CERT_DIR").into(), // openssl_capath_env + default_capath + .map(|s| vm.ctx.new_str(s).into()) + .unwrap_or_else(|| vm.ctx.none()), // openssl_capath + ]); + Ok(tuple.into()) } - type CipherTuple = (&'static str, &'static str, i32); - - fn cipher_to_tuple(cipher: &ssl::SslCipherRef) -> CipherTuple { - (cipher.name(), cipher.version(), cipher.bits().secret) + #[pyfunction] + fn RAND_status() -> i32 { + 1 // Always have good randomness with aws-lc-rs } - fn cipher_description(cipher: *const sys::SSL_CIPHER) -> String { - unsafe { - // SSL_CIPHER_description writes up to 128 bytes - let mut buf = vec![0u8; 256]; - let result = sys::SSL_CIPHER_description( - cipher, - buf.as_mut_ptr() as *mut libc::c_char, - buf.len() as i32, - ); - if result.is_null() { - return String::from("No description available"); - } - // Find the null terminator - let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len()); - String::from_utf8_lossy(&buf[..len]).trim().to_string() - } + #[pyfunction] + fn RAND_add(_string: PyObjectRef, _entropy: f64) { + // No-op: aws-lc-rs handles its own entropy + // Accept any type (str, bytes, bytearray) } - impl Read for SocketStream { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - let mut socket: &PySocket = &self.0; - socket.read(buf) + #[pyfunction] + fn RAND_bytes(n: i64, vm: &VirtualMachine) -> PyResult<PyBytesRef> { + use aws_lc_rs::rand::{SecureRandom, SystemRandom}; + + // Validate n is not negative + if n < 0 { + return Err(vm.new_value_error("num must be positive")); } + + let n_usize = n as usize; + let rng = SystemRandom::new(); + let mut buf = vec![0u8; n_usize]; + rng.fill(&mut buf) + .map_err(|_| vm.new_os_error("Failed to generate random bytes"))?; + Ok(PyBytesRef::from(vm.ctx.new_bytes(buf))) } - impl Write for SocketStream { - fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { - let mut socket: &PySocket = &self.0; - socket.write(buf) - } - fn flush(&mut self) -> std::io::Result<()> { - let mut socket: &PySocket = &self.0; - socket.flush() - } + #[pyfunction] + fn RAND_pseudo_bytes(n: i64, vm: &VirtualMachine) -> PyResult<(PyBytesRef, bool)> { + // In rustls/aws-lc-rs, all random bytes are cryptographically strong + let bytes = RAND_bytes(n, vm)?; + Ok((bytes, true)) } - #[cfg(target_os = "android")] - mod android { - use super::convert_openssl_error; - use crate::vm::{VirtualMachine, builtins::PyBaseExceptionRef}; - use openssl::{ - ssl::SslContextBuilder, - x509::{X509, store::X509StoreBuilder}, - }; - use std::{ - fs::{File, read_dir}, - io::Read, - path::Path, + /// Test helper to decode a certificate from a file path + /// + /// This is a simplified wrapper around cert_der_to_dict_helper that handles + /// file reading and PEM/DER auto-detection. Used by test suite. + #[pyfunction] + fn _test_decode_cert(path: PyStrRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // Read certificate file + let cert_data = std::fs::read(path.as_str()).map_err(|e| { + vm.new_os_error(format!( + "Failed to read certificate file {}: {}", + path.as_str(), + e + )) + })?; + + // Auto-detect PEM vs DER format + let cert_der = if cert_data + .windows(27) + .any(|w| w == b"-----BEGIN CERTIFICATE-----") + { + // Parse PEM format + let mut cursor = std::io::Cursor::new(&cert_data); + rustls_pemfile::certs(&mut cursor) + .find_map(|r| r.ok()) + .ok_or_else(|| vm.new_value_error("No valid certificate found in PEM file"))? + .to_vec() + } else { + // Assume DER format + cert_data }; - static CERT_DIR: &'static str = "/system/etc/security/cacerts"; - - pub(super) fn load_client_ca_list( - vm: &VirtualMachine, - b: &mut SslContextBuilder, - ) -> Result<(), PyBaseExceptionRef> { - let root = Path::new(CERT_DIR); - if !root.is_dir() { - return Err(vm.new_exception_msg( - vm.ctx.exceptions.file_not_found_error.to_owned(), - CERT_DIR.to_string(), - )); - } + // Reuse the comprehensive helper function + cert::cert_der_to_dict_helper(vm, &cert_der) + } - let mut combined_pem = String::new(); - let entries = read_dir(root) - .map_err(|err| vm.new_os_error(format!("read cert root: {}", err)))?; - for entry in entries { - let entry = - entry.map_err(|err| vm.new_os_error(format!("iter cert root: {}", err)))?; + #[pyfunction] + fn DER_cert_to_PEM_cert(der_cert: ArgBytesLike, vm: &VirtualMachine) -> PyResult<PyStrRef> { + let der_bytes = der_cert.borrow_buf(); + let bytes_slice: &[u8] = der_bytes.as_ref(); - let path = entry.path(); - if !path.is_file() { - continue; - } + // Use pem-rfc7468 for RFC 7468 compliant PEM encoding + let pem_str = encode_string("CERTIFICATE", LineEnding::LF, bytes_slice) + .map_err(|e| vm.new_value_error(format!("PEM encoding failed: {e}")))?; - File::open(&path) - .and_then(|mut file| file.read_to_string(&mut combined_pem)) - .map_err(|err| { - vm.new_os_error(format!("open cert file {}: {}", path.display(), err)) - })?; + Ok(vm.ctx.new_str(pem_str)) + } - combined_pem.push('\n'); - } + #[pyfunction] + fn PEM_cert_to_DER_cert(pem_cert: PyStrRef, vm: &VirtualMachine) -> PyResult<PyBytesRef> { + let pem_str = pem_cert.as_str(); - let mut store_b = - X509StoreBuilder::new().map_err(|err| convert_openssl_error(vm, err))?; - let x509_vec = X509::stack_from_pem(combined_pem.as_bytes()) - .map_err(|err| convert_openssl_error(vm, err))?; - for x509 in x509_vec { - store_b - .add_cert(x509) - .map_err(|err| convert_openssl_error(vm, err))?; - } - b.set_cert_store(store_b.build()); + // Parse PEM format + let mut cursor = std::io::Cursor::new(pem_str.as_bytes()); + let mut certs = rustls_pemfile::certs(&mut cursor); - Ok(()) + if let Some(Ok(cert)) = certs.next() { + Ok(vm.ctx.new_bytes(cert.to_vec())) + } else { + Err(vm.new_value_error("Failed to parse PEM certificate")) } } -} - -#[cfg(not(ossl101))] -#[pymodule(sub)] -mod ossl101 {} -#[cfg(not(ossl111))] -#[pymodule(sub)] -mod ossl111 {} - -#[cfg(not(windows))] -#[pymodule(sub)] -mod windows {} - -#[allow(non_upper_case_globals)] -#[cfg(ossl101)] -#[pymodule(sub)] -mod ossl101 { - #[pyattr] - use openssl_sys::{ - SSL_OP_NO_COMPRESSION as OP_NO_COMPRESSION, SSL_OP_NO_TLSv1_1 as OP_NO_TLSv1_1, - SSL_OP_NO_TLSv1_2 as OP_NO_TLSv1_2, - }; -} - -#[allow(non_upper_case_globals)] -#[cfg(ossl111)] -#[pymodule(sub)] -mod ossl111 { + // Certificate type for SSL module (pure Rust implementation) #[pyattr] - use openssl_sys::SSL_OP_NO_TLSv1_3 as OP_NO_TLSv1_3; -} - -#[cfg(windows)] -#[pymodule(sub)] -mod windows { - use crate::{ - common::ascii, - vm::{ - PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyFrozenSet, PyStrRef}, - convert::ToPyException, - }, - }; - - #[pyfunction] - fn enum_certificates(store_name: PyStrRef, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> { - use schannel::{RawPointer, cert_context::ValidUses, cert_store::CertStore}; - use windows_sys::Win32::Security::Cryptography; - - // TODO: check every store for it, not just 2 of them: - // https://github.com/python/cpython/blob/3.8/Modules/_ssl.c#L5603-L5610 - let open_fns = [CertStore::open_current_user, CertStore::open_local_machine]; - let stores = open_fns - .iter() - .filter_map(|open| open(store_name.as_str()).ok()) - .collect::<Vec<_>>(); - let certs = stores.iter().flat_map(|s| s.certs()).map(|c| { - let cert = vm.ctx.new_bytes(c.to_der().to_owned()); - let enc_type = unsafe { - let ptr = c.as_ptr() as *const Cryptography::CERT_CONTEXT; - (*ptr).dwCertEncodingType - }; - let enc_type = match enc_type { - Cryptography::X509_ASN_ENCODING => vm.new_pyobj(ascii!("x509_asn")), - Cryptography::PKCS_7_ASN_ENCODING => vm.new_pyobj(ascii!("pkcs_7_asn")), - other => vm.new_pyobj(other), - }; - let usage: PyObjectRef = match c.valid_uses().map_err(|e| e.to_pyexception(vm))? { - ValidUses::All => vm.ctx.new_bool(true).into(), - ValidUses::Oids(oids) => PyFrozenSet::from_iter( - vm, - oids.into_iter().map(|oid| vm.ctx.new_str(oid).into()), - )? - .into_ref(&vm.ctx) - .into(), - }; - Ok(vm.new_tuple((cert, enc_type, usage)).into()) - }); - let certs: Vec<PyObjectRef> = certs.collect::<PyResult<Vec<_>>>()?; - Ok(certs) + #[pyclass(module = "_ssl", name = "Certificate")] + #[derive(Debug, PyPayload)] + pub struct PySSLCertificate { + // Store the raw DER bytes + der_bytes: Vec<u8>, } -} - -mod bio { - //! based off rust-openssl's private `bio` module - use libc::c_int; - use openssl::error::ErrorStack; - use openssl_sys as sys; - use std::marker::PhantomData; + impl PySSLCertificate { + // Parse the certificate lazily + fn parse(&self) -> Result<x509_parser::certificate::X509Certificate<'_>, String> { + match x509_parser::parse_x509_certificate(&self.der_bytes) { + Ok((_, cert)) => Ok(cert), + Err(e) => Err(format!("Failed to parse certificate: {e}")), + } + } + } - pub struct MemBioSlice<'a>(*mut sys::BIO, PhantomData<&'a [u8]>); + #[pyclass(with(Comparable, Hashable, Representable))] + impl PySSLCertificate { + #[pymethod] + fn public_bytes( + &self, + format: OptionalArg<i32>, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + let format = format.unwrap_or(ENCODING_PEM); - impl Drop for MemBioSlice<'_> { - fn drop(&mut self) { - unsafe { - sys::BIO_free_all(self.0); + match format { + x if x == ENCODING_DER => { + // Return DER bytes directly + Ok(vm.ctx.new_bytes(self.der_bytes.clone()).into()) + } + x if x == ENCODING_PEM => { + // Convert DER to PEM using RFC 7468 compliant encoding + let pem_str = encode_string("CERTIFICATE", LineEnding::LF, &self.der_bytes) + .map_err(|e| vm.new_value_error(format!("PEM encoding failed: {e}")))?; + Ok(vm.ctx.new_str(pem_str).into()) + } + _ => Err(vm.new_value_error("Unsupported format")), } } + + #[pymethod] + fn get_info(&self, vm: &VirtualMachine) -> PyResult { + let cert = self.parse().map_err(|e| vm.new_value_error(e))?; + cert::cert_to_dict(vm, &cert) + } } - impl<'a> MemBioSlice<'a> { - pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> { - openssl::init(); + // Implement Comparable trait for PySSLCertificate + impl Comparable for PySSLCertificate { + fn cmp( + zelf: &Py<Self>, + other: &PyObject, + op: PyComparisonOp, + _vm: &VirtualMachine, + ) -> PyResult<PyComparisonValue> { + op.eq_only(|| { + if let Some(other_cert) = other.downcast_ref::<Self>() { + Ok((zelf.der_bytes == other_cert.der_bytes).into()) + } else { + Ok(PyComparisonValue::NotImplemented) + } + }) + } + } - assert!(buf.len() <= c_int::MAX as usize); - let bio = unsafe { sys::BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int) }; - if bio.is_null() { - return Err(ErrorStack::get()); - } + // Implement Hashable trait for PySSLCertificate + impl Hashable for PySSLCertificate { + fn hash(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyHash> { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; - Ok(MemBioSlice(bio, PhantomData)) + let mut hasher = DefaultHasher::new(); + zelf.der_bytes.hash(&mut hasher); + Ok(hasher.finish() as PyHash) } + } - pub fn as_ptr(&self) -> *mut sys::BIO { - self.0 + // Implement Representable trait for PySSLCertificate + impl Representable for PySSLCertificate { + #[inline] + fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { + // Try to parse and show subject + match zelf.parse() { + Ok(cert) => { + let subject = cert.subject(); + // Get CN if available + let cn = subject + .iter_common_name() + .next() + .and_then(|attr| attr.as_str().ok()) + .unwrap_or("Unknown"); + Ok(format!("<Certificate(subject=CN={cn})>")) + } + Err(_) => Ok("<Certificate(invalid)>".to_owned()), + } } } } diff --git a/stdlib/src/ssl/cert.rs b/stdlib/src/ssl/cert.rs index 19dd09f3379..2baefad700b 100644 --- a/stdlib/src/ssl/cert.rs +++ b/stdlib/src/ssl/cert.rs @@ -1,232 +1,1774 @@ -pub(super) use ssl_cert::{PySSLCertificate, cert_to_certificate, cert_to_py, obj2txt}; - -// Certificate type for SSL module - -#[pymodule(sub)] -pub(crate) mod ssl_cert { - use crate::{ - common::ascii, - vm::{ - PyObjectRef, PyPayload, PyResult, VirtualMachine, - convert::{ToPyException, ToPyObject}, - function::{FsPath, OptionalArg}, - }, +// cspell: ignore accessdescs + +//! Certificate parsing, validation, and conversion utilities for SSL/TLS +//! +//! This module provides reusable functions for working with X.509 certificates: +//! - Parsing PEM/DER encoded certificates +//! - Validating certificate properties (CA status, etc.) +//! - Converting certificates to Python dict format +//! - Building and verifying certificate chains +//! - Loading certificates from files, directories, and bytes + +use chrono::{DateTime, Utc}; +use parking_lot::RwLock as ParkingRwLock; +use rustls::{ + DigitallySignedStruct, RootCertStore, SignatureScheme, + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime}, + server::danger::{ClientCertVerified, ClientCertVerifier}, +}; +use rustpython_vm::{PyObjectRef, PyResult, VirtualMachine}; +use std::collections::HashSet; +use std::sync::Arc; +use x509_parser::prelude::*; + +use super::compat::{VERIFY_X509_PARTIAL_CHAIN, VERIFY_X509_STRICT}; + +// Certificate Verification Constants + +/// All supported signature schemes for certificate verification +/// +/// This list includes all modern signature algorithms supported by rustls. +/// Used by verifiers that accept any signature scheme (NoVerifier, EmptyRootStoreVerifier). +const ALL_SIGNATURE_SCHEMES: &[SignatureScheme] = &[ + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP521_SHA512, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::ED25519, +]; + +// Error Handling Utilities + +/// Certificate loading error types with specific error messages +/// +/// This module provides consistent error creation functions for certificate +/// operations, reducing code duplication and ensuring uniform error messages +/// across the codebase. +mod cert_error { + use std::io; + use std::sync::Arc; + + /// Create InvalidData error with formatted message + pub fn invalid_data(msg: impl Into<String>) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, msg.into()) + } + + /// PEM parsing error variants + pub mod pem { + use super::*; + + pub fn no_start_line(context: &str) -> io::Error { + invalid_data(format!("no start line: {context}")) + } + + pub fn parse_failed(e: impl std::fmt::Display) -> io::Error { + invalid_data(format!("Failed to parse PEM certificate: {e}")) + } + + pub fn parse_failed_debug(e: impl std::fmt::Debug) -> io::Error { + invalid_data(format!("Failed to parse PEM certificate: {e:?}")) + } + + pub fn invalid_cert() -> io::Error { + invalid_data("No certificates found in certificate file") + } + } + + /// DER parsing error variants + pub mod der { + use super::*; + + pub fn not_enough_data(context: &str) -> io::Error { + invalid_data(format!("not enough data: {context}")) + } + + pub fn parse_failed(e: impl std::fmt::Display) -> io::Error { + invalid_data(format!("Failed to parse DER certificate: {e}")) + } + } + + /// Private key error variants + pub mod key { + use super::*; + + pub fn not_found(context: &str) -> io::Error { + invalid_data(format!("No private key found in {context}")) + } + + pub fn parse_failed(e: impl std::fmt::Display) -> io::Error { + invalid_data(format!("Failed to parse private key: {e}")) + } + + pub fn parse_encrypted_failed(e: impl std::fmt::Display) -> io::Error { + invalid_data(format!("Failed to parse encrypted private key: {e}")) + } + + pub fn decrypt_failed(e: impl std::fmt::Display) -> io::Error { + io::Error::other(format!( + "Failed to decrypt private key (wrong password?): {e}", + )) + } + } + + /// Convert error message to rustls::Error with InvalidCertificate wrapper + pub fn to_rustls_invalid_cert(msg: impl Into<String>) -> rustls::Error { + rustls::Error::InvalidCertificate(rustls::CertificateError::Other(rustls::OtherError( + Arc::new(invalid_data(msg)), + ))) + } + + /// Convert error message to rustls::Error with InvalidCertificate wrapper and custom ErrorKind + pub fn to_rustls_cert_error(kind: io::ErrorKind, msg: impl Into<String>) -> rustls::Error { + rustls::Error::InvalidCertificate(rustls::CertificateError::Other(rustls::OtherError( + Arc::new(io::Error::new(kind, msg.into())), + ))) + } +} + +// Helper Functions for Certificate Parsing + +/// Map X.509 OID to human-readable attribute name +/// +/// Converts common X.509 Distinguished Name OIDs to their standard names. +/// Returns the OID string itself if not recognized. +fn oid_to_attribute_name(oid_str: &str) -> &str { + match oid_str { + "2.5.4.3" => "commonName", + "2.5.4.6" => "countryName", + "2.5.4.7" => "localityName", + "2.5.4.8" => "stateOrProvinceName", + "2.5.4.10" => "organizationName", + "2.5.4.11" => "organizationalUnitName", + "1.2.840.113549.1.9.1" => "emailAddress", + _ => oid_str, + } +} + +/// Format IP address (IPv4 or IPv6) to string +/// +/// Formats raw IP address bytes according to standard notation: +/// - IPv4: dotted decimal (e.g., "192.0.2.1") +/// - IPv6: colon-separated hex (e.g., "2001:DB8:0:0:0:0:0:1") +fn format_ip_address(ip: &[u8]) -> String { + if ip.len() == 4 { + // IPv4 + format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]) + } else if ip.len() == 16 { + // IPv6 - format in full form without compression (uppercase) + // CPython returns IPv6 in full form: 2001:DB8:0:0:0:0:0:1 (not 2001:db8::1) + let segments = [ + u16::from_be_bytes([ip[0], ip[1]]), + u16::from_be_bytes([ip[2], ip[3]]), + u16::from_be_bytes([ip[4], ip[5]]), + u16::from_be_bytes([ip[6], ip[7]]), + u16::from_be_bytes([ip[8], ip[9]]), + u16::from_be_bytes([ip[10], ip[11]]), + u16::from_be_bytes([ip[12], ip[13]]), + u16::from_be_bytes([ip[14], ip[15]]), + ]; + format!( + "{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}", + segments[0], + segments[1], + segments[2], + segments[3], + segments[4], + segments[5], + segments[6], + segments[7] + ) + } else { + // Unknown format - return as debug string + format!("{ip:?}") + } +} + +/// Format ASN.1 time to string +/// +/// Formats certificate validity dates in the format: +/// "Mon DD HH:MM:SS YYYY GMT" +fn format_asn1_time(time: &x509_parser::time::ASN1Time) -> String { + let timestamp = time.timestamp(); + DateTime::<Utc>::from_timestamp(timestamp, 0) + .expect("ASN1Time must be valid timestamp") + .format("%b %e %H:%M:%S %Y GMT") + .to_string() +} + +/// Format certificate serial number to hexadecimal string with even padding +/// +/// Converts a BigUint serial number to uppercase hex string, ensuring +/// even length by prepending '0' if necessary. +fn format_serial_number(serial: &num_bigint::BigUint) -> String { + let mut serial_str = serial.to_str_radix(16).to_uppercase(); + if serial_str.len() % 2 == 1 { + serial_str.insert(0, '0'); + } + serial_str +} + +/// Normalize wildcard hostname by stripping "*." prefix +/// +/// Returns the normalized hostname without the wildcard prefix. +/// Used for wildcard certificate matching. +fn normalize_wildcard_hostname(hostname: &str) -> &str { + hostname.strip_prefix("*.").unwrap_or(hostname) +} + +/// Process Subject Alternative Name (SAN) general names into Python tuples +/// +/// Converts X.509 GeneralName entries into Python tuple format. +/// Returns a vector of PyObjectRef tuples in the format: (type, value) +fn process_san_general_names( + vm: &VirtualMachine, + general_names: &[GeneralName<'_>], +) -> Vec<PyObjectRef> { + general_names + .iter() + .filter_map(|name| match name { + GeneralName::DNSName(dns) => Some(vm.new_tuple(("DNS", *dns)).into()), + GeneralName::IPAddress(ip) => { + let ip_str = format_ip_address(ip); + Some(vm.new_tuple(("IP Address", ip_str)).into()) + } + GeneralName::RFC822Name(email) => Some(vm.new_tuple(("email", *email)).into()), + GeneralName::URI(uri) => Some(vm.new_tuple(("URI", *uri)).into()), + GeneralName::DirectoryName(dn) => { + let dn_str = format!("{dn}"); + Some(vm.new_tuple(("DirName", dn_str)).into()) + } + GeneralName::RegisteredID(oid) => { + let oid_str = oid.to_string(); + Some(vm.new_tuple(("Registered ID", oid_str)).into()) + } + GeneralName::OtherName(oid, value) => { + let oid_str = oid.to_string(); + let value_str = format!("{value:?}"); + Some( + vm.new_tuple(("othername", format!("{oid_str}:{value_str}"))) + .into(), + ) + } + _ => None, + }) + .collect() +} + +// Certificate Validation and Parsing + +/// Check if a certificate is a CA certificate by examining the Basic Constraints extension +/// +/// Returns `true` if the certificate has Basic Constraints with CA=true, +/// `false` otherwise (including parse errors or missing extension). +/// This matches OpenSSL's X509_check_ca() behavior. +pub fn is_ca_certificate(cert_der: &[u8]) -> bool { + // Parse the certificate + let Ok((_, cert)) = X509Certificate::from_der(cert_der) else { + return false; }; - use foreign_types_shared::ForeignTypeRef; - use openssl::{ - asn1::Asn1ObjectRef, - x509::{self, X509, X509Ref}, + + // Check Basic Constraints extension + // If extension exists and CA=true, it's a CA certificate + // Otherwise (no extension or CA=false), it's NOT a CA certificate + if let Ok(Some(ext)) = cert.basic_constraints() { + return ext.value.ca; + } + + // No Basic Constraints extension -> NOT a CA certificate + // (matches OpenSSL X509_check_ca() behavior) + false +} + +/// Convert an X509Name to Python nested tuple format for SSL certificate dicts +/// +/// Format: ((('CN', 'example.com'),), (('O', 'Example Org'),), ...) +fn name_to_py(vm: &VirtualMachine, name: &x509_parser::x509::X509Name<'_>) -> PyResult { + let list: Vec<PyObjectRef> = name + .iter() + .flat_map(|rdn| { + // Each RDN can have multiple attributes + rdn.iter() + .map(|attr| { + let oid_str = attr.attr_type().to_id_string(); + let value_str = attr.attr_value().as_str().unwrap_or("").to_string(); + let key = oid_to_attribute_name(&oid_str); + + vm.new_tuple((vm.new_tuple((vm.ctx.new_str(key), vm.ctx.new_str(value_str))),)) + .into() + }) + .collect::<Vec<_>>() + }) + .collect(); + + Ok(vm.ctx.new_tuple(list).into()) +} + +/// Convert DER-encoded certificate to Python dict (for getpeercert with binary_form=False) +/// +/// Returns a dict with fields: subject, issuer, version, serialNumber, +/// notBefore, notAfter, subjectAltName (if present) +pub fn cert_to_dict( + vm: &VirtualMachine, + cert: &x509_parser::certificate::X509Certificate<'_>, +) -> PyResult { + let dict = vm.ctx.new_dict(); + + // Subject and Issuer + dict.set_item("subject", name_to_py(vm, cert.subject())?, vm)?; + dict.set_item("issuer", name_to_py(vm, cert.issuer())?, vm)?; + + // Version (X.509 v3 = version 2 in the cert, but Python uses 3) + dict.set_item( + "version", + vm.ctx.new_int(cert.version().0 as i32 + 1).into(), + vm, + )?; + + // Serial number - hex format with even length + let serial = format_serial_number(&cert.serial); + dict.set_item("serialNumber", vm.ctx.new_str(serial).into(), vm)?; + + // Validity dates - format with GMT using chrono + dict.set_item( + "notBefore", + vm.ctx + .new_str(format_asn1_time(&cert.validity().not_before)) + .into(), + vm, + )?; + dict.set_item( + "notAfter", + vm.ctx + .new_str(format_asn1_time(&cert.validity().not_after)) + .into(), + vm, + )?; + + // Subject Alternative Names (if present) + if let Ok(Some(san_ext)) = cert.subject_alternative_name() { + let san_list = process_san_general_names(vm, &san_ext.value.general_names); + + if !san_list.is_empty() { + dict.set_item("subjectAltName", vm.ctx.new_tuple(san_list).into(), vm)?; + } + } + + Ok(dict.into()) +} + +/// Convert DER-encoded certificate to Python dict (for get_ca_certs) +/// +/// Similar to cert_to_dict but includes additional fields like crlDistributionPoints +/// and uses CPython's specific ordering: issuer, notAfter, notBefore, serialNumber, subject, version +pub fn cert_der_to_dict_helper(vm: &VirtualMachine, cert_der: &[u8]) -> PyResult<PyObjectRef> { + // Parse the certificate using x509-parser + let (_, cert) = x509_parser::parse_x509_certificate(cert_der) + .map_err(|e| vm.new_value_error(format!("Failed to parse certificate: {e}")))?; + + // Helper to convert X509Name to nested tuple format + let name_to_tuple = |name: &x509_parser::x509::X509Name<'_>| -> PyResult { + let mut entries = Vec::new(); + for rdn in name.iter() { + for attr in rdn.iter() { + let oid_str = attr.attr_type().to_id_string(); + + // Get value as bytes and convert to string + let value_str = if let Ok(s) = attr.attr_value().as_str() { + s.to_string() + } else { + let value_bytes = attr.attr_value().data; + match std::str::from_utf8(value_bytes) { + Ok(s) => s.to_string(), + Err(_) => String::from_utf8_lossy(value_bytes).into_owned(), + } + }; + + let key = oid_to_attribute_name(&oid_str); + + let entry = + vm.new_tuple((vm.ctx.new_str(key.to_string()), vm.ctx.new_str(value_str))); + entries.push(vm.new_tuple((entry,)).into()); + } + } + Ok(vm.ctx.new_tuple(entries).into()) }; - use openssl_sys as sys; - use std::fmt; - - // Import constants and error converter from _ssl module - use crate::ssl::_ssl::{ENCODING_DER, ENCODING_PEM, convert_openssl_error}; - - pub(crate) fn obj2txt(obj: &Asn1ObjectRef, no_name: bool) -> Option<String> { - let no_name = i32::from(no_name); - let ptr = obj.as_ptr(); - let b = unsafe { - let buflen = sys::OBJ_obj2txt(std::ptr::null_mut(), 0, ptr, no_name); - assert!(buflen >= 0); - if buflen == 0 { - return None; - } - let buflen = buflen as usize; - let mut buf = Vec::<u8>::with_capacity(buflen + 1); - let ret = sys::OBJ_obj2txt( - buf.as_mut_ptr() as *mut libc::c_char, - buf.capacity() as _, - ptr, - no_name, - ); - assert!(ret >= 0); - // SAFETY: OBJ_obj2txt initialized the buffer successfully - buf.set_len(buflen); - buf + + let dict = vm.ctx.new_dict(); + + // CPython ordering: issuer, notAfter, notBefore, serialNumber, subject, version + dict.set_item("issuer", name_to_tuple(cert.issuer())?, vm)?; + + // Validity - format with GMT using chrono + dict.set_item( + "notAfter", + vm.ctx + .new_str(format_asn1_time(&cert.validity().not_after)) + .into(), + vm, + )?; + dict.set_item( + "notBefore", + vm.ctx + .new_str(format_asn1_time(&cert.validity().not_before)) + .into(), + vm, + )?; + + // Serial number - hex format with even length + let serial = format_serial_number(&cert.serial); + dict.set_item("serialNumber", vm.ctx.new_str(serial).into(), vm)?; + + dict.set_item("subject", name_to_tuple(cert.subject())?, vm)?; + + // Version + dict.set_item( + "version", + vm.ctx.new_int(cert.version().0 as i32 + 1).into(), + vm, + )?; + + // Authority Information Access (OCSP and caIssuers) - use x509-parser's extensions_map + let mut ocsp_urls = Vec::new(); + let mut ca_issuer_urls = Vec::new(); + let mut crl_urls = Vec::new(); + + if let Ok(ext_map) = cert.tbs_certificate.extensions_map() { + use x509_parser::extensions::{GeneralName, ParsedExtension}; + use x509_parser::oid_registry::{ + OID_PKIX_AUTHORITY_INFO_ACCESS, OID_X509_EXT_CRL_DISTRIBUTION_POINTS, }; - let s = String::from_utf8(b) - .unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned()); - Some(s) + + // Authority Information Access + if let Some(ext) = ext_map.get(&OID_PKIX_AUTHORITY_INFO_ACCESS) + && let ParsedExtension::AuthorityInfoAccess(aia) = &ext.parsed_extension() + { + for desc in &aia.accessdescs { + if let GeneralName::URI(uri) = &desc.access_location { + let method_str = desc.access_method.to_id_string(); + if method_str == "1.3.6.1.5.5.7.48.1" { + // OCSP + ocsp_urls.push(vm.ctx.new_str(uri.to_string()).into()); + } else if method_str == "1.3.6.1.5.5.7.48.2" { + // caIssuers + ca_issuer_urls.push(vm.ctx.new_str(uri.to_string()).into()); + } + } + } + } + + // CRL Distribution Points + if let Some(ext) = ext_map.get(&OID_X509_EXT_CRL_DISTRIBUTION_POINTS) + && let ParsedExtension::CRLDistributionPoints(cdp) = &ext.parsed_extension() + { + for dp in cdp.points.iter() { + if let Some(dist_point) = &dp.distribution_point { + use x509_parser::extensions::DistributionPointName; + if let DistributionPointName::FullName(names) = dist_point { + for name in names { + if let GeneralName::URI(uri) = name { + crl_urls.push(vm.ctx.new_str(uri.to_string()).into()); + } + } + } + } + } + } } - #[pyattr] - #[pyclass(module = "ssl", name = "Certificate")] - #[derive(PyPayload)] - pub(crate) struct PySSLCertificate { - cert: X509, + if !ocsp_urls.is_empty() { + dict.set_item("OCSP", vm.ctx.new_tuple(ocsp_urls).into(), vm)?; + } + if !ca_issuer_urls.is_empty() { + dict.set_item("caIssuers", vm.ctx.new_tuple(ca_issuer_urls).into(), vm)?; + } + if !crl_urls.is_empty() { + dict.set_item( + "crlDistributionPoints", + vm.ctx.new_tuple(crl_urls).into(), + vm, + )?; } - impl fmt::Debug for PySSLCertificate { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("Certificate") + // Subject Alternative Names + if let Ok(Some(san_ext)) = cert.subject_alternative_name() { + let mut san_entries = Vec::new(); + for name in &san_ext.value.general_names { + use x509_parser::extensions::GeneralName; + match name { + GeneralName::DNSName(dns) => { + san_entries.push(vm.new_tuple(("DNS", *dns)).into()); + } + GeneralName::IPAddress(ip) => { + let ip_str = format_ip_address(ip); + san_entries.push(vm.new_tuple(("IP Address", ip_str)).into()); + } + GeneralName::RFC822Name(email) => { + san_entries.push(vm.new_tuple(("email", *email)).into()); + } + GeneralName::URI(uri) => { + san_entries.push(vm.new_tuple(("URI", *uri)).into()); + } + GeneralName::OtherName(_oid, _data) => { + // OtherName is not fully supported, mark as unsupported + san_entries.push(vm.new_tuple(("othername", "<unsupported>")).into()); + } + GeneralName::DirectoryName(name) => { + // Convert X509Name to nested tuple format + let dir_tuple = name_to_tuple(name)?; + san_entries.push(vm.new_tuple(("DirName", dir_tuple)).into()); + } + GeneralName::RegisteredID(oid) => { + // Convert OID to string representation + let oid_str = oid.to_id_string(); + san_entries.push(vm.new_tuple(("Registered ID", oid_str)).into()); + } + _ => {} + } + } + if !san_entries.is_empty() { + dict.set_item("subjectAltName", vm.ctx.new_tuple(san_entries).into(), vm)?; } } - #[pyclass] - impl PySSLCertificate { - #[pymethod] - fn public_bytes( - &self, - format: OptionalArg<i32>, - vm: &VirtualMachine, - ) -> PyResult<PyObjectRef> { - let format = format.unwrap_or(ENCODING_PEM); + Ok(dict.into()) +} + +/// Build a verified certificate chain by adding CA certificates from the trust store +/// +/// Takes peer certificates (from TLS handshake) and extends the chain by finding +/// issuer certificates from the trust store until reaching a root certificate. +/// +/// Returns the complete chain as DER-encoded bytes. +pub fn build_verified_chain( + peer_certs: &[CertificateDer<'static>], + ca_certs_der: &[Vec<u8>], +) -> Vec<Vec<u8>> { + let mut chain_der: Vec<Vec<u8>> = Vec::new(); + + // Start with peer certificates (what was sent during handshake) + for cert in peer_certs { + chain_der.push(cert.as_ref().to_vec()); + } + + // Keep adding issuers until we reach a root or can't find the issuer + while let Some(der) = chain_der.last() { + let last_cert_der = der; + + // Parse the last certificate in the chain + let (_, last_cert) = match X509Certificate::from_der(last_cert_der) { + Ok(parsed) => parsed, + Err(_) => break, + }; + + // Check if it's self-signed (root certificate) + if last_cert.subject() == last_cert.issuer() { + // This is a root certificate, we're done + break; + } - match format { - x if x == ENCODING_DER => { - // DER encoding - let der = self - .cert - .to_der() - .map_err(|e| convert_openssl_error(vm, e))?; - Ok(vm.ctx.new_bytes(der).into()) + // Try to find the issuer in the trust store + let issuer_name = last_cert.issuer(); + let mut found_issuer = false; + + for ca_der in ca_certs_der.iter() { + let (_, ca_cert) = match X509Certificate::from_der(ca_der) { + Ok(parsed) => parsed, + Err(_) => continue, + }; + + // Check if this CA's subject matches the certificate's issuer + if ca_cert.subject() == issuer_name { + // Check if we already have this certificate in the chain + if !chain_der.iter().any(|existing| existing == ca_der) { + chain_der.push(ca_der.clone()); + found_issuer = true; + break; } - x if x == ENCODING_PEM => { - // PEM encoding - let pem = self - .cert - .to_pem() - .map_err(|e| convert_openssl_error(vm, e))?; - Ok(vm.ctx.new_bytes(pem).into()) + } + } + + if !found_issuer { + // Can't find issuer, stop here + break; + } + } + + chain_der +} + +/// Statistics from certificate loading operations +#[derive(Debug, Clone, Default)] +pub struct CertStats { + pub total_certs: usize, + pub ca_certs: usize, +} + +/// Certificate loader that handles PEM/DER parsing and validation +/// +/// This structure encapsulates the common pattern of loading certificates +/// from various sources (files, directories, bytes) and adding them to +/// a RootCertStore while tracking statistics. +/// +/// Duplicate certificates are detected and only counted once. +pub struct CertLoader<'a> { + store: &'a mut RootCertStore, + ca_certs_der: &'a mut Vec<Vec<u8>>, + seen_certs: HashSet<Vec<u8>>, +} + +impl<'a> CertLoader<'a> { + /// Create a new CertLoader with references to the store and DER cache + pub fn new(store: &'a mut RootCertStore, ca_certs_der: &'a mut Vec<Vec<u8>>) -> Self { + // Initialize seen_certs with existing certificates + let seen_certs = ca_certs_der.iter().cloned().collect(); + Self { + store, + ca_certs_der, + seen_certs, + } + } + + /// Load certificates from a file (supports both PEM and DER formats) + /// + /// Returns statistics about loaded certificates + pub fn load_from_file(&mut self, path: &str) -> Result<CertStats, std::io::Error> { + let contents = std::fs::read(path)?; + self.load_from_bytes(&contents) + } + + /// Load certificates from a directory + /// + /// Reads all files in the directory and attempts to parse them as certificates. + /// Invalid files are silently skipped (matches OpenSSL capath behavior). + pub fn load_from_dir(&mut self, dir_path: &str) -> Result<CertStats, std::io::Error> { + let entries = std::fs::read_dir(dir_path)?; + let mut stats = CertStats::default(); + + for entry in entries { + let entry = entry?; + let path = entry.path(); + + // Skip directories and process all files + // OpenSSL capath uses hash-based naming like "4e1295a3.0" + if path.is_file() + && let Ok(contents) = std::fs::read(&path) + { + // Ignore errors for individual files (some may not be certs) + if let Ok(file_stats) = self.load_from_bytes(&contents) { + stats.total_certs += file_stats.total_certs; + stats.ca_certs += file_stats.ca_certs; } - _ => Err(vm.new_value_error("Unsupported format".to_owned())), } } - #[pymethod] - fn get_info(&self, vm: &VirtualMachine) -> PyResult { - cert_to_dict(vm, &self.cert) + Ok(stats) + } + + /// Helper: Add a certificate to the store with duplicate checking + /// + /// Returns true if the certificate was added (not a duplicate), false if it was a duplicate. + fn add_cert_to_store( + &mut self, + cert_bytes: Vec<u8>, + cert_der: CertificateDer<'static>, + treat_all_as_ca: bool, + stats: &mut CertStats, + ) -> bool { + // Check for duplicates using HashSet + if !self.seen_certs.insert(cert_bytes.clone()) { + return false; // Duplicate certificate - skip + } + + // Determine if this is a CA certificate + let is_ca = if treat_all_as_ca { + true + } else { + is_ca_certificate(&cert_bytes) + }; + + // Store full DER for get_ca_certs() + self.ca_certs_der.push(cert_bytes); + + // Add to trust store (rustls may handle duplicates internally) + let _ = self.store.add(cert_der); + + // Update statistics + stats.total_certs += 1; + if is_ca { + stats.ca_certs += 1; } + + true } - fn name_to_py(vm: &VirtualMachine, name: &x509::X509NameRef) -> PyResult { - let list = name - .entries() - .map(|entry| { - let txt = obj2txt(entry.object(), false).to_pyobject(vm); - let asn1_str = entry.data(); - let data_bytes = asn1_str.as_slice(); - let data = match std::str::from_utf8(data_bytes) { - Ok(s) => vm.ctx.new_str(s.to_owned()), - Err(_) => vm - .ctx - .new_str(String::from_utf8_lossy(data_bytes).into_owned()), - }; - Ok(vm.new_tuple(((txt, data),)).into()) - }) - .collect::<Result<_, _>>()?; - Ok(vm.ctx.new_tuple(list).into()) - } - - // Helper to convert X509 to dict (for getpeercert with binary=False) - fn cert_to_dict(vm: &VirtualMachine, cert: &X509Ref) -> PyResult { - let dict = vm.ctx.new_dict(); - - dict.set_item("subject", name_to_py(vm, cert.subject_name())?, vm)?; - dict.set_item("issuer", name_to_py(vm, cert.issuer_name())?, vm)?; - // X.509 version: OpenSSL uses 0-based (0=v1, 1=v2, 2=v3) but Python uses 1-based (1=v1, 2=v2, 3=v3) - dict.set_item("version", vm.new_pyobj(cert.version() + 1), vm)?; - - let serial_num = cert - .serial_number() - .to_bn() - .and_then(|bn| bn.to_hex_str()) - .map_err(|e| convert_openssl_error(vm, e))?; - dict.set_item( - "serialNumber", - vm.ctx.new_str(serial_num.to_owned()).into(), - vm, - )?; + /// Load certificates from byte slice (auto-detects PEM vs DER format) + /// + /// Tries to parse as PEM first, falls back to DER if that fails. + /// Duplicate certificates are detected and only counted once. + /// + /// If `treat_all_as_ca` is true, all certificates are counted as CA certificates + /// regardless of their Basic Constraints (this matches + /// load_verify_locations with cadata parameter). + /// + /// If `pem_only` is true, only PEM parsing is attempted (for string input) + pub fn load_from_bytes_ex( + &mut self, + data: &[u8], + treat_all_as_ca: bool, + pem_only: bool, + ) -> Result<CertStats, std::io::Error> { + let mut stats = CertStats::default(); - dict.set_item( - "notBefore", - vm.ctx.new_str(cert.not_before().to_string()).into(), - vm, - )?; - dict.set_item( - "notAfter", - vm.ctx.new_str(cert.not_after().to_string()).into(), - vm, - )?; + // Try to parse as PEM first + let mut cursor = std::io::Cursor::new(data); + let certs_iter = rustls_pemfile::certs(&mut cursor); + + let mut found_any = false; + let mut first_pem_error = None; // Store first PEM parsing error + for cert_result in certs_iter { + match cert_result { + Ok(cert) => { + found_any = true; + let cert_bytes = cert.to_vec(); + + // Validate that this is actually a valid X.509 certificate + // rustls_pemfile only does base64 decoding, not X.509 validation + if let Err(e) = X509Certificate::from_der(&cert_bytes) { + // Invalid X.509 certificate + return Err(cert_error::pem::parse_failed_debug(e)); + } + + // Add certificate using helper method (handles duplicates) + self.add_cert_to_store(cert_bytes, cert, treat_all_as_ca, &mut stats); + // Helper returns false for duplicates (skip counting) + } + Err(e) if !found_any => { + // PEM parsing failed on first certificate + if pem_only { + // For string input (PEM only), return "no start line" error + return Err(cert_error::pem::no_start_line( + "cadata does not contain a certificate", + )); + } + // Store the error and break to try DER format below + first_pem_error = Some(e); + break; + } + Err(e) => { + // PEM parsing failed after some certs were loaded + return Err(cert_error::pem::parse_failed(e)); + } + } + } + + // If PEM parsing found nothing, try DER format (unless pem_only) + // DER can have multiple certificates concatenated, so parse them sequentially + if !found_any && stats.total_certs == 0 { + // If we had a PEM parsing error, return it instead of trying DER fallback + // This ensures that malformed PEM files (like badcert.pem) raise an error + if let Some(e) = first_pem_error { + return Err(cert_error::pem::parse_failed(e)); + } - if let Some(names) = cert.subject_alt_names() { - let san: Vec<PyObjectRef> = names - .iter() - .map(|gen_name| { - if let Some(email) = gen_name.email() { - vm.new_tuple((ascii!("email"), email)).into() - } else if let Some(dnsname) = gen_name.dnsname() { - vm.new_tuple((ascii!("DNS"), dnsname)).into() - } else if let Some(ip) = gen_name.ipaddress() { - // Parse IP address properly (IPv4 or IPv6) - let ip_str = if ip.len() == 4 { - // IPv4 - format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]) - } else if ip.len() == 16 { - // IPv6 - format with all zeros visible (not compressed) - let ip_addr = std::net::Ipv6Addr::from([ - ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], - ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15], - ]); - let s = ip_addr.segments(); - format!( - "{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}", - s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7] - ) + // For PEM-only mode (string input), don't fallback to DER + if pem_only { + return Err(cert_error::pem::no_start_line( + "cadata does not contain a certificate", + )); + } + let mut remaining = data; + let mut loaded_count = 0; + + while !remaining.is_empty() { + match X509Certificate::from_der(remaining) { + Ok((rest, _parsed_cert)) => { + // Extract the DER bytes for this certificate + // Length = total remaining - bytes left after parsing + let cert_len = remaining.len() - rest.len(); + let cert_bytes = &remaining[..cert_len]; + let cert_der = CertificateDer::from(cert_bytes.to_vec()); + + // Add certificate using helper method (handles duplicates) + self.add_cert_to_store( + cert_bytes.to_vec(), + cert_der, + treat_all_as_ca, + &mut stats, + ); + + loaded_count += 1; + remaining = rest; // Move to next certificate + } + Err(e) => { + if loaded_count == 0 { + // Failed to parse first certificate - invalid data + return Err(cert_error::der::not_enough_data( + "cadata does not contain a certificate", + )); } else { - // Fallback for unexpected length - String::from_utf8_lossy(ip).into_owned() - }; - vm.new_tuple((ascii!("IP Address"), ip_str)).into() - } else if let Some(uri) = gen_name.uri() { - vm.new_tuple((ascii!("URI"), uri)).into() - } else { - // Handle DirName, Registered ID, and othername - // Check if this is a directory name - if let Some(dirname) = gen_name.directory_name() - && let Ok(py_name) = name_to_py(vm, dirname) - { - return vm.new_tuple((ascii!("DirName"), py_name)).into(); + // Loaded some certificates but failed on subsequent data (garbage) + return Err(cert_error::der::parse_failed(e)); } + } + } + } + + // If we somehow got here with no certificates loaded + if loaded_count == 0 { + return Err(cert_error::der::not_enough_data( + "cadata does not contain a certificate", + )); + } + } + + Ok(stats) + } + + /// Load certificates from byte slice (auto-detects PEM vs DER format) + /// + /// This is a convenience wrapper that calls load_from_bytes_ex with treat_all_as_ca=false + /// and pem_only=false. + pub fn load_from_bytes(&mut self, data: &[u8]) -> Result<CertStats, std::io::Error> { + self.load_from_bytes_ex(data, false, false) + } +} + +// NoVerifier: disables certificate verification (for CERT_NONE mode) +#[derive(Debug)] +pub struct NoVerifier; + +impl ServerCertVerifier for NoVerifier { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result<ServerCertVerified, rustls::Error> { + // Accept all certificates without verification + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + // Accept all signatures without verification + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + // Accept all signatures without verification + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + ALL_SIGNATURE_SCHEMES.to_vec() + } +} - // TODO: Handle Registered ID (GEN_RID) - // CPython implementation uses i2t_ASN1_OBJECT to convert OID - // This requires accessing GENERAL_NAME union which is complex in Rust - // For now, we return <unsupported> for unhandled types +// HostnameIgnoringVerifier: verifies certificate chain but ignores hostname +// This is used when check_hostname=False but verify_mode != CERT_NONE +// +// Unlike the previous implementation that used an inner WebPkiServerVerifier, +// this version uses webpki directly to verify only the certificate chain, +// completely bypassing hostname verification. +#[derive(Debug)] +pub struct HostnameIgnoringVerifier { + inner: Arc<dyn ServerCertVerifier>, +} - // For othername and other unsupported types - vm.new_tuple((ascii!("othername"), ascii!("<unsupported>"))) - .into() +impl HostnameIgnoringVerifier { + /// Create a new HostnameIgnoringVerifier with a pre-built verifier + /// This is useful when you need to configure the verifier with CRLs or other options + pub fn new_with_verifier(inner: Arc<dyn ServerCertVerifier>) -> Self { + Self { inner } + } +} + +impl ServerCertVerifier for HostnameIgnoringVerifier { + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, // Intentionally ignored + ocsp_response: &[u8], + now: UnixTime, + ) -> Result<ServerCertVerified, rustls::Error> { + // Extract a hostname from the certificate to pass to inner verifier + // The inner verifier will validate certificate chain, trust anchors, etc. + // but may fail on hostname mismatch - we'll catch and ignore that error + let dummy_hostname = extract_first_dns_name(end_entity) + .unwrap_or_else(|| ServerName::try_from("localhost").expect("localhost is valid")); + + // Call inner verifier for full certificate validation + match self.inner.verify_server_cert( + end_entity, + intermediates, + &dummy_hostname, + ocsp_response, + now, + ) { + Ok(verified) => Ok(verified), + Err(e) => { + // Check if the error is a hostname mismatch + // If so, ignore it (that's the whole point of HostnameIgnoringVerifier) + match e { + rustls::Error::InvalidCertificate( + rustls::CertificateError::NotValidForName, + ) + | rustls::Error::InvalidCertificate( + rustls::CertificateError::NotValidForNameContext { .. }, + ) => { + // Hostname mismatch - this is expected and acceptable + // The certificate chain, trust anchor, and expiry are valid + Ok(ServerCertVerified::assertion()) } - }) - .collect(); - dict.set_item("subjectAltName", vm.ctx.new_tuple(san).into(), vm)?; - }; + _ => { + // Other errors (expired cert, untrusted CA, etc.) should propagate + Err(e) + } + } + } + } + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + self.inner.verify_tls12_signature(message, cert, dss) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + self.inner.verify_tls13_signature(message, cert, dss) + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + self.inner.supported_verify_schemes() + } +} + +// Helper function to extract the first DNS name from a certificate +fn extract_first_dns_name(cert_der: &CertificateDer<'_>) -> Option<ServerName<'static>> { + let (_, cert) = X509Certificate::from_der(cert_der.as_ref()).ok()?; + + // Try Subject Alternative Names first + if let Ok(Some(san_ext)) = cert.subject_alternative_name() { + for name in &san_ext.value.general_names { + if let x509_parser::extensions::GeneralName::DNSName(dns) = name { + // Remove wildcard prefix if present (e.g., "*.example.com" → "example.com") + // This allows us to use the domain for certificate chain verification + // when check_hostname=False + let dns_str = dns.to_string(); + let normalized_dns = normalize_wildcard_hostname(&dns_str); + + match ServerName::try_from(normalized_dns.to_string()) { + Ok(server_name) => { + return Some(server_name); + } + Err(_e) => { + // Continue to next + } + } + } + } + } - Ok(dict.into()) + // Fallback to Common Name + for rdn in cert.subject().iter() { + for attr in rdn.iter() { + if attr.attr_type() == &x509_parser::oid_registry::OID_X509_COMMON_NAME + && let Ok(cn) = attr.attr_value().as_str() + { + // Remove wildcard prefix if present + let normalized_cn = normalize_wildcard_hostname(cn); + + match ServerName::try_from(normalized_cn.to_string()) { + Ok(server_name) => { + return Some(server_name); + } + Err(_e) => {} + } + } + } } - // Helper to create Certificate object from X509 - pub(crate) fn cert_to_certificate(vm: &VirtualMachine, cert: X509) -> PyResult { - Ok(PySSLCertificate { cert }.into_ref(&vm.ctx).into()) + None +} + +// Custom client certificate verifier for TLS 1.3 deferred validation +// This verifier always succeeds during handshake but stores verification errors +// for later retrieval during I/O operations +#[derive(Debug)] +pub struct DeferredClientCertVerifier { + // The actual verifier that performs validation + inner: Arc<dyn ClientCertVerifier>, + // Shared storage for deferred error message + deferred_error: Arc<ParkingRwLock<Option<String>>>, +} + +impl DeferredClientCertVerifier { + pub fn new( + inner: Arc<dyn ClientCertVerifier>, + deferred_error: Arc<ParkingRwLock<Option<String>>>, + ) -> Self { + Self { + inner, + deferred_error, + } } +} - // For getpeercert() - returns bytes or dict depending on binary flag - pub(crate) fn cert_to_py(vm: &VirtualMachine, cert: &X509Ref, binary: bool) -> PyResult { - if binary { - let b = cert.to_der().map_err(|e| convert_openssl_error(vm, e))?; - Ok(vm.ctx.new_bytes(b).into()) +impl ClientCertVerifier for DeferredClientCertVerifier { + fn offer_client_auth(&self) -> bool { + self.inner.offer_client_auth() + } + + fn client_auth_mandatory(&self) -> bool { + // Delegate to inner verifier to respect CERT_REQUIRED mode + // This ensures client certificates are mandatory when verify_mode=CERT_REQUIRED + self.inner.client_auth_mandatory() + } + + fn root_hint_subjects(&self) -> &[rustls::DistinguishedName] { + self.inner.root_hint_subjects() + } + + fn verify_client_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + now: UnixTime, + ) -> Result<ClientCertVerified, rustls::Error> { + // Perform the actual verification + let result = self + .inner + .verify_client_cert(end_entity, intermediates, now); + + // If verification failed, store the error for later + if result.is_err() { + let error_msg = "TLS handshake failed: received fatal alert: UnknownCA".to_string(); + *self.deferred_error.write() = Some(error_msg); + } + + // Always return success to allow handshake to complete + // The error will be raised during the first I/O operation + Ok(ClientCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> { + self.inner.verify_tls12_signature(message, cert, dss) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> { + self.inner.verify_tls13_signature(message, cert, dss) + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + self.inner.supported_verify_schemes() + } +} + +// Public Utility Functions + +/// Load certificate chain and private key from files +/// +/// This function loads a certificate chain from `cert_path` and a private key +/// from `key_path`. If `password` is provided, it will be used to decrypt +/// an encrypted private key. +/// +/// Returns (certificate_chain, private_key) on success. +/// +/// # Arguments +/// * `cert_path` - Path to certificate file (PEM or DER format) +/// * `key_path` - Path to private key file (PEM or DER format, optionally encrypted) +/// * `password` - Optional password for encrypted private key +/// +/// # Errors +/// Returns error if: +/// - Files cannot be read +/// - Certificate or key cannot be parsed +/// - Password is incorrect for encrypted key +pub(super) fn load_cert_chain_from_file( + cert_path: &str, + key_path: &str, + password: Option<&str>, +) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), Box<dyn std::error::Error>> { + // Load certificate file - preserve io::Error for errno + let cert_contents = std::fs::read(cert_path)?; + + // Parse certificates (PEM format) + let mut cert_cursor = std::io::Cursor::new(&cert_contents); + let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut cert_cursor) + .collect::<Result<Vec<_>, _>>() + .map_err(cert_error::pem::parse_failed)?; + + if certs.is_empty() { + return Err(Box::new(cert_error::pem::invalid_cert())); + } + + // Load private key file - preserve io::Error for errno + let key_contents = std::fs::read(key_path)?; + + // Parse private key (supports PKCS8, RSA, EC formats) + let private_key = if let Some(pwd) = password { + // Try to parse as encrypted PKCS#8 + use der::SecretDocument; + use pkcs8::EncryptedPrivateKeyInfo; + use rustls::pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer}; + + let pem_str = String::from_utf8_lossy(&key_contents); + + // Extract just the ENCRYPTED PRIVATE KEY block if present + // (file may contain multiple PEM blocks like key + certificate) + let encrypted_key_pem = if let Some(start) = + pem_str.find("-----BEGIN ENCRYPTED PRIVATE KEY-----") + { + if let Some(end_marker) = pem_str[start..].find("-----END ENCRYPTED PRIVATE KEY-----") { + let end = start + end_marker + "-----END ENCRYPTED PRIVATE KEY-----".len(); + Some(&pem_str[start..end]) + } else { + None + } } else { - cert_to_dict(vm, cert) + None + }; + + // Try to decode and decrypt PEM-encoded encrypted private key using pkcs8's PEM support + let decrypted_key_result = if let Some(key_pem) = encrypted_key_pem { + match SecretDocument::from_pem(key_pem) { + Ok((label, doc)) => { + if label == "ENCRYPTED PRIVATE KEY" { + // Parse encrypted key info from DER + match EncryptedPrivateKeyInfo::try_from(doc.as_bytes()) { + Ok(encrypted_key) => { + // Decrypt with password + match encrypted_key.decrypt(pwd.as_bytes()) { + Ok(decrypted) => { + // Convert decrypted SecretDocument to PrivateKeyDer + let key_vec: Vec<u8> = decrypted.as_bytes().to_vec(); + let pkcs8_key: PrivatePkcs8KeyDer<'static> = key_vec.into(); + Some(PrivateKeyDer::Pkcs8(pkcs8_key)) + } + Err(e) => { + return Err(Box::new(cert_error::key::decrypt_failed(e))); + } + } + } + Err(e) => { + return Err(Box::new(cert_error::key::parse_encrypted_failed(e))); + } + } + } else { + None + } + } + Err(_) => None, + } + } else { + None + }; + + match decrypted_key_result { + Some(key) => key, + None => { + // Not encrypted PKCS#8, try as unencrypted key + // (password might have been provided for an unencrypted key) + let mut key_cursor = std::io::Cursor::new(&key_contents); + match rustls_pemfile::private_key(&mut key_cursor) { + Ok(Some(key)) => key, + Ok(None) => { + return Err(Box::new(cert_error::key::not_found("key file"))); + } + Err(e) => { + return Err(Box::new(cert_error::key::parse_failed(e))); + } + } + } + } + } else { + // No password provided - try to parse unencrypted key + let mut key_cursor = std::io::Cursor::new(&key_contents); + match rustls_pemfile::private_key(&mut key_cursor) { + Ok(Some(key)) => key, + Ok(None) => { + return Err(Box::new(cert_error::key::not_found("key file"))); + } + Err(e) => { + return Err(Box::new(cert_error::key::parse_failed(e))); + } + } + }; + + Ok((certs, private_key)) +} + +/// Validate that a certificate and private key match +/// +/// This function checks that the public key in the certificate matches +/// the provided private key. This is a basic sanity check to prevent +/// configuration errors. +/// +/// # Arguments +/// * `certs` - Certificate chain (first certificate is the leaf) +/// * `private_key` - Private key to validate against +/// +/// # Errors +/// Returns error if: +/// - Certificate chain is empty +/// - Public key extraction fails +/// - Keys don't match +/// +/// Note: This is a simplified validation. Full validation would require +/// signing and verifying a test message, which is complex with rustls. +pub fn validate_cert_key_match( + certs: &[CertificateDer<'_>], + private_key: &PrivateKeyDer<'_>, +) -> Result<(), String> { + if certs.is_empty() { + return Err("Certificate chain is empty".to_string()); + } + + // For rustls, the actual validation happens when creating CertifiedKey + // We can attempt to create a signing key to verify the key is valid + use rustls::crypto::aws_lc_rs::sign::any_supported_type; + + match any_supported_type(private_key) { + Ok(_signing_key) => { + // If we can create a signing key, the private key is valid + // Rustls will validate the cert-key match when building config + Ok(()) + } + Err(_) => Err("PEM lib".to_string()), + } +} + +/// StrictCertVerifier: wraps a ServerCertVerifier and adds RFC 5280 strict validation +/// +/// When VERIFY_X509_STRICT flag is set, performs additional validation: +/// - Checks for Authority Key Identifier (AKI) extension (required by RFC 5280 Section 4.2.1.1) +/// - Validates other RFC 5280 compliance requirements +/// +/// This matches X509_V_FLAG_X509_STRICT behavior in OpenSSL. +#[derive(Debug)] +pub struct StrictCertVerifier { + inner: Arc<dyn ServerCertVerifier>, + verify_flags: i32, +} + +impl StrictCertVerifier { + /// Create a new StrictCertVerifier + /// + /// # Arguments + /// * `inner` - The underlying verifier to wrap + /// * `verify_flags` - SSL verification flags (e.g., VERIFY_X509_STRICT) + pub fn new(inner: Arc<dyn ServerCertVerifier>, verify_flags: i32) -> Self { + Self { + inner, + verify_flags, } } - #[pyfunction] - pub(crate) fn _test_decode_cert(path: FsPath, vm: &VirtualMachine) -> PyResult { - let path = path.to_path_buf(vm)?; - let pem = std::fs::read(path).map_err(|e| e.to_pyexception(vm))?; - let x509 = X509::from_pem(&pem).map_err(|e| convert_openssl_error(vm, e))?; - cert_to_py(vm, &x509, false) + /// Check if a certificate has the Authority Key Identifier extension + /// + /// RFC 5280 Section 4.2.1.1 states that conforming CAs MUST include this + /// extension in all certificates except self-signed certificates. + fn check_aki_present(cert_der: &[u8]) -> Result<(), String> { + let (_, cert) = X509Certificate::from_der(cert_der) + .map_err(|e| format!("Failed to parse certificate: {e}"))?; + + // Check for Authority Key Identifier extension (OID 2.5.29.35) + let has_aki = cert + .tbs_certificate + .extensions() + .iter() + .any(|ext| ext.oid == oid_registry::OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER); + + if !has_aki { + return Err( + "certificate verification failed: certificate missing required Authority Key Identifier extension" + .to_string(), + ); + } + + Ok(()) } } + +impl ServerCertVerifier for StrictCertVerifier { + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + server_name: &ServerName<'_>, + ocsp_response: &[u8], + now: UnixTime, + ) -> Result<ServerCertVerified, rustls::Error> { + // First, perform the standard verification + let result = self.inner.verify_server_cert( + end_entity, + intermediates, + server_name, + ocsp_response, + now, + )?; + + // If VERIFY_X509_STRICT flag is set, perform additional validation + if self.verify_flags & VERIFY_X509_STRICT != 0 { + // Check end entity certificate for AKI + // RFC 5280 Section 4.2.1.1: self-signed certificates are exempt from AKI requirement + if !is_self_signed(end_entity) { + Self::check_aki_present(end_entity.as_ref()) + .map_err(cert_error::to_rustls_invalid_cert)?; + } + + // Check intermediate certificates for AKI + for intermediate in intermediates { + Self::check_aki_present(intermediate.as_ref()) + .map_err(cert_error::to_rustls_invalid_cert)?; + } + } + + Ok(result) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + self.inner.verify_tls12_signature(message, cert, dss) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + self.inner.verify_tls13_signature(message, cert, dss) + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + self.inner.supported_verify_schemes() + } +} + +/// EmptyRootStoreVerifier: used when verify_mode != CERT_NONE but no CA certs are loaded +/// +/// This verifier always fails certificate verification with UnknownIssuer error, +/// when no root certificates are available. +/// This allows the SSL context to be created successfully, but handshake will fail +/// with a proper SSLCertVerificationError (verify_code=20, UNABLE_TO_GET_ISSUER_CERT_LOCALLY). +#[derive(Debug)] +pub struct EmptyRootStoreVerifier; + +impl ServerCertVerifier for EmptyRootStoreVerifier { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result<ServerCertVerified, rustls::Error> { + // Always fail with UnknownIssuer - when no CA certs loaded + // This will be mapped to X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (20) + Err(rustls::Error::InvalidCertificate( + rustls::CertificateError::UnknownIssuer, + )) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + // Accept signatures during handshake - the cert verification will fail anyway + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + // Accept signatures during handshake - the cert verification will fail anyway + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + ALL_SIGNATURE_SCHEMES.to_vec() + } +} + +/// CRLCheckVerifier: Wraps a verifier to enforce CRL checking when flags are set +/// +/// This verifier ensures that when CRL checking flags are set (VERIFY_CRL_CHECK_LEAF = 4) +/// but no CRLs have been loaded, the verification fails with UnknownRevocationStatus. +/// This matches X509_V_FLAG_CRL_CHECK without loaded CRLs +/// causes "unable to get CRL" error. +#[derive(Debug)] +pub struct CRLCheckVerifier { + inner: Arc<dyn ServerCertVerifier>, + has_crls: bool, + crl_check_enabled: bool, +} + +impl CRLCheckVerifier { + pub fn new( + inner: Arc<dyn ServerCertVerifier>, + has_crls: bool, + crl_check_enabled: bool, + ) -> Self { + Self { + inner, + has_crls, + crl_check_enabled, + } + } +} + +impl ServerCertVerifier for CRLCheckVerifier { + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + server_name: &ServerName<'_>, + ocsp_response: &[u8], + now: UnixTime, + ) -> Result<ServerCertVerified, rustls::Error> { + // If CRL checking is enabled but no CRLs are loaded, fail with UnknownRevocationStatus + // X509_V_ERR_UNABLE_TO_GET_CRL (3) + if self.crl_check_enabled && !self.has_crls { + return Err(rustls::Error::InvalidCertificate( + rustls::CertificateError::UnknownRevocationStatus, + )); + } + + // Otherwise, delegate to inner verifier + self.inner + .verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + self.inner.verify_tls12_signature(message, cert, dss) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + self.inner.verify_tls13_signature(message, cert, dss) + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + self.inner.supported_verify_schemes() + } +} + +/// Partial Chain Verifier - Handles VERIFY_X509_PARTIAL_CHAIN flag +/// +/// OpenSSL's X509_V_FLAG_PARTIAL_CHAIN allows verification to succeed if any certificate +/// in the presented chain is found in the trust store, not just the root CA. This is useful +/// for trusting intermediate certificates or self-signed certificates directly. +/// +/// rustls's WebPkiServerVerifier doesn't support this behavior by default, so we wrap it +/// to add partial chain support when the flag is set. +/// +/// Behavior: +/// 1. Try standard verification first (full chain to trusted root) +/// 2. If that fails and VERIFY_X509_PARTIAL_CHAIN is set: +/// - Check if the end-entity certificate is in the trust store +/// - If yes, accept the certificate as trusted +/// +/// This matches accepting self-signed certificates that +/// are explicitly loaded via load_verify_locations(). +#[derive(Debug)] +pub struct PartialChainVerifier { + inner: Arc<dyn ServerCertVerifier>, + ca_certs_der: Vec<Vec<u8>>, + verify_flags: i32, +} + +impl PartialChainVerifier { + pub fn new( + inner: Arc<dyn ServerCertVerifier>, + ca_certs_der: Vec<Vec<u8>>, + verify_flags: i32, + ) -> Self { + Self { + inner, + ca_certs_der, + verify_flags, + } + } +} + +impl ServerCertVerifier for PartialChainVerifier { + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + server_name: &ServerName<'_>, + ocsp_response: &[u8], + now: UnixTime, + ) -> Result<ServerCertVerified, rustls::Error> { + // Try standard verification first + match self.inner.verify_server_cert( + end_entity, + intermediates, + server_name, + ocsp_response, + now, + ) { + Ok(result) => Ok(result), + Err(e) => { + // If verification failed, check if the end-entity certificate is in the trust store + // OpenSSL behavior: + // 1. Self-signed certs in trust store: ALWAYS trusted (flag not required) + // 2. Non-self-signed end-entity certs in trust store: require VERIFY_X509_PARTIAL_CHAIN + // 3. Intermediate certs in trust store: require VERIFY_X509_PARTIAL_CHAIN + let end_entity_der = end_entity.as_ref(); + if self + .ca_certs_der + .iter() + .any(|cert_der| cert_der.as_slice() == end_entity_der) + { + // End-entity certificate is in the trust store + // Check if this is a self-signed certificate + let is_self_signed_cert = is_self_signed(end_entity); + + // Self-signed: always trust (OpenSSL behavior) + // Non-self-signed: require VERIFY_X509_PARTIAL_CHAIN flag + if is_self_signed_cert || (self.verify_flags & VERIFY_X509_PARTIAL_CHAIN != 0) { + // Certificate is trusted, but still perform hostname verification + verify_hostname(end_entity, server_name)?; + return Ok(ServerCertVerified::assertion()); + } + } + // No match found or non-self-signed without flag - return original error + Err(e) + } + } + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + self.inner.verify_tls12_signature(message, cert, dss) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, rustls::Error> { + self.inner.verify_tls13_signature(message, cert, dss) + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + self.inner.supported_verify_schemes() + } +} + +// Hostname Verification: + +/// Check if a certificate is self-signed by comparing issuer and subject. +/// Returns true if the certificate is self-signed (issuer == subject). +fn is_self_signed(cert_der: &CertificateDer<'_>) -> bool { + use x509_parser::prelude::*; + + // Parse the certificate + let Ok((_, cert)) = X509Certificate::from_der(cert_der.as_ref()) else { + // If we can't parse it, assume it's not self-signed (conservative approach) + return false; + }; + + // Compare issuer and subject + // A certificate is self-signed if issuer == subject + cert.issuer() == cert.subject() +} + +/// Verify that a certificate is valid for the given hostname/IP address. +/// This function checks Subject Alternative Names (SAN) and Common Name (CN). +fn verify_hostname( + cert_der: &CertificateDer<'_>, + server_name: &ServerName<'_>, +) -> Result<(), rustls::Error> { + use x509_parser::extensions::GeneralName; + use x509_parser::prelude::*; + + // Parse the certificate + let (_, cert) = X509Certificate::from_der(cert_der.as_ref()).map_err(|e| { + cert_error::to_rustls_invalid_cert(format!( + "Failed to parse certificate for hostname verification: {e}" + )) + })?; + + match server_name { + ServerName::DnsName(dns) => { + let expected_name = dns.as_ref(); + + // 1. Check Subject Alternative Names (SAN) - preferred method + if let Ok(Some(san_ext)) = cert.subject_alternative_name() { + for name in &san_ext.value.general_names { + if let GeneralName::DNSName(dns_name) = name + && hostname_matches(expected_name, dns_name) + { + return Ok(()); + } + } + } + + // 2. Fallback to Common Name (CN) - deprecated but still checked for compatibility + for rdn in cert.subject().iter() { + for attr in rdn.iter() { + if attr.attr_type() == &x509_parser::oid_registry::OID_X509_COMMON_NAME + && let Ok(cn) = attr.attr_value().as_str() + && hostname_matches(expected_name, cn) + { + return Ok(()); + } + } + } + + // No match found - return error + Err(cert_error::to_rustls_invalid_cert(format!( + "Hostname mismatch: certificate is not valid for '{expected_name}'", + ))) + } + ServerName::IpAddress(ip) => verify_ip_address(&cert, ip), + _ => { + // Unknown server name type + Err(cert_error::to_rustls_cert_error( + std::io::ErrorKind::InvalidInput, + "Unsupported server name type for hostname verification", + )) + } + } +} + +/// Match a hostname against a pattern, supporting wildcard certificates (*.example.com). +/// Implements RFC 6125 wildcard matching rules: +/// - Wildcard must be in the leftmost label +/// - Wildcard must be the only character in that label +/// - Wildcard must match at least one character +fn hostname_matches(expected: &str, pattern: &str) -> bool { + // Wildcard matching for *.example.com + if let Some(pattern_base) = pattern.strip_prefix("*.") { + // Find the first dot in expected hostname + if let Some(dot_pos) = expected.find('.') { + let expected_base = &expected[dot_pos + 1..]; + + // The base domains must match (case insensitive) + // and the leftmost label must not be empty + return dot_pos > 0 && expected_base.eq_ignore_ascii_case(pattern_base); + } + + // No dot in expected, can't match wildcard + return false; + } + + // Exact match (case insensitive per RFC 4343) + expected.eq_ignore_ascii_case(pattern) +} + +/// Verify that a certificate is valid for the given IP address. +/// Checks Subject Alternative Names for IP Address entries. +fn verify_ip_address( + cert: &X509Certificate<'_>, + expected_ip: &rustls::pki_types::IpAddr, +) -> Result<(), rustls::Error> { + use std::net::IpAddr; + use x509_parser::extensions::GeneralName; + + // Convert rustls IpAddr to std::net::IpAddr for comparison + let expected_std_ip: IpAddr = match expected_ip { + rustls::pki_types::IpAddr::V4(octets) => IpAddr::V4(std::net::Ipv4Addr::from(*octets)), + rustls::pki_types::IpAddr::V6(octets) => IpAddr::V6(std::net::Ipv6Addr::from(*octets)), + }; + + // Check Subject Alternative Names for IP addresses + if let Ok(Some(san_ext)) = cert.subject_alternative_name() { + for name in &san_ext.value.general_names { + if let GeneralName::IPAddress(cert_ip_bytes) = name { + // Parse the IP address from the certificate + let cert_ip = match cert_ip_bytes.len() { + 4 => { + // IPv4 + if let Ok(octets) = <[u8; 4]>::try_from(*cert_ip_bytes) { + IpAddr::V4(std::net::Ipv4Addr::from(octets)) + } else { + continue; + } + } + 16 => { + // IPv6 + if let Ok(octets) = <[u8; 16]>::try_from(*cert_ip_bytes) { + IpAddr::V6(std::net::Ipv6Addr::from(octets)) + } else { + continue; + } + } + _ => continue, // Invalid IP address length + }; + + if cert_ip == expected_std_ip { + return Ok(()); + } + } + } + } + + // No matching IP address found + Err(cert_error::to_rustls_invalid_cert(format!( + "IP address mismatch: certificate is not valid for '{expected_std_ip}'", + ))) +} diff --git a/stdlib/src/ssl/compat.rs b/stdlib/src/ssl/compat.rs new file mode 100644 index 00000000000..e4f979968e4 --- /dev/null +++ b/stdlib/src/ssl/compat.rs @@ -0,0 +1,1786 @@ +// spell-checker: ignore webpki ssleof sslerror akid certsign sslerr aesgcm + +// OpenSSL compatibility layer for rustls +// +// This module provides OpenSSL-like abstractions over rustls APIs, +// making the code more readable and maintainable. Each function is named +// after its OpenSSL equivalent (e.g., ssl_do_handshake corresponds to SSL_do_handshake). + +// SSL error code data tables (shared with OpenSSL backend for compatibility) +// These map OpenSSL error codes to human-readable strings +#[path = "../openssl/ssl_data_31.rs"] +mod ssl_data; + +use crate::socket::{SelectKind, timeout_error_msg}; +use crate::vm::VirtualMachine; +use parking_lot::RwLock as ParkingRwLock; +use rustls::RootCertStore; +use rustls::client::ClientConfig; +use rustls::client::ClientConnection; +use rustls::crypto::SupportedKxGroup; +use rustls::pki_types::{CertificateDer, CertificateRevocationListDer, PrivateKeyDer}; +use rustls::server::ResolvesServerCert; +use rustls::server::ServerConfig; +use rustls::server::ServerConnection; +use rustls::sign::CertifiedKey; +use rustpython_vm::builtins::PyBaseExceptionRef; +use rustpython_vm::function::ArgBytesLike; +use rustpython_vm::{AsObject, PyObjectRef, PyPayload, PyResult, TryFromObject}; +use std::io::Read; +use std::sync::{Arc, Once}; + +// Import PySSLSocket and helper functions from parent module +use super::_ssl::{ + PySSLCertVerificationError, PySSLError, PySSLSocket, create_ssl_eof_error, + create_ssl_want_read_error, create_ssl_want_write_error, create_ssl_zero_return_error, +}; + +// SSL Verification Flags +/// VERIFY_X509_STRICT flag for RFC 5280 strict compliance +/// When set, performs additional validation including AKI extension checks +pub const VERIFY_X509_STRICT: i32 = 0x20; + +/// VERIFY_X509_PARTIAL_CHAIN flag for partial chain validation +/// When set, accept certificates if any certificate in the chain is in the trust store +/// (not just root CAs). This matches OpenSSL's X509_V_FLAG_PARTIAL_CHAIN behavior. +pub const VERIFY_X509_PARTIAL_CHAIN: i32 = 0x80000; + +// CryptoProvider Initialization: + +/// Ensure the default CryptoProvider is installed (thread-safe, runs once) +/// +/// This is necessary because rustls 0.23+ requires a process-level CryptoProvider +/// to be installed before using default_provider(). We use Once to ensure this +/// happens exactly once, even if called from multiple threads. +static INIT_PROVIDER: Once = Once::new(); + +fn ensure_default_provider() { + INIT_PROVIDER.call_once(|| { + let _ = rustls::crypto::CryptoProvider::install_default( + rustls::crypto::aws_lc_rs::default_provider(), + ); + }); +} + +// OpenSSL Constants: + +// OpenSSL TLS record maximum plaintext size (ssl/ssl_local.h) +// #define SSL3_RT_MAX_PLAIN_LENGTH 16384 +const SSL3_RT_MAX_PLAIN_LENGTH: usize = 16384; + +// OpenSSL error library codes (include/openssl/err.h) +// #define ERR_LIB_SSL 20 +const ERR_LIB_SSL: i32 = 20; + +// OpenSSL SSL error reason codes (include/openssl/sslerr.h) +// #define SSL_R_NO_SHARED_CIPHER 193 +const SSL_R_NO_SHARED_CIPHER: i32 = 193; + +// OpenSSL X509 verification flags (include/openssl/x509_vfy.h) +// #define X509_V_FLAG_CRL_CHECK 4 +const X509_V_FLAG_CRL_CHECK: i32 = 4; + +// X509 Certificate Verification Error Codes (OpenSSL Compatible): +// +// These constants match OpenSSL's X509_V_ERR_* values for certificate +// verification. They are used to map rustls certificate errors to OpenSSL +// error codes for compatibility. + +pub use x509::{ + X509_V_ERR_CERT_HAS_EXPIRED, X509_V_ERR_CERT_NOT_YET_VALID, X509_V_ERR_CERT_REVOKED, + X509_V_ERR_HOSTNAME_MISMATCH, X509_V_ERR_INVALID_PURPOSE, X509_V_ERR_IP_ADDRESS_MISMATCH, + X509_V_ERR_UNABLE_TO_GET_CRL, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, + X509_V_ERR_UNSPECIFIED, +}; + +#[allow(dead_code)] +mod x509 { + pub const X509_V_OK: i32 = 0; + pub const X509_V_ERR_UNSPECIFIED: i32 = 1; + pub const X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: i32 = 2; + pub const X509_V_ERR_UNABLE_TO_GET_CRL: i32 = 3; + pub const X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: i32 = 4; + pub const X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: i32 = 5; + pub const X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: i32 = 6; + pub const X509_V_ERR_CERT_SIGNATURE_FAILURE: i32 = 7; + pub const X509_V_ERR_CRL_SIGNATURE_FAILURE: i32 = 8; + pub const X509_V_ERR_CERT_NOT_YET_VALID: i32 = 9; + pub const X509_V_ERR_CERT_HAS_EXPIRED: i32 = 10; + pub const X509_V_ERR_CRL_NOT_YET_VALID: i32 = 11; + pub const X509_V_ERR_CRL_HAS_EXPIRED: i32 = 12; + pub const X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: i32 = 13; + pub const X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: i32 = 14; + pub const X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: i32 = 15; + pub const X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: i32 = 16; + pub const X509_V_ERR_OUT_OF_MEM: i32 = 17; + pub const X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: i32 = 18; + pub const X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: i32 = 19; + pub const X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: i32 = 20; + pub const X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: i32 = 21; + pub const X509_V_ERR_CERT_CHAIN_TOO_LONG: i32 = 22; + pub const X509_V_ERR_CERT_REVOKED: i32 = 23; + pub const X509_V_ERR_INVALID_CA: i32 = 24; + pub const X509_V_ERR_PATH_LENGTH_EXCEEDED: i32 = 25; + pub const X509_V_ERR_INVALID_PURPOSE: i32 = 26; + pub const X509_V_ERR_CERT_UNTRUSTED: i32 = 27; + pub const X509_V_ERR_CERT_REJECTED: i32 = 28; + pub const X509_V_ERR_SUBJECT_ISSUER_MISMATCH: i32 = 29; + pub const X509_V_ERR_AKID_SKID_MISMATCH: i32 = 30; + pub const X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: i32 = 31; + pub const X509_V_ERR_KEYUSAGE_NO_CERTSIGN: i32 = 32; + pub const X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER: i32 = 33; + pub const X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: i32 = 34; + pub const X509_V_ERR_KEYUSAGE_NO_CRL_SIGN: i32 = 35; + pub const X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION: i32 = 36; + pub const X509_V_ERR_INVALID_NON_CA: i32 = 37; + pub const X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED: i32 = 38; + pub const X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE: i32 = 39; + pub const X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED: i32 = 40; + pub const X509_V_ERR_INVALID_EXTENSION: i32 = 41; + pub const X509_V_ERR_INVALID_POLICY_EXTENSION: i32 = 42; + pub const X509_V_ERR_NO_EXPLICIT_POLICY: i32 = 43; + pub const X509_V_ERR_DIFFERENT_CRL_SCOPE: i32 = 44; + pub const X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE: i32 = 45; + pub const X509_V_ERR_UNNESTED_RESOURCE: i32 = 46; + pub const X509_V_ERR_PERMITTED_VIOLATION: i32 = 47; + pub const X509_V_ERR_EXCLUDED_VIOLATION: i32 = 48; + pub const X509_V_ERR_SUBTREE_MINMAX: i32 = 49; + pub const X509_V_ERR_APPLICATION_VERIFICATION: i32 = 50; + pub const X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE: i32 = 51; + pub const X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX: i32 = 52; + pub const X509_V_ERR_UNSUPPORTED_NAME_SYNTAX: i32 = 53; + pub const X509_V_ERR_CRL_PATH_VALIDATION_ERROR: i32 = 54; + pub const X509_V_ERR_HOSTNAME_MISMATCH: i32 = 62; + pub const X509_V_ERR_EMAIL_MISMATCH: i32 = 63; + pub const X509_V_ERR_IP_ADDRESS_MISMATCH: i32 = 64; +} + +// Certificate Error Conversion Functions: + +/// Convert rustls CertificateError to X509 verification code and message +/// +/// Maps rustls certificate errors to OpenSSL X509_V_ERR_* codes for compatibility. +/// Returns (verify_code, verify_message) tuple. +fn rustls_cert_error_to_verify_info(cert_err: &rustls::CertificateError) -> (i32, &'static str) { + use rustls::CertificateError; + + match cert_err { + CertificateError::UnknownIssuer => ( + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, + "unable to get local issuer certificate", + ), + CertificateError::Expired => (X509_V_ERR_CERT_HAS_EXPIRED, "certificate has expired"), + CertificateError::NotValidYet => ( + X509_V_ERR_CERT_NOT_YET_VALID, + "certificate is not yet valid", + ), + CertificateError::Revoked => (X509_V_ERR_CERT_REVOKED, "certificate revoked"), + CertificateError::UnknownRevocationStatus => ( + X509_V_ERR_UNABLE_TO_GET_CRL, + "unable to get certificate CRL", + ), + CertificateError::InvalidPurpose => ( + X509_V_ERR_INVALID_PURPOSE, + "unsupported certificate purpose", + ), + CertificateError::Other(other_err) => { + // Check if this is a hostname mismatch error from our verify_hostname function + let err_msg = format!("{other_err:?}"); + if err_msg.contains("Hostname mismatch") || err_msg.contains("not valid for") { + ( + X509_V_ERR_HOSTNAME_MISMATCH, + "Hostname mismatch, certificate is not valid for", + ) + } else if err_msg.contains("IP address mismatch") { + ( + X509_V_ERR_IP_ADDRESS_MISMATCH, + "IP address mismatch, certificate is not valid for", + ) + } else { + (X509_V_ERR_UNSPECIFIED, "certificate verification failed") + } + } + _ => (X509_V_ERR_UNSPECIFIED, "certificate verification failed"), + } +} + +/// Create SSLCertVerificationError with proper attributes +/// +/// Matches CPython's _ssl.c fill_and_set_sslerror() behavior. +/// This function creates a Python SSLCertVerificationError exception with verify_code +/// and verify_message attributes set appropriately for the given rustls certificate error. +/// +/// # Note +/// If attribute setting fails (extremely rare), returns the exception without attributes +pub(super) fn create_ssl_cert_verification_error( + vm: &VirtualMachine, + cert_err: &rustls::CertificateError, +) -> PyResult<PyBaseExceptionRef> { + let (verify_code, verify_message) = rustls_cert_error_to_verify_info(cert_err); + + let msg = + format!("[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: {verify_message}",); + + let exc = vm.new_exception_msg(PySSLCertVerificationError::class(&vm.ctx).to_owned(), msg); + + // Set verify_code and verify_message attributes + // Ignore errors as they're extremely rare (e.g., out of memory) + exc.as_object().set_attr( + "verify_code", + vm.ctx.new_int(verify_code).as_object().to_owned(), + vm, + )?; + exc.as_object().set_attr( + "verify_message", + vm.ctx.new_str(verify_message).as_object().to_owned(), + vm, + )?; + + exc.as_object() + .set_attr("library", vm.ctx.new_str("SSL").as_object().to_owned(), vm)?; + exc.as_object().set_attr( + "reason", + vm.ctx + .new_str("CERTIFICATE_VERIFY_FAILED") + .as_object() + .to_owned(), + vm, + )?; + + Ok(exc) +} + +/// Unified TLS connection type (client or server) +#[derive(Debug)] +pub(super) enum TlsConnection { + Client(ClientConnection), + Server(ServerConnection), +} + +impl TlsConnection { + /// Check if handshake is in progress + pub fn is_handshaking(&self) -> bool { + match self { + TlsConnection::Client(conn) => conn.is_handshaking(), + TlsConnection::Server(conn) => conn.is_handshaking(), + } + } + + /// Check if connection wants to read data + pub fn wants_read(&self) -> bool { + match self { + TlsConnection::Client(conn) => conn.wants_read(), + TlsConnection::Server(conn) => conn.wants_read(), + } + } + + /// Check if connection wants to write data + pub fn wants_write(&self) -> bool { + match self { + TlsConnection::Client(conn) => conn.wants_write(), + TlsConnection::Server(conn) => conn.wants_write(), + } + } + + /// Read TLS data from socket + pub fn read_tls(&mut self, reader: &mut dyn std::io::Read) -> std::io::Result<usize> { + match self { + TlsConnection::Client(conn) => conn.read_tls(reader), + TlsConnection::Server(conn) => conn.read_tls(reader), + } + } + + /// Write TLS data to socket + pub fn write_tls(&mut self, writer: &mut dyn std::io::Write) -> std::io::Result<usize> { + match self { + TlsConnection::Client(conn) => conn.write_tls(writer), + TlsConnection::Server(conn) => conn.write_tls(writer), + } + } + + /// Process new TLS packets + pub fn process_new_packets(&mut self) -> Result<rustls::IoState, rustls::Error> { + match self { + TlsConnection::Client(conn) => conn.process_new_packets(), + TlsConnection::Server(conn) => conn.process_new_packets(), + } + } + + /// Get reader for plaintext data (rustls native type) + pub fn reader(&mut self) -> rustls::Reader<'_> { + match self { + TlsConnection::Client(conn) => conn.reader(), + TlsConnection::Server(conn) => conn.reader(), + } + } + + /// Get writer for plaintext data (rustls native type) + pub fn writer(&mut self) -> rustls::Writer<'_> { + match self { + TlsConnection::Client(conn) => conn.writer(), + TlsConnection::Server(conn) => conn.writer(), + } + } + + /// Check if session was resumed + pub fn is_session_resumed(&self) -> bool { + use rustls::HandshakeKind; + match self { + TlsConnection::Client(conn) => { + matches!(conn.handshake_kind(), Some(HandshakeKind::Resumed)) + } + TlsConnection::Server(conn) => { + matches!(conn.handshake_kind(), Some(HandshakeKind::Resumed)) + } + } + } + + /// Send close_notify alert + pub fn send_close_notify(&mut self) { + match self { + TlsConnection::Client(conn) => conn.send_close_notify(), + TlsConnection::Server(conn) => conn.send_close_notify(), + } + } + + /// Get negotiated ALPN protocol + pub fn alpn_protocol(&self) -> Option<&[u8]> { + match self { + TlsConnection::Client(conn) => conn.alpn_protocol(), + TlsConnection::Server(conn) => conn.alpn_protocol(), + } + } + + /// Get negotiated cipher suite + pub fn negotiated_cipher_suite(&self) -> Option<rustls::SupportedCipherSuite> { + match self { + TlsConnection::Client(conn) => conn.negotiated_cipher_suite(), + TlsConnection::Server(conn) => conn.negotiated_cipher_suite(), + } + } + + /// Get peer certificates + pub fn peer_certificates(&self) -> Option<&[rustls::pki_types::CertificateDer<'static>]> { + match self { + TlsConnection::Client(conn) => conn.peer_certificates(), + TlsConnection::Server(conn) => conn.peer_certificates(), + } + } +} + +/// Error types matching OpenSSL error codes +#[derive(Debug)] +pub(super) enum SslError { + /// SSL_ERROR_WANT_READ + WantRead, + /// SSL_ERROR_WANT_WRITE + WantWrite, + /// SSL_ERROR_SYSCALL + Syscall(String), + /// SSL_ERROR_SSL + Ssl(String), + /// SSL_ERROR_ZERO_RETURN (clean closure with close_notify) + ZeroReturn, + /// Unexpected EOF without close_notify (protocol violation) + Eof, + /// Certificate verification error + CertVerification(rustls::CertificateError), + /// I/O error + Io(std::io::Error), + /// Timeout error (socket.timeout) + Timeout(String), + /// SNI callback triggered - need to restart handshake + SniCallbackRestart, + /// Python exception (pass through directly) + Py(PyBaseExceptionRef), + /// TLS alert received with OpenSSL-compatible error code + AlertReceived { lib: i32, reason: i32 }, + /// NO_SHARED_CIPHER error (OpenSSL SSL_R_NO_SHARED_CIPHER) + NoCipherSuites, +} + +impl SslError { + /// Convert TLS alert code to OpenSSL error reason code + /// OpenSSL uses reason = 1000 + alert_code for TLS alerts + fn alert_to_openssl_reason(alert: rustls::AlertDescription) -> i32 { + // AlertDescription can be converted to u8 via as u8 cast + 1000 + (u8::from(alert) as i32) + } + + /// Convert rustls error to SslError + pub fn from_rustls(err: rustls::Error) -> Self { + match err { + rustls::Error::InvalidCertificate(cert_err) => SslError::CertVerification(cert_err), + rustls::Error::AlertReceived(alert_desc) => { + // Map TLS alerts to OpenSSL-compatible error codes + // lib = 20 (ERR_LIB_SSL), reason = 1000 + alert_code + match alert_desc { + rustls::AlertDescription::CloseNotify => { + // Special case: close_notify is handled as ZeroReturn + SslError::ZeroReturn + } + _ => { + // All other alerts: convert to OpenSSL error code + // This includes InternalError (80 -> reason 1080) + SslError::AlertReceived { + lib: ERR_LIB_SSL, + reason: Self::alert_to_openssl_reason(alert_desc), + } + } + } + } + // OpenSSL 3.0 changed transport EOF from SSL_ERROR_SYSCALL with + // zero return value to SSL_ERROR_SSL with SSL_R_UNEXPECTED_EOF_WHILE_READING. + // In rustls, these cases correspond to unexpected connection closure: + rustls::Error::InvalidMessage(_) => { + // UnexpectedMessage, CorruptMessage, etc. → SSLEOFError + // Matches CPython's "EOF occurred in violation of protocol" + SslError::Eof + } + rustls::Error::PeerIncompatible(peer_err) => { + // Check for specific incompatibility types + use rustls::PeerIncompatible; + match peer_err { + PeerIncompatible::NoCipherSuitesInCommon => { + // Maps to OpenSSL SSL_R_NO_SHARED_CIPHER (lib=20, reason=193) + SslError::NoCipherSuites + } + _ => { + // Other protocol incompatibilities → SSLEOFError + SslError::Eof + } + } + } + _ => SslError::Ssl(format!("{err}")), + } + } + + /// Create SSLError with library and reason from string values + /// + /// This is the base helper for creating SSLError with _library and _reason + /// attributes when you already have the string values. + /// + /// # Arguments + /// * `vm` - Virtual machine reference + /// * `library` - Library name (e.g., "PEM", "SSL") + /// * `reason` - Error reason (e.g., "PEM lib", "NO_SHARED_CIPHER") + /// * `message` - Main error message + /// + /// # Returns + /// PyBaseExceptionRef with _library and _reason attributes set + /// + /// # Note + /// If attribute setting fails (extremely rare), returns the exception without attributes + pub(super) fn create_ssl_error_with_reason( + vm: &VirtualMachine, + library: &str, + reason: &str, + message: impl Into<String>, + ) -> PyBaseExceptionRef { + let exc = vm.new_exception_msg(PySSLError::class(&vm.ctx).to_owned(), message.into()); + + // Set library and reason attributes + // Ignore errors as they're extremely rare (e.g., out of memory) + let _ = exc.as_object().set_attr( + "library", + vm.ctx.new_str(library).as_object().to_owned(), + vm, + ); + let _ = + exc.as_object() + .set_attr("reason", vm.ctx.new_str(reason).as_object().to_owned(), vm); + + exc + } + + /// Create SSLError with library and reason from ssl_data codes + /// + /// This helper converts OpenSSL numeric error codes to Python SSLError exceptions + /// with proper _library and _reason attributes by looking up the error strings + /// in ssl_data tables, then delegates to create_ssl_error_with_reason. + /// + /// # Arguments + /// * `vm` - Virtual machine reference + /// * `lib` - OpenSSL library code (e.g., ERR_LIB_SSL = 20) + /// * `reason` - OpenSSL reason code (e.g., SSL_R_NO_SHARED_CIPHER = 193) + /// + /// # Returns + /// PyBaseExceptionRef with _library and _reason attributes set + fn create_ssl_error_from_codes( + vm: &VirtualMachine, + lib: i32, + reason: i32, + ) -> PyBaseExceptionRef { + // Look up error strings from ssl_data tables + let key = ssl_data::encode_error_key(lib, reason); + let reason_str = ssl_data::ERROR_CODES + .get(&key) + .copied() + .unwrap_or("unknown error"); + + let lib_str = ssl_data::LIBRARY_CODES + .get(&(lib as u32)) + .copied() + .unwrap_or("UNKNOWN"); + + // Delegate to create_ssl_error_with_reason for actual exception creation + Self::create_ssl_error_with_reason(vm, lib_str, reason_str, format!("[SSL] {reason_str}")) + } + + /// Convert to Python exception + pub fn into_py_err(self, vm: &VirtualMachine) -> PyBaseExceptionRef { + match self { + SslError::WantRead => create_ssl_want_read_error(vm), + SslError::WantWrite => create_ssl_want_write_error(vm), + SslError::Timeout(msg) => timeout_error_msg(vm, msg), + SslError::Syscall(msg) => vm.new_os_error(msg), + SslError::Ssl(msg) => vm.new_exception_msg( + PySSLError::class(&vm.ctx).to_owned(), + format!("SSL error: {msg}"), + ), + SslError::ZeroReturn => create_ssl_zero_return_error(vm), + SslError::Eof => create_ssl_eof_error(vm), + SslError::CertVerification(cert_err) => { + // Use the proper cert verification error creator + create_ssl_cert_verification_error(vm, &cert_err).expect("unlikely to happen") + } + SslError::Io(err) => vm.new_os_error(format!("I/O error: {err}")), + SslError::SniCallbackRestart => { + // This should be handled at PySSLSocket level + unreachable!("SniCallbackRestart should not reach Python layer") + } + SslError::Py(exc) => exc, + SslError::AlertReceived { lib, reason } => { + Self::create_ssl_error_from_codes(vm, lib, reason) + } + SslError::NoCipherSuites => { + // OpenSSL error: lib=20 (ERR_LIB_SSL), reason=193 (SSL_R_NO_SHARED_CIPHER) + Self::create_ssl_error_from_codes(vm, ERR_LIB_SSL, SSL_R_NO_SHARED_CIPHER) + } + } + } +} + +pub type SslResult<T> = Result<T, SslError>; +/// Common protocol settings shared between client and server connections +#[derive(Debug)] +pub struct ProtocolSettings { + pub versions: &'static [&'static rustls::SupportedProtocolVersion], + pub kx_groups: Option<Vec<&'static dyn rustls::crypto::SupportedKxGroup>>, + pub cipher_suites: Option<Vec<rustls::SupportedCipherSuite>>, + pub alpn_protocols: Vec<Vec<u8>>, +} + +/// Options for creating a server TLS configuration +#[derive(Debug)] +pub struct ServerConfigOptions { + /// Common protocol settings (versions, ALPN, KX groups, cipher suites) + pub protocol_settings: ProtocolSettings, + /// Server certificate chain + pub cert_chain: Vec<CertificateDer<'static>>, + /// Server private key + pub private_key: PrivateKeyDer<'static>, + /// Root certificates for client verification (if required) + pub root_store: Option<RootCertStore>, + /// Whether to request client certificate + pub request_client_cert: bool, + /// Whether to use deferred client certificate validation (TLS 1.3) + pub use_deferred_validation: bool, + /// Custom certificate resolver (for SNI support) + pub cert_resolver: Option<Arc<dyn ResolvesServerCert>>, + /// Deferred certificate error storage (for TLS 1.3) + pub deferred_cert_error: Option<Arc<ParkingRwLock<Option<String>>>>, + /// Session storage for server-side session resumption + pub session_storage: Option<Arc<rustls::server::ServerSessionMemoryCache>>, + /// Shared ticketer for TLS 1.2 session tickets (stateless resumption) + pub ticketer: Option<Arc<dyn rustls::server::ProducesTickets>>, +} + +/// Options for creating a client TLS configuration +#[derive(Debug)] +pub struct ClientConfigOptions { + /// Common protocol settings (versions, ALPN, KX groups, cipher suites) + pub protocol_settings: ProtocolSettings, + /// Root certificates for server verification + pub root_store: Option<RootCertStore>, + /// DER-encoded CA certificates (for partial chain verification) + pub ca_certs_der: Vec<Vec<u8>>, + /// Client certificate chain (for mTLS) + pub cert_chain: Option<Vec<CertificateDer<'static>>>, + /// Client private key (for mTLS) + pub private_key: Option<PrivateKeyDer<'static>>, + /// Whether to verify server certificates (CERT_NONE disables verification) + pub verify_server_cert: bool, + /// Whether to check hostname against certificate (check_hostname) + pub check_hostname: bool, + /// SSL verification flags (e.g., VERIFY_X509_STRICT) + pub verify_flags: i32, + /// Session store for client-side session resumption + pub session_store: Option<Arc<dyn rustls::client::ClientSessionStore>>, + /// Certificate Revocation Lists for CRL checking + pub crls: Vec<CertificateRevocationListDer<'static>>, +} + +/// Create custom CryptoProvider with specified cipher suites and key exchange groups +/// +/// This helper function consolidates the duplicated CryptoProvider creation logic +/// for both server and client configurations. +fn create_custom_crypto_provider( + cipher_suites: Option<Vec<rustls::SupportedCipherSuite>>, + kx_groups: Option<Vec<&'static dyn rustls::crypto::SupportedKxGroup>>, +) -> Arc<rustls::crypto::CryptoProvider> { + use rustls::crypto::aws_lc_rs::{ALL_CIPHER_SUITES, ALL_KX_GROUPS}; + let default_provider = rustls::crypto::aws_lc_rs::default_provider(); + + Arc::new(rustls::crypto::CryptoProvider { + cipher_suites: cipher_suites.unwrap_or_else(|| ALL_CIPHER_SUITES.to_vec()), + kx_groups: kx_groups.unwrap_or_else(|| ALL_KX_GROUPS.to_vec()), + signature_verification_algorithms: default_provider.signature_verification_algorithms, + secure_random: default_provider.secure_random, + key_provider: default_provider.key_provider, + }) +} + +/// Create a server TLS configuration +/// +/// This abstracts the complex rustls ServerConfig building logic, +/// matching SSL_CTX initialization for server sockets. +pub(super) fn create_server_config(options: ServerConfigOptions) -> Result<ServerConfig, String> { + use rustls::server::WebPkiClientVerifier; + + // Ensure default CryptoProvider is installed + ensure_default_provider(); + + // Create custom crypto provider using helper function + let custom_provider = create_custom_crypto_provider( + options.protocol_settings.cipher_suites.clone(), + options.protocol_settings.kx_groups.clone(), + ); + + // Step 1: Build the appropriate client cert verifier based on settings + let client_cert_verifier: Option<Arc<dyn rustls::server::danger::ClientCertVerifier>> = + if let Some(root_store) = options.root_store { + if options.request_client_cert { + // Client certificate verification required + let base_verifier = WebPkiClientVerifier::builder(Arc::new(root_store)) + .build() + .map_err(|e| format!("Failed to create client verifier: {e}"))?; + + if options.use_deferred_validation { + // TLS 1.3: Use deferred validation + if let Some(deferred_error) = options.deferred_cert_error { + use crate::ssl::cert::DeferredClientCertVerifier; + let deferred_verifier = + DeferredClientCertVerifier::new(base_verifier, deferred_error); + Some(Arc::new(deferred_verifier)) + } else { + // No deferred error storage provided, use immediate validation + Some(base_verifier) + } + } else { + // TLS 1.2 or non-deferred: Use immediate validation + Some(base_verifier) + } + } else { + // No client authentication + None + } + } else { + // No root store - no client authentication + None + }; + + // Step 2: Create ServerConfig builder once with the selected verifier + let builder = ServerConfig::builder_with_provider(custom_provider.clone()) + .with_protocol_versions(options.protocol_settings.versions) + .map_err(|e| format!("Failed to create server config builder: {e}"))?; + + let builder = if let Some(verifier) = client_cert_verifier { + builder.with_client_cert_verifier(verifier) + } else { + builder.with_no_client_auth() + }; + + // Add certificate + let mut config = if let Some(resolver) = options.cert_resolver { + // Use custom cert resolver (e.g., for SNI) + builder.with_cert_resolver(resolver) + } else { + // Use single certificate + builder + .with_single_cert(options.cert_chain, options.private_key) + .map_err(|e| format!("Failed to set server certificate: {e}"))? + }; + + // Set ALPN protocols with fallback + apply_alpn_with_fallback( + &mut config.alpn_protocols, + &options.protocol_settings.alpn_protocols, + ); + + // Set session storage for server-side session resumption (TLS 1.3) + if let Some(session_storage) = options.session_storage { + config.session_storage = session_storage; + } + + // Set ticketer for TLS 1.2 session tickets (stateless resumption) + if let Some(ticketer) = options.ticketer { + config.ticketer = ticketer.clone(); + } + + Ok(config) +} + +/// Build WebPki verifier with CRL support +/// +/// This helper function consolidates the duplicated CRL setup logic for both +/// check_hostname=True and check_hostname=False cases. +fn build_webpki_verifier_with_crls( + root_store: Arc<RootCertStore>, + crls: Vec<CertificateRevocationListDer<'static>>, + verify_flags: i32, +) -> Result<Arc<dyn rustls::client::danger::ServerCertVerifier>, String> { + use rustls::client::WebPkiServerVerifier; + + let mut verifier_builder = WebPkiServerVerifier::builder(root_store); + + // Check if CRL verification is requested + let crl_check_requested = verify_flags & X509_V_FLAG_CRL_CHECK != 0; + let has_crls = !crls.is_empty(); + + // Add CRLs if provided OR if CRL checking is explicitly requested + // (even with empty CRLs, rustls will fail verification if CRL checking is enabled) + if has_crls || crl_check_requested { + verifier_builder = verifier_builder.with_crls(crls); + + // Check if we should only verify end-entity (leaf) certificates + if verify_flags & X509_V_FLAG_CRL_CHECK != 0 { + verifier_builder = verifier_builder.only_check_end_entity_revocation(); + } + } + + let webpki_verifier = verifier_builder + .build() + .map_err(|e| format!("Failed to build WebPkiServerVerifier: {e}"))?; + + Ok(webpki_verifier as Arc<dyn rustls::client::danger::ServerCertVerifier>) +} + +/// Apply verifier wrappers (CRLCheckVerifier and StrictCertVerifier) +/// +/// This helper function consolidates the duplicated verifier wrapping logic. +fn apply_verifier_wrappers( + verifier: Arc<dyn rustls::client::danger::ServerCertVerifier>, + verify_flags: i32, + has_crls: bool, + ca_certs_der: Vec<Vec<u8>>, +) -> Arc<dyn rustls::client::danger::ServerCertVerifier> { + let crl_check_requested = verify_flags & X509_V_FLAG_CRL_CHECK != 0; + + // Wrap with CRLCheckVerifier to enforce CRL checking when flags are set + let verifier = if crl_check_requested { + use crate::ssl::cert::CRLCheckVerifier; + Arc::new(CRLCheckVerifier::new( + verifier, + has_crls, + crl_check_requested, + )) + } else { + verifier + }; + + // Always use PartialChainVerifier when trust store is not empty + // This allows self-signed certificates in trust store to be trusted + // (OpenSSL behavior: self-signed certs are always trusted, non-self-signed require flag) + let verifier = if !ca_certs_der.is_empty() { + use crate::ssl::cert::PartialChainVerifier; + Arc::new(PartialChainVerifier::new( + verifier, + ca_certs_der, + verify_flags, + )) + } else { + verifier + }; + + // Wrap with StrictCertVerifier if VERIFY_X509_STRICT flag is set + if verify_flags & VERIFY_X509_STRICT != 0 { + Arc::new(super::cert::StrictCertVerifier::new(verifier, verify_flags)) + } else { + verifier + } +} + +/// Apply ALPN protocols +/// +/// OpenSSL 1.1.0f+ allows ALPN negotiation to fail without aborting handshake. +/// rustls follows RFC 7301 strictly and rejects connections with no matching protocol. +/// To emulate OpenSSL behavior, we add a special fallback protocol (null byte). +fn apply_alpn_with_fallback(config_alpn: &mut Vec<Vec<u8>>, alpn_protocols: &[Vec<u8>]) { + if !alpn_protocols.is_empty() { + *config_alpn = alpn_protocols.to_vec(); + config_alpn.push(vec![0u8]); // Add null byte as fallback marker + } +} + +/// Create a client TLS configuration +/// +/// This abstracts the complex rustls ClientConfig building logic, +/// matching SSL_CTX initialization for client sockets. +pub(super) fn create_client_config(options: ClientConfigOptions) -> Result<ClientConfig, String> { + // Ensure default CryptoProvider is installed + ensure_default_provider(); + + // Create custom crypto provider using helper function + let custom_provider = create_custom_crypto_provider( + options.protocol_settings.cipher_suites.clone(), + options.protocol_settings.kx_groups.clone(), + ); + + // Step 1: Build the appropriate verifier based on verification settings + let verifier: Arc<dyn rustls::client::danger::ServerCertVerifier> = if options + .verify_server_cert + { + // Verify server certificates + let root_store = options + .root_store + .ok_or("Root store required for server verification")?; + + let root_store_arc = Arc::new(root_store); + + // Check if root_store is empty (no CA certs loaded) + // CPython allows this and fails during handshake with SSLCertVerificationError + if root_store_arc.is_empty() { + // Use EmptyRootStoreVerifier - always fails with UnknownIssuer during handshake + use crate::ssl::cert::EmptyRootStoreVerifier; + Arc::new(EmptyRootStoreVerifier) + } else { + // Calculate has_crls once for both hostname verification paths + let has_crls = !options.crls.is_empty(); + + if options.check_hostname { + // Default behavior: verify both certificate chain and hostname + let base_verifier = build_webpki_verifier_with_crls( + root_store_arc.clone(), + options.crls, + options.verify_flags, + )?; + + // Apply CRL and Strict verifier wrappers using helper function + apply_verifier_wrappers( + base_verifier, + options.verify_flags, + has_crls, + options.ca_certs_der.clone(), + ) + } else { + // check_hostname=False: verify certificate chain but ignore hostname + use crate::ssl::cert::HostnameIgnoringVerifier; + + // Build verifier with CRL support using helper function + let webpki_verifier = build_webpki_verifier_with_crls( + root_store_arc.clone(), + options.crls, + options.verify_flags, + )?; + + // Apply CRL verifier wrapper if needed (without Strict wrapper yet) + let crl_check_requested = options.verify_flags & X509_V_FLAG_CRL_CHECK != 0; + let verifier = if crl_check_requested { + use crate::ssl::cert::CRLCheckVerifier; + Arc::new(CRLCheckVerifier::new( + webpki_verifier, + has_crls, + crl_check_requested, + )) as Arc<dyn rustls::client::danger::ServerCertVerifier> + } else { + webpki_verifier + }; + + // Wrap with PartialChainVerifier if VERIFY_X509_PARTIAL_CHAIN is set + const VERIFY_X509_PARTIAL_CHAIN: i32 = 0x80000; + let verifier = if options.verify_flags & VERIFY_X509_PARTIAL_CHAIN != 0 { + use crate::ssl::cert::PartialChainVerifier; + Arc::new(PartialChainVerifier::new( + verifier, + options.ca_certs_der.clone(), + options.verify_flags, + )) as Arc<dyn rustls::client::danger::ServerCertVerifier> + } else { + verifier + }; + + // Wrap with HostnameIgnoringVerifier to bypass hostname checking + let hostname_ignoring_verifier: Arc< + dyn rustls::client::danger::ServerCertVerifier, + > = Arc::new(HostnameIgnoringVerifier::new_with_verifier(verifier)); + + // Apply Strict verifier wrapper once at the end if needed + if options.verify_flags & VERIFY_X509_STRICT != 0 { + Arc::new(crate::ssl::cert::StrictCertVerifier::new( + hostname_ignoring_verifier, + options.verify_flags, + )) + } else { + hostname_ignoring_verifier + } + } + } + } else { + // CERT_NONE: disable all verification + use crate::ssl::cert::NoVerifier; + Arc::new(NoVerifier) + }; + + // Step 2: Create ClientConfig builder once with the selected verifier + let builder = ClientConfig::builder_with_provider(custom_provider.clone()) + .with_protocol_versions(options.protocol_settings.versions) + .map_err(|e| format!("Failed to create client config builder: {e}"))? + .dangerous() + .with_custom_certificate_verifier(verifier); + + // Add client certificate if provided (mTLS) + let mut config = + if let (Some(cert_chain), Some(private_key)) = (options.cert_chain, options.private_key) { + builder + .with_client_auth_cert(cert_chain, private_key) + .map_err(|e| format!("Failed to set client certificate: {e}"))? + } else { + builder.with_no_client_auth() + }; + + // Set ALPN protocols + apply_alpn_with_fallback( + &mut config.alpn_protocols, + &options.protocol_settings.alpn_protocols, + ); + + // Set session resumption + if let Some(session_store) = options.session_store { + use rustls::client::Resumption; + config.resumption = Resumption::store(session_store); + } + + Ok(config) +} + +/// Helper function - check if error is BlockingIOError +pub(super) fn is_blocking_io_error(err: &PyBaseExceptionRef, vm: &VirtualMachine) -> bool { + err.fast_isinstance(vm.ctx.exceptions.blocking_io_error) +} + +// Handshake Helper Functions + +/// Write TLS handshake data to socket/BIO +/// +/// Drains all pending TLS data from rustls and sends it to the peer. +/// Returns whether any progress was made. +fn handshake_write_loop( + conn: &mut TlsConnection, + socket: &PySSLSocket, + force_initial_write: bool, + vm: &VirtualMachine, +) -> SslResult<bool> { + let mut made_progress = false; + + while conn.wants_write() || force_initial_write { + if force_initial_write && !conn.wants_write() { + // No data to write on first iteration - break to avoid infinite loop + break; + } + + let mut buf = Vec::new(); + let written = conn + .write_tls(&mut buf as &mut dyn std::io::Write) + .map_err(SslError::Io)?; + + if written > 0 && !buf.is_empty() { + // Send directly without select - blocking sockets will wait automatically + // Handle BlockingIOError from non-blocking sockets + match socket.sock_send(buf, vm) { + Ok(_) => { + made_progress = true; + } + Err(e) => { + if is_blocking_io_error(&e, vm) { + // Non-blocking socket would block - return SSLWantWriteError + return Err(SslError::WantWrite); + } + return Err(SslError::Py(e)); + } + } + } else if written == 0 { + // No data written but wants_write is true - should not happen normally + // Break to avoid infinite loop + break; + } + + // Check if there's more to write + if !conn.wants_write() { + break; + } + } + + Ok(made_progress) +} + +/// Read TLS handshake data from socket/BIO +/// +/// Waits for and reads TLS records from the peer, handling SNI callback setup. +/// Returns (made_progress, is_first_sni_read). +fn handshake_read_data( + conn: &mut TlsConnection, + socket: &PySSLSocket, + is_bio: bool, + is_server: bool, + vm: &VirtualMachine, +) -> SslResult<(bool, bool)> { + if !conn.wants_read() { + return Ok((false, false)); + } + + // SERVER-SPECIFIC: Check if this is the first read (for SNI callback) + // Must check BEFORE reading data, so we can detect first time + let is_first_sni_read = is_server && socket.is_first_sni_read(); + + // Wait for data in socket mode + if !is_bio { + let timed_out = socket + .sock_wait_for_io_impl(SelectKind::Read, vm) + .map_err(SslError::Py)?; + + if timed_out { + // This should rarely happen now - only if socket itself has a timeout + // and we're waiting for required handshake data + return Err(SslError::Timeout("timed out".to_string())); + } + } + + let data_obj = match socket.sock_recv(SSL3_RT_MAX_PLAIN_LENGTH, vm) { + Ok(d) => d, + Err(e) => { + if is_blocking_io_error(&e, vm) { + return Err(SslError::WantRead); + } + // In socket mode, if recv times out and we're only waiting for read + // (no wants_write), we might be waiting for optional NewSessionTicket in TLS 1.3 + // Consider the handshake complete + if !is_bio && !conn.wants_write() { + // Check if it's a timeout exception + if e.fast_isinstance(vm.ctx.exceptions.timeout_error) { + // Timeout waiting for optional data - handshake is complete + return Ok((false, false)); + } + } + return Err(SslError::Py(e)); + } + }; + + // SERVER-SPECIFIC: Save ClientHello on first read for potential connection recreation + if is_first_sni_read { + // Extract bytes from PyObjectRef + use rustpython_vm::builtins::PyBytes; + if let Some(bytes_obj) = data_obj.downcast_ref::<PyBytes>() { + socket.save_client_hello_from_bytes(bytes_obj.as_bytes()); + } + } + + // Feed data to rustls + ssl_read_tls_records(conn, data_obj, is_bio, vm)?; + + Ok((true, is_first_sni_read)) +} + +/// Handle handshake completion for server-side TLS 1.3 +/// +/// Tries to send NewSessionTicket in non-blocking mode to avoid deadlocks. +/// Returns true if handshake is complete and we should exit. +fn handle_handshake_complete( + conn: &mut TlsConnection, + socket: &PySSLSocket, + _is_server: bool, + vm: &VirtualMachine, +) -> SslResult<bool> { + if conn.is_handshaking() { + return Ok(false); // Not complete yet + } + + // Handshake is complete! + // + // Different behavior for BIO mode vs socket mode: + // + // BIO mode (CPython-compatible): + // - Python code calls outgoing.read() to get pending data + // - We just return here and let Python handle the data + // + // Socket mode (rustls-specific): + // - OpenSSL automatically writes to socket in SSL_do_handshake() + // - We must explicitly call write_tls() to send pending data + // - Without this, client hangs waiting for server's NewSessionTicket + + if socket.is_bio_mode() { + // BIO mode: Write pending data to outgoing BIO (one-time drain) + // Python's ssl_io_loop will read from outgoing BIO + if conn.wants_write() { + // Call write_tls ONCE to drain pending data + // Do NOT loop on wants_write() - avoid infinite loop/deadlock + let tls_data = ssl_write_tls_records(conn)?; + if !tls_data.is_empty() { + socket.sock_send(tls_data, vm).map_err(SslError::Py)?; + } + + // IMPORTANT: Don't check wants_write() again! + // Python's ssl_io_loop will call do_handshake() again if needed + } + } else if conn.wants_write() { + // Send all pending data (e.g., TLS 1.3 NewSessionTicket) to socket + while conn.wants_write() { + let tls_data = ssl_write_tls_records(conn)?; + if tls_data.is_empty() { + break; + } + socket.sock_send(tls_data, vm).map_err(SslError::Py)?; + } + } + + Ok(true) +} + +/// Try to read plaintext data from TLS connection buffer +/// +/// Returns Ok(Some(n)) if n bytes were read, Ok(None) if would block, +/// or Err on real errors. +fn try_read_plaintext(conn: &mut TlsConnection, buf: &mut [u8]) -> SslResult<Option<usize>> { + let mut reader = conn.reader(); + match reader.read(buf) { + Ok(0) => { + // EOF from TLS connection + Ok(Some(0)) + } + Ok(n) => { + // Successfully read n bytes + Ok(Some(n)) + } + Err(e) if e.kind() != std::io::ErrorKind::WouldBlock => { + // Real error + Err(SslError::Io(e)) + } + Err(_) => { + // WouldBlock - no plaintext available + Ok(None) + } + } +} + +/// Equivalent to OpenSSL's SSL_do_handshake() +/// +/// Performs TLS handshake by exchanging data with the peer until completion. +/// This abstracts away the low-level rustls read_tls/write_tls loop. +/// +/// = SSL_do_handshake() +pub(super) fn ssl_do_handshake( + conn: &mut TlsConnection, + socket: &PySSLSocket, + vm: &VirtualMachine, +) -> SslResult<()> { + // Check if handshake is already done + if !conn.is_handshaking() { + return Ok(()); + } + + let is_bio = socket.is_bio_mode(); + let is_server = matches!(conn, TlsConnection::Server(_)); + let mut first_iteration = true; // Track if this is the first loop iteration + let mut iteration_count = 0; + + loop { + iteration_count += 1; + let mut made_progress = false; + + // IMPORTANT: In BIO mode, force initial write even if wants_write() is false + // rustls requires write_tls() to be called to generate ClientHello/ServerHello + let force_initial_write = is_bio && first_iteration; + + // Write TLS handshake data to socket/BIO + let write_progress = handshake_write_loop(conn, socket, force_initial_write, vm)?; + made_progress |= write_progress; + + // Read TLS handshake data from socket/BIO + let (read_progress, is_first_sni_read) = + handshake_read_data(conn, socket, is_bio, is_server, vm)?; + made_progress |= read_progress; + + // Process TLS packets (state machine) + if let Err(e) = conn.process_new_packets() { + // Send close_notify on error + if !is_bio { + conn.send_close_notify(); + // Actually send the close_notify alert + if let Ok(alert_data) = ssl_write_tls_records(conn) + && !alert_data.is_empty() + { + let _ = socket.sock_send(alert_data, vm); + } + } + + // Certificate verification errors are already handled by from_rustls + + return Err(SslError::from_rustls(e)); + } + + // SERVER-SPECIFIC: Check SNI callback after processing packets + // SNI name is extracted during process_new_packets() + // Invoke callback on FIRST read if callback is configured, regardless of SNI presence + if is_server && is_first_sni_read && socket.has_sni_callback() { + // IMPORTANT: Do NOT call the callback here! + // The connection lock is still held, which would cause deadlock. + // Return SniCallbackRestart to signal do_handshake to: + // 1. Drop conn_guard + // 2. Call the callback (with Some(name) or None) + // 3. Restart handshake + return Err(SslError::SniCallbackRestart); + } + + // Check if handshake is complete and handle post-handshake processing + // CRITICAL: We do NOT check wants_read() - this matches CPython/OpenSSL behavior! + if handle_handshake_complete(conn, socket, is_server, vm)? { + return Ok(()); + } + + // In BIO mode, stop after one iteration + if is_bio { + // Before returning WANT error, write any pending TLS data to BIO + // This is critical: if wants_write is true after process_new_packets, + // we need to write that data to the outgoing BIO before returning + if conn.wants_write() { + // Write all pending TLS data to outgoing BIO + loop { + let mut buf = vec![0u8; SSL3_RT_MAX_PLAIN_LENGTH]; + let n = match conn.write_tls(&mut buf.as_mut_slice()) { + Ok(n) => n, + Err(_) => break, + }; + if n == 0 { + break; + } + // Send to outgoing BIO + socket + .sock_send(buf[..n].to_vec(), vm) + .map_err(SslError::Py)?; + // Check if there's more to write + if !conn.wants_write() { + break; + } + } + // After writing, check if we still want more + // If all data was written, wants_write may now be false + if conn.wants_write() { + // Still need more - return WANT_WRITE + return Err(SslError::WantWrite); + } + // Otherwise fall through to check wants_read + } + + // Check if we need to read + if conn.wants_read() { + return Err(SslError::WantRead); + } + break; + } + + // Mark that we've completed the first iteration + first_iteration = false; + + // Improved loop termination logic: + // Continue looping if: + // 1. Rustls wants more I/O (wants_read or wants_write), OR + // 2. We made progress in this iteration + // + // This is more robust than just checking made_progress, because: + // - Rustls may need multiple iterations to process TLS state machine + // - Network delays may cause temporary "no progress" situations + // - wants_read/wants_write accurately reflect Rustls internal state + let should_continue = conn.wants_read() || conn.wants_write() || made_progress; + + if !should_continue { + break; + } + + // Safety check: prevent truly infinite loops (should never happen) + if iteration_count > 1000 { + break; + } + } + + // If we exit the loop without completing handshake, return error + // Check rustls state to provide better error message + if conn.is_handshaking() { + Err(SslError::Syscall(format!( + "SSL handshake failed: incomplete after {iteration_count} iterations", + ))) + } else { + // Handshake completed successfully (shouldn't reach here normally) + Ok(()) + } +} + +/// Equivalent to OpenSSL's SSL_read() +/// +/// Reads application data from TLS connection. +/// Automatically handles TLS record I/O as needed. +/// +/// = SSL_read_ex() +pub(super) fn ssl_read( + conn: &mut TlsConnection, + buf: &mut [u8], + socket: &PySSLSocket, + vm: &VirtualMachine, +) -> SslResult<usize> { + let is_bio = socket.is_bio_mode(); + + // Get socket timeout and calculate deadline (= _PyDeadline_Init) + let deadline = if !is_bio { + match socket.get_socket_timeout(vm).map_err(SslError::Py)? { + Some(timeout) if !timeout.is_zero() => Some(std::time::Instant::now() + timeout), + _ => None, // None = blocking (no deadline), Some(0) = non-blocking (handled below) + } + } else { + None // BIO mode has no deadline + }; + + // Loop to handle TLS records and post-handshake messages + // Matches SSL_read behavior which loops until data is available + // - CPython uses OpenSSL's SSL_read which loops on SSL_ERROR_WANT_READ/WANT_WRITE + // - We use rustls which requires manual read_tls/process_new_packets loop + // - No iteration limit: relies on deadline and blocking I/O + // - Blocking sockets: sock_select() and recv() wait at kernel level (no CPU busy-wait) + // - Non-blocking sockets: immediate return on first WantRead + // - Deadline prevents timeout issues + loop { + // Check deadline + if let Some(deadline) = deadline + && std::time::Instant::now() >= deadline + { + // Timeout expired + return Err(SslError::Timeout( + "The read operation timed out".to_string(), + )); + } + // Check if we need to read more TLS records BEFORE trying plaintext read + // This ensures we don't miss data that's already been processed + let needs_more_tls = conn.wants_read(); + + // Try to read plaintext from rustls buffer + if let Some(n) = try_read_plaintext(conn, buf)? { + return Ok(n); + } + + // No plaintext available and cannot read more TLS records + if !needs_more_tls { + if is_bio && let Some(bio_obj) = socket.incoming_bio() { + let is_eof = bio_obj + .get_attr("eof", vm) + .and_then(|v| v.try_into_value::<bool>(vm)) + .unwrap_or(false); + if is_eof { + return Err(SslError::Eof); + } + } + return Err(SslError::WantRead); + } + + // Read and process TLS records + // This will block for blocking sockets + match ssl_ensure_data_available(conn, socket, vm) { + Ok(_bytes_read) => { + // Successfully read and processed TLS data + // Continue loop to try reading plaintext + } + Err(SslError::Io(ref io_err)) if io_err.to_string().contains("message buffer full") => { + // Buffer is full - we need to consume plaintext before reading more + // Try to read plaintext now + match try_read_plaintext(conn, buf)? { + Some(n) if n > 0 => { + // Have plaintext - return it + // Python will call read() again if it needs more data + return Ok(n); + } + _ => { + // No plaintext available yet - this is unusual + // Return WantRead to let Python retry + return Err(SslError::WantRead); + } + } + } + Err(e) => { + // Other errors - check for buffered plaintext before propagating + match try_read_plaintext(conn, buf)? { + Some(n) if n > 0 => { + // Have buffered plaintext - return it successfully + return Ok(n); + } + _ => { + // No buffered data - propagate the error + return Err(e); + } + } + } + } + } +} + +// Helper functions (private-ish, used by public SSL functions) + +/// Write TLS records from rustls to socket +fn ssl_write_tls_records(conn: &mut TlsConnection) -> SslResult<Vec<u8>> { + let mut buf = Vec::new(); + let n = conn + .write_tls(&mut buf as &mut dyn std::io::Write) + .map_err(SslError::Io)?; + + if n > 0 { Ok(buf) } else { Ok(Vec::new()) } +} + +/// Read TLS records from socket to rustls +fn ssl_read_tls_records( + conn: &mut TlsConnection, + data: PyObjectRef, + is_bio: bool, + vm: &VirtualMachine, +) -> SslResult<()> { + // Convert PyObject to bytes-like (supports bytes, bytearray, etc.) + let bytes = ArgBytesLike::try_from_object(vm, data) + .map_err(|_| SslError::Syscall("Expected bytes-like object".to_string()))?; + + let bytes_data = bytes.borrow_buf(); + + if bytes_data.is_empty() { + // different error for BIO vs socket mode + if is_bio { + // In BIO mode, no data means WANT_READ + return Err(SslError::WantRead); + } else { + // In socket mode, empty recv() means TCP EOF (FIN received) + // Need to distinguish: + // 1. Clean shutdown: received TLS close_notify → return ZeroReturn (0 bytes) + // 2. Unexpected EOF: no close_notify → return Eof (SSLEOFError) + // + // SSL_ERROR_ZERO_RETURN vs SSL_ERROR_SYSCALL(errno=0) logic + // CPython checks SSL_get_shutdown() & SSL_RECEIVED_SHUTDOWN + // + // Process any buffered TLS records (may contain close_notify) + let _ = conn.process_new_packets(); + + // IMPORTANT: CPython's default behavior (suppress_ragged_eofs=True) + // treats empty recv() as clean shutdown, returning 0 bytes instead of raising SSLEOFError. + // + // This is necessary for HTTP/1.0 servers that: + // 1. Send response without Content-Length header + // 2. Signal end-of-response by closing connection (TCP FIN) + // 3. Don't send TLS close_notify before TCP close + // + // While this could theoretically allow truncation attacks, + // it's the standard behavior for compatibility with real-world servers. + // Python only raises SSLEOFError when suppress_ragged_eofs=False is explicitly set. + // + // TODO: Implement suppress_ragged_eofs parameter if needed for strict security mode. + return Err(SslError::ZeroReturn); + } + } + + // Feed all received data to read_tls - loop to consume all data + // read_tls may not consume all data in one call + let mut offset = 0; + while offset < bytes_data.len() { + let remaining = &bytes_data[offset..]; + let mut cursor = std::io::Cursor::new(remaining); + + match conn.read_tls(&mut cursor) { + Ok(read_bytes) => { + if read_bytes == 0 { + // No more data can be consumed + break; + } + offset += read_bytes; + } + Err(e) => { + // Real error - propagate it + return Err(SslError::Io(e)); + } + } + } + + Ok(()) +} + +/// Ensure TLS data is available for reading +/// Returns the number of bytes read from the socket +fn ssl_ensure_data_available( + conn: &mut TlsConnection, + socket: &PySSLSocket, + vm: &VirtualMachine, +) -> SslResult<usize> { + // Unlike OpenSSL's SSL_read, rustls requires explicit I/O + if conn.wants_read() { + let is_bio = socket.is_bio_mode(); + + // For non-BIO mode (regular sockets), check if socket is ready first + // PERFORMANCE OPTIMIZATION: Only use select for sockets with timeout + // - Blocking sockets (timeout=None): Skip select, recv() will block naturally + // - Timeout sockets: Use select to enforce timeout + // - Non-blocking sockets: Skip select, recv() will return EAGAIN immediately + if !is_bio { + let timeout = socket.get_socket_timeout(vm).map_err(SslError::Py)?; + + // Only use select if socket has a positive timeout + if let Some(t) = timeout + && !t.is_zero() + { + // Socket has timeout - use select to enforce it + let timed_out = socket + .sock_wait_for_io_impl(SelectKind::Read, vm) + .map_err(SslError::Py)?; + if timed_out { + // Socket not ready within timeout + return Err(SslError::WantRead); + } + } + // else: non-blocking socket (timeout=0) or blocking socket (timeout=None) - skip select + } + + let data = socket.sock_recv(2048, vm).map_err(SslError::Py)?; + + // Get the size of received data + let bytes_read = data + .clone() + .try_into_value::<rustpython_vm::builtins::PyBytes>(vm) + .map(|b| b.as_bytes().len()) + .unwrap_or(0); + + // Check if BIO has EOF set (incoming BIO closed) + let is_eof = if is_bio { + // Check incoming BIO's eof property + if let Some(bio_obj) = socket.incoming_bio() { + bio_obj + .get_attr("eof", vm) + .and_then(|v| v.try_into_value::<bool>(vm)) + .unwrap_or(false) + } else { + false + } + } else { + false + }; + + // If BIO EOF is set and no data available, treat as connection EOF + if is_eof && bytes_read == 0 { + return Err(SslError::Eof); + } + + // Feed data to rustls and process packets + ssl_read_tls_records(conn, data, is_bio, vm)?; + + // Process any packets we successfully read + conn.process_new_packets().map_err(SslError::from_rustls)?; + + Ok(bytes_read) + } else { + // No data to read + Ok(0) + } +} + +// Multi-Certificate Resolver for RSA/ECC Support + +/// Multi-certificate resolver that selects appropriate certificate based on client capabilities +/// +/// This resolver implements OpenSSL's behavior of supporting multiple certificate/key pairs +/// (e.g., one RSA and one ECC) and selecting the appropriate one based on the client's +/// supported signature algorithms during the TLS handshake. +/// +/// OpenSSL's SSL_CTX_use_certificate_chain_file can be called multiple +/// times to add different certificate types, and OpenSSL automatically selects the best one. +#[derive(Debug)] +pub(super) struct MultiCertResolver { + cert_keys: Vec<Arc<CertifiedKey>>, +} + +impl MultiCertResolver { + /// Create a new multi-certificate resolver + pub fn new(cert_keys: Vec<Arc<CertifiedKey>>) -> Self { + Self { cert_keys } + } +} + +impl ResolvesServerCert for MultiCertResolver { + fn resolve(&self, client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<CertifiedKey>> { + // Get the signature schemes supported by the client + let client_schemes = client_hello.signature_schemes(); + + // Try to find a certificate that matches the client's signature schemes + for cert_key in &self.cert_keys { + // Check if this certificate's signing key is compatible with any of the + // client's supported signature schemes + if let Some(_scheme) = cert_key.key.choose_scheme(client_schemes) { + return Some(cert_key.clone()); + } + } + + // If no perfect match, return the first certificate as fallback + // (This matches OpenSSL's behavior of using the first loaded cert if negotiation fails) + self.cert_keys.first().cloned() + } +} + +// Helper Functions for OpenSSL Compatibility: + +/// Normalize cipher suite name for OpenSSL compatibility +/// +/// Converts rustls cipher names to OpenSSL format: +/// - TLS_AES_256_GCM_SHA384 → AES256-GCM-SHA384 +/// - Replaces "AES-256" with "AES256" and "AES-128" with "AES128" +pub(super) fn normalize_cipher_name(rustls_name: &str) -> String { + rustls_name + .strip_prefix("TLS_") + .unwrap_or(rustls_name) + .replace("_WITH_", "_") + .replace('_', "-") + .replace("AES-256", "AES256") + .replace("AES-128", "AES128") +} + +/// Get cipher key size in bits from cipher suite name +/// +/// Returns: +/// - 256 for AES-256 and ChaCha20 +/// - 128 for AES-128 +/// - 0 for unknown ciphers +pub(super) fn get_cipher_key_bits(cipher_name: &str) -> i32 { + if cipher_name.contains("256") || cipher_name.contains("CHACHA20") { + 256 + } else if cipher_name.contains("128") { + 128 + } else { + 0 + } +} + +/// Get encryption algorithm description from cipher name +/// +/// Returns human-readable encryption description for OpenSSL compatibility +pub(super) fn get_cipher_encryption_desc(cipher_name: &str) -> &'static str { + if cipher_name.contains("AES256") { + "AESGCM(256)" + } else if cipher_name.contains("AES128") { + "AESGCM(128)" + } else if cipher_name.contains("CHACHA20") { + "CHACHA20-POLY1305(256)" + } else { + "Unknown" + } +} + +/// Normalize rustls cipher suite name to IANA standard format +/// +/// Converts rustls Debug format names to IANA standard: +/// - "TLS13_AES_256_GCM_SHA384" -> "TLS_AES_256_GCM_SHA384" +/// - Other names remain unchanged +pub(super) fn normalize_rustls_cipher_name(rustls_name: &str) -> String { + if rustls_name.starts_with("TLS13_") { + rustls_name.replace("TLS13_", "TLS_") + } else { + rustls_name.to_string() + } +} + +/// Convert rustls protocol version to string representation +/// +/// Returns the TLS version string +/// - TLSv1.2, TLSv1.3, or "Unknown" +pub(super) fn get_protocol_version_str(version: &rustls::SupportedProtocolVersion) -> &'static str { + match version.version { + rustls::ProtocolVersion::TLSv1_2 => "TLSv1.2", + rustls::ProtocolVersion::TLSv1_3 => "TLSv1.3", + _ => "Unknown", + } +} + +/// Cipher suite information +/// +/// Contains all relevant cipher information extracted from a rustls CipherSuite +pub(super) struct CipherInfo { + /// IANA standard cipher name (e.g., "TLS_AES_256_GCM_SHA384") + pub name: String, + /// TLS protocol version (e.g., "TLSv1.2", "TLSv1.3") + pub protocol: &'static str, + /// Key size in bits (e.g., 128, 256) + pub bits: i32, +} + +/// Extract cipher information from a rustls CipherSuite +/// +/// This consolidates the common cipher extraction logic used across +/// get_ciphers(), cipher(), and shared_ciphers() methods. +pub(super) fn extract_cipher_info(suite: &rustls::SupportedCipherSuite) -> CipherInfo { + let rustls_name = format!("{:?}", suite.suite()); + let name = normalize_rustls_cipher_name(&rustls_name); + let protocol = get_protocol_version_str(suite.version()); + let bits = get_cipher_key_bits(&name); + + CipherInfo { + name, + protocol, + bits, + } +} + +/// Convert curve name to rustls key exchange group +/// +/// Maps OpenSSL curve names (e.g., "prime256v1", "secp384r1") to rustls KxGroups. +/// Returns an error if the curve is not supported by rustls. +pub(super) fn curve_name_to_kx_group( + curve: &str, +) -> Result<Vec<&'static dyn SupportedKxGroup>, String> { + // Get the default crypto provider's key exchange groups + let provider = rustls::crypto::aws_lc_rs::default_provider(); + let all_groups = &provider.kx_groups; + + match curve { + // P-256 (also known as secp256r1 or prime256v1) + "prime256v1" | "secp256r1" => { + // Find SECP256R1 in the provider's groups + all_groups + .iter() + .find(|g| g.name() == rustls::NamedGroup::secp256r1) + .map(|g| vec![*g]) + .ok_or_else(|| "secp256r1 not supported by crypto provider".to_owned()) + } + // P-384 (also known as secp384r1 or prime384v1) + "secp384r1" | "prime384v1" => all_groups + .iter() + .find(|g| g.name() == rustls::NamedGroup::secp384r1) + .map(|g| vec![*g]) + .ok_or_else(|| "secp384r1 not supported by crypto provider".to_owned()), + // X25519 + "X25519" | "x25519" => all_groups + .iter() + .find(|g| g.name() == rustls::NamedGroup::X25519) + .map(|g| vec![*g]) + .ok_or_else(|| "X25519 not supported by crypto provider".to_owned()), + // P-521 (also known as secp521r1 or prime521v1) + // Now supported with aws-lc-rs crypto provider + "prime521v1" | "secp521r1" => all_groups + .iter() + .find(|g| g.name() == rustls::NamedGroup::secp521r1) + .map(|g| vec![*g]) + .ok_or_else(|| "secp521r1 not supported by crypto provider".to_owned()), + // X448 + // Now supported with aws-lc-rs crypto provider + "X448" | "x448" => all_groups + .iter() + .find(|g| g.name() == rustls::NamedGroup::X448) + .map(|g| vec![*g]) + .ok_or_else(|| "X448 not supported by crypto provider".to_owned()), + _ => Err(format!("unknown curve name '{curve}'")), + } +} diff --git a/stdlib/src/ssl/oid.rs b/stdlib/src/ssl/oid.rs new file mode 100644 index 00000000000..2e13733a2a2 --- /dev/null +++ b/stdlib/src/ssl/oid.rs @@ -0,0 +1,464 @@ +// spell-checker: disable + +//! OID (Object Identifier) management for SSL/TLS +//! +//! This module provides OID lookup functionality compatible with CPython's ssl module. +//! It uses oid-registry crate for well-known OIDs while maintaining NID (Numerical Identifier) +//! mappings for CPython compatibility. + +use oid_registry::asn1_rs::Oid; +use std::collections::HashMap; + +/// OID entry with openssl-compatible metadata +#[derive(Debug, Clone)] +pub struct OidEntry { + /// NID (OpenSSL Numerical Identifier) - must match CPython/OpenSSL values + pub nid: i32, + /// Short name (e.g., "CN", "serverAuth") + pub short_name: &'static str, + /// Long name/description (e.g., "commonName", "TLS Web Server Authentication") + pub long_name: &'static str, + /// OID reference (static or dynamic) + pub oid: OidRef, +} + +/// OID reference - either from oid-registry or runtime-created +#[derive(Debug, Clone)] +pub enum OidRef { + /// Static OID from oid-registry crate (stored as value) + Static(Oid<'static>), + /// OID string (for OIDs not in oid-registry) - parsed on demand + String(&'static str), +} + +impl OidEntry { + /// Create entry from oid-registry static constant + pub fn from_static( + nid: i32, + short_name: &'static str, + long_name: &'static str, + oid: &Oid<'static>, + ) -> Self { + Self { + nid, + short_name, + long_name, + oid: OidRef::Static(oid.clone()), + } + } + + /// Create entry from OID string (for OIDs not in oid-registry) + pub const fn from_string( + nid: i32, + short_name: &'static str, + long_name: &'static str, + oid_str: &'static str, + ) -> Self { + Self { + nid, + short_name, + long_name, + oid: OidRef::String(oid_str), + } + } + + /// Get OID as string (e.g., "2.5.4.3") + pub fn oid_string(&self) -> String { + match &self.oid { + OidRef::Static(oid) => oid.to_id_string(), + OidRef::String(s) => s.to_string(), + } + } +} + +/// OID table with multiple indices for fast lookup +pub struct OidTable { + /// All entries + entries: Vec<OidEntry>, + /// NID -> index mapping + nid_to_idx: HashMap<i32, usize>, + /// Short name -> index mapping + short_name_to_idx: HashMap<&'static str, usize>, + /// Long name -> index mapping (case-insensitive) + long_name_to_idx: HashMap<String, usize>, + /// OID string -> index mapping + oid_str_to_idx: HashMap<String, usize>, +} + +impl OidTable { + fn build() -> Self { + let entries = build_oid_entries(); + let mut nid_to_idx = HashMap::with_capacity(entries.len()); + let mut short_name_to_idx = HashMap::with_capacity(entries.len()); + let mut long_name_to_idx = HashMap::with_capacity(entries.len()); + let mut oid_str_to_idx = HashMap::with_capacity(entries.len()); + + for (idx, entry) in entries.iter().enumerate() { + nid_to_idx.insert(entry.nid, idx); + short_name_to_idx.insert(entry.short_name, idx); + long_name_to_idx.insert(entry.long_name.to_lowercase(), idx); + oid_str_to_idx.insert(entry.oid_string(), idx); + } + + Self { + entries, + nid_to_idx, + short_name_to_idx, + long_name_to_idx, + oid_str_to_idx, + } + } + + pub fn find_by_nid(&self, nid: i32) -> Option<&OidEntry> { + self.nid_to_idx.get(&nid).map(|&idx| &self.entries[idx]) + } + + pub fn find_by_oid_string(&self, oid_str: &str) -> Option<&OidEntry> { + self.oid_str_to_idx + .get(oid_str) + .map(|&idx| &self.entries[idx]) + } + + pub fn find_by_name(&self, name: &str) -> Option<&OidEntry> { + // Try short name first (exact match) + self.short_name_to_idx + .get(name) + .or_else(|| { + // Try long name (case-insensitive) + self.long_name_to_idx.get(&name.to_lowercase()) + }) + .map(|&idx| &self.entries[idx]) + } +} + +/// Global OID table +static OID_TABLE: std::sync::LazyLock<OidTable> = std::sync::LazyLock::new(OidTable::build); + +/// Macro to define OID entry using oid-registry constant +macro_rules! oid_static { + ($nid:expr, $short:expr, $long:expr, $oid_const:path) => { + OidEntry::from_static($nid, $short, $long, &$oid_const) + }; +} + +/// Macro to define OID entry from string +macro_rules! oid_string { + ($nid:expr, $short:expr, $long:expr, $oid_str:expr) => { + OidEntry::from_string($nid, $short, $long, $oid_str) + }; +} + +/// Build the complete OID table +fn build_oid_entries() -> Vec<OidEntry> { + vec![ + // Priority 1: X.509 DN Attributes (OpenSSL NID values) + // These NIDs MUST match OpenSSL for CPython compatibility + oid_static!(13, "CN", "commonName", oid_registry::OID_X509_COMMON_NAME), + oid_static!(14, "C", "countryName", oid_registry::OID_X509_COUNTRY_NAME), + oid_static!( + 15, + "L", + "localityName", + oid_registry::OID_X509_LOCALITY_NAME + ), + oid_static!( + 16, + "ST", + "stateOrProvinceName", + oid_registry::OID_X509_STATE_OR_PROVINCE_NAME + ), + oid_static!( + 17, + "O", + "organizationName", + oid_registry::OID_X509_ORGANIZATION_NAME + ), + oid_static!( + 18, + "OU", + "organizationalUnitName", + oid_registry::OID_X509_ORGANIZATIONAL_UNIT + ), + oid_static!(41, "name", "name", oid_registry::OID_X509_NAME), + oid_static!(42, "GN", "givenName", oid_registry::OID_X509_GIVEN_NAME), + oid_static!(43, "initials", "initials", oid_registry::OID_X509_INITIALS), + oid_static!( + 4, + "serialNumber", + "serialNumber", + oid_registry::OID_X509_SERIALNUMBER + ), + oid_static!(100, "surname", "surname", oid_registry::OID_X509_SURNAME), + // emailAddress is special - it's in PKCS#9, not X.509 + oid_static!( + 48, + "emailAddress", + "emailAddress", + oid_registry::OID_PKCS9_EMAIL_ADDRESS + ), + // Priority 2: X.509 Extensions (Critical ones) + oid_static!( + 82, + "subjectKeyIdentifier", + "X509v3 Subject Key Identifier", + oid_registry::OID_X509_EXT_SUBJECT_KEY_IDENTIFIER + ), + oid_static!( + 83, + "keyUsage", + "X509v3 Key Usage", + oid_registry::OID_X509_EXT_KEY_USAGE + ), + oid_static!( + 85, + "subjectAltName", + "X509v3 Subject Alternative Name", + oid_registry::OID_X509_EXT_SUBJECT_ALT_NAME + ), + oid_static!( + 86, + "issuerAltName", + "X509v3 Issuer Alternative Name", + oid_registry::OID_X509_EXT_ISSUER_ALT_NAME + ), + oid_static!( + 87, + "basicConstraints", + "X509v3 Basic Constraints", + oid_registry::OID_X509_EXT_BASIC_CONSTRAINTS + ), + oid_static!( + 88, + "crlNumber", + "X509v3 CRL Number", + oid_registry::OID_X509_EXT_CRL_NUMBER + ), + oid_static!( + 90, + "authorityKeyIdentifier", + "X509v3 Authority Key Identifier", + oid_registry::OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER + ), + oid_static!( + 126, + "extendedKeyUsage", + "X509v3 Extended Key Usage", + oid_registry::OID_X509_EXT_EXTENDED_KEY_USAGE + ), + oid_static!( + 103, + "crlDistributionPoints", + "X509v3 CRL Distribution Points", + oid_registry::OID_X509_EXT_CRL_DISTRIBUTION_POINTS + ), + oid_static!( + 89, + "certificatePolicies", + "X509v3 Certificate Policies", + oid_registry::OID_X509_EXT_CERTIFICATE_POLICIES + ), + oid_static!( + 177, + "authorityInfoAccess", + "Authority Information Access", + oid_registry::OID_PKIX_AUTHORITY_INFO_ACCESS + ), + oid_static!( + 105, + "nameConstraints", + "X509v3 Name Constraints", + oid_registry::OID_X509_EXT_NAME_CONSTRAINTS + ), + // Priority 3: Extended Key Usage OIDs (not in oid-registry) + // These are defined in RFC 5280 but not in oid-registry, so we use strings + oid_string!( + 129, + "serverAuth", + "TLS Web Server Authentication", + "1.3.6.1.5.5.7.3.1" + ), + oid_string!( + 130, + "clientAuth", + "TLS Web Client Authentication", + "1.3.6.1.5.5.7.3.2" + ), + oid_string!(131, "codeSigning", "Code Signing", "1.3.6.1.5.5.7.3.3"), + oid_string!( + 132, + "emailProtection", + "E-mail Protection", + "1.3.6.1.5.5.7.3.4" + ), + oid_string!(133, "timeStamping", "Time Stamping", "1.3.6.1.5.5.7.3.8"), + oid_string!(180, "OCSPSigning", "OCSP Signing", "1.3.6.1.5.5.7.3.9"), + // Priority 4: Signature Algorithms + oid_static!( + 6, + "rsaEncryption", + "rsaEncryption", + oid_registry::OID_PKCS1_RSAENCRYPTION + ), + oid_static!( + 65, + "sha1WithRSAEncryption", + "sha1WithRSAEncryption", + oid_registry::OID_PKCS1_SHA1WITHRSA + ), + oid_static!( + 668, + "sha256WithRSAEncryption", + "sha256WithRSAEncryption", + oid_registry::OID_PKCS1_SHA256WITHRSA + ), + oid_static!( + 669, + "sha384WithRSAEncryption", + "sha384WithRSAEncryption", + oid_registry::OID_PKCS1_SHA384WITHRSA + ), + oid_static!( + 670, + "sha512WithRSAEncryption", + "sha512WithRSAEncryption", + oid_registry::OID_PKCS1_SHA512WITHRSA + ), + oid_static!( + 408, + "id-ecPublicKey", + "id-ecPublicKey", + oid_registry::OID_KEY_TYPE_EC_PUBLIC_KEY + ), + oid_static!( + 794, + "ecdsa-with-SHA256", + "ecdsa-with-SHA256", + oid_registry::OID_SIG_ECDSA_WITH_SHA256 + ), + oid_static!( + 795, + "ecdsa-with-SHA384", + "ecdsa-with-SHA384", + oid_registry::OID_SIG_ECDSA_WITH_SHA384 + ), + oid_static!( + 796, + "ecdsa-with-SHA512", + "ecdsa-with-SHA512", + oid_registry::OID_SIG_ECDSA_WITH_SHA512 + ), + // Priority 5: Hash Algorithms + oid_string!(64, "sha1", "sha1", "1.3.14.3.2.26"), + oid_static!(672, "sha256", "sha256", oid_registry::OID_NIST_HASH_SHA256), + oid_static!(673, "sha384", "sha384", oid_registry::OID_NIST_HASH_SHA384), + oid_static!(674, "sha512", "sha512", oid_registry::OID_NIST_HASH_SHA512), + oid_string!(675, "sha224", "sha224", "2.16.840.1.101.3.4.2.4"), + // Priority 6: Elliptic Curve OIDs + oid_static!(714, "secp256r1", "secp256r1", oid_registry::OID_EC_P256), + oid_string!(715, "secp384r1", "secp384r1", "1.3.132.0.34"), + oid_string!(716, "secp521r1", "secp521r1", "1.3.132.0.35"), + oid_string!(1172, "X25519", "X25519", "1.3.101.110"), + oid_string!(1173, "Ed25519", "Ed25519", "1.3.101.112"), + // Additional useful OIDs + oid_string!( + 183, + "subjectInfoAccess", + "Subject Information Access", + "1.3.6.1.5.5.7.1.11" + ), + oid_string!(920, "OCSP", "OCSP", "1.3.6.1.5.5.7.48.1"), + oid_string!(921, "caIssuers", "CA Issuers", "1.3.6.1.5.5.7.48.2"), + ] +} + +// Public API Functions + +/// Find OID entry by NID +pub fn find_by_nid(nid: i32) -> Option<&'static OidEntry> { + OID_TABLE.find_by_nid(nid) +} + +/// Find OID entry by OID string (e.g., "2.5.4.3") +pub fn find_by_oid_string(oid_str: &str) -> Option<&'static OidEntry> { + OID_TABLE.find_by_oid_string(oid_str) +} + +/// Find OID entry by name (short or long name) +pub fn find_by_name(name: &str) -> Option<&'static OidEntry> { + OID_TABLE.find_by_name(name) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_find_by_nid() { + let entry = find_by_nid(13).unwrap(); + assert_eq!(entry.short_name, "CN"); + assert_eq!(entry.long_name, "commonName"); + assert_eq!(entry.oid_string(), "2.5.4.3"); + } + + #[test] + fn test_find_by_oid_string() { + let entry = find_by_oid_string("2.5.4.3").unwrap(); + assert_eq!(entry.nid, 13); + assert_eq!(entry.short_name, "CN"); + } + + #[test] + fn test_find_by_name_short() { + let entry = find_by_name("CN").unwrap(); + assert_eq!(entry.nid, 13); + assert_eq!(entry.oid_string(), "2.5.4.3"); + } + + #[test] + fn test_find_by_name_long() { + let entry = find_by_name("commonName").unwrap(); + assert_eq!(entry.nid, 13); + assert_eq!(entry.short_name, "CN"); + } + + #[test] + fn test_find_by_name_case_insensitive() { + let entry = find_by_name("COMMONNAME").unwrap(); + assert_eq!(entry.nid, 13); + } + + #[test] + fn test_subject_alt_name() { + let entry = find_by_nid(85).unwrap(); + assert_eq!(entry.short_name, "subjectAltName"); + assert_eq!(entry.oid_string(), "2.5.29.17"); + } + + #[test] + fn test_server_auth_eku() { + let entry = find_by_nid(129).unwrap(); + assert_eq!(entry.short_name, "serverAuth"); + assert_eq!(entry.oid_string(), "1.3.6.1.5.5.7.3.1"); + } + + #[test] + fn test_no_duplicate_nids() { + let table = &*OID_TABLE; + assert_eq!( + table.entries.len(), + table.nid_to_idx.len(), + "Duplicate NIDs detected!" + ); + } + + #[test] + fn test_oid_count() { + let table = &*OID_TABLE; + // We should have 50+ OIDs defined + assert!( + table.entries.len() >= 50, + "Expected at least 50 OIDs, got {}", + table.entries.len() + ); + } +} From 88eca9693af8e958784c5cab1d45198a2d44d1a0 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 16 Nov 2025 16:25:45 +0200 Subject: [PATCH 360/819] Move `stdlib` -> `crates/stdlib` (#6268) --- Cargo.lock | 8 ++++++++ Cargo.toml | 3 +-- {stdlib => crates/stdlib}/Cargo.toml | 0 {stdlib => crates/stdlib}/build.rs | 0 {stdlib => crates/stdlib}/src/array.rs | 0 {stdlib => crates/stdlib}/src/binascii.rs | 0 {stdlib => crates/stdlib}/src/bisect.rs | 0 {stdlib => crates/stdlib}/src/blake2.rs | 0 {stdlib => crates/stdlib}/src/bz2.rs | 0 {stdlib => crates/stdlib}/src/cmath.rs | 0 {stdlib => crates/stdlib}/src/compression.rs | 0 {stdlib => crates/stdlib}/src/contextvars.rs | 0 {stdlib => crates/stdlib}/src/csv.rs | 0 {stdlib => crates/stdlib}/src/dis.rs | 0 {stdlib => crates/stdlib}/src/faulthandler.rs | 0 {stdlib => crates/stdlib}/src/fcntl.rs | 0 {stdlib => crates/stdlib}/src/gc.rs | 0 {stdlib => crates/stdlib}/src/grp.rs | 0 {stdlib => crates/stdlib}/src/hashlib.rs | 0 {stdlib => crates/stdlib}/src/json.rs | 0 {stdlib => crates/stdlib}/src/json/machinery.rs | 0 {stdlib => crates/stdlib}/src/lib.rs | 0 {stdlib => crates/stdlib}/src/locale.rs | 0 {stdlib => crates/stdlib}/src/lzma.rs | 0 {stdlib => crates/stdlib}/src/math.rs | 0 {stdlib => crates/stdlib}/src/md5.rs | 0 {stdlib => crates/stdlib}/src/mmap.rs | 0 {stdlib => crates/stdlib}/src/multiprocessing.rs | 0 {stdlib => crates/stdlib}/src/opcode.rs | 0 {stdlib => crates/stdlib}/src/openssl.rs | 0 {stdlib => crates/stdlib}/src/openssl/cert.rs | 0 {stdlib => crates/stdlib}/src/openssl/ssl_data_111.rs | 0 {stdlib => crates/stdlib}/src/openssl/ssl_data_300.rs | 0 {stdlib => crates/stdlib}/src/openssl/ssl_data_31.rs | 0 {stdlib => crates/stdlib}/src/overlapped.rs | 0 {stdlib => crates/stdlib}/src/posixsubprocess.rs | 0 {stdlib => crates/stdlib}/src/pyexpat.rs | 0 {stdlib => crates/stdlib}/src/pystruct.rs | 0 {stdlib => crates/stdlib}/src/random.rs | 0 {stdlib => crates/stdlib}/src/re.rs | 0 {stdlib => crates/stdlib}/src/resource.rs | 0 {stdlib => crates/stdlib}/src/scproxy.rs | 0 {stdlib => crates/stdlib}/src/select.rs | 0 {stdlib => crates/stdlib}/src/sha1.rs | 0 {stdlib => crates/stdlib}/src/sha256.rs | 0 {stdlib => crates/stdlib}/src/sha3.rs | 0 {stdlib => crates/stdlib}/src/sha512.rs | 0 {stdlib => crates/stdlib}/src/socket.rs | 0 {stdlib => crates/stdlib}/src/sqlite.rs | 0 {stdlib => crates/stdlib}/src/ssl.rs | 0 {stdlib => crates/stdlib}/src/ssl/cert.rs | 0 {stdlib => crates/stdlib}/src/ssl/compat.rs | 0 {stdlib => crates/stdlib}/src/ssl/oid.rs | 0 {stdlib => crates/stdlib}/src/statistics.rs | 0 {stdlib => crates/stdlib}/src/suggestions.rs | 0 {stdlib => crates/stdlib}/src/syslog.rs | 0 {stdlib => crates/stdlib}/src/termios.rs | 0 {stdlib => crates/stdlib}/src/tkinter.rs | 0 {stdlib => crates/stdlib}/src/unicodedata.rs | 0 {stdlib => crates/stdlib}/src/uuid.rs | 0 {stdlib => crates/stdlib}/src/zlib.rs | 0 61 files changed, 9 insertions(+), 2 deletions(-) rename {stdlib => crates/stdlib}/Cargo.toml (100%) rename {stdlib => crates/stdlib}/build.rs (100%) rename {stdlib => crates/stdlib}/src/array.rs (100%) rename {stdlib => crates/stdlib}/src/binascii.rs (100%) rename {stdlib => crates/stdlib}/src/bisect.rs (100%) rename {stdlib => crates/stdlib}/src/blake2.rs (100%) rename {stdlib => crates/stdlib}/src/bz2.rs (100%) rename {stdlib => crates/stdlib}/src/cmath.rs (100%) rename {stdlib => crates/stdlib}/src/compression.rs (100%) rename {stdlib => crates/stdlib}/src/contextvars.rs (100%) rename {stdlib => crates/stdlib}/src/csv.rs (100%) rename {stdlib => crates/stdlib}/src/dis.rs (100%) rename {stdlib => crates/stdlib}/src/faulthandler.rs (100%) rename {stdlib => crates/stdlib}/src/fcntl.rs (100%) rename {stdlib => crates/stdlib}/src/gc.rs (100%) rename {stdlib => crates/stdlib}/src/grp.rs (100%) rename {stdlib => crates/stdlib}/src/hashlib.rs (100%) rename {stdlib => crates/stdlib}/src/json.rs (100%) rename {stdlib => crates/stdlib}/src/json/machinery.rs (100%) rename {stdlib => crates/stdlib}/src/lib.rs (100%) rename {stdlib => crates/stdlib}/src/locale.rs (100%) rename {stdlib => crates/stdlib}/src/lzma.rs (100%) rename {stdlib => crates/stdlib}/src/math.rs (100%) rename {stdlib => crates/stdlib}/src/md5.rs (100%) rename {stdlib => crates/stdlib}/src/mmap.rs (100%) rename {stdlib => crates/stdlib}/src/multiprocessing.rs (100%) rename {stdlib => crates/stdlib}/src/opcode.rs (100%) rename {stdlib => crates/stdlib}/src/openssl.rs (100%) rename {stdlib => crates/stdlib}/src/openssl/cert.rs (100%) rename {stdlib => crates/stdlib}/src/openssl/ssl_data_111.rs (100%) rename {stdlib => crates/stdlib}/src/openssl/ssl_data_300.rs (100%) rename {stdlib => crates/stdlib}/src/openssl/ssl_data_31.rs (100%) rename {stdlib => crates/stdlib}/src/overlapped.rs (100%) rename {stdlib => crates/stdlib}/src/posixsubprocess.rs (100%) rename {stdlib => crates/stdlib}/src/pyexpat.rs (100%) rename {stdlib => crates/stdlib}/src/pystruct.rs (100%) rename {stdlib => crates/stdlib}/src/random.rs (100%) rename {stdlib => crates/stdlib}/src/re.rs (100%) rename {stdlib => crates/stdlib}/src/resource.rs (100%) rename {stdlib => crates/stdlib}/src/scproxy.rs (100%) rename {stdlib => crates/stdlib}/src/select.rs (100%) rename {stdlib => crates/stdlib}/src/sha1.rs (100%) rename {stdlib => crates/stdlib}/src/sha256.rs (100%) rename {stdlib => crates/stdlib}/src/sha3.rs (100%) rename {stdlib => crates/stdlib}/src/sha512.rs (100%) rename {stdlib => crates/stdlib}/src/socket.rs (100%) rename {stdlib => crates/stdlib}/src/sqlite.rs (100%) rename {stdlib => crates/stdlib}/src/ssl.rs (100%) rename {stdlib => crates/stdlib}/src/ssl/cert.rs (100%) rename {stdlib => crates/stdlib}/src/ssl/compat.rs (100%) rename {stdlib => crates/stdlib}/src/ssl/oid.rs (100%) rename {stdlib => crates/stdlib}/src/statistics.rs (100%) rename {stdlib => crates/stdlib}/src/suggestions.rs (100%) rename {stdlib => crates/stdlib}/src/syslog.rs (100%) rename {stdlib => crates/stdlib}/src/termios.rs (100%) rename {stdlib => crates/stdlib}/src/tkinter.rs (100%) rename {stdlib => crates/stdlib}/src/unicodedata.rs (100%) rename {stdlib => crates/stdlib}/src/uuid.rs (100%) rename {stdlib => crates/stdlib}/src/zlib.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index cec9f7afb47..4f749bc49b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3025,6 +3025,14 @@ dependencies = [ "rustpython-wtf8", ] +[[package]] +name = "rustpython-compiler-source" +version = "0.5.0+deprecated" +dependencies = [ + "ruff_source_file", + "ruff_text_size", +] + [[package]] name = "rustpython-derive" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index c1ee8eaa3d5..0773e8a410a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,7 +124,6 @@ template = "installer-config/installer.wxs" resolver = "2" members = [ ".", - "stdlib", "wasm/lib", "crates/*", ] @@ -148,7 +147,7 @@ rustpython-jit = { path = "crates/jit", version = "0.4.0" } rustpython-literal = { path = "crates/literal", version = "0.4.0" } rustpython-vm = { path = "crates/vm", default-features = false, version = "0.4.0" } rustpython-pylib = { path = "crates/pylib", version = "0.4.0" } -rustpython-stdlib = { path = "stdlib", default-features = false, version = "0.4.0" } +rustpython-stdlib = { path = "crates/stdlib", default-features = false, version = "0.4.0" } rustpython-sre_engine = { path = "crates/sre_engine", version = "0.4.0" } rustpython-wtf8 = { path = "crates/wtf8", version = "0.4.0" } rustpython-doc = { path = "crates/doc", version = "0.4.0" } diff --git a/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml similarity index 100% rename from stdlib/Cargo.toml rename to crates/stdlib/Cargo.toml diff --git a/stdlib/build.rs b/crates/stdlib/build.rs similarity index 100% rename from stdlib/build.rs rename to crates/stdlib/build.rs diff --git a/stdlib/src/array.rs b/crates/stdlib/src/array.rs similarity index 100% rename from stdlib/src/array.rs rename to crates/stdlib/src/array.rs diff --git a/stdlib/src/binascii.rs b/crates/stdlib/src/binascii.rs similarity index 100% rename from stdlib/src/binascii.rs rename to crates/stdlib/src/binascii.rs diff --git a/stdlib/src/bisect.rs b/crates/stdlib/src/bisect.rs similarity index 100% rename from stdlib/src/bisect.rs rename to crates/stdlib/src/bisect.rs diff --git a/stdlib/src/blake2.rs b/crates/stdlib/src/blake2.rs similarity index 100% rename from stdlib/src/blake2.rs rename to crates/stdlib/src/blake2.rs diff --git a/stdlib/src/bz2.rs b/crates/stdlib/src/bz2.rs similarity index 100% rename from stdlib/src/bz2.rs rename to crates/stdlib/src/bz2.rs diff --git a/stdlib/src/cmath.rs b/crates/stdlib/src/cmath.rs similarity index 100% rename from stdlib/src/cmath.rs rename to crates/stdlib/src/cmath.rs diff --git a/stdlib/src/compression.rs b/crates/stdlib/src/compression.rs similarity index 100% rename from stdlib/src/compression.rs rename to crates/stdlib/src/compression.rs diff --git a/stdlib/src/contextvars.rs b/crates/stdlib/src/contextvars.rs similarity index 100% rename from stdlib/src/contextvars.rs rename to crates/stdlib/src/contextvars.rs diff --git a/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs similarity index 100% rename from stdlib/src/csv.rs rename to crates/stdlib/src/csv.rs diff --git a/stdlib/src/dis.rs b/crates/stdlib/src/dis.rs similarity index 100% rename from stdlib/src/dis.rs rename to crates/stdlib/src/dis.rs diff --git a/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs similarity index 100% rename from stdlib/src/faulthandler.rs rename to crates/stdlib/src/faulthandler.rs diff --git a/stdlib/src/fcntl.rs b/crates/stdlib/src/fcntl.rs similarity index 100% rename from stdlib/src/fcntl.rs rename to crates/stdlib/src/fcntl.rs diff --git a/stdlib/src/gc.rs b/crates/stdlib/src/gc.rs similarity index 100% rename from stdlib/src/gc.rs rename to crates/stdlib/src/gc.rs diff --git a/stdlib/src/grp.rs b/crates/stdlib/src/grp.rs similarity index 100% rename from stdlib/src/grp.rs rename to crates/stdlib/src/grp.rs diff --git a/stdlib/src/hashlib.rs b/crates/stdlib/src/hashlib.rs similarity index 100% rename from stdlib/src/hashlib.rs rename to crates/stdlib/src/hashlib.rs diff --git a/stdlib/src/json.rs b/crates/stdlib/src/json.rs similarity index 100% rename from stdlib/src/json.rs rename to crates/stdlib/src/json.rs diff --git a/stdlib/src/json/machinery.rs b/crates/stdlib/src/json/machinery.rs similarity index 100% rename from stdlib/src/json/machinery.rs rename to crates/stdlib/src/json/machinery.rs diff --git a/stdlib/src/lib.rs b/crates/stdlib/src/lib.rs similarity index 100% rename from stdlib/src/lib.rs rename to crates/stdlib/src/lib.rs diff --git a/stdlib/src/locale.rs b/crates/stdlib/src/locale.rs similarity index 100% rename from stdlib/src/locale.rs rename to crates/stdlib/src/locale.rs diff --git a/stdlib/src/lzma.rs b/crates/stdlib/src/lzma.rs similarity index 100% rename from stdlib/src/lzma.rs rename to crates/stdlib/src/lzma.rs diff --git a/stdlib/src/math.rs b/crates/stdlib/src/math.rs similarity index 100% rename from stdlib/src/math.rs rename to crates/stdlib/src/math.rs diff --git a/stdlib/src/md5.rs b/crates/stdlib/src/md5.rs similarity index 100% rename from stdlib/src/md5.rs rename to crates/stdlib/src/md5.rs diff --git a/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs similarity index 100% rename from stdlib/src/mmap.rs rename to crates/stdlib/src/mmap.rs diff --git a/stdlib/src/multiprocessing.rs b/crates/stdlib/src/multiprocessing.rs similarity index 100% rename from stdlib/src/multiprocessing.rs rename to crates/stdlib/src/multiprocessing.rs diff --git a/stdlib/src/opcode.rs b/crates/stdlib/src/opcode.rs similarity index 100% rename from stdlib/src/opcode.rs rename to crates/stdlib/src/opcode.rs diff --git a/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs similarity index 100% rename from stdlib/src/openssl.rs rename to crates/stdlib/src/openssl.rs diff --git a/stdlib/src/openssl/cert.rs b/crates/stdlib/src/openssl/cert.rs similarity index 100% rename from stdlib/src/openssl/cert.rs rename to crates/stdlib/src/openssl/cert.rs diff --git a/stdlib/src/openssl/ssl_data_111.rs b/crates/stdlib/src/openssl/ssl_data_111.rs similarity index 100% rename from stdlib/src/openssl/ssl_data_111.rs rename to crates/stdlib/src/openssl/ssl_data_111.rs diff --git a/stdlib/src/openssl/ssl_data_300.rs b/crates/stdlib/src/openssl/ssl_data_300.rs similarity index 100% rename from stdlib/src/openssl/ssl_data_300.rs rename to crates/stdlib/src/openssl/ssl_data_300.rs diff --git a/stdlib/src/openssl/ssl_data_31.rs b/crates/stdlib/src/openssl/ssl_data_31.rs similarity index 100% rename from stdlib/src/openssl/ssl_data_31.rs rename to crates/stdlib/src/openssl/ssl_data_31.rs diff --git a/stdlib/src/overlapped.rs b/crates/stdlib/src/overlapped.rs similarity index 100% rename from stdlib/src/overlapped.rs rename to crates/stdlib/src/overlapped.rs diff --git a/stdlib/src/posixsubprocess.rs b/crates/stdlib/src/posixsubprocess.rs similarity index 100% rename from stdlib/src/posixsubprocess.rs rename to crates/stdlib/src/posixsubprocess.rs diff --git a/stdlib/src/pyexpat.rs b/crates/stdlib/src/pyexpat.rs similarity index 100% rename from stdlib/src/pyexpat.rs rename to crates/stdlib/src/pyexpat.rs diff --git a/stdlib/src/pystruct.rs b/crates/stdlib/src/pystruct.rs similarity index 100% rename from stdlib/src/pystruct.rs rename to crates/stdlib/src/pystruct.rs diff --git a/stdlib/src/random.rs b/crates/stdlib/src/random.rs similarity index 100% rename from stdlib/src/random.rs rename to crates/stdlib/src/random.rs diff --git a/stdlib/src/re.rs b/crates/stdlib/src/re.rs similarity index 100% rename from stdlib/src/re.rs rename to crates/stdlib/src/re.rs diff --git a/stdlib/src/resource.rs b/crates/stdlib/src/resource.rs similarity index 100% rename from stdlib/src/resource.rs rename to crates/stdlib/src/resource.rs diff --git a/stdlib/src/scproxy.rs b/crates/stdlib/src/scproxy.rs similarity index 100% rename from stdlib/src/scproxy.rs rename to crates/stdlib/src/scproxy.rs diff --git a/stdlib/src/select.rs b/crates/stdlib/src/select.rs similarity index 100% rename from stdlib/src/select.rs rename to crates/stdlib/src/select.rs diff --git a/stdlib/src/sha1.rs b/crates/stdlib/src/sha1.rs similarity index 100% rename from stdlib/src/sha1.rs rename to crates/stdlib/src/sha1.rs diff --git a/stdlib/src/sha256.rs b/crates/stdlib/src/sha256.rs similarity index 100% rename from stdlib/src/sha256.rs rename to crates/stdlib/src/sha256.rs diff --git a/stdlib/src/sha3.rs b/crates/stdlib/src/sha3.rs similarity index 100% rename from stdlib/src/sha3.rs rename to crates/stdlib/src/sha3.rs diff --git a/stdlib/src/sha512.rs b/crates/stdlib/src/sha512.rs similarity index 100% rename from stdlib/src/sha512.rs rename to crates/stdlib/src/sha512.rs diff --git a/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs similarity index 100% rename from stdlib/src/socket.rs rename to crates/stdlib/src/socket.rs diff --git a/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs similarity index 100% rename from stdlib/src/sqlite.rs rename to crates/stdlib/src/sqlite.rs diff --git a/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs similarity index 100% rename from stdlib/src/ssl.rs rename to crates/stdlib/src/ssl.rs diff --git a/stdlib/src/ssl/cert.rs b/crates/stdlib/src/ssl/cert.rs similarity index 100% rename from stdlib/src/ssl/cert.rs rename to crates/stdlib/src/ssl/cert.rs diff --git a/stdlib/src/ssl/compat.rs b/crates/stdlib/src/ssl/compat.rs similarity index 100% rename from stdlib/src/ssl/compat.rs rename to crates/stdlib/src/ssl/compat.rs diff --git a/stdlib/src/ssl/oid.rs b/crates/stdlib/src/ssl/oid.rs similarity index 100% rename from stdlib/src/ssl/oid.rs rename to crates/stdlib/src/ssl/oid.rs diff --git a/stdlib/src/statistics.rs b/crates/stdlib/src/statistics.rs similarity index 100% rename from stdlib/src/statistics.rs rename to crates/stdlib/src/statistics.rs diff --git a/stdlib/src/suggestions.rs b/crates/stdlib/src/suggestions.rs similarity index 100% rename from stdlib/src/suggestions.rs rename to crates/stdlib/src/suggestions.rs diff --git a/stdlib/src/syslog.rs b/crates/stdlib/src/syslog.rs similarity index 100% rename from stdlib/src/syslog.rs rename to crates/stdlib/src/syslog.rs diff --git a/stdlib/src/termios.rs b/crates/stdlib/src/termios.rs similarity index 100% rename from stdlib/src/termios.rs rename to crates/stdlib/src/termios.rs diff --git a/stdlib/src/tkinter.rs b/crates/stdlib/src/tkinter.rs similarity index 100% rename from stdlib/src/tkinter.rs rename to crates/stdlib/src/tkinter.rs diff --git a/stdlib/src/unicodedata.rs b/crates/stdlib/src/unicodedata.rs similarity index 100% rename from stdlib/src/unicodedata.rs rename to crates/stdlib/src/unicodedata.rs diff --git a/stdlib/src/uuid.rs b/crates/stdlib/src/uuid.rs similarity index 100% rename from stdlib/src/uuid.rs rename to crates/stdlib/src/uuid.rs diff --git a/stdlib/src/zlib.rs b/crates/stdlib/src/zlib.rs similarity index 100% rename from stdlib/src/zlib.rs rename to crates/stdlib/src/zlib.rs From 916d3ba94be74e82427c378ab604b87ea5806f76 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 17 Nov 2025 11:47:05 +0200 Subject: [PATCH 361/819] Move `wasm/lib` -> `crates/wasm` (#6280) --- .github/workflows/ci.yaml | 2 +- Cargo.toml | 1 - {wasm/lib => crates/wasm}/.cargo/config.toml | 0 {wasm/lib => crates/wasm}/Cargo.toml | 0 {wasm/lib => crates/wasm}/Lib/_microdistlib.py | 0 {wasm/lib => crates/wasm}/Lib/asyncweb.py | 0 {wasm/lib => crates/wasm}/Lib/browser.py | 0 {wasm/lib => crates/wasm}/Lib/whlimport.py | 0 {wasm/lib => crates/wasm}/README.md | 0 {wasm/lib => crates/wasm}/src/browser_module.rs | 0 {wasm/lib => crates/wasm}/src/convert.rs | 0 {wasm/lib => crates/wasm}/src/js_module.rs | 0 {wasm/lib => crates/wasm}/src/lib.rs | 0 {wasm/lib => crates/wasm}/src/vm_class.rs | 0 {wasm/lib => crates/wasm}/src/wasm_builtins.rs | 0 wasm/demo/webpack.config.js | 4 ++-- wasm/notebook/webpack.config.js | 2 +- 17 files changed, 4 insertions(+), 5 deletions(-) rename {wasm/lib => crates/wasm}/.cargo/config.toml (100%) rename {wasm/lib => crates/wasm}/Cargo.toml (100%) rename {wasm/lib => crates/wasm}/Lib/_microdistlib.py (100%) rename {wasm/lib => crates/wasm}/Lib/asyncweb.py (100%) rename {wasm/lib => crates/wasm}/Lib/browser.py (100%) rename {wasm/lib => crates/wasm}/Lib/whlimport.py (100%) rename {wasm/lib => crates/wasm}/README.md (100%) rename {wasm/lib => crates/wasm}/src/browser_module.rs (100%) rename {wasm/lib => crates/wasm}/src/convert.rs (100%) rename {wasm/lib => crates/wasm}/src/js_module.rs (100%) rename {wasm/lib => crates/wasm}/src/lib.rs (100%) rename {wasm/lib => crates/wasm}/src/vm_class.rs (100%) rename {wasm/lib => crates/wasm}/src/wasm_builtins.rs (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6963ee5f7c4..fe45a1d71bb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -316,7 +316,7 @@ jobs: with: components: clippy - name: run clippy on wasm - run: cargo clippy --manifest-path=wasm/lib/Cargo.toml -- -Dwarnings + run: cargo clippy --manifest-path=crates/wasm/Cargo.toml -- -Dwarnings - uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} diff --git a/Cargo.toml b/Cargo.toml index 0773e8a410a..53d1eb6ea2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,7 +124,6 @@ template = "installer-config/installer.wxs" resolver = "2" members = [ ".", - "wasm/lib", "crates/*", ] diff --git a/wasm/lib/.cargo/config.toml b/crates/wasm/.cargo/config.toml similarity index 100% rename from wasm/lib/.cargo/config.toml rename to crates/wasm/.cargo/config.toml diff --git a/wasm/lib/Cargo.toml b/crates/wasm/Cargo.toml similarity index 100% rename from wasm/lib/Cargo.toml rename to crates/wasm/Cargo.toml diff --git a/wasm/lib/Lib/_microdistlib.py b/crates/wasm/Lib/_microdistlib.py similarity index 100% rename from wasm/lib/Lib/_microdistlib.py rename to crates/wasm/Lib/_microdistlib.py diff --git a/wasm/lib/Lib/asyncweb.py b/crates/wasm/Lib/asyncweb.py similarity index 100% rename from wasm/lib/Lib/asyncweb.py rename to crates/wasm/Lib/asyncweb.py diff --git a/wasm/lib/Lib/browser.py b/crates/wasm/Lib/browser.py similarity index 100% rename from wasm/lib/Lib/browser.py rename to crates/wasm/Lib/browser.py diff --git a/wasm/lib/Lib/whlimport.py b/crates/wasm/Lib/whlimport.py similarity index 100% rename from wasm/lib/Lib/whlimport.py rename to crates/wasm/Lib/whlimport.py diff --git a/wasm/lib/README.md b/crates/wasm/README.md similarity index 100% rename from wasm/lib/README.md rename to crates/wasm/README.md diff --git a/wasm/lib/src/browser_module.rs b/crates/wasm/src/browser_module.rs similarity index 100% rename from wasm/lib/src/browser_module.rs rename to crates/wasm/src/browser_module.rs diff --git a/wasm/lib/src/convert.rs b/crates/wasm/src/convert.rs similarity index 100% rename from wasm/lib/src/convert.rs rename to crates/wasm/src/convert.rs diff --git a/wasm/lib/src/js_module.rs b/crates/wasm/src/js_module.rs similarity index 100% rename from wasm/lib/src/js_module.rs rename to crates/wasm/src/js_module.rs diff --git a/wasm/lib/src/lib.rs b/crates/wasm/src/lib.rs similarity index 100% rename from wasm/lib/src/lib.rs rename to crates/wasm/src/lib.rs diff --git a/wasm/lib/src/vm_class.rs b/crates/wasm/src/vm_class.rs similarity index 100% rename from wasm/lib/src/vm_class.rs rename to crates/wasm/src/vm_class.rs diff --git a/wasm/lib/src/wasm_builtins.rs b/crates/wasm/src/wasm_builtins.rs similarity index 100% rename from wasm/lib/src/wasm_builtins.rs rename to crates/wasm/src/wasm_builtins.rs diff --git a/wasm/demo/webpack.config.js b/wasm/demo/webpack.config.js index 75fb6786e66..6c798d2ca4f 100644 --- a/wasm/demo/webpack.config.js +++ b/wasm/demo/webpack.config.js @@ -18,7 +18,7 @@ module.exports = (env = {}) => { alias: { rustpython: path.resolve( __dirname, - env.rustpythonPkg || '../lib/pkg', + env.rustpythonPkg || '../../crates/wasm/pkg', ), }, }, @@ -65,7 +65,7 @@ module.exports = (env = {}) => { if (!env.noWasmPack) { config.plugins.push( new WasmPackPlugin({ - crateDirectory: path.join(__dirname, '../lib'), + crateDirectory: path.join(__dirname, '../../crates/wasm'), }), ); } diff --git a/wasm/notebook/webpack.config.js b/wasm/notebook/webpack.config.js index ca19f3384aa..a3e3f8ef713 100644 --- a/wasm/notebook/webpack.config.js +++ b/wasm/notebook/webpack.config.js @@ -18,7 +18,7 @@ module.exports = (env = {}) => { alias: { rustpython: path.resolve( __dirname, - env.rustpythonPkg || '../lib/pkg', + env.rustpythonPkg || '../../crates/wasm/pkg', ), }, }, From 5fb5db961764b2043cb6fcd77feebe4ebcf42c61 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Mon, 17 Nov 2025 18:58:07 +0900 Subject: [PATCH 362/819] relocate wasm test crate under example_projects --- .../wasm32_without_js/rustpython-without-js}/.cargo/config.toml | 0 .../wasm32_without_js/rustpython-without-js}/Cargo.toml | 2 +- .../wasm32_without_js/rustpython-without-js}/README.md | 0 .../wasm32_without_js/rustpython-without-js}/src/lib.rs | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename {wasm/wasm-unknown-test => example_projects/wasm32_without_js/rustpython-without-js}/.cargo/config.toml (100%) rename {wasm/wasm-unknown-test => example_projects/wasm32_without_js/rustpython-without-js}/Cargo.toml (88%) rename {wasm/wasm-unknown-test => example_projects/wasm32_without_js/rustpython-without-js}/README.md (100%) rename {wasm/wasm-unknown-test => example_projects/wasm32_without_js/rustpython-without-js}/src/lib.rs (100%) diff --git a/wasm/wasm-unknown-test/.cargo/config.toml b/example_projects/wasm32_without_js/rustpython-without-js/.cargo/config.toml similarity index 100% rename from wasm/wasm-unknown-test/.cargo/config.toml rename to example_projects/wasm32_without_js/rustpython-without-js/.cargo/config.toml diff --git a/wasm/wasm-unknown-test/Cargo.toml b/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml similarity index 88% rename from wasm/wasm-unknown-test/Cargo.toml rename to example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml index 277a9810b99..78c64eabe63 100644 --- a/wasm/wasm-unknown-test/Cargo.toml +++ b/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wasm-unknown-test" +name = "rustpython-without-js" version = "0.1.0" edition = "2021" diff --git a/wasm/wasm-unknown-test/README.md b/example_projects/wasm32_without_js/rustpython-without-js/README.md similarity index 100% rename from wasm/wasm-unknown-test/README.md rename to example_projects/wasm32_without_js/rustpython-without-js/README.md diff --git a/wasm/wasm-unknown-test/src/lib.rs b/example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs similarity index 100% rename from wasm/wasm-unknown-test/src/lib.rs rename to example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs From eac8968f84c5709f92fd9057b559241f7f79a330 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Mon, 17 Nov 2025 20:30:43 +0900 Subject: [PATCH 363/819] Add wasm runtime and fix the example code to actually run Co-Authored-By: Valentyn Faychuk <valy@faychuk.com> Co-Authored-By: Lee Dogeon <dev.moreal@gmail.com> --- .cspell.dict/rust-more.txt | 1 + example_projects/wasm32_without_js/.gitignore | 2 + example_projects/wasm32_without_js/README.md | 18 +++ .../rustpython-without-js/Cargo.toml | 4 +- .../rustpython-without-js/src/lib.rs | 58 ++++++-- .../wasm32_without_js/wasm-runtime/.gitignore | 4 + .../wasm32_without_js/wasm-runtime/Cargo.toml | 9 ++ .../wasm32_without_js/wasm-runtime/README.md | 19 +++ .../wasm-runtime/src/main.rs | 133 ++++++++++++++++++ 9 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 example_projects/wasm32_without_js/.gitignore create mode 100644 example_projects/wasm32_without_js/README.md create mode 100644 example_projects/wasm32_without_js/wasm-runtime/.gitignore create mode 100644 example_projects/wasm32_without_js/wasm-runtime/Cargo.toml create mode 100644 example_projects/wasm32_without_js/wasm-runtime/README.md create mode 100644 example_projects/wasm32_without_js/wasm-runtime/src/main.rs diff --git a/.cspell.dict/rust-more.txt b/.cspell.dict/rust-more.txt index f27e53bd6ed..ff2013e81a7 100644 --- a/.cspell.dict/rust-more.txt +++ b/.cspell.dict/rust-more.txt @@ -82,6 +82,7 @@ unsync wasip1 wasip2 wasmbind +wasmer wasmtime widestring winapi diff --git a/example_projects/wasm32_without_js/.gitignore b/example_projects/wasm32_without_js/.gitignore new file mode 100644 index 00000000000..50a623b5daa --- /dev/null +++ b/example_projects/wasm32_without_js/.gitignore @@ -0,0 +1,2 @@ +*/target/ +*/Cargo.lock diff --git a/example_projects/wasm32_without_js/README.md b/example_projects/wasm32_without_js/README.md new file mode 100644 index 00000000000..67fef3fba47 --- /dev/null +++ b/example_projects/wasm32_without_js/README.md @@ -0,0 +1,18 @@ +# RustPython wasm32 build without JS + +To test, build rustpython to wasm32-unknown-unknown target first. + +```shell +cd rustpython-without-js # due to `.cargo/config.toml` +cargo build +cd .. +``` + +Then there will be `rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm` file. + +Now we can run the wasm file with wasm runtime: + +```shell +cargo run --release --manifest-path wasm-runtime/Cargo.toml rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm +``` + diff --git a/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml b/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml index 78c64eabe63..f987fe94a24 100644 --- a/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml +++ b/example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "rustpython-without-js" version = "0.1.0" -edition = "2021" +edition = "2024" [lib] crate-type = ["cdylib"] [dependencies] getrandom = "0.3" -rustpython-vm = { path = "../../crates/vm", default-features = false, features = ["compiler"] } +rustpython-vm = { path = "../../../crates/vm", default-features = false, features = ["compiler"] } [workspace] diff --git a/example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs b/example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs index aae922864dd..0a8695fd7fb 100644 --- a/example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs +++ b/example_projects/wasm32_without_js/rustpython-without-js/src/lib.rs @@ -1,13 +1,50 @@ -use rustpython_vm::{Interpreter, eval}; +use rustpython_vm::{Interpreter}; + +unsafe extern "C" { + fn kv_get(kp: i32, kl: i32, vp: i32, vl: i32) -> i32; + + /// kp and kl are the key pointer and length in wasm memory, vp and vl are for the value + fn kv_put(kp: i32, kl: i32, vp: i32, vl: i32) -> i32; + + fn print(p: i32, l: i32) -> i32; +} #[unsafe(no_mangle)] -pub unsafe extern "C" fn eval(s: *const u8, l: usize) -> u32 { - let src = std::slice::from_raw_parts(s, l); - let src = std::str::from_utf8(src).unwrap(); - Interpreter::without_stdlib(Default::default()).enter(|vm| { - let res = eval::eval(vm, src, vm.new_scope_with_builtins(), "<string>").unwrap(); - res.try_into_value(vm).unwrap() - }) +pub unsafe extern "C" fn eval(s: *const u8, l: usize) -> i32 { + // let src = unsafe { std::slice::from_raw_parts(s, l) }; + // let src = std::str::from_utf8(src).unwrap(); + // TODO: use src + let src = "1 + 3"; + + // 2. Execute Python code + let interpreter = Interpreter::without_stdlib(Default::default()); + let result = interpreter.enter(|vm| { + let scope = vm.new_scope_with_builtins(); + let res = match vm.run_block_expr(scope, src) { + Ok(val) => val, + Err(_) => return Err(-1), // Python execution error + }; + let repr_str = match res.repr(vm) { + Ok(repr) => repr.as_str().to_string(), + Err(_) => return Err(-1), // Failed to get string representation + }; + Ok(repr_str) + }); + let result = match result { + Ok(r) => r, + Err(code) => return code, + }; + + let msg = format!("eval result: {result}"); + + unsafe { + print( + msg.as_str().as_ptr() as usize as i32, + msg.len() as i32, + ) + }; + + 0 } #[unsafe(no_mangle)] @@ -15,5 +52,8 @@ unsafe extern "Rust" fn __getrandom_v03_custom( _dest: *mut u8, _len: usize, ) -> Result<(), getrandom::Error> { - Err(getrandom::Error::UNSUPPORTED) + // Err(getrandom::Error::UNSUPPORTED) + + // WARNING: This function **MUST** perform proper getrandom + Ok(()) } diff --git a/example_projects/wasm32_without_js/wasm-runtime/.gitignore b/example_projects/wasm32_without_js/wasm-runtime/.gitignore new file mode 100644 index 00000000000..2e2101b5066 --- /dev/null +++ b/example_projects/wasm32_without_js/wasm-runtime/.gitignore @@ -0,0 +1,4 @@ +*.wasm +target +Cargo.lock +!wasm/rustpython.wasm diff --git a/example_projects/wasm32_without_js/wasm-runtime/Cargo.toml b/example_projects/wasm32_without_js/wasm-runtime/Cargo.toml new file mode 100644 index 00000000000..a1d0de51719 --- /dev/null +++ b/example_projects/wasm32_without_js/wasm-runtime/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "wasm-runtime" +version = "0.1.0" +edition = "2024" + +[dependencies] +wasmer = "6.1.0" + +[workspace] \ No newline at end of file diff --git a/example_projects/wasm32_without_js/wasm-runtime/README.md b/example_projects/wasm32_without_js/wasm-runtime/README.md new file mode 100644 index 00000000000..2fa2f9e119a --- /dev/null +++ b/example_projects/wasm32_without_js/wasm-runtime/README.md @@ -0,0 +1,19 @@ +# Simple WASM Runtime + +WebAssembly runtime POC with wasmer with HashMap-based KV store. +First make sure to install wat2wasm and rust. + +```bash +# following command installs rust +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +cargo run --release <wasm binary> +``` + +## WASM binary requirements + +Entry point is `eval(code_ptr: i32, code_len: i32) -> i32`, following are exported functions, on error return -1: + +- `kv_put(key_ptr: i32, key_len: i32, val_ptr: i32, val_len: i32) -> i32` +- `kv_get(key_ptr: i32, key_len: i32, val_ptr: i32, val_len: i32) -> i32` +- `print(msg_ptr: i32, msg_len: i32) -> i32` diff --git a/example_projects/wasm32_without_js/wasm-runtime/src/main.rs b/example_projects/wasm32_without_js/wasm-runtime/src/main.rs new file mode 100644 index 00000000000..8ca7d581ce4 --- /dev/null +++ b/example_projects/wasm32_without_js/wasm-runtime/src/main.rs @@ -0,0 +1,133 @@ +use std::collections::HashMap; +use wasmer::{ + Function, FunctionEnv, FunctionEnvMut, Instance, Memory, Module, Store, Value, imports, +}; + +struct Ctx { + kv: HashMap<Vec<u8>, Vec<u8>>, + mem: Option<Memory>, +} + +/// kp and kl are the key pointer and length in wasm memory, vp and vl are for the return value +/// if read value is bigger than vl then it will be truncated to vl, returns read bytes +fn kv_get(mut ctx: FunctionEnvMut<Ctx>, kp: i32, kl: i32, vp: i32, vl: i32) -> i32 { + let (c, s) = ctx.data_and_store_mut(); + let mut key = vec![0u8; kl as usize]; + if c.mem + .as_ref() + .unwrap() + .view(&s) + .read(kp as u64, &mut key) + .is_err() + { + return -1; + } + match c.kv.get(&key) { + Some(val) => { + let len = val.len().min(vl as usize); + if c.mem + .as_ref() + .unwrap() + .view(&s) + .write(vp as u64, &val[..len]) + .is_err() + { + return -1; + } + len as i32 + } + None => 0, + } +} + +/// kp and kl are the key pointer and length in wasm memory, vp and vl are for the value +fn kv_put(mut ctx: FunctionEnvMut<Ctx>, kp: i32, kl: i32, vp: i32, vl: i32) -> i32 { + let (c, s) = ctx.data_and_store_mut(); + let mut key = vec![0u8; kl as usize]; + let mut val = vec![0u8; vl as usize]; + let m = c.mem.as_ref().unwrap().view(&s); + if m.read(kp as u64, &mut key).is_err() || m.read(vp as u64, &mut val).is_err() { + return -1; + } + c.kv.insert(key, val); + 0 +} + +// // p and l are the buffer pointer and length in wasm memory. +// fn get_code(mut ctx:FunctionEnvMut<Ctx>, p: i32, l: i32) -> i32 { +// let file_name = std::env::args().nth(2).expect("file_name is not given"); +// let code : String = std::fs::read_to_string(file_name).expect("file read failed"); +// if code.len() > l as usize { +// eprintln!("code is too long"); +// return -1; +// } + +// let (c, s) = ctx.data_and_store_mut(); +// let m = c.mem.as_ref().unwrap().view(&s); +// if m.write(p as u64, code.as_bytes()).is_err() { +// return -2; +// } + +// 0 +// } + +// p and l are the message pointer and length in wasm memory. +fn print(mut ctx: FunctionEnvMut<Ctx>, p: i32, l: i32) -> i32 { + let (c, s) = ctx.data_and_store_mut(); + let mut msg = vec![0u8; l as usize]; + let m = c.mem.as_ref().unwrap().view(&s); + if m.read(p as u64, &mut msg).is_err() { + return -1; + } + let s = std::str::from_utf8(&msg).expect("print got non-utf8 str"); + println!("{s}"); + 0 +} + +fn main() { + let mut store = Store::default(); + let module = Module::new( + &store, + &std::fs::read(&std::env::args().nth(1).unwrap()).unwrap(), + ) + .unwrap(); + + // Prepare initial KV store with Python code + let mut initial_kv = HashMap::new(); + initial_kv.insert( + b"code".to_vec(), + b"a=10;b='str';f'{a}{b}'".to_vec(), // Python code to execute + ); + + let env = FunctionEnv::new( + &mut store, + Ctx { + kv: initial_kv, + mem: None, + }, + ); + let imports = imports! { + "env" => { + "kv_get" => Function::new_typed_with_env(&mut store, &env, kv_get), + "kv_put" => Function::new_typed_with_env(&mut store, &env, kv_put), + // "get_code" => Function::new_typed_with_env(&mut store, &env, get_code), + "print" => Function::new_typed_with_env(&mut store, &env, print), + } + }; + let inst = Instance::new(&mut store, &module, &imports).unwrap(); + env.as_mut(&mut store).mem = inst.exports.get_memory("memory").ok().cloned(); + let res = inst + .exports + .get_function("eval") + .unwrap() + // TODO: actually pass source code + .call(&mut store, &[wasmer::Value::I32(0), wasmer::Value::I32(0)]) + .unwrap(); + println!( + "Result: {}", + match res[0] { + Value::I32(v) => v, + _ => -1, + } + ); +} From 9134cca17b8f8059bcbd7de6cbed93544146060c Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Mon, 17 Nov 2025 20:38:19 +0900 Subject: [PATCH 364/819] Make CI to run rustpython-without-js test --- .github/workflows/ci.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fe45a1d71bb..95a1ac98d62 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -404,11 +404,13 @@ jobs: with: { wabt-version: "1.0.36" } - name: check wasm32-unknown without js run: | - cd wasm/wasm-unknown-test - cargo build --release --verbose - if wasm-objdump -xj Import target/wasm32-unknown-unknown/release/wasm_unknown_test.wasm; then - echo "ERROR: wasm32-unknown module expects imports from the host environment" >2 + cd example_projects/wasm32_without_js/rustpython-without-js + cargo build + cd .. + if wasm-objdump -xj Import rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm; then + echo "ERROR: wasm32-unknown module expects imports from the host environment" >&2 fi + cargo run --release --manifest-path wasm-runtime/Cargo.toml rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm - name: build notebook demo if: github.ref == 'refs/heads/release' run: | From f7ddcd2795ee5997d3ee9ab2cbfdb11385e9802d Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:00:41 +0200 Subject: [PATCH 365/819] Break after annotation future found (#6284) --- crates/codegen/src/symboltable.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index 0464e09c138..3c8454b9e22 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -736,12 +736,10 @@ impl SymbolTableBuilder { if let Stmt::ImportFrom(StmtImportFrom { module, names, .. }) = &statement && module.as_ref().map(|id| id.as_str()) == Some("__future__") { - for feature in names { - if &feature.name == "annotations" { - self.future_annotations = true; - } - } + self.future_annotations = + self.future_annotations || names.iter().any(|future| &future.name == "annotations"); } + match &statement { Stmt::Global(StmtGlobal { names, .. }) => { for name in names { From 567fb4dec05fa15b73af26c1cd203039912e0437 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI <jiseok.dev@gmail.com> Date: Sat, 22 Nov 2025 20:42:24 +0900 Subject: [PATCH 366/819] fix(sqlite): raise `ProgrammingError` when operating on a blob with a closed connection, (#6286) Fixed #6285 --- Lib/test/test_sqlite3/test_dbapi.py | 2 -- crates/stdlib/src/sqlite.rs | 11 +++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index 49c9764a1b7..a2530a03e2f 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -1469,8 +1469,6 @@ def test_blob_closed(self): with self.assertRaisesRegex(sqlite.ProgrammingError, msg): blob[0] = b"" - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_blob_closed_db_read(self): with memory_database() as cx: cx.execute("create table test(b blob)") diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index e19aca1f6d5..b875c609449 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -1007,6 +1007,10 @@ mod _sqlite { Ok(()) } + fn is_closed(&self) -> bool { + self.db.lock().is_none() + } + #[pymethod] fn commit(&self, vm: &VirtualMachine) -> PyResult<()> { self.db_lock(vm)?.implicit_commit(vm) @@ -2169,6 +2173,13 @@ mod _sqlite { length: OptionalArg<c_int>, vm: &VirtualMachine, ) -> PyResult<PyRef<PyBytes>> { + if self.connection.is_closed() { + return Err(new_programming_error( + vm, + "Cannot operate on a closed database".to_owned(), + )); + } + let mut length = length.unwrap_or(-1); let mut inner = self.inner(vm)?; let blob_len = inner.blob.bytes(); From a9469a20d57d809fb1ee04e8d0259f4374b10533 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI <jiseok.dev@gmail.com> Date: Sat, 22 Nov 2025 22:13:06 +0900 Subject: [PATCH 367/819] Fix sqlite connection reinitialization (#6288) * Fix sqlite connection reinitialization * Align sqlite connection reinit with CPython * Enable sqlite test_connection_bad_reinit * Fix sqlite reinit flag without threading * Use stronger memory ordering for initialized flag synchronization --- Lib/test/test_sqlite3/test_dbapi.py | 2 - crates/stdlib/src/sqlite.rs | 88 +++++++++++++++++++++-------- 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index a2530a03e2f..d8772dfffba 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -573,8 +573,6 @@ def test_connection_reinit(self): self.assertTrue(all(isinstance(r, sqlite.Row) for r in rows)) self.assertEqual([r[0] for r in rows], ["2", "3"]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_connection_bad_reinit(self): cx = sqlite.connect(":memory:") with cx: diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index b875c609449..6851462e525 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -833,10 +833,11 @@ mod _sqlite { #[derive(PyPayload)] struct Connection { db: PyMutex<Option<Sqlite>>, - detect_types: c_int, + initialized: PyAtomic<bool>, + detect_types: PyAtomic<c_int>, isolation_level: PyAtomicRef<Option<PyStr>>, - check_same_thread: bool, - thread_ident: ThreadId, + check_same_thread: PyAtomic<bool>, + thread_ident: PyMutex<ThreadId>, // TODO: Use atomic row_factory: PyAtomicRef<Option<PyObject>>, text_factory: PyAtomicRef<PyObject>, } @@ -865,12 +866,15 @@ mod _sqlite { None }; + let initialized = db.is_some(); + let conn = Self { db: PyMutex::new(db), - detect_types: args.detect_types, + initialized: Radium::new(initialized), + detect_types: Radium::new(args.detect_types), isolation_level: PyAtomicRef::from(args.isolation_level), - check_same_thread: args.check_same_thread, - thread_ident: std::thread::current().id(), + check_same_thread: Radium::new(args.check_same_thread), + thread_ident: PyMutex::new(std::thread::current().id()), row_factory: PyAtomicRef::from(None), text_factory: PyAtomicRef::from(text_factory), }; @@ -899,20 +903,51 @@ mod _sqlite { type Args = ConnectArgs; fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { - let mut guard = zelf.db.lock(); - if guard.is_some() { - // Already initialized - return Ok(()); + let was_initialized = Radium::swap(&zelf.initialized, false, Ordering::AcqRel); + + // Reset factories to their defaults, matching CPython's behavior. + zelf.reset_factories(vm); + + if was_initialized { + zelf.drop_db(); } + // Attempt to open the new database before mutating other state so failures leave + // the connection uninitialized (and subsequent operations raise ProgrammingError). let db = Self::initialize_db(&args, vm)?; + + let ConnectArgs { + detect_types, + isolation_level, + check_same_thread, + .. + } = args; + + zelf.detect_types.store(detect_types, Ordering::Relaxed); + zelf.check_same_thread + .store(check_same_thread, Ordering::Relaxed); + *zelf.thread_ident.lock() = std::thread::current().id(); + let _ = unsafe { zelf.isolation_level.swap(isolation_level) }; + + let mut guard = zelf.db.lock(); *guard = Some(db); + Radium::store(&zelf.initialized, true, Ordering::Release); Ok(()) } } #[pyclass(with(Constructor, Callable, Initializer), flags(BASETYPE))] impl Connection { + fn drop_db(&self) { + self.db.lock().take(); + } + + fn reset_factories(&self, vm: &VirtualMachine) { + let default_text_factory = PyStr::class(&vm.ctx).to_owned().into_object(); + let _ = unsafe { self.row_factory.swap(None) }; + let _ = unsafe { self.text_factory.swap(default_text_factory) }; + } + fn initialize_db(args: &ConnectArgs, vm: &VirtualMachine) -> PyResult<Sqlite> { let path = args.database.to_cstring(vm)?; let db = Sqlite::from(SqliteRaw::open(path.as_ptr(), args.uri, vm)?); @@ -1003,7 +1038,7 @@ mod _sqlite { #[pymethod] fn close(&self, vm: &VirtualMachine) -> PyResult<()> { self.check_thread(vm)?; - self.db.lock().take(); + self.drop_db(); Ok(()) } @@ -1450,15 +1485,17 @@ mod _sqlite { } fn check_thread(&self, vm: &VirtualMachine) -> PyResult<()> { - if self.check_same_thread && (std::thread::current().id() != self.thread_ident) { - Err(new_programming_error( - vm, - "SQLite objects created in a thread can only be used in that same thread." - .to_owned(), - )) - } else { - Ok(()) + if self.check_same_thread.load(Ordering::Relaxed) { + let creator_id = *self.thread_ident.lock(); + if std::thread::current().id() != creator_id { + return Err(new_programming_error( + vm, + "SQLite objects created in a thread can only be used in that same thread." + .to_owned(), + )); + } } + Ok(()) } #[pygetset] @@ -1632,7 +1669,8 @@ mod _sqlite { inner.row_cast_map = zelf.build_row_cast_map(&st, vm)?; - inner.description = st.columns_description(zelf.connection.detect_types, vm)?; + let detect_types = zelf.connection.detect_types.load(Ordering::Relaxed); + inner.description = st.columns_description(detect_types, vm)?; if ret == SQLITE_ROW { drop(st); @@ -1680,7 +1718,8 @@ mod _sqlite { )); } - inner.description = st.columns_description(zelf.connection.detect_types, vm)?; + let detect_types = zelf.connection.detect_types.load(Ordering::Relaxed); + inner.description = st.columns_description(detect_types, vm)?; inner.rowcount = if stmt.is_dml { 0 } else { -1 }; @@ -1845,7 +1884,8 @@ mod _sqlite { st: &SqliteStatementRaw, vm: &VirtualMachine, ) -> PyResult<Vec<Option<PyObjectRef>>> { - if self.connection.detect_types == 0 { + let detect_types = self.connection.detect_types.load(Ordering::Relaxed); + if detect_types == 0 { return Ok(vec![]); } @@ -1853,7 +1893,7 @@ mod _sqlite { let num_cols = st.column_count(); for i in 0..num_cols { - if self.connection.detect_types & PARSE_COLNAMES != 0 { + if detect_types & PARSE_COLNAMES != 0 { let col_name = st.column_name(i); let col_name = ptr_to_str(col_name, vm)?; let col_name = col_name @@ -1868,7 +1908,7 @@ mod _sqlite { continue; } } - if self.connection.detect_types & PARSE_DECLTYPES != 0 { + if detect_types & PARSE_DECLTYPES != 0 { let decltype = st.column_decltype(i); let decltype = ptr_to_str(decltype, vm)?; if let Some(decltype) = decltype.split_terminator(&[' ', '(']).next() { From f61b62e5a2e126a8cf16793f94e2801a6a47da02 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI <jiseok.dev@gmail.com> Date: Mon, 24 Nov 2025 00:04:43 +0900 Subject: [PATCH 368/819] Ensure sqlite blob methods respect closed connections (#6290) --- crates/stdlib/src/sqlite.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index 6851462e525..9fdb1716ac6 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -2207,18 +2207,24 @@ mod _sqlite { self.inner.lock().take(); } + fn ensure_connection_open(&self, vm: &VirtualMachine) -> PyResult<()> { + if self.connection.is_closed() { + Err(new_programming_error( + vm, + "Cannot operate on a closed database".to_owned(), + )) + } else { + Ok(()) + } + } + #[pymethod] fn read( &self, length: OptionalArg<c_int>, vm: &VirtualMachine, ) -> PyResult<PyRef<PyBytes>> { - if self.connection.is_closed() { - return Err(new_programming_error( - vm, - "Cannot operate on a closed database".to_owned(), - )); - } + self.ensure_connection_open(vm)?; let mut length = length.unwrap_or(-1); let mut inner = self.inner(vm)?; @@ -2245,6 +2251,7 @@ mod _sqlite { #[pymethod] fn write(&self, data: PyBuffer, vm: &VirtualMachine) -> PyResult<()> { + self.ensure_connection_open(vm)?; let mut inner = self.inner(vm)?; let blob_len = inner.blob.bytes(); let length = Self::expect_write(blob_len, data.desc.len, inner.offset, vm)?; @@ -2260,6 +2267,7 @@ mod _sqlite { #[pymethod] fn tell(&self, vm: &VirtualMachine) -> PyResult<c_int> { + self.ensure_connection_open(vm)?; self.inner(vm).map(|x| x.offset) } @@ -2270,6 +2278,7 @@ mod _sqlite { origin: OptionalArg<c_int>, vm: &VirtualMachine, ) -> PyResult<()> { + self.ensure_connection_open(vm)?; let origin = origin.unwrap_or(libc::SEEK_SET); let mut inner = self.inner(vm)?; let blob_len = inner.blob.bytes(); @@ -2299,12 +2308,14 @@ mod _sqlite { #[pymethod] fn __enter__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { + zelf.ensure_connection_open(vm)?; let _ = zelf.inner(vm)?; Ok(zelf) } #[pymethod] fn __exit__(&self, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + self.ensure_connection_open(vm)?; let _ = self.inner(vm)?; self.close(); Ok(()) @@ -2351,6 +2362,7 @@ mod _sqlite { } fn subscript(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult { + self.ensure_connection_open(vm)?; let inner = self.inner(vm)?; if let Some(index) = needle.try_index_opt(vm) { let blob_len = inner.blob.bytes(); @@ -2396,6 +2408,7 @@ mod _sqlite { let Some(value) = value else { return Err(vm.new_type_error("Blob doesn't support slice deletion")); }; + self.ensure_connection_open(vm)?; let inner = self.inner(vm)?; if let Some(index) = needle.try_index_opt(vm) { From 817f91b5bfd314aa44a5caebc2c689c6da53812d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 05:29:53 +0900 Subject: [PATCH 369/819] Bump xml from 1.0.1 to 1.2.0 (#6292) Bumps [xml](https://github.com/kornelski/xml-rs) from 1.0.1 to 1.2.0. - [Changelog](https://github.com/kornelski/xml-rs/blob/main/Changelog.md) - [Commits](https://github.com/kornelski/xml-rs/compare/1.0.1...1.2.0) --- updated-dependencies: - dependency-name: xml dependency-version: 1.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f749bc49b5..718631a8d69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4756,9 +4756,9 @@ dependencies = [ [[package]] name = "xml" -version = "1.0.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58a4274c410d957424a1502b21126915b45d9956b2f80a88d4f6f906af29facc" +checksum = "2df5825faced2427b2da74d9100f1e2e93c533fff063506a81ede1cf517b2e7e" [[package]] name = "xz2" diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index e62872324ea..dbff752b99c 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -51,7 +51,7 @@ base64 = "0.22" csv-core = "0.1.11" dyn-clone = "1.0.10" pymath = { workspace = true } -xml = "1.0" +xml = "1.2" # random rand_core = { workspace = true } From ea3eb2a9efac84018f390ecf3af29d5ab7ec0bd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 05:30:13 +0900 Subject: [PATCH 370/819] Bump actions/checkout from 4 to 6 (#6294) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 14 +++++++------- .github/workflows/cron-ci.yaml | 8 ++++---- .github/workflows/pr-auto-commit.yaml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/update-doc-db.yml | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 95a1ac98d62..23d16ffadb9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -120,7 +120,7 @@ jobs: os: [macos-latest, ubuntu-latest, windows-latest] fail-fast: false steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: clippy @@ -179,7 +179,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: target: i686-unknown-linux-gnu @@ -246,7 +246,7 @@ jobs: os: [macos-latest, ubuntu-latest, windows-latest] fail-fast: false steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - uses: actions/setup-python@v6 @@ -311,7 +311,7 @@ jobs: name: Check Rust code with clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: clippy @@ -350,7 +350,7 @@ jobs: env: NIGHTLY_CHANNEL: nightly steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@master with: @@ -372,7 +372,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 @@ -435,7 +435,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: target: wasm32-wasip1 diff --git a/.github/workflows/cron-ci.yaml b/.github/workflows/cron-ci.yaml index a94c8df9e23..e10c5ac0d1f 100644 --- a/.github/workflows/cron-ci.yaml +++ b/.github/workflows/cron-ci.yaml @@ -21,7 +21,7 @@ jobs: # Disable this scheduled job when running on a fork. if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-llvm-cov - uses: actions/setup-python@v6 @@ -49,7 +49,7 @@ jobs: # Disable this scheduled job when running on a fork. if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - name: build rustpython run: cargo build --release --verbose @@ -80,7 +80,7 @@ jobs: # Disable this scheduled job when running on a fork. if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - uses: actions/setup-python@v6 with: @@ -137,7 +137,7 @@ jobs: # Disable this scheduled job when running on a fork. if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - uses: actions/setup-python@v6 with: diff --git a/.github/workflows/pr-auto-commit.yaml b/.github/workflows/pr-auto-commit.yaml index 8546c2abe51..62f9eb9c332 100644 --- a/.github/workflows/pr-auto-commit.yaml +++ b/.github/workflows/pr-auto-commit.yaml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout PR branch - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.ref }} repository: ${{ github.event.pull_request.head.repo.full_name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43286780ef7..a69169c3234 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,7 +50,7 @@ jobs: # target: aarch64-pc-windows-msvc fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - uses: cargo-bins/cargo-binstall@main @@ -93,7 +93,7 @@ jobs: # Disable this scheduled job when running on a fork. if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-wasip1 diff --git a/.github/workflows/update-doc-db.yml b/.github/workflows/update-doc-db.yml index dcef94f20d2..0f84b8d23a9 100644 --- a/.github/workflows/update-doc-db.yml +++ b/.github/workflows/update-doc-db.yml @@ -26,7 +26,7 @@ jobs: - windows-latest - macos-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false sparse-checkout: | @@ -51,7 +51,7 @@ jobs: runs-on: ubuntu-latest needs: generate steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false sparse-checkout: | From 1e7a49036a9e6f22867511b553ffcb76be62ac5f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 25 Nov 2025 20:04:30 +0900 Subject: [PATCH 371/819] try auto-format again (#6295) --- .github/workflows/pr-auto-commit.yaml | 60 ++++++++------------------- 1 file changed, 18 insertions(+), 42 deletions(-) diff --git a/.github/workflows/pr-auto-commit.yaml b/.github/workflows/pr-auto-commit.yaml index 62f9eb9c332..118f7bcd4d4 100644 --- a/.github/workflows/pr-auto-commit.yaml +++ b/.github/workflows/pr-auto-commit.yaml @@ -1,4 +1,4 @@ -name: PR Auto-format +name: Auto-format PR # This workflow triggers when a PR is opened/updated on: @@ -9,42 +9,26 @@ on: - release concurrency: - group: pr-fmt-${{ github.event.pull_request.number }} + group: auto-format-${{ github.event.pull_request.number }} cancel-in-progress: true jobs: auto_format: - if: | - !contains(github.event.pull_request.labels.*.name, 'skip:ci') && - !contains(github.event.pull_request.head.sha, '[skip ci]') + if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} permissions: contents: write pull-requests: write - checks: read runs-on: ubuntu-latest timeout-minutes: 60 - steps: - name: Checkout PR branch uses: actions/checkout@v6 with: - ref: ${{ github.event.pull_request.head.ref }} + ref: ${{ github.event.pull_request.head.sha }} repository: ${{ github.event.pull_request.head.repo.full_name }} - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.AUTO_COMMIT_PAT }} fetch-depth: 0 - # Wait for all PR check runs to complete - - name: Wait for all checks to complete - uses: poseidon/wait-for-status-checks@v0.6.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - delay: 60 - interval: 30 - timeout: 7200 - - - name: CI completed successfully - run: echo "CI workflow completed successfully - proceeding with auto-format" - - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: @@ -55,8 +39,13 @@ jobs: echo "Running cargo fmt --all on PR #${{ github.event.pull_request.number }}" cargo fmt --all - - name: Check for formatting changes - id: check_changes + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Check for changes + id: check-changes run: | if [ -n "$(git status --porcelain)" ]; then echo "has_changes=true" >> $GITHUB_OUTPUT @@ -65,35 +54,22 @@ jobs: fi - name: Commit and push formatting changes - if: steps.check_changes.outputs.has_changes == 'true' + if: steps.check-changes.outputs.has_changes == 'true' run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add -u - git commit -m "Auto-format code [skip ci]" - + git commit -m "Auto-format: cargo fmt --all" git push origin HEAD:${{ github.event.pull_request.head.ref }} - name: Comment on PR - if: steps.check_changes.outputs.has_changes == 'true' + if: steps.check-changes.outputs.has_changes == 'true' uses: marocchino/sticky-pull-request-comment@v2 with: number: ${{ github.event.pull_request.number }} message: | **Code has been automatically formatted** - - The code in this PR has been formatted using `cargo fmt`. - The changes have been committed with `[skip ci]` to avoid triggering another CI run. - - **Triggered by commit:** `${{ github.event.pull_request.head.sha }}` - **Last formatted:** ${{ github.event.pull_request.updated_at }} - - You may need to pull the latest changes before pushing again: + + The code in this PR has been formatted using `cargo fmt --all`. + Please pull the latest changes before pushing again: ```bash git pull origin ${{ github.event.pull_request.head.ref }} ``` - - - name: No formatting needed - if: steps.check_changes.outputs.has_changes == 'false' - run: echo "Code is already properly formatted" From e733b7ecf99a2d3f255bc775d0f1df213c0db2fe Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:15:02 +0200 Subject: [PATCH 372/819] Update `libc` to 0.2.177 --- Cargo.lock | 303 +++++++++++++++++++++++++---------------------------- Cargo.toml | 2 +- 2 files changed, 144 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 718631a8d69..f96b527dfe2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -100,22 +100,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -231,9 +231,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-fips-sys" -version = "0.13.9" +version = "0.13.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede71ad84efb06d748d9af3bc500b14957a96282a69a6833b1420dcacb411cc3" +checksum = "57900537c00a0565a35b63c4c281b372edfc9744b072fd4a3b414350a8f5ed48" dependencies = [ "bindgen 0.72.1", "cc", @@ -245,9 +245,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.14.1" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" dependencies = [ "aws-lc-fips-sys", "aws-lc-sys", @@ -257,11 +257,10 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.3" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" +checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" dependencies = [ - "bindgen 0.72.1", "cc", "cmake", "dunce", @@ -286,7 +285,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -306,7 +305,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -328,9 +327,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "blake2" @@ -361,9 +360,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", @@ -435,9 +434,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.41" +version = "1.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", "jobserver", @@ -535,18 +534,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.49" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.49" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstyle", "clap_lex", @@ -926,9 +925,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -1052,9 +1051,9 @@ dependencies = [ [[package]] name = "dns-lookup" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853d5bcf0b73bd5e6d945b976288621825c7166e9f06c5a035ae1aaf42d1b64f" +checksum = "6e39034cee21a2f5bbb66ba0e3689819c4bb5d00382a282006e802a7ffa6c41d" dependencies = [ "cfg-if", "libc", @@ -1174,9 +1173,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flagset" @@ -1222,9 +1221,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "libz-rs-sys", @@ -1260,9 +1259,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1270,9 +1269,9 @@ dependencies = [ [[package]] name = "get-size-derive2" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3814abc7da8ab18d2fd820f5b540b5e39b6af0a32de1bdd7c47576693074843" +checksum = "ff47daa61505c85af126e9dd64af6a342a33dc0cccfe1be74ceadc7d352e6efd" dependencies = [ "attribute-derive", "quote", @@ -1281,13 +1280,13 @@ dependencies = [ [[package]] name = "get-size2" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfe2cec5b5ce8fb94dcdb16a1708baa4d0609cc3ce305ca5d3f6f2ffb59baed" +checksum = "ac7bb8710e1f09672102be7ddf39f764d8440ae74a9f4e30aaa4820dcdffa4af" dependencies = [ "compact_str", "get-size-derive2", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "smallvec", ] @@ -1374,9 +1373,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -1413,11 +1412,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1446,19 +1445,22 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", ] [[package]] name = "indoc" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] [[package]] name = "inout" @@ -1472,9 +1474,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.43.2" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" +checksum = "dfd3461e1f00283105bdf97c3a1aca2b3f8456eb809a96938d2b190cd4dbc6d2" dependencies = [ "console", "once_cell", @@ -1501,9 +1503,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -1531,22 +1533,22 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" dependencies = [ "proc-macro2", "quote", @@ -1587,9 +1589,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1710,7 +1712,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", ] @@ -1951,7 +1953,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", @@ -1964,7 +1966,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", @@ -2036,9 +2038,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -2046,9 +2048,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", @@ -2072,9 +2074,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" @@ -2084,11 +2086,11 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl" -version = "0.10.74" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "foreign-types", "libc", @@ -2116,18 +2118,18 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.3+3.5.4" +version = "300.5.4+3.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6bad8cd0233b63971e232cc9c5e83039375b8586d2312f31fda85db8f888c2" +checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.110" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -2419,9 +2421,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -2498,9 +2500,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -2643,7 +2645,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -2753,7 +2755,7 @@ version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" dependencies = [ "aho-corasick", - "bitflags 2.9.4", + "bitflags 2.10.0", "compact_str", "get-size2", "is-macro", @@ -2771,7 +2773,7 @@ name = "ruff_python_parser" version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bstr", "compact_str", "get-size2", @@ -2835,7 +2837,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", @@ -2953,7 +2955,7 @@ name = "rustpython-codegen" version = "0.4.0" dependencies = [ "ahash", - "bitflags 2.9.4", + "bitflags 2.10.0", "indexmap", "insta", "itertools 0.14.0", @@ -2977,7 +2979,7 @@ name = "rustpython-common" version = "0.4.0" dependencies = [ "ascii", - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "getrandom 0.3.4", "itertools 0.14.0", @@ -3016,7 +3018,7 @@ dependencies = [ name = "rustpython-compiler-core" version = "0.4.0" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "itertools 0.14.0", "lz4_flex", "malachite-bigint", @@ -3105,7 +3107,7 @@ dependencies = [ name = "rustpython-sre_engine" version = "0.4.0" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "criterion", "num_enum", "optional", @@ -3208,7 +3210,7 @@ version = "0.4.0" dependencies = [ "ahash", "ascii", - "bitflags 2.9.4", + "bitflags 2.10.0", "bstr", "caseless", "cfg-if", @@ -3319,7 +3321,7 @@ version = "17.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e902948a25149d50edc1a8e0141aad50f54e22ba83ff988cf8f7c9ef07f50564" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "clipboard-win", "fd-lock", @@ -3406,7 +3408,7 @@ version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -3479,11 +3481,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -3634,9 +3636,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.107" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -3671,7 +3673,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3870,44 +3872,42 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "serde", + "indexmap", + "serde_core", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ - "serde", + "serde_core", ] [[package]] -name = "toml_edit" -version = "0.22.27" +name = "toml_parser" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_writer" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "twox-hash" @@ -4055,15 +4055,15 @@ checksum = "061dbb8cc7f108532b6087a0065eff575e892a4bcb503dc57323a197457cc202" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] @@ -4196,9 +4196,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -4207,25 +4207,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -4236,9 +4222,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4246,22 +4232,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -4280,9 +4266,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -4684,12 +4670,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" [[package]] name = "winreg" @@ -4703,9 +4686,9 @@ dependencies = [ [[package]] name = "winresource" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcacf11b6f48dd21b9ba002f991bdd5de29b2da8cc2800412f4b80f677e4957" +checksum = "f1ef04dd590e94ff7431a8eda99d5ca659e688d60e930bd0a330062acea4608f" dependencies = [ "toml", "version_check", @@ -4771,18 +4754,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 53d1eb6ea2d..8993ce145ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -175,7 +175,7 @@ insta = "1.42" itertools = "0.14.0" is-macro = "0.3.7" junction = "1.3.0" -libc = "0.2.169" +libc = "0.2.177" libffi = "4.1" log = "0.4.28" nix = { version = "0.30", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] } From 081a8f0451317f776388c5160d67db51fd2fa1bd Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:16:42 +0200 Subject: [PATCH 373/819] Regenerate libc constatnts --- crates/vm/src/stdlib/posix.rs | 6 +++--- scripts/libc_posix.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index e8a01d0e687..071f93d7ee0 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -202,8 +202,7 @@ pub mod module { #[pyattr] use libc::{ CLD_CONTINUED, CLD_DUMPED, CLD_EXITED, CLD_KILLED, CLD_STOPPED, CLD_TRAPPED, F_LOCK, - F_TEST, F_TLOCK, F_ULOCK, O_NDELAY, O_NOCTTY, O_SYNC, P_ALL, P_PGID, P_PID, SCHED_FIFO, - SCHED_RR, + F_TEST, F_TLOCK, F_ULOCK, O_SYNC, P_ALL, P_PGID, P_PID, SCHED_FIFO, SCHED_RR, }; #[cfg(any( @@ -217,7 +216,8 @@ pub mod module { target_os = "redox" ))] #[pyattr] - use libc::{O_ASYNC, WEXITED, WNOWAIT, WSTOPPED}; + use libc::{O_ASYNC, O_NDELAY, O_NOCTTY, WEXITED, WNOWAIT, WSTOPPED}; + #[pyattr] const EX_OK: i8 = exitcode::OK as i8; diff --git a/scripts/libc_posix.py b/scripts/libc_posix.py index 9ed6890e70f..73f082a0658 100644 --- a/scripts/libc_posix.py +++ b/scripts/libc_posix.py @@ -13,7 +13,7 @@ ) # TODO: Exclude matches if they have `(` after (those are functions) -LIBC_VERSION = "0.2.175" +LIBC_VERSION = "0.2.177" EXCLUDE = frozenset( { From 1b3261a090b4331cfcc60e8bad2ced92f0f8fb28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 08:39:46 +0900 Subject: [PATCH 374/819] Bump node-forge from 1.3.1 to 1.3.2 in /wasm/demo (#6297) Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.3.1 to 1.3.2. - [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md) - [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.3.2) --- updated-dependencies: - dependency-name: node-forge dependency-version: 1.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- wasm/demo/package-lock.json | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/wasm/demo/package-lock.json b/wasm/demo/package-lock.json index e3cf79298ac..3acc105e3c3 100644 --- a/wasm/demo/package-lock.json +++ b/wasm/demo/package-lock.json @@ -775,7 +775,8 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", @@ -831,6 +832,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1149,6 +1151,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -3495,9 +3498,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", + "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { @@ -3824,6 +3827,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -5082,7 +5086,8 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/type-fest": { "version": "2.19.0", @@ -5266,6 +5271,7 @@ "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -5313,6 +5319,7 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", From 0e6e256f8e1448411d7c86d5fa23ec3fe72f4296 Mon Sep 17 00:00:00 2001 From: Wildan M <willnode@wellosoft.net> Date: Fri, 28 Nov 2025 06:41:22 +0700 Subject: [PATCH 375/819] Fix redox compilation in stdlib (#6298) --- crates/stdlib/src/posixsubprocess.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/stdlib/src/posixsubprocess.rs b/crates/stdlib/src/posixsubprocess.rs index 7f418c89931..4da6a6858dd 100644 --- a/crates/stdlib/src/posixsubprocess.rs +++ b/crates/stdlib/src/posixsubprocess.rs @@ -441,15 +441,14 @@ fn close_dir_fds(keep: KeepFds<'_>) -> nix::Result<()> { fn close_filetable_fds(keep: KeepFds<'_>) -> nix::Result<()> { use nix::fcntl; use std::os::fd::{FromRawFd, OwnedFd}; - let fd = fcntl::open( + let filetable = fcntl::open( c"/scheme/thisproc/current/filetable", fcntl::OFlag::O_RDONLY, nix::sys::stat::Mode::empty(), )?; - let filetable = unsafe { OwnedFd::from_raw_fd(fd) }; let read_one = || -> nix::Result<_> { let mut byte = 0; - let n = nix::unistd::read(filetable.as_raw_fd(), std::slice::from_mut(&mut byte))?; + let n = nix::unistd::read(&filetable, std::slice::from_mut(&mut byte))?; Ok((n > 0).then_some(byte)) }; while let Some(c) = read_one()? { From f37ea525650d975a8a87fa8052e1785612684ab1 Mon Sep 17 00:00:00 2001 From: Ashwin Naren <arihant2math@gmail.com> Date: Mon, 31 Mar 2025 21:42:54 -0700 Subject: [PATCH 376/819] Ctypes more pointer implementation fix import add more classes ctypes overhall updates fix build fix warnings, pass test fix panic on improper library load test on macos formatting tmp minor updates --- crates/vm/src/stdlib/ctypes.rs | 11 +- crates/vm/src/stdlib/ctypes/array.rs | 1 + crates/vm/src/stdlib/ctypes/base.rs | 22 +- crates/vm/src/stdlib/ctypes/field.rs | 134 ++++++++ crates/vm/src/stdlib/ctypes/function.rs | 386 +++++++++++++++--------- crates/vm/src/stdlib/ctypes/pointer.rs | 41 ++- crates/vm/src/stdlib/ctypes/thunk.rs | 22 ++ crates/vm/src/stdlib/ctypes/util.rs | 24 ++ extra_tests/snippets/stdlib_ctypes.py | 11 +- 9 files changed, 491 insertions(+), 161 deletions(-) create mode 100644 crates/vm/src/stdlib/ctypes/field.rs create mode 100644 crates/vm/src/stdlib/ctypes/thunk.rs create mode 100644 crates/vm/src/stdlib/ctypes/util.rs diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 8ea4dd165eb..ac74418354e 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -2,11 +2,14 @@ pub(crate) mod array; pub(crate) mod base; +pub(crate) mod field; pub(crate) mod function; pub(crate) mod library; pub(crate) mod pointer; pub(crate) mod structure; +pub(crate) mod thunk; pub(crate) mod union; +pub(crate) mod util; use crate::builtins::PyModule; use crate::class::PyClassImpl; @@ -17,14 +20,18 @@ pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py<PyModule>) { let ctx = &vm.ctx; PyCSimpleType::make_class(ctx); array::PyCArrayType::make_class(ctx); + field::PyCFieldType::make_class(ctx); + pointer::PyCPointerType::make_class(ctx); extend_module!(vm, module, { "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PyCSimple::make_class(ctx), "Array" => array::PyCArray::make_class(ctx), + "CField" => field::PyCField::make_class(ctx), "CFuncPtr" => function::PyCFuncPtr::make_class(ctx), "_Pointer" => pointer::PyCPointer::make_class(ctx), "_pointer_type_cache" => ctx.new_dict(), "Structure" => structure::PyCStructure::make_class(ctx), + "CThunkObject" => thunk::PyCThunk::make_class(ctx), "Union" => union::PyCUnion::make_class(ctx), }) } @@ -207,7 +214,9 @@ pub(crate) mod _ctypes { // TODO: load_flags let cache = library::libcache(); let mut cache_write = cache.write(); - let (id, _) = cache_write.get_or_insert_lib(&name, vm).unwrap(); + let (id, _) = cache_write + .get_or_insert_lib(&name, vm) + .map_err(|e| vm.new_os_error(e.to_string()))?; Ok(id) } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 5290ec42f37..a1adf847a99 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -106,6 +106,7 @@ impl PyCArray { } impl PyCArray { + #[allow(unused)] pub fn to_arg(&self, _vm: &VirtualMachine) -> PyResult<libffi::middle::Arg> { let value = self.value.read(); let py_bytes = value.downcast_ref::<PyBytes>().unwrap(); diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 23cb505adaf..f5a25ad740b 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -276,25 +276,25 @@ impl PyCSimple { let value = unsafe { (*self.value.as_ptr()).clone() }; if let Ok(i) = value.try_int(vm) { let i = i.as_bigint(); - if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u8().as_raw_ptr()) { - return i.to_u8().map(|r: u8| libffi::middle::Arg::new(&r)); + return if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u8().as_raw_ptr()) { + i.to_u8().map(|r: u8| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i8().as_raw_ptr()) { - return i.to_i8().map(|r: i8| libffi::middle::Arg::new(&r)); + i.to_i8().map(|r: i8| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u16().as_raw_ptr()) { - return i.to_u16().map(|r: u16| libffi::middle::Arg::new(&r)); + i.to_u16().map(|r: u16| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i16().as_raw_ptr()) { - return i.to_i16().map(|r: i16| libffi::middle::Arg::new(&r)); + i.to_i16().map(|r: i16| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u32().as_raw_ptr()) { - return i.to_u32().map(|r: u32| libffi::middle::Arg::new(&r)); + i.to_u32().map(|r: u32| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i32().as_raw_ptr()) { - return i.to_i32().map(|r: i32| libffi::middle::Arg::new(&r)); + i.to_i32().map(|r: i32| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u64().as_raw_ptr()) { - return i.to_u64().map(|r: u64| libffi::middle::Arg::new(&r)); + i.to_u64().map(|r: u64| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i64().as_raw_ptr()) { - return i.to_i64().map(|r: i64| libffi::middle::Arg::new(&r)); + i.to_i64().map(|r: i64| libffi::middle::Arg::new(&r)) } else { - return None; - } + None + }; } if let Ok(_f) = value.try_float(vm) { todo!(); diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs new file mode 100644 index 00000000000..b50b8b54ace --- /dev/null +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -0,0 +1,134 @@ +use crate::builtins::PyType; +use crate::builtins::PyTypeRef; +use crate::stdlib::ctypes::PyCData; +use crate::types::Constructor; +use crate::types::Representable; +use crate::{Py, PyResult, VirtualMachine}; + +#[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")] +#[derive(PyPayload, Debug)] +pub struct PyCFieldType { + pub(super) inner: PyCField, +} + +#[pyclass] +impl PyCFieldType {} + +#[pyclass( + name = "CField", + base = PyCData, + metaclass = "PyCFieldType", + module = "_ctypes" +)] +#[derive(Debug, PyPayload)] +pub struct PyCField { + byte_offset: usize, + byte_size: usize, + #[allow(unused)] + index: usize, + proto: PyTypeRef, + anonymous: bool, + bitfield_size: bool, + bit_offset: u8, + name: String, +} + +impl Representable for PyCField { + fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { + let tp_name = zelf.proto.name().to_string(); + if zelf.bitfield_size != false { + Ok(format!( + "<{} type={}, ofs={byte_offset}, bit_size={bitfield_size}, bit_offset={bit_offset}", + zelf.name, + tp_name, + byte_offset = zelf.byte_offset, + bitfield_size = zelf.bitfield_size, + bit_offset = zelf.bit_offset + )) + } else { + Ok(format!( + "<{} type={tp_name}, ofs={}, size={}", + zelf.name, zelf.byte_offset, zelf.byte_size + )) + } + } +} + +#[derive(Debug, FromArgs)] +pub struct PyCFieldConstructorArgs { + // PyObject *name, PyObject *proto, + // Py_ssize_t byte_size, Py_ssize_t byte_offset, + // Py_ssize_t index, int _internal_use, + // PyObject *bit_size_obj, PyObject *bit_offset_obj +} + +impl Constructor for PyCField { + type Args = PyCFieldConstructorArgs; + + fn py_new(_cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("Cannot instantiate a PyCField".to_string())) + } +} + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, Representable))] +impl PyCField { + #[pygetset] + fn size(&self) -> usize { + self.byte_size + } + + #[pygetset] + fn bit_size(&self) -> bool { + self.bitfield_size + } + + #[pygetset] + fn is_bitfield(&self) -> bool { + self.bitfield_size + } + + #[pygetset] + fn is_anonymous(&self) -> bool { + self.anonymous + } + + #[pygetset] + fn name(&self) -> String { + self.name.clone() + } + + #[pygetset(name = "type")] + fn type_(&self) -> PyTypeRef { + self.proto.clone() + } + + #[pygetset] + fn offset(&self) -> usize { + self.byte_offset + } + + #[pygetset] + fn byte_offset(&self) -> usize { + self.byte_offset + } + + #[pygetset] + fn byte_size(&self) -> usize { + self.byte_size + } + + #[pygetset] + fn bit_offset(&self) -> u8 { + self.bit_offset + } +} + +#[inline(always)] +pub const fn low_bit(offset: usize) -> usize { + offset & 0xFFFF +} + +#[inline(always)] +pub const fn high_bit(offset: usize) -> usize { + offset >> 16 +} diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 6703dcc0f52..88d0fbb35ea 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -1,142 +1,117 @@ // spell-checker:disable -use crate::builtins::{PyStr, PyTupleRef, PyTypeRef}; +use crate::builtins::{PyNone, PyStr, PyTuple, PyTupleRef, PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; use crate::stdlib::ctypes::PyCData; -use crate::stdlib::ctypes::array::PyCArray; use crate::stdlib::ctypes::base::{PyCSimple, ffi_type_from_str}; +use crate::types::Representable; use crate::types::{Callable, Constructor}; -use crate::{Py, PyObjectRef, PyResult, VirtualMachine}; +use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use libffi::middle::{Arg, Cif, CodePtr, Type}; use libloading::Symbol; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; -use std::ffi::CString; +use std::ffi::{self, CString, c_void}; use std::fmt::Debug; -// https://github.com/python/cpython/blob/4f8bb3947cfbc20f970ff9d9531e1132a9e95396/Modules/_ctypes/callproc.c#L15 +// See also: https://github.com/python/cpython/blob/4f8bb3947cfbc20f970ff9d9531e1132a9e95396/Modules/_ctypes/callproc.c#L15 -#[derive(Debug)] -pub struct Function { - args: Vec<Type>, - // TODO: no protection from use-after-free - pointer: CodePtr, - cif: Cif, +type FP = unsafe extern "C" fn(); + +pub trait ArgumentType { + fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult<Type>; + fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<Arg>; } -unsafe impl Send for Function {} -unsafe impl Sync for Function {} +impl ArgumentType for PyTypeRef { + fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult<Type> { + let typ = self + .get_class_attr(vm.ctx.intern_str("_type_")) + .ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; + let typ = typ + .downcast_ref::<PyStr>() + .ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; + let typ = typ.to_string(); + let typ = typ.as_str(); + let converted_typ = ffi_type_from_str(typ); + if let Some(typ) = converted_typ { + Ok(typ) + } else { + Err(vm.new_type_error(format!("Unsupported argument type: {}", typ))) + } + } -type FP = unsafe extern "C" fn(); + fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<Arg> { + // if self.fast_isinstance::<PyCArray>(vm) { + // let array = value.downcast::<PyCArray>()?; + // return Ok(Arg::from(array.as_ptr())); + // } + if let Ok(simple) = value.downcast::<PyCSimple>() { + let typ = ArgumentType::to_ffi_type(self, vm)?; + let arg = simple + .to_arg(typ, vm) + .ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; + return Ok(arg); + } + Err(vm.new_type_error("Unsupported argument type".to_string())) + } +} -impl Function { - pub unsafe fn load( - library: &libloading::Library, - function: &str, - args: &[PyObjectRef], - ret_type: &Option<PyTypeRef>, +pub trait ReturnType { + fn to_ffi_type(&self) -> Option<Type>; + fn from_ffi_type( + &self, + value: *mut ffi::c_void, vm: &VirtualMachine, - ) -> PyResult<Self> { - // map each arg to a PyCSimple - let args = args - .iter() - .map(|arg| { - if let Some(arg) = arg.downcast_ref::<PyCSimple>() { - let converted = ffi_type_from_str(&arg._type_); - return match converted { - Some(t) => Ok(t), - None => Err(vm.new_type_error("Invalid type")), // TODO: add type name - }; - } - if let Some(arg) = arg.downcast_ref::<PyCArray>() { - let t = arg.typ.read(); - let ty_attributes = t.attributes.read(); - let ty_pystr = ty_attributes - .get(vm.ctx.intern_str("_type_")) - .ok_or_else(|| vm.new_type_error("Expected a ctypes simple type"))?; - let ty_str = ty_pystr - .downcast_ref::<PyStr>() - .ok_or_else(|| vm.new_type_error("Expected a ctypes simple type"))? - .to_string(); - let converted = ffi_type_from_str(&ty_str); - match converted { - Some(_t) => { - // TODO: Use - Ok(Type::void()) - } - None => Err(vm.new_type_error("Invalid type")), // TODO: add type name - } - } else { - Err(vm.new_type_error("Expected a ctypes simple type")) - } - }) - .collect::<PyResult<Vec<Type>>>()?; - let c_function_name = CString::new(function) - .map_err(|_| vm.new_value_error("Function name contains null bytes"))?; - let pointer: Symbol<'_, FP> = unsafe { - library - .get(c_function_name.as_bytes()) - .map_err(|err| err.to_string()) - .map_err(|err| vm.new_attribute_error(err))? - }; - let code_ptr = CodePtr(*pointer as *mut _); - let return_type = match ret_type { - // TODO: Fix this - Some(_t) => { - return Err(vm.new_not_implemented_error("Return type not implemented")); - } - None => Type::c_int(), - }; - let cif = Cif::new(args.clone(), return_type); - Ok(Function { - args, - cif, - pointer: code_ptr, - }) + ) -> PyResult<Option<PyObjectRef>>; +} + +impl ReturnType for PyTypeRef { + fn to_ffi_type(&self) -> Option<Type> { + ffi_type_from_str(self.name().to_string().as_str()) } - pub unsafe fn call( + fn from_ffi_type( &self, - args: Vec<PyObjectRef>, - vm: &VirtualMachine, - ) -> PyResult<PyObjectRef> { - let args = args - .into_iter() - .enumerate() - .map(|(count, arg)| { - // none type check - if let Some(d) = arg.downcast_ref::<PyCSimple>() { - return Ok(d.to_arg(self.args[count].clone(), vm).unwrap()); - } - if let Some(d) = arg.downcast_ref::<PyCArray>() { - return Ok(d.to_arg(vm).unwrap()); - } - Err(vm.new_type_error("Expected a ctypes simple type")) - }) - .collect::<PyResult<Vec<Arg>>>()?; - // TODO: FIX return - let result: i32 = unsafe { self.cif.call(self.pointer, &args) }; - Ok(vm.ctx.new_int(result).into()) + _value: *mut ffi::c_void, + _vm: &VirtualMachine, + ) -> PyResult<Option<PyObjectRef>> { + todo!() + } +} + +impl ReturnType for PyNone { + fn to_ffi_type(&self) -> Option<Type> { + ffi_type_from_str("void") + } + + fn from_ffi_type( + &self, + _value: *mut ffi::c_void, + _vm: &VirtualMachine, + ) -> PyResult<Option<PyObjectRef>> { + Ok(None) } } #[pyclass(module = "_ctypes", name = "CFuncPtr", base = PyCData)] #[derive(PyPayload)] pub struct PyCFuncPtr { - pub name: PyRwLock<String>, - pub _flags_: AtomicCell<u32>, - // FIXME(arihant2math): This shouldn't be an option, setting the default as the none type should work - // This is a workaround for now and I'll fix it later - pub _restype_: PyRwLock<Option<PyTypeRef>>, + pub name: PyRwLock<Option<String>>, + pub ptr: PyRwLock<Option<CodePtr>>, + pub needs_free: AtomicCell<bool>, + pub arg_types: PyRwLock<Option<Vec<PyTypeRef>>>, + pub res_type: PyRwLock<Option<PyObjectRef>>, + pub _flags_: AtomicCell<i32>, pub handler: PyObjectRef, } impl Debug for PyCFuncPtr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PyCFuncPtr") - .field("name", &self.name) + .field("flags", &self._flags_) .finish() } } @@ -156,10 +131,43 @@ impl Constructor for PyCFuncPtr { .nth(1) .ok_or(vm.new_type_error("Expected a tuple with at least 2 elements"))? .clone(); + let handle = handler.try_int(vm); + let handle = match handle { + Ok(handle) => handle.as_bigint().clone(), + Err(_) => handler + .get_attr("_handle", vm)? + .try_int(vm)? + .as_bigint() + .clone(), + }; + let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library = library_cache + .get_lib( + handle + .to_usize() + .ok_or(vm.new_value_error("Invalid handle".to_string()))?, + ) + .ok_or_else(|| vm.new_value_error("Library not found".to_string()))?; + let inner_lib = library.lib.lock(); + + let terminated = format!("{}\0", &name); + let code_ptr = if let Some(lib) = &*inner_lib { + let pointer: Symbol<'_, FP> = unsafe { + lib.get(terminated.as_bytes()) + .map_err(|err| err.to_string()) + .map_err(|err| vm.new_attribute_error(err))? + }; + Some(CodePtr(*pointer as *mut _)) + } else { + None + }; Ok(Self { + ptr: PyRwLock::new(code_ptr), + needs_free: AtomicCell::new(false), + arg_types: PyRwLock::new(None), _flags_: AtomicCell::new(0), - name: PyRwLock::new(name), - _restype_: PyRwLock::new(None), + res_type: PyRwLock::new(None), + name: PyRwLock::new(Some(name)), handler, } .to_pyobject(vm)) @@ -169,53 +177,145 @@ impl Constructor for PyCFuncPtr { impl Callable for PyCFuncPtr { type Args = FuncArgs; fn call(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult { - unsafe { - let handle = zelf.handler.get_attr("_handle", vm)?; - let handle = handle.try_int(vm)?.as_bigint().clone(); - let library_cache = crate::stdlib::ctypes::library::libcache().read(); - let library = library_cache - .get_lib( - handle - .to_usize() - .ok_or(vm.new_value_error("Invalid handle"))?, - ) - .ok_or_else(|| vm.new_value_error("Library not found"))?; - let inner_lib = library.lib.lock(); - let name = zelf.name.read(); - let res_type = zelf._restype_.read(); - let func = Function::load( - inner_lib - .as_ref() - .ok_or_else(|| vm.new_value_error("Library not found"))?, - &name, - &args.args, - &res_type, - vm, - )?; - func.call(args.args, vm) + // This is completely seperate from the C python implementation + + // Cif init + let arg_types: Vec<_> = match zelf.arg_types.read().clone() { + Some(tys) => tys, + None => args + .args + .clone() + .into_iter() + .map(|a| a.class().as_object().to_pyobject(vm).downcast().unwrap()) + .collect(), + }; + let ffi_arg_types = arg_types + .clone() + .iter() + .map(|t| ArgumentType::to_ffi_type(t, vm)) + .collect::<PyResult<Vec<_>>>()?; + let return_type = zelf.res_type.read(); + let ffi_return_type = return_type + .as_ref() + .map(|t| ReturnType::to_ffi_type(&t.clone().downcast::<PyType>().unwrap())) + .flatten() + .unwrap_or_else(|| Type::i32()); + let cif = Cif::new(ffi_arg_types, ffi_return_type); + + // Call the function + let ffi_args = args + .args + .into_iter() + .enumerate() + .map(|(n, arg)| { + let arg_type = arg_types + .get(n) + .ok_or_else(|| vm.new_type_error("argument amount mismatch".to_string()))?; + arg_type.convert_object(arg, vm) + }) + .collect::<Result<Vec<_>, _>>()?; + let pointer = zelf.ptr.read(); + let code_ptr = pointer + .as_ref() + .ok_or_else(|| vm.new_type_error("Function pointer not set".to_string()))?; + let mut output: c_void = unsafe { cif.call(*code_ptr, &ffi_args) }; + let return_type = return_type + .as_ref() + .map(|f| { + f.clone() + .downcast::<PyType>() + .unwrap() + .from_ffi_type(&mut output, vm) + .ok() + .flatten() + }) + .unwrap_or_else(|| Some(vm.ctx.new_int(output as i32).as_object().to_pyobject(vm))); + if let Some(return_type) = return_type { + Ok(return_type) + } else { + Ok(vm.ctx.none()) } } } -#[pyclass(flags(BASETYPE), with(Callable, Constructor))] -impl PyCFuncPtr { - #[pygetset] - fn __name__(&self) -> String { - self.name.read().clone() +impl Representable for PyCFuncPtr { + fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { + let index = zelf.ptr.read(); + let index = index.map(|ptr| ptr.0 as usize).unwrap_or(0); + let type_name = zelf.class().name(); + #[cfg(windows)] + { + let index = index - 0x1000; + return Ok(format!("<COM method offset {index:#x} {type_name}>")); + } + Ok(format!("<{type_name} object at {index:#x}>")) } +} - #[pygetset(setter)] - fn set___name__(&self, name: String) { - *self.name.write() = name; - } +// TODO: fix +unsafe impl Send for PyCFuncPtr {} +unsafe impl Sync for PyCFuncPtr {} +#[pyclass(flags(BASETYPE), with(Callable, Constructor, Representable))] +impl PyCFuncPtr { #[pygetset(name = "_restype_")] - fn restype(&self) -> Option<PyTypeRef> { - self._restype_.read().as_ref().cloned() + fn restype(&self) -> Option<PyObjectRef> { + self.res_type.read().as_ref().cloned() } #[pygetset(name = "_restype_", setter)] - fn set_restype(&self, restype: PyTypeRef) { - *self._restype_.write() = Some(restype); + fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // has to be type, callable, or none + // TODO: Callable support + if vm.is_none(&restype) || restype.downcast_ref::<PyType>().is_some() { + *self.res_type.write() = Some(restype); + Ok(()) + } else { + Err(vm.new_type_error("restype must be a type, a callable, or None".to_string())) + } + } + + #[pygetset(name = "argtypes")] + fn argtypes(&self, vm: &VirtualMachine) -> PyTupleRef { + PyTuple::new_ref( + self.arg_types + .read() + .clone() + .unwrap_or_default() + .into_iter() + .map(|t| t.to_pyobject(vm)) + .collect(), + &vm.ctx, + ) + } + + #[pygetset(name = "argtypes", setter)] + fn set_argtypes(&self, argtypes: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let none = vm.is_none(&argtypes); + if none { + *self.arg_types.write() = None; + Ok(()) + } else { + let tuple = argtypes.downcast::<PyTuple>().unwrap(); + *self.arg_types.write() = Some( + tuple + .iter() + .map(|obj| obj.clone().downcast::<PyType>().unwrap()) + .collect::<Vec<_>>(), + ); + Ok(()) + } + } + + #[pygetset] + fn __name__(&self) -> Option<String> { + self.name.read().clone() + } + + #[pygetset(setter)] + fn set___name__(&self, name: String) -> PyResult<()> { + *self.name.write() = Some(name); + // TODO: update handle and stuff + Ok(()) } } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index d1360f9862e..e0723125628 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -1,5 +1,40 @@ -#[pyclass(name = "Pointer", module = "_ctypes")] -pub struct PyCPointer {} +use rustpython_common::lock::PyRwLock; + +use crate::builtins::PyType; +use crate::stdlib::ctypes::PyCData; +use crate::{PyObjectRef, PyResult}; + +#[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] +#[derive(PyPayload, Debug)] +pub struct PyCPointerType { + pub inner: PyCPointer, +} + +#[pyclass] +impl PyCPointerType {} + +#[pyclass( + name = "_Pointer", + base = PyCData, + metaclass = "PyCPointerType", + module = "_ctypes" +)] +#[derive(Debug, PyPayload)] +pub struct PyCPointer { + contents: PyRwLock<PyObjectRef>, +} #[pyclass(flags(BASETYPE, IMMUTABLETYPE))] -impl PyCPointer {} +impl PyCPointer { + // TODO: not correct + #[pygetset] + fn contents(&self) -> PyResult<PyObjectRef> { + let contents = self.contents.read().clone(); + Ok(contents) + } + #[pygetset(setter)] + fn set_contents(&self, contents: PyObjectRef) -> PyResult<()> { + *self.contents.write() = contents; + Ok(()) + } +} diff --git a/crates/vm/src/stdlib/ctypes/thunk.rs b/crates/vm/src/stdlib/ctypes/thunk.rs new file mode 100644 index 00000000000..a65b04684b0 --- /dev/null +++ b/crates/vm/src/stdlib/ctypes/thunk.rs @@ -0,0 +1,22 @@ +//! Yes, really, this is not a typo. + +// typedef struct { +// PyObject_VAR_HEAD +// ffi_closure *pcl_write; /* the C callable, writeable */ +// void *pcl_exec; /* the C callable, executable */ +// ffi_cif cif; +// int flags; +// PyObject *converters; +// PyObject *callable; +// PyObject *restype; +// SETFUNC setfunc; +// ffi_type *ffi_restype; +// ffi_type *atypes[1]; +// } CThunkObject; + +#[pyclass(name = "CThunkObject", module = "_ctypes")] +#[derive(Debug, PyPayload)] +pub struct PyCThunk {} + +#[pyclass] +impl PyCThunk {} diff --git a/crates/vm/src/stdlib/ctypes/util.rs b/crates/vm/src/stdlib/ctypes/util.rs new file mode 100644 index 00000000000..df5d3186682 --- /dev/null +++ b/crates/vm/src/stdlib/ctypes/util.rs @@ -0,0 +1,24 @@ +use crate::PyObjectRef; + +#[pyclass(name, module = "_ctypes")] +#[derive(Debug, PyPayload)] +pub struct StgInfo { + pub initialized: i32, + pub size: usize, // number of bytes + pub align: usize, // alignment requirements + pub length: usize, // number of fields + // ffi_type_pointer: ffi::ffi_type, + pub proto: PyObjectRef, // Only for Pointer/ArrayObject + pub setfunc: Option<PyObjectRef>, // Only for simple objects + pub getfunc: Option<PyObjectRef>, // Only for simple objects + pub paramfunc: Option<PyObjectRef>, + + /* Following fields only used by PyCFuncPtrType_Type instances */ + pub argtypes: Option<PyObjectRef>, // tuple of CDataObjects + pub converters: Option<PyObjectRef>, // tuple([t.from_param for t in argtypes]) + pub restype: Option<PyObjectRef>, // CDataObject or NULL + pub checker: Option<PyObjectRef>, + pub module: Option<PyObjectRef>, + pub flags: i32, // calling convention and such + pub dict_final: u8, +} diff --git a/extra_tests/snippets/stdlib_ctypes.py b/extra_tests/snippets/stdlib_ctypes.py index 32ed17d19f6..3ef6df689fa 100644 --- a/extra_tests/snippets/stdlib_ctypes.py +++ b/extra_tests/snippets/stdlib_ctypes.py @@ -40,7 +40,6 @@ def create_string_buffer(init, size=None): size = len(init) + 1 _sys.audit("ctypes.create_string_buffer", init, size) buftype = c_char.__mul__(size) - print(type(c_char.__mul__(size))) # buftype = c_char * size buf = buftype() buf.value = init @@ -334,8 +333,14 @@ def LoadLibrary(self, name): test_byte_array = create_string_buffer(b"Hello, World!\n") assert test_byte_array._length_ == 15 -if _os.name == "posix" or _sys.platform == "darwin": - pass +if _os.name == "posix": + if _sys.platform == "darwin": + libc = cdll.LoadLibrary("libc.dylib") + libc.rand() + i = c_int(1) + print("start srand") + print(libc.srand(i)) + print(test_byte_array) else: import os From 14cf4e32d04b234549fae3ae0dddb0b34a09f76a Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Fri, 28 Nov 2025 23:41:42 +0900 Subject: [PATCH 377/819] Fix PyCSimple --- crates/vm/src/stdlib/ctypes/base.rs | 5 +++-- crates/vm/src/stdlib/ctypes/field.rs | 2 +- crates/vm/src/stdlib/ctypes/function.rs | 16 ++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index f5a25ad740b..1b07d73f8d2 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -218,11 +218,12 @@ impl Constructor for PyCSimple { _ => vm.ctx.none(), // "z" | "Z" | "P" } }; - Ok(PyCSimple { + PyCSimple { _type_, value: AtomicCell::new(value), } - .to_pyobject(vm)) + .into_ref_with_type(vm, cls) + .map(Into::into) } } diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index b50b8b54ace..20aded85a7e 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -36,7 +36,7 @@ pub struct PyCField { impl Representable for PyCField { fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { let tp_name = zelf.proto.name().to_string(); - if zelf.bitfield_size != false { + if zelf.bitfield_size { Ok(format!( "<{} type={}, ofs={byte_offset}, bit_size={bitfield_size}, bit_offset={bit_offset}", zelf.name, diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 88d0fbb35ea..bb4cc65212e 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -13,7 +13,7 @@ use libffi::middle::{Arg, Cif, CodePtr, Type}; use libloading::Symbol; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; -use std::ffi::{self, CString, c_void}; +use std::ffi::{self, c_void}; use std::fmt::Debug; // See also: https://github.com/python/cpython/blob/4f8bb3947cfbc20f970ff9d9531e1132a9e95396/Modules/_ctypes/callproc.c#L15 @@ -61,6 +61,7 @@ impl ArgumentType for PyTypeRef { pub trait ReturnType { fn to_ffi_type(&self) -> Option<Type>; + #[allow(clippy::wrong_self_convention)] fn from_ffi_type( &self, value: *mut ffi::c_void, @@ -197,9 +198,8 @@ impl Callable for PyCFuncPtr { let return_type = zelf.res_type.read(); let ffi_return_type = return_type .as_ref() - .map(|t| ReturnType::to_ffi_type(&t.clone().downcast::<PyType>().unwrap())) - .flatten() - .unwrap_or_else(|| Type::i32()); + .and_then(|t| ReturnType::to_ffi_type(&t.clone().downcast::<PyType>().unwrap())) + .unwrap_or_else(Type::i32); let cif = Cif::new(ffi_arg_types, ffi_return_type); // Call the function @@ -243,12 +243,12 @@ impl Representable for PyCFuncPtr { let index = zelf.ptr.read(); let index = index.map(|ptr| ptr.0 as usize).unwrap_or(0); let type_name = zelf.class().name(); - #[cfg(windows)] - { + if cfg!(windows) { let index = index - 0x1000; - return Ok(format!("<COM method offset {index:#x} {type_name}>")); + Ok(format!("<COM method offset {index:#x} {type_name}>")) + } else { + Ok(format!("<{type_name} object at {index:#x}>")) } - Ok(format!("<{type_name} object at {index:#x}>")) } } From 8af105fc4ff5f5eb8e9a534b826b27ec47a6a576 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Fri, 28 Nov 2025 23:46:14 +0900 Subject: [PATCH 378/819] Make mergeable --- crates/vm/src/stdlib/ctypes.rs | 1 - crates/vm/src/stdlib/ctypes/field.rs | 11 +---------- crates/vm/src/stdlib/ctypes/function.rs | 2 ++ crates/vm/src/stdlib/ctypes/pointer.rs | 3 ++- crates/vm/src/stdlib/ctypes/util.rs | 24 ------------------------ extra_tests/snippets/stdlib_ctypes.py | 12 ++++++------ 6 files changed, 11 insertions(+), 42 deletions(-) delete mode 100644 crates/vm/src/stdlib/ctypes/util.rs diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index ac74418354e..92629dcadb8 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -9,7 +9,6 @@ pub(crate) mod pointer; pub(crate) mod structure; pub(crate) mod thunk; pub(crate) mod union; -pub(crate) mod util; use crate::builtins::PyModule; use crate::class::PyClassImpl; diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index 20aded85a7e..e4e3bd6a09e 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -8,6 +8,7 @@ use crate::{Py, PyResult, VirtualMachine}; #[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")] #[derive(PyPayload, Debug)] pub struct PyCFieldType { + #[allow(dead_code)] pub(super) inner: PyCField, } @@ -122,13 +123,3 @@ impl PyCField { self.bit_offset } } - -#[inline(always)] -pub const fn low_bit(offset: usize) -> usize { - offset & 0xFFFF -} - -#[inline(always)] -pub const fn high_bit(offset: usize) -> usize { - offset >> 16 -} diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index bb4cc65212e..64a230ad568 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -102,10 +102,12 @@ impl ReturnType for PyNone { pub struct PyCFuncPtr { pub name: PyRwLock<Option<String>>, pub ptr: PyRwLock<Option<CodePtr>>, + #[allow(dead_code)] pub needs_free: AtomicCell<bool>, pub arg_types: PyRwLock<Option<Vec<PyTypeRef>>>, pub res_type: PyRwLock<Option<PyObjectRef>>, pub _flags_: AtomicCell<i32>, + #[allow(dead_code)] pub handler: PyObjectRef, } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index e0723125628..b60280c73c8 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -7,7 +7,8 @@ use crate::{PyObjectRef, PyResult}; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] #[derive(PyPayload, Debug)] pub struct PyCPointerType { - pub inner: PyCPointer, + #[allow(dead_code)] + pub(crate) inner: PyCPointer, } #[pyclass] diff --git a/crates/vm/src/stdlib/ctypes/util.rs b/crates/vm/src/stdlib/ctypes/util.rs deleted file mode 100644 index df5d3186682..00000000000 --- a/crates/vm/src/stdlib/ctypes/util.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::PyObjectRef; - -#[pyclass(name, module = "_ctypes")] -#[derive(Debug, PyPayload)] -pub struct StgInfo { - pub initialized: i32, - pub size: usize, // number of bytes - pub align: usize, // alignment requirements - pub length: usize, // number of fields - // ffi_type_pointer: ffi::ffi_type, - pub proto: PyObjectRef, // Only for Pointer/ArrayObject - pub setfunc: Option<PyObjectRef>, // Only for simple objects - pub getfunc: Option<PyObjectRef>, // Only for simple objects - pub paramfunc: Option<PyObjectRef>, - - /* Following fields only used by PyCFuncPtrType_Type instances */ - pub argtypes: Option<PyObjectRef>, // tuple of CDataObjects - pub converters: Option<PyObjectRef>, // tuple([t.from_param for t in argtypes]) - pub restype: Option<PyObjectRef>, // CDataObject or NULL - pub checker: Option<PyObjectRef>, - pub module: Option<PyObjectRef>, - pub flags: i32, // calling convention and such - pub dict_final: u8, -} diff --git a/extra_tests/snippets/stdlib_ctypes.py b/extra_tests/snippets/stdlib_ctypes.py index 3ef6df689fa..b4bc05dcb4c 100644 --- a/extra_tests/snippets/stdlib_ctypes.py +++ b/extra_tests/snippets/stdlib_ctypes.py @@ -338,9 +338,9 @@ def LoadLibrary(self, name): libc = cdll.LoadLibrary("libc.dylib") libc.rand() i = c_int(1) - print("start srand") - print(libc.srand(i)) - print(test_byte_array) + # print("start srand") + # print(libc.srand(i)) + # print(test_byte_array) else: import os @@ -348,9 +348,9 @@ def LoadLibrary(self, name): libc.rand() i = c_int(1) print("start srand") - print(libc.srand(i)) - print(test_byte_array) - print(test_byte_array._type_) + # print(libc.srand(i)) + # print(test_byte_array) + # print(test_byte_array._type_) # print("start printf") # libc.printf(test_byte_array) From fef9de22c4d31eb062073ba4752aa5a8ed18ad45 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 28 Nov 2025 17:46:27 +0200 Subject: [PATCH 379/819] Remove `Rotate*` & `Duplicate*` instructions (#6303) * Remove `Instruction::Duplicate2?` * Remove `Instruction::Rotate*` instructions * Fix jit * Update snapshot --- crates/codegen/src/compile.rs | 38 +++++++++-------- ...pile__tests__nested_double_async_with.snap | 2 +- crates/compiler-core/src/bytecode.rs | 11 ----- crates/jit/tests/common.rs | 12 ------ crates/vm/src/frame.rs | 41 ++++--------------- 5 files changed, 30 insertions(+), 74 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 5f8caebf272..b2dc10cda6c 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -1063,7 +1063,7 @@ impl Compiler { if let Stmt::Expr(StmtExpr { value, .. }) = &last { self.compile_expression(value)?; - emit!(self, Instruction::Duplicate); + emit!(self, Instruction::CopyItem { index: 1_u32 }); emit!(self, Instruction::PrintExpr); } else { self.compile_statement(last)?; @@ -1094,7 +1094,7 @@ impl Compiler { Stmt::FunctionDef(_) | Stmt::ClassDef(_) => { let pop_instructions = self.current_block().instructions.pop(); let store_inst = compiler_unwrap_option(self, pop_instructions); // pop Instruction::Store - emit!(self, Instruction::Duplicate); + emit!(self, Instruction::CopyItem { index: 1_u32 }); self.current_block().instructions.push(store_inst); } _ => self.emit_load_const(ConstantData::None), @@ -1656,7 +1656,7 @@ impl Compiler { for (i, target) in targets.iter().enumerate() { if i + 1 != targets.len() { - emit!(self, Instruction::Duplicate); + emit!(self, Instruction::CopyItem { index: 1_u32 }); } self.compile_store(target)?; } @@ -1917,7 +1917,7 @@ impl Compiler { ); } - emit!(self, Instruction::Duplicate); + emit!(self, Instruction::CopyItem { index: 1_u32 }); self.store_name(name.as_ref())?; } TypeParam::ParamSpec(TypeParamParamSpec { name, default, .. }) => { @@ -1943,7 +1943,7 @@ impl Compiler { ); } - emit!(self, Instruction::Duplicate); + emit!(self, Instruction::CopyItem { index: 1_u32 }); self.store_name(name.as_ref())?; } TypeParam::TypeVarTuple(TypeParamTypeVarTuple { name, default, .. }) => { @@ -1970,7 +1970,7 @@ impl Compiler { ); } - emit!(self, Instruction::Duplicate); + emit!(self, Instruction::CopyItem { index: 1_u32 }); self.store_name(name.as_ref())?; } }; @@ -2030,7 +2030,7 @@ impl Compiler { // check if this handler can handle the exception: if let Some(exc_type) = type_ { // Duplicate exception for test: - emit!(self, Instruction::Duplicate); + emit!(self, Instruction::CopyItem { index: 1_u32 }); // Check exception type: self.compile_expression(exc_type)?; @@ -2251,11 +2251,11 @@ impl Compiler { // Handle docstring if present if let Some(doc) = doc_str { - emit!(self, Instruction::Duplicate); + emit!(self, Instruction::CopyItem { index: 1_u32 }); self.emit_load_const(ConstantData::Str { value: doc.to_string().into(), }); - emit!(self, Instruction::Rotate2); + emit!(self, Instruction::Swap { index: 2 }); let doc_attr = self.name("__doc__"); emit!(self, Instruction::StoreAttr { idx: doc_attr }); } @@ -2726,7 +2726,7 @@ impl Compiler { if let Some(classcell_idx) = classcell_idx { emit!(self, Instruction::LoadClosure(classcell_idx.to_u32())); - emit!(self, Instruction::Duplicate); + emit!(self, Instruction::CopyItem { index: 1_u32 }); let classcell = self.name("__classcell__"); emit!(self, Instruction::StoreLocal(classcell)); } else { @@ -4121,8 +4121,8 @@ impl Compiler { for (op, val) in mid_ops.iter().zip(mid_exprs) { self.compile_expression(val)?; // store rhs for the next comparison in chain - emit!(self, Instruction::Duplicate); - emit!(self, Instruction::Rotate3); + emit!(self, Instruction::Swap { index: 2 }); + emit!(self, Instruction::CopyItem { index: 2_u32 }); compile_cmpop(self, op); @@ -4151,7 +4151,7 @@ impl Compiler { // early exit left us with stack: `rhs, comparison_result`. We need to clean up rhs. self.switch_to_block(break_block); - emit!(self, Instruction::Rotate2); + emit!(self, Instruction::Swap { index: 2 }); emit!(self, Instruction::Pop); self.switch_to_block(after_block); @@ -4322,7 +4322,8 @@ impl Compiler { // But we can't use compile_subscript directly because we need DUP_TOP2 self.compile_expression(value)?; self.compile_expression(slice)?; - emit!(self, Instruction::Duplicate2); + emit!(self, Instruction::CopyItem { index: 2_u32 }); + emit!(self, Instruction::CopyItem { index: 2_u32 }); emit!(self, Instruction::Subscript); AugAssignKind::Subscript } @@ -4330,7 +4331,7 @@ impl Compiler { let attr = attr.as_str(); self.check_forbidden_name(attr, NameUsage::Store)?; self.compile_expression(value)?; - emit!(self, Instruction::Duplicate); + emit!(self, Instruction::CopyItem { index: 1_u32 }); let idx = self.name(attr); emit!(self, Instruction::LoadAttr { idx }); AugAssignKind::Attr { idx } @@ -4350,12 +4351,13 @@ impl Compiler { } AugAssignKind::Subscript => { // stack: CONTAINER SLICE RESULT - emit!(self, Instruction::Rotate3); + emit!(self, Instruction::Swap { index: 3 }); + emit!(self, Instruction::Swap { index: 2 }); emit!(self, Instruction::StoreSubscript); } AugAssignKind::Attr { idx } => { // stack: CONTAINER RESULT - emit!(self, Instruction::Rotate2); + emit!(self, Instruction::Swap { index: 2 }); emit!(self, Instruction::StoreAttr { idx }); } } @@ -4896,7 +4898,7 @@ impl Compiler { range: _, }) => { self.compile_expression(value)?; - emit!(self, Instruction::Duplicate); + emit!(self, Instruction::CopyItem { index: 1_u32 }); self.compile_store(target)?; } Expr::FString(fstring) => { diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index 82c02d5ea34..dc624c7b6a1 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap @@ -49,7 +49,7 @@ expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyn 39 WithCleanupFinish 40 PopBlock 41 Jump (59) - >> 42 Duplicate + >> 42 CopyItem (1) 6 43 LoadNameAny (7, Exception) 44 TestOperation (ExceptionMatch) diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 2cfb70e2782..fe28517cda8 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -614,10 +614,6 @@ pub enum Instruction { index: Arg<u32>, }, ToBool, - Rotate2, - Rotate3, - Duplicate, - Duplicate2, GetIter, GetLen, CallIntrinsic1 { @@ -1475,9 +1471,6 @@ impl Instruction { Pop => -1, Swap { .. } => 0, ToBool => 0, - Rotate2 | Rotate3 => 0, - Duplicate => 1, - Duplicate2 => 2, GetIter => 0, GetLen => 1, CallIntrinsic1 { .. } => 0, // Takes 1, pushes 1 @@ -1678,10 +1671,6 @@ impl Instruction { Pop => w!(Pop), Swap { index } => w!(Swap, index), ToBool => w!(ToBool), - Rotate2 => w!(Rotate2), - Rotate3 => w!(Rotate3), - Duplicate => w!(Duplicate), - Duplicate2 => w!(Duplicate2), GetIter => w!(GetIter), // GET_LEN GetLen => w!(GetLen), diff --git a/crates/jit/tests/common.rs b/crates/jit/tests/common.rs index a88d3207f2a..5dafeaeb807 100644 --- a/crates/jit/tests/common.rs +++ b/crates/jit/tests/common.rs @@ -164,18 +164,6 @@ impl StackMachine { self.stack.push(StackValue::Function(func)); } } - Instruction::Duplicate => { - let value = self.stack.last().unwrap().clone(); - self.stack.push(value); - } - Instruction::Rotate2 => { - let i = self.stack.len() - 2; - self.stack[i..].rotate_right(1); - } - Instruction::Rotate3 => { - let i = self.stack.len() - 3; - self.stack[i..].rotate_right(1); - } Instruction::ReturnConst { idx } => { let idx = idx.get(arg); self.stack.push(constants[idx as usize].clone().into()); diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index ab90f52b2df..8deaada0827 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -719,32 +719,16 @@ impl ExecutingFrame<'_> { self.state.stack.swap(i, j); Ok(None) } - // bytecode::Instruction::ToBool => { - // dbg!("Shouldn't be called outside of match statements for now") - // let value = self.pop_value(); - // // call __bool__ - // let result = value.try_to_bool(vm)?; - // self.push_value(vm.ctx.new_bool(result).into()); - // Ok(None) - // } - bytecode::Instruction::Duplicate => { - // Duplicate top of stack - let value = self.top_value(); - self.push_value(value.to_owned()); - Ok(None) - } - bytecode::Instruction::Duplicate2 => { - // Duplicate top 2 of stack - let len = self.state.stack.len(); - self.push_value(self.state.stack[len - 2].clone()); - self.push_value(self.state.stack[len - 1].clone()); - Ok(None) + /* + bytecode::Instruction::ToBool => { + dbg!("Shouldn't be called outside of match statements for now") + let value = self.pop_value(); + // call __bool__ + let result = value.try_to_bool(vm)?; + self.push_value(vm.ctx.new_bool(result).into()); + Ok(None) } - // splitting the instructions like this offloads the cost of "dynamic" dispatch (on the - // amount to rotate) to the opcode dispatcher, and generates optimized code for the - // concrete cases we actually have - bytecode::Instruction::Rotate2 => self.execute_rotate(2), - bytecode::Instruction::Rotate3 => self.execute_rotate(3), + */ bytecode::Instruction::BuildString { size } => { let s = self .pop_multiple(size.get(arg) as usize) @@ -1685,13 +1669,6 @@ impl ExecutingFrame<'_> { } } - #[inline(always)] - fn execute_rotate(&mut self, amount: usize) -> FrameResult { - let i = self.state.stack.len() - amount; - self.state.stack[i..].rotate_right(1); - Ok(None) - } - fn execute_subscript(&mut self, vm: &VirtualMachine) -> FrameResult { let b_ref = self.pop_value(); let a_ref = self.pop_value(); From 23ec5a55adc7053a105235cd2dfe6ee078c9e712 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 29 Nov 2025 01:32:52 +0900 Subject: [PATCH 380/819] Fix import ctypes and Lib/ctypes/__init__.py from cpython3.13.9 (#6304) * Fix ctypes import blockers * Update Lib/ctypes/__init__.py from cpython3.13.9 --- Lib/ctypes/__init__.py | 39 ++++--- crates/vm/src/stdlib/ctypes.rs | 79 +++++++++++-- crates/vm/src/stdlib/ctypes/array.rs | 7 +- crates/vm/src/stdlib/ctypes/base.rs | 15 +++ crates/vm/src/stdlib/ctypes/function.rs | 148 ++++++++++++++++-------- 5 files changed, 211 insertions(+), 77 deletions(-) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index a5d27daff0b..3599e13ed28 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -347,6 +347,19 @@ def __init__(self, name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=None): + if name: + name = _os.fspath(name) + + # If the filename that has been provided is an iOS/tvOS/watchOS + # .fwork file, dereference the location to the true origin of the + # binary. + if name.endswith(".fwork"): + with open(name) as f: + name = _os.path.join( + _os.path.dirname(_sys.executable), + f.read().strip() + ) + self._name = name flags = self._func_flags_ if use_errno: @@ -467,6 +480,8 @@ def LoadLibrary(self, name): if _os.name == "nt": pythonapi = PyDLL("python dll", None, _sys.dllhandle) +elif _sys.platform == "android": + pythonapi = PyDLL("libpython%d.%d.so" % _sys.version_info[:2]) elif _sys.platform == "cygwin": pythonapi = PyDLL("libpython%d.%d.dll" % _sys.version_info[:2]) else: @@ -498,15 +513,14 @@ def WinError(code=None, descr=None): c_ssize_t = c_longlong # functions + from _ctypes import _memmove_addr, _memset_addr, _string_at_addr, _cast_addr ## void *memmove(void *, const void *, size_t); -# XXX: RUSTPYTHON -# memmove = CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_size_t)(_memmove_addr) +memmove = CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_size_t)(_memmove_addr) ## void *memset(void *, int, size_t) -# XXX: RUSTPYTHON -# memset = CFUNCTYPE(c_void_p, c_void_p, c_int, c_size_t)(_memset_addr) +memset = CFUNCTYPE(c_void_p, c_void_p, c_int, c_size_t)(_memset_addr) def PYFUNCTYPE(restype, *argtypes): class CFunctionType(_CFuncPtr): @@ -515,17 +529,15 @@ class CFunctionType(_CFuncPtr): _flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI return CFunctionType -# XXX: RUSTPYTHON -# _cast = PYFUNCTYPE(py_object, c_void_p, py_object, py_object)(_cast_addr) +_cast = PYFUNCTYPE(py_object, c_void_p, py_object, py_object)(_cast_addr) def cast(obj, typ): return _cast(obj, obj, typ) -# XXX: RUSTPYTHON -# _string_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_string_at_addr) +_string_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_string_at_addr) def string_at(ptr, size=-1): - """string_at(addr[, size]) -> string + """string_at(ptr[, size]) -> string - Return the string at addr.""" + Return the byte string at void *ptr.""" return _string_at(ptr, size) try: @@ -533,12 +545,11 @@ def string_at(ptr, size=-1): except ImportError: pass else: - # XXX: RUSTPYTHON - # _wstring_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_wstring_at_addr) + _wstring_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_wstring_at_addr) def wstring_at(ptr, size=-1): - """wstring_at(addr[, size]) -> string + """wstring_at(ptr[, size]) -> string - Return the string at addr.""" + Return the wide-character string at void *ptr.""" return _wstring_at(ptr, size) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 92629dcadb8..500ddbfb06b 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -189,6 +189,7 @@ pub(crate) mod _ctypes { } } + #[cfg(windows)] #[pyfunction(name = "LoadLibrary")] fn load_library_windows( name: String, @@ -203,20 +204,33 @@ pub(crate) mod _ctypes { Ok(id) } + #[cfg(not(windows))] #[pyfunction(name = "dlopen")] fn load_library_unix( - name: String, + name: Option<String>, _load_flags: OptionalArg<i32>, vm: &VirtualMachine, ) -> PyResult<usize> { // TODO: audit functions first // TODO: load_flags - let cache = library::libcache(); - let mut cache_write = cache.write(); - let (id, _) = cache_write - .get_or_insert_lib(&name, vm) - .map_err(|e| vm.new_os_error(e.to_string()))?; - Ok(id) + match name { + Some(name) => { + let cache = library::libcache(); + let mut cache_write = cache.write(); + let (id, _) = cache_write + .get_or_insert_lib(&name, vm) + .map_err(|e| vm.new_os_error(e.to_string()))?; + Ok(id) + } + None => { + // If None, call libc::dlopen(null, mode) to get the current process handle + let handle = unsafe { libc::dlopen(std::ptr::null(), libc::RTLD_NOW) }; + if handle.is_null() { + return Err(vm.new_os_error("dlopen() error")); + } + Ok(handle as usize) + } + } } #[pyfunction(name = "FreeLibrary")] @@ -228,10 +242,57 @@ pub(crate) mod _ctypes { } #[pyfunction(name = "POINTER")] - pub fn pointer(_cls: PyTypeRef) {} + pub fn create_pointer_type(cls: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // Get the _pointer_type_cache + let ctypes_module = vm.import("_ctypes", 0)?; + let cache = ctypes_module.get_attr("_pointer_type_cache", vm)?; + + // Check if already in cache using __getitem__ + if let Ok(cached) = vm.call_method(&cache, "__getitem__", (cls.clone(),)) + && !vm.is_none(&cached) + { + return Ok(cached); + } + + // Get the _Pointer base class + let pointer_base = ctypes_module.get_attr("_Pointer", vm)?; + + // Create the name for the pointer type + let name = if let Ok(type_obj) = cls.get_attr("__name__", vm) { + format!("LP_{}", type_obj.str(vm)?) + } else if let Ok(s) = cls.str(vm) { + format!("LP_{}", s) + } else { + "LP_unknown".to_string() + }; + + // Create a new type that inherits from _Pointer + let type_type = &vm.ctx.types.type_type; + let bases = vm.ctx.new_tuple(vec![pointer_base]); + let dict = vm.ctx.new_dict(); + dict.set_item("_type_", cls.clone(), vm)?; + + let new_type = type_type + .as_object() + .call((vm.ctx.new_str(name), bases, dict), vm)?; + + // Store in cache using __setitem__ + vm.call_method(&cache, "__setitem__", (cls, new_type.clone()))?; + + Ok(new_type) + } #[pyfunction(name = "pointer")] - pub fn pointer_fn(_inst: PyObjectRef) {} + pub fn create_pointer_inst(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // Get the type of the object + let obj_type = obj.class().to_owned(); + + // Create pointer type for this object's type + let ptr_type = create_pointer_type(obj_type.into(), vm)?; + + // Create an instance of the pointer type with the object + ptr_type.call((obj,), vm) + } #[pyfunction] fn _pointer_type_cache() -> PyObjectRef { diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index a1adf847a99..a46322460a9 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -72,13 +72,14 @@ impl std::fmt::Debug for PyCArray { impl Constructor for PyCArray { type Args = (PyTypeRef, usize); - fn py_new(_cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - Ok(Self { + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + Self { typ: PyRwLock::new(args.0), length: AtomicCell::new(args.1), value: PyRwLock::new(vm.ctx.none()), } - .into_pyobject(vm)) + .into_ref_with_type(vm, cls) + .map(Into::into) } } diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 1b07d73f8d2..29f3b9da2ae 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -171,6 +171,21 @@ impl PyCSimpleType { .clone(), )) } + + #[pyclassmethod] + fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // If the value is already an instance of the requested type, return it + if value.fast_isinstance(&cls) { + return Ok(value); + } + + // Check for _as_parameter_ attribute + let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) else { + return Err(vm.new_type_error("wrong type")); + }; + + PyCSimpleType::from_param(cls, as_parameter, vm) + } } #[pyclass( diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 64a230ad568..c1db4d58f8d 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -120,60 +120,106 @@ impl Debug for PyCFuncPtr { } impl Constructor for PyCFuncPtr { - type Args = (PyTupleRef, FuncArgs); + type Args = FuncArgs; - fn py_new(_cls: PyTypeRef, (tuple, _args): Self::Args, vm: &VirtualMachine) -> PyResult { - let name = tuple - .first() - .ok_or(vm.new_type_error("Expected a tuple with at least 2 elements"))? - .downcast_ref::<PyStr>() - .ok_or(vm.new_type_error("Expected a string"))? - .to_string(); - let handler = tuple - .into_iter() - .nth(1) - .ok_or(vm.new_type_error("Expected a tuple with at least 2 elements"))? - .clone(); - let handle = handler.try_int(vm); - let handle = match handle { - Ok(handle) => handle.as_bigint().clone(), - Err(_) => handler - .get_attr("_handle", vm)? - .try_int(vm)? - .as_bigint() - .clone(), - }; - let library_cache = crate::stdlib::ctypes::library::libcache().read(); - let library = library_cache - .get_lib( - handle - .to_usize() - .ok_or(vm.new_value_error("Invalid handle".to_string()))?, - ) - .ok_or_else(|| vm.new_value_error("Library not found".to_string()))?; - let inner_lib = library.lib.lock(); - - let terminated = format!("{}\0", &name); - let code_ptr = if let Some(lib) = &*inner_lib { - let pointer: Symbol<'_, FP> = unsafe { - lib.get(terminated.as_bytes()) - .map_err(|err| err.to_string()) - .map_err(|err| vm.new_attribute_error(err))? + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Handle different argument forms like CPython: + // 1. Empty args: create uninitialized + // 2. One integer argument: function address + // 3. Tuple argument: (name, dll) form + + if args.args.is_empty() { + return Self { + ptr: PyRwLock::new(None), + needs_free: AtomicCell::new(false), + arg_types: PyRwLock::new(None), + _flags_: AtomicCell::new(0), + res_type: PyRwLock::new(None), + name: PyRwLock::new(None), + handler: vm.ctx.none(), + } + .into_ref_with_type(vm, cls) + .map(Into::into); + } + + let first_arg = &args.args[0]; + + // Check if first argument is an integer (function address) + if let Ok(addr) = first_arg.try_int(vm) { + let ptr_val = addr.as_bigint().to_usize().unwrap_or(0); + return Self { + ptr: PyRwLock::new(Some(CodePtr(ptr_val as *mut _))), + needs_free: AtomicCell::new(false), + arg_types: PyRwLock::new(None), + _flags_: AtomicCell::new(0), + res_type: PyRwLock::new(None), + name: PyRwLock::new(Some(format!("CFuncPtr@{:#x}", ptr_val))), + handler: vm.ctx.new_int(ptr_val).into(), + } + .into_ref_with_type(vm, cls) + .map(Into::into); + } + + // Check if first argument is a tuple (name, dll) form + if let Some(tuple) = first_arg.downcast_ref::<PyTuple>() { + let name = tuple + .first() + .ok_or(vm.new_type_error("Expected a tuple with at least 2 elements"))? + .downcast_ref::<PyStr>() + .ok_or(vm.new_type_error("Expected a string"))? + .to_string(); + let handler = tuple + .iter() + .nth(1) + .ok_or(vm.new_type_error("Expected a tuple with at least 2 elements"))? + .clone(); + + // Get library handle and load function + let handle = handler.try_int(vm); + let handle = match handle { + Ok(handle) => handle.as_bigint().clone(), + Err(_) => handler + .get_attr("_handle", vm)? + .try_int(vm)? + .as_bigint() + .clone(), }; - Some(CodePtr(*pointer as *mut _)) - } else { - None - }; - Ok(Self { - ptr: PyRwLock::new(code_ptr), - needs_free: AtomicCell::new(false), - arg_types: PyRwLock::new(None), - _flags_: AtomicCell::new(0), - res_type: PyRwLock::new(None), - name: PyRwLock::new(Some(name)), - handler, + let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library = library_cache + .get_lib( + handle + .to_usize() + .ok_or(vm.new_value_error("Invalid handle".to_string()))?, + ) + .ok_or_else(|| vm.new_value_error("Library not found".to_string()))?; + let inner_lib = library.lib.lock(); + + let terminated = format!("{}\0", &name); + let code_ptr = if let Some(lib) = &*inner_lib { + let pointer: Symbol<'_, FP> = unsafe { + lib.get(terminated.as_bytes()) + .map_err(|err| err.to_string()) + .map_err(|err| vm.new_attribute_error(err))? + }; + Some(CodePtr(*pointer as *mut _)) + } else { + None + }; + + return Self { + ptr: PyRwLock::new(code_ptr), + needs_free: AtomicCell::new(false), + arg_types: PyRwLock::new(None), + _flags_: AtomicCell::new(0), + res_type: PyRwLock::new(None), + name: PyRwLock::new(Some(name)), + handler, + } + .into_ref_with_type(vm, cls) + .map(Into::into); } - .to_pyobject(vm)) + + Err(vm.new_type_error("Expected an integer address or a tuple")) } } From a81912857d6151689ff8cb62dd7fec320cf33212 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 29 Nov 2025 02:15:25 +0900 Subject: [PATCH 381/819] Ctypes __mul__ (#6305) --- crates/vm/src/stdlib/ctypes/base.rs | 52 +++++++++++++++++++++----- crates/vm/src/stdlib/ctypes/pointer.rs | 49 ++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 13 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 29f3b9da2ae..6fdb79e11e9 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -3,8 +3,9 @@ use crate::builtins::PyType; use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::{Either, OptionalArg}; +use crate::protocol::PyNumberMethods; use crate::stdlib::ctypes::_ctypes::new_simple_type; -use crate::types::Constructor; +use crate::types::{AsNumber, Constructor}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use num_traits::ToPrimitive; @@ -158,9 +159,10 @@ pub struct PyCData { impl PyCData {} #[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)] +#[derive(Debug, PyPayload)] pub struct PyCSimpleType {} -#[pyclass(flags(BASETYPE))] +#[pyclass(flags(BASETYPE), with(AsNumber))] impl PyCSimpleType { #[allow(clippy::new_ret_no_self)] #[pymethod] @@ -186,6 +188,33 @@ impl PyCSimpleType { PyCSimpleType::from_param(cls, as_parameter, vm) } + + #[pymethod] + fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { + PyCSimple::repeat(cls, n, vm) + } +} + +impl AsNumber for PyCSimpleType { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + multiply: Some(|a, b, vm| { + // a is a PyCSimpleType instance (type object like c_char) + // b is int (array size) + let cls = a + .downcast_ref::<PyType>() + .ok_or_else(|| vm.new_type_error("expected type".to_owned()))?; + let n = b + .try_index(vm)? + .as_bigint() + .to_isize() + .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; + PyCSimple::repeat(cls.to_owned(), n, vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } } #[pyclass( @@ -215,8 +244,18 @@ impl Constructor for PyCSimple { let attributes = cls.get_attributes(); let _type_ = attributes .iter() - .find(|(k, _)| k.to_object().str(vm).unwrap().to_string() == *"_type_") - .unwrap() + .find(|(k, _)| { + k.to_object() + .str(vm) + .map(|s| s.to_string() == "_type_") + .unwrap_or(false) + }) + .ok_or_else(|| { + vm.new_type_error(format!( + "cannot create '{}' instances: no _type_ attribute", + cls.name() + )) + })? .1 .str(vm)? .to_string(); @@ -276,11 +315,6 @@ impl PyCSimple { } .to_pyobject(vm)) } - - #[pyclassmethod] - fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - PyCSimple::repeat(cls, n, vm) - } } impl PyCSimple { diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index b60280c73c8..3eb6f68e6dd 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -1,8 +1,13 @@ +use crossbeam_utils::atomic::AtomicCell; +use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; -use crate::builtins::PyType; +use crate::builtins::{PyType, PyTypeRef}; +use crate::convert::ToPyObject; +use crate::protocol::PyNumberMethods; use crate::stdlib::ctypes::PyCData; -use crate::{PyObjectRef, PyResult}; +use crate::types::AsNumber; +use crate::{PyObjectRef, PyResult, VirtualMachine}; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] #[derive(PyPayload, Debug)] @@ -11,8 +16,44 @@ pub struct PyCPointerType { pub(crate) inner: PyCPointer, } -#[pyclass] -impl PyCPointerType {} +#[pyclass(flags(IMMUTABLETYPE), with(AsNumber))] +impl PyCPointerType { + #[pymethod] + fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { + use super::array::{PyCArray, PyCArrayType}; + if n < 0 { + return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); + } + Ok(PyCArrayType { + inner: PyCArray { + typ: PyRwLock::new(cls), + length: AtomicCell::new(n as usize), + value: PyRwLock::new(vm.ctx.none()), + }, + } + .to_pyobject(vm)) + } +} + +impl AsNumber for PyCPointerType { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + multiply: Some(|a, b, vm| { + let cls = a + .downcast_ref::<PyType>() + .ok_or_else(|| vm.new_type_error("expected type".to_owned()))?; + let n = b + .try_index(vm)? + .as_bigint() + .to_isize() + .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; + PyCPointerType::__mul__(cls.to_owned(), n, vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} #[pyclass( name = "_Pointer", From 7d8f0b989c542ee856615ee7e569898081312479 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 29 Nov 2025 02:02:52 +0200 Subject: [PATCH 382/819] Split `TestOperator` instruction (#6306) * Split `TestOperator` inatruction * Update snapshot * Set as have label --- crates/codegen/src/compile.rs | 48 +++-------- ...pile__tests__nested_double_async_with.snap | 69 ++++++++-------- crates/compiler-core/src/bytecode.rs | 54 ++++++++----- crates/vm/src/frame.rs | 81 +++++++++++-------- 4 files changed, 123 insertions(+), 129 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index b2dc10cda6c..4a4f17edcfa 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -36,7 +36,7 @@ use rustpython_compiler_core::{ Mode, OneIndexed, PositionEncoding, SourceFile, SourceLocation, bytecode::{ self, Arg as OpArgMarker, BinaryOperator, CodeObject, ComparisonOperator, ConstantData, - Instruction, OpArg, OpArgType, UnpackExArgs, + Instruction, Invert, OpArg, OpArgType, UnpackExArgs, }, }; use rustpython_wtf8::Wtf8Buf; @@ -2034,20 +2034,7 @@ impl Compiler { // Check exception type: self.compile_expression(exc_type)?; - emit!( - self, - Instruction::TestOperation { - op: bytecode::TestOperator::ExceptionMatch, - } - ); - - // We cannot handle this exception type: - emit!( - self, - Instruction::PopJumpIfFalse { - target: next_handler, - } - ); + emit!(self, Instruction::JumpIfNotExcMatch(next_handler)); // We have a match, store in name (except x as y) if let Some(alias) = name { @@ -3477,12 +3464,7 @@ impl Compiler { // 4. Load None. self.emit_load_const(ConstantData::None); // 5. Compare with IS_OP 1. - emit!( - self, - Instruction::TestOperation { - op: bytecode::TestOperator::IsNot - } - ); + emit!(self, Instruction::IsOp(Invert::Yes)); // At this point the TOS is a tuple of (nargs + n_attrs) attributes (or None). pc.on_top += 1; @@ -3648,12 +3630,8 @@ impl Compiler { // Check if copy is None (consumes the copy like POP_JUMP_IF_NONE) self.emit_load_const(ConstantData::None); - emit!( - self, - Instruction::TestOperation { - op: bytecode::TestOperator::IsNot - } - ); + emit!(self, Instruction::IsOp(Invert::Yes)); + // Stack: [subject, keys_tuple, values_tuple, bool] self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?; // Stack: [subject, keys_tuple, values_tuple] @@ -3948,12 +3926,7 @@ impl Compiler { Singleton::True => ConstantData::Boolean { value: true }, }); // Compare using the "Is" operator. - emit!( - self, - Instruction::TestOperation { - op: bytecode::TestOperator::Is - } - ); + emit!(self, Instruction::IsOp(Invert::No)); // Jump to the failure label if the comparison is false. self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?; Ok(()) @@ -4082,7 +4055,6 @@ impl Compiler { let (last_val, mid_exprs) = exprs.split_last().unwrap(); use bytecode::ComparisonOperator::*; - use bytecode::TestOperator::*; let compile_cmpop = |c: &mut Self, op: &CmpOp| match op { CmpOp::Eq => emit!(c, Instruction::CompareOperation { op: Equal }), CmpOp::NotEq => emit!(c, Instruction::CompareOperation { op: NotEqual }), @@ -4092,10 +4064,10 @@ impl Compiler { CmpOp::GtE => { emit!(c, Instruction::CompareOperation { op: GreaterOrEqual }) } - CmpOp::In => emit!(c, Instruction::TestOperation { op: In }), - CmpOp::NotIn => emit!(c, Instruction::TestOperation { op: NotIn }), - CmpOp::Is => emit!(c, Instruction::TestOperation { op: Is }), - CmpOp::IsNot => emit!(c, Instruction::TestOperation { op: IsNot }), + CmpOp::In => emit!(c, Instruction::ContainsOp(Invert::No)), + CmpOp::NotIn => emit!(c, Instruction::ContainsOp(Invert::Yes)), + CmpOp::Is => emit!(c, Instruction::IsOp(Invert::No)), + CmpOp::IsNot => emit!(c, Instruction::IsOp(Invert::Yes)), }; // a == b == c == d diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index dc624c7b6a1..3042e35f179 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap @@ -1,5 +1,5 @@ --- -source: compiler/codegen/src/compile.rs +source: crates/codegen/src/compile.rs expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\")" --- 1 0 SetupLoop @@ -11,7 +11,7 @@ expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyn 6 CallFunctionPositional(1) 7 BuildTuple (2) 8 GetIter - >> 9 ForIter (73) + >> 9 ForIter (72) 10 StoreLocal (2, stop_exc) 2 11 LoadNameAny (3, self) @@ -21,7 +21,7 @@ expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyn 15 CallFunctionPositional(1) 16 LoadConst (("type")) 17 CallMethodKeyword (1) - 18 SetupWith (70) + 18 SetupWith (69) 19 Pop 3 20 SetupExcept (42) @@ -48,41 +48,40 @@ expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyn 38 Resume (3) 39 WithCleanupFinish 40 PopBlock - 41 Jump (59) + 41 Jump (58) >> 42 CopyItem (1) 6 43 LoadNameAny (7, Exception) - 44 TestOperation (ExceptionMatch) - 45 PopJumpIfFalse (58) - 46 StoreLocal (8, ex) + 44 JUMP_IF_NOT_EXC_MATCH(57) + 45 StoreLocal (8, ex) - 7 47 LoadNameAny (3, self) - 48 LoadMethod (9, assertIs) - 49 LoadNameAny (8, ex) - 50 LoadNameAny (2, stop_exc) - 51 CallMethodPositional (2) - 52 Pop - 53 PopException - 54 LoadConst (None) - 55 StoreLocal (8, ex) - 56 DeleteLocal (8, ex) - 57 Jump (68) - >> 58 Raise (Reraise) + 7 46 LoadNameAny (3, self) + 47 LoadMethod (9, assertIs) + 48 LoadNameAny (8, ex) + 49 LoadNameAny (2, stop_exc) + 50 CallMethodPositional (2) + 51 Pop + 52 PopException + 53 LoadConst (None) + 54 StoreLocal (8, ex) + 55 DeleteLocal (8, ex) + 56 Jump (67) + >> 57 Raise (Reraise) - 9 >> 59 LoadNameAny (3, self) - 60 LoadMethod (10, fail) - 61 LoadConst ("") - 62 LoadNameAny (2, stop_exc) - 63 FormatValue (None) - 64 LoadConst (" was suppressed") - 65 BuildString (2) - 66 CallMethodPositional (1) - 67 Pop + 9 >> 58 LoadNameAny (3, self) + 59 LoadMethod (10, fail) + 60 LoadConst ("") + 61 LoadNameAny (2, stop_exc) + 62 FormatValue (None) + 63 LoadConst (" was suppressed") + 64 BuildString (2) + 65 CallMethodPositional (1) + 66 Pop - 2 >> 68 PopBlock - 69 EnterFinally - >> 70 WithCleanupStart - 71 WithCleanupFinish - 72 Jump (9) - >> 73 PopBlock - 74 ReturnConst (None) + 2 >> 67 PopBlock + 68 EnterFinally + >> 69 WithCleanupStart + 70 WithCleanupFinish + 71 Jump (9) + >> 72 PopBlock + 73 ReturnConst (None) diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index fe28517cda8..144054860e4 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -577,6 +577,10 @@ pub enum Instruction { Subscript, StoreSubscript, DeleteSubscript, + /// Performs `is` comparison, or `is not` if `invert` is 1. + IsOp(Arg<Invert>), + /// Performs `in` comparison, or `not in` if `invert` is 1. + ContainsOp(Arg<Invert>), StoreAttr { idx: Arg<NameIdx>, }, @@ -600,9 +604,6 @@ pub enum Instruction { LoadAttr { idx: Arg<NameIdx>, }, - TestOperation { - op: Arg<TestOperator>, - }, CompareOperation { op: Arg<ComparisonOperator>, }, @@ -628,6 +629,10 @@ pub enum Instruction { Break { target: Arg<Label>, }, + /// Performs exception matching for except. + /// Tests whether the STACK[-2] is an exception matching STACK[-1]. + /// Pops STACK[-1] and pushes the boolean result of the test. + JumpIfNotExcMatch(Arg<Label>), Jump { target: Arg<Label>, }, @@ -1088,19 +1093,6 @@ op_arg_enum!( } ); -op_arg_enum!( - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - #[repr(u8)] - pub enum TestOperator { - In = 0, - NotIn = 1, - Is = 2, - IsNot = 3, - /// two exceptions that match? - ExceptionMatch = 4, - } -); - op_arg_enum!( /// The possible Binary operators /// # Examples @@ -1141,6 +1133,24 @@ op_arg_enum!( } ); +op_arg_enum!( + /// Whether or not to invert the operation. + #[repr(u8)] + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub enum Invert { + /// ```py + /// foo is bar + /// x in lst + /// ``` + No = 0, + /// ```py + /// foo is not bar + /// x not in lst + /// ``` + Yes = 1, + } +); + #[derive(Copy, Clone)] pub struct UnpackExArgs { pub before: u8, @@ -1395,6 +1405,7 @@ impl Instruction { pub const fn label_arg(&self) -> Option<Arg<Label>> { match self { Jump { target: l } + | JumpIfNotExcMatch(l) | PopJumpIfTrue { target: l } | PopJumpIfFalse { target: l } | JumpIfTrueOrPop { target: l } @@ -1462,10 +1473,7 @@ impl Instruction { DeleteAttr { .. } => -1, LoadConst { .. } => 1, UnaryOperation { .. } => 0, - BinaryOperation { .. } - | BinaryOperationInplace { .. } - | TestOperation { .. } - | CompareOperation { .. } => -1, + BinaryOperation { .. } | BinaryOperationInplace { .. } | CompareOperation { .. } => -1, BinarySubscript => -1, CopyItem { .. } => 1, Pop => -1, @@ -1508,6 +1516,8 @@ impl Instruction { 1 } } + IsOp(_) | ContainsOp(_) => -1, + JumpIfNotExcMatch(_) => -2, ReturnValue => -1, ReturnConst { .. } => 0, Resume { .. } => 0, @@ -1654,6 +1664,9 @@ impl Instruction { DeleteGlobal(idx) => w!(DeleteGlobal, name = idx), DeleteDeref(idx) => w!(DeleteDeref, cell_name = idx), LoadClosure(i) => w!(LoadClosure, cell_name = i), + IsOp(inv) => w!(IS_OP, ?inv), + ContainsOp(inv) => w!(CONTAINS_OP, ?inv), + JumpIfNotExcMatch(target) => w!(JUMP_IF_NOT_EXC_MATCH, target), Subscript => w!(Subscript), StoreSubscript => w!(StoreSubscript), DeleteSubscript => w!(DeleteSubscript), @@ -1665,7 +1678,6 @@ impl Instruction { BinaryOperationInplace { op } => w!(BinaryOperationInplace, ?op), BinarySubscript => w!(BinarySubscript), LoadAttr { idx } => w!(LoadAttr, name = idx), - TestOperation { op } => w!(TestOperation, ?op), CompareOperation { op } => w!(CompareOperation, ?op), CopyItem { index } => w!(CopyItem, index), Pop => w!(Pop), diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 8deaada0827..148abacf6ad 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -884,7 +884,52 @@ impl ExecutingFrame<'_> { bytecode::Instruction::StoreAttr { idx } => self.store_attr(vm, idx.get(arg)), bytecode::Instruction::DeleteAttr { idx } => self.delete_attr(vm, idx.get(arg)), bytecode::Instruction::UnaryOperation { op } => self.execute_unary_op(vm, op.get(arg)), - bytecode::Instruction::TestOperation { op } => self.execute_test(vm, op.get(arg)), + bytecode::Instruction::IsOp(invert) => { + let b = self.pop_value(); + let a = self.pop_value(); + let res = a.is(&b); + + let value = match invert.get(arg) { + bytecode::Invert::No => res, + bytecode::Invert::Yes => !res, + }; + self.push_value(vm.ctx.new_bool(value).into()); + Ok(None) + } + bytecode::Instruction::ContainsOp(invert) => { + let b = self.pop_value(); + let a = self.pop_value(); + + let value = match invert.get(arg) { + bytecode::Invert::No => self._in(vm, &a, &b)?, + bytecode::Invert::Yes => self._not_in(vm, &a, &b)?, + }; + self.push_value(vm.ctx.new_bool(value).into()); + Ok(None) + } + bytecode::Instruction::JumpIfNotExcMatch(target) => { + let b = self.pop_value(); + let a = self.pop_value(); + if let Some(tuple_of_exceptions) = b.downcast_ref::<PyTuple>() { + for exception in tuple_of_exceptions { + if !exception + .is_subclass(vm.ctx.exceptions.base_exception_type.into(), vm)? + { + return Err(vm.new_type_error( + "catching classes that do not inherit from BaseException is not allowed", + )); + } + } + } else if !b.is_subclass(vm.ctx.exceptions.base_exception_type.into(), vm)? { + return Err(vm.new_type_error( + "catching classes that do not inherit from BaseException is not allowed", + )); + } + + let value = a.is_instance(&b, vm)?; + self.push_value(vm.ctx.new_bool(value).into()); + self.pop_jump_if(vm, target.get(arg), false) + } bytecode::Instruction::CompareOperation { op } => self.execute_compare(vm, op.get(arg)), bytecode::Instruction::ReturnValue => { let value = self.pop_value(); @@ -2234,40 +2279,6 @@ impl ExecutingFrame<'_> { Ok(!self._in(vm, needle, haystack)?) } - #[cfg_attr(feature = "flame-it", flame("Frame"))] - fn execute_test(&mut self, vm: &VirtualMachine, op: bytecode::TestOperator) -> FrameResult { - let b = self.pop_value(); - let a = self.pop_value(); - let value = match op { - bytecode::TestOperator::Is => a.is(&b), - bytecode::TestOperator::IsNot => !a.is(&b), - bytecode::TestOperator::In => self._in(vm, &a, &b)?, - bytecode::TestOperator::NotIn => self._not_in(vm, &a, &b)?, - bytecode::TestOperator::ExceptionMatch => { - if let Some(tuple_of_exceptions) = b.downcast_ref::<PyTuple>() { - for exception in tuple_of_exceptions { - if !exception - .is_subclass(vm.ctx.exceptions.base_exception_type.into(), vm)? - { - return Err(vm.new_type_error( - "catching classes that do not inherit from BaseException is not allowed", - )); - } - } - } else if !b.is_subclass(vm.ctx.exceptions.base_exception_type.into(), vm)? { - return Err(vm.new_type_error( - "catching classes that do not inherit from BaseException is not allowed", - )); - } - - a.is_instance(&b, vm)? - } - }; - - self.push_value(vm.ctx.new_bool(value).into()); - Ok(None) - } - #[cfg_attr(feature = "flame-it", flame("Frame"))] fn execute_compare( &mut self, From 95b8c60756aad27a5968df6fb7a2c69de084dbdd Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:54:04 +0900 Subject: [PATCH 383/819] Fix __build_class__ compatibility (#6308) * Update __build_class__ * fix test --- Lib/test/test_descr.py | 2 -- Lib/test/test_super.py | 4 --- crates/vm/src/stdlib/builtins.rs | 54 +++++++++++++++++++++----------- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 6f1849738c9..8a424c8be8e 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -479,8 +479,6 @@ def __getitem__(self, i): self.assertEqual(a[2], 102) self.assertEqual(a[100:200], (100,200)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_metaclass(self): # Testing metaclasses... class C(metaclass=type): diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index ca8dc6e869d..3fd5833d8be 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -162,8 +162,6 @@ def f(): self.assertIs(test_class, A) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test___class___delayed(self): # See issue #23722 test_namespace = None @@ -236,8 +234,6 @@ def f(self): with self.assertRaises(AttributeError): WithClassRef.__classcell__ - # TODO: RUSTPYTHON - @unittest.expectedFailure def test___classcell___missing(self): # See issue #23722 # Some metaclasses may not pass the original namespace to type.__new__ diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index dfc3e5a5326..d115c027955 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -892,13 +892,12 @@ mod builtins { #[pyfunction] pub fn __build_class__( function: PyRef<PyFunction>, - qualified_name: PyStrRef, + name: PyStrRef, bases: PosArgs, mut kwargs: KwArgs, vm: &VirtualMachine, ) -> PyResult { - let name = qualified_name.as_str().split('.').next_back().unwrap(); - let name_obj = vm.ctx.new_str(name); + let name_obj: PyObjectRef = name.clone().into(); // Update bases. let mut new_bases: Option<Vec<PyObjectRef>> = None; @@ -942,20 +941,33 @@ mod builtins { .downcast_exact::<PyType>(vm) .map(|m| m.into_pyref()) }) - .unwrap_or_else(|| Ok(vm.ctx.types.type_type.to_owned())); + .unwrap_or_else(|| { + // if there are no bases, use type; else get the type of the first base + Ok(if bases.is_empty() { + vm.ctx.types.type_type.to_owned() + } else { + bases.first().unwrap().class().to_owned() + }) + }); let (metaclass, meta_name) = match metaclass { Ok(mut metaclass) => { for base in bases.iter() { let base_class = base.class(); + // if winner is subtype of tmptype, continue (winner is more derived) + if metaclass.fast_issubclass(base_class) { + continue; + } + // if tmptype is subtype of winner, update (tmptype is more derived) if base_class.fast_issubclass(&metaclass) { - metaclass = base.class().to_owned(); - } else if !metaclass.fast_issubclass(base_class) { - return Err(vm.new_type_error( - "metaclass conflict: the metaclass of a derived class must be a (non-strict) \ - subclass of the metaclasses of all its bases", - )); + metaclass = base_class.to_owned(); + continue; } + // Metaclass conflict + return Err(vm.new_type_error( + "metaclass conflict: the metaclass of a derived class must be a (non-strict) \ + subclass of the metaclasses of all its bases", + )); } let meta_name = metaclass.slot_name(); (metaclass.to_owned().into(), meta_name.to_owned()) @@ -969,8 +981,7 @@ mod builtins { let namespace = vm .get_attribute_opt(metaclass.clone(), identifier!(vm, __prepare__))? .map_or(Ok(vm.ctx.new_dict().into()), |prepare| { - let args = - FuncArgs::new(vec![name_obj.clone().into(), bases.clone()], kwargs.clone()); + let args = FuncArgs::new(vec![name_obj.clone(), bases.clone()], kwargs.clone()); prepare.call(args, vm) })?; @@ -1013,7 +1024,7 @@ mod builtins { .del_item(vm.ctx.intern_str(".type_params"), vm) .ok(); - let args = FuncArgs::new(vec![name_obj.into(), bases, namespace.into()], kwargs); + let args = FuncArgs::new(vec![name_obj, bases, namespace.into()], kwargs); let class = metaclass.call(args, vm)?; // For PEP 695 classes, set __type_params__ on the class from the function @@ -1028,16 +1039,21 @@ mod builtins { class.set_attr(identifier!(vm, __parameters__), type_params, vm)?; } - if let Some(ref classcell) = classcell { - let classcell = classcell.get().ok_or_else(|| { - vm.new_type_error(format!( - "__class__ not set defining {meta_name:?} as {class:?}. Was __classcell__ propagated to type.__new__?" + // only check cell if cls is a type and cell is a cell object + if let Some(ref classcell) = classcell + && class.fast_isinstance(vm.ctx.types.type_type) + { + let cell_value = classcell.get().ok_or_else(|| { + vm.new_runtime_error(format!( + "__class__ not set defining {:?} as {:?}. Was __classcell__ propagated to type.__new__?", + name, class )) })?; - if !classcell.is(&class) { + if !cell_value.is(&class) { return Err(vm.new_type_error(format!( - "__class__ set to {classcell:?} defining {meta_name:?} as {class:?}" + "__class__ set to {:?} defining {:?} as {:?}", + cell_value, name, class ))); } } From 4fcce4d0b9be51e98ec154793bcfb8de7f7c8d79 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:13:49 +0900 Subject: [PATCH 384/819] ctypes struct/union/array (#6309) * PyCStructure * impl union * array * temp remove * apply review --- crates/vm/src/stdlib/ctypes.rs | 64 ++++- crates/vm/src/stdlib/ctypes/array.rs | 348 +++++++++++++++++++++-- crates/vm/src/stdlib/ctypes/base.rs | 19 +- crates/vm/src/stdlib/ctypes/field.rs | 217 ++++++++++++-- crates/vm/src/stdlib/ctypes/pointer.rs | 5 +- crates/vm/src/stdlib/ctypes/structure.rs | 333 +++++++++++++++++++--- crates/vm/src/stdlib/ctypes/union.rs | 116 +++++++- 7 files changed, 1003 insertions(+), 99 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 500ddbfb06b..7e9557458cf 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -21,6 +21,8 @@ pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py<PyModule>) { array::PyCArrayType::make_class(ctx); field::PyCFieldType::make_class(ctx); pointer::PyCPointerType::make_class(ctx); + structure::PyCStructType::make_class(ctx); + union::PyCUnionType::make_class(ctx); extend_module!(vm, module, { "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PyCSimple::make_class(ctx), @@ -46,9 +48,10 @@ pub(crate) mod _ctypes { use super::base::PyCSimple; use crate::builtins::PyTypeRef; use crate::class::StaticType; + use crate::convert::ToPyObject; use crate::function::{Either, FuncArgs, OptionalArg}; use crate::stdlib::ctypes::library; - use crate::{AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine}; + use crate::{AsObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use std::ffi::{ c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, @@ -57,6 +60,23 @@ pub(crate) mod _ctypes { use std::mem; use widestring::WideChar; + /// CArgObject - returned by byref() + #[pyclass(name = "CArgObject", module = "_ctypes", no_attr)] + #[derive(Debug, PyPayload)] + pub struct CArgObject { + pub obj: PyObjectRef, + #[allow(dead_code)] + pub offset: isize, + } + + #[pyclass] + impl CArgObject { + #[pygetset] + fn _obj(&self) -> PyObjectRef { + self.obj.clone() + } + } + #[pyattr(name = "__version__")] const __VERSION__: &str = "1.1.0"; @@ -322,9 +342,28 @@ pub(crate) mod _ctypes { } #[pyfunction] - fn byref(_args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - // TODO: RUSTPYTHON - Err(vm.new_value_error("not implemented")) + fn byref(obj: PyObjectRef, offset: OptionalArg<isize>, vm: &VirtualMachine) -> PyResult { + use super::base::PyCData; + use crate::class::StaticType; + + // Check if obj is a ctypes instance + if !obj.fast_isinstance(PyCData::static_type()) + && !obj.fast_isinstance(PyCSimple::static_type()) + { + return Err(vm.new_type_error(format!( + "byref() argument must be a ctypes instance, not '{}'", + obj.class().name() + ))); + } + + let offset_val = offset.unwrap_or(0); + + // Create CArgObject to hold the reference + Ok(CArgObject { + obj, + offset: offset_val, + } + .to_pyobject(vm)) } #[pyfunction] @@ -380,9 +419,24 @@ pub(crate) mod _ctypes { f as usize } + #[pyattr] + fn _wstring_at_addr(_vm: &VirtualMachine) -> usize { + // Return address of wcsnlen or similar wide string function + #[cfg(not(target_os = "windows"))] + { + let f = libc::wcslen; + f as usize + } + #[cfg(target_os = "windows")] + { + // FIXME: On Windows, use wcslen from ucrt + 0 + } + } + #[pyattr] fn _cast_addr(_vm: &VirtualMachine) -> usize { - // TODO: RUSTPYTHON + // todo!("Implement _cast_addr") 0 } } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index a46322460a9..73217d7a6a6 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -1,13 +1,19 @@ -use crate::builtins::PyBytes; -use crate::types::Callable; -use crate::{Py, PyObjectRef, PyPayload}; +use crate::atomic_func; +use crate::builtins::{PyBytes, PyInt}; +use crate::convert::ToPyObject; +use crate::function::FuncArgs; +use crate::protocol::{PyNumberMethods, PySequenceMethods}; +use crate::types::{AsNumber, AsSequence, Callable}; +use crate::{AsObject, Py, PyObjectRef, PyPayload}; use crate::{ PyResult, VirtualMachine, builtins::{PyType, PyTypeRef}, types::Constructor, }; use crossbeam_utils::atomic::AtomicCell; +use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; +use rustpython_vm::stdlib::ctypes::_ctypes::get_size; use rustpython_vm::stdlib::ctypes::base::PyCData; #[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")] @@ -25,12 +31,34 @@ impl std::fmt::Debug for PyCArrayType { } impl Callable for PyCArrayType { - type Args = (); - fn call(zelf: &Py<Self>, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + type Args = FuncArgs; + fn call(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Create an instance of the array + let element_type = zelf.inner.typ.read().clone(); + let length = zelf.inner.length.load(); + let element_size = zelf.inner.element_size.load(); + let total_size = element_size * length; + let mut buffer = vec![0u8; total_size]; + + // Initialize from positional arguments + for (i, value) in args.args.iter().enumerate() { + if i >= length { + break; + } + let offset = i * element_size; + if let Ok(int_val) = value.try_int(vm) { + let bytes = PyCArray::int_to_bytes(int_val.as_bigint(), element_size); + if offset + element_size <= buffer.len() { + buffer[offset..offset + element_size].copy_from_slice(&bytes); + } + } + } + Ok(PyCArray { - typ: PyRwLock::new(zelf.inner.typ.read().clone()), - length: AtomicCell::new(zelf.inner.length.load()), - value: PyRwLock::new(zelf.inner.value.read().clone()), + typ: PyRwLock::new(element_type), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(buffer), } .into_pyobject(vm)) } @@ -44,8 +72,69 @@ impl Constructor for PyCArrayType { } } -#[pyclass(flags(IMMUTABLETYPE), with(Callable, Constructor))] -impl PyCArrayType {} +#[pyclass(flags(IMMUTABLETYPE), with(Callable, Constructor, AsNumber))] +impl PyCArrayType { + #[pygetset(name = "_type_")] + fn typ(&self) -> PyTypeRef { + self.inner.typ.read().clone() + } + + #[pygetset(name = "_length_")] + fn length(&self) -> usize { + self.inner.length.load() + } + + #[pymethod] + fn __mul__(zelf: &Py<Self>, n: isize, vm: &VirtualMachine) -> PyResult { + if n < 0 { + return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); + } + // Create a nested array type: (inner_type * inner_length) * n + // The new array has n elements, each element is the current array type + // e.g., (c_int * 5) * 3 = Array of 3 elements, each is (c_int * 5) + let inner_length = zelf.inner.length.load(); + let inner_element_size = zelf.inner.element_size.load(); + + // The element type of the new array is the current array type itself + let obj_ref: PyObjectRef = zelf.to_owned().into(); + let current_array_type = obj_ref + .downcast::<PyType>() + .expect("PyCArrayType should be a PyType"); + + // Element size is the total size of the inner array + let new_element_size = inner_length * inner_element_size; + + Ok(PyCArrayType { + inner: PyCArray { + typ: PyRwLock::new(current_array_type), + length: AtomicCell::new(n as usize), + element_size: AtomicCell::new(new_element_size), + buffer: PyRwLock::new(vec![]), + }, + } + .to_pyobject(vm)) + } +} + +impl AsNumber for PyCArrayType { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + multiply: Some(|a, b, vm| { + let zelf = a + .downcast_ref::<PyCArrayType>() + .ok_or_else(|| vm.new_type_error("expected PyCArrayType".to_owned()))?; + let n = b + .try_index(vm)? + .as_bigint() + .to_isize() + .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; + PyCArrayType::__mul__(zelf, n, vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} #[pyclass( name = "Array", @@ -57,7 +146,8 @@ impl PyCArrayType {} pub struct PyCArray { pub(super) typ: PyRwLock<PyTypeRef>, pub(super) length: AtomicCell<usize>, - pub(super) value: PyRwLock<PyObjectRef>, + pub(super) element_size: AtomicCell<usize>, + pub(super) buffer: PyRwLock<Vec<u8>>, } impl std::fmt::Debug for PyCArray { @@ -70,48 +160,254 @@ impl std::fmt::Debug for PyCArray { } impl Constructor for PyCArray { - type Args = (PyTypeRef, usize); + type Args = FuncArgs; fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - Self { - typ: PyRwLock::new(args.0), - length: AtomicCell::new(args.1), - value: PyRwLock::new(vm.ctx.none()), + // Get _type_ and _length_ from the class + let type_attr = cls.as_object().get_attr("_type_", vm).ok(); + let length_attr = cls.as_object().get_attr("_length_", vm).ok(); + + let element_type = type_attr.unwrap_or_else(|| vm.ctx.types.object_type.to_owned().into()); + let length = if let Some(len_obj) = length_attr { + len_obj.try_int(vm)?.as_bigint().to_usize().unwrap_or(0) + } else { + 0 + }; + + // Get element size from _type_ + let element_size = if let Ok(type_code) = element_type.get_attr("_type_", vm) { + if let Ok(s) = type_code.str(vm) { + let s = s.to_string(); + if s.len() == 1 { + get_size(&s) + } else { + std::mem::size_of::<usize>() + } + } else { + std::mem::size_of::<usize>() + } + } else { + std::mem::size_of::<usize>() + }; + + let total_size = element_size * length; + let mut buffer = vec![0u8; total_size]; + + // Initialize from positional arguments + for (i, value) in args.args.iter().enumerate() { + if i >= length { + break; + } + let offset = i * element_size; + if let Ok(int_val) = value.try_int(vm) { + let bytes = Self::int_to_bytes(int_val.as_bigint(), element_size); + if offset + element_size <= buffer.len() { + buffer[offset..offset + element_size].copy_from_slice(&bytes); + } + } + } + + let element_type_ref = element_type + .downcast::<PyType>() + .unwrap_or_else(|_| vm.ctx.types.object_type.to_owned()); + + PyCArray { + typ: PyRwLock::new(element_type_ref), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(buffer), } .into_ref_with_type(vm, cls) .map(Into::into) } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] +impl AsSequence for PyCArray { + fn as_sequence() -> &'static PySequenceMethods { + use std::sync::LazyLock; + static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods { + length: atomic_func!(|seq, _vm| Ok(PyCArray::sequence_downcast(seq).length.load())), + item: atomic_func!(|seq, i, vm| { + PyCArray::getitem_by_index(PyCArray::sequence_downcast(seq), i, vm) + }), + ass_item: atomic_func!(|seq, i, value, vm| { + let zelf = PyCArray::sequence_downcast(seq); + match value { + Some(v) => PyCArray::setitem_by_index(zelf, i, v, vm), + None => Err(vm.new_type_error("cannot delete array elements".to_owned())), + } + }), + ..PySequenceMethods::NOT_IMPLEMENTED + }); + &AS_SEQUENCE + } +} + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsSequence))] impl PyCArray { + fn int_to_bytes(i: &malachite_bigint::BigInt, size: usize) -> Vec<u8> { + match size { + 1 => vec![i.to_i8().unwrap_or(0) as u8], + 2 => i.to_i16().unwrap_or(0).to_ne_bytes().to_vec(), + 4 => i.to_i32().unwrap_or(0).to_ne_bytes().to_vec(), + 8 => i.to_i64().unwrap_or(0).to_ne_bytes().to_vec(), + _ => vec![0u8; size], + } + } + + fn bytes_to_int(bytes: &[u8], size: usize, vm: &VirtualMachine) -> PyObjectRef { + match size { + 1 => vm.ctx.new_int(bytes[0] as i8).into(), + 2 => { + let val = i16::from_ne_bytes([bytes[0], bytes[1]]); + vm.ctx.new_int(val).into() + } + 4 => { + let val = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + vm.ctx.new_int(val).into() + } + 8 => { + let val = i64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ]); + vm.ctx.new_int(val).into() + } + _ => vm.ctx.new_int(0).into(), + } + } + + fn getitem_by_index(zelf: &PyCArray, i: isize, vm: &VirtualMachine) -> PyResult { + let length = zelf.length.load() as isize; + let index = if i < 0 { length + i } else { i }; + if index < 0 || index >= length { + return Err(vm.new_index_error("array index out of range".to_owned())); + } + let index = index as usize; + let element_size = zelf.element_size.load(); + let offset = index * element_size; + let buffer = zelf.buffer.read(); + if offset + element_size <= buffer.len() { + let bytes = &buffer[offset..offset + element_size]; + Ok(Self::bytes_to_int(bytes, element_size, vm)) + } else { + Ok(vm.ctx.new_int(0).into()) + } + } + + fn setitem_by_index( + zelf: &PyCArray, + i: isize, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + let length = zelf.length.load() as isize; + let index = if i < 0 { length + i } else { i }; + if index < 0 || index >= length { + return Err(vm.new_index_error("array index out of range".to_owned())); + } + let index = index as usize; + let element_size = zelf.element_size.load(); + let offset = index * element_size; + + let int_val = value.try_int(vm)?; + let bytes = Self::int_to_bytes(int_val.as_bigint(), element_size); + + let mut buffer = zelf.buffer.write(); + if offset + element_size <= buffer.len() { + buffer[offset..offset + element_size].copy_from_slice(&bytes); + } + Ok(()) + } + + #[pymethod] + fn __getitem__(&self, index: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(i) = index.downcast_ref::<PyInt>() { + let i = i.as_bigint().to_isize().ok_or_else(|| { + vm.new_index_error("cannot fit index into an index-sized integer".to_owned()) + })?; + Self::getitem_by_index(self, i, vm) + } else { + Err(vm.new_type_error("array indices must be integers".to_owned())) + } + } + + #[pymethod] + fn __setitem__( + &self, + index: PyObjectRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + if let Some(i) = index.downcast_ref::<PyInt>() { + let i = i.as_bigint().to_isize().ok_or_else(|| { + vm.new_index_error("cannot fit index into an index-sized integer".to_owned()) + })?; + Self::setitem_by_index(self, i, value, vm) + } else { + Err(vm.new_type_error("array indices must be integers".to_owned())) + } + } + + #[pymethod] + fn __len__(&self) -> usize { + self.length.load() + } + #[pygetset(name = "_type_")] fn typ(&self) -> PyTypeRef { self.typ.read().clone() } #[pygetset(name = "_length_")] - fn length(&self) -> usize { + fn length_getter(&self) -> usize { self.length.load() } #[pygetset] - fn value(&self) -> PyObjectRef { - self.value.read().clone() + fn value(&self, vm: &VirtualMachine) -> PyObjectRef { + // Return bytes representation of the buffer + let buffer = self.buffer.read(); + vm.ctx.new_bytes(buffer.clone()).into() } #[pygetset(setter)] - fn set_value(&self, value: PyObjectRef) { - *self.value.write() = value; + fn set_value(&self, value: PyObjectRef, _vm: &VirtualMachine) -> PyResult<()> { + if let Some(bytes) = value.downcast_ref::<PyBytes>() { + let mut buffer = self.buffer.write(); + let src = bytes.as_bytes(); + let len = std::cmp::min(src.len(), buffer.len()); + buffer[..len].copy_from_slice(&src[..len]); + } + Ok(()) + } + + #[pygetset] + fn raw(&self, vm: &VirtualMachine) -> PyObjectRef { + let buffer = self.buffer.read(); + vm.ctx.new_bytes(buffer.clone()).into() + } + + #[pygetset(setter)] + fn set_raw(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + if let Some(bytes) = value.downcast_ref::<PyBytes>() { + let mut buffer = self.buffer.write(); + let src = bytes.as_bytes(); + let len = std::cmp::min(src.len(), buffer.len()); + buffer[..len].copy_from_slice(&src[..len]); + Ok(()) + } else { + Err(vm.new_type_error("expected bytes".to_owned())) + } } } impl PyCArray { #[allow(unused)] pub fn to_arg(&self, _vm: &VirtualMachine) -> PyResult<libffi::middle::Arg> { - let value = self.value.read(); - let py_bytes = value.downcast_ref::<PyBytes>().unwrap(); - let bytes = py_bytes.payload().to_vec(); - Ok(libffi::middle::Arg::new(&bytes)) + // TODO: This needs a different approach to ensure buffer lifetime + // The buffer must outlive the Arg returned here + let buffer = self.buffer.read(); + let ptr = buffer.as_ptr(); + Ok(libffi::middle::Arg::new(&ptr)) } } diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 6fdb79e11e9..73d97a2593d 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -303,14 +303,31 @@ impl PyCSimple { #[pyclassmethod] fn repeat(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { + use super::_ctypes::get_size; if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } + // Get element size from cls + let element_size = if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) { + if let Ok(s) = type_attr.str(vm) { + let s = s.to_string(); + if s.len() == 1 { + get_size(&s) + } else { + std::mem::size_of::<usize>() + } + } else { + std::mem::size_of::<usize>() + } + } else { + std::mem::size_of::<usize>() + }; Ok(PyCArrayType { inner: PyCArray { typ: PyRwLock::new(cls), length: AtomicCell::new(n as usize), - value: PyRwLock::new(vm.ctx.none()), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(vec![]), }, } .to_pyobject(vm)) diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index e4e3bd6a09e..5b4837cf6e3 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -1,9 +1,10 @@ -use crate::builtins::PyType; -use crate::builtins::PyTypeRef; -use crate::stdlib::ctypes::PyCData; -use crate::types::Constructor; -use crate::types::Representable; -use crate::{Py, PyResult, VirtualMachine}; +use crate::builtins::{PyType, PyTypeRef}; +use crate::function::PySetterValue; +use crate::types::{Constructor, GetDescriptor, Representable}; +use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; +use num_traits::ToPrimitive; + +use super::structure::PyCStructure; #[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")] #[derive(PyPayload, Debug)] @@ -15,28 +16,55 @@ pub struct PyCFieldType { #[pyclass] impl PyCFieldType {} -#[pyclass( - name = "CField", - base = PyCData, - metaclass = "PyCFieldType", - module = "_ctypes" -)] +#[pyclass(name = "CField", module = "_ctypes")] #[derive(Debug, PyPayload)] pub struct PyCField { - byte_offset: usize, - byte_size: usize, + pub(super) byte_offset: usize, + pub(super) byte_size: usize, #[allow(unused)] - index: usize, - proto: PyTypeRef, - anonymous: bool, - bitfield_size: bool, - bit_offset: u8, - name: String, + pub(super) index: usize, + /// The ctypes type for this field (can be any ctypes type including arrays) + pub(super) proto: PyObjectRef, + pub(super) anonymous: bool, + pub(super) bitfield_size: bool, + pub(super) bit_offset: u8, + pub(super) name: String, +} + +impl PyCField { + pub fn new( + name: String, + proto: PyObjectRef, + byte_offset: usize, + byte_size: usize, + index: usize, + ) -> Self { + Self { + name, + proto, + byte_offset, + byte_size, + index, + anonymous: false, + bitfield_size: false, + bit_offset: 0, + } + } } impl Representable for PyCField { - fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { - let tp_name = zelf.proto.name().to_string(); + fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> { + // Get type name from the proto object + let tp_name = if let Some(name_attr) = vm + .ctx + .interned_str("__name__") + .and_then(|s| zelf.proto.get_attr(s, vm).ok()) + { + name_attr.str(vm)?.to_string() + } else { + zelf.proto.class().name().to_string() + }; + if zelf.bitfield_size { Ok(format!( "<{} type={}, ofs={byte_offset}, bit_size={bitfield_size}, bit_offset={bit_offset}", @@ -71,8 +99,149 @@ impl Constructor for PyCField { } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, Representable))] +impl GetDescriptor for PyCField { + fn descr_get( + zelf: PyObjectRef, + obj: Option<PyObjectRef>, + _cls: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult { + let zelf = zelf + .downcast::<PyCField>() + .map_err(|_| vm.new_type_error("expected CField".to_owned()))?; + + // If obj is None, return the descriptor itself (class attribute access) + let obj = match obj { + Some(obj) if !vm.is_none(&obj) => obj, + _ => return Ok(zelf.into()), + }; + + // Instance attribute access - read value from the structure's buffer + if let Some(structure) = obj.downcast_ref::<PyCStructure>() { + let buffer = structure.buffer.read(); + let offset = zelf.byte_offset; + let size = zelf.byte_size; + + if offset + size <= buffer.len() { + let bytes = &buffer[offset..offset + size]; + return PyCField::bytes_to_value(bytes, size, vm); + } + } + + // Fallback: return 0 for uninitialized or unsupported types + Ok(vm.ctx.new_int(0).into()) + } +} + impl PyCField { + /// Convert bytes to a Python value based on size + fn bytes_to_value(bytes: &[u8], size: usize, vm: &VirtualMachine) -> PyResult { + match size { + 1 => Ok(vm.ctx.new_int(bytes[0] as i8).into()), + 2 => { + let val = i16::from_ne_bytes([bytes[0], bytes[1]]); + Ok(vm.ctx.new_int(val).into()) + } + 4 => { + let val = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + Ok(vm.ctx.new_int(val).into()) + } + 8 => { + let val = i64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ]); + Ok(vm.ctx.new_int(val).into()) + } + _ => Ok(vm.ctx.new_int(0).into()), + } + } + + /// Convert a Python value to bytes + fn value_to_bytes(value: &PyObjectRef, size: usize, vm: &VirtualMachine) -> PyResult<Vec<u8>> { + if let Ok(int_val) = value.try_int(vm) { + let i = int_val.as_bigint(); + match size { + 1 => { + let val = i.to_i8().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 2 => { + let val = i.to_i16().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 4 => { + let val = i.to_i32().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 8 => { + let val = i.to_i64().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + _ => Ok(vec![0u8; size]), + } + } else { + Ok(vec![0u8; size]) + } + } +} + +#[pyclass( + flags(BASETYPE, IMMUTABLETYPE), + with(Constructor, Representable, GetDescriptor) +)] +impl PyCField { + #[pyslot] + fn descr_set( + zelf: &crate::PyObject, + obj: PyObjectRef, + value: PySetterValue<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + let zelf = zelf + .downcast_ref::<PyCField>() + .ok_or_else(|| vm.new_type_error("expected CField".to_owned()))?; + + // Get the structure instance - use downcast_ref() to access the struct data + if let Some(structure) = obj.downcast_ref::<PyCStructure>() { + match value { + PySetterValue::Assign(value) => { + let offset = zelf.byte_offset; + let size = zelf.byte_size; + let bytes = PyCField::value_to_bytes(&value, size, vm)?; + + let mut buffer = structure.buffer.write(); + if offset + size <= buffer.len() { + buffer[offset..offset + size].copy_from_slice(&bytes); + } + Ok(()) + } + PySetterValue::Delete => { + Err(vm.new_type_error("cannot delete structure field".to_owned())) + } + } + } else { + Err(vm.new_type_error(format!( + "descriptor works only on Structure instances, got {}", + obj.class().name() + ))) + } + } + + #[pymethod] + fn __set__( + zelf: PyObjectRef, + obj: PyObjectRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + Self::descr_set(&zelf, obj, PySetterValue::Assign(value), vm) + } + + #[pymethod] + fn __delete__(zelf: PyObjectRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + Self::descr_set(&zelf, obj, PySetterValue::Delete, vm) + } + #[pygetset] fn size(&self) -> usize { self.byte_size @@ -99,7 +268,7 @@ impl PyCField { } #[pygetset(name = "type")] - fn type_(&self) -> PyTypeRef { + fn type_(&self) -> PyObjectRef { self.proto.clone() } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 3eb6f68e6dd..16d795ea4da 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -24,11 +24,14 @@ impl PyCPointerType { if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } + // Pointer size + let element_size = std::mem::size_of::<usize>(); Ok(PyCArrayType { inner: PyCArray { typ: PyRwLock::new(cls), length: AtomicCell::new(n as usize), - value: PyRwLock::new(vm.ctx.none()), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(vec![]), }, } .to_pyobject(vm)) diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index aef95fe5619..e7180a48e7b 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -1,60 +1,313 @@ use super::base::PyCData; -use crate::builtins::{PyList, PyStr, PyTuple, PyTypeRef}; +use super::field::PyCField; +use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; +use crate::convert::ToPyObject; use crate::function::FuncArgs; -use crate::types::GetAttr; -use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::protocol::PyNumberMethods; +use crate::stdlib::ctypes::_ctypes::get_size; +use crate::types::{AsNumber, Constructor}; +use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crossbeam_utils::atomic::AtomicCell; +use indexmap::IndexMap; +use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; -use rustpython_vm::types::Constructor; -use std::collections::HashMap; use std::fmt::Debug; -#[pyclass(module = "_ctypes", name = "Structure", base = PyCData)] -#[derive(PyPayload, Debug)] -pub struct PyCStructure { - #[allow(dead_code)] - field_data: PyRwLock<HashMap<String, PyObjectRef>>, - data: PyRwLock<HashMap<String, PyObjectRef>>, -} +/// PyCStructType - metaclass for Structure +#[pyclass(name = "PyCStructType", base = PyType, module = "_ctypes")] +#[derive(Debug, PyPayload)] +pub struct PyCStructType {} -impl Constructor for PyCStructure { +impl Constructor for PyCStructType { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { - let fields_attr = cls - .get_class_attr(vm.ctx.interned_str("_fields_").unwrap()) - .ok_or_else(|| vm.new_attribute_error("Structure must have a _fields_ attribute"))?; - // downcast into list - let fields = fields_attr - .downcast_ref::<PyList>() - .ok_or_else(|| vm.new_type_error("Structure _fields_ attribute must be a list"))?; - let fields = fields.borrow_vec(); - let mut field_data = HashMap::new(); - for field in fields.iter() { - let field = field + fn py_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // 1. Create the new class using PyType::py_new + let new_class = crate::builtins::type_::PyType::py_new(metatype, args, vm)?; + + // 2. Process _fields_ if defined on the new class + let new_type = new_class + .clone() + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("expected type"))?; + + // Only process _fields_ if defined directly on this class (not inherited) + if let Some(fields_attr) = new_type.get_direct_attr(vm.ctx.intern_str("_fields_")) { + Self::process_fields(&new_type, fields_attr, vm)?; + } + + Ok(new_class) + } +} + +#[pyclass(flags(BASETYPE), with(AsNumber, Constructor))] +impl PyCStructType { + /// Called when a new Structure subclass is created + #[pyclassmethod] + fn __init_subclass__(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult<()> { + // Check if _fields_ is defined + if let Some(fields_attr) = cls.get_direct_attr(vm.ctx.intern_str("_fields_")) { + Self::process_fields(&cls, fields_attr, vm)?; + } + Ok(()) + } + + /// Process _fields_ and create CField descriptors + fn process_fields( + cls: &PyTypeRef, + fields_attr: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Try to downcast to list or tuple + let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>() { + list.borrow_vec().to_vec() + } else if let Some(tuple) = fields_attr.downcast_ref::<PyTuple>() { + tuple.to_vec() + } else { + return Err(vm.new_type_error("_fields_ must be a list or tuple".to_string())); + }; + + let mut offset = 0usize; + for (index, field) in fields.iter().enumerate() { + let field_tuple = field .downcast_ref::<PyTuple>() - .ok_or_else(|| vm.new_type_error("Field must be a tuple"))?; - let name = field + .ok_or_else(|| vm.new_type_error("_fields_ must contain tuples".to_string()))?; + + if field_tuple.len() < 2 { + return Err(vm.new_type_error( + "_fields_ tuple must have at least 2 elements (name, type)".to_string(), + )); + } + + let name = field_tuple .first() .unwrap() .downcast_ref::<PyStr>() - .ok_or_else(|| vm.new_type_error("Field name must be a string"))?; - let typ = field.get(1).unwrap().clone(); - field_data.insert(name.to_string(), typ); + .ok_or_else(|| vm.new_type_error("field name must be a string".to_string()))? + .to_string(); + + let field_type = field_tuple.get(1).unwrap().clone(); + + // Get size of the field type + let size = Self::get_field_size(&field_type, vm)?; + + // Create CField descriptor (accepts any ctypes type including arrays) + let cfield = PyCField::new(name.clone(), field_type, offset, size, index); + + // Set the CField as a class attribute + cls.set_attr(vm.ctx.intern_str(name), cfield.to_pyobject(vm)); + + offset += size; + } + + Ok(()) + } + + /// Get the size of a ctypes type + fn get_field_size(field_type: &PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { + // Try to get _type_ attribute for simple types + if let Some(size) = field_type + .get_attr("_type_", vm) + .ok() + .and_then(|type_attr| type_attr.str(vm).ok()) + .and_then(|type_str| { + let s = type_str.to_string(); + (s.len() == 1).then(|| get_size(&s)) + }) + { + return Ok(size); + } + + // Try sizeof for other types + if let Some(s) = field_type + .get_attr("size_of_instances", vm) + .ok() + .and_then(|size_method| size_method.call((), vm).ok()) + .and_then(|size| size.try_int(vm).ok()) + .and_then(|n| n.as_bigint().to_usize()) + { + return Ok(s); + } + + // Default to pointer size for unknown types + Ok(std::mem::size_of::<usize>()) + } + + #[pymethod] + fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { + use super::array::{PyCArray, PyCArrayType}; + if n < 0 { + return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); + } + // TODO: Calculate element size properly + // For structures, element size is the structure size (sum of field sizes) + let element_size = std::mem::size_of::<usize>(); // Default, should calculate from fields + Ok(PyCArrayType { + inner: PyCArray { + typ: PyRwLock::new(cls), + length: AtomicCell::new(n as usize), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(vec![]), + }, } - todo!("Implement PyCStructure::py_new") + .to_pyobject(vm)) } } -impl GetAttr for PyCStructure { - fn getattro(zelf: &Py<Self>, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult { - let name = name.to_string(); - let data = zelf.data.read(); - match data.get(&name) { - Some(value) => Ok(value.clone()), - None => Err(vm.new_attribute_error(format!("No attribute named {name}"))), +impl AsNumber for PyCStructType { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + multiply: Some(|a, b, vm| { + let cls = a + .downcast_ref::<PyType>() + .ok_or_else(|| vm.new_type_error("expected type".to_owned()))?; + let n = b + .try_index(vm)? + .as_bigint() + .to_isize() + .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; + PyCStructType::__mul__(cls.to_owned(), n, vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + +/// Structure field info stored in instance +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub struct FieldInfo { + pub name: String, + pub offset: usize, + pub size: usize, + pub type_ref: PyTypeRef, +} + +/// PyCStructure - base class for Structure instances +#[pyclass( + module = "_ctypes", + name = "Structure", + base = PyCData, + metaclass = "PyCStructType" +)] +#[derive(PyPayload)] +pub struct PyCStructure { + /// Raw memory buffer for the structure + pub(super) buffer: PyRwLock<Vec<u8>>, + /// Field information (name -> FieldInfo) + #[allow(dead_code)] + pub(super) fields: PyRwLock<IndexMap<String, FieldInfo>>, + /// Total size of the structure + pub(super) size: AtomicCell<usize>, +} + +impl Debug for PyCStructure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyCStructure") + .field("size", &self.size.load()) + .finish() + } +} + +impl Constructor for PyCStructure { + type Args = FuncArgs; + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Get _fields_ from the class using get_attr to properly search MRO + let fields_attr = cls.as_object().get_attr("_fields_", vm).ok(); + + let mut fields_map = IndexMap::new(); + let mut total_size = 0usize; + + if let Some(fields_attr) = fields_attr { + let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>() + { + list.borrow_vec().to_vec() + } else if let Some(tuple) = fields_attr.downcast_ref::<PyTuple>() { + tuple.to_vec() + } else { + vec![] + }; + + let mut offset = 0usize; + for field in fields.iter() { + let Some(field_tuple) = field.downcast_ref::<PyTuple>() else { + continue; + }; + if field_tuple.len() < 2 { + continue; + } + let Some(name) = field_tuple.first().unwrap().downcast_ref::<PyStr>() else { + continue; + }; + let name = name.to_string(); + let field_type = field_tuple.get(1).unwrap().clone(); + let size = PyCStructType::get_field_size(&field_type, vm)?; + + let type_ref = field_type + .downcast::<PyType>() + .unwrap_or_else(|_| vm.ctx.types.object_type.to_owned()); + + fields_map.insert( + name.clone(), + FieldInfo { + name, + offset, + size, + type_ref, + }, + ); + + offset += size; + } + total_size = offset; + } + + // Initialize buffer with zeros + let buffer = vec![0u8; total_size]; + + let instance = PyCStructure { + buffer: PyRwLock::new(buffer), + fields: PyRwLock::new(fields_map.clone()), + size: AtomicCell::new(total_size), + }; + + // Handle keyword arguments for field initialization + let py_instance = instance.into_ref_with_type(vm, cls.clone())?; + let py_obj: PyObjectRef = py_instance.clone().into(); + + // Set field values from kwargs using standard attribute setting + for (key, value) in args.kwargs.iter() { + if fields_map.contains_key(key.as_str()) { + py_obj.set_attr(vm.ctx.intern_str(key.as_str()), value.clone(), vm)?; + } + } + + // Set field values from positional args + let field_names: Vec<String> = fields_map.keys().cloned().collect(); + for (i, value) in args.args.iter().enumerate() { + if i < field_names.len() { + py_obj.set_attr( + vm.ctx.intern_str(field_names[i].as_str()), + value.clone(), + vm, + )?; + } } + + Ok(py_instance.into()) } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE))] -impl PyCStructure {} +// Note: GetAttr and SetAttr are not implemented here. +// Field access is handled by CField descriptors registered on the class. + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] +impl PyCStructure { + #[pygetset] + fn _fields_(&self, vm: &VirtualMachine) -> PyObjectRef { + // Return the _fields_ from the class, not instance + vm.ctx.none() + } +} diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 93a53b2b6db..a357c195d69 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -1,7 +1,119 @@ use super::base::PyCData; +use super::field::PyCField; +use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; +use crate::convert::ToPyObject; +use crate::function::FuncArgs; +use crate::stdlib::ctypes::_ctypes::get_size; +use crate::types::Constructor; +use crate::{PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use num_traits::ToPrimitive; -// TODO: metaclass = "UnionType" -#[pyclass(module = "_ctypes", name = "Union", base = PyCData)] +/// PyCUnionType - metaclass for Union +#[pyclass(name = "UnionType", base = PyType, module = "_ctypes")] +#[derive(Debug, PyPayload)] +pub struct PyCUnionType {} + +impl Constructor for PyCUnionType { + type Args = FuncArgs; + + fn py_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // 1. Create the new class using PyType::py_new + let new_class = crate::builtins::type_::PyType::py_new(metatype, args, vm)?; + + // 2. Process _fields_ if defined on the new class + let new_type = new_class + .clone() + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("expected type"))?; + + // Only process _fields_ if defined directly on this class (not inherited) + if let Some(fields_attr) = new_type.get_direct_attr(vm.ctx.intern_str("_fields_")) { + Self::process_fields(&new_type, fields_attr, vm)?; + } + + Ok(new_class) + } +} + +impl PyCUnionType { + /// Process _fields_ and create CField descriptors + /// For Union, all fields start at offset 0 + fn process_fields( + cls: &PyTypeRef, + fields_attr: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>() { + list.borrow_vec().to_vec() + } else if let Some(tuple) = fields_attr.downcast_ref::<PyTuple>() { + tuple.to_vec() + } else { + return Err(vm.new_type_error("_fields_ must be a list or tuple".to_string())); + }; + + for (index, field) in fields.iter().enumerate() { + let field_tuple = field + .downcast_ref::<PyTuple>() + .ok_or_else(|| vm.new_type_error("_fields_ must contain tuples".to_string()))?; + + if field_tuple.len() < 2 { + return Err(vm.new_type_error( + "_fields_ tuple must have at least 2 elements (name, type)".to_string(), + )); + } + + let name = field_tuple + .first() + .unwrap() + .downcast_ref::<PyStr>() + .ok_or_else(|| vm.new_type_error("field name must be a string".to_string()))? + .to_string(); + + let field_type = field_tuple.get(1).unwrap().clone(); + let size = Self::get_field_size(&field_type, vm)?; + + // For Union, all fields start at offset 0 + // Create CField descriptor (accepts any ctypes type including arrays) + let cfield = PyCField::new(name.clone(), field_type, 0, size, index); + + cls.set_attr(vm.ctx.intern_str(name), cfield.to_pyobject(vm)); + } + + Ok(()) + } + + fn get_field_size(field_type: &PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { + if let Some(size) = field_type + .get_attr("_type_", vm) + .ok() + .and_then(|type_attr| type_attr.str(vm).ok()) + .and_then(|type_str| { + let s = type_str.to_string(); + (s.len() == 1).then(|| get_size(&s)) + }) + { + return Ok(size); + } + + if let Some(s) = field_type + .get_attr("size_of_instances", vm) + .ok() + .and_then(|size_method| size_method.call((), vm).ok()) + .and_then(|size| size.try_int(vm).ok()) + .and_then(|n| n.as_bigint().to_usize()) + { + return Ok(s); + } + + Ok(std::mem::size_of::<usize>()) + } +} + +#[pyclass(flags(BASETYPE), with(Constructor))] +impl PyCUnionType {} + +/// PyCUnion - base class for Union +#[pyclass(module = "_ctypes", name = "Union", base = PyCData, metaclass = "PyCUnionType")] pub struct PyCUnion {} #[pyclass(flags(BASETYPE, IMMUTABLETYPE))] From e5aec9d7fd13befc0554283c90ec5067dc597db1 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:46:14 +0900 Subject: [PATCH 385/819] DISALLOW_INSTANTIATION (#6310) --- crates/vm/src/stdlib/ctypes/field.rs | 24 +++++------------------- crates/vm/src/types/slot.rs | 1 + 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index 5b4837cf6e3..05e4bdd0354 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -1,6 +1,6 @@ -use crate::builtins::{PyType, PyTypeRef}; +use crate::builtins::PyType; use crate::function::PySetterValue; -use crate::types::{Constructor, GetDescriptor, Representable}; +use crate::types::{GetDescriptor, Representable, Unconstructible}; use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; use num_traits::ToPrimitive; @@ -83,21 +83,7 @@ impl Representable for PyCField { } } -#[derive(Debug, FromArgs)] -pub struct PyCFieldConstructorArgs { - // PyObject *name, PyObject *proto, - // Py_ssize_t byte_size, Py_ssize_t byte_offset, - // Py_ssize_t index, int _internal_use, - // PyObject *bit_size_obj, PyObject *bit_offset_obj -} - -impl Constructor for PyCField { - type Args = PyCFieldConstructorArgs; - - fn py_new(_cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("Cannot instantiate a PyCField".to_string())) - } -} +impl Unconstructible for PyCField {} impl GetDescriptor for PyCField { fn descr_get( @@ -186,8 +172,8 @@ impl PyCField { } #[pyclass( - flags(BASETYPE, IMMUTABLETYPE), - with(Constructor, Representable, GetDescriptor) + flags(DISALLOW_INSTANTIATION, IMMUTABLETYPE), + with(Unconstructible, Representable, GetDescriptor) )] impl PyCField { #[pyslot] diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 44e46fdb5ab..7f1d4e03561 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -125,6 +125,7 @@ bitflags! { const MANAGED_DICT = 1 << 4; const SEQUENCE = 1 << 5; const MAPPING = 1 << 6; + const DISALLOW_INSTANTIATION = 1 << 7; const IMMUTABLETYPE = 1 << 8; const HEAPTYPE = 1 << 9; const BASETYPE = 1 << 10; From 6782fa2219de4b21d0a8686213cf8d425e017d95 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 29 Nov 2025 21:17:04 +0900 Subject: [PATCH 386/819] ctypes buffer (#6311) * Fix array * from buffer * AsBuffer * Fix instruction * constructable pointer * thunk * in_dll * fix sizeof/alignment --- .cspell.dict/cpython.txt | 1 + .github/copilot-instructions.md | 9 +- Lib/ctypes/wintypes.py | 2 +- crates/vm/src/stdlib/ctypes.rs | 465 +++++++++++++- crates/vm/src/stdlib/ctypes/array.rs | 482 +++++++++++++-- crates/vm/src/stdlib/ctypes/base.rs | 735 ++++++++++++++++++++++- crates/vm/src/stdlib/ctypes/field.rs | 45 +- crates/vm/src/stdlib/ctypes/function.rs | 128 +++- crates/vm/src/stdlib/ctypes/pointer.rs | 208 ++++++- crates/vm/src/stdlib/ctypes/structure.rs | 241 +++++++- crates/vm/src/stdlib/ctypes/thunk.rs | 333 +++++++++- crates/vm/src/stdlib/ctypes/union.rs | 226 ++++++- crates/vm/src/stdlib/ctypes/util.rs | 41 ++ 13 files changed, 2721 insertions(+), 195 deletions(-) create mode 100644 crates/vm/src/stdlib/ctypes/util.rs diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 5676549ec1a..19c8725fa66 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -50,6 +50,7 @@ stackdepth stringlib structseq subparams +swappedbytes ticketer tok_oldval tvars diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 46fc16c5ed8..1db02dd17a9 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -44,7 +44,7 @@ cargo run -- script.py cargo run # With specific features -cargo run --features ssl +cargo run --features jit # Release mode (recommended for better performance) cargo run --release -- script.py @@ -176,13 +176,6 @@ cargo build --target wasm32-wasip1 --no-default-features --features freeze-stdli cargo run --features jit ``` -### SSL Support - -```bash -# Enable SSL support -cargo run --features ssl -``` - ## Test Code Modification Rules **CRITICAL: Test code modification restrictions** diff --git a/Lib/ctypes/wintypes.py b/Lib/ctypes/wintypes.py index c619d27596d..9c4e721438a 100644 --- a/Lib/ctypes/wintypes.py +++ b/Lib/ctypes/wintypes.py @@ -1,7 +1,7 @@ # The most useful windows datatypes import ctypes -BYTE = ctypes.c_byte +BYTE = ctypes.c_ubyte WORD = ctypes.c_ushort DWORD = ctypes.c_ulong diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 7e9557458cf..7c35d4a700a 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -9,12 +9,14 @@ pub(crate) mod pointer; pub(crate) mod structure; pub(crate) mod thunk; pub(crate) mod union; +pub(crate) mod util; use crate::builtins::PyModule; use crate::class::PyClassImpl; -use crate::stdlib::ctypes::base::{PyCData, PyCSimple, PyCSimpleType}; use crate::{Py, PyRef, VirtualMachine}; +pub use crate::stdlib::ctypes::base::{PyCData, PyCSimple, PyCSimpleType}; + pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py<PyModule>) { let ctx = &vm.ctx; PyCSimpleType::make_class(ctx); @@ -45,17 +47,17 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { #[pymodule] pub(crate) mod _ctypes { - use super::base::PyCSimple; + use super::base::{CDataObject, PyCSimple}; use crate::builtins::PyTypeRef; use crate::class::StaticType; use crate::convert::ToPyObject; use crate::function::{Either, FuncArgs, OptionalArg}; use crate::stdlib::ctypes::library; - use crate::{AsObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine}; + use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use std::ffi::{ c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, - c_ulonglong, + c_ulonglong, c_ushort, }; use std::mem; use widestring::WideChar; @@ -158,6 +160,192 @@ pub(crate) mod _ctypes { } } + /// Get alignment for a simple type - for C types, alignment equals size + pub fn get_align(ty: &str) -> usize { + get_size(ty) + } + + /// Get the size of a ctypes type from its type object + #[allow(dead_code)] + pub fn get_size_from_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult<usize> { + // Try to get _type_ attribute for simple types + if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) + && let Ok(s) = type_attr.str(vm) + { + let s = s.to_string(); + if s.len() == 1 && SIMPLE_TYPE_CHARS.contains(s.as_str()) { + return Ok(get_size(&s)); + } + } + // Fall back to sizeof + size_of(cls.clone().into(), vm) + } + + /// Convert bytes to appropriate Python object based on ctypes type + pub fn bytes_to_pyobject( + cls: &PyTypeRef, + bytes: &[u8], + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + // Try to get _type_ attribute + if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) + && let Ok(s) = type_attr.str(vm) + { + let ty = s.to_string(); + return match ty.as_str() { + "c" => { + // c_char - single byte + Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) + } + "b" => { + // c_byte - signed char + let val = if !bytes.is_empty() { bytes[0] as i8 } else { 0 }; + Ok(vm.ctx.new_int(val).into()) + } + "B" => { + // c_ubyte - unsigned char + let val = if !bytes.is_empty() { bytes[0] } else { 0 }; + Ok(vm.ctx.new_int(val).into()) + } + "h" => { + // c_short + const SIZE: usize = mem::size_of::<c_short>(); + let val = if bytes.len() >= SIZE { + c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "H" => { + // c_ushort + const SIZE: usize = mem::size_of::<c_ushort>(); + let val = if bytes.len() >= SIZE { + c_ushort::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "i" => { + // c_int + const SIZE: usize = mem::size_of::<c_int>(); + let val = if bytes.len() >= SIZE { + c_int::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "I" => { + // c_uint + const SIZE: usize = mem::size_of::<c_uint>(); + let val = if bytes.len() >= SIZE { + c_uint::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "l" => { + // c_long + const SIZE: usize = mem::size_of::<c_long>(); + let val = if bytes.len() >= SIZE { + c_long::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "L" => { + // c_ulong + const SIZE: usize = mem::size_of::<c_ulong>(); + let val = if bytes.len() >= SIZE { + c_ulong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "q" => { + // c_longlong + const SIZE: usize = mem::size_of::<c_longlong>(); + let val = if bytes.len() >= SIZE { + c_longlong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "Q" => { + // c_ulonglong + const SIZE: usize = mem::size_of::<c_ulonglong>(); + let val = if bytes.len() >= SIZE { + c_ulonglong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "f" => { + // c_float + const SIZE: usize = mem::size_of::<c_float>(); + let val = if bytes.len() >= SIZE { + c_float::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0.0 + }; + Ok(vm.ctx.new_float(val as f64).into()) + } + "d" | "g" => { + // c_double + const SIZE: usize = mem::size_of::<c_double>(); + let val = if bytes.len() >= SIZE { + c_double::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0.0 + }; + Ok(vm.ctx.new_float(val).into()) + } + "?" => { + // c_bool + let val = !bytes.is_empty() && bytes[0] != 0; + Ok(vm.ctx.new_bool(val).into()) + } + "P" | "z" | "Z" => { + // Pointer types - return as integer address + let val = if bytes.len() >= mem::size_of::<libc::uintptr_t>() { + const UINTPTR_LEN: usize = mem::size_of::<libc::uintptr_t>(); + let mut arr = [0u8; UINTPTR_LEN]; + arr[..bytes.len().min(UINTPTR_LEN)] + .copy_from_slice(&bytes[..bytes.len().min(UINTPTR_LEN)]); + usize::from_ne_bytes(arr) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "u" => { + // c_wchar - wide character + let val = if bytes.len() >= mem::size_of::<WideChar>() { + let wc = if mem::size_of::<WideChar>() == 2 { + u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 + } else { + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + }; + char::from_u32(wc).unwrap_or('\0') + } else { + '\0' + }; + Ok(vm.ctx.new_str(val.to_string()).into()) + } + _ => Ok(vm.ctx.none()), + }; + } + // Default: return bytes as-is + Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) + } + const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?O"; pub fn new_simple_type( @@ -180,9 +368,14 @@ pub(crate) mod _ctypes { } else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) { Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\n a single character string containing one of {SIMPLE_TYPE_CHARS}, currently it is {tp_str}."))) } else { + let size = get_size(&tp_str); Ok(PyCSimple { _type_: tp_str, value: AtomicCell::new(vm.ctx.none()), + cdata: rustpython_common::lock::PyRwLock::new(CDataObject::from_bytes( + vec![0u8; size], + None, + )), }) } } else { @@ -193,20 +386,118 @@ pub(crate) mod _ctypes { } } + /// Get the size of a ctypes type or instance #[pyfunction(name = "sizeof")] - pub fn size_of(tp: Either<PyTypeRef, PyObjectRef>, vm: &VirtualMachine) -> PyResult<usize> { - match tp { - Either::A(type_) if type_.fast_issubclass(PyCSimple::static_type()) => { - let zelf = new_simple_type(Either::B(&type_), vm)?; - Ok(get_size(zelf._type_.as_str())) + pub fn size_of(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { + use super::array::{PyCArray, PyCArrayType}; + use super::pointer::PyCPointer; + use super::structure::{PyCStructType, PyCStructure}; + use super::union::{PyCUnion, PyCUnionType}; + + // 1. Instances with stg_info + if obj.fast_isinstance(PyCArray::static_type()) { + // Get stg_info from the type + if let Some(type_obj) = obj.class().as_object().downcast_ref::<PyCArrayType>() { + return Ok(type_obj.stg_info.size); } - Either::B(obj) if obj.has_attr("size_of_instances", vm)? => { - let size_of_method = obj.get_attr("size_of_instances", vm)?; - let size_of_return = size_of_method.call(vec![], vm)?; - Ok(usize::try_from_object(vm, size_of_return)?) + } + if let Some(structure) = obj.downcast_ref::<PyCStructure>() { + return Ok(structure.cdata.read().size()); + } + if obj.fast_isinstance(PyCUnion::static_type()) { + // Get stg_info from the type + if let Some(type_obj) = obj.class().as_object().downcast_ref::<PyCUnionType>() { + return Ok(type_obj.stg_info.size); } - _ => Err(vm.new_type_error("this type has no size")), } + if let Some(simple) = obj.downcast_ref::<PyCSimple>() { + return Ok(simple.cdata.read().size()); + } + if obj.fast_isinstance(PyCPointer::static_type()) { + return Ok(std::mem::size_of::<usize>()); + } + + // 2. Types (metatypes with stg_info) + if let Some(array_type) = obj.downcast_ref::<PyCArrayType>() { + return Ok(array_type.stg_info.size); + } + + // 3. Type objects + if let Ok(type_ref) = obj.clone().downcast::<crate::builtins::PyType>() { + // Structure types - check if metaclass is or inherits from PyCStructType + if type_ref + .class() + .fast_issubclass(PyCStructType::static_type()) + { + return calculate_struct_size(&type_ref, vm); + } + // Union types - check if metaclass is or inherits from PyCUnionType + if type_ref + .class() + .fast_issubclass(PyCUnionType::static_type()) + { + return calculate_union_size(&type_ref, vm); + } + // Simple types (c_int, c_char, etc.) + if type_ref.fast_issubclass(PyCSimple::static_type()) { + let instance = new_simple_type(Either::B(&type_ref), vm)?; + return Ok(get_size(&instance._type_)); + } + // Pointer types + if type_ref.fast_issubclass(PyCPointer::static_type()) { + return Ok(std::mem::size_of::<usize>()); + } + } + + Err(vm.new_type_error("this type has no size")) + } + + /// Calculate Structure type size from _fields_ (sum of field sizes) + fn calculate_struct_size( + cls: &crate::builtins::PyTypeRef, + vm: &VirtualMachine, + ) -> PyResult<usize> { + use crate::AsObject; + + if let Ok(fields_attr) = cls.as_object().get_attr("_fields_", vm) { + let fields: Vec<PyObjectRef> = fields_attr.try_to_value(vm).unwrap_or_default(); + let mut total_size = 0usize; + + for field in fields.iter() { + if let Some(tuple) = field.downcast_ref::<crate::builtins::PyTuple>() + && let Some(field_type) = tuple.get(1) + { + // Recursively calculate field type size + total_size += size_of(field_type.clone(), vm)?; + } + } + return Ok(total_size); + } + Ok(0) + } + + /// Calculate Union type size from _fields_ (max field size) + fn calculate_union_size( + cls: &crate::builtins::PyTypeRef, + vm: &VirtualMachine, + ) -> PyResult<usize> { + use crate::AsObject; + + if let Ok(fields_attr) = cls.as_object().get_attr("_fields_", vm) { + let fields: Vec<PyObjectRef> = fields_attr.try_to_value(vm).unwrap_or_default(); + let mut max_size = 0usize; + + for field in fields.iter() { + if let Some(tuple) = field.downcast_ref::<crate::builtins::PyTuple>() + && let Some(field_type) = tuple.get(1) + { + let field_size = size_of(field_type.clone(), vm)?; + max_size = max_size.max(field_size); + } + } + return Ok(max_size); + } + Ok(0) } #[cfg(windows)] @@ -367,9 +658,100 @@ pub(crate) mod _ctypes { } #[pyfunction] - fn alignment(_args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - // TODO: RUSTPYTHON - Err(vm.new_value_error("not implemented")) + fn alignment(tp: Either<PyTypeRef, PyObjectRef>, vm: &VirtualMachine) -> PyResult<usize> { + use super::array::{PyCArray, PyCArrayType}; + use super::base::PyCSimpleType; + use super::pointer::PyCPointer; + use super::structure::PyCStructure; + use super::union::PyCUnion; + + let obj = match &tp { + Either::A(t) => t.as_object(), + Either::B(o) => o.as_ref(), + }; + + // Try to get alignment from stg_info directly (for instances) + if let Some(array_type) = obj.downcast_ref::<PyCArrayType>() { + return Ok(array_type.stg_info.align); + } + if obj.fast_isinstance(PyCSimple::static_type()) { + // Get stg_info from the type by reading _type_ attribute + let cls = obj.class().to_owned(); + let stg_info = PyCSimpleType::get_stg_info(&cls, vm); + return Ok(stg_info.align); + } + if obj.fast_isinstance(PyCArray::static_type()) { + // Get stg_info from the type + if let Some(type_obj) = obj.class().as_object().downcast_ref::<PyCArrayType>() { + return Ok(type_obj.stg_info.align); + } + } + if obj.fast_isinstance(PyCStructure::static_type()) { + // Calculate alignment from _fields_ + let cls = obj.class(); + return alignment(Either::A(cls.to_owned()), vm); + } + if obj.fast_isinstance(PyCPointer::static_type()) { + // Pointer alignment is always pointer size + return Ok(std::mem::align_of::<usize>()); + } + if obj.fast_isinstance(PyCUnion::static_type()) { + // Calculate alignment from _fields_ + let cls = obj.class(); + return alignment(Either::A(cls.to_owned()), vm); + } + + // Get the type object to check + let type_obj: PyObjectRef = match &tp { + Either::A(t) => t.clone().into(), + Either::B(obj) => obj.class().to_owned().into(), + }; + + // For type objects, try to get alignment from _type_ attribute + if let Ok(type_attr) = type_obj.get_attr("_type_", vm) { + // Array/Pointer: _type_ is the element type (a PyType) + if let Ok(elem_type) = type_attr.clone().downcast::<crate::builtins::PyType>() { + return alignment(Either::A(elem_type), vm); + } + // Simple type: _type_ is a single character string + if let Ok(s) = type_attr.str(vm) { + let ty = s.to_string(); + if ty.len() == 1 && SIMPLE_TYPE_CHARS.contains(ty.as_str()) { + return Ok(get_align(&ty)); + } + } + } + + // Structure/Union: max alignment of fields + if let Ok(fields_attr) = type_obj.get_attr("_fields_", vm) + && let Ok(fields) = fields_attr.try_to_value::<Vec<PyObjectRef>>(vm) + { + let mut max_align = 1usize; + for field in fields.iter() { + if let Some(tuple) = field.downcast_ref::<crate::builtins::PyTuple>() + && let Some(field_type) = tuple.get(1) + { + let align = + if let Ok(ft) = field_type.clone().downcast::<crate::builtins::PyType>() { + alignment(Either::A(ft), vm).unwrap_or(1) + } else { + 1 + }; + max_align = max_align.max(align); + } + } + return Ok(max_align); + } + + // For instances, delegate to their class + if let Either::B(obj) = &tp + && !obj.class().is(vm.ctx.types.type_type.as_ref()) + { + return alignment(Either::A(obj.class().to_owned()), vm); + } + + // No alignment info found + Err(vm.new_type_error("no alignment info")) } #[pyfunction] @@ -439,4 +821,53 @@ pub(crate) mod _ctypes { // todo!("Implement _cast_addr") 0 } + + #[pyfunction(name = "_cast")] + pub fn pycfunction_cast( + obj: PyObjectRef, + _obj2: PyObjectRef, + ctype: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + use super::array::PyCArray; + use super::base::PyCData; + use super::pointer::PyCPointer; + use crate::class::StaticType; + + // Python signature: _cast(obj, obj, ctype) + // Python passes the same object twice (obj and _obj2 are the same) + // We ignore _obj2 as it's redundant + + // Check if this is a pointer type (has _type_ attribute) + if ctype.get_attr("_type_", vm).is_err() { + return Err(vm.new_type_error("cast() argument 2 must be a pointer type".to_string())); + } + + // Create an instance of the target pointer type with no arguments + let result = ctype.call((), vm)?; + + // Get the pointer value from the source object + // If obj is a CData instance (including arrays), use the object itself + // If obj is an integer, use it directly as the pointer value + let ptr_value: PyObjectRef = if obj.fast_isinstance(PyCData::static_type()) + || obj.fast_isinstance(PyCArray::static_type()) + || obj.fast_isinstance(PyCPointer::static_type()) + { + // For CData objects (including arrays and pointers), store the object itself + obj.clone() + } else if let Ok(int_val) = obj.try_int(vm) { + // For integers, treat as pointer address + vm.ctx.new_int(int_val.as_bigint().clone()).into() + } else { + return Err(vm.new_type_error(format!( + "cast() argument 1 must be a ctypes instance or an integer, not {}", + obj.class().name() + ))); + }; + + // Set the contents of the pointer by setting the attribute + result.set_attr("contents", ptr_value, vm)?; + + Ok(result) + } } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 73217d7a6a6..62d714329e6 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -2,8 +2,12 @@ use crate::atomic_func; use crate::builtins::{PyBytes, PyInt}; use crate::convert::ToPyObject; use crate::function::FuncArgs; -use crate::protocol::{PyNumberMethods, PySequenceMethods}; -use crate::types::{AsNumber, AsSequence, Callable}; +use crate::protocol::{ + BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods, PySequenceMethods, +}; +use crate::stdlib::ctypes::base::CDataObject; +use crate::stdlib::ctypes::util::StgInfo; +use crate::types::{AsBuffer, AsNumber, AsSequence, Callable}; use crate::{AsObject, Py, PyObjectRef, PyPayload}; use crate::{ PyResult, VirtualMachine, @@ -19,13 +23,17 @@ use rustpython_vm::stdlib::ctypes::base::PyCData; #[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")] #[derive(PyPayload)] pub struct PyCArrayType { - pub(super) inner: PyCArray, + pub(super) stg_info: StgInfo, + pub(super) typ: PyRwLock<PyObjectRef>, + pub(super) length: AtomicCell<usize>, + pub(super) element_size: AtomicCell<usize>, } impl std::fmt::Debug for PyCArrayType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PyCArrayType") - .field("inner", &self.inner) + .field("typ", &self.typ) + .field("length", &self.length) .finish() } } @@ -34,9 +42,9 @@ impl Callable for PyCArrayType { type Args = FuncArgs; fn call(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult { // Create an instance of the array - let element_type = zelf.inner.typ.read().clone(); - let length = zelf.inner.length.load(); - let element_size = zelf.inner.element_size.load(); + let element_type = zelf.typ.read().clone(); + let length = zelf.length.load(); + let element_size = zelf.element_size.load(); let total_size = element_size * length; let mut buffer = vec![0u8; total_size]; @@ -58,7 +66,7 @@ impl Callable for PyCArrayType { typ: PyRwLock::new(element_type), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(buffer), + cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)), } .into_pyobject(vm)) } @@ -75,13 +83,13 @@ impl Constructor for PyCArrayType { #[pyclass(flags(IMMUTABLETYPE), with(Callable, Constructor, AsNumber))] impl PyCArrayType { #[pygetset(name = "_type_")] - fn typ(&self) -> PyTypeRef { - self.inner.typ.read().clone() + fn typ(&self) -> PyObjectRef { + self.typ.read().clone() } #[pygetset(name = "_length_")] fn length(&self) -> usize { - self.inner.length.load() + self.length.load() } #[pymethod] @@ -92,28 +100,106 @@ impl PyCArrayType { // Create a nested array type: (inner_type * inner_length) * n // The new array has n elements, each element is the current array type // e.g., (c_int * 5) * 3 = Array of 3 elements, each is (c_int * 5) - let inner_length = zelf.inner.length.load(); - let inner_element_size = zelf.inner.element_size.load(); + let inner_length = zelf.length.load(); + let inner_element_size = zelf.element_size.load(); // The element type of the new array is the current array type itself - let obj_ref: PyObjectRef = zelf.to_owned().into(); - let current_array_type = obj_ref - .downcast::<PyType>() - .expect("PyCArrayType should be a PyType"); + let current_array_type: PyObjectRef = zelf.as_object().to_owned(); // Element size is the total size of the inner array let new_element_size = inner_length * inner_element_size; + let total_size = new_element_size * (n as usize); + let stg_info = StgInfo::new(total_size, inner_element_size); Ok(PyCArrayType { - inner: PyCArray { - typ: PyRwLock::new(current_array_type), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(new_element_size), - buffer: PyRwLock::new(vec![]), - }, + stg_info, + typ: PyRwLock::new(current_array_type), + length: AtomicCell::new(n as usize), + element_size: AtomicCell::new(new_element_size), } .to_pyobject(vm)) } + + #[pyclassmethod] + fn in_dll( + zelf: &Py<Self>, + dll: PyObjectRef, + name: crate::builtins::PyStrRef, + vm: &VirtualMachine, + ) -> PyResult { + use crate::stdlib::ctypes::_ctypes::size_of; + use libloading::Symbol; + + // Get the library handle from dll object + let handle = if let Ok(int_handle) = dll.try_int(vm) { + // dll is an integer handle + int_handle + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + } else { + // dll is a CDLL/PyDLL/WinDLL object with _handle attribute + dll.get_attr("_handle", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + }; + + // Get the library from cache + let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library = library_cache + .get_lib(handle) + .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; + + // Get symbol address from library + let symbol_name = format!("{}\0", name.as_str()); + let inner_lib = library.lib.lock(); + + let symbol_address = if let Some(lib) = &*inner_lib { + unsafe { + // Try to get the symbol from the library + let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { + vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) + })?; + *symbol as usize + } + } else { + return Err(vm.new_attribute_error("Library is closed".to_owned())); + }; + + // Get size from the array type + let element_type = zelf.typ.read().clone(); + let length = zelf.length.load(); + let element_size = size_of(element_type.clone(), vm)?; + let total_size = element_size * length; + + // Read data from symbol address + let data = if symbol_address != 0 && total_size > 0 { + unsafe { + let ptr = symbol_address as *const u8; + std::slice::from_raw_parts(ptr, total_size).to_vec() + } + } else { + vec![0; total_size] + }; + + // Create instance + let instance = PyCArray { + typ: PyRwLock::new(element_type), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + } + .into_pyobject(vm); + + // Store base reference to keep dll alive + if let Ok(array_ref) = instance.clone().downcast::<PyCArray>() { + array_ref.cdata.write().base = Some(dll); + } + + Ok(instance) + } } impl AsNumber for PyCArrayType { @@ -144,10 +230,11 @@ impl AsNumber for PyCArrayType { )] #[derive(PyPayload)] pub struct PyCArray { - pub(super) typ: PyRwLock<PyTypeRef>, + /// Element type - can be a simple type (c_int) or an array type (c_int * 5) + pub(super) typ: PyRwLock<PyObjectRef>, pub(super) length: AtomicCell<usize>, pub(super) element_size: AtomicCell<usize>, - pub(super) buffer: PyRwLock<Vec<u8>>, + pub(super) cdata: PyRwLock<CDataObject>, } impl std::fmt::Debug for PyCArray { @@ -207,15 +294,11 @@ impl Constructor for PyCArray { } } - let element_type_ref = element_type - .downcast::<PyType>() - .unwrap_or_else(|_| vm.ctx.types.object_type.to_owned()); - PyCArray { - typ: PyRwLock::new(element_type_ref), + typ: PyRwLock::new(element_type), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(buffer), + cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)), } .into_ref_with_type(vm, cls) .map(Into::into) @@ -243,8 +326,16 @@ impl AsSequence for PyCArray { } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsSequence))] +#[pyclass( + flags(BASETYPE, IMMUTABLETYPE), + with(Constructor, AsSequence, AsBuffer) +)] impl PyCArray { + #[pygetset] + fn _objects(&self) -> Option<PyObjectRef> { + self.cdata.read().objects.clone() + } + fn int_to_bytes(i: &malachite_bigint::BigInt, size: usize) -> Vec<u8> { match size { 1 => vec![i.to_i8().unwrap_or(0) as u8], @@ -285,7 +376,7 @@ impl PyCArray { let index = index as usize; let element_size = zelf.element_size.load(); let offset = index * element_size; - let buffer = zelf.buffer.read(); + let buffer = zelf.cdata.read().buffer.clone(); if offset + element_size <= buffer.len() { let bytes = &buffer[offset..offset + element_size]; Ok(Self::bytes_to_int(bytes, element_size, vm)) @@ -312,9 +403,9 @@ impl PyCArray { let int_val = value.try_int(vm)?; let bytes = Self::int_to_bytes(int_val.as_bigint(), element_size); - let mut buffer = zelf.buffer.write(); - if offset + element_size <= buffer.len() { - buffer[offset..offset + element_size].copy_from_slice(&bytes); + let mut cdata = zelf.cdata.write(); + if offset + element_size <= cdata.buffer.len() { + cdata.buffer[offset..offset + element_size].copy_from_slice(&bytes); } Ok(()) } @@ -354,7 +445,7 @@ impl PyCArray { } #[pygetset(name = "_type_")] - fn typ(&self) -> PyTypeRef { + fn typ(&self) -> PyObjectRef { self.typ.read().clone() } @@ -366,48 +457,337 @@ impl PyCArray { #[pygetset] fn value(&self, vm: &VirtualMachine) -> PyObjectRef { // Return bytes representation of the buffer - let buffer = self.buffer.read(); + let buffer = self.cdata.read().buffer.clone(); vm.ctx.new_bytes(buffer.clone()).into() } #[pygetset(setter)] fn set_value(&self, value: PyObjectRef, _vm: &VirtualMachine) -> PyResult<()> { if let Some(bytes) = value.downcast_ref::<PyBytes>() { - let mut buffer = self.buffer.write(); + let mut cdata = self.cdata.write(); let src = bytes.as_bytes(); - let len = std::cmp::min(src.len(), buffer.len()); - buffer[..len].copy_from_slice(&src[..len]); + let len = std::cmp::min(src.len(), cdata.buffer.len()); + cdata.buffer[..len].copy_from_slice(&src[..len]); } Ok(()) } #[pygetset] fn raw(&self, vm: &VirtualMachine) -> PyObjectRef { - let buffer = self.buffer.read(); - vm.ctx.new_bytes(buffer.clone()).into() + let cdata = self.cdata.read(); + vm.ctx.new_bytes(cdata.buffer.clone()).into() } #[pygetset(setter)] fn set_raw(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { if let Some(bytes) = value.downcast_ref::<PyBytes>() { - let mut buffer = self.buffer.write(); + let mut cdata = self.cdata.write(); let src = bytes.as_bytes(); - let len = std::cmp::min(src.len(), buffer.len()); - buffer[..len].copy_from_slice(&src[..len]); + let len = std::cmp::min(src.len(), cdata.buffer.len()); + cdata.buffer[..len].copy_from_slice(&src[..len]); Ok(()) } else { Err(vm.new_type_error("expected bytes".to_owned())) } } + + #[pyclassmethod] + fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { + use crate::stdlib::ctypes::_ctypes::size_of; + + // Get size from cls + let size = size_of(cls.clone().into(), vm)?; + + // Create instance with data from address + if address == 0 || size == 0 { + return Err(vm.new_value_error("NULL pointer access".to_owned())); + } + unsafe { + let ptr = address as *const u8; + let bytes = std::slice::from_raw_parts(ptr, size); + // Get element type and length from cls + let element_type = cls.as_object().get_attr("_type_", vm)?; + let element_type: PyTypeRef = element_type + .downcast() + .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; + let length = cls + .as_object() + .get_attr("_length_", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .unwrap_or(0); + let element_size = if length > 0 { size / length } else { 0 }; + + Ok(PyCArray { + typ: PyRwLock::new(element_type.into()), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + cdata: PyRwLock::new(CDataObject::from_bytes(bytes.to_vec(), None)), + } + .into_pyobject(vm)) + } + } + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + source: PyObjectRef, + offset: crate::function::OptionalArg<isize>, + vm: &VirtualMachine, + ) -> PyResult { + use crate::TryFromObject; + use crate::protocol::PyBuffer; + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get buffer from source + let buffer = PyBuffer::try_from_object(vm, source.clone())?; + + // Check if buffer is writable + if buffer.desc.readonly { + return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + } + + // Get size from cls + let size = size_of(cls.clone().into(), vm)?; + + // Check if buffer is large enough + let buffer_len = buffer.desc.len; + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Read bytes from buffer at offset + let bytes = buffer.obj_bytes(); + let data = &bytes[offset..offset + size]; + + // Get element type and length from cls + let element_type = cls.as_object().get_attr("_type_", vm)?; + let element_type: PyTypeRef = element_type + .downcast() + .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; + let length = cls + .as_object() + .get_attr("_length_", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .unwrap_or(0); + let element_size = if length > 0 { size / length } else { 0 }; + + Ok(PyCArray { + typ: PyRwLock::new(element_type.into()), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + cdata: PyRwLock::new(CDataObject::from_bytes( + data.to_vec(), + Some(buffer.obj.clone()), + )), + } + .into_pyobject(vm)) + } + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + source: crate::function::ArgBytesLike, + offset: crate::function::OptionalArg<isize>, + vm: &VirtualMachine, + ) -> PyResult { + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get size from cls + let size = size_of(cls.clone().into(), vm)?; + + // Borrow bytes from source + let source_bytes = source.borrow_buf(); + let buffer_len = source_bytes.len(); + + // Check if buffer is large enough + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Copy bytes from buffer at offset + let data = &source_bytes[offset..offset + size]; + + // Get element type and length from cls + let element_type = cls.as_object().get_attr("_type_", vm)?; + let element_type: PyTypeRef = element_type + .downcast() + .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; + let length = cls + .as_object() + .get_attr("_length_", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .unwrap_or(0); + let element_size = if length > 0 { size / length } else { 0 }; + + Ok(PyCArray { + typ: PyRwLock::new(element_type.into()), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + cdata: PyRwLock::new(CDataObject::from_bytes(data.to_vec(), None)), + } + .into_pyobject(vm)) + } + + #[pyclassmethod] + fn in_dll( + cls: PyTypeRef, + dll: PyObjectRef, + name: crate::builtins::PyStrRef, + vm: &VirtualMachine, + ) -> PyResult { + use crate::stdlib::ctypes::_ctypes::size_of; + use libloading::Symbol; + + // Get the library handle from dll object + let handle = if let Ok(int_handle) = dll.try_int(vm) { + // dll is an integer handle + int_handle + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + } else { + // dll is a CDLL/PyDLL/WinDLL object with _handle attribute + dll.get_attr("_handle", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + }; + + // Get the library from cache + let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library = library_cache + .get_lib(handle) + .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; + + // Get symbol address from library + let symbol_name = format!("{}\0", name.as_str()); + let inner_lib = library.lib.lock(); + + let symbol_address = if let Some(lib) = &*inner_lib { + unsafe { + // Try to get the symbol from the library + let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { + vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) + })?; + *symbol as usize + } + } else { + return Err(vm.new_attribute_error("Library is closed".to_owned())); + }; + + // Get size from cls + let size = size_of(cls.clone().into(), vm)?; + + // Read data from symbol address + let data = if symbol_address != 0 && size > 0 { + unsafe { + let ptr = symbol_address as *const u8; + std::slice::from_raw_parts(ptr, size).to_vec() + } + } else { + vec![0; size] + }; + + // Get element type and length from cls + let element_type = cls.as_object().get_attr("_type_", vm)?; + let element_type: PyTypeRef = element_type + .downcast() + .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; + let length = cls + .as_object() + .get_attr("_length_", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .unwrap_or(0); + let element_size = if length > 0 { size / length } else { 0 }; + + // Create instance + let instance = PyCArray { + typ: PyRwLock::new(element_type.into()), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + } + .into_pyobject(vm); + + // Store base reference to keep dll alive + if let Ok(array_ref) = instance.clone().downcast::<PyCArray>() { + array_ref.cdata.write().base = Some(dll); + } + + Ok(instance) + } } impl PyCArray { #[allow(unused)] pub fn to_arg(&self, _vm: &VirtualMachine) -> PyResult<libffi::middle::Arg> { - // TODO: This needs a different approach to ensure buffer lifetime - // The buffer must outlive the Arg returned here - let buffer = self.buffer.read(); - let ptr = buffer.as_ptr(); - Ok(libffi::middle::Arg::new(&ptr)) + let cdata = self.cdata.read(); + Ok(libffi::middle::Arg::new(&cdata.buffer)) + } +} + +static ARRAY_BUFFER_METHODS: BufferMethods = BufferMethods { + obj_bytes: |buffer| { + rustpython_common::lock::PyMappedRwLockReadGuard::map( + rustpython_common::lock::PyRwLockReadGuard::map( + buffer.obj_as::<PyCArray>().cdata.read(), + |x: &CDataObject| x, + ), + |x: &CDataObject| x.buffer.as_slice(), + ) + .into() + }, + obj_bytes_mut: |buffer| { + rustpython_common::lock::PyMappedRwLockWriteGuard::map( + rustpython_common::lock::PyRwLockWriteGuard::map( + buffer.obj_as::<PyCArray>().cdata.write(), + |x: &mut CDataObject| x, + ), + |x: &mut CDataObject| x.buffer.as_mut_slice(), + ) + .into() + }, + release: |_| {}, + retain: |_| {}, +}; + +impl AsBuffer for PyCArray { + fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { + let buffer_len = zelf.cdata.read().buffer.len(); + let buf = PyBuffer::new( + zelf.to_owned().into(), + BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes + &ARRAY_BUFFER_METHODS, + ); + Ok(buf) } } diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 73d97a2593d..5e2deeb5607 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -1,17 +1,27 @@ -use super::array::{PyCArray, PyCArrayType}; -use crate::builtins::PyType; -use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyTypeRef}; +use super::_ctypes::bytes_to_pyobject; +use super::array::PyCArrayType; +use super::util::StgInfo; +use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyStrRef, PyType, PyTypeRef}; use crate::convert::ToPyObject; -use crate::function::{Either, OptionalArg}; -use crate::protocol::PyNumberMethods; +use crate::function::{ArgBytesLike, Either, OptionalArg}; +use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; use crate::stdlib::ctypes::_ctypes::new_simple_type; -use crate::types::{AsNumber, Constructor}; +use crate::types::{AsBuffer, AsNumber, Constructor}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; +use std::ffi::{c_uint, c_ulong, c_ulonglong, c_ushort}; use std::fmt::Debug; +/// Get the type code string from a ctypes type (e.g., "i" for c_int) +pub fn get_type_code(cls: &PyTypeRef, vm: &VirtualMachine) -> Option<String> { + cls.as_object() + .get_attr("_type_", vm) + .ok() + .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())) +} + pub fn ffi_type_from_str(_type_: &str) -> Option<libffi::middle::Type> { match _type_ { "c" => Some(libffi::middle::Type::u8()), @@ -91,7 +101,10 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe } } "f" | "d" | "g" => { - if value.clone().downcast_exact::<PyFloat>(vm).is_ok() { + // float allows int + if value.clone().downcast_exact::<PyFloat>(vm).is_ok() + || value.clone().downcast_exact::<PyInt>(vm).is_ok() + { Ok(value.clone()) } else { Err(vm.new_type_error(format!("must be real number, not {}", value.class().name()))) @@ -102,7 +115,8 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe )), "B" => { if value.clone().downcast_exact::<PyInt>(vm).is_ok() { - Ok(vm.new_pyobj(u8::try_from_object(vm, value.clone())?)) + // Store as-is, conversion to unsigned happens in the getter + Ok(value.clone()) } else { Err(vm.new_type_error(format!("int expected instead of {}", value.class().name()))) } @@ -142,28 +156,102 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe } } -pub struct RawBuffer { +/// Common data object for all ctypes types +#[derive(Debug, Clone)] +pub struct CDataObject { + /// pointer to memory block (b_ptr + b_size) + pub buffer: Vec<u8>, + /// pointer to base object or None (b_base) + #[allow(dead_code)] + pub base: Option<PyObjectRef>, + /// index into base's b_objects list (b_index) #[allow(dead_code)] - pub inner: Box<[u8]>, + pub index: usize, + /// dictionary of references we need to keep (b_objects) + pub objects: Option<PyObjectRef>, +} + +impl CDataObject { + /// Create from StgInfo (PyCData_MallocBuffer pattern) + pub fn from_stg_info(stg_info: &StgInfo) -> Self { + CDataObject { + buffer: vec![0u8; stg_info.size], + base: None, + index: 0, + objects: None, + } + } + + /// Create from existing bytes (copies data) + pub fn from_bytes(data: Vec<u8>, objects: Option<PyObjectRef>) -> Self { + CDataObject { + buffer: data, + base: None, + index: 0, + objects, + } + } + + /// Create from base object (copies data from base's buffer at offset) #[allow(dead_code)] - pub size: usize, + pub fn from_base( + base: PyObjectRef, + _offset: usize, + size: usize, + index: usize, + objects: Option<PyObjectRef>, + ) -> Self { + CDataObject { + buffer: vec![0u8; size], + base: Some(base), + index, + objects, + } + } + + #[inline] + pub fn size(&self) -> usize { + self.buffer.len() + } } #[pyclass(name = "_CData", module = "_ctypes")] +#[derive(Debug, PyPayload)] pub struct PyCData { - _objects: AtomicCell<Vec<PyObjectRef>>, - _buffer: PyRwLock<RawBuffer>, + pub cdata: PyRwLock<CDataObject>, } -#[pyclass] -impl PyCData {} +#[pyclass(flags(BASETYPE))] +impl PyCData { + #[pygetset] + fn _objects(&self) -> Option<PyObjectRef> { + self.cdata.read().objects.clone() + } +} #[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)] -#[derive(Debug, PyPayload)] -pub struct PyCSimpleType {} +#[derive(Debug, PyPayload, Default)] +pub struct PyCSimpleType { + #[allow(dead_code)] + pub stg_info: StgInfo, +} #[pyclass(flags(BASETYPE), with(AsNumber))] impl PyCSimpleType { + /// Get stg_info for a simple type by reading _type_ attribute + pub fn get_stg_info(cls: &PyTypeRef, vm: &VirtualMachine) -> StgInfo { + if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) + && let Ok(type_str) = type_attr.str(vm) + { + let tp_str = type_str.to_string(); + if tp_str.len() == 1 { + let size = super::_ctypes::get_size(&tp_str); + let align = super::_ctypes::get_align(&tp_str); + return StgInfo::new(size, align); + } + } + StgInfo::default() + } #[allow(clippy::new_ret_no_self)] #[pymethod] fn new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult { @@ -176,17 +264,122 @@ impl PyCSimpleType { #[pyclassmethod] fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // If the value is already an instance of the requested type, return it + // 1. If the value is already an instance of the requested type, return it if value.fast_isinstance(&cls) { return Ok(value); } - // Check for _as_parameter_ attribute - let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) else { - return Err(vm.new_type_error("wrong type")); - }; + // 2. Get the type code to determine conversion rules + let type_code = get_type_code(&cls, vm); + + // 3. Handle None for pointer types (c_char_p, c_wchar_p, c_void_p) + if vm.is_none(&value) && matches!(type_code.as_deref(), Some("z") | Some("Z") | Some("P")) { + return Ok(value); + } + + // 4. Try to convert value based on type code + match type_code.as_deref() { + // Integer types: accept integers + Some("b" | "B" | "h" | "H" | "i" | "I" | "l" | "L" | "q" | "Q") => { + if value.try_int(vm).is_ok() { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + } + // Float types: accept numbers + Some("f" | "d" | "g") => { + if value.try_float(vm).is_ok() || value.try_int(vm).is_ok() { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + } + // c_char: 1 byte character + Some("c") => { + if let Some(bytes) = value.downcast_ref::<PyBytes>() + && bytes.len() == 1 + { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + if let Ok(int_val) = value.try_int(vm) + && int_val.as_bigint().to_u8().is_some() + { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + return Err(vm.new_type_error( + "one character bytes, bytearray or integer expected".to_string(), + )); + } + // c_wchar: 1 unicode character + Some("u") => { + if let Some(s) = value.downcast_ref::<PyStr>() + && s.as_str().chars().count() == 1 + { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + return Err(vm.new_type_error("one character unicode string expected".to_string())); + } + // c_char_p: bytes pointer + Some("z") => { + if value.downcast_ref::<PyBytes>().is_some() { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + } + // c_wchar_p: unicode pointer + Some("Z") => { + if value.downcast_ref::<PyStr>().is_some() { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + } + // c_void_p: most flexible - accepts int, bytes, str + Some("P") => { + if value.try_int(vm).is_ok() + || value.downcast_ref::<PyBytes>().is_some() + || value.downcast_ref::<PyStr>().is_some() + { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + } + // c_bool + Some("?") => { + let bool_val = value.is_true(vm)?; + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(vm.ctx.new_bool(bool_val).into()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + _ => {} + } - PyCSimpleType::from_param(cls, as_parameter, vm) + // 5. Check for _as_parameter_ attribute + if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) { + return PyCSimpleType::from_param(cls, as_parameter, vm); + } + + // 6. Type-specific error messages + match type_code.as_deref() { + Some("z") => Err(vm.new_type_error(format!( + "'{}' object cannot be interpreted as ctypes.c_char_p", + value.class().name() + ))), + Some("Z") => Err(vm.new_type_error(format!( + "'{}' object cannot be interpreted as ctypes.c_wchar_p", + value.class().name() + ))), + _ => Err(vm.new_type_error("wrong type".to_string())), + } } #[pymethod] @@ -227,6 +420,7 @@ impl AsNumber for PyCSimpleType { pub struct PyCSimple { pub _type_: String, pub value: AtomicCell<PyObjectRef>, + pub cdata: PyRwLock<CDataObject>, } impl Debug for PyCSimple { @@ -237,6 +431,182 @@ impl Debug for PyCSimple { } } +fn value_to_bytes_endian( + _type_: &str, + value: &PyObjectRef, + swapped: bool, + vm: &VirtualMachine, +) -> Vec<u8> { + // Helper macro for endian conversion + macro_rules! to_bytes { + ($val:expr) => { + if swapped { + // Use opposite endianness + #[cfg(target_endian = "little")] + { + $val.to_be_bytes().to_vec() + } + #[cfg(target_endian = "big")] + { + $val.to_le_bytes().to_vec() + } + } else { + $val.to_ne_bytes().to_vec() + } + }; + } + + match _type_ { + "c" => { + // c_char - single byte + if let Some(bytes) = value.downcast_ref::<PyBytes>() + && !bytes.is_empty() + { + return vec![bytes.as_bytes()[0]]; + } + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_u8() + { + return vec![v]; + } + vec![0] + } + "u" => { + // c_wchar - 4 bytes (wchar_t on most platforms) + if let Ok(s) = value.str(vm) + && let Some(c) = s.as_str().chars().next() + { + return to_bytes!(c as u32); + } + vec![0; 4] + } + "b" => { + // c_byte - signed char (1 byte) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_i8() + { + return vec![v as u8]; + } + vec![0] + } + "B" => { + // c_ubyte - unsigned char (1 byte) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_u8() + { + return vec![v]; + } + vec![0] + } + "h" => { + // c_short (2 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_i16() + { + return to_bytes!(v); + } + vec![0; 2] + } + "H" => { + // c_ushort (2 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_u16() + { + return to_bytes!(v); + } + vec![0; 2] + } + "i" => { + // c_int (4 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_i32() + { + return to_bytes!(v); + } + vec![0; 4] + } + "I" => { + // c_uint (4 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_u32() + { + return to_bytes!(v); + } + vec![0; 4] + } + "l" => { + // c_long (platform dependent) + if let Ok(int_val) = value.try_to_value::<libc::c_long>(vm) { + return to_bytes!(int_val); + } + const SIZE: usize = std::mem::size_of::<libc::c_long>(); + vec![0; SIZE] + } + "L" => { + // c_ulong (platform dependent) + if let Ok(int_val) = value.try_to_value::<libc::c_ulong>(vm) { + return to_bytes!(int_val); + } + const SIZE: usize = std::mem::size_of::<libc::c_ulong>(); + vec![0; SIZE] + } + "q" => { + // c_longlong (8 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_i64() + { + return to_bytes!(v); + } + vec![0; 8] + } + "Q" => { + // c_ulonglong (8 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_u64() + { + return to_bytes!(v); + } + vec![0; 8] + } + "f" => { + // c_float (4 bytes) - int도 허용 + if let Ok(float_val) = value.try_float(vm) { + return to_bytes!(float_val.to_f64() as f32); + } + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_f64() + { + return to_bytes!(v as f32); + } + vec![0; 4] + } + "d" | "g" => { + // c_double (8 bytes) - int도 허용 + if let Ok(float_val) = value.try_float(vm) { + return to_bytes!(float_val.to_f64()); + } + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_f64() + { + return to_bytes!(v); + } + vec![0; 8] + } + "?" => { + // c_bool (1 byte) + if let Ok(b) = value.clone().try_to_bool(vm) { + return vec![if b { 1 } else { 0 }]; + } + vec![0] + } + "P" | "z" | "Z" => { + // Pointer types (platform pointer size) + vec![0; std::mem::size_of::<usize>()] + } + _ => vec![0], + } +} + impl Constructor for PyCSimple { type Args = (OptionalArg,); @@ -272,31 +642,99 @@ impl Constructor for PyCSimple { _ => vm.ctx.none(), // "z" | "Z" | "P" } }; + + // Check if this is a swapped endian type + let swapped = cls + .as_object() + .get_attr("_swappedbytes_", vm) + .map(|v| v.is_true(vm).unwrap_or(false)) + .unwrap_or(false); + + let buffer = value_to_bytes_endian(&_type_, &value, swapped, vm); PyCSimple { _type_, value: AtomicCell::new(value), + cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)), } .into_ref_with_type(vm, cls) .map(Into::into) } } -#[pyclass(flags(BASETYPE), with(Constructor))] +#[pyclass(flags(BASETYPE), with(Constructor, AsBuffer))] impl PyCSimple { + #[pygetset] + fn _objects(&self) -> Option<PyObjectRef> { + self.cdata.read().objects.clone() + } + #[pygetset(name = "value")] pub fn value(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> { let zelf: &Py<Self> = instance .downcast_ref() .ok_or_else(|| vm.new_type_error("cannot get value of instance"))?; - Ok(unsafe { (*zelf.value.as_ptr()).clone() }) + let raw_value = unsafe { (*zelf.value.as_ptr()).clone() }; + + // Convert to unsigned if needed for unsigned types + match zelf._type_.as_str() { + "B" | "H" | "I" | "L" | "Q" => { + if let Ok(int_val) = raw_value.try_int(vm) { + let n = int_val.as_bigint(); + // Use platform-specific C types for correct unsigned conversion + match zelf._type_.as_str() { + "B" => { + if let Some(v) = n.to_i64() { + return Ok(vm.ctx.new_int((v as u8) as u64).into()); + } + } + "H" => { + if let Some(v) = n.to_i64() { + return Ok(vm.ctx.new_int((v as c_ushort) as u64).into()); + } + } + "I" => { + if let Some(v) = n.to_i64() { + return Ok(vm.ctx.new_int((v as c_uint) as u64).into()); + } + } + "L" => { + if let Some(v) = n.to_i128() { + return Ok(vm.ctx.new_int(v as c_ulong).into()); + } + } + "Q" => { + if let Some(v) = n.to_i128() { + return Ok(vm.ctx.new_int(v as c_ulonglong).into()); + } + } + _ => {} + }; + } + Ok(raw_value) + } + _ => Ok(raw_value), + } } #[pygetset(name = "value", setter)] fn set_value(instance: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { let zelf: PyRef<Self> = instance + .clone() .downcast() .map_err(|_| vm.new_type_error("cannot set value of instance"))?; let content = set_primitive(zelf._type_.as_str(), &value, vm)?; + + // Check if this is a swapped endian type + let swapped = instance + .class() + .as_object() + .get_attr("_swappedbytes_", vm) + .map(|v| v.is_true(vm).unwrap_or(false)) + .unwrap_or(false); + + // Update buffer when value changes + let buffer_bytes = value_to_bytes_endian(&zelf._type_, &content, swapped, vm); + zelf.cdata.write().buffer = buffer_bytes; zelf.value.store(content); Ok(()) } @@ -322,16 +760,214 @@ impl PyCSimple { } else { std::mem::size_of::<usize>() }; + let total_size = element_size * (n as usize); + let stg_info = super::util::StgInfo::new(total_size, element_size); Ok(PyCArrayType { - inner: PyCArray { - typ: PyRwLock::new(cls), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(vec![]), - }, + stg_info, + typ: PyRwLock::new(cls.clone().into()), + length: AtomicCell::new(n as usize), + element_size: AtomicCell::new(element_size), } .to_pyobject(vm)) } + + #[pyclassmethod] + fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { + use super::_ctypes::get_size; + // Get _type_ attribute directly + let type_attr = cls + .as_object() + .get_attr("_type_", vm) + .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; + let type_str = type_attr.str(vm)?.to_string(); + let size = get_size(&type_str); + + // Create instance with value read from address + let value = if address != 0 && size > 0 { + // Safety: This is inherently unsafe - reading from arbitrary memory address + unsafe { + let ptr = address as *const u8; + let bytes = std::slice::from_raw_parts(ptr, size); + // Convert bytes to appropriate Python value based on type + bytes_to_pyobject(&cls, bytes, vm)? + } + } else { + vm.ctx.none() + }; + + // Create instance using the type's constructor + let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?; + Ok(instance) + } + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + source: PyObjectRef, + offset: OptionalArg<isize>, + vm: &VirtualMachine, + ) -> PyResult { + use super::_ctypes::get_size; + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get buffer from source + let buffer = PyBuffer::try_from_object(vm, source.clone())?; + + // Check if buffer is writable + if buffer.desc.readonly { + return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + } + + // Get _type_ attribute directly + let type_attr = cls + .as_object() + .get_attr("_type_", vm) + .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; + let type_str = type_attr.str(vm)?.to_string(); + let size = get_size(&type_str); + + // Check if buffer is large enough + let buffer_len = buffer.desc.len; + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Read bytes from buffer at offset + let bytes = buffer.obj_bytes(); + let data = &bytes[offset..offset + size]; + let value = bytes_to_pyobject(&cls, data, vm)?; + + // Create instance + let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?; + + // TODO: Store reference to source in _objects to keep buffer alive + Ok(instance) + } + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + source: ArgBytesLike, + offset: OptionalArg<isize>, + vm: &VirtualMachine, + ) -> PyResult { + use super::_ctypes::get_size; + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get _type_ attribute directly for simple types + let type_attr = cls + .as_object() + .get_attr("_type_", vm) + .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; + let type_str = type_attr.str(vm)?.to_string(); + let size = get_size(&type_str); + + // Borrow bytes from source + let source_bytes = source.borrow_buf(); + let buffer_len = source_bytes.len(); + + // Check if buffer is large enough + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Copy bytes from buffer at offset + let data = &source_bytes[offset..offset + size]; + let value = bytes_to_pyobject(&cls, data, vm)?; + + // Create instance (independent copy, no reference tracking) + PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm) + } + + #[pyclassmethod] + fn in_dll(cls: PyTypeRef, dll: PyObjectRef, name: PyStrRef, vm: &VirtualMachine) -> PyResult { + use super::_ctypes::get_size; + use libloading::Symbol; + + // Get the library handle from dll object + let handle = if let Ok(int_handle) = dll.try_int(vm) { + // dll is an integer handle + int_handle + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + } else { + // dll is a CDLL/PyDLL/WinDLL object with _handle attribute + dll.get_attr("_handle", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + }; + + // Get the library from cache + let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library = library_cache + .get_lib(handle) + .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; + + // Get symbol address from library + let symbol_name = format!("{}\0", name.as_str()); + let inner_lib = library.lib.lock(); + + let symbol_address = if let Some(lib) = &*inner_lib { + unsafe { + // Try to get the symbol from the library + let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { + vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) + })?; + *symbol as usize + } + } else { + return Err(vm.new_attribute_error("Library is closed".to_owned())); + }; + + // Get _type_ attribute and size + let type_attr = cls + .as_object() + .get_attr("_type_", vm) + .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; + let type_str = type_attr.str(vm)?.to_string(); + let size = get_size(&type_str); + + // Read value from symbol address + let value = if symbol_address != 0 && size > 0 { + // Safety: Reading from a symbol address provided by dlsym + unsafe { + let ptr = symbol_address as *const u8; + let bytes = std::slice::from_raw_parts(ptr, size); + bytes_to_pyobject(&cls, bytes, vm)? + } + } else { + vm.ctx.none() + }; + + // Create instance + let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?; + + // Store base reference to keep dll alive + if let Ok(simple_ref) = instance.clone().downcast::<PyCSimple>() { + simple_ref.cdata.write().base = Some(dll); + } + + Ok(instance) + } } impl PyCSimple { @@ -372,3 +1008,40 @@ impl PyCSimple { None } } + +static SIMPLE_BUFFER_METHODS: BufferMethods = BufferMethods { + obj_bytes: |buffer| { + rustpython_common::lock::PyMappedRwLockReadGuard::map( + rustpython_common::lock::PyRwLockReadGuard::map( + buffer.obj_as::<PyCSimple>().cdata.read(), + |x: &CDataObject| x, + ), + |x: &CDataObject| x.buffer.as_slice(), + ) + .into() + }, + obj_bytes_mut: |buffer| { + rustpython_common::lock::PyMappedRwLockWriteGuard::map( + rustpython_common::lock::PyRwLockWriteGuard::map( + buffer.obj_as::<PyCSimple>().cdata.write(), + |x: &mut CDataObject| x, + ), + |x: &mut CDataObject| x.buffer.as_mut_slice(), + ) + .into() + }, + release: |_| {}, + retain: |_| {}, +}; + +impl AsBuffer for PyCSimple { + fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { + let buffer_len = zelf.cdata.read().buffer.len(); + let buf = PyBuffer::new( + zelf.to_owned().into(), + BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes + &SIMPLE_BUFFER_METHODS, + ); + Ok(buf) + } +} diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index 05e4bdd0354..8d6da5808a7 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -5,6 +5,7 @@ use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; use num_traits::ToPrimitive; use super::structure::PyCStructure; +use super::union::PyCUnion; #[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")] #[derive(PyPayload, Debug)] @@ -102,14 +103,23 @@ impl GetDescriptor for PyCField { _ => return Ok(zelf.into()), }; - // Instance attribute access - read value from the structure's buffer + // Instance attribute access - read value from the structure/union's buffer if let Some(structure) = obj.downcast_ref::<PyCStructure>() { - let buffer = structure.buffer.read(); + let cdata = structure.cdata.read(); let offset = zelf.byte_offset; let size = zelf.byte_size; - if offset + size <= buffer.len() { - let bytes = &buffer[offset..offset + size]; + if offset + size <= cdata.buffer.len() { + let bytes = &cdata.buffer[offset..offset + size]; + return PyCField::bytes_to_value(bytes, size, vm); + } + } else if let Some(union) = obj.downcast_ref::<PyCUnion>() { + let cdata = union.cdata.read(); + let offset = zelf.byte_offset; + let size = zelf.byte_size; + + if offset + size <= cdata.buffer.len() { + let bytes = &cdata.buffer[offset..offset + size]; return PyCField::bytes_to_value(bytes, size, vm); } } @@ -187,7 +197,7 @@ impl PyCField { .downcast_ref::<PyCField>() .ok_or_else(|| vm.new_type_error("expected CField".to_owned()))?; - // Get the structure instance - use downcast_ref() to access the struct data + // Get the structure/union instance - use downcast_ref() to access the struct data if let Some(structure) = obj.downcast_ref::<PyCStructure>() { match value { PySetterValue::Assign(value) => { @@ -195,9 +205,9 @@ impl PyCField { let size = zelf.byte_size; let bytes = PyCField::value_to_bytes(&value, size, vm)?; - let mut buffer = structure.buffer.write(); - if offset + size <= buffer.len() { - buffer[offset..offset + size].copy_from_slice(&bytes); + let mut cdata = structure.cdata.write(); + if offset + size <= cdata.buffer.len() { + cdata.buffer[offset..offset + size].copy_from_slice(&bytes); } Ok(()) } @@ -205,9 +215,26 @@ impl PyCField { Err(vm.new_type_error("cannot delete structure field".to_owned())) } } + } else if let Some(union) = obj.downcast_ref::<PyCUnion>() { + match value { + PySetterValue::Assign(value) => { + let offset = zelf.byte_offset; + let size = zelf.byte_size; + let bytes = PyCField::value_to_bytes(&value, size, vm)?; + + let mut cdata = union.cdata.write(); + if offset + size <= cdata.buffer.len() { + cdata.buffer[offset..offset + size].copy_from_slice(&bytes); + } + Ok(()) + } + PySetterValue::Delete => { + Err(vm.new_type_error("cannot delete union field".to_owned())) + } + } } else { Err(vm.new_type_error(format!( - "descriptor works only on Structure instances, got {}", + "descriptor works only on Structure or Union instances, got {}", obj.class().name() ))) } diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index c1db4d58f8d..27e85c563ef 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -5,9 +5,10 @@ use crate::convert::ToPyObject; use crate::function::FuncArgs; use crate::stdlib::ctypes::PyCData; use crate::stdlib::ctypes::base::{PyCSimple, ffi_type_from_str}; +use crate::stdlib::ctypes::thunk::PyCThunk; use crate::types::Representable; use crate::types::{Callable, Constructor}; -use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use libffi::middle::{Arg, Cif, CodePtr, Type}; use libloading::Symbol; @@ -76,10 +77,73 @@ impl ReturnType for PyTypeRef { fn from_ffi_type( &self, - _value: *mut ffi::c_void, - _vm: &VirtualMachine, + value: *mut ffi::c_void, + vm: &VirtualMachine, ) -> PyResult<Option<PyObjectRef>> { - todo!() + // Get the type code from _type_ attribute + let type_code = self + .get_class_attr(vm.ctx.intern_str("_type_")) + .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())); + + let result = match type_code.as_deref() { + Some("b") => vm + .ctx + .new_int(unsafe { *(value as *const i8) } as i32) + .into(), + Some("B") => vm + .ctx + .new_int(unsafe { *(value as *const u8) } as i32) + .into(), + Some("c") => vm + .ctx + .new_bytes(vec![unsafe { *(value as *const u8) }]) + .into(), + Some("h") => vm + .ctx + .new_int(unsafe { *(value as *const i16) } as i32) + .into(), + Some("H") => vm + .ctx + .new_int(unsafe { *(value as *const u16) } as i32) + .into(), + Some("i") => vm.ctx.new_int(unsafe { *(value as *const i32) }).into(), + Some("I") => vm.ctx.new_int(unsafe { *(value as *const u32) }).into(), + Some("l") => vm + .ctx + .new_int(unsafe { *(value as *const libc::c_long) }) + .into(), + Some("L") => vm + .ctx + .new_int(unsafe { *(value as *const libc::c_ulong) }) + .into(), + Some("q") => vm + .ctx + .new_int(unsafe { *(value as *const libc::c_longlong) }) + .into(), + Some("Q") => vm + .ctx + .new_int(unsafe { *(value as *const libc::c_ulonglong) }) + .into(), + Some("f") => vm + .ctx + .new_float(unsafe { *(value as *const f32) } as f64) + .into(), + Some("d") => vm.ctx.new_float(unsafe { *(value as *const f64) }).into(), + Some("P") | Some("z") | Some("Z") => vm.ctx.new_int(value as usize).into(), + Some("?") => vm + .ctx + .new_bool(unsafe { *(value as *const u8) } != 0) + .into(), + None => { + // No _type_ attribute, try to create an instance of the type + // This handles cases like Structure or Array return types + return Ok(Some( + vm.ctx.new_int(unsafe { *(value as *const i32) }).into(), + )); + } + _ => return Err(vm.new_type_error("Unsupported return type".to_string())), + }; + Ok(Some(result)) } } @@ -219,6 +283,49 @@ impl Constructor for PyCFuncPtr { .map(Into::into); } + // Check if first argument is a Python callable (callback creation) + if first_arg.is_callable() { + // Get argument types and result type from the class + let argtypes = cls.get_attr(vm.ctx.intern_str("_argtypes_")); + let restype = cls.get_attr(vm.ctx.intern_str("_restype_")); + + // Create the thunk (C-callable wrapper for the Python function) + let thunk = PyCThunk::new(first_arg.clone(), argtypes.clone(), restype.clone(), vm)?; + let code_ptr = thunk.code_ptr(); + + // Parse argument types for storage + let arg_type_vec: Option<Vec<PyTypeRef>> = if let Some(ref args) = argtypes { + if vm.is_none(args) { + None + } else { + let mut types = Vec::new(); + for item in args.try_to_value::<Vec<PyObjectRef>>(vm)? { + types.push(item.downcast::<PyType>().map_err(|_| { + vm.new_type_error("_argtypes_ must be a sequence of types".to_string()) + })?); + } + Some(types) + } + } else { + None + }; + + // Store the thunk as a Python object to keep it alive + let thunk_ref: PyRef<PyCThunk> = thunk.into_ref(&vm.ctx); + + return Self { + ptr: PyRwLock::new(Some(code_ptr)), + needs_free: AtomicCell::new(true), + arg_types: PyRwLock::new(arg_type_vec), + _flags_: AtomicCell::new(0), + res_type: PyRwLock::new(restype), + name: PyRwLock::new(Some("<callback>".to_string())), + handler: thunk_ref.into(), + } + .into_ref_with_type(vm, cls) + .map(Into::into); + } + Err(vm.new_type_error("Expected an integer address or a tuple")) } } @@ -246,7 +353,8 @@ impl Callable for PyCFuncPtr { let return_type = zelf.res_type.read(); let ffi_return_type = return_type .as_ref() - .and_then(|t| ReturnType::to_ffi_type(&t.clone().downcast::<PyType>().unwrap())) + .and_then(|t| t.clone().downcast::<PyType>().ok()) + .and_then(|t| ReturnType::to_ffi_type(&t)) .unwrap_or_else(Type::i32); let cif = Cif::new(ffi_arg_types, ffi_return_type); @@ -269,14 +377,8 @@ impl Callable for PyCFuncPtr { let mut output: c_void = unsafe { cif.call(*code_ptr, &ffi_args) }; let return_type = return_type .as_ref() - .map(|f| { - f.clone() - .downcast::<PyType>() - .unwrap() - .from_ffi_type(&mut output, vm) - .ok() - .flatten() - }) + .and_then(|f| f.clone().downcast::<PyType>().ok()) + .map(|f| f.from_ffi_type(&mut output, vm).ok().flatten()) .unwrap_or_else(|| Some(vm.ctx.new_int(output as i32).as_object().to_pyobject(vm))); if let Some(return_type) = return_type { Ok(return_type) diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 16d795ea4da..cd9ba8a7a15 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -6,33 +6,36 @@ use crate::builtins::{PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::protocol::PyNumberMethods; use crate::stdlib::ctypes::PyCData; -use crate::types::AsNumber; -use crate::{PyObjectRef, PyResult, VirtualMachine}; +use crate::types::{AsNumber, Constructor}; +use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; + +use super::util::StgInfo; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] #[derive(PyPayload, Debug)] pub struct PyCPointerType { #[allow(dead_code)] - pub(crate) inner: PyCPointer, + pub stg_info: StgInfo, } #[pyclass(flags(IMMUTABLETYPE), with(AsNumber))] impl PyCPointerType { #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - use super::array::{PyCArray, PyCArrayType}; + use super::array::PyCArrayType; if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } // Pointer size let element_size = std::mem::size_of::<usize>(); + let total_size = element_size * (n as usize); + let mut stg_info = super::util::StgInfo::new(total_size, element_size); + stg_info.length = n as usize; Ok(PyCArrayType { - inner: PyCArray { - typ: PyRwLock::new(cls), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(vec![]), - }, + stg_info, + typ: PyRwLock::new(cls.as_object().to_owned()), + length: AtomicCell::new(n as usize), + element_size: AtomicCell::new(element_size), } .to_pyobject(vm)) } @@ -69,7 +72,23 @@ pub struct PyCPointer { contents: PyRwLock<PyObjectRef>, } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE))] +impl Constructor for PyCPointer { + type Args = (crate::function::OptionalArg<PyObjectRef>,); + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Get the initial contents value if provided + let initial_contents = args.0.into_option().unwrap_or_else(|| vm.ctx.none()); + + // Create a new PyCPointer instance with the provided value + PyCPointer { + contents: PyRwLock::new(initial_contents), + } + .into_ref_with_type(vm, cls) + .map(Into::into) + } +} + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] impl PyCPointer { // TODO: not correct #[pygetset] @@ -78,8 +97,173 @@ impl PyCPointer { Ok(contents) } #[pygetset(setter)] - fn set_contents(&self, contents: PyObjectRef) -> PyResult<()> { + fn set_contents(&self, contents: PyObjectRef, _vm: &VirtualMachine) -> PyResult<()> { + // Validate that the contents is a CData instance if we have a _type_ + // For now, just store it *self.contents.write() = contents; Ok(()) } + + #[pymethod] + fn __init__( + &self, + value: crate::function::OptionalArg<PyObjectRef>, + _vm: &VirtualMachine, + ) -> PyResult<()> { + // Pointer can be initialized with 0 or 1 argument + // If 1 argument is provided, it should be a CData instance + if let crate::function::OptionalArg::Present(val) = value { + *self.contents.write() = val; + } + + Ok(()) + } + + #[pyclassmethod] + fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { + if address == 0 { + return Err(vm.new_value_error("NULL pointer access".to_owned())); + } + // Pointer just stores the address value + Ok(PyCPointer { + contents: PyRwLock::new(vm.ctx.new_int(address).into()), + } + .into_ref_with_type(vm, cls)? + .into()) + } + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + source: PyObjectRef, + offset: crate::function::OptionalArg<isize>, + vm: &VirtualMachine, + ) -> PyResult { + use crate::TryFromObject; + use crate::protocol::PyBuffer; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + let size = std::mem::size_of::<usize>(); + + let buffer = PyBuffer::try_from_object(vm, source.clone())?; + + if buffer.desc.readonly { + return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + } + + let buffer_len = buffer.desc.len; + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Read pointer value from buffer + let bytes = buffer.obj_bytes(); + let ptr_bytes = &bytes[offset..offset + size]; + let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above")); + + Ok(PyCPointer { + contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()), + } + .into_ref_with_type(vm, cls)? + .into()) + } + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + source: crate::function::ArgBytesLike, + offset: crate::function::OptionalArg<isize>, + vm: &VirtualMachine, + ) -> PyResult { + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + let size = std::mem::size_of::<usize>(); + + let source_bytes = source.borrow_buf(); + let buffer_len = source_bytes.len(); + + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Read pointer value from buffer + let ptr_bytes = &source_bytes[offset..offset + size]; + let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above")); + + Ok(PyCPointer { + contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()), + } + .into_ref_with_type(vm, cls)? + .into()) + } + + #[pyclassmethod] + fn in_dll( + cls: PyTypeRef, + dll: PyObjectRef, + name: crate::builtins::PyStrRef, + vm: &VirtualMachine, + ) -> PyResult { + use libloading::Symbol; + + // Get the library handle from dll object + let handle = if let Ok(int_handle) = dll.try_int(vm) { + // dll is an integer handle + int_handle + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + } else { + // dll is a CDLL/PyDLL/WinDLL object with _handle attribute + dll.get_attr("_handle", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + }; + + // Get the library from cache + let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library = library_cache + .get_lib(handle) + .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; + + // Get symbol address from library + let symbol_name = format!("{}\0", name.as_str()); + let inner_lib = library.lib.lock(); + + let symbol_address = if let Some(lib) = &*inner_lib { + unsafe { + // Try to get the symbol from the library + let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { + vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) + })?; + *symbol as usize + } + } else { + return Err(vm.new_attribute_error("Library is closed".to_owned())); + }; + + // For pointer types, we return a pointer to the symbol address + Ok(PyCPointer { + contents: PyRwLock::new(vm.ctx.new_int(symbol_address).into()), + } + .into_ref_with_type(vm, cls)? + .into()) + } } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index e7180a48e7b..1c842e21b5c 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -1,12 +1,13 @@ -use super::base::PyCData; +use super::base::{CDataObject, PyCData}; use super::field::PyCField; +use super::util::StgInfo; use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; -use crate::protocol::PyNumberMethods; +use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; use crate::stdlib::ctypes::_ctypes::get_size; -use crate::types::{AsNumber, Constructor}; -use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::types::{AsBuffer, AsNumber, Constructor}; +use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use indexmap::IndexMap; use num_traits::ToPrimitive; @@ -16,7 +17,10 @@ use std::fmt::Debug; /// PyCStructType - metaclass for Structure #[pyclass(name = "PyCStructType", base = PyType, module = "_ctypes")] #[derive(Debug, PyPayload)] -pub struct PyCStructType {} +pub struct PyCStructType { + #[allow(dead_code)] + pub stg_info: StgInfo, +} impl Constructor for PyCStructType { type Args = FuncArgs; @@ -92,10 +96,10 @@ impl PyCStructType { let size = Self::get_field_size(&field_type, vm)?; // Create CField descriptor (accepts any ctypes type including arrays) - let cfield = PyCField::new(name.clone(), field_type, offset, size, index); + let c_field = PyCField::new(name.clone(), field_type, offset, size, index); // Set the CField as a class attribute - cls.set_attr(vm.ctx.intern_str(name), cfield.to_pyobject(vm)); + cls.set_attr(vm.ctx.intern_str(name), c_field.to_pyobject(vm)); offset += size; } @@ -133,22 +137,46 @@ impl PyCStructType { Ok(std::mem::size_of::<usize>()) } + /// Get the alignment of a ctypes type + fn get_field_align(field_type: &PyObjectRef, vm: &VirtualMachine) -> usize { + // Try to get _type_ attribute for simple types + if let Some(align) = field_type + .get_attr("_type_", vm) + .ok() + .and_then(|type_attr| type_attr.str(vm).ok()) + .and_then(|type_str| { + let s = type_str.to_string(); + (s.len() == 1).then(|| get_size(&s)) // alignment == size for simple types + }) + { + return align; + } + // Default alignment + 1 + } + #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - use super::array::{PyCArray, PyCArrayType}; + use super::array::PyCArrayType; + use crate::stdlib::ctypes::_ctypes::size_of; + if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } - // TODO: Calculate element size properly - // For structures, element size is the structure size (sum of field sizes) - let element_size = std::mem::size_of::<usize>(); // Default, should calculate from fields + + // Calculate element size from the Structure type + let element_size = size_of(cls.clone().into(), vm)?; + + let total_size = element_size + .checked_mul(n as usize) + .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; + let mut stg_info = super::util::StgInfo::new(total_size, element_size); + stg_info.length = n as usize; Ok(PyCArrayType { - inner: PyCArray { - typ: PyRwLock::new(cls), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(vec![]), - }, + stg_info, + typ: PyRwLock::new(cls.clone().into()), + length: AtomicCell::new(n as usize), + element_size: AtomicCell::new(element_size), } .to_pyobject(vm)) } @@ -193,19 +221,17 @@ pub struct FieldInfo { )] #[derive(PyPayload)] pub struct PyCStructure { - /// Raw memory buffer for the structure - pub(super) buffer: PyRwLock<Vec<u8>>, + /// Common CDataObject for memory buffer + pub(super) cdata: PyRwLock<CDataObject>, /// Field information (name -> FieldInfo) #[allow(dead_code)] pub(super) fields: PyRwLock<IndexMap<String, FieldInfo>>, - /// Total size of the structure - pub(super) size: AtomicCell<usize>, } impl Debug for PyCStructure { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PyCStructure") - .field("size", &self.size.load()) + .field("size", &self.cdata.read().size()) .finish() } } @@ -219,6 +245,7 @@ impl Constructor for PyCStructure { let mut fields_map = IndexMap::new(); let mut total_size = 0usize; + let mut max_align = 1usize; if let Some(fields_attr) = fields_attr { let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>() @@ -244,6 +271,8 @@ impl Constructor for PyCStructure { let name = name.to_string(); let field_type = field_tuple.get(1).unwrap().clone(); let size = PyCStructType::get_field_size(&field_type, vm)?; + let field_align = PyCStructType::get_field_align(&field_type, vm); + max_align = max_align.max(field_align); let type_ref = field_type .downcast::<PyType>() @@ -265,12 +294,11 @@ impl Constructor for PyCStructure { } // Initialize buffer with zeros - let buffer = vec![0u8; total_size]; - + let mut stg_info = StgInfo::new(total_size, max_align); + stg_info.length = fields_map.len(); let instance = PyCStructure { - buffer: PyRwLock::new(buffer), + cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)), fields: PyRwLock::new(fields_map.clone()), - size: AtomicCell::new(total_size), }; // Handle keyword arguments for field initialization @@ -305,9 +333,170 @@ impl Constructor for PyCStructure { #[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] impl PyCStructure { + #[pygetset] + fn _objects(&self) -> Option<PyObjectRef> { + self.cdata.read().objects.clone() + } + #[pygetset] fn _fields_(&self, vm: &VirtualMachine) -> PyObjectRef { // Return the _fields_ from the class, not instance vm.ctx.none() } + + #[pyclassmethod] + fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { + use crate::stdlib::ctypes::_ctypes::size_of; + + // Get size from cls + let size = size_of(cls.clone().into(), vm)?; + + // Read data from address + if address == 0 || size == 0 { + return Err(vm.new_value_error("NULL pointer access".to_owned())); + } + let data = unsafe { + let ptr = address as *const u8; + std::slice::from_raw_parts(ptr, size).to_vec() + }; + + // Create instance + Ok(PyCStructure { + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + fields: PyRwLock::new(IndexMap::new()), + } + .into_ref_with_type(vm, cls)? + .into()) + } + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + source: PyObjectRef, + offset: crate::function::OptionalArg<isize>, + vm: &VirtualMachine, + ) -> PyResult { + use crate::TryFromObject; + use crate::protocol::PyBuffer; + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get buffer from source + let buffer = PyBuffer::try_from_object(vm, source.clone())?; + + // Check if buffer is writable + if buffer.desc.readonly { + return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + } + + // Get size from cls + let size = size_of(cls.clone().into(), vm)?; + + // Check if buffer is large enough + let buffer_len = buffer.desc.len; + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Read bytes from buffer at offset + let bytes = buffer.obj_bytes(); + let data = bytes[offset..offset + size].to_vec(); + + // Create instance + Ok(PyCStructure { + cdata: PyRwLock::new(CDataObject::from_bytes(data, Some(source))), + fields: PyRwLock::new(IndexMap::new()), + } + .into_ref_with_type(vm, cls)? + .into()) + } + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + source: crate::function::ArgBytesLike, + offset: crate::function::OptionalArg<isize>, + vm: &VirtualMachine, + ) -> PyResult { + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get size from cls + let size = size_of(cls.clone().into(), vm)?; + + // Borrow bytes from source + let source_bytes = source.borrow_buf(); + let buffer_len = source_bytes.len(); + + // Check if buffer is large enough + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Copy bytes from buffer at offset + let data = source_bytes[offset..offset + size].to_vec(); + + // Create instance + Ok(PyCStructure { + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + fields: PyRwLock::new(IndexMap::new()), + } + .into_ref_with_type(vm, cls)? + .into()) + } +} + +static STRUCTURE_BUFFER_METHODS: BufferMethods = BufferMethods { + obj_bytes: |buffer| { + rustpython_common::lock::PyMappedRwLockReadGuard::map( + rustpython_common::lock::PyRwLockReadGuard::map( + buffer.obj_as::<PyCStructure>().cdata.read(), + |x: &CDataObject| x, + ), + |x: &CDataObject| x.buffer.as_slice(), + ) + .into() + }, + obj_bytes_mut: |buffer| { + rustpython_common::lock::PyMappedRwLockWriteGuard::map( + rustpython_common::lock::PyRwLockWriteGuard::map( + buffer.obj_as::<PyCStructure>().cdata.write(), + |x: &mut CDataObject| x, + ), + |x: &mut CDataObject| x.buffer.as_mut_slice(), + ) + .into() + }, + release: |_| {}, + retain: |_| {}, +}; + +impl AsBuffer for PyCStructure { + fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { + let buffer_len = zelf.cdata.read().buffer.len(); + let buf = PyBuffer::new( + zelf.to_owned().into(), + BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes + &STRUCTURE_BUFFER_METHODS, + ); + Ok(buf) + } } diff --git a/crates/vm/src/stdlib/ctypes/thunk.rs b/crates/vm/src/stdlib/ctypes/thunk.rs index a65b04684b0..2de2308e1a3 100644 --- a/crates/vm/src/stdlib/ctypes/thunk.rs +++ b/crates/vm/src/stdlib/ctypes/thunk.rs @@ -1,22 +1,319 @@ -//! Yes, really, this is not a typo. - -// typedef struct { -// PyObject_VAR_HEAD -// ffi_closure *pcl_write; /* the C callable, writeable */ -// void *pcl_exec; /* the C callable, executable */ -// ffi_cif cif; -// int flags; -// PyObject *converters; -// PyObject *callable; -// PyObject *restype; -// SETFUNC setfunc; -// ffi_type *ffi_restype; -// ffi_type *atypes[1]; -// } CThunkObject; +//! FFI callback (thunk) implementation for ctypes. +//! +//! This module implements CThunkObject which wraps Python callables +//! to be callable from C code via libffi closures. +use crate::builtins::{PyStr, PyType, PyTypeRef}; +use crate::vm::thread::with_current_vm; +use crate::{PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use libffi::low; +use libffi::middle::{Cif, Closure, CodePtr, Type}; +use num_traits::ToPrimitive; +use rustpython_common::lock::PyRwLock; +use std::ffi::c_void; +use std::fmt::Debug; + +use super::base::ffi_type_from_str; +/// Userdata passed to the libffi callback. +/// This contains everything needed to invoke the Python callable. +pub struct ThunkUserData { + /// The Python callable to invoke + pub callable: PyObjectRef, + /// Argument types for conversion + pub arg_types: Vec<PyTypeRef>, + /// Result type for conversion (None means void) + pub res_type: Option<PyTypeRef>, +} + +/// Get the type code string from a ctypes type +fn get_type_code(ty: &PyTypeRef, vm: &VirtualMachine) -> Option<String> { + ty.get_attr(vm.ctx.intern_str("_type_")) + .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())) +} + +/// Convert a C value to a Python object based on the type code +fn ffi_to_python(ty: &PyTypeRef, ptr: *const c_void, vm: &VirtualMachine) -> PyObjectRef { + let type_code = get_type_code(ty, vm); + // SAFETY: ptr is guaranteed to be valid by libffi calling convention + unsafe { + match type_code.as_deref() { + Some("b") => vm.ctx.new_int(*(ptr as *const i8) as i32).into(), + Some("B") => vm.ctx.new_int(*(ptr as *const u8) as i32).into(), + Some("c") => vm.ctx.new_bytes(vec![*(ptr as *const u8)]).into(), + Some("h") => vm.ctx.new_int(*(ptr as *const i16) as i32).into(), + Some("H") => vm.ctx.new_int(*(ptr as *const u16) as i32).into(), + Some("i") => vm.ctx.new_int(*(ptr as *const i32)).into(), + Some("I") => vm.ctx.new_int(*(ptr as *const u32)).into(), + Some("l") => vm.ctx.new_int(*(ptr as *const libc::c_long)).into(), + Some("L") => vm.ctx.new_int(*(ptr as *const libc::c_ulong)).into(), + Some("q") => vm.ctx.new_int(*(ptr as *const libc::c_longlong)).into(), + Some("Q") => vm.ctx.new_int(*(ptr as *const libc::c_ulonglong)).into(), + Some("f") => vm.ctx.new_float(*(ptr as *const f32) as f64).into(), + Some("d") => vm.ctx.new_float(*(ptr as *const f64)).into(), + Some("P") | Some("z") | Some("Z") => vm.ctx.new_int(ptr as usize).into(), + _ => vm.ctx.none(), + } + } +} + +/// Convert a Python object to a C value and store it at the result pointer +fn python_to_ffi(obj: PyResult, ty: &PyTypeRef, result: *mut c_void, vm: &VirtualMachine) { + let obj = match obj { + Ok(o) => o, + Err(_) => return, // Exception occurred, leave result as-is + }; + + let type_code = get_type_code(ty, vm); + // SAFETY: result is guaranteed to be valid by libffi calling convention + unsafe { + match type_code.as_deref() { + Some("b") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut i8) = i.as_bigint().to_i8().unwrap_or(0); + } + } + Some("B") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0); + } + } + Some("c") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0); + } + } + Some("h") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut i16) = i.as_bigint().to_i16().unwrap_or(0); + } + } + Some("H") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u16) = i.as_bigint().to_u16().unwrap_or(0); + } + } + Some("i") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut i32) = i.as_bigint().to_i32().unwrap_or(0); + } + } + Some("I") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u32) = i.as_bigint().to_u32().unwrap_or(0); + } + } + Some("l") | Some("q") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut i64) = i.as_bigint().to_i64().unwrap_or(0); + } + } + Some("L") | Some("Q") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u64) = i.as_bigint().to_u64().unwrap_or(0); + } + } + Some("f") => { + if let Ok(f) = obj.try_float(vm) { + *(result as *mut f32) = f.to_f64() as f32; + } + } + Some("d") => { + if let Ok(f) = obj.try_float(vm) { + *(result as *mut f64) = f.to_f64(); + } + } + Some("P") | Some("z") | Some("Z") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut usize) = i.as_bigint().to_usize().unwrap_or(0); + } + } + _ => {} + } + } +} + +/// The callback function that libffi calls when the closure is invoked. +/// This function converts C arguments to Python objects, calls the Python +/// callable, and converts the result back to C. +unsafe extern "C" fn thunk_callback( + _cif: &low::ffi_cif, + result: &mut c_void, + args: *const *const c_void, + userdata: &ThunkUserData, +) { + with_current_vm(|vm| { + // Convert C arguments to Python objects + let py_args: Vec<PyObjectRef> = userdata + .arg_types + .iter() + .enumerate() + .map(|(i, ty)| { + let arg_ptr = unsafe { *args.add(i) }; + ffi_to_python(ty, arg_ptr, vm) + }) + .collect(); + + // Call the Python callable + let py_result = userdata.callable.call(py_args, vm); + + // Convert result back to C type + if let Some(ref res_type) = userdata.res_type { + python_to_ffi(py_result, res_type, result as *mut c_void, vm); + } + }); +} + +/// Holds the closure and userdata together to ensure proper lifetime. +/// The userdata is leaked to create a 'static reference that the closure can use. +struct ThunkData { + #[allow(dead_code)] + closure: Closure<'static>, + /// Raw pointer to the leaked userdata, for cleanup + userdata_ptr: *mut ThunkUserData, +} + +impl Drop for ThunkData { + fn drop(&mut self) { + // SAFETY: We created this with Box::into_raw, so we can reclaim it + unsafe { + drop(Box::from_raw(self.userdata_ptr)); + } + } +} + +/// CThunkObject wraps a Python callable to make it callable from C code. #[pyclass(name = "CThunkObject", module = "_ctypes")] -#[derive(Debug, PyPayload)] -pub struct PyCThunk {} +#[derive(PyPayload)] +pub struct PyCThunk { + /// The Python callable + callable: PyObjectRef, + /// The libffi closure (must be kept alive) + #[allow(dead_code)] + thunk_data: PyRwLock<Option<ThunkData>>, + /// The code pointer for the closure + code_ptr: CodePtr, +} + +impl Debug for PyCThunk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyCThunk") + .field("callable", &self.callable) + .finish() + } +} + +impl PyCThunk { + /// Create a new thunk wrapping a Python callable. + /// + /// # Arguments + /// * `callable` - The Python callable to wrap + /// * `arg_types` - Optional sequence of argument types + /// * `res_type` - Optional result type + /// * `vm` - The virtual machine + pub fn new( + callable: PyObjectRef, + arg_types: Option<PyObjectRef>, + res_type: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<Self> { + // Parse argument types + let arg_type_vec: Vec<PyTypeRef> = if let Some(args) = arg_types { + if vm.is_none(&args) { + Vec::new() + } else { + let mut types = Vec::new(); + for item in args.try_to_value::<Vec<PyObjectRef>>(vm)? { + types.push(item.downcast::<PyType>().map_err(|_| { + vm.new_type_error("_argtypes_ must be a sequence of types".to_string()) + })?); + } + types + } + } else { + Vec::new() + }; + + // Parse result type + let res_type_ref: Option<PyTypeRef> = + if let Some(ref rt) = res_type { + if vm.is_none(rt) { + None + } else { + Some(rt.clone().downcast::<PyType>().map_err(|_| { + vm.new_type_error("restype must be a ctypes type".to_string()) + })?) + } + } else { + None + }; + + // Build FFI types + let ffi_arg_types: Vec<Type> = arg_type_vec + .iter() + .map(|ty| { + get_type_code(ty, vm) + .and_then(|code| ffi_type_from_str(&code)) + .unwrap_or(Type::pointer()) + }) + .collect(); + + let ffi_res_type = res_type_ref + .as_ref() + .and_then(|ty| get_type_code(ty, vm)) + .and_then(|code| ffi_type_from_str(&code)) + .unwrap_or(Type::void()); + + // Create the CIF + let cif = Cif::new(ffi_arg_types, ffi_res_type); + + // Create userdata and leak it to get a 'static reference + let userdata = Box::new(ThunkUserData { + callable: callable.clone(), + arg_types: arg_type_vec, + res_type: res_type_ref, + }); + let userdata_ptr = Box::into_raw(userdata); + + // SAFETY: We maintain the userdata lifetime by storing it in ThunkData + // and cleaning it up in Drop + let userdata_ref: &'static ThunkUserData = unsafe { &*userdata_ptr }; + + // Create the closure + let closure = Closure::new(cif, thunk_callback, userdata_ref); + + // Get the code pointer + let code_ptr = CodePtr(*closure.code_ptr() as *mut _); + + // Store closure and userdata together + let thunk_data = ThunkData { + closure, + userdata_ptr, + }; + + Ok(Self { + callable, + thunk_data: PyRwLock::new(Some(thunk_data)), + code_ptr, + }) + } + + /// Get the code pointer for this thunk + pub fn code_ptr(&self) -> CodePtr { + self.code_ptr + } +} + +// SAFETY: PyCThunk is safe to send/sync because: +// - callable is a PyObjectRef which is Send+Sync +// - thunk_data contains the libffi closure which is heap-allocated +// - code_ptr is just a pointer to executable memory +unsafe impl Send for PyCThunk {} +unsafe impl Sync for PyCThunk {} #[pyclass] -impl PyCThunk {} +impl PyCThunk { + #[pygetset] + fn callable(&self) -> PyObjectRef { + self.callable.clone() + } +} diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index a357c195d69..aa78d56c46b 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -1,17 +1,30 @@ -use super::base::PyCData; +use super::base::{CDataObject, PyCData}; use super::field::PyCField; +use super::util::StgInfo; use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; +use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer as ProtocolPyBuffer}; use crate::stdlib::ctypes::_ctypes::get_size; -use crate::types::Constructor; -use crate::{PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::types::{AsBuffer, Constructor}; +use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use num_traits::ToPrimitive; +use rustpython_common::lock::PyRwLock; /// PyCUnionType - metaclass for Union #[pyclass(name = "UnionType", base = PyType, module = "_ctypes")] #[derive(Debug, PyPayload)] -pub struct PyCUnionType {} +pub struct PyCUnionType { + pub stg_info: StgInfo, +} + +impl Default for PyCUnionType { + fn default() -> Self { + PyCUnionType { + stg_info: StgInfo::new(0, 1), + } + } +} impl Constructor for PyCUnionType { type Args = FuncArgs; @@ -74,9 +87,9 @@ impl PyCUnionType { // For Union, all fields start at offset 0 // Create CField descriptor (accepts any ctypes type including arrays) - let cfield = PyCField::new(name.clone(), field_type, 0, size, index); + let c_field = PyCField::new(name.clone(), field_type, 0, size, index); - cls.set_attr(vm.ctx.intern_str(name), cfield.to_pyobject(vm)); + cls.set_attr(vm.ctx.intern_str(name), c_field.to_pyobject(vm)); } Ok(()) @@ -114,7 +127,202 @@ impl PyCUnionType {} /// PyCUnion - base class for Union #[pyclass(module = "_ctypes", name = "Union", base = PyCData, metaclass = "PyCUnionType")] -pub struct PyCUnion {} +#[derive(PyPayload)] +pub struct PyCUnion { + /// Common CDataObject for memory buffer + pub(super) cdata: PyRwLock<CDataObject>, +} + +impl std::fmt::Debug for PyCUnion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyCUnion") + .field("size", &self.cdata.read().size()) + .finish() + } +} + +impl Constructor for PyCUnion { + type Args = FuncArgs; + + fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Get _fields_ from the class + let fields_attr = cls.as_object().get_attr("_fields_", vm).ok(); + + // Calculate union size (max of all field sizes) and alignment + let mut max_size = 0usize; + let mut max_align = 1usize; + + if let Some(fields_attr) = fields_attr { + let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>() + { + list.borrow_vec().to_vec() + } else if let Some(tuple) = fields_attr.downcast_ref::<PyTuple>() { + tuple.to_vec() + } else { + vec![] + }; + + for field in fields.iter() { + let Some(field_tuple) = field.downcast_ref::<PyTuple>() else { + continue; + }; + if field_tuple.len() < 2 { + continue; + } + let field_type = field_tuple.get(1).unwrap().clone(); + let size = PyCUnionType::get_field_size(&field_type, vm)?; + max_size = max_size.max(size); + // For simple types, alignment == size + max_align = max_align.max(size); + } + } + + // Initialize buffer with zeros + let stg_info = StgInfo::new(max_size, max_align); + PyCUnion { + cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)), + } + .into_ref_with_type(vm, cls) + .map(Into::into) + } +} + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsBuffer))] +impl PyCUnion { + #[pygetset] + fn _objects(&self) -> Option<PyObjectRef> { + self.cdata.read().objects.clone() + } + + #[pyclassmethod] + fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { + use crate::stdlib::ctypes::_ctypes::size_of; + + // Get size from cls + let size = size_of(cls.clone().into(), vm)?; + + // Create instance with data from address + if address == 0 || size == 0 { + return Err(vm.new_value_error("NULL pointer access".to_owned())); + } + let stg_info = StgInfo::new(size, 1); + Ok(PyCUnion { + cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)), + } + .into_ref_with_type(vm, cls)? + .into()) + } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE))] -impl PyCUnion {} + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + source: PyObjectRef, + offset: crate::function::OptionalArg<isize>, + vm: &VirtualMachine, + ) -> PyResult { + use crate::TryFromObject; + use crate::protocol::PyBuffer; + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + let buffer = PyBuffer::try_from_object(vm, source.clone())?; + + if buffer.desc.readonly { + return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + } + + let size = size_of(cls.clone().into(), vm)?; + let buffer_len = buffer.desc.len; + + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Copy data from source buffer + let bytes = buffer.obj_bytes(); + let data = bytes[offset..offset + size].to_vec(); + + Ok(PyCUnion { + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + } + .into_ref_with_type(vm, cls)? + .into()) + } + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + source: crate::function::ArgBytesLike, + offset: crate::function::OptionalArg<isize>, + vm: &VirtualMachine, + ) -> PyResult { + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + let size = size_of(cls.clone().into(), vm)?; + let source_bytes = source.borrow_buf(); + let buffer_len = source_bytes.len(); + + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Copy data from source + let data = source_bytes[offset..offset + size].to_vec(); + + Ok(PyCUnion { + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + } + .into_ref_with_type(vm, cls)? + .into()) + } +} + +static UNION_BUFFER_METHODS: BufferMethods = BufferMethods { + obj_bytes: |buffer| { + rustpython_common::lock::PyRwLockReadGuard::map( + buffer.obj_as::<PyCUnion>().cdata.read(), + |x: &CDataObject| x.buffer.as_slice(), + ) + .into() + }, + obj_bytes_mut: |buffer| { + rustpython_common::lock::PyRwLockWriteGuard::map( + buffer.obj_as::<PyCUnion>().cdata.write(), + |x: &mut CDataObject| x.buffer.as_mut_slice(), + ) + .into() + }, + release: |_| {}, + retain: |_| {}, +}; + +impl AsBuffer for PyCUnion { + fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<ProtocolPyBuffer> { + let buffer_len = zelf.cdata.read().buffer.len(); + let buf = ProtocolPyBuffer::new( + zelf.to_owned().into(), + BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes + &UNION_BUFFER_METHODS, + ); + Ok(buf) + } +} diff --git a/crates/vm/src/stdlib/ctypes/util.rs b/crates/vm/src/stdlib/ctypes/util.rs new file mode 100644 index 00000000000..b8fd9c89c0b --- /dev/null +++ b/crates/vm/src/stdlib/ctypes/util.rs @@ -0,0 +1,41 @@ +use crate::PyObjectRef; + +/// Storage information for ctypes types +#[derive(Debug, Clone)] +pub struct StgInfo { + #[allow(dead_code)] + pub initialized: bool, + pub size: usize, // number of bytes + pub align: usize, // alignment requirements + pub length: usize, // number of fields (for arrays/structures) + #[allow(dead_code)] + pub proto: Option<PyObjectRef>, // Only for Pointer/ArrayObject + #[allow(dead_code)] + pub flags: i32, // calling convention and such +} + +impl Default for StgInfo { + fn default() -> Self { + StgInfo { + initialized: false, + size: 0, + align: 1, + length: 0, + proto: None, + flags: 0, + } + } +} + +impl StgInfo { + pub fn new(size: usize, align: usize) -> Self { + StgInfo { + initialized: true, + size, + align, + length: 0, + proto: None, + flags: 0, + } + } +} From 4051becc9e252e91424b00656079b8878f7c0995 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 30 Nov 2025 02:05:29 +0200 Subject: [PATCH 387/819] Ensure `BuildSlice` oparg to be either 2 or 3 (#6313) * Force `BuildSlice` oparg to be either 2 or 3 * `compile_slice` to return `BuildSliceArgCount` --- crates/codegen/src/compile.rs | 34 +++++++++-------- crates/compiler-core/src/bytecode.rs | 55 +++++++++++++++++++++++++--- crates/vm/src/frame.rs | 15 ++++++-- 3 files changed, 80 insertions(+), 24 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 4a4f17edcfa..387c2e819ed 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -35,8 +35,8 @@ use ruff_text_size::{Ranged, TextRange}; use rustpython_compiler_core::{ Mode, OneIndexed, PositionEncoding, SourceFile, SourceLocation, bytecode::{ - self, Arg as OpArgMarker, BinaryOperator, CodeObject, ComparisonOperator, ConstantData, - Instruction, Invert, OpArg, OpArgType, UnpackExArgs, + self, Arg as OpArgMarker, BinaryOperator, BuildSliceArgCount, CodeObject, + ComparisonOperator, ConstantData, Instruction, Invert, OpArg, OpArgType, UnpackExArgs, }, }; use rustpython_wtf8::Wtf8Buf; @@ -401,7 +401,7 @@ impl Compiler { /// Compile a slice expression // = compiler_slice - fn compile_slice(&mut self, s: &ExprSlice) -> CompileResult<u32> { + fn compile_slice(&mut self, s: &ExprSlice) -> CompileResult<BuildSliceArgCount> { // Compile lower if let Some(lower) = &s.lower { self.compile_expression(lower)?; @@ -416,13 +416,14 @@ impl Compiler { self.emit_load_const(ConstantData::None); } - // Compile step if present - if let Some(step) = &s.step { - self.compile_expression(step)?; - Ok(3) // Three values on stack - } else { - Ok(2) // Two values on stack - } + Ok(match &s.step { + Some(step) => { + // Compile step if present + self.compile_expression(step)?; + BuildSliceArgCount::Three + } + None => BuildSliceArgCount::Two, + }) } /// Compile a subscript expression @@ -449,19 +450,19 @@ impl Compiler { // Handle two-element slice (for Load/Store, not Del) if Self::is_two_element_slice(slice) && !matches!(ctx, ExprContext::Del) { - let n = match slice { + let argc = match slice { Expr::Slice(s) => self.compile_slice(s)?, _ => unreachable!("is_two_element_slice should only return true for Expr::Slice"), }; match ctx { ExprContext::Load => { // CPython uses BINARY_SLICE - emit!(self, Instruction::BuildSlice { step: n == 3 }); + emit!(self, Instruction::BuildSlice { argc }); emit!(self, Instruction::Subscript); } ExprContext::Store => { // CPython uses STORE_SLICE - emit!(self, Instruction::BuildSlice { step: n == 3 }); + emit!(self, Instruction::BuildSlice { argc }); emit!(self, Instruction::StoreSubscript); } _ => unreachable!(), @@ -4587,8 +4588,11 @@ impl Compiler { if let Some(step) = step { self.compile_expression(step)?; } - let step = step.is_some(); - emit!(self, Instruction::BuildSlice { step }); + let argc = match step { + Some(_) => BuildSliceArgCount::Three, + None => BuildSliceArgCount::Two, + }; + emit!(self, Instruction::BuildSlice { argc }); } Expr::Yield(ExprYield { value, .. }) => { if !self.ctx.in_func() { diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 144054860e4..8a095de6114 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -10,7 +10,7 @@ use itertools::Itertools; use malachite_bigint::BigInt; use num_complex::Complex64; use rustpython_wtf8::{Wtf8, Wtf8Buf}; -use std::{collections::BTreeSet, fmt, hash, marker::PhantomData, mem, ops::Deref}; +use std::{collections::BTreeSet, fmt, hash, marker::PhantomData, mem, num::NonZeroU8, ops::Deref}; #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] #[repr(i8)] @@ -760,8 +760,7 @@ pub enum Instruction { index: Arg<u32>, }, BuildSlice { - /// whether build a slice with a third step argument - step: Arg<bool>, + argc: Arg<BuildSliceArgCount>, }, ListAppend { i: Arg<u32>, @@ -1151,6 +1150,48 @@ op_arg_enum!( } ); +/// Specifies if a slice is built with either 2 or 3 arguments. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum BuildSliceArgCount { + /// ```py + /// x[5:10] + /// ``` + Two, + /// ```py + /// x[5:10:2] + /// ``` + Three, +} + +impl OpArgType for BuildSliceArgCount { + #[inline(always)] + fn from_op_arg(x: u32) -> Option<Self> { + Some(match x { + 2 => Self::Two, + 3 => Self::Three, + _ => return None, + }) + } + + #[inline(always)] + fn to_op_arg(self) -> u32 { + u32::from(self.argc().get()) + } +} + +impl BuildSliceArgCount { + /// Get the numeric value of `Self`. + #[must_use] + pub const fn argc(self) -> NonZeroU8 { + let inner = match self { + Self::Two => 2, + Self::Three => 3, + }; + // Safety: `inner` can be either 2 or 3. + unsafe { NonZeroU8::new_unchecked(inner) } + } +} + #[derive(Copy, Clone)] pub struct UnpackExArgs { pub before: u8, @@ -1547,7 +1588,11 @@ impl Instruction { -(nargs as i32) + 1 } DictUpdate { .. } => -1, - BuildSlice { step } => -2 - (step.get(arg) as i32) + 1, + BuildSlice { argc } => { + // push 1 + // pops either 2/3 + 1 - (argc.get(arg).argc().get() as i32) + } ListAppend { .. } | SetAdd { .. } => -1, MapAdd { .. } => -2, PrintExpr => -1, @@ -1734,7 +1779,7 @@ impl Instruction { BuildMap { size } => w!(BuildMap, size), BuildMapForCall { size } => w!(BuildMapForCall, size), DictUpdate { index } => w!(DictUpdate, index), - BuildSlice { step } => w!(BuildSlice, step), + BuildSlice { argc } => w!(BuildSlice, ?argc), ListAppend { i } => w!(ListAppend, i), SetAdd { i } => w!(SetAdd, i), MapAdd { i } => w!(MapAdd, i), diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 148abacf6ad..088b5dcb4b5 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -834,8 +834,8 @@ impl ExecutingFrame<'_> { dict.merge_object(source, vm)?; Ok(None) } - bytecode::Instruction::BuildSlice { step } => { - self.execute_build_slice(vm, step.get(arg)) + bytecode::Instruction::BuildSlice { argc } => { + self.execute_build_slice(vm, argc.get(arg)) } bytecode::Instruction::ListAppend { i } => { let item = self.pop_value(); @@ -1777,8 +1777,15 @@ impl ExecutingFrame<'_> { Ok(None) } - fn execute_build_slice(&mut self, vm: &VirtualMachine, step: bool) -> FrameResult { - let step = if step { Some(self.pop_value()) } else { None }; + fn execute_build_slice( + &mut self, + vm: &VirtualMachine, + argc: bytecode::BuildSliceArgCount, + ) -> FrameResult { + let step = match argc { + bytecode::BuildSliceArgCount::Two => None, + bytecode::BuildSliceArgCount::Three => Some(self.pop_value()), + }; let stop = self.pop_value(); let start = self.pop_value(); From b84f7c19ad3cf4808d2f8bb0aef8dd69b9a06415 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 30 Nov 2025 03:16:50 +0200 Subject: [PATCH 388/819] Fix super and Update `test_{descr,super}.py` from 3.13.9 (#6314) * Update `test_descr.py` from 3.13.9 * Update `test_super.py` from 3.13.9 --- Lib/test/test_descr.py | 265 ++++++++++++++------------- Lib/test/test_super.py | 310 +++++++++++++++++++++++++++----- crates/vm/src/builtins/super.rs | 25 ++- 3 files changed, 423 insertions(+), 177 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 8a424c8be8e..f592a88fc2c 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -15,6 +15,7 @@ from copy import deepcopy from contextlib import redirect_stdout from test import support +from test.support.testcase import ExtraAssertions try: import _testcapi @@ -403,15 +404,7 @@ def test_wrap_lenfunc_bad_cast(self): self.assertEqual(range(sys.maxsize).__len__(), sys.maxsize) -class ClassPropertiesAndMethods(unittest.TestCase): - - def assertHasAttr(self, obj, name): - self.assertTrue(hasattr(obj, name), - '%r has no attribute %r' % (obj, name)) - - def assertNotHasAttr(self, obj, name): - self.assertFalse(hasattr(obj, name), - '%r has unexpected attribute %r' % (obj, name)) +class ClassPropertiesAndMethods(unittest.TestCase, ExtraAssertions): def test_python_dicts(self): # Testing Python subclass of dict... @@ -989,8 +982,8 @@ class EditableScrollablePane(ScrollablePane,EditablePane): pass def test_mro_disagreement(self): # Testing error messages for MRO disagreement... - mro_err_msg = """Cannot create a consistent method resolution -order (MRO) for bases """ + mro_err_msg = ("Cannot create a consistent method resolution " + "order (MRO) for bases ") def raises(exc, expected, callable, *args): try: @@ -1109,8 +1102,7 @@ class MyFrozenSet(frozenset): with self.assertRaises(TypeError): frozenset().__class__ = MyFrozenSet - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_slots(self): # Testing __slots__... class C0(object): @@ -1199,10 +1191,9 @@ class C(object): pass else: self.fail("[''] slots not caught") - class C(object): + + class WithValidIdentifiers(object): __slots__ = ["a", "a_b", "_a", "A0123456789Z"] - # XXX(nnorwitz): was there supposed to be something tested - # from the class above? # Test a single string is not expanded as a sequence. class C(object): @@ -1316,7 +1307,7 @@ class X(object): # Inherit from object on purpose to check some backwards compatibility paths class X(object): __slots__ = "a" - with self.assertRaisesRegex(AttributeError, "'X' object has no attribute 'a'"): + with self.assertRaisesRegex(AttributeError, "'test.test_descr.ClassPropertiesAndMethods.test_slots.<locals>.X' object has no attribute 'a'"): X().a # Test string subclass in `__slots__`, see gh-98783 @@ -1328,8 +1319,7 @@ class X(object): with self.assertRaisesRegex(AttributeError, "'X' object has no attribute 'a'"): X().a - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_slots_special(self): # Testing __dict__ and __weakref__ in __slots__... class D(object): @@ -1368,8 +1358,7 @@ class C2(D, W): a.foo = 42 self.assertEqual(a.__dict__, {"foo": 42}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_slots_special2(self): # Testing __qualname__ and __classcell__ in __slots__ class Meta(type): @@ -1410,8 +1399,7 @@ class Q2: __qualname__ = object() __slots__ = ["__qualname__"] - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_slots_descriptor(self): # Issue2115: slot descriptors did not correctly check # the type of the given object @@ -1602,7 +1590,11 @@ def f(cls, arg): cm = classmethod(f) cm_dict = {'__annotations__': {}, - '__doc__': "f docstring", + '__doc__': ( + "f docstring" + if support.HAVE_PY_DOCSTRINGS + else None + ), '__module__': __name__, '__name__': 'f', '__qualname__': f.__qualname__} @@ -1674,8 +1666,6 @@ class SubSpam(spam.spamlist): pass spam_cm.__get__(None, list) self.assertEqual(str(cm.exception), expected_errmsg) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_staticmethods(self): # Testing static methods... class C(object): @@ -1693,10 +1683,10 @@ class D(C): self.assertEqual(d.foo(1), (d, 1)) self.assertEqual(D.foo(d, 1), (d, 1)) sm = staticmethod(None) - self.assertEqual(sm.__dict__, {'__doc__': None}) + self.assertEqual(sm.__dict__, {'__doc__': None.__doc__}) sm.x = 42 self.assertEqual(sm.x, 42) - self.assertEqual(sm.__dict__, {"x" : 42, '__doc__': None}) + self.assertEqual(sm.__dict__, {"x" : 42, '__doc__': None.__doc__}) del sm.x self.assertNotHasAttr(sm, "x") @@ -1811,8 +1801,7 @@ class C(list): __new__ = object.__new__ self.assertRaises(TypeError, C) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_object_new(self): class A(object): pass @@ -1847,6 +1836,8 @@ def __init__(self, foo): object.__init__(A(3)) self.assertRaises(TypeError, object.__init__, A(3), 5) + @unittest.skip('TODO: RUSTPYTHON; This passes, but the `expectedFailure` here is from CPython, so this test is an "UNEXPECTED SUCCESS" (not good)') + @unittest.expectedFailure def test_restored_object_new(self): class A(object): def __new__(cls, *args, **kwargs): @@ -1870,8 +1861,7 @@ def __init__(self, foo): self.assertEqual(b.foo, 3) self.assertEqual(b.__class__, B) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_altmro(self): # Testing mro() and overriding it... class A(object): @@ -2050,8 +2040,7 @@ def test_methods_in_c(self): set_add.__get__(0) self.assertEqual(cm.exception.args[0], expected_errmsg) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_special_method_lookup(self): # The lookup of special methods bypasses __getattr__ and # __getattribute__, but they still can be descriptors. @@ -2254,8 +2243,7 @@ def __contains__(self, value): self.assertIn(i, p10) self.assertNotIn(10, p10) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_weakrefs(self): # Testing weak references... import weakref @@ -2287,8 +2275,7 @@ class Weak(object): self.assertEqual(r(), None) del r - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_properties(self): # Testing property... class C(object): @@ -2616,8 +2603,7 @@ def __getclass(self): dir(C()) # This used to segfault - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_supers(self): # Testing super... @@ -2730,8 +2716,7 @@ def test(klass): with self.assertRaises(TypeError): super(Base, kw=1) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_basic_inheritance(self): # Testing inheritance from basic types... @@ -3064,8 +3049,7 @@ class sublist(list): ## pass ## os_helper.unlink(os_helper.TESTFN) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_keywords(self): # Testing keyword args to basic type constructors ... with self.assertRaisesRegex(TypeError, 'keyword argument'): @@ -3268,8 +3252,7 @@ def __ge__(self, other): eval("x %s y" % op), "x=%d, y=%d" % (x, y)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_descrdoc(self): # Testing descriptor doc strings... from _io import FileIO @@ -3293,8 +3276,7 @@ class NewClass: self.assertEqual(NewClass.__doc__, 'object=None; type=NewClass') self.assertEqual(NewClass().__doc__, 'object=NewClass instance; type=NewClass') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_set_class(self): # Testing __class__ assignment... class C(object): pass @@ -3384,8 +3366,7 @@ def __del__(self): l = [A() for x in range(100)] del l - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_set_dict(self): # Testing __dict__ assignment... class C(object): pass @@ -3644,7 +3625,7 @@ def f(a): return a encoding='latin1', errors='replace') self.assertEqual(ba, b'abc\xbd?') - @unittest.skip("TODO: RUSTPYTHON, rustpython segmentation fault") + @unittest.skip('TODO: RUSTPYTHON; rustpython segmentation fault') def test_recursive_call(self): # Testing recursive __call__() by setting to instance of class... class A(object): @@ -3763,8 +3744,7 @@ def test_uninitialized_modules(self): m.foo = 1 self.assertEqual(m.__dict__, {"foo": 1}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_funny_new(self): # Testing __new__ returning something unexpected... class C(object): @@ -3926,7 +3906,7 @@ def __del__(self): # it as a leak. del C.__del__ - @unittest.skip("TODO: RUSTPYTHON, rustpython segmentation fault") + @unittest.skip('TODO: RUSTPYTHON; rustpython segmentation fault') def test_slots_trash(self): # Testing slot trash... # Deallocating deeply nested slotted trash caused stack overflows @@ -3939,8 +3919,7 @@ def __init__(self, x): o = trash(o) del o - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_slots_multiple_inheritance(self): # SF bug 575229, multiple inheritance w/ slots dumps core class A(object): @@ -4085,8 +4064,7 @@ class E(D): else: self.fail("shouldn't be able to create inheritance cycles") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_builtin_bases(self): # Make sure all the builtin types can have their base queried without # segfaulting. See issue #5787. @@ -4131,8 +4109,7 @@ class D(C): else: self.fail("best_base calculation found wanting") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unsubclassable_types(self): with self.assertRaises(TypeError): class X(type(None)): @@ -4165,8 +4142,7 @@ class X(object): with self.assertRaises(TypeError): X.__bases__ = type(None), O - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_mutable_bases_with_failing_mro(self): # Testing mutable bases with failing mro... class WorkOnce(type): @@ -4273,8 +4249,7 @@ class C: C.__name__ = Nasty("abc") C.__name__ = "normal" - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_subclass_right_op(self): # Testing correct dispatch of subclass overloading __r<op>__... @@ -4409,8 +4384,7 @@ class D(C): self.assertIsInstance(a, C) # Baseline self.assertIsInstance(pa, C) # Test - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_proxy_super(self): # Testing super() for a proxy object... class Proxy(object): @@ -4434,8 +4408,7 @@ def f(self): p = Proxy(obj) self.assertEqual(C.__dict__["f"](p), "B.f->C.f") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_carloverre(self): # Testing prohibition of Carlo Verre's hack... try: @@ -4468,8 +4441,7 @@ class C(B, A): except TypeError: self.fail("setattr through direct base types should be legal") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_carloverre_multi_inherit_invalid(self): class A(type): def __setattr__(cls, key, value): @@ -4507,8 +4479,8 @@ class Oops(object): o = Oops() o.whatever = Provoker(o) del o - - @unittest.skip("TODO: RUSTPYTHON, rustpython segmentation fault") + + @unittest.skip('TODO: RUSTPYTHON; rustpython segmentation fault') @support.requires_resource('cpu') def test_wrapper_segfault(self): # SF 927248: deeply nested wrappers could cause stack overflow @@ -4583,8 +4555,7 @@ def assertNotOrderable(self, a, b): with self.assertRaises(TypeError): a >= b - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_method_wrapper(self): # Testing method-wrapper objects... # <type 'method-wrapper'> did not support any reflection before 2.5 @@ -4644,18 +4615,16 @@ def test_special_unbound_method_types(self): def test_not_implemented(self): # Testing NotImplemented... # all binary methods should be able to return a NotImplemented - import operator def specialmethod(self, other): return NotImplemented def check(expr, x, y): - try: - exec(expr, {'x': x, 'y': y, 'operator': operator}) - except TypeError: - pass - else: - self.fail("no TypeError from %r" % (expr,)) + with ( + self.subTest(expr=expr, x=x, y=y), + self.assertRaises(TypeError), + ): + exec(expr, {'x': x, 'y': y}) N1 = sys.maxsize + 1 # might trigger OverflowErrors instead of # TypeErrors @@ -4676,12 +4645,23 @@ def check(expr, x, y): ('__and__', 'x & y', 'x &= y'), ('__or__', 'x | y', 'x |= y'), ('__xor__', 'x ^ y', 'x ^= y')]: - rname = '__r' + name[2:] + # Defines 'left' magic method: A = type('A', (), {name: specialmethod}) a = A() check(expr, a, a) check(expr, a, N1) check(expr, a, N2) + # Defines 'right' magic method: + rname = '__r' + name[2:] + B = type('B', (), {rname: specialmethod}) + b = B() + check(expr, b, b) + check(expr, a, b) + check(expr, b, a) + check(expr, b, N1) + check(expr, b, N2) + check(expr, N1, b) + check(expr, N2, b) if iexpr: check(iexpr, a, a) check(iexpr, a, N1) @@ -4788,6 +4768,21 @@ class X(object): with self.assertRaises(AttributeError): del X.__abstractmethods__ + @unittest.skip('TODO: RUSTPYTHON; crash. "dict has non-string keys: [PyObject PyInt { value: 1 }]"') + def test_gh55664(self): + # gh-55664: issue a warning when the + # __dict__ of a class contains non-string keys + with self.assertWarnsRegex(RuntimeWarning, 'MyClass'): + MyClass = type('MyClass', (), {1: 2}) + + class meta(type): + def __new__(mcls, name, bases, ns): + ns[1] = 2 + return super().__new__(mcls, name, bases, ns) + + with self.assertWarnsRegex(RuntimeWarning, 'MyClass'): + MyClass = meta('MyClass', (), {}) + def test_proxy_call(self): class FakeStr: __class__ = str @@ -4811,24 +4806,24 @@ class Thing: thing = Thing() for i in range(20): with self.assertRaises(TypeError): - # PRECALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS + # CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS list.sort(thing) for i in range(20): with self.assertRaises(TypeError): - # PRECALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS + # CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS str.split(thing) for i in range(20): with self.assertRaises(TypeError): - # PRECALL_NO_KW_METHOD_DESCRIPTOR_NOARGS + # CALL_METHOD_DESCRIPTOR_NOARGS str.upper(thing) for i in range(20): with self.assertRaises(TypeError): - # PRECALL_NO_KW_METHOD_DESCRIPTOR_FAST + # CALL_METHOD_DESCRIPTOR_FAST str.strip(thing) from collections import deque for i in range(20): with self.assertRaises(TypeError): - # PRECALL_NO_KW_METHOD_DESCRIPTOR_O + # CALL_METHOD_DESCRIPTOR_O deque.append(thing, thing) def test_repr_as_str(self): @@ -4862,8 +4857,7 @@ class A(int): with self.assertRaises(TypeError): a + a - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_slot_shadows_class_variable(self): with self.assertRaises(ValueError) as cm: class X: @@ -4872,8 +4866,7 @@ class X: m = str(cm.exception) self.assertEqual("'foo' in __slots__ conflicts with class variable", m) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_set_doc(self): class X: "elephant" @@ -4889,8 +4882,7 @@ class X: self.assertIn("cannot delete '__doc__' attribute of immutable type 'X'", str(cm.exception)) self.assertEqual(X.__doc__, "banana") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_qualname(self): descriptors = [str.lower, complex.real, float.real, int.__add__] types = ['method', 'member', 'getset', 'wrapper'] @@ -4923,8 +4915,7 @@ class Inside: self.assertEqual(Y.__qualname__, 'Y') self.assertEqual(Y.Inside.__qualname__, 'Y.Inside') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_qualname_dict(self): ns = {'__qualname__': 'some.name'} tp = type('Foo', (), ns) @@ -4935,8 +4926,7 @@ def test_qualname_dict(self): ns = {'__qualname__': 1} self.assertRaises(TypeError, type, 'Foo', (), ns) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cycle_through_dict(self): # See bug #1469629 class X(dict): @@ -4952,8 +4942,7 @@ def __init__(self): for o in gc.get_objects(): self.assertIsNot(type(o), X) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_object_new_and_init_with_parameters(self): # See issue #1683368 class OverrideNeither: @@ -4974,8 +4963,7 @@ class OverrideBoth(OverrideNew, OverrideInit): self.assertRaises(TypeError, case, 1, 2, 3) self.assertRaises(TypeError, case, 1, 2, foo=3) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_subclassing_does_not_duplicate_dict_descriptors(self): class Base: pass @@ -5055,6 +5043,7 @@ def __new__(cls): cls.lst = [2**i for i in range(10000)] X.descr + @support.suppress_immortalization() def test_remove_subclass(self): # bpo-46417: when the last subclass of a type is deleted, # remove_subclass() clears the internal dictionary of subclasses: @@ -5072,6 +5061,22 @@ class Child(Parent): gc.collect() self.assertEqual(Parent.__subclasses__(), []) + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_instance_method_get_behavior(self): + # test case for gh-113157 + + class A: + def meth(self): + return self + + class B: + pass + + a = A() + b = B() + b.meth = a.meth.__get__(b, B) + self.assertEqual(b.meth(), a) + def test_attr_raise_through_property(self): # test case for gh-103272 class A: @@ -5106,8 +5111,7 @@ def meth(self): pass self.C = C - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 'trace function introduces __local__') def test_iter_keys(self): @@ -5116,9 +5120,12 @@ def test_iter_keys(self): self.assertNotIsInstance(it, list) keys = list(it) keys.sort() - self.assertEqual(keys, ['__dict__', '__doc__', '__module__', - '__weakref__', 'meth']) + self.assertEqual(keys, ['__dict__', '__doc__', '__firstlineno__', + '__module__', + '__static_attributes__', '__weakref__', + 'meth']) + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 5 != 7 @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 'trace function introduces __local__') def test_iter_values(self): @@ -5126,10 +5133,9 @@ def test_iter_values(self): it = self.C.__dict__.values() self.assertNotIsInstance(it, list) values = list(it) - self.assertEqual(len(values), 5) + self.assertEqual(len(values), 7) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 'trace function introduces __local__') def test_iter_items(self): @@ -5138,8 +5144,10 @@ def test_iter_items(self): self.assertNotIsInstance(it, list) keys = [item[0] for item in it] keys.sort() - self.assertEqual(keys, ['__dict__', '__doc__', '__module__', - '__weakref__', 'meth']) + self.assertEqual(keys, ['__dict__', '__doc__', '__firstlineno__', + '__module__', + '__static_attributes__', '__weakref__', + 'meth']) def test_dict_type_with_metaclass(self): # Testing type of __dict__ when metaclass set... @@ -5189,7 +5197,7 @@ def __pow__(self, *args): class MiscTests(unittest.TestCase): - @unittest.skip("TODO: RUSTPYTHON, rustpython panicked at 'dict has non-string keys: [PyObject PyBaseObject]'") + @unittest.skip("TODO: RUSTPYTHON; rustpython panicked at 'dict has non-string keys: [PyObject PyBaseObject]'") def test_type_lookup_mro_reference(self): # Issue #14199: _PyType_Lookup() has to keep a strong reference to # the type MRO because it may be modified during the lookup, if @@ -5209,11 +5217,17 @@ class Base2(object): mykey = 'from Base2' mykey2 = 'from Base2' - X = type('X', (Base,), {MyKey(): 5}) + with self.assertWarnsRegex(RuntimeWarning, 'X'): + X = type('X', (Base,), {MyKey(): 5}) + + # Note that the access below uses getattr() rather than normally + # accessing the attribute. That is done to avoid the bytecode + # specializer activating on repeated runs of the test. + # mykey is read from Base - self.assertEqual(X.mykey, 'from Base') + self.assertEqual(getattr(X, 'mykey'), 'from Base') # mykey2 is read from Base2 because MyKey.__eq__ has set __bases__ - self.assertEqual(X.mykey2, 'from Base2') + self.assertEqual(getattr(X, 'mykey2'), 'from Base2') class PicklingTests(unittest.TestCase): @@ -5248,8 +5262,7 @@ def _check_reduce(self, proto, obj, args=(), kwargs={}, state=None, self.assertEqual(obj.__reduce_ex__(proto), reduce_value) self.assertEqual(obj.__reduce__(), reduce_value) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_reduce(self): protocols = range(pickle.HIGHEST_PROTOCOL + 1) args = (-101, "spam") @@ -5373,8 +5386,7 @@ class C16(list): for proto in protocols: self._check_reduce(proto, obj, listitems=list(obj)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_special_method_lookup(self): protocols = range(pickle.HIGHEST_PROTOCOL + 1) class Picky: @@ -5507,8 +5519,7 @@ class E(C): y = pickle_copier.copy(x) self._assert_is_copy(x, y) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_reduce_copying(self): # Tests pickling and copying new-style classes and objects. global C1 @@ -5633,7 +5644,7 @@ def __repr__(self): objcopy2 = deepcopy(objcopy) self._assert_is_copy(obj, objcopy2) - @unittest.skip("TODO: RUSTPYTHON") + @unittest.skip('TODO: RUSTPYTHON') def test_issue24097(self): # Slot name is freed inside __getattr__ and is later used. class S(str): # Not interned @@ -5774,8 +5785,7 @@ class B(A): class C(B): pass - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_reent_set_bases_tp_base_cycle(self): """ type_set_bases must check for an inheritance cycle not only through @@ -5812,8 +5822,7 @@ class B2(A): with self.assertRaises(TypeError): B1.__bases__ += () - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tp_subclasses_cycle_in_update_slots(self): """ type_set_bases must check for reentrancy upon finishing its job @@ -5850,8 +5859,7 @@ class C(A): self.assertEqual(B1.__bases__, (C,)) self.assertEqual(C.__subclasses__(), [B1]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_tp_subclasses_cycle_error_return_path(self): """ The same as test_tp_subclasses_cycle_in_update_slots, but tests @@ -5920,8 +5928,7 @@ def mro(cls): class A(metaclass=M): pass - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_disappearing_custom_mro(self): """ gh-92112: A custom mro() returning a result conflicting with diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index 3fd5833d8be..8967dab8bdd 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -1,6 +1,13 @@ """Unit tests for zero-argument super() & related machinery.""" +import textwrap +import threading import unittest +from unittest.mock import patch +from test.support import import_helper, threading_helper + + +ADAPTIVE_WARMUP_DELAY = 2 class A: @@ -84,41 +91,43 @@ def nested(): self.assertEqual(E().f(), 'AE') - # SyntaxError - # def test_various___class___pathologies(self): - # # See issue #12370 - # class X(A): - # def f(self): - # return super().f() - # __class__ = 413 - # x = X() - # self.assertEqual(x.f(), 'A') - # self.assertEqual(x.__class__, 413) - # class X: - # x = __class__ - # def f(): - # __class__ - # self.assertIs(X.x, type(self)) - # with self.assertRaises(NameError) as e: - # exec("""class X: - # __class__ - # def f(): - # __class__""", globals(), {}) - # self.assertIs(type(e.exception), NameError) # Not UnboundLocalError - # class X: - # global __class__ - # __class__ = 42 - # def f(): - # __class__ - # self.assertEqual(globals()["__class__"], 42) - # del globals()["__class__"] - # self.assertNotIn("__class__", X.__dict__) - # class X: - # nonlocal __class__ - # __class__ = 42 - # def f(): - # __class__ - # self.assertEqual(__class__, 42) + # TODO: RUSTPYTHON; SyntaxError: name '__class__' is assigned to before global declaration + ''' + def test_various___class___pathologies(self): + # See issue #12370 + class X(A): + def f(self): + return super().f() + __class__ = 413 + x = X() + self.assertEqual(x.f(), 'A') + self.assertEqual(x.__class__, 413) + class X: + x = __class__ + def f(): + __class__ + self.assertIs(X.x, type(self)) + with self.assertRaises(NameError) as e: + exec("""class X: + __class__ + def f(): + __class__""", globals(), {}) + self.assertIs(type(e.exception), NameError) # Not UnboundLocalError + class X: + global __class__ + __class__ = 42 + def f(): + __class__ + self.assertEqual(globals()["__class__"], 42) + del globals()["__class__"] + self.assertNotIn("__class__", X.__dict__) + class X: + nonlocal __class__ + __class__ = 42 + def f(): + __class__ + self.assertEqual(__class__, 42) + ''' def test___class___instancemethod(self): # See issue #14857 @@ -182,8 +191,7 @@ def f(): B = type("B", (), test_namespace) self.assertIs(B.f(), B) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test___class___mro(self): # See issue #23722 test_class = None @@ -201,8 +209,7 @@ def f(): self.assertIs(test_class, A) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test___classcell___expected_behaviour(self): # See issue #23722 class Meta(type): @@ -288,17 +295,28 @@ def f(self): def test_obscure_super_errors(self): def f(): super() - self.assertRaises(RuntimeError, f) + with self.assertRaisesRegex(RuntimeError, r"no arguments"): + f() + + class C: + def f(): + super() + with self.assertRaisesRegex(RuntimeError, r"no arguments"): + C.f() + def f(x): del x super() - self.assertRaises(RuntimeError, f, None) + with self.assertRaisesRegex(RuntimeError, r"arg\[0\] deleted"): + f(None) + class X: def f(x): nonlocal __class__ del __class__ super() - self.assertRaises(RuntimeError, X().f) + with self.assertRaisesRegex(RuntimeError, r"empty __class__ cell"): + X().f() def test_cell_as_self(self): class X: @@ -322,6 +340,214 @@ def test_super_init_leaks(self): for i in range(1000): super.__init__(sp, int, i) + def test_super_argcount(self): + with self.assertRaisesRegex(TypeError, "expected at most"): + super(int, int, int) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "argument 1 must be a type" does not match "Expected type 'type' but 'int' found." + def test_super_argtype(self): + with self.assertRaisesRegex(TypeError, "argument 1 must be a type"): + super(1, int) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'test.support.import_helper' has no attribute 'ready_to_import' + def test_shadowed_global(self): + source = textwrap.dedent( + """ + class super: + msg = "truly super" + + class C: + def method(self): + return super().msg + """, + ) + with import_helper.ready_to_import(name="shadowed_super", source=source): + import shadowed_super + self.assertEqual(shadowed_super.C().method(), "truly super") + import_helper.unload("shadowed_super") + + def test_shadowed_local(self): + class super: + msg = "quite super" + + class C: + def method(self): + return super().msg + + self.assertEqual(C().method(), "quite super") + + def test_shadowed_dynamic(self): + class MySuper: + msg = "super super" + + class C: + def method(self): + return super().msg + + with patch(f"{__name__}.super", MySuper) as m: + self.assertEqual(C().method(), "super super") + + def test_shadowed_dynamic_two_arg(self): + call_args = [] + class MySuper: + def __init__(self, *args): + call_args.append(args) + msg = "super super" + + class C: + def method(self): + return super(1, 2).msg + + with patch(f"{__name__}.super", MySuper) as m: + self.assertEqual(C().method(), "super super") + self.assertEqual(call_args, [(1, 2)]) + + def test_attribute_error(self): + class C: + def method(self): + return super().msg + + with self.assertRaisesRegex(AttributeError, "'super' object has no attribute 'msg'"): + C().method() + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "argument 1 must be a type" does not match "Expected type 'type' but 'int' found." + def test_bad_first_arg(self): + class C: + def method(self): + return super(1, self).method() + + with self.assertRaisesRegex(TypeError, "argument 1 must be a type"): + C().method() + + def test_supercheck_fail(self): + class C: + def method(self, type_, obj): + return super(type_, obj).method() + + c = C() + err_msg = ( + r"super\(type, obj\): obj \({} {}\) is not " + r"an instance or subtype of type \({}\)." + ) + + cases = ( + (int, c, int.__name__, C.__name__, "instance of"), + # obj is instance of type + (C, list(), C.__name__, list.__name__, "instance of"), + # obj is type itself + (C, list, C.__name__, list.__name__, "type"), + ) + + for case in cases: + with self.subTest(case=case): + type_, obj, type_str, obj_str, instance_or_type = case + regex = err_msg.format(instance_or_type, obj_str, type_str) + + with self.assertRaisesRegex(TypeError, regex): + c.method(type_, obj) + + def test_super___class__(self): + class C: + def method(self): + return super().__class__ + + self.assertEqual(C().method(), super) + + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: type 'super' is not an acceptable base type + def test_super_subclass___class__(self): + class mysuper(super): + pass + + class C: + def method(self): + return mysuper(C, self).__class__ + + self.assertEqual(C().method(), mysuper) + + def test_unusual_getattro(self): + class MyType(type): + pass + + def test(name): + mytype = MyType(name, (MyType,), {}) + super(MyType, type(mytype)).__setattr__(mytype, "bar", 1) + self.assertEqual(mytype.bar, 1) + + for _ in range(ADAPTIVE_WARMUP_DELAY): + test("foo1") + + def test_reassigned_new(self): + class A: + def __new__(cls): + pass + + def __init_subclass__(cls): + if "__new__" not in cls.__dict__: + cls.__new__ = cls.__new__ + + class B(A): + pass + + class C(B): + def __new__(cls): + return super().__new__(cls) + + for _ in range(ADAPTIVE_WARMUP_DELAY): + C() + + def test_mixed_staticmethod_hierarchy(self): + # This test is just a desugared version of `test_reassigned_new` + class A: + @staticmethod + def some(cls, *args, **kwargs): + self.assertFalse(args) + self.assertFalse(kwargs) + + class B(A): + def some(cls, *args, **kwargs): + return super().some(cls, *args, **kwargs) + + class C(B): + @staticmethod + def some(cls): + return super().some(cls) + + for _ in range(ADAPTIVE_WARMUP_DELAY): + C.some(C) + + @threading_helper.requires_working_threading() + def test___class___modification_multithreaded(self): + """ Note: this test isn't actually testing anything on its own. + It requires a sys audithook to be set to crash on older Python. + This should be the case anyways as our test suite sets + an audit hook. + """ + + class Foo: + pass + + class Bar: + pass + + thing = Foo() + def work(): + foo = thing + for _ in range(200): + foo.__class__ = Bar + type(foo) + foo.__class__ = Foo + type(foo) + + + threads = [] + for _ in range(6): + thread = threading.Thread(target=work) + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() + if __name__ == "__main__": unittest.main() diff --git a/crates/vm/src/builtins/super.rs b/crates/vm/src/builtins/super.rs index ce467ecefaf..419055b79ff 100644 --- a/crates/vm/src/builtins/super.rs +++ b/crates/vm/src/builtins/super.rs @@ -239,14 +239,16 @@ impl Representable for PySuper { } fn super_check(ty: PyTypeRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyTypeRef> { - if let Ok(cls) = obj.clone().downcast::<PyType>() - && cls.fast_issubclass(&ty) - { - return Ok(cls); - } + let typ = match obj.clone().downcast::<PyType>() { + Ok(cls) if cls.fast_issubclass(&ty) => return Ok(cls), + Ok(cls) => Some(cls), + Err(_) => None, + }; + if obj.fast_isinstance(&ty) { return Ok(obj.class().to_owned()); } + let class_attr = obj.get_attr("__class__", vm)?; if let Ok(cls) = class_attr.downcast::<PyType>() && !cls.is(&ty) @@ -254,7 +256,18 @@ fn super_check(ty: PyTypeRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { return Ok(cls); } - Err(vm.new_type_error("super(type, obj): obj must be an instance or subtype of type")) + + let (type_or_instance, obj_str) = match typ { + Some(t) => ("type", t.name().to_owned()), + None => ("instance of", obj.class().name().to_owned()), + }; + + Err(vm.new_type_error(format!( + "super(type, obj): obj ({} {}) is not an instance or subtype of type ({}).", + type_or_instance, + obj_str, + ty.name(), + ))) } pub fn init(context: &Context) { From 8e0a86d1634e999a5bc8533a3caf2ac9d763e5ca Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:58:07 +0900 Subject: [PATCH 389/819] `init` debug helper (#6315) --- crates/vm/src/types/slot.rs | 24 +++++++++++++++++++++++- crates/vm/src/vm/vm_new.rs | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 7f1d4e03561..0cdb3c7189b 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -835,7 +835,28 @@ pub trait Initializer: PyPayload { #[pyslot] #[inline] fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - let zelf = zelf.try_into_value(vm)?; + #[cfg(debug_assertions)] + let class_name_for_debug = zelf.class().name().to_string(); + + let zelf = match zelf.try_into_value(vm) { + Ok(zelf) => zelf, + Err(err) => { + #[cfg(debug_assertions)] + { + if let Ok(msg) = err.as_object().repr(vm) { + let double_appearance = + msg.as_str().matches(&class_name_for_debug as &str).count() == 2; + if double_appearance { + panic!( + "This type `{}` doesn't seem to support `init`. Override `slot_init` instead: {}", + class_name_for_debug, msg + ); + } + } + } + return Err(err); + } + }; let args: Self::Args = args.bind(vm)?; Self::init(zelf, args, vm) } @@ -843,6 +864,7 @@ pub trait Initializer: PyPayload { #[pymethod] #[inline] fn __init__(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + // TODO: check if this is safe. zelf may need to be `PyObjectRef` Self::init(zelf, args, vm) } diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index d0b78cfe5bd..63622b90a27 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -509,7 +509,7 @@ impl VirtualMachine { #[cfg(debug_assertions)] let msg = if class.get_id() == actual_class.get_id() { let mut msg = msg; - msg += " Did you forget to add `#[pyclass(with(Constructor))]`?"; + msg += " It might mean this type doesn't support subclassing very well. e.g. Did you forget to add `#[pyclass(with(Constructor))]`?"; msg } else { msg From 9130dd8068eb8afe8482a1f067098d0bd0131f08 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 2 Dec 2025 02:00:26 +0200 Subject: [PATCH 390/819] Unify BINARY_OP bytecodes (#6317) * Unify BINARY_OP bytecodes * Add missing op to `as_inplace` * Fix doc example * Fix jit * Fix doc * Use correct opname * Fix dis fmt * Inplace ops support in JIT --- crates/codegen/src/compile.rs | 38 ++++--- crates/compiler-core/src/bytecode.rs | 155 ++++++++++++++++++++++----- crates/jit/src/instructions.rs | 140 +++++++++++++++--------- crates/vm/src/frame.rs | 50 ++++----- 4 files changed, 255 insertions(+), 128 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 387c2e819ed..e8dc269dca8 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -3301,7 +3301,7 @@ impl Compiler { // Subtract to compute the correct index. emit!( self, - Instruction::BinaryOperation { + Instruction::BinaryOp { op: BinaryOperator::Subtract } ); @@ -4339,26 +4339,24 @@ impl Compiler { } fn compile_op(&mut self, op: &Operator, inplace: bool) { - let op = match op { - Operator::Add => bytecode::BinaryOperator::Add, - Operator::Sub => bytecode::BinaryOperator::Subtract, - Operator::Mult => bytecode::BinaryOperator::Multiply, - Operator::MatMult => bytecode::BinaryOperator::MatrixMultiply, - Operator::Div => bytecode::BinaryOperator::Divide, - Operator::FloorDiv => bytecode::BinaryOperator::FloorDivide, - Operator::Mod => bytecode::BinaryOperator::Modulo, - Operator::Pow => bytecode::BinaryOperator::Power, - Operator::LShift => bytecode::BinaryOperator::Lshift, - Operator::RShift => bytecode::BinaryOperator::Rshift, - Operator::BitOr => bytecode::BinaryOperator::Or, - Operator::BitXor => bytecode::BinaryOperator::Xor, - Operator::BitAnd => bytecode::BinaryOperator::And, + let bin_op = match op { + Operator::Add => BinaryOperator::Add, + Operator::Sub => BinaryOperator::Subtract, + Operator::Mult => BinaryOperator::Multiply, + Operator::MatMult => BinaryOperator::MatrixMultiply, + Operator::Div => BinaryOperator::TrueDivide, + Operator::FloorDiv => BinaryOperator::FloorDivide, + Operator::Mod => BinaryOperator::Remainder, + Operator::Pow => BinaryOperator::Power, + Operator::LShift => BinaryOperator::Lshift, + Operator::RShift => BinaryOperator::Rshift, + Operator::BitOr => BinaryOperator::Or, + Operator::BitXor => BinaryOperator::Xor, + Operator::BitAnd => BinaryOperator::And, }; - if inplace { - emit!(self, Instruction::BinaryOperationInplace { op }) - } else { - emit!(self, Instruction::BinaryOperation { op }) - } + + let op = if inplace { bin_op.as_inplace() } else { bin_op }; + emit!(self, Instruction::BinaryOp { op }) } /// Implement boolean short circuit evaluation logic. diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 8a095de6114..2a0ad7e280a 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -594,10 +594,7 @@ pub enum Instruction { UnaryOperation { op: Arg<UnaryOperator>, }, - BinaryOperation { - op: Arg<BinaryOperator>, - }, - BinaryOperationInplace { + BinaryOp { op: Arg<BinaryOperator>, }, BinarySubscript, @@ -1094,32 +1091,142 @@ op_arg_enum!( op_arg_enum!( /// The possible Binary operators + /// /// # Examples /// - /// ```ignore - /// use rustpython_compiler_core::Instruction::BinaryOperation; - /// use rustpython_compiler_core::BinaryOperator::Add; - /// let op = BinaryOperation {op: Add}; + /// ```rust + /// use rustpython_compiler_core::bytecode::{Arg, BinaryOperator, Instruction}; + /// let (op, _) = Arg::new(BinaryOperator::Add); + /// let instruction = Instruction::BinaryOp { op }; /// ``` - #[derive(Debug, Copy, Clone, PartialEq, Eq)] + /// + /// See also: + /// - [_PyEval_BinaryOps](https://github.com/python/cpython/blob/8183fa5e3f78ca6ab862de7fb8b14f3d929421e0/Python/ceval.c#L316-L343) #[repr(u8)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum BinaryOperator { - Power = 0, - Multiply = 1, - MatrixMultiply = 2, - Divide = 3, - FloorDivide = 4, - Modulo = 5, - Add = 6, - Subtract = 7, - Lshift = 8, + /// `+` + Add = 0, + /// `&` + And = 1, + /// `//` + FloorDivide = 2, + /// `<<` + Lshift = 3, + /// `@` + MatrixMultiply = 4, + /// `*` + Multiply = 5, + /// `%` + Remainder = 6, + /// `|` + Or = 7, + /// `**` + Power = 8, + /// `>>` Rshift = 9, - And = 10, - Xor = 11, - Or = 12, + /// `-` + Subtract = 10, + /// `/` + TrueDivide = 11, + /// `^` + Xor = 12, + /// `+=` + InplaceAdd = 13, + /// `&=` + InplaceAnd = 14, + /// `//=` + InplaceFloorDivide = 15, + /// `<<=` + InplaceLshift = 16, + /// `@=` + InplaceMatrixMultiply = 17, + /// `*=` + InplaceMultiply = 18, + /// `%=` + InplaceRemainder = 19, + /// `|=` + InplaceOr = 20, + /// `**=` + InplacePower = 21, + /// `>>=` + InplaceRshift = 22, + /// `-=` + InplaceSubtract = 23, + /// `/=` + InplaceTrueDivide = 24, + /// `^=` + InplaceXor = 25, } ); +impl BinaryOperator { + /// Get the "inplace" version of the operator. + /// This has no effect if `self` is already an "inplace" operator. + /// + /// # Example + /// ```rust + /// use rustpython_compiler_core::bytecode::BinaryOperator; + /// + /// assert_eq!(BinaryOperator::Power.as_inplace(), BinaryOperator::InplacePower); + /// + /// assert_eq!(BinaryOperator::InplaceSubtract.as_inplace(), BinaryOperator::InplaceSubtract); + /// ``` + #[must_use] + pub const fn as_inplace(self) -> Self { + match self { + Self::Add => Self::InplaceAdd, + Self::And => Self::InplaceAnd, + Self::FloorDivide => Self::InplaceFloorDivide, + Self::Lshift => Self::InplaceLshift, + Self::MatrixMultiply => Self::InplaceMatrixMultiply, + Self::Multiply => Self::InplaceMultiply, + Self::Remainder => Self::InplaceRemainder, + Self::Or => Self::InplaceOr, + Self::Power => Self::InplacePower, + Self::Rshift => Self::InplaceRshift, + Self::Subtract => Self::InplaceSubtract, + Self::TrueDivide => Self::InplaceTrueDivide, + Self::Xor => Self::InplaceXor, + _ => self, + } + } +} + +impl fmt::Display for BinaryOperator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let op = match self { + Self::Add => "+", + Self::And => "&", + Self::FloorDivide => "//", + Self::Lshift => "<<", + Self::MatrixMultiply => "@", + Self::Multiply => "*", + Self::Remainder => "%", + Self::Or => "|", + Self::Power => "**", + Self::Rshift => ">>", + Self::Subtract => "-", + Self::TrueDivide => "/", + Self::Xor => "^", + Self::InplaceAdd => "+=", + Self::InplaceAnd => "&=", + Self::InplaceFloorDivide => "//=", + Self::InplaceLshift => "<<=", + Self::InplaceMatrixMultiply => "@=", + Self::InplaceMultiply => "*=", + Self::InplaceRemainder => "%=", + Self::InplaceOr => "|=", + Self::InplacePower => "**=", + Self::InplaceRshift => ">>=", + Self::InplaceSubtract => "-=", + Self::InplaceTrueDivide => "/=", + Self::InplaceXor => "^=", + }; + write!(f, "{op}") + } +} + op_arg_enum!( /// The possible unary operators #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -1514,7 +1621,7 @@ impl Instruction { DeleteAttr { .. } => -1, LoadConst { .. } => 1, UnaryOperation { .. } => 0, - BinaryOperation { .. } | BinaryOperationInplace { .. } | CompareOperation { .. } => -1, + BinaryOp { .. } | CompareOperation { .. } => -1, BinarySubscript => -1, CopyItem { .. } => 1, Pop => -1, @@ -1719,8 +1826,8 @@ impl Instruction { DeleteAttr { idx } => w!(DeleteAttr, name = idx), LoadConst { idx } => fmt_const("LoadConst", arg, f, idx), UnaryOperation { op } => w!(UnaryOperation, ?op), - BinaryOperation { op } => w!(BinaryOperation, ?op), - BinaryOperationInplace { op } => w!(BinaryOperationInplace, ?op), + BinaryOp { op } => write!(f, "{:pad$}({})", "BINARY_OP", op.get(arg)), + BinarySubscript => w!(BinarySubscript), LoadAttr { idx } => w!(LoadAttr, name = idx), CompareOperation { op } => w!(CompareOperation, ?op), diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index 5acf4a53edd..8f25c757d19 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -436,7 +436,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { _ => Err(JitCompileError::NotSupported), } } - Instruction::BinaryOperation { op } | Instruction::BinaryOperationInplace { op } => { + Instruction::BinaryOp { op } => { let op = op.get(arg); // the rhs is popped off first let b = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; @@ -446,18 +446,30 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { let b_type = b.to_jit_type(); let val = match (op, a, b) { - (BinaryOperator::Add, JitValue::Int(a), JitValue::Int(b)) => { + ( + BinaryOperator::Add | BinaryOperator::InplaceAdd, + JitValue::Int(a), + JitValue::Int(b), + ) => { let (out, carry) = self.builder.ins().sadd_overflow(a, b); self.builder.ins().trapnz(carry, TrapCode::INTEGER_OVERFLOW); JitValue::Int(out) } - (BinaryOperator::Subtract, JitValue::Int(a), JitValue::Int(b)) => { - JitValue::Int(self.compile_sub(a, b)) - } - (BinaryOperator::FloorDivide, JitValue::Int(a), JitValue::Int(b)) => { - JitValue::Int(self.builder.ins().sdiv(a, b)) - } - (BinaryOperator::Divide, JitValue::Int(a), JitValue::Int(b)) => { + ( + BinaryOperator::Subtract | BinaryOperator::InplaceSubtract, + JitValue::Int(a), + JitValue::Int(b), + ) => JitValue::Int(self.compile_sub(a, b)), + ( + BinaryOperator::FloorDivide | BinaryOperator::InplaceFloorDivide, + JitValue::Int(a), + JitValue::Int(b), + ) => JitValue::Int(self.builder.ins().sdiv(a, b)), + ( + BinaryOperator::TrueDivide | BinaryOperator::InplaceTrueDivide, + JitValue::Int(a), + JitValue::Int(b), + ) => { // Check if b == 0, If so trap with a division by zero error self.builder .ins() @@ -467,15 +479,21 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { let b_float = self.builder.ins().fcvt_from_sint(types::F64, b); JitValue::Float(self.builder.ins().fdiv(a_float, b_float)) } - (BinaryOperator::Multiply, JitValue::Int(a), JitValue::Int(b)) => { - JitValue::Int(self.builder.ins().imul(a, b)) - } - (BinaryOperator::Modulo, JitValue::Int(a), JitValue::Int(b)) => { - JitValue::Int(self.builder.ins().srem(a, b)) - } - (BinaryOperator::Power, JitValue::Int(a), JitValue::Int(b)) => { - JitValue::Int(self.compile_ipow(a, b)) - } + ( + BinaryOperator::Multiply | BinaryOperator::InplaceMultiply, + JitValue::Int(a), + JitValue::Int(b), + ) => JitValue::Int(self.builder.ins().imul(a, b)), + ( + BinaryOperator::Remainder | BinaryOperator::InplaceRemainder, + JitValue::Int(a), + JitValue::Int(b), + ) => JitValue::Int(self.builder.ins().srem(a, b)), + ( + BinaryOperator::Power | BinaryOperator::InplacePower, + JitValue::Int(a), + JitValue::Int(b), + ) => JitValue::Int(self.compile_ipow(a, b)), ( BinaryOperator::Lshift | BinaryOperator::Rshift, JitValue::Int(a), @@ -489,39 +507,57 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { TrapCode::user(CustomTrapCode::NegativeShiftCount as u8).unwrap(), ); - let out = if op == BinaryOperator::Lshift { - self.builder.ins().ishl(a, b) - } else { - self.builder.ins().sshr(a, b) - }; + let out = + if matches!(op, BinaryOperator::Lshift | BinaryOperator::InplaceLshift) + { + self.builder.ins().ishl(a, b) + } else { + self.builder.ins().sshr(a, b) + }; JitValue::Int(out) } - (BinaryOperator::And, JitValue::Int(a), JitValue::Int(b)) => { - JitValue::Int(self.builder.ins().band(a, b)) - } - (BinaryOperator::Or, JitValue::Int(a), JitValue::Int(b)) => { - JitValue::Int(self.builder.ins().bor(a, b)) - } - (BinaryOperator::Xor, JitValue::Int(a), JitValue::Int(b)) => { - JitValue::Int(self.builder.ins().bxor(a, b)) - } + ( + BinaryOperator::And | BinaryOperator::InplaceAnd, + JitValue::Int(a), + JitValue::Int(b), + ) => JitValue::Int(self.builder.ins().band(a, b)), + ( + BinaryOperator::Or | BinaryOperator::InplaceOr, + JitValue::Int(a), + JitValue::Int(b), + ) => JitValue::Int(self.builder.ins().bor(a, b)), + ( + BinaryOperator::Xor | BinaryOperator::InplaceXor, + JitValue::Int(a), + JitValue::Int(b), + ) => JitValue::Int(self.builder.ins().bxor(a, b)), // Floats - (BinaryOperator::Add, JitValue::Float(a), JitValue::Float(b)) => { - JitValue::Float(self.builder.ins().fadd(a, b)) - } - (BinaryOperator::Subtract, JitValue::Float(a), JitValue::Float(b)) => { - JitValue::Float(self.builder.ins().fsub(a, b)) - } - (BinaryOperator::Multiply, JitValue::Float(a), JitValue::Float(b)) => { - JitValue::Float(self.builder.ins().fmul(a, b)) - } - (BinaryOperator::Divide, JitValue::Float(a), JitValue::Float(b)) => { - JitValue::Float(self.builder.ins().fdiv(a, b)) - } - (BinaryOperator::Power, JitValue::Float(a), JitValue::Float(b)) => { - JitValue::Float(self.compile_fpow(a, b)) - } + ( + BinaryOperator::Add | BinaryOperator::InplaceAdd, + JitValue::Float(a), + JitValue::Float(b), + ) => JitValue::Float(self.builder.ins().fadd(a, b)), + ( + BinaryOperator::Subtract | BinaryOperator::InplaceSubtract, + JitValue::Float(a), + JitValue::Float(b), + ) => JitValue::Float(self.builder.ins().fsub(a, b)), + ( + BinaryOperator::Multiply | BinaryOperator::InplaceMultiply, + JitValue::Float(a), + JitValue::Float(b), + ) => JitValue::Float(self.builder.ins().fmul(a, b)), + ( + BinaryOperator::TrueDivide | BinaryOperator::InplaceTrueDivide, + JitValue::Float(a), + JitValue::Float(b), + ) => JitValue::Float(self.builder.ins().fdiv(a, b)), + ( + BinaryOperator::Power | BinaryOperator::InplacePower, + JitValue::Float(a), + JitValue::Float(b), + ) => JitValue::Float(self.compile_fpow(a, b)), // Floats and Integers (_, JitValue::Int(a), JitValue::Float(b)) @@ -537,19 +573,19 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { }; match op { - BinaryOperator::Add => { + BinaryOperator::Add | BinaryOperator::InplaceAdd => { JitValue::Float(self.builder.ins().fadd(operand_one, operand_two)) } - BinaryOperator::Subtract => { + BinaryOperator::Subtract | BinaryOperator::InplaceSubtract => { JitValue::Float(self.builder.ins().fsub(operand_one, operand_two)) } - BinaryOperator::Multiply => { + BinaryOperator::Multiply | BinaryOperator::InplaceMultiply => { JitValue::Float(self.builder.ins().fmul(operand_one, operand_two)) } - BinaryOperator::Divide => { + BinaryOperator::TrueDivide | BinaryOperator::InplaceTrueDivide => { JitValue::Float(self.builder.ins().fdiv(operand_one, operand_two)) } - BinaryOperator::Power => { + BinaryOperator::Power | BinaryOperator::InplacePower => { JitValue::Float(self.compile_fpow(operand_one, operand_two)) } _ => return Err(JitCompileError::NotSupported), diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 088b5dcb4b5..bc684bc9f68 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -868,10 +868,7 @@ impl ExecutingFrame<'_> { dict.set_item(&*key, value, vm)?; Ok(None) } - bytecode::Instruction::BinaryOperation { op } => self.execute_bin_op(vm, op.get(arg)), - bytecode::Instruction::BinaryOperationInplace { op } => { - self.execute_bin_op_inplace(vm, op.get(arg)) - } + bytecode::Instruction::BinaryOp { op } => self.execute_bin_op(vm, op.get(arg)), bytecode::Instruction::BinarySubscript => { let key = self.pop_value(); let container = self.pop_value(); @@ -2126,40 +2123,29 @@ impl ExecutingFrame<'_> { bytecode::BinaryOperator::Multiply => vm._mul(a_ref, b_ref), bytecode::BinaryOperator::MatrixMultiply => vm._matmul(a_ref, b_ref), bytecode::BinaryOperator::Power => vm._pow(a_ref, b_ref, vm.ctx.none.as_object()), - bytecode::BinaryOperator::Divide => vm._truediv(a_ref, b_ref), + bytecode::BinaryOperator::TrueDivide => vm._truediv(a_ref, b_ref), bytecode::BinaryOperator::FloorDivide => vm._floordiv(a_ref, b_ref), - bytecode::BinaryOperator::Modulo => vm._mod(a_ref, b_ref), + bytecode::BinaryOperator::Remainder => vm._mod(a_ref, b_ref), bytecode::BinaryOperator::Lshift => vm._lshift(a_ref, b_ref), bytecode::BinaryOperator::Rshift => vm._rshift(a_ref, b_ref), bytecode::BinaryOperator::Xor => vm._xor(a_ref, b_ref), bytecode::BinaryOperator::Or => vm._or(a_ref, b_ref), bytecode::BinaryOperator::And => vm._and(a_ref, b_ref), - }?; - - self.push_value(value); - Ok(None) - } - fn execute_bin_op_inplace( - &mut self, - vm: &VirtualMachine, - op: bytecode::BinaryOperator, - ) -> FrameResult { - let b_ref = &self.pop_value(); - let a_ref = &self.pop_value(); - let value = match op { - bytecode::BinaryOperator::Subtract => vm._isub(a_ref, b_ref), - bytecode::BinaryOperator::Add => vm._iadd(a_ref, b_ref), - bytecode::BinaryOperator::Multiply => vm._imul(a_ref, b_ref), - bytecode::BinaryOperator::MatrixMultiply => vm._imatmul(a_ref, b_ref), - bytecode::BinaryOperator::Power => vm._ipow(a_ref, b_ref, vm.ctx.none.as_object()), - bytecode::BinaryOperator::Divide => vm._itruediv(a_ref, b_ref), - bytecode::BinaryOperator::FloorDivide => vm._ifloordiv(a_ref, b_ref), - bytecode::BinaryOperator::Modulo => vm._imod(a_ref, b_ref), - bytecode::BinaryOperator::Lshift => vm._ilshift(a_ref, b_ref), - bytecode::BinaryOperator::Rshift => vm._irshift(a_ref, b_ref), - bytecode::BinaryOperator::Xor => vm._ixor(a_ref, b_ref), - bytecode::BinaryOperator::Or => vm._ior(a_ref, b_ref), - bytecode::BinaryOperator::And => vm._iand(a_ref, b_ref), + bytecode::BinaryOperator::InplaceSubtract => vm._isub(a_ref, b_ref), + bytecode::BinaryOperator::InplaceAdd => vm._iadd(a_ref, b_ref), + bytecode::BinaryOperator::InplaceMultiply => vm._imul(a_ref, b_ref), + bytecode::BinaryOperator::InplaceMatrixMultiply => vm._imatmul(a_ref, b_ref), + bytecode::BinaryOperator::InplacePower => { + vm._ipow(a_ref, b_ref, vm.ctx.none.as_object()) + } + bytecode::BinaryOperator::InplaceTrueDivide => vm._itruediv(a_ref, b_ref), + bytecode::BinaryOperator::InplaceFloorDivide => vm._ifloordiv(a_ref, b_ref), + bytecode::BinaryOperator::InplaceRemainder => vm._imod(a_ref, b_ref), + bytecode::BinaryOperator::InplaceLshift => vm._ilshift(a_ref, b_ref), + bytecode::BinaryOperator::InplaceRshift => vm._irshift(a_ref, b_ref), + bytecode::BinaryOperator::InplaceXor => vm._ixor(a_ref, b_ref), + bytecode::BinaryOperator::InplaceOr => vm._ior(a_ref, b_ref), + bytecode::BinaryOperator::InplaceAnd => vm._iand(a_ref, b_ref), }?; self.push_value(value); From bf8152b4b8eb3da66e4e00293e148d8e7c8211ad Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:11:04 +0900 Subject: [PATCH 391/819] Remove unused _membership_iter_search (#6318) --- crates/vm/src/builtins/range.rs | 2 -- crates/vm/src/vm/vm_ops.rs | 24 ++---------------------- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/crates/vm/src/builtins/range.rs b/crates/vm/src/builtins/range.rs index ef55feed603..3edd130ee28 100644 --- a/crates/vm/src/builtins/range.rs +++ b/crates/vm/src/builtins/range.rs @@ -28,8 +28,6 @@ enum SearchType { Index, } -// Note: might be a good idea to merge with _membership_iter_search or generalize (_sequence_iter_check?) -// and place in vm.rs for all sequences to be able to use it. #[inline] fn iter_search( obj: &PyObject, diff --git a/crates/vm/src/vm/vm_ops.rs b/crates/vm/src/vm/vm_ops.rs index a5656250c3b..e30e19981a9 100644 --- a/crates/vm/src/vm/vm_ops.rs +++ b/crates/vm/src/vm/vm_ops.rs @@ -2,9 +2,9 @@ use super::VirtualMachine; use crate::stdlib::warnings; use crate::{ PyRef, - builtins::{PyInt, PyIntRef, PyStr, PyStrRef, PyUtf8Str}, + builtins::{PyInt, PyStr, PyStrRef, PyUtf8Str}, object::{AsObject, PyObject, PyObjectRef, PyResult}, - protocol::{PyIterReturn, PyNumberBinaryOp, PyNumberTernaryOp, PySequence}, + protocol::{PyNumberBinaryOp, PyNumberTernaryOp, PySequence}, types::PyComparisonOp, }; use num_traits::ToPrimitive; @@ -529,26 +529,6 @@ impl VirtualMachine { self.format(obj, format_spec)?.try_into_utf8(self) } - // https://docs.python.org/3/reference/expressions.html#membership-test-operations - fn _membership_iter_search( - &self, - haystack: &PyObject, - needle: PyObjectRef, - ) -> PyResult<PyIntRef> { - let iter = haystack.get_iter(self)?; - loop { - if let PyIterReturn::Return(element) = iter.next(self)? { - if self.bool_eq(&element, &needle)? { - return Ok(self.ctx.new_bool(true)); - } else { - continue; - } - } else { - return Ok(self.ctx.new_bool(false)); - } - } - } - pub fn _contains(&self, haystack: &PyObject, needle: &PyObject) -> PyResult<bool> { let seq = haystack.to_sequence(); seq.contains(needle, self) From 563dc0fc9e71b6a45c10069515c7de88628132e9 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 2 Dec 2025 20:20:31 +0900 Subject: [PATCH 392/819] Separate Debug from PyPayload (#6320) --- crates/vm/src/builtins/dict.rs | 2 +- crates/vm/src/object/core.rs | 92 +++++++++++++++------------------ crates/vm/src/object/ext.rs | 60 ++++++++++----------- crates/vm/src/object/payload.rs | 31 +++++++---- crates/vm/src/types/slot.rs | 2 +- crates/vm/src/vm/context.rs | 2 +- 6 files changed, 96 insertions(+), 93 deletions(-) diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index f9911ce033b..77126d4ee62 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -751,7 +751,7 @@ impl ExactSizeIterator for DictIter<'_> { #[pyclass] trait DictView: PyPayload + PyClassDef + Iterable + Representable { - type ReverseIter: PyPayload; + type ReverseIter: PyPayload + std::fmt::Debug; fn dict(&self) -> &PyDictRef; fn item(vm: &VirtualMachine, key: PyObjectRef, value: PyObjectRef) -> PyObjectRef; diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index fb2c5b6303e..6530abdbaba 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -13,7 +13,7 @@ use super::{ PyAtomicRef, ext::{AsObject, PyRefExact, PyResult}, - payload::PyObjectPayload, + payload::PyPayload, }; use crate::object::traverse::{MaybeTraverse, Traverse, TraverseFn}; use crate::object::traverse_object::PyObjVTable; @@ -76,10 +76,10 @@ use std::{ #[derive(Debug)] pub(super) struct Erased; -pub(super) unsafe fn drop_dealloc_obj<T: PyObjectPayload>(x: *mut PyObject) { +pub(super) unsafe fn drop_dealloc_obj<T: PyPayload>(x: *mut PyObject) { drop(unsafe { Box::from_raw(x as *mut PyInner<T>) }); } -pub(super) unsafe fn debug_obj<T: PyObjectPayload>( +pub(super) unsafe fn debug_obj<T: PyPayload + std::fmt::Debug>( x: &PyObject, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { @@ -88,10 +88,7 @@ pub(super) unsafe fn debug_obj<T: PyObjectPayload>( } /// Call `try_trace` on payload -pub(super) unsafe fn try_trace_obj<T: PyObjectPayload>( - x: &PyObject, - tracer_fn: &mut TraverseFn<'_>, -) { +pub(super) unsafe fn try_trace_obj<T: PyPayload>(x: &PyObject, tracer_fn: &mut TraverseFn<'_>) { let x = unsafe { &*(x as *const PyObject as *const PyInner<T>) }; let payload = &x.payload; payload.try_traverse(tracer_fn) @@ -441,7 +438,7 @@ impl InstanceDict { } } -impl<T: PyObjectPayload> PyInner<T> { +impl<T: PyPayload + std::fmt::Debug> PyInner<T> { fn new(payload: T, typ: PyTypeRef, dict: Option<PyDictRef>) -> Box<Self> { let member_count = typ.slots.member_count; Box::new(Self { @@ -531,7 +528,7 @@ impl PyObjectRef { /// If the downcast fails, the original ref is returned in as `Err` so /// another downcast can be attempted without unnecessary cloning. #[inline(always)] - pub fn downcast<T: PyObjectPayload>(self) -> Result<PyRef<T>, Self> { + pub fn downcast<T: PyPayload>(self) -> Result<PyRef<T>, Self> { if self.downcastable::<T>() { Ok(unsafe { self.downcast_unchecked() }) } else { @@ -539,7 +536,7 @@ impl PyObjectRef { } } - pub fn try_downcast<T: PyObjectPayload>(self, vm: &VirtualMachine) -> PyResult<PyRef<T>> { + pub fn try_downcast<T: PyPayload>(self, vm: &VirtualMachine) -> PyResult<PyRef<T>> { T::try_downcast_from(&self, vm)?; Ok(unsafe { self.downcast_unchecked() }) } @@ -565,10 +562,7 @@ impl PyObjectRef { /// If the downcast fails, the original ref is returned in as `Err` so /// another downcast can be attempted without unnecessary cloning. #[inline] - pub fn downcast_exact<T: PyObjectPayload + crate::PyPayload>( - self, - vm: &VirtualMachine, - ) -> Result<PyRefExact<T>, Self> { + pub fn downcast_exact<T: PyPayload>(self, vm: &VirtualMachine) -> Result<PyRefExact<T>, Self> { if self.class().is(T::class(&vm.ctx)) { // TODO: is this always true? assert!( @@ -638,7 +632,7 @@ impl PyObject { #[deprecated(note = "use downcastable instead")] #[inline(always)] - pub fn payload_is<T: PyObjectPayload>(&self) -> bool { + pub fn payload_is<T: PyPayload>(&self) -> bool { self.0.typeid == T::payload_type_id() } @@ -648,7 +642,7 @@ impl PyObject { /// The actual payload type must be T. #[deprecated(note = "use downcast_unchecked_ref instead")] #[inline(always)] - pub const unsafe fn payload_unchecked<T: PyObjectPayload>(&self) -> &T { + pub const unsafe fn payload_unchecked<T: PyPayload>(&self) -> &T { // we cast to a PyInner<T> first because we don't know T's exact offset because of // varying alignment, but once we get a PyInner<T> the compiler can get it for us let inner = unsafe { &*(&self.0 as *const PyInner<Erased> as *const PyInner<T>) }; @@ -657,7 +651,7 @@ impl PyObject { #[deprecated(note = "use downcast_ref instead")] #[inline(always)] - pub fn payload<T: PyObjectPayload>(&self) -> Option<&T> { + pub fn payload<T: PyPayload>(&self) -> Option<&T> { #[allow(deprecated)] if self.payload_is::<T>() { #[allow(deprecated)] @@ -678,10 +672,7 @@ impl PyObject { #[deprecated(note = "use downcast_ref_if_exact instead")] #[inline(always)] - pub fn payload_if_exact<T: PyObjectPayload + crate::PyPayload>( - &self, - vm: &VirtualMachine, - ) -> Option<&T> { + pub fn payload_if_exact<T: PyPayload>(&self, vm: &VirtualMachine) -> Option<&T> { if self.class().is(T::class(&vm.ctx)) { #[allow(deprecated)] self.payload() @@ -730,12 +721,12 @@ impl PyObject { /// Check if this object can be downcast to T. #[inline(always)] - pub fn downcastable<T: PyObjectPayload>(&self) -> bool { + pub fn downcastable<T: PyPayload>(&self) -> bool { T::downcastable_from(self) } /// Attempt to downcast this reference to a subclass. - pub fn try_downcast_ref<'a, T: PyObjectPayload>( + pub fn try_downcast_ref<'a, T: PyPayload>( &'a self, vm: &VirtualMachine, ) -> PyResult<&'a Py<T>> { @@ -745,7 +736,7 @@ impl PyObject { /// Attempt to downcast this reference to a subclass. #[inline(always)] - pub fn downcast_ref<T: PyObjectPayload>(&self) -> Option<&Py<T>> { + pub fn downcast_ref<T: PyPayload>(&self) -> Option<&Py<T>> { if self.downcastable::<T>() { // SAFETY: just checked that the payload is T, and PyRef is repr(transparent) over // PyObjectRef @@ -756,10 +747,7 @@ impl PyObject { } #[inline(always)] - pub fn downcast_ref_if_exact<T: PyObjectPayload + crate::PyPayload>( - &self, - vm: &VirtualMachine, - ) -> Option<&Py<T>> { + pub fn downcast_ref_if_exact<T: PyPayload>(&self, vm: &VirtualMachine) -> Option<&Py<T>> { self.class() .is(T::class(&vm.ctx)) .then(|| unsafe { self.downcast_unchecked_ref::<T>() }) @@ -768,7 +756,7 @@ impl PyObject { /// # Safety /// T must be the exact payload type #[inline(always)] - pub unsafe fn downcast_unchecked_ref<T: PyObjectPayload>(&self) -> &Py<T> { + pub unsafe fn downcast_unchecked_ref<T: PyPayload>(&self) -> &Py<T> { debug_assert!(self.downcastable::<T>()); // SAFETY: requirements forwarded from caller unsafe { &*(self as *const Self as *const Py<T>) } @@ -875,7 +863,7 @@ impl AsRef<PyObject> for PyObjectRef { } } -impl<'a, T: PyObjectPayload> From<&'a Py<T>> for &'a PyObject { +impl<'a, T: PyPayload> From<&'a Py<T>> for &'a PyObject { #[inline(always)] fn from(py_ref: &'a Py<T>) -> Self { py_ref.as_object() @@ -908,7 +896,7 @@ impl fmt::Debug for PyObjectRef { #[repr(transparent)] pub struct Py<T>(PyInner<T>); -impl<T: PyObjectPayload> Py<T> { +impl<T: PyPayload> Py<T> { pub fn downgrade( &self, callback: Option<PyObjectRef>, @@ -947,7 +935,7 @@ impl<T> Deref for Py<T> { } } -impl<T: PyObjectPayload> Borrow<PyObject> for Py<T> { +impl<T: PyPayload> Borrow<PyObject> for Py<T> { #[inline(always)] fn borrow(&self) -> &PyObject { unsafe { &*(&self.0 as *const PyInner<T> as *const PyObject) } @@ -956,7 +944,7 @@ impl<T: PyObjectPayload> Borrow<PyObject> for Py<T> { impl<T> std::hash::Hash for Py<T> where - T: std::hash::Hash + PyObjectPayload, + T: std::hash::Hash + PyPayload, { #[inline] fn hash<H: std::hash::Hasher>(&self, state: &mut H) { @@ -966,7 +954,7 @@ where impl<T> PartialEq for Py<T> where - T: PartialEq + PyObjectPayload, + T: PartialEq + PyPayload, { #[inline] fn eq(&self, other: &Self) -> bool { @@ -974,11 +962,11 @@ where } } -impl<T> Eq for Py<T> where T: Eq + PyObjectPayload {} +impl<T> Eq for Py<T> where T: Eq + PyPayload {} impl<T> AsRef<PyObject> for Py<T> where - T: PyObjectPayload, + T: PyPayload, { #[inline(always)] fn as_ref(&self) -> &PyObject { @@ -986,7 +974,7 @@ where } } -impl<T: PyObjectPayload> fmt::Debug for Py<T> { +impl<T: PyPayload + std::fmt::Debug> fmt::Debug for Py<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { (**self).fmt(f) } @@ -1035,7 +1023,7 @@ impl<T> Clone for PyRef<T> { } } -impl<T: PyObjectPayload> PyRef<T> { +impl<T: PyPayload> PyRef<T> { // #[inline(always)] // pub(crate) const fn into_non_null(self) -> NonNull<Py<T>> { // let ptr = self.ptr; @@ -1065,6 +1053,14 @@ impl<T: PyObjectPayload> PyRef<T> { } } + pub const fn leak(pyref: Self) -> &'static Py<T> { + let ptr = pyref.ptr; + std::mem::forget(pyref); + unsafe { ptr.as_ref() } + } +} + +impl<T: PyPayload + std::fmt::Debug> PyRef<T> { #[inline(always)] pub fn new_ref(payload: T, typ: crate::builtins::PyTypeRef, dict: Option<PyDictRef>) -> Self { let inner = Box::into_raw(PyInner::new(payload, typ, dict)); @@ -1072,17 +1068,11 @@ impl<T: PyObjectPayload> PyRef<T> { ptr: unsafe { NonNull::new_unchecked(inner.cast::<Py<T>>()) }, } } - - pub const fn leak(pyref: Self) -> &'static Py<T> { - let ptr = pyref.ptr; - std::mem::forget(pyref); - unsafe { ptr.as_ref() } - } } impl<T> Borrow<PyObject> for PyRef<T> where - T: PyObjectPayload, + T: PyPayload, { #[inline(always)] fn borrow(&self) -> &PyObject { @@ -1092,7 +1082,7 @@ where impl<T> AsRef<PyObject> for PyRef<T> where - T: PyObjectPayload, + T: PyPayload, { #[inline(always)] fn as_ref(&self) -> &PyObject { @@ -1133,7 +1123,7 @@ impl<T> Deref for PyRef<T> { impl<T> std::hash::Hash for PyRef<T> where - T: std::hash::Hash + PyObjectPayload, + T: std::hash::Hash + PyPayload, { #[inline] fn hash<H: std::hash::Hasher>(&self, state: &mut H) { @@ -1143,7 +1133,7 @@ where impl<T> PartialEq for PyRef<T> where - T: PartialEq + PyObjectPayload, + T: PartialEq + PyPayload, { #[inline] fn eq(&self, other: &Self) -> bool { @@ -1151,15 +1141,15 @@ where } } -impl<T> Eq for PyRef<T> where T: Eq + PyObjectPayload {} +impl<T> Eq for PyRef<T> where T: Eq + PyPayload {} #[repr(transparent)] -pub struct PyWeakRef<T: PyObjectPayload> { +pub struct PyWeakRef<T: PyPayload> { weak: PyRef<PyWeak>, _marker: PhantomData<T>, } -impl<T: PyObjectPayload> PyWeakRef<T> { +impl<T: PyPayload> PyWeakRef<T> { pub fn upgrade(&self) -> Option<PyRef<T>> { self.weak .upgrade() diff --git a/crates/vm/src/object/ext.rs b/crates/vm/src/object/ext.rs index 1e2b78d9a9e..88f5fdc66d7 100644 --- a/crates/vm/src/object/ext.rs +++ b/crates/vm/src/object/ext.rs @@ -1,6 +1,6 @@ use super::{ core::{Py, PyObject, PyObjectRef, PyRef}, - payload::{PyObjectPayload, PyPayload}, + payload::PyPayload, }; use crate::common::{ atomic::{Ordering, PyAtomic, Radium}, @@ -41,7 +41,7 @@ pub type PyResult<T = PyObjectRef> = Result<T, PyBaseExceptionRef>; // A valid v impl<T: fmt::Display> fmt::Display for PyRef<T> where - T: PyObjectPayload + fmt::Display, + T: PyPayload + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&**self, f) @@ -50,7 +50,7 @@ where impl<T: fmt::Display> fmt::Display for Py<T> where - T: PyObjectPayload + fmt::Display, + T: PyPayload + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&**self, f) @@ -58,7 +58,7 @@ where } #[repr(transparent)] -pub struct PyExact<T: PyObjectPayload> { +pub struct PyExact<T> { inner: Py<T>, } @@ -80,28 +80,28 @@ impl<T: PyPayload> Deref for PyExact<T> { } } -impl<T: PyObjectPayload> Borrow<PyObject> for PyExact<T> { +impl<T: PyPayload> Borrow<PyObject> for PyExact<T> { #[inline(always)] fn borrow(&self) -> &PyObject { self.inner.borrow() } } -impl<T: PyObjectPayload> AsRef<PyObject> for PyExact<T> { +impl<T: PyPayload> AsRef<PyObject> for PyExact<T> { #[inline(always)] fn as_ref(&self) -> &PyObject { self.inner.as_ref() } } -impl<T: PyObjectPayload> Borrow<Py<T>> for PyExact<T> { +impl<T: PyPayload> Borrow<Py<T>> for PyExact<T> { #[inline(always)] fn borrow(&self) -> &Py<T> { &self.inner } } -impl<T: PyObjectPayload> AsRef<Py<T>> for PyExact<T> { +impl<T: PyPayload> AsRef<Py<T>> for PyExact<T> { #[inline(always)] fn as_ref(&self) -> &Py<T> { &self.inner @@ -134,11 +134,11 @@ impl<T: PyPayload> PyRef<T> { /// PyRef but guaranteed not to be a subtype instance #[derive(Debug)] #[repr(transparent)] -pub struct PyRefExact<T: PyObjectPayload> { +pub struct PyRefExact<T: PyPayload> { inner: PyRef<T>, } -impl<T: PyObjectPayload> PyRefExact<T> { +impl<T: PyPayload> PyRefExact<T> { /// # Safety /// obj must have exact type for the payload pub const unsafe fn new_unchecked(obj: PyRef<T>) -> Self { @@ -150,7 +150,7 @@ impl<T: PyObjectPayload> PyRefExact<T> { } } -impl<T: PyObjectPayload> Clone for PyRefExact<T> { +impl<T: PyPayload> Clone for PyRefExact<T> { fn clone(&self) -> Self { let inner = self.inner.clone(); Self { inner } @@ -191,28 +191,28 @@ impl<T: PyPayload> Deref for PyRefExact<T> { } } -impl<T: PyObjectPayload> Borrow<PyObject> for PyRefExact<T> { +impl<T: PyPayload> Borrow<PyObject> for PyRefExact<T> { #[inline(always)] fn borrow(&self) -> &PyObject { self.inner.borrow() } } -impl<T: PyObjectPayload> AsRef<PyObject> for PyRefExact<T> { +impl<T: PyPayload> AsRef<PyObject> for PyRefExact<T> { #[inline(always)] fn as_ref(&self) -> &PyObject { self.inner.as_ref() } } -impl<T: PyObjectPayload> Borrow<Py<T>> for PyRefExact<T> { +impl<T: PyPayload> Borrow<Py<T>> for PyRefExact<T> { #[inline(always)] fn borrow(&self) -> &Py<T> { self.inner.borrow() } } -impl<T: PyObjectPayload> AsRef<Py<T>> for PyRefExact<T> { +impl<T: PyPayload> AsRef<Py<T>> for PyRefExact<T> { #[inline(always)] fn as_ref(&self) -> &Py<T> { self.inner.as_ref() @@ -260,10 +260,10 @@ impl<T> Drop for PyAtomicRef<T> { cfg_if::cfg_if! { if #[cfg(feature = "threading")] { - unsafe impl<T: Send + PyObjectPayload> Send for PyAtomicRef<T> {} - unsafe impl<T: Sync + PyObjectPayload> Sync for PyAtomicRef<T> {} - unsafe impl<T: Send + PyObjectPayload> Send for PyAtomicRef<Option<T>> {} - unsafe impl<T: Sync + PyObjectPayload> Sync for PyAtomicRef<Option<T>> {} + unsafe impl<T: Send + PyPayload> Send for PyAtomicRef<T> {} + unsafe impl<T: Sync + PyPayload> Sync for PyAtomicRef<T> {} + unsafe impl<T: Send + PyPayload> Send for PyAtomicRef<Option<T>> {} + unsafe impl<T: Sync + PyPayload> Sync for PyAtomicRef<Option<T>> {} unsafe impl Send for PyAtomicRef<PyObject> {} unsafe impl Sync for PyAtomicRef<PyObject> {} unsafe impl Send for PyAtomicRef<Option<PyObject>> {} @@ -285,7 +285,7 @@ impl<T: fmt::Debug> fmt::Debug for PyAtomicRef<T> { } } -impl<T: PyObjectPayload> From<PyRef<T>> for PyAtomicRef<T> { +impl<T: PyPayload> From<PyRef<T>> for PyAtomicRef<T> { fn from(pyref: PyRef<T>) -> Self { let py = PyRef::leak(pyref); Self { @@ -295,7 +295,7 @@ impl<T: PyObjectPayload> From<PyRef<T>> for PyAtomicRef<T> { } } -impl<T: PyObjectPayload> Deref for PyAtomicRef<T> { +impl<T: PyPayload> Deref for PyAtomicRef<T> { type Target = Py<T>; fn deref(&self) -> &Self::Target { @@ -309,7 +309,7 @@ impl<T: PyObjectPayload> Deref for PyAtomicRef<T> { } } -impl<T: PyObjectPayload> PyAtomicRef<T> { +impl<T: PyPayload> PyAtomicRef<T> { /// # Safety /// The caller is responsible to keep the returned PyRef alive /// until no more reference can be used via PyAtomicRef::deref() @@ -328,7 +328,7 @@ impl<T: PyObjectPayload> PyAtomicRef<T> { } } -impl<T: PyObjectPayload> From<Option<PyRef<T>>> for PyAtomicRef<Option<T>> { +impl<T: PyPayload> From<Option<PyRef<T>>> for PyAtomicRef<Option<T>> { fn from(opt_ref: Option<PyRef<T>>) -> Self { let val = opt_ref .map(|x| PyRef::leak(x) as *const Py<T> as *mut _) @@ -340,7 +340,7 @@ impl<T: PyObjectPayload> From<Option<PyRef<T>>> for PyAtomicRef<Option<T>> { } } -impl<T: PyObjectPayload> PyAtomicRef<Option<T>> { +impl<T: PyPayload> PyAtomicRef<Option<T>> { pub fn deref(&self) -> Option<&Py<T>> { unsafe { self.inner.load(Ordering::Relaxed).cast::<Py<T>>().as_ref() } } @@ -521,25 +521,25 @@ impl PyObject { /// `PyObjectRef`, which isn't that cheap as that increments the atomic reference counter. // TODO: check if we still need this #[allow(dead_code)] -pub struct PyLease<'a, T: PyObjectPayload> { +pub struct PyLease<'a, T: PyPayload> { inner: PyRwLockReadGuard<'a, PyRef<T>>, } -impl<T: PyObjectPayload + PyPayload> PyLease<'_, T> { +impl<T: PyPayload> PyLease<'_, T> { #[inline(always)] pub fn into_owned(self) -> PyRef<T> { self.inner.clone() } } -impl<T: PyObjectPayload + PyPayload> Borrow<PyObject> for PyLease<'_, T> { +impl<T: PyPayload> Borrow<PyObject> for PyLease<'_, T> { #[inline(always)] fn borrow(&self) -> &PyObject { self.inner.as_ref() } } -impl<T: PyObjectPayload + PyPayload> Deref for PyLease<'_, T> { +impl<T: PyPayload> Deref for PyLease<'_, T> { type Target = PyRef<T>; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -556,7 +556,7 @@ where } } -impl<T: PyObjectPayload> ToPyObject for PyRef<T> { +impl<T: PyPayload> ToPyObject for PyRef<T> { #[inline(always)] fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef { self.into() @@ -581,7 +581,7 @@ impl ToPyObject for &PyObject { // explicitly implementing `ToPyObject`. impl<T> ToPyObject for T where - T: PyPayload + Sized, + T: PyPayload + std::fmt::Debug + Sized, { #[inline(always)] fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef { diff --git a/crates/vm/src/object/payload.rs b/crates/vm/src/object/payload.rs index 0b7bfe0dc1a..7fc40ddba90 100644 --- a/crates/vm/src/object/payload.rs +++ b/crates/vm/src/object/payload.rs @@ -16,9 +16,7 @@ cfg_if::cfg_if! { } } -pub trait PyPayload: - std::fmt::Debug + MaybeTraverse + PyThreadingConstraint + Sized + 'static -{ +pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { #[inline] fn payload_type_id() -> std::any::TypeId { std::any::TypeId::of::<Self>() @@ -51,12 +49,18 @@ pub trait PyPayload: fn class(ctx: &Context) -> &'static Py<PyType>; #[inline] - fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef { + fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef + where + Self: std::fmt::Debug, + { self.into_ref(&vm.ctx).into() } #[inline] - fn _into_ref(self, cls: PyTypeRef, ctx: &Context) -> PyRef<Self> { + fn _into_ref(self, cls: PyTypeRef, ctx: &Context) -> PyRef<Self> + where + Self: std::fmt::Debug, + { let dict = if cls.slots.flags.has_feature(PyTypeFlags::HAS_DICT) { Some(ctx.new_dict()) } else { @@ -66,7 +70,10 @@ pub trait PyPayload: } #[inline] - fn into_exact_ref(self, ctx: &Context) -> PyRefExact<Self> { + fn into_exact_ref(self, ctx: &Context) -> PyRefExact<Self> + where + Self: std::fmt::Debug, + { unsafe { // Self::into_ref() always returns exact typed PyRef PyRefExact::new_unchecked(self.into_ref(ctx)) @@ -74,13 +81,19 @@ pub trait PyPayload: } #[inline] - fn into_ref(self, ctx: &Context) -> PyRef<Self> { + fn into_ref(self, ctx: &Context) -> PyRef<Self> + where + Self: std::fmt::Debug, + { let cls = Self::class(ctx); self._into_ref(cls.to_owned(), ctx) } #[inline] - fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyTypeRef) -> PyResult<PyRef<Self>> { + fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyTypeRef) -> PyResult<PyRef<Self>> + where + Self: std::fmt::Debug, + { let exact_class = Self::class(&vm.ctx); if cls.fast_issubclass(exact_class) { Ok(self._into_ref(cls, &vm.ctx)) @@ -108,7 +121,7 @@ pub trait PyObjectPayload: { } -impl<T: PyPayload + 'static> PyObjectPayload for T {} +impl<T: PyPayload + std::fmt::Debug + 'static> PyObjectPayload for T {} pub trait SlotOffset { fn offset() -> usize; diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 0cdb3c7189b..834b3b5cb45 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -793,7 +793,7 @@ pub trait Constructor: PyPayload { fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult; } -pub trait DefaultConstructor: PyPayload + Default { +pub trait DefaultConstructor: PyPayload + Default + std::fmt::Debug { fn construct_and_init(args: Self::Args, vm: &VirtualMachine) -> PyResult<PyRef<Self>> where Self: Initializer, diff --git a/crates/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs index 3fc25f3b992..c75bfe18558 100644 --- a/crates/vm/src/vm/context.rs +++ b/crates/vm/src/vm/context.rs @@ -380,7 +380,7 @@ impl Context { pub fn new_pyref<T, P>(&self, value: T) -> PyRef<P> where T: Into<P>, - P: PyPayload, + P: PyPayload + std::fmt::Debug, { value.into().into_ref(self) } From d287d1e06388555c791479c323a44f8fc7834a51 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 2 Dec 2025 16:25:33 +0200 Subject: [PATCH 393/819] Sort `Instruction` enum & related match arms (#6322) * Sort `Instruction` enum * Sort `fmt_dis` match arms * Sort `run_instruction` * Sort JIT `add_instruction` --- crates/compiler-core/src/bytecode.rs | 550 +++++---- crates/jit/src/instructions.rs | 368 +++--- crates/vm/src/frame.rs | 1574 +++++++++++++------------- 3 files changed, 1245 insertions(+), 1247 deletions(-) diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 2a0ad7e280a..d1482b8c8a6 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -549,161 +549,92 @@ pub type NameIdx = u32; #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(u8)] pub enum Instruction { - Nop, - /// Importing by name - ImportName { - idx: Arg<NameIdx>, - }, - /// Importing without name - ImportNameless, - /// from ... import ... - ImportFrom { - idx: Arg<NameIdx>, - }, - LoadFast(Arg<NameIdx>), - LoadNameAny(Arg<NameIdx>), - LoadGlobal(Arg<NameIdx>), - LoadDeref(Arg<NameIdx>), - LoadClassDeref(Arg<NameIdx>), - StoreFast(Arg<NameIdx>), - StoreLocal(Arg<NameIdx>), - StoreGlobal(Arg<NameIdx>), - StoreDeref(Arg<NameIdx>), - DeleteFast(Arg<NameIdx>), - DeleteLocal(Arg<NameIdx>), - DeleteGlobal(Arg<NameIdx>), - DeleteDeref(Arg<NameIdx>), - LoadClosure(Arg<NameIdx>), - Subscript, - StoreSubscript, - DeleteSubscript, - /// Performs `is` comparison, or `is not` if `invert` is 1. - IsOp(Arg<Invert>), - /// Performs `in` comparison, or `not in` if `invert` is 1. - ContainsOp(Arg<Invert>), - StoreAttr { - idx: Arg<NameIdx>, - }, - DeleteAttr { - idx: Arg<NameIdx>, - }, - LoadConst { - /// index into constants vec - idx: Arg<u32>, - }, - UnaryOperation { - op: Arg<UnaryOperator>, - }, + BeforeAsyncWith, BinaryOp { op: Arg<BinaryOperator>, }, BinarySubscript, - LoadAttr { - idx: Arg<NameIdx>, - }, - CompareOperation { - op: Arg<ComparisonOperator>, + Break { + target: Arg<Label>, }, - CopyItem { - index: Arg<u32>, + BuildListFromTuples { + size: Arg<u32>, }, - Pop, - Swap { - index: Arg<u32>, + BuildList { + size: Arg<u32>, }, - ToBool, - GetIter, - GetLen, - CallIntrinsic1 { - func: Arg<IntrinsicFunction1>, + BuildMapForCall { + size: Arg<u32>, }, - CallIntrinsic2 { - func: Arg<IntrinsicFunction2>, + BuildMap { + size: Arg<u32>, }, - Continue { - target: Arg<Label>, + BuildSetFromTuples { + size: Arg<u32>, }, - Break { - target: Arg<Label>, + BuildSet { + size: Arg<u32>, }, - /// Performs exception matching for except. - /// Tests whether the STACK[-2] is an exception matching STACK[-1]. - /// Pops STACK[-1] and pushes the boolean result of the test. - JumpIfNotExcMatch(Arg<Label>), - Jump { - target: Arg<Label>, + BuildSlice { + argc: Arg<BuildSliceArgCount>, }, - /// Pop the top of the stack, and jump if this value is true. - PopJumpIfTrue { - target: Arg<Label>, + BuildString { + size: Arg<u32>, }, - /// Pop the top of the stack, and jump if this value is false. - PopJumpIfFalse { - target: Arg<Label>, + BuildTupleFromIter, + BuildTupleFromTuples { + size: Arg<u32>, }, - /// Peek at the top of the stack, and jump if this value is true. - /// Otherwise, pop top of stack. - JumpIfTrueOrPop { - target: Arg<Label>, + BuildTuple { + size: Arg<u32>, }, - /// Peek at the top of the stack, and jump if this value is false. - /// Otherwise, pop top of stack. - JumpIfFalseOrPop { - target: Arg<Label>, + CallFunctionEx { + has_kwargs: Arg<bool>, }, - MakeFunction, - SetFunctionAttribute { - attr: Arg<MakeFunctionFlags>, + CallFunctionKeyword { + nargs: Arg<u32>, }, CallFunctionPositional { nargs: Arg<u32>, }, - CallFunctionKeyword { - nargs: Arg<u32>, + CallIntrinsic1 { + func: Arg<IntrinsicFunction1>, }, - CallFunctionEx { - has_kwargs: Arg<bool>, + CallIntrinsic2 { + func: Arg<IntrinsicFunction2>, }, - LoadMethod { - idx: Arg<NameIdx>, + CallMethodEx { + has_kwargs: Arg<bool>, }, - CallMethodPositional { + CallMethodKeyword { nargs: Arg<u32>, }, - CallMethodKeyword { + CallMethodPositional { nargs: Arg<u32>, }, - CallMethodEx { - has_kwargs: Arg<bool>, + CompareOperation { + op: Arg<ComparisonOperator>, }, - ForIter { + /// Performs `in` comparison, or `not in` if `invert` is 1. + ContainsOp(Arg<Invert>), + Continue { target: Arg<Label>, }, - ReturnValue, - ReturnConst { - idx: Arg<u32>, + CopyItem { + index: Arg<u32>, }, - YieldValue, - YieldFrom, - - /// Resume execution (e.g., at function start, after yield, etc.) - Resume { - arg: Arg<u32>, + DeleteAttr { + idx: Arg<NameIdx>, }, - - SetupAnnotation, - SetupLoop, - - /// Setup a finally handler, which will be called whenever one of this events occurs: - /// - the block is popped - /// - the function returns - /// - an exception is returned - SetupFinally { - handler: Arg<Label>, + DeleteDeref(Arg<NameIdx>), + DeleteFast(Arg<NameIdx>), + DeleteGlobal(Arg<NameIdx>), + DeleteLocal(Arg<NameIdx>), + DeleteSubscript, + DictUpdate { + index: Arg<u32>, }, - - /// Enter a finally block, without returning, excepting, just because we are there. - EnterFinally, + EndAsyncFor, /// Marker bytecode for the end of a finally sequence. /// When this bytecode is executed, the eval loop does one of those things: @@ -713,95 +644,164 @@ pub enum Instruction { /// - Do nothing at all, just continue EndFinally, - SetupExcept { - handler: Arg<Label>, + /// Enter a finally block, without returning, excepting, just because we are there. + EnterFinally, + ExtendedArg, + ForIter { + target: Arg<Label>, }, - SetupWith { - end: Arg<Label>, + FormatValue { + conversion: Arg<ConversionFlag>, }, - WithCleanupStart, - WithCleanupFinish, - PopBlock, - Raise { - kind: Arg<RaiseKind>, + GetAIter, + GetANext, + GetAwaitable, + GetIter, + GetLen, + /// from ... import ... + ImportFrom { + idx: Arg<NameIdx>, }, - BuildString { - size: Arg<u32>, + /// Importing without name + ImportNameless, + /// Importing by name + ImportName { + idx: Arg<NameIdx>, }, - BuildTuple { - size: Arg<u32>, + /// Performs `is` comparison, or `is not` if `invert` is 1. + IsOp(Arg<Invert>), + /// Peek at the top of the stack, and jump if this value is false. + /// Otherwise, pop top of stack. + JumpIfFalseOrPop { + target: Arg<Label>, }, - BuildTupleFromTuples { - size: Arg<u32>, + /// Performs exception matching for except. + /// Tests whether the STACK[-2] is an exception matching STACK[-1]. + /// Pops STACK[-1] and pushes the boolean result of the test. + JumpIfNotExcMatch(Arg<Label>), + /// Peek at the top of the stack, and jump if this value is true. + /// Otherwise, pop top of stack. + JumpIfTrueOrPop { + target: Arg<Label>, }, - BuildTupleFromIter, - BuildList { - size: Arg<u32>, + Jump { + target: Arg<Label>, }, - BuildListFromTuples { - size: Arg<u32>, + ListAppend { + i: Arg<u32>, }, - BuildSet { - size: Arg<u32>, + LoadAttr { + idx: Arg<NameIdx>, }, - BuildSetFromTuples { - size: Arg<u32>, + LoadBuildClass, + LoadClassDeref(Arg<NameIdx>), + LoadClosure(Arg<NameIdx>), + LoadConst { + /// index into constants vec + idx: Arg<u32>, }, - BuildMap { - size: Arg<u32>, + LoadDeref(Arg<NameIdx>), + LoadFast(Arg<NameIdx>), + LoadGlobal(Arg<NameIdx>), + LoadMethod { + idx: Arg<NameIdx>, }, - BuildMapForCall { - size: Arg<u32>, + LoadNameAny(Arg<NameIdx>), + MakeFunction, + MapAdd { + i: Arg<u32>, }, - DictUpdate { - index: Arg<u32>, + MatchClass(Arg<u32>), + MatchKeys, + MatchMapping, + MatchSequence, + Nop, + Pop, + PopBlock, + PopException, + /// Pop the top of the stack, and jump if this value is false. + PopJumpIfFalse { + target: Arg<Label>, }, - BuildSlice { - argc: Arg<BuildSliceArgCount>, + /// Pop the top of the stack, and jump if this value is true. + PopJumpIfTrue { + target: Arg<Label>, }, - ListAppend { - i: Arg<u32>, + + PrintExpr, + Raise { + kind: Arg<RaiseKind>, + }, + + /// Resume execution (e.g., at function start, after yield, etc.) + Resume { + arg: Arg<u32>, + }, + ReturnConst { + idx: Arg<u32>, + }, + ReturnValue, + Reverse { + amount: Arg<u32>, }, SetAdd { i: Arg<u32>, }, - MapAdd { - i: Arg<u32>, + SetFunctionAttribute { + attr: Arg<MakeFunctionFlags>, }, - PrintExpr, - LoadBuildClass, - UnpackSequence { - size: Arg<u32>, - }, - UnpackEx { - args: Arg<UnpackExArgs>, + SetupAnnotation, + SetupAsyncWith { + end: Arg<Label>, }, - FormatValue { - conversion: Arg<ConversionFlag>, + + SetupExcept { + handler: Arg<Label>, }, - PopException, - Reverse { - amount: Arg<u32>, + + /// Setup a finally handler, which will be called whenever one of this events occurs: + /// - the block is popped + /// - the function returns + /// - an exception is returned + SetupFinally { + handler: Arg<Label>, }, - GetAwaitable, - BeforeAsyncWith, - SetupAsyncWith { + SetupLoop, + SetupWith { end: Arg<Label>, }, - GetAIter, - GetANext, - EndAsyncFor, - MatchMapping, - MatchSequence, - MatchKeys, - MatchClass(Arg<u32>), - ExtendedArg, + StoreAttr { + idx: Arg<NameIdx>, + }, + StoreDeref(Arg<NameIdx>), + StoreFast(Arg<NameIdx>), + StoreGlobal(Arg<NameIdx>), + StoreLocal(Arg<NameIdx>), + StoreSubscript, + Subscript, + Swap { + index: Arg<u32>, + }, + ToBool, + UnaryOperation { + op: Arg<UnaryOperator>, + }, + UnpackEx { + args: Arg<UnpackExArgs>, + }, + UnpackSequence { + size: Arg<u32>, + }, + WithCleanupFinish, + WithCleanupStart, + YieldFrom, + YieldValue, // If you add a new instruction here, be sure to keep LAST_INSTRUCTION updated } // This must be kept up to date to avoid marshaling errors -const LAST_INSTRUCTION: Instruction = Instruction::ExtendedArg; +const LAST_INSTRUCTION: Instruction = Instruction::YieldValue; const _: () = assert!(mem::size_of::<Instruction>() == 1); @@ -1798,114 +1798,112 @@ impl Instruction { }; match self { - Nop => w!(Nop), - ImportName { idx } => w!(ImportName, name = idx), - ImportNameless => w!(ImportNameless), - ImportFrom { idx } => w!(ImportFrom, name = idx), - LoadFast(idx) => w!(LoadFast, varname = idx), - LoadNameAny(idx) => w!(LoadNameAny, name = idx), - LoadGlobal(idx) => w!(LoadGlobal, name = idx), - LoadDeref(idx) => w!(LoadDeref, cell_name = idx), - LoadClassDeref(idx) => w!(LoadClassDeref, cell_name = idx), - StoreFast(idx) => w!(StoreFast, varname = idx), - StoreLocal(idx) => w!(StoreLocal, name = idx), - StoreGlobal(idx) => w!(StoreGlobal, name = idx), - StoreDeref(idx) => w!(StoreDeref, cell_name = idx), - DeleteFast(idx) => w!(DeleteFast, varname = idx), - DeleteLocal(idx) => w!(DeleteLocal, name = idx), - DeleteGlobal(idx) => w!(DeleteGlobal, name = idx), - DeleteDeref(idx) => w!(DeleteDeref, cell_name = idx), - LoadClosure(i) => w!(LoadClosure, cell_name = i), - IsOp(inv) => w!(IS_OP, ?inv), - ContainsOp(inv) => w!(CONTAINS_OP, ?inv), - JumpIfNotExcMatch(target) => w!(JUMP_IF_NOT_EXC_MATCH, target), - Subscript => w!(Subscript), - StoreSubscript => w!(StoreSubscript), - DeleteSubscript => w!(DeleteSubscript), - StoreAttr { idx } => w!(StoreAttr, name = idx), - DeleteAttr { idx } => w!(DeleteAttr, name = idx), - LoadConst { idx } => fmt_const("LoadConst", arg, f, idx), - UnaryOperation { op } => w!(UnaryOperation, ?op), + BeforeAsyncWith => w!(BeforeAsyncWith), BinaryOp { op } => write!(f, "{:pad$}({})", "BINARY_OP", op.get(arg)), - BinarySubscript => w!(BinarySubscript), - LoadAttr { idx } => w!(LoadAttr, name = idx), + Break { target } => w!(Break, target), + BuildListFromTuples { size } => w!(BuildListFromTuples, size), + BuildList { size } => w!(BuildList, size), + BuildMapForCall { size } => w!(BuildMapForCall, size), + BuildMap { size } => w!(BuildMap, size), + BuildSetFromTuples { size } => w!(BuildSetFromTuples, size), + BuildSet { size } => w!(BuildSet, size), + BuildSlice { argc } => w!(BuildSlice, ?argc), + BuildString { size } => w!(BuildString, size), + BuildTupleFromIter => w!(BuildTupleFromIter), + BuildTupleFromTuples { size } => w!(BuildTupleFromTuples, size), + BuildTuple { size } => w!(BuildTuple, size), + CallFunctionEx { has_kwargs } => w!(CallFunctionEx, has_kwargs), + CallFunctionKeyword { nargs } => w!(CallFunctionKeyword, nargs), + CallFunctionPositional { nargs } => w!(CallFunctionPositional, nargs), + CallIntrinsic1 { func } => w!(CallIntrinsic1, ?func), + CallIntrinsic2 { func } => w!(CallIntrinsic2, ?func), + CallMethodEx { has_kwargs } => w!(CallMethodEx, has_kwargs), + CallMethodKeyword { nargs } => w!(CallMethodKeyword, nargs), + CallMethodPositional { nargs } => w!(CallMethodPositional, nargs), CompareOperation { op } => w!(CompareOperation, ?op), + ContainsOp(inv) => w!(CONTAINS_OP, ?inv), + Continue { target } => w!(Continue, target), CopyItem { index } => w!(CopyItem, index), - Pop => w!(Pop), - Swap { index } => w!(Swap, index), - ToBool => w!(ToBool), + DeleteAttr { idx } => w!(DeleteAttr, name = idx), + DeleteDeref(idx) => w!(DeleteDeref, cell_name = idx), + DeleteFast(idx) => w!(DeleteFast, varname = idx), + DeleteGlobal(idx) => w!(DeleteGlobal, name = idx), + DeleteLocal(idx) => w!(DeleteLocal, name = idx), + DeleteSubscript => w!(DeleteSubscript), + DictUpdate { index } => w!(DictUpdate, index), + EndAsyncFor => w!(EndAsyncFor), + EndFinally => w!(EndFinally), + EnterFinally => w!(EnterFinally), + ExtendedArg => w!(ExtendedArg, Arg::<u32>::marker()), + ForIter { target } => w!(ForIter, target), + FormatValue { conversion } => w!(FormatValue, ?conversion), + GetAIter => w!(GetAIter), + GetANext => w!(GetANext), + GetAwaitable => w!(GetAwaitable), GetIter => w!(GetIter), - // GET_LEN GetLen => w!(GetLen), - CallIntrinsic1 { func } => w!(CallIntrinsic1, ?func), - CallIntrinsic2 { func } => w!(CallIntrinsic2, ?func), - Continue { target } => w!(Continue, target), - Break { target } => w!(Break, target), - Jump { target } => w!(Jump, target), - PopJumpIfTrue { target } => w!(PopJumpIfTrue, target), - PopJumpIfFalse { target } => w!(PopJumpIfFalse, target), - JumpIfTrueOrPop { target } => w!(JumpIfTrueOrPop, target), + ImportFrom { idx } => w!(ImportFrom, name = idx), + ImportNameless => w!(ImportNameless), + ImportName { idx } => w!(ImportName, name = idx), + IsOp(inv) => w!(IS_OP, ?inv), JumpIfFalseOrPop { target } => w!(JumpIfFalseOrPop, target), - MakeFunction => w!(MakeFunction), - SetFunctionAttribute { attr } => w!(SetFunctionAttribute, ?attr), - CallFunctionPositional { nargs } => w!(CallFunctionPositional, nargs), - CallFunctionKeyword { nargs } => w!(CallFunctionKeyword, nargs), - CallFunctionEx { has_kwargs } => w!(CallFunctionEx, has_kwargs), + JumpIfNotExcMatch(target) => w!(JUMP_IF_NOT_EXC_MATCH, target), + JumpIfTrueOrPop { target } => w!(JumpIfTrueOrPop, target), + Jump { target } => w!(Jump, target), + ListAppend { i } => w!(ListAppend, i), + LoadAttr { idx } => w!(LoadAttr, name = idx), + LoadBuildClass => w!(LoadBuildClass), + LoadClassDeref(idx) => w!(LoadClassDeref, cell_name = idx), + LoadClosure(i) => w!(LoadClosure, cell_name = i), + LoadConst { idx } => fmt_const("LoadConst", arg, f, idx), + LoadDeref(idx) => w!(LoadDeref, cell_name = idx), + LoadFast(idx) => w!(LoadFast, varname = idx), + LoadGlobal(idx) => w!(LoadGlobal, name = idx), LoadMethod { idx } => w!(LoadMethod, name = idx), - CallMethodPositional { nargs } => w!(CallMethodPositional, nargs), - CallMethodKeyword { nargs } => w!(CallMethodKeyword, nargs), - CallMethodEx { has_kwargs } => w!(CallMethodEx, has_kwargs), - ForIter { target } => w!(ForIter, target), - ReturnValue => w!(ReturnValue), - ReturnConst { idx } => fmt_const("ReturnConst", arg, f, idx), + LoadNameAny(idx) => w!(LoadNameAny, name = idx), + MakeFunction => w!(MakeFunction), + MapAdd { i } => w!(MapAdd, i), + MatchClass(arg) => w!(MatchClass, arg), + MatchKeys => w!(MatchKeys), + MatchMapping => w!(MatchMapping), + MatchSequence => w!(MatchSequence), + Nop => w!(Nop), + Pop => w!(Pop), + PopBlock => w!(PopBlock), + PopException => w!(PopException), + PopJumpIfFalse { target } => w!(PopJumpIfFalse, target), + PopJumpIfTrue { target } => w!(PopJumpIfTrue, target), + PrintExpr => w!(PrintExpr), + Raise { kind } => w!(Raise, ?kind), Resume { arg } => w!(Resume, arg), - YieldValue => w!(YieldValue), - YieldFrom => w!(YieldFrom), + ReturnConst { idx } => fmt_const("ReturnConst", arg, f, idx), + ReturnValue => w!(ReturnValue), + Reverse { amount } => w!(Reverse, amount), + SetAdd { i } => w!(SetAdd, i), + SetFunctionAttribute { attr } => w!(SetFunctionAttribute, ?attr), SetupAnnotation => w!(SetupAnnotation), - SetupLoop => w!(SetupLoop), + SetupAsyncWith { end } => w!(SetupAsyncWith, end), SetupExcept { handler } => w!(SetupExcept, handler), SetupFinally { handler } => w!(SetupFinally, handler), - EnterFinally => w!(EnterFinally), - EndFinally => w!(EndFinally), + SetupLoop => w!(SetupLoop), SetupWith { end } => w!(SetupWith, end), - WithCleanupStart => w!(WithCleanupStart), - WithCleanupFinish => w!(WithCleanupFinish), - BeforeAsyncWith => w!(BeforeAsyncWith), - SetupAsyncWith { end } => w!(SetupAsyncWith, end), - PopBlock => w!(PopBlock), - Raise { kind } => w!(Raise, ?kind), - BuildString { size } => w!(BuildString, size), - BuildTuple { size } => w!(BuildTuple, size), - BuildTupleFromTuples { size } => w!(BuildTupleFromTuples, size), - BuildTupleFromIter => w!(BuildTupleFromIter), - BuildList { size } => w!(BuildList, size), - BuildListFromTuples { size } => w!(BuildListFromTuples, size), - BuildSet { size } => w!(BuildSet, size), - BuildSetFromTuples { size } => w!(BuildSetFromTuples, size), - BuildMap { size } => w!(BuildMap, size), - BuildMapForCall { size } => w!(BuildMapForCall, size), - DictUpdate { index } => w!(DictUpdate, index), - BuildSlice { argc } => w!(BuildSlice, ?argc), - ListAppend { i } => w!(ListAppend, i), - SetAdd { i } => w!(SetAdd, i), - MapAdd { i } => w!(MapAdd, i), - PrintExpr => w!(PrintExpr), - LoadBuildClass => w!(LoadBuildClass), - UnpackSequence { size } => w!(UnpackSequence, size), + StoreAttr { idx } => w!(StoreAttr, name = idx), + StoreDeref(idx) => w!(StoreDeref, cell_name = idx), + StoreFast(idx) => w!(StoreFast, varname = idx), + StoreGlobal(idx) => w!(StoreGlobal, name = idx), + StoreLocal(idx) => w!(StoreLocal, name = idx), + StoreSubscript => w!(StoreSubscript), + Subscript => w!(Subscript), + Swap { index } => w!(Swap, index), + ToBool => w!(ToBool), + UnaryOperation { op } => w!(UnaryOperation, ?op), UnpackEx { args } => w!(UnpackEx, args), - FormatValue { conversion } => w!(FormatValue, ?conversion), - PopException => w!(PopException), - Reverse { amount } => w!(Reverse, amount), - GetAwaitable => w!(GetAwaitable), - GetAIter => w!(GetAIter), - GetANext => w!(GetANext), - EndAsyncFor => w!(EndAsyncFor), - MatchMapping => w!(MatchMapping), - MatchSequence => w!(MatchSequence), - MatchKeys => w!(MatchKeys), - MatchClass(arg) => w!(MatchClass, arg), - ExtendedArg => w!(ExtendedArg, Arg::<u32>::marker()), + UnpackSequence { size } => w!(UnpackSequence, size), + WithCleanupFinish => w!(WithCleanupFinish), + WithCleanupStart => w!(WithCleanupStart), + YieldFrom => w!(YieldFrom), + YieldValue => w!(YieldValue), } } } diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index 8f25c757d19..9ada6c32781 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -275,167 +275,6 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { arg: OpArg, ) -> Result<(), JitCompileError> { match instruction { - Instruction::ExtendedArg => Ok(()), - Instruction::PopJumpIfFalse { target } => { - let cond = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; - let val = self.boolean_val(cond)?; - let then_block = self.get_or_create_block(target.get(arg)); - let else_block = self.builder.create_block(); - - self.builder - .ins() - .brif(val, else_block, &[], then_block, &[]); - self.builder.switch_to_block(else_block); - - Ok(()) - } - Instruction::PopJumpIfTrue { target } => { - let cond = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; - let val = self.boolean_val(cond)?; - let then_block = self.get_or_create_block(target.get(arg)); - let else_block = self.builder.create_block(); - - self.builder - .ins() - .brif(val, then_block, &[], else_block, &[]); - self.builder.switch_to_block(else_block); - - Ok(()) - } - - Instruction::Jump { target } => { - let target_block = self.get_or_create_block(target.get(arg)); - self.builder.ins().jump(target_block, &[]); - Ok(()) - } - Instruction::LoadFast(idx) => { - let local = self.variables[idx.get(arg) as usize] - .as_ref() - .ok_or(JitCompileError::BadBytecode)?; - self.stack.push(JitValue::from_type_and_value( - local.ty.clone(), - self.builder.use_var(local.var), - )); - Ok(()) - } - Instruction::StoreFast(idx) => { - let val = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; - self.store_variable(idx.get(arg), val) - } - Instruction::LoadConst { idx } => { - let val = self - .prepare_const(bytecode.constants[idx.get(arg) as usize].borrow_constant())?; - self.stack.push(val); - Ok(()) - } - Instruction::BuildTuple { size } => { - let elements = self.pop_multiple(size.get(arg) as usize); - self.stack.push(JitValue::Tuple(elements)); - Ok(()) - } - Instruction::UnpackSequence { size } => { - let val = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; - - let elements = match val { - JitValue::Tuple(elements) => elements, - _ => return Err(JitCompileError::NotSupported), - }; - - if elements.len() != size.get(arg) as usize { - return Err(JitCompileError::NotSupported); - } - - self.stack.extend(elements.into_iter().rev()); - Ok(()) - } - Instruction::ReturnValue => { - let val = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; - self.return_value(val) - } - Instruction::ReturnConst { idx } => { - let val = self - .prepare_const(bytecode.constants[idx.get(arg) as usize].borrow_constant())?; - self.return_value(val) - } - Instruction::CompareOperation { op, .. } => { - let op = op.get(arg); - // the rhs is popped off first - let b = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; - let a = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; - - let a_type: Option<JitType> = a.to_jit_type(); - let b_type: Option<JitType> = b.to_jit_type(); - - match (a, b) { - (JitValue::Int(a), JitValue::Int(b)) - | (JitValue::Bool(a), JitValue::Bool(b)) - | (JitValue::Bool(a), JitValue::Int(b)) - | (JitValue::Int(a), JitValue::Bool(b)) => { - let operand_one = match a_type.unwrap() { - JitType::Bool => self.builder.ins().uextend(types::I64, a), - _ => a, - }; - - let operand_two = match b_type.unwrap() { - JitType::Bool => self.builder.ins().uextend(types::I64, b), - _ => b, - }; - - let cond = match op { - ComparisonOperator::Equal => IntCC::Equal, - ComparisonOperator::NotEqual => IntCC::NotEqual, - ComparisonOperator::Less => IntCC::SignedLessThan, - ComparisonOperator::LessOrEqual => IntCC::SignedLessThanOrEqual, - ComparisonOperator::Greater => IntCC::SignedGreaterThan, - ComparisonOperator::GreaterOrEqual => IntCC::SignedGreaterThanOrEqual, - }; - - let val = self.builder.ins().icmp(cond, operand_one, operand_two); - self.stack.push(JitValue::Bool(val)); - Ok(()) - } - (JitValue::Float(a), JitValue::Float(b)) => { - let cond = match op { - ComparisonOperator::Equal => FloatCC::Equal, - ComparisonOperator::NotEqual => FloatCC::NotEqual, - ComparisonOperator::Less => FloatCC::LessThan, - ComparisonOperator::LessOrEqual => FloatCC::LessThanOrEqual, - ComparisonOperator::Greater => FloatCC::GreaterThan, - ComparisonOperator::GreaterOrEqual => FloatCC::GreaterThanOrEqual, - }; - - let val = self.builder.ins().fcmp(cond, a, b); - self.stack.push(JitValue::Bool(val)); - Ok(()) - } - _ => Err(JitCompileError::NotSupported), - } - } - Instruction::UnaryOperation { op, .. } => { - let op = op.get(arg); - let a = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; - match (op, a) { - (UnaryOperator::Minus, JitValue::Int(val)) => { - // Compile minus as 0 - a. - let zero = self.builder.ins().iconst(types::I64, 0); - let out = self.compile_sub(zero, val); - self.stack.push(JitValue::Int(out)); - Ok(()) - } - (UnaryOperator::Plus, JitValue::Int(val)) => { - // Nothing to do - self.stack.push(JitValue::Int(val)); - Ok(()) - } - (UnaryOperator::Not, a) => { - let boolean = self.boolean_val(a)?; - let not_boolean = self.builder.ins().bxor_imm(boolean, 1); - self.stack.push(JitValue::Bool(not_boolean)); - Ok(()) - } - _ => Err(JitCompileError::NotSupported), - } - } Instruction::BinaryOp { op } => { let op = op.get(arg); // the rhs is popped off first @@ -597,26 +436,11 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { Ok(()) } - Instruction::SetupLoop => { - let loop_head = self.builder.create_block(); - self.builder.ins().jump(loop_head, &[]); - self.builder.switch_to_block(loop_head); - Ok(()) - } - Instruction::PopBlock => { - // TODO: block support + Instruction::BuildTuple { size } => { + let elements = self.pop_multiple(size.get(arg) as usize); + self.stack.push(JitValue::Tuple(elements)); Ok(()) } - Instruction::LoadGlobal(idx) => { - let name = &bytecode.names[idx.get(arg) as usize]; - - if name.as_ref() != bytecode.obj_name.as_ref() { - Err(JitCompileError::NotSupported) - } else { - self.stack.push(JitValue::FuncRef(func_ref)); - Ok(()) - } - } Instruction::CallFunctionPositional { nargs } => { let nargs = nargs.get(arg); @@ -637,7 +461,151 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { _ => Err(JitCompileError::BadBytecode), } } + Instruction::CompareOperation { op, .. } => { + let op = op.get(arg); + // the rhs is popped off first + let b = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; + let a = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; + + let a_type: Option<JitType> = a.to_jit_type(); + let b_type: Option<JitType> = b.to_jit_type(); + + match (a, b) { + (JitValue::Int(a), JitValue::Int(b)) + | (JitValue::Bool(a), JitValue::Bool(b)) + | (JitValue::Bool(a), JitValue::Int(b)) + | (JitValue::Int(a), JitValue::Bool(b)) => { + let operand_one = match a_type.unwrap() { + JitType::Bool => self.builder.ins().uextend(types::I64, a), + _ => a, + }; + + let operand_two = match b_type.unwrap() { + JitType::Bool => self.builder.ins().uextend(types::I64, b), + _ => b, + }; + + let cond = match op { + ComparisonOperator::Equal => IntCC::Equal, + ComparisonOperator::NotEqual => IntCC::NotEqual, + ComparisonOperator::Less => IntCC::SignedLessThan, + ComparisonOperator::LessOrEqual => IntCC::SignedLessThanOrEqual, + ComparisonOperator::Greater => IntCC::SignedGreaterThan, + ComparisonOperator::GreaterOrEqual => IntCC::SignedGreaterThanOrEqual, + }; + + let val = self.builder.ins().icmp(cond, operand_one, operand_two); + self.stack.push(JitValue::Bool(val)); + Ok(()) + } + (JitValue::Float(a), JitValue::Float(b)) => { + let cond = match op { + ComparisonOperator::Equal => FloatCC::Equal, + ComparisonOperator::NotEqual => FloatCC::NotEqual, + ComparisonOperator::Less => FloatCC::LessThan, + ComparisonOperator::LessOrEqual => FloatCC::LessThanOrEqual, + ComparisonOperator::Greater => FloatCC::GreaterThan, + ComparisonOperator::GreaterOrEqual => FloatCC::GreaterThanOrEqual, + }; + + let val = self.builder.ins().fcmp(cond, a, b); + self.stack.push(JitValue::Bool(val)); + Ok(()) + } + _ => Err(JitCompileError::NotSupported), + } + } + Instruction::ExtendedArg => Ok(()), + + Instruction::Jump { target } => { + let target_block = self.get_or_create_block(target.get(arg)); + self.builder.ins().jump(target_block, &[]); + Ok(()) + } + Instruction::LoadConst { idx } => { + let val = self + .prepare_const(bytecode.constants[idx.get(arg) as usize].borrow_constant())?; + self.stack.push(val); + Ok(()) + } + Instruction::LoadFast(idx) => { + let local = self.variables[idx.get(arg) as usize] + .as_ref() + .ok_or(JitCompileError::BadBytecode)?; + self.stack.push(JitValue::from_type_and_value( + local.ty.clone(), + self.builder.use_var(local.var), + )); + Ok(()) + } + Instruction::LoadGlobal(idx) => { + let name = &bytecode.names[idx.get(arg) as usize]; + + if name.as_ref() != bytecode.obj_name.as_ref() { + Err(JitCompileError::NotSupported) + } else { + self.stack.push(JitValue::FuncRef(func_ref)); + Ok(()) + } + } Instruction::Nop => Ok(()), + Instruction::Pop => { + self.stack.pop(); + Ok(()) + } + Instruction::PopBlock => { + // TODO: block support + Ok(()) + } + Instruction::PopJumpIfFalse { target } => { + let cond = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; + let val = self.boolean_val(cond)?; + let then_block = self.get_or_create_block(target.get(arg)); + let else_block = self.builder.create_block(); + + self.builder + .ins() + .brif(val, else_block, &[], then_block, &[]); + self.builder.switch_to_block(else_block); + + Ok(()) + } + Instruction::PopJumpIfTrue { target } => { + let cond = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; + let val = self.boolean_val(cond)?; + let then_block = self.get_or_create_block(target.get(arg)); + let else_block = self.builder.create_block(); + + self.builder + .ins() + .brif(val, then_block, &[], else_block, &[]); + self.builder.switch_to_block(else_block); + + Ok(()) + } + Instruction::Resume { arg: _resume_arg } => { + // TODO: Implement the resume instruction + Ok(()) + } + Instruction::ReturnConst { idx } => { + let val = self + .prepare_const(bytecode.constants[idx.get(arg) as usize].borrow_constant())?; + self.return_value(val) + } + Instruction::ReturnValue => { + let val = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; + self.return_value(val) + } + Instruction::SetupLoop => { + let loop_head = self.builder.create_block(); + self.builder.ins().jump(loop_head, &[]); + self.builder.switch_to_block(loop_head); + Ok(()) + } + Instruction::StoreFast(idx) => { + let val = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; + self.store_variable(idx.get(arg), val) + } Instruction::Swap { index } => { let len = self.stack.len(); let i = len - 1; @@ -645,12 +613,44 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { self.stack.swap(i, j); Ok(()) } - Instruction::Pop => { - self.stack.pop(); - Ok(()) + Instruction::UnaryOperation { op, .. } => { + let op = op.get(arg); + let a = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; + match (op, a) { + (UnaryOperator::Minus, JitValue::Int(val)) => { + // Compile minus as 0 - a. + let zero = self.builder.ins().iconst(types::I64, 0); + let out = self.compile_sub(zero, val); + self.stack.push(JitValue::Int(out)); + Ok(()) + } + (UnaryOperator::Plus, JitValue::Int(val)) => { + // Nothing to do + self.stack.push(JitValue::Int(val)); + Ok(()) + } + (UnaryOperator::Not, a) => { + let boolean = self.boolean_val(a)?; + let not_boolean = self.builder.ins().bxor_imm(boolean, 1); + self.stack.push(JitValue::Bool(not_boolean)); + Ok(()) + } + _ => Err(JitCompileError::NotSupported), + } } - Instruction::Resume { arg: _resume_arg } => { - // TODO: Implement the resume instruction + Instruction::UnpackSequence { size } => { + let val = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; + + let elements = match val { + JitValue::Tuple(elements) => elements, + _ => return Err(JitCompileError::NotSupported), + }; + + if elements.len() != size.get(arg) as usize { + return Err(JitCompileError::NotSupported); + } + + self.stack.extend(elements.into_iter().rev()); Ok(()) } _ => Err(JitCompileError::NotSupported), diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index bc684bc9f68..d6d5e42a21a 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -531,104 +531,202 @@ impl ExecutingFrame<'_> { } match instruction { - bytecode::Instruction::Nop => Ok(None), - bytecode::Instruction::LoadConst { idx } => { - self.push_value(self.code.constants[idx.get(arg) as usize].clone().into()); + bytecode::Instruction::BeforeAsyncWith => { + let mgr = self.pop_value(); + let error_string = || -> String { + format!( + "'{:.200}' object does not support the asynchronous context manager protocol", + mgr.class().name(), + ) + }; + + let aenter_res = vm + .get_special_method(&mgr, identifier!(vm, __aenter__))? + .ok_or_else(|| vm.new_type_error(error_string()))? + .invoke((), vm)?; + let aexit = mgr + .get_attr(identifier!(vm, __aexit__), vm) + .map_err(|_exc| { + vm.new_type_error({ + format!("{} (missed __aexit__ method)", error_string()) + }) + })?; + self.push_value(aexit); + self.push_value(aenter_res); + Ok(None) } - bytecode::Instruction::ImportName { idx } => { - self.import(vm, Some(self.code.names[idx.get(arg) as usize]))?; + bytecode::Instruction::BinaryOp { op } => self.execute_bin_op(vm, op.get(arg)), + bytecode::Instruction::BinarySubscript => { + let key = self.pop_value(); + let container = self.pop_value(); + self.state + .stack + .push(container.get_item(key.as_object(), vm)?); Ok(None) } - bytecode::Instruction::ImportNameless => { - self.import(vm, None)?; + + bytecode::Instruction::Break { target } => self.unwind_blocks( + vm, + UnwindReason::Break { + target: target.get(arg), + }, + ), + bytecode::Instruction::BuildListFromTuples { size } => { + // SAFETY: compiler guarantees `size` tuples are on the stack + let elements = unsafe { self.flatten_tuples(size.get(arg) as usize) }; + let list_obj = vm.ctx.new_list(elements); + self.push_value(list_obj.into()); Ok(None) } - bytecode::Instruction::ImportFrom { idx } => { - let obj = self.import_from(vm, idx.get(arg))?; - self.push_value(obj); + bytecode::Instruction::BuildList { size } => { + let elements = self.pop_multiple(size.get(arg) as usize).collect(); + let list_obj = vm.ctx.new_list(elements); + self.push_value(list_obj.into()); Ok(None) } - bytecode::Instruction::LoadFast(idx) => { - #[cold] - fn reference_error( - varname: &'static PyStrInterned, - vm: &VirtualMachine, - ) -> PyBaseExceptionRef { - vm.new_exception_msg( - vm.ctx.exceptions.unbound_local_error.to_owned(), - format!("local variable '{varname}' referenced before assignment",), - ) + bytecode::Instruction::BuildMapForCall { size } => { + self.execute_build_map_for_call(vm, size.get(arg)) + } + bytecode::Instruction::BuildMap { size } => self.execute_build_map(vm, size.get(arg)), + bytecode::Instruction::BuildSetFromTuples { size } => { + let set = PySet::default().into_ref(&vm.ctx); + for element in self.pop_multiple(size.get(arg) as usize) { + // SAFETY: trust compiler + let tup = unsafe { element.downcast_unchecked::<PyTuple>() }; + for item in tup.iter() { + set.add(item.clone(), vm)?; + } } - let idx = idx.get(arg) as usize; - let x = self.fastlocals.lock()[idx] - .clone() - .ok_or_else(|| reference_error(self.code.varnames[idx], vm))?; - self.push_value(x); + self.push_value(set.into()); Ok(None) } - bytecode::Instruction::LoadNameAny(idx) => { - let name = self.code.names[idx.get(arg) as usize]; - let result = self.locals.mapping().subscript(name, vm); - match result { - Ok(x) => self.push_value(x), - Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => { - self.push_value(self.load_global_or_builtin(name, vm)?); - } - Err(e) => return Err(e), + bytecode::Instruction::BuildSet { size } => { + let set = PySet::default().into_ref(&vm.ctx); + for element in self.pop_multiple(size.get(arg) as usize) { + set.add(element, vm)?; } + self.push_value(set.into()); Ok(None) } - bytecode::Instruction::LoadGlobal(idx) => { - let name = &self.code.names[idx.get(arg) as usize]; - let x = self.load_global_or_builtin(name, vm)?; - self.push_value(x); + bytecode::Instruction::BuildSlice { argc } => { + self.execute_build_slice(vm, argc.get(arg)) + } + /* + bytecode::Instruction::ToBool => { + dbg!("Shouldn't be called outside of match statements for now") + let value = self.pop_value(); + // call __bool__ + let result = value.try_to_bool(vm)?; + self.push_value(vm.ctx.new_bool(result).into()); + Ok(None) + } + */ + bytecode::Instruction::BuildString { size } => { + let s = self + .pop_multiple(size.get(arg) as usize) + .as_slice() + .iter() + .map(|pyobj| pyobj.downcast_ref::<PyStr>().unwrap()) + .collect::<Wtf8Buf>(); + let str_obj = vm.ctx.new_str(s); + self.push_value(str_obj.into()); Ok(None) } - bytecode::Instruction::LoadDeref(i) => { - let i = i.get(arg) as usize; - let x = self.cells_frees[i] - .get() - .ok_or_else(|| self.unbound_cell_exception(i, vm))?; - self.push_value(x); + bytecode::Instruction::BuildTupleFromIter => { + if !self.top_value().class().is(vm.ctx.types.tuple_type) { + let elements: Vec<_> = self.pop_value().try_to_value(vm)?; + let list_obj = vm.ctx.new_tuple(elements); + self.push_value(list_obj.into()); + } Ok(None) } - bytecode::Instruction::LoadClassDeref(i) => { - let i = i.get(arg) as usize; - let name = if i < self.code.cellvars.len() { - self.code.cellvars[i] - } else { - self.code.freevars[i - self.code.cellvars.len()] - }; - let value = self.locals.mapping().subscript(name, vm).ok(); - self.push_value(match value { - Some(v) => v, - None => self.cells_frees[i] - .get() - .ok_or_else(|| self.unbound_cell_exception(i, vm))?, - }); + bytecode::Instruction::BuildTupleFromTuples { size } => { + // SAFETY: compiler guarantees `size` tuples are on the stack + let elements = unsafe { self.flatten_tuples(size.get(arg) as usize) }; + let list_obj = vm.ctx.new_tuple(elements); + self.push_value(list_obj.into()); Ok(None) } - bytecode::Instruction::StoreFast(idx) => { - let value = self.pop_value(); - self.fastlocals.lock()[idx.get(arg) as usize] = Some(value); + bytecode::Instruction::BuildTuple { size } => { + let elements = self.pop_multiple(size.get(arg) as usize).collect(); + let list_obj = vm.ctx.new_tuple(elements); + self.push_value(list_obj.into()); Ok(None) } - bytecode::Instruction::StoreLocal(idx) => { - let name = self.code.names[idx.get(arg) as usize]; + bytecode::Instruction::CallFunctionEx { has_kwargs } => { + let args = self.collect_ex_args(vm, has_kwargs.get(arg))?; + self.execute_call(args, vm) + } + bytecode::Instruction::CallFunctionKeyword { nargs } => { + let args = self.collect_keyword_args(nargs.get(arg)); + self.execute_call(args, vm) + } + bytecode::Instruction::CallFunctionPositional { nargs } => { + let args = self.collect_positional_args(nargs.get(arg)); + self.execute_call(args, vm) + } + bytecode::Instruction::CallIntrinsic1 { func } => { let value = self.pop_value(); - self.locals.mapping().ass_subscript(name, Some(value), vm)?; + let result = self.call_intrinsic_1(func.get(arg), value, vm)?; + self.push_value(result); Ok(None) } - bytecode::Instruction::StoreGlobal(idx) => { - let value = self.pop_value(); - self.globals - .set_item(self.code.names[idx.get(arg) as usize], value, vm)?; + bytecode::Instruction::CallIntrinsic2 { func } => { + let value2 = self.pop_value(); + let value1 = self.pop_value(); + let result = self.call_intrinsic_2(func.get(arg), value1, value2, vm)?; + self.push_value(result); Ok(None) } - bytecode::Instruction::StoreDeref(i) => { - let value = self.pop_value(); - self.cells_frees[i.get(arg) as usize].set(Some(value)); + bytecode::Instruction::CallMethodEx { has_kwargs } => { + let args = self.collect_ex_args(vm, has_kwargs.get(arg))?; + self.execute_method_call(args, vm) + } + bytecode::Instruction::CallMethodKeyword { nargs } => { + let args = self.collect_keyword_args(nargs.get(arg)); + self.execute_method_call(args, vm) + } + bytecode::Instruction::CallMethodPositional { nargs } => { + let args = self.collect_positional_args(nargs.get(arg)); + self.execute_method_call(args, vm) + } + bytecode::Instruction::CompareOperation { op } => self.execute_compare(vm, op.get(arg)), + bytecode::Instruction::ContainsOp(invert) => { + let b = self.pop_value(); + let a = self.pop_value(); + + let value = match invert.get(arg) { + bytecode::Invert::No => self._in(vm, &a, &b)?, + bytecode::Invert::Yes => self._not_in(vm, &a, &b)?, + }; + self.push_value(vm.ctx.new_bool(value).into()); + Ok(None) + } + bytecode::Instruction::Continue { target } => self.unwind_blocks( + vm, + UnwindReason::Continue { + target: target.get(arg), + }, + ), + bytecode::Instruction::CopyItem { index } => { + // CopyItem { index: 1 } copies TOS + // CopyItem { index: 2 } copies second from top + // This is 1-indexed to match CPython + let idx = index.get(arg) as usize; + let value = self + .state + .stack + .len() + .checked_sub(idx) + .map(|i| &self.state.stack[i]) + .unwrap(); + self.push_value(value.clone()); + Ok(None) + } + bytecode::Instruction::DeleteAttr { idx } => self.delete_attr(vm, idx.get(arg)), + bytecode::Instruction::DeleteDeref(i) => { + self.cells_frees[i.get(arg) as usize].set(None); Ok(None) } bytecode::Instruction::DeleteFast(idx) => { @@ -646,11 +744,9 @@ impl ExecutingFrame<'_> { fastlocals[idx] = None; Ok(None) } - bytecode::Instruction::DeleteLocal(idx) => { + bytecode::Instruction::DeleteGlobal(idx) => { let name = self.code.names[idx.get(arg) as usize]; - let res = self.locals.mapping().ass_subscript(name, None, vm); - - match res { + match self.globals.del_item(name, vm) { Ok(()) => {} Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => { return Err(name_error(name, vm)); @@ -659,9 +755,11 @@ impl ExecutingFrame<'_> { } Ok(None) } - bytecode::Instruction::DeleteGlobal(idx) => { + bytecode::Instruction::DeleteLocal(idx) => { let name = self.code.names[idx.get(arg) as usize]; - match self.globals.del_item(name, vm) { + let res = self.locals.mapping().ass_subscript(name, None, vm); + + match res { Ok(()) => {} Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => { return Err(name_error(name, vm)); @@ -670,134 +768,7 @@ impl ExecutingFrame<'_> { } Ok(None) } - bytecode::Instruction::DeleteDeref(i) => { - self.cells_frees[i.get(arg) as usize].set(None); - Ok(None) - } - bytecode::Instruction::LoadClosure(i) => { - let value = self.cells_frees[i.get(arg) as usize].clone(); - self.push_value(value.into()); - Ok(None) - } - bytecode::Instruction::Subscript => self.execute_subscript(vm), - bytecode::Instruction::StoreSubscript => self.execute_store_subscript(vm), bytecode::Instruction::DeleteSubscript => self.execute_delete_subscript(vm), - bytecode::Instruction::CopyItem { index } => { - // CopyItem { index: 1 } copies TOS - // CopyItem { index: 2 } copies second from top - // This is 1-indexed to match CPython - let idx = index.get(arg) as usize; - let value = self - .state - .stack - .len() - .checked_sub(idx) - .map(|i| &self.state.stack[i]) - .unwrap(); - self.push_value(value.clone()); - Ok(None) - } - bytecode::Instruction::Pop => { - // Pop value from stack and ignore. - self.pop_value(); - Ok(None) - } - bytecode::Instruction::Swap { index } => { - let len = self.state.stack.len(); - debug_assert!(len > 0, "stack underflow in SWAP"); - let i = len - 1; // TOS index - let index_val = index.get(arg) as usize; - // CPython: SWAP(n) swaps TOS with PEEK(n) where PEEK(n) = stack_pointer[-n] - // This means swap TOS with the element at index (len - n) - debug_assert!( - index_val <= len, - "SWAP index {} exceeds stack size {}", - index_val, - len - ); - let j = len - index_val; - self.state.stack.swap(i, j); - Ok(None) - } - /* - bytecode::Instruction::ToBool => { - dbg!("Shouldn't be called outside of match statements for now") - let value = self.pop_value(); - // call __bool__ - let result = value.try_to_bool(vm)?; - self.push_value(vm.ctx.new_bool(result).into()); - Ok(None) - } - */ - bytecode::Instruction::BuildString { size } => { - let s = self - .pop_multiple(size.get(arg) as usize) - .as_slice() - .iter() - .map(|pyobj| pyobj.downcast_ref::<PyStr>().unwrap()) - .collect::<Wtf8Buf>(); - let str_obj = vm.ctx.new_str(s); - self.push_value(str_obj.into()); - Ok(None) - } - bytecode::Instruction::BuildList { size } => { - let elements = self.pop_multiple(size.get(arg) as usize).collect(); - let list_obj = vm.ctx.new_list(elements); - self.push_value(list_obj.into()); - Ok(None) - } - bytecode::Instruction::BuildListFromTuples { size } => { - // SAFETY: compiler guarantees `size` tuples are on the stack - let elements = unsafe { self.flatten_tuples(size.get(arg) as usize) }; - let list_obj = vm.ctx.new_list(elements); - self.push_value(list_obj.into()); - Ok(None) - } - bytecode::Instruction::BuildSet { size } => { - let set = PySet::default().into_ref(&vm.ctx); - for element in self.pop_multiple(size.get(arg) as usize) { - set.add(element, vm)?; - } - self.push_value(set.into()); - Ok(None) - } - bytecode::Instruction::BuildSetFromTuples { size } => { - let set = PySet::default().into_ref(&vm.ctx); - for element in self.pop_multiple(size.get(arg) as usize) { - // SAFETY: trust compiler - let tup = unsafe { element.downcast_unchecked::<PyTuple>() }; - for item in tup.iter() { - set.add(item.clone(), vm)?; - } - } - self.push_value(set.into()); - Ok(None) - } - bytecode::Instruction::BuildTuple { size } => { - let elements = self.pop_multiple(size.get(arg) as usize).collect(); - let list_obj = vm.ctx.new_tuple(elements); - self.push_value(list_obj.into()); - Ok(None) - } - bytecode::Instruction::BuildTupleFromTuples { size } => { - // SAFETY: compiler guarantees `size` tuples are on the stack - let elements = unsafe { self.flatten_tuples(size.get(arg) as usize) }; - let list_obj = vm.ctx.new_tuple(elements); - self.push_value(list_obj.into()); - Ok(None) - } - bytecode::Instruction::BuildTupleFromIter => { - if !self.top_value().class().is(vm.ctx.types.tuple_type) { - let elements: Vec<_> = self.pop_value().try_to_value(vm)?; - let list_obj = vm.ctx.new_tuple(elements); - self.push_value(list_obj.into()); - } - Ok(None) - } - bytecode::Instruction::BuildMap { size } => self.execute_build_map(vm, size.get(arg)), - bytecode::Instruction::BuildMapForCall { size } => { - self.execute_build_map_for_call(vm, size.get(arg)) - } bytecode::Instruction::DictUpdate { index } => { // Stack before: [..., dict, ..., source] (source at TOS) // Stack after: [..., dict, ...] (source consumed) @@ -834,76 +805,163 @@ impl ExecutingFrame<'_> { dict.merge_object(source, vm)?; Ok(None) } - bytecode::Instruction::BuildSlice { argc } => { - self.execute_build_slice(vm, argc.get(arg)) + bytecode::Instruction::EndAsyncFor => { + let exc = self.pop_value(); + let except_block = self.pop_block(); // pushed by TryExcept unwind + debug_assert_eq!(except_block.level, self.state.stack.len()); + let _async_iterator = self.pop_value(); // __anext__ provider in the loop + if exc.fast_isinstance(vm.ctx.exceptions.stop_async_iteration) { + vm.take_exception().expect("Should have exception in stack"); + Ok(None) + } else { + Err(exc.downcast().unwrap()) + } } - bytecode::Instruction::ListAppend { i } => { - let item = self.pop_value(); - let obj = self.nth_value(i.get(arg)); - let list: &Py<PyList> = unsafe { - // SAFETY: trust compiler - obj.downcast_unchecked_ref() - }; - list.append(item); - Ok(None) + bytecode::Instruction::EndFinally => { + // Pop the finally handler from the stack, and recall + // what was the reason we were in this finally clause. + let block = self.pop_block(); + + if let BlockType::FinallyHandler { reason, prev_exc } = block.typ { + vm.set_exception(prev_exc); + if let Some(reason) = reason { + self.unwind_blocks(vm, reason) + } else { + Ok(None) + } + } else { + self.fatal( + "Block type must be finally handler when reaching EndFinally instruction!", + ); + } } - bytecode::Instruction::SetAdd { i } => { - let item = self.pop_value(); - let obj = self.nth_value(i.get(arg)); - let set: &Py<PySet> = unsafe { - // SAFETY: trust compiler - obj.downcast_unchecked_ref() - }; - set.add(item, vm)?; + bytecode::Instruction::EnterFinally => { + self.push_block(BlockType::FinallyHandler { + reason: None, + prev_exc: vm.current_exception(), + }); Ok(None) } - bytecode::Instruction::MapAdd { i } => { - let value = self.pop_value(); - let key = self.pop_value(); - let obj = self.nth_value(i.get(arg)); - let dict: &Py<PyDict> = unsafe { - // SAFETY: trust compiler - obj.downcast_unchecked_ref() - }; - dict.set_item(&*key, value, vm)?; + bytecode::Instruction::ExtendedArg => { + *extend_arg = true; Ok(None) } - bytecode::Instruction::BinaryOp { op } => self.execute_bin_op(vm, op.get(arg)), - bytecode::Instruction::BinarySubscript => { - let key = self.pop_value(); - let container = self.pop_value(); - self.state - .stack - .push(container.get_item(key.as_object(), vm)?); + bytecode::Instruction::ForIter { target } => self.execute_for_iter(vm, target.get(arg)), + bytecode::Instruction::FormatValue { conversion } => { + self.format_value(conversion.get(arg), vm) + } + bytecode::Instruction::GetAIter => { + let aiterable = self.pop_value(); + let aiter = vm.call_special_method(&aiterable, identifier!(vm, __aiter__), ())?; + self.push_value(aiter); Ok(None) } - bytecode::Instruction::LoadAttr { idx } => self.load_attr(vm, idx.get(arg)), - bytecode::Instruction::StoreAttr { idx } => self.store_attr(vm, idx.get(arg)), - bytecode::Instruction::DeleteAttr { idx } => self.delete_attr(vm, idx.get(arg)), - bytecode::Instruction::UnaryOperation { op } => self.execute_unary_op(vm, op.get(arg)), - bytecode::Instruction::IsOp(invert) => { - let b = self.pop_value(); - let a = self.pop_value(); - let res = a.is(&b); + bytecode::Instruction::GetANext => { + #[cfg(debug_assertions)] // remove when GetANext is fully implemented + let orig_stack_len = self.state.stack.len(); - let value = match invert.get(arg) { - bytecode::Invert::No => res, - bytecode::Invert::Yes => !res, + let aiter = self.top_value(); + let awaitable = if aiter.class().is(vm.ctx.types.async_generator) { + vm.call_special_method(aiter, identifier!(vm, __anext__), ())? + } else { + if !aiter.has_attr("__anext__", vm).unwrap_or(false) { + // TODO: __anext__ must be protocol + let msg = format!( + "'async for' requires an iterator with __anext__ method, got {:.100}", + aiter.class().name() + ); + return Err(vm.new_type_error(msg)); + } + let next_iter = + vm.call_special_method(aiter, identifier!(vm, __anext__), ())?; + + // _PyCoro_GetAwaitableIter in CPython + fn get_awaitable_iter(next_iter: &PyObject, vm: &VirtualMachine) -> PyResult { + let gen_is_coroutine = |_| { + // TODO: cpython gen_is_coroutine + true + }; + if next_iter.class().is(vm.ctx.types.coroutine_type) + || gen_is_coroutine(next_iter) + { + return Ok(next_iter.to_owned()); + } + // TODO: error handling + vm.call_special_method(next_iter, identifier!(vm, __await__), ()) + } + get_awaitable_iter(&next_iter, vm).map_err(|_| { + vm.new_type_error(format!( + "'async for' received an invalid object from __anext__: {:.200}", + next_iter.class().name() + )) + })? }; - self.push_value(vm.ctx.new_bool(value).into()); + self.push_value(awaitable); + #[cfg(debug_assertions)] + debug_assert_eq!(orig_stack_len + 1, self.state.stack.len()); Ok(None) } - bytecode::Instruction::ContainsOp(invert) => { - let b = self.pop_value(); + bytecode::Instruction::GetAwaitable => { + let awaited_obj = self.pop_value(); + let awaitable = if awaited_obj.downcastable::<PyCoroutine>() { + awaited_obj + } else { + let await_method = vm.get_method_or_type_error( + awaited_obj.clone(), + identifier!(vm, __await__), + || { + format!( + "object {} can't be used in 'await' expression", + awaited_obj.class().name(), + ) + }, + )?; + await_method.call((), vm)? + }; + self.push_value(awaitable); + Ok(None) + } + bytecode::Instruction::GetIter => { + let iterated_obj = self.pop_value(); + let iter_obj = iterated_obj.get_iter(vm)?; + self.push_value(iter_obj.into()); + Ok(None) + } + bytecode::Instruction::GetLen => { + // STACK.append(len(STACK[-1])) + let obj = self.top_value(); + let len = obj.length(vm)?; + self.push_value(vm.ctx.new_int(len).into()); + Ok(None) + } + bytecode::Instruction::ImportFrom { idx } => { + let obj = self.import_from(vm, idx.get(arg))?; + self.push_value(obj); + Ok(None) + } + bytecode::Instruction::ImportNameless => { + self.import(vm, None)?; + Ok(None) + } + bytecode::Instruction::ImportName { idx } => { + self.import(vm, Some(self.code.names[idx.get(arg) as usize]))?; + Ok(None) + } + bytecode::Instruction::IsOp(invert) => { + let b = self.pop_value(); let a = self.pop_value(); + let res = a.is(&b); let value = match invert.get(arg) { - bytecode::Invert::No => self._in(vm, &a, &b)?, - bytecode::Invert::Yes => self._not_in(vm, &a, &b)?, + bytecode::Invert::No => res, + bytecode::Invert::Yes => !res, }; self.push_value(vm.ctx.new_bool(value).into()); Ok(None) } + bytecode::Instruction::JumpIfFalseOrPop { target } => { + self.jump_if_or_pop(vm, target.get(arg), false) + } bytecode::Instruction::JumpIfNotExcMatch(target) => { let b = self.pop_value(); let a = self.pop_value(); @@ -927,619 +985,561 @@ impl ExecutingFrame<'_> { self.push_value(vm.ctx.new_bool(value).into()); self.pop_jump_if(vm, target.get(arg), false) } - bytecode::Instruction::CompareOperation { op } => self.execute_compare(vm, op.get(arg)), - bytecode::Instruction::ReturnValue => { - let value = self.pop_value(); - self.unwind_blocks(vm, UnwindReason::Returning { value }) + bytecode::Instruction::JumpIfTrueOrPop { target } => { + self.jump_if_or_pop(vm, target.get(arg), true) } - bytecode::Instruction::ReturnConst { idx } => { - let value = self.code.constants[idx.get(arg) as usize].clone().into(); - self.unwind_blocks(vm, UnwindReason::Returning { value }) + bytecode::Instruction::Jump { target } => { + self.jump(target.get(arg)); + Ok(None) } - bytecode::Instruction::YieldValue => { - let value = self.pop_value(); - let value = if self.code.flags.contains(bytecode::CodeFlags::IS_COROUTINE) { - PyAsyncGenWrappedValue(value).into_pyobject(vm) - } else { - value + bytecode::Instruction::ListAppend { i } => { + let item = self.pop_value(); + let obj = self.nth_value(i.get(arg)); + let list: &Py<PyList> = unsafe { + // SAFETY: trust compiler + obj.downcast_unchecked_ref() }; - Ok(Some(ExecutionResult::Yield(value))) - } - bytecode::Instruction::YieldFrom => self.execute_yield_from(vm), - bytecode::Instruction::Resume { arg: resume_arg } => { - // Resume execution after yield, await, or at function start - // In CPython, this checks instrumentation and eval breaker - // For now, we just check for signals/interrupts - let _resume_type = resume_arg.get(arg); - - // Check for interrupts if not resuming from yield_from - // if resume_type < bytecode::ResumeType::AfterYieldFrom as u32 { - // vm.check_signals()?; - // } + list.append(item); Ok(None) } - bytecode::Instruction::SetupAnnotation => self.setup_annotations(vm), - bytecode::Instruction::SetupLoop => { - self.push_block(BlockType::Loop); + bytecode::Instruction::LoadAttr { idx } => self.load_attr(vm, idx.get(arg)), + bytecode::Instruction::LoadBuildClass => { + self.push_value(vm.builtins.get_attr(identifier!(vm, __build_class__), vm)?); Ok(None) } - bytecode::Instruction::SetupExcept { handler } => { - self.push_block(BlockType::TryExcept { - handler: handler.get(arg), + bytecode::Instruction::LoadClassDeref(i) => { + let i = i.get(arg) as usize; + let name = if i < self.code.cellvars.len() { + self.code.cellvars[i] + } else { + self.code.freevars[i - self.code.cellvars.len()] + }; + let value = self.locals.mapping().subscript(name, vm).ok(); + self.push_value(match value { + Some(v) => v, + None => self.cells_frees[i] + .get() + .ok_or_else(|| self.unbound_cell_exception(i, vm))?, }); Ok(None) } - bytecode::Instruction::SetupFinally { handler } => { - self.push_block(BlockType::Finally { - handler: handler.get(arg), - }); + bytecode::Instruction::LoadClosure(i) => { + let value = self.cells_frees[i.get(arg) as usize].clone(); + self.push_value(value.into()); Ok(None) } - bytecode::Instruction::EnterFinally => { - self.push_block(BlockType::FinallyHandler { - reason: None, - prev_exc: vm.current_exception(), - }); + bytecode::Instruction::LoadConst { idx } => { + self.push_value(self.code.constants[idx.get(arg) as usize].clone().into()); Ok(None) } - bytecode::Instruction::EndFinally => { - // Pop the finally handler from the stack, and recall - // what was the reason we were in this finally clause. - let block = self.pop_block(); - - if let BlockType::FinallyHandler { reason, prev_exc } = block.typ { - vm.set_exception(prev_exc); - if let Some(reason) = reason { - self.unwind_blocks(vm, reason) - } else { - Ok(None) - } - } else { - self.fatal( - "Block type must be finally handler when reaching EndFinally instruction!", - ); - } + bytecode::Instruction::LoadDeref(i) => { + let i = i.get(arg) as usize; + let x = self.cells_frees[i] + .get() + .ok_or_else(|| self.unbound_cell_exception(i, vm))?; + self.push_value(x); + Ok(None) } - bytecode::Instruction::SetupWith { end } => { - let context_manager = self.pop_value(); - let error_string = || -> String { - format!( - "'{:.200}' object does not support the context manager protocol", - context_manager.class().name(), + bytecode::Instruction::LoadFast(idx) => { + #[cold] + fn reference_error( + varname: &'static PyStrInterned, + vm: &VirtualMachine, + ) -> PyBaseExceptionRef { + vm.new_exception_msg( + vm.ctx.exceptions.unbound_local_error.to_owned(), + format!("local variable '{varname}' referenced before assignment",), ) - }; - let enter_res = vm - .get_special_method(&context_manager, identifier!(vm, __enter__))? - .ok_or_else(|| vm.new_type_error(error_string()))? - .invoke((), vm)?; - - let exit = context_manager - .get_attr(identifier!(vm, __exit__), vm) - .map_err(|_exc| { - vm.new_type_error({ - format!("{} (missed __exit__ method)", error_string()) - }) - })?; - self.push_value(exit); - self.push_block(BlockType::Finally { - handler: end.get(arg), - }); - self.push_value(enter_res); + } + let idx = idx.get(arg) as usize; + let x = self.fastlocals.lock()[idx] + .clone() + .ok_or_else(|| reference_error(self.code.varnames[idx], vm))?; + self.push_value(x); Ok(None) } - bytecode::Instruction::BeforeAsyncWith => { - let mgr = self.pop_value(); - let error_string = || -> String { - format!( - "'{:.200}' object does not support the asynchronous context manager protocol", - mgr.class().name(), - ) + bytecode::Instruction::LoadGlobal(idx) => { + let name = &self.code.names[idx.get(arg) as usize]; + let x = self.load_global_or_builtin(name, vm)?; + self.push_value(x); + Ok(None) + } + bytecode::Instruction::LoadMethod { idx } => { + let obj = self.pop_value(); + let method_name = self.code.names[idx.get(arg) as usize]; + let method = PyMethod::get(obj, method_name, vm)?; + let (target, is_method, func) = match method { + PyMethod::Function { target, func } => (target, true, func), + PyMethod::Attribute(val) => (vm.ctx.none(), false, val), }; - - let aenter_res = vm - .get_special_method(&mgr, identifier!(vm, __aenter__))? - .ok_or_else(|| vm.new_type_error(error_string()))? - .invoke((), vm)?; - let aexit = mgr - .get_attr(identifier!(vm, __aexit__), vm) - .map_err(|_exc| { - vm.new_type_error({ - format!("{} (missed __aexit__ method)", error_string()) - }) - })?; - self.push_value(aexit); - self.push_value(aenter_res); - + // TODO: figure out a better way to communicate PyMethod::Attribute - CPython uses + // target==NULL, maybe we could use a sentinel value or something? + self.push_value(target); + self.push_value(vm.ctx.new_bool(is_method).into()); + self.push_value(func); Ok(None) } - bytecode::Instruction::SetupAsyncWith { end } => { - let enter_res = self.pop_value(); - self.push_block(BlockType::Finally { - handler: end.get(arg), - }); - self.push_value(enter_res); + bytecode::Instruction::LoadNameAny(idx) => { + let name = self.code.names[idx.get(arg) as usize]; + let result = self.locals.mapping().subscript(name, vm); + match result { + Ok(x) => self.push_value(x), + Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => { + self.push_value(self.load_global_or_builtin(name, vm)?); + } + Err(e) => return Err(e), + } Ok(None) } - bytecode::Instruction::WithCleanupStart => { - let block = self.current_block().unwrap(); - let reason = match block.typ { - BlockType::FinallyHandler { reason, .. } => reason, - _ => self.fatal("WithCleanupStart expects a FinallyHandler block on stack"), - }; - let exc = match reason { - Some(UnwindReason::Raising { exception }) => Some(exception), - _ => None, + bytecode::Instruction::MakeFunction => self.execute_make_function(vm), + bytecode::Instruction::MapAdd { i } => { + let value = self.pop_value(); + let key = self.pop_value(); + let obj = self.nth_value(i.get(arg)); + let dict: &Py<PyDict> = unsafe { + // SAFETY: trust compiler + obj.downcast_unchecked_ref() }; + dict.set_item(&*key, value, vm)?; + Ok(None) + } + bytecode::Instruction::MatchClass(nargs) => { + // STACK[-1] is a tuple of keyword attribute names, STACK[-2] is the class being matched against, and STACK[-3] is the match subject. + // nargs is the number of positional sub-patterns. + let kwd_attrs = self.pop_value(); + let kwd_attrs = kwd_attrs.downcast_ref::<PyTuple>().unwrap(); + let cls = self.pop_value(); + let subject = self.pop_value(); + let nargs_val = nargs.get(arg) as usize; - let exit = self.top_value(); + // Check if subject is an instance of cls + if subject.is_instance(cls.as_ref(), vm)? { + let mut extracted = vec![]; - let args = if let Some(exc) = exc { - vm.split_exception(exc) - } else { - (vm.ctx.none(), vm.ctx.none(), vm.ctx.none()) - }; - let exit_res = exit.call(args, vm)?; - self.replace_top(exit_res); + // Get __match_args__ for positional arguments if nargs > 0 + if nargs_val > 0 { + // Get __match_args__ from the class + let match_args = + vm.get_attribute_opt(cls.clone(), identifier!(vm, __match_args__))?; + + if let Some(match_args) = match_args { + // Convert to tuple + let match_args = match match_args.downcast_exact::<PyTuple>(vm) { + Ok(tuple) => tuple, + Err(match_args) => { + // __match_args__ must be a tuple + // Get type names for error message + let type_name = cls + .downcast::<crate::builtins::PyType>() + .map(|t| t.__name__(vm).as_str().to_owned()) + .unwrap_or_else(|_| String::from("?")); + let match_args_type_name = match_args.class().__name__(vm); + return Err(vm.new_type_error(format!( + "{}.__match_args__ must be a tuple (got {})", + type_name, match_args_type_name + ))); + } + }; + + // Check if we have enough match args + if match_args.len() < nargs_val { + return Err(vm.new_type_error(format!( + "class pattern accepts at most {} positional sub-patterns ({} given)", + match_args.len(), + nargs_val + ))); + } + + // Extract positional attributes + for i in 0..nargs_val { + let attr_name = &match_args[i]; + let attr_name_str = match attr_name.downcast_ref::<PyStr>() { + Some(s) => s, + None => { + return Err(vm.new_type_error( + "__match_args__ elements must be strings".to_string(), + )); + } + }; + match subject.get_attr(attr_name_str, vm) { + Ok(value) => extracted.push(value), + Err(e) + if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => + { + // Missing attribute → non-match + self.push_value(vm.ctx.none()); + return Ok(None); + } + Err(e) => return Err(e), + } + } + } else { + // No __match_args__, check if this is a type with MATCH_SELF behavior + // For built-in types like bool, int, str, list, tuple, dict, etc. + // they match the subject itself as the single positional argument + let is_match_self_type = cls + .downcast::<PyType>() + .is_ok_and(|t| t.slots.flags.contains(PyTypeFlags::_MATCH_SELF)); + + if is_match_self_type { + if nargs_val == 1 { + // Match the subject itself as the single positional argument + extracted.push(subject.clone()); + } else if nargs_val > 1 { + // Too many positional arguments for MATCH_SELF + return Err(vm.new_type_error( + "class pattern accepts at most 1 positional sub-pattern for MATCH_SELF types" + .to_string(), + )); + } + } else { + // No __match_args__ and not a MATCH_SELF type + if nargs_val > 0 { + return Err(vm.new_type_error( + "class pattern defines no positional sub-patterns (__match_args__ missing)" + .to_string(), + )); + } + } + } + } + + // Extract keyword attributes + for name in kwd_attrs { + let name_str = name.downcast_ref::<PyStr>().unwrap(); + match subject.get_attr(name_str, vm) { + Ok(value) => extracted.push(value), + Err(e) if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => { + self.push_value(vm.ctx.none()); + return Ok(None); + } + Err(e) => return Err(e), + } + } + self.push_value(vm.ctx.new_tuple(extracted).into()); + } else { + // Not an instance, push None + self.push_value(vm.ctx.none()); + } Ok(None) } - bytecode::Instruction::WithCleanupFinish => { - let block = self.pop_block(); - let (reason, prev_exc) = match block.typ { - BlockType::FinallyHandler { reason, prev_exc } => (reason, prev_exc), - _ => self.fatal("WithCleanupFinish expects a FinallyHandler block on stack"), - }; + bytecode::Instruction::MatchKeys => { + // MATCH_KEYS doesn't pop subject and keys, only reads them + let keys_tuple = self.top_value(); // stack[-1] + let subject = self.nth_value(1); // stack[-2] - vm.set_exception(prev_exc); + // Check if subject is a mapping and extract values for keys + if subject.class().slots.flags.contains(PyTypeFlags::MAPPING) { + let keys = keys_tuple.downcast_ref::<PyTuple>().unwrap(); + let mut values = Vec::new(); + let mut all_match = true; - let suppress_exception = self.pop_value().try_to_bool(vm)?; + // We use the two argument form of map.get(key, default) for two reasons: + // - Atomically check for a key and get its value without error handling. + // - Don't cause key creation or resizing in dict subclasses like + // collections.defaultdict that define __missing__ (or similar). + // See CPython's _PyEval_MatchKeys - if suppress_exception { - Ok(None) - } else if let Some(reason) = reason { - self.unwind_blocks(vm, reason) + if let Some(get_method) = vm + .get_method(subject.to_owned(), vm.ctx.intern_str("get")) + .transpose()? + { + // dummy = object() + // CPython: dummy = _PyObject_CallNoArgs((PyObject *)&PyBaseObject_Type); + let dummy = vm + .ctx + .new_base_object(vm.ctx.types.object_type.to_owned(), None); + + for key in keys { + // value = map.get(key, dummy) + match get_method.call((key.as_object(), dummy.clone()), vm) { + Ok(value) => { + // if value == dummy: key not in map! + if value.is(&dummy) { + all_match = false; + break; + } + values.push(value); + } + Err(e) => return Err(e), + } + } + } else { + // Fallback if .get() method is not available (shouldn't happen for mappings) + for key in keys { + match subject.get_item(key.as_object(), vm) { + Ok(value) => values.push(value), + Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => { + all_match = false; + break; + } + Err(e) => return Err(e), + } + } + } + + if all_match { + // Push values tuple on successful match + self.push_value(vm.ctx.new_tuple(values).into()); + } else { + // No match - push None + self.push_value(vm.ctx.none()); + } } else { - Ok(None) + // Not a mapping - push None + self.push_value(vm.ctx.none()); } - } - bytecode::Instruction::PopBlock => { - self.pop_block(); Ok(None) } - bytecode::Instruction::GetIter => { - let iterated_obj = self.pop_value(); - let iter_obj = iterated_obj.get_iter(vm)?; - self.push_value(iter_obj.into()); + bytecode::Instruction::MatchMapping => { + // Pop and push back the subject to keep it on stack + let subject = self.pop_value(); + + // Check if the type has the MAPPING flag + let is_mapping = subject.class().slots.flags.contains(PyTypeFlags::MAPPING); + + self.push_value(subject); + self.push_value(vm.ctx.new_bool(is_mapping).into()); Ok(None) } - bytecode::Instruction::GetLen => { - // STACK.append(len(STACK[-1])) - let obj = self.top_value(); - let len = obj.length(vm)?; - self.push_value(vm.ctx.new_int(len).into()); + bytecode::Instruction::MatchSequence => { + // Pop and push back the subject to keep it on stack + let subject = self.pop_value(); + + // Check if the type has the SEQUENCE flag + let is_sequence = subject.class().slots.flags.contains(PyTypeFlags::SEQUENCE); + + self.push_value(subject); + self.push_value(vm.ctx.new_bool(is_sequence).into()); Ok(None) } - bytecode::Instruction::ToBool => { - let obj = self.pop_value(); - let bool_val = obj.try_to_bool(vm)?; - self.push_value(vm.ctx.new_bool(bool_val).into()); + bytecode::Instruction::Nop => Ok(None), + bytecode::Instruction::Pop => { + // Pop value from stack and ignore. + self.pop_value(); Ok(None) } - bytecode::Instruction::CallIntrinsic1 { func } => { - let value = self.pop_value(); - let result = self.call_intrinsic_1(func.get(arg), value, vm)?; - self.push_value(result); + bytecode::Instruction::PopBlock => { + self.pop_block(); Ok(None) } - bytecode::Instruction::CallIntrinsic2 { func } => { - let value2 = self.pop_value(); - let value1 = self.pop_value(); - let result = self.call_intrinsic_2(func.get(arg), value1, value2, vm)?; - self.push_value(result); - Ok(None) - } - bytecode::Instruction::GetAwaitable => { - let awaited_obj = self.pop_value(); - let awaitable = if awaited_obj.downcastable::<PyCoroutine>() { - awaited_obj - } else { - let await_method = vm.get_method_or_type_error( - awaited_obj.clone(), - identifier!(vm, __await__), - || { - format!( - "object {} can't be used in 'await' expression", - awaited_obj.class().name(), - ) - }, - )?; - await_method.call((), vm)? - }; - self.push_value(awaitable); - Ok(None) - } - bytecode::Instruction::GetAIter => { - let aiterable = self.pop_value(); - let aiter = vm.call_special_method(&aiterable, identifier!(vm, __aiter__), ())?; - self.push_value(aiter); - Ok(None) - } - bytecode::Instruction::GetANext => { - #[cfg(debug_assertions)] // remove when GetANext is fully implemented - let orig_stack_len = self.state.stack.len(); - - let aiter = self.top_value(); - let awaitable = if aiter.class().is(vm.ctx.types.async_generator) { - vm.call_special_method(aiter, identifier!(vm, __anext__), ())? - } else { - if !aiter.has_attr("__anext__", vm).unwrap_or(false) { - // TODO: __anext__ must be protocol - let msg = format!( - "'async for' requires an iterator with __anext__ method, got {:.100}", - aiter.class().name() - ); - return Err(vm.new_type_error(msg)); - } - let next_iter = - vm.call_special_method(aiter, identifier!(vm, __anext__), ())?; - - // _PyCoro_GetAwaitableIter in CPython - fn get_awaitable_iter(next_iter: &PyObject, vm: &VirtualMachine) -> PyResult { - let gen_is_coroutine = |_| { - // TODO: cpython gen_is_coroutine - true - }; - if next_iter.class().is(vm.ctx.types.coroutine_type) - || gen_is_coroutine(next_iter) - { - return Ok(next_iter.to_owned()); - } - // TODO: error handling - vm.call_special_method(next_iter, identifier!(vm, __await__), ()) - } - get_awaitable_iter(&next_iter, vm).map_err(|_| { - vm.new_type_error(format!( - "'async for' received an invalid object from __anext__: {:.200}", - next_iter.class().name() - )) - })? - }; - self.push_value(awaitable); - #[cfg(debug_assertions)] - debug_assert_eq!(orig_stack_len + 1, self.state.stack.len()); - Ok(None) - } - bytecode::Instruction::EndAsyncFor => { - let exc = self.pop_value(); - let except_block = self.pop_block(); // pushed by TryExcept unwind - debug_assert_eq!(except_block.level, self.state.stack.len()); - let _async_iterator = self.pop_value(); // __anext__ provider in the loop - if exc.fast_isinstance(vm.ctx.exceptions.stop_async_iteration) { - vm.take_exception().expect("Should have exception in stack"); + bytecode::Instruction::PopException => { + let block = self.pop_block(); + if let BlockType::ExceptHandler { prev_exc } = block.typ { + vm.set_exception(prev_exc); Ok(None) } else { - Err(exc.downcast().unwrap()) + self.fatal("block type must be ExceptHandler here.") } } - bytecode::Instruction::ForIter { target } => self.execute_for_iter(vm, target.get(arg)), - bytecode::Instruction::MakeFunction => self.execute_make_function(vm), - bytecode::Instruction::SetFunctionAttribute { attr } => { - self.execute_set_function_attribute(vm, attr.get(arg)) - } - bytecode::Instruction::CallFunctionPositional { nargs } => { - let args = self.collect_positional_args(nargs.get(arg)); - self.execute_call(args, vm) - } - bytecode::Instruction::CallFunctionKeyword { nargs } => { - let args = self.collect_keyword_args(nargs.get(arg)); - self.execute_call(args, vm) - } - bytecode::Instruction::CallFunctionEx { has_kwargs } => { - let args = self.collect_ex_args(vm, has_kwargs.get(arg))?; - self.execute_call(args, vm) - } - bytecode::Instruction::LoadMethod { idx } => { - let obj = self.pop_value(); - let method_name = self.code.names[idx.get(arg) as usize]; - let method = PyMethod::get(obj, method_name, vm)?; - let (target, is_method, func) = match method { - PyMethod::Function { target, func } => (target, true, func), - PyMethod::Attribute(val) => (vm.ctx.none(), false, val), - }; - // TODO: figure out a better way to communicate PyMethod::Attribute - CPython uses - // target==NULL, maybe we could use a sentinel value or something? - self.push_value(target); - self.push_value(vm.ctx.new_bool(is_method).into()); - self.push_value(func); - Ok(None) - } - bytecode::Instruction::CallMethodPositional { nargs } => { - let args = self.collect_positional_args(nargs.get(arg)); - self.execute_method_call(args, vm) - } - bytecode::Instruction::CallMethodKeyword { nargs } => { - let args = self.collect_keyword_args(nargs.get(arg)); - self.execute_method_call(args, vm) - } - bytecode::Instruction::CallMethodEx { has_kwargs } => { - let args = self.collect_ex_args(vm, has_kwargs.get(arg))?; - self.execute_method_call(args, vm) - } - bytecode::Instruction::Jump { target } => { - self.jump(target.get(arg)); - Ok(None) - } - bytecode::Instruction::PopJumpIfTrue { target } => { - self.pop_jump_if(vm, target.get(arg), true) - } bytecode::Instruction::PopJumpIfFalse { target } => { self.pop_jump_if(vm, target.get(arg), false) } - bytecode::Instruction::JumpIfTrueOrPop { target } => { - self.jump_if_or_pop(vm, target.get(arg), true) - } - bytecode::Instruction::JumpIfFalseOrPop { target } => { - self.jump_if_or_pop(vm, target.get(arg), false) + bytecode::Instruction::PopJumpIfTrue { target } => { + self.pop_jump_if(vm, target.get(arg), true) } + bytecode::Instruction::PrintExpr => self.print_expr(vm), bytecode::Instruction::Raise { kind } => self.execute_raise(vm, kind.get(arg)), + bytecode::Instruction::Resume { arg: resume_arg } => { + // Resume execution after yield, await, or at function start + // In CPython, this checks instrumentation and eval breaker + // For now, we just check for signals/interrupts + let _resume_type = resume_arg.get(arg); - bytecode::Instruction::Break { target } => self.unwind_blocks( - vm, - UnwindReason::Break { - target: target.get(arg), - }, - ), - bytecode::Instruction::Continue { target } => self.unwind_blocks( - vm, - UnwindReason::Continue { - target: target.get(arg), - }, - ), - bytecode::Instruction::PrintExpr => self.print_expr(vm), - bytecode::Instruction::LoadBuildClass => { - self.push_value(vm.builtins.get_attr(identifier!(vm, __build_class__), vm)?); + // Check for interrupts if not resuming from yield_from + // if resume_type < bytecode::ResumeType::AfterYieldFrom as u32 { + // vm.check_signals()?; + // } Ok(None) } - bytecode::Instruction::UnpackSequence { size } => { - self.unpack_sequence(size.get(arg), vm) - } - bytecode::Instruction::UnpackEx { args } => { - let args = args.get(arg); - self.execute_unpack_ex(vm, args.before, args.after) - } - bytecode::Instruction::FormatValue { conversion } => { - self.format_value(conversion.get(arg), vm) + bytecode::Instruction::ReturnConst { idx } => { + let value = self.code.constants[idx.get(arg) as usize].clone().into(); + self.unwind_blocks(vm, UnwindReason::Returning { value }) } - bytecode::Instruction::PopException => { - let block = self.pop_block(); - if let BlockType::ExceptHandler { prev_exc } = block.typ { - vm.set_exception(prev_exc); - Ok(None) - } else { - self.fatal("block type must be ExceptHandler here.") - } + bytecode::Instruction::ReturnValue => { + let value = self.pop_value(); + self.unwind_blocks(vm, UnwindReason::Returning { value }) } bytecode::Instruction::Reverse { amount } => { let stack_len = self.state.stack.len(); self.state.stack[stack_len - amount.get(arg) as usize..stack_len].reverse(); Ok(None) } - bytecode::Instruction::ExtendedArg => { - *extend_arg = true; + bytecode::Instruction::SetAdd { i } => { + let item = self.pop_value(); + let obj = self.nth_value(i.get(arg)); + let set: &Py<PySet> = unsafe { + // SAFETY: trust compiler + obj.downcast_unchecked_ref() + }; + set.add(item, vm)?; Ok(None) } - bytecode::Instruction::MatchMapping => { - // Pop and push back the subject to keep it on stack - let subject = self.pop_value(); - - // Check if the type has the MAPPING flag - let is_mapping = subject.class().slots.flags.contains(PyTypeFlags::MAPPING); - - self.push_value(subject); - self.push_value(vm.ctx.new_bool(is_mapping).into()); + bytecode::Instruction::SetFunctionAttribute { attr } => { + self.execute_set_function_attribute(vm, attr.get(arg)) + } + bytecode::Instruction::SetupAnnotation => self.setup_annotations(vm), + bytecode::Instruction::SetupAsyncWith { end } => { + let enter_res = self.pop_value(); + self.push_block(BlockType::Finally { + handler: end.get(arg), + }); + self.push_value(enter_res); Ok(None) } - bytecode::Instruction::MatchSequence => { - // Pop and push back the subject to keep it on stack - let subject = self.pop_value(); - - // Check if the type has the SEQUENCE flag - let is_sequence = subject.class().slots.flags.contains(PyTypeFlags::SEQUENCE); - - self.push_value(subject); - self.push_value(vm.ctx.new_bool(is_sequence).into()); + bytecode::Instruction::SetupExcept { handler } => { + self.push_block(BlockType::TryExcept { + handler: handler.get(arg), + }); Ok(None) } - bytecode::Instruction::MatchKeys => { - // MATCH_KEYS doesn't pop subject and keys, only reads them - let keys_tuple = self.top_value(); // stack[-1] - let subject = self.nth_value(1); // stack[-2] - - // Check if subject is a mapping and extract values for keys - if subject.class().slots.flags.contains(PyTypeFlags::MAPPING) { - let keys = keys_tuple.downcast_ref::<PyTuple>().unwrap(); - let mut values = Vec::new(); - let mut all_match = true; - - // We use the two argument form of map.get(key, default) for two reasons: - // - Atomically check for a key and get its value without error handling. - // - Don't cause key creation or resizing in dict subclasses like - // collections.defaultdict that define __missing__ (or similar). - // See CPython's _PyEval_MatchKeys - - if let Some(get_method) = vm - .get_method(subject.to_owned(), vm.ctx.intern_str("get")) - .transpose()? - { - // dummy = object() - // CPython: dummy = _PyObject_CallNoArgs((PyObject *)&PyBaseObject_Type); - let dummy = vm - .ctx - .new_base_object(vm.ctx.types.object_type.to_owned(), None); - - for key in keys { - // value = map.get(key, dummy) - match get_method.call((key.as_object(), dummy.clone()), vm) { - Ok(value) => { - // if value == dummy: key not in map! - if value.is(&dummy) { - all_match = false; - break; - } - values.push(value); - } - Err(e) => return Err(e), - } - } - } else { - // Fallback if .get() method is not available (shouldn't happen for mappings) - for key in keys { - match subject.get_item(key.as_object(), vm) { - Ok(value) => values.push(value), - Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => { - all_match = false; - break; - } - Err(e) => return Err(e), - } - } - } - - if all_match { - // Push values tuple on successful match - self.push_value(vm.ctx.new_tuple(values).into()); - } else { - // No match - push None - self.push_value(vm.ctx.none()); - } - } else { - // Not a mapping - push None - self.push_value(vm.ctx.none()); - } + bytecode::Instruction::SetupFinally { handler } => { + self.push_block(BlockType::Finally { + handler: handler.get(arg), + }); Ok(None) } - bytecode::Instruction::MatchClass(nargs) => { - // STACK[-1] is a tuple of keyword attribute names, STACK[-2] is the class being matched against, and STACK[-3] is the match subject. - // nargs is the number of positional sub-patterns. - let kwd_attrs = self.pop_value(); - let kwd_attrs = kwd_attrs.downcast_ref::<PyTuple>().unwrap(); - let cls = self.pop_value(); - let subject = self.pop_value(); - let nargs_val = nargs.get(arg) as usize; - - // Check if subject is an instance of cls - if subject.is_instance(cls.as_ref(), vm)? { - let mut extracted = vec![]; - - // Get __match_args__ for positional arguments if nargs > 0 - if nargs_val > 0 { - // Get __match_args__ from the class - let match_args = - vm.get_attribute_opt(cls.clone(), identifier!(vm, __match_args__))?; + bytecode::Instruction::SetupLoop => { + self.push_block(BlockType::Loop); + Ok(None) + } + bytecode::Instruction::SetupWith { end } => { + let context_manager = self.pop_value(); + let error_string = || -> String { + format!( + "'{:.200}' object does not support the context manager protocol", + context_manager.class().name(), + ) + }; + let enter_res = vm + .get_special_method(&context_manager, identifier!(vm, __enter__))? + .ok_or_else(|| vm.new_type_error(error_string()))? + .invoke((), vm)?; - if let Some(match_args) = match_args { - // Convert to tuple - let match_args = match match_args.downcast_exact::<PyTuple>(vm) { - Ok(tuple) => tuple, - Err(match_args) => { - // __match_args__ must be a tuple - // Get type names for error message - let type_name = cls - .downcast::<crate::builtins::PyType>() - .map(|t| t.__name__(vm).as_str().to_owned()) - .unwrap_or_else(|_| String::from("?")); - let match_args_type_name = match_args.class().__name__(vm); - return Err(vm.new_type_error(format!( - "{}.__match_args__ must be a tuple (got {})", - type_name, match_args_type_name - ))); - } - }; + let exit = context_manager + .get_attr(identifier!(vm, __exit__), vm) + .map_err(|_exc| { + vm.new_type_error({ + format!("{} (missed __exit__ method)", error_string()) + }) + })?; + self.push_value(exit); + self.push_block(BlockType::Finally { + handler: end.get(arg), + }); + self.push_value(enter_res); + Ok(None) + } + bytecode::Instruction::StoreAttr { idx } => self.store_attr(vm, idx.get(arg)), + bytecode::Instruction::StoreDeref(i) => { + let value = self.pop_value(); + self.cells_frees[i.get(arg) as usize].set(Some(value)); + Ok(None) + } + bytecode::Instruction::StoreFast(idx) => { + let value = self.pop_value(); + self.fastlocals.lock()[idx.get(arg) as usize] = Some(value); + Ok(None) + } + bytecode::Instruction::StoreGlobal(idx) => { + let value = self.pop_value(); + self.globals + .set_item(self.code.names[idx.get(arg) as usize], value, vm)?; + Ok(None) + } + bytecode::Instruction::StoreLocal(idx) => { + let name = self.code.names[idx.get(arg) as usize]; + let value = self.pop_value(); + self.locals.mapping().ass_subscript(name, Some(value), vm)?; + Ok(None) + } + bytecode::Instruction::StoreSubscript => self.execute_store_subscript(vm), + bytecode::Instruction::Subscript => self.execute_subscript(vm), + bytecode::Instruction::Swap { index } => { + let len = self.state.stack.len(); + debug_assert!(len > 0, "stack underflow in SWAP"); + let i = len - 1; // TOS index + let index_val = index.get(arg) as usize; + // CPython: SWAP(n) swaps TOS with PEEK(n) where PEEK(n) = stack_pointer[-n] + // This means swap TOS with the element at index (len - n) + debug_assert!( + index_val <= len, + "SWAP index {} exceeds stack size {}", + index_val, + len + ); + let j = len - index_val; + self.state.stack.swap(i, j); + Ok(None) + } + bytecode::Instruction::ToBool => { + let obj = self.pop_value(); + let bool_val = obj.try_to_bool(vm)?; + self.push_value(vm.ctx.new_bool(bool_val).into()); + Ok(None) + } + bytecode::Instruction::UnaryOperation { op } => self.execute_unary_op(vm, op.get(arg)), + bytecode::Instruction::UnpackEx { args } => { + let args = args.get(arg); + self.execute_unpack_ex(vm, args.before, args.after) + } + bytecode::Instruction::UnpackSequence { size } => { + self.unpack_sequence(size.get(arg), vm) + } + bytecode::Instruction::WithCleanupFinish => { + let block = self.pop_block(); + let (reason, prev_exc) = match block.typ { + BlockType::FinallyHandler { reason, prev_exc } => (reason, prev_exc), + _ => self.fatal("WithCleanupFinish expects a FinallyHandler block on stack"), + }; - // Check if we have enough match args - if match_args.len() < nargs_val { - return Err(vm.new_type_error(format!( - "class pattern accepts at most {} positional sub-patterns ({} given)", - match_args.len(), - nargs_val - ))); - } + vm.set_exception(prev_exc); - // Extract positional attributes - for i in 0..nargs_val { - let attr_name = &match_args[i]; - let attr_name_str = match attr_name.downcast_ref::<PyStr>() { - Some(s) => s, - None => { - return Err(vm.new_type_error( - "__match_args__ elements must be strings".to_string(), - )); - } - }; - match subject.get_attr(attr_name_str, vm) { - Ok(value) => extracted.push(value), - Err(e) - if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => - { - // Missing attribute → non-match - self.push_value(vm.ctx.none()); - return Ok(None); - } - Err(e) => return Err(e), - } - } - } else { - // No __match_args__, check if this is a type with MATCH_SELF behavior - // For built-in types like bool, int, str, list, tuple, dict, etc. - // they match the subject itself as the single positional argument - let is_match_self_type = cls - .downcast::<PyType>() - .is_ok_and(|t| t.slots.flags.contains(PyTypeFlags::_MATCH_SELF)); + let suppress_exception = self.pop_value().try_to_bool(vm)?; - if is_match_self_type { - if nargs_val == 1 { - // Match the subject itself as the single positional argument - extracted.push(subject.clone()); - } else if nargs_val > 1 { - // Too many positional arguments for MATCH_SELF - return Err(vm.new_type_error( - "class pattern accepts at most 1 positional sub-pattern for MATCH_SELF types" - .to_string(), - )); - } - } else { - // No __match_args__ and not a MATCH_SELF type - if nargs_val > 0 { - return Err(vm.new_type_error( - "class pattern defines no positional sub-patterns (__match_args__ missing)" - .to_string(), - )); - } - } - } - } + if suppress_exception { + Ok(None) + } else if let Some(reason) = reason { + self.unwind_blocks(vm, reason) + } else { + Ok(None) + } + } + bytecode::Instruction::WithCleanupStart => { + let block = self.current_block().unwrap(); + let reason = match block.typ { + BlockType::FinallyHandler { reason, .. } => reason, + _ => self.fatal("WithCleanupStart expects a FinallyHandler block on stack"), + }; + let exc = match reason { + Some(UnwindReason::Raising { exception }) => Some(exception), + _ => None, + }; - // Extract keyword attributes - for name in kwd_attrs { - let name_str = name.downcast_ref::<PyStr>().unwrap(); - match subject.get_attr(name_str, vm) { - Ok(value) => extracted.push(value), - Err(e) if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => { - self.push_value(vm.ctx.none()); - return Ok(None); - } - Err(e) => return Err(e), - } - } + let exit = self.top_value(); - self.push_value(vm.ctx.new_tuple(extracted).into()); + let args = if let Some(exc) = exc { + vm.split_exception(exc) } else { - // Not an instance, push None - self.push_value(vm.ctx.none()); - } + (vm.ctx.none(), vm.ctx.none(), vm.ctx.none()) + }; + let exit_res = exit.call(args, vm)?; + self.replace_top(exit_res); + Ok(None) } + bytecode::Instruction::YieldFrom => self.execute_yield_from(vm), + bytecode::Instruction::YieldValue => { + let value = self.pop_value(); + let value = if self.code.flags.contains(bytecode::CodeFlags::IS_COROUTINE) { + PyAsyncGenWrappedValue(value).into_pyobject(vm) + } else { + value + }; + Ok(Some(ExecutionResult::Yield(value))) + } } } From 26d5307520ee33cf1b8486c766f98436427a2f5f Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 3 Dec 2025 00:05:16 +0200 Subject: [PATCH 394/819] Align `f-string` related bytecodes with 3.13 (#6321) * Align `f-string` related bytecodes with 3.13 * Resolve name collision * Adjust for ruff return value --- crates/codegen/src/compile.rs | 58 ++++--- ...pile__tests__nested_double_async_with.snap | 33 ++-- crates/compiler-core/src/bytecode.rs | 144 +++++++++++++----- crates/vm/src/frame.rs | 38 +++-- crates/vm/src/stdlib/ast/other.rs | 10 +- 5 files changed, 187 insertions(+), 96 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index e8dc269dca8..ead5dda57d0 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -36,7 +36,8 @@ use rustpython_compiler_core::{ Mode, OneIndexed, PositionEncoding, SourceFile, SourceLocation, bytecode::{ self, Arg as OpArgMarker, BinaryOperator, BuildSliceArgCount, CodeObject, - ComparisonOperator, ConstantData, Instruction, Invert, OpArg, OpArgType, UnpackExArgs, + ComparisonOperator, ConstantData, ConvertValueOparg, Instruction, Invert, OpArg, OpArgType, + UnpackExArgs, }, }; use rustpython_wtf8::Wtf8Buf; @@ -5636,7 +5637,12 @@ impl Compiler { } } InterpolatedStringElement::Interpolation(fstring_expr) => { - let mut conversion = fstring_expr.conversion; + let mut conversion = match fstring_expr.conversion { + ConversionFlag::None => ConvertValueOparg::None, + ConversionFlag::Str => ConvertValueOparg::Str, + ConversionFlag::Repr => ConvertValueOparg::Repr, + ConversionFlag::Ascii => ConvertValueOparg::Ascii, + }; if let Some(DebugText { leading, trailing }) = &fstring_expr.debug_text { let range = fstring_expr.expression.range(); @@ -5645,35 +5651,39 @@ impl Compiler { self.emit_load_const(ConstantData::Str { value: text.into() }); element_count += 1; + + // Match CPython behavior: If debug text is present, apply repr conversion. + // if no `format_spec` specified. + // See: https://github.com/python/cpython/blob/f61afca262d3a0aa6a8a501db0b1936c60858e35/Parser/action_helpers.c#L1456 + if matches!( + (conversion, &fstring_expr.format_spec), + (ConvertValueOparg::None, None) + ) { + conversion = ConvertValueOparg::Repr; + } } - match &fstring_expr.format_spec { - None => { - self.emit_load_const(ConstantData::Str { - value: Wtf8Buf::new(), - }); - // Match CPython behavior: If debug text is present, apply repr conversion. - // See: https://github.com/python/cpython/blob/f61afca262d3a0aa6a8a501db0b1936c60858e35/Parser/action_helpers.c#L1456 - if conversion == ConversionFlag::None - && fstring_expr.debug_text.is_some() - { - conversion = ConversionFlag::Repr; - } + self.compile_expression(&fstring_expr.expression)?; + + match conversion { + ConvertValueOparg::None => {} + ConvertValueOparg::Str + | ConvertValueOparg::Repr + | ConvertValueOparg::Ascii => { + emit!(self, Instruction::ConvertValue { oparg: conversion }) } + } + + match &fstring_expr.format_spec { Some(format_spec) => { self.compile_fstring_elements(flags, &format_spec.elements)?; + + emit!(self, Instruction::FormatWithSpec); + } + None => { + emit!(self, Instruction::FormatSimple); } } - - self.compile_expression(&fstring_expr.expression)?; - - let conversion = match conversion { - ConversionFlag::None => bytecode::ConversionFlag::None, - ConversionFlag::Str => bytecode::ConversionFlag::Str, - ConversionFlag::Ascii => bytecode::ConversionFlag::Ascii, - ConversionFlag::Repr => bytecode::ConversionFlag::Repr, - }; - emit!(self, Instruction::FormatValue { conversion }); } } } diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index 3042e35f179..435b73a14de 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap @@ -11,7 +11,7 @@ expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyn 6 CallFunctionPositional(1) 7 BuildTuple (2) 8 GetIter - >> 9 ForIter (72) + >> 9 ForIter (71) 10 StoreLocal (2, stop_exc) 2 11 LoadNameAny (3, self) @@ -21,7 +21,7 @@ expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyn 15 CallFunctionPositional(1) 16 LoadConst (("type")) 17 CallMethodKeyword (1) - 18 SetupWith (69) + 18 SetupWith (68) 19 Pop 3 20 SetupExcept (42) @@ -65,23 +65,22 @@ expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyn 53 LoadConst (None) 54 StoreLocal (8, ex) 55 DeleteLocal (8, ex) - 56 Jump (67) + 56 Jump (66) >> 57 Raise (Reraise) 9 >> 58 LoadNameAny (3, self) 59 LoadMethod (10, fail) - 60 LoadConst ("") - 61 LoadNameAny (2, stop_exc) - 62 FormatValue (None) - 63 LoadConst (" was suppressed") - 64 BuildString (2) - 65 CallMethodPositional (1) - 66 Pop + 60 LoadNameAny (2, stop_exc) + 61 FORMAT_SIMPLE + 62 LoadConst (" was suppressed") + 63 BuildString (2) + 64 CallMethodPositional (1) + 65 Pop - 2 >> 67 PopBlock - 68 EnterFinally - >> 69 WithCleanupStart - 70 WithCleanupFinish - 71 Jump (9) - >> 72 PopBlock - 73 ReturnConst (None) + 2 >> 66 PopBlock + 67 EnterFinally + >> 68 WithCleanupStart + 69 WithCleanupFinish + 70 Jump (9) + >> 71 PopBlock + 72 ReturnConst (None) diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index d1482b8c8a6..609ec4a6322 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -12,18 +12,76 @@ use num_complex::Complex64; use rustpython_wtf8::{Wtf8, Wtf8Buf}; use std::{collections::BTreeSet, fmt, hash, marker::PhantomData, mem, num::NonZeroU8, ops::Deref}; +/// Oparg values for [`Instruction::ConvertValue`]. +/// +/// ## See also +/// +/// - [CPython FVC_* flags](https://github.com/python/cpython/blob/8183fa5e3f78ca6ab862de7fb8b14f3d929421e0/Include/ceval.h#L129-L132) +#[repr(u8)] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -#[repr(i8)] -#[allow(clippy::cast_possible_wrap)] -pub enum ConversionFlag { - /// No conversion - None = -1, // CPython uses -1 +pub enum ConvertValueOparg { + /// No conversion. + /// + /// ```python + /// f"{x}" + /// f"{x:4}" + /// ``` + None = 0, /// Converts by calling `str(<value>)`. - Str = b's' as i8, - /// Converts by calling `ascii(<value>)`. - Ascii = b'a' as i8, + /// + /// ```python + /// f"{x!s}" + /// f"{x!s:2}" + /// ``` + Str = 1, /// Converts by calling `repr(<value>)`. - Repr = b'r' as i8, + /// + /// ```python + /// f"{x!r}" + /// f"{x!r:2}" + /// ``` + Repr = 2, + /// Converts by calling `ascii(<value>)`. + /// + /// ```python + /// f"{x!a}" + /// f"{x!a:2}" + /// ``` + Ascii = 3, +} + +impl fmt::Display for ConvertValueOparg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let out = match self { + Self::Str => "1 (str)", + Self::Repr => "2 (repr)", + Self::Ascii => "3 (ascii)", + // We should never reach this. `FVC_NONE` are being handled by `Instruction::FormatSimple` + Self::None => "", + }; + + write!(f, "{out}") + } +} + +impl OpArgType for ConvertValueOparg { + #[inline] + fn from_op_arg(x: u32) -> Option<Self> { + Some(match x { + // Ruff `ConversionFlag::None` is `-1i8`, + // when its converted to `u8` its value is `u8::MAX` + 0 | 255 => Self::None, + 1 => Self::Str, + 2 => Self::Repr, + 3 => Self::Ascii, + _ => return None, + }) + } + + #[inline] + fn to_op_arg(self) -> u32 { + self as u32 + } } /// Resume type for the RESUME instruction @@ -476,24 +534,6 @@ impl fmt::Display for Label { } } -impl OpArgType for ConversionFlag { - #[inline] - fn from_op_arg(x: u32) -> Option<Self> { - match x as u8 { - b's' => Some(Self::Str), - b'a' => Some(Self::Ascii), - b'r' => Some(Self::Repr), - std::u8::MAX => Some(Self::None), - _ => None, - } - } - - #[inline] - fn to_op_arg(self) -> u32 { - self as i8 as u8 as u32 - } -} - op_arg_enum!( /// The kind of Raise that occurred. #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -620,6 +660,18 @@ pub enum Instruction { Continue { target: Arg<Label>, }, + /// Convert value to a string, depending on `oparg`: + /// + /// ```python + /// value = STACK.pop() + /// result = func(value) + /// STACK.append(result) + /// ``` + /// + /// Used for implementing formatted string literals (f-strings). + ConvertValue { + oparg: Arg<ConvertValueOparg>, + }, CopyItem { index: Arg<u32>, }, @@ -635,7 +687,6 @@ pub enum Instruction { index: Arg<u32>, }, EndAsyncFor, - /// Marker bytecode for the end of a finally sequence. /// When this bytecode is executed, the eval loop does one of those things: /// - Continue at a certain bytecode position @@ -643,16 +694,33 @@ pub enum Instruction { /// - Return from a function /// - Do nothing at all, just continue EndFinally, - /// Enter a finally block, without returning, excepting, just because we are there. EnterFinally, ExtendedArg, ForIter { target: Arg<Label>, }, - FormatValue { - conversion: Arg<ConversionFlag>, - }, + /// Formats the value on top of stack: + /// + /// ```python + /// value = STACK.pop() + /// result = value.__format__("") + /// STACK.append(result) + /// ``` + /// + /// Used for implementing formatted string literals (f-strings). + FormatSimple, + /// Formats the given value with the given format spec: + /// + /// ```python + /// spec = STACK.pop() + /// value = STACK.pop() + /// result = value.__format__(spec) + /// STACK.append(result) + /// ``` + /// + /// Used for implementing formatted string literals (f-strings). + FormatWithSpec, GetAIter, GetANext, GetAwaitable, @@ -727,12 +795,10 @@ pub enum Instruction { PopJumpIfTrue { target: Arg<Label>, }, - PrintExpr, Raise { kind: Arg<RaiseKind>, }, - /// Resume execution (e.g., at function start, after yield, etc.) Resume { arg: Arg<u32>, @@ -750,7 +816,6 @@ pub enum Instruction { SetFunctionAttribute { attr: Arg<MakeFunctionFlags>, }, - SetupAnnotation, SetupAsyncWith { end: Arg<Label>, @@ -759,7 +824,6 @@ pub enum Instruction { SetupExcept { handler: Arg<Label>, }, - /// Setup a finally handler, which will be called whenever one of this events occurs: /// - the block is popped /// - the function returns @@ -1656,6 +1720,9 @@ impl Instruction { CallMethodKeyword { nargs } => -1 - (nargs.get(arg) as i32) - 3 + 1, CallFunctionEx { has_kwargs } => -1 - (has_kwargs.get(arg) as i32) - 1 + 1, CallMethodEx { has_kwargs } => -1 - (has_kwargs.get(arg) as i32) - 3 + 1, + ConvertValue { .. } => 0, + FormatSimple => 0, + FormatWithSpec => -1, LoadMethod { .. } => -1 + 3, ForIter { .. } => { if jump { @@ -1709,7 +1776,6 @@ impl Instruction { let UnpackExArgs { before, after } = args.get(arg); -1 + before as i32 + 1 + after as i32 } - FormatValue { .. } => -1, PopException => 0, Reverse { .. } => 0, GetAwaitable => 0, @@ -1824,6 +1890,7 @@ impl Instruction { CompareOperation { op } => w!(CompareOperation, ?op), ContainsOp(inv) => w!(CONTAINS_OP, ?inv), Continue { target } => w!(Continue, target), + ConvertValue { oparg } => write!(f, "{:pad$}{}", "CONVERT_VALUE", oparg.get(arg)), CopyItem { index } => w!(CopyItem, index), DeleteAttr { idx } => w!(DeleteAttr, name = idx), DeleteDeref(idx) => w!(DeleteDeref, cell_name = idx), @@ -1837,7 +1904,8 @@ impl Instruction { EnterFinally => w!(EnterFinally), ExtendedArg => w!(ExtendedArg, Arg::<u32>::marker()), ForIter { target } => w!(ForIter, target), - FormatValue { conversion } => w!(FormatValue, ?conversion), + FormatSimple => w!(FORMAT_SIMPLE), + FormatWithSpec => w!(FORMAT_WITH_SPEC), GetAIter => w!(GetAIter), GetANext => w!(GetANext), GetAwaitable => w!(GetAwaitable), diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index d6d5e42a21a..ed76fe6aab4 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -709,6 +709,10 @@ impl ExecutingFrame<'_> { target: target.get(arg), }, ), + + bytecode::Instruction::ConvertValue { oparg: conversion } => { + self.convert_value(conversion.get(arg), vm) + } bytecode::Instruction::CopyItem { index } => { // CopyItem { index: 1 } copies TOS // CopyItem { index: 2 } copies second from top @@ -847,8 +851,20 @@ impl ExecutingFrame<'_> { Ok(None) } bytecode::Instruction::ForIter { target } => self.execute_for_iter(vm, target.get(arg)), - bytecode::Instruction::FormatValue { conversion } => { - self.format_value(conversion.get(arg), vm) + bytecode::Instruction::FormatSimple => { + let value = self.pop_value(); + let formatted = vm.format(&value, vm.ctx.new_str(""))?; + self.push_value(formatted.into()); + + Ok(None) + } + bytecode::Instruction::FormatWithSpec => { + let spec = self.pop_value(); + let value = self.pop_value(); + let formatted = vm.format(&value, spec.downcast::<PyStr>().unwrap())?; + self.push_value(formatted.into()); + + Ok(None) } bytecode::Instruction::GetAIter => { let aiterable = self.pop_value(); @@ -2237,23 +2253,21 @@ impl ExecutingFrame<'_> { Err(vm.new_value_error(msg)) } - fn format_value( + fn convert_value( &mut self, - conversion: bytecode::ConversionFlag, + conversion: bytecode::ConvertValueOparg, vm: &VirtualMachine, ) -> FrameResult { - use bytecode::ConversionFlag; + use bytecode::ConvertValueOparg; let value = self.pop_value(); let value = match conversion { - ConversionFlag::Str => value.str(vm)?.into(), - ConversionFlag::Repr => value.repr(vm)?.into(), - ConversionFlag::Ascii => vm.ctx.new_str(builtins::ascii(value, vm)?).into(), - ConversionFlag::None => value, + ConvertValueOparg::Str => value.str(vm)?.into(), + ConvertValueOparg::Repr => value.repr(vm)?.into(), + ConvertValueOparg::Ascii => vm.ctx.new_str(builtins::ascii(value, vm)?).into(), + ConvertValueOparg::None => value, }; - let spec = self.pop_value(); - let formatted = vm.format(&value, spec.downcast::<PyStr>().unwrap())?; - self.push_value(formatted.into()); + self.push_value(value); Ok(None) } diff --git a/crates/vm/src/stdlib/ast/other.rs b/crates/vm/src/stdlib/ast/other.rs index 780d7cc7124..cf8a8319749 100644 --- a/crates/vm/src/stdlib/ast/other.rs +++ b/crates/vm/src/stdlib/ast/other.rs @@ -14,12 +14,12 @@ impl Node for ruff::ConversionFlag { ) -> PyResult<Self> { i32::try_from_object(vm, object)? .to_u32() - .and_then(bytecode::ConversionFlag::from_op_arg) + .and_then(bytecode::ConvertValueOparg::from_op_arg) .map(|flag| match flag { - bytecode::ConversionFlag::None => Self::None, - bytecode::ConversionFlag::Str => Self::Str, - bytecode::ConversionFlag::Ascii => Self::Ascii, - bytecode::ConversionFlag::Repr => Self::Repr, + bytecode::ConvertValueOparg::None => Self::None, + bytecode::ConvertValueOparg::Str => Self::Str, + bytecode::ConvertValueOparg::Repr => Self::Repr, + bytecode::ConvertValueOparg::Ascii => Self::Ascii, }) .ok_or_else(|| vm.new_value_error("invalid conversion flag")) } From 9f203ee7d2243229162f22692ce3423b866e19bd Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 3 Dec 2025 00:05:58 +0200 Subject: [PATCH 395/819] Update `test_print` from 3.13.9 (#6323) --- Lib/test/test_print.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_print.py b/Lib/test/test_print.py index 8445a501cf9..6107b7032f9 100644 --- a/Lib/test/test_print.py +++ b/Lib/test/test_print.py @@ -129,14 +129,24 @@ def flush(self): raise RuntimeError self.assertRaises(RuntimeError, print, 1, file=noflush(), flush=True) + def test_gh130163(self): + class X: + def __str__(self): + sys.stdout = StringIO() + support.gc_collect() + return 'foo' + + with support.swap_attr(sys, 'stdout', None): + sys.stdout = StringIO() # the only reference + print(X()) # should not crash + class TestPy2MigrationHint(unittest.TestCase): """Test that correct hint is produced analogous to Python3 syntax, if print statement is executed as in Python 2. """ - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_normal_string(self): python2_print_str = 'print "Hello World"' with self.assertRaises(SyntaxError) as context: @@ -145,8 +155,7 @@ def test_normal_string(self): self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)", str(context.exception)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_string_with_soft_space(self): python2_print_str = 'print "Hello World",' with self.assertRaises(SyntaxError) as context: @@ -155,8 +164,7 @@ def test_string_with_soft_space(self): self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)", str(context.exception)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_string_with_excessive_whitespace(self): python2_print_str = 'print "Hello World", ' with self.assertRaises(SyntaxError) as context: @@ -165,8 +173,7 @@ def test_string_with_excessive_whitespace(self): self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)", str(context.exception)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_string_with_leading_whitespace(self): python2_print_str = '''if 1: print "Hello World" @@ -180,9 +187,7 @@ def test_string_with_leading_whitespace(self): # bpo-32685: Suggestions for print statement should be proper when # it is in the same line as the header of a compound statement # and/or followed by a semicolon - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_string_with_semicolon(self): python2_print_str = 'print p;' with self.assertRaises(SyntaxError) as context: @@ -191,8 +196,7 @@ def test_string_with_semicolon(self): self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)", str(context.exception)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_string_in_loop_on_same_line(self): python2_print_str = 'for i in s: print i' with self.assertRaises(SyntaxError) as context: @@ -201,8 +205,7 @@ def test_string_in_loop_on_same_line(self): self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)", str(context.exception)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_stream_redirection_hint_for_py2_migration(self): # Test correct hint produced for Py2 redirection syntax with self.assertRaises(TypeError) as context: From 305fb489e72a132d5a46f8c891afe2dc53a11d4b Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 3 Dec 2025 00:07:33 +0200 Subject: [PATCH 396/819] Remove `ImportNameless` bytecode (#6325) --- crates/codegen/src/compile.rs | 11 ++++------- crates/compiler-core/src/bytecode.rs | 5 +---- crates/vm/src/frame.rs | 4 ---- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index ead5dda57d0..040571ca31f 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -1352,8 +1352,6 @@ impl Compiler { .collect() }; - let module_idx = module.as_ref().map(|s| self.name(s.as_str())); - // from .... import (*fromlist) self.emit_load_const(ConstantData::Integer { value: (*level).into(), @@ -1361,11 +1359,10 @@ impl Compiler { self.emit_load_const(ConstantData::Tuple { elements: from_list, }); - if let Some(idx) = module_idx { - emit!(self, Instruction::ImportName { idx }); - } else { - emit!(self, Instruction::ImportNameless); - } + + let module_name = module.as_ref().map_or("", |s| s.as_str()); + let module_idx = self.name(module_name); + emit!(self, Instruction::ImportName { idx: module_idx }); if import_star { // from .... import * diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 609ec4a6322..18a3572d192 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -730,8 +730,6 @@ pub enum Instruction { ImportFrom { idx: Arg<NameIdx>, }, - /// Importing without name - ImportNameless, /// Importing by name ImportName { idx: Arg<NameIdx>, @@ -1671,7 +1669,7 @@ impl Instruction { pub fn stack_effect(&self, arg: OpArg, jump: bool) -> i32 { match self { Nop => 0, - ImportName { .. } | ImportNameless => -1, + ImportName { .. } => -1, ImportFrom { .. } => 1, LoadFast(_) | LoadNameAny(_) | LoadGlobal(_) | LoadDeref(_) | LoadClassDeref(_) => 1, StoreFast(_) | StoreLocal(_) | StoreGlobal(_) | StoreDeref(_) => -1, @@ -1912,7 +1910,6 @@ impl Instruction { GetIter => w!(GetIter), GetLen => w!(GetLen), ImportFrom { idx } => w!(ImportFrom, name = idx), - ImportNameless => w!(ImportNameless), ImportName { idx } => w!(ImportName, name = idx), IsOp(inv) => w!(IS_OP, ?inv), JumpIfFalseOrPop { target } => w!(JumpIfFalseOrPop, target), diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index ed76fe6aab4..99b1485a278 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -955,10 +955,6 @@ impl ExecutingFrame<'_> { self.push_value(obj); Ok(None) } - bytecode::Instruction::ImportNameless => { - self.import(vm, None)?; - Ok(None) - } bytecode::Instruction::ImportName { idx } => { self.import(vm, Some(self.code.names[idx.get(arg) as usize]))?; Ok(None) From 59f422de66d834a28a114b69a20ad2483ef450c6 Mon Sep 17 00:00:00 2001 From: fanninpm <fanninpm@miamioh.edu> Date: Wed, 3 Dec 2025 02:11:39 -0500 Subject: [PATCH 397/819] Add `_io.TextIOWrapper.detach` method (#6267) * Stub out _io.TextIOWrapper.detach method * Implement _io.TextIOWrapper.detach method * Make _io.TextIOWrapper.detach not re-enter lock --- Lib/test/test_io.py | 1 - Lib/test/test_tarfile.py | 6 ------ Lib/test/test_tempfile.py | 4 ---- Lib/test/test_xml_etree.py | 3 --- crates/vm/src/stdlib/io.rs | 29 +++++++++++++++++++++++++---- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 272098782d6..5bfaa89ba27 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -4217,7 +4217,6 @@ def test_basic_io(self): def test_constructor(self): return super().test_constructor() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_detach(self): return super().test_detach() diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 61929e537ff..9e756c7fb95 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -258,8 +258,6 @@ class ListTest(ReadTest, unittest.TestCase): def setUp(self): self.tar = tarfile.open(self.tarname, mode=self.mode) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_list(self): tio = io.TextIOWrapper(io.BytesIO(), 'ascii', newline='\n') with support.swap_attr(sys, 'stdout', tio): @@ -297,8 +295,6 @@ def conv(b): self.assertNotIn(b'link to', out) self.assertNotIn(b'->', out) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_list_verbose(self): tio = io.TextIOWrapper(io.BytesIO(), 'ascii', newline='\n') with support.swap_attr(sys, 'stdout', tio): @@ -323,8 +319,6 @@ def test_list_verbose(self): self.assertIn(b'pax' + (b'/123' * 125) + b'/longlink link to pax' + (b'/123' * 125) + b'/longname', out) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_list_members(self): tio = io.TextIOWrapper(io.BytesIO(), 'ascii', newline='\n') def members(tar): diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index 913897de40e..5674839cd43 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -1187,8 +1187,6 @@ def test_properties(self): with self.assertRaises(AttributeError): f.errors - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_text_mode(self): # Creating a SpooledTemporaryFile with a text mode should produce # a file object reading and writing (Unicode) text strings. @@ -1221,8 +1219,6 @@ def test_text_mode(self): self.assertEqual(f.encoding, "utf-8") self.assertEqual(f.errors, "strict") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_text_newline_and_encoding(self): f = tempfile.SpooledTemporaryFile(mode='w+', max_size=10, newline='', encoding='utf-8', diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 57b082fc584..11c617b5f34 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -437,7 +437,6 @@ def test_copy(self): self.serialize_check(e2, '<tag>hello<bar /></tag>') self.serialize_check(e3, '<tag>hello<foo /></tag>') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_attrib(self): # Test attribute handling. @@ -1481,7 +1480,6 @@ def check(p, expected, namespaces=None): {'': 'http://www.w3.org/2001/XMLSchema', 'ns': 'http://www.w3.org/2001/XMLSchema'}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_processinginstruction(self): # Test ProcessingInstruction directly @@ -2369,7 +2367,6 @@ def test_bug_200709_default_namespace(self): self.assertEqual(str(cm.exception), 'cannot use non-qualified names with default_namespace option') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_200709_register_namespace(self): e = ET.Element("{http://namespace.invalid/does/not/exist/}title") self.assertEqual(ET.tostring(e), diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index d1ffe5a9723..39ea310c9d3 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -2405,6 +2405,14 @@ mod _io { } } + #[inline] + fn flush_inner(textio: &mut TextIOData, vm: &VirtualMachine) -> PyResult { + textio.check_closed(vm)?; + textio.telling = textio.seekable; + textio.write_pending(vm)?; + vm.call_method(&textio.buffer, "flush", ()) + } + #[pyclass(with(Constructor, Initializer), flags(BASETYPE))] impl TextIOWrapper { #[pymethod] @@ -2439,6 +2447,22 @@ mod _io { Ok(()) } + #[pymethod] + fn detach(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult { + let mut textio = zelf.lock(vm)?; + + // Fail fast if already detached + if vm.is_none(&textio.buffer) { + return Err(vm.new_value_error("underlying buffer has been detached")); + } + + flush_inner(&mut textio, vm)?; + + let buffer = textio.buffer.clone(); + textio.buffer = vm.ctx.none(); + Ok(buffer) + } + #[pymethod] fn seekable(&self, vm: &VirtualMachine) -> PyResult { let textio = self.lock(vm)?; @@ -2873,10 +2897,7 @@ mod _io { #[pymethod] fn flush(&self, vm: &VirtualMachine) -> PyResult { let mut textio = self.lock(vm)?; - textio.check_closed(vm)?; - textio.telling = textio.seekable; - textio.write_pending(vm)?; - vm.call_method(&textio.buffer, "flush", ()) + flush_inner(&mut textio, vm) } #[pymethod] From f49c18578aae5fd923bfaf6b7c49c98fb90ac439 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:17:36 +0200 Subject: [PATCH 398/819] Move `PrintExpr` to `IntristicFunction1` (#6324) --- crates/codegen/src/compile.rs | 18 ++++++++++++++++-- crates/compiler-core/src/bytecode.rs | 5 +---- crates/vm/src/frame.rs | 21 +++++++-------------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 040571ca31f..67f5ddd4dfc 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -1057,7 +1057,14 @@ impl Compiler { for statement in body { if let Stmt::Expr(StmtExpr { value, .. }) = &statement { self.compile_expression(value)?; - emit!(self, Instruction::PrintExpr); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::Print + } + ); + + emit!(self, Instruction::Pop); } else { self.compile_statement(statement)?; } @@ -1066,7 +1073,14 @@ impl Compiler { if let Stmt::Expr(StmtExpr { value, .. }) = &last { self.compile_expression(value)?; emit!(self, Instruction::CopyItem { index: 1_u32 }); - emit!(self, Instruction::PrintExpr); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::Print + } + ); + + emit!(self, Instruction::Pop); } else { self.compile_statement(last)?; self.emit_load_const(ConstantData::None); diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 18a3572d192..11d2a7b5f1b 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -551,7 +551,7 @@ op_arg_enum!( #[repr(u8)] pub enum IntrinsicFunction1 { // Invalid = 0, - // Print = 1, + Print = 1, /// Import * operation ImportStar = 2, // StopIterationError = 3, @@ -793,7 +793,6 @@ pub enum Instruction { PopJumpIfTrue { target: Arg<Label>, }, - PrintExpr, Raise { kind: Arg<RaiseKind>, }, @@ -1767,7 +1766,6 @@ impl Instruction { } ListAppend { .. } | SetAdd { .. } => -1, MapAdd { .. } => -2, - PrintExpr => -1, LoadBuildClass => 1, UnpackSequence { size } => -1 + size.get(arg) as i32, UnpackEx { args } => { @@ -1939,7 +1937,6 @@ impl Instruction { PopException => w!(PopException), PopJumpIfFalse { target } => w!(PopJumpIfFalse, target), PopJumpIfTrue { target } => w!(PopJumpIfTrue, target), - PrintExpr => w!(PrintExpr), Raise { kind } => w!(Raise, ?kind), Resume { arg } => w!(Resume, arg), ReturnConst { idx } => fmt_const("ReturnConst", arg, f, idx), diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 99b1485a278..77b034ade9a 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1351,8 +1351,6 @@ impl ExecutingFrame<'_> { bytecode::Instruction::PopJumpIfTrue { target } => { self.pop_jump_if(vm, target.get(arg), true) } - bytecode::Instruction::PrintExpr => self.print_expr(vm), - bytecode::Instruction::Raise { kind } => self.execute_raise(vm, kind.get(arg)), bytecode::Instruction::Resume { arg: resume_arg } => { // Resume execution after yield, await, or at function start @@ -2208,18 +2206,6 @@ impl ExecutingFrame<'_> { Ok(None) } - fn print_expr(&mut self, vm: &VirtualMachine) -> FrameResult { - let expr = self.pop_value(); - - let displayhook = vm - .sys_module - .get_attr("displayhook", vm) - .map_err(|_| vm.new_runtime_error("lost sys.displayhook"))?; - displayhook.call((expr,), vm)?; - - Ok(None) - } - fn unpack_sequence(&mut self, size: u32, vm: &VirtualMachine) -> FrameResult { let value = self.pop_value(); let elements: Vec<_> = value.try_to_value(vm).map_err(|e| { @@ -2390,6 +2376,13 @@ impl ExecutingFrame<'_> { vm: &VirtualMachine, ) -> PyResult { match func { + bytecode::IntrinsicFunction1::Print => { + let displayhook = vm + .sys_module + .get_attr("displayhook", vm) + .map_err(|_| vm.new_runtime_error("lost sys.displayhook"))?; + displayhook.call((arg,), vm) + } bytecode::IntrinsicFunction1::ImportStar => { // arg is the module object self.push_value(arg); // Push module back on stack for import_star From 2b90e826ec89a75253fca0fd09927811f555d91c Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 4 Dec 2025 02:17:04 +0200 Subject: [PATCH 399/819] Use rust idioms for accessing a `Vec` in `codegen/src/compile.rs` (#6326) * Use rust idioms for accessing a `Vec` * clippy * Remove `unsafe` --- crates/codegen/src/compile.rs | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 67f5ddd4dfc..5e2c7b81b33 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -557,11 +557,9 @@ impl Compiler { /// Get the SymbolTable for the current scope. fn current_symbol_table(&self) -> &SymbolTable { - if self.symbol_table_stack.is_empty() { - panic!("symbol_table_stack is empty! This is a compiler bug."); - } - let index = self.symbol_table_stack.len() - 1; - &self.symbol_table_stack[index] + self.symbol_table_stack + .last() + .expect("symbol_table_stack is empty! This is a compiler bug.") } /// Get the index of a free variable. @@ -626,9 +624,8 @@ impl Compiler { let table = current_table.sub_tables.remove(0); // Push the next table onto the stack - let last_idx = self.symbol_table_stack.len(); self.symbol_table_stack.push(table); - &self.symbol_table_stack[last_idx] + self.current_symbol_table() } /// Pop the current symbol table off the stack @@ -657,12 +654,13 @@ impl Compiler { let source_path = self.source_file.name().to_owned(); // Lookup symbol table entry using key (_PySymtable_Lookup) - let ste = if key < self.symbol_table_stack.len() { - &self.symbol_table_stack[key] - } else { - return Err(self.error(CodegenErrorType::SyntaxError( - "unknown symbol table entry".to_owned(), - ))); + let ste = match self.symbol_table_stack.get(key) { + Some(v) => v, + None => { + return Err(self.error(CodegenErrorType::SyntaxError( + "unknown symbol table entry".to_owned(), + ))); + } }; // Use varnames from symbol table (already collected in definition order) @@ -1199,12 +1197,10 @@ impl Compiler { // If not found and we're in TypeParams scope, try parent scope let symbol = if symbol.is_none() && is_typeparams { - if self.symbol_table_stack.len() > 1 { - let parent_idx = self.symbol_table_stack.len() - 2; - self.symbol_table_stack[parent_idx].lookup(name.as_ref()) - } else { - None - } + self.symbol_table_stack + .get(self.symbol_table_stack.len() - 2) // Try to get parent index + .expect("Symbol has no parent! This is a compiler bug.") + .lookup(name.as_ref()) } else { symbol }; From c2ca9a7d319f93ffde23bd228dac1d0e7e03f8a7 Mon Sep 17 00:00:00 2001 From: Yash Suthar <yashsuthar983@gmail.com> Date: Fri, 5 Dec 2025 05:02:45 +0530 Subject: [PATCH 400/819] prefer nb_bool slot in try_to_bool instead of __bool__ (#6328) Fixed: #6113 --------- Signed-off-by: Yash Suthar <yashsuthar983@gmail.com> --- crates/vm/src/builtins/bool.rs | 59 ++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 47e46541e39..829d8b2384b 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -39,39 +39,44 @@ impl PyObjectRef { if self.is(&vm.ctx.false_value) { return Ok(false); } - let rs_bool = match vm.get_method(self.clone(), identifier!(vm, __bool__)) { - Some(method_or_err) => { - // If descriptor returns Error, propagate it further - let method = method_or_err?; - let bool_obj = method.call((), vm)?; - if !bool_obj.fast_isinstance(vm.ctx.types.bool_type) { - return Err(vm.new_type_error(format!( - "__bool__ should return bool, returned type {}", - bool_obj.class().name() - ))); - } - - get_value(&bool_obj) - } - None => match vm.get_method(self, identifier!(vm, __len__)) { + let rs_bool = if let Some(nb_bool) = self.class().slots.as_number.boolean.load() { + nb_bool(self.as_object().to_number(), vm)? + } else { + // TODO: Fully implement AsNumber and remove this block + match vm.get_method(self.clone(), identifier!(vm, __bool__)) { Some(method_or_err) => { + // If descriptor returns Error, propagate it further let method = method_or_err?; let bool_obj = method.call((), vm)?; - let int_obj = bool_obj.downcast_ref::<PyInt>().ok_or_else(|| { - vm.new_type_error(format!( - "'{}' object cannot be interpreted as an integer", + if !bool_obj.fast_isinstance(vm.ctx.types.bool_type) { + return Err(vm.new_type_error(format!( + "__bool__ should return bool, returned type {}", bool_obj.class().name() - )) - })?; - - let len_val = int_obj.as_bigint(); - if len_val.sign() == Sign::Minus { - return Err(vm.new_value_error("__len__() should return >= 0")); + ))); } - !len_val.is_zero() + + get_value(&bool_obj) } - None => true, - }, + None => match vm.get_method(self, identifier!(vm, __len__)) { + Some(method_or_err) => { + let method = method_or_err?; + let bool_obj = method.call((), vm)?; + let int_obj = bool_obj.downcast_ref::<PyInt>().ok_or_else(|| { + vm.new_type_error(format!( + "'{}' object cannot be interpreted as an integer", + bool_obj.class().name() + )) + })?; + + let len_val = int_obj.as_bigint(); + if len_val.sign() == Sign::Minus { + return Err(vm.new_value_error("__len__() should return >= 0")); + } + !len_val.is_zero() + } + None => true, + }, + } }; Ok(rs_bool) } From 517987c7b3c657a705130285e48423572af4ba52 Mon Sep 17 00:00:00 2001 From: Ashwin Naren <arihant2math@gmail.com> Date: Mon, 10 Mar 2025 00:32:51 -0700 Subject: [PATCH 401/819] rewrite of winreg module and add test_winreg --- Cargo.lock | 11 - Lib/test/test_winreg.py | 552 +++++++++++++++++ crates/vm/Cargo.toml | 1 - crates/vm/src/stdlib/winreg.rs | 1038 +++++++++++++++++++++++++------- 4 files changed, 1365 insertions(+), 237 deletions(-) create mode 100644 Lib/test/test_winreg.py diff --git a/Cargo.lock b/Cargo.lock index f96b527dfe2..aef688eb21f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3278,7 +3278,6 @@ dependencies = [ "widestring", "windows", "windows-sys 0.59.0", - "winreg", ] [[package]] @@ -4674,16 +4673,6 @@ version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - [[package]] name = "winresource" version = "0.1.27" diff --git a/Lib/test/test_winreg.py b/Lib/test/test_winreg.py new file mode 100644 index 00000000000..2c530ee754e --- /dev/null +++ b/Lib/test/test_winreg.py @@ -0,0 +1,552 @@ +# Test the windows specific win32reg module. +# Only win32reg functions not hit here: FlushKey, LoadKey and SaveKey + +import gc +import os, sys, errno +import threading +import unittest +from platform import machine, win32_edition +from test.support import cpython_only, import_helper + +# Do this first so test will be skipped if module doesn't exist +import_helper.import_module('winreg', required_on=['win']) +# Now import everything +from winreg import * + +try: + REMOTE_NAME = sys.argv[sys.argv.index("--remote")+1] +except (IndexError, ValueError): + REMOTE_NAME = None + +# tuple of (major, minor) +WIN_VER = sys.getwindowsversion()[:2] +# Some tests should only run on 64-bit architectures where WOW64 will be. +WIN64_MACHINE = True if machine() == "AMD64" else False + +# Starting with Windows 7 and Windows Server 2008 R2, WOW64 no longer uses +# registry reflection and formerly reflected keys are shared instead. +# Windows 7 and Windows Server 2008 R2 are version 6.1. Due to this, some +# tests are only valid up until 6.1 +HAS_REFLECTION = True if WIN_VER < (6, 1) else False + +# Use a per-process key to prevent concurrent test runs (buildbot!) from +# stomping on each other. +test_key_base = "Python Test Key [%d] - Delete Me" % (os.getpid(),) +test_key_name = "SOFTWARE\\" + test_key_base +# On OS'es that support reflection we should test with a reflected key +test_reflect_key_name = "SOFTWARE\\Classes\\" + test_key_base + +test_data = [ + ("Int Value", 45, REG_DWORD), + ("Qword Value", 0x1122334455667788, REG_QWORD), + ("String Val", "A string value", REG_SZ), + ("StringExpand", "The path is %path%", REG_EXPAND_SZ), + ("Multi-string", ["Lots", "of", "string", "values"], REG_MULTI_SZ), + ("Multi-nul", ["", "", "", ""], REG_MULTI_SZ), + ("Raw Data", b"binary\x00data", REG_BINARY), + ("Big String", "x"*(2**14-1), REG_SZ), + ("Big Binary", b"x"*(2**14), REG_BINARY), + # Two and three kanjis, meaning: "Japan" and "Japanese". + ("Japanese 日本", "日本語", REG_SZ), +] + + +@cpython_only +class HeapTypeTests(unittest.TestCase): + def test_have_gc(self): + self.assertTrue(gc.is_tracked(HKEYType)) + + def test_immutable(self): + with self.assertRaisesRegex(TypeError, "immutable"): + HKEYType.foo = "bar" + + +class BaseWinregTests(unittest.TestCase): + + def setUp(self): + # Make sure that the test key is absent when the test + # starts. + self.delete_tree(HKEY_CURRENT_USER, test_key_name) + + def delete_tree(self, root, subkey): + try: + hkey = OpenKey(root, subkey, 0, KEY_ALL_ACCESS) + except OSError: + # subkey does not exist + return + while True: + try: + subsubkey = EnumKey(hkey, 0) + except OSError: + # no more subkeys + break + self.delete_tree(hkey, subsubkey) + CloseKey(hkey) + DeleteKey(root, subkey) + + def _write_test_data(self, root_key, subkeystr="sub_key", + CreateKey=CreateKey): + # Set the default value for this key. + SetValue(root_key, test_key_name, REG_SZ, "Default value") + key = CreateKey(root_key, test_key_name) + self.assertTrue(key.handle != 0) + # Create a sub-key + sub_key = CreateKey(key, subkeystr) + # Give the sub-key some named values + + for value_name, value_data, value_type in test_data: + SetValueEx(sub_key, value_name, 0, value_type, value_data) + + # Check we wrote as many items as we thought. + nkeys, nvalues, since_mod = QueryInfoKey(key) + self.assertEqual(nkeys, 1, "Not the correct number of sub keys") + self.assertEqual(nvalues, 1, "Not the correct number of values") + nkeys, nvalues, since_mod = QueryInfoKey(sub_key) + self.assertEqual(nkeys, 0, "Not the correct number of sub keys") + self.assertEqual(nvalues, len(test_data), + "Not the correct number of values") + # Close this key this way... + # (but before we do, copy the key as an integer - this allows + # us to test that the key really gets closed). + int_sub_key = int(sub_key) + CloseKey(sub_key) + try: + QueryInfoKey(int_sub_key) + self.fail("It appears the CloseKey() function does " + "not close the actual key!") + except OSError: + pass + # ... and close that key that way :-) + int_key = int(key) + key.Close() + try: + QueryInfoKey(int_key) + self.fail("It appears the key.Close() function " + "does not close the actual key!") + except OSError: + pass + def _read_test_data(self, root_key, subkeystr="sub_key", OpenKey=OpenKey): + # Check we can get default value for this key. + val = QueryValue(root_key, test_key_name) + self.assertEqual(val, "Default value", + "Registry didn't give back the correct value") + + key = OpenKey(root_key, test_key_name) + # Read the sub-keys + with OpenKey(key, subkeystr) as sub_key: + # Check I can enumerate over the values. + index = 0 + while 1: + try: + data = EnumValue(sub_key, index) + except OSError: + break + self.assertEqual(data in test_data, True, + "Didn't read back the correct test data") + index = index + 1 + self.assertEqual(index, len(test_data), + "Didn't read the correct number of items") + # Check I can directly access each item + for value_name, value_data, value_type in test_data: + read_val, read_typ = QueryValueEx(sub_key, value_name) + self.assertEqual(read_val, value_data, + "Could not directly read the value") + self.assertEqual(read_typ, value_type, + "Could not directly read the value") + sub_key.Close() + # Enumerate our main key. + read_val = EnumKey(key, 0) + self.assertEqual(read_val, subkeystr, "Read subkey value wrong") + try: + EnumKey(key, 1) + self.fail("Was able to get a second key when I only have one!") + except OSError: + pass + + key.Close() + + def _delete_test_data(self, root_key, subkeystr="sub_key"): + key = OpenKey(root_key, test_key_name, 0, KEY_ALL_ACCESS) + sub_key = OpenKey(key, subkeystr, 0, KEY_ALL_ACCESS) + # It is not necessary to delete the values before deleting + # the key (although subkeys must not exist). We delete them + # manually just to prove we can :-) + for value_name, value_data, value_type in test_data: + DeleteValue(sub_key, value_name) + + nkeys, nvalues, since_mod = QueryInfoKey(sub_key) + self.assertEqual(nkeys, 0, "subkey not empty before delete") + self.assertEqual(nvalues, 0, "subkey not empty before delete") + sub_key.Close() + DeleteKey(key, subkeystr) + + try: + # Shouldn't be able to delete it twice! + DeleteKey(key, subkeystr) + self.fail("Deleting the key twice succeeded") + except OSError: + pass + key.Close() + DeleteKey(root_key, test_key_name) + # Opening should now fail! + try: + key = OpenKey(root_key, test_key_name) + self.fail("Could open the non-existent key") + except OSError: # Use this error name this time + pass + + def _test_all(self, root_key, subkeystr="sub_key"): + self._write_test_data(root_key, subkeystr) + self._read_test_data(root_key, subkeystr) + self._delete_test_data(root_key, subkeystr) + + def _test_named_args(self, key, sub_key): + with CreateKeyEx(key=key, sub_key=sub_key, reserved=0, + access=KEY_ALL_ACCESS) as ckey: + self.assertTrue(ckey.handle != 0) + + with OpenKeyEx(key=key, sub_key=sub_key, reserved=0, + access=KEY_ALL_ACCESS) as okey: + self.assertTrue(okey.handle != 0) + + +class LocalWinregTests(BaseWinregTests): + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_registry_works(self): + self._test_all(HKEY_CURRENT_USER) + self._test_all(HKEY_CURRENT_USER, "日本-subkey") + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_registry_works_extended_functions(self): + # Substitute the regular CreateKey and OpenKey calls with their + # extended counterparts. + # Note: DeleteKeyEx is not used here because it is platform dependent + cke = lambda key, sub_key: CreateKeyEx(key, sub_key, 0, KEY_ALL_ACCESS) + self._write_test_data(HKEY_CURRENT_USER, CreateKey=cke) + + oke = lambda key, sub_key: OpenKeyEx(key, sub_key, 0, KEY_READ) + self._read_test_data(HKEY_CURRENT_USER, OpenKey=oke) + + self._delete_test_data(HKEY_CURRENT_USER) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_named_arguments(self): + self._test_named_args(HKEY_CURRENT_USER, test_key_name) + # Use the regular DeleteKey to clean up + # DeleteKeyEx takes named args and is tested separately + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + def test_connect_registry_to_local_machine_works(self): + # perform minimal ConnectRegistry test which just invokes it + h = ConnectRegistry(None, HKEY_LOCAL_MACHINE) + self.assertNotEqual(h.handle, 0) + h.Close() + self.assertEqual(h.handle, 0) + + def test_nonexistent_remote_registry(self): + connect = lambda: ConnectRegistry("abcdefghijkl", HKEY_CURRENT_USER) + self.assertRaises(OSError, connect) + + # TODO: RUSTPYTHON + @unittest.skip("flaky") + def testExpandEnvironmentStrings(self): + r = ExpandEnvironmentStrings("%windir%\\test") + self.assertEqual(type(r), str) + self.assertEqual(r, os.environ["windir"] + "\\test") + + def test_context_manager(self): + # ensure that the handle is closed if an exception occurs + try: + with ConnectRegistry(None, HKEY_LOCAL_MACHINE) as h: + self.assertNotEqual(h.handle, 0) + raise OSError + except OSError: + self.assertEqual(h.handle, 0) + + def test_changing_value(self): + # Issue2810: A race condition in 2.6 and 3.1 may cause + # EnumValue or QueryValue to raise "WindowsError: More data is + # available" + done = False + + class VeryActiveThread(threading.Thread): + def run(self): + with CreateKey(HKEY_CURRENT_USER, test_key_name) as key: + use_short = True + long_string = 'x'*2000 + while not done: + s = 'x' if use_short else long_string + use_short = not use_short + SetValue(key, 'changing_value', REG_SZ, s) + + thread = VeryActiveThread() + thread.start() + try: + with CreateKey(HKEY_CURRENT_USER, + test_key_name+'\\changing_value') as key: + for _ in range(1000): + num_subkeys, num_values, t = QueryInfoKey(key) + for i in range(num_values): + name = EnumValue(key, i) + QueryValue(key, name[0]) + finally: + done = True + thread.join() + DeleteKey(HKEY_CURRENT_USER, test_key_name+'\\changing_value') + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_long_key(self): + # Issue2810, in 2.6 and 3.1 when the key name was exactly 256 + # characters, EnumKey raised "WindowsError: More data is + # available" + name = 'x'*256 + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as key: + SetValue(key, name, REG_SZ, 'x') + num_subkeys, num_values, t = QueryInfoKey(key) + EnumKey(key, 0) + finally: + DeleteKey(HKEY_CURRENT_USER, '\\'.join((test_key_name, name))) + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_dynamic_key(self): + # Issue2810, when the value is dynamically generated, these + # raise "WindowsError: More data is available" in 2.6 and 3.1 + try: + EnumValue(HKEY_PERFORMANCE_DATA, 0) + except OSError as e: + if e.errno in (errno.EPERM, errno.EACCES): + self.skipTest("access denied to registry key " + "(are you running in a non-interactive session?)") + raise + QueryValueEx(HKEY_PERFORMANCE_DATA, "") + + # Reflection requires XP x64/Vista at a minimum. XP doesn't have this stuff + # or DeleteKeyEx so make sure their use raises NotImplementedError + @unittest.skipUnless(WIN_VER < (5, 2), "Requires Windows XP") + def test_reflection_unsupported(self): + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as ck: + self.assertNotEqual(ck.handle, 0) + + key = OpenKey(HKEY_CURRENT_USER, test_key_name) + self.assertNotEqual(key.handle, 0) + + with self.assertRaises(NotImplementedError): + DisableReflectionKey(key) + with self.assertRaises(NotImplementedError): + EnableReflectionKey(key) + with self.assertRaises(NotImplementedError): + QueryReflectionKey(key) + with self.assertRaises(NotImplementedError): + DeleteKeyEx(HKEY_CURRENT_USER, test_key_name) + finally: + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_setvalueex_value_range(self): + # Test for Issue #14420, accept proper ranges for SetValueEx. + # Py2Reg, which gets called by SetValueEx, was using PyLong_AsLong, + # thus raising OverflowError. The implementation now uses + # PyLong_AsUnsignedLong to match DWORD's size. + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as ck: + self.assertNotEqual(ck.handle, 0) + SetValueEx(ck, "test_name", None, REG_DWORD, 0x80000000) + finally: + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_setvalueex_negative_one_check(self): + # Test for Issue #43984, check -1 was not set by SetValueEx. + # Py2Reg, which gets called by SetValueEx, wasn't checking return + # value by PyLong_AsUnsignedLong, thus setting -1 as value in the registry. + # The implementation now checks PyLong_AsUnsignedLong return value to assure + # the value set was not -1. + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as ck: + with self.assertRaises(OverflowError): + SetValueEx(ck, "test_name_dword", None, REG_DWORD, -1) + SetValueEx(ck, "test_name_qword", None, REG_QWORD, -1) + self.assertRaises(FileNotFoundError, QueryValueEx, ck, "test_name_dword") + self.assertRaises(FileNotFoundError, QueryValueEx, ck, "test_name_qword") + + finally: + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_queryvalueex_return_value(self): + # Test for Issue #16759, return unsigned int from QueryValueEx. + # Reg2Py, which gets called by QueryValueEx, was returning a value + # generated by PyLong_FromLong. The implementation now uses + # PyLong_FromUnsignedLong to match DWORD's size. + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as ck: + self.assertNotEqual(ck.handle, 0) + test_val = 0x80000000 + SetValueEx(ck, "test_name", None, REG_DWORD, test_val) + ret_val, ret_type = QueryValueEx(ck, "test_name") + self.assertEqual(ret_type, REG_DWORD) + self.assertEqual(ret_val, test_val) + finally: + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_setvalueex_crash_with_none_arg(self): + # Test for Issue #21151, segfault when None is passed to SetValueEx + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as ck: + self.assertNotEqual(ck.handle, 0) + test_val = None + SetValueEx(ck, "test_name", 0, REG_BINARY, test_val) + ret_val, ret_type = QueryValueEx(ck, "test_name") + self.assertEqual(ret_type, REG_BINARY) + self.assertEqual(ret_val, test_val) + finally: + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_read_string_containing_null(self): + # Test for issue 25778: REG_SZ should not contain null characters + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as ck: + self.assertNotEqual(ck.handle, 0) + test_val = "A string\x00 with a null" + SetValueEx(ck, "test_name", 0, REG_SZ, test_val) + ret_val, ret_type = QueryValueEx(ck, "test_name") + self.assertEqual(ret_type, REG_SZ) + self.assertEqual(ret_val, "A string") + finally: + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + +@unittest.skipUnless(REMOTE_NAME, "Skipping remote registry tests") +class RemoteWinregTests(BaseWinregTests): + + def test_remote_registry_works(self): + remote_key = ConnectRegistry(REMOTE_NAME, HKEY_CURRENT_USER) + self._test_all(remote_key) + + +@unittest.skipUnless(WIN64_MACHINE, "x64 specific registry tests") +class Win64WinregTests(BaseWinregTests): + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_named_arguments(self): + self._test_named_args(HKEY_CURRENT_USER, test_key_name) + # Clean up and also exercise the named arguments + DeleteKeyEx(key=HKEY_CURRENT_USER, sub_key=test_key_name, + access=KEY_ALL_ACCESS, reserved=0) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + @unittest.skipIf(win32_edition() in ('WindowsCoreHeadless', 'IoTEdgeOS'), "APIs not available on WindowsCoreHeadless") + def test_reflection_functions(self): + # Test that we can call the query, enable, and disable functions + # on a key which isn't on the reflection list with no consequences. + with OpenKey(HKEY_LOCAL_MACHINE, "Software") as key: + # HKLM\Software is redirected but not reflected in all OSes + self.assertTrue(QueryReflectionKey(key)) + self.assertIsNone(EnableReflectionKey(key)) + self.assertIsNone(DisableReflectionKey(key)) + self.assertTrue(QueryReflectionKey(key)) + + @unittest.skipUnless(HAS_REFLECTION, "OS doesn't support reflection") + def test_reflection(self): + # Test that we can create, open, and delete keys in the 32-bit + # area. Because we are doing this in a key which gets reflected, + # test the differences of 32 and 64-bit keys before and after the + # reflection occurs (ie. when the created key is closed). + try: + with CreateKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_ALL_ACCESS | KEY_WOW64_32KEY) as created_key: + self.assertNotEqual(created_key.handle, 0) + + # The key should now be available in the 32-bit area + with OpenKey(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_ALL_ACCESS | KEY_WOW64_32KEY) as key: + self.assertNotEqual(key.handle, 0) + + # Write a value to what currently is only in the 32-bit area + SetValueEx(created_key, "", 0, REG_SZ, "32KEY") + + # The key is not reflected until created_key is closed. + # The 64-bit version of the key should not be available yet. + open_fail = lambda: OpenKey(HKEY_CURRENT_USER, + test_reflect_key_name, 0, + KEY_READ | KEY_WOW64_64KEY) + self.assertRaises(OSError, open_fail) + + # Now explicitly open the 64-bit version of the key + with OpenKey(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_ALL_ACCESS | KEY_WOW64_64KEY) as key: + self.assertNotEqual(key.handle, 0) + # Make sure the original value we set is there + self.assertEqual("32KEY", QueryValue(key, "")) + # Set a new value, which will get reflected to 32-bit + SetValueEx(key, "", 0, REG_SZ, "64KEY") + + # Reflection uses a "last-writer wins policy, so the value we set + # on the 64-bit key should be the same on 32-bit + with OpenKey(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_READ | KEY_WOW64_32KEY) as key: + self.assertEqual("64KEY", QueryValue(key, "")) + finally: + DeleteKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, + KEY_WOW64_32KEY, 0) + + @unittest.skipUnless(HAS_REFLECTION, "OS doesn't support reflection") + def test_disable_reflection(self): + # Make use of a key which gets redirected and reflected + try: + with CreateKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_ALL_ACCESS | KEY_WOW64_32KEY) as created_key: + # QueryReflectionKey returns whether or not the key is disabled + disabled = QueryReflectionKey(created_key) + self.assertEqual(type(disabled), bool) + # HKCU\Software\Classes is reflected by default + self.assertFalse(disabled) + + DisableReflectionKey(created_key) + self.assertTrue(QueryReflectionKey(created_key)) + + # The key is now closed and would normally be reflected to the + # 64-bit area, but let's make sure that didn't happen. + open_fail = lambda: OpenKeyEx(HKEY_CURRENT_USER, + test_reflect_key_name, 0, + KEY_READ | KEY_WOW64_64KEY) + self.assertRaises(OSError, open_fail) + + # Make sure the 32-bit key is actually there + with OpenKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_READ | KEY_WOW64_32KEY) as key: + self.assertNotEqual(key.handle, 0) + finally: + DeleteKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, + KEY_WOW64_32KEY, 0) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_exception_numbers(self): + with self.assertRaises(FileNotFoundError) as ctx: + QueryValue(HKEY_CLASSES_ROOT, 'some_value_that_does_not_exist') + + +if __name__ == "__main__": + if not REMOTE_NAME: + print("Remote registry calls can be tested using", + "'test_winreg.py --remote \\\\machine_name'") + unittest.main() diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 1e12454a6ba..45c32239c9b 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -111,7 +111,6 @@ num_cpus = "1.17.0" [target.'cfg(windows)'.dependencies] junction = { workspace = true } -winreg = "0.55" [target.'cfg(windows)'.dependencies.windows] version = "0.52.0" diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index d64a05ea404..f3744d93ff2 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -4,39 +4,31 @@ use crate::{PyRef, VirtualMachine, builtins::PyModule}; pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { - let module = winreg::make_module(vm); - - macro_rules! add_constants { - ($($name:ident),*$(,)?) => { - extend_module!(vm, &module, { - $((stringify!($name)) => vm.new_pyobj(::winreg::enums::$name as usize)),* - }) - }; - } - - add_constants!( - HKEY_CLASSES_ROOT, - HKEY_CURRENT_USER, - HKEY_LOCAL_MACHINE, - HKEY_USERS, - HKEY_PERFORMANCE_DATA, - HKEY_CURRENT_CONFIG, - HKEY_DYN_DATA, - ); - module + winreg::make_module(vm) } #[pymodule] mod winreg { - use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; - use crate::{ - PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::PyStrRef, - convert::ToPyException, - }; - use ::winreg::{RegKey, RegValue, enums::RegType}; - use std::mem::ManuallyDrop; - use std::{ffi::OsStr, io}; - use windows_sys::Win32::Foundation; + use std::ffi::OsStr; + use std::os::windows::ffi::OsStrExt; + use std::ptr; + use std::sync::Arc; + + use crate::builtins::{PyInt, PyTuple}; + use crate::common::lock::PyRwLock; + use crate::function::FuncArgs; + use crate::protocol::PyNumberMethods; + use crate::types::AsNumber; + use crate::{PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; + + use windows_sys::Win32::Foundation::{self, ERROR_MORE_DATA}; + use windows_sys::Win32::System::Registry; + + use num_traits::ToPrimitive; + + pub(crate) fn to_utf16<P: AsRef<OsStr>>(s: P) -> Vec<u16> { + s.as_ref().encode_wide().chain(Some(0)).collect() + } // access rights #[pyattr] @@ -57,288 +49,884 @@ mod winreg { REG_RESOURCE_REQUIREMENTS_LIST, REG_SZ, REG_WHOLE_HIVE_VOLATILE, }; - #[pyattr] - #[pyclass(module = "winreg", name = "HKEYType")] - #[derive(Debug, PyPayload)] - struct PyHkey { - key: PyRwLock<RegKey>, + #[pyattr(once)] + fn HKEY_CLASSES_ROOT(_vm: &VirtualMachine) -> PyHKEYObject { + PyHKEYObject { + #[allow(clippy::arc_with_non_send_sync)] + hkey: Arc::new(PyRwLock::new(Registry::HKEY_CLASSES_ROOT)), + } } - type PyHkeyRef = PyRef<PyHkey>; - // TODO: fix this - unsafe impl Sync for PyHkey {} + #[pyattr(once)] + fn HKEY_CURRENT_USER(_vm: &VirtualMachine) -> PyHKEYObject { + PyHKEYObject { + #[allow(clippy::arc_with_non_send_sync)] + hkey: Arc::new(PyRwLock::new(Registry::HKEY_CURRENT_USER)), + } + } - impl PyHkey { - fn new(key: RegKey) -> Self { - Self { - key: PyRwLock::new(key), - } + #[pyattr(once)] + fn HKEY_LOCAL_MACHINE(_vm: &VirtualMachine) -> PyHKEYObject { + PyHKEYObject { + #[allow(clippy::arc_with_non_send_sync)] + hkey: Arc::new(PyRwLock::new(Registry::HKEY_LOCAL_MACHINE)), + } + } + + #[pyattr(once)] + fn HKEY_USERS(_vm: &VirtualMachine) -> PyHKEYObject { + PyHKEYObject { + #[allow(clippy::arc_with_non_send_sync)] + hkey: Arc::new(PyRwLock::new(Registry::HKEY_USERS)), + } + } + + #[pyattr(once)] + fn HKEY_PERFORMANCE_DATA(_vm: &VirtualMachine) -> PyHKEYObject { + PyHKEYObject { + #[allow(clippy::arc_with_non_send_sync)] + hkey: Arc::new(PyRwLock::new(Registry::HKEY_PERFORMANCE_DATA)), } + } - fn key(&self) -> PyRwLockReadGuard<'_, RegKey> { - self.key.read() + #[pyattr(once)] + fn HKEY_CURRENT_CONFIG(_vm: &VirtualMachine) -> PyHKEYObject { + PyHKEYObject { + #[allow(clippy::arc_with_non_send_sync)] + hkey: Arc::new(PyRwLock::new(Registry::HKEY_CURRENT_CONFIG)), } + } - fn key_mut(&self) -> PyRwLockWriteGuard<'_, RegKey> { - self.key.write() + #[pyattr(once)] + fn HKEY_DYN_DATA(_vm: &VirtualMachine) -> PyHKEYObject { + PyHKEYObject { + #[allow(clippy::arc_with_non_send_sync)] + hkey: Arc::new(PyRwLock::new(Registry::HKEY_DYN_DATA)), } } - #[pyclass] - impl PyHkey { + #[pyattr] + #[pyclass(name)] + #[derive(Clone, Debug, PyPayload)] + pub struct PyHKEYObject { + hkey: Arc<PyRwLock<Registry::HKEY>>, + } + + // TODO: Fix + unsafe impl Send for PyHKEYObject {} + unsafe impl Sync for PyHKEYObject {} + + #[pyclass(with(AsNumber))] + impl PyHKEYObject { + #[pygetset] + fn handle(&self) -> usize { + *self.hkey.read() as usize + } + #[pymethod] - fn Close(&self) { - let null_key = RegKey::predef(0 as ::winreg::HKEY); - let key = std::mem::replace(&mut *self.key_mut(), null_key); - drop(key); + fn __bool__(&self) -> bool { + !self.hkey.read().is_null() } + #[pymethod] - fn Detach(&self) -> usize { - let null_key = RegKey::predef(0 as ::winreg::HKEY); - let key = std::mem::replace(&mut *self.key_mut(), null_key); - let handle = key.raw_handle(); - std::mem::forget(key); - handle as usize + fn __int__(&self) -> usize { + *self.hkey.read() as usize } #[pymethod] - fn __bool__(&self) -> bool { - !self.key().raw_handle().is_null() + fn __str__(&self) -> String { + format!("<PyHKEY:{}>", *self.hkey.read() as usize) } + #[pymethod] - fn __enter__(zelf: PyRef<Self>) -> PyRef<Self> { - zelf + fn Close(&self, vm: &VirtualMachine) -> PyResult<()> { + let res = unsafe { Registry::RegCloseKey(*self.hkey.write()) }; + *self.hkey.write() = std::ptr::null_mut(); + if res == 0 { + Ok(()) + } else { + Err(vm.new_os_error("msg TODO".to_string())) + } } + #[pymethod] - fn __exit__(&self, _cls: PyObjectRef, _exc: PyObjectRef, _tb: PyObjectRef) { - self.Close(); + fn Detach(&self) -> PyResult<usize> { + let hkey = *self.hkey.write(); + // std::mem::forget(self); + // TODO: Fix this + Ok(hkey as usize) } - } - enum Hkey { - PyHkey(PyHkeyRef), - Constant(::winreg::HKEY), - } - impl TryFromObject for Hkey { - fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { - obj.downcast().map(Self::PyHkey).or_else(|o| { - usize::try_from_object(vm, o).map(|i| Self::Constant(i as ::winreg::HKEY)) - }) + // fn AsHKEY(object: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { + // if vm.is_none(object) { + // return Err(vm.new_type_error("cannot convert None to HKEY".to_owned())) + // } else if let Some(hkey) = object.downcast_ref::<PyHKEYObject>() { + // Ok(true) + // } else { + // Err(vm.new_type_error("The object is not a PyHKEY object".to_owned())) + // } + // } + + #[pymethod] + fn __enter__(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult<PyRef<Self>> { + Ok(zelf) + } + + #[pymethod] + fn __exit__(zelf: PyRef<Self>, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + let res = unsafe { Registry::RegCloseKey(*zelf.hkey.write()) }; + *zelf.hkey.write() = std::ptr::null_mut(); + if res == 0 { + Ok(()) + } else { + Err(vm.new_os_error("msg TODO".to_string())) + } } } - impl Hkey { - fn with_key<R>(&self, f: impl FnOnce(&RegKey) -> R) -> R { - match self { - Self::PyHkey(py) => f(&py.key()), - Self::Constant(hkey) => { - let k = ManuallyDrop::new(RegKey::predef(*hkey)); - f(&k) + + impl Drop for PyHKEYObject { + fn drop(&mut self) { + unsafe { + let hkey = *self.hkey.write(); + if !hkey.is_null() { + Registry::RegCloseKey(hkey); } } } - fn into_key(self) -> RegKey { - let k = match self { - Self::PyHkey(py) => py.key().raw_handle(), - Self::Constant(k) => k, + } + + pub const HKEY_ERR_MSG: &str = "bad operand type"; + + impl PyHKEYObject { + pub fn new(hkey: *mut std::ffi::c_void) -> Self { + Self { + #[allow(clippy::arc_with_non_send_sync)] + hkey: Arc::new(PyRwLock::new(hkey)), + } + } + + pub fn unary_fail(vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error(HKEY_ERR_MSG.to_owned())) + } + + pub fn binary_fail(vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error(HKEY_ERR_MSG.to_owned())) + } + + pub fn ternary_fail(vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error(HKEY_ERR_MSG.to_owned())) + } + } + + impl AsNumber for PyHKEYObject { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + add: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), + subtract: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), + multiply: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), + remainder: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), + divmod: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), + power: Some(|_a, _b, _c, vm| PyHKEYObject::ternary_fail(vm)), + negative: Some(|_a, vm| PyHKEYObject::unary_fail(vm)), + positive: Some(|_a, vm| PyHKEYObject::unary_fail(vm)), + absolute: Some(|_a, vm| PyHKEYObject::unary_fail(vm)), + boolean: Some(|a, vm| { + if let Some(a) = a.downcast_ref::<PyHKEYObject>() { + Ok(a.__bool__()) + } else { + PyHKEYObject::unary_fail(vm)?; + unreachable!() + } + }), + invert: Some(|_a, vm| PyHKEYObject::unary_fail(vm)), + lshift: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), + rshift: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), + and: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), + xor: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), + or: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), + int: Some(|a, vm| { + if let Some(a) = a.downcast_ref::<PyHKEYObject>() { + Ok(vm.new_pyobj(a.__int__())) + } else { + PyHKEYObject::unary_fail(vm)?; + unreachable!() + } + }), + float: Some(|_a, vm| PyHKEYObject::unary_fail(vm)), + ..PyNumberMethods::NOT_IMPLEMENTED }; - RegKey::predef(k) + &AS_NUMBER } } - #[derive(FromArgs)] - struct OpenKeyArgs { - key: Hkey, - sub_key: Option<PyStrRef>, + // TODO: Computer name can be `None` + #[pyfunction] + fn ConnectRegistry( + computer_name: Option<String>, + key: PyRef<PyHKEYObject>, + vm: &VirtualMachine, + ) -> PyResult<PyHKEYObject> { + if let Some(computer_name) = computer_name { + let mut ret_key = std::ptr::null_mut(); + let wide_computer_name = to_utf16(computer_name); + let res = unsafe { + Registry::RegConnectRegistryW( + wide_computer_name.as_ptr(), + *key.hkey.read(), + &mut ret_key, + ) + }; + if res == 0 { + Ok(PyHKEYObject::new(ret_key)) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } + } else { + let mut ret_key = std::ptr::null_mut(); + let res = unsafe { + Registry::RegConnectRegistryW(std::ptr::null_mut(), *key.hkey.read(), &mut ret_key) + }; + if res == 0 { + Ok(PyHKEYObject::new(ret_key)) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } + } + } + + #[pyfunction] + fn CreateKey( + key: PyRef<PyHKEYObject>, + sub_key: String, + vm: &VirtualMachine, + ) -> PyResult<PyHKEYObject> { + let wide_sub_key = to_utf16(sub_key); + let mut out_key = std::ptr::null_mut(); + let res = unsafe { + Registry::RegCreateKeyW(*key.hkey.read(), wide_sub_key.as_ptr(), &mut out_key) + }; + if res == 0 { + Ok(PyHKEYObject::new(out_key)) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } + } + + #[derive(FromArgs, Debug)] + struct CreateKeyExArgs { + #[pyarg(any)] + key: PyRef<PyHKEYObject>, + #[pyarg(any)] + sub_key: String, #[pyarg(any, default = 0)] - reserved: i32, - #[pyarg(any, default = ::winreg::enums::KEY_READ)] + reserved: u32, + #[pyarg(any, default = windows_sys::Win32::System::Registry::KEY_WRITE)] access: u32, } - #[pyfunction(name = "OpenKeyEx")] #[pyfunction] - fn OpenKey(args: OpenKeyArgs, vm: &VirtualMachine) -> PyResult<PyHkey> { - let OpenKeyArgs { - key, - sub_key, - reserved, - access, - } = args; + fn CreateKeyEx(args: CreateKeyExArgs, vm: &VirtualMachine) -> PyResult<PyHKEYObject> { + let wide_sub_key = to_utf16(args.sub_key); + let mut res: *mut std::ffi::c_void = core::ptr::null_mut(); + let err = unsafe { + let key = *args.key.hkey.read(); + Registry::RegCreateKeyExW( + key, + wide_sub_key.as_ptr(), + args.reserved, + std::ptr::null(), + Registry::REG_OPTION_NON_VOLATILE, + args.access, + std::ptr::null(), + &mut res, + std::ptr::null_mut(), + ) + }; + if err == 0 { + Ok(PyHKEYObject { + #[allow(clippy::arc_with_non_send_sync)] + hkey: Arc::new(PyRwLock::new(res)), + }) + } else { + Err(vm.new_os_error(format!("error code: {err}"))) + } + } - if reserved != 0 { - // RegKey::open_subkey* doesn't have a reserved param, so this'll do - return Err(vm.new_value_error("reserved param must be 0")); + #[pyfunction] + fn DeleteKey(key: PyRef<PyHKEYObject>, sub_key: String, vm: &VirtualMachine) -> PyResult<()> { + let wide_sub_key = to_utf16(sub_key); + let res = unsafe { Registry::RegDeleteKeyW(*key.hkey.read(), wide_sub_key.as_ptr()) }; + if res == 0 { + Ok(()) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) } + } - let sub_key = sub_key.as_ref().map_or("", |s| s.as_str()); - let key = key - .with_key(|k| k.open_subkey_with_flags(sub_key, access)) - .map_err(|e| e.to_pyexception(vm))?; + #[derive(FromArgs, Debug)] + struct DeleteKeyExArgs { + #[pyarg(any)] + key: PyRef<PyHKEYObject>, + #[pyarg(any)] + sub_key: String, + #[pyarg(any, default = 0)] + reserved: u32, + #[pyarg(any, default = windows_sys::Win32::System::Registry::KEY_WOW64_32KEY)] + access: u32, + } - Ok(PyHkey::new(key)) + #[pyfunction] + fn DeleteKeyEx(args: DeleteKeyExArgs, vm: &VirtualMachine) -> PyResult<()> { + let wide_sub_key = to_utf16(args.sub_key); + let res = unsafe { + Registry::RegDeleteKeyExW( + *args.key.hkey.read(), + wide_sub_key.as_ptr(), + args.access, + args.reserved, + ) + }; + if res == 0 { + Ok(()) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } } + // #[pyfunction] + // fn EnumKey(key: PyRef<PyHKEYObject>, index: i32, vm: &VirtualMachine) -> PyResult<String> { + // let mut tmpbuf = [0u16; 257]; + // let mut len = std::mem::sizeof(tmpbuf.len())/std::mem::sizeof(tmpbuf[0]); + // let res = unsafe { + // Registry::RegEnumKeyExW( + // *key.hkey.read(), + // index as u32, + // tmpbuf.as_mut_ptr(), + // &mut len, + // std::ptr::null_mut(), + // std::ptr::null_mut(), + // std::ptr::null_mut(), + // std::ptr::null_mut(), + // ) + // }; + // if res != 0 { + // return Err(vm.new_os_error(format!("error code: {res}"))); + // } + // let s = String::from_utf16(&tmpbuf[..len as usize]) + // .map_err(|e| vm.new_value_error(format!("UTF16 error: {e}")))?; + // Ok(s) + // } + #[pyfunction] - fn QueryValue(key: Hkey, subkey: Option<PyStrRef>, vm: &VirtualMachine) -> PyResult<String> { - let subkey = subkey.as_ref().map_or("", |s| s.as_str()); - key.with_key(|k| k.get_value(subkey)) - .map_err(|e| e.to_pyexception(vm)) + fn EnumValue(hkey: PyRef<PyHKEYObject>, index: u32, vm: &VirtualMachine) -> PyResult { + // Query registry for the required buffer sizes. + let mut ret_value_size: u32 = 0; + let mut ret_data_size: u32 = 0; + let hkey: *mut std::ffi::c_void = *hkey.hkey.read(); + let rc = unsafe { + Registry::RegQueryInfoKeyW( + hkey, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + &mut ret_value_size as *mut u32, + &mut ret_data_size as *mut u32, + ptr::null_mut(), + ptr::null_mut(), + ) + }; + if rc != 0 { + return Err(vm.new_os_error(format!("RegQueryInfoKeyW failed with error code {rc}"))); + } + + // Include room for null terminators. + ret_value_size += 1; + ret_data_size += 1; + let mut buf_value_size = ret_value_size; + let mut buf_data_size = ret_data_size; + + // Allocate buffers. + let mut ret_value_buf: Vec<u16> = vec![0; ret_value_size as usize]; + let mut ret_data_buf: Vec<u8> = vec![0; ret_data_size as usize]; + + // Loop to enumerate the registry value. + loop { + let mut current_value_size = ret_value_size; + let mut current_data_size = ret_data_size; + let rc = unsafe { + Registry::RegEnumValueW( + hkey, + index, + ret_value_buf.as_mut_ptr(), + &mut current_value_size as *mut u32, + ptr::null_mut(), + { + // typ will hold the registry data type. + let mut t = 0u32; + &mut t + }, + ret_data_buf.as_mut_ptr(), + &mut current_data_size as *mut u32, + ) + }; + if rc == ERROR_MORE_DATA { + // Double the buffer sizes. + buf_data_size *= 2; + buf_value_size *= 2; + ret_data_buf.resize(buf_data_size as usize, 0); + ret_value_buf.resize(buf_value_size as usize, 0); + // Reset sizes for next iteration. + ret_value_size = buf_value_size; + ret_data_size = buf_data_size; + continue; + } + if rc != 0 { + return Err(vm.new_os_error(format!("RegEnumValueW failed with error code {rc}"))); + } + + // At this point, current_value_size and current_data_size have been updated. + // Retrieve the registry type. + let mut reg_type: u32 = 0; + unsafe { + Registry::RegEnumValueW( + hkey, + index, + ret_value_buf.as_mut_ptr(), + &mut current_value_size as *mut u32, + ptr::null_mut(), + &mut reg_type as *mut u32, + ret_data_buf.as_mut_ptr(), + &mut current_data_size as *mut u32, + ) + }; + + // Convert the registry value name from UTF‑16. + let name_len = ret_value_buf + .iter() + .position(|&c| c == 0) + .unwrap_or(ret_value_buf.len()); + let name = String::from_utf16(&ret_value_buf[..name_len]) + .map_err(|e| vm.new_value_error(format!("UTF16 conversion error: {e}")))?; + + // Slice the data buffer to the actual size returned. + let data_slice = &ret_data_buf[..current_data_size as usize]; + let py_data = reg_to_py(vm, data_slice, reg_type)?; + + // Return tuple (value_name, data, type) + return Ok(vm + .ctx + .new_tuple(vec![ + vm.ctx.new_str(name).into(), + py_data, + vm.ctx.new_int(reg_type).into(), + ]) + .into()); + } } #[pyfunction] - fn QueryValueEx( - key: Hkey, - subkey: Option<PyStrRef>, + fn FlushKey(key: PyRef<PyHKEYObject>, vm: &VirtualMachine) -> PyResult<()> { + let res = unsafe { Registry::RegFlushKey(*key.hkey.read()) }; + if res == 0 { + Ok(()) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } + } + + #[pyfunction] + fn LoadKey( + key: PyRef<PyHKEYObject>, + sub_key: String, + file_name: String, vm: &VirtualMachine, - ) -> PyResult<(PyObjectRef, usize)> { - let subkey = subkey.as_ref().map_or("", |s| s.as_str()); - let regval = key - .with_key(|k| k.get_raw_value(subkey)) - .map_err(|e| e.to_pyexception(vm))?; - #[allow(clippy::redundant_clone)] - let ty = regval.vtype.clone() as usize; - Ok((reg_to_py(regval, vm)?, ty)) + ) -> PyResult<()> { + let sub_key = to_utf16(sub_key); + let file_name = to_utf16(file_name); + let res = unsafe { + Registry::RegLoadKeyW(*key.hkey.read(), sub_key.as_ptr(), file_name.as_ptr()) + }; + if res == 0 { + Ok(()) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } + } + + #[derive(Debug, FromArgs)] + struct OpenKeyArgs { + #[pyarg(any)] + key: PyRef<PyHKEYObject>, + #[pyarg(any)] + sub_key: String, + #[pyarg(any, default = 0)] + reserved: u32, + #[pyarg(any, default = windows_sys::Win32::System::Registry::KEY_READ)] + access: u32, } #[pyfunction] - fn EnumKey(key: Hkey, index: u32, vm: &VirtualMachine) -> PyResult<String> { - key.with_key(|k| k.enum_keys().nth(index as usize)) - .unwrap_or_else(|| { - Err(io::Error::from_raw_os_error( - Foundation::ERROR_NO_MORE_ITEMS as i32, - )) - }) - .map_err(|e| e.to_pyexception(vm)) + #[pyfunction(name = "OpenKeyEx")] + fn OpenKey(args: OpenKeyArgs, vm: &VirtualMachine) -> PyResult<PyHKEYObject> { + let wide_sub_key = to_utf16(args.sub_key); + let res: *mut *mut std::ffi::c_void = core::ptr::null_mut(); + let err = unsafe { + let key = *args.key.hkey.read(); + Registry::RegOpenKeyExW(key, wide_sub_key.as_ptr(), args.reserved, args.access, res) + }; + if err == 0 { + unsafe { + Ok(PyHKEYObject { + #[allow(clippy::arc_with_non_send_sync)] + hkey: Arc::new(PyRwLock::new(*res)), + }) + } + } else { + Err(vm.new_os_error(format!("error code: {err}"))) + } } #[pyfunction] - fn EnumValue( - key: Hkey, - index: u32, - vm: &VirtualMachine, - ) -> PyResult<(String, PyObjectRef, usize)> { - let (name, value) = key - .with_key(|k| k.enum_values().nth(index as usize)) - .unwrap_or_else(|| { - Err(io::Error::from_raw_os_error( - Foundation::ERROR_NO_MORE_ITEMS as i32, - )) - }) - .map_err(|e| e.to_pyexception(vm))?; - #[allow(clippy::redundant_clone)] - let ty = value.vtype.clone() as usize; - Ok((name, reg_to_py(value, vm)?, ty)) + fn QueryInfoKey(key: PyRef<PyHKEYObject>, vm: &VirtualMachine) -> PyResult<PyRef<PyTuple>> { + let key = *key.hkey.read(); + let mut lpcsubkeys: u32 = 0; + let mut lpcvalues: u32 = 0; + let mut lpftlastwritetime: Foundation::FILETIME = unsafe { std::mem::zeroed() }; + let err = unsafe { + Registry::RegQueryInfoKeyW( + key, + std::ptr::null_mut(), + std::ptr::null_mut(), + 0 as _, + &mut lpcsubkeys, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut lpcvalues, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut lpftlastwritetime, + ) + }; + + if err != 0 { + return Err(vm.new_os_error(format!("error code: {err}"))); + } + let l: u64 = (lpftlastwritetime.dwHighDateTime as u64) << 32 + | lpftlastwritetime.dwLowDateTime as u64; + let tup: Vec<PyObjectRef> = vec![ + vm.ctx.new_int(lpcsubkeys).into(), + vm.ctx.new_int(lpcvalues).into(), + vm.ctx.new_int(l).into(), + ]; + Ok(vm.ctx.new_tuple(tup)) } #[pyfunction] - fn CloseKey(key: Hkey) { - match key { - Hkey::PyHkey(py) => py.Close(), - Hkey::Constant(hkey) => drop(RegKey::predef(hkey)), + fn QueryValue(key: PyRef<PyHKEYObject>, sub_key: String, vm: &VirtualMachine) -> PyResult<()> { + let key = *key.hkey.read(); + let mut lpcbdata: i32 = 0; + // let mut lpdata = 0; + let wide_sub_key = to_utf16(sub_key); + let err = unsafe { + Registry::RegQueryValueW( + key, + wide_sub_key.as_ptr(), + std::ptr::null_mut(), + &mut lpcbdata, + ) + }; + + if err != 0 { + return Err(vm.new_os_error(format!("error code: {err}"))); } + + Ok(()) } #[pyfunction] - fn CreateKey(key: Hkey, subkey: Option<PyStrRef>, vm: &VirtualMachine) -> PyResult<PyHkey> { - let k = match subkey { - Some(subkey) => { - let (k, _disp) = key - .with_key(|k| k.create_subkey(subkey.as_str())) - .map_err(|e| e.to_pyexception(vm))?; - k - } - None => key.into_key(), + fn QueryValueEx( + key: PyRef<PyHKEYObject>, + name: String, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + let wide_name = to_utf16(name); + let mut buf_size = 0; + let res = unsafe { + Registry::RegQueryValueExW( + *key.hkey.read(), + wide_name.as_ptr(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut buf_size, + ) }; - Ok(PyHkey::new(k)) + // TODO: res == ERROR_MORE_DATA + if res != 0 { + return Err(vm.new_os_error(format!("error code: {res}"))); + } + let mut retBuf = Vec::with_capacity(buf_size as usize); + let mut typ = 0; + let res = unsafe { + Registry::RegQueryValueExW( + *key.hkey.read(), + wide_name.as_ptr(), + std::ptr::null_mut(), + &mut typ, + retBuf.as_mut_ptr(), + &mut buf_size, + ) + }; + // TODO: res == ERROR_MORE_DATA + if res != 0 { + return Err(vm.new_os_error(format!("error code: {res}"))); + } + let obj = reg_to_py(vm, retBuf.as_slice(), typ)?; + Ok(obj) + } + + #[pyfunction] + fn SaveKey(key: PyRef<PyHKEYObject>, file_name: String, vm: &VirtualMachine) -> PyResult<()> { + let file_name = to_utf16(file_name); + let res = unsafe { + Registry::RegSaveKeyW(*key.hkey.read(), file_name.as_ptr(), std::ptr::null_mut()) + }; + if res == 0 { + Ok(()) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } } #[pyfunction] fn SetValue( - key: Hkey, - subkey: Option<PyStrRef>, + key: PyRef<PyHKEYObject>, + sub_key: String, typ: u32, - value: PyStrRef, + value: String, vm: &VirtualMachine, ) -> PyResult<()> { - if typ != REG_SZ { + if typ != Registry::REG_SZ { return Err(vm.new_type_error("type must be winreg.REG_SZ")); } - let subkey = subkey.as_ref().map_or("", |s| s.as_str()); - key.with_key(|k| k.set_value(subkey, &OsStr::new(value.as_str()))) - .map_err(|e| e.to_pyexception(vm)) - } - #[pyfunction] - fn DeleteKey(key: Hkey, subkey: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { - key.with_key(|k| k.delete_subkey(subkey.as_str())) - .map_err(|e| e.to_pyexception(vm)) + let wide_sub_key = to_utf16(sub_key); + + // TODO: Value check + if *key.hkey.read() == Registry::HKEY_PERFORMANCE_DATA { + return Err(vm.new_os_error("Cannot set value on HKEY_PERFORMANCE_DATA")); + } + + // if (sub_key && sub_key[0]) { + // // TODO: create key + // } + + let res = unsafe { + Registry::RegSetValueExW( + *key.hkey.read(), + wide_sub_key.as_ptr(), + 0, + typ, + value.as_ptr(), + value.len() as u32, + ) + }; + + if res == 0 { + Ok(()) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } } - fn reg_to_py(value: RegValue, vm: &VirtualMachine) -> PyResult { - macro_rules! bytes_to_int { - ($int:ident, $f:ident, $name:ident) => {{ - let i = if value.bytes.is_empty() { - Ok(0 as $int) + fn reg_to_py(vm: &VirtualMachine, ret_data: &[u8], typ: u32) -> PyResult { + match typ { + REG_DWORD => { + // If there isn’t enough data, return 0. + if ret_data.len() < std::mem::size_of::<u32>() { + Ok(vm.ctx.new_int(0).into()) } else { - (&*value.bytes).try_into().map($int::$f).map_err(|_| { - vm.new_value_error(format!("{} value is wrong length", stringify!(name))) - }) - }; - i.map(|i| vm.ctx.new_int(i).into()) - }}; - } - let bytes_to_wide = |b| { - if <[u8]>::len(b) % 2 == 0 { - let (pref, wide, suf) = unsafe { <[u8]>::align_to::<u16>(b) }; - assert!(pref.is_empty() && suf.is_empty(), "wide slice is unaligned"); - Some(wide) - } else { - None + let val = u32::from_ne_bytes(ret_data[..4].try_into().unwrap()); + Ok(vm.ctx.new_int(val).into()) + } } - }; - match value.vtype { - RegType::REG_DWORD => bytes_to_int!(u32, from_ne_bytes, REG_DWORD), - RegType::REG_DWORD_BIG_ENDIAN => { - bytes_to_int!(u32, from_be_bytes, REG_DWORD_BIG_ENDIAN) + REG_QWORD => { + if ret_data.len() < std::mem::size_of::<u64>() { + Ok(vm.ctx.new_int(0).into()) + } else { + let val = u64::from_ne_bytes(ret_data[..8].try_into().unwrap()); + Ok(vm.ctx.new_int(val).into()) + } } - RegType::REG_QWORD => bytes_to_int!(u64, from_ne_bytes, REG_DWORD), - // RegType::REG_QWORD_BIG_ENDIAN => bytes_to_int!(u64, from_be_bytes, REG_DWORD_BIG_ENDIAN), - RegType::REG_SZ | RegType::REG_EXPAND_SZ => { - let wide_slice = bytes_to_wide(&value.bytes).ok_or_else(|| { - vm.new_value_error("REG_SZ string doesn't have an even byte length") - })?; - let nul_pos = wide_slice + REG_SZ | REG_EXPAND_SZ => { + // Treat the data as a UTF-16 string. + let u16_count = ret_data.len() / 2; + let u16_slice = unsafe { + std::slice::from_raw_parts(ret_data.as_ptr() as *const u16, u16_count) + }; + // Only use characters up to the first NUL. + let len = u16_slice .iter() - .position(|w| *w == 0) - .unwrap_or(wide_slice.len()); - let s = String::from_utf16_lossy(&wide_slice[..nul_pos]); + .position(|&c| c == 0) + .unwrap_or(u16_slice.len()); + let s = String::from_utf16(&u16_slice[..len]) + .map_err(|e| vm.new_value_error(format!("UTF16 error: {e}")))?; Ok(vm.ctx.new_str(s).into()) } - RegType::REG_MULTI_SZ => { - if value.bytes.is_empty() { - return Ok(vm.ctx.new_list(vec![]).into()); - } - let wide_slice = bytes_to_wide(&value.bytes).ok_or_else(|| { - vm.new_value_error("REG_MULTI_SZ string doesn't have an even byte length") - })?; - let wide_slice = if let Some((0, rest)) = wide_slice.split_last() { - rest + REG_MULTI_SZ => { + if ret_data.is_empty() { + Ok(vm.ctx.new_list(vec![]).into()) } else { - wide_slice - }; - let strings = wide_slice - .split(|c| *c == 0) - .map(|s| vm.new_pyobj(String::from_utf16_lossy(s))) - .collect(); - Ok(vm.ctx.new_list(strings).into()) + let u16_count = ret_data.len() / 2; + let u16_slice = unsafe { + std::slice::from_raw_parts(ret_data.as_ptr() as *const u16, u16_count) + }; + let mut strings: Vec<PyObjectRef> = Vec::new(); + let mut start = 0; + for (i, &c) in u16_slice.iter().enumerate() { + if c == 0 { + // An empty string signals the end. + if start == i { + break; + } + let s = String::from_utf16(&u16_slice[start..i]) + .map_err(|e| vm.new_value_error(format!("UTF16 error: {e}")))?; + strings.push(vm.ctx.new_str(s).into()); + start = i + 1; + } + } + Ok(vm.ctx.new_list(strings).into()) + } } + // For REG_BINARY and any other unknown types, return a bytes object if data exists. _ => { - if value.bytes.is_empty() { + if ret_data.is_empty() { Ok(vm.ctx.none()) } else { - Ok(vm.ctx.new_bytes(value.bytes).into()) + Ok(vm.ctx.new_bytes(ret_data.to_vec()).into()) } } } } + + fn py2reg(value: PyObjectRef, typ: u32, vm: &VirtualMachine) -> PyResult<Option<Vec<u8>>> { + match typ { + REG_DWORD => { + let val = value.downcast_ref::<PyInt>(); + if val.is_none() { + return Err(vm.new_type_error("value must be an integer".to_string())); + } + let val = val.unwrap().as_bigint().to_u32().unwrap(); + Ok(Some(val.to_le_bytes().to_vec())) + } + REG_QWORD => { + let val = value.downcast_ref::<PyInt>(); + if val.is_none() { + return Err(vm.new_type_error("value must be an integer".to_string())); + } + let val = val.unwrap().as_bigint().to_u64().unwrap(); + Ok(Some(val.to_le_bytes().to_vec())) + } + // REG_SZ is fallthrough + REG_EXPAND_SZ => { + Err(vm + .new_type_error("TODO: RUSTPYTHON REG_EXPAND_SZ is not supported".to_string())) + } + REG_MULTI_SZ => { + Err(vm.new_type_error("TODO: RUSTPYTHON REG_MULTI_SZ is not supported".to_string())) + } + // REG_BINARY is fallthrough + _ => { + if vm.is_none(&value) { + return Ok(None); + } + Err(vm.new_type_error("TODO: RUSTPYTHON Not supported".to_string())) + } + } + } + + #[pyfunction] + fn SetValueEx( + key: PyRef<PyHKEYObject>, + value_name: String, + _reserved: u32, + typ: u32, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + match py2reg(value, typ, vm) { + Ok(Some(v)) => { + let len = v.len() as u32; + let ptr = v.as_ptr(); + let wide_value_name = to_utf16(value_name); + let res = unsafe { + Registry::RegSetValueExW( + *key.hkey.read(), + wide_value_name.as_ptr(), + 0, + typ, + ptr, + len, + ) + }; + if res != 0 { + return Err(vm.new_os_error(format!("error code: {res}"))); + } + } + Ok(None) => { + let len = 0; + let ptr = std::ptr::null(); + let wide_value_name = to_utf16(value_name); + let res = unsafe { + Registry::RegSetValueExW( + *key.hkey.read(), + wide_value_name.as_ptr(), + 0, + typ, + ptr, + len, + ) + }; + if res != 0 { + return Err(vm.new_os_error(format!("error code: {res}"))); + } + } + Err(_) => return Err(vm.new_type_error("value must be an integer".to_string())), + } + Ok(()) + } + + #[pyfunction] + fn EnableReflectionKey(key: PyRef<PyHKEYObject>, vm: &VirtualMachine) -> PyResult<()> { + let res = unsafe { Registry::RegEnableReflectionKey(*key.hkey.read()) }; + if res == 0 { + Ok(()) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } + } + + #[pyfunction] + fn ExpandEnvironmentStrings(i: String) -> PyResult<String> { + let mut out = vec![0; 1024]; + let r = unsafe { + windows_sys::Win32::System::Environment::ExpandEnvironmentStringsA( + i.as_ptr(), + out.as_mut_ptr(), + out.len() as u32, + ) + }; + let s = String::from_utf8(out[..r as usize].to_vec()) + .unwrap() + .replace("\0", "") + .replace("\x02", "") + .to_string(); + + Ok(s) + } } From d8dde123b1489ce78482607369b47764b0475ef8 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Fri, 5 Dec 2025 11:08:45 +0900 Subject: [PATCH 402/819] Complete winreg --- Lib/test/test_winreg.py | 29 +- crates/vm/src/stdlib/winreg.rs | 959 +++++++++++++++++++++------------ 2 files changed, 611 insertions(+), 377 deletions(-) diff --git a/Lib/test/test_winreg.py b/Lib/test/test_winreg.py index 2c530ee754e..924a962781a 100644 --- a/Lib/test/test_winreg.py +++ b/Lib/test/test_winreg.py @@ -212,14 +212,10 @@ def _test_named_args(self, key, sub_key): class LocalWinregTests(BaseWinregTests): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_registry_works(self): self._test_all(HKEY_CURRENT_USER) self._test_all(HKEY_CURRENT_USER, "日本-subkey") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_registry_works_extended_functions(self): # Substitute the regular CreateKey and OpenKey calls with their # extended counterparts. @@ -232,8 +228,6 @@ def test_registry_works_extended_functions(self): self._delete_test_data(HKEY_CURRENT_USER) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_named_arguments(self): self._test_named_args(HKEY_CURRENT_USER, test_key_name) # Use the regular DeleteKey to clean up @@ -251,8 +245,6 @@ def test_nonexistent_remote_registry(self): connect = lambda: ConnectRegistry("abcdefghijkl", HKEY_CURRENT_USER) self.assertRaises(OSError, connect) - # TODO: RUSTPYTHON - @unittest.skip("flaky") def testExpandEnvironmentStrings(self): r = ExpandEnvironmentStrings("%windir%\\test") self.assertEqual(type(r), str) @@ -299,8 +291,6 @@ def run(self): DeleteKey(HKEY_CURRENT_USER, test_key_name+'\\changing_value') DeleteKey(HKEY_CURRENT_USER, test_key_name) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_long_key(self): # Issue2810, in 2.6 and 3.1 when the key name was exactly 256 # characters, EnumKey raised "WindowsError: More data is @@ -315,8 +305,6 @@ def test_long_key(self): DeleteKey(HKEY_CURRENT_USER, '\\'.join((test_key_name, name))) DeleteKey(HKEY_CURRENT_USER, test_key_name) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_dynamic_key(self): # Issue2810, when the value is dynamically generated, these # raise "WindowsError: More data is available" in 2.6 and 3.1 @@ -351,8 +339,6 @@ def test_reflection_unsupported(self): finally: DeleteKey(HKEY_CURRENT_USER, test_key_name) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_setvalueex_value_range(self): # Test for Issue #14420, accept proper ranges for SetValueEx. # Py2Reg, which gets called by SetValueEx, was using PyLong_AsLong, @@ -365,8 +351,6 @@ def test_setvalueex_value_range(self): finally: DeleteKey(HKEY_CURRENT_USER, test_key_name) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_setvalueex_negative_one_check(self): # Test for Issue #43984, check -1 was not set by SetValueEx. # Py2Reg, which gets called by SetValueEx, wasn't checking return @@ -384,8 +368,6 @@ def test_setvalueex_negative_one_check(self): finally: DeleteKey(HKEY_CURRENT_USER, test_key_name) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_queryvalueex_return_value(self): # Test for Issue #16759, return unsigned int from QueryValueEx. # Reg2Py, which gets called by QueryValueEx, was returning a value @@ -402,8 +384,6 @@ def test_queryvalueex_return_value(self): finally: DeleteKey(HKEY_CURRENT_USER, test_key_name) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_setvalueex_crash_with_none_arg(self): # Test for Issue #21151, segfault when None is passed to SetValueEx try: @@ -417,8 +397,6 @@ def test_setvalueex_crash_with_none_arg(self): finally: DeleteKey(HKEY_CURRENT_USER, test_key_name) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_read_string_containing_null(self): # Test for issue 25778: REG_SZ should not contain null characters try: @@ -443,16 +421,13 @@ def test_remote_registry_works(self): @unittest.skipUnless(WIN64_MACHINE, "x64 specific registry tests") class Win64WinregTests(BaseWinregTests): - # TODO: RUSTPYTHON - @unittest.expectedFailure + def test_named_arguments(self): self._test_named_args(HKEY_CURRENT_USER, test_key_name) # Clean up and also exercise the named arguments DeleteKeyEx(key=HKEY_CURRENT_USER, sub_key=test_key_name, access=KEY_ALL_ACCESS, reserved=0) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(win32_edition() in ('WindowsCoreHeadless', 'IoTEdgeOS'), "APIs not available on WindowsCoreHeadless") def test_reflection_functions(self): # Test that we can call the query, enable, and disable functions @@ -538,8 +513,6 @@ def test_disable_reflection(self): DeleteKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, KEY_WOW64_32KEY, 0) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_numbers(self): with self.assertRaises(FileNotFoundError) as ctx: QueryValue(HKEY_CLASSES_ROOT, 'some_value_that_does_not_exist') diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index f3744d93ff2..396f60a2bc2 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -9,25 +9,67 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { #[pymodule] mod winreg { - use std::ffi::OsStr; - use std::os::windows::ffi::OsStrExt; - use std::ptr; - use std::sync::Arc; - - use crate::builtins::{PyInt, PyTuple}; - use crate::common::lock::PyRwLock; + use crate::builtins::{PyInt, PyTuple, PyTypeRef}; + use crate::common::hash::PyHash; + use crate::common::windows::ToWideString; + use crate::convert::TryFromObject; use crate::function::FuncArgs; + use crate::object::AsObject; use crate::protocol::PyNumberMethods; - use crate::types::AsNumber; - use crate::{PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; - + use crate::types::{AsNumber, Hashable}; + use crate::{Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; + use crossbeam_utils::atomic::AtomicCell; + use malachite_bigint::Sign; + use num_traits::ToPrimitive; + use std::ptr; use windows_sys::Win32::Foundation::{self, ERROR_MORE_DATA}; use windows_sys::Win32::System::Registry; - use num_traits::ToPrimitive; + /// Atomic HKEY handle type for lock-free thread-safe access + type AtomicHKEY = AtomicCell<Registry::HKEY>; + + /// Convert byte slice to UTF-16 slice (zero-copy when aligned) + fn bytes_as_wide_slice(bytes: &[u8]) -> &[u16] { + // SAFETY: Windows Registry API returns properly aligned UTF-16 data. + // align_to handles any edge cases safely by returning empty prefix/suffix + // if alignment doesn't match. + let (prefix, u16_slice, suffix) = unsafe { bytes.align_to::<u16>() }; + debug_assert!( + prefix.is_empty() && suffix.is_empty(), + "Registry data should be u16-aligned" + ); + u16_slice + } + + // TODO: check if errno.rs can be reused here or not + fn os_error_from_windows_code( + vm: &VirtualMachine, + code: i32, + func_name: &str, + ) -> crate::PyRef<crate::builtins::PyBaseException> { + use windows_sys::Win32::Foundation::{ERROR_ACCESS_DENIED, ERROR_FILE_NOT_FOUND}; + let msg = format!("[WinError {}] {}", code, func_name); + let exc_type = match code as u32 { + ERROR_FILE_NOT_FOUND => vm.ctx.exceptions.file_not_found_error.to_owned(), + ERROR_ACCESS_DENIED => vm.ctx.exceptions.permission_error.to_owned(), + _ => vm.ctx.exceptions.os_error.to_owned(), + }; + vm.new_exception_msg(exc_type, msg) + } + + /// Wrapper type for HKEY that can be created from PyHkey or int + struct HKEYArg(Registry::HKEY); - pub(crate) fn to_utf16<P: AsRef<OsStr>>(s: P) -> Vec<u16> { - s.as_ref().encode_wide().chain(Some(0)).collect() + impl TryFromObject for HKEYArg { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { + // Try PyHkey first + if let Some(hkey_obj) = obj.downcast_ref::<PyHkey>() { + return Ok(HKEYArg(hkey_obj.hkey.load())); + } + // Then try int + let handle = usize::try_from_object(vm, obj)?; + Ok(HKEYArg(handle as Registry::HKEY)) + } } // access rights @@ -49,123 +91,131 @@ mod winreg { REG_RESOURCE_REQUIREMENTS_LIST, REG_SZ, REG_WHOLE_HIVE_VOLATILE, }; + // Additional constants not in windows-sys + #[pyattr] + const REG_REFRESH_HIVE: u32 = 0x00000002; + #[pyattr] + const REG_NO_LAZY_FLUSH: u32 = 0x00000004; + // REG_LEGAL_OPTION is a mask of all option flags + #[pyattr] + const REG_LEGAL_OPTION: u32 = Registry::REG_OPTION_RESERVED + | Registry::REG_OPTION_NON_VOLATILE + | Registry::REG_OPTION_VOLATILE + | Registry::REG_OPTION_CREATE_LINK + | Registry::REG_OPTION_BACKUP_RESTORE + | Registry::REG_OPTION_OPEN_LINK; + // REG_LEGAL_CHANGE_FILTER is a mask of all notify flags + #[pyattr] + const REG_LEGAL_CHANGE_FILTER: u32 = Registry::REG_NOTIFY_CHANGE_NAME + | Registry::REG_NOTIFY_CHANGE_ATTRIBUTES + | Registry::REG_NOTIFY_CHANGE_LAST_SET + | Registry::REG_NOTIFY_CHANGE_SECURITY; + + // error is an alias for OSError (for backwards compatibility) + #[pyattr] + fn error(vm: &VirtualMachine) -> PyTypeRef { + vm.ctx.exceptions.os_error.to_owned() + } + #[pyattr(once)] - fn HKEY_CLASSES_ROOT(_vm: &VirtualMachine) -> PyHKEYObject { - PyHKEYObject { - #[allow(clippy::arc_with_non_send_sync)] - hkey: Arc::new(PyRwLock::new(Registry::HKEY_CLASSES_ROOT)), - } + fn HKEY_CLASSES_ROOT(vm: &VirtualMachine) -> PyRef<PyHkey> { + PyHkey::new(Registry::HKEY_CLASSES_ROOT).into_ref(&vm.ctx) } #[pyattr(once)] - fn HKEY_CURRENT_USER(_vm: &VirtualMachine) -> PyHKEYObject { - PyHKEYObject { - #[allow(clippy::arc_with_non_send_sync)] - hkey: Arc::new(PyRwLock::new(Registry::HKEY_CURRENT_USER)), - } + fn HKEY_CURRENT_USER(vm: &VirtualMachine) -> PyRef<PyHkey> { + PyHkey::new(Registry::HKEY_CURRENT_USER).into_ref(&vm.ctx) } #[pyattr(once)] - fn HKEY_LOCAL_MACHINE(_vm: &VirtualMachine) -> PyHKEYObject { - PyHKEYObject { - #[allow(clippy::arc_with_non_send_sync)] - hkey: Arc::new(PyRwLock::new(Registry::HKEY_LOCAL_MACHINE)), - } + fn HKEY_LOCAL_MACHINE(vm: &VirtualMachine) -> PyRef<PyHkey> { + PyHkey::new(Registry::HKEY_LOCAL_MACHINE).into_ref(&vm.ctx) } #[pyattr(once)] - fn HKEY_USERS(_vm: &VirtualMachine) -> PyHKEYObject { - PyHKEYObject { - #[allow(clippy::arc_with_non_send_sync)] - hkey: Arc::new(PyRwLock::new(Registry::HKEY_USERS)), - } + fn HKEY_USERS(vm: &VirtualMachine) -> PyRef<PyHkey> { + PyHkey::new(Registry::HKEY_USERS).into_ref(&vm.ctx) } #[pyattr(once)] - fn HKEY_PERFORMANCE_DATA(_vm: &VirtualMachine) -> PyHKEYObject { - PyHKEYObject { - #[allow(clippy::arc_with_non_send_sync)] - hkey: Arc::new(PyRwLock::new(Registry::HKEY_PERFORMANCE_DATA)), - } + fn HKEY_PERFORMANCE_DATA(vm: &VirtualMachine) -> PyRef<PyHkey> { + PyHkey::new(Registry::HKEY_PERFORMANCE_DATA).into_ref(&vm.ctx) } #[pyattr(once)] - fn HKEY_CURRENT_CONFIG(_vm: &VirtualMachine) -> PyHKEYObject { - PyHKEYObject { - #[allow(clippy::arc_with_non_send_sync)] - hkey: Arc::new(PyRwLock::new(Registry::HKEY_CURRENT_CONFIG)), - } + fn HKEY_CURRENT_CONFIG(vm: &VirtualMachine) -> PyRef<PyHkey> { + PyHkey::new(Registry::HKEY_CURRENT_CONFIG).into_ref(&vm.ctx) } #[pyattr(once)] - fn HKEY_DYN_DATA(_vm: &VirtualMachine) -> PyHKEYObject { - PyHKEYObject { - #[allow(clippy::arc_with_non_send_sync)] - hkey: Arc::new(PyRwLock::new(Registry::HKEY_DYN_DATA)), - } + fn HKEY_DYN_DATA(vm: &VirtualMachine) -> PyRef<PyHkey> { + PyHkey::new(Registry::HKEY_DYN_DATA).into_ref(&vm.ctx) } #[pyattr] - #[pyclass(name)] - #[derive(Clone, Debug, PyPayload)] - pub struct PyHKEYObject { - hkey: Arc<PyRwLock<Registry::HKEY>>, + #[pyclass(name = "HKEYType")] + #[derive(Debug, PyPayload)] + struct PyHkey { + hkey: AtomicHKEY, } - // TODO: Fix - unsafe impl Send for PyHKEYObject {} - unsafe impl Sync for PyHKEYObject {} + unsafe impl Send for PyHkey {} + unsafe impl Sync for PyHkey {} - #[pyclass(with(AsNumber))] - impl PyHKEYObject { - #[pygetset] - fn handle(&self) -> usize { - *self.hkey.read() as usize + impl PyHkey { + fn new(hkey: Registry::HKEY) -> Self { + Self { + hkey: AtomicHKEY::new(hkey), + } } - #[pymethod] - fn __bool__(&self) -> bool { - !self.hkey.read().is_null() + fn unary_fail(vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error(HKEY_ERR_MSG.to_owned())) } - #[pymethod] - fn __int__(&self) -> usize { - *self.hkey.read() as usize + fn binary_fail(vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error(HKEY_ERR_MSG.to_owned())) } - #[pymethod] - fn __str__(&self) -> String { - format!("<PyHKEY:{}>", *self.hkey.read() as usize) + fn ternary_fail(vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error(HKEY_ERR_MSG.to_owned())) + } + } + + #[pyclass(with(AsNumber, Hashable))] + impl PyHkey { + #[pygetset] + fn handle(&self) -> usize { + self.hkey.load() as usize } #[pymethod] fn Close(&self, vm: &VirtualMachine) -> PyResult<()> { - let res = unsafe { Registry::RegCloseKey(*self.hkey.write()) }; - *self.hkey.write() = std::ptr::null_mut(); + // Atomically swap the handle with null and get the old value + let old_hkey = self.hkey.swap(std::ptr::null_mut()); + // Already closed - silently succeed + if old_hkey.is_null() { + return Ok(()); + } + let res = unsafe { Registry::RegCloseKey(old_hkey) }; if res == 0 { Ok(()) } else { - Err(vm.new_os_error("msg TODO".to_string())) + Err(vm.new_os_error(format!("RegCloseKey failed with error code: {res}"))) } } #[pymethod] fn Detach(&self) -> PyResult<usize> { - let hkey = *self.hkey.write(); - // std::mem::forget(self); - // TODO: Fix this - Ok(hkey as usize) - } - - // fn AsHKEY(object: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { - // if vm.is_none(object) { - // return Err(vm.new_type_error("cannot convert None to HKEY".to_owned())) - // } else if let Some(hkey) = object.downcast_ref::<PyHKEYObject>() { - // Ok(true) - // } else { - // Err(vm.new_type_error("The object is not a PyHKEY object".to_owned())) - // } - // } + // Atomically swap the handle with null and return the old value + let old_hkey = self.hkey.swap(std::ptr::null_mut()); + Ok(old_hkey as usize) + } + + #[pymethod] + fn __bool__(&self) -> bool { + !self.hkey.load().is_null() + } #[pymethod] fn __enter__(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult<PyRef<Self>> { @@ -174,120 +224,107 @@ mod winreg { #[pymethod] fn __exit__(zelf: PyRef<Self>, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - let res = unsafe { Registry::RegCloseKey(*zelf.hkey.write()) }; - *zelf.hkey.write() = std::ptr::null_mut(); - if res == 0 { - Ok(()) - } else { - Err(vm.new_os_error("msg TODO".to_string())) - } + zelf.Close(vm) } - } - impl Drop for PyHKEYObject { - fn drop(&mut self) { - unsafe { - let hkey = *self.hkey.write(); - if !hkey.is_null() { - Registry::RegCloseKey(hkey); - } - } - } - } - - pub const HKEY_ERR_MSG: &str = "bad operand type"; - - impl PyHKEYObject { - pub fn new(hkey: *mut std::ffi::c_void) -> Self { - Self { - #[allow(clippy::arc_with_non_send_sync)] - hkey: Arc::new(PyRwLock::new(hkey)), - } + #[pymethod] + fn __int__(&self) -> usize { + self.hkey.load() as usize } - pub fn unary_fail(vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error(HKEY_ERR_MSG.to_owned())) + #[pymethod] + fn __str__(&self) -> String { + format!("<PyHkey:{:p}>", self.hkey.load()) } + } - pub fn binary_fail(vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error(HKEY_ERR_MSG.to_owned())) + impl Drop for PyHkey { + fn drop(&mut self) { + let hkey = self.hkey.swap(std::ptr::null_mut()); + if !hkey.is_null() { + unsafe { Registry::RegCloseKey(hkey) }; + } } + } - pub fn ternary_fail(vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error(HKEY_ERR_MSG.to_owned())) + impl Hashable for PyHkey { + // CPython uses PyObject_GenericHash which hashes the object's address + fn hash(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyHash> { + Ok(zelf.get_id() as PyHash) } } - impl AsNumber for PyHKEYObject { + pub const HKEY_ERR_MSG: &str = "bad operand type"; + + impl AsNumber for PyHkey { fn as_number() -> &'static PyNumberMethods { static AS_NUMBER: PyNumberMethods = PyNumberMethods { - add: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), - subtract: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), - multiply: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), - remainder: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), - divmod: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), - power: Some(|_a, _b, _c, vm| PyHKEYObject::ternary_fail(vm)), - negative: Some(|_a, vm| PyHKEYObject::unary_fail(vm)), - positive: Some(|_a, vm| PyHKEYObject::unary_fail(vm)), - absolute: Some(|_a, vm| PyHKEYObject::unary_fail(vm)), + add: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), + subtract: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), + multiply: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), + remainder: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), + divmod: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), + power: Some(|_a, _b, _c, vm| PyHkey::ternary_fail(vm)), + negative: Some(|_a, vm| PyHkey::unary_fail(vm)), + positive: Some(|_a, vm| PyHkey::unary_fail(vm)), + absolute: Some(|_a, vm| PyHkey::unary_fail(vm)), boolean: Some(|a, vm| { - if let Some(a) = a.downcast_ref::<PyHKEYObject>() { + if let Some(a) = a.downcast_ref::<PyHkey>() { Ok(a.__bool__()) } else { - PyHKEYObject::unary_fail(vm)?; + PyHkey::unary_fail(vm)?; unreachable!() } }), - invert: Some(|_a, vm| PyHKEYObject::unary_fail(vm)), - lshift: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), - rshift: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), - and: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), - xor: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), - or: Some(|_a, _b, vm| PyHKEYObject::binary_fail(vm)), + invert: Some(|_a, vm| PyHkey::unary_fail(vm)), + lshift: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), + rshift: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), + and: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), + xor: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), + or: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), int: Some(|a, vm| { - if let Some(a) = a.downcast_ref::<PyHKEYObject>() { + if let Some(a) = a.downcast_ref::<PyHkey>() { Ok(vm.new_pyobj(a.__int__())) } else { - PyHKEYObject::unary_fail(vm)?; + PyHkey::unary_fail(vm)?; unreachable!() } }), - float: Some(|_a, vm| PyHKEYObject::unary_fail(vm)), + float: Some(|_a, vm| PyHkey::unary_fail(vm)), ..PyNumberMethods::NOT_IMPLEMENTED }; &AS_NUMBER } } - // TODO: Computer name can be `None` #[pyfunction] fn ConnectRegistry( computer_name: Option<String>, - key: PyRef<PyHKEYObject>, + key: PyRef<PyHkey>, vm: &VirtualMachine, - ) -> PyResult<PyHKEYObject> { + ) -> PyResult<PyHkey> { if let Some(computer_name) = computer_name { let mut ret_key = std::ptr::null_mut(); - let wide_computer_name = to_utf16(computer_name); + let wide_computer_name = computer_name.to_wide_with_nul(); let res = unsafe { Registry::RegConnectRegistryW( wide_computer_name.as_ptr(), - *key.hkey.read(), + key.hkey.load(), &mut ret_key, ) }; if res == 0 { - Ok(PyHKEYObject::new(ret_key)) + Ok(PyHkey::new(ret_key)) } else { Err(vm.new_os_error(format!("error code: {res}"))) } } else { let mut ret_key = std::ptr::null_mut(); let res = unsafe { - Registry::RegConnectRegistryW(std::ptr::null_mut(), *key.hkey.read(), &mut ret_key) + Registry::RegConnectRegistryW(std::ptr::null_mut(), key.hkey.load(), &mut ret_key) }; if res == 0 { - Ok(PyHKEYObject::new(ret_key)) + Ok(PyHkey::new(ret_key)) } else { Err(vm.new_os_error(format!("error code: {res}"))) } @@ -295,18 +332,14 @@ mod winreg { } #[pyfunction] - fn CreateKey( - key: PyRef<PyHKEYObject>, - sub_key: String, - vm: &VirtualMachine, - ) -> PyResult<PyHKEYObject> { - let wide_sub_key = to_utf16(sub_key); + fn CreateKey(key: PyRef<PyHkey>, sub_key: String, vm: &VirtualMachine) -> PyResult<PyHkey> { + let wide_sub_key = sub_key.to_wide_with_nul(); let mut out_key = std::ptr::null_mut(); let res = unsafe { - Registry::RegCreateKeyW(*key.hkey.read(), wide_sub_key.as_ptr(), &mut out_key) + Registry::RegCreateKeyW(key.hkey.load(), wide_sub_key.as_ptr(), &mut out_key) }; if res == 0 { - Ok(PyHKEYObject::new(out_key)) + Ok(PyHkey::new(out_key)) } else { Err(vm.new_os_error(format!("error code: {res}"))) } @@ -315,7 +348,7 @@ mod winreg { #[derive(FromArgs, Debug)] struct CreateKeyExArgs { #[pyarg(any)] - key: PyRef<PyHKEYObject>, + key: PyRef<PyHkey>, #[pyarg(any)] sub_key: String, #[pyarg(any, default = 0)] @@ -325,11 +358,11 @@ mod winreg { } #[pyfunction] - fn CreateKeyEx(args: CreateKeyExArgs, vm: &VirtualMachine) -> PyResult<PyHKEYObject> { - let wide_sub_key = to_utf16(args.sub_key); - let mut res: *mut std::ffi::c_void = core::ptr::null_mut(); + fn CreateKeyEx(args: CreateKeyExArgs, vm: &VirtualMachine) -> PyResult<PyHkey> { + let wide_sub_key = args.sub_key.to_wide_with_nul(); + let mut res: Registry::HKEY = core::ptr::null_mut(); let err = unsafe { - let key = *args.key.hkey.read(); + let key = args.key.hkey.load(); Registry::RegCreateKeyExW( key, wide_sub_key.as_ptr(), @@ -343,9 +376,9 @@ mod winreg { ) }; if err == 0 { - Ok(PyHKEYObject { + Ok(PyHkey { #[allow(clippy::arc_with_non_send_sync)] - hkey: Arc::new(PyRwLock::new(res)), + hkey: AtomicHKEY::new(res), }) } else { Err(vm.new_os_error(format!("error code: {err}"))) @@ -353,9 +386,26 @@ mod winreg { } #[pyfunction] - fn DeleteKey(key: PyRef<PyHKEYObject>, sub_key: String, vm: &VirtualMachine) -> PyResult<()> { - let wide_sub_key = to_utf16(sub_key); - let res = unsafe { Registry::RegDeleteKeyW(*key.hkey.read(), wide_sub_key.as_ptr()) }; + fn CloseKey(hkey: PyRef<PyHkey>, vm: &VirtualMachine) -> PyResult<()> { + hkey.Close(vm) + } + + #[pyfunction] + fn DeleteKey(key: PyRef<PyHkey>, sub_key: String, vm: &VirtualMachine) -> PyResult<()> { + let wide_sub_key = sub_key.to_wide_with_nul(); + let res = unsafe { Registry::RegDeleteKeyW(key.hkey.load(), wide_sub_key.as_ptr()) }; + if res == 0 { + Ok(()) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } + } + + #[pyfunction] + fn DeleteValue(key: PyRef<PyHkey>, value: Option<String>, vm: &VirtualMachine) -> PyResult<()> { + let wide_value = value.map(|v| v.to_wide_with_nul()); + let value_ptr = wide_value.as_ref().map_or(std::ptr::null(), |v| v.as_ptr()); + let res = unsafe { Registry::RegDeleteValueW(key.hkey.load(), value_ptr) }; if res == 0 { Ok(()) } else { @@ -366,21 +416,21 @@ mod winreg { #[derive(FromArgs, Debug)] struct DeleteKeyExArgs { #[pyarg(any)] - key: PyRef<PyHKEYObject>, + key: PyRef<PyHkey>, #[pyarg(any)] sub_key: String, + #[pyarg(any, default = windows_sys::Win32::System::Registry::KEY_WOW64_64KEY)] + access: u32, #[pyarg(any, default = 0)] reserved: u32, - #[pyarg(any, default = windows_sys::Win32::System::Registry::KEY_WOW64_32KEY)] - access: u32, } #[pyfunction] fn DeleteKeyEx(args: DeleteKeyExArgs, vm: &VirtualMachine) -> PyResult<()> { - let wide_sub_key = to_utf16(args.sub_key); + let wide_sub_key = args.sub_key.to_wide_with_nul(); let res = unsafe { Registry::RegDeleteKeyExW( - *args.key.hkey.read(), + args.key.hkey.load(), wide_sub_key.as_ptr(), args.access, args.reserved, @@ -393,36 +443,41 @@ mod winreg { } } - // #[pyfunction] - // fn EnumKey(key: PyRef<PyHKEYObject>, index: i32, vm: &VirtualMachine) -> PyResult<String> { - // let mut tmpbuf = [0u16; 257]; - // let mut len = std::mem::sizeof(tmpbuf.len())/std::mem::sizeof(tmpbuf[0]); - // let res = unsafe { - // Registry::RegEnumKeyExW( - // *key.hkey.read(), - // index as u32, - // tmpbuf.as_mut_ptr(), - // &mut len, - // std::ptr::null_mut(), - // std::ptr::null_mut(), - // std::ptr::null_mut(), - // std::ptr::null_mut(), - // ) - // }; - // if res != 0 { - // return Err(vm.new_os_error(format!("error code: {res}"))); - // } - // let s = String::from_utf16(&tmpbuf[..len as usize]) - // .map_err(|e| vm.new_value_error(format!("UTF16 error: {e}")))?; - // Ok(s) - // } + #[pyfunction] + fn EnumKey(key: PyRef<PyHkey>, index: i32, vm: &VirtualMachine) -> PyResult<String> { + // The Windows docs claim that the max key name length is 255 + // characters, plus a terminating nul character. However, + // empirical testing demonstrates that it is possible to + // create a 256 character key that is missing the terminating + // nul. RegEnumKeyEx requires a 257 character buffer to + // retrieve such a key name. + let mut tmpbuf = [0u16; 257]; + let mut len = tmpbuf.len() as u32; + let res = unsafe { + Registry::RegEnumKeyExW( + key.hkey.load(), + index as u32, + tmpbuf.as_mut_ptr(), + &mut len, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + ) + }; + if res != 0 { + return Err(vm.new_os_error(format!("error code: {res}"))); + } + String::from_utf16(&tmpbuf[..len as usize]) + .map_err(|e| vm.new_value_error(format!("UTF16 error: {e}"))) + } #[pyfunction] - fn EnumValue(hkey: PyRef<PyHKEYObject>, index: u32, vm: &VirtualMachine) -> PyResult { + fn EnumValue(hkey: PyRef<PyHkey>, index: u32, vm: &VirtualMachine) -> PyResult { // Query registry for the required buffer sizes. let mut ret_value_size: u32 = 0; let mut ret_data_size: u32 = 0; - let hkey: *mut std::ffi::c_void = *hkey.hkey.read(); + let hkey: Registry::HKEY = hkey.hkey.load(); let rc = unsafe { Registry::RegQueryInfoKeyW( hkey, @@ -457,6 +512,7 @@ mod winreg { loop { let mut current_value_size = ret_value_size; let mut current_data_size = ret_data_size; + let mut reg_type: u32 = 0; let rc = unsafe { Registry::RegEnumValueW( hkey, @@ -464,11 +520,7 @@ mod winreg { ret_value_buf.as_mut_ptr(), &mut current_value_size as *mut u32, ptr::null_mut(), - { - // typ will hold the registry data type. - let mut t = 0u32; - &mut t - }, + &mut reg_type as *mut u32, ret_data_buf.as_mut_ptr(), &mut current_data_size as *mut u32, ) @@ -488,22 +540,6 @@ mod winreg { return Err(vm.new_os_error(format!("RegEnumValueW failed with error code {rc}"))); } - // At this point, current_value_size and current_data_size have been updated. - // Retrieve the registry type. - let mut reg_type: u32 = 0; - unsafe { - Registry::RegEnumValueW( - hkey, - index, - ret_value_buf.as_mut_ptr(), - &mut current_value_size as *mut u32, - ptr::null_mut(), - &mut reg_type as *mut u32, - ret_data_buf.as_mut_ptr(), - &mut current_data_size as *mut u32, - ) - }; - // Convert the registry value name from UTF‑16. let name_len = ret_value_buf .iter() @@ -529,8 +565,8 @@ mod winreg { } #[pyfunction] - fn FlushKey(key: PyRef<PyHKEYObject>, vm: &VirtualMachine) -> PyResult<()> { - let res = unsafe { Registry::RegFlushKey(*key.hkey.read()) }; + fn FlushKey(key: PyRef<PyHkey>, vm: &VirtualMachine) -> PyResult<()> { + let res = unsafe { Registry::RegFlushKey(key.hkey.load()) }; if res == 0 { Ok(()) } else { @@ -540,16 +576,15 @@ mod winreg { #[pyfunction] fn LoadKey( - key: PyRef<PyHKEYObject>, + key: PyRef<PyHkey>, sub_key: String, file_name: String, vm: &VirtualMachine, ) -> PyResult<()> { - let sub_key = to_utf16(sub_key); - let file_name = to_utf16(file_name); - let res = unsafe { - Registry::RegLoadKeyW(*key.hkey.read(), sub_key.as_ptr(), file_name.as_ptr()) - }; + let sub_key = sub_key.to_wide_with_nul(); + let file_name = file_name.to_wide_with_nul(); + let res = + unsafe { Registry::RegLoadKeyW(key.hkey.load(), sub_key.as_ptr(), file_name.as_ptr()) }; if res == 0 { Ok(()) } else { @@ -560,7 +595,7 @@ mod winreg { #[derive(Debug, FromArgs)] struct OpenKeyArgs { #[pyarg(any)] - key: PyRef<PyHKEYObject>, + key: PyRef<PyHkey>, #[pyarg(any)] sub_key: String, #[pyarg(any, default = 0)] @@ -571,28 +606,32 @@ mod winreg { #[pyfunction] #[pyfunction(name = "OpenKeyEx")] - fn OpenKey(args: OpenKeyArgs, vm: &VirtualMachine) -> PyResult<PyHKEYObject> { - let wide_sub_key = to_utf16(args.sub_key); - let res: *mut *mut std::ffi::c_void = core::ptr::null_mut(); + fn OpenKey(args: OpenKeyArgs, vm: &VirtualMachine) -> PyResult<PyHkey> { + let wide_sub_key = args.sub_key.to_wide_with_nul(); + let mut res: Registry::HKEY = std::ptr::null_mut(); let err = unsafe { - let key = *args.key.hkey.read(); - Registry::RegOpenKeyExW(key, wide_sub_key.as_ptr(), args.reserved, args.access, res) + let key = args.key.hkey.load(); + Registry::RegOpenKeyExW( + key, + wide_sub_key.as_ptr(), + args.reserved, + args.access, + &mut res, + ) }; if err == 0 { - unsafe { - Ok(PyHKEYObject { - #[allow(clippy::arc_with_non_send_sync)] - hkey: Arc::new(PyRwLock::new(*res)), - }) - } + Ok(PyHkey { + #[allow(clippy::arc_with_non_send_sync)] + hkey: AtomicHKEY::new(res), + }) } else { - Err(vm.new_os_error(format!("error code: {err}"))) + Err(os_error_from_windows_code(vm, err as i32, "RegOpenKeyEx")) } } #[pyfunction] - fn QueryInfoKey(key: PyRef<PyHKEYObject>, vm: &VirtualMachine) -> PyResult<PyRef<PyTuple>> { - let key = *key.hkey.read(); + fn QueryInfoKey(key: HKEYArg, vm: &VirtualMachine) -> PyResult<PyRef<PyTuple>> { + let key = key.0; let mut lpcsubkeys: u32 = 0; let mut lpcvalues: u32 = 0; let mut lpftlastwritetime: Foundation::FILETIME = unsafe { std::mem::zeroed() }; @@ -627,38 +666,110 @@ mod winreg { } #[pyfunction] - fn QueryValue(key: PyRef<PyHKEYObject>, sub_key: String, vm: &VirtualMachine) -> PyResult<()> { - let key = *key.hkey.read(); - let mut lpcbdata: i32 = 0; - // let mut lpdata = 0; - let wide_sub_key = to_utf16(sub_key); - let err = unsafe { - Registry::RegQueryValueW( - key, - wide_sub_key.as_ptr(), - std::ptr::null_mut(), - &mut lpcbdata, - ) + fn QueryValue(key: HKEYArg, sub_key: Option<String>, vm: &VirtualMachine) -> PyResult<String> { + let hkey = key.0; + + if hkey == Registry::HKEY_PERFORMANCE_DATA { + return Err(os_error_from_windows_code( + vm, + Foundation::ERROR_INVALID_HANDLE as i32, + "RegQueryValue", + )); + } + + // Open subkey if provided and non-empty + let child_key = if let Some(ref sk) = sub_key { + if !sk.is_empty() { + let wide_sub_key = sk.to_wide_with_nul(); + let mut out_key = std::ptr::null_mut(); + let res = unsafe { + Registry::RegOpenKeyExW( + hkey, + wide_sub_key.as_ptr(), + 0, + Registry::KEY_QUERY_VALUE, + &mut out_key, + ) + }; + if res != 0 { + return Err(os_error_from_windows_code(vm, res as i32, "RegOpenKeyEx")); + } + Some(out_key) + } else { + None + } + } else { + None }; - if err != 0 { - return Err(vm.new_os_error(format!("error code: {err}"))); + let target_key = child_key.unwrap_or(hkey); + let mut buf_size: u32 = 256; + let mut buffer: Vec<u8> = vec![0; buf_size as usize]; + let mut reg_type: u32 = 0; + + // Loop to handle ERROR_MORE_DATA + let result = loop { + let mut size = buf_size; + let res = unsafe { + Registry::RegQueryValueExW( + target_key, + std::ptr::null(), // NULL value name for default value + std::ptr::null_mut(), + &mut reg_type, + buffer.as_mut_ptr(), + &mut size, + ) + }; + if res == ERROR_MORE_DATA { + buf_size *= 2; + buffer.resize(buf_size as usize, 0); + continue; + } + if res == Foundation::ERROR_FILE_NOT_FOUND { + // Return empty string if there's no default value + break Ok(String::new()); + } + if res != 0 { + break Err(os_error_from_windows_code( + vm, + res as i32, + "RegQueryValueEx", + )); + } + if reg_type != Registry::REG_SZ { + break Err(os_error_from_windows_code( + vm, + Foundation::ERROR_INVALID_DATA as i32, + "RegQueryValue", + )); + } + + // Convert UTF-16 to String + let u16_slice = bytes_as_wide_slice(&buffer[..size as usize]); + let len = u16_slice + .iter() + .position(|&c| c == 0) + .unwrap_or(u16_slice.len()); + break String::from_utf16(&u16_slice[..len]) + .map_err(|e| vm.new_value_error(format!("UTF16 error: {e}"))); + }; + + // Close child key if we opened one + if let Some(ck) = child_key { + unsafe { Registry::RegCloseKey(ck) }; } - Ok(()) + result } #[pyfunction] - fn QueryValueEx( - key: PyRef<PyHKEYObject>, - name: String, - vm: &VirtualMachine, - ) -> PyResult<PyObjectRef> { - let wide_name = to_utf16(name); - let mut buf_size = 0; + fn QueryValueEx(key: HKEYArg, name: String, vm: &VirtualMachine) -> PyResult<PyRef<PyTuple>> { + let hkey = key.0; + let wide_name = name.to_wide_with_nul(); + let mut buf_size: u32 = 0; let res = unsafe { Registry::RegQueryValueExW( - *key.hkey.read(), + hkey, wide_name.as_ptr(), std::ptr::null_mut(), std::ptr::null_mut(), @@ -666,35 +777,62 @@ mod winreg { &mut buf_size, ) }; - // TODO: res == ERROR_MORE_DATA - if res != 0 { - return Err(vm.new_os_error(format!("error code: {res}"))); + // Handle ERROR_MORE_DATA by using a default buffer size + if res == ERROR_MORE_DATA || buf_size == 0 { + buf_size = 256; + } else if res != 0 { + return Err(os_error_from_windows_code( + vm, + res as i32, + "RegQueryValueEx", + )); } - let mut retBuf = Vec::with_capacity(buf_size as usize); + + let mut ret_buf = vec![0u8; buf_size as usize]; let mut typ = 0; - let res = unsafe { - Registry::RegQueryValueExW( - *key.hkey.read(), - wide_name.as_ptr(), - std::ptr::null_mut(), - &mut typ, - retBuf.as_mut_ptr(), - &mut buf_size, - ) - }; - // TODO: res == ERROR_MORE_DATA - if res != 0 { - return Err(vm.new_os_error(format!("error code: {res}"))); + let mut ret_size: u32; + + // Loop to handle ERROR_MORE_DATA + loop { + ret_size = buf_size; + let res = unsafe { + Registry::RegQueryValueExW( + hkey, + wide_name.as_ptr(), + std::ptr::null_mut(), + &mut typ, + ret_buf.as_mut_ptr(), + &mut ret_size, + ) + }; + + if res != ERROR_MORE_DATA { + if res != 0 { + return Err(os_error_from_windows_code( + vm, + res as i32, + "RegQueryValueEx", + )); + } + break; + } + + // Double buffer size and retry + buf_size *= 2; + ret_buf.resize(buf_size as usize, 0); } - let obj = reg_to_py(vm, retBuf.as_slice(), typ)?; - Ok(obj) + + // Only pass the bytes actually returned by the API + let obj = reg_to_py(vm, &ret_buf[..ret_size as usize], typ)?; + // Return tuple (value, type) + Ok(vm.ctx.new_tuple(vec![obj, vm.ctx.new_int(typ).into()])) } #[pyfunction] - fn SaveKey(key: PyRef<PyHKEYObject>, file_name: String, vm: &VirtualMachine) -> PyResult<()> { - let file_name = to_utf16(file_name); + fn SaveKey(key: PyRef<PyHkey>, file_name: String, vm: &VirtualMachine) -> PyResult<()> { + let file_name = file_name.to_wide_with_nul(); let res = unsafe { - Registry::RegSaveKeyW(*key.hkey.read(), file_name.as_ptr(), std::ptr::null_mut()) + Registry::RegSaveKeyW(key.hkey.load(), file_name.as_ptr(), std::ptr::null_mut()) }; if res == 0 { Ok(()) @@ -705,7 +843,7 @@ mod winreg { #[pyfunction] fn SetValue( - key: PyRef<PyHKEYObject>, + key: PyRef<PyHkey>, sub_key: String, typ: u32, value: String, @@ -715,32 +853,63 @@ mod winreg { return Err(vm.new_type_error("type must be winreg.REG_SZ")); } - let wide_sub_key = to_utf16(sub_key); - - // TODO: Value check - if *key.hkey.read() == Registry::HKEY_PERFORMANCE_DATA { - return Err(vm.new_os_error("Cannot set value on HKEY_PERFORMANCE_DATA")); + let hkey = key.hkey.load(); + if hkey == Registry::HKEY_PERFORMANCE_DATA { + return Err(os_error_from_windows_code( + vm, + Foundation::ERROR_INVALID_HANDLE as i32, + "RegSetValue", + )); } - // if (sub_key && sub_key[0]) { - // // TODO: create key - // } + // Create subkey if sub_key is non-empty + let child_key = if !sub_key.is_empty() { + let wide_sub_key = sub_key.to_wide_with_nul(); + let mut out_key = std::ptr::null_mut(); + let res = unsafe { + Registry::RegCreateKeyExW( + hkey, + wide_sub_key.as_ptr(), + 0, + std::ptr::null(), + 0, + Registry::KEY_SET_VALUE, + std::ptr::null(), + &mut out_key, + std::ptr::null_mut(), + ) + }; + if res != 0 { + return Err(os_error_from_windows_code(vm, res as i32, "RegCreateKeyEx")); + } + Some(out_key) + } else { + None + }; + let target_key = child_key.unwrap_or(hkey); + // Convert value to UTF-16 for Wide API + let wide_value = value.to_wide_with_nul(); let res = unsafe { Registry::RegSetValueExW( - *key.hkey.read(), - wide_sub_key.as_ptr(), + target_key, + std::ptr::null(), // value name is NULL 0, typ, - value.as_ptr(), - value.len() as u32, + wide_value.as_ptr() as *const u8, + (wide_value.len() * 2) as u32, // byte count ) }; + // Close child key if we created one + if let Some(ck) = child_key { + unsafe { Registry::RegCloseKey(ck) }; + } + if res == 0 { Ok(()) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(os_error_from_windows_code(vm, res as i32, "RegSetValueEx")) } } @@ -764,11 +933,7 @@ mod winreg { } } REG_SZ | REG_EXPAND_SZ => { - // Treat the data as a UTF-16 string. - let u16_count = ret_data.len() / 2; - let u16_slice = unsafe { - std::slice::from_raw_parts(ret_data.as_ptr() as *const u16, u16_count) - }; + let u16_slice = bytes_as_wide_slice(ret_data); // Only use characters up to the first NUL. let len = u16_slice .iter() @@ -782,24 +947,32 @@ mod winreg { if ret_data.is_empty() { Ok(vm.ctx.new_list(vec![]).into()) } else { - let u16_count = ret_data.len() / 2; - let u16_slice = unsafe { - std::slice::from_raw_parts(ret_data.as_ptr() as *const u16, u16_count) + let u16_slice = bytes_as_wide_slice(ret_data); + let u16_count = u16_slice.len(); + + // Remove trailing null if present (like countStrings) + let len = if u16_count > 0 && u16_slice[u16_count - 1] == 0 { + u16_count - 1 + } else { + u16_count }; + let mut strings: Vec<PyObjectRef> = Vec::new(); let mut start = 0; - for (i, &c) in u16_slice.iter().enumerate() { - if c == 0 { - // An empty string signals the end. - if start == i { - break; - } + for i in 0..len { + if u16_slice[i] == 0 { let s = String::from_utf16(&u16_slice[start..i]) .map_err(|e| vm.new_value_error(format!("UTF16 error: {e}")))?; strings.push(vm.ctx.new_str(s).into()); start = i + 1; } } + // Handle last string if not null-terminated + if start < len { + let s = String::from_utf16(&u16_slice[start..len]) + .map_err(|e| vm.new_value_error(format!("UTF16 error: {e}")))?; + strings.push(vm.ctx.new_str(s).into()); + } Ok(vm.ctx.new_list(strings).into()) } } @@ -817,44 +990,97 @@ mod winreg { fn py2reg(value: PyObjectRef, typ: u32, vm: &VirtualMachine) -> PyResult<Option<Vec<u8>>> { match typ { REG_DWORD => { - let val = value.downcast_ref::<PyInt>(); - if val.is_none() { - return Err(vm.new_type_error("value must be an integer".to_string())); + if vm.is_none(&value) { + return Ok(Some(0u32.to_le_bytes().to_vec())); } - let val = val.unwrap().as_bigint().to_u32().unwrap(); + let val = value + .downcast_ref::<PyInt>() + .ok_or_else(|| vm.new_type_error("value must be an integer".to_string()))?; + let bigint = val.as_bigint(); + // Check for negative value - raise OverflowError + if bigint.sign() == Sign::Minus { + return Err(vm.new_overflow_error("int too big to convert".to_string())); + } + let val = bigint + .to_u32() + .ok_or_else(|| vm.new_overflow_error("int too big to convert".to_string()))?; Ok(Some(val.to_le_bytes().to_vec())) } REG_QWORD => { - let val = value.downcast_ref::<PyInt>(); - if val.is_none() { - return Err(vm.new_type_error("value must be an integer".to_string())); + if vm.is_none(&value) { + return Ok(Some(0u64.to_le_bytes().to_vec())); + } + let val = value + .downcast_ref::<PyInt>() + .ok_or_else(|| vm.new_type_error("value must be an integer".to_string()))?; + let bigint = val.as_bigint(); + // Check for negative value - raise OverflowError + if bigint.sign() == Sign::Minus { + return Err(vm.new_overflow_error("int too big to convert".to_string())); } - let val = val.unwrap().as_bigint().to_u64().unwrap(); + let val = bigint + .to_u64() + .ok_or_else(|| vm.new_overflow_error("int too big to convert".to_string()))?; Ok(Some(val.to_le_bytes().to_vec())) } - // REG_SZ is fallthrough - REG_EXPAND_SZ => { - Err(vm - .new_type_error("TODO: RUSTPYTHON REG_EXPAND_SZ is not supported".to_string())) + REG_SZ | REG_EXPAND_SZ => { + if vm.is_none(&value) { + // Return empty string as UTF-16 null terminator + return Ok(Some(vec![0u8, 0u8])); + } + let s = value + .downcast::<crate::builtins::PyStr>() + .map_err(|_| vm.new_type_error("value must be a string".to_string()))?; + let wide = s.as_str().to_wide_with_nul(); + // Convert Vec<u16> to Vec<u8> + let bytes: Vec<u8> = wide.iter().flat_map(|&c| c.to_le_bytes()).collect(); + Ok(Some(bytes)) } REG_MULTI_SZ => { - Err(vm.new_type_error("TODO: RUSTPYTHON REG_MULTI_SZ is not supported".to_string())) + if vm.is_none(&value) { + // Empty list = double null terminator + return Ok(Some(vec![0u8, 0u8, 0u8, 0u8])); + } + let list = value.downcast::<crate::builtins::PyList>().map_err(|_| { + vm.new_type_error("value must be a list of strings".to_string()) + })?; + + let mut bytes: Vec<u8> = Vec::new(); + for item in list.borrow_vec().iter() { + let s = item + .downcast_ref::<crate::builtins::PyStr>() + .ok_or_else(|| { + vm.new_type_error("list items must be strings".to_string()) + })?; + let wide = s.as_str().to_wide_with_nul(); + bytes.extend(wide.iter().flat_map(|&c| c.to_le_bytes())); + } + // Add final null terminator (double null at end) + bytes.extend([0u8, 0u8]); + Ok(Some(bytes)) } - // REG_BINARY is fallthrough + // REG_BINARY and other types _ => { if vm.is_none(&value) { return Ok(None); } - Err(vm.new_type_error("TODO: RUSTPYTHON Not supported".to_string())) + // Try to get bytes + if let Some(bytes) = value.downcast_ref::<crate::builtins::PyBytes>() { + return Ok(Some(bytes.as_bytes().to_vec())); + } + Err(vm.new_type_error(format!( + "Objects of type '{}' can not be used as binary registry values", + value.class().name() + ))) } } } #[pyfunction] fn SetValueEx( - key: PyRef<PyHKEYObject>, + key: PyRef<PyHkey>, value_name: String, - _reserved: u32, + _reserved: PyObjectRef, typ: u32, value: PyObjectRef, vm: &VirtualMachine, @@ -863,10 +1089,10 @@ mod winreg { Ok(Some(v)) => { let len = v.len() as u32; let ptr = v.as_ptr(); - let wide_value_name = to_utf16(value_name); + let wide_value_name = value_name.to_wide_with_nul(); let res = unsafe { Registry::RegSetValueExW( - *key.hkey.read(), + key.hkey.load(), wide_value_name.as_ptr(), 0, typ, @@ -881,10 +1107,10 @@ mod winreg { Ok(None) => { let len = 0; let ptr = std::ptr::null(); - let wide_value_name = to_utf16(value_name); + let wide_value_name = value_name.to_wide_with_nul(); let res = unsafe { Registry::RegSetValueExW( - *key.hkey.read(), + key.hkey.load(), wide_value_name.as_ptr(), 0, typ, @@ -896,14 +1122,24 @@ mod winreg { return Err(vm.new_os_error(format!("error code: {res}"))); } } - Err(_) => return Err(vm.new_type_error("value must be an integer".to_string())), + Err(e) => return Err(e), } Ok(()) } #[pyfunction] - fn EnableReflectionKey(key: PyRef<PyHKEYObject>, vm: &VirtualMachine) -> PyResult<()> { - let res = unsafe { Registry::RegEnableReflectionKey(*key.hkey.read()) }; + fn DisableReflectionKey(key: PyRef<PyHkey>, vm: &VirtualMachine) -> PyResult<()> { + let res = unsafe { Registry::RegDisableReflectionKey(key.hkey.load()) }; + if res == 0 { + Ok(()) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } + } + + #[pyfunction] + fn EnableReflectionKey(key: PyRef<PyHkey>, vm: &VirtualMachine) -> PyResult<()> { + let res = unsafe { Registry::RegEnableReflectionKey(key.hkey.load()) }; if res == 0 { Ok(()) } else { @@ -912,21 +1148,46 @@ mod winreg { } #[pyfunction] - fn ExpandEnvironmentStrings(i: String) -> PyResult<String> { - let mut out = vec![0; 1024]; + fn QueryReflectionKey(key: PyRef<PyHkey>, vm: &VirtualMachine) -> PyResult<bool> { + let mut result: windows_sys::Win32::Foundation::BOOL = 0; + let res = unsafe { Registry::RegQueryReflectionKey(key.hkey.load(), &mut result) }; + if res == 0 { + Ok(result != 0) + } else { + Err(vm.new_os_error(format!("error code: {res}"))) + } + } + + #[pyfunction] + fn ExpandEnvironmentStrings(i: String, vm: &VirtualMachine) -> PyResult<String> { + let wide_input = i.to_wide_with_nul(); + + // First call with size=0 to get required buffer size + let required_size = unsafe { + windows_sys::Win32::System::Environment::ExpandEnvironmentStringsW( + wide_input.as_ptr(), + std::ptr::null_mut(), + 0, + ) + }; + if required_size == 0 { + return Err(vm.new_os_error("ExpandEnvironmentStringsW failed".to_string())); + } + + // Allocate buffer with exact size and expand + let mut out = vec![0u16; required_size as usize]; let r = unsafe { - windows_sys::Win32::System::Environment::ExpandEnvironmentStringsA( - i.as_ptr(), + windows_sys::Win32::System::Environment::ExpandEnvironmentStringsW( + wide_input.as_ptr(), out.as_mut_ptr(), - out.len() as u32, + required_size, ) }; - let s = String::from_utf8(out[..r as usize].to_vec()) - .unwrap() - .replace("\0", "") - .replace("\x02", "") - .to_string(); + if r == 0 { + return Err(vm.new_os_error("ExpandEnvironmentStringsW failed".to_string())); + } - Ok(s) + let len = out.iter().position(|&c| c == 0).unwrap_or(out.len()); + String::from_utf16(&out[..len]).map_err(|e| vm.new_value_error(format!("UTF16 error: {e}"))) } } From c826f9d809dabb55460105451a256a68c007ce87 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 5 Dec 2025 22:44:00 +0900 Subject: [PATCH 403/819] PyStructSequence Compatibility (#6327) * Fix local time * PyStructSequence --- Lib/test/datetimetester.py | 2 - Lib/test/test_logging.py | 2 - Lib/test/test_structseq.py | 4 - crates/derive-impl/src/lib.rs | 8 +- crates/derive-impl/src/pymodule.rs | 107 +++++- crates/derive-impl/src/pystructseq.rs | 479 +++++++++++++++++++++++--- crates/derive/src/lib.rs | 57 ++- crates/stdlib/src/grp.rs | 30 +- crates/stdlib/src/resource.rs | 18 +- crates/vm/src/stdlib/nt.rs | 4 +- crates/vm/src/stdlib/os.rs | 89 +++-- crates/vm/src/stdlib/posix.rs | 8 +- crates/vm/src/stdlib/pwd.rs | 26 +- crates/vm/src/stdlib/sys.rs | 151 +++++--- crates/vm/src/stdlib/time.rs | 91 ++--- crates/vm/src/types/mod.rs | 2 +- crates/vm/src/types/structseq.rs | 344 ++++++++++++++++-- crates/vm/src/vm/context.rs | 3 + crates/vm/src/vm/mod.rs | 2 +- 19 files changed, 1145 insertions(+), 282 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 7c6c8d7dd2c..018b0c4d03a 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2943,8 +2943,6 @@ def newmeth(self, start): self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month + dt1.second - 7) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_subclass_alternate_constructors_datetime(self): # Test that alternate constructors call the constructor class DateTimeSubclass(self.theclass): diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 84a659ebe4b..3ec6c3c3a1d 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -4748,7 +4748,6 @@ def test_defaults_parameter(self): def test_invalid_style(self): self.assertRaises(ValueError, logging.Formatter, None, None, 'x') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'struct_time' object has no attribute 'tm_gmtoff' def test_time(self): r = self.get_record() dt = datetime.datetime(1993, 4, 21, 8, 3, 0, 0, utc) @@ -4763,7 +4762,6 @@ def test_time(self): f.format(r) self.assertEqual(r.asctime, '1993-04-21 08:03:00,123') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'struct_time' object has no attribute 'tm_gmtoff' def test_default_msec_format_none(self): class NoMsecFormatter(logging.Formatter): default_msec_format = None diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py index 41095f63ad6..a9fe193028e 100644 --- a/Lib/test/test_structseq.py +++ b/Lib/test/test_structseq.py @@ -75,8 +75,6 @@ def test_cmp(self): self.assertTrue(t1 >= t2) self.assertTrue(not (t1 != t2)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_fields(self): t = time.gmtime() self.assertEqual(len(t), t.n_sequence_fields) @@ -129,8 +127,6 @@ def test_match_args(self): 'tm_sec', 'tm_wday', 'tm_yday', 'tm_isdst') self.assertEqual(time.struct_time.__match_args__, expected_args) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_match_args_with_unnamed_fields(self): expected_args = ('st_mode', 'st_ino', 'st_dev', 'st_nlink', 'st_uid', 'st_gid', 'st_size') diff --git a/crates/derive-impl/src/lib.rs b/crates/derive-impl/src/lib.rs index 786c77e3212..51bb0af406f 100644 --- a/crates/derive-impl/src/lib.rs +++ b/crates/derive-impl/src/lib.rs @@ -58,12 +58,12 @@ pub fn pymodule(attr: PunctuatedNestedMeta, item: Item) -> TokenStream { result_to_tokens(pymodule::impl_pymodule(attr, item)) } -pub fn pystruct_sequence(input: DeriveInput) -> TokenStream { - result_to_tokens(pystructseq::impl_pystruct_sequence(input)) +pub fn pystruct_sequence(attr: PunctuatedNestedMeta, item: Item) -> TokenStream { + result_to_tokens(pystructseq::impl_pystruct_sequence(attr, item)) } -pub fn pystruct_sequence_try_from_object(input: DeriveInput) -> TokenStream { - result_to_tokens(pystructseq::impl_pystruct_sequence_try_from_object(input)) +pub fn pystruct_sequence_data(attr: PunctuatedNestedMeta, item: Item) -> TokenStream { + result_to_tokens(pystructseq::impl_pystruct_sequence_data(attr, item)) } pub fn py_compile(input: TokenStream, compiler: &dyn Compiler) -> TokenStream { diff --git a/crates/derive-impl/src/pymodule.rs b/crates/derive-impl/src/pymodule.rs index 9db7128b3a1..2d5ff7cb0c2 100644 --- a/crates/derive-impl/src/pymodule.rs +++ b/crates/derive-impl/src/pymodule.rs @@ -1,4 +1,5 @@ use crate::error::Diagnostic; +use crate::pystructseq::PyStructSequenceMeta; use crate::util::{ ALL_ALLOWED_NAMES, AttrItemMeta, AttributeExt, ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, format_doc, iter_use_idents, @@ -18,6 +19,7 @@ enum AttrName { Attr, Class, Exception, + StructSequence, } impl std::fmt::Display for AttrName { @@ -27,6 +29,7 @@ impl std::fmt::Display for AttrName { Self::Attr => "pyattr", Self::Class => "pyclass", Self::Exception => "pyexception", + Self::StructSequence => "pystruct_sequence", }; s.fmt(f) } @@ -41,6 +44,7 @@ impl FromStr for AttrName { "pyattr" => Self::Attr, "pyclass" => Self::Class, "pyexception" => Self::Exception, + "pystruct_sequence" => Self::StructSequence, s => { return Err(s.to_owned()); } @@ -235,6 +239,10 @@ fn module_item_new( inner: ContentItemInner { index, attr_name }, py_attrs, }), + AttrName::StructSequence => Box::new(StructSequenceItem { + inner: ContentItemInner { index, attr_name }, + py_attrs, + }), } } @@ -301,13 +309,16 @@ where result.push(item_new(i, attr_name, Vec::new())); } else { match attr_name { - AttrName::Class | AttrName::Function | AttrName::Exception => { + AttrName::Class + | AttrName::Function + | AttrName::Exception + | AttrName::StructSequence => { result.push(item_new(i, attr_name, py_attrs.clone())); } _ => { bail_span!( attr, - "#[pyclass], #[pyfunction], or #[pyexception] can follow #[pyattr]", + "#[pyclass], #[pyfunction], #[pyexception], or #[pystruct_sequence] can follow #[pyattr]", ) } } @@ -402,6 +413,12 @@ struct AttributeItem { py_attrs: Vec<usize>, } +/// #[pystruct_sequence] +struct StructSequenceItem { + inner: ContentItemInner<AttrName>, + py_attrs: Vec<usize>, +} + impl ContentItem for FunctionItem { type AttrName = AttrName; @@ -426,6 +443,14 @@ impl ContentItem for AttributeItem { } } +impl ContentItem for StructSequenceItem { + type AttrName = AttrName; + + fn inner(&self) -> &ContentItemInner<AttrName> { + &self.inner + } +} + struct ModuleItemArgs<'a> { item: &'a mut Item, attrs: &'a mut Vec<Attribute>, @@ -602,6 +627,84 @@ impl ModuleItem for ClassItem { } } +impl ModuleItem for StructSequenceItem { + fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> { + // Get the struct identifier (this IS the Python type, e.g., PyStructTime) + let pytype_ident = match args.item { + Item::Struct(s) => s.ident.clone(), + other => bail_span!(other, "#[pystruct_sequence] can only be on a struct"), + }; + + // Parse the #[pystruct_sequence(name = "...", module = "...", no_attr)] attribute + let structseq_attr = &args.attrs[self.inner.index]; + let meta = PyStructSequenceMeta::from_attr(pytype_ident.clone(), structseq_attr)?; + + let class_name = meta.class_name()?.ok_or_else(|| { + syn::Error::new_spanned( + structseq_attr, + "#[pystruct_sequence] requires name parameter", + ) + })?; + let module_name = meta.module()?.unwrap_or_else(|| args.context.name.clone()); + let no_attr = meta.no_attr()?; + + // Generate the class creation code + let class_new = quote_spanned!(pytype_ident.span() => + let new_class = <#pytype_ident as ::rustpython_vm::class::PyClassImpl>::make_class(ctx); + new_class.set_attr(rustpython_vm::identifier!(ctx, __module__), vm.new_pyobj(#module_name)); + ); + + // Handle py_attrs for custom names, or use class_name as default + let mut py_names = Vec::new(); + for attr_index in self.py_attrs.iter().rev() { + let attr_attr = args.attrs.remove(*attr_index); + let item_meta = SimpleItemMeta::from_attr(pytype_ident.clone(), &attr_attr)?; + let py_name = item_meta + .optional_name() + .unwrap_or_else(|| class_name.clone()); + py_names.push(py_name); + } + + // Require explicit #[pyattr] or no_attr, like #[pyclass] + if self.py_attrs.is_empty() && !no_attr { + bail_span!( + pytype_ident, + "#[pystruct_sequence] requires #[pyattr] to be a module attribute. \ + To keep it free type, try #[pystruct_sequence(..., no_attr)]" + ) + } + + let set_attr = match py_names.len() { + 0 => quote! { + let _ = new_class; // suppress warning + }, + 1 => { + let py_name = &py_names[0]; + quote! { + vm.__module_set_attr(&module, vm.ctx.intern_str(#py_name), new_class).unwrap(); + } + } + _ => quote! { + for name in [#(#py_names,)*] { + vm.__module_set_attr(&module, vm.ctx.intern_str(name), new_class.clone()).unwrap(); + } + }, + }; + + args.context.attribute_items.add_item( + pytype_ident.clone(), + py_names, + args.cfgs.to_vec(), + quote_spanned! { pytype_ident.span() => + #class_new + #set_attr + }, + 0, + )?; + Ok(()) + } +} + impl ModuleItem for AttributeItem { fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> { let cfgs = args.cfgs.to_vec(); diff --git a/crates/derive-impl/src/pystructseq.rs b/crates/derive-impl/src/pystructseq.rs index 89dee8da106..c43673fe975 100644 --- a/crates/derive-impl/src/pystructseq.rs +++ b/crates/derive-impl/src/pystructseq.rs @@ -1,34 +1,90 @@ +use crate::util::{ItemMeta, ItemMetaInner}; use proc_macro2::TokenStream; -use quote::quote; -use syn::{DeriveInput, Ident, Result}; +use quote::{format_ident, quote}; +use syn::{DeriveInput, Ident, Item, Result}; use syn_ext::ext::{AttributeExt, GetIdent}; -use syn_ext::types::Meta; +use syn_ext::types::{Meta, PunctuatedNestedMeta}; -// returning a pair of not-skipped and skipped field names -fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> { +// #[pystruct_sequence_data] - For Data structs + +/// Field kind for struct sequence +#[derive(Clone, Copy, PartialEq, Eq)] +enum FieldKind { + /// Named visible field (has getter, shown in repr) + Named, + /// Unnamed visible field (index-only, no getter) + Unnamed, + /// Hidden/skipped field (stored in tuple, but hidden from repr/len/index) + Skipped, +} + +/// Parsed field with its kind +struct ParsedField { + ident: Ident, + kind: FieldKind, +} + +/// Parsed field info from struct +struct FieldInfo { + /// All fields in order with their kinds + fields: Vec<ParsedField>, +} + +impl FieldInfo { + fn named_fields(&self) -> Vec<Ident> { + self.fields + .iter() + .filter(|f| f.kind == FieldKind::Named) + .map(|f| f.ident.clone()) + .collect() + } + + fn visible_fields(&self) -> Vec<Ident> { + self.fields + .iter() + .filter(|f| f.kind != FieldKind::Skipped) + .map(|f| f.ident.clone()) + .collect() + } + + fn skipped_fields(&self) -> Vec<Ident> { + self.fields + .iter() + .filter(|f| f.kind == FieldKind::Skipped) + .map(|f| f.ident.clone()) + .collect() + } + + fn n_unnamed_fields(&self) -> usize { + self.fields + .iter() + .filter(|f| f.kind == FieldKind::Unnamed) + .count() + } +} + +/// Parse field info from struct +fn parse_fields(input: &mut DeriveInput) -> Result<FieldInfo> { let syn::Data::Struct(struc) = &mut input.data else { - bail_span!( - input, - "#[pystruct_sequence] can only be on a struct declaration" - ) + bail_span!(input, "#[pystruct_sequence_data] can only be on a struct") }; let syn::Fields::Named(fields) = &mut struc.fields else { bail_span!( input, - "#[pystruct_sequence] can only be on a struct with named fields" + "#[pystruct_sequence_data] can only be on a struct with named fields" ); }; - let mut not_skipped = Vec::with_capacity(fields.named.len()); - let mut skipped = Vec::with_capacity(fields.named.len()); + let mut parsed_fields = Vec::with_capacity(fields.named.len()); + for field in &mut fields.named { let mut skip = false; - // Collect all attributes with pystruct and their indices + let mut unnamed = false; let mut attrs_to_remove = Vec::new(); for (i, attr) in field.attrs.iter().enumerate() { - if !attr.path().is_ident("pystruct") { + if !attr.path().is_ident("pystruct_sequence") { continue; } @@ -37,7 +93,7 @@ fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> { }; let Meta::List(l) = meta else { - bail_span!(input, "Only #[pystruct(...)] form is allowed"); + bail_span!(input, "Only #[pystruct_sequence(...)] form is allowed"); }; let idents: Vec<_> = l @@ -47,15 +103,16 @@ fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> { .cloned() .collect(); - // Follow #[serde(skip)] convention. - // Consider to add skip_serializing and skip_deserializing if required. for ident in idents { match ident.to_string().as_str() { "skip" => { skip = true; } + "unnamed" => { + unnamed = true; + } _ => { - bail_span!(ident, "Unknown item for #[pystruct(...)]") + bail_span!(ident, "Unknown item for #[pystruct_sequence(...)]") } } } @@ -63,67 +120,381 @@ fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> { attrs_to_remove.push(i); } - // Remove attributes in reverse order to maintain valid indices - attrs_to_remove.sort_unstable_by(|a, b| b.cmp(a)); // Sort in descending order + // Remove attributes in reverse order + attrs_to_remove.sort_unstable_by(|a, b| b.cmp(a)); for index in attrs_to_remove { field.attrs.remove(index); } + let ident = field.ident.clone().unwrap(); - if skip { - skipped.push(ident.clone()); + let kind = if skip { + FieldKind::Skipped + } else if unnamed { + FieldKind::Unnamed } else { - not_skipped.push(ident.clone()); - } + FieldKind::Named + }; + + parsed_fields.push(ParsedField { ident, kind }); } - Ok((not_skipped, skipped)) + Ok(FieldInfo { + fields: parsed_fields, + }) } -pub(crate) fn impl_pystruct_sequence(mut input: DeriveInput) -> Result<TokenStream> { - let (not_skipped_fields, skipped_fields) = field_names(&mut input)?; - let ty = &input.ident; - let ret = quote! { - impl ::rustpython_vm::types::PyStructSequence for #ty { - const REQUIRED_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#not_skipped_fields),)*]; +/// Check if `try_from_object` is present in attribute arguments +fn has_try_from_object(attr: &PunctuatedNestedMeta) -> bool { + attr.iter().any(|nested| { + nested + .get_ident() + .is_some_and(|ident| ident == "try_from_object") + }) +} + +/// Attribute macro for Data structs: #[pystruct_sequence_data(...)] +/// +/// Generates: +/// - `REQUIRED_FIELD_NAMES` constant (named visible fields) +/// - `OPTIONAL_FIELD_NAMES` constant (hidden/skipped fields) +/// - `UNNAMED_FIELDS_LEN` constant +/// - `into_tuple()` method +/// - Field index constants (e.g., `TM_YEAR_INDEX`) +/// +/// Options: +/// - `try_from_object`: Generate `try_from_elements()` method and `TryFromObject` impl +pub(crate) fn impl_pystruct_sequence_data( + attr: PunctuatedNestedMeta, + item: Item, +) -> Result<TokenStream> { + let Item::Struct(item_struct) = item else { + bail_span!( + item, + "#[pystruct_sequence_data] can only be applied to structs" + ); + }; + + let try_from_object = has_try_from_object(&attr); + let mut input: DeriveInput = DeriveInput { + attrs: item_struct.attrs.clone(), + vis: item_struct.vis.clone(), + ident: item_struct.ident.clone(), + generics: item_struct.generics.clone(), + data: syn::Data::Struct(syn::DataStruct { + struct_token: item_struct.struct_token, + fields: item_struct.fields.clone(), + semi_token: item_struct.semi_token, + }), + }; + let field_info = parse_fields(&mut input)?; + let data_ident = &input.ident; + + let named_fields = field_info.named_fields(); + let visible_fields = field_info.visible_fields(); + let skipped_fields = field_info.skipped_fields(); + let n_unnamed_fields = field_info.n_unnamed_fields(); + + // Generate field index constants for visible fields + let field_indices: Vec<_> = visible_fields + .iter() + .enumerate() + .map(|(i, field)| { + let const_name = format_ident!("{}_INDEX", field.to_string().to_uppercase()); + quote! { + pub const #const_name: usize = #i; + } + }) + .collect(); + + // Generate TryFromObject impl only when try_from_object=true + let try_from_object_impl = if try_from_object { + let n_required = visible_fields.len(); + quote! { + impl ::rustpython_vm::TryFromObject for #data_ident { + fn try_from_object( + vm: &::rustpython_vm::VirtualMachine, + obj: ::rustpython_vm::PyObjectRef, + ) -> ::rustpython_vm::PyResult<Self> { + let seq: Vec<::rustpython_vm::PyObjectRef> = obj.try_into_value(vm)?; + if seq.len() < #n_required { + return Err(vm.new_type_error(format!( + "{} requires at least {} elements", + stringify!(#data_ident), + #n_required + ))); + } + <Self as ::rustpython_vm::types::PyStructSequenceData>::try_from_elements(seq, vm) + } + } + } + } else { + quote! {} + }; + + // Generate try_from_elements trait override only when try_from_object=true + let try_from_elements_trait_override = if try_from_object { + quote! { + fn try_from_elements( + elements: Vec<::rustpython_vm::PyObjectRef>, + vm: &::rustpython_vm::VirtualMachine, + ) -> ::rustpython_vm::PyResult<Self> { + let mut iter = elements.into_iter(); + Ok(Self { + #(#visible_fields: iter.next().unwrap().clone().try_into_value(vm)?,)* + #(#skipped_fields: match iter.next() { + Some(v) => v.clone().try_into_value(vm)?, + None => vm.ctx.none(), + },)* + }) + } + } + } else { + quote! {} + }; + + let output = quote! { + impl #data_ident { + #(#field_indices)* + } + + // PyStructSequenceData trait impl + impl ::rustpython_vm::types::PyStructSequenceData for #data_ident { + const REQUIRED_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#named_fields),)*]; const OPTIONAL_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#skipped_fields),)*]; + const UNNAMED_FIELDS_LEN: usize = #n_unnamed_fields; + fn into_tuple(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::builtins::PyTuple { let items = vec![ #(::rustpython_vm::convert::ToPyObject::to_pyobject( - self.#not_skipped_fields, + self.#visible_fields, + vm, + ),)* + #(::rustpython_vm::convert::ToPyObject::to_pyobject( + self.#skipped_fields, vm, ),)* ]; ::rustpython_vm::builtins::PyTuple::new_unchecked(items.into_boxed_slice()) } + + #try_from_elements_trait_override } - impl ::rustpython_vm::convert::ToPyObject for #ty { - fn to_pyobject(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::PyObjectRef { - ::rustpython_vm::types::PyStructSequence::into_struct_sequence(self, vm).into() + + #try_from_object_impl + }; + + // For attribute macro, we need to output the original struct as well + // But first, strip #[pystruct_sequence] attributes from fields + let mut clean_struct = item_struct.clone(); + if let syn::Fields::Named(ref mut fields) = clean_struct.fields { + for field in &mut fields.named { + field + .attrs + .retain(|attr| !attr.path().is_ident("pystruct_sequence")); + } + } + + Ok(quote! { + #clean_struct + #output + }) +} + +// #[pystruct_sequence(...)] - For Python type structs + +/// Meta parser for #[pystruct_sequence(...)] +pub(crate) struct PyStructSequenceMeta { + inner: ItemMetaInner, +} + +impl ItemMeta for PyStructSequenceMeta { + const ALLOWED_NAMES: &'static [&'static str] = &["name", "module", "data", "no_attr"]; + + fn from_inner(inner: ItemMetaInner) -> Self { + Self { inner } + } + fn inner(&self) -> &ItemMetaInner { + &self.inner + } +} + +impl PyStructSequenceMeta { + pub fn class_name(&self) -> Result<Option<String>> { + const KEY: &str = "name"; + let inner = self.inner(); + if let Some((_, meta)) = inner.meta_map.get(KEY) { + if let Meta::NameValue(syn::MetaNameValue { + value: + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }), + .. + }) = meta + { + return Ok(Some(lit.value())); } + bail_span!( + inner.meta_ident, + "#[pystruct_sequence({KEY}=value)] expects a string value" + ) + } else { + Ok(None) } - }; - Ok(ret) + } + + pub fn module(&self) -> Result<Option<String>> { + const KEY: &str = "module"; + let inner = self.inner(); + if let Some((_, meta)) = inner.meta_map.get(KEY) { + if let Meta::NameValue(syn::MetaNameValue { + value: + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }), + .. + }) = meta + { + return Ok(Some(lit.value())); + } + bail_span!( + inner.meta_ident, + "#[pystruct_sequence({KEY}=value)] expects a string value" + ) + } else { + Ok(None) + } + } + + fn data_type(&self) -> Result<Ident> { + const KEY: &str = "data"; + let inner = self.inner(); + if let Some((_, meta)) = inner.meta_map.get(KEY) { + if let Meta::NameValue(syn::MetaNameValue { + value: + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }), + .. + }) = meta + { + return Ok(format_ident!("{}", lit.value())); + } + bail_span!( + inner.meta_ident, + "#[pystruct_sequence({KEY}=value)] expects a string value" + ) + } else { + bail_span!( + inner.meta_ident, + "#[pystruct_sequence] requires data parameter (e.g., data = \"DataStructName\")" + ) + } + } + + pub fn no_attr(&self) -> Result<bool> { + self.inner()._bool("no_attr") + } } -pub(crate) fn impl_pystruct_sequence_try_from_object( - mut input: DeriveInput, +/// Attribute macro for struct sequences. +/// +/// Usage: +/// ```ignore +/// #[pystruct_sequence_data] +/// struct StructTimeData { ... } +/// +/// #[pystruct_sequence(name = "struct_time", module = "time", data = "StructTimeData")] +/// struct PyStructTime; +/// ``` +pub(crate) fn impl_pystruct_sequence( + attr: PunctuatedNestedMeta, + item: Item, ) -> Result<TokenStream> { - let (not_skipped_fields, skipped_fields) = field_names(&mut input)?; - let ty = &input.ident; - let ret = quote! { - impl ::rustpython_vm::TryFromObject for #ty { - fn try_from_object(vm: &::rustpython_vm::VirtualMachine, seq: ::rustpython_vm::PyObjectRef) -> ::rustpython_vm::PyResult<Self> { - let seq = Self::try_elements_from(seq, vm)?; - let mut iter = seq.into_iter(); - Ok(Self { - #(#not_skipped_fields: iter.next().unwrap().clone().try_into_value(vm)?,)* - #(#skipped_fields: match iter.next() { - Some(v) => v.clone().try_into_value(vm)?, - None => vm.ctx.none(), - },)* - }) + let Item::Struct(struct_item) = item else { + bail_span!(item, "#[pystruct_sequence] can only be applied to a struct"); + }; + + let ident = struct_item.ident.clone(); + let fake_ident = Ident::new("pystruct_sequence", ident.span()); + let meta = PyStructSequenceMeta::from_nested(ident, fake_ident, attr.into_iter())?; + + let pytype_ident = struct_item.ident.clone(); + let pytype_vis = struct_item.vis.clone(); + let data_ident = meta.data_type()?; + + let class_name = meta.class_name()?.ok_or_else(|| { + syn::Error::new_spanned( + &struct_item.ident, + "#[pystruct_sequence] requires name parameter", + ) + })?; + let module_name = meta.module()?; + + // Module name handling + let module_name_tokens = match &module_name { + Some(m) => quote!(Some(#m)), + None => quote!(None), + }; + + let module_class_name = if let Some(ref m) = module_name { + format!("{}.{}", m, class_name) + } else { + class_name.clone() + }; + + let output = quote! { + // The Python type struct (user-defined, possibly empty) + #pytype_vis struct #pytype_ident; + + // PyClassDef for Python type + impl ::rustpython_vm::class::PyClassDef for #pytype_ident { + const NAME: &'static str = #class_name; + const MODULE_NAME: Option<&'static str> = #module_name_tokens; + const TP_NAME: &'static str = #module_class_name; + const DOC: Option<&'static str> = None; + const BASICSIZE: usize = 0; + const UNHASHABLE: bool = false; + + type Base = ::rustpython_vm::builtins::PyTuple; + } + + // StaticType for Python type + impl ::rustpython_vm::class::StaticType for #pytype_ident { + fn static_cell() -> &'static ::rustpython_vm::common::static_cell::StaticCell<::rustpython_vm::builtins::PyTypeRef> { + ::rustpython_vm::common::static_cell! { + static CELL: ::rustpython_vm::builtins::PyTypeRef; + } + &CELL + } + + fn static_baseclass() -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { + use ::rustpython_vm::class::StaticType; + ::rustpython_vm::builtins::PyTuple::static_type() + } + } + + // MaybeTraverse (empty - no GC fields in empty struct) + impl ::rustpython_vm::object::MaybeTraverse for #pytype_ident { + fn try_traverse(&self, _traverse_fn: &mut ::rustpython_vm::object::TraverseFn<'_>) { + // Empty struct has no fields to traverse + } + } + + // PyStructSequence trait for Python type + impl ::rustpython_vm::types::PyStructSequence for #pytype_ident { + type Data = #data_ident; + } + + // ToPyObject for Data struct - uses PyStructSequence::from_data + impl ::rustpython_vm::convert::ToPyObject for #data_ident { + fn to_pyobject(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::PyObjectRef { + <#pytype_ident as ::rustpython_vm::types::PyStructSequence>::from_data(self, vm).into() } } }; - Ok(ret) + + Ok(output) } diff --git a/crates/derive/src/lib.rs b/crates/derive/src/lib.rs index 6aef58a2aa3..655ad3b4c9e 100644 --- a/crates/derive/src/lib.rs +++ b/crates/derive/src/lib.rs @@ -214,16 +214,57 @@ pub fn pymodule(attr: TokenStream, item: TokenStream) -> TokenStream { derive_impl::pymodule(attr, item).into() } -#[proc_macro_derive(PyStructSequence, attributes(pystruct))] -pub fn pystruct_sequence(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input); - derive_impl::pystruct_sequence(input).into() +/// Attribute macro for defining Python struct sequence types. +/// +/// This macro is applied to an empty struct to create a Python type +/// that wraps a Data struct. +/// +/// # Example +/// ```ignore +/// #[pystruct_sequence_data] +/// struct StructTimeData { +/// pub tm_year: PyObjectRef, +/// #[pystruct_sequence(skip)] +/// pub tm_gmtoff: PyObjectRef, +/// } +/// +/// #[pystruct_sequence(name = "struct_time", module = "time", data = "StructTimeData")] +/// struct PyStructTime; +/// ``` +#[proc_macro_attribute] +pub fn pystruct_sequence(attr: TokenStream, item: TokenStream) -> TokenStream { + let attr = parse_macro_input!(attr with Punctuated::parse_terminated); + let item = parse_macro_input!(item); + derive_impl::pystruct_sequence(attr, item).into() } -#[proc_macro_derive(TryIntoPyStructSequence, attributes(pystruct))] -pub fn pystruct_sequence_try_from_object(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input); - derive_impl::pystruct_sequence_try_from_object(input).into() +/// Attribute macro for struct sequence Data structs. +/// +/// Generates field name constants, index constants, and `into_tuple()` method. +/// +/// # Example +/// ```ignore +/// #[pystruct_sequence_data] +/// struct StructTimeData { +/// pub tm_year: PyObjectRef, +/// pub tm_mon: PyObjectRef, +/// #[pystruct_sequence(skip)] // optional field, not included in tuple +/// pub tm_gmtoff: PyObjectRef, +/// } +/// ``` +/// +/// # Options +/// - `try_from_object`: Generate `try_from_elements()` method and `TryFromObject` impl +/// +/// ```ignore +/// #[pystruct_sequence_data(try_from_object)] +/// struct StructTimeData { ... } +/// ``` +#[proc_macro_attribute] +pub fn pystruct_sequence_data(attr: TokenStream, item: TokenStream) -> TokenStream { + let attr = parse_macro_input!(attr with Punctuated::parse_terminated); + let item = parse_macro_input!(item); + derive_impl::pystruct_sequence_data(attr, item).into() } struct Compiler; diff --git a/crates/stdlib/src/grp.rs b/crates/stdlib/src/grp.rs index b640494c13b..4664d5fc575 100644 --- a/crates/stdlib/src/grp.rs +++ b/crates/stdlib/src/grp.rs @@ -13,26 +13,28 @@ mod grp { use nix::unistd; use std::ptr::NonNull; - #[pyattr] - #[pyclass(module = "grp", name = "struct_group", traverse)] - #[derive(PyStructSequence)] - struct Group { - #[pytraverse(skip)] + #[pystruct_sequence_data] + struct GroupData { gr_name: String, - #[pytraverse(skip)] gr_passwd: String, - #[pytraverse(skip)] gr_gid: u32, gr_mem: PyListRef, } + + #[pyattr] + #[pystruct_sequence(name = "struct_group", module = "grp", data = "GroupData")] + struct PyGroup; + #[pyclass(with(PyStructSequence))] - impl Group { + impl PyGroup {} + + impl GroupData { fn from_unistd_group(group: unistd::Group, vm: &VirtualMachine) -> Self { let cstr_lossy = |s: std::ffi::CString| { s.into_string() .unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned()) }; - Group { + GroupData { gr_name: group.name, gr_passwd: cstr_lossy(group.passwd), gr_gid: group.gid.as_raw(), @@ -44,7 +46,7 @@ mod grp { } #[pyfunction] - fn getgrgid(gid: PyIntRef, vm: &VirtualMachine) -> PyResult<Group> { + fn getgrgid(gid: PyIntRef, vm: &VirtualMachine) -> PyResult<GroupData> { let gr_gid = gid.as_bigint(); let gid = libc::gid_t::try_from(gr_gid) .map(unistd::Gid::from_raw) @@ -61,11 +63,11 @@ mod grp { .into(), ) })?; - Ok(Group::from_unistd_group(group, vm)) + Ok(GroupData::from_unistd_group(group, vm)) } #[pyfunction] - fn getgrnam(name: PyStrRef, vm: &VirtualMachine) -> PyResult<Group> { + fn getgrnam(name: PyStrRef, vm: &VirtualMachine) -> PyResult<GroupData> { let gr_name = name.as_str(); if gr_name.contains('\0') { return Err(exceptions::cstring_error(vm)); @@ -78,7 +80,7 @@ mod grp { .into(), ) })?; - Ok(Group::from_unistd_group(group, vm)) + Ok(GroupData::from_unistd_group(group, vm)) } #[pyfunction] @@ -91,7 +93,7 @@ mod grp { unsafe { libc::setgrent() }; while let Some(ptr) = NonNull::new(unsafe { libc::getgrent() }) { let group = unistd::Group::from(unsafe { ptr.as_ref() }); - let group = Group::from_unistd_group(group, vm).to_pyobject(vm); + let group = GroupData::from_unistd_group(group, vm).to_pyobject(vm); list.push(group); } unsafe { libc::endgrent() }; diff --git a/crates/stdlib/src/resource.rs b/crates/stdlib/src/resource.rs index 8bd60cee879..59205a4fa46 100644 --- a/crates/stdlib/src/resource.rs +++ b/crates/stdlib/src/resource.rs @@ -63,10 +63,8 @@ mod resource { #[pyattr] use libc::{RUSAGE_CHILDREN, RUSAGE_SELF}; - #[pyattr] - #[pyclass(name = "struct_rusage")] - #[derive(PyStructSequence)] - struct Rusage { + #[pystruct_sequence_data] + struct RUsageData { ru_utime: f64, ru_stime: f64, ru_maxrss: libc::c_long, @@ -85,10 +83,14 @@ mod resource { ru_nivcsw: libc::c_long, } + #[pyattr] + #[pystruct_sequence(name = "struct_rusage", module = "resource", data = "RUsageData")] + struct PyRUsage; + #[pyclass(with(PyStructSequence))] - impl Rusage {} + impl PyRUsage {} - impl From<libc::rusage> for Rusage { + impl From<libc::rusage> for RUsageData { fn from(rusage: libc::rusage) -> Self { let tv = |tv: libc::timeval| tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0); Self { @@ -113,7 +115,7 @@ mod resource { } #[pyfunction] - fn getrusage(who: i32, vm: &VirtualMachine) -> PyResult<Rusage> { + fn getrusage(who: i32, vm: &VirtualMachine) -> PyResult<RUsageData> { let res = unsafe { let mut rusage = mem::MaybeUninit::<libc::rusage>::uninit(); if libc::getrusage(who, rusage.as_mut_ptr()) == -1 { @@ -122,7 +124,7 @@ mod resource { Ok(rusage.assume_init()) } }; - res.map(Rusage::from).map_err(|e| { + res.map(RUsageData::from).map_err(|e| { if e.kind() == io::ErrorKind::InvalidInput { vm.new_value_error("invalid who parameter") } else { diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index cd2ff476ff7..9ed89b0bab7 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -179,7 +179,7 @@ pub(crate) mod module { fn get_terminal_size( fd: OptionalArg<i32>, vm: &VirtualMachine, - ) -> PyResult<_os::PyTerminalSize> { + ) -> PyResult<_os::TerminalSizeData> { let (columns, lines) = { let stdhandle = match fd { OptionalArg::Present(0) => Console::STD_INPUT_HANDLE, @@ -206,7 +206,7 @@ pub(crate) mod module { (w.Bottom - w.Top + 1) as usize, ) }; - Ok(_os::PyTerminalSize { columns, lines }) + Ok(_os::TerminalSizeData { columns, lines }) } #[cfg(target_env = "msvc")] diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 1c65d4d12cf..45d4e41bcba 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -735,10 +735,9 @@ pub(super) mod _os { .into()) } - #[pyattr] - #[pyclass(module = "os", name = "stat_result")] - #[derive(Debug, PyStructSequence, FromArgs)] - struct StatResult { + #[derive(Debug, FromArgs)] + #[pystruct_sequence_data] + struct StatResultData { pub st_mode: PyIntRef, pub st_ino: PyIntRef, pub st_dev: PyIntRef, @@ -746,31 +745,42 @@ pub(super) mod _os { pub st_uid: PyIntRef, pub st_gid: PyIntRef, pub st_size: PyIntRef, - // TODO: unnamed structsequence fields + // Indices 7-9: integer seconds #[pyarg(positional, default)] - pub __st_atime_int: libc::time_t, + #[pystruct_sequence(unnamed)] + pub st_atime_int: libc::time_t, #[pyarg(positional, default)] - pub __st_mtime_int: libc::time_t, + #[pystruct_sequence(unnamed)] + pub st_mtime_int: libc::time_t, #[pyarg(positional, default)] - pub __st_ctime_int: libc::time_t, + #[pystruct_sequence(unnamed)] + pub st_ctime_int: libc::time_t, + // Float time attributes #[pyarg(any, default)] + #[pystruct_sequence(skip)] pub st_atime: f64, #[pyarg(any, default)] + #[pystruct_sequence(skip)] pub st_mtime: f64, #[pyarg(any, default)] + #[pystruct_sequence(skip)] pub st_ctime: f64, + // Nanosecond attributes #[pyarg(any, default)] + #[pystruct_sequence(skip)] pub st_atime_ns: i128, #[pyarg(any, default)] + #[pystruct_sequence(skip)] pub st_mtime_ns: i128, #[pyarg(any, default)] + #[pystruct_sequence(skip)] pub st_ctime_ns: i128, #[pyarg(any, default)] + #[pystruct_sequence(skip)] pub st_reparse_tag: u32, } - #[pyclass(with(PyStructSequence))] - impl StatResult { + impl StatResultData { fn from_stat(stat: &StatStruct, vm: &VirtualMachine) -> Self { let (atime, mtime, ctime); #[cfg(any(unix, windows))] @@ -810,9 +820,9 @@ pub(super) mod _os { st_uid: vm.ctx.new_pyref(stat.st_uid), st_gid: vm.ctx.new_pyref(stat.st_gid), st_size: vm.ctx.new_pyref(stat.st_size), - __st_atime_int: atime.0, - __st_mtime_int: mtime.0, - __st_ctime_int: ctime.0, + st_atime_int: atime.0, + st_mtime_int: mtime.0, + st_ctime_int: ctime.0, st_atime: to_f64(atime), st_mtime: to_f64(mtime), st_ctime: to_f64(ctime), @@ -822,7 +832,14 @@ pub(super) mod _os { st_reparse_tag, } } + } + + #[pyattr] + #[pystruct_sequence(name = "stat_result", module = "os", data = "StatResultData")] + struct PyStatResult; + #[pyclass(with(PyStructSequence))] + impl PyStatResult { #[pyslot] fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { let flatten_args = |r: &[PyObjectRef]| { @@ -845,7 +862,7 @@ pub(super) mod _os { let args: FuncArgs = flatten_args(&args.args).into(); - let stat: Self = args.bind(vm)?; + let stat: StatResultData = args.bind(vm)?; Ok(stat.to_pyobject(vm)) } } @@ -920,7 +937,7 @@ pub(super) mod _os { let stat = stat_inner(file.clone(), dir_fd, follow_symlinks) .map_err(|err| IOErrorBuilder::with_filename(&err, file, vm))? .ok_or_else(|| crate::exceptions::cstring_error(vm))?; - Ok(StatResult::from_stat(&stat, vm).to_pyobject(vm)) + Ok(StatResultData::from_stat(&stat, vm).to_pyobject(vm)) } #[pyfunction] @@ -1215,10 +1232,9 @@ pub(super) mod _os { } #[cfg(all(any(unix, windows), not(target_os = "redox")))] - #[pyattr] - #[pyclass(module = "os", name = "times_result")] - #[derive(Debug, PyStructSequence)] - struct TimesResult { + #[derive(Debug)] + #[pystruct_sequence_data] + struct TimesResultData { pub user: f64, pub system: f64, pub children_user: f64, @@ -1226,9 +1242,14 @@ pub(super) mod _os { pub elapsed: f64, } + #[cfg(all(any(unix, windows), not(target_os = "redox")))] + #[pyattr] + #[pystruct_sequence(name = "times_result", module = "os", data = "TimesResultData")] + struct PyTimesResult; + #[cfg(all(any(unix, windows), not(target_os = "redox")))] #[pyclass(with(PyStructSequence))] - impl TimesResult {} + impl PyTimesResult {} #[cfg(all(any(unix, windows), not(target_os = "redox")))] #[pyfunction] @@ -1257,7 +1278,7 @@ pub(super) mod _os { let kernel = unsafe { kernel.assume_init() }; let user = unsafe { user.assume_init() }; - let times_result = TimesResult { + let times_result = TimesResultData { user: user.dwHighDateTime as f64 * 429.4967296 + user.dwLowDateTime as f64 * 1e-7, system: kernel.dwHighDateTime as f64 * 429.4967296 + kernel.dwLowDateTime as f64 * 1e-7, @@ -1285,7 +1306,7 @@ pub(super) mod _os { return Err(vm.new_os_error("Fail to get times".to_string())); } - let times_result = TimesResult { + let times_result = TimesResultData { user: t.tms_utime as f64 / tick_for_second, system: t.tms_stime as f64 / tick_for_second, children_user: t.tms_cutime as f64 / tick_for_second, @@ -1458,21 +1479,23 @@ pub(super) mod _os { } } - #[pyattr] - #[pyclass(module = "os", name = "terminal_size")] - #[derive(PyStructSequence)] + #[pystruct_sequence_data] #[allow(dead_code)] - pub(crate) struct PyTerminalSize { + pub(crate) struct TerminalSizeData { pub columns: usize, pub lines: usize, } + + #[pyattr] + #[pystruct_sequence(name = "terminal_size", module = "os", data = "TerminalSizeData")] + pub(crate) struct PyTerminalSize; + #[pyclass(with(PyStructSequence))] impl PyTerminalSize {} - #[pyattr] - #[pyclass(module = "os", name = "uname_result")] - #[derive(Debug, PyStructSequence)] - pub(crate) struct UnameResult { + #[derive(Debug)] + #[pystruct_sequence_data] + pub(crate) struct UnameResultData { pub sysname: String, pub nodename: String, pub release: String, @@ -1480,8 +1503,12 @@ pub(super) mod _os { pub machine: String, } + #[pyattr] + #[pystruct_sequence(name = "uname_result", module = "os", data = "UnameResultData")] + pub(crate) struct PyUnameResult; + #[pyclass(with(PyStructSequence))] - impl UnameResult {} + impl PyUnameResult {} pub(super) fn support_funcs() -> Vec<SupportFunc> { let mut supports = super::platform::module::support_funcs(); diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 071f93d7ee0..7409064668c 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -1330,9 +1330,9 @@ pub mod module { } #[pyfunction] - fn uname(vm: &VirtualMachine) -> PyResult<_os::UnameResult> { + fn uname(vm: &VirtualMachine) -> PyResult<_os::UnameResultData> { let info = uname::uname().map_err(|err| err.into_pyexception(vm))?; - Ok(_os::UnameResult { + Ok(_os::UnameResultData { sysname: info.sysname, nodename: info.nodename, release: info.release, @@ -1730,7 +1730,7 @@ pub mod module { fn get_terminal_size( fd: OptionalArg<i32>, vm: &VirtualMachine, - ) -> PyResult<_os::PyTerminalSize> { + ) -> PyResult<_os::TerminalSizeData> { let (columns, lines) = { nix::ioctl_read_bad!(winsz, libc::TIOCGWINSZ, libc::winsize); let mut w = libc::winsize { @@ -1743,7 +1743,7 @@ pub mod module { .map_err(|err| err.into_pyexception(vm))?; (w.ws_col.into(), w.ws_row.into()) }; - Ok(_os::PyTerminalSize { columns, lines }) + Ok(_os::TerminalSizeData { columns, lines }) } // from libstd: diff --git a/crates/vm/src/stdlib/pwd.rs b/crates/vm/src/stdlib/pwd.rs index 525b957e56d..e4d7075dbc8 100644 --- a/crates/vm/src/stdlib/pwd.rs +++ b/crates/vm/src/stdlib/pwd.rs @@ -16,10 +16,8 @@ mod pwd { #[cfg(not(target_os = "android"))] use crate::{PyObjectRef, convert::ToPyObject}; - #[pyattr] - #[pyclass(module = "pwd", name = "struct_passwd")] - #[derive(PyStructSequence)] - struct Passwd { + #[pystruct_sequence_data] + struct PasswdData { pw_name: String, pw_passwd: String, pw_uid: u32, @@ -29,10 +27,14 @@ mod pwd { pw_shell: String, } + #[pyattr] + #[pystruct_sequence(name = "struct_passwd", module = "pwd", data = "PasswdData")] + struct PyPasswd; + #[pyclass(with(PyStructSequence))] - impl Passwd {} + impl PyPasswd {} - impl From<User> for Passwd { + impl From<User> for PasswdData { fn from(user: User) -> Self { // this is just a pain... let cstr_lossy = |s: std::ffi::CString| { @@ -44,7 +46,7 @@ mod pwd { .into_string() .unwrap_or_else(|s| s.to_string_lossy().into_owned()) }; - Passwd { + PasswdData { pw_name: user.name, pw_passwd: cstr_lossy(user.passwd), pw_uid: user.uid.as_raw(), @@ -57,7 +59,7 @@ mod pwd { } #[pyfunction] - fn getpwnam(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<Passwd> { + fn getpwnam(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<PasswdData> { let pw_name = name.as_str(); if pw_name.contains('\0') { return Err(exceptions::cstring_error(vm)); @@ -70,11 +72,11 @@ mod pwd { .into(), ) })?; - Ok(Passwd::from(user)) + Ok(PasswdData::from(user)) } #[pyfunction] - fn getpwuid(uid: PyIntRef, vm: &VirtualMachine) -> PyResult<Passwd> { + fn getpwuid(uid: PyIntRef, vm: &VirtualMachine) -> PyResult<PasswdData> { let uid_t = libc::uid_t::try_from(uid.as_bigint()) .map(unistd::Uid::from_raw) .ok(); @@ -90,7 +92,7 @@ mod pwd { .into(), ) })?; - Ok(Passwd::from(user)) + Ok(PasswdData::from(user)) } // TODO: maybe merge this functionality into nix? @@ -105,7 +107,7 @@ mod pwd { unsafe { libc::setpwent() }; while let Some(ptr) = std::ptr::NonNull::new(unsafe { libc::getpwent() }) { let user = User::from(unsafe { ptr.as_ref() }); - let passwd = Passwd::from(user).to_pyobject(vm); + let passwd = PasswdData::from(user).to_pyobject(vm); list.push(passwd); } unsafe { libc::endpwent() }; diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 12a69c23138..602fdff1eb1 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -1,6 +1,6 @@ use crate::{Py, PyResult, VirtualMachine, builtins::PyModule, convert::ToPyObject}; -pub(crate) use sys::{__module_def, DOC, MAXSIZE, MULTIARCH, UnraisableHookArgs}; +pub(crate) use sys::{__module_def, DOC, MAXSIZE, MULTIARCH, UnraisableHookArgsData}; #[pymodule] mod sys { @@ -450,12 +450,12 @@ mod sys { #[pyattr] fn flags(vm: &VirtualMachine) -> PyTupleRef { - Flags::from_settings(&vm.state.settings).into_struct_sequence(vm) + PyFlags::from_data(FlagsData::from_settings(&vm.state.settings), vm) } #[pyattr] fn float_info(vm: &VirtualMachine) -> PyTupleRef { - PyFloatInfo::INFO.into_struct_sequence(vm) + PyFloatInfo::from_data(FloatInfoData::INFO, vm) } #[pyfunction] @@ -656,7 +656,7 @@ mod sys { .map_err(|_| vm.new_os_error("service pack is not ASCII".to_owned()))? }; let real_version = get_kernel32_version().map_err(|e| vm.new_os_error(e.to_string()))?; - Ok(WindowsVersion { + let winver = WindowsVersionData { major: real_version.0, minor: real_version.1, build: real_version.2, @@ -667,11 +667,11 @@ mod sys { suite_mask: version.wSuiteMask, product_type: version.wProductType, platform_version: (real_version.0, real_version.1, real_version.2), // TODO Provide accurate version, like CPython impl - } - .into_struct_sequence(vm)) + }; + Ok(PyWindowsVersion::from_data(winver, vm)) } - fn _unraisablehook(unraisable: UnraisableHookArgs, vm: &VirtualMachine) -> PyResult<()> { + fn _unraisablehook(unraisable: UnraisableHookArgsData, vm: &VirtualMachine) -> PyResult<()> { use super::PyStderr; let stderr = PyStderr(vm); @@ -727,7 +727,7 @@ mod sys { #[pyattr] #[pyfunction(name = "__unraisablehook__")] - fn unraisablehook(unraisable: UnraisableHookArgs, vm: &VirtualMachine) { + fn unraisablehook(unraisable: UnraisableHookArgsData, vm: &VirtualMachine) { if let Err(e) = _unraisablehook(unraisable, vm) { let stderr = super::PyStderr(vm); writeln!( @@ -742,7 +742,7 @@ mod sys { #[pyattr] fn hash_info(vm: &VirtualMachine) -> PyTupleRef { - PyHashInfo::INFO.into_struct_sequence(vm) + PyHashInfo::from_data(HashInfoData::INFO, vm) } #[pyfunction] @@ -752,7 +752,7 @@ mod sys { #[pyattr] fn int_info(vm: &VirtualMachine) -> PyTupleRef { - PyIntInfo::INFO.into_struct_sequence(vm) + PyIntInfo::from_data(IntInfoData::INFO, vm) } #[pyfunction] @@ -762,7 +762,7 @@ mod sys { #[pyfunction] fn set_int_max_str_digits(maxdigits: usize, vm: &VirtualMachine) -> PyResult<()> { - let threshold = PyIntInfo::INFO.str_digits_check_threshold; + let threshold = IntInfoData::INFO.str_digits_check_threshold; if maxdigits == 0 || maxdigits >= threshold { vm.state.int_max_str_digits.store(maxdigits); Ok(()) @@ -812,12 +812,12 @@ mod sys { #[cfg(feature = "threading")] #[pyattr] fn thread_info(vm: &VirtualMachine) -> PyTupleRef { - PyThreadInfo::INFO.into_struct_sequence(vm) + PyThreadInfo::from_data(ThreadInfoData::INFO, vm) } #[pyattr] fn version_info(vm: &VirtualMachine) -> PyTupleRef { - VersionInfo::VERSION.into_struct_sequence(vm) + PyVersionInfo::from_data(VersionInfoData::VERSION, vm) } fn update_use_tracing(vm: &VirtualMachine) { @@ -898,19 +898,22 @@ mod sys { Ok(()) } - #[pyclass(no_attr, name = "asyncgen_hooks")] - #[derive(PyStructSequence)] - pub(super) struct PyAsyncgenHooks { + #[pystruct_sequence_data] + pub(super) struct AsyncgenHooksData { firstiter: PyObjectRef, finalizer: PyObjectRef, } + #[pyattr] + #[pystruct_sequence(name = "asyncgen_hooks", data = "AsyncgenHooksData")] + pub(super) struct PyAsyncgenHooks; + #[pyclass(with(PyStructSequence))] impl PyAsyncgenHooks {} #[pyfunction] - fn get_asyncgen_hooks(vm: &VirtualMachine) -> PyAsyncgenHooks { - PyAsyncgenHooks { + fn get_asyncgen_hooks(vm: &VirtualMachine) -> AsyncgenHooksData { + AsyncgenHooksData { firstiter: crate::vm::thread::ASYNC_GEN_FIRSTITER .with_borrow(Clone::clone) .to_pyobject(vm), @@ -923,9 +926,9 @@ mod sys { /// sys.flags /// /// Flags provided through command line arguments or environment vars. - #[pyclass(no_attr, name = "flags", module = "sys")] - #[derive(Debug, PyStructSequence)] - pub(super) struct Flags { + #[derive(Debug)] + #[pystruct_sequence_data] + pub(super) struct FlagsData { /// -d debug: u8, /// -i @@ -964,8 +967,7 @@ mod sys { warn_default_encoding: u8, } - #[pyclass(with(PyStructSequence))] - impl Flags { + impl FlagsData { const fn from_settings(settings: &Settings) -> Self { Self { debug: settings.debug, @@ -988,7 +990,13 @@ mod sys { warn_default_encoding: settings.warn_default_encoding as u8, } } + } + + #[pystruct_sequence(name = "flags", module = "sys", data = "FlagsData", no_attr)] + pub(super) struct PyFlags; + #[pyclass(with(PyStructSequence))] + impl PyFlags { #[pyslot] fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { Err(vm.new_type_error("cannot create 'sys.flags' instances")) @@ -996,17 +1004,15 @@ mod sys { } #[cfg(feature = "threading")] - #[pyclass(no_attr, name = "thread_info")] - #[derive(PyStructSequence)] - pub(super) struct PyThreadInfo { + #[pystruct_sequence_data] + pub(super) struct ThreadInfoData { name: Option<&'static str>, lock: Option<&'static str>, version: Option<&'static str>, } #[cfg(feature = "threading")] - #[pyclass(with(PyStructSequence))] - impl PyThreadInfo { + impl ThreadInfoData { const INFO: Self = Self { name: crate::stdlib::thread::_thread::PYTHREAD_NAME, // As I know, there's only way to use lock as "Mutex" in Rust @@ -1016,9 +1022,16 @@ mod sys { }; } - #[pyclass(no_attr, name = "float_info")] - #[derive(PyStructSequence)] - pub(super) struct PyFloatInfo { + #[cfg(feature = "threading")] + #[pystruct_sequence(name = "thread_info", data = "ThreadInfoData", no_attr)] + pub(super) struct PyThreadInfo; + + #[cfg(feature = "threading")] + #[pyclass(with(PyStructSequence))] + impl PyThreadInfo {} + + #[pystruct_sequence_data] + pub(super) struct FloatInfoData { max: f64, max_exp: i32, max_10_exp: i32, @@ -1032,8 +1045,7 @@ mod sys { rounds: i32, } - #[pyclass(with(PyStructSequence))] - impl PyFloatInfo { + impl FloatInfoData { const INFO: Self = Self { max: f64::MAX, max_exp: f64::MAX_EXP, @@ -1049,9 +1061,14 @@ mod sys { }; } - #[pyclass(no_attr, name = "hash_info")] - #[derive(PyStructSequence)] - pub(super) struct PyHashInfo { + #[pystruct_sequence(name = "float_info", data = "FloatInfoData", no_attr)] + pub(super) struct PyFloatInfo; + + #[pyclass(with(PyStructSequence))] + impl PyFloatInfo {} + + #[pystruct_sequence_data] + pub(super) struct HashInfoData { width: usize, modulus: PyUHash, inf: PyHash, @@ -1063,8 +1080,7 @@ mod sys { cutoff: usize, } - #[pyclass(with(PyStructSequence))] - impl PyHashInfo { + impl HashInfoData { const INFO: Self = { use rustpython_common::hash::*; Self { @@ -1081,17 +1097,21 @@ mod sys { }; } - #[pyclass(no_attr, name = "int_info")] - #[derive(PyStructSequence)] - pub(super) struct PyIntInfo { + #[pystruct_sequence(name = "hash_info", data = "HashInfoData", no_attr)] + pub(super) struct PyHashInfo; + + #[pyclass(with(PyStructSequence))] + impl PyHashInfo {} + + #[pystruct_sequence_data] + pub(super) struct IntInfoData { bits_per_digit: usize, sizeof_digit: usize, default_max_str_digits: usize, str_digits_check_threshold: usize, } - #[pyclass(with(PyStructSequence))] - impl PyIntInfo { + impl IntInfoData { const INFO: Self = Self { bits_per_digit: 30, //? sizeof_digit: std::mem::size_of::<u32>(), @@ -1100,9 +1120,15 @@ mod sys { }; } - #[pyclass(no_attr, name = "version_info")] - #[derive(Default, Debug, PyStructSequence)] - pub struct VersionInfo { + #[pystruct_sequence(name = "int_info", data = "IntInfoData", no_attr)] + pub(super) struct PyIntInfo; + + #[pyclass(with(PyStructSequence))] + impl PyIntInfo {} + + #[derive(Default, Debug)] + #[pystruct_sequence_data] + pub struct VersionInfoData { major: usize, minor: usize, micro: usize, @@ -1110,8 +1136,7 @@ mod sys { serial: usize, } - #[pyclass(with(PyStructSequence))] - impl VersionInfo { + impl VersionInfoData { pub const VERSION: Self = Self { major: version::MAJOR, minor: version::MINOR, @@ -1119,6 +1144,13 @@ mod sys { releaselevel: version::RELEASELEVEL, serial: version::SERIAL, }; + } + + #[pystruct_sequence(name = "version_info", data = "VersionInfoData", no_attr)] + pub struct PyVersionInfo; + + #[pyclass(with(PyStructSequence))] + impl PyVersionInfo { #[pyslot] fn slot_new( _cls: crate::builtins::type_::PyTypeRef, @@ -1130,9 +1162,9 @@ mod sys { } #[cfg(windows)] - #[pyclass(no_attr, name = "getwindowsversion")] - #[derive(Default, Debug, PyStructSequence)] - pub(super) struct WindowsVersion { + #[derive(Default, Debug)] + #[pystruct_sequence_data] + pub(super) struct WindowsVersionData { major: u32, minor: u32, build: u32, @@ -1145,13 +1177,17 @@ mod sys { platform_version: (u32, u32, u32), } + #[cfg(windows)] + #[pystruct_sequence(name = "getwindowsversion", data = "WindowsVersionData", no_attr)] + pub(super) struct PyWindowsVersion; + #[cfg(windows)] #[pyclass(with(PyStructSequence))] - impl WindowsVersion {} + impl PyWindowsVersion {} - #[pyclass(no_attr, name = "UnraisableHookArgs")] - #[derive(Debug, PyStructSequence, TryIntoPyStructSequence)] - pub struct UnraisableHookArgs { + #[derive(Debug)] + #[pystruct_sequence_data(try_from_object)] + pub struct UnraisableHookArgsData { pub exc_type: PyTypeRef, pub exc_value: PyObjectRef, pub exc_traceback: PyObjectRef, @@ -1159,8 +1195,11 @@ mod sys { pub object: PyObjectRef, } + #[pystruct_sequence(name = "UnraisableHookArgs", data = "UnraisableHookArgsData", no_attr)] + pub struct PyUnraisableHookArgs; + #[pyclass(with(PyStructSequence))] - impl UnraisableHookArgs {} + impl PyUnraisableHookArgs {} } pub(crate) fn init_module(vm: &VirtualMachine, module: &Py<PyModule>, builtins: &Py<PyModule>) { diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index fb64e28b906..0f3b21a3d93 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -34,10 +34,10 @@ unsafe extern "C" { #[pymodule(name = "time", with(platform))] mod decl { use crate::{ - AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, + AsObject, PyObjectRef, PyResult, VirtualMachine, builtins::{PyStrRef, PyTypeRef, PyUtf8StrRef}, function::{Either, FuncArgs, OptionalArg}, - types::PyStructSequence, + types::{PyStructSequence, struct_sequence_new}, }; use chrono::{ DateTime, Datelike, TimeZone, Timelike, @@ -280,7 +280,9 @@ mod decl { /// Construct a localtime from the optional seconds, or get the current local time. fn naive_or_local(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> { Ok(match self { - Self::Present(secs) => pyobj_to_date_time(secs, vm)?.naive_utc(), + Self::Present(secs) => pyobj_to_date_time(secs, vm)? + .with_timezone(&chrono::Local) + .naive_local(), Self::Missing => chrono::offset::Local::now().naive_local(), }) } @@ -293,7 +295,7 @@ mod decl { } } - impl OptionalArg<PyStructTime> { + impl OptionalArg<StructTimeData> { fn naive_or_local(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> { Ok(match self { Self::Present(t) => t.to_date_time(vm)?, @@ -304,33 +306,41 @@ mod decl { /// https://docs.python.org/3/library/time.html?highlight=gmtime#time.gmtime #[pyfunction] - fn gmtime(secs: OptionalArg<Either<f64, i64>>, vm: &VirtualMachine) -> PyResult<PyStructTime> { + fn gmtime( + secs: OptionalArg<Either<f64, i64>>, + vm: &VirtualMachine, + ) -> PyResult<StructTimeData> { let instant = secs.naive_or_utc(vm)?; - Ok(PyStructTime::new(vm, instant, 0)) + Ok(StructTimeData::new(vm, instant, 0)) } #[pyfunction] fn localtime( secs: OptionalArg<Either<f64, i64>>, vm: &VirtualMachine, - ) -> PyResult<PyStructTime> { + ) -> PyResult<StructTimeData> { let instant = secs.naive_or_local(vm)?; // TODO: isdst flag must be valid value here // https://docs.python.org/3/library/time.html#time.localtime - Ok(PyStructTime::new(vm, instant, -1)) + Ok(StructTimeData::new(vm, instant, -1)) } #[pyfunction] - fn mktime(t: PyStructTime, vm: &VirtualMachine) -> PyResult<f64> { + fn mktime(t: StructTimeData, vm: &VirtualMachine) -> PyResult<f64> { let datetime = t.to_date_time(vm)?; - let seconds_since_epoch = datetime.and_utc().timestamp() as f64; + // mktime interprets struct_time as local time + let local_dt = chrono::Local + .from_local_datetime(&datetime) + .single() + .ok_or_else(|| vm.new_overflow_error("mktime argument out of range"))?; + let seconds_since_epoch = local_dt.timestamp() as f64; Ok(seconds_since_epoch) } const CFMT: &str = "%a %b %e %H:%M:%S %Y"; #[pyfunction] - fn asctime(t: OptionalArg<PyStructTime>, vm: &VirtualMachine) -> PyResult { + fn asctime(t: OptionalArg<StructTimeData>, vm: &VirtualMachine) -> PyResult { let instant = t.naive_or_local(vm)?; let formatted_time = instant.format(CFMT).to_string(); Ok(vm.ctx.new_str(formatted_time).into()) @@ -345,7 +355,7 @@ mod decl { #[pyfunction] fn strftime( format: PyUtf8StrRef, - t: OptionalArg<PyStructTime>, + t: OptionalArg<StructTimeData>, vm: &VirtualMachine, ) -> PyResult { use std::fmt::Write; @@ -465,34 +475,31 @@ mod decl { Ok(get_process_time(vm)?.as_nanos() as u64) } - #[pyattr] - #[pyclass(name = "struct_time")] - #[derive(PyStructSequence, TryIntoPyStructSequence)] - #[allow(dead_code)] - struct PyStructTime { - tm_year: PyObjectRef, - tm_mon: PyObjectRef, - tm_mday: PyObjectRef, - tm_hour: PyObjectRef, - tm_min: PyObjectRef, - tm_sec: PyObjectRef, - tm_wday: PyObjectRef, - tm_yday: PyObjectRef, - tm_isdst: PyObjectRef, - #[pystruct(skip)] - tm_gmtoff: PyObjectRef, - #[pystruct(skip)] - tm_zone: PyObjectRef, - } - - impl std::fmt::Debug for PyStructTime { + /// Data struct for struct_time + #[pystruct_sequence_data(try_from_object)] + pub struct StructTimeData { + pub tm_year: PyObjectRef, + pub tm_mon: PyObjectRef, + pub tm_mday: PyObjectRef, + pub tm_hour: PyObjectRef, + pub tm_min: PyObjectRef, + pub tm_sec: PyObjectRef, + pub tm_wday: PyObjectRef, + pub tm_yday: PyObjectRef, + pub tm_isdst: PyObjectRef, + #[pystruct_sequence(skip)] + pub tm_gmtoff: PyObjectRef, + #[pystruct_sequence(skip)] + pub tm_zone: PyObjectRef, + } + + impl std::fmt::Debug for StructTimeData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "struct_time()") } } - #[pyclass(with(PyStructSequence))] - impl PyStructTime { + impl StructTimeData { fn new(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self { let local_time = chrono::Local.from_local_datetime(&tm).unwrap(); let offset_seconds = @@ -531,12 +538,18 @@ mod decl { ); Ok(dt) } + } + + #[pyattr] + #[pystruct_sequence(name = "struct_time", module = "time", data = "StructTimeData")] + pub struct PyStructTime; + #[pyclass(with(PyStructSequence))] + impl PyStructTime { #[pyslot] - fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // cls is ignorable because this is not a basetype - let seq = args.bind(vm)?; - Ok(vm.new_pyobj(Self::try_from_object(vm, seq)?)) + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let seq: PyObjectRef = args.bind(vm)?; + struct_sequence_new(cls, seq, vm) } } diff --git a/crates/vm/src/types/mod.rs b/crates/vm/src/types/mod.rs index 56d925bfaf1..f19328cdd2d 100644 --- a/crates/vm/src/types/mod.rs +++ b/crates/vm/src/types/mod.rs @@ -3,5 +3,5 @@ mod structseq; mod zoo; pub use slot::*; -pub use structseq::PyStructSequence; +pub use structseq::{PyStructSequence, PyStructSequenceData, struct_sequence_new}; pub(crate) use zoo::TypeZoo; diff --git a/crates/vm/src/types/structseq.rs b/crates/vm/src/types/structseq.rs index 318280f8620..b2ff5868d45 100644 --- a/crates/vm/src/types/structseq.rs +++ b/crates/vm/src/types/structseq.rs @@ -1,50 +1,193 @@ use crate::{ - AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyBaseExceptionRef, PyStrRef, PyTuple, PyTupleRef, PyType}, + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, atomic_func, + builtins::{ + PyBaseExceptionRef, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, type_::PointerSlot, + }, class::{PyClassImpl, StaticType}, + function::{Either, PyComparisonValue}, + iter::PyExactSizeIterator, + protocol::{PyMappingMethods, PySequenceMethods}, + sliceable::{SequenceIndex, SliceableSequenceOp}, + types::PyComparisonOp, vm::Context, }; +use std::sync::LazyLock; -#[pyclass] -pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { +/// Create a new struct sequence instance from a sequence. +/// +/// The class must have `n_sequence_fields` and `n_fields` attributes set +/// (done automatically by `PyStructSequence::extend_pyclass`). +pub fn struct_sequence_new(cls: PyTypeRef, seq: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // = structseq_new + + #[cold] + fn length_error( + tp_name: &str, + min_len: usize, + max_len: usize, + len: usize, + vm: &VirtualMachine, + ) -> PyBaseExceptionRef { + if min_len == max_len { + vm.new_type_error(format!( + "{tp_name}() takes a {min_len}-sequence ({len}-sequence given)" + )) + } else if len < min_len { + vm.new_type_error(format!( + "{tp_name}() takes an at least {min_len}-sequence ({len}-sequence given)" + )) + } else { + vm.new_type_error(format!( + "{tp_name}() takes an at most {max_len}-sequence ({len}-sequence given)" + )) + } + } + + let min_len: usize = cls + .get_attr(identifier!(vm.ctx, n_sequence_fields)) + .ok_or_else(|| vm.new_type_error("missing n_sequence_fields attribute"))? + .try_into_value(vm)?; + let max_len: usize = cls + .get_attr(identifier!(vm.ctx, n_fields)) + .ok_or_else(|| vm.new_type_error("missing n_fields attribute"))? + .try_into_value(vm)?; + + let seq: Vec<PyObjectRef> = seq.try_into_value(vm)?; + let len = seq.len(); + + if len < min_len || len > max_len { + return Err(length_error(&cls.slot_name(), min_len, max_len, len, vm)); + } + + // Copy items and pad with None + let mut items = seq; + items.resize_with(max_len, || vm.ctx.none()); + + PyTuple::new_unchecked(items.into_boxed_slice()) + .into_ref_with_type(vm, cls) + .map(Into::into) +} + +fn get_visible_len(obj: &PyObject, vm: &VirtualMachine) -> PyResult<usize> { + obj.class() + .get_attr(identifier!(vm.ctx, n_sequence_fields)) + .ok_or_else(|| vm.new_type_error("missing n_sequence_fields"))? + .try_into_value(vm) +} + +/// Sequence methods for struct sequences. +/// Uses n_sequence_fields to determine visible length. +static STRUCT_SEQUENCE_AS_SEQUENCE: LazyLock<PySequenceMethods> = + LazyLock::new(|| PySequenceMethods { + length: atomic_func!(|seq, vm| get_visible_len(seq.obj, vm)), + concat: atomic_func!(|seq, other, vm| { + // Convert to visible-only tuple, then use regular tuple concat + let n_seq = get_visible_len(seq.obj, vm)?; + let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap(); + let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect(); + let visible_tuple = PyTuple::new_ref(visible, &vm.ctx); + // Use tuple's concat implementation + visible_tuple.as_object().to_sequence().concat(other, vm) + }), + repeat: atomic_func!(|seq, n, vm| { + // Convert to visible-only tuple, then use regular tuple repeat + let n_seq = get_visible_len(seq.obj, vm)?; + let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap(); + let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect(); + let visible_tuple = PyTuple::new_ref(visible, &vm.ctx); + // Use tuple's repeat implementation + visible_tuple.as_object().to_sequence().repeat(n, vm) + }), + item: atomic_func!(|seq, i, vm| { + let n_seq = get_visible_len(seq.obj, vm)?; + let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap(); + let idx = if i < 0 { + let pos_i = n_seq as isize + i; + if pos_i < 0 { + return Err(vm.new_index_error("tuple index out of range")); + } + pos_i as usize + } else { + i as usize + }; + if idx >= n_seq { + return Err(vm.new_index_error("tuple index out of range")); + } + Ok(tuple[idx].clone()) + }), + contains: atomic_func!(|seq, needle, vm| { + let n_seq = get_visible_len(seq.obj, vm)?; + let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap(); + for item in tuple.iter().take(n_seq) { + if item.rich_compare_bool(needle, PyComparisonOp::Eq, vm)? { + return Ok(true); + } + } + Ok(false) + }), + ..PySequenceMethods::NOT_IMPLEMENTED + }); + +/// Mapping methods for struct sequences. +/// Handles subscript (indexing) with visible length bounds. +static STRUCT_SEQUENCE_AS_MAPPING: LazyLock<PyMappingMethods> = + LazyLock::new(|| PyMappingMethods { + length: atomic_func!(|mapping, vm| get_visible_len(mapping.obj, vm)), + subscript: atomic_func!(|mapping, needle, vm| { + let n_seq = get_visible_len(mapping.obj, vm)?; + let tuple = mapping.obj.downcast_ref::<PyTuple>().unwrap(); + let visible_elements = &tuple.as_slice()[..n_seq]; + + match SequenceIndex::try_from_borrowed_object(vm, needle, "tuple")? { + SequenceIndex::Int(i) => visible_elements.getitem_by_index(vm, i), + SequenceIndex::Slice(slice) => visible_elements + .getitem_by_slice(vm, slice) + .map(|x| vm.ctx.new_tuple(x).into()), + } + }), + ..PyMappingMethods::NOT_IMPLEMENTED + }); + +/// Trait for Data structs that back a PyStructSequence. +/// +/// This trait is implemented by `#[pystruct_sequence_data]` on the Data struct. +/// It provides field information, tuple conversion, and element parsing. +pub trait PyStructSequenceData: Sized { + /// Names of required fields (in order). Shown in repr. const REQUIRED_FIELD_NAMES: &'static [&'static str]; + + /// Names of optional/skipped fields (in order, after required fields). const OPTIONAL_FIELD_NAMES: &'static [&'static str]; + /// Number of unnamed fields (visible but index-only access). + const UNNAMED_FIELDS_LEN: usize = 0; + + /// Convert this Data struct into a PyTuple. fn into_tuple(self, vm: &VirtualMachine) -> PyTuple; - fn into_struct_sequence(self, vm: &VirtualMachine) -> PyTupleRef { - self.into_tuple(vm) - .into_ref_with_type(vm, Self::static_type().to_owned()) - .unwrap() + /// Construct this Data struct from tuple elements. + /// Default implementation returns an error. + /// Override with `#[pystruct_sequence_data(try_from_object)]` to enable. + fn try_from_elements(_elements: Vec<PyObjectRef>, vm: &VirtualMachine) -> PyResult<Self> { + Err(vm.new_type_error("This struct sequence does not support construction from elements")) } +} - fn try_elements_from(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> { - #[cold] - fn sequence_length_error( - name: &str, - len: usize, - vm: &VirtualMachine, - ) -> PyBaseExceptionRef { - vm.new_type_error(format!("{name} takes a sequence of length {len}")) - } +/// Trait for Python struct sequence types. +/// +/// This trait is implemented by the `#[pystruct_sequence]` macro on the Python type struct. +/// It connects to the Data struct and provides Python-level functionality. +#[pyclass] +pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { + /// The Data struct that provides field definitions. + type Data: PyStructSequenceData; + /// Convert a Data struct into a PyStructSequence instance. + fn from_data(data: Self::Data, vm: &VirtualMachine) -> PyTupleRef { let typ = Self::static_type(); - // if !obj.fast_isinstance(typ) { - // return Err(vm.new_type_error(format!( - // "{} is not a subclass of {}", - // obj.class().name(), - // typ.name(), - // ))); - // } - let seq: Vec<PyObjectRef> = obj.try_into_value(vm)?; - if seq.len() < Self::REQUIRED_FIELD_NAMES.len() { - return Err(sequence_length_error( - &typ.name(), - Self::REQUIRED_FIELD_NAMES.len(), - vm, - )); - } - Ok(seq) + data.into_tuple(vm) + .into_ref_with_type(vm, typ.to_owned()) + .unwrap() } #[pyslot] @@ -53,20 +196,21 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { .downcast_ref::<PyTuple>() .ok_or_else(|| vm.new_type_error("unexpected payload for __repr__"))?; + let field_names = Self::Data::REQUIRED_FIELD_NAMES; let format_field = |(value, name): (&PyObjectRef, _)| { let s = value.repr(vm)?; Ok(format!("{name}={s}")) }; let (body, suffix) = if let Some(_guard) = rustpython_vm::recursion::ReprGuard::enter(vm, zelf.as_ref()) { - if Self::REQUIRED_FIELD_NAMES.len() == 1 { + if field_names.len() == 1 { let value = zelf.first().unwrap(); - let formatted = format_field((value, Self::REQUIRED_FIELD_NAMES[0]))?; + let formatted = format_field((value, field_names[0]))?; (formatted, ",") } else { let fields: PyResult<Vec<_>> = zelf .iter() - .zip(Self::REQUIRED_FIELD_NAMES.iter().copied()) + .zip(field_names.iter().copied()) .map(format_field) .collect(); (fields?.join(", "), "") @@ -88,9 +232,23 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { vm.new_tuple((zelf.class().to_owned(), (vm.ctx.new_tuple(zelf.to_vec()),))) } + #[pymethod] + fn __getitem__(zelf: PyRef<PyTuple>, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let n_seq = get_visible_len(zelf.as_ref(), vm)?; + let visible_elements = &zelf.as_slice()[..n_seq]; + + match SequenceIndex::try_from_borrowed_object(vm, &needle, "tuple")? { + SequenceIndex::Int(i) => visible_elements.getitem_by_index(vm, i), + SequenceIndex::Slice(slice) => visible_elements + .getitem_by_slice(vm, slice) + .map(|x| vm.ctx.new_tuple(x).into()), + } + } + #[extend_class] fn extend_pyclass(ctx: &Context, class: &'static Py<PyType>) { - for (i, &name) in Self::REQUIRED_FIELD_NAMES.iter().enumerate() { + // Getters for named visible fields (indices 0 to REQUIRED_FIELD_NAMES.len() - 1) + for (i, &name) in Self::Data::REQUIRED_FIELD_NAMES.iter().enumerate() { // cast i to a u8 so there's less to store in the getter closure. // Hopefully there's not struct sequences with >=256 elements :P let i = i as u8; @@ -103,15 +261,125 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { ); } + // Getters for hidden/skipped fields (indices after visible fields) + let visible_count = Self::Data::REQUIRED_FIELD_NAMES.len() + Self::Data::UNNAMED_FIELDS_LEN; + for (i, &name) in Self::Data::OPTIONAL_FIELD_NAMES.iter().enumerate() { + let idx = (visible_count + i) as u8; + class.set_attr( + ctx.intern_str(name), + ctx.new_readonly_getset(name, class, move |zelf: &PyTuple| { + zelf[idx as usize].to_owned() + }) + .into(), + ); + } + class.set_attr( identifier!(ctx, __match_args__), ctx.new_tuple( - Self::REQUIRED_FIELD_NAMES + Self::Data::REQUIRED_FIELD_NAMES .iter() .map(|&name| ctx.new_str(name).into()) .collect::<Vec<_>>(), ) .into(), ); + + // special fields: + // n_sequence_fields = visible fields (named + unnamed) + // n_fields = all fields (visible + hidden/skipped) + // n_unnamed_fields + let n_unnamed_fields = Self::Data::UNNAMED_FIELDS_LEN; + let n_sequence_fields = Self::Data::REQUIRED_FIELD_NAMES.len() + n_unnamed_fields; + let n_fields = n_sequence_fields + Self::Data::OPTIONAL_FIELD_NAMES.len(); + class.set_attr( + identifier!(ctx, n_sequence_fields), + ctx.new_int(n_sequence_fields).into(), + ); + class.set_attr(identifier!(ctx, n_fields), ctx.new_int(n_fields).into()); + class.set_attr( + identifier!(ctx, n_unnamed_fields), + ctx.new_int(n_unnamed_fields).into(), + ); + + // Override as_sequence and as_mapping slots to use visible length + class.slots.as_sequence.store(Some(PointerSlot::from( + &*STRUCT_SEQUENCE_AS_SEQUENCE as &'static PySequenceMethods, + ))); + class.slots.as_mapping.store(Some(PointerSlot::from( + &*STRUCT_SEQUENCE_AS_MAPPING as &'static PyMappingMethods, + ))); + + // Override iter slot to return only visible elements + class.slots.iter.store(Some(struct_sequence_iter)); + + // Override hash slot to hash only visible elements + class.slots.hash.store(Some(struct_sequence_hash)); + + // Override richcompare slot to compare only visible elements + class + .slots + .richcompare + .store(Some(struct_sequence_richcompare)); } } + +/// Iterator function for struct sequences - returns only visible elements +fn struct_sequence_iter(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let tuple = zelf + .downcast_ref::<PyTuple>() + .ok_or_else(|| vm.new_type_error("expected tuple"))?; + let n_seq = get_visible_len(&zelf, vm)?; + let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect(); + let visible_tuple = PyTuple::new_ref(visible, &vm.ctx); + visible_tuple + .as_object() + .to_owned() + .get_iter(vm) + .map(Into::into) +} + +/// Hash function for struct sequences - hashes only visible elements +fn struct_sequence_hash( + zelf: &PyObject, + vm: &VirtualMachine, +) -> PyResult<crate::common::hash::PyHash> { + let tuple = zelf + .downcast_ref::<PyTuple>() + .ok_or_else(|| vm.new_type_error("expected tuple"))?; + let n_seq = get_visible_len(zelf, vm)?; + // Create a visible-only tuple and hash it + let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect(); + let visible_tuple = PyTuple::new_ref(visible, &vm.ctx); + visible_tuple.as_object().hash(vm) +} + +/// Rich comparison for struct sequences - compares only visible elements +fn struct_sequence_richcompare( + zelf: &PyObject, + other: &PyObject, + op: PyComparisonOp, + vm: &VirtualMachine, +) -> PyResult<Either<PyObjectRef, PyComparisonValue>> { + let zelf_tuple = zelf + .downcast_ref::<PyTuple>() + .ok_or_else(|| vm.new_type_error("expected tuple"))?; + + // If other is not a tuple, return NotImplemented + let Some(other_tuple) = other.downcast_ref::<PyTuple>() else { + return Ok(Either::B(PyComparisonValue::NotImplemented)); + }; + + let zelf_len = get_visible_len(zelf, vm)?; + // For other, try to get visible len; if it fails (not a struct sequence), use full length + let other_len = get_visible_len(other, vm).unwrap_or(other_tuple.len()); + + let zelf_visible = &zelf_tuple.as_slice()[..zelf_len]; + let other_visible = &other_tuple.as_slice()[..other_len]; + + // Use the same comparison logic as regular tuples + zelf_visible + .iter() + .richcompare(other_visible.iter(), op, vm) + .map(|v| Either::B(PyComparisonValue::Implemented(v))) +} diff --git a/crates/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs index c75bfe18558..191c090f121 100644 --- a/crates/vm/src/vm/context.rs +++ b/crates/vm/src/vm/context.rs @@ -245,6 +245,9 @@ declare_const_name! { items, keys, modules, + n_fields, + n_sequence_fields, + n_unnamed_fields, namereplace, replace, strict, diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index b793a283525..e6217e3e35d 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -465,7 +465,7 @@ impl VirtualMachine { let exc_type = e.class().to_owned(); let exc_traceback = e.__traceback__().to_pyobject(self); // TODO: actual traceback let exc_value = e.into(); - let args = stdlib::sys::UnraisableHookArgs { + let args = stdlib::sys::UnraisableHookArgsData { exc_type, exc_value, exc_traceback, From 89bcae7bea4c332f93ee575c83fdb968ba684364 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 6 Dec 2025 08:30:57 +0900 Subject: [PATCH 404/819] fix time (#6330) --- crates/vm/src/stdlib/time.rs | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index 0f3b21a3d93..ef4fbbea7f8 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -311,7 +311,7 @@ mod decl { vm: &VirtualMachine, ) -> PyResult<StructTimeData> { let instant = secs.naive_or_utc(vm)?; - Ok(StructTimeData::new(vm, instant, 0)) + Ok(StructTimeData::new_utc(vm, instant)) } #[pyfunction] @@ -322,7 +322,7 @@ mod decl { let instant = secs.naive_or_local(vm)?; // TODO: isdst flag must be valid value here // https://docs.python.org/3/library/time.html#time.localtime - Ok(StructTimeData::new(vm, instant, -1)) + Ok(StructTimeData::new_local(vm, instant, -1)) } #[pyfunction] @@ -500,12 +500,13 @@ mod decl { } impl StructTimeData { - fn new(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self { - let local_time = chrono::Local.from_local_datetime(&tm).unwrap(); - let offset_seconds = - local_time.offset().local_minus_utc() + if isdst == 1 { 3600 } else { 0 }; - let tz_abbr = local_time.format("%Z").to_string(); - + fn new_inner( + vm: &VirtualMachine, + tm: NaiveDateTime, + isdst: i32, + gmtoff: i32, + zone: &str, + ) -> Self { Self { tm_year: vm.ctx.new_int(tm.year()).into(), tm_mon: vm.ctx.new_int(tm.month()).into(), @@ -516,11 +517,25 @@ mod decl { tm_wday: vm.ctx.new_int(tm.weekday().num_days_from_monday()).into(), tm_yday: vm.ctx.new_int(tm.ordinal()).into(), tm_isdst: vm.ctx.new_int(isdst).into(), - tm_gmtoff: vm.ctx.new_int(offset_seconds).into(), - tm_zone: vm.ctx.new_str(tz_abbr).into(), + tm_gmtoff: vm.ctx.new_int(gmtoff).into(), + tm_zone: vm.ctx.new_str(zone).into(), } } + /// Create struct_time for UTC (gmtime) + fn new_utc(vm: &VirtualMachine, tm: NaiveDateTime) -> Self { + Self::new_inner(vm, tm, 0, 0, "UTC") + } + + /// Create struct_time for local timezone (localtime) + fn new_local(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self { + let local_time = chrono::Local.from_local_datetime(&tm).unwrap(); + let offset_seconds = + local_time.offset().local_minus_utc() + if isdst == 1 { 3600 } else { 0 }; + let tz_abbr = local_time.format("%Z").to_string(); + Self::new_inner(vm, tm, isdst, offset_seconds, &tz_abbr) + } + fn to_date_time(&self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> { let invalid_overflow = || vm.new_overflow_error("mktime argument out of range"); let invalid_value = || vm.new_value_error("invalid struct_time parameter"); From b3141d12310f521cdf2488037d8903170a86987d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 6 Dec 2025 09:26:33 +0900 Subject: [PATCH 405/819] mmap for windows (#6329) --- Cargo.lock | 4 +- Lib/test/test_mmap.py | 2 + crates/stdlib/Cargo.toml | 13 +- crates/stdlib/src/lib.rs | 5 +- crates/stdlib/src/mmap.rs | 638 ++++++++++++++++++++++++++++++-------- 5 files changed, 527 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aef688eb21f..569f52bc9bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1897,9 +1897,9 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.5.10" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index ed96be53cca..f80df63bb86 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -618,6 +618,7 @@ def test_non_ascii_byte(self): self.assertEqual(m.read_byte(), b) m.close() + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_tagname(self): data1 = b"0123456789" @@ -867,6 +868,7 @@ def test_resize_fails_if_mapping_held_elsewhere(self): finally: f.close() + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_resize_succeeds_with_error_for_second_named_mapping(self): """If a more than one mapping exists of the same name, none of them can diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index dbff752b99c..785e280f6ab 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -97,18 +97,16 @@ chrono.workspace = true mac_address = "1.1.3" uuid = { version = "1.1.2", features = ["v1"] } -# mmap -[target.'cfg(all(unix, not(target_arch = "wasm32")))'.dependencies] -memmap2 = "0.5.10" -page_size = "0.6" - [target.'cfg(all(unix, not(target_os = "redox"), not(target_os = "ios")))'.dependencies] termios = "0.3.3" [target.'cfg(unix)'.dependencies] rustix = { workspace = true } +# mmap + socket dependencies [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +memmap2 = "0.9.9" +page_size = "0.6" gethostname = "1.0.2" socket2 = { version = "0.6.0", features = ["all"] } dns-lookup = "3.0" @@ -146,12 +144,15 @@ widestring = { workspace = true } [target.'cfg(windows)'.dependencies.windows-sys] workspace = true features = [ + "Win32_Foundation", "Win32_Networking_WinSock", "Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_Ndis", "Win32_Security_Cryptography", + "Win32_Storage_FileSystem", "Win32_System_Environment", - "Win32_System_IO" + "Win32_System_IO", + "Win32_System_Threading" ] [target.'cfg(target_os = "macos")'.dependencies] diff --git a/crates/stdlib/src/lib.rs b/crates/stdlib/src/lib.rs index 01a27b76609..c9b5ca32b57 100644 --- a/crates/stdlib/src/lib.rs +++ b/crates/stdlib/src/lib.rs @@ -36,7 +36,7 @@ mod json; mod locale; mod math; -#[cfg(unix)] +#[cfg(any(unix, windows))] mod mmap; mod opcode; mod pyexpat; @@ -189,6 +189,9 @@ pub fn get_module_inits() -> impl Iterator<Item = (Cow<'static, str>, StdlibInit #[cfg(unix)] { "_posixsubprocess" => posixsubprocess::make_module, + } + #[cfg(any(unix, windows))] + { "mmap" => mmap::make_module, } #[cfg(all(unix, not(target_os = "redox")))] diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index 143bdcb43d7..7e53a27be85 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -22,46 +22,63 @@ mod mmap { types::{AsBuffer, AsMapping, AsSequence, Constructor, Representable}, }; use crossbeam_utils::atomic::AtomicCell; - use memmap2::{Advice, Mmap, MmapMut, MmapOptions}; - #[cfg(unix)] - use nix::sys::stat::fstat; - use nix::unistd; + use memmap2::{Mmap, MmapMut, MmapOptions}; use num_traits::Signed; - use rustpython_common::crt_fd; use std::io::{self, Write}; use std::ops::{Deref, DerefMut}; - fn advice_try_from_i32(vm: &VirtualMachine, i: i32) -> PyResult<Advice> { - Ok(match i { - libc::MADV_NORMAL => Advice::Normal, - libc::MADV_RANDOM => Advice::Random, - libc::MADV_SEQUENTIAL => Advice::Sequential, - libc::MADV_WILLNEED => Advice::WillNeed, - libc::MADV_DONTNEED => Advice::DontNeed, - #[cfg(any(target_os = "linux", target_os = "macos", target_os = "ios"))] - libc::MADV_FREE => Advice::Free, - #[cfg(target_os = "linux")] - libc::MADV_DONTFORK => Advice::DontFork, - #[cfg(target_os = "linux")] - libc::MADV_DOFORK => Advice::DoFork, - #[cfg(target_os = "linux")] - libc::MADV_MERGEABLE => Advice::Mergeable, - #[cfg(target_os = "linux")] - libc::MADV_UNMERGEABLE => Advice::Unmergeable, - #[cfg(target_os = "linux")] - libc::MADV_HUGEPAGE => Advice::HugePage, - #[cfg(target_os = "linux")] - libc::MADV_NOHUGEPAGE => Advice::NoHugePage, - #[cfg(target_os = "linux")] - libc::MADV_REMOVE => Advice::Remove, - #[cfg(target_os = "linux")] - libc::MADV_DONTDUMP => Advice::DontDump, - #[cfg(target_os = "linux")] - libc::MADV_DODUMP => Advice::DoDump, + #[cfg(unix)] + use nix::{sys::stat::fstat, unistd}; + #[cfg(unix)] + use rustpython_common::crt_fd; + + #[cfg(windows)] + use rustpython_common::suppress_iph; + #[cfg(windows)] + use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; + #[cfg(windows)] + use windows_sys::Win32::{ + Foundation::{ + CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE, + }, + Storage::FileSystem::{FILE_BEGIN, GetFileSize, SetEndOfFile, SetFilePointerEx}, + System::Threading::GetCurrentProcess, + }; + + #[cfg(unix)] + fn validate_advice(vm: &VirtualMachine, advice: i32) -> PyResult<i32> { + match advice { + libc::MADV_NORMAL + | libc::MADV_RANDOM + | libc::MADV_SEQUENTIAL + | libc::MADV_WILLNEED + | libc::MADV_DONTNEED => Ok(advice), + #[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "ios", + target_os = "freebsd" + ))] + libc::MADV_FREE => Ok(advice), #[cfg(target_os = "linux")] - libc::MADV_HWPOISON => Advice::HwPoison, - _ => return Err(vm.new_value_error("Not a valid Advice value")), - }) + libc::MADV_DONTFORK + | libc::MADV_DOFORK + | libc::MADV_MERGEABLE + | libc::MADV_UNMERGEABLE + | libc::MADV_HUGEPAGE + | libc::MADV_NOHUGEPAGE + | libc::MADV_REMOVE + | libc::MADV_DONTDUMP + | libc::MADV_DODUMP + | libc::MADV_HWPOISON => Ok(advice), + #[cfg(target_os = "freebsd")] + libc::MADV_NOSYNC + | libc::MADV_AUTOSYNC + | libc::MADV_NOCORE + | libc::MADV_CORE + | libc::MADV_PROTECT => Ok(advice), + _ => Err(vm.new_value_error("Not a valid Advice value")), + } } #[repr(C)] @@ -86,10 +103,11 @@ mod mmap { } } + #[cfg(unix)] #[pyattr] use libc::{ MADV_DONTNEED, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED, MAP_ANON, - MAP_ANONYMOUS, MAP_PRIVATE, MAP_SHARED, PROT_READ, PROT_WRITE, + MAP_ANONYMOUS, MAP_PRIVATE, MAP_SHARED, PROT_EXEC, PROT_READ, PROT_WRITE, }; #[cfg(target_os = "macos")] @@ -139,6 +157,16 @@ mod mmap { #[pyattr] use libc::{MAP_DENYWRITE, MAP_EXECUTABLE, MAP_POPULATE}; + // MAP_STACK is available on Linux, OpenBSD, and NetBSD + #[cfg(any(target_os = "linux", target_os = "openbsd", target_os = "netbsd"))] + #[pyattr] + use libc::MAP_STACK; + + // FreeBSD-specific MADV constants + #[cfg(target_os = "freebsd")] + #[pyattr] + use libc::{MADV_AUTOSYNC, MADV_CORE, MADV_NOCORE, MADV_NOSYNC, MADV_PROTECT}; + #[pyattr] const ACCESS_DEFAULT: u32 = AccessMode::Default as u32; #[pyattr] @@ -148,13 +176,13 @@ mod mmap { #[pyattr] const ACCESS_COPY: u32 = AccessMode::Copy as u32; - #[cfg(all(unix, not(target_arch = "wasm32")))] + #[cfg(not(target_arch = "wasm32"))] #[pyattr(name = "PAGESIZE", once)] fn page_size(_vm: &VirtualMachine) -> usize { page_size::get() } - #[cfg(all(unix, not(target_arch = "wasm32")))] + #[cfg(not(target_arch = "wasm32"))] #[pyattr(name = "ALLOCATIONGRANULARITY", once)] fn granularity(_vm: &VirtualMachine) -> usize { page_size::get_granularity() @@ -171,34 +199,102 @@ mod mmap { Read(Mmap), } + impl MmapObj { + fn as_slice(&self) -> &[u8] { + match self { + MmapObj::Read(mmap) => &mmap[..], + MmapObj::Write(mmap) => &mmap[..], + } + } + } + #[pyattr] #[pyclass(name = "mmap")] #[derive(Debug, PyPayload)] struct PyMmap { closed: AtomicCell<bool>, mmap: PyMutex<Option<MmapObj>>, + #[cfg(unix)] fd: AtomicCell<i32>, - offset: libc::off_t, + #[cfg(windows)] + handle: AtomicCell<isize>, // HANDLE is isize on Windows + offset: i64, size: AtomicCell<usize>, pos: AtomicCell<usize>, // relative to offset exports: AtomicCell<usize>, access: AccessMode, } + impl PyMmap { + /// Close the underlying file handle/descriptor if open + fn close_handle(&self) { + #[cfg(unix)] + { + let fd = self.fd.swap(-1); + if fd >= 0 { + unsafe { libc::close(fd) }; + } + } + #[cfg(windows)] + { + let handle = self.handle.swap(INVALID_HANDLE_VALUE as isize); + if handle != INVALID_HANDLE_VALUE as isize { + unsafe { CloseHandle(handle as HANDLE) }; + } + } + } + } + + impl Drop for PyMmap { + fn drop(&mut self) { + self.close_handle(); + } + } + + #[cfg(unix)] #[derive(FromArgs)] struct MmapNewArgs { #[pyarg(any)] fileno: i32, #[pyarg(any)] length: isize, - #[pyarg(any, default = MAP_SHARED)] + #[pyarg(any, default = libc::MAP_SHARED)] flags: libc::c_int, - #[pyarg(any, default = PROT_WRITE|PROT_READ)] + #[pyarg(any, default = libc::PROT_WRITE | libc::PROT_READ)] prot: libc::c_int, #[pyarg(any, default = AccessMode::Default)] access: AccessMode, #[pyarg(any, default = 0)] - offset: libc::off_t, + offset: i64, + } + + #[cfg(windows)] + #[derive(FromArgs)] + struct MmapNewArgs { + #[pyarg(any)] + fileno: i32, + #[pyarg(any)] + length: isize, + #[pyarg(any, default)] + #[allow(dead_code)] + tagname: Option<PyObjectRef>, + #[pyarg(any, default = AccessMode::Default)] + access: AccessMode, + #[pyarg(any, default = 0)] + offset: i64, + } + + impl MmapNewArgs { + /// Validate mmap constructor arguments + fn validate_new_args(&self, vm: &VirtualMachine) -> PyResult<usize> { + if self.length < 0 { + return Err(vm.new_overflow_error("memory mapped length must be positive")); + } + if self.offset < 0 { + return Err(vm.new_overflow_error("memory mapped offset must be positive")); + } + Ok(self.length as usize) + } } #[derive(FromArgs)] @@ -241,7 +337,7 @@ mod mmap { end: Option<isize>, } - #[cfg(not(target_os = "redox"))] + #[cfg(all(unix, not(target_os = "redox")))] #[derive(FromArgs)] pub struct AdviseOptions { #[pyarg(positional)] @@ -252,7 +348,7 @@ mod mmap { length: Option<PyIntRef>, } - #[cfg(not(target_os = "redox"))] + #[cfg(all(unix, not(target_os = "redox")))] impl AdviseOptions { fn values(self, len: usize, vm: &VirtualMachine) -> PyResult<(libc::c_int, usize, usize)> { let start = self @@ -291,29 +387,19 @@ mod mmap { impl Constructor for PyMmap { type Args = MmapNewArgs; - // TODO: Windows is not supported right now. #[cfg(unix)] - fn py_new( - cls: PyTypeRef, - MmapNewArgs { + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + use libc::{MAP_PRIVATE, MAP_SHARED, PROT_READ, PROT_WRITE}; + + let mut map_size = args.validate_new_args(vm)?; + let MmapNewArgs { fileno: fd, - length, flags, prot, access, offset, - }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { - let map_size = length; - if map_size < 0 { - return Err(vm.new_overflow_error("memory mapped length must be positive")); - } - let mut map_size = map_size as usize; - - if offset < 0 { - return Err(vm.new_overflow_error("memory mapped offset must be positive")); - } + .. + } = args; if (access != AccessMode::Default) && ((flags != MAP_SHARED) || (prot != (PROT_WRITE | PROT_READ))) @@ -321,7 +407,7 @@ mod mmap { return Err(vm.new_value_error("mmap can't specify both access and flags, prot.")); } - // TODO: memmap2 doesn't support mapping with pro and flags right now + // TODO: memmap2 doesn't support mapping with prot and flags right now let (_flags, _prot, access) = match access { AccessMode::Read => (MAP_SHARED, PROT_READ, access), AccessMode::Write => (MAP_SHARED, PROT_READ | PROT_WRITE, access), @@ -339,10 +425,19 @@ mod mmap { }; let fd = unsafe { crt_fd::Borrowed::try_borrow_raw(fd) }; + + // macOS: Issue #11277: fsync(2) is not enough on OS X - a special, OS X specific + // fcntl(2) is necessary to force DISKSYNC and get around mmap(2) bug + #[cfg(target_os = "macos")] + if let Ok(fd) = fd { + use std::os::fd::AsRawFd; + unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_FULLFSYNC) }; + } + if let Ok(fd) = fd { let metadata = fstat(fd) .map_err(|err| io::Error::from_raw_os_error(err as i32).to_pyexception(vm))?; - let file_len = metadata.st_size; + let file_len = metadata.st_size as i64; if map_size == 0 { if file_len == 0 { @@ -356,13 +451,13 @@ mod mmap { map_size = (file_len - offset) .try_into() .map_err(|_| vm.new_value_error("mmap length is too large"))?; - } else if offset > file_len || file_len - offset < map_size as libc::off_t { + } else if offset > file_len || file_len - offset < map_size as i64 { return Err(vm.new_value_error("mmap length is greater than file size")); } } let mut mmap_opt = MmapOptions::new(); - let mmap_opt = mmap_opt.offset(offset.try_into().unwrap()).len(map_size); + let mmap_opt = mmap_opt.offset(offset as u64).len(map_size); let (fd, mmap) = || -> std::io::Result<_> { if let Ok(fd) = fd { @@ -395,6 +490,156 @@ mod mmap { m_obj.into_ref_with_type(vm, cls).map(Into::into) } + + #[cfg(windows)] + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + let mut map_size = args.validate_new_args(vm)?; + let MmapNewArgs { + fileno, + access, + offset, + .. + } = args; + + // Get file handle from fileno + // fileno -1 or 0 means anonymous mapping + let fh: Option<HANDLE> = if fileno != -1 && fileno != 0 { + // Convert CRT file descriptor to Windows HANDLE + // Use suppress_iph! to avoid crashes when the fd is invalid. + // This is critical because socket fds wrapped via _open_osfhandle + // may cause crashes in _get_osfhandle on Windows. + // See Python bug https://bugs.python.org/issue30114 + let handle = unsafe { suppress_iph!(libc::get_osfhandle(fileno)) }; + // Check for invalid handle value (-1 on Windows) + if handle == -1 || handle == INVALID_HANDLE_VALUE as isize { + return Err(vm.new_os_error(format!("Invalid file descriptor: {}", fileno))); + } + Some(handle as HANDLE) + } else { + None + }; + + // Get file size if we have a file handle and map_size is 0 + let mut duplicated_handle: HANDLE = INVALID_HANDLE_VALUE; + if let Some(fh) = fh { + // Duplicate handle so Python code can close the original + let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; + let result = unsafe { + DuplicateHandle( + GetCurrentProcess(), + fh, + GetCurrentProcess(), + &mut new_handle, + 0, + 0, // not inheritable + DUPLICATE_SAME_ACCESS, + ) + }; + if result == 0 { + return Err(io::Error::last_os_error().to_pyexception(vm)); + } + duplicated_handle = new_handle; + + // Get file size + let mut high: u32 = 0; + let low = unsafe { GetFileSize(fh, &mut high) }; + if low == u32::MAX { + let err = io::Error::last_os_error(); + if err.raw_os_error() != Some(0) { + unsafe { CloseHandle(duplicated_handle) }; + return Err(err.to_pyexception(vm)); + } + } + let file_len = ((high as i64) << 32) | (low as i64); + + if map_size == 0 { + if file_len == 0 { + unsafe { CloseHandle(duplicated_handle) }; + return Err(vm.new_value_error("cannot mmap an empty file")); + } + if offset >= file_len { + unsafe { CloseHandle(duplicated_handle) }; + return Err(vm.new_value_error("mmap offset is greater than file size")); + } + if file_len - offset > isize::MAX as i64 { + unsafe { CloseHandle(duplicated_handle) }; + return Err(vm.new_value_error("mmap length is too large")); + } + map_size = (file_len - offset) as usize; + } else { + // If map_size > file_len, extend the file (Windows behavior) + let required_size = offset.checked_add(map_size as i64).ok_or_else(|| { + unsafe { CloseHandle(duplicated_handle) }; + vm.new_overflow_error("mmap size would cause file size overflow") + })?; + if required_size > file_len { + // Extend file using SetFilePointerEx + SetEndOfFile + let result = unsafe { + SetFilePointerEx( + duplicated_handle, + required_size, + std::ptr::null_mut(), + FILE_BEGIN, + ) + }; + if result == 0 { + let err = io::Error::last_os_error(); + unsafe { CloseHandle(duplicated_handle) }; + return Err(err.to_pyexception(vm)); + } + let result = unsafe { SetEndOfFile(duplicated_handle) }; + if result == 0 { + let err = io::Error::last_os_error(); + unsafe { CloseHandle(duplicated_handle) }; + return Err(err.to_pyexception(vm)); + } + } + } + } + + let mut mmap_opt = MmapOptions::new(); + let mmap_opt = mmap_opt.offset(offset as u64).len(map_size); + + let (handle, mmap) = if duplicated_handle != INVALID_HANDLE_VALUE { + // Safety: We just duplicated this handle and it's valid + let owned_handle = + unsafe { OwnedHandle::from_raw_handle(duplicated_handle as RawHandle) }; + + let mmap_result = match access { + AccessMode::Default | AccessMode::Write => { + unsafe { mmap_opt.map_mut(&owned_handle) }.map(MmapObj::Write) + } + AccessMode::Read => unsafe { mmap_opt.map(&owned_handle) }.map(MmapObj::Read), + AccessMode::Copy => { + unsafe { mmap_opt.map_copy(&owned_handle) }.map(MmapObj::Write) + } + }; + + let mmap = mmap_result.map_err(|e| e.to_pyexception(vm))?; + + // Keep the handle alive + let raw = owned_handle.as_raw_handle() as isize; + std::mem::forget(owned_handle); + (raw, mmap) + } else { + // Anonymous mapping + let mmap = mmap_opt.map_anon().map_err(|e| e.to_pyexception(vm))?; + (INVALID_HANDLE_VALUE as isize, MmapObj::Write(mmap)) + }; + + let m_obj = Self { + closed: AtomicCell::new(false), + mmap: PyMutex::new(Some(mmap)), + handle: AtomicCell::new(handle), + offset, + size: AtomicCell::new(map_size), + pos: AtomicCell::new(0), + exports: AtomicCell::new(0), + access, + }; + + m_obj.into_ref_with_type(vm, cls).map(Into::into) + } } static BUFFER_METHODS: BufferMethods = BufferMethods { @@ -484,10 +729,7 @@ mod mmap { fn as_bytes(&self) -> BorrowedValue<'_, [u8]> { PyMutexGuard::map_immutable(self.mmap.lock(), |m| { - match m.as_ref().expect("mmap closed or invalid") { - MmapObj::Read(mmap) => &mmap[..], - MmapObj::Write(mmap) => &mmap[..], - } + m.as_ref().expect("mmap closed or invalid").as_slice() }) .into() } @@ -566,6 +808,8 @@ mod mmap { self.closed.store(true); *mmap = None; + self.close_handle(); + Ok(()) } @@ -588,15 +832,13 @@ mod mmap { let sub = &options.sub; + // returns start position for empty string if sub.is_empty() { - return Ok(PyInt::from(0isize)); + return Ok(PyInt::from(start as isize)); } let mmap = self.check_valid(vm)?; - let buf = match mmap.as_ref().unwrap() { - MmapObj::Read(mmap) => &mmap[start..end], - MmapObj::Write(mmap) => &mmap[start..end], - }; + let buf = &mmap.as_ref().unwrap().as_slice()[start..end]; let pos = buf.windows(sub.len()).position(|window| window == sub); Ok(pos.map_or(PyInt::from(-1isize), |i| PyInt::from(start + i))) @@ -607,15 +849,13 @@ mod mmap { let (start, end) = self.get_find_range(options.clone()); let sub = &options.sub; + // returns start position for empty string if sub.is_empty() { - return Ok(PyInt::from(0isize)); + return Ok(PyInt::from(start as isize)); } let mmap = self.check_valid(vm)?; - let buf = match mmap.as_ref().unwrap() { - MmapObj::Read(mmap) => &mmap[start..end], - MmapObj::Write(mmap) => &mmap[start..end], - }; + let buf = &mmap.as_ref().unwrap().as_slice()[start..end]; let pos = buf.windows(sub.len()).rposition(|window| window == sub); Ok(pos.map_or(PyInt::from(-1isize), |i| PyInt::from(start + i))) @@ -642,19 +882,26 @@ mod mmap { Ok(()) } - #[cfg(not(target_os = "redox"))] - #[allow(unused_assignments)] + #[cfg(all(unix, not(target_os = "redox")))] #[pymethod] fn madvise(&self, options: AdviseOptions, vm: &VirtualMachine) -> PyResult<()> { - let (option, _start, _length) = options.values(self.__len__(), vm)?; - let advice = advice_try_from_i32(vm, option)?; + let (option, start, length) = options.values(self.__len__(), vm)?; + let advice = validate_advice(vm, option)?; + + let guard = self.check_valid(vm)?; + let mmap = guard.deref().as_ref().unwrap(); + let ptr = match mmap { + MmapObj::Read(m) => m.as_ptr(), + MmapObj::Write(m) => m.as_ptr(), + }; - //TODO: memmap2 doesn't support madvise range right now. - match self.check_valid(vm)?.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap.advise(advice), - MmapObj::Write(mmap) => mmap.advise(advice), + // Apply madvise to the specified range (start, length) + let ptr_with_offset = unsafe { ptr.add(start) }; + let result = + unsafe { libc::madvise(ptr_with_offset as *mut libc::c_void, length, advice) }; + if result != 0 { + return Err(io::Error::last_os_error().to_pyexception(vm)); } - .map_err(|e| e.to_pyexception(vm))?; Ok(()) } @@ -728,10 +975,7 @@ mod mmap { .unwrap_or(remaining); let end_pos = pos + num_bytes; - let bytes = match mmap.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[pos..end_pos].to_vec(), - MmapObj::Write(mmap) => mmap[pos..end_pos].to_vec(), - }; + let bytes = mmap.deref().as_ref().unwrap().as_slice()[pos..end_pos].to_vec(); let result = PyBytes::from(bytes).into_ref(&vm.ctx); @@ -747,10 +991,7 @@ mod mmap { return Err(vm.new_value_error("read byte out of range")); } - let b = match self.check_valid(vm)?.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[pos], - MmapObj::Write(mmap) => mmap[pos], - }; + let b = self.check_valid(vm)?.deref().as_ref().unwrap().as_slice()[pos]; self.advance_pos(1); @@ -767,12 +1008,8 @@ mod mmap { return Ok(PyBytes::from(vec![]).into_ref(&vm.ctx)); } - let eof = match mmap.as_ref().unwrap() { - MmapObj::Read(mmap) => &mmap[pos..], - MmapObj::Write(mmap) => &mmap[pos..], - } - .iter() - .position(|&x| x == b'\n'); + let slice = mmap.as_ref().unwrap().as_slice(); + let eof = slice[pos..].iter().position(|&x| x == b'\n'); let end_pos = if let Some(i) = eof { pos + i + 1 @@ -780,10 +1017,7 @@ mod mmap { self.__len__() }; - let bytes = match mmap.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[pos..end_pos].to_vec(), - MmapObj::Write(mmap) => mmap[pos..end_pos].to_vec(), - }; + let bytes = slice[pos..end_pos].to_vec(); let result = PyBytes::from(bytes).into_ref(&vm.ctx); @@ -792,13 +1026,108 @@ mod mmap { Ok(result) } - // TODO: supports resize + #[cfg(unix)] #[pymethod] fn resize(&self, _newsize: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { self.check_resizeable(vm)?; + // TODO: implement using mremap on Linux Err(vm.new_system_error("mmap: resizing not available--no mremap()")) } + #[cfg(windows)] + #[pymethod] + fn resize(&self, newsize: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + self.check_resizeable(vm)?; + + let newsize: usize = newsize + .try_to_primitive(vm) + .map_err(|_| vm.new_value_error("new size out of range"))?; + + if newsize == 0 { + return Err(vm.new_value_error("new size must be positive")); + } + + let handle = self.handle.load(); + let is_anonymous = handle == INVALID_HANDLE_VALUE as isize; + + // Get the lock on mmap + let mut mmap_guard = self.mmap.lock(); + + if is_anonymous { + // For anonymous mmap, we need to: + // 1. Create a new anonymous mmap with the new size + // 2. Copy data from old mmap to new mmap + // 3. Replace the old mmap + + let old_size = self.size.load(); + let copy_size = std::cmp::min(old_size, newsize); + + // Create new anonymous mmap + let mut new_mmap_opts = MmapOptions::new(); + let mut new_mmap = new_mmap_opts + .len(newsize) + .map_anon() + .map_err(|e| e.to_pyexception(vm))?; + + // Copy data from old mmap to new mmap + if let Some(old_mmap) = mmap_guard.as_ref() { + let src = match old_mmap { + MmapObj::Write(m) => &m[..copy_size], + MmapObj::Read(m) => &m[..copy_size], + }; + new_mmap[..copy_size].copy_from_slice(src); + } + + *mmap_guard = Some(MmapObj::Write(new_mmap)); + self.size.store(newsize); + } else { + // File-backed mmap resize + + // Drop the current mmap to release the file mapping + *mmap_guard = None; + + // Resize the file + let required_size = self.offset + newsize as i64; + let result = unsafe { + SetFilePointerEx( + handle as HANDLE, + required_size, + std::ptr::null_mut(), + FILE_BEGIN, + ) + }; + if result == 0 { + // Restore original mmap on error + let err = io::Error::last_os_error(); + self.try_restore_mmap(&mut mmap_guard, handle as HANDLE, self.size.load()); + return Err(err.to_pyexception(vm)); + } + + let result = unsafe { SetEndOfFile(handle as HANDLE) }; + if result == 0 { + let err = io::Error::last_os_error(); + self.try_restore_mmap(&mut mmap_guard, handle as HANDLE, self.size.load()); + return Err(err.to_pyexception(vm)); + } + + // Create new mmap with the new size + let new_mmap = + Self::create_mmap_windows(handle as HANDLE, self.offset, newsize, &self.access) + .map_err(|e| e.to_pyexception(vm))?; + + *mmap_guard = Some(new_mmap); + self.size.store(newsize); + } + + // Adjust position if it's beyond the new size + let pos = self.pos.load(); + if pos > newsize { + self.pos.store(newsize); + } + + Ok(()) + } + #[pymethod] fn seek( &self, @@ -838,6 +1167,7 @@ mod mmap { Ok(()) } + #[cfg(unix)] #[pymethod] fn size(&self, vm: &VirtualMachine) -> std::io::Result<PyIntRef> { let fd = unsafe { crt_fd::Borrowed::try_borrow_raw(self.fd.load())? }; @@ -845,6 +1175,27 @@ mod mmap { Ok(PyInt::from(file_len).into_ref(&vm.ctx)) } + #[cfg(windows)] + #[pymethod] + fn size(&self, vm: &VirtualMachine) -> PyResult<PyIntRef> { + let handle = self.handle.load(); + if handle == INVALID_HANDLE_VALUE as isize { + // Anonymous mapping, return the mmap size + return Ok(PyInt::from(self.__len__()).into_ref(&vm.ctx)); + } + + let mut high: u32 = 0; + let low = unsafe { GetFileSize(handle as HANDLE, &mut high) }; + if low == u32::MAX { + let err = io::Error::last_os_error(); + if err.raw_os_error() != Some(0) { + return Err(err.to_pyexception(vm)); + } + } + let file_len = ((high as i64) << 32) | (low as i64); + Ok(PyInt::from(file_len).into_ref(&vm.ctx)) + } + #[pymethod] fn tell(&self) -> PyResult<usize> { Ok(self.pos()) @@ -918,18 +1269,62 @@ mod mmap { fn __exit__(zelf: &Py<Self>, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { zelf.close(vm) } + + #[cfg(windows)] + #[pymethod] + fn __sizeof__(&self) -> usize { + std::mem::size_of::<Self>() + } } impl PyMmap { + #[cfg(windows)] + fn create_mmap_windows( + handle: HANDLE, + offset: i64, + size: usize, + access: &AccessMode, + ) -> io::Result<MmapObj> { + use std::fs::File; + + // Create an owned handle wrapper for memmap2 + // We need to create a File from the handle + let file = unsafe { File::from_raw_handle(handle as RawHandle) }; + + let mut mmap_opt = MmapOptions::new(); + let mmap_opt = mmap_opt.offset(offset as u64).len(size); + + let result = match access { + AccessMode::Default | AccessMode::Write => { + unsafe { mmap_opt.map_mut(&file) }.map(MmapObj::Write) + } + AccessMode::Read => unsafe { mmap_opt.map(&file) }.map(MmapObj::Read), + AccessMode::Copy => unsafe { mmap_opt.map_copy(&file) }.map(MmapObj::Write), + }; + + // Don't close the file handle - we're borrowing it + std::mem::forget(file); + + result + } + + /// Try to restore mmap after a failed resize operation. + /// Returns true if restoration succeeded, false otherwise. + /// If restoration fails, marks the mmap as closed. + #[cfg(windows)] + fn try_restore_mmap(&self, mmap_guard: &mut Option<MmapObj>, handle: HANDLE, size: usize) { + match Self::create_mmap_windows(handle, self.offset, size, &self.access) { + Ok(mmap) => *mmap_guard = Some(mmap), + Err(_) => self.closed.store(true), + } + } + fn getitem_by_index(&self, i: isize, vm: &VirtualMachine) -> PyResult<PyObjectRef> { let i = i .wrapped_at(self.__len__()) .ok_or_else(|| vm.new_index_error("mmap index out of range"))?; - let b = match self.check_valid(vm)?.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[i], - MmapObj::Write(mmap) => mmap[i], - }; + let b = self.check_valid(vm)?.deref().as_ref().unwrap().as_slice()[i]; Ok(PyInt::from(b).into_ref(&vm.ctx).into()) } @@ -942,33 +1337,24 @@ mod mmap { let (range, step, slice_len) = slice.adjust_indices(self.__len__()); let mmap = self.check_valid(vm)?; + let slice_data = mmap.deref().as_ref().unwrap().as_slice(); if slice_len == 0 { return Ok(PyBytes::from(vec![]).into_ref(&vm.ctx).into()); } else if step == 1 { - let bytes = match mmap.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => &mmap[range], - MmapObj::Write(mmap) => &mmap[range], - }; - return Ok(PyBytes::from(bytes.to_vec()).into_ref(&vm.ctx).into()); + return Ok(PyBytes::from(slice_data[range].to_vec()) + .into_ref(&vm.ctx) + .into()); } let mut result_buf = Vec::with_capacity(slice_len); if step.is_negative() { for i in range.rev().step_by(step.unsigned_abs()) { - let b = match mmap.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[i], - MmapObj::Write(mmap) => mmap[i], - }; - result_buf.push(b); + result_buf.push(slice_data[i]); } } else { for i in range.step_by(step.unsigned_abs()) { - let b = match mmap.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[i], - MmapObj::Write(mmap) => mmap[i], - }; - result_buf.push(b); + result_buf.push(slice_data[i]); } } Ok(PyBytes::from(result_buf).into_ref(&vm.ctx).into()) From 56a7fb129af19648ec32be7457bcac612019c4d1 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 6 Dec 2025 10:01:37 +0900 Subject: [PATCH 406/819] upgrade cranelift (#6331) --- Cargo.lock | 95 +++++++++++++++++++--------------- crates/jit/Cargo.toml | 6 +-- crates/jit/src/instructions.rs | 79 ++++++++++++++++------------ 3 files changed, 101 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 569f52bc9bf..2e3b9add57a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -682,9 +682,9 @@ dependencies = [ [[package]] name = "cranelift" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf99ca3e855b6ca01ee5a334542704274d046deb25cf3013a74eda9e1f7ce0f" +checksum = "68971376deb1edf5e9c0ac77ef00479d740ce7a60e6181adb0648afe1dc7b8f4" dependencies = [ "cranelift-codegen", "cranelift-frontend", @@ -693,42 +693,42 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "359c047862387091eb0363ce8b5cabb4a8be1cc16a6fa151fe079c09796461f3" +checksum = "30054f4aef4d614d37f27d5b77e36e165f0b27a71563be348e7c9fcfac41eed8" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf62afda29fcde09d922f125a7d47880b540fd1de069558bfa637b4ce7aa1ca" +checksum = "0beab56413879d4f515e08bcf118b1cb85f294129bb117057f573d37bfbb925a" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3537273471ebdae55791869ee16f71a4a51e34ad47cdc64269a9c2255b5dce03" +checksum = "6d054747549a69b264d5299c8ca1b0dd45dc6bd0ee43f1edfcc42a8b12952c7a" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b872fde1717c508f842ad1ad8768fbe16caf7e8e049215b0e09429bbf00d3ce9" +checksum = "98b92d481b77a7dc9d07c96e24a16f29e0c9c27d042828fdf7e49e54ee9819bf" [[package]] name = "cranelift-codegen" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a74ef998eb9f985dc0d987d3aac0fe4bd1b59ec707461b2d6d20cda1b0a5e1" +checksum = "6eeccfc043d599b0ef1806942707fc51cdd1c3965c343956dc975a55d82a920f" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -747,48 +747,50 @@ dependencies = [ "serde", "smallvec", "target-lexicon", + "wasmtime-internal-math", ] [[package]] name = "cranelift-codegen-meta" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04a532b9a7b69c28e7e37d15bca7f7f5cc56399df890ec399333e2d548004a" +checksum = "1174cdb9d9d43b2bdaa612a07ed82af13db9b95526bc2c286c2aec4689bcc038" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", "cranelift-srcgen", + "heck", ] [[package]] name = "cranelift-codegen-shared" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c4556174c6eb7d586bd1715b7f9c3a43a0835d6a95715893718b2f263af895" +checksum = "7d572be73fae802eb115f45e7e67a9ed16acb4ee683b67c4086768786545419a" [[package]] name = "cranelift-control" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d8e9ae221e352dbea7f6f389705365f8128e7e0a7de5cf787ab7b2ccd1c522" +checksum = "e1587465cc84c5cc793b44add928771945f3132bbf6b3621ee9473c631a87156" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40d10b531267cc86ba4fbb7b718b646df503713828b37841a867f332954b24ad" +checksum = "063b83448b1343e79282c3c7cbda7ed5f0816f0b763a4c15f7cecb0a17d87ea6" dependencies = [ "cranelift-bitset", ] [[package]] name = "cranelift-frontend" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07540e6f75357d655743008965018fe243434ec6755078794616fde31f783a03" +checksum = "aa4461c2d2ca48bc72883f5f5c3129d9aefac832df1db824af9db8db3efee109" dependencies = [ "cranelift-codegen", "log", @@ -798,15 +800,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0909e87af454a7ff542ece2d66f901f2cc9483ab36572a924eb5e58ce51fc0" +checksum = "acd811b25e18f14810d09c504e06098acc1d9dbfa24879bf0d6b6fb44415fc66" [[package]] name = "cranelift-jit" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e353bd2b08aed8e0a0da4838fcf1a5b6004464675e5651f050bdcd952f12f479" +checksum = "01527663ba63c10509d7c87fd1f8495d21170ba35bf714f57271495689d8fde5" dependencies = [ "anyhow", "cranelift-codegen", @@ -818,15 +820,15 @@ dependencies = [ "log", "region", "target-lexicon", - "wasmtime-jit-icache-coherence", - "windows-sys 0.59.0", + "wasmtime-internal-jit-icache-coherence", + "windows-sys 0.60.2", ] [[package]] name = "cranelift-module" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fa60ec6f91746d560064c8900d9566a239cb6ae788a62cd5b3908589ca749" +checksum = "72328edb49aeafb1655818c91c476623970cb7b8a89ffbdadd82ce7d13dedc1d" dependencies = [ "anyhow", "cranelift-codegen", @@ -835,9 +837,9 @@ dependencies = [ [[package]] name = "cranelift-native" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2d3963401ea1f8f84bdb0b654f1ca186be97e6ca94ccd2a8037b9edee47e17" +checksum = "2417046989d8d6367a55bbab2e406a9195d176f4779be4aa484d645887217d37" dependencies = [ "cranelift-codegen", "libc", @@ -846,9 +848,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.119.1" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823558b0a406b7f7d5dad0c925b29e8192792476faaa71615d40cb5a842a9040" +checksum = "8d039de901c8d928222b8128e1b9a9ab27b82a7445cb749a871c75d9cb25c57d" [[package]] name = "crc32fast" @@ -1336,9 +1338,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" dependencies = [ "fallible-iterator", "indexmap", @@ -2661,9 +2663,9 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.11.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc06e6b318142614e4a48bc725abbf08ff166694835c43c9dae5a9009704639a" +checksum = "4e249c660440317032a71ddac302f25f1d5dff387667bcc3978d1f77aa31ac34" dependencies = [ "allocator-api2", "bumpalo", @@ -4252,15 +4254,24 @@ dependencies = [ ] [[package]] -name = "wasmtime-jit-icache-coherence" -version = "32.0.1" +name = "wasmtime-internal-jit-icache-coherence" +version = "39.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ccb3dd740a0601addd260f4a6d91470cd3f7a2058efe46662054ca6b6da592" +checksum = "b97ccd36e25390258ce6720add639ffe5a7d81a5c904350aa08f5bbc60433d22" dependencies = [ "anyhow", "cfg-if", "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", +] + +[[package]] +name = "wasmtime-internal-math" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1b856e1bbf0230ab560ba4204e944b141971adc4e6cdf3feb6979c1a7b7953" +dependencies = [ + "libm", ] [[package]] diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index 2ef8c344a9d..a79e48bac2d 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -17,9 +17,9 @@ num-traits = { workspace = true } thiserror = { workspace = true } libffi = { workspace = true } -cranelift = "0.119" -cranelift-jit = "0.119" -cranelift-module = "0.119" +cranelift = "0.126" +cranelift-jit = "0.126" +cranelift-module = "0.126" [dev-dependencies] rustpython-derive = { workspace = true } diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index 9ada6c32781..8a70b9a73cb 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -111,13 +111,11 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { let builder = &mut self.builder; let ty = val.to_jit_type().ok_or(JitCompileError::NotSupported)?; let local = self.variables[idx as usize].get_or_insert_with(|| { - let var = Variable::new(idx as usize); - let local = Local { + let var = builder.declare_var(ty.to_cranelift()); + Local { var, ty: ty.clone(), - }; - builder.declare_var(var, ty.to_cranelift()); - local + } }); if ty != local.ty { Err(JitCompileError::NotSupported) @@ -1021,7 +1019,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { .ins() .brif(cmp_b_zero, b_zero_block, &[], continue_block, &[]); self.builder.switch_to_block(b_zero_block); - self.builder.ins().jump(merge_block, &[one_f]); + self.builder.ins().jump(merge_block, &[one_f.into()]); self.builder.switch_to_block(continue_block); // --- Edge Case 2: b is NaN → return NaN @@ -1032,7 +1030,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { .ins() .brif(cmp_b_nan, b_nan_block, &[], continue_block2, &[]); self.builder.switch_to_block(b_nan_block); - self.builder.ins().jump(merge_block, &[nan_f]); + self.builder.ins().jump(merge_block, &[nan_f.into()]); self.builder.switch_to_block(continue_block2); // --- Edge Case 3: a == 0.0 → return 0.0 @@ -1043,7 +1041,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { .ins() .brif(cmp_a_zero, a_zero_block, &[], continue_block3, &[]); self.builder.switch_to_block(a_zero_block); - self.builder.ins().jump(merge_block, &[zero_f]); + self.builder.ins().jump(merge_block, &[zero_f.into()]); self.builder.switch_to_block(continue_block3); // --- Edge Case 4: a is NaN → return NaN @@ -1054,7 +1052,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { .ins() .brif(cmp_a_nan, a_nan_block, &[], continue_block4, &[]); self.builder.switch_to_block(a_nan_block); - self.builder.ins().jump(merge_block, &[nan_f]); + self.builder.ins().jump(merge_block, &[nan_f.into()]); self.builder.switch_to_block(continue_block4); // --- Edge Case 5: b == +infinity → return +infinity @@ -1065,7 +1063,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { .ins() .brif(cmp_b_inf, b_inf_block, &[], continue_block5, &[]); self.builder.switch_to_block(b_inf_block); - self.builder.ins().jump(merge_block, &[inf_f]); + self.builder.ins().jump(merge_block, &[inf_f.into()]); self.builder.switch_to_block(continue_block5); // --- Edge Case 6: b == -infinity → return 0.0 @@ -1076,7 +1074,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { .ins() .brif(cmp_b_neg_inf, b_neg_inf_block, &[], continue_block6, &[]); self.builder.switch_to_block(b_neg_inf_block); - self.builder.ins().jump(merge_block, &[zero_f]); + self.builder.ins().jump(merge_block, &[zero_f.into()]); self.builder.switch_to_block(continue_block6); // --- Edge Case 7: a == +infinity → return +infinity @@ -1087,7 +1085,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { .ins() .brif(cmp_a_inf, a_inf_block, &[], continue_block7, &[]); self.builder.switch_to_block(a_inf_block); - self.builder.ins().jump(merge_block, &[inf_f]); + self.builder.ins().jump(merge_block, &[inf_f.into()]); self.builder.switch_to_block(continue_block7); // --- Edge Case 8: a == -infinity → check exponent parity @@ -1109,7 +1107,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { .brif(cmp_int, continue_neg_inf, &[], domain_error_blk, &[]); self.builder.switch_to_block(domain_error_blk); - self.builder.ins().jump(merge_block, &[nan_f]); + self.builder.ins().jump(merge_block, &[nan_f.into()]); self.builder.switch_to_block(continue_neg_inf); // b is an integer here; convert b_floor to an i64. @@ -1124,17 +1122,21 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { let even_block = self.builder.create_block(); self.builder.append_block_param(odd_block, f64_ty); self.builder.append_block_param(even_block, f64_ty); - self.builder - .ins() - .brif(is_odd, odd_block, &[neg_inf_f], even_block, &[inf_f]); + self.builder.ins().brif( + is_odd, + odd_block, + &[neg_inf_f.into()], + even_block, + &[inf_f.into()], + ); self.builder.switch_to_block(odd_block); let phi_neg_inf = self.builder.block_params(odd_block)[0]; - self.builder.ins().jump(merge_block, &[phi_neg_inf]); + self.builder.ins().jump(merge_block, &[phi_neg_inf.into()]); self.builder.switch_to_block(even_block); let phi_inf = self.builder.block_params(even_block)[0]; - self.builder.ins().jump(merge_block, &[phi_inf]); + self.builder.ins().jump(merge_block, &[phi_inf.into()]); self.builder.switch_to_block(continue_block8); @@ -1154,7 +1156,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { let product_dd = self.dd_mul(ln_a_dd, b_dd); let exp_dd = self.dd_exp(product_dd); let pos_res = self.dd_to_f64(exp_dd); - self.builder.ins().jump(merge_block, &[pos_res]); + self.builder.ins().jump(merge_block, &[pos_res.into()]); // ----- Case: a < 0: Only allow an integral exponent. self.builder.switch_to_block(a_neg_block); @@ -1168,7 +1170,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { // Domain error: non-integer exponent for negative base self.builder.switch_to_block(domain_error_blk); - self.builder.ins().jump(merge_block, &[nan_f]); + self.builder.ins().jump(merge_block, &[nan_f.into()]); // For negative base with an integer exponent: self.builder.switch_to_block(neg_int_block); @@ -1191,18 +1193,24 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { self.builder.append_block_param(odd_block, f64_ty); self.builder.append_block_param(even_block, f64_ty); // Pass mag_val to both branches: - self.builder - .ins() - .brif(is_odd, odd_block, &[mag_val], even_block, &[mag_val]); + self.builder.ins().brif( + is_odd, + odd_block, + &[mag_val.into()], + even_block, + &[mag_val.into()], + ); self.builder.switch_to_block(odd_block); let phi_mag_val = self.builder.block_params(odd_block)[0]; let neg_val = self.builder.ins().fneg(phi_mag_val); - self.builder.ins().jump(merge_block, &[neg_val]); + self.builder.ins().jump(merge_block, &[neg_val.into()]); self.builder.switch_to_block(even_block); let phi_mag_val_even = self.builder.block_params(even_block)[0]; - self.builder.ins().jump(merge_block, &[phi_mag_val_even]); + self.builder + .ins() + .jump(merge_block, &[phi_mag_val_even.into()]); // ----- Merge: Return the final result. self.builder.switch_to_block(merge_block); @@ -1239,7 +1247,9 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { self.builder.append_block_param(continue_block, types::I64); // base // Initial jump to check if exponent is negative - self.builder.ins().jump(check_negative, &[b, a]); + self.builder + .ins() + .jump(check_negative, &[b.into(), a.into()]); // Check if exponent is negative self.builder.switch_to_block(check_negative); @@ -1254,14 +1264,14 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { self.builder.ins().brif( is_negative, handle_negative, - &[exp_check, base_check], + &[exp_check.into(), base_check.into()], loop_block, - &[exp_check, one_i64, base_check], + &[exp_check.into(), one_i64.into(), base_check.into()], ); // Handle negative exponent (return 0 for integer exponentiation) self.builder.switch_to_block(handle_negative); - self.builder.ins().jump(exit_block, &[zero]); // Return 0 for negative exponents + self.builder.ins().jump(exit_block, &[zero.into()]); // Return 0 for negative exponents // Loop block logic (square-and-multiply algorithm) self.builder.switch_to_block(loop_block); @@ -1275,9 +1285,9 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { self.builder.ins().brif( is_zero, exit_block, - &[result_phi], + &[result_phi.into()], continue_block, - &[exp_phi, result_phi, base_phi], + &[exp_phi.into(), result_phi.into(), base_phi.into()], ); // Continue block for non-zero case @@ -1296,9 +1306,10 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { // Square the base and divide exponent by 2 let squared_base = self.builder.ins().imul(base_phi, base_phi); let new_exp = self.builder.ins().sshr_imm(exp_phi, 1); - self.builder - .ins() - .jump(loop_block, &[new_exp, new_result, squared_base]); + self.builder.ins().jump( + loop_block, + &[new_exp.into(), new_result.into(), squared_base.into()], + ); // Exit block self.builder.switch_to_block(exit_block); From aea956d6011c22084c6794c7d25ea219a1ae7bd6 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 6 Dec 2025 17:21:52 +0900 Subject: [PATCH 407/819] WindowsError (#6333) --- Lib/test/test_exception_hierarchy.py | 2 -- crates/vm/src/exceptions.rs | 26 ++++++++++++++++++++++---- crates/vm/src/stdlib/builtins.rs | 6 ++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_exception_hierarchy.py b/Lib/test/test_exception_hierarchy.py index e2f2844512a..e8c1c7fd1e7 100644 --- a/Lib/test/test_exception_hierarchy.py +++ b/Lib/test/test_exception_hierarchy.py @@ -136,8 +136,6 @@ def test_posix_error(self): if os.name == "nt": self.assertEqual(e.winerror, None) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipUnless(os.name == "nt", "Windows-specific test") def test_errno_translation(self): # ERROR_ALREADY_EXISTS (183) -> EEXIST diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index a8b55ae8a4d..1916972a799 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1501,6 +1501,14 @@ pub(super) mod types { #[cfg(windows)] if let Some(winerror) = new_args.args.get(3) { zelf.set_attr("winerror", winerror.clone(), vm)?; + // Convert winerror to errno and replace args[0] (CPython behavior) + if let Some(winerror_int) = winerror + .downcast_ref::<crate::builtins::PyInt>() + .and_then(|w| w.try_to_primitive::<i32>(vm).ok()) + { + let errno = crate::common::os::winerror_to_errno(winerror_int); + new_args.args[0] = vm.new_pyobj(errno); + } } if let Some(filename2) = new_args.args.get(4) { zelf.set_attr("filename2", filename2.clone(), vm)?; @@ -1521,19 +1529,29 @@ pub(super) mod types { let errno = exc.get_arg(0).unwrap().str(vm)?; let msg = exc.get_arg(1).unwrap().str(vm)?; + // On Windows, use [WinError X] format when winerror is set + #[cfg(windows)] + let (label, code) = match obj.get_attr("winerror", vm) { + Ok(winerror) if !vm.is_none(&winerror) => ("WinError", winerror.str(vm)?), + _ => ("Errno", errno.clone()), + }; + #[cfg(not(windows))] + let (label, code) = ("Errno", errno.clone()); + let s = match obj.get_attr("filename", vm) { Ok(filename) if !vm.is_none(&filename) => match obj.get_attr("filename2", vm) { Ok(filename2) if !vm.is_none(&filename2) => format!( - "[Errno {}] {}: '{}' -> '{}'", - errno, + "[{} {}] {}: '{}' -> '{}'", + label, + code, msg, filename.str(vm)?, filename2.str(vm)? ), - _ => format!("[Errno {}] {}: '{}'", errno, msg, filename.str(vm)?), + _ => format!("[{} {}] {}: '{}'", label, code, msg, filename.str(vm)?), }, _ => { - format!("[Errno {errno}] {msg}") + format!("[{label} {code}] {msg}") } }; vm.ctx.new_str(s) diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index d115c027955..fb665dbdefb 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -1187,4 +1187,10 @@ pub fn init_module(vm: &VirtualMachine, module: &Py<PyModule>) { extend_module!(vm, module, { "JitError" => ctx.exceptions.jit_error.to_owned(), }); + + #[cfg(windows)] + extend_module!(vm, module, { + // OSError alias for Windows + "WindowsError" => ctx.exceptions.os_error.to_owned(), + }); } From 590da474997d001278c32a75aee4b01fb3cd607a Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 7 Dec 2025 12:10:09 +0900 Subject: [PATCH 408/819] pin flate2 under 1.1.5 (#6335) --- Cargo.lock | 4 ++-- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e3b9add57a..b770168af02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4794,6 +4794,6 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" +checksum = "36134c44663532e6519d7a6dfdbbe06f6f8192bde8ae9ed076e9b213f0e31df7" diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 785e280f6ab..2fe8e3ec85a 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -83,7 +83,7 @@ unicode-bidi-mirroring = { workspace = true } # compression adler32 = "1.2.0" crc32fast = "1.3.2" -flate2 = { version = "1.1", default-features = false, features = ["zlib-rs"] } +flate2 = { version = "<=1.1.5", default-features = false, features = ["zlib-rs"] } libz-sys = { package = "libz-rs-sys", version = "0.5" } bzip2 = "0.6" From cb7450df317eb903bd9a92b087803358ca263fca Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 8 Dec 2025 00:08:53 +0900 Subject: [PATCH 409/819] ssl module for windows (#6332) * SSL for windows * mark expected failure on test_ssl_in_multiple_threads --- Lib/test/test_ssl.py | 1 + crates/stdlib/src/ssl.rs | 312 ++++++++++++++++++++++++++++++--------- 2 files changed, 242 insertions(+), 71 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index f073def5bc1..e40628640a9 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -2891,6 +2891,7 @@ def test_echo(self): 'Cannot create a client socket with a PROTOCOL_TLS_SERVER context', str(e.exception)) + @unittest.skip("TODO: RUSTPYTHON; Flaky on windows") @unittest.skipUnless(support.Py_GIL_DISABLED, "test is only useful if the GIL is disabled") def test_ssl_in_multiple_threads(self): # See GH-124984: OpenSSL is not thread safe. diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index e0019ae4750..03909046c2a 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -46,7 +46,6 @@ mod _ssl { }; use std::{ collections::HashMap, - io::Write, sync::{ Arc, atomic::{AtomicUsize, Ordering}, @@ -479,9 +478,8 @@ mod _ssl { return Err(vm.new_value_error("server_hostname cannot start with a dot")); } - if hostname.parse::<std::net::IpAddr>().is_ok() { - return Err(vm.new_value_error("server_hostname cannot be an IP address")); - } + // IP addresses are allowed as server_hostname + // SNI will not be sent for IP addresses if hostname.contains('\0') { return Err(vm.new_type_error("embedded null character")); @@ -1452,35 +1450,74 @@ mod _ssl { /// This uses platform-specific methods: /// - Linux: openssl-probe to find certificate files /// - macOS: Keychain API - /// - Windows: System certificate store + /// - Windows: System certificate store (ROOT + CA stores) fn load_system_certificates( &self, store: &mut rustls::RootCertStore, vm: &VirtualMachine, ) -> PyResult<()> { - let result = rustls_native_certs::load_native_certs(); - - // Load successfully found certificates - for cert in result.certs { - let is_ca = cert::is_ca_certificate(cert.as_ref()); - if store.add(cert).is_ok() { - *self.x509_cert_count.write() += 1; - if is_ca { - *self.ca_cert_count.write() += 1; + #[cfg(windows)] + { + // Windows: Use schannel to load from both ROOT and CA stores + use schannel::cert_store::CertStore; + + let store_names = ["ROOT", "CA"]; + let open_fns = [CertStore::open_current_user, CertStore::open_local_machine]; + + for store_name in store_names { + for open_fn in &open_fns { + if let Ok(cert_store) = open_fn(store_name) { + for cert_ctx in cert_store.certs() { + let der_bytes = cert_ctx.to_der(); + let cert = + rustls::pki_types::CertificateDer::from(der_bytes.to_vec()); + let is_ca = cert::is_ca_certificate(cert.as_ref()); + if store.add(cert).is_ok() { + *self.x509_cert_count.write() += 1; + if is_ca { + *self.ca_cert_count.write() += 1; + } + } + } + } } } - } - // If there were errors but some certs loaded, just continue - // If NO certs loaded and there were errors, report the first error - if *self.x509_cert_count.read() == 0 && !result.errors.is_empty() { - return Err(vm.new_os_error(format!( - "Failed to load native certificates: {}", - result.errors[0] - ))); + if *self.x509_cert_count.read() == 0 { + return Err(vm.new_os_error( + "Failed to load certificates from Windows store".to_owned(), + )); + } + + Ok(()) } - Ok(()) + #[cfg(not(windows))] + { + let result = rustls_native_certs::load_native_certs(); + + // Load successfully found certificates + for cert in result.certs { + let is_ca = cert::is_ca_certificate(cert.as_ref()); + if store.add(cert).is_ok() { + *self.x509_cert_count.write() += 1; + if is_ca { + *self.ca_cert_count.write() += 1; + } + } + } + + // If there were errors but some certs loaded, just continue + // If NO certs loaded and there were errors, report the first error + if *self.x509_cert_count.read() == 0 && !result.errors.is_empty() { + return Err(vm.new_os_error(format!( + "Failed to load native certificates: {}", + result.errors[0] + ))); + } + + Ok(()) + } } #[pymethod] @@ -1491,17 +1528,28 @@ mod _ssl { ) -> PyResult<()> { let mut store = self.root_certs.write(); - // Create loader (without ca_certs_der - default certs don't go to get_ca_certs()) - let mut lazy_ca_certs = Vec::new(); - let mut loader = cert::CertLoader::new(&mut store, &mut lazy_ca_certs); + #[cfg(windows)] + { + // Windows: Load system certificates first, then additionally load from env + // see: test_load_default_certs_env_windows + let _ = self.load_system_certificates(&mut store, vm); - // Try Python os.environ first (allows runtime env changes) - // This checks SSL_CERT_FILE and SSL_CERT_DIR from Python's os.environ - let loaded = self.try_load_from_python_environ(&mut loader, vm)?; + let mut lazy_ca_certs = Vec::new(); + let mut loader = cert::CertLoader::new(&mut store, &mut lazy_ca_certs); + let _ = self.try_load_from_python_environ(&mut loader, vm)?; + } - // Fallback to system certificates if environment variables didn't provide any - if !loaded { - let _ = self.load_system_certificates(&mut store, vm); + #[cfg(not(windows))] + { + // Non-Windows: Try env vars first; only fallback to system certs if not set + // see: test_load_default_certs_env + let mut lazy_ca_certs = Vec::new(); + let mut loader = cert::CertLoader::new(&mut store, &mut lazy_ca_certs); + let loaded = self.try_load_from_python_environ(&mut loader, vm)?; + + if !loaded { + let _ = self.load_system_certificates(&mut store, vm); + } } // If no certificates were loaded from system, fallback to webpki-roots (Mozilla CA bundle) @@ -1892,10 +1940,8 @@ mod _ssl { return Err(vm.new_value_error("server_hostname cannot start with a dot")); } - // Check if it's a bare IP address (not allowed for SNI) - if hostname.parse::<std::net::IpAddr>().is_ok() { - return Err(vm.new_value_error("server_hostname cannot be an IP address")); - } + // IP addresses are allowed + // SNI will not be sent for IP addresses // Check for NULL bytes if hostname.contains('\0') { @@ -3393,44 +3439,56 @@ mod _ssl { .as_mut() .ok_or_else(|| vm.new_value_error("Connection not established"))?; - // Unified write logic - no need to match on Client/Server anymore - let mut writer = conn.writer(); - writer - .write_all(data_bytes.as_ref()) - .map_err(|e| vm.new_os_error(format!("Write failed: {e}")))?; + let is_bio = self.is_bio_mode(); + let data: &[u8] = data_bytes.as_ref(); - // Flush to get TLS-encrypted data (writer automatically flushed on drop) - // Send encrypted data to socket - if conn.wants_write() { - let is_bio = self.is_bio_mode(); + // Write data in chunks to avoid filling the internal TLS buffer + // rustls has a limited internal buffer, so we need to flush periodically + const CHUNK_SIZE: usize = 16384; // 16KB chunks (typical TLS record size) + let mut written = 0; - if is_bio { - // BIO mode: Write ALL pending TLS data to outgoing BIO - // This prevents hangs where Python's ssl_io_loop waits for data - self.write_pending_tls(conn, vm)?; - } else { - // Socket mode: Try once and may return SSLWantWriteError - let mut buf = Vec::new(); - conn.write_tls(&mut buf) - .map_err(|e| vm.new_os_error(format!("TLS write failed: {e}")))?; - - if !buf.is_empty() { - // Wait for socket to be ready for writing - let timed_out = self.sock_wait_for_io_impl(SelectKind::Write, vm)?; - if timed_out { - return Err(vm.new_os_error("Write operation timed out")); - } + while written < data.len() { + let chunk_end = std::cmp::min(written + CHUNK_SIZE, data.len()); + let chunk = &data[written..chunk_end]; + + // Write chunk to TLS layer + { + let mut writer = conn.writer(); + use std::io::Write; + writer + .write_all(chunk) + .map_err(|e| vm.new_os_error(format!("Write failed: {e}")))?; + } + + written = chunk_end; - // Send encrypted data to socket - // Convert BlockingIOError to SSLWantWriteError - match self.sock_send(buf, vm) { - Ok(_) => {} - Err(e) => { - if is_blocking_io_error(&e, vm) { - // Non-blocking socket would block - return SSLWantWriteError - return Err(create_ssl_want_write_error(vm)); + // Flush TLS data to socket after each chunk + if conn.wants_write() { + if is_bio { + self.write_pending_tls(conn, vm)?; + } else { + // Socket mode: flush all pending TLS data + while conn.wants_write() { + let mut buf = Vec::new(); + conn.write_tls(&mut buf) + .map_err(|e| vm.new_os_error(format!("TLS write failed: {e}")))?; + + if !buf.is_empty() { + let timed_out = + self.sock_wait_for_io_impl(SelectKind::Write, vm)?; + if timed_out { + return Err(vm.new_os_error("Write operation timed out")); + } + + match self.sock_send(buf, vm) { + Ok(_) => {} + Err(e) => { + if is_blocking_io_error(&e, vm) { + return Err(create_ssl_want_write_error(vm)); + } + return Err(e); + } } - return Err(e); } } } @@ -4284,7 +4342,14 @@ mod _ssl { (Some("/etc/ssl/cert.pem"), Some("/etc/ssl/certs")) }; - #[cfg(not(any(target_os = "macos", target_os = "linux")))] + #[cfg(windows)] + let (default_cafile, default_capath) = { + // Windows uses certificate store, not file paths + // Return empty strings to avoid None being passed to os.path.isfile() + (Some(""), Some("")) + }; + + #[cfg(not(any(target_os = "macos", target_os = "linux", windows)))] let (default_cafile, default_capath): (Option<&str>, Option<&str>) = (None, None); let tuple = vm.ctx.new_tuple(vec![ @@ -4397,6 +4462,111 @@ mod _ssl { } } + // Windows-specific certificate store enumeration functions + #[cfg(windows)] + #[pyfunction] + fn enum_certificates(store_name: PyStrRef, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> { + use schannel::{RawPointer, cert_context::ValidUses, cert_store::CertStore}; + use windows_sys::Win32::Security::Cryptography; + + // Try both Current User and Local Machine stores + let open_fns = [CertStore::open_current_user, CertStore::open_local_machine]; + let stores = open_fns + .iter() + .filter_map(|open| open(store_name.as_str()).ok()) + .collect::<Vec<_>>(); + + // If no stores could be opened, raise OSError + if stores.is_empty() { + return Err(vm.new_os_error(format!( + "failed to open certificate store {:?}", + store_name.as_str() + ))); + } + + let certs = stores.iter().flat_map(|s| s.certs()).map(|c| { + let cert = vm.ctx.new_bytes(c.to_der().to_owned()); + let enc_type = unsafe { + let ptr = c.as_ptr() as *const Cryptography::CERT_CONTEXT; + (*ptr).dwCertEncodingType + }; + let enc_type = match enc_type { + Cryptography::X509_ASN_ENCODING => vm.new_pyobj("x509_asn"), + Cryptography::PKCS_7_ASN_ENCODING => vm.new_pyobj("pkcs_7_asn"), + other => vm.new_pyobj(other), + }; + let usage: PyObjectRef = match c.valid_uses() { + Ok(ValidUses::All) => vm.ctx.new_bool(true).into(), + Ok(ValidUses::Oids(oids)) => { + match crate::builtins::PyFrozenSet::from_iter( + vm, + oids.into_iter().map(|oid| vm.ctx.new_str(oid).into()), + ) { + Ok(set) => set.into_ref(&vm.ctx).into(), + Err(_) => vm.ctx.new_bool(true).into(), + } + } + Err(_) => vm.ctx.new_bool(true).into(), + }; + Ok(vm.new_tuple((cert, enc_type, usage)).into()) + }); + certs.collect::<PyResult<Vec<_>>>() + } + + #[cfg(windows)] + #[pyfunction] + fn enum_crls(store_name: PyStrRef, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> { + use windows_sys::Win32::Security::Cryptography::{ + CRL_CONTEXT, CertCloseStore, CertEnumCRLsInStore, CertOpenSystemStoreW, + X509_ASN_ENCODING, + }; + + let store_name_wide: Vec<u16> = store_name + .as_str() + .encode_utf16() + .chain(std::iter::once(0)) + .collect(); + + // Open system store + let store = unsafe { CertOpenSystemStoreW(0, store_name_wide.as_ptr()) }; + + if store.is_null() { + return Err(vm.new_os_error(format!( + "failed to open certificate store {:?}", + store_name.as_str() + ))); + } + + let mut result = Vec::new(); + + let mut crl_context: *const CRL_CONTEXT = std::ptr::null(); + loop { + crl_context = unsafe { CertEnumCRLsInStore(store, crl_context) }; + if crl_context.is_null() { + break; + } + + let crl = unsafe { &*crl_context }; + let crl_bytes = + unsafe { std::slice::from_raw_parts(crl.pbCrlEncoded, crl.cbCrlEncoded as usize) }; + + let enc_type = if crl.dwCertEncodingType == X509_ASN_ENCODING { + vm.new_pyobj("x509_asn") + } else { + vm.new_pyobj(crl.dwCertEncodingType) + }; + + result.push( + vm.new_tuple((vm.ctx.new_bytes(crl_bytes.to_vec()), enc_type)) + .into(), + ); + } + + unsafe { CertCloseStore(store, 0) }; + + Ok(result) + } + // Certificate type for SSL module (pure Rust implementation) #[pyattr] #[pyclass(module = "_ssl", name = "Certificate")] From a14dd5921da1a41dc60f9bb4211181acf41046a6 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI <jiseok.dev@gmail.com> Date: Mon, 8 Dec 2025 00:14:31 +0900 Subject: [PATCH 410/819] fix(sqlite): Raise ProgrammingError when operating on a closed cursor (#6339) --- Lib/test/test_sqlite3/test_dbapi.py | 2 -- crates/stdlib/src/sqlite.rs | 16 +++++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index d8772dfffba..9a95c489a31 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -1735,8 +1735,6 @@ def test_closed_call(self): con() class ClosedCurTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_closed(self): con = sqlite.connect(":memory:") cur = con.cursor() diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index 9fdb1716ac6..12af619dba9 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -1771,6 +1771,8 @@ mod _sqlite { script: PyUtf8StrRef, vm: &VirtualMachine, ) -> PyResult<PyRef<Self>> { + let _ = zelf.clone().inner(vm)?; + let db = zelf.connection.db_lock(vm)?; db.sql_limit(script.byte_len(), vm)?; @@ -1829,19 +1831,19 @@ mod _sqlite { fn close(&self, vm: &VirtualMachine) -> PyResult<()> { // Check if __init__ was called let mut guard = self.inner.lock(); - if guard.is_none() { + + let Some(inner) = guard.as_mut() else { return Err(new_programming_error( vm, "Base Cursor.__init__ not called.".to_owned(), )); - } + }; - if let Some(inner) = guard.as_mut() { - if let Some(stmt) = &inner.statement { - stmt.lock().reset(); - } - inner.closed = true; + if let Some(stmt) = &inner.statement { + stmt.lock().reset(); } + inner.closed = true; + Ok(()) } From f4b8b019ca1af4e21f4a7644b2844d1ec57349ea Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 8 Dec 2025 01:35:31 +0900 Subject: [PATCH 411/819] winapi suppliment (#6338) * winapi.ExitProcess * Fix winapi Handle * msvcrt.GetErrorMode * windows stats * unmark tests --- Lib/test/test_faulthandler.py | 1 - Lib/test/test_shutil.py | 2 -- Lib/test/test_subprocess.py | 1 - crates/vm/src/stdlib/msvcrt.rs | 6 ++++++ crates/vm/src/stdlib/stat.rs | 11 +++++++++++ crates/vm/src/stdlib/winapi.rs | 24 ++++++++++++++++++++---- 6 files changed, 37 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index 1225db997e3..6316f937e21 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -374,7 +374,6 @@ def test_enable_single_thread(self): 'Segmentation fault', all_threads=False) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AttributeError: module 'msvcrt' has no attribute 'GetErrorMode'") @skip_segfault_on_android def test_disable(self): code = """ diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 1fe2aef39de..91bc986aab5 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -739,7 +739,6 @@ def test_copytree_dirs_exist_ok(self): with self.assertRaises(FileExistsError): shutil.copytree(src_dir, dst_dir, dirs_exist_ok=False) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_copytree_symlinks(self): tmp_dir = self.mkdtemp() @@ -1007,7 +1006,6 @@ def test_copytree_dangling_symlinks(self): shutil.copytree(src_dir, dst_dir, symlinks=True) self.assertIn('test.txt', os.listdir(dst_dir)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_copytree_symlink_dir(self): src_dir = self.mkdtemp() diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 5a75971be65..e2aabf3dcf1 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1337,7 +1337,6 @@ def _test_bufsize_equal_one(self, line, expected, universal_newlines): self.assertEqual(p.returncode, 0) self.assertEqual(read_line, expected) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_bufsize_equal_one_text_mode(self): # line is flushed in text mode with bufsize=1. # we should get the full line in return diff --git a/crates/vm/src/stdlib/msvcrt.rs b/crates/vm/src/stdlib/msvcrt.rs index 3eff6849e4c..aee36adcf22 100644 --- a/crates/vm/src/stdlib/msvcrt.rs +++ b/crates/vm/src/stdlib/msvcrt.rs @@ -109,6 +109,12 @@ mod msvcrt { .map_err(|e| e.into_pyexception(vm)) } + #[allow(non_snake_case)] + #[pyfunction] + fn GetErrorMode() -> u32 { + unsafe { suppress_iph!(Debug::GetErrorMode()) } + } + #[allow(non_snake_case)] #[pyfunction] fn SetErrorMode(mode: Debug::THREAD_ERROR_MODE, _: &VirtualMachine) -> u32 { diff --git a/crates/vm/src/stdlib/stat.rs b/crates/vm/src/stdlib/stat.rs index f7b3abb4325..90f045fcaf0 100644 --- a/crates/vm/src/stdlib/stat.rs +++ b/crates/vm/src/stdlib/stat.rs @@ -237,6 +237,17 @@ mod stat { FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_VIRTUAL, }; + // Windows reparse point tags + #[cfg(windows)] + #[pyattr] + pub const IO_REPARSE_TAG_SYMLINK: u32 = 0xA000000C; + #[cfg(windows)] + #[pyattr] + pub const IO_REPARSE_TAG_MOUNT_POINT: u32 = 0xA0000003; + #[cfg(windows)] + #[pyattr] + pub const IO_REPARSE_TAG_APPEXECLINK: u32 = 0x8000001B; + // Unix file flags (if on Unix) #[pyattr] diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index 10a14acbc95..58bfdf61d4f 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -19,7 +19,7 @@ mod _winapi { Win32::Foundation::{HANDLE, HINSTANCE, MAX_PATH}, core::PCWSTR, }; - use windows_sys::Win32::Foundation::{BOOL, HANDLE as RAW_HANDLE}; + use windows_sys::Win32::Foundation::{BOOL, INVALID_HANDLE_VALUE}; #[pyattr] use windows_sys::Win32::{ @@ -87,8 +87,18 @@ mod _winapi { #[pyfunction] fn GetStdHandle( std_handle: windows_sys::Win32::System::Console::STD_HANDLE, - ) -> WindowsSysResult<RAW_HANDLE> { - WindowsSysResult(unsafe { windows_sys::Win32::System::Console::GetStdHandle(std_handle) }) + vm: &VirtualMachine, + ) -> PyResult<Option<HANDLE>> { + let handle = unsafe { windows_sys::Win32::System::Console::GetStdHandle(std_handle) }; + if handle == INVALID_HANDLE_VALUE { + return Err(errno_err(vm)); + } + Ok(if handle.is_null() { + // NULL handle - return None + None + } else { + Some(HANDLE(handle as isize)) + }) } #[pyfunction] @@ -114,7 +124,8 @@ mod _winapi { #[pyfunction] fn DuplicateHandle( - (src_process, src): (HANDLE, HANDLE), + src_process: HANDLE, + src: HANDLE, target_process: HANDLE, access: u32, inherit: BOOL, @@ -294,6 +305,11 @@ mod _winapi { } } + #[pyfunction] + fn ExitProcess(exit_code: u32) { + unsafe { windows_sys::Win32::System::Threading::ExitProcess(exit_code) } + } + #[pyfunction] fn NeedCurrentDirectoryForExePath(exe_name: PyStrRef) -> bool { let exe_name = exe_name.as_str().to_wide_with_nul(); From cab41c807b92f02ecad32d91dd563756f8e545dd Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 8 Dec 2025 01:51:51 +0900 Subject: [PATCH 412/819] windows codecs (#6337) * mbcs_codec * oem codec --- Lib/test/test_subprocess.py | 10 - crates/vm/src/stdlib/codecs.rs | 396 +++++++++++++++++++++++++++++++++ 2 files changed, 396 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index e2aabf3dcf1..28b5124b8ea 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -3648,8 +3648,6 @@ def test_shell_string(self): with p: self.assertIn(b"physalis", p.stdout.read()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_shell_encodings(self): # Run command through the shell (string) for enc in ['ansi', 'oem']: @@ -3868,28 +3866,20 @@ def with_spaces(self, *args, **kwargs): "2 [%r, 'ab cd']" % self.fname ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_shell_string_with_spaces(self): # call() function with string argument with spaces on Windows self.with_spaces('"%s" "%s" "%s"' % (sys.executable, self.fname, "ab cd"), shell=1) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_shell_sequence_with_spaces(self): # call() function with sequence argument with spaces on Windows self.with_spaces([sys.executable, self.fname, "ab cd"], shell=1) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_noshell_string_with_spaces(self): # call() function with string argument with spaces on Windows self.with_spaces('"%s" "%s" "%s"' % (sys.executable, self.fname, "ab cd")) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_noshell_sequence_with_spaces(self): # call() function with sequence argument with spaces on Windows self.with_spaces([sys.executable, self.fname, "ab cd"]) diff --git a/crates/vm/src/stdlib/codecs.rs b/crates/vm/src/stdlib/codecs.rs index 46b1608f243..dee5b17b822 100644 --- a/crates/vm/src/stdlib/codecs.rs +++ b/crates/vm/src/stdlib/codecs.rs @@ -225,14 +225,410 @@ mod _codecs { }}; } + #[cfg(windows)] + #[derive(FromArgs)] + struct MbcsEncodeArgs { + #[pyarg(positional)] + s: PyStrRef, + #[pyarg(positional, optional)] + errors: Option<PyStrRef>, + } + + #[cfg(windows)] + #[pyfunction] + fn mbcs_encode(args: MbcsEncodeArgs, vm: &VirtualMachine) -> PyResult<(Vec<u8>, usize)> { + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::Globalization::{ + CP_ACP, WC_NO_BEST_FIT_CHARS, WideCharToMultiByte, + }; + + let errors = args.errors.as_ref().map(|s| s.as_str()).unwrap_or("strict"); + let s = match args.s.to_str() { + Some(s) => s, + None => { + // String contains surrogates - not encodable with mbcs + return Err(vm.new_unicode_encode_error( + "'mbcs' codec can't encode character: surrogates not allowed".to_string(), + )); + } + }; + let char_len = args.s.char_len(); + + if s.is_empty() { + return Ok((Vec::new(), char_len)); + } + + // Convert UTF-8 string to UTF-16 + let wide: Vec<u16> = std::ffi::OsStr::new(s).encode_wide().collect(); + + // Get the required buffer size + let size = unsafe { + WideCharToMultiByte( + CP_ACP, + WC_NO_BEST_FIT_CHARS, + wide.as_ptr(), + wide.len() as i32, + std::ptr::null_mut(), + 0, + std::ptr::null(), + std::ptr::null_mut(), + ) + }; + + if size == 0 { + let err = std::io::Error::last_os_error(); + return Err(vm.new_os_error(format!("mbcs_encode failed: {}", err))); + } + + let mut buffer = vec![0u8; size as usize]; + let mut used_default_char: i32 = 0; + + let result = unsafe { + WideCharToMultiByte( + CP_ACP, + WC_NO_BEST_FIT_CHARS, + wide.as_ptr(), + wide.len() as i32, + buffer.as_mut_ptr().cast(), + size, + std::ptr::null(), + if errors == "strict" { + &mut used_default_char + } else { + std::ptr::null_mut() + }, + ) + }; + + if result == 0 { + let err = std::io::Error::last_os_error(); + return Err(vm.new_os_error(format!("mbcs_encode failed: {err}"))); + } + + if errors == "strict" && used_default_char != 0 { + return Err(vm.new_unicode_encode_error( + "'mbcs' codec can't encode characters: invalid character", + )); + } + + buffer.truncate(result as usize); + Ok((buffer, char_len)) + } + + #[cfg(not(windows))] #[pyfunction] fn mbcs_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult { delegate_pycodecs!(mbcs_encode, args, vm) } + + #[cfg(windows)] + #[derive(FromArgs)] + struct MbcsDecodeArgs { + #[pyarg(positional)] + data: ArgBytesLike, + #[pyarg(positional, optional)] + errors: Option<PyStrRef>, + #[pyarg(positional, default = false)] + #[allow(dead_code)] + r#final: bool, + } + + #[cfg(windows)] + #[pyfunction] + fn mbcs_decode(args: MbcsDecodeArgs, vm: &VirtualMachine) -> PyResult<(String, usize)> { + use windows_sys::Win32::Globalization::{ + CP_ACP, MB_ERR_INVALID_CHARS, MultiByteToWideChar, + }; + + let _errors = args.errors.as_ref().map(|s| s.as_str()).unwrap_or("strict"); + let data = args.data.borrow_buf(); + let len = data.len(); + + if data.is_empty() { + return Ok((String::new(), 0)); + } + + // Get the required buffer size for UTF-16 + let size = unsafe { + MultiByteToWideChar( + CP_ACP, + MB_ERR_INVALID_CHARS, + data.as_ptr().cast(), + len as i32, + std::ptr::null_mut(), + 0, + ) + }; + + if size == 0 { + // Try without MB_ERR_INVALID_CHARS for non-strict mode (replacement behavior) + let size = unsafe { + MultiByteToWideChar( + CP_ACP, + 0, + data.as_ptr().cast(), + len as i32, + std::ptr::null_mut(), + 0, + ) + }; + if size == 0 { + let err = std::io::Error::last_os_error(); + return Err(vm.new_os_error(format!("mbcs_decode failed: {}", err))); + } + + let mut buffer = vec![0u16; size as usize]; + let result = unsafe { + MultiByteToWideChar( + CP_ACP, + 0, + data.as_ptr().cast(), + len as i32, + buffer.as_mut_ptr(), + size, + ) + }; + if result == 0 { + let err = std::io::Error::last_os_error(); + return Err(vm.new_os_error(format!("mbcs_decode failed: {}", err))); + } + buffer.truncate(result as usize); + let s = String::from_utf16(&buffer) + .map_err(|e| vm.new_unicode_decode_error(format!("mbcs_decode failed: {}", e)))?; + return Ok((s, len)); + } + + // Strict mode succeeded - no invalid characters + let mut buffer = vec![0u16; size as usize]; + let result = unsafe { + MultiByteToWideChar( + CP_ACP, + MB_ERR_INVALID_CHARS, + data.as_ptr().cast(), + len as i32, + buffer.as_mut_ptr(), + size, + ) + }; + if result == 0 { + let err = std::io::Error::last_os_error(); + return Err(vm.new_os_error(format!("mbcs_decode failed: {}", err))); + } + buffer.truncate(result as usize); + let s = String::from_utf16(&buffer) + .map_err(|e| vm.new_unicode_decode_error(format!("mbcs_decode failed: {}", e)))?; + + Ok((s, len)) + } + + #[cfg(not(windows))] #[pyfunction] fn mbcs_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult { delegate_pycodecs!(mbcs_decode, args, vm) } + + #[cfg(windows)] + #[derive(FromArgs)] + struct OemEncodeArgs { + #[pyarg(positional)] + s: PyStrRef, + #[pyarg(positional, optional)] + errors: Option<PyStrRef>, + } + + #[cfg(windows)] + #[pyfunction] + fn oem_encode(args: OemEncodeArgs, vm: &VirtualMachine) -> PyResult<(Vec<u8>, usize)> { + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::Globalization::{ + CP_OEMCP, WC_NO_BEST_FIT_CHARS, WideCharToMultiByte, + }; + + let errors = args.errors.as_ref().map(|s| s.as_str()).unwrap_or("strict"); + let s = match args.s.to_str() { + Some(s) => s, + None => { + // String contains surrogates - not encodable with oem + return Err(vm.new_unicode_encode_error( + "'oem' codec can't encode character: surrogates not allowed".to_string(), + )); + } + }; + let char_len = args.s.char_len(); + + if s.is_empty() { + return Ok((Vec::new(), char_len)); + } + + // Convert UTF-8 string to UTF-16 + let wide: Vec<u16> = std::ffi::OsStr::new(s).encode_wide().collect(); + + // Get the required buffer size + let size = unsafe { + WideCharToMultiByte( + CP_OEMCP, + WC_NO_BEST_FIT_CHARS, + wide.as_ptr(), + wide.len() as i32, + std::ptr::null_mut(), + 0, + std::ptr::null(), + std::ptr::null_mut(), + ) + }; + + if size == 0 { + let err = std::io::Error::last_os_error(); + return Err(vm.new_os_error(format!("oem_encode failed: {}", err))); + } + + let mut buffer = vec![0u8; size as usize]; + let mut used_default_char: i32 = 0; + + let result = unsafe { + WideCharToMultiByte( + CP_OEMCP, + WC_NO_BEST_FIT_CHARS, + wide.as_ptr(), + wide.len() as i32, + buffer.as_mut_ptr().cast(), + size, + std::ptr::null(), + if errors == "strict" { + &mut used_default_char + } else { + std::ptr::null_mut() + }, + ) + }; + + if result == 0 { + let err = std::io::Error::last_os_error(); + return Err(vm.new_os_error(format!("oem_encode failed: {err}"))); + } + + if errors == "strict" && used_default_char != 0 { + return Err(vm.new_unicode_encode_error( + "'oem' codec can't encode characters: invalid character", + )); + } + + buffer.truncate(result as usize); + Ok((buffer, char_len)) + } + + #[cfg(not(windows))] + #[pyfunction] + fn oem_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult { + delegate_pycodecs!(oem_encode, args, vm) + } + + #[cfg(windows)] + #[derive(FromArgs)] + struct OemDecodeArgs { + #[pyarg(positional)] + data: ArgBytesLike, + #[pyarg(positional, optional)] + errors: Option<PyStrRef>, + #[pyarg(positional, default = false)] + #[allow(dead_code)] + r#final: bool, + } + + #[cfg(windows)] + #[pyfunction] + fn oem_decode(args: OemDecodeArgs, vm: &VirtualMachine) -> PyResult<(String, usize)> { + use windows_sys::Win32::Globalization::{ + CP_OEMCP, MB_ERR_INVALID_CHARS, MultiByteToWideChar, + }; + + let _errors = args.errors.as_ref().map(|s| s.as_str()).unwrap_or("strict"); + let data = args.data.borrow_buf(); + let len = data.len(); + + if data.is_empty() { + return Ok((String::new(), 0)); + } + + // Get the required buffer size for UTF-16 + let size = unsafe { + MultiByteToWideChar( + CP_OEMCP, + MB_ERR_INVALID_CHARS, + data.as_ptr().cast(), + len as i32, + std::ptr::null_mut(), + 0, + ) + }; + + if size == 0 { + // Try without MB_ERR_INVALID_CHARS for non-strict mode (replacement behavior) + let size = unsafe { + MultiByteToWideChar( + CP_OEMCP, + 0, + data.as_ptr().cast(), + len as i32, + std::ptr::null_mut(), + 0, + ) + }; + if size == 0 { + let err = std::io::Error::last_os_error(); + return Err(vm.new_os_error(format!("oem_decode failed: {}", err))); + } + + let mut buffer = vec![0u16; size as usize]; + let result = unsafe { + MultiByteToWideChar( + CP_OEMCP, + 0, + data.as_ptr().cast(), + len as i32, + buffer.as_mut_ptr(), + size, + ) + }; + if result == 0 { + let err = std::io::Error::last_os_error(); + return Err(vm.new_os_error(format!("oem_decode failed: {}", err))); + } + buffer.truncate(result as usize); + let s = String::from_utf16(&buffer) + .map_err(|e| vm.new_unicode_decode_error(format!("oem_decode failed: {}", e)))?; + return Ok((s, len)); + } + + // Strict mode succeeded - no invalid characters + let mut buffer = vec![0u16; size as usize]; + let result = unsafe { + MultiByteToWideChar( + CP_OEMCP, + MB_ERR_INVALID_CHARS, + data.as_ptr().cast(), + len as i32, + buffer.as_mut_ptr(), + size, + ) + }; + if result == 0 { + let err = std::io::Error::last_os_error(); + return Err(vm.new_os_error(format!("oem_decode failed: {}", err))); + } + buffer.truncate(result as usize); + let s = String::from_utf16(&buffer) + .map_err(|e| vm.new_unicode_decode_error(format!("oem_decode failed: {}", e)))?; + + Ok((s, len)) + } + + #[cfg(not(windows))] + #[pyfunction] + fn oem_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult { + delegate_pycodecs!(oem_decode, args, vm) + } + #[pyfunction] fn readbuffer_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult { delegate_pycodecs!(readbuffer_encode, args, vm) From 876368e4760133b9e969473fd31d51ac53d44d24 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:39:15 +0900 Subject: [PATCH 413/819] windows umask, win32_xstat_slow_impl, fake EXT_SUFFIX (#6340) * umask * EXT_SUFFIX * File Attributes and win32_xstat_slow_impl * unmark tests --- .cspell.dict/cpython.txt | 1 + Lib/test/test_genericpath.py | 7 - Lib/test/test_logging.py | 1 - Lib/test/test_ntpath.py | 10 - Lib/test/test_shutil.py | 6 - crates/vm/src/stdlib/nt.rs | 14 + crates/vm/src/stdlib/sysconfig.rs | 11 + crates/vm/src/windows.rs | 490 +++++++++++++++++++++++++----- 8 files changed, 436 insertions(+), 104 deletions(-) diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 19c8725fa66..26921e04080 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -46,6 +46,7 @@ prec preinitialized PYTHREAD_NAME SA_ONSTACK +SOABI stackdepth stringlib structseq diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py index 3475c026bb8..89e4fe1882e 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py @@ -352,13 +352,6 @@ def test_invalid_paths(self): with self.assertRaisesRegex(ValueError, 'embedded null'): func(b'/tmp\x00abcds') - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') - def test_samestat_on_symlink(self): - return super().test_samestat_on_symlink() - - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') - def test_samefile_on_symlink(self): - return super().test_samefile_on_symlink() # Following TestCase is not supposed to be run from test_genericpath. # It is inherited by other test modules (ntpath, posixpath). diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 3ec6c3c3a1d..c0f49fac6d2 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -6384,7 +6384,6 @@ def rotator(source, dest): rh.close() class TimedRotatingFileHandlerTest(BaseFileTest): - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') @unittest.skipIf(support.is_wasi, "WASI does not have /dev/null.") def test_should_not_rollover(self): # See bpo-45401. Should only ever rollover regular files diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 96c20fe8934..023be1a9656 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -1458,8 +1458,6 @@ def test_isfile_named_pipe(self): finally: _winapi.CloseHandle(h) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(sys.platform != 'win32', "windows only") def test_con_device(self): self.assertFalse(os.path.isfile(r"\\.\CON")) @@ -1527,14 +1525,6 @@ def test_expandvars(self): def test_expandvars_nonascii(self): return super().test_expandvars_nonascii() - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') - def test_samefile_on_symlink(self): - return super().test_samefile_on_symlink() - - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') - def test_samestat_on_symlink(self): - return super().test_samestat_on_symlink() - class PathLikeTests(NtpathTestCase): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 91bc986aab5..124430c8922 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1476,7 +1476,6 @@ def test_dont_copy_file_onto_link_to_itself(self): finally: shutil.rmtree(TESTFN, ignore_errors=True) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; AssertionError: SameFileError not raised for copyfile') @os_helper.skip_unless_symlink def test_dont_copy_file_onto_symlink_to_itself(self): # bug 851123. @@ -2577,7 +2576,6 @@ def test_destinsrc_false_positive(self): finally: os_helper.rmtree(TESTFN) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink @mock_rename def test_move_file_symlink(self): @@ -2587,7 +2585,6 @@ def test_move_file_symlink(self): self.assertTrue(os.path.islink(self.dst_file)) self.assertTrue(os.path.samefile(self.src_file, self.dst_file)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink @mock_rename def test_move_file_symlink_to_dir(self): @@ -2611,7 +2608,6 @@ def test_move_dangling_symlink(self): self.assertTrue(os.path.islink(dst_link)) self.assertEqual(os.path.realpath(src), os.path.realpath(dst_link)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink @mock_rename def test_move_dir_symlink(self): @@ -2687,12 +2683,10 @@ def _test_move_symlink_to_dir_into_dir(self, dst): self.assertTrue(os.path.samefile(self.dst_dir, dst_link)) self.assertTrue(os.path.exists(src)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_move_symlink_to_dir_into_dir(self): self._test_move_symlink_to_dir_into_dir(self.dst_dir) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_move_symlink_to_dir_into_symlink_to_dir(self): dst = os.path.join(self.src_dir, 'otherlinktodir') diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 9ed89b0bab7..e2d4b41cc3c 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -475,6 +475,20 @@ pub(crate) mod module { Ok(()) } + unsafe extern "C" { + fn _umask(mask: i32) -> i32; + } + + #[pyfunction] + fn umask(mask: i32, vm: &VirtualMachine) -> PyResult<i32> { + let result = unsafe { _umask(mask) }; + if result < 0 { + Err(errno_err(vm)) + } else { + Ok(result) + } + } + pub(crate) fn support_funcs() -> Vec<SupportFunc> { Vec::new() } diff --git a/crates/vm/src/stdlib/sysconfig.rs b/crates/vm/src/stdlib/sysconfig.rs index 2e0a8a51c7a..df5b7100a90 100644 --- a/crates/vm/src/stdlib/sysconfig.rs +++ b/crates/vm/src/stdlib/sysconfig.rs @@ -7,8 +7,19 @@ pub(crate) mod sysconfig { #[pyfunction] fn config_vars(vm: &VirtualMachine) -> PyDictRef { let vars = vm.ctx.new_dict(); + + // FIXME: This is an entirely wrong implementation of EXT_SUFFIX. + // EXT_SUFFIX must be a string starting with "." for pip compatibility + // Using ".pyd" causes pip's _generic_abi() to fall back to _cpython_abis() + vars.set_item("EXT_SUFFIX", ".pyd".to_pyobject(vm), vm) + .unwrap(); + vars.set_item("SOABI", vm.ctx.none(), vm).unwrap(); + vars.set_item("Py_GIL_DISABLED", true.to_pyobject(vm), vm) .unwrap(); + vars.set_item("Py_DEBUG", false.to_pyobject(vm), vm) + .unwrap(); + vars } } diff --git a/crates/vm/src/windows.rs b/crates/vm/src/windows.rs index 907f61c7b8a..26fec850215 100644 --- a/crates/vm/src/windows.rs +++ b/crates/vm/src/windows.rs @@ -7,7 +7,7 @@ use crate::{ convert::{ToPyObject, ToPyResult}, stdlib::os::errno_err, }; -use std::{ffi::OsStr, time::SystemTime}; +use std::ffi::OsStr; use windows::Win32::Foundation::HANDLE; use windows_sys::Win32::Foundation::{BOOL, HANDLE as RAW_HANDLE, INVALID_HANDLE_VALUE}; @@ -93,6 +93,413 @@ fn is_reparse_tag_name_surrogate(tag: u32) -> bool { (tag & 0x20000000) > 0 } +// Constants +const IO_REPARSE_TAG_SYMLINK: u32 = 0xA000000C; +const S_IFMT: u16 = libc::S_IFMT as u16; +const S_IFDIR: u16 = libc::S_IFDIR as u16; +const S_IFREG: u16 = libc::S_IFREG as u16; +const S_IFCHR: u16 = libc::S_IFCHR as u16; +const S_IFLNK: u16 = crate::common::fileutils::windows::S_IFLNK as u16; +const S_IFIFO: u16 = crate::common::fileutils::windows::S_IFIFO as u16; + +/// FILE_ATTRIBUTE_TAG_INFO structure for GetFileInformationByHandleEx +#[repr(C)] +#[derive(Default)] +struct FileAttributeTagInfo { + file_attributes: u32, + reparse_tag: u32, +} + +/// Ported from attributes_to_mode (fileutils.c) +fn attributes_to_mode(attr: u32) -> u16 { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_READONLY, + }; + let mut m: u16 = 0; + if attr & FILE_ATTRIBUTE_DIRECTORY != 0 { + m |= S_IFDIR | 0o111; // IFEXEC for user,group,other + } else { + m |= S_IFREG; + } + if attr & FILE_ATTRIBUTE_READONLY != 0 { + m |= 0o444; + } else { + m |= 0o666; + } + m +} + +/// Ported from _Py_attribute_data_to_stat (fileutils.c) +/// Converts BY_HANDLE_FILE_INFORMATION to StatStruct +fn attribute_data_to_stat( + info: &windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION, + reparse_tag: u32, + basic_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_BASIC_INFO>, + id_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_ID_INFO>, +) -> StatStruct { + use crate::common::fileutils::windows::SECS_BETWEEN_EPOCHS; + use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT; + + let mut st_mode = attributes_to_mode(info.dwFileAttributes); + let st_size = ((info.nFileSizeHigh as u64) << 32) | (info.nFileSizeLow as u64); + let st_dev = id_info + .map(|id| id.VolumeSerialNumber as u32) + .unwrap_or(info.dwVolumeSerialNumber); + let st_nlink = info.nNumberOfLinks as i32; + + // Convert FILETIME/LARGE_INTEGER to (time_t, nsec) + let filetime_to_time = |ft_low: u32, ft_high: u32| -> (libc::time_t, i32) { + let ticks = ((ft_high as i64) << 32) | (ft_low as i64); + let nsec = ((ticks % 10_000_000) * 100) as i32; + let sec = (ticks / 10_000_000 - SECS_BETWEEN_EPOCHS) as libc::time_t; + (sec, nsec) + }; + + let large_integer_to_time = |li: i64| -> (libc::time_t, i32) { + let nsec = ((li % 10_000_000) * 100) as i32; + let sec = (li / 10_000_000 - SECS_BETWEEN_EPOCHS) as libc::time_t; + (sec, nsec) + }; + + let (st_birthtime, st_birthtime_nsec); + let (st_mtime, st_mtime_nsec); + let (st_atime, st_atime_nsec); + + if let Some(bi) = basic_info { + (st_birthtime, st_birthtime_nsec) = large_integer_to_time(bi.CreationTime); + (st_mtime, st_mtime_nsec) = large_integer_to_time(bi.LastWriteTime); + (st_atime, st_atime_nsec) = large_integer_to_time(bi.LastAccessTime); + } else { + (st_birthtime, st_birthtime_nsec) = filetime_to_time( + info.ftCreationTime.dwLowDateTime, + info.ftCreationTime.dwHighDateTime, + ); + (st_mtime, st_mtime_nsec) = filetime_to_time( + info.ftLastWriteTime.dwLowDateTime, + info.ftLastWriteTime.dwHighDateTime, + ); + (st_atime, st_atime_nsec) = filetime_to_time( + info.ftLastAccessTime.dwLowDateTime, + info.ftLastAccessTime.dwHighDateTime, + ); + } + + // Get file ID from id_info or fallback to file index + let (st_ino, st_ino_high) = if let Some(id) = id_info { + // FILE_ID_INFO.FileId is FILE_ID_128 which is [u8; 16] + let bytes = id.FileId.Identifier; + let low = u64::from_le_bytes(bytes[0..8].try_into().unwrap()); + let high = u64::from_le_bytes(bytes[8..16].try_into().unwrap()); + (low, high) + } else { + let ino = ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64); + (ino, 0u64) + }; + + // Set symlink mode if applicable + if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 + && reparse_tag == IO_REPARSE_TAG_SYMLINK + { + st_mode = (st_mode & !S_IFMT) | S_IFLNK; + } + + StatStruct { + st_dev, + st_ino, + st_ino_high, + st_mode, + st_nlink, + st_uid: 0, + st_gid: 0, + st_rdev: 0, + st_size, + st_atime, + st_atime_nsec, + st_mtime, + st_mtime_nsec, + st_ctime: 0, // Will be set by caller + st_ctime_nsec: 0, + st_birthtime, + st_birthtime_nsec, + st_file_attributes: info.dwFileAttributes, + st_reparse_tag: reparse_tag, + } +} + +/// Get file info using FindFirstFileW (fallback when CreateFileW fails) +/// Ported from attributes_from_dir +fn attributes_from_dir( + path: &OsStr, +) -> std::io::Result<( + windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION, + u32, +)> { + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::Storage::FileSystem::{ + BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_REPARSE_POINT, FindClose, FindFirstFileW, + WIN32_FIND_DATAW, + }; + + let wide: Vec<u16> = path.encode_wide().chain(std::iter::once(0)).collect(); + let mut find_data: WIN32_FIND_DATAW = unsafe { std::mem::zeroed() }; + + let handle = unsafe { FindFirstFileW(wide.as_ptr(), &mut find_data) }; + if handle == INVALID_HANDLE_VALUE { + return Err(std::io::Error::last_os_error()); + } + unsafe { FindClose(handle) }; + + let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() }; + info.dwFileAttributes = find_data.dwFileAttributes; + info.ftCreationTime = find_data.ftCreationTime; + info.ftLastAccessTime = find_data.ftLastAccessTime; + info.ftLastWriteTime = find_data.ftLastWriteTime; + info.nFileSizeHigh = find_data.nFileSizeHigh; + info.nFileSizeLow = find_data.nFileSizeLow; + + let reparse_tag = if find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { + find_data.dwReserved0 + } else { + 0 + }; + + Ok((info, reparse_tag)) +} + +/// Ported from win32_xstat_slow_impl +fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatStruct> { + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::{ + Foundation::{ + CloseHandle, ERROR_ACCESS_DENIED, ERROR_CANT_ACCESS_FILE, ERROR_INVALID_FUNCTION, + ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED, ERROR_SHARING_VIOLATION, GENERIC_READ, + INVALID_HANDLE_VALUE, + }, + Storage::FileSystem::{ + BY_HANDLE_FILE_INFORMATION, CreateFileW, FILE_ATTRIBUTE_DIRECTORY, + FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_REPARSE_POINT, FILE_BASIC_INFO, + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_ID_INFO, + FILE_READ_ATTRIBUTES, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_TYPE_CHAR, + FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_UNKNOWN, FileAttributeTagInfo, FileBasicInfo, + FileIdInfo, GetFileAttributesW, GetFileInformationByHandle, + GetFileInformationByHandleEx, GetFileType, INVALID_FILE_ATTRIBUTES, OPEN_EXISTING, + }, + }; + + let wide: Vec<u16> = path.encode_wide().chain(std::iter::once(0)).collect(); + + let access = FILE_READ_ATTRIBUTES; + let mut flags = FILE_FLAG_BACKUP_SEMANTICS; + if !traverse { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + + let mut h_file = unsafe { + CreateFileW( + wide.as_ptr(), + access, + 0, + std::ptr::null(), + OPEN_EXISTING, + flags, + std::ptr::null_mut(), + ) + }; + + let mut file_info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() }; + let mut tag_info = FileAttributeTagInfo::default(); + let mut is_unhandled_tag = false; + + if h_file == INVALID_HANDLE_VALUE { + let error = std::io::Error::last_os_error(); + let error_code = error.raw_os_error().unwrap_or(0) as u32; + + match error_code { + ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION => { + // Try reading the parent directory using FindFirstFileW + let (info, reparse_tag) = attributes_from_dir(path)?; + file_info = info; + tag_info.reparse_tag = reparse_tag; + + if file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 + && (traverse || !is_reparse_tag_name_surrogate(tag_info.reparse_tag)) + { + return Err(error); + } + // h_file remains INVALID_HANDLE_VALUE, we'll use file_info from FindFirstFileW + } + ERROR_INVALID_PARAMETER => { + // Retry with GENERIC_READ (needed for \\.\con) + h_file = unsafe { + CreateFileW( + wide.as_ptr(), + access | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + std::ptr::null(), + OPEN_EXISTING, + flags, + std::ptr::null_mut(), + ) + }; + if h_file == INVALID_HANDLE_VALUE { + return Err(error); + } + } + ERROR_CANT_ACCESS_FILE if traverse => { + // bpo37834: open unhandled reparse points if traverse fails + is_unhandled_tag = true; + h_file = unsafe { + CreateFileW( + wide.as_ptr(), + access, + 0, + std::ptr::null(), + OPEN_EXISTING, + flags | FILE_FLAG_OPEN_REPARSE_POINT, + std::ptr::null_mut(), + ) + }; + if h_file == INVALID_HANDLE_VALUE { + return Err(error); + } + } + _ => return Err(error), + } + } + + // Scope for handle cleanup + let result = (|| -> std::io::Result<StatStruct> { + if h_file != INVALID_HANDLE_VALUE { + // Handle types other than files on disk + let file_type = unsafe { GetFileType(h_file) }; + if file_type != FILE_TYPE_DISK { + if file_type == FILE_TYPE_UNKNOWN { + let err = std::io::Error::last_os_error(); + if err.raw_os_error().unwrap_or(0) != 0 { + return Err(err); + } + } + let file_attributes = unsafe { GetFileAttributesW(wide.as_ptr()) }; + let mut st_mode: u16 = 0; + if file_attributes != INVALID_FILE_ATTRIBUTES + && file_attributes & FILE_ATTRIBUTE_DIRECTORY != 0 + { + st_mode = S_IFDIR; + } else if file_type == FILE_TYPE_CHAR { + st_mode = S_IFCHR; + } else if file_type == FILE_TYPE_PIPE { + st_mode = S_IFIFO; + } + return Ok(StatStruct { + st_mode, + ..Default::default() + }); + } + + // Query the reparse tag + if !traverse || is_unhandled_tag { + let mut local_tag_info: FileAttributeTagInfo = unsafe { std::mem::zeroed() }; + let ret = unsafe { + GetFileInformationByHandleEx( + h_file, + FileAttributeTagInfo, + &mut local_tag_info as *mut _ as *mut _, + std::mem::size_of::<FileAttributeTagInfo>() as u32, + ) + }; + if ret == 0 { + let err_code = + std::io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32; + match err_code { + ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED => { + local_tag_info.file_attributes = FILE_ATTRIBUTE_NORMAL; + local_tag_info.reparse_tag = 0; + } + _ => return Err(std::io::Error::last_os_error()), + } + } else if local_tag_info.file_attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { + if is_reparse_tag_name_surrogate(local_tag_info.reparse_tag) { + if is_unhandled_tag { + return Err(std::io::Error::from_raw_os_error( + ERROR_CANT_ACCESS_FILE as i32, + )); + } + // This is a symlink, keep the tag info + } else if !is_unhandled_tag { + // Traverse a non-link reparse point + unsafe { CloseHandle(h_file) }; + return win32_xstat_slow_impl(path, true); + } + } + tag_info = local_tag_info; + } + + // Get file information + let ret = unsafe { GetFileInformationByHandle(h_file, &mut file_info) }; + if ret == 0 { + let err_code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32; + match err_code { + ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED => { + // Volumes and physical disks are block devices + return Ok(StatStruct { + st_mode: 0x6000, // S_IFBLK + ..Default::default() + }); + } + _ => return Err(std::io::Error::last_os_error()), + } + } + + // Get FILE_BASIC_INFO + let mut basic_info: FILE_BASIC_INFO = unsafe { std::mem::zeroed() }; + let has_basic_info = unsafe { + GetFileInformationByHandleEx( + h_file, + FileBasicInfo, + &mut basic_info as *mut _ as *mut _, + std::mem::size_of::<FILE_BASIC_INFO>() as u32, + ) + } != 0; + + // Get FILE_ID_INFO (optional) + let mut id_info: FILE_ID_INFO = unsafe { std::mem::zeroed() }; + let has_id_info = unsafe { + GetFileInformationByHandleEx( + h_file, + FileIdInfo, + &mut id_info as *mut _ as *mut _, + std::mem::size_of::<FILE_ID_INFO>() as u32, + ) + } != 0; + + let mut result = attribute_data_to_stat( + &file_info, + tag_info.reparse_tag, + if has_basic_info { + Some(&basic_info) + } else { + None + }, + if has_id_info { Some(&id_info) } else { None }, + ); + result.update_st_mode_from_path(path, file_info.dwFileAttributes); + Ok(result) + } else { + // We got file_info from attributes_from_dir + let mut result = attribute_data_to_stat(&file_info, tag_info.reparse_tag, None, None); + result.update_st_mode_from_path(path, file_info.dwFileAttributes); + Ok(result) + } + })(); + + // Cleanup + if h_file != INVALID_HANDLE_VALUE { + unsafe { CloseHandle(h_file) }; + } + + result +} + fn win32_xstat_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatStruct> { use windows_sys::Win32::{Foundation, Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT}; @@ -124,83 +531,6 @@ fn win32_xstat_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatStruct> } } - // TODO: check if win32_xstat_slow_impl(&path, result, traverse) is required - meta_to_stat( - &crate::stdlib::os::fs_metadata(path, traverse)?, - file_id(path)?, - ) -} - -// Ported from zed: https://github.com/zed-industries/zed/blob/v0.131.6/crates/fs/src/fs.rs#L1532-L1562 -// can we get file id not open the file twice? -// https://github.com/rust-lang/rust/issues/63010 -fn file_id(path: &OsStr) -> std::io::Result<u64> { - use std::os::windows::{fs::OpenOptionsExt, io::AsRawHandle}; - use windows_sys::Win32::{ - Foundation::HANDLE, - Storage::FileSystem::{ - BY_HANDLE_FILE_INFORMATION, FILE_FLAG_BACKUP_SEMANTICS, GetFileInformationByHandle, - }, - }; - - let file = std::fs::OpenOptions::new() - .read(true) - .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) - .open(path)?; - - let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() }; - // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle - // This function supports Windows XP+ - let ret = unsafe { GetFileInformationByHandle(file.as_raw_handle() as HANDLE, &mut info) }; - if ret == 0 { - return Err(std::io::Error::last_os_error()); - }; - - Ok(((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64)) -} - -fn meta_to_stat(meta: &std::fs::Metadata, file_id: u64) -> std::io::Result<StatStruct> { - let st_mode = { - // Based on CPython fileutils.c' attributes_to_mode - let mut m = 0; - if meta.is_dir() { - m |= libc::S_IFDIR | 0o111; /* IFEXEC for user,group,other */ - } else { - m |= libc::S_IFREG; - } - if meta.is_symlink() { - m |= 0o100000; - } - if meta.permissions().readonly() { - m |= 0o444; - } else { - m |= 0o666; - } - m as _ - }; - let (atime, mtime, ctime) = (meta.accessed()?, meta.modified()?, meta.created()?); - let sec = |systime: SystemTime| match systime.duration_since(SystemTime::UNIX_EPOCH) { - Ok(d) => d.as_secs() as libc::time_t, - Err(e) => -(e.duration().as_secs() as libc::time_t), - }; - let nsec = |systime: SystemTime| match systime.duration_since(SystemTime::UNIX_EPOCH) { - Ok(d) => d.subsec_nanos() as i32, - Err(e) => -(e.duration().subsec_nanos() as i32), - }; - Ok(StatStruct { - st_dev: 0, - st_ino: file_id, - st_mode, - st_nlink: 0, - st_uid: 0, - st_gid: 0, - st_size: meta.len(), - st_atime: sec(atime), - st_mtime: sec(mtime), - st_birthtime: sec(ctime), - st_atime_nsec: nsec(atime), - st_mtime_nsec: nsec(mtime), - st_birthtime_nsec: nsec(ctime), - ..Default::default() - }) + // Fallback to slow implementation + win32_xstat_slow_impl(path, traverse) } From 42d497a142ae48f8d9c81d462256f7cd15d454af Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:36:33 +0900 Subject: [PATCH 414/819] Enable PIP test on windows (#6211) * Patch ensurepip whl to work on windows * Enable PIP test on windows --- .github/workflows/ci.yaml | 3 +-- .../_bundled/pip-25.2-py3-none-any.whl | Bin 1752557 -> 1764055 bytes 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 23d16ffadb9..f33671d5391 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -294,8 +294,7 @@ jobs: mkdir site-packages target/release/rustpython --install-pip ensurepip --user target/release/rustpython -m pip install six - - if: runner.os != 'Windows' - name: Check that ensurepip succeeds. + - name: Check that ensurepip succeeds. run: | target/release/rustpython -m ensurepip target/release/rustpython -c "import pip" diff --git a/Lib/ensurepip/_bundled/pip-25.2-py3-none-any.whl b/Lib/ensurepip/_bundled/pip-25.2-py3-none-any.whl index e14bb3f37c0ff4cfa105c71d5e536f2f496cb160..4db7e7207189d3d95b8377b8481112995f8c95c9 100644 GIT binary patch delta 43981 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 delta 36863 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 D=kN>u From 7250b1f85481316b561cb6fb5c892b0c275d9ca4 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI <jiseok.dev@gmail.com> Date: Mon, 8 Dec 2025 21:54:40 +0900 Subject: [PATCH 415/819] refactor(sqlite): add check_cursor_valid method for cursor state validation (#6342) Extract cursor validity check into a separate method that doesn't hold the lock. This is useful for executescript which only needs to verify the cursor state but doesn't need to modify CursorInner. - Add check_cursor_valid() method that checks if cursor is initialized and not closed without retaining the mutex guard - Use check_cursor_valid() in executescript instead of inner() since executescript doesn't modify CursorInner --- crates/stdlib/src/sqlite.rs | 41 ++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index 12af619dba9..7650221fb90 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -1589,26 +1589,35 @@ mod _sqlite { Ok(()) } - fn inner(&self, vm: &VirtualMachine) -> PyResult<PyMappedMutexGuard<'_, CursorInner>> { - let guard = self.inner.lock(); - if guard.is_some() { - let inner_guard = - PyMutexGuard::map(guard, |x| unsafe { x.as_mut().unwrap_unchecked() }); - if inner_guard.closed { - return Err(new_programming_error( - vm, - "Cannot operate on a closed cursor.".to_owned(), - )); - } - Ok(inner_guard) - } else { - Err(new_programming_error( + fn check_cursor_state(inner: Option<&CursorInner>, vm: &VirtualMachine) -> PyResult<()> { + match inner { + Some(inner) if inner.closed => Err(new_programming_error( + vm, + "Cannot operate on a closed cursor.".to_owned(), + )), + Some(_) => Ok(()), + None => Err(new_programming_error( vm, "Base Cursor.__init__ not called.".to_owned(), - )) + )), } } + fn inner(&self, vm: &VirtualMachine) -> PyResult<PyMappedMutexGuard<'_, CursorInner>> { + let guard = self.inner.lock(); + Self::check_cursor_state(guard.as_ref(), vm)?; + Ok(PyMutexGuard::map(guard, |x| unsafe { + x.as_mut().unwrap_unchecked() + })) + } + + /// Check if cursor is valid without retaining the lock. + /// Use this when you only need to verify the cursor state but don't need to modify it. + fn check_cursor_valid(&self, vm: &VirtualMachine) -> PyResult<()> { + let guard = self.inner.lock(); + Self::check_cursor_state(guard.as_ref(), vm) + } + #[pymethod] fn execute( zelf: PyRef<Self>, @@ -1771,7 +1780,7 @@ mod _sqlite { script: PyUtf8StrRef, vm: &VirtualMachine, ) -> PyResult<PyRef<Self>> { - let _ = zelf.clone().inner(vm)?; + zelf.check_cursor_valid(vm)?; let db = zelf.connection.db_lock(vm)?; From 4e6ef4150db7f9ecae910720f373d412c94b8a8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:09:23 +0900 Subject: [PATCH 416/819] Bump webpki-roots from 0.26.11 to 1.0.4 (#6347) Bumps [webpki-roots](https://github.com/rustls/webpki-roots) from 0.26.11 to 1.0.4. - [Release notes](https://github.com/rustls/webpki-roots/releases) - [Commits](https://github.com/rustls/webpki-roots/compare/v/0.26.11...v/1.0.4) --- updated-dependencies: - dependency-name: webpki-roots dependency-version: 1.0.4 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 11 +---------- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b770168af02..652e7e1f120 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3197,7 +3197,7 @@ dependencies = [ "unicode-casing", "unicode_names2 2.0.0", "uuid", - "webpki-roots 0.26.11", + "webpki-roots", "widestring", "windows-sys 0.59.0", "x509-cert", @@ -4293,15 +4293,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.4", -] - [[package]] name = "webpki-roots" version = "1.0.4" diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 2fe8e3ec85a..623e57e8706 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -126,7 +126,7 @@ x509-cert = { version = "0.2.5", features = ["pem", "builder"], optional = true x509-parser = { version = "0.16", optional = true } der = { version = "0.7", features = ["alloc", "oid"], optional = true } pem-rfc7468 = { version = "0.7", optional = true } -webpki-roots = { version = "0.26", optional = true } +webpki-roots = { version = "1.0", optional = true } aws-lc-rs = { version = "1.14.1", optional = true } oid-registry = { version = "0.7", features = ["x509", "pkcs1", "nist_algs"], optional = true } pkcs8 = { version = "0.10", features = ["encryption", "pkcs5", "pem"], optional = true } From 7e34ab743af6fd98dac2472838a71a3a2944a40e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:09:50 +0900 Subject: [PATCH 417/819] Bump actions/checkout from 5.0.1 to 6.0.1 (#6348) Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6.0.1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 14 +++++++------- .github/workflows/cron-ci.yaml | 8 ++++---- .github/workflows/pr-auto-commit.yaml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/update-doc-db.yml | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f33671d5391..62052da5a0d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -120,7 +120,7 @@ jobs: os: [macos-latest, ubuntu-latest, windows-latest] fail-fast: false steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable with: components: clippy @@ -179,7 +179,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable with: target: i686-unknown-linux-gnu @@ -246,7 +246,7 @@ jobs: os: [macos-latest, ubuntu-latest, windows-latest] fail-fast: false steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - uses: actions/setup-python@v6 @@ -310,7 +310,7 @@ jobs: name: Check Rust code with clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable with: components: clippy @@ -349,7 +349,7 @@ jobs: env: NIGHTLY_CHANNEL: nightly steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@master with: @@ -371,7 +371,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 @@ -434,7 +434,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable with: target: wasm32-wasip1 diff --git a/.github/workflows/cron-ci.yaml b/.github/workflows/cron-ci.yaml index e10c5ac0d1f..ddfefa0ab03 100644 --- a/.github/workflows/cron-ci.yaml +++ b/.github/workflows/cron-ci.yaml @@ -21,7 +21,7 @@ jobs: # Disable this scheduled job when running on a fork. if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-llvm-cov - uses: actions/setup-python@v6 @@ -49,7 +49,7 @@ jobs: # Disable this scheduled job when running on a fork. if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable - name: build rustpython run: cargo build --release --verbose @@ -80,7 +80,7 @@ jobs: # Disable this scheduled job when running on a fork. if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable - uses: actions/setup-python@v6 with: @@ -137,7 +137,7 @@ jobs: # Disable this scheduled job when running on a fork. if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable - uses: actions/setup-python@v6 with: diff --git a/.github/workflows/pr-auto-commit.yaml b/.github/workflows/pr-auto-commit.yaml index 118f7bcd4d4..c01466dbcfd 100644 --- a/.github/workflows/pr-auto-commit.yaml +++ b/.github/workflows/pr-auto-commit.yaml @@ -22,7 +22,7 @@ jobs: timeout-minutes: 60 steps: - name: Checkout PR branch - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} repository: ${{ github.event.pull_request.head.repo.full_name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a69169c3234..0026a74b868 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,7 +50,7 @@ jobs: # target: aarch64-pc-windows-msvc fail-fast: false steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable - uses: cargo-bins/cargo-binstall@main @@ -93,7 +93,7 @@ jobs: # Disable this scheduled job when running on a fork. if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-wasip1 diff --git a/.github/workflows/update-doc-db.yml b/.github/workflows/update-doc-db.yml index 0f84b8d23a9..e0880afe0ee 100644 --- a/.github/workflows/update-doc-db.yml +++ b/.github/workflows/update-doc-db.yml @@ -26,7 +26,7 @@ jobs: - windows-latest - macos-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false sparse-checkout: | @@ -51,7 +51,7 @@ jobs: runs-on: ubuntu-latest needs: generate steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false sparse-checkout: | From 8ad7f912eabab54d54709f8e810caa2943935438 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:10:53 +0900 Subject: [PATCH 418/819] Bump actions/setup-python from 6.0.0 to 6.1.0 (#6349) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6.0.0 to 6.1.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v6...v6.1.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 8 ++++---- .github/workflows/cron-ci.yaml | 6 +++--- .github/workflows/update-doc-db.yml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 62052da5a0d..960a25e82a3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -249,7 +249,7 @@ jobs: - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Set up the Windows environment @@ -268,7 +268,7 @@ jobs: - name: build rustpython run: cargo build --release --verbose --features=threading ${{ env.CARGO_ARGS }},jit if: runner.os != 'macOS' - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: run snippets @@ -316,7 +316,7 @@ jobs: components: clippy - name: run clippy on wasm run: cargo clippy --manifest-path=crates/wasm/Cargo.toml -- -Dwarnings - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: install ruff @@ -382,7 +382,7 @@ jobs: wget https://github.com/mozilla/geckodriver/releases/download/v0.36.0/geckodriver-v0.36.0-linux64.tar.gz mkdir geckodriver tar -xzf geckodriver-v0.36.0-linux64.tar.gz -C geckodriver - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - run: python -m pip install -r requirements.txt diff --git a/.github/workflows/cron-ci.yaml b/.github/workflows/cron-ci.yaml index ddfefa0ab03..59d664e0ea1 100644 --- a/.github/workflows/cron-ci.yaml +++ b/.github/workflows/cron-ci.yaml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-llvm-cov - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - run: sudo apt-get update && sudo apt-get -y install lcov @@ -82,7 +82,7 @@ jobs: steps: - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: build rustpython @@ -139,7 +139,7 @@ jobs: steps: - uses: actions/checkout@v6.0.1 - uses: dtolnay/rust-toolchain@stable - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v6.1.0 with: python-version: 3.9 - run: cargo install cargo-criterion diff --git a/.github/workflows/update-doc-db.yml b/.github/workflows/update-doc-db.yml index e0880afe0ee..1376e844fd1 100644 --- a/.github/workflows/update-doc-db.yml +++ b/.github/workflows/update-doc-db.yml @@ -32,7 +32,7 @@ jobs: sparse-checkout: | crates/rustpython-doc - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ inputs.python-version }} From 4592787946a6182aa90ce956d81bff72c35617db Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:55:03 +0900 Subject: [PATCH 419/819] get_inheritable, dup for windows (#6343) --- Lib/test/test_builtin.py | 1 - Lib/test/test_os.py | 6 ---- crates/vm/src/stdlib/nt.rs | 57 +++++++++++++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index b4119305f99..183caa898ef 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1358,7 +1358,6 @@ def test_open_default_encoding(self): os.environ.clear() os.environ.update(old_environ) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON Windows') @support.requires_subprocess() def test_open_non_inheritable(self): fileobj = open(__file__, encoding="utf-8") diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index c86d910eef6..6f111b2ffcd 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -2102,7 +2102,6 @@ def test_urandom_failure(self): """ assert_python_ok('-c', code) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; on Windows (ModuleNotFoundError: No module named 'os')") def test_urandom_fd_closed(self): # Issue #21207: urandom() should reopen its fd to /dev/urandom if # closed. @@ -4621,7 +4620,6 @@ def test_process_cpu_count_affinity(self): # FD inheritance check is only useful for systems with process support. @support.requires_subprocess() class FDInheritanceTests(unittest.TestCase): - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms') def test_get_set_inheritable(self): fd = os.open(__file__, os.O_RDONLY) self.addCleanup(os.close, fd) @@ -4682,7 +4680,6 @@ def test_get_set_inheritable_badf(self): os.set_inheritable(fd, False) self.assertEqual(ctx.exception.errno, errno.EBADF) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms') def test_open(self): fd = os.open(__file__, os.O_RDONLY) self.addCleanup(os.close, fd) @@ -4696,7 +4693,6 @@ def test_pipe(self): self.assertEqual(os.get_inheritable(rfd), False) self.assertEqual(os.get_inheritable(wfd), False) - @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; os.dup on windows') def test_dup(self): fd1 = os.open(__file__, os.O_RDONLY) self.addCleanup(os.close, fd1) @@ -4705,13 +4701,11 @@ def test_dup(self): self.addCleanup(os.close, fd2) self.assertEqual(os.get_inheritable(fd2), False) - @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; os.dup on windows') def test_dup_standard_stream(self): fd = os.dup(1) self.addCleanup(os.close, fd) self.assertGreater(fd, 0) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.dup not implemented yet for all platforms') @unittest.skipUnless(sys.platform == 'win32', 'win32-specific test') def test_dup_nul(self): # os.dup() was creating inheritable fds for character files. diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index e2d4b41cc3c..0464fa42f30 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -14,7 +14,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { pub(crate) mod module { use crate::{ PyResult, TryFromObject, VirtualMachine, - builtins::{PyDictRef, PyListRef, PyStrRef, PyTupleRef}, + builtins::{PyBaseExceptionRef, PyDictRef, PyListRef, PyStrRef, PyTupleRef}, common::{crt_fd, os::last_os_error, suppress_iph}, convert::ToPyException, function::{Either, OptionalArg}, @@ -391,6 +391,13 @@ pub(crate) mod module { } } + #[pyfunction] + fn get_inheritable(fd: i32, vm: &VirtualMachine) -> PyResult<bool> { + let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd) }; + let handle = crt_fd::as_handle(borrowed).map_err(|e| e.to_pyexception(vm))?; + get_handle_inheritable(handle.as_raw_handle() as _, vm) + } + #[pyfunction] fn getlogin(vm: &VirtualMachine) -> PyResult<String> { let mut buffer = [0u16; 257]; @@ -477,6 +484,15 @@ pub(crate) mod module { unsafe extern "C" { fn _umask(mask: i32) -> i32; + fn _dup(fd: i32) -> i32; + fn _dup2(fd: i32, fd2: i32) -> i32; + } + + /// Close fd and convert error to PyException (PEP 446 cleanup) + #[cold] + fn close_fd_and_raise(fd: i32, err: std::io::Error, vm: &VirtualMachine) -> PyBaseExceptionRef { + let _ = unsafe { crt_fd::Owned::from_raw(fd) }; + err.to_pyexception(vm) } #[pyfunction] @@ -489,6 +505,45 @@ pub(crate) mod module { } } + #[pyfunction] + fn dup(fd: i32, vm: &VirtualMachine) -> PyResult<i32> { + let fd2 = unsafe { suppress_iph!(_dup(fd)) }; + if fd2 < 0 { + return Err(errno_err(vm)); + } + let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd2) }; + let handle = crt_fd::as_handle(borrowed).map_err(|e| close_fd_and_raise(fd2, e, vm))?; + raw_set_handle_inheritable(handle.as_raw_handle() as _, false) + .map_err(|e| close_fd_and_raise(fd2, e, vm))?; + Ok(fd2) + } + + #[derive(FromArgs)] + struct Dup2Args { + #[pyarg(positional)] + fd: i32, + #[pyarg(positional)] + fd2: i32, + #[pyarg(any, default = true)] + inheritable: bool, + } + + #[pyfunction] + fn dup2(args: Dup2Args, vm: &VirtualMachine) -> PyResult<i32> { + let result = unsafe { suppress_iph!(_dup2(args.fd, args.fd2)) }; + if result < 0 { + return Err(errno_err(vm)); + } + if !args.inheritable { + let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(args.fd2) }; + let handle = + crt_fd::as_handle(borrowed).map_err(|e| close_fd_and_raise(args.fd2, e, vm))?; + raw_set_handle_inheritable(handle.as_raw_handle() as _, false) + .map_err(|e| close_fd_and_raise(args.fd2, e, vm))?; + } + Ok(args.fd2) + } + pub(crate) fn support_funcs() -> Vec<SupportFunc> { Vec::new() } From 2a425351e2f3cdc446faf3e841eb9d81672a6a8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:55:58 +0900 Subject: [PATCH 420/819] Bump phf from 0.11.3 to 0.13.1 (#6345) Bumps [phf](https://github.com/rust-phf/rust-phf) from 0.11.3 to 0.13.1. - [Release notes](https://github.com/rust-phf/rust-phf/releases) - [Changelog](https://github.com/rust-phf/rust-phf/blob/main/RELEASE_PROCESS.md) - [Commits](https://github.com/rust-phf/rust-phf/compare/phf-v0.11.3...v0.13.1) --- updated-dependencies: - dependency-name: phf dependency-version: 0.13.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 19 +++---------------- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 652e7e1f120..2c42fc0e53a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2210,7 +2210,6 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_macros 0.11.3", "phf_shared 0.11.3", ] @@ -2220,8 +2219,9 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros 0.13.1", + "phf_macros", "phf_shared 0.13.1", + "serde", ] [[package]] @@ -2254,19 +2254,6 @@ dependencies = [ "phf_shared 0.13.1", ] -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "phf_macros" version = "0.13.1" @@ -3165,7 +3152,7 @@ dependencies = [ "parking_lot", "paste", "pem-rfc7468", - "phf 0.11.3", + "phf 0.13.1", "pkcs8", "pymath", "rand_core 0.9.3", diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 623e57e8706..82efa47679c 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -44,7 +44,7 @@ num-integer = { workspace = true } num-traits = { workspace = true } num_enum = { workspace = true } parking_lot = { workspace = true } -phf = { version = "0.11", features = ["macros"] } +phf = { version = "0.13", features = ["macros"] } memchr = { workspace = true } base64 = "0.22" From bf565e917aca8ea574f2b6b50845901527f1e09f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:37:52 +0900 Subject: [PATCH 421/819] Windows execv, spawnv, wait (#6350) * more const * wait * exec * mkdir --- Lib/test/test_os.py | 3 - crates/vm/Cargo.toml | 1 + crates/vm/src/stdlib/nt.rs | 305 ++++++++++++++++++++++++++++++++++--- 3 files changed, 288 insertions(+), 21 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 6f111b2ffcd..e751b6423d6 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -2204,7 +2204,6 @@ def test_execv_with_bad_arglist(self): self.assertRaises(ValueError, os.execv, 'notepad', ('',)) self.assertRaises(ValueError, os.execv, 'notepad', ['']) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.execve not implemented yet for all platforms') def test_execvpe_with_bad_arglist(self): self.assertRaises(ValueError, os.execvpe, 'notepad', [], None) self.assertRaises(ValueError, os.execvpe, 'notepad', [], {}) @@ -2264,7 +2263,6 @@ def test_internal_execvpe_str(self): if os.name != "nt": self._test_internal_execvpe(bytes) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.execve not implemented yet for all platforms') def test_execve_invalid_env(self): args = [sys.executable, '-c', 'pass'] @@ -2286,7 +2284,6 @@ def test_execve_invalid_env(self): with self.assertRaises(ValueError): os.execve(args[0], args, newenv) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.execve not implemented yet for all platforms') @unittest.skipUnless(sys.platform == "win32", "Win32-specific test") def test_execve_with_empty_path(self): # bpo-32890: Check GetLastError() misuse diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 45c32239c9b..0e3f90a4613 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -129,6 +129,7 @@ features = [ "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", + "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_Diagnostics_Debug", diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 0464fa42f30..2304a15e4fd 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -35,7 +35,31 @@ pub(crate) mod module { }; #[pyattr] - use libc::{O_BINARY, O_TEMPORARY}; + use libc::{O_BINARY, O_NOINHERIT, O_RANDOM, O_SEQUENTIAL, O_TEMPORARY, O_TEXT}; + + // Windows spawn mode constants + #[pyattr] + const P_WAIT: i32 = 0; + #[pyattr] + const P_NOWAIT: i32 = 1; + #[pyattr] + const P_OVERLAY: i32 = 2; + #[pyattr] + const P_NOWAITO: i32 = 3; + #[pyattr] + const P_DETACH: i32 = 4; + + // _O_SHORT_LIVED is not in libc, define manually + #[pyattr] + const O_SHORT_LIVED: i32 = 0x1000; + + // Exit code constant + #[pyattr] + const EX_OK: i32 = 0; + + // Maximum number of temporary files + #[pyattr] + const TMP_MAX: i32 = i32::MAX; #[pyattr] use windows_sys::Win32::System::LibraryLoader::{ @@ -138,19 +162,22 @@ pub(crate) mod module { #[cfg(target_env = "msvc")] #[pyfunction] - fn waitpid(pid: intptr_t, opt: i32, vm: &VirtualMachine) -> PyResult<(intptr_t, i32)> { - let mut status = 0; + fn waitpid(pid: intptr_t, opt: i32, vm: &VirtualMachine) -> PyResult<(intptr_t, u64)> { + let mut status: i32 = 0; let pid = unsafe { suppress_iph!(_cwait(&mut status, pid, opt)) }; if pid == -1 { Err(errno_err(vm)) } else { - Ok((pid, status << 8)) + // Cast to unsigned to handle large exit codes (like 0xC000013A) + // then shift left by 8 to match POSIX waitpid format + let ustatus = (status as u32) as u64; + Ok((pid, ustatus << 8)) } } #[cfg(target_env = "msvc")] #[pyfunction] - fn wait(vm: &VirtualMachine) -> PyResult<(intptr_t, i32)> { + fn wait(vm: &VirtualMachine) -> PyResult<(intptr_t, u64)> { waitpid(-1, 0, vm) } @@ -212,12 +239,143 @@ pub(crate) mod module { #[cfg(target_env = "msvc")] unsafe extern "C" { fn _wexecv(cmdname: *const u16, argv: *const *const u16) -> intptr_t; + fn _wexecve( + cmdname: *const u16, + argv: *const *const u16, + envp: *const *const u16, + ) -> intptr_t; + fn _wspawnv(mode: i32, cmdname: *const u16, argv: *const *const u16) -> intptr_t; + fn _wspawnve( + mode: i32, + cmdname: *const u16, + argv: *const *const u16, + envp: *const *const u16, + ) -> intptr_t; + } + + #[cfg(target_env = "msvc")] + #[pyfunction] + fn spawnv( + mode: i32, + path: OsPath, + argv: Either<PyListRef, PyTupleRef>, + vm: &VirtualMachine, + ) -> PyResult<intptr_t> { + use std::iter::once; + + let make_widestring = + |s: &str| widestring::WideCString::from_os_str(s).map_err(|err| err.to_pyexception(vm)); + + let path = path.to_wide_cstring(vm)?; + + let argv = vm.extract_elements_with(argv.as_ref(), |obj| { + let arg = PyStrRef::try_from_object(vm, obj)?; + make_widestring(arg.as_str()) + })?; + + let first = argv + .first() + .ok_or_else(|| vm.new_value_error("spawnv() arg 3 must not be empty"))?; + + if first.is_empty() { + return Err(vm.new_value_error("spawnv() arg 3 first element cannot be empty")); + } + + let argv_spawn: Vec<*const u16> = argv + .iter() + .map(|v| v.as_ptr()) + .chain(once(std::ptr::null())) + .collect(); + + let result = unsafe { suppress_iph!(_wspawnv(mode, path.as_ptr(), argv_spawn.as_ptr())) }; + if result == -1 { + Err(errno_err(vm)) + } else { + Ok(result) + } + } + + #[cfg(target_env = "msvc")] + #[pyfunction] + fn spawnve( + mode: i32, + path: OsPath, + argv: Either<PyListRef, PyTupleRef>, + env: PyDictRef, + vm: &VirtualMachine, + ) -> PyResult<intptr_t> { + use std::iter::once; + + let make_widestring = + |s: &str| widestring::WideCString::from_os_str(s).map_err(|err| err.to_pyexception(vm)); + + let path = path.to_wide_cstring(vm)?; + + let argv = vm.extract_elements_with(argv.as_ref(), |obj| { + let arg = PyStrRef::try_from_object(vm, obj)?; + make_widestring(arg.as_str()) + })?; + + let first = argv + .first() + .ok_or_else(|| vm.new_value_error("spawnve() arg 2 cannot be empty"))?; + + if first.is_empty() { + return Err(vm.new_value_error("spawnve() arg 2 first element cannot be empty")); + } + + let argv_spawn: Vec<*const u16> = argv + .iter() + .map(|v| v.as_ptr()) + .chain(once(std::ptr::null())) + .collect(); + + // Build environment strings as "KEY=VALUE\0" wide strings + let mut env_strings: Vec<widestring::WideCString> = Vec::new(); + for (key, value) in env.into_iter() { + let key = PyStrRef::try_from_object(vm, key)?; + let value = PyStrRef::try_from_object(vm, value)?; + let key_str = key.as_str(); + let value_str = value.as_str(); + + // Validate: no null characters in key or value + if key_str.contains('\0') || value_str.contains('\0') { + return Err(vm.new_value_error("embedded null character")); + } + // Validate: no '=' in key + if key_str.contains('=') { + return Err(vm.new_value_error("illegal environment variable name")); + } + + let env_str = format!("{}={}", key_str, value_str); + env_strings.push(make_widestring(&env_str)?); + } + + let envp: Vec<*const u16> = env_strings + .iter() + .map(|s| s.as_ptr()) + .chain(once(std::ptr::null())) + .collect(); + + let result = unsafe { + suppress_iph!(_wspawnve( + mode, + path.as_ptr(), + argv_spawn.as_ptr(), + envp.as_ptr() + )) + }; + if result == -1 { + Err(errno_err(vm)) + } else { + Ok(result) + } } #[cfg(target_env = "msvc")] #[pyfunction] fn execv( - path: PyStrRef, + path: OsPath, argv: Either<PyListRef, PyTupleRef>, vm: &VirtualMachine, ) -> PyResult<()> { @@ -226,7 +384,7 @@ pub(crate) mod module { let make_widestring = |s: &str| widestring::WideCString::from_os_str(s).map_err(|err| err.to_pyexception(vm)); - let path = make_widestring(path.as_str())?; + let path = path.to_wide_cstring(vm)?; let argv = vm.extract_elements_with(argv.as_ref(), |obj| { let arg = PyStrRef::try_from_object(vm, obj)?; @@ -254,6 +412,76 @@ pub(crate) mod module { } } + #[cfg(target_env = "msvc")] + #[pyfunction] + fn execve( + path: OsPath, + argv: Either<PyListRef, PyTupleRef>, + env: PyDictRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + use std::iter::once; + + let make_widestring = + |s: &str| widestring::WideCString::from_os_str(s).map_err(|err| err.to_pyexception(vm)); + + let path = path.to_wide_cstring(vm)?; + + let argv = vm.extract_elements_with(argv.as_ref(), |obj| { + let arg = PyStrRef::try_from_object(vm, obj)?; + make_widestring(arg.as_str()) + })?; + + let first = argv + .first() + .ok_or_else(|| vm.new_value_error("execve: argv must not be empty"))?; + + if first.is_empty() { + return Err(vm.new_value_error("execve: argv first element cannot be empty")); + } + + let argv_execve: Vec<*const u16> = argv + .iter() + .map(|v| v.as_ptr()) + .chain(once(std::ptr::null())) + .collect(); + + // Build environment strings as "KEY=VALUE\0" wide strings + let mut env_strings: Vec<widestring::WideCString> = Vec::new(); + for (key, value) in env.into_iter() { + let key = PyStrRef::try_from_object(vm, key)?; + let value = PyStrRef::try_from_object(vm, value)?; + let key_str = key.as_str(); + let value_str = value.as_str(); + + // Validate: no null characters in key or value + if key_str.contains('\0') || value_str.contains('\0') { + return Err(vm.new_value_error("embedded null character")); + } + // Validate: no '=' in key + if key_str.contains('=') { + return Err(vm.new_value_error("illegal environment variable name")); + } + + let env_str = format!("{}={}", key_str, value_str); + env_strings.push(make_widestring(&env_str)?); + } + + let envp: Vec<*const u16> = env_strings + .iter() + .map(|s| s.as_ptr()) + .chain(once(std::ptr::null())) + .collect(); + + if (unsafe { suppress_iph!(_wexecve(path.as_ptr(), argv_execve.as_ptr(), envp.as_ptr())) } + == -1) + { + Err(errno_err(vm)) + } else { + Ok(()) + } + } + #[pyfunction] fn _getfinalpathname(path: OsPath, vm: &VirtualMachine) -> PyResult { let real = path @@ -464,18 +692,59 @@ pub(crate) mod module { raw_set_handle_inheritable(handle, inheritable).map_err(|e| e.to_pyexception(vm)) } - #[pyfunction] - fn mkdir( + #[derive(FromArgs)] + struct MkdirArgs<'a> { + #[pyarg(any)] path: OsPath, - mode: OptionalArg<i32>, - dir_fd: DirFd<'_, { _os::MKDIR_DIR_FD as usize }>, - vm: &VirtualMachine, - ) -> PyResult<()> { - let mode = mode.unwrap_or(0o777); - let [] = dir_fd.0; - let _ = mode; - let wide = path.to_wide_cstring(vm)?; - let res = unsafe { FileSystem::CreateDirectoryW(wide.as_ptr(), std::ptr::null_mut()) }; + #[pyarg(any, default = 0o777)] + mode: i32, + #[pyarg(flatten)] + dir_fd: DirFd<'a, { _os::MKDIR_DIR_FD as usize }>, + } + + #[pyfunction] + fn mkdir(args: MkdirArgs<'_>, vm: &VirtualMachine) -> PyResult<()> { + use windows_sys::Win32::Foundation::LocalFree; + use windows_sys::Win32::Security::Authorization::{ + ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1, + }; + use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; + + let [] = args.dir_fd.0; + let wide = args.path.to_wide_cstring(vm)?; + + // CPython special case: mode 0o700 sets a protected ACL + let res = if args.mode == 0o700 { + let mut sec_attr = SECURITY_ATTRIBUTES { + nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32, + lpSecurityDescriptor: std::ptr::null_mut(), + bInheritHandle: 0, + }; + // Set a discretionary ACL (D) that is protected (P) and includes + // inheritable (OICI) entries that allow (A) full control (FA) to + // SYSTEM (SY), Administrators (BA), and the owner (OW). + let sddl: Vec<u16> = "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)\0" + .encode_utf16() + .collect(); + let convert_result = unsafe { + ConvertStringSecurityDescriptorToSecurityDescriptorW( + sddl.as_ptr(), + SDDL_REVISION_1, + &mut sec_attr.lpSecurityDescriptor, + std::ptr::null_mut(), + ) + }; + if convert_result == 0 { + return Err(errno_err(vm)); + } + let res = + unsafe { FileSystem::CreateDirectoryW(wide.as_ptr(), &sec_attr as *const _ as _) }; + unsafe { LocalFree(sec_attr.lpSecurityDescriptor) }; + res + } else { + unsafe { FileSystem::CreateDirectoryW(wide.as_ptr(), std::ptr::null_mut()) } + }; + if res == 0 { return Err(errno_err(vm)); } From a3d638ab5fc77e78361bcc5c353f340aabd6a0fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:53:12 +0900 Subject: [PATCH 422/819] Bump windows-sys from 0.59.0 to 0.61.2 (#6346) * Bump windows-sys from 0.59.0 to 0.61.2 Bumps [windows-sys](https://github.com/microsoft/windows-rs) from 0.59.0 to 0.61.2. - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/commits) --- updated-dependencies: - dependency-name: windows-sys dependency-version: 0.61.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Fix breaking changes --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jeong, YunWon <jeong@youknowone.org> --- Cargo.lock | 8 +++---- Cargo.toml | 2 +- crates/common/src/fileutils.rs | 4 ++-- crates/vm/src/stdlib/winapi.rs | 42 ++++++++++++++++------------------ crates/vm/src/stdlib/winreg.rs | 2 +- crates/vm/src/windows.rs | 5 ++-- 6 files changed, 31 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c42fc0e53a..f839258a61c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1170,7 +1170,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2987,7 +2987,7 @@ dependencies = [ "siphasher", "unicode_names2 2.0.0", "widestring", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3186,7 +3186,7 @@ dependencies = [ "uuid", "webpki-roots", "widestring", - "windows-sys 0.59.0", + "windows-sys 0.61.2", "x509-cert", "x509-parser", "xml", @@ -3266,7 +3266,7 @@ dependencies = [ "which", "widestring", "windows", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8993ce145ff..f990b636360 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -219,7 +219,7 @@ unic-ucd-ident = "0.9.0" unicode_names2 = "2.0.0" unicode-bidi-mirroring = "0.4" widestring = "1.2.0" -windows-sys = "0.59.0" +windows-sys = "0.61.2" wasm-bindgen = "0.2.100" # Lints diff --git a/crates/common/src/fileutils.rs b/crates/common/src/fileutils.rs index cd61d9b204a..3e03f0299ad 100644 --- a/crates/common/src/fileutils.rs +++ b/crates/common/src/fileutils.rs @@ -30,7 +30,7 @@ pub mod windows { use std::os::windows::io::AsRawHandle; use std::sync::OnceLock; use windows_sys::Win32::Foundation::{ - BOOL, ERROR_INVALID_HANDLE, ERROR_NOT_SUPPORTED, FILETIME, FreeLibrary, SetLastError, + ERROR_INVALID_HANDLE, ERROR_NOT_SUPPORTED, FILETIME, FreeLibrary, SetLastError, }; use windows_sys::Win32::Storage::FileSystem::{ BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_READONLY, @@ -299,7 +299,7 @@ pub mod windows { FILE_INFO_BY_NAME_CLASS, *mut libc::c_void, u32, - ) -> BOOL, + ) -> i32, >, > = OnceLock::new(); diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index 58bfdf61d4f..fea24d226dd 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -19,7 +19,7 @@ mod _winapi { Win32::Foundation::{HANDLE, HINSTANCE, MAX_PATH}, core::PCWSTR, }; - use windows_sys::Win32::Foundation::{BOOL, INVALID_HANDLE_VALUE}; + use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; #[pyattr] use windows_sys::Win32::{ @@ -36,14 +36,19 @@ mod _winapi { LCMAP_TRADITIONAL_CHINESE, LCMAP_UPPERCASE, }, Storage::FileSystem::{ - COPYFILE2_CALLBACK_CHUNK_FINISHED, COPYFILE2_CALLBACK_CHUNK_STARTED, - COPYFILE2_CALLBACK_ERROR, COPYFILE2_CALLBACK_POLL_CONTINUE, - COPYFILE2_CALLBACK_STREAM_FINISHED, COPYFILE2_CALLBACK_STREAM_STARTED, - COPYFILE2_PROGRESS_CANCEL, COPYFILE2_PROGRESS_CONTINUE, COPYFILE2_PROGRESS_PAUSE, - COPYFILE2_PROGRESS_QUIET, COPYFILE2_PROGRESS_STOP, FILE_FLAG_FIRST_PIPE_INSTANCE, - FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_TYPE_CHAR, - FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_REMOTE, FILE_TYPE_UNKNOWN, OPEN_EXISTING, - PIPE_ACCESS_DUPLEX, PIPE_ACCESS_INBOUND, SYNCHRONIZE, + COPY_FILE_ALLOW_DECRYPTED_DESTINATION, COPY_FILE_COPY_SYMLINK, + COPY_FILE_FAIL_IF_EXISTS, COPY_FILE_NO_BUFFERING, COPY_FILE_NO_OFFLOAD, + COPY_FILE_OPEN_SOURCE_FOR_WRITE, COPY_FILE_REQUEST_COMPRESSED_TRAFFIC, + COPY_FILE_REQUEST_SECURITY_PRIVILEGES, COPY_FILE_RESTARTABLE, + COPY_FILE_RESUME_FROM_PAUSE, COPYFILE2_CALLBACK_CHUNK_FINISHED, + COPYFILE2_CALLBACK_CHUNK_STARTED, COPYFILE2_CALLBACK_ERROR, + COPYFILE2_CALLBACK_POLL_CONTINUE, COPYFILE2_CALLBACK_STREAM_FINISHED, + COPYFILE2_CALLBACK_STREAM_STARTED, COPYFILE2_PROGRESS_CANCEL, + COPYFILE2_PROGRESS_CONTINUE, COPYFILE2_PROGRESS_PAUSE, COPYFILE2_PROGRESS_QUIET, + COPYFILE2_PROGRESS_STOP, FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, + FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_TYPE_CHAR, FILE_TYPE_DISK, FILE_TYPE_PIPE, + FILE_TYPE_REMOTE, FILE_TYPE_UNKNOWN, OPEN_EXISTING, PIPE_ACCESS_DUPLEX, + PIPE_ACCESS_INBOUND, SYNCHRONIZE, }, System::{ Console::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}, @@ -65,13 +70,6 @@ mod _winapi { IDLE_PRIORITY_CLASS, INFINITE, NORMAL_PRIORITY_CLASS, PROCESS_DUP_HANDLE, REALTIME_PRIORITY_CLASS, STARTF_USESHOWWINDOW, STARTF_USESTDHANDLES, }, - WindowsProgramming::{ - COPY_FILE_ALLOW_DECRYPTED_DESTINATION, COPY_FILE_COPY_SYMLINK, - COPY_FILE_FAIL_IF_EXISTS, COPY_FILE_NO_BUFFERING, COPY_FILE_NO_OFFLOAD, - COPY_FILE_OPEN_SOURCE_FOR_WRITE, COPY_FILE_REQUEST_COMPRESSED_TRAFFIC, - COPY_FILE_REQUEST_SECURITY_PRIVILEGES, COPY_FILE_RESTARTABLE, - COPY_FILE_RESUME_FROM_PAUSE, - }, }, UI::WindowsAndMessaging::SW_HIDE, }; @@ -80,7 +78,7 @@ mod _winapi { const NULL: isize = 0; #[pyfunction] - fn CloseHandle(handle: HANDLE) -> WindowsSysResult<BOOL> { + fn CloseHandle(handle: HANDLE) -> WindowsSysResult<i32> { WindowsSysResult(unsafe { windows_sys::Win32::Foundation::CloseHandle(handle.0 as _) }) } @@ -128,7 +126,7 @@ mod _winapi { src: HANDLE, target_process: HANDLE, access: u32, - inherit: BOOL, + inherit: i32, options: OptionalArg<u32>, vm: &VirtualMachine, ) -> PyResult<HANDLE> { @@ -299,7 +297,7 @@ mod _winapi { unsafe { windows_sys::Win32::System::Threading::OpenProcess( desired_access, - BOOL::from(inherit_handle), + i32::from(inherit_handle), process_id, ) as _ } @@ -473,7 +471,7 @@ mod _winapi { } #[pyfunction] - fn TerminateProcess(h: HANDLE, exit_code: u32) -> WindowsSysResult<BOOL> { + fn TerminateProcess(h: HANDLE, exit_code: u32) -> WindowsSysResult<i32> { WindowsSysResult(unsafe { windows_sys::Win32::System::Threading::TerminateProcess(h.0 as _, exit_code) }) @@ -513,7 +511,7 @@ mod _winapi { let handle = unsafe { windows_sys::Win32::System::Threading::OpenMutexW( desired_access, - BOOL::from(inherit_handle), + i32::from(inherit_handle), windows_sys::core::PCWSTR::from(name as _), ) }; @@ -524,7 +522,7 @@ mod _winapi { } #[pyfunction] - fn ReleaseMutex(handle: isize) -> WindowsSysResult<BOOL> { + fn ReleaseMutex(handle: isize) -> WindowsSysResult<i32> { WindowsSysResult(unsafe { windows_sys::Win32::System::Threading::ReleaseMutex(handle as _) }) diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index 396f60a2bc2..a7619025866 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -1149,7 +1149,7 @@ mod winreg { #[pyfunction] fn QueryReflectionKey(key: PyRef<PyHkey>, vm: &VirtualMachine) -> PyResult<bool> { - let mut result: windows_sys::Win32::Foundation::BOOL = 0; + let mut result: i32 = 0; let res = unsafe { Registry::RegQueryReflectionKey(key.hkey.load(), &mut result) }; if res == 0 { Ok(result != 0) diff --git a/crates/vm/src/windows.rs b/crates/vm/src/windows.rs index 26fec850215..6825ee5841b 100644 --- a/crates/vm/src/windows.rs +++ b/crates/vm/src/windows.rs @@ -9,7 +9,7 @@ use crate::{ }; use std::ffi::OsStr; use windows::Win32::Foundation::HANDLE; -use windows_sys::Win32::Foundation::{BOOL, HANDLE as RAW_HANDLE, INVALID_HANDLE_VALUE}; +use windows_sys::Win32::Foundation::{HANDLE as RAW_HANDLE, INVALID_HANDLE_VALUE}; pub(crate) trait WindowsSysResultValue { type Ok: ToPyObject; @@ -27,7 +27,8 @@ impl WindowsSysResultValue for RAW_HANDLE { } } -impl WindowsSysResultValue for BOOL { +// BOOL is i32 in windows-sys 0.61+ +impl WindowsSysResultValue for i32 { type Ok = (); fn is_err(&self) -> bool { *self == 0 From 638a408effc3af33aad1ca937b2c933fc6faa793 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 01:54:25 +0900 Subject: [PATCH 423/819] Bump malachite-* from 0.6.1 to 0.8.0 (#6344) * Bump malachite-base from 0.6.1 to 0.8.0 Bumps [malachite-base](https://github.com/mhogrefe/malachite) from 0.6.1 to 0.8.0. - [Release notes](https://github.com/mhogrefe/malachite/releases) - [Commits](https://github.com/mhogrefe/malachite/compare/v0.6.1...v0.8.0) --- updated-dependencies: - dependency-name: malachite-base dependency-version: 0.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Bump malachite-* --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jeong, YunWon <jeong@youknowone.org> --- Cargo.lock | 36 ++++++++++++++++++------------------ Cargo.toml | 6 +++--- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f839258a61c..790f777c21f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1234,9 +1234,9 @@ dependencies = [ [[package]] name = "foldhash" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "foreign-types" @@ -1369,15 +1369,15 @@ name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash", +] [[package]] name = "heck" @@ -1800,11 +1800,11 @@ dependencies = [ [[package]] name = "malachite-base" -version = "0.6.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c738d3789301e957a8f7519318fcbb1b92bb95863b28f6938ae5a05be6259f34" +checksum = "c0c91cb6071ed9ac48669d3c79bd2792db596c7e542dbadd217b385bb359f42d" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", "itertools 0.14.0", "libm", "ryu", @@ -1812,9 +1812,9 @@ dependencies = [ [[package]] name = "malachite-bigint" -version = "0.6.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f46b904a4725706c5ad0133b662c20b388a3ffb04bda5154029dcb0cd28ae34" +checksum = "7ff3af5010102f29f2ef4ee6f7b1c5b3f08a6c261b5164e01c41cf43772b6f90" dependencies = [ "malachite-base", "malachite-nz", @@ -1825,9 +1825,9 @@ dependencies = [ [[package]] name = "malachite-nz" -version = "0.6.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1707c9a1fa36ce21749b35972bfad17bbf34cf5a7c96897c0491da321e387d3b" +checksum = "1d9ecf4dd76246fd622de4811097966106aa43f9cd7cc36cb85e774fe84c8adc" dependencies = [ "itertools 0.14.0", "libm", @@ -1837,9 +1837,9 @@ dependencies = [ [[package]] name = "malachite-q" -version = "0.6.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d764801aa4e96bbb69b389dcd03b50075345131cd63ca2e380bca71cc37a3675" +checksum = "b7bc9d9adf5b0a7999d84f761c809bec3dc46fe983e4de547725d2b7730462a0" dependencies = [ "itertools 0.14.0", "malachite-base", @@ -3333,9 +3333,9 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "safe_arch" -version = "0.7.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +checksum = "629516c85c29fe757770fa03f2074cf1eac43d44c02a3de9fc2ef7b0e207dfdd" dependencies = [ "bytemuck", ] @@ -4302,9 +4302,9 @@ dependencies = [ [[package]] name = "wide" -version = "0.7.33" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +checksum = "13ca908d26e4786149c48efcf6c0ea09ab0e06d1fe3c17dc1b4b0f1ca4a7e788" dependencies = [ "bytemuck", "safe_arch", diff --git a/Cargo.toml b/Cargo.toml index f990b636360..e4f68c77068 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,9 +179,9 @@ libc = "0.2.177" libffi = "4.1" log = "0.4.28" nix = { version = "0.30", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] } -malachite-bigint = "0.6" -malachite-q = "0.6" -malachite-base = "0.6" +malachite-bigint = "0.8" +malachite-q = "0.8" +malachite-base = "0.8" memchr = "2.7.4" num-complex = "0.4.6" num-integer = "0.1.46" From 4438dba49cebd8c46e46ad7598f532e062199d05 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Tue, 9 Dec 2025 00:32:10 +0900 Subject: [PATCH 424/819] use ToWide --- crates/common/src/fileutils.rs | 4 +--- crates/vm/src/stdlib/codecs.rs | 8 ++++---- crates/vm/src/stdlib/nt.rs | 10 +++------- crates/vm/src/stdlib/sys.rs | 13 +++---------- crates/vm/src/windows.rs | 7 +++---- 5 files changed, 14 insertions(+), 28 deletions(-) diff --git a/crates/common/src/fileutils.rs b/crates/common/src/fileutils.rs index 3e03f0299ad..a1c6eec8178 100644 --- a/crates/common/src/fileutils.rs +++ b/crates/common/src/fileutils.rs @@ -26,7 +26,6 @@ pub mod windows { use crate::windows::ToWideString; use libc::{S_IFCHR, S_IFDIR, S_IFMT}; use std::ffi::{CString, OsStr, OsString}; - use std::os::windows::ffi::OsStrExt; use std::os::windows::io::AsRawHandle; use std::sync::OnceLock; use windows_sys::Win32::Foundation::{ @@ -75,8 +74,7 @@ pub mod windows { pub fn update_st_mode_from_path(&mut self, path: &OsStr, attr: u32) { if attr & FILE_ATTRIBUTE_DIRECTORY == 0 { let file_extension = path - .encode_wide() - .collect::<Vec<u16>>() + .to_wide() .split(|&c| c == '.' as u16) .next_back() .and_then(|s| String::from_utf16(s).ok()); diff --git a/crates/vm/src/stdlib/codecs.rs b/crates/vm/src/stdlib/codecs.rs index dee5b17b822..5f1b721dfb4 100644 --- a/crates/vm/src/stdlib/codecs.rs +++ b/crates/vm/src/stdlib/codecs.rs @@ -237,7 +237,7 @@ mod _codecs { #[cfg(windows)] #[pyfunction] fn mbcs_encode(args: MbcsEncodeArgs, vm: &VirtualMachine) -> PyResult<(Vec<u8>, usize)> { - use std::os::windows::ffi::OsStrExt; + use crate::common::windows::ToWideString; use windows_sys::Win32::Globalization::{ CP_ACP, WC_NO_BEST_FIT_CHARS, WideCharToMultiByte, }; @@ -259,7 +259,7 @@ mod _codecs { } // Convert UTF-8 string to UTF-16 - let wide: Vec<u16> = std::ffi::OsStr::new(s).encode_wide().collect(); + let wide: Vec<u16> = std::ffi::OsStr::new(s).to_wide(); // Get the required buffer size let size = unsafe { @@ -439,7 +439,7 @@ mod _codecs { #[cfg(windows)] #[pyfunction] fn oem_encode(args: OemEncodeArgs, vm: &VirtualMachine) -> PyResult<(Vec<u8>, usize)> { - use std::os::windows::ffi::OsStrExt; + use crate::common::windows::ToWideString; use windows_sys::Win32::Globalization::{ CP_OEMCP, WC_NO_BEST_FIT_CHARS, WideCharToMultiByte, }; @@ -461,7 +461,7 @@ mod _codecs { } // Convert UTF-8 string to UTF-16 - let wide: Vec<u16> = std::ffi::OsStr::new(s).encode_wide().collect(); + let wide: Vec<u16> = std::ffi::OsStr::new(s).to_wide(); // Get the required buffer size let size = unsafe { diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 2304a15e4fd..4a1a13c9014 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -15,7 +15,7 @@ pub(crate) mod module { use crate::{ PyResult, TryFromObject, VirtualMachine, builtins::{PyBaseExceptionRef, PyDictRef, PyListRef, PyStrRef, PyTupleRef}, - common::{crt_fd, os::last_os_error, suppress_iph}, + common::{crt_fd, os::last_os_error, suppress_iph, windows::ToWideString}, convert::ToPyException, function::{Either, OptionalArg}, ospath::OsPath, @@ -23,11 +23,7 @@ pub(crate) mod module { }; use libc::intptr_t; use std::os::windows::io::AsRawHandle; - use std::{ - env, fs, io, - mem::MaybeUninit, - os::windows::ffi::{OsStrExt, OsStringExt}, - }; + use std::{env, fs, io, mem::MaybeUninit, os::windows::ffi::OsStringExt}; use windows_sys::Win32::{ Foundation::{self, INVALID_HANDLE_VALUE}, Storage::FileSystem, @@ -541,7 +537,7 @@ pub(crate) mod module { #[pyfunction] fn _path_splitroot(path: OsPath, vm: &VirtualMachine) -> PyResult<(String, String)> { - let orig: Vec<_> = path.path.encode_wide().collect(); + let orig: Vec<_> = path.path.to_wide(); if orig.is_empty() { return Ok(("".to_owned(), "".to_owned())); } diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 602fdff1eb1..53ad06dd4e9 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -12,6 +12,7 @@ mod sys { common::{ ascii, hash::{PyHash, PyUHash}, + windows::ToWideString, }, convert::ToPyObject, frame::FrameRef, @@ -22,8 +23,6 @@ mod sys { vm::{Settings, VirtualMachine}, }; use num_traits::ToPrimitive; - #[cfg(windows)] - use std::os::windows::ffi::OsStrExt; use std::{ env::{self, VarError}, io::Read, @@ -553,10 +552,7 @@ mod sys { fn get_kernel32_version() -> std::io::Result<(u32, u32, u32)> { unsafe { // Create a wide string for "kernel32.dll" - let module_name: Vec<u16> = std::ffi::OsStr::new("kernel32.dll") - .encode_wide() - .chain(Some(0)) - .collect(); + let module_name: Vec<u16> = std::ffi::OsStr::new("kernel32.dll").to_wide_with_nul(); let h_kernel32 = GetModuleHandleW(module_name.as_ptr()); if h_kernel32.is_null() { return Err(std::io::Error::last_os_error()); @@ -593,10 +589,7 @@ mod sys { } // Prepare an empty sub-block string (L"") as required by VerQueryValueW - let sub_block: Vec<u16> = std::ffi::OsStr::new("") - .encode_wide() - .chain(Some(0)) - .collect(); + let sub_block: Vec<u16> = std::ffi::OsStr::new("").to_wide_with_nul(); let mut ffi_ptr: *mut VS_FIXEDFILEINFO = std::ptr::null_mut(); let mut ffi_len: u32 = 0; diff --git a/crates/vm/src/windows.rs b/crates/vm/src/windows.rs index 6825ee5841b..d91c436db6c 100644 --- a/crates/vm/src/windows.rs +++ b/crates/vm/src/windows.rs @@ -7,6 +7,7 @@ use crate::{ convert::{ToPyObject, ToPyResult}, stdlib::os::errno_err, }; +use rustpython_common::windows::ToWideString; use std::ffi::OsStr; use windows::Win32::Foundation::HANDLE; use windows_sys::Win32::Foundation::{HANDLE as RAW_HANDLE, INVALID_HANDLE_VALUE}; @@ -235,13 +236,12 @@ fn attributes_from_dir( windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION, u32, )> { - use std::os::windows::ffi::OsStrExt; use windows_sys::Win32::Storage::FileSystem::{ BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_REPARSE_POINT, FindClose, FindFirstFileW, WIN32_FIND_DATAW, }; - let wide: Vec<u16> = path.encode_wide().chain(std::iter::once(0)).collect(); + let wide: Vec<u16> = path.to_wide_with_nul(); let mut find_data: WIN32_FIND_DATAW = unsafe { std::mem::zeroed() }; let handle = unsafe { FindFirstFileW(wide.as_ptr(), &mut find_data) }; @@ -269,7 +269,6 @@ fn attributes_from_dir( /// Ported from win32_xstat_slow_impl fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatStruct> { - use std::os::windows::ffi::OsStrExt; use windows_sys::Win32::{ Foundation::{ CloseHandle, ERROR_ACCESS_DENIED, ERROR_CANT_ACCESS_FILE, ERROR_INVALID_FUNCTION, @@ -287,7 +286,7 @@ fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatSt }, }; - let wide: Vec<u16> = path.encode_wide().chain(std::iter::once(0)).collect(); + let wide: Vec<u16> = path.to_wide_with_nul(); let access = FILE_READ_ATTRIBUTES; let mut flags = FILE_FLAG_BACKUP_SEMANTICS; From 649606fc580b2a43a7c5ca6efccf45896357b590 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Tue, 9 Dec 2025 00:34:25 +0900 Subject: [PATCH 425/819] Unconstructible --- crates/vm/src/stdlib/os.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 45d4e41bcba..2620fcb2755 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -168,7 +168,7 @@ pub(super) mod _os { ospath::{IOErrorBuilder, OsPath, OsPathOrFd, OutputMode}, protocol::PyIterReturn, recursion::ReprGuard, - types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter}, + types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter, Unconstructible}, utils::ToCString, vm::VirtualMachine, }; @@ -474,7 +474,7 @@ pub(super) mod _os { ino: AtomicCell<Option<u64>>, } - #[pyclass(with(Representable))] + #[pyclass(with(Representable, Unconstructible))] impl DirEntry { #[pygetset] fn name(&self, vm: &VirtualMachine) -> PyResult { @@ -652,6 +652,7 @@ pub(super) mod _os { } } } + impl Unconstructible for DirEntry {} #[pyattr] #[pyclass(name = "ScandirIter")] @@ -661,7 +662,7 @@ pub(super) mod _os { mode: OutputMode, } - #[pyclass(with(IterNext, Iterable))] + #[pyclass(with(IterNext, Iterable, Unconstructible))] impl ScandirIterator { #[pymethod] fn close(&self) { @@ -679,6 +680,7 @@ pub(super) mod _os { zelf.close() } } + impl Unconstructible for ScandirIterator {} impl SelfIter for ScandirIterator {} impl IterNext for ScandirIterator { fn next(zelf: &crate::Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> { From 42d0a583e8cc7c0a66ed4e8693397c718f0874e2 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Tue, 9 Dec 2025 00:33:02 +0900 Subject: [PATCH 426/819] fix remove --- Lib/test/test_os.py | 8 -------- Lib/test/test_shutil.py | 1 - crates/vm/src/stdlib/os.rs | 29 +++++++++++++++++++++++++---- crates/vm/src/stdlib/sys.rs | 2 +- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index e751b6423d6..a19366039ef 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -736,7 +736,6 @@ def test_file_attributes(self): result.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY, stat.FILE_ATTRIBUTE_DIRECTORY) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.stat (PermissionError: [Errno 5] Access is denied.)') @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") def test_access_denied(self): # Default to FindFirstFile WIN32_FIND_DATA when access is @@ -759,7 +758,6 @@ def test_access_denied(self): self.assertNotEqual(result.st_size, 0) self.assertTrue(os.path.isfile(fname)) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.stat (PermissionError: [Errno 1] Incorrect function.)') @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") def test_stat_block_device(self): # bpo-38030: os.stat fails for block devices @@ -1796,7 +1794,6 @@ def test_mode(self): self.assertEqual(os.stat(path).st_mode & 0o777, 0o555) self.assertEqual(os.stat(parent).st_mode & 0o777, 0o775) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.umask not implemented yet for all platforms') @unittest.skipIf( support.is_emscripten or support.is_wasi, "Emscripten's/WASI's umask is a stub." @@ -2116,7 +2113,6 @@ def test_urandom_fd_closed(self): """ rc, out, err = assert_python_ok('-Sc', code) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (ModuleNotFoundError: No module named 'os'") def test_urandom_fd_reopened(self): # Issue #21207: urandom() should detect its fd to /dev/urandom # changed to something else, and reopen it. @@ -3221,7 +3217,6 @@ def test_getfinalpathname_handles(self): self.assertEqual(0, handle_delta) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.stat (PermissionError: [Errno 5] Access is denied.)') @support.requires_subprocess() def test_stat_unlink_race(self): # bpo-46785: the implementation of os.stat() falls back to reading @@ -4927,7 +4922,6 @@ def setUp(self): self.addCleanup(os_helper.rmtree, self.path) os.mkdir(self.path) - @unittest.expectedFailure # TODO: RUSTPYTHON; (AssertionError: TypeError not raised by DirEntry) def test_uninstantiable(self): self.assertRaises(TypeError, os.DirEntry) @@ -4976,7 +4970,6 @@ def assert_stat_equal(self, stat1, stat2, skip_fields): else: self.assertEqual(stat1, stat2) - @unittest.expectedFailure # TODO: RUSTPYTHON; (AssertionError: TypeError not raised by ScandirIter) def test_uninstantiable(self): scandir_iter = os.scandir(self.path) self.assertRaises(TypeError, type(scandir_iter)) @@ -5230,7 +5223,6 @@ def test_fd(self): st = os.stat(entry.name, dir_fd=fd, follow_symlinks=False) self.assertEqual(entry.stat(follow_symlinks=False), st) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: FileNotFoundError not raised by scandir)') @unittest.skipIf(support.is_wasi, "WASI maps '' to cwd") def test_empty_path(self): self.assertRaises(FileNotFoundError, os.scandir, '') diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 124430c8922..0abf7ca61a1 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -235,7 +235,6 @@ def onexc(*args): self.assertEqual(errors[0][1], link) self.assertIsInstance(errors[0][2], OSError) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_rmtree_works_on_symlinks(self): tmp = self.mkdtemp() diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 2620fcb2755..d6b10341da7 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -152,6 +152,8 @@ impl ToPyObject for crt_fd::Borrowed<'_> { #[pymodule(sub)] pub(super) mod _os { use super::{DirFd, FollowSymlinks, SupportFunc, errno_err}; + #[cfg(windows)] + use crate::common::windows::ToWideString; use crate::{ AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, builtins::{ @@ -292,10 +294,29 @@ pub(super) mod _os { #[pyfunction(name = "unlink")] fn remove(path: OsPath, dir_fd: DirFd<'_, 0>, vm: &VirtualMachine) -> PyResult<()> { let [] = dir_fd.0; - let is_junction = cfg!(windows) - && fs::metadata(&path).is_ok_and(|meta| meta.file_type().is_dir()) - && fs::symlink_metadata(&path).is_ok_and(|meta| meta.file_type().is_symlink()); - let res = if is_junction { + #[cfg(windows)] + let is_dir_link = { + // On Windows, we need to check if it's a directory symlink/junction + // using GetFileAttributesW, which doesn't follow symlinks. + // This is similar to CPython's Py_DeleteFileW. + use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_REPARSE_POINT, GetFileAttributesW, + INVALID_FILE_ATTRIBUTES, + }; + let wide_path: Vec<u16> = path.path.as_os_str().to_wide_with_nul(); + let attrs = unsafe { GetFileAttributesW(wide_path.as_ptr()) }; + if attrs != INVALID_FILE_ATTRIBUTES { + let is_dir = (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0; + let is_reparse = (attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + is_dir && is_reparse + } else { + false + } + }; + #[cfg(not(windows))] + let is_dir_link = false; + + let res = if is_dir_link { fs::remove_dir(&path) } else { fs::remove_file(&path) diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 53ad06dd4e9..45b1d566058 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -12,7 +12,6 @@ mod sys { common::{ ascii, hash::{PyHash, PyUHash}, - windows::ToWideString, }, convert::ToPyObject, frame::FrameRef, @@ -550,6 +549,7 @@ mod sys { #[cfg(windows)] fn get_kernel32_version() -> std::io::Result<(u32, u32, u32)> { + use crate::common::windows::ToWideString; unsafe { // Create a wide string for "kernel32.dll" let module_name: Vec<u16> = std::ffi::OsStr::new("kernel32.dll").to_wide_with_nul(); From abc5c223a6d1c1098499a8043187584c988d662d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 02:46:05 +0900 Subject: [PATCH 427/819] os.waitstatus_to_exitcode for windows (#6355) --- Lib/test/test_os.py | 2 -- crates/vm/src/stdlib/os.rs | 35 +++++++++++++++++++---------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index a19366039ef..df09d81ead0 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3426,7 +3426,6 @@ def test_waitstatus_to_exitcode(self): with self.assertRaises(TypeError): os.waitstatus_to_exitcode(0.0) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.spawnv not implemented yet for all platforms') @unittest.skipUnless(sys.platform == 'win32', 'win32-specific test') def test_waitpid_windows(self): # bpo-40138: test os.waitpid() and os.waitstatus_to_exitcode() @@ -3435,7 +3434,6 @@ def test_waitpid_windows(self): code = f'import _winapi; _winapi.ExitProcess({STATUS_CONTROL_C_EXIT})' self.check_waitpid(code, exitcode=STATUS_CONTROL_C_EXIT) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (OverflowError: Python int too large to convert to Rust i32)') @unittest.skipUnless(sys.platform == 'win32', 'win32-specific test') def test_waitstatus_to_exitcode_windows(self): max_exitcode = 2 ** 32 - 1 diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index d6b10341da7..f65de9170f3 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1444,29 +1444,32 @@ pub(super) mod _os { Ok((loadavg[0], loadavg[1], loadavg[2])) } - #[cfg(any(unix, windows))] + #[cfg(unix)] #[pyfunction] fn waitstatus_to_exitcode(status: i32, vm: &VirtualMachine) -> PyResult<i32> { let status = u32::try_from(status) .map_err(|_| vm.new_value_error(format!("invalid WEXITSTATUS: {status}")))?; - cfg_if::cfg_if! { - if #[cfg(not(windows))] { - let status = status as libc::c_int; - if libc::WIFEXITED(status) { - return Ok(libc::WEXITSTATUS(status)); - } - - if libc::WIFSIGNALED(status) { - return Ok(-libc::WTERMSIG(status)); - } + let status = status as libc::c_int; + if libc::WIFEXITED(status) { + return Ok(libc::WEXITSTATUS(status)); + } - Err(vm.new_value_error(format!("Invalid wait status: {status}"))) - } else { - i32::try_from(status.rotate_right(8)) - .map_err(|_| vm.new_value_error(format!("invalid wait status: {status}"))) - } + if libc::WIFSIGNALED(status) { + return Ok(-libc::WTERMSIG(status)); } + + Err(vm.new_value_error(format!("Invalid wait status: {status}"))) + } + + #[cfg(windows)] + #[pyfunction] + fn waitstatus_to_exitcode(status: u64, vm: &VirtualMachine) -> PyResult<u32> { + let exitcode = status >> 8; + // ExitProcess() accepts an UINT type: + // reject exit code which doesn't fit in an UINT + u32::try_from(exitcode) + .map_err(|_| vm.new_value_error(format!("Invalid exit code: {exitcode}"))) } #[pyfunction] From a484ba4790d24223e9e9a6b545258175f8240292 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 02:49:36 +0900 Subject: [PATCH 428/819] st_file_attributes (#6353) --- Lib/test/test_os.py | 1 - Lib/test/test_site.py | 1 - crates/vm/src/stdlib/os.rs | 9 +++++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index df09d81ead0..9e422fd1629 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -714,7 +714,6 @@ def check_file_attributes(self, result): self.assertTrue(isinstance(result.st_file_attributes, int)) self.assertTrue(0 <= result.st_file_attributes <= 0xFFFFFFFF) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.stat return value doesnt have st_file_attributes attribute') @unittest.skipUnless(sys.platform == "win32", "st_file_attributes is Win32 specific") def test_file_attributes(self): diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 88ca66ac7ad..df279bd9652 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -223,7 +223,6 @@ def test_addsitedir_hidden_flags(self): finally: pth_file.cleanup() - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(sys.platform == 'win32', 'test needs Windows') @support.requires_subprocess() def test_addsitedir_hidden_file_attribute(self): diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index f65de9170f3..3218599442e 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -801,6 +801,9 @@ pub(super) mod _os { #[pyarg(any, default)] #[pystruct_sequence(skip)] pub st_reparse_tag: u32, + #[pyarg(any, default)] + #[pystruct_sequence(skip)] + pub st_file_attributes: u32, } impl StatResultData { @@ -835,6 +838,11 @@ pub(super) mod _os { #[cfg(not(windows))] let st_reparse_tag = 0; + #[cfg(windows)] + let st_file_attributes = stat.st_file_attributes; + #[cfg(not(windows))] + let st_file_attributes = 0; + Self { st_mode: vm.ctx.new_pyref(stat.st_mode), st_ino: vm.ctx.new_pyref(stat.st_ino), @@ -853,6 +861,7 @@ pub(super) mod _os { st_mtime_ns: to_ns(mtime), st_ctime_ns: to_ns(ctime), st_reparse_tag, + st_file_attributes, } } } From abfd148d639ef63397ba6a5891bb3e7782afd294 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 02:55:34 +0900 Subject: [PATCH 429/819] SSLError (#6351) --- crates/stdlib/src/ssl.rs | 24 +++++++++++++++++++----- crates/stdlib/src/ssl/compat.rs | 32 +++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 03909046c2a..b5f12c5a85c 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -1254,18 +1254,29 @@ mod _ssl { } else { // [SSL] PEM lib super::compat::SslError::create_ssl_error_with_reason( - vm, "SSL", "", "PEM lib", + vm, + Some("SSL"), + "", + "PEM lib", ) } } // PEM parsing errors - [SSL] PEM lib _ => super::compat::SslError::create_ssl_error_with_reason( - vm, "SSL", "", "PEM lib", + vm, + Some("SSL"), + "", + "PEM lib", ), } } else { // Unknown error type - [SSL] PEM lib - super::compat::SslError::create_ssl_error_with_reason(vm, "SSL", "", "PEM lib") + super::compat::SslError::create_ssl_error_with_reason( + vm, + Some("SSL"), + "", + "PEM lib", + ) } })?; @@ -1866,7 +1877,7 @@ mod _ssl { // [PEM: NO_START_LINE] no start line return Err(super::compat::SslError::create_ssl_error_with_reason( vm, - "PEM", + Some("PEM"), "NO_START_LINE", "[PEM: NO_START_LINE] no start line", )); @@ -2127,7 +2138,10 @@ mod _ssl { } // PEM parsing errors _ => super::compat::SslError::create_ssl_error_with_reason( - vm, "X509", "", "PEM lib", + vm, + Some("X509"), + "", + "PEM lib", ), } }) diff --git a/crates/stdlib/src/ssl/compat.rs b/crates/stdlib/src/ssl/compat.rs index e4f979968e4..f4c860fdbc1 100644 --- a/crates/stdlib/src/ssl/compat.rs +++ b/crates/stdlib/src/ssl/compat.rs @@ -473,19 +473,25 @@ impl SslError { /// If attribute setting fails (extremely rare), returns the exception without attributes pub(super) fn create_ssl_error_with_reason( vm: &VirtualMachine, - library: &str, + library: Option<&str>, reason: &str, message: impl Into<String>, ) -> PyBaseExceptionRef { - let exc = vm.new_exception_msg(PySSLError::class(&vm.ctx).to_owned(), message.into()); + let msg = message.into(); + // SSLError args should be (errno, message) format + // FIXME: Use 1 as generic SSL error code + let exc = vm.new_exception( + PySSLError::class(&vm.ctx).to_owned(), + vec![vm.new_pyobj(1i32), vm.new_pyobj(msg)], + ); // Set library and reason attributes // Ignore errors as they're extremely rare (e.g., out of memory) - let _ = exc.as_object().set_attr( - "library", - vm.ctx.new_str(library).as_object().to_owned(), - vm, - ); + let library_obj = match library { + Some(lib) => vm.ctx.new_str(lib).as_object().to_owned(), + None => vm.ctx.none(), + }; + let _ = exc.as_object().set_attr("library", library_obj, vm); let _ = exc.as_object() .set_attr("reason", vm.ctx.new_str(reason).as_object().to_owned(), vm); @@ -524,7 +530,12 @@ impl SslError { .unwrap_or("UNKNOWN"); // Delegate to create_ssl_error_with_reason for actual exception creation - Self::create_ssl_error_with_reason(vm, lib_str, reason_str, format!("[SSL] {reason_str}")) + Self::create_ssl_error_with_reason( + vm, + Some(lib_str), + reason_str, + format!("[SSL] {reason_str}"), + ) } /// Convert to Python exception @@ -533,7 +544,10 @@ impl SslError { SslError::WantRead => create_ssl_want_read_error(vm), SslError::WantWrite => create_ssl_want_write_error(vm), SslError::Timeout(msg) => timeout_error_msg(vm, msg), - SslError::Syscall(msg) => vm.new_os_error(msg), + SslError::Syscall(msg) => { + // Create SSLError with library=None for syscall errors during SSL operations + Self::create_ssl_error_with_reason(vm, None, &msg, msg.clone()) + } SslError::Ssl(msg) => vm.new_exception_msg( PySSLError::class(&vm.ctx).to_owned(), format!("SSL error: {msg}"), From bafaa1a826fefcfb40723f7f39d00935ebc9d8ee Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 04:32:06 +0900 Subject: [PATCH 430/819] scandir/lstat (#6357) --- Lib/test/test_os.py | 3 --- crates/vm/src/stdlib/os.rs | 27 +++++++++++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 9e422fd1629..d968aa87f08 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -4834,7 +4834,6 @@ class PathTConverterTests(unittest.TestCase): ('open', False, (os.O_RDONLY,), getattr(os, 'close', None)), ] - @unittest.expectedFailure # TODO: RUSTPYTHON; (AssertionError: TypeError not raised) def test_path_t_converter(self): str_filename = os_helper.TESTFN if os.name == 'nt': @@ -5109,7 +5108,6 @@ def test_fspath_protocol_bytes(self): self.assertEqual(fspath, os.path.join(os.fsencode(self.path),bytes_filename)) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; entry.is_dir() is False') def test_removed_dir(self): path = os.path.join(self.path, 'dir') @@ -5132,7 +5130,6 @@ def test_removed_dir(self): self.assertRaises(FileNotFoundError, entry.stat) self.assertRaises(FileNotFoundError, entry.stat, follow_symlinks=False) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; entry.is_file() is False') def test_removed_file(self): entry = self.create_file_entry() os.unlink(entry.path) diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 3218599442e..378cffd8315 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -720,13 +720,32 @@ pub(super) mod _os { #[cfg(not(unix))] let ino = None; + let pathval = entry.path(); + + // On Windows, pre-cache lstat from directory entry metadata + // This allows stat() to return cached data even if file is removed + #[cfg(windows)] + let lstat = { + let cell = OnceCell::new(); + if let Ok(stat_struct) = + crate::windows::win32_xstat(pathval.as_os_str(), false) + { + let stat_obj = + StatResultData::from_stat(&stat_struct, vm).to_pyobject(vm); + let _ = cell.set(stat_obj); + } + cell + }; + #[cfg(not(windows))] + let lstat = OnceCell::new(); + Ok(PyIterReturn::Return( DirEntry { file_name: entry.file_name(), - pathval: entry.path(), + pathval, file_type: entry.file_type(), mode: zelf.mode, - lstat: OnceCell::new(), + lstat, stat: OnceCell::new(), ino: AtomicCell::new(ino), } @@ -974,11 +993,11 @@ pub(super) mod _os { #[pyfunction] fn lstat( - file: OsPathOrFd<'_>, + file: OsPath, dir_fd: DirFd<'_, { STAT_DIR_FD as usize }>, vm: &VirtualMachine, ) -> PyResult { - stat(file, dir_fd, FollowSymlinks(false), vm) + stat(file.into(), dir_fd, FollowSymlinks(false), vm) } fn curdir_inner(vm: &VirtualMachine) -> PyResult<PathBuf> { From 7157697f96adb42a9340a9488b2875a97502dbda Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 04:41:44 +0900 Subject: [PATCH 431/819] last_posix_errno (#6341) --- crates/common/src/os.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/crates/common/src/os.rs b/crates/common/src/os.rs index e298db462a8..0aef70d5820 100644 --- a/crates/common/src/os.rs +++ b/crates/common/src/os.rs @@ -43,17 +43,12 @@ pub fn last_os_error() -> io::Error { #[cfg(windows)] pub fn last_posix_errno() -> i32 { - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(0) { - unsafe extern "C" { - fn _get_errno(pValue: *mut i32) -> i32; - } - let mut errno = 0; - unsafe { suppress_iph!(_get_errno(&mut errno)) }; - errno - } else { - err.posix_errno() + unsafe extern "C" { + fn _get_errno(pValue: *mut i32) -> i32; } + let mut errno = 0; + unsafe { suppress_iph!(_get_errno(&mut errno)) }; + errno } #[cfg(not(windows))] From b200f0e8d26c30a9739574923b0fb89999b2a4ee Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 05:08:45 +0900 Subject: [PATCH 432/819] Replace windows to windows-sys (#6356) --- Cargo.lock | 22 +----- crates/vm/Cargo.toml | 11 +-- crates/vm/src/stdlib/nt.rs | 37 +++++----- crates/vm/src/stdlib/time.rs | 9 +-- crates/vm/src/stdlib/winapi.rs | 130 ++++++++++++++++++--------------- crates/vm/src/windows.rs | 19 +++-- 6 files changed, 109 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 790f777c21f..12bc1af1162 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1433,7 +1433,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -3265,7 +3265,6 @@ dependencies = [ "wasm-bindgen", "which", "widestring", - "windows", "windows-sys 0.61.2", ] @@ -4347,25 +4346,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.62.2" diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 0e3f90a4613..502ace0126a 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -112,16 +112,6 @@ num_cpus = "1.17.0" [target.'cfg(windows)'.dependencies] junction = { workspace = true } -[target.'cfg(windows)'.dependencies.windows] -version = "0.52.0" -features = [ - "Win32_Foundation", - "Win32_System_LibraryLoader", - "Win32_System_Threading", - "Win32_System_Time", - "Win32_UI_Shell", -] - [target.'cfg(windows)'.dependencies.windows-sys] workspace = true features = [ @@ -143,6 +133,7 @@ features = [ "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", + "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 4a1a13c9014..c041dab9aa5 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -552,23 +552,26 @@ pub(crate) mod module { String::from_utf16(wstr).map_err(|e| vm.new_unicode_decode_error(e.to_string())) } - let wbuf = windows::core::PCWSTR::from_raw(backslashed.as_ptr()); - let (root, path) = match unsafe { windows::Win32::UI::Shell::PathCchSkipRoot(wbuf) } { - Ok(end) => { - assert!(!end.is_null()); - let len: usize = unsafe { end.as_ptr().offset_from(wbuf.as_ptr()) } - .try_into() - .expect("len must be non-negative"); - assert!( - len < backslashed.len(), // backslashed is null-terminated - "path: {:?} {} < {}", - std::path::PathBuf::from(std::ffi::OsString::from_wide(&backslashed)), - len, - backslashed.len() - ); - (from_utf16(&orig[..len], vm)?, from_utf16(&orig[len..], vm)?) - } - Err(_) => ("".to_owned(), from_utf16(&orig, vm)?), + let mut end: *const u16 = std::ptr::null(); + let hr = unsafe { + windows_sys::Win32::UI::Shell::PathCchSkipRoot(backslashed.as_ptr(), &mut end) + }; + let (root, path) = if hr == 0 { + // S_OK + assert!(!end.is_null()); + let len: usize = unsafe { end.offset_from(backslashed.as_ptr()) } + .try_into() + .expect("len must be non-negative"); + assert!( + len < backslashed.len(), // backslashed is null-terminated + "path: {:?} {} < {}", + std::path::PathBuf::from(std::ffi::OsString::from_wide(&backslashed)), + len, + backslashed.len() + ); + (from_utf16(&orig[..len], vm)?, from_utf16(&orig[len..], vm)?) + } else { + ("".to_owned(), from_utf16(&orig, vm)?) }; Ok((root, path)) } diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index ef4fbbea7f8..212697b7a93 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -46,7 +46,7 @@ mod decl { use std::time::Duration; #[cfg(target_env = "msvc")] #[cfg(not(target_arch = "wasm32"))] - use windows::Win32::System::Time; + use windows_sys::Win32::System::Time::{GetTimeZoneInformation, TIME_ZONE_INFORMATION}; #[allow(dead_code)] pub(super) const SEC_TO_MS: i64 = 1000; @@ -186,10 +186,9 @@ mod decl { #[cfg(target_env = "msvc")] #[cfg(not(target_arch = "wasm32"))] - fn get_tz_info() -> Time::TIME_ZONE_INFORMATION { - let mut info = Time::TIME_ZONE_INFORMATION::default(); - let info_ptr = &mut info as *mut Time::TIME_ZONE_INFORMATION; - let _ = unsafe { Time::GetTimeZoneInformation(info_ptr) }; + fn get_tz_info() -> TIME_ZONE_INFORMATION { + let mut info: TIME_ZONE_INFORMATION = unsafe { std::mem::zeroed() }; + unsafe { GetTimeZoneInformation(&mut info) }; info } diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index fea24d226dd..6b6e452b384 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -12,14 +12,10 @@ mod _winapi { convert::{ToPyException, ToPyResult}, function::{ArgMapping, ArgSequence, OptionalArg}, stdlib::os::errno_err, - windows::WindowsSysResult, + windows::{WinHandle, WindowsSysResult}, }; use std::ptr::{null, null_mut}; - use windows::{ - Win32::Foundation::{HANDLE, HINSTANCE, MAX_PATH}, - core::PCWSTR, - }; - use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; + use windows_sys::Win32::Foundation::{INVALID_HANDLE_VALUE, MAX_PATH}; #[pyattr] use windows_sys::Win32::{ @@ -78,15 +74,15 @@ mod _winapi { const NULL: isize = 0; #[pyfunction] - fn CloseHandle(handle: HANDLE) -> WindowsSysResult<i32> { - WindowsSysResult(unsafe { windows_sys::Win32::Foundation::CloseHandle(handle.0 as _) }) + fn CloseHandle(handle: WinHandle) -> WindowsSysResult<i32> { + WindowsSysResult(unsafe { windows_sys::Win32::Foundation::CloseHandle(handle.0) }) } #[pyfunction] fn GetStdHandle( std_handle: windows_sys::Win32::System::Console::STD_HANDLE, vm: &VirtualMachine, - ) -> PyResult<Option<HANDLE>> { + ) -> PyResult<Option<WinHandle>> { let handle = unsafe { windows_sys::Win32::System::Console::GetStdHandle(std_handle) }; if handle == INVALID_HANDLE_VALUE { return Err(errno_err(vm)); @@ -95,7 +91,7 @@ mod _winapi { // NULL handle - return None None } else { - Some(HANDLE(handle as isize)) + Some(WinHandle(handle)) }) } @@ -104,39 +100,41 @@ mod _winapi { _pipe_attrs: PyObjectRef, size: u32, vm: &VirtualMachine, - ) -> PyResult<(HANDLE, HANDLE)> { + ) -> PyResult<(WinHandle, WinHandle)> { + use windows_sys::Win32::Foundation::HANDLE; let (read, write) = unsafe { - let mut read = std::mem::MaybeUninit::<isize>::uninit(); - let mut write = std::mem::MaybeUninit::<isize>::uninit(); + let mut read = std::mem::MaybeUninit::<HANDLE>::uninit(); + let mut write = std::mem::MaybeUninit::<HANDLE>::uninit(); WindowsSysResult(windows_sys::Win32::System::Pipes::CreatePipe( - read.as_mut_ptr() as _, - write.as_mut_ptr() as _, + read.as_mut_ptr(), + write.as_mut_ptr(), std::ptr::null(), size, )) .to_pyresult(vm)?; (read.assume_init(), write.assume_init()) }; - Ok((HANDLE(read), HANDLE(write))) + Ok((WinHandle(read), WinHandle(write))) } #[pyfunction] fn DuplicateHandle( - src_process: HANDLE, - src: HANDLE, - target_process: HANDLE, + src_process: WinHandle, + src: WinHandle, + target_process: WinHandle, access: u32, inherit: i32, options: OptionalArg<u32>, vm: &VirtualMachine, - ) -> PyResult<HANDLE> { + ) -> PyResult<WinHandle> { + use windows_sys::Win32::Foundation::HANDLE; let target = unsafe { - let mut target = std::mem::MaybeUninit::<isize>::uninit(); + let mut target = std::mem::MaybeUninit::<HANDLE>::uninit(); WindowsSysResult(windows_sys::Win32::Foundation::DuplicateHandle( - src_process.0 as _, - src.0 as _, - target_process.0 as _, - target.as_mut_ptr() as _, + src_process.0, + src.0, + target_process.0, + target.as_mut_ptr(), access, inherit, options.unwrap_or(0), @@ -144,7 +142,7 @@ mod _winapi { .to_pyresult(vm)?; target.assume_init() }; - Ok(HANDLE(target)) + Ok(WinHandle(target)) } #[pyfunction] @@ -153,16 +151,16 @@ mod _winapi { } #[pyfunction] - fn GetCurrentProcess() -> HANDLE { - unsafe { windows::Win32::System::Threading::GetCurrentProcess() } + fn GetCurrentProcess() -> WinHandle { + WinHandle(unsafe { windows_sys::Win32::System::Threading::GetCurrentProcess() }) } #[pyfunction] fn GetFileType( - h: HANDLE, + h: WinHandle, vm: &VirtualMachine, ) -> PyResult<windows_sys::Win32::Storage::FileSystem::FILE_TYPE> { - let file_type = unsafe { windows_sys::Win32::Storage::FileSystem::GetFileType(h.0 as _) }; + let file_type = unsafe { windows_sys::Win32::Storage::FileSystem::GetFileType(h.0) }; if file_type == 0 && unsafe { windows_sys::Win32::Foundation::GetLastError() } != 0 { Err(errno_err(vm)) } else { @@ -206,7 +204,7 @@ mod _winapi { fn CreateProcess( args: CreateProcessArgs, vm: &VirtualMachine, - ) -> PyResult<(HANDLE, HANDLE, u32, u32)> { + ) -> PyResult<(WinHandle, WinHandle, u32, u32)> { let mut si: windows_sys::Win32::System::Threading::STARTUPINFOEXW = unsafe { std::mem::zeroed() }; si.StartupInfo.cb = std::mem::size_of_val(&si) as _; @@ -285,22 +283,31 @@ mod _winapi { }; Ok(( - HANDLE(procinfo.hProcess as _), - HANDLE(procinfo.hThread as _), + WinHandle(procinfo.hProcess), + WinHandle(procinfo.hThread), procinfo.dwProcessId, procinfo.dwThreadId, )) } #[pyfunction] - fn OpenProcess(desired_access: u32, inherit_handle: bool, process_id: u32) -> isize { - unsafe { + fn OpenProcess( + desired_access: u32, + inherit_handle: bool, + process_id: u32, + vm: &VirtualMachine, + ) -> PyResult<WinHandle> { + let handle = unsafe { windows_sys::Win32::System::Threading::OpenProcess( desired_access, i32::from(inherit_handle), process_id, - ) as _ + ) + }; + if handle.is_null() { + return Err(errno_err(vm)); } + Ok(WinHandle(handle)) } #[pyfunction] @@ -434,7 +441,7 @@ mod _winapi { 0, (2 & 0xffff) | 0x20000, // PROC_THREAD_ATTRIBUTE_HANDLE_LIST handlelist.as_mut_ptr() as _, - (handlelist.len() * std::mem::size_of::<HANDLE>()) as _, + (handlelist.len() * std::mem::size_of::<usize>()) as _, std::ptr::null_mut(), std::ptr::null(), ) @@ -447,9 +454,8 @@ mod _winapi { } #[pyfunction] - fn WaitForSingleObject(h: HANDLE, ms: u32, vm: &VirtualMachine) -> PyResult<u32> { - let ret = - unsafe { windows_sys::Win32::System::Threading::WaitForSingleObject(h.0 as _, ms) }; + fn WaitForSingleObject(h: WinHandle, ms: u32, vm: &VirtualMachine) -> PyResult<u32> { + let ret = unsafe { windows_sys::Win32::System::Threading::WaitForSingleObject(h.0, ms) }; if ret == windows_sys::Win32::Foundation::WAIT_FAILED { Err(errno_err(vm)) } else { @@ -458,11 +464,11 @@ mod _winapi { } #[pyfunction] - fn GetExitCodeProcess(h: HANDLE, vm: &VirtualMachine) -> PyResult<u32> { + fn GetExitCodeProcess(h: WinHandle, vm: &VirtualMachine) -> PyResult<u32> { unsafe { let mut ec = std::mem::MaybeUninit::uninit(); WindowsSysResult(windows_sys::Win32::System::Threading::GetExitCodeProcess( - h.0 as _, + h.0, ec.as_mut_ptr(), )) .to_pyresult(vm)?; @@ -471,9 +477,9 @@ mod _winapi { } #[pyfunction] - fn TerminateProcess(h: HANDLE, exit_code: u32) -> WindowsSysResult<i32> { + fn TerminateProcess(h: WinHandle, exit_code: u32) -> WindowsSysResult<i32> { WindowsSysResult(unsafe { - windows_sys::Win32::System::Threading::TerminateProcess(h.0 as _, exit_code) + windows_sys::Win32::System::Threading::TerminateProcess(h.0, exit_code) }) } @@ -481,23 +487,25 @@ mod _winapi { #[allow(dead_code)] fn LoadLibrary(path: PyStrRef, vm: &VirtualMachine) -> PyResult<isize> { let path = path.as_str().to_wide_with_nul(); - let handle = unsafe { - windows::Win32::System::LibraryLoader::LoadLibraryW(PCWSTR::from_raw(path.as_ptr())) - .unwrap() - }; - if handle.is_invalid() { + let handle = + unsafe { windows_sys::Win32::System::LibraryLoader::LoadLibraryW(path.as_ptr()) }; + if handle.is_null() { return Err(vm.new_runtime_error("LoadLibrary failed")); } - Ok(handle.0) + Ok(handle as isize) } #[pyfunction] fn GetModuleFileName(handle: isize, vm: &VirtualMachine) -> PyResult<String> { let mut path: Vec<u16> = vec![0; MAX_PATH as usize]; - let handle = HINSTANCE(handle); - let length = - unsafe { windows::Win32::System::LibraryLoader::GetModuleFileNameW(handle, &mut path) }; + let length = unsafe { + windows_sys::Win32::System::LibraryLoader::GetModuleFileNameW( + handle as windows_sys::Win32::Foundation::HMODULE, + path.as_mut_ptr(), + path.len() as u32, + ) + }; if length == 0 { return Err(vm.new_runtime_error("GetModuleFileName failed")); } @@ -507,17 +515,23 @@ mod _winapi { } #[pyfunction] - fn OpenMutexW(desired_access: u32, inherit_handle: bool, name: u16) -> PyResult<isize> { + fn OpenMutexW( + desired_access: u32, + inherit_handle: bool, + name: PyStrRef, + vm: &VirtualMachine, + ) -> PyResult<isize> { + let name_wide = name.as_str().to_wide_with_nul(); let handle = unsafe { windows_sys::Win32::System::Threading::OpenMutexW( desired_access, i32::from(inherit_handle), - windows_sys::core::PCWSTR::from(name as _), + name_wide.as_ptr(), ) }; - // if handle.is_invalid() { - // return Err(errno_err(vm)); - // } + if handle == INVALID_HANDLE_VALUE { + return Err(errno_err(vm)); + } Ok(handle as _) } diff --git a/crates/vm/src/windows.rs b/crates/vm/src/windows.rs index d91c436db6c..9b48e54e120 100644 --- a/crates/vm/src/windows.rs +++ b/crates/vm/src/windows.rs @@ -9,8 +9,11 @@ use crate::{ }; use rustpython_common::windows::ToWideString; use std::ffi::OsStr; -use windows::Win32::Foundation::HANDLE; -use windows_sys::Win32::Foundation::{HANDLE as RAW_HANDLE, INVALID_HANDLE_VALUE}; +use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}; + +/// Windows HANDLE wrapper for Python interop +#[derive(Clone, Copy)] +pub struct WinHandle(pub HANDLE); pub(crate) trait WindowsSysResultValue { type Ok: ToPyObject; @@ -18,13 +21,13 @@ pub(crate) trait WindowsSysResultValue { fn into_ok(self) -> Self::Ok; } -impl WindowsSysResultValue for RAW_HANDLE { - type Ok = HANDLE; +impl WindowsSysResultValue for HANDLE { + type Ok = WinHandle; fn is_err(&self) -> bool { *self == INVALID_HANDLE_VALUE } fn into_ok(self) -> Self::Ok { - HANDLE(self as _) + WinHandle(self) } } @@ -61,14 +64,14 @@ impl<T: WindowsSysResultValue> ToPyResult for WindowsSysResult<T> { type HandleInt = usize; // TODO: change to isize when fully ported to windows-rs -impl TryFromObject for HANDLE { +impl TryFromObject for WinHandle { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { let handle = HandleInt::try_from_object(vm, obj)?; - Ok(HANDLE(handle as isize)) + Ok(WinHandle(handle as HANDLE)) } } -impl ToPyObject for HANDLE { +impl ToPyObject for WinHandle { fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef { (self.0 as HandleInt).to_pyobject(vm) } From bb4e30a6dfd633685b14723d7e42cf58771e3616 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 05:44:21 +0900 Subject: [PATCH 433/819] fspath try from (#6359) --- Lib/test/test_os.py | 2 -- crates/vm/src/function/fspath.rs | 39 ++++++++++++++++++++++++-------- crates/vm/src/ospath.rs | 15 +++++++++++- crates/vm/src/stdlib/io.rs | 2 +- crates/vm/src/stdlib/os.rs | 2 +- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index d968aa87f08..30a24e28d45 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1811,7 +1811,6 @@ def test_exist_ok_existing_directory(self): # Issue #25583: A drive root could raise PermissionError on Windows os.makedirs(os.path.abspath('/'), exist_ok=True) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.umask not implemented yet for all platforms') @unittest.skipIf( support.is_emscripten or support.is_wasi, "Emscripten's/WASI's umask is a stub." @@ -4653,7 +4652,6 @@ def test_get_set_inheritable_o_path(self): os.set_inheritable(fd, False) self.assertEqual(os.get_inheritable(fd), False) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms') def test_get_set_inheritable_badf(self): fd = os_helper.make_bad_fd() diff --git a/crates/vm/src/function/fspath.rs b/crates/vm/src/function/fspath.rs index 5e0108986de..2bc331844c6 100644 --- a/crates/vm/src/function/fspath.rs +++ b/crates/vm/src/function/fspath.rs @@ -14,8 +14,26 @@ pub enum FsPath { } impl FsPath { + pub fn try_from_path_like( + obj: PyObjectRef, + check_for_nul: bool, + vm: &VirtualMachine, + ) -> PyResult<Self> { + Self::try_from( + obj, + check_for_nul, + "expected str, bytes or os.PathLike object", + vm, + ) + } + // PyOS_FSPath in CPython - pub fn try_from(obj: PyObjectRef, check_for_nul: bool, vm: &VirtualMachine) -> PyResult<Self> { + pub fn try_from( + obj: PyObjectRef, + check_for_nul: bool, + msg: &'static str, + vm: &VirtualMachine, + ) -> PyResult<Self> { let check_nul = |b: &[u8]| { if !check_for_nul || memchr::memchr(b'\0', b).is_none() { Ok(()) @@ -41,13 +59,16 @@ impl FsPath { Ok(pathlike) => return Ok(pathlike), Err(obj) => obj, }; - let method = - vm.get_method_or_type_error(obj.clone(), identifier!(vm, __fspath__), || { - format!( - "should be string, bytes, os.PathLike or integer, not {}", - obj.class().name() - ) - })?; + let not_pathlike_error = || format!("{msg}, not {}", obj.class().name()); + let method = vm.get_method_or_type_error( + obj.clone(), + identifier!(vm, __fspath__), + not_pathlike_error, + )?; + // If __fspath__ is explicitly set to None, treat it as if it doesn't have __fspath__ + if vm.is_none(&method) { + return Err(vm.new_type_error(not_pathlike_error())); + } let result = method.call((), vm)?; match1(result)?.map_err(|result| { vm.new_type_error(format!( @@ -125,6 +146,6 @@ impl TryFromObject for FsPath { } Err(_) => obj, }; - Self::try_from(obj, true, vm) + Self::try_from_path_like(obj, true, vm) } } diff --git a/crates/vm/src/ospath.rs b/crates/vm/src/ospath.rs index 5083c1df550..add40f9b20c 100644 --- a/crates/vm/src/ospath.rs +++ b/crates/vm/src/ospath.rs @@ -55,6 +55,14 @@ impl OsPath { Ok(Self { path, mode }) } + /// Convert an object to OsPath using the os.fspath-style error message. + /// This is used by open() which should report "expected str, bytes or os.PathLike object, not" + /// instead of "should be string, bytes or os.PathLike, not". + pub(crate) fn try_from_fspath(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> { + let fspath = FsPath::try_from_path_like(obj, true, vm)?; + Self::from_fspath(fspath, vm) + } + pub fn as_path(&self) -> &Path { Path::new(&self.path) } @@ -90,7 +98,12 @@ impl AsRef<Path> for OsPath { impl TryFromObject for OsPath { // TODO: path_converter with allow_fd=0 in CPython fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { - let fspath = FsPath::try_from(obj, true, vm)?; + let fspath = FsPath::try_from( + obj, + true, + "should be string, bytes, os.PathLike or integer", + vm, + )?; Self::from_fspath(fspath, vm) } } diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 39ea310c9d3..8efd52a29f3 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -4340,7 +4340,7 @@ mod fileio { } (fd, None) } else { - let path = OsPath::try_from_object(vm, name.clone())?; + let path = OsPath::try_from_fspath(name.clone(), vm)?; #[cfg(any(unix, target_os = "wasi"))] let fd = crt_fd::open(&path.clone().into_cstring(vm)?, flags, 0o666); #[cfg(windows)] diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 378cffd8315..0034b087a56 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1022,7 +1022,7 @@ pub(super) mod _os { #[pyfunction] fn fspath(path: PyObjectRef, vm: &VirtualMachine) -> PyResult<FsPath> { - FsPath::try_from(path, false, vm) + FsPath::try_from_path_like(path, false, vm) } #[pyfunction] From e4b9b26037bd403f410d3d5cf91b524f14d5bacd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 06:07:31 +0900 Subject: [PATCH 434/819] Bump winresource from 0.1.27 to 0.1.28 (#6360) Bumps [winresource](https://github.com/BenjaminRi/winresource) from 0.1.27 to 0.1.28. - [Commits](https://github.com/BenjaminRi/winresource/commits) --- updated-dependencies: - dependency-name: winresource dependency-version: 0.1.28 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12bc1af1162..495e30732b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1170,7 +1170,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4644,9 +4644,9 @@ checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" [[package]] name = "winresource" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ef04dd590e94ff7431a8eda99d5ca659e688d60e930bd0a330062acea4608f" +checksum = "6b021990998587d4438bb672b5c5f034cbc927f51b45e3807ab7323645ef4899" dependencies = [ "toml", "version_check", From 7a5d81a46953d56c5151451c9b89e73a99d7b284 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 06:07:48 +0900 Subject: [PATCH 435/819] Fix os.utime for windows/macos (#6354) --- Lib/test/test_logging.py | 2 -- Lib/test/test_os.py | 6 ------ Lib/test/test_shutil.py | 1 - Lib/test/test_tarfile.py | 4 ---- crates/vm/src/stdlib/os.rs | 31 +++++++++++++++++++++++++------ 5 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index c0f49fac6d2..9004e9ed744 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -6437,7 +6437,6 @@ def test_rollover(self): print(tf.read()) self.assertTrue(found, msg=msg) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') def test_rollover_at_midnight(self, weekly=False): os_helper.unlink(self.fn) now = datetime.datetime.now() @@ -6481,7 +6480,6 @@ def test_rollover_at_midnight(self, weekly=False): for i, line in enumerate(f): self.assertIn(f'testing1 {i}', line) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') def test_rollover_at_weekday(self): self.test_rollover_at_midnight(weekly=True) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 30a24e28d45..28b0874658d 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -814,7 +814,6 @@ def _test_utime(self, set_time, filename=None): self.assertEqual(st.st_atime_ns, atime_ns) self.assertEqual(st.st_mtime_ns, mtime_ns) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference))') def test_utime(self): def set_time(filename, ns): # test the ns keyword parameter @@ -880,7 +879,6 @@ def set_time(filename, ns): os.utime(name, dir_fd=dirfd, ns=ns) self._test_utime(set_time) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference))') def test_utime_directory(self): def set_time(filename, ns): # test calling os.utime() on a directory @@ -909,21 +907,18 @@ def _test_utime_current(self, set_time): self.assertAlmostEqual(st.st_mtime, current, delta=delta, msg=msg) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: 3359485824.516508 != 1679742912.516503 within 0.05 delta (1679742912.000005 difference) : st_time=3359485824.516508, current=1679742912.516503, dt=1679742912.000005)') def test_utime_current(self): def set_time(filename): # Set to the current time in the new way os.utime(self.fname) self._test_utime_current(set_time) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: 3359485824.5186944 != 1679742912.5186892 within 0.05 delta (1679742912.0000052 difference) : st_time=3359485824.5186944, current=1679742912.5186892, dt=1679742912.0000052)') def test_utime_current_old(self): def set_time(filename): # Set to the current time in the old explicit way. os.utime(self.fname, None) self._test_utime_current(set_time) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_utime_nonexistent(self): now = time.time() filename = 'nonexistent' @@ -955,7 +950,6 @@ def test_large_time(self): os.utime(self.fname, (large, large)) self.assertEqual(os.stat(self.fname).st_mtime, large) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: NotImplementedError not raised)') def test_utime_invalid_arguments(self): # seconds and nanoseconds parameters are mutually exclusive with self.assertRaises(ValueError): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 0abf7ca61a1..486d123d2f8 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1131,7 +1131,6 @@ def test_copymode_symlink_to_symlink_wo_lchmod(self): ### shutil.copystat - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_copystat_symlinks(self): tmp_dir = self.mkdtemp() diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 9e756c7fb95..635a1c1c85a 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -622,7 +622,6 @@ def test_extract_hardlink(self): data = f.read() self.assertEqual(sha256sum(data), sha256_regtype) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_extractall(self): # Test if extractall() correctly restores directory permissions # and times (see issue1735). @@ -653,7 +652,6 @@ def format_mtime(mtime): tar.close() os_helper.rmtree(DIR) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_extract_directory(self): dirtype = "ustar/dirtype" DIR = os.path.join(TEMPDIR, "extractdir") @@ -669,7 +667,6 @@ def test_extract_directory(self): finally: os_helper.rmtree(DIR) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_extractall_pathlike_name(self): DIR = pathlib.Path(TEMPDIR) / "extractall" with os_helper.temp_dir(DIR), \ @@ -680,7 +677,6 @@ def test_extractall_pathlike_name(self): path = DIR / tarinfo.name self.assertEqual(os.path.getmtime(path), tarinfo.mtime) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_extract_pathlike_name(self): dirtype = "ustar/dirtype" DIR = pathlib.Path(TEMPDIR) / "extractall" diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 0034b087a56..f484f69c06d 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1210,6 +1210,7 @@ pub(super) mod _os { { #[cfg(not(target_os = "redox"))] { + let path_for_err = path.clone(); let path = path.into_cstring(vm)?; let ts = |d: Duration| libc::timespec { @@ -1230,7 +1231,15 @@ pub(super) mod _os { }, ) }; - if ret < 0 { Err(errno_err(vm)) } else { Ok(()) } + if ret < 0 { + Err(IOErrorBuilder::with_filename( + &io::Error::last_os_error(), + path_for_err, + vm, + )) + } else { + Ok(()) + } } #[cfg(target_os = "redox")] { @@ -1252,9 +1261,15 @@ pub(super) mod _os { let [] = dir_fd.0; + if !_follow_symlinks.0 { + return Err(vm.new_not_implemented_error( + "utime: follow_symlinks unavailable on this platform", + )); + } + let ft = |d: Duration| { - let intervals = - ((d.as_secs() as i64 + 11644473600) * 10_000_000) + (d.as_nanos() as i64 / 100); + let intervals = ((d.as_secs() as i64 + 11644473600) * 10_000_000) + + (d.subsec_nanos() as i64 / 100); FILETIME { dwLowDateTime: intervals as DWORD, dwHighDateTime: (intervals >> 32) as DWORD, @@ -1267,15 +1282,19 @@ pub(super) mod _os { let f = OpenOptions::new() .write(true) .custom_flags(windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS) - .open(path) - .map_err(|err| err.into_pyexception(vm))?; + .open(&path) + .map_err(|err| IOErrorBuilder::with_filename(&err, path.clone(), vm))?; let ret = unsafe { FileSystem::SetFileTime(f.as_raw_handle() as _, std::ptr::null(), &acc, &modif) }; if ret == 0 { - Err(io::Error::last_os_error().into_pyexception(vm)) + Err(IOErrorBuilder::with_filename( + &io::Error::last_os_error(), + path, + vm, + )) } else { Ok(()) } From d432cfd3505c484f44232ac7d9507589f496819c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 06:08:04 +0900 Subject: [PATCH 436/819] Bump uuid from 1.18.1 to 1.19.0 (#6362) Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.18.1 to 1.19.0. - [Release notes](https://github.com/uuid-rs/uuid/releases) - [Commits](https://github.com/uuid-rs/uuid/compare/v1.18.1...v1.19.0) --- updated-dependencies: - dependency-name: uuid dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 495e30732b1..3d13c6dac5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4135,9 +4135,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "atomic", "js-sys", diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 82efa47679c..03a928e1e26 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -95,7 +95,7 @@ chrono.workspace = true # uuid [target.'cfg(not(any(target_os = "ios", target_os = "android", target_os = "windows", target_arch = "wasm32", target_os = "redox")))'.dependencies] mac_address = "1.1.3" -uuid = { version = "1.1.2", features = ["v1"] } +uuid = { version = "1.19.0", features = ["v1"] } [target.'cfg(all(unix, not(target_os = "redox"), not(target_os = "ios")))'.dependencies] termios = "0.3.3" From 808c8afbe14a5f3ee199a505d79d5ad02f0692ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 06:08:15 +0900 Subject: [PATCH 437/819] Bump criterion from 0.7.0 to 0.8.1 (#6361) Bumps [criterion](https://github.com/criterion-rs/criterion.rs) from 0.7.0 to 0.8.1. - [Release notes](https://github.com/criterion-rs/criterion.rs/releases) - [Changelog](https://github.com/criterion-rs/criterion.rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/criterion-rs/criterion.rs/compare/criterion-plot-v0.7.0...criterion-v0.8.1) --- updated-dependencies: - dependency-name: criterion dependency-version: 0.8.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 19 +++++++++++++++---- Cargo.toml | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d13c6dac5e..4797e53be06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,6 +47,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -863,10 +872,11 @@ dependencies = [ [[package]] name = "criterion" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" dependencies = [ + "alloca", "anes", "cast", "ciborium", @@ -875,6 +885,7 @@ dependencies = [ "itertools 0.13.0", "num-traits", "oorandom", + "page_size", "plotters", "rayon", "regex", @@ -886,9 +897,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" dependencies = [ "cast", "itertools 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index e4f68c77068..4a8b351b028 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -164,7 +164,7 @@ bstr = "1" cfg-if = "1.0" chrono = { version = "0.4.42", default-features = false, features = ["clock", "oldtime", "std"] } constant_time_eq = "0.4" -criterion = { version = "0.7", features = ["html_reports"] } +criterion = { version = "0.8", features = ["html_reports"] } crossbeam-utils = "0.8.21" flame = "0.2.2" getrandom = { version = "0.3", features = ["std"] } From 087e812bc04022cc44d57ca6a2431767c20dd4a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 06:08:52 +0900 Subject: [PATCH 438/819] Bump pyo3 from 0.26.0 to 0.27.2 (#6363) Bumps [pyo3](https://github.com/pyo3/pyo3) from 0.26.0 to 0.27.2. - [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.26.0...v0.27.2) --- updated-dependencies: - dependency-name: pyo3 dependency-version: 0.27.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4797e53be06..bb8788befb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2439,9 +2439,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.26.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383" +checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d" dependencies = [ "indoc", "libc", @@ -2456,18 +2456,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.26.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f" +checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.26.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105" +checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089" dependencies = [ "libc", "pyo3-build-config", @@ -2475,9 +2475,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.26.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded" +checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -2487,9 +2487,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.26.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf" +checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9" dependencies = [ "heck", "proc-macro2", From df7694ca51e4646ace80165411e713005b29c8a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 06:24:38 +0900 Subject: [PATCH 439/819] Bump x509-parser from 0.16.0 to 0.18.0 (#6364) Bumps [x509-parser](https://github.com/rusticata/x509-parser) from 0.16.0 to 0.18.0. - [Changelog](https://github.com/rusticata/x509-parser/blob/x509-parser-0.18.0/CHANGELOG.md) - [Commits](https://github.com/rusticata/x509-parser/compare/x509-parser-0.16.0...x509-parser-0.18.0) --- updated-dependencies: - dependency-name: x509-parser dependency-version: 0.18.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 58 ++++++++++++++++++++++++++++++++-------- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb8788befb1..e1db81f14e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,13 +160,28 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ - "asn1-rs-derive", + "asn1-rs-derive 0.5.1", "asn1-rs-impl", "displaydoc", "nom", "num-traits", "rusticata-macros", "thiserror 1.0.69", +] + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.17", "time", ] @@ -182,6 +197,18 @@ dependencies = [ "synstructure", ] +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "asn1-rs-impl" version = "0.2.0" @@ -976,11 +1003,11 @@ dependencies = [ [[package]] name = "der-parser" -version = "9.0.0" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ - "asn1-rs", + "asn1-rs 0.7.1", "displaydoc", "nom", "num-bigint", @@ -2076,7 +2103,16 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ - "asn1-rs", + "asn1-rs 0.6.2", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs 0.7.1", ] [[package]] @@ -3155,7 +3191,7 @@ dependencies = [ "num-integer", "num-traits", "num_enum", - "oid-registry", + "oid-registry 0.7.1", "openssl", "openssl-probe", "openssl-sys", @@ -4691,18 +4727,18 @@ dependencies = [ [[package]] name = "x509-parser" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425" dependencies = [ - "asn1-rs", + "asn1-rs 0.7.1", "data-encoding", "der-parser", "lazy_static 1.5.0", "nom", - "oid-registry", + "oid-registry 0.8.1", "rusticata-macros", - "thiserror 1.0.69", + "thiserror 2.0.17", "time", ] diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 03a928e1e26..36b743296db 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -123,7 +123,7 @@ rustls-native-certs = { version = "0.8", optional = true } rustls-pemfile = { version = "2.2", optional = true } rustls-platform-verifier = { version = "0.6", optional = true } x509-cert = { version = "0.2.5", features = ["pem", "builder"], optional = true } -x509-parser = { version = "0.16", optional = true } +x509-parser = { version = "0.18", optional = true } der = { version = "0.7", features = ["alloc", "oid"], optional = true } pem-rfc7468 = { version = "0.7", optional = true } webpki-roots = { version = "1.0", optional = true } From 614cb84e7aae9a4b4a9b1f111f74f9613496da7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:28:33 +0900 Subject: [PATCH 440/819] Bump wasm-bindgen from 0.2.105 to 0.2.106 (#6365) Bumps [wasm-bindgen](https://github.com/wasm-bindgen/wasm-bindgen) from 0.2.105 to 0.2.106. - [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases) - [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md) - [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.105...0.2.106) --- updated-dependencies: - dependency-name: wasm-bindgen dependency-version: 0.2.106 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 48 ++++++++++++++++++++++++------------------------ Cargo.toml | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1db81f14e5..e126110332c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1629,9 +1629,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -2475,9 +2475,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.27.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d" +checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383" dependencies = [ "indoc", "libc", @@ -2492,18 +2492,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.27.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6" +checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.27.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089" +checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105" dependencies = [ "libc", "pyo3-build-config", @@ -2511,9 +2511,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.27.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02" +checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -2523,9 +2523,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.27.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9" +checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf" dependencies = [ "heck", "proc-macro2", @@ -4230,9 +4230,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -4243,9 +4243,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -4256,9 +4256,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4266,9 +4266,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", @@ -4279,9 +4279,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] @@ -4309,9 +4309,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 4a8b351b028..04e089d00e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -220,7 +220,7 @@ unicode_names2 = "2.0.0" unicode-bidi-mirroring = "0.4" widestring = "1.2.0" windows-sys = "0.61.2" -wasm-bindgen = "0.2.100" +wasm-bindgen = "0.2.106" # Lints From 7f15c8c1bd66fd5dddcd4010a4158a0e77628b28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:28:49 +0900 Subject: [PATCH 441/819] Bump js-sys from 0.3.82 to 0.3.83 (#6366) Bumps [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) from 0.3.82 to 0.3.83. - [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases) - [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md) - [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits) --- updated-dependencies: - dependency-name: js-sys dependency-version: 0.3.83 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 0760551cf76af3039f7ff2d0bc0610ccca862346 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:29:01 +0900 Subject: [PATCH 442/819] Bump lz4_flex from 0.11.5 to 0.12.0 (#6367) Bumps [lz4_flex](https://github.com/pseitz/lz4_flex) from 0.11.5 to 0.12.0. - [Release notes](https://github.com/pseitz/lz4_flex/releases) - [Changelog](https://github.com/PSeitz/lz4_flex/blob/main/CHANGELOG.md) - [Commits](https://github.com/pseitz/lz4_flex/compare/0.11.5...0.12.0) --- updated-dependencies: - dependency-name: lz4_flex dependency-version: 0.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/compiler-core/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e126110332c..f8f448a682e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1799,9 +1799,9 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lz4_flex" -version = "0.11.5" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" +checksum = "ab6473172471198271ff72e9379150e9dfd70d8e533e0752a27e515b48dd375e" dependencies = [ "twox-hash", ] diff --git a/crates/compiler-core/Cargo.toml b/crates/compiler-core/Cargo.toml index e49c73eb14a..f4e619b95a4 100644 --- a/crates/compiler-core/Cargo.toml +++ b/crates/compiler-core/Cargo.toml @@ -18,7 +18,7 @@ itertools = { workspace = true } malachite-bigint = { workspace = true } num-complex = { workspace = true } -lz4_flex = "0.11" +lz4_flex = "0.12" [lints] workspace = true From 904cc0a5755b0135bd55b5bf5f597afe9b2147e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:46:14 +0900 Subject: [PATCH 443/819] Bump pem-rfc7468 from 0.7.0 to 1.0.0 (#6369) Bumps [pem-rfc7468](https://github.com/RustCrypto/formats) from 0.7.0 to 1.0.0. - [Commits](https://github.com/RustCrypto/formats/compare/pem-rfc7468/v0.7.0...pem-rfc7468/v1.0.0) --- updated-dependencies: - dependency-name: pem-rfc7468 dependency-version: 1.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 13 +++++++++++-- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8f448a682e..b083bbb97ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -997,7 +997,7 @@ dependencies = [ "const-oid", "der_derive", "flagset", - "pem-rfc7468", + "pem-rfc7468 0.7.0", "zeroize", ] @@ -2251,6 +2251,15 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "phf" version = "0.11.3" @@ -3198,7 +3207,7 @@ dependencies = [ "page_size", "parking_lot", "paste", - "pem-rfc7468", + "pem-rfc7468 1.0.0", "phf 0.13.1", "pkcs8", "pymath", diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 36b743296db..8cc04d5fd43 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -125,7 +125,7 @@ rustls-platform-verifier = { version = "0.6", optional = true } x509-cert = { version = "0.2.5", features = ["pem", "builder"], optional = true } x509-parser = { version = "0.18", optional = true } der = { version = "0.7", features = ["alloc", "oid"], optional = true } -pem-rfc7468 = { version = "0.7", optional = true } +pem-rfc7468 = { version = "1.0", features = ["alloc"], optional = true } webpki-roots = { version = "1.0", optional = true } aws-lc-rs = { version = "1.14.1", optional = true } oid-registry = { version = "0.7", features = ["x509", "pkcs1", "nist_algs"], optional = true } From cc534d29542c3b8273f9255fd67a6c9827ead24e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:46:40 +0900 Subject: [PATCH 444/819] Fix failing reason (#6370) --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 960a25e82a3..27a70ced156 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,7 +22,7 @@ env: # test_glob: many failing tests # test_io: many failing tests # test_os: many failing tests - # test_pathlib: support.rmtree() failing + # test_pathlib: panic by surrogate chars # test_posixpath: OSError: (22, 'The filename, directory name, or volume label syntax is incorrect. (os error 123)') # test_venv: couple of failing tests WINDOWS_SKIPS: >- From a99164fd7b2b2119ca6d551a197feb0ab19a56e1 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:08:16 +0900 Subject: [PATCH 445/819] nt is_dir,is_file,listmount,listvolume (#6373) * is_dir/is_file for windows * listmount/listvolume * check_env_var_len --- Lib/test/test_os.py | 1 - crates/common/src/windows.rs | 3 ++ crates/vm/src/stdlib/nt.rs | 81 ++++++++++++++++++++++++++++++++++++ crates/vm/src/stdlib/os.rs | 64 ++++++++++++++++++---------- 4 files changed, 126 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 28b0874658d..25e2c30e4d7 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1152,7 +1152,6 @@ def test_putenv_unsetenv(self): self.assertEqual(proc.stdout.rstrip(), repr(None)) # On OS X < 10.6, unsetenv() doesn't return a value (bpo-13415). - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: ValueError not raised by putenv)') @support.requires_mac_ver(10, 6) def test_putenv_unsetenv_error(self): # Empty variable name is invalid. diff --git a/crates/common/src/windows.rs b/crates/common/src/windows.rs index 4a922ce4354..dd4ea82c874 100644 --- a/crates/common/src/windows.rs +++ b/crates/common/src/windows.rs @@ -3,6 +3,9 @@ use std::{ os::windows::ffi::{OsStrExt, OsStringExt}, }; +/// _MAX_ENV from Windows CRT stdlib.h - maximum environment variable size +pub const _MAX_ENV: usize = 32767; + pub trait ToWideString { fn to_wide(&self) -> Vec<u16>; fn to_wide_with_nul(&self) -> Vec<u16>; diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index c041dab9aa5..0bbb5881a9f 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -682,6 +682,87 @@ pub(crate) mod module { Ok(vm.ctx.new_list(drives)) } + #[pyfunction] + fn listvolumes(vm: &VirtualMachine) -> PyResult<PyListRef> { + use windows_sys::Win32::Foundation::ERROR_NO_MORE_FILES; + + let mut result = Vec::new(); + let mut buffer = [0u16; Foundation::MAX_PATH as usize + 1]; + + let find = unsafe { FileSystem::FindFirstVolumeW(buffer.as_mut_ptr(), buffer.len() as _) }; + if find == INVALID_HANDLE_VALUE { + return Err(errno_err(vm)); + } + + loop { + // Find the null terminator + let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len()); + let volume = String::from_utf16_lossy(&buffer[..len]); + result.push(vm.new_pyobj(volume)); + + let ret = unsafe { + FileSystem::FindNextVolumeW(find, buffer.as_mut_ptr(), buffer.len() as _) + }; + if ret == 0 { + let err = io::Error::last_os_error(); + unsafe { FileSystem::FindVolumeClose(find) }; + if err.raw_os_error() == Some(ERROR_NO_MORE_FILES as i32) { + break; + } + return Err(err.to_pyexception(vm)); + } + } + + Ok(vm.ctx.new_list(result)) + } + + #[pyfunction] + fn listmounts(volume: OsPath, vm: &VirtualMachine) -> PyResult<PyListRef> { + use windows_sys::Win32::Foundation::ERROR_MORE_DATA; + + let wide = volume.to_wide_cstring(vm)?; + let mut buflen: u32 = Foundation::MAX_PATH + 1; + let mut buffer: Vec<u16> = vec![0; buflen as usize]; + + loop { + let success = unsafe { + FileSystem::GetVolumePathNamesForVolumeNameW( + wide.as_ptr(), + buffer.as_mut_ptr(), + buflen, + &mut buflen, + ) + }; + if success != 0 { + break; + } + let err = io::Error::last_os_error(); + if err.raw_os_error() == Some(ERROR_MORE_DATA as i32) { + buffer.resize(buflen as usize, 0); + continue; + } + return Err(err.to_pyexception(vm)); + } + + // Parse null-separated strings + let mut result = Vec::new(); + let mut start = 0; + for (i, &c) in buffer.iter().enumerate() { + if c == 0 { + if i > start { + let mount = String::from_utf16_lossy(&buffer[start..i]); + result.push(vm.new_pyobj(mount)); + } + start = i + 1; + if start < buffer.len() && buffer[start] == 0 { + break; // Double null = end + } + } + } + + Ok(vm.ctx.new_list(result)) + } + #[pyfunction] fn set_handle_inheritable( handle: intptr_t, diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index f484f69c06d..e9e1337235b 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -428,6 +428,20 @@ pub(super) mod _os { } } + /// Check if environment variable length exceeds Windows limit. + /// size should be key.len() + value.len() + 2 (for '=' and null terminator) + #[cfg(windows)] + fn check_env_var_len(size: usize, vm: &VirtualMachine) -> PyResult<()> { + use crate::common::windows::_MAX_ENV; + if size > _MAX_ENV { + return Err(vm.new_value_error(format!( + "the environment variable is longer than {} characters", + _MAX_ENV + ))); + } + Ok(()) + } + #[pyfunction] fn putenv( key: Either<PyStrRef, PyBytesRef>, @@ -442,6 +456,8 @@ pub(super) mod _os { if key.is_empty() || key.contains(&b'=') { return Err(vm.new_value_error("illegal environment variable name")); } + #[cfg(windows)] + check_env_var_len(key.len() + value.len() + 2, vm)?; let key = super::bytes_as_os_str(key, vm)?; let value = super::bytes_as_os_str(value, vm)?; // SAFETY: requirements forwarded from the caller @@ -464,6 +480,9 @@ pub(super) mod _os { ), )); } + // For unsetenv, size is key + '=' (no value, just clearing) + #[cfg(windows)] + check_env_var_len(key.len() + 1, vm)?; let key = super::bytes_as_os_str(key, vm)?; // SAFETY: requirements forwarded from the caller unsafe { env::remove_var(key) }; @@ -507,17 +526,17 @@ pub(super) mod _os { Ok(self.mode.process_path(&self.pathval, vm)) } - fn perform_on_metadata( - &self, - follow_symlinks: FollowSymlinks, - action: fn(fs::Metadata) -> bool, - vm: &VirtualMachine, - ) -> PyResult<bool> { + #[pymethod] + fn is_dir(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> { match super::fs_metadata(&self.pathval, follow_symlinks.0) { - Ok(meta) => Ok(action(meta)), + Ok(meta) => Ok(meta.is_dir()), Err(e) => { - // FileNotFoundError is caught and not raised if e.kind() == io::ErrorKind::NotFound { + // On Windows, use cached file_type when file is removed + #[cfg(windows)] + if let Ok(file_type) = &self.file_type { + return Ok(file_type.is_dir()); + } Ok(false) } else { Err(e.into_pyexception(vm)) @@ -526,22 +545,23 @@ pub(super) mod _os { } } - #[pymethod] - fn is_dir(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> { - self.perform_on_metadata( - follow_symlinks, - |meta: fs::Metadata| -> bool { meta.is_dir() }, - vm, - ) - } - #[pymethod] fn is_file(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> { - self.perform_on_metadata( - follow_symlinks, - |meta: fs::Metadata| -> bool { meta.is_file() }, - vm, - ) + match super::fs_metadata(&self.pathval, follow_symlinks.0) { + Ok(meta) => Ok(meta.is_file()), + Err(e) => { + if e.kind() == io::ErrorKind::NotFound { + // On Windows, use cached file_type when file is removed + #[cfg(windows)] + if let Ok(file_type) = &self.file_type { + return Ok(file_type.is_file()); + } + Ok(false) + } else { + Err(e.into_pyexception(vm)) + } + } + } } #[pymethod] From db95946b8d83c885ad9aba5a42358c82d6acef0b Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:59:31 +0900 Subject: [PATCH 446/819] Fix SSL deferred error (#6371) * Fix SSL to return deferred error on the right time * lease conn_guard * SslError::Io * is_connection_closed --- crates/stdlib/src/ssl.rs | 243 +++++++++++++++----------------- crates/stdlib/src/ssl/cert.rs | 13 +- crates/stdlib/src/ssl/compat.rs | 48 ++++++- 3 files changed, 170 insertions(+), 134 deletions(-) diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index b5f12c5a85c..37fad0d7f5c 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -2598,24 +2598,26 @@ mod _ssl { fn complete_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { *self.handshake_done.lock() = true; - // Check if session was resumed before creating session object - let conn_guard = self.connection.lock(); - if let Some(ref conn) = *conn_guard { - let was_resumed = conn.is_session_resumed(); - *self.session_was_reused.lock() = was_resumed; + // Check if session was resumed - get value and release lock immediately + let was_resumed = self + .connection + .lock() + .as_ref() + .map(|conn| conn.is_session_resumed()) + .unwrap_or(false); - // Update context session statistics if server-side - if self.server_side { - let context = self.context.read(); - // Increment accept count for every successful server handshake - context.accept_count.fetch_add(1, Ordering::SeqCst); - // Increment hits count if session was resumed - if was_resumed { - context.session_hits.fetch_add(1, Ordering::SeqCst); - } + *self.session_was_reused.lock() = was_resumed; + + // Update context session statistics if server-side + if self.server_side { + let context = self.context.read(); + // Increment accept count for every successful server handshake + context.accept_count.fetch_add(1, Ordering::SeqCst); + // Increment hits count if session was resumed + if was_resumed { + context.session_hits.fetch_add(1, Ordering::SeqCst); } } - drop(conn_guard); // Track CA certificate used during handshake (client-side only) // This simulates lazy loading behavior for capath certificates @@ -3209,62 +3211,46 @@ mod _ssl { } // Perform the actual handshake by exchanging data with the socket/BIO - match conn_guard.as_mut() { - Some(TlsConnection::Client(_conn)) => { - // CLIENT is simple - no SNI callback handling needed - ssl_do_handshake(conn_guard.as_mut().unwrap(), self, vm) - .map_err(|e| e.into_py_err(vm))?; - drop(conn_guard); - self.complete_handshake(vm)?; - Ok(()) - } - Some(TlsConnection::Server(_conn)) => { - // Use OpenSSL-compatible handshake for server - // Handle SNI callback restart - match ssl_do_handshake(conn_guard.as_mut().unwrap(), self, vm) { - Ok(()) => { - // Handshake completed successfully - drop(conn_guard); - self.complete_handshake(vm)?; - Ok(()) - } - Err(SslError::SniCallbackRestart) => { - // SNI detected - need to call callback and recreate connection - - // CRITICAL: Drop connection lock BEFORE calling Python callback to avoid deadlock - // - // Deadlock scenario if we keep the lock: - // 1. This thread holds self.connection.lock() - // 2. Python callback invokes other SSL methods (e.g., getpeercert(), cipher()) - // 3. Those methods try to acquire self.connection.lock() again - // 4. PyMutex (parking_lot::Mutex) is not reentrant -> DEADLOCK - // - // Trade-off: By dropping the lock, we lose the ability to send TLS alerts - // because Rustls doesn't provide a send_fatal_alert() API. See detailed - // explanation in invoke_sni_callback() where we set _reason attribute. - drop(conn_guard); - - // Get the SNI name that was extracted (may be None if client didn't send SNI) - let sni_name = self.get_extracted_sni_name(); - - // Now safe to call Python callback (no locks held) - self.invoke_sni_callback(sni_name.as_deref(), vm)?; - - // Clear connection to trigger recreation - *self.connection.lock() = None; - - // Recursively call do_handshake to recreate with new context - self.do_handshake(vm) - } - Err(e) => { - // Other errors - convert to Python exception - drop(conn_guard); - Err(e.into_py_err(vm)) - } + let conn = conn_guard.as_mut().expect("unreachable"); + let is_client = matches!(conn, TlsConnection::Client(_)); + let handshake_result = ssl_do_handshake(conn, self, vm); + drop(conn_guard); + + if is_client { + // CLIENT is simple - no SNI callback handling needed + handshake_result.map_err(|e| e.into_py_err(vm))?; + self.complete_handshake(vm)?; + Ok(()) + } else { + // Use OpenSSL-compatible handshake for server + // Handle SNI callback restart + match handshake_result { + Ok(()) => { + // Handshake completed successfully + self.complete_handshake(vm)?; + Ok(()) + } + Err(SslError::SniCallbackRestart) => { + // SNI detected - need to call callback and recreate connection + + // Get the SNI name that was extracted (may be None if client didn't send SNI) + let sni_name = self.get_extracted_sni_name(); + + // Now safe to call Python callback (no locks held) + self.invoke_sni_callback(sni_name.as_deref(), vm)?; + + // Clear connection to trigger recreation + *self.connection.lock() = None; + + // Recursively call do_handshake to recreate with new context + self.do_handshake(vm) + } + Err(e) => { + // Other errors - convert to Python exception + Err(e.into_py_err(vm)) } } - None => unreachable!(), } } @@ -3323,9 +3309,6 @@ mod _ssl { )); } - // Check for deferred certificate verification errors (TLS 1.3) - self.check_deferred_cert_error(vm)?; - // Helper function to handle return value based on buffer presence let return_data = |data: Vec<u8>, buffer_arg: &OptionalArg<ArgMemoryBuffer>, @@ -3350,17 +3333,21 @@ mod _ssl { } }; - let mut conn_guard = self.connection.lock(); - let conn = conn_guard - .as_mut() - .ok_or_else(|| vm.new_value_error("Connection not established"))?; - // Use compat layer for unified read logic with proper EOF handling // This matches CPython's SSL_read_ex() approach let mut buf = vec![0u8; len]; - - match crate::ssl::compat::ssl_read(conn, &mut buf, self, vm) { + let read_result = { + let mut conn_guard = self.connection.lock(); + let conn = conn_guard + .as_mut() + .ok_or_else(|| vm.new_value_error("Connection not established"))?; + crate::ssl::compat::ssl_read(conn, &mut buf, self, vm) + }; + match read_result { Ok(n) => { + // Check for deferred certificate verification errors (TLS 1.3) + // Must be checked AFTER ssl_read, as the error is set during I/O + self.check_deferred_cert_error(vm)?; buf.truncate(n); return_data(buf, &buffer, vm) } @@ -3445,62 +3432,62 @@ mod _ssl { )); } - // Check for deferred certificate verification errors (TLS 1.3) - self.check_deferred_cert_error(vm)?; - - let mut conn_guard = self.connection.lock(); - let conn = conn_guard - .as_mut() - .ok_or_else(|| vm.new_value_error("Connection not established"))?; + { + let mut conn_guard = self.connection.lock(); + let conn = conn_guard + .as_mut() + .ok_or_else(|| vm.new_value_error("Connection not established"))?; - let is_bio = self.is_bio_mode(); - let data: &[u8] = data_bytes.as_ref(); + let is_bio = self.is_bio_mode(); + let data: &[u8] = data_bytes.as_ref(); - // Write data in chunks to avoid filling the internal TLS buffer - // rustls has a limited internal buffer, so we need to flush periodically - const CHUNK_SIZE: usize = 16384; // 16KB chunks (typical TLS record size) - let mut written = 0; + // Write data in chunks to avoid filling the internal TLS buffer + // rustls has a limited internal buffer, so we need to flush periodically + const CHUNK_SIZE: usize = 16384; // 16KB chunks (typical TLS record size) + let mut written = 0; - while written < data.len() { - let chunk_end = std::cmp::min(written + CHUNK_SIZE, data.len()); - let chunk = &data[written..chunk_end]; + while written < data.len() { + let chunk_end = std::cmp::min(written + CHUNK_SIZE, data.len()); + let chunk = &data[written..chunk_end]; - // Write chunk to TLS layer - { - let mut writer = conn.writer(); - use std::io::Write; - writer - .write_all(chunk) - .map_err(|e| vm.new_os_error(format!("Write failed: {e}")))?; - } + // Write chunk to TLS layer + { + let mut writer = conn.writer(); + use std::io::Write; + writer + .write_all(chunk) + .map_err(|e| vm.new_os_error(format!("Write failed: {e}")))?; + } - written = chunk_end; + written = chunk_end; - // Flush TLS data to socket after each chunk - if conn.wants_write() { - if is_bio { - self.write_pending_tls(conn, vm)?; - } else { - // Socket mode: flush all pending TLS data - while conn.wants_write() { - let mut buf = Vec::new(); - conn.write_tls(&mut buf) - .map_err(|e| vm.new_os_error(format!("TLS write failed: {e}")))?; - - if !buf.is_empty() { - let timed_out = - self.sock_wait_for_io_impl(SelectKind::Write, vm)?; - if timed_out { - return Err(vm.new_os_error("Write operation timed out")); - } + // Flush TLS data to socket after each chunk + if conn.wants_write() { + if is_bio { + self.write_pending_tls(conn, vm)?; + } else { + // Socket mode: flush all pending TLS data + while conn.wants_write() { + let mut buf = Vec::new(); + conn.write_tls(&mut buf).map_err(|e| { + vm.new_os_error(format!("TLS write failed: {e}")) + })?; + + if !buf.is_empty() { + let timed_out = + self.sock_wait_for_io_impl(SelectKind::Write, vm)?; + if timed_out { + return Err(vm.new_os_error("Write operation timed out")); + } - match self.sock_send(buf, vm) { - Ok(_) => {} - Err(e) => { - if is_blocking_io_error(&e, vm) { - return Err(create_ssl_want_write_error(vm)); + match self.sock_send(buf, vm) { + Ok(_) => {} + Err(e) => { + if is_blocking_io_error(&e, vm) { + return Err(create_ssl_want_write_error(vm)); + } + return Err(e); } - return Err(e); } } } @@ -3509,6 +3496,10 @@ mod _ssl { } } + // Check for deferred certificate verification errors (TLS 1.3) + // Must be checked AFTER write completes, as the error may be set during I/O + self.check_deferred_cert_error(vm)?; + Ok(data_len) } diff --git a/crates/stdlib/src/ssl/cert.rs b/crates/stdlib/src/ssl/cert.rs index 2baefad700b..b3cb7d6c14e 100644 --- a/crates/stdlib/src/ssl/cert.rs +++ b/crates/stdlib/src/ssl/cert.rs @@ -1067,15 +1067,16 @@ impl ClientCertVerifier for DeferredClientCertVerifier { .inner .verify_client_cert(end_entity, intermediates, now); - // If verification failed, store the error for later - if result.is_err() { - let error_msg = "TLS handshake failed: received fatal alert: UnknownCA".to_string(); + // If verification failed, store the error for the server's Python code + // AND return the error so rustls sends the appropriate TLS alert + if let Err(ref e) = result { + let error_msg = format!("certificate verify failed: {e}"); *self.deferred_error.write() = Some(error_msg); + // Return the error to rustls so it sends the alert to the client + return result; } - // Always return success to allow handshake to complete - // The error will be raised during the first I/O operation - Ok(ClientCertVerified::assertion()) + result } fn verify_tls12_signature( diff --git a/crates/stdlib/src/ssl/compat.rs b/crates/stdlib/src/ssl/compat.rs index f4c860fdbc1..a55b3058884 100644 --- a/crates/stdlib/src/ssl/compat.rs +++ b/crates/stdlib/src/ssl/compat.rs @@ -24,6 +24,7 @@ use rustls::server::ServerConfig; use rustls::server::ServerConnection; use rustls::sign::CertifiedKey; use rustpython_vm::builtins::PyBaseExceptionRef; +use rustpython_vm::convert::IntoPyException; use rustpython_vm::function::ArgBytesLike; use rustpython_vm::{AsObject, PyObjectRef, PyPayload, PyResult, TryFromObject}; use std::io::Read; @@ -558,7 +559,7 @@ impl SslError { // Use the proper cert verification error creator create_ssl_cert_verification_error(vm, &cert_err).expect("unlikely to happen") } - SslError::Io(err) => vm.new_os_error(format!("I/O error: {err}")), + SslError::Io(err) => err.into_pyexception(vm), SslError::SniCallbackRestart => { // This should be handled at PySSLSocket level unreachable!("SniCallbackRestart should not reach Python layer") @@ -1527,6 +1528,29 @@ fn ssl_read_tls_records( Ok(()) } +/// Check if an exception is a connection closed error +/// In SSL context, these errors indicate unexpected connection termination without proper TLS shutdown +fn is_connection_closed_error(exc: &PyBaseExceptionRef, vm: &VirtualMachine) -> bool { + use rustpython_vm::stdlib::errno::errors; + + // Check for ConnectionAbortedError, ConnectionResetError (Python exception types) + if exc.fast_isinstance(vm.ctx.exceptions.connection_aborted_error) + || exc.fast_isinstance(vm.ctx.exceptions.connection_reset_error) + { + return true; + } + + // Also check OSError with specific errno values (ECONNABORTED, ECONNRESET) + if exc.fast_isinstance(vm.ctx.exceptions.os_error) + && let Ok(errno) = exc.as_object().get_attr("errno", vm) + && let Ok(errno_int) = errno.try_int(vm) + && let Ok(errno_val) = errno_int.try_to_primitive::<i32>(vm) + { + return errno_val == errors::ECONNABORTED || errno_val == errors::ECONNRESET; + } + false +} + /// Ensure TLS data is available for reading /// Returns the number of bytes read from the socket fn ssl_ensure_data_available( @@ -1562,7 +1586,27 @@ fn ssl_ensure_data_available( // else: non-blocking socket (timeout=0) or blocking socket (timeout=None) - skip select } - let data = socket.sock_recv(2048, vm).map_err(SslError::Py)?; + let data = match socket.sock_recv(2048, vm) { + Ok(data) => data, + Err(e) => { + // Before returning socket error, check if rustls already has a queued TLS alert + // This mirrors CPython/OpenSSL behavior: SSL errors take precedence over socket errors + // On Windows, TCP RST may arrive before we read the alert, but rustls may have + // already received and buffered the alert from a previous read + if let Err(rustls_err) = conn.process_new_packets() { + return Err(SslError::from_rustls(rustls_err)); + } + // In SSL context, connection closed errors (ECONNABORTED, ECONNRESET) indicate + // unexpected connection termination - the peer closed without proper TLS shutdown. + // This is semantically equivalent to "EOF occurred in violation of protocol" + // because no close_notify alert was received. + // On Windows, TCP RST can arrive before we read the TLS alert, causing these errors. + if is_connection_closed_error(&e, vm) { + return Err(SslError::Eof); + } + return Err(SslError::Py(e)); + } + }; // Get the size of received data let bytes_read = data From 5365805312bd4683846b93d987b6eef09b7642a9 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:01:12 +0900 Subject: [PATCH 447/819] minimize ssl lock (#6376) --- crates/stdlib/src/ssl.rs | 210 ++++++++++++++++++++++----------------- 1 file changed, 117 insertions(+), 93 deletions(-) diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 37fad0d7f5c..25227a5556a 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -1357,47 +1357,69 @@ mod _ssl { ); } - // Get mutable references to store and ca_certs_der + // Parse arguments BEFORE acquiring locks to reduce lock scope + let cafile_path = if let OptionalArg::Present(Some(ref cafile_obj)) = args.cafile { + Some(Self::parse_path_arg(cafile_obj, vm)?) + } else { + None + }; + + let capath_dir = if let OptionalArg::Present(Some(ref capath_obj)) = args.capath { + Some(Self::parse_path_arg(capath_obj, vm)?) + } else { + None + }; + + let cadata_parsed = if let OptionalArg::Present(ref cadata_obj) = args.cadata + && !vm.is_none(cadata_obj) + { + let is_string = PyStrRef::try_from_object(vm, cadata_obj.clone()).is_ok(); + let data_vec = self.parse_cadata_arg(cadata_obj, vm)?; + Some((data_vec, is_string)) + } else { + None + }; + + // Check for CRL before acquiring main locks + let (crl_opt, cafile_is_crl) = if let Some(ref path) = cafile_path { + let crl = self.load_crl_from_file(path, vm)?; + let is_crl = crl.is_some(); + (crl, is_crl) + } else { + (None, false) + }; + + // If it's a CRL, just add it (separate lock, no conflict with root_store) + if let Some(crl) = crl_opt { + self.crls.write().push(crl); + } + + // Now acquire write locks for certificate loading let mut root_store = self.root_certs.write(); let mut ca_certs_der = self.ca_certs_der.write(); - // Load from file - if let OptionalArg::Present(Some(ref cafile_obj)) = args.cafile { - let path = Self::parse_path_arg(cafile_obj, vm)?; - - // Try to load as CRL first - if let Some(crl) = self.load_crl_from_file(&path, vm)? { - self.crls.write().push(crl); - } else { - // Not a CRL, load as certificate - let stats = self.load_certs_from_file_helper( - &mut root_store, - &mut ca_certs_der, - &path, - vm, - )?; - self.update_cert_stats(stats); - } + // Load from file (if not CRL) + if let Some(ref path) = cafile_path + && !cafile_is_crl + { + // Not a CRL, load as certificate + let stats = + self.load_certs_from_file_helper(&mut root_store, &mut ca_certs_der, path, vm)?; + self.update_cert_stats(stats); } // Load from directory (don't add to ca_certs_der) - if let OptionalArg::Present(Some(ref capath_obj)) = args.capath { - let dir_path = Self::parse_path_arg(capath_obj, vm)?; - let stats = self.load_certs_from_dir_helper(&mut root_store, &dir_path, vm)?; + if let Some(ref dir_path) = capath_dir { + let stats = self.load_certs_from_dir_helper(&mut root_store, dir_path, vm)?; self.update_cert_stats(stats); } // Load from bytes or str - if let OptionalArg::Present(cadata_obj) = args.cadata - && !vm.is_none(&cadata_obj) - { - // Check if input is string or bytes - let is_string = PyStrRef::try_from_object(vm, cadata_obj.clone()).is_ok(); - let data_vec = self.parse_cadata_arg(&cadata_obj, vm)?; + if let Some((ref data_vec, is_string)) = cadata_parsed { let stats = self.load_certs_from_bytes_helper( &mut root_store, &mut ca_certs_der, - &data_vec, + data_vec, is_string, // PEM only for strings vm, )?; @@ -2547,48 +2569,51 @@ mod _ssl { /// This simulates lazy loading behavior: capath certificates /// are only added to get_ca_certs() after they're actually used in a handshake. fn track_used_ca_from_capath(&self) -> Result<(), String> { - let context = self.context.read(); - let capath_certs = context.capath_certs_der.read(); - - // No capath certs to track - if capath_certs.is_empty() { - return Ok(()); - } - - // Get peer certificate chain - let conn_guard = self.connection.lock(); - let conn = conn_guard.as_ref().ok_or("No connection")?; - - let peer_certs = conn.peer_certificates().ok_or("No peer certificates")?; + // Extract capath_certs, releasing context lock quickly + let capath_certs = { + let context = self.context.read(); + let certs = context.capath_certs_der.read(); + if certs.is_empty() { + return Ok(()); + } + certs.clone() + }; - if peer_certs.is_empty() { - return Ok(()); - } + // Extract peer certificates, releasing connection lock quickly + let top_cert_der = { + let conn_guard = self.connection.lock(); + let conn = conn_guard.as_ref().ok_or("No connection")?; + let peer_certs = conn.peer_certificates().ok_or("No peer certificates")?; + if peer_certs.is_empty() { + return Ok(()); + } + peer_certs + .iter() + .map(|c| c.as_ref().to_vec()) + .next_back() + .expect("is_empty checked above") + }; // Get the top certificate in the chain (closest to root) // Note: Server usually doesn't send the root CA, so we check the last cert's issuer - let top_cert_der = peer_certs.last().unwrap(); - let (_, top_cert) = x509_parser::parse_x509_certificate(top_cert_der) + let (_, top_cert) = x509_parser::parse_x509_certificate(&top_cert_der) .map_err(|e| format!("Failed to parse top cert: {e}"))?; let top_issuer = top_cert.issuer(); - // Find matching CA in capath certs - for ca_der in capath_certs.iter() { - let (_, ca) = x509_parser::parse_x509_certificate(ca_der) - .map_err(|e| format!("Failed to parse CA: {e}"))?; + // Find matching CA in capath certs (skip unparseable certificates) + let matching_ca = capath_certs.iter().find_map(|ca_der| { + let (_, ca) = x509_parser::parse_x509_certificate(ca_der).ok()?; + // Check if this CA is self-signed (root CA) and matches the issuer + (ca.subject() == ca.issuer() && ca.subject() == top_issuer).then(|| ca_der.clone()) + }); - // Check if this CA is self-signed and matches the issuer - if ca.subject() == ca.issuer() // Self-signed (root CA) - && ca.subject() == top_issuer - // Matches top cert's issuer - { - // Check if not already in ca_certs_der - let mut ca_certs_der = context.ca_certs_der.write(); - if !ca_certs_der.iter().any(|c| c == ca_der) { - ca_certs_der.push(ca_der.clone()); - } - break; + // Update ca_certs_der if we found a match + if let Some(ca_der) = matching_ca { + let context = self.context.read(); + let mut ca_certs_der = context.ca_certs_der.write(); + if !ca_certs_der.iter().any(|c| c == &ca_der) { + ca_certs_der.push(ca_der); } } @@ -2675,6 +2700,7 @@ mod _ssl { /// Check if SNI callback is configured pub(crate) fn has_sni_callback(&self) -> bool { + // Nested read locks are safe self.context.read().sni_callback.read().is_some() } @@ -2685,10 +2711,9 @@ mod _ssl { /// Get the extracted SNI name from resolver pub(crate) fn get_extracted_sni_name(&self) -> Option<String> { - self.sni_state - .read() - .as_ref() - .and_then(|arc| arc.lock().1.clone()) + // Clone the Arc option to avoid nested lock (sni_state.read -> arc.lock) + let sni_state_opt = self.sni_state.read().clone(); + sni_state_opt.as_ref().and_then(|arc| arc.lock().1.clone()) } /// Invoke the Python SNI callback @@ -3516,27 +3541,24 @@ mod _ssl { return Err(vm.new_value_error("handshake not done yet")); } - // Get peer certificates from TLS connection - let conn_guard = self.connection.lock(); - let conn = conn_guard - .as_ref() - .ok_or_else(|| vm.new_value_error("No TLS connection established"))?; - - let certs = conn.peer_certificates(); + // Extract DER bytes from connection, releasing lock quickly + let der_bytes = { + let conn_guard = self.connection.lock(); + let conn = conn_guard + .as_ref() + .ok_or_else(|| vm.new_value_error("No TLS connection established"))?; - // Return None if no peer certificate - let Some(certs) = certs else { - return Ok(None); + let Some(peer_certificates) = conn.peer_certificates() else { + return Ok(None); + }; + let cert = peer_certificates + .first() + .ok_or_else(|| vm.new_value_error("No peer certificate available"))?; + cert.as_ref().to_vec() }; - // Get first certificate (peer's certificate) - let cert_der = certs - .first() - .ok_or_else(|| vm.new_value_error("No peer certificate available"))?; - if binary { // Return DER-encoded certificate as bytes - let der_bytes = cert_der.as_ref().to_vec(); return Ok(Some(vm.ctx.new_bytes(der_bytes).into())); } @@ -3548,9 +3570,8 @@ mod _ssl { return Ok(Some(vm.ctx.new_dict().into())); } - // Parse DER certificate and convert to dict - let der_bytes = cert_der.as_ref(); - let (_, cert) = x509_parser::parse_x509_certificate(der_bytes) + // Parse DER certificate and convert to dict (outside lock) + let (_, cert) = x509_parser::parse_x509_certificate(&der_bytes) .map_err(|e| vm.new_value_error(format!("Failed to parse certificate: {e}")))?; cert::cert_to_dict(vm, &cert).map(Some) @@ -3558,12 +3579,13 @@ mod _ssl { #[pymethod] fn cipher(&self) -> Option<(String, String, i32)> { - let conn_guard = self.connection.lock(); - let conn = conn_guard.as_ref()?; - - let suite = conn.negotiated_cipher_suite()?; + // Extract cipher suite, releasing lock quickly + let suite = { + let conn_guard = self.connection.lock(); + conn_guard.as_ref()?.negotiated_cipher_suite()? + }; - // Extract cipher information using unified helper + // Extract cipher information outside the lock let cipher_info = extract_cipher_info(&suite); // Note: returns a 3-tuple (name, protocol_version, bits) @@ -3577,11 +3599,13 @@ mod _ssl { #[pymethod] fn version(&self) -> Option<String> { - let conn_guard = self.connection.lock(); - let conn = conn_guard.as_ref()?; - - let suite = conn.negotiated_cipher_suite()?; + // Extract cipher suite, releasing lock quickly + let suite = { + let conn_guard = self.connection.lock(); + conn_guard.as_ref()?.negotiated_cipher_suite()? + }; + // Convert to string outside the lock let version_str = match suite.version().version { rustls::ProtocolVersion::TLSv1_2 => "TLSv1.2", rustls::ProtocolVersion::TLSv1_3 => "TLSv1.3", From 79cd048e1f8ba732be3e6e40bcf3af4c08b9d1b3 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:02:44 +0900 Subject: [PATCH 448/819] native ExceptionGroup (#6358) --- Lib/test/test_exception_group.py | 6 - Lib/test/test_socket.py | 2 - .../Lib/python_builtins/_py_exceptiongroup.py | 330 ------------ crates/vm/src/exception_group.rs | 469 ++++++++++++++++++ crates/vm/src/exceptions.rs | 35 +- crates/vm/src/lib.rs | 1 + crates/vm/src/stdlib/builtins.rs | 5 +- crates/vm/src/vm/mod.rs | 19 +- 8 files changed, 484 insertions(+), 383 deletions(-) delete mode 100644 crates/vm/Lib/python_builtins/_py_exceptiongroup.py create mode 100644 crates/vm/src/exception_group.rs diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py index ffe4dc4f352..2b48530a309 100644 --- a/Lib/test/test_exception_group.py +++ b/Lib/test/test_exception_group.py @@ -15,8 +15,6 @@ def test_exception_is_not_generic_type(self): with self.assertRaisesRegex(TypeError, 'Exception'): Exception[OSError] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_group_is_generic_type(self): E = OSError self.assertIsInstance(ExceptionGroup[E], types.GenericAlias) @@ -810,8 +808,6 @@ def test_split_copies_notes(self): self.assertEqual(match.__notes__, orig_notes + ["match"]) self.assertEqual(rest.__notes__, orig_notes + ["rest"]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_split_does_not_copy_non_sequence_notes(self): # __notes__ should be a sequence, which is shallow copied. # If it is not a sequence, the split parts don't get any notes. @@ -821,8 +817,6 @@ def test_split_does_not_copy_non_sequence_notes(self): self.assertFalse(hasattr(match, '__notes__')) self.assertFalse(hasattr(rest, '__notes__')) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_drive_invalid_return_value(self): class MyEg(ExceptionGroup): def derive(self, excs): diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index ea544f6afac..5ccbfa7ff83 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -5360,8 +5360,6 @@ def test_create_connection(self): expected_errnos = socket_helper.get_socket_conn_refused_errs() self.assertIn(cm.exception.errno, expected_errnos) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_create_connection_all_errors(self): port = socket_helper.find_unused_port() try: diff --git a/crates/vm/Lib/python_builtins/_py_exceptiongroup.py b/crates/vm/Lib/python_builtins/_py_exceptiongroup.py deleted file mode 100644 index 91e9354d8ae..00000000000 --- a/crates/vm/Lib/python_builtins/_py_exceptiongroup.py +++ /dev/null @@ -1,330 +0,0 @@ -# Copied from https://github.com/agronholm/ExceptionGroup/blob/1.2.1/src/exceptiongroup/_exceptions.py -# License: https://github.com/agronholm/exceptiongroup/blob/1.2.1/LICENSE -from __future__ import annotations - -from collections.abc import Callable, Sequence -from functools import partial -from typing import TYPE_CHECKING, Generic, Type, TypeVar, cast, overload - -_BaseExceptionT_co = TypeVar("_BaseExceptionT_co", bound=BaseException, covariant=True) -_BaseExceptionT = TypeVar("_BaseExceptionT", bound=BaseException) -_ExceptionT_co = TypeVar("_ExceptionT_co", bound=Exception, covariant=True) -_ExceptionT = TypeVar("_ExceptionT", bound=Exception) -# using typing.Self would require a typing_extensions dependency on py<3.11 -_ExceptionGroupSelf = TypeVar("_ExceptionGroupSelf", bound="ExceptionGroup") -_BaseExceptionGroupSelf = TypeVar("_BaseExceptionGroupSelf", bound="BaseExceptionGroup") - - -def check_direct_subclass( - exc: BaseException, parents: tuple[type[BaseException]] -) -> bool: - from inspect import getmro # requires rustpython-stdlib - - for cls in getmro(exc.__class__)[:-1]: - if cls in parents: - return True - - return False - - -def get_condition_filter( - condition: type[_BaseExceptionT] - | tuple[type[_BaseExceptionT], ...] - | Callable[[_BaseExceptionT_co], bool], -) -> Callable[[_BaseExceptionT_co], bool]: - from inspect import isclass # requires rustpython-stdlib - - if isclass(condition) and issubclass( - cast(Type[BaseException], condition), BaseException - ): - return partial(check_direct_subclass, parents=(condition,)) - elif isinstance(condition, tuple): - if all(isclass(x) and issubclass(x, BaseException) for x in condition): - return partial(check_direct_subclass, parents=condition) - elif callable(condition): - return cast("Callable[[BaseException], bool]", condition) - - raise TypeError("expected a function, exception type or tuple of exception types") - - -def _derive_and_copy_attributes(self, excs): - eg = self.derive(excs) - eg.__cause__ = self.__cause__ - eg.__context__ = self.__context__ - eg.__traceback__ = self.__traceback__ - if hasattr(self, "__notes__"): - # Create a new list so that add_note() only affects one exceptiongroup - eg.__notes__ = list(self.__notes__) - return eg - - -class BaseExceptionGroup(BaseException, Generic[_BaseExceptionT_co]): - """A combination of multiple unrelated exceptions.""" - - def __new__( - cls: type[_BaseExceptionGroupSelf], - __message: str, - __exceptions: Sequence[_BaseExceptionT_co], - ) -> _BaseExceptionGroupSelf: - if not isinstance(__message, str): - raise TypeError(f"argument 1 must be str, not {type(__message)}") - if not isinstance(__exceptions, Sequence): - raise TypeError("second argument (exceptions) must be a sequence") - if not __exceptions: - raise ValueError( - "second argument (exceptions) must be a non-empty sequence" - ) - - for i, exc in enumerate(__exceptions): - if not isinstance(exc, BaseException): - raise ValueError( - f"Item {i} of second argument (exceptions) is not an exception" - ) - - if cls is BaseExceptionGroup: - if all(isinstance(exc, Exception) for exc in __exceptions): - cls = ExceptionGroup - - if issubclass(cls, Exception): - for exc in __exceptions: - if not isinstance(exc, Exception): - if cls is ExceptionGroup: - raise TypeError( - "Cannot nest BaseExceptions in an ExceptionGroup" - ) - else: - raise TypeError( - f"Cannot nest BaseExceptions in {cls.__name__!r}" - ) - - instance = super().__new__(cls, __message, __exceptions) - instance._message = __message - instance._exceptions = __exceptions - return instance - - def add_note(self, note: str) -> None: - if not isinstance(note, str): - raise TypeError( - f"Expected a string, got note={note!r} (type {type(note).__name__})" - ) - - if not hasattr(self, "__notes__"): - self.__notes__: list[str] = [] - - self.__notes__.append(note) - - @property - def message(self) -> str: - return self._message - - @property - def exceptions( - self, - ) -> tuple[_BaseExceptionT_co | BaseExceptionGroup[_BaseExceptionT_co], ...]: - return tuple(self._exceptions) - - @overload - def subgroup( - self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] - ) -> ExceptionGroup[_ExceptionT] | None: ... - - @overload - def subgroup( - self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...] - ) -> BaseExceptionGroup[_BaseExceptionT] | None: ... - - @overload - def subgroup( - self, - __condition: Callable[[_BaseExceptionT_co | _BaseExceptionGroupSelf], bool], - ) -> BaseExceptionGroup[_BaseExceptionT_co] | None: ... - - def subgroup( - self, - __condition: type[_BaseExceptionT] - | tuple[type[_BaseExceptionT], ...] - | Callable[[_BaseExceptionT_co | _BaseExceptionGroupSelf], bool], - ) -> BaseExceptionGroup[_BaseExceptionT] | None: - condition = get_condition_filter(__condition) - modified = False - if condition(self): - return self - - exceptions: list[BaseException] = [] - for exc in self.exceptions: - if isinstance(exc, BaseExceptionGroup): - subgroup = exc.subgroup(__condition) - if subgroup is not None: - exceptions.append(subgroup) - - if subgroup is not exc: - modified = True - elif condition(exc): - exceptions.append(exc) - else: - modified = True - - if not modified: - return self - elif exceptions: - group = _derive_and_copy_attributes(self, exceptions) - return group - else: - return None - - @overload - def split( - self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] - ) -> tuple[ - ExceptionGroup[_ExceptionT] | None, - BaseExceptionGroup[_BaseExceptionT_co] | None, - ]: ... - - @overload - def split( - self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...] - ) -> tuple[ - BaseExceptionGroup[_BaseExceptionT] | None, - BaseExceptionGroup[_BaseExceptionT_co] | None, - ]: ... - - @overload - def split( - self, - __condition: Callable[[_BaseExceptionT_co | _BaseExceptionGroupSelf], bool], - ) -> tuple[ - BaseExceptionGroup[_BaseExceptionT_co] | None, - BaseExceptionGroup[_BaseExceptionT_co] | None, - ]: ... - - def split( - self, - __condition: type[_BaseExceptionT] - | tuple[type[_BaseExceptionT], ...] - | Callable[[_BaseExceptionT_co], bool], - ) -> ( - tuple[ - ExceptionGroup[_ExceptionT] | None, - BaseExceptionGroup[_BaseExceptionT_co] | None, - ] - | tuple[ - BaseExceptionGroup[_BaseExceptionT] | None, - BaseExceptionGroup[_BaseExceptionT_co] | None, - ] - | tuple[ - BaseExceptionGroup[_BaseExceptionT_co] | None, - BaseExceptionGroup[_BaseExceptionT_co] | None, - ] - ): - condition = get_condition_filter(__condition) - if condition(self): - return self, None - - matching_exceptions: list[BaseException] = [] - nonmatching_exceptions: list[BaseException] = [] - for exc in self.exceptions: - if isinstance(exc, BaseExceptionGroup): - matching, nonmatching = exc.split(condition) - if matching is not None: - matching_exceptions.append(matching) - - if nonmatching is not None: - nonmatching_exceptions.append(nonmatching) - elif condition(exc): - matching_exceptions.append(exc) - else: - nonmatching_exceptions.append(exc) - - matching_group: _BaseExceptionGroupSelf | None = None - if matching_exceptions: - matching_group = _derive_and_copy_attributes(self, matching_exceptions) - - nonmatching_group: _BaseExceptionGroupSelf | None = None - if nonmatching_exceptions: - nonmatching_group = _derive_and_copy_attributes( - self, nonmatching_exceptions - ) - - return matching_group, nonmatching_group - - @overload - def derive(self, __excs: Sequence[_ExceptionT]) -> ExceptionGroup[_ExceptionT]: ... - - @overload - def derive( - self, __excs: Sequence[_BaseExceptionT] - ) -> BaseExceptionGroup[_BaseExceptionT]: ... - - def derive( - self, __excs: Sequence[_BaseExceptionT] - ) -> BaseExceptionGroup[_BaseExceptionT]: - return BaseExceptionGroup(self.message, __excs) - - def __str__(self) -> str: - suffix = "" if len(self._exceptions) == 1 else "s" - return f"{self.message} ({len(self._exceptions)} sub-exception{suffix})" - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.message!r}, {self._exceptions!r})" - - -class ExceptionGroup(BaseExceptionGroup[_ExceptionT_co], Exception): - def __new__( - cls: type[_ExceptionGroupSelf], - __message: str, - __exceptions: Sequence[_ExceptionT_co], - ) -> _ExceptionGroupSelf: - return super().__new__(cls, __message, __exceptions) - - if TYPE_CHECKING: - - @property - def exceptions( - self, - ) -> tuple[_ExceptionT_co | ExceptionGroup[_ExceptionT_co], ...]: ... - - @overload # type: ignore[override] - def subgroup( - self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] - ) -> ExceptionGroup[_ExceptionT] | None: ... - - @overload - def subgroup( - self, __condition: Callable[[_ExceptionT_co | _ExceptionGroupSelf], bool] - ) -> ExceptionGroup[_ExceptionT_co] | None: ... - - def subgroup( - self, - __condition: type[_ExceptionT] - | tuple[type[_ExceptionT], ...] - | Callable[[_ExceptionT_co], bool], - ) -> ExceptionGroup[_ExceptionT] | None: - return super().subgroup(__condition) - - @overload - def split( - self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] - ) -> tuple[ - ExceptionGroup[_ExceptionT] | None, ExceptionGroup[_ExceptionT_co] | None - ]: ... - - @overload - def split( - self, __condition: Callable[[_ExceptionT_co | _ExceptionGroupSelf], bool] - ) -> tuple[ - ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None - ]: ... - - def split( - self: _ExceptionGroupSelf, - __condition: type[_ExceptionT] - | tuple[type[_ExceptionT], ...] - | Callable[[_ExceptionT_co], bool], - ) -> tuple[ - ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None - ]: - return super().split(__condition) - - -BaseExceptionGroup.__module__ = 'builtins' -ExceptionGroup.__module__ = 'builtins' diff --git a/crates/vm/src/exception_group.rs b/crates/vm/src/exception_group.rs new file mode 100644 index 00000000000..5eb011960e1 --- /dev/null +++ b/crates/vm/src/exception_group.rs @@ -0,0 +1,469 @@ +//! ExceptionGroup implementation for Python 3.11+ +//! +//! This module implements BaseExceptionGroup and ExceptionGroup with multiple inheritance support. + +use crate::builtins::{PyList, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef}; +use crate::function::{ArgIterable, FuncArgs}; +use crate::types::{PyTypeFlags, PyTypeSlots}; +use crate::{ + AsObject, Context, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine, +}; + +use crate::exceptions::types::PyBaseException; + +/// Create dynamic ExceptionGroup type with multiple inheritance +fn create_exception_group(ctx: &Context) -> PyRef<PyType> { + let excs = &ctx.exceptions; + let exception_group_slots = PyTypeSlots { + flags: PyTypeFlags::heap_type_flags() | PyTypeFlags::HAS_DICT, + ..Default::default() + }; + PyType::new_heap( + "ExceptionGroup", + vec![ + excs.base_exception_group.to_owned(), + excs.exception_type.to_owned(), + ], + Default::default(), + exception_group_slots, + ctx.types.type_type.to_owned(), + ctx, + ) + .expect("Failed to create ExceptionGroup type with multiple inheritance") +} + +pub fn exception_group() -> &'static Py<PyType> { + ::rustpython_vm::common::static_cell! { + static CELL: ::rustpython_vm::builtins::PyTypeRef; + } + CELL.get_or_init(|| create_exception_group(Context::genesis())) +} + +pub(super) mod types { + use super::*; + use crate::PyPayload; + use crate::builtins::PyGenericAlias; + + #[pyexception(name, base = PyBaseException, ctx = "base_exception_group")] + #[derive(Debug)] + pub struct PyBaseExceptionGroup {} + + #[pyexception] + impl PyBaseExceptionGroup { + #[pyclassmethod] + fn __class_getitem__( + cls: PyTypeRef, + args: PyObjectRef, + vm: &VirtualMachine, + ) -> PyGenericAlias { + PyGenericAlias::from_args(cls, args, vm) + } + + #[pyslot] + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // Validate exactly 2 positional arguments + if args.args.len() != 2 { + return Err(vm.new_type_error(format!( + "BaseExceptionGroup.__new__() takes exactly 2 positional arguments ({} given)", + args.args.len() + ))); + } + + // Validate message is str + let message = args.args[0].clone(); + if !message.fast_isinstance(vm.ctx.types.str_type) { + return Err(vm.new_type_error(format!( + "argument 1 must be str, not {}", + message.class().name() + ))); + } + + // Validate exceptions is a sequence (not set or None) + let exceptions_arg = &args.args[1]; + + // Check for set/frozenset (not a sequence - unordered) + if exceptions_arg.fast_isinstance(vm.ctx.types.set_type) + || exceptions_arg.fast_isinstance(vm.ctx.types.frozenset_type) + { + return Err(vm.new_type_error("second argument (exceptions) must be a sequence")); + } + + // Check for None + if exceptions_arg.is(&vm.ctx.none) { + return Err(vm.new_type_error("second argument (exceptions) must be a sequence")); + } + + let exceptions: Vec<PyObjectRef> = exceptions_arg.try_to_value(vm).map_err(|_| { + vm.new_type_error("second argument (exceptions) must be a sequence") + })?; + + // Validate non-empty + if exceptions.is_empty() { + return Err(vm.new_value_error( + "second argument (exceptions) must be a non-empty sequence".to_owned(), + )); + } + + // Validate all items are BaseException instances + let mut has_non_exception = false; + for (i, exc) in exceptions.iter().enumerate() { + if !exc.fast_isinstance(vm.ctx.exceptions.base_exception_type) { + return Err(vm.new_value_error(format!( + "Item {} of second argument (exceptions) is not an exception", + i + ))); + } + // Check if any exception is not an Exception subclass + // With dynamic ExceptionGroup (inherits from both BaseExceptionGroup and Exception), + // ExceptionGroup instances are automatically instances of Exception + if !exc.fast_isinstance(vm.ctx.exceptions.exception_type) { + has_non_exception = true; + } + } + + // Get the dynamic ExceptionGroup type + let exception_group_type = crate::exception_group::exception_group(); + + // Determine the actual class to use + let actual_cls = if cls.is(exception_group_type) { + // ExceptionGroup cannot contain BaseExceptions that are not Exception + if has_non_exception { + return Err( + vm.new_type_error("Cannot nest BaseExceptions in an ExceptionGroup") + ); + } + cls + } else if cls.is(vm.ctx.exceptions.base_exception_group) { + // Auto-convert to ExceptionGroup if all are Exception subclasses + if !has_non_exception { + exception_group_type.to_owned() + } else { + cls + } + } else { + // User-defined subclass + if has_non_exception && cls.fast_issubclass(vm.ctx.exceptions.exception_type) { + return Err(vm.new_type_error(format!( + "Cannot nest BaseExceptions in '{}'", + cls.name() + ))); + } + cls + }; + + // Create the exception with (message, exceptions_tuple) as args + let exceptions_tuple = vm.ctx.new_tuple(exceptions); + let init_args = vec![message, exceptions_tuple.into()]; + PyBaseException::new(init_args, vm) + .into_ref_with_type(vm, actual_cls) + .map(Into::into) + } + + #[pyslot] + #[pymethod(name = "__init__")] + fn slot_init(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> { + // CPython's BaseExceptionGroup.__init__ just calls BaseException.__init__ + // which stores args as-is. Since __new__ already set up the correct args + // (message, exceptions_tuple), we don't need to do anything here. + // This also allows subclasses to pass extra arguments to __new__ without + // __init__ complaining about argument count. + Ok(()) + } + + #[pymethod] + fn derive( + zelf: PyRef<PyBaseException>, + excs: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + let message = zelf.get_arg(0).unwrap_or_else(|| vm.ctx.new_str("").into()); + vm.invoke_exception( + vm.ctx.exceptions.base_exception_group.to_owned(), + vec![message, excs], + ) + .map(|e| e.into()) + } + + #[pymethod] + fn subgroup( + zelf: PyRef<PyBaseException>, + condition: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + let matcher = get_condition_matcher(&condition, vm)?; + + // If self matches the condition entirely, return self + let zelf_obj: PyObjectRef = zelf.clone().into(); + if matcher.check(&zelf_obj, vm)? { + return Ok(zelf_obj); + } + + let exceptions = get_exceptions_tuple(&zelf, vm)?; + let mut matching: Vec<PyObjectRef> = Vec::new(); + let mut modified = false; + + for exc in exceptions { + if is_base_exception_group(&exc, vm) { + // Recursive call for nested groups + let subgroup_result = vm.call_method(&exc, "subgroup", (condition.clone(),))?; + if !vm.is_none(&subgroup_result) { + matching.push(subgroup_result.clone()); + } + if !subgroup_result.is(&exc) { + modified = true; + } + } else if matcher.check(&exc, vm)? { + matching.push(exc); + } else { + modified = true; + } + } + + if !modified { + return Ok(zelf.clone().into()); + } + + if matching.is_empty() { + return Ok(vm.ctx.none()); + } + + // Create new group with matching exceptions and copy metadata + derive_and_copy_attributes(&zelf, matching, vm) + } + + #[pymethod] + fn split( + zelf: PyRef<PyBaseException>, + condition: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<PyTupleRef> { + let matcher = get_condition_matcher(&condition, vm)?; + + // If self matches the condition entirely + let zelf_obj: PyObjectRef = zelf.clone().into(); + if matcher.check(&zelf_obj, vm)? { + return Ok(vm.ctx.new_tuple(vec![zelf_obj, vm.ctx.none()])); + } + + let exceptions = get_exceptions_tuple(&zelf, vm)?; + let mut matching: Vec<PyObjectRef> = Vec::new(); + let mut rest: Vec<PyObjectRef> = Vec::new(); + + for exc in exceptions { + if is_base_exception_group(&exc, vm) { + let result = vm.call_method(&exc, "split", (condition.clone(),))?; + let result_tuple: PyTupleRef = result.try_into_value(vm)?; + let match_part = result_tuple + .first() + .cloned() + .unwrap_or_else(|| vm.ctx.none()); + let rest_part = result_tuple + .get(1) + .cloned() + .unwrap_or_else(|| vm.ctx.none()); + + if !vm.is_none(&match_part) { + matching.push(match_part); + } + if !vm.is_none(&rest_part) { + rest.push(rest_part); + } + } else if matcher.check(&exc, vm)? { + matching.push(exc); + } else { + rest.push(exc); + } + } + + let match_group = if matching.is_empty() { + vm.ctx.none() + } else { + derive_and_copy_attributes(&zelf, matching, vm)? + }; + + let rest_group = if rest.is_empty() { + vm.ctx.none() + } else { + derive_and_copy_attributes(&zelf, rest, vm)? + }; + + Ok(vm.ctx.new_tuple(vec![match_group, rest_group])) + } + + #[pymethod] + fn __str__(zelf: PyRef<PyBaseException>, vm: &VirtualMachine) -> PyResult<String> { + let message = zelf + .get_arg(0) + .map(|m| m.str(vm)) + .transpose()? + .map(|s| s.as_str().to_owned()) + .unwrap_or_default(); + + let num_excs = zelf + .get_arg(1) + .and_then(|obj| obj.downcast_ref::<PyTuple>().map(|t| t.len())) + .unwrap_or(0); + + let suffix = if num_excs == 1 { "" } else { "s" }; + Ok(format!( + "{} ({} sub-exception{})", + message, num_excs, suffix + )) + } + + #[pymethod] + fn __repr__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { + Self::slot_repr(&zelf, vm) + } + + #[pyslot] + fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> { + let zelf = zelf + .downcast_ref::<PyBaseException>() + .expect("exception group must be BaseException"); + let class_name = zelf.class().name().to_owned(); + let message = zelf + .get_arg(0) + .map(|m| m.repr(vm)) + .transpose()? + .map(|s| s.as_str().to_owned()) + .unwrap_or_else(|| "''".to_owned()); + + // Format exceptions as list [exc1, exc2, ...] instead of tuple (exc1, exc2, ...) + // CPython displays exceptions in list format even though they're stored as tuple + let exceptions_str = if let Some(exceptions_obj) = zelf.get_arg(1) { + // Get exceptions using ArgIterable for robustness + let iter: ArgIterable<PyObjectRef> = + ArgIterable::try_from_object(vm, exceptions_obj.clone())?; + let mut exc_repr_list = Vec::new(); + for exc in iter.iter(vm)? { + exc_repr_list.push(exc?.repr(vm)?.as_str().to_owned()); + } + format!("[{}]", exc_repr_list.join(", ")) + } else { + "[]".to_owned() + }; + + Ok(vm + .ctx + .new_str(format!("{}({}, {})", class_name, message, exceptions_str))) + } + } + + // Helper functions for ExceptionGroup + fn is_base_exception_group(obj: &PyObjectRef, vm: &VirtualMachine) -> bool { + obj.fast_isinstance(vm.ctx.exceptions.base_exception_group) + } + + fn get_exceptions_tuple( + exc: &PyRef<PyBaseException>, + vm: &VirtualMachine, + ) -> PyResult<Vec<PyObjectRef>> { + let obj = exc + .get_arg(1) + .ok_or_else(|| vm.new_type_error("exceptions must be a tuple"))?; + let tuple = obj + .downcast_ref::<PyTuple>() + .ok_or_else(|| vm.new_type_error("exceptions must be a tuple"))?; + Ok(tuple.to_vec()) + } + + enum ConditionMatcher { + Type(PyTypeRef), + Types(Vec<PyTypeRef>), + Callable(PyObjectRef), + } + + fn get_condition_matcher( + condition: &PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<ConditionMatcher> { + // If it's a type and subclass of BaseException + if let Some(typ) = condition.downcast_ref::<PyType>() + && typ.fast_issubclass(vm.ctx.exceptions.base_exception_type) + { + return Ok(ConditionMatcher::Type(typ.to_owned())); + } + + // If it's a tuple of types + if let Some(tuple) = condition.downcast_ref::<PyTuple>() { + let mut types = Vec::new(); + for item in tuple.iter() { + let typ: PyTypeRef = item.clone().try_into_value(vm).map_err(|_| { + vm.new_type_error( + "expected a function, exception type or tuple of exception types", + ) + })?; + if !typ.fast_issubclass(vm.ctx.exceptions.base_exception_type) { + return Err(vm.new_type_error( + "expected a function, exception type or tuple of exception types", + )); + } + types.push(typ); + } + if !types.is_empty() { + return Ok(ConditionMatcher::Types(types)); + } + } + + // If it's callable (but not a type) + if condition.is_callable() && condition.downcast_ref::<PyType>().is_none() { + return Ok(ConditionMatcher::Callable(condition.clone())); + } + + Err(vm.new_type_error("expected a function, exception type or tuple of exception types")) + } + + impl ConditionMatcher { + fn check(&self, exc: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { + match self { + ConditionMatcher::Type(typ) => Ok(exc.fast_isinstance(typ)), + ConditionMatcher::Types(types) => Ok(types.iter().any(|t| exc.fast_isinstance(t))), + ConditionMatcher::Callable(func) => { + let result = func.call((exc.clone(),), vm)?; + result.try_to_bool(vm) + } + } + } + } + + fn derive_and_copy_attributes( + orig: &PyRef<PyBaseException>, + excs: Vec<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + // Call derive method to create new group + let excs_seq = vm.ctx.new_list(excs); + let new_group = vm.call_method(orig.as_object(), "derive", (excs_seq,))?; + + // Verify derive returned a BaseExceptionGroup + if !is_base_exception_group(&new_group, vm) { + return Err(vm.new_type_error("derive must return an instance of BaseExceptionGroup")); + } + + // Copy traceback + if let Some(tb) = orig.__traceback__() { + new_group.set_attr("__traceback__", tb, vm)?; + } + + // Copy context + if let Some(ctx) = orig.__context__() { + new_group.set_attr("__context__", ctx, vm)?; + } + + // Copy cause + if let Some(cause) = orig.__cause__() { + new_group.set_attr("__cause__", cause, vm)?; + } + + // Copy notes (if present) - make a copy of the list + if let Ok(notes) = orig.as_object().get_attr("__notes__", vm) + && let Some(notes_list) = notes.downcast_ref::<PyList>() + { + let notes_copy = vm.ctx.new_list(notes_list.borrow_vec().to_vec()); + new_group.set_attr("__notes__", notes_copy, vm)?; + } + + Ok(new_group) + } +} diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 1916972a799..9d3d239ad46 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -22,6 +22,8 @@ use std::{ io::{self, BufRead, BufReader}, }; +pub use super::exception_group::exception_group; + unsafe impl Traverse for PyBaseException { fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { self.traceback.traverse(tracer_fn); @@ -445,11 +447,10 @@ impl ExceptionCtor { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct ExceptionZoo { pub base_exception_type: &'static Py<PyType>, pub base_exception_group: &'static Py<PyType>, - pub exception_group: &'static Py<PyType>, pub system_exit: &'static Py<PyType>, pub keyboard_interrupt: &'static Py<PyType>, pub generator_exit: &'static Py<PyType>, @@ -742,7 +743,6 @@ impl ExceptionZoo { // Sorted By Hierarchy then alphabetized. let base_exception_group = PyBaseExceptionGroup::init_builtin_type(); - let exception_group = PyExceptionGroup::init_builtin_type(); let system_exit = PySystemExit::init_builtin_type(); let keyboard_interrupt = PyKeyboardInterrupt::init_builtin_type(); let generator_exit = PyGeneratorExit::init_builtin_type(); @@ -830,7 +830,6 @@ impl ExceptionZoo { Self { base_exception_type, base_exception_group, - exception_group, system_exit, keyboard_interrupt, generator_exit, @@ -917,7 +916,7 @@ impl ExceptionZoo { "message" => ctx.new_readonly_getset("message", excs.base_exception_group, make_arg_getter(0)), "exceptions" => ctx.new_readonly_getset("exceptions", excs.base_exception_group, make_arg_getter(1)), }); - extend_exception!(PyExceptionGroup, ctx, excs.exception_group); + extend_exception!(PySystemExit, ctx, excs.system_exit, { "code" => ctx.new_readonly_getset("code", excs.system_exit, system_exit_code), }); @@ -1220,8 +1219,7 @@ pub(super) mod types { use crate::{ AsObject, PyObjectRef, PyRef, PyResult, VirtualMachine, builtins::{ - PyGenericAlias, PyInt, PyStrRef, PyTupleRef, PyTypeRef, traceback::PyTracebackRef, - tuple::IntoPyTuple, + PyInt, PyStrRef, PyTupleRef, PyTypeRef, traceback::PyTracebackRef, tuple::IntoPyTuple, }, convert::ToPyResult, function::{ArgBytesLike, FuncArgs}, @@ -1231,6 +1229,9 @@ pub(super) mod types { use itertools::Itertools; use rustpython_common::str::UnicodeEscapeCodepoint; + // Re-export exception group types from dedicated module + pub use crate::exception_group::types::PyBaseExceptionGroup; + // This module is designed to be used as `use builtins::*;`. // Do not add any pub symbols not included in builtins module. // `PyBaseExceptionRef` is the only exception. @@ -1252,26 +1253,6 @@ pub(super) mod types { #[derive(Debug)] pub struct PySystemExit {} - #[pyexception(name, base = PyBaseException, ctx = "base_exception_group")] - #[derive(Debug)] - pub struct PyBaseExceptionGroup {} - - #[pyexception] - impl PyBaseExceptionGroup { - #[pyclassmethod] - fn __class_getitem__( - cls: PyTypeRef, - args: PyObjectRef, - vm: &VirtualMachine, - ) -> PyGenericAlias { - PyGenericAlias::from_args(cls, args, vm) - } - } - - #[pyexception(name, base = PyBaseExceptionGroup, ctx = "exception_group", impl)] - #[derive(Debug)] - pub struct PyExceptionGroup {} - #[pyexception(name, base = PyBaseException, ctx = "generator_exit", impl)] #[derive(Debug)] pub struct PyGeneratorExit {} diff --git a/crates/vm/src/lib.rs b/crates/vm/src/lib.rs index 94147345a6b..923b33d2acc 100644 --- a/crates/vm/src/lib.rs +++ b/crates/vm/src/lib.rs @@ -55,6 +55,7 @@ mod dict_inner; #[cfg(feature = "rustpython-compiler")] pub mod eval; +mod exception_group; pub mod exceptions; pub mod format; pub mod frame; diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index fb665dbdefb..5d4a28bf183 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -1070,6 +1070,9 @@ pub fn init_module(vm: &VirtualMachine, module: &Py<PyModule>) { builtins::extend_module(vm, module).unwrap(); let debug_mode: bool = vm.state.settings.optimize == 0; + // Create dynamic ExceptionGroup with multiple inheritance (BaseExceptionGroup + Exception) + let exception_group = crate::exception_group::exception_group(); + extend_module!(vm, module, { "__debug__" => ctx.new_bool(debug_mode), @@ -1110,7 +1113,7 @@ pub fn init_module(vm: &VirtualMachine, module: &Py<PyModule>) { // Exceptions: "BaseException" => ctx.exceptions.base_exception_type.to_owned(), "BaseExceptionGroup" => ctx.exceptions.base_exception_group.to_owned(), - "ExceptionGroup" => ctx.exceptions.exception_group.to_owned(), + "ExceptionGroup" => exception_group.to_owned(), "SystemExit" => ctx.exceptions.system_exit.to_owned(), "KeyboardInterrupt" => ctx.exceptions.keyboard_interrupt.to_owned(), "GeneratorExit" => ctx.exceptions.generator_exit.to_owned(), diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index e6217e3e35d..0d21d708ac8 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -373,11 +373,11 @@ impl VirtualMachine { self.print_exception(e); } - let expect_stdlib = + let _expect_stdlib = cfg!(feature = "freeze-stdlib") || !self.state.settings.path_list.is_empty(); #[cfg(feature = "encodings")] - if expect_stdlib { + if _expect_stdlib { if let Err(e) = self.import_encodings() { eprintln!( "encodings initialization failed. Only utf-8 encoding will be supported." @@ -394,21 +394,6 @@ impl VirtualMachine { ); } - if expect_stdlib { - // enable python-implemented ExceptionGroup when stdlib exists - let py_core_init = || -> PyResult<()> { - let exception_group = import::import_frozen(self, "_py_exceptiongroup")?; - let base_exception_group = exception_group.get_attr("BaseExceptionGroup", self)?; - self.builtins - .set_attr("BaseExceptionGroup", base_exception_group, self)?; - let exception_group = exception_group.get_attr("ExceptionGroup", self)?; - self.builtins - .set_attr("ExceptionGroup", exception_group, self)?; - Ok(()) - }; - self.expect_pyresult(py_core_init(), "exceptiongroup initialization failed"); - } - self.initialized = true; } From c578861598f26ef530cb9d3f65f03104d3c9c9e6 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:17:53 +0900 Subject: [PATCH 449/819] nt.terminalsize (#6374) * terminalsize * use _get_osfhandle --- crates/vm/src/stdlib/nt.rs | 57 +++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 0bbb5881a9f..c4d71ff0e95 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -203,32 +203,49 @@ pub(crate) mod module { fd: OptionalArg<i32>, vm: &VirtualMachine, ) -> PyResult<_os::TerminalSizeData> { - let (columns, lines) = { - let stdhandle = match fd { - OptionalArg::Present(0) => Console::STD_INPUT_HANDLE, - OptionalArg::Present(1) | OptionalArg::Missing => Console::STD_OUTPUT_HANDLE, - OptionalArg::Present(2) => Console::STD_ERROR_HANDLE, - _ => return Err(vm.new_value_error("bad file descriptor")), - }; - let h = unsafe { Console::GetStdHandle(stdhandle) }; - if h.is_null() { - return Err(vm.new_os_error("handle cannot be retrieved".to_owned())); + let fd = fd.unwrap_or(1); // default to stdout + + // Use _get_osfhandle for all fds + let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd) }; + let handle = crt_fd::as_handle(borrowed).map_err(|e| e.to_pyexception(vm))?; + let h = handle.as_raw_handle() as Foundation::HANDLE; + + let mut csbi = MaybeUninit::uninit(); + let ret = unsafe { Console::GetConsoleScreenBufferInfo(h, csbi.as_mut_ptr()) }; + if ret == 0 { + // Check if error is due to lack of read access on a console handle + // ERROR_ACCESS_DENIED (5) means it's a console but without read permission + // In that case, try opening CONOUT$ directly with read access + let err = unsafe { Foundation::GetLastError() }; + if err != Foundation::ERROR_ACCESS_DENIED { + return Err(errno_err(vm)); } - if h == INVALID_HANDLE_VALUE { + let conout: Vec<u16> = "CONOUT$\0".encode_utf16().collect(); + let console_handle = unsafe { + FileSystem::CreateFileW( + conout.as_ptr(), + Foundation::GENERIC_READ | Foundation::GENERIC_WRITE, + FileSystem::FILE_SHARE_READ | FileSystem::FILE_SHARE_WRITE, + std::ptr::null(), + FileSystem::OPEN_EXISTING, + 0, + std::ptr::null_mut(), + ) + }; + if console_handle == INVALID_HANDLE_VALUE { return Err(errno_err(vm)); } - let mut csbi = MaybeUninit::uninit(); - let ret = unsafe { Console::GetConsoleScreenBufferInfo(h, csbi.as_mut_ptr()) }; - let csbi = unsafe { csbi.assume_init() }; + let ret = + unsafe { Console::GetConsoleScreenBufferInfo(console_handle, csbi.as_mut_ptr()) }; + unsafe { Foundation::CloseHandle(console_handle) }; if ret == 0 { return Err(errno_err(vm)); } - let w = csbi.srWindow; - ( - (w.Right - w.Left + 1) as usize, - (w.Bottom - w.Top + 1) as usize, - ) - }; + } + let csbi = unsafe { csbi.assume_init() }; + let w = csbi.srWindow; + let columns = (w.Right - w.Left + 1) as usize; + let lines = (w.Bottom - w.Top + 1) as usize; Ok(_os::TerminalSizeData { columns, lines }) } From 6003c87582c7481ee236dbf552b07a657b77ddcf Mon Sep 17 00:00:00 2001 From: Sean Lawlor <slawlor@slawlor.com> Date: Tue, 9 Dec 2025 09:19:42 -0500 Subject: [PATCH 450/819] Freeze ruff libraries to the REV that the TAG points to. (#6375) Tags can move and be re-aliased to different revisions, and this actually freezes the rev (similar to how a published crate would) so that it only applies to this specific commit hash. --- Cargo.lock | 10 +++++----- Cargo.toml | 10 ++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b083bbb97ae..87e8d381bc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2797,7 +2797,7 @@ dependencies = [ [[package]] name = "ruff_python_ast" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" +source = "git+https://github.com/astral-sh/ruff.git?rev=2bffef59665ce7d2630dfd72ee99846663660db8#2bffef59665ce7d2630dfd72ee99846663660db8" dependencies = [ "aho-corasick", "bitflags 2.10.0", @@ -2816,7 +2816,7 @@ dependencies = [ [[package]] name = "ruff_python_parser" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" +source = "git+https://github.com/astral-sh/ruff.git?rev=2bffef59665ce7d2630dfd72ee99846663660db8#2bffef59665ce7d2630dfd72ee99846663660db8" dependencies = [ "bitflags 2.10.0", "bstr", @@ -2836,7 +2836,7 @@ dependencies = [ [[package]] name = "ruff_python_trivia" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" +source = "git+https://github.com/astral-sh/ruff.git?rev=2bffef59665ce7d2630dfd72ee99846663660db8#2bffef59665ce7d2630dfd72ee99846663660db8" dependencies = [ "itertools 0.14.0", "ruff_source_file", @@ -2847,7 +2847,7 @@ dependencies = [ [[package]] name = "ruff_source_file" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" +source = "git+https://github.com/astral-sh/ruff.git?rev=2bffef59665ce7d2630dfd72ee99846663660db8#2bffef59665ce7d2630dfd72ee99846663660db8" dependencies = [ "memchr", "ruff_text_size", @@ -2856,7 +2856,7 @@ dependencies = [ [[package]] name = "ruff_text_size" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.14.1#2bffef59665ce7d2630dfd72ee99846663660db8" +source = "git+https://github.com/astral-sh/ruff.git?rev=2bffef59665ce7d2630dfd72ee99846663660db8#2bffef59665ce7d2630dfd72ee99846663660db8" dependencies = [ "get-size2", ] diff --git a/Cargo.toml b/Cargo.toml index 04e089d00e7..5429ed2c827 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -151,10 +151,12 @@ rustpython-sre_engine = { path = "crates/sre_engine", version = "0.4.0" } rustpython-wtf8 = { path = "crates/wtf8", version = "0.4.0" } rustpython-doc = { path = "crates/doc", version = "0.4.0" } -ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } -ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } -ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } -ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } +# Ruff tag 0.14.1 is based on commit 2bffef59665ce7d2630dfd72ee99846663660db8 +# at the time of this capture. We use the commit hash to ensure reproducible builds. +ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", rev = "2bffef59665ce7d2630dfd72ee99846663660db8" } +ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", rev = "2bffef59665ce7d2630dfd72ee99846663660db8" } +ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", rev = "2bffef59665ce7d2630dfd72ee99846663660db8" } +ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", rev = "2bffef59665ce7d2630dfd72ee99846663660db8" } phf = { version = "0.13.1", default-features = false, features = ["macros"]} ahash = "0.8.12" From 8bc46cf83da2176ad169aeb45f7a19b61ed07240 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:40:54 +0900 Subject: [PATCH 451/819] Fix windows inode to use u128 (#6377) --- crates/vm/src/stdlib/os.rs | 38 ++++++++++++++++++++++++++++++++------ crates/vm/src/windows.rs | 7 +++++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index e9e1337235b..6d4a0836a02 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -510,7 +510,9 @@ pub(super) mod _os { lstat: OnceCell<PyObjectRef>, #[cfg(unix)] ino: AtomicCell<u64>, - #[cfg(not(unix))] + #[cfg(windows)] + ino: AtomicCell<Option<u128>>, + #[cfg(not(any(unix, windows)))] ino: AtomicCell<Option<u64>>, } @@ -608,9 +610,9 @@ pub(super) mod _os { Ok(stat.clone()) } - #[cfg(not(unix))] + #[cfg(windows)] #[pymethod] - fn inode(&self, vm: &VirtualMachine) -> PyResult<u64> { + fn inode(&self, vm: &VirtualMachine) -> PyResult<u128> { match self.ino.load() { Some(ino) => Ok(ino), None => { @@ -625,9 +627,14 @@ pub(super) mod _os { ) .map_err(|e| e.into_pyexception(vm))? .ok_or_else(|| crate::exceptions::cstring_error(vm))?; + // On Windows, combine st_ino and st_ino_high into 128-bit value + #[cfg(windows)] + let ino: u128 = stat.st_ino as u128 | ((stat.st_ino_high as u128) << 64); + #[cfg(not(windows))] + let ino: u128 = stat.st_ino as u128; // Err(T) means other thread set `ino` at the mean time which is safe to ignore - let _ = self.ino.compare_exchange(None, Some(stat.st_ino)); - Ok(stat.st_ino) + let _ = self.ino.compare_exchange(None, Some(ino)); + Ok(ino) } } } @@ -638,6 +645,12 @@ pub(super) mod _os { Ok(self.ino.load()) } + #[cfg(not(any(unix, windows)))] + #[pymethod] + fn inode(&self, _vm: &VirtualMachine) -> PyResult<Option<u64>> { + Ok(self.ino.load()) + } + #[cfg(not(windows))] #[pymethod] const fn is_junction(&self, _vm: &VirtualMachine) -> PyResult<bool> { @@ -737,6 +750,12 @@ pub(super) mod _os { use std::os::unix::fs::DirEntryExt; entry.ino() }; + // TODO: wasi is nightly + // #[cfg(target_os = "wasi")] + // let ino = { + // use std::os::wasi::fs::DirEntryExt; + // entry.ino() + // }; #[cfg(not(unix))] let ino = None; @@ -882,9 +901,16 @@ pub(super) mod _os { #[cfg(not(windows))] let st_file_attributes = 0; + // On Windows, combine st_ino and st_ino_high into a 128-bit value + // like _pystat_l128_from_l64_l64 + #[cfg(windows)] + let st_ino: u128 = stat.st_ino as u128 | ((stat.st_ino_high as u128) << 64); + #[cfg(not(windows))] + let st_ino = stat.st_ino; + Self { st_mode: vm.ctx.new_pyref(stat.st_mode), - st_ino: vm.ctx.new_pyref(stat.st_ino), + st_ino: vm.ctx.new_pyref(st_ino), st_dev: vm.ctx.new_pyref(stat.st_dev), st_nlink: vm.ctx.new_pyref(stat.st_nlink), st_uid: vm.ctx.new_pyref(stat.st_uid), diff --git a/crates/vm/src/windows.rs b/crates/vm/src/windows.rs index 9b48e54e120..394714be95f 100644 --- a/crates/vm/src/windows.rs +++ b/crates/vm/src/windows.rs @@ -515,8 +515,11 @@ fn win32_xstat_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatStruct> { let mut result = crate::common::fileutils::windows::stat_basic_info_to_stat(&stat_info); - result.update_st_mode_from_path(path, stat_info.FileAttributes); - return Ok(result); + // If st_ino is 0, fall through to slow path to get proper file ID + if result.st_ino != 0 || result.st_ino_high != 0 { + result.update_st_mode_from_path(path, stat_info.FileAttributes); + return Ok(result); + } } } Err(e) => { From 26b3445d14ca44514fa13c02d736c3b140d72d20 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:49:32 +0900 Subject: [PATCH 452/819] windows pipe/getppid (#6378) * pipe/pid * libc first --- Lib/test/test_subprocess.py | 3 - crates/common/src/fileutils.rs | 10 +-- crates/vm/src/stdlib/msvcrt.rs | 6 +- crates/vm/src/stdlib/nt.rs | 113 +++++++++++++++++++++++++++++++-- 4 files changed, 112 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 28b5124b8ea..29ed4639c42 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1016,7 +1016,6 @@ def test_communicate_returns(self): self.assertEqual(stdout, None) self.assertEqual(stderr, None) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_communicate_pipe_buf(self): # communicate() with writes larger than pipe_buf # This test will probably deadlock rather than fail, if @@ -3564,8 +3563,6 @@ def test_close_fds(self): close_fds=True) self.assertEqual(rc, 47) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_close_fds_with_stdio(self): import msvcrt diff --git a/crates/common/src/fileutils.rs b/crates/common/src/fileutils.rs index a1c6eec8178..8e21ef656c6 100644 --- a/crates/common/src/fileutils.rs +++ b/crates/common/src/fileutils.rs @@ -460,22 +460,16 @@ pub fn fopen(path: &std::path::Path, mode: &str) -> std::io::Result<*mut libc::F { use std::os::windows::io::IntoRawHandle; - // Declare Windows CRT functions - unsafe extern "C" { - fn _open_osfhandle(handle: isize, flags: libc::c_int) -> libc::c_int; - fn _fdopen(fd: libc::c_int, mode: *const libc::c_char) -> *mut libc::FILE; - } - // Convert File handle to CRT file descriptor let handle = file.into_raw_handle(); - let fd = unsafe { _open_osfhandle(handle as isize, libc::O_RDONLY) }; + let fd = unsafe { libc::open_osfhandle(handle as isize, libc::O_RDONLY) }; if fd == -1 { return Err(std::io::Error::last_os_error()); } // Convert fd to FILE* let mode_cstr = CString::new(mode).unwrap(); - let fp = unsafe { _fdopen(fd, mode_cstr.as_ptr()) }; + let fp = unsafe { libc::fdopen(fd, mode_cstr.as_ptr()) }; if fp.is_null() { unsafe { libc::close(fd) }; return Err(std::io::Error::last_os_error()); diff --git a/crates/vm/src/stdlib/msvcrt.rs b/crates/vm/src/stdlib/msvcrt.rs index aee36adcf22..70ac09f8a05 100644 --- a/crates/vm/src/stdlib/msvcrt.rs +++ b/crates/vm/src/stdlib/msvcrt.rs @@ -88,13 +88,9 @@ mod msvcrt { } } - unsafe extern "C" { - fn _open_osfhandle(osfhandle: isize, flags: i32) -> i32; - } - #[pyfunction] fn open_osfhandle(handle: isize, flags: i32, vm: &VirtualMachine) -> PyResult<i32> { - let ret = unsafe { suppress_iph!(_open_osfhandle(handle, flags)) }; + let ret = unsafe { suppress_iph!(libc::open_osfhandle(handle, flags)) }; if ret == -1 { Err(errno_err(vm)) } else { diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index c4d71ff0e95..6c42517f5f4 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -850,8 +850,6 @@ pub(crate) mod module { unsafe extern "C" { fn _umask(mask: i32) -> i32; - fn _dup(fd: i32) -> i32; - fn _dup2(fd: i32, fd2: i32) -> i32; } /// Close fd and convert error to PyException (PEP 446 cleanup) @@ -871,9 +869,116 @@ pub(crate) mod module { } } + #[pyfunction] + fn pipe(vm: &VirtualMachine) -> PyResult<(i32, i32)> { + use windows_sys::Win32::System::Pipes::CreatePipe; + + let (read_handle, write_handle) = unsafe { + let mut read = MaybeUninit::<isize>::uninit(); + let mut write = MaybeUninit::<isize>::uninit(); + let res = CreatePipe( + read.as_mut_ptr() as *mut _, + write.as_mut_ptr() as *mut _, + std::ptr::null(), + 0, + ); + if res == 0 { + return Err(errno_err(vm)); + } + (read.assume_init(), write.assume_init()) + }; + + // Convert handles to file descriptors + // O_NOINHERIT = 0x80 (MSVC CRT) + const O_NOINHERIT: i32 = 0x80; + let read_fd = unsafe { libc::open_osfhandle(read_handle, O_NOINHERIT) }; + let write_fd = unsafe { libc::open_osfhandle(write_handle, libc::O_WRONLY | O_NOINHERIT) }; + + if read_fd == -1 || write_fd == -1 { + unsafe { + Foundation::CloseHandle(read_handle as _); + Foundation::CloseHandle(write_handle as _); + } + return Err(errno_err(vm)); + } + + Ok((read_fd, write_fd)) + } + + #[pyfunction] + fn getppid() -> u32 { + use windows_sys::Win32::System::Threading::GetCurrentProcess; + + #[repr(C)] + struct ProcessBasicInformation { + exit_status: isize, + peb_base_address: *mut std::ffi::c_void, + affinity_mask: usize, + base_priority: i32, + unique_process_id: usize, + inherited_from_unique_process_id: usize, + } + + type NtQueryInformationProcessFn = unsafe extern "system" fn( + process_handle: isize, + process_information_class: u32, + process_information: *mut std::ffi::c_void, + process_information_length: u32, + return_length: *mut u32, + ) -> i32; + + let ntdll = unsafe { + windows_sys::Win32::System::LibraryLoader::GetModuleHandleW(windows_sys::w!( + "ntdll.dll" + )) + }; + if ntdll.is_null() { + return 0; + } + + let func = unsafe { + windows_sys::Win32::System::LibraryLoader::GetProcAddress( + ntdll, + c"NtQueryInformationProcess".as_ptr() as *const u8, + ) + }; + let Some(func) = func else { + return 0; + }; + let nt_query: NtQueryInformationProcessFn = unsafe { std::mem::transmute(func) }; + + let mut info = ProcessBasicInformation { + exit_status: 0, + peb_base_address: std::ptr::null_mut(), + affinity_mask: 0, + base_priority: 0, + unique_process_id: 0, + inherited_from_unique_process_id: 0, + }; + + let status = unsafe { + nt_query( + GetCurrentProcess() as isize, + 0, // ProcessBasicInformation + &mut info as *mut _ as *mut std::ffi::c_void, + std::mem::size_of::<ProcessBasicInformation>() as u32, + std::ptr::null_mut(), + ) + }; + + if status >= 0 + && info.inherited_from_unique_process_id != 0 + && info.inherited_from_unique_process_id < u32::MAX as usize + { + info.inherited_from_unique_process_id as u32 + } else { + 0 + } + } + #[pyfunction] fn dup(fd: i32, vm: &VirtualMachine) -> PyResult<i32> { - let fd2 = unsafe { suppress_iph!(_dup(fd)) }; + let fd2 = unsafe { suppress_iph!(libc::dup(fd)) }; if fd2 < 0 { return Err(errno_err(vm)); } @@ -896,7 +1001,7 @@ pub(crate) mod module { #[pyfunction] fn dup2(args: Dup2Args, vm: &VirtualMachine) -> PyResult<i32> { - let result = unsafe { suppress_iph!(_dup2(args.fd, args.fd2)) }; + let result = unsafe { suppress_iph!(libc::dup2(args.fd, args.fd2)) }; if result < 0 { return Err(errno_err(vm)); } From ac48643447a2b71608a11a876cac889e45be7a95 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:58:21 +0900 Subject: [PATCH 453/819] Add TextIOWrapper.truncate, fix a few bugs (#6382) --- Lib/test/test_io.py | 1 - crates/vm/src/stdlib/io.rs | 25 ++++++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 5bfaa89ba27..19027370dcd 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -4209,7 +4209,6 @@ def write(self, data): self.assertEqual([b"abcdef", b"middle", b"g"*chunk_size], buf._write_stack) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_basic_io(self): return super().test_basic_io() diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 8efd52a29f3..cef73ca4a86 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -144,7 +144,7 @@ mod _io { }; use bstr::ByteSlice; use crossbeam_utils::atomic::AtomicCell; - use malachite_bigint::{BigInt, BigUint}; + use malachite_bigint::BigInt; use num_traits::ToPrimitive; use std::{ borrow::Cow, @@ -2244,7 +2244,7 @@ mod _io { set_field!(self.chars_to_skip, CHARS_TO_SKIP_OFF); set_field!(self.need_eof as u8, NEED_EOF_OFF); set_field!(self.bytes_to_skip, BYTES_TO_SKIP_OFF); - BigUint::from_bytes_le(&buf).into() + BigInt::from_signed_bytes_le(&buf) } fn set_decoder_state(&self, decoder: &PyObject, vm: &VirtualMachine) -> PyResult<()> { @@ -2728,7 +2728,7 @@ mod _io { n_decoded += n; cookie.bytes_to_feed += 1; let (dec_buffer, dec_flags) = decoder_getstate()?; - if dec_buffer.is_empty() && n_decoded.chars < num_to_skip.chars { + if dec_buffer.is_empty() && n_decoded.chars <= num_to_skip.chars { cookie.start_pos += cookie.bytes_to_feed as Offset; num_to_skip -= n_decoded; cookie.dec_flags = dec_flags; @@ -2900,6 +2900,25 @@ mod _io { flush_inner(&mut textio, vm) } + #[pymethod] + fn truncate( + zelf: PyRef<Self>, + pos: OptionalArg<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult { + // Implementation follows _pyio.py TextIOWrapper.truncate + let mut textio = zelf.lock(vm)?; + flush_inner(&mut textio, vm)?; + let buffer = textio.buffer.clone(); + drop(textio); + + let pos = match pos.into_option() { + Some(p) => p, + None => vm.call_method(zelf.as_object(), "tell", ())?, + }; + vm.call_method(&buffer, "truncate", (pos,)) + } + #[pymethod] fn isatty(&self, vm: &VirtualMachine) -> PyResult { let textio = self.lock(vm)?; From 4ebdadea453c5b00cdb55c6b92a5041f64f57ea5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 01:02:31 +0900 Subject: [PATCH 454/819] Bump pyo3 from 0.26.0 to 0.27.2 (#6386) Bumps [pyo3](https://github.com/pyo3/pyo3) from 0.26.0 to 0.27.2. - [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.26.0...v0.27.2) --- updated-dependencies: - dependency-name: pyo3 dependency-version: 0.27.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87e8d381bc1..aac2f16638b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2484,9 +2484,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.26.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383" +checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d" dependencies = [ "indoc", "libc", @@ -2501,18 +2501,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.26.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f" +checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.26.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105" +checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089" dependencies = [ "libc", "pyo3-build-config", @@ -2520,9 +2520,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.26.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded" +checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -2532,9 +2532,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.26.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf" +checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9" dependencies = [ "heck", "proc-macro2", From 42b969b088646ec7bae90027ba823415dfc0961b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:01:05 +0900 Subject: [PATCH 455/819] Bump oid-registry from 0.7.1 to 0.8.1 (#6383) Bumps [oid-registry](https://github.com/rusticata/oid-registry) from 0.7.1 to 0.8.1. - [Commits](https://github.com/rusticata/oid-registry/compare/oid-registry-0.7.1...oid-registry-0.8.1) --- updated-dependencies: - dependency-name: oid-registry dependency-version: 0.8.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 48 +++++----------------------------------- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aac2f16638b..64a12eb9f7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,28 +154,13 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" -[[package]] -name = "asn1-rs" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" -dependencies = [ - "asn1-rs-derive 0.5.1", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror 1.0.69", -] - [[package]] name = "asn1-rs" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" dependencies = [ - "asn1-rs-derive 0.6.0", + "asn1-rs-derive", "asn1-rs-impl", "displaydoc", "nom", @@ -185,18 +170,6 @@ dependencies = [ "time", ] -[[package]] -name = "asn1-rs-derive" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "asn1-rs-derive" version = "0.6.0" @@ -1007,7 +980,7 @@ version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ - "asn1-rs 0.7.1", + "asn1-rs", "displaydoc", "nom", "num-bigint", @@ -2097,22 +2070,13 @@ dependencies = [ "syn", ] -[[package]] -name = "oid-registry" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" -dependencies = [ - "asn1-rs 0.6.2", -] - [[package]] name = "oid-registry" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" dependencies = [ - "asn1-rs 0.7.1", + "asn1-rs", ] [[package]] @@ -3200,7 +3164,7 @@ dependencies = [ "num-integer", "num-traits", "num_enum", - "oid-registry 0.7.1", + "oid-registry", "openssl", "openssl-probe", "openssl-sys", @@ -4740,12 +4704,12 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425" dependencies = [ - "asn1-rs 0.7.1", + "asn1-rs", "data-encoding", "der-parser", "lazy_static 1.5.0", "nom", - "oid-registry 0.8.1", + "oid-registry", "rusticata-macros", "thiserror 2.0.17", "time", diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 8cc04d5fd43..58850cdaf8d 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -128,7 +128,7 @@ der = { version = "0.7", features = ["alloc", "oid"], optional = true } pem-rfc7468 = { version = "1.0", features = ["alloc"], optional = true } webpki-roots = { version = "1.0", optional = true } aws-lc-rs = { version = "1.14.1", optional = true } -oid-registry = { version = "0.7", features = ["x509", "pkcs1", "nist_algs"], optional = true } +oid-registry = { version = "0.8", features = ["x509", "pkcs1", "nist_algs"], optional = true } pkcs8 = { version = "0.10", features = ["encryption", "pkcs5", "pem"], optional = true } [target.'cfg(not(any(target_os = "android", target_arch = "wasm32")))'.dependencies] From 9f8090c8301d6f94c1612ed7210285506aed1f86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:01:17 +0900 Subject: [PATCH 456/819] Bump libloading from 0.8.9 to 0.9.0 (#6384) Bumps [libloading](https://github.com/nagisa/rust_libloading) from 0.8.9 to 0.9.0. - [Commits](https://github.com/nagisa/rust_libloading/compare/0.8.9...0.9.0) --- updated-dependencies: - dependency-name: libloading dependency-version: 0.9.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 14 ++++++++++++-- crates/vm/Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64a12eb9f7c..e8313d7c18b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -538,7 +538,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.8.9", ] [[package]] @@ -1713,6 +1713,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libm" version = "0.2.15" @@ -3240,7 +3250,7 @@ dependencies = [ "junction", "libc", "libffi", - "libloading", + "libloading 0.9.0", "log", "malachite-bigint", "memchr", diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 502ace0126a..8c3cee37aa3 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -104,7 +104,7 @@ widestring = { workspace = true } [target.'cfg(all(any(target_os = "linux", target_os = "macos", target_os = "windows"), not(any(target_env = "musl", target_env = "sgx"))))'.dependencies] libffi = { workspace = true, features = ["system"] } -libloading = "0.8" +libloading = "0.9" [target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies] num_cpus = "1.17.0" From f723974924ee8d172f8852b2e8fbcc9b0c5d52d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:01:29 +0900 Subject: [PATCH 457/819] Bump system-configuration from 0.6.1 to 0.7.0 (#6385) Bumps [system-configuration](https://github.com/mullvad/system-configuration-rs) from 0.6.1 to 0.7.0. - [Changelog](https://github.com/mullvad/system-configuration-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/mullvad/system-configuration-rs/compare/v0.6.1...v0.7.0) --- updated-dependencies: - dependency-name: system-configuration dependency-version: 0.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8313d7c18b..aa2382d99d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3686,9 +3686,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags 2.10.0", "core-foundation 0.9.4", diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 58850cdaf8d..593b5f196f1 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -156,7 +156,7 @@ features = [ ] [target.'cfg(target_os = "macos")'.dependencies] -system-configuration = "0.6.1" +system-configuration = "0.7.0" [lints] workspace = true From 14232ad0d2c29729ba213777a4824b3e2c6254d1 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:12:38 +0900 Subject: [PATCH 458/819] new_last_{os,errno}_error (#6381) * new_last_{os,errno}_error * Remove os::errno_err * enable ssl multithread test --- Lib/test/test_os.py | 4 -- Lib/test/test_ssl.py | 1 - Lib/test/test_support.py | 1 - crates/common/src/crt_fd.rs | 6 ++- crates/common/src/fileutils.rs | 2 +- crates/common/src/os.rs | 30 +++++------- crates/stdlib/src/fcntl.rs | 16 +++---- crates/stdlib/src/multiprocessing.rs | 8 ++-- crates/stdlib/src/overlapped.rs | 15 +++--- crates/stdlib/src/resource.rs | 3 +- crates/stdlib/src/socket.rs | 10 ++-- crates/vm/src/stdlib/io.rs | 3 +- crates/vm/src/stdlib/msvcrt.rs | 5 +- crates/vm/src/stdlib/nt.rs | 68 ++++++++++++++++------------ crates/vm/src/stdlib/os.rs | 26 +++++------ crates/vm/src/stdlib/posix.rs | 52 +++++++++++++-------- crates/vm/src/stdlib/signal.rs | 2 +- crates/vm/src/stdlib/time.rs | 5 +- crates/vm/src/stdlib/winapi.rs | 13 +++--- crates/vm/src/vm/vm_new.rs | 20 +++++++- crates/vm/src/windows.rs | 7 ++- 21 files changed, 158 insertions(+), 139 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 25e2c30e4d7..4fefc9d88ab 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -2428,12 +2428,10 @@ def test_ftruncate(self): self.check(os.ftruncate, 0) self.check_bool(os.truncate, 0) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (OSError: [Errno 18] There are no more files.)') @unittest.skipUnless(hasattr(os, 'lseek'), 'test needs os.lseek()') def test_lseek(self): self.check(os.lseek, 0, 0) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (OSError: [Errno 18] There are no more files.)') @unittest.skipUnless(hasattr(os, 'read'), 'test needs os.read()') def test_read(self): self.check(os.read, 1) @@ -2447,7 +2445,6 @@ def test_readv(self): def test_tcsetpgrpt(self): self.check(os.tcsetpgrp, 0) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (OSError: [Errno 18] There are no more files.)') @unittest.skipUnless(hasattr(os, 'write'), 'test needs os.write()') def test_write(self): self.check(os.write, b" ") @@ -2456,7 +2453,6 @@ def test_write(self): def test_writev(self): self.check(os.writev, [b'abc']) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms') @support.requires_subprocess() def test_inheritable(self): self.check(os.get_inheritable) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index e40628640a9..f073def5bc1 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -2891,7 +2891,6 @@ def test_echo(self): 'Cannot create a client socket with a PROTOCOL_TLS_SERVER context', str(e.exception)) - @unittest.skip("TODO: RUSTPYTHON; Flaky on windows") @unittest.skipUnless(support.Py_GIL_DISABLED, "test is only useful if the GIL is disabled") def test_ssl_in_multiple_threads(self): # See GH-124984: OpenSSL is not thread safe. diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 4381a82b6b5..7c8380498e3 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -310,7 +310,6 @@ def test_temp_cwd__name_none(self): def test_sortdict(self): self.assertEqual(support.sortdict({3:3, 2:2, 1:1}), "{1: 1, 2: 2, 3: 3}") - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; actual c fds on windows") def test_make_bad_fd(self): fd = os_helper.make_bad_fd() with self.assertRaises(OSError) as cm: diff --git a/crates/common/src/crt_fd.rs b/crates/common/src/crt_fd.rs index 4fda2f3dcf8..7b8279adbe3 100644 --- a/crates/common/src/crt_fd.rs +++ b/crates/common/src/crt_fd.rs @@ -35,7 +35,8 @@ pub type Raw = i32; #[inline] fn cvt<I: num_traits::PrimInt>(ret: I) -> io::Result<I> { if ret < I::zero() { - Err(crate::os::last_os_error()) + // CRT functions set errno, not GetLastError(), so use errno_io_error + Err(crate::os::errno_io_error()) } else { Ok(ret) } @@ -345,7 +346,8 @@ pub fn as_handle(fd: Borrowed<'_>) -> io::Result<BorrowedHandle<'_>> { } let handle = unsafe { suppress_iph!(_get_osfhandle(fd)) }; if handle as HANDLE == INVALID_HANDLE_VALUE { - Err(crate::os::last_os_error()) + // _get_osfhandle is a CRT function that sets errno, not GetLastError() + Err(crate::os::errno_io_error()) } else { Ok(unsafe { BorrowedHandle::borrow_raw(handle as _) }) } diff --git a/crates/common/src/fileutils.rs b/crates/common/src/fileutils.rs index 8e21ef656c6..a12c1cd82e5 100644 --- a/crates/common/src/fileutils.rs +++ b/crates/common/src/fileutils.rs @@ -13,7 +13,7 @@ pub fn fstat(fd: crate::crt_fd::Borrowed<'_>) -> std::io::Result<StatStruct> { unsafe { let ret = libc::fstat(fd.as_raw(), stat.as_mut_ptr()); if ret == -1 { - Err(crate::os::last_os_error()) + Err(crate::os::errno_io_error()) } else { Ok(stat.assume_init()) } diff --git a/crates/common/src/os.rs b/crates/common/src/os.rs index 0aef70d5820..e61c77e0fd1 100644 --- a/crates/common/src/os.rs +++ b/crates/common/src/os.rs @@ -19,30 +19,22 @@ impl ErrorExt for io::Error { } } +/// Get the last error from C runtime library functions (like _dup, _dup2, _fstat, etc.) +/// CRT functions set errno, not GetLastError(), so we need to read errno directly. #[cfg(windows)] -pub fn last_os_error() -> io::Error { - let err = io::Error::last_os_error(); - // FIXME: probably not ideal, we need a bigger dichotomy between GetLastError and errno - if err.raw_os_error() == Some(0) { - unsafe extern "C" { - fn _get_errno(pValue: *mut i32) -> i32; - } - let mut errno = 0; - unsafe { suppress_iph!(_get_errno(&mut errno)) }; - let errno = errno_to_winerror(errno); - io::Error::from_raw_os_error(errno) - } else { - err - } +pub fn errno_io_error() -> io::Error { + let errno: i32 = get_errno(); + let winerror = errno_to_winerror(errno); + io::Error::from_raw_os_error(winerror) } #[cfg(not(windows))] -pub fn last_os_error() -> io::Error { - io::Error::last_os_error() +pub fn errno_io_error() -> io::Error { + std::io::Error::last_os_error() } #[cfg(windows)] -pub fn last_posix_errno() -> i32 { +pub fn get_errno() -> i32 { unsafe extern "C" { fn _get_errno(pValue: *mut i32) -> i32; } @@ -52,8 +44,8 @@ pub fn last_posix_errno() -> i32 { } #[cfg(not(windows))] -pub fn last_posix_errno() -> i32 { - last_os_error().posix_errno() +pub fn get_errno() -> i32 { + std::io::Error::last_os_error().posix_errno() } #[cfg(unix)] diff --git a/crates/stdlib/src/fcntl.rs b/crates/stdlib/src/fcntl.rs index 84b60b43baf..dc6a0b8171e 100644 --- a/crates/stdlib/src/fcntl.rs +++ b/crates/stdlib/src/fcntl.rs @@ -8,7 +8,7 @@ mod fcntl { PyResult, VirtualMachine, builtins::PyIntRef, function::{ArgMemoryBuffer, ArgStrOrBytesLike, Either, OptionalArg}, - stdlib::{io, os}, + stdlib::io, }; // TODO: supply these from <asm-generic/fnctl.h> (please file an issue/PR upstream): @@ -75,7 +75,7 @@ mod fcntl { } let ret = unsafe { libc::fcntl(fd, cmd, buf.as_mut_ptr()) }; if ret < 0 { - return Err(os::errno_err(vm)); + return Err(vm.new_last_errno_error()); } return Ok(vm.ctx.new_bytes(buf[..arg_len].to_vec()).into()); } @@ -84,7 +84,7 @@ mod fcntl { }; let ret = unsafe { libc::fcntl(fd, cmd, int as i32) }; if ret < 0 { - return Err(os::errno_err(vm)); + return Err(vm.new_last_errno_error()); } Ok(vm.new_pyobj(ret)) } @@ -117,7 +117,7 @@ mod fcntl { let ret = unsafe { libc::ioctl(fd, request as _, arg_buf.as_mut_ptr()) }; if ret < 0 { - return Err(os::errno_err(vm)); + return Err(vm.new_last_errno_error()); } return Ok(vm.ctx.new_int(ret).into()); } @@ -128,14 +128,14 @@ mod fcntl { }; let ret = unsafe { libc::ioctl(fd, request as _, buf.as_mut_ptr()) }; if ret < 0 { - return Err(os::errno_err(vm)); + return Err(vm.new_last_errno_error()); } Ok(vm.ctx.new_bytes(buf[..buf_len].to_vec()).into()) } Either::B(i) => { let ret = unsafe { libc::ioctl(fd, request as _, i) }; if ret < 0 { - return Err(os::errno_err(vm)); + return Err(vm.new_last_errno_error()); } Ok(vm.ctx.new_int(ret).into()) } @@ -149,7 +149,7 @@ mod fcntl { let ret = unsafe { libc::flock(fd, operation) }; // TODO: add support for platforms that don't have a builtin `flock` syscall if ret < 0 { - return Err(os::errno_err(vm)); + return Err(vm.new_last_errno_error()); } Ok(vm.ctx.new_int(ret).into()) } @@ -209,7 +209,7 @@ mod fcntl { ) }; if ret < 0 { - return Err(os::errno_err(vm)); + return Err(vm.new_last_errno_error()); } Ok(vm.ctx.new_int(ret).into()) } diff --git a/crates/stdlib/src/multiprocessing.rs b/crates/stdlib/src/multiprocessing.rs index 4a98c1afadc..280eff7d32f 100644 --- a/crates/stdlib/src/multiprocessing.rs +++ b/crates/stdlib/src/multiprocessing.rs @@ -3,14 +3,14 @@ pub(crate) use _multiprocessing::make_module; #[cfg(windows)] #[pymodule] mod _multiprocessing { - use crate::vm::{PyResult, VirtualMachine, function::ArgBytesLike, stdlib::os}; + use crate::vm::{PyResult, VirtualMachine, function::ArgBytesLike}; use windows_sys::Win32::Networking::WinSock::{self, SOCKET}; #[pyfunction] fn closesocket(socket: usize, vm: &VirtualMachine) -> PyResult<()> { let res = unsafe { WinSock::closesocket(socket as SOCKET) }; if res == 0 { - Err(os::errno_err(vm)) + Err(vm.new_last_os_error()) } else { Ok(()) } @@ -22,7 +22,7 @@ mod _multiprocessing { let n_read = unsafe { WinSock::recv(socket as SOCKET, buf.as_mut_ptr() as *mut _, size as i32, 0) }; if n_read < 0 { - Err(os::errno_err(vm)) + Err(vm.new_last_os_error()) } else { Ok(n_read) } @@ -34,7 +34,7 @@ mod _multiprocessing { WinSock::send(socket as SOCKET, b.as_ptr() as *const _, b.len() as i32, 0) }); if ret < 0 { - Err(os::errno_err(vm)) + Err(vm.new_last_os_error()) } else { Ok(ret) } diff --git a/crates/stdlib/src/overlapped.rs b/crates/stdlib/src/overlapped.rs index 9d816a03d40..a5b5fa14fc3 100644 --- a/crates/stdlib/src/overlapped.rs +++ b/crates/stdlib/src/overlapped.rs @@ -13,7 +13,6 @@ mod _overlapped { common::lock::PyMutex, convert::{ToPyException, ToPyObject}, protocol::PyBuffer, - stdlib::os::errno_err, types::Constructor, }; use windows_sys::Win32::{ @@ -256,7 +255,7 @@ mod _overlapped { }; // CancelIoEx returns ERROR_NOT_FOUND if the I/O completed in-between if ret == 0 && unsafe { GetLastError() } != Foundation::ERROR_NOT_FOUND { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } Ok(()) } @@ -276,7 +275,7 @@ mod _overlapped { ) as isize }; if event == NULL { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } } @@ -318,7 +317,7 @@ mod _overlapped { ) as isize }; if r as usize == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } Ok(r) } @@ -346,7 +345,7 @@ mod _overlapped { if err == Foundation::WAIT_TIMEOUT { return Ok(vm.ctx.none()); } else { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } } @@ -387,7 +386,7 @@ mod _overlapped { ) as isize }; if event == NULL { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } Ok(event) } @@ -396,7 +395,7 @@ mod _overlapped { fn SetEvent(handle: u64, vm: &VirtualMachine) -> PyResult<()> { let ret = unsafe { windows_sys::Win32::System::Threading::SetEvent(u64_to_handle(handle)) }; if ret == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } Ok(()) } @@ -406,7 +405,7 @@ mod _overlapped { let ret = unsafe { windows_sys::Win32::System::Threading::ResetEvent(u64_to_handle(handle)) }; if ret == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } Ok(()) } diff --git a/crates/stdlib/src/resource.rs b/crates/stdlib/src/resource.rs index 59205a4fa46..052f45e0cad 100644 --- a/crates/stdlib/src/resource.rs +++ b/crates/stdlib/src/resource.rs @@ -7,7 +7,6 @@ mod resource { use crate::vm::{ PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, VirtualMachine, convert::{ToPyException, ToPyObject}, - stdlib::os, types::PyStructSequence, }; use std::{io, mem}; @@ -161,7 +160,7 @@ mod resource { let rlimit = unsafe { let mut rlimit = mem::MaybeUninit::<libc::rlimit>::uninit(); if libc::getrlimit(resource as _, rlimit.as_mut_ptr()) == -1 { - return Err(os::errno_err(vm)); + return Err(vm.new_last_errno_error()); } rlimit.assume_init() }; diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index 234d94a5263..c00799feabd 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -1547,7 +1547,7 @@ mod _socket { ) }; if ret < 0 { - return Err(crate::common::os::last_os_error().into()); + return Err(crate::common::os::errno_io_error().into()); } Ok(vm.ctx.new_int(flag).into()) } else { @@ -1568,7 +1568,7 @@ mod _socket { ) }; if ret < 0 { - return Err(crate::common::os::last_os_error().into()); + return Err(crate::common::os::errno_io_error().into()); } buf.truncate(buflen as usize); Ok(vm.ctx.new_bytes(buf).into()) @@ -1609,7 +1609,7 @@ mod _socket { } }; if ret < 0 { - Err(crate::common::os::last_os_error().into()) + Err(crate::common::os::errno_io_error().into()) } else { Ok(()) } @@ -2158,7 +2158,7 @@ mod _socket { let mut buf = [0; c::IF_NAMESIZE + 1]; let ret = unsafe { c::if_indextoname(index, buf.as_mut_ptr()) }; if ret.is_null() { - Err(crate::vm::stdlib::os::errno_err(vm)) + Err(vm.new_last_errno_error()) } else { let buf = unsafe { ffi::CStr::from_ptr(buf.as_ptr() as _) }; Ok(buf.to_string_lossy().into_owned()) @@ -2488,7 +2488,7 @@ mod _socket { use windows_sys::Win32::Networking::WinSock::closesocket as close; let ret = unsafe { close(x as _) }; if ret < 0 { - let err = crate::common::os::last_os_error(); + let err = std::io::Error::last_os_error(); if err.raw_os_error() != Some(errcode!(ECONNRESET)) { return Err(err); } diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index cef73ca4a86..7cf21cc77e4 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -4010,8 +4010,7 @@ mod _io { // check file descriptor validity #[cfg(unix)] if let Ok(crate::ospath::OsPathOrFd::Fd(fd)) = file.clone().try_into_value(vm) { - nix::fcntl::fcntl(fd, nix::fcntl::F_GETFD) - .map_err(|_| crate::stdlib::os::errno_err(vm))?; + nix::fcntl::fcntl(fd, nix::fcntl::F_GETFD).map_err(|_| vm.new_last_errno_error())?; } // Construct a FileIO (subclass of RawIOBase) diff --git a/crates/vm/src/stdlib/msvcrt.rs b/crates/vm/src/stdlib/msvcrt.rs index 70ac09f8a05..cf0dac2c9db 100644 --- a/crates/vm/src/stdlib/msvcrt.rs +++ b/crates/vm/src/stdlib/msvcrt.rs @@ -9,7 +9,6 @@ mod msvcrt { builtins::{PyBytes, PyStrRef}, common::{crt_fd, suppress_iph}, convert::IntoPyException, - stdlib::os::errno_err, }; use itertools::Itertools; use std::os::windows::io::AsRawHandle; @@ -82,7 +81,7 @@ mod msvcrt { fn setmode(fd: crt_fd::Borrowed<'_>, flags: i32, vm: &VirtualMachine) -> PyResult<i32> { let flags = unsafe { suppress_iph!(_setmode(fd, flags)) }; if flags == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(flags) } @@ -92,7 +91,7 @@ mod msvcrt { fn open_osfhandle(handle: isize, flags: i32, vm: &VirtualMachine) -> PyResult<i32> { let ret = unsafe { suppress_iph!(libc::open_osfhandle(handle, flags)) }; if ret == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(ret) } diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 6c42517f5f4..785e794a268 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -15,12 +15,13 @@ pub(crate) mod module { use crate::{ PyResult, TryFromObject, VirtualMachine, builtins::{PyBaseExceptionRef, PyDictRef, PyListRef, PyStrRef, PyTupleRef}, - common::{crt_fd, os::last_os_error, suppress_iph, windows::ToWideString}, + common::{crt_fd, suppress_iph, windows::ToWideString}, convert::ToPyException, function::{Either, OptionalArg}, ospath::OsPath, - stdlib::os::{_os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, errno_err}, + stdlib::os::{_os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory}, }; + use libc::intptr_t; use std::os::windows::io::AsRawHandle; use std::{env, fs, io, mem::MaybeUninit, os::windows::ffi::OsStringExt}; @@ -162,7 +163,7 @@ pub(crate) mod module { let mut status: i32 = 0; let pid = unsafe { suppress_iph!(_cwait(&mut status, pid, opt)) }; if pid == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { // Cast to unsigned to handle large exit codes (like 0xC000013A) // then shift left by 8 to match POSIX waitpid format @@ -184,16 +185,24 @@ pub(crate) mod module { if sig == Console::CTRL_C_EVENT || sig == Console::CTRL_BREAK_EVENT { let ret = unsafe { Console::GenerateConsoleCtrlEvent(sig, pid) }; - let res = if ret == 0 { Err(errno_err(vm)) } else { Ok(()) }; + let res = if ret == 0 { + Err(vm.new_last_os_error()) + } else { + Ok(()) + }; return res; } let h = unsafe { Threading::OpenProcess(Threading::PROCESS_ALL_ACCESS, 0, pid) }; if h.is_null() { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } let ret = unsafe { Threading::TerminateProcess(h, sig) }; - let res = if ret == 0 { Err(errno_err(vm)) } else { Ok(()) }; + let res = if ret == 0 { + Err(vm.new_last_os_error()) + } else { + Ok(()) + }; unsafe { Foundation::CloseHandle(h) }; res } @@ -218,7 +227,7 @@ pub(crate) mod module { // In that case, try opening CONOUT$ directly with read access let err = unsafe { Foundation::GetLastError() }; if err != Foundation::ERROR_ACCESS_DENIED { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } let conout: Vec<u16> = "CONOUT$\0".encode_utf16().collect(); let console_handle = unsafe { @@ -233,13 +242,13 @@ pub(crate) mod module { ) }; if console_handle == INVALID_HANDLE_VALUE { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } let ret = unsafe { Console::GetConsoleScreenBufferInfo(console_handle, csbi.as_mut_ptr()) }; unsafe { Foundation::CloseHandle(console_handle) }; if ret == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } } let csbi = unsafe { csbi.assume_init() }; @@ -302,7 +311,7 @@ pub(crate) mod module { let result = unsafe { suppress_iph!(_wspawnv(mode, path.as_ptr(), argv_spawn.as_ptr())) }; if result == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(result) } @@ -379,7 +388,7 @@ pub(crate) mod module { )) }; if result == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(result) } @@ -419,7 +428,7 @@ pub(crate) mod module { .collect(); if (unsafe { suppress_iph!(_wexecv(path.as_ptr(), argv_execv.as_ptr())) } == -1) { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(()) } @@ -489,7 +498,7 @@ pub(crate) mod module { if (unsafe { suppress_iph!(_wexecve(path.as_ptr(), argv_execve.as_ptr(), envp.as_ptr())) } == -1) { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(()) } @@ -517,7 +526,7 @@ pub(crate) mod module { ) }; if ret == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } if ret as usize > buffer.len() { buffer.resize(ret as usize, 0); @@ -530,7 +539,7 @@ pub(crate) mod module { ) }; if ret == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } } let buffer = widestring::WideCString::from_vec_truncate(buffer); @@ -546,7 +555,7 @@ pub(crate) mod module { FileSystem::GetVolumePathNameW(wide.as_ptr(), buffer.as_mut_ptr(), buflen as _) }; if ret == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } let buffer = widestring::WideCString::from_vec_truncate(buffer); Ok(path.mode.process_path(buffer.to_os_string(), vm)) @@ -617,7 +626,7 @@ pub(crate) mod module { }; return if ret == 0 { - Err(errno_err(vm)) + Err(err.to_pyexception(vm)) } else { Ok((total, free)) }; @@ -629,10 +638,9 @@ pub(crate) mod module { fn get_handle_inheritable(handle: intptr_t, vm: &VirtualMachine) -> PyResult<bool> { let mut flags = 0; if unsafe { Foundation::GetHandleInformation(handle as _, &mut flags) } == 0 { - Err(errno_err(vm)) - } else { - Ok(flags & Foundation::HANDLE_FLAG_INHERIT != 0) + return Err(vm.new_last_os_error()); } + Ok(flags & Foundation::HANDLE_FLAG_INHERIT != 0) } #[pyfunction] @@ -673,7 +681,7 @@ pub(crate) mod module { Foundation::SetHandleInformation(handle as _, Foundation::HANDLE_FLAG_INHERIT, flags) }; if res == 0 { - Err(last_os_error()) + Err(std::io::Error::last_os_error()) } else { Ok(()) } @@ -687,7 +695,7 @@ pub(crate) mod module { let len = unsafe { FileSystem::GetLogicalDriveStringsW(buffer.len() as _, buffer.as_mut_ptr()) }; if len == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } if len as usize >= buffer.len() { return Err(std::io::Error::from_raw_os_error(ERROR_MORE_DATA as _).to_pyexception(vm)); @@ -708,7 +716,7 @@ pub(crate) mod module { let find = unsafe { FileSystem::FindFirstVolumeW(buffer.as_mut_ptr(), buffer.len() as _) }; if find == INVALID_HANDLE_VALUE { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } loop { @@ -832,7 +840,7 @@ pub(crate) mod module { ) }; if convert_result == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } let res = unsafe { FileSystem::CreateDirectoryW(wide.as_ptr(), &sec_attr as *const _ as _) }; @@ -843,7 +851,7 @@ pub(crate) mod module { }; if res == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } Ok(()) } @@ -863,7 +871,7 @@ pub(crate) mod module { fn umask(mask: i32, vm: &VirtualMachine) -> PyResult<i32> { let result = unsafe { _umask(mask) }; if result < 0 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(result) } @@ -883,7 +891,7 @@ pub(crate) mod module { 0, ); if res == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } (read.assume_init(), write.assume_init()) }; @@ -899,7 +907,7 @@ pub(crate) mod module { Foundation::CloseHandle(read_handle as _); Foundation::CloseHandle(write_handle as _); } - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } Ok((read_fd, write_fd)) @@ -980,7 +988,7 @@ pub(crate) mod module { fn dup(fd: i32, vm: &VirtualMachine) -> PyResult<i32> { let fd2 = unsafe { suppress_iph!(libc::dup(fd)) }; if fd2 < 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_errno_error()); } let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd2) }; let handle = crt_fd::as_handle(borrowed).map_err(|e| close_fd_and_raise(fd2, e, vm))?; @@ -1003,7 +1011,7 @@ pub(crate) mod module { fn dup2(args: Dup2Args, vm: &VirtualMachine) -> PyResult<i32> { let result = unsafe { suppress_iph!(libc::dup2(args.fd, args.fd2)) }; if result < 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_errno_error()); } if !args.inheritable { let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(args.fd2) }; diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 6d4a0836a02..8f42d24878f 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -2,7 +2,7 @@ use crate::{ AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, - builtins::{PyBaseExceptionRef, PyModule, PySet}, + builtins::{PyModule, PySet}, common::crt_fd, convert::{IntoPyException, ToPyException, ToPyObject}, function::{ArgumentError, FromArgs, FuncArgs}, @@ -22,24 +22,18 @@ pub(crate) fn fs_metadata<P: AsRef<Path>>( #[cfg(unix)] impl crate::convert::IntoPyException for nix::Error { - fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef { + fn into_pyexception(self, vm: &VirtualMachine) -> crate::builtins::PyBaseExceptionRef { io::Error::from(self).into_pyexception(vm) } } #[cfg(unix)] impl crate::convert::IntoPyException for rustix::io::Errno { - fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef { + fn into_pyexception(self, vm: &VirtualMachine) -> crate::builtins::PyBaseExceptionRef { io::Error::from(self).into_pyexception(vm) } } -/// Convert the error stored in the `errno` variable into an Exception -#[inline] -pub fn errno_err(vm: &VirtualMachine) -> PyBaseExceptionRef { - crate::common::os::last_os_error().to_pyexception(vm) -} - #[allow(dead_code)] #[derive(FromArgs, Default)] pub struct TargetIsDirectory { @@ -151,7 +145,7 @@ impl ToPyObject for crt_fd::Borrowed<'_> { #[pymodule(sub)] pub(super) mod _os { - use super::{DirFd, FollowSymlinks, SupportFunc, errno_err}; + use super::{DirFd, FollowSymlinks, SupportFunc}; #[cfg(windows)] use crate::common::windows::ToWideString; use crate::{ @@ -338,7 +332,7 @@ pub(super) mod _os { if let Some(fd) = dir_fd.raw_opt() { let res = unsafe { libc::mkdirat(fd, c_path.as_ptr(), mode as _) }; return if res < 0 { - let err = crate::common::os::last_os_error(); + let err = crate::common::os::errno_io_error(); Err(IOErrorBuilder::with_filename(&err, path, vm)) } else { Ok(()) @@ -348,7 +342,7 @@ pub(super) mod _os { let [] = dir_fd.0; let res = unsafe { libc::mkdir(c_path.as_ptr(), mode as _) }; if res < 0 { - let err = crate::common::os::last_os_error(); + let err = crate::common::os::errno_io_error(); return Err(IOErrorBuilder::with_filename(&err, path, vm)); } Ok(()) @@ -1157,7 +1151,11 @@ pub(super) mod _os { std::mem::transmute::<[i32; 2], i64>(distance_to_move) } }; - if res < 0 { Err(errno_err(vm)) } else { Ok(res) } + if res < 0 { + Err(vm.new_last_os_error()) + } else { + Ok(res) + } } #[pyfunction] @@ -1480,7 +1478,7 @@ pub(super) mod _os { ) }; - usize::try_from(ret).map_err(|_| errno_err(vm)) + usize::try_from(ret).map_err(|_| vm.new_last_errno_error()) } #[pyfunction] diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 7409064668c..3fbea58e45b 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -28,9 +28,7 @@ pub mod module { convert::{IntoPyException, ToPyObject, TryFromObject}, function::{Either, KwArgs, OptionalArg}, ospath::{IOErrorBuilder, OsPath, OsPathOrFd}, - stdlib::os::{ - _os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, errno_err, fs_metadata, - }, + stdlib::os::{_os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, fs_metadata}, types::{Constructor, Representable}, utils::ToCString, }; @@ -467,7 +465,11 @@ pub mod module { { let [] = args.dir_fd.0; let res = unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) }; - if res < 0 { Err(errno_err(vm)) } else { Ok(()) } + if res < 0 { + Err(vm.new_last_errno_error()) + } else { + Ok(()) + } } } @@ -715,14 +717,22 @@ pub mod module { ) }, }; - if ret != 0 { Err(errno_err(vm)) } else { Ok(()) } + if ret != 0 { + Err(vm.new_last_errno_error()) + } else { + Ok(()) + } } #[cfg(target_vendor = "apple")] fn mknod(self, vm: &VirtualMachine) -> PyResult<()> { let [] = self.dir_fd.0; let ret = self._mknod(vm)?; - if ret != 0 { Err(errno_err(vm)) } else { Ok(()) } + if ret != 0 { + Err(vm.new_last_errno_error()) + } else { + Ok(()) + } } } @@ -739,7 +749,7 @@ pub mod module { Errno::clear(); let res = unsafe { libc::nice(increment) }; if res == -1 && Errno::last_raw() != 0 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(res) } @@ -750,7 +760,7 @@ pub mod module { fn sched_get_priority_max(policy: i32, vm: &VirtualMachine) -> PyResult<i32> { let max = unsafe { libc::sched_get_priority_max(policy) }; if max == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(max) } @@ -761,7 +771,7 @@ pub mod module { fn sched_get_priority_min(policy: i32, vm: &VirtualMachine) -> PyResult<i32> { let min = unsafe { libc::sched_get_priority_min(policy) }; if min == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(min) } @@ -852,7 +862,7 @@ pub mod module { fn sched_getscheduler(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<i32> { let policy = unsafe { libc::sched_getscheduler(pid) }; if policy == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(policy) } @@ -886,7 +896,7 @@ pub mod module { let libc_sched_param = args.sched_param_obj.try_to_libc(vm)?; let policy = unsafe { libc::sched_setscheduler(args.pid, args.policy, &libc_sched_param) }; if policy == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(policy) } @@ -902,7 +912,7 @@ pub mod module { let param = unsafe { let mut param = std::mem::MaybeUninit::uninit(); if -1 == libc::sched_getparam(pid, param.as_mut_ptr()) { - return Err(errno_err(vm)); + return Err(vm.new_last_errno_error()); } param.assume_init() }; @@ -937,7 +947,7 @@ pub mod module { let libc_sched_param = args.sched_param_obj.try_to_libc(vm)?; let ret = unsafe { libc::sched_setparam(args.pid, &libc_sched_param) }; if ret == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(ret) } @@ -1719,7 +1729,7 @@ pub mod module { { let ret = unsafe { libc::kill(pid, sig as i32) }; if ret == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(()) } @@ -1762,7 +1772,11 @@ pub mod module { #[pyfunction] fn _fcopyfile(in_fd: i32, out_fd: i32, flags: i32, vm: &VirtualMachine) -> PyResult<()> { let ret = unsafe { fcopyfile(in_fd, out_fd, std::ptr::null_mut(), flags as u32) }; - if ret < 0 { Err(errno_err(vm)) } else { Ok(()) } + if ret < 0 { + Err(vm.new_last_errno_error()) + } else { + Ok(()) + } } #[pyfunction] @@ -1884,7 +1898,7 @@ pub mod module { Errno::clear(); let retval = unsafe { libc::getpriority(which, who) }; if Errno::last_raw() != 0 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(vm.ctx.new_int(retval).into()) } @@ -1900,7 +1914,7 @@ pub mod module { ) -> PyResult<()> { let retval = unsafe { libc::setpriority(which, who, priority) }; if retval == -1 { - Err(errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(()) } @@ -2325,7 +2339,7 @@ pub mod module { fn sysconf(name: SysconfName, vm: &VirtualMachine) -> PyResult<libc::c_long> { let r = unsafe { libc::sysconf(name.0) }; if r == -1 { - return Err(errno_err(vm)); + return Err(vm.new_last_errno_error()); } Ok(r) } @@ -2452,7 +2466,7 @@ pub mod module { flags.unwrap_or(0), ) .try_into() - .map_err(|_| errno_err(vm))?; + .map_err(|_| vm.new_last_os_error())?; buf.set_len(len); } Ok(buf) diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index bab45b34151..5c3d8825f78 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -296,7 +296,7 @@ pub(crate) mod _signal { signal::assert_in_range(signum, vm)?; let res = unsafe { siginterrupt(signum, flag) }; if res < 0 { - Err(crate::stdlib::os::errno_err(vm)) + Err(vm.new_last_errno_error()) } else { Ok(()) } diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index 212697b7a93..1c6113bad77 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -799,7 +799,6 @@ mod platform { use crate::{ PyRef, PyResult, VirtualMachine, builtins::{PyNamespace, PyStrRef}, - stdlib::os::errno_err, }; use std::time::Duration; use windows_sys::Win32::{ @@ -818,7 +817,7 @@ mod platform { let frequency = unsafe { let mut freq = std::mem::MaybeUninit::uninit(); if QueryPerformanceFrequency(freq.as_mut_ptr()) == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } freq.assume_init() }; @@ -866,7 +865,7 @@ mod platform { _is_time_adjustment_disabled.as_mut_ptr(), ) == 0 { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } time_increment.assume_init() }; diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index 6b6e452b384..62505b2b74b 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -11,7 +11,6 @@ mod _winapi { common::windows::ToWideString, convert::{ToPyException, ToPyResult}, function::{ArgMapping, ArgSequence, OptionalArg}, - stdlib::os::errno_err, windows::{WinHandle, WindowsSysResult}, }; use std::ptr::{null, null_mut}; @@ -85,7 +84,7 @@ mod _winapi { ) -> PyResult<Option<WinHandle>> { let handle = unsafe { windows_sys::Win32::System::Console::GetStdHandle(std_handle) }; if handle == INVALID_HANDLE_VALUE { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } Ok(if handle.is_null() { // NULL handle - return None @@ -162,7 +161,7 @@ mod _winapi { ) -> PyResult<windows_sys::Win32::Storage::FileSystem::FILE_TYPE> { let file_type = unsafe { windows_sys::Win32::Storage::FileSystem::GetFileType(h.0) }; if file_type == 0 && unsafe { windows_sys::Win32::Foundation::GetLastError() } != 0 { - Err(errno_err(vm)) + Err(vm.new_last_os_error()) } else { Ok(file_type) } @@ -305,7 +304,7 @@ mod _winapi { ) }; if handle.is_null() { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } Ok(WinHandle(handle)) } @@ -418,7 +417,7 @@ mod _winapi { || unsafe { windows_sys::Win32::Foundation::GetLastError() } != windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } let mut attrlist = vec![0u8; size]; WindowsSysResult(unsafe { @@ -457,7 +456,7 @@ mod _winapi { fn WaitForSingleObject(h: WinHandle, ms: u32, vm: &VirtualMachine) -> PyResult<u32> { let ret = unsafe { windows_sys::Win32::System::Threading::WaitForSingleObject(h.0, ms) }; if ret == windows_sys::Win32::Foundation::WAIT_FAILED { - Err(errno_err(vm)) + Err(vm.new_last_os_error()) } else { Ok(ret) } @@ -530,7 +529,7 @@ mod _winapi { ) }; if handle == INVALID_HANDLE_VALUE { - return Err(errno_err(vm)); + return Err(vm.new_last_os_error()); } Ok(handle as _) } diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 63622b90a27..1054ba9b313 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -7,7 +7,7 @@ use crate::{ descriptor::PyMethodDescriptor, tuple::{IntoPyTuple, PyTupleRef}, }, - convert::ToPyObject, + convert::{ToPyException, ToPyObject}, function::{IntoPyNativeFn, PyMethodFlags}, scope::Scope, vm::VirtualMachine, @@ -202,6 +202,24 @@ impl VirtualMachine { )) } + /// Create a new OSError from the last OS error. + /// + /// On windows, windows-sys errors are expected to be handled by this function. + /// This is identical to `new_last_errno_error` on non-Windows platforms. + pub fn new_last_os_error(&self) -> PyBaseExceptionRef { + let err = std::io::Error::last_os_error(); + err.to_pyexception(self) + } + + /// Create a new OSError from the last POSIX errno. + /// + /// On windows, CRT errno are expected to be handled by this function. + /// This is identical to `new_last_os_error` on non-Windows platforms. + pub fn new_last_errno_error(&self) -> PyBaseExceptionRef { + let err = crate::common::os::errno_io_error(); + err.to_pyexception(self) + } + pub fn new_errno_error(&self, errno: i32, msg: impl Into<String>) -> PyBaseExceptionRef { let vm = self; let exc_type = diff --git a/crates/vm/src/windows.rs b/crates/vm/src/windows.rs index 394714be95f..ccf940811b8 100644 --- a/crates/vm/src/windows.rs +++ b/crates/vm/src/windows.rs @@ -5,7 +5,6 @@ use crate::common::fileutils::{ use crate::{ PyObjectRef, PyResult, TryFromObject, VirtualMachine, convert::{ToPyObject, ToPyResult}, - stdlib::os::errno_err, }; use rustpython_common::windows::ToWideString; use std::ffi::OsStr; @@ -47,10 +46,10 @@ impl<T: WindowsSysResultValue> WindowsSysResult<T> { self.0.is_err() } pub fn into_pyresult(self, vm: &VirtualMachine) -> PyResult<T::Ok> { - if self.is_err() { - Err(errno_err(vm)) - } else { + if !self.is_err() { Ok(self.0.into_ok()) + } else { + Err(vm.new_last_os_error()) } } } From 30fb9d73cc710eb97bc3f5dad7935a682ad836a6 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:21:32 +0900 Subject: [PATCH 459/819] Fix test_io on windows (#6387) * mark skip on test_io * Drop for FileIO * IO Desctructors * Iterator --- Lib/test/test_io.py | 9 +-- crates/vm/src/stdlib/io.rs | 143 ++++++++++++++++++++++++++++++++++--- 2 files changed, 140 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 19027370dcd..0142208427b 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -780,6 +780,7 @@ def test_closefd_attr(self): file = self.open(f.fileno(), "r", encoding="utf-8", closefd=False) self.assertEqual(file.buffer.raw.closefd, False) + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; cyclic GC not supported, causes file locking') @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): # FileIO objects are collected, and collecting them flushes @@ -1795,6 +1796,7 @@ def test_misbehaved_io_read(self): # checking this is not so easy. self.assertRaises(OSError, bufio.read, 10) + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; cyclic GC not supported, causes file locking') @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): # C BufferedReader objects are collected. @@ -2166,6 +2168,7 @@ def test_initialization(self): self.assertRaises(ValueError, bufio.__init__, rawio, buffer_size=-1) self.assertRaises(ValueError, bufio.write, b"def") + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; cyclic GC not supported, causes file locking') @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): # C BufferedWriter objects are collected, and collecting them flushes @@ -2677,6 +2680,7 @@ def test_interleaved_readline_write(self): class CBufferedRandomTest(BufferedRandomTest, SizeofTest): tp = io.BufferedRandom + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; cyclic GC not supported, causes file locking') @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): CBufferedReaderTest.test_garbage_collection(self) @@ -4122,6 +4126,7 @@ def test_initialization(self): t = self.TextIOWrapper.__new__(self.TextIOWrapper) self.assertRaises(Exception, repr, t) + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; cyclic GC not supported, causes file locking') @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): # C TextIOWrapper objects are collected, and collecting them flushes @@ -4271,10 +4276,6 @@ def test_reconfigure_write_through(self): def test_repr(self): return super().test_repr() - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_telling(self): - return super().test_telling() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_uninitialized(self): return super().test_uninitialized() diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 7cf21cc77e4..c373f88d20b 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -1753,11 +1753,23 @@ mod _io { } #[pyclass( - with(Constructor, BufferedMixin, BufferedReadable), + with(Constructor, BufferedMixin, BufferedReadable, Destructor), flags(BASETYPE, HAS_DICT) )] impl BufferedReader {} + impl Destructor for BufferedReader { + fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> { + let _ = vm.call_method(zelf, "close", ()); + Ok(()) + } + + #[cold] + fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_del is implemented") + } + } + impl DefaultConstructor for BufferedReader {} #[pyclass] @@ -1810,11 +1822,23 @@ mod _io { } #[pyclass( - with(Constructor, BufferedMixin, BufferedWritable), + with(Constructor, BufferedMixin, BufferedWritable, Destructor), flags(BASETYPE, HAS_DICT) )] impl BufferedWriter {} + impl Destructor for BufferedWriter { + fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> { + let _ = vm.call_method(zelf, "close", ()); + Ok(()) + } + + #[cold] + fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_del is implemented") + } + } + impl DefaultConstructor for BufferedWriter {} #[pyattr] @@ -1852,11 +1876,29 @@ mod _io { } #[pyclass( - with(Constructor, BufferedMixin, BufferedReadable, BufferedWritable), + with( + Constructor, + BufferedMixin, + BufferedReadable, + BufferedWritable, + Destructor + ), flags(BASETYPE, HAS_DICT) )] impl BufferedRandom {} + impl Destructor for BufferedRandom { + fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> { + let _ = vm.call_method(zelf, "close", ()); + Ok(()) + } + + #[cold] + fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_del is implemented") + } + } + impl DefaultConstructor for BufferedRandom {} #[pyattr] @@ -1900,7 +1942,13 @@ mod _io { } #[pyclass( - with(Constructor, Initializer, BufferedReadable, BufferedWritable), + with( + Constructor, + Initializer, + BufferedReadable, + BufferedWritable, + Destructor + ), flags(BASETYPE, HAS_DICT) )] impl BufferedRWPair { @@ -1942,6 +1990,18 @@ mod _io { } } + impl Destructor for BufferedRWPair { + fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> { + let _ = vm.call_method(zelf, "close", ()); + Ok(()) + } + + #[cold] + fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_del is implemented") + } + } + #[derive(FromArgs)] struct TextIOWrapperArgs { #[pyarg(any, default)] @@ -2413,7 +2473,10 @@ mod _io { vm.call_method(&textio.buffer, "flush", ()) } - #[pyclass(with(Constructor, Initializer), flags(BASETYPE))] + #[pyclass( + with(Constructor, Initializer, Destructor, Iterable, IterNext), + flags(BASETYPE) + )] impl TextIOWrapper { #[pymethod] fn reconfigure(&self, args: TextIOWrapperArgs, vm: &VirtualMachine) -> PyResult<()> { @@ -3276,6 +3339,57 @@ mod _io { } } + impl Destructor for TextIOWrapper { + fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> { + let _ = vm.call_method(zelf, "close", ()); + Ok(()) + } + + #[cold] + fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_del is implemented") + } + } + + impl Iterable for TextIOWrapper { + fn slot_iter(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + check_closed(&zelf, vm)?; + Ok(zelf) + } + + fn iter(_zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult { + unreachable!("slot_iter is implemented") + } + } + + impl IterNext for TextIOWrapper { + fn slot_iternext(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyIterReturn> { + // Set telling = false during iteration (matches CPython behavior) + let textio_ref: PyRef<TextIOWrapper> = + zelf.downcast_ref::<TextIOWrapper>().unwrap().to_owned(); + { + let mut textio = textio_ref.lock(vm)?; + textio.telling = false; + } + + let line = vm.call_method(zelf, "readline", ())?; + + if !line.clone().try_to_bool(vm)? { + // Restore telling on StopIteration + let mut textio = textio_ref.lock(vm)?; + textio.snapshot = None; + textio.telling = textio.seekable; + Ok(PyIterReturn::StopIteration(None)) + } else { + Ok(PyIterReturn::Return(line)) + } + } + + fn next(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyIterReturn> { + unreachable!("slot_iternext is implemented") + } + } + #[pyattr] #[pyclass(name)] #[derive(Debug, PyPayload, Default)] @@ -4166,14 +4280,15 @@ mod _io { mod fileio { use super::{_io::*, Offset}; use crate::{ - AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, + VirtualMachine, builtins::{PyBaseExceptionRef, PyUtf8Str, PyUtf8StrRef}, common::crt_fd, convert::{IntoPyException, ToPyException}, function::{ArgBytesLike, ArgMemoryBuffer, OptionalArg, OptionalOption}, ospath::{IOErrorBuilder, OsPath, OsPathOrFd}, stdlib::os, - types::{Constructor, DefaultConstructor, Initializer, Representable}, + types::{Constructor, DefaultConstructor, Destructor, Initializer, Representable}, }; use crossbeam_utils::atomic::AtomicCell; use std::io::{Read, Write}; @@ -4433,7 +4548,7 @@ mod fileio { } #[pyclass( - with(Constructor, Initializer, Representable), + with(Constructor, Initializer, Representable, Destructor), flags(BASETYPE, HAS_DICT) )] impl FileIO { @@ -4649,4 +4764,16 @@ mod fileio { Err(vm.new_type_error(format!("cannot pickle '{}' object", zelf.class().name()))) } } + + impl Destructor for FileIO { + fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> { + let _ = vm.call_method(zelf, "close", ()); + Ok(()) + } + + #[cold] + fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_del is implemented") + } + } } From a64cfac2ca594d08b42d06cdb0f6bdd18404e8a6 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:02:53 +0900 Subject: [PATCH 460/819] Fix multiprocessing closesocket, set_errno (#6388) * Fix multiprocessing closesocket * set_errno --- Cargo.lock | 1 + crates/common/Cargo.toml | 3 +++ crates/common/src/os.rs | 14 ++++++++++++++ crates/stdlib/src/multiprocessing.rs | 2 +- crates/stdlib/src/socket.rs | 7 +++++-- crates/vm/src/stdlib/posix.rs | 3 ++- 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa2382d99d8..1de5f6751ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3007,6 +3007,7 @@ dependencies = [ "malachite-base", "malachite-bigint", "malachite-q", + "nix 0.30.1", "num-complex", "num-traits", "once_cell", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index c75f33a7b7e..9fd7ea3880a 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -35,6 +35,9 @@ lock_api = "0.4" siphasher = "1" num-complex.workspace = true +[target.'cfg(unix)'.dependencies] +nix = { workspace = true } + [target.'cfg(windows)'.dependencies] widestring = { workspace = true } windows-sys = { workspace = true, features = [ diff --git a/crates/common/src/os.rs b/crates/common/src/os.rs index e61c77e0fd1..2d82b1a6ccc 100644 --- a/crates/common/src/os.rs +++ b/crates/common/src/os.rs @@ -48,6 +48,20 @@ pub fn get_errno() -> i32 { std::io::Error::last_os_error().posix_errno() } +/// Set errno to the specified value. +#[cfg(windows)] +pub fn set_errno(value: i32) { + unsafe extern "C" { + fn _set_errno(value: i32) -> i32; + } + unsafe { suppress_iph!(_set_errno(value)) }; +} + +#[cfg(unix)] +pub fn set_errno(value: i32) { + nix::errno::Errno::from_raw(value).set(); +} + #[cfg(unix)] pub fn bytes_as_os_str(b: &[u8]) -> Result<&std::ffi::OsStr, Utf8Error> { use std::os::unix::ffi::OsStrExt; diff --git a/crates/stdlib/src/multiprocessing.rs b/crates/stdlib/src/multiprocessing.rs index 280eff7d32f..9ff2d3dc318 100644 --- a/crates/stdlib/src/multiprocessing.rs +++ b/crates/stdlib/src/multiprocessing.rs @@ -9,7 +9,7 @@ mod _multiprocessing { #[pyfunction] fn closesocket(socket: usize, vm: &VirtualMachine) -> PyResult<()> { let res = unsafe { WinSock::closesocket(socket as SOCKET) }; - if res == 0 { + if res != 0 { Err(vm.new_last_os_error()) } else { Ok(()) diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index c00799feabd..d75595648aa 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -2143,10 +2143,11 @@ mod _socket { #[pyfunction] fn if_nametoindex(name: FsPath, vm: &VirtualMachine) -> PyResult<IfIndex> { let name = name.to_cstring(vm)?; - + // in case 'if_nametoindex' does not set errno + crate::common::os::set_errno(libc::ENODEV); let ret = unsafe { c::if_nametoindex(name.as_ptr() as _) }; if ret == 0 { - Err(vm.new_os_error("no interface with this name".to_owned())) + Err(vm.new_last_errno_error()) } else { Ok(ret) } @@ -2156,6 +2157,8 @@ mod _socket { #[pyfunction] fn if_indextoname(index: IfIndex, vm: &VirtualMachine) -> PyResult<String> { let mut buf = [0; c::IF_NAMESIZE + 1]; + // in case 'if_indextoname' does not set errno + crate::common::os::set_errno(libc::ENXIO); let ret = unsafe { c::if_indextoname(index, buf.as_mut_ptr()) }; if ret.is_null() { Err(vm.new_last_errno_error()) diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 3fbea58e45b..6a6703f48a5 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -2337,8 +2337,9 @@ pub mod module { #[pyfunction] fn sysconf(name: SysconfName, vm: &VirtualMachine) -> PyResult<libc::c_long> { + crate::common::os::set_errno(0); let r = unsafe { libc::sysconf(name.0) }; - if r == -1 { + if r == -1 && crate::common::os::get_errno() != 0 { return Err(vm.new_last_errno_error()); } Ok(r) From c71c78c1a077af0ce698e2519076ee78a657be32 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:29:20 +0900 Subject: [PATCH 461/819] Upgrade pyo3 (#6389) --- Cargo.toml | 2 +- benches/microbenchmarks.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5429ed2c827..f00f8c27f82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ rustyline = { workspace = true } [dev-dependencies] criterion = { workspace = true } -pyo3 = { version = "0.26", features = ["auto-initialize"] } +pyo3 = { version = "0.27", features = ["auto-initialize"] } [[bench]] name = "execution" diff --git a/benches/microbenchmarks.rs b/benches/microbenchmarks.rs index f70be595228..98993b41543 100644 --- a/benches/microbenchmarks.rs +++ b/benches/microbenchmarks.rs @@ -101,7 +101,10 @@ fn cpy_compile_code<'a>( let builtins = pyo3::types::PyModule::import(py, "builtins").expect("Failed to import builtins"); let compile = builtins.getattr("compile").expect("no compile in builtins"); - compile.call1((code, name, "exec"))?.extract() + Ok(compile + .call1((code, name, "exec"))? + .cast_into() + .expect("compile() should return a code object")) } fn bench_rustpython_code(group: &mut BenchmarkGroup<WallTime>, bench: &MicroBenchmark) { From 90717e5ef7c6d5e87f8b74a947643f0d91d6b8e7 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:23:27 +0900 Subject: [PATCH 462/819] doc db to include types.* (#6391) * Add types.* for doc/generate.py * udpate-doc-db workflow * Update doc DB for CPython 3.13.9 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- .github/workflows/update-doc-db.yml | 41 +- crates/doc/generate.py | 11 +- crates/doc/src/data.inc.rs | 554 ++++++++++++++++++++++++++++ 3 files changed, 591 insertions(+), 15 deletions(-) diff --git a/.github/workflows/update-doc-db.yml b/.github/workflows/update-doc-db.yml index 1376e844fd1..c53de5461eb 100644 --- a/.github/workflows/update-doc-db.yml +++ b/.github/workflows/update-doc-db.yml @@ -1,7 +1,7 @@ name: Update doc DB permissions: - contents: read + contents: write on: workflow_dispatch: @@ -10,11 +10,14 @@ on: description: Target python version to generate doc db for type: string default: "3.13.9" + ref: + description: Branch to commit to (leave empty for current branch) + type: string + default: "" defaults: run: shell: bash - working-directory: ./crates/rustpython-doc jobs: generate: @@ -30,19 +33,19 @@ jobs: with: persist-credentials: false sparse-checkout: | - crates/rustpython-doc + crates/doc - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ inputs.python-version }} - name: Generate docs - run: python ./generate.py + run: python crates/doc/generate.py - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: doc-db-${{ inputs.python-version }}-${{ matrix.os }} - path: "crates/rustpython-doc/generated/*.json" + path: "crates/doc/generated/*.json" if-no-files-found: error retention-days: 7 overwrite: true @@ -53,26 +56,25 @@ jobs: steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: - persist-credentials: false - sparse-checkout: | - crates/rustpython-doc + ref: ${{ inputs.ref || github.ref }} + token: ${{ secrets.AUTO_COMMIT_PAT }} - name: Download generated doc DBs uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: pattern: "doc-db-${{ inputs.python-version }}-**" - path: crates/rustpython-doc/generated/ + path: crates/doc/generated/ merge-multiple: true - name: Transform JSON run: | # Merge all artifacts - jq -s "add" --sort-keys generated/*.json > generated/merged.json + jq -s "add" --sort-keys crates/doc/generated/*.json > crates/doc/generated/merged.json # Format merged json for the phf macro - jq -r 'to_entries[] | " \(.key | @json) => \(.value | @json),"' generated/merged.json > generated/raw_entries.txt + jq -r 'to_entries[] | " \(.key | @json) => \(.value | @json),"' crates/doc/generated/merged.json > crates/doc/generated/raw_entries.txt - OUTPUT_FILE='src/data.inc.rs' + OUTPUT_FILE='crates/doc/src/data.inc.rs' echo -n '' > $OUTPUT_FILE @@ -83,13 +85,24 @@ jobs: echo '' >> $OUTPUT_FILE echo "pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! {" >> $OUTPUT_FILE - cat generated/raw_entries.txt >> $OUTPUT_FILE + cat crates/doc/generated/raw_entries.txt >> $OUTPUT_FILE echo '};' >> $OUTPUT_FILE - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: doc-db-${{ inputs.python-version }} - path: "crates/rustpython-doc/src/data.inc.rs" + path: "crates/doc/src/data.inc.rs" if-no-files-found: error retention-days: 7 overwrite: true + + - name: Commit and push (non-main branches only) + if: github.ref != 'refs/heads/main' && inputs.ref != 'main' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + if [ -n "$(git status --porcelain)" ]; then + git add crates/doc/src/data.inc.rs + git commit -m "Update doc DB for CPython ${{ inputs.python-version }}" + git push + fi diff --git a/crates/doc/generate.py b/crates/doc/generate.py index 2f553ac0a19..73cb462bb9f 100644 --- a/crates/doc/generate.py +++ b/crates/doc/generate.py @@ -194,6 +194,15 @@ def find_doc_entries() -> "Iterable[DocEntry]": type(str().__iter__()), type(tuple().__iter__()), ] + + # Add types from the types module (e.g., ModuleType, FunctionType, etc.) + for name in dir(types): + if name.startswith("_"): + continue + obj = getattr(types, name) + if isinstance(obj, type): + builtin_types.append(obj) + for typ in builtin_types: parts = ("builtins", typ.__name__) yield DocEntry(parts, pydoc._getowndoc(typ)) @@ -204,7 +213,7 @@ def main(): docs = { entry.key: entry.doc for entry in find_doc_entries() - if entry.raw_doc is not None + if entry.raw_doc is not None and isinstance(entry.raw_doc, str) } dumped = json.dumps(docs, sort_keys=True, indent=4) OUTPUT_FILE.write_text(dumped) diff --git a/crates/doc/src/data.inc.rs b/crates/doc/src/data.inc.rs index ee08b59f7ba..4411587ca8b 100644 --- a/crates/doc/src/data.inc.rs +++ b/crates/doc/src/data.inc.rs @@ -4050,6 +4050,28 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.DeprecationWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.DeprecationWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", "builtins.DeprecationWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.DynamicClassAttribute" => "Route attribute access on a class to __getattr__.\n\nThis is a descriptor, used to define attributes that act differently when\naccessed through an instance and through a class. Instance access remains\nnormal, but access to an attribute through a class will be routed to the\nclass's __getattr__ method; this is done by raising AttributeError.\n\nThis allows one to have properties active on an instance, and have virtual\nattributes on the class with the same name. (Enum used this between Python\nversions 3.4 - 3.9 .)\n\nSubclass from this to use a different method of accessing virtual attributes\nand still be treated properly by the inspect module. (Enum uses this since\nPython 3.10 .)", + "builtins.DynamicClassAttribute.__delattr__" => "Implement delattr(self, name).", + "builtins.DynamicClassAttribute.__eq__" => "Return self==value.", + "builtins.DynamicClassAttribute.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.DynamicClassAttribute.__ge__" => "Return self>=value.", + "builtins.DynamicClassAttribute.__getattribute__" => "Return getattr(self, name).", + "builtins.DynamicClassAttribute.__getstate__" => "Helper for pickle.", + "builtins.DynamicClassAttribute.__gt__" => "Return self>value.", + "builtins.DynamicClassAttribute.__hash__" => "Return hash(self).", + "builtins.DynamicClassAttribute.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.DynamicClassAttribute.__le__" => "Return self<=value.", + "builtins.DynamicClassAttribute.__lt__" => "Return self<value.", + "builtins.DynamicClassAttribute.__ne__" => "Return self!=value.", + "builtins.DynamicClassAttribute.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.DynamicClassAttribute.__reduce__" => "Helper for pickle.", + "builtins.DynamicClassAttribute.__reduce_ex__" => "Helper for pickle.", + "builtins.DynamicClassAttribute.__repr__" => "Return repr(self).", + "builtins.DynamicClassAttribute.__setattr__" => "Implement setattr(self, name, value).", + "builtins.DynamicClassAttribute.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.DynamicClassAttribute.__str__" => "Return str(self).", + "builtins.DynamicClassAttribute.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.DynamicClassAttribute.__weakref__" => "list of weak references to the object", "builtins.EOFError" => "Read beyond end of file.", "builtins.EOFError.__cause__" => "exception cause", "builtins.EOFError.__context__" => "exception context", @@ -4318,6 +4340,33 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.GeneratorExit.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.GeneratorExit.add_note" => "Exception.add_note(note) --\nadd a note to the exception", "builtins.GeneratorExit.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.GenericAlias" => "Represent a PEP 585 generic type\n\nE.g. for t = list[int], t.__origin__ is list and t.__args__ is (int,).", + "builtins.GenericAlias.__call__" => "Call self as a function.", + "builtins.GenericAlias.__delattr__" => "Implement delattr(self, name).", + "builtins.GenericAlias.__eq__" => "Return self==value.", + "builtins.GenericAlias.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.GenericAlias.__ge__" => "Return self>=value.", + "builtins.GenericAlias.__getattribute__" => "Return getattr(self, name).", + "builtins.GenericAlias.__getitem__" => "Return self[key].", + "builtins.GenericAlias.__getstate__" => "Helper for pickle.", + "builtins.GenericAlias.__gt__" => "Return self>value.", + "builtins.GenericAlias.__hash__" => "Return hash(self).", + "builtins.GenericAlias.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.GenericAlias.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.GenericAlias.__iter__" => "Implement iter(self).", + "builtins.GenericAlias.__le__" => "Return self<=value.", + "builtins.GenericAlias.__lt__" => "Return self<value.", + "builtins.GenericAlias.__ne__" => "Return self!=value.", + "builtins.GenericAlias.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.GenericAlias.__or__" => "Return self|value.", + "builtins.GenericAlias.__parameters__" => "Type variables in the GenericAlias.", + "builtins.GenericAlias.__reduce_ex__" => "Helper for pickle.", + "builtins.GenericAlias.__repr__" => "Return repr(self).", + "builtins.GenericAlias.__ror__" => "Return value|self.", + "builtins.GenericAlias.__setattr__" => "Implement setattr(self, name, value).", + "builtins.GenericAlias.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.GenericAlias.__str__" => "Return str(self).", + "builtins.GenericAlias.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.IOError" => "Base class for I/O related errors.", "builtins.IOError.__cause__" => "exception cause", "builtins.IOError.__context__" => "exception context", @@ -4753,6 +4802,28 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.NotImplementedError.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.NotImplementedError.add_note" => "Exception.add_note(note) --\nadd a note to the exception", "builtins.NotImplementedError.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.NotImplementedType" => "The type of the NotImplemented singleton.", + "builtins.NotImplementedType.__bool__" => "True if self else False", + "builtins.NotImplementedType.__delattr__" => "Implement delattr(self, name).", + "builtins.NotImplementedType.__eq__" => "Return self==value.", + "builtins.NotImplementedType.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.NotImplementedType.__ge__" => "Return self>=value.", + "builtins.NotImplementedType.__getattribute__" => "Return getattr(self, name).", + "builtins.NotImplementedType.__getstate__" => "Helper for pickle.", + "builtins.NotImplementedType.__gt__" => "Return self>value.", + "builtins.NotImplementedType.__hash__" => "Return hash(self).", + "builtins.NotImplementedType.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.NotImplementedType.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.NotImplementedType.__le__" => "Return self<=value.", + "builtins.NotImplementedType.__lt__" => "Return self<value.", + "builtins.NotImplementedType.__ne__" => "Return self!=value.", + "builtins.NotImplementedType.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.NotImplementedType.__reduce_ex__" => "Helper for pickle.", + "builtins.NotImplementedType.__repr__" => "Return repr(self).", + "builtins.NotImplementedType.__setattr__" => "Implement setattr(self, name, value).", + "builtins.NotImplementedType.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.NotImplementedType.__str__" => "Return str(self).", + "builtins.NotImplementedType.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.OSError" => "Base class for I/O related errors.", "builtins.OSError.__cause__" => "exception cause", "builtins.OSError.__context__" => "exception context", @@ -5043,6 +5114,28 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.RuntimeWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.RuntimeWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", "builtins.RuntimeWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.SimpleNamespace" => "A simple attribute-based namespace.", + "builtins.SimpleNamespace.__delattr__" => "Implement delattr(self, name).", + "builtins.SimpleNamespace.__eq__" => "Return self==value.", + "builtins.SimpleNamespace.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.SimpleNamespace.__ge__" => "Return self>=value.", + "builtins.SimpleNamespace.__getattribute__" => "Return getattr(self, name).", + "builtins.SimpleNamespace.__getstate__" => "Helper for pickle.", + "builtins.SimpleNamespace.__gt__" => "Return self>value.", + "builtins.SimpleNamespace.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.SimpleNamespace.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.SimpleNamespace.__le__" => "Return self<=value.", + "builtins.SimpleNamespace.__lt__" => "Return self<value.", + "builtins.SimpleNamespace.__ne__" => "Return self!=value.", + "builtins.SimpleNamespace.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.SimpleNamespace.__reduce__" => "Return state information for pickling", + "builtins.SimpleNamespace.__reduce_ex__" => "Helper for pickle.", + "builtins.SimpleNamespace.__replace__" => "Return a copy of the namespace object with new values for the specified attributes.", + "builtins.SimpleNamespace.__repr__" => "Return repr(self).", + "builtins.SimpleNamespace.__setattr__" => "Implement setattr(self, name, value).", + "builtins.SimpleNamespace.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.SimpleNamespace.__str__" => "Return str(self).", + "builtins.SimpleNamespace.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.StopAsyncIteration" => "Signal the end from iterator.__anext__().", "builtins.StopAsyncIteration.__cause__" => "exception cause", "builtins.StopAsyncIteration.__context__" => "exception context", @@ -5457,6 +5550,32 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.UnicodeWarning.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.UnicodeWarning.add_note" => "Exception.add_note(note) --\nadd a note to the exception", "builtins.UnicodeWarning.with_traceback" => "Exception.with_traceback(tb) --\nset self.__traceback__ to tb and return self.", + "builtins.UnionType" => "Represent a PEP 604 union type\n\nE.g. for int | str", + "builtins.UnionType.__delattr__" => "Implement delattr(self, name).", + "builtins.UnionType.__eq__" => "Return self==value.", + "builtins.UnionType.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.UnionType.__ge__" => "Return self>=value.", + "builtins.UnionType.__getattribute__" => "Return getattr(self, name).", + "builtins.UnionType.__getitem__" => "Return self[key].", + "builtins.UnionType.__getstate__" => "Helper for pickle.", + "builtins.UnionType.__gt__" => "Return self>value.", + "builtins.UnionType.__hash__" => "Return hash(self).", + "builtins.UnionType.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.UnionType.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.UnionType.__le__" => "Return self<=value.", + "builtins.UnionType.__lt__" => "Return self<value.", + "builtins.UnionType.__ne__" => "Return self!=value.", + "builtins.UnionType.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.UnionType.__or__" => "Return self|value.", + "builtins.UnionType.__parameters__" => "Type variables in the types.UnionType.", + "builtins.UnionType.__reduce__" => "Helper for pickle.", + "builtins.UnionType.__reduce_ex__" => "Helper for pickle.", + "builtins.UnionType.__repr__" => "Return repr(self).", + "builtins.UnionType.__ror__" => "Return value|self.", + "builtins.UnionType.__setattr__" => "Implement setattr(self, name, value).", + "builtins.UnionType.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.UnionType.__str__" => "Return str(self).", + "builtins.UnionType.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.UserWarning" => "Base class for warnings generated by user code.", "builtins.UserWarning.__cause__" => "exception cause", "builtins.UserWarning.__context__" => "exception context", @@ -5628,6 +5747,35 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.anext" => "Return the next item from the async iterator.\n\nIf default is given and the async iterator is exhausted,\nit is returned instead of raising StopAsyncIteration.", "builtins.any" => "Return True if bool(x) is True for any x in the iterable.\n\nIf the iterable is empty, return False.", "builtins.ascii" => "Return an ASCII-only representation of an object.\n\nAs repr(), return a string containing a printable representation of an\nobject, but escape the non-ASCII characters in the string returned by\nrepr() using \\\\x, \\\\u or \\\\U escapes. This generates a string similar\nto that returned by repr() in Python 2.", + "builtins.async_generator.__aiter__" => "Return an awaitable, that resolves in asynchronous iterator.", + "builtins.async_generator.__anext__" => "Return a value or raise StopAsyncIteration.", + "builtins.async_generator.__class_getitem__" => "See PEP 585", + "builtins.async_generator.__del__" => "Called when the instance is about to be destroyed.", + "builtins.async_generator.__delattr__" => "Implement delattr(self, name).", + "builtins.async_generator.__eq__" => "Return self==value.", + "builtins.async_generator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.async_generator.__ge__" => "Return self>=value.", + "builtins.async_generator.__getattribute__" => "Return getattr(self, name).", + "builtins.async_generator.__getstate__" => "Helper for pickle.", + "builtins.async_generator.__gt__" => "Return self>value.", + "builtins.async_generator.__hash__" => "Return hash(self).", + "builtins.async_generator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.async_generator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.async_generator.__le__" => "Return self<=value.", + "builtins.async_generator.__lt__" => "Return self<value.", + "builtins.async_generator.__ne__" => "Return self!=value.", + "builtins.async_generator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.async_generator.__reduce__" => "Helper for pickle.", + "builtins.async_generator.__reduce_ex__" => "Helper for pickle.", + "builtins.async_generator.__repr__" => "Return repr(self).", + "builtins.async_generator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.async_generator.__sizeof__" => "gen.__sizeof__() -> size of gen in memory, in bytes", + "builtins.async_generator.__str__" => "Return str(self).", + "builtins.async_generator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.async_generator.aclose" => "aclose() -> raise GeneratorExit inside generator.", + "builtins.async_generator.ag_await" => "object being awaited on, or None", + "builtins.async_generator.asend" => "asend(v) -> send 'v' in generator.", + "builtins.async_generator.athrow" => "athrow(value)\nathrow(type[,value[,tb]])\n\nraise exception in generator.\nthe (type, val, tb) signature is deprecated, \nand may be removed in a future version of Python.", "builtins.bin" => "Return the binary representation of an integer.\n\n>>> bin(2796202)\n'0b1010101010101010101010'", "builtins.bool" => "Returns True when the argument is true, False otherwise.\nThe builtins True and False are the only two instances of the class bool.\nThe class bool is a subclass of the class int, and cannot be subclassed.", "builtins.bool.__abs__" => "abs(self)", @@ -5701,6 +5849,27 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.bool.real" => "the real part of a complex number", "builtins.bool.to_bytes" => "Return an array of bytes representing an integer.\n\nlength\n Length of bytes object to use. An OverflowError is raised if the\n integer is not representable with the given number of bytes. Default\n is length 1.\nbyteorder\n The byte order used to represent the integer. If byteorder is 'big',\n the most significant byte is at the beginning of the byte array. If\n byteorder is 'little', the most significant byte is at the end of the\n byte array. To request the native byte order of the host system, use\n sys.byteorder as the byte order value. Default is to use 'big'.\nsigned\n Determines whether two's complement is used to represent the integer.\n If signed is False and a negative integer is given, an OverflowError\n is raised.", "builtins.breakpoint" => "Call sys.breakpointhook(*args, **kws). sys.breakpointhook() must accept\nwhatever arguments are passed.\n\nBy default, this drops you into the pdb debugger.", + "builtins.builtin_function_or_method.__call__" => "Call self as a function.", + "builtins.builtin_function_or_method.__delattr__" => "Implement delattr(self, name).", + "builtins.builtin_function_or_method.__eq__" => "Return self==value.", + "builtins.builtin_function_or_method.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.builtin_function_or_method.__ge__" => "Return self>=value.", + "builtins.builtin_function_or_method.__getattribute__" => "Return getattr(self, name).", + "builtins.builtin_function_or_method.__getstate__" => "Helper for pickle.", + "builtins.builtin_function_or_method.__gt__" => "Return self>value.", + "builtins.builtin_function_or_method.__hash__" => "Return hash(self).", + "builtins.builtin_function_or_method.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.builtin_function_or_method.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.builtin_function_or_method.__le__" => "Return self<=value.", + "builtins.builtin_function_or_method.__lt__" => "Return self<value.", + "builtins.builtin_function_or_method.__ne__" => "Return self!=value.", + "builtins.builtin_function_or_method.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.builtin_function_or_method.__reduce_ex__" => "Helper for pickle.", + "builtins.builtin_function_or_method.__repr__" => "Return repr(self).", + "builtins.builtin_function_or_method.__setattr__" => "Implement setattr(self, name, value).", + "builtins.builtin_function_or_method.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.builtin_function_or_method.__str__" => "Return str(self).", + "builtins.builtin_function_or_method.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.bytearray" => "bytearray(iterable_of_ints) -> bytearray\nbytearray(string, encoding[, errors]) -> bytearray\nbytearray(bytes_or_buffer) -> mutable copy of bytes_or_buffer\nbytearray(int) -> bytes array of size given by the parameter initialized with null bytes\nbytearray() -> empty bytes array\n\nConstruct a mutable bytearray object from:\n - an iterable yielding integers in range(256)\n - a text string encoded using the specified encoding\n - a bytes or a buffer object\n - any object implementing the buffer API.\n - an integer", "builtins.bytearray.__add__" => "Return self+value.", "builtins.bytearray.__alloc__" => "B.__alloc__() -> int\n\nReturn the number of bytes actually allocated.", @@ -5914,6 +6083,27 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.bytes_iterator.__str__" => "Return str(self).", "builtins.bytes_iterator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.callable" => "Return whether the object is callable (i.e., some kind of function).\n\nNote that classes are callable, as are instances of classes with a\n__call__() method.", + "builtins.cell" => "Create a new cell object.\n\n contents\n the contents of the cell. If not specified, the cell will be empty,\n and \nfurther attempts to access its cell_contents attribute will\n raise a ValueError.", + "builtins.cell.__delattr__" => "Implement delattr(self, name).", + "builtins.cell.__eq__" => "Return self==value.", + "builtins.cell.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.cell.__ge__" => "Return self>=value.", + "builtins.cell.__getattribute__" => "Return getattr(self, name).", + "builtins.cell.__getstate__" => "Helper for pickle.", + "builtins.cell.__gt__" => "Return self>value.", + "builtins.cell.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.cell.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.cell.__le__" => "Return self<=value.", + "builtins.cell.__lt__" => "Return self<value.", + "builtins.cell.__ne__" => "Return self!=value.", + "builtins.cell.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.cell.__reduce__" => "Helper for pickle.", + "builtins.cell.__reduce_ex__" => "Helper for pickle.", + "builtins.cell.__repr__" => "Return repr(self).", + "builtins.cell.__setattr__" => "Implement setattr(self, name, value).", + "builtins.cell.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.cell.__str__" => "Return str(self).", + "builtins.cell.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.chr" => "Return a Unicode string of one character with ordinal i; 0 <= i <= 0x10ffff.", "builtins.classmethod" => "Convert a function to be a class method.\n\nA class method receives the class as implicit first argument,\njust like an instance method receives the instance.\nTo declare a class method, use this idiom:\n\n class C:\n @classmethod\n def f(cls, arg1, arg2, argN):\n ...\n\nIt can be called either on the class (e.g. C.f()) or on an instance\n(e.g. C().f()). The instance is ignored except for its class.\nIf a class method is called for a derived class, the derived class\nobject is passed as the implied first argument.\n\nClass methods are different than C++ or Java static methods.\nIf you want those, see the staticmethod builtin.", "builtins.classmethod.__delattr__" => "Implement delattr(self, name).", @@ -5938,6 +6128,53 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.classmethod.__sizeof__" => "Size of object in memory, in bytes.", "builtins.classmethod.__str__" => "Return str(self).", "builtins.classmethod.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.classmethod_descriptor.__call__" => "Call self as a function.", + "builtins.classmethod_descriptor.__delattr__" => "Implement delattr(self, name).", + "builtins.classmethod_descriptor.__eq__" => "Return self==value.", + "builtins.classmethod_descriptor.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.classmethod_descriptor.__ge__" => "Return self>=value.", + "builtins.classmethod_descriptor.__get__" => "Return an attribute of instance, which is of type owner.", + "builtins.classmethod_descriptor.__getattribute__" => "Return getattr(self, name).", + "builtins.classmethod_descriptor.__getstate__" => "Helper for pickle.", + "builtins.classmethod_descriptor.__gt__" => "Return self>value.", + "builtins.classmethod_descriptor.__hash__" => "Return hash(self).", + "builtins.classmethod_descriptor.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.classmethod_descriptor.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.classmethod_descriptor.__le__" => "Return self<=value.", + "builtins.classmethod_descriptor.__lt__" => "Return self<value.", + "builtins.classmethod_descriptor.__ne__" => "Return self!=value.", + "builtins.classmethod_descriptor.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.classmethod_descriptor.__reduce__" => "Helper for pickle.", + "builtins.classmethod_descriptor.__reduce_ex__" => "Helper for pickle.", + "builtins.classmethod_descriptor.__repr__" => "Return repr(self).", + "builtins.classmethod_descriptor.__setattr__" => "Implement setattr(self, name, value).", + "builtins.classmethod_descriptor.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.classmethod_descriptor.__str__" => "Return str(self).", + "builtins.classmethod_descriptor.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.code" => "Create a code object. Not for the faint of heart.", + "builtins.code.__delattr__" => "Implement delattr(self, name).", + "builtins.code.__eq__" => "Return self==value.", + "builtins.code.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.code.__ge__" => "Return self>=value.", + "builtins.code.__getattribute__" => "Return getattr(self, name).", + "builtins.code.__getstate__" => "Helper for pickle.", + "builtins.code.__gt__" => "Return self>value.", + "builtins.code.__hash__" => "Return hash(self).", + "builtins.code.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.code.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.code.__le__" => "Return self<=value.", + "builtins.code.__lt__" => "Return self<value.", + "builtins.code.__ne__" => "Return self!=value.", + "builtins.code.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.code.__reduce__" => "Helper for pickle.", + "builtins.code.__reduce_ex__" => "Helper for pickle.", + "builtins.code.__replace__" => "The same as replace().", + "builtins.code.__repr__" => "Return repr(self).", + "builtins.code.__setattr__" => "Implement setattr(self, name, value).", + "builtins.code.__str__" => "Return str(self).", + "builtins.code.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.code._varname_from_oparg" => "(internal-only) Return the local variable name for the given oparg.\n\nWARNING: this method is for internal use only and may change or go away.", + "builtins.code.replace" => "Return a copy of the code object with new values for the specified fields.", "builtins.compile" => "Compile source into a code object that can be executed by exec() or eval().\n\nThe source code may represent a Python module, statement or expression.\nThe filename will be used for run-time error messages.\nThe mode must be 'exec' to compile a module, 'single' to compile a\nsingle (interactive) statement, or 'eval' to compile an expression.\nThe flags argument, if present, controls which future statements influence\nthe compilation of the code.\nThe dont_inherit argument, if true, stops the compilation inheriting\nthe effects of any future statements in effect in the code calling\ncompile; if absent or false these statements do influence the compilation,\nin addition to any features explicitly specified.", "builtins.complex" => "Create a complex number from a string or numbers.\n\nIf a string is given, parse it as a complex number.\nIf a single number is given, convert it to a complex number.\nIf the 'real' or 'imag' arguments are given, create a complex number\nwith the specified real and imaginary components.", "builtins.complex.__abs__" => "abs(self)", @@ -5979,6 +6216,34 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.complex.conjugate" => "Return the complex conjugate of its argument. (3-4j).conjugate() == 3+4j.", "builtins.complex.imag" => "the imaginary part of a complex number", "builtins.complex.real" => "the real part of a complex number", + "builtins.coroutine.__await__" => "Return an iterator to be used in await expression.", + "builtins.coroutine.__class_getitem__" => "See PEP 585", + "builtins.coroutine.__del__" => "Called when the instance is about to be destroyed.", + "builtins.coroutine.__delattr__" => "Implement delattr(self, name).", + "builtins.coroutine.__eq__" => "Return self==value.", + "builtins.coroutine.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.coroutine.__ge__" => "Return self>=value.", + "builtins.coroutine.__getattribute__" => "Return getattr(self, name).", + "builtins.coroutine.__getstate__" => "Helper for pickle.", + "builtins.coroutine.__gt__" => "Return self>value.", + "builtins.coroutine.__hash__" => "Return hash(self).", + "builtins.coroutine.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.coroutine.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.coroutine.__le__" => "Return self<=value.", + "builtins.coroutine.__lt__" => "Return self<value.", + "builtins.coroutine.__ne__" => "Return self!=value.", + "builtins.coroutine.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.coroutine.__reduce__" => "Helper for pickle.", + "builtins.coroutine.__reduce_ex__" => "Helper for pickle.", + "builtins.coroutine.__repr__" => "Return repr(self).", + "builtins.coroutine.__setattr__" => "Implement setattr(self, name, value).", + "builtins.coroutine.__sizeof__" => "gen.__sizeof__() -> size of gen in memory, in bytes", + "builtins.coroutine.__str__" => "Return str(self).", + "builtins.coroutine.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.coroutine.close" => "close() -> raise GeneratorExit inside coroutine.", + "builtins.coroutine.cr_await" => "object being awaited on, or None", + "builtins.coroutine.send" => "send(arg) -> send 'arg' into coroutine,\nreturn next iterated value or raise StopIteration.", + "builtins.coroutine.throw" => "throw(value)\nthrow(type[,value[,traceback]])\n\nRaise exception in coroutine, return next iterated value or raise\nStopIteration.\nthe (type, val, tb) signature is deprecated, \nand may be removed in a future version of Python.", "builtins.delattr" => "Deletes the named attribute from the given object.\n\ndelattr(x, 'y') is equivalent to ``del x.y``", "builtins.dict" => "dict() -> new empty dictionary\ndict(mapping) -> new dictionary initialized from a mapping object's\n (key, value) pairs\ndict(iterable) -> new dictionary initialized as if via:\n d = {}\n for k, v in iterable:\n d[k] = v\ndict(**kwargs) -> new dictionary initialized with the name=value pairs\n in the keyword argument list. For example: dict(one=1, two=2)", "builtins.dict.__class_getitem__" => "See PEP 585", @@ -6156,6 +6421,27 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.dict_values.mapping" => "dictionary that this view refers to", "builtins.dir" => "dir([object]) -> list of strings\n\nIf called without an argument, return the names in the current scope.\nElse, return an alphabetized list of names comprising (some of) the attributes\nof the given object, and of attributes reachable from it.\nIf the object supplies a method named __dir__, it will be used; otherwise\nthe default dir() logic is used and returns:\n for a module object: the module's attributes.\n for a class object: its attributes, and recursively the attributes\n of its bases.\n for any other object: its attributes, its class's attributes, and\n recursively the attributes of its class's base classes.", "builtins.divmod" => "Return the tuple (x//y, x%y). Invariant: div*y + mod == x.", + "builtins.ellipsis" => "The type of the Ellipsis singleton.", + "builtins.ellipsis.__delattr__" => "Implement delattr(self, name).", + "builtins.ellipsis.__eq__" => "Return self==value.", + "builtins.ellipsis.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.ellipsis.__ge__" => "Return self>=value.", + "builtins.ellipsis.__getattribute__" => "Return getattr(self, name).", + "builtins.ellipsis.__getstate__" => "Helper for pickle.", + "builtins.ellipsis.__gt__" => "Return self>value.", + "builtins.ellipsis.__hash__" => "Return hash(self).", + "builtins.ellipsis.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.ellipsis.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.ellipsis.__le__" => "Return self<=value.", + "builtins.ellipsis.__lt__" => "Return self<value.", + "builtins.ellipsis.__ne__" => "Return self!=value.", + "builtins.ellipsis.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.ellipsis.__reduce_ex__" => "Helper for pickle.", + "builtins.ellipsis.__repr__" => "Return repr(self).", + "builtins.ellipsis.__setattr__" => "Implement setattr(self, name, value).", + "builtins.ellipsis.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.ellipsis.__str__" => "Return str(self).", + "builtins.ellipsis.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.enumerate" => "Return an enumerate object.\n\n iterable\n an object supporting iteration\n\nThe enumerate object yields pairs containing a count (from start, which\ndefaults to zero) and a value yielded by the iterable argument.\n\nenumerate is useful for obtaining an indexed list:\n (0, seq[0]), (1, seq[1]), (2, seq[2]), ...", "builtins.enumerate.__class_getitem__" => "See PEP 585", "builtins.enumerate.__delattr__" => "Implement delattr(self, name).", @@ -6264,6 +6550,28 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.float.is_integer" => "Return True if the float is an integer.", "builtins.float.real" => "the real part of a complex number", "builtins.format" => "Return type(value).__format__(value, format_spec)\n\nMany built-in types implement format_spec according to the\nFormat Specification Mini-language. See help('FORMATTING').\n\nIf type(value) does not supply a method named __format__\nand format_spec is empty, then str(value) is returned.\nSee also help('SPECIALMETHODS').", + "builtins.frame.__delattr__" => "Implement delattr(self, name).", + "builtins.frame.__eq__" => "Return self==value.", + "builtins.frame.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.frame.__ge__" => "Return self>=value.", + "builtins.frame.__getattribute__" => "Return getattr(self, name).", + "builtins.frame.__getstate__" => "Helper for pickle.", + "builtins.frame.__gt__" => "Return self>value.", + "builtins.frame.__hash__" => "Return hash(self).", + "builtins.frame.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.frame.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.frame.__le__" => "Return self<=value.", + "builtins.frame.__lt__" => "Return self<value.", + "builtins.frame.__ne__" => "Return self!=value.", + "builtins.frame.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.frame.__reduce__" => "Helper for pickle.", + "builtins.frame.__reduce_ex__" => "Helper for pickle.", + "builtins.frame.__repr__" => "Return repr(self).", + "builtins.frame.__setattr__" => "Implement setattr(self, name, value).", + "builtins.frame.__sizeof__" => "F.__sizeof__() -> size of F in memory, in bytes", + "builtins.frame.__str__" => "Return str(self).", + "builtins.frame.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.frame.clear" => "F.clear(): clear most references held by the frame", "builtins.frozenset" => "Build an immutable unordered collection of unique elements.", "builtins.frozenset.__and__" => "Return self&value.", "builtins.frozenset.__class_getitem__" => "See PEP 585", @@ -6331,7 +6639,60 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.function.__str__" => "Return str(self).", "builtins.function.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.function.__type_params__" => "Get the declared type parameters for a function.", + "builtins.generator.__class_getitem__" => "See PEP 585", + "builtins.generator.__del__" => "Called when the instance is about to be destroyed.", + "builtins.generator.__delattr__" => "Implement delattr(self, name).", + "builtins.generator.__eq__" => "Return self==value.", + "builtins.generator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.generator.__ge__" => "Return self>=value.", + "builtins.generator.__getattribute__" => "Return getattr(self, name).", + "builtins.generator.__getstate__" => "Helper for pickle.", + "builtins.generator.__gt__" => "Return self>value.", + "builtins.generator.__hash__" => "Return hash(self).", + "builtins.generator.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.generator.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.generator.__iter__" => "Implement iter(self).", + "builtins.generator.__le__" => "Return self<=value.", + "builtins.generator.__lt__" => "Return self<value.", + "builtins.generator.__ne__" => "Return self!=value.", + "builtins.generator.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.generator.__next__" => "Implement next(self).", + "builtins.generator.__reduce__" => "Helper for pickle.", + "builtins.generator.__reduce_ex__" => "Helper for pickle.", + "builtins.generator.__repr__" => "Return repr(self).", + "builtins.generator.__setattr__" => "Implement setattr(self, name, value).", + "builtins.generator.__sizeof__" => "gen.__sizeof__() -> size of gen in memory, in bytes", + "builtins.generator.__str__" => "Return str(self).", + "builtins.generator.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.generator.close" => "close() -> raise GeneratorExit inside generator.", + "builtins.generator.gi_yieldfrom" => "object being iterated by yield from, or None", + "builtins.generator.send" => "send(value) -> send 'value' into generator,\nreturn next yielded value or raise StopIteration.", + "builtins.generator.throw" => "throw(value)\nthrow(type[,value[,tb]])\n\nRaise exception in generator, return next yielded value or raise\nStopIteration.\nthe (type, val, tb) signature is deprecated, \nand may be removed in a future version of Python.", "builtins.getattr" => "getattr(object, name[, default]) -> value\n\nGet a named attribute from an object; getattr(x, 'y') is equivalent to x.y.\nWhen a default argument is given, it is returned when the attribute doesn't\nexist; without it, an exception is raised in that case.", + "builtins.getset_descriptor.__delattr__" => "Implement delattr(self, name).", + "builtins.getset_descriptor.__delete__" => "Delete an attribute of instance.", + "builtins.getset_descriptor.__eq__" => "Return self==value.", + "builtins.getset_descriptor.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.getset_descriptor.__ge__" => "Return self>=value.", + "builtins.getset_descriptor.__get__" => "Return an attribute of instance, which is of type owner.", + "builtins.getset_descriptor.__getattribute__" => "Return getattr(self, name).", + "builtins.getset_descriptor.__getstate__" => "Helper for pickle.", + "builtins.getset_descriptor.__gt__" => "Return self>value.", + "builtins.getset_descriptor.__hash__" => "Return hash(self).", + "builtins.getset_descriptor.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.getset_descriptor.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.getset_descriptor.__le__" => "Return self<=value.", + "builtins.getset_descriptor.__lt__" => "Return self<value.", + "builtins.getset_descriptor.__ne__" => "Return self!=value.", + "builtins.getset_descriptor.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.getset_descriptor.__reduce__" => "Helper for pickle.", + "builtins.getset_descriptor.__reduce_ex__" => "Helper for pickle.", + "builtins.getset_descriptor.__repr__" => "Return repr(self).", + "builtins.getset_descriptor.__set__" => "Set an attribute of instance to value.", + "builtins.getset_descriptor.__setattr__" => "Implement setattr(self, name, value).", + "builtins.getset_descriptor.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.getset_descriptor.__str__" => "Return str(self).", + "builtins.getset_descriptor.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.globals" => "Return the dictionary containing the current scope's global variables.\n\nNOTE: Updates to this dictionary *will* affect name lookups in the current\nglobal scope and vice-versa.", "builtins.hasattr" => "Return whether the object has an attribute with the given name.\n\nThis is done by calling getattr(obj, name) and catching AttributeError.", "builtins.hash" => "Return the hash value for the given object.\n\nTwo objects that compare equal must also have the same hash value, but the\nreverse is not necessarily true.", @@ -6508,7 +6869,66 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.map.__sizeof__" => "Size of object in memory, in bytes.", "builtins.map.__str__" => "Return str(self).", "builtins.map.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.mappingproxy" => "Read-only proxy of a mapping.", + "builtins.mappingproxy.__class_getitem__" => "See PEP 585", + "builtins.mappingproxy.__contains__" => "Return bool(key in self).", + "builtins.mappingproxy.__delattr__" => "Implement delattr(self, name).", + "builtins.mappingproxy.__eq__" => "Return self==value.", + "builtins.mappingproxy.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.mappingproxy.__ge__" => "Return self>=value.", + "builtins.mappingproxy.__getattribute__" => "Return getattr(self, name).", + "builtins.mappingproxy.__getitem__" => "Return self[key].", + "builtins.mappingproxy.__getstate__" => "Helper for pickle.", + "builtins.mappingproxy.__gt__" => "Return self>value.", + "builtins.mappingproxy.__hash__" => "Return hash(self).", + "builtins.mappingproxy.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.mappingproxy.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.mappingproxy.__ior__" => "Return self|=value.", + "builtins.mappingproxy.__iter__" => "Implement iter(self).", + "builtins.mappingproxy.__le__" => "Return self<=value.", + "builtins.mappingproxy.__len__" => "Return len(self).", + "builtins.mappingproxy.__lt__" => "Return self<value.", + "builtins.mappingproxy.__ne__" => "Return self!=value.", + "builtins.mappingproxy.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.mappingproxy.__or__" => "Return self|value.", + "builtins.mappingproxy.__reduce__" => "Helper for pickle.", + "builtins.mappingproxy.__reduce_ex__" => "Helper for pickle.", + "builtins.mappingproxy.__repr__" => "Return repr(self).", + "builtins.mappingproxy.__reversed__" => "D.__reversed__() -> reverse iterator", + "builtins.mappingproxy.__ror__" => "Return value|self.", + "builtins.mappingproxy.__setattr__" => "Implement setattr(self, name, value).", + "builtins.mappingproxy.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.mappingproxy.__str__" => "Return str(self).", + "builtins.mappingproxy.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.mappingproxy.copy" => "D.copy() -> a shallow copy of D", + "builtins.mappingproxy.get" => "Return the value for key if key is in the mapping, else default.", + "builtins.mappingproxy.items" => "D.items() -> a set-like object providing a view on D's items", + "builtins.mappingproxy.keys" => "D.keys() -> a set-like object providing a view on D's keys", + "builtins.mappingproxy.values" => "D.values() -> an object providing a view on D's values", "builtins.max" => "max(iterable, *[, default=obj, key=func]) -> value\nmax(arg1, arg2, *args, *[, key=func]) -> value\n\nWith a single iterable argument, return its biggest item. The\ndefault keyword-only argument specifies an object to return if\nthe provided iterable is empty.\nWith two or more positional arguments, return the largest argument.", + "builtins.member_descriptor.__delattr__" => "Implement delattr(self, name).", + "builtins.member_descriptor.__delete__" => "Delete an attribute of instance.", + "builtins.member_descriptor.__eq__" => "Return self==value.", + "builtins.member_descriptor.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.member_descriptor.__ge__" => "Return self>=value.", + "builtins.member_descriptor.__get__" => "Return an attribute of instance, which is of type owner.", + "builtins.member_descriptor.__getattribute__" => "Return getattr(self, name).", + "builtins.member_descriptor.__getstate__" => "Helper for pickle.", + "builtins.member_descriptor.__gt__" => "Return self>value.", + "builtins.member_descriptor.__hash__" => "Return hash(self).", + "builtins.member_descriptor.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.member_descriptor.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.member_descriptor.__le__" => "Return self<=value.", + "builtins.member_descriptor.__lt__" => "Return self<value.", + "builtins.member_descriptor.__ne__" => "Return self!=value.", + "builtins.member_descriptor.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.member_descriptor.__reduce_ex__" => "Helper for pickle.", + "builtins.member_descriptor.__repr__" => "Return repr(self).", + "builtins.member_descriptor.__set__" => "Set an attribute of instance to value.", + "builtins.member_descriptor.__setattr__" => "Implement setattr(self, name, value).", + "builtins.member_descriptor.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.member_descriptor.__str__" => "Return str(self).", + "builtins.member_descriptor.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.memory_iterator.__delattr__" => "Implement delattr(self, name).", "builtins.memory_iterator.__eq__" => "Return self==value.", "builtins.memory_iterator.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", @@ -6581,7 +7001,97 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.memoryview.tobytes" => "Return the data in the buffer as a byte string.\n\nOrder can be {'C', 'F', 'A'}. When order is 'C' or 'F', the data of the\noriginal array is converted to C or Fortran order. For contiguous views,\n'A' returns an exact copy of the physical memory. In particular, in-memory\nFortran order is preserved. For non-contiguous views, the data is converted\nto C first. order=None is the same as order='C'.", "builtins.memoryview.tolist" => "Return the data in the buffer as a list of elements.", "builtins.memoryview.toreadonly" => "Return a readonly version of the memoryview.", + "builtins.method" => "Create a bound instance method object.", + "builtins.method-wrapper.__call__" => "Call self as a function.", + "builtins.method-wrapper.__delattr__" => "Implement delattr(self, name).", + "builtins.method-wrapper.__eq__" => "Return self==value.", + "builtins.method-wrapper.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.method-wrapper.__ge__" => "Return self>=value.", + "builtins.method-wrapper.__getattribute__" => "Return getattr(self, name).", + "builtins.method-wrapper.__getstate__" => "Helper for pickle.", + "builtins.method-wrapper.__gt__" => "Return self>value.", + "builtins.method-wrapper.__hash__" => "Return hash(self).", + "builtins.method-wrapper.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.method-wrapper.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.method-wrapper.__le__" => "Return self<=value.", + "builtins.method-wrapper.__lt__" => "Return self<value.", + "builtins.method-wrapper.__ne__" => "Return self!=value.", + "builtins.method-wrapper.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.method-wrapper.__reduce_ex__" => "Helper for pickle.", + "builtins.method-wrapper.__repr__" => "Return repr(self).", + "builtins.method-wrapper.__setattr__" => "Implement setattr(self, name, value).", + "builtins.method-wrapper.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.method-wrapper.__str__" => "Return str(self).", + "builtins.method-wrapper.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.method.__call__" => "Call self as a function.", + "builtins.method.__delattr__" => "Implement delattr(self, name).", + "builtins.method.__eq__" => "Return self==value.", + "builtins.method.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.method.__func__" => "the function (or other callable) implementing a method", + "builtins.method.__ge__" => "Return self>=value.", + "builtins.method.__get__" => "Return an attribute of instance, which is of type owner.", + "builtins.method.__getattribute__" => "Return getattr(self, name).", + "builtins.method.__getstate__" => "Helper for pickle.", + "builtins.method.__gt__" => "Return self>value.", + "builtins.method.__hash__" => "Return hash(self).", + "builtins.method.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.method.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.method.__le__" => "Return self<=value.", + "builtins.method.__lt__" => "Return self<value.", + "builtins.method.__ne__" => "Return self!=value.", + "builtins.method.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.method.__reduce_ex__" => "Helper for pickle.", + "builtins.method.__repr__" => "Return repr(self).", + "builtins.method.__self__" => "the instance to which a method is bound", + "builtins.method.__setattr__" => "Implement setattr(self, name, value).", + "builtins.method.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.method.__str__" => "Return str(self).", + "builtins.method.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + "builtins.method_descriptor.__call__" => "Call self as a function.", + "builtins.method_descriptor.__delattr__" => "Implement delattr(self, name).", + "builtins.method_descriptor.__eq__" => "Return self==value.", + "builtins.method_descriptor.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.method_descriptor.__ge__" => "Return self>=value.", + "builtins.method_descriptor.__get__" => "Return an attribute of instance, which is of type owner.", + "builtins.method_descriptor.__getattribute__" => "Return getattr(self, name).", + "builtins.method_descriptor.__getstate__" => "Helper for pickle.", + "builtins.method_descriptor.__gt__" => "Return self>value.", + "builtins.method_descriptor.__hash__" => "Return hash(self).", + "builtins.method_descriptor.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.method_descriptor.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.method_descriptor.__le__" => "Return self<=value.", + "builtins.method_descriptor.__lt__" => "Return self<value.", + "builtins.method_descriptor.__ne__" => "Return self!=value.", + "builtins.method_descriptor.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.method_descriptor.__reduce_ex__" => "Helper for pickle.", + "builtins.method_descriptor.__repr__" => "Return repr(self).", + "builtins.method_descriptor.__setattr__" => "Implement setattr(self, name, value).", + "builtins.method_descriptor.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.method_descriptor.__str__" => "Return str(self).", + "builtins.method_descriptor.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.min" => "min(iterable, *[, default=obj, key=func]) -> value\nmin(arg1, arg2, *args, *[, key=func]) -> value\n\nWith a single iterable argument, return its smallest item. The\ndefault keyword-only argument specifies an object to return if\nthe provided iterable is empty.\nWith two or more positional arguments, return the smallest argument.", + "builtins.module" => "Create a module object.\n\nThe name must be a string; the optional doc argument can have any type.", + "builtins.module.__delattr__" => "Implement delattr(self, name).", + "builtins.module.__eq__" => "Return self==value.", + "builtins.module.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.module.__ge__" => "Return self>=value.", + "builtins.module.__getattribute__" => "Return getattr(self, name).", + "builtins.module.__getstate__" => "Helper for pickle.", + "builtins.module.__gt__" => "Return self>value.", + "builtins.module.__hash__" => "Return hash(self).", + "builtins.module.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.module.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.module.__le__" => "Return self<=value.", + "builtins.module.__lt__" => "Return self<value.", + "builtins.module.__ne__" => "Return self!=value.", + "builtins.module.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.module.__reduce__" => "Helper for pickle.", + "builtins.module.__reduce_ex__" => "Helper for pickle.", + "builtins.module.__repr__" => "Return repr(self).", + "builtins.module.__setattr__" => "Implement setattr(self, name, value).", + "builtins.module.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.module.__str__" => "Return str(self).", + "builtins.module.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.next" => "next(iterator[, default])\n\nReturn the next item from the iterator. If default is given and the iterator\nis exhausted, it is returned instead of raising StopIteration.", "builtins.object" => "The base class of the class hierarchy.\n\nWhen called, it accepts no arguments and returns a new featureless\ninstance that has no instance attributes and cannot be given any.", "builtins.object.__delattr__" => "Implement delattr(self, name).", @@ -6977,6 +7487,28 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.super.__str__" => "Return str(self).", "builtins.super.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.super.__thisclass__" => "the class invoking super()", + "builtins.traceback" => "Create a new traceback object.", + "builtins.traceback.__delattr__" => "Implement delattr(self, name).", + "builtins.traceback.__eq__" => "Return self==value.", + "builtins.traceback.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.traceback.__ge__" => "Return self>=value.", + "builtins.traceback.__getattribute__" => "Return getattr(self, name).", + "builtins.traceback.__getstate__" => "Helper for pickle.", + "builtins.traceback.__gt__" => "Return self>value.", + "builtins.traceback.__hash__" => "Return hash(self).", + "builtins.traceback.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.traceback.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.traceback.__le__" => "Return self<=value.", + "builtins.traceback.__lt__" => "Return self<value.", + "builtins.traceback.__ne__" => "Return self!=value.", + "builtins.traceback.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.traceback.__reduce__" => "Helper for pickle.", + "builtins.traceback.__reduce_ex__" => "Helper for pickle.", + "builtins.traceback.__repr__" => "Return repr(self).", + "builtins.traceback.__setattr__" => "Implement setattr(self, name, value).", + "builtins.traceback.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.traceback.__str__" => "Return str(self).", + "builtins.traceback.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.tuple" => "Built-in immutable sequence.\n\nIf no argument is given, the constructor returns an empty tuple.\nIf iterable is specified the tuple is initialized from iterable's items.\n\nIf the argument is a tuple, the return value is the same object.", "builtins.tuple.__add__" => "Return self+value.", "builtins.tuple.__class_getitem__" => "See PEP 585", @@ -7087,6 +7619,28 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "builtins.type.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.type.mro" => "Return a type's method resolution order.", "builtins.vars" => "vars([object]) -> dictionary\n\nWithout arguments, equivalent to locals().\nWith an argument, equivalent to object.__dict__.", + "builtins.wrapper_descriptor.__call__" => "Call self as a function.", + "builtins.wrapper_descriptor.__delattr__" => "Implement delattr(self, name).", + "builtins.wrapper_descriptor.__eq__" => "Return self==value.", + "builtins.wrapper_descriptor.__format__" => "Default object formatter.\n\nReturn str(self) if format_spec is empty. Raise TypeError otherwise.", + "builtins.wrapper_descriptor.__ge__" => "Return self>=value.", + "builtins.wrapper_descriptor.__get__" => "Return an attribute of instance, which is of type owner.", + "builtins.wrapper_descriptor.__getattribute__" => "Return getattr(self, name).", + "builtins.wrapper_descriptor.__getstate__" => "Helper for pickle.", + "builtins.wrapper_descriptor.__gt__" => "Return self>value.", + "builtins.wrapper_descriptor.__hash__" => "Return hash(self).", + "builtins.wrapper_descriptor.__init__" => "Initialize self. See help(type(self)) for accurate signature.", + "builtins.wrapper_descriptor.__init_subclass__" => "This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + "builtins.wrapper_descriptor.__le__" => "Return self<=value.", + "builtins.wrapper_descriptor.__lt__" => "Return self<value.", + "builtins.wrapper_descriptor.__ne__" => "Return self!=value.", + "builtins.wrapper_descriptor.__new__" => "Create and return a new object. See help(type) for accurate signature.", + "builtins.wrapper_descriptor.__reduce_ex__" => "Helper for pickle.", + "builtins.wrapper_descriptor.__repr__" => "Return repr(self).", + "builtins.wrapper_descriptor.__setattr__" => "Implement setattr(self, name, value).", + "builtins.wrapper_descriptor.__sizeof__" => "Size of object in memory, in bytes.", + "builtins.wrapper_descriptor.__str__" => "Return str(self).", + "builtins.wrapper_descriptor.__subclasshook__" => "Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", "builtins.zip" => "The zip object yields n-length tuples, where n is the number of iterables\npassed as positional arguments to zip(). The i-th element in every tuple\ncomes from the i-th iterable argument to zip(). This continues until the\nshortest argument is exhausted.\n\nIf strict is true and one of the arguments is exhausted before the others,\nraise a ValueError.\n\n >>> list(zip('abcdefg', range(3), range(4)))\n [('a', 0, 0), ('b', 1, 1), ('c', 2, 2)]", "builtins.zip.__delattr__" => "Implement delattr(self, name).", "builtins.zip.__eq__" => "Return self==value.", From 98fff96f1c911d59fbcb9239b132c6139149a581 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 10 Dec 2025 20:04:09 +0900 Subject: [PATCH 463/819] __doc__ handing in the right way (#6390) --- Lib/test/test_property.py | 1 - Lib/test/test_typing.py | 4 ++-- crates/codegen/src/compile.rs | 21 ++++++--------------- crates/vm/src/builtins/type.rs | 24 +++++++++++++++++------- extra_tests/snippets/syntax_class.py | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 49dc2331e94..cea241b0f20 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -342,7 +342,6 @@ def documented_getter(): with self.assertRaises(AttributeError): p = slotted_prop(documented_getter) - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_property_with_slots_and_doc_slot_docstring_present(self): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 3f268c62384..4dd3cd8b78a 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -5269,7 +5269,7 @@ def test_weakref_all(self): for t in things: self.assertEqual(weakref.ref(t)(), t) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON - __slots__ with Generic doesn't prevent new attributes def test_parameterized_slots(self): T = TypeVar('T') class C(Generic[T]): @@ -5289,7 +5289,7 @@ def foo(x: C['C']): ... self.assertEqual(get_type_hints(foo, globals(), locals())['x'], C[C]) self.assertEqual(copy(C[int]), deepcopy(C[int])) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON - __slots__ with Generic doesn't prevent new attributes def test_parameterized_slots_dict(self): T = TypeVar('T') class D(Generic[T]): diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 5e2c7b81b33..3e7640cc1e0 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -2679,10 +2679,12 @@ impl Compiler { let qualname_name = self.name("__qualname__"); emit!(self, Instruction::StoreLocal(qualname_name)); - // Store __doc__ - self.load_docstring(doc_str); - let doc = self.name("__doc__"); - emit!(self, Instruction::StoreLocal(doc)); + // Store __doc__ only if there's an explicit docstring + if let Some(doc) = doc_str { + self.emit_load_const(ConstantData::Str { value: doc.into() }); + let doc_name = self.name("__doc__"); + emit!(self, Instruction::StoreLocal(doc_name)); + } // Store __firstlineno__ (new in Python 3.12+) self.emit_load_const(ConstantData::Integer { @@ -2876,17 +2878,6 @@ impl Compiler { self.store_name(name) } - fn load_docstring(&mut self, doc_str: Option<String>) { - // TODO: __doc__ must be default None and no bytecode unless it is Some - // Duplicate top of stack (the function or class object) - - // Doc string value: - self.emit_load_const(match doc_str { - Some(doc) => ConstantData::Str { value: doc.into() }, - None => ConstantData::None, // set docstring None if not declared - }); - } - fn compile_while(&mut self, test: &Expr, body: &[Stmt], orelse: &[Stmt]) -> CompileResult<()> { let while_block = self.new_block(); let else_block = self.new_block(); diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 849c03bc78d..883be4f8bfc 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1198,11 +1198,10 @@ impl Constructor for PyType { member: member_def, }); - let attr_name = vm.ctx.intern_str(member.to_string()); - if !typ.has_attr(attr_name) { - typ.set_attr(attr_name, member_descriptor.into()); - } - + let attr_name = vm.ctx.intern_str(member.as_str()); + // __slots__ attributes always get a member descriptor + // (this overrides any inherited attribute from MRO) + typ.set_attr(attr_name, member_descriptor.into()); offset += 1; } } @@ -1236,6 +1235,16 @@ impl Constructor for PyType { } } + // Set __doc__ to None if not already present in the type's dict + // This matches CPython's behavior in type_dict_set_doc (typeobject.c) + // which ensures every type has a __doc__ entry in its dict + { + let __doc__ = identifier!(vm, __doc__); + if !typ.attributes.read().contains_key(&__doc__) { + typ.attributes.write().insert(__doc__, vm.ctx.none()); + } + } + // avoid deadlock let attributes = typ .attributes @@ -1377,8 +1386,9 @@ impl Py<PyType> { return Ok(vm.ctx.new_str(doc_str).into()); } - // Check if there's a __doc__ in the type's dict - if let Some(doc_attr) = self.get_attr(vm.ctx.intern_str("__doc__")) { + // Check if there's a __doc__ in THIS type's dict only (not MRO) + // CPython returns None if __doc__ is not in the type's own dict + if let Some(doc_attr) = self.get_direct_attr(vm.ctx.intern_str("__doc__")) { // If it's a descriptor, call its __get__ method let descr_get = doc_attr .class() diff --git a/extra_tests/snippets/syntax_class.py b/extra_tests/snippets/syntax_class.py index fb702f33044..4e80e7edf8c 100644 --- a/extra_tests/snippets/syntax_class.py +++ b/extra_tests/snippets/syntax_class.py @@ -186,7 +186,7 @@ def b(): class A: pass -assert A.__doc__ == None +assert A.__doc__ is None, A.__doc__ class B: "Docstring" From 1b17587585346e22b8d5ef73c4d1b1e13b50f172 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 10 Dec 2025 23:30:10 +0900 Subject: [PATCH 464/819] __slots__ xor __dict__ , name mangling (#6392) * __slots__ xor __dict__ * mangle_name for `__` prefixed members --- Lib/test/datetimetester.py | 2 -- Lib/test/test_contextlib.py | 2 -- Lib/test/test_contextlib_async.py | 2 -- Lib/test/test_dataclasses.py | 4 ---- Lib/test/test_decimal.py | 4 ---- Lib/test/test_fractions.py | 2 -- Lib/test/test_functools.py | 1 - Lib/test/test_os.py | 1 - Lib/test/test_statistics.py | 2 -- Lib/test/test_typing.py | 2 -- crates/vm/src/builtins/object.rs | 12 ++++++++--- crates/vm/src/builtins/type.rs | 34 +++++++++++++++++++++++++------ crates/vm/src/stdlib/typevar.rs | 7 ++++++- 13 files changed, 43 insertions(+), 32 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 018b0c4d03a..2636c1fbb98 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -5455,8 +5455,6 @@ def test_bug_1028306(self): self.assertEqual(as_datetime, datetime_sc) self.assertEqual(datetime_sc, as_datetime) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_extra_attributes(self): with self.assertWarns(DeprecationWarning): utcnow = datetime.utcnow() diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index 0982a21b2fc..9bb3fd0179b 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -24,8 +24,6 @@ def __exit__(self, *args): manager = DefaultEnter() self.assertIs(manager.__enter__(), manager) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_slots(self): class DefaultContextManager(AbstractContextManager): __slots__ = () diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index f7edcfe55da..d7331c4d433 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -27,8 +27,6 @@ async def __aexit__(self, *args): async with manager as context: self.assertIs(manager, context) - # TODO: RUSTPYTHON - @unittest.expectedFailure async def test_slots(self): class DefaultAsyncContextManager(AbstractAsyncContextManager): __slots__ = () diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index 46430d32310..f11edd957c4 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -2776,8 +2776,6 @@ class C: class TestSlots(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_simple(self): @dataclass class C: @@ -2819,8 +2817,6 @@ class Derived(Base): # We can add a new field to the derived instance. d.z = 10 - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_generated_slots(self): @dataclass(slots=True) class C: diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 163ca92bb42..01b0c06196c 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -2923,10 +2923,6 @@ class CPythonAPItests(PythonAPItests, unittest.TestCase): class PyPythonAPItests(PythonAPItests, unittest.TestCase): decimal = P - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_complex(self): # TODO(RUSTPYTHON): Remove this test when it pass - return super().test_complex() class ContextAPItests: diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 41f93390b56..5c74e36a182 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1137,8 +1137,6 @@ def test_copy_deepcopy_pickle(self): self.assertTypedEquals(dr, copy(dr)) self.assertTypedEquals(dr, deepcopy(dr)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_slots(self): # Issue 4998 r = F(13, 7) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 8050c4c8973..6de5d14bf73 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -3278,7 +3278,6 @@ def test_cached_attribute_name_differs_from_func_name(self): self.assertEqual(item.get_cost(), 4) self.assertEqual(item.cached_cost, 3) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_object_with_slots(self): item = CachedCostItemWithSlots() with self.assertRaisesRegex( diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 4fefc9d88ab..6fce916c7d9 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -5332,7 +5332,6 @@ class A(os.PathLike): def test_pathlike_class_getitem(self): self.assertIsInstance(os.PathLike[bytes], types.GenericAlias) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_pathlike_subclass_slots(self): class A(os.PathLike): __slots__ = () diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index 22bd17908a7..9c2714e99d4 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -2889,8 +2889,6 @@ class TestNormalDist: # inaccurate. There isn't much we can do about this short of # implementing our own implementations from scratch. - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_slots(self): nd = self.module.NormalDist(300, 23) with self.assertRaises(TypeError): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 4dd3cd8b78a..db0dc916f1a 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -5269,7 +5269,6 @@ def test_weakref_all(self): for t in things: self.assertEqual(weakref.ref(t)(), t) - @unittest.expectedFailure # TODO: RUSTPYTHON - __slots__ with Generic doesn't prevent new attributes def test_parameterized_slots(self): T = TypeVar('T') class C(Generic[T]): @@ -5289,7 +5288,6 @@ def foo(x: C['C']): ... self.assertEqual(get_type_hints(foo, globals(), locals())['x'], C[C]) self.assertEqual(copy(C[int]), deepcopy(C[int])) - @unittest.expectedFailure # TODO: RUSTPYTHON - __slots__ with Generic doesn't prevent new attributes def test_parameterized_slots_dict(self): T = TypeVar('T') class D(Generic[T]): diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 810e3c25402..94a1e0c9ad9 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -66,10 +66,16 @@ impl Constructor for PyBaseObject { } // more or less __new__ operator - let dict = if cls.is(vm.ctx.types.object_type) { - None - } else { + // Only create dict if the class has HAS_DICT flag (i.e., __slots__ was not defined + // or __dict__ is in __slots__) + let dict = if cls + .slots + .flags + .has_feature(crate::types::PyTypeFlags::HAS_DICT) + { Some(vm.ctx.new_dict()) + } else { + None }; // Ensure that all abstract methods are implemented before instantiating instance. diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 883be4f8bfc..b9c996371a0 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -307,7 +307,12 @@ impl PyType { ) -> Result<PyRef<Self>, String> { let mro = Self::resolve_mro(&bases)?; - if base.slots.flags.has_feature(PyTypeFlags::HAS_DICT) { + // Inherit HAS_DICT from any base in MRO that has it + // (not just the first base, as any base with __dict__ means subclass needs it too) + if mro + .iter() + .any(|b| b.slots.flags.has_feature(PyTypeFlags::HAS_DICT)) + { slots.flags |= PyTypeFlags::HAS_DICT } @@ -1180,25 +1185,27 @@ impl Constructor for PyType { if let Some(ref slots) = heaptype_slots { let mut offset = base_member_count; + let class_name = typ.name().to_string(); for member in slots.as_slice() { + // Apply name mangling for private attributes (__x -> _ClassName__x) + let mangled_name = mangle_name(&class_name, member.as_str()); let member_def = PyMemberDef { - name: member.to_string(), + name: mangled_name.clone(), kind: MemberKind::ObjectEx, getter: MemberGetter::Offset(offset), setter: MemberSetter::Offset(offset), doc: None, }; + let attr_name = vm.ctx.intern_str(mangled_name.as_str()); let member_descriptor: PyRef<PyMemberDescriptor> = vm.ctx.new_pyref(PyMemberDescriptor { common: PyDescriptorOwned { typ: typ.clone(), - name: vm.ctx.intern_str(member.as_str()), + name: attr_name, qualname: PyRwLock::new(None), }, member: member_def, }); - - let attr_name = vm.ctx.intern_str(member.as_str()); // __slots__ attributes always get a member descriptor // (this overrides any inherited attribute from MRO) typ.set_attr(attr_name, member_descriptor.into()); @@ -1223,7 +1230,10 @@ impl Constructor for PyType { // since they inherit it from type // Add __dict__ descriptor after type creation to ensure correct __objclass__ - if !base_is_type { + // Only add if: + // 1. base is not type (type subclasses inherit __dict__ from type) + // 2. the class has HAS_DICT flag (i.e., __slots__ was not defined or __dict__ is in __slots__) + if !base_is_type && typ.slots.flags.has_feature(PyTypeFlags::HAS_DICT) { let __dict__ = identifier!(vm, __dict__); if !typ.attributes.read().contains_key(&__dict__) { unsafe { @@ -1785,6 +1795,18 @@ fn best_base<'a>(bases: &'a [PyTypeRef], vm: &VirtualMachine) -> PyResult<&'a Py Ok(base.unwrap()) } +/// Apply Python name mangling for private attributes. +/// `__x` becomes `_ClassName__x` if inside a class. +fn mangle_name(class_name: &str, name: &str) -> String { + // Only mangle names starting with __ and not ending with __ + if !name.starts_with("__") || name.ends_with("__") || name.contains('.') { + return name.to_string(); + } + // Strip leading underscores from class name + let class_name = class_name.trim_start_matches('_'); + format!("_{}{}", class_name, name) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/vm/src/stdlib/typevar.rs b/crates/vm/src/stdlib/typevar.rs index 11c20ba7878..4d56fb3ce3c 100644 --- a/crates/vm/src/stdlib/typevar.rs +++ b/crates/vm/src/stdlib/typevar.rs @@ -1,6 +1,6 @@ // spell-checker:ignore typevarobject funcobj use crate::{ - AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + AsObject, Context, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr}, common::lock::PyMutex, function::{FuncArgs, IntoFuncArgs, PyComparisonValue}, @@ -972,6 +972,11 @@ pub struct Generic {} #[pyclass(flags(BASETYPE))] impl Generic { + #[pyattr] + fn __slots__(ctx: &Context) -> PyTupleRef { + ctx.empty_tuple.clone() + } + #[pyclassmethod] fn __class_getitem__(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { call_typing_args_kwargs("_generic_class_getitem", cls, args, vm) From 2055145a7e7e2e2b03273519df5e40e98dbf770e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 10 Dec 2025 23:31:38 +0900 Subject: [PATCH 465/819] validate_downcast (#6393) --- crates/vm/src/builtins/builtin_func.rs | 8 +------- crates/vm/src/builtins/str.rs | 10 ++++------ crates/vm/src/object/payload.rs | 7 ++++++- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/crates/vm/src/builtins/builtin_func.rs b/crates/vm/src/builtins/builtin_func.rs index 8c160c2eb4b..d1ce107e374 100644 --- a/crates/vm/src/builtins/builtin_func.rs +++ b/crates/vm/src/builtins/builtin_func.rs @@ -148,7 +148,7 @@ impl Representable for PyNativeFunction { impl Unconstructible for PyNativeFunction {} // `PyCMethodObject` in CPython -#[pyclass(name = "builtin_method", module = false, base = PyNativeFunction)] +#[pyclass(name = "builtin_method", module = false, base = PyNativeFunction, ctx = "builtin_method_type")] pub struct PyNativeMethod { pub(crate) func: PyNativeFunction, pub(crate) class: &'static Py<PyType>, // TODO: the actual life is &'self @@ -189,12 +189,6 @@ impl PyNativeMethod { } } -impl PyPayload for PyNativeMethod { - fn class(ctx: &Context) -> &'static Py<PyType> { - ctx.types.builtin_method_type - } -} - impl fmt::Debug for PyNativeMethod { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 80da626f318..9fa1472c26e 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -1932,12 +1932,10 @@ impl PyPayload for PyUtf8Str { std::any::TypeId::of::<PyStr>() } - fn downcastable_from(obj: &PyObject) -> bool { - obj.typeid() == Self::payload_type_id() && { - // SAFETY: we know the object is a PyStr in this context - let wtf8 = unsafe { obj.downcast_unchecked_ref::<PyStr>() }; - wtf8.is_utf8() - } + fn validate_downcastable_from(obj: &PyObject) -> bool { + // SAFETY: we know the object is a PyStr in this context + let wtf8 = unsafe { obj.downcast_unchecked_ref::<PyStr>() }; + wtf8.is_utf8() } fn try_downcast_from(obj: &PyObject, vm: &VirtualMachine) -> PyResult<()> { diff --git a/crates/vm/src/object/payload.rs b/crates/vm/src/object/payload.rs index 7fc40ddba90..b2211761492 100644 --- a/crates/vm/src/object/payload.rs +++ b/crates/vm/src/object/payload.rs @@ -25,7 +25,12 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { /// # Safety: this function should only be called if `payload_type_id` matches the type of `obj`. #[inline] fn downcastable_from(obj: &PyObject) -> bool { - obj.typeid() == Self::payload_type_id() + obj.typeid() == Self::payload_type_id() && Self::validate_downcastable_from(obj) + } + + #[inline] + fn validate_downcastable_from(_obj: &PyObject) -> bool { + true } fn try_downcast_from(obj: &PyObject, vm: &VirtualMachine) -> PyResult<()> { From 81c9b05f979734dcb682a90a4506683f83761cd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 08:34:06 +0900 Subject: [PATCH 466/819] Bump libc from 0.2.177 to 0.2.178 (#6395) Bumps [libc](https://github.com/rust-lang/libc) from 0.2.177 to 0.2.178. - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/0.2.178/CHANGELOG.md) - [Commits](https://github.com/rust-lang/libc/compare/0.2.177...0.2.178) --- updated-dependencies: - dependency-name: libc dependency-version: 0.2.178 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1de5f6751ab..69f880aad45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1680,9 +1680,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libffi" diff --git a/Cargo.toml b/Cargo.toml index f00f8c27f82..4cd45114444 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,7 +177,7 @@ insta = "1.42" itertools = "0.14.0" is-macro = "0.3.7" junction = "1.3.0" -libc = "0.2.177" +libc = "0.2.178" libffi = "4.1" log = "0.4.28" nix = { version = "0.30", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] } From 3a7a5205fb1442c356054ca338b8f1dab297f224 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 08:34:20 +0900 Subject: [PATCH 467/819] Bump log from 0.4.28 to 0.4.29 (#6396) Bumps [log](https://github.com/rust-lang/log) from 0.4.28 to 0.4.29. - [Release notes](https://github.com/rust-lang/log/releases) - [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/log/compare/0.4.28...0.4.29) --- updated-dependencies: - dependency-name: log dependency-version: 0.4.29 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69f880aad45..7760da3801d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1776,9 +1776,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lz4_flex" diff --git a/Cargo.toml b/Cargo.toml index 4cd45114444..53742c58f1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,7 +179,7 @@ is-macro = "0.3.7" junction = "1.3.0" libc = "0.2.178" libffi = "4.1" -log = "0.4.28" +log = "0.4.29" nix = { version = "0.30", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] } malachite-bigint = "0.8" malachite-q = "0.8" From 20d9099bf3757fc0985ac1c12f3c5178d141e86d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 08:34:32 +0900 Subject: [PATCH 468/819] Bump insta from 1.44.2 to 1.44.3 (#6394) Bumps [insta](https://github.com/mitsuhiko/insta) from 1.44.2 to 1.44.3. - [Release notes](https://github.com/mitsuhiko/insta/releases) - [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md) - [Commits](https://github.com/mitsuhiko/insta/compare/1.44.2...1.44.3) --- updated-dependencies: - dependency-name: insta dependency-version: 1.44.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7760da3801d..2873f3a529f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,7 +1181,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1487,9 +1487,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.44.2" +version = "1.44.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfd3461e1f00283105bdf97c3a1aca2b3f8456eb809a96938d2b190cd4dbc6d2" +checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698" dependencies = [ "console", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 53742c58f1b..f68e6e68157 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,7 +173,7 @@ getrandom = { version = "0.3", features = ["std"] } glob = "0.3" hex = "0.4.3" indexmap = { version = "2.11.3", features = ["std"] } -insta = "1.42" +insta = "1.44" itertools = "0.14.0" is-macro = "0.3.7" junction = "1.3.0" From 4828fb3ba6a6bb475fed85d43dec68b5fb940acf Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:11:29 +0900 Subject: [PATCH 469/819] test_os, test_io on windows (#6379) * Allow hidden env vars on nt * Enable test_os on windows * enable test_io on windows --- .github/workflows/ci.yaml | 4 - Lib/test/test_ntpath.py | 16 --- Lib/test/test_unittest/testmock/testpatch.py | 2 - crates/vm/src/stdlib/nt.rs | 17 ++- crates/vm/src/stdlib/os.rs | 106 ++++++++++++++----- 5 files changed, 94 insertions(+), 51 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 27a70ced156..a91fd175ff8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,15 +20,11 @@ env: CARGO_ARGS_NO_SSL: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite # Skip additional tests on Windows. They are checked on Linux and MacOS. # test_glob: many failing tests - # test_io: many failing tests - # test_os: many failing tests # test_pathlib: panic by surrogate chars # test_posixpath: OSError: (22, 'The filename, directory name, or volume label syntax is incorrect. (os error 123)') # test_venv: couple of failing tests WINDOWS_SKIPS: >- test_glob - test_io - test_os test_rlcompleter test_pathlib test_posixpath diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 023be1a9656..86c34a43f10 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -990,8 +990,6 @@ def test_realpath_permission(self): self.assertPathEqual(test_file, ntpath.realpath(test_file_short)) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ValueError: illegal environment variable name") def test_expandvars(self): with os_helper.EnvironmentVarGuard() as env: env.clear() @@ -1018,8 +1016,6 @@ def test_expandvars(self): tester('ntpath.expandvars("\'%foo%\'%bar")', "\'%foo%\'%bar") tester('ntpath.expandvars("bar\'%foo%")', "bar\'%foo%") - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ValueError: illegal environment variable name") @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') def test_expandvars_nonascii(self): def check(value, expected): @@ -1040,8 +1036,6 @@ def check(value, expected): check('%spam%bar', '%sbar' % nonascii) check('%{}%bar'.format(nonascii), 'ham%sbar' % nonascii) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_expanduser(self): tester('ntpath.expanduser("test")', 'test') @@ -1515,16 +1509,6 @@ class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase): pathmodule = ntpath attributes = ['relpath'] - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ValueError: illegal environment variable name") - def test_expandvars(self): - return super().test_expandvars() - - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ValueError: illegal environment variable name") - def test_expandvars_nonascii(self): - return super().test_expandvars_nonascii() - class PathLikeTests(NtpathTestCase): diff --git a/Lib/test/test_unittest/testmock/testpatch.py b/Lib/test/test_unittest/testmock/testpatch.py index fe08777e3ed..62c6221f774 100644 --- a/Lib/test/test_unittest/testmock/testpatch.py +++ b/Lib/test/test_unittest/testmock/testpatch.py @@ -658,8 +658,6 @@ def test(): self.assertEqual(foo, {}) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_patch_dict_with_string(self): @patch.dict('os.environ', {'konrad_delong': 'some value'}) def test(): diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 785e794a268..f1a5a71af9a 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -125,6 +125,13 @@ pub(crate) mod module { let environ = vm.ctx.new_dict(); for (key, value) in env::vars() { + // Skip hidden Windows environment variables (e.g., =C:, =D:, =ExitCode) + // These are internal cmd.exe bookkeeping variables that store per-drive + // current directories. They cannot be modified via _wputenv() and should + // not be exposed to Python code. + if key.starts_with('=') { + continue; + } environ.set_item(&key, vm.new_pyobj(value), vm).unwrap(); } environ @@ -364,8 +371,9 @@ pub(crate) mod module { if key_str.contains('\0') || value_str.contains('\0') { return Err(vm.new_value_error("embedded null character")); } - // Validate: no '=' in key - if key_str.contains('=') { + // Validate: no '=' in key (search from index 1 because on Windows + // starting '=' is allowed for defining hidden environment variables) + if key_str.get(1..).is_some_and(|s| s.contains('=')) { return Err(vm.new_value_error("illegal environment variable name")); } @@ -480,8 +488,9 @@ pub(crate) mod module { if key_str.contains('\0') || value_str.contains('\0') { return Err(vm.new_value_error("embedded null character")); } - // Validate: no '=' in key - if key_str.contains('=') { + // Validate: no '=' in key (search from index 1 because on Windows + // starting '=' is allowed for defining hidden environment variables) + if key_str.get(1..).is_some_and(|s| s.contains('=')) { return Err(vm.new_value_error("illegal environment variable name")); } diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 8f42d24878f..e42ec064cdf 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -7,7 +7,7 @@ use crate::{ convert::{IntoPyException, ToPyException, ToPyObject}, function::{ArgumentError, FromArgs, FuncArgs}, }; -use std::{ffi, fs, io, path::Path}; +use std::{fs, io, path::Path}; pub(crate) fn fs_metadata<P: AsRef<Path>>( path: P, @@ -112,7 +112,8 @@ pub(super) struct FollowSymlinks( #[pyarg(named, name = "follow_symlinks", default = true)] pub bool, ); -fn bytes_as_os_str<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a ffi::OsStr> { +#[cfg(not(windows))] +fn bytes_as_os_str<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a std::ffi::OsStr> { rustpython_common::os::bytes_as_os_str(b) .map_err(|_| vm.new_unicode_decode_error("can't decode path for utf-8")) } @@ -160,7 +161,7 @@ pub(super) mod _os { suppress_iph, }, convert::{IntoPyException, ToPyObject}, - function::{ArgBytesLike, Either, FsPath, FuncArgs, OptionalArg}, + function::{ArgBytesLike, FsPath, FuncArgs, OptionalArg}, ospath::{IOErrorBuilder, OsPath, OsPathOrFd, OutputMode}, protocol::PyIterReturn, recursion::ReprGuard, @@ -171,7 +172,7 @@ pub(super) mod _os { use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; use std::{ - env, ffi, fs, + env, fs, fs::OpenOptions, io, path::PathBuf, @@ -403,7 +404,7 @@ pub(super) mod _os { b"." | b".." => None, _ => Some( OutputMode::String - .process_path(ffi::OsStr::from_bytes(fname), vm), + .process_path(std::ffi::OsStr::from_bytes(fname), vm), ), } }) @@ -415,31 +416,62 @@ pub(super) mod _os { Ok(list) } - fn env_bytes_as_bytes(obj: &Either<PyStrRef, PyBytesRef>) -> &[u8] { + #[cfg(not(windows))] + fn env_bytes_as_bytes(obj: &crate::function::Either<PyStrRef, PyBytesRef>) -> &[u8] { match obj { - Either::A(s) => s.as_bytes(), - Either::B(b) => b.as_bytes(), + crate::function::Either::A(s) => s.as_bytes(), + crate::function::Either::B(b) => b.as_bytes(), } } - /// Check if environment variable length exceeds Windows limit. - /// size should be key.len() + value.len() + 2 (for '=' and null terminator) #[cfg(windows)] - fn check_env_var_len(size: usize, vm: &VirtualMachine) -> PyResult<()> { + unsafe extern "C" { + fn _wputenv(envstring: *const u16) -> libc::c_int; + } + + /// Check if wide string length exceeds Windows environment variable limit. + #[cfg(windows)] + fn check_env_var_len(wide_len: usize, vm: &VirtualMachine) -> PyResult<()> { use crate::common::windows::_MAX_ENV; - if size > _MAX_ENV { + if wide_len > _MAX_ENV + 1 { return Err(vm.new_value_error(format!( - "the environment variable is longer than {} characters", - _MAX_ENV + "the environment variable is longer than {_MAX_ENV} characters", ))); } Ok(()) } + #[cfg(windows)] + #[pyfunction] + fn putenv(key: PyStrRef, value: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { + let key_str = key.as_str(); + let value_str = value.as_str(); + // Search from index 1 because on Windows starting '=' is allowed for + // defining hidden environment variables. + if key_str.is_empty() + || key_str.get(1..).is_some_and(|s| s.contains('=')) + || key_str.contains('\0') + || value_str.contains('\0') + { + return Err(vm.new_value_error("illegal environment variable name")); + } + let env_str = format!("{}={}", key_str, value_str); + let wide = env_str.to_wide_with_nul(); + check_env_var_len(wide.len(), vm)?; + + // Use _wputenv like CPython (not SetEnvironmentVariableW) to update CRT environ + let result = unsafe { suppress_iph!(_wputenv(wide.as_ptr())) }; + if result != 0 { + return Err(vm.new_last_errno_error()); + } + Ok(()) + } + + #[cfg(not(windows))] #[pyfunction] fn putenv( - key: Either<PyStrRef, PyBytesRef>, - value: Either<PyStrRef, PyBytesRef>, + key: crate::function::Either<PyStrRef, PyBytesRef>, + value: crate::function::Either<PyStrRef, PyBytesRef>, vm: &VirtualMachine, ) -> PyResult<()> { let key = env_bytes_as_bytes(&key); @@ -450,8 +482,6 @@ pub(super) mod _os { if key.is_empty() || key.contains(&b'=') { return Err(vm.new_value_error("illegal environment variable name")); } - #[cfg(windows)] - check_env_var_len(key.len() + value.len() + 2, vm)?; let key = super::bytes_as_os_str(key, vm)?; let value = super::bytes_as_os_str(value, vm)?; // SAFETY: requirements forwarded from the caller @@ -459,8 +489,37 @@ pub(super) mod _os { Ok(()) } + #[cfg(windows)] + #[pyfunction] + fn unsetenv(key: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { + let key_str = key.as_str(); + // Search from index 1 because on Windows starting '=' is allowed for + // defining hidden environment variables. + if key_str.is_empty() + || key_str.get(1..).is_some_and(|s| s.contains('=')) + || key_str.contains('\0') + { + return Err(vm.new_value_error("illegal environment variable name")); + } + // "key=" to unset (empty value removes the variable) + let env_str = format!("{}=", key_str); + let wide = env_str.to_wide_with_nul(); + check_env_var_len(wide.len(), vm)?; + + // Use _wputenv like CPython (not SetEnvironmentVariableW) to update CRT environ + let result = unsafe { suppress_iph!(_wputenv(wide.as_ptr())) }; + if result != 0 { + return Err(vm.new_last_errno_error()); + } + Ok(()) + } + + #[cfg(not(windows))] #[pyfunction] - fn unsetenv(key: Either<PyStrRef, PyBytesRef>, vm: &VirtualMachine) -> PyResult<()> { + fn unsetenv( + key: crate::function::Either<PyStrRef, PyBytesRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { let key = env_bytes_as_bytes(&key); if key.contains(&b'\0') { return Err(vm.new_value_error("embedded null byte")); @@ -474,9 +533,6 @@ pub(super) mod _os { ), )); } - // For unsetenv, size is key + '=' (no value, just clearing) - #[cfg(windows)] - check_env_var_len(key.len() + 1, vm)?; let key = super::bytes_as_os_str(key, vm)?; // SAFETY: requirements forwarded from the caller unsafe { env::remove_var(key) }; @@ -984,7 +1040,7 @@ pub(super) mod _os { OsPathOrFd::Path(path) => { use rustpython_common::os::ffi::OsStrExt; let path = path.as_ref().as_os_str().as_bytes(); - let path = match ffi::CString::new(path) { + let path = match std::ffi::CString::new(path) { Ok(x) => x, Err(_) => return Ok(None), }; @@ -1483,7 +1539,7 @@ pub(super) mod _os { #[pyfunction] fn strerror(e: i32) -> String { - unsafe { ffi::CStr::from_ptr(libc::strerror(e)) } + unsafe { std::ffi::CStr::from_ptr(libc::strerror(e)) } .to_string_lossy() .into_owned() } @@ -1587,7 +1643,7 @@ pub(super) mod _os { if encoding.is_null() || encoding.read() == '\0' as libc::c_char { "UTF-8".to_owned() } else { - ffi::CStr::from_ptr(encoding).to_string_lossy().into_owned() + std::ffi::CStr::from_ptr(encoding).to_string_lossy().into_owned() } }; From 00543942aab5ca47469c5fbb334e293c91e95e71 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 11 Dec 2025 21:42:28 +0900 Subject: [PATCH 470/819] nt.skiproot, winapi.LCMapStringEx (#6399) --- Lib/test/test_fnmatch.py | 1 - Lib/test/test_ntpath.py | 16 ---- Lib/test/test_posixpath.py | 4 - crates/vm/src/stdlib/nt.rs | 145 +++++++++++++++++++++++++++++++++ crates/vm/src/stdlib/winapi.rs | 129 +++++++++++++++++++++++++++++ 5 files changed, 274 insertions(+), 21 deletions(-) diff --git a/Lib/test/test_fnmatch.py b/Lib/test/test_fnmatch.py index b977b8f8eb0..10ed496d4e2 100644 --- a/Lib/test/test_fnmatch.py +++ b/Lib/test/test_fnmatch.py @@ -71,7 +71,6 @@ def test_fnmatchcase(self): check('usr/bin', 'usr\\bin', False, fnmatchcase) check('usr\\bin', 'usr\\bin', True, fnmatchcase) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') def test_bytes(self): self.check_match(b'test', b'te*') self.check_match(b'test\xff', b'te*\xff') diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 86c34a43f10..1efe8bfa0e0 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -130,8 +130,6 @@ def test_splitdrive(self): tester('ntpath.splitdrive("//?/UNC/server/share/dir")', ("//?/UNC/server/share", "/dir")) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_splitdrive_invalid_paths(self): splitdrive = ntpath.splitdrive self.assertEqual(splitdrive('\\\\ser\x00ver\\sha\x00re\\di\x00r'), @@ -238,8 +236,6 @@ def test_splitroot(self): tester('ntpath.splitroot(" :/foo")', (" :", "/", "foo")) tester('ntpath.splitroot("/:/foo")', ("", "/", ":/foo")) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_splitroot_invalid_paths(self): splitroot = ntpath.splitroot self.assertEqual(splitroot('\\\\ser\x00ver\\sha\x00re\\di\x00r'), @@ -268,8 +264,6 @@ def test_split(self): tester('ntpath.split("c:/")', ('c:/', '')) tester('ntpath.split("//conky/mountpoint/")', ('//conky/mountpoint/', '')) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_split_invalid_paths(self): split = ntpath.split self.assertEqual(split('c:\\fo\x00o\\ba\x00r'), @@ -392,8 +386,6 @@ def test_join(self): tester("ntpath.join('D:a', './c:b')", 'D:a\\.\\c:b') tester("ntpath.join('D:/a', './c:b')", 'D:\\a\\.\\c:b') - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_normcase(self): normcase = ntpath.normcase self.assertEqual(normcase(''), '') @@ -409,8 +401,6 @@ def test_normcase(self): self.assertEqual(normcase('\u03a9\u2126'.encode()), expected.encode()) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_normcase_invalid_paths(self): normcase = ntpath.normcase self.assertEqual(normcase('abc\x00def'), 'abc\x00def') @@ -468,8 +458,6 @@ def test_normpath(self): tester("ntpath.normpath('\\\\')", '\\\\') tester("ntpath.normpath('//?/UNC/server/share/..')", '\\\\?\\UNC\\server\\share\\') - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_normpath_invalid_paths(self): normpath = ntpath.normpath self.assertEqual(normpath('fo\x00o'), 'fo\x00o') @@ -1130,8 +1118,6 @@ def test_abspath(self): drive, _ = ntpath.splitdrive(cwd_dir) tester('ntpath.abspath("/abc/")', drive + "\\abc") - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_abspath_invalid_paths(self): abspath = ntpath.abspath if sys.platform == 'win32': @@ -1438,8 +1424,6 @@ def test_isfile_anonymous_pipe(self): os.close(pr) os.close(pw) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @unittest.skipIf(sys.platform != 'win32', "windows only") def test_isfile_named_pipe(self): import _winapi diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index 3614fc40ba3..0dc0211eada 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -189,8 +189,6 @@ def test_dirname(self): self.assertEqual(posixpath.dirname(b"////foo"), b"////") self.assertEqual(posixpath.dirname(b"//foo//bar"), b"//foo") - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_islink(self): self.assertIs(posixpath.islink(TESTFN + "1"), False) self.assertIs(posixpath.lexists(TESTFN + "2"), False) @@ -236,8 +234,6 @@ def test_ismount_invalid_paths(self): self.assertIs(posixpath.ismount('/\x00'), False) self.assertIs(posixpath.ismount(b'/\x00'), False) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_ismount_symlinks(self): # Symlinks are never mountpoints. diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index f1a5a71af9a..0b8b2ec6374 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -570,6 +570,151 @@ pub(crate) mod module { Ok(path.mode.process_path(buffer.to_os_string(), vm)) } + /// Implements CPython's _Py_skiproot logic for Windows paths + /// Returns (drive_size, root_size) where: + /// - drive_size: length of the drive/UNC portion + /// - root_size: length of the root separator (0 or 1) + fn skiproot(path: &[u16]) -> (usize, usize) { + let len = path.len(); + if len == 0 { + return (0, 0); + } + + const SEP: u16 = b'\\' as u16; + const ALTSEP: u16 = b'/' as u16; + const COLON: u16 = b':' as u16; + + let is_sep = |c: u16| c == SEP || c == ALTSEP; + let get = |i: usize| path.get(i).copied().unwrap_or(0); + + if is_sep(get(0)) { + if is_sep(get(1)) { + // UNC or device path: \\server\share or \\?\device + // Check for \\?\UNC\server\share + let idx = if len >= 8 + && get(2) == b'?' as u16 + && is_sep(get(3)) + && (get(4) == b'U' as u16 || get(4) == b'u' as u16) + && (get(5) == b'N' as u16 || get(5) == b'n' as u16) + && (get(6) == b'C' as u16 || get(6) == b'c' as u16) + && is_sep(get(7)) + { + 8 + } else { + 2 + }; + + // Find the end of server name + let mut i = idx; + while i < len && !is_sep(get(i)) { + i += 1; + } + + if i >= len { + // No share part: \\server + return (i, 0); + } + + // Skip separator and find end of share name + i += 1; + while i < len && !is_sep(get(i)) { + i += 1; + } + + // drive = \\server\share, root = \ (if present) + if i >= len { (i, 0) } else { (i, 1) } + } else { + // Relative path with root: \Windows + (0, 1) + } + } else if len >= 2 && get(1) == COLON { + // Drive letter path + if len >= 3 && is_sep(get(2)) { + // Absolute: X:\Windows + (2, 1) + } else { + // Relative with drive: X:Windows + (2, 0) + } + } else { + // Relative path: Windows + (0, 0) + } + } + + #[pyfunction] + fn _path_splitroot_ex(path: crate::PyObjectRef, vm: &VirtualMachine) -> PyResult<PyTupleRef> { + use crate::builtins::{PyBytes, PyStr}; + use rustpython_common::wtf8::Wtf8Buf; + + // Handle path-like objects via os.fspath, but without null check (nonstrict=True in CPython) + let path = if let Some(fspath) = vm.get_method(path.clone(), identifier!(vm, __fspath__)) { + fspath?.call((), vm)? + } else { + path + }; + + // Convert to wide string, validating UTF-8 for bytes input + let (wide, is_bytes): (Vec<u16>, bool) = if let Some(s) = path.downcast_ref::<PyStr>() { + // Use encode_wide which handles WTF-8 (including surrogates) + let wide: Vec<u16> = s.as_wtf8().encode_wide().collect(); + (wide, false) + } else if let Some(b) = path.downcast_ref::<PyBytes>() { + // On Windows, bytes must be valid UTF-8 - this raises UnicodeDecodeError if not + let s = std::str::from_utf8(b.as_bytes()).map_err(|e| { + vm.new_exception_msg( + vm.ctx.exceptions.unicode_decode_error.to_owned(), + format!( + "'utf-8' codec can't decode byte {:#x} in position {}: invalid start byte", + b.as_bytes().get(e.valid_up_to()).copied().unwrap_or(0), + e.valid_up_to() + ), + ) + })?; + let wide: Vec<u16> = s.encode_utf16().collect(); + (wide, true) + } else { + return Err(vm.new_type_error(format!( + "expected str or bytes, not {}", + path.class().name() + ))); + }; + + // Normalize slashes for parsing + let normalized: Vec<u16> = wide + .iter() + .map(|&c| if c == b'/' as u16 { b'\\' as u16 } else { c }) + .collect(); + + let (drv_size, root_size) = skiproot(&normalized); + + // Return as bytes if input was bytes, preserving the original content + if is_bytes { + // Convert UTF-16 back to UTF-8 for bytes output + let drv = String::from_utf16(&wide[..drv_size]) + .map_err(|e| vm.new_unicode_decode_error(e.to_string()))?; + let root = String::from_utf16(&wide[drv_size..drv_size + root_size]) + .map_err(|e| vm.new_unicode_decode_error(e.to_string()))?; + let tail = String::from_utf16(&wide[drv_size + root_size..]) + .map_err(|e| vm.new_unicode_decode_error(e.to_string()))?; + Ok(vm.ctx.new_tuple(vec![ + vm.ctx.new_bytes(drv.into_bytes()).into(), + vm.ctx.new_bytes(root.into_bytes()).into(), + vm.ctx.new_bytes(tail.into_bytes()).into(), + ])) + } else { + // For str output, use WTF-8 to handle surrogates + let drv = Wtf8Buf::from_wide(&wide[..drv_size]); + let root = Wtf8Buf::from_wide(&wide[drv_size..drv_size + root_size]); + let tail = Wtf8Buf::from_wide(&wide[drv_size + root_size..]); + Ok(vm.ctx.new_tuple(vec![ + vm.ctx.new_str(drv).into(), + vm.ctx.new_str(root).into(), + vm.ctx.new_str(tail).into(), + ])) + } + } + #[pyfunction] fn _path_splitroot(path: OsPath, vm: &VirtualMachine) -> PyResult<(String, String)> { let orig: Vec<_> = path.path.to_wide(); diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index 62505b2b74b..956d96d5432 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -540,4 +540,133 @@ mod _winapi { windows_sys::Win32::System::Threading::ReleaseMutex(handle as _) }) } + + // LOCALE_NAME_INVARIANT is an empty string in Windows API + #[pyattr] + const LOCALE_NAME_INVARIANT: &str = ""; + + /// LCMapStringEx - Map a string to another string using locale-specific rules + /// This is used by ntpath.normcase() for proper Windows case conversion + #[pyfunction] + fn LCMapStringEx( + locale: PyStrRef, + flags: u32, + src: PyStrRef, + vm: &VirtualMachine, + ) -> PyResult<PyStrRef> { + use rustpython_common::wtf8::Wtf8Buf; + use windows_sys::Win32::Globalization::{ + LCMAP_BYTEREV, LCMAP_HASH, LCMAP_SORTHANDLE, LCMAP_SORTKEY, + LCMapStringEx as WinLCMapStringEx, + }; + + // Reject unsupported flags (same as CPython) + if flags & (LCMAP_SORTHANDLE | LCMAP_HASH | LCMAP_BYTEREV | LCMAP_SORTKEY) != 0 { + return Err(vm.new_value_error("unsupported flags")); + } + + // Use encode_wide() which properly handles WTF-8 (including surrogates) + let locale_wide: Vec<u16> = locale + .as_wtf8() + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + let src_wide: Vec<u16> = src.as_wtf8().encode_wide().collect(); + + if src_wide.len() > i32::MAX as usize { + return Err(vm.new_overflow_error("input string is too long".to_string())); + } + + // First call to get required buffer size + let dest_size = unsafe { + WinLCMapStringEx( + locale_wide.as_ptr(), + flags, + src_wide.as_ptr(), + src_wide.len() as i32, + null_mut(), + 0, + null(), + null(), + 0, + ) + }; + + if dest_size <= 0 { + return Err(vm.new_last_os_error()); + } + + // Second call to perform the mapping + let mut dest = vec![0u16; dest_size as usize]; + let nmapped = unsafe { + WinLCMapStringEx( + locale_wide.as_ptr(), + flags, + src_wide.as_ptr(), + src_wide.len() as i32, + dest.as_mut_ptr(), + dest_size, + null(), + null(), + 0, + ) + }; + + if nmapped <= 0 { + return Err(vm.new_last_os_error()); + } + + dest.truncate(nmapped as usize); + + // Convert UTF-16 back to WTF-8 (handles surrogates properly) + let result = Wtf8Buf::from_wide(&dest); + Ok(vm.ctx.new_str(result)) + } + + #[derive(FromArgs)] + struct CreateNamedPipeArgs { + #[pyarg(positional)] + name: PyStrRef, + #[pyarg(positional)] + open_mode: u32, + #[pyarg(positional)] + pipe_mode: u32, + #[pyarg(positional)] + max_instances: u32, + #[pyarg(positional)] + out_buffer_size: u32, + #[pyarg(positional)] + in_buffer_size: u32, + #[pyarg(positional)] + default_timeout: u32, + #[pyarg(positional)] + _security_attributes: PyObjectRef, // Ignored, can be None + } + + /// CreateNamedPipe - Create a named pipe + #[pyfunction] + fn CreateNamedPipe(args: CreateNamedPipeArgs, vm: &VirtualMachine) -> PyResult<WinHandle> { + use windows_sys::Win32::System::Pipes::CreateNamedPipeW; + + let name_wide = args.name.as_str().to_wide_with_nul(); + + let handle = unsafe { + CreateNamedPipeW( + name_wide.as_ptr(), + args.open_mode, + args.pipe_mode, + args.max_instances, + args.out_buffer_size, + args.in_buffer_size, + args.default_timeout, + null(), // security_attributes - NULL for now + ) + }; + + if handle == INVALID_HANDLE_VALUE { + return Err(vm.new_last_os_error()); + } + + Ok(WinHandle(handle)) + } } From 5f496c955cf9bb7fa467b848831649c38a9c788b Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:19:11 +0900 Subject: [PATCH 471/819] PyType::py_new to return Self (#6398) * PyType::py_new to return Self * int/float/complex --- crates/stdlib/src/array.rs | 10 +- crates/stdlib/src/bz2.rs | 27 ++-- crates/stdlib/src/contextvars.rs | 20 ++- crates/stdlib/src/csv.rs | 6 +- crates/stdlib/src/json.rs | 10 +- crates/stdlib/src/lzma.rs | 17 +-- crates/stdlib/src/mmap.rs | 18 +-- crates/stdlib/src/overlapped.rs | 13 +- crates/stdlib/src/pystruct.rs | 8 +- crates/stdlib/src/select.rs | 12 +- crates/stdlib/src/sqlite.rs | 40 ++--- crates/stdlib/src/ssl.rs | 30 ++-- crates/stdlib/src/zlib.rs | 12 +- crates/vm/src/builtins/bool.rs | 9 +- crates/vm/src/builtins/bytes.rs | 10 +- crates/vm/src/builtins/classmethod.rs | 8 +- crates/vm/src/builtins/code.rs | 8 +- crates/vm/src/builtins/complex.rs | 38 ++--- crates/vm/src/builtins/enumerate.rs | 12 +- crates/vm/src/builtins/filter.rs | 12 +- crates/vm/src/builtins/float.rs | 27 ++-- crates/vm/src/builtins/function.rs | 22 ++- crates/vm/src/builtins/genericalias.rs | 6 +- crates/vm/src/builtins/int.rs | 33 ++-- crates/vm/src/builtins/list.rs | 4 +- crates/vm/src/builtins/map.rs | 10 +- crates/vm/src/builtins/mappingproxy.rs | 8 +- crates/vm/src/builtins/memory.rs | 5 +- crates/vm/src/builtins/object.rs | 6 +- crates/vm/src/builtins/property.rs | 12 +- crates/vm/src/builtins/set.rs | 9 +- crates/vm/src/builtins/singletons.rs | 15 +- crates/vm/src/builtins/slice.rs | 7 +- crates/vm/src/builtins/staticmethod.rs | 7 +- crates/vm/src/builtins/str.rs | 7 +- crates/vm/src/builtins/super.rs | 8 +- crates/vm/src/builtins/traceback.rs | 7 +- crates/vm/src/builtins/tuple.rs | 9 +- crates/vm/src/builtins/type.rs | 6 +- crates/vm/src/builtins/weakproxy.rs | 10 +- crates/vm/src/builtins/weakref.rs | 13 +- crates/vm/src/builtins/zip.rs | 12 +- crates/vm/src/exceptions.rs | 6 +- crates/vm/src/protocol/object.rs | 17 +-- crates/vm/src/stdlib/ast/python.rs | 8 +- crates/vm/src/stdlib/collections.rs | 19 ++- crates/vm/src/stdlib/ctypes/array.rs | 12 +- crates/vm/src/stdlib/ctypes/base.rs | 22 ++- crates/vm/src/stdlib/ctypes/function.rs | 14 +- crates/vm/src/stdlib/ctypes/pointer.rs | 10 +- crates/vm/src/stdlib/ctypes/structure.rs | 14 +- crates/vm/src/stdlib/ctypes/union.rs | 14 +- crates/vm/src/stdlib/functools.rs | 12 +- crates/vm/src/stdlib/io.rs | 20 +-- crates/vm/src/stdlib/itertools.rs | 187 ++++++++++------------- crates/vm/src/stdlib/operator.rs | 22 +-- crates/vm/src/stdlib/posix.rs | 10 +- crates/vm/src/stdlib/thread.rs | 5 +- crates/vm/src/stdlib/typevar.rs | 45 +++--- crates/vm/src/stdlib/typing.rs | 21 ++- crates/vm/src/types/slot.rs | 35 ++++- 61 files changed, 579 insertions(+), 477 deletions(-) diff --git a/crates/stdlib/src/array.rs b/crates/stdlib/src/array.rs index 0501faedffd..4fcba1f8725 100644 --- a/crates/stdlib/src/array.rs +++ b/crates/stdlib/src/array.rs @@ -45,7 +45,8 @@ mod array { atomic_func, builtins::{ PositionIterInternal, PyByteArray, PyBytes, PyBytesRef, PyDictRef, PyFloat, - PyGenericAlias, PyInt, PyList, PyListRef, PyStr, PyStrRef, PyTupleRef, PyTypeRef, + PyGenericAlias, PyInt, PyList, PyListRef, PyStr, PyStrRef, PyTupleRef, PyType, + PyTypeRef, }, class_or_notimplemented, convert::{ToPyObject, ToPyResult, TryFromBorrowedObject, TryFromObject}, @@ -651,10 +652,10 @@ mod array { type Args = (ArrayNewArgs, KwArgs); fn py_new( - cls: PyTypeRef, + cls: &Py<PyType>, (ArrayNewArgs { spec, init }, kwargs): Self::Args, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult<Self> { let spec = spec.as_str().chars().exactly_one().map_err(|_| { vm.new_type_error("array() argument 1 must be a unicode character, not str") })?; @@ -701,8 +702,7 @@ mod array { } } - let zelf = Self::from(array).into_ref_with_type(vm, cls)?; - Ok(zelf.into()) + Ok(Self::from(array)) } } diff --git a/crates/stdlib/src/bz2.rs b/crates/stdlib/src/bz2.rs index f4db2d9fa1c..a2a40953cff 100644 --- a/crates/stdlib/src/bz2.rs +++ b/crates/stdlib/src/bz2.rs @@ -8,11 +8,11 @@ mod _bz2 { DecompressArgs, DecompressError, DecompressState, DecompressStatus, Decompressor, }; use crate::vm::{ - VirtualMachine, - builtins::{PyBytesRef, PyTypeRef}, + Py, VirtualMachine, + builtins::{PyBytesRef, PyType}, common::lock::PyMutex, function::{ArgBytesLike, OptionalArg}, - object::{PyPayload, PyResult}, + object::PyResult, types::Constructor, }; use bzip2::{Decompress, Status, write::BzEncoder}; @@ -61,12 +61,10 @@ mod _bz2 { impl Constructor for BZ2Decompressor { type Args = (); - fn py_new(cls: PyTypeRef, _: Self::Args, vm: &VirtualMachine) -> PyResult { - Self { + fn py_new(_cls: &Py<PyType>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self { state: PyMutex::new(DecompressState::new(Decompress::new(false), vm)), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -132,8 +130,11 @@ mod _bz2 { impl Constructor for BZ2Compressor { type Args = (OptionalArg<i32>,); - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - let (compresslevel,) = args; + fn py_new( + _cls: &Py<PyType>, + (compresslevel,): Self::Args, + vm: &VirtualMachine, + ) -> PyResult<Self> { // TODO: seriously? // compresslevel.unwrap_or(bzip2::Compression::best().level().try_into().unwrap()); let compresslevel = compresslevel.unwrap_or(9); @@ -144,14 +145,12 @@ mod _bz2 { } }; - Self { + Ok(Self { state: PyMutex::new(CompressorState { flushed: false, encoder: Some(BzEncoder::new(Vec::new(), level)), }), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } diff --git a/crates/stdlib/src/contextvars.rs b/crates/stdlib/src/contextvars.rs index cb66b8511f9..f48df167a71 100644 --- a/crates/stdlib/src/contextvars.rs +++ b/crates/stdlib/src/contextvars.rs @@ -24,7 +24,7 @@ thread_local! { mod _contextvars { use crate::vm::{ AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, atomic_func, - builtins::{PyGenericAlias, PyStrRef, PyTypeRef}, + builtins::{PyGenericAlias, PyStrRef, PyType, PyTypeRef}, class::StaticType, common::hash::PyHash, function::{ArgCallable, FuncArgs, OptionalArg}, @@ -244,8 +244,8 @@ mod _contextvars { impl Constructor for PyContext { type Args = (); - fn py_new(_cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { - Ok(Self::empty(vm).into_pyobject(vm)) + fn py_new(_cls: &Py<PyType>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self::empty(vm)) } } @@ -488,7 +488,9 @@ mod _contextvars { impl Constructor for ContextVar { type Args = ContextVarOptions; - fn py_new(_cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let args: Self::Args = args.bind(vm)?; let var = Self { name: args.name.to_string(), default: args.default.into_option(), @@ -496,7 +498,7 @@ mod _contextvars { cached: AtomicCell::new(None), hash: UnsafeCell::new(0), }; - let py_var = var.into_ref(&vm.ctx); + let py_var = var.into_ref_with_type(vm, cls)?; unsafe { // SAFETY: py_var is not exposed to python memory model yet @@ -504,6 +506,10 @@ mod _contextvars { }; Ok(py_var.into()) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl std::hash::Hash for ContextVar { @@ -578,8 +584,8 @@ mod _contextvars { fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { Err(vm.new_runtime_error("Tokens can only be created by ContextVars")) } - fn py_new(_cls: PyTypeRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult { - unreachable!() + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") } } diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index 5f6d2494763..39ca70640d0 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -68,10 +68,8 @@ mod _csv { impl Constructor for PyDialect { type Args = PyObjectRef; - fn py_new(cls: PyTypeRef, ctx: Self::Args, vm: &VirtualMachine) -> PyResult { - Self::try_from_object(vm, ctx)? - .into_ref_with_type(vm, cls) - .map(Into::into) + fn py_new(_cls: &Py<PyType>, ctx: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { + Self::try_from_object(vm, ctx) } } #[pyclass(with(Constructor))] diff --git a/crates/stdlib/src/json.rs b/crates/stdlib/src/json.rs index afc9af234b6..eb6ed3a5f64 100644 --- a/crates/stdlib/src/json.rs +++ b/crates/stdlib/src/json.rs @@ -6,7 +6,7 @@ mod _json { use super::machinery; use crate::vm::{ AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyBaseExceptionRef, PyStrRef, PyType, PyTypeRef}, + builtins::{PyBaseExceptionRef, PyStrRef, PyType}, convert::{ToPyObject, ToPyResult}, function::{IntoFuncArgs, OptionalArg}, protocol::PyIterReturn, @@ -33,7 +33,7 @@ mod _json { impl Constructor for JsonScanner { type Args = PyObjectRef; - fn py_new(cls: PyTypeRef, ctx: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, ctx: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { let strict = ctx.get_attr("strict", vm)?.try_to_bool(vm)?; let object_hook = vm.option_if_none(ctx.get_attr("object_hook", vm)?); let object_pairs_hook = vm.option_if_none(ctx.get_attr("object_pairs_hook", vm)?); @@ -52,7 +52,7 @@ mod _json { }; let parse_constant = ctx.get_attr("parse_constant", vm)?; - Self { + Ok(Self { strict, object_hook, object_pairs_hook, @@ -60,9 +60,7 @@ mod _json { parse_int, parse_constant, ctx, - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } diff --git a/crates/stdlib/src/lzma.rs b/crates/stdlib/src/lzma.rs index 885af30bc6f..855a5eae562 100644 --- a/crates/stdlib/src/lzma.rs +++ b/crates/stdlib/src/lzma.rs @@ -33,11 +33,11 @@ mod _lzma { LZMA_PRESET_LEVEL_MASK as PRESET_LEVEL_MASK, }; use rustpython_common::lock::PyMutex; - use rustpython_vm::builtins::{PyBaseExceptionRef, PyBytesRef, PyTypeRef}; + use rustpython_vm::builtins::{PyBaseExceptionRef, PyBytesRef, PyType, PyTypeRef}; use rustpython_vm::convert::ToPyException; use rustpython_vm::function::ArgBytesLike; use rustpython_vm::types::Constructor; - use rustpython_vm::{PyObjectRef, PyPayload, PyResult, VirtualMachine}; + use rustpython_vm::{Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use std::fmt; use xz2::stream::{Action, Check, Error, Filters, LzmaOptions, Status, Stream}; @@ -148,7 +148,7 @@ mod _lzma { impl Constructor for LZMADecompressor { type Args = LZMADecompressorConstructorArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { if args.format == FORMAT_RAW && args.mem_limit.is_some() { return Err(vm.new_value_error("Cannot specify memory limit with FORMAT_RAW")); } @@ -161,15 +161,13 @@ mod _lzma { // TODO: FORMAT_RAW _ => return Err(new_lzma_error("Invalid format", vm)), }; - Self { + Ok(Self { state: PyMutex::new(DecompressState::new( stream_result .map_err(|_| new_lzma_error("Failed to initialize decoder", vm))?, vm, )), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -366,7 +364,7 @@ mod _lzma { impl Constructor for LZMACompressor { type Args = LZMACompressorConstructorArgs; - fn py_new(_cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { let preset = args.preset.unwrap_or(PRESET_DEFAULT); #[allow(clippy::unnecessary_cast)] if args.format != FORMAT_XZ as i32 @@ -392,8 +390,7 @@ mod _lzma { }; Ok(Self { state: PyMutex::new(CompressState::new(CompressorInner::new(stream))), - } - .into_pyobject(vm)) + }) } } diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index 7e53a27be85..5309917a999 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -11,7 +11,7 @@ mod mmap { use crate::vm::{ AsObject, FromArgs, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromBorrowedObject, VirtualMachine, atomic_func, - builtins::{PyBytes, PyBytesRef, PyInt, PyIntRef, PyTypeRef}, + builtins::{PyBytes, PyBytesRef, PyInt, PyIntRef, PyType, PyTypeRef}, byte::{bytes_from_object, value_from_object}, convert::ToPyException, function::{ArgBytesLike, FuncArgs, OptionalArg}, @@ -388,7 +388,7 @@ mod mmap { type Args = MmapNewArgs; #[cfg(unix)] - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { use libc::{MAP_PRIVATE, MAP_SHARED, PROT_READ, PROT_WRITE}; let mut map_size = args.validate_new_args(vm)?; @@ -477,7 +477,7 @@ mod mmap { }() .map_err(|e| e.to_pyexception(vm))?; - let m_obj = Self { + Ok(Self { closed: AtomicCell::new(false), mmap: PyMutex::new(Some(mmap)), fd: AtomicCell::new(fd.map_or(-1, |fd| fd.into_raw())), @@ -486,13 +486,11 @@ mod mmap { pos: AtomicCell::new(0), exports: AtomicCell::new(0), access, - }; - - m_obj.into_ref_with_type(vm, cls).map(Into::into) + }) } #[cfg(windows)] - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { let mut map_size = args.validate_new_args(vm)?; let MmapNewArgs { fileno, @@ -627,7 +625,7 @@ mod mmap { (INVALID_HANDLE_VALUE as isize, MmapObj::Write(mmap)) }; - let m_obj = Self { + Ok(Self { closed: AtomicCell::new(false), mmap: PyMutex::new(Some(mmap)), handle: AtomicCell::new(handle), @@ -636,9 +634,7 @@ mod mmap { pos: AtomicCell::new(0), exports: AtomicCell::new(0), access, - }; - - m_obj.into_ref_with_type(vm, cls).map(Into::into) + }) } } diff --git a/crates/stdlib/src/overlapped.rs b/crates/stdlib/src/overlapped.rs index a5b5fa14fc3..d8f14baf35e 100644 --- a/crates/stdlib/src/overlapped.rs +++ b/crates/stdlib/src/overlapped.rs @@ -9,7 +9,7 @@ mod _overlapped { use crate::vm::{ Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyBaseExceptionRef, PyBytesRef, PyTypeRef}, + builtins::{PyBaseExceptionRef, PyBytesRef, PyType}, common::lock::PyMutex, convert::{ToPyException, ToPyObject}, protocol::PyBuffer, @@ -264,7 +264,11 @@ mod _overlapped { impl Constructor for Overlapped { type Args = (isize,); - fn py_new(cls: PyTypeRef, (mut event,): Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new( + _cls: &Py<PyType>, + (mut event,): Self::Args, + vm: &VirtualMachine, + ) -> PyResult<Self> { if event == INVALID_HANDLE_VALUE { event = unsafe { windows_sys::Win32::System::Threading::CreateEventA( @@ -289,10 +293,9 @@ mod _overlapped { error: 0, data: OverlappedData::None, }; - let overlapped = Overlapped { + Ok(Overlapped { inner: PyMutex::new(inner), - }; - overlapped.into_ref_with_type(vm, cls).map(Into::into) + }) } } diff --git a/crates/stdlib/src/pystruct.rs b/crates/stdlib/src/pystruct.rs index a4c91507621..798e5f5de80 100644 --- a/crates/stdlib/src/pystruct.rs +++ b/crates/stdlib/src/pystruct.rs @@ -12,7 +12,7 @@ pub(crate) mod _struct { use crate::vm::{ AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, buffer::{FormatSpec, new_struct_error, struct_error_type}, - builtins::{PyBytes, PyStr, PyStrRef, PyTupleRef, PyTypeRef}, + builtins::{PyBytes, PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef}, function::{ArgBytesLike, ArgMemoryBuffer, PosArgs}, match_class, protocol::PyIterReturn, @@ -241,12 +241,10 @@ pub(crate) mod _struct { impl Constructor for PyStruct { type Args = IntoStructFormatBytes; - fn py_new(cls: PyTypeRef, fmt: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, fmt: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { let spec = fmt.format_spec(vm)?; let format = fmt.0; - Self { spec, format } - .into_ref_with_type(vm, cls) - .map(Into::into) + Ok(Self { spec, format }) } } diff --git a/crates/stdlib/src/select.rs b/crates/stdlib/src/select.rs index c19052965b4..5639a66d2cc 100644 --- a/crates/stdlib/src/select.rs +++ b/crates/stdlib/src/select.rs @@ -546,8 +546,8 @@ mod decl { pub(super) mod epoll { use super::*; use crate::vm::{ - PyPayload, - builtins::PyTypeRef, + Py, PyPayload, + builtins::PyType, common::lock::{PyRwLock, PyRwLockReadGuard}, convert::{IntoPyException, ToPyObject}, function::OptionalArg, @@ -575,17 +575,15 @@ mod decl { impl Constructor for PyEpoll { type Args = EpollNewArgs; - fn py_new(cls: PyTypeRef, args: EpollNewArgs, vm: &VirtualMachine) -> PyResult { + + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { if let ..=-2 | 0 = args.sizehint { return Err(vm.new_value_error("negative sizehint")); } if !matches!(args.flags, 0 | libc::EPOLL_CLOEXEC) { return Err(vm.new_os_error("invalid flags".to_owned())); } - Self::new() - .map_err(|e| e.into_pyexception(vm))? - .into_ref_with_type(vm, cls) - .map(Into::into) + Self::new().map_err(|e| e.into_pyexception(vm)) } } diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index 7650221fb90..deff3c3a66a 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -650,7 +650,9 @@ mod _sqlite { #[pyfunction] fn connect(args: ConnectArgs, vm: &VirtualMachine) -> PyResult { - Connection::py_new(args.factory.clone(), args, vm) + let factory = args.factory.clone(); + let conn = Connection::py_new(&factory, args, vm)?; + conn.into_ref_with_type(vm, factory).map(Into::into) } #[pyfunction] @@ -851,12 +853,12 @@ mod _sqlite { impl Constructor for Connection { type Args = ConnectArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { let text_factory = PyStr::class(&vm.ctx).to_owned().into_object(); // For non-subclassed Connection, initialize in __new__ // For subclassed Connection, leave db as None and require __init__ to be called - let is_base_class = cls.is(Connection::class(&vm.ctx).as_object()); + let is_base_class = cls.is(Connection::class(&vm.ctx)); let db = if is_base_class { // Initialize immediately for base class @@ -868,7 +870,7 @@ mod _sqlite { let initialized = db.is_some(); - let conn = Self { + Ok(Self { db: PyMutex::new(db), initialized: Radium::new(initialized), detect_types: Radium::new(args.detect_types), @@ -877,9 +879,7 @@ mod _sqlite { thread_ident: PyMutex::new(std::thread::current().id()), row_factory: PyAtomicRef::from(None), text_factory: PyAtomicRef::from(text_factory), - }; - - Ok(conn.into_ref_with_type(vm, cls)?.into()) + }) } } @@ -1940,10 +1940,12 @@ mod _sqlite { impl Constructor for Cursor { type Args = (PyRef<Connection>,); - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - Self::new_uninitialized(args.0, vm) - .into_ref_with_type(vm, cls) - .map(Into::into) + fn py_new( + _cls: &Py<PyType>, + (connection,): Self::Args, + vm: &VirtualMachine, + ) -> PyResult<Self> { + Ok(Self::new_uninitialized(connection, vm)) } } @@ -2109,20 +2111,18 @@ mod _sqlite { impl Constructor for Row { type Args = (PyRef<Cursor>, PyTupleRef); - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - let description = args - .0 + fn py_new( + _cls: &Py<PyType>, + (cursor, data): Self::Args, + vm: &VirtualMachine, + ) -> PyResult<Self> { + let description = cursor .inner(vm)? .description .clone() .ok_or_else(|| vm.new_value_error("no description in Cursor"))?; - Self { - data: args.1, - description, - } - .into_ref_with_type(vm, cls) - .map(Into::into) + Ok(Self { data, description }) } } diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 25227a5556a..ad8952aedb7 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -37,9 +37,9 @@ mod _ssl { vm::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyTypeRef}, + builtins::{PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyType, PyTypeRef}, convert::IntoPyException, - function::{ArgBytesLike, ArgMemoryBuffer, OptionalArg, PyComparisonValue}, + function::{ArgBytesLike, ArgMemoryBuffer, FuncArgs, OptionalArg, PyComparisonValue}, stdlib::warnings, types::{Comparable, Constructor, Hashable, PyComparisonOp, Representable}, }, @@ -2288,7 +2288,11 @@ mod _ssl { impl Constructor for PySSLContext { type Args = (i32,); - fn py_new(cls: PyTypeRef, (protocol,): Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new( + _cls: &Py<PyType>, + (protocol,): Self::Args, + vm: &VirtualMachine, + ) -> PyResult<Self> { // Validate protocol match protocol { PROTOCOL_TLS | PROTOCOL_TLS_CLIENT | PROTOCOL_TLS_SERVER | PROTOCOL_TLSv1_2 @@ -2353,7 +2357,7 @@ mod _ssl { session_cache: shared_session_cache.clone(), }); - PySSLContext { + Ok(PySSLContext { protocol, check_hostname: PyRwLock::new(protocol == PROTOCOL_TLS_CLIENT), verify_mode: PyRwLock::new(default_verify_mode), @@ -2388,9 +2392,7 @@ mod _ssl { accept_count: AtomicUsize::new(0), session_hits: AtomicUsize::new(0), selected_ciphers: PyRwLock::new(None), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -4107,11 +4109,15 @@ mod _ssl { impl Constructor for PySSLSocket { type Args = (); - fn py_new(_cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { Err(vm.new_type_error( "Cannot directly instantiate SSLSocket, use SSLContext.wrap_socket()", )) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } // MemoryBIO - provides in-memory buffer for SSL/TLS I/O @@ -4204,13 +4210,11 @@ mod _ssl { impl Constructor for PyMemoryBIO { type Args = (); - fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { - let obj = PyMemoryBIO { + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + Ok(PyMemoryBIO { buffer: PyMutex::new(Vec::new()), eof: PyRwLock::new(false), - } - .into_ref_with_type(vm, cls)?; - Ok(obj.into()) + }) } } diff --git a/crates/stdlib/src/zlib.rs b/crates/stdlib/src/zlib.rs index cb444ec8c0a..328452ae9d5 100644 --- a/crates/stdlib/src/zlib.rs +++ b/crates/stdlib/src/zlib.rs @@ -10,8 +10,8 @@ mod zlib { Decompressor, USE_AFTER_FINISH_ERR, flush_sync, }; use crate::vm::{ - PyObject, PyPayload, PyResult, VirtualMachine, - builtins::{PyBaseExceptionRef, PyBytesRef, PyIntRef, PyTypeRef}, + Py, PyObject, PyPayload, PyResult, VirtualMachine, + builtins::{PyBaseExceptionRef, PyBytesRef, PyIntRef, PyType, PyTypeRef}, common::lock::PyMutex, convert::{ToPyException, TryFromBorrowedObject}, function::{ArgBytesLike, ArgPrimitiveIndex, ArgSize, OptionalArg}, @@ -570,7 +570,7 @@ mod zlib { impl Constructor for ZlibDecompressor { type Args = DecompressobjArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { let mut decompress = InitOptions::new(args.wbits.value, vm)?.decompress(); let zdict = args.zdict.into_option(); if let Some(dict) = &zdict @@ -580,11 +580,9 @@ mod zlib { .map_err(|_| new_zlib_error("failed to set dictionary", vm))?; } let inner = DecompressState::new(DecompressWithDict { decompress, zdict }, vm); - Self { + Ok(Self { inner: PyMutex::new(inner), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 829d8b2384b..902656da2c8 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -5,7 +5,7 @@ use crate::{ VirtualMachine, class::PyClassImpl, convert::{IntoPyException, ToPyObject, ToPyResult}, - function::OptionalArg, + function::{FuncArgs, OptionalArg}, identifier, protocol::PyNumberMethods, types::{AsNumber, Constructor, Representable}, @@ -101,7 +101,8 @@ impl Debug for PyBool { impl Constructor for PyBool { type Args = OptionalArg<PyObjectRef>; - fn py_new(zelf: PyTypeRef, x: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(zelf: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let x: Self::Args = args.bind(vm)?; if !zelf.fast_isinstance(vm.ctx.types.type_type) { let actual_class = zelf.class(); let actual_type = &actual_class.name(); @@ -112,6 +113,10 @@ impl Constructor for PyBool { let val = x.map_or(Ok(false), |val| val.try_to_bool(vm))?; Ok(vm.ctx.new_bool(val).into()) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } #[pyclass(with(Constructor, AsNumber, Representable), flags(_MATCH_SELF))] diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index a06588cc8f2..3da1b68ef07 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -15,7 +15,8 @@ use crate::{ common::{hash::PyHash, lock::PyMutex}, convert::{ToPyObject, ToPyResult}, function::{ - ArgBytesLike, ArgIndex, ArgIterable, Either, OptionalArg, OptionalOption, PyComparisonValue, + ArgBytesLike, ArgIndex, ArgIterable, Either, FuncArgs, OptionalArg, OptionalOption, + PyComparisonValue, }, protocol::{ BufferDescriptor, BufferMethods, PyBuffer, PyIterReturn, PyMappingMethods, PyNumberMethods, @@ -93,9 +94,14 @@ pub(crate) fn init(context: &Context) { impl Constructor for PyBytes { type Args = ByteInnerNewOptions; - fn py_new(cls: PyTypeRef, options: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let options: Self::Args = args.bind(vm)?; options.get_bytes(cls, vm).to_pyresult(vm) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl PyBytes { diff --git a/crates/vm/src/builtins/classmethod.rs b/crates/vm/src/builtins/classmethod.rs index f88f14819d2..197b6805ef9 100644 --- a/crates/vm/src/builtins/classmethod.rs +++ b/crates/vm/src/builtins/classmethod.rs @@ -3,6 +3,7 @@ use crate::{ AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, common::lock::PyMutex, + function::FuncArgs, types::{Constructor, GetDescriptor, Initializer, Representable}, }; @@ -69,7 +70,8 @@ impl GetDescriptor for PyClassMethod { impl Constructor for PyClassMethod { type Args = PyObjectRef; - fn py_new(cls: PyTypeRef, callable: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let callable: Self::Args = args.bind(vm)?; // Create a dictionary to hold copied attributes let dict = vm.ctx.new_dict(); @@ -99,6 +101,10 @@ impl Constructor for PyClassMethod { let result = PyRef::new_ref(classmethod, cls, Some(dict)); Ok(PyObjectRef::from(result)) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl Initializer for PyClassMethod { diff --git a/crates/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index f9f3429e303..e46cc711bb3 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -1,6 +1,6 @@ //! Infamous code object. The python class `code` -use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType, PyTypeRef}; +use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType}; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::PyStrInterned, @@ -390,7 +390,7 @@ pub struct PyCodeNewArgs { impl Constructor for PyCode { type Args = PyCodeNewArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { // Convert names tuple to vector of interned strings let names: Box<[&'static PyStrInterned]> = args .names @@ -508,9 +508,7 @@ impl Constructor for PyCode { exceptiontable: args.exceptiontable.as_bytes().to_vec().into_boxed_slice(), }; - Ok(PyCode::new(code) - .into_ref_with_type(vm, cls)? - .to_pyobject(vm)) + Ok(PyCode::new(code)) } } diff --git a/crates/vm/src/builtins/complex.rs b/crates/vm/src/builtins/complex.rs index d2e0911240b..3b38ab0dc94 100644 --- a/crates/vm/src/builtins/complex.rs +++ b/crates/vm/src/builtins/complex.rs @@ -6,7 +6,7 @@ use crate::{ common::format::FormatSpec, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{ - OptionalArg, OptionalOption, + FuncArgs, OptionalArg, OptionalOption, PyArithmeticValue::{self, *}, PyComparisonValue, }, @@ -151,22 +151,26 @@ fn powc(a: Complex64, exp: Complex64) -> Complex64 { impl Constructor for PyComplex { type Args = ComplexArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, func_args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // Optimization: return exact complex as-is (only when imag is not provided) + if cls.is(vm.ctx.types.complex_type) + && func_args.args.len() == 1 + && func_args.kwargs.is_empty() + && func_args.args[0].class().is(vm.ctx.types.complex_type) + { + return Ok(func_args.args[0].clone()); + } + + let args: Self::Args = func_args.bind(vm)?; + let payload = Self::py_new(&cls, args, vm)?; + payload.into_ref_with_type(vm, cls).map(Into::into) + } + + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { let imag_missing = args.imag.is_missing(); let (real, real_was_complex) = match args.real { OptionalArg::Missing => (Complex64::new(0.0, 0.0), false), OptionalArg::Present(val) => { - let val = if cls.is(vm.ctx.types.complex_type) && imag_missing { - match val.downcast_exact::<Self>(vm) { - Ok(c) => { - return Ok(c.into_pyref().into()); - } - Err(val) => val, - } - } else { - val - }; - if let Some(c) = val.try_complex(vm)? { c } else if let Some(s) = val.downcast_ref::<PyStr>() { @@ -179,9 +183,7 @@ impl Constructor for PyComplex { .to_str() .and_then(rustpython_literal::complex::parse_str) .ok_or_else(|| vm.new_value_error("complex() arg is a malformed string"))?; - return Self::from(Complex64 { re, im }) - .into_ref_with_type(vm, cls) - .map(Into::into); + return Ok(Self::from(Complex64 { re, im })); } else { return Err(vm.new_type_error(format!( "complex() first argument must be a string or a number, not '{}'", @@ -221,9 +223,7 @@ impl Constructor for PyComplex { imag.re }; let value = Complex64::new(final_real, final_imag); - Self::from(value) - .into_ref_with_type(vm, cls) - .map(Into::into) + Ok(Self::from(value)) } } diff --git a/crates/vm/src/builtins/enumerate.rs b/crates/vm/src/builtins/enumerate.rs index 9d163899a3a..29d51f420e1 100644 --- a/crates/vm/src/builtins/enumerate.rs +++ b/crates/vm/src/builtins/enumerate.rs @@ -41,17 +41,15 @@ impl Constructor for PyEnumerate { type Args = EnumerateArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { iterable, start }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { + _vm: &VirtualMachine, + ) -> PyResult<Self> { let counter = start.map_or_else(BigInt::zero, |start| start.as_bigint().clone()); - Self { + Ok(Self { counter: PyRwLock::new(counter), iterable, - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } diff --git a/crates/vm/src/builtins/filter.rs b/crates/vm/src/builtins/filter.rs index 661fbd02286..388fb8bb603 100644 --- a/crates/vm/src/builtins/filter.rs +++ b/crates/vm/src/builtins/filter.rs @@ -24,13 +24,15 @@ impl PyPayload for PyFilter { impl Constructor for PyFilter { type Args = (PyObjectRef, PyIter); - fn py_new(cls: PyTypeRef, (function, iterator): Self::Args, vm: &VirtualMachine) -> PyResult { - Self { + fn py_new( + _cls: &Py<PyType>, + (function, iterator): Self::Args, + _vm: &VirtualMachine, + ) -> PyResult<Self> { + Ok(Self { predicate: function, iterator, - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } diff --git a/crates/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs index ad18db36a43..26182d748a9 100644 --- a/crates/vm/src/builtins/float.rs +++ b/crates/vm/src/builtins/float.rs @@ -8,7 +8,7 @@ use crate::{ common::{float_ops, format::FormatSpec, hash}, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{ - ArgBytesLike, OptionalArg, OptionalOption, + ArgBytesLike, FuncArgs, OptionalArg, OptionalOption, PyArithmeticValue::{self, *}, PyComparisonValue, }, @@ -131,14 +131,25 @@ pub fn float_pow(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult { impl Constructor for PyFloat { type Args = OptionalArg<PyObjectRef>; - fn py_new(cls: PyTypeRef, arg: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // Optimization: return exact float as-is + if cls.is(vm.ctx.types.float_type) + && args.kwargs.is_empty() + && let Some(first) = args.args.first() + && first.class().is(vm.ctx.types.float_type) + { + return Ok(first.clone()); + } + + let arg: Self::Args = args.bind(vm)?; + let payload = Self::py_new(&cls, arg, vm)?; + payload.into_ref_with_type(vm, cls).map(Into::into) + } + + fn py_new(_cls: &Py<PyType>, arg: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { let float_val = match arg { OptionalArg::Missing => 0.0, OptionalArg::Present(val) => { - if cls.is(vm.ctx.types.float_type) && val.class().is(vm.ctx.types.float_type) { - return Ok(val); - } - if let Some(f) = val.try_float_opt(vm) { f?.value } else { @@ -146,9 +157,7 @@ impl Constructor for PyFloat { } } }; - Self::from(float_val) - .into_ref_with_type(vm, cls) - .map(Into::into) + Ok(Self::from(float_val)) } } diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index 19daf885fb1..0b322c6be5b 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -3,7 +3,7 @@ mod jit; use super::{ PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyStr, PyStrRef, PyTuple, PyTupleRef, - PyType, PyTypeRef, + PyType, }; #[cfg(feature = "jit")] use crate::common::lock::OnceCell; @@ -670,7 +670,7 @@ pub struct PyFunctionNewArgs { impl Constructor for PyFunction { type Args = PyFunctionNewArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { // Handle closure - must be a tuple of cells let closure = if let Some(closure_tuple) = args.closure.into_option() { // Check that closure length matches code's free variables @@ -710,7 +710,7 @@ impl Constructor for PyFunction { func.defaults_and_kwdefaults.lock().1 = Some(kwdefaults); } - func.into_ref_with_type(vm, cls).map(Into::into) + Ok(func) } } @@ -771,13 +771,11 @@ impl Constructor for PyBoundMethod { type Args = PyBoundMethodNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { function, object }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { - Self::new(object, function) - .into_ref_with_type(vm, cls) - .map(Into::into) + _vm: &VirtualMachine, + ) -> PyResult<Self> { + Ok(Self::new(object, function)) } } @@ -897,10 +895,8 @@ impl PyPayload for PyCell { impl Constructor for PyCell { type Args = OptionalArg; - fn py_new(cls: PyTypeRef, value: Self::Args, vm: &VirtualMachine) -> PyResult { - Self::new(value.into_option()) - .into_ref_with_type(vm, cls) - .map(Into::into) + fn py_new(_cls: &Py<PyType>, value: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self::new(value.into_option())) } } diff --git a/crates/vm/src/builtins/genericalias.rs b/crates/vm/src/builtins/genericalias.rs index bcaeb1bfb2d..494b580e563 100644 --- a/crates/vm/src/builtins/genericalias.rs +++ b/crates/vm/src/builtins/genericalias.rs @@ -58,7 +58,7 @@ impl PyPayload for PyGenericAlias { impl Constructor for PyGenericAlias { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { if !args.kwargs.is_empty() { return Err(vm.new_type_error("GenericAlias() takes no keyword arguments")); } @@ -68,9 +68,7 @@ impl Constructor for PyGenericAlias { } else { PyTuple::new_ref(vec![arguments], &vm.ctx) }; - Self::new(origin, args, false, vm) - .into_ref_with_type(vm, cls) - .map(Into::into) + Ok(Self::new(origin, args, false, vm)) } } diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 36cfd425328..f019d2634a5 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -12,7 +12,7 @@ use crate::{ }, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{ - ArgByteOrder, ArgIntoBool, OptionalArg, OptionalOption, PyArithmeticValue, + ArgByteOrder, ArgIntoBool, FuncArgs, OptionalArg, OptionalOption, PyArithmeticValue, PyComparisonValue, }, protocol::{PyNumberMethods, handle_bytes_to_int_err}, @@ -205,13 +205,23 @@ fn inner_truediv(i1: &BigInt, i2: &BigInt, vm: &VirtualMachine) -> PyResult { } impl Constructor for PyInt { - type Args = IntOptions; + type Args = FuncArgs; - fn py_new(cls: PyTypeRef, options: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { if cls.is(vm.ctx.types.bool_type) { return Err(vm.new_type_error("int.__new__(bool) is not safe, use bool.__new__()")); } + // Optimization: return exact int as-is (only for exact int type, not subclasses) + if cls.is(vm.ctx.types.int_type) + && args.args.len() == 1 + && args.kwargs.is_empty() + && args.args[0].class().is(vm.ctx.types.int_type) + { + return Ok(args.args[0].clone()); + } + + let options: IntOptions = args.bind(vm)?; let value = if let OptionalArg::Present(val) = options.val_options { if let OptionalArg::Present(base) = options.base { let base = base @@ -222,17 +232,6 @@ impl Constructor for PyInt { .ok_or_else(|| vm.new_value_error("int() base must be >= 2 and <= 36, or 0"))?; try_int_radix(&val, base, vm) } else { - let val = if cls.is(vm.ctx.types.int_type) { - match val.downcast_exact::<Self>(vm) { - Ok(i) => { - return Ok(i.into_pyref().into()); - } - Err(val) => val, - } - } else { - val - }; - val.try_int(vm).map(|x| x.as_bigint().clone()) } } else if let OptionalArg::Present(_) = options.base { @@ -241,7 +240,11 @@ impl Constructor for PyInt { Ok(Zero::zero()) }?; - Self::with_value(cls, value, vm).to_pyresult(vm) + Self::with_value(cls, value, vm).map(Into::into) + } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") } } diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index 13f29de401b..b2927462cac 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -379,8 +379,8 @@ impl MutObjectSequenceOp for PyList { impl Constructor for PyList { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { - Self::default().into_ref_with_type(vm, cls).map(Into::into) + fn py_new(_cls: &Py<PyType>, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self::default()) } } diff --git a/crates/vm/src/builtins/map.rs b/crates/vm/src/builtins/map.rs index 06a533f8bcd..f5cee945ece 100644 --- a/crates/vm/src/builtins/map.rs +++ b/crates/vm/src/builtins/map.rs @@ -26,11 +26,13 @@ impl PyPayload for PyMap { impl Constructor for PyMap { type Args = (PyObjectRef, PosArgs<PyIter>); - fn py_new(cls: PyTypeRef, (mapper, iterators): Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new( + _cls: &Py<PyType>, + (mapper, iterators): Self::Args, + _vm: &VirtualMachine, + ) -> PyResult<Self> { let iterators = iterators.into_vec(); - Self { mapper, iterators } - .into_ref_with_type(vm, cls) - .map(Into::into) + Ok(Self { mapper, iterators }) } } diff --git a/crates/vm/src/builtins/mappingproxy.rs b/crates/vm/src/builtins/mappingproxy.rs index 04eb6ef671c..fb8ff5de9cc 100644 --- a/crates/vm/src/builtins/mappingproxy.rs +++ b/crates/vm/src/builtins/mappingproxy.rs @@ -61,18 +61,16 @@ impl From<PyDictRef> for PyMappingProxy { impl Constructor for PyMappingProxy { type Args = PyObjectRef; - fn py_new(cls: PyTypeRef, mapping: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, mapping: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { if let Some(methods) = PyMapping::find_methods(&mapping) && !mapping.downcastable::<PyList>() && !mapping.downcastable::<PyTuple>() { - return Self { + return Ok(Self { mapping: MappingProxyInner::Mapping(ArgMapping::with_methods(mapping, unsafe { methods.borrow_static() })), - } - .into_ref_with_type(vm, cls) - .map(Into::into); + }); } Err(vm.new_type_error(format!( "mappingproxy() argument must be a mapping, not {}", diff --git a/crates/vm/src/builtins/memory.rs b/crates/vm/src/builtins/memory.rs index 87e31256c39..c8656cb1362 100644 --- a/crates/vm/src/builtins/memory.rs +++ b/crates/vm/src/builtins/memory.rs @@ -61,9 +61,8 @@ pub struct PyMemoryView { impl Constructor for PyMemoryView { type Args = PyMemoryViewNewArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - let zelf = Self::from_object(&args.object, vm)?; - zelf.into_ref_with_type(vm, cls).map(Into::into) + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { + Self::from_object(&args.object, vm) } } diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 94a1e0c9ad9..735d5461186 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -32,7 +32,7 @@ impl Constructor for PyBaseObject { type Args = FuncArgs; // = object_new - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { if !args.args.is_empty() || !args.kwargs.is_empty() { // Check if type's __new__ != object.__new__ let tp_new = cls.get_attr(identifier!(vm, __new__)); @@ -109,6 +109,10 @@ impl Constructor for PyBaseObject { Ok(crate::PyRef::new_ref(Self, cls, dict).into()) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } // TODO: implement _PyType_GetSlotNames properly diff --git a/crates/vm/src/builtins/property.rs b/crates/vm/src/builtins/property.rs index a75357f7611..1a2e04ee8b0 100644 --- a/crates/vm/src/builtins/property.rs +++ b/crates/vm/src/builtins/property.rs @@ -1,7 +1,7 @@ /*! Python `property` descriptor class. */ -use super::{PyStrRef, PyType, PyTypeRef}; +use super::{PyStrRef, PyType}; use crate::common::lock::PyRwLock; use crate::function::{IntoFuncArgs, PosArgs}; use crate::{ @@ -224,7 +224,7 @@ impl PyProperty { }; // Create new property using py_new and init - let new_prop = Self::py_new(zelf.class().to_owned(), FuncArgs::default(), vm)?; + let new_prop = Self::slot_new(zelf.class().to_owned(), FuncArgs::default(), vm)?; let new_prop_ref = new_prop.downcast::<Self>().unwrap(); Self::init(new_prop_ref.clone(), args, vm)?; @@ -336,17 +336,15 @@ impl PyProperty { impl Constructor for PyProperty { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { - Self { + fn py_new(_cls: &Py<PyType>, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self { getter: PyRwLock::new(None), setter: PyRwLock::new(None), deleter: PyRwLock::new(None), doc: PyRwLock::new(None), name: PyRwLock::new(None), getter_doc: AtomicBool::new(false), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } diff --git a/crates/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs index 53ebc931a74..429fd94c2a2 100644 --- a/crates/vm/src/builtins/set.rs +++ b/crates/vm/src/builtins/set.rs @@ -12,7 +12,7 @@ use crate::{ common::{ascii, hash::PyHash, lock::PyMutex, rc::PyRc}, convert::ToPyResult, dict_inner::{self, DictSize}, - function::{ArgIterable, OptionalArg, PosArgs, PyArithmeticValue, PyComparisonValue}, + function::{ArgIterable, FuncArgs, OptionalArg, PosArgs, PyArithmeticValue, PyComparisonValue}, protocol::{PyIterReturn, PyNumberMethods, PySequenceMethods}, recursion::ReprGuard, types::AsNumber, @@ -920,7 +920,8 @@ impl Representable for PySet { impl Constructor for PyFrozenSet { type Args = OptionalArg<PyObjectRef>; - fn py_new(cls: PyTypeRef, iterable: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let iterable: Self::Args = args.bind(vm)?; let elements = if let OptionalArg::Present(iterable) = iterable { let iterable = if cls.is(vm.ctx.types.frozenset_type) { match iterable.downcast_exact::<Self>(vm) { @@ -943,6 +944,10 @@ impl Constructor for PyFrozenSet { .and_then(|o| o.into_ref_with_type(vm, cls).map(Into::into)) } } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } #[pyclass( diff --git a/crates/vm/src/builtins/singletons.rs b/crates/vm/src/builtins/singletons.rs index 7b674cb35b1..bdc032cc865 100644 --- a/crates/vm/src/builtins/singletons.rs +++ b/crates/vm/src/builtins/singletons.rs @@ -3,6 +3,7 @@ use crate::{ Context, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, class::PyClassImpl, convert::ToPyObject, + function::FuncArgs, protocol::PyNumberMethods, types::{AsNumber, Constructor, Representable}, }; @@ -38,9 +39,14 @@ impl<T: ToPyObject> ToPyObject for Option<T> { impl Constructor for PyNone { type Args = (); - fn py_new(_: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let _: () = args.bind(vm)?; Ok(vm.ctx.none.clone().into()) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("None is a singleton") + } } #[pyclass(with(Constructor, AsNumber, Representable))] @@ -87,9 +93,14 @@ impl PyPayload for PyNotImplemented { impl Constructor for PyNotImplemented { type Args = (); - fn py_new(_: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let _: () = args.bind(vm)?; Ok(vm.ctx.not_implemented.clone().into()) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("PyNotImplemented is a singleton") + } } #[pyclass(with(Constructor))] diff --git a/crates/vm/src/builtins/slice.rs b/crates/vm/src/builtins/slice.rs index bcb959ea98c..09a6c462ed5 100644 --- a/crates/vm/src/builtins/slice.rs +++ b/crates/vm/src/builtins/slice.rs @@ -313,9 +313,14 @@ impl PyPayload for PyEllipsis { impl Constructor for PyEllipsis { type Args = (); - fn py_new(_cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let _: () = args.bind(vm)?; Ok(vm.ctx.ellipsis.clone().into()) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("Ellipsis is a singleton") + } } #[pyclass(with(Constructor, Representable))] diff --git a/crates/vm/src/builtins/staticmethod.rs b/crates/vm/src/builtins/staticmethod.rs index 36aef728a3a..98717205503 100644 --- a/crates/vm/src/builtins/staticmethod.rs +++ b/crates/vm/src/builtins/staticmethod.rs @@ -43,7 +43,8 @@ impl From<PyObjectRef> for PyStaticMethod { impl Constructor for PyStaticMethod { type Args = PyObjectRef; - fn py_new(cls: PyTypeRef, callable: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let callable: Self::Args = args.bind(vm)?; let doc = callable.get_attr("__doc__", vm); let result = Self { @@ -58,6 +59,10 @@ impl Constructor for PyStaticMethod { Ok(obj) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl PyStaticMethod { diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 9fa1472c26e..86c3ab9c81f 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -350,7 +350,8 @@ pub struct StrArgs { impl Constructor for PyStr { type Args = StrArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, func_args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let args: Self::Args = func_args.bind(vm)?; let string: PyRef<PyStr> = match args.object { OptionalArg::Present(input) => { if let OptionalArg::Present(enc) = args.encoding { @@ -376,6 +377,10 @@ impl Constructor for PyStr { .map(Into::into) } } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl PyStr { diff --git a/crates/vm/src/builtins/super.rs b/crates/vm/src/builtins/super.rs index 419055b79ff..f0d873abfbb 100644 --- a/crates/vm/src/builtins/super.rs +++ b/crates/vm/src/builtins/super.rs @@ -47,16 +47,14 @@ impl PyPayload for PySuper { impl Constructor for PySuper { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { - let obj = Self { + fn py_new(_cls: &Py<PyType>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self { inner: PyRwLock::new(PySuperInner::new( vm.ctx.types.object_type.to_owned(), // is this correct? vm.ctx.none(), vm, )?), - } - .into_ref_with_type(vm, cls)?; - Ok(obj.into()) + }) } } diff --git a/crates/vm/src/builtins/traceback.rs b/crates/vm/src/builtins/traceback.rs index b5a185a8ab6..6bf4070c9b5 100644 --- a/crates/vm/src/builtins/traceback.rs +++ b/crates/vm/src/builtins/traceback.rs @@ -1,4 +1,4 @@ -use super::{PyType, PyTypeRef}; +use super::PyType; use crate::{ Context, Py, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, frame::FrameRef, types::Constructor, @@ -71,12 +71,11 @@ impl PyTraceback { impl Constructor for PyTraceback { type Args = (Option<PyRef<Self>>, FrameRef, u32, usize); - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { let (next, frame, lasti, lineno) = args; let lineno = OneIndexed::new(lineno) .ok_or_else(|| vm.new_value_error("lineno must be positive".to_owned()))?; - let tb = Self::new(next, frame, lasti, lineno); - tb.into_ref_with_type(vm, cls).map(Into::into) + Ok(Self::new(next, frame, lasti, lineno)) } } diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index f6a6dde7914..61a9cf414c4 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -8,7 +8,7 @@ use crate::{ atomic_func, class::PyClassImpl, convert::{ToPyObject, TransmuteFromObject}, - function::{ArgSize, OptionalArg, PyArithmeticValue, PyComparisonValue}, + function::{ArgSize, FuncArgs, OptionalArg, PyArithmeticValue, PyComparisonValue}, iter::PyExactSizeIterator, protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods}, recursion::ReprGuard, @@ -112,7 +112,8 @@ pub type PyTupleRef = PyRef<PyTuple>; impl Constructor for PyTuple { type Args = OptionalArg<PyObjectRef>; - fn py_new(cls: PyTypeRef, iterable: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let iterable: Self::Args = args.bind(vm)?; let elements = if let OptionalArg::Present(iterable) = iterable { let iterable = if cls.is(vm.ctx.types.tuple_type) { match iterable.downcast_exact::<Self>(vm) { @@ -137,6 +138,10 @@ impl Constructor for PyTuple { .map(Into::into) } } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl<R> AsRef<[R]> for PyTuple<R> { diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index b9c996371a0..11d74982681 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -991,7 +991,7 @@ impl PyType { impl Constructor for PyType { type Args = FuncArgs; - fn py_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + fn slot_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { vm_trace!("type.__new__ {:?}", args); let is_type_type = metatype.is(vm.ctx.types.type_type); @@ -1287,6 +1287,10 @@ impl Constructor for PyType { Ok(typ.into()) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } const SIGNATURE_END_MARKER: &str = ")\n--\n\n"; diff --git a/crates/vm/src/builtins/weakproxy.rs b/crates/vm/src/builtins/weakproxy.rs index 6f01e5eb225..a9221ec876f 100644 --- a/crates/vm/src/builtins/weakproxy.rs +++ b/crates/vm/src/builtins/weakproxy.rs @@ -38,10 +38,10 @@ impl Constructor for PyWeakProxy { type Args = WeakProxyNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { referent, callback }: Self::Args, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult<Self> { // using an internal subclass as the class prevents us from getting the generic weakref, // which would mess up the weakref count let weak_cls = WEAK_SUBCLASS.get_or_init(|| { @@ -53,11 +53,9 @@ impl Constructor for PyWeakProxy { ) }); // TODO: PyWeakProxy should use the same payload as PyWeak - Self { + Ok(Self { weak: referent.downgrade_with_typ(callback.into_option(), weak_cls.clone(), vm)?, - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } diff --git a/crates/vm/src/builtins/weakref.rs b/crates/vm/src/builtins/weakref.rs index 441cac9b3f9..93edbbecbb1 100644 --- a/crates/vm/src/builtins/weakref.rs +++ b/crates/vm/src/builtins/weakref.rs @@ -6,7 +6,7 @@ use crate::common::{ use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, class::PyClassImpl, - function::OptionalArg, + function::{FuncArgs, OptionalArg}, types::{Callable, Comparable, Constructor, Hashable, PyComparisonOp, Representable}, }; @@ -38,14 +38,15 @@ impl Callable for PyWeak { impl Constructor for PyWeak { type Args = WeakNewArgs; - fn py_new( - cls: PyTypeRef, - Self::Args { referent, callback }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let Self::Args { referent, callback } = args.bind(vm)?; let weak = referent.downgrade_with_typ(callback.into_option(), cls, vm)?; Ok(weak.into()) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } #[pyclass( diff --git a/crates/vm/src/builtins/zip.rs b/crates/vm/src/builtins/zip.rs index 98371d2f6c3..ee3ecb57b27 100644 --- a/crates/vm/src/builtins/zip.rs +++ b/crates/vm/src/builtins/zip.rs @@ -1,4 +1,4 @@ -use super::{PyType, PyTypeRef}; +use super::PyType; use crate::{ AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::PyTupleRef, @@ -33,12 +33,14 @@ pub struct PyZipNewArgs { impl Constructor for PyZip { type Args = (PosArgs<PyIter>, PyZipNewArgs); - fn py_new(cls: PyTypeRef, (iterators, args): Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new( + _cls: &Py<PyType>, + (iterators, args): Self::Args, + _vm: &VirtualMachine, + ) -> PyResult<Self> { let iterators = iterators.into_vec(); let strict = Radium::new(args.strict.unwrap_or(false)); - Self { iterators, strict } - .into_ref_with_type(vm, cls) - .map(Into::into) + Ok(Self { iterators, strict }) } } diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 9d3d239ad46..a5bc3f0a71b 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -707,7 +707,7 @@ impl PyRef<PyBaseException> { impl Constructor for PyBaseException { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { if cls.is(Self::class(&vm.ctx)) && !args.kwargs.is_empty() { return Err(vm.new_type_error("BaseException() takes no keyword arguments")); } @@ -715,6 +715,10 @@ impl Constructor for PyBaseException { .into_ref_with_type(vm, cls) .map(Into::into) } + + fn py_new(_cls: &Py<PyType>, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl Initializer for PyBaseException { diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index 0e8d7cf5fa7..744eba9c555 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -4,8 +4,8 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ - PyAsyncGen, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple, - PyTupleRef, PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr, + PyAsyncGen, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple, PyTupleRef, + PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr, }, bytes_inner::ByteInnerNewOptions, common::{hash::PyHash, str::to_ascii}, @@ -14,7 +14,7 @@ use crate::{ function::{Either, OptionalArg, PyArithmeticValue, PySetterValue}, object::PyPayload, protocol::{PyIter, PyMapping, PySequence}, - types::{Constructor, PyComparisonOp}, + types::PyComparisonOp, }; // RustPython doesn't need these items @@ -36,15 +36,14 @@ impl PyObjectRef { let bytes_type = vm.ctx.types.bytes_type; match self.downcast_exact::<PyInt>(vm) { Ok(int) => Err(vm.new_downcast_type_error(bytes_type, &int)), - Err(obj) => PyBytes::py_new( - bytes_type.to_owned(), - ByteInnerNewOptions { + Err(obj) => { + let options = ByteInnerNewOptions { source: OptionalArg::Present(obj), encoding: OptionalArg::Missing, errors: OptionalArg::Missing, - }, - vm, - ), + }; + options.get_bytes(bytes_type.to_owned(), vm).map(Into::into) + } } } diff --git a/crates/vm/src/stdlib/ast/python.rs b/crates/vm/src/stdlib/ast/python.rs index 40ab3ce72dd..ef8a85f85f2 100644 --- a/crates/vm/src/stdlib/ast/python.rs +++ b/crates/vm/src/stdlib/ast/python.rs @@ -3,8 +3,8 @@ use super::{PY_CF_OPTIMIZED_AST, PY_CF_TYPE_COMMENTS, PY_COMPILE_FLAG_AST_ONLY}; #[pymodule] pub(crate) mod _ast { use crate::{ - AsObject, Context, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyStrRef, PyTupleRef, PyTypeRef}, + AsObject, Context, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, + builtins::{PyStrRef, PyTupleRef, PyType, PyTypeRef}, function::FuncArgs, types::Constructor, }; @@ -76,8 +76,8 @@ pub(crate) mod _ast { Ok(zelf) } - fn py_new(_cls: PyTypeRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult { - unreachable!("slow_new is implemented"); + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") } } diff --git a/crates/vm/src/stdlib/collections.rs b/crates/vm/src/stdlib/collections.rs index 651a470bfa3..32596b65386 100644 --- a/crates/vm/src/stdlib/collections.rs +++ b/crates/vm/src/stdlib/collections.rs @@ -7,7 +7,7 @@ mod _collections { atomic_func, builtins::{ IterStatus::{Active, Exhausted}, - PositionIterInternal, PyGenericAlias, PyInt, PyTypeRef, + PositionIterInternal, PyGenericAlias, PyInt, PyType, PyTypeRef, }, common::lock::{PyMutex, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}, function::{KwArgs, OptionalArg, PyComparisonValue}, @@ -606,16 +606,16 @@ mod _collections { type Args = (DequeIterArgs, KwArgs); fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, (DequeIterArgs { deque, index }, _kwargs): Self::Args, - vm: &VirtualMachine, - ) -> PyResult { + _vm: &VirtualMachine, + ) -> PyResult<Self> { let iter = Self::new(deque); if let OptionalArg::Present(index) = index { let index = max(index, 0) as usize; iter.internal.lock().position = index; } - iter.into_ref_with_type(vm, cls).map(Into::into) + Ok(iter) } } @@ -678,17 +678,16 @@ mod _collections { type Args = (DequeIterArgs, KwArgs); fn py_new( - cls: PyTypeRef, - + _cls: &Py<PyType>, (DequeIterArgs { deque, index }, _kwargs): Self::Args, - vm: &VirtualMachine, - ) -> PyResult { + _vm: &VirtualMachine, + ) -> PyResult<Self> { let iter = PyDeque::__reversed__(deque)?; if let OptionalArg::Present(index) = index { let index = max(index, 0) as usize; iter.internal.lock().position = index; } - iter.into_ref_with_type(vm, cls).map(Into::into) + Ok(iter) } } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 62d714329e6..d87679f3595 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -75,8 +75,8 @@ impl Callable for PyCArrayType { impl Constructor for PyCArrayType { type Args = PyObjectRef; - fn py_new(_cls: PyTypeRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult { - unreachable!() + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") } } @@ -249,7 +249,7 @@ impl std::fmt::Debug for PyCArray { impl Constructor for PyCArray { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { // Get _type_ and _length_ from the class let type_attr = cls.as_object().get_attr("_type_", vm).ok(); let length_attr = cls.as_object().get_attr("_length_", vm).ok(); @@ -287,7 +287,7 @@ impl Constructor for PyCArray { } let offset = i * element_size; if let Ok(int_val) = value.try_int(vm) { - let bytes = Self::int_to_bytes(int_val.as_bigint(), element_size); + let bytes = PyCArray::int_to_bytes(int_val.as_bigint(), element_size); if offset + element_size <= buffer.len() { buffer[offset..offset + element_size].copy_from_slice(&bytes); } @@ -303,6 +303,10 @@ impl Constructor for PyCArray { .into_ref_with_type(vm, cls) .map(Into::into) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl AsSequence for PyCArray { diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 5e2deeb5607..6b03e0e2348 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -3,7 +3,7 @@ use super::array::PyCArrayType; use super::util::StgInfo; use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyStrRef, PyType, PyTypeRef}; use crate::convert::ToPyObject; -use crate::function::{ArgBytesLike, Either, OptionalArg}; +use crate::function::{ArgBytesLike, Either, FuncArgs, KwArgs, OptionalArg}; use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; use crate::stdlib::ctypes::_ctypes::new_simple_type; use crate::types::{AsBuffer, AsNumber, Constructor}; @@ -610,7 +610,8 @@ fn value_to_bytes_endian( impl Constructor for PyCSimple { type Args = (OptionalArg,); - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let args: Self::Args = args.bind(vm)?; let attributes = cls.get_attributes(); let _type_ = attributes .iter() @@ -659,6 +660,10 @@ impl Constructor for PyCSimple { .into_ref_with_type(vm, cls) .map(Into::into) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } #[pyclass(flags(BASETYPE), with(Constructor, AsBuffer))] @@ -796,8 +801,8 @@ impl PyCSimple { }; // Create instance using the type's constructor - let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?; - Ok(instance) + let args = FuncArgs::new(vec![value], KwArgs::default()); + PyCSimple::slot_new(cls.clone(), args, vm) } #[pyclassmethod] @@ -846,7 +851,8 @@ impl PyCSimple { let value = bytes_to_pyobject(&cls, data, vm)?; // Create instance - let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?; + let args = FuncArgs::new(vec![value], KwArgs::default()); + let instance = PyCSimple::slot_new(cls.clone(), args, vm)?; // TODO: Store reference to source in _objects to keep buffer alive Ok(instance) @@ -892,7 +898,8 @@ impl PyCSimple { let value = bytes_to_pyobject(&cls, data, vm)?; // Create instance (independent copy, no reference tracking) - PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm) + let args = FuncArgs::new(vec![value], KwArgs::default()); + PyCSimple::slot_new(cls.clone(), args, vm) } #[pyclassmethod] @@ -959,7 +966,8 @@ impl PyCSimple { }; // Create instance - let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?; + let args = FuncArgs::new(vec![value], KwArgs::default()); + let instance = PyCSimple::slot_new(cls.clone(), args, vm)?; // Store base reference to keep dll alive if let Ok(simple_ref) = instance.clone().downcast::<PyCSimple>() { diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 27e85c563ef..8784fd55ef5 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -186,14 +186,14 @@ impl Debug for PyCFuncPtr { impl Constructor for PyCFuncPtr { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { // Handle different argument forms like CPython: // 1. Empty args: create uninitialized // 2. One integer argument: function address // 3. Tuple argument: (name, dll) form if args.args.is_empty() { - return Self { + return PyCFuncPtr { ptr: PyRwLock::new(None), needs_free: AtomicCell::new(false), arg_types: PyRwLock::new(None), @@ -211,7 +211,7 @@ impl Constructor for PyCFuncPtr { // Check if first argument is an integer (function address) if let Ok(addr) = first_arg.try_int(vm) { let ptr_val = addr.as_bigint().to_usize().unwrap_or(0); - return Self { + return PyCFuncPtr { ptr: PyRwLock::new(Some(CodePtr(ptr_val as *mut _))), needs_free: AtomicCell::new(false), arg_types: PyRwLock::new(None), @@ -270,7 +270,7 @@ impl Constructor for PyCFuncPtr { None }; - return Self { + return PyCFuncPtr { ptr: PyRwLock::new(code_ptr), needs_free: AtomicCell::new(false), arg_types: PyRwLock::new(None), @@ -313,7 +313,7 @@ impl Constructor for PyCFuncPtr { // Store the thunk as a Python object to keep it alive let thunk_ref: PyRef<PyCThunk> = thunk.into_ref(&vm.ctx); - return Self { + return PyCFuncPtr { ptr: PyRwLock::new(Some(code_ptr)), needs_free: AtomicCell::new(true), arg_types: PyRwLock::new(arg_type_vec), @@ -328,6 +328,10 @@ impl Constructor for PyCFuncPtr { Err(vm.new_type_error("Expected an integer address or a tuple")) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl Callable for PyCFuncPtr { diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index cd9ba8a7a15..d7fbd67b157 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -4,10 +4,11 @@ use rustpython_common::lock::PyRwLock; use crate::builtins::{PyType, PyTypeRef}; use crate::convert::ToPyObject; +use crate::function::FuncArgs; use crate::protocol::PyNumberMethods; use crate::stdlib::ctypes::PyCData; use crate::types::{AsNumber, Constructor}; -use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use super::util::StgInfo; @@ -75,7 +76,8 @@ pub struct PyCPointer { impl Constructor for PyCPointer { type Args = (crate::function::OptionalArg<PyObjectRef>,); - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let args: Self::Args = args.bind(vm)?; // Get the initial contents value if provided let initial_contents = args.0.into_option().unwrap_or_else(|| vm.ctx.none()); @@ -86,6 +88,10 @@ impl Constructor for PyCPointer { .into_ref_with_type(vm, cls) .map(Into::into) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } #[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 1c842e21b5c..58acc26d4ce 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -25,9 +25,9 @@ pub struct PyCStructType { impl Constructor for PyCStructType { type Args = FuncArgs; - fn py_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + fn slot_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { // 1. Create the new class using PyType::py_new - let new_class = crate::builtins::type_::PyType::py_new(metatype, args, vm)?; + let new_class = crate::builtins::type_::PyType::slot_new(metatype, args, vm)?; // 2. Process _fields_ if defined on the new class let new_type = new_class @@ -42,6 +42,10 @@ impl Constructor for PyCStructType { Ok(new_class) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } #[pyclass(flags(BASETYPE), with(AsNumber, Constructor))] @@ -239,7 +243,7 @@ impl Debug for PyCStructure { impl Constructor for PyCStructure { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { // Get _fields_ from the class using get_attr to properly search MRO let fields_attr = cls.as_object().get_attr("_fields_", vm).ok(); @@ -326,6 +330,10 @@ impl Constructor for PyCStructure { Ok(py_instance.into()) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } // Note: GetAttr and SetAttr are not implemented here. diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index aa78d56c46b..d260cfc9ecf 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -29,9 +29,9 @@ impl Default for PyCUnionType { impl Constructor for PyCUnionType { type Args = FuncArgs; - fn py_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + fn slot_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { // 1. Create the new class using PyType::py_new - let new_class = crate::builtins::type_::PyType::py_new(metatype, args, vm)?; + let new_class = crate::builtins::type_::PyType::slot_new(metatype, args, vm)?; // 2. Process _fields_ if defined on the new class let new_type = new_class @@ -46,6 +46,10 @@ impl Constructor for PyCUnionType { Ok(new_class) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl PyCUnionType { @@ -144,7 +148,7 @@ impl std::fmt::Debug for PyCUnion { impl Constructor for PyCUnion { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { // Get _fields_ from the class let fields_attr = cls.as_object().get_attr("_fields_", vm).ok(); @@ -185,6 +189,10 @@ impl Constructor for PyCUnion { .into_ref_with_type(vm, cls) .map(Into::into) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } #[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsBuffer))] diff --git a/crates/vm/src/stdlib/functools.rs b/crates/vm/src/stdlib/functools.rs index 21724db8922..d5a42739e96 100644 --- a/crates/vm/src/stdlib/functools.rs +++ b/crates/vm/src/stdlib/functools.rs @@ -204,7 +204,11 @@ mod _functools { impl Constructor for PyPartial { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new( + _cls: &crate::Py<crate::builtins::PyType>, + args: Self::Args, + vm: &VirtualMachine, + ) -> PyResult<Self> { let (func, args_slice) = args .args .split_first() @@ -230,15 +234,13 @@ mod _functools { final_keywords.set_item(vm.ctx.intern_str(key.as_str()), value, vm)?; } - let partial = Self { + Ok(Self { inner: PyRwLock::new(PyPartialInner { func: final_func, args: vm.ctx.new_tuple(final_args), keywords: final_keywords, }), - }; - - partial.into_ref_with_type(vm, cls).map(Into::into) + }) } } diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index c373f88d20b..c54f3b853dc 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -3637,20 +3637,18 @@ mod _io { #[allow(unused_variables)] fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { object, newline }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { + _vm: &VirtualMachine, + ) -> PyResult<Self> { let raw_bytes = object .flatten() .map_or_else(Vec::new, |v| v.as_bytes().to_vec()); - Self { + Ok(Self { buffer: PyRwLock::new(BufferedIO::new(Cursor::new(raw_bytes))), closed: AtomicCell::new(false), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -3770,18 +3768,16 @@ mod _io { impl Constructor for BytesIO { type Args = OptionalArg<Option<PyBytesRef>>; - fn py_new(cls: PyTypeRef, object: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, object: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { let raw_bytes = object .flatten() .map_or_else(Vec::new, |input| input.as_bytes().to_vec()); - Self { + Ok(Self { buffer: PyRwLock::new(BufferedIO::new(Cursor::new(raw_bytes))), closed: AtomicCell::new(false), exports: AtomicCell::new(0), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } diff --git a/crates/vm/src/stdlib/itertools.rs b/crates/vm/src/stdlib/itertools.rs index ba629a88770..2956654185d 100644 --- a/crates/vm/src/stdlib/itertools.rs +++ b/crates/vm/src/stdlib/itertools.rs @@ -7,7 +7,7 @@ mod decl { AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, PyWeakRef, TryFromObject, VirtualMachine, builtins::{ - PyGenericAlias, PyInt, PyIntRef, PyList, PyTuple, PyTupleRef, PyTypeRef, int, + PyGenericAlias, PyInt, PyIntRef, PyList, PyTuple, PyTupleRef, PyType, PyTypeRef, int, tuple::IntoPyTuple, }, common::{ @@ -201,13 +201,11 @@ mod decl { type Args = CompressNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { data, selectors }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { - Self { data, selectors } - .into_ref_with_type(vm, cls) - .map(Into::into) + _vm: &VirtualMachine, + ) -> PyResult<Self> { + Ok(Self { data, selectors }) } } @@ -260,22 +258,20 @@ mod decl { type Args = CountNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { start, step }: Self::Args, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult<Self> { let start = start.into_option().unwrap_or_else(|| vm.new_pyobj(0)); let step = step.into_option().unwrap_or_else(|| vm.new_pyobj(1)); if !PyNumber::check(&start) || !PyNumber::check(&step) { return Err(vm.new_type_error("a number is required")); } - Self { + Ok(Self { cur: PyRwLock::new(start), step, - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -327,14 +323,12 @@ mod decl { impl Constructor for PyItertoolsCycle { type Args = PyIter; - fn py_new(cls: PyTypeRef, iter: Self::Args, vm: &VirtualMachine) -> PyResult { - Self { + fn py_new(_cls: &Py<PyType>, iter: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self { iter, saved: PyRwLock::new(Vec::new()), index: AtomicCell::new(0), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -386,10 +380,10 @@ mod decl { type Args = PyRepeatNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { object, times }: Self::Args, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult<Self> { let times = match times.into_option() { Some(int) => { let val: isize = int.try_to_primitive(vm)?; @@ -398,9 +392,7 @@ mod decl { } None => None, }; - Self { object, times } - .into_ref_with_type(vm, cls) - .map(Into::into) + Ok(Self { object, times }) } } @@ -474,13 +466,11 @@ mod decl { type Args = StarmapNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { function, iterable }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { - Self { function, iterable } - .into_ref_with_type(vm, cls) - .map(Into::into) + _vm: &VirtualMachine, + ) -> PyResult<Self> { + Ok(Self { function, iterable }) } } @@ -536,20 +526,18 @@ mod decl { type Args = TakewhileNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { predicate, iterable, }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { - Self { + _vm: &VirtualMachine, + ) -> PyResult<Self> { + Ok(Self { predicate, iterable, stop_flag: AtomicCell::new(false), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -624,20 +612,18 @@ mod decl { type Args = DropwhileNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { predicate, iterable, }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { - Self { + _vm: &VirtualMachine, + ) -> PyResult<Self> { + Ok(Self { predicate, iterable, start_flag: AtomicCell::new(false), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -748,17 +734,15 @@ mod decl { type Args = GroupByArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { iterable, key }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { - Self { + _vm: &VirtualMachine, + ) -> PyResult<Self> { + Ok(Self { iterable, key_func: key.flatten(), state: PyMutex::new(GroupByState::default()), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -1039,19 +1023,17 @@ mod decl { type Args = FilterFalseNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { predicate, iterable, }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { - Self { + _vm: &VirtualMachine, + ) -> PyResult<Self> { + Ok(Self { predicate, iterable, - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -1114,15 +1096,13 @@ mod decl { impl Constructor for PyItertoolsAccumulate { type Args = AccumulateArgs; - fn py_new(cls: PyTypeRef, args: AccumulateArgs, vm: &VirtualMachine) -> PyResult { - Self { + fn py_new(_cls: &Py<PyType>, args: AccumulateArgs, _vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self { iterable: args.iterable, bin_op: args.func.flatten(), initial: args.initial.flatten(), acc_value: PyRwLock::new(None), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -1257,11 +1237,8 @@ mod decl { // TODO: make tee() a function, rename this class to itertools._tee and make // teedata a python class - fn py_new( - _cls: PyTypeRef, - Self::Args { iterable, n }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { + fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let TeeNewArgs { iterable, n } = args.bind(vm)?; let n = n.unwrap_or(2); let copyable = if iterable.class().has_attr(identifier!(vm, __copy__)) { @@ -1277,6 +1254,10 @@ mod decl { Ok(PyTuple::new_ref(tee_vec, &vm.ctx).into()) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } #[pyclass(with(IterNext, Iterable, Constructor))] @@ -1330,7 +1311,11 @@ mod decl { impl Constructor for PyItertoolsProduct { type Args = (PosArgs<PyObjectRef>, ProductArgs); - fn py_new(cls: PyTypeRef, (iterables, args): Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new( + _cls: &Py<PyType>, + (iterables, args): Self::Args, + vm: &VirtualMachine, + ) -> PyResult<Self> { let repeat = args.repeat.unwrap_or(1); let mut pools = Vec::new(); for arg in iterables.iter() { @@ -1342,14 +1327,12 @@ mod decl { let l = pools.len(); - Self { + Ok(Self { pools, idxs: PyRwLock::new(vec![0; l]), cur: AtomicCell::new(l.wrapping_sub(1)), stop: AtomicCell::new(false), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -1485,10 +1468,10 @@ mod decl { type Args = CombinationsNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { iterable, r }: Self::Args, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult<Self> { let pool: Vec<_> = iterable.try_to_value(vm)?; let r = r.as_bigint(); @@ -1499,15 +1482,13 @@ mod decl { let n = pool.len(); - Self { + Ok(Self { pool, indices: PyRwLock::new((0..r).collect()), result: PyRwLock::new(None), r: AtomicCell::new(r), exhausted: AtomicCell::new(r > n), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -1618,10 +1599,10 @@ mod decl { type Args = CombinationsNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { iterable, r }: Self::Args, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult<Self> { let pool: Vec<_> = iterable.try_to_value(vm)?; let r = r.as_bigint(); if r.is_negative() { @@ -1631,14 +1612,12 @@ mod decl { let n = pool.len(); - Self { + Ok(Self { pool, indices: PyRwLock::new(vec![0; r]), r: AtomicCell::new(r), exhausted: AtomicCell::new(n == 0 && r > 0), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -1716,10 +1695,10 @@ mod decl { type Args = PermutationsNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { iterable, r }: Self::Args, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult<Self> { let pool: Vec<_> = iterable.try_to_value(vm)?; let n = pool.len(); @@ -1740,16 +1719,14 @@ mod decl { None => n, }; - Self { + Ok(Self { pool, indices: PyRwLock::new((0..n).collect()), cycles: PyRwLock::new((0..r.min(n)).map(|i| n - i).collect()), result: PyRwLock::new(None), r: AtomicCell::new(r), exhausted: AtomicCell::new(r > n), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -1847,15 +1824,17 @@ mod decl { impl Constructor for PyItertoolsZipLongest { type Args = (PosArgs<PyIter>, ZipLongestArgs); - fn py_new(cls: PyTypeRef, (iterators, args): Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new( + _cls: &Py<PyType>, + (iterators, args): Self::Args, + vm: &VirtualMachine, + ) -> PyResult<Self> { let fillvalue = args.fillvalue.unwrap_or_none(vm); let iterators = iterators.into_vec(); - Self { + Ok(Self { iterators, fillvalue: PyRwLock::new(fillvalue), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -1933,13 +1912,11 @@ mod decl { impl Constructor for PyItertoolsPairwise { type Args = PyIter; - fn py_new(cls: PyTypeRef, iterator: Self::Args, vm: &VirtualMachine) -> PyResult { - Self { + fn py_new(_cls: &Py<PyType>, iterator: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self { iterator, old: PyRwLock::new(None), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -1997,14 +1974,14 @@ mod decl { type Args = BatchedNewArgs; fn py_new( - cls: PyTypeRef, + _cls: &Py<PyType>, Self::Args { iterable_ref, n, strict, }: Self::Args, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult<Self> { let n = n.as_bigint(); if n.lt(&BigInt::one()) { return Err(vm.new_value_error("n must be at least one")); @@ -2014,14 +1991,12 @@ mod decl { .ok_or(vm.new_overflow_error("Python int too large to convert to usize"))?; let iterable = iterable_ref.get_iter(vm)?; - Self { + Ok(Self { iterable, n: AtomicCell::new(n), exhausted: AtomicCell::new(false), strict: AtomicCell::new(strict), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } diff --git a/crates/vm/src/stdlib/operator.rs b/crates/vm/src/stdlib/operator.rs index f13c00ac1de..d61adaea561 100644 --- a/crates/vm/src/stdlib/operator.rs +++ b/crates/vm/src/stdlib/operator.rs @@ -4,7 +4,7 @@ pub(crate) use _operator::make_module; mod _operator { use crate::{ AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyInt, PyIntRef, PyStr, PyStrRef, PyTupleRef, PyTypeRef}, + builtins::{PyInt, PyIntRef, PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef}, common::wtf8::Wtf8, function::{ArgBytesLike, Either, FuncArgs, KwArgs, OptionalArg}, identifier, @@ -388,7 +388,7 @@ mod _operator { impl Constructor for PyAttrGetter { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { let n_attr = args.args.len(); // Check we get no keyword and at least one positional. if !args.kwargs.is_empty() { @@ -405,7 +405,7 @@ mod _operator { return Err(vm.new_type_error("attribute name must be a string")); } } - Self { attrs }.into_ref_with_type(vm, cls).map(Into::into) + Ok(Self { attrs }) } } @@ -464,7 +464,7 @@ mod _operator { impl Constructor for PyItemGetter { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { // Check we get no keyword and at least one positional. if !args.kwargs.is_empty() { return Err(vm.new_type_error("itemgetter() takes no keyword arguments")); @@ -472,9 +472,7 @@ mod _operator { if args.args.is_empty() { return Err(vm.new_type_error("itemgetter expected 1 argument, got 0.")); } - Self { items: args.args } - .into_ref_with_type(vm, cls) - .map(Into::into) + Ok(Self { items: args.args }) } } @@ -550,11 +548,13 @@ mod _operator { impl Constructor for PyMethodCaller { type Args = (PyObjectRef, FuncArgs); - fn py_new(cls: PyTypeRef, (name, args): Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new( + _cls: &Py<PyType>, + (name, args): Self::Args, + vm: &VirtualMachine, + ) -> PyResult<Self> { if let Ok(name) = name.try_into_value(vm) { - Self { name, args } - .into_ref_with_type(vm, cls) - .map(Into::into) + Ok(Self { name, args }) } else { Err(vm.new_type_error("method name must be a string")) } diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 6a6703f48a5..680e9914a03 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -24,7 +24,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { pub mod module { use crate::{ AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyDictRef, PyInt, PyListRef, PyStrRef, PyTupleRef, PyTypeRef, PyUtf8StrRef}, + builtins::{PyDictRef, PyInt, PyListRef, PyStrRef, PyTupleRef, PyType, PyUtf8StrRef}, convert::{IntoPyException, ToPyObject, TryFromObject}, function::{Either, KwArgs, OptionalArg}, ospath::{IOErrorBuilder, OsPath, OsPathOrFd}, @@ -832,12 +832,10 @@ pub mod module { impl Constructor for SchedParam { type Args = SchedParamArg; - fn py_new(cls: PyTypeRef, arg: Self::Args, vm: &VirtualMachine) -> PyResult { - Self { + fn py_new(_cls: &Py<PyType>, arg: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self { sched_priority: arg.sched_priority, - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } diff --git a/crates/vm/src/stdlib/thread.rs b/crates/vm/src/stdlib/thread.rs index 9e494456538..36252279397 100644 --- a/crates/vm/src/stdlib/thread.rs +++ b/crates/vm/src/stdlib/thread.rs @@ -6,7 +6,7 @@ pub(crate) use _thread::{RawRMutex, make_module}; pub(crate) mod _thread { use crate::{ AsObject, Py, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyDictRef, PyStr, PyTupleRef, PyTypeRef}, + builtins::{PyDictRef, PyStr, PyTupleRef, PyType, PyTypeRef}, convert::ToPyException, function::{ArgCallable, Either, FuncArgs, KwArgs, OptionalArg, PySetterValue}, types::{Constructor, GetAttr, Representable, SetAttr}, @@ -171,7 +171,8 @@ pub(crate) mod _thread { impl Constructor for Lock { type Args = FuncArgs; - fn py_new(_cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { Err(vm.new_type_error("cannot create '_thread.lock' instances")) } } diff --git a/crates/vm/src/stdlib/typevar.rs b/crates/vm/src/stdlib/typevar.rs index 4d56fb3ce3c..5509e547072 100644 --- a/crates/vm/src/stdlib/typevar.rs +++ b/crates/vm/src/stdlib/typevar.rs @@ -1,7 +1,7 @@ // spell-checker:ignore typevarobject funcobj use crate::{ - AsObject, Context, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr}, + AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + builtins::{PyTupleRef, PyType, PyTypeRef, pystr::AsPyStr}, common::lock::PyMutex, function::{FuncArgs, IntoFuncArgs, PyComparisonValue}, protocol::PyNumberMethods, @@ -261,7 +261,15 @@ impl AsNumber for TypeVar { impl Constructor for TypeVar { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let typevar = <Self as Constructor>::py_new(&cls, args, vm)?; + let obj = typevar.into_ref_with_type(vm, cls)?; + let obj_ref: PyObjectRef = obj.into(); + set_module_from_caller(&obj_ref, vm)?; + Ok(obj_ref) + } + + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { let mut kwargs = args.kwargs; // Parse arguments manually let (name, constraints) = if args.args.is_empty() { @@ -353,7 +361,7 @@ impl Constructor for TypeVar { (vm.ctx.typing_no_default.clone().into(), vm.ctx.none()) }; - let typevar = Self { + Ok(Self { name, bound: parking_lot::Mutex::new(bound_obj), evaluate_bound, @@ -364,12 +372,7 @@ impl Constructor for TypeVar { covariant, contravariant, infer_variance, - }; - - let obj = typevar.into_ref_with_type(vm, cls)?; - let obj_ref: PyObjectRef = obj.into(); - set_module_from_caller(&obj_ref, vm)?; - Ok(obj_ref) + }) } } @@ -533,7 +536,7 @@ impl AsNumber for ParamSpec { impl Constructor for ParamSpec { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { let mut kwargs = args.kwargs; // Parse arguments manually let name = if args.args.is_empty() { @@ -605,6 +608,10 @@ impl Constructor for ParamSpec { set_module_from_caller(&obj_ref, vm)?; Ok(obj_ref) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl Representable for ParamSpec { @@ -715,7 +722,7 @@ impl Iterable for TypeVarTuple { impl Constructor for TypeVarTuple { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { let mut kwargs = args.kwargs; // Parse arguments manually let name = if args.args.is_empty() { @@ -763,6 +770,10 @@ impl Constructor for TypeVarTuple { set_module_from_caller(&obj_ref, vm)?; Ok(obj_ref) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("use slot_new") + } } impl Representable for TypeVarTuple { @@ -805,10 +816,9 @@ impl ParamSpecArgs { impl Constructor for ParamSpecArgs { type Args = (PyObjectRef,); - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { let origin = args.0; - let psa = Self { __origin__: origin }; - psa.into_ref_with_type(vm, cls).map(Into::into) + Ok(Self { __origin__: origin }) } } @@ -884,10 +894,9 @@ impl ParamSpecKwargs { impl Constructor for ParamSpecKwargs { type Args = (PyObjectRef,); - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { let origin = args.0; - let psa = Self { __origin__: origin }; - psa.into_ref_with_type(vm, cls).map(Into::into) + Ok(Self { __origin__: origin }) } } diff --git a/crates/vm/src/stdlib/typing.rs b/crates/vm/src/stdlib/typing.rs index c014266935c..afa8bd6eb90 100644 --- a/crates/vm/src/stdlib/typing.rs +++ b/crates/vm/src/stdlib/typing.rs @@ -31,7 +31,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { pub(crate) mod decl { use crate::{ Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyStrRef, PyTupleRef, PyTypeRef, pystr::AsPyStr}, + builtins::{PyStrRef, PyTupleRef, PyType, PyTypeRef, pystr::AsPyStr}, function::{FuncArgs, IntoFuncArgs}, types::{Constructor, Representable}, }; @@ -74,16 +74,16 @@ pub(crate) mod decl { } impl Constructor for NoDefault { - type Args = FuncArgs; - - fn py_new(_cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - if !args.args.is_empty() || !args.kwargs.is_empty() { - return Err(vm.new_type_error("NoDefaultType takes no arguments")); - } + type Args = (); - // Return singleton instance from context + fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let _: () = args.bind(vm)?; Ok(vm.ctx.typing_no_default.clone().into()) } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unreachable!("NoDefault is a singleton, use slot_new") + } } impl Representable for NoDefault { @@ -133,7 +133,7 @@ pub(crate) mod decl { impl Constructor for TypeAliasType { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { // TypeAliasType(name, value, *, type_params=None) if args.args.len() < 2 { return Err(vm.new_type_error(format!( @@ -168,8 +168,7 @@ pub(crate) mod decl { vm.ctx.empty_tuple.clone() }; - let ta = Self::new(name, type_params, value); - ta.into_ref_with_type(vm, cls).map(Into::into) + Ok(Self::new(name, type_params, value)) } } diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 834b3b5cb45..e3775058d44 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -779,18 +779,45 @@ impl PyType { } } +/// Trait for types that can be constructed via Python's `__new__` method. +/// +/// `slot_new` corresponds to the `__new__` type slot. +/// +/// In most cases, `__new__` simply initializes the payload and assigns a type, +/// so you only need to override `py_new`. The default `slot_new` implementation +/// will call `py_new` and then wrap the result with `into_ref_with_type`. +/// +/// However, if a subtype requires more than just payload initialization +/// (e.g., returning an existing object for optimization, setting attributes +/// after creation, or special handling of the class type), you should override +/// `slot_new` directly instead of `py_new`. +/// +/// # When to use `py_new` only (most common case): +/// - Simple payload initialization that just creates `Self` +/// - The type doesn't need special handling for subtypes +/// +/// # When to override `slot_new`: +/// - Returning existing objects (e.g., `PyInt`, `PyStr`, `PyBool` for optimization) +/// - Setting attributes or dict entries after object creation +/// - Special class type handling (e.g., `PyType` and its metaclasses) +/// - Post-creation mutations that require `PyRef` #[pyclass] -pub trait Constructor: PyPayload { +pub trait Constructor: PyPayload + std::fmt::Debug { type Args: FromArgs; + /// The type slot for `__new__`. Override this only when you need special + /// behavior beyond simple payload creation. #[inline] #[pyslot] fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { let args: Self::Args = args.bind(vm)?; - Self::py_new(cls, args, vm) + let payload = Self::py_new(&cls, args, vm)?; + payload.into_ref_with_type(vm, cls).map(Into::into) } - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult; + /// Creates the payload for this type. In most cases, just implement this method + /// and let the default `slot_new` handle wrapping with the correct type. + fn py_new(cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self>; } pub trait DefaultConstructor: PyPayload + Default + std::fmt::Debug { @@ -823,7 +850,7 @@ where Self::default().into_ref_with_type(vm, cls).map(Into::into) } - fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(cls: &Py<PyType>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { Err(vm.new_type_error(format!("cannot create {} instances", cls.slot_name()))) } } From a4778359702f1d5f9624c074247e559318d0779e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 11 Dec 2025 23:17:10 +0900 Subject: [PATCH 472/819] py_new separation for set/tuple/bytes/str --- crates/vm/src/builtins/bytes.rs | 59 +++++++++++++++++++++++++++++--- crates/vm/src/builtins/set.rs | 44 ++++++++++++++---------- crates/vm/src/builtins/str.rs | 39 +++++++++++---------- crates/vm/src/builtins/tuple.rs | 48 +++++++++++++++----------- crates/vm/src/bytes_inner.rs | 42 ++--------------------- crates/vm/src/protocol/object.rs | 17 ++++----- 6 files changed, 137 insertions(+), 112 deletions(-) diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index 3da1b68ef07..70a33401271 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -92,15 +92,64 @@ pub(crate) fn init(context: &Context) { } impl Constructor for PyBytes { - type Args = ByteInnerNewOptions; + type Args = Vec<u8>; fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - let options: Self::Args = args.bind(vm)?; - options.get_bytes(cls, vm).to_pyresult(vm) + let options: ByteInnerNewOptions = args.bind(vm)?; + + // Optimizations for exact bytes type + if cls.is(vm.ctx.types.bytes_type) { + // Return empty bytes singleton + if options.source.is_missing() + && options.encoding.is_missing() + && options.errors.is_missing() + { + return Ok(vm.ctx.empty_bytes.clone().into()); + } + + // Return exact bytes as-is + if let OptionalArg::Present(ref obj) = options.source + && options.encoding.is_missing() + && options.errors.is_missing() + && let Ok(b) = obj.clone().downcast_exact::<PyBytes>(vm) + { + return Ok(b.into_pyref().into()); + } + } + + // Handle __bytes__ method - may return PyBytes directly + if let OptionalArg::Present(ref obj) = options.source + && options.encoding.is_missing() + && options.errors.is_missing() + && let Some(bytes_method) = vm.get_method(obj.clone(), identifier!(vm, __bytes__)) + { + let bytes = bytes_method?.call((), vm)?; + // If exact bytes type and __bytes__ returns bytes, use it directly + if cls.is(vm.ctx.types.bytes_type) + && let Ok(b) = bytes.clone().downcast::<PyBytes>() + { + return Ok(b.into()); + } + // Otherwise convert to Vec<u8> + let inner = PyBytesInner::try_from_borrowed_object(vm, &bytes)?; + let payload = Self::py_new(&cls, inner.elements, vm)?; + return payload.into_ref_with_type(vm, cls).map(Into::into); + } + + // Fallback to get_bytearray_inner + let elements = options.get_bytearray_inner(vm)?.elements; + + // Return empty bytes singleton for exact bytes types + if elements.is_empty() && cls.is(vm.ctx.types.bytes_type) { + return Ok(vm.ctx.empty_bytes.clone().into()); + } + + let payload = Self::py_new(&cls, elements, vm)?; + payload.into_ref_with_type(vm, cls).map(Into::into) } - fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + fn py_new(_cls: &Py<PyType>, elements: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self::from(elements)) } } diff --git a/crates/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs index 429fd94c2a2..7fde8d32781 100644 --- a/crates/vm/src/builtins/set.rs +++ b/crates/vm/src/builtins/set.rs @@ -918,35 +918,43 @@ impl Representable for PySet { } impl Constructor for PyFrozenSet { - type Args = OptionalArg<PyObjectRef>; + type Args = Vec<PyObjectRef>; fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - let iterable: Self::Args = args.bind(vm)?; - let elements = if let OptionalArg::Present(iterable) = iterable { - let iterable = if cls.is(vm.ctx.types.frozenset_type) { - match iterable.downcast_exact::<Self>(vm) { - Ok(fs) => return Ok(fs.into_pyref().into()), - Err(iterable) => iterable, - } - } else { - iterable - }; + let iterable: OptionalArg<PyObjectRef> = args.bind(vm)?; + + // Optimizations for exact frozenset type + if cls.is(vm.ctx.types.frozenset_type) { + // Return exact frozenset as-is + if let OptionalArg::Present(ref input) = iterable + && let Ok(fs) = input.clone().downcast_exact::<PyFrozenSet>(vm) + { + return Ok(fs.into_pyref().into()); + } + + // Return empty frozenset singleton + if iterable.is_missing() { + return Ok(vm.ctx.empty_frozenset.clone().into()); + } + } + + let elements: Vec<PyObjectRef> = if let OptionalArg::Present(iterable) = iterable { iterable.try_to_value(vm)? } else { vec![] }; - // Return empty fs if iterable passed is empty and only for exact fs types. + // Return empty frozenset singleton for exact frozenset types (when iterable was empty) if elements.is_empty() && cls.is(vm.ctx.types.frozenset_type) { - Ok(vm.ctx.empty_frozenset.clone().into()) - } else { - Self::from_iter(vm, elements) - .and_then(|o| o.into_ref_with_type(vm, cls).map(Into::into)) + return Ok(vm.ctx.empty_frozenset.clone().into()); } + + let payload = Self::py_new(&cls, elements, vm)?; + payload.into_ref_with_type(vm, cls).map(Into::into) } - fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + fn py_new(_cls: &Py<PyType>, elements: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { + Self::from_iter(vm, elements) } } diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 86c3ab9c81f..9b05e195722 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -351,36 +351,39 @@ impl Constructor for PyStr { type Args = StrArgs; fn slot_new(cls: PyTypeRef, func_args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // Optimization: return exact str as-is (only when no encoding/errors provided) + if cls.is(vm.ctx.types.str_type) + && func_args.args.len() == 1 + && func_args.kwargs.is_empty() + && func_args.args[0].class().is(vm.ctx.types.str_type) + { + return Ok(func_args.args[0].clone()); + } + let args: Self::Args = func_args.bind(vm)?; - let string: PyRef<PyStr> = match args.object { + let payload = Self::py_new(&cls, args, vm)?; + payload.into_ref_with_type(vm, cls).map(Into::into) + } + + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { + match args.object { OptionalArg::Present(input) => { if let OptionalArg::Present(enc) = args.encoding { - vm.state.codec_registry.decode_text( + let s = vm.state.codec_registry.decode_text( input, enc.as_str(), args.errors.into_option(), vm, - )? + )?; + Ok(Self::from(s.as_wtf8().to_owned())) } else { - input.str(vm)? + let s = input.str(vm)?; + Ok(Self::from(s.as_wtf8().to_owned())) } } - OptionalArg::Missing => { - Self::from(String::new()).into_ref_with_type(vm, cls.clone())? - } - }; - if string.class().is(&cls) { - Ok(string.into()) - } else { - Self::from(string.as_wtf8()) - .into_ref_with_type(vm, cls) - .map(Into::into) + OptionalArg::Missing => Ok(Self::from(String::new())), } } - - fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") - } } impl PyStr { diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index 61a9cf414c4..fada8840bb1 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -110,37 +110,45 @@ impl_from_into_pytuple!(A, B, C, D, E, F, G); pub type PyTupleRef = PyRef<PyTuple>; impl Constructor for PyTuple { - type Args = OptionalArg<PyObjectRef>; + type Args = Vec<PyObjectRef>; fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - let iterable: Self::Args = args.bind(vm)?; + let iterable: OptionalArg<PyObjectRef> = args.bind(vm)?; + + // Optimizations for exact tuple type + if cls.is(vm.ctx.types.tuple_type) { + // Return exact tuple as-is + if let OptionalArg::Present(ref input) = iterable + && let Ok(tuple) = input.clone().downcast_exact::<PyTuple>(vm) + { + return Ok(tuple.into_pyref().into()); + } + + // Return empty tuple singleton + if iterable.is_missing() { + return Ok(vm.ctx.empty_tuple.clone().into()); + } + } + let elements = if let OptionalArg::Present(iterable) = iterable { - let iterable = if cls.is(vm.ctx.types.tuple_type) { - match iterable.downcast_exact::<Self>(vm) { - Ok(tuple) => return Ok(tuple.into_pyref().into()), - Err(iterable) => iterable, - } - } else { - iterable - }; iterable.try_to_value(vm)? } else { vec![] }; - // Return empty tuple only for exact tuple types if the iterable is empty. + + // Return empty tuple singleton for exact tuple types (when iterable was empty) if elements.is_empty() && cls.is(vm.ctx.types.tuple_type) { - Ok(vm.ctx.empty_tuple.clone().into()) - } else { - Self { - elements: elements.into_boxed_slice(), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + return Ok(vm.ctx.empty_tuple.clone().into()); } + + let payload = Self::py_new(&cls, elements, vm)?; + payload.into_ref_with_type(vm, cls).map(Into::into) } - fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + fn py_new(_cls: &Py<PyType>, elements: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self { + elements: elements.into_boxed_slice(), + }) } } diff --git a/crates/vm/src/bytes_inner.rs b/crates/vm/src/bytes_inner.rs index 58d272e1cff..8593f16fcd9 100644 --- a/crates/vm/src/bytes_inner.rs +++ b/crates/vm/src/bytes_inner.rs @@ -1,16 +1,15 @@ // spell-checker:ignore unchunked use crate::{ - AsObject, PyObject, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, VirtualMachine, + AsObject, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, VirtualMachine, anystr::{self, AnyStr, AnyStrContainer, AnyStrWrapper}, builtins::{ PyBaseExceptionRef, PyByteArray, PyBytes, PyBytesRef, PyInt, PyIntRef, PyStr, PyStrRef, - PyTypeRef, pystr, + pystr, }, byte::bytes_from_object, cformat::cformat_bytes, common::hash, function::{ArgIterable, Either, OptionalArg, OptionalOption, PyComparisonValue}, - identifier, literal::escape::Escape, protocol::PyBuffer, sequence::{SequenceExt, SequenceMutExt}, @@ -91,43 +90,6 @@ impl ByteInnerNewOptions { }) } - pub fn get_bytes(self, cls: PyTypeRef, vm: &VirtualMachine) -> PyResult<PyBytesRef> { - let inner = match (&self.source, &self.encoding, &self.errors) { - (OptionalArg::Present(obj), OptionalArg::Missing, OptionalArg::Missing) => { - let obj = obj.clone(); - // construct an exact bytes from an exact bytes do not clone - let obj = if cls.is(vm.ctx.types.bytes_type) { - match obj.downcast_exact::<PyBytes>(vm) { - Ok(b) => return Ok(b.into_pyref()), - Err(obj) => obj, - } - } else { - obj - }; - - if let Some(bytes_method) = vm.get_method(obj, identifier!(vm, __bytes__)) { - // construct an exact bytes from __bytes__ slot. - // if __bytes__ return a bytes, use the bytes object except we are the subclass of the bytes - let bytes = bytes_method?.call((), vm)?; - let bytes = if cls.is(vm.ctx.types.bytes_type) { - match bytes.downcast::<PyBytes>() { - Ok(b) => return Ok(b), - Err(bytes) => bytes, - } - } else { - bytes - }; - Some(PyBytesInner::try_from_borrowed_object(vm, &bytes)) - } else { - None - } - } - _ => None, - } - .unwrap_or_else(|| self.get_bytearray_inner(vm))?; - PyBytes::from(inner).into_ref_with_type(vm, cls) - } - pub fn get_bytearray_inner(self, vm: &VirtualMachine) -> PyResult<PyBytesInner> { match (self.source, self.encoding, self.errors) { (OptionalArg::Present(obj), OptionalArg::Missing, OptionalArg::Missing) => { diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index 744eba9c555..66f1483c2e1 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -4,17 +4,16 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ - PyAsyncGen, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple, PyTupleRef, - PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr, + PyAsyncGen, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple, + PyTupleRef, PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr, }, - bytes_inner::ByteInnerNewOptions, common::{hash::PyHash, str::to_ascii}, convert::{ToPyObject, ToPyResult}, dict_inner::DictKey, - function::{Either, OptionalArg, PyArithmeticValue, PySetterValue}, + function::{Either, FuncArgs, PyArithmeticValue, PySetterValue}, object::PyPayload, protocol::{PyIter, PyMapping, PySequence}, - types::PyComparisonOp, + types::{Constructor, PyComparisonOp}, }; // RustPython doesn't need these items @@ -37,12 +36,8 @@ impl PyObjectRef { match self.downcast_exact::<PyInt>(vm) { Ok(int) => Err(vm.new_downcast_type_error(bytes_type, &int)), Err(obj) => { - let options = ByteInnerNewOptions { - source: OptionalArg::Present(obj), - encoding: OptionalArg::Missing, - errors: OptionalArg::Missing, - }; - options.get_bytes(bytes_type.to_owned(), vm).map(Into::into) + let args = FuncArgs::from(vec![obj]); + <PyBytes as Constructor>::slot_new(bytes_type.to_owned(), args, vm) } } } From 72e892a57df27a86d3a2089070b048a72d186d24 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 11 Dec 2025 23:20:24 +0900 Subject: [PATCH 473/819] Remove cargo vcpkg from windows build (#6404) --- .github/workflows/ci.yaml | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a91fd175ff8..2f70bb54e09 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -122,13 +122,14 @@ jobs: components: clippy - uses: Swatinem/rust-cache@v2 - - name: Set up the Windows environment - shell: bash - run: | - git config --system core.longpaths true - cargo install --target-dir=target -v cargo-vcpkg - cargo vcpkg -v build - if: runner.os == 'Windows' + # Only for OpenSSL builds + # - name: Set up the Windows environment + # shell: bash + # run: | + # git config --system core.longpaths true + # cargo install --target-dir=target -v cargo-vcpkg + # cargo vcpkg -v build + # if: runner.os == 'Windows' - name: Set up the Mac environment run: brew install autoconf automake libtool if: runner.os == 'macOS' @@ -248,13 +249,14 @@ jobs: - uses: actions/setup-python@v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - - name: Set up the Windows environment - shell: bash - run: | - git config --system core.longpaths true - cargo install cargo-vcpkg - cargo vcpkg build - if: runner.os == 'Windows' + # Only for OpenSSL builds + # - name: Set up the Windows environment + # shell: bash + # run: | + # git config --system core.longpaths true + # cargo install cargo-vcpkg + # cargo vcpkg build + # if: runner.os == 'Windows' - name: Set up the Mac environment run: brew install autoconf automake libtool openssl@3 if: runner.os == 'macOS' From 04d55fa5c6c23b3fc78cad2267a2742164012358 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 12 Dec 2025 00:08:55 +0900 Subject: [PATCH 474/819] Faulthandler (#6400) --- Lib/test/test_faulthandler.py | 21 +- Lib/test/test_subprocess.py | 2 - crates/stdlib/Cargo.toml | 1 + crates/stdlib/src/faulthandler.rs | 869 +++++++++++++++++++++++++++++- src/lib.rs | 2 +- 5 files changed, 848 insertions(+), 47 deletions(-) diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index 6316f937e21..ffdc82bf053 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -411,8 +411,6 @@ def test_dump_ext_modules(self): for name in ('sys', 'faulthandler'): self.assertIn(name, modules) - # TODO: RUSTPYTHON, AttributeError: module 'faulthandler' has no attribute 'is_enabled' - @unittest.expectedFailure def test_is_enabled(self): orig_stderr = sys.stderr try: @@ -435,8 +433,6 @@ def test_is_enabled(self): finally: sys.stderr = orig_stderr - # TODO: RUSTPYTHON, subprocess.CalledProcessError: Command ... returned non-zero exit status 1. - @unittest.expectedFailure @support.requires_subprocess() def test_disabled_by_default(self): # By default, the module should be disabled @@ -528,12 +524,10 @@ def funcA(): self.assertEqual(trace, expected) self.assertEqual(exitcode, 0) - # TODO: RUSTPYTHON, AssertionError: Lists differ - @unittest.expectedFailure def test_dump_traceback(self): self.check_dump_traceback() - # TODO: RUSTPYTHON + # TODO: RUSTPYTHON - binary file write needs different handling @unittest.expectedFailure def test_dump_traceback_file(self): with temporary_filename() as filename: @@ -547,8 +541,6 @@ def test_dump_traceback_fd(self): with tempfile.TemporaryFile('wb+') as fp: self.check_dump_traceback(fd=fp.fileno()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_truncate(self): maxlen = 500 func_name = 'x' * (maxlen + 50) @@ -870,8 +862,6 @@ def check_stderr_none(self): finally: sys.stderr = stderr - # TODO: RUSTPYTHON, AssertionError: RuntimeError not raised - @unittest.expectedFailure def test_stderr_None(self): # Issue #21497: provide a helpful error if sys.stderr is None, # instead of just an attribute error: "None has no attribute fileno". @@ -902,8 +892,6 @@ def test_raise_exception(self): 3, name) - # TODO: RUSTPYTHON, AttributeError: module 'msvcrt' has no attribute 'GetErrorMode' - @unittest.expectedFailure @unittest.skipUnless(MS_WINDOWS, 'specific to Windows') def test_ignore_exception(self): for exc_code in ( @@ -920,8 +908,6 @@ def test_ignore_exception(self): self.assertEqual(output, []) self.assertEqual(exitcode, exc_code) - # TODO: RUSTPYTHON, AttributeError: module 'msvcrt' has no attribute 'GetErrorMode' - @unittest.expectedFailure @unittest.skipUnless(MS_WINDOWS, 'specific to Windows') def test_raise_nonfatal_exception(self): # These exceptions are not strictly errors. Letting @@ -950,8 +936,6 @@ def test_raise_nonfatal_exception(self): self.assertIn(exitcode, (exc, exc & ~0x10000000)) - # TODO: RUSTPYTHON, AttributeError: module 'msvcrt' has no attribute 'GetErrorMode' - @unittest.expectedFailure @unittest.skipUnless(MS_WINDOWS, 'specific to Windows') def test_disable_windows_exc_handler(self): code = dedent(""" @@ -965,8 +949,6 @@ def test_disable_windows_exc_handler(self): self.assertEqual(output, []) self.assertEqual(exitcode, 0xC0000005) - # TODO: RUSTPYTHON, AssertionError: Lists differ - @unittest.expectedFailure def test_cancel_later_without_dump_traceback_later(self): # bpo-37933: Calling cancel_dump_traceback_later() # without dump_traceback_later() must not segfault. @@ -978,7 +960,6 @@ def test_cancel_later_without_dump_traceback_later(self): self.assertEqual(output, []) self.assertEqual(exitcode, 0) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AttributeError: module 'msvcrt' has no attribute 'GetErrorMode'") @threading_helper.requires_working_threading() @unittest.skipUnless(support.Py_GIL_DISABLED, "only meaningful if the GIL is disabled") def test_free_threaded_dump_traceback(self): diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 29ed4639c42..87562230261 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1833,8 +1833,6 @@ def test_run_with_shell_timeout_and_capture_output(self): msg="TimeoutExpired was delayed! Bad traceback:\n```\n" f"{stacks}```") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_encoding_warning(self): code = textwrap.dedent("""\ from subprocess import * diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 593b5f196f1..a5328697ca8 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -150,6 +150,7 @@ features = [ "Win32_NetworkManagement_Ndis", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", + "Win32_System_Diagnostics_Debug", "Win32_System_Environment", "Win32_System_IO", "Win32_System_Threading" diff --git a/crates/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs index 5c9196ad33f..e9006b982a3 100644 --- a/crates/stdlib/src/faulthandler.rs +++ b/crates/stdlib/src/faulthandler.rs @@ -2,62 +2,883 @@ pub(crate) use decl::make_module; #[pymodule(name = "faulthandler")] mod decl { - use crate::vm::{VirtualMachine, frame::Frame, function::OptionalArg, stdlib::sys::PyStderr}; + use crate::vm::{ + PyObjectRef, PyResult, VirtualMachine, builtins::PyFloat, frame::Frame, + function::OptionalArg, py_io::Write, + }; + use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; + use std::sync::{Arc, Condvar, Mutex}; + use std::thread; + use std::time::Duration; - fn dump_frame(frame: &Frame, vm: &VirtualMachine) { - let stderr = PyStderr(vm); - writeln!( - stderr, + static ENABLED: AtomicBool = AtomicBool::new(false); + static FATAL_ERROR_FD: AtomicI32 = AtomicI32::new(2); // stderr by default + + // Watchdog thread state for dump_traceback_later + struct WatchdogState { + cancel: bool, + fd: i32, + timeout_us: u64, + repeat: bool, + exit: bool, + header: String, + } + + type WatchdogHandle = Arc<(Mutex<WatchdogState>, Condvar)>; + static WATCHDOG: Mutex<Option<WatchdogHandle>> = Mutex::new(None); + + // Number of fatal signals we handle + #[cfg(unix)] + const NUM_FATAL_SIGNALS: usize = 5; + #[cfg(windows)] + const NUM_FATAL_SIGNALS: usize = 3; + + // Fatal signals to handle (with names for error messages) + #[cfg(unix)] + const FATAL_SIGNALS: [(libc::c_int, &str); NUM_FATAL_SIGNALS] = [ + (libc::SIGBUS, "Bus error"), + (libc::SIGILL, "Illegal instruction"), + (libc::SIGFPE, "Floating-point exception"), + (libc::SIGABRT, "Aborted"), + (libc::SIGSEGV, "Segmentation fault"), + ]; + + #[cfg(windows)] + const FATAL_SIGNALS: [(libc::c_int, &str); NUM_FATAL_SIGNALS] = [ + (libc::SIGFPE, "Floating-point exception"), + (libc::SIGABRT, "Aborted"), + (libc::SIGSEGV, "Segmentation fault"), + ]; + + // Storage for previous signal handlers (Unix) + #[cfg(unix)] + static mut PREVIOUS_HANDLERS: [libc::sigaction; NUM_FATAL_SIGNALS] = + unsafe { std::mem::zeroed() }; + + /// Signal-safe write function - no memory allocation + #[cfg(all(not(target_arch = "wasm32"), not(windows)))] + fn write_str_noraise(fd: i32, s: &str) { + unsafe { + libc::write( + fd as libc::c_int, + s.as_ptr() as *const libc::c_void, + s.len(), + ); + } + } + + #[cfg(windows)] + fn write_str_noraise(fd: i32, s: &str) { + unsafe { + libc::write( + fd as libc::c_int, + s.as_ptr() as *const libc::c_void, + s.len() as u32, + ); + } + } + + const MAX_FUNCTION_NAME_LEN: usize = 500; + + fn truncate_name(name: &str) -> String { + if name.len() > MAX_FUNCTION_NAME_LEN { + format!("{}...", &name[..MAX_FUNCTION_NAME_LEN]) + } else { + name.to_string() + } + } + + fn get_file_for_output( + file: OptionalArg<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + match file { + OptionalArg::Present(f) => { + // If it's an integer, we can't use it directly as a file object + // For now, just return it and let the caller handle it + Ok(f) + } + OptionalArg::Missing => { + // Get sys.stderr + let stderr = vm.sys_module.get_attr("stderr", vm)?; + if vm.is_none(&stderr) { + return Err(vm.new_runtime_error("sys.stderr is None".to_owned())); + } + Ok(stderr) + } + } + } + + fn collect_frame_info(frame: &crate::vm::PyRef<Frame>) -> String { + let func_name = truncate_name(frame.code.obj_name.as_str()); + // If lasti is 0, execution hasn't started yet - use first line number or 1 + let line = if frame.lasti() == 0 { + frame.code.first_line_number.map(|n| n.get()).unwrap_or(1) + } else { + frame.current_location().line.get() + }; + format!( " File \"{}\", line {} in {}", - frame.code.source_path, - frame.current_location().line, - frame.code.obj_name + frame.code.source_path, line, func_name ) } + #[derive(FromArgs)] + struct DumpTracebackArgs { + #[pyarg(any, default)] + file: OptionalArg<PyObjectRef>, + #[pyarg(any, default = true)] + all_threads: bool, + } + #[pyfunction] - fn dump_traceback( - _file: OptionalArg<i64>, - _all_threads: OptionalArg<bool>, - vm: &VirtualMachine, - ) { - let stderr = PyStderr(vm); - writeln!(stderr, "Stack (most recent call first):"); + fn dump_traceback(args: DumpTracebackArgs, vm: &VirtualMachine) -> PyResult<()> { + let _ = args.all_threads; // TODO: implement all_threads support + + let file = get_file_for_output(args.file, vm)?; - for frame in vm.frames.borrow().iter() { - dump_frame(frame, vm); + // Collect frame info first to avoid RefCell borrow conflict + let frame_lines: Vec<String> = vm.frames.borrow().iter().map(collect_frame_info).collect(); + + // Now write to file (in reverse order - most recent call first) + let mut writer = crate::vm::py_io::PyWriter(file, vm); + writeln!(writer, "Stack (most recent call first):")?; + for line in frame_lines.iter().rev() { + writeln!(writer, "{}", line)?; } + Ok(()) } #[derive(FromArgs)] #[allow(unused)] struct EnableArgs { #[pyarg(any, default)] - file: Option<i64>, + file: OptionalArg<PyObjectRef>, #[pyarg(any, default = true)] all_threads: bool, } #[pyfunction] - const fn enable(_args: EnableArgs) { - // TODO + fn enable(args: EnableArgs, vm: &VirtualMachine) -> PyResult<()> { + // Check that file is valid (if provided) or sys.stderr is not None + let _file = get_file_for_output(args.file, vm)?; + + ENABLED.store(true, Ordering::Relaxed); + + // Install signal handlers for fatal errors + #[cfg(any(unix, windows))] + { + install_fatal_handlers(vm); + } + + Ok(()) + } + + /// Unix signal handler for fatal errors + // faulthandler_fatal_error() in Modules/faulthandler.c + #[cfg(unix)] + extern "C" fn faulthandler_fatal_error(signum: libc::c_int) { + if !ENABLED.load(Ordering::Relaxed) { + return; + } + + let fd = FATAL_ERROR_FD.load(Ordering::Relaxed); + + // Find handler and restore previous handler BEFORE printing + let signal_name = + if let Some(idx) = FATAL_SIGNALS.iter().position(|(sig, _)| *sig == signum) { + // Restore previous handler first + unsafe { + libc::sigaction(signum, &PREVIOUS_HANDLERS[idx], std::ptr::null_mut()); + } + FATAL_SIGNALS[idx].1 + } else { + "Unknown signal" + }; + + // Print error message + write_str_noraise(fd, "Fatal Python error: "); + write_str_noraise(fd, signal_name); + write_str_noraise(fd, "\n\n"); + + // Re-raise signal to trigger default behavior (core dump, etc.) + // Called immediately thanks to SA_NODEFER flag + unsafe { + libc::raise(signum); + } + } + + #[cfg(unix)] + fn install_fatal_handlers(_vm: &VirtualMachine) { + for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() { + let mut action: libc::sigaction = unsafe { std::mem::zeroed() }; + action.sa_sigaction = faulthandler_fatal_error as libc::sighandler_t; + action.sa_flags = libc::SA_NODEFER; + + unsafe { + libc::sigaction(*signum, &action, &mut PREVIOUS_HANDLERS[idx]); + } + } + } + + /// Windows signal handler for fatal errors + // faulthandler_fatal_error() in Modules/faulthandler.c + #[cfg(windows)] + extern "C" fn faulthandler_fatal_error_windows(signum: libc::c_int) { + if !ENABLED.load(Ordering::Relaxed) { + return; + } + + let fd = FATAL_ERROR_FD.load(Ordering::Relaxed); + + // Find handler and restore previous handler BEFORE printing + let signal_name = + if let Some(idx) = FATAL_SIGNALS.iter().position(|(sig, _)| *sig == signum) { + // Restore previous handler first + unsafe { + libc::signal(signum, PREVIOUS_HANDLERS_WIN[idx]); + } + FATAL_SIGNALS[idx].1 + } else { + "Unknown signal" + }; + + // Print error message + write_str_noraise(fd, "Fatal Python error: "); + write_str_noraise(fd, signal_name); + write_str_noraise(fd, "\n\n"); + + // On Windows, don't explicitly call the previous handler for SIGSEGV + // The execution continues and the same instruction raises the same fault, + // calling the now-restored previous handler + if signum == libc::SIGSEGV { + return; + } + + // For other signals, re-raise to call the previous handler + unsafe { + libc::raise(signum); + } + } + + #[cfg(windows)] + static mut PREVIOUS_HANDLERS_WIN: [libc::sighandler_t; NUM_FATAL_SIGNALS] = + [0; NUM_FATAL_SIGNALS]; + + #[cfg(windows)] + fn install_fatal_handlers(_vm: &VirtualMachine) { + for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() { + unsafe { + PREVIOUS_HANDLERS_WIN[idx] = libc::signal( + *signum, + faulthandler_fatal_error_windows as libc::sighandler_t, + ); + } + } + } + + #[pyfunction] + fn disable() -> bool { + let was_enabled = ENABLED.swap(false, Ordering::Relaxed); + + // Restore default signal handlers + #[cfg(any(unix, windows))] + { + uninstall_fatal_handlers(); + } + + was_enabled + } + + #[cfg(unix)] + fn uninstall_fatal_handlers() { + for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() { + unsafe { + libc::sigaction(*signum, &PREVIOUS_HANDLERS[idx], std::ptr::null_mut()); + } + } + } + + #[cfg(windows)] + fn uninstall_fatal_handlers() { + for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() { + unsafe { + libc::signal(*signum, PREVIOUS_HANDLERS_WIN[idx]); + } + } + } + + #[pyfunction] + fn is_enabled() -> bool { + ENABLED.load(Ordering::Relaxed) + } + + fn format_timeout(timeout_us: u64) -> String { + let sec = timeout_us / 1_000_000; + let us = timeout_us % 1_000_000; + let min = sec / 60; + let sec = sec % 60; + let hour = min / 60; + let min = min % 60; + + if us != 0 { + format!("Timeout ({:02}:{:02}:{:02}.{:06})!\n", hour, min, sec, us) + } else { + format!("Timeout ({:02}:{:02}:{:02})!\n", hour, min, sec) + } + } + + fn get_fd_from_file_opt(file: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult<i32> { + match file { + OptionalArg::Present(f) => { + // Check if it's an integer (file descriptor) + if let Ok(fd) = f.try_to_value::<i32>(vm) { + if fd < 0 { + return Err( + vm.new_value_error("file is not a valid file descriptor".to_owned()) + ); + } + return Ok(fd); + } + // Try to get fileno() from file object + let fileno = vm.call_method(&f, "fileno", ())?; + let fd: i32 = fileno.try_to_value(vm)?; + if fd < 0 { + return Err( + vm.new_value_error("file is not a valid file descriptor".to_owned()) + ); + } + // Try to flush the file + let _ = vm.call_method(&f, "flush", ()); + Ok(fd) + } + OptionalArg::Missing => { + // Get sys.stderr + let stderr = vm.sys_module.get_attr("stderr", vm)?; + if vm.is_none(&stderr) { + return Err(vm.new_runtime_error("sys.stderr is None".to_owned())); + } + let fileno = vm.call_method(&stderr, "fileno", ())?; + let fd: i32 = fileno.try_to_value(vm)?; + let _ = vm.call_method(&stderr, "flush", ()); + Ok(fd) + } + } + } + + fn watchdog_thread(state: WatchdogHandle) { + let (lock, cvar) = &*state; + + loop { + // Hold lock across wait_timeout to avoid race condition + let mut guard = lock.lock().unwrap(); + if guard.cancel { + return; + } + let timeout = Duration::from_micros(guard.timeout_us); + let result = cvar.wait_timeout(guard, timeout).unwrap(); + guard = result.0; + + // Check if cancelled after wait + if guard.cancel { + return; + } + + // Extract values before releasing lock for I/O + let (repeat, exit, fd, header) = + (guard.repeat, guard.exit, guard.fd, guard.header.clone()); + drop(guard); // Release lock before I/O + + // Timeout occurred, dump traceback + #[cfg(not(target_arch = "wasm32"))] + { + let header_bytes = header.as_bytes(); + #[cfg(windows)] + unsafe { + libc::write( + fd, + header_bytes.as_ptr() as *const libc::c_void, + header_bytes.len() as u32, + ); + } + #[cfg(not(windows))] + unsafe { + libc::write( + fd, + header_bytes.as_ptr() as *const libc::c_void, + header_bytes.len(), + ); + } + + // Note: We cannot dump actual Python traceback from a separate thread + // because we don't have access to the VM's frame stack. + // Just output a message indicating timeout occurred. + let msg = b"<timeout: cannot dump traceback from watchdog thread>\n"; + #[cfg(windows)] + unsafe { + libc::write(fd, msg.as_ptr() as *const libc::c_void, msg.len() as u32); + } + #[cfg(not(windows))] + unsafe { + libc::write(fd, msg.as_ptr() as *const libc::c_void, msg.len()); + } + + if exit { + std::process::exit(1); + } + } + + if !repeat { + return; + } + } } + #[derive(FromArgs)] + #[allow(unused)] + struct DumpTracebackLaterArgs { + #[pyarg(positional)] + timeout: PyObjectRef, + #[pyarg(any, default = false)] + repeat: bool, + #[pyarg(any, default)] + file: OptionalArg<PyObjectRef>, + #[pyarg(any, default = false)] + exit: bool, + } + + #[pyfunction] + fn dump_traceback_later(args: DumpTracebackLaterArgs, vm: &VirtualMachine) -> PyResult<()> { + use num_traits::ToPrimitive; + // Convert timeout to f64 (accepting int or float) + let timeout: f64 = if let Some(float) = args.timeout.downcast_ref::<PyFloat>() { + float.to_f64() + } else if let Some(int) = args.timeout.try_index_opt(vm).transpose()? { + int.as_bigint() + .to_i64() + .ok_or_else(|| vm.new_overflow_error("timeout value is too large".to_owned()))? + as f64 + } else { + return Err(vm.new_type_error("timeout must be a number (int or float)".to_owned())); + }; + + if timeout <= 0.0 { + return Err(vm.new_value_error("timeout must be greater than 0".to_owned())); + } + + let fd = get_fd_from_file_opt(args.file, vm)?; + + // Convert timeout to microseconds + let timeout_us = (timeout * 1_000_000.0) as u64; + if timeout_us == 0 { + return Err(vm.new_value_error("timeout must be greater than 0".to_owned())); + } + + let header = format_timeout(timeout_us); + + // Cancel any previous watchdog + cancel_dump_traceback_later(); + + // Create new watchdog state + let state = Arc::new(( + Mutex::new(WatchdogState { + cancel: false, + fd, + timeout_us, + repeat: args.repeat, + exit: args.exit, + header, + }), + Condvar::new(), + )); + + // Store the state + { + let mut watchdog = WATCHDOG.lock().unwrap(); + *watchdog = Some(Arc::clone(&state)); + } + + // Start watchdog thread + thread::spawn(move || { + watchdog_thread(state); + }); + + Ok(()) + } + + #[pyfunction] + fn cancel_dump_traceback_later() { + let state = { + let mut watchdog = WATCHDOG.lock().unwrap(); + watchdog.take() + }; + + if let Some(state) = state { + let (lock, cvar) = &*state; + { + let mut guard = lock.lock().unwrap(); + guard.cancel = true; + } + cvar.notify_all(); + } + } + + #[cfg(unix)] + mod user_signals { + use std::sync::Mutex; + + const NSIG: usize = 64; + + #[derive(Clone)] + pub struct UserSignal { + pub enabled: bool, + pub fd: i32, + #[allow(dead_code)] + pub all_threads: bool, + pub chain: bool, + pub previous: libc::sighandler_t, + } + + impl Default for UserSignal { + fn default() -> Self { + Self { + enabled: false, + fd: 2, // stderr + all_threads: true, + chain: false, + previous: libc::SIG_DFL, + } + } + } + + static USER_SIGNALS: Mutex<Option<Vec<UserSignal>>> = Mutex::new(None); + + pub fn get_user_signal(signum: usize) -> Option<UserSignal> { + let guard = USER_SIGNALS.lock().unwrap(); + guard.as_ref().and_then(|v| v.get(signum).cloned()) + } + + pub fn set_user_signal(signum: usize, signal: UserSignal) { + let mut guard = USER_SIGNALS.lock().unwrap(); + if guard.is_none() { + *guard = Some(vec![UserSignal::default(); NSIG]); + } + if let Some(ref mut v) = *guard + && signum < v.len() + { + v[signum] = signal; + } + } + + pub fn clear_user_signal(signum: usize) -> Option<UserSignal> { + let mut guard = USER_SIGNALS.lock().unwrap(); + if let Some(ref mut v) = *guard + && signum < v.len() + && v[signum].enabled + { + let old = v[signum].clone(); + v[signum] = UserSignal::default(); + return Some(old); + } + None + } + + pub fn is_enabled(signum: usize) -> bool { + let guard = USER_SIGNALS.lock().unwrap(); + guard + .as_ref() + .and_then(|v| v.get(signum)) + .is_some_and(|s| s.enabled) + } + } + + #[cfg(unix)] + extern "C" fn faulthandler_user_signal(signum: libc::c_int) { + let user = match user_signals::get_user_signal(signum as usize) { + Some(u) if u.enabled => u, + _ => return, + }; + + // Write traceback header + let header = b"Current thread 0x0000 (most recent call first):\n"; + let _ = unsafe { + libc::write( + user.fd, + header.as_ptr() as *const libc::c_void, + header.len(), + ) + }; + + // Note: We cannot easily access RustPython's frame stack from a signal handler + // because signal handlers run asynchronously. We just output a placeholder. + let msg = b" <signal handler invoked, traceback unavailable in signal context>\n"; + let _ = unsafe { libc::write(user.fd, msg.as_ptr() as *const libc::c_void, msg.len()) }; + + // If chain is enabled, call the previous handler + if user.chain && user.previous != libc::SIG_DFL && user.previous != libc::SIG_IGN { + // Re-register the old handler and raise the signal + unsafe { + libc::signal(signum, user.previous); + libc::raise(signum); + // Re-register our handler + libc::signal(signum, faulthandler_user_signal as libc::sighandler_t); + } + } + } + + #[cfg(unix)] + fn check_signum(signum: i32, vm: &VirtualMachine) -> PyResult<()> { + // Check if it's a fatal signal + if FATAL_SIGNALS.iter().any(|(sig, _)| *sig == signum) { + return Err(vm.new_runtime_error(format!( + "signal {} cannot be registered, use enable() instead", + signum + ))); + } + + // Check if signal is in valid range + if !(1..64).contains(&signum) { + return Err(vm.new_value_error("signal number out of range".to_owned())); + } + + Ok(()) + } + + #[cfg(unix)] #[derive(FromArgs)] #[allow(unused)] struct RegisterArgs { #[pyarg(positional)] - signum: i64, + signum: i32, #[pyarg(any, default)] - file: Option<i64>, + file: OptionalArg<PyObjectRef>, #[pyarg(any, default = true)] all_threads: bool, #[pyarg(any, default = false)] chain: bool, } + #[cfg(unix)] #[pyfunction] - const fn register(_args: RegisterArgs) { - // TODO + fn register(args: RegisterArgs, vm: &VirtualMachine) -> PyResult<()> { + check_signum(args.signum, vm)?; + + let fd = get_fd_from_file_opt(args.file, vm)?; + + let signum = args.signum as usize; + + // Get current handler to save as previous + let previous = if !user_signals::is_enabled(signum) { + // Install signal handler + let prev = unsafe { + libc::signal(args.signum, faulthandler_user_signal as libc::sighandler_t) + }; + if prev == libc::SIG_ERR { + return Err(vm.new_os_error(format!( + "Failed to register signal handler for signal {}", + args.signum + ))); + } + prev + } else { + // Already registered, keep previous handler + user_signals::get_user_signal(signum) + .map(|u| u.previous) + .unwrap_or(libc::SIG_DFL) + }; + + user_signals::set_user_signal( + signum, + user_signals::UserSignal { + enabled: true, + fd, + all_threads: args.all_threads, + chain: args.chain, + previous, + }, + ); + + Ok(()) + } + + #[cfg(unix)] + #[pyfunction] + fn unregister(signum: i32, vm: &VirtualMachine) -> PyResult<bool> { + check_signum(signum, vm)?; + + if let Some(old) = user_signals::clear_user_signal(signum as usize) { + // Restore previous handler + unsafe { + libc::signal(signum, old.previous); + } + Ok(true) + } else { + Ok(false) + } + } + + // Test functions for faulthandler testing + + #[pyfunction] + fn _read_null() { + // This function intentionally causes a segmentation fault by reading from NULL + // Used for testing faulthandler + #[cfg(not(target_arch = "wasm32"))] + unsafe { + suppress_crash_report(); + let ptr: *const i32 = std::ptr::null(); + std::ptr::read_volatile(ptr); + } + } + + #[derive(FromArgs)] + #[allow(dead_code)] + struct SigsegvArgs { + #[pyarg(any, default = false)] + release_gil: bool, + } + + #[pyfunction] + fn _sigsegv(_args: SigsegvArgs) { + // Raise SIGSEGV signal + #[cfg(not(target_arch = "wasm32"))] + { + suppress_crash_report(); + + // Reset SIGSEGV to default behavior before raising + // This ensures the process will actually crash + unsafe { + libc::signal(libc::SIGSEGV, libc::SIG_DFL); + } + + #[cfg(windows)] + { + // On Windows, we need to raise SIGSEGV multiple times + loop { + unsafe { + libc::raise(libc::SIGSEGV); + } + } + } + #[cfg(not(windows))] + unsafe { + libc::raise(libc::SIGSEGV); + } + } + } + + #[pyfunction] + fn _sigabrt() { + #[cfg(not(target_arch = "wasm32"))] + { + suppress_crash_report(); + unsafe { + libc::abort(); + } + } + } + + #[pyfunction] + fn _sigfpe() { + #[cfg(not(target_arch = "wasm32"))] + { + suppress_crash_report(); + + // Reset SIGFPE to default behavior before raising + unsafe { + libc::signal(libc::SIGFPE, libc::SIG_DFL); + } + + // Raise SIGFPE + unsafe { + libc::raise(libc::SIGFPE); + } + } + } + + #[pyfunction] + fn _fatal_error_c_thread() { + // This would call Py_FatalError in a new C thread + // For RustPython, we just panic in a new thread + #[cfg(not(target_arch = "wasm32"))] + { + suppress_crash_report(); + std::thread::spawn(|| { + panic!("Fatal Python error: in new thread"); + }); + // Wait a bit for the thread to panic + std::thread::sleep(std::time::Duration::from_secs(1)); + } + } + + #[cfg(not(target_arch = "wasm32"))] + fn suppress_crash_report() { + #[cfg(windows)] + { + use windows_sys::Win32::System::Diagnostics::Debug::{ + SEM_NOGPFAULTERRORBOX, SetErrorMode, + }; + unsafe { + let mode = SetErrorMode(SEM_NOGPFAULTERRORBOX); + SetErrorMode(mode | SEM_NOGPFAULTERRORBOX); + } + } + + #[cfg(unix)] + { + // Disable core dumps + #[cfg(not(any(target_os = "redox", target_os = "wasi")))] + { + use libc::{RLIMIT_CORE, rlimit, setrlimit}; + let rl = rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + unsafe { + let _ = setrlimit(RLIMIT_CORE, &rl); + } + } + } + } + + // Windows-specific constants + #[cfg(windows)] + #[pyattr] + const _EXCEPTION_ACCESS_VIOLATION: u32 = 0xC0000005; + + #[cfg(windows)] + #[pyattr] + const _EXCEPTION_INT_DIVIDE_BY_ZERO: u32 = 0xC0000094; + + #[cfg(windows)] + #[pyattr] + const _EXCEPTION_STACK_OVERFLOW: u32 = 0xC00000FD; + + #[cfg(windows)] + #[pyattr] + const _EXCEPTION_NONCONTINUABLE: u32 = 0x00000001; + + #[cfg(windows)] + #[pyattr] + const _EXCEPTION_NONCONTINUABLE_EXCEPTION: u32 = 0xC0000025; + + #[cfg(windows)] + #[derive(FromArgs)] + struct RaiseExceptionArgs { + #[pyarg(positional)] + code: u32, + #[pyarg(positional, default = 0)] + flags: u32, + } + + #[cfg(windows)] + #[pyfunction] + fn _raise_exception(args: RaiseExceptionArgs) { + use windows_sys::Win32::System::Diagnostics::Debug::RaiseException; + + suppress_crash_report(); + unsafe { + RaiseException(args.code, args.flags, 0, std::ptr::null()); + } } } diff --git a/src/lib.rs b/src/lib.rs index 976d0bc0a34..0737ebdddc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -207,7 +207,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { let res = match run_mode { RunMode::Command(command) => { debug!("Running command {command}"); - vm.run_code_string(scope.clone(), &command, "<stdin>".to_owned()) + vm.run_code_string(scope.clone(), &command, "<string>".to_owned()) .map(drop) } RunMode::Module(module) => { From f379ea8327ce976b51ed2a1b5ad451346d4d2cc5 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 12 Dec 2025 00:18:02 +0900 Subject: [PATCH 475/819] winapi._findfirstfile,nt.chmod (#6401) --- Lib/test/test_compileall.py | 2 - Lib/test/test_ntpath.py | 3 +- Lib/test/test_os.py | 1 - Lib/test/test_posix.py | 1 - Lib/test/test_shutil.py | 7 --- crates/common/src/windows.rs | 20 +++++++ crates/vm/src/stdlib/nt.rs | 105 +++++++++++++++++++++++++++++---- crates/vm/src/stdlib/winapi.rs | 22 +++---- 8 files changed, 122 insertions(+), 39 deletions(-) diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py index a490b8a1d5b..f84e67e9cf6 100644 --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py @@ -459,7 +459,6 @@ def test_multiple_optimization_levels(self): except Exception: pass - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_ignore_symlink_destination(self): # Create folders for allowed files, symlinks and prohibited area @@ -933,7 +932,6 @@ def test_multiple_optimization_levels(self): except Exception: pass - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_ignore_symlink_destination(self): # Create folders for allowed files, symlinks and prohibited area diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 1efe8bfa0e0..c297e6b36ec 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -650,8 +650,6 @@ def test_realpath_relative(self, kwargs): os.symlink(ABSTFN, ntpath.relpath(ABSTFN + "1")) self.assertPathEqual(ntpath.realpath(ABSTFN + "1", **kwargs), ABSTFN) - # TODO: RUSTPYTHON - @unittest.expectedFailure @os_helper.skip_unless_symlink @unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname') def test_realpath_broken_symlinks(self): @@ -1385,6 +1383,7 @@ def test_nt_helpers(self): self.assertIsInstance(b_final_path, bytes) self.assertGreater(len(b_final_path), 0) + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.platform != 'win32', "Can only test junctions with creation on win32.") def test_isjunction(self): with os_helper.temp_dir() as d: diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 6fce916c7d9..657a69c4ec9 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3144,7 +3144,6 @@ def test_create_junction(self): self.assertEqual(os.path.normcase("\\\\?\\" + self.junction_target), os.path.normcase(os.readlink(self.junction))) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (AttributeError: module '_winapi' has no attribute 'CreateJunction')") def test_unlink_removes_junction(self): _winapi.CreateJunction(self.junction_target, self.junction) self.assertTrue(os.path.exists(self.junction)) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index de3184a3736..a4809a23798 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1063,7 +1063,6 @@ def check_lchmod_link(self, chmod_func, target, link, **kwargs): self.assertEqual(os.stat(target).st_mode, target_mode) self.assertEqual(os.lstat(link).st_mode, new_mode) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') @os_helper.skip_unless_symlink def test_chmod_file_symlink(self): target = os_helper.TESTFN diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 486d123d2f8..ad67583d438 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -257,7 +257,6 @@ def test_rmtree_works_on_symlinks(self): self.assertTrue(os.path.exists(dir3)) self.assertTrue(os.path.exists(file1)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @unittest.skipUnless(_winapi, 'only relevant on Windows') def test_rmtree_fails_on_junctions_onerror(self): tmp = self.mkdtemp() @@ -278,7 +277,6 @@ def onerror(*args): self.assertEqual(errors[0][1], link) self.assertIsInstance(errors[0][2][1], OSError) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @unittest.skipUnless(_winapi, 'only relevant on Windows') def test_rmtree_fails_on_junctions_onexc(self): tmp = self.mkdtemp() @@ -299,7 +297,6 @@ def onexc(*args): self.assertEqual(errors[0][1], link) self.assertIsInstance(errors[0][2], OSError) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @unittest.skipUnless(_winapi, 'only relevant on Windows') def test_rmtree_works_on_junctions(self): tmp = self.mkdtemp() @@ -659,7 +656,6 @@ def test_rmtree_on_symlink(self): finally: shutil.rmtree(TESTFN, ignore_errors=True) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @unittest.skipUnless(_winapi, 'only relevant on Windows') def test_rmtree_on_junction(self): os.mkdir(TESTFN) @@ -977,7 +973,6 @@ def _copy(src, dst): shutil.copytree(src_dir, dst_dir, copy_function=_copy) self.assertEqual(len(copied), 2) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_copytree_dangling_symlinks(self): src_dir = self.mkdtemp() @@ -1051,7 +1046,6 @@ class TestCopy(BaseTest, unittest.TestCase): ### shutil.copymode - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_copymode_follow_symlinks(self): tmp_dir = self.mkdtemp() @@ -2594,7 +2588,6 @@ def test_move_file_symlink_to_dir(self): self.assertTrue(os.path.islink(final_link)) self.assertTrue(os.path.samefile(self.src_file, final_link)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink @mock_rename def test_move_dangling_symlink(self): diff --git a/crates/common/src/windows.rs b/crates/common/src/windows.rs index dd4ea82c874..a934a118ecf 100644 --- a/crates/common/src/windows.rs +++ b/crates/common/src/windows.rs @@ -1,3 +1,4 @@ +use rustpython_wtf8::Wtf8; use std::{ ffi::{OsStr, OsString}, os::windows::ffi::{OsStrExt, OsStringExt}, @@ -10,6 +11,7 @@ pub trait ToWideString { fn to_wide(&self) -> Vec<u16>; fn to_wide_with_nul(&self) -> Vec<u16>; } + impl<T> ToWideString for T where T: AsRef<OsStr>, @@ -22,6 +24,24 @@ where } } +impl ToWideString for OsStr { + fn to_wide(&self) -> Vec<u16> { + self.encode_wide().collect() + } + fn to_wide_with_nul(&self) -> Vec<u16> { + self.encode_wide().chain(Some(0)).collect() + } +} + +impl ToWideString for Wtf8 { + fn to_wide(&self) -> Vec<u16> { + self.encode_wide().collect() + } + fn to_wide_with_nul(&self) -> Vec<u16> { + self.encode_wide().chain(Some(0)).collect() + } +} + pub trait FromWideString where Self: Sized, diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 0b8b2ec6374..bc82e0a74a3 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -19,7 +19,7 @@ pub(crate) mod module { convert::ToPyException, function::{Either, OptionalArg}, ospath::OsPath, - stdlib::os::{_os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory}, + stdlib::os::{_os, DirFd, SupportFunc, TargetIsDirectory}, }; use libc::intptr_t; @@ -137,25 +137,104 @@ pub(crate) mod module { environ } - #[pyfunction] - fn chmod( + #[derive(FromArgs)] + struct ChmodArgs { + #[pyarg(any)] path: OsPath, - dir_fd: DirFd<'_, 0>, + #[pyarg(any)] mode: u32, - follow_symlinks: FollowSymlinks, - vm: &VirtualMachine, - ) -> PyResult<()> { + #[pyarg(flatten)] + dir_fd: DirFd<'static, 0>, + #[pyarg(named, name = "follow_symlinks", optional)] + follow_symlinks: OptionalArg<bool>, + } + + #[pyfunction] + fn chmod(args: ChmodArgs, vm: &VirtualMachine) -> PyResult<()> { + let ChmodArgs { + path, + mode, + dir_fd, + follow_symlinks, + } = args; const S_IWRITE: u32 = 128; let [] = dir_fd.0; - let metadata = if follow_symlinks.0 { - fs::metadata(&path) - } else { - fs::symlink_metadata(&path) + + // On Windows, os.chmod behavior differs based on whether follow_symlinks is explicitly provided: + // - Not provided (default): use SetFileAttributesW on the path directly (doesn't follow symlinks) + // - Explicitly True: resolve symlink first, then apply permissions to target + // - Explicitly False: raise NotImplementedError (Windows can't change symlink permissions) + let actual_path: std::borrow::Cow<'_, std::path::Path> = match follow_symlinks.into_option() + { + None => { + // Default behavior: don't resolve symlinks, operate on path directly + std::borrow::Cow::Borrowed(path.as_ref()) + } + Some(true) => { + // Explicitly follow symlinks: resolve the path first + match fs::canonicalize(&path) { + Ok(p) => std::borrow::Cow::Owned(p), + Err(_) => std::borrow::Cow::Borrowed(path.as_ref()), + } + } + Some(false) => { + // follow_symlinks=False on Windows - not supported for symlinks + // Check if path is a symlink + if let Ok(meta) = fs::symlink_metadata(&path) + && meta.file_type().is_symlink() + { + return Err(vm.new_not_implemented_error( + "chmod: follow_symlinks=False is not supported on Windows for symlinks" + .to_owned(), + )); + } + std::borrow::Cow::Borrowed(path.as_ref()) + } }; - let meta = metadata.map_err(|err| err.to_pyexception(vm))?; + + // Use symlink_metadata to avoid following dangling symlinks + let meta = fs::symlink_metadata(&actual_path).map_err(|err| err.to_pyexception(vm))?; let mut permissions = meta.permissions(); permissions.set_readonly(mode & S_IWRITE == 0); - fs::set_permissions(&path, permissions).map_err(|err| err.to_pyexception(vm)) + fs::set_permissions(&*actual_path, permissions).map_err(|err| err.to_pyexception(vm)) + } + + /// Get the real file name (with correct case) without accessing the file. + /// Uses FindFirstFileW to get the name as stored on the filesystem. + #[pyfunction] + fn _findfirstfile(path: OsPath, vm: &VirtualMachine) -> PyResult<PyStrRef> { + use crate::common::windows::ToWideString; + use std::os::windows::ffi::OsStringExt; + use windows_sys::Win32::Storage::FileSystem::{ + FindClose, FindFirstFileW, WIN32_FIND_DATAW, + }; + + let wide_path = path.as_ref().to_wide_with_nul(); + let mut find_data: WIN32_FIND_DATAW = unsafe { std::mem::zeroed() }; + + let handle = unsafe { FindFirstFileW(wide_path.as_ptr(), &mut find_data) }; + if handle == INVALID_HANDLE_VALUE { + return Err(vm.new_os_error(format!( + "FindFirstFileW failed for path: {}", + path.as_ref().display() + ))); + } + + unsafe { FindClose(handle) }; + + // Convert the filename from the find data to a Rust string + // cFileName is a null-terminated wide string + let len = find_data + .cFileName + .iter() + .position(|&c| c == 0) + .unwrap_or(find_data.cFileName.len()); + let filename = std::ffi::OsString::from_wide(&find_data.cFileName[..len]); + let filename_str = filename + .to_str() + .ok_or_else(|| vm.new_unicode_decode_error("filename contains invalid UTF-8"))?; + + Ok(vm.ctx.new_str(filename_str).to_owned()) } // cwait is available on MSVC only (according to CPython) diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index 956d96d5432..3fb1068a60e 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -316,7 +316,7 @@ mod _winapi { #[pyfunction] fn NeedCurrentDirectoryForExePath(exe_name: PyStrRef) -> bool { - let exe_name = exe_name.as_str().to_wide_with_nul(); + let exe_name = exe_name.as_wtf8().to_wide_with_nul(); let return_value = unsafe { windows_sys::Win32::System::Environment::NeedCurrentDirectoryForExePathW( exe_name.as_ptr(), @@ -334,7 +334,7 @@ mod _winapi { let src_path = std::path::Path::new(src_path.as_str()); let dest_path = std::path::Path::new(dest_path.as_str()); - junction::create(dest_path, src_path).map_err(|e| e.to_pyexception(vm)) + junction::create(src_path, dest_path).map_err(|e| e.to_pyexception(vm)) } fn getenvironment(env: ArgMapping, vm: &VirtualMachine) -> PyResult<Vec<u16>> { @@ -485,9 +485,9 @@ mod _winapi { // TODO: ctypes.LibraryLoader.LoadLibrary #[allow(dead_code)] fn LoadLibrary(path: PyStrRef, vm: &VirtualMachine) -> PyResult<isize> { - let path = path.as_str().to_wide_with_nul(); + let path_wide = path.as_wtf8().to_wide_with_nul(); let handle = - unsafe { windows_sys::Win32::System::LibraryLoader::LoadLibraryW(path.as_ptr()) }; + unsafe { windows_sys::Win32::System::LibraryLoader::LoadLibraryW(path_wide.as_ptr()) }; if handle.is_null() { return Err(vm.new_runtime_error("LoadLibrary failed")); } @@ -520,7 +520,7 @@ mod _winapi { name: PyStrRef, vm: &VirtualMachine, ) -> PyResult<isize> { - let name_wide = name.as_str().to_wide_with_nul(); + let name_wide = name.as_wtf8().to_wide_with_nul(); let handle = unsafe { windows_sys::Win32::System::Threading::OpenMutexW( desired_access, @@ -565,13 +565,9 @@ mod _winapi { return Err(vm.new_value_error("unsupported flags")); } - // Use encode_wide() which properly handles WTF-8 (including surrogates) - let locale_wide: Vec<u16> = locale - .as_wtf8() - .encode_wide() - .chain(std::iter::once(0)) - .collect(); - let src_wide: Vec<u16> = src.as_wtf8().encode_wide().collect(); + // Use ToWideString which properly handles WTF-8 (including surrogates) + let locale_wide = locale.as_wtf8().to_wide_with_nul(); + let src_wide = src.as_wtf8().to_wide(); if src_wide.len() > i32::MAX as usize { return Err(vm.new_overflow_error("input string is too long".to_string())); @@ -648,7 +644,7 @@ mod _winapi { fn CreateNamedPipe(args: CreateNamedPipeArgs, vm: &VirtualMachine) -> PyResult<WinHandle> { use windows_sys::Win32::System::Pipes::CreateNamedPipeW; - let name_wide = args.name.as_str().to_wide_with_nul(); + let name_wide = args.name.as_wtf8().to_wide_with_nul(); let handle = unsafe { CreateNamedPipeW( From bfd873a8b87220ef702da0dedf81659bece34b8a Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 12 Dec 2025 01:13:28 +0900 Subject: [PATCH 476/819] a few unreachable->unimplemented, clippy (#6405) * unreachable -> unimplemented * Fix clippy on 1.92 --- crates/codegen/src/unparse.rs | 6 +++++- crates/stdlib/src/contextvars.rs | 4 ++-- crates/stdlib/src/ssl.rs | 2 +- crates/vm/src/builtins/bool.rs | 3 +-- crates/vm/src/builtins/classmethod.rs | 2 +- crates/vm/src/builtins/complex.rs | 1 - crates/vm/src/builtins/int.rs | 2 +- crates/vm/src/builtins/object.rs | 2 +- crates/vm/src/builtins/staticmethod.rs | 2 +- crates/vm/src/builtins/type.rs | 3 +-- crates/vm/src/builtins/weakref.rs | 2 +- crates/vm/src/class.rs | 1 - crates/vm/src/exceptions.rs | 2 +- crates/vm/src/function/protocol.rs | 1 - crates/vm/src/stdlib/ast/python.rs | 2 +- crates/vm/src/stdlib/ctypes/array.rs | 4 ++-- crates/vm/src/stdlib/ctypes/base.rs | 2 +- crates/vm/src/stdlib/ctypes/function.rs | 2 +- crates/vm/src/stdlib/ctypes/pointer.rs | 2 +- crates/vm/src/stdlib/ctypes/structure.rs | 4 ++-- crates/vm/src/stdlib/ctypes/union.rs | 4 ++-- crates/vm/src/stdlib/itertools.rs | 3 +-- crates/vm/src/stdlib/operator.rs | 1 - crates/vm/src/stdlib/os.rs | 2 ++ crates/vm/src/stdlib/typevar.rs | 4 ++-- crates/vm/src/types/slot.rs | 1 - crates/vm/src/vm/vm_object.rs | 1 - 27 files changed, 31 insertions(+), 34 deletions(-) diff --git a/crates/codegen/src/unparse.rs b/crates/codegen/src/unparse.rs index 7f2bcf16b47..74e35fd5e2a 100644 --- a/crates/codegen/src/unparse.rs +++ b/crates/codegen/src/unparse.rs @@ -380,7 +380,11 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { .fmt(self.f)? } Expr::NumberLiteral(ruff::ExprNumberLiteral { value, .. }) => { - const { assert!(f64::MAX_10_EXP == 308) }; + #[allow(clippy::correctness, clippy::assertions_on_constants)] + const { + assert!(f64::MAX_10_EXP == 308) + }; + let inf_str = "1e309"; match value { ruff::Number::Int(int) => int.fmt(self.f)?, diff --git a/crates/stdlib/src/contextvars.rs b/crates/stdlib/src/contextvars.rs index f48df167a71..56c2657f585 100644 --- a/crates/stdlib/src/contextvars.rs +++ b/crates/stdlib/src/contextvars.rs @@ -508,7 +508,7 @@ mod _contextvars { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } @@ -585,7 +585,7 @@ mod _contextvars { Err(vm.new_runtime_error("Tokens can only be created by ContextVars")) } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index ad8952aedb7..14e087668bb 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -4116,7 +4116,7 @@ mod _ssl { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 902656da2c8..9b519dbbde5 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -6,7 +6,6 @@ use crate::{ class::PyClassImpl, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{FuncArgs, OptionalArg}, - identifier, protocol::PyNumberMethods, types::{AsNumber, Constructor, Representable}, }; @@ -115,7 +114,7 @@ impl Constructor for PyBool { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/builtins/classmethod.rs b/crates/vm/src/builtins/classmethod.rs index 197b6805ef9..911960bf691 100644 --- a/crates/vm/src/builtins/classmethod.rs +++ b/crates/vm/src/builtins/classmethod.rs @@ -103,7 +103,7 @@ impl Constructor for PyClassMethod { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/builtins/complex.rs b/crates/vm/src/builtins/complex.rs index 3b38ab0dc94..8bf46cfdd5c 100644 --- a/crates/vm/src/builtins/complex.rs +++ b/crates/vm/src/builtins/complex.rs @@ -10,7 +10,6 @@ use crate::{ PyArithmeticValue::{self, *}, PyComparisonValue, }, - identifier, protocol::PyNumberMethods, stdlib::warnings, types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp, Representable}, diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index f019d2634a5..b210d41823d 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -244,7 +244,7 @@ impl Constructor for PyInt { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 735d5461186..6f917cd853c 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -111,7 +111,7 @@ impl Constructor for PyBaseObject { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/builtins/staticmethod.rs b/crates/vm/src/builtins/staticmethod.rs index 98717205503..5d2474a567c 100644 --- a/crates/vm/src/builtins/staticmethod.rs +++ b/crates/vm/src/builtins/staticmethod.rs @@ -61,7 +61,7 @@ impl Constructor for PyStaticMethod { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 11d74982681..a20e966d6ad 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -22,7 +22,6 @@ use crate::{ }, convert::ToPyResult, function::{FuncArgs, KwArgs, OptionalArg, PyMethodDef, PySetterValue}, - identifier, object::{Traverse, TraverseFn}, protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, types::{ @@ -1289,7 +1288,7 @@ impl Constructor for PyType { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/builtins/weakref.rs b/crates/vm/src/builtins/weakref.rs index 93edbbecbb1..88d6dbac3ed 100644 --- a/crates/vm/src/builtins/weakref.rs +++ b/crates/vm/src/builtins/weakref.rs @@ -45,7 +45,7 @@ impl Constructor for PyWeak { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index f977f07ca77..8850de36152 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -3,7 +3,6 @@ use crate::{ builtins::{PyBaseObject, PyType, PyTypeRef}, function::PyMethodDef, - identifier, object::Py, types::{PyTypeFlags, PyTypeSlots, hash_not_implemented}, vm::Context, diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index a5bc3f0a71b..4a8bea23557 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -717,7 +717,7 @@ impl Constructor for PyBaseException { } fn py_new(_cls: &Py<PyType>, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/function/protocol.rs b/crates/vm/src/function/protocol.rs index 3205f75c27d..1e670b96389 100644 --- a/crates/vm/src/function/protocol.rs +++ b/crates/vm/src/function/protocol.rs @@ -3,7 +3,6 @@ use crate::{ AsObject, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, builtins::{PyDict, PyDictRef, iter::PySequenceIterator}, convert::ToPyObject, - identifier, object::{Traverse, TraverseFn}, protocol::{PyIter, PyIterIter, PyMapping, PyMappingMethods}, types::{AsMapping, GenericMethod}, diff --git a/crates/vm/src/stdlib/ast/python.rs b/crates/vm/src/stdlib/ast/python.rs index ef8a85f85f2..042db4aa74e 100644 --- a/crates/vm/src/stdlib/ast/python.rs +++ b/crates/vm/src/stdlib/ast/python.rs @@ -77,7 +77,7 @@ pub(crate) mod _ast { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index d87679f3595..499b525b89b 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -76,7 +76,7 @@ impl Constructor for PyCArrayType { type Args = PyObjectRef; fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } @@ -305,7 +305,7 @@ impl Constructor for PyCArray { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 6b03e0e2348..22f3703f1b0 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -662,7 +662,7 @@ impl Constructor for PyCSimple { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 8784fd55ef5..d202410b14a 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -330,7 +330,7 @@ impl Constructor for PyCFuncPtr { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index d7fbd67b157..a5b3a9eb04e 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -90,7 +90,7 @@ impl Constructor for PyCPointer { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 58acc26d4ce..403dbad74b9 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -44,7 +44,7 @@ impl Constructor for PyCStructType { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } @@ -332,7 +332,7 @@ impl Constructor for PyCStructure { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index d260cfc9ecf..84afd0bec12 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -48,7 +48,7 @@ impl Constructor for PyCUnionType { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } @@ -191,7 +191,7 @@ impl Constructor for PyCUnion { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/stdlib/itertools.rs b/crates/vm/src/stdlib/itertools.rs index 2956654185d..3fedd17f12b 100644 --- a/crates/vm/src/stdlib/itertools.rs +++ b/crates/vm/src/stdlib/itertools.rs @@ -16,7 +16,6 @@ mod decl { }, convert::ToPyObject, function::{ArgCallable, ArgIntoBool, FuncArgs, OptionalArg, OptionalOption, PosArgs}, - identifier, protocol::{PyIter, PyIterReturn, PyNumber}, raise_if_stop, stdlib::{sys, warnings}, @@ -1256,7 +1255,7 @@ mod decl { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/stdlib/operator.rs b/crates/vm/src/stdlib/operator.rs index d61adaea561..0c048ea2a3f 100644 --- a/crates/vm/src/stdlib/operator.rs +++ b/crates/vm/src/stdlib/operator.rs @@ -7,7 +7,6 @@ mod _operator { builtins::{PyInt, PyIntRef, PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef}, common::wtf8::Wtf8, function::{ArgBytesLike, Either, FuncArgs, KwArgs, OptionalArg}, - identifier, protocol::PyIter, recursion::ReprGuard, types::{Callable, Constructor, PyComparisonOp, Representable}, diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index e42ec064cdf..b75f601c8db 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1506,7 +1506,9 @@ pub(super) mod _os { #[cfg(target_os = "linux")] #[pyfunction] fn copy_file_range(args: CopyFileRangeArgs<'_>, vm: &VirtualMachine) -> PyResult<usize> { + #[allow(clippy::unnecessary_option_map_or_else)] let p_offset_src = args.offset_src.as_ref().map_or_else(std::ptr::null, |x| x); + #[allow(clippy::unnecessary_option_map_or_else)] let p_offset_dst = args.offset_dst.as_ref().map_or_else(std::ptr::null, |x| x); let count: usize = args .count diff --git a/crates/vm/src/stdlib/typevar.rs b/crates/vm/src/stdlib/typevar.rs index 5509e547072..e8cc407da15 100644 --- a/crates/vm/src/stdlib/typevar.rs +++ b/crates/vm/src/stdlib/typevar.rs @@ -610,7 +610,7 @@ impl Constructor for ParamSpec { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } @@ -772,7 +772,7 @@ impl Constructor for TypeVarTuple { } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unreachable!("use slot_new") + unimplemented!("use slot_new") } } diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index e3775058d44..8166ca83357 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -7,7 +7,6 @@ use crate::{ function::{ Either, FromArgs, FuncArgs, OptionalArg, PyComparisonValue, PyMethodDef, PySetterValue, }, - identifier, protocol::{ PyBuffer, PyIterReturn, PyMapping, PyMappingMethods, PyNumber, PyNumberMethods, PyNumberSlots, PySequence, PySequenceMethods, diff --git a/crates/vm/src/vm/vm_object.rs b/crates/vm/src/vm/vm_object.rs index 69e8bcd8f35..e69301820d6 100644 --- a/crates/vm/src/vm/vm_object.rs +++ b/crates/vm/src/vm/vm_object.rs @@ -2,7 +2,6 @@ use super::PyMethod; use crate::{ builtins::{PyBaseExceptionRef, PyList, PyStrInterned, pystr::AsPyStr}, function::IntoFuncArgs, - identifier, object::{AsObject, PyObject, PyObjectRef, PyResult}, stdlib::sys, vm::VirtualMachine, From d34b2cf8f0e9d2188245e2cbb6e4ad35e3a4b5cb Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 12 Dec 2025 07:57:31 +0900 Subject: [PATCH 477/819] TypeData (#6403) * HeapTypeExt::type_data * Apply TypeDataSlot to ctypes --- crates/vm/src/builtins/type.rs | 50 ++++++- crates/vm/src/stdlib/ctypes.rs | 58 ++++---- crates/vm/src/stdlib/ctypes/array.rs | 167 ++++++++++++----------- crates/vm/src/stdlib/ctypes/base.rs | 25 ++-- crates/vm/src/stdlib/ctypes/pointer.rs | 30 ++-- crates/vm/src/stdlib/ctypes/structure.rs | 27 ++-- crates/vm/src/stdlib/ctypes/union.rs | 14 +- crates/vm/src/stdlib/ctypes/util.rs | 63 +++++++-- crates/vm/src/types/slot.rs | 94 ++++++++++++- 9 files changed, 348 insertions(+), 180 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index a20e966d6ad..0f619b1399a 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -26,12 +26,13 @@ use crate::{ protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, types::{ AsNumber, Callable, Constructor, GetAttr, PyTypeFlags, PyTypeSlots, Representable, SetAttr, + TypeDataRef, TypeDataRefMut, TypeDataSlot, }, }; use indexmap::{IndexMap, map::Entry}; use itertools::Itertools; use num_traits::ToPrimitive; -use std::{borrow::Borrow, collections::HashSet, ops::Deref, pin::Pin, ptr::NonNull}; +use std::{any::Any, borrow::Borrow, collections::HashSet, ops::Deref, pin::Pin, ptr::NonNull}; #[pyclass(module = false, name = "type", traverse = "manual")] pub struct PyType { @@ -65,6 +66,7 @@ pub struct HeapTypeExt { pub slots: Option<PyRef<PyTuple<PyStrRef>>>, pub sequence_methods: PySequenceMethods, pub mapping_methods: PyMappingMethods, + pub type_data: PyRwLock<Option<TypeDataSlot>>, } pub struct PointerSlot<T>(NonNull<T>); @@ -203,6 +205,7 @@ impl PyType { slots: None, sequence_methods: PySequenceMethods::default(), mapping_methods: PyMappingMethods::default(), + type_data: PyRwLock::new(None), }; let base = bases[0].clone(); @@ -563,6 +566,50 @@ impl PyType { |ext| PyRwLockReadGuard::map(ext.name.read(), |name| name.as_str()).into(), ) } + + // Type Data Slot API - CPython's PyObject_GetTypeData equivalent + + /// Initialize type data for this type. Can only be called once. + /// Returns an error if the type is not a heap type or if data is already initialized. + pub fn init_type_data<T: Any + Send + Sync + 'static>(&self, data: T) -> Result<(), String> { + let ext = self + .heaptype_ext + .as_ref() + .ok_or_else(|| "Cannot set type data on non-heap types".to_string())?; + + let mut type_data = ext.type_data.write(); + if type_data.is_some() { + return Err("Type data already initialized".to_string()); + } + *type_data = Some(TypeDataSlot::new(data)); + Ok(()) + } + + /// Get a read guard to the type data. + /// Returns None if the type is not a heap type, has no data, or the data type doesn't match. + pub fn get_type_data<T: Any + 'static>(&self) -> Option<TypeDataRef<'_, T>> { + self.heaptype_ext + .as_ref() + .and_then(|ext| TypeDataRef::try_new(ext.type_data.read())) + } + + /// Get a write guard to the type data. + /// Returns None if the type is not a heap type, has no data, or the data type doesn't match. + pub fn get_type_data_mut<T: Any + 'static>(&self) -> Option<TypeDataRefMut<'_, T>> { + self.heaptype_ext + .as_ref() + .and_then(|ext| TypeDataRefMut::try_new(ext.type_data.write())) + } + + /// Check if this type has type data of the given type. + pub fn has_type_data<T: Any + 'static>(&self) -> bool { + self.heaptype_ext.as_ref().is_some_and(|ext| { + ext.type_data + .read() + .as_ref() + .is_some_and(|slot| slot.get::<T>().is_some()) + }) + } } impl Py<PyType> { @@ -1167,6 +1214,7 @@ impl Constructor for PyType { slots: heaptype_slots.clone(), sequence_methods: PySequenceMethods::default(), mapping_methods: PyMappingMethods::default(), + type_data: PyRwLock::new(None), }; (slots, heaptype_ext) }; diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 7c35d4a700a..2daaa6f3abd 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -389,27 +389,28 @@ pub(crate) mod _ctypes { /// Get the size of a ctypes type or instance #[pyfunction(name = "sizeof")] pub fn size_of(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { - use super::array::{PyCArray, PyCArrayType}; use super::pointer::PyCPointer; use super::structure::{PyCStructType, PyCStructure}; - use super::union::{PyCUnion, PyCUnionType}; + use super::union::PyCUnionType; + use super::util::StgInfo; + use crate::builtins::PyType; - // 1. Instances with stg_info - if obj.fast_isinstance(PyCArray::static_type()) { - // Get stg_info from the type - if let Some(type_obj) = obj.class().as_object().downcast_ref::<PyCArrayType>() { - return Ok(type_obj.stg_info.size); - } + // 1. Check TypeDataSlot on class (for instances) + if let Some(stg_info) = obj.class().get_type_data::<StgInfo>() { + return Ok(stg_info.size); } + + // 2. Check TypeDataSlot on type itself (for type objects) + if let Some(type_obj) = obj.downcast_ref::<PyType>() + && let Some(stg_info) = type_obj.get_type_data::<StgInfo>() + { + return Ok(stg_info.size); + } + + // 3. Instances with cdata buffer if let Some(structure) = obj.downcast_ref::<PyCStructure>() { return Ok(structure.cdata.read().size()); } - if obj.fast_isinstance(PyCUnion::static_type()) { - // Get stg_info from the type - if let Some(type_obj) = obj.class().as_object().downcast_ref::<PyCUnionType>() { - return Ok(type_obj.stg_info.size); - } - } if let Some(simple) = obj.downcast_ref::<PyCSimple>() { return Ok(simple.cdata.read().size()); } @@ -417,11 +418,6 @@ pub(crate) mod _ctypes { return Ok(std::mem::size_of::<usize>()); } - // 2. Types (metatypes with stg_info) - if let Some(array_type) = obj.downcast_ref::<PyCArrayType>() { - return Ok(array_type.stg_info.size); - } - // 3. Type objects if let Ok(type_ref) = obj.clone().downcast::<crate::builtins::PyType>() { // Structure types - check if metaclass is or inherits from PyCStructType @@ -659,33 +655,37 @@ pub(crate) mod _ctypes { #[pyfunction] fn alignment(tp: Either<PyTypeRef, PyObjectRef>, vm: &VirtualMachine) -> PyResult<usize> { - use super::array::{PyCArray, PyCArrayType}; use super::base::PyCSimpleType; use super::pointer::PyCPointer; use super::structure::PyCStructure; use super::union::PyCUnion; + use super::util::StgInfo; + use crate::builtins::PyType; let obj = match &tp { Either::A(t) => t.as_object(), Either::B(o) => o.as_ref(), }; - // Try to get alignment from stg_info directly (for instances) - if let Some(array_type) = obj.downcast_ref::<PyCArrayType>() { - return Ok(array_type.stg_info.align); + // 1. Check TypeDataSlot on class (for instances) + if let Some(stg_info) = obj.class().get_type_data::<StgInfo>() { + return Ok(stg_info.align); } + + // 2. Check TypeDataSlot on type itself (for type objects) + if let Some(type_obj) = obj.downcast_ref::<PyType>() + && let Some(stg_info) = type_obj.get_type_data::<StgInfo>() + { + return Ok(stg_info.align); + } + + // 3. Fallback for simple types without TypeDataSlot if obj.fast_isinstance(PyCSimple::static_type()) { // Get stg_info from the type by reading _type_ attribute let cls = obj.class().to_owned(); let stg_info = PyCSimpleType::get_stg_info(&cls, vm); return Ok(stg_info.align); } - if obj.fast_isinstance(PyCArray::static_type()) { - // Get stg_info from the type - if let Some(type_obj) = obj.class().as_object().downcast_ref::<PyCArrayType>() { - return Ok(type_obj.stg_info.align); - } - } if obj.fast_isinstance(PyCStructure::static_type()) { // Calculate alignment from _fields_ let cls = obj.class(); diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 499b525b89b..98274e388bf 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -1,13 +1,13 @@ use crate::atomic_func; use crate::builtins::{PyBytes, PyInt}; -use crate::convert::ToPyObject; +use crate::class::StaticType; use crate::function::FuncArgs; use crate::protocol::{ BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods, PySequenceMethods, }; use crate::stdlib::ctypes::base::CDataObject; use crate::stdlib::ctypes::util::StgInfo; -use crate::types::{AsBuffer, AsNumber, AsSequence, Callable}; +use crate::types::{AsBuffer, AsNumber, AsSequence}; use crate::{AsObject, Py, PyObjectRef, PyPayload}; use crate::{ PyResult, VirtualMachine, @@ -20,56 +20,49 @@ use rustpython_common::lock::PyRwLock; use rustpython_vm::stdlib::ctypes::_ctypes::get_size; use rustpython_vm::stdlib::ctypes::base::PyCData; +/// PyCArrayType - metatype for Array types +/// CPython stores array info (type, length) in StgInfo via type_data #[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")] -#[derive(PyPayload)] -pub struct PyCArrayType { - pub(super) stg_info: StgInfo, - pub(super) typ: PyRwLock<PyObjectRef>, - pub(super) length: AtomicCell<usize>, - pub(super) element_size: AtomicCell<usize>, -} - -impl std::fmt::Debug for PyCArrayType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PyCArrayType") - .field("typ", &self.typ) - .field("length", &self.length) - .finish() - } -} - -impl Callable for PyCArrayType { - type Args = FuncArgs; - fn call(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult { - // Create an instance of the array - let element_type = zelf.typ.read().clone(); - let length = zelf.length.load(); - let element_size = zelf.element_size.load(); - let total_size = element_size * length; - let mut buffer = vec![0u8; total_size]; - - // Initialize from positional arguments - for (i, value) in args.args.iter().enumerate() { - if i >= length { - break; - } - let offset = i * element_size; - if let Ok(int_val) = value.try_int(vm) { - let bytes = PyCArray::int_to_bytes(int_val.as_bigint(), element_size); - if offset + element_size <= buffer.len() { - buffer[offset..offset + element_size].copy_from_slice(&bytes); - } - } +#[derive(Debug, Default, PyPayload)] +pub struct PyCArrayType {} + +/// Create a new Array type with StgInfo stored in type_data (CPython style) +pub fn create_array_type_with_stg_info(stg_info: StgInfo, vm: &VirtualMachine) -> PyResult { + // Get PyCArrayType as metaclass + let metaclass = PyCArrayType::static_type().to_owned(); + + // Create a unique name for the array type + let type_name = format!("Array_{}", stg_info.length); + + // Create args for type(): (name, bases, dict) + let name = vm.ctx.new_str(type_name); + let bases = vm + .ctx + .new_tuple(vec![PyCArray::static_type().to_owned().into()]); + let dict = vm.ctx.new_dict(); + + let args = FuncArgs::new( + vec![name.into(), bases.into(), dict.into()], + crate::function::KwArgs::default(), + ); + + // Create the new type using PyType::slot_new with PyCArrayType as metaclass + let new_type = crate::builtins::type_::PyType::slot_new(metaclass, args, vm)?; + + // Set StgInfo in type_data + let type_ref: PyTypeRef = new_type + .clone() + .downcast() + .map_err(|_| vm.new_type_error("Failed to create array type".to_owned()))?; + + if type_ref.init_type_data(stg_info.clone()).is_err() { + // Type data already initialized - update it + if let Some(mut existing) = type_ref.get_type_data_mut::<StgInfo>() { + *existing = stg_info; } - - Ok(PyCArray { - typ: PyRwLock::new(element_type), - length: AtomicCell::new(length), - element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)), - } - .into_pyobject(vm)) } + + Ok(new_type) } impl Constructor for PyCArrayType { @@ -80,54 +73,62 @@ impl Constructor for PyCArrayType { } } -#[pyclass(flags(IMMUTABLETYPE), with(Callable, Constructor, AsNumber))] +#[pyclass(flags(IMMUTABLETYPE), with(Constructor, AsNumber))] impl PyCArrayType { #[pygetset(name = "_type_")] - fn typ(&self) -> PyObjectRef { - self.typ.read().clone() + fn typ(zelf: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + zelf.downcast_ref::<PyType>() + .and_then(|t| t.get_type_data::<StgInfo>()) + .and_then(|stg| stg.element_type.clone()) + .unwrap_or_else(|| vm.ctx.none()) } #[pygetset(name = "_length_")] - fn length(&self) -> usize { - self.length.load() + fn length(zelf: PyObjectRef) -> usize { + zelf.downcast_ref::<PyType>() + .and_then(|t| t.get_type_data::<StgInfo>()) + .map(|stg| stg.length) + .unwrap_or(0) } #[pymethod] - fn __mul__(zelf: &Py<Self>, n: isize, vm: &VirtualMachine) -> PyResult { + fn __mul__(zelf: PyObjectRef, n: isize, vm: &VirtualMachine) -> PyResult { if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } - // Create a nested array type: (inner_type * inner_length) * n - // The new array has n elements, each element is the current array type - // e.g., (c_int * 5) * 3 = Array of 3 elements, each is (c_int * 5) - let inner_length = zelf.length.load(); - let inner_element_size = zelf.element_size.load(); + + // Get inner array info from TypeDataSlot + let type_ref = zelf.downcast_ref::<PyType>().unwrap(); + let (_inner_length, inner_size) = type_ref + .get_type_data::<StgInfo>() + .map(|stg| (stg.length, stg.size)) + .unwrap_or((0, 0)); // The element type of the new array is the current array type itself - let current_array_type: PyObjectRef = zelf.as_object().to_owned(); + let current_array_type: PyObjectRef = zelf.clone(); // Element size is the total size of the inner array - let new_element_size = inner_length * inner_element_size; + let new_element_size = inner_size; let total_size = new_element_size * (n as usize); - let stg_info = StgInfo::new(total_size, inner_element_size); - Ok(PyCArrayType { - stg_info, - typ: PyRwLock::new(current_array_type), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(new_element_size), - } - .to_pyobject(vm)) + let stg_info = StgInfo::new_array( + total_size, + new_element_size, + n as usize, + current_array_type, + new_element_size, + ); + + create_array_type_with_stg_info(stg_info, vm) } #[pyclassmethod] fn in_dll( - zelf: &Py<Self>, + zelf: PyObjectRef, dll: PyObjectRef, name: crate::builtins::PyStrRef, vm: &VirtualMachine, ) -> PyResult { - use crate::stdlib::ctypes::_ctypes::size_of; use libloading::Symbol; // Get the library handle from dll object @@ -168,10 +169,18 @@ impl PyCArrayType { return Err(vm.new_attribute_error("Library is closed".to_owned())); }; - // Get size from the array type - let element_type = zelf.typ.read().clone(); - let length = zelf.length.load(); - let element_size = size_of(element_type.clone(), vm)?; + // Get size from the array type via TypeDataSlot + let type_ref = zelf.downcast_ref::<PyType>().unwrap(); + let (element_type, length, element_size) = type_ref + .get_type_data::<StgInfo>() + .map(|stg| { + ( + stg.element_type.clone().unwrap_or_else(|| vm.ctx.none()), + stg.length, + stg.element_size, + ) + }) + .unwrap_or_else(|| (vm.ctx.none(), 0, 0)); let total_size = element_size * length; // Read data from symbol address @@ -206,15 +215,13 @@ impl AsNumber for PyCArrayType { fn as_number() -> &'static PyNumberMethods { static AS_NUMBER: PyNumberMethods = PyNumberMethods { multiply: Some(|a, b, vm| { - let zelf = a - .downcast_ref::<PyCArrayType>() - .ok_or_else(|| vm.new_type_error("expected PyCArrayType".to_owned()))?; + // a is a type object whose metaclass is PyCArrayType (e.g., Array_5) let n = b .try_index(vm)? .as_bigint() .to_isize() .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; - PyCArrayType::__mul__(zelf, n, vm) + PyCArrayType::__mul__(a.to_owned(), n, vm) }), ..PyNumberMethods::NOT_IMPLEMENTED }; diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 22f3703f1b0..80d42fba3ce 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -1,8 +1,6 @@ use super::_ctypes::bytes_to_pyobject; -use super::array::PyCArrayType; use super::util::StgInfo; use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyStrRef, PyType, PyTypeRef}; -use crate::convert::ToPyObject; use crate::function::{ArgBytesLike, Either, FuncArgs, KwArgs, OptionalArg}; use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; use crate::stdlib::ctypes::_ctypes::new_simple_type; @@ -231,10 +229,7 @@ impl PyCData { #[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)] #[derive(Debug, PyPayload, Default)] -pub struct PyCSimpleType { - #[allow(dead_code)] - pub stg_info: StgInfo, -} +pub struct PyCSimpleType {} #[pyclass(flags(BASETYPE), with(AsNumber))] impl PyCSimpleType { @@ -747,6 +742,8 @@ impl PyCSimple { #[pyclassmethod] fn repeat(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { use super::_ctypes::get_size; + use super::array::create_array_type_with_stg_info; + if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } @@ -766,14 +763,14 @@ impl PyCSimple { std::mem::size_of::<usize>() }; let total_size = element_size * (n as usize); - let stg_info = super::util::StgInfo::new(total_size, element_size); - Ok(PyCArrayType { - stg_info, - typ: PyRwLock::new(cls.clone().into()), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - } - .to_pyobject(vm)) + let stg_info = super::util::StgInfo::new_array( + total_size, + element_size, + n as usize, + cls.clone().into(), + element_size, + ); + create_array_type_with_stg_info(stg_info, vm) } #[pyclassmethod] diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index a5b3a9eb04e..156c4e54ee5 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -1,44 +1,36 @@ -use crossbeam_utils::atomic::AtomicCell; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; use crate::builtins::{PyType, PyTypeRef}; -use crate::convert::ToPyObject; use crate::function::FuncArgs; use crate::protocol::PyNumberMethods; use crate::stdlib::ctypes::PyCData; use crate::types::{AsNumber, Constructor}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; -use super::util::StgInfo; - #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] -#[derive(PyPayload, Debug)] -pub struct PyCPointerType { - #[allow(dead_code)] - pub stg_info: StgInfo, -} +#[derive(PyPayload, Debug, Default)] +pub struct PyCPointerType {} #[pyclass(flags(IMMUTABLETYPE), with(AsNumber))] impl PyCPointerType { #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - use super::array::PyCArrayType; + use super::array::create_array_type_with_stg_info; if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } // Pointer size let element_size = std::mem::size_of::<usize>(); let total_size = element_size * (n as usize); - let mut stg_info = super::util::StgInfo::new(total_size, element_size); - stg_info.length = n as usize; - Ok(PyCArrayType { - stg_info, - typ: PyRwLock::new(cls.as_object().to_owned()), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - } - .to_pyobject(vm)) + let stg_info = super::util::StgInfo::new_array( + total_size, + element_size, + n as usize, + cls.as_object().to_owned(), + element_size, + ); + create_array_type_with_stg_info(stg_info, vm) } } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 403dbad74b9..d3cdea69c72 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -8,7 +8,6 @@ use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods use crate::stdlib::ctypes::_ctypes::get_size; use crate::types::{AsBuffer, AsNumber, Constructor}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; -use crossbeam_utils::atomic::AtomicCell; use indexmap::IndexMap; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; @@ -16,11 +15,8 @@ use std::fmt::Debug; /// PyCStructType - metaclass for Structure #[pyclass(name = "PyCStructType", base = PyType, module = "_ctypes")] -#[derive(Debug, PyPayload)] -pub struct PyCStructType { - #[allow(dead_code)] - pub stg_info: StgInfo, -} +#[derive(Debug, PyPayload, Default)] +pub struct PyCStructType {} impl Constructor for PyCStructType { type Args = FuncArgs; @@ -161,7 +157,7 @@ impl PyCStructType { #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - use super::array::PyCArrayType; + use super::array::create_array_type_with_stg_info; use crate::stdlib::ctypes::_ctypes::size_of; if n < 0 { @@ -174,15 +170,14 @@ impl PyCStructType { let total_size = element_size .checked_mul(n as usize) .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; - let mut stg_info = super::util::StgInfo::new(total_size, element_size); - stg_info.length = n as usize; - Ok(PyCArrayType { - stg_info, - typ: PyRwLock::new(cls.clone().into()), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - } - .to_pyobject(vm)) + let stg_info = super::util::StgInfo::new_array( + total_size, + element_size, + n as usize, + cls.clone().into(), + element_size, + ); + create_array_type_with_stg_info(stg_info, vm) } } diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 84afd0bec12..37d8e4f688b 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -13,18 +13,8 @@ use rustpython_common::lock::PyRwLock; /// PyCUnionType - metaclass for Union #[pyclass(name = "UnionType", base = PyType, module = "_ctypes")] -#[derive(Debug, PyPayload)] -pub struct PyCUnionType { - pub stg_info: StgInfo, -} - -impl Default for PyCUnionType { - fn default() -> Self { - PyCUnionType { - stg_info: StgInfo::new(0, 1), - } - } -} +#[derive(Debug, PyPayload, Default)] +pub struct PyCUnionType {} impl Constructor for PyCUnionType { type Args = FuncArgs; diff --git a/crates/vm/src/stdlib/ctypes/util.rs b/crates/vm/src/stdlib/ctypes/util.rs index b8fd9c89c0b..b8c6def63ca 100644 --- a/crates/vm/src/stdlib/ctypes/util.rs +++ b/crates/vm/src/stdlib/ctypes/util.rs @@ -1,17 +1,40 @@ use crate::PyObjectRef; /// Storage information for ctypes types -#[derive(Debug, Clone)] +/// Stored in TypeDataSlot of heap types (PyType::init_type_data/get_type_data) +#[derive(Clone)] pub struct StgInfo { - #[allow(dead_code)] pub initialized: bool, - pub size: usize, // number of bytes - pub align: usize, // alignment requirements - pub length: usize, // number of fields (for arrays/structures) - #[allow(dead_code)] + pub size: usize, // number of bytes + pub align: usize, // alignment requirements + pub length: usize, // number of fields (for arrays/structures) pub proto: Option<PyObjectRef>, // Only for Pointer/ArrayObject - #[allow(dead_code)] - pub flags: i32, // calling convention and such + pub flags: i32, // calling convention and such + + // Array-specific fields (moved from PyCArrayType) + pub element_type: Option<PyObjectRef>, // _type_ for arrays + pub element_size: usize, // size of each element +} + +// StgInfo is stored in type_data which requires Send + Sync. +// The PyObjectRef in proto/element_type fields is protected by the type system's locking mechanism. +// CPython: ctypes objects are not thread-safe by design; users must synchronize access. +unsafe impl Send for StgInfo {} +unsafe impl Sync for StgInfo {} + +impl std::fmt::Debug for StgInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("StgInfo") + .field("initialized", &self.initialized) + .field("size", &self.size) + .field("align", &self.align) + .field("length", &self.length) + .field("proto", &self.proto) + .field("flags", &self.flags) + .field("element_type", &self.element_type) + .field("element_size", &self.element_size) + .finish() + } } impl Default for StgInfo { @@ -23,6 +46,8 @@ impl Default for StgInfo { length: 0, proto: None, flags: 0, + element_type: None, + element_size: 0, } } } @@ -36,6 +61,28 @@ impl StgInfo { length: 0, proto: None, flags: 0, + element_type: None, + element_size: 0, + } + } + + /// Create StgInfo for an array type + pub fn new_array( + size: usize, + align: usize, + length: usize, + element_type: PyObjectRef, + element_size: usize, + ) -> Self { + StgInfo { + initialized: true, + size, + align, + length, + proto: None, + flags: 0, + element_type: Some(element_type), + element_size, } } } diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 8166ca83357..a4169fdd837 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -1,3 +1,6 @@ +use crate::common::lock::{ + PyMappedRwLockReadGuard, PyMappedRwLockWriteGuard, PyRwLockReadGuard, PyRwLockWriteGuard, +}; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyInt, PyStr, PyStrInterned, PyStrRef, PyType, PyTypeRef, type_::PointerSlot}, @@ -16,7 +19,96 @@ use crate::{ use crossbeam_utils::atomic::AtomicCell; use malachite_bigint::BigInt; use num_traits::{Signed, ToPrimitive}; -use std::{borrow::Borrow, cmp::Ordering, ops::Deref}; +use std::{any::Any, any::TypeId, borrow::Borrow, cmp::Ordering, ops::Deref}; + +/// Type-erased storage for extension module data attached to heap types. +pub struct TypeDataSlot { + // PyObject_GetTypeData + type_id: TypeId, + data: Box<dyn Any + Send + Sync>, +} + +impl TypeDataSlot { + /// Create a new type data slot with the given data. + pub fn new<T: Any + Send + Sync + 'static>(data: T) -> Self { + Self { + type_id: TypeId::of::<T>(), + data: Box::new(data), + } + } + + /// Get a reference to the data if the type matches. + pub fn get<T: Any + 'static>(&self) -> Option<&T> { + if self.type_id == TypeId::of::<T>() { + self.data.downcast_ref() + } else { + None + } + } + + /// Get a mutable reference to the data if the type matches. + pub fn get_mut<T: Any + 'static>(&mut self) -> Option<&mut T> { + if self.type_id == TypeId::of::<T>() { + self.data.downcast_mut() + } else { + None + } + } +} + +/// Read guard for type data access, using mapped guard for zero-cost deref. +pub struct TypeDataRef<'a, T: 'static> { + guard: PyMappedRwLockReadGuard<'a, T>, +} + +impl<'a, T: Any + 'static> TypeDataRef<'a, T> { + /// Try to create a TypeDataRef from a read guard. + /// Returns None if the slot is empty or contains a different type. + pub fn try_new(guard: PyRwLockReadGuard<'a, Option<TypeDataSlot>>) -> Option<Self> { + PyRwLockReadGuard::try_map(guard, |opt| opt.as_ref().and_then(|slot| slot.get::<T>())) + .ok() + .map(|guard| Self { guard }) + } +} + +impl<T: Any + 'static> std::ops::Deref for TypeDataRef<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.guard + } +} + +/// Write guard for type data access, using mapped guard for zero-cost deref. +pub struct TypeDataRefMut<'a, T: 'static> { + guard: PyMappedRwLockWriteGuard<'a, T>, +} + +impl<'a, T: Any + 'static> TypeDataRefMut<'a, T> { + /// Try to create a TypeDataRefMut from a write guard. + /// Returns None if the slot is empty or contains a different type. + pub fn try_new(guard: PyRwLockWriteGuard<'a, Option<TypeDataSlot>>) -> Option<Self> { + PyRwLockWriteGuard::try_map(guard, |opt| { + opt.as_mut().and_then(|slot| slot.get_mut::<T>()) + }) + .ok() + .map(|guard| Self { guard }) + } +} + +impl<T: Any + 'static> std::ops::Deref for TypeDataRefMut<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.guard + } +} + +impl<T: Any + 'static> std::ops::DerefMut for TypeDataRefMut<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.guard + } +} #[macro_export] macro_rules! atomic_func { From 3438fb2850a3ff8afff5f5fc8e55262907ad09c3 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 12 Dec 2025 09:03:19 +0900 Subject: [PATCH 478/819] nt junction (#6407) * pylib strip path * nt._path_* functions, * nt junction --- Lib/test/test_ntpath.py | 1 - Lib/test/test_os.py | 1 - crates/pylib/build.rs | 8 +- crates/vm/Cargo.toml | 2 + crates/vm/src/stdlib/nt.rs | 492 ++++++++++++++++++++++++++++++++++--- 5 files changed, 470 insertions(+), 34 deletions(-) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index c297e6b36ec..ed9d8b5d281 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -1383,7 +1383,6 @@ def test_nt_helpers(self): self.assertIsInstance(b_final_path, bytes) self.assertGreater(len(b_final_path), 0) - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.platform != 'win32', "Can only test junctions with creation on win32.") def test_isjunction(self): with os_helper.temp_dir() as d: diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 657a69c4ec9..105629bda19 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3130,7 +3130,6 @@ def tearDown(self): if os.path.lexists(self.junction): os.unlink(self.junction) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (AttributeError: module '_winapi' has no attribute 'CreateJunction')") def test_create_junction(self): _winapi.CreateJunction(self.junction_target, self.junction) self.assertTrue(os.path.lexists(self.junction)) diff --git a/crates/pylib/build.rs b/crates/pylib/build.rs index 8885870e40f..9b135690f82 100644 --- a/crates/pylib/build.rs +++ b/crates/pylib/build.rs @@ -16,10 +16,10 @@ fn main() { { let canonicalized_path = std::fs::canonicalize(real_path) .expect("failed to resolve RUSTPYTHONPATH during build time"); - println!( - "cargo:rustc-env=win_lib_path={}", - canonicalized_path.to_str().unwrap() - ); + // Strip the extended path prefix (\\?\) that canonicalize adds on Windows + let path_str = canonicalized_path.to_str().unwrap(); + let path_str = path_str.strip_prefix(r"\\?\").unwrap_or(path_str); + println!("cargo:rustc-env=win_lib_path={path_str}"); } } diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 8c3cee37aa3..b74aba41145 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -124,7 +124,9 @@ features = [ "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_Environment", + "Win32_System_IO", "Win32_System_Ioctl", + "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index bc82e0a74a3..ce68b9d25f8 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -18,7 +18,7 @@ pub(crate) mod module { common::{crt_fd, suppress_iph, windows::ToWideString}, convert::ToPyException, function::{Either, OptionalArg}, - ospath::OsPath, + ospath::{OsPath, OsPathOrFd}, stdlib::os::{_os, DirFd, SupportFunc, TargetIsDirectory}, }; @@ -237,7 +237,334 @@ pub(crate) mod module { Ok(vm.ctx.new_str(filename_str).to_owned()) } - // cwait is available on MSVC only (according to CPython) + #[derive(FromArgs)] + struct PathArg { + #[pyarg(any)] + path: crate::PyObjectRef, + } + + impl PathArg { + fn to_path_or_fd(&self, vm: &VirtualMachine) -> Option<OsPathOrFd<'static>> { + OsPathOrFd::try_from_object(vm, self.path.clone()).ok() + } + } + + // File type test constants (PY_IF* constants - internal, not from Windows API) + const PY_IFREG: u32 = 1; // Regular file + const PY_IFDIR: u32 = 2; // Directory + const PY_IFLNK: u32 = 4; // Symlink + const PY_IFMNT: u32 = 8; // Mount point (junction) + + /// _testInfo - determine file type based on attributes and reparse tag + fn _test_info(attributes: u32, reparse_tag: u32, disk_device: bool, tested_type: u32) -> bool { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_REPARSE_POINT, + }; + use windows_sys::Win32::System::SystemServices::{ + IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, + }; + + match tested_type { + PY_IFREG => { + // diskDevice && attributes && !(attributes & FILE_ATTRIBUTE_DIRECTORY) + disk_device && attributes != 0 && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0 + } + PY_IFDIR => (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0, + PY_IFLNK => { + (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 + && reparse_tag == IO_REPARSE_TAG_SYMLINK + } + PY_IFMNT => { + (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 + && reparse_tag == IO_REPARSE_TAG_MOUNT_POINT + } + _ => false, + } + } + + /// _testFileTypeByHandle - test file type using an open handle + fn _test_file_type_by_handle( + handle: windows_sys::Win32::Foundation::HANDLE, + tested_type: u32, + disk_only: bool, + ) -> bool { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_TAG_INFO, FILE_BASIC_INFO, FILE_TYPE_DISK, + FileAttributeTagInfo as FileAttributeTagInfoClass, FileBasicInfo, + GetFileInformationByHandleEx, GetFileType, + }; + + let disk_device = unsafe { GetFileType(handle) } == FILE_TYPE_DISK; + if disk_only && !disk_device { + return false; + } + + if tested_type != PY_IFREG && tested_type != PY_IFDIR { + // For symlinks/junctions, need FileAttributeTagInfo to get reparse tag + let mut info: FILE_ATTRIBUTE_TAG_INFO = unsafe { std::mem::zeroed() }; + let ret = unsafe { + GetFileInformationByHandleEx( + handle, + FileAttributeTagInfoClass, + &mut info as *mut _ as *mut _, + std::mem::size_of::<FILE_ATTRIBUTE_TAG_INFO>() as u32, + ) + }; + if ret == 0 { + return false; + } + _test_info( + info.FileAttributes, + info.ReparseTag, + disk_device, + tested_type, + ) + } else { + // For regular files/directories, FileBasicInfo is sufficient + let mut info: FILE_BASIC_INFO = unsafe { std::mem::zeroed() }; + let ret = unsafe { + GetFileInformationByHandleEx( + handle, + FileBasicInfo, + &mut info as *mut _ as *mut _, + std::mem::size_of::<FILE_BASIC_INFO>() as u32, + ) + }; + if ret == 0 { + return false; + } + _test_info(info.FileAttributes, 0, disk_device, tested_type) + } + } + + /// _testFileTypeByName - test file type by path name + fn _test_file_type_by_name(path: &std::path::Path, tested_type: u32) -> bool { + use crate::common::windows::ToWideString; + use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; + use windows_sys::Win32::Storage::FileSystem::{ + CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, + FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, + OPEN_EXISTING, + }; + + // For islink/isjunction, use symlink_metadata to check reparse points + if (tested_type == PY_IFLNK || tested_type == PY_IFMNT) + && let Ok(meta) = path.symlink_metadata() + { + use std::os::windows::fs::MetadataExt; + let attrs = meta.file_attributes(); + use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT; + if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) == 0 { + return false; + } + // Need to check reparse tag, fall through to CreateFileW + } + + let wide_path = path.to_wide_with_nul(); + + // For symlinks/junctions, add FILE_FLAG_OPEN_REPARSE_POINT to not follow + let mut flags = FILE_FLAG_BACKUP_SEMANTICS; + if tested_type != PY_IFREG && tested_type != PY_IFDIR { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + + // Use sharing flags to avoid access denied errors + let handle = unsafe { + CreateFileW( + wide_path.as_ptr(), + FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + std::ptr::null(), + OPEN_EXISTING, + flags, + std::ptr::null_mut(), + ) + }; + + if handle == INVALID_HANDLE_VALUE { + // Fallback: try using Rust's metadata for isdir/isfile + if tested_type == PY_IFDIR { + return path.metadata().is_ok_and(|m| m.is_dir()); + } else if tested_type == PY_IFREG { + return path.metadata().is_ok_and(|m| m.is_file()); + } + // For symlinks/junctions, try without FILE_FLAG_BACKUP_SEMANTICS + let handle = unsafe { + CreateFileW( + wide_path.as_ptr(), + FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + std::ptr::null(), + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT, + std::ptr::null_mut(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + return false; + } + let result = _test_file_type_by_handle(handle, tested_type, true); + unsafe { CloseHandle(handle) }; + return result; + } + + let result = _test_file_type_by_handle(handle, tested_type, true); + unsafe { CloseHandle(handle) }; + result + } + + /// _testFileExistsByName - test if path exists + fn _test_file_exists_by_name(path: &std::path::Path, follow_links: bool) -> bool { + use crate::common::windows::ToWideString; + use windows_sys::Win32::Foundation::{CloseHandle, GENERIC_READ, INVALID_HANDLE_VALUE}; + use windows_sys::Win32::Storage::FileSystem::{ + CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, + FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, + OPEN_EXISTING, + }; + + // First try standard Rust exists/symlink_metadata (handles \\?\ paths well) + if follow_links { + if path.exists() { + return true; + } + } else if path.symlink_metadata().is_ok() { + return true; + } + + let wide_path = path.to_wide_with_nul(); + + let mut flags = FILE_FLAG_BACKUP_SEMANTICS; + if !follow_links { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + + // Fallback: try with FILE_READ_ATTRIBUTES and sharing flags + let handle = unsafe { + CreateFileW( + wide_path.as_ptr(), + FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + std::ptr::null(), + OPEN_EXISTING, + flags, + std::ptr::null_mut(), + ) + }; + + if handle != INVALID_HANDLE_VALUE { + unsafe { CloseHandle(handle) }; + return true; + } + + // Fallback for console devices like \\.\CON + let handle = unsafe { + CreateFileW( + wide_path.as_ptr(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + std::ptr::null(), + OPEN_EXISTING, + 0, + std::ptr::null_mut(), + ) + }; + + if handle != INVALID_HANDLE_VALUE { + unsafe { CloseHandle(handle) }; + return true; + } + + false + } + + /// _testFileType wrapper - handles both fd and path + fn _test_file_type(path_or_fd: &OsPathOrFd<'_>, tested_type: u32) -> bool { + match path_or_fd { + OsPathOrFd::Fd(fd) => { + if let Ok(handle) = crate::common::crt_fd::as_handle(*fd) { + use std::os::windows::io::AsRawHandle; + _test_file_type_by_handle(handle.as_raw_handle() as _, tested_type, true) + } else { + false + } + } + OsPathOrFd::Path(path) => _test_file_type_by_name(path.as_ref(), tested_type), + } + } + + /// _testFileExists wrapper - handles both fd and path + fn _test_file_exists(path_or_fd: &OsPathOrFd<'_>, follow_links: bool) -> bool { + use windows_sys::Win32::Storage::FileSystem::{FILE_TYPE_UNKNOWN, GetFileType}; + + match path_or_fd { + OsPathOrFd::Fd(fd) => { + if let Ok(handle) = crate::common::crt_fd::as_handle(*fd) { + use std::os::windows::io::AsRawHandle; + let file_type = unsafe { GetFileType(handle.as_raw_handle() as _) }; + // GetFileType(hfile) != FILE_TYPE_UNKNOWN || !GetLastError() + if file_type != FILE_TYPE_UNKNOWN { + return true; + } + // Check if GetLastError is 0 (no error means valid handle) + unsafe { windows_sys::Win32::Foundation::GetLastError() == 0 } + } else { + false + } + } + OsPathOrFd::Path(path) => _test_file_exists_by_name(path.as_ref(), follow_links), + } + } + + /// Check if a path is a directory. + /// return _testFileType(path, PY_IFDIR) + #[pyfunction] + fn _path_isdir(args: PathArg, vm: &VirtualMachine) -> bool { + args.to_path_or_fd(vm) + .is_some_and(|p| _test_file_type(&p, PY_IFDIR)) + } + + /// Check if a path is a regular file. + /// return _testFileType(path, PY_IFREG) + #[pyfunction] + fn _path_isfile(args: PathArg, vm: &VirtualMachine) -> bool { + args.to_path_or_fd(vm) + .is_some_and(|p| _test_file_type(&p, PY_IFREG)) + } + + /// Check if a path is a symbolic link. + /// return _testFileType(path, PY_IFLNK) + #[pyfunction] + fn _path_islink(args: PathArg, vm: &VirtualMachine) -> bool { + args.to_path_or_fd(vm) + .is_some_and(|p| _test_file_type(&p, PY_IFLNK)) + } + + /// Check if a path is a junction (mount point). + /// return _testFileType(path, PY_IFMNT) + #[pyfunction] + fn _path_isjunction(args: PathArg, vm: &VirtualMachine) -> bool { + args.to_path_or_fd(vm) + .is_some_and(|p| _test_file_type(&p, PY_IFMNT)) + } + + /// Check if a path exists (follows symlinks). + /// return _testFileExists(path, TRUE) + #[pyfunction] + fn _path_exists(args: PathArg, vm: &VirtualMachine) -> bool { + args.to_path_or_fd(vm) + .is_some_and(|p| _test_file_exists(&p, true)) + } + + /// Check if a path exists (does not follow symlinks). + /// return _testFileExists(path, FALSE) + #[pyfunction] + fn _path_lexists(args: PathArg, vm: &VirtualMachine) -> bool { + args.to_path_or_fd(vm) + .is_some_and(|p| _test_file_exists(&p, false)) + } + + // cwait is available on MSVC only #[cfg(target_env = "msvc")] unsafe extern "C" { fn _cwait(termstat: *mut i32, procHandle: intptr_t, action: i32) -> intptr_t; @@ -649,7 +976,7 @@ pub(crate) mod module { Ok(path.mode.process_path(buffer.to_os_string(), vm)) } - /// Implements CPython's _Py_skiproot logic for Windows paths + /// Implements _Py_skiproot logic for Windows paths /// Returns (drive_size, root_size) where: /// - drive_size: length of the drive/UNC portion /// - root_size: length of the root separator (0 or 1) @@ -726,7 +1053,7 @@ pub(crate) mod module { use crate::builtins::{PyBytes, PyStr}; use rustpython_common::wtf8::Wtf8Buf; - // Handle path-like objects via os.fspath, but without null check (nonstrict=True in CPython) + // Handle path-like objects via os.fspath, but without null check (nonstrict=True) let path = if let Some(fspath) = vm.get_method(path.clone(), identifier!(vm, __fspath__)) { fspath?.call((), vm)? } else { @@ -1051,7 +1378,7 @@ pub(crate) mod module { let [] = args.dir_fd.0; let wide = args.path.to_wide_cstring(vm)?; - // CPython special case: mode 0o700 sets a protected ACL + // special case: mode 0o700 sets a protected ACL let res = if args.mode == 0o700 { let mut sec_attr = SECURITY_ATTRIBUTES { nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32, @@ -1148,17 +1475,7 @@ pub(crate) mod module { #[pyfunction] fn getppid() -> u32 { - use windows_sys::Win32::System::Threading::GetCurrentProcess; - - #[repr(C)] - struct ProcessBasicInformation { - exit_status: isize, - peb_base_address: *mut std::ffi::c_void, - affinity_mask: usize, - base_priority: i32, - unique_process_id: usize, - inherited_from_unique_process_id: usize, - } + use windows_sys::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION}; type NtQueryInformationProcessFn = unsafe extern "system" fn( process_handle: isize, @@ -1188,30 +1505,23 @@ pub(crate) mod module { }; let nt_query: NtQueryInformationProcessFn = unsafe { std::mem::transmute(func) }; - let mut info = ProcessBasicInformation { - exit_status: 0, - peb_base_address: std::ptr::null_mut(), - affinity_mask: 0, - base_priority: 0, - unique_process_id: 0, - inherited_from_unique_process_id: 0, - }; + let mut info: PROCESS_BASIC_INFORMATION = unsafe { std::mem::zeroed() }; let status = unsafe { nt_query( GetCurrentProcess() as isize, 0, // ProcessBasicInformation &mut info as *mut _ as *mut std::ffi::c_void, - std::mem::size_of::<ProcessBasicInformation>() as u32, + std::mem::size_of::<PROCESS_BASIC_INFORMATION>() as u32, std::ptr::null_mut(), ) }; if status >= 0 - && info.inherited_from_unique_process_id != 0 - && info.inherited_from_unique_process_id < u32::MAX as usize + && info.InheritedFromUniqueProcessId != 0 + && info.InheritedFromUniqueProcessId < u32::MAX as usize { - info.inherited_from_unique_process_id as u32 + info.InheritedFromUniqueProcessId as u32 } else { 0 } @@ -1256,6 +1566,132 @@ pub(crate) mod module { Ok(args.fd2) } + /// Windows-specific readlink that preserves \\?\ prefix for junctions + /// returns the substitute name from reparse data which includes the prefix + #[pyfunction] + fn readlink(path: OsPath, vm: &VirtualMachine) -> PyResult { + use crate::common::windows::ToWideString; + use windows_sys::Win32::Foundation::CloseHandle; + use windows_sys::Win32::Storage::FileSystem::{ + CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, + FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, + }; + use windows_sys::Win32::System::IO::DeviceIoControl; + use windows_sys::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT; + + let mode = path.mode; + let wide_path = path.as_ref().to_wide_with_nul(); + + // Open the file/directory with reparse point flag + let handle = unsafe { + CreateFileW( + wide_path.as_ptr(), + 0, // No access needed, just reading reparse data + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + std::ptr::null(), + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + std::ptr::null_mut(), + ) + }; + + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error().to_pyexception(vm)); + } + + // Buffer for reparse data - MAXIMUM_REPARSE_DATA_BUFFER_SIZE is 16384 + const BUFFER_SIZE: usize = 16384; + let mut buffer = vec![0u8; BUFFER_SIZE]; + let mut bytes_returned: u32 = 0; + + let result = unsafe { + DeviceIoControl( + handle, + FSCTL_GET_REPARSE_POINT, + std::ptr::null(), + 0, + buffer.as_mut_ptr() as *mut _, + BUFFER_SIZE as u32, + &mut bytes_returned, + std::ptr::null_mut(), + ) + }; + + unsafe { CloseHandle(handle) }; + + if result == 0 { + return Err(io::Error::last_os_error().to_pyexception(vm)); + } + + // Parse the reparse data buffer + // REPARSE_DATA_BUFFER structure: + // DWORD ReparseTag + // WORD ReparseDataLength + // WORD Reserved + // For symlinks/junctions (IO_REPARSE_TAG_SYMLINK/MOUNT_POINT): + // WORD SubstituteNameOffset + // WORD SubstituteNameLength + // WORD PrintNameOffset + // WORD PrintNameLength + // (For symlinks only: DWORD Flags) + // PathBuffer... + + let reparse_tag = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]); + + // Check if it's a symlink or mount point (junction) + use windows_sys::Win32::System::SystemServices::{ + IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, + }; + + let (substitute_offset, substitute_length, path_buffer_start) = + if reparse_tag == IO_REPARSE_TAG_SYMLINK { + // Symlink has Flags field (4 bytes) before PathBuffer + let sub_offset = u16::from_le_bytes([buffer[8], buffer[9]]) as usize; + let sub_length = u16::from_le_bytes([buffer[10], buffer[11]]) as usize; + // PathBuffer starts at offset 20 (after Flags at offset 16) + (sub_offset, sub_length, 20usize) + } else if reparse_tag == IO_REPARSE_TAG_MOUNT_POINT { + // Mount point (junction) has no Flags field + let sub_offset = u16::from_le_bytes([buffer[8], buffer[9]]) as usize; + let sub_length = u16::from_le_bytes([buffer[10], buffer[11]]) as usize; + // PathBuffer starts at offset 16 + (sub_offset, sub_length, 16usize) + } else { + // Unknown reparse tag - fall back to std::fs::read_link + let link_path = fs::read_link(path.as_ref()) + .map_err(|e| crate::convert::ToPyException::to_pyexception(&e, vm))?; + return Ok(mode.process_path(link_path, vm)); + }; + + // Extract the substitute name + let path_start = path_buffer_start + substitute_offset; + let path_end = path_start + substitute_length; + + if path_end > buffer.len() { + return Err(vm.new_os_error("Invalid reparse data".to_owned())); + } + + // Convert from UTF-16LE + let path_slice = &buffer[path_start..path_end]; + let wide_chars: Vec<u16> = path_slice + .chunks_exact(2) + .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) + .collect(); + + let mut result_path = std::ffi::OsString::from_wide(&wide_chars); + + // For mount points (junctions), the substitute name typically starts with \??\ + // Convert this to \\?\ + let result_str = result_path.to_string_lossy(); + if let Some(stripped) = result_str.strip_prefix(r"\??\") { + // Replace \??\ with \\?\ + let new_path = format!(r"\\?\{}", stripped); + result_path = std::ffi::OsString::from(new_path); + } + + Ok(mode.process_path(std::path::PathBuf::from(result_path), vm)) + } + pub(crate) fn support_funcs() -> Vec<SupportFunc> { Vec::new() } From 2ff1fba6ed9f59a71d01a97320cc6c100a190345 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 12 Dec 2025 09:23:59 +0900 Subject: [PATCH 479/819] Faulthandler (#6406) --- .cspell.dict/python-more.txt | 1 + Lib/test/test_cmd_line.py | 2 - Lib/test/test_faulthandler.py | 4 - crates/stdlib/src/faulthandler.rs | 601 +++++++++++++++++++++++------- crates/vm/src/vm/setting.rs | 5 +- src/lib.rs | 10 + src/settings.rs | 5 +- 7 files changed, 482 insertions(+), 146 deletions(-) diff --git a/.cspell.dict/python-more.txt b/.cspell.dict/python-more.txt index 8e1c0128383..620843cdd6a 100644 --- a/.cspell.dict/python-more.txt +++ b/.cspell.dict/python-more.txt @@ -170,6 +170,7 @@ pyexpat PYTHONBREAKPOINT PYTHONDEBUG PYTHONDONTWRITEBYTECODE +PYTHONFAULTHANDLER PYTHONHASHSEED PYTHONHOME PYTHONINSPECT diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index a7e4f6dd27f..eb455ebaed7 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -713,8 +713,6 @@ def run_xdev(self, *args, check_exitcode=True, xdev=True): self.assertEqual(proc.returncode, 0, proc) return proc.stdout.rstrip() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_xdev(self): # sys.flags.dev_mode code = "import sys; print(sys.flags.dev_mode)" diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index ffdc82bf053..5596e5b1669 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -442,8 +442,6 @@ def test_disabled_by_default(self): output = subprocess.check_output(args) self.assertEqual(output.rstrip(), b"False") - # TODO: RUSTPYTHON, subprocess.CalledProcessError: Command '<filter object at ...>' returned non-zero exit status 1. - @unittest.expectedFailure @support.requires_subprocess() def test_sys_xoptions(self): # Test python -X faulthandler @@ -457,8 +455,6 @@ def test_sys_xoptions(self): output = subprocess.check_output(args, env=env) self.assertEqual(output.rstrip(), b"True") - # TODO: RUSTPYTHON - @unittest.expectedFailure @support.requires_subprocess() def test_env_var(self): # empty env var diff --git a/crates/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs index e9006b982a3..a06b063d305 100644 --- a/crates/stdlib/src/faulthandler.rs +++ b/crates/stdlib/src/faulthandler.rs @@ -1,18 +1,102 @@ pub(crate) use decl::make_module; +#[allow(static_mut_refs)] // TODO: group code only with static mut refs #[pymodule(name = "faulthandler")] mod decl { use crate::vm::{ PyObjectRef, PyResult, VirtualMachine, builtins::PyFloat, frame::Frame, function::OptionalArg, py_io::Write, }; + #[cfg(any(unix, windows))] + use rustpython_common::os::{get_errno, set_errno}; use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use std::thread; use std::time::Duration; - static ENABLED: AtomicBool = AtomicBool::new(false); - static FATAL_ERROR_FD: AtomicI32 = AtomicI32::new(2); // stderr by default + /// fault_handler_t + #[cfg(unix)] + struct FaultHandler { + signum: libc::c_int, + enabled: bool, + name: &'static str, + previous: libc::sigaction, + } + + #[cfg(windows)] + struct FaultHandler { + signum: libc::c_int, + enabled: bool, + name: &'static str, + previous: libc::sighandler_t, + } + + #[cfg(unix)] + impl FaultHandler { + const fn new(signum: libc::c_int, name: &'static str) -> Self { + Self { + signum, + enabled: false, + name, + // SAFETY: sigaction is a C struct that can be zero-initialized + previous: unsafe { std::mem::zeroed() }, + } + } + } + + #[cfg(windows)] + impl FaultHandler { + const fn new(signum: libc::c_int, name: &'static str) -> Self { + Self { + signum, + enabled: false, + name, + previous: 0, + } + } + } + + /// faulthandler_handlers[] + /// Number of fatal signals + #[cfg(unix)] + const FAULTHANDLER_NSIGNALS: usize = 5; + #[cfg(windows)] + const FAULTHANDLER_NSIGNALS: usize = 4; + + // CPython uses static arrays for signal handlers which requires mutable static access. + // This is safe because: + // 1. Signal handlers run in a single-threaded context (from the OS perspective) + // 2. FAULTHANDLER_HANDLERS is only modified during enable/disable operations + // 3. This matches CPython's faulthandler.c implementation + #[cfg(unix)] + static mut FAULTHANDLER_HANDLERS: [FaultHandler; FAULTHANDLER_NSIGNALS] = [ + FaultHandler::new(libc::SIGBUS, "Bus error"), + FaultHandler::new(libc::SIGILL, "Illegal instruction"), + FaultHandler::new(libc::SIGFPE, "Floating-point exception"), + FaultHandler::new(libc::SIGABRT, "Aborted"), + FaultHandler::new(libc::SIGSEGV, "Segmentation fault"), + ]; + + #[cfg(windows)] + static mut FAULTHANDLER_HANDLERS: [FaultHandler; FAULTHANDLER_NSIGNALS] = [ + FaultHandler::new(libc::SIGILL, "Illegal instruction"), + FaultHandler::new(libc::SIGFPE, "Floating-point exception"), + FaultHandler::new(libc::SIGABRT, "Aborted"), + FaultHandler::new(libc::SIGSEGV, "Segmentation fault"), + ]; + + /// fatal_error state + struct FatalErrorState { + enabled: AtomicBool, + fd: AtomicI32, + all_threads: AtomicBool, + } + + static FATAL_ERROR: FatalErrorState = FatalErrorState { + enabled: AtomicBool::new(false), + fd: AtomicI32::new(2), // stderr by default + all_threads: AtomicBool::new(true), + }; // Watchdog thread state for dump_traceback_later struct WatchdogState { @@ -27,55 +111,211 @@ mod decl { type WatchdogHandle = Arc<(Mutex<WatchdogState>, Condvar)>; static WATCHDOG: Mutex<Option<WatchdogHandle>> = Mutex::new(None); - // Number of fatal signals we handle - #[cfg(unix)] - const NUM_FATAL_SIGNALS: usize = 5; - #[cfg(windows)] - const NUM_FATAL_SIGNALS: usize = 3; + // Frame snapshot for signal-safe traceback (RustPython-specific) + + /// Frame information snapshot for signal-safe access + #[cfg(any(unix, windows))] + #[derive(Clone, Copy)] + struct FrameSnapshot { + filename: [u8; 256], + filename_len: usize, + lineno: u32, + funcname: [u8; 128], + funcname_len: usize, + } - // Fatal signals to handle (with names for error messages) - #[cfg(unix)] - const FATAL_SIGNALS: [(libc::c_int, &str); NUM_FATAL_SIGNALS] = [ - (libc::SIGBUS, "Bus error"), - (libc::SIGILL, "Illegal instruction"), - (libc::SIGFPE, "Floating-point exception"), - (libc::SIGABRT, "Aborted"), - (libc::SIGSEGV, "Segmentation fault"), - ]; + #[cfg(any(unix, windows))] + impl FrameSnapshot { + const EMPTY: Self = Self { + filename: [0; 256], + filename_len: 0, + lineno: 0, + funcname: [0; 128], + funcname_len: 0, + }; + } - #[cfg(windows)] - const FATAL_SIGNALS: [(libc::c_int, &str); NUM_FATAL_SIGNALS] = [ - (libc::SIGFPE, "Floating-point exception"), - (libc::SIGABRT, "Aborted"), - (libc::SIGSEGV, "Segmentation fault"), - ]; + #[cfg(any(unix, windows))] + const MAX_SNAPSHOT_FRAMES: usize = 100; - // Storage for previous signal handlers (Unix) - #[cfg(unix)] - static mut PREVIOUS_HANDLERS: [libc::sigaction; NUM_FATAL_SIGNALS] = - unsafe { std::mem::zeroed() }; + /// Signal-safe global storage for frame snapshots + #[cfg(any(unix, windows))] + static mut FRAME_SNAPSHOTS: [FrameSnapshot; MAX_SNAPSHOT_FRAMES] = + [FrameSnapshot::EMPTY; MAX_SNAPSHOT_FRAMES]; + #[cfg(any(unix, windows))] + static SNAPSHOT_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); - /// Signal-safe write function - no memory allocation - #[cfg(all(not(target_arch = "wasm32"), not(windows)))] - fn write_str_noraise(fd: i32, s: &str) { - unsafe { - libc::write( - fd as libc::c_int, - s.as_ptr() as *const libc::c_void, - s.len(), - ); + // Signal-safe output functions + + // PUTS macro + #[cfg(any(unix, windows))] + fn puts(fd: i32, s: &str) { + let _ = unsafe { + #[cfg(windows)] + { + libc::write(fd, s.as_ptr() as *const libc::c_void, s.len() as u32) + } + #[cfg(not(windows))] + { + libc::write(fd, s.as_ptr() as *const libc::c_void, s.len()) + } + }; + } + + // _Py_DumpHexadecimal (traceback.c) + #[cfg(any(unix, windows))] + fn dump_hexadecimal(fd: i32, value: u64, width: usize) { + const HEX_CHARS: &[u8; 16] = b"0123456789abcdef"; + let mut buf = [0u8; 18]; // "0x" + 16 hex digits + buf[0] = b'0'; + buf[1] = b'x'; + + for i in 0..width { + let digit = ((value >> (4 * (width - 1 - i))) & 0xf) as usize; + buf[2 + i] = HEX_CHARS[digit]; + } + + let _ = unsafe { + #[cfg(windows)] + { + libc::write(fd, buf.as_ptr() as *const libc::c_void, (2 + width) as u32) + } + #[cfg(not(windows))] + { + libc::write(fd, buf.as_ptr() as *const libc::c_void, 2 + width) + } + }; + } + + // _Py_DumpDecimal (traceback.c) + #[cfg(any(unix, windows))] + fn dump_decimal(fd: i32, value: usize) { + let mut buf = [0u8; 20]; + let mut v = value; + let mut i = buf.len(); + + if v == 0 { + puts(fd, "0"); + return; } + + while v > 0 { + i -= 1; + buf[i] = b'0' + (v % 10) as u8; + v /= 10; + } + + let len = buf.len() - i; + let _ = unsafe { + #[cfg(windows)] + { + libc::write(fd, buf[i..].as_ptr() as *const libc::c_void, len as u32) + } + #[cfg(not(windows))] + { + libc::write(fd, buf[i..].as_ptr() as *const libc::c_void, len) + } + }; + } + + /// Get current thread ID + #[cfg(unix)] + fn current_thread_id() -> u64 { + unsafe { libc::pthread_self() as u64 } } #[cfg(windows)] - fn write_str_noraise(fd: i32, s: &str) { - unsafe { - libc::write( - fd as libc::c_int, - s.as_ptr() as *const libc::c_void, - s.len() as u32, - ); + fn current_thread_id() -> u64 { + unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() as u64 } + } + + // write_thread_id (traceback.c:1240-1256) + #[cfg(any(unix, windows))] + fn write_thread_id(fd: i32, is_current: bool) { + if is_current { + puts(fd, "Current thread 0x"); + } else { + puts(fd, "Thread 0x"); + } + let thread_id = current_thread_id(); + // Use appropriate width based on platform pointer size + dump_hexadecimal(fd, thread_id, std::mem::size_of::<usize>() * 2); + puts(fd, " (most recent call first):\n"); + } + + // dump_frame (traceback.c:1037-1087) + #[cfg(any(unix, windows))] + fn dump_frame(fd: i32, filename: &[u8], lineno: u32, funcname: &[u8]) { + puts(fd, " File \""); + let _ = unsafe { + #[cfg(windows)] + { + libc::write( + fd, + filename.as_ptr() as *const libc::c_void, + filename.len() as u32, + ) + } + #[cfg(not(windows))] + { + libc::write(fd, filename.as_ptr() as *const libc::c_void, filename.len()) + } + }; + puts(fd, "\", line "); + dump_decimal(fd, lineno as usize); + puts(fd, " in "); + let _ = unsafe { + #[cfg(windows)] + { + libc::write( + fd, + funcname.as_ptr() as *const libc::c_void, + funcname.len() as u32, + ) + } + #[cfg(not(windows))] + { + libc::write(fd, funcname.as_ptr() as *const libc::c_void, funcname.len()) + } + }; + puts(fd, "\n"); + } + + // faulthandler_dump_traceback + #[cfg(any(unix, windows))] + fn faulthandler_dump_traceback(fd: i32, _all_threads: bool) { + static REENTRANT: AtomicBool = AtomicBool::new(false); + + if REENTRANT.swap(true, Ordering::SeqCst) { + return; } + + // Write thread header + write_thread_id(fd, true); + + // Try to dump traceback from snapshot + let count = SNAPSHOT_COUNT.load(Ordering::Acquire); + if count > 0 { + // Using index access instead of iterator because FRAME_SNAPSHOTS is static mut + #[allow(clippy::needless_range_loop)] + for i in 0..count { + unsafe { + let snap = &FRAME_SNAPSHOTS[i]; + if snap.filename_len > 0 { + dump_frame( + fd, + &snap.filename[..snap.filename_len], + snap.lineno, + &snap.funcname[..snap.funcname_len], + ); + } + } + } + } else { + puts(fd, " <no Python frame>\n"); + } + + REENTRANT.store(false, Ordering::SeqCst); } const MAX_FUNCTION_NAME_LEN: usize = 500; @@ -158,159 +398,240 @@ mod decl { all_threads: bool, } + // faulthandler_py_enable #[pyfunction] fn enable(args: EnableArgs, vm: &VirtualMachine) -> PyResult<()> { - // Check that file is valid (if provided) or sys.stderr is not None - let _file = get_file_for_output(args.file, vm)?; + // Get file descriptor + let fd = get_fd_from_file_opt(args.file, vm)?; - ENABLED.store(true, Ordering::Relaxed); + // Store fd and all_threads in global state + FATAL_ERROR.fd.store(fd, Ordering::Relaxed); + FATAL_ERROR + .all_threads + .store(args.all_threads, Ordering::Relaxed); - // Install signal handlers for fatal errors - #[cfg(any(unix, windows))] - { - install_fatal_handlers(vm); + // Install signal handlers + if !faulthandler_enable_internal() { + return Err(vm.new_runtime_error("Failed to enable faulthandler".to_owned())); } Ok(()) } - /// Unix signal handler for fatal errors - // faulthandler_fatal_error() in Modules/faulthandler.c + // Signal handlers + + /// faulthandler_disable_fatal_handler (faulthandler.c:310-321) #[cfg(unix)] - extern "C" fn faulthandler_fatal_error(signum: libc::c_int) { - if !ENABLED.load(Ordering::Relaxed) { + unsafe fn faulthandler_disable_fatal_handler(handler: &mut FaultHandler) { + if !handler.enabled { return; } + handler.enabled = false; + unsafe { + libc::sigaction(handler.signum, &handler.previous, std::ptr::null_mut()); + } + } - let fd = FATAL_ERROR_FD.load(Ordering::Relaxed); - - // Find handler and restore previous handler BEFORE printing - let signal_name = - if let Some(idx) = FATAL_SIGNALS.iter().position(|(sig, _)| *sig == signum) { - // Restore previous handler first - unsafe { - libc::sigaction(signum, &PREVIOUS_HANDLERS[idx], std::ptr::null_mut()); - } - FATAL_SIGNALS[idx].1 - } else { - "Unknown signal" - }; - - // Print error message - write_str_noraise(fd, "Fatal Python error: "); - write_str_noraise(fd, signal_name); - write_str_noraise(fd, "\n\n"); - - // Re-raise signal to trigger default behavior (core dump, etc.) - // Called immediately thanks to SA_NODEFER flag + #[cfg(windows)] + unsafe fn faulthandler_disable_fatal_handler(handler: &mut FaultHandler) { + if !handler.enabled { + return; + } + handler.enabled = false; unsafe { - libc::raise(signum); + libc::signal(handler.signum, handler.previous); } } + // faulthandler_fatal_error #[cfg(unix)] - fn install_fatal_handlers(_vm: &VirtualMachine) { - for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() { - let mut action: libc::sigaction = unsafe { std::mem::zeroed() }; - action.sa_sigaction = faulthandler_fatal_error as libc::sighandler_t; - action.sa_flags = libc::SA_NODEFER; + extern "C" fn faulthandler_fatal_error(signum: libc::c_int) { + let save_errno = get_errno(); + if !FATAL_ERROR.enabled.load(Ordering::Relaxed) { + return; + } + + let fd = FATAL_ERROR.fd.load(Ordering::Relaxed); + + let handler = unsafe { + FAULTHANDLER_HANDLERS + .iter_mut() + .find(|h| h.signum == signum) + }; + + // faulthandler_fatal_error + if let Some(h) = handler { + // Disable handler first (restores previous) unsafe { - libc::sigaction(*signum, &action, &mut PREVIOUS_HANDLERS[idx]); + faulthandler_disable_fatal_handler(h); } + + puts(fd, "Fatal Python error: "); + puts(fd, h.name); + puts(fd, "\n\n"); + } else { + puts(fd, "Fatal Python error from unexpected signum: "); + dump_decimal(fd, signum as usize); + puts(fd, "\n\n"); + } + + // faulthandler_dump_traceback + let all_threads = FATAL_ERROR.all_threads.load(Ordering::Relaxed); + faulthandler_dump_traceback(fd, all_threads); + + // restore errno + set_errno(save_errno); + + // raise + // Called immediately thanks to SA_NODEFER flag + unsafe { + libc::raise(signum); } } - /// Windows signal handler for fatal errors - // faulthandler_fatal_error() in Modules/faulthandler.c + // faulthandler_fatal_error for Windows #[cfg(windows)] - extern "C" fn faulthandler_fatal_error_windows(signum: libc::c_int) { - if !ENABLED.load(Ordering::Relaxed) { + extern "C" fn faulthandler_fatal_error(signum: libc::c_int) { + let save_errno = get_errno(); + + if !FATAL_ERROR.enabled.load(Ordering::Relaxed) { return; } - let fd = FATAL_ERROR_FD.load(Ordering::Relaxed); + let fd = FATAL_ERROR.fd.load(Ordering::Relaxed); - // Find handler and restore previous handler BEFORE printing - let signal_name = - if let Some(idx) = FATAL_SIGNALS.iter().position(|(sig, _)| *sig == signum) { - // Restore previous handler first - unsafe { - libc::signal(signum, PREVIOUS_HANDLERS_WIN[idx]); - } - FATAL_SIGNALS[idx].1 - } else { - "Unknown signal" - }; + let handler = unsafe { + FAULTHANDLER_HANDLERS + .iter_mut() + .find(|h| h.signum == signum) + }; + + if let Some(h) = handler { + unsafe { + faulthandler_disable_fatal_handler(h); + } + puts(fd, "Fatal Python error: "); + puts(fd, h.name); + puts(fd, "\n\n"); + } else { + puts(fd, "Fatal Python error from unexpected signum: "); + dump_decimal(fd, signum as usize); + puts(fd, "\n\n"); + } + + let all_threads = FATAL_ERROR.all_threads.load(Ordering::Relaxed); + faulthandler_dump_traceback(fd, all_threads); - // Print error message - write_str_noraise(fd, "Fatal Python error: "); - write_str_noraise(fd, signal_name); - write_str_noraise(fd, "\n\n"); + set_errno(save_errno); // On Windows, don't explicitly call the previous handler for SIGSEGV - // The execution continues and the same instruction raises the same fault, - // calling the now-restored previous handler if signum == libc::SIGSEGV { return; } - // For other signals, re-raise to call the previous handler unsafe { libc::raise(signum); } } - #[cfg(windows)] - static mut PREVIOUS_HANDLERS_WIN: [libc::sighandler_t; NUM_FATAL_SIGNALS] = - [0; NUM_FATAL_SIGNALS]; + // faulthandler_enable + #[cfg(unix)] + fn faulthandler_enable_internal() -> bool { + if FATAL_ERROR.enabled.load(Ordering::Relaxed) { + return true; + } - #[cfg(windows)] - fn install_fatal_handlers(_vm: &VirtualMachine) { - for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() { - unsafe { - PREVIOUS_HANDLERS_WIN[idx] = libc::signal( - *signum, - faulthandler_fatal_error_windows as libc::sighandler_t, - ); + unsafe { + for handler in FAULTHANDLER_HANDLERS.iter_mut() { + if handler.enabled { + continue; + } + + let mut action: libc::sigaction = std::mem::zeroed(); + action.sa_sigaction = faulthandler_fatal_error as libc::sighandler_t; + // SA_NODEFER flag + action.sa_flags = libc::SA_NODEFER; + + if libc::sigaction(handler.signum, &action, &mut handler.previous) != 0 { + return false; + } + + handler.enabled = true; } } - } - #[pyfunction] - fn disable() -> bool { - let was_enabled = ENABLED.swap(false, Ordering::Relaxed); + FATAL_ERROR.enabled.store(true, Ordering::Relaxed); + true + } - // Restore default signal handlers - #[cfg(any(unix, windows))] - { - uninstall_fatal_handlers(); + #[cfg(windows)] + fn faulthandler_enable_internal() -> bool { + if FATAL_ERROR.enabled.load(Ordering::Relaxed) { + return true; } - was_enabled - } + unsafe { + for handler in FAULTHANDLER_HANDLERS.iter_mut() { + if handler.enabled { + continue; + } - #[cfg(unix)] - fn uninstall_fatal_handlers() { - for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() { - unsafe { - libc::sigaction(*signum, &PREVIOUS_HANDLERS[idx], std::ptr::null_mut()); + handler.previous = libc::signal( + handler.signum, + faulthandler_fatal_error as libc::sighandler_t, + ); + + // SIG_ERR is -1 as sighandler_t (which is usize on Windows) + if handler.previous == libc::SIG_ERR as libc::sighandler_t { + return false; + } + + handler.enabled = true; } } + + FATAL_ERROR.enabled.store(true, Ordering::Relaxed); + true } - #[cfg(windows)] - fn uninstall_fatal_handlers() { - for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() { - unsafe { - libc::signal(*signum, PREVIOUS_HANDLERS_WIN[idx]); + // faulthandler_disable + #[cfg(any(unix, windows))] + fn faulthandler_disable_internal() { + if !FATAL_ERROR.enabled.swap(false, Ordering::Relaxed) { + return; + } + + unsafe { + for handler in FAULTHANDLER_HANDLERS.iter_mut() { + faulthandler_disable_fatal_handler(handler); } } } + #[cfg(not(any(unix, windows)))] + fn faulthandler_enable_internal() -> bool { + FATAL_ERROR.enabled.store(true, Ordering::Relaxed); + true + } + + #[cfg(not(any(unix, windows)))] + fn faulthandler_disable_internal() { + FATAL_ERROR.enabled.store(false, Ordering::Relaxed); + } + + // faulthandler_disable_py + #[pyfunction] + fn disable() -> bool { + let was_enabled = FATAL_ERROR.enabled.load(Ordering::Relaxed); + faulthandler_disable_internal(); + was_enabled + } + + // faulthandler_is_enabled #[pyfunction] fn is_enabled() -> bool { - ENABLED.load(Ordering::Relaxed) + FATAL_ERROR.enabled.load(Ordering::Relaxed) } fn format_timeout(timeout_us: u64) -> String { @@ -390,6 +711,9 @@ mod decl { drop(guard); // Release lock before I/O // Timeout occurred, dump traceback + #[cfg(target_arch = "wasm32")] + let _ = (exit, fd, &header); + #[cfg(not(target_arch = "wasm32"))] { let header_bytes = header.as_bytes(); @@ -628,8 +952,9 @@ mod decl { #[cfg(unix)] fn check_signum(signum: i32, vm: &VirtualMachine) -> PyResult<()> { - // Check if it's a fatal signal - if FATAL_SIGNALS.iter().any(|(sig, _)| *sig == signum) { + // Check if it's a fatal signal (faulthandler.c uses faulthandler_handlers array) + let is_fatal = unsafe { FAULTHANDLER_HANDLERS.iter().any(|h| h.signum == signum) }; + if is_fatal { return Err(vm.new_runtime_error(format!( "signal {} cannot be registered, use enable() instead", signum diff --git a/crates/vm/src/vm/setting.rs b/crates/vm/src/vm/setting.rs index caf8c9139d6..deaca705c47 100644 --- a/crates/vm/src/vm/setting.rs +++ b/crates/vm/src/vm/setting.rs @@ -19,7 +19,9 @@ pub struct Settings { /// None means use_hash_seed = 0 in CPython pub hash_seed: Option<u32>, - // int faulthandler; + /// -X faulthandler, PYTHONFAULTHANDLER + pub faulthandler: bool, + // int tracemalloc; // int perf_profiling; // int import_time; @@ -157,6 +159,7 @@ impl Default for Settings { path_list: vec![], argv: vec![], hash_seed: None, + faulthandler: false, buffered_stdio: true, check_hash_pycs_mode: CheckHashPycsMode::Default, allow_external_library: cfg!(feature = "importlib"), diff --git a/src/lib.rs b/src/lib.rs index 0737ebdddc4..40e50ab84c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,6 +187,16 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { ); } + // Enable faulthandler if -X faulthandler, PYTHONFAULTHANDLER or -X dev is set + // _PyFaulthandler_Init() + if vm.state.settings.faulthandler { + let _ = vm.run_code_string( + vm.new_scope_with_builtins(), + "import faulthandler; faulthandler.enable()", + "<faulthandler>".to_owned(), + ); + } + let is_repl = matches!(run_mode, RunMode::Repl); if !vm.state.settings.quiet && (vm.state.settings.verbose > 0 || (is_repl && std::io::stdin().is_terminal())) diff --git a/src/settings.rs b/src/settings.rs index 23553aa5c06..f77db4d159d 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -267,6 +267,7 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> { }; match &*name { "dev" => settings.dev_mode = true, + "faulthandler" => settings.faulthandler = true, "warn_default_encoding" => settings.warn_default_encoding = true, "no_sig_int" => settings.install_signal_handlers = false, "int_max_str_digits" => { @@ -291,9 +292,11 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> { settings.warn_default_encoding = settings.warn_default_encoding || env_bool("PYTHONWARNDEFAULTENCODING"); + settings.faulthandler = settings.faulthandler || env_bool("PYTHONFAULTHANDLER"); if settings.dev_mode { - settings.warnoptions.push("default".to_owned()) + settings.warnoptions.push("default".to_owned()); + settings.faulthandler = true; } if settings.bytes_warning > 0 { let warn = if settings.bytes_warning > 1 { From 772b92edde5061782620b10a2b17bf9948ecb4ab Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:37:40 +0900 Subject: [PATCH 480/819] Fix windows socket (#6408) * fix _path_splitroot * Fix windows socket --- Lib/test/test_selectors.py | 2 -- Lib/test/test_socketserver.py | 5 ----- Lib/test/test_zipimport.py | 1 - crates/stdlib/src/socket.rs | 15 +++++++-------- crates/vm/src/stdlib/nt.rs | 28 +++++++++++++++++----------- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py index 59c1d26a7c5..643775597c5 100644 --- a/Lib/test/test_selectors.py +++ b/Lib/test/test_selectors.py @@ -131,8 +131,6 @@ def test_unregister_after_fd_close_and_reuse(self): s.unregister(r) s.unregister(w) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_unregister_after_socket_close(self): s = self.SELECTOR() self.addCleanup(s.close) diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py index 113f959ff2d..cdbf341b9cb 100644 --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -178,13 +178,11 @@ def dgram_examine(self, proto, addr): buf += data self.assertEqual(buf, TEST_STR) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: -1 != 18446744073709551615") def test_TCPServer(self): self.run_server(socketserver.TCPServer, socketserver.StreamRequestHandler, self.stream_examine) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: -1 != 18446744073709551615") def test_ThreadingTCPServer(self): self.run_server(socketserver.ThreadingTCPServer, socketserver.StreamRequestHandler, @@ -217,13 +215,11 @@ def test_ForkingUnixStreamServer(self): socketserver.StreamRequestHandler, self.stream_examine) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: -1 != 18446744073709551615") def test_UDPServer(self): self.run_server(socketserver.UDPServer, socketserver.DatagramRequestHandler, self.dgram_examine) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: -1 != 18446744073709551615") def test_ThreadingUDPServer(self): self.run_server(socketserver.ThreadingUDPServer, socketserver.DatagramRequestHandler, @@ -298,7 +294,6 @@ def test_tcpserver_bind_leak(self): socketserver.TCPServer((HOST, -1), socketserver.StreamRequestHandler) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: -1 != 18446744073709551615") def test_context_manager(self): with socketserver.TCPServer((HOST, 0), socketserver.StreamRequestHandler) as server: diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py index 488a67e80fd..b291d530169 100644 --- a/Lib/test/test_zipimport.py +++ b/Lib/test/test_zipimport.py @@ -730,7 +730,6 @@ def testTraceback(self): @unittest.skipIf(os_helper.TESTFN_UNENCODABLE is None, "need an unencodable filename") - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def testUnencodable(self): filename = os_helper.TESTFN_UNENCODABLE + ".zip" self.addCleanup(os_helper.unlink, filename) diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index d75595648aa..b4e4dac88aa 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -1065,8 +1065,7 @@ mod _socket { fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { Ok(format!( "<socket object, fd={}, family={}, type={}, proto={}>", - // cast because INVALID_SOCKET is unsigned, so would show usize::MAX instead of -1 - zelf.fileno() as i64, + zelf.fileno(), zelf.family.load(), zelf.kind.load(), zelf.proto.load(), @@ -1462,25 +1461,25 @@ mod _socket { #[pymethod] fn close(&self) -> io::Result<()> { let sock = self.detach(); - if sock != INVALID_SOCKET { - close_inner(sock)?; + if sock != INVALID_SOCKET as i64 { + close_inner(sock as RawSocket)?; } Ok(()) } #[pymethod] #[inline] - fn detach(&self) -> RawSocket { + fn detach(&self) -> i64 { let sock = self.sock.write().take(); - sock.map_or(INVALID_SOCKET, into_sock_fileno) + sock.map_or(INVALID_SOCKET as i64, |s| into_sock_fileno(s) as i64) } #[pymethod] - fn fileno(&self) -> RawSocket { + fn fileno(&self) -> i64 { self.sock .read() .as_ref() - .map_or(INVALID_SOCKET, sock_fileno) + .map_or(INVALID_SOCKET as i64, |s| sock_fileno(s) as i64) } #[pymethod] diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index ce68b9d25f8..ada939b1549 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -1122,10 +1122,18 @@ pub(crate) mod module { } #[pyfunction] - fn _path_splitroot(path: OsPath, vm: &VirtualMachine) -> PyResult<(String, String)> { + fn _path_splitroot( + path: OsPath, + _vm: &VirtualMachine, + ) -> ( + rustpython_common::wtf8::Wtf8Buf, + rustpython_common::wtf8::Wtf8Buf, + ) { + use rustpython_common::wtf8::Wtf8Buf; + let orig: Vec<_> = path.path.to_wide(); if orig.is_empty() { - return Ok(("".to_owned(), "".to_owned())); + return (Wtf8Buf::new(), Wtf8Buf::new()); } let backslashed: Vec<_> = orig .iter() @@ -1134,15 +1142,11 @@ pub(crate) mod module { .chain(std::iter::once(0)) // null-terminated .collect(); - fn from_utf16(wstr: &[u16], vm: &VirtualMachine) -> PyResult<String> { - String::from_utf16(wstr).map_err(|e| vm.new_unicode_decode_error(e.to_string())) - } - let mut end: *const u16 = std::ptr::null(); let hr = unsafe { windows_sys::Win32::UI::Shell::PathCchSkipRoot(backslashed.as_ptr(), &mut end) }; - let (root, path) = if hr == 0 { + if hr == 0 { // S_OK assert!(!end.is_null()); let len: usize = unsafe { end.offset_from(backslashed.as_ptr()) } @@ -1155,11 +1159,13 @@ pub(crate) mod module { len, backslashed.len() ); - (from_utf16(&orig[..len], vm)?, from_utf16(&orig[len..], vm)?) + ( + Wtf8Buf::from_wide(&orig[..len]), + Wtf8Buf::from_wide(&orig[len..]), + ) } else { - ("".to_owned(), from_utf16(&orig, vm)?) - }; - Ok((root, path)) + (Wtf8Buf::new(), Wtf8Buf::from_wide(&orig)) + } } #[pyfunction] From 4bec0ad1c6b58c30f47bf1268565f2ae1b9841a0 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:26:00 +0900 Subject: [PATCH 481/819] Fix test_runpy (#6409) --- Lib/test/test_runpy.py | 6 ------ Lib/test/test_signal.py | 2 -- crates/common/src/os.rs | 20 +++++++++++++++++++- crates/vm/src/vm/interpreter.rs | 4 ++-- crates/vm/src/vm/mod.rs | 13 +++++++++---- examples/generator.rs | 2 +- examples/package_embed.rs | 2 +- src/lib.rs | 2 +- 8 files changed, 33 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 2492abff019..c1e255e7af3 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -801,11 +801,9 @@ def assertSigInt(self, cmd, *args, **kwargs): self.assertTrue(proc.stderr.endswith("\nKeyboardInterrupt\n"), proc.stderr) self.assertEqual(proc.returncode, self.EXPECTED_CODE) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_file(self): self.assertSigInt([self.ham]) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_file_runpy_run_module(self): tmp = self.ham.parent run_module = tmp / "run_module.py" @@ -819,7 +817,6 @@ def test_pymain_run_file_runpy_run_module(self): ) self.assertSigInt([run_module], cwd=tmp) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_file_runpy_run_module_as_main(self): tmp = self.ham.parent run_module_as_main = tmp / "run_module_as_main.py" @@ -833,14 +830,12 @@ def test_pymain_run_file_runpy_run_module_as_main(self): ) self.assertSigInt([run_module_as_main], cwd=tmp) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_command_run_module(self): self.assertSigInt( ["-c", "import runpy; runpy.run_module('ham')"], cwd=self.ham.parent, ) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_command(self): self.assertSigInt(["-c", "import ham"], cwd=self.ham.parent) @@ -848,7 +843,6 @@ def test_pymain_run_command(self): def test_pymain_run_stdin(self): self.assertSigInt([], input="import ham", cwd=self.ham.parent) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ") def test_pymain_run_module(self): ham = self.ham self.assertSigInt(["-m", ham.stem], cwd=ham.parent) diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index e05467f7762..5fbb26df831 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -224,8 +224,6 @@ def test_issue9324(self): with self.assertRaises(ValueError): signal.signal(7, handler) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipUnless(sys.executable, "sys.executable required.") @support.requires_subprocess() def test_keyboard_interrupt_exit_code(self): diff --git a/crates/common/src/os.rs b/crates/common/src/os.rs index 2d82b1a6ccc..e77a81fd94f 100644 --- a/crates/common/src/os.rs +++ b/crates/common/src/os.rs @@ -1,7 +1,25 @@ // spell-checker:disable // TODO: we can move more os-specific bindings/interfaces from stdlib::{os, posix, nt} to here -use std::{io, str::Utf8Error}; +use std::{io, process::ExitCode, str::Utf8Error}; + +/// Convert exit code to std::process::ExitCode +/// +/// On Windows, this supports the full u32 range including STATUS_CONTROL_C_EXIT (0xC000013A). +/// On other platforms, only the lower 8 bits are used. +pub fn exit_code(code: u32) -> ExitCode { + #[cfg(windows)] + { + // For large exit codes like STATUS_CONTROL_C_EXIT (0xC000013A), + // we need to call std::process::exit() directly since ExitCode::from(u8) + // would truncate the value, and ExitCode::from_raw() is still unstable. + // FIXME: side effect in exit_code is not ideal. + if code > u8::MAX as u32 { + std::process::exit(code as i32) + } + } + ExitCode::from(code as u8) +} pub trait ErrorExt { fn posix_errno(&self) -> i32; diff --git a/crates/vm/src/vm/interpreter.rs b/crates/vm/src/vm/interpreter.rs index efe06d24171..05613d43384 100644 --- a/crates/vm/src/vm/interpreter.rs +++ b/crates/vm/src/vm/interpreter.rs @@ -96,7 +96,7 @@ impl Interpreter { /// /// See [`Interpreter::finalize`] for the finalization steps. /// See also [`Interpreter::enter`] for pure function call to obtain Python exception. - pub fn run<F>(self, f: F) -> u8 + pub fn run<F>(self, f: F) -> u32 where F: FnOnce(&VirtualMachine) -> PyResult<()>, { @@ -113,7 +113,7 @@ impl Interpreter { /// 1. Mark vm as finalized. /// /// Note that calling `finalize` is not necessary by purpose though. - pub fn finalize(self, exc: Option<PyBaseExceptionRef>) -> u8 { + pub fn finalize(self, exc: Option<PyBaseExceptionRef>) -> u32 { self.enter(|vm| { vm.flush_std(); diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 0d21d708ac8..fd37b2494dd 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -841,7 +841,7 @@ impl VirtualMachine { } } - pub fn handle_exit_exception(&self, exc: PyBaseExceptionRef) -> u8 { + pub fn handle_exit_exception(&self, exc: PyBaseExceptionRef) -> u32 { if exc.fast_isinstance(self.ctx.exceptions.system_exit) { let args = exc.args(); let msg = match args.as_slice() { @@ -849,7 +849,7 @@ impl VirtualMachine { [arg] => match_class!(match arg { ref i @ PyInt => { use num_traits::cast::ToPrimitive; - return i.as_bigint().to_u8().unwrap_or(0); + return i.as_bigint().to_u32().unwrap_or(0); } arg => { if self.is_none(arg) { @@ -883,9 +883,14 @@ impl VirtualMachine { kill(getpid(), SIGINT).expect("Expect to be killed."); } - (libc::SIGINT as u8) + 128u8 + (libc::SIGINT as u32) + 128 } - #[cfg(not(unix))] + #[cfg(windows)] + { + // STATUS_CONTROL_C_EXIT - same as CPython + 0xC000013A + } + #[cfg(not(any(unix, windows)))] { 1 } diff --git a/examples/generator.rs b/examples/generator.rs index 937687ab8fa..27733a1913d 100644 --- a/examples/generator.rs +++ b/examples/generator.rs @@ -46,5 +46,5 @@ fn main() -> ExitCode { vm.add_native_modules(rustpython_stdlib::get_module_inits()); }); let result = py_main(&interp); - ExitCode::from(interp.run(|_vm| result)) + vm::common::os::exit_code(interp.run(|_vm| result)) } diff --git a/examples/package_embed.rs b/examples/package_embed.rs index 975e734593d..e82e71f5ceb 100644 --- a/examples/package_embed.rs +++ b/examples/package_embed.rs @@ -26,5 +26,5 @@ fn main() -> ExitCode { let result = result.map(|result| { println!("name: {result}"); }); - ExitCode::from(interp.run(|_vm| result)) + vm::common::os::exit_code(interp.run(|_vm| result)) } diff --git a/src/lib.rs b/src/lib.rs index 40e50ab84c6..84a774ab029 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,7 +111,7 @@ pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode { let interp = config.interpreter(); let exitcode = interp.run(move |vm| run_rustpython(vm, run_mode)); - ExitCode::from(exitcode) + rustpython_vm::common::os::exit_code(exitcode) } fn setup_main_module(vm: &VirtualMachine) -> PyResult<Scope> { From df3a0b2f2555a797fc409ea7b615e0354110b17b Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:02:32 +0900 Subject: [PATCH 482/819] impl {raise_,str}signal (#6411) * year>=1900 on windows * signal * set_wakeup_fd --- Lib/test/test_regrtest.py | 4 - Lib/test/test_signal.py | 16 ---- Lib/test/test_strftime.py | 1 - crates/vm/src/stdlib/signal.rs | 134 ++++++++++++++++++++++++++++++++- crates/vm/src/stdlib/time.rs | 8 ++ 5 files changed, 140 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index fc56ec4afc9..d9ae2f35487 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -820,8 +820,6 @@ def test_fromfile(self): output = self.run_tests('--fromfile', filename) self.check_executed_tests(output, tests) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_interrupted(self): code = TEST_INTERRUPTED test = self.create_test('sigint', code=code) @@ -839,8 +837,6 @@ def test_slowest(self): % (self.TESTNAME_REGEX, len(tests))) self.check_line(output, regex) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_slowest_interrupted(self): # Issue #25373: test --slowest with an interrupted test code = TEST_INTERRUPTED diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 5fbb26df831..34ef13adf33 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -82,8 +82,6 @@ def trivial_signal_handler(self, *args): def create_handler_with_partial(self, argument): return functools.partial(self.trivial_signal_handler, argument) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_out_of_range_signal_number_raises_error(self): self.assertRaises(ValueError, signal.getsignal, 4242) @@ -126,8 +124,6 @@ def __repr__(self): self.assertEqual(signal.getsignal(signal.SIGHUP), hup) self.assertEqual(0, argument.repr_count) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_strsignal(self): self.assertIn("Interrupt", signal.strsignal(signal.SIGINT)) self.assertIn("Terminated", signal.strsignal(signal.SIGTERM)) @@ -141,8 +137,6 @@ def test_interprocess_signal(self): script = os.path.join(dirname, 'signalinterproctester.py') assert_python_ok(script) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipUnless( hasattr(signal, "valid_signals"), "requires signal.valid_signals" @@ -190,8 +184,6 @@ def test_keyboard_interrupt_exit_code(self): @unittest.skipUnless(sys.platform == "win32", "Windows specific") class WindowsSignalTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_valid_signals(self): s = signal.valid_signals() self.assertIsInstance(s, set) @@ -251,13 +243,11 @@ def test_invalid_call(self): with self.assertRaises(TypeError): signal.set_wakeup_fd(signal.SIGINT, False) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_invalid_fd(self): fd = os_helper.make_bad_fd() self.assertRaises((ValueError, OSError), signal.set_wakeup_fd, fd) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @unittest.skipUnless(support.has_socket_support, "needs working sockets.") def test_invalid_socket(self): sock = socket.socket() @@ -266,7 +256,6 @@ def test_invalid_socket(self): self.assertRaises((ValueError, OSError), signal.set_wakeup_fd, fd) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") # Emscripten does not support fstat on pipes yet. # https://github.com/emscripten-core/emscripten/issues/16414 @unittest.skipIf(support.is_emscripten, "Emscripten cannot fstat pipes.") @@ -288,7 +277,6 @@ def test_set_wakeup_fd_result(self): self.assertEqual(signal.set_wakeup_fd(-1), w2) self.assertEqual(signal.set_wakeup_fd(-1), -1) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @unittest.skipIf(support.is_emscripten, "Emscripten cannot fstat pipes.") @unittest.skipUnless(support.has_socket_support, "needs working sockets.") def test_set_wakeup_fd_socket_result(self): @@ -1440,8 +1428,6 @@ def cycle_handlers(): class RaiseSignalTest(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_sigint(self): with self.assertRaises(KeyboardInterrupt): signal.raise_signal(signal.SIGINT) @@ -1460,8 +1446,6 @@ def test_invalid_argument(self): else: raise - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_handler(self): is_ok = False def handler(a, b): diff --git a/Lib/test/test_strftime.py b/Lib/test/test_strftime.py index f5024d8e6da..be43c49e40a 100644 --- a/Lib/test/test_strftime.py +++ b/Lib/test/test_strftime.py @@ -184,7 +184,6 @@ class Y1900Tests(unittest.TestCase): a date before 1900 is passed with a format string containing "%y" """ - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_y_before_1900(self): # Issue #13674, #19634 t = (1899, 1, 1, 0, 0, 0, 0, 0, 0) diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index 5c3d8825f78..4eacb10154c 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -228,7 +228,7 @@ pub(crate) mod _signal { } #[pyfunction] - fn set_wakeup_fd(args: SetWakeupFdArgs, vm: &VirtualMachine) -> PyResult<WakeupFdRaw> { + fn set_wakeup_fd(args: SetWakeupFdArgs, vm: &VirtualMachine) -> PyResult<i64> { // TODO: implement warn_on_full_buffer let _ = args.warn_on_full_buffer; #[cfg(windows)] @@ -264,6 +264,15 @@ pub(crate) mod _signal { if err.raw_os_error() != Some(WinSock::WSAENOTSOCK) { return Err(err.into_pyexception(vm)); } + // Validate that fd is a valid file descriptor using fstat + // First check if SOCKET can be safely cast to i32 (file descriptor) + let fd_i32 = + i32::try_from(fd).map_err(|_| vm.new_value_error("invalid fd".to_owned()))?; + // Verify the fd is valid by trying to fstat it + let borrowed_fd = + unsafe { crate::common::crt_fd::Borrowed::try_borrow_raw(fd_i32) } + .map_err(|e| e.into_pyexception(vm))?; + crate::common::fileutils::fstat(borrowed_fd).map_err(|e| e.into_pyexception(vm))?; } is_socket } else { @@ -287,7 +296,18 @@ pub(crate) mod _signal { #[cfg(windows)] WAKEUP_IS_SOCKET.store(is_socket, Ordering::Relaxed); - Ok(old_fd) + #[cfg(windows)] + { + if old_fd == INVALID_WAKEUP { + Ok(-1) + } else { + Ok(old_fd as i64) + } + } + #[cfg(not(windows))] + { + Ok(old_fd as i64) + } } #[cfg(all(unix, not(target_os = "redox")))] @@ -302,6 +322,116 @@ pub(crate) mod _signal { } } + /// CPython: signal_raise_signal (signalmodule.c) + #[cfg(any(unix, windows))] + #[pyfunction] + fn raise_signal(signalnum: i32, vm: &VirtualMachine) -> PyResult<()> { + signal::assert_in_range(signalnum, vm)?; + + // On Windows, only certain signals are supported + #[cfg(windows)] + { + use crate::convert::IntoPyException; + // Windows supports: SIGINT(2), SIGILL(4), SIGFPE(8), SIGSEGV(11), SIGTERM(15), SIGABRT(22) + const VALID_SIGNALS: &[i32] = &[ + libc::SIGINT, + libc::SIGILL, + libc::SIGFPE, + libc::SIGSEGV, + libc::SIGTERM, + libc::SIGABRT, + ]; + if !VALID_SIGNALS.contains(&signalnum) { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL).into_pyexception(vm)); + } + } + + let res = unsafe { libc::raise(signalnum) }; + if res != 0 { + return Err(vm.new_os_error(format!("raise_signal failed for signal {}", signalnum))); + } + + // Check if a signal was triggered and handle it + signal::check_signals(vm)?; + + Ok(()) + } + + /// CPython: signal_strsignal (signalmodule.c) + #[cfg(unix)] + #[pyfunction] + fn strsignal(signalnum: i32, vm: &VirtualMachine) -> PyResult<Option<String>> { + if signalnum < 1 || signalnum >= signal::NSIG as i32 { + return Err(vm.new_value_error(format!("signal number {} out of range", signalnum))); + } + let s = unsafe { libc::strsignal(signalnum) }; + if s.is_null() { + Ok(None) + } else { + let cstr = unsafe { std::ffi::CStr::from_ptr(s) }; + Ok(Some(cstr.to_string_lossy().into_owned())) + } + } + + #[cfg(windows)] + #[pyfunction] + fn strsignal(signalnum: i32, vm: &VirtualMachine) -> PyResult<Option<String>> { + if signalnum < 1 || signalnum >= signal::NSIG as i32 { + return Err(vm.new_value_error(format!("signal number {} out of range", signalnum))); + } + // Windows doesn't have strsignal(), provide our own mapping + let name = match signalnum { + libc::SIGINT => "Interrupt", + libc::SIGILL => "Illegal instruction", + libc::SIGFPE => "Floating-point exception", + libc::SIGSEGV => "Segmentation fault", + libc::SIGTERM => "Terminated", + libc::SIGABRT => "Aborted", + _ => return Ok(None), + }; + Ok(Some(name.to_owned())) + } + + /// CPython: signal_valid_signals (signalmodule.c) + #[pyfunction] + fn valid_signals(vm: &VirtualMachine) -> PyResult { + use crate::PyPayload; + use crate::builtins::PySet; + let set = PySet::default().into_ref(&vm.ctx); + #[cfg(unix)] + { + // On Unix, most signals 1..NSIG are valid + for signum in 1..signal::NSIG { + // Skip signals that cannot be caught + #[cfg(not(target_os = "wasi"))] + if signum == libc::SIGKILL as usize || signum == libc::SIGSTOP as usize { + continue; + } + set.add(vm.ctx.new_int(signum as i32).into(), vm)?; + } + } + #[cfg(windows)] + { + // Windows only supports a limited set of signals + for &signum in &[ + libc::SIGINT, + libc::SIGILL, + libc::SIGFPE, + libc::SIGSEGV, + libc::SIGTERM, + libc::SIGABRT, + ] { + set.add(vm.ctx.new_int(signum).into(), vm)?; + } + } + #[cfg(not(any(unix, windows)))] + { + // Empty set for platforms without signal support (e.g., WASM) + let _ = &set; + } + Ok(set.into()) + } + #[cfg(any(unix, windows))] pub extern "C" fn run_signal(signum: i32) { signal::TRIGGERS[signum as usize].store(true, Ordering::Relaxed); diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index 1c6113bad77..b9b53cdc5c5 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -360,6 +360,14 @@ mod decl { use std::fmt::Write; let instant = t.naive_or_local(vm)?; + + // On Windows/AIX/Solaris, %y format with year < 1900 is not supported + #[cfg(any(windows, target_os = "aix", target_os = "solaris"))] + if instant.year() < 1900 && format.as_str().contains("%y") { + let msg = "format %y requires year >= 1900 on Windows"; + return Err(vm.new_value_error(msg.to_owned())); + } + let mut formatted_time = String::new(); /* From 5a5b7215768c9c52a68a3a7b73ae47d0288b29ff Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:23:27 +0900 Subject: [PATCH 483/819] Fix misused PyTypeError (#6412) --- crates/stdlib/src/csv.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index 39ca70640d0..3c7cc2ff807 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -5,7 +5,7 @@ mod _csv { use crate::common::lock::PyMutex; use crate::vm::{ AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyBaseExceptionRef, PyInt, PyNone, PyStr, PyType, PyTypeError, PyTypeRef}, + builtins::{PyBaseExceptionRef, PyInt, PyNone, PyStr, PyType, PyTypeRef}, function::{ArgIterable, ArgumentError, FromArgs, FuncArgs, OptionalArg}, protocol::{PyIter, PyIterReturn}, raise_if_stop, @@ -442,8 +442,8 @@ mod _csv { } } impl TryFrom<isize> for QuoteStyle { - type Error = PyTypeError; - fn try_from(num: isize) -> Result<Self, PyTypeError> { + type Error = (); + fn try_from(num: isize) -> Result<Self, Self::Error> { match num { 0 => Ok(Self::Minimal), 1 => Ok(Self::All), @@ -451,7 +451,7 @@ mod _csv { 3 => Ok(Self::None), 4 => Ok(Self::Strings), 5 => Ok(Self::Notnull), - _ => Err(PyTypeError {}), + _ => Err(()), } } } From 75dcf8042e8bbe69312c08a7d45118af9807f3a5 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:05:31 +0200 Subject: [PATCH 484/819] Tidy `codegen::ir::BlockIdx` api (#6413) * Tidy `codegen::ir::BlockIdx` api * Update crates/codegen/src/ir.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- crates/codegen/src/compile.rs | 39 ++++++++++++++++------------------- crates/codegen/src/ir.rs | 22 +++++++++++++++++--- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 3e7640cc1e0..9620edbd107 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -122,7 +122,7 @@ pub struct CompileOpts { #[derive(Debug, Clone, Copy)] struct CompileContext { - loop_data: Option<(ir::BlockIdx, ir::BlockIdx)>, + loop_data: Option<(BlockIdx, BlockIdx)>, in_class: bool, func: FunctionContext, } @@ -356,7 +356,7 @@ impl Compiler { source_path: source_file.name().to_owned(), private: None, blocks: vec![ir::Block::default()], - current_block: ir::BlockIdx(0), + current_block: BlockIdx::new(0), metadata: ir::CodeUnitMetadata { name: code_name.clone(), qualname: Some(code_name), @@ -745,7 +745,7 @@ impl Compiler { source_path: source_path.clone(), private, blocks: vec![ir::Block::default()], - current_block: BlockIdx(0), + current_block: BlockIdx::new(0), metadata: ir::CodeUnitMetadata { name: name.to_owned(), qualname: None, // Will be set below @@ -4370,7 +4370,7 @@ impl Compiler { &mut self, expression: &Expr, condition: bool, - target_block: ir::BlockIdx, + target_block: BlockIdx, ) -> CompileResult<()> { // Compile expression for test, and jump to label if false match &expression { @@ -5187,7 +5187,7 @@ impl Compiler { let return_none = init_collection.is_none(); // Create empty object of proper type: if let Some(init_collection) = init_collection { - self._emit(init_collection, OpArg(0), ir::BlockIdx::NULL) + self._emit(init_collection, OpArg(0), BlockIdx::NULL) } let mut loop_labels = vec![]; @@ -5328,7 +5328,7 @@ impl Compiler { } // Low level helper functions: - fn _emit(&mut self, instr: Instruction, arg: OpArg, target: ir::BlockIdx) { + fn _emit(&mut self, instr: Instruction, arg: OpArg, target: BlockIdx) { let range = self.current_source_range; let location = self .source_file @@ -5345,7 +5345,7 @@ impl Compiler { } fn emit_no_arg(&mut self, ins: Instruction) { - self._emit(ins, OpArg::null(), ir::BlockIdx::NULL) + self._emit(ins, OpArg::null(), BlockIdx::NULL) } fn emit_arg<A: OpArgType, T: EmitArg<A>>( @@ -5393,25 +5393,25 @@ impl Compiler { &mut info.blocks[info.current_block] } - fn new_block(&mut self) -> ir::BlockIdx { + fn new_block(&mut self) -> BlockIdx { let code = self.current_code_info(); - let idx = ir::BlockIdx(code.blocks.len().to_u32()); + let idx = BlockIdx::new(code.blocks.len().to_u32()); code.blocks.push(ir::Block::default()); idx } - fn switch_to_block(&mut self, block: ir::BlockIdx) { + fn switch_to_block(&mut self, block: BlockIdx) { let code = self.current_code_info(); let prev = code.current_block; assert_ne!(prev, block, "recursive switching {prev:?} -> {block:?}"); assert_eq!( code.blocks[block].next, - ir::BlockIdx::NULL, + BlockIdx::NULL, "switching {prev:?} -> {block:?} to completed block" ); - let prev_block = &mut code.blocks[prev.0 as usize]; + let prev_block = &mut code.blocks[prev.idx()]; assert_eq!( - prev_block.next.0, + u32::from(prev_block.next), u32::MAX, "switching {prev:?} -> {block:?} from block that's already got a next" ); @@ -5708,22 +5708,19 @@ trait EmitArg<Arg: OpArgType> { fn emit( self, f: impl FnOnce(OpArgMarker<Arg>) -> Instruction, - ) -> (Instruction, OpArg, ir::BlockIdx); + ) -> (Instruction, OpArg, BlockIdx); } impl<T: OpArgType> EmitArg<T> for T { - fn emit( - self, - f: impl FnOnce(OpArgMarker<T>) -> Instruction, - ) -> (Instruction, OpArg, ir::BlockIdx) { + fn emit(self, f: impl FnOnce(OpArgMarker<T>) -> Instruction) -> (Instruction, OpArg, BlockIdx) { let (marker, arg) = OpArgMarker::new(self); - (f(marker), arg, ir::BlockIdx::NULL) + (f(marker), arg, BlockIdx::NULL) } } -impl EmitArg<bytecode::Label> for ir::BlockIdx { +impl EmitArg<bytecode::Label> for BlockIdx { fn emit( self, f: impl FnOnce(OpArgMarker<bytecode::Label>) -> Instruction, - ) -> (Instruction, OpArg, ir::BlockIdx) { + ) -> (Instruction, OpArg, BlockIdx) { (f(OpArgMarker::marker()), OpArg::null(), self) } } diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index f08b37e8887..de0126f1122 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -29,14 +29,30 @@ pub struct CodeUnitMetadata { // use rustpython_parser_core::source_code::{LineNumber, SourceLocation}; #[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct BlockIdx(pub u32); +pub struct BlockIdx(u32); + impl BlockIdx { - pub const NULL: Self = Self(u32::MAX); - const fn idx(self) -> usize { + pub const NULL: Self = Self::new(u32::MAX); + + /// Creates a new instance of [`BlockIdx`] from a [`u32`]. + #[must_use] + pub const fn new(value: u32) -> Self { + Self(value) + } + + /// Returns the inner value as a [`usize`]. + #[must_use] + pub const fn idx(self) -> usize { self.0 as usize } } +impl From<BlockIdx> for u32 { + fn from(block_idx: BlockIdx) -> Self { + block_idx.0 + } +} + impl ops::Index<BlockIdx> for [Block] { type Output = Block; From b9fa405fd4b9ef7e408b3a47c8301d9dda88e88c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Fri, 12 Dec 2025 19:55:57 +0900 Subject: [PATCH 485/819] Fix test_subprocess --- Lib/test/test_subprocess.py | 1 - crates/vm/src/stdlib/winapi.rs | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 87562230261..bf0099554b5 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -857,7 +857,6 @@ def test_one_environment_variable(self): self.assertEqual(p.returncode, 0) self.assertEqual(stdout.strip(), b"fruit=orange") - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON, null byte is not checked") def test_invalid_cmd(self): # null character in the command name cmd = sys.executable + '\0' diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index 3fb1068a60e..1df3ff0e42d 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -248,6 +248,18 @@ mod _winapi { Ok(ws.into_vec_with_nul()) }; + // Validate no embedded null bytes in command name and command line + if let Some(ref name) = args.name + && name.as_str().contains('\0') + { + return Err(crate::exceptions::cstring_error(vm)); + } + if let Some(ref cmd) = args.command_line + && cmd.as_str().contains('\0') + { + return Err(crate::exceptions::cstring_error(vm)); + } + let app_name = args.name.map(wstr).transpose()?; let app_name = app_name.as_ref().map_or_else(null, |w| w.as_ptr()); From 752c0f68fd76b76aa85552be33a880fac65745e7 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Fri, 12 Dec 2025 20:12:30 +0900 Subject: [PATCH 486/819] fix windows locale --- Lib/test/test_locale.py | 1 - crates/stdlib/src/locale.rs | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py index 9e1f46f6444..71d03f3a3f9 100644 --- a/Lib/test/test_locale.py +++ b/Lib/test/test_locale.py @@ -511,7 +511,6 @@ def test_getsetlocale_issue1813(self): self.skipTest(f"setlocale(LC_CTYPE, {loc!r}) failed: {exc!r}") self.assertEqual(loc, locale.getlocale(locale.LC_CTYPE)) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; Error not raised') @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_setlocale_long_encoding(self): with self.assertRaises(locale.Error): diff --git a/crates/stdlib/src/locale.rs b/crates/stdlib/src/locale.rs index 630d2a419d4..6cca8b9123b 100644 --- a/crates/stdlib/src/locale.rs +++ b/crates/stdlib/src/locale.rs @@ -198,6 +198,34 @@ mod _locale { locale: OptionalArg<Option<PyStrRef>>, } + /// Maximum code page encoding name length on Windows + #[cfg(windows)] + const MAX_CP_LEN: usize = 15; + + /// Check if the encoding part of a locale string is too long (Windows only) + #[cfg(windows)] + fn check_locale_name(locale: &str) -> bool { + if let Some(dot_pos) = locale.find('.') { + let encoding_part = &locale[dot_pos + 1..]; + // Find the end of encoding (could be followed by '@' modifier) + let encoding_len = encoding_part.find('@').unwrap_or(encoding_part.len()); + encoding_len <= MAX_CP_LEN + } else { + true + } + } + + /// Check locale names for LC_ALL (handles semicolon-separated locales) + #[cfg(windows)] + fn check_locale_name_all(locale: &str) -> bool { + for part in locale.split(';') { + if !check_locale_name(part) { + return false; + } + } + true + } + #[pyfunction] fn setlocale(args: LocaleArgs, vm: &VirtualMachine) -> PyResult { let error = error(vm); @@ -208,6 +236,21 @@ mod _locale { let result = match args.locale.flatten() { None => libc::setlocale(args.category, ptr::null()), Some(locale) => { + // On Windows, validate encoding name length + #[cfg(windows)] + { + let valid = if args.category == LC_ALL { + check_locale_name_all(locale.as_str()) + } else { + check_locale_name(locale.as_str()) + }; + if !valid { + return Err(vm.new_exception_msg( + error, + String::from("unsupported locale setting"), + )); + } + } let c_locale: CString = CString::new(locale.as_str()).map_err(|e| e.to_pyexception(vm))?; libc::setlocale(args.category, c_locale.as_ptr()) From a47b32816b1302aa051f0d95e6beace26ad0804e Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:12:02 +0200 Subject: [PATCH 487/819] Merge pull request #6417 from ShaharNaveh/regr-test-doc-leak Add regression test for #4505 --- extra_tests/snippets/builtin_type.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/extra_tests/snippets/builtin_type.py b/extra_tests/snippets/builtin_type.py index 820ee366155..abb68f812be 100644 --- a/extra_tests/snippets/builtin_type.py +++ b/extra_tests/snippets/builtin_type.py @@ -1,6 +1,6 @@ import types -from testutils import assert_raises +from testutils import assert_raises # Spec: https://docs.python.org/2/library/types.html print(None) @@ -111,8 +111,6 @@ class D: with assert_raises(TypeError): del int.__qualname__ -from testutils import assert_raises - import platform if platform.python_implementation() == "RustPython": @@ -607,3 +605,24 @@ class A(type): assert "__dict__" not in A.__dict__ + + +# regression tests for: https://github.com/RustPython/RustPython/issues/4505 + + +def foo(): + def inner(): + pass + + +assert foo.__code__.co_names == () + +stmts = """ +import blah + +def foo(): + pass +""" + +code = compile(stmts, "<test>", "exec") +assert code.co_names == ("blah", "foo") From 49522e7a5ecc0ecc42a9550a36a886db88ee5c2b Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:12:19 +0200 Subject: [PATCH 488/819] Update ruff CI version (#6418) --- .github/workflows/ci.yaml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2f70bb54e09..c5ac2c87ae4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -314,19 +314,22 @@ jobs: components: clippy - name: run clippy on wasm run: cargo clippy --manifest-path=crates/wasm/Cargo.toml -- -Dwarnings - - uses: actions/setup-python@v6.1.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - - name: install ruff - run: python -m pip install ruff==0.11.8 + - name: Ensure docs generate no warnings run: cargo doc - - name: run ruff check - run: ruff check --diff - - name: run ruff format - run: ruff format --check + + - name: Install ruff + uses: astral-sh/ruff-action@57714a7c8a2e59f32539362ba31877a1957dded1 # v3.5.1 + with: + version: "0.14.9" + + - run: ruff check --diff + + - run: ruff format --check + - name: install prettier run: yarn global add prettier && echo "$(yarn global bin)" >>$GITHUB_PATH + - name: check wasm code with prettier # prettier doesn't handle ignore files very well: https://github.com/prettier/prettier/issues/8506 run: cd wasm && git ls-files -z | xargs -0 prettier --check -u From be5f660dfe4acb701adf2a4d3f5020bfe83597ee Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Fri, 12 Dec 2025 20:43:04 +0900 Subject: [PATCH 489/819] impl more ast --- crates/vm/src/stdlib/ast/pattern.rs | 103 ++++++++++++++++++---------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/crates/vm/src/stdlib/ast/pattern.rs b/crates/vm/src/stdlib/ast/pattern.rs index 9567ed38d41..d8128cb0622 100644 --- a/crates/vm/src/stdlib/ast/pattern.rs +++ b/crates/vm/src/stdlib/ast/pattern.rs @@ -199,16 +199,31 @@ impl Node for ruff::PatternMatchSingleton { } impl Node for ruff::Singleton { - fn ast_to_object(self, _vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { - todo!() + fn ast_to_object(self, vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { + match self { + ruff::Singleton::None => vm.ctx.none(), + ruff::Singleton::True => vm.ctx.new_bool(true).into(), + ruff::Singleton::False => vm.ctx.new_bool(false).into(), + } } fn ast_from_object( - _vm: &VirtualMachine, + vm: &VirtualMachine, _source_file: &SourceFile, - _object: PyObjectRef, + object: PyObjectRef, ) -> PyResult<Self> { - todo!() + if vm.is_none(&object) { + Ok(ruff::Singleton::None) + } else if object.is(&vm.ctx.true_value) { + Ok(ruff::Singleton::True) + } else if object.is(&vm.ctx.false_value) { + Ok(ruff::Singleton::False) + } else { + Err(vm.new_value_error(format!( + "Expected None, True, or False, got {:?}", + object.class().name() + ))) + } } } @@ -372,57 +387,51 @@ impl Node for ruff::PatternMatchClass { } } -struct PatternMatchClassPatterns { - pub _range: TextRange, // TODO: Use this -} +struct PatternMatchClassPatterns(Vec<ruff::Pattern>); impl Node for PatternMatchClassPatterns { - fn ast_to_object(self, _vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { - todo!() + fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { + self.0.ast_to_object(vm, source_file) } fn ast_from_object( - _vm: &VirtualMachine, - _source_file: &SourceFile, - _object: PyObjectRef, + vm: &VirtualMachine, + source_file: &SourceFile, + object: PyObjectRef, ) -> PyResult<Self> { - todo!() + Ok(Self(Node::ast_from_object(vm, source_file, object)?)) } } -struct PatternMatchClassKeywordAttributes { - pub _range: TextRange, // TODO: Use this -} +struct PatternMatchClassKeywordAttributes(Vec<ruff::Identifier>); impl Node for PatternMatchClassKeywordAttributes { - fn ast_to_object(self, _vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { - todo!() + fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { + self.0.ast_to_object(vm, source_file) } fn ast_from_object( - _vm: &VirtualMachine, - _source_file: &SourceFile, - _object: PyObjectRef, + vm: &VirtualMachine, + source_file: &SourceFile, + object: PyObjectRef, ) -> PyResult<Self> { - todo!() + Ok(Self(Node::ast_from_object(vm, source_file, object)?)) } } -struct PatternMatchClassKeywordPatterns { - pub _range: TextRange, // TODO: Use this -} +struct PatternMatchClassKeywordPatterns(Vec<ruff::Pattern>); impl Node for PatternMatchClassKeywordPatterns { - fn ast_to_object(self, _vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { - todo!() + fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { + self.0.ast_to_object(vm, source_file) } fn ast_from_object( - _vm: &VirtualMachine, - _source_file: &SourceFile, - _object: PyObjectRef, + vm: &VirtualMachine, + source_file: &SourceFile, + object: PyObjectRef, ) -> PyResult<Self> { - todo!() + Ok(Self(Node::ast_from_object(vm, source_file, object)?)) } } // constructor @@ -532,20 +541,38 @@ impl Node for ruff::PatternMatchOr { } fn split_pattern_match_class( - _arguments: ruff::PatternArguments, + arguments: ruff::PatternArguments, ) -> ( PatternMatchClassPatterns, PatternMatchClassKeywordAttributes, PatternMatchClassKeywordPatterns, ) { - todo!() + let patterns = PatternMatchClassPatterns(arguments.patterns); + let kwd_attrs = PatternMatchClassKeywordAttributes( + arguments.keywords.iter().map(|k| k.attr.clone()).collect(), + ); + let kwd_patterns = PatternMatchClassKeywordPatterns( + arguments.keywords.into_iter().map(|k| k.pattern).collect(), + ); + (patterns, kwd_attrs, kwd_patterns) } /// Merges the pattern match class attributes and patterns, opposite of [`split_pattern_match_class`]. fn merge_pattern_match_class( - _patterns: PatternMatchClassPatterns, - _kwd_attrs: PatternMatchClassKeywordAttributes, - _kwd_patterns: PatternMatchClassKeywordPatterns, + patterns: PatternMatchClassPatterns, + kwd_attrs: PatternMatchClassKeywordAttributes, + kwd_patterns: PatternMatchClassKeywordPatterns, ) -> (Vec<ruff::Pattern>, Vec<ruff::PatternKeyword>) { - todo!() + let keywords = kwd_attrs + .0 + .into_iter() + .zip(kwd_patterns.0) + .map(|(attr, pattern)| ruff::PatternKeyword { + range: Default::default(), + node_index: Default::default(), + attr, + pattern, + }) + .collect(); + (patterns.0, keywords) } From d22956ebc1d06ace68bf0e429fa28a1c8b160a10 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Fri, 12 Dec 2025 15:48:33 +0900 Subject: [PATCH 490/819] inspect from v3.13.10 --- Lib/inspect.py | 1441 ++-- Lib/test/test_inspect/__init__.py | 6 + Lib/test/test_inspect/inspect_fodder.py | 120 + Lib/test/test_inspect/inspect_fodder2.py | 403 + .../test_inspect/inspect_stock_annotations.py | 28 + .../inspect_stringized_annotations.py | 34 + .../inspect_stringized_annotations_2.py | 3 + .../inspect_stringized_annotations_pep695.py | 87 + Lib/test/test_inspect/test_inspect.py | 6626 +++++++++++++++++ 9 files changed, 8183 insertions(+), 565 deletions(-) create mode 100644 Lib/test/test_inspect/__init__.py create mode 100644 Lib/test/test_inspect/inspect_fodder.py create mode 100644 Lib/test/test_inspect/inspect_fodder2.py create mode 100644 Lib/test/test_inspect/inspect_stock_annotations.py create mode 100644 Lib/test/test_inspect/inspect_stringized_annotations.py create mode 100644 Lib/test/test_inspect/inspect_stringized_annotations_2.py create mode 100644 Lib/test/test_inspect/inspect_stringized_annotations_pep695.py create mode 100644 Lib/test/test_inspect/test_inspect.py diff --git a/Lib/inspect.py b/Lib/inspect.py index a84f3346b35..5a814f97b5b 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -24,6 +24,8 @@ stack(), trace() - get info about frames on the stack or in a traceback signature() - get a Signature object for the callable + + get_annotations() - safely compute an object's annotations """ # This module is in the public domain. No warranties. @@ -31,7 +33,116 @@ __author__ = ('Ka-Ping Yee <ping@lfw.org>', 'Yury Selivanov <yselivanov@sprymix.com>') +__all__ = [ + "AGEN_CLOSED", + "AGEN_CREATED", + "AGEN_RUNNING", + "AGEN_SUSPENDED", + "ArgInfo", + "Arguments", + "Attribute", + "BlockFinder", + "BoundArguments", + "BufferFlags", + "CORO_CLOSED", + "CORO_CREATED", + "CORO_RUNNING", + "CORO_SUSPENDED", + "CO_ASYNC_GENERATOR", + "CO_COROUTINE", + "CO_GENERATOR", + "CO_ITERABLE_COROUTINE", + "CO_NESTED", + "CO_NEWLOCALS", + "CO_NOFREE", + "CO_OPTIMIZED", + "CO_VARARGS", + "CO_VARKEYWORDS", + "ClassFoundException", + "ClosureVars", + "EndOfBlock", + "FrameInfo", + "FullArgSpec", + "GEN_CLOSED", + "GEN_CREATED", + "GEN_RUNNING", + "GEN_SUSPENDED", + "Parameter", + "Signature", + "TPFLAGS_IS_ABSTRACT", + "Traceback", + "classify_class_attrs", + "cleandoc", + "currentframe", + "findsource", + "formatannotation", + "formatannotationrelativeto", + "formatargvalues", + "get_annotations", + "getabsfile", + "getargs", + "getargvalues", + "getasyncgenlocals", + "getasyncgenstate", + "getattr_static", + "getblock", + "getcallargs", + "getclasstree", + "getclosurevars", + "getcomments", + "getcoroutinelocals", + "getcoroutinestate", + "getdoc", + "getfile", + "getframeinfo", + "getfullargspec", + "getgeneratorlocals", + "getgeneratorstate", + "getinnerframes", + "getlineno", + "getmembers", + "getmembers_static", + "getmodule", + "getmodulename", + "getmro", + "getouterframes", + "getsource", + "getsourcefile", + "getsourcelines", + "indentsize", + "isabstract", + "isasyncgen", + "isasyncgenfunction", + "isawaitable", + "isbuiltin", + "isclass", + "iscode", + "iscoroutine", + "iscoroutinefunction", + "isdatadescriptor", + "isframe", + "isfunction", + "isgenerator", + "isgeneratorfunction", + "isgetsetdescriptor", + "ismemberdescriptor", + "ismethod", + "ismethoddescriptor", + "ismethodwrapper", + "ismodule", + "isroutine", + "istraceback", + "markcoroutinefunction", + "signature", + "stack", + "trace", + "unwrap", + "walktree", +] + + import abc +import ast import dis import collections.abc import enum @@ -44,47 +155,156 @@ import tokenize import token import types -import warnings import functools import builtins +from keyword import iskeyword from operator import attrgetter from collections import namedtuple, OrderedDict +from weakref import ref as make_weakref # Create constants for the compiler flags in Include/code.h # We try to get them from dis to avoid duplication mod_dict = globals() for k, v in dis.COMPILER_FLAG_NAMES.items(): mod_dict["CO_" + v] = k +del k, v, mod_dict # See Include/object.h TPFLAGS_IS_ABSTRACT = 1 << 20 + +def get_annotations(obj, *, globals=None, locals=None, eval_str=False): + """Compute the annotations dict for an object. + + obj may be a callable, class, or module. + Passing in an object of any other type raises TypeError. + + Returns a dict. get_annotations() returns a new dict every time + it's called; calling it twice on the same object will return two + different but equivalent dicts. + + This function handles several details for you: + + * If eval_str is true, values of type str will + be un-stringized using eval(). This is intended + for use with stringized annotations + ("from __future__ import annotations"). + * If obj doesn't have an annotations dict, returns an + empty dict. (Functions and methods always have an + annotations dict; classes, modules, and other types of + callables may not.) + * Ignores inherited annotations on classes. If a class + doesn't have its own annotations dict, returns an empty dict. + * All accesses to object members and dict values are done + using getattr() and dict.get() for safety. + * Always, always, always returns a freshly-created dict. + + eval_str controls whether or not values of type str are replaced + with the result of calling eval() on those values: + + * If eval_str is true, eval() is called on values of type str. + * If eval_str is false (the default), values of type str are unchanged. + + globals and locals are passed in to eval(); see the documentation + for eval() for more information. If either globals or locals is + None, this function may replace that value with a context-specific + default, contingent on type(obj): + + * If obj is a module, globals defaults to obj.__dict__. + * If obj is a class, globals defaults to + sys.modules[obj.__module__].__dict__ and locals + defaults to the obj class namespace. + * If obj is a callable, globals defaults to obj.__globals__, + although if obj is a wrapped function (using + functools.update_wrapper()) it is first unwrapped. + """ + if isinstance(obj, type): + # class + obj_dict = getattr(obj, '__dict__', None) + if obj_dict and hasattr(obj_dict, 'get'): + ann = obj_dict.get('__annotations__', None) + if isinstance(ann, types.GetSetDescriptorType): + ann = None + else: + ann = None + + obj_globals = None + module_name = getattr(obj, '__module__', None) + if module_name: + module = sys.modules.get(module_name, None) + if module: + obj_globals = getattr(module, '__dict__', None) + obj_locals = dict(vars(obj)) + unwrap = obj + elif isinstance(obj, types.ModuleType): + # module + ann = getattr(obj, '__annotations__', None) + obj_globals = getattr(obj, '__dict__') + obj_locals = None + unwrap = None + elif callable(obj): + # this includes types.Function, types.BuiltinFunctionType, + # types.BuiltinMethodType, functools.partial, functools.singledispatch, + # "class funclike" from Lib/test/test_inspect... on and on it goes. + ann = getattr(obj, '__annotations__', None) + obj_globals = getattr(obj, '__globals__', None) + obj_locals = None + unwrap = obj + else: + raise TypeError(f"{obj!r} is not a module, class, or callable.") + + if ann is None: + return {} + + if not isinstance(ann, dict): + raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None") + + if not ann: + return {} + + if not eval_str: + return dict(ann) + + if unwrap is not None: + while True: + if hasattr(unwrap, '__wrapped__'): + unwrap = unwrap.__wrapped__ + continue + if isinstance(unwrap, functools.partial): + unwrap = unwrap.func + continue + break + if hasattr(unwrap, "__globals__"): + obj_globals = unwrap.__globals__ + + if globals is None: + globals = obj_globals + if locals is None: + locals = obj_locals or {} + + # "Inject" type parameters into the local namespace + # (unless they are shadowed by assignments *in* the local namespace), + # as a way of emulating annotation scopes when calling `eval()` + if type_params := getattr(obj, "__type_params__", ()): + locals = {param.__name__: param for param in type_params} | locals + + return_value = {key: + value if not isinstance(value, str) else eval(value, globals, locals) + for key, value in ann.items() } + return return_value + + # ----------------------------------------------------------- type-checking def ismodule(object): - """Return true if the object is a module. - - Module objects provide these attributes: - __cached__ pathname to byte compiled file - __doc__ documentation string - __file__ filename (missing for built-in modules)""" + """Return true if the object is a module.""" return isinstance(object, types.ModuleType) def isclass(object): - """Return true if the object is a class. - - Class objects provide these attributes: - __doc__ documentation string - __module__ name of module in which this class was defined""" + """Return true if the object is a class.""" return isinstance(object, type) def ismethod(object): - """Return true if the object is an instance method. - - Instance method objects provide these attributes: - __doc__ documentation string - __name__ name with which this method was defined - __func__ function object containing implementation of method - __self__ instance to which this method is bound""" + """Return true if the object is an instance method.""" return isinstance(object, types.MethodType) def ismethoddescriptor(object): @@ -93,9 +313,10 @@ def ismethoddescriptor(object): But not if ismethod() or isclass() or isfunction() are true. This is new in Python 2.2, and, for example, is true of int.__add__. - An object passing this test has a __get__ attribute but not a __set__ - attribute, but beyond that the set of attributes varies. __name__ is - usually sensible, and __doc__ often is. + An object passing this test has a __get__ attribute, but not a + __set__ attribute or a __delete__ attribute. Beyond that, the set + of attributes varies; __name__ is usually sensible, and __doc__ + often is. Methods implemented via descriptors that also pass one of the other tests return false from the ismethoddescriptor() test, simply because @@ -104,8 +325,15 @@ def ismethoddescriptor(object): if isclass(object) or ismethod(object) or isfunction(object): # mutual exclusion return False + if isinstance(object, functools.partial): + # Lie for children. The addition of partial.__get__ + # doesn't currently change the partial objects behaviour, + # not counting a warning about future changes. + return False tp = type(object) - return hasattr(tp, "__get__") and not hasattr(tp, "__set__") + return (hasattr(tp, "__get__") + and not hasattr(tp, "__set__") + and not hasattr(tp, "__delete__")) def isdatadescriptor(object): """Return true if the object is a data descriptor. @@ -170,12 +398,14 @@ def isfunction(object): def _has_code_flag(f, flag): """Return true if ``f`` is a function (or a method or functools.partial - wrapper wrapping a function) whose code object has the given ``flag`` + wrapper wrapping a function or a functools.partialmethod wrapping a + function) whose code object has the given ``flag`` set in its flags.""" + f = functools._unwrap_partialmethod(f) while ismethod(f): f = f.__func__ f = functools._unwrap_partial(f) - if not isfunction(f): + if not (isfunction(f) or _signature_is_functionlike(f)): return False return bool(f.__code__.co_flags & flag) @@ -186,12 +416,31 @@ def isgeneratorfunction(obj): See help(isfunction) for a list of attributes.""" return _has_code_flag(obj, CO_GENERATOR) +# A marker for markcoroutinefunction and iscoroutinefunction. +_is_coroutine_mark = object() + +def _has_coroutine_mark(f): + while ismethod(f): + f = f.__func__ + f = functools._unwrap_partial(f) + return getattr(f, "_is_coroutine_marker", None) is _is_coroutine_mark + +def markcoroutinefunction(func): + """ + Decorator to ensure callable is recognised as a coroutine function. + """ + if hasattr(func, '__func__'): + func = func.__func__ + func._is_coroutine_marker = _is_coroutine_mark + return func + def iscoroutinefunction(obj): """Return true if the object is a coroutine function. - Coroutine functions are defined with "async def" syntax. + Coroutine functions are normally defined with "async def" syntax, but may + be marked via markcoroutinefunction. """ - return _has_code_flag(obj, CO_COROUTINE) + return _has_code_flag(obj, CO_COROUTINE) or _has_coroutine_mark(obj) def isasyncgenfunction(obj): """Return true if the object is an asynchronous generator function. @@ -276,7 +525,7 @@ def iscode(object): co_kwonlyargcount number of keyword only arguments (not including ** arg) co_lnotab encoded mapping of line numbers to bytecode indices co_name name with which this code object was defined - co_names tuple of names of local variables + co_names tuple of names other than arguments and function locals co_nlocals number of local variables co_stacksize virtual machine stack space required co_varnames tuple of names of arguments and local variables""" @@ -291,28 +540,30 @@ def isbuiltin(object): __self__ instance to which a method is bound, or None""" return isinstance(object, types.BuiltinFunctionType) +def ismethodwrapper(object): + """Return true if the object is a method wrapper.""" + return isinstance(object, types.MethodWrapperType) + def isroutine(object): """Return true if the object is any kind of function or method.""" return (isbuiltin(object) or isfunction(object) or ismethod(object) - or ismethoddescriptor(object)) + or ismethoddescriptor(object) + or ismethodwrapper(object)) def isabstract(object): """Return true if the object is an abstract base class (ABC).""" if not isinstance(object, type): return False - # TODO: RUSTPYTHON - # TPFLAGS_IS_ABSTRACT is not being set for abstract classes, so this implementation differs from CPython. - # if object.__flags__ & TPFLAGS_IS_ABSTRACT: - # return True + if object.__flags__ & TPFLAGS_IS_ABSTRACT: + return True if not issubclass(type(object), abc.ABCMeta): return False if hasattr(object, '__abstractmethods__'): # It looks like ABCMeta.__new__ has finished running; # TPFLAGS_IS_ABSTRACT should have been accurate. - # return False - return bool(getattr(object, '__abstractmethods__')) + return False # It looks like ABCMeta.__new__ has not finished running yet; we're # probably in __init_subclass__. We'll look for abstractmethods manually. for name, value in object.__dict__.items(): @@ -325,32 +576,30 @@ def isabstract(object): return True return False -def getmembers(object, predicate=None): - """Return all members of an object as (name, value) pairs sorted by name. - Optionally, only return members that satisfy a given predicate.""" - if isclass(object): - mro = (object,) + getmro(object) - else: - mro = () +def _getmembers(object, predicate, getter): results = [] processed = set() names = dir(object) - # :dd any DynamicClassAttributes to the list of names if object is a class; - # this may result in duplicate entries if, for example, a virtual - # attribute with the same name as a DynamicClassAttribute exists - try: - for base in object.__bases__: - for k, v in base.__dict__.items(): - if isinstance(v, types.DynamicClassAttribute): - names.append(k) - except AttributeError: - pass + if isclass(object): + mro = getmro(object) + # add any DynamicClassAttributes to the list of names if object is a class; + # this may result in duplicate entries if, for example, a virtual + # attribute with the same name as a DynamicClassAttribute exists + try: + for base in object.__bases__: + for k, v in base.__dict__.items(): + if isinstance(v, types.DynamicClassAttribute): + names.append(k) + except AttributeError: + pass + else: + mro = () for key in names: # First try to get the value via getattr. Some descriptors don't # like calling their __get__ (see bug #1785), so fall back to # looking in the __dict__. try: - value = getattr(object, key) + value = getter(object, key) # handle the duplicate key if key in processed: raise AttributeError @@ -369,6 +618,25 @@ def getmembers(object, predicate=None): results.sort(key=lambda pair: pair[0]) return results +def getmembers(object, predicate=None): + """Return all members of an object as (name, value) pairs sorted by name. + Optionally, only return members that satisfy a given predicate.""" + return _getmembers(object, predicate, getattr) + +def getmembers_static(object, predicate=None): + """Return all members of an object as (name, value) pairs sorted by name + without triggering dynamic lookup via the descriptor protocol, + __getattr__ or __getattribute__. Optionally, only return members that + satisfy a given predicate. + + Note: this function may not be able to retrieve all members + that getmembers can fetch (like dynamically created attributes) + and may find members that getmembers can't (like descriptors + that raise AttributeError). It can also return descriptor objects + instead of instance members in some cases. + """ + return _getmembers(object, predicate, getattr_static) + Attribute = namedtuple('Attribute', 'name kind defining_class object') def classify_class_attrs(cls): @@ -409,7 +677,7 @@ def classify_class_attrs(cls): # attribute with the same name as a DynamicClassAttribute exists. for base in mro: for k, v in base.__dict__.items(): - if isinstance(v, types.DynamicClassAttribute): + if isinstance(v, types.DynamicClassAttribute) and v.fget is not None: names.append(k) result = [] processed = set() @@ -432,7 +700,7 @@ def classify_class_attrs(cls): if name == '__dict__': raise Exception("__dict__ is special, don't want the proxy") get_obj = getattr(cls, name) - except Exception as exc: + except Exception: pass else: homecls = getattr(get_obj, "__objclass__", homecls) @@ -509,18 +777,14 @@ def unwrap(func, *, stop=None): :exc:`ValueError` is raised if a cycle is encountered. """ - if stop is None: - def _is_wrapper(f): - return hasattr(f, '__wrapped__') - else: - def _is_wrapper(f): - return hasattr(f, '__wrapped__') and not stop(f) f = func # remember the original func for error reporting # Memoise by id to tolerate non-hashable objects, but store objects to # ensure they aren't destroyed, which would allow their IDs to be reused. memo = {id(f): f} recursion_limit = sys.getrecursionlimit() - while _is_wrapper(func): + while not isinstance(func, type) and hasattr(func, '__wrapped__'): + if stop is not None and stop(func): + break func = func.__wrapped__ id_func = id(func) if (id_func in memo) or (len(memo) >= recursion_limit): @@ -581,9 +845,8 @@ def _finddoc(obj): cls = self.__class__ # Should be tested before isdatadescriptor(). elif isinstance(obj, property): - func = obj.fget - name = func.__name__ - cls = _findclass(func) + name = obj.__name__ + cls = _findclass(obj.fget) if cls is None or getattr(cls, name) is not obj: return None elif ismethoddescriptor(obj) or isdatadescriptor(obj): @@ -630,29 +893,28 @@ def cleandoc(doc): Any whitespace that can be uniformly removed from the second line onwards is removed.""" - try: - lines = doc.expandtabs().split('\n') - except UnicodeError: - return None - else: - # Find minimum indentation of any non-blank lines after first line. - margin = sys.maxsize - for line in lines[1:]: - content = len(line.lstrip()) - if content: - indent = len(line) - content - margin = min(margin, indent) - # Remove indentation. - if lines: - lines[0] = lines[0].lstrip() - if margin < sys.maxsize: - for i in range(1, len(lines)): lines[i] = lines[i][margin:] - # Remove any trailing or leading blank lines. - while lines and not lines[-1]: - lines.pop() - while lines and not lines[0]: - lines.pop(0) - return '\n'.join(lines) + lines = doc.expandtabs().split('\n') + + # Find minimum indentation of any non-blank lines after first line. + margin = sys.maxsize + for line in lines[1:]: + content = len(line.lstrip(' ')) + if content: + indent = len(line) - content + margin = min(margin, indent) + # Remove indentation. + if lines: + lines[0] = lines[0].lstrip(' ') + if margin < sys.maxsize: + for i in range(1, len(lines)): + lines[i] = lines[i][margin:] + # Remove any trailing or leading blank lines. + while lines and not lines[-1]: + lines.pop() + while lines and not lines[0]: + lines.pop(0) + return '\n'.join(lines) + def getfile(object): """Work out which source or compiled file an object was defined in.""" @@ -665,6 +927,8 @@ def getfile(object): module = sys.modules.get(object.__module__) if getattr(module, '__file__', None): return module.__file__ + if object.__module__ == '__main__': + raise OSError('source code not available') raise TypeError('{!r} is a built-in class'.format(object)) if ismethod(object): object = object.__func__ @@ -705,13 +969,20 @@ def getsourcefile(object): elif any(filename.endswith(s) for s in importlib.machinery.EXTENSION_SUFFIXES): return None + elif filename.endswith(".fwork"): + # Apple mobile framework markers are another type of non-source file + return None + + # return a filename found in the linecache even if it doesn't exist on disk + if filename in linecache.cache: + return filename if os.path.exists(filename): return filename # only return a non-existent filename if the module has a PEP 302 loader - if getattr(getmodule(object, filename), '__loader__', None) is not None: + module = getmodule(object, filename) + if getattr(module, '__loader__', None) is not None: return filename - # or it is in the linecache - if filename in linecache.cache: + elif getattr(getattr(module, "__spec__", None), "loader", None) is not None: return filename def getabsfile(object, _filename=None): @@ -732,19 +1003,20 @@ def getmodule(object, _filename=None): return object if hasattr(object, '__module__'): return sys.modules.get(object.__module__) + # Try the filename to modulename cache if _filename is not None and _filename in modulesbyfile: return sys.modules.get(modulesbyfile[_filename]) # Try the cache again with the absolute file name try: file = getabsfile(object, _filename) - except TypeError: + except (TypeError, FileNotFoundError): return None if file in modulesbyfile: return sys.modules.get(modulesbyfile[file]) # Update the filename to module name cache and check yet again # Copy sys.modules in order to cope with changes while iterating - for modname, module in list(sys.modules.items()): + for modname, module in sys.modules.copy().items(): if ismodule(module) and hasattr(module, '__file__'): f = module.__file__ if f == _filesbymodname.get(modname, None): @@ -772,6 +1044,11 @@ def getmodule(object, _filename=None): if builtinobject is object: return builtin + +class ClassFoundException(Exception): + pass + + def findsource(object): """Return the entire source file and starting line number for an object. @@ -789,12 +1066,14 @@ def findsource(object): # Allow filenames in form of "<something>" to pass through. # `doctest` monkeypatches `linecache` module to enable # inspection, so let `linecache.getlines` to be called. - if not (file.startswith('<') and file.endswith('>')): + if (not (file.startswith('<') and file.endswith('>'))) or file.endswith('.fwork'): raise OSError('source code not available') module = getmodule(object, file) if module: lines = linecache.getlines(file, module.__dict__) + if not lines and file.startswith('<') and hasattr(object, "__code__"): + lines = linecache._getlines_from_code(object.__code__) else: lines = linecache.getlines(file) if not lines: @@ -804,27 +1083,13 @@ def findsource(object): return lines, 0 if isclass(object): - name = object.__name__ - pat = re.compile(r'^(\s*)class\s*' + name + r'\b') - # make some effort to find the best matching class definition: - # use the one with the least indentation, which is the one - # that's most probably not inside a function definition. - candidates = [] - for i in range(len(lines)): - match = pat.match(lines[i]) - if match: - # if it's at toplevel, it's already the best one - if lines[i][0] == 'c': - return lines, i - # else add whitespace to candidate list - candidates.append((match.group(1), i)) - if candidates: - # this will sort by whitespace, and by line number, - # less whitespace first - candidates.sort() - return lines, candidates[0][1] - else: - raise OSError('could not find class definition') + try: + lnum = vars(object)['__firstlineno__'] - 1 + except (TypeError, KeyError): + raise OSError('source code not available') + if lnum >= len(lines): + raise OSError('lineno is out of bounds') + return lines, lnum if ismethod(object): object = object.__func__ @@ -838,10 +1103,8 @@ def findsource(object): if not hasattr(object, 'co_firstlineno'): raise OSError('could not find function definition') lnum = object.co_firstlineno - 1 - pat = re.compile(r'^(\s*def\s)|(\s*async\s+def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)') - while lnum > 0: - if pat.match(lines[lnum]): break - lnum = lnum - 1 + if lnum >= len(lines): + raise OSError('lineno is out of bounds') return lines, lnum raise OSError('could not find code object') @@ -896,43 +1159,43 @@ class BlockFinder: """Provide a tokeneater() method to detect the end of a code block.""" def __init__(self): self.indent = 0 - self.islambda = False + self.singleline = False self.started = False self.passline = False self.indecorator = False - self.decoratorhasargs = False self.last = 1 + self.body_col0 = None def tokeneater(self, type, token, srowcol, erowcol, line): if not self.started and not self.indecorator: + if type in (tokenize.INDENT, tokenize.COMMENT, tokenize.NL): + pass + elif token == "async": + pass # skip any decorators - if token == "@": + elif token == "@": self.indecorator = True - # look for the first "def", "class" or "lambda" - elif token in ("def", "class", "lambda"): - if token == "lambda": - self.islambda = True + else: + # For "def" and "class" scan to the end of the block. + # For "lambda" and generator expression scan to + # the end of the logical line. + self.singleline = token not in ("def", "class") self.started = True self.passline = True # skip to the end of the line - elif token == "(": - if self.indecorator: - self.decoratorhasargs = True - elif token == ")": - if self.indecorator: - self.indecorator = False - self.decoratorhasargs = False elif type == tokenize.NEWLINE: self.passline = False # stop skipping when a NEWLINE is seen self.last = srowcol[0] - if self.islambda: # lambdas always end at the first NEWLINE + if self.singleline: raise EndOfBlock # hitting a NEWLINE when in a decorator without args # ends the decorator - if self.indecorator and not self.decoratorhasargs: + if self.indecorator: self.indecorator = False elif self.passline: pass elif type == tokenize.INDENT: + if self.body_col0 is None and self.started: + self.body_col0 = erowcol[1] self.indent = self.indent + 1 self.passline = True elif type == tokenize.DEDENT: @@ -942,6 +1205,10 @@ def tokeneater(self, type, token, srowcol, erowcol, line): # not e.g. for "if: else:" or "try: finally:" blocks) if self.indent <= 0: raise EndOfBlock + elif type == tokenize.COMMENT: + if self.body_col0 is not None and srowcol[1] >= self.body_col0: + # Include comments if indented at least as much as the block + self.last = srowcol[0] elif self.indent == 0 and type not in (tokenize.COMMENT, tokenize.NL): # any other token on the same indentation level end the previous # block as well, except the pseudo-tokens COMMENT and NL. @@ -956,6 +1223,14 @@ def getblock(lines): blockfinder.tokeneater(*_token) except (EndOfBlock, IndentationError): pass + except SyntaxError as e: + if "unmatched" not in e.msg: + raise e from None + _, *_token_info = _token + try: + blockfinder.tokeneater(tokenize.NEWLINE, *_token_info) + except (EndOfBlock, IndentationError): + pass return lines[:blockfinder.last] def getsourcelines(object): @@ -1043,7 +1318,6 @@ def getargs(co): nkwargs = co.co_kwonlyargcount args = list(names[:nargs]) kwonlyargs = list(names[nargs:nargs+nkwargs]) - step = 0 nargs += nkwargs varargs = None @@ -1055,37 +1329,6 @@ def getargs(co): varkw = co.co_varnames[nargs] return Arguments(args + kwonlyargs, varargs, varkw) -ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults') - -def getargspec(func): - """Get the names and default values of a function's parameters. - - A tuple of four things is returned: (args, varargs, keywords, defaults). - 'args' is a list of the argument names, including keyword-only argument names. - 'varargs' and 'keywords' are the names of the * and ** parameters or None. - 'defaults' is an n-tuple of the default values of the last n parameters. - - This function is deprecated, as it does not support annotations or - keyword-only parameters and will raise ValueError if either is present - on the supplied callable. - - For a more structured introspection API, use inspect.signature() instead. - - Alternatively, use getfullargspec() for an API with a similar namedtuple - based interface, but full support for annotations and keyword-only - parameters. - - Deprecated since Python 3.5, use `inspect.getfullargspec()`. - """ - warnings.warn("inspect.getargspec() is deprecated since Python 3.0, " - "use inspect.signature() or inspect.getfullargspec()", - DeprecationWarning, stacklevel=2) - args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \ - getfullargspec(func) - if kwonlyargs or ann: - raise ValueError("Function has keyword-only parameters or annotations" - ", use inspect.signature() API which can support them") - return ArgSpec(args, varargs, varkw, defaults) FullArgSpec = namedtuple('FullArgSpec', 'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations') @@ -1126,7 +1369,8 @@ def getfullargspec(func): sig = _signature_from_callable(func, follow_wrapper_chains=False, skip_bound_arg=False, - sigcls=Signature) + sigcls=Signature, + eval_str=False) except Exception as ex: # Most of the times 'signature' will raise ValueError. # But, it can also raise AttributeError, and, maybe something @@ -1139,7 +1383,6 @@ def getfullargspec(func): varkw = None posonlyargs = [] kwonlyargs = [] - defaults = () annotations = {} defaults = () kwdefaults = {} @@ -1197,7 +1440,12 @@ def getargvalues(frame): def formatannotation(annotation, base_module=None): if getattr(annotation, '__module__', None) == 'typing': - return repr(annotation).replace('typing.', '') + def repl(match): + text = match.group() + return text.removeprefix('typing.') + return re.sub(r'[\w\.]+', repl, repr(annotation)) + if isinstance(annotation, types.GenericAlias): + return str(annotation) if isinstance(annotation, type): if annotation.__module__ in ('builtins', base_module): return annotation.__qualname__ @@ -1210,63 +1458,6 @@ def _formatannotation(annotation): return formatannotation(annotation, module) return _formatannotation -def formatargspec(args, varargs=None, varkw=None, defaults=None, - kwonlyargs=(), kwonlydefaults={}, annotations={}, - formatarg=str, - formatvarargs=lambda name: '*' + name, - formatvarkw=lambda name: '**' + name, - formatvalue=lambda value: '=' + repr(value), - formatreturns=lambda text: ' -> ' + text, - formatannotation=formatannotation): - """Format an argument spec from the values returned by getfullargspec. - - The first seven arguments are (args, varargs, varkw, defaults, - kwonlyargs, kwonlydefaults, annotations). The other five arguments - are the corresponding optional formatting functions that are called to - turn names and values into strings. The last argument is an optional - function to format the sequence of arguments. - - Deprecated since Python 3.5: use the `signature` function and `Signature` - objects. - """ - - from warnings import warn - - warn("`formatargspec` is deprecated since Python 3.5. Use `signature` and " - "the `Signature` object directly", - DeprecationWarning, - stacklevel=2) - - def formatargandannotation(arg): - result = formatarg(arg) - if arg in annotations: - result += ': ' + formatannotation(annotations[arg]) - return result - specs = [] - if defaults: - firstdefault = len(args) - len(defaults) - for i, arg in enumerate(args): - spec = formatargandannotation(arg) - if defaults and i >= firstdefault: - spec = spec + formatvalue(defaults[i - firstdefault]) - specs.append(spec) - if varargs is not None: - specs.append(formatvarargs(formatargandannotation(varargs))) - else: - if kwonlyargs: - specs.append('*') - if kwonlyargs: - for kwonlyarg in kwonlyargs: - spec = formatargandannotation(kwonlyarg) - if kwonlydefaults and kwonlyarg in kwonlydefaults: - spec += formatvalue(kwonlydefaults[kwonlyarg]) - specs.append(spec) - if varkw is not None: - specs.append(formatvarkw(formatargandannotation(varkw))) - result = '(' + ', '.join(specs) + ')' - if 'return' in annotations: - result += formatreturns(formatannotation(annotations['return'])) - return result def formatargvalues(args, varargs, varkw, locals, formatarg=str, @@ -1425,11 +1616,15 @@ def getclosurevars(func): global_vars = {} builtin_vars = {} unbound_names = set() - for name in code.co_names: - if name in ("None", "True", "False"): - # Because these used to be builtins instead of keywords, they - # may still show up as name references. We ignore them. - continue + global_names = set() + for instruction in dis.get_instructions(code): + opname = instruction.opname + name = instruction.argval + if opname == "LOAD_ATTR": + unbound_names.add(name) + elif opname == "LOAD_GLOBAL": + global_names.add(name) + for name in global_names: try: global_vars[name] = global_ns[name] except KeyError: @@ -1443,7 +1638,30 @@ def getclosurevars(func): # -------------------------------------------------- stack frame extraction -Traceback = namedtuple('Traceback', 'filename lineno function code_context index') +_Traceback = namedtuple('_Traceback', 'filename lineno function code_context index') + +class Traceback(_Traceback): + def __new__(cls, filename, lineno, function, code_context, index, *, positions=None): + instance = super().__new__(cls, filename, lineno, function, code_context, index) + instance.positions = positions + return instance + + def __repr__(self): + return ('Traceback(filename={!r}, lineno={!r}, function={!r}, ' + 'code_context={!r}, index={!r}, positions={!r})'.format( + self.filename, self.lineno, self.function, self.code_context, + self.index, self.positions)) + +def _get_code_position_from_tb(tb): + code, instruction_index = tb.tb_frame.f_code, tb.tb_lasti + return _get_code_position(code, instruction_index) + +def _get_code_position(code, instruction_index): + if instruction_index < 0: + return (None, None, None, None) + positions_gen = code.co_positions() + # The nth entry in code.co_positions() corresponds to instruction (2*n)th since Python 3.10+ + return next(itertools.islice(positions_gen, instruction_index // 2, None)) def getframeinfo(frame, context=1): """Get information about a frame or traceback object. @@ -1454,10 +1672,20 @@ def getframeinfo(frame, context=1): The optional second argument specifies the number of lines of context to return, which are centered around the current line.""" if istraceback(frame): + positions = _get_code_position_from_tb(frame) lineno = frame.tb_lineno frame = frame.tb_frame else: lineno = frame.f_lineno + positions = _get_code_position(frame.f_code, frame.f_lasti) + + if positions[0] is None: + frame, *positions = (frame, lineno, *positions[1:]) + else: + frame, *positions = (frame, *positions) + + lineno = positions[0] + if not isframe(frame): raise TypeError('{!r} is not a frame or traceback object'.format(frame)) @@ -1475,14 +1703,26 @@ def getframeinfo(frame, context=1): else: lines = index = None - return Traceback(filename, lineno, frame.f_code.co_name, lines, index) + return Traceback(filename, lineno, frame.f_code.co_name, lines, + index, positions=dis.Positions(*positions)) def getlineno(frame): """Get the line number from a frame object, allowing for optimization.""" # FrameType.f_lineno is now a descriptor that grovels co_lnotab return frame.f_lineno -FrameInfo = namedtuple('FrameInfo', ('frame',) + Traceback._fields) +_FrameInfo = namedtuple('_FrameInfo', ('frame',) + Traceback._fields) +class FrameInfo(_FrameInfo): + def __new__(cls, frame, filename, lineno, function, code_context, index, *, positions=None): + instance = super().__new__(cls, frame, filename, lineno, function, code_context, index) + instance.positions = positions + return instance + + def __repr__(self): + return ('FrameInfo(frame={!r}, filename={!r}, lineno={!r}, function={!r}, ' + 'code_context={!r}, index={!r}, positions={!r})'.format( + self.frame, self.filename, self.lineno, self.function, + self.code_context, self.index, self.positions)) def getouterframes(frame, context=1): """Get a list of records for a frame and all higher (calling) frames. @@ -1491,8 +1731,9 @@ def getouterframes(frame, context=1): name, a list of lines of context, and index within the context.""" framelist = [] while frame: - frameinfo = (frame,) + getframeinfo(frame, context) - framelist.append(FrameInfo(*frameinfo)) + traceback_info = getframeinfo(frame, context) + frameinfo = (frame,) + traceback_info + framelist.append(FrameInfo(*frameinfo, positions=traceback_info.positions)) frame = frame.f_back return framelist @@ -1503,8 +1744,9 @@ def getinnerframes(tb, context=1): name, a list of lines of context, and index within the context.""" framelist = [] while tb: - frameinfo = (tb.tb_frame,) + getframeinfo(tb, context) - framelist.append(FrameInfo(*frameinfo)) + traceback_info = getframeinfo(tb, context) + frameinfo = (tb.tb_frame,) + traceback_info + framelist.append(FrameInfo(*frameinfo, positions=traceback_info.positions)) tb = tb.tb_next return framelist @@ -1518,15 +1760,17 @@ def stack(context=1): def trace(context=1): """Return a list of records for the stack below the current exception.""" - return getinnerframes(sys.exc_info()[2], context) + exc = sys.exception() + tb = None if exc is None else exc.__traceback__ + return getinnerframes(tb, context) # ------------------------------------------------ static version of getattr _sentinel = object() +_static_getmro = type.__dict__['__mro__'].__get__ +_get_dunder_dict_of_class = type.__dict__["__dict__"].__get__ -def _static_getmro(klass): - return type.__dict__['__mro__'].__get__(klass) def _check_instance(obj, attr): instance_dict = {} @@ -1539,34 +1783,43 @@ def _check_instance(obj, attr): def _check_class(klass, attr): for entry in _static_getmro(klass): - if _shadowed_dict(type(entry)) is _sentinel: - try: - return entry.__dict__[attr] - except KeyError: - pass + if _shadowed_dict(type(entry)) is _sentinel and attr in entry.__dict__: + return entry.__dict__[attr] return _sentinel -def _is_type(obj): - try: - _static_getmro(obj) - except TypeError: - return False - return True -def _shadowed_dict(klass): - dict_attr = type.__dict__["__dict__"] - for entry in _static_getmro(klass): - try: - class_dict = dict_attr.__get__(entry)["__dict__"] - except KeyError: - pass - else: +@functools.lru_cache() +def _shadowed_dict_from_weakref_mro_tuple(*weakref_mro): + for weakref_entry in weakref_mro: + # Normally we'd have to check whether the result of weakref_entry() + # is None here, in case the object the weakref is pointing to has died. + # In this specific case, however, we know that the only caller of this + # function is `_shadowed_dict()`, and that therefore this weakref is + # guaranteed to point to an object that is still alive. + entry = weakref_entry() + dunder_dict = _get_dunder_dict_of_class(entry) + if '__dict__' in dunder_dict: + class_dict = dunder_dict['__dict__'] if not (type(class_dict) is types.GetSetDescriptorType and class_dict.__name__ == "__dict__" and class_dict.__objclass__ is entry): return class_dict return _sentinel + +def _shadowed_dict(klass): + # gh-118013: the inner function here is decorated with lru_cache for + # performance reasons, *but* make sure not to pass strong references + # to the items in the mro. Doing so can lead to unexpected memory + # consumption in cases where classes are dynamically created and + # destroyed, and the dynamically created classes happen to be the only + # objects that hold strong references to other objects that take up a + # significant amount of memory. + return _shadowed_dict_from_weakref_mro_tuple( + *[make_weakref(entry) for entry in _static_getmro(klass)] + ) + + def getattr_static(obj, attr, default=_sentinel): """Retrieve attributes without triggering dynamic lookup via the descriptor protocol, __getattr__ or __getattribute__. @@ -1579,8 +1832,10 @@ def getattr_static(obj, attr, default=_sentinel): documentation for details. """ instance_result = _sentinel - if not _is_type(obj): - klass = type(obj) + + objtype = type(obj) + if type not in _static_getmro(objtype): + klass = objtype dict_attr = _shadowed_dict(klass) if (dict_attr is _sentinel or type(dict_attr) is types.MemberDescriptorType): @@ -1591,8 +1846,10 @@ def getattr_static(obj, attr, default=_sentinel): klass_result = _check_class(klass, attr) if instance_result is not _sentinel and klass_result is not _sentinel: - if (_check_class(type(klass_result), '__get__') is not _sentinel and - _check_class(type(klass_result), '__set__') is not _sentinel): + if _check_class(type(klass_result), "__get__") is not _sentinel and ( + _check_class(type(klass_result), "__set__") is not _sentinel + or _check_class(type(klass_result), "__delete__") is not _sentinel + ): return klass_result if instance_result is not _sentinel: @@ -1603,11 +1860,11 @@ def getattr_static(obj, attr, default=_sentinel): if obj is klass: # for types we check the metaclass too for entry in _static_getmro(type(klass)): - if _shadowed_dict(type(entry)) is _sentinel: - try: - return entry.__dict__[attr] - except KeyError: - pass + if ( + _shadowed_dict(type(entry)) is _sentinel + and attr in entry.__dict__ + ): + return entry.__dict__[attr] if default is not _sentinel: return default raise AttributeError(attr) @@ -1631,11 +1888,11 @@ def getgeneratorstate(generator): """ if generator.gi_running: return GEN_RUNNING + if generator.gi_suspended: + return GEN_SUSPENDED if generator.gi_frame is None: return GEN_CLOSED - if generator.gi_frame.f_lasti == -1: - return GEN_CREATED - return GEN_SUSPENDED + return GEN_CREATED def getgeneratorlocals(generator): @@ -1673,11 +1930,11 @@ def getcoroutinestate(coroutine): """ if coroutine.cr_running: return CORO_RUNNING + if coroutine.cr_suspended: + return CORO_SUSPENDED if coroutine.cr_frame is None: return CORO_CLOSED - if coroutine.cr_frame.f_lasti == -1: - return CORO_CREATED - return CORO_SUSPENDED + return CORO_CREATED def getcoroutinelocals(coroutine): @@ -1693,35 +1950,89 @@ def getcoroutinelocals(coroutine): return {} +# ----------------------------------- asynchronous generator introspection + +AGEN_CREATED = 'AGEN_CREATED' +AGEN_RUNNING = 'AGEN_RUNNING' +AGEN_SUSPENDED = 'AGEN_SUSPENDED' +AGEN_CLOSED = 'AGEN_CLOSED' + + +def getasyncgenstate(agen): + """Get current state of an asynchronous generator object. + + Possible states are: + AGEN_CREATED: Waiting to start execution. + AGEN_RUNNING: Currently being executed by the interpreter. + AGEN_SUSPENDED: Currently suspended at a yield expression. + AGEN_CLOSED: Execution has completed. + """ + if agen.ag_running: + return AGEN_RUNNING + if agen.ag_suspended: + return AGEN_SUSPENDED + if agen.ag_frame is None: + return AGEN_CLOSED + return AGEN_CREATED + + +def getasyncgenlocals(agen): + """ + Get the mapping of asynchronous generator local variables to their current + values. + + A dict is returned, with the keys the local variable names and values the + bound values.""" + + if not isasyncgen(agen): + raise TypeError(f"{agen!r} is not a Python async generator") + + frame = getattr(agen, "ag_frame", None) + if frame is not None: + return agen.ag_frame.f_locals + else: + return {} + + ############################################################################### ### Function Signature Object (PEP 362) ############################################################################### -_WrapperDescriptor = type(type.__call__) -_MethodWrapper = type(all.__call__) -_ClassMethodWrapper = type(int.__dict__['from_bytes']) - -_NonUserDefinedCallables = (_WrapperDescriptor, - _MethodWrapper, - _ClassMethodWrapper, +_NonUserDefinedCallables = (types.WrapperDescriptorType, + types.MethodWrapperType, + types.ClassMethodDescriptorType, types.BuiltinFunctionType) -def _signature_get_user_defined_method(cls, method_name): +def _signature_get_user_defined_method(cls, method_name, *, follow_wrapper_chains=True): """Private helper. Checks if ``cls`` has an attribute named ``method_name`` and returns it only if it is a pure python function. """ - try: - meth = getattr(cls, method_name) - except AttributeError: - return + if method_name == '__new__': + meth = getattr(cls, method_name, None) else: - if not isinstance(meth, _NonUserDefinedCallables): - # Once '__signature__' will be added to 'C'-level - # callables, this check won't be necessary - return meth + meth = getattr_static(cls, method_name, None) + if meth is None: + return None + + # NOTE: The meth may wraps a non-user-defined callable. + # In this case, we treat the meth as non-user-defined callable too. + # (e.g. cls.__new__ generated by @warnings.deprecated) + unwrapped_meth = None + if follow_wrapper_chains: + unwrapped_meth = unwrap(meth, stop=(lambda m: hasattr(m, "__signature__") + or _signature_is_builtin(m))) + + if (isinstance(meth, _NonUserDefinedCallables) + or isinstance(unwrapped_meth, _NonUserDefinedCallables)): + # Once '__signature__' will be added to 'C'-level + # callables, this check won't be necessary + return None + if method_name != '__new__': + meth = _descriptor_get(meth, cls) + return meth def _signature_get_partial(wrapped_sig, partial, extra_args=()): @@ -1834,8 +2145,10 @@ def _signature_is_builtin(obj): ismethoddescriptor(obj) or isinstance(obj, _NonUserDefinedCallables) or # Can't test 'isinstance(type)' here, as it would - # also be True for regular python classes - obj in (type, object)) + # also be True for regular python classes. + # Can't use the `in` operator here, as it would + # invoke the custom __eq__ method. + obj is type or obj is object) def _signature_is_functionlike(obj): @@ -1860,30 +2173,7 @@ def _signature_is_functionlike(obj): isinstance(name, str) and (defaults is None or isinstance(defaults, tuple)) and (kwdefaults is None or isinstance(kwdefaults, dict)) and - isinstance(annotations, dict)) - - -def _signature_get_bound_param(spec): - """ Private helper to get first parameter name from a - __text_signature__ of a builtin method, which should - be in the following format: '($param1, ...)'. - Assumptions are that the first argument won't have - a default value or an annotation. - """ - - assert spec.startswith('($') - - pos = spec.find(',') - if pos == -1: - pos = spec.find(')') - - cpos = spec.find(':') - assert cpos == -1 or cpos > pos - - cpos = spec.find('=') - assert cpos == -1 or cpos > pos - - return spec[2:pos] + (isinstance(annotations, (dict)) or annotations is None) ) def _signature_strip_non_python_syntax(signature): @@ -1891,26 +2181,21 @@ def _signature_strip_non_python_syntax(signature): Private helper function. Takes a signature in Argument Clinic's extended signature format. - Returns a tuple of three things: - * that signature re-rendered in standard Python syntax, + Returns a tuple of two things: + * that signature re-rendered in standard Python syntax, and * the index of the "self" parameter (generally 0), or None if - the function does not have a "self" parameter, and - * the index of the last "positional only" parameter, - or None if the signature has no positional-only parameters. + the function does not have a "self" parameter. """ if not signature: - return signature, None, None + return signature, None self_parameter = None - last_positional_only = None - lines = [l.encode('ascii') for l in signature.split('\n')] + lines = [l.encode('ascii') for l in signature.split('\n') if l] generator = iter(lines).__next__ token_stream = tokenize.tokenize(generator) - delayed_comma = False - skip_next_comma = False text = [] add = text.append @@ -1927,49 +2212,27 @@ def _signature_strip_non_python_syntax(signature): if type == OP: if string == ',': - if skip_next_comma: - skip_next_comma = False - else: - assert not delayed_comma - delayed_comma = True - current_parameter += 1 - continue + current_parameter += 1 - if string == '/': - assert not skip_next_comma - assert last_positional_only is None - skip_next_comma = True - last_positional_only = current_parameter - 1 - continue - - if (type == ERRORTOKEN) and (string == '$'): + if (type == OP) and (string == '$'): assert self_parameter is None self_parameter = current_parameter continue - if delayed_comma: - delayed_comma = False - if not ((type == OP) and (string == ')')): - add(', ') add(string) if (string == ','): add(' ') - clean_signature = ''.join(text) - return clean_signature, self_parameter, last_positional_only + clean_signature = ''.join(text).strip().replace("\n", "") + return clean_signature, self_parameter def _signature_fromstr(cls, obj, s, skip_bound_arg=True): """Private helper to parse content of '__text_signature__' and return a Signature based on it. """ - # Lazy import ast because it's relatively heavy and - # it's not used for other than this function. - import ast - Parameter = cls._parameter_cls - clean_signature, self_parameter, last_positional_only = \ - _signature_strip_non_python_syntax(s) + clean_signature, self_parameter = _signature_strip_non_python_syntax(s) program = "def foo" + clean_signature + ": pass" @@ -1985,11 +2248,15 @@ def _signature_fromstr(cls, obj, s, skip_bound_arg=True): parameters = [] empty = Parameter.empty - invalid = object() module = None module_dict = {} + module_name = getattr(obj, '__module__', None) + if not module_name: + objclass = getattr(obj, '__objclass__', None) + module_name = getattr(objclass, '__module__', None) + if module_name: module = sys.modules.get(module_name, None) if module: @@ -2009,11 +2276,11 @@ def wrap_value(s): try: value = eval(s, sys_module_dict) except NameError: - raise RuntimeError() + raise ValueError if isinstance(value, (str, int, float, bytes, bool, type(None))): return ast.Constant(value) - raise RuntimeError() + raise ValueError class RewriteSymbolics(ast.NodeTransformer): def visit_Attribute(self, node): @@ -2023,7 +2290,7 @@ def visit_Attribute(self, node): a.append(n.attr) n = n.value if not isinstance(n, ast.Name): - raise RuntimeError() + raise ValueError a.append(n.id) value = ".".join(reversed(a)) return wrap_value(value) @@ -2033,33 +2300,43 @@ def visit_Name(self, node): raise ValueError() return wrap_value(node.id) + def visit_BinOp(self, node): + # Support constant folding of a couple simple binary operations + # commonly used to define default values in text signatures + left = self.visit(node.left) + right = self.visit(node.right) + if not isinstance(left, ast.Constant) or not isinstance(right, ast.Constant): + raise ValueError + if isinstance(node.op, ast.Add): + return ast.Constant(left.value + right.value) + elif isinstance(node.op, ast.Sub): + return ast.Constant(left.value - right.value) + elif isinstance(node.op, ast.BitOr): + return ast.Constant(left.value | right.value) + raise ValueError + def p(name_node, default_node, default=empty): name = parse_name(name_node) - if name is invalid: - return None if default_node and default_node is not _empty: try: default_node = RewriteSymbolics().visit(default_node) - o = ast.literal_eval(default_node) + default = ast.literal_eval(default_node) except ValueError: - o = invalid - if o is invalid: - return None - default = o if o is not invalid else default + raise ValueError("{!r} builtin has invalid signature".format(obj)) from None parameters.append(Parameter(name, kind, default=default, annotation=empty)) # non-keyword-only parameters - args = reversed(f.args.args) - defaults = reversed(f.args.defaults) - iter = itertools.zip_longest(args, defaults, fillvalue=None) - if last_positional_only is not None: - kind = Parameter.POSITIONAL_ONLY - else: - kind = Parameter.POSITIONAL_OR_KEYWORD - for i, (name, default) in enumerate(reversed(list(iter))): + total_non_kw_args = len(f.args.posonlyargs) + len(f.args.args) + required_non_kw_args = total_non_kw_args - len(f.args.defaults) + defaults = itertools.chain(itertools.repeat(None, required_non_kw_args), f.args.defaults) + + kind = Parameter.POSITIONAL_ONLY + for (name, default) in zip(f.args.posonlyargs, defaults): + p(name, default) + + kind = Parameter.POSITIONAL_OR_KEYWORD + for (name, default) in zip(f.args.args, defaults): p(name, default) - if i == last_positional_only: - kind = Parameter.POSITIONAL_OR_KEYWORD # *args if f.args.vararg: @@ -2112,7 +2389,8 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True): return _signature_fromstr(cls, func, s, skip_bound_arg) -def _signature_from_function(cls, func, skip_bound_arg=True): +def _signature_from_function(cls, func, skip_bound_arg=True, + globals=None, locals=None, eval_str=False): """Private helper: constructs Signature for the given python function.""" is_duck_function = False @@ -2138,7 +2416,7 @@ def _signature_from_function(cls, func, skip_bound_arg=True): positional = arg_names[:pos_count] keyword_only_count = func_code.co_kwonlyargcount keyword_only = arg_names[pos_count:pos_count + keyword_only_count] - annotations = func.__annotations__ + annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str) defaults = func.__defaults__ kwdefaults = func.__kwdefaults__ @@ -2206,26 +2484,42 @@ def _signature_from_function(cls, func, skip_bound_arg=True): __validate_parameters__=is_duck_function) +def _descriptor_get(descriptor, obj): + if isclass(descriptor): + return descriptor + get = getattr(type(descriptor), '__get__', _sentinel) + if get is _sentinel: + return descriptor + return get(descriptor, obj, type(obj)) + + def _signature_from_callable(obj, *, follow_wrapper_chains=True, skip_bound_arg=True, + globals=None, + locals=None, + eval_str=False, sigcls): """Private helper function to get signature for arbitrary callable objects. """ + _get_signature_of = functools.partial(_signature_from_callable, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + globals=globals, + locals=locals, + sigcls=sigcls, + eval_str=eval_str) + if not callable(obj): raise TypeError('{!r} is not a callable object'.format(obj)) if isinstance(obj, types.MethodType): # In this case we skip the first parameter of the underlying # function (usually `self` or `cls`). - sig = _signature_from_callable( - obj.__func__, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + sig = _get_signature_of(obj.__func__) if skip_bound_arg: return _signature_bound_method(sig) @@ -2234,16 +2528,15 @@ def _signature_from_callable(obj, *, # Was this function wrapped by a decorator? if follow_wrapper_chains: - obj = unwrap(obj, stop=(lambda f: hasattr(f, "__signature__"))) + # Unwrap until we find an explicit signature or a MethodType (which will be + # handled explicitly below). + obj = unwrap(obj, stop=(lambda f: hasattr(f, "__signature__") + or isinstance(f, types.MethodType))) if isinstance(obj, types.MethodType): # If the unwrapped object is a *method*, we might want to # skip its first parameter (self). # See test_signature_wrapped_bound_method for details. - return _signature_from_callable( - obj, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + return _get_signature_of(obj) try: sig = obj.__signature__ @@ -2251,14 +2544,22 @@ def _signature_from_callable(obj, *, pass else: if sig is not None: + # since __text_signature__ is not writable on classes, __signature__ + # may contain text (or be a callable that returns text); + # if so, convert it + o_sig = sig + if not isinstance(sig, (Signature, str)) and callable(sig): + sig = sig() + if isinstance(sig, str): + sig = _signature_fromstr(sigcls, obj, sig) if not isinstance(sig, Signature): raise TypeError( 'unexpected object {!r} in __signature__ ' - 'attribute'.format(sig)) + 'attribute'.format(o_sig)) return sig try: - partialmethod = obj._partialmethod + partialmethod = obj.__partialmethod__ except AttributeError: pass else: @@ -2270,11 +2571,7 @@ def _signature_from_callable(obj, *, # (usually `self`, or `cls`) will not be passed # automatically (as for boundmethods) - wrapped_sig = _signature_from_callable( - partialmethod.func, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + wrapped_sig = _get_signature_of(partialmethod.func) sig = _signature_get_partial(wrapped_sig, partialmethod, (None,)) first_wrapped_param = tuple(wrapped_sig.parameters.values())[0] @@ -2289,121 +2586,112 @@ def _signature_from_callable(obj, *, new_params = (first_wrapped_param,) + sig_params return sig.replace(parameters=new_params) + if isinstance(obj, functools.partial): + wrapped_sig = _get_signature_of(obj.func) + return _signature_get_partial(wrapped_sig, obj) + if isfunction(obj) or _signature_is_functionlike(obj): # If it's a pure Python function, or an object that is duck type # of a Python function (Cython functions, for instance), then: return _signature_from_function(sigcls, obj, - skip_bound_arg=skip_bound_arg) + skip_bound_arg=skip_bound_arg, + globals=globals, locals=locals, eval_str=eval_str) if _signature_is_builtin(obj): return _signature_from_builtin(sigcls, obj, skip_bound_arg=skip_bound_arg) - if isinstance(obj, functools.partial): - wrapped_sig = _signature_from_callable( - obj.func, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) - return _signature_get_partial(wrapped_sig, obj) - - sig = None if isinstance(obj, type): # obj is a class or a metaclass # First, let's see if it has an overloaded __call__ defined # in its metaclass - call = _signature_get_user_defined_method(type(obj), '__call__') + call = _signature_get_user_defined_method( + type(obj), + '__call__', + follow_wrapper_chains=follow_wrapper_chains, + ) if call is not None: - sig = _signature_from_callable( - call, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) - else: - # Now we check if the 'obj' class has a '__new__' method - new = _signature_get_user_defined_method(obj, '__new__') - if new is not None: - sig = _signature_from_callable( - new, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + return _get_signature_of(call) + + # NOTE: The user-defined method can be a function with a thin wrapper + # around object.__new__ (e.g., generated by `@warnings.deprecated`) + new = _signature_get_user_defined_method( + obj, + '__new__', + follow_wrapper_chains=follow_wrapper_chains, + ) + init = _signature_get_user_defined_method( + obj, + '__init__', + follow_wrapper_chains=follow_wrapper_chains, + ) + + # Go through the MRO and see if any class has user-defined + # pure Python __new__ or __init__ method + for base in obj.__mro__: + # Now we check if the 'obj' class has an own '__new__' method + if new is not None and '__new__' in base.__dict__: + sig = _get_signature_of(new) + if skip_bound_arg: + sig = _signature_bound_method(sig) + return sig + # or an own '__init__' method + elif init is not None and '__init__' in base.__dict__: + return _get_signature_of(init) + + # At this point we know, that `obj` is a class, with no user- + # defined '__init__', '__new__', or class-level '__call__' + + for base in obj.__mro__[:-1]: + # Since '__text_signature__' is implemented as a + # descriptor that extracts text signature from the + # class docstring, if 'obj' is derived from a builtin + # class, its own '__text_signature__' may be 'None'. + # Therefore, we go through the MRO (except the last + # class in there, which is 'object') to find the first + # class with non-empty text signature. + try: + text_sig = base.__text_signature__ + except AttributeError: + pass else: - # Finally, we should have at least __init__ implemented - init = _signature_get_user_defined_method(obj, '__init__') - if init is not None: - sig = _signature_from_callable( - init, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) - - if sig is None: - # At this point we know, that `obj` is a class, with no user- - # defined '__init__', '__new__', or class-level '__call__' - - for base in obj.__mro__[:-1]: - # Since '__text_signature__' is implemented as a - # descriptor that extracts text signature from the - # class docstring, if 'obj' is derived from a builtin - # class, its own '__text_signature__' may be 'None'. - # Therefore, we go through the MRO (except the last - # class in there, which is 'object') to find the first - # class with non-empty text signature. - try: - text_sig = base.__text_signature__ - except AttributeError: - pass - else: - if text_sig: - # If 'obj' class has a __text_signature__ attribute: - # return a signature based on it - return _signature_fromstr(sigcls, obj, text_sig) - - # No '__text_signature__' was found for the 'obj' class. - # Last option is to check if its '__init__' is - # object.__init__ or type.__init__. - if type not in obj.__mro__: - # We have a class (not metaclass), but no user-defined - # __init__ or __new__ for it - if (obj.__init__ is object.__init__ and - obj.__new__ is object.__new__): - # Return a signature of 'object' builtin. - return sigcls.from_callable(object) - else: - raise ValueError( - 'no signature found for builtin type {!r}'.format(obj)) + if text_sig: + # If 'base' class has a __text_signature__ attribute: + # return a signature based on it + return _signature_fromstr(sigcls, base, text_sig) + + # No '__text_signature__' was found for the 'obj' class. + # Last option is to check if its '__init__' is + # object.__init__ or type.__init__. + if type not in obj.__mro__: + obj_init = obj.__init__ + obj_new = obj.__new__ + if follow_wrapper_chains: + obj_init = unwrap(obj_init) + obj_new = unwrap(obj_new) + # We have a class (not metaclass), but no user-defined + # __init__ or __new__ for it + if obj_init is object.__init__ and obj_new is object.__new__: + # Return a signature of 'object' builtin. + return sigcls.from_callable(object) + else: + raise ValueError( + 'no signature found for builtin type {!r}'.format(obj)) - elif not isinstance(obj, _NonUserDefinedCallables): + else: # An object with __call__ - # We also check that the 'obj' is not an instance of - # _WrapperDescriptor or _MethodWrapper to avoid - # infinite recursion (and even potential segfault) - call = _signature_get_user_defined_method(type(obj), '__call__') + call = getattr_static(type(obj), '__call__', None) if call is not None: try: - sig = _signature_from_callable( - call, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) - except ValueError as ex: - msg = 'no signature found for {!r}'.format(obj) - raise ValueError(msg) from ex - - if sig is not None: - # For classes and objects we skip the first parameter of their - # __call__, __new__, or __init__ methods - if skip_bound_arg: - return _signature_bound_method(sig) - else: - return sig - - if isinstance(obj, types.BuiltinFunctionType): - # Raise a nicer error message for builtins - msg = 'no signature found for builtin function {!r}'.format(obj) - raise ValueError(msg) + text_sig = obj.__text_signature__ + except AttributeError: + pass + else: + if text_sig: + return _signature_fromstr(sigcls, obj, text_sig) + call = _descriptor_get(call, obj) + return _get_signature_of(call) raise ValueError('callable {!r} is not supported by signature'.format(obj)) @@ -2417,18 +2705,21 @@ class _empty: class _ParameterKind(enum.IntEnum): - POSITIONAL_ONLY = 0 - POSITIONAL_OR_KEYWORD = 1 - VAR_POSITIONAL = 2 - KEYWORD_ONLY = 3 - VAR_KEYWORD = 4 + POSITIONAL_ONLY = 'positional-only' + POSITIONAL_OR_KEYWORD = 'positional or keyword' + VAR_POSITIONAL = 'variadic positional' + KEYWORD_ONLY = 'keyword-only' + VAR_KEYWORD = 'variadic keyword' + + def __new__(cls, description): + value = len(cls.__members__) + member = int.__new__(cls, value) + member._value_ = value + member.description = description + return member def __str__(self): - return self._name_ - - @property - def description(self): - return _PARAM_NAME_MAPPING[self] + return self.name _POSITIONAL_ONLY = _ParameterKind.POSITIONAL_ONLY _POSITIONAL_OR_KEYWORD = _ParameterKind.POSITIONAL_OR_KEYWORD @@ -2436,14 +2727,6 @@ def description(self): _KEYWORD_ONLY = _ParameterKind.KEYWORD_ONLY _VAR_KEYWORD = _ParameterKind.VAR_KEYWORD -_PARAM_NAME_MAPPING = { - _POSITIONAL_ONLY: 'positional-only', - _POSITIONAL_OR_KEYWORD: 'positional or keyword', - _VAR_POSITIONAL: 'variadic positional', - _KEYWORD_ONLY: 'keyword-only', - _VAR_KEYWORD: 'variadic keyword' -} - class Parameter: """Represents a parameter in a function signature. @@ -2512,7 +2795,10 @@ def __init__(self, name, kind, *, default=_empty, annotation=_empty): self._kind = _POSITIONAL_ONLY name = 'implicit{}'.format(name[1:]) - if not name.isidentifier(): + # It's possible for C functions to have a positional-only parameter + # where the name is a keyword, so for compatibility we'll allow it. + is_keyword = iskeyword(name) and self._kind is not _POSITIONAL_ONLY + if is_keyword or not name.isidentifier(): raise ValueError('{!r} is not a valid parameter name'.format(name)) self._name = name @@ -2583,11 +2869,13 @@ def __str__(self): return formatted + __replace__ = replace + def __repr__(self): return '<{} "{}">'.format(self.__class__.__name__, self) def __hash__(self): - return hash((self.name, self.kind, self.annotation, self.default)) + return hash((self._name, self._kind, self._annotation, self._default)) def __eq__(self, other): if self is other: @@ -2606,7 +2894,7 @@ class BoundArguments: Has the following public attributes: - * arguments : OrderedDict + * arguments : dict An ordered mutable mapping of parameters' names to arguments' values. Does not contain arguments' default values. * signature : Signature @@ -2706,7 +2994,7 @@ def apply_defaults(self): # Signature.bind_partial(). continue new_arguments.append((name, val)) - self.arguments = OrderedDict(new_arguments) + self.arguments = dict(new_arguments) def __eq__(self, other): if self is other: @@ -2772,9 +3060,9 @@ def __init__(self, parameters=None, *, return_annotation=_empty, if __validate_parameters__: params = OrderedDict() top_kind = _POSITIONAL_ONLY - kind_defaults = False + seen_default = False - for idx, param in enumerate(parameters): + for param in parameters: kind = param.kind name = param.name @@ -2787,21 +3075,19 @@ def __init__(self, parameters=None, *, return_annotation=_empty, kind.description) raise ValueError(msg) elif kind > top_kind: - kind_defaults = False top_kind = kind if kind in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD): if param.default is _empty: - if kind_defaults: + if seen_default: # No default for this parameter, but the - # previous parameter of the same kind had - # a default + # previous parameter of had a default msg = 'non-default argument follows default ' \ 'argument' raise ValueError(msg) else: # There is a default for this parameter. - kind_defaults = True + seen_default = True if name in params: msg = 'duplicate parameter name: {!r}'.format(name) @@ -2809,41 +3095,18 @@ def __init__(self, parameters=None, *, return_annotation=_empty, params[name] = param else: - params = OrderedDict(((param.name, param) - for param in parameters)) + params = OrderedDict((param.name, param) for param in parameters) self._parameters = types.MappingProxyType(params) self._return_annotation = return_annotation @classmethod - def from_function(cls, func): - """Constructs Signature for the given python function. - - Deprecated since Python 3.5, use `Signature.from_callable()`. - """ - - warnings.warn("inspect.Signature.from_function() is deprecated since " - "Python 3.5, use Signature.from_callable()", - DeprecationWarning, stacklevel=2) - return _signature_from_function(cls, func) - - @classmethod - def from_builtin(cls, func): - """Constructs Signature for the given builtin function. - - Deprecated since Python 3.5, use `Signature.from_callable()`. - """ - - warnings.warn("inspect.Signature.from_builtin() is deprecated since " - "Python 3.5, use Signature.from_callable()", - DeprecationWarning, stacklevel=2) - return _signature_from_builtin(cls, func) - - @classmethod - def from_callable(cls, obj, *, follow_wrapped=True): + def from_callable(cls, obj, *, + follow_wrapped=True, globals=None, locals=None, eval_str=False): """Constructs Signature for the given callable object.""" return _signature_from_callable(obj, sigcls=cls, - follow_wrapper_chains=follow_wrapped) + follow_wrapper_chains=follow_wrapped, + globals=globals, locals=locals, eval_str=eval_str) @property def parameters(self): @@ -2868,6 +3131,8 @@ def replace(self, *, parameters=_void, return_annotation=_void): return type(self)(parameters, return_annotation=return_annotation) + __replace__ = replace + def _hash_basis(self): params = tuple(param for param in self.parameters.values() if param.kind != _KEYWORD_ONLY) @@ -2892,12 +3157,14 @@ def __eq__(self, other): def _bind(self, args, kwargs, *, partial=False): """Private method. Don't use directly.""" - arguments = OrderedDict() + arguments = {} parameters = iter(self.parameters.values()) parameters_ex = () arg_vals = iter(args) + pos_only_param_in_kwargs = [] + while True: # Let's iterate through the positional arguments and corresponding # parameters @@ -2918,10 +3185,13 @@ def _bind(self, args, kwargs, *, partial=False): break elif param.name in kwargs: if param.kind == _POSITIONAL_ONLY: - msg = '{arg!r} parameter is positional only, ' \ - 'but was passed as a keyword' - msg = msg.format(arg=param.name) - raise TypeError(msg) from None + if param.default is _empty: + msg = f'missing a required positional-only argument: {param.name!r}' + raise TypeError(msg) + # Raise a TypeError once we are sure there is no + # **kwargs param later. + pos_only_param_in_kwargs.append(param) + continue parameters_ex = (param,) break elif (param.kind == _VAR_KEYWORD or @@ -2938,8 +3208,12 @@ def _bind(self, args, kwargs, *, partial=False): parameters_ex = (param,) break else: - msg = 'missing a required argument: {arg!r}' - msg = msg.format(arg=param.name) + if param.kind == _KEYWORD_ONLY: + argtype = ' keyword-only' + else: + argtype = '' + msg = 'missing a required{argtype} argument: {arg!r}' + msg = msg.format(arg=param.name, argtype=argtype) raise TypeError(msg) from None else: # We have a positional argument to process @@ -2999,20 +3273,22 @@ def _bind(self, args, kwargs, *, partial=False): format(arg=param_name)) from None else: - if param.kind == _POSITIONAL_ONLY: - # This should never happen in case of a properly built - # Signature object (but let's have this check here - # to ensure correct behaviour just in case) - raise TypeError('{arg!r} parameter is positional only, ' - 'but was passed as a keyword'. \ - format(arg=param.name)) - arguments[param_name] = arg_val if kwargs: if kwargs_param is not None: # Process our '**kwargs'-like parameter arguments[kwargs_param.name] = kwargs + elif pos_only_param_in_kwargs: + raise TypeError( + 'got some positional-only arguments passed as ' + 'keyword arguments: {arg!r}'.format( + arg=', '.join( + param.name + for param in pos_only_param_in_kwargs + ), + ), + ) else: raise TypeError( 'got an unexpected keyword argument {arg!r}'.format( @@ -3046,6 +3322,16 @@ def __repr__(self): return '<{} {}>'.format(self.__class__.__name__, self) def __str__(self): + return self.format() + + def format(self, *, max_width=None): + """Create a string representation of the Signature object. + + If *max_width* integer is passed, + signature will try to fit into the *max_width*. + If signature is longer than *max_width*, + all parameters will be on separate lines. + """ result = [] render_pos_only_separator = False render_kw_only_separator = True @@ -3083,6 +3369,8 @@ def __str__(self): result.append('/') rendered = '({})'.format(', '.join(result)) + if max_width is not None and len(rendered) > max_width: + rendered = '(\n {}\n)'.format(',\n '.join(result)) if self.return_annotation is not _empty: anno = formatannotation(self.return_annotation) @@ -3091,9 +3379,32 @@ def __str__(self): return rendered -def signature(obj, *, follow_wrapped=True): +def signature(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False): """Get a signature object for the passed callable.""" - return Signature.from_callable(obj, follow_wrapped=follow_wrapped) + return Signature.from_callable(obj, follow_wrapped=follow_wrapped, + globals=globals, locals=locals, eval_str=eval_str) + + +class BufferFlags(enum.IntFlag): + SIMPLE = 0x0 + WRITABLE = 0x1 + FORMAT = 0x4 + ND = 0x8 + STRIDES = 0x10 | ND + C_CONTIGUOUS = 0x20 | STRIDES + F_CONTIGUOUS = 0x40 | STRIDES + ANY_CONTIGUOUS = 0x80 | STRIDES + INDIRECT = 0x100 | STRIDES + CONTIG = ND | WRITABLE + CONTIG_RO = ND + STRIDED = STRIDES | WRITABLE + STRIDED_RO = STRIDES + RECORDS = STRIDES | WRITABLE | FORMAT + RECORDS_RO = STRIDES | FORMAT + FULL = INDIRECT | WRITABLE | FORMAT + FULL_RO = INDIRECT | FORMAT + READ = 0x100 + WRITE = 0x200 def _main(): diff --git a/Lib/test/test_inspect/__init__.py b/Lib/test/test_inspect/__init__.py new file mode 100644 index 00000000000..f2a39a3fe29 --- /dev/null +++ b/Lib/test/test_inspect/__init__.py @@ -0,0 +1,6 @@ +import os +from test import support + + +def load_tests(*args): + return support.load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_inspect/inspect_fodder.py b/Lib/test/test_inspect/inspect_fodder.py new file mode 100644 index 00000000000..febd54c86fe --- /dev/null +++ b/Lib/test/test_inspect/inspect_fodder.py @@ -0,0 +1,120 @@ +# line 1 +'A module docstring.' + +import inspect +# line 5 + +# line 7 +def spam(a, /, b, c, d=3, e=4, f=5, *g, **h): + eggs(b + d, c + f) + +# line 11 +def eggs(x, y): + "A docstring." + global fr, st + fr = inspect.currentframe() + st = inspect.stack() + p = x + q = y / 0 + +# line 20 +class StupidGit: + """A longer, + + indented + + docstring.""" +# line 27 + + def abuse(self, a, b, c): + """Another + +\tdocstring + + containing + +\ttabs +\t + """ + self.argue(a, b, c) +# line 40 + def argue(self, a, b, c): + try: + spam(a, b, c) + except BaseException as e: + self.ex = e + self.tr = inspect.trace() + + @property + def contradiction(self): + 'The automatic gainsaying.' + pass + +# line 53 +class MalodorousPervert(StupidGit): + def abuse(self, a, b, c): + pass + + @property + def contradiction(self): + pass + +Tit = MalodorousPervert + +class ParrotDroppings: + pass + +class FesteringGob(MalodorousPervert, ParrotDroppings): + def abuse(self, a, b, c): + pass + + def _getter(self): + pass + contradiction = property(_getter) + +async def lobbest(grenade): + pass + +currentframe = inspect.currentframe() +try: + raise Exception() +except BaseException as e: + tb = e.__traceback__ + +class Callable: + def __call__(self, *args): + return args + + def as_method_of(self, obj): + from types import MethodType + return MethodType(self, obj) + +custom_method = Callable().as_method_of(42) +del Callable + +# line 95 +class WhichComments: + # line 97 + # before f + def f(self): + # line 100 + # start f + return 1 + # line 103 + # end f + # line 105 + # after f + + # before asyncf - line 108 + async def asyncf(self): + # start asyncf + return 2 + # end asyncf + # after asyncf - line 113 + # end of WhichComments - line 114 + # after WhichComments - line 115 + +# Test that getsource works on a line that includes +# a closing parenthesis with the opening paren being in another line +( +); after_closing = lambda: 1 diff --git a/Lib/test/test_inspect/inspect_fodder2.py b/Lib/test/test_inspect/inspect_fodder2.py new file mode 100644 index 00000000000..157e12167b5 --- /dev/null +++ b/Lib/test/test_inspect/inspect_fodder2.py @@ -0,0 +1,403 @@ +# line 1 +def wrap(foo=None): + def wrapper(func): + return func + return wrapper + +# line 7 +def replace(func): + def insteadfunc(): + print('hello') + return insteadfunc + +# line 13 +@wrap() +@wrap(wrap) +def wrapped(): + pass + +# line 19 +@replace +def gone(): + pass + +# line 24 +oll = lambda m: m + +# line 27 +tll = lambda g: g and \ +g and \ +g + +# line 32 +tlli = lambda d: d and \ + d + +# line 36 +def onelinefunc(): pass + +# line 39 +def manyargs(arg1, arg2, +arg3, arg4): pass + +# line 43 +def twolinefunc(m): return m and \ +m + +# line 47 +a = [None, + lambda x: x, + None] + +# line 52 +def setfunc(func): + globals()["anonymous"] = func +setfunc(lambda x, y: x*y) + +# line 57 +def with_comment(): # hello + world + +# line 61 +multiline_sig = [ + lambda x, \ + y: x+y, + None, + ] + +# line 68 +def func69(): + class cls70: + def func71(): + pass + return cls70 +extra74 = 74 + +# line 76 +def func77(): pass +(extra78, stuff78) = 'xy' +extra79 = 'stop' + +# line 81 +class cls82: + def func83(): pass +(extra84, stuff84) = 'xy' +extra85 = 'stop' + +# line 87 +def func88(): + # comment + return 90 + +# line 92 +def f(): + class X: + def g(): + "doc" + return 42 + return X +method_in_dynamic_class = f().g + +#line 101 +def keyworded(*arg1, arg2=1): + pass + +#line 105 +def annotated(arg1: list): + pass + +#line 109 +def keyword_only_arg(*, arg): + pass + +@wrap(lambda: None) +def func114(): + return 115 + +class ClassWithMethod: + def method(self): + pass + +from functools import wraps + +def decorator(func): + @wraps(func) + def fake(): + return 42 + return fake + +#line 129 +@decorator +def real(): + return 20 + +#line 134 +class cls135: + def func136(): + def func137(): + never_reached1 + never_reached2 + +# line 141 +class cls142: + a = """ +class cls149: + ... +""" + +# line 148 +class cls149: + + def func151(self): + pass + +''' +class cls160: + pass +''' + +# line 159 +class cls160: + + def func162(self): + pass + +# line 165 +class cls166: + a = ''' + class cls175: + ... + ''' + +# line 172 +class cls173: + + class cls175: + pass + +# line 178 +class cls179: + pass + +# line 182 +class cls183: + + class cls185: + + def func186(self): + pass + +def class_decorator(cls): + return cls + +# line 193 +@class_decorator +@class_decorator +class cls196: + + @class_decorator + @class_decorator + class cls200: + pass + +class cls203: + class cls204: + class cls205: + pass + class cls207: + class cls205: + pass + +# line 211 +def func212(): + class cls213: + pass + return cls213 + +# line 217 +class cls213: + def func219(self): + class cls220: + pass + return cls220 + +# line 224 +async def func225(): + class cls226: + pass + return cls226 + +# line 230 +class cls226: + async def func232(self): + class cls233: + pass + return cls233 + +if True: + class cls238: + class cls239: + '''if clause cls239''' +else: + class cls238: + class cls239: + '''else clause 239''' + pass + +#line 247 +def positional_only_arg(a, /): + pass + +#line 251 +def all_markers(a, b, /, c, d, *, e, f): + pass + +# line 255 +def all_markers_with_args_and_kwargs(a, b, /, c, d, *args, e, f, **kwargs): + pass + +#line 259 +def all_markers_with_defaults(a, b=1, /, c=2, d=3, *, e=4, f=5): + pass + +# line 263 +def deco_factory(**kwargs): + def deco(f): + @wraps(f) + def wrapper(*a, **kwd): + kwd.update(kwargs) + return f(*a, **kwd) + return wrapper + return deco + +@deco_factory(foo=(1 + 2), bar=lambda: 1) +def complex_decorated(foo=0, bar=lambda: 0): + return foo + bar() + +# line 276 +parenthesized_lambda = ( + lambda: ()) +parenthesized_lambda2 = [ + lambda: ()][0] +parenthesized_lambda3 = {0: + lambda: ()}[0] + +# line 285 +post_line_parenthesized_lambda1 = (lambda: () +) + +# line 289 +nested_lambda = ( + lambda right: [].map( + lambda length: ())) + +# line 294 +if True: + class cls296: + def f(): + pass +else: + class cls296: + def g(): + pass + +# line 304 +if False: + class cls310: + def f(): + pass +else: + class cls310: + def g(): + pass + +# line 314 +class ClassWithCodeObject: + import sys + code = sys._getframe(0).f_code + +import enum + +# line 321 +class enum322(enum.Enum): + A = 'a' + +# line 325 +class enum326(enum.IntEnum): + A = 1 + +# line 329 +class flag330(enum.Flag): + A = 1 + +# line 333 +class flag334(enum.IntFlag): + A = 1 + +# line 337 +simple_enum338 = enum.Enum('simple_enum338', 'A') +simple_enum339 = enum.IntEnum('simple_enum339', 'A') +simple_flag340 = enum.Flag('simple_flag340', 'A') +simple_flag341 = enum.IntFlag('simple_flag341', 'A') + +import typing + +# line 345 +class nt346(typing.NamedTuple): + x: int + y: int + +# line 350 +nt351 = typing.NamedTuple('nt351', (('x', int), ('y', int))) + +# line 353 +class td354(typing.TypedDict): + x: int + y: int + +# line 358 +td359 = typing.TypedDict('td359', (('x', int), ('y', int))) + +import dataclasses + +# line 363 +@dataclasses.dataclass +class dc364: + x: int + y: int + +# line 369 +dc370 = dataclasses.make_dataclass('dc370', (('x', int), ('y', int))) +dc371 = dataclasses.make_dataclass('dc370', (('x', int), ('y', int)), module=__name__) + +import inspect +import itertools + +# line 376 +ge377 = ( + inspect.currentframe() + for i in itertools.count() +) + +# line 382 +def func383(): + # line 384 + ge385 = ( + inspect.currentframe() + for i in itertools.count() + ) + return ge385 + +# line 391 +@decorator +# comment +def func394(): + return 395 + +# line 397 +@decorator + +def func400(): + return 401 + +pass # end of file diff --git a/Lib/test/test_inspect/inspect_stock_annotations.py b/Lib/test/test_inspect/inspect_stock_annotations.py new file mode 100644 index 00000000000..d115a25b650 --- /dev/null +++ b/Lib/test/test_inspect/inspect_stock_annotations.py @@ -0,0 +1,28 @@ +a:int=3 +b:str="foo" + +class MyClass: + a:int=4 + b:str="bar" + def __init__(self, a, b): + self.a = a + self.b = b + def __eq__(self, other): + return isinstance(other, MyClass) and self.a == other.a and self.b == other.b + +def function(a:int, b:str) -> MyClass: + return MyClass(a, b) + + +def function2(a:int, b:"str", c:MyClass) -> MyClass: + pass + + +def function3(a:"int", b:"str", c:"MyClass"): + pass + + +class UnannotatedClass: + pass + +def unannotated_function(a, b, c): pass diff --git a/Lib/test/test_inspect/inspect_stringized_annotations.py b/Lib/test/test_inspect/inspect_stringized_annotations.py new file mode 100644 index 00000000000..a56fb050ead --- /dev/null +++ b/Lib/test/test_inspect/inspect_stringized_annotations.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +a:int=3 +b:str="foo" + +class MyClass: + a:int=4 + b:str="bar" + def __init__(self, a, b): + self.a = a + self.b = b + def __eq__(self, other): + return isinstance(other, MyClass) and self.a == other.a and self.b == other.b + +def function(a:int, b:str) -> MyClass: + return MyClass(a, b) + + +def function2(a:int, b:"str", c:MyClass) -> MyClass: + pass + + +def function3(a:"int", b:"str", c:"MyClass"): + pass + + +class UnannotatedClass: + pass + +def unannotated_function(a, b, c): pass + +class MyClassWithLocalAnnotations: + mytype = int + x: mytype diff --git a/Lib/test/test_inspect/inspect_stringized_annotations_2.py b/Lib/test/test_inspect/inspect_stringized_annotations_2.py new file mode 100644 index 00000000000..87206d5a646 --- /dev/null +++ b/Lib/test/test_inspect/inspect_stringized_annotations_2.py @@ -0,0 +1,3 @@ +from __future__ import annotations + +def foo(a, b, c): pass diff --git a/Lib/test/test_inspect/inspect_stringized_annotations_pep695.py b/Lib/test/test_inspect/inspect_stringized_annotations_pep695.py new file mode 100644 index 00000000000..39bfe2edb03 --- /dev/null +++ b/Lib/test/test_inspect/inspect_stringized_annotations_pep695.py @@ -0,0 +1,87 @@ +from __future__ import annotations +from typing import Callable, Unpack + + +class A[T, *Ts, **P]: + x: T + y: tuple[*Ts] + z: Callable[P, str] + + +class B[T, *Ts, **P]: + T = int + Ts = str + P = bytes + x: T + y: Ts + z: P + + +Eggs = int +Spam = str + + +class C[Eggs, **Spam]: + x: Eggs + y: Spam + + +def generic_function[T, *Ts, **P]( + x: T, *y: Unpack[Ts], z: P.args, zz: P.kwargs +) -> None: ... + + +def generic_function_2[Eggs, **Spam](x: Eggs, y: Spam): pass + + +class D: + Foo = int + Bar = str + + def generic_method[Foo, **Bar]( + self, x: Foo, y: Bar + ) -> None: ... + + def generic_method_2[Eggs, **Spam](self, x: Eggs, y: Spam): pass + + +# Eggs is `int` in globals, a TypeVar in type_params, and `str` in locals: +class E[Eggs]: + Eggs = str + x: Eggs + + + +def nested(): + from types import SimpleNamespace + from inspect import get_annotations + + Eggs = bytes + Spam = memoryview + + + class F[Eggs, **Spam]: + x: Eggs + y: Spam + + def generic_method[Eggs, **Spam](self, x: Eggs, y: Spam): pass + + + def generic_function[Eggs, **Spam](x: Eggs, y: Spam): pass + + + # Eggs is `int` in globals, `bytes` in the function scope, + # a TypeVar in the type_params, and `str` in locals: + class G[Eggs]: + Eggs = str + x: Eggs + + + return SimpleNamespace( + F=F, + F_annotations=get_annotations(F, eval_str=True), + F_meth_annotations=get_annotations(F.generic_method, eval_str=True), + G_annotations=get_annotations(G, eval_str=True), + generic_func=generic_function, + generic_func_annotations=get_annotations(generic_function, eval_str=True) + ) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py new file mode 100644 index 00000000000..49996bba2ca --- /dev/null +++ b/Lib/test/test_inspect/test_inspect.py @@ -0,0 +1,6626 @@ +import asyncio +import builtins +import collections +import copy +import datetime +import functools +import gc +import importlib +import inspect +import io +import linecache +import os +import dis +from os.path import normcase +import _pickle +import pickle +import shutil +import stat +import sys +import subprocess +import time +import types +import tempfile +import textwrap +from typing import Unpack +import unicodedata +import unittest +import unittest.mock +import warnings +import weakref + + +try: + from concurrent.futures import ThreadPoolExecutor +except ImportError: + ThreadPoolExecutor = None + +from test.support import cpython_only, import_helper, suppress_immortalization +from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ +from test.support.import_helper import DirsOnSysPath, ready_to_import +from test.support.os_helper import TESTFN, temp_cwd +from test.support.script_helper import assert_python_ok, assert_python_failure, kill_python +from test.support import has_subprocess_support, SuppressCrashReport +from test import support + +from test.test_inspect import inspect_fodder as mod +from test.test_inspect import inspect_fodder2 as mod2 +from test.test_inspect import inspect_stock_annotations +from test.test_inspect import inspect_stringized_annotations +from test.test_inspect import inspect_stringized_annotations_2 +from test.test_inspect import inspect_stringized_annotations_pep695 + + +# Functions tested in this suite: +# ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode, +# isbuiltin, isroutine, isgenerator, isgeneratorfunction, getmembers, +# getdoc, getfile, getmodule, getsourcefile, getcomments, getsource, +# getclasstree, getargvalues, formatargvalues, currentframe, +# stack, trace, ismethoddescriptor, isdatadescriptor, ismethodwrapper + +# NOTE: There are some additional tests relating to interaction with +# zipimport in the test_zipimport_support test module. + +modfile = mod.__file__ +if modfile.endswith(('c', 'o')): + modfile = modfile[:-1] + +# Normalize file names: on Windows, the case of file names of compiled +# modules depends on the path used to start the python executable. +modfile = normcase(modfile) + +def revise(filename, *args): + return (normcase(filename),) + args + +git = mod.StupidGit() + + +def tearDownModule(): + if support.has_socket_support: + asyncio.set_event_loop_policy(None) + + +def signatures_with_lexicographic_keyword_only_parameters(): + """ + Yields a whole bunch of functions with only keyword-only parameters, + where those parameters are always in lexicographically sorted order. + """ + parameters = ['a', 'bar', 'c', 'delta', 'ephraim', 'magical', 'yoyo', 'z'] + for i in range(1, 2**len(parameters)): + p = [] + bit = 1 + for j in range(len(parameters)): + if i & (bit << j): + p.append(parameters[j]) + fn_text = "def foo(*, " + ", ".join(p) + "): pass" + symbols = {} + exec(fn_text, symbols, symbols) + yield symbols['foo'] + + +def unsorted_keyword_only_parameters_fn(*, throw, out, the, baby, with_, + the_, bathwater): + pass + +unsorted_keyword_only_parameters = 'throw out the baby with_ the_ bathwater'.split() + +class IsTestBase(unittest.TestCase): + predicates = set([inspect.isbuiltin, inspect.isclass, inspect.iscode, + inspect.isframe, inspect.isfunction, inspect.ismethod, + inspect.ismodule, inspect.istraceback, + inspect.isgenerator, inspect.isgeneratorfunction, + inspect.iscoroutine, inspect.iscoroutinefunction, + inspect.isasyncgen, inspect.isasyncgenfunction, + inspect.ismethodwrapper]) + + def istest(self, predicate, exp): + obj = eval(exp) + self.assertTrue(predicate(obj), '%s(%s)' % (predicate.__name__, exp)) + + for other in self.predicates - set([predicate]): + if (predicate == inspect.isgeneratorfunction or \ + predicate == inspect.isasyncgenfunction or \ + predicate == inspect.iscoroutinefunction) and \ + other == inspect.isfunction: + continue + self.assertFalse(other(obj), 'not %s(%s)' % (other.__name__, exp)) + + def test__all__(self): + support.check__all__(self, inspect, not_exported=("modulesbyfile",)) + +def generator_function_example(self): + for i in range(2): + yield i + +async def async_generator_function_example(self): + async for i in range(2): + yield i + +async def coroutine_function_example(self): + return 'spam' + +@types.coroutine +def gen_coroutine_function_example(self): + yield + return 'spam' + +def meth_noargs(): pass +def meth_o(object, /): pass +def meth_self_noargs(self, /): pass +def meth_self_o(self, object, /): pass +def meth_type_noargs(type, /): pass +def meth_type_o(type, object, /): pass + +# Decorator decorator that returns a simple wrapped function +def identity_wrapper(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + return func(*args, **kwargs) + return wrapped + +# Original signature of the simple wrapped function returned by +# identity_wrapper(). +varargs_signature = ( + (('args', ..., ..., 'var_positional'), + ('kwargs', ..., ..., 'var_keyword')), + ..., +) + +# Decorator decorator that returns a simple descriptor +class custom_descriptor: + def __init__(self, func): + self.func = func + + def __get__(self, instance, owner): + return self.func.__get__(instance, owner) + + +class TestPredicates(IsTestBase): + + def test_excluding_predicates(self): + global tb + self.istest(inspect.isbuiltin, 'sys.exit') + self.istest(inspect.isbuiltin, '[].append') + self.istest(inspect.iscode, 'mod.spam.__code__') + try: + 1/0 + except Exception as e: + tb = e.__traceback__ + self.istest(inspect.isframe, 'tb.tb_frame') + self.istest(inspect.istraceback, 'tb') + if hasattr(types, 'GetSetDescriptorType'): + self.istest(inspect.isgetsetdescriptor, + 'type(tb.tb_frame).f_locals') + else: + self.assertFalse(inspect.isgetsetdescriptor(type(tb.tb_frame).f_locals)) + finally: + # Clear traceback and all the frames and local variables hanging to it. + tb = None + self.istest(inspect.isfunction, 'mod.spam') + self.istest(inspect.isfunction, 'mod.StupidGit.abuse') + self.istest(inspect.ismethod, 'git.argue') + self.istest(inspect.ismethod, 'mod.custom_method') + self.istest(inspect.ismodule, 'mod') + self.istest(inspect.ismethoddescriptor, 'int.__add__') + self.istest(inspect.isdatadescriptor, 'collections.defaultdict.default_factory') + self.istest(inspect.isgenerator, '(x for x in range(2))') + self.istest(inspect.isgeneratorfunction, 'generator_function_example') + self.istest(inspect.isasyncgen, + 'async_generator_function_example(1)') + self.istest(inspect.isasyncgenfunction, + 'async_generator_function_example') + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + self.istest(inspect.iscoroutine, 'coroutine_function_example(1)') + self.istest(inspect.iscoroutinefunction, 'coroutine_function_example') + + if hasattr(types, 'MemberDescriptorType'): + self.istest(inspect.ismemberdescriptor, 'datetime.timedelta.days') + else: + self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days)) + self.istest(inspect.ismethodwrapper, "object().__str__") + self.istest(inspect.ismethodwrapper, "object().__eq__") + self.istest(inspect.ismethodwrapper, "object().__repr__") + self.assertFalse(inspect.ismethodwrapper(type)) + self.assertFalse(inspect.ismethodwrapper(int)) + self.assertFalse(inspect.ismethodwrapper(type("AnyClass", (), {}))) + + + + def test_iscoroutine(self): + async_gen_coro = async_generator_function_example(1) + gen_coro = gen_coroutine_function_example(1) + coro = coroutine_function_example(1) + + class PMClass: + async_generator_partialmethod_example = functools.partialmethod( + async_generator_function_example) + coroutine_partialmethod_example = functools.partialmethod( + coroutine_function_example) + gen_coroutine_partialmethod_example = functools.partialmethod( + gen_coroutine_function_example) + + # partialmethods on the class, bound to an instance + pm_instance = PMClass() + async_gen_coro_pmi = pm_instance.async_generator_partialmethod_example + gen_coro_pmi = pm_instance.gen_coroutine_partialmethod_example + coro_pmi = pm_instance.coroutine_partialmethod_example + + # partialmethods on the class, unbound but accessed via the class + async_gen_coro_pmc = PMClass.async_generator_partialmethod_example + gen_coro_pmc = PMClass.gen_coroutine_partialmethod_example + coro_pmc = PMClass.coroutine_partialmethod_example + + self.assertFalse( + inspect.iscoroutinefunction(gen_coroutine_function_example)) + self.assertFalse( + inspect.iscoroutinefunction( + functools.partial(functools.partial( + gen_coroutine_function_example)))) + self.assertFalse(inspect.iscoroutinefunction(gen_coro_pmi)) + self.assertFalse(inspect.iscoroutinefunction(gen_coro_pmc)) + self.assertFalse(inspect.iscoroutinefunction(inspect)) + self.assertFalse(inspect.iscoroutine(gen_coro)) + + self.assertTrue( + inspect.isgeneratorfunction(gen_coroutine_function_example)) + self.assertTrue( + inspect.isgeneratorfunction( + functools.partial(functools.partial( + gen_coroutine_function_example)))) + self.assertTrue(inspect.isgeneratorfunction(gen_coro_pmi)) + self.assertTrue(inspect.isgeneratorfunction(gen_coro_pmc)) + self.assertTrue(inspect.isgenerator(gen_coro)) + + async def _fn3(): + pass + + @inspect.markcoroutinefunction + def fn3(): + return _fn3() + + self.assertTrue(inspect.iscoroutinefunction(fn3)) + self.assertTrue( + inspect.iscoroutinefunction( + inspect.markcoroutinefunction(lambda: _fn3()) + ) + ) + + class Cl: + async def __call__(self): + pass + + self.assertFalse(inspect.iscoroutinefunction(Cl)) + # instances with async def __call__ are NOT recognised. + self.assertFalse(inspect.iscoroutinefunction(Cl())) + # unless explicitly marked. + self.assertTrue(inspect.iscoroutinefunction( + inspect.markcoroutinefunction(Cl()) + )) + + class Cl2: + @inspect.markcoroutinefunction + def __call__(self): + pass + + self.assertFalse(inspect.iscoroutinefunction(Cl2)) + # instances with marked __call__ are NOT recognised. + self.assertFalse(inspect.iscoroutinefunction(Cl2())) + # unless explicitly marked. + self.assertTrue(inspect.iscoroutinefunction( + inspect.markcoroutinefunction(Cl2()) + )) + + class Cl3: + @inspect.markcoroutinefunction + @classmethod + def do_something_classy(cls): + pass + + @inspect.markcoroutinefunction + @staticmethod + def do_something_static(): + pass + + self.assertTrue(inspect.iscoroutinefunction(Cl3.do_something_classy)) + self.assertTrue(inspect.iscoroutinefunction(Cl3.do_something_static)) + + self.assertFalse( + inspect.iscoroutinefunction(unittest.mock.Mock())) + self.assertTrue( + inspect.iscoroutinefunction(unittest.mock.AsyncMock())) + self.assertTrue( + inspect.iscoroutinefunction(coroutine_function_example)) + self.assertTrue( + inspect.iscoroutinefunction( + functools.partial(functools.partial( + coroutine_function_example)))) + self.assertTrue(inspect.iscoroutinefunction(coro_pmi)) + self.assertTrue(inspect.iscoroutinefunction(coro_pmc)) + self.assertTrue(inspect.iscoroutine(coro)) + + self.assertFalse( + inspect.isgeneratorfunction(unittest.mock.Mock())) + self.assertFalse( + inspect.isgeneratorfunction(unittest.mock.AsyncMock())) + self.assertFalse( + inspect.isgeneratorfunction(coroutine_function_example)) + self.assertFalse( + inspect.isgeneratorfunction( + functools.partial(functools.partial( + coroutine_function_example)))) + self.assertFalse(inspect.isgeneratorfunction(coro_pmi)) + self.assertFalse(inspect.isgeneratorfunction(coro_pmc)) + self.assertFalse(inspect.isgenerator(coro)) + + self.assertFalse( + inspect.isasyncgenfunction(unittest.mock.Mock())) + self.assertFalse( + inspect.isasyncgenfunction(unittest.mock.AsyncMock())) + self.assertFalse( + inspect.isasyncgenfunction(coroutine_function_example)) + self.assertTrue( + inspect.isasyncgenfunction(async_generator_function_example)) + self.assertTrue( + inspect.isasyncgenfunction( + functools.partial(functools.partial( + async_generator_function_example)))) + self.assertTrue(inspect.isasyncgenfunction(async_gen_coro_pmi)) + self.assertTrue(inspect.isasyncgenfunction(async_gen_coro_pmc)) + self.assertTrue(inspect.isasyncgen(async_gen_coro)) + + coro.close(); gen_coro.close(); # silence warnings + + def test_isawaitable(self): + def gen(): yield + self.assertFalse(inspect.isawaitable(gen())) + + coro = coroutine_function_example(1) + gen_coro = gen_coroutine_function_example(1) + + self.assertTrue(inspect.isawaitable(coro)) + self.assertTrue(inspect.isawaitable(gen_coro)) + + class Future: + def __await__(): + pass + self.assertTrue(inspect.isawaitable(Future())) + self.assertFalse(inspect.isawaitable(Future)) + + class NotFuture: pass + not_fut = NotFuture() + not_fut.__await__ = lambda: None + self.assertFalse(inspect.isawaitable(not_fut)) + + coro.close(); gen_coro.close() # silence warnings + + def test_isroutine(self): + # method + self.assertTrue(inspect.isroutine(git.argue)) + self.assertTrue(inspect.isroutine(mod.custom_method)) + self.assertTrue(inspect.isroutine([].count)) + # function + self.assertTrue(inspect.isroutine(mod.spam)) + self.assertTrue(inspect.isroutine(mod.StupidGit.abuse)) + # slot-wrapper + self.assertTrue(inspect.isroutine(object.__init__)) + self.assertTrue(inspect.isroutine(object.__str__)) + self.assertTrue(inspect.isroutine(object.__lt__)) + self.assertTrue(inspect.isroutine(int.__lt__)) + # method-wrapper + self.assertTrue(inspect.isroutine(object().__init__)) + self.assertTrue(inspect.isroutine(object().__str__)) + self.assertTrue(inspect.isroutine(object().__lt__)) + self.assertTrue(inspect.isroutine((42).__lt__)) + # method-descriptor + self.assertTrue(inspect.isroutine(str.join)) + self.assertTrue(inspect.isroutine(list.append)) + self.assertTrue(inspect.isroutine(''.join)) + self.assertTrue(inspect.isroutine([].append)) + # object + self.assertFalse(inspect.isroutine(object)) + self.assertFalse(inspect.isroutine(object())) + self.assertFalse(inspect.isroutine(str())) + # module + self.assertFalse(inspect.isroutine(mod)) + # type + self.assertFalse(inspect.isroutine(type)) + self.assertFalse(inspect.isroutine(int)) + self.assertFalse(inspect.isroutine(type('some_class', (), {}))) + # partial + self.assertFalse(inspect.isroutine(functools.partial(mod.spam))) + + def test_isroutine_singledispatch(self): + self.assertTrue(inspect.isroutine(functools.singledispatch(mod.spam))) + + class A: + @functools.singledispatchmethod + def method(self, arg): + pass + @functools.singledispatchmethod + @classmethod + def class_method(cls, arg): + pass + @functools.singledispatchmethod + @staticmethod + def static_method(arg): + pass + + self.assertTrue(inspect.isroutine(A.method)) + self.assertTrue(inspect.isroutine(A().method)) + self.assertTrue(inspect.isroutine(A.static_method)) + self.assertTrue(inspect.isroutine(A.class_method)) + + def test_isclass(self): + self.istest(inspect.isclass, 'mod.StupidGit') + self.assertTrue(inspect.isclass(list)) + + class CustomGetattr(object): + def __getattr__(self, attr): + return None + self.assertFalse(inspect.isclass(CustomGetattr())) + + def test_get_slot_members(self): + class C(object): + __slots__ = ("a", "b") + x = C() + x.a = 42 + members = dict(inspect.getmembers(x)) + self.assertIn('a', members) + self.assertNotIn('b', members) + + def test_isabstract(self): + from abc import ABCMeta, abstractmethod + + class AbstractClassExample(metaclass=ABCMeta): + + @abstractmethod + def foo(self): + pass + + class ClassExample(AbstractClassExample): + def foo(self): + pass + + a = ClassExample() + + # Test general behaviour. + self.assertTrue(inspect.isabstract(AbstractClassExample)) + self.assertFalse(inspect.isabstract(ClassExample)) + self.assertFalse(inspect.isabstract(a)) + self.assertFalse(inspect.isabstract(int)) + self.assertFalse(inspect.isabstract(5)) + + def test_isabstract_during_init_subclass(self): + from abc import ABCMeta, abstractmethod + isabstract_checks = [] + class AbstractChecker(metaclass=ABCMeta): + def __init_subclass__(cls): + isabstract_checks.append(inspect.isabstract(cls)) + class AbstractClassExample(AbstractChecker): + @abstractmethod + def foo(self): + pass + class ClassExample(AbstractClassExample): + def foo(self): + pass + self.assertEqual(isabstract_checks, [True, False]) + + isabstract_checks.clear() + class AbstractChild(AbstractClassExample): + pass + class AbstractGrandchild(AbstractChild): + pass + class ConcreteGrandchild(ClassExample): + pass + self.assertEqual(isabstract_checks, [True, True, False]) + + +class TestInterpreterStack(IsTestBase): + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + + git.abuse(7, 8, 9) + + def test_abuse_done(self): + self.istest(inspect.istraceback, 'git.ex.__traceback__') + self.istest(inspect.isframe, 'mod.fr') + + def test_stack(self): + self.assertTrue(len(mod.st) >= 5) + frame1, frame2, frame3, frame4, *_ = mod.st + frameinfo = revise(*frame1[1:]) + self.assertEqual(frameinfo, + (modfile, 16, 'eggs', [' st = inspect.stack()\n'], 0)) + self.assertEqual(frame1.positions, dis.Positions(16, 16, 9, 24)) + frameinfo = revise(*frame2[1:]) + self.assertEqual(frameinfo, + (modfile, 9, 'spam', [' eggs(b + d, c + f)\n'], 0)) + self.assertEqual(frame2.positions, dis.Positions(9, 9, 4, 22)) + frameinfo = revise(*frame3[1:]) + self.assertEqual(frameinfo, + (modfile, 43, 'argue', [' spam(a, b, c)\n'], 0)) + self.assertEqual(frame3.positions, dis.Positions(43, 43, 12, 25)) + frameinfo = revise(*frame4[1:]) + self.assertEqual(frameinfo, + (modfile, 39, 'abuse', [' self.argue(a, b, c)\n'], 0)) + self.assertEqual(frame4.positions, dis.Positions(39, 39, 8, 27)) + # Test named tuple fields + record = mod.st[0] + self.assertIs(record.frame, mod.fr) + self.assertEqual(record.lineno, 16) + self.assertEqual(record.filename, mod.__file__) + self.assertEqual(record.function, 'eggs') + self.assertIn('inspect.stack()', record.code_context[0]) + self.assertEqual(record.index, 0) + + def test_trace(self): + self.assertEqual(len(git.tr), 3) + frame1, frame2, frame3, = git.tr + self.assertEqual(revise(*frame1[1:]), + (modfile, 43, 'argue', [' spam(a, b, c)\n'], 0)) + self.assertEqual(frame1.positions, dis.Positions(43, 43, 12, 25)) + self.assertEqual(revise(*frame2[1:]), + (modfile, 9, 'spam', [' eggs(b + d, c + f)\n'], 0)) + self.assertEqual(frame2.positions, dis.Positions(9, 9, 4, 22)) + self.assertEqual(revise(*frame3[1:]), + (modfile, 18, 'eggs', [' q = y / 0\n'], 0)) + self.assertEqual(frame3.positions, dis.Positions(18, 18, 8, 13)) + + def test_frame(self): + args, varargs, varkw, locals = inspect.getargvalues(mod.fr) + self.assertEqual(args, ['x', 'y']) + self.assertEqual(varargs, None) + self.assertEqual(varkw, None) + self.assertEqual(locals, {'x': 11, 'p': 11, 'y': 14}) + self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals), + '(x=11, y=14)') + + def test_previous_frame(self): + args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back) + self.assertEqual(args, ['a', 'b', 'c', 'd', 'e', 'f']) + self.assertEqual(varargs, 'g') + self.assertEqual(varkw, 'h') + self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals), + '(a=7, b=8, c=9, d=3, e=4, f=5, *g=(), **h={})') + +class GetSourceBase(unittest.TestCase): + # Subclasses must override. + fodderModule = None + + def setUp(self): + with open(inspect.getsourcefile(self.fodderModule), encoding="utf-8") as fp: + self.source = fp.read() + + def sourcerange(self, top, bottom): + lines = self.source.split("\n") + return "\n".join(lines[top-1:bottom]) + ("\n" if bottom else "") + + def assertSourceEqual(self, obj, top, bottom): + self.assertEqual(inspect.getsource(obj), + self.sourcerange(top, bottom)) + +class SlotUser: + 'Docstrings for __slots__' + __slots__ = {'power': 'measured in kilowatts', + 'distance': 'measured in kilometers'} + +class TestRetrievingSourceCode(GetSourceBase): + fodderModule = mod + + def test_getclasses(self): + classes = inspect.getmembers(mod, inspect.isclass) + self.assertEqual(classes, + [('FesteringGob', mod.FesteringGob), + ('MalodorousPervert', mod.MalodorousPervert), + ('ParrotDroppings', mod.ParrotDroppings), + ('StupidGit', mod.StupidGit), + ('Tit', mod.MalodorousPervert), + ('WhichComments', mod.WhichComments), + ]) + tree = inspect.getclasstree([cls[1] for cls in classes]) + self.assertEqual(tree, + [(object, ()), + [(mod.ParrotDroppings, (object,)), + [(mod.FesteringGob, (mod.MalodorousPervert, + mod.ParrotDroppings)) + ], + (mod.StupidGit, (object,)), + [(mod.MalodorousPervert, (mod.StupidGit,)), + [(mod.FesteringGob, (mod.MalodorousPervert, + mod.ParrotDroppings)) + ] + ], + (mod.WhichComments, (object,),) + ] + ]) + tree = inspect.getclasstree([cls[1] for cls in classes], True) + self.assertEqual(tree, + [(object, ()), + [(mod.ParrotDroppings, (object,)), + (mod.StupidGit, (object,)), + [(mod.MalodorousPervert, (mod.StupidGit,)), + [(mod.FesteringGob, (mod.MalodorousPervert, + mod.ParrotDroppings)) + ] + ], + (mod.WhichComments, (object,),) + ] + ]) + + def test_getfunctions(self): + functions = inspect.getmembers(mod, inspect.isfunction) + self.assertEqual(functions, [('after_closing', mod.after_closing), + ('eggs', mod.eggs), + ('lobbest', mod.lobbest), + ('spam', mod.spam)]) + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_getdoc(self): + self.assertEqual(inspect.getdoc(mod), 'A module docstring.') + self.assertEqual(inspect.getdoc(mod.StupidGit), + 'A longer,\n\nindented\n\ndocstring.') + self.assertEqual(inspect.getdoc(git.abuse), + 'Another\n\ndocstring\n\ncontaining\n\ntabs') + self.assertEqual(inspect.getdoc(SlotUser.power), + 'measured in kilowatts') + self.assertEqual(inspect.getdoc(SlotUser.distance), + 'measured in kilometers') + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_getdoc_inherited(self): + self.assertEqual(inspect.getdoc(mod.FesteringGob), + 'A longer,\n\nindented\n\ndocstring.') + self.assertEqual(inspect.getdoc(mod.FesteringGob.abuse), + 'Another\n\ndocstring\n\ncontaining\n\ntabs') + self.assertEqual(inspect.getdoc(mod.FesteringGob().abuse), + 'Another\n\ndocstring\n\ncontaining\n\ntabs') + self.assertEqual(inspect.getdoc(mod.FesteringGob.contradiction), + 'The automatic gainsaying.') + + @unittest.skipIf(MISSING_C_DOCSTRINGS, "test requires docstrings") + def test_finddoc(self): + finddoc = inspect._finddoc + self.assertEqual(finddoc(int), int.__doc__) + self.assertEqual(finddoc(int.to_bytes), int.to_bytes.__doc__) + self.assertEqual(finddoc(int().to_bytes), int.to_bytes.__doc__) + self.assertEqual(finddoc(int.from_bytes), int.from_bytes.__doc__) + self.assertEqual(finddoc(int.real), int.real.__doc__) + + cleandoc_testdata = [ + # first line should have different margin + (' An\n indented\n docstring.', 'An\nindented\n docstring.'), + # trailing whitespace are not removed. + (' An \n \n indented \n docstring. ', + 'An \n \nindented \n docstring. '), + # NUL is not termination. + ('doc\0string\n\n second\0line\n third\0line\0', + 'doc\0string\n\nsecond\0line\nthird\0line\0'), + # first line is lstrip()-ped. other lines are kept when no margin.[w: + (' ', ''), + # compiler.cleandoc() doesn't strip leading/trailing newlines + # to keep maximum backward compatibility. + # inspect.cleandoc() removes them. + ('\n\n\n first paragraph\n\n second paragraph\n\n', + '\n\n\nfirst paragraph\n\n second paragraph\n\n'), + (' \n \n \n ', '\n \n \n '), + ] + + def test_cleandoc(self): + func = inspect.cleandoc + for i, (input, expected) in enumerate(self.cleandoc_testdata): + # only inspect.cleandoc() strip \n + expected = expected.strip('\n') + with self.subTest(i=i): + self.assertEqual(func(input), expected) + + @cpython_only + def test_c_cleandoc(self): + try: + import _testinternalcapi + except ImportError: + return unittest.skip("requires _testinternalcapi") + func = _testinternalcapi.compiler_cleandoc + for i, (input, expected) in enumerate(self.cleandoc_testdata): + with self.subTest(i=i): + self.assertEqual(func(input), expected) + + def test_getcomments(self): + self.assertEqual(inspect.getcomments(mod), '# line 1\n') + self.assertEqual(inspect.getcomments(mod.StupidGit), '# line 20\n') + self.assertEqual(inspect.getcomments(mod2.cls160), '# line 159\n') + # If the object source file is not available, return None. + co = compile('x=1', '_non_existing_filename.py', 'exec') + self.assertIsNone(inspect.getcomments(co)) + # If the object has been defined in C, return None. + self.assertIsNone(inspect.getcomments(list)) + + def test_getmodule(self): + # Check actual module + self.assertEqual(inspect.getmodule(mod), mod) + # Check class (uses __module__ attribute) + self.assertEqual(inspect.getmodule(mod.StupidGit), mod) + # Check a method (no __module__ attribute, falls back to filename) + self.assertEqual(inspect.getmodule(mod.StupidGit.abuse), mod) + # Do it again (check the caching isn't broken) + self.assertEqual(inspect.getmodule(mod.StupidGit.abuse), mod) + # Check a builtin + self.assertEqual(inspect.getmodule(str), sys.modules["builtins"]) + # Check filename override + self.assertEqual(inspect.getmodule(None, modfile), mod) + + def test_getmodule_file_not_found(self): + # See bpo-45406 + def _getabsfile(obj, _filename): + raise FileNotFoundError('bad file') + with unittest.mock.patch('inspect.getabsfile', _getabsfile): + f = inspect.currentframe() + self.assertIsNone(inspect.getmodule(f)) + inspect.getouterframes(f) # smoke test + + def test_getframeinfo_get_first_line(self): + frame_info = inspect.getframeinfo(self.fodderModule.fr, 50) + self.assertEqual(frame_info.code_context[0], "# line 1\n") + self.assertEqual(frame_info.code_context[1], "'A module docstring.'\n") + + def test_getsource(self): + self.assertSourceEqual(git.abuse, 29, 39) + self.assertSourceEqual(mod.StupidGit, 21, 51) + self.assertSourceEqual(mod.lobbest, 75, 76) + self.assertSourceEqual(mod.after_closing, 120, 120) + + def test_getsourcefile(self): + self.assertEqual(normcase(inspect.getsourcefile(mod.spam)), modfile) + self.assertEqual(normcase(inspect.getsourcefile(git.abuse)), modfile) + fn = "_non_existing_filename_used_for_sourcefile_test.py" + co = compile("x=1", fn, "exec") + self.assertEqual(inspect.getsourcefile(co), None) + linecache.cache[co.co_filename] = (1, None, "None", co.co_filename) + try: + self.assertEqual(normcase(inspect.getsourcefile(co)), fn) + finally: + del linecache.cache[co.co_filename] + + def test_getsource_empty_file(self): + with temp_cwd() as cwd: + with open('empty_file.py', 'w'): + pass + sys.path.insert(0, cwd) + try: + import empty_file + self.assertEqual(inspect.getsource(empty_file), '\n') + self.assertEqual(inspect.getsourcelines(empty_file), (['\n'], 0)) + finally: + sys.path.remove(cwd) + + def test_getfile(self): + self.assertEqual(inspect.getfile(mod.StupidGit), mod.__file__) + + def test_getfile_builtin_module(self): + with self.assertRaises(TypeError) as e: + inspect.getfile(sys) + self.assertTrue(str(e.exception).startswith('<module')) + + def test_getfile_builtin_class(self): + with self.assertRaises(TypeError) as e: + inspect.getfile(int) + self.assertTrue(str(e.exception).startswith('<class')) + + def test_getfile_builtin_function_or_method(self): + with self.assertRaises(TypeError) as e_abs: + inspect.getfile(abs) + self.assertIn('expected, got', str(e_abs.exception)) + with self.assertRaises(TypeError) as e_append: + inspect.getfile(list.append) + self.assertIn('expected, got', str(e_append.exception)) + + @suppress_immortalization() + def test_getfile_class_without_module(self): + class CM(type): + @property + def __module__(cls): + raise AttributeError + class C(metaclass=CM): + pass + with self.assertRaises(TypeError): + inspect.getfile(C) + + def test_getfile_broken_repr(self): + class ErrorRepr: + def __repr__(self): + raise Exception('xyz') + er = ErrorRepr() + with self.assertRaises(TypeError): + inspect.getfile(er) + + def test_getmodule_recursion(self): + from types import ModuleType + name = '__inspect_dummy' + m = sys.modules[name] = ModuleType(name) + m.__file__ = "<string>" # hopefully not a real filename... + m.__loader__ = "dummy" # pretend the filename is understood by a loader + exec("def x(): pass", m.__dict__) + self.assertEqual(inspect.getsourcefile(m.x.__code__), '<string>') + del sys.modules[name] + inspect.getmodule(compile('a=10','','single')) + + def test_proceed_with_fake_filename(self): + '''doctest monkeypatches linecache to enable inspection''' + fn, source = '<test>', 'def x(): pass\n' + getlines = linecache.getlines + def monkey(filename, module_globals=None): + if filename == fn: + return source.splitlines(keepends=True) + else: + return getlines(filename, module_globals) + linecache.getlines = monkey + try: + ns = {} + exec(compile(source, fn, 'single'), ns) + inspect.getsource(ns["x"]) + finally: + linecache.getlines = getlines + + def test_getsource_on_code_object(self): + self.assertSourceEqual(mod.eggs.__code__, 12, 18) + + def test_getsource_on_generated_class(self): + A = type('A', (unittest.TestCase,), {}) + self.assertEqual(inspect.getsourcefile(A), __file__) + self.assertEqual(inspect.getfile(A), __file__) + self.assertIs(inspect.getmodule(A), sys.modules[__name__]) + self.assertRaises(OSError, inspect.getsource, A) + self.assertRaises(OSError, inspect.getsourcelines, A) + self.assertIsNone(inspect.getcomments(A)) + + def test_getsource_on_class_without_firstlineno(self): + __firstlineno__ = 1 + class C: + nonlocal __firstlineno__ + self.assertRaises(OSError, inspect.getsource, C) + +class TestGetsourceStdlib(unittest.TestCase): + # Test Python implementations of the stdlib modules + + def test_getsource_stdlib_collections_abc(self): + import collections.abc + lines, lineno = inspect.getsourcelines(collections.abc.Sequence) + self.assertEqual(lines[0], 'class Sequence(Reversible, Collection):\n') + src = inspect.getsource(collections.abc.Sequence) + self.assertEqual(src.splitlines(True), lines) + + def test_getsource_stdlib_tomllib(self): + import tomllib + self.assertRaises(OSError, inspect.getsource, tomllib.TOMLDecodeError) + self.assertRaises(OSError, inspect.getsourcelines, tomllib.TOMLDecodeError) + + def test_getsource_stdlib_abc(self): + # Pure Python implementation + abc = import_helper.import_fresh_module('abc', blocked=['_abc']) + with support.swap_item(sys.modules, 'abc', abc): + self.assertRaises(OSError, inspect.getsource, abc.ABCMeta) + self.assertRaises(OSError, inspect.getsourcelines, abc.ABCMeta) + # With C acceleration + import abc + try: + src = inspect.getsource(abc.ABCMeta) + lines, lineno = inspect.getsourcelines(abc.ABCMeta) + except OSError: + pass + else: + self.assertEqual(lines[0], ' class ABCMeta(type):\n') + self.assertEqual(src.splitlines(True), lines) + + def test_getsource_stdlib_decimal(self): + # Pure Python implementation + decimal = import_helper.import_fresh_module('decimal', blocked=['_decimal']) + with support.swap_item(sys.modules, 'decimal', decimal): + src = inspect.getsource(decimal.Decimal) + lines, lineno = inspect.getsourcelines(decimal.Decimal) + self.assertEqual(lines[0], 'class Decimal(object):\n') + self.assertEqual(src.splitlines(True), lines) + +class TestGetsourceInteractive(unittest.TestCase): + @support.force_not_colorized + def test_getclasses_interactive(self): + # bpo-44648: simulate a REPL session; + # there is no `__file__` in the __main__ module + code = "import sys, inspect; \ + assert not hasattr(sys.modules['__main__'], '__file__'); \ + A = type('A', (), {}); \ + inspect.getsource(A)" + _, _, stderr = assert_python_failure("-c", code, __isolated=True) + self.assertIn(b'OSError: source code not available', stderr) + +class TestGettingSourceOfToplevelFrames(GetSourceBase): + fodderModule = mod + + def test_range_toplevel_frame(self): + self.maxDiff = None + self.assertSourceEqual(mod.currentframe, 1, None) + + def test_range_traceback_toplevel_frame(self): + self.assertSourceEqual(mod.tb, 1, None) + +class TestDecorators(GetSourceBase): + fodderModule = mod2 + + def test_wrapped_decorator(self): + self.assertSourceEqual(mod2.wrapped, 14, 17) + + def test_replacing_decorator(self): + self.assertSourceEqual(mod2.gone, 9, 10) + + def test_getsource_unwrap(self): + self.assertSourceEqual(mod2.real, 130, 132) + + def test_decorator_with_lambda(self): + self.assertSourceEqual(mod2.func114, 113, 115) + +class TestOneliners(GetSourceBase): + fodderModule = mod2 + def test_oneline_lambda(self): + # Test inspect.getsource with a one-line lambda function. + self.assertSourceEqual(mod2.oll, 25, 25) + + def test_threeline_lambda(self): + # Test inspect.getsource with a three-line lambda function, + # where the second and third lines are _not_ indented. + self.assertSourceEqual(mod2.tll, 28, 30) + + def test_twoline_indented_lambda(self): + # Test inspect.getsource with a two-line lambda function, + # where the second line _is_ indented. + self.assertSourceEqual(mod2.tlli, 33, 34) + + def test_parenthesized_multiline_lambda(self): + # Test inspect.getsource with a parenthesized multi-line lambda + # function. + self.assertSourceEqual(mod2.parenthesized_lambda, 279, 279) + self.assertSourceEqual(mod2.parenthesized_lambda2, 281, 281) + self.assertSourceEqual(mod2.parenthesized_lambda3, 283, 283) + + def test_post_line_parenthesized_lambda(self): + # Test inspect.getsource with a parenthesized multi-line lambda + # function. + self.assertSourceEqual(mod2.post_line_parenthesized_lambda1, 286, 287) + + def test_nested_lambda(self): + # Test inspect.getsource with a nested lambda function. + self.assertSourceEqual(mod2.nested_lambda, 291, 292) + + def test_onelinefunc(self): + # Test inspect.getsource with a regular one-line function. + self.assertSourceEqual(mod2.onelinefunc, 37, 37) + + def test_manyargs(self): + # Test inspect.getsource with a regular function where + # the arguments are on two lines and _not_ indented and + # the body on the second line with the last arguments. + self.assertSourceEqual(mod2.manyargs, 40, 41) + + def test_twolinefunc(self): + # Test inspect.getsource with a regular function where + # the body is on two lines, following the argument list and + # continued on the next line by a \\. + self.assertSourceEqual(mod2.twolinefunc, 44, 45) + + def test_lambda_in_list(self): + # Test inspect.getsource with a one-line lambda function + # defined in a list, indented. + self.assertSourceEqual(mod2.a[1], 49, 49) + + def test_anonymous(self): + # Test inspect.getsource with a lambda function defined + # as argument to another function. + self.assertSourceEqual(mod2.anonymous, 55, 55) + + def test_enum(self): + self.assertSourceEqual(mod2.enum322, 322, 323) + self.assertSourceEqual(mod2.enum326, 326, 327) + self.assertSourceEqual(mod2.flag330, 330, 331) + self.assertSourceEqual(mod2.flag334, 334, 335) + self.assertRaises(OSError, inspect.getsource, mod2.simple_enum338) + self.assertRaises(OSError, inspect.getsource, mod2.simple_enum339) + self.assertRaises(OSError, inspect.getsource, mod2.simple_flag340) + self.assertRaises(OSError, inspect.getsource, mod2.simple_flag341) + + def test_namedtuple(self): + self.assertSourceEqual(mod2.nt346, 346, 348) + self.assertRaises(OSError, inspect.getsource, mod2.nt351) + + def test_typeddict(self): + self.assertSourceEqual(mod2.td354, 354, 356) + self.assertRaises(OSError, inspect.getsource, mod2.td359) + + def test_dataclass(self): + self.assertSourceEqual(mod2.dc364, 364, 367) + self.assertRaises(OSError, inspect.getsource, mod2.dc370) + self.assertRaises(OSError, inspect.getsource, mod2.dc371) + +class TestBlockComments(GetSourceBase): + fodderModule = mod + + def test_toplevel_class(self): + self.assertSourceEqual(mod.WhichComments, 96, 114) + + def test_class_method(self): + self.assertSourceEqual(mod.WhichComments.f, 99, 104) + + def test_class_async_method(self): + self.assertSourceEqual(mod.WhichComments.asyncf, 109, 112) + +class TestBuggyCases(GetSourceBase): + fodderModule = mod2 + + def test_with_comment(self): + self.assertSourceEqual(mod2.with_comment, 58, 59) + + def test_multiline_sig(self): + self.assertSourceEqual(mod2.multiline_sig[0], 63, 64) + + def test_nested_class(self): + self.assertSourceEqual(mod2.func69().func71, 71, 72) + + def test_one_liner_followed_by_non_name(self): + self.assertSourceEqual(mod2.func77, 77, 77) + + def test_one_liner_dedent_non_name(self): + self.assertSourceEqual(mod2.cls82.func83, 83, 83) + + def test_with_comment_instead_of_docstring(self): + self.assertSourceEqual(mod2.func88, 88, 90) + + def test_method_in_dynamic_class(self): + self.assertSourceEqual(mod2.method_in_dynamic_class, 95, 97) + + # This should not skip for CPython, but might on a repackaged python where + # unicodedata is not an external module, or on pypy. + @unittest.skipIf(not hasattr(unicodedata, '__file__') or + unicodedata.__file__.endswith('.py'), + "unicodedata is not an external binary module") + def test_findsource_binary(self): + self.assertRaises(OSError, inspect.getsource, unicodedata) + self.assertRaises(OSError, inspect.findsource, unicodedata) + + def test_findsource_code_in_linecache(self): + lines = ["x=1"] + co = compile(lines[0], "_dynamically_created_file", "exec") + self.assertRaises(OSError, inspect.findsource, co) + self.assertRaises(OSError, inspect.getsource, co) + linecache.cache[co.co_filename] = (1, None, lines, co.co_filename) + try: + self.assertEqual(inspect.findsource(co), (lines,0)) + self.assertEqual(inspect.getsource(co), lines[0]) + finally: + del linecache.cache[co.co_filename] + + def test_findsource_without_filename(self): + for fname in ['', '<string>']: + co = compile('x=1', fname, "exec") + self.assertRaises(IOError, inspect.findsource, co) + self.assertRaises(IOError, inspect.getsource, co) + + def test_findsource_on_func_with_out_of_bounds_lineno(self): + mod_len = len(inspect.getsource(mod)) + src = '\n' * 2* mod_len + "def f(): pass" + co = compile(src, mod.__file__, "exec") + g, l = {}, {} + eval(co, g, l) + func = l['f'] + self.assertEqual(func.__code__.co_firstlineno, 1+2*mod_len) + with self.assertRaisesRegex(OSError, "lineno is out of bounds"): + inspect.findsource(func) + + def test_findsource_on_class_with_out_of_bounds_lineno(self): + mod_len = len(inspect.getsource(mod)) + src = '\n' * 2* mod_len + "class A: pass" + co = compile(src, mod.__file__, "exec") + g, l = {'__name__': mod.__name__}, {} + eval(co, g, l) + cls = l['A'] + self.assertEqual(cls.__firstlineno__, 1+2*mod_len) + with self.assertRaisesRegex(OSError, "lineno is out of bounds"): + inspect.findsource(cls) + + def test_getsource_on_method(self): + self.assertSourceEqual(mod2.ClassWithMethod.method, 118, 119) + + def test_getsource_on_class_code_object(self): + self.assertSourceEqual(mod2.ClassWithCodeObject.code, 315, 317) + + def test_nested_func(self): + self.assertSourceEqual(mod2.cls135.func136, 136, 139) + + def test_class_definition_in_multiline_string_definition(self): + self.assertSourceEqual(mod2.cls149, 149, 152) + + def test_class_definition_in_multiline_comment(self): + self.assertSourceEqual(mod2.cls160, 160, 163) + + def test_nested_class_definition_indented_string(self): + self.assertSourceEqual(mod2.cls173.cls175, 175, 176) + + def test_nested_class_definition(self): + self.assertSourceEqual(mod2.cls183, 183, 188) + self.assertSourceEqual(mod2.cls183.cls185, 185, 188) + + def test_class_decorator(self): + self.assertSourceEqual(mod2.cls196, 194, 201) + self.assertSourceEqual(mod2.cls196.cls200, 198, 201) + + @support.requires_docstrings + def test_class_inside_conditional(self): + # We skip this test when docstrings are not present, + # because docstrings are one of the main factors of + # finding the correct class in the source code. + self.assertSourceEqual(mod2.cls238.cls239, 239, 240) + + def test_multiple_children_classes(self): + self.assertSourceEqual(mod2.cls203, 203, 209) + self.assertSourceEqual(mod2.cls203.cls204, 204, 206) + self.assertSourceEqual(mod2.cls203.cls204.cls205, 205, 206) + self.assertSourceEqual(mod2.cls203.cls207, 207, 209) + self.assertSourceEqual(mod2.cls203.cls207.cls205, 208, 209) + + def test_nested_class_definition_inside_function(self): + self.assertSourceEqual(mod2.func212(), 213, 214) + self.assertSourceEqual(mod2.cls213, 218, 222) + self.assertSourceEqual(mod2.cls213().func219(), 220, 221) + + def test_class_with_method_from_other_module(self): + with tempfile.TemporaryDirectory() as tempdir: + with open(os.path.join(tempdir, 'inspect_actual%spy' % os.extsep), + 'w', encoding='utf-8') as f: + f.write(textwrap.dedent(""" + import inspect_other + class A: + def f(self): + pass + class A: + def f(self): + pass # correct one + A.f = inspect_other.A.f + """)) + + with open(os.path.join(tempdir, 'inspect_other%spy' % os.extsep), + 'w', encoding='utf-8') as f: + f.write(textwrap.dedent(""" + class A: + def f(self): + pass + """)) + + with DirsOnSysPath(tempdir): + import inspect_actual + self.assertIn("correct", inspect.getsource(inspect_actual.A)) + # Remove the module from sys.modules to force it to be reloaded. + # This is necessary when the test is run multiple times. + sys.modules.pop("inspect_actual") + + @unittest.skipIf( + support.is_emscripten or support.is_wasi, + "socket.accept is broken" + ) + def test_nested_class_definition_inside_async_function(self): + import asyncio + self.addCleanup(asyncio.set_event_loop_policy, None) + self.assertSourceEqual(asyncio.run(mod2.func225()), 226, 227) + self.assertSourceEqual(mod2.cls226, 231, 235) + self.assertSourceEqual(mod2.cls226.func232, 232, 235) + self.assertSourceEqual(asyncio.run(mod2.cls226().func232()), 233, 234) + + def test_class_definition_same_name_diff_methods(self): + self.assertSourceEqual(mod2.cls296, 296, 298) + self.assertSourceEqual(mod2.cls310, 310, 312) + + def test_generator_expression(self): + self.assertSourceEqual(next(mod2.ge377), 377, 380) + self.assertSourceEqual(next(mod2.func383()), 385, 388) + + def test_comment_or_empty_line_after_decorator(self): + self.assertSourceEqual(mod2.func394, 392, 395) + self.assertSourceEqual(mod2.func400, 398, 401) + + +class TestNoEOL(GetSourceBase): + def setUp(self): + self.tempdir = TESTFN + '_dir' + os.mkdir(self.tempdir) + with open(os.path.join(self.tempdir, 'inspect_fodder3%spy' % os.extsep), + 'w', encoding='utf-8') as f: + f.write("class X:\n pass # No EOL") + with DirsOnSysPath(self.tempdir): + import inspect_fodder3 as mod3 + self.fodderModule = mod3 + super().setUp() + + def tearDown(self): + shutil.rmtree(self.tempdir) + + def test_class(self): + self.assertSourceEqual(self.fodderModule.X, 1, 2) + + +class TestComplexDecorator(GetSourceBase): + fodderModule = mod2 + + def test_parens_in_decorator(self): + self.assertSourceEqual(self.fodderModule.complex_decorated, 273, 275) + +class _BrokenDataDescriptor(object): + """ + A broken data descriptor. See bug #1785. + """ + def __get__(*args): + raise AttributeError("broken data descriptor") + + def __set__(*args): + raise RuntimeError + + def __getattr__(*args): + raise AttributeError("broken data descriptor") + + +class _BrokenMethodDescriptor(object): + """ + A broken method descriptor. See bug #1785. + """ + def __get__(*args): + raise AttributeError("broken method descriptor") + + def __getattr__(*args): + raise AttributeError("broken method descriptor") + + +# Helper for testing classify_class_attrs. +def attrs_wo_objs(cls): + return [t[:3] for t in inspect.classify_class_attrs(cls)] + + +class TestClassesAndFunctions(unittest.TestCase): + def test_newstyle_mro(self): + # The same w/ new-class MRO. + class A(object): pass + class B(A): pass + class C(A): pass + class D(B, C): pass + + expected = (D, B, C, A, object) + got = inspect.getmro(D) + self.assertEqual(expected, got) + + def assertFullArgSpecEquals(self, routine, args_e, varargs_e=None, + varkw_e=None, defaults_e=None, + posonlyargs_e=[], kwonlyargs_e=[], + kwonlydefaults_e=None, + ann_e={}): + args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \ + inspect.getfullargspec(routine) + self.assertEqual(args, args_e) + self.assertEqual(varargs, varargs_e) + self.assertEqual(varkw, varkw_e) + self.assertEqual(defaults, defaults_e) + self.assertEqual(kwonlyargs, kwonlyargs_e) + self.assertEqual(kwonlydefaults, kwonlydefaults_e) + self.assertEqual(ann, ann_e) + + def test_getfullargspec(self): + self.assertFullArgSpecEquals(mod2.keyworded, [], varargs_e='arg1', + kwonlyargs_e=['arg2'], + kwonlydefaults_e={'arg2':1}) + + self.assertFullArgSpecEquals(mod2.annotated, ['arg1'], + ann_e={'arg1' : list}) + self.assertFullArgSpecEquals(mod2.keyword_only_arg, [], + kwonlyargs_e=['arg']) + + self.assertFullArgSpecEquals(mod2.all_markers, ['a', 'b', 'c', 'd'], + kwonlyargs_e=['e', 'f']) + + self.assertFullArgSpecEquals(mod2.all_markers_with_args_and_kwargs, + ['a', 'b', 'c', 'd'], + varargs_e='args', + varkw_e='kwargs', + kwonlyargs_e=['e', 'f']) + + self.assertFullArgSpecEquals(mod2.all_markers_with_defaults, ['a', 'b', 'c', 'd'], + defaults_e=(1,2,3), + kwonlyargs_e=['e', 'f'], + kwonlydefaults_e={'e': 4, 'f': 5}) + + def test_argspec_api_ignores_wrapped(self): + # Issue 20684: low level introspection API must ignore __wrapped__ + @functools.wraps(mod.spam) + def ham(x, y): + pass + # Basic check + self.assertFullArgSpecEquals(ham, ['x', 'y']) + self.assertFullArgSpecEquals(functools.partial(ham), + ['x', 'y']) + + def test_getfullargspec_signature_attr(self): + def test(): + pass + spam_param = inspect.Parameter('spam', inspect.Parameter.POSITIONAL_ONLY) + test.__signature__ = inspect.Signature(parameters=(spam_param,)) + + self.assertFullArgSpecEquals(test, ['spam']) + + def test_getfullargspec_signature_annos(self): + def test(a:'spam') -> 'ham': pass + spec = inspect.getfullargspec(test) + self.assertEqual(test.__annotations__, spec.annotations) + + def test(): pass + spec = inspect.getfullargspec(test) + self.assertEqual(test.__annotations__, spec.annotations) + + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_getfullargspec_builtin_methods(self): + self.assertFullArgSpecEquals(_pickle.Pickler.dump, ['self', 'obj']) + + self.assertFullArgSpecEquals(_pickle.Pickler(io.BytesIO()).dump, ['self', 'obj']) + + self.assertFullArgSpecEquals( + os.stat, + args_e=['path'], + kwonlyargs_e=['dir_fd', 'follow_symlinks'], + kwonlydefaults_e={'dir_fd': None, 'follow_symlinks': True}) + + @cpython_only + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_getfullargspec_builtin_func(self): + _testcapi = import_helper.import_module("_testcapi") + builtin = _testcapi.docstring_with_signature_with_defaults + spec = inspect.getfullargspec(builtin) + self.assertEqual(spec.defaults[0], 'avocado') + + @cpython_only + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_getfullargspec_builtin_func_no_signature(self): + _testcapi = import_helper.import_module("_testcapi") + builtin = _testcapi.docstring_no_signature + with self.assertRaises(TypeError): + inspect.getfullargspec(builtin) + + cls = _testcapi.DocStringNoSignatureTest + obj = _testcapi.DocStringNoSignatureTest() + tests = [ + (_testcapi.docstring_no_signature_noargs, meth_noargs), + (_testcapi.docstring_no_signature_o, meth_o), + (cls.meth_noargs, meth_self_noargs), + (cls.meth_o, meth_self_o), + (obj.meth_noargs, meth_self_noargs), + (obj.meth_o, meth_self_o), + (cls.meth_noargs_class, meth_type_noargs), + (cls.meth_o_class, meth_type_o), + (cls.meth_noargs_static, meth_noargs), + (cls.meth_o_static, meth_o), + (cls.meth_noargs_coexist, meth_self_noargs), + (cls.meth_o_coexist, meth_self_o), + + (time.time, meth_noargs), + (str.lower, meth_self_noargs), + (''.lower, meth_self_noargs), + (set.add, meth_self_o), + (set().add, meth_self_o), + (set.__contains__, meth_self_o), + (set().__contains__, meth_self_o), + (datetime.datetime.__dict__['utcnow'], meth_type_noargs), + (datetime.datetime.utcnow, meth_type_noargs), + (dict.__dict__['__class_getitem__'], meth_type_o), + (dict.__class_getitem__, meth_type_o), + ] + try: + import _stat + except ImportError: + # if the _stat extension is not available, stat.S_IMODE() is + # implemented in Python, not in C + pass + else: + tests.append((stat.S_IMODE, meth_o)) + for builtin, template in tests: + with self.subTest(builtin): + self.assertEqual(inspect.getfullargspec(builtin), + inspect.getfullargspec(template)) + + def test_getfullargspec_definition_order_preserved_on_kwonly(self): + for fn in signatures_with_lexicographic_keyword_only_parameters(): + signature = inspect.getfullargspec(fn) + l = list(signature.kwonlyargs) + sorted_l = sorted(l) + self.assertTrue(l) + self.assertEqual(l, sorted_l) + signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn) + l = list(signature.kwonlyargs) + self.assertEqual(l, unsorted_keyword_only_parameters) + + def test_classify_newstyle(self): + class A(object): + + def s(): pass + s = staticmethod(s) + + def c(cls): pass + c = classmethod(c) + + def getp(self): pass + p = property(getp) + + def m(self): pass + + def m1(self): pass + + datablob = '1' + + dd = _BrokenDataDescriptor() + md = _BrokenMethodDescriptor() + + attrs = attrs_wo_objs(A) + + self.assertIn(('__new__', 'static method', object), attrs, + 'missing __new__') + self.assertIn(('__init__', 'method', object), attrs, 'missing __init__') + + self.assertIn(('s', 'static method', A), attrs, 'missing static method') + self.assertIn(('c', 'class method', A), attrs, 'missing class method') + self.assertIn(('p', 'property', A), attrs, 'missing property') + self.assertIn(('m', 'method', A), attrs, + 'missing plain method: %r' % attrs) + self.assertIn(('m1', 'method', A), attrs, 'missing plain method') + self.assertIn(('datablob', 'data', A), attrs, 'missing data') + self.assertIn(('md', 'method', A), attrs, 'missing method descriptor') + self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor') + + class B(A): + + def m(self): pass + + attrs = attrs_wo_objs(B) + self.assertIn(('s', 'static method', A), attrs, 'missing static method') + self.assertIn(('c', 'class method', A), attrs, 'missing class method') + self.assertIn(('p', 'property', A), attrs, 'missing property') + self.assertIn(('m', 'method', B), attrs, 'missing plain method') + self.assertIn(('m1', 'method', A), attrs, 'missing plain method') + self.assertIn(('datablob', 'data', A), attrs, 'missing data') + self.assertIn(('md', 'method', A), attrs, 'missing method descriptor') + self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor') + + + class C(A): + + def m(self): pass + def c(self): pass + + attrs = attrs_wo_objs(C) + self.assertIn(('s', 'static method', A), attrs, 'missing static method') + self.assertIn(('c', 'method', C), attrs, 'missing plain method') + self.assertIn(('p', 'property', A), attrs, 'missing property') + self.assertIn(('m', 'method', C), attrs, 'missing plain method') + self.assertIn(('m1', 'method', A), attrs, 'missing plain method') + self.assertIn(('datablob', 'data', A), attrs, 'missing data') + self.assertIn(('md', 'method', A), attrs, 'missing method descriptor') + self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor') + + class D(B, C): + + def m1(self): pass + + attrs = attrs_wo_objs(D) + self.assertIn(('s', 'static method', A), attrs, 'missing static method') + self.assertIn(('c', 'method', C), attrs, 'missing plain method') + self.assertIn(('p', 'property', A), attrs, 'missing property') + self.assertIn(('m', 'method', B), attrs, 'missing plain method') + self.assertIn(('m1', 'method', D), attrs, 'missing plain method') + self.assertIn(('datablob', 'data', A), attrs, 'missing data') + self.assertIn(('md', 'method', A), attrs, 'missing method descriptor') + self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor') + + def test_classify_builtin_types(self): + # Simple sanity check that all built-in types can have their + # attributes classified. + for name in dir(__builtins__): + builtin = getattr(__builtins__, name) + if isinstance(builtin, type): + inspect.classify_class_attrs(builtin) + + attrs = attrs_wo_objs(bool) + self.assertIn(('__new__', 'static method', bool), attrs, + 'missing __new__') + self.assertIn(('from_bytes', 'class method', int), attrs, + 'missing class method') + self.assertIn(('to_bytes', 'method', int), attrs, + 'missing plain method') + self.assertIn(('__add__', 'method', int), attrs, + 'missing plain method') + self.assertIn(('__and__', 'method', bool), attrs, + 'missing plain method') + + def test_classify_DynamicClassAttribute(self): + class Meta(type): + def __getattr__(self, name): + if name == 'ham': + return 'spam' + return super().__getattr__(name) + class VA(metaclass=Meta): + @types.DynamicClassAttribute + def ham(self): + return 'eggs' + should_find_dca = inspect.Attribute('ham', 'data', VA, VA.__dict__['ham']) + self.assertIn(should_find_dca, inspect.classify_class_attrs(VA)) + should_find_ga = inspect.Attribute('ham', 'data', Meta, 'spam') + self.assertIn(should_find_ga, inspect.classify_class_attrs(VA)) + + def test_classify_overrides_bool(self): + class NoBool(object): + def __eq__(self, other): + return NoBool() + + def __bool__(self): + raise NotImplementedError( + "This object does not specify a boolean value") + + class HasNB(object): + dd = NoBool() + + should_find_attr = inspect.Attribute('dd', 'data', HasNB, HasNB.dd) + self.assertIn(should_find_attr, inspect.classify_class_attrs(HasNB)) + + def test_classify_metaclass_class_attribute(self): + class Meta(type): + fish = 'slap' + def __dir__(self): + return ['__class__', '__module__', '__name__', 'fish'] + class Class(metaclass=Meta): + pass + should_find = inspect.Attribute('fish', 'data', Meta, 'slap') + self.assertIn(should_find, inspect.classify_class_attrs(Class)) + + def test_classify_VirtualAttribute(self): + class Meta(type): + def __dir__(cls): + return ['__class__', '__module__', '__name__', 'BOOM'] + def __getattr__(self, name): + if name =='BOOM': + return 42 + return super().__getattr(name) + class Class(metaclass=Meta): + pass + should_find = inspect.Attribute('BOOM', 'data', Meta, 42) + self.assertIn(should_find, inspect.classify_class_attrs(Class)) + + def test_classify_VirtualAttribute_multi_classes(self): + class Meta1(type): + def __dir__(cls): + return ['__class__', '__module__', '__name__', 'one'] + def __getattr__(self, name): + if name =='one': + return 1 + return super().__getattr__(name) + class Meta2(type): + def __dir__(cls): + return ['__class__', '__module__', '__name__', 'two'] + def __getattr__(self, name): + if name =='two': + return 2 + return super().__getattr__(name) + class Meta3(Meta1, Meta2): + def __dir__(cls): + return list(sorted(set(['__class__', '__module__', '__name__', 'three'] + + Meta1.__dir__(cls) + Meta2.__dir__(cls)))) + def __getattr__(self, name): + if name =='three': + return 3 + return super().__getattr__(name) + class Class1(metaclass=Meta1): + pass + class Class2(Class1, metaclass=Meta3): + pass + + should_find1 = inspect.Attribute('one', 'data', Meta1, 1) + should_find2 = inspect.Attribute('two', 'data', Meta2, 2) + should_find3 = inspect.Attribute('three', 'data', Meta3, 3) + cca = inspect.classify_class_attrs(Class2) + for sf in (should_find1, should_find2, should_find3): + self.assertIn(sf, cca) + + def test_classify_class_attrs_with_buggy_dir(self): + class M(type): + def __dir__(cls): + return ['__class__', '__name__', 'missing'] + class C(metaclass=M): + pass + attrs = [a[0] for a in inspect.classify_class_attrs(C)] + self.assertNotIn('missing', attrs) + + def test_getmembers_descriptors(self): + class A(object): + dd = _BrokenDataDescriptor() + md = _BrokenMethodDescriptor() + + def pred_wrapper(pred): + # A quick'n'dirty way to discard standard attributes of new-style + # classes. + class Empty(object): + pass + def wrapped(x): + if '__name__' in dir(x) and hasattr(Empty, x.__name__): + return False + return pred(x) + return wrapped + + ismethoddescriptor = pred_wrapper(inspect.ismethoddescriptor) + isdatadescriptor = pred_wrapper(inspect.isdatadescriptor) + + self.assertEqual(inspect.getmembers(A, ismethoddescriptor), + [('md', A.__dict__['md'])]) + self.assertEqual(inspect.getmembers(A, isdatadescriptor), + [('dd', A.__dict__['dd'])]) + + class B(A): + pass + + self.assertEqual(inspect.getmembers(B, ismethoddescriptor), + [('md', A.__dict__['md'])]) + self.assertEqual(inspect.getmembers(B, isdatadescriptor), + [('dd', A.__dict__['dd'])]) + + def test_getmembers_method(self): + class B: + def f(self): + pass + + self.assertIn(('f', B.f), inspect.getmembers(B)) + self.assertNotIn(('f', B.f), inspect.getmembers(B, inspect.ismethod)) + b = B() + self.assertIn(('f', b.f), inspect.getmembers(b)) + self.assertIn(('f', b.f), inspect.getmembers(b, inspect.ismethod)) + + def test_getmembers_custom_dir(self): + class CorrectDir: + def __init__(self, attr): + self.attr = attr + def method(self): + return self.attr + 1 + def __dir__(self): + return ['attr', 'method'] + + cd = CorrectDir(5) + self.assertEqual(inspect.getmembers(cd), [ + ('attr', 5), + ('method', cd.method), + ]) + self.assertEqual(inspect.getmembers(cd, inspect.ismethod), [ + ('method', cd.method), + ]) + + def test_getmembers_custom_broken_dir(self): + # inspect.getmembers calls `dir()` on the passed object inside. + # if `__dir__` mentions some non-existent attribute, + # we still need to return others correctly. + class BrokenDir: + existing = 1 + def method(self): + return self.existing + 1 + def __dir__(self): + return ['method', 'missing', 'existing'] + + bd = BrokenDir() + self.assertEqual(inspect.getmembers(bd), [ + ('existing', 1), + ('method', bd.method), + ]) + self.assertEqual(inspect.getmembers(bd, inspect.ismethod), [ + ('method', bd.method), + ]) + + def test_getmembers_custom_duplicated_dir(self): + # Duplicates in `__dir__` must not fail and return just one result. + class DuplicatedDir: + attr = 1 + def __dir__(self): + return ['attr', 'attr'] + + dd = DuplicatedDir() + self.assertEqual(inspect.getmembers(dd), [ + ('attr', 1), + ]) + + def test_getmembers_VirtualAttribute(self): + class M(type): + def __getattr__(cls, name): + if name == 'eggs': + return 'scrambled' + return super().__getattr__(name) + class A(metaclass=M): + @types.DynamicClassAttribute + def eggs(self): + return 'spam' + class B: + def __getattr__(self, attribute): + return None + self.assertIn(('eggs', 'scrambled'), inspect.getmembers(A)) + self.assertIn(('eggs', 'spam'), inspect.getmembers(A())) + b = B() + self.assertIn(('__getattr__', b.__getattr__), inspect.getmembers(b)) + + def test_getmembers_static(self): + class A: + @property + def name(self): + raise NotImplementedError + @types.DynamicClassAttribute + def eggs(self): + raise NotImplementedError + + a = A() + instance_members = inspect.getmembers_static(a) + class_members = inspect.getmembers_static(A) + self.assertIn(('name', inspect.getattr_static(a, 'name')), instance_members) + self.assertIn(('eggs', inspect.getattr_static(a, 'eggs')), instance_members) + self.assertIn(('name', inspect.getattr_static(A, 'name')), class_members) + self.assertIn(('eggs', inspect.getattr_static(A, 'eggs')), class_members) + + def test_getmembers_with_buggy_dir(self): + class M(type): + def __dir__(cls): + return ['__class__', '__name__', 'missing'] + class C(metaclass=M): + pass + attrs = [a[0] for a in inspect.getmembers(C)] + self.assertNotIn('missing', attrs) + + def test_get_annotations_with_stock_annotations(self): + def foo(a:int, b:str): pass + self.assertEqual(inspect.get_annotations(foo), {'a': int, 'b': str}) + + foo.__annotations__ = {'a': 'foo', 'b':'str'} + self.assertEqual(inspect.get_annotations(foo), {'a': 'foo', 'b': 'str'}) + + self.assertEqual(inspect.get_annotations(foo, eval_str=True, locals=locals()), {'a': foo, 'b': str}) + self.assertEqual(inspect.get_annotations(foo, eval_str=True, globals=locals()), {'a': foo, 'b': str}) + + isa = inspect_stock_annotations + self.assertEqual(inspect.get_annotations(isa), {'a': int, 'b': str}) + self.assertEqual(inspect.get_annotations(isa.MyClass), {'a': int, 'b': str}) + self.assertEqual(inspect.get_annotations(isa.function), {'a': int, 'b': str, 'return': isa.MyClass}) + self.assertEqual(inspect.get_annotations(isa.function2), {'a': int, 'b': 'str', 'c': isa.MyClass, 'return': isa.MyClass}) + self.assertEqual(inspect.get_annotations(isa.function3), {'a': 'int', 'b': 'str', 'c': 'MyClass'}) + self.assertEqual(inspect.get_annotations(inspect), {}) # inspect module has no annotations + self.assertEqual(inspect.get_annotations(isa.UnannotatedClass), {}) + self.assertEqual(inspect.get_annotations(isa.unannotated_function), {}) + + self.assertEqual(inspect.get_annotations(isa, eval_str=True), {'a': int, 'b': str}) + self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=True), {'a': int, 'b': str}) + self.assertEqual(inspect.get_annotations(isa.function, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass}) + self.assertEqual(inspect.get_annotations(isa.function2, eval_str=True), {'a': int, 'b': str, 'c': isa.MyClass, 'return': isa.MyClass}) + self.assertEqual(inspect.get_annotations(isa.function3, eval_str=True), {'a': int, 'b': str, 'c': isa.MyClass}) + self.assertEqual(inspect.get_annotations(inspect, eval_str=True), {}) + self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=True), {}) + self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=True), {}) + + self.assertEqual(inspect.get_annotations(isa, eval_str=False), {'a': int, 'b': str}) + self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=False), {'a': int, 'b': str}) + self.assertEqual(inspect.get_annotations(isa.function, eval_str=False), {'a': int, 'b': str, 'return': isa.MyClass}) + self.assertEqual(inspect.get_annotations(isa.function2, eval_str=False), {'a': int, 'b': 'str', 'c': isa.MyClass, 'return': isa.MyClass}) + self.assertEqual(inspect.get_annotations(isa.function3, eval_str=False), {'a': 'int', 'b': 'str', 'c': 'MyClass'}) + self.assertEqual(inspect.get_annotations(inspect, eval_str=False), {}) + self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=False), {}) + self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=False), {}) + + def times_three(fn): + @functools.wraps(fn) + def wrapper(a, b): + return fn(a*3, b*3) + return wrapper + + wrapped = times_three(isa.function) + self.assertEqual(wrapped(1, 'x'), isa.MyClass(3, 'xxx')) + self.assertIsNot(wrapped.__globals__, isa.function.__globals__) + self.assertEqual(inspect.get_annotations(wrapped), {'a': int, 'b': str, 'return': isa.MyClass}) + self.assertEqual(inspect.get_annotations(wrapped, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass}) + self.assertEqual(inspect.get_annotations(wrapped, eval_str=False), {'a': int, 'b': str, 'return': isa.MyClass}) + + def test_get_annotations_with_stringized_annotations(self): + isa = inspect_stringized_annotations + self.assertEqual(inspect.get_annotations(isa), {'a': 'int', 'b': 'str'}) + self.assertEqual(inspect.get_annotations(isa.MyClass), {'a': 'int', 'b': 'str'}) + self.assertEqual(inspect.get_annotations(isa.function), {'a': 'int', 'b': 'str', 'return': 'MyClass'}) + self.assertEqual(inspect.get_annotations(isa.function2), {'a': 'int', 'b': "'str'", 'c': 'MyClass', 'return': 'MyClass'}) + self.assertEqual(inspect.get_annotations(isa.function3), {'a': "'int'", 'b': "'str'", 'c': "'MyClass'"}) + self.assertEqual(inspect.get_annotations(isa.UnannotatedClass), {}) + self.assertEqual(inspect.get_annotations(isa.unannotated_function), {}) + + self.assertEqual(inspect.get_annotations(isa, eval_str=True), {'a': int, 'b': str}) + self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=True), {'a': int, 'b': str}) + self.assertEqual(inspect.get_annotations(isa.function, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass}) + self.assertEqual(inspect.get_annotations(isa.function2, eval_str=True), {'a': int, 'b': 'str', 'c': isa.MyClass, 'return': isa.MyClass}) + self.assertEqual(inspect.get_annotations(isa.function3, eval_str=True), {'a': 'int', 'b': 'str', 'c': 'MyClass'}) + self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=True), {}) + self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=True), {}) + + self.assertEqual(inspect.get_annotations(isa, eval_str=False), {'a': 'int', 'b': 'str'}) + self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=False), {'a': 'int', 'b': 'str'}) + self.assertEqual(inspect.get_annotations(isa.function, eval_str=False), {'a': 'int', 'b': 'str', 'return': 'MyClass'}) + self.assertEqual(inspect.get_annotations(isa.function2, eval_str=False), {'a': 'int', 'b': "'str'", 'c': 'MyClass', 'return': 'MyClass'}) + self.assertEqual(inspect.get_annotations(isa.function3, eval_str=False), {'a': "'int'", 'b': "'str'", 'c': "'MyClass'"}) + self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=False), {}) + self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=False), {}) + + isa2 = inspect_stringized_annotations_2 + self.assertEqual(inspect.get_annotations(isa2), {}) + self.assertEqual(inspect.get_annotations(isa2, eval_str=True), {}) + self.assertEqual(inspect.get_annotations(isa2, eval_str=False), {}) + + def times_three(fn): + @functools.wraps(fn) + def wrapper(a, b): + return fn(a*3, b*3) + return wrapper + + wrapped = times_three(isa.function) + self.assertEqual(wrapped(1, 'x'), isa.MyClass(3, 'xxx')) + self.assertIsNot(wrapped.__globals__, isa.function.__globals__) + self.assertEqual(inspect.get_annotations(wrapped), {'a': 'int', 'b': 'str', 'return': 'MyClass'}) + self.assertEqual(inspect.get_annotations(wrapped, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass}) + self.assertEqual(inspect.get_annotations(wrapped, eval_str=False), {'a': 'int', 'b': 'str', 'return': 'MyClass'}) + + # test that local namespace lookups work + self.assertEqual(inspect.get_annotations(isa.MyClassWithLocalAnnotations), {'x': 'mytype'}) + self.assertEqual(inspect.get_annotations(isa.MyClassWithLocalAnnotations, eval_str=True), {'x': int}) + + def test_pep695_generic_class_with_future_annotations(self): + ann_module695 = inspect_stringized_annotations_pep695 + A_annotations = inspect.get_annotations(ann_module695.A, eval_str=True) + A_type_params = ann_module695.A.__type_params__ + self.assertIs(A_annotations["x"], A_type_params[0]) + self.assertEqual(A_annotations["y"].__args__[0], Unpack[A_type_params[1]]) + self.assertIs(A_annotations["z"].__args__[0], A_type_params[2]) + + def test_pep695_generic_class_with_future_annotations_and_local_shadowing(self): + B_annotations = inspect.get_annotations( + inspect_stringized_annotations_pep695.B, eval_str=True + ) + self.assertEqual(B_annotations, {"x": int, "y": str, "z": bytes}) + + def test_pep695_generic_class_with_future_annotations_name_clash_with_global_vars(self): + ann_module695 = inspect_stringized_annotations_pep695 + C_annotations = inspect.get_annotations(ann_module695.C, eval_str=True) + self.assertEqual( + set(C_annotations.values()), + set(ann_module695.C.__type_params__) + ) + + def test_pep_695_generic_function_with_future_annotations(self): + ann_module695 = inspect_stringized_annotations_pep695 + generic_func_annotations = inspect.get_annotations( + ann_module695.generic_function, eval_str=True + ) + func_t_params = ann_module695.generic_function.__type_params__ + self.assertEqual( + generic_func_annotations.keys(), {"x", "y", "z", "zz", "return"} + ) + self.assertIs(generic_func_annotations["x"], func_t_params[0]) + self.assertEqual(generic_func_annotations["y"], Unpack[func_t_params[1]]) + self.assertIs(generic_func_annotations["z"].__origin__, func_t_params[2]) + self.assertIs(generic_func_annotations["zz"].__origin__, func_t_params[2]) + + def test_pep_695_generic_function_with_future_annotations_name_clash_with_global_vars(self): + self.assertEqual( + set( + inspect.get_annotations( + inspect_stringized_annotations_pep695.generic_function_2, + eval_str=True + ).values() + ), + set( + inspect_stringized_annotations_pep695.generic_function_2.__type_params__ + ) + ) + + def test_pep_695_generic_method_with_future_annotations(self): + ann_module695 = inspect_stringized_annotations_pep695 + generic_method_annotations = inspect.get_annotations( + ann_module695.D.generic_method, eval_str=True + ) + params = { + param.__name__: param + for param in ann_module695.D.generic_method.__type_params__ + } + self.assertEqual( + generic_method_annotations, + {"x": params["Foo"], "y": params["Bar"], "return": None} + ) + + def test_pep_695_generic_method_with_future_annotations_name_clash_with_global_vars(self): + self.assertEqual( + set( + inspect.get_annotations( + inspect_stringized_annotations_pep695.D.generic_method_2, + eval_str=True + ).values() + ), + set( + inspect_stringized_annotations_pep695.D.generic_method_2.__type_params__ + ) + ) + + def test_pep_695_generic_method_with_future_annotations_name_clash_with_global_and_local_vars(self): + self.assertEqual( + inspect.get_annotations( + inspect_stringized_annotations_pep695.E, eval_str=True + ), + {"x": str}, + ) + + def test_pep_695_generics_with_future_annotations_nested_in_function(self): + results = inspect_stringized_annotations_pep695.nested() + + self.assertEqual( + set(results.F_annotations.values()), + set(results.F.__type_params__) + ) + self.assertEqual( + set(results.F_meth_annotations.values()), + set(results.F.generic_method.__type_params__) + ) + self.assertNotEqual( + set(results.F_meth_annotations.values()), + set(results.F.__type_params__) + ) + self.assertEqual( + set(results.F_meth_annotations.values()).intersection(results.F.__type_params__), + set() + ) + + self.assertEqual(results.G_annotations, {"x": str}) + + self.assertEqual( + set(results.generic_func_annotations.values()), + set(results.generic_func.__type_params__) + ) + + +class TestFormatAnnotation(unittest.TestCase): + def test_typing_replacement(self): + from test.typinganndata.ann_module9 import A, ann, ann1 + self.assertEqual(inspect.formatannotation(ann), 'Union[List[str], int]') + self.assertEqual(inspect.formatannotation(ann1), 'Union[List[testModule.typing.A], int]') + + self.assertEqual(inspect.formatannotation(A, 'testModule.typing'), 'A') + self.assertEqual(inspect.formatannotation(A, 'other'), 'testModule.typing.A') + self.assertEqual( + inspect.formatannotation(ann1, 'testModule.typing'), + 'Union[List[testModule.typing.A], int]', + ) + + def test_formatannotationrelativeto(self): + from test.typinganndata.ann_module9 import A, ann1 + + # Builtin types: + self.assertEqual( + inspect.formatannotationrelativeto(object)(type), + 'type', + ) + + # Custom types: + self.assertEqual( + inspect.formatannotationrelativeto(None)(A), + 'testModule.typing.A', + ) + + class B: ... + B.__module__ = 'testModule.typing' + + self.assertEqual( + inspect.formatannotationrelativeto(B)(A), + 'A', + ) + + self.assertEqual( + inspect.formatannotationrelativeto(object)(A), + 'testModule.typing.A', + ) + + # Not an instance of "type": + self.assertEqual( + inspect.formatannotationrelativeto(A)(ann1), + 'Union[List[testModule.typing.A], int]', + ) + + +class TestIsMethodDescriptor(unittest.TestCase): + + def test_custom_descriptors(self): + class MethodDescriptor: + def __get__(self, *_): pass + class MethodDescriptorSub(MethodDescriptor): + pass + class DataDescriptorWithNoGet: + def __set__(self, *_): pass + class DataDescriptorWithGetSet: + def __get__(self, *_): pass + def __set__(self, *_): pass + class DataDescriptorWithGetDelete: + def __get__(self, *_): pass + def __delete__(self, *_): pass + class DataDescriptorSub(DataDescriptorWithNoGet, + DataDescriptorWithGetDelete): + pass + + # Custom method descriptors: + self.assertTrue( + inspect.ismethoddescriptor(MethodDescriptor()), + '__get__ and no __set__/__delete__ => method descriptor') + self.assertTrue( + inspect.ismethoddescriptor(MethodDescriptorSub()), + '__get__ (inherited) and no __set__/__delete__' + ' => method descriptor') + + # Custom data descriptors: + self.assertFalse( + inspect.ismethoddescriptor(DataDescriptorWithNoGet()), + '__set__ (and no __get__) => not a method descriptor') + self.assertFalse( + inspect.ismethoddescriptor(DataDescriptorWithGetSet()), + '__get__ and __set__ => not a method descriptor') + self.assertFalse( + inspect.ismethoddescriptor(DataDescriptorWithGetDelete()), + '__get__ and __delete__ => not a method descriptor') + self.assertFalse( + inspect.ismethoddescriptor(DataDescriptorSub()), + '__get__, __set__ and __delete__ => not a method descriptor') + + # Classes of descriptors (are *not* descriptors themselves): + self.assertFalse(inspect.ismethoddescriptor(MethodDescriptor)) + self.assertFalse(inspect.ismethoddescriptor(MethodDescriptorSub)) + self.assertFalse(inspect.ismethoddescriptor(DataDescriptorSub)) + + def test_builtin_descriptors(self): + builtin_slot_wrapper = int.__add__ # This one is mentioned in docs. + class Owner: + def instance_method(self): pass + @classmethod + def class_method(cls): pass + @staticmethod + def static_method(): pass + @property + def a_property(self): pass + class Slotermeyer: + __slots__ = 'a_slot', + def function(): + pass + a_lambda = lambda: None + + # Example builtin method descriptors: + self.assertTrue( + inspect.ismethoddescriptor(builtin_slot_wrapper), + 'a builtin slot wrapper is a method descriptor') + self.assertTrue( + inspect.ismethoddescriptor(Owner.__dict__['class_method']), + 'a classmethod object is a method descriptor') + self.assertTrue( + inspect.ismethoddescriptor(Owner.__dict__['static_method']), + 'a staticmethod object is a method descriptor') + + # Example builtin data descriptors: + self.assertFalse( + inspect.ismethoddescriptor(Owner.__dict__['a_property']), + 'a property is not a method descriptor') + self.assertFalse( + inspect.ismethoddescriptor(Slotermeyer.__dict__['a_slot']), + 'a slot is not a method descriptor') + + # `types.MethodType`/`types.FunctionType` instances (they *are* + # method descriptors, but `ismethoddescriptor()` explicitly + # excludes them): + self.assertFalse(inspect.ismethoddescriptor(Owner().instance_method)) + self.assertFalse(inspect.ismethoddescriptor(Owner().class_method)) + self.assertFalse(inspect.ismethoddescriptor(Owner().static_method)) + self.assertFalse(inspect.ismethoddescriptor(Owner.instance_method)) + self.assertFalse(inspect.ismethoddescriptor(Owner.class_method)) + self.assertFalse(inspect.ismethoddescriptor(Owner.static_method)) + self.assertFalse(inspect.ismethoddescriptor(function)) + self.assertFalse(inspect.ismethoddescriptor(a_lambda)) + self.assertFalse(inspect.ismethoddescriptor(functools.partial(function))) + + def test_descriptor_being_a_class(self): + class MethodDescriptorMeta(type): + def __get__(self, *_): pass + class ClassBeingMethodDescriptor(metaclass=MethodDescriptorMeta): + pass + # `ClassBeingMethodDescriptor` itself *is* a method descriptor, + # but it is *also* a class, and `ismethoddescriptor()` explicitly + # excludes classes. + self.assertFalse( + inspect.ismethoddescriptor(ClassBeingMethodDescriptor), + 'classes (instances of type) are explicitly excluded') + + def test_non_descriptors(self): + class Test: + pass + self.assertFalse(inspect.ismethoddescriptor(Test())) + self.assertFalse(inspect.ismethoddescriptor(Test)) + self.assertFalse(inspect.ismethoddescriptor([42])) + self.assertFalse(inspect.ismethoddescriptor(42)) + + +class TestIsDataDescriptor(unittest.TestCase): + + def test_custom_descriptors(self): + class NonDataDescriptor: + def __get__(self, value, type=None): pass + class DataDescriptor0: + def __set__(self, name, value): pass + class DataDescriptor1: + def __delete__(self, name): pass + class DataDescriptor2: + __set__ = None + self.assertFalse(inspect.isdatadescriptor(NonDataDescriptor()), + 'class with only __get__ not a data descriptor') + self.assertTrue(inspect.isdatadescriptor(DataDescriptor0()), + 'class with __set__ is a data descriptor') + self.assertTrue(inspect.isdatadescriptor(DataDescriptor1()), + 'class with __delete__ is a data descriptor') + self.assertTrue(inspect.isdatadescriptor(DataDescriptor2()), + 'class with __set__ = None is a data descriptor') + + def test_slot(self): + class Slotted: + __slots__ = 'foo', + self.assertTrue(inspect.isdatadescriptor(Slotted.foo), + 'a slot is a data descriptor') + + def test_property(self): + class Propertied: + @property + def a_property(self): + pass + self.assertTrue(inspect.isdatadescriptor(Propertied.a_property), + 'a property is a data descriptor') + + def test_functions(self): + class Test(object): + def instance_method(self): pass + @classmethod + def class_method(cls): pass + @staticmethod + def static_method(): pass + def function(): + pass + a_lambda = lambda: None + self.assertFalse(inspect.isdatadescriptor(Test().instance_method), + 'a instance method is not a data descriptor') + self.assertFalse(inspect.isdatadescriptor(Test().class_method), + 'a class method is not a data descriptor') + self.assertFalse(inspect.isdatadescriptor(Test().static_method), + 'a static method is not a data descriptor') + self.assertFalse(inspect.isdatadescriptor(function), + 'a function is not a data descriptor') + self.assertFalse(inspect.isdatadescriptor(a_lambda), + 'a lambda is not a data descriptor') + + +_global_ref = object() +class TestGetClosureVars(unittest.TestCase): + + def test_name_resolution(self): + # Basic test of the 4 different resolution mechanisms + def f(nonlocal_ref): + def g(local_ref): + print(local_ref, nonlocal_ref, _global_ref, unbound_ref) + return g + _arg = object() + nonlocal_vars = {"nonlocal_ref": _arg} + global_vars = {"_global_ref": _global_ref} + builtin_vars = {"print": print} + unbound_names = {"unbound_ref"} + expected = inspect.ClosureVars(nonlocal_vars, global_vars, + builtin_vars, unbound_names) + self.assertEqual(inspect.getclosurevars(f(_arg)), expected) + + def test_generator_closure(self): + def f(nonlocal_ref): + def g(local_ref): + print(local_ref, nonlocal_ref, _global_ref, unbound_ref) + yield + return g + _arg = object() + nonlocal_vars = {"nonlocal_ref": _arg} + global_vars = {"_global_ref": _global_ref} + builtin_vars = {"print": print} + unbound_names = {"unbound_ref"} + expected = inspect.ClosureVars(nonlocal_vars, global_vars, + builtin_vars, unbound_names) + self.assertEqual(inspect.getclosurevars(f(_arg)), expected) + + def test_method_closure(self): + class C: + def f(self, nonlocal_ref): + def g(local_ref): + print(local_ref, nonlocal_ref, _global_ref, unbound_ref) + return g + _arg = object() + nonlocal_vars = {"nonlocal_ref": _arg} + global_vars = {"_global_ref": _global_ref} + builtin_vars = {"print": print} + unbound_names = {"unbound_ref"} + expected = inspect.ClosureVars(nonlocal_vars, global_vars, + builtin_vars, unbound_names) + self.assertEqual(inspect.getclosurevars(C().f(_arg)), expected) + + def test_attribute_same_name_as_global_var(self): + class C: + _global_ref = object() + def f(): + print(C._global_ref, _global_ref) + nonlocal_vars = {"C": C} + global_vars = {"_global_ref": _global_ref} + builtin_vars = {"print": print} + unbound_names = {"_global_ref"} + expected = inspect.ClosureVars(nonlocal_vars, global_vars, + builtin_vars, unbound_names) + self.assertEqual(inspect.getclosurevars(f), expected) + + def test_nonlocal_vars(self): + # More complex tests of nonlocal resolution + def _nonlocal_vars(f): + return inspect.getclosurevars(f).nonlocals + + def make_adder(x): + def add(y): + return x + y + return add + + def curry(func, arg1): + return lambda arg2: func(arg1, arg2) + + def less_than(a, b): + return a < b + + # The infamous Y combinator. + def Y(le): + def g(f): + return le(lambda x: f(f)(x)) + Y.g_ref = g + return g(g) + + def check_y_combinator(func): + self.assertEqual(_nonlocal_vars(func), {'f': Y.g_ref}) + + inc = make_adder(1) + add_two = make_adder(2) + greater_than_five = curry(less_than, 5) + + self.assertEqual(_nonlocal_vars(inc), {'x': 1}) + self.assertEqual(_nonlocal_vars(add_two), {'x': 2}) + self.assertEqual(_nonlocal_vars(greater_than_five), + {'arg1': 5, 'func': less_than}) + self.assertEqual(_nonlocal_vars((lambda x: lambda y: x + y)(3)), + {'x': 3}) + Y(check_y_combinator) + + def test_getclosurevars_empty(self): + def foo(): pass + _empty = inspect.ClosureVars({}, {}, {}, set()) + self.assertEqual(inspect.getclosurevars(lambda: True), _empty) + self.assertEqual(inspect.getclosurevars(foo), _empty) + + def test_getclosurevars_error(self): + class T: pass + self.assertRaises(TypeError, inspect.getclosurevars, 1) + self.assertRaises(TypeError, inspect.getclosurevars, list) + self.assertRaises(TypeError, inspect.getclosurevars, {}) + + def _private_globals(self): + code = """def f(): print(path)""" + ns = {} + exec(code, ns) + return ns["f"], ns + + def test_builtins_fallback(self): + f, ns = self._private_globals() + ns.pop("__builtins__", None) + expected = inspect.ClosureVars({}, {}, {"print":print}, {"path"}) + self.assertEqual(inspect.getclosurevars(f), expected) + + def test_builtins_as_dict(self): + f, ns = self._private_globals() + ns["__builtins__"] = {"path":1} + expected = inspect.ClosureVars({}, {}, {"path":1}, {"print"}) + self.assertEqual(inspect.getclosurevars(f), expected) + + def test_builtins_as_module(self): + f, ns = self._private_globals() + ns["__builtins__"] = os + expected = inspect.ClosureVars({}, {}, {"path":os.path}, {"print"}) + self.assertEqual(inspect.getclosurevars(f), expected) + + +class TestGetcallargsFunctions(unittest.TestCase): + + def assertEqualCallArgs(self, func, call_params_string, locs=None): + locs = dict(locs or {}, func=func) + r1 = eval('func(%s)' % call_params_string, None, locs) + r2 = eval('inspect.getcallargs(func, %s)' % call_params_string, None, + locs) + self.assertEqual(r1, r2) + + def assertEqualException(self, func, call_param_string, locs=None): + locs = dict(locs or {}, func=func) + try: + eval('func(%s)' % call_param_string, None, locs) + except Exception as e: + ex1 = e + else: + self.fail('Exception not raised') + try: + eval('inspect.getcallargs(func, %s)' % call_param_string, None, + locs) + except Exception as e: + ex2 = e + else: + self.fail('Exception not raised') + self.assertIs(type(ex1), type(ex2)) + self.assertEqual(str(ex1), str(ex2)) + del ex1, ex2 + + def makeCallable(self, signature): + """Create a function that returns its locals()""" + code = "lambda %s: locals()" + return eval(code % signature) + + def test_plain(self): + f = self.makeCallable('a, b=1') + self.assertEqualCallArgs(f, '2') + self.assertEqualCallArgs(f, '2, 3') + self.assertEqualCallArgs(f, 'a=2') + self.assertEqualCallArgs(f, 'b=3, a=2') + self.assertEqualCallArgs(f, '2, b=3') + # expand *iterable / **mapping + self.assertEqualCallArgs(f, '*(2,)') + self.assertEqualCallArgs(f, '*[2]') + self.assertEqualCallArgs(f, '*(2, 3)') + self.assertEqualCallArgs(f, '*[2, 3]') + self.assertEqualCallArgs(f, '**{"a":2}') + self.assertEqualCallArgs(f, 'b=3, **{"a":2}') + self.assertEqualCallArgs(f, '2, **{"b":3}') + self.assertEqualCallArgs(f, '**{"b":3, "a":2}') + # expand UserList / UserDict + self.assertEqualCallArgs(f, '*collections.UserList([2])') + self.assertEqualCallArgs(f, '*collections.UserList([2, 3])') + self.assertEqualCallArgs(f, '**collections.UserDict(a=2)') + self.assertEqualCallArgs(f, '2, **collections.UserDict(b=3)') + self.assertEqualCallArgs(f, 'b=2, **collections.UserDict(a=3)') + + def test_varargs(self): + f = self.makeCallable('a, b=1, *c') + self.assertEqualCallArgs(f, '2') + self.assertEqualCallArgs(f, '2, 3') + self.assertEqualCallArgs(f, '2, 3, 4') + self.assertEqualCallArgs(f, '*(2,3,4)') + self.assertEqualCallArgs(f, '2, *[3,4]') + self.assertEqualCallArgs(f, '2, 3, *collections.UserList([4])') + + def test_varkw(self): + f = self.makeCallable('a, b=1, **c') + self.assertEqualCallArgs(f, 'a=2') + self.assertEqualCallArgs(f, '2, b=3, c=4') + self.assertEqualCallArgs(f, 'b=3, a=2, c=4') + self.assertEqualCallArgs(f, 'c=4, **{"a":2, "b":3}') + self.assertEqualCallArgs(f, '2, c=4, **{"b":3}') + self.assertEqualCallArgs(f, 'b=2, **{"a":3, "c":4}') + self.assertEqualCallArgs(f, '**collections.UserDict(a=2, b=3, c=4)') + self.assertEqualCallArgs(f, '2, c=4, **collections.UserDict(b=3)') + self.assertEqualCallArgs(f, 'b=2, **collections.UserDict(a=3, c=4)') + + def test_varkw_only(self): + # issue11256: + f = self.makeCallable('**c') + self.assertEqualCallArgs(f, '') + self.assertEqualCallArgs(f, 'a=1') + self.assertEqualCallArgs(f, 'a=1, b=2') + self.assertEqualCallArgs(f, 'c=3, **{"a": 1, "b": 2}') + self.assertEqualCallArgs(f, '**collections.UserDict(a=1, b=2)') + self.assertEqualCallArgs(f, 'c=3, **collections.UserDict(a=1, b=2)') + + def test_keyword_only(self): + f = self.makeCallable('a=3, *, c, d=2') + self.assertEqualCallArgs(f, 'c=3') + self.assertEqualCallArgs(f, 'c=3, a=3') + self.assertEqualCallArgs(f, 'a=2, c=4') + self.assertEqualCallArgs(f, '4, c=4') + self.assertEqualException(f, '') + self.assertEqualException(f, '3') + self.assertEqualException(f, 'a=3') + self.assertEqualException(f, 'd=4') + + f = self.makeCallable('*, c, d=2') + self.assertEqualCallArgs(f, 'c=3') + self.assertEqualCallArgs(f, 'c=3, d=4') + self.assertEqualCallArgs(f, 'd=4, c=3') + + def test_multiple_features(self): + f = self.makeCallable('a, b=2, *f, **g') + self.assertEqualCallArgs(f, '2, 3, 7') + self.assertEqualCallArgs(f, '2, 3, x=8') + self.assertEqualCallArgs(f, '2, 3, x=8, *[(4,[5,6]), 7]') + self.assertEqualCallArgs(f, '2, x=8, *[3, (4,[5,6]), 7], y=9') + self.assertEqualCallArgs(f, 'x=8, *[2, 3, (4,[5,6])], y=9') + self.assertEqualCallArgs(f, 'x=8, *collections.UserList(' + '[2, 3, (4,[5,6])]), **{"y":9, "z":10}') + self.assertEqualCallArgs(f, '2, x=8, *collections.UserList([3, ' + '(4,[5,6])]), **collections.UserDict(' + 'y=9, z=10)') + + f = self.makeCallable('a, b=2, *f, x, y=99, **g') + self.assertEqualCallArgs(f, '2, 3, x=8') + self.assertEqualCallArgs(f, '2, 3, x=8, *[(4,[5,6]), 7]') + self.assertEqualCallArgs(f, '2, x=8, *[3, (4,[5,6]), 7], y=9, z=10') + self.assertEqualCallArgs(f, 'x=8, *[2, 3, (4,[5,6])], y=9, z=10') + self.assertEqualCallArgs(f, 'x=8, *collections.UserList(' + '[2, 3, (4,[5,6])]), q=0, **{"y":9, "z":10}') + self.assertEqualCallArgs(f, '2, x=8, *collections.UserList([3, ' + '(4,[5,6])]), q=0, **collections.UserDict(' + 'y=9, z=10)') + + def test_errors(self): + f0 = self.makeCallable('') + f1 = self.makeCallable('a, b') + f2 = self.makeCallable('a, b=1') + # f0 takes no arguments + self.assertEqualException(f0, '1') + self.assertEqualException(f0, 'x=1') + self.assertEqualException(f0, '1,x=1') + # f1 takes exactly 2 arguments + self.assertEqualException(f1, '') + self.assertEqualException(f1, '1') + self.assertEqualException(f1, 'a=2') + self.assertEqualException(f1, 'b=3') + # f2 takes at least 1 argument + self.assertEqualException(f2, '') + self.assertEqualException(f2, 'b=3') + for f in f1, f2: + # f1/f2 takes exactly/at most 2 arguments + self.assertEqualException(f, '2, 3, 4') + self.assertEqualException(f, '1, 2, 3, a=1') + self.assertEqualException(f, '2, 3, 4, c=5') + self.assertEqualException(f, '2, 3, 4, a=1, c=5') + # f got an unexpected keyword argument + self.assertEqualException(f, 'c=2') + self.assertEqualException(f, '2, c=3') + self.assertEqualException(f, '2, 3, c=4') + self.assertEqualException(f, '2, c=4, b=3') + self.assertEqualException(f, '**{u"\u03c0\u03b9": 4}') + # f got multiple values for keyword argument + self.assertEqualException(f, '1, a=2') + self.assertEqualException(f, '1, **{"a":2}') + self.assertEqualException(f, '1, 2, b=3') + self.assertEqualException(f, '1, c=3, a=2') + # issue11256: + f3 = self.makeCallable('**c') + self.assertEqualException(f3, '1, 2') + self.assertEqualException(f3, '1, 2, a=1, b=2') + f4 = self.makeCallable('*, a, b=0') + self.assertEqualException(f4, '1, 2') + self.assertEqualException(f4, '1, 2, a=1, b=2') + self.assertEqualException(f4, 'a=1, a=3') + self.assertEqualException(f4, 'a=1, c=3') + self.assertEqualException(f4, 'a=1, a=3, b=4') + self.assertEqualException(f4, 'a=1, b=2, a=3, b=4') + self.assertEqualException(f4, 'a=1, a=2, a=3, b=4') + + # issue #20816: getcallargs() fails to iterate over non-existent + # kwonlydefaults and raises a wrong TypeError + def f5(*, a): pass + with self.assertRaisesRegex(TypeError, + 'missing 1 required keyword-only'): + inspect.getcallargs(f5) + + + # issue20817: + def f6(a, b, c): + pass + with self.assertRaisesRegex(TypeError, "'a', 'b' and 'c'"): + inspect.getcallargs(f6) + + # bpo-33197 + with self.assertRaisesRegex(ValueError, + 'variadic keyword parameters cannot' + ' have default values'): + inspect.Parameter("foo", kind=inspect.Parameter.VAR_KEYWORD, + default=42) + with self.assertRaisesRegex(ValueError, + "value 5 is not a valid Parameter.kind"): + inspect.Parameter("bar", kind=5, default=42) + + with self.assertRaisesRegex(TypeError, + 'name must be a str, not a int'): + inspect.Parameter(123, kind=4) + +class TestGetcallargsMethods(TestGetcallargsFunctions): + + def setUp(self): + class Foo(object): + pass + self.cls = Foo + self.inst = Foo() + + def makeCallable(self, signature): + assert 'self' not in signature + mk = super(TestGetcallargsMethods, self).makeCallable + self.cls.method = mk('self, ' + signature) + return self.inst.method + +class TestGetcallargsUnboundMethods(TestGetcallargsMethods): + + def makeCallable(self, signature): + super(TestGetcallargsUnboundMethods, self).makeCallable(signature) + return self.cls.method + + def assertEqualCallArgs(self, func, call_params_string, locs=None): + return super(TestGetcallargsUnboundMethods, self).assertEqualCallArgs( + *self._getAssertEqualParams(func, call_params_string, locs)) + + def assertEqualException(self, func, call_params_string, locs=None): + return super(TestGetcallargsUnboundMethods, self).assertEqualException( + *self._getAssertEqualParams(func, call_params_string, locs)) + + def _getAssertEqualParams(self, func, call_params_string, locs=None): + assert 'inst' not in call_params_string + locs = dict(locs or {}, inst=self.inst) + return (func, 'inst,' + call_params_string, locs) + + +class TestGetattrStatic(unittest.TestCase): + + def test_basic(self): + class Thing(object): + x = object() + + thing = Thing() + self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x) + self.assertEqual(inspect.getattr_static(thing, 'x', None), Thing.x) + with self.assertRaises(AttributeError): + inspect.getattr_static(thing, 'y') + + self.assertEqual(inspect.getattr_static(thing, 'y', 3), 3) + + def test_inherited(self): + class Thing(object): + x = object() + class OtherThing(Thing): + pass + + something = OtherThing() + self.assertEqual(inspect.getattr_static(something, 'x'), Thing.x) + + def test_instance_attr(self): + class Thing(object): + x = 2 + def __init__(self, x): + self.x = x + thing = Thing(3) + self.assertEqual(inspect.getattr_static(thing, 'x'), 3) + del thing.x + self.assertEqual(inspect.getattr_static(thing, 'x'), 2) + + def test_property(self): + class Thing(object): + @property + def x(self): + raise AttributeError("I'm pretending not to exist") + thing = Thing() + self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x) + + def test_descriptor_raises_AttributeError(self): + class descriptor(object): + def __get__(*_): + raise AttributeError("I'm pretending not to exist") + desc = descriptor() + class Thing(object): + x = desc + thing = Thing() + self.assertEqual(inspect.getattr_static(thing, 'x'), desc) + + def test_classAttribute(self): + class Thing(object): + x = object() + + self.assertEqual(inspect.getattr_static(Thing, 'x'), Thing.x) + + def test_classVirtualAttribute(self): + class Thing(object): + @types.DynamicClassAttribute + def x(self): + return self._x + _x = object() + + self.assertEqual(inspect.getattr_static(Thing, 'x'), Thing.__dict__['x']) + + def test_inherited_classattribute(self): + class Thing(object): + x = object() + class OtherThing(Thing): + pass + + self.assertEqual(inspect.getattr_static(OtherThing, 'x'), Thing.x) + + def test_slots(self): + class Thing(object): + y = 'bar' + __slots__ = ['x'] + def __init__(self): + self.x = 'foo' + thing = Thing() + self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x) + self.assertEqual(inspect.getattr_static(thing, 'y'), 'bar') + + del thing.x + self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x) + + def test_metaclass(self): + class meta(type): + attr = 'foo' + class Thing(object, metaclass=meta): + pass + self.assertEqual(inspect.getattr_static(Thing, 'attr'), 'foo') + + class sub(meta): + pass + class OtherThing(object, metaclass=sub): + x = 3 + self.assertEqual(inspect.getattr_static(OtherThing, 'attr'), 'foo') + + class OtherOtherThing(OtherThing): + pass + # this test is odd, but it was added as it exposed a bug + self.assertEqual(inspect.getattr_static(OtherOtherThing, 'x'), 3) + + def test_no_dict_no_slots(self): + self.assertEqual(inspect.getattr_static(1, 'foo', None), None) + self.assertNotEqual(inspect.getattr_static('foo', 'lower'), None) + + def test_no_dict_no_slots_instance_member(self): + # returns descriptor + with open(__file__, encoding='utf-8') as handle: + self.assertEqual(inspect.getattr_static(handle, 'name'), type(handle).name) + + def test_inherited_slots(self): + # returns descriptor + class Thing(object): + __slots__ = ['x'] + def __init__(self): + self.x = 'foo' + + class OtherThing(Thing): + pass + # it would be nice if this worked... + # we get the descriptor instead of the instance attribute + self.assertEqual(inspect.getattr_static(OtherThing(), 'x'), Thing.x) + + def test_descriptor(self): + class descriptor(object): + def __get__(self, instance, owner): + return 3 + class Foo(object): + d = descriptor() + + foo = Foo() + + # for a non data descriptor we return the instance attribute + foo.__dict__['d'] = 1 + self.assertEqual(inspect.getattr_static(foo, 'd'), 1) + + # if the descriptor is a data-descriptor we should return the + # descriptor + descriptor.__set__ = lambda s, i, v: None + self.assertEqual(inspect.getattr_static(foo, 'd'), Foo.__dict__['d']) + + del descriptor.__set__ + descriptor.__delete__ = lambda s, i, o: None + self.assertEqual(inspect.getattr_static(foo, 'd'), Foo.__dict__['d']) + + def test_metaclass_with_descriptor(self): + class descriptor(object): + def __get__(self, instance, owner): + return 3 + class meta(type): + d = descriptor() + class Thing(object, metaclass=meta): + pass + self.assertEqual(inspect.getattr_static(Thing, 'd'), meta.__dict__['d']) + + + def test_class_as_property(self): + class Base(object): + foo = 3 + + class Something(Base): + executed = False + @property + def __class__(self): + self.executed = True + return object + + instance = Something() + self.assertEqual(inspect.getattr_static(instance, 'foo'), 3) + self.assertFalse(instance.executed) + self.assertEqual(inspect.getattr_static(Something, 'foo'), 3) + + def test_mro_as_property(self): + class Meta(type): + @property + def __mro__(self): + return (object,) + + class Base(object): + foo = 3 + + class Something(Base, metaclass=Meta): + pass + + self.assertEqual(inspect.getattr_static(Something(), 'foo'), 3) + self.assertEqual(inspect.getattr_static(Something, 'foo'), 3) + + def test_dict_as_property(self): + test = self + test.called = False + + class Foo(dict): + a = 3 + @property + def __dict__(self): + test.called = True + return {} + + foo = Foo() + foo.a = 4 + self.assertEqual(inspect.getattr_static(foo, 'a'), 3) + self.assertFalse(test.called) + + class Bar(Foo): pass + + bar = Bar() + bar.a = 5 + self.assertEqual(inspect.getattr_static(bar, 'a'), 3) + self.assertFalse(test.called) + + def test_mutated_mro(self): + test = self + test.called = False + + class Foo(dict): + a = 3 + @property + def __dict__(self): + test.called = True + return {} + + class Bar(dict): + a = 4 + + class Baz(Bar): pass + + baz = Baz() + self.assertEqual(inspect.getattr_static(baz, 'a'), 4) + Baz.__bases__ = (Foo,) + self.assertEqual(inspect.getattr_static(baz, 'a'), 3) + self.assertFalse(test.called) + + def test_custom_object_dict(self): + test = self + test.called = False + + class Custom(dict): + def get(self, key, default=None): + test.called = True + super().get(key, default) + + class Foo(object): + a = 3 + foo = Foo() + foo.__dict__ = Custom() + self.assertEqual(inspect.getattr_static(foo, 'a'), 3) + self.assertFalse(test.called) + + def test_metaclass_dict_as_property(self): + class Meta(type): + @property + def __dict__(self): + self.executed = True + + class Thing(metaclass=Meta): + executed = False + + def __init__(self): + self.spam = 42 + + instance = Thing() + self.assertEqual(inspect.getattr_static(instance, "spam"), 42) + self.assertFalse(Thing.executed) + + def test_module(self): + sentinel = object() + self.assertIsNot(inspect.getattr_static(sys, "version", sentinel), + sentinel) + + def test_metaclass_with_metaclass_with_dict_as_property(self): + class MetaMeta(type): + @property + def __dict__(self): + self.executed = True + return dict(spam=42) + + class Meta(type, metaclass=MetaMeta): + executed = False + + class Thing(metaclass=Meta): + pass + + with self.assertRaises(AttributeError): + inspect.getattr_static(Thing, "spam") + self.assertFalse(Thing.executed) + + def test_custom___getattr__(self): + test = self + test.called = False + + class Foo: + def __getattr__(self, attr): + test.called = True + return {} + + with self.assertRaises(AttributeError): + inspect.getattr_static(Foo(), 'whatever') + + self.assertFalse(test.called) + + def test_custom___getattribute__(self): + test = self + test.called = False + + class Foo: + def __getattribute__(self, attr): + test.called = True + return {} + + with self.assertRaises(AttributeError): + inspect.getattr_static(Foo(), 'really_could_be_anything') + + self.assertFalse(test.called) + + @suppress_immortalization() + def test_cache_does_not_cause_classes_to_persist(self): + # regression test for gh-118013: + # check that the internal _shadowed_dict cache does not cause + # dynamically created classes to have extended lifetimes even + # when no other strong references to those classes remain. + # Since these classes can themselves hold strong references to + # other objects, this can cause unexpected memory consumption. + class Foo: pass + Foo.instance = Foo() + weakref_to_class = weakref.ref(Foo) + inspect.getattr_static(Foo.instance, 'whatever', 'irrelevant') + del Foo + gc.collect() + self.assertIsNone(weakref_to_class()) + + +class TestGetGeneratorState(unittest.TestCase): + + def setUp(self): + def number_generator(): + for number in range(5): + yield number + self.generator = number_generator() + + def _generatorstate(self): + return inspect.getgeneratorstate(self.generator) + + def test_created(self): + self.assertEqual(self._generatorstate(), inspect.GEN_CREATED) + + def test_suspended(self): + next(self.generator) + self.assertEqual(self._generatorstate(), inspect.GEN_SUSPENDED) + + def test_closed_after_exhaustion(self): + for i in self.generator: + pass + self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) + + def test_closed_after_immediate_exception(self): + with self.assertRaises(RuntimeError): + self.generator.throw(RuntimeError) + self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) + + def test_closed_after_close(self): + self.generator.close() + self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) + + def test_running(self): + # As mentioned on issue #10220, checking for the RUNNING state only + # makes sense inside the generator itself. + # The following generator checks for this by using the closure's + # reference to self and the generator state checking helper method + def running_check_generator(): + for number in range(5): + self.assertEqual(self._generatorstate(), inspect.GEN_RUNNING) + yield number + self.assertEqual(self._generatorstate(), inspect.GEN_RUNNING) + self.generator = running_check_generator() + # Running up to the first yield + next(self.generator) + # Running after the first yield + next(self.generator) + + def test_easy_debugging(self): + # repr() and str() of a generator state should contain the state name + names = 'GEN_CREATED GEN_RUNNING GEN_SUSPENDED GEN_CLOSED'.split() + for name in names: + state = getattr(inspect, name) + self.assertIn(name, repr(state)) + self.assertIn(name, str(state)) + + def test_getgeneratorlocals(self): + def each(lst, a=None): + b=(1, 2, 3) + for v in lst: + if v == 3: + c = 12 + yield v + + numbers = each([1, 2, 3]) + self.assertEqual(inspect.getgeneratorlocals(numbers), + {'a': None, 'lst': [1, 2, 3]}) + next(numbers) + self.assertEqual(inspect.getgeneratorlocals(numbers), + {'a': None, 'lst': [1, 2, 3], 'v': 1, + 'b': (1, 2, 3)}) + next(numbers) + self.assertEqual(inspect.getgeneratorlocals(numbers), + {'a': None, 'lst': [1, 2, 3], 'v': 2, + 'b': (1, 2, 3)}) + next(numbers) + self.assertEqual(inspect.getgeneratorlocals(numbers), + {'a': None, 'lst': [1, 2, 3], 'v': 3, + 'b': (1, 2, 3), 'c': 12}) + try: + next(numbers) + except StopIteration: + pass + self.assertEqual(inspect.getgeneratorlocals(numbers), {}) + + def test_getgeneratorlocals_empty(self): + def yield_one(): + yield 1 + one = yield_one() + self.assertEqual(inspect.getgeneratorlocals(one), {}) + try: + next(one) + except StopIteration: + pass + self.assertEqual(inspect.getgeneratorlocals(one), {}) + + def test_getgeneratorlocals_error(self): + self.assertRaises(TypeError, inspect.getgeneratorlocals, 1) + self.assertRaises(TypeError, inspect.getgeneratorlocals, lambda x: True) + self.assertRaises(TypeError, inspect.getgeneratorlocals, set) + self.assertRaises(TypeError, inspect.getgeneratorlocals, (2,3)) + + +class TestGetCoroutineState(unittest.TestCase): + + def setUp(self): + @types.coroutine + def number_coroutine(): + for number in range(5): + yield number + async def coroutine(): + await number_coroutine() + self.coroutine = coroutine() + + def tearDown(self): + self.coroutine.close() + + def _coroutinestate(self): + return inspect.getcoroutinestate(self.coroutine) + + def test_created(self): + self.assertEqual(self._coroutinestate(), inspect.CORO_CREATED) + + def test_suspended(self): + self.coroutine.send(None) + self.assertEqual(self._coroutinestate(), inspect.CORO_SUSPENDED) + + def test_closed_after_exhaustion(self): + while True: + try: + self.coroutine.send(None) + except StopIteration: + break + + self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) + + def test_closed_after_immediate_exception(self): + with self.assertRaises(RuntimeError): + self.coroutine.throw(RuntimeError) + self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) + + def test_closed_after_close(self): + self.coroutine.close() + self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) + + def test_easy_debugging(self): + # repr() and str() of a coroutine state should contain the state name + names = 'CORO_CREATED CORO_RUNNING CORO_SUSPENDED CORO_CLOSED'.split() + for name in names: + state = getattr(inspect, name) + self.assertIn(name, repr(state)) + self.assertIn(name, str(state)) + + def test_getcoroutinelocals(self): + @types.coroutine + def gencoro(): + yield + + gencoro = gencoro() + async def func(a=None): + b = 'spam' + await gencoro + + coro = func() + self.assertEqual(inspect.getcoroutinelocals(coro), + {'a': None, 'gencoro': gencoro}) + coro.send(None) + self.assertEqual(inspect.getcoroutinelocals(coro), + {'a': None, 'gencoro': gencoro, 'b': 'spam'}) + + +@support.requires_working_socket() +class TestGetAsyncGenState(unittest.IsolatedAsyncioTestCase): + + def setUp(self): + async def number_asyncgen(): + for number in range(5): + yield number + self.asyncgen = number_asyncgen() + + async def asyncTearDown(self): + await self.asyncgen.aclose() + + def _asyncgenstate(self): + return inspect.getasyncgenstate(self.asyncgen) + + def test_created(self): + self.assertEqual(self._asyncgenstate(), inspect.AGEN_CREATED) + + async def test_suspended(self): + value = await anext(self.asyncgen) + self.assertEqual(self._asyncgenstate(), inspect.AGEN_SUSPENDED) + self.assertEqual(value, 0) + + async def test_closed_after_exhaustion(self): + countdown = 7 + with self.assertRaises(StopAsyncIteration): + while countdown := countdown - 1: + await anext(self.asyncgen) + self.assertEqual(countdown, 1) + self.assertEqual(self._asyncgenstate(), inspect.AGEN_CLOSED) + + async def test_closed_after_immediate_exception(self): + with self.assertRaises(RuntimeError): + await self.asyncgen.athrow(RuntimeError) + self.assertEqual(self._asyncgenstate(), inspect.AGEN_CLOSED) + + async def test_running(self): + async def running_check_asyncgen(): + for number in range(5): + self.assertEqual(self._asyncgenstate(), inspect.AGEN_RUNNING) + yield number + self.assertEqual(self._asyncgenstate(), inspect.AGEN_RUNNING) + self.asyncgen = running_check_asyncgen() + # Running up to the first yield + await anext(self.asyncgen) + self.assertEqual(self._asyncgenstate(), inspect.AGEN_SUSPENDED) + # Running after the first yield + await anext(self.asyncgen) + self.assertEqual(self._asyncgenstate(), inspect.AGEN_SUSPENDED) + + def test_easy_debugging(self): + # repr() and str() of a asyncgen state should contain the state name + names = 'AGEN_CREATED AGEN_RUNNING AGEN_SUSPENDED AGEN_CLOSED'.split() + for name in names: + state = getattr(inspect, name) + self.assertIn(name, repr(state)) + self.assertIn(name, str(state)) + + async def test_getasyncgenlocals(self): + async def each(lst, a=None): + b=(1, 2, 3) + for v in lst: + if v == 3: + c = 12 + yield v + + numbers = each([1, 2, 3]) + self.assertEqual(inspect.getasyncgenlocals(numbers), + {'a': None, 'lst': [1, 2, 3]}) + await anext(numbers) + self.assertEqual(inspect.getasyncgenlocals(numbers), + {'a': None, 'lst': [1, 2, 3], 'v': 1, + 'b': (1, 2, 3)}) + await anext(numbers) + self.assertEqual(inspect.getasyncgenlocals(numbers), + {'a': None, 'lst': [1, 2, 3], 'v': 2, + 'b': (1, 2, 3)}) + await anext(numbers) + self.assertEqual(inspect.getasyncgenlocals(numbers), + {'a': None, 'lst': [1, 2, 3], 'v': 3, + 'b': (1, 2, 3), 'c': 12}) + with self.assertRaises(StopAsyncIteration): + await anext(numbers) + self.assertEqual(inspect.getasyncgenlocals(numbers), {}) + + async def test_getasyncgenlocals_empty(self): + async def yield_one(): + yield 1 + one = yield_one() + self.assertEqual(inspect.getasyncgenlocals(one), {}) + await anext(one) + self.assertEqual(inspect.getasyncgenlocals(one), {}) + with self.assertRaises(StopAsyncIteration): + await anext(one) + self.assertEqual(inspect.getasyncgenlocals(one), {}) + + def test_getasyncgenlocals_error(self): + self.assertRaises(TypeError, inspect.getasyncgenlocals, 1) + self.assertRaises(TypeError, inspect.getasyncgenlocals, lambda x: True) + self.assertRaises(TypeError, inspect.getasyncgenlocals, set) + self.assertRaises(TypeError, inspect.getasyncgenlocals, (2,3)) + + +class MySignature(inspect.Signature): + # Top-level to make it picklable; + # used in test_signature_object_pickle + pass + +class MyParameter(inspect.Parameter): + # Top-level to make it picklable; + # used in test_signature_object_pickle + pass + + + +class TestSignatureObject(unittest.TestCase): + @staticmethod + def signature(func, **kw): + sig = inspect.signature(func, **kw) + return (tuple((param.name, + (... if param.default is param.empty else param.default), + (... if param.annotation is param.empty + else param.annotation), + str(param.kind).lower()) + for param in sig.parameters.values()), + (... if sig.return_annotation is sig.empty + else sig.return_annotation)) + + def test_signature_object(self): + S = inspect.Signature + P = inspect.Parameter + + self.assertEqual(str(S()), '()') + self.assertEqual(repr(S().parameters), 'mappingproxy(OrderedDict())') + + def test(po, /, pk, pkd=100, *args, ko, kod=10, **kwargs): + pass + + sig = inspect.signature(test) + self.assertTrue(repr(sig).startswith('<Signature')) + self.assertTrue('(po, /, pk' in repr(sig)) + + # We need two functions, because it is impossible to represent + # all param kinds in a single one. + def test2(pod=42, /): + pass + + sig2 = inspect.signature(test2) + self.assertTrue(repr(sig2).startswith('<Signature')) + self.assertTrue('(pod=42, /)' in repr(sig2)) + + po = sig.parameters['po'] + pod = sig2.parameters['pod'] + pk = sig.parameters['pk'] + pkd = sig.parameters['pkd'] + args = sig.parameters['args'] + ko = sig.parameters['ko'] + kod = sig.parameters['kod'] + kwargs = sig.parameters['kwargs'] + + S((po, pk, args, ko, kwargs)) + S((po, pk, ko, kod)) + S((po, pod, ko)) + S((po, pod, kod)) + S((pod, ko, kod)) + S((pod, kod)) + S((pod, args, kod, kwargs)) + # keyword-only parameters without default values + # can follow keyword-only parameters with default values: + S((kod, ko)) + S((kod, ko, kwargs)) + S((args, kod, ko)) + + with self.assertRaisesRegex(ValueError, 'wrong parameter order'): + S((pk, po, args, ko, kwargs)) + + with self.assertRaisesRegex(ValueError, 'wrong parameter order'): + S((po, args, pk, ko, kwargs)) + + with self.assertRaisesRegex(ValueError, 'wrong parameter order'): + S((args, po, pk, ko, kwargs)) + + with self.assertRaisesRegex(ValueError, 'wrong parameter order'): + S((po, pk, args, kwargs, ko)) + + kwargs2 = kwargs.replace(name='args') + with self.assertRaisesRegex(ValueError, 'duplicate parameter name'): + S((po, pk, args, kwargs2, ko)) + + with self.assertRaisesRegex(ValueError, 'follows default argument'): + S((pod, po)) + + with self.assertRaisesRegex(ValueError, 'follows default argument'): + S((pod, pk)) + + with self.assertRaisesRegex(ValueError, 'follows default argument'): + S((po, pod, pk)) + + with self.assertRaisesRegex(ValueError, 'follows default argument'): + S((po, pkd, pk)) + + with self.assertRaisesRegex(ValueError, 'follows default argument'): + S((pkd, pk)) + + def test_signature_object_pickle(self): + def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass + foo_partial = functools.partial(foo, a=1) + + sig = inspect.signature(foo_partial) + + for ver in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(pickle_ver=ver, subclass=False): + sig_pickled = pickle.loads(pickle.dumps(sig, ver)) + self.assertEqual(sig, sig_pickled) + + # Test that basic sub-classing works + sig = inspect.signature(foo) + myparam = MyParameter(name='z', kind=inspect.Parameter.POSITIONAL_ONLY) + myparams = collections.OrderedDict(sig.parameters, a=myparam) + mysig = MySignature().replace(parameters=myparams.values(), + return_annotation=sig.return_annotation) + self.assertTrue(isinstance(mysig, MySignature)) + self.assertTrue(isinstance(mysig.parameters['z'], MyParameter)) + + for ver in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(pickle_ver=ver, subclass=True): + sig_pickled = pickle.loads(pickle.dumps(mysig, ver)) + self.assertEqual(mysig, sig_pickled) + self.assertTrue(isinstance(sig_pickled, MySignature)) + self.assertTrue(isinstance(sig_pickled.parameters['z'], + MyParameter)) + + def test_signature_immutability(self): + def test(a): + pass + sig = inspect.signature(test) + + with self.assertRaises(AttributeError): + sig.foo = 'bar' + + with self.assertRaises(TypeError): + sig.parameters['a'] = None + + def test_signature_on_noarg(self): + def test(): + pass + self.assertEqual(self.signature(test), ((), ...)) + + def test_signature_on_wargs(self): + def test(a, b:'foo') -> 123: + pass + self.assertEqual(self.signature(test), + ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., 'foo', "positional_or_keyword")), + 123)) + + def test_signature_on_wkwonly(self): + def test(*, a:float, b:str) -> int: + pass + self.assertEqual(self.signature(test), + ((('a', ..., float, "keyword_only"), + ('b', ..., str, "keyword_only")), + int)) + + def test_signature_on_complex_args(self): + def test(a, b:'foo'=10, *args:'bar', spam:'baz', ham=123, **kwargs:int): + pass + self.assertEqual(self.signature(test), + ((('a', ..., ..., "positional_or_keyword"), + ('b', 10, 'foo', "positional_or_keyword"), + ('args', ..., 'bar', "var_positional"), + ('spam', ..., 'baz', "keyword_only"), + ('ham', 123, ..., "keyword_only"), + ('kwargs', ..., int, "var_keyword")), + ...)) + + def test_signature_without_self(self): + def test_args_only(*args): # NOQA + pass + + def test_args_kwargs_only(*args, **kwargs): # NOQA + pass + + class A: + @classmethod + def test_classmethod(*args): # NOQA + pass + + @staticmethod + def test_staticmethod(*args): # NOQA + pass + + f1 = functools.partialmethod((test_classmethod), 1) + f2 = functools.partialmethod((test_args_only), 1) + f3 = functools.partialmethod((test_staticmethod), 1) + f4 = functools.partialmethod((test_args_kwargs_only),1) + + self.assertEqual(self.signature(test_args_only), + ((('args', ..., ..., 'var_positional'),), ...)) + self.assertEqual(self.signature(test_args_kwargs_only), + ((('args', ..., ..., 'var_positional'), + ('kwargs', ..., ..., 'var_keyword')), ...)) + self.assertEqual(self.signature(A.f1), + ((('args', ..., ..., 'var_positional'),), ...)) + self.assertEqual(self.signature(A.f2), + ((('args', ..., ..., 'var_positional'),), ...)) + self.assertEqual(self.signature(A.f3), + ((('args', ..., ..., 'var_positional'),), ...)) + self.assertEqual(self.signature(A.f4), + ((('args', ..., ..., 'var_positional'), + ('kwargs', ..., ..., 'var_keyword')), ...)) + @cpython_only + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_on_builtins(self): + _testcapi = import_helper.import_module("_testcapi") + + def test_unbound_method(o): + """Use this to test unbound methods (things that should have a self)""" + signature = inspect.signature(o) + self.assertTrue(isinstance(signature, inspect.Signature)) + self.assertEqual(list(signature.parameters.values())[0].name, 'self') + return signature + + def test_callable(o): + """Use this to test bound methods or normal callables (things that don't expect self)""" + signature = inspect.signature(o) + self.assertTrue(isinstance(signature, inspect.Signature)) + if signature.parameters: + self.assertNotEqual(list(signature.parameters.values())[0].name, 'self') + return signature + + signature = test_callable(_testcapi.docstring_with_signature_with_defaults) + def p(name): return signature.parameters[name].default + self.assertEqual(p('s'), 'avocado') + self.assertEqual(p('b'), b'bytes') + self.assertEqual(p('d'), 3.14) + self.assertEqual(p('i'), 35) + self.assertEqual(p('n'), None) + self.assertEqual(p('t'), True) + self.assertEqual(p('f'), False) + self.assertEqual(p('local'), 3) + self.assertEqual(p('sys'), sys.maxsize) + self.assertEqual(p('exp'), sys.maxsize - 1) + + test_callable(object) + + # normal method + # (PyMethodDescr_Type, "method_descriptor") + test_unbound_method(_pickle.Pickler.dump) + d = _pickle.Pickler(io.StringIO()) + test_callable(d.dump) + + # static method + test_callable(bytes.maketrans) + test_callable(b'abc'.maketrans) + + # class method + test_callable(dict.fromkeys) + test_callable({}.fromkeys) + + # wrapper around slot (PyWrapperDescr_Type, "wrapper_descriptor") + test_unbound_method(type.__call__) + test_unbound_method(int.__add__) + test_callable((3).__add__) + + # _PyMethodWrapper_Type + # support for 'method-wrapper' + test_callable(min.__call__) + + # This doesn't work now. + # (We don't have a valid signature for "type" in 3.4) + class ThisWorksNow: + __call__ = type + # TODO: Support type. + self.assertEqual(ThisWorksNow()(1), int) + self.assertEqual(ThisWorksNow()('A', (), {}).__name__, 'A') + with self.assertRaisesRegex(ValueError, "no signature found"): + test_callable(ThisWorksNow()) + + # Regression test for issue #20786 + test_unbound_method(dict.__delitem__) + test_unbound_method(property.__delete__) + + # Regression test for issue #20586 + test_callable(_testcapi.docstring_with_signature_but_no_doc) + + # Regression test for gh-104955 + method = bytearray.__release_buffer__ + sig = test_unbound_method(method) + self.assertEqual(list(sig.parameters), ['self', 'buffer']) + + @cpython_only + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_on_decorated_builtins(self): + _testcapi = import_helper.import_module("_testcapi") + func = _testcapi.docstring_with_signature_with_defaults + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs) -> int: + return func(*args, **kwargs) + return wrapper + + decorated_func = decorator(func) + + self.assertEqual(inspect.signature(func), + inspect.signature(decorated_func)) + + def wrapper_like(*args, **kwargs) -> int: pass + self.assertEqual(inspect.signature(decorated_func, + follow_wrapped=False), + inspect.signature(wrapper_like)) + + @cpython_only + def test_signature_on_builtins_no_signature(self): + _testcapi = import_helper.import_module("_testcapi") + with self.assertRaisesRegex(ValueError, + 'no signature found for builtin'): + inspect.signature(_testcapi.docstring_no_signature) + + with self.assertRaisesRegex(ValueError, + 'no signature found for builtin'): + inspect.signature(str) + + cls = _testcapi.DocStringNoSignatureTest + obj = _testcapi.DocStringNoSignatureTest() + tests = [ + (_testcapi.docstring_no_signature_noargs, meth_noargs), + (_testcapi.docstring_no_signature_o, meth_o), + (cls.meth_noargs, meth_self_noargs), + (cls.meth_o, meth_self_o), + (obj.meth_noargs, meth_noargs), + (obj.meth_o, meth_o), + (cls.meth_noargs_class, meth_noargs), + (cls.meth_o_class, meth_o), + (cls.meth_noargs_static, meth_noargs), + (cls.meth_o_static, meth_o), + (cls.meth_noargs_coexist, meth_self_noargs), + (cls.meth_o_coexist, meth_self_o), + + (time.time, meth_noargs), + (str.lower, meth_self_noargs), + (''.lower, meth_noargs), + (set.add, meth_self_o), + (set().add, meth_o), + (set.__contains__, meth_self_o), + (set().__contains__, meth_o), + (datetime.datetime.__dict__['utcnow'], meth_type_noargs), + (datetime.datetime.utcnow, meth_noargs), + (dict.__dict__['__class_getitem__'], meth_type_o), + (dict.__class_getitem__, meth_o), + ] + try: + import _stat + except ImportError: + # if the _stat extension is not available, stat.S_IMODE() is + # implemented in Python, not in C + pass + else: + tests.append((stat.S_IMODE, meth_o)) + for builtin, template in tests: + with self.subTest(builtin): + self.assertEqual(inspect.signature(builtin), + inspect.signature(template)) + + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_parsing_with_defaults(self): + _testcapi = import_helper.import_module("_testcapi") + meth = _testcapi.DocStringUnrepresentableSignatureTest.with_default + self.assertEqual(str(inspect.signature(meth)), '(self, /, x=1)') + + def test_signature_on_non_function(self): + with self.assertRaisesRegex(TypeError, 'is not a callable object'): + inspect.signature(42) + + def test_signature_from_functionlike_object(self): + def func(a,b, *args, kwonly=True, kwonlyreq, **kwargs): + pass + + class funclike: + # Has to be callable, and have correct + # __code__, __annotations__, __defaults__, __name__, + # and __kwdefaults__ attributes + + def __init__(self, func): + self.__name__ = func.__name__ + self.__code__ = func.__code__ + self.__annotations__ = func.__annotations__ + self.__defaults__ = func.__defaults__ + self.__kwdefaults__ = func.__kwdefaults__ + self.func = func + + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) + + sig_func = inspect.Signature.from_callable(func) + + sig_funclike = inspect.Signature.from_callable(funclike(func)) + self.assertEqual(sig_funclike, sig_func) + + sig_funclike = inspect.signature(funclike(func)) + self.assertEqual(sig_funclike, sig_func) + + # If object is not a duck type of function, then + # signature will try to get a signature for its '__call__' + # method + fl = funclike(func) + del fl.__defaults__ + self.assertEqual(self.signature(fl), + ((('args', ..., ..., "var_positional"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + # Test with cython-like builtins: + _orig_isdesc = inspect.ismethoddescriptor + def _isdesc(obj): + if hasattr(obj, '_builtinmock'): + return True + return _orig_isdesc(obj) + + with unittest.mock.patch('inspect.ismethoddescriptor', _isdesc): + builtin_func = funclike(func) + # Make sure that our mock setup is working + self.assertFalse(inspect.ismethoddescriptor(builtin_func)) + builtin_func._builtinmock = True + self.assertTrue(inspect.ismethoddescriptor(builtin_func)) + self.assertEqual(inspect.signature(builtin_func), sig_func) + + def test_signature_functionlike_class(self): + # We only want to duck type function-like objects, + # not classes. + + def func(a,b, *args, kwonly=True, kwonlyreq, **kwargs): + pass + + class funclike: + def __init__(self, marker): + pass + + __name__ = func.__name__ + __code__ = func.__code__ + __annotations__ = func.__annotations__ + __defaults__ = func.__defaults__ + __kwdefaults__ = func.__kwdefaults__ + + self.assertEqual(str(inspect.signature(funclike)), '(marker)') + + def test_signature_on_method(self): + class Test: + def __init__(*args): + pass + def m1(self, arg1, arg2=1) -> int: + pass + def m2(*args): + pass + def __call__(*, a): + pass + + self.assertEqual(self.signature(Test().m1), + ((('arg1', ..., ..., "positional_or_keyword"), + ('arg2', 1, ..., "positional_or_keyword")), + int)) + + self.assertEqual(self.signature(Test().m2), + ((('args', ..., ..., "var_positional"),), + ...)) + + self.assertEqual(self.signature(Test), + ((('args', ..., ..., "var_positional"),), + ...)) + + with self.assertRaisesRegex(ValueError, 'invalid method signature'): + self.signature(Test()) + + def test_signature_wrapped_bound_method(self): + # Issue 24298 + class Test: + def m1(self, arg1, arg2=1) -> int: + pass + @functools.wraps(Test().m1) + def m1d(*args, **kwargs): + pass + self.assertEqual(self.signature(m1d), + ((('arg1', ..., ..., "positional_or_keyword"), + ('arg2', 1, ..., "positional_or_keyword")), + int)) + + def test_signature_on_classmethod(self): + if not support.MISSING_C_DOCSTRINGS: + self.assertEqual(self.signature(classmethod), + ((('function', ..., ..., "positional_only"),), + ...)) + + class Test: + @classmethod + def foo(cls, arg1, *, arg2=1): + pass + + meth = Test().foo + self.assertEqual(self.signature(meth), + ((('arg1', ..., ..., "positional_or_keyword"), + ('arg2', 1, ..., "keyword_only")), + ...)) + + meth = Test.foo + self.assertEqual(self.signature(meth), + ((('arg1', ..., ..., "positional_or_keyword"), + ('arg2', 1, ..., "keyword_only")), + ...)) + + def test_signature_on_staticmethod(self): + if not support.MISSING_C_DOCSTRINGS: + self.assertEqual(self.signature(staticmethod), + ((('function', ..., ..., "positional_only"),), + ...)) + + class Test: + @staticmethod + def foo(cls, *, arg): + pass + + meth = Test().foo + self.assertEqual(self.signature(meth), + ((('cls', ..., ..., "positional_or_keyword"), + ('arg', ..., ..., "keyword_only")), + ...)) + + meth = Test.foo + self.assertEqual(self.signature(meth), + ((('cls', ..., ..., "positional_or_keyword"), + ('arg', ..., ..., "keyword_only")), + ...)) + + def test_signature_on_partial(self): + from functools import partial + + def test(): + pass + + self.assertEqual(self.signature(partial(test)), ((), ...)) + + with self.assertRaisesRegex(ValueError, "has incorrect arguments"): + inspect.signature(partial(test, 1)) + + with self.assertRaisesRegex(ValueError, "has incorrect arguments"): + inspect.signature(partial(test, a=1)) + + def test(a, b, *, c, d): + pass + + self.assertEqual(self.signature(partial(test)), + ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword"), + ('c', ..., ..., "keyword_only"), + ('d', ..., ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(partial(test, 1)), + ((('b', ..., ..., "positional_or_keyword"), + ('c', ..., ..., "keyword_only"), + ('d', ..., ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(partial(test, 1, c=2)), + ((('b', ..., ..., "positional_or_keyword"), + ('c', 2, ..., "keyword_only"), + ('d', ..., ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(partial(test, b=1, c=2)), + ((('a', ..., ..., "positional_or_keyword"), + ('b', 1, ..., "keyword_only"), + ('c', 2, ..., "keyword_only"), + ('d', ..., ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(partial(test, 0, b=1, c=2)), + ((('b', 1, ..., "keyword_only"), + ('c', 2, ..., "keyword_only"), + ('d', ..., ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(partial(test, a=1)), + ((('a', 1, ..., "keyword_only"), + ('b', ..., ..., "keyword_only"), + ('c', ..., ..., "keyword_only"), + ('d', ..., ..., "keyword_only")), + ...)) + + def test(a, *args, b, **kwargs): + pass + + self.assertEqual(self.signature(partial(test, 1)), + ((('args', ..., ..., "var_positional"), + ('b', ..., ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + self.assertEqual(self.signature(partial(test, a=1)), + ((('a', 1, ..., "keyword_only"), + ('b', ..., ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + self.assertEqual(self.signature(partial(test, 1, 2, 3)), + ((('args', ..., ..., "var_positional"), + ('b', ..., ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + self.assertEqual(self.signature(partial(test, 1, 2, 3, test=True)), + ((('args', ..., ..., "var_positional"), + ('b', ..., ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + self.assertEqual(self.signature(partial(test, 1, 2, 3, test=1, b=0)), + ((('args', ..., ..., "var_positional"), + ('b', 0, ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + self.assertEqual(self.signature(partial(test, b=0)), + ((('a', ..., ..., "positional_or_keyword"), + ('args', ..., ..., "var_positional"), + ('b', 0, ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + self.assertEqual(self.signature(partial(test, b=0, test=1)), + ((('a', ..., ..., "positional_or_keyword"), + ('args', ..., ..., "var_positional"), + ('b', 0, ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + def test(a, b, c:int) -> 42: + pass + + sig = test.__signature__ = inspect.signature(test) + + self.assertEqual(self.signature(partial(partial(test, 1))), + ((('b', ..., ..., "positional_or_keyword"), + ('c', ..., int, "positional_or_keyword")), + 42)) + + self.assertEqual(self.signature(partial(partial(test, 1), 2)), + ((('c', ..., int, "positional_or_keyword"),), + 42)) + + def foo(a): + return a + _foo = partial(partial(foo, a=10), a=20) + self.assertEqual(self.signature(_foo), + ((('a', 20, ..., "keyword_only"),), + ...)) + # check that we don't have any side-effects in signature(), + # and the partial object is still functioning + self.assertEqual(_foo(), 20) + + def foo(a, b, c): + return a, b, c + _foo = partial(partial(foo, 1, b=20), b=30) + + self.assertEqual(self.signature(_foo), + ((('b', 30, ..., "keyword_only"), + ('c', ..., ..., "keyword_only")), + ...)) + self.assertEqual(_foo(c=10), (1, 30, 10)) + + def foo(a, b, c, *, d): + return a, b, c, d + _foo = partial(partial(foo, d=20, c=20), b=10, d=30) + self.assertEqual(self.signature(_foo), + ((('a', ..., ..., "positional_or_keyword"), + ('b', 10, ..., "keyword_only"), + ('c', 20, ..., "keyword_only"), + ('d', 30, ..., "keyword_only"), + ), + ...)) + ba = inspect.signature(_foo).bind(a=200, b=11) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (200, 11, 20, 30)) + + def foo(a=1, b=2, c=3): + return a, b, c + _foo = partial(foo, c=13) # (a=1, b=2, *, c=13) + + ba = inspect.signature(_foo).bind(a=11) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 2, 13)) + + ba = inspect.signature(_foo).bind(11, 12) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13)) + + ba = inspect.signature(_foo).bind(11, b=12) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13)) + + ba = inspect.signature(_foo).bind(b=12) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (1, 12, 13)) + + _foo = partial(_foo, b=10, c=20) + ba = inspect.signature(_foo).bind(12) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (12, 10, 20)) + + + def foo(a, b, /, c, d, **kwargs): + pass + sig = inspect.signature(foo) + self.assertEqual(str(sig), '(a, b, /, c, d, **kwargs)') + + self.assertEqual(self.signature(partial(foo, 1)), + ((('b', ..., ..., 'positional_only'), + ('c', ..., ..., 'positional_or_keyword'), + ('d', ..., ..., 'positional_or_keyword'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + self.assertEqual(self.signature(partial(foo, 1, 2)), + ((('c', ..., ..., 'positional_or_keyword'), + ('d', ..., ..., 'positional_or_keyword'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + self.assertEqual(self.signature(partial(foo, 1, 2, 3)), + ((('d', ..., ..., 'positional_or_keyword'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + self.assertEqual(self.signature(partial(foo, 1, 2, c=3)), + ((('c', 3, ..., 'keyword_only'), + ('d', ..., ..., 'keyword_only'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + self.assertEqual(self.signature(partial(foo, 1, c=3)), + ((('b', ..., ..., 'positional_only'), + ('c', 3, ..., 'keyword_only'), + ('d', ..., ..., 'keyword_only'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + def test_signature_on_partialmethod(self): + from functools import partialmethod + + class Spam: + def test(): + pass + ham = partialmethod(test) + + with self.assertRaisesRegex(ValueError, "has incorrect arguments"): + inspect.signature(Spam.ham) + + class Spam: + def test(it, a, *, c) -> 'spam': + pass + ham = partialmethod(test, c=1) + + self.assertEqual(self.signature(Spam.ham, eval_str=False), + ((('it', ..., ..., 'positional_or_keyword'), + ('a', ..., ..., 'positional_or_keyword'), + ('c', 1, ..., 'keyword_only')), + 'spam')) + + self.assertEqual(self.signature(Spam().ham, eval_str=False), + ((('a', ..., ..., 'positional_or_keyword'), + ('c', 1, ..., 'keyword_only')), + 'spam')) + + class Spam: + def test(self: 'anno', x): + pass + + g = partialmethod(test, 1) + + self.assertEqual(self.signature(Spam.g, eval_str=False), + ((('self', ..., 'anno', 'positional_or_keyword'),), + ...)) + + def test_signature_on_fake_partialmethod(self): + def foo(a): pass + foo.__partialmethod__ = 'spam' + self.assertEqual(str(inspect.signature(foo)), '(a)') + + def test_signature_on_decorated(self): + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs) -> int: + return func(*args, **kwargs) + return wrapper + + class Foo: + @decorator + def bar(self, a, b): + pass + + bar = decorator(Foo().bar) + + self.assertEqual(self.signature(Foo.bar), + ((('self', ..., ..., "positional_or_keyword"), + ('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword")), + ...)) + + self.assertEqual(self.signature(Foo().bar), + ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword")), + ...)) + + self.assertEqual(self.signature(Foo.bar, follow_wrapped=False), + ((('args', ..., ..., "var_positional"), + ('kwargs', ..., ..., "var_keyword")), + ...)) # functools.wraps will copy __annotations__ + # from "func" to "wrapper", hence no + # return_annotation + + self.assertEqual(self.signature(bar), + ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword")), + ...)) + + # Test that we handle method wrappers correctly + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs) -> int: + return func(42, *args, **kwargs) + sig = inspect.signature(func) + new_params = tuple(sig.parameters.values())[1:] + wrapper.__signature__ = sig.replace(parameters=new_params) + return wrapper + + class Foo: + @decorator + def __call__(self, a, b): + pass + + self.assertEqual(self.signature(Foo.__call__), + ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword")), + ...)) + + self.assertEqual(self.signature(Foo().__call__), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + # Test we handle __signature__ partway down the wrapper stack + def wrapped_foo_call(): + pass + wrapped_foo_call.__wrapped__ = Foo.__call__ + + self.assertEqual(self.signature(wrapped_foo_call), + ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword")), + ...)) + + def test_signature_on_class(self): + class C: + def __init__(self, a): + pass + + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + class CM(type): + def __call__(cls, a): + pass + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('classmethod'): + class CM(type): + @classmethod + def __call__(cls, a): + return a + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class CM(type): + @staticmethod + def __call__(a): + return a + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + def call(self, a): + return a + class CM(type): + __call__ = A().call + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partial'): + class CM(type): + __call__ = functools.partial(lambda x, a: (x, a), 2) + class C(metaclass=CM): + def __init__(self, b): + pass + + with self.assertWarns(FutureWarning): + self.assertEqual(C(1), (2, 1)) + with self.assertWarns(FutureWarning): + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class CM(type): + __call__ = functools.partialmethod(lambda self, x, a: (x, a), 2) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('BuiltinMethodType'): + class CM(type): + __call__ = ':'.join + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(['a', 'bc']), 'a:bc') + # BUG: Returns '<Signature (b)>' + with self.assertRaises(AssertionError): + self.assertEqual(self.signature(C), self.signature(''.join)) + + with self.subTest('MethodWrapperType'): + class CM(type): + __call__ = (2).__pow__ + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(3), 8) + self.assertEqual(C(3, 7), 1) + if not support.MISSING_C_DOCSTRINGS: + # BUG: Returns '<Signature (b)>' + with self.assertRaises(AssertionError): + self.assertEqual(self.signature(C), self.signature((0).__pow__)) + + class CM(type): + def __new__(mcls, name, bases, dct, *, foo=1): + return super().__new__(mcls, name, bases, dct) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + self.assertEqual(self.signature(CM), + ((('name', ..., ..., "positional_or_keyword"), + ('bases', ..., ..., "positional_or_keyword"), + ('dct', ..., ..., "positional_or_keyword"), + ('foo', 1, ..., "keyword_only")), + ...)) + + class CMM(type): + def __new__(mcls, name, bases, dct, *, foo=1): + return super().__new__(mcls, name, bases, dct) + def __call__(cls, nm, bs, dt): + return type(nm, bs, dt) + class CM(type, metaclass=CMM): + def __new__(mcls, name, bases, dct, *, bar=2): + return super().__new__(mcls, name, bases, dct) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(CMM), + ((('name', ..., ..., "positional_or_keyword"), + ('bases', ..., ..., "positional_or_keyword"), + ('dct', ..., ..., "positional_or_keyword"), + ('foo', 1, ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(CM), + ((('nm', ..., ..., "positional_or_keyword"), + ('bs', ..., ..., "positional_or_keyword"), + ('dt', ..., ..., "positional_or_keyword")), + ...)) + + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + class CM(type): + def __init__(cls, name, bases, dct, *, bar=2): + return super().__init__(name, bases, dct) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(CM), + ((('name', ..., ..., "positional_or_keyword"), + ('bases', ..., ..., "positional_or_keyword"), + ('dct', ..., ..., "positional_or_keyword"), + ('bar', 2, ..., "keyword_only")), + ...)) + + def test_signature_on_class_with_wrapped_metaclass_call(self): + class CM(type): + @identity_wrapper + def __call__(cls, a): + pass + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('classmethod'): + class CM(type): + @classmethod + @identity_wrapper + def __call__(cls, a): + return a + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class CM(type): + @staticmethod + @identity_wrapper + def __call__(a): + return a + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + @identity_wrapper + def call(self, a): + return a + class CM(type): + __call__ = A().call + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('descriptor'): + class CM(type): + @custom_descriptor + @identity_wrapper + def __call__(self, a): + return a + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + self.assertEqual(self.signature(C.__call__), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + self.assertEqual(self.signature(C, follow_wrapped=False), + varargs_signature) + self.assertEqual(self.signature(C.__call__, follow_wrapped=False), + varargs_signature) + + def test_signature_on_class_with_wrapped_init(self): + class C: + @identity_wrapper + def __init__(self, b): + pass + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('classmethod'): + class C: + @classmethod + @identity_wrapper + def __init__(cls, b): + pass + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class C: + @staticmethod + @identity_wrapper + def __init__(b): + pass + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + @identity_wrapper + def call(self, a): + pass + + class C: + __init__ = A().call + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partial'): + class C: + __init__ = functools.partial(identity_wrapper(lambda x, a: None), 2) + + with self.assertWarns(FutureWarning): + C(1) # does not raise + with self.assertWarns(FutureWarning): + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class C: + @identity_wrapper + def _init(self, x, a): + self.a = (x, a) + __init__ = functools.partialmethod(_init, 2) + + self.assertEqual(C(1).a, (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('descriptor'): + class C: + @custom_descriptor + @identity_wrapper + def __init__(self, a): + pass + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + self.assertEqual(self.signature(C.__init__), + ((('self', ..., ..., "positional_or_keyword"), + ('a', ..., ..., "positional_or_keyword")), + ...)) + + self.assertEqual(self.signature(C, follow_wrapped=False), + varargs_signature) + if support.MISSING_C_DOCSTRINGS: + self.assertRaisesRegex( + ValueError, "no signature found", + self.signature, C.__new__, follow_wrapped=False, + ) + else: + self.assertEqual(self.signature(C.__new__, follow_wrapped=False), + varargs_signature) + + def test_signature_on_class_with_wrapped_new(self): + with self.subTest('FunctionType'): + class C: + @identity_wrapper + def __new__(cls, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('classmethod'): + class C: + @classmethod + @identity_wrapper + def __new__(cls, cls2, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class C: + @staticmethod + @identity_wrapper + def __new__(cls, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + @identity_wrapper + def call(self, cls, a): + return a + class C: + __new__ = A().call + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partial'): + class C: + __new__ = functools.partial(identity_wrapper(lambda x, cls, a: (x, a)), 2) + + self.assertEqual(C(1), (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class C: + __new__ = functools.partialmethod(identity_wrapper(lambda cls, x, a: (x, a)), 2) + + self.assertEqual(C(1), (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('descriptor'): + class C: + @custom_descriptor + @identity_wrapper + def __new__(cls, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + self.assertEqual(self.signature(C.__new__), + ((('cls', ..., ..., "positional_or_keyword"), + ('a', ..., ..., "positional_or_keyword")), + ...)) + + self.assertEqual(self.signature(C, follow_wrapped=False), + varargs_signature) + self.assertEqual(self.signature(C.__new__, follow_wrapped=False), + varargs_signature) + + def test_signature_on_class_with_init(self): + class C: + def __init__(self, b): + pass + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('classmethod'): + class C: + @classmethod + def __init__(cls, b): + pass + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class C: + @staticmethod + def __init__(b): + pass + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + def call(self, a): + pass + class C: + __init__ = A().call + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partial'): + class C: + __init__ = functools.partial(lambda x, a: None, 2) + + with self.assertWarns(FutureWarning): + C(1) # does not raise + with self.assertWarns(FutureWarning): + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class C: + def _init(self, x, a): + self.a = (x, a) + __init__ = functools.partialmethod(_init, 2) + + self.assertEqual(C(1).a, (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + def test_signature_on_class_with_new(self): + with self.subTest('FunctionType'): + class C: + def __new__(cls, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('classmethod'): + class C: + @classmethod + def __new__(cls, cls2, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class C: + @staticmethod + def __new__(cls, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + def call(self, cls, a): + return a + class C: + __new__ = A().call + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partial'): + class C: + __new__ = functools.partial(lambda x, cls, a: (x, a), 2) + + self.assertEqual(C(1), (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class C: + __new__ = functools.partialmethod(lambda cls, x, a: (x, a), 2) + + self.assertEqual(C(1), (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('BuiltinMethodType'): + class C: + __new__ = str.__subclasscheck__ + + self.assertEqual(C(), False) + # TODO: Support BuiltinMethodType + # self.assertEqual(self.signature(C), ((), ...)) + self.assertRaises(ValueError, self.signature, C) + + with self.subTest('MethodWrapperType'): + class C: + __new__ = type.__or__.__get__(int, type) + + self.assertEqual(C(), C | int) + # TODO: Support MethodWrapperType + # self.assertEqual(self.signature(C), ((), ...)) + self.assertRaises(ValueError, self.signature, C) + + # TODO: Test ClassMethodDescriptorType + + with self.subTest('MethodDescriptorType'): + class C: + __new__ = type.__dict__['__subclasscheck__'] + + self.assertEqual(C(C), True) + self.assertEqual(self.signature(C), self.signature(C.__subclasscheck__)) + + with self.subTest('WrapperDescriptorType'): + class C: + __new__ = type.__or__ + + self.assertEqual(C(int), C | int) + # TODO: Support WrapperDescriptorType + # self.assertEqual(self.signature(C), self.signature(C.__or__)) + self.assertRaises(ValueError, self.signature, C) + + def test_signature_on_subclass(self): + class A: + def __new__(cls, a=1, *args, **kwargs): + return object.__new__(cls) + class B(A): + def __init__(self, b): + pass + class C(A): + def __new__(cls, a=1, b=2, *args, **kwargs): + return object.__new__(cls) + class D(A): + pass + + self.assertEqual(self.signature(B), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + self.assertEqual(self.signature(C), + ((('a', 1, ..., 'positional_or_keyword'), + ('b', 2, ..., 'positional_or_keyword'), + ('args', ..., ..., 'var_positional'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + self.assertEqual(self.signature(D), + ((('a', 1, ..., 'positional_or_keyword'), + ('args', ..., ..., 'var_positional'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + def test_signature_on_generic_subclass(self): + from typing import Generic, TypeVar + + T = TypeVar('T') + + class A(Generic[T]): + def __init__(self, *, a: int) -> None: + pass + + self.assertEqual(self.signature(A), + ((('a', ..., int, 'keyword_only'),), + None)) + + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_on_class_without_init(self): + # Test classes without user-defined __init__ or __new__ + class C: pass + self.assertEqual(str(inspect.signature(C)), '()') + class D(C): pass + self.assertEqual(str(inspect.signature(D)), '()') + + # Test meta-classes without user-defined __init__ or __new__ + class C(type): pass + class D(C): pass + self.assertEqual(C('A', (), {}).__name__, 'A') + # TODO: Support type. + with self.assertRaisesRegex(ValueError, "callable.*is not supported"): + self.assertEqual(inspect.signature(C), None) + self.assertEqual(D('A', (), {}).__name__, 'A') + with self.assertRaisesRegex(ValueError, "callable.*is not supported"): + self.assertEqual(inspect.signature(D), None) + + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_on_builtin_class(self): + expected = ('(file, protocol=None, fix_imports=True, ' + 'buffer_callback=None)') + self.assertEqual(str(inspect.signature(_pickle.Pickler)), expected) + + class P(_pickle.Pickler): pass + class EmptyTrait: pass + class P2(EmptyTrait, P): pass + self.assertEqual(str(inspect.signature(P)), expected) + self.assertEqual(str(inspect.signature(P2)), expected) + + class P3(P2): + def __init__(self, spam): + pass + self.assertEqual(str(inspect.signature(P3)), '(spam)') + + class MetaP(type): + def __call__(cls, foo, bar): + pass + class P4(P2, metaclass=MetaP): + pass + self.assertEqual(str(inspect.signature(P4)), '(foo, bar)') + + def test_signature_on_callable_objects(self): + class Foo: + def __call__(self, a): + pass + + self.assertEqual(self.signature(Foo()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + class Spam: + pass + with self.assertRaisesRegex(TypeError, "is not a callable object"): + inspect.signature(Spam()) + + class Bar(Spam, Foo): + pass + + self.assertEqual(self.signature(Bar()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('classmethod'): + class C: + @classmethod + def __call__(cls, a): + pass + + self.assertEqual(self.signature(C()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class C: + @staticmethod + def __call__(a): + pass + + self.assertEqual(self.signature(C()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + def call(self, a): + return a + class C: + __call__ = A().call + + self.assertEqual(C()(1), 1) + self.assertEqual(self.signature(C()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partial'): + class C: + __call__ = functools.partial(lambda x, a: (x, a), 2) + + c = C() + with self.assertWarns(FutureWarning): + self.assertEqual(c(1), (2, 1)) + with self.assertWarns(FutureWarning): + self.assertEqual(self.signature(c), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class C: + __call__ = functools.partialmethod(lambda self, x, a: (x, a), 2) + + self.assertEqual(C()(1), (2, 1)) + self.assertEqual(self.signature(C()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('BuiltinMethodType'): + class C: + __call__ = ':'.join + + self.assertEqual(C()(['a', 'bc']), 'a:bc') + self.assertEqual(self.signature(C()), self.signature(''.join)) + + with self.subTest('MethodWrapperType'): + class C: + __call__ = (2).__pow__ + + self.assertEqual(C()(3), 8) + if not support.MISSING_C_DOCSTRINGS: + self.assertEqual(self.signature(C()), self.signature((0).__pow__)) + + with self.subTest('ClassMethodDescriptorType'): + class C(dict): + __call__ = dict.__dict__['fromkeys'] + + res = C()([1, 2], 3) + self.assertEqual(res, {1: 3, 2: 3}) + self.assertEqual(type(res), C) + if not support.MISSING_C_DOCSTRINGS: + self.assertEqual(self.signature(C()), self.signature(dict.fromkeys)) + + with self.subTest('MethodDescriptorType'): + class C(str): + __call__ = str.join + + self.assertEqual(C(':')(['a', 'bc']), 'a:bc') + self.assertEqual(self.signature(C()), self.signature(''.join)) + + with self.subTest('WrapperDescriptorType'): + class C(int): + __call__ = int.__pow__ + + self.assertEqual(C(2)(3), 8) + if not support.MISSING_C_DOCSTRINGS: + self.assertEqual(self.signature(C()), self.signature((0).__pow__)) + + with self.subTest('MemberDescriptorType'): + class C: + __slots__ = '__call__' + c = C() + c.__call__ = lambda a: a + self.assertEqual(c(1), 1) + self.assertEqual(self.signature(c), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + def test_signature_on_callable_objects_with_text_signature_attr(self): + class C: + __text_signature__ = '(a, /, b, c=True)' + def __call__(self, *args, **kwargs): + pass + + if not support.MISSING_C_DOCSTRINGS: + self.assertEqual(self.signature(C), ((), ...)) + self.assertEqual(self.signature(C()), + ((('a', ..., ..., "positional_only"), + ('b', ..., ..., "positional_or_keyword"), + ('c', True, ..., "positional_or_keyword"), + ), + ...)) + + c = C() + c.__text_signature__ = '(x, y)' + self.assertEqual(self.signature(c), + ((('x', ..., ..., "positional_or_keyword"), + ('y', ..., ..., "positional_or_keyword"), + ), + ...)) + + def test_signature_on_wrapper(self): + class Wrapper: + def __call__(self, b): + pass + wrapper = Wrapper() + wrapper.__wrapped__ = lambda a: None + self.assertEqual(self.signature(wrapper), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + # wrapper loop: + wrapper = Wrapper() + wrapper.__wrapped__ = wrapper + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + self.signature(wrapper) + + def test_signature_on_lambdas(self): + self.assertEqual(self.signature((lambda a=10: a)), + ((('a', 10, ..., "positional_or_keyword"),), + ...)) + + def test_signature_on_mocks(self): + # https://github.com/python/cpython/issues/96127 + for mock in ( + unittest.mock.Mock(), + unittest.mock.AsyncMock(), + unittest.mock.MagicMock(), + ): + with self.subTest(mock=mock): + self.assertEqual(str(inspect.signature(mock)), '(*args, **kwargs)') + + def test_signature_on_noncallable_mocks(self): + for mock in ( + unittest.mock.NonCallableMock(), + unittest.mock.NonCallableMagicMock(), + ): + with self.subTest(mock=mock): + with self.assertRaises(TypeError): + inspect.signature(mock) + + def test_signature_equality(self): + def foo(a, *, b:int) -> float: pass + self.assertFalse(inspect.signature(foo) == 42) + self.assertTrue(inspect.signature(foo) != 42) + self.assertTrue(inspect.signature(foo) == ALWAYS_EQ) + self.assertFalse(inspect.signature(foo) != ALWAYS_EQ) + + def bar(a, *, b:int) -> float: pass + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def bar(a, *, b:int) -> int: pass + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def bar(a, *, b:int): pass + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def bar(a, *, b:int=42) -> float: pass + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def bar(a, *, c) -> float: pass + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def bar(a, b:int) -> float: pass + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + def spam(b:int, a) -> float: pass + self.assertFalse(inspect.signature(spam) == inspect.signature(bar)) + self.assertTrue(inspect.signature(spam) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(spam)), hash(inspect.signature(bar))) + + def foo(*, a, b, c): pass + def bar(*, c, b, a): pass + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def foo(*, a=1, b, c): pass + def bar(*, c, b, a=1): pass + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def foo(pos, *, a=1, b, c): pass + def bar(pos, *, c, b, a=1): pass + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def foo(pos, *, a, b, c): pass + def bar(pos, *, c, b, a=1): pass + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def foo(pos, *args, a=42, b, c, **kwargs:int): pass + def bar(pos, *args, c, b, a=42, **kwargs:int): pass + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def test_signature_hashable(self): + S = inspect.Signature + P = inspect.Parameter + + def foo(a): pass + foo_sig = inspect.signature(foo) + + manual_sig = S(parameters=[P('a', P.POSITIONAL_OR_KEYWORD)]) + + self.assertEqual(hash(foo_sig), hash(manual_sig)) + self.assertNotEqual(hash(foo_sig), + hash(manual_sig.replace(return_annotation='spam'))) + + def bar(a) -> 1: pass + self.assertNotEqual(hash(foo_sig), hash(inspect.signature(bar))) + + def foo(a={}): pass + with self.assertRaisesRegex(TypeError, 'unhashable type'): + hash(inspect.signature(foo)) + + def foo(a) -> {}: pass + with self.assertRaisesRegex(TypeError, 'unhashable type'): + hash(inspect.signature(foo)) + + def test_signature_str(self): + def foo(a:int=1, *, b, c=None, **kwargs) -> 42: + pass + self.assertEqual(str(inspect.signature(foo)), + '(a: int = 1, *, b, c=None, **kwargs) -> 42') + self.assertEqual(str(inspect.signature(foo)), + inspect.signature(foo).format()) + + def foo(a:int=1, *args, b, c=None, **kwargs) -> 42: + pass + self.assertEqual(str(inspect.signature(foo)), + '(a: int = 1, *args, b, c=None, **kwargs) -> 42') + self.assertEqual(str(inspect.signature(foo)), + inspect.signature(foo).format()) + + def foo(): + pass + self.assertEqual(str(inspect.signature(foo)), '()') + self.assertEqual(str(inspect.signature(foo)), + inspect.signature(foo).format()) + + def foo(a: list[str]) -> tuple[str, float]: + pass + self.assertEqual(str(inspect.signature(foo)), + '(a: list[str]) -> tuple[str, float]') + self.assertEqual(str(inspect.signature(foo)), + inspect.signature(foo).format()) + + from typing import Tuple + def foo(a: list[str]) -> Tuple[str, float]: + pass + self.assertEqual(str(inspect.signature(foo)), + '(a: list[str]) -> Tuple[str, float]') + self.assertEqual(str(inspect.signature(foo)), + inspect.signature(foo).format()) + + def test_signature_str_positional_only(self): + P = inspect.Parameter + S = inspect.Signature + + def test(a_po, /, *, b, **kwargs): + return a_po, kwargs + + self.assertEqual(str(inspect.signature(test)), + '(a_po, /, *, b, **kwargs)') + self.assertEqual(str(inspect.signature(test)), + inspect.signature(test).format()) + + test = S(parameters=[P('foo', P.POSITIONAL_ONLY)]) + self.assertEqual(str(test), '(foo, /)') + self.assertEqual(str(test), test.format()) + + test = S(parameters=[P('foo', P.POSITIONAL_ONLY), + P('bar', P.VAR_KEYWORD)]) + self.assertEqual(str(test), '(foo, /, **bar)') + self.assertEqual(str(test), test.format()) + + test = S(parameters=[P('foo', P.POSITIONAL_ONLY), + P('bar', P.VAR_POSITIONAL)]) + self.assertEqual(str(test), '(foo, /, *bar)') + self.assertEqual(str(test), test.format()) + + def test_signature_format(self): + from typing import Annotated, Literal + + def func(x: Annotated[int, 'meta'], y: Literal['a', 'b'], z: 'LiteralString'): + pass + + expected_singleline = "(x: Annotated[int, 'meta'], y: Literal['a', 'b'], z: 'LiteralString')" + expected_multiline = """( + x: Annotated[int, 'meta'], + y: Literal['a', 'b'], + z: 'LiteralString' +)""" + self.assertEqual( + inspect.signature(func).format(), + expected_singleline, + ) + self.assertEqual( + inspect.signature(func).format(max_width=None), + expected_singleline, + ) + self.assertEqual( + inspect.signature(func).format(max_width=len(expected_singleline)), + expected_singleline, + ) + self.assertEqual( + inspect.signature(func).format(max_width=len(expected_singleline) - 1), + expected_multiline, + ) + self.assertEqual( + inspect.signature(func).format(max_width=0), + expected_multiline, + ) + self.assertEqual( + inspect.signature(func).format(max_width=-1), + expected_multiline, + ) + + def test_signature_format_all_arg_types(self): + from typing import Annotated, Literal + + def func( + x: Annotated[int, 'meta'], + /, + y: Literal['a', 'b'], + *, + z: 'LiteralString', + **kwargs: object, + ) -> None: + pass + + expected_multiline = """( + x: Annotated[int, 'meta'], + /, + y: Literal['a', 'b'], + *, + z: 'LiteralString', + **kwargs: object +) -> None""" + self.assertEqual( + inspect.signature(func).format(max_width=-1), + expected_multiline, + ) + + def test_signature_replace_parameters(self): + def test(a, b) -> 42: + pass + + sig = inspect.signature(test) + parameters = sig.parameters + sig = sig.replace(parameters=list(parameters.values())[1:]) + self.assertEqual(list(sig.parameters), ['b']) + self.assertEqual(sig.parameters['b'], parameters['b']) + self.assertEqual(sig.return_annotation, 42) + sig = sig.replace(parameters=()) + self.assertEqual(dict(sig.parameters), {}) + + sig = inspect.signature(test) + parameters = sig.parameters + sig = copy.replace(sig, parameters=list(parameters.values())[1:]) + self.assertEqual(list(sig.parameters), ['b']) + self.assertEqual(sig.parameters['b'], parameters['b']) + self.assertEqual(sig.return_annotation, 42) + sig = copy.replace(sig, parameters=()) + self.assertEqual(dict(sig.parameters), {}) + + def test_signature_replace_anno(self): + def test() -> 42: + pass + + sig = inspect.signature(test) + sig = sig.replace(return_annotation=None) + self.assertIs(sig.return_annotation, None) + sig = sig.replace(return_annotation=sig.empty) + self.assertIs(sig.return_annotation, sig.empty) + sig = sig.replace(return_annotation=42) + self.assertEqual(sig.return_annotation, 42) + self.assertEqual(sig, inspect.signature(test)) + + sig = inspect.signature(test) + sig = copy.replace(sig, return_annotation=None) + self.assertIs(sig.return_annotation, None) + sig = copy.replace(sig, return_annotation=sig.empty) + self.assertIs(sig.return_annotation, sig.empty) + sig = copy.replace(sig, return_annotation=42) + self.assertEqual(sig.return_annotation, 42) + self.assertEqual(sig, inspect.signature(test)) + + def test_signature_replaced(self): + def test(): + pass + + spam_param = inspect.Parameter('spam', inspect.Parameter.POSITIONAL_ONLY) + sig = test.__signature__ = inspect.Signature(parameters=(spam_param,)) + self.assertEqual(sig, inspect.signature(test)) + + def test_signature_on_mangled_parameters(self): + class Spam: + def foo(self, __p1:1=2, *, __p2:2=3): + pass + class Ham(Spam): + pass + + self.assertEqual(self.signature(Spam.foo), + ((('self', ..., ..., "positional_or_keyword"), + ('_Spam__p1', 2, 1, "positional_or_keyword"), + ('_Spam__p2', 3, 2, "keyword_only")), + ...)) + + self.assertEqual(self.signature(Spam.foo), + self.signature(Ham.foo)) + + def test_signature_from_callable_python_obj(self): + class MySignature(inspect.Signature): pass + def foo(a, *, b:1): pass + foo_sig = MySignature.from_callable(foo) + self.assertIsInstance(foo_sig, MySignature) + + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_from_callable_class(self): + # A regression test for a class inheriting its signature from `object`. + class MySignature(inspect.Signature): pass + class foo: pass + foo_sig = MySignature.from_callable(foo) + self.assertIsInstance(foo_sig, MySignature) + + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_from_callable_builtin_obj(self): + class MySignature(inspect.Signature): pass + sig = MySignature.from_callable(_pickle.Pickler) + self.assertIsInstance(sig, MySignature) + + def test_signature_definition_order_preserved_on_kwonly(self): + for fn in signatures_with_lexicographic_keyword_only_parameters(): + signature = inspect.signature(fn) + l = list(signature.parameters) + sorted_l = sorted(l) + self.assertTrue(l) + self.assertEqual(l, sorted_l) + signature = inspect.signature(unsorted_keyword_only_parameters_fn) + l = list(signature.parameters) + self.assertEqual(l, unsorted_keyword_only_parameters) + + def test_signater_parameters_is_ordered(self): + p1 = inspect.signature(lambda x, y: None).parameters + p2 = inspect.signature(lambda y, x: None).parameters + self.assertNotEqual(p1, p2) + + def test_signature_annotations_with_local_namespaces(self): + class Foo: ... + def func(foo: Foo) -> int: pass + def func2(foo: Foo, bar: 'Bar') -> int: pass + + for signature_func in (inspect.signature, inspect.Signature.from_callable): + with self.subTest(signature_func = signature_func): + sig1 = signature_func(func) + self.assertEqual(sig1.return_annotation, int) + self.assertEqual(sig1.parameters['foo'].annotation, Foo) + + sig2 = signature_func(func, locals=locals()) + self.assertEqual(sig2.return_annotation, int) + self.assertEqual(sig2.parameters['foo'].annotation, Foo) + + sig3 = signature_func(func2, globals={'Bar': int}, locals=locals()) + self.assertEqual(sig3.return_annotation, int) + self.assertEqual(sig3.parameters['foo'].annotation, Foo) + self.assertEqual(sig3.parameters['bar'].annotation, 'Bar') + + def test_signature_eval_str(self): + isa = inspect_stringized_annotations + sig = inspect.Signature + par = inspect.Parameter + PORK = inspect.Parameter.POSITIONAL_OR_KEYWORD + for signature_func in (inspect.signature, inspect.Signature.from_callable): + with self.subTest(signature_func = signature_func): + self.assertEqual( + signature_func(isa.MyClass), + sig( + parameters=( + par('a', PORK), + par('b', PORK), + ))) + self.assertEqual( + signature_func(isa.function), + sig( + return_annotation='MyClass', + parameters=( + par('a', PORK, annotation='int'), + par('b', PORK, annotation='str'), + ))) + self.assertEqual( + signature_func(isa.function2), + sig( + return_annotation='MyClass', + parameters=( + par('a', PORK, annotation='int'), + par('b', PORK, annotation="'str'"), + par('c', PORK, annotation="MyClass"), + ))) + self.assertEqual( + signature_func(isa.function3), + sig( + parameters=( + par('a', PORK, annotation="'int'"), + par('b', PORK, annotation="'str'"), + par('c', PORK, annotation="'MyClass'"), + ))) + + if not MISSING_C_DOCSTRINGS: + self.assertEqual(signature_func(isa.UnannotatedClass), sig()) + self.assertEqual(signature_func(isa.unannotated_function), + sig( + parameters=( + par('a', PORK), + par('b', PORK), + par('c', PORK), + ))) + + self.assertEqual( + signature_func(isa.MyClass, eval_str=True), + sig( + parameters=( + par('a', PORK), + par('b', PORK), + ))) + self.assertEqual( + signature_func(isa.function, eval_str=True), + sig( + return_annotation=isa.MyClass, + parameters=( + par('a', PORK, annotation=int), + par('b', PORK, annotation=str), + ))) + self.assertEqual( + signature_func(isa.function2, eval_str=True), + sig( + return_annotation=isa.MyClass, + parameters=( + par('a', PORK, annotation=int), + par('b', PORK, annotation='str'), + par('c', PORK, annotation=isa.MyClass), + ))) + self.assertEqual( + signature_func(isa.function3, eval_str=True), + sig( + parameters=( + par('a', PORK, annotation='int'), + par('b', PORK, annotation='str'), + par('c', PORK, annotation='MyClass'), + ))) + + globalns = {'int': float, 'str': complex} + localns = {'str': tuple, 'MyClass': dict} + with self.assertRaises(NameError): + signature_func(isa.function, eval_str=True, globals=globalns) + + self.assertEqual( + signature_func(isa.function, eval_str=True, locals=localns), + sig( + return_annotation=dict, + parameters=( + par('a', PORK, annotation=int), + par('b', PORK, annotation=tuple), + ))) + + self.assertEqual( + signature_func(isa.function, eval_str=True, globals=globalns, locals=localns), + sig( + return_annotation=dict, + parameters=( + par('a', PORK, annotation=float), + par('b', PORK, annotation=tuple), + ))) + + def test_signature_none_annotation(self): + class funclike: + # Has to be callable, and have correct + # __code__, __annotations__, __defaults__, __name__, + # and __kwdefaults__ attributes + + def __init__(self, func): + self.__name__ = func.__name__ + self.__code__ = func.__code__ + self.__annotations__ = func.__annotations__ + self.__defaults__ = func.__defaults__ + self.__kwdefaults__ = func.__kwdefaults__ + self.func = func + + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) + + def foo(): pass + foo = funclike(foo) + foo.__annotations__ = None + for signature_func in (inspect.signature, inspect.Signature.from_callable): + with self.subTest(signature_func = signature_func): + self.assertEqual(signature_func(foo), inspect.Signature()) + self.assertEqual(inspect.get_annotations(foo), {}) + + def test_signature_as_str(self): + self.maxDiff = None + class S: + __signature__ = '(a, b=2)' + + self.assertEqual(self.signature(S), + ((('a', ..., ..., 'positional_or_keyword'), + ('b', 2, ..., 'positional_or_keyword')), + ...)) + + def test_signature_as_callable(self): + # __signature__ should be either a staticmethod or a bound classmethod + class S: + @classmethod + def __signature__(cls): + return '(a, b=2)' + + self.assertEqual(self.signature(S), + ((('a', ..., ..., 'positional_or_keyword'), + ('b', 2, ..., 'positional_or_keyword')), + ...)) + + class S: + @staticmethod + def __signature__(): + return '(a, b=2)' + + self.assertEqual(self.signature(S), + ((('a', ..., ..., 'positional_or_keyword'), + ('b', 2, ..., 'positional_or_keyword')), + ...)) + + def test_signature_on_derived_classes(self): + # gh-105080: Make sure that signatures are consistent on derived classes + + class B: + def __new__(self, *args, **kwargs): + return super().__new__(self) + def __init__(self, value): + self.value = value + + class D1(B): + def __init__(self, value): + super().__init__(value) + + class D2(D1): + pass + + self.assertEqual(inspect.signature(D2), inspect.signature(D1)) + + def test_signature_on_non_comparable(self): + class NoncomparableCallable: + def __call__(self, a): + pass + def __eq__(self, other): + 1/0 + self.assertEqual(self.signature(NoncomparableCallable()), + ((('a', ..., ..., 'positional_or_keyword'),), + ...)) + + +class TestParameterObject(unittest.TestCase): + def test_signature_parameter_kinds(self): + P = inspect.Parameter + self.assertTrue(P.POSITIONAL_ONLY < P.POSITIONAL_OR_KEYWORD < \ + P.VAR_POSITIONAL < P.KEYWORD_ONLY < P.VAR_KEYWORD) + + self.assertEqual(str(P.POSITIONAL_ONLY), 'POSITIONAL_ONLY') + self.assertTrue('POSITIONAL_ONLY' in repr(P.POSITIONAL_ONLY)) + + def test_signature_parameter_object(self): + p = inspect.Parameter('foo', default=10, + kind=inspect.Parameter.POSITIONAL_ONLY) + self.assertEqual(p.name, 'foo') + self.assertEqual(p.default, 10) + self.assertIs(p.annotation, p.empty) + self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY) + + with self.assertRaisesRegex(ValueError, "value '123' is " + "not a valid Parameter.kind"): + inspect.Parameter('foo', default=10, kind='123') + + with self.assertRaisesRegex(ValueError, 'not a valid parameter name'): + inspect.Parameter('1', kind=inspect.Parameter.VAR_KEYWORD) + + with self.assertRaisesRegex(ValueError, 'not a valid parameter name'): + inspect.Parameter('from', kind=inspect.Parameter.VAR_KEYWORD) + + with self.assertRaisesRegex(TypeError, 'name must be a str'): + inspect.Parameter(None, kind=inspect.Parameter.VAR_KEYWORD) + + with self.assertRaisesRegex(ValueError, + 'is not a valid parameter name'): + inspect.Parameter('$', kind=inspect.Parameter.VAR_KEYWORD) + + with self.assertRaisesRegex(ValueError, + 'is not a valid parameter name'): + inspect.Parameter('.a', kind=inspect.Parameter.VAR_KEYWORD) + + with self.assertRaisesRegex(ValueError, 'cannot have default values'): + inspect.Parameter('a', default=42, + kind=inspect.Parameter.VAR_KEYWORD) + + with self.assertRaisesRegex(ValueError, 'cannot have default values'): + inspect.Parameter('a', default=42, + kind=inspect.Parameter.VAR_POSITIONAL) + + p = inspect.Parameter('a', default=42, + kind=inspect.Parameter.POSITIONAL_OR_KEYWORD) + with self.assertRaisesRegex(ValueError, 'cannot have default values'): + p.replace(kind=inspect.Parameter.VAR_POSITIONAL) + + self.assertTrue(repr(p).startswith('<Parameter')) + self.assertTrue('"a=42"' in repr(p)) + + def test_signature_parameter_hashable(self): + P = inspect.Parameter + foo = P('foo', kind=P.POSITIONAL_ONLY) + self.assertEqual(hash(foo), hash(P('foo', kind=P.POSITIONAL_ONLY))) + self.assertNotEqual(hash(foo), hash(P('foo', kind=P.POSITIONAL_ONLY, + default=42))) + self.assertNotEqual(hash(foo), + hash(foo.replace(kind=P.VAR_POSITIONAL))) + + def test_signature_parameter_equality(self): + P = inspect.Parameter + p = P('foo', default=42, kind=inspect.Parameter.KEYWORD_ONLY) + + self.assertTrue(p == p) + self.assertFalse(p != p) + self.assertFalse(p == 42) + self.assertTrue(p != 42) + self.assertTrue(p == ALWAYS_EQ) + self.assertFalse(p != ALWAYS_EQ) + + self.assertTrue(p == P('foo', default=42, + kind=inspect.Parameter.KEYWORD_ONLY)) + self.assertFalse(p != P('foo', default=42, + kind=inspect.Parameter.KEYWORD_ONLY)) + + def test_signature_parameter_replace(self): + p = inspect.Parameter('foo', default=42, + kind=inspect.Parameter.KEYWORD_ONLY) + + self.assertIsNot(p.replace(), p) + self.assertEqual(p.replace(), p) + self.assertIsNot(copy.replace(p), p) + self.assertEqual(copy.replace(p), p) + + p2 = p.replace(annotation=1) + self.assertEqual(p2.annotation, 1) + p2 = p2.replace(annotation=p2.empty) + self.assertEqual(p2, p) + p3 = copy.replace(p, annotation=1) + self.assertEqual(p3.annotation, 1) + p3 = copy.replace(p3, annotation=p3.empty) + self.assertEqual(p3, p) + + p2 = p2.replace(name='bar') + self.assertEqual(p2.name, 'bar') + self.assertNotEqual(p2, p) + p3 = copy.replace(p3, name='bar') + self.assertEqual(p3.name, 'bar') + self.assertNotEqual(p3, p) + + with self.assertRaisesRegex(ValueError, + 'name is a required attribute'): + p2 = p2.replace(name=p2.empty) + with self.assertRaisesRegex(ValueError, + 'name is a required attribute'): + p3 = copy.replace(p3, name=p3.empty) + + p2 = p2.replace(name='foo', default=None) + self.assertIs(p2.default, None) + self.assertNotEqual(p2, p) + p3 = copy.replace(p3, name='foo', default=None) + self.assertIs(p3.default, None) + self.assertNotEqual(p3, p) + + p2 = p2.replace(name='foo', default=p2.empty) + self.assertIs(p2.default, p2.empty) + p3 = copy.replace(p3, name='foo', default=p3.empty) + self.assertIs(p3.default, p3.empty) + + p2 = p2.replace(default=42, kind=p2.POSITIONAL_OR_KEYWORD) + self.assertEqual(p2.kind, p2.POSITIONAL_OR_KEYWORD) + self.assertNotEqual(p2, p) + p3 = copy.replace(p3, default=42, kind=p3.POSITIONAL_OR_KEYWORD) + self.assertEqual(p3.kind, p3.POSITIONAL_OR_KEYWORD) + self.assertNotEqual(p3, p) + + with self.assertRaisesRegex(ValueError, + "value <class 'inspect._empty'> " + "is not a valid Parameter.kind"): + p2 = p2.replace(kind=p2.empty) + with self.assertRaisesRegex(ValueError, + "value <class 'inspect._empty'> " + "is not a valid Parameter.kind"): + p3 = copy.replace(p3, kind=p3.empty) + + p2 = p2.replace(kind=p2.KEYWORD_ONLY) + self.assertEqual(p2, p) + p3 = copy.replace(p3, kind=p3.KEYWORD_ONLY) + self.assertEqual(p3, p) + + def test_signature_parameter_positional_only(self): + with self.assertRaisesRegex(TypeError, 'name must be a str'): + inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY) + + @cpython_only + def test_signature_parameter_implicit(self): + with self.assertRaisesRegex(ValueError, + 'implicit arguments must be passed as ' + 'positional or keyword arguments, ' + 'not positional-only'): + inspect.Parameter('.0', kind=inspect.Parameter.POSITIONAL_ONLY) + + param = inspect.Parameter( + '.0', kind=inspect.Parameter.POSITIONAL_OR_KEYWORD) + self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_ONLY) + self.assertEqual(param.name, 'implicit0') + + def test_signature_parameter_immutability(self): + p = inspect.Parameter('spam', kind=inspect.Parameter.KEYWORD_ONLY) + + with self.assertRaises(AttributeError): + p.foo = 'bar' + + with self.assertRaises(AttributeError): + p.kind = 123 + + +class TestSignatureBind(unittest.TestCase): + @staticmethod + def call(func, *args, **kwargs): + sig = inspect.signature(func) + ba = sig.bind(*args, **kwargs) + # Prevent unexpected success of assertRaises(TypeError, ...) + try: + return func(*ba.args, **ba.kwargs) + except TypeError as e: + raise AssertionError from e + + def test_signature_bind_empty(self): + def test(): + return 42 + + self.assertEqual(self.call(test), 42) + with self.assertRaisesRegex(TypeError, 'too many positional arguments'): + self.call(test, 1) + with self.assertRaisesRegex(TypeError, 'too many positional arguments'): + self.call(test, 1, spam=10) + with self.assertRaisesRegex( + TypeError, "got an unexpected keyword argument 'spam'"): + + self.call(test, spam=1) + + def test_signature_bind_var(self): + def test(*args, **kwargs): + return args, kwargs + + self.assertEqual(self.call(test), ((), {})) + self.assertEqual(self.call(test, 1), ((1,), {})) + self.assertEqual(self.call(test, 1, 2), ((1, 2), {})) + self.assertEqual(self.call(test, foo='bar'), ((), {'foo': 'bar'})) + self.assertEqual(self.call(test, 1, foo='bar'), ((1,), {'foo': 'bar'})) + self.assertEqual(self.call(test, args=10), ((), {'args': 10})) + self.assertEqual(self.call(test, 1, 2, foo='bar'), + ((1, 2), {'foo': 'bar'})) + + def test_signature_bind_just_args(self): + def test(a, b, c): + return a, b, c + + self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3)) + + with self.assertRaisesRegex(TypeError, 'too many positional arguments'): + self.call(test, 1, 2, 3, 4) + + with self.assertRaisesRegex(TypeError, + "missing a required argument: 'b'"): + self.call(test, 1) + + with self.assertRaisesRegex(TypeError, + "missing a required argument: 'a'"): + self.call(test) + + def test(a, b, c=10): + return a, b, c + self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3)) + self.assertEqual(self.call(test, 1, 2), (1, 2, 10)) + + def test(a=1, b=2, c=3): + return a, b, c + self.assertEqual(self.call(test, a=10, c=13), (10, 2, 13)) + self.assertEqual(self.call(test, a=10), (10, 2, 3)) + self.assertEqual(self.call(test, b=10), (1, 10, 3)) + + def test_signature_bind_varargs_order(self): + def test(*args): + return args + + self.assertEqual(self.call(test), ()) + self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3)) + + def test_signature_bind_args_and_varargs(self): + def test(a, b, c=3, *args): + return a, b, c, args + + self.assertEqual(self.call(test, 1, 2, 3, 4, 5), (1, 2, 3, (4, 5))) + self.assertEqual(self.call(test, 1, 2), (1, 2, 3, ())) + self.assertEqual(self.call(test, b=1, a=2), (2, 1, 3, ())) + self.assertEqual(self.call(test, 1, b=2), (1, 2, 3, ())) + + with self.assertRaisesRegex(TypeError, + "multiple values for argument 'c'"): + self.call(test, 1, 2, 3, c=4) + + def test_signature_bind_just_kwargs(self): + def test(**kwargs): + return kwargs + + self.assertEqual(self.call(test), {}) + self.assertEqual(self.call(test, foo='bar', spam='ham'), + {'foo': 'bar', 'spam': 'ham'}) + + def test_signature_bind_args_and_kwargs(self): + def test(a, b, c=3, **kwargs): + return a, b, c, kwargs + + self.assertEqual(self.call(test, 1, 2), (1, 2, 3, {})) + self.assertEqual(self.call(test, 1, 2, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, b=2, a=1, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, a=1, b=2, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, 1, b=2, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, 1, b=2, c=4, foo='bar', spam='ham'), + (1, 2, 4, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, 1, 2, 4, foo='bar'), + (1, 2, 4, {'foo': 'bar'})) + self.assertEqual(self.call(test, c=5, a=4, b=3), + (4, 3, 5, {})) + + def test_signature_bind_kwonly(self): + def test(*, foo): + return foo + with self.assertRaisesRegex(TypeError, + 'too many positional arguments'): + self.call(test, 1) + self.assertEqual(self.call(test, foo=1), 1) + + def test(a, *, foo=1, bar): + return foo + with self.assertRaisesRegex(TypeError, + "missing a required argument: 'bar'"): + self.call(test, 1) + + def test(foo, *, bar): + return foo, bar + self.assertEqual(self.call(test, 1, bar=2), (1, 2)) + self.assertEqual(self.call(test, bar=2, foo=1), (1, 2)) + + with self.assertRaisesRegex( + TypeError, "got an unexpected keyword argument 'spam'"): + + self.call(test, bar=2, foo=1, spam=10) + + with self.assertRaisesRegex(TypeError, + 'too many positional arguments'): + self.call(test, 1, 2) + + with self.assertRaisesRegex(TypeError, + 'too many positional arguments'): + self.call(test, 1, 2, bar=2) + + with self.assertRaisesRegex( + TypeError, "got an unexpected keyword argument 'spam'"): + + self.call(test, 1, bar=2, spam='ham') + + with self.assertRaisesRegex(TypeError, + "missing a required keyword-only " + "argument: 'bar'"): + self.call(test, 1) + + def test(foo, *, bar, **bin): + return foo, bar, bin + self.assertEqual(self.call(test, 1, bar=2), (1, 2, {})) + self.assertEqual(self.call(test, foo=1, bar=2), (1, 2, {})) + self.assertEqual(self.call(test, 1, bar=2, spam='ham'), + (1, 2, {'spam': 'ham'})) + self.assertEqual(self.call(test, spam='ham', foo=1, bar=2), + (1, 2, {'spam': 'ham'})) + with self.assertRaisesRegex(TypeError, + "missing a required argument: 'foo'"): + self.call(test, spam='ham', bar=2) + self.assertEqual(self.call(test, 1, bar=2, bin=1, spam=10), + (1, 2, {'bin': 1, 'spam': 10})) + + def test_signature_bind_arguments(self): + def test(a, *args, b, z=100, **kwargs): + pass + sig = inspect.signature(test) + ba = sig.bind(10, 20, b=30, c=40, args=50, kwargs=60) + # we won't have 'z' argument in the bound arguments object, as we didn't + # pass it to the 'bind' + self.assertEqual(tuple(ba.arguments.items()), + (('a', 10), ('args', (20,)), ('b', 30), + ('kwargs', {'c': 40, 'args': 50, 'kwargs': 60}))) + self.assertEqual(ba.kwargs, + {'b': 30, 'c': 40, 'args': 50, 'kwargs': 60}) + self.assertEqual(ba.args, (10, 20)) + + def test_signature_bind_positional_only(self): + def test(a_po, b_po, c_po=3, /, foo=42, *, bar=50, **kwargs): + return a_po, b_po, c_po, foo, bar, kwargs + + self.assertEqual(self.call(test, 1, 2, 4, 5, bar=6), + (1, 2, 4, 5, 6, {})) + + self.assertEqual(self.call(test, 1, 2), + (1, 2, 3, 42, 50, {})) + + self.assertEqual(self.call(test, 1, 2, foo=4, bar=5), + (1, 2, 3, 4, 5, {})) + + self.assertEqual(self.call(test, 1, 2, foo=4, bar=5, c_po=10), + (1, 2, 3, 4, 5, {'c_po': 10})) + + self.assertEqual(self.call(test, 1, 2, 30, c_po=31, foo=4, bar=5), + (1, 2, 30, 4, 5, {'c_po': 31})) + + self.assertEqual(self.call(test, 1, 2, 30, foo=4, bar=5, c_po=31), + (1, 2, 30, 4, 5, {'c_po': 31})) + + self.assertEqual(self.call(test, 1, 2, c_po=4), + (1, 2, 3, 42, 50, {'c_po': 4})) + + with self.assertRaisesRegex(TypeError, "missing a required positional-only argument: 'a_po'"): + self.call(test, a_po=1, b_po=2) + + def without_var_kwargs(c_po=3, d_po=4, /): + return c_po, d_po + + with self.assertRaisesRegex( + TypeError, + "positional-only arguments passed as keyword arguments: 'c_po, d_po'", + ): + self.call(without_var_kwargs, c_po=33, d_po=44) + + def test_signature_bind_with_self_arg(self): + # Issue #17071: one of the parameters is named "self + def test(a, self, b): + pass + sig = inspect.signature(test) + ba = sig.bind(1, 2, 3) + self.assertEqual(ba.args, (1, 2, 3)) + ba = sig.bind(1, self=2, b=3) + self.assertEqual(ba.args, (1, 2, 3)) + + def test_signature_bind_vararg_name(self): + def test(a, *args): + return a, args + sig = inspect.signature(test) + + with self.assertRaisesRegex( + TypeError, "got an unexpected keyword argument 'args'"): + + sig.bind(a=0, args=1) + + def test(*args, **kwargs): + return args, kwargs + self.assertEqual(self.call(test, args=1), ((), {'args': 1})) + + sig = inspect.signature(test) + ba = sig.bind(args=1) + self.assertEqual(ba.arguments, {'kwargs': {'args': 1}}) + + @cpython_only + def test_signature_bind_implicit_arg(self): + # Issue #19611: getcallargs should work with comprehensions + def make_set(): + return set(z * z for z in range(5)) + gencomp_code = make_set.__code__.co_consts[1] + gencomp_func = types.FunctionType(gencomp_code, {}) + + iterator = iter(range(5)) + self.assertEqual(set(self.call(gencomp_func, iterator)), {0, 1, 4, 9, 16}) + + def test_signature_bind_posonly_kwargs(self): + def foo(bar, /, **kwargs): + return bar, kwargs.get(bar) + + sig = inspect.signature(foo) + result = sig.bind("pos-only", bar="keyword") + + self.assertEqual(result.kwargs, {"bar": "keyword"}) + self.assertIn(("bar", "pos-only"), result.arguments.items()) + + +class TestBoundArguments(unittest.TestCase): + def test_signature_bound_arguments_unhashable(self): + def foo(a): pass + ba = inspect.signature(foo).bind(1) + + with self.assertRaisesRegex(TypeError, 'unhashable type'): + hash(ba) + + def test_signature_bound_arguments_equality(self): + def foo(a): pass + ba = inspect.signature(foo).bind(1) + self.assertTrue(ba == ba) + self.assertFalse(ba != ba) + self.assertTrue(ba == ALWAYS_EQ) + self.assertFalse(ba != ALWAYS_EQ) + + ba2 = inspect.signature(foo).bind(1) + self.assertTrue(ba == ba2) + self.assertFalse(ba != ba2) + + ba3 = inspect.signature(foo).bind(2) + self.assertFalse(ba == ba3) + self.assertTrue(ba != ba3) + ba3.arguments['a'] = 1 + self.assertTrue(ba == ba3) + self.assertFalse(ba != ba3) + + def bar(b): pass + ba4 = inspect.signature(bar).bind(1) + self.assertFalse(ba == ba4) + self.assertTrue(ba != ba4) + + def foo(*, a, b): pass + sig = inspect.signature(foo) + ba1 = sig.bind(a=1, b=2) + ba2 = sig.bind(b=2, a=1) + self.assertTrue(ba1 == ba2) + self.assertFalse(ba1 != ba2) + + def test_signature_bound_arguments_pickle(self): + def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass + sig = inspect.signature(foo) + ba = sig.bind(20, 30, z={}) + + for ver in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(pickle_ver=ver): + ba_pickled = pickle.loads(pickle.dumps(ba, ver)) + self.assertEqual(ba, ba_pickled) + + def test_signature_bound_arguments_repr(self): + def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass + sig = inspect.signature(foo) + ba = sig.bind(20, 30, z={}) + self.assertRegex(repr(ba), r'<BoundArguments \(a=20,.*\}\}\)>') + + def test_signature_bound_arguments_apply_defaults(self): + def foo(a, b=1, *args, c:1={}, **kw): pass + sig = inspect.signature(foo) + + ba = sig.bind(20) + ba.apply_defaults() + self.assertEqual( + list(ba.arguments.items()), + [('a', 20), ('b', 1), ('args', ()), ('c', {}), ('kw', {})]) + + # Make sure that we preserve the order: + # i.e. 'c' should be *before* 'kw'. + ba = sig.bind(10, 20, 30, d=1) + ba.apply_defaults() + self.assertEqual( + list(ba.arguments.items()), + [('a', 10), ('b', 20), ('args', (30,)), ('c', {}), ('kw', {'d':1})]) + + # Make sure that BoundArguments produced by bind_partial() + # are supported. + def foo(a, b): pass + sig = inspect.signature(foo) + ba = sig.bind_partial(20) + ba.apply_defaults() + self.assertEqual( + list(ba.arguments.items()), + [('a', 20)]) + + # Test no args + def foo(): pass + sig = inspect.signature(foo) + ba = sig.bind() + ba.apply_defaults() + self.assertEqual(list(ba.arguments.items()), []) + + # Make sure a no-args binding still acquires proper defaults. + def foo(a='spam'): pass + sig = inspect.signature(foo) + ba = sig.bind() + ba.apply_defaults() + self.assertEqual(list(ba.arguments.items()), [('a', 'spam')]) + + def test_signature_bound_arguments_arguments_type(self): + def foo(a): pass + ba = inspect.signature(foo).bind(1) + self.assertIs(type(ba.arguments), dict) + +class TestSignaturePrivateHelpers(unittest.TestCase): + def _strip_non_python_syntax(self, input, + clean_signature, self_parameter): + computed_clean_signature, \ + computed_self_parameter = \ + inspect._signature_strip_non_python_syntax(input) + self.assertEqual(computed_clean_signature, clean_signature) + self.assertEqual(computed_self_parameter, self_parameter) + + def test_signature_strip_non_python_syntax(self): + self._strip_non_python_syntax( + "($module, /, path, mode, *, dir_fd=None, " + + "effective_ids=False,\n follow_symlinks=True)", + "(module, /, path, mode, *, dir_fd=None, " + + "effective_ids=False, follow_symlinks=True)", + 0) + + self._strip_non_python_syntax( + "($module, word, salt, /)", + "(module, word, salt, /)", + 0) + + self._strip_non_python_syntax( + "(x, y=None, z=None, /)", + "(x, y=None, z=None, /)", + None) + + self._strip_non_python_syntax( + "(x, y=None, z=None)", + "(x, y=None, z=None)", + None) + + self._strip_non_python_syntax( + "(x,\n y=None,\n z = None )", + "(x, y=None, z=None)", + None) + + self._strip_non_python_syntax( + "", + "", + None) + + self._strip_non_python_syntax( + None, + None, + None) + +class TestSignatureDefinitions(unittest.TestCase): + # This test case provides a home for checking that particular APIs + # have signatures available for introspection + + @staticmethod + def is_public(name): + return not name.startswith('_') or name.startswith('__') and name.endswith('__') + + @cpython_only + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def _test_module_has_signatures(self, module, + no_signature=(), unsupported_signature=(), + methods_no_signature={}, methods_unsupported_signature={}, + good_exceptions=()): + # This checks all builtin callables in CPython have signatures + # A few have signatures Signature can't yet handle, so we skip those + # since they will have to wait until PEP 457 adds the required + # introspection support to the inspect module + # Some others also haven't been converted yet for various other + # reasons, so we also skip those for the time being, but design + # the test to fail in order to indicate when it needs to be + # updated. + no_signature = no_signature or set() + # Check the signatures we expect to be there + ns = vars(module) + try: + names = set(module.__all__) + except AttributeError: + names = set(name for name in ns if self.is_public(name)) + for name, obj in sorted(ns.items()): + if name not in names: + continue + if not callable(obj): + continue + if (isinstance(obj, type) and + issubclass(obj, BaseException) and + name not in good_exceptions): + no_signature.add(name) + if name not in no_signature and name not in unsupported_signature: + with self.subTest('supported', builtin=name): + self.assertIsNotNone(inspect.signature(obj)) + if isinstance(obj, type): + with self.subTest(type=name): + self._test_builtin_methods_have_signatures(obj, + methods_no_signature.get(name, ()), + methods_unsupported_signature.get(name, ())) + # Check callables that haven't been converted don't claim a signature + # This ensures this test will start failing as more signatures are + # added, so the affected items can be moved into the scope of the + # regression test above + for name in no_signature: + with self.subTest('none', builtin=name): + obj = ns[name] + self.assertIsNone(obj.__text_signature__) + self.assertRaises(ValueError, inspect.signature, obj) + for name in unsupported_signature: + with self.subTest('unsupported', builtin=name): + obj = ns[name] + self.assertIsNotNone(obj.__text_signature__) + self.assertRaises(ValueError, inspect.signature, obj) + + def _test_builtin_methods_have_signatures(self, cls, no_signature, unsupported_signature): + ns = vars(cls) + for name in ns: + obj = getattr(cls, name, None) + if not callable(obj) or isinstance(obj, type): + continue + if name not in no_signature and name not in unsupported_signature: + with self.subTest('supported', method=name): + self.assertIsNotNone(inspect.signature(obj)) + for name in no_signature: + with self.subTest('none', method=name): + self.assertIsNone(getattr(cls, name).__text_signature__) + self.assertRaises(ValueError, inspect.signature, getattr(cls, name)) + for name in unsupported_signature: + with self.subTest('unsupported', method=name): + self.assertIsNotNone(getattr(cls, name).__text_signature__) + self.assertRaises(ValueError, inspect.signature, getattr(cls, name)) + + def test_builtins_have_signatures(self): + no_signature = {'type', 'super', 'bytearray', 'bytes', 'dict', 'int', 'str'} + # These need PEP 457 groups + needs_groups = {"range", "slice", "dir", "getattr", + "next", "iter", "vars"} + no_signature |= needs_groups + # These have unrepresentable parameter default values of NULL + unsupported_signature = {"anext"} + # These need *args support in Argument Clinic + needs_varargs = {"min", "max", "__build_class__"} + no_signature |= needs_varargs + + methods_no_signature = { + 'dict': {'update'}, + 'object': {'__class__'}, + } + methods_unsupported_signature = { + 'bytearray': {'count', 'endswith', 'find', 'hex', 'index', 'rfind', 'rindex', 'startswith'}, + 'bytes': {'count', 'endswith', 'find', 'hex', 'index', 'rfind', 'rindex', 'startswith'}, + 'dict': {'pop'}, + 'int': {'__round__'}, + 'memoryview': {'cast', 'hex'}, + 'str': {'count', 'endswith', 'find', 'index', 'maketrans', 'rfind', 'rindex', 'startswith'}, + } + self._test_module_has_signatures(builtins, + no_signature, unsupported_signature, + methods_no_signature, methods_unsupported_signature) + + def test_types_module_has_signatures(self): + unsupported_signature = {'CellType'} + methods_no_signature = { + 'AsyncGeneratorType': {'athrow'}, + 'CoroutineType': {'throw'}, + 'GeneratorType': {'throw'}, + } + self._test_module_has_signatures(types, + unsupported_signature=unsupported_signature, + methods_no_signature=methods_no_signature) + + def test_sys_module_has_signatures(self): + no_signature = {'getsizeof', 'set_asyncgen_hooks'} + no_signature |= {name for name in ['getobjects'] + if hasattr(sys, name)} + self._test_module_has_signatures(sys, no_signature) + + def test_abc_module_has_signatures(self): + import abc + self._test_module_has_signatures(abc) + + def test_atexit_module_has_signatures(self): + import atexit + self._test_module_has_signatures(atexit) + + def test_codecs_module_has_signatures(self): + import codecs + methods_no_signature = {'StreamReader': {'charbuffertype'}} + self._test_module_has_signatures(codecs, + methods_no_signature=methods_no_signature) + + def test_collections_module_has_signatures(self): + no_signature = {'OrderedDict', 'defaultdict'} + unsupported_signature = {'deque'} + methods_no_signature = { + 'OrderedDict': {'update'}, + } + methods_unsupported_signature = { + 'deque': {'index'}, + 'OrderedDict': {'pop'}, + 'UserString': {'maketrans'}, + } + self._test_module_has_signatures(collections, + no_signature, unsupported_signature, + methods_no_signature, methods_unsupported_signature) + + def test_collections_abc_module_has_signatures(self): + import collections.abc + self._test_module_has_signatures(collections.abc) + + def test_errno_module_has_signatures(self): + import errno + self._test_module_has_signatures(errno) + + def test_faulthandler_module_has_signatures(self): + import faulthandler + unsupported_signature = {'dump_traceback', 'dump_traceback_later', 'enable'} + unsupported_signature |= {name for name in ['register'] + if hasattr(faulthandler, name)} + self._test_module_has_signatures(faulthandler, unsupported_signature=unsupported_signature) + + def test_functools_module_has_signatures(self): + no_signature = {'reduce'} + self._test_module_has_signatures(functools, no_signature) + + def test_gc_module_has_signatures(self): + import gc + no_signature = {'set_threshold'} + self._test_module_has_signatures(gc, no_signature) + + def test_io_module_has_signatures(self): + methods_no_signature = { + 'BufferedRWPair': {'read', 'peek', 'read1', 'readinto', 'readinto1', 'write'}, + } + self._test_module_has_signatures(io, + methods_no_signature=methods_no_signature) + + def test_itertools_module_has_signatures(self): + import itertools + no_signature = {'islice', 'repeat'} + self._test_module_has_signatures(itertools, no_signature) + + def test_locale_module_has_signatures(self): + import locale + self._test_module_has_signatures(locale) + + def test_marshal_module_has_signatures(self): + import marshal + self._test_module_has_signatures(marshal) + + def test_operator_module_has_signatures(self): + import operator + self._test_module_has_signatures(operator) + + def test_os_module_has_signatures(self): + unsupported_signature = {'chmod', 'utime'} + unsupported_signature |= {name for name in + ['get_terminal_size', 'posix_spawn', 'posix_spawnp', + 'register_at_fork', 'startfile'] + if hasattr(os, name)} + self._test_module_has_signatures(os, unsupported_signature=unsupported_signature) + + def test_pwd_module_has_signatures(self): + pwd = import_helper.import_module('pwd') + self._test_module_has_signatures(pwd) + + def test_re_module_has_signatures(self): + import re + methods_no_signature = {'Match': {'group'}} + self._test_module_has_signatures(re, + methods_no_signature=methods_no_signature, + good_exceptions={'error', 'PatternError'}) + + def test_signal_module_has_signatures(self): + import signal + self._test_module_has_signatures(signal) + + def test_stat_module_has_signatures(self): + import stat + self._test_module_has_signatures(stat) + + def test_string_module_has_signatures(self): + import string + self._test_module_has_signatures(string) + + def test_symtable_module_has_signatures(self): + import symtable + self._test_module_has_signatures(symtable) + + def test_sysconfig_module_has_signatures(self): + import sysconfig + self._test_module_has_signatures(sysconfig) + + def test_threading_module_has_signatures(self): + import threading + self._test_module_has_signatures(threading) + self.assertIsNotNone(inspect.signature(threading.__excepthook__)) + + def test_thread_module_has_signatures(self): + import _thread + no_signature = {'RLock'} + self._test_module_has_signatures(_thread, no_signature) + + def test_time_module_has_signatures(self): + no_signature = { + 'asctime', 'ctime', 'get_clock_info', 'gmtime', 'localtime', + 'strftime', 'strptime' + } + no_signature |= {name for name in + ['clock_getres', 'clock_settime', 'clock_settime_ns', + 'pthread_getcpuclockid'] + if hasattr(time, name)} + self._test_module_has_signatures(time, no_signature) + + def test_tokenize_module_has_signatures(self): + import tokenize + self._test_module_has_signatures(tokenize) + + def test_tracemalloc_module_has_signatures(self): + import tracemalloc + self._test_module_has_signatures(tracemalloc) + + def test_typing_module_has_signatures(self): + import typing + no_signature = {'ParamSpec', 'ParamSpecArgs', 'ParamSpecKwargs', + 'Text', 'TypeAliasType', 'TypeVar', 'TypeVarTuple'} + methods_no_signature = { + 'Generic': {'__class_getitem__', '__init_subclass__'}, + } + methods_unsupported_signature = { + 'Text': {'count', 'find', 'index', 'rfind', 'rindex', 'startswith', 'endswith', 'maketrans'}, + } + self._test_module_has_signatures(typing, no_signature, + methods_no_signature=methods_no_signature, + methods_unsupported_signature=methods_unsupported_signature) + + def test_warnings_module_has_signatures(self): + unsupported_signature = {'warn', 'warn_explicit'} + self._test_module_has_signatures(warnings, unsupported_signature=unsupported_signature) + + def test_weakref_module_has_signatures(self): + import weakref + no_signature = {'ReferenceType', 'ref'} + self._test_module_has_signatures(weakref, no_signature) + + def test_python_function_override_signature(self): + def func(*args, **kwargs): + pass + func.__text_signature__ = '($self, a, b=1, *args, c, d=2, **kwargs)' + sig = inspect.signature(func) + self.assertIsNotNone(sig) + self.assertEqual(str(sig), '(self, /, a, b=1, *args, c, d=2, **kwargs)') + + func.__text_signature__ = '($self, a, b=1, /, *args, c, d=2, **kwargs)' + sig = inspect.signature(func) + self.assertEqual(str(sig), '(self, a, b=1, /, *args, c, d=2, **kwargs)') + + func.__text_signature__ = '(self, a=1+2, b=4-3, c=1 | 3 | 16)' + sig = inspect.signature(func) + self.assertEqual(str(sig), '(self, a=3, b=1, c=19)') + + func.__text_signature__ = '(self, a=1,\nb=2,\n\n\n c=3)' + sig = inspect.signature(func) + self.assertEqual(str(sig), '(self, a=1, b=2, c=3)') + + func.__text_signature__ = '(self, x=does_not_exist)' + with self.assertRaises(ValueError): + inspect.signature(func) + func.__text_signature__ = '(self, x=sys, y=inspect)' + with self.assertRaises(ValueError): + inspect.signature(func) + func.__text_signature__ = '(self, 123)' + with self.assertRaises(ValueError): + inspect.signature(func) + + @support.requires_docstrings + def test_base_class_have_text_signature(self): + # see issue 43118 + from test.typinganndata.ann_module7 import BufferedReader + class MyBufferedReader(BufferedReader): + """buffer reader class.""" + + text_signature = BufferedReader.__text_signature__ + self.assertEqual(text_signature, '(raw, buffer_size=DEFAULT_BUFFER_SIZE)') + sig = inspect.signature(MyBufferedReader) + self.assertEqual(str(sig), '(raw, buffer_size=8192)') + + +class NTimesUnwrappable: + def __init__(self, n): + self.n = n + self._next = None + + @property + def __wrapped__(self): + if self.n <= 0: + raise Exception("Unwrapped too many times") + if self._next is None: + self._next = NTimesUnwrappable(self.n - 1) + return self._next + +class TestUnwrap(unittest.TestCase): + + def test_unwrap_one(self): + def func(a, b): + return a + b + wrapper = functools.lru_cache(maxsize=20)(func) + self.assertIs(inspect.unwrap(wrapper), func) + + def test_unwrap_several(self): + def func(a, b): + return a + b + wrapper = func + for __ in range(10): + @functools.wraps(wrapper) + def wrapper(): + pass + self.assertIsNot(wrapper.__wrapped__, func) + self.assertIs(inspect.unwrap(wrapper), func) + + def test_stop(self): + def func1(a, b): + return a + b + @functools.wraps(func1) + def func2(): + pass + @functools.wraps(func2) + def wrapper(): + pass + func2.stop_here = 1 + unwrapped = inspect.unwrap(wrapper, + stop=(lambda f: hasattr(f, "stop_here"))) + self.assertIs(unwrapped, func2) + + def test_cycle(self): + def func1(): pass + func1.__wrapped__ = func1 + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + inspect.unwrap(func1) + + def func2(): pass + func2.__wrapped__ = func1 + func1.__wrapped__ = func2 + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + inspect.unwrap(func1) + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + inspect.unwrap(func2) + + def test_unhashable(self): + def func(): pass + func.__wrapped__ = None + class C: + __hash__ = None + __wrapped__ = func + self.assertIsNone(inspect.unwrap(C())) + + def test_recursion_limit(self): + obj = NTimesUnwrappable(sys.getrecursionlimit() + 1) + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + inspect.unwrap(obj) + + def test_wrapped_descriptor(self): + self.assertIs(inspect.unwrap(NTimesUnwrappable), NTimesUnwrappable) + self.assertIs(inspect.unwrap(staticmethod), staticmethod) + self.assertIs(inspect.unwrap(classmethod), classmethod) + self.assertIs(inspect.unwrap(staticmethod(classmethod)), classmethod) + self.assertIs(inspect.unwrap(classmethod(staticmethod)), staticmethod) + + +class TestMain(unittest.TestCase): + def test_only_source(self): + module = importlib.import_module('unittest') + rc, out, err = assert_python_ok('-m', 'inspect', + 'unittest') + lines = out.decode().splitlines() + # ignore the final newline + self.assertEqual(lines[:-1], inspect.getsource(module).splitlines()) + self.assertEqual(err, b'') + + def test_custom_getattr(self): + def foo(): + pass + foo.__signature__ = 42 + with self.assertRaises(TypeError): + inspect.signature(foo) + + @unittest.skipIf(ThreadPoolExecutor is None, + 'threads required to test __qualname__ for source files') + def test_qualname_source(self): + rc, out, err = assert_python_ok('-m', 'inspect', + 'concurrent.futures:ThreadPoolExecutor') + lines = out.decode().splitlines() + # ignore the final newline + self.assertEqual(lines[:-1], + inspect.getsource(ThreadPoolExecutor).splitlines()) + self.assertEqual(err, b'') + + def test_builtins(self): + _, out, err = assert_python_failure('-m', 'inspect', + 'sys') + lines = err.decode().splitlines() + self.assertEqual(lines, ["Can't get info for builtin modules."]) + + def test_details(self): + module = importlib.import_module('unittest') + args = support.optim_args_from_interpreter_flags() + rc, out, err = assert_python_ok(*args, '-m', 'inspect', + 'unittest', '--details') + output = out.decode() + # Just a quick sanity check on the output + self.assertIn(module.__spec__.name, output) + self.assertIn(module.__name__, output) + self.assertIn(module.__spec__.origin, output) + self.assertIn(module.__file__, output) + self.assertIn(module.__spec__.cached, output) + self.assertIn(module.__cached__, output) + self.assertEqual(err, b'') + + +class TestReload(unittest.TestCase): + + src_before = textwrap.dedent("""\ +def foo(): + print("Bla") + """) + + src_after = textwrap.dedent("""\ +def foo(): + print("Oh no!") + """) + + def assertInspectEqual(self, path, source): + inspected_src = inspect.getsource(source) + with open(path, encoding='utf-8') as src: + self.assertEqual( + src.read().splitlines(True), + inspected_src.splitlines(True) + ) + + def test_getsource_reload(self): + # see issue 1218234 + with ready_to_import('reload_bug', self.src_before) as (name, path): + module = importlib.import_module(name) + self.assertInspectEqual(path, module) + with open(path, 'w', encoding='utf-8') as src: + src.write(self.src_after) + self.assertInspectEqual(path, module) + + +class TestRepl(unittest.TestCase): + + def spawn_repl(self, *args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): + """Run the Python REPL with the given arguments. + + kw is extra keyword args to pass to subprocess.Popen. Returns a Popen + object. + """ + + # To run the REPL without using a terminal, spawn python with the command + # line option '-i' and the process name set to '<stdin>'. + # The directory of argv[0] must match the directory of the Python + # executable for the Popen() call to python to succeed as the directory + # path may be used by Py_GetPath() to build the default module search + # path. + stdin_fname = os.path.join(os.path.dirname(sys.executable), "<stdin>") + cmd_line = [stdin_fname, '-E', '-i'] + cmd_line.extend(args) + + # Set TERM=vt100, for the rationale see the comments in spawn_python() of + # test.support.script_helper. + env = kw.setdefault('env', dict(os.environ)) + env['TERM'] = 'vt100' + return subprocess.Popen(cmd_line, + executable=sys.executable, + text=True, + stdin=subprocess.PIPE, + stdout=stdout, stderr=stderr, + **kw) + + def run_on_interactive_mode(self, source): + """Spawn a new Python interpreter, pass the given + input source code from the stdin and return the + result back. If the interpreter exits non-zero, it + raises a ValueError.""" + + process = self.spawn_repl() + process.stdin.write(source) + output = kill_python(process) + + if process.returncode != 0: + raise ValueError("Process didn't exit properly.") + return output + + @unittest.skipIf(not has_subprocess_support, "test requires subprocess") + def test_getsource(self): + output = self.run_on_interactive_mode(textwrap.dedent("""\ + def f(): + print(0) + return 1 + 2 + + import inspect + print(f"The source is: <<<{inspect.getsource(f)}>>>") + """)) + + expected = "The source is: <<<def f():\n print(0)\n return 1 + 2\n>>>" + self.assertIn(expected, output) + + + + +if __name__ == "__main__": + unittest.main() From d11604995ae28c58233ff9aa664cca3c83fe58bc Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Fri, 12 Dec 2025 16:18:20 +0900 Subject: [PATCH 491/819] Upgrade dataclasses from CPython 3.13.10 --- Lib/dataclasses.py | 743 +++++++++----- Lib/test/test_copy.py | 2 - .../__init__.py} | 970 +++++++++++++++++- .../test_dataclasses/dataclass_module_1.py | 32 + .../dataclass_module_1_str.py | 32 + .../test_dataclasses/dataclass_module_2.py | 32 + .../dataclass_module_2_str.py | 32 + .../test_dataclasses/dataclass_textanno.py | 12 + 8 files changed, 1533 insertions(+), 322 deletions(-) rename Lib/test/{test_dataclasses.py => test_dataclasses/__init__.py} (80%) create mode 100644 Lib/test/test_dataclasses/dataclass_module_1.py create mode 100644 Lib/test/test_dataclasses/dataclass_module_1_str.py create mode 100644 Lib/test/test_dataclasses/dataclass_module_2.py create mode 100644 Lib/test/test_dataclasses/dataclass_module_2_str.py create mode 100644 Lib/test/test_dataclasses/dataclass_textanno.py diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index e1687a117d6..7883ce78e57 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -4,11 +4,9 @@ import types import inspect import keyword -import builtins -import functools +import itertools import abc -import _thread -from types import FunctionType, GenericAlias +from reprlib import recursive_repr __all__ = ['dataclass', @@ -222,6 +220,30 @@ def __repr__(self): # https://bugs.python.org/issue33453 for details. _MODULE_IDENTIFIER_RE = re.compile(r'^(?:\s*(\w+)\s*\.)?\s*(\w+)') +# Atomic immutable types which don't require any recursive handling and for which deepcopy +# returns the same object. We can provide a fast-path for these types in asdict and astuple. +_ATOMIC_TYPES = frozenset({ + # Common JSON Serializable types + types.NoneType, + bool, + int, + float, + str, + # Other common types + complex, + bytes, + # Other types that are also unaffected by deepcopy + types.EllipsisType, + types.NotImplementedType, + types.CodeType, + types.BuiltinFunctionType, + types.FunctionType, + type, + range, + property, +}) + + class InitVar: __slots__ = ('type', ) @@ -229,7 +251,7 @@ def __init__(self, type): self.type = type def __repr__(self): - if isinstance(self.type, type) and not isinstance(self.type, GenericAlias): + if isinstance(self.type, type): type_name = self.type.__name__ else: # typing objects, e.g. List[int] @@ -279,6 +301,7 @@ def __init__(self, default, default_factory, init, repr, hash, compare, self.kw_only = kw_only self._field_type = None + @recursive_repr() def __repr__(self): return ('Field(' f'name={self.name!r},' @@ -297,7 +320,7 @@ def __repr__(self): # This is used to support the PEP 487 __set_name__ protocol in the # case where we're using a field that contains a descriptor as a # default value. For details on __set_name__, see - # https://www.python.org/dev/peps/pep-0487/#implementation-details. + # https://peps.python.org/pep-0487/#implementation-details. # # Note that in _process_class, this Field object is overwritten # with the default value, so the end result is a descriptor that @@ -309,7 +332,7 @@ def __set_name__(self, owner, name): # it. func(self.default, owner, name) - __class_getitem__ = classmethod(GenericAlias) + __class_getitem__ = classmethod(types.GenericAlias) class _DataclassParams: @@ -319,15 +342,25 @@ class _DataclassParams: 'order', 'unsafe_hash', 'frozen', + 'match_args', + 'kw_only', + 'slots', + 'weakref_slot', ) - def __init__(self, init, repr, eq, order, unsafe_hash, frozen): + def __init__(self, + init, repr, eq, order, unsafe_hash, frozen, + match_args, kw_only, slots, weakref_slot): self.init = init self.repr = repr self.eq = eq self.order = order self.unsafe_hash = unsafe_hash self.frozen = frozen + self.match_args = match_args + self.kw_only = kw_only + self.slots = slots + self.weakref_slot = weakref_slot def __repr__(self): return ('_DataclassParams(' @@ -336,7 +369,11 @@ def __repr__(self): f'eq={self.eq!r},' f'order={self.order!r},' f'unsafe_hash={self.unsafe_hash!r},' - f'frozen={self.frozen!r}' + f'frozen={self.frozen!r},' + f'match_args={self.match_args!r},' + f'kw_only={self.kw_only!r},' + f'slots={self.slots!r},' + f'weakref_slot={self.weakref_slot!r}' ')') @@ -388,49 +425,95 @@ def _tuple_str(obj_name, fields): return f'({",".join([f"{obj_name}.{f.name}" for f in fields])},)' -# This function's logic is copied from "recursive_repr" function in -# reprlib module to avoid dependency. -def _recursive_repr(user_function): - # Decorator to make a repr function return "..." for a recursive - # call. - repr_running = set() +class _FuncBuilder: + def __init__(self, globals): + self.names = [] + self.src = [] + self.globals = globals + self.locals = {} + self.overwrite_errors = {} + self.unconditional_adds = {} + + def add_fn(self, name, args, body, *, locals=None, return_type=MISSING, + overwrite_error=False, unconditional_add=False, decorator=None): + if locals is not None: + self.locals.update(locals) + + # Keep track if this method is allowed to be overwritten if it already + # exists in the class. The error is method-specific, so keep it with + # the name. We'll use this when we generate all of the functions in + # the add_fns_to_class call. overwrite_error is either True, in which + # case we'll raise an error, or it's a string, in which case we'll + # raise an error and append this string. + if overwrite_error: + self.overwrite_errors[name] = overwrite_error + + # Should this function always overwrite anything that's already in the + # class? The default is to not overwrite a function that already + # exists. + if unconditional_add: + self.unconditional_adds[name] = True + + self.names.append(name) + + if return_type is not MISSING: + self.locals[f'__dataclass_{name}_return_type__'] = return_type + return_annotation = f'->__dataclass_{name}_return_type__' + else: + return_annotation = '' + args = ','.join(args) + body = '\n'.join(body) + + # Compute the text of the entire function, add it to the text we're generating. + self.src.append(f'{f' {decorator}\n' if decorator else ''} def {name}({args}){return_annotation}:\n{body}') - @functools.wraps(user_function) - def wrapper(self): - key = id(self), _thread.get_ident() - if key in repr_running: - return '...' - repr_running.add(key) - try: - result = user_function(self) - finally: - repr_running.discard(key) - return result - return wrapper - - -def _create_fn(name, args, body, *, globals=None, locals=None, - return_type=MISSING): - # Note that we may mutate locals. Callers beware! - # The only callers are internal to this module, so no - # worries about external callers. - if locals is None: - locals = {} - return_annotation = '' - if return_type is not MISSING: - locals['_return_type'] = return_type - return_annotation = '->_return_type' - args = ','.join(args) - body = '\n'.join(f' {b}' for b in body) - - # Compute the text of the entire function. - txt = f' def {name}({args}){return_annotation}:\n{body}' - - local_vars = ', '.join(locals.keys()) - txt = f"def __create_fn__({local_vars}):\n{txt}\n return {name}" - ns = {} - exec(txt, globals, ns) - return ns['__create_fn__'](**locals) + def add_fns_to_class(self, cls): + # The source to all of the functions we're generating. + fns_src = '\n'.join(self.src) + + # The locals they use. + local_vars = ','.join(self.locals.keys()) + + # The names of all of the functions, used for the return value of the + # outer function. Need to handle the 0-tuple specially. + if len(self.names) == 0: + return_names = '()' + else: + return_names =f'({",".join(self.names)},)' + + # txt is the entire function we're going to execute, including the + # bodies of the functions we're defining. Here's a greatly simplified + # version: + # def __create_fn__(): + # def __init__(self, x, y): + # self.x = x + # self.y = y + # @recursive_repr + # def __repr__(self): + # return f"cls(x={self.x!r},y={self.y!r})" + # return __init__,__repr__ + + txt = f"def __create_fn__({local_vars}):\n{fns_src}\n return {return_names}" + ns = {} + exec(txt, self.globals, ns) + fns = ns['__create_fn__'](**self.locals) + + # Now that we've generated the functions, assign them into cls. + for name, fn in zip(self.names, fns): + fn.__qualname__ = f"{cls.__qualname__}.{fn.__name__}" + if self.unconditional_adds.get(name, False): + setattr(cls, name, fn) + else: + already_exists = _set_new_attribute(cls, name, fn) + + # See if it's an error to overwrite this particular function. + if already_exists and (msg_extra := self.overwrite_errors.get(name)): + error_msg = (f'Cannot overwrite attribute {fn.__name__} ' + f'in class {cls.__name__}') + if not msg_extra is True: + error_msg = f'{error_msg} {msg_extra}' + + raise TypeError(error_msg) def _field_assign(frozen, name, value, self_name): @@ -441,22 +524,22 @@ def _field_assign(frozen, name, value, self_name): # self_name is what "self" is called in this function: don't # hard-code "self", since that might be a field name. if frozen: - return f'__dataclass_builtins_object__.__setattr__({self_name},{name!r},{value})' - return f'{self_name}.{name}={value}' + return f' __dataclass_builtins_object__.__setattr__({self_name},{name!r},{value})' + return f' {self_name}.{name}={value}' def _field_init(f, frozen, globals, self_name, slots): # Return the text of the line in the body of __init__ that will # initialize this field. - default_name = f'_dflt_{f.name}' + default_name = f'__dataclass_dflt_{f.name}__' if f.default_factory is not MISSING: if f.init: # This field has a default factory. If a parameter is # given, use it. If not, call the factory. globals[default_name] = f.default_factory value = (f'{default_name}() ' - f'if {f.name} is _HAS_DEFAULT_FACTORY ' + f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__ ' f'else {f.name}') else: # This is a field that's not in the __init__ params, but @@ -517,15 +600,15 @@ def _init_param(f): elif f.default is not MISSING: # There's a default, this will be the name that's used to look # it up. - default = f'=_dflt_{f.name}' + default = f'=__dataclass_dflt_{f.name}__' elif f.default_factory is not MISSING: # There's a factory function. Set a marker. - default = '=_HAS_DEFAULT_FACTORY' - return f'{f.name}:_type_{f.name}{default}' + default = '=__dataclass_HAS_DEFAULT_FACTORY__' + return f'{f.name}:__dataclass_type_{f.name}__{default}' def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init, - self_name, globals, slots): + self_name, func_builder, slots): # fields contains both real fields and InitVar pseudo-fields. # Make sure we don't have fields without defaults following fields @@ -534,22 +617,21 @@ def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init, # message, and future-proofs us in case we build up the function # using ast. - seen_default = False + seen_default = None for f in std_fields: # Only consider the non-kw-only fields in the __init__ call. if f.init: if not (f.default is MISSING and f.default_factory is MISSING): - seen_default = True + seen_default = f elif seen_default: raise TypeError(f'non-default argument {f.name!r} ' - 'follows default argument') + f'follows default argument {seen_default.name!r}') - locals = {f'_type_{f.name}': f.type for f in fields} - locals.update({ - 'MISSING': MISSING, - '_HAS_DEFAULT_FACTORY': _HAS_DEFAULT_FACTORY, - '__dataclass_builtins_object__': object, - }) + locals = {**{f'__dataclass_type_{f.name}__': f.type for f in fields}, + **{'__dataclass_HAS_DEFAULT_FACTORY__': _HAS_DEFAULT_FACTORY, + '__dataclass_builtins_object__': object, + } + } body_lines = [] for f in fields: @@ -563,11 +645,11 @@ def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init, if has_post_init: params_str = ','.join(f.name for f in fields if f._field_type is _FIELD_INITVAR) - body_lines.append(f'{self_name}.{_POST_INIT_NAME}({params_str})') + body_lines.append(f' {self_name}.{_POST_INIT_NAME}({params_str})') # If no body lines, use 'pass'. if not body_lines: - body_lines = ['pass'] + body_lines = [' pass'] _init_params = [_init_param(f) for f in std_fields] if kw_only_fields: @@ -576,70 +658,34 @@ def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init, # (instead of just concatenting the lists together). _init_params += ['*'] _init_params += [_init_param(f) for f in kw_only_fields] - return _create_fn('__init__', - [self_name] + _init_params, - body_lines, - locals=locals, - globals=globals, - return_type=None) - - -def _repr_fn(fields, globals): - fn = _create_fn('__repr__', - ('self',), - ['return self.__class__.__qualname__ + f"(' + - ', '.join([f"{f.name}={{self.{f.name}!r}}" - for f in fields]) + - ')"'], - globals=globals) - return _recursive_repr(fn) - - -def _frozen_get_del_attr(cls, fields, globals): + func_builder.add_fn('__init__', + [self_name] + _init_params, + body_lines, + locals=locals, + return_type=None) + + +def _frozen_get_del_attr(cls, fields, func_builder): locals = {'cls': cls, 'FrozenInstanceError': FrozenInstanceError} + condition = 'type(self) is cls' if fields: - fields_str = '(' + ','.join(repr(f.name) for f in fields) + ',)' - else: - # Special case for the zero-length tuple. - fields_str = '()' - return (_create_fn('__setattr__', - ('self', 'name', 'value'), - (f'if type(self) is cls or name in {fields_str}:', - ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', - f'super(cls, self).__setattr__(name, value)'), - locals=locals, - globals=globals), - _create_fn('__delattr__', - ('self', 'name'), - (f'if type(self) is cls or name in {fields_str}:', - ' raise FrozenInstanceError(f"cannot delete field {name!r}")', - f'super(cls, self).__delattr__(name)'), - locals=locals, - globals=globals), - ) - - -def _cmp_fn(name, op, self_tuple, other_tuple, globals): - # Create a comparison function. If the fields in the object are - # named 'x' and 'y', then self_tuple is the string - # '(self.x,self.y)' and other_tuple is the string - # '(other.x,other.y)'. - - return _create_fn(name, - ('self', 'other'), - [ 'if other.__class__ is self.__class__:', - f' return {self_tuple}{op}{other_tuple}', - 'return NotImplemented'], - globals=globals) - - -def _hash_fn(fields, globals): - self_tuple = _tuple_str('self', fields) - return _create_fn('__hash__', - ('self',), - [f'return hash({self_tuple})'], - globals=globals) + condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}' + + func_builder.add_fn('__setattr__', + ('self', 'name', 'value'), + (f' if {condition}:', + ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', + f' super(cls, self).__setattr__(name, value)'), + locals=locals, + overwrite_error=True) + func_builder.add_fn('__delattr__', + ('self', 'name'), + (f' if {condition}:', + ' raise FrozenInstanceError(f"cannot delete field {name!r}")', + f' super(cls, self).__delattr__(name)'), + locals=locals, + overwrite_error=True) def _is_classvar(a_type, typing): @@ -807,26 +853,20 @@ def _get_field(cls, a_name, a_type, default_kw_only): raise TypeError(f'field {f.name} is a ClassVar but specifies ' 'kw_only') - # For real fields, disallow mutable defaults for known types. - if f._field_type is _FIELD and isinstance(f.default, (list, dict, set)): + # For real fields, disallow mutable defaults. Use unhashable as a proxy + # indicator for mutability. Read the __hash__ attribute from the class, + # not the instance. + if f._field_type is _FIELD and f.default.__class__.__hash__ is None: raise ValueError(f'mutable default {type(f.default)} for field ' f'{f.name} is not allowed: use default_factory') return f -def _set_qualname(cls, value): - # Ensure that the functions returned from _create_fn uses the proper - # __qualname__ (the class they belong to). - if isinstance(value, FunctionType): - value.__qualname__ = f"{cls.__qualname__}.{value.__name__}" - return value - def _set_new_attribute(cls, name, value): # Never overwrites an existing attribute. Returns True if the # attribute already exists. if name in cls.__dict__: return True - _set_qualname(cls, value) setattr(cls, name, value) return False @@ -836,14 +876,22 @@ def _set_new_attribute(cls, name, value): # take. The common case is to do nothing, so instead of providing a # function that is a no-op, use None to signify that. -def _hash_set_none(cls, fields, globals): - return None +def _hash_set_none(cls, fields, func_builder): + # It's sort of a hack that I'm setting this here, instead of at + # func_builder.add_fns_to_class time, but since this is an exceptional case + # (it's not setting an attribute to a function, but to a scalar value), + # just do it directly here. I might come to regret this. + cls.__hash__ = None -def _hash_add(cls, fields, globals): +def _hash_add(cls, fields, func_builder): flds = [f for f in fields if (f.compare if f.hash is None else f.hash)] - return _set_qualname(cls, _hash_fn(flds, globals)) + self_tuple = _tuple_str('self', flds) + func_builder.add_fn('__hash__', + ('self',), + [f' return hash({self_tuple})'], + unconditional_add=True) -def _hash_exception(cls, fields, globals): +def _hash_exception(cls, fields, func_builder): # Raise an exception. raise TypeError(f'Cannot overwrite attribute __hash__ ' f'in class {cls.__name__}') @@ -879,7 +927,7 @@ def _hash_exception(cls, fields, globals): def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, - match_args, kw_only, slots): + match_args, kw_only, slots, weakref_slot): # Now that dicts retain insertion order, there's no reason to use # an ordered dict. I am leveraging that ordering here, because # derived class fields overwrite base class fields, but the order @@ -897,13 +945,18 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, globals = {} setattr(cls, _PARAMS, _DataclassParams(init, repr, eq, order, - unsafe_hash, frozen)) + unsafe_hash, frozen, + match_args, kw_only, + slots, weakref_slot)) # Find our base classes in reverse MRO order, and exclude # ourselves. In reversed order so that more derived classes # override earlier field definitions in base classes. As long as - # we're iterating over them, see if any are frozen. + # we're iterating over them, see if all or any of them are frozen. any_frozen_base = False + # By default `all_frozen_bases` is `None` to represent a case, + # where some dataclasses does not have any bases with `_FIELDS` + all_frozen_bases = None has_dataclass_bases = False for b in cls.__mro__[-1:0:-1]: # Only process classes that have been processed by our @@ -913,13 +966,13 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, has_dataclass_bases = True for f in base_fields.values(): fields[f.name] = f - if getattr(b, _PARAMS).frozen: - any_frozen_base = True + if all_frozen_bases is None: + all_frozen_bases = True + current_frozen = getattr(b, _PARAMS).frozen + all_frozen_bases = all_frozen_bases and current_frozen + any_frozen_base = any_frozen_base or current_frozen - # Annotations that are defined in this class (not in base - # classes). If __annotations__ isn't present, then this class - # adds no new annotations. We use this to compute fields that are - # added by this class. + # Annotations defined specifically in this class (not in base classes). # # Fields are found from cls_annotations, which is guaranteed to be # ordered. Default values are from class attributes, if a field @@ -928,7 +981,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, # actual default value. Pseudo-fields ClassVars and InitVars are # included, despite the fact that they're not real fields. That's # dealt with later. - cls_annotations = cls.__dict__.get('__annotations__', {}) + cls_annotations = inspect.get_annotations(cls) # Now find fields in our class. While doing so, validate some # things, and set the default values (as class attributes) where @@ -986,7 +1039,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, 'frozen one') # Raise an exception if we're frozen, but none of our bases are. - if not any_frozen_base and frozen: + if all_frozen_bases is False and frozen: raise TypeError('cannot inherit frozen dataclass from a ' 'non-frozen one') @@ -997,7 +1050,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, # Was this class defined with an explicit __hash__? Note that if # __eq__ is defined in this class, then python will automatically # set __hash__ to None. This is a heuristic, as it's possible - # that such a __hash__ == None was not auto-generated, but it + # that such a __hash__ == None was not auto-generated, but it's # close enough. class_hash = cls.__dict__.get('__hash__', MISSING) has_explicit_hash = not (class_hash is MISSING or @@ -1016,24 +1069,27 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, (std_init_fields, kw_only_init_fields) = _fields_in_init_order(all_init_fields) + func_builder = _FuncBuilder(globals) + if init: # Does this class have a post-init function? has_post_init = hasattr(cls, _POST_INIT_NAME) - _set_new_attribute(cls, '__init__', - _init_fn(all_init_fields, - std_init_fields, - kw_only_init_fields, - frozen, - has_post_init, - # The name to use for the "self" - # param in __init__. Use "self" - # if possible. - '__dataclass_self__' if 'self' in fields - else 'self', - globals, - slots, - )) + _init_fn(all_init_fields, + std_init_fields, + kw_only_init_fields, + frozen, + has_post_init, + # The name to use for the "self" + # param in __init__. Use "self" + # if possible. + '__dataclass_self__' if 'self' in fields + else 'self', + func_builder, + slots, + ) + + _set_new_attribute(cls, '__replace__', _replace) # Get the fields as a list, and include only real fields. This is # used in all of the following methods. @@ -1041,18 +1097,27 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, if repr: flds = [f for f in field_list if f.repr] - _set_new_attribute(cls, '__repr__', _repr_fn(flds, globals)) + func_builder.add_fn('__repr__', + ('self',), + [' return f"{self.__class__.__qualname__}(' + + ', '.join([f"{f.name}={{self.{f.name}!r}}" + for f in flds]) + ')"'], + locals={'__dataclasses_recursive_repr': recursive_repr}, + decorator="@__dataclasses_recursive_repr()") if eq: # Create __eq__ method. There's no need for a __ne__ method, # since python will call __eq__ and negate it. - flds = [f for f in field_list if f.compare] - self_tuple = _tuple_str('self', flds) - other_tuple = _tuple_str('other', flds) - _set_new_attribute(cls, '__eq__', - _cmp_fn('__eq__', '==', - self_tuple, other_tuple, - globals=globals)) + cmp_fields = (field for field in field_list if field.compare) + terms = [f'self.{field.name}==other.{field.name}' for field in cmp_fields] + field_comparisons = ' and '.join(terms) or 'True' + func_builder.add_fn('__eq__', + ('self', 'other'), + [ ' if self is other:', + ' return True', + ' if other.__class__ is self.__class__:', + f' return {field_comparisons}', + ' return NotImplemented']) if order: # Create and set the ordering methods. @@ -1064,18 +1129,19 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, ('__gt__', '>'), ('__ge__', '>='), ]: - if _set_new_attribute(cls, name, - _cmp_fn(name, op, self_tuple, other_tuple, - globals=globals)): - raise TypeError(f'Cannot overwrite attribute {name} ' - f'in class {cls.__name__}. Consider using ' - 'functools.total_ordering') + # Create a comparison function. If the fields in the object are + # named 'x' and 'y', then self_tuple is the string + # '(self.x,self.y)' and other_tuple is the string + # '(other.x,other.y)'. + func_builder.add_fn(name, + ('self', 'other'), + [ ' if other.__class__ is self.__class__:', + f' return {self_tuple}{op}{other_tuple}', + ' return NotImplemented'], + overwrite_error='Consider using functools.total_ordering') if frozen: - for fn in _frozen_get_del_attr(cls, field_list, globals): - if _set_new_attribute(cls, fn.__name__, fn): - raise TypeError(f'Cannot overwrite attribute {fn.__name__} ' - f'in class {cls.__name__}') + _frozen_get_del_attr(cls, field_list, func_builder) # Decide if/how we're going to create a hash function. hash_action = _hash_action[bool(unsafe_hash), @@ -1083,22 +1149,33 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, bool(frozen), has_explicit_hash] if hash_action: - # No need to call _set_new_attribute here, since by the time - # we're here the overwriting is unconditional. - cls.__hash__ = hash_action(cls, field_list, globals) + cls.__hash__ = hash_action(cls, field_list, func_builder) + + # Generate the methods and add them to the class. This needs to be done + # before the __doc__ logic below, since inspect will look at the __init__ + # signature. + func_builder.add_fns_to_class(cls) if not getattr(cls, '__doc__'): # Create a class doc-string. - cls.__doc__ = (cls.__name__ + - str(inspect.signature(cls)).replace(' -> None', '')) + try: + # In some cases fetching a signature is not possible. + # But, we surely should not fail in this case. + text_sig = str(inspect.signature(cls)).replace(' -> None', '') + except (TypeError, ValueError): + text_sig = '' + cls.__doc__ = (cls.__name__ + text_sig) if match_args: - # I could probably compute this once + # I could probably compute this once. _set_new_attribute(cls, '__match_args__', tuple(f.name for f in std_init_fields)) + # It's an error to specify weakref_slot if slots is False. + if weakref_slot and not slots: + raise TypeError('weakref_slot is True but slots is False') if slots: - cls = _add_slots(cls, frozen) + cls = _add_slots(cls, frozen, weakref_slot) abc.update_abstractmethods(cls) @@ -1106,7 +1183,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, # _dataclass_getstate and _dataclass_setstate are needed for pickling frozen -# classes with slots. These could be slighly more performant if we generated +# classes with slots. These could be slightly more performant if we generated # the code instead of iterating over fields. But that can be a project for # another day, if performance becomes an issue. def _dataclass_getstate(self): @@ -1119,7 +1196,30 @@ def _dataclass_setstate(self, state): object.__setattr__(self, field.name, value) -def _add_slots(cls, is_frozen): +def _get_slots(cls): + match cls.__dict__.get('__slots__'): + # `__dictoffset__` and `__weakrefoffset__` can tell us whether + # the base type has dict/weakref slots, in a way that works correctly + # for both Python classes and C extension types. Extension types + # don't use `__slots__` for slot creation + case None: + slots = [] + if getattr(cls, '__weakrefoffset__', -1) != 0: + slots.append('__weakref__') + if getattr(cls, '__dictoffset__', -1) != 0: + slots.append('__dict__') + yield from slots + case str(slot): + yield slot + # Slots may be any iterable, but we cannot handle an iterator + # because it will already be (partially) consumed. + case iterable if not hasattr(iterable, '__next__'): + yield from iterable + case _: + raise TypeError(f"Slots of '{cls.__name__}' cannot be determined") + + +def _add_slots(cls, is_frozen, weakref_slot): # Need to create a new class, since we can't set __slots__ # after a class has been created. @@ -1130,7 +1230,23 @@ def _add_slots(cls, is_frozen): # Create a new dict for our new class. cls_dict = dict(cls.__dict__) field_names = tuple(f.name for f in fields(cls)) - cls_dict['__slots__'] = field_names + # Make sure slots don't overlap with those in base classes. + inherited_slots = set( + itertools.chain.from_iterable(map(_get_slots, cls.__mro__[1:-1])) + ) + # The slots for our class. Remove slots from our base classes. Add + # '__weakref__' if weakref_slot was given, unless it is already present. + cls_dict["__slots__"] = tuple( + itertools.filterfalse( + inherited_slots.__contains__, + itertools.chain( + # gh-93521: '__weakref__' also needs to be filtered out if + # already present in inherited_slots + field_names, ('__weakref__',) if weakref_slot else () + ) + ), + ) + for field_name in field_names: # Remove our attributes, if present. They'll still be # available in _MARKER. @@ -1139,6 +1255,9 @@ def _add_slots(cls, is_frozen): # Remove __dict__ itself. cls_dict.pop('__dict__', None) + # Clear existing `__weakref__` descriptor, it belongs to a previous type: + cls_dict.pop('__weakref__', None) # gh-102069 + # And finally create the class. qualname = getattr(cls, '__qualname__', None) cls = type(cls)(cls.__name__, cls.__bases__, cls_dict) @@ -1147,33 +1266,35 @@ def _add_slots(cls, is_frozen): if is_frozen: # Need this for pickling frozen classes with slots. - cls.__getstate__ = _dataclass_getstate - cls.__setstate__ = _dataclass_setstate + if '__getstate__' not in cls_dict: + cls.__getstate__ = _dataclass_getstate + if '__setstate__' not in cls_dict: + cls.__setstate__ = _dataclass_setstate return cls def dataclass(cls=None, /, *, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, - kw_only=False, slots=False): - """Returns the same class as was passed in, with dunder methods - added based on the fields defined in the class. + kw_only=False, slots=False, weakref_slot=False): + """Add dunder methods based on the fields defined in the class. Examines PEP 526 __annotations__ to determine fields. - If init is true, an __init__() method is added to the class. If - repr is true, a __repr__() method is added. If order is true, rich + If init is true, an __init__() method is added to the class. If repr + is true, a __repr__() method is added. If order is true, rich comparison dunder methods are added. If unsafe_hash is true, a - __hash__() method function is added. If frozen is true, fields may - not be assigned to after instance creation. If match_args is true, - the __match_args__ tuple is added. If kw_only is true, then by - default all fields are keyword-only. If slots is true, an - __slots__ attribute is added. + __hash__() method is added. If frozen is true, fields may not be + assigned to after instance creation. If match_args is true, the + __match_args__ tuple is added. If kw_only is true, then by default + all fields are keyword-only. If slots is true, a new class with a + __slots__ attribute is returned. """ def wrap(cls): return _process_class(cls, init, repr, eq, order, unsafe_hash, - frozen, match_args, kw_only, slots) + frozen, match_args, kw_only, slots, + weakref_slot) # See if we're being called as @dataclass or @dataclass(). if cls is None: @@ -1195,7 +1316,7 @@ def fields(class_or_instance): try: fields = getattr(class_or_instance, _FIELDS) except AttributeError: - raise TypeError('must be called with a dataclass type or instance') + raise TypeError('must be called with a dataclass type or instance') from None # Exclude pseudo-fields. Note that fields is sorted by insertion # order, so the order of the tuple is as the fields were defined. @@ -1210,7 +1331,7 @@ def _is_dataclass_instance(obj): def is_dataclass(obj): """Returns True if obj is a dataclass or an instance of a dataclass.""" - cls = obj if isinstance(obj, type) and not isinstance(obj, GenericAlias) else type(obj) + cls = obj if isinstance(obj, type) else type(obj) return hasattr(cls, _FIELDS) @@ -1218,7 +1339,7 @@ def asdict(obj, *, dict_factory=dict): """Return the fields of a dataclass instance as a new dictionary mapping field names to field values. - Example usage: + Example usage:: @dataclass class C: @@ -1231,7 +1352,7 @@ class C: If given, 'dict_factory' will be used instead of built-in dict. The function applies recursively to field values that are dataclass instances. This will also look into built-in containers: - tuples, lists, and dicts. + tuples, lists, and dicts. Other objects are copied with 'copy.deepcopy()'. """ if not _is_dataclass_instance(obj): raise TypeError("asdict() should be called on dataclass instances") @@ -1239,42 +1360,69 @@ class C: def _asdict_inner(obj, dict_factory): - if _is_dataclass_instance(obj): - result = [] - for f in fields(obj): - value = _asdict_inner(getattr(obj, f.name), dict_factory) - result.append((f.name, value)) - return dict_factory(result) - elif isinstance(obj, tuple) and hasattr(obj, '_fields'): - # obj is a namedtuple. Recurse into it, but the returned - # object is another namedtuple of the same type. This is - # similar to how other list- or tuple-derived classes are - # treated (see below), but we just need to create them - # differently because a namedtuple's __init__ needs to be - # called differently (see bpo-34363). - - # I'm not using namedtuple's _asdict() - # method, because: - # - it does not recurse in to the namedtuple fields and - # convert them to dicts (using dict_factory). - # - I don't actually want to return a dict here. The main - # use case here is json.dumps, and it handles converting - # namedtuples to lists. Admittedly we're losing some - # information here when we produce a json list instead of a - # dict. Note that if we returned dicts here instead of - # namedtuples, we could no longer call asdict() on a data - # structure where a namedtuple was used as a dict key. - - return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj]) - elif isinstance(obj, (list, tuple)): + obj_type = type(obj) + if obj_type in _ATOMIC_TYPES: + return obj + elif hasattr(obj_type, _FIELDS): + # dataclass instance: fast path for the common case + if dict_factory is dict: + return { + f.name: _asdict_inner(getattr(obj, f.name), dict) + for f in fields(obj) + } + else: + return dict_factory([ + (f.name, _asdict_inner(getattr(obj, f.name), dict_factory)) + for f in fields(obj) + ]) + # handle the builtin types first for speed; subclasses handled below + elif obj_type is list: + return [_asdict_inner(v, dict_factory) for v in obj] + elif obj_type is dict: + return { + _asdict_inner(k, dict_factory): _asdict_inner(v, dict_factory) + for k, v in obj.items() + } + elif obj_type is tuple: + return tuple([_asdict_inner(v, dict_factory) for v in obj]) + elif issubclass(obj_type, tuple): + if hasattr(obj, '_fields'): + # obj is a namedtuple. Recurse into it, but the returned + # object is another namedtuple of the same type. This is + # similar to how other list- or tuple-derived classes are + # treated (see below), but we just need to create them + # differently because a namedtuple's __init__ needs to be + # called differently (see bpo-34363). + + # I'm not using namedtuple's _asdict() + # method, because: + # - it does not recurse in to the namedtuple fields and + # convert them to dicts (using dict_factory). + # - I don't actually want to return a dict here. The main + # use case here is json.dumps, and it handles converting + # namedtuples to lists. Admittedly we're losing some + # information here when we produce a json list instead of a + # dict. Note that if we returned dicts here instead of + # namedtuples, we could no longer call asdict() on a data + # structure where a namedtuple was used as a dict key. + return obj_type(*[_asdict_inner(v, dict_factory) for v in obj]) + else: + return obj_type(_asdict_inner(v, dict_factory) for v in obj) + elif issubclass(obj_type, dict): + if hasattr(obj_type, 'default_factory'): + # obj is a defaultdict, which has a different constructor from + # dict as it requires the default_factory as its first arg. + result = obj_type(obj.default_factory) + for k, v in obj.items(): + result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory) + return result + return obj_type((_asdict_inner(k, dict_factory), + _asdict_inner(v, dict_factory)) + for k, v in obj.items()) + elif issubclass(obj_type, list): # Assume we can create an object of this type by passing in a - # generator (which is not true for namedtuples, handled - # above). - return type(obj)(_asdict_inner(v, dict_factory) for v in obj) - elif isinstance(obj, dict): - return type(obj)((_asdict_inner(k, dict_factory), - _asdict_inner(v, dict_factory)) - for k, v in obj.items()) + # generator + return obj_type(_asdict_inner(v, dict_factory) for v in obj) else: return copy.deepcopy(obj) @@ -1289,13 +1437,13 @@ class C: x: int y: int - c = C(1, 2) - assert astuple(c) == (1, 2) + c = C(1, 2) + assert astuple(c) == (1, 2) If given, 'tuple_factory' will be used instead of built-in tuple. The function applies recursively to field values that are dataclass instances. This will also look into built-in containers: - tuples, lists, and dicts. + tuples, lists, and dicts. Other objects are copied with 'copy.deepcopy()'. """ if not _is_dataclass_instance(obj): @@ -1304,12 +1452,13 @@ class C: def _astuple_inner(obj, tuple_factory): - if _is_dataclass_instance(obj): - result = [] - for f in fields(obj): - value = _astuple_inner(getattr(obj, f.name), tuple_factory) - result.append(value) - return tuple_factory(result) + if type(obj) in _ATOMIC_TYPES: + return obj + elif _is_dataclass_instance(obj): + return tuple_factory([ + _astuple_inner(getattr(obj, f.name), tuple_factory) + for f in fields(obj) + ]) elif isinstance(obj, tuple) and hasattr(obj, '_fields'): # obj is a namedtuple. Recurse into it, but the returned # object is another namedtuple of the same type. This is @@ -1324,7 +1473,15 @@ def _astuple_inner(obj, tuple_factory): # above). return type(obj)(_astuple_inner(v, tuple_factory) for v in obj) elif isinstance(obj, dict): - return type(obj)((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory)) + obj_type = type(obj) + if hasattr(obj_type, 'default_factory'): + # obj is a defaultdict, which has a different constructor from + # dict as it requires the default_factory as its first arg. + result = obj_type(getattr(obj, 'default_factory')) + for k, v in obj.items(): + result[_astuple_inner(k, tuple_factory)] = _astuple_inner(v, tuple_factory) + return result + return obj_type((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory)) for k, v in obj.items()) else: return copy.deepcopy(obj) @@ -1332,17 +1489,18 @@ def _astuple_inner(obj, tuple_factory): def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, - frozen=False, match_args=True, kw_only=False, slots=False): + frozen=False, match_args=True, kw_only=False, slots=False, + weakref_slot=False, module=None): """Return a new dynamically created dataclass. The dataclass name will be 'cls_name'. 'fields' is an iterable of either (name), (name, type) or (name, type, Field) objects. If type is omitted, use the string 'typing.Any'. Field objects are created by - the equivalent of calling 'field(name, type [, Field-info])'. + the equivalent of calling 'field(name, type [, Field-info])'.:: C = make_dataclass('C', ['x', ('y', int), ('z', int, field(init=False))], bases=(Base,)) - is equivalent to: + is equivalent to:: @dataclass class C(Base): @@ -1352,8 +1510,11 @@ class C(Base): For the bases and namespace parameters, see the builtin type() function. - The parameters init, repr, eq, order, unsafe_hash, and frozen are passed to - dataclass(). + The parameters init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, + slots, and weakref_slot are passed to dataclass(). + + If module parameter is defined, the '__module__' attribute of the dataclass is + set to that value. """ if namespace is None: @@ -1396,16 +1557,30 @@ def exec_body_callback(ns): # of generic dataclasses. cls = types.new_class(cls_name, bases, {}, exec_body_callback) + # For pickling to work, the __module__ variable needs to be set to the frame + # where the dataclass is created. + if module is None: + try: + module = sys._getframemodulename(1) or '__main__' + except AttributeError: + try: + module = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + if module is not None: + cls.__module__ = module + # Apply the normal decorator. return dataclass(cls, init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen, - match_args=match_args, kw_only=kw_only, slots=slots) + match_args=match_args, kw_only=kw_only, slots=slots, + weakref_slot=weakref_slot) def replace(obj, /, **changes): """Return a new object replacing specified fields with new values. - This is especially useful for frozen classes. Example usage: + This is especially useful for frozen classes. Example usage:: @dataclass(frozen=True) class C: @@ -1415,18 +1590,20 @@ class C: c = C(1, 2) c1 = replace(c, x=3) assert c1.x == 3 and c1.y == 2 - """ - - # We're going to mutate 'changes', but that's okay because it's a - # new dict, even if called with 'replace(obj, **my_changes)'. - + """ if not _is_dataclass_instance(obj): raise TypeError("replace() should be called on dataclass instances") + return _replace(obj, **changes) + + +def _replace(self, /, **changes): + # We're going to mutate 'changes', but that's okay because it's a + # new dict, even if called with 'replace(self, **my_changes)'. # It's an error to have init=False fields in 'changes'. - # If a field is not in 'changes', read its value from the provided obj. + # If a field is not in 'changes', read its value from the provided 'self'. - for f in getattr(obj, _FIELDS).values(): + for f in getattr(self, _FIELDS).values(): # Only consider normal fields or InitVars. if f._field_type is _FIELD_CLASSVAR: continue @@ -1434,20 +1611,20 @@ class C: if not f.init: # Error if this field is specified in changes. if f.name in changes: - raise ValueError(f'field {f.name} is declared with ' - 'init=False, it cannot be specified with ' - 'replace()') + raise TypeError(f'field {f.name} is declared with ' + f'init=False, it cannot be specified with ' + f'replace()') continue if f.name not in changes: if f._field_type is _FIELD_INITVAR and f.default is MISSING: - raise ValueError(f"InitVar {f.name!r} " - 'must be specified with replace()') - changes[f.name] = getattr(obj, f.name) + raise TypeError(f"InitVar {f.name!r} " + f'must be specified with replace()') + changes[f.name] = getattr(self, f.name) # Create the new object, which calls __init__() and # __post_init__() (if defined), using all of the init fields we've # added and/or left in 'changes'. If there are values supplied in # changes that aren't fields, this will correctly raise a # TypeError. - return obj.__class__(**changes) + return self.__class__(**changes) diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index 2f9d8ed9b6e..456767bbe0c 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -961,8 +961,6 @@ class PointFromClass(NamedTuple): with self.assertRaisesRegex(TypeError, 'unexpected field name'): copy.replace(p, x=1, error=2) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_dataclass(self): from dataclasses import dataclass @dataclass diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses/__init__.py similarity index 80% rename from Lib/test/test_dataclasses.py rename to Lib/test/test_dataclasses/__init__.py index f11edd957c4..1b16da42648 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -5,20 +5,26 @@ from dataclasses import * import abc +import io import pickle import inspect import builtins import types +import weakref +import traceback import unittest from unittest.mock import Mock -from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional, Protocol +from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional, Protocol, DefaultDict from typing import get_type_hints -from collections import deque, OrderedDict, namedtuple +from collections import deque, OrderedDict, namedtuple, defaultdict +from copy import deepcopy from functools import total_ordering import typing # Needed for the string "typing.ClassVar[int]" to work as an annotation. import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation. +from test import support + # Just any custom exception we can catch. class CustomError(Exception): pass @@ -67,6 +73,50 @@ def test_field_repr(self): self.assertEqual(repr_output, expected_output) + def test_field_recursive_repr(self): + rec_field = field() + rec_field.type = rec_field + rec_field.name = "id" + repr_output = repr(rec_field) + + self.assertIn(",type=...,", repr_output) + + def test_recursive_annotation(self): + class C: + pass + + @dataclass + class D: + C: C = field() + + self.assertIn(",type=...,", repr(D.__dataclass_fields__["C"])) + + def test_dataclass_params_repr(self): + # Even though this is testing an internal implementation detail, + # it's testing a feature we want to make sure is correctly implemented + # for the sake of dataclasses itself + @dataclass(slots=True, frozen=True) + class Some: pass + + repr_output = repr(Some.__dataclass_params__) + expected_output = "_DataclassParams(init=True,repr=True," \ + "eq=True,order=False,unsafe_hash=False,frozen=True," \ + "match_args=True,kw_only=False," \ + "slots=True,weakref_slot=False)" + self.assertEqual(repr_output, expected_output) + + def test_dataclass_params_signature(self): + # Even though this is testing an internal implementation detail, + # it's testing a feature we want to make sure is correctly implemented + # for the sake of dataclasses itself + @dataclass + class Some: pass + + for param in inspect.signature(dataclass).parameters: + if param == 'cls': + continue + self.assertTrue(hasattr(Some.__dataclass_params__, param), msg=param) + def test_named_init_params(self): @dataclass class C: @@ -87,7 +137,7 @@ class C: # Non-defaults following defaults. with self.assertRaisesRegex(TypeError, "non-default argument 'y' follows " - "default argument"): + "default argument 'x'"): @dataclass class C: x: int = 0 @@ -96,7 +146,7 @@ class C: # A derived class adds a non-default field after a default one. with self.assertRaisesRegex(TypeError, "non-default argument 'y' follows " - "default argument"): + "default argument 'x'"): @dataclass class B: x: int = 0 @@ -109,7 +159,7 @@ class C(B): # a field which didn't use to have a default. with self.assertRaisesRegex(TypeError, "non-default argument 'y' follows " - "default argument"): + "default argument 'x'"): @dataclass class B: x: int @@ -230,6 +280,31 @@ class C: c = C('foo') self.assertEqual(c.object, 'foo') + def test_field_named_BUILTINS_frozen(self): + # gh-96151 + @dataclass(frozen=True) + class C: + BUILTINS: int + c = C(5) + self.assertEqual(c.BUILTINS, 5) + + def test_field_with_special_single_underscore_names(self): + # gh-98886 + + @dataclass + class X: + x: int = field(default_factory=lambda: 111) + _dflt_x: int = field(default_factory=lambda: 222) + + X() + + @dataclass + class Y: + y: int = field(default_factory=lambda: 111) + _HAS_DEFAULT_FACTORY: int = 222 + + assert Y(y=222).y == 222 + def test_field_named_like_builtin(self): # Attribute names can shadow built-in names # since code generation is used. @@ -501,6 +576,32 @@ class C: self.assertNotEqual(C(3), C(4, 10)) self.assertNotEqual(C(3, 10), C(4, 10)) + def test_no_unhashable_default(self): + # See bpo-44674. + class Unhashable: + __hash__ = None + + unhashable_re = 'mutable default .* for field a is not allowed' + with self.assertRaisesRegex(ValueError, unhashable_re): + @dataclass + class A: + a: dict = {} + + with self.assertRaisesRegex(ValueError, unhashable_re): + @dataclass + class A: + a: Any = Unhashable() + + # Make sure that the machinery looking for hashability is using the + # class's __hash__, not the instance's __hash__. + with self.assertRaisesRegex(ValueError, unhashable_re): + unhashable = Unhashable() + # This shouldn't make the variable hashable. + unhashable.__hash__ = lambda: 0 + @dataclass + class A: + a: Any = unhashable + def test_hash_field_rules(self): # Test all 6 cases of: # hash=True/False/None @@ -659,8 +760,8 @@ class Point: class Subclass(typ): pass with self.assertRaisesRegex(ValueError, - f"mutable default .*Subclass'>" - ' for field z is not allowed' + "mutable default .*Subclass'>" + " for field z is not allowed" ): @dataclass class Point: @@ -668,12 +769,12 @@ class Point: # Because this is a ClassVar, it can be mutable. @dataclass - class C: + class UsesMutableClassVar: z: ClassVar[typ] = typ() # Because this is a ClassVar, it can be mutable. @dataclass - class C: + class UsesMutableClassVarWithSubType: x: ClassVar[typ] = Subclass() def test_deliberately_mutable_defaults(self): @@ -990,6 +1091,65 @@ def __post_init__(cls): self.assertEqual((c.x, c.y), (3, 4)) self.assertTrue(C.flag) + def test_post_init_not_auto_added(self): + # See bpo-46757, which had proposed always adding __post_init__. As + # Raymond Hettinger pointed out, that would be a breaking change. So, + # add a test to make sure that the current behavior doesn't change. + + @dataclass + class A0: + pass + + @dataclass + class B0: + b_called: bool = False + def __post_init__(self): + self.b_called = True + + @dataclass + class C0(A0, B0): + c_called: bool = False + def __post_init__(self): + super().__post_init__() + self.c_called = True + + # Since A0 has no __post_init__, and one wasn't automatically added + # (because that's the rule: it's never added by @dataclass, it's only + # the class author that can add it), then B0.__post_init__ is called. + # Verify that. + c = C0() + self.assertTrue(c.b_called) + self.assertTrue(c.c_called) + + ###################################### + # Now, the same thing, except A1 defines __post_init__. + @dataclass + class A1: + def __post_init__(self): + pass + + @dataclass + class B1: + b_called: bool = False + def __post_init__(self): + self.b_called = True + + @dataclass + class C1(A1, B1): + c_called: bool = False + def __post_init__(self): + super().__post_init__() + self.c_called = True + + # This time, B1.__post_init__ isn't being called. This mimics what + # would happen if A1.__post_init__ had been automatically added, + # instead of manually added as we see here. This test isn't really + # needed, but I'm including it just to demonstrate the changed + # behavior when A1 does define __post_init__. + c = C1() + self.assertFalse(c.b_called) + self.assertTrue(c.c_called) + def test_class_var(self): # Make sure ClassVars are ignored in __init__, __repr__, etc. @dataclass @@ -1158,6 +1318,29 @@ def __post_init__(self, init_base, init_derived): c = C(10, 11, 50, 51) self.assertEqual(vars(c), {'x': 21, 'y': 101}) + def test_init_var_name_shadowing(self): + # Because dataclasses rely exclusively on `__annotations__` for + # handling InitVar and `__annotations__` preserves shadowed definitions, + # you can actually shadow an InitVar with a method or property. + # + # This only works when there is no default value; `dataclasses` uses the + # actual name (which will be bound to the shadowing method) for default + # values. + @dataclass + class C: + shadowed: InitVar[int] + _shadowed: int = field(init=False) + + def __post_init__(self, shadowed): + self._shadowed = shadowed * 2 + + @property + def shadowed(self): + return self._shadowed * 3 + + c = C(5) + self.assertEqual(c.shadowed, 30) + def test_default_factory(self): # Test a factory that returns a new list. @dataclass @@ -1365,6 +1548,24 @@ class A(types.GenericAlias): self.assertTrue(is_dataclass(type(a))) self.assertTrue(is_dataclass(a)) + def test_is_dataclass_inheritance(self): + @dataclass + class X: + y: int + + class Z(X): + pass + + self.assertTrue(is_dataclass(X), "X should be a dataclass") + self.assertTrue( + is_dataclass(Z), + "Z should be a dataclass because it inherits from X", + ) + z_instance = Z(y=5) + self.assertTrue( + is_dataclass(z_instance), + "z_instance should be a dataclass because it is an instance of Z", + ) def test_helper_fields_with_class_instance(self): # Check that we can call fields() on either a class or instance, @@ -1388,6 +1589,16 @@ class C: pass with self.assertRaisesRegex(TypeError, 'dataclass type or instance'): fields(C()) + def test_clean_traceback_from_fields_exception(self): + stdout = io.StringIO() + try: + fields(object) + except TypeError as exc: + traceback.print_exception(exc, file=stdout) + printed_traceback = stdout.getvalue() + self.assertNotIn("AttributeError", printed_traceback) + self.assertNotIn("__dataclass_fields__", printed_traceback) + def test_helper_asdict(self): # Basic tests for asdict(), it should return a new dictionary. @dataclass @@ -1565,6 +1776,23 @@ class C: self.assertIsNot(d['f'], t) self.assertEqual(d['f'].my_a(), 6) + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_helper_asdict_defaultdict(self): + # Ensure asdict() does not throw exceptions when a + # defaultdict is a member of a dataclass + @dataclass + class C: + mp: DefaultDict[str, List] + + dd = defaultdict(list) + dd["x"].append(12) + c = C(mp=dd) + d = asdict(c) + + self.assertEqual(d, {"mp": {"x": [12]}}) + self.assertTrue(d["mp"] is not c.mp) # make sure defaultdict is copied + def test_helper_astuple(self): # Basic tests for astuple(), it should return a new tuple. @dataclass @@ -1692,6 +1920,23 @@ class C: t = astuple(c, tuple_factory=list) self.assertEqual(t, ['outer', T(1, ['inner', T(11, 12, 13)], 2)]) + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_helper_astuple_defaultdict(self): + # Ensure astuple() does not throw exceptions when a + # defaultdict is a member of a dataclass + @dataclass + class C: + mp: DefaultDict[str, List] + + dd = defaultdict(list) + dd["x"].append(12) + c = C(mp=dd) + t = astuple(c) + + self.assertEqual(t, ({"x": [12]},)) + self.assertTrue(t[0] is not dd) # make sure defaultdict is copied + def test_dynamic_class_creation(self): cls_dict = {'__annotations__': {'x': int, 'y': int}, } @@ -2019,6 +2264,7 @@ def assertDocStrEqual(self, a, b): # whitespace stripped. self.assertEqual(a.replace(' ', ''), b.replace(' ', '')) + @support.requires_docstrings def test_existing_docstring_not_overridden(self): @dataclass class C: @@ -2100,13 +2346,25 @@ class C: self.assertDocStrEqual(C.__doc__, "C(x:collections.deque=<factory>)") + def test_docstring_with_no_signature(self): + # See https://github.com/python/cpython/issues/103449 + class Meta(type): + __call__ = dict + class Base(metaclass=Meta): + pass + + @dataclass + class C(Base): + pass + + self.assertDocStrEqual(C.__doc__, "C") + class TestInit(unittest.TestCase): def test_base_has_init(self): class B: def __init__(self): self.z = 100 - pass # Make sure that declaring this class doesn't raise an error. # The issue is that we can't override __init__ in our class, @@ -2129,12 +2387,12 @@ class C(B): self.assertEqual(c.z, 100) def test_no_init(self): - dataclass(init=False) + @dataclass(init=False) class C: i: int = 0 self.assertEqual(C().i, 0) - dataclass(init=False) + @dataclass(init=False) class C: i: int = 2 def __init__(self): @@ -2259,6 +2517,15 @@ def __repr__(self): class TestEq(unittest.TestCase): + def test_recursive_eq(self): + # Test a class with recursive child + @dataclass + class C: + recursive: object = ... + c = C() + c.recursive = c + self.assertEqual(c, c) + def test_no_eq(self): # Test a class with no __eq__ and eq=False. @dataclass(eq=False) @@ -2613,6 +2880,19 @@ class C: c.i = 5 self.assertEqual(c.i, 10) + def test_frozen_empty(self): + @dataclass(frozen=True) + class C: + pass + + c = C() + self.assertFalse(hasattr(c, 'i')) + with self.assertRaises(FrozenInstanceError): + c.i = 5 + self.assertFalse(hasattr(c, 'i')) + with self.assertRaises(FrozenInstanceError): + del c.i + def test_inherit(self): @dataclass(frozen=True) class C: @@ -2641,6 +2921,101 @@ class C: class D(C): j: int + def test_inherit_frozen_mutliple_inheritance(self): + @dataclass + class NotFrozen: + pass + + @dataclass(frozen=True) + class Frozen: + pass + + class NotDataclass: + pass + + for bases in ( + (NotFrozen, Frozen), + (Frozen, NotFrozen), + (Frozen, NotDataclass), + (NotDataclass, Frozen), + ): + with self.subTest(bases=bases): + with self.assertRaisesRegex( + TypeError, + 'cannot inherit non-frozen dataclass from a frozen one', + ): + @dataclass + class NotFrozenChild(*bases): + pass + + for bases in ( + (NotFrozen, Frozen), + (Frozen, NotFrozen), + (NotFrozen, NotDataclass), + (NotDataclass, NotFrozen), + ): + with self.subTest(bases=bases): + with self.assertRaisesRegex( + TypeError, + 'cannot inherit frozen dataclass from a non-frozen one', + ): + @dataclass(frozen=True) + class FrozenChild(*bases): + pass + + def test_inherit_frozen_mutliple_inheritance_regular_mixins(self): + @dataclass(frozen=True) + class Frozen: + pass + + class NotDataclass: + pass + + class C1(Frozen, NotDataclass): + pass + self.assertEqual(C1.__mro__, (C1, Frozen, NotDataclass, object)) + + class C2(NotDataclass, Frozen): + pass + self.assertEqual(C2.__mro__, (C2, NotDataclass, Frozen, object)) + + @dataclass(frozen=True) + class C3(Frozen, NotDataclass): + pass + self.assertEqual(C3.__mro__, (C3, Frozen, NotDataclass, object)) + + @dataclass(frozen=True) + class C4(NotDataclass, Frozen): + pass + self.assertEqual(C4.__mro__, (C4, NotDataclass, Frozen, object)) + + def test_multiple_frozen_dataclasses_inheritance(self): + @dataclass(frozen=True) + class FrozenA: + pass + + @dataclass(frozen=True) + class FrozenB: + pass + + class C1(FrozenA, FrozenB): + pass + self.assertEqual(C1.__mro__, (C1, FrozenA, FrozenB, object)) + + class C2(FrozenB, FrozenA): + pass + self.assertEqual(C2.__mro__, (C2, FrozenB, FrozenA, object)) + + @dataclass(frozen=True) + class C3(FrozenA, FrozenB): + pass + self.assertEqual(C3.__mro__, (C3, FrozenA, FrozenB, object)) + + @dataclass(frozen=True) + class C4(FrozenB, FrozenA): + pass + self.assertEqual(C4.__mro__, (C4, FrozenB, FrozenA, object)) + def test_inherit_nonfrozen_from_empty(self): @dataclass class C: @@ -2736,6 +3111,37 @@ class S(D): self.assertEqual(s.y, 10) self.assertEqual(s.cached, True) + with self.assertRaises(FrozenInstanceError): + del s.x + self.assertEqual(s.x, 3) + with self.assertRaises(FrozenInstanceError): + del s.y + self.assertEqual(s.y, 10) + del s.cached + self.assertFalse(hasattr(s, 'cached')) + with self.assertRaises(AttributeError) as cm: + del s.cached + self.assertNotIsInstance(cm.exception, FrozenInstanceError) + + def test_non_frozen_normal_derived_from_empty_frozen(self): + @dataclass(frozen=True) + class D: + pass + + class S(D): + pass + + s = S() + self.assertFalse(hasattr(s, 'x')) + s.x = 5 + self.assertEqual(s.x, 5) + + del s.x + self.assertFalse(hasattr(s, 'x')) + with self.assertRaises(AttributeError) as cm: + del s.x + self.assertNotIsInstance(cm.exception, FrozenInstanceError) + def test_overwriting_frozen(self): # frozen uses __setattr__ and __delattr__. with self.assertRaisesRegex(TypeError, @@ -2774,6 +3180,48 @@ class C: with self.assertRaisesRegex(TypeError, 'unhashable type'): hash(C({})) + def test_frozen_deepcopy_without_slots(self): + # see: https://github.com/python/cpython/issues/89683 + @dataclass(frozen=True, slots=False) + class C: + s: str + + c = C('hello') + self.assertEqual(deepcopy(c), c) + + def test_frozen_deepcopy_with_slots(self): + # see: https://github.com/python/cpython/issues/89683 + with self.subTest('generated __slots__'): + @dataclass(frozen=True, slots=True) + class C: + s: str + + c = C('hello') + self.assertEqual(deepcopy(c), c) + + with self.subTest('user-defined __slots__ and no __{get,set}state__'): + @dataclass(frozen=True, slots=False) + class C: + __slots__ = ('s',) + s: str + + # with user-defined slots, __getstate__ and __setstate__ are not + # automatically added, hence the error + err = r"^cannot\ assign\ to\ field\ 's'$" + self.assertRaisesRegex(FrozenInstanceError, err, deepcopy, C('')) + + with self.subTest('user-defined __slots__ and __{get,set}state__'): + @dataclass(frozen=True, slots=False) + class C: + __slots__ = ('s',) + __getstate__ = dataclasses._dataclass_getstate + __setstate__ = dataclasses._dataclass_setstate + + s: str + + c = C('hello') + self.assertEqual(deepcopy(c), c) + class TestSlots(unittest.TestCase): def test_simple(self): @@ -2841,23 +3289,58 @@ class C: x: int def test_generated_slots_value(self): - @dataclass(slots=True) - class Base: - x: int - self.assertEqual(Base.__slots__, ('x',)) + class Root: + __slots__ = {'x'} + + class Root2(Root): + __slots__ = {'k': '...', 'j': ''} + + class Root3(Root2): + __slots__ = ['h'] + + class Root4(Root3): + __slots__ = 'aa' @dataclass(slots=True) - class Delivered(Base): + class Base(Root4): y: int + j: str + h: str + + self.assertEqual(Base.__slots__, ('y', )) + + @dataclass(slots=True) + class Derived(Base): + aa: float + x: str + z: int + k: str + h: str - self.assertEqual(Delivered.__slots__, ('x', 'y')) + self.assertEqual(Derived.__slots__, ('z', )) @dataclass - class AnotherDelivered(Base): + class AnotherDerived(Base): z: int - self.assertTrue('__slots__' not in AnotherDelivered.__dict__) + self.assertNotIn('__slots__', AnotherDerived.__dict__) + + def test_cant_inherit_from_iterator_slots(self): + + class Root: + __slots__ = iter(['a']) + + class Root2(Root): + __slots__ = ('b', ) + + with self.assertRaisesRegex( + TypeError, + "^Slots of 'Root' cannot be determined" + ): + @dataclass(slots=True) + class C(Root2): + x: int def test_returns_new_class(self): class A: @@ -2896,6 +3379,74 @@ def test_frozen_pickle(self): self.assertIsNot(obj, p) self.assertEqual(obj, p) + @dataclass(frozen=True, slots=True) + class FrozenSlotsGetStateClass: + foo: str + bar: int + + getstate_called: bool = field(default=False, compare=False) + + def __getstate__(self): + object.__setattr__(self, 'getstate_called', True) + return [self.foo, self.bar] + + @dataclass(frozen=True, slots=True) + class FrozenSlotsSetStateClass: + foo: str + bar: int + + setstate_called: bool = field(default=False, compare=False) + + def __setstate__(self, state): + object.__setattr__(self, 'setstate_called', True) + object.__setattr__(self, 'foo', state[0]) + object.__setattr__(self, 'bar', state[1]) + + @dataclass(frozen=True, slots=True) + class FrozenSlotsAllStateClass: + foo: str + bar: int + + getstate_called: bool = field(default=False, compare=False) + setstate_called: bool = field(default=False, compare=False) + + def __getstate__(self): + object.__setattr__(self, 'getstate_called', True) + return [self.foo, self.bar] + + def __setstate__(self, state): + object.__setattr__(self, 'setstate_called', True) + object.__setattr__(self, 'foo', state[0]) + object.__setattr__(self, 'bar', state[1]) + + def test_frozen_slots_pickle_custom_state(self): + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + obj = self.FrozenSlotsGetStateClass('a', 1) + dumped = pickle.dumps(obj, protocol=proto) + + self.assertTrue(obj.getstate_called) + self.assertEqual(obj, pickle.loads(dumped)) + + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + obj = self.FrozenSlotsSetStateClass('a', 1) + obj2 = pickle.loads(pickle.dumps(obj, protocol=proto)) + + self.assertTrue(obj2.setstate_called) + self.assertEqual(obj, obj2) + + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + obj = self.FrozenSlotsAllStateClass('a', 1) + dumped = pickle.dumps(obj, protocol=proto) + + self.assertTrue(obj.getstate_called) + + obj2 = pickle.loads(dumped) + self.assertTrue(obj2.setstate_called) + self.assertEqual(obj, obj2) + def test_slots_with_default_no_init(self): # Originally reported in bpo-44649. @dataclass(slots=True) @@ -2918,6 +3469,311 @@ class A: self.assertEqual(obj.a, 'a') self.assertEqual(obj.b, 'b') + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_slots_no_weakref(self): + @dataclass(slots=True) + class A: + # No weakref. + pass + + self.assertNotIn("__weakref__", A.__slots__) + a = A() + with self.assertRaisesRegex(TypeError, + "cannot create weak reference"): + weakref.ref(a) + with self.assertRaises(AttributeError): + a.__weakref__ + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_slots_weakref(self): + @dataclass(slots=True, weakref_slot=True) + class A: + a: int + + self.assertIn("__weakref__", A.__slots__) + a = A(1) + a_ref = weakref.ref(a) + + self.assertIs(a.__weakref__, a_ref) + + def test_slots_weakref_base_str(self): + class Base: + __slots__ = '__weakref__' + + @dataclass(slots=True) + class A(Base): + a: int + + # __weakref__ is in the base class, not A. But an A is still weakref-able. + self.assertIn("__weakref__", Base.__slots__) + self.assertNotIn("__weakref__", A.__slots__) + a = A(1) + weakref.ref(a) + + def test_slots_weakref_base_tuple(self): + # Same as test_slots_weakref_base, but use a tuple instead of a string + # in the base class. + class Base: + __slots__ = ('__weakref__',) + + @dataclass(slots=True) + class A(Base): + a: int + + # __weakref__ is in the base class, not A. But an A is still + # weakref-able. + self.assertIn("__weakref__", Base.__slots__) + self.assertNotIn("__weakref__", A.__slots__) + a = A(1) + weakref.ref(a) + + def test_weakref_slot_without_slot(self): + with self.assertRaisesRegex(TypeError, + "weakref_slot is True but slots is False"): + @dataclass(weakref_slot=True) + class A: + a: int + + def test_weakref_slot_make_dataclass(self): + A = make_dataclass('A', [('a', int),], slots=True, weakref_slot=True) + self.assertIn("__weakref__", A.__slots__) + a = A(1) + weakref.ref(a) + + # And make sure if raises if slots=True is not given. + with self.assertRaisesRegex(TypeError, + "weakref_slot is True but slots is False"): + B = make_dataclass('B', [('a', int),], weakref_slot=True) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_weakref_slot_subclass_weakref_slot(self): + @dataclass(slots=True, weakref_slot=True) + class Base: + field: int + + # A *can* also specify weakref_slot=True if it wants to (gh-93521) + @dataclass(slots=True, weakref_slot=True) + class A(Base): + ... + + # __weakref__ is in the base class, not A. But an instance of A + # is still weakref-able. + self.assertIn("__weakref__", Base.__slots__) + self.assertNotIn("__weakref__", A.__slots__) + a = A(1) + a_ref = weakref.ref(a) + self.assertIs(a.__weakref__, a_ref) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_weakref_slot_subclass_no_weakref_slot(self): + @dataclass(slots=True, weakref_slot=True) + class Base: + field: int + + @dataclass(slots=True) + class A(Base): + ... + + # __weakref__ is in the base class, not A. Even though A doesn't + # specify weakref_slot, it should still be weakref-able. + self.assertIn("__weakref__", Base.__slots__) + self.assertNotIn("__weakref__", A.__slots__) + a = A(1) + a_ref = weakref.ref(a) + self.assertIs(a.__weakref__, a_ref) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_weakref_slot_normal_base_weakref_slot(self): + class Base: + __slots__ = ('__weakref__',) + + @dataclass(slots=True, weakref_slot=True) + class A(Base): + field: int + + # __weakref__ is in the base class, not A. But an instance of + # A is still weakref-able. + self.assertIn("__weakref__", Base.__slots__) + self.assertNotIn("__weakref__", A.__slots__) + a = A(1) + a_ref = weakref.ref(a) + self.assertIs(a.__weakref__, a_ref) + + def test_dataclass_derived_weakref_slot(self): + class A: + pass + + @dataclass(slots=True, weakref_slot=True) + class B(A): + pass + + self.assertEqual(B.__slots__, ()) + B() + + def test_dataclass_derived_generic(self): + T = typing.TypeVar('T') + + @dataclass(slots=True, weakref_slot=True) + class A(typing.Generic[T]): + pass + self.assertEqual(A.__slots__, ('__weakref__',)) + self.assertTrue(A.__weakref__) + A() + + @dataclass(slots=True, weakref_slot=True) + class B[T2]: + pass + self.assertEqual(B.__slots__, ('__weakref__',)) + self.assertTrue(B.__weakref__) + B() + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_dataclass_derived_generic_from_base(self): + T = typing.TypeVar('T') + + class RawBase: ... + + @dataclass(slots=True, weakref_slot=True) + class C1(typing.Generic[T], RawBase): + pass + self.assertEqual(C1.__slots__, ()) + self.assertTrue(C1.__weakref__) + C1() + @dataclass(slots=True, weakref_slot=True) + class C2(RawBase, typing.Generic[T]): + pass + self.assertEqual(C2.__slots__, ()) + self.assertTrue(C2.__weakref__) + C2() + + @dataclass(slots=True, weakref_slot=True) + class D[T2](RawBase): + pass + self.assertEqual(D.__slots__, ()) + self.assertTrue(D.__weakref__) + D() + + def test_dataclass_derived_generic_from_slotted_base(self): + T = typing.TypeVar('T') + + class WithSlots: + __slots__ = ('a', 'b') + + @dataclass(slots=True, weakref_slot=True) + class E1(WithSlots, Generic[T]): + pass + self.assertEqual(E1.__slots__, ('__weakref__',)) + self.assertTrue(E1.__weakref__) + E1() + @dataclass(slots=True, weakref_slot=True) + class E2(Generic[T], WithSlots): + pass + self.assertEqual(E2.__slots__, ('__weakref__',)) + self.assertTrue(E2.__weakref__) + E2() + + @dataclass(slots=True, weakref_slot=True) + class F[T2](WithSlots): + pass + self.assertEqual(F.__slots__, ('__weakref__',)) + self.assertTrue(F.__weakref__) + F() + + def test_dataclass_derived_generic_from_slotted_base_with_weakref(self): + T = typing.TypeVar('T') + + class WithWeakrefSlot: + __slots__ = ('__weakref__',) + + @dataclass(slots=True, weakref_slot=True) + class G1(WithWeakrefSlot, Generic[T]): + pass + self.assertEqual(G1.__slots__, ()) + self.assertTrue(G1.__weakref__) + G1() + @dataclass(slots=True, weakref_slot=True) + class G2(Generic[T], WithWeakrefSlot): + pass + self.assertEqual(G2.__slots__, ()) + self.assertTrue(G2.__weakref__) + G2() + + @dataclass(slots=True, weakref_slot=True) + class H[T2](WithWeakrefSlot): + pass + self.assertEqual(H.__slots__, ()) + self.assertTrue(H.__weakref__) + H() + + def test_dataclass_slot_dict(self): + class WithDictSlot: + __slots__ = ('__dict__',) + + @dataclass(slots=True) + class A(WithDictSlot): ... + + self.assertEqual(A.__slots__, ()) + self.assertEqual(A().__dict__, {}) + A() + + @support.cpython_only + def test_dataclass_slot_dict_ctype(self): + # https://github.com/python/cpython/issues/123935 + from test.support import import_helper + # Skips test if `_testcapi` is not present: + _testcapi = import_helper.import_module('_testcapi') + + @dataclass(slots=True) + class HasDictOffset(_testcapi.HeapCTypeWithDict): + __dict__: dict = {} + self.assertNotEqual(_testcapi.HeapCTypeWithDict.__dictoffset__, 0) + self.assertEqual(HasDictOffset.__slots__, ()) + + @dataclass(slots=True) + class DoesNotHaveDictOffset(_testcapi.HeapCTypeWithWeakref): + __dict__: dict = {} + self.assertEqual(_testcapi.HeapCTypeWithWeakref.__dictoffset__, 0) + self.assertEqual(DoesNotHaveDictOffset.__slots__, ('__dict__',)) + + @support.cpython_only + def test_slots_with_wrong_init_subclass(self): + # TODO: This test is for a kinda-buggy behavior. + # Ideally, it should be fixed and `__init_subclass__` + # should be fully supported in the future versions. + # See https://github.com/python/cpython/issues/91126 + class WrongSuper: + def __init_subclass__(cls, arg): + pass + + with self.assertRaisesRegex( + TypeError, + "missing 1 required positional argument: 'arg'", + ): + @dataclass(slots=True) + class WithWrongSuper(WrongSuper, arg=1): + pass + + class CorrectSuper: + args = [] + def __init_subclass__(cls, arg="default"): + cls.args.append(arg) + + @dataclass(slots=True) + class WithCorrectSuper(CorrectSuper): + pass + + # __init_subclass__ is called twice: once for `WithCorrectSuper` + # and once for `WithCorrectSuper__slots__` new class + # that we create internally. + self.assertEqual(CorrectSuper.args, ["default", "default"]) + + class TestDescriptors(unittest.TestCase): def test_set_name(self): # See bpo-33141. @@ -3202,10 +4058,10 @@ class C: self.assertEqual(C(10).x, 10) def test_classvar_module_level_import(self): - from test import dataclass_module_1 - from test import dataclass_module_1_str - from test import dataclass_module_2 - from test import dataclass_module_2_str + from test.test_dataclasses import dataclass_module_1 + from test.test_dataclasses import dataclass_module_1_str + from test.test_dataclasses import dataclass_module_2 + from test.test_dataclasses import dataclass_module_2_str for m in (dataclass_module_1, dataclass_module_1_str, dataclass_module_2, dataclass_module_2_str, @@ -3243,7 +4099,7 @@ def test_classvar_module_level_import(self): self.assertNotIn('not_iv4', c.__dict__) def test_text_annotations(self): - from test import dataclass_textanno + from test.test_dataclasses import dataclass_textanno self.assertEqual( get_type_hints(dataclass_textanno.Bar), @@ -3254,6 +4110,15 @@ def test_text_annotations(self): 'return': type(None)}) +ByMakeDataClass = make_dataclass('ByMakeDataClass', [('x', int)]) +ManualModuleMakeDataClass = make_dataclass('ManualModuleMakeDataClass', + [('x', int)], + module=__name__) +WrongNameMakeDataclass = make_dataclass('Wrong', [('x', int)]) +WrongModuleMakeDataclass = make_dataclass('WrongModuleMakeDataclass', + [('x', int)], + module='custom') + class TestMakeDataclass(unittest.TestCase): def test_simple(self): C = make_dataclass('C', @@ -3363,6 +4228,36 @@ def test_no_types(self): 'y': int, 'z': 'typing.Any'}) + def test_module_attr(self): + self.assertEqual(ByMakeDataClass.__module__, __name__) + self.assertEqual(ByMakeDataClass(1).__module__, __name__) + self.assertEqual(WrongModuleMakeDataclass.__module__, "custom") + Nested = make_dataclass('Nested', []) + self.assertEqual(Nested.__module__, __name__) + self.assertEqual(Nested().__module__, __name__) + + def test_pickle_support(self): + for klass in [ByMakeDataClass, ManualModuleMakeDataClass]: + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + self.assertEqual( + pickle.loads(pickle.dumps(klass, proto)), + klass, + ) + self.assertEqual( + pickle.loads(pickle.dumps(klass(1), proto)), + klass(1), + ) + + def test_cannot_be_pickled(self): + for klass in [WrongNameMakeDataclass, WrongModuleMakeDataclass]: + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + with self.assertRaises(pickle.PickleError): + pickle.dumps(klass, proto) + with self.assertRaises(pickle.PickleError): + pickle.dumps(klass(1), proto) + def test_invalid_type_specification(self): for bad_field in [(), (1, 2, 3, 4), @@ -3444,9 +4339,9 @@ class C: self.assertEqual((c1.x, c1.y, c1.z, c1.t), (3, 2, 10, 100)) - with self.assertRaisesRegex(ValueError, 'init=False'): + with self.assertRaisesRegex(TypeError, 'init=False'): replace(c, x=3, z=20, t=50) - with self.assertRaisesRegex(ValueError, 'init=False'): + with self.assertRaisesRegex(TypeError, 'init=False'): replace(c, z=20) replace(c, x=3, z=20, t=50) @@ -3499,10 +4394,10 @@ class C: self.assertEqual((c1.x, c1.y), (5, 10)) # Trying to replace y is an error. - with self.assertRaisesRegex(ValueError, 'init=False'): + with self.assertRaisesRegex(TypeError, 'init=False'): replace(c, x=2, y=30) - with self.assertRaisesRegex(ValueError, 'init=False'): + with self.assertRaisesRegex(TypeError, 'init=False'): replace(c, y=30) def test_classvar(self): @@ -3535,8 +4430,8 @@ def __post_init__(self, y): c = C(1, 10) self.assertEqual(c.x, 10) - with self.assertRaisesRegex(ValueError, r"InitVar 'y' must be " - "specified with replace()"): + with self.assertRaisesRegex(TypeError, r"InitVar 'y' must be " + r"specified with replace\(\)"): replace(c, x=3) c = replace(c, x=3, y=5) self.assertEqual(c.x, 15) @@ -3666,6 +4561,7 @@ class Date(Ordered): self.assertFalse(inspect.isabstract(Date)) self.assertGreater(Date(2020,12,25), Date(2020,8,31)) + # TODO: RUSTPYTHON @unittest.expectedFailure def test_maintain_abc(self): class A(abc.ABC): @@ -3680,7 +4576,7 @@ class Date(A): day: 'int' self.assertTrue(inspect.isabstract(Date)) - msg = 'class Date with abstract method foo' + msg = "class Date without an implementation for abstract method 'foo'" self.assertRaisesRegex(TypeError, msg, Date) @@ -3926,7 +4822,7 @@ class A: # But this usage is okay, since it's not using KW_ONLY. @dataclass - class A: + class NoDuplicateKwOnlyAnnotation: a: int _: KW_ONLY b: int @@ -3934,13 +4830,13 @@ class A: # And if inheriting, it's okay. @dataclass - class A: + class BaseUsesKwOnly: a: int _: KW_ONLY b: int c: int @dataclass - class B(A): + class SubclassUsesKwOnly(BaseUsesKwOnly): _: KW_ONLY d: int @@ -4003,7 +4899,7 @@ class A: # Make sure we still check for non-kwarg non-defaults not following # defaults. - err_regex = "non-default argument 'z' follows default argument" + err_regex = "non-default argument 'z' follows default argument 'a'" with self.assertRaisesRegex(TypeError, err_regex): @dataclass class A: @@ -4026,4 +4922,4 @@ def test_make_dataclass(self): if __name__ == '__main__': - unittest.main() + unittest.main() \ No newline at end of file diff --git a/Lib/test/test_dataclasses/dataclass_module_1.py b/Lib/test/test_dataclasses/dataclass_module_1.py new file mode 100644 index 00000000000..87a33f8191d --- /dev/null +++ b/Lib/test/test_dataclasses/dataclass_module_1.py @@ -0,0 +1,32 @@ +#from __future__ import annotations +USING_STRINGS = False + +# dataclass_module_1.py and dataclass_module_1_str.py are identical +# except only the latter uses string annotations. + +import dataclasses +import typing + +T_CV2 = typing.ClassVar[int] +T_CV3 = typing.ClassVar + +T_IV2 = dataclasses.InitVar[int] +T_IV3 = dataclasses.InitVar + +@dataclasses.dataclass +class CV: + T_CV4 = typing.ClassVar + cv0: typing.ClassVar[int] = 20 + cv1: typing.ClassVar = 30 + cv2: T_CV2 + cv3: T_CV3 + not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar. + +@dataclasses.dataclass +class IV: + T_IV4 = dataclasses.InitVar + iv0: dataclasses.InitVar[int] + iv1: dataclasses.InitVar + iv2: T_IV2 + iv3: T_IV3 + not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar. diff --git a/Lib/test/test_dataclasses/dataclass_module_1_str.py b/Lib/test/test_dataclasses/dataclass_module_1_str.py new file mode 100644 index 00000000000..6de490b7ad7 --- /dev/null +++ b/Lib/test/test_dataclasses/dataclass_module_1_str.py @@ -0,0 +1,32 @@ +from __future__ import annotations +USING_STRINGS = True + +# dataclass_module_1.py and dataclass_module_1_str.py are identical +# except only the latter uses string annotations. + +import dataclasses +import typing + +T_CV2 = typing.ClassVar[int] +T_CV3 = typing.ClassVar + +T_IV2 = dataclasses.InitVar[int] +T_IV3 = dataclasses.InitVar + +@dataclasses.dataclass +class CV: + T_CV4 = typing.ClassVar + cv0: typing.ClassVar[int] = 20 + cv1: typing.ClassVar = 30 + cv2: T_CV2 + cv3: T_CV3 + not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar. + +@dataclasses.dataclass +class IV: + T_IV4 = dataclasses.InitVar + iv0: dataclasses.InitVar[int] + iv1: dataclasses.InitVar + iv2: T_IV2 + iv3: T_IV3 + not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar. diff --git a/Lib/test/test_dataclasses/dataclass_module_2.py b/Lib/test/test_dataclasses/dataclass_module_2.py new file mode 100644 index 00000000000..68fb733e299 --- /dev/null +++ b/Lib/test/test_dataclasses/dataclass_module_2.py @@ -0,0 +1,32 @@ +#from __future__ import annotations +USING_STRINGS = False + +# dataclass_module_2.py and dataclass_module_2_str.py are identical +# except only the latter uses string annotations. + +from dataclasses import dataclass, InitVar +from typing import ClassVar + +T_CV2 = ClassVar[int] +T_CV3 = ClassVar + +T_IV2 = InitVar[int] +T_IV3 = InitVar + +@dataclass +class CV: + T_CV4 = ClassVar + cv0: ClassVar[int] = 20 + cv1: ClassVar = 30 + cv2: T_CV2 + cv3: T_CV3 + not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar. + +@dataclass +class IV: + T_IV4 = InitVar + iv0: InitVar[int] + iv1: InitVar + iv2: T_IV2 + iv3: T_IV3 + not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar. diff --git a/Lib/test/test_dataclasses/dataclass_module_2_str.py b/Lib/test/test_dataclasses/dataclass_module_2_str.py new file mode 100644 index 00000000000..b363d17c176 --- /dev/null +++ b/Lib/test/test_dataclasses/dataclass_module_2_str.py @@ -0,0 +1,32 @@ +from __future__ import annotations +USING_STRINGS = True + +# dataclass_module_2.py and dataclass_module_2_str.py are identical +# except only the latter uses string annotations. + +from dataclasses import dataclass, InitVar +from typing import ClassVar + +T_CV2 = ClassVar[int] +T_CV3 = ClassVar + +T_IV2 = InitVar[int] +T_IV3 = InitVar + +@dataclass +class CV: + T_CV4 = ClassVar + cv0: ClassVar[int] = 20 + cv1: ClassVar = 30 + cv2: T_CV2 + cv3: T_CV3 + not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar. + +@dataclass +class IV: + T_IV4 = InitVar + iv0: InitVar[int] + iv1: InitVar + iv2: T_IV2 + iv3: T_IV3 + not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar. diff --git a/Lib/test/test_dataclasses/dataclass_textanno.py b/Lib/test/test_dataclasses/dataclass_textanno.py new file mode 100644 index 00000000000..3eb6c943d4c --- /dev/null +++ b/Lib/test/test_dataclasses/dataclass_textanno.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +import dataclasses + + +class Foo: + pass + + +@dataclasses.dataclass +class Bar: + foo: Foo From 152d10bfea2374ca15fc835c146d720b437f803a Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Fri, 12 Dec 2025 16:26:56 +0900 Subject: [PATCH 492/819] hybrid dis.py from CPython 3.13.10 --- Lib/dis.py | 1054 ++++++++++++++++++++++++++++++++++++++ Lib/test/test__opcode.py | 2 - Lib/test/test_compile.py | 2 - Lib/test/test_fstring.py | 1 - 4 files changed, 1054 insertions(+), 5 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 53c85555bc9..05de51ce49c 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -1,3 +1,1057 @@ +"""Disassembler of Python byte code into mnemonics.""" + +import sys +import types +import collections +import io + +from opcode import * +from opcode import ( + __all__ as _opcodes_all, + _cache_format, + _inline_cache_entries, + _nb_ops, + _intrinsic_1_descs, + _intrinsic_2_descs, + _specializations, + _specialized_opmap, +) + +from _opcode import get_executor + + +__all__ = ["code_info", "dis", "disassemble", "distb", "disco", + "findlinestarts", "findlabels", "show_code", + "get_instructions", "Instruction", "Bytecode"] + _opcodes_all +del _opcodes_all + +_have_code = (types.MethodType, types.FunctionType, types.CodeType, + classmethod, staticmethod, type) + +CONVERT_VALUE = opmap['CONVERT_VALUE'] + +SET_FUNCTION_ATTRIBUTE = opmap['SET_FUNCTION_ATTRIBUTE'] +FUNCTION_ATTR_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure') + +ENTER_EXECUTOR = opmap['ENTER_EXECUTOR'] +LOAD_CONST = opmap['LOAD_CONST'] +RETURN_CONST = opmap['RETURN_CONST'] +LOAD_GLOBAL = opmap['LOAD_GLOBAL'] +BINARY_OP = opmap['BINARY_OP'] +JUMP_BACKWARD = opmap['JUMP_BACKWARD'] +FOR_ITER = opmap['FOR_ITER'] +SEND = opmap['SEND'] +LOAD_ATTR = opmap['LOAD_ATTR'] +LOAD_SUPER_ATTR = opmap['LOAD_SUPER_ATTR'] +CALL_INTRINSIC_1 = opmap['CALL_INTRINSIC_1'] +CALL_INTRINSIC_2 = opmap['CALL_INTRINSIC_2'] +LOAD_FAST_LOAD_FAST = opmap['LOAD_FAST_LOAD_FAST'] +STORE_FAST_LOAD_FAST = opmap['STORE_FAST_LOAD_FAST'] +STORE_FAST_STORE_FAST = opmap['STORE_FAST_STORE_FAST'] + +CACHE = opmap["CACHE"] + +_all_opname = list(opname) +_all_opmap = dict(opmap) +for name, op in _specialized_opmap.items(): + # fill opname and opmap + assert op < len(_all_opname) + _all_opname[op] = name + _all_opmap[name] = op + +deoptmap = { + specialized: base for base, family in _specializations.items() for specialized in family +} + +def _try_compile(source, name): + """Attempts to compile the given source, first as an expression and + then as a statement if the first approach fails. + + Utility function to accept strings in functions that otherwise + expect code objects + """ + try: + return compile(source, name, 'eval') + except SyntaxError: + pass + return compile(source, name, 'exec') + +def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False, + show_offsets=False): + """Disassemble classes, methods, functions, and other compiled objects. + + With no argument, disassemble the last traceback. + + Compiled objects currently include generator objects, async generator + objects, and coroutine objects, all of which store their code object + in a special attribute. + """ + if x is None: + distb(file=file, show_caches=show_caches, adaptive=adaptive, + show_offsets=show_offsets) + return + # Extract functions from methods. + if hasattr(x, '__func__'): + x = x.__func__ + # Extract compiled code objects from... + if hasattr(x, '__code__'): # ...a function, or + x = x.__code__ + elif hasattr(x, 'gi_code'): #...a generator object, or + x = x.gi_code + elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or + x = x.ag_code + elif hasattr(x, 'cr_code'): #...a coroutine. + x = x.cr_code + # Perform the disassembly. + if hasattr(x, '__dict__'): # Class or module + items = sorted(x.__dict__.items()) + for name, x1 in items: + if isinstance(x1, _have_code): + print("Disassembly of %s:" % name, file=file) + try: + dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) + except TypeError as msg: + print("Sorry:", msg, file=file) + print(file=file) + elif hasattr(x, 'co_code'): # Code object + _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) + elif isinstance(x, (bytes, bytearray)): # Raw bytecode + labels_map = _make_labels_map(x) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=file, + offset_width=len(str(max(len(x) - 2, 9999))) if show_offsets else 0, + label_width=label_width, + show_caches=show_caches) + arg_resolver = ArgResolver(labels_map=labels_map) + _disassemble_bytes(x, arg_resolver=arg_resolver, formatter=formatter) + elif isinstance(x, str): # Source code + _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) + else: + raise TypeError("don't know how to disassemble %s objects" % + type(x).__name__) + +def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets=False): + """Disassemble a traceback (default: last traceback).""" + if tb is None: + try: + if hasattr(sys, 'last_exc'): + tb = sys.last_exc.__traceback__ + else: + tb = sys.last_traceback + except AttributeError: + raise RuntimeError("no last traceback to disassemble") from None + while tb.tb_next: tb = tb.tb_next + disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) + +# The inspect module interrogates this dictionary to build its +# list of CO_* constants. It is also used by pretty_flags to +# turn the co_flags field into a human readable list. +COMPILER_FLAG_NAMES = { + 1: "OPTIMIZED", + 2: "NEWLOCALS", + 4: "VARARGS", + 8: "VARKEYWORDS", + 16: "NESTED", + 32: "GENERATOR", + 64: "NOFREE", + 128: "COROUTINE", + 256: "ITERABLE_COROUTINE", + 512: "ASYNC_GENERATOR", +} + +def pretty_flags(flags): + """Return pretty representation of code flags.""" + names = [] + for i in range(32): + flag = 1<<i + if flags & flag: + names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag))) + flags ^= flag + if not flags: + break + else: + names.append(hex(flags)) + return ", ".join(names) + +class _Unknown: + def __repr__(self): + return "<unknown>" + +# Sentinel to represent values that cannot be calculated +UNKNOWN = _Unknown() + +def _get_code_object(x): + """Helper to handle methods, compiled or raw code objects, and strings.""" + # Extract functions from methods. + if hasattr(x, '__func__'): + x = x.__func__ + # Extract compiled code objects from... + if hasattr(x, '__code__'): # ...a function, or + x = x.__code__ + elif hasattr(x, 'gi_code'): #...a generator object, or + x = x.gi_code + elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or + x = x.ag_code + elif hasattr(x, 'cr_code'): #...a coroutine. + x = x.cr_code + # Handle source code. + if isinstance(x, str): + x = _try_compile(x, "<disassembly>") + # By now, if we don't have a code object, we can't disassemble x. + if hasattr(x, 'co_code'): + return x + raise TypeError("don't know how to disassemble %s objects" % + type(x).__name__) + +def _deoptop(op): + name = _all_opname[op] + return _all_opmap[deoptmap[name]] if name in deoptmap else op + +def _get_code_array(co, adaptive): + if adaptive: + code = co._co_code_adaptive + res = [] + found = False + for i in range(0, len(code), 2): + op, arg = code[i], code[i+1] + if op == ENTER_EXECUTOR: + try: + ex = get_executor(co, i) + except (ValueError, RuntimeError): + ex = None + + if ex: + op, arg = ex.get_opcode(), ex.get_oparg() + found = True + + res.append(op.to_bytes()) + res.append(arg.to_bytes()) + return code if not found else b''.join(res) + else: + return co.co_code + +def code_info(x): + """Formatted details of methods, functions, or code.""" + return _format_code_info(_get_code_object(x)) + +def _format_code_info(co): + lines = [] + lines.append("Name: %s" % co.co_name) + lines.append("Filename: %s" % co.co_filename) + lines.append("Argument count: %s" % co.co_argcount) + lines.append("Positional-only arguments: %s" % co.co_posonlyargcount) + lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount) + lines.append("Number of locals: %s" % co.co_nlocals) + lines.append("Stack size: %s" % co.co_stacksize) + lines.append("Flags: %s" % pretty_flags(co.co_flags)) + if co.co_consts: + lines.append("Constants:") + for i_c in enumerate(co.co_consts): + lines.append("%4d: %r" % i_c) + if co.co_names: + lines.append("Names:") + for i_n in enumerate(co.co_names): + lines.append("%4d: %s" % i_n) + if co.co_varnames: + lines.append("Variable names:") + for i_n in enumerate(co.co_varnames): + lines.append("%4d: %s" % i_n) + if co.co_freevars: + lines.append("Free variables:") + for i_n in enumerate(co.co_freevars): + lines.append("%4d: %s" % i_n) + if co.co_cellvars: + lines.append("Cell variables:") + for i_n in enumerate(co.co_cellvars): + lines.append("%4d: %s" % i_n) + return "\n".join(lines) + +def show_code(co, *, file=None): + """Print details of methods, functions, or code to *file*. + + If *file* is not provided, the output is printed on stdout. + """ + print(code_info(co), file=file) + +Positions = collections.namedtuple( + 'Positions', + [ + 'lineno', + 'end_lineno', + 'col_offset', + 'end_col_offset', + ], + defaults=[None] * 4 +) + +_Instruction = collections.namedtuple( + "_Instruction", + [ + 'opname', + 'opcode', + 'arg', + 'argval', + 'argrepr', + 'offset', + 'start_offset', + 'starts_line', + 'line_number', + 'label', + 'positions', + 'cache_info', + ], + defaults=[None, None, None] +) + +_Instruction.opname.__doc__ = "Human readable name for operation" +_Instruction.opcode.__doc__ = "Numeric code for operation" +_Instruction.arg.__doc__ = "Numeric argument to operation (if any), otherwise None" +_Instruction.argval.__doc__ = "Resolved arg value (if known), otherwise same as arg" +_Instruction.argrepr.__doc__ = "Human readable description of operation argument" +_Instruction.offset.__doc__ = "Start index of operation within bytecode sequence" +_Instruction.start_offset.__doc__ = ( + "Start index of operation within bytecode sequence, including extended args if present; " + "otherwise equal to Instruction.offset" +) +_Instruction.starts_line.__doc__ = "True if this opcode starts a source line, otherwise False" +_Instruction.line_number.__doc__ = "source line number associated with this opcode (if any), otherwise None" +_Instruction.label.__doc__ = "A label (int > 0) if this instruction is a jump target, otherwise None" +_Instruction.positions.__doc__ = "dis.Positions object holding the span of source code covered by this instruction" +_Instruction.cache_info.__doc__ = "list of (name, size, data), one for each cache entry of the instruction" + +_ExceptionTableEntryBase = collections.namedtuple("_ExceptionTableEntryBase", + "start end target depth lasti") + +class _ExceptionTableEntry(_ExceptionTableEntryBase): + pass + +_OPNAME_WIDTH = 20 +_OPARG_WIDTH = 5 + +def _get_cache_size(opname): + return _inline_cache_entries.get(opname, 0) + +def _get_jump_target(op, arg, offset): + """Gets the bytecode offset of the jump target if this is a jump instruction. + + Otherwise return None. + """ + deop = _deoptop(op) + caches = _get_cache_size(_all_opname[deop]) + if deop in hasjrel: + if _is_backward_jump(deop): + arg = -arg + target = offset + 2 + arg*2 + target += 2 * caches + elif deop in hasjabs: + target = arg*2 + else: + target = None + return target + +class Instruction(_Instruction): + """Details for a bytecode operation. + + Defined fields: + opname - human readable name for operation + opcode - numeric code for operation + arg - numeric argument to operation (if any), otherwise None + argval - resolved arg value (if known), otherwise same as arg + argrepr - human readable description of operation argument + offset - start index of operation within bytecode sequence + start_offset - start index of operation within bytecode sequence including extended args if present; + otherwise equal to Instruction.offset + starts_line - True if this opcode starts a source line, otherwise False + line_number - source line number associated with this opcode (if any), otherwise None + label - A label if this instruction is a jump target, otherwise None + positions - Optional dis.Positions object holding the span of source code + covered by this instruction + cache_info - information about the format and content of the instruction's cache + entries (if any) + """ + + @property + def oparg(self): + """Alias for Instruction.arg.""" + return self.arg + + @property + def baseopcode(self): + """Numeric code for the base operation if operation is specialized. + + Otherwise equal to Instruction.opcode. + """ + return _deoptop(self.opcode) + + @property + def baseopname(self): + """Human readable name for the base operation if operation is specialized. + + Otherwise equal to Instruction.opname. + """ + return opname[self.baseopcode] + + @property + def cache_offset(self): + """Start index of the cache entries following the operation.""" + return self.offset + 2 + + @property + def end_offset(self): + """End index of the cache entries following the operation.""" + return self.cache_offset + _get_cache_size(_all_opname[self.opcode])*2 + + @property + def jump_target(self): + """Bytecode index of the jump target if this is a jump operation. + + Otherwise return None. + """ + return _get_jump_target(self.opcode, self.arg, self.offset) + + @property + def is_jump_target(self): + """True if other code jumps to here, otherwise False""" + return self.label is not None + + def __str__(self): + output = io.StringIO() + formatter = Formatter(file=output) + formatter.print_instruction(self, False) + return output.getvalue() + + +class Formatter: + + def __init__(self, file=None, lineno_width=0, offset_width=0, label_width=0, + line_offset=0, show_caches=False): + """Create a Formatter + + *file* where to write the output + *lineno_width* sets the width of the line number field (0 omits it) + *offset_width* sets the width of the instruction offset field + *label_width* sets the width of the label field + *show_caches* is a boolean indicating whether to display cache lines + + """ + self.file = file + self.lineno_width = lineno_width + self.offset_width = offset_width + self.label_width = label_width + self.show_caches = show_caches + + def print_instruction(self, instr, mark_as_current=False): + self.print_instruction_line(instr, mark_as_current) + if self.show_caches and instr.cache_info: + offset = instr.offset + for name, size, data in instr.cache_info: + for i in range(size): + offset += 2 + # Only show the fancy argrepr for a CACHE instruction when it's + # the first entry for a particular cache value: + if i == 0: + argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" + else: + argrepr = "" + self.print_instruction_line( + Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, + False, None, None, instr.positions), + False) + + def print_instruction_line(self, instr, mark_as_current): + """Format instruction details for inclusion in disassembly output.""" + lineno_width = self.lineno_width + offset_width = self.offset_width + label_width = self.label_width + + new_source_line = (lineno_width > 0 and + instr.starts_line and + instr.offset > 0) + if new_source_line: + print(file=self.file) + + fields = [] + # Column: Source code line number + if lineno_width: + if instr.starts_line: + lineno_fmt = "%%%dd" if instr.line_number is not None else "%%%ds" + lineno_fmt = lineno_fmt % lineno_width + lineno = _NO_LINENO if instr.line_number is None else instr.line_number + fields.append(lineno_fmt % lineno) + else: + fields.append(' ' * lineno_width) + # Column: Label + if instr.label is not None: + lbl = f"L{instr.label}:" + fields.append(f"{lbl:>{label_width}}") + else: + fields.append(' ' * label_width) + # Column: Instruction offset from start of code sequence + if offset_width > 0: + fields.append(f"{repr(instr.offset):>{offset_width}} ") + # Column: Current instruction indicator + if mark_as_current: + fields.append('-->') + else: + fields.append(' ') + # Column: Opcode name + fields.append(instr.opname.ljust(_OPNAME_WIDTH)) + # Column: Opcode argument + if instr.arg is not None: + arg = repr(instr.arg) + # If opname is longer than _OPNAME_WIDTH, we allow it to overflow into + # the space reserved for oparg. This results in fewer misaligned opargs + # in the disassembly output. + opname_excess = max(0, len(instr.opname) - _OPNAME_WIDTH) + fields.append(repr(instr.arg).rjust(_OPARG_WIDTH - opname_excess)) + # Column: Opcode argument details + if instr.argrepr: + fields.append('(' + instr.argrepr + ')') + print(' '.join(fields).rstrip(), file=self.file) + + def print_exception_table(self, exception_entries): + file = self.file + if exception_entries: + print("ExceptionTable:", file=file) + for entry in exception_entries: + lasti = " lasti" if entry.lasti else "" + start = entry.start_label + end = entry.end_label + target = entry.target_label + print(f" L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file) + + +class ArgResolver: + def __init__(self, co_consts=None, names=None, varname_from_oparg=None, labels_map=None): + self.co_consts = co_consts + self.names = names + self.varname_from_oparg = varname_from_oparg + self.labels_map = labels_map or {} + + def offset_from_jump_arg(self, op, arg, offset): + deop = _deoptop(op) + if deop in hasjabs: + return arg * 2 + elif deop in hasjrel: + signed_arg = -arg if _is_backward_jump(deop) else arg + argval = offset + 2 + signed_arg*2 + caches = _get_cache_size(_all_opname[deop]) + argval += 2 * caches + return argval + return None + + def get_label_for_offset(self, offset): + return self.labels_map.get(offset, None) + + def get_argval_argrepr(self, op, arg, offset): + get_name = None if self.names is None else self.names.__getitem__ + argval = None + argrepr = '' + deop = _deoptop(op) + if arg is not None: + # Set argval to the dereferenced value of the argument when + # available, and argrepr to the string representation of argval. + # _disassemble_bytes needs the string repr of the + # raw name index for LOAD_GLOBAL, LOAD_CONST, etc. + argval = arg + if deop in hasconst: + argval, argrepr = _get_const_info(deop, arg, self.co_consts) + elif deop in hasname: + if deop == LOAD_GLOBAL: + argval, argrepr = _get_name_info(arg//2, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + NULL" + elif deop == LOAD_ATTR: + argval, argrepr = _get_name_info(arg//2, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + NULL|self" + elif deop == LOAD_SUPER_ATTR: + argval, argrepr = _get_name_info(arg//4, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + NULL|self" + else: + argval, argrepr = _get_name_info(arg, get_name) + elif deop in hasjump or deop in hasexc: + argval = self.offset_from_jump_arg(op, arg, offset) + lbl = self.get_label_for_offset(argval) + assert lbl is not None + argrepr = f"to L{lbl}" + elif deop in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST): + arg1 = arg >> 4 + arg2 = arg & 15 + val1, argrepr1 = _get_name_info(arg1, self.varname_from_oparg) + val2, argrepr2 = _get_name_info(arg2, self.varname_from_oparg) + argrepr = argrepr1 + ", " + argrepr2 + argval = val1, val2 + elif deop in haslocal or deop in hasfree: + argval, argrepr = _get_name_info(arg, self.varname_from_oparg) + elif deop in hascompare: + argval = cmp_op[arg >> 5] + argrepr = argval + if arg & 16: + argrepr = f"bool({argrepr})" + elif deop == CONVERT_VALUE: + argval = (None, str, repr, ascii)[arg] + argrepr = ('', 'str', 'repr', 'ascii')[arg] + elif deop == SET_FUNCTION_ATTRIBUTE: + argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS) + if arg & (1<<i)) + elif deop == BINARY_OP: + _, argrepr = _nb_ops[arg] + elif deop == CALL_INTRINSIC_1: + argrepr = _intrinsic_1_descs[arg] + elif deop == CALL_INTRINSIC_2: + argrepr = _intrinsic_2_descs[arg] + return argval, argrepr + +def get_instructions(x, *, first_line=None, show_caches=None, adaptive=False): + """Iterator for the opcodes in methods, functions or code + + Generates a series of Instruction named tuples giving the details of + each operations in the supplied code. + + If *first_line* is not None, it indicates the line number that should + be reported for the first source line in the disassembled code. + Otherwise, the source line information (if any) is taken directly from + the disassembled code object. + """ + co = _get_code_object(x) + linestarts = dict(findlinestarts(co)) + if first_line is not None: + line_offset = first_line - co.co_firstlineno + else: + line_offset = 0 + + original_code = co.co_code + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=_make_labels_map(original_code)) + return _get_instructions_bytes(_get_code_array(co, adaptive), + linestarts=linestarts, + line_offset=line_offset, + co_positions=co.co_positions(), + original_code=original_code, + arg_resolver=arg_resolver) + +def _get_const_value(op, arg, co_consts): + """Helper to get the value of the const in a hasconst op. + + Returns the dereferenced constant if this is possible. + Otherwise (if it is a LOAD_CONST and co_consts is not + provided) returns the dis.UNKNOWN sentinel. + """ + assert op in hasconst + + argval = UNKNOWN + if co_consts is not None: + argval = co_consts[arg] + return argval + +def _get_const_info(op, arg, co_consts): + """Helper to get optional details about const references + + Returns the dereferenced constant and its repr if the value + can be calculated. + Otherwise returns the sentinel value dis.UNKNOWN for the value + and an empty string for its repr. + """ + argval = _get_const_value(op, arg, co_consts) + argrepr = repr(argval) if argval is not UNKNOWN else '' + return argval, argrepr + +def _get_name_info(name_index, get_name, **extrainfo): + """Helper to get optional details about named references + + Returns the dereferenced name as both value and repr if the name + list is defined. + Otherwise returns the sentinel value dis.UNKNOWN for the value + and an empty string for its repr. + """ + if get_name is not None: + argval = get_name(name_index, **extrainfo) + return argval, argval + else: + return UNKNOWN, '' + +def _parse_varint(iterator): + b = next(iterator) + val = b & 63 + while b&64: + val <<= 6 + b = next(iterator) + val |= b&63 + return val + +def _parse_exception_table(code): + iterator = iter(code.co_exceptiontable) + entries = [] + try: + while True: + start = _parse_varint(iterator)*2 + length = _parse_varint(iterator)*2 + end = start + length + target = _parse_varint(iterator)*2 + dl = _parse_varint(iterator) + depth = dl >> 1 + lasti = bool(dl&1) + entries.append(_ExceptionTableEntry(start, end, target, depth, lasti)) + except StopIteration: + return entries + +def _is_backward_jump(op): + return opname[op] in ('JUMP_BACKWARD', + 'JUMP_BACKWARD_NO_INTERRUPT') + +def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=None, + original_code=None, arg_resolver=None): + """Iterate over the instructions in a bytecode string. + + Generates a sequence of Instruction namedtuples giving the details of each + opcode. + + """ + # Use the basic, unadaptive code for finding labels and actually walking the + # bytecode, since replacements like ENTER_EXECUTOR and INSTRUMENTED_* can + # mess that logic up pretty badly: + original_code = original_code or code + co_positions = co_positions or iter(()) + + starts_line = False + local_line_number = None + line_number = None + for offset, start_offset, op, arg in _unpack_opargs(original_code): + if linestarts is not None: + starts_line = offset in linestarts + if starts_line: + local_line_number = linestarts[offset] + if local_line_number is not None: + line_number = local_line_number + line_offset + else: + line_number = None + positions = Positions(*next(co_positions, ())) + deop = _deoptop(op) + op = code[offset] + + if arg_resolver: + argval, argrepr = arg_resolver.get_argval_argrepr(op, arg, offset) + else: + argval, argrepr = arg, repr(arg) + + caches = _get_cache_size(_all_opname[deop]) + # Advance the co_positions iterator: + for _ in range(caches): + next(co_positions, ()) + + if caches: + cache_info = [] + for name, size in _cache_format[opname[deop]].items(): + data = code[offset + 2: offset + 2 + 2 * size] + cache_info.append((name, size, data)) + else: + cache_info = None + + label = arg_resolver.get_label_for_offset(offset) if arg_resolver else None + yield Instruction(_all_opname[op], op, arg, argval, argrepr, + offset, start_offset, starts_line, line_number, + label, positions, cache_info) + + +def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False, + show_offsets=False): + """Disassemble a code object.""" + linestarts = dict(findlinestarts(co)) + exception_entries = _parse_exception_table(co) + labels_map = _make_labels_map(co.co_code, exception_entries=exception_entries) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=file, + lineno_width=_get_lineno_width(linestarts), + offset_width=len(str(max(len(co.co_code) - 2, 9999))) if show_offsets else 0, + label_width=label_width, + show_caches=show_caches) + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) + _disassemble_bytes(_get_code_array(co, adaptive), lasti, linestarts, + exception_entries=exception_entries, co_positions=co.co_positions(), + original_code=co.co_code, arg_resolver=arg_resolver, formatter=formatter) + +def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False): + disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) + if depth is None or depth > 0: + if depth is not None: + depth = depth - 1 + for x in co.co_consts: + if hasattr(x, 'co_code'): + print(file=file) + print("Disassembly of %r:" % (x,), file=file) + _disassemble_recursive( + x, file=file, depth=depth, show_caches=show_caches, + adaptive=adaptive, show_offsets=show_offsets + ) + + +def _make_labels_map(original_code, exception_entries=()): + jump_targets = set(findlabels(original_code)) + labels = set(jump_targets) + for start, end, target, _, _ in exception_entries: + labels.add(start) + labels.add(end) + labels.add(target) + labels = sorted(labels) + labels_map = {offset: i+1 for (i, offset) in enumerate(sorted(labels))} + for e in exception_entries: + e.start_label = labels_map[e.start] + e.end_label = labels_map[e.end] + e.target_label = labels_map[e.target] + return labels_map + +_NO_LINENO = ' --' + +def _get_lineno_width(linestarts): + if linestarts is None: + return 0 + maxlineno = max(filter(None, linestarts.values()), default=-1) + if maxlineno == -1: + # Omit the line number column entirely if we have no line number info + return 0 + lineno_width = max(3, len(str(maxlineno))) + if lineno_width < len(_NO_LINENO) and None in linestarts.values(): + lineno_width = len(_NO_LINENO) + return lineno_width + + +def _disassemble_bytes(code, lasti=-1, linestarts=None, + *, line_offset=0, exception_entries=(), + co_positions=None, original_code=None, + arg_resolver=None, formatter=None): + + assert formatter is not None + assert arg_resolver is not None + + instrs = _get_instructions_bytes(code, linestarts=linestarts, + line_offset=line_offset, + co_positions=co_positions, + original_code=original_code, + arg_resolver=arg_resolver) + + print_instructions(instrs, exception_entries, formatter, lasti=lasti) + + +def print_instructions(instrs, exception_entries, formatter, lasti=-1): + for instr in instrs: + # Each CACHE takes 2 bytes + is_current_instr = instr.offset <= lasti \ + <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) + formatter.print_instruction(instr, is_current_instr) + + formatter.print_exception_table(exception_entries) + +def _disassemble_str(source, **kwargs): + """Compile the source string, then disassemble the code object.""" + _disassemble_recursive(_try_compile(source, '<dis>'), **kwargs) + +disco = disassemble # XXX For backwards compatibility + + +# Rely on C `int` being 32 bits for oparg +_INT_BITS = 32 +# Value for c int when it overflows +_INT_OVERFLOW = 2 ** (_INT_BITS - 1) + +def _unpack_opargs(code): + extended_arg = 0 + extended_args_offset = 0 # Number of EXTENDED_ARG instructions preceding the current instruction + caches = 0 + for i in range(0, len(code), 2): + # Skip inline CACHE entries: + if caches: + caches -= 1 + continue + op = code[i] + deop = _deoptop(op) + caches = _get_cache_size(_all_opname[deop]) + if deop in hasarg: + arg = code[i+1] | extended_arg + extended_arg = (arg << 8) if deop == EXTENDED_ARG else 0 + # The oparg is stored as a signed integer + # If the value exceeds its upper limit, it will overflow and wrap + # to a negative integer + if extended_arg >= _INT_OVERFLOW: + extended_arg -= 2 * _INT_OVERFLOW + else: + arg = None + extended_arg = 0 + if deop == EXTENDED_ARG: + extended_args_offset += 1 + yield (i, i, op, arg) + else: + start_offset = i - extended_args_offset*2 + yield (i, start_offset, op, arg) + extended_args_offset = 0 + +def findlabels(code): + """Detect all offsets in a byte code which are jump targets. + + Return the list of offsets. + + """ + labels = [] + for offset, _, op, arg in _unpack_opargs(code): + if arg is not None: + label = _get_jump_target(op, arg, offset) + if label is None: + continue + if label not in labels: + labels.append(label) + return labels + +def findlinestarts(code): + """Find the offsets in a byte code which are start of lines in the source. + + Generate pairs (offset, lineno) + lineno will be an integer or None the offset does not have a source line. + """ + + lastline = False # None is a valid line number + for start, end, line in code.co_lines(): + if line is not lastline: + lastline = line + yield start, line + return + +def _find_imports(co): + """Find import statements in the code + + Generate triplets (name, level, fromlist) where + name is the imported module and level, fromlist are + the corresponding args to __import__. + """ + IMPORT_NAME = opmap['IMPORT_NAME'] + + consts = co.co_consts + names = co.co_names + opargs = [(op, arg) for _, _, op, arg in _unpack_opargs(co.co_code) + if op != EXTENDED_ARG] + for i, (op, oparg) in enumerate(opargs): + if op == IMPORT_NAME and i >= 2: + from_op = opargs[i-1] + level_op = opargs[i-2] + if (from_op[0] in hasconst and level_op[0] in hasconst): + level = _get_const_value(level_op[0], level_op[1], consts) + fromlist = _get_const_value(from_op[0], from_op[1], consts) + yield (names[oparg], level, fromlist) + +def _find_store_names(co): + """Find names of variables which are written in the code + + Generate sequence of strings + """ + STORE_OPS = { + opmap['STORE_NAME'], + opmap['STORE_GLOBAL'] + } + + names = co.co_names + for _, _, op, arg in _unpack_opargs(co.co_code): + if op in STORE_OPS: + yield names[arg] + + +class Bytecode: + """The bytecode operations of a piece of code + + Instantiate this with a function, method, other compiled object, string of + code, or a code object (as returned by compile()). + + Iterating over this yields the bytecode operations as Instruction instances. + """ + def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False, adaptive=False, show_offsets=False): + self.codeobj = co = _get_code_object(x) + if first_line is None: + self.first_line = co.co_firstlineno + self._line_offset = 0 + else: + self.first_line = first_line + self._line_offset = first_line - co.co_firstlineno + self._linestarts = dict(findlinestarts(co)) + self._original_object = x + self.current_offset = current_offset + self.exception_entries = _parse_exception_table(co) + self.show_caches = show_caches + self.adaptive = adaptive + self.show_offsets = show_offsets + + def __iter__(self): + co = self.codeobj + original_code = co.co_code + labels_map = _make_labels_map(original_code, self.exception_entries) + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) + return _get_instructions_bytes(_get_code_array(co, self.adaptive), + linestarts=self._linestarts, + line_offset=self._line_offset, + co_positions=co.co_positions(), + original_code=original_code, + arg_resolver=arg_resolver) + + def __repr__(self): + return "{}({!r})".format(self.__class__.__name__, + self._original_object) + + @classmethod + def from_traceback(cls, tb, *, show_caches=False, adaptive=False): + """ Construct a Bytecode from the given traceback """ + while tb.tb_next: + tb = tb.tb_next + return cls( + tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches, adaptive=adaptive + ) + + def info(self): + """Return formatted information about the code object.""" + return _format_code_info(self.codeobj) + + def dis(self): + """Return a formatted view of the bytecode operations.""" + co = self.codeobj + if self.current_offset is not None: + offset = self.current_offset + else: + offset = -1 + with io.StringIO() as output: + code = _get_code_array(co, self.adaptive) + offset_width = len(str(max(len(code) - 2, 9999))) if self.show_offsets else 0 + + + labels_map = _make_labels_map(co.co_code, self.exception_entries) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=output, + lineno_width=_get_lineno_width(self._linestarts), + offset_width=offset_width, + label_width=label_width, + line_offset=self._line_offset, + show_caches=self.show_caches) + + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) + _disassemble_bytes(code, + linestarts=self._linestarts, + line_offset=self._line_offset, + lasti=offset, + exception_entries=self.exception_entries, + co_positions=co.co_positions(), + original_code=co.co_code, + arg_resolver=arg_resolver, + formatter=formatter) + return output.getvalue() + + from _dis import * diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py index b1e38b43dc8..dd4f30ab17d 100644 --- a/Lib/test/test__opcode.py +++ b/Lib/test/test__opcode.py @@ -27,7 +27,6 @@ def test_invalid_opcodes(self): self.check_bool_function_result(_opcode.has_local, invalid, False) self.check_bool_function_result(_opcode.has_exc, invalid, False) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'dis' has no attribute 'opmap' def test_is_valid(self): names = [ 'CACHE', @@ -39,7 +38,6 @@ def test_is_valid(self): opcodes = [dis.opmap[opname] for opname in names] self.check_bool_function_result(_opcode.is_valid, opcodes, True) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'dis' has no attribute 'hasarg' def test_oplists(self): def check_function(self, func, expected): for op in [-10, 520]: diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 27bbe0b64ab..e4d335a193d 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1070,8 +1070,6 @@ def if_else_break(): elif instr.opname in HANDLED_JUMPS: self.assertNotEqual(instr.arg, (line + 1)*INSTR_SIZE) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_no_wraparound_jump(self): # See https://bugs.python.org/issue46724 diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index 229f16393fe..930e409fb2e 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -1799,7 +1799,6 @@ def test_syntax_warning_infinite_recursion_in_file(self): self.assertIn(rb'\1', stdout) self.assertEqual(len(stderr.strip().splitlines()), 2) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'dis' has no attribute 'get_instructions' def test_fstring_without_formatting_bytecode(self): # f-string without any formatting should emit the same bytecode # as a normal string. See gh-99606. From ef6c6ad117c93d183313e5ba461c39460f4808c8 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Fri, 12 Dec 2025 16:28:42 +0900 Subject: [PATCH 493/819] patch test_inspect --- Lib/test/test_inspect/test_inspect.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 49996bba2ca..0670a21bebc 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -12,7 +12,6 @@ import os import dis from os.path import normcase -import _pickle import pickle import shutil import stat @@ -29,6 +28,12 @@ import warnings import weakref +# XXX: RUSTPYTHON; skip _pickle tests if _pickle is not available +try: + import _pickle +except ImportError: + _pickle = None + try: from concurrent.futures import ThreadPoolExecutor @@ -37,7 +42,8 @@ from test.support import cpython_only, import_helper, suppress_immortalization from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ -from test.support.import_helper import DirsOnSysPath, ready_to_import +# XXX: RUSTPYTHON; test.support is not updated yet +from test.support.import_helper import DirsOnSysPath #, ready_to_import from test.support.os_helper import TESTFN, temp_cwd from test.support.script_helper import assert_python_ok, assert_python_failure, kill_python from test.support import has_subprocess_support, SuppressCrashReport @@ -1361,6 +1367,7 @@ def test(): pass @unittest.skipIf(MISSING_C_DOCSTRINGS, "Signature information for builtins requires docstrings") + @unittest.skipIf(_pickle is None, "requires _pickle") def test_getfullargspec_builtin_methods(self): self.assertFullArgSpecEquals(_pickle.Pickler.dump, ['self', 'obj']) @@ -4714,6 +4721,7 @@ class D(C): pass @unittest.skipIf(MISSING_C_DOCSTRINGS, "Signature information for builtins requires docstrings") + @unittest.skipIf(_pickle is None, "requires _pickle") def test_signature_on_builtin_class(self): expected = ('(file, protocol=None, fix_imports=True, ' 'buffer_callback=None)') @@ -5240,6 +5248,7 @@ class foo: pass @unittest.skipIf(MISSING_C_DOCSTRINGS, "Signature information for builtins requires docstrings") + @unittest.skipIf(_pickle is None, "requires _pickle") def test_signature_from_callable_builtin_obj(self): class MySignature(inspect.Signature): pass sig = MySignature.from_callable(_pickle.Pickler) From 9bbfdf5067542d3b23e48f9e5019a6dc17dddaf6 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Fri, 12 Dec 2025 19:35:38 +0900 Subject: [PATCH 494/819] mark expectedFailure --- Lib/test/test_abc.py | 2 + Lib/test/test_enum.py | 2 - Lib/test/test_inspect/test_inspect.py | 126 +++++++++++++++++- .../test_unittest/testmock/testhelpers.py | 2 - Lib/test/test_warnings/__init__.py | 4 - 5 files changed, 127 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index ac46ea67bb5..92bd955855b 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -168,6 +168,8 @@ def method_two(self): msg = r"class C without an implementation for abstract methods 'method_one', 'method_two'" self.assertRaisesRegex(TypeError, msg, C) + # TODO: RUSTPYTHON; AssertionError: False is not true + @unittest.expectedFailure def test_abstractmethod_integration(self): for abstractthing in [abc.abstractmethod, abc.abstractproperty, abc.abstractclassmethod, diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index bff85a7ec26..32a3c1dee07 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -4882,8 +4882,6 @@ def test_inspect_classify_class_attrs(self): if failed: self.fail("result does not equal expected, see print above") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_inspect_signatures(self): from inspect import signature, Signature, Parameter self.assertEqual( diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 0670a21bebc..d0fec18250e 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -131,6 +131,8 @@ def istest(self, predicate, exp): continue self.assertFalse(other(obj), 'not %s(%s)' % (other.__name__, exp)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test__all__(self): support.check__all__(self, inspect, not_exported=("modulesbyfile",)) @@ -183,6 +185,8 @@ def __get__(self, instance, owner): class TestPredicates(IsTestBase): + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_excluding_predicates(self): global tb self.istest(inspect.isbuiltin, 'sys.exit') @@ -234,6 +238,8 @@ def test_excluding_predicates(self): + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_iscoroutine(self): async_gen_coro = async_generator_function_example(1) gen_coro = gen_coroutine_function_example(1) @@ -378,6 +384,8 @@ def do_something_static(): coro.close(); gen_coro.close(); # silence warnings + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_isawaitable(self): def gen(): yield self.assertFalse(inspect.isawaitable(gen())) @@ -476,6 +484,8 @@ class C(object): self.assertIn('a', members) self.assertNotIn('b', members) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_isabstract(self): from abc import ABCMeta, abstractmethod @@ -498,6 +508,8 @@ def foo(self): self.assertFalse(inspect.isabstract(int)) self.assertFalse(inspect.isabstract(5)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_isabstract_during_init_subclass(self): from abc import ABCMeta, abstractmethod isabstract_checks = [] @@ -533,6 +545,8 @@ def test_abuse_done(self): self.istest(inspect.istraceback, 'git.ex.__traceback__') self.istest(inspect.isframe, 'mod.fr') + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_stack(self): self.assertTrue(len(mod.st) >= 5) frame1, frame2, frame3, frame4, *_ = mod.st @@ -561,6 +575,8 @@ def test_stack(self): self.assertIn('inspect.stack()', record.code_context[0]) self.assertEqual(record.index, 0) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_trace(self): self.assertEqual(len(git.tr), 3) frame1, frame2, frame3, = git.tr @@ -583,6 +599,8 @@ def test_frame(self): self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals), '(x=11, y=14)') + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_previous_frame(self): args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back) self.assertEqual(args, ['a', 'b', 'c', 'd', 'e', 'f']) @@ -664,6 +682,8 @@ def test_getfunctions(self): @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_getdoc(self): self.assertEqual(inspect.getdoc(mod), 'A module docstring.') self.assertEqual(inspect.getdoc(mod.StupidGit), @@ -882,6 +902,8 @@ def test_getsource_on_generated_class(self): self.assertRaises(OSError, inspect.getsourcelines, A) self.assertIsNone(inspect.getcomments(A)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_getsource_on_class_without_firstlineno(self): __firstlineno__ = 1 class C: @@ -891,6 +913,8 @@ class C: class TestGetsourceStdlib(unittest.TestCase): # Test Python implementations of the stdlib modules + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_getsource_stdlib_collections_abc(self): import collections.abc lines, lineno = inspect.getsourcelines(collections.abc.Sequence) @@ -903,6 +927,8 @@ def test_getsource_stdlib_tomllib(self): self.assertRaises(OSError, inspect.getsource, tomllib.TOMLDecodeError) self.assertRaises(OSError, inspect.getsourcelines, tomllib.TOMLDecodeError) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_getsource_stdlib_abc(self): # Pure Python implementation abc = import_helper.import_fresh_module('abc', blocked=['_abc']) @@ -931,6 +957,8 @@ def test_getsource_stdlib_decimal(self): class TestGetsourceInteractive(unittest.TestCase): @support.force_not_colorized + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_getclasses_interactive(self): # bpo-44648: simulate a REPL session; # there is no `__file__` in the __main__ module @@ -954,6 +982,8 @@ def test_range_traceback_toplevel_frame(self): class TestDecorators(GetSourceBase): fodderModule = mod2 + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_wrapped_decorator(self): self.assertSourceEqual(mod2.wrapped, 14, 17) @@ -1154,6 +1184,8 @@ def test_nested_class_definition(self): self.assertSourceEqual(mod2.cls183, 183, 188) self.assertSourceEqual(mod2.cls183.cls185, 185, 188) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_class_decorator(self): self.assertSourceEqual(mod2.cls196, 194, 201) self.assertSourceEqual(mod2.cls196.cls200, 198, 201) @@ -1254,6 +1286,8 @@ def test_class(self): class TestComplexDecorator(GetSourceBase): fodderModule = mod2 + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_parens_in_decorator(self): self.assertSourceEqual(self.fodderModule.complex_decorated, 273, 275) @@ -1449,6 +1483,8 @@ def test_getfullargspec_definition_order_preserved_on_kwonly(self): l = list(signature.kwonlyargs) self.assertEqual(l, unsorted_keyword_only_parameters) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_classify_newstyle(self): class A(object): @@ -1530,6 +1566,8 @@ def m1(self): pass self.assertIn(('md', 'method', A), attrs, 'missing method descriptor') self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor') + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_classify_builtin_types(self): # Simple sanity check that all built-in types can have their # attributes classified. @@ -2090,6 +2128,8 @@ class DataDescriptorSub(DataDescriptorWithNoGet, self.assertFalse(inspect.ismethoddescriptor(MethodDescriptorSub)) self.assertFalse(inspect.ismethoddescriptor(DataDescriptorSub)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_builtin_descriptors(self): builtin_slot_wrapper = int.__add__ # This one is mentioned in docs. class Owner: @@ -2179,6 +2219,8 @@ class DataDescriptor2: self.assertTrue(inspect.isdatadescriptor(DataDescriptor2()), 'class with __set__ = None is a data descriptor') + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_slot(self): class Slotted: __slots__ = 'foo', @@ -2218,6 +2260,8 @@ def function(): _global_ref = object() class TestGetClosureVars(unittest.TestCase): + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_name_resolution(self): # Basic test of the 4 different resolution mechanisms def f(nonlocal_ref): @@ -2233,6 +2277,8 @@ def g(local_ref): builtin_vars, unbound_names) self.assertEqual(inspect.getclosurevars(f(_arg)), expected) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_generator_closure(self): def f(nonlocal_ref): def g(local_ref): @@ -2248,6 +2294,8 @@ def g(local_ref): builtin_vars, unbound_names) self.assertEqual(inspect.getclosurevars(f(_arg)), expected) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_method_closure(self): class C: def f(self, nonlocal_ref): @@ -2263,6 +2311,8 @@ def g(local_ref): builtin_vars, unbound_names) self.assertEqual(inspect.getclosurevars(C().f(_arg)), expected) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_attribute_same_name_as_global_var(self): class C: _global_ref = object() @@ -2332,18 +2382,24 @@ def _private_globals(self): exec(code, ns) return ns["f"], ns + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_builtins_fallback(self): f, ns = self._private_globals() ns.pop("__builtins__", None) expected = inspect.ClosureVars({}, {}, {"print":print}, {"path"}) self.assertEqual(inspect.getclosurevars(f), expected) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_builtins_as_dict(self): f, ns = self._private_globals() ns["__builtins__"] = {"path":1} expected = inspect.ClosureVars({}, {}, {"path":1}, {"print"}) self.assertEqual(inspect.getclosurevars(f), expected) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_builtins_as_module(self): f, ns = self._private_globals() ns["__builtins__"] = os @@ -2438,6 +2494,8 @@ def test_varkw_only(self): self.assertEqualCallArgs(f, '**collections.UserDict(a=1, b=2)') self.assertEqualCallArgs(f, 'c=3, **collections.UserDict(a=1, b=2)') + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_keyword_only(self): f = self.makeCallable('a=3, *, c, d=2') self.assertEqualCallArgs(f, 'c=3') @@ -2478,6 +2536,8 @@ def test_multiple_features(self): '(4,[5,6])]), q=0, **collections.UserDict(' 'y=9, z=10)') + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_errors(self): f0 = self.makeCallable('') f1 = self.makeCallable('a, b') @@ -2932,23 +2992,33 @@ def number_generator(): def _generatorstate(self): return inspect.getgeneratorstate(self.generator) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_created(self): self.assertEqual(self._generatorstate(), inspect.GEN_CREATED) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_suspended(self): next(self.generator) self.assertEqual(self._generatorstate(), inspect.GEN_SUSPENDED) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_closed_after_exhaustion(self): for i in self.generator: pass self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_closed_after_immediate_exception(self): with self.assertRaises(RuntimeError): self.generator.throw(RuntimeError) self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_closed_after_close(self): self.generator.close() self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) @@ -2977,6 +3047,8 @@ def test_easy_debugging(self): self.assertIn(name, repr(state)) self.assertIn(name, str(state)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_getgeneratorlocals(self): def each(lst, a=None): b=(1, 2, 3) @@ -3041,13 +3113,19 @@ def tearDown(self): def _coroutinestate(self): return inspect.getcoroutinestate(self.coroutine) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_created(self): self.assertEqual(self._coroutinestate(), inspect.CORO_CREATED) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_suspended(self): self.coroutine.send(None) self.assertEqual(self._coroutinestate(), inspect.CORO_SUSPENDED) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_closed_after_exhaustion(self): while True: try: @@ -3057,11 +3135,15 @@ def test_closed_after_exhaustion(self): self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_closed_after_immediate_exception(self): with self.assertRaises(RuntimeError): self.coroutine.throw(RuntimeError) self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_closed_after_close(self): self.coroutine.close() self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) @@ -3107,14 +3189,20 @@ async def asyncTearDown(self): def _asyncgenstate(self): return inspect.getasyncgenstate(self.asyncgen) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_created(self): self.assertEqual(self._asyncgenstate(), inspect.AGEN_CREATED) + # TODO: RUSTPYTHON + @unittest.expectedFailure async def test_suspended(self): value = await anext(self.asyncgen) self.assertEqual(self._asyncgenstate(), inspect.AGEN_SUSPENDED) self.assertEqual(value, 0) + # TODO: RUSTPYTHON + @unittest.expectedFailure async def test_closed_after_exhaustion(self): countdown = 7 with self.assertRaises(StopAsyncIteration): @@ -3123,11 +3211,15 @@ async def test_closed_after_exhaustion(self): self.assertEqual(countdown, 1) self.assertEqual(self._asyncgenstate(), inspect.AGEN_CLOSED) + # TODO: RUSTPYTHON + @unittest.expectedFailure async def test_closed_after_immediate_exception(self): with self.assertRaises(RuntimeError): await self.asyncgen.athrow(RuntimeError) self.assertEqual(self._asyncgenstate(), inspect.AGEN_CLOSED) + # TODO: RUSTPYTHON + @unittest.expectedFailure async def test_running(self): async def running_check_asyncgen(): for number in range(5): @@ -3150,6 +3242,8 @@ def test_easy_debugging(self): self.assertIn(name, repr(state)) self.assertIn(name, str(state)) + # TODO: RUSTPYTHON + @unittest.expectedFailure async def test_getasyncgenlocals(self): async def each(lst, a=None): b=(1, 2, 3) @@ -3682,6 +3776,8 @@ def m1d(*args, **kwargs): ('arg2', 1, ..., "positional_or_keyword")), int)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_signature_on_classmethod(self): if not support.MISSING_C_DOCSTRINGS: self.assertEqual(self.signature(classmethod), @@ -3705,6 +3801,8 @@ def foo(cls, arg1, *, arg2=1): ('arg2', 1, ..., "keyword_only")), ...)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_signature_on_staticmethod(self): if not support.MISSING_C_DOCSTRINGS: self.assertEqual(self.signature(staticmethod), @@ -4048,6 +4146,8 @@ def wrapped_foo_call(): ('b', ..., ..., "positional_or_keyword")), ...)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_signature_on_class(self): class C: def __init__(self, a): @@ -4306,6 +4406,8 @@ def __init__(self, b): self.assertEqual(self.signature(C.__call__, follow_wrapped=False), varargs_signature) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_signature_on_class_with_wrapped_init(self): class C: @identity_wrapper @@ -4405,6 +4507,8 @@ def __init__(self, a): self.assertEqual(self.signature(C.__new__, follow_wrapped=False), varargs_signature) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_signature_on_class_with_wrapped_new(self): with self.subTest('FunctionType'): class C: @@ -4493,6 +4597,8 @@ def __new__(cls, a): self.assertEqual(self.signature(C.__new__, follow_wrapped=False), varargs_signature) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_signature_on_class_with_init(self): class C: def __init__(self, b): @@ -4559,6 +4665,8 @@ def _init(self, x, a): ((('a', ..., ..., "positional_or_keyword"),), ...)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_signature_on_class_with_new(self): with self.subTest('FunctionType'): class C: @@ -4745,6 +4853,8 @@ class P4(P2, metaclass=MetaP): pass self.assertEqual(str(inspect.signature(P4)), '(foo, bar)') + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_signature_on_callable_objects(self): class Foo: def __call__(self, a): @@ -5215,6 +5325,8 @@ def test(): sig = test.__signature__ = inspect.Signature(parameters=(spam_param,)) self.assertEqual(sig, inspect.signature(test)) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_signature_on_mangled_parameters(self): class Spam: def foo(self, __p1:1=2, *, __p2:2=3): @@ -6035,6 +6147,8 @@ def _strip_non_python_syntax(self, input, self.assertEqual(computed_clean_signature, clean_signature) self.assertEqual(computed_self_parameter, self_parameter) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_signature_strip_non_python_syntax(self): self._strip_non_python_syntax( "($module, /, path, mode, *, dir_fd=None, " + @@ -6339,6 +6453,8 @@ def test_tokenize_module_has_signatures(self): import tokenize self._test_module_has_signatures(tokenize) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_tracemalloc_module_has_signatures(self): import tracemalloc self._test_module_has_signatures(tracemalloc) @@ -6366,6 +6482,8 @@ def test_weakref_module_has_signatures(self): no_signature = {'ReferenceType', 'ref'} self._test_module_has_signatures(weakref, no_signature) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_python_function_override_signature(self): def func(*args, **kwargs): pass @@ -6397,6 +6515,8 @@ def func(*args, **kwargs): inspect.signature(func) @support.requires_docstrings + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_base_class_have_text_signature(self): # see issue 43118 from test.typinganndata.ann_module7 import BufferedReader @@ -6560,6 +6680,8 @@ def assertInspectEqual(self, path, source): inspected_src.splitlines(True) ) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_getsource_reload(self): # see issue 1218234 with ready_to_import('reload_bug', self.src_before) as (name, path): @@ -6615,6 +6737,8 @@ def run_on_interactive_mode(self, source): return output @unittest.skipIf(not has_subprocess_support, "test requires subprocess") + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_getsource(self): output = self.run_on_interactive_mode(textwrap.dedent("""\ def f(): @@ -6632,4 +6756,4 @@ def f(): if __name__ == "__main__": - unittest.main() + unittest.main() \ No newline at end of file diff --git a/Lib/test/test_unittest/testmock/testhelpers.py b/Lib/test/test_unittest/testmock/testhelpers.py index 040e28517f2..5facac685fd 100644 --- a/Lib/test/test_unittest/testmock/testhelpers.py +++ b/Lib/test/test_unittest/testmock/testhelpers.py @@ -930,8 +930,6 @@ def check_data_descriptor(mock_attr): check_data_descriptor(foo.desc) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_autospec_on_bound_builtin_function(self): meth = types.MethodType(time.ctime, time.time()) self.assertIsInstance(meth(), str) diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index b8152b1b462..123d51b77ea 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -1886,8 +1886,6 @@ def d(): pass isinstance(cell.cell_contents, deprecated) for cell in d.__closure__ )) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_inspect(self): @deprecated("depr") def sync(): @@ -1911,8 +1909,6 @@ async def coro(self): self.assertFalse(inspect.iscoroutinefunction(Cls.sync)) self.assertTrue(inspect.iscoroutinefunction(Cls.coro)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_inspect_class_signature(self): class Cls1: # no __init__ or __new__ pass From c11a72a4f18e85c2c4e0da8982266950d58cc78b Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:50:54 +0900 Subject: [PATCH 495/819] impl more ast (#6419) --- crates/vm/src/stdlib/ast/pattern.rs | 103 ++++++++++++++++++---------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/crates/vm/src/stdlib/ast/pattern.rs b/crates/vm/src/stdlib/ast/pattern.rs index 9567ed38d41..d8128cb0622 100644 --- a/crates/vm/src/stdlib/ast/pattern.rs +++ b/crates/vm/src/stdlib/ast/pattern.rs @@ -199,16 +199,31 @@ impl Node for ruff::PatternMatchSingleton { } impl Node for ruff::Singleton { - fn ast_to_object(self, _vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { - todo!() + fn ast_to_object(self, vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { + match self { + ruff::Singleton::None => vm.ctx.none(), + ruff::Singleton::True => vm.ctx.new_bool(true).into(), + ruff::Singleton::False => vm.ctx.new_bool(false).into(), + } } fn ast_from_object( - _vm: &VirtualMachine, + vm: &VirtualMachine, _source_file: &SourceFile, - _object: PyObjectRef, + object: PyObjectRef, ) -> PyResult<Self> { - todo!() + if vm.is_none(&object) { + Ok(ruff::Singleton::None) + } else if object.is(&vm.ctx.true_value) { + Ok(ruff::Singleton::True) + } else if object.is(&vm.ctx.false_value) { + Ok(ruff::Singleton::False) + } else { + Err(vm.new_value_error(format!( + "Expected None, True, or False, got {:?}", + object.class().name() + ))) + } } } @@ -372,57 +387,51 @@ impl Node for ruff::PatternMatchClass { } } -struct PatternMatchClassPatterns { - pub _range: TextRange, // TODO: Use this -} +struct PatternMatchClassPatterns(Vec<ruff::Pattern>); impl Node for PatternMatchClassPatterns { - fn ast_to_object(self, _vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { - todo!() + fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { + self.0.ast_to_object(vm, source_file) } fn ast_from_object( - _vm: &VirtualMachine, - _source_file: &SourceFile, - _object: PyObjectRef, + vm: &VirtualMachine, + source_file: &SourceFile, + object: PyObjectRef, ) -> PyResult<Self> { - todo!() + Ok(Self(Node::ast_from_object(vm, source_file, object)?)) } } -struct PatternMatchClassKeywordAttributes { - pub _range: TextRange, // TODO: Use this -} +struct PatternMatchClassKeywordAttributes(Vec<ruff::Identifier>); impl Node for PatternMatchClassKeywordAttributes { - fn ast_to_object(self, _vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { - todo!() + fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { + self.0.ast_to_object(vm, source_file) } fn ast_from_object( - _vm: &VirtualMachine, - _source_file: &SourceFile, - _object: PyObjectRef, + vm: &VirtualMachine, + source_file: &SourceFile, + object: PyObjectRef, ) -> PyResult<Self> { - todo!() + Ok(Self(Node::ast_from_object(vm, source_file, object)?)) } } -struct PatternMatchClassKeywordPatterns { - pub _range: TextRange, // TODO: Use this -} +struct PatternMatchClassKeywordPatterns(Vec<ruff::Pattern>); impl Node for PatternMatchClassKeywordPatterns { - fn ast_to_object(self, _vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { - todo!() + fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { + self.0.ast_to_object(vm, source_file) } fn ast_from_object( - _vm: &VirtualMachine, - _source_file: &SourceFile, - _object: PyObjectRef, + vm: &VirtualMachine, + source_file: &SourceFile, + object: PyObjectRef, ) -> PyResult<Self> { - todo!() + Ok(Self(Node::ast_from_object(vm, source_file, object)?)) } } // constructor @@ -532,20 +541,38 @@ impl Node for ruff::PatternMatchOr { } fn split_pattern_match_class( - _arguments: ruff::PatternArguments, + arguments: ruff::PatternArguments, ) -> ( PatternMatchClassPatterns, PatternMatchClassKeywordAttributes, PatternMatchClassKeywordPatterns, ) { - todo!() + let patterns = PatternMatchClassPatterns(arguments.patterns); + let kwd_attrs = PatternMatchClassKeywordAttributes( + arguments.keywords.iter().map(|k| k.attr.clone()).collect(), + ); + let kwd_patterns = PatternMatchClassKeywordPatterns( + arguments.keywords.into_iter().map(|k| k.pattern).collect(), + ); + (patterns, kwd_attrs, kwd_patterns) } /// Merges the pattern match class attributes and patterns, opposite of [`split_pattern_match_class`]. fn merge_pattern_match_class( - _patterns: PatternMatchClassPatterns, - _kwd_attrs: PatternMatchClassKeywordAttributes, - _kwd_patterns: PatternMatchClassKeywordPatterns, + patterns: PatternMatchClassPatterns, + kwd_attrs: PatternMatchClassKeywordAttributes, + kwd_patterns: PatternMatchClassKeywordPatterns, ) -> (Vec<ruff::Pattern>, Vec<ruff::PatternKeyword>) { - todo!() + let keywords = kwd_attrs + .0 + .into_iter() + .zip(kwd_patterns.0) + .map(|(attr, pattern)| ruff::PatternKeyword { + range: Default::default(), + node_index: Default::default(), + attr, + pattern, + }) + .collect(); + (patterns.0, keywords) } From 6f8050526f1e7a3e4829c3bc493a61135dfd62e7 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:59:56 +0200 Subject: [PATCH 496/819] Ruff as part of PR auto-format (#6421) --- .github/workflows/ci.yaml | 1 + .github/workflows/pr-auto-commit.yaml | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c5ac2c87ae4..f1bc7fed709 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -322,6 +322,7 @@ jobs: uses: astral-sh/ruff-action@57714a7c8a2e59f32539362ba31877a1957dded1 # v3.5.1 with: version: "0.14.9" + args: "--version" - run: ruff check --diff diff --git a/.github/workflows/pr-auto-commit.yaml b/.github/workflows/pr-auto-commit.yaml index c01466dbcfd..0cbd2bfefb0 100644 --- a/.github/workflows/pr-auto-commit.yaml +++ b/.github/workflows/pr-auto-commit.yaml @@ -39,6 +39,15 @@ jobs: echo "Running cargo fmt --all on PR #${{ github.event.pull_request.number }}" cargo fmt --all + - name: Install ruff + uses: astral-sh/ruff-action@57714a7c8a2e59f32539362ba31877a1957dded1 # v3.5.1 + with: + version: "0.14.9" + args: "--version" + + - run: ruff format + - run: ruff check --select I --fix + - name: Configure git run: | git config user.name "github-actions[bot]" From 9ccf4c1872343a507bd34d1c5ce6bd36939cb35b Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:46:39 +0200 Subject: [PATCH 497/819] Ruff auto-format (#6422) --- crates/sre_engine/generate_tests.py | 4 ++-- crawl_sourcecode.py | 4 ++-- extra_tests/custom_text_test_runner.py | 13 ++++++------ extra_tests/jsontests.py | 4 ++-- extra_tests/snippets/builtin_all.py | 3 +-- extra_tests/snippets/builtin_any.py | 3 +-- extra_tests/snippets/builtin_bytearray.py | 3 ++- extra_tests/snippets/builtin_exceptions.py | 2 +- extra_tests/snippets/builtin_pow.py | 2 +- extra_tests/snippets/builtin_print.py | 3 ++- extra_tests/snippets/builtin_str.py | 2 +- extra_tests/snippets/dir_module/__init__.py | 2 +- .../snippets/forbidden_instantiation.py | 13 ++++++------ extra_tests/snippets/import.py | 11 +++++----- extra_tests/snippets/protocol_index_bad.py | 3 ++- extra_tests/snippets/stdlib_array.py | 3 ++- extra_tests/snippets/stdlib_binascii.py | 2 +- extra_tests/snippets/stdlib_collections.py | 1 - .../snippets/stdlib_collections_deque.py | 3 ++- extra_tests/snippets/stdlib_csv.py | 4 ++-- extra_tests/snippets/stdlib_ctypes.py | 18 +++++++++------- extra_tests/snippets/stdlib_datetime.py | 21 +++++++------------ extra_tests/snippets/stdlib_dir_module.py | 3 +-- extra_tests/snippets/stdlib_functools.py | 1 + extra_tests/snippets/stdlib_io.py | 3 ++- extra_tests/snippets/stdlib_itertools.py | 3 +-- extra_tests/snippets/stdlib_json.py | 5 +++-- extra_tests/snippets/stdlib_marshal.py | 2 +- extra_tests/snippets/stdlib_math.py | 1 + extra_tests/snippets/stdlib_os.py | 2 +- extra_tests/snippets/stdlib_pwd.py | 3 ++- extra_tests/snippets/stdlib_select.py | 6 +++--- extra_tests/snippets/stdlib_signal.py | 3 ++- extra_tests/snippets/stdlib_socket.py | 3 ++- extra_tests/snippets/stdlib_string.py | 1 - extra_tests/snippets/stdlib_struct.py | 3 ++- extra_tests/snippets/stdlib_subprocess.py | 4 ++-- extra_tests/snippets/stdlib_sys.py | 2 +- extra_tests/snippets/stdlib_weakref.py | 3 ++- extra_tests/snippets/stdlib_zlib.py | 1 + extra_tests/test_snippets.py | 10 ++++----- scripts/generate_checklist.py | 2 +- scripts/make_ssl_data_rs.py | 1 - wasm/demo/snippets/asyncbrowser.py | 2 +- wasm/demo/snippets/import_pypi.py | 3 +-- wasm/example/src/main.py | 2 +- wasm/tests/conftest.py | 15 ++++++------- whats_left.py | 13 ++++++------ 48 files changed, 113 insertions(+), 108 deletions(-) diff --git a/crates/sre_engine/generate_tests.py b/crates/sre_engine/generate_tests.py index 3af4d7e6a5b..542852a2496 100644 --- a/crates/sre_engine/generate_tests.py +++ b/crates/sre_engine/generate_tests.py @@ -1,8 +1,8 @@ +import json import os -from pathlib import Path import re -import json from itertools import chain +from pathlib import Path m = re.search(r"const SRE_MAGIC: usize = (\d+);", open("src/constants.rs").read()) sre_engine_magic = int(m.group(1)) diff --git a/crawl_sourcecode.py b/crawl_sourcecode.py index a96ec283faa..30e01d04450 100644 --- a/crawl_sourcecode.py +++ b/crawl_sourcecode.py @@ -9,9 +9,9 @@ """ import ast -import sys -import symtable import dis +import symtable +import sys filename = sys.argv[1] print("Crawling file:", filename) diff --git a/extra_tests/custom_text_test_runner.py b/extra_tests/custom_text_test_runner.py index 4c48e615c36..018121f0da4 100644 --- a/extra_tests/custom_text_test_runner.py +++ b/extra_tests/custom_text_test_runner.py @@ -25,16 +25,17 @@ # SOFTWARE. -import unittest -import os, sys, traceback import inspect import json -import time -import re import operator -from unittest.runner import result -from unittest.runner import registerResult +import os +import re +import sys +import time +import traceback +import unittest from functools import reduce +from unittest.runner import registerResult, result class TablePrinter(object): diff --git a/extra_tests/jsontests.py b/extra_tests/jsontests.py index a54fd4234e1..c1f92509fe7 100644 --- a/extra_tests/jsontests.py +++ b/extra_tests/jsontests.py @@ -1,8 +1,8 @@ +import os import unittest + from custom_text_test_runner import CustomTextTestRunner as Runner from test.libregrtest.runtest import findtests -import os - testnames = findtests() # idk why this fixes the hanging, if it does diff --git a/extra_tests/snippets/builtin_all.py b/extra_tests/snippets/builtin_all.py index cf0eea39321..ba11ce0de92 100644 --- a/extra_tests/snippets/builtin_all.py +++ b/extra_tests/snippets/builtin_all.py @@ -1,5 +1,4 @@ -from testutils import assert_raises -from testutils import TestFailingBool, TestFailingIter +from testutils import TestFailingBool, TestFailingIter, assert_raises assert all([True]) assert not all([False]) diff --git a/extra_tests/snippets/builtin_any.py b/extra_tests/snippets/builtin_any.py index 59b4514f85c..11b47d1f084 100644 --- a/extra_tests/snippets/builtin_any.py +++ b/extra_tests/snippets/builtin_any.py @@ -1,5 +1,4 @@ -from testutils import assert_raises -from testutils import TestFailingBool, TestFailingIter +from testutils import TestFailingBool, TestFailingIter, assert_raises assert any([True]) assert not any([False]) diff --git a/extra_tests/snippets/builtin_bytearray.py b/extra_tests/snippets/builtin_bytearray.py index 1a6993e205d..0b7e419390e 100644 --- a/extra_tests/snippets/builtin_bytearray.py +++ b/extra_tests/snippets/builtin_bytearray.py @@ -1,7 +1,8 @@ -from testutils import assert_raises import pickle import sys +from testutils import assert_raises + # new assert bytearray([1, 2, 3]) assert bytearray((1, 2, 3)) diff --git a/extra_tests/snippets/builtin_exceptions.py b/extra_tests/snippets/builtin_exceptions.py index 82aa54d6327..490f831f522 100644 --- a/extra_tests/snippets/builtin_exceptions.py +++ b/extra_tests/snippets/builtin_exceptions.py @@ -1,6 +1,6 @@ import builtins -import platform import pickle +import platform import sys diff --git a/extra_tests/snippets/builtin_pow.py b/extra_tests/snippets/builtin_pow.py index c769914231c..d38b57c37a0 100644 --- a/extra_tests/snippets/builtin_pow.py +++ b/extra_tests/snippets/builtin_pow.py @@ -1,4 +1,4 @@ -from testutils import assert_raises, assert_equal +from testutils import assert_equal, assert_raises assert pow(3, 2) == 9 assert pow(5, 3, 100) == 25 diff --git a/extra_tests/snippets/builtin_print.py b/extra_tests/snippets/builtin_print.py index b9f3b8cc177..2778cff65a6 100644 --- a/extra_tests/snippets/builtin_print.py +++ b/extra_tests/snippets/builtin_print.py @@ -1,6 +1,7 @@ -from testutils import assert_raises import io +from testutils import assert_raises + print(2 + 3) assert_raises(TypeError, print, "test", end=4, _msg="wrong type passed to end") diff --git a/extra_tests/snippets/builtin_str.py b/extra_tests/snippets/builtin_str.py index 1b9a7bde1a6..415156184c4 100644 --- a/extra_tests/snippets/builtin_str.py +++ b/extra_tests/snippets/builtin_str.py @@ -1,4 +1,4 @@ -from testutils import assert_raises, AssertRaises, skip_if_unsupported +from testutils import AssertRaises, assert_raises, skip_if_unsupported assert "".__eq__(1) == NotImplemented assert "a" == "a" diff --git a/extra_tests/snippets/dir_module/__init__.py b/extra_tests/snippets/dir_module/__init__.py index 5d9faff33d4..7408005f548 100644 --- a/extra_tests/snippets/dir_module/__init__.py +++ b/extra_tests/snippets/dir_module/__init__.py @@ -1,2 +1,2 @@ -from .relative import value from .dir_module_inner import value2 +from .relative import value diff --git a/extra_tests/snippets/forbidden_instantiation.py b/extra_tests/snippets/forbidden_instantiation.py index 904c5c6f085..50b6f58f07f 100644 --- a/extra_tests/snippets/forbidden_instantiation.py +++ b/extra_tests/snippets/forbidden_instantiation.py @@ -1,18 +1,19 @@ -from typing import Type from types import ( - GeneratorType, - CoroutineType, AsyncGeneratorType, BuiltinFunctionType, BuiltinMethodType, - WrapperDescriptorType, - MethodWrapperType, - MethodDescriptorType, ClassMethodDescriptorType, + CoroutineType, FrameType, + GeneratorType, GetSetDescriptorType, MemberDescriptorType, + MethodDescriptorType, + MethodWrapperType, + WrapperDescriptorType, ) +from typing import Type + from testutils import assert_raises diff --git a/extra_tests/snippets/import.py b/extra_tests/snippets/import.py index 466e1315acb..435e622418e 100644 --- a/extra_tests/snippets/import.py +++ b/extra_tests/snippets/import.py @@ -1,9 +1,10 @@ -import import_target, import_target as aliased -from import_target import func, other_func -from import_target import func as aliased_func, other_func as aliased_other_func -from import_star import * - import import_mutual1 +import import_target +import import_target as aliased +from import_star import * +from import_target import func, other_func +from import_target import func as aliased_func +from import_target import other_func as aliased_other_func assert import_target.X == import_target.func() assert import_target.X == func() diff --git a/extra_tests/snippets/protocol_index_bad.py b/extra_tests/snippets/protocol_index_bad.py index d4ac003c857..51d90426023 100644 --- a/extra_tests/snippets/protocol_index_bad.py +++ b/extra_tests/snippets/protocol_index_bad.py @@ -1,9 +1,10 @@ """Test that indexing ops don't hang when an object with a mutating __index__ is used.""" -from testutils import assert_raises from array import array +from testutils import assert_raises + class BadIndex: def __index__(self): diff --git a/extra_tests/snippets/stdlib_array.py b/extra_tests/snippets/stdlib_array.py index a31b2f8e422..34eac949168 100644 --- a/extra_tests/snippets/stdlib_array.py +++ b/extra_tests/snippets/stdlib_array.py @@ -1,7 +1,8 @@ -from testutils import assert_raises from array import array from pickle import dumps, loads +from testutils import assert_raises + a1 = array("b", [0, 1, 2, 3]) assert a1.tobytes() == b"\x00\x01\x02\x03" diff --git a/extra_tests/snippets/stdlib_binascii.py b/extra_tests/snippets/stdlib_binascii.py index 5dcb5a03227..c4c2121fa46 100644 --- a/extra_tests/snippets/stdlib_binascii.py +++ b/extra_tests/snippets/stdlib_binascii.py @@ -1,6 +1,6 @@ import binascii -from testutils import assert_raises, assert_equal +from testutils import assert_equal, assert_raises # hexlify tests h = binascii.hexlify diff --git a/extra_tests/snippets/stdlib_collections.py b/extra_tests/snippets/stdlib_collections.py index 8fd7cb6a887..8a9bdb45336 100644 --- a/extra_tests/snippets/stdlib_collections.py +++ b/extra_tests/snippets/stdlib_collections.py @@ -1,6 +1,5 @@ from collections import deque - d = deque([0, 1, 2]) d.append(1) diff --git a/extra_tests/snippets/stdlib_collections_deque.py b/extra_tests/snippets/stdlib_collections_deque.py index 86e566f4182..25b2069f6e8 100644 --- a/extra_tests/snippets/stdlib_collections_deque.py +++ b/extra_tests/snippets/stdlib_collections_deque.py @@ -1,7 +1,8 @@ -from testutils import assert_raises from collections import deque from typing import Deque +from testutils import assert_raises + def test_deque_iterator__new__(): klass = type(iter(deque())) diff --git a/extra_tests/snippets/stdlib_csv.py b/extra_tests/snippets/stdlib_csv.py index f762c58010e..eb3461e9082 100644 --- a/extra_tests/snippets/stdlib_csv.py +++ b/extra_tests/snippets/stdlib_csv.py @@ -1,7 +1,7 @@ -from testutils import assert_raises - import csv +from testutils import assert_raises + for row in csv.reader(["one,two,three"]): [one, two, three] = row assert one == "one" diff --git a/extra_tests/snippets/stdlib_ctypes.py b/extra_tests/snippets/stdlib_ctypes.py index b4bc05dcb4c..0ec7568a839 100644 --- a/extra_tests/snippets/stdlib_ctypes.py +++ b/extra_tests/snippets/stdlib_ctypes.py @@ -1,14 +1,10 @@ -import os as _os, sys as _sys +import os as _os +import sys as _sys import types as _types - -from _ctypes import RTLD_LOCAL, RTLD_GLOBAL -from _ctypes import sizeof -from _ctypes import _SimpleCData, Array +from _ctypes import RTLD_GLOBAL, RTLD_LOCAL, Array, _SimpleCData, sizeof from _ctypes import CFuncPtr as _CFuncPtr - from struct import calcsize as _calcsize - assert Array.__class__.__name__ == "PyCArrayType" assert Array.__base__.__name__ == "_CData" @@ -24,8 +20,14 @@ from _ctypes import ( FUNCFLAG_CDECL as _FUNCFLAG_CDECL, +) +from _ctypes import ( FUNCFLAG_PYTHONAPI as _FUNCFLAG_PYTHONAPI, +) +from _ctypes import ( FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, +) +from _ctypes import ( FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR, ) @@ -210,8 +212,8 @@ class c_bool(_SimpleCData): assert abs(f.value - 3.14) < 1e-06 if _os.name == "nt": - from _ctypes import LoadLibrary as _dlopen from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL + from _ctypes import LoadLibrary as _dlopen elif _os.name == "posix": from _ctypes import dlopen as _dlopen diff --git a/extra_tests/snippets/stdlib_datetime.py b/extra_tests/snippets/stdlib_datetime.py index 60e80494011..57ffd3a4413 100644 --- a/extra_tests/snippets/stdlib_datetime.py +++ b/extra_tests/snippets/stdlib_datetime.py @@ -4,27 +4,20 @@ """ # import copy -import sys -import random - -from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod - import datetime as datetime_module -from datetime import MINYEAR, MAXYEAR -from datetime import timedelta -from datetime import tzinfo -from datetime import time -from datetime import timezone -from datetime import date, datetime +import random +import sys import time as _time +from datetime import MAXYEAR, MINYEAR, date, datetime, time, timedelta, timezone, tzinfo +from operator import eq, floordiv, ge, gt, le, lt, mod, ne, truediv from testutils import ( - assert_raises, assert_equal, - assert_true, assert_false, - assert_isinstance, assert_in, + assert_isinstance, + assert_raises, + assert_true, ) # An arbitrary collection of objects of non-datetime types, for testing diff --git a/extra_tests/snippets/stdlib_dir_module.py b/extra_tests/snippets/stdlib_dir_module.py index a8ab233f37b..db00055c7cc 100644 --- a/extra_tests/snippets/stdlib_dir_module.py +++ b/extra_tests/snippets/stdlib_dir_module.py @@ -1,6 +1,5 @@ -from testutils import assert_equal - import dir_module +from testutils import assert_equal assert dir_module.value == 5 assert dir_module.value2 == 7 diff --git a/extra_tests/snippets/stdlib_functools.py b/extra_tests/snippets/stdlib_functools.py index 3d323bfbad0..d3e2495e8a2 100644 --- a/extra_tests/snippets/stdlib_functools.py +++ b/extra_tests/snippets/stdlib_functools.py @@ -1,4 +1,5 @@ from functools import reduce + from testutils import assert_raises diff --git a/extra_tests/snippets/stdlib_io.py b/extra_tests/snippets/stdlib_io.py index 011bb051c27..722886d34ee 100644 --- a/extra_tests/snippets/stdlib_io.py +++ b/extra_tests/snippets/stdlib_io.py @@ -1,5 +1,6 @@ -from io import BufferedReader, FileIO, StringIO, BytesIO import os +from io import BufferedReader, BytesIO, FileIO, StringIO + from testutils import assert_raises fi = FileIO("README.md") diff --git a/extra_tests/snippets/stdlib_itertools.py b/extra_tests/snippets/stdlib_itertools.py index 31d8adbfd12..4d2e9f6e1f7 100644 --- a/extra_tests/snippets/stdlib_itertools.py +++ b/extra_tests/snippets/stdlib_itertools.py @@ -1,9 +1,8 @@ import itertools +import pickle from testutils import assert_raises -import pickle - # itertools.chain tests chain = itertools.chain diff --git a/extra_tests/snippets/stdlib_json.py b/extra_tests/snippets/stdlib_json.py index ca35c2c2b24..758cd129cd0 100644 --- a/extra_tests/snippets/stdlib_json.py +++ b/extra_tests/snippets/stdlib_json.py @@ -1,6 +1,7 @@ -from testutils import assert_raises import json -from io import StringIO, BytesIO +from io import BytesIO, StringIO + +from testutils import assert_raises def round_trip_test(obj): diff --git a/extra_tests/snippets/stdlib_marshal.py b/extra_tests/snippets/stdlib_marshal.py index c5fb1e533e2..db843ff65d5 100644 --- a/extra_tests/snippets/stdlib_marshal.py +++ b/extra_tests/snippets/stdlib_marshal.py @@ -1,5 +1,5 @@ -import unittest import marshal +import unittest class MarshalTests(unittest.TestCase): diff --git a/extra_tests/snippets/stdlib_math.py b/extra_tests/snippets/stdlib_math.py index 090de710edf..a6bb0099c05 100644 --- a/extra_tests/snippets/stdlib_math.py +++ b/extra_tests/snippets/stdlib_math.py @@ -1,4 +1,5 @@ import math + from testutils import assert_raises, skip_if_unsupported NAN = float("nan") diff --git a/extra_tests/snippets/stdlib_os.py b/extra_tests/snippets/stdlib_os.py index f5d26030a4d..a538365f707 100644 --- a/extra_tests/snippets/stdlib_os.py +++ b/extra_tests/snippets/stdlib_os.py @@ -1,7 +1,7 @@ import os -import time import stat import sys +import time from testutils import assert_raises diff --git a/extra_tests/snippets/stdlib_pwd.py b/extra_tests/snippets/stdlib_pwd.py index 2a44aed32c7..c3aeb7c8703 100644 --- a/extra_tests/snippets/stdlib_pwd.py +++ b/extra_tests/snippets/stdlib_pwd.py @@ -4,9 +4,10 @@ if sys.platform.startswith("win"): exit(0) -from testutils import assert_raises import pwd +from testutils import assert_raises + with assert_raises(KeyError): fake_name = "fake_user" while pwd.getpwnam(fake_name): diff --git a/extra_tests/snippets/stdlib_select.py b/extra_tests/snippets/stdlib_select.py index fe246db99b9..d27bb82b1c3 100644 --- a/extra_tests/snippets/stdlib_select.py +++ b/extra_tests/snippets/stdlib_select.py @@ -1,8 +1,8 @@ -from testutils import assert_raises - import select -import sys import socket +import sys + +from testutils import assert_raises class Nope: diff --git a/extra_tests/snippets/stdlib_signal.py b/extra_tests/snippets/stdlib_signal.py index 0abfd7cb715..9d0b13bbd67 100644 --- a/extra_tests/snippets/stdlib_signal.py +++ b/extra_tests/snippets/stdlib_signal.py @@ -1,6 +1,7 @@ import signal -import time import sys +import time + from testutils import assert_raises assert_raises(TypeError, lambda: signal.signal(signal.SIGINT, 2)) diff --git a/extra_tests/snippets/stdlib_socket.py b/extra_tests/snippets/stdlib_socket.py index 199ff9fe47a..b49fdcf08c2 100644 --- a/extra_tests/snippets/stdlib_socket.py +++ b/extra_tests/snippets/stdlib_socket.py @@ -1,6 +1,7 @@ import _socket -import socket import os +import socket + from testutils import assert_raises assert _socket.socket == _socket.SocketType diff --git a/extra_tests/snippets/stdlib_string.py b/extra_tests/snippets/stdlib_string.py index ae544f3289e..02bfc95c2db 100644 --- a/extra_tests/snippets/stdlib_string.py +++ b/extra_tests/snippets/stdlib_string.py @@ -1,6 +1,5 @@ import string - assert string.ascii_letters == "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" assert string.ascii_lowercase == "abcdefghijklmnopqrstuvwxyz" assert string.ascii_uppercase == "ABCDEFGHIJKLMNOPQRSTUVWXYZ" diff --git a/extra_tests/snippets/stdlib_struct.py b/extra_tests/snippets/stdlib_struct.py index 1e08d0a223f..3f97f2519cc 100644 --- a/extra_tests/snippets/stdlib_struct.py +++ b/extra_tests/snippets/stdlib_struct.py @@ -1,6 +1,7 @@ -from testutils import assert_raises import struct +from testutils import assert_raises + data = struct.pack("IH", 14, 12) assert data == bytes([14, 0, 0, 0, 12, 0]) diff --git a/extra_tests/snippets/stdlib_subprocess.py b/extra_tests/snippets/stdlib_subprocess.py index a2aa026f4c2..014011ea145 100644 --- a/extra_tests/snippets/stdlib_subprocess.py +++ b/extra_tests/snippets/stdlib_subprocess.py @@ -1,7 +1,7 @@ +import signal import subprocess -import time import sys -import signal +import time from testutils import assert_raises diff --git a/extra_tests/snippets/stdlib_sys.py b/extra_tests/snippets/stdlib_sys.py index a3e9367c526..155fc905a73 100644 --- a/extra_tests/snippets/stdlib_sys.py +++ b/extra_tests/snippets/stdlib_sys.py @@ -1,6 +1,6 @@ -import sys import os import subprocess +import sys from testutils import assert_raises diff --git a/extra_tests/snippets/stdlib_weakref.py b/extra_tests/snippets/stdlib_weakref.py index 059ddd00009..17762addca7 100644 --- a/extra_tests/snippets/stdlib_weakref.py +++ b/extra_tests/snippets/stdlib_weakref.py @@ -1,4 +1,5 @@ -from _weakref import ref, proxy +from _weakref import proxy, ref + from testutils import assert_raises diff --git a/extra_tests/snippets/stdlib_zlib.py b/extra_tests/snippets/stdlib_zlib.py index 308a8d23bfb..ac2b525b0a7 100644 --- a/extra_tests/snippets/stdlib_zlib.py +++ b/extra_tests/snippets/stdlib_zlib.py @@ -1,4 +1,5 @@ import zlib + from testutils import assert_raises # checksum functions diff --git a/extra_tests/test_snippets.py b/extra_tests/test_snippets.py index 5ff944c7726..d58b86f66b2 100644 --- a/extra_tests/test_snippets.py +++ b/extra_tests/test_snippets.py @@ -2,14 +2,14 @@ # in the snippets folder. -import sys -import os -import unittest +import contextlib +import enum import glob import logging +import os import subprocess -import contextlib -import enum +import sys +import unittest from pathlib import Path diff --git a/scripts/generate_checklist.py b/scripts/generate_checklist.py index 9a444b16e24..759e4bd2a24 100644 --- a/scripts/generate_checklist.py +++ b/scripts/generate_checklist.py @@ -7,8 +7,8 @@ import dataclasses import difflib import pathlib -from typing import Optional import warnings +from typing import Optional import requests from jinja2 import Environment, FileSystemLoader diff --git a/scripts/make_ssl_data_rs.py b/scripts/make_ssl_data_rs.py index b6891416a07..a089fe6ee0c 100755 --- a/scripts/make_ssl_data_rs.py +++ b/scripts/make_ssl_data_rs.py @@ -19,7 +19,6 @@ import re import sys - parser = argparse.ArgumentParser( description="Generate ssl_data.rs from OpenSSL sources" ) diff --git a/wasm/demo/snippets/asyncbrowser.py b/wasm/demo/snippets/asyncbrowser.py index d3a9dca85f5..979c1d389d0 100644 --- a/wasm/demo/snippets/asyncbrowser.py +++ b/wasm/demo/snippets/asyncbrowser.py @@ -1,5 +1,5 @@ -import browser import asyncweb +import browser async def main(delay): diff --git a/wasm/demo/snippets/import_pypi.py b/wasm/demo/snippets/import_pypi.py index e3325d56f4c..547fe2365e6 100644 --- a/wasm/demo/snippets/import_pypi.py +++ b/wasm/demo/snippets/import_pypi.py @@ -2,7 +2,6 @@ # available as os.path as well as a few other utilities, but will raise an # OSError for anything that actually requires an OS import _dummy_os - import asyncweb import whlimport @@ -13,8 +12,8 @@ async def main(): await whlimport.load_package("pygments") import pygments - import pygments.lexers import pygments.formatters.html + import pygments.lexers lexer = pygments.lexers.get_lexer_by_name("python") fmter = pygments.formatters.html.HtmlFormatter(noclasses=True, style="default") diff --git a/wasm/example/src/main.py b/wasm/example/src/main.py index b5a1bda7c07..855b1e2ce49 100644 --- a/wasm/example/src/main.py +++ b/wasm/example/src/main.py @@ -1,4 +1,4 @@ -from browser import fetch, alert +from browser import alert, fetch def fetch_handler(repos): diff --git a/wasm/tests/conftest.py b/wasm/tests/conftest.py index e01f5eddc5f..c7555c9958d 100644 --- a/wasm/tests/conftest.py +++ b/wasm/tests/conftest.py @@ -1,10 +1,11 @@ -import subprocess +import atexit import os -import time import socket -import atexit -import pytest +import subprocess import sys +import time + +import pytest PORT = 8080 @@ -65,11 +66,11 @@ def wait_for_port(port, host="localhost", timeout=5.0): from selenium import webdriver -from selenium.webdriver.firefox.options import Options +from selenium.common.exceptions import JavascriptException from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.firefox.options import Options from selenium.webdriver.support import expected_conditions as EC -from selenium.common.exceptions import JavascriptException +from selenium.webdriver.support.ui import WebDriverWait class Driver(webdriver.Firefox): diff --git a/whats_left.py b/whats_left.py index c79bfb146a8..91e46bef7ef 100755 --- a/whats_left.py +++ b/whats_left.py @@ -17,15 +17,14 @@ # We then run this second generated script with RustPython. import argparse -import re +import inspect +import json import os +import platform import re +import subprocess import sys -import json import warnings -import inspect -import subprocess -import platform from pydoc import ModuleScanner if not sys.flags.isolated: @@ -327,13 +326,13 @@ def gen_modules(): def compare(): import inspect import io + import json import os + import platform import re import sys import warnings from contextlib import redirect_stdout - import json - import platform def method_incompatibility_reason(typ, method_name, real_method_value): has_method = hasattr(typ, method_name) From f49d20df22a7ed4a91eb3ef3a30365c36e3e0450 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:47:16 +0200 Subject: [PATCH 498/819] Update `test_setcomps.py` from 3.13.11 (#6425) --- Lib/test/test_setcomps.py | 65 +++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_setcomps.py b/Lib/test/test_setcomps.py index ecc4fffec0d..e8c0c33e980 100644 --- a/Lib/test/test_setcomps.py +++ b/Lib/test/test_setcomps.py @@ -1,3 +1,10 @@ +import doctest +import traceback +import unittest + +from test.support import BrokenIter + + doctests = """ ########### Tests mostly copied from test_listcomps.py ############ @@ -144,24 +151,50 @@ """ +class SetComprehensionTest(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'FrameSummary' object has no attribute 'end_lineno' + def test_exception_locations(self): + # The location of an exception raised from __init__ or + # __next__ should should be the iterator expression + + def init_raises(): + try: + {x for x in BrokenIter(init_raises=True)} + except Exception as e: + return e + + def next_raises(): + try: + {x for x in BrokenIter(next_raises=True)} + except Exception as e: + return e + + def iter_raises(): + try: + {x for x in BrokenIter(iter_raises=True)} + except Exception as e: + return e + + for func, expected in [(init_raises, "BrokenIter(init_raises=True)"), + (next_raises, "BrokenIter(next_raises=True)"), + (iter_raises, "BrokenIter(iter_raises=True)"), + ]: + with self.subTest(func): + exc = func() + f = traceback.extract_tb(exc.__traceback__)[0] + indent = 16 + co = func.__code__ + self.assertEqual(f.lineno, co.co_firstlineno + 2) + self.assertEqual(f.end_lineno, co.co_firstlineno + 2) + self.assertEqual(f.line[f.colno - indent : f.end_colno - indent], + expected) __test__ = {'doctests' : doctests} -def test_main(verbose=None): - import sys - from test import support - from test import test_setcomps - support.run_doctest(test_setcomps, verbose) - - # verify reference counting - if verbose and hasattr(sys, "gettotalrefcount"): - import gc - counts = [None] * 5 - for i in range(len(counts)): - support.run_doctest(test_setcomps, verbose) - gc.collect() - counts[i] = sys.gettotalrefcount() - print(counts) +def load_tests(loader, tests, pattern): + tests.addTest(doctest.DocTestSuite()) + return tests + if __name__ == "__main__": - test_main(verbose=True) + unittest.main() From e227956a58f0f072f8be177264e0fdc1b9280e8a Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:02:02 +0200 Subject: [PATCH 499/819] Update `test_generators.py` from 3.13.11 (#6426) --- Lib/test/test_generators.py | 479 ++++++++++++++++++++++++++++++++---- 1 file changed, 429 insertions(+), 50 deletions(-) diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index 5ed0986150a..a27b483e3e7 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -2,9 +2,11 @@ import gc import pickle import sys +import doctest import unittest import weakref import inspect +import types from test import support @@ -46,8 +48,7 @@ def test_raise_and_yield_from(self): class FinalizationTest(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_frame_resurrect(self): # A generator frame can be resurrected by a generator's finalization. def gen(): @@ -67,8 +68,7 @@ def gen(): del frame support.gc_collect() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_refcycle(self): # A generator caught in a refcycle gets finalized anyway. old_garbage = gc.garbage[:] @@ -92,9 +92,12 @@ def gen(): self.assertEqual(gc.garbage, old_garbage) def test_lambda_generator(self): - # Issue #23192: Test that a lambda returning a generator behaves + # bpo-23192, gh-119897: Test that a lambda returning a generator behaves # like the equivalent function f = lambda: (yield 1) + self.assertIsInstance(f(), types.GeneratorType) + self.assertEqual(next(f()), 1) + def g(): return (yield 1) # test 'yield from' @@ -114,8 +117,7 @@ def g3(): return (yield from f()) class GeneratorTest(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_name(self): def func(): yield 1 @@ -175,6 +177,153 @@ def f(): g.send(0) self.assertEqual(next(g), 1) + @unittest.expectedFailure # TODO: RUSTPYTHON; NotImplementedError + def test_handle_frame_object_in_creation(self): + + #Attempt to expose partially constructed frames + #See https://github.com/python/cpython/issues/94262 + + def cb(*args): + inspect.stack() + + def gen(): + yield 1 + + thresholds = gc.get_threshold() + + gc.callbacks.append(cb) + gc.set_threshold(1, 0, 0) + try: + gen() + finally: + gc.set_threshold(*thresholds) + gc.callbacks.pop() + + class Sneaky: + def __del__(self): + inspect.stack() + + sneaky = Sneaky() + sneaky._s = Sneaky() + sneaky._s._s = sneaky + + gc.set_threshold(1, 0, 0) + try: + del sneaky + gen() + finally: + gc.set_threshold(*thresholds) + + def test_ag_frame_f_back(self): + async def f(): + yield + ag = f() + self.assertIsNone(ag.ag_frame.f_back) + + def test_cr_frame_f_back(self): + async def f(): + pass + cr = f() + self.assertIsNone(cr.cr_frame.f_back) + cr.close() # Suppress RuntimeWarning. + + def test_gi_frame_f_back(self): + def f(): + yield + gi = f() + self.assertIsNone(gi.gi_frame.f_back) + + def test_issue103488(self): + + def gen_raises(): + yield + raise ValueError() + + def loop(): + try: + for _ in gen_raises(): + if True is False: + return + except ValueError: + pass + + #This should not raise + loop() + + +class ModifyUnderlyingIterableTest(unittest.TestCase): + iterables = [ + range(0), + range(20), + [1, 2, 3], + (2,), + {13, 48, 211}, + frozenset((15, 8, 6)), + {1: 2, 3: 4}, + ] + + non_iterables = [ + None, + 42, + 3.0, + 2j, + ] + + def genexpr(self): + return (x for x in range(10)) + + def genfunc(self): + def gen(it): + for x in it: + yield x + return gen(range(10)) + + def process_tests(self, get_generator): + for obj in self.iterables: + g_obj = get_generator(obj) + with self.subTest(g_obj=g_obj, obj=obj): + self.assertListEqual(list(g_obj), list(obj)) + + g_iter = get_generator(iter(obj)) + with self.subTest(g_iter=g_iter, obj=obj): + self.assertListEqual(list(g_iter), list(obj)) + + err_regex = "'.*' object is not iterable" + for obj in self.non_iterables: + g_obj = get_generator(obj) + with self.subTest(g_obj=g_obj): + self.assertRaisesRegex(TypeError, err_regex, list, g_obj) + + @unittest.expectedFailure # AssertionError: TypeError not raised by list + def test_modify_f_locals(self): + def modify_f_locals(g, local, obj): + g.gi_frame.f_locals[local] = obj + return g + + def get_generator_genexpr(obj): + return modify_f_locals(self.genexpr(), '.0', obj) + + def get_generator_genfunc(obj): + return modify_f_locals(self.genfunc(), 'it', obj) + + self.process_tests(get_generator_genexpr) + self.process_tests(get_generator_genfunc) + + @unittest.expectedFailure # AssertionError: "'.*' object is not iterable" does not match "'complex' object is not an iterator" + def test_new_gen_from_gi_code(self): + def new_gen_from_gi_code(g, obj): + generator_func = types.FunctionType(g.gi_code, {}) + return generator_func(obj) + + def get_generator_genexpr(obj): + return new_gen_from_gi_code(self.genexpr(), obj) + + def get_generator_genfunc(obj): + return new_gen_from_gi_code(self.genfunc(), obj) + + self.process_tests(get_generator_genexpr) + self.process_tests(get_generator_genfunc) + class ExceptionTest(unittest.TestCase): # Tests for the issue #23353: check that the currently handled exception @@ -183,16 +332,16 @@ class ExceptionTest(unittest.TestCase): def test_except_throw(self): def store_raise_exc_generator(): try: - self.assertEqual(sys.exc_info()[0], None) + self.assertIsNone(sys.exception()) yield except Exception as exc: # exception raised by gen.throw(exc) - self.assertEqual(sys.exc_info()[0], ValueError) + self.assertIsInstance(sys.exception(), ValueError) self.assertIsNone(exc.__context__) yield # ensure that the exception is not lost - self.assertEqual(sys.exc_info()[0], ValueError) + self.assertIsInstance(sys.exception(), ValueError) yield # we should be able to raise back the ValueError @@ -214,11 +363,11 @@ def store_raise_exc_generator(): next(make) self.assertIsNone(cm.exception.__context__) - self.assertEqual(sys.exc_info(), (None, None, None)) + self.assertIsNone(sys.exception()) def test_except_next(self): def gen(): - self.assertEqual(sys.exc_info()[0], ValueError) + self.assertIsInstance(sys.exception(), ValueError) yield "done" g = gen() @@ -226,23 +375,23 @@ def gen(): raise ValueError except Exception: self.assertEqual(next(g), "done") - self.assertEqual(sys.exc_info(), (None, None, None)) + self.assertIsNone(sys.exception()) def test_except_gen_except(self): def gen(): try: - self.assertEqual(sys.exc_info()[0], None) + self.assertIsNone(sys.exception()) yield # we are called from "except ValueError:", TypeError must # inherit ValueError in its context raise TypeError() except TypeError as exc: - self.assertEqual(sys.exc_info()[0], TypeError) + self.assertIsInstance(sys.exception(), TypeError) self.assertEqual(type(exc.__context__), ValueError) # here we are still called from the "except ValueError:" - self.assertEqual(sys.exc_info()[0], ValueError) + self.assertIsInstance(sys.exception(), ValueError) yield - self.assertIsNone(sys.exc_info()[0]) + self.assertIsNone(sys.exception()) yield "done" g = gen() @@ -253,25 +402,45 @@ def gen(): next(g) self.assertEqual(next(g), "done") - self.assertEqual(sys.exc_info(), (None, None, None)) + self.assertIsNone(sys.exception()) + + def test_nested_gen_except_loop(self): + def gen(): + for i in range(100): + self.assertIsInstance(sys.exception(), TypeError) + yield "doing" + + def outer(): + try: + raise TypeError + except: + for x in gen(): + yield x + + try: + raise ValueError + except Exception: + for x in outer(): + self.assertEqual(x, "doing") + self.assertEqual(sys.exception(), None) def test_except_throw_exception_context(self): def gen(): try: try: - self.assertEqual(sys.exc_info()[0], None) + self.assertIsNone(sys.exception()) yield except ValueError: # we are called from "except ValueError:" - self.assertEqual(sys.exc_info()[0], ValueError) + self.assertIsInstance(sys.exception(), ValueError) raise TypeError() except Exception as exc: - self.assertEqual(sys.exc_info()[0], TypeError) + self.assertIsInstance(sys.exception(), TypeError) self.assertEqual(type(exc.__context__), ValueError) # we are still called from "except ValueError:" - self.assertEqual(sys.exc_info()[0], ValueError) + self.assertIsInstance(sys.exception(), ValueError) yield - self.assertIsNone(sys.exc_info()[0]) + self.assertIsNone(sys.exception()) yield "done" g = gen() @@ -282,10 +451,9 @@ def gen(): g.throw(exc) self.assertEqual(next(g), "done") - self.assertEqual(sys.exc_info(), (None, None, None)) + self.assertIsNone(sys.exception()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_except_throw_bad_exception(self): class E(Exception): def __new__(cls, *args, **kwargs): @@ -312,6 +480,16 @@ def generator(): with self.assertRaises(StopIteration): gen.throw(E) + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: DeprecationWarning not triggered + def test_gen_3_arg_deprecation_warning(self): + def g(): + yield 42 + + gen = g() + with self.assertWarns(DeprecationWarning): + with self.assertRaises(TypeError): + gen.throw(TypeError, TypeError(24), None) + def test_stopiteration_error(self): # See also PEP 479. @@ -358,10 +536,198 @@ def g(): self.assertEqual(cm.exception.value.value, 2) +class GeneratorCloseTest(unittest.TestCase): + + def test_close_no_return_value(self): + def f(): + yield + + gen = f() + gen.send(None) + self.assertIsNone(gen.close()) + + @unittest.expectedFailure # AssertionError: None != 0 + def test_close_return_value(self): + def f(): + try: + yield + # close() raises GeneratorExit here, which is caught + except GeneratorExit: + return 0 + + gen = f() + gen.send(None) + self.assertEqual(gen.close(), 0) + + def test_close_not_catching_exit(self): + def f(): + yield + # close() raises GeneratorExit here, which isn't caught and + # therefore propagates -- no return value + return 0 + + gen = f() + gen.send(None) + self.assertIsNone(gen.close()) + + def test_close_not_started(self): + def f(): + try: + yield + except GeneratorExit: + return 0 + + gen = f() + self.assertIsNone(gen.close()) + + def test_close_exhausted(self): + def f(): + try: + yield + except GeneratorExit: + return 0 + + gen = f() + next(gen) + with self.assertRaises(StopIteration): + next(gen) + self.assertIsNone(gen.close()) + + @unittest.expectedFailure # AssertionError: None != 0 + def test_close_closed(self): + def f(): + try: + yield + except GeneratorExit: + return 0 + + gen = f() + gen.send(None) + self.assertEqual(gen.close(), 0) + self.assertIsNone(gen.close()) + + def test_close_raises(self): + def f(): + try: + yield + except GeneratorExit: + pass + raise RuntimeError + + gen = f() + gen.send(None) + with self.assertRaises(RuntimeError): + gen.close() + + @unittest.expectedFailure # AssertionError: <test.test_generators.GeneratorCloseTest.test_close_releases_frame_locals.<locals>.Foo object at 0xb400007e3c212160> is not None + def test_close_releases_frame_locals(self): + # See gh-118272 + + class Foo: + pass + + f = Foo() + f_wr = weakref.ref(f) + + def genfn(): + a = f + yield + + g = genfn() + next(g) + del f + g.close() + support.gc_collect() + self.assertIsNone(f_wr()) + + +# See https://github.com/python/cpython/issues/125723 +class GeneratorDeallocTest(unittest.TestCase): + def test_frame_outlives_generator(self): + def g1(): + a = 42 + yield sys._getframe() + + def g2(): + a = 42 + yield + + def g3(obj): + a = 42 + obj.frame = sys._getframe() + yield + + class ObjectWithFrame(): + def __init__(self): + self.frame = None + + def get_frame(index): + if index == 1: + return next(g1()) + elif index == 2: + gen = g2() + next(gen) + return gen.gi_frame + elif index == 3: + obj = ObjectWithFrame() + next(g3(obj)) + return obj.frame + else: + return None + + for index in (1, 2, 3): + with self.subTest(index=index): + frame = get_frame(index) + frame_locals = frame.f_locals + self.assertIn('a', frame_locals) + self.assertEqual(frame_locals['a'], 42) + + @unittest.expectedFailure # AssertionError: 'a' not found in {'frame_locals1': None} + def test_frame_locals_outlive_generator(self): + frame_locals1 = None + + def g1(): + nonlocal frame_locals1 + frame_locals1 = sys._getframe().f_locals + a = 42 + yield + + def g2(): + a = 42 + yield sys._getframe().f_locals + + def get_frame_locals(index): + if index == 1: + nonlocal frame_locals1 + next(g1()) + return frame_locals1 + if index == 2: + return next(g2()) + else: + return None + + for index in (1, 2): + with self.subTest(index=index): + frame_locals = get_frame_locals(index) + self.assertIn('a', frame_locals) + self.assertEqual(frame_locals['a'], 42) + + def test_frame_locals_outlive_generator_with_exec(self): + def g(): + a = 42 + yield locals(), sys._getframe().f_locals + + locals_ = {'g': g} + for i in range(10): + exec("snapshot, live_locals = next(g())", locals=locals_) + for l in (locals_['snapshot'], locals_['live_locals']): + self.assertIn('a', l) + self.assertEqual(l['a'], 42) + + class GeneratorThrowTest(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_exception_context_with_yield(self): def f(): try: @@ -398,8 +764,7 @@ def f(): # This ensures that the assertions inside were executed. self.assertEqual(actual, 'b') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_exception_context_with_yield_from(self): def f(): yield @@ -511,8 +876,7 @@ def call_throw(gen): class YieldFromTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_generator_gi_yieldfrom(self): def a(): self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_RUNNING) @@ -910,7 +1274,7 @@ def b(): >>> type(i) <class 'generator'> >>> [s for s in dir(i) if not s.startswith('_')] -['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw'] +['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw'] >>> from test.support import HAVE_DOCSTRINGS >>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).') Implement next(self). @@ -2054,11 +2418,20 @@ def printsolution(self, x): ... SyntaxError: 'yield' outside function -# Pegen does not produce this error message yet -# >>> def f(): x = yield = y -# Traceback (most recent call last): -# ... -# SyntaxError: assignment to yield expression not possible +>>> f=lambda: (yield from (1,2)), (yield from (3,4)) +Traceback (most recent call last): + ... +SyntaxError: 'yield from' outside function + +>>> yield from [1,2] +Traceback (most recent call last): + ... +SyntaxError: 'yield from' outside function + +>>> def f(): x = yield = y +Traceback (most recent call last): + ... +SyntaxError: assignment to yield expression not possible >>> def f(): (yield bar) = y Traceback (most recent call last): @@ -2089,6 +2462,13 @@ def printsolution(self, x): >>> g.throw(ValueError("xyz")) # value only caught ValueError (xyz) +>>> import warnings +>>> old_filters = warnings.filters.copy() +>>> warnings.filterwarnings("ignore", category=DeprecationWarning) + +# Filter DeprecationWarning: regarding the (type, val, tb) signature of throw(). +# Deprecation warnings are re-enabled below. + >>> g.throw(ValueError, ValueError(1)) # value+matching type caught ValueError (1) @@ -2157,6 +2537,11 @@ def printsolution(self, x): ... ValueError: 7 +>>> warnings.filters[:] = old_filters + +# Re-enable DeprecationWarning: the (type, val, tb) exception representation is deprecated, +# and may be removed in a future version of Python. + Plain "raise" inside a generator should preserve the traceback (#13188). The traceback should have 3 levels: - g.throw() @@ -2393,16 +2778,10 @@ def printsolution(self, x): "refleaks": refleaks_tests, } -# Magic test name that regrtest.py invokes *after* importing this module. -# This worms around a bootstrap problem. -# Note that doctest and regrtest both look in sys.argv for a "-v" argument, -# so this works as expected in both ways of running regrtest. -def test_main(verbose=None): - from test import support, test_generators - support.run_unittest(__name__) - # TODO: RUSTPYTHON - # support.run_doctest(test_generators, verbose) - -# This part isn't needed for regrtest, but for running the test directly. +def load_tests(loader, tests, pattern): + # tests.addTest(doctest.DocTestSuite()) # TODO: RUSTPYTHON + return tests + + if __name__ == "__main__": - test_main(1) + unittest.main() From 30dd5bedc6ac8bcc34e6dd7371fd7a75c0def795 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 13 Dec 2025 09:24:17 +0900 Subject: [PATCH 500/819] use posix error messages under 127 (#6420) * use posix error messages under 127 * use posix error message for unix * unmark successful tests --- Lib/test/test_argparse.py | 2 -- Lib/test/test_py_compile.py | 1 - Lib/test/test_subprocess.py | 6 ------ crates/vm/src/stdlib/io.rs | 33 ++++++++++++++++++++++++++++++++- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index dc2df795a7d..b7e995334fe 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -6747,8 +6747,6 @@ def test_ambiguous_option(self): "ambiguous option: --foob=1 could match --foobaz, --fooble$", self.parser.parse_args, ['--foob=1', '--foogle', '2']) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_os_error(self): self.parser.add_argument('file') self.assertRaisesRegex(argparse.ArgumentError, diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py index 6d10099c614..54302eba4df 100644 --- a/Lib/test/test_py_compile.py +++ b/Lib/test/test_py_compile.py @@ -266,7 +266,6 @@ def test_bad_syntax_with_quiet(self): self.assertEqual(stdout, b'') self.assertEqual(stderr, b'') - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_file_not_exists(self): should_not_exists = os.path.join(os.path.dirname(__file__), 'should_not_exists.py') rc, stdout, stderr = self.pycompilecmd_failure(self.source_path, should_not_exists) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index bf0099554b5..6b20a5c00a5 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1878,8 +1878,6 @@ def _get_chdir_exception(self): self._nonexistent_dir) return desired_exception - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_cwd(self): """Test error in the child raised in the parent for a bad cwd.""" desired_exception = self._get_chdir_exception() @@ -1895,8 +1893,6 @@ def test_exception_cwd(self): else: self.fail("Expected OSError: %s" % desired_exception) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_bad_executable(self): """Test error in the child raised in the parent for a bad executable.""" desired_exception = self._get_chdir_exception() @@ -1912,8 +1908,6 @@ def test_exception_bad_executable(self): else: self.fail("Expected OSError: %s" % desired_exception) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_bad_args_0(self): """Test error in the child raised in the parent for a bad args[0].""" desired_exception = self._get_chdir_exception() diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index c54f3b853dc..e5a438b86de 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -20,10 +20,41 @@ pub use _io::{OpenArgs, io_open as open}; impl ToPyException for std::io::Error { fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef { let errno = self.posix_errno(); + #[cfg(windows)] + let msg = 'msg: { + // On Windows, use C runtime's strerror for POSIX errno values + // For Windows-specific error codes, fall back to FormatMessage + + // UCRT's strerror returns "Unknown error" for invalid errno values + // Windows UCRT defines errno values 1-42 plus some more up to ~127 + const MAX_POSIX_ERRNO: i32 = 127; + if errno > 0 && errno <= MAX_POSIX_ERRNO { + let ptr = unsafe { libc::strerror(errno) }; + if !ptr.is_null() { + let s = unsafe { std::ffi::CStr::from_ptr(ptr) }.to_string_lossy(); + if !s.starts_with("Unknown error") { + break 'msg s.into_owned(); + } + } + } + self.to_string() + }; + #[cfg(unix)] + let msg = { + let ptr = unsafe { libc::strerror(errno) }; + if !ptr.is_null() { + unsafe { std::ffi::CStr::from_ptr(ptr) } + .to_string_lossy() + .into_owned() + } else { + self.to_string() + } + }; + #[cfg(not(any(windows, unix)))] let msg = self.to_string(); + #[allow(clippy::let_and_return)] let exc = vm.new_errno_error(errno, msg); - #[cfg(windows)] { use crate::object::AsObject; From 901324f21e9b9bc64b780d529cc835846b0af1b7 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 13 Dec 2025 04:08:47 +0200 Subject: [PATCH 501/819] Update `test_raise.py` from 3.13.11 (#6423) --- Lib/test/test_raise.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py index d7f01509078..53fce0a501d 100644 --- a/Lib/test/test_raise.py +++ b/Lib/test/test_raise.py @@ -187,18 +187,14 @@ def test_class_cause(self): @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: 'classmethod' object is not callable def test_class_cause_nonexception_result(self): - class ConstructsNone(BaseException): - @classmethod + # See https://github.com/python/cpython/issues/140530. + class ConstructMortal(BaseException): def __new__(*args, **kwargs): - return None - try: - raise IndexError from ConstructsNone - except TypeError as e: - self.assertIn("should have returned an instance of BaseException", str(e)) - except IndexError: - self.fail("Wrong kind of exception raised") - else: - self.fail("No exception raised") + return ["mortal value"] + + msg = ".*should have returned an instance of BaseException.*" + with self.assertRaisesRegex(TypeError, msg): + raise IndexError from ConstructMortal def test_instance_cause(self): cause = KeyError() From 1e3d4fe2183bc79b8143e2059613de29fe0f9b61 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 13 Dec 2025 04:09:11 +0200 Subject: [PATCH 502/819] Update `test_xmlrpc.py` from 3.13.11 (#6430) --- Lib/test/test_xmlrpc.py | 183 ++++++++++++++++++---------------------- 1 file changed, 80 insertions(+), 103 deletions(-) diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py index 25a4be0e039..8684e042bee 100644 --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@ -25,6 +25,8 @@ except ImportError: gzip = None +support.requires_working_socket(module=True) + alist = [{'astring': 'foo@bar.baz.spam', 'afloat': 7283.43, 'anint': 2**20, @@ -45,15 +47,13 @@ class XMLRPCTestCase(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_load(self): dump = xmlrpclib.dumps((alist,)) load = xmlrpclib.loads(dump) self.assertEqual(alist, load[0][0]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_bare_datetime(self): # This checks that an unwrapped datetime.date object can be handled # by the marshalling code. This can't be done via test_dump_load() @@ -88,8 +88,7 @@ def test_dump_bare_datetime(self): self.assertIsNone(m) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_datetime_before_1900(self): # same as before but with a date before 1900 dt = datetime.datetime(1, 2, 10, 11, 41, 23) @@ -108,8 +107,7 @@ def test_datetime_before_1900(self): self.assertIs(type(newdt), xmlrpclib.DateTime) self.assertIsNone(m) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_1164912 (self): d = xmlrpclib.DateTime() ((new_d,), dummy) = xmlrpclib.loads(xmlrpclib.dumps((d,), @@ -120,8 +118,7 @@ def test_bug_1164912 (self): s = xmlrpclib.dumps((new_d,), methodresponse=True) self.assertIsInstance(s, str) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_newstyle_class(self): class T(object): pass @@ -187,8 +184,7 @@ def dummy_write(s): m.dump_double(xmlrpclib.MAXINT + 42, dummy_write) m.dump_double(xmlrpclib.MININT - 42, dummy_write) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_none(self): value = alist + [None] arg1 = (alist + [None],) @@ -197,8 +193,7 @@ def test_dump_none(self): xmlrpclib.loads(strg)[0][0]) self.assertRaises(TypeError, xmlrpclib.dumps, (arg1,)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_encoding(self): value = {'key\u20ac\xa4': 'value\u20ac\xa4'} @@ -220,8 +215,7 @@ def test_dump_encoding(self): self.assertEqual(xmlrpclib.loads(strg)[0][0], value) self.assertEqual(xmlrpclib.loads(strg)[1], methodname) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_bytes(self): sample = b"my dog has fleas" self.assertEqual(sample, xmlrpclib.Binary(sample)) @@ -241,8 +235,7 @@ def test_dump_bytes(self): self.assertIs(type(newvalue), xmlrpclib.Binary) self.assertIsNone(m) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_loads_unsupported(self): ResponseError = xmlrpclib.ResponseError data = '<params><param><value><spam/></value></param></params>' @@ -265,8 +258,7 @@ def check_loads(self, s, value, **kwargs): self.assertIs(type(newvalue), type(value)) self.assertIsNone(m) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_load_standard_types(self): check = self.check_loads check('string', 'string') @@ -294,8 +286,7 @@ def test_load_standard_types(self): '<member><name>a</name><value><int>1</int></value></member>' '</struct>', {'a': 1, 'b': 2}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_load_extension_types(self): check = self.check_loads check('<nil/>', None) @@ -309,6 +300,17 @@ def test_load_extension_types(self): check('<bigdecimal>9876543210.0123456789</bigdecimal>', decimal.Decimal('9876543210.0123456789')) + @unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'expat' is not defined + def test_limit_int(self): + check = self.check_loads + maxdigits = 5000 + with support.adjust_int_max_str_digits(maxdigits): + s = '1' * (maxdigits + 1) + with self.assertRaises(ValueError): + check(f'<int>{s}</int>', None) + with self.assertRaises(ValueError): + check(f'<biginteger>{s}</biginteger>', None) + def test_get_host_info(self): # see bug #3613, this raised a TypeError transp = xmlrpc.client.Transport() @@ -330,8 +332,7 @@ def test_ssl_presence(self): except OSError: self.assertTrue(has_ssl) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_keepalive_disconnect(self): class RequestHandler(http.server.BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" @@ -471,8 +472,7 @@ def test_repr(self): self.assertEqual(repr(f), "<Fault 42: 'Test Fault'>") self.assertEqual(repr(f), str(f)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_fault(self): f = xmlrpclib.Fault(42, 'Test Fault') s = xmlrpclib.dumps((f,)) @@ -518,10 +518,16 @@ def test_time_struct(self): self.assertEqual(str(t), time.strftime("%Y%m%dT%H:%M:%S", d)) def test_datetime_datetime(self): + # naive (no tzinfo) d = datetime.datetime(2007,1,2,3,4,5) t = xmlrpclib.DateTime(d) self.assertEqual(str(t), '20070102T03:04:05') + # aware (with tzinfo): the timezone is ignored + d = datetime.datetime(2023, 6, 12, 13, 30, tzinfo=datetime.UTC) + t = xmlrpclib.DateTime(d) + self.assertEqual(str(t), '20230612T13:30:00') + def test_repr(self): d = datetime.datetime(2007,1,2,3,4,5) t = xmlrpclib.DateTime(d) @@ -814,8 +820,7 @@ def tearDown(self): xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = False class SimpleServerTestCase(BaseServerTestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_simple1(self): try: p = xmlrpclib.ServerProxy(URL) @@ -826,8 +831,7 @@ def test_simple1(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_nonascii(self): start_string = 'P\N{LATIN SMALL LETTER Y WITH CIRCUMFLEX}t' end_string = 'h\N{LATIN SMALL LETTER O WITH HORN}n' @@ -841,8 +845,7 @@ def test_nonascii(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_client_encoding(self): start_string = '\u20ac' end_string = '\xa4' @@ -857,8 +860,7 @@ def test_client_encoding(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_nonascii_methodname(self): try: p = xmlrpclib.ServerProxy(URL, encoding='ascii') @@ -879,8 +881,7 @@ def test_404(self): self.assertEqual(response.status, 404) self.assertEqual(response.reason, 'Not Found') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_introspection1(self): expected_methods = set(['pow', 'div', 'my_function', 'add', 'têšt', 'system.listMethods', 'system.methodHelp', @@ -897,8 +898,7 @@ def test_introspection1(self): self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_introspection2(self): try: # test _methodHelp() @@ -911,8 +911,7 @@ def test_introspection2(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @make_request_and_skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_introspection3(self): @@ -927,8 +926,7 @@ def test_introspection3(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_introspection4(self): # the SimpleXMLRPCServer doesn't support signatures, but # at least check that we can try making the call @@ -942,8 +940,7 @@ def test_introspection4(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_multicall(self): try: p = xmlrpclib.ServerProxy(URL) @@ -961,8 +958,7 @@ def test_multicall(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_non_existing_multicall(self): try: p = xmlrpclib.ServerProxy(URL) @@ -984,8 +980,7 @@ def test_non_existing_multicall(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dotted_attribute(self): # Raises an AttributeError because private methods are not allowed. self.assertRaises(AttributeError, @@ -996,16 +991,14 @@ def test_dotted_attribute(self): # This avoids waiting for the socket timeout. self.test_simple1() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_allow_dotted_names_true(self): # XXX also need allow_dotted_names_false test. server = xmlrpclib.ServerProxy("http://%s:%d/RPC2" % (ADDR, PORT)) data = server.Fixture.getData() self.assertEqual(data, '42') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unicode_host(self): server = xmlrpclib.ServerProxy("http://%s:%d/RPC2" % (ADDR, PORT)) self.assertEqual(server.add("a", "\xe9"), "a\xe9") @@ -1020,8 +1013,7 @@ def test_partial_post(self): 'Accept-Encoding: identity\r\n' 'Content-Length: 0\r\n\r\n'.encode('ascii')) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_context_manager(self): with xmlrpclib.ServerProxy(URL) as server: server.add(2, 3) @@ -1030,8 +1022,7 @@ def test_context_manager(self): self.assertEqual(server('transport')._connection, (None, None)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_context_manager_method_error(self): try: with xmlrpclib.ServerProxy(URL) as server: @@ -1047,8 +1038,7 @@ class SimpleServerEncodingTestCase(BaseServerTestCase): def threadFunc(evt, numrequests, requestHandler=None, encoding=None): http_server(evt, numrequests, requestHandler, 'iso-8859-15') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_server_encoding(self): start_string = '\u20ac' end_string = '\xa4' @@ -1067,70 +1057,68 @@ def test_server_encoding(self): class MultiPathServerTestCase(BaseServerTestCase): threadFunc = staticmethod(http_multi_server) request_count = 2 - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_path1(self): p = xmlrpclib.ServerProxy(URL+"/foo") self.assertEqual(p.pow(6,8), 6**8) self.assertRaises(xmlrpclib.Fault, p.add, 6, 8) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_path2(self): p = xmlrpclib.ServerProxy(URL+"/foo/bar") self.assertEqual(p.add(6,8), 6+8) self.assertRaises(xmlrpclib.Fault, p.pow, 6, 8) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.requires_resource('walltime') def test_path3(self): p = xmlrpclib.ServerProxy(URL+"/is/broken") self.assertRaises(xmlrpclib.Fault, p.add, 6, 8) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.requires_resource('walltime') def test_invalid_path(self): p = xmlrpclib.ServerProxy(URL+"/invalid") self.assertRaises(xmlrpclib.Fault, p.add, 6, 8) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.requires_resource('walltime') def test_path_query_fragment(self): p = xmlrpclib.ServerProxy(URL+"/foo?k=v#frag") self.assertEqual(p.test(), "/foo?k=v#frag") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.requires_resource('walltime') def test_path_fragment(self): p = xmlrpclib.ServerProxy(URL+"/foo#frag") self.assertEqual(p.test(), "/foo#frag") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.requires_resource('walltime') def test_path_query(self): p = xmlrpclib.ServerProxy(URL+"/foo?k=v") self.assertEqual(p.test(), "/foo?k=v") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.requires_resource('walltime') def test_empty_path(self): p = xmlrpclib.ServerProxy(URL) self.assertEqual(p.test(), "/RPC2") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.requires_resource('walltime') def test_root_path(self): p = xmlrpclib.ServerProxy(URL + "/") self.assertEqual(p.test(), "/") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.requires_resource('walltime') def test_empty_path_query(self): p = xmlrpclib.ServerProxy(URL + "?k=v") self.assertEqual(p.test(), "?k=v") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.requires_resource('walltime') def test_empty_path_fragment(self): p = xmlrpclib.ServerProxy(URL + "#frag") self.assertEqual(p.test(), "#frag") @@ -1163,8 +1151,7 @@ def setUp(self): #A test case that verifies that a server using the HTTP/1.1 keep-alive mechanism #does indeed serve subsequent requests on the same connection class KeepaliveServerTestCase1(BaseKeepaliveServerTestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_two(self): p = xmlrpclib.ServerProxy(URL) #do three requests. @@ -1181,9 +1168,9 @@ def test_two(self): self.assertGreaterEqual(len(self.RequestHandler.myRequests[-1]), 2) -@unittest.skip("TODO: RUSTPYTHON, appears to hang") #test special attribute access on the serverproxy, through the __call__ #function. +@unittest.skip("TODO: RUSTPYTHON, appears to hang") class KeepaliveServerTestCase2(BaseKeepaliveServerTestCase): #ask for two keepalive requests to be handled. request_count=2 @@ -1248,8 +1235,7 @@ def send_content(self, connection, body): def setUp(self): BaseServerTestCase.setUp(self) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_gzip_request(self): t = self.Transport() t.encode_threshold = None @@ -1273,8 +1259,7 @@ def test_bad_gzip_request(self): p.pow(6, 8) p("close")() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_gzip_response(self): t = self.Transport() p = xmlrpclib.ServerProxy(URL, transport=t) @@ -1333,8 +1318,7 @@ def assertContainsAdditionalHeaders(self, headers, additional): for key, value in additional.items(): self.assertEqual(headers.get(key), value) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_header(self): p = xmlrpclib.ServerProxy(URL, headers=[('X-Test', 'foo')]) self.assertEqual(p.pow(6, 8), 6**8) @@ -1342,8 +1326,7 @@ def test_header(self): headers = self.RequestHandler.test_headers self.assertContainsAdditionalHeaders(headers, {'X-Test': 'foo'}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_header_many(self): p = xmlrpclib.ServerProxy( URL, headers=[('X-Test', 'foo'), ('X-Test-Second', 'bar')]) @@ -1353,8 +1336,7 @@ def test_header_many(self): self.assertContainsAdditionalHeaders( headers, {'X-Test': 'foo', 'X-Test-Second': 'bar'}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_header_empty(self): p = xmlrpclib.ServerProxy(URL, headers=[]) self.assertEqual(p.pow(6, 8), 6**8) @@ -1362,8 +1344,7 @@ def test_header_empty(self): headers = self.RequestHandler.test_headers self.assertContainsAdditionalHeaders(headers, {}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_header_tuple(self): p = xmlrpclib.ServerProxy(URL, headers=(('X-Test', 'foo'),)) self.assertEqual(p.pow(6, 8), 6**8) @@ -1371,8 +1352,7 @@ def test_header_tuple(self): headers = self.RequestHandler.test_headers self.assertContainsAdditionalHeaders(headers, {'X-Test': 'foo'}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_header_items(self): p = xmlrpclib.ServerProxy(URL, headers={'X-Test': 'foo'}.items()) self.assertEqual(p.pow(6, 8), 6**8) @@ -1431,8 +1411,7 @@ def tearDown(self): default_class = http.client.HTTPMessage xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = default_class - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_basic(self): # check that flag is false by default flagval = xmlrpc.server.SimpleXMLRPCServer._send_traceback_header @@ -1527,8 +1506,7 @@ def test_cgi_get(self): self.assertEqual(message, 'Bad Request') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cgi_xmlrpc_response(self): data = """<?xml version='1.0'?> <methodCall> @@ -1574,8 +1552,7 @@ def test_cgi_xmlrpc_response(self): class UseBuiltinTypesTestCase(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_use_builtin_types(self): # SimpleXMLRPCDispatcher.__init__ accepts use_builtin_types, which # makes all dispatch of binary data as bytes instances, and all From 835918afa14e0145295e1e32094310dd7626c484 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 13 Dec 2025 04:09:26 +0200 Subject: [PATCH 503/819] Set timeout only on test step (#6431) --- .github/workflows/ci.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f1bc7fed709..8539db1a27e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -237,7 +237,6 @@ jobs: RUST_BACKTRACE: full name: Run snippets and cpython tests runs-on: ${{ matrix.os }} - timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 35 }} strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] @@ -272,20 +271,29 @@ jobs: - name: run snippets run: python -m pip install -r requirements.txt && pytest -v working-directory: ./extra_tests + - if: runner.os == 'Linux' name: run cpython platform-independent tests run: target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v ${{ env.PLATFORM_INDEPENDENT_TESTS }} + timeout-minutes: 35 + - if: runner.os == '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 }} + timeout-minutes: 35 + - if: runner.os == 'macOS' name: run cpython platform-dependent tests (MacOS) run: target/release/rustpython -m test -j 1 --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} + timeout-minutes: 35 + - if: runner.os == 'Windows' name: run cpython platform-dependent tests (windows partial - fixme) run: target/release/rustpython -m test -j 1 --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} ${{ env.WINDOWS_SKIPS }} + timeout-minutes: 45 + - if: runner.os != 'Windows' name: check that --install-pip succeeds run: | From dbacb0724621cf1a0a72f1765d0978b73c1660cc Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 13 Dec 2025 15:06:41 +0200 Subject: [PATCH 504/819] Update `test_urllibnet.py` from 3.13.11 (#6432) --- Lib/test/test_urllibnet.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_urllibnet.py b/Lib/test/test_urllibnet.py index 773101ce41f..6733fe9c6ea 100644 --- a/Lib/test/test_urllibnet.py +++ b/Lib/test/test_urllibnet.py @@ -2,6 +2,7 @@ from test import support from test.support import os_helper from test.support import socket_helper +from test.support.testcase import ExtraAssertions import contextlib import socket @@ -34,7 +35,7 @@ def testURLread(self): f.read() -class urlopenNetworkTests(unittest.TestCase): +class urlopenNetworkTests(unittest.TestCase, ExtraAssertions): """Tests urllib.request.urlopen using the network. These tests are not exhaustive. Assuming that testing using files does a @@ -70,8 +71,7 @@ def test_basic(self): with self.urlopen(self.url) as open_url: for attr in ("read", "readline", "readlines", "fileno", "close", "info", "geturl"): - self.assertTrue(hasattr(open_url, attr), "object returned from " - "urlopen lacks the %s attribute" % attr) + self.assertHasAttr(open_url, attr) self.assertTrue(open_url.read(), "calling 'read' failed") def test_readlines(self): @@ -109,6 +109,7 @@ def test_getcode(self): open_url.close() self.assertEqual(code, 404) + @support.requires_resource('walltime') def test_bad_address(self): # Make sure proper exception is raised when connecting to a bogus # address. @@ -191,6 +192,7 @@ def test_header(self): logo = "http://www.pythontest.net/" + @support.requires_resource('walltime') def test_data_header(self): with self.urlretrieve(self.logo) as (file_location, fileheaders): datevalue = fileheaders.get('Date') From db71554dbc168a3ff8169254dff430158b9ae5c1 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 13 Dec 2025 15:06:55 +0200 Subject: [PATCH 505/819] Update `test_urllib2net.py` from 3.13.11 (#6434) --- Lib/test/test_urllib2net.py | 78 ++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_urllib2net.py b/Lib/test/test_urllib2net.py index a7e7c9f0b91..41f170a6ad5 100644 --- a/Lib/test/test_urllib2net.py +++ b/Lib/test/test_urllib2net.py @@ -1,10 +1,14 @@ +import contextlib import errno +import sysconfig import unittest +from unittest import mock from test import support from test.support import os_helper from test.support import socket_helper from test.support import ResourceDenied from test.test_urllib2 import sanepathname2url +from test.support.warnings_helper import check_no_resource_warning import os import socket @@ -29,13 +33,6 @@ def wrapped(*args, **kwargs): return _retry_thrice(func, exc, *args, **kwargs) return wrapped -# bpo-35411: FTP tests of test_urllib2net randomly fail -# with "425 Security: Bad IP connecting" on Travis CI -skip_ftp_test_on_travis = unittest.skipIf('TRAVIS' in os.environ, - 'bpo-35411: skip FTP test ' - 'on Travis CI') - - # Connecting to remote hosts is flaky. Make it more robust by retrying # the connection several times. _urlopen_with_retry = _wrap_with_retry_thrice(urllib.request.urlopen, @@ -140,15 +137,54 @@ def setUp(self): # XXX The rest of these tests aren't very good -- they don't check much. # They do sometimes catch some major disasters, though. - @skip_ftp_test_on_travis + @support.requires_resource('walltime') def test_ftp(self): + # Testing the same URL twice exercises the caching in CacheFTPHandler urls = [ + 'ftp://www.pythontest.net/README', 'ftp://www.pythontest.net/README', ('ftp://www.pythontest.net/non-existent-file', None, urllib.error.URLError), ] self._test_urls(urls, self._extra_handlers()) + @support.requires_resource('walltime') + @unittest.skipIf(sysconfig.get_platform() == 'linux-ppc64le', + 'leaks on PPC64LE (gh-140691)') + def test_ftp_no_leak(self): + # gh-140691: When the data connection (but not control connection) + # cannot be made established, we shouldn't leave an open socket object. + + class MockError(OSError): + pass + + orig_create_connection = socket.create_connection + def patched_create_connection(address, *args, **kwargs): + """Simulate REJECTing connections to ports other than 21""" + host, port = address + if port != 21: + raise MockError() + return orig_create_connection(address, *args, **kwargs) + + url = 'ftp://www.pythontest.net/README' + entry = url, None, urllib.error.URLError + no_cache_handlers = [urllib.request.FTPHandler()] + cache_handlers = self._extra_handlers() + with mock.patch('socket.create_connection', patched_create_connection): + with check_no_resource_warning(self): + # Try without CacheFTPHandler + self._test_urls([entry], handlers=no_cache_handlers, + retry=False) + with check_no_resource_warning(self): + # Try with CacheFTPHandler (uncached) + self._test_urls([entry], cache_handlers, retry=False) + with check_no_resource_warning(self): + # Try with CacheFTPHandler (cached) + self._test_urls([entry], cache_handlers, retry=False) + # Try without the mock: the handler should not use a closed connection + with check_no_resource_warning(self): + self._test_urls([url], cache_handlers, retry=False) + def test_file(self): TESTFN = os_helper.TESTFN f = open(TESTFN, 'w') @@ -202,6 +238,7 @@ def test_urlwithfrag(self): self.assertEqual(res.geturl(), "http://www.pythontest.net/index.html#frag") + @support.requires_resource('walltime') def test_redirect_url_withfrag(self): redirect_url_with_frag = "http://www.pythontest.net/redir/with_frag/" with socket_helper.transient_internet(redirect_url_with_frag): @@ -260,18 +297,16 @@ def _test_urls(self, urls, handlers, retry=True): else: req = expected_err = None + if expected_err: + context = self.assertRaises(expected_err) + else: + context = contextlib.nullcontext() + with socket_helper.transient_internet(url): - try: + f = None + with context: f = urlopen(url, req, support.INTERNET_TIMEOUT) - # urllib.error.URLError is a subclass of OSError - except OSError as err: - if expected_err: - msg = ("Didn't get expected error(s) %s for %s %s, got %s: %s" % - (expected_err, url, req, type(err), err)) - self.assertIsInstance(err, expected_err, msg) - else: - raise - else: + if f is not None: try: with time_out, \ socket_peer_reset, \ @@ -340,7 +375,7 @@ def test_http_timeout(self): FTP_HOST = 'ftp://www.pythontest.net/' - @skip_ftp_test_on_travis + @support.requires_resource('walltime') def test_ftp_basic(self): self.assertIsNone(socket.getdefaulttimeout()) with socket_helper.transient_internet(self.FTP_HOST, timeout=None): @@ -348,7 +383,6 @@ def test_ftp_basic(self): self.addCleanup(u.close) self.assertIsNone(u.fp.fp.raw._sock.gettimeout()) - @skip_ftp_test_on_travis def test_ftp_default_timeout(self): self.assertIsNone(socket.getdefaulttimeout()) with socket_helper.transient_internet(self.FTP_HOST): @@ -360,7 +394,7 @@ def test_ftp_default_timeout(self): socket.setdefaulttimeout(None) self.assertEqual(u.fp.fp.raw._sock.gettimeout(), 60) - @skip_ftp_test_on_travis + @support.requires_resource('walltime') def test_ftp_no_timeout(self): self.assertIsNone(socket.getdefaulttimeout()) with socket_helper.transient_internet(self.FTP_HOST): @@ -372,7 +406,7 @@ def test_ftp_no_timeout(self): socket.setdefaulttimeout(None) self.assertIsNone(u.fp.fp.raw._sock.gettimeout()) - @skip_ftp_test_on_travis + @support.requires_resource('walltime') def test_ftp_timeout(self): with socket_helper.transient_internet(self.FTP_HOST): u = _urlopen_with_retry(self.FTP_HOST, timeout=60) From b3c2aa6b51fde56884d78fef87bb911067d06816 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:15:29 +0900 Subject: [PATCH 506/819] PyAnextAwaitable (#6427) --- .cspell.dict/python-more.txt | 1 + Lib/test/test_asyncgen.py | 16 +-- Lib/test/test_generators.py | 1 - Lib/test/test_types.py | 2 - crates/vm/src/builtins/asyncgenerator.rs | 157 ++++++++++++++++++++++- crates/vm/src/builtins/coroutine.rs | 19 ++- crates/vm/src/builtins/function.rs | 12 +- crates/vm/src/builtins/generator.rs | 14 +- crates/vm/src/coroutine.rs | 13 +- crates/vm/src/protocol/object.rs | 38 +++++- crates/vm/src/stdlib/builtins.rs | 17 ++- crates/vm/src/types/zoo.rs | 2 + 12 files changed, 255 insertions(+), 37 deletions(-) diff --git a/.cspell.dict/python-more.txt b/.cspell.dict/python-more.txt index 620843cdd6a..58a0e816087 100644 --- a/.cspell.dict/python-more.txt +++ b/.cspell.dict/python-more.txt @@ -4,6 +4,7 @@ aenter aexit aiter anext +anextawaitable appendleft argcount arrayiterator diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index 797ce1c091c..3618fb60d8f 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -375,8 +375,6 @@ async def async_gen_wrapper(): self.compare_generators(sync_gen_wrapper(), async_gen_wrapper()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_async_gen_api_01(self): async def gen(): yield 123 @@ -467,16 +465,12 @@ async def test_throw(): result = self.loop.run_until_complete(test_throw()) self.assertEqual(result, "completed") - # TODO: RUSTPYTHON, NameError: name 'anext' is not defined - @unittest.expectedFailure def test_async_generator_anext(self): async def agen(): yield 1 yield 2 self.check_async_iterator_anext(agen) - # TODO: RUSTPYTHON, NameError: name 'anext' is not defined - @unittest.expectedFailure def test_python_async_iterator_anext(self): class MyAsyncIter: """Asynchronously yield 1, then 2.""" @@ -492,8 +486,6 @@ async def __anext__(self): return self.yielded self.check_async_iterator_anext(MyAsyncIter) - # TODO: RUSTPYTHON, NameError: name 'anext' is not defined - @unittest.expectedFailure def test_python_async_iterator_types_coroutine_anext(self): import types class MyAsyncIterWithTypesCoro: @@ -523,8 +515,6 @@ async def consume(): res = self.loop.run_until_complete(consume()) self.assertEqual(res, [1, 2]) - # TODO: RUSTPYTHON, NameError: name 'aiter' is not defined - @unittest.expectedFailure def test_async_gen_aiter_class(self): results = [] class Gen: @@ -549,8 +539,6 @@ async def gen(): applied_twice = aiter(applied_once) self.assertIs(applied_once, applied_twice) - # TODO: RUSTPYTHON, NameError: name 'anext' is not defined - @unittest.expectedFailure def test_anext_bad_args(self): async def gen(): yield 1 @@ -571,7 +559,7 @@ async def call_with_kwarg(): with self.assertRaises(TypeError): self.loop.run_until_complete(call_with_kwarg()) - # TODO: RUSTPYTHON, NameError: name 'anext' is not defined + # TODO: RUSTPYTHON, error message mismatch @unittest.expectedFailure def test_anext_bad_await(self): async def bad_awaitable(): @@ -642,7 +630,7 @@ async def do_test(): result = self.loop.run_until_complete(do_test()) self.assertEqual(result, "completed") - # TODO: RUSTPYTHON, NameError: name 'anext' is not defined + # TODO: RUSTPYTHON, anext coroutine iteration issue @unittest.expectedFailure def test_anext_iter(self): @types.coroutine diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index a27b483e3e7..c6f66085c74 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -117,7 +117,6 @@ def g3(): return (yield from f()) class GeneratorTest(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_name(self): def func(): yield 1 diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 59dc9814fbb..fdcb9060e83 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -2052,8 +2052,6 @@ async def corofunc(): else: self.fail('StopIteration was expected') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_gen(self): def gen_func(): yield 1 diff --git a/crates/vm/src/builtins/asyncgenerator.rs b/crates/vm/src/builtins/asyncgenerator.rs index f9389263980..b41a49f931e 100644 --- a/crates/vm/src/builtins/asyncgenerator.rs +++ b/crates/vm/src/builtins/asyncgenerator.rs @@ -33,9 +33,9 @@ impl PyAsyncGen { &self.inner } - pub fn new(frame: FrameRef, name: PyStrRef) -> Self { + pub fn new(frame: FrameRef, name: PyStrRef, qualname: PyStrRef) -> Self { Self { - inner: Coro::new(frame, name), + inner: Coro::new(frame, name, qualname), running_async: AtomicCell::new(false), } } @@ -50,6 +50,16 @@ impl PyAsyncGen { self.inner.set_name(name) } + #[pygetset] + fn __qualname__(&self) -> PyStrRef { + self.inner.qualname() + } + + #[pygetset(setter)] + fn set___qualname__(&self, qualname: PyStrRef) { + self.inner.set_qualname(qualname) + } + #[pygetset] fn ag_await(&self, _vm: &VirtualMachine) -> Option<PyObjectRef> { self.inner.frame().yield_from_target() @@ -424,8 +434,151 @@ impl IterNext for PyAsyncGenAThrow { } } +/// Awaitable wrapper for anext() builtin with default value. +/// When StopAsyncIteration is raised, it converts it to StopIteration(default). +#[pyclass(module = false, name = "anext_awaitable")] +#[derive(Debug)] +pub struct PyAnextAwaitable { + wrapped: PyObjectRef, + default_value: PyObjectRef, +} + +impl PyPayload for PyAnextAwaitable { + #[inline] + fn class(ctx: &Context) -> &'static Py<PyType> { + ctx.types.anext_awaitable + } +} + +#[pyclass(with(IterNext, Iterable))] +impl PyAnextAwaitable { + pub fn new(wrapped: PyObjectRef, default_value: PyObjectRef) -> Self { + Self { + wrapped, + default_value, + } + } + + #[pymethod(name = "__await__")] + fn r#await(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyRef<Self> { + zelf + } + + /// Get the awaitable iterator from wrapped object. + // = anextawaitable_getiter. + fn get_awaitable_iter(&self, vm: &VirtualMachine) -> PyResult { + use crate::builtins::PyCoroutine; + use crate::protocol::PyIter; + + let wrapped = &self.wrapped; + + // If wrapped is already an async_generator_asend, it's an iterator + if wrapped.class().is(vm.ctx.types.async_generator_asend) + || wrapped.class().is(vm.ctx.types.async_generator_athrow) + { + return Ok(wrapped.clone()); + } + + // _PyCoro_GetAwaitableIter equivalent + let awaitable = if wrapped.class().is(vm.ctx.types.coroutine_type) { + // Coroutine - get __await__ later + wrapped.clone() + } else { + // Try to get __await__ method + if let Some(await_method) = vm.get_method(wrapped.clone(), identifier!(vm, __await__)) { + await_method?.call((), vm)? + } else { + return Err(vm.new_type_error(format!( + "object {} can't be used in 'await' expression", + wrapped.class().name() + ))); + } + }; + + // If awaitable is a coroutine, get its __await__ + if awaitable.class().is(vm.ctx.types.coroutine_type) { + let coro_await = vm.call_method(&awaitable, "__await__", ())?; + // Check that __await__ returned an iterator + if !PyIter::check(&coro_await) { + return Err(vm.new_type_error("__await__ returned a non-iterable")); + } + return Ok(coro_await); + } + + // Check the result is an iterator, not a coroutine + if awaitable.downcast_ref::<PyCoroutine>().is_some() { + return Err(vm.new_type_error("__await__() returned a coroutine")); + } + + // Check that the result is an iterator + if !PyIter::check(&awaitable) { + return Err(vm.new_type_error(format!( + "__await__() returned non-iterator of type '{}'", + awaitable.class().name() + ))); + } + + Ok(awaitable) + } + + #[pymethod] + fn send(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let awaitable = self.get_awaitable_iter(vm)?; + let result = vm.call_method(&awaitable, "send", (val,)); + self.handle_result(result, vm) + } + + #[pymethod] + fn throw( + &self, + exc_type: PyObjectRef, + exc_val: OptionalArg, + exc_tb: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let awaitable = self.get_awaitable_iter(vm)?; + let result = vm.call_method( + &awaitable, + "throw", + ( + exc_type, + exc_val.unwrap_or_none(vm), + exc_tb.unwrap_or_none(vm), + ), + ); + self.handle_result(result, vm) + } + + #[pymethod] + fn close(&self, vm: &VirtualMachine) -> PyResult<()> { + if let Ok(awaitable) = self.get_awaitable_iter(vm) { + let _ = vm.call_method(&awaitable, "close", ()); + } + Ok(()) + } + + /// Convert StopAsyncIteration to StopIteration(default_value) + fn handle_result(&self, result: PyResult, vm: &VirtualMachine) -> PyResult { + match result { + Ok(value) => Ok(value), + Err(exc) if exc.fast_isinstance(vm.ctx.exceptions.stop_async_iteration) => { + Err(vm.new_stop_iteration(Some(self.default_value.clone()))) + } + Err(exc) => Err(exc), + } + } +} + +impl SelfIter for PyAnextAwaitable {} +impl IterNext for PyAnextAwaitable { + fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> { + PyIterReturn::from_pyresult(zelf.send(vm.ctx.none(), vm), vm) + } +} + pub fn init(ctx: &Context) { PyAsyncGen::extend_class(ctx, ctx.types.async_generator); PyAsyncGenASend::extend_class(ctx, ctx.types.async_generator_asend); PyAsyncGenAThrow::extend_class(ctx, ctx.types.async_generator_athrow); + PyAnextAwaitable::extend_class(ctx, ctx.types.anext_awaitable); } diff --git a/crates/vm/src/builtins/coroutine.rs b/crates/vm/src/builtins/coroutine.rs index e084bf50efc..8f57059e085 100644 --- a/crates/vm/src/builtins/coroutine.rs +++ b/crates/vm/src/builtins/coroutine.rs @@ -29,9 +29,9 @@ impl PyCoroutine { &self.inner } - pub fn new(frame: FrameRef, name: PyStrRef) -> Self { + pub fn new(frame: FrameRef, name: PyStrRef, qualname: PyStrRef) -> Self { Self { - inner: Coro::new(frame, name), + inner: Coro::new(frame, name, qualname), } } @@ -45,6 +45,16 @@ impl PyCoroutine { self.inner.set_name(name) } + #[pygetset] + fn __qualname__(&self) -> PyStrRef { + self.inner.qualname() + } + + #[pygetset(setter)] + fn set___qualname__(&self, qualname: PyStrRef) { + self.inner.set_qualname(qualname) + } + #[pymethod(name = "__await__")] const fn r#await(zelf: PyRef<Self>) -> PyCoroutineWrapper { PyCoroutineWrapper { coro: zelf } @@ -156,6 +166,11 @@ impl PyCoroutineWrapper { ) -> PyResult<PyIterReturn> { self.coro.throw(exc_type, exc_val, exc_tb, vm) } + + #[pymethod] + fn close(&self, vm: &VirtualMachine) -> PyResult<()> { + self.coro.close(vm) + } } impl SelfIter for PyCoroutineWrapper {} diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index 0b322c6be5b..0459cecbdd2 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -425,9 +425,15 @@ impl Py<PyFunction> { let is_gen = code.flags.contains(bytecode::CodeFlags::IS_GENERATOR); let is_coro = code.flags.contains(bytecode::CodeFlags::IS_COROUTINE); match (is_gen, is_coro) { - (true, false) => Ok(PyGenerator::new(frame, self.__name__()).into_pyobject(vm)), - (false, true) => Ok(PyCoroutine::new(frame, self.__name__()).into_pyobject(vm)), - (true, true) => Ok(PyAsyncGen::new(frame, self.__name__()).into_pyobject(vm)), + (true, false) => { + Ok(PyGenerator::new(frame, self.__name__(), self.__qualname__()).into_pyobject(vm)) + } + (false, true) => { + Ok(PyCoroutine::new(frame, self.__name__(), self.__qualname__()).into_pyobject(vm)) + } + (true, true) => { + Ok(PyAsyncGen::new(frame, self.__name__(), self.__qualname__()).into_pyobject(vm)) + } (false, false) => vm.run_frame(frame), } } diff --git a/crates/vm/src/builtins/generator.rs b/crates/vm/src/builtins/generator.rs index 44021c24ed8..da981b5a6c2 100644 --- a/crates/vm/src/builtins/generator.rs +++ b/crates/vm/src/builtins/generator.rs @@ -32,9 +32,9 @@ impl PyGenerator { &self.inner } - pub fn new(frame: FrameRef, name: PyStrRef) -> Self { + pub fn new(frame: FrameRef, name: PyStrRef, qualname: PyStrRef) -> Self { Self { - inner: Coro::new(frame, name), + inner: Coro::new(frame, name, qualname), } } @@ -48,6 +48,16 @@ impl PyGenerator { self.inner.set_name(name) } + #[pygetset] + fn __qualname__(&self) -> PyStrRef { + self.inner.qualname() + } + + #[pygetset(setter)] + fn set___qualname__(&self, qualname: PyStrRef) { + self.inner.set_qualname(qualname) + } + #[pygetset] fn gi_frame(&self, _vm: &VirtualMachine) -> FrameRef { self.inner.frame() diff --git a/crates/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs index 5e0ca62caee..4e76490ed65 100644 --- a/crates/vm/src/coroutine.rs +++ b/crates/vm/src/coroutine.rs @@ -32,7 +32,7 @@ pub struct Coro { // code // _weakreflist name: PyMutex<PyStrRef>, - // qualname + qualname: PyMutex<PyStrRef>, exception: PyMutex<Option<PyBaseExceptionRef>>, // exc_state } @@ -48,13 +48,14 @@ fn gen_name(jen: &PyObject, vm: &VirtualMachine) -> &'static str { } impl Coro { - pub fn new(frame: FrameRef, name: PyStrRef) -> Self { + pub fn new(frame: FrameRef, name: PyStrRef, qualname: PyStrRef) -> Self { Self { frame, closed: AtomicCell::new(false), running: AtomicCell::new(false), exception: PyMutex::default(), name: PyMutex::new(name), + qualname: PyMutex::new(qualname), } } @@ -188,6 +189,14 @@ impl Coro { *self.name.lock() = name; } + pub fn qualname(&self) -> PyStrRef { + self.qualname.lock().clone() + } + + pub fn set_qualname(&self, qualname: PyStrRef) { + *self.qualname.lock() = qualname; + } + pub fn repr(&self, jen: &PyObject, id: usize, vm: &VirtualMachine) -> String { format!( "<{} object {} at {:#x}>", diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index 66f1483c2e1..f2e52a94004 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -4,8 +4,8 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ - PyAsyncGen, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple, - PyTupleRef, PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr, + PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple, PyTupleRef, + PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr, }, common::{hash::PyHash, str::to_ascii}, convert::{ToPyObject, ToPyResult}, @@ -87,11 +87,37 @@ impl PyObject { // PyObject *PyObject_GetAIter(PyObject *o) pub fn get_aiter(&self, vm: &VirtualMachine) -> PyResult { - if self.downcastable::<PyAsyncGen>() { - vm.call_special_method(self, identifier!(vm, __aiter__), ()) - } else { - Err(vm.new_type_error("wrong argument type")) + use crate::builtins::PyCoroutine; + + // Check if object has __aiter__ method + let aiter_method = self.class().get_attr(identifier!(vm, __aiter__)); + let Some(_aiter_method) = aiter_method else { + return Err(vm.new_type_error(format!( + "'{}' object is not an async iterable", + self.class().name() + ))); + }; + + // Call __aiter__ + let iterator = vm.call_special_method(self, identifier!(vm, __aiter__), ())?; + + // Check that __aiter__ did not return a coroutine + if iterator.downcast_ref::<PyCoroutine>().is_some() { + return Err(vm.new_type_error( + "'async_iterator' object cannot be interpreted as an async iterable; \ + perhaps you forgot to call aiter()?", + )); + } + + // Check that the result is an async iterator (has __anext__) + if !iterator.class().has_attr(identifier!(vm, __anext__)) { + return Err(vm.new_type_error(format!( + "'{}' object is not an async iterator", + iterator.class().name() + ))); } + + Ok(iterator) } pub fn has_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult<bool> { diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 5d4a28bf183..542476d68c7 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -540,12 +540,23 @@ mod builtins { default_value: OptionalArg<PyObjectRef>, vm: &VirtualMachine, ) -> PyResult { + use crate::builtins::asyncgenerator::PyAnextAwaitable; + + // Check if object is an async iterator (has __anext__ method) + if !aiter.class().has_attr(identifier!(vm, __anext__)) { + return Err(vm.new_type_error(format!( + "'{}' object is not an async iterator", + aiter.class().name() + ))); + } + let awaitable = vm.call_method(&aiter, "__anext__", ())?; - if default_value.is_missing() { - Ok(awaitable) + if let OptionalArg::Present(default) = default_value { + Ok(PyAnextAwaitable::new(awaitable, default) + .into_ref(&vm.ctx) + .into()) } else { - // TODO: Implement CPython like PyAnextAwaitable to properly handle the default value. Ok(awaitable) } } diff --git a/crates/vm/src/types/zoo.rs b/crates/vm/src/types/zoo.rs index 383732cf691..d994ff60210 100644 --- a/crates/vm/src/types/zoo.rs +++ b/crates/vm/src/types/zoo.rs @@ -21,6 +21,7 @@ pub struct TypeZoo { pub async_generator_asend: &'static Py<PyType>, pub async_generator_athrow: &'static Py<PyType>, pub async_generator_wrapped_value: &'static Py<PyType>, + pub anext_awaitable: &'static Py<PyType>, pub bytes_type: &'static Py<PyType>, pub bytes_iterator_type: &'static Py<PyType>, pub bytearray_type: &'static Py<PyType>, @@ -139,6 +140,7 @@ impl TypeZoo { async_generator_athrow: asyncgenerator::PyAsyncGenAThrow::init_builtin_type(), async_generator_wrapped_value: asyncgenerator::PyAsyncGenWrappedValue::init_builtin_type(), + anext_awaitable: asyncgenerator::PyAnextAwaitable::init_builtin_type(), bound_method_type: function::PyBoundMethod::init_builtin_type(), builtin_function_or_method_type: builtin_func::PyNativeFunction::init_builtin_type(), builtin_method_type: builtin_func::PyNativeMethod::init_builtin_type(), From 21300f689d41144b73717ae8b6cec3ea9ad75498 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:16:49 +0900 Subject: [PATCH 507/819] Allow with() in pyexception (#6436) --- crates/derive-impl/src/pyclass.rs | 39 +++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 84559e3574e..86b038f2082 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -579,14 +579,30 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R return Ok(item.into_token_stream()); }; - if !attr.is_empty() { - return Err(syn::Error::new_spanned( - &attr[0], - "#[pyexception] impl doesn't allow attrs. Use #[pyclass] instead.", - )); + // Check if with(Constructor) is specified. If Constructor trait is used, don't generate slot_new + let mut has_slot_new = false; + + let mut extra_attrs = Vec::new(); + for nested in &attr { + if let NestedMeta::Meta(Meta::List(MetaList { path, nested, .. })) = nested { + if path.is_ident("with") { + // Check if Constructor is in the list + for meta in nested { + if let NestedMeta::Meta(Meta::Path(p)) = meta + && p.is_ident("Constructor") + { + has_slot_new = true; + } + } + } + extra_attrs.push(NestedMeta::Meta(Meta::List(MetaList { + path: path.clone(), + paren_token: Default::default(), + nested: nested.clone(), + }))); + } } - let mut has_slot_new = false; let mut has_slot_init = false; let syn::ItemImpl { generics, @@ -611,6 +627,8 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R } } + // TODO: slot_new, slot_init must be Constructor or Initializer later + let slot_new = if has_slot_new { quote!() } else { @@ -646,8 +664,15 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R } } }; + + let extra_attrs_tokens = if extra_attrs.is_empty() { + quote!() + } else { + quote!(, #(#extra_attrs),*) + }; + Ok(quote! { - #[pyclass(flags(BASETYPE, HAS_DICT))] + #[pyclass(flags(BASETYPE, HAS_DICT) #extra_attrs_tokens)] impl #generics #self_ty { #(#items)* From d36a2cffdef2218f3264cef9145a1f781d474ea3 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:17:14 +0900 Subject: [PATCH 508/819] New subclass payload layout (#6319) * PySubclass * Add base payload to exception * PyStructSequecne base * redefine PyOSError and subtypes * heap exception new * rework UNSUPPORTED_OPERATION --- crates/derive-impl/src/pyclass.rs | 196 ++++++++-- crates/derive-impl/src/pystructseq.rs | 39 +- crates/stdlib/src/socket.rs | 22 +- crates/stdlib/src/ssl.rs | 299 ++++++++------- crates/stdlib/src/ssl/compat.rs | 36 +- crates/vm/src/builtins/bool.rs | 39 +- crates/vm/src/builtins/int.rs | 2 +- crates/vm/src/class.rs | 15 + crates/vm/src/exception_group.rs | 3 +- crates/vm/src/exceptions.rs | 459 ++++++++++++++++------- crates/vm/src/object/core.rs | 34 +- crates/vm/src/object/payload.rs | 36 +- crates/vm/src/stdlib/ast/pyast.rs | 54 +-- crates/vm/src/stdlib/ctypes.rs | 11 +- crates/vm/src/stdlib/ctypes/array.rs | 34 +- crates/vm/src/stdlib/ctypes/base.rs | 19 +- crates/vm/src/stdlib/ctypes/field.rs | 3 +- crates/vm/src/stdlib/ctypes/function.rs | 8 +- crates/vm/src/stdlib/ctypes/pointer.rs | 15 +- crates/vm/src/stdlib/ctypes/structure.rs | 23 +- crates/vm/src/stdlib/ctypes/union.rs | 23 +- crates/vm/src/stdlib/io.rs | 68 ++-- crates/vm/src/stdlib/os.rs | 6 +- crates/vm/src/types/structseq.rs | 6 +- crates/vm/src/vm/context.rs | 37 +- crates/vm/src/vm/vm_new.rs | 65 +++- extra_tests/snippets/stdlib_ctypes.py | 2 + 27 files changed, 1063 insertions(+), 491 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 86b038f2082..5060dced2b0 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -305,6 +305,85 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul Ok(tokens) } +/// Validates that when a base class is specified, the struct has the base type as its first field. +/// This ensures proper memory layout for subclassing (required for #[repr(transparent)] to work correctly). +fn validate_base_field(item: &Item, base_path: &syn::Path) -> Result<()> { + let Item::Struct(item_struct) = item else { + // Only validate structs - enums with base are already an error elsewhere + return Ok(()); + }; + + // Get the base type name for error messages + let base_name = base_path + .segments + .last() + .map(|s| s.ident.to_string()) + .unwrap_or_else(|| quote!(#base_path).to_string()); + + match &item_struct.fields { + syn::Fields::Named(fields) => { + let Some(first_field) = fields.named.first() else { + bail_span!( + item_struct, + "#[pyclass] with base = {base_name} requires the first field to be of type {base_name}, but the struct has no fields" + ); + }; + if !type_matches_path(&first_field.ty, base_path) { + bail_span!( + first_field, + "#[pyclass] with base = {base_name} requires the first field to be of type {base_name}" + ); + } + } + syn::Fields::Unnamed(fields) => { + let Some(first_field) = fields.unnamed.first() else { + bail_span!( + item_struct, + "#[pyclass] with base = {base_name} requires the first field to be of type {base_name}, but the struct has no fields" + ); + }; + if !type_matches_path(&first_field.ty, base_path) { + bail_span!( + first_field, + "#[pyclass] with base = {base_name} requires the first field to be of type {base_name}" + ); + } + } + syn::Fields::Unit => { + bail_span!( + item_struct, + "#[pyclass] with base = {base_name} requires the first field to be of type {base_name}, but the struct is a unit struct" + ); + } + } + + Ok(()) +} + +/// Check if a type matches a given path (handles simple cases like `Foo` or `path::to::Foo`) +fn type_matches_path(ty: &syn::Type, path: &syn::Path) -> bool { + // Compare by converting both to string representation for macro hygiene + let ty_str = quote!(#ty).to_string().replace(' ', ""); + let path_str = quote!(#path).to_string().replace(' ', ""); + + // Check if both are the same or if the type ends with the path's last segment + if ty_str == path_str { + return true; + } + + // Also match if just the last segment matches (e.g., foo::Bar matches Bar) + let syn::Type::Path(type_path) = ty else { + return false; + }; + let Some(type_last) = type_path.path.segments.last() else { + return false; + }; + let Some(path_last) = path.segments.last() else { + return false; + }; + type_last.ident == path_last.ident +} + fn generate_class_def( ident: &Ident, name: &str, @@ -339,7 +418,6 @@ fn generate_class_def( } else { quote!(false) }; - let basicsize = quote!(std::mem::size_of::<#ident>()); let is_pystruct = attrs.iter().any(|attr| { attr.path().is_ident("derive") && if let Ok(Meta::List(l)) = attr.parse_meta() { @@ -350,6 +428,25 @@ fn generate_class_def( false } }); + // Check if the type has #[repr(transparent)] - only then we can safely + // generate PySubclass impl (requires same memory layout as base type) + let is_repr_transparent = attrs.iter().any(|attr| { + attr.path().is_ident("repr") + && if let Ok(Meta::List(l)) = attr.parse_meta() { + l.nested + .into_iter() + .any(|n| n.get_ident().is_some_and(|p| p == "transparent")) + } else { + false + } + }); + // If repr(transparent) with a base, the type has the same memory layout as base, + // so basicsize should be 0 (no additional space beyond the base type) + let basicsize = if is_repr_transparent && base.is_some() { + quote!(0) + } else { + quote!(std::mem::size_of::<#ident>()) + }; if base.is_some() && is_pystruct { bail_span!(ident, "PyStructSequence cannot have `base` class attr",); } @@ -379,12 +476,31 @@ fn generate_class_def( } }); - let base_or_object = if let Some(base) = base { + let base_or_object = if let Some(ref base) = base { quote! { #base } } else { quote! { ::rustpython_vm::builtins::PyBaseObject } }; + // Generate PySubclass impl for #[repr(transparent)] types with base class + // (tuple struct assumed, so &self.0 works) + let subclass_impl = if !is_pystruct && is_repr_transparent { + base.as_ref().map(|typ| { + quote! { + impl ::rustpython_vm::class::PySubclass for #ident { + type Base = #typ; + + #[inline] + fn as_base(&self) -> &Self::Base { + &self.0 + } + } + } + }) + } else { + None + }; + let tokens = quote! { impl ::rustpython_vm::class::PyClassDef for #ident { const NAME: &'static str = #name; @@ -409,6 +525,8 @@ fn generate_class_def( #base_class } + + #subclass_impl }; Ok(tokens) } @@ -426,11 +544,16 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result<Tok let metaclass = class_meta.metaclass()?; let unhashable = class_meta.unhashable()?; + // Validate that if base is specified, the first field must be of the base type + if let Some(ref base_path) = base { + validate_base_field(&item, base_path)?; + } + let class_def = generate_class_def( ident, &class_name, module_name.as_deref(), - base, + base.clone(), metaclass, unhashable, attrs, @@ -485,19 +608,47 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result<Tok } }; - let impl_payload = if let Some(ctx_type_name) = class_meta.ctx_name()? { - let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); // FIXME span + // Generate PyPayload impl based on whether base exists + #[allow(clippy::collapsible_else_if)] + let impl_payload = if let Some(base_type) = &base { + let class_fn = if let Some(ctx_type_name) = class_meta.ctx_name()? { + let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); + quote! { ctx.types.#ctx_type_ident } + } else { + quote! { <Self as ::rustpython_vm::class::StaticType>::static_type() } + }; - // We need this to make extend mechanism work: quote! { + // static_assertions::const_assert!(std::mem::size_of::<#base_type>() <= std::mem::size_of::<#ident>()); impl ::rustpython_vm::PyPayload for #ident { + #[inline] + fn payload_type_id() -> ::std::any::TypeId { + <#base_type as ::rustpython_vm::PyPayload>::payload_type_id() + } + + #[inline] + fn validate_downcastable_from(obj: &::rustpython_vm::PyObject) -> bool { + <Self as ::rustpython_vm::class::PyClassDef>::BASICSIZE <= obj.class().slots.basicsize && obj.class().fast_issubclass(<Self as ::rustpython_vm::class::StaticType>::static_type()) + } + fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { - ctx.types.#ctx_type_ident + #class_fn } } } } else { - quote! {} + if let Some(ctx_type_name) = class_meta.ctx_name()? { + let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); + quote! { + impl ::rustpython_vm::PyPayload for #ident { + fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { + ctx.types.#ctx_type_ident + } + } + } + } else { + quote! {} + } }; let empty_impl = if let Some(attrs) = class_meta.impl_attrs()? { @@ -536,26 +687,6 @@ pub(crate) fn impl_pyexception(attr: PunctuatedNestedMeta, item: Item) -> Result let class_name = class_meta.class_name()?; let base_class_name = class_meta.base()?; - let impl_payload = if let Some(ctx_type_name) = class_meta.ctx_name()? { - let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); // FIXME span - - // We need this to make extend mechanism work: - quote! { - impl ::rustpython_vm::PyPayload for #ident { - fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { - ctx.exceptions.#ctx_type_ident - } - } - } - } else { - quote! { - impl ::rustpython_vm::PyPayload for #ident { - fn class(_ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { - <Self as ::rustpython_vm::class::StaticType>::static_type() - } - } - } - }; let impl_pyclass = if class_meta.has_impl()? { quote! { #[pyexception] @@ -568,7 +699,6 @@ pub(crate) fn impl_pyexception(attr: PunctuatedNestedMeta, item: Item) -> Result let ret = quote! { #[pyclass(module = false, name = #class_name, base = #base_class_name)] #item - #impl_payload #impl_pyclass }; Ok(ret) @@ -585,7 +715,8 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R let mut extra_attrs = Vec::new(); for nested in &attr { if let NestedMeta::Meta(Meta::List(MetaList { path, nested, .. })) = nested { - if path.is_ident("with") { + // If we already found the constructor trait, no need to keep looking for it + if !has_slot_new && path.is_ident("with") { // Check if Constructor is in the list for meta in nested { if let NestedMeta::Meta(Meta::Path(p)) = meta @@ -1078,9 +1209,8 @@ impl GetSetNursery { item_ident: Ident, ) -> Result<()> { assert!(!self.validated, "new item is not allowed after validation"); - if !matches!(kind, GetSetItemKind::Get) && !cfgs.is_empty() { - bail_span!(item_ident, "Only the getter can have #[cfg]",); - } + // Note: Both getter and setter can have #[cfg], but they must have matching cfgs + // since the map key is (name, cfgs). This ensures getter and setter are paired correctly. let entry = self.map.entry((name.clone(), cfgs)).or_default(); let func = match kind { GetSetItemKind::Get => &mut entry.0, diff --git a/crates/derive-impl/src/pystructseq.rs b/crates/derive-impl/src/pystructseq.rs index c43673fe975..6c34844696d 100644 --- a/crates/derive-impl/src/pystructseq.rs +++ b/crates/derive-impl/src/pystructseq.rs @@ -446,8 +446,10 @@ pub(crate) fn impl_pystruct_sequence( }; let output = quote! { - // The Python type struct (user-defined, possibly empty) - #pytype_vis struct #pytype_ident; + // The Python type struct - newtype wrapping PyTuple + #[derive(Debug)] + #[repr(transparent)] + #pytype_vis struct #pytype_ident(pub ::rustpython_vm::builtins::PyTuple); // PyClassDef for Python type impl ::rustpython_vm::class::PyClassDef for #pytype_ident { @@ -476,10 +478,37 @@ pub(crate) fn impl_pystruct_sequence( } } - // MaybeTraverse (empty - no GC fields in empty struct) + // Subtype uses base type's payload_type_id + impl ::rustpython_vm::PyPayload for #pytype_ident { + #[inline] + fn payload_type_id() -> ::std::any::TypeId { + <::rustpython_vm::builtins::PyTuple as ::rustpython_vm::PyPayload>::payload_type_id() + } + + #[inline] + fn validate_downcastable_from(obj: &::rustpython_vm::PyObject) -> bool { + obj.class().fast_issubclass(<Self as ::rustpython_vm::class::StaticType>::static_type()) + } + + fn class(_ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { + <Self as ::rustpython_vm::class::StaticType>::static_type() + } + } + + // MaybeTraverse - delegate to inner PyTuple impl ::rustpython_vm::object::MaybeTraverse for #pytype_ident { - fn try_traverse(&self, _traverse_fn: &mut ::rustpython_vm::object::TraverseFn<'_>) { - // Empty struct has no fields to traverse + fn try_traverse(&self, traverse_fn: &mut ::rustpython_vm::object::TraverseFn<'_>) { + self.0.try_traverse(traverse_fn) + } + } + + // PySubclass for proper inheritance + impl ::rustpython_vm::class::PySubclass for #pytype_ident { + type Base = ::rustpython_vm::builtins::PyTuple; + + #[inline] + fn as_base(&self) -> &Self::Base { + &self.0 } } diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index b4e4dac88aa..08b05b56aa8 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -15,7 +15,7 @@ mod _socket { use crate::common::lock::{PyMappedRwLockReadGuard, PyRwLock, PyRwLockReadGuard}; use crate::vm::{ AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyBaseExceptionRef, PyListRef, PyStrRef, PyTupleRef, PyTypeRef}, + builtins::{PyBaseExceptionRef, PyListRef, PyOSError, PyStrRef, PyTupleRef, PyTypeRef}, common::os::ErrorExt, convert::{IntoPyException, ToPyObject, TryFromBorrowedObject, TryFromObject}, function::{ArgBytesLike, ArgMemoryBuffer, Either, FsPath, OptionalArg, OptionalOption}, @@ -1826,6 +1826,11 @@ mod _socket { Self::Py(exc) } } + impl From<PyRef<PyOSError>> for IoOrPyException { + fn from(exc: PyRef<PyOSError>) -> Self { + Self::Py(exc.upcast()) + } + } impl From<io::Error> for IoOrPyException { fn from(err: io::Error) -> Self { Self::Io(err) @@ -1844,7 +1849,7 @@ mod _socket { #[inline] fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef { match self { - Self::Timeout => timeout_error(vm), + Self::Timeout => timeout_error(vm).upcast(), Self::Py(exc) => exc, Self::Io(err) => err.into_pyexception(vm), } @@ -2412,18 +2417,15 @@ mod _socket { SocketError::GaiError => gaierror(vm), SocketError::HError => herror(vm), }; - vm.new_exception( - exception_cls, - vec![vm.new_pyobj(err.error_num()), vm.ctx.new_str(strerr).into()], - ) - .into() + vm.new_os_subtype_error(exception_cls, Some(err.error_num()), strerr) + .into() } - fn timeout_error(vm: &VirtualMachine) -> PyBaseExceptionRef { + fn timeout_error(vm: &VirtualMachine) -> PyRef<PyOSError> { timeout_error_msg(vm, "timed out".to_owned()) } - pub(crate) fn timeout_error_msg(vm: &VirtualMachine, msg: String) -> PyBaseExceptionRef { - vm.new_exception_msg(timeout(vm), msg) + pub(crate) fn timeout_error_msg(vm: &VirtualMachine, msg: String) -> PyRef<PyOSError> { + vm.new_os_subtype_error(timeout(vm), None, msg) } fn get_ipv6_addr_str(ipv6: Ipv6Addr) -> String { diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 14e087668bb..c23062d639d 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -37,7 +37,9 @@ mod _ssl { vm::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyType, PyTypeRef}, + builtins::{ + PyBaseExceptionRef, PyBytesRef, PyListRef, PyOSError, PyStrRef, PyType, PyTypeRef, + }, convert::IntoPyException, function::{ArgBytesLike, ArgMemoryBuffer, FuncArgs, OptionalArg, PyComparisonValue}, stdlib::warnings, @@ -340,13 +342,11 @@ mod _ssl { #[pyattr] const ENCODING_PEM_AUX: i32 = 0x101; // PEM + 0x100 - // Exception types - use rustpython_vm::builtins::PyOSError; - #[pyattr] #[pyexception(name = "SSLError", base = PyOSError)] #[derive(Debug)] - pub struct PySSLError {} + #[repr(transparent)] + pub struct PySSLError(PyOSError); #[pyexception] impl PySSLError { @@ -373,89 +373,72 @@ mod _ssl { #[pyattr] #[pyexception(name = "SSLZeroReturnError", base = PySSLError)] #[derive(Debug)] - pub struct PySSLZeroReturnError {} + #[repr(transparent)] + pub struct PySSLZeroReturnError(PySSLError); #[pyexception] impl PySSLZeroReturnError {} #[pyattr] - #[pyexception(name = "SSLWantReadError", base = PySSLError)] + #[pyexception(name = "SSLWantReadError", base = PySSLError, impl)] #[derive(Debug)] - pub struct PySSLWantReadError {} - - #[pyexception] - impl PySSLWantReadError {} + #[repr(transparent)] + pub struct PySSLWantReadError(PySSLError); #[pyattr] - #[pyexception(name = "SSLWantWriteError", base = PySSLError)] + #[pyexception(name = "SSLWantWriteError", base = PySSLError, impl)] #[derive(Debug)] - pub struct PySSLWantWriteError {} - - #[pyexception] - impl PySSLWantWriteError {} + #[repr(transparent)] + pub struct PySSLWantWriteError(PySSLError); #[pyattr] - #[pyexception(name = "SSLSyscallError", base = PySSLError)] + #[pyexception(name = "SSLSyscallError", base = PySSLError, impl)] #[derive(Debug)] - pub struct PySSLSyscallError {} - - #[pyexception] - impl PySSLSyscallError {} + #[repr(transparent)] + pub struct PySSLSyscallError(PySSLError); #[pyattr] - #[pyexception(name = "SSLEOFError", base = PySSLError)] + #[pyexception(name = "SSLEOFError", base = PySSLError, impl)] #[derive(Debug)] - pub struct PySSLEOFError {} - - #[pyexception] - impl PySSLEOFError {} + #[repr(transparent)] + pub struct PySSLEOFError(PySSLError); #[pyattr] - #[pyexception(name = "SSLCertVerificationError", base = PySSLError)] + #[pyexception(name = "SSLCertVerificationError", base = PySSLError, impl)] #[derive(Debug)] - pub struct PySSLCertVerificationError {} - - #[pyexception] - impl PySSLCertVerificationError {} + #[repr(transparent)] + pub struct PySSLCertVerificationError(PySSLError); // Helper functions to create SSL exceptions with proper errno attribute - pub(super) fn create_ssl_want_read_error(vm: &VirtualMachine) -> PyBaseExceptionRef { - // args = (errno, message) - vm.new_exception( + pub(super) fn create_ssl_want_read_error(vm: &VirtualMachine) -> PyRef<PyOSError> { + vm.new_os_subtype_error( PySSLWantReadError::class(&vm.ctx).to_owned(), - vec![ - vm.ctx.new_int(SSL_ERROR_WANT_READ).into(), - vm.ctx - .new_str("The operation did not complete (read)") - .into(), - ], + Some(SSL_ERROR_WANT_READ), + "The operation did not complete (read)", ) } - pub(super) fn create_ssl_want_write_error(vm: &VirtualMachine) -> PyBaseExceptionRef { - // args = (errno, message) - vm.new_exception( + pub(super) fn create_ssl_want_write_error(vm: &VirtualMachine) -> PyRef<PyOSError> { + vm.new_os_subtype_error( PySSLWantWriteError::class(&vm.ctx).to_owned(), - vec![ - vm.ctx.new_int(SSL_ERROR_WANT_WRITE).into(), - vm.ctx - .new_str("The operation did not complete (write)") - .into(), - ], + Some(SSL_ERROR_WANT_WRITE), + "The operation did not complete (write)", ) } - pub(crate) fn create_ssl_eof_error(vm: &VirtualMachine) -> PyBaseExceptionRef { - vm.new_exception_msg( + pub(crate) fn create_ssl_eof_error(vm: &VirtualMachine) -> PyRef<PyOSError> { + vm.new_os_subtype_error( PySSLEOFError::class(&vm.ctx).to_owned(), - "EOF occurred in violation of protocol".to_owned(), + None, + "EOF occurred in violation of protocol", ) } - pub(crate) fn create_ssl_zero_return_error(vm: &VirtualMachine) -> PyBaseExceptionRef { - vm.new_exception_msg( + pub(crate) fn create_ssl_zero_return_error(vm: &VirtualMachine) -> PyRef<PyOSError> { + vm.new_os_subtype_error( PySSLZeroReturnError::class(&vm.ctx).to_owned(), - "TLS/SSL connection has been closed (EOF)".to_owned(), + None, + "TLS/SSL connection has been closed (EOF)", ) } @@ -1250,7 +1233,12 @@ mod _ssl { let msg = io_err.to_string(); if msg.contains("Failed to decrypt") || msg.contains("wrong password") { // Wrong password error - vm.new_exception_msg(PySSLError::class(&vm.ctx).to_owned(), msg) + vm.new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + msg, + ) + .upcast() } else { // [SSL] PEM lib super::compat::SslError::create_ssl_error_with_reason( @@ -1282,14 +1270,13 @@ mod _ssl { // Validate certificate and key match cert::validate_cert_key_match(&certs, &key).map_err(|e| { - vm.new_exception_msg( - PySSLError::class(&vm.ctx).to_owned(), - if e.contains("key values mismatch") { - "[SSL: KEY_VALUES_MISMATCH] key values mismatch".to_owned() - } else { - e - }, - ) + let msg = if e.contains("key values mismatch") { + "[SSL: KEY_VALUES_MISMATCH] key values mismatch".to_owned() + } else { + e + }; + vm.new_os_subtype_error(PySSLError::class(&vm.ctx).to_owned(), Some(0), msg) + .upcast() })?; // Auto-build certificate chain: if only leaf cert is in file, try to add CA certs @@ -1311,18 +1298,23 @@ mod _ssl { // Additional validation: Create CertifiedKey to ensure rustls accepts it let signing_key = rustls::crypto::aws_lc_rs::sign::any_supported_type(&key).map_err(|_| { - vm.new_exception_msg( + vm.new_os_subtype_error( PySSLError::class(&vm.ctx).to_owned(), - "[SSL: KEY_VALUES_MISMATCH] key values mismatch".to_owned(), + None, + "[SSL: KEY_VALUES_MISMATCH] key values mismatch", ) + .upcast() })?; let certified_key = CertifiedKey::new(full_chain.clone(), signing_key); if certified_key.keys_match().is_err() { - return Err(vm.new_exception_msg( - PySSLError::class(&vm.ctx).to_owned(), - "[SSL: KEY_VALUES_MISMATCH] key values mismatch".to_owned(), - )); + return Err(vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + "[SSL: KEY_VALUES_MISMATCH] key values mismatch", + ) + .upcast()); } // Add cert/key pair to collection (OpenSSL allows multiple cert/key pairs) @@ -1517,9 +1509,7 @@ mod _ssl { } if *self.x509_cert_count.read() == 0 { - return Err(vm.new_os_error( - "Failed to load certificates from Windows store".to_owned(), - )); + return Err(vm.new_os_error("Failed to load certificates from Windows store")); } Ok(()) @@ -1626,8 +1616,10 @@ mod _ssl { let cipher_str = ciphers.as_str(); // Parse cipher string and store selected ciphers - let selected_ciphers = parse_cipher_string(cipher_str) - .map_err(|e| vm.new_exception_msg(PySSLError::class(&vm.ctx).to_owned(), e))?; + let selected_ciphers = parse_cipher_string(cipher_str).map_err(|e| { + vm.new_os_subtype_error(PySSLError::class(&vm.ctx).to_owned(), None, e) + .upcast() + })?; // Store in context *self.selected_ciphers.write() = Some(selected_ciphers); @@ -1875,16 +1867,17 @@ mod _ssl { // Check if file exists if !std::path::Path::new(&path_str).exists() { - // Create FileNotFoundError with errno=ENOENT (2) using args - let exc = vm.new_exception( + // Create FileNotFoundError with errno=ENOENT (2) + let exc = vm.new_os_subtype_error( vm.ctx.exceptions.file_not_found_error.to_owned(), - vec![ - vm.ctx.new_int(2).into(), // errno = ENOENT (2) - vm.ctx.new_str("No such file or directory").into(), - vm.ctx.new_str(path_str.clone()).into(), // filename - ], + Some(2), // errno = ENOENT (2) + "No such file or directory", ); - return Err(exc); + // Set filename attribute + let _ = exc + .as_object() + .set_attr("filename", vm.ctx.new_str(path_str.clone()), vm); + return Err(exc.upcast()); } // Validate that the file contains DH parameters @@ -1988,16 +1981,22 @@ mod _ssl { // Validate socket type and context protocol if args.server_side && zelf.protocol == PROTOCOL_TLS_CLIENT { - return Err(vm.new_exception_msg( - PySSLError::class(&vm.ctx).to_owned(), - "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context".to_owned(), - )); + return Err(vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context", + ) + .upcast()); } if !args.server_side && zelf.protocol == PROTOCOL_TLS_SERVER { - return Err(vm.new_exception_msg( - PySSLError::class(&vm.ctx).to_owned(), - "Cannot create a client socket with a PROTOCOL_TLS_SERVER context".to_owned(), - )); + return Err(vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + "Cannot create a client socket with a PROTOCOL_TLS_SERVER context", + ) + .upcast()); } // Create _SSLSocket instance @@ -2052,16 +2051,22 @@ mod _ssl { // Validate socket type and context protocol if server_side && zelf.protocol == PROTOCOL_TLS_CLIENT { - return Err(vm.new_exception_msg( - PySSLError::class(&vm.ctx).to_owned(), - "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context".to_owned(), - )); + return Err(vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context", + ) + .upcast()); } if !server_side && zelf.protocol == PROTOCOL_TLS_SERVER { - return Err(vm.new_exception_msg( - PySSLError::class(&vm.ctx).to_owned(), - "Cannot create a client socket with a PROTOCOL_TLS_SERVER context".to_owned(), - )); + return Err(vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + "Cannot create a client socket with a PROTOCOL_TLS_SERVER context", + ) + .upcast()); } // Create _SSLSocket instance with BIO mode @@ -2209,20 +2214,26 @@ mod _ssl { // Preserve specific error messages from cert.rs let err_msg = e.to_string(); if err_msg.contains("no start line") { - // no start line: cadata does not contain a certificate - vm.new_exception_msg( + vm.new_os_subtype_error( PySSLError::class(&vm.ctx).to_owned(), - "no start line: cadata does not contain a certificate".to_string(), + None, + "no start line: cadata does not contain a certificate", ) + .upcast() } else if err_msg.contains("not enough data") { - // not enough data: cadata does not contain a certificate - vm.new_exception_msg( + vm.new_os_subtype_error( PySSLError::class(&vm.ctx).to_owned(), - "not enough data: cadata does not contain a certificate".to_string(), + None, + "not enough data: cadata does not contain a certificate", ) + .upcast() } else { - // Generic PEM error - vm.new_exception_msg(PySSLError::class(&vm.ctx).to_owned(), err_msg) + vm.new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + err_msg, + ) + .upcast() } }) } @@ -2774,10 +2785,13 @@ mod _ssl { // - Re-acquire connection lock after callback // - Call: connection.send_fatal_alert(AlertDescription::InternalError) // - Then close connection - let exc = vm.new_exception_msg( - PySSLError::class(&vm.ctx).to_owned(), - "SNI callback returned invalid type".to_owned(), - ); + let exc: PyBaseExceptionRef = vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + "SNI callback returned invalid type", + ) + .upcast(); let _ = exc.as_object().set_attr( "reason", vm.ctx.new_str("TLSV1_ALERT_INTERNAL_ERROR"), @@ -3287,7 +3301,7 @@ mod _ssl { len: OptionalArg<isize>, buffer: OptionalArg<ArgMemoryBuffer>, vm: &VirtualMachine, - ) -> PyResult<PyObjectRef> { + ) -> PyResult { // Convert len to usize, defaulting to 1024 if not provided // -1 means read all available data (treat as large buffer size) let len_val = len.unwrap_or(PEM_BUFSIZE as isize); @@ -3330,10 +3344,13 @@ mod _ssl { // After unwrap()/shutdown(), read operations should fail with SSLError let shutdown_state = *self.shutdown_state.lock(); if shutdown_state != ShutdownState::NotStarted { - return Err(vm.new_exception_msg( - PySSLError::class(&vm.ctx).to_owned(), - "cannot read after shutdown".to_owned(), - )); + return Err(vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + "cannot read after shutdown", + ) + .upcast()); } // Helper function to handle return value based on buffer presence @@ -3380,10 +3397,13 @@ mod _ssl { } Err(crate::ssl::compat::SslError::Eof) => { // EOF occurred in violation of protocol (unexpected closure) - Err(vm.new_exception_msg( - PySSLEOFError::class(&vm.ctx).to_owned(), - "EOF occurred in violation of protocol".to_owned(), - )) + Err(vm + .new_os_subtype_error( + PySSLEOFError::class(&vm.ctx).to_owned(), + None, + "EOF occurred in violation of protocol", + ) + .upcast()) } Err(crate::ssl::compat::SslError::ZeroReturn) => { // Clean closure with close_notify - return empty data @@ -3391,13 +3411,15 @@ mod _ssl { } Err(crate::ssl::compat::SslError::WantRead) => { // Non-blocking mode: would block - Err(create_ssl_want_read_error(vm)) + Err(create_ssl_want_read_error(vm).upcast()) } Err(crate::ssl::compat::SslError::WantWrite) => { // Non-blocking mode: would block on write - Err(create_ssl_want_write_error(vm)) + Err(create_ssl_want_write_error(vm).upcast()) + } + Err(crate::ssl::compat::SslError::Timeout(msg)) => { + Err(timeout_error_msg(vm, msg).upcast()) } - Err(crate::ssl::compat::SslError::Timeout(msg)) => Err(timeout_error_msg(vm, msg)), Err(crate::ssl::compat::SslError::Py(e)) => { // Python exception - pass through Err(e) @@ -3453,10 +3475,13 @@ mod _ssl { // After unwrap()/shutdown(), write operations should fail with SSLError let shutdown_state = *self.shutdown_state.lock(); if shutdown_state != ShutdownState::NotStarted { - return Err(vm.new_exception_msg( - PySSLError::class(&vm.ctx).to_owned(), - "cannot write after shutdown".to_owned(), - )); + return Err(vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + "cannot write after shutdown", + ) + .upcast()); } { @@ -3511,7 +3536,9 @@ mod _ssl { Ok(_) => {} Err(e) => { if is_blocking_io_error(&e, vm) { - return Err(create_ssl_want_write_error(vm)); + return Err( + create_ssl_want_write_error(vm).upcast() + ); } return Err(e); } @@ -3902,7 +3929,7 @@ mod _ssl { // Still waiting for peer's close-notify // Raise SSLWantReadError to signal app needs to transfer data // This is correct for non-blocking sockets and BIO mode - return Err(create_ssl_want_read_error(vm)); + return Err(create_ssl_want_read_error(vm).upcast()); } // Both close-notify exchanged, shutdown complete *self.shutdown_state.lock() = ShutdownState::Completed; @@ -4066,15 +4093,17 @@ mod _ssl { // The rustls TLS library does not support requesting client certificates // after the initial handshake is completed. // Raise SSLError instead of NotImplementedError for compatibility - Err(vm.new_exception_msg( - PySSLError::class(&vm.ctx).to_owned(), - "Post-handshake authentication is not supported by the rustls backend. \ + Err(vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + "Post-handshake authentication is not supported by the rustls backend. \ The rustls TLS library does not provide an API to request client certificates \ after the initial handshake. Consider requesting the client certificate \ during the initial handshake by setting the appropriate verify_mode before \ - calling do_handshake()." - .to_owned(), - )) + calling do_handshake().", + ) + .upcast()) } #[pymethod] diff --git a/crates/stdlib/src/ssl/compat.rs b/crates/stdlib/src/ssl/compat.rs index a55b3058884..798542f210a 100644 --- a/crates/stdlib/src/ssl/compat.rs +++ b/crates/stdlib/src/ssl/compat.rs @@ -222,7 +222,11 @@ pub(super) fn create_ssl_cert_verification_error( let msg = format!("[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: {verify_message}",); - let exc = vm.new_exception_msg(PySSLCertVerificationError::class(&vm.ctx).to_owned(), msg); + let exc = vm.new_os_subtype_error( + PySSLCertVerificationError::class(&vm.ctx).to_owned(), + None, + msg, + ); // Set verify_code and verify_message attributes // Ignore errors as they're extremely rare (e.g., out of memory) @@ -248,7 +252,7 @@ pub(super) fn create_ssl_cert_verification_error( vm, )?; - Ok(exc) + Ok(exc.upcast()) } /// Unified TLS connection type (client or server) @@ -481,10 +485,7 @@ impl SslError { let msg = message.into(); // SSLError args should be (errno, message) format // FIXME: Use 1 as generic SSL error code - let exc = vm.new_exception( - PySSLError::class(&vm.ctx).to_owned(), - vec![vm.new_pyobj(1i32), vm.new_pyobj(msg)], - ); + let exc = vm.new_os_subtype_error(PySSLError::class(&vm.ctx).to_owned(), Some(1), msg); // Set library and reason attributes // Ignore errors as they're extremely rare (e.g., out of memory) @@ -497,7 +498,7 @@ impl SslError { exc.as_object() .set_attr("reason", vm.ctx.new_str(reason).as_object().to_owned(), vm); - exc + exc.upcast() } /// Create SSLError with library and reason from ssl_data codes @@ -542,19 +543,22 @@ impl SslError { /// Convert to Python exception pub fn into_py_err(self, vm: &VirtualMachine) -> PyBaseExceptionRef { match self { - SslError::WantRead => create_ssl_want_read_error(vm), - SslError::WantWrite => create_ssl_want_write_error(vm), - SslError::Timeout(msg) => timeout_error_msg(vm, msg), + SslError::WantRead => create_ssl_want_read_error(vm).upcast(), + SslError::WantWrite => create_ssl_want_write_error(vm).upcast(), + SslError::Timeout(msg) => timeout_error_msg(vm, msg).upcast(), SslError::Syscall(msg) => { // Create SSLError with library=None for syscall errors during SSL operations Self::create_ssl_error_with_reason(vm, None, &msg, msg.clone()) } - SslError::Ssl(msg) => vm.new_exception_msg( - PySSLError::class(&vm.ctx).to_owned(), - format!("SSL error: {msg}"), - ), - SslError::ZeroReturn => create_ssl_zero_return_error(vm), - SslError::Eof => create_ssl_eof_error(vm), + SslError::Ssl(msg) => vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + format!("SSL error: {msg}"), + ) + .upcast(), + SslError::ZeroReturn => create_ssl_zero_return_error(vm).upcast(), + SslError::Eof => create_ssl_eof_error(vm).upcast(), SslError::CertVerification(cert_err) => { // Use the proper cert verification error creator create_ssl_cert_verification_error(vm, &cert_err).expect("unlikely to happen") diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 9b519dbbde5..6b3ddd8241a 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -1,8 +1,7 @@ use super::{PyInt, PyStrRef, PyType, PyTypeRef}; use crate::common::format::FormatSpec; use crate::{ - AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, - VirtualMachine, + AsObject, Context, Py, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, VirtualMachine, class::PyClassImpl, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{FuncArgs, OptionalArg}, @@ -21,10 +20,15 @@ impl ToPyObject for bool { impl<'a> TryFromBorrowedObject<'a> for bool { fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> { - if obj.fast_isinstance(vm.ctx.types.int_type) { - Ok(get_value(obj)) - } else { - Err(vm.new_type_error(format!("Expected type bool, not {}", obj.class().name()))) + // Python takes integers as a legit bool value + match obj.downcast_ref::<PyInt>() { + Some(int_obj) => { + let int_val = int_obj.as_bigint(); + Ok(!int_val.is_zero()) + } + None => { + Err(vm.new_type_error(format!("Expected type bool, not {}", obj.class().name()))) + } } } } @@ -81,19 +85,14 @@ impl PyObjectRef { } } -#[pyclass(name = "bool", module = false, base = PyInt)] -pub struct PyBool; - -impl PyPayload for PyBool { - #[inline] - fn class(ctx: &Context) -> &'static Py<PyType> { - ctx.types.bool_type - } -} +#[pyclass(name = "bool", module = false, base = PyInt, ctx = "bool_type")] +#[repr(transparent)] +pub struct PyBool(pub PyInt); impl Debug for PyBool { - fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { - todo!() + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let value = !self.0.as_bigint().is_zero(); + write!(f, "PyBool({})", value) } } @@ -221,5 +220,9 @@ pub(crate) fn init(context: &Context) { // Retrieve inner int value: pub(crate) fn get_value(obj: &PyObject) -> bool { - !obj.downcast_ref::<PyInt>().unwrap().as_bigint().is_zero() + !obj.downcast_ref::<PyBool>() + .unwrap() + .0 + .as_bigint() + .is_zero() } diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index b210d41823d..8fe85267cd0 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -256,7 +256,7 @@ impl PyInt { if cls.is(vm.ctx.types.int_type) { Ok(vm.ctx.new_int(value)) } else if cls.is(vm.ctx.types.bool_type) { - Ok(vm.ctx.new_bool(!value.into().eq(&BigInt::zero()))) + Ok(vm.ctx.new_bool(!value.into().eq(&BigInt::zero())).upcast()) } else { Self::from(value).into_ref_with_type(vm, cls) } diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 8850de36152..6b5a02dea73 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -171,3 +171,18 @@ pub trait PyClassImpl: PyClassDef { slots } } + +/// Trait for Python subclasses that can provide a reference to their base type. +/// +/// This trait is automatically implemented by the `#[pyclass]` macro when +/// `base = SomeType` is specified. It provides safe reference access to the +/// base type's payload. +/// +/// For subclasses with `#[repr(transparent)]` +/// which enables ownership transfer via `into_base()`. +pub trait PySubclass: crate::PyPayload { + type Base: crate::PyPayload; + + /// Returns a reference to the base type's payload. + fn as_base(&self) -> &Self::Base; +} diff --git a/crates/vm/src/exception_group.rs b/crates/vm/src/exception_group.rs index 5eb011960e1..8d033b26110 100644 --- a/crates/vm/src/exception_group.rs +++ b/crates/vm/src/exception_group.rs @@ -46,7 +46,8 @@ pub(super) mod types { #[pyexception(name, base = PyBaseException, ctx = "base_exception_group")] #[derive(Debug)] - pub struct PyBaseExceptionGroup {} + #[repr(transparent)] + pub struct PyBaseExceptionGroup(PyBaseException); #[pyexception] impl PyBaseExceptionGroup { diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 4a8bea23557..04dd78fb448 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -964,32 +964,8 @@ impl ExceptionZoo { extend_exception!(PyUnboundLocalError, ctx, excs.unbound_local_error); // os errors: - let errno_getter = - ctx.new_readonly_getset("errno", excs.os_error, |exc: PyBaseExceptionRef| { - let args = exc.args(); - args.first() - .filter(|_| args.len() > 1 && args.len() <= 5) - .cloned() - }); - let strerror_getter = - ctx.new_readonly_getset("strerror", excs.os_error, |exc: PyBaseExceptionRef| { - let args = exc.args(); - args.get(1) - .filter(|_| args.len() >= 2 && args.len() <= 5) - .cloned() - }); - extend_exception!(PyOSError, ctx, excs.os_error, { - // POSIX exception code - "errno" => errno_getter.clone(), - // exception strerror - "strerror" => strerror_getter.clone(), - // exception filename - "filename" => ctx.none(), - // second exception filename - "filename2" => ctx.none(), - }); - #[cfg(windows)] - excs.os_error.set_str_attr("winerror", ctx.none(), ctx); + // PyOSError now uses struct fields with pygetset, no need for dynamic attributes + extend_exception!(PyOSError, ctx, excs.os_error); extend_exception!(PyBlockingIOError, ctx, excs.blocking_io_error); extend_exception!(PyChildProcessError, ctx, excs.child_process_error); @@ -1219,11 +1195,14 @@ pub(crate) fn errno_to_exc_type(_errno: i32, _vm: &VirtualMachine) -> Option<&'s pub(super) mod types { use crate::common::lock::PyRwLock; + use crate::object::{MaybeTraverse, Traverse, TraverseFn}; #[cfg_attr(target_arch = "wasm32", allow(unused_imports))] use crate::{ - AsObject, PyObjectRef, PyRef, PyResult, VirtualMachine, + AsObject, Py, PyAtomicRef, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, + VirtualMachine, builtins::{ - PyInt, PyStrRef, PyTupleRef, PyTypeRef, traceback::PyTracebackRef, tuple::IntoPyTuple, + PyInt, PyStrRef, PyTupleRef, PyType, PyTypeRef, traceback::PyTracebackRef, + tuple::IntoPyTuple, }, convert::ToPyResult, function::{ArgBytesLike, FuncArgs}, @@ -1255,23 +1234,28 @@ pub(super) mod types { #[pyexception(name, base = PyBaseException, ctx = "system_exit", impl)] #[derive(Debug)] - pub struct PySystemExit {} + #[repr(transparent)] + pub struct PySystemExit(PyBaseException); #[pyexception(name, base = PyBaseException, ctx = "generator_exit", impl)] #[derive(Debug)] - pub struct PyGeneratorExit {} + #[repr(transparent)] + pub struct PyGeneratorExit(PyBaseException); #[pyexception(name, base = PyBaseException, ctx = "keyboard_interrupt", impl)] #[derive(Debug)] - pub struct PyKeyboardInterrupt {} + #[repr(transparent)] + pub struct PyKeyboardInterrupt(PyBaseException); #[pyexception(name, base = PyBaseException, ctx = "exception_type", impl)] #[derive(Debug)] - pub struct PyException {} + #[repr(transparent)] + pub struct PyException(PyBaseException); #[pyexception(name, base = PyException, ctx = "stop_iteration")] #[derive(Debug)] - pub struct PyStopIteration {} + #[repr(transparent)] + pub struct PyStopIteration(PyException); #[pyexception] impl PyStopIteration { @@ -1289,31 +1273,37 @@ pub(super) mod types { #[pyexception(name, base = PyException, ctx = "stop_async_iteration", impl)] #[derive(Debug)] - pub struct PyStopAsyncIteration {} + #[repr(transparent)] + pub struct PyStopAsyncIteration(PyException); #[pyexception(name, base = PyException, ctx = "arithmetic_error", impl)] #[derive(Debug)] - pub struct PyArithmeticError {} + #[repr(transparent)] + pub struct PyArithmeticError(PyException); #[pyexception(name, base = PyArithmeticError, ctx = "floating_point_error", impl)] #[derive(Debug)] - pub struct PyFloatingPointError {} - + #[repr(transparent)] + pub struct PyFloatingPointError(PyArithmeticError); #[pyexception(name, base = PyArithmeticError, ctx = "overflow_error", impl)] #[derive(Debug)] - pub struct PyOverflowError {} + #[repr(transparent)] + pub struct PyOverflowError(PyArithmeticError); #[pyexception(name, base = PyArithmeticError, ctx = "zero_division_error", impl)] #[derive(Debug)] - pub struct PyZeroDivisionError {} + #[repr(transparent)] + pub struct PyZeroDivisionError(PyArithmeticError); #[pyexception(name, base = PyException, ctx = "assertion_error", impl)] #[derive(Debug)] - pub struct PyAssertionError {} + #[repr(transparent)] + pub struct PyAssertionError(PyException); #[pyexception(name, base = PyException, ctx = "attribute_error")] #[derive(Debug)] - pub struct PyAttributeError {} + #[repr(transparent)] + pub struct PyAttributeError(PyException); #[pyexception] impl PyAttributeError { @@ -1340,15 +1330,18 @@ pub(super) mod types { #[pyexception(name, base = PyException, ctx = "buffer_error", impl)] #[derive(Debug)] - pub struct PyBufferError {} + #[repr(transparent)] + pub struct PyBufferError(PyException); #[pyexception(name, base = PyException, ctx = "eof_error", impl)] #[derive(Debug)] - pub struct PyEOFError {} + #[repr(transparent)] + pub struct PyEOFError(PyException); #[pyexception(name, base = PyException, ctx = "import_error")] #[derive(Debug)] - pub struct PyImportError {} + #[repr(transparent)] + pub struct PyImportError(PyException); #[pyexception] impl PyImportError { @@ -1393,19 +1386,23 @@ pub(super) mod types { #[pyexception(name, base = PyImportError, ctx = "module_not_found_error", impl)] #[derive(Debug)] - pub struct PyModuleNotFoundError {} + #[repr(transparent)] + pub struct PyModuleNotFoundError(PyImportError); #[pyexception(name, base = PyException, ctx = "lookup_error", impl)] #[derive(Debug)] - pub struct PyLookupError {} + #[repr(transparent)] + pub struct PyLookupError(PyException); #[pyexception(name, base = PyLookupError, ctx = "index_error", impl)] #[derive(Debug)] - pub struct PyIndexError {} + #[repr(transparent)] + pub struct PyIndexError(PyLookupError); #[pyexception(name, base = PyLookupError, ctx = "key_error")] #[derive(Debug)] - pub struct PyKeyError {} + #[repr(transparent)] + pub struct PyKeyError(PyLookupError); #[pyexception] impl PyKeyError { @@ -1425,80 +1422,180 @@ pub(super) mod types { #[pyexception(name, base = PyException, ctx = "memory_error", impl)] #[derive(Debug)] - pub struct PyMemoryError {} + #[repr(transparent)] + pub struct PyMemoryError(PyException); #[pyexception(name, base = PyException, ctx = "name_error", impl)] #[derive(Debug)] - pub struct PyNameError {} + #[repr(transparent)] + pub struct PyNameError(PyException); #[pyexception(name, base = PyNameError, ctx = "unbound_local_error", impl)] #[derive(Debug)] - pub struct PyUnboundLocalError {} + #[repr(transparent)] + pub struct PyUnboundLocalError(PyNameError); #[pyexception(name, base = PyException, ctx = "os_error")] - #[derive(Debug)] - pub struct PyOSError {} + pub struct PyOSError { + base: PyException, + errno: PyAtomicRef<Option<PyObject>>, + strerror: PyAtomicRef<Option<PyObject>>, + filename: PyAtomicRef<Option<PyObject>>, + filename2: PyAtomicRef<Option<PyObject>>, + #[cfg(windows)] + winerror: PyAtomicRef<Option<PyObject>>, + } + + impl crate::class::PySubclass for PyOSError { + type Base = PyException; + fn as_base(&self) -> &Self::Base { + &self.base + } + } + + impl std::fmt::Debug for PyOSError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyOSError").finish_non_exhaustive() + } + } + + unsafe impl Traverse for PyOSError { + fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { + self.base.try_traverse(tracer_fn); + if let Some(obj) = self.errno.deref() { + tracer_fn(obj); + } + if let Some(obj) = self.strerror.deref() { + tracer_fn(obj); + } + if let Some(obj) = self.filename.deref() { + tracer_fn(obj); + } + if let Some(obj) = self.filename2.deref() { + tracer_fn(obj); + } + #[cfg(windows)] + if let Some(obj) = self.winerror.deref() { + tracer_fn(obj); + } + } + } // OS Errors: - #[pyexception] - impl PyOSError { - #[cfg(not(target_arch = "wasm32"))] - fn optional_new(args: Vec<PyObjectRef>, vm: &VirtualMachine) -> Option<PyBaseExceptionRef> { - let len = args.len(); - if (2..=5).contains(&len) { - let errno = &args[0]; - errno - .downcast_ref::<PyInt>() - .and_then(|errno| errno.try_to_primitive::<i32>(vm).ok()) - .and_then(|errno| super::errno_to_exc_type(errno, vm)) - .and_then(|typ| vm.invoke_exception(typ.to_owned(), args.to_vec()).ok()) + impl Constructor for PyOSError { + type Args = FuncArgs; + + fn py_new(_cls: &Py<PyType>, args: FuncArgs, vm: &VirtualMachine) -> PyResult<Self> { + let len = args.args.len(); + // CPython only sets errno/strerror when args len is 2-5 + let (errno, strerror) = if (2..=5).contains(&len) { + (Some(args.args[0].clone()), Some(args.args[1].clone())) + } else { + (None, None) + }; + let filename = if (3..=5).contains(&len) { + Some(args.args[2].clone()) } else { None - } + }; + let filename2 = if len == 5 { + args.args.get(4).cloned() + } else { + None + }; + // Truncate args for base exception when 3-5 args + let base_args = if (3..=5).contains(&len) { + args.args[..2].to_vec() + } else { + args.args.to_vec() + }; + let base_exception = PyBaseException::new(base_args, vm); + Ok(Self { + base: PyException(base_exception), + errno: errno.into(), + strerror: strerror.into(), + filename: filename.into(), + filename2: filename2.into(), + #[cfg(windows)] + winerror: None.into(), + }) } - #[cfg(not(target_arch = "wasm32"))] - #[pyslot] - pub fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { // We need this method, because of how `CPython` copies `init` // from `BaseException` in `SimpleExtendsException` macro. // See: `BaseException_new` if *cls.name() == *vm.ctx.exceptions.os_error.name() { - match Self::optional_new(args.args.to_vec(), vm) { - Some(error) => error.to_pyresult(vm), - None => PyBaseException::slot_new(cls, args, vm), + let args_vec = args.args.to_vec(); + let len = args_vec.len(); + if (2..=5).contains(&len) { + let errno = &args_vec[0]; + if let Some(error) = errno + .downcast_ref::<PyInt>() + .and_then(|errno| errno.try_to_primitive::<i32>(vm).ok()) + .and_then(|errno| super::errno_to_exc_type(errno, vm)) + .and_then(|typ| vm.invoke_exception(typ.to_owned(), args_vec).ok()) + { + return error.to_pyresult(vm); + } } - } else { - PyBaseException::slot_new(cls, args, vm) } + let payload = Self::py_new(&cls, args, vm)?; + payload.into_ref_with_type(vm, cls).map(Into::into) } - #[cfg(target_arch = "wasm32")] - #[pyslot] - pub fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - PyBaseException::slot_new(cls, args, vm) - } + } + + #[pyexception(with(Constructor))] + impl PyOSError { #[pyslot] #[pymethod(name = "__init__")] pub fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { let len = args.args.len(); let mut new_args = args; - if (3..=5).contains(&len) { - zelf.set_attr("filename", new_args.args[2].clone(), vm)?; + + // All OSError subclasses use #[repr(transparent)] wrapping PyOSError, + // so we can safely access the PyOSError fields through pointer cast + // SAFETY: All OSError subclasses (FileNotFoundError, etc.) are + // #[repr(transparent)] wrappers around PyOSError with identical memory layout + #[allow(deprecated)] + let exc: &Py<PyOSError> = zelf.downcast_ref::<PyOSError>().unwrap(); + + // SAFETY: slot_init is called during object initialization, + // so fields are None and swap result can be safely ignored + if len <= 5 { + // Only set errno/strerror when args len is 2-5 (CPython behavior) + if 2 <= len { + let _ = unsafe { exc.errno.swap(Some(new_args.args[0].clone())) }; + let _ = unsafe { exc.strerror.swap(Some(new_args.args[1].clone())) }; + } + if 3 <= len { + let _ = unsafe { exc.filename.swap(Some(new_args.args[2].clone())) }; + } #[cfg(windows)] - if let Some(winerror) = new_args.args.get(3) { - zelf.set_attr("winerror", winerror.clone(), vm)?; - // Convert winerror to errno and replace args[0] (CPython behavior) - if let Some(winerror_int) = winerror - .downcast_ref::<crate::builtins::PyInt>() + if 4 <= len { + let winerror = new_args.args.get(3).cloned(); + // Store original winerror + let _ = unsafe { exc.winerror.swap(winerror.clone()) }; + + // Convert winerror to errno and update errno + args[0] + if let Some(errno) = winerror + .as_ref() + .and_then(|w| w.downcast_ref::<crate::builtins::PyInt>()) .and_then(|w| w.try_to_primitive::<i32>(vm).ok()) + .map(crate::common::os::winerror_to_errno) { - let errno = crate::common::os::winerror_to_errno(winerror_int); - new_args.args[0] = vm.new_pyobj(errno); + let errno_obj = vm.new_pyobj(errno); + let _ = unsafe { exc.errno.swap(Some(errno_obj.clone())) }; + new_args.args[0] = errno_obj; } } - if let Some(filename2) = new_args.args.get(4) { - zelf.set_attr("filename2", filename2.clone(), vm)?; + if len == 5 { + let _ = unsafe { exc.filename2.swap(new_args.args.get(4).cloned()) }; } + } + // args are truncated to 2 for compatibility (only when 2-5 args) + if (3..=5).contains(&len) { new_args.args.truncate(2); } PyBaseException::slot_init(zelf, new_args, vm) @@ -1582,23 +1679,80 @@ pub(super) mod types { } result.into_pytuple(vm) } + + // Getters and setters for OSError fields + #[pygetset] + fn errno(&self) -> Option<PyObjectRef> { + self.errno.to_owned() + } + + #[pygetset(setter)] + fn set_errno(&self, value: Option<PyObjectRef>, vm: &VirtualMachine) { + self.errno.swap_to_temporary_refs(value, vm); + } + + #[pygetset(name = "strerror")] + fn get_strerror(&self) -> Option<PyObjectRef> { + self.strerror.to_owned() + } + + #[pygetset(setter, name = "strerror")] + fn set_strerror(&self, value: Option<PyObjectRef>, vm: &VirtualMachine) { + self.strerror.swap_to_temporary_refs(value, vm); + } + + #[pygetset] + fn filename(&self) -> Option<PyObjectRef> { + self.filename.to_owned() + } + + #[pygetset(setter)] + fn set_filename(&self, value: Option<PyObjectRef>, vm: &VirtualMachine) { + self.filename.swap_to_temporary_refs(value, vm); + } + + #[pygetset] + fn filename2(&self) -> Option<PyObjectRef> { + self.filename2.to_owned() + } + + #[pygetset(setter)] + fn set_filename2(&self, value: Option<PyObjectRef>, vm: &VirtualMachine) { + self.filename2.swap_to_temporary_refs(value, vm); + } + + #[cfg(windows)] + #[pygetset] + fn winerror(&self) -> Option<PyObjectRef> { + self.winerror.to_owned() + } + + #[cfg(windows)] + #[pygetset(setter)] + fn set_winerror(&self, value: Option<PyObjectRef>, vm: &VirtualMachine) { + self.winerror.swap_to_temporary_refs(value, vm); + } } #[pyexception(name, base = PyOSError, ctx = "blocking_io_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyBlockingIOError {} + pub struct PyBlockingIOError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "child_process_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyChildProcessError {} + pub struct PyChildProcessError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "connection_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyConnectionError {} + pub struct PyConnectionError(PyOSError); #[pyexception(name, base = PyConnectionError, ctx = "broken_pipe_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyBrokenPipeError {} + pub struct PyBrokenPipeError(PyConnectionError); #[pyexception( name, @@ -1606,8 +1760,9 @@ pub(super) mod types { ctx = "connection_aborted_error", impl )] + #[repr(transparent)] #[derive(Debug)] - pub struct PyConnectionAbortedError {} + pub struct PyConnectionAbortedError(PyConnectionError); #[pyexception( name, @@ -1615,64 +1770,79 @@ pub(super) mod types { ctx = "connection_refused_error", impl )] + #[repr(transparent)] #[derive(Debug)] - pub struct PyConnectionRefusedError {} + pub struct PyConnectionRefusedError(PyConnectionError); #[pyexception(name, base = PyConnectionError, ctx = "connection_reset_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyConnectionResetError {} + pub struct PyConnectionResetError(PyConnectionError); #[pyexception(name, base = PyOSError, ctx = "file_exists_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyFileExistsError {} + pub struct PyFileExistsError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "file_not_found_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyFileNotFoundError {} + pub struct PyFileNotFoundError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "interrupted_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyInterruptedError {} + pub struct PyInterruptedError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "is_a_directory_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyIsADirectoryError {} + pub struct PyIsADirectoryError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "not_a_directory_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyNotADirectoryError {} + pub struct PyNotADirectoryError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "permission_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyPermissionError {} + pub struct PyPermissionError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "process_lookup_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyProcessLookupError {} + pub struct PyProcessLookupError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "timeout_error", impl)] #[derive(Debug)] - pub struct PyTimeoutError {} + #[repr(transparent)] + pub struct PyTimeoutError(PyOSError); #[pyexception(name, base = PyException, ctx = "reference_error", impl)] #[derive(Debug)] - pub struct PyReferenceError {} + #[repr(transparent)] + pub struct PyReferenceError(PyException); #[pyexception(name, base = PyException, ctx = "runtime_error", impl)] #[derive(Debug)] - pub struct PyRuntimeError {} + #[repr(transparent)] + pub struct PyRuntimeError(PyException); #[pyexception(name, base = PyRuntimeError, ctx = "not_implemented_error", impl)] #[derive(Debug)] - pub struct PyNotImplementedError {} + #[repr(transparent)] + pub struct PyNotImplementedError(PyRuntimeError); #[pyexception(name, base = PyRuntimeError, ctx = "recursion_error", impl)] #[derive(Debug)] - pub struct PyRecursionError {} + #[repr(transparent)] + pub struct PyRecursionError(PyRuntimeError); #[pyexception(name, base = PyException, ctx = "syntax_error")] #[derive(Debug)] - pub struct PySyntaxError {} + #[repr(transparent)] + pub struct PySyntaxError(PyException); #[pyexception] impl PySyntaxError { @@ -1766,7 +1936,8 @@ pub(super) mod types { ctx = "incomplete_input_error" )] #[derive(Debug)] - pub struct PyIncompleteInputError {} + #[repr(transparent)] + pub struct PyIncompleteInputError(PySyntaxError); #[pyexception] impl PyIncompleteInputError { @@ -1784,31 +1955,38 @@ pub(super) mod types { #[pyexception(name, base = PySyntaxError, ctx = "indentation_error", impl)] #[derive(Debug)] - pub struct PyIndentationError {} + #[repr(transparent)] + pub struct PyIndentationError(PySyntaxError); #[pyexception(name, base = PyIndentationError, ctx = "tab_error", impl)] #[derive(Debug)] - pub struct PyTabError {} + #[repr(transparent)] + pub struct PyTabError(PyIndentationError); #[pyexception(name, base = PyException, ctx = "system_error", impl)] #[derive(Debug)] - pub struct PySystemError {} + #[repr(transparent)] + pub struct PySystemError(PyException); #[pyexception(name, base = PyException, ctx = "type_error", impl)] #[derive(Debug)] - pub struct PyTypeError {} + #[repr(transparent)] + pub struct PyTypeError(PyException); #[pyexception(name, base = PyException, ctx = "value_error", impl)] #[derive(Debug)] - pub struct PyValueError {} + #[repr(transparent)] + pub struct PyValueError(PyException); #[pyexception(name, base = PyValueError, ctx = "unicode_error", impl)] #[derive(Debug)] - pub struct PyUnicodeError {} + #[repr(transparent)] + pub struct PyUnicodeError(PyValueError); #[pyexception(name, base = PyUnicodeError, ctx = "unicode_decode_error")] #[derive(Debug)] - pub struct PyUnicodeDecodeError {} + #[repr(transparent)] + pub struct PyUnicodeDecodeError(PyUnicodeError); #[pyexception] impl PyUnicodeDecodeError { @@ -1859,7 +2037,8 @@ pub(super) mod types { #[pyexception(name, base = PyUnicodeError, ctx = "unicode_encode_error")] #[derive(Debug)] - pub struct PyUnicodeEncodeError {} + #[repr(transparent)] + pub struct PyUnicodeEncodeError(PyUnicodeError); #[pyexception] impl PyUnicodeEncodeError { @@ -1910,7 +2089,8 @@ pub(super) mod types { #[pyexception(name, base = PyUnicodeError, ctx = "unicode_translate_error")] #[derive(Debug)] - pub struct PyUnicodeTranslateError {} + #[repr(transparent)] + pub struct PyUnicodeTranslateError(PyUnicodeError); #[pyexception] impl PyUnicodeTranslateError { @@ -1958,54 +2138,67 @@ pub(super) mod types { #[cfg(feature = "jit")] #[pyexception(name, base = PyException, ctx = "jit_error", impl)] #[derive(Debug)] - pub struct PyJitError {} + #[repr(transparent)] + pub struct PyJitError(PyException); // Warnings #[pyexception(name, base = PyException, ctx = "warning", impl)] #[derive(Debug)] - pub struct PyWarning {} + #[repr(transparent)] + pub struct PyWarning(PyException); #[pyexception(name, base = PyWarning, ctx = "deprecation_warning", impl)] #[derive(Debug)] - pub struct PyDeprecationWarning {} + #[repr(transparent)] + pub struct PyDeprecationWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "pending_deprecation_warning", impl)] #[derive(Debug)] - pub struct PyPendingDeprecationWarning {} + #[repr(transparent)] + pub struct PyPendingDeprecationWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "runtime_warning", impl)] #[derive(Debug)] - pub struct PyRuntimeWarning {} + #[repr(transparent)] + pub struct PyRuntimeWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "syntax_warning", impl)] #[derive(Debug)] - pub struct PySyntaxWarning {} + #[repr(transparent)] + pub struct PySyntaxWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "user_warning", impl)] #[derive(Debug)] - pub struct PyUserWarning {} + #[repr(transparent)] + pub struct PyUserWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "future_warning", impl)] #[derive(Debug)] - pub struct PyFutureWarning {} + #[repr(transparent)] + pub struct PyFutureWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "import_warning", impl)] #[derive(Debug)] - pub struct PyImportWarning {} + #[repr(transparent)] + pub struct PyImportWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "unicode_warning", impl)] #[derive(Debug)] - pub struct PyUnicodeWarning {} + #[repr(transparent)] + pub struct PyUnicodeWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "bytes_warning", impl)] #[derive(Debug)] - pub struct PyBytesWarning {} + #[repr(transparent)] + pub struct PyBytesWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "resource_warning", impl)] #[derive(Debug)] - pub struct PyResourceWarning {} + #[repr(transparent)] + pub struct PyResourceWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "encoding_warning", impl)] #[derive(Debug)] - pub struct PyEncodingWarning {} + #[repr(transparent)] + pub struct PyEncodingWarning(PyWarning); } diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 6530abdbaba..e04b87de594 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -15,7 +15,6 @@ use super::{ ext::{AsObject, PyRefExact, PyResult}, payload::PyPayload, }; -use crate::object::traverse::{MaybeTraverse, Traverse, TraverseFn}; use crate::object::traverse_object::PyObjVTable; use crate::{ builtins::{PyDictRef, PyType, PyTypeRef}, @@ -27,6 +26,10 @@ use crate::{ }, vm::VirtualMachine, }; +use crate::{ + class::StaticType, + object::traverse::{MaybeTraverse, Traverse, TraverseFn}, +}; use itertools::Itertools; use std::{ any::TypeId, @@ -1070,6 +1073,35 @@ impl<T: PyPayload + std::fmt::Debug> PyRef<T> { } } +impl<T: crate::class::PySubclass + std::fmt::Debug> PyRef<T> +where + T::Base: std::fmt::Debug, +{ + /// Converts this reference to the base type (ownership transfer). + /// # Safety + /// T and T::Base must have compatible layouts in size_of::<T::Base>() bytes. + #[inline] + pub fn into_base(self) -> PyRef<T::Base> { + let obj: PyObjectRef = self.into(); + match obj.downcast() { + Ok(base_ref) => base_ref, + Err(_) => unsafe { std::hint::unreachable_unchecked() }, + } + } + #[inline] + pub fn upcast<U: PyPayload + StaticType>(self) -> PyRef<U> + where + T: StaticType, + { + debug_assert!(T::static_type().is_subtype(U::static_type())); + let obj: PyObjectRef = self.into(); + match obj.downcast::<U>() { + Ok(upcast_ref) => upcast_ref, + Err(_) => unsafe { std::hint::unreachable_unchecked() }, + } + } +} + impl<T> Borrow<PyObject> for PyRef<T> where T: PyPayload, diff --git a/crates/vm/src/object/payload.rs b/crates/vm/src/object/payload.rs index b2211761492..cf903871179 100644 --- a/crates/vm/src/object/payload.rs +++ b/crates/vm/src/object/payload.rs @@ -16,6 +16,15 @@ cfg_if::cfg_if! { } } +#[cold] +pub(crate) fn cold_downcast_type_error( + vm: &VirtualMachine, + class: &Py<PyType>, + obj: &PyObject, +) -> PyBaseExceptionRef { + vm.new_downcast_type_error(class, obj) +} + pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { #[inline] fn payload_type_id() -> std::any::TypeId { @@ -38,17 +47,8 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { return Ok(()); } - #[cold] - fn raise_downcast_type_error( - vm: &VirtualMachine, - class: &Py<PyType>, - obj: &PyObject, - ) -> PyBaseExceptionRef { - vm.new_downcast_type_error(class, obj) - } - let class = Self::class(&vm.ctx); - Err(raise_downcast_type_error(vm, class, obj)) + Err(cold_downcast_type_error(vm, class, obj)) } fn class(ctx: &Context) -> &'static Py<PyType>; @@ -101,6 +101,22 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { { let exact_class = Self::class(&vm.ctx); if cls.fast_issubclass(exact_class) { + if exact_class.slots.basicsize != cls.slots.basicsize { + #[cold] + #[inline(never)] + fn _into_ref_size_error( + vm: &VirtualMachine, + cls: &PyTypeRef, + exact_class: &Py<PyType>, + ) -> PyBaseExceptionRef { + vm.new_type_error(format!( + "cannot create '{}' instance: size differs from base type '{}'", + cls.name(), + exact_class.name() + )) + } + return Err(_into_ref_size_error(vm, &cls, exact_class)); + } Ok(self._into_ref(cls, &vm.ctx)) } else { #[cold] diff --git a/crates/vm/src/stdlib/ast/pyast.rs b/crates/vm/src/stdlib/ast/pyast.rs index b891a605dc2..e36635fe4b9 100644 --- a/crates/vm/src/stdlib/ast/pyast.rs +++ b/crates/vm/src/stdlib/ast/pyast.rs @@ -5,13 +5,14 @@ use crate::common::ascii; macro_rules! impl_node { ( - $(#[$meta:meta])* + #[pyclass(module = $_mod:literal, name = $_name:literal, base = $base:ty)] $vis:vis struct $name:ident, fields: [$($field:expr),* $(,)?], attributes: [$($attr:expr),* $(,)?] $(,)? ) => { - $(#[$meta])* - $vis struct $name; + #[pyclass(module = $_mod, name = $_name, base = $base)] + #[repr(transparent)] + $vis struct $name($base); #[pyclass(flags(HAS_DICT, BASETYPE))] impl $name { @@ -39,12 +40,12 @@ macro_rules! impl_node { }; // Without attributes ( - $(#[$meta:meta])* + #[pyclass(module = $_mod:literal, name = $_name:literal, base = $base:ty)] $vis:vis struct $name:ident, fields: [$($field:expr),* $(,)?] $(,)? ) => { impl_node!( - $(#[$meta])* + #[pyclass(module = $_mod, name = $_name, base = $base)] $vis struct $name, fields: [$($field),*], attributes: [], @@ -52,12 +53,12 @@ macro_rules! impl_node { }; // Without fields ( - $(#[$meta:meta])* + #[pyclass(module = $_mod:literal, name = $_name:literal, base = $base:ty)] $vis:vis struct $name:ident, attributes: [$($attr:expr),* $(,)?] $(,)? ) => { impl_node!( - $(#[$meta])* + #[pyclass(module = $_mod, name = $_name, base = $base)] $vis struct $name, fields: [], attributes: [$($attr),*], @@ -65,11 +66,11 @@ macro_rules! impl_node { }; // Without fields and attributes ( - $(#[$meta:meta])* + #[pyclass(module = $_mod:literal, name = $_name:literal, base = $base:ty)] $vis:vis struct $name:ident $(,)? ) => { impl_node!( - $(#[$meta])* + #[pyclass(module = $_mod, name = $_name, base = $base)] $vis struct $name, fields: [], attributes: [], @@ -78,7 +79,7 @@ macro_rules! impl_node { } #[pyclass(module = "_ast", name = "mod", base = NodeAst)] -pub(crate) struct NodeMod; +pub(crate) struct NodeMod(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeMod {} @@ -102,7 +103,8 @@ impl_node!( ); #[pyclass(module = "_ast", name = "stmt", base = NodeAst)] -pub(crate) struct NodeStmt; +#[repr(transparent)] +pub(crate) struct NodeStmt(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeStmt {} @@ -301,7 +303,8 @@ impl_node!( ); #[pyclass(module = "_ast", name = "expr", base = NodeAst)] -pub(crate) struct NodeExpr; +#[repr(transparent)] +pub(crate) struct NodeExpr(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeExpr {} @@ -495,7 +498,8 @@ impl_node!( ); #[pyclass(module = "_ast", name = "expr_context", base = NodeAst)] -pub(crate) struct NodeExprContext; +#[repr(transparent)] +pub(crate) struct NodeExprContext(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeExprContext {} @@ -518,7 +522,8 @@ impl_node!( ); #[pyclass(module = "_ast", name = "boolop", base = NodeAst)] -pub(crate) struct NodeBoolOp; +#[repr(transparent)] +pub(crate) struct NodeBoolOp(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeBoolOp {} @@ -534,7 +539,8 @@ impl_node!( ); #[pyclass(module = "_ast", name = "operator", base = NodeAst)] -pub(crate) struct NodeOperator; +#[repr(transparent)] +pub(crate) struct NodeOperator(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeOperator {} @@ -605,7 +611,8 @@ impl_node!( ); #[pyclass(module = "_ast", name = "unaryop", base = NodeAst)] -pub(crate) struct NodeUnaryOp; +#[repr(transparent)] +pub(crate) struct NodeUnaryOp(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeUnaryOp {} @@ -631,7 +638,8 @@ impl_node!( ); #[pyclass(module = "_ast", name = "cmpop", base = NodeAst)] -pub(crate) struct NodeCmpOp; +#[repr(transparent)] +pub(crate) struct NodeCmpOp(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeCmpOp {} @@ -692,7 +700,8 @@ impl_node!( ); #[pyclass(module = "_ast", name = "excepthandler", base = NodeAst)] -pub(crate) struct NodeExceptHandler; +#[repr(transparent)] +pub(crate) struct NodeExceptHandler(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeExceptHandler {} @@ -744,7 +753,8 @@ impl_node!( ); #[pyclass(module = "_ast", name = "pattern", base = NodeAst)] -pub(crate) struct NodePattern; +#[repr(transparent)] +pub(crate) struct NodePattern(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodePattern {} @@ -805,7 +815,8 @@ impl_node!( ); #[pyclass(module = "_ast", name = "type_ignore", base = NodeAst)] -pub(crate) struct NodeTypeIgnore; +#[repr(transparent)] +pub(crate) struct NodeTypeIgnore(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeTypeIgnore {} @@ -818,7 +829,8 @@ impl_node!( ); #[pyclass(module = "_ast", name = "type_param", base = NodeAst)] -pub(crate) struct NodeTypeParam; +#[repr(transparent)] +pub(crate) struct NodeTypeParam(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeTypeParam {} diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 2daaa6f3abd..70aee7378d3 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -15,7 +15,7 @@ use crate::builtins::PyModule; use crate::class::PyClassImpl; use crate::{Py, PyRef, VirtualMachine}; -pub use crate::stdlib::ctypes::base::{PyCData, PyCSimple, PyCSimpleType}; +pub use crate::stdlib::ctypes::base::{CDataObject, PyCData, PyCSimple, PyCSimpleType}; pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py<PyModule>) { let ctx = &vm.ctx; @@ -47,7 +47,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { #[pymodule] pub(crate) mod _ctypes { - use super::base::{CDataObject, PyCSimple}; + use super::base::{CDataObject, PyCData, PyCSimple}; use crate::builtins::PyTypeRef; use crate::class::StaticType; use crate::convert::ToPyObject; @@ -369,13 +369,12 @@ pub(crate) mod _ctypes { Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\n a single character string containing one of {SIMPLE_TYPE_CHARS}, currently it is {tp_str}."))) } else { let size = get_size(&tp_str); + let cdata = CDataObject::from_bytes(vec![0u8; size], None); Ok(PyCSimple { + _base: PyCData::new(cdata.clone()), _type_: tp_str, value: AtomicCell::new(vm.ctx.none()), - cdata: rustpython_common::lock::PyRwLock::new(CDataObject::from_bytes( - vec![0u8; size], - None, - )), + cdata: rustpython_common::lock::PyRwLock::new(cdata), }) } } else { diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 98274e388bf..fe12a781d9f 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -23,8 +23,9 @@ use rustpython_vm::stdlib::ctypes::base::PyCData; /// PyCArrayType - metatype for Array types /// CPython stores array info (type, length) in StgInfo via type_data #[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")] -#[derive(Debug, Default, PyPayload)] -pub struct PyCArrayType {} +#[derive(Debug)] +#[repr(transparent)] +pub struct PyCArrayType(PyType); /// Create a new Array type with StgInfo stored in type_data (CPython style) pub fn create_array_type_with_stg_info(stg_info: StgInfo, vm: &VirtualMachine) -> PyResult { @@ -194,11 +195,13 @@ impl PyCArrayType { }; // Create instance + let cdata = CDataObject::from_bytes(data, None); let instance = PyCArray { + _base: PyCData::new(cdata.clone()), typ: PyRwLock::new(element_type), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + cdata: PyRwLock::new(cdata), } .into_pyobject(vm); @@ -235,8 +238,8 @@ impl AsNumber for PyCArrayType { metaclass = "PyCArrayType", module = "_ctypes" )] -#[derive(PyPayload)] pub struct PyCArray { + _base: PyCData, /// Element type - can be a simple type (c_int) or an array type (c_int * 5) pub(super) typ: PyRwLock<PyObjectRef>, pub(super) length: AtomicCell<usize>, @@ -301,11 +304,13 @@ impl Constructor for PyCArray { } } + let cdata = CDataObject::from_bytes(buffer, None); PyCArray { + _base: PyCData::new(cdata.clone()), typ: PyRwLock::new(element_type), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)), + cdata: PyRwLock::new(cdata), } .into_ref_with_type(vm, cls) .map(Into::into) @@ -530,11 +535,13 @@ impl PyCArray { .unwrap_or(0); let element_size = if length > 0 { size / length } else { 0 }; + let cdata = CDataObject::from_bytes(bytes.to_vec(), None); Ok(PyCArray { + _base: PyCData::new(cdata.clone()), typ: PyRwLock::new(element_type.into()), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::from_bytes(bytes.to_vec(), None)), + cdata: PyRwLock::new(cdata), } .into_pyobject(vm)) } @@ -596,14 +603,13 @@ impl PyCArray { .unwrap_or(0); let element_size = if length > 0 { size / length } else { 0 }; + let cdata = CDataObject::from_bytes(data.to_vec(), Some(buffer.obj.clone())); Ok(PyCArray { + _base: PyCData::new(cdata.clone()), typ: PyRwLock::new(element_type.into()), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::from_bytes( - data.to_vec(), - Some(buffer.obj.clone()), - )), + cdata: PyRwLock::new(cdata), } .into_pyobject(vm)) } @@ -656,11 +662,13 @@ impl PyCArray { .unwrap_or(0); let element_size = if length > 0 { size / length } else { 0 }; + let cdata = CDataObject::from_bytes(data.to_vec(), None); Ok(PyCArray { + _base: PyCData::new(cdata.clone()), typ: PyRwLock::new(element_type.into()), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::from_bytes(data.to_vec(), None)), + cdata: PyRwLock::new(cdata), } .into_pyobject(vm)) } @@ -741,11 +749,13 @@ impl PyCArray { let element_size = if length > 0 { size / length } else { 0 }; // Create instance + let cdata = CDataObject::from_bytes(data, None); let instance = PyCArray { + _base: PyCData::new(cdata.clone()), typ: PyRwLock::new(element_type.into()), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + cdata: PyRwLock::new(cdata), } .into_pyobject(vm); diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 80d42fba3ce..a4664ad3671 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -219,6 +219,14 @@ pub struct PyCData { pub cdata: PyRwLock<CDataObject>, } +impl PyCData { + pub fn new(cdata: CDataObject) -> Self { + Self { + cdata: PyRwLock::new(cdata), + } + } +} + #[pyclass(flags(BASETYPE))] impl PyCData { #[pygetset] @@ -228,8 +236,9 @@ impl PyCData { } #[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)] -#[derive(Debug, PyPayload, Default)] -pub struct PyCSimpleType {} +#[derive(Debug)] +#[repr(transparent)] +pub struct PyCSimpleType(PyType); #[pyclass(flags(BASETYPE), with(AsNumber))] impl PyCSimpleType { @@ -411,8 +420,8 @@ impl AsNumber for PyCSimpleType { base = PyCData, metaclass = "PyCSimpleType" )] -#[derive(PyPayload)] pub struct PyCSimple { + pub _base: PyCData, pub _type_: String, pub value: AtomicCell<PyObjectRef>, pub cdata: PyRwLock<CDataObject>, @@ -647,10 +656,12 @@ impl Constructor for PyCSimple { .unwrap_or(false); let buffer = value_to_bytes_endian(&_type_, &value, swapped, vm); + let cdata = CDataObject::from_bytes(buffer, None); PyCSimple { + _base: PyCData::new(cdata.clone()), _type_, value: AtomicCell::new(value), - cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)), + cdata: PyRwLock::new(cdata), } .into_ref_with_type(vm, cls) .map(Into::into) diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index 8d6da5808a7..e760f07d035 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -8,8 +8,9 @@ use super::structure::PyCStructure; use super::union::PyCUnion; #[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")] -#[derive(PyPayload, Debug)] +#[derive(Debug)] pub struct PyCFieldType { + pub _base: PyType, #[allow(dead_code)] pub(super) inner: PyCField, } diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index d202410b14a..b4e600f77ba 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -4,7 +4,7 @@ use crate::builtins::{PyNone, PyStr, PyTuple, PyTupleRef, PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; use crate::stdlib::ctypes::PyCData; -use crate::stdlib::ctypes::base::{PyCSimple, ffi_type_from_str}; +use crate::stdlib::ctypes::base::{CDataObject, PyCSimple, ffi_type_from_str}; use crate::stdlib::ctypes::thunk::PyCThunk; use crate::types::Representable; use crate::types::{Callable, Constructor}; @@ -162,8 +162,8 @@ impl ReturnType for PyNone { } #[pyclass(module = "_ctypes", name = "CFuncPtr", base = PyCData)] -#[derive(PyPayload)] pub struct PyCFuncPtr { + _base: PyCData, pub name: PyRwLock<Option<String>>, pub ptr: PyRwLock<Option<CodePtr>>, #[allow(dead_code)] @@ -194,6 +194,7 @@ impl Constructor for PyCFuncPtr { if args.args.is_empty() { return PyCFuncPtr { + _base: PyCData::new(CDataObject::from_bytes(vec![], None)), ptr: PyRwLock::new(None), needs_free: AtomicCell::new(false), arg_types: PyRwLock::new(None), @@ -212,6 +213,7 @@ impl Constructor for PyCFuncPtr { if let Ok(addr) = first_arg.try_int(vm) { let ptr_val = addr.as_bigint().to_usize().unwrap_or(0); return PyCFuncPtr { + _base: PyCData::new(CDataObject::from_bytes(vec![], None)), ptr: PyRwLock::new(Some(CodePtr(ptr_val as *mut _))), needs_free: AtomicCell::new(false), arg_types: PyRwLock::new(None), @@ -271,6 +273,7 @@ impl Constructor for PyCFuncPtr { }; return PyCFuncPtr { + _base: PyCData::new(CDataObject::from_bytes(vec![], None)), ptr: PyRwLock::new(code_ptr), needs_free: AtomicCell::new(false), arg_types: PyRwLock::new(None), @@ -314,6 +317,7 @@ impl Constructor for PyCFuncPtr { let thunk_ref: PyRef<PyCThunk> = thunk.into_ref(&vm.ctx); return PyCFuncPtr { + _base: PyCData::new(CDataObject::from_bytes(vec![], None)), ptr: PyRwLock::new(Some(code_ptr)), needs_free: AtomicCell::new(true), arg_types: PyRwLock::new(arg_type_vec), diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 156c4e54ee5..735034e7936 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -4,13 +4,14 @@ use rustpython_common::lock::PyRwLock; use crate::builtins::{PyType, PyTypeRef}; use crate::function::FuncArgs; use crate::protocol::PyNumberMethods; -use crate::stdlib::ctypes::PyCData; +use crate::stdlib::ctypes::{CDataObject, PyCData}; use crate::types::{AsNumber, Constructor}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] -#[derive(PyPayload, Debug, Default)] -pub struct PyCPointerType {} +#[derive(Debug)] +#[repr(transparent)] +pub struct PyCPointerType(PyType); #[pyclass(flags(IMMUTABLETYPE), with(AsNumber))] impl PyCPointerType { @@ -60,8 +61,9 @@ impl AsNumber for PyCPointerType { metaclass = "PyCPointerType", module = "_ctypes" )] -#[derive(Debug, PyPayload)] +#[derive(Debug)] pub struct PyCPointer { + _base: PyCData, contents: PyRwLock<PyObjectRef>, } @@ -75,6 +77,7 @@ impl Constructor for PyCPointer { // Create a new PyCPointer instance with the provided value PyCPointer { + _base: PyCData::new(CDataObject::from_bytes(vec![], None)), contents: PyRwLock::new(initial_contents), } .into_ref_with_type(vm, cls) @@ -124,6 +127,7 @@ impl PyCPointer { } // Pointer just stores the address value Ok(PyCPointer { + _base: PyCData::new(CDataObject::from_bytes(vec![], None)), contents: PyRwLock::new(vm.ctx.new_int(address).into()), } .into_ref_with_type(vm, cls)? @@ -168,6 +172,7 @@ impl PyCPointer { let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above")); Ok(PyCPointer { + _base: PyCData::new(CDataObject::from_bytes(vec![], None)), contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()), } .into_ref_with_type(vm, cls)? @@ -204,6 +209,7 @@ impl PyCPointer { let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above")); Ok(PyCPointer { + _base: PyCData::new(CDataObject::from_bytes(vec![], None)), contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()), } .into_ref_with_type(vm, cls)? @@ -259,6 +265,7 @@ impl PyCPointer { // For pointer types, we return a pointer to the symbol address Ok(PyCPointer { + _base: PyCData::new(CDataObject::from_bytes(vec![], None)), contents: PyRwLock::new(vm.ctx.new_int(symbol_address).into()), } .into_ref_with_type(vm, cls)? diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index d3cdea69c72..f32d6865cb6 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -15,8 +15,9 @@ use std::fmt::Debug; /// PyCStructType - metaclass for Structure #[pyclass(name = "PyCStructType", base = PyType, module = "_ctypes")] -#[derive(Debug, PyPayload, Default)] -pub struct PyCStructType {} +#[derive(Debug)] +#[repr(transparent)] +pub struct PyCStructType(PyType); impl Constructor for PyCStructType { type Args = FuncArgs; @@ -218,8 +219,8 @@ pub struct FieldInfo { base = PyCData, metaclass = "PyCStructType" )] -#[derive(PyPayload)] pub struct PyCStructure { + _base: PyCData, /// Common CDataObject for memory buffer pub(super) cdata: PyRwLock<CDataObject>, /// Field information (name -> FieldInfo) @@ -295,8 +296,10 @@ impl Constructor for PyCStructure { // Initialize buffer with zeros let mut stg_info = StgInfo::new(total_size, max_align); stg_info.length = fields_map.len(); + let cdata = CDataObject::from_stg_info(&stg_info); let instance = PyCStructure { - cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)), + _base: PyCData::new(cdata.clone()), + cdata: PyRwLock::new(cdata), fields: PyRwLock::new(fields_map.clone()), }; @@ -364,8 +367,10 @@ impl PyCStructure { }; // Create instance + let cdata = CDataObject::from_bytes(data, None); Ok(PyCStructure { - cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + _base: PyCData::new(cdata.clone()), + cdata: PyRwLock::new(cdata), fields: PyRwLock::new(IndexMap::new()), } .into_ref_with_type(vm, cls)? @@ -415,8 +420,10 @@ impl PyCStructure { let data = bytes[offset..offset + size].to_vec(); // Create instance + let cdata = CDataObject::from_bytes(data, Some(source)); Ok(PyCStructure { - cdata: PyRwLock::new(CDataObject::from_bytes(data, Some(source))), + _base: PyCData::new(cdata.clone()), + cdata: PyRwLock::new(cdata), fields: PyRwLock::new(IndexMap::new()), } .into_ref_with_type(vm, cls)? @@ -458,8 +465,10 @@ impl PyCStructure { let data = source_bytes[offset..offset + size].to_vec(); // Create instance + let cdata = CDataObject::from_bytes(data, None); Ok(PyCStructure { - cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + _base: PyCData::new(cdata.clone()), + cdata: PyRwLock::new(cdata), fields: PyRwLock::new(IndexMap::new()), } .into_ref_with_type(vm, cls)? diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 37d8e4f688b..e6873e87506 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -13,8 +13,9 @@ use rustpython_common::lock::PyRwLock; /// PyCUnionType - metaclass for Union #[pyclass(name = "UnionType", base = PyType, module = "_ctypes")] -#[derive(Debug, PyPayload, Default)] -pub struct PyCUnionType {} +#[derive(Debug)] +#[repr(transparent)] +pub struct PyCUnionType(PyType); impl Constructor for PyCUnionType { type Args = FuncArgs; @@ -121,8 +122,8 @@ impl PyCUnionType {} /// PyCUnion - base class for Union #[pyclass(module = "_ctypes", name = "Union", base = PyCData, metaclass = "PyCUnionType")] -#[derive(PyPayload)] pub struct PyCUnion { + _base: PyCData, /// Common CDataObject for memory buffer pub(super) cdata: PyRwLock<CDataObject>, } @@ -173,8 +174,10 @@ impl Constructor for PyCUnion { // Initialize buffer with zeros let stg_info = StgInfo::new(max_size, max_align); + let cdata = CDataObject::from_stg_info(&stg_info); PyCUnion { - cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)), + _base: PyCData::new(cdata.clone()), + cdata: PyRwLock::new(cdata), } .into_ref_with_type(vm, cls) .map(Into::into) @@ -204,8 +207,10 @@ impl PyCUnion { return Err(vm.new_value_error("NULL pointer access".to_owned())); } let stg_info = StgInfo::new(size, 1); + let cdata = CDataObject::from_stg_info(&stg_info); Ok(PyCUnion { - cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)), + _base: PyCData::new(cdata.clone()), + cdata: PyRwLock::new(cdata), } .into_ref_with_type(vm, cls)? .into()) @@ -249,8 +254,10 @@ impl PyCUnion { let bytes = buffer.obj_bytes(); let data = bytes[offset..offset + size].to_vec(); + let cdata = CDataObject::from_bytes(data, None); Ok(PyCUnion { - cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + _base: PyCData::new(cdata.clone()), + cdata: PyRwLock::new(cdata), } .into_ref_with_type(vm, cls)? .into()) @@ -286,8 +293,10 @@ impl PyCUnion { // Copy data from source let data = source_bytes[offset..offset + size].to_vec(); + let cdata = CDataObject::from_bytes(data, None); Ok(PyCUnion { - cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + _base: PyCData::new(cdata.clone()), + cdata: PyRwLock::new(cdata), } .into_ref_with_type(vm, cls)? .into()) diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index e5a438b86de..3d67591d567 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -69,7 +69,7 @@ impl ToPyException for std::io::Error { .set_attr("winerror", vm.new_pyobj(winerror), vm) .unwrap(); } - exc + exc.upcast() } } @@ -87,9 +87,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] fileio::extend_module(vm, &module).unwrap(); - let unsupported_operation = _io::UNSUPPORTED_OPERATION - .get_or_init(|| _io::make_unsupportedop(ctx)) - .clone(); + let unsupported_operation = _io::unsupported_operation().to_owned(); extend_module!(vm, &module, { "UnsupportedOperation" => unsupported_operation, "BlockingIOError" => ctx.exceptions.blocking_io_error.to_owned(), @@ -150,7 +148,7 @@ mod _io { AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromBorrowedObject, TryFromObject, builtins::{ - PyBaseExceptionRef, PyByteArray, PyBytes, PyBytesRef, PyIntRef, PyMemoryView, PyStr, + PyBaseExceptionRef, PyBool, PyByteArray, PyBytes, PyBytesRef, PyMemoryView, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, PyUtf8StrRef, }, class::StaticType, @@ -204,7 +202,8 @@ mod _io { } pub fn new_unsupported_operation(vm: &VirtualMachine, msg: String) -> PyBaseExceptionRef { - vm.new_exception_msg(UNSUPPORTED_OPERATION.get().unwrap().clone(), msg) + vm.new_os_subtype_error(unsupported_operation().to_owned(), None, msg) + .upcast() } fn _unsupported<T>(vm: &VirtualMachine, zelf: &PyObject, operation: &str) -> PyResult<T> { @@ -425,7 +424,7 @@ mod _io { #[pyattr] #[pyclass(name = "_IOBase")] - #[derive(Debug, PyPayload)] + #[derive(Debug, Default, PyPayload)] pub struct _IOBase; #[pyclass(with(IterNext, Iterable, Destructor), flags(BASETYPE, HAS_DICT))] @@ -456,7 +455,7 @@ mod _io { } #[pyattr] - fn __closed(ctx: &Context) -> PyIntRef { + fn __closed(ctx: &Context) -> PyRef<PyBool> { ctx.new_bool(false) } @@ -639,7 +638,9 @@ mod _io { #[pyattr] #[pyclass(name = "_RawIOBase", base = _IOBase)] - pub(super) struct _RawIOBase; + #[derive(Debug, Default)] + #[repr(transparent)] + pub(super) struct _RawIOBase(_IOBase); #[pyclass(flags(BASETYPE, HAS_DICT))] impl _RawIOBase { @@ -697,7 +698,9 @@ mod _io { #[pyattr] #[pyclass(name = "_BufferedIOBase", base = _IOBase)] - struct _BufferedIOBase; + #[derive(Debug, Default)] + #[repr(transparent)] + struct _BufferedIOBase(_IOBase); #[pyclass(flags(BASETYPE))] impl _BufferedIOBase { @@ -760,8 +763,9 @@ mod _io { // TextIO Base has no public constructor #[pyattr] #[pyclass(name = "_TextIOBase", base = _IOBase)] - #[derive(Debug, PyPayload)] - struct _TextIOBase; + #[derive(Debug, Default)] + #[repr(transparent)] + struct _TextIOBase(_IOBase); #[pyclass(flags(BASETYPE))] impl _TextIOBase { @@ -1760,8 +1764,9 @@ mod _io { #[pyattr] #[pyclass(name = "BufferedReader", base = _BufferedIOBase)] - #[derive(Debug, Default, PyPayload)] + #[derive(Debug, Default)] struct BufferedReader { + _base: _BufferedIOBase, data: PyThreadMutex<BufferedData>, } @@ -1829,8 +1834,9 @@ mod _io { #[pyattr] #[pyclass(name = "BufferedWriter", base = _BufferedIOBase)] - #[derive(Debug, Default, PyPayload)] + #[derive(Debug, Default)] struct BufferedWriter { + _base: _BufferedIOBase, data: PyThreadMutex<BufferedData>, } @@ -1874,8 +1880,9 @@ mod _io { #[pyattr] #[pyclass(name = "BufferedRandom", base = _BufferedIOBase)] - #[derive(Debug, Default, PyPayload)] + #[derive(Debug, Default)] struct BufferedRandom { + _base: _BufferedIOBase, data: PyThreadMutex<BufferedData>, } @@ -1934,8 +1941,9 @@ mod _io { #[pyattr] #[pyclass(name = "BufferedRWPair", base = _BufferedIOBase)] - #[derive(Debug, Default, PyPayload)] + #[derive(Debug, Default)] struct BufferedRWPair { + _base: _BufferedIOBase, read: BufferedReader, write: BufferedWriter, } @@ -2366,8 +2374,9 @@ mod _io { #[pyattr] #[pyclass(name = "TextIOWrapper", base = _TextIOBase)] - #[derive(Debug, Default, PyPayload)] + #[derive(Debug, Default)] struct TextIOWrapper { + _base: _TextIOBase, data: PyThreadMutex<Option<TextIOData>>, } @@ -3646,8 +3655,9 @@ mod _io { #[pyattr] #[pyclass(name = "StringIO", base = _TextIOBase)] - #[derive(Debug, PyPayload)] + #[derive(Debug)] struct StringIO { + _base: _TextIOBase, buffer: PyRwLock<BufferedIO>, closed: AtomicCell<bool>, } @@ -3677,6 +3687,7 @@ mod _io { .map_or_else(Vec::new, |v| v.as_bytes().to_vec()); Ok(Self { + _base: Default::default(), buffer: PyRwLock::new(BufferedIO::new(Cursor::new(raw_bytes))), closed: AtomicCell::new(false), }) @@ -3789,8 +3800,9 @@ mod _io { #[pyattr] #[pyclass(name = "BytesIO", base = _BufferedIOBase)] - #[derive(Debug, PyPayload)] + #[derive(Debug)] struct BytesIO { + _base: _BufferedIOBase, buffer: PyRwLock<BufferedIO>, closed: AtomicCell<bool>, exports: AtomicCell<usize>, @@ -3805,6 +3817,7 @@ mod _io { .map_or_else(Vec::new, |input| input.as_bytes().to_vec()); Ok(Self { + _base: Default::default(), buffer: PyRwLock::new(BufferedIO::new(Cursor::new(raw_bytes))), closed: AtomicCell::new(false), exports: AtomicCell::new(0), @@ -4229,11 +4242,7 @@ mod _io { } } - rustpython_common::static_cell! { - pub(super) static UNSUPPORTED_OPERATION: PyTypeRef; - } - - pub(super) fn make_unsupportedop(ctx: &Context) -> PyTypeRef { + fn create_unsupported_operation(ctx: &Context) -> PyTypeRef { use crate::types::PyTypeSlots; PyType::new_heap( "UnsupportedOperation", @@ -4249,6 +4258,13 @@ mod _io { .unwrap() } + pub fn unsupported_operation() -> &'static Py<PyType> { + rustpython_common::static_cell! { + static CELL: PyTypeRef; + } + CELL.get_or_init(|| create_unsupported_operation(Context::genesis())) + } + #[pyfunction] fn text_encoding( encoding: PyObjectRef, @@ -4423,8 +4439,9 @@ mod fileio { #[pyattr] #[pyclass(module = "io", name, base = _RawIOBase)] - #[derive(Debug, PyPayload)] + #[derive(Debug)] pub(super) struct FileIO { + _base: _RawIOBase, fd: AtomicCell<i32>, closefd: AtomicCell<bool>, mode: AtomicCell<Mode>, @@ -4446,6 +4463,7 @@ mod fileio { impl Default for FileIO { fn default() -> Self { Self { + _base: Default::default(), fd: AtomicCell::new(-1), closefd: AtomicCell::new(true), mode: AtomicCell::new(Mode::empty()), diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index b75f601c8db..f6ffd66759d 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -525,13 +525,15 @@ pub(super) mod _os { return Err(vm.new_value_error("embedded null byte")); } if key.is_empty() || key.contains(&b'=') { - return Err(vm.new_errno_error( + let x = vm.new_errno_error( 22, format!( "Invalid argument: {}", std::str::from_utf8(key).unwrap_or("<bytes encoding failure>") ), - )); + ); + + return Err(x.upcast()); } let key = super::bytes_as_os_str(key, vm)?; // SAFETY: requirements forwarded from the caller diff --git a/crates/vm/src/types/structseq.rs b/crates/vm/src/types/structseq.rs index b2ff5868d45..be0a1c9a70c 100644 --- a/crates/vm/src/types/structseq.rs +++ b/crates/vm/src/types/structseq.rs @@ -184,10 +184,12 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { /// Convert a Data struct into a PyStructSequence instance. fn from_data(data: Self::Data, vm: &VirtualMachine) -> PyTupleRef { + let tuple = + <Self::Data as ::rustpython_vm::types::PyStructSequenceData>::into_tuple(data, vm); let typ = Self::static_type(); - data.into_tuple(vm) + tuple .into_ref_with_type(vm, typ.to_owned()) - .unwrap() + .expect("Every PyStructSequence must be a valid tuple. This is a RustPython bug.") } #[pyslot] diff --git a/crates/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs index 191c090f121..fbda71dc1f6 100644 --- a/crates/vm/src/vm/context.rs +++ b/crates/vm/src/vm/context.rs @@ -1,9 +1,10 @@ use crate::{ PyResult, VirtualMachine, builtins::{ - PyBaseException, PyByteArray, PyBytes, PyComplex, PyDict, PyDictRef, PyEllipsis, PyFloat, - PyFrozenSet, PyInt, PyIntRef, PyList, PyListRef, PyNone, PyNotImplemented, PyStr, - PyStrInterned, PyTuple, PyTupleRef, PyType, PyTypeRef, + PyByteArray, PyBytes, PyComplex, PyDict, PyDictRef, PyEllipsis, PyFloat, PyFrozenSet, + PyInt, PyIntRef, PyList, PyListRef, PyNone, PyNotImplemented, PyStr, PyStrInterned, + PyTuple, PyTupleRef, PyType, PyTypeRef, + bool_::PyBool, code::{self, PyCode}, descriptor::{ MemberGetter, MemberKind, MemberSetter, MemberSetterFunc, PyDescriptorOwned, @@ -13,7 +14,7 @@ use crate::{ object, pystr, type_::PyAttributes, }, - class::{PyClassImpl, StaticType}, + class::StaticType, common::rc::PyRc, exceptions, function::{ @@ -31,8 +32,8 @@ use rustpython_common::lock::PyRwLock; #[derive(Debug)] pub struct Context { - pub true_value: PyIntRef, - pub false_value: PyIntRef, + pub true_value: PyRef<PyBool>, + pub false_value: PyRef<PyBool>, pub none: PyRef<PyNone>, pub empty_tuple: PyTupleRef, pub empty_frozenset: PyRef<PyFrozenSet>, @@ -279,10 +280,7 @@ impl Context { let exceptions = exceptions::ExceptionZoo::init(); #[inline] - fn create_object<T: PyObjectPayload + PyPayload>( - payload: T, - cls: &'static Py<PyType>, - ) -> PyRef<T> { + fn create_object<T: PyObjectPayload>(payload: T, cls: &'static Py<PyType>) -> PyRef<T> { PyRef::new_ref(payload, cls.to_owned(), None) } @@ -305,8 +303,8 @@ impl Context { }) .collect(); - let true_value = create_object(PyInt::from(1), types.bool_type); - let false_value = create_object(PyInt::from(0), types.bool_type); + let true_value = create_object(PyBool(PyInt::from(1)), types.bool_type); + let false_value = create_object(PyBool(PyInt::from(0)), types.bool_type); let empty_tuple = create_object( PyTuple::new_unchecked(Vec::new().into_boxed_slice()), @@ -449,13 +447,13 @@ impl Context { } #[inline(always)] - pub fn new_bool(&self, b: bool) -> PyIntRef { + pub fn new_bool(&self, b: bool) -> PyRef<PyBool> { let value = if b { &self.true_value } else { &self.false_value }; - value.clone() + value.to_owned() } #[inline(always)] @@ -510,14 +508,17 @@ impl Context { attrs.insert(identifier!(self, __module__), self.new_str(module).into()); let interned_name = self.intern_str(name); + let slots = PyTypeSlots { + name: interned_name.as_str(), + basicsize: 0, + flags: PyTypeFlags::heap_type_flags() | PyTypeFlags::HAS_DICT, + ..PyTypeSlots::default() + }; PyType::new_heap( name, bases, attrs, - PyTypeSlots { - name: interned_name.as_str(), - ..PyBaseException::make_slots() - }, + slots, self.types.type_type.to_owned(), self, ) diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 1054ba9b313..36481e5dbe3 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -1,8 +1,8 @@ use crate::{ - AsObject, Py, PyObject, PyObjectRef, PyRef, + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, builtins::{ - PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyStrRef, PyType, - PyTypeRef, + PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyOSError, PyStrRef, + PyType, PyTypeRef, builtin_func::PyNativeFunction, descriptor::PyMethodDescriptor, tuple::{IntoPyTuple, PyTupleRef}, @@ -10,6 +10,7 @@ use crate::{ convert::{ToPyException, ToPyObject}, function::{IntoPyNativeFn, PyMethodFlags}, scope::Scope, + types::Constructor, vm::VirtualMachine, }; use rustpython_compiler_core::SourceLocation; @@ -92,18 +93,54 @@ impl VirtualMachine { /// [`vm.invoke_exception()`][Self::invoke_exception] or /// [`exceptions::ExceptionCtor`][crate::exceptions::ExceptionCtor] instead. pub fn new_exception(&self, exc_type: PyTypeRef, args: Vec<PyObjectRef>) -> PyBaseExceptionRef { - // TODO: add repr of args into logging? + debug_assert_eq!( + exc_type.slots.basicsize, + std::mem::size_of::<PyBaseException>(), + "vm.new_exception() is only for exception types without additional payload. The given type '{}' is not allowed.", + exc_type.class().name() + ); PyRef::new_ref( - // TODO: this constructor might be invalid, because multiple - // exception (even builtin ones) are using custom constructors, - // see `OSError` as an example: PyBaseException::new(args, self), exc_type, Some(self.ctx.new_dict()), ) } + pub fn new_os_error(&self, msg: impl ToPyObject) -> PyRef<PyBaseException> { + self.new_os_subtype_error(self.ctx.exceptions.os_error.to_owned(), None, msg) + .upcast() + } + + pub fn new_os_subtype_error( + &self, + exc_type: PyTypeRef, + errno: Option<i32>, + msg: impl ToPyObject, + ) -> PyRef<PyOSError> { + debug_assert_eq!(exc_type.slots.basicsize, std::mem::size_of::<PyOSError>()); + let msg = msg.to_pyobject(self); + + fn new_os_subtype_error_impl( + vm: &VirtualMachine, + exc_type: PyTypeRef, + errno: Option<i32>, + msg: PyObjectRef, + ) -> PyRef<PyOSError> { + let args = match errno { + Some(e) => vec![vm.new_pyobj(e), msg], + None => vec![msg], + }; + let payload = + PyOSError::py_new(&exc_type, args.into(), vm).expect("new_os_error usage error"); + payload + .into_ref_with_type(vm, exc_type) + .expect("new_os_error usage error") + } + + new_os_subtype_error_impl(self, exc_type, errno, msg) + } + /// Instantiate an exception with no arguments. /// This function should only be used with builtin exception types; if a user-defined exception /// type is passed in, it may not be fully initialized; try using @@ -220,16 +257,11 @@ impl VirtualMachine { err.to_pyexception(self) } - pub fn new_errno_error(&self, errno: i32, msg: impl Into<String>) -> PyBaseExceptionRef { - let vm = self; - let exc_type = - crate::exceptions::errno_to_exc_type(errno, vm).unwrap_or(vm.ctx.exceptions.os_error); + pub fn new_errno_error(&self, errno: i32, msg: impl ToPyObject) -> PyRef<PyOSError> { + let exc_type = crate::exceptions::errno_to_exc_type(errno, self) + .unwrap_or(self.ctx.exceptions.os_error); - let errno_obj = vm.new_pyobj(errno); - vm.new_exception( - exc_type.to_owned(), - vec![errno_obj, vm.new_pyobj(msg.into())], - ) + self.new_os_subtype_error(exc_type.to_owned(), Some(errno), msg) } pub fn new_unicode_decode_error_real( @@ -565,7 +597,6 @@ impl VirtualMachine { define_exception_fn!(fn new_eof_error, eof_error, EOFError); define_exception_fn!(fn new_attribute_error, attribute_error, AttributeError); define_exception_fn!(fn new_type_error, type_error, TypeError); - define_exception_fn!(fn new_os_error, os_error, OSError); define_exception_fn!(fn new_system_error, system_error, SystemError); // TODO: remove & replace with new_unicode_decode_error_real diff --git a/extra_tests/snippets/stdlib_ctypes.py b/extra_tests/snippets/stdlib_ctypes.py index 0ec7568a839..0a5d1387a8d 100644 --- a/extra_tests/snippets/stdlib_ctypes.py +++ b/extra_tests/snippets/stdlib_ctypes.py @@ -397,3 +397,5 @@ def get_win_folder_via_ctypes(csidl_name: str) -> str: return buf.value # print(get_win_folder_via_ctypes("CSIDL_DOWNLOADS")) + +print("done") From 8e21db11d683ebd15f58208c12b82001babc5668 Mon Sep 17 00:00:00 2001 From: Lee Dogeon <dev.moreal@gmail.com> Date: Mon, 15 Dec 2025 09:57:06 +0900 Subject: [PATCH 509/819] Update `architecture/architecture.md` (#6437) * Replace architecture overview image with mermaid graph * Update architecture.md with correct paths and links - Fix file paths to reflect crates/ directory structure - Update GitHub permalink references to current commit - Remove trailing slashes from package names - Use archive.org for broken desosa.nl link - Change master to main branch reference * Update architecture documentation for ruff_python_parser RustPython now uses ruff_python_parser from astral-sh/ruff instead of the separate RustPython/Parser project. Update architecture documentation to reflect this change. --- architecture/architecture.md | 70 ++++++++++++++++++++++++++--------- architecture/overview.png | Bin 35246 -> 0 bytes 2 files changed, 53 insertions(+), 17 deletions(-) delete mode 100644 architecture/overview.png diff --git a/architecture/architecture.md b/architecture/architecture.md index a59b6498bf1..4b4110ee895 100644 --- a/architecture/architecture.md +++ b/architecture/architecture.md @@ -20,7 +20,43 @@ If, after reading this, you want to contribute to RustPython, take a look at the A high-level overview of the workings of RustPython is visible in the figure below, showing how Python source files are interpreted. -![overview.png](overview.png) +```mermaid +flowchart TB + SourceCode["🐍 Source code"] + + subgraph Interpreter["RustPython Interpreter"] + direction TB + + subgraph Parser["Parser"] + ParserBox["• Tokenize source code<br/>• Validate tokens<br/>• Create AST"] + end + + AST[["AST"]] + + subgraph Compiler["Compiler"] + CompilerBox["• Converts AST to bytecode"] + end + + Bytecode[["Bytecode"]] + + subgraph VM["VM"] + VMBox["• Executes bytecode given input"] + end + + Parser --> AST + AST --> Compiler + Compiler --> Bytecode + Bytecode --> VM + end + + SourceCode -------> Interpreter + + Input[["Input"]] + Output["Code gets executed"] + + Input --> VM + VM --> Output +``` Main architecture of RustPython. @@ -36,9 +72,9 @@ The main entry point of RustPython is located in `src/main.rs` and simply forwar For each of the three components, the entry point is as follows: -- Parser: The Parser is located in a separate project, [RustPython/Parser][10]. See the documentation there for more information. -- Compiler: `compile`, located in [`vm/src/vm/compile.rs`][11], this eventually forwards a call to [`compiler::compile`][12]. -- VM: `run_code_obj`, located in [`vm/src/vm/mod.rs`][13]. This creates a new frame in which the bytecode is executed. +- Parser: The Parser is located in a separate project, [ruff_python_parser][10]. See the documentation there for more information. +- Compiler: `compile`, located in [`crates/vm/src/vm/compile.rs`][11], this eventually forwards a call to [`compiler::compile`][12]. +- VM: `run_code_obj`, located in [`crates/vm/src/vm/mod.rs`][13]. This creates a new frame in which the bytecode is executed. ## Components @@ -46,7 +82,7 @@ Here we give a brief overview of each component and its function. For more detai ### Compiler -This component, implemented as the `rustpython-compiler/` package, is responsible for translating a Python source file into its equivalent bytecode representation. As an example, the following Python file: +This component, implemented as the `rustpython-compiler` package, is responsible for translating a Python source file into its equivalent bytecode representation. As an example, the following Python file: ```python def f(x): @@ -71,11 +107,11 @@ The Parser is the main sub-component of the compiler. All the functionality requ 1. Lexical Analysis 2. Parsing -The functionality for parsing resides in the RustPython/Parser project. See the documentation there for more information. +The functionality for parsing resides in the ruff_python_parser project in the astral-sh/ruff repository. See the documentation there for more information. ### VM -The Virtual Machine (VM) is responsible for executing the bytecode generated by the compiler. It is implemented in the `rustpython-vm/` package. The VM is currently implemented as a stack machine, meaning that it uses a stack to store intermediate results. In the `rustpython-vm/` package, additional sub-components are present, for example: +The Virtual Machine (VM) is responsible for executing the bytecode generated by the compiler. It is implemented in the `rustpython-vm` package. The VM is currently implemented as a stack machine, meaning that it uses a stack to store intermediate results. In the `rustpython-vm` package, additional sub-components are present, for example: - builtins: the built in objects of Python, such as `int` and `str`. - stdlib: parts of the standard library that contains built-in modules needed for the VM to function, such as `sys`. @@ -122,7 +158,7 @@ The RustPython executable/REPL (Read-Eval-Print-Loop) is implemented here, which Some things to note: - The CLI is defined in the [`run` function of `src/lib.rs`][16]. -- The interface and helper for the REPL are defined in this package, but the actual REPL can be found in `vm/src/readline.rs` +- The interface and helper for the REPL are defined in this package, but the actual REPL can be found in `crates/vm/src/readline.rs` ### WASM @@ -133,18 +169,18 @@ Crate for WebAssembly build, which compiles the RustPython package to a format t Integration and snippets that test for additional edge-cases, implementation specific functionality and bug report snippets. [1]: https://github.com/RustPython/RustPython -[2]: https://2021.desosa.nl/projects/rustpython/posts/vision/ +[2]: https://web.archive.org/web/20240723122357/https://2021.desosa.nl/projects/rustpython/posts/vision/ [3]: https://www.youtube.com/watch?v=nJDY9ASuiLc&t=213s [4]: https://rustpython.github.io/2020/04/02/thing-explainer-parser.html [5]: https://rustpython.github.io/demo/ [6]: https://rustpython.github.io/demo/notebook/ -[7]: https://github.com/RustPython/RustPython/blob/master/DEVELOPMENT.md +[7]: https://github.com/RustPython/RustPython/blob/main/DEVELOPMENT.md [8]: https://rustpython.github.io/guideline/2020/04/04/how-to-contribute-by-cpython-unittest.html -[9]: https://github.com/RustPython/RustPython/blob/0e24cf48c63ae4ca9f829e88142a987cab3a9966/src/lib.rs#L63 -[10]: https://github.com/RustPython/Parser -[11]: https://github.com/RustPython/RustPython/blob/0e24cf48c63ae4ca9f829e88142a987cab3a9966/vm/src/vm/compile.rs#LL10C17-L10C17 -[12]: https://github.com/RustPython/RustPython/blob/0e24cf48c63ae4ca9f829e88142a987cab3a9966/vm/src/vm/compile.rs#L26 -[13]: https://github.com/RustPython/RustPython/blob/0e24cf48c63ae4ca9f829e88142a987cab3a9966/vm/src/vm/mod.rs#L356 -[14]: https://github.com/RustPython/RustPython/blob/0e24cf48c63ae4ca9f829e88142a987cab3a9966/common/src/float_ops.rs +[9]: https://github.com/RustPython/RustPython/blob/d36a2cffdef2218f3264cef9145a1f781d474ea3/src/lib.rs#L72 +[10]: https://github.com/astral-sh/ruff/tree/2bffef59665ce7d2630dfd72ee99846663660db8/crates/ruff_python_parser +[11]: https://github.com/RustPython/RustPython/blob/d36a2cffdef2218f3264cef9145a1f781d474ea3/crates/vm/src/vm/compile.rs#L10 +[12]: https://github.com/RustPython/RustPython/blob/d36a2cffdef2218f3264cef9145a1f781d474ea3/crates/vm/src/vm/compile.rs#L26 +[13]: https://github.com/RustPython/RustPython/blob/d36a2cffdef2218f3264cef9145a1f781d474ea3/crates/vm/src/vm/mod.rs#L433 +[14]: https://github.com/RustPython/RustPython/blob/d36a2cffdef2218f3264cef9145a1f781d474ea3/crates/common/src/float_ops.rs [15]: https://rustpython.github.io/guideline/2020/04/04/how-to-contribute-by-cpython-unittest.html -[16]: https://github.com/RustPython/RustPython/blob/0e24cf48c63ae4ca9f829e88142a987cab3a9966/src/lib.rs#L63 +[16]: https://github.com/RustPython/RustPython/blob/d36a2cffdef2218f3264cef9145a1f781d474ea3/src/lib.rs#L72 diff --git a/architecture/overview.png b/architecture/overview.png deleted file mode 100644 index ce66706f3c83ebe48d4df1a796723a8aa9ffde4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35246 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 Koyk<c?)PtWhV&Ew From 550497eb7935044f4319148e7024703dd7a0275e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 15 Dec 2025 17:56:48 +0900 Subject: [PATCH 510/819] Use parking_lot in faulthandler (#6438) --- crates/stdlib/src/faulthandler.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs index a06b063d305..f45c9909c6f 100644 --- a/crates/stdlib/src/faulthandler.rs +++ b/crates/stdlib/src/faulthandler.rs @@ -7,10 +7,11 @@ mod decl { PyObjectRef, PyResult, VirtualMachine, builtins::PyFloat, frame::Frame, function::OptionalArg, py_io::Write, }; + use parking_lot::{Condvar, Mutex}; #[cfg(any(unix, windows))] use rustpython_common::os::{get_errno, set_errno}; + use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; - use std::sync::{Arc, Condvar, Mutex}; use std::thread; use std::time::Duration; @@ -692,13 +693,12 @@ mod decl { loop { // Hold lock across wait_timeout to avoid race condition - let mut guard = lock.lock().unwrap(); + let mut guard = lock.lock(); if guard.cancel { return; } let timeout = Duration::from_micros(guard.timeout_us); - let result = cvar.wait_timeout(guard, timeout).unwrap(); - guard = result.0; + cvar.wait_for(&mut guard, timeout); // Check if cancelled after wait if guard.cancel { @@ -818,7 +818,7 @@ mod decl { // Store the state { - let mut watchdog = WATCHDOG.lock().unwrap(); + let mut watchdog = WATCHDOG.lock(); *watchdog = Some(Arc::clone(&state)); } @@ -833,14 +833,14 @@ mod decl { #[pyfunction] fn cancel_dump_traceback_later() { let state = { - let mut watchdog = WATCHDOG.lock().unwrap(); + let mut watchdog = WATCHDOG.lock(); watchdog.take() }; if let Some(state) = state { let (lock, cvar) = &*state; { - let mut guard = lock.lock().unwrap(); + let mut guard = lock.lock(); guard.cancel = true; } cvar.notify_all(); @@ -849,7 +849,7 @@ mod decl { #[cfg(unix)] mod user_signals { - use std::sync::Mutex; + use parking_lot::Mutex; const NSIG: usize = 64; @@ -878,12 +878,12 @@ mod decl { static USER_SIGNALS: Mutex<Option<Vec<UserSignal>>> = Mutex::new(None); pub fn get_user_signal(signum: usize) -> Option<UserSignal> { - let guard = USER_SIGNALS.lock().unwrap(); + let guard = USER_SIGNALS.lock(); guard.as_ref().and_then(|v| v.get(signum).cloned()) } pub fn set_user_signal(signum: usize, signal: UserSignal) { - let mut guard = USER_SIGNALS.lock().unwrap(); + let mut guard = USER_SIGNALS.lock(); if guard.is_none() { *guard = Some(vec![UserSignal::default(); NSIG]); } @@ -895,7 +895,7 @@ mod decl { } pub fn clear_user_signal(signum: usize) -> Option<UserSignal> { - let mut guard = USER_SIGNALS.lock().unwrap(); + let mut guard = USER_SIGNALS.lock(); if let Some(ref mut v) = *guard && signum < v.len() && v[signum].enabled @@ -908,7 +908,7 @@ mod decl { } pub fn is_enabled(signum: usize) -> bool { - let guard = USER_SIGNALS.lock().unwrap(); + let guard = USER_SIGNALS.lock(); guard .as_ref() .and_then(|v| v.get(signum)) From 78d2e5bdf16edb626fea0649a6b3d437c4d03ee7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:15:37 +0000 Subject: [PATCH 511/819] Bump actions/upload-artifact from 5.0.0 to 6.0.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5.0.0 to 6.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/release.yml | 4 ++-- .github/workflows/update-doc-db.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0026a74b868..1b282d1e0e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,7 +83,7 @@ jobs: if: runner.os == 'Windows' - name: Upload Binary Artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6.0.0 with: name: rustpython-release-${{ runner.os }}-${{ matrix.platform.target }} path: target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}* @@ -105,7 +105,7 @@ jobs: run: cp target/wasm32-wasip1/release/rustpython.wasm target/rustpython-release-wasm32-wasip1.wasm - name: Upload Binary Artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6.0.0 with: name: rustpython-release-wasm32-wasip1 path: target/rustpython-release-wasm32-wasip1.wasm diff --git a/.github/workflows/update-doc-db.yml b/.github/workflows/update-doc-db.yml index c53de5461eb..61a4cc0ab4f 100644 --- a/.github/workflows/update-doc-db.yml +++ b/.github/workflows/update-doc-db.yml @@ -42,7 +42,7 @@ jobs: - name: Generate docs run: python crates/doc/generate.py - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: doc-db-${{ inputs.python-version }}-${{ matrix.os }} path: "crates/doc/generated/*.json" @@ -88,7 +88,7 @@ jobs: cat crates/doc/generated/raw_entries.txt >> $OUTPUT_FILE echo '};' >> $OUTPUT_FILE - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: doc-db-${{ inputs.python-version }} path: "crates/doc/src/data.inc.rs" From e6d9a9aef049442e870ced604fd326bc0cba70ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 23:54:53 +0900 Subject: [PATCH 512/819] Bump actions/download-artifact from 6.0.0 to 7.0.0 (#6441) --- .github/workflows/release.yml | 2 +- .github/workflows/update-doc-db.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b282d1e0e7..3efff295c37 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -145,7 +145,7 @@ jobs: needs: [build, build-wasm] steps: - name: Download Binary Artifacts - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7.0.0 with: path: bin pattern: rustpython-* diff --git a/.github/workflows/update-doc-db.yml b/.github/workflows/update-doc-db.yml index 61a4cc0ab4f..c580e7d0eaf 100644 --- a/.github/workflows/update-doc-db.yml +++ b/.github/workflows/update-doc-db.yml @@ -60,7 +60,7 @@ jobs: token: ${{ secrets.AUTO_COMMIT_PAT }} - name: Download generated doc DBs - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: pattern: "doc-db-${{ inputs.python-version }}-**" path: crates/doc/generated/ From 30cc772454cf02ec5ae69c32e7ea224fc2a7af27 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:11:17 +0900 Subject: [PATCH 513/819] sys.set_asyncgen_hook (#6439) * PyAnextAwaitable::state * sys.set_asyncgen_hook --- Lib/test/test_asyncgen.py | 6 -- crates/vm/src/builtins/asyncgenerator.rs | 91 +++++++++++++++++++++--- crates/vm/src/builtins/coroutine.rs | 33 +++++++-- crates/vm/src/frame.rs | 12 +++- 4 files changed, 120 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index 3618fb60d8f..7039bd7054c 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -559,8 +559,6 @@ async def call_with_kwarg(): with self.assertRaises(TypeError): self.loop.run_until_complete(call_with_kwarg()) - # TODO: RUSTPYTHON, error message mismatch - @unittest.expectedFailure def test_anext_bad_await(self): async def bad_awaitable(): class BadAwaitable: @@ -630,8 +628,6 @@ async def do_test(): result = self.loop.run_until_complete(do_test()) self.assertEqual(result, "completed") - # TODO: RUSTPYTHON, anext coroutine iteration issue - @unittest.expectedFailure def test_anext_iter(self): @types.coroutine def _async_yield(v): @@ -1489,8 +1485,6 @@ async def main(): self.assertEqual(messages, []) - # TODO: RUSTPYTHON, ValueError: not enough values to unpack (expected 1, got 0) - @unittest.expectedFailure def test_async_gen_asyncio_shutdown_exception_01(self): messages = [] diff --git a/crates/vm/src/builtins/asyncgenerator.rs b/crates/vm/src/builtins/asyncgenerator.rs index b41a49f931e..073513184ff 100644 --- a/crates/vm/src/builtins/asyncgenerator.rs +++ b/crates/vm/src/builtins/asyncgenerator.rs @@ -3,6 +3,7 @@ use crate::{ AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::PyBaseExceptionRef, class::PyClassImpl, + common::lock::PyMutex, coroutine::Coro, frame::FrameRef, function::OptionalArg, @@ -17,6 +18,10 @@ use crossbeam_utils::atomic::AtomicCell; pub struct PyAsyncGen { inner: Coro, running_async: AtomicCell<bool>, + // whether hooks have been initialized + ag_hooks_inited: AtomicCell<bool>, + // ag_origin_or_finalizer - stores the finalizer callback + ag_finalizer: PyMutex<Option<PyObjectRef>>, } type PyAsyncGenRef = PyRef<PyAsyncGen>; @@ -37,6 +42,48 @@ impl PyAsyncGen { Self { inner: Coro::new(frame, name, qualname), running_async: AtomicCell::new(false), + ag_hooks_inited: AtomicCell::new(false), + ag_finalizer: PyMutex::new(None), + } + } + + /// Initialize async generator hooks. + /// Returns Ok(()) if successful, Err if firstiter hook raised an exception. + fn init_hooks(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()> { + // = async_gen_init_hooks + if zelf.ag_hooks_inited.load() { + return Ok(()); + } + + zelf.ag_hooks_inited.store(true); + + // Get and store finalizer from thread-local storage + let finalizer = crate::vm::thread::ASYNC_GEN_FINALIZER.with_borrow(|f| f.as_ref().cloned()); + if let Some(finalizer) = finalizer { + *zelf.ag_finalizer.lock() = Some(finalizer); + } + + // Call firstiter hook + let firstiter = crate::vm::thread::ASYNC_GEN_FIRSTITER.with_borrow(|f| f.as_ref().cloned()); + if let Some(firstiter) = firstiter { + let obj: PyObjectRef = zelf.to_owned().into(); + firstiter.call((obj,), vm)?; + } + + Ok(()) + } + + /// Call finalizer hook if set + #[allow(dead_code)] + fn call_finalizer(zelf: &Py<Self>, vm: &VirtualMachine) { + // = gen_dealloc + let finalizer = zelf.ag_finalizer.lock().clone(); + if let Some(finalizer) = finalizer + && !zelf.inner.closed.load() + { + // Call finalizer, ignore any errors (PyErr_WriteUnraisable) + let obj: PyObjectRef = zelf.to_owned().into(); + let _ = finalizer.call((obj,), vm); } } @@ -91,17 +138,23 @@ impl PyRef<PyAsyncGen> { } #[pymethod] - fn __anext__(self, vm: &VirtualMachine) -> PyAsyncGenASend { - Self::asend(self, vm.ctx.none(), vm) + fn __anext__(self, vm: &VirtualMachine) -> PyResult<PyAsyncGenASend> { + PyAsyncGen::init_hooks(&self, vm)?; + Ok(PyAsyncGenASend { + ag: self, + state: AtomicCell::new(AwaitableState::Init), + value: vm.ctx.none(), + }) } #[pymethod] - const fn asend(self, value: PyObjectRef, _vm: &VirtualMachine) -> PyAsyncGenASend { - PyAsyncGenASend { + fn asend(self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyAsyncGenASend> { + PyAsyncGen::init_hooks(&self, vm)?; + Ok(PyAsyncGenASend { ag: self, state: AtomicCell::new(AwaitableState::Init), value, - } + }) } #[pymethod] @@ -111,8 +164,9 @@ impl PyRef<PyAsyncGen> { exc_val: OptionalArg, exc_tb: OptionalArg, vm: &VirtualMachine, - ) -> PyAsyncGenAThrow { - PyAsyncGenAThrow { + ) -> PyResult<PyAsyncGenAThrow> { + PyAsyncGen::init_hooks(&self, vm)?; + Ok(PyAsyncGenAThrow { ag: self, aclose: false, state: AtomicCell::new(AwaitableState::Init), @@ -121,12 +175,13 @@ impl PyRef<PyAsyncGen> { exc_val.unwrap_or_none(vm), exc_tb.unwrap_or_none(vm), ), - } + }) } #[pymethod] - fn aclose(self, vm: &VirtualMachine) -> PyAsyncGenAThrow { - PyAsyncGenAThrow { + fn aclose(self, vm: &VirtualMachine) -> PyResult<PyAsyncGenAThrow> { + PyAsyncGen::init_hooks(&self, vm)?; + Ok(PyAsyncGenAThrow { ag: self, aclose: true, state: AtomicCell::new(AwaitableState::Init), @@ -135,7 +190,7 @@ impl PyRef<PyAsyncGen> { vm.ctx.none(), vm.ctx.none(), ), - } + }) } } @@ -441,6 +496,7 @@ impl IterNext for PyAsyncGenAThrow { pub struct PyAnextAwaitable { wrapped: PyObjectRef, default_value: PyObjectRef, + state: AtomicCell<AwaitableState>, } impl PyPayload for PyAnextAwaitable { @@ -456,6 +512,7 @@ impl PyAnextAwaitable { Self { wrapped, default_value, + state: AtomicCell::new(AwaitableState::Init), } } @@ -464,6 +521,13 @@ impl PyAnextAwaitable { zelf } + fn check_closed(&self, vm: &VirtualMachine) -> PyResult<()> { + if let AwaitableState::Closed = self.state.load() { + return Err(vm.new_runtime_error("cannot reuse already awaited __anext__()/asend()")); + } + Ok(()) + } + /// Get the awaitable iterator from wrapped object. // = anextawaitable_getiter. fn get_awaitable_iter(&self, vm: &VirtualMachine) -> PyResult { @@ -523,6 +587,8 @@ impl PyAnextAwaitable { #[pymethod] fn send(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.check_closed(vm)?; + self.state.store(AwaitableState::Iter); let awaitable = self.get_awaitable_iter(vm)?; let result = vm.call_method(&awaitable, "send", (val,)); self.handle_result(result, vm) @@ -536,6 +602,8 @@ impl PyAnextAwaitable { exc_tb: OptionalArg, vm: &VirtualMachine, ) -> PyResult { + self.check_closed(vm)?; + self.state.store(AwaitableState::Iter); let awaitable = self.get_awaitable_iter(vm)?; let result = vm.call_method( &awaitable, @@ -551,6 +619,7 @@ impl PyAnextAwaitable { #[pymethod] fn close(&self, vm: &VirtualMachine) -> PyResult<()> { + self.state.store(AwaitableState::Closed); if let Ok(awaitable) = self.get_awaitable_iter(vm) { let _ = vm.call_method(&awaitable, "close", ()); } diff --git a/crates/vm/src/builtins/coroutine.rs b/crates/vm/src/builtins/coroutine.rs index 8f57059e085..0909cdfb444 100644 --- a/crates/vm/src/builtins/coroutine.rs +++ b/crates/vm/src/builtins/coroutine.rs @@ -8,6 +8,7 @@ use crate::{ protocol::PyIterReturn, types::{IterNext, Iterable, Representable, SelfIter, Unconstructible}, }; +use crossbeam_utils::atomic::AtomicCell; #[pyclass(module = false, name = "coroutine")] #[derive(Debug)] @@ -56,8 +57,11 @@ impl PyCoroutine { } #[pymethod(name = "__await__")] - const fn r#await(zelf: PyRef<Self>) -> PyCoroutineWrapper { - PyCoroutineWrapper { coro: zelf } + fn r#await(zelf: PyRef<Self>) -> PyCoroutineWrapper { + PyCoroutineWrapper { + coro: zelf, + closed: AtomicCell::new(false), + } } #[pygetset] @@ -140,6 +144,7 @@ impl IterNext for PyCoroutine { // PyCoroWrapper_Type in CPython pub struct PyCoroutineWrapper { coro: PyRef<PyCoroutine>, + closed: AtomicCell<bool>, } impl PyPayload for PyCoroutineWrapper { @@ -151,9 +156,22 @@ impl PyPayload for PyCoroutineWrapper { #[pyclass(with(IterNext, Iterable))] impl PyCoroutineWrapper { + fn check_closed(&self, vm: &VirtualMachine) -> PyResult<()> { + if self.closed.load() { + return Err(vm.new_runtime_error("cannot reuse already awaited coroutine")); + } + Ok(()) + } + #[pymethod] fn send(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyIterReturn> { - self.coro.send(val, vm) + self.check_closed(vm)?; + let result = self.coro.send(val, vm); + // Mark as closed if exhausted + if let Ok(PyIterReturn::StopIteration(_)) = &result { + self.closed.store(true); + } + result } #[pymethod] @@ -164,11 +182,18 @@ impl PyCoroutineWrapper { exc_tb: OptionalArg, vm: &VirtualMachine, ) -> PyResult<PyIterReturn> { - self.coro.throw(exc_type, exc_val, exc_tb, vm) + self.check_closed(vm)?; + let result = self.coro.throw(exc_type, exc_val, exc_tb, vm); + // Mark as closed if exhausted + if let Ok(PyIterReturn::StopIteration(_)) = &result { + self.closed.store(true); + } + result } #[pymethod] fn close(&self, vm: &VirtualMachine) -> PyResult<()> { + self.closed.store(true); self.coro.close(vm) } } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 77b034ade9a..4a460a95884 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -918,6 +918,8 @@ impl ExecutingFrame<'_> { Ok(None) } bytecode::Instruction::GetAwaitable => { + use crate::protocol::PyIter; + let awaited_obj = self.pop_value(); let awaitable = if awaited_obj.downcastable::<PyCoroutine>() { awaited_obj @@ -932,7 +934,15 @@ impl ExecutingFrame<'_> { ) }, )?; - await_method.call((), vm)? + let result = await_method.call((), vm)?; + // Check that __await__ returned an iterator + if !PyIter::check(&result) { + return Err(vm.new_type_error(format!( + "__await__() returned non-iterator of type '{}'", + result.class().name() + ))); + } + result }; self.push_value(awaitable); Ok(None) From 272b36daa54c0babada805b3a24c8ef6c2928b00 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:16:38 +0900 Subject: [PATCH 514/819] small fixes (#6444) * fix memoryview * retype slot zelf --- crates/vm/src/builtins/memory.rs | 9 +++++---- crates/vm/src/types/slot.rs | 22 ++++++++-------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/crates/vm/src/builtins/memory.rs b/crates/vm/src/builtins/memory.rs index c8656cb1362..c1b1496e8c6 100644 --- a/crates/vm/src/builtins/memory.rs +++ b/crates/vm/src/builtins/memory.rs @@ -693,12 +693,13 @@ impl PyMemoryView { #[pymethod] fn __len__(&self, vm: &VirtualMachine) -> PyResult<usize> { self.try_not_released(vm)?; - Ok(if self.desc.ndim() == 0 { - 1 + if self.desc.ndim() == 0 { + // 0-dimensional memoryview has no length + Err(vm.new_type_error("0-dim memory has no length".to_owned())) } else { // shape for dim[0] - self.desc.dim_desc[0].0 - }) + Ok(self.desc.dim_desc[0].0) + } } #[pymethod] diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index a4169fdd837..f52e7296a7b 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -950,8 +950,9 @@ where pub trait Initializer: PyPayload { type Args: FromArgs; - #[pyslot] #[inline] + #[pyslot] + #[pymethod(name = "__init__")] fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { #[cfg(debug_assertions)] let class_name_for_debug = zelf.class().name().to_string(); @@ -979,13 +980,6 @@ pub trait Initializer: PyPayload { Self::init(zelf, args, vm) } - #[pymethod] - #[inline] - fn __init__(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { - // TODO: check if this is safe. zelf may need to be `PyObjectRef` - Self::init(zelf, args, vm) - } - fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()>; } @@ -1340,8 +1334,8 @@ pub trait GetAttr: PyPayload { #[inline] #[pymethod] - fn __getattribute__(zelf: PyRef<Self>, name: PyStrRef, vm: &VirtualMachine) -> PyResult { - Self::getattro(&zelf, &name, vm) + fn __getattribute__(zelf: PyObjectRef, name: PyStrRef, vm: &VirtualMachine) -> PyResult { + Self::slot_getattro(&zelf, &name, vm) } } @@ -1371,18 +1365,18 @@ pub trait SetAttr: PyPayload { #[inline] #[pymethod] fn __setattr__( - zelf: PyRef<Self>, + zelf: PyObjectRef, name: PyStrRef, value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { - Self::setattro(&zelf, &name, PySetterValue::Assign(value), vm) + Self::slot_setattro(&zelf, &name, PySetterValue::Assign(value), vm) } #[inline] #[pymethod] - fn __delattr__(zelf: PyRef<Self>, name: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { - Self::setattro(&zelf, &name, PySetterValue::Delete, vm) + fn __delattr__(zelf: PyObjectRef, name: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { + Self::slot_setattro(&zelf, &name, PySetterValue::Delete, vm) } } From 65bdfc3d4edfa6a0e3e86edb78dc8f7cca172311 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 16 Dec 2025 23:11:35 +0900 Subject: [PATCH 515/819] Integrate OSError creations into OSErrorBuilder (#6443) --- crates/vm/src/exceptions.rs | 114 ++++++++++++++++++++++++++++++++++ crates/vm/src/ospath.rs | 67 +++----------------- crates/vm/src/stdlib/io.rs | 46 +++++++------- crates/vm/src/stdlib/os.rs | 50 ++++++++------- crates/vm/src/stdlib/posix.rs | 23 +++---- crates/vm/src/vm/vm_new.rs | 24 +------ 6 files changed, 189 insertions(+), 135 deletions(-) diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 04dd78fb448..2b725085bec 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1193,6 +1193,8 @@ pub(crate) fn errno_to_exc_type(_errno: i32, _vm: &VirtualMachine) -> Option<&'s None } +pub(crate) use types::{OSErrorBuilder, ToOSErrorBuilder}; + pub(super) mod types { use crate::common::lock::PyRwLock; use crate::object::{MaybeTraverse, Traverse, TraverseFn}; @@ -1204,6 +1206,7 @@ pub(super) mod types { PyInt, PyStrRef, PyTupleRef, PyType, PyTypeRef, traceback::PyTracebackRef, tuple::IntoPyTuple, }, + convert::ToPyObject, convert::ToPyResult, function::{ArgBytesLike, FuncArgs}, types::{Constructor, Initializer}, @@ -1212,6 +1215,117 @@ pub(super) mod types { use itertools::Itertools; use rustpython_common::str::UnicodeEscapeCodepoint; + pub(crate) trait ToOSErrorBuilder { + fn to_os_error_builder(&self, vm: &VirtualMachine) -> OSErrorBuilder; + } + + pub struct OSErrorBuilder { + exc_type: PyTypeRef, + errno: Option<i32>, + strerror: Option<PyObjectRef>, + filename: Option<PyObjectRef>, + #[cfg(windows)] + winerror: Option<PyObjectRef>, + filename2: Option<PyObjectRef>, + } + + impl OSErrorBuilder { + #[must_use] + pub fn with_subtype( + exc_type: PyTypeRef, + errno: Option<i32>, + strerror: impl ToPyObject, + vm: &VirtualMachine, + ) -> Self { + let strerror = strerror.to_pyobject(vm); + Self { + exc_type, + errno, + strerror: Some(strerror), + filename: None, + #[cfg(windows)] + winerror: None, + filename2: None, + } + } + #[must_use] + pub fn with_errno(errno: i32, strerror: impl ToPyObject, vm: &VirtualMachine) -> Self { + let exc_type = crate::exceptions::errno_to_exc_type(errno, vm) + .unwrap_or(vm.ctx.exceptions.os_error) + .to_owned(); + Self::with_subtype(exc_type, Some(errno), strerror, vm) + } + + // #[must_use] + // pub(crate) fn errno(mut self, errno: i32) -> Self { + // self.errno.replace(errno); + // self + // } + + #[must_use] + pub(crate) fn filename(mut self, filename: PyObjectRef) -> Self { + self.filename.replace(filename); + self + } + + #[must_use] + pub(crate) fn filename2(mut self, filename: PyObjectRef) -> Self { + self.filename2.replace(filename); + self + } + + #[must_use] + #[cfg(windows)] + pub(crate) fn winerror(mut self, winerror: PyObjectRef) -> Self { + self.winerror.replace(winerror); + self + } + + pub fn build(self, vm: &VirtualMachine) -> PyRef<PyOSError> { + let OSErrorBuilder { + exc_type, + errno, + strerror, + filename, + #[cfg(windows)] + winerror, + filename2, + } = self; + + let args = if let Some(errno) = errno { + #[cfg(windows)] + let winerror = winerror.to_pyobject(vm); + #[cfg(not(windows))] + let winerror = vm.ctx.none(); + + vec![ + errno.to_pyobject(vm), + strerror.to_pyobject(vm), + filename.to_pyobject(vm), + winerror, + filename2.to_pyobject(vm), + ] + } else { + vec![strerror.to_pyobject(vm)] + }; + + let payload = PyOSError::py_new(&exc_type, args.clone().into(), vm) + .expect("new_os_error usage error"); + let os_error = payload + .into_ref_with_type(vm, exc_type) + .expect("new_os_error usage error"); + PyOSError::slot_init(os_error.as_object().to_owned(), args.into(), vm) + .expect("new_os_error usage error"); + os_error + } + } + + impl crate::convert::IntoPyException for OSErrorBuilder { + fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef { + self.build(vm).upcast() + } + } + // Re-export exception group types from dedicated module pub use crate::exception_group::types::PyBaseExceptionGroup; diff --git a/crates/vm/src/ospath.rs b/crates/vm/src/ospath.rs index add40f9b20c..9fca53d869c 100644 --- a/crates/vm/src/ospath.rs +++ b/crates/vm/src/ospath.rs @@ -2,10 +2,8 @@ use rustpython_common::crt_fd; use crate::{ PyObjectRef, PyResult, VirtualMachine, - builtins::PyBaseExceptionRef, convert::{IntoPyException, ToPyException, ToPyObject, TryFromObject}, function::FsPath, - object::AsObject, }; use std::path::{Path, PathBuf}; @@ -144,62 +142,17 @@ impl OsPathOrFd<'_> { } } -// TODO: preserve the input `PyObjectRef` of filename and filename2 (Failing check `self.assertIs(err.filename, name, str(func)`) -pub struct IOErrorBuilder<'a> { - error: &'a std::io::Error, - filename: Option<OsPathOrFd<'a>>, - filename2: Option<OsPathOrFd<'a>>, -} - -impl<'a> IOErrorBuilder<'a> { - pub const fn new(error: &'a std::io::Error) -> Self { - Self { - error, - filename: None, - filename2: None, - } - } - - pub(crate) fn filename(mut self, filename: impl Into<OsPathOrFd<'a>>) -> Self { - let filename = filename.into(); - self.filename.replace(filename); - self - } - - pub(crate) fn filename2(mut self, filename: impl Into<OsPathOrFd<'a>>) -> Self { - let filename = filename.into(); - self.filename2.replace(filename); - self - } - - pub(crate) fn with_filename( - error: &'a std::io::Error, +impl crate::exceptions::OSErrorBuilder { + #[must_use] + pub(crate) fn with_filename<'a>( + error: &std::io::Error, filename: impl Into<OsPathOrFd<'a>>, vm: &VirtualMachine, - ) -> PyBaseExceptionRef { - let zelf = IOErrorBuilder { - error, - filename: Some(filename.into()), - filename2: None, - }; - zelf.to_pyexception(vm) - } -} - -impl ToPyException for IOErrorBuilder<'_> { - fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef { - let exc = self.error.to_pyexception(vm); - - if let Some(filename) = &self.filename { - exc.as_object() - .set_attr("filename", filename.filename(vm), vm) - .unwrap(); - } - if let Some(filename2) = &self.filename2 { - exc.as_object() - .set_attr("filename2", filename2.filename(vm), vm) - .unwrap(); - } - exc + ) -> crate::builtins::PyBaseExceptionRef { + // TODO: return type to PyRef<PyOSError> + use crate::exceptions::ToOSErrorBuilder; + let builder = error.to_os_error_builder(vm); + let builder = builder.filename(filename.into().filename(vm)); + builder.build(vm).upcast() } } diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 3d67591d567..56bcbefddeb 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -14,11 +14,12 @@ use crate::{ builtins::{PyBaseExceptionRef, PyModule}, common::os::ErrorExt, convert::{IntoPyException, ToPyException}, + exceptions::{OSErrorBuilder, ToOSErrorBuilder}, }; pub use _io::{OpenArgs, io_open as open}; -impl ToPyException for std::io::Error { - fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef { +impl ToOSErrorBuilder for std::io::Error { + fn to_os_error_builder(&self, vm: &VirtualMachine) -> OSErrorBuilder { let errno = self.posix_errno(); #[cfg(windows)] let msg = 'msg: { @@ -53,23 +54,23 @@ impl ToPyException for std::io::Error { #[cfg(not(any(windows, unix)))] let msg = self.to_string(); - #[allow(clippy::let_and_return)] - let exc = vm.new_errno_error(errno, msg); - #[cfg(windows)] - { - use crate::object::AsObject; - let winerror = if let Some(winerror) = self.raw_os_error() { - vm.new_pyobj(winerror) - } else { - vm.ctx.none() - }; + #[allow(unused_mut)] + let mut builder = OSErrorBuilder::with_errno(errno, msg, vm); - // FIXME: manual setup winerror due to lack of OSError.__init__ support - exc.as_object() - .set_attr("winerror", vm.new_pyobj(winerror), vm) - .unwrap(); + #[cfg(windows)] + if let Some(winerror) = self.raw_os_error() { + use crate::convert::ToPyObject; + builder = builder.winerror(winerror.to_pyobject(vm)); } - exc.upcast() + + builder + } +} + +impl ToPyException for std::io::Error { + fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef { + let builder = self.to_os_error_builder(vm); + builder.into_pyexception(vm) } } @@ -4328,8 +4329,9 @@ mod fileio { builtins::{PyBaseExceptionRef, PyUtf8Str, PyUtf8StrRef}, common::crt_fd, convert::{IntoPyException, ToPyException}, + exceptions::OSErrorBuilder, function::{ArgBytesLike, ArgMemoryBuffer, OptionalArg, OptionalOption}, - ospath::{IOErrorBuilder, OsPath, OsPathOrFd}, + ospath::{OsPath, OsPathOrFd}, stdlib::os, types::{Constructor, DefaultConstructor, Destructor, Initializer, Representable}, }; @@ -4526,7 +4528,7 @@ mod fileio { let filename = OsPathOrFd::Path(path); match fd { Ok(fd) => (fd.into_raw(), Some(filename)), - Err(e) => return Err(IOErrorBuilder::with_filename(&e, filename, vm)), + Err(e) => return Err(OSErrorBuilder::with_filename(&e, filename, vm)), } } }; @@ -4541,7 +4543,7 @@ mod fileio { #[cfg(windows)] { if let Err(err) = fd_fstat { - return Err(IOErrorBuilder::with_filename(&err, filename, vm)); + return Err(OSErrorBuilder::with_filename(&err, filename, vm)); } } #[cfg(any(unix, target_os = "wasi"))] @@ -4550,12 +4552,12 @@ mod fileio { Ok(status) => { if (status.st_mode & libc::S_IFMT) == libc::S_IFDIR { let err = std::io::Error::from_raw_os_error(libc::EISDIR); - return Err(IOErrorBuilder::with_filename(&err, filename, vm)); + return Err(OSErrorBuilder::with_filename(&err, filename, vm)); } } Err(err) => { if err.raw_os_error() == Some(libc::EBADF) { - return Err(IOErrorBuilder::with_filename(&err, filename, vm)); + return Err(OSErrorBuilder::with_filename(&err, filename, vm)); } } } diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index f6ffd66759d..a698cae059c 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -153,6 +153,7 @@ pub(super) mod _os { AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, builtins::{ PyBytesRef, PyGenericAlias, PyIntRef, PyStrRef, PyTuple, PyTupleRef, PyTypeRef, + ToOSErrorBuilder, }, common::{ crt_fd, @@ -161,8 +162,9 @@ pub(super) mod _os { suppress_iph, }, convert::{IntoPyException, ToPyObject}, + exceptions::OSErrorBuilder, function::{ArgBytesLike, FsPath, FuncArgs, OptionalArg}, - ospath::{IOErrorBuilder, OsPath, OsPathOrFd, OutputMode}, + ospath::{OsPath, OsPathOrFd, OutputMode}, protocol::PyIterReturn, recursion::ReprGuard, types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter, Unconstructible}, @@ -263,7 +265,7 @@ pub(super) mod _os { crt_fd::open(&name, flags, mode) } }; - fd.map_err(|err| IOErrorBuilder::with_filename(&err, name, vm)) + fd.map_err(|err| OSErrorBuilder::with_filename(&err, name, vm)) } #[pyfunction] @@ -316,7 +318,7 @@ pub(super) mod _os { } else { fs::remove_file(&path) }; - res.map_err(|err| IOErrorBuilder::with_filename(&err, path, vm)) + res.map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } #[cfg(not(windows))] @@ -334,7 +336,7 @@ pub(super) mod _os { let res = unsafe { libc::mkdirat(fd, c_path.as_ptr(), mode as _) }; return if res < 0 { let err = crate::common::os::errno_io_error(); - Err(IOErrorBuilder::with_filename(&err, path, vm)) + Err(OSErrorBuilder::with_filename(&err, path, vm)) } else { Ok(()) }; @@ -344,7 +346,7 @@ pub(super) mod _os { let res = unsafe { libc::mkdir(c_path.as_ptr(), mode as _) }; if res < 0 { let err = crate::common::os::errno_io_error(); - return Err(IOErrorBuilder::with_filename(&err, path, vm)); + return Err(OSErrorBuilder::with_filename(&err, path, vm)); } Ok(()) } @@ -357,7 +359,7 @@ pub(super) mod _os { #[pyfunction] fn rmdir(path: OsPath, dir_fd: DirFd<'_, 0>, vm: &VirtualMachine) -> PyResult<()> { let [] = dir_fd.0; - fs::remove_dir(&path).map_err(|err| IOErrorBuilder::with_filename(&err, path, vm)) + fs::remove_dir(&path).map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } const LISTDIR_FD: bool = cfg!(all(unix, not(target_os = "redox"))); @@ -373,13 +375,13 @@ pub(super) mod _os { let dir_iter = match fs::read_dir(&path) { Ok(iter) => iter, Err(err) => { - return Err(IOErrorBuilder::with_filename(&err, path, vm)); + return Err(OSErrorBuilder::with_filename(&err, path, vm)); } }; dir_iter .map(|entry| match entry { Ok(entry_path) => Ok(path.mode.process_path(entry_path.file_name(), vm)), - Err(err) => Err(IOErrorBuilder::with_filename(&err, path.clone(), vm)), + Err(err) => Err(OSErrorBuilder::with_filename(&err, path.clone(), vm)), }) .collect::<PyResult<_>>()? } @@ -546,7 +548,7 @@ pub(super) mod _os { let mode = path.mode; let [] = dir_fd.0; let path = - fs::read_link(&path).map_err(|err| IOErrorBuilder::with_filename(&err, path, vm))?; + fs::read_link(&path).map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))?; Ok(mode.process_path(path, vm)) } @@ -859,7 +861,7 @@ pub(super) mod _os { fn scandir(path: OptionalArg<OsPath>, vm: &VirtualMachine) -> PyResult { let path = path.unwrap_or_else(|| OsPath::new_str(".")); let entries = fs::read_dir(&path.path) - .map_err(|err| IOErrorBuilder::with_filename(&err, path.clone(), vm))?; + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; Ok(ScandirIterator { entries: PyRwLock::new(Some(entries)), mode: path.mode, @@ -1084,7 +1086,7 @@ pub(super) mod _os { vm: &VirtualMachine, ) -> PyResult { let stat = stat_inner(file.clone(), dir_fd, follow_symlinks) - .map_err(|err| IOErrorBuilder::with_filename(&err, file, vm))? + .map_err(|err| OSErrorBuilder::with_filename(&err, file, vm))? .ok_or_else(|| crate::exceptions::cstring_error(vm))?; Ok(StatResultData::from_stat(&stat, vm).to_pyobject(vm)) } @@ -1115,7 +1117,7 @@ pub(super) mod _os { #[pyfunction] fn chdir(path: OsPath, vm: &VirtualMachine) -> PyResult<()> { env::set_current_dir(&path.path) - .map_err(|err| IOErrorBuilder::with_filename(&err, path, vm)) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } #[pyfunction] @@ -1127,10 +1129,10 @@ pub(super) mod _os { #[pyfunction(name = "replace")] fn rename(src: OsPath, dst: OsPath, vm: &VirtualMachine) -> PyResult<()> { fs::rename(&src.path, &dst.path).map_err(|err| { - IOErrorBuilder::new(&err) - .filename(src) - .filename2(dst) - .into_pyexception(vm) + let builder = err.to_os_error_builder(vm); + let builder = builder.filename(src.filename(vm)); + let builder = builder.filename2(dst.filename(vm)); + builder.build(vm).upcast() }) } @@ -1219,10 +1221,10 @@ pub(super) mod _os { #[pyfunction] fn link(src: OsPath, dst: OsPath, vm: &VirtualMachine) -> PyResult<()> { fs::hard_link(&src.path, &dst.path).map_err(|err| { - IOErrorBuilder::new(&err) - .filename(src) - .filename2(dst) - .into_pyexception(vm) + let builder = err.to_os_error_builder(vm); + let builder = builder.filename(src.filename(vm)); + let builder = builder.filename2(dst.filename(vm)); + builder.build(vm).upcast() }) } @@ -1334,7 +1336,7 @@ pub(super) mod _os { ) }; if ret < 0 { - Err(IOErrorBuilder::with_filename( + Err(OSErrorBuilder::with_filename( &io::Error::last_os_error(), path_for_err, vm, @@ -1385,14 +1387,14 @@ pub(super) mod _os { .write(true) .custom_flags(windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS) .open(&path) - .map_err(|err| IOErrorBuilder::with_filename(&err, path.clone(), vm))?; + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; let ret = unsafe { FileSystem::SetFileTime(f.as_raw_handle() as _, std::ptr::null(), &acc, &modif) }; if ret == 0 { - Err(IOErrorBuilder::with_filename( + Err(OSErrorBuilder::with_filename( &io::Error::last_os_error(), path, vm, @@ -1565,7 +1567,7 @@ pub(super) mod _os { error: std::io::Error, path: OsPath, ) -> crate::builtins::PyBaseExceptionRef { - IOErrorBuilder::with_filename(&error, path, vm) + OSErrorBuilder::with_filename(&error, path, vm) } let path = OsPath::try_from_object(vm, path)?; diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 680e9914a03..cfe605733d3 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -26,8 +26,9 @@ pub mod module { AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::{PyDictRef, PyInt, PyListRef, PyStrRef, PyTupleRef, PyType, PyUtf8StrRef}, convert::{IntoPyException, ToPyObject, TryFromObject}, + exceptions::OSErrorBuilder, function::{Either, KwArgs, OptionalArg}, - ospath::{IOErrorBuilder, OsPath, OsPathOrFd}, + ospath::{OsPath, OsPathOrFd}, stdlib::os::{_os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, fs_metadata}, types::{Constructor, Representable}, utils::ToCString, @@ -412,7 +413,7 @@ pub mod module { } let metadata = - metadata.map_err(|err| IOErrorBuilder::with_filename(&err, path.clone(), vm))?; + metadata.map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; let user_id = metadata.uid(); let group_id = metadata.gid(); @@ -482,12 +483,12 @@ pub mod module { #[cfg(not(target_os = "redox"))] #[pyfunction] fn chroot(path: OsPath, vm: &VirtualMachine) -> PyResult<()> { - use crate::ospath::IOErrorBuilder; + use crate::exceptions::OSErrorBuilder; nix::unistd::chroot(&*path.path).map_err(|err| { // Use `From<nix::Error> for io::Error` when it is available - let err = io::Error::from_raw_os_error(err as i32); - IOErrorBuilder::with_filename(&err, path, vm) + let io_err: io::Error = err.into(); + OSErrorBuilder::with_filename(&io_err, path, vm) }) } @@ -533,7 +534,7 @@ pub mod module { .map_err(|err| { // Use `From<nix::Error> for io::Error` when it is available let err = io::Error::from_raw_os_error(err as i32); - IOErrorBuilder::with_filename(&err, path, vm) + OSErrorBuilder::with_filename(&err, path, vm) }) } @@ -1031,7 +1032,7 @@ pub mod module { permissions.set_mode(mode); fs::set_permissions(&path, permissions) }; - body().map_err(|err| IOErrorBuilder::with_filename(&err, err_path, vm)) + body().map_err(|err| OSErrorBuilder::with_filename(&err, err_path, vm)) } #[cfg(not(target_os = "redox"))] @@ -1093,7 +1094,7 @@ pub mod module { Ok(()) } else { let err = std::io::Error::last_os_error(); - Err(IOErrorBuilder::with_filename(&err, path, vm)) + Err(OSErrorBuilder::with_filename(&err, path, vm)) } } @@ -1554,7 +1555,7 @@ pub mod module { }; if let Err(err) = ret { let err = err.into(); - return Err(IOErrorBuilder::with_filename(&err, self.path, vm)); + return Err(OSErrorBuilder::with_filename(&err, self.path, vm)); } } } @@ -1653,7 +1654,7 @@ pub mod module { nix::spawn::posix_spawn(&*path, &file_actions, &attrp, &args, &env) }; ret.map(Into::into) - .map_err(|err| IOErrorBuilder::with_filename(&err.into(), self.path, vm)) + .map_err(|err| OSErrorBuilder::with_filename(&err.into(), self.path, vm)) } } @@ -2126,7 +2127,7 @@ pub mod module { if Errno::last_raw() == 0 { Ok(None) } else { - Err(IOErrorBuilder::with_filename( + Err(OSErrorBuilder::with_filename( &io::Error::from(Errno::last()), path, vm, diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 36481e5dbe3..6d0e983c844 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -1,5 +1,5 @@ use crate::{ - AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, + AsObject, Py, PyObject, PyObjectRef, PyRef, builtins::{ PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyOSError, PyStrRef, PyType, PyTypeRef, @@ -8,9 +8,9 @@ use crate::{ tuple::{IntoPyTuple, PyTupleRef}, }, convert::{ToPyException, ToPyObject}, + exceptions::OSErrorBuilder, function::{IntoPyNativeFn, PyMethodFlags}, scope::Scope, - types::Constructor, vm::VirtualMachine, }; use rustpython_compiler_core::SourceLocation; @@ -119,26 +119,8 @@ impl VirtualMachine { msg: impl ToPyObject, ) -> PyRef<PyOSError> { debug_assert_eq!(exc_type.slots.basicsize, std::mem::size_of::<PyOSError>()); - let msg = msg.to_pyobject(self); - - fn new_os_subtype_error_impl( - vm: &VirtualMachine, - exc_type: PyTypeRef, - errno: Option<i32>, - msg: PyObjectRef, - ) -> PyRef<PyOSError> { - let args = match errno { - Some(e) => vec![vm.new_pyobj(e), msg], - None => vec![msg], - }; - let payload = - PyOSError::py_new(&exc_type, args.into(), vm).expect("new_os_error usage error"); - payload - .into_ref_with_type(vm, exc_type) - .expect("new_os_error usage error") - } - new_os_subtype_error_impl(self, exc_type, errno, msg) + OSErrorBuilder::with_subtype(exc_type, errno, msg, self).build(self) } /// Instantiate an exception with no arguments. From aef4de4ab883a8a442e956da5ce0cccf66466a8f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 16 Dec 2025 23:27:21 +0900 Subject: [PATCH 516/819] fix buffer (#6447) --- .cspell.dict/cpython.txt | 3 +++ crates/vm/src/buffer.rs | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 26921e04080..8acb1468f66 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -15,6 +15,7 @@ cellvar cellvars cmpop denom +DICTFLAG dictoffset elts excepthandler @@ -28,6 +29,7 @@ heaptype HIGHRES IMMUTABLETYPE Itertool +keeped kwonlyarg kwonlyargs lasti @@ -48,6 +50,7 @@ PYTHREAD_NAME SA_ONSTACK SOABI stackdepth +stginfo stringlib structseq subparams diff --git a/crates/vm/src/buffer.rs b/crates/vm/src/buffer.rs index 13cebfc6a28..cf49d6815c0 100644 --- a/crates/vm/src/buffer.rs +++ b/crates/vm/src/buffer.rs @@ -238,10 +238,23 @@ impl FormatCode { _ => 1, }; + // Skip whitespace (Python ignores whitespace in format strings) + while let Some(b' ' | b'\t' | b'\n' | b'\r') = chars.peek() { + chars.next(); + } + // determine format char: - let c = chars - .next() - .ok_or_else(|| "repeat count given without format specifier".to_owned())?; + let c = match chars.next() { + Some(c) => c, + None => { + // If we have a repeat count but only whitespace follows, error + if repeat != 1 { + return Err("repeat count given without format specifier".to_owned()); + } + // Otherwise, we're done parsing + break; + } + }; // Check for embedded null character if c == 0 { From 246fab63f7cc89fa1a0ba423b7ada2bbce6f31f1 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 17 Dec 2025 00:12:03 +0900 Subject: [PATCH 517/819] flag: DISALLOW_INSTANTIATION (#6445) * Set tp_new slot when build heap/static type * Improve type tp_call impl to check tp_new existence and error if not exist * Set DISALLOW_INSTANTIATION flag on several types according to cpython impl * Allow #[pyslot] for function pointer * Fix DISALLOW_INSTANTIATION --------- Signed-off-by: snowapril <sinjihng@gmail.com> Co-authored-by: snowapril <sinjihng@gmail.com> --- Lib/test/test_descr.py | 1 - crates/derive-impl/src/pyclass.rs | 18 ++---- crates/stdlib/src/array.rs | 2 +- crates/stdlib/src/csv.rs | 4 +- crates/stdlib/src/pystruct.rs | 6 +- crates/stdlib/src/sqlite.rs | 10 +-- crates/stdlib/src/unicodedata.rs | 2 +- crates/stdlib/src/zlib.rs | 4 +- crates/vm/src/builtins/asyncgenerator.rs | 6 +- crates/vm/src/builtins/builtin_func.rs | 12 ++-- crates/vm/src/builtins/bytearray.rs | 6 +- crates/vm/src/builtins/bytes.rs | 5 +- crates/vm/src/builtins/coroutine.rs | 6 +- crates/vm/src/builtins/descriptor.rs | 15 ++--- crates/vm/src/builtins/dict.rs | 62 +++++++++--------- crates/vm/src/builtins/frame.rs | 6 +- crates/vm/src/builtins/generator.rs | 6 +- crates/vm/src/builtins/getset.rs | 5 +- crates/vm/src/builtins/list.rs | 8 +-- crates/vm/src/builtins/memory.rs | 5 +- crates/vm/src/builtins/range.rs | 8 +-- crates/vm/src/builtins/set.rs | 5 +- crates/vm/src/builtins/str.rs | 6 +- crates/vm/src/builtins/tuple.rs | 5 +- crates/vm/src/builtins/type.rs | 81 +++++++++++++++++++++--- crates/vm/src/class.rs | 24 +++++-- crates/vm/src/protocol/buffer.rs | 5 +- crates/vm/src/stdlib/ctypes/field.rs | 6 +- crates/vm/src/stdlib/os.rs | 9 +-- crates/vm/src/types/slot.rs | 9 --- 30 files changed, 184 insertions(+), 163 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index f592a88fc2c..8c711207fae 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -1788,7 +1788,6 @@ class D(C): self.assertEqual(b.foo, 3) self.assertEqual(b.__class__, D) - @unittest.expectedFailure def test_bad_new(self): self.assertRaises(TypeError, object.__new__) self.assertRaises(TypeError, object.__new__, '') diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 5060dced2b0..f784a2e2a76 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -954,7 +954,10 @@ where } else if let Ok(f) = args.item.function_or_method() { (&f.sig().ident, f.span()) } else { - return Err(self.new_syn_error(args.item.span(), "can only be on a method")); + return Err(self.new_syn_error( + args.item.span(), + "can only be on a method or const function pointer", + )); }; let item_attr = args.attrs.remove(self.index()); @@ -1496,7 +1499,9 @@ impl SlotItemMeta { } } else { let ident_str = self.inner().item_name(); - let name = if let Some(stripped) = ident_str.strip_prefix("slot_") { + // Convert to lowercase to handle both SLOT_NEW and slot_new + let ident_lower = ident_str.to_lowercase(); + let name = if let Some(stripped) = ident_lower.strip_prefix("slot_") { proc_macro2::Ident::new(stripped, inner.item_ident.span()) } else { inner.item_ident.clone() @@ -1609,7 +1614,6 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac }]; let mut payload = None; - let mut has_constructor = false; for attr in attr { match attr { NestedMeta::Meta(Meta::List(MetaList { path, nested, .. })) => { @@ -1634,9 +1638,6 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac "Try `#[pyclass(with(Constructor, ...))]` instead of `#[pyclass(with(DefaultConstructor, ...))]`. DefaultConstructor implicitly implements Constructor." ) } - if path.is_ident("Constructor") || path.is_ident("Unconstructible") { - has_constructor = true; - } ( quote!(<Self as #path>::__extend_py_class), quote!(<Self as #path>::__OWN_METHOD_DEFS), @@ -1689,11 +1690,6 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac attr => bail_span!(attr, "Unknown pyimpl attribute"), } } - // TODO: DISALLOW_INSTANTIATION check is required - let _ = has_constructor; - // if !withs.is_empty() && !has_constructor { - // bail_span!(item, "#[pyclass(with(...))] does not have a Constructor. Either #[pyclass(with(Constructor, ...))] or #[pyclass(with(Unconstructible, ...))] is mandatory. Consider to add `impl DefaultConstructor for T {{}}` or `impl Unconstructible for T {{}}`.") - // } Ok(ExtractedImplAttrs { payload, diff --git a/crates/stdlib/src/array.rs b/crates/stdlib/src/array.rs index 4fcba1f8725..49ca4a89037 100644 --- a/crates/stdlib/src/array.rs +++ b/crates/stdlib/src/array.rs @@ -1399,7 +1399,7 @@ mod array { internal: PyMutex<PositionIterInternal<PyArrayRef>>, } - #[pyclass(with(IterNext, Iterable), flags(HAS_DICT))] + #[pyclass(with(IterNext, Iterable), flags(HAS_DICT, DISALLOW_INSTANTIATION))] impl PyArrayIter { #[pymethod] fn __setstate__(&self, state: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index 3c7cc2ff807..792402e0580 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -908,7 +908,7 @@ mod _csv { } } - #[pyclass(with(IterNext, Iterable))] + #[pyclass(with(IterNext, Iterable), flags(DISALLOW_INSTANTIATION))] impl Reader { #[pygetset] fn line_num(&self) -> u64 { @@ -1059,7 +1059,7 @@ mod _csv { } } - #[pyclass] + #[pyclass(flags(DISALLOW_INSTANTIATION))] impl Writer { #[pygetset(name = "dialect")] const fn get_dialect(&self, _vm: &VirtualMachine) -> PyDialect { diff --git a/crates/stdlib/src/pystruct.rs b/crates/stdlib/src/pystruct.rs index 798e5f5de80..0a006f5a0f2 100644 --- a/crates/stdlib/src/pystruct.rs +++ b/crates/stdlib/src/pystruct.rs @@ -16,7 +16,7 @@ pub(crate) mod _struct { function::{ArgBytesLike, ArgMemoryBuffer, PosArgs}, match_class, protocol::PyIterReturn, - types::{Constructor, IterNext, Iterable, Representable, SelfIter, Unconstructible}, + types::{Constructor, IterNext, Iterable, Representable, SelfIter}, }; use crossbeam_utils::atomic::AtomicCell; @@ -189,7 +189,7 @@ pub(crate) mod _struct { } } - #[pyclass(with(Unconstructible, IterNext, Iterable))] + #[pyclass(with(IterNext, Iterable), flags(DISALLOW_INSTANTIATION))] impl UnpackIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -197,7 +197,7 @@ pub(crate) mod _struct { } } impl SelfIter for UnpackIterator {} - impl Unconstructible for UnpackIterator {} + impl IterNext for UnpackIterator { fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> { let size = zelf.format_spec.size; diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index deff3c3a66a..bc84cffbf80 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -75,7 +75,7 @@ mod _sqlite { sliceable::{SaturatedSliceIter, SliceableSequenceOp}, types::{ AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable, - Initializer, IterNext, Iterable, PyComparisonOp, SelfIter, Unconstructible, + Initializer, IterNext, Iterable, PyComparisonOp, SelfIter, }, utils::ToCString, }; @@ -2197,8 +2197,6 @@ mod _sqlite { inner: PyMutex<Option<BlobInner>>, } - impl Unconstructible for Blob {} - #[derive(Debug)] struct BlobInner { blob: SqliteBlob, @@ -2211,7 +2209,7 @@ mod _sqlite { } } - #[pyclass(with(AsMapping, Unconstructible, AsNumber, AsSequence))] + #[pyclass(flags(DISALLOW_INSTANTIATION), with(AsMapping, AsNumber, AsSequence))] impl Blob { #[pymethod] fn close(&self) { @@ -2592,9 +2590,7 @@ mod _sqlite { } } - impl Unconstructible for Statement {} - - #[pyclass(with(Unconstructible))] + #[pyclass(flags(DISALLOW_INSTANTIATION))] impl Statement { fn new( connection: &Connection, diff --git a/crates/stdlib/src/unicodedata.rs b/crates/stdlib/src/unicodedata.rs index 46e18357260..68d9a17e575 100644 --- a/crates/stdlib/src/unicodedata.rs +++ b/crates/stdlib/src/unicodedata.rs @@ -105,7 +105,7 @@ mod unicodedata { } } - #[pyclass] + #[pyclass(flags(DISALLOW_INSTANTIATION))] impl Ucd { #[pymethod] fn category(&self, character: PyStrRef, vm: &VirtualMachine) -> PyResult<String> { diff --git a/crates/stdlib/src/zlib.rs b/crates/stdlib/src/zlib.rs index 328452ae9d5..9ca94939f78 100644 --- a/crates/stdlib/src/zlib.rs +++ b/crates/stdlib/src/zlib.rs @@ -225,7 +225,7 @@ mod zlib { inner: PyMutex<PyDecompressInner>, } - #[pyclass] + #[pyclass(flags(DISALLOW_INSTANTIATION))] impl PyDecompress { #[pygetset] fn eof(&self) -> bool { @@ -383,7 +383,7 @@ mod zlib { inner: PyMutex<CompressState<CompressInner>>, } - #[pyclass] + #[pyclass(flags(DISALLOW_INSTANTIATION))] impl PyCompress { #[pymethod] fn compress(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<Vec<u8>> { diff --git a/crates/vm/src/builtins/asyncgenerator.rs b/crates/vm/src/builtins/asyncgenerator.rs index 073513184ff..483ba6a7f96 100644 --- a/crates/vm/src/builtins/asyncgenerator.rs +++ b/crates/vm/src/builtins/asyncgenerator.rs @@ -8,7 +8,7 @@ use crate::{ frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, - types::{IterNext, Iterable, Representable, SelfIter, Unconstructible}, + types::{IterNext, Iterable, Representable, SelfIter}, }; use crossbeam_utils::atomic::AtomicCell; @@ -32,7 +32,7 @@ impl PyPayload for PyAsyncGen { } } -#[pyclass(with(PyRef, Unconstructible, Representable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(PyRef, Representable))] impl PyAsyncGen { pub const fn as_coro(&self) -> &Coro { &self.inner @@ -201,8 +201,6 @@ impl Representable for PyAsyncGen { } } -impl Unconstructible for PyAsyncGen {} - #[pyclass(module = false, name = "async_generator_wrapped_value")] #[derive(Debug)] pub(crate) struct PyAsyncGenWrappedValue(pub PyObjectRef); diff --git a/crates/vm/src/builtins/builtin_func.rs b/crates/vm/src/builtins/builtin_func.rs index d1ce107e374..d25188affd2 100644 --- a/crates/vm/src/builtins/builtin_func.rs +++ b/crates/vm/src/builtins/builtin_func.rs @@ -5,7 +5,7 @@ use crate::{ common::wtf8::Wtf8, convert::TryFromObject, function::{FuncArgs, PyComparisonValue, PyMethodDef, PyMethodFlags, PyNativeFn}, - types::{Callable, Comparable, PyComparisonOp, Representable, Unconstructible}, + types::{Callable, Comparable, PyComparisonOp, Representable}, }; use std::fmt; @@ -74,7 +74,7 @@ impl Callable for PyNativeFunction { } } -#[pyclass(with(Callable, Unconstructible), flags(HAS_DICT))] +#[pyclass(with(Callable), flags(HAS_DICT, DISALLOW_INSTANTIATION))] impl PyNativeFunction { #[pygetset] fn __module__(zelf: NativeFunctionOrMethod) -> Option<&'static PyStrInterned> { @@ -145,8 +145,6 @@ impl Representable for PyNativeFunction { } } -impl Unconstructible for PyNativeFunction {} - // `PyCMethodObject` in CPython #[pyclass(name = "builtin_method", module = false, base = PyNativeFunction, ctx = "builtin_method_type")] pub struct PyNativeMethod { @@ -155,8 +153,8 @@ pub struct PyNativeMethod { } #[pyclass( - with(Unconstructible, Callable, Comparable, Representable), - flags(HAS_DICT) + with(Callable, Comparable, Representable), + flags(HAS_DICT, DISALLOW_INSTANTIATION) )] impl PyNativeMethod { #[pygetset] @@ -246,8 +244,6 @@ impl Representable for PyNativeMethod { } } -impl Unconstructible for PyNativeMethod {} - pub fn init(context: &Context) { PyNativeFunction::extend_class(context, context.types.builtin_function_or_method_type); PyNativeMethod::extend_class(context, context.types.builtin_method_type); diff --git a/crates/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs index 32eaa2b3e27..c5861befb73 100644 --- a/crates/vm/src/builtins/bytearray.rs +++ b/crates/vm/src/builtins/bytearray.rs @@ -33,7 +33,7 @@ use crate::{ types::{ AsBuffer, AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, DefaultConstructor, Initializer, IterNext, Iterable, PyComparisonOp, Representable, - SelfIter, Unconstructible, + SelfIter, }, }; use bstr::ByteSlice; @@ -865,7 +865,7 @@ impl PyPayload for PyByteArrayIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyByteArrayIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -886,8 +886,6 @@ impl PyByteArrayIterator { } } -impl Unconstructible for PyByteArrayIterator {} - impl SelfIter for PyByteArrayIterator {} impl IterNext for PyByteArrayIterator { fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> { diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index 70a33401271..f782c035f86 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -25,7 +25,7 @@ use crate::{ sliceable::{SequenceIndex, SliceableSequenceOp}, types::{ AsBuffer, AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable, - IterNext, Iterable, PyComparisonOp, Representable, SelfIter, Unconstructible, + IterNext, Iterable, PyComparisonOp, Representable, SelfIter, }, }; use bstr::ByteSlice; @@ -749,7 +749,7 @@ impl PyPayload for PyBytesIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyBytesIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -770,7 +770,6 @@ impl PyBytesIterator { .set_state(state, |obj, pos| pos.min(obj.len()), vm) } } -impl Unconstructible for PyBytesIterator {} impl SelfIter for PyBytesIterator {} impl IterNext for PyBytesIterator { diff --git a/crates/vm/src/builtins/coroutine.rs b/crates/vm/src/builtins/coroutine.rs index 0909cdfb444..21405448693 100644 --- a/crates/vm/src/builtins/coroutine.rs +++ b/crates/vm/src/builtins/coroutine.rs @@ -6,7 +6,7 @@ use crate::{ frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, - types::{IterNext, Iterable, Representable, SelfIter, Unconstructible}, + types::{IterNext, Iterable, Representable, SelfIter}, }; use crossbeam_utils::atomic::AtomicCell; @@ -24,7 +24,7 @@ impl PyPayload for PyCoroutine { } } -#[pyclass(with(Py, Unconstructible, IterNext, Representable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(Py, IterNext, Representable))] impl PyCoroutine { pub const fn as_coro(&self) -> &Coro { &self.inner @@ -123,8 +123,6 @@ impl Py<PyCoroutine> { } } -impl Unconstructible for PyCoroutine {} - impl Representable for PyCoroutine { #[inline] fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> { diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index bc3aded3253..cdcc456edfc 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -4,7 +4,7 @@ use crate::{ builtins::{PyTypeRef, builtin_func::PyNativeMethod, type_}, class::PyClassImpl, function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue}, - types::{Callable, GetDescriptor, Representable, Unconstructible}, + types::{Callable, GetDescriptor, Representable}, }; use rustpython_common::lock::PyRwLock; @@ -105,8 +105,8 @@ impl PyMethodDescriptor { } #[pyclass( - with(GetDescriptor, Callable, Unconstructible, Representable), - flags(METHOD_DESCRIPTOR) + with(GetDescriptor, Callable, Representable), + flags(METHOD_DESCRIPTOR, DISALLOW_INSTANTIATION) )] impl PyMethodDescriptor { #[pygetset] @@ -159,8 +159,6 @@ impl Representable for PyMethodDescriptor { } } -impl Unconstructible for PyMethodDescriptor {} - #[derive(Debug)] pub enum MemberKind { Bool = 14, @@ -246,7 +244,10 @@ fn calculate_qualname(descr: &PyDescriptorOwned, vm: &VirtualMachine) -> PyResul } } -#[pyclass(with(GetDescriptor, Unconstructible, Representable), flags(BASETYPE))] +#[pyclass( + with(GetDescriptor, Representable), + flags(BASETYPE, DISALLOW_INSTANTIATION) +)] impl PyMemberDescriptor { #[pygetset] fn __doc__(&self) -> Option<String> { @@ -339,8 +340,6 @@ fn set_slot_at_object( Ok(()) } -impl Unconstructible for PyMemberDescriptor {} - impl Representable for PyMemberDescriptor { #[inline] fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 77126d4ee62..b34299d4170 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -19,7 +19,7 @@ use crate::{ recursion::ReprGuard, types::{ AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, DefaultConstructor, - Initializer, IterNext, Iterable, PyComparisonOp, Representable, SelfIter, Unconstructible, + Initializer, IterNext, Iterable, PyComparisonOp, Representable, SelfIter, }, vm::VirtualMachine, }; @@ -848,7 +848,7 @@ macro_rules! dict_view { } } - #[pyclass(with(Unconstructible, IterNext, Iterable))] + #[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl $iter_name { fn new(dict: PyDictRef) -> Self { $iter_name { @@ -878,8 +878,6 @@ macro_rules! dict_view { } } - impl Unconstructible for $iter_name {} - impl SelfIter for $iter_name {} impl IterNext for $iter_name { #[allow(clippy::redundant_closure_call)] @@ -923,7 +921,7 @@ macro_rules! dict_view { } } - #[pyclass(with(Unconstructible, IterNext, Iterable))] + #[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl $reverse_iter_name { fn new(dict: PyDictRef) -> Self { let size = dict.size(); @@ -957,8 +955,6 @@ macro_rules! dict_view { .rev_length_hint(|_| self.size.entries_size) } } - impl Unconstructible for $reverse_iter_name {} - impl SelfIter for $reverse_iter_name {} impl IterNext for $reverse_iter_name { #[allow(clippy::redundant_closure_call)] @@ -1126,16 +1122,18 @@ trait ViewSetOps: DictView { } impl ViewSetOps for PyDictKeys {} -#[pyclass(with( - DictView, - Unconstructible, - Comparable, - Iterable, - ViewSetOps, - AsSequence, - AsNumber, - Representable -))] +#[pyclass( + flags(DISALLOW_INSTANTIATION), + with( + DictView, + Comparable, + Iterable, + ViewSetOps, + AsSequence, + AsNumber, + Representable + ) +)] impl PyDictKeys { #[pymethod] fn __contains__(zelf: PyObjectRef, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { @@ -1147,7 +1145,6 @@ impl PyDictKeys { PyMappingProxy::from(zelf.dict().clone()) } } -impl Unconstructible for PyDictKeys {} impl Comparable for PyDictKeys { fn cmp( @@ -1190,16 +1187,18 @@ impl AsNumber for PyDictKeys { } impl ViewSetOps for PyDictItems {} -#[pyclass(with( - DictView, - Unconstructible, - Comparable, - Iterable, - ViewSetOps, - AsSequence, - AsNumber, - Representable -))] +#[pyclass( + flags(DISALLOW_INSTANTIATION), + with( + DictView, + Comparable, + Iterable, + ViewSetOps, + AsSequence, + AsNumber, + Representable + ) +)] impl PyDictItems { #[pymethod] fn __contains__(zelf: PyObjectRef, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { @@ -1210,7 +1209,6 @@ impl PyDictItems { PyMappingProxy::from(zelf.dict().clone()) } } -impl Unconstructible for PyDictItems {} impl Comparable for PyDictItems { fn cmp( @@ -1264,14 +1262,16 @@ impl AsNumber for PyDictItems { } } -#[pyclass(with(DictView, Unconstructible, Iterable, AsSequence, Representable))] +#[pyclass( + flags(DISALLOW_INSTANTIATION), + with(DictView, Iterable, AsSequence, Representable) +)] impl PyDictValues { #[pygetset] fn mapping(zelf: PyRef<Self>) -> PyMappingProxy { PyMappingProxy::from(zelf.dict().clone()) } } -impl Unconstructible for PyDictValues {} impl AsSequence for PyDictValues { fn as_sequence() -> &'static PySequenceMethods { diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index 17dc88ac042..6ccc594d338 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -8,7 +8,7 @@ use crate::{ class::PyClassImpl, frame::{Frame, FrameRef}, function::PySetterValue, - types::{Representable, Unconstructible}, + types::Representable, }; use num_traits::Zero; @@ -16,8 +16,6 @@ pub fn init(context: &Context) { Frame::extend_class(context, context.types.frame_type); } -impl Unconstructible for Frame {} - impl Representable for Frame { #[inline] fn repr(_zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> { @@ -31,7 +29,7 @@ impl Representable for Frame { } } -#[pyclass(with(Unconstructible, Py))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(Py))] impl Frame { #[pymethod] const fn clear(&self) { diff --git a/crates/vm/src/builtins/generator.rs b/crates/vm/src/builtins/generator.rs index da981b5a6c2..04cd7dd3456 100644 --- a/crates/vm/src/builtins/generator.rs +++ b/crates/vm/src/builtins/generator.rs @@ -10,7 +10,7 @@ use crate::{ frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, - types::{IterNext, Iterable, Representable, SelfIter, Unconstructible}, + types::{IterNext, Iterable, Representable, SelfIter}, }; #[pyclass(module = false, name = "generator")] @@ -26,7 +26,7 @@ impl PyPayload for PyGenerator { } } -#[pyclass(with(Py, Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(Py, IterNext, Iterable))] impl PyGenerator { pub const fn as_coro(&self) -> &Coro { &self.inner @@ -114,8 +114,6 @@ impl Py<PyGenerator> { } } -impl Unconstructible for PyGenerator {} - impl Representable for PyGenerator { #[inline] fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> { diff --git a/crates/vm/src/builtins/getset.rs b/crates/vm/src/builtins/getset.rs index 4b966bbc31b..f56191f5f8b 100644 --- a/crates/vm/src/builtins/getset.rs +++ b/crates/vm/src/builtins/getset.rs @@ -7,7 +7,7 @@ use crate::{ builtins::type_::PointerSlot, class::PyClassImpl, function::{IntoPyGetterFunc, IntoPySetterFunc, PyGetterFunc, PySetterFunc, PySetterValue}, - types::{GetDescriptor, Unconstructible}, + types::GetDescriptor, }; #[pyclass(module = false, name = "getset_descriptor")] @@ -96,7 +96,7 @@ impl PyGetSet { } } -#[pyclass(with(GetDescriptor, Unconstructible))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(GetDescriptor))] impl PyGetSet { // Descriptor methods @@ -152,7 +152,6 @@ impl PyGetSet { Ok(unsafe { zelf.class.borrow_static() }.to_owned().into()) } } -impl Unconstructible for PyGetSet {} pub(crate) fn init(context: &Context) { PyGetSet::extend_class(context, context.types.getset_type); diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index b2927462cac..0683381f38a 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -15,7 +15,7 @@ use crate::{ sliceable::{SequenceIndex, SliceableSequenceMutOp, SliceableSequenceOp}, types::{ AsMapping, AsSequence, Comparable, Constructor, Initializer, IterNext, Iterable, - PyComparisonOp, Representable, SelfIter, Unconstructible, + PyComparisonOp, Representable, SelfIter, }, utils::collection_repr, vm::VirtualMachine, @@ -544,7 +544,7 @@ impl PyPayload for PyListIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyListIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -565,7 +565,6 @@ impl PyListIterator { .builtins_iter_reduce(|x| x.clone().into(), vm) } } -impl Unconstructible for PyListIterator {} impl SelfIter for PyListIterator {} impl IterNext for PyListIterator { @@ -590,7 +589,7 @@ impl PyPayload for PyListReverseIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyListReverseIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -611,7 +610,6 @@ impl PyListReverseIterator { .builtins_reversed_reduce(|x| x.clone().into(), vm) } } -impl Unconstructible for PyListReverseIterator {} impl SelfIter for PyListReverseIterator {} impl IterNext for PyListReverseIterator { diff --git a/crates/vm/src/builtins/memory.rs b/crates/vm/src/builtins/memory.rs index c1b1496e8c6..4e895f92b7e 100644 --- a/crates/vm/src/builtins/memory.rs +++ b/crates/vm/src/builtins/memory.rs @@ -23,7 +23,7 @@ use crate::{ sliceable::SequenceIndexOp, types::{ AsBuffer, AsMapping, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable, - PyComparisonOp, Representable, SelfIter, Unconstructible, + PyComparisonOp, Representable, SelfIter, }, }; use crossbeam_utils::atomic::AtomicCell; @@ -1132,7 +1132,7 @@ impl PyPayload for PyMemoryViewIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyMemoryViewIterator { #[pymethod] fn __reduce__(&self, vm: &VirtualMachine) -> PyTupleRef { @@ -1141,7 +1141,6 @@ impl PyMemoryViewIterator { .builtins_iter_reduce(|x| x.clone().into(), vm) } } -impl Unconstructible for PyMemoryViewIterator {} impl SelfIter for PyMemoryViewIterator {} impl IterNext for PyMemoryViewIterator { diff --git a/crates/vm/src/builtins/range.rs b/crates/vm/src/builtins/range.rs index 3edd130ee28..9f79f8efb2d 100644 --- a/crates/vm/src/builtins/range.rs +++ b/crates/vm/src/builtins/range.rs @@ -11,7 +11,7 @@ use crate::{ protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods}, types::{ AsMapping, AsSequence, Comparable, Hashable, IterNext, Iterable, PyComparisonOp, - Representable, SelfIter, Unconstructible, + Representable, SelfIter, }, }; use crossbeam_utils::atomic::AtomicCell; @@ -548,7 +548,7 @@ impl PyPayload for PyLongRangeIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyLongRangeIterator { #[pymethod] fn __length_hint__(&self) -> BigInt { @@ -577,7 +577,6 @@ impl PyLongRangeIterator { ) } } -impl Unconstructible for PyLongRangeIterator {} impl SelfIter for PyLongRangeIterator {} impl IterNext for PyLongRangeIterator { @@ -614,7 +613,7 @@ impl PyPayload for PyRangeIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyRangeIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -640,7 +639,6 @@ impl PyRangeIterator { ) } } -impl Unconstructible for PyRangeIterator {} impl SelfIter for PyRangeIterator {} impl IterNext for PyRangeIterator { diff --git a/crates/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs index 7fde8d32781..5582ff3323c 100644 --- a/crates/vm/src/builtins/set.rs +++ b/crates/vm/src/builtins/set.rs @@ -18,7 +18,7 @@ use crate::{ types::AsNumber, types::{ AsSequence, Comparable, Constructor, DefaultConstructor, Hashable, Initializer, IterNext, - Iterable, PyComparisonOp, Representable, SelfIter, Unconstructible, + Iterable, PyComparisonOp, Representable, SelfIter, }, utils::collection_repr, vm::VirtualMachine, @@ -1304,7 +1304,7 @@ impl PyPayload for PySetIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PySetIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -1330,7 +1330,6 @@ impl PySetIterator { )) } } -impl Unconstructible for PySetIterator {} impl SelfIter for PySetIterator {} impl IterNext for PySetIterator { diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 9b05e195722..279b84362a6 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -21,7 +21,7 @@ use crate::{ sliceable::{SequenceIndex, SliceableSequenceOp}, types::{ AsMapping, AsNumber, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable, - PyComparisonOp, Representable, SelfIter, Unconstructible, + PyComparisonOp, Representable, SelfIter, }, }; use ascii::{AsciiChar, AsciiStr, AsciiString}; @@ -282,7 +282,7 @@ impl PyPayload for PyStrIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyStrIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -307,8 +307,6 @@ impl PyStrIterator { } } -impl Unconstructible for PyStrIterator {} - impl SelfIter for PyStrIterator {} impl IterNext for PyStrIterator { diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index fada8840bb1..adc5b483de3 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -16,7 +16,7 @@ use crate::{ sliceable::{SequenceIndex, SliceableSequenceOp}, types::{ AsMapping, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable, - PyComparisonOp, Representable, SelfIter, Unconstructible, + PyComparisonOp, Representable, SelfIter, }, utils::collection_repr, vm::VirtualMachine, @@ -533,7 +533,7 @@ impl PyPayload for PyTupleIterator { } } -#[pyclass(with(Unconstructible, IterNext, Iterable))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl PyTupleIterator { #[pymethod] fn __length_hint__(&self) -> usize { @@ -554,7 +554,6 @@ impl PyTupleIterator { .builtins_iter_reduce(|x| x.clone().into(), vm) } } -impl Unconstructible for PyTupleIterator {} impl SelfIter for PyTupleIterator {} impl IterNext for PyTupleIterator { diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 0f619b1399a..d014cdf015e 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -191,13 +191,16 @@ impl PyType { name: &str, bases: Vec<PyRef<Self>>, attrs: PyAttributes, - slots: PyTypeSlots, + mut slots: PyTypeSlots, metaclass: PyRef<Self>, ctx: &Context, ) -> Result<PyRef<Self>, String> { // TODO: ensure clean slot name // assert_eq!(slots.name.borrow(), ""); + // Set HEAPTYPE flag for heap-allocated types + slots.flags |= PyTypeFlags::HEAPTYPE; + let name = ctx.new_str(name); let heaptype_ext = HeapTypeExt { name: PyRwLock::new(name.clone()), @@ -401,6 +404,8 @@ impl PyType { None, ); + Self::set_new(&new_type.slots, &new_type.base); + let weakref_type = super::PyWeak::static_type(); for base in new_type.bases.read().iter() { base.subclasses.write().push( @@ -420,9 +425,6 @@ impl PyType { for cls in self.mro.read().iter() { for &name in cls.attributes.read().keys() { - if name == identifier!(ctx, __new__) { - continue; - } if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") { slot_name_set.insert(name); } @@ -436,6 +438,20 @@ impl PyType { for attr_name in slot_name_set { self.update_slot::<true>(attr_name, ctx); } + + Self::set_new(&self.slots, &self.base); + } + + fn set_new(slots: &PyTypeSlots, base: &Option<PyTypeRef>) { + if slots.flags.contains(PyTypeFlags::DISALLOW_INSTANTIATION) { + slots.new.store(None) + } else if slots.new.load().is_none() { + slots.new.store( + base.as_ref() + .map(|base| base.slots.new.load()) + .unwrap_or(None), + ) + } } // This is used for class initialization where the vm is not yet available. @@ -1563,15 +1579,28 @@ impl Callable for PyType { type Args = FuncArgs; fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult { vm_trace!("type_call: {:?}", zelf); - let obj = call_slot_new(zelf.to_owned(), zelf.to_owned(), args.clone(), vm)?; - if (zelf.is(vm.ctx.types.type_type) && args.kwargs.is_empty()) || !obj.fast_isinstance(zelf) - { + if zelf.is(vm.ctx.types.type_type) { + let num_args = args.args.len(); + if num_args == 1 && args.kwargs.is_empty() { + return Ok(args.args[0].obj_type()); + } + if num_args != 3 { + return Err(vm.new_type_error("type() takes 1 or 3 arguments".to_owned())); + } + } + + let obj = if let Some(slot_new) = zelf.slots.new.load() { + slot_new(zelf.to_owned(), args.clone(), vm)? + } else { + return Err(vm.new_type_error(format!("cannot create '{}' instances", zelf.slots.name))); + }; + + if !obj.class().fast_issubclass(zelf) { return Ok(obj); } - let init = obj.class().mro_find_map(|cls| cls.slots.init.load()); - if let Some(init_method) = init { + if let Some(init_method) = obj.class().slots.init.load() { init_method(obj.clone(), args, vm)?; } Ok(obj) @@ -1700,6 +1729,40 @@ pub(crate) fn call_slot_new( args: FuncArgs, vm: &VirtualMachine, ) -> PyResult { + // Check DISALLOW_INSTANTIATION flag on subtype (the type being instantiated) + if subtype + .slots + .flags + .has_feature(PyTypeFlags::DISALLOW_INSTANTIATION) + { + return Err(vm.new_type_error(format!("cannot create '{}' instances", subtype.slot_name()))); + } + + // "is not safe" check (tp_new_wrapper logic) + // Check that the user doesn't do something silly and unsafe like + // object.__new__(dict). To do this, we check that the most derived base + // that's not a heap type is this type. + let mut staticbase = subtype.clone(); + while staticbase.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) { + if let Some(base) = staticbase.base.as_ref() { + staticbase = base.clone(); + } else { + break; + } + } + + // Check if staticbase's tp_new differs from typ's tp_new + let typ_new = typ.slots.new.load(); + let staticbase_new = staticbase.slots.new.load(); + if typ_new.map(|f| f as usize) != staticbase_new.map(|f| f as usize) { + return Err(vm.new_type_error(format!( + "{}.__new__({}) is not safe, use {}.__new__()", + typ.slot_name(), + subtype.slot_name(), + staticbase.slot_name() + ))); + } + let slot_new = typ .deref() .mro_find_map(|cls| cls.slots.new.load()) diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 6b5a02dea73..6a366385702 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -116,13 +116,23 @@ pub trait PyClassImpl: PyClassDef { ); } - if class.slots.new.load().is_some() { - let bound_new = Context::genesis().slot_new_wrapper.build_bound_method( - ctx, - class.to_owned().into(), - class, - ); - class.set_attr(identifier!(ctx, __new__), bound_new.into()); + // Don't add __new__ attribute if slot_new is inherited from object + // (Python doesn't add __new__ to __dict__ for inherited slots) + // Exception: object itself should have __new__ in its dict + if let Some(slot_new) = class.slots.new.load() { + let object_new = ctx.types.object_type.slots.new.load(); + let is_object_itself = std::ptr::eq(class, ctx.types.object_type); + let is_inherited_from_object = !is_object_itself + && object_new.is_some_and(|obj_new| slot_new as usize == obj_new as usize); + + if !is_inherited_from_object { + let bound_new = Context::genesis().slot_new_wrapper.build_bound_method( + ctx, + class.to_owned().into(), + class, + ); + class.set_attr(identifier!(ctx, __new__), bound_new.into()); + } } if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize { diff --git a/crates/vm/src/protocol/buffer.rs b/crates/vm/src/protocol/buffer.rs index 1b1a4a14df5..1dafda203d9 100644 --- a/crates/vm/src/protocol/buffer.rs +++ b/crates/vm/src/protocol/buffer.rs @@ -9,7 +9,6 @@ use crate::{ }, object::PyObjectPayload, sliceable::SequenceIndexOp, - types::Unconstructible, }; use itertools::Itertools; use std::{borrow::Cow, fmt::Debug, ops::Range}; @@ -402,7 +401,7 @@ pub struct VecBuffer { data: PyMutex<Vec<u8>>, } -#[pyclass(flags(BASETYPE), with(Unconstructible))] +#[pyclass(flags(BASETYPE, DISALLOW_INSTANTIATION))] impl VecBuffer { pub fn take(&self) -> Vec<u8> { std::mem::take(&mut self.data.lock()) @@ -417,8 +416,6 @@ impl From<Vec<u8>> for VecBuffer { } } -impl Unconstructible for VecBuffer {} - impl PyRef<VecBuffer> { pub fn into_pybuffer(self, readonly: bool) -> PyBuffer { let len = self.data.lock().len(); diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index e760f07d035..659255f3329 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -1,6 +1,6 @@ use crate::builtins::PyType; use crate::function::PySetterValue; -use crate::types::{GetDescriptor, Representable, Unconstructible}; +use crate::types::{GetDescriptor, Representable}; use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; use num_traits::ToPrimitive; @@ -85,8 +85,6 @@ impl Representable for PyCField { } } -impl Unconstructible for PyCField {} - impl GetDescriptor for PyCField { fn descr_get( zelf: PyObjectRef, @@ -184,7 +182,7 @@ impl PyCField { #[pyclass( flags(DISALLOW_INSTANTIATION, IMMUTABLETYPE), - with(Unconstructible, Representable, GetDescriptor) + with(Representable, GetDescriptor) )] impl PyCField { #[pyslot] diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index a698cae059c..868fc727c65 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -167,7 +167,7 @@ pub(super) mod _os { ospath::{OsPath, OsPathOrFd, OutputMode}, protocol::PyIterReturn, recursion::ReprGuard, - types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter, Unconstructible}, + types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter}, utils::ToCString, vm::VirtualMachine, }; @@ -570,7 +570,7 @@ pub(super) mod _os { ino: AtomicCell<Option<u64>>, } - #[pyclass(with(Representable, Unconstructible))] + #[pyclass(flags(DISALLOW_INSTANTIATION), with(Representable))] impl DirEntry { #[pygetset] fn name(&self, vm: &VirtualMachine) -> PyResult { @@ -760,8 +760,6 @@ pub(super) mod _os { } } } - impl Unconstructible for DirEntry {} - #[pyattr] #[pyclass(name = "ScandirIter")] #[derive(Debug, PyPayload)] @@ -770,7 +768,7 @@ pub(super) mod _os { mode: OutputMode, } - #[pyclass(with(IterNext, Iterable, Unconstructible))] + #[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))] impl ScandirIterator { #[pymethod] fn close(&self) { @@ -788,7 +786,6 @@ pub(super) mod _os { zelf.close() } } - impl Unconstructible for ScandirIterator {} impl SelfIter for ScandirIterator {} impl IterNext for ScandirIterator { fn next(zelf: &crate::Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> { diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index f52e7296a7b..5deb593818e 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -922,15 +922,6 @@ pub trait DefaultConstructor: PyPayload + Default + std::fmt::Debug { } } -/// For types that cannot be instantiated through Python code. -#[pyclass] -pub trait Unconstructible: PyPayload { - #[pyslot] - fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error(format!("cannot create '{}' instances", cls.slot_name()))) - } -} - impl<T> Constructor for T where T: DefaultConstructor, From 9aa1f189982ff561478f30315c19c47b9c9d9cde Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 17 Dec 2025 00:16:51 +0900 Subject: [PATCH 518/819] Unraiseable traceback (#6449) --- crates/vm/src/stdlib/sys.rs | 50 ++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 45b1d566058..52ecf927d5c 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -15,7 +15,7 @@ mod sys { }, convert::ToPyObject, frame::FrameRef, - function::{FuncArgs, OptionalArg, PosArgs}, + function::{FuncArgs, KwArgs, OptionalArg, PosArgs}, stdlib::{builtins, warnings::warn}, types::PyStructSequence, version, @@ -688,13 +688,21 @@ mod sys { writeln!(stderr, "{}:", unraisable.err_msg.str(vm)?); } - // TODO: print received unraisable.exc_traceback - let tb_module = vm.import("traceback", 0)?; - let print_stack = tb_module.get_attr("print_stack", vm)?; - print_stack.call((), vm)?; + // Print traceback (using actual exc_traceback, not current stack) + if !vm.is_none(&unraisable.exc_traceback) { + let tb_module = vm.import("traceback", 0)?; + let print_tb = tb_module.get_attr("print_tb", vm)?; + let stderr_obj = super::get_stderr(vm)?; + let kwargs: KwArgs = [("file".to_string(), stderr_obj)].into_iter().collect(); + let _ = print_tb.call( + FuncArgs::new(vec![unraisable.exc_traceback.clone()], kwargs), + vm, + ); + } + // Check exc_type if vm.is_none(unraisable.exc_type.as_object()) { - // TODO: early return, but with what error? + return Ok(()); } assert!( unraisable @@ -702,10 +710,28 @@ mod sys { .fast_issubclass(vm.ctx.exceptions.base_exception_type) ); - // TODO: print module name and qualname + // Print module name (if not builtins or __main__) + let module_name = unraisable.exc_type.__module__(vm); + if let Ok(module_str) = module_name.downcast::<PyStr>() { + let module = module_str.as_str(); + if module != "builtins" && module != "__main__" { + write!(stderr, "{}.", module); + } + } else { + write!(stderr, "<unknown>."); + } + // Print qualname + let qualname = unraisable.exc_type.__qualname__(vm); + if let Ok(qualname_str) = qualname.downcast::<PyStr>() { + write!(stderr, "{}", qualname_str.as_str()); + } else { + write!(stderr, "{}", unraisable.exc_type.name()); + } + + // Print exception value if !vm.is_none(&unraisable.exc_value) { - write!(stderr, "{}: ", unraisable.exc_type); + write!(stderr, ": "); if let Ok(str) = unraisable.exc_value.str(vm) { write!(stderr, "{}", str.to_str().unwrap_or("<str with surrogate>")); } else { @@ -713,7 +739,13 @@ mod sys { } } writeln!(stderr); - // TODO: call file.flush() + + // Flush stderr + if let Ok(stderr_obj) = super::get_stderr(vm) + && let Ok(flush) = stderr_obj.get_attr("flush", vm) + { + let _ = flush.call((), vm); + } Ok(()) } From adc2b0dbbe043095be17c037e3daa39b5351c96c Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:28:06 +0100 Subject: [PATCH 519/819] Update `test_zipfile64.py` from 3.13.11 (#6433) * Update `test_zipfile64.py` from 3.13.11 * Mark flaky test --- Lib/test/test_import/__init__.py | 1 + Lib/test/test_zipfile64.py | 13 +++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 44e7da1033d..85913cd7c58 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -457,6 +457,7 @@ def test_issue31492(self): with self.assertRaises(AttributeError): os.does_not_exist + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; Flaky') @threading_helper.requires_working_threading() def test_concurrency(self): # bpo 38091: this is a hack to slow down the code that calls diff --git a/Lib/test/test_zipfile64.py b/Lib/test/test_zipfile64.py index 0947013afbc..2e1affe0252 100644 --- a/Lib/test/test_zipfile64.py +++ b/Lib/test/test_zipfile64.py @@ -11,7 +11,7 @@ 'test requires loads of disk-space bytes and a long time to run' ) -import zipfile, os, unittest +import zipfile, unittest import time import sys @@ -32,10 +32,6 @@ def setUp(self): line_gen = ("Test of zipfile line %d." % i for i in range(1000000)) self.data = '\n'.join(line_gen).encode('ascii') - # And write it to a file. - with open(TESTFN, "wb") as fp: - fp.write(self.data) - def zipTest(self, f, compression): # Create the ZIP archive. with zipfile.ZipFile(f, "w", compression) as zipfp: @@ -67,6 +63,9 @@ def zipTest(self, f, compression): (num, filecount)), file=sys.__stdout__) sys.__stdout__.flush() + # Check that testzip thinks the archive is valid + self.assertIsNone(zipfp.testzip()) + def testStored(self): # Try the temp file first. If we do TESTFN2 first, then it hogs # gigabytes of disk space for the duration of the test. @@ -85,9 +84,7 @@ def testDeflated(self): self.zipTest(TESTFN2, zipfile.ZIP_DEFLATED) def tearDown(self): - for fname in TESTFN, TESTFN2: - if os.path.exists(fname): - os.remove(fname) + os_helper.unlink(TESTFN2) class OtherTests(unittest.TestCase): From f3916950bf823f398b25cf554f0485787a2a84be Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:35:55 +0100 Subject: [PATCH 520/819] Skip flakey test --- Lib/test/test_subprocess.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 6b20a5c00a5..e04f8b8fcc4 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1770,8 +1770,7 @@ def test_run_with_pathlike_path_and_arguments(self): res = subprocess.run(args) self.assertEqual(res.returncode, 57) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.skipIf(mswindows, 'TODO: RUSTPYTHON; Flakey') @unittest.skipUnless(mswindows, "Maybe test trigger a leak on Ubuntu") def test_run_with_an_empty_env(self): # gh-105436: fix subprocess.run(..., env={}) broken on Windows From be559ae4530f85c9101f166fd702646353f7f064 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 17 Dec 2025 00:15:25 +0100 Subject: [PATCH 521/819] Update `support/import_helper.py` from 3.13.11 (#6451) * Update `support/import_helper.py` from 3.13.11 * Unmark passing test --- Lib/test/support/import_helper.py | 43 ++++++++++++++++++++++++++++--- Lib/test/test_super.py | 1 - 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Lib/test/support/import_helper.py b/Lib/test/support/import_helper.py index 67f18e530ed..2b91bdcf9cd 100644 --- a/Lib/test/support/import_helper.py +++ b/Lib/test/support/import_helper.py @@ -8,7 +8,7 @@ import unittest import warnings -from .os_helper import unlink +from .os_helper import unlink, temp_dir @contextlib.contextmanager @@ -58,8 +58,8 @@ def make_legacy_pyc(source): :return: The file system path to the legacy pyc file. """ pyc_file = importlib.util.cache_from_source(source) - up_one = os.path.dirname(os.path.abspath(source)) - legacy_pyc = os.path.join(up_one, source + 'c') + assert source.endswith('.py') + legacy_pyc = source + 'c' shutil.move(pyc_file, legacy_pyc) return legacy_pyc @@ -114,7 +114,7 @@ def multi_interp_extensions_check(enabled=True): This only applies to modules that haven't been imported yet. It overrides the PyInterpreterConfig.check_multi_interp_extensions setting (see support.run_in_subinterp_with_config() and - _xxsubinterpreters.create()). + _interpreters.create()). Also see importlib.utils.allowing_all_extensions(). """ @@ -268,9 +268,44 @@ def modules_cleanup(oldmodules): sys.modules.update(oldmodules) +@contextlib.contextmanager +def isolated_modules(): + """ + Save modules on entry and cleanup on exit. + """ + (saved,) = modules_setup() + try: + yield + finally: + modules_cleanup(saved) + + def mock_register_at_fork(func): # bpo-30599: Mock os.register_at_fork() when importing the random module, # since this function doesn't allow to unregister callbacks and would leak # memory. from unittest import mock return mock.patch('os.register_at_fork', create=True)(func) + + +@contextlib.contextmanager +def ready_to_import(name=None, source=""): + from test.support import script_helper + + # 1. Sets up a temporary directory and removes it afterwards + # 2. Creates the module file + # 3. Temporarily clears the module from sys.modules (if any) + # 4. Reverts or removes the module when cleaning up + name = name or "spam" + with temp_dir() as tempdir: + path = script_helper.make_script(tempdir, name, source) + old_module = sys.modules.pop(name, None) + try: + sys.path.insert(0, tempdir) + yield name, path + sys.path.remove(tempdir) + finally: + if old_module is not None: + sys.modules[name] = old_module + else: + sys.modules.pop(name, None) diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index 8967dab8bdd..76eda799da4 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -349,7 +349,6 @@ def test_super_argtype(self): with self.assertRaisesRegex(TypeError, "argument 1 must be a type"): super(1, int) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'test.support.import_helper' has no attribute 'ready_to_import' def test_shadowed_global(self): source = textwrap.dedent( """ From 9d2dd1745565547d3fcaf0b792612a48192c02de Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:02:44 +0100 Subject: [PATCH 522/819] Add regression test (#6452) --- extra_tests/snippets/builtin_bytes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extra_tests/snippets/builtin_bytes.py b/extra_tests/snippets/builtin_bytes.py index 9347fbc8fab..4cb743baa6f 100644 --- a/extra_tests/snippets/builtin_bytes.py +++ b/extra_tests/snippets/builtin_bytes.py @@ -1,3 +1,5 @@ +import sys + from testutils import assert_raises, skip_if_unsupported # new @@ -611,6 +613,9 @@ assert b"\xc2\xae\x75\x73\x74".decode() == "®ust" assert b"\xe4\xb8\xad\xe6\x96\x87\xe5\xad\x97".decode("utf-8") == "中文字" +# gh-2391 +assert b"-\xff".decode(sys.getfilesystemencoding(), "surrogateescape") == "-\udcff" + # mod assert b"rust%bpython%b" % (b" ", b"!") == b"rust python!" assert b"x=%i y=%f" % (1, 2.5) == b"x=1 y=2.500000" From 0412dfdb3b4b64aeacf82822352ae6c93580688c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:57:34 +0900 Subject: [PATCH 523/819] Fix winerror handling (#6454) --- crates/vm/src/exceptions.rs | 122 +++++++++++++++++++++++++----------- crates/vm/src/stdlib/io.rs | 11 ++-- 2 files changed, 94 insertions(+), 39 deletions(-) diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 2b725085bec..2958d1e497e 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1677,7 +1677,7 @@ pub(super) mod types { // SAFETY: slot_init is called during object initialization, // so fields are None and swap result can be safely ignored if len <= 5 { - // Only set errno/strerror when args len is 2-5 (CPython behavior) + // Only set errno/strerror when args len is 2-5 if 2 <= len { let _ = unsafe { exc.errno.swap(Some(new_args.args[0].clone())) }; let _ = unsafe { exc.strerror.swap(Some(new_args.args[1].clone())) }; @@ -1708,8 +1708,10 @@ pub(super) mod types { } } - // args are truncated to 2 for compatibility (only when 2-5 args) - if (3..=5).contains(&len) { + // args are truncated to 2 for compatibility (only when 2-5 args and filename is not None) + // truncation happens inside "if (filename && filename != Py_None)" block + let has_filename = exc.filename.to_owned().filter(|f| !vm.is_none(f)).is_some(); + if (3..=5).contains(&len) && has_filename { new_args.args.truncate(2); } PyBaseException::slot_init(zelf, new_args, vm) @@ -1717,44 +1719,94 @@ pub(super) mod types { #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { - let args = exc.args(); let obj = exc.as_object().to_owned(); - let str = if args.len() == 2 { - // SAFETY: len() == 2 is checked so get_arg 1 or 2 won't panic - let errno = exc.get_arg(0).unwrap().str(vm)?; - let msg = exc.get_arg(1).unwrap().str(vm)?; - - // On Windows, use [WinError X] format when winerror is set - #[cfg(windows)] - let (label, code) = match obj.get_attr("winerror", vm) { - Ok(winerror) if !vm.is_none(&winerror) => ("WinError", winerror.str(vm)?), - _ => ("Errno", errno.clone()), - }; - #[cfg(not(windows))] - let (label, code) = ("Errno", errno.clone()); + // Get OSError fields directly + let errno_field = obj.get_attr("errno", vm).ok().filter(|v| !vm.is_none(v)); + let strerror = obj.get_attr("strerror", vm).ok().filter(|v| !vm.is_none(v)); + let filename = obj.get_attr("filename", vm).ok().filter(|v| !vm.is_none(v)); + let filename2 = obj + .get_attr("filename2", vm) + .ok() + .filter(|v| !vm.is_none(v)); + #[cfg(windows)] + let winerror = obj.get_attr("winerror", vm).ok().filter(|v| !vm.is_none(v)); - let s = match obj.get_attr("filename", vm) { - Ok(filename) if !vm.is_none(&filename) => match obj.get_attr("filename2", vm) { - Ok(filename2) if !vm.is_none(&filename2) => format!( - "[{} {}] {}: '{}' -> '{}'", - label, + // Windows: winerror takes priority over errno + #[cfg(windows)] + if let Some(ref win_err) = winerror { + let code = win_err.str(vm)?; + if let Some(ref f) = filename { + let msg = strerror + .as_ref() + .map(|s| s.str(vm)) + .transpose()? + .map(|s| s.to_string()) + .unwrap_or_else(|| "None".to_owned()); + if let Some(ref f2) = filename2 { + return Ok(vm.ctx.new_str(format!( + "[WinError {}] {}: {} -> {}", code, msg, - filename.str(vm)?, - filename2.str(vm)? - ), - _ => format!("[{} {}] {}: '{}'", label, code, msg, filename.str(vm)?), - }, - _ => { - format!("[{label} {code}] {msg}") + f.repr(vm)?, + f2.repr(vm)? + ))); } - }; - vm.ctx.new_str(s) - } else { - exc.__str__(vm) - }; - Ok(str) + return Ok(vm.ctx.new_str(format!( + "[WinError {}] {}: {}", + code, + msg, + f.repr(vm)? + ))); + } + // winerror && strerror (no filename) + if let Some(ref s) = strerror { + return Ok(vm + .ctx + .new_str(format!("[WinError {}] {}", code, s.str(vm)?))); + } + } + + // Non-Windows or fallback: use errno + if let Some(ref f) = filename { + let errno_str = errno_field + .as_ref() + .map(|e| e.str(vm)) + .transpose()? + .map(|s| s.to_string()) + .unwrap_or_else(|| "None".to_owned()); + let msg = strerror + .as_ref() + .map(|s| s.str(vm)) + .transpose()? + .map(|s| s.to_string()) + .unwrap_or_else(|| "None".to_owned()); + if let Some(ref f2) = filename2 { + return Ok(vm.ctx.new_str(format!( + "[Errno {}] {}: {} -> {}", + errno_str, + msg, + f.repr(vm)?, + f2.repr(vm)? + ))); + } + return Ok(vm.ctx.new_str(format!( + "[Errno {}] {}: {}", + errno_str, + msg, + f.repr(vm)? + ))); + } + + // errno && strerror (no filename) + if let (Some(e), Some(s)) = (&errno_field, &strerror) { + return Ok(vm + .ctx + .new_str(format!("[Errno {}] {}", e.str(vm)?, s.str(vm)?))); + } + + // fallback to BaseException.__str__ + Ok(exc.__str__(vm)) } #[pymethod] diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 56bcbefddeb..547e482f13a 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -1084,10 +1084,13 @@ mod _io { self.write_end = buffer_size; // TODO: BlockingIOError(errno, msg, written) // written += self.buffer.len(); - return Err(vm.new_exception_msg( - vm.ctx.exceptions.blocking_io_error.to_owned(), - "write could not complete without blocking".to_owned(), - )); + return Err(vm + .new_os_subtype_error( + vm.ctx.exceptions.blocking_io_error.to_owned(), + None, + "write could not complete without blocking".to_owned(), + ) + .upcast()); } else { break; } From a4c93dfbbfed868e3fe896463a39d401079a3160 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:58:00 +0900 Subject: [PATCH 524/819] Remove useless &PyRef patterns (#6455) * fix &PyTypeRef * Remove useless &PyObjectRef * Remove useless &PyRef --- crates/stdlib/src/csv.rs | 18 ++++---- crates/stdlib/src/openssl.rs | 5 ++- crates/stdlib/src/scproxy.rs | 6 +-- crates/stdlib/src/ssl.rs | 14 +++---- crates/stdlib/src/ssl/compat.rs | 8 ++-- crates/vm/src/builtins/builtin_func.rs | 4 +- crates/vm/src/builtins/dict.rs | 10 ++--- crates/vm/src/builtins/function/jit.rs | 6 ++- crates/vm/src/builtins/genericalias.rs | 6 +-- crates/vm/src/builtins/list.rs | 4 +- crates/vm/src/builtins/property.rs | 6 +-- crates/vm/src/builtins/type.rs | 4 +- crates/vm/src/builtins/union.rs | 2 +- crates/vm/src/cformat.rs | 16 +++++--- crates/vm/src/codecs.rs | 2 +- crates/vm/src/coroutine.rs | 5 ++- crates/vm/src/exception_group.rs | 14 +++---- crates/vm/src/exceptions.rs | 14 +++---- crates/vm/src/frame.rs | 4 +- crates/vm/src/import.rs | 7 ++-- crates/vm/src/object/payload.rs | 4 +- crates/vm/src/scope.rs | 2 +- crates/vm/src/sequence.rs | 4 +- crates/vm/src/stdlib/builtins.rs | 4 +- crates/vm/src/stdlib/collections.rs | 4 +- crates/vm/src/stdlib/ctypes.rs | 4 +- crates/vm/src/stdlib/ctypes/base.rs | 52 ++++++++++++------------ crates/vm/src/stdlib/ctypes/field.rs | 4 +- crates/vm/src/stdlib/ctypes/structure.rs | 6 +-- crates/vm/src/stdlib/ctypes/union.rs | 4 +- crates/vm/src/stdlib/typevar.rs | 8 ++-- crates/vm/src/suggestion.rs | 8 ++-- crates/vm/src/vm/context.rs | 6 +-- crates/vm/src/vm/mod.rs | 5 ++- crates/vm/src/warn.rs | 4 +- crates/wasm/src/convert.rs | 6 +-- crates/wasm/src/js_module.rs | 3 +- 37 files changed, 146 insertions(+), 137 deletions(-) diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index 792402e0580..a62594a9f1b 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -4,7 +4,8 @@ pub(crate) use _csv::make_module; mod _csv { use crate::common::lock::PyMutex; use crate::vm::{ - AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, + VirtualMachine, builtins::{PyBaseExceptionRef, PyInt, PyNone, PyStr, PyType, PyTypeRef}, function::{ArgIterable, ArgumentError, FromArgs, FuncArgs, OptionalArg}, protocol::{PyIter, PyIterReturn}, @@ -130,11 +131,11 @@ mod _csv { /// /// * If the 'delimiter' attribute is not a single-character string, a type error is returned. /// * If the 'obj' is not of string type and does not have a 'delimiter' attribute, a type error is returned. - fn parse_delimiter_from_obj(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult<u8> { + fn parse_delimiter_from_obj(vm: &VirtualMachine, obj: &PyObject) -> PyResult<u8> { if let Ok(attr) = obj.get_attr("delimiter", vm) { parse_delimiter_from_obj(vm, &attr) } else { - match_class!(match obj.clone() { + match_class!(match obj.to_owned() { s @ PyStr => { Ok(s.as_str().bytes().exactly_one().map_err(|_| { let msg = r#""delimiter" must be a 1-character string"#; @@ -148,7 +149,7 @@ mod _csv { }) } } - fn parse_quotechar_from_obj(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult<Option<u8>> { + fn parse_quotechar_from_obj(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Option<u8>> { match_class!(match obj.get_attr("quotechar", vm)? { s @ PyStr => { Ok(Some(s.as_str().bytes().exactly_one().map_err(|_| { @@ -169,7 +170,7 @@ mod _csv { } }) } - fn parse_escapechar_from_obj(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult<Option<u8>> { + fn parse_escapechar_from_obj(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Option<u8>> { match_class!(match obj.get_attr("escapechar", vm)? { s @ PyStr => { Ok(Some(s.as_str().bytes().exactly_one().map_err(|_| { @@ -191,10 +192,7 @@ mod _csv { } }) } - fn prase_lineterminator_from_obj( - vm: &VirtualMachine, - obj: &PyObjectRef, - ) -> PyResult<Terminator> { + fn prase_lineterminator_from_obj(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Terminator> { match_class!(match obj.get_attr("lineterminator", vm)? { s @ PyStr => { Ok(if s.as_bytes().eq(b"\r\n") { @@ -217,7 +215,7 @@ mod _csv { } }) } - fn prase_quoting_from_obj(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult<QuoteStyle> { + fn prase_quoting_from_obj(vm: &VirtualMachine, obj: &PyObject) -> PyResult<QuoteStyle> { match_class!(match obj.get_attr("quoting", vm)? { i @ PyInt => { Ok(i.try_to_primitive::<isize>(vm)?.try_into().map_err(|_| { diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index ea67d605f76..bf03813b180 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -56,7 +56,8 @@ mod _ssl { vm::{ AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{ - PyBaseExceptionRef, PyBytesRef, PyListRef, PyOSError, PyStrRef, PyTypeRef, PyWeak, + PyBaseException, PyBaseExceptionRef, PyBytesRef, PyListRef, PyOSError, PyStrRef, + PyTypeRef, PyWeak, }, class_or_notimplemented, convert::ToPyException, @@ -3351,7 +3352,7 @@ mod _ssl { // Helper function to set verify_code and verify_message on SSLCertVerificationError fn set_verify_error_info( - exc: &PyBaseExceptionRef, + exc: &Py<PyBaseException>, ssl_ptr: *const sys::SSL, vm: &VirtualMachine, ) { diff --git a/crates/stdlib/src/scproxy.rs b/crates/stdlib/src/scproxy.rs index f49b6890a69..1974e7814ae 100644 --- a/crates/stdlib/src/scproxy.rs +++ b/crates/stdlib/src/scproxy.rs @@ -5,8 +5,8 @@ mod _scproxy { // straight-forward port of Modules/_scproxy.c use crate::vm::{ - PyResult, VirtualMachine, - builtins::{PyDictRef, PyStr}, + Py, PyResult, VirtualMachine, + builtins::{PyDict, PyDictRef, PyStr}, convert::ToPyObject, }; use system_configuration::core_foundation::{ @@ -74,7 +74,7 @@ mod _scproxy { let result = vm.ctx.new_dict(); - let set_proxy = |result: &PyDictRef, + let set_proxy = |result: &Py<PyDict>, proto: &str, enabled_key: CFStringRef, host_key: CFStringRef, diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index c23062d639d..e6f8c5fda7c 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -1423,7 +1423,7 @@ mod _ssl { /// Helper: Get path from Python's os.environ fn get_env_path( - environ: &PyObjectRef, + environ: &PyObject, var_name: &str, vm: &VirtualMachine, ) -> PyResult<String> { @@ -2101,10 +2101,10 @@ mod _ssl { // Helper functions (private): /// Parse path argument (str or bytes) to string - fn parse_path_arg(arg: &PyObjectRef, vm: &VirtualMachine) -> PyResult<String> { - if let Ok(s) = PyStrRef::try_from_object(vm, arg.clone()) { + fn parse_path_arg(arg: &PyObject, vm: &VirtualMachine) -> PyResult<String> { + if let Ok(s) = PyStrRef::try_from_object(vm, arg.to_owned()) { Ok(s.as_str().to_owned()) - } else if let Ok(b) = ArgBytesLike::try_from_object(vm, arg.clone()) { + } else if let Ok(b) = ArgBytesLike::try_from_object(vm, arg.to_owned()) { String::from_utf8(b.borrow_buf().to_vec()) .map_err(|_| vm.new_value_error("path contains invalid UTF-8".to_owned())) } else { @@ -2279,10 +2279,10 @@ mod _ssl { } /// Helper: Parse cadata argument (str or bytes) - fn parse_cadata_arg(&self, arg: &PyObjectRef, vm: &VirtualMachine) -> PyResult<Vec<u8>> { - if let Ok(s) = PyStrRef::try_from_object(vm, arg.clone()) { + fn parse_cadata_arg(&self, arg: &PyObject, vm: &VirtualMachine) -> PyResult<Vec<u8>> { + if let Ok(s) = PyStrRef::try_from_object(vm, arg.to_owned()) { Ok(s.as_str().as_bytes().to_vec()) - } else if let Ok(b) = ArgBytesLike::try_from_object(vm, arg.clone()) { + } else if let Ok(b) = ArgBytesLike::try_from_object(vm, arg.to_owned()) { Ok(b.borrow_buf().to_vec()) } else { Err(vm.new_type_error("cadata should be a str or bytes".to_owned())) diff --git a/crates/stdlib/src/ssl/compat.rs b/crates/stdlib/src/ssl/compat.rs index 798542f210a..4ccc590360a 100644 --- a/crates/stdlib/src/ssl/compat.rs +++ b/crates/stdlib/src/ssl/compat.rs @@ -23,10 +23,10 @@ use rustls::server::ResolvesServerCert; use rustls::server::ServerConfig; use rustls::server::ServerConnection; use rustls::sign::CertifiedKey; -use rustpython_vm::builtins::PyBaseExceptionRef; +use rustpython_vm::builtins::{PyBaseException, PyBaseExceptionRef}; use rustpython_vm::convert::IntoPyException; use rustpython_vm::function::ArgBytesLike; -use rustpython_vm::{AsObject, PyObjectRef, PyPayload, PyResult, TryFromObject}; +use rustpython_vm::{AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject}; use std::io::Read; use std::sync::{Arc, Once}; @@ -984,7 +984,7 @@ pub(super) fn create_client_config(options: ClientConfigOptions) -> Result<Clien } /// Helper function - check if error is BlockingIOError -pub(super) fn is_blocking_io_error(err: &PyBaseExceptionRef, vm: &VirtualMachine) -> bool { +pub(super) fn is_blocking_io_error(err: &Py<PyBaseException>, vm: &VirtualMachine) -> bool { err.fast_isinstance(vm.ctx.exceptions.blocking_io_error) } @@ -1534,7 +1534,7 @@ fn ssl_read_tls_records( /// Check if an exception is a connection closed error /// In SSL context, these errors indicate unexpected connection termination without proper TLS shutdown -fn is_connection_closed_error(exc: &PyBaseExceptionRef, vm: &VirtualMachine) -> bool { +fn is_connection_closed_error(exc: &Py<PyBaseException>, vm: &VirtualMachine) -> bool { use rustpython_vm::stdlib::errno::errors; // Check for ConnectionAbortedError, ConnectionResetError (Python exception types) diff --git a/crates/vm/src/builtins/builtin_func.rs b/crates/vm/src/builtins/builtin_func.rs index d25188affd2..da5fd5e8075 100644 --- a/crates/vm/src/builtins/builtin_func.rs +++ b/crates/vm/src/builtins/builtin_func.rs @@ -51,11 +51,11 @@ impl PyNativeFunction { } // PyCFunction_GET_SELF - pub const fn get_self(&self) -> Option<&PyObjectRef> { + pub fn get_self(&self) -> Option<&PyObject> { if self.value.flags.contains(PyMethodFlags::STATIC) { return None; } - self.zelf.as_ref() + self.zelf.as_deref() } pub const fn as_func(&self) -> &'static dyn PyNativeFn { diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index b34299d4170..04915b035f8 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -753,7 +753,7 @@ impl ExactSizeIterator for DictIter<'_> { trait DictView: PyPayload + PyClassDef + Iterable + Representable { type ReverseIter: PyPayload + std::fmt::Debug; - fn dict(&self) -> &PyDictRef; + fn dict(&self) -> &Py<PyDict>; fn item(vm: &VirtualMachine, key: PyObjectRef, value: PyObjectRef) -> PyObjectRef; #[pymethod] @@ -785,7 +785,7 @@ macro_rules! dict_view { impl DictView for $name { type ReverseIter = $reverse_iter_name; - fn dict(&self) -> &PyDictRef { + fn dict(&self) -> &Py<PyDict> { &self.dict } @@ -1142,7 +1142,7 @@ impl PyDictKeys { #[pygetset] fn mapping(zelf: PyRef<Self>) -> PyMappingProxy { - PyMappingProxy::from(zelf.dict().clone()) + PyMappingProxy::from(zelf.dict().to_owned()) } } @@ -1206,7 +1206,7 @@ impl PyDictItems { } #[pygetset] fn mapping(zelf: PyRef<Self>) -> PyMappingProxy { - PyMappingProxy::from(zelf.dict().clone()) + PyMappingProxy::from(zelf.dict().to_owned()) } } @@ -1269,7 +1269,7 @@ impl AsNumber for PyDictItems { impl PyDictValues { #[pygetset] fn mapping(zelf: PyRef<Self>) -> PyMappingProxy { - PyMappingProxy::from(zelf.dict().clone()) + PyMappingProxy::from(zelf.dict().to_owned()) } } diff --git a/crates/vm/src/builtins/function/jit.rs b/crates/vm/src/builtins/function/jit.rs index 21d8c9c0abf..de0a528b734 100644 --- a/crates/vm/src/builtins/function/jit.rs +++ b/crates/vm/src/builtins/function/jit.rs @@ -1,6 +1,8 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyBaseExceptionRef, PyDictRef, PyFunction, PyStrInterned, bool_, float, int}, + builtins::{ + PyBaseExceptionRef, PyDict, PyDictRef, PyFunction, PyStrInterned, bool_, float, int, + }, bytecode::CodeFlags, convert::ToPyObject, function::FuncArgs, @@ -42,7 +44,7 @@ pub fn new_jit_error(msg: String, vm: &VirtualMachine) -> PyBaseExceptionRef { vm.new_exception_msg(jit_error, msg) } -fn get_jit_arg_type(dict: &PyDictRef, name: &str, vm: &VirtualMachine) -> PyResult<JitType> { +fn get_jit_arg_type(dict: &Py<PyDict>, name: &str, vm: &VirtualMachine) -> PyResult<JitType> { if let Some(value) = dict.get_item_opt(name, vm)? { if value.is(vm.ctx.types.int_type) { Ok(JitType::Int) diff --git a/crates/vm/src/builtins/genericalias.rs b/crates/vm/src/builtins/genericalias.rs index 494b580e563..8a7288980fa 100644 --- a/crates/vm/src/builtins/genericalias.rs +++ b/crates/vm/src/builtins/genericalias.rs @@ -294,11 +294,11 @@ pub(crate) fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupl } #[inline] -fn tuple_index(vec: &[PyObjectRef], item: &PyObjectRef) -> Option<usize> { +fn tuple_index(vec: &[PyObjectRef], item: &PyObject) -> Option<usize> { vec.iter().position(|element| element.is(item)) } -fn is_unpacked_typevartuple(arg: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { +fn is_unpacked_typevartuple(arg: &PyObject, vm: &VirtualMachine) -> PyResult<bool> { if arg.class().is(vm.ctx.types.type_type) { return Ok(false); } @@ -312,7 +312,7 @@ fn is_unpacked_typevartuple(arg: &PyObjectRef, vm: &VirtualMachine) -> PyResult< fn subs_tvars( obj: PyObjectRef, - params: &PyTupleRef, + params: &Py<PyTuple>, arg_items: &[PyObjectRef], vm: &VirtualMachine, ) -> PyResult { diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index 0683381f38a..13e8864cd1f 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -367,8 +367,8 @@ where impl MutObjectSequenceOp for PyList { type Inner = [PyObjectRef]; - fn do_get(index: usize, inner: &[PyObjectRef]) -> Option<&PyObjectRef> { - inner.get(index) + fn do_get(index: usize, inner: &[PyObjectRef]) -> Option<&PyObject> { + inner.get(index).map(|r| r.as_ref()) } fn do_lock(&self) -> impl std::ops::Deref<Target = [PyObjectRef]> { diff --git a/crates/vm/src/builtins/property.rs b/crates/vm/src/builtins/property.rs index 1a2e04ee8b0..41b05a60049 100644 --- a/crates/vm/src/builtins/property.rs +++ b/crates/vm/src/builtins/property.rs @@ -266,7 +266,7 @@ impl PyProperty { #[pygetset] fn __isabstractmethod__(&self, vm: &VirtualMachine) -> PyResult { // Helper to check if a method is abstract - let is_abstract = |method: &PyObjectRef| -> PyResult<bool> { + let is_abstract = |method: &PyObject| -> PyResult<bool> { match method.get_attr("__isabstractmethod__", vm) { Ok(isabstract) => isabstract.try_to_bool(vm), Err(_) => Ok(false), @@ -309,7 +309,7 @@ impl PyProperty { #[cold] fn format_property_error( &self, - obj: &PyObjectRef, + obj: &PyObject, error_type: &str, vm: &VirtualMachine, ) -> PyResult<String> { @@ -356,7 +356,7 @@ impl Initializer for PyProperty { let mut getter_doc = false; // Helper to get doc from getter - let get_getter_doc = |fget: &PyObjectRef| -> Option<PyObjectRef> { + let get_getter_doc = |fget: &PyObject| -> Option<PyObjectRef> { fget.get_attr("__doc__", vm) .ok() .filter(|doc| !vm.is_none(doc)) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index d014cdf015e..15743350397 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -175,12 +175,12 @@ fn is_subtype_with_mro(a_mro: &[PyTypeRef], a: &Py<PyType>, b: &Py<PyType>) -> b impl PyType { pub fn new_simple_heap( name: &str, - base: &PyTypeRef, + base: &Py<PyType>, ctx: &Context, ) -> Result<PyRef<Self>, String> { Self::new_heap( name, - vec![base.clone()], + vec![base.to_owned()], Default::default(), Default::default(), Self::static_type().to_owned(), diff --git a/crates/vm/src/builtins/union.rs b/crates/vm/src/builtins/union.rs index 16d2b7831cd..8310201a129 100644 --- a/crates/vm/src/builtins/union.rs +++ b/crates/vm/src/builtins/union.rs @@ -42,7 +42,7 @@ impl PyUnion { /// Direct access to args field, matching CPython's _Py_union_args #[inline] - pub const fn args(&self) -> &PyTupleRef { + pub fn args(&self) -> &Py<PyTuple> { &self.args } diff --git a/crates/vm/src/cformat.rs b/crates/vm/src/cformat.rs index 507079e7deb..efb3cb2acc9 100644 --- a/crates/vm/src/cformat.rs +++ b/crates/vm/src/cformat.rs @@ -6,7 +6,8 @@ use crate::common::cformat::*; use crate::common::wtf8::{CodePoint, Wtf8, Wtf8Buf}; use crate::{ - AsObject, PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject, VirtualMachine, + AsObject, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject, + VirtualMachine, builtins::{ PyBaseExceptionRef, PyByteArray, PyBytes, PyFloat, PyInt, PyStr, try_f64_to_bigint, tuple, }, @@ -207,7 +208,7 @@ fn spec_format_string( fn try_update_quantity_from_element( vm: &VirtualMachine, - element: Option<&PyObjectRef>, + element: Option<&PyObject>, ) -> PyResult<CFormatQuantity> { match element { Some(width_obj) => { @@ -224,7 +225,7 @@ fn try_update_quantity_from_element( fn try_conversion_flag_from_tuple( vm: &VirtualMachine, - element: Option<&PyObjectRef>, + element: Option<&PyObject>, ) -> PyResult<CConversionFlags> { match element { Some(width_obj) => { @@ -254,8 +255,11 @@ fn try_update_quantity_from_tuple<'a, I: Iterator<Item = &'a PyObjectRef>>( return Ok(()); }; let element = elements.next(); - f.insert(try_conversion_flag_from_tuple(vm, element)?); - let quantity = try_update_quantity_from_element(vm, element)?; + f.insert(try_conversion_flag_from_tuple( + vm, + element.map(|v| v.as_ref()), + )?); + let quantity = try_update_quantity_from_element(vm, element.map(|v| v.as_ref()))?; *q = Some(quantity); Ok(()) } @@ -268,7 +272,7 @@ fn try_update_precision_from_tuple<'a, I: Iterator<Item = &'a PyObjectRef>>( let Some(CFormatPrecision::Quantity(CFormatQuantity::FromValuesTuple)) = p else { return Ok(()); }; - let quantity = try_update_quantity_from_element(vm, elements.next())?; + let quantity = try_update_quantity_from_element(vm, elements.next().map(|v| v.as_ref()))?; *p = Some(CFormatPrecision::Quantity(quantity)); Ok(()) } diff --git a/crates/vm/src/codecs.rs b/crates/vm/src/codecs.rs index dac637c396d..2edb67b497b 100644 --- a/crates/vm/src/codecs.rs +++ b/crates/vm/src/codecs.rs @@ -52,7 +52,7 @@ impl PyCodec { self.0 } #[inline] - pub const fn as_tuple(&self) -> &PyTupleRef { + pub fn as_tuple(&self) -> &Py<PyTuple> { &self.0 } diff --git a/crates/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs index 4e76490ed65..ebe3107cb36 100644 --- a/crates/vm/src/coroutine.rs +++ b/crates/vm/src/coroutine.rs @@ -1,7 +1,8 @@ use crate::{ - AsObject, PyObject, PyObjectRef, PyResult, VirtualMachine, + AsObject, Py, PyObject, PyObjectRef, PyResult, VirtualMachine, builtins::{PyBaseExceptionRef, PyStrRef}, common::lock::PyMutex, + exceptions::types::PyBaseException, frame::{ExecutionResult, FrameRef}, protocol::PyIterReturn, }; @@ -207,6 +208,6 @@ impl Coro { } } -pub fn is_gen_exit(exc: &PyBaseExceptionRef, vm: &VirtualMachine) -> bool { +pub fn is_gen_exit(exc: &Py<PyBaseException>, vm: &VirtualMachine) -> bool { exc.fast_isinstance(vm.ctx.exceptions.generator_exit) } diff --git a/crates/vm/src/exception_group.rs b/crates/vm/src/exception_group.rs index 8d033b26110..cd943ae1bd9 100644 --- a/crates/vm/src/exception_group.rs +++ b/crates/vm/src/exception_group.rs @@ -352,12 +352,12 @@ pub(super) mod types { } // Helper functions for ExceptionGroup - fn is_base_exception_group(obj: &PyObjectRef, vm: &VirtualMachine) -> bool { + fn is_base_exception_group(obj: &PyObject, vm: &VirtualMachine) -> bool { obj.fast_isinstance(vm.ctx.exceptions.base_exception_group) } fn get_exceptions_tuple( - exc: &PyRef<PyBaseException>, + exc: &Py<PyBaseException>, vm: &VirtualMachine, ) -> PyResult<Vec<PyObjectRef>> { let obj = exc @@ -376,7 +376,7 @@ pub(super) mod types { } fn get_condition_matcher( - condition: &PyObjectRef, + condition: &PyObject, vm: &VirtualMachine, ) -> PyResult<ConditionMatcher> { // If it's a type and subclass of BaseException @@ -409,19 +409,19 @@ pub(super) mod types { // If it's callable (but not a type) if condition.is_callable() && condition.downcast_ref::<PyType>().is_none() { - return Ok(ConditionMatcher::Callable(condition.clone())); + return Ok(ConditionMatcher::Callable(condition.to_owned())); } Err(vm.new_type_error("expected a function, exception type or tuple of exception types")) } impl ConditionMatcher { - fn check(&self, exc: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { + fn check(&self, exc: &PyObject, vm: &VirtualMachine) -> PyResult<bool> { match self { ConditionMatcher::Type(typ) => Ok(exc.fast_isinstance(typ)), ConditionMatcher::Types(types) => Ok(types.iter().any(|t| exc.fast_isinstance(t))), ConditionMatcher::Callable(func) => { - let result = func.call((exc.clone(),), vm)?; + let result = func.call((exc.to_owned(),), vm)?; result.try_to_bool(vm) } } @@ -429,7 +429,7 @@ pub(super) mod types { } fn derive_and_copy_attributes( - orig: &PyRef<PyBaseException>, + orig: &Py<PyBaseException>, excs: Vec<PyObjectRef>, vm: &VirtualMachine, ) -> PyResult<PyObjectRef> { diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 2958d1e497e..036d914810d 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -79,7 +79,7 @@ impl VirtualMachine { pub fn write_exception<W: Write>( &self, output: &mut W, - exc: &PyBaseExceptionRef, + exc: &Py<PyBaseException>, ) -> Result<(), W::Error> { let seen = &mut HashSet::<usize>::new(); self.write_exception_recursive(output, exc, seen) @@ -88,7 +88,7 @@ impl VirtualMachine { fn write_exception_recursive<W: Write>( &self, output: &mut W, - exc: &PyBaseExceptionRef, + exc: &Py<PyBaseException>, seen: &mut HashSet<usize>, ) -> Result<(), W::Error> { // This function should not be called directly, @@ -132,7 +132,7 @@ impl VirtualMachine { pub fn write_exception_inner<W: Write>( &self, output: &mut W, - exc: &PyBaseExceptionRef, + exc: &Py<PyBaseException>, ) -> Result<(), W::Error> { let vm = self; if let Some(tb) = exc.traceback.read().clone() { @@ -177,7 +177,7 @@ impl VirtualMachine { fn write_syntaxerror<W: Write>( &self, output: &mut W, - exc: &PyBaseExceptionRef, + exc: &Py<PyBaseException>, exc_type: &Py<PyType>, args_repr: &[PyRef<PyStr>], ) -> Result<(), W::Error> { @@ -369,7 +369,7 @@ fn print_source_line<W: Write>( /// Print exception occurrence location from traceback element fn write_traceback_entry<W: Write>( output: &mut W, - tb_entry: &PyTracebackRef, + tb_entry: &Py<PyTraceback>, ) -> Result<(), W::Error> { let filename = tb_entry.frame.code.source_path.as_str(); writeln!( @@ -1053,12 +1053,12 @@ fn system_exit_code(exc: PyBaseExceptionRef) -> Option<PyObjectRef> { #[cfg(feature = "serde")] pub struct SerializeException<'vm, 's> { vm: &'vm VirtualMachine, - exc: &'s PyBaseExceptionRef, + exc: &'s Py<PyBaseException>, } #[cfg(feature = "serde")] impl<'vm, 's> SerializeException<'vm, 's> { - pub fn new(vm: &'vm VirtualMachine, exc: &'s PyBaseExceptionRef) -> Self { + pub fn new(vm: &'vm VirtualMachine, exc: &'s Py<PyBaseException>) -> Self { SerializeException { vm, exc } } } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 4a460a95884..ad50f972aef 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1866,14 +1866,14 @@ impl ExecutingFrame<'_> { /// This ensures proper order preservation for OrderedDict and other custom mappings. fn iterate_mapping_keys<F>( vm: &VirtualMachine, - mapping: &PyObjectRef, + mapping: &PyObject, error_prefix: &str, mut key_handler: F, ) -> PyResult<()> where F: FnMut(PyObjectRef) -> PyResult<()>, { - let Some(keys_method) = vm.get_method(mapping.clone(), vm.ctx.intern_str("keys")) else { + let Some(keys_method) = vm.get_method(mapping.to_owned(), vm.ctx.intern_str("keys")) else { return Err(vm.new_type_error(format!("{error_prefix} must be a mapping"))); }; diff --git a/crates/vm/src/import.rs b/crates/vm/src/import.rs index c119405fe1d..3f4a437c599 100644 --- a/crates/vm/src/import.rs +++ b/crates/vm/src/import.rs @@ -1,8 +1,9 @@ //! Import mechanics use crate::{ - AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, - builtins::{PyBaseExceptionRef, PyCode, list, traceback::PyTraceback}, + AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, + builtins::{PyCode, list, traceback::PyTraceback}, + exceptions::types::PyBaseException, scope::Scope, version::get_git_revision, vm::{VirtualMachine, thread}, @@ -204,7 +205,7 @@ fn remove_importlib_frames_inner( // TODO: This function should do nothing on verbose mode. // TODO: Fix this function after making PyTraceback.next mutable -pub fn remove_importlib_frames(vm: &VirtualMachine, exc: &PyBaseExceptionRef) { +pub fn remove_importlib_frames(vm: &VirtualMachine, exc: &Py<PyBaseException>) { if vm.state.settings.verbose != 0 { return; } diff --git a/crates/vm/src/object/payload.rs b/crates/vm/src/object/payload.rs index cf903871179..4b900b7caa1 100644 --- a/crates/vm/src/object/payload.rs +++ b/crates/vm/src/object/payload.rs @@ -106,7 +106,7 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { #[inline(never)] fn _into_ref_size_error( vm: &VirtualMachine, - cls: &PyTypeRef, + cls: &Py<PyType>, exact_class: &Py<PyType>, ) -> PyBaseExceptionRef { vm.new_type_error(format!( @@ -123,7 +123,7 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { #[inline(never)] fn _into_ref_with_type_error( vm: &VirtualMachine, - cls: &PyTypeRef, + cls: &Py<PyType>, exact_class: &Py<PyType>, ) -> PyBaseExceptionRef { vm.new_type_error(format!( diff --git a/crates/vm/src/scope.rs b/crates/vm/src/scope.rs index 9311fa5c2db..4f80e9999ec 100644 --- a/crates/vm/src/scope.rs +++ b/crates/vm/src/scope.rs @@ -34,7 +34,7 @@ impl Scope { Self::new(locals, globals) } - // pub fn get_locals(&self) -> &PyDictRef { + // pub fn get_locals(&self) -> &Py<PyDict> { // match self.locals.first() { // Some(dict) => dict, // None => &self.globals, diff --git a/crates/vm/src/sequence.rs b/crates/vm/src/sequence.rs index e75c0a6da55..6e03ad1697e 100644 --- a/crates/vm/src/sequence.rs +++ b/crates/vm/src/sequence.rs @@ -12,7 +12,7 @@ use std::ops::{Deref, Range}; pub trait MutObjectSequenceOp { type Inner: ?Sized; - fn do_get(index: usize, inner: &Self::Inner) -> Option<&PyObjectRef>; + fn do_get(index: usize, inner: &Self::Inner) -> Option<&PyObject>; fn do_lock(&self) -> impl Deref<Target = Self::Inner>; fn mut_count(&self, vm: &VirtualMachine, needle: &PyObject) -> PyResult<usize> { @@ -76,7 +76,7 @@ pub trait MutObjectSequenceOp { } borrower = Some(guard); } else { - let elem = elem.clone(); + let elem = elem.to_owned(); drop(guard); if elem.rich_compare_bool(needle, PyComparisonOp::Eq, vm)? { diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 542476d68c7..442bb79b94e 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -10,7 +10,7 @@ mod builtins { use std::io::IsTerminal; use crate::{ - AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, + AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ PyByteArray, PyBytes, PyDictRef, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, enumerate::PyReverseSequenceIterator, @@ -261,7 +261,7 @@ mod builtins { func_name: &'static str, ) -> PyResult<crate::scope::Scope> { fn validate_globals_dict( - globals: &PyObjectRef, + globals: &PyObject, vm: &VirtualMachine, func_name: &'static str, ) -> PyResult<()> { diff --git a/crates/vm/src/stdlib/collections.rs b/crates/vm/src/stdlib/collections.rs index 32596b65386..eae56968cba 100644 --- a/crates/vm/src/stdlib/collections.rs +++ b/crates/vm/src/stdlib/collections.rs @@ -422,8 +422,8 @@ mod _collections { impl MutObjectSequenceOp for PyDeque { type Inner = VecDeque<PyObjectRef>; - fn do_get(index: usize, inner: &Self::Inner) -> Option<&PyObjectRef> { - inner.get(index) + fn do_get(index: usize, inner: &Self::Inner) -> Option<&PyObject> { + inner.get(index).map(|r| r.as_ref()) } fn do_lock(&self) -> impl std::ops::Deref<Target = Self::Inner> { diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 70aee7378d3..ebe2d16ffb2 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -53,7 +53,7 @@ pub(crate) mod _ctypes { use crate::convert::ToPyObject; use crate::function::{Either, FuncArgs, OptionalArg}; use crate::stdlib::ctypes::library; - use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; + use crate::{AsObject, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use std::ffi::{ c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, @@ -349,7 +349,7 @@ pub(crate) mod _ctypes { const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?O"; pub fn new_simple_type( - cls: Either<&PyObjectRef, &PyTypeRef>, + cls: Either<&PyObject, &PyTypeRef>, vm: &VirtualMachine, ) -> PyResult<PyCSimple> { let cls = match cls { diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index a4664ad3671..e45ff0b3b70 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -5,7 +5,9 @@ use crate::function::{ArgBytesLike, Either, FuncArgs, KwArgs, OptionalArg}; use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; use crate::stdlib::ctypes::_ctypes::new_simple_type; use crate::types::{AsBuffer, AsNumber, Constructor}; -use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine}; +use crate::{ + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, +}; use crossbeam_utils::atomic::AtomicCell; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; @@ -46,19 +48,19 @@ pub fn ffi_type_from_str(_type_: &str) -> Option<libffi::middle::Type> { } #[allow(dead_code)] -fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyResult { +fn set_primitive(_type_: &str, value: &PyObject, vm: &VirtualMachine) -> PyResult { match _type_ { "c" => { if value - .clone() + .to_owned() .downcast_exact::<PyBytes>(vm) .is_ok_and(|v| v.len() == 1) || value - .clone() + .to_owned() .downcast_exact::<PyBytes>(vm) .is_ok_and(|v| v.len() == 1) || value - .clone() + .to_owned() .downcast_exact::<PyInt>(vm) .map_or(Ok(false), |v| { let n = v.as_bigint().to_i64(); @@ -69,7 +71,7 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe } })? { - Ok(value.clone()) + Ok(value.to_owned()) } else { Err(vm.new_type_error("one character bytes, bytearray or integer expected")) } @@ -77,7 +79,7 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe "u" => { if let Ok(b) = value.str(vm).map(|v| v.to_string().chars().count() == 1) { if b { - Ok(value.clone()) + Ok(value.to_owned()) } else { Err(vm.new_type_error("one character unicode string expected")) } @@ -89,8 +91,8 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe } } "b" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => { - if value.clone().downcast_exact::<PyInt>(vm).is_ok() { - Ok(value.clone()) + if value.to_owned().downcast_exact::<PyInt>(vm).is_ok() { + Ok(value.to_owned()) } else { Err(vm.new_type_error(format!( "an integer is required (got type {})", @@ -100,30 +102,30 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe } "f" | "d" | "g" => { // float allows int - if value.clone().downcast_exact::<PyFloat>(vm).is_ok() - || value.clone().downcast_exact::<PyInt>(vm).is_ok() + if value.to_owned().downcast_exact::<PyFloat>(vm).is_ok() + || value.to_owned().downcast_exact::<PyInt>(vm).is_ok() { - Ok(value.clone()) + Ok(value.to_owned()) } else { Err(vm.new_type_error(format!("must be real number, not {}", value.class().name()))) } } "?" => Ok(PyObjectRef::from( - vm.ctx.new_bool(value.clone().try_to_bool(vm)?), + vm.ctx.new_bool(value.to_owned().try_to_bool(vm)?), )), "B" => { - if value.clone().downcast_exact::<PyInt>(vm).is_ok() { + if value.to_owned().downcast_exact::<PyInt>(vm).is_ok() { // Store as-is, conversion to unsigned happens in the getter - Ok(value.clone()) + Ok(value.to_owned()) } else { Err(vm.new_type_error(format!("int expected instead of {}", value.class().name()))) } } "z" => { - if value.clone().downcast_exact::<PyInt>(vm).is_ok() - || value.clone().downcast_exact::<PyBytes>(vm).is_ok() + if value.to_owned().downcast_exact::<PyInt>(vm).is_ok() + || value.to_owned().downcast_exact::<PyBytes>(vm).is_ok() { - Ok(value.clone()) + Ok(value.to_owned()) } else { Err(vm.new_type_error(format!( "bytes or integer address expected instead of {} instance", @@ -132,8 +134,8 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe } } "Z" => { - if value.clone().downcast_exact::<PyStr>(vm).is_ok() { - Ok(value.clone()) + if value.to_owned().downcast_exact::<PyStr>(vm).is_ok() { + Ok(value.to_owned()) } else { Err(vm.new_type_error(format!( "unicode string or integer address expected instead of {} instance", @@ -143,10 +145,10 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe } _ => { // "P" - if value.clone().downcast_exact::<PyInt>(vm).is_ok() - || value.clone().downcast_exact::<PyNone>(vm).is_ok() + if value.to_owned().downcast_exact::<PyInt>(vm).is_ok() + || value.to_owned().downcast_exact::<PyNone>(vm).is_ok() { - Ok(value.clone()) + Ok(value.to_owned()) } else { Err(vm.new_type_error("cannot be converted to pointer")) } @@ -437,7 +439,7 @@ impl Debug for PyCSimple { fn value_to_bytes_endian( _type_: &str, - value: &PyObjectRef, + value: &PyObject, swapped: bool, vm: &VirtualMachine, ) -> Vec<u8> { @@ -598,7 +600,7 @@ fn value_to_bytes_endian( } "?" => { // c_bool (1 byte) - if let Ok(b) = value.clone().try_to_bool(vm) { + if let Ok(b) = value.to_owned().try_to_bool(vm) { return vec![if b { 1 } else { 0 }]; } vec![0] diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index 659255f3329..ea57d68065a 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -1,7 +1,7 @@ use crate::builtins::PyType; use crate::function::PySetterValue; use crate::types::{GetDescriptor, Representable}; -use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; +use crate::{AsObject, Py, PyObject, PyObjectRef, PyResult, VirtualMachine}; use num_traits::ToPrimitive; use super::structure::PyCStructure; @@ -152,7 +152,7 @@ impl PyCField { } /// Convert a Python value to bytes - fn value_to_bytes(value: &PyObjectRef, size: usize, vm: &VirtualMachine) -> PyResult<Vec<u8>> { + fn value_to_bytes(value: &PyObject, size: usize, vm: &VirtualMachine) -> PyResult<Vec<u8>> { if let Ok(int_val) = value.try_int(vm) { let i = int_val.as_bigint(); match size { diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index f32d6865cb6..ca67a2fe7d6 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -7,7 +7,7 @@ use crate::function::FuncArgs; use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; use crate::stdlib::ctypes::_ctypes::get_size; use crate::types::{AsBuffer, AsNumber, Constructor}; -use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use indexmap::IndexMap; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; @@ -109,7 +109,7 @@ impl PyCStructType { } /// Get the size of a ctypes type - fn get_field_size(field_type: &PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { + fn get_field_size(field_type: &PyObject, vm: &VirtualMachine) -> PyResult<usize> { // Try to get _type_ attribute for simple types if let Some(size) = field_type .get_attr("_type_", vm) @@ -139,7 +139,7 @@ impl PyCStructType { } /// Get the alignment of a ctypes type - fn get_field_align(field_type: &PyObjectRef, vm: &VirtualMachine) -> usize { + fn get_field_align(field_type: &PyObject, vm: &VirtualMachine) -> usize { // Try to get _type_ attribute for simple types if let Some(align) = field_type .get_attr("_type_", vm) diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index e6873e87506..308a5e4e98f 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -7,7 +7,7 @@ use crate::function::FuncArgs; use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer as ProtocolPyBuffer}; use crate::stdlib::ctypes::_ctypes::get_size; use crate::types::{AsBuffer, Constructor}; -use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; @@ -90,7 +90,7 @@ impl PyCUnionType { Ok(()) } - fn get_field_size(field_type: &PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { + fn get_field_size(field_type: &PyObject, vm: &VirtualMachine) -> PyResult<usize> { if let Some(size) = field_type .get_attr("_type_", vm) .ok() diff --git a/crates/vm/src/stdlib/typevar.rs b/crates/vm/src/stdlib/typevar.rs index e8cc407da15..65249bfd075 100644 --- a/crates/vm/src/stdlib/typevar.rs +++ b/crates/vm/src/stdlib/typevar.rs @@ -44,7 +44,7 @@ fn caller(vm: &VirtualMachine) -> Option<PyObjectRef> { /// Set __module__ attribute for an object based on the caller's module. /// This follows CPython's behavior for TypeVar and similar objects. -fn set_module_from_caller(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { +fn set_module_from_caller(obj: &PyObject, vm: &VirtualMachine) -> PyResult<()> { // Note: CPython gets module from frame->f_funcobj, but RustPython's Frame // architecture is different - we use globals['__name__'] instead if let Some(module_name) = caller(vm) { @@ -1006,15 +1006,15 @@ pub fn set_typeparam_default( ) -> PyResult { // Inner function to handle common pattern of setting evaluate_default fn try_set_default<T>( - obj: &PyObjectRef, - evaluate_default: &PyObjectRef, + obj: &PyObject, + evaluate_default: &PyObject, get_field: impl FnOnce(&T) -> &PyMutex<PyObjectRef>, ) -> bool where T: PyPayload, { if let Some(typed_obj) = obj.downcast_ref::<T>() { - *get_field(typed_obj).lock() = evaluate_default.clone(); + *get_field(typed_obj).lock() = evaluate_default.to_owned(); true } else { false diff --git a/crates/vm/src/suggestion.rs b/crates/vm/src/suggestion.rs index 2cc935873c2..866deb668eb 100644 --- a/crates/vm/src/suggestion.rs +++ b/crates/vm/src/suggestion.rs @@ -2,9 +2,9 @@ //! This is used during tracebacks. use crate::{ - AsObject, Py, PyObjectRef, VirtualMachine, + AsObject, Py, PyObject, PyObjectRef, VirtualMachine, builtins::{PyStr, PyStrRef}, - exceptions::types::PyBaseExceptionRef, + exceptions::types::PyBaseException, sliceable::SliceableSequenceOp, }; use rustpython_common::str::levenshtein::{MOVE_COST, levenshtein_distance}; @@ -14,7 +14,7 @@ const MAX_CANDIDATE_ITEMS: usize = 750; pub fn calculate_suggestions<'a>( dir_iter: impl ExactSizeIterator<Item = &'a PyObjectRef>, - name: &PyObjectRef, + name: &PyObject, ) -> Option<PyStrRef> { if dir_iter.len() >= MAX_CANDIDATE_ITEMS { return None; @@ -47,7 +47,7 @@ pub fn calculate_suggestions<'a>( suggestion.map(|r| r.to_owned()) } -pub fn offer_suggestions(exc: &PyBaseExceptionRef, vm: &VirtualMachine) -> Option<PyStrRef> { +pub fn offer_suggestions(exc: &Py<PyBaseException>, vm: &VirtualMachine) -> Option<PyStrRef> { if exc.class().is(vm.ctx.exceptions.attribute_error) { let name = exc.as_object().get_attr("name", vm).unwrap(); let obj = exc.as_object().get_attr("obj", vm).unwrap(); diff --git a/crates/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs index fbda71dc1f6..486c1861fb1 100644 --- a/crates/vm/src/vm/context.rs +++ b/crates/vm/src/vm/context.rs @@ -62,9 +62,9 @@ macro_rules! declare_const_name { } impl ConstName { - unsafe fn new(pool: &StringPool, typ: &PyTypeRef) -> Self { + unsafe fn new(pool: &StringPool, typ: &Py<PyType>) -> Self { Self { - $($name: unsafe { pool.intern(declare_const_name!(@string $name $($s)?), typ.clone()) },)* + $($name: unsafe { pool.intern(declare_const_name!(@string $name $($s)?), typ.to_owned()) },)* } } } @@ -317,7 +317,7 @@ impl Context { ); let string_pool = StringPool::default(); - let names = unsafe { ConstName::new(&string_pool, &types.str_type.to_owned()) }; + let names = unsafe { ConstName::new(&string_pool, types.str_type) }; let slot_new_wrapper = PyMethodDef::new_const( names.__new__.as_str(), diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index fd37b2494dd..4574b2de370 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -23,6 +23,7 @@ use crate::{ codecs::CodecsRegistry, common::{hash::HashSecret, lock::PyMutex, rc::PyRc}, convert::ToPyObject, + exceptions::types::PyBaseException, frame::{ExecutionResult, Frame, FrameRef}, frozen::FrozenModule, function::{ArgMapping, FuncArgs, PySetterValue}, @@ -728,7 +729,7 @@ impl VirtualMachine { pub fn set_attribute_error_context( &self, - exc: &PyBaseExceptionRef, + exc: &Py<PyBaseException>, obj: PyObjectRef, name: PyStrRef, ) { @@ -814,7 +815,7 @@ impl VirtualMachine { drop(prev); } - pub(crate) fn contextualize_exception(&self, exception: &PyBaseExceptionRef) { + pub(crate) fn contextualize_exception(&self, exception: &Py<PyBaseException>) { if let Some(context_exc) = self.topmost_exception() && !context_exc.is(exception) { diff --git a/crates/vm/src/warn.rs b/crates/vm/src/warn.rs index 6480f778433..b632495eb4a 100644 --- a/crates/vm/src/warn.rs +++ b/crates/vm/src/warn.rs @@ -1,5 +1,5 @@ use crate::{ - AsObject, Context, Py, PyObjectRef, PyResult, VirtualMachine, + AsObject, Context, Py, PyObject, PyObjectRef, PyResult, VirtualMachine, builtins::{ PyDictRef, PyListRef, PyStr, PyStrInterned, PyStrRef, PyTuple, PyTupleRef, PyTypeRef, }, @@ -38,7 +38,7 @@ impl WarningsState { } } -fn check_matched(obj: &PyObjectRef, arg: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { +fn check_matched(obj: &PyObject, arg: &PyObject, vm: &VirtualMachine) -> PyResult<bool> { if obj.class().is(vm.ctx.types.none_type) { return Ok(true); } diff --git a/crates/wasm/src/convert.rs b/crates/wasm/src/convert.rs index d1821f2e733..f84b0d46239 100644 --- a/crates/wasm/src/convert.rs +++ b/crates/wasm/src/convert.rs @@ -4,8 +4,8 @@ use crate::js_module; use crate::vm_class::{WASMVirtualMachine, stored_vm_from_wasm}; use js_sys::{Array, ArrayBuffer, Object, Promise, Reflect, SyntaxError, Uint8Array}; use rustpython_vm::{ - AsObject, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, VirtualMachine, - builtins::PyBaseExceptionRef, + AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, VirtualMachine, + builtins::{PyBaseException, PyBaseExceptionRef}, compiler::{CompileError, ParseError, parser::LexicalErrorType, parser::ParseErrorType}, exceptions, function::{ArgBytesLike, FuncArgs}, @@ -32,7 +32,7 @@ extern "C" { fn new(info: JsValue) -> PyError; } -pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyBaseExceptionRef) -> JsValue { +pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &Py<PyBaseException>) -> JsValue { let js_err = vm.try_class("_js", "JSError").ok(); let js_arg = if js_err.is_some_and(|js_err| py_err.fast_isinstance(&js_err)) { py_err.get_arg(0) diff --git a/crates/wasm/src/js_module.rs b/crates/wasm/src/js_module.rs index 1d8ca0961ca..d4f623da9f4 100644 --- a/crates/wasm/src/js_module.rs +++ b/crates/wasm/src/js_module.rs @@ -612,8 +612,7 @@ mod _js { fn js_error(vm: &VirtualMachine) -> PyTypeRef { let ctx = &vm.ctx; let js_error = PyRef::leak( - PyType::new_simple_heap("JSError", &vm.ctx.exceptions.exception_type.to_owned(), ctx) - .unwrap(), + PyType::new_simple_heap("JSError", vm.ctx.exceptions.exception_type, ctx).unwrap(), ); extend_class!(ctx, js_error, { "value" => ctx.new_readonly_getset("value", js_error, |exc: PyBaseExceptionRef| exc.get_arg(0)), From 9e439667dac0729f3d0c488ab3f6fedaadc580f7 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:44:56 +0900 Subject: [PATCH 525/819] Fix openssl build and shared ssl/error.rs (#6456) * Fix openssl * shared error --- crates/stdlib/src/openssl.rs | 502 +++++++++++++----------------- crates/stdlib/src/openssl/cert.rs | 3 +- crates/stdlib/src/ssl.rs | 114 +------ crates/stdlib/src/ssl/compat.rs | 11 +- crates/stdlib/src/ssl/error.rs | 117 +++++++ 5 files changed, 354 insertions(+), 393 deletions(-) create mode 100644 crates/stdlib/src/ssl/error.rs diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index bf03813b180..e07ad552f17 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -2,6 +2,10 @@ mod cert; +// SSL exception types (shared with rustls backend) +#[path = "ssl/error.rs"] +mod ssl_error; + // Conditional compilation for OpenSSL version-specific error codes cfg_if::cfg_if! { if #[cfg(ossl310)] { @@ -45,9 +49,16 @@ cfg_if::cfg_if! { } #[allow(non_upper_case_globals)] -#[pymodule(with(cert::ssl_cert, ossl101, ossl111, windows))] +#[pymodule(with(cert::ssl_cert, ssl_error::ssl_error, ossl101, ossl111, windows))] mod _ssl { use super::{bio, probe}; + + // Import error types used in this module (others are exposed via pymodule(with(...))) + use super::ssl_error::{ + PySSLCertVerificationError as PySslCertVerificationError, PySSLEOFError as PySslEOFError, + PySSLError as PySslError, PySSLWantReadError as PySslWantReadError, + PySSLWantWriteError as PySslWantWriteError, + }; use crate::{ common::lock::{ PyMappedRwLockReadGuard, PyMutex, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard, @@ -56,8 +67,8 @@ mod _ssl { vm::{ AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{ - PyBaseException, PyBaseExceptionRef, PyBytesRef, PyListRef, PyOSError, PyStrRef, - PyTypeRef, PyWeak, + PyBaseException, PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyType, + PyWeak, }, class_or_notimplemented, convert::ToPyException, @@ -300,85 +311,6 @@ mod _ssl { parse_version_info(openssl_api_version) } - // SSL Exception Types - - /// An error occurred in the SSL implementation. - #[pyattr] - #[pyexception(name = "SSLError", base = PyOSError)] - #[derive(Debug)] - pub struct PySslError {} - - #[pyexception] - impl PySslError { - // Returns strerror attribute if available, otherwise str(args) - #[pymethod] - fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { - // Try to get strerror attribute first (OSError compatibility) - if let Ok(strerror) = exc.as_object().get_attr("strerror", vm) - && !vm.is_none(&strerror) - { - return strerror.str(vm); - } - - // Otherwise return str(args) - exc.args().as_object().str(vm) - } - } - - /// A certificate could not be verified. - #[pyattr] - #[pyexception(name = "SSLCertVerificationError", base = PySslError)] - #[derive(Debug)] - pub struct PySslCertVerificationError {} - - #[pyexception] - impl PySslCertVerificationError {} - - /// SSL/TLS session closed cleanly. - #[pyattr] - #[pyexception(name = "SSLZeroReturnError", base = PySslError)] - #[derive(Debug)] - pub struct PySslZeroReturnError {} - - #[pyexception] - impl PySslZeroReturnError {} - - /// Non-blocking SSL socket needs to read more data. - #[pyattr] - #[pyexception(name = "SSLWantReadError", base = PySslError)] - #[derive(Debug)] - pub struct PySslWantReadError {} - - #[pyexception] - impl PySslWantReadError {} - - /// Non-blocking SSL socket needs to write more data. - #[pyattr] - #[pyexception(name = "SSLWantWriteError", base = PySslError)] - #[derive(Debug)] - pub struct PySslWantWriteError {} - - #[pyexception] - impl PySslWantWriteError {} - - /// System error when attempting SSL operation. - #[pyattr] - #[pyexception(name = "SSLSyscallError", base = PySslError)] - #[derive(Debug)] - pub struct PySslSyscallError {} - - #[pyexception] - impl PySslSyscallError {} - - /// SSL/TLS connection terminated abruptly. - #[pyattr] - #[pyexception(name = "SSLEOFError", base = PySslError)] - #[derive(Debug)] - pub struct PySslEOFError {} - - #[pyexception] - impl PySslEOFError {} - type OpensslVersionInfo = (u8, u8, u8, u8, u8); const fn parse_version_info(mut n: i64) -> OpensslVersionInfo { let status = (n & 0xF) as u8; @@ -582,18 +514,53 @@ mod _ssl { Ok(buf) } - // Callback data stored in SSL context for SNI + // Callback data stored in SSL ex_data for SNI/msg callbacks struct SniCallbackData { ssl_context: PyRef<PySslContext>, - vm_ptr: *const VirtualMachine, + // Use weak reference to avoid reference cycle: + // PySslSocket -> SslStream -> SSL -> ex_data -> SniCallbackData -> PySslSocket + ssl_socket_weak: PyRef<PyWeak>, + } + + // Thread-local storage for VirtualMachine pointer during handshake + // SNI callback is only called during handshake which is synchronous + thread_local! { + static HANDSHAKE_VM: std::cell::Cell<Option<*const VirtualMachine>> = const { std::cell::Cell::new(None) }; + // SSL pointer during handshake - needed because connection lock is held during handshake + // and callbacks may need to access SSL without acquiring the lock + static HANDSHAKE_SSL_PTR: std::cell::Cell<Option<*mut sys::SSL>> = const { std::cell::Cell::new(None) }; } - impl Drop for SniCallbackData { + // RAII guard to set/clear thread-local handshake context + struct HandshakeVmGuard { + _ssl_ptr: *mut sys::SSL, + } + + impl HandshakeVmGuard { + fn new(vm: &VirtualMachine, ssl_ptr: *mut sys::SSL) -> Self { + HANDSHAKE_VM.with(|cell| cell.set(Some(vm as *const _))); + HANDSHAKE_SSL_PTR.with(|cell| cell.set(Some(ssl_ptr))); + HandshakeVmGuard { _ssl_ptr: ssl_ptr } + } + } + + impl Drop for HandshakeVmGuard { fn drop(&mut self) { - // PyRef will handle reference counting + HANDSHAKE_VM.with(|cell| cell.set(None)); + HANDSHAKE_SSL_PTR.with(|cell| cell.set(None)); } } + // Get SSL pointer - either from thread-local (during handshake) or from connection + fn get_ssl_ptr_for_context_change(connection: &PyRwLock<SslConnection>) -> *mut sys::SSL { + // First check if we're in a handshake callback (lock already held) + if let Some(ptr) = HANDSHAKE_SSL_PTR.with(|cell| cell.get()) { + return ptr; + } + // Otherwise, acquire the lock normally + connection.read().ssl().as_ptr() + } + // Get or create an ex_data index for SNI callback data fn get_sni_ex_data_index() -> libc::c_int { use std::sync::LazyLock; @@ -610,17 +577,30 @@ mod _ssl { } // Free function for callback data + // NOTE: We don't free the data here because it's managed manually in do_handshake + // to avoid use-after-free when the SSL object is dropped after timeout unsafe extern "C" fn sni_callback_data_free( _parent: *mut libc::c_void, - ptr: *mut libc::c_void, + _ptr: *mut libc::c_void, _ad: *mut sys::CRYPTO_EX_DATA, _idx: libc::c_int, _argl: libc::c_long, _argp: *mut libc::c_void, ) { - if !ptr.is_null() { - unsafe { - let _ = Box::from_raw(ptr as *mut SniCallbackData); + // Intentionally empty - data is freed in cleanup_sni_ex_data() + } + + // Clean up SNI callback data from SSL ex_data + // Called after handshake to free the data and release references + unsafe fn cleanup_sni_ex_data(ssl_ptr: *mut sys::SSL) { + unsafe { + let idx = get_sni_ex_data_index(); + let data_ptr = sys::SSL_get_ex_data(ssl_ptr, idx); + if !data_ptr.is_null() { + // Free the Box<SniCallbackData> - this releases references to context and socket + let _ = Box::from_raw(data_ptr as *mut SniCallbackData); + // Clear the ex_data to prevent double-free + sys::SSL_set_ex_data(ssl_ptr, idx, std::ptr::null_mut()); } } } @@ -658,9 +638,13 @@ mod _ssl { let callback_data = &*(data_ptr as *const SniCallbackData); - // SAFETY: vm_ptr is stored during wrap_socket and is valid for the lifetime - // of the SSL connection. The handshake happens synchronously in the same thread. - let vm = &*callback_data.vm_ptr; + // Get VM from thread-local storage (set by HandshakeVmGuard in do_handshake) + let Some(vm_ptr) = HANDSHAKE_VM.with(|cell| cell.get()) else { + // VM not available - this shouldn't happen during handshake + *al = SSL_AD_INTERNAL_ERROR; + return SSL_TLSEXT_ERR_ALERT_FATAL; + }; + let vm = &*vm_ptr; // Get server name let servername = sys::SSL_get_servername(ssl_ptr, TLSEXT_NAMETYPE_host_name); @@ -674,20 +658,11 @@ mod _ssl { } }; - // Get SSL socket from SSL ex_data (stored as PySslSocket pointer) - let ssl_socket_ptr = sys::SSL_get_ex_data(ssl_ptr, 0); // Index 0 for SSL socket - let ssl_socket_obj = if !ssl_socket_ptr.is_null() { - let ssl_socket = &*(ssl_socket_ptr as *const PySslSocket); - // Try to get owner first - ssl_socket - .owner - .read() - .as_ref() - .and_then(|weak| weak.upgrade()) - .unwrap_or_else(|| vm.ctx.none()) - } else { - vm.ctx.none() - }; + // Get SSL socket from callback data via weak reference + let ssl_socket_obj = callback_data + .ssl_socket_weak + .upgrade() + .unwrap_or_else(|| vm.ctx.none()); // Call the Python callback match callback.call( @@ -735,81 +710,20 @@ mod _ssl { } // Message callback function called by OpenSSL - // Based on CPython's _PySSL_msg_callback in Modules/_ssl/debughelpers.c + // NOTE: This callback is intentionally a no-op to avoid deadlocks. + // The msg_callback can be called during various SSL operations (read, write, handshake), + // and invoking Python code from within these operations can cause deadlocks + // (see CPython bpo-43577). A proper implementation would require careful lock ordering. unsafe extern "C" fn _msg_callback( - write_p: libc::c_int, - version: libc::c_int, - content_type: libc::c_int, - buf: *const libc::c_void, - len: usize, - ssl_ptr: *mut sys::SSL, + _write_p: libc::c_int, + _version: libc::c_int, + _content_type: libc::c_int, + _buf: *const libc::c_void, + _len: usize, + _ssl_ptr: *mut sys::SSL, _arg: *mut libc::c_void, ) { - if ssl_ptr.is_null() { - return; - } - - unsafe { - // Get SSL socket from SSL_get_app_data (index 0) - let ssl_socket_ptr = sys::SSL_get_ex_data(ssl_ptr, 0); - if ssl_socket_ptr.is_null() { - return; - } - - let ssl_socket = &*(ssl_socket_ptr as *const PySslSocket); - - // Get the callback from the context - let callback_opt = ssl_socket.ctx.read().msg_callback.lock().clone(); - let Some(callback) = callback_opt else { - return; - }; - - // Get callback data from SSL ex_data (for VM) - let idx = get_sni_ex_data_index(); - let data_ptr = sys::SSL_get_ex_data(ssl_ptr, idx); - if data_ptr.is_null() { - return; - } - - let callback_data = &*(data_ptr as *const SniCallbackData); - let vm = &*callback_data.vm_ptr; - - // Get SSL socket owner object - let ssl_socket_obj = ssl_socket - .owner - .read() - .as_ref() - .and_then(|weak| weak.upgrade()) - .unwrap_or_else(|| vm.ctx.none()); - - // Create the message bytes - let buf_slice = std::slice::from_raw_parts(buf as *const u8, len); - let msg_bytes = vm.ctx.new_bytes(buf_slice.to_vec()); - - // Determine direction string - let direction_str = if write_p != 0 { "write" } else { "read" }; - - // Call the Python callback - // Signature: callback(conn, direction, version, content_type, msg_type, data) - // For simplicity, we'll pass msg_type as 0 (would need more parsing to get the actual type) - match callback.call( - ( - ssl_socket_obj, - vm.ctx.new_str(direction_str), - vm.ctx.new_int(version), - vm.ctx.new_int(content_type), - vm.ctx.new_int(0), // msg_type - would need parsing - msg_bytes, - ), - vm, - ) { - Ok(_) => {} - Err(exc) => { - // Log the exception but don't propagate it - vm.run_unraisable(exc, None, vm.ctx.none()); - } - } - } + // Intentionally empty to avoid deadlocks } #[pyfunction(name = "RAND_pseudo_bytes")] @@ -850,7 +764,11 @@ mod _ssl { impl Constructor for PySslContext { type Args = i32; - fn py_new(cls: PyTypeRef, proto_version: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new( + _cls: &Py<PyType>, + proto_version: Self::Args, + vm: &VirtualMachine, + ) -> PyResult<Self> { let proto = SslVersion::try_from(proto_version) .map_err(|_| vm.new_value_error("invalid protocol version"))?; let method = match proto { @@ -932,16 +850,14 @@ mod _ssl { sys::X509_VERIFY_PARAM_set_flags(param, sys::X509_V_FLAG_TRUSTED_FIRST); } - PySslContext { + Ok(PySslContext { ctx: PyRwLock::new(builder), check_hostname: AtomicCell::new(check_hostname), protocol: proto, post_handshake_auth: PyMutex::new(false), sni_callback: PyMutex::new(None), msg_callback: PyMutex::new(None), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } @@ -981,12 +897,9 @@ mod _ssl { if ciphers.contains('\0') { return Err(exceptions::cstring_error(vm)); } - self.builder().set_cipher_list(ciphers).map_err(|_| { - vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "No cipher can be selected.".to_owned(), - ) - }) + self.builder() + .set_cipher_list(ciphers) + .map_err(|_| new_ssl_error(vm, "No cipher can be selected.")) } #[pymethod] @@ -1126,16 +1039,10 @@ mod _ssl { let set = !flags & new_flags; if clear != 0 && sys::X509_VERIFY_PARAM_clear_flags(param, clear) == 0 { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Failed to clear verify flags".to_owned(), - )); + return Err(new_ssl_error(vm, "Failed to clear verify flags")); } if set != 0 && sys::X509_VERIFY_PARAM_set_flags(param, set) == 0 { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Failed to set verify flags".to_owned(), - )); + return Err(new_ssl_error(vm, "Failed to set verify flags")); } Ok(()) } @@ -1477,10 +1384,13 @@ mod _ssl { let fp = rustpython_common::fileutils::fopen(path.as_path(), "rb").map_err(|e| { match e.kind() { - std::io::ErrorKind::NotFound => vm.new_exception_msg( - vm.ctx.exceptions.file_not_found_error.to_owned(), - e.to_string(), - ), + std::io::ErrorKind::NotFound => vm + .new_os_subtype_error( + vm.ctx.exceptions.file_not_found_error.to_owned(), + None, + e.to_string(), + ) + .upcast(), _ => vm.new_os_error(e.to_string()), } })?; @@ -1670,15 +1580,15 @@ mod _ssl { ) -> PyResult<(ssl::Ssl, SslServerOrClient, Option<PyStrRef>)> { // Validate socket type and context protocol if server_side && ctx_ref.protocol == SslVersion::TlsClient { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context".to_owned(), + return Err(new_ssl_error( + vm, + "Cannot create a server socket with a PROTOCOL_TLS_CLIENT context", )); } if !server_side && ctx_ref.protocol == SslVersion::TlsServer { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Cannot create a client socket with a PROTOCOL_TLS_SERVER context".to_owned(), + return Err(new_ssl_error( + vm, + "Cannot create a client socket with a PROTOCOL_TLS_SERVER context", )); } @@ -1791,21 +1701,22 @@ mod _ssl { let py_ref = py_ssl_socket.into_ref_with_type(vm, PySslSocket::class(&vm.ctx).to_owned())?; - // Set SNI callback data if callback is configured - if zelf.sni_callback.lock().is_some() { + // Check if SNI callback is configured (minimize lock time) + let has_sni_callback = zelf.sni_callback.lock().is_some(); + + // Set SNI callback data if needed (after releasing the lock) + if has_sni_callback { + let ssl_socket_weak = py_ref.as_object().downgrade(None, vm)?; unsafe { let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); - // Store callback data in SSL ex_data + // Store callback data in SSL ex_data - use weak reference to avoid cycle let callback_data = Box::new(SniCallbackData { ssl_context: zelf.clone(), - vm_ptr: vm as *const _, + ssl_socket_weak, }); let idx = get_sni_ex_data_index(); sys::SSL_set_ex_data(ssl_ptr, idx, Box::into_raw(callback_data) as *mut _); - - // Store PyRef pointer (heap-allocated) in ex_data index 0 - sys::SSL_set_ex_data(ssl_ptr, 0, &*py_ref as *const _ as *mut _); } } @@ -1851,21 +1762,22 @@ mod _ssl { let py_ref = py_ssl_socket.into_ref_with_type(vm, PySslSocket::class(&vm.ctx).to_owned())?; - // Set SNI callback data if callback is configured - if zelf.sni_callback.lock().is_some() { + // Check if SNI callback is configured (minimize lock time) + let has_sni_callback = zelf.sni_callback.lock().is_some(); + + // Set SNI callback data if needed (after releasing the lock) + if has_sni_callback { + let ssl_socket_weak = py_ref.as_object().downgrade(None, vm)?; unsafe { let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); - // Store callback data in SSL ex_data + // Store callback data in SSL ex_data - use weak reference to avoid cycle let callback_data = Box::new(SniCallbackData { ssl_context: zelf.clone(), - vm_ptr: vm as *const _, + ssl_socket_weak, }); let idx = get_sni_ex_data_index(); sys::SSL_set_ex_data(ssl_ptr, idx, Box::into_raw(callback_data) as *mut _); - - // Store PyRef pointer (heap-allocated) in ex_data index 0 - sys::SSL_set_ex_data(ssl_ptr, 0, &*py_ref as *const _ as *mut _); } } @@ -1992,10 +1904,7 @@ mod _ssl { } fn socket_closed_error(vm: &VirtualMachine) -> PyBaseExceptionRef { - vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "Underlying socket has been closed.".to_owned(), - ) + new_ssl_error(vm, "Underlying socket has been closed.") } // BIO stream wrapper to implement Read/Write traits for MemoryBIO @@ -2152,12 +2061,13 @@ mod _ssl { } #[pygetset(setter)] fn set_context(&self, value: PyRef<PySslContext>, vm: &VirtualMachine) -> PyResult<()> { - // Update the SSL context in the underlying SSL object - let stream = self.connection.read(); + // Get SSL pointer - use thread-local during handshake to avoid deadlock + // (connection lock is already held during handshake) + let ssl_ptr = get_ssl_ptr_for_context_change(&self.connection); // Set the new SSL_CTX on the SSL object unsafe { - let result = SSL_set_SSL_CTX(stream.ssl().as_ptr(), value.ctx().as_ptr()); + let result = SSL_set_SSL_CTX(ssl_ptr, value.ctx().as_ptr()); if result.is_null() { return Err(vm.new_runtime_error("Failed to set SSL context".to_owned())); } @@ -2275,6 +2185,12 @@ mod _ssl { .map(cipher_to_tuple) } + #[pymethod] + fn pending(&self) -> i32 { + let stream = self.connection.read(); + unsafe { sys::SSL_pending(stream.ssl().as_ptr()) } + } + #[pymethod] fn shared_ciphers(&self, vm: &VirtualMachine) -> Option<PyListRef> { #[cfg(ossl110)] @@ -2450,8 +2366,8 @@ mod _ssl { // Non-blocking would block - this is okay for shutdown // Return the underlying socket } else { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), + return Err(new_ssl_error( + vm, format!("SSL shutdown failed: error code {}", err), )); } @@ -2491,9 +2407,13 @@ mod _ssl { let mut stream = self.connection.write(); let ssl_ptr = stream.ssl().as_ptr(); + // Set up thread-local VM and SSL pointer for callbacks + // This allows callbacks to access SSL without acquiring the connection lock + let _vm_guard = HandshakeVmGuard::new(vm, ssl_ptr); + // BIO mode: no timeout/select logic, just do handshake if stream.is_bio() { - return stream.do_handshake().map_err(|e| { + let result = stream.do_handshake().map_err(|e| { let exc = convert_ssl_error(vm, e); // If it's a cert verification error, set verify info if exc.class().is(PySslCertVerificationError::class(&vm.ctx)) { @@ -2501,6 +2421,10 @@ mod _ssl { } exc }); + // Clean up SNI ex_data after handshake (success or failure) + // SAFETY: ssl_ptr is valid for the lifetime of stream + unsafe { cleanup_sni_ex_data(ssl_ptr) }; + return result; } // Socket mode: handle timeout and blocking @@ -2510,7 +2434,12 @@ mod _ssl { .timeout_deadline(); loop { let err = match stream.do_handshake() { - Ok(()) => return Ok(()), + Ok(()) => { + // Clean up SNI ex_data after successful handshake + // SAFETY: ssl_ptr is valid for the lifetime of stream + unsafe { cleanup_sni_ex_data(ssl_ptr) }; + return Ok(()); + } Err(e) => e, }; let (needs, state) = stream @@ -2519,12 +2448,20 @@ mod _ssl { .socket_needs(&err, &timeout); match state { SelectRet::TimedOut => { + // Clean up SNI ex_data before returning error + // SAFETY: ssl_ptr is valid for the lifetime of stream + unsafe { cleanup_sni_ex_data(ssl_ptr) }; return Err(socket::timeout_error_msg( vm, "The handshake operation timed out".to_owned(), - )); + ) + .upcast()); + } + SelectRet::Closed => { + // SAFETY: ssl_ptr is valid for the lifetime of stream + unsafe { cleanup_sni_ex_data(ssl_ptr) }; + return Err(socket_closed_error(vm)); } - SelectRet::Closed => return Err(socket_closed_error(vm)), SelectRet::Nonblocking => {} SelectRet::IsBlocking | SelectRet::Ok => { // For blocking sockets, select() has completed successfully @@ -2539,6 +2476,9 @@ mod _ssl { if exc.class().is(PySslCertVerificationError::class(&vm.ctx)) { set_verify_error_info(&exc, ssl_ptr, vm); } + // Clean up SNI ex_data before returning error + // SAFETY: ssl_ptr is valid for the lifetime of stream + unsafe { cleanup_sni_ex_data(ssl_ptr) }; return Err(exc); } } @@ -2565,7 +2505,8 @@ mod _ssl { return Err(socket::timeout_error_msg( vm, "The write operation timed out".to_owned(), - )); + ) + .upcast()); } SelectRet::Closed => return Err(socket_closed_error(vm)), _ => {} @@ -2584,7 +2525,8 @@ mod _ssl { return Err(socket::timeout_error_msg( vm, "The write operation timed out".to_owned(), - )); + ) + .upcast()); } SelectRet::Closed => return Err(socket_closed_error(vm)), SelectRet::Nonblocking => {} @@ -2727,7 +2669,8 @@ mod _ssl { return Err(socket::timeout_error_msg( vm, "The read operation timed out".to_owned(), - )); + ) + .upcast()); } SelectRet::Closed => return Err(socket_closed_error(vm)), SelectRet::Nonblocking => {} @@ -3070,7 +3013,7 @@ mod _ssl { impl Constructor for PySslMemoryBio { type Args = (); - fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py<PyType>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { unsafe { let bio = sys::BIO_new(sys::BIO_s_mem()); if bio.is_null() { @@ -3080,12 +3023,10 @@ mod _ssl { sys::BIO_set_retry_read(bio); BIO_set_mem_eof_return(bio, -1); - PySslMemoryBio { + Ok(PySslMemoryBio { bio, eof_written: AtomicCell::new(false), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + }) } } } @@ -3143,10 +3084,7 @@ mod _ssl { #[pymethod] fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<i32> { if self.eof_written.load() { - return Err(vm.new_exception_msg( - PySslError::class(&vm.ctx).to_owned(), - "cannot write() after write_eof()".to_owned(), - )); + return Err(new_ssl_error(vm, "cannot write() after write_eof()")); } data.with_ref(|buf| unsafe { @@ -3235,6 +3173,12 @@ mod _ssl { } } + /// Helper function to create SSL error with proper OSError subtype handling + fn new_ssl_error(vm: &VirtualMachine, msg: impl ToString) -> PyBaseExceptionRef { + vm.new_os_subtype_error(PySslError::class(&vm.ctx).to_owned(), None, msg.to_string()) + .upcast() + } + #[track_caller] pub(crate) fn convert_openssl_error( vm: &VirtualMachine, @@ -3255,12 +3199,7 @@ mod _ssl { } else { vm.ctx.exceptions.os_error.to_owned() }; - let exc = vm.new_exception(exc_type, vec![vm.ctx.new_int(reason).into()]); - // Set errno attribute explicitly - let _ = exc - .as_object() - .set_attr("errno", vm.ctx.new_int(reason), vm); - return exc; + return vm.new_os_subtype_error(exc_type, Some(reason), "").upcast(); } let caller = std::panic::Location::caller(); @@ -3310,13 +3249,8 @@ mod _ssl { // Create exception instance let reason = sys::ERR_GET_REASON(e.code()); - let exc = vm.new_exception( - cls, - vec![vm.ctx.new_int(reason).into(), vm.ctx.new_str(msg).into()], - ); - - // Set attributes on instance, not class - let exc_obj: PyObjectRef = exc.into(); + let exc = vm.new_os_subtype_error(cls, Some(reason), msg); + let exc_obj: PyObjectRef = exc.upcast::<PyBaseException>().into(); // Set reason attribute (always set, even if just the error string) let reason_value = vm.ctx.new_str(errstr); @@ -3345,7 +3279,8 @@ mod _ssl { } None => { let cls = PySslError::class(&vm.ctx).to_owned(); - vm.new_exception_empty(cls) + vm.new_os_subtype_error(cls, None, "unknown SSL error") + .upcast() } } } @@ -3396,15 +3331,13 @@ mod _ssl { // this is an EOF in violation of protocol -> SSLEOFError // Need to set args[0] = SSL_ERROR_EOF for suppress_ragged_eofs check None => { - return vm.new_exception( - PySslEOFError::class(&vm.ctx).to_owned(), - vec![ - vm.ctx.new_int(SSL_ERROR_EOF).into(), - vm.ctx - .new_str("EOF occurred in violation of protocol") - .into(), - ], - ); + return vm + .new_os_subtype_error( + PySslEOFError::class(&vm.ctx).to_owned(), + Some(SSL_ERROR_EOF as i32), + "EOF occurred in violation of protocol", + ) + .upcast(); } }, ssl::ErrorCode::SSL => { @@ -3417,15 +3350,13 @@ mod _ssl { let reason = sys::ERR_GET_REASON(err_code); let lib = sys::ERR_GET_LIB(err_code); if lib == ERR_LIB_SSL && reason == SSL_R_UNEXPECTED_EOF_WHILE_READING { - return vm.new_exception( - PySslEOFError::class(&vm.ctx).to_owned(), - vec![ - vm.ctx.new_int(SSL_ERROR_EOF).into(), - vm.ctx - .new_str("EOF occurred in violation of protocol") - .into(), - ], - ); + return vm + .new_os_subtype_error( + PySslEOFError::class(&vm.ctx).to_owned(), + Some(SSL_ERROR_EOF as i32), + "EOF occurred in violation of protocol", + ) + .upcast(); } } return convert_openssl_error(vm, ssl_err.clone()); @@ -3440,7 +3371,7 @@ mod _ssl { "A failure in the SSL library occurred", ), }; - vm.new_exception_msg(cls, msg.to_owned()) + vm.new_os_subtype_error(cls, None, msg).upcast() } // SSL_FILETYPE_ASN1 part of _add_ca_certs in CPython @@ -3543,10 +3474,13 @@ mod _ssl { ) -> Result<(), PyBaseExceptionRef> { let root = Path::new(CERT_DIR); if !root.is_dir() { - return Err(vm.new_exception_msg( - vm.ctx.exceptions.file_not_found_error.to_owned(), - CERT_DIR.to_string(), - )); + return Err(vm + .new_os_subtype_error( + vm.ctx.exceptions.file_not_found_error.to_owned(), + None, + CERT_DIR.to_string(), + ) + .upcast()); } let mut combined_pem = String::new(); diff --git a/crates/stdlib/src/openssl/cert.rs b/crates/stdlib/src/openssl/cert.rs index 1139f0e26f0..1197bf4aa46 100644 --- a/crates/stdlib/src/openssl/cert.rs +++ b/crates/stdlib/src/openssl/cert.rs @@ -165,7 +165,8 @@ pub(crate) mod ssl_cert { format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]) } else if ip.len() == 16 { // IPv6 - format with all zeros visible (not compressed) - let ip_addr = std::net::Ipv6Addr::from(ip[0..16]); + let ip_addr = + std::net::Ipv6Addr::from(<[u8; 16]>::try_from(&ip[0..16]).unwrap()); let s = ip_addr.segments(); format!( "{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}:{:X}", diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index e6f8c5fda7c..992b32e00ea 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -22,11 +22,14 @@ mod cert; // OpenSSL compatibility layer (abstracts rustls operations) mod compat; +// SSL exception types (shared with openssl backend) +mod error; + pub(crate) use _ssl::make_module; #[allow(non_snake_case)] #[allow(non_upper_case_globals)] -#[pymodule] +#[pymodule(with(error::ssl_error))] mod _ssl { use crate::{ common::{ @@ -37,15 +40,18 @@ mod _ssl { vm::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, - builtins::{ - PyBaseExceptionRef, PyBytesRef, PyListRef, PyOSError, PyStrRef, PyType, PyTypeRef, - }, + builtins::{PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyType, PyTypeRef}, convert::IntoPyException, function::{ArgBytesLike, ArgMemoryBuffer, FuncArgs, OptionalArg, PyComparisonValue}, stdlib::warnings, types::{Comparable, Constructor, Hashable, PyComparisonOp, Representable}, }, }; + + // Import error types used in this module (others are exposed via pymodule(with(...))) + use super::error::{ + PySSLEOFError, PySSLError, create_ssl_want_read_error, create_ssl_want_write_error, + }; use std::{ collections::HashMap, sync::{ @@ -342,106 +348,6 @@ mod _ssl { #[pyattr] const ENCODING_PEM_AUX: i32 = 0x101; // PEM + 0x100 - #[pyattr] - #[pyexception(name = "SSLError", base = PyOSError)] - #[derive(Debug)] - #[repr(transparent)] - pub struct PySSLError(PyOSError); - - #[pyexception] - impl PySSLError { - // Returns strerror attribute if available, otherwise str(args) - #[pymethod] - fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { - // Try to get strerror attribute first (OSError compatibility) - if let Ok(strerror) = exc.as_object().get_attr("strerror", vm) - && !vm.is_none(&strerror) - { - return strerror.str(vm); - } - - // Otherwise return str(args) - let args = exc.args(); - if args.len() == 1 { - args.as_slice()[0].str(vm) - } else { - args.as_object().str(vm) - } - } - } - - #[pyattr] - #[pyexception(name = "SSLZeroReturnError", base = PySSLError)] - #[derive(Debug)] - #[repr(transparent)] - pub struct PySSLZeroReturnError(PySSLError); - - #[pyexception] - impl PySSLZeroReturnError {} - - #[pyattr] - #[pyexception(name = "SSLWantReadError", base = PySSLError, impl)] - #[derive(Debug)] - #[repr(transparent)] - pub struct PySSLWantReadError(PySSLError); - - #[pyattr] - #[pyexception(name = "SSLWantWriteError", base = PySSLError, impl)] - #[derive(Debug)] - #[repr(transparent)] - pub struct PySSLWantWriteError(PySSLError); - - #[pyattr] - #[pyexception(name = "SSLSyscallError", base = PySSLError, impl)] - #[derive(Debug)] - #[repr(transparent)] - pub struct PySSLSyscallError(PySSLError); - - #[pyattr] - #[pyexception(name = "SSLEOFError", base = PySSLError, impl)] - #[derive(Debug)] - #[repr(transparent)] - pub struct PySSLEOFError(PySSLError); - - #[pyattr] - #[pyexception(name = "SSLCertVerificationError", base = PySSLError, impl)] - #[derive(Debug)] - #[repr(transparent)] - pub struct PySSLCertVerificationError(PySSLError); - - // Helper functions to create SSL exceptions with proper errno attribute - pub(super) fn create_ssl_want_read_error(vm: &VirtualMachine) -> PyRef<PyOSError> { - vm.new_os_subtype_error( - PySSLWantReadError::class(&vm.ctx).to_owned(), - Some(SSL_ERROR_WANT_READ), - "The operation did not complete (read)", - ) - } - - pub(super) fn create_ssl_want_write_error(vm: &VirtualMachine) -> PyRef<PyOSError> { - vm.new_os_subtype_error( - PySSLWantWriteError::class(&vm.ctx).to_owned(), - Some(SSL_ERROR_WANT_WRITE), - "The operation did not complete (write)", - ) - } - - pub(crate) fn create_ssl_eof_error(vm: &VirtualMachine) -> PyRef<PyOSError> { - vm.new_os_subtype_error( - PySSLEOFError::class(&vm.ctx).to_owned(), - None, - "EOF occurred in violation of protocol", - ) - } - - pub(crate) fn create_ssl_zero_return_error(vm: &VirtualMachine) -> PyRef<PyOSError> { - vm.new_os_subtype_error( - PySSLZeroReturnError::class(&vm.ctx).to_owned(), - None, - "TLS/SSL connection has been closed (EOF)", - ) - } - /// Validate server hostname for TLS SNI /// /// Checks that the hostname: diff --git a/crates/stdlib/src/ssl/compat.rs b/crates/stdlib/src/ssl/compat.rs index 4ccc590360a..ab3c81b7a4e 100644 --- a/crates/stdlib/src/ssl/compat.rs +++ b/crates/stdlib/src/ssl/compat.rs @@ -30,10 +30,13 @@ use rustpython_vm::{AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObjec use std::io::Read; use std::sync::{Arc, Once}; -// Import PySSLSocket and helper functions from parent module -use super::_ssl::{ - PySSLCertVerificationError, PySSLError, PySSLSocket, create_ssl_eof_error, - create_ssl_want_read_error, create_ssl_want_write_error, create_ssl_zero_return_error, +// Import PySSLSocket from parent module +use super::_ssl::PySSLSocket; + +// Import error types and helper functions from error module +use super::error::{ + PySSLCertVerificationError, PySSLError, create_ssl_eof_error, create_ssl_want_read_error, + create_ssl_want_write_error, create_ssl_zero_return_error, }; // SSL Verification Flags diff --git a/crates/stdlib/src/ssl/error.rs b/crates/stdlib/src/ssl/error.rs new file mode 100644 index 00000000000..e31683ec72d --- /dev/null +++ b/crates/stdlib/src/ssl/error.rs @@ -0,0 +1,117 @@ +// SSL exception types shared between ssl (rustls) and openssl backends + +pub(crate) use ssl_error::*; + +#[pymodule(sub)] +pub(crate) mod ssl_error { + use crate::vm::{ + PyPayload, PyRef, PyResult, VirtualMachine, + builtins::{PyBaseExceptionRef, PyOSError, PyStrRef}, + types::Constructor, + }; + + // Error type constants (needed for create_ssl_want_read_error etc.) + pub(crate) const SSL_ERROR_WANT_READ: i32 = 2; + pub(crate) const SSL_ERROR_WANT_WRITE: i32 = 3; + + #[pyattr] + #[pyexception(name = "SSLError", base = PyOSError)] + #[derive(Debug)] + #[repr(transparent)] + pub struct PySSLError(PyOSError); + + #[pyexception] + impl PySSLError { + // Returns strerror attribute if available, otherwise str(args) + #[pymethod] + fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { + use crate::vm::AsObject; + // Try to get strerror attribute first (OSError compatibility) + if let Ok(strerror) = exc.as_object().get_attr("strerror", vm) + && !vm.is_none(&strerror) + { + return strerror.str(vm); + } + + // Otherwise return str(args) + let args = exc.args(); + if args.len() == 1 { + args.as_slice()[0].str(vm) + } else { + args.as_object().str(vm) + } + } + } + + #[pyattr] + #[pyexception(name = "SSLZeroReturnError", base = PySSLError)] + #[derive(Debug)] + #[repr(transparent)] + pub struct PySSLZeroReturnError(PySSLError); + + #[pyexception] + impl PySSLZeroReturnError {} + + #[pyattr] + #[pyexception(name = "SSLWantReadError", base = PySSLError, impl)] + #[derive(Debug)] + #[repr(transparent)] + pub struct PySSLWantReadError(PySSLError); + + #[pyattr] + #[pyexception(name = "SSLWantWriteError", base = PySSLError, impl)] + #[derive(Debug)] + #[repr(transparent)] + pub struct PySSLWantWriteError(PySSLError); + + #[pyattr] + #[pyexception(name = "SSLSyscallError", base = PySSLError, impl)] + #[derive(Debug)] + #[repr(transparent)] + pub struct PySSLSyscallError(PySSLError); + + #[pyattr] + #[pyexception(name = "SSLEOFError", base = PySSLError, impl)] + #[derive(Debug)] + #[repr(transparent)] + pub struct PySSLEOFError(PySSLError); + + #[pyattr] + #[pyexception(name = "SSLCertVerificationError", base = PySSLError, impl)] + #[derive(Debug)] + #[repr(transparent)] + pub struct PySSLCertVerificationError(PySSLError); + + // Helper functions to create SSL exceptions with proper errno attribute + pub fn create_ssl_want_read_error(vm: &VirtualMachine) -> PyRef<PyOSError> { + vm.new_os_subtype_error( + PySSLWantReadError::class(&vm.ctx).to_owned(), + Some(SSL_ERROR_WANT_READ), + "The operation did not complete (read)", + ) + } + + pub fn create_ssl_want_write_error(vm: &VirtualMachine) -> PyRef<PyOSError> { + vm.new_os_subtype_error( + PySSLWantWriteError::class(&vm.ctx).to_owned(), + Some(SSL_ERROR_WANT_WRITE), + "The operation did not complete (write)", + ) + } + + pub fn create_ssl_eof_error(vm: &VirtualMachine) -> PyRef<PyOSError> { + vm.new_os_subtype_error( + PySSLEOFError::class(&vm.ctx).to_owned(), + None, + "EOF occurred in violation of protocol", + ) + } + + pub fn create_ssl_zero_return_error(vm: &VirtualMachine) -> PyRef<PyOSError> { + vm.new_os_subtype_error( + PySSLZeroReturnError::class(&vm.ctx).to_owned(), + None, + "TLS/SSL connection has been closed (EOF)", + ) + } +} From 70b93898d4e4dfe5b9badc29618a0fbbc6542787 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:10:55 +0900 Subject: [PATCH 526/819] ctypes overhaul (#6450) --- .cspell.dict/python-more.txt | 2 + .cspell.json | 5 +- crates/stdlib/src/pystruct.rs | 2 +- crates/vm/src/buffer.rs | 50 + crates/vm/src/builtins/builtin_func.rs | 4 +- crates/vm/src/builtins/function.rs | 5 +- crates/vm/src/builtins/object.rs | 4 +- crates/vm/src/builtins/str.rs | 6 +- crates/vm/src/builtins/type.rs | 4 +- crates/vm/src/exceptions.rs | 11 +- crates/vm/src/object/core.rs | 22 + crates/vm/src/protocol/buffer.rs | 18 +- crates/vm/src/stdlib/ast/python.rs | 4 +- crates/vm/src/stdlib/codecs.rs | 4 +- crates/vm/src/stdlib/ctypes.rs | 1351 ++++++---- crates/vm/src/stdlib/ctypes/array.rs | 1658 +++++++----- crates/vm/src/stdlib/ctypes/base.rs | 3068 +++++++++++++++------- crates/vm/src/stdlib/ctypes/function.rs | 1986 ++++++++++++-- crates/vm/src/stdlib/ctypes/library.rs | 34 +- crates/vm/src/stdlib/ctypes/pointer.rs | 792 ++++-- crates/vm/src/stdlib/ctypes/simple.rs | 1379 ++++++++++ crates/vm/src/stdlib/ctypes/structure.rs | 747 +++--- crates/vm/src/stdlib/ctypes/thunk.rs | 319 --- crates/vm/src/stdlib/ctypes/union.rs | 665 +++-- crates/vm/src/stdlib/ctypes/util.rs | 88 - crates/vm/src/stdlib/functools.rs | 4 +- crates/vm/src/stdlib/operator.rs | 2 +- crates/vm/src/types/structseq.rs | 3 +- 28 files changed, 8879 insertions(+), 3358 deletions(-) create mode 100644 crates/vm/src/stdlib/ctypes/simple.rs delete mode 100644 crates/vm/src/stdlib/ctypes/thunk.rs delete mode 100644 crates/vm/src/stdlib/ctypes/util.rs diff --git a/.cspell.dict/python-more.txt b/.cspell.dict/python-more.txt index 58a0e816087..e8534e9744a 100644 --- a/.cspell.dict/python-more.txt +++ b/.cspell.dict/python-more.txt @@ -148,6 +148,7 @@ nbytes ncallbacks ndigits ndim +needsfree nldecoder nlocals NOARGS @@ -168,6 +169,7 @@ pycache pycodecs pycs pyexpat +PYTHONAPI PYTHONBREAKPOINT PYTHONDEBUG PYTHONDONTWRITEBYTECODE diff --git a/.cspell.json b/.cspell.json index 9f88a74f96d..3bd06fc2032 100644 --- a/.cspell.json +++ b/.cspell.json @@ -75,9 +75,9 @@ "makeunicodedata", "miri", "notrace", + "oparg", "openat", "pyarg", - "pyarg", "pyargs", "pyast", "PyAttr", @@ -107,6 +107,7 @@ "pystruct", "pystructseq", "pytrace", + "pytype", "reducelib", "richcompare", "RustPython", @@ -116,7 +117,6 @@ "sysmodule", "tracebacks", "typealiases", - "unconstructible", "unhashable", "uninit", "unraisable", @@ -131,6 +131,7 @@ "getrusage", "nanosleep", "sigaction", + "sighandler", "WRLCK", // win32 "birthtime", diff --git a/crates/stdlib/src/pystruct.rs b/crates/stdlib/src/pystruct.rs index 0a006f5a0f2..34a4905ed9f 100644 --- a/crates/stdlib/src/pystruct.rs +++ b/crates/stdlib/src/pystruct.rs @@ -28,7 +28,7 @@ pub(crate) mod _struct { // CPython turns str to bytes but we do reversed way here // The only performance difference is this transition cost let fmt = match_class!(match obj { - s @ PyStr => s.is_ascii().then_some(s), + s @ PyStr => s.isascii().then_some(s), b @ PyBytes => ascii::AsciiStr::from_ascii(&b) .ok() .map(|s| vm.ctx.new_str(s)), diff --git a/crates/vm/src/buffer.rs b/crates/vm/src/buffer.rs index cf49d6815c0..eeb6a676542 100644 --- a/crates/vm/src/buffer.rs +++ b/crates/vm/src/buffer.rs @@ -261,6 +261,56 @@ impl FormatCode { return Err("embedded null character".to_owned()); } + // PEP3118: Handle extended format specifiers + // T{...} - struct, X{} - function pointer, (...) - array shape, :name: - field name + if c == b'T' || c == b'X' { + // Skip struct/function pointer: consume until matching '}' + if chars.peek() == Some(&b'{') { + chars.next(); // consume '{' + let mut depth = 1; + while depth > 0 { + match chars.next() { + Some(b'{') => depth += 1, + Some(b'}') => depth -= 1, + None => return Err("unmatched '{' in format".to_owned()), + _ => {} + } + } + continue; + } + } + + if c == b'(' { + // Skip array shape: consume until matching ')' + let mut depth = 1; + while depth > 0 { + match chars.next() { + Some(b'(') => depth += 1, + Some(b')') => depth -= 1, + None => return Err("unmatched '(' in format".to_owned()), + _ => {} + } + } + continue; + } + + if c == b':' { + // Skip field name: consume until next ':' + loop { + match chars.next() { + Some(b':') => break, + None => return Err("unmatched ':' in format".to_owned()), + _ => {} + } + } + continue; + } + + if c == b'{' || c == b'}' { + // Skip standalone braces (pointer targets, etc.) + continue; + } + let code = FormatType::try_from(c) .ok() .filter(|c| match c { diff --git a/crates/vm/src/builtins/builtin_func.rs b/crates/vm/src/builtins/builtin_func.rs index da5fd5e8075..2b569375b28 100644 --- a/crates/vm/src/builtins/builtin_func.rs +++ b/crates/vm/src/builtins/builtin_func.rs @@ -114,7 +114,7 @@ impl PyNativeFunction { zelf.0.value.doc } - #[pygetset(name = "__self__")] + #[pygetset] fn __self__(_zelf: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { vm.ctx.none() } @@ -181,7 +181,7 @@ impl PyNativeMethod { Ok((getattr, (target, name))) } - #[pygetset(name = "__self__")] + #[pygetset] fn __self__(zelf: PyRef<Self>, _vm: &VirtualMachine) -> Option<PyObjectRef> { zelf.func.zelf.clone() } diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index 0459cecbdd2..c29e45ddcf6 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -629,12 +629,11 @@ impl GetDescriptor for PyFunction { vm: &VirtualMachine, ) -> PyResult { let (_zelf, obj) = Self::_unwrap(&zelf, obj, vm)?; - let obj = if vm.is_none(&obj) && !Self::_cls_is(&cls, obj.class()) { + Ok(if vm.is_none(&obj) && !Self::_cls_is(&cls, obj.class()) { zelf } else { PyBoundMethod::new(obj, zelf).into_ref(&vm.ctx).into() - }; - Ok(obj) + }) } } diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 6f917cd853c..cb95652f937 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -450,8 +450,8 @@ impl PyBaseObject { Ok(()) } - #[pygetset(name = "__class__")] - fn get_class(obj: PyObjectRef) -> PyTypeRef { + #[pygetset] + fn __class__(obj: PyObjectRef) -> PyTypeRef { obj.class().to_owned() } diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 279b84362a6..8084c4d053e 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -625,9 +625,9 @@ impl PyStr { self.data.char_len() } - #[pymethod(name = "isascii")] + #[pymethod] #[inline(always)] - pub const fn is_ascii(&self) -> bool { + pub const fn isascii(&self) -> bool { matches!(self.kind(), StrKind::Ascii) } @@ -960,7 +960,7 @@ impl PyStr { format_map(&format_string, &mapping, vm) } - #[pymethod(name = "__format__")] + #[pymethod] fn __format__( zelf: PyRef<PyStr>, spec: PyStrRef, diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 15743350397..68de17f60b6 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1445,8 +1445,8 @@ impl GetAttr for PyType { #[pyclass] impl Py<PyType> { - #[pygetset(name = "__mro__")] - fn get_mro(&self) -> PyTuple { + #[pygetset] + fn __mro__(&self) -> PyTuple { let elements: Vec<PyObjectRef> = self.mro_map_collect(|x| x.as_object().to_owned()); PyTuple::new_unchecked(elements.into_boxed_slice()) } diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 036d914810d..bb10ca02c2c 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -624,8 +624,8 @@ impl PyBaseException { *self.context.write() = context; } - #[pygetset(name = "__suppress_context__")] - pub(super) fn get_suppress_context(&self) -> bool { + #[pygetset] + pub(super) fn __suppress_context__(&self) -> bool { self.suppress_context.load() } @@ -1112,7 +1112,7 @@ impl serde::Serialize for SerializeException<'_, '_> { .__context__() .map(|exc| SerializeExceptionOwned { vm: self.vm, exc }), )?; - struc.serialize_field("suppress_context", &self.exc.get_suppress_context())?; + struc.serialize_field("suppress_context", &self.exc.__suppress_context__())?; let args = { struct Args<'vm>(&'vm VirtualMachine, PyTupleRef); @@ -1550,6 +1550,7 @@ pub(super) mod types { pub struct PyUnboundLocalError(PyNameError); #[pyexception(name, base = PyException, ctx = "os_error")] + #[repr(C)] pub struct PyOSError { base: PyException, errno: PyAtomicRef<Option<PyObject>>, @@ -1857,8 +1858,8 @@ pub(super) mod types { self.errno.swap_to_temporary_refs(value, vm); } - #[pygetset(name = "strerror")] - fn get_strerror(&self) -> Option<PyObjectRef> { + #[pygetset] + fn strerror(&self) -> Option<PyObjectRef> { self.strerror.to_owned() } diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index e04b87de594..60b623ef3ed 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -1102,6 +1102,28 @@ where } } +impl<T: crate::class::PySubclass> Py<T> { + /// Converts `&Py<T>` to `&Py<T::Base>`. + #[inline] + pub fn to_base(&self) -> &Py<T::Base> { + debug_assert!(self.as_object().downcast_ref::<T::Base>().is_some()); + // SAFETY: T is #[repr(transparent)] over T::Base, + // so Py<T> and Py<T::Base> have the same layout. + unsafe { &*(self as *const Py<T> as *const Py<T::Base>) } + } + + /// Converts `&Py<T>` to `&Py<U>` where U is an ancestor type. + #[inline] + pub fn upcast_ref<U: PyPayload + StaticType>(&self) -> &Py<U> + where + T: StaticType, + { + debug_assert!(T::static_type().is_subtype(U::static_type())); + // SAFETY: T is a subtype of U, so Py<T> can be viewed as Py<U>. + unsafe { &*(self as *const Py<T> as *const Py<U>) } + } +} + impl<T> Borrow<PyObject> for PyRef<T> where T: PyPayload, diff --git a/crates/vm/src/protocol/buffer.rs b/crates/vm/src/protocol/buffer.rs index 1dafda203d9..948ec763dc6 100644 --- a/crates/vm/src/protocol/buffer.rs +++ b/crates/vm/src/protocol/buffer.rs @@ -202,14 +202,18 @@ impl BufferDescriptor { #[cfg(debug_assertions)] pub fn validate(self) -> Self { assert!(self.itemsize != 0); - assert!(self.ndim() != 0); - let mut shape_product = 1; - for (shape, stride, suboffset) in self.dim_desc.iter().cloned() { - shape_product *= shape; - assert!(suboffset >= 0); - assert!(stride != 0); + // ndim=0 is valid for scalar types (e.g., ctypes Structure) + if self.ndim() == 0 { + assert!(self.itemsize == self.len); + } else { + let mut shape_product = 1; + for (shape, stride, suboffset) in self.dim_desc.iter().cloned() { + shape_product *= shape; + assert!(suboffset >= 0); + assert!(stride != 0); + } + assert!(shape_product * self.itemsize == self.len); } - assert!(shape_product * self.itemsize == self.len); self } diff --git a/crates/vm/src/stdlib/ast/python.rs b/crates/vm/src/stdlib/ast/python.rs index 042db4aa74e..aa21d8b034a 100644 --- a/crates/vm/src/stdlib/ast/python.rs +++ b/crates/vm/src/stdlib/ast/python.rs @@ -47,8 +47,8 @@ pub(crate) mod _ast { Ok(()) } - #[pyattr(name = "_fields")] - fn fields(ctx: &Context) -> PyTupleRef { + #[pyattr] + fn _fields(ctx: &Context) -> PyTupleRef { ctx.empty_tuple.clone() } } diff --git a/crates/vm/src/stdlib/codecs.rs b/crates/vm/src/stdlib/codecs.rs index 5f1b721dfb4..821b313090c 100644 --- a/crates/vm/src/stdlib/codecs.rs +++ b/crates/vm/src/stdlib/codecs.rs @@ -176,7 +176,7 @@ mod _codecs { #[pyfunction] fn latin_1_encode(args: EncodeArgs, vm: &VirtualMachine) -> EncodeResult { - if args.s.is_ascii() { + if args.s.isascii() { return Ok((args.s.as_bytes().to_vec(), args.s.byte_len())); } do_codec!(latin_1::encode, args, vm) @@ -189,7 +189,7 @@ mod _codecs { #[pyfunction] fn ascii_encode(args: EncodeArgs, vm: &VirtualMachine) -> EncodeResult { - if args.s.is_ascii() { + if args.s.isascii() { return Ok((args.s.as_bytes().to_vec(), args.s.byte_len())); } do_codec!(ascii::encode, args, vm) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index ebe2d16ffb2..3fdb2df6104 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -1,77 +1,372 @@ // spell-checker:disable -pub(crate) mod array; -pub(crate) mod base; -pub(crate) mod field; -pub(crate) mod function; -pub(crate) mod library; -pub(crate) mod pointer; -pub(crate) mod structure; -pub(crate) mod thunk; -pub(crate) mod union; -pub(crate) mod util; - -use crate::builtins::PyModule; -use crate::class::PyClassImpl; -use crate::{Py, PyRef, VirtualMachine}; - -pub use crate::stdlib::ctypes::base::{CDataObject, PyCData, PyCSimple, PyCSimpleType}; - -pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py<PyModule>) { +mod array; +mod base; +mod function; +mod library; +mod pointer; +mod simple; +mod structure; +mod union; + +use crate::{ + AsObject, Py, PyObjectRef, PyRef, PyResult, VirtualMachine, + builtins::{PyModule, PyStr, PyType}, + class::PyClassImpl, + types::TypeDataRef, +}; +use std::ffi::{ + c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, + c_ulonglong, c_ushort, +}; +use std::mem; +use widestring::WideChar; + +pub use array::PyCArray; +pub use base::{FfiArgValue, PyCData, PyCField, StgInfo, StgInfoFlags}; +pub use pointer::PyCPointer; +pub use simple::{PyCSimple, PyCSimpleType}; +pub use structure::PyCStructure; +pub use union::PyCUnion; + +/// Extension for PyType to get StgInfo +/// PyStgInfo_FromType +impl Py<PyType> { + /// Get StgInfo from a ctypes type object + /// + /// Returns a TypeDataRef to StgInfo if the type has one and is initialized, error otherwise. + /// Abstract classes (whose metaclass __init__ was not called) will have uninitialized StgInfo. + fn stg_info<'a>(&'a self, vm: &VirtualMachine) -> PyResult<TypeDataRef<'a, StgInfo>> { + self.stg_info_opt() + .ok_or_else(|| vm.new_type_error("abstract class")) + } + + /// Get StgInfo if initialized, None otherwise. + fn stg_info_opt(&self) -> Option<TypeDataRef<'_, StgInfo>> { + self.get_type_data::<StgInfo>() + .filter(|info| info.initialized) + } + + /// Get _type_ attribute as String (type code like "i", "d", etc.) + fn type_code(&self, vm: &VirtualMachine) -> Option<String> { + self.as_object() + .get_attr("_type_", vm) + .ok() + .and_then(|t: PyObjectRef| t.downcast_ref::<PyStr>().map(|s| s.to_string())) + } + + /// Mark all base classes as finalized + fn mark_bases_final(&self) { + for base in self.bases.read().iter() { + if let Some(mut stg) = base.get_type_data_mut::<StgInfo>() { + stg.flags |= StgInfoFlags::DICTFLAG_FINAL; + } else { + let mut stg = StgInfo::default(); + stg.flags |= StgInfoFlags::DICTFLAG_FINAL; + let _ = base.init_type_data(stg); + } + } + } +} + +impl PyType { + /// Check if StgInfo is already initialized - prevent double initialization + pub(crate) fn check_not_initialized(&self, vm: &VirtualMachine) -> PyResult<()> { + if let Some(stg_info) = self.get_type_data::<StgInfo>() + && stg_info.initialized + { + return Err(vm.new_exception_msg( + vm.ctx.exceptions.system_error.to_owned(), + format!("StgInfo of '{}' is already initialized.", self.name()), + )); + } + Ok(()) + } +} + +// Dynamic type check helpers for PyCData +// These check if an object's type's metaclass is a subclass of a specific metaclass + +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { + let module = _ctypes::make_module(vm); let ctx = &vm.ctx; PyCSimpleType::make_class(ctx); array::PyCArrayType::make_class(ctx); - field::PyCFieldType::make_class(ctx); pointer::PyCPointerType::make_class(ctx); structure::PyCStructType::make_class(ctx); union::PyCUnionType::make_class(ctx); - extend_module!(vm, module, { + extend_module!(vm, &module, { "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PyCSimple::make_class(ctx), - "Array" => array::PyCArray::make_class(ctx), - "CField" => field::PyCField::make_class(ctx), + "Array" => PyCArray::make_class(ctx), + "CField" => PyCField::make_class(ctx), "CFuncPtr" => function::PyCFuncPtr::make_class(ctx), - "_Pointer" => pointer::PyCPointer::make_class(ctx), + "_Pointer" => PyCPointer::make_class(ctx), "_pointer_type_cache" => ctx.new_dict(), - "Structure" => structure::PyCStructure::make_class(ctx), - "CThunkObject" => thunk::PyCThunk::make_class(ctx), - "Union" => union::PyCUnion::make_class(ctx), - }) + "_array_type_cache" => ctx.new_dict(), + "Structure" => PyCStructure::make_class(ctx), + "CThunkObject" => function::PyCThunk::make_class(ctx), + "Union" => PyCUnion::make_class(ctx), + }); + module } -pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { - let module = _ctypes::make_module(vm); - extend_module_nodes(vm, &module); - module +/// Size of long double - platform dependent +/// x86_64 macOS/Linux: 16 bytes (80-bit extended + padding) +/// ARM64: 16 bytes (128-bit) +/// Windows: 8 bytes (same as double) +#[cfg(all( + any(target_arch = "x86_64", target_arch = "aarch64"), + not(target_os = "windows") +))] +const LONG_DOUBLE_SIZE: usize = 16; + +#[cfg(target_os = "windows")] +const LONG_DOUBLE_SIZE: usize = mem::size_of::<c_double>(); + +#[cfg(not(any( + all( + any(target_arch = "x86_64", target_arch = "aarch64"), + not(target_os = "windows") + ), + target_os = "windows" +)))] +const LONG_DOUBLE_SIZE: usize = mem::size_of::<c_double>(); + +/// Type information for ctypes simple types +struct TypeInfo { + pub size: usize, + pub ffi_type_fn: fn() -> libffi::middle::Type, +} + +/// Get type information (size and ffi_type) for a ctypes type code +fn type_info(ty: &str) -> Option<TypeInfo> { + use libffi::middle::Type; + match ty { + "c" => Some(TypeInfo { + size: mem::size_of::<c_schar>(), + ffi_type_fn: Type::u8, + }), + "u" => Some(TypeInfo { + size: mem::size_of::<WideChar>(), + ffi_type_fn: if mem::size_of::<WideChar>() == 2 { + Type::u16 + } else { + Type::u32 + }, + }), + "b" => Some(TypeInfo { + size: mem::size_of::<c_schar>(), + ffi_type_fn: Type::i8, + }), + "B" => Some(TypeInfo { + size: mem::size_of::<c_uchar>(), + ffi_type_fn: Type::u8, + }), + "h" | "v" => Some(TypeInfo { + size: mem::size_of::<c_short>(), + ffi_type_fn: Type::i16, + }), + "H" => Some(TypeInfo { + size: mem::size_of::<c_ushort>(), + ffi_type_fn: Type::u16, + }), + "i" => Some(TypeInfo { + size: mem::size_of::<c_int>(), + ffi_type_fn: Type::i32, + }), + "I" => Some(TypeInfo { + size: mem::size_of::<c_uint>(), + ffi_type_fn: Type::u32, + }), + "l" => Some(TypeInfo { + size: mem::size_of::<c_long>(), + ffi_type_fn: if mem::size_of::<c_long>() == 8 { + Type::i64 + } else { + Type::i32 + }, + }), + "L" => Some(TypeInfo { + size: mem::size_of::<c_ulong>(), + ffi_type_fn: if mem::size_of::<c_ulong>() == 8 { + Type::u64 + } else { + Type::u32 + }, + }), + "q" => Some(TypeInfo { + size: mem::size_of::<c_longlong>(), + ffi_type_fn: Type::i64, + }), + "Q" => Some(TypeInfo { + size: mem::size_of::<c_ulonglong>(), + ffi_type_fn: Type::u64, + }), + "f" => Some(TypeInfo { + size: mem::size_of::<c_float>(), + ffi_type_fn: Type::f32, + }), + "d" => Some(TypeInfo { + size: mem::size_of::<c_double>(), + ffi_type_fn: Type::f64, + }), + "g" => Some(TypeInfo { + // long double - platform dependent size + // x86_64 macOS/Linux: 16 bytes (80-bit extended + padding) + // ARM64: 16 bytes (128-bit) + // Windows: 8 bytes (same as double) + // Note: Use f64 as FFI type since Rust doesn't support long double natively + size: LONG_DOUBLE_SIZE, + ffi_type_fn: Type::f64, + }), + "?" => Some(TypeInfo { + size: mem::size_of::<c_uchar>(), + ffi_type_fn: Type::u8, + }), + "z" | "Z" | "P" | "X" | "O" => Some(TypeInfo { + size: mem::size_of::<usize>(), + ffi_type_fn: Type::pointer, + }), + "void" => Some(TypeInfo { + size: 0, + ffi_type_fn: Type::void, + }), + _ => None, + } +} + +/// Get size for a ctypes type code +fn get_size(ty: &str) -> usize { + type_info(ty).map(|t| t.size).expect("invalid type code") +} + +/// Get alignment for simple type codes from type_info(). +/// For primitive C types (c_int, c_long, etc.), alignment equals size. +fn get_align(ty: &str) -> usize { + get_size(ty) } #[pymodule] pub(crate) mod _ctypes { - use super::base::{CDataObject, PyCData, PyCSimple}; - use crate::builtins::PyTypeRef; + use super::library; + use super::{PyCArray, PyCData, PyCPointer, PyCSimple, PyCStructure, PyCUnion}; + use crate::builtins::{PyType, PyTypeRef}; use crate::class::StaticType; use crate::convert::ToPyObject; - use crate::function::{Either, FuncArgs, OptionalArg}; - use crate::stdlib::ctypes::library; - use crate::{AsObject, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; - use crossbeam_utils::atomic::AtomicCell; - use std::ffi::{ - c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, - c_ulonglong, c_ushort, - }; - use std::mem; - use widestring::WideChar; - - /// CArgObject - returned by byref() + use crate::function::{Either, OptionalArg}; + use crate::types::Representable; + use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; + use num_traits::ToPrimitive; + + /// CArgObject - returned by byref() and paramfunc + /// tagPyCArgObject #[pyclass(name = "CArgObject", module = "_ctypes", no_attr)] #[derive(Debug, PyPayload)] pub struct CArgObject { + /// Type tag ('P', 'V', 'i', 'd', etc.) + pub tag: u8, + /// The actual FFI value (mirrors union value) + pub value: super::FfiArgValue, + /// Reference to original object (for memory safety) pub obj: PyObjectRef, + /// Size for struct/union ('V' tag) #[allow(dead_code)] + pub size: usize, + /// Offset for byref() pub offset: isize, } - #[pyclass] + /// is_literal_char - check if character is printable literal (not \\ or ') + fn is_literal_char(c: u8) -> bool { + c < 128 && c.is_ascii_graphic() && c != b'\\' && c != b'\'' + } + + impl Representable for CArgObject { + // PyCArg_repr - use tag and value fields directly + fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { + use super::base::FfiArgValue; + + let tag_char = zelf.tag as char; + + // Format value based on tag + match zelf.tag { + b'b' | b'h' | b'i' | b'l' | b'q' => { + // Signed integers + let n = match zelf.value { + FfiArgValue::I8(v) => v as i64, + FfiArgValue::I16(v) => v as i64, + FfiArgValue::I32(v) => v as i64, + FfiArgValue::I64(v) => v, + _ => 0, + }; + Ok(format!("<cparam '{}' ({})>", tag_char, n)) + } + b'B' | b'H' | b'I' | b'L' | b'Q' => { + // Unsigned integers + let n = match zelf.value { + FfiArgValue::U8(v) => v as u64, + FfiArgValue::U16(v) => v as u64, + FfiArgValue::U32(v) => v as u64, + FfiArgValue::U64(v) => v, + _ => 0, + }; + Ok(format!("<cparam '{}' ({})>", tag_char, n)) + } + b'f' => { + let v = match zelf.value { + FfiArgValue::F32(v) => v as f64, + _ => 0.0, + }; + Ok(format!("<cparam '{}' ({})>", tag_char, v)) + } + b'd' | b'g' => { + let v = match zelf.value { + FfiArgValue::F64(v) => v, + FfiArgValue::F32(v) => v as f64, + _ => 0.0, + }; + Ok(format!("<cparam '{}' ({})>", tag_char, v)) + } + b'c' => { + // c_char - single byte + let byte = match zelf.value { + FfiArgValue::I8(v) => v as u8, + FfiArgValue::U8(v) => v, + _ => 0, + }; + if is_literal_char(byte) { + Ok(format!("<cparam '{}' ('{}')>", tag_char, byte as char)) + } else { + Ok(format!("<cparam '{}' ('\\x{:02x}')>", tag_char, byte)) + } + } + b'z' | b'Z' | b'P' | b'V' => { + // Pointer types + let ptr = match zelf.value { + FfiArgValue::Pointer(v) => v, + _ => 0, + }; + if ptr == 0 { + Ok(format!("<cparam '{}' (nil)>", tag_char)) + } else { + Ok(format!("<cparam '{}' ({:#x})>", tag_char, ptr)) + } + } + _ => { + // Default fallback + let addr = zelf.get_id(); + if is_literal_char(zelf.tag) { + Ok(format!("<cparam '{}' at {:#x}>", tag_char, addr)) + } else { + Ok(format!("<cparam {:#04x} at {:#x}>", zelf.tag, addr)) + } + } + } + } + } + + #[pyclass(with(Representable))] impl CArgObject { #[pygetset] fn _obj(&self) -> PyObjectRef { @@ -83,43 +378,43 @@ pub(crate) mod _ctypes { const __VERSION__: &str = "1.1.0"; // TODO: get properly - #[pyattr(name = "RTLD_LOCAL")] + #[pyattr] const RTLD_LOCAL: i32 = 0; // TODO: get properly - #[pyattr(name = "RTLD_GLOBAL")] + #[pyattr] const RTLD_GLOBAL: i32 = 0; #[cfg(target_os = "windows")] - #[pyattr(name = "SIZEOF_TIME_T")] - pub const SIZEOF_TIME_T: usize = 8; + #[pyattr] + const SIZEOF_TIME_T: usize = 8; #[cfg(not(target_os = "windows"))] - #[pyattr(name = "SIZEOF_TIME_T")] - pub const SIZEOF_TIME_T: usize = 4; + #[pyattr] + const SIZEOF_TIME_T: usize = 4; - #[pyattr(name = "CTYPES_MAX_ARGCOUNT")] - pub const CTYPES_MAX_ARGCOUNT: usize = 1024; + #[pyattr] + const CTYPES_MAX_ARGCOUNT: usize = 1024; #[pyattr] - pub const FUNCFLAG_STDCALL: u32 = 0x0; + const FUNCFLAG_STDCALL: u32 = 0x0; #[pyattr] - pub const FUNCFLAG_CDECL: u32 = 0x1; + const FUNCFLAG_CDECL: u32 = 0x1; #[pyattr] - pub const FUNCFLAG_HRESULT: u32 = 0x2; + const FUNCFLAG_HRESULT: u32 = 0x2; #[pyattr] - pub const FUNCFLAG_PYTHONAPI: u32 = 0x4; + const FUNCFLAG_PYTHONAPI: u32 = 0x4; #[pyattr] - pub const FUNCFLAG_USE_ERRNO: u32 = 0x8; + const FUNCFLAG_USE_ERRNO: u32 = 0x8; #[pyattr] - pub const FUNCFLAG_USE_LASTERROR: u32 = 0x10; + const FUNCFLAG_USE_LASTERROR: u32 = 0x10; #[pyattr] - pub const TYPEFLAG_ISPOINTER: u32 = 0x100; + const TYPEFLAG_ISPOINTER: u32 = 0x100; #[pyattr] - pub const TYPEFLAG_HASPOINTER: u32 = 0x200; + const TYPEFLAG_HASPOINTER: u32 = 0x200; #[pyattr] - pub const DICTFLAG_FINAL: u32 = 0x1000; + const DICTFLAG_FINAL: u32 = 0x1000; #[pyattr(name = "ArgumentError", once)] fn argument_error(vm: &VirtualMachine) -> PyTypeRef { @@ -130,369 +425,138 @@ pub(crate) mod _ctypes { ) } - #[pyattr(name = "FormatError", once)] - fn format_error(vm: &VirtualMachine) -> PyTypeRef { - vm.ctx.new_exception_type( - "_ctypes", - "FormatError", - Some(vec![vm.ctx.exceptions.exception_type.to_owned()]), - ) - } - - pub fn get_size(ty: &str) -> usize { - match ty { - "u" => mem::size_of::<WideChar>(), - "c" | "b" => mem::size_of::<c_schar>(), - "h" => mem::size_of::<c_short>(), - "H" => mem::size_of::<c_short>(), - "i" => mem::size_of::<c_int>(), - "I" => mem::size_of::<c_uint>(), - "l" => mem::size_of::<c_long>(), - "q" => mem::size_of::<c_longlong>(), - "L" => mem::size_of::<c_ulong>(), - "Q" => mem::size_of::<c_ulonglong>(), - "f" => mem::size_of::<c_float>(), - "d" | "g" => mem::size_of::<c_double>(), - "?" | "B" => mem::size_of::<c_uchar>(), - "P" | "z" | "Z" => mem::size_of::<usize>(), - "O" => mem::size_of::<PyObjectRef>(), - _ => unreachable!(), - } - } - - /// Get alignment for a simple type - for C types, alignment equals size - pub fn get_align(ty: &str) -> usize { - get_size(ty) - } - - /// Get the size of a ctypes type from its type object - #[allow(dead_code)] - pub fn get_size_from_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult<usize> { - // Try to get _type_ attribute for simple types - if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) - && let Ok(s) = type_attr.str(vm) - { - let s = s.to_string(); - if s.len() == 1 && SIMPLE_TYPE_CHARS.contains(s.as_str()) { - return Ok(get_size(&s)); - } + #[cfg(target_os = "windows")] + #[pyattr(name = "COMError", once)] + fn com_error(vm: &VirtualMachine) -> PyTypeRef { + use crate::builtins::type_::PyAttributes; + use crate::function::FuncArgs; + use crate::types::{PyTypeFlags, PyTypeSlots}; + + // Sets hresult, text, details as instance attributes in __init__ + // This function has InitFunc signature for direct slots.init use + fn comerror_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + let (hresult, text, details): ( + Option<PyObjectRef>, + Option<PyObjectRef>, + Option<PyObjectRef>, + ) = args.bind(vm)?; + let hresult = hresult.unwrap_or_else(|| vm.ctx.none()); + let text = text.unwrap_or_else(|| vm.ctx.none()); + let details = details.unwrap_or_else(|| vm.ctx.none()); + + // Set instance attributes + zelf.set_attr("hresult", hresult.clone(), vm)?; + zelf.set_attr("text", text.clone(), vm)?; + zelf.set_attr("details", details.clone(), vm)?; + + // self.args = args[1:] = (text, details) + // via: PyObject_SetAttrString(self, "args", PySequence_GetSlice(args, 1, size)) + let args_tuple: PyObjectRef = vm.ctx.new_tuple(vec![text, details]).into(); + zelf.set_attr("args", args_tuple, vm)?; + + Ok(()) } - // Fall back to sizeof - size_of(cls.clone().into(), vm) - } - /// Convert bytes to appropriate Python object based on ctypes type - pub fn bytes_to_pyobject( - cls: &PyTypeRef, - bytes: &[u8], - vm: &VirtualMachine, - ) -> PyResult<PyObjectRef> { - // Try to get _type_ attribute - if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) - && let Ok(s) = type_attr.str(vm) - { - let ty = s.to_string(); - return match ty.as_str() { - "c" => { - // c_char - single byte - Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) - } - "b" => { - // c_byte - signed char - let val = if !bytes.is_empty() { bytes[0] as i8 } else { 0 }; - Ok(vm.ctx.new_int(val).into()) - } - "B" => { - // c_ubyte - unsigned char - let val = if !bytes.is_empty() { bytes[0] } else { 0 }; - Ok(vm.ctx.new_int(val).into()) - } - "h" => { - // c_short - const SIZE: usize = mem::size_of::<c_short>(); - let val = if bytes.len() >= SIZE { - c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "H" => { - // c_ushort - const SIZE: usize = mem::size_of::<c_ushort>(); - let val = if bytes.len() >= SIZE { - c_ushort::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "i" => { - // c_int - const SIZE: usize = mem::size_of::<c_int>(); - let val = if bytes.len() >= SIZE { - c_int::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "I" => { - // c_uint - const SIZE: usize = mem::size_of::<c_uint>(); - let val = if bytes.len() >= SIZE { - c_uint::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "l" => { - // c_long - const SIZE: usize = mem::size_of::<c_long>(); - let val = if bytes.len() >= SIZE { - c_long::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "L" => { - // c_ulong - const SIZE: usize = mem::size_of::<c_ulong>(); - let val = if bytes.len() >= SIZE { - c_ulong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "q" => { - // c_longlong - const SIZE: usize = mem::size_of::<c_longlong>(); - let val = if bytes.len() >= SIZE { - c_longlong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "Q" => { - // c_ulonglong - const SIZE: usize = mem::size_of::<c_ulonglong>(); - let val = if bytes.len() >= SIZE { - c_ulonglong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "f" => { - // c_float - const SIZE: usize = mem::size_of::<c_float>(); - let val = if bytes.len() >= SIZE { - c_float::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0.0 - }; - Ok(vm.ctx.new_float(val as f64).into()) - } - "d" | "g" => { - // c_double - const SIZE: usize = mem::size_of::<c_double>(); - let val = if bytes.len() >= SIZE { - c_double::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) - } else { - 0.0 - }; - Ok(vm.ctx.new_float(val).into()) - } - "?" => { - // c_bool - let val = !bytes.is_empty() && bytes[0] != 0; - Ok(vm.ctx.new_bool(val).into()) - } - "P" | "z" | "Z" => { - // Pointer types - return as integer address - let val = if bytes.len() >= mem::size_of::<libc::uintptr_t>() { - const UINTPTR_LEN: usize = mem::size_of::<libc::uintptr_t>(); - let mut arr = [0u8; UINTPTR_LEN]; - arr[..bytes.len().min(UINTPTR_LEN)] - .copy_from_slice(&bytes[..bytes.len().min(UINTPTR_LEN)]); - usize::from_ne_bytes(arr) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "u" => { - // c_wchar - wide character - let val = if bytes.len() >= mem::size_of::<WideChar>() { - let wc = if mem::size_of::<WideChar>() == 2 { - u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 - } else { - u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) - }; - char::from_u32(wc).unwrap_or('\0') - } else { - '\0' - }; - Ok(vm.ctx.new_str(val.to_string()).into()) - } - _ => Ok(vm.ctx.none()), - }; - } - // Default: return bytes as-is - Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) - } + // Create exception type with IMMUTABLETYPE flag + let mut attrs = PyAttributes::default(); + attrs.insert( + vm.ctx.intern_str("__module__"), + vm.ctx.new_str("_ctypes").into(), + ); + attrs.insert( + vm.ctx.intern_str("__doc__"), + vm.ctx + .new_str("Raised when a COM method call failed.") + .into(), + ); + + // Create slots with IMMUTABLETYPE flag + let slots = PyTypeSlots { + name: "COMError", + flags: PyTypeFlags::heap_type_flags() + | PyTypeFlags::HAS_DICT + | PyTypeFlags::IMMUTABLETYPE, + ..PyTypeSlots::default() + }; - const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?O"; + let exc_type = PyType::new_heap( + "COMError", + vec![vm.ctx.exceptions.exception_type.to_owned()], + attrs, + slots, + vm.ctx.types.type_type.to_owned(), + &vm.ctx, + ) + .unwrap(); - pub fn new_simple_type( - cls: Either<&PyObject, &PyTypeRef>, - vm: &VirtualMachine, - ) -> PyResult<PyCSimple> { - let cls = match cls { - Either::A(obj) => obj, - Either::B(typ) => typ.as_object(), - }; + // Set our custom init after new_heap, which runs init_slots that would + // otherwise overwrite slots.init with init_wrapper (due to __init__ in MRO). + exc_type.slots.init.store(Some(comerror_init)); - if let Ok(_type_) = cls.get_attr("_type_", vm) { - if _type_.is_instance((&vm.ctx.types.str_type).as_ref(), vm)? { - let tp_str = _type_.str(vm)?.to_string(); - - if tp_str.len() != 1 { - Err(vm.new_value_error( - format!("class must define a '_type_' attribute which must be a string of length 1, str: {tp_str}"), - )) - } else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) { - Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\n a single character string containing one of {SIMPLE_TYPE_CHARS}, currently it is {tp_str}."))) - } else { - let size = get_size(&tp_str); - let cdata = CDataObject::from_bytes(vec![0u8; size], None); - Ok(PyCSimple { - _base: PyCData::new(cdata.clone()), - _type_: tp_str, - value: AtomicCell::new(vm.ctx.none()), - cdata: rustpython_common::lock::PyRwLock::new(cdata), - }) - } - } else { - Err(vm.new_type_error("class must define a '_type_' string attribute")) - } - } else { - Err(vm.new_attribute_error("class must define a '_type_' attribute")) - } + exc_type } /// Get the size of a ctypes type or instance - #[pyfunction(name = "sizeof")] - pub fn size_of(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { - use super::pointer::PyCPointer; - use super::structure::{PyCStructType, PyCStructure}; + #[pyfunction] + pub fn sizeof(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { + use super::structure::PyCStructType; use super::union::PyCUnionType; - use super::util::StgInfo; - use crate::builtins::PyType; - // 1. Check TypeDataSlot on class (for instances) - if let Some(stg_info) = obj.class().get_type_data::<StgInfo>() { - return Ok(stg_info.size); - } - - // 2. Check TypeDataSlot on type itself (for type objects) - if let Some(type_obj) = obj.downcast_ref::<PyType>() - && let Some(stg_info) = type_obj.get_type_data::<StgInfo>() - { - return Ok(stg_info.size); - } - - // 3. Instances with cdata buffer - if let Some(structure) = obj.downcast_ref::<PyCStructure>() { - return Ok(structure.cdata.read().size()); - } - if let Some(simple) = obj.downcast_ref::<PyCSimple>() { - return Ok(simple.cdata.read().size()); - } - if obj.fast_isinstance(PyCPointer::static_type()) { - return Ok(std::mem::size_of::<usize>()); - } - - // 3. Type objects - if let Ok(type_ref) = obj.clone().downcast::<crate::builtins::PyType>() { - // Structure types - check if metaclass is or inherits from PyCStructType - if type_ref + // 1. Check if obj is a TYPE object (not instance) - PyStgInfo_FromType + if let Some(type_obj) = obj.downcast_ref::<PyType>() { + // Type object - return StgInfo.size + if let Some(stg_info) = type_obj.stg_info_opt() { + return Ok(stg_info.size); + } + // Fallback for type objects without StgInfo + // Array types + if type_obj + .class() + .fast_issubclass(super::array::PyCArrayType::static_type()) + && let Ok(stg) = type_obj.stg_info(vm) + { + return Ok(stg.size); + } + // Structure types + if type_obj .class() .fast_issubclass(PyCStructType::static_type()) { - return calculate_struct_size(&type_ref, vm); + return super::structure::calculate_struct_size(type_obj, vm); } - // Union types - check if metaclass is or inherits from PyCUnionType - if type_ref + // Union types + if type_obj .class() .fast_issubclass(PyCUnionType::static_type()) { - return calculate_union_size(&type_ref, vm); + return super::union::calculate_union_size(type_obj, vm); } - // Simple types (c_int, c_char, etc.) - if type_ref.fast_issubclass(PyCSimple::static_type()) { - let instance = new_simple_type(Either::B(&type_ref), vm)?; - return Ok(get_size(&instance._type_)); + // Simple types + if type_obj.fast_issubclass(PyCSimple::static_type()) { + if let Ok(type_attr) = type_obj.as_object().get_attr("_type_", vm) + && let Ok(type_str) = type_attr.str(vm) + { + return Ok(super::get_size(type_str.as_ref())); + } + return Ok(std::mem::size_of::<usize>()); } // Pointer types - if type_ref.fast_issubclass(PyCPointer::static_type()) { + if type_obj.fast_issubclass(PyCPointer::static_type()) { return Ok(std::mem::size_of::<usize>()); } + return Err(vm.new_type_error("this type has no size")); } - Err(vm.new_type_error("this type has no size")) - } - - /// Calculate Structure type size from _fields_ (sum of field sizes) - fn calculate_struct_size( - cls: &crate::builtins::PyTypeRef, - vm: &VirtualMachine, - ) -> PyResult<usize> { - use crate::AsObject; - - if let Ok(fields_attr) = cls.as_object().get_attr("_fields_", vm) { - let fields: Vec<PyObjectRef> = fields_attr.try_to_value(vm).unwrap_or_default(); - let mut total_size = 0usize; - - for field in fields.iter() { - if let Some(tuple) = field.downcast_ref::<crate::builtins::PyTuple>() - && let Some(field_type) = tuple.get(1) - { - // Recursively calculate field type size - total_size += size_of(field_type.clone(), vm)?; - } - } - return Ok(total_size); + // 2. Instance object - return actual buffer size (b_size) + // CDataObject_Check + return obj->b_size + if let Some(cdata) = obj.downcast_ref::<PyCData>() { + return Ok(cdata.size()); } - Ok(0) - } - - /// Calculate Union type size from _fields_ (max field size) - fn calculate_union_size( - cls: &crate::builtins::PyTypeRef, - vm: &VirtualMachine, - ) -> PyResult<usize> { - use crate::AsObject; - - if let Ok(fields_attr) = cls.as_object().get_attr("_fields_", vm) { - let fields: Vec<PyObjectRef> = fields_attr.try_to_value(vm).unwrap_or_default(); - let mut max_size = 0usize; - - for field in fields.iter() { - if let Some(tuple) = field.downcast_ref::<crate::builtins::PyTuple>() - && let Some(field_type) = tuple.get(1) - { - let field_size = size_of(field_type.clone(), vm)?; - max_size = max_size.max(field_size); - } - } - return Ok(max_size); + if obj.fast_isinstance(PyCPointer::static_type()) { + return Ok(std::mem::size_of::<usize>()); } - Ok(0) + + Err(vm.new_type_error("this type has no size")) } #[cfg(windows)] @@ -513,7 +577,7 @@ pub(crate) mod _ctypes { #[cfg(not(windows))] #[pyfunction(name = "dlopen")] fn load_library_unix( - name: Option<String>, + name: Option<crate::function::FsPath>, _load_flags: OptionalArg<i32>, vm: &VirtualMachine, ) -> PyResult<usize> { @@ -523,9 +587,12 @@ pub(crate) mod _ctypes { Some(name) => { let cache = library::libcache(); let mut cache_write = cache.write(); - let (id, _) = cache_write - .get_or_insert_lib(&name, vm) - .map_err(|e| vm.new_os_error(e.to_string()))?; + let os_str = name.as_os_str(vm)?; + let (id, _) = cache_write.get_or_insert_lib(&*os_str, vm).map_err(|e| { + // Include filename in error message for better diagnostics + let name_str = os_str.to_string_lossy(); + vm.new_os_error(format!("{}: {}", name_str, e)) + })?; Ok(id) } None => { @@ -548,7 +615,9 @@ pub(crate) mod _ctypes { } #[pyfunction(name = "POINTER")] - pub fn create_pointer_type(cls: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn create_pointer_type(cls: PyObjectRef, vm: &VirtualMachine) -> PyResult { + use crate::builtins::PyStr; + // Get the _pointer_type_cache let ctypes_module = vm.import("_ctypes", 0)?; let cache = ctypes_module.get_attr("_pointer_type_cache", vm)?; @@ -563,33 +632,60 @@ pub(crate) mod _ctypes { // Get the _Pointer base class let pointer_base = ctypes_module.get_attr("_Pointer", vm)?; + // Create a new type that inherits from _Pointer + let pointer_base_type = pointer_base + .clone() + .downcast::<crate::builtins::PyType>() + .map_err(|_| vm.new_type_error("_Pointer must be a type"))?; + let metaclass = pointer_base_type.class().to_owned(); + + let bases = vm.ctx.new_tuple(vec![pointer_base]); + let dict = vm.ctx.new_dict(); + + // PyUnicode_CheckExact(cls) - string creates incomplete pointer type + if let Some(s) = cls.downcast_ref::<PyStr>() { + // Incomplete pointer type: _type_ not set, cache key is id(result) + let name = format!("LP_{}", s.as_str()); + + let new_type = metaclass + .as_object() + .call((vm.ctx.new_str(name), bases, dict), vm)?; + + // Store with id(result) as key for incomplete pointer types + let id_key: PyObjectRef = vm.ctx.new_int(new_type.get_id() as i64).into(); + vm.call_method(&cache, "__setitem__", (id_key, new_type.clone()))?; + + return Ok(new_type); + } + + // PyType_Check(cls) - type creates complete pointer type + if !cls.class().fast_issubclass(vm.ctx.types.type_type.as_ref()) { + return Err(vm.new_type_error("must be a ctypes type")); + } + // Create the name for the pointer type let name = if let Ok(type_obj) = cls.get_attr("__name__", vm) { format!("LP_{}", type_obj.str(vm)?) - } else if let Ok(s) = cls.str(vm) { - format!("LP_{}", s) } else { "LP_unknown".to_string() }; - // Create a new type that inherits from _Pointer - let type_type = &vm.ctx.types.type_type; - let bases = vm.ctx.new_tuple(vec![pointer_base]); - let dict = vm.ctx.new_dict(); + // Complete pointer type: set _type_ attribute dict.set_item("_type_", cls.clone(), vm)?; - let new_type = type_type + // Call the metaclass (PyCPointerType) to create the new type + let new_type = metaclass .as_object() .call((vm.ctx.new_str(name), bases, dict), vm)?; - // Store in cache using __setitem__ + // Store in cache with cls as key vm.call_method(&cache, "__setitem__", (cls, new_type.clone()))?; Ok(new_type) } - #[pyfunction(name = "pointer")] - pub fn create_pointer_inst(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pyfunction] + fn pointer(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { // Get the type of the object let obj_type = obj.class().to_owned(); @@ -607,7 +703,7 @@ pub(crate) mod _ctypes { #[cfg(target_os = "windows")] #[pyfunction(name = "_check_HRESULT")] - pub fn check_hresult(_self: PyObjectRef, hr: i32, _vm: &VirtualMachine) -> PyResult<i32> { + fn check_hresult(_self: PyObjectRef, hr: i32, _vm: &VirtualMachine) -> PyResult<i32> { // TODO: fixme if hr < 0 { // vm.ctx.new_windows_error(hr) @@ -619,18 +715,17 @@ pub(crate) mod _ctypes { #[pyfunction] fn addressof(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { - if obj.is_instance(PyCSimple::static_type().as_ref(), vm)? { - let simple = obj.downcast_ref::<PyCSimple>().unwrap(); - Ok(simple.value.as_ptr() as usize) + // All ctypes objects should return cdata buffer pointer + if let Some(cdata) = obj.downcast_ref::<PyCData>() { + Ok(cdata.buffer.read().as_ptr() as usize) } else { Err(vm.new_type_error("expected a ctypes instance")) } } #[pyfunction] - fn byref(obj: PyObjectRef, offset: OptionalArg<isize>, vm: &VirtualMachine) -> PyResult { - use super::base::PyCData; - use crate::class::StaticType; + pub fn byref(obj: PyObjectRef, offset: OptionalArg<isize>, vm: &VirtualMachine) -> PyResult { + use super::FfiArgValue; // Check if obj is a ctypes instance if !obj.fast_isinstance(PyCData::static_type()) @@ -644,9 +739,23 @@ pub(crate) mod _ctypes { let offset_val = offset.unwrap_or(0); + // Get buffer address: (char *)((CDataObject *)obj)->b_ptr + offset + let ptr_val = if let Some(simple) = obj.downcast_ref::<PyCSimple>() { + let buffer = simple.0.buffer.read(); + (buffer.as_ptr() as isize + offset_val) as usize + } else if let Some(cdata) = obj.downcast_ref::<PyCData>() { + let buffer = cdata.buffer.read(); + (buffer.as_ptr() as isize + offset_val) as usize + } else { + 0 + }; + // Create CArgObject to hold the reference Ok(CArgObject { + tag: b'P', + value: FfiArgValue::Pointer(ptr_val), obj, + size: 0, offset: offset_val, } .to_pyobject(vm)) @@ -654,11 +763,6 @@ pub(crate) mod _ctypes { #[pyfunction] fn alignment(tp: Either<PyTypeRef, PyObjectRef>, vm: &VirtualMachine) -> PyResult<usize> { - use super::base::PyCSimpleType; - use super::pointer::PyCPointer; - use super::structure::PyCStructure; - use super::union::PyCUnion; - use super::util::StgInfo; use crate::builtins::PyType; let obj = match &tp { @@ -667,23 +771,27 @@ pub(crate) mod _ctypes { }; // 1. Check TypeDataSlot on class (for instances) - if let Some(stg_info) = obj.class().get_type_data::<StgInfo>() { + if let Some(stg_info) = obj.class().stg_info_opt() { return Ok(stg_info.align); } // 2. Check TypeDataSlot on type itself (for type objects) if let Some(type_obj) = obj.downcast_ref::<PyType>() - && let Some(stg_info) = type_obj.get_type_data::<StgInfo>() + && let Some(stg_info) = type_obj.stg_info_opt() { return Ok(stg_info.align); } - // 3. Fallback for simple types without TypeDataSlot - if obj.fast_isinstance(PyCSimple::static_type()) { - // Get stg_info from the type by reading _type_ attribute - let cls = obj.class().to_owned(); - let stg_info = PyCSimpleType::get_stg_info(&cls, vm); - return Ok(stg_info.align); + // 3. Fallback for simple types + if obj.fast_isinstance(PyCSimple::static_type()) + && let Ok(stg) = obj.class().stg_info(vm) + { + return Ok(stg.align); + } + if obj.fast_isinstance(PyCArray::static_type()) + && let Ok(stg) = obj.class().stg_info(vm) + { + return Ok(stg.align); } if obj.fast_isinstance(PyCStructure::static_type()) { // Calculate alignment from _fields_ @@ -715,8 +823,8 @@ pub(crate) mod _ctypes { // Simple type: _type_ is a single character string if let Ok(s) = type_attr.str(vm) { let ty = s.to_string(); - if ty.len() == 1 && SIMPLE_TYPE_CHARS.contains(ty.as_str()) { - return Ok(get_align(&ty)); + if ty.len() == 1 && super::simple::SIMPLE_TYPE_CHARS.contains(ty.as_str()) { + return Ok(super::get_align(&ty)); } } } @@ -754,9 +862,45 @@ pub(crate) mod _ctypes { } #[pyfunction] - fn resize(_args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - // TODO: RUSTPYTHON - Err(vm.new_value_error("not implemented")) + fn resize(obj: PyObjectRef, size: isize, vm: &VirtualMachine) -> PyResult<()> { + use std::borrow::Cow; + + // 1. Get StgInfo from object's class (validates ctypes instance) + let stg_info = obj + .class() + .stg_info_opt() + .ok_or_else(|| vm.new_type_error("expected ctypes instance"))?; + + // 2. Validate size + if size < 0 || (size as usize) < stg_info.size { + return Err(vm.new_value_error(format!("minimum size is {}", stg_info.size))); + } + + // 3. Get PyCData via upcast (works for all ctypes types due to repr(transparent)) + let cdata = obj + .downcast_ref::<PyCData>() + .ok_or_else(|| vm.new_type_error("expected ctypes instance"))?; + + // 4. Check if buffer is owned (not borrowed from external memory) + { + let buffer = cdata.buffer.read(); + if matches!(&*buffer, Cow::Borrowed(_)) { + return Err(vm.new_value_error( + "Memory cannot be resized because this object doesn't own it".to_owned(), + )); + } + } + + // 5. Resize the buffer + let new_size = size as usize; + let mut buffer = cdata.buffer.write(); + let old_data = buffer.to_vec(); + let mut new_data = vec![0u8; new_size]; + let copy_len = old_data.len().min(new_size); + new_data[..copy_len].copy_from_slice(&old_data[..copy_len]); + *buffer = Cow::Owned(new_data); + + Ok(()) } #[pyfunction] @@ -796,77 +940,306 @@ pub(crate) mod _ctypes { #[pyattr] fn _string_at_addr(_vm: &VirtualMachine) -> usize { - let f = libc::strnlen; - f as usize + super::function::INTERNAL_STRING_AT_ADDR } #[pyattr] fn _wstring_at_addr(_vm: &VirtualMachine) -> usize { - // Return address of wcsnlen or similar wide string function - #[cfg(not(target_os = "windows"))] - { - let f = libc::wcslen; - f as usize - } - #[cfg(target_os = "windows")] - { - // FIXME: On Windows, use wcslen from ucrt - 0 - } + super::function::INTERNAL_WSTRING_AT_ADDR } #[pyattr] fn _cast_addr(_vm: &VirtualMachine) -> usize { - // todo!("Implement _cast_addr") - 0 + super::function::INTERNAL_CAST_ADDR } - #[pyfunction(name = "_cast")] - pub fn pycfunction_cast( + #[pyfunction] + fn _cast( obj: PyObjectRef, - _obj2: PyObjectRef, + src: PyObjectRef, ctype: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - use super::array::PyCArray; - use super::base::PyCData; - use super::pointer::PyCPointer; - use crate::class::StaticType; + super::function::cast_impl(obj, src, ctype, vm) + } + + /// Python-level cast function (PYFUNCTYPE wrapper) + #[pyfunction] + fn cast(obj: PyObjectRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult { + super::function::cast_impl(obj.clone(), obj, typ, vm) + } + + /// Return buffer interface information for a ctypes type or object. + /// Returns a tuple (format, ndim, shape) where: + /// - format: PEP 3118 format string + /// - ndim: number of dimensions + /// - shape: tuple of dimension sizes + #[pyfunction] + fn buffer_info(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // Determine if obj is a type or an instance + let is_type = obj.class().fast_issubclass(vm.ctx.types.type_type.as_ref()); + let cls = if is_type { + obj.clone() + } else { + obj.class().to_owned().into() + }; + + // Get format from type - try _type_ first (for simple types), then _stg_info_format_ + let format = if let Ok(type_attr) = cls.get_attr("_type_", vm) { + type_attr.str(vm)?.to_string() + } else if let Ok(format_attr) = cls.get_attr("_stg_info_format_", vm) { + format_attr.str(vm)?.to_string() + } else { + return Err(vm.new_type_error("not a ctypes type or object")); + }; + + // Non-array types have ndim=0 and empty shape + // TODO: Implement ndim/shape for arrays when StgInfo supports it + let ndim = 0; + let shape: Vec<PyObjectRef> = vec![]; + + let shape_tuple = vm.ctx.new_tuple(shape); + Ok(vm + .ctx + .new_tuple(vec![ + vm.ctx.new_str(format).into(), + vm.ctx.new_int(ndim).into(), + shape_tuple.into(), + ]) + .into()) + } + + /// Unpickle a ctypes object. + #[pyfunction] + fn _unpickle(typ: PyObjectRef, state: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if !state.class().is(vm.ctx.types.tuple_type.as_ref()) { + return Err(vm.new_type_error("state must be a tuple")); + } + let obj = vm.call_method(&typ, "__new__", (typ.clone(),))?; + vm.call_method(&obj, "__setstate__", (state,))?; + Ok(obj) + } + + /// Call a function at the given address with the given arguments. + #[pyfunction] + fn call_function( + func_addr: usize, + args: crate::builtins::PyTupleRef, + vm: &VirtualMachine, + ) -> PyResult { + call_function_internal(func_addr, args, 0, vm) + } + + /// Call a cdecl function at the given address with the given arguments. + #[pyfunction] + fn call_cdeclfunction( + func_addr: usize, + args: crate::builtins::PyTupleRef, + vm: &VirtualMachine, + ) -> PyResult { + call_function_internal(func_addr, args, FUNCFLAG_CDECL, vm) + } - // Python signature: _cast(obj, obj, ctype) - // Python passes the same object twice (obj and _obj2 are the same) - // We ignore _obj2 as it's redundant + fn call_function_internal( + func_addr: usize, + args: crate::builtins::PyTupleRef, + _flags: u32, + vm: &VirtualMachine, + ) -> PyResult { + use libffi::middle::{Arg, Cif, CodePtr, Type}; - // Check if this is a pointer type (has _type_ attribute) - if ctype.get_attr("_type_", vm).is_err() { - return Err(vm.new_type_error("cast() argument 2 must be a pointer type".to_string())); + if func_addr == 0 { + return Err(vm.new_value_error("NULL function pointer")); } - // Create an instance of the target pointer type with no arguments - let result = ctype.call((), vm)?; + let mut ffi_args: Vec<Arg> = Vec::with_capacity(args.len()); + let mut arg_values: Vec<isize> = Vec::with_capacity(args.len()); + let mut arg_types: Vec<Type> = Vec::with_capacity(args.len()); + + for arg in args.iter() { + if vm.is_none(arg) { + arg_values.push(0); + arg_types.push(Type::pointer()); + } else if let Ok(int_val) = arg.try_int(vm) { + let val = int_val.as_bigint().to_i64().unwrap_or(0) as isize; + arg_values.push(val); + arg_types.push(Type::isize()); + } else if let Some(bytes) = arg.downcast_ref::<crate::builtins::PyBytes>() { + let ptr = bytes.as_bytes().as_ptr() as isize; + arg_values.push(ptr); + arg_types.push(Type::pointer()); + } else if let Some(s) = arg.downcast_ref::<crate::builtins::PyStr>() { + let ptr = s.as_str().as_ptr() as isize; + arg_values.push(ptr); + arg_types.push(Type::pointer()); + } else { + return Err(vm.new_type_error(format!( + "Don't know how to convert parameter of type '{}'", + arg.class().name() + ))); + } + } - // Get the pointer value from the source object - // If obj is a CData instance (including arrays), use the object itself - // If obj is an integer, use it directly as the pointer value - let ptr_value: PyObjectRef = if obj.fast_isinstance(PyCData::static_type()) - || obj.fast_isinstance(PyCArray::static_type()) - || obj.fast_isinstance(PyCPointer::static_type()) - { - // For CData objects (including arrays and pointers), store the object itself - obj.clone() - } else if let Ok(int_val) = obj.try_int(vm) { - // For integers, treat as pointer address - vm.ctx.new_int(int_val.as_bigint().clone()).into() - } else { - return Err(vm.new_type_error(format!( - "cast() argument 1 must be a ctypes instance or an integer, not {}", - obj.class().name() - ))); + for val in &arg_values { + ffi_args.push(Arg::new(val)); + } + + let cif = Cif::new(arg_types, Type::isize()); + let code_ptr = CodePtr::from_ptr(func_addr as *const _); + let result: isize = unsafe { cif.call(code_ptr, &ffi_args) }; + Ok(vm.ctx.new_int(result).into()) + } + + /// Convert a pointer (as integer) to a Python object. + #[pyfunction(name = "PyObj_FromPtr")] + fn py_obj_from_ptr(ptr: usize, vm: &VirtualMachine) -> PyResult { + if ptr == 0 { + return Err(vm.new_value_error("NULL pointer access")); + } + let raw_ptr = ptr as *mut crate::object::PyObject; + unsafe { + let obj = crate::PyObjectRef::from_raw(std::ptr::NonNull::new_unchecked(raw_ptr)); + let obj = std::mem::ManuallyDrop::new(obj); + Ok((*obj).clone()) + } + } + + #[pyfunction(name = "Py_INCREF")] + fn py_incref(obj: PyObjectRef, _vm: &VirtualMachine) -> PyObjectRef { + // TODO: + obj + } + + #[pyfunction(name = "Py_DECREF")] + fn py_decref(obj: PyObjectRef, _vm: &VirtualMachine) -> PyObjectRef { + // TODO: + obj + } + + #[cfg(target_os = "macos")] + #[pyfunction] + fn _dyld_shared_cache_contains_path( + path: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<bool> { + use std::ffi::CString; + + let path = match path { + Some(p) if !vm.is_none(&p) => p, + _ => return Ok(false), }; - // Set the contents of the pointer by setting the attribute - result.set_attr("contents", ptr_value, vm)?; + let path_str = path.str(vm)?.to_string(); + let c_path = + CString::new(path_str).map_err(|_| vm.new_value_error("path contains null byte"))?; + unsafe extern "C" { + fn _dyld_shared_cache_contains_path(path: *const libc::c_char) -> bool; + } + + let result = unsafe { _dyld_shared_cache_contains_path(c_path.as_ptr()) }; Ok(result) } + + #[cfg(windows)] + #[pyfunction(name = "FormatError")] + fn format_error_func(code: OptionalArg<u32>, _vm: &VirtualMachine) -> PyResult<String> { + use windows_sys::Win32::Foundation::{GetLastError, LocalFree}; + use windows_sys::Win32::System::Diagnostics::Debug::{ + FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, + FORMAT_MESSAGE_IGNORE_INSERTS, FormatMessageW, + }; + + let error_code = code.unwrap_or_else(|| unsafe { GetLastError() }); + + let mut buffer: *mut u16 = std::ptr::null_mut(); + let len = unsafe { + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + std::ptr::null(), + error_code, + 0, + &mut buffer as *mut *mut u16 as *mut u16, + 0, + std::ptr::null(), + ) + }; + + if len == 0 || buffer.is_null() { + return Ok("<no description>".to_string()); + } + + let message = unsafe { + let slice = std::slice::from_raw_parts(buffer, len as usize); + let msg = String::from_utf16_lossy(slice).trim_end().to_string(); + LocalFree(buffer as *mut _); + msg + }; + + Ok(message) + } + + #[cfg(windows)] + #[pyfunction(name = "CopyComPointer")] + fn copy_com_pointer(src: PyObjectRef, dst: PyObjectRef, vm: &VirtualMachine) -> PyResult<i32> { + use windows_sys::Win32::Foundation::{E_POINTER, S_OK}; + + // 1. Extract pointer-to-pointer address from dst (byref() result) + let pdst: usize = if let Some(carg) = dst.downcast_ref::<CArgObject>() { + // byref() result: object buffer address + offset + let base = if let Some(cdata) = carg.obj.downcast_ref::<PyCData>() { + cdata.buffer.read().as_ptr() as usize + } else { + return Ok(E_POINTER); + }; + (base as isize + carg.offset) as usize + } else { + return Ok(E_POINTER); + }; + + if pdst == 0 { + return Ok(E_POINTER); + } + + // 2. Extract COM pointer value from src + let src_ptr: usize = if vm.is_none(&src) { + 0 + } else if let Some(cdata) = src.downcast_ref::<PyCData>() { + // c_void_p etc: read pointer value from buffer + let buffer = cdata.buffer.read(); + if buffer.len() >= std::mem::size_of::<usize>() { + usize::from_ne_bytes( + buffer[..std::mem::size_of::<usize>()] + .try_into() + .unwrap_or([0; std::mem::size_of::<usize>()]), + ) + } else { + 0 + } + } else { + return Ok(E_POINTER); + }; + + // 3. Call IUnknown::AddRef if src is non-NULL + if src_ptr != 0 { + unsafe { + // IUnknown vtable: [QueryInterface, AddRef, Release, ...] + let iunknown = src_ptr as *mut *const usize; + let vtable = *iunknown; + debug_assert!(!vtable.is_null(), "IUnknown vtable is null"); + let addref_fn: extern "system" fn(*mut std::ffi::c_void) -> u32 = + std::mem::transmute(*vtable.add(1)); // AddRef is index 1 + addref_fn(src_ptr as *mut std::ffi::c_void); + } + } + + // 4. Copy pointer: *pdst = src + unsafe { + *(pdst as *mut usize) = src_ptr; + } + + Ok(S_OK) + } } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index fe12a781d9f..60e6516bfe0 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -1,41 +1,109 @@ -use crate::atomic_func; -use crate::builtins::{PyBytes, PyInt}; -use crate::class::StaticType; -use crate::function::FuncArgs; -use crate::protocol::{ - BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods, PySequenceMethods, -}; -use crate::stdlib::ctypes::base::CDataObject; -use crate::stdlib::ctypes::util::StgInfo; -use crate::types::{AsBuffer, AsNumber, AsSequence}; -use crate::{AsObject, Py, PyObjectRef, PyPayload}; +use super::StgInfo; +use super::base::{CDATA_BUFFER_METHODS, PyCData}; use crate::{ - PyResult, VirtualMachine, - builtins::{PyType, PyTypeRef}, - types::Constructor, + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, + atomic_func, + builtins::{PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef}, + class::StaticType, + function::{ArgBytesLike, FuncArgs, PySetterValue}, + protocol::{BufferDescriptor, PyBuffer, PyNumberMethods, PySequenceMethods}, + types::{AsBuffer, AsNumber, AsSequence, Constructor, Initializer}, }; -use crossbeam_utils::atomic::AtomicCell; -use num_traits::ToPrimitive; -use rustpython_common::lock::PyRwLock; -use rustpython_vm::stdlib::ctypes::_ctypes::get_size; -use rustpython_vm::stdlib::ctypes::base::PyCData; +use num_traits::{Signed, ToPrimitive}; + +/// Creates array type for (element_type, length) +/// Uses _array_type_cache to ensure identical calls return the same type object +pub(super) fn array_type_from_ctype( + itemtype: PyObjectRef, + length: usize, + vm: &VirtualMachine, +) -> PyResult { + // PyCArrayType_from_ctype + + // Get the _array_type_cache from _ctypes module + let ctypes_module = vm.import("_ctypes", 0)?; + let cache = ctypes_module.get_attr("_array_type_cache", vm)?; + + // Create cache key: (itemtype, length) tuple + let length_obj: PyObjectRef = vm.ctx.new_int(length).into(); + let cache_key = vm.ctx.new_tuple(vec![itemtype.clone(), length_obj]); + + // Check if already in cache + if let Ok(cached) = vm.call_method(&cache, "__getitem__", (cache_key.clone(),)) + && !vm.is_none(&cached) + { + return Ok(cached); + } -/// PyCArrayType - metatype for Array types -/// CPython stores array info (type, length) in StgInfo via type_data -#[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")] -#[derive(Debug)] -#[repr(transparent)] -pub struct PyCArrayType(PyType); + // Cache miss - create new array type + let itemtype_ref = itemtype + .clone() + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("Expected a type object"))?; + + let item_stg = itemtype_ref + .stg_info_opt() + .ok_or_else(|| vm.new_type_error("_type_ must have storage info"))?; + + let element_size = item_stg.size; + let element_align = item_stg.align; + let item_format = item_stg.format.clone(); + let item_shape = item_stg.shape.clone(); + let item_flags = item_stg.flags; + + // Check overflow before multiplication + let total_size = element_size + .checked_mul(length) + .ok_or_else(|| vm.new_overflow_error("array too large"))?; + + // format name: "c_int_Array_5" + let type_name = format!("{}_Array_{}", itemtype_ref.name(), length); + + // Get item type code before moving itemtype + let item_type_code = itemtype_ref + .as_object() + .get_attr("_type_", vm) + .ok() + .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())); + + let stg_info = StgInfo::new_array( + total_size, + element_align, + length, + itemtype_ref.clone(), + element_size, + item_format.as_deref(), + &item_shape, + item_flags, + ); -/// Create a new Array type with StgInfo stored in type_data (CPython style) -pub fn create_array_type_with_stg_info(stg_info: StgInfo, vm: &VirtualMachine) -> PyResult { - // Get PyCArrayType as metaclass - let metaclass = PyCArrayType::static_type().to_owned(); + let new_type = create_array_type_with_name(stg_info, &type_name, vm)?; + + // Special case for character arrays - add value/raw attributes + let new_type_ref: PyTypeRef = new_type + .clone() + .downcast() + .map_err(|_| vm.new_type_error("expected type"))?; + + match item_type_code.as_deref() { + Some("c") => add_char_array_getsets(&new_type_ref, vm), + Some("u") => add_wchar_array_getsets(&new_type_ref, vm), + _ => {} + } + + // Store in cache + vm.call_method(&cache, "__setitem__", (cache_key, new_type.clone()))?; - // Create a unique name for the array type - let type_name = format!("Array_{}", stg_info.length); + Ok(new_type) +} - // Create args for type(): (name, bases, dict) +/// create_array_type_with_name - create array type with specified name +fn create_array_type_with_name( + stg_info: StgInfo, + type_name: &str, + vm: &VirtualMachine, +) -> PyResult { + let metaclass = PyCArrayType::static_type().to_owned(); let name = vm.ctx.new_str(type_name); let bases = vm .ctx @@ -47,170 +115,205 @@ pub fn create_array_type_with_stg_info(stg_info: StgInfo, vm: &VirtualMachine) - crate::function::KwArgs::default(), ); - // Create the new type using PyType::slot_new with PyCArrayType as metaclass let new_type = crate::builtins::type_::PyType::slot_new(metaclass, args, vm)?; - // Set StgInfo in type_data let type_ref: PyTypeRef = new_type .clone() .downcast() - .map_err(|_| vm.new_type_error("Failed to create array type".to_owned()))?; + .map_err(|_| vm.new_type_error("Failed to create array type"))?; - if type_ref.init_type_data(stg_info.clone()).is_err() { - // Type data already initialized - update it - if let Some(mut existing) = type_ref.get_type_data_mut::<StgInfo>() { - *existing = stg_info; - } + // Set class attributes for _type_ and _length_ + if let Some(element_type) = stg_info.element_type.clone() { + new_type.set_attr("_type_", element_type, vm)?; } + new_type.set_attr("_length_", vm.ctx.new_int(stg_info.length), vm)?; + + super::base::set_or_init_stginfo(&type_ref, stg_info); Ok(new_type) } -impl Constructor for PyCArrayType { - type Args = PyObjectRef; +/// PyCArrayType - metatype for Array types +#[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")] +#[derive(Debug)] +#[repr(transparent)] +pub(super) struct PyCArrayType(PyType); - fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unimplemented!("use slot_new") - } -} +// PyCArrayType implements Initializer for slots.init (PyCArrayType_init) +impl Initializer for PyCArrayType { + type Args = FuncArgs; -#[pyclass(flags(IMMUTABLETYPE), with(Constructor, AsNumber))] -impl PyCArrayType { - #[pygetset(name = "_type_")] - fn typ(zelf: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - zelf.downcast_ref::<PyType>() - .and_then(|t| t.get_type_data::<StgInfo>()) - .and_then(|stg| stg.element_type.clone()) - .unwrap_or_else(|| vm.ctx.none()) - } + fn init(zelf: PyRef<Self>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + // zelf is the newly created array type (e.g., T in "class T(Array)") + let new_type: &PyType = &zelf.0; + + new_type.check_not_initialized(vm)?; + + // 1. Get _length_ from class dict first + let direct_length = new_type + .attributes + .read() + .get(vm.ctx.intern_str("_length_")) + .cloned(); + + // 2. Get _type_ from class dict first + let direct_type = new_type + .attributes + .read() + .get(vm.ctx.intern_str("_type_")) + .cloned(); + + // 3. Find parent StgInfo from MRO (for inheritance) + // Note: PyType.mro does NOT include self, so no skip needed + let parent_stg_info = new_type + .mro + .read() + .iter() + .find_map(|base| base.stg_info_opt().map(|s| s.clone())); + + // 4. Resolve _length_ (direct or inherited) + let length = if let Some(length_attr) = direct_length { + // Direct _length_ defined - validate it (PyLong_Check) + let length_int = length_attr + .downcast_ref::<PyInt>() + .ok_or_else(|| vm.new_type_error("The '_length_' attribute must be an integer"))?; + let bigint = length_int.as_bigint(); + // Check sign first - negative values are ValueError + if bigint.is_negative() { + return Err(vm.new_value_error("The '_length_' attribute must not be negative")); + } + // Positive values that don't fit in usize are OverflowError + bigint + .to_usize() + .ok_or_else(|| vm.new_overflow_error("The '_length_' attribute is too large"))? + } else if let Some(ref parent_info) = parent_stg_info { + // Inherit from parent + parent_info.length + } else { + return Err(vm.new_attribute_error("class must define a '_length_' attribute")); + }; - #[pygetset(name = "_length_")] - fn length(zelf: PyObjectRef) -> usize { - zelf.downcast_ref::<PyType>() - .and_then(|t| t.get_type_data::<StgInfo>()) - .map(|stg| stg.length) - .unwrap_or(0) - } + // 5. Resolve _type_ and get item_info (direct or inherited) + let (element_type, item_size, item_align, item_format, item_shape, item_flags) = + if let Some(type_attr) = direct_type { + // Direct _type_ defined - validate it (PyStgInfo_FromType) + let type_ref = type_attr + .clone() + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("_type_ must be a type"))?; + let (size, align, format, shape, flags) = { + let item_info = type_ref + .stg_info_opt() + .ok_or_else(|| vm.new_type_error("_type_ must have storage info"))?; + ( + item_info.size, + item_info.align, + item_info.format.clone(), + item_info.shape.clone(), + item_info.flags, + ) + }; + (type_ref, size, align, format, shape, flags) + } else if let Some(ref parent_info) = parent_stg_info { + // Inherit from parent + let parent_type = parent_info + .element_type + .clone() + .ok_or_else(|| vm.new_type_error("_type_ must have storage info"))?; + ( + parent_type, + parent_info.element_size, + parent_info.align, + parent_info.format.clone(), + parent_info.shape.clone(), + parent_info.flags, + ) + } else { + return Err(vm.new_attribute_error("class must define a '_type_' attribute")); + }; - #[pymethod] - fn __mul__(zelf: PyObjectRef, n: isize, vm: &VirtualMachine) -> PyResult { - if n < 0 { - return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); + // 6. Check overflow (item_size != 0 && length > MAX / item_size) + if item_size != 0 && length > usize::MAX / item_size { + return Err(vm.new_overflow_error("array too large")); } - // Get inner array info from TypeDataSlot - let type_ref = zelf.downcast_ref::<PyType>().unwrap(); - let (_inner_length, inner_size) = type_ref - .get_type_data::<StgInfo>() - .map(|stg| (stg.length, stg.size)) - .unwrap_or((0, 0)); - - // The element type of the new array is the current array type itself - let current_array_type: PyObjectRef = zelf.clone(); - - // Element size is the total size of the inner array - let new_element_size = inner_size; - let total_size = new_element_size * (n as usize); - + // 7. Initialize StgInfo (PyStgInfo_Init + field assignment) let stg_info = StgInfo::new_array( - total_size, - new_element_size, - n as usize, - current_array_type, - new_element_size, + item_size * length, // size = item_size * length + item_align, // align = item_info->align + length, // length + element_type.clone(), + item_size, // element_size + item_format.as_deref(), + &item_shape, + item_flags, ); - create_array_type_with_stg_info(stg_info, vm) - } + // 8. Store StgInfo in type_data + super::base::set_or_init_stginfo(new_type, stg_info); - #[pyclassmethod] - fn in_dll( - zelf: PyObjectRef, - dll: PyObjectRef, - name: crate::builtins::PyStrRef, - vm: &VirtualMachine, - ) -> PyResult { - use libloading::Symbol; + // 9. Get type code before moving element_type + let item_type_code = element_type + .as_object() + .get_attr("_type_", vm) + .ok() + .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())); + + // 10. Set class attributes for _type_ and _length_ + zelf.as_object().set_attr("_type_", element_type, vm)?; + zelf.as_object() + .set_attr("_length_", vm.ctx.new_int(length), vm)?; + + // 11. Special case for character arrays - add value/raw attributes + // if (iteminfo->getfunc == _ctypes_get_fielddesc("c")->getfunc) + // add_getset((PyTypeObject*)self, CharArray_getsets); + // else if (iteminfo->getfunc == _ctypes_get_fielddesc("u")->getfunc) + // add_getset((PyTypeObject*)self, WCharArray_getsets); + + // Get type ref for add_getset + let type_ref: PyTypeRef = zelf.as_object().to_owned().downcast().unwrap(); + match item_type_code.as_deref() { + Some("c") => add_char_array_getsets(&type_ref, vm), + Some("u") => add_wchar_array_getsets(&type_ref, vm), + _ => {} + } - // Get the library handle from dll object - let handle = if let Ok(int_handle) = dll.try_int(vm) { - // dll is an integer handle - int_handle - .as_bigint() - .to_usize() - .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? - } else { - // dll is a CDLL/PyDLL/WinDLL object with _handle attribute - dll.get_attr("_handle", vm)? - .try_int(vm)? - .as_bigint() - .to_usize() - .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? - }; + Ok(()) + } +} - // Get the library from cache - let library_cache = crate::stdlib::ctypes::library::libcache().read(); - let library = library_cache - .get_lib(handle) - .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; - - // Get symbol address from library - let symbol_name = format!("{}\0", name.as_str()); - let inner_lib = library.lib.lock(); - - let symbol_address = if let Some(lib) = &*inner_lib { - unsafe { - // Try to get the symbol from the library - let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { - vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) - })?; - *symbol as usize - } - } else { - return Err(vm.new_attribute_error("Library is closed".to_owned())); - }; +#[pyclass(flags(IMMUTABLETYPE), with(Initializer, AsNumber))] +impl PyCArrayType { + #[pymethod] + fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // zelf is the array type class that from_param was called on + let cls = zelf + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("from_param: expected a type"))?; + + // 1. If already an instance of the requested type, return it + if value.is_instance(cls.as_object(), vm)? { + return Ok(value); + } - // Get size from the array type via TypeDataSlot - let type_ref = zelf.downcast_ref::<PyType>().unwrap(); - let (element_type, length, element_size) = type_ref - .get_type_data::<StgInfo>() - .map(|stg| { - ( - stg.element_type.clone().unwrap_or_else(|| vm.ctx.none()), - stg.length, - stg.element_size, - ) - }) - .unwrap_or_else(|| (vm.ctx.none(), 0, 0)); - let total_size = element_size * length; - - // Read data from symbol address - let data = if symbol_address != 0 && total_size > 0 { - unsafe { - let ptr = symbol_address as *const u8; - std::slice::from_raw_parts(ptr, total_size).to_vec() + // 2. Check for CArgObject (PyCArg_CheckExact) + if let Some(carg) = value.downcast_ref::<super::_ctypes::CArgObject>() { + // Check if the wrapped object is an instance of the requested type + if carg.obj.is_instance(cls.as_object(), vm)? { + return Ok(value); // Return the CArgObject as-is } - } else { - vec![0; total_size] - }; - - // Create instance - let cdata = CDataObject::from_bytes(data, None); - let instance = PyCArray { - _base: PyCData::new(cdata.clone()), - typ: PyRwLock::new(element_type), - length: AtomicCell::new(length), - element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(cdata), } - .into_pyobject(vm); - // Store base reference to keep dll alive - if let Ok(array_ref) = instance.clone().downcast::<PyCArray>() { - array_ref.cdata.write().base = Some(dll); + // 3. Check for _as_parameter_ attribute + if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) { + return PyCArrayType::from_param(cls.as_object().to_owned(), as_parameter, vm); } - Ok(instance) + Err(vm.new_type_error(format!( + "expected {} instance instead of {}", + cls.name(), + value.class().name() + ))) } } @@ -223,8 +326,28 @@ impl AsNumber for PyCArrayType { .try_index(vm)? .as_bigint() .to_isize() - .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; - PyCArrayType::__mul__(a.to_owned(), n, vm) + .ok_or_else(|| vm.new_overflow_error("array size too large"))?; + + if n < 0 { + return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); + } + + // Check for overflow before creating the new array type + let zelf_type = a + .downcast_ref::<PyType>() + .ok_or_else(|| vm.new_type_error("Expected type"))?; + + if let Some(stg_info) = zelf_type.stg_info_opt() { + let current_size = stg_info.size; + // Check if current_size * n would overflow + if current_size != 0 && (n as usize) > isize::MAX as usize / current_size { + return Err(vm.new_overflow_error("array too large")); + } + } + + // Use cached array type creation + // The element type of the new array is the current array type itself + array_type_from_ctype(a.to_owned(), n as usize, vm) }), ..PyNumberMethods::NOT_IMPLEMENTED }; @@ -232,27 +355,28 @@ impl AsNumber for PyCArrayType { } } +/// PyCArray - Array instance +/// All array metadata (element_type, length, element_size) is stored in the type's StgInfo #[pyclass( name = "Array", base = PyCData, metaclass = "PyCArrayType", module = "_ctypes" )] -pub struct PyCArray { - _base: PyCData, - /// Element type - can be a simple type (c_int) or an array type (c_int * 5) - pub(super) typ: PyRwLock<PyObjectRef>, - pub(super) length: AtomicCell<usize>, - pub(super) element_size: AtomicCell<usize>, - pub(super) cdata: PyRwLock<CDataObject>, -} +#[derive(Debug)] +#[repr(transparent)] +pub struct PyCArray(pub PyCData); -impl std::fmt::Debug for PyCArray { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PyCArray") - .field("typ", &self.typ) - .field("length", &self.length) - .finish() +impl PyCArray { + /// Get the type code of array element type (e.g., "c" for c_char, "u" for c_wchar) + fn get_element_type_code(zelf: &Py<Self>, vm: &VirtualMachine) -> Option<String> { + zelf.class() + .stg_info_opt() + .and_then(|info| info.element_type.clone())? + .as_object() + .get_attr("_type_", vm) + .ok() + .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())) } } @@ -260,60 +384,29 @@ impl Constructor for PyCArray { type Args = FuncArgs; fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // Get _type_ and _length_ from the class - let type_attr = cls.as_object().get_attr("_type_", vm).ok(); - let length_attr = cls.as_object().get_attr("_length_", vm).ok(); - - let element_type = type_attr.unwrap_or_else(|| vm.ctx.types.object_type.to_owned().into()); - let length = if let Some(len_obj) = length_attr { - len_obj.try_int(vm)?.as_bigint().to_usize().unwrap_or(0) - } else { - 0 + // Check for abstract class - StgInfo must exist and be initialized + // Extract values in a block to drop the borrow before using cls + let (length, total_size) = { + let stg = cls.stg_info(vm)?; + (stg.length, stg.size) }; - // Get element size from _type_ - let element_size = if let Ok(type_code) = element_type.get_attr("_type_", vm) { - if let Ok(s) = type_code.str(vm) { - let s = s.to_string(); - if s.len() == 1 { - get_size(&s) - } else { - std::mem::size_of::<usize>() - } - } else { - std::mem::size_of::<usize>() - } - } else { - std::mem::size_of::<usize>() - }; + // Check for too many initializers + if args.args.len() > length { + return Err(vm.new_index_error("too many initializers")); + } - let total_size = element_size * length; - let mut buffer = vec![0u8; total_size]; + // Create array with zero-initialized buffer + let buffer = vec![0u8; total_size]; + let instance = PyCArray(PyCData::from_bytes_with_length(buffer, None, length)) + .into_ref_with_type(vm, cls)?; - // Initialize from positional arguments + // Initialize elements using setitem_by_index (Array_init pattern) for (i, value) in args.args.iter().enumerate() { - if i >= length { - break; - } - let offset = i * element_size; - if let Ok(int_val) = value.try_int(vm) { - let bytes = PyCArray::int_to_bytes(int_val.as_bigint(), element_size); - if offset + element_size <= buffer.len() { - buffer[offset..offset + element_size].copy_from_slice(&bytes); - } - } + PyCArray::setitem_by_index(&instance, i as isize, value.clone(), vm)?; } - let cdata = CDataObject::from_bytes(buffer, None); - PyCArray { - _base: PyCData::new(cdata.clone()), - typ: PyRwLock::new(element_type), - length: AtomicCell::new(length), - element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(cdata), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + Ok(instance.into()) } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { @@ -325,15 +418,19 @@ impl AsSequence for PyCArray { fn as_sequence() -> &'static PySequenceMethods { use std::sync::LazyLock; static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods { - length: atomic_func!(|seq, _vm| Ok(PyCArray::sequence_downcast(seq).length.load())), + length: atomic_func!(|seq, _vm| { + let zelf = PyCArray::sequence_downcast(seq); + Ok(zelf.class().stg_info_opt().map_or(0, |i| i.length)) + }), item: atomic_func!(|seq, i, vm| { - PyCArray::getitem_by_index(PyCArray::sequence_downcast(seq), i, vm) + let zelf = PyCArray::sequence_downcast(seq); + PyCArray::getitem_by_index(zelf, i, vm) }), ass_item: atomic_func!(|seq, i, value, vm| { let zelf = PyCArray::sequence_downcast(seq); match value { Some(v) => PyCArray::setitem_by_index(zelf, i, v, vm), - None => Err(vm.new_type_error("cannot delete array elements".to_owned())), + None => Err(vm.new_type_error("cannot delete array elements")), } }), ..PySequenceMethods::NOT_IMPLEMENTED @@ -347,468 +444,839 @@ impl AsSequence for PyCArray { with(Constructor, AsSequence, AsBuffer) )] impl PyCArray { - #[pygetset] - fn _objects(&self) -> Option<PyObjectRef> { - self.cdata.read().objects.clone() - } - fn int_to_bytes(i: &malachite_bigint::BigInt, size: usize) -> Vec<u8> { + // Try unsigned first (handles values like 0xFFFFFFFF that overflow signed) + // then fall back to signed (handles negative values) match size { - 1 => vec![i.to_i8().unwrap_or(0) as u8], - 2 => i.to_i16().unwrap_or(0).to_ne_bytes().to_vec(), - 4 => i.to_i32().unwrap_or(0).to_ne_bytes().to_vec(), - 8 => i.to_i64().unwrap_or(0).to_ne_bytes().to_vec(), + 1 => { + if let Some(v) = i.to_u8() { + vec![v] + } else { + vec![i.to_i8().unwrap_or(0) as u8] + } + } + 2 => { + if let Some(v) = i.to_u16() { + v.to_ne_bytes().to_vec() + } else { + i.to_i16().unwrap_or(0).to_ne_bytes().to_vec() + } + } + 4 => { + if let Some(v) = i.to_u32() { + v.to_ne_bytes().to_vec() + } else { + i.to_i32().unwrap_or(0).to_ne_bytes().to_vec() + } + } + 8 => { + if let Some(v) = i.to_u64() { + v.to_ne_bytes().to_vec() + } else { + i.to_i64().unwrap_or(0).to_ne_bytes().to_vec() + } + } _ => vec![0u8; size], } } - fn bytes_to_int(bytes: &[u8], size: usize, vm: &VirtualMachine) -> PyObjectRef { - match size { - 1 => vm.ctx.new_int(bytes[0] as i8).into(), - 2 => { + fn bytes_to_int( + bytes: &[u8], + size: usize, + type_code: Option<&str>, + vm: &VirtualMachine, + ) -> PyObjectRef { + // Unsigned type codes: B (uchar), H (ushort), I (uint), L (ulong), Q (ulonglong) + let is_unsigned = matches!( + type_code, + Some("B") | Some("H") | Some("I") | Some("L") | Some("Q") + ); + + match (size, is_unsigned) { + (1, false) => vm.ctx.new_int(bytes[0] as i8).into(), + (1, true) => vm.ctx.new_int(bytes[0]).into(), + (2, false) => { let val = i16::from_ne_bytes([bytes[0], bytes[1]]); vm.ctx.new_int(val).into() } - 4 => { + (2, true) => { + let val = u16::from_ne_bytes([bytes[0], bytes[1]]); + vm.ctx.new_int(val).into() + } + (4, false) => { let val = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); vm.ctx.new_int(val).into() } - 8 => { + (4, true) => { + let val = u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + vm.ctx.new_int(val).into() + } + (8, false) => { let val = i64::from_ne_bytes([ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], ]); vm.ctx.new_int(val).into() } + (8, true) => { + let val = u64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ]); + vm.ctx.new_int(val).into() + } _ => vm.ctx.new_int(0).into(), } } - fn getitem_by_index(zelf: &PyCArray, i: isize, vm: &VirtualMachine) -> PyResult { - let length = zelf.length.load() as isize; + fn getitem_by_index(zelf: &Py<PyCArray>, i: isize, vm: &VirtualMachine) -> PyResult { + let stg = zelf.class().stg_info_opt(); + let length = stg.as_ref().map_or(0, |i| i.length) as isize; let index = if i < 0 { length + i } else { i }; if index < 0 || index >= length { - return Err(vm.new_index_error("array index out of range".to_owned())); + return Err(vm.new_index_error("invalid index")); } let index = index as usize; - let element_size = zelf.element_size.load(); + let element_size = stg.as_ref().map_or(0, |i| i.element_size); let offset = index * element_size; - let buffer = zelf.cdata.read().buffer.clone(); - if offset + element_size <= buffer.len() { - let bytes = &buffer[offset..offset + element_size]; - Ok(Self::bytes_to_int(bytes, element_size, vm)) + let type_code = Self::get_element_type_code(zelf, vm); + + // Get target buffer and offset (base's buffer if available, otherwise own) + let base_obj = zelf.0.base.read().clone(); + let (buffer_lock, final_offset) = if let Some(cdata) = base_obj + .as_ref() + .and_then(|b| b.downcast_ref::<super::PyCData>()) + { + (&cdata.buffer, zelf.0.base_offset.load() + offset) } else { - Ok(vm.ctx.new_int(0).into()) + (&zelf.0.buffer, offset) + }; + + let buffer = buffer_lock.read(); + Self::read_element_from_buffer( + &buffer, + final_offset, + element_size, + type_code.as_deref(), + vm, + ) + } + + /// Helper to read an element value from a buffer at given offset + fn read_element_from_buffer( + buffer: &[u8], + offset: usize, + element_size: usize, + type_code: Option<&str>, + vm: &VirtualMachine, + ) -> PyResult { + match type_code { + Some("c") => { + // Return single byte as bytes + if offset < buffer.len() { + Ok(vm.ctx.new_bytes(vec![buffer[offset]]).into()) + } else { + Ok(vm.ctx.new_bytes(vec![0]).into()) + } + } + Some("u") => { + // Return single wchar as str + if let Some(code) = wchar_from_bytes(&buffer[offset..]) { + let s = char::from_u32(code) + .map(|c| c.to_string()) + .unwrap_or_default(); + Ok(vm.ctx.new_str(s).into()) + } else { + Ok(vm.ctx.new_str("").into()) + } + } + Some("z") => { + // c_char_p: pointer to bytes - dereference to get string + if offset + element_size > buffer.len() { + return Ok(vm.ctx.none()); + } + let ptr_bytes = &buffer[offset..offset + element_size]; + let ptr_val = usize::from_ne_bytes( + ptr_bytes + .try_into() + .unwrap_or([0; std::mem::size_of::<usize>()]), + ); + if ptr_val == 0 { + return Ok(vm.ctx.none()); + } + // Read null-terminated string from pointer address + unsafe { + let ptr = ptr_val as *const u8; + let mut len = 0; + while *ptr.add(len) != 0 { + len += 1; + } + let bytes = std::slice::from_raw_parts(ptr, len); + Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) + } + } + Some("Z") => { + // c_wchar_p: pointer to wchar_t - dereference to get string + if offset + element_size > buffer.len() { + return Ok(vm.ctx.none()); + } + let ptr_bytes = &buffer[offset..offset + element_size]; + let ptr_val = usize::from_ne_bytes( + ptr_bytes + .try_into() + .unwrap_or([0; std::mem::size_of::<usize>()]), + ); + if ptr_val == 0 { + return Ok(vm.ctx.none()); + } + // Read null-terminated wide string using WCHAR_SIZE + unsafe { + let ptr = ptr_val as *const u8; + let mut chars = Vec::new(); + let mut pos = 0usize; + loop { + let code = if WCHAR_SIZE == 2 { + let bytes = std::slice::from_raw_parts(ptr.add(pos), 2); + u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 + } else { + let bytes = std::slice::from_raw_parts(ptr.add(pos), 4); + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + }; + if code == 0 { + break; + } + if let Some(ch) = char::from_u32(code) { + chars.push(ch); + } + pos += WCHAR_SIZE; + } + let s: String = chars.into_iter().collect(); + Ok(vm.ctx.new_str(s).into()) + } + } + Some("f") => { + // c_float + if offset + 4 <= buffer.len() { + let bytes: [u8; 4] = buffer[offset..offset + 4].try_into().unwrap(); + let val = f32::from_ne_bytes(bytes); + Ok(vm.ctx.new_float(val as f64).into()) + } else { + Ok(vm.ctx.new_float(0.0).into()) + } + } + Some("d") | Some("g") => { + // c_double / c_longdouble - read f64 from first 8 bytes + if offset + 8 <= buffer.len() { + let bytes: [u8; 8] = buffer[offset..offset + 8].try_into().unwrap(); + let val = f64::from_ne_bytes(bytes); + Ok(vm.ctx.new_float(val).into()) + } else { + Ok(vm.ctx.new_float(0.0).into()) + } + } + _ => { + if offset + element_size <= buffer.len() { + let bytes = &buffer[offset..offset + element_size]; + Ok(Self::bytes_to_int(bytes, element_size, type_code, vm)) + } else { + Ok(vm.ctx.new_int(0).into()) + } + } } } + /// Helper to write an element value to a buffer at given offset + /// This is extracted to share code between direct write and base-buffer write + #[allow(clippy::too_many_arguments)] + fn write_element_to_buffer( + buffer: &mut [u8], + offset: usize, + element_size: usize, + type_code: Option<&str>, + value: &PyObject, + zelf: &Py<PyCArray>, + index: usize, + vm: &VirtualMachine, + ) -> PyResult<()> { + match type_code { + Some("c") => { + if let Some(b) = value.downcast_ref::<PyBytes>() { + if offset < buffer.len() { + buffer[offset] = b.as_bytes().first().copied().unwrap_or(0); + } + } else if let Ok(int_val) = value.try_int(vm) { + if offset < buffer.len() { + buffer[offset] = int_val.as_bigint().to_u8().unwrap_or(0); + } + } else { + return Err(vm.new_type_error("an integer or bytes of length 1 is required")); + } + } + Some("u") => { + if let Some(s) = value.downcast_ref::<PyStr>() { + let code = s.as_str().chars().next().map(|c| c as u32).unwrap_or(0); + if offset + WCHAR_SIZE <= buffer.len() { + wchar_to_bytes(code, &mut buffer[offset..]); + } + } else { + return Err(vm.new_type_error("unicode string expected")); + } + } + Some("z") => { + let (ptr_val, converted) = if value.is(&vm.ctx.none) { + (0usize, None) + } else if let Some(bytes) = value.downcast_ref::<PyBytes>() { + let (c, ptr) = super::base::ensure_z_null_terminated(bytes, vm); + (ptr, Some(c)) + } else if let Ok(int_val) = value.try_index(vm) { + (int_val.as_bigint().to_usize().unwrap_or(0), None) + } else { + return Err(vm.new_type_error( + "bytes or integer address expected instead of {}".to_owned(), + )); + }; + if offset + element_size <= buffer.len() { + buffer[offset..offset + element_size].copy_from_slice(&ptr_val.to_ne_bytes()); + } + if let Some(c) = converted { + return zelf.0.keep_ref(index, c, vm); + } + } + Some("Z") => { + let (ptr_val, converted) = if value.is(&vm.ctx.none) { + (0usize, None) + } else if let Some(s) = value.downcast_ref::<PyStr>() { + let (holder, ptr) = super::base::str_to_wchar_bytes(s.as_str(), vm); + (ptr, Some(holder)) + } else if let Ok(int_val) = value.try_index(vm) { + (int_val.as_bigint().to_usize().unwrap_or(0), None) + } else { + return Err(vm.new_type_error("unicode string or integer address expected")); + }; + if offset + element_size <= buffer.len() { + buffer[offset..offset + element_size].copy_from_slice(&ptr_val.to_ne_bytes()); + } + if let Some(c) = converted { + return zelf.0.keep_ref(index, c, vm); + } + } + Some("f") => { + // c_float: convert int/float to f32 bytes + let f32_val = if let Ok(float_val) = value.try_float(vm) { + float_val.to_f64() as f32 + } else if let Ok(int_val) = value.try_int(vm) { + int_val.as_bigint().to_f64().unwrap_or(0.0) as f32 + } else { + return Err(vm.new_type_error("a float is required")); + }; + if offset + 4 <= buffer.len() { + buffer[offset..offset + 4].copy_from_slice(&f32_val.to_ne_bytes()); + } + } + Some("d") | Some("g") => { + // c_double / c_longdouble: convert int/float to f64 bytes + let f64_val = if let Ok(float_val) = value.try_float(vm) { + float_val.to_f64() + } else if let Ok(int_val) = value.try_int(vm) { + int_val.as_bigint().to_f64().unwrap_or(0.0) + } else { + return Err(vm.new_type_error("a float is required")); + }; + if offset + 8 <= buffer.len() { + buffer[offset..offset + 8].copy_from_slice(&f64_val.to_ne_bytes()); + } + // For "g" type, remaining bytes stay zero + } + _ => { + // Handle ctypes instances (copy their buffer) + if let Some(cdata) = value.downcast_ref::<PyCData>() { + let src_buffer = cdata.buffer.read(); + let copy_len = src_buffer.len().min(element_size); + if offset + copy_len <= buffer.len() { + buffer[offset..offset + copy_len].copy_from_slice(&src_buffer[..copy_len]); + } + // Other types: use int_to_bytes + } else if let Ok(int_val) = value.try_int(vm) { + let bytes = Self::int_to_bytes(int_val.as_bigint(), element_size); + if offset + element_size <= buffer.len() { + buffer[offset..offset + element_size].copy_from_slice(&bytes); + } + } else { + return Err(vm.new_type_error(format!( + "expected {} instance, not {}", + type_code.unwrap_or("value"), + value.class().name() + ))); + } + } + } + + // KeepRef + if super::base::PyCData::should_keep_ref(value) { + let to_keep = super::base::PyCData::get_kept_objects(value, vm); + zelf.0.keep_ref(index, to_keep, vm)?; + } + + Ok(()) + } + fn setitem_by_index( - zelf: &PyCArray, + zelf: &Py<PyCArray>, i: isize, value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { - let length = zelf.length.load() as isize; + let stg = zelf.class().stg_info_opt(); + let length = stg.as_ref().map_or(0, |i| i.length) as isize; let index = if i < 0 { length + i } else { i }; if index < 0 || index >= length { - return Err(vm.new_index_error("array index out of range".to_owned())); + return Err(vm.new_index_error("invalid index")); } let index = index as usize; - let element_size = zelf.element_size.load(); + let element_size = stg.as_ref().map_or(0, |i| i.element_size); let offset = index * element_size; + let type_code = Self::get_element_type_code(zelf, vm); + + // Get target buffer and offset (base's buffer if available, otherwise own) + let base_obj = zelf.0.base.read().clone(); + let (buffer_lock, final_offset) = if let Some(cdata) = base_obj + .as_ref() + .and_then(|b| b.downcast_ref::<super::PyCData>()) + { + (&cdata.buffer, zelf.0.base_offset.load() + offset) + } else { + (&zelf.0.buffer, offset) + }; - let int_val = value.try_int(vm)?; - let bytes = Self::int_to_bytes(int_val.as_bigint(), element_size); - - let mut cdata = zelf.cdata.write(); - if offset + element_size <= cdata.buffer.len() { - cdata.buffer[offset..offset + element_size].copy_from_slice(&bytes); - } - Ok(()) + let mut buffer = buffer_lock.write(); + Self::write_element_to_buffer( + buffer.to_mut(), + final_offset, + element_size, + type_code.as_deref(), + &value, + zelf, + index, + vm, + ) } + // Array_subscript #[pymethod] - fn __getitem__(&self, index: PyObjectRef, vm: &VirtualMachine) -> PyResult { - if let Some(i) = index.downcast_ref::<PyInt>() { + fn __getitem__(zelf: &Py<Self>, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // PyIndex_Check + if let Some(i) = item.downcast_ref::<PyInt>() { let i = i.as_bigint().to_isize().ok_or_else(|| { - vm.new_index_error("cannot fit index into an index-sized integer".to_owned()) + vm.new_index_error("cannot fit index into an index-sized integer") })?; - Self::getitem_by_index(self, i, vm) + // getitem_by_index handles negative index normalization + Self::getitem_by_index(zelf, i, vm) + } + // PySlice_Check + else if let Some(slice) = item.downcast_ref::<PySlice>() { + Self::getitem_by_slice(zelf, slice, vm) } else { - Err(vm.new_type_error("array indices must be integers".to_owned())) + Err(vm.new_type_error("indices must be integers")) + } + } + + // Array_subscript slice handling + fn getitem_by_slice(zelf: &Py<Self>, slice: &PySlice, vm: &VirtualMachine) -> PyResult { + use crate::sliceable::SaturatedSliceIter; + + let stg = zelf.class().stg_info_opt(); + let length = stg.as_ref().map_or(0, |i| i.length); + + // PySlice_Unpack + PySlice_AdjustIndices + let sat_slice = slice.to_saturated(vm)?; + let (range, step, slice_len) = sat_slice.adjust_indices(length); + + let type_code = Self::get_element_type_code(zelf, vm); + let element_size = stg.as_ref().map_or(0, |i| i.element_size); + let start = range.start; + + match type_code.as_deref() { + // c_char → bytes (item_info->getfunc == "c") + Some("c") => { + if slice_len == 0 { + return Ok(vm.ctx.new_bytes(vec![]).into()); + } + let buffer = zelf.0.buffer.read(); + // step == 1 optimization: direct memcpy + if step == 1 { + let start_offset = start * element_size; + let end_offset = start_offset + slice_len; + if end_offset <= buffer.len() { + return Ok(vm + .ctx + .new_bytes(buffer[start_offset..end_offset].to_vec()) + .into()); + } + } + // Non-contiguous: iterate + let iter = SaturatedSliceIter::from_adjust_indices(range, step, slice_len); + let mut result = Vec::with_capacity(slice_len); + for idx in iter { + let offset = idx * element_size; + if offset < buffer.len() { + result.push(buffer[offset]); + } + } + Ok(vm.ctx.new_bytes(result).into()) + } + // c_wchar → str (item_info->getfunc == "u") + Some("u") => { + if slice_len == 0 { + return Ok(vm.ctx.new_str("").into()); + } + let buffer = zelf.0.buffer.read(); + // step == 1 optimization: direct conversion + if step == 1 { + let start_offset = start * WCHAR_SIZE; + let end_offset = start_offset + slice_len * WCHAR_SIZE; + if end_offset <= buffer.len() { + let wchar_bytes = &buffer[start_offset..end_offset]; + let result: String = wchar_bytes + .chunks(WCHAR_SIZE) + .filter_map(|chunk| wchar_from_bytes(chunk).and_then(char::from_u32)) + .collect(); + return Ok(vm.ctx.new_str(result).into()); + } + } + // Non-contiguous: iterate + let iter = SaturatedSliceIter::from_adjust_indices(range, step, slice_len); + let mut result = String::with_capacity(slice_len); + for idx in iter { + let offset = idx * WCHAR_SIZE; + if let Some(code_point) = wchar_from_bytes(&buffer[offset..]) + && let Some(c) = char::from_u32(code_point) + { + result.push(c); + } + } + Ok(vm.ctx.new_str(result).into()) + } + // Other types → list (PyList_New + Array_item for each) + _ => { + let iter = SaturatedSliceIter::from_adjust_indices(range, step, slice_len); + let mut result = Vec::with_capacity(slice_len); + for idx in iter { + result.push(Self::getitem_by_index(zelf, idx as isize, vm)?); + } + Ok(PyList::from(result).into_ref(&vm.ctx).into()) + } } } + // Array_ass_subscript #[pymethod] fn __setitem__( - &self, - index: PyObjectRef, + zelf: &Py<Self>, + item: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { - if let Some(i) = index.downcast_ref::<PyInt>() { + // Array does not support item deletion + // (handled implicitly - value is always provided in __setitem__) + + // PyIndex_Check + if let Some(i) = item.downcast_ref::<PyInt>() { let i = i.as_bigint().to_isize().ok_or_else(|| { - vm.new_index_error("cannot fit index into an index-sized integer".to_owned()) + vm.new_index_error("cannot fit index into an index-sized integer") })?; - Self::setitem_by_index(self, i, value, vm) + // setitem_by_index handles negative index normalization + Self::setitem_by_index(zelf, i, value, vm) + } + // PySlice_Check + else if let Some(slice) = item.downcast_ref::<PySlice>() { + Self::setitem_by_slice(zelf, slice, value, vm) } else { - Err(vm.new_type_error("array indices must be integers".to_owned())) + Err(vm.new_type_error("indices must be integer")) } } + // Array does not support item deletion #[pymethod] - fn __len__(&self) -> usize { - self.length.load() + fn __delitem__(&self, _item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + Err(vm.new_type_error("Array does not support item deletion")) } - #[pygetset(name = "_type_")] - fn typ(&self) -> PyObjectRef { - self.typ.read().clone() - } + // Array_ass_subscript slice handling + fn setitem_by_slice( + zelf: &Py<Self>, + slice: &PySlice, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + use crate::sliceable::SaturatedSliceIter; - #[pygetset(name = "_length_")] - fn length_getter(&self) -> usize { - self.length.load() - } + let length = zelf.class().stg_info_opt().map_or(0, |i| i.length); - #[pygetset] - fn value(&self, vm: &VirtualMachine) -> PyObjectRef { - // Return bytes representation of the buffer - let buffer = self.cdata.read().buffer.clone(); - vm.ctx.new_bytes(buffer.clone()).into() - } + // PySlice_Unpack + PySlice_AdjustIndices + let sat_slice = slice.to_saturated(vm)?; + let (range, step, slice_len) = sat_slice.adjust_indices(length); - #[pygetset(setter)] - fn set_value(&self, value: PyObjectRef, _vm: &VirtualMachine) -> PyResult<()> { - if let Some(bytes) = value.downcast_ref::<PyBytes>() { - let mut cdata = self.cdata.write(); - let src = bytes.as_bytes(); - let len = std::cmp::min(src.len(), cdata.buffer.len()); - cdata.buffer[..len].copy_from_slice(&src[..len]); + // other_len = PySequence_Length(value); + let items: Vec<PyObjectRef> = vm.extract_elements_with(&value, Ok)?; + let other_len = items.len(); + + if other_len != slice_len { + return Err(vm.new_value_error("Can only assign sequence of same size")); } - Ok(()) - } - #[pygetset] - fn raw(&self, vm: &VirtualMachine) -> PyObjectRef { - let cdata = self.cdata.read(); - vm.ctx.new_bytes(cdata.buffer.clone()).into() - } + // Use SaturatedSliceIter for correct index iteration (handles negative step) + let iter = SaturatedSliceIter::from_adjust_indices(range, step, slice_len); - #[pygetset(setter)] - fn set_raw(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - if let Some(bytes) = value.downcast_ref::<PyBytes>() { - let mut cdata = self.cdata.write(); - let src = bytes.as_bytes(); - let len = std::cmp::min(src.len(), cdata.buffer.len()); - cdata.buffer[..len].copy_from_slice(&src[..len]); - Ok(()) - } else { - Err(vm.new_type_error("expected bytes".to_owned())) + for (idx, item) in iter.zip(items) { + Self::setitem_by_index(zelf, idx as isize, item, vm)?; } + Ok(()) } - #[pyclassmethod] - fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { - use crate::stdlib::ctypes::_ctypes::size_of; + #[pymethod] + fn __len__(zelf: &Py<Self>, _vm: &VirtualMachine) -> usize { + zelf.class().stg_info_opt().map_or(0, |i| i.length) + } +} - // Get size from cls - let size = size_of(cls.clone().into(), vm)?; +impl PyCArray { + #[allow(unused)] + pub fn to_arg(&self, _vm: &VirtualMachine) -> PyResult<libffi::middle::Arg> { + let buffer = self.0.buffer.read(); + Ok(libffi::middle::Arg::new(&*buffer)) + } +} - // Create instance with data from address - if address == 0 || size == 0 { - return Err(vm.new_value_error("NULL pointer access".to_owned())); - } - unsafe { - let ptr = address as *const u8; - let bytes = std::slice::from_raw_parts(ptr, size); - // Get element type and length from cls - let element_type = cls.as_object().get_attr("_type_", vm)?; - let element_type: PyTypeRef = element_type - .downcast() - .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; - let length = cls - .as_object() - .get_attr("_length_", vm)? - .try_int(vm)? - .as_bigint() - .to_usize() - .unwrap_or(0); - let element_size = if length > 0 { size / length } else { 0 }; - - let cdata = CDataObject::from_bytes(bytes.to_vec(), None); - Ok(PyCArray { - _base: PyCData::new(cdata.clone()), - typ: PyRwLock::new(element_type.into()), - length: AtomicCell::new(length), - element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(cdata), +impl AsBuffer for PyCArray { + fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { + let buffer_len = zelf.0.buffer.read().len(); + + // Get format and shape from type's StgInfo + let stg_info = zelf + .class() + .stg_info_opt() + .expect("PyCArray type must have StgInfo"); + let format = stg_info.format.clone(); + let shape = stg_info.shape.clone(); + let element_size = stg_info.element_size; + + let desc = if let Some(fmt) = format + && !shape.is_empty() + { + // Build dim_desc from shape (C-contiguous: row-major order) + // stride[i] = product(shape[i+1:]) * itemsize + let mut dim_desc = Vec::with_capacity(shape.len()); + let mut stride = element_size as isize; + + // Calculate strides from innermost to outermost dimension + for &dim_size in shape.iter().rev() { + dim_desc.push((dim_size, stride, 0)); + stride *= dim_size as isize; } - .into_pyobject(vm)) - } - } + dim_desc.reverse(); + + BufferDescriptor { + len: buffer_len, + readonly: false, + itemsize: element_size, + format: std::borrow::Cow::Owned(fmt), + dim_desc, + } + } else { + // Fallback to simple buffer if no format/shape info + BufferDescriptor::simple(buffer_len, false) + }; - #[pyclassmethod] - fn from_buffer( - cls: PyTypeRef, - source: PyObjectRef, - offset: crate::function::OptionalArg<isize>, - vm: &VirtualMachine, - ) -> PyResult { - use crate::TryFromObject; - use crate::protocol::PyBuffer; - use crate::stdlib::ctypes::_ctypes::size_of; + let buf = PyBuffer::new(zelf.to_owned().into(), desc, &CDATA_BUFFER_METHODS); + Ok(buf) + } +} - let offset = offset.unwrap_or(0); - if offset < 0 { - return Err(vm.new_value_error("offset cannot be negative".to_owned())); - } - let offset = offset as usize; +// CharArray and WCharArray getsets - added dynamically via add_getset - // Get buffer from source - let buffer = PyBuffer::try_from_object(vm, source.clone())?; +// CharArray_get_value +fn char_array_get_value(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let zelf = obj.downcast_ref::<PyCArray>().unwrap(); + let buffer = zelf.0.buffer.read(); + let len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len()); + Ok(vm.ctx.new_bytes(buffer[..len].to_vec()).into()) +} - // Check if buffer is writable - if buffer.desc.readonly { - return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); - } +// CharArray_set_value +fn char_array_set_value(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let zelf = obj.downcast_ref::<PyCArray>().unwrap(); + let bytes = value + .downcast_ref::<PyBytes>() + .ok_or_else(|| vm.new_type_error("bytes expected"))?; + let mut buffer = zelf.0.buffer.write(); + let src = bytes.as_bytes(); + + if src.len() > buffer.len() { + return Err(vm.new_value_error("byte string too long")); + } - // Get size from cls - let size = size_of(cls.clone().into(), vm)?; - - // Check if buffer is large enough - let buffer_len = buffer.desc.len; - if offset + size > buffer_len { - return Err(vm.new_value_error(format!( - "Buffer size too small ({} instead of at least {} bytes)", - buffer_len, - offset + size - ))); - } + buffer.to_mut()[..src.len()].copy_from_slice(src); + if src.len() < buffer.len() { + buffer.to_mut()[src.len()] = 0; + } + Ok(()) +} - // Read bytes from buffer at offset - let bytes = buffer.obj_bytes(); - let data = &bytes[offset..offset + size]; +// CharArray_get_raw +fn char_array_get_raw(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let zelf = obj.downcast_ref::<PyCArray>().unwrap(); + let buffer = zelf.0.buffer.read(); + Ok(vm.ctx.new_bytes(buffer.to_vec()).into()) +} - // Get element type and length from cls - let element_type = cls.as_object().get_attr("_type_", vm)?; - let element_type: PyTypeRef = element_type - .downcast() - .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; - let length = cls - .as_object() - .get_attr("_length_", vm)? - .try_int(vm)? - .as_bigint() - .to_usize() - .unwrap_or(0); - let element_size = if length > 0 { size / length } else { 0 }; - - let cdata = CDataObject::from_bytes(data.to_vec(), Some(buffer.obj.clone())); - Ok(PyCArray { - _base: PyCData::new(cdata.clone()), - typ: PyRwLock::new(element_type.into()), - length: AtomicCell::new(length), - element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(cdata), - } - .into_pyobject(vm)) +// CharArray_set_raw +fn char_array_set_raw( + obj: PyObjectRef, + value: PySetterValue<PyObjectRef>, + vm: &VirtualMachine, +) -> PyResult<()> { + let value = value.ok_or_else(|| vm.new_attribute_error("cannot delete attribute"))?; + let zelf = obj.downcast_ref::<PyCArray>().unwrap(); + let bytes_like = ArgBytesLike::try_from_object(vm, value)?; + let mut buffer = zelf.0.buffer.write(); + let src = bytes_like.borrow_buf(); + if src.len() > buffer.len() { + return Err(vm.new_value_error("byte string too long")); } + buffer.to_mut()[..src.len()].copy_from_slice(&src); + Ok(()) +} - #[pyclassmethod] - fn from_buffer_copy( - cls: PyTypeRef, - source: crate::function::ArgBytesLike, - offset: crate::function::OptionalArg<isize>, - vm: &VirtualMachine, - ) -> PyResult { - use crate::stdlib::ctypes::_ctypes::size_of; - - let offset = offset.unwrap_or(0); - if offset < 0 { - return Err(vm.new_value_error("offset cannot be negative".to_owned())); - } - let offset = offset as usize; - - // Get size from cls - let size = size_of(cls.clone().into(), vm)?; - - // Borrow bytes from source - let source_bytes = source.borrow_buf(); - let buffer_len = source_bytes.len(); - - // Check if buffer is large enough - if offset + size > buffer_len { - return Err(vm.new_value_error(format!( - "Buffer size too small ({} instead of at least {} bytes)", - buffer_len, - offset + size - ))); - } +// WCharArray_get_value +fn wchar_array_get_value(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let zelf = obj.downcast_ref::<PyCArray>().unwrap(); + let buffer = zelf.0.buffer.read(); + Ok(vm.ctx.new_str(wstring_from_bytes(&buffer)).into()) +} - // Copy bytes from buffer at offset - let data = &source_bytes[offset..offset + size]; +// WCharArray_set_value +fn wchar_array_set_value( + obj: PyObjectRef, + value: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult<()> { + let zelf = obj.downcast_ref::<PyCArray>().unwrap(); + let s = value + .downcast_ref::<PyStr>() + .ok_or_else(|| vm.new_type_error("unicode string expected"))?; + let mut buffer = zelf.0.buffer.write(); + let wchar_count = buffer.len() / WCHAR_SIZE; + let char_count = s.as_str().chars().count(); + + if char_count > wchar_count { + return Err(vm.new_value_error("string too long")); + } - // Get element type and length from cls - let element_type = cls.as_object().get_attr("_type_", vm)?; - let element_type: PyTypeRef = element_type - .downcast() - .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; - let length = cls - .as_object() - .get_attr("_length_", vm)? - .try_int(vm)? - .as_bigint() - .to_usize() - .unwrap_or(0); - let element_size = if length > 0 { size / length } else { 0 }; - - let cdata = CDataObject::from_bytes(data.to_vec(), None); - Ok(PyCArray { - _base: PyCData::new(cdata.clone()), - typ: PyRwLock::new(element_type.into()), - length: AtomicCell::new(length), - element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(cdata), - } - .into_pyobject(vm)) + for (i, ch) in s.as_str().chars().enumerate() { + let offset = i * WCHAR_SIZE; + wchar_to_bytes(ch as u32, &mut buffer.to_mut()[offset..]); } - #[pyclassmethod] - fn in_dll( - cls: PyTypeRef, - dll: PyObjectRef, - name: crate::builtins::PyStrRef, - vm: &VirtualMachine, - ) -> PyResult { - use crate::stdlib::ctypes::_ctypes::size_of; - use libloading::Symbol; - - // Get the library handle from dll object - let handle = if let Ok(int_handle) = dll.try_int(vm) { - // dll is an integer handle - int_handle - .as_bigint() - .to_usize() - .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? - } else { - // dll is a CDLL/PyDLL/WinDLL object with _handle attribute - dll.get_attr("_handle", vm)? - .try_int(vm)? - .as_bigint() - .to_usize() - .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? - }; + let terminator_offset = char_count * WCHAR_SIZE; + if terminator_offset + WCHAR_SIZE <= buffer.len() { + wchar_to_bytes(0, &mut buffer.to_mut()[terminator_offset..]); + } + Ok(()) +} - // Get the library from cache - let library_cache = crate::stdlib::ctypes::library::libcache().read(); - let library = library_cache - .get_lib(handle) - .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; - - // Get symbol address from library - let symbol_name = format!("{}\0", name.as_str()); - let inner_lib = library.lib.lock(); - - let symbol_address = if let Some(lib) = &*inner_lib { - unsafe { - // Try to get the symbol from the library - let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { - vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) - })?; - *symbol as usize - } - } else { - return Err(vm.new_attribute_error("Library is closed".to_owned())); - }; +/// add_getset for c_char arrays - adds 'value' and 'raw' attributes +/// add_getset((PyTypeObject*)self, CharArray_getsets) +fn add_char_array_getsets(array_type: &Py<PyType>, vm: &VirtualMachine) { + // SAFETY: getset is owned by array_type which outlives the getset + let value_getset = unsafe { + vm.ctx.new_getset( + "value", + array_type, + char_array_get_value, + char_array_set_value, + ) + }; + let raw_getset = unsafe { + vm.ctx + .new_getset("raw", array_type, char_array_get_raw, char_array_set_raw) + }; + + array_type + .attributes + .write() + .insert(vm.ctx.intern_str("value"), value_getset.into()); + array_type + .attributes + .write() + .insert(vm.ctx.intern_str("raw"), raw_getset.into()); +} - // Get size from cls - let size = size_of(cls.clone().into(), vm)?; +/// add_getset for c_wchar arrays - adds only 'value' attribute (no 'raw') +fn add_wchar_array_getsets(array_type: &Py<PyType>, vm: &VirtualMachine) { + // SAFETY: getset is owned by array_type which outlives the getset + let value_getset = unsafe { + vm.ctx.new_getset( + "value", + array_type, + wchar_array_get_value, + wchar_array_set_value, + ) + }; - // Read data from symbol address - let data = if symbol_address != 0 && size > 0 { - unsafe { - let ptr = symbol_address as *const u8; - std::slice::from_raw_parts(ptr, size).to_vec() - } - } else { - vec![0; size] - }; + array_type + .attributes + .write() + .insert(vm.ctx.intern_str("value"), value_getset.into()); +} - // Get element type and length from cls - let element_type = cls.as_object().get_attr("_type_", vm)?; - let element_type: PyTypeRef = element_type - .downcast() - .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; - let length = cls - .as_object() - .get_attr("_length_", vm)? - .try_int(vm)? - .as_bigint() - .to_usize() - .unwrap_or(0); - let element_size = if length > 0 { size / length } else { 0 }; - - // Create instance - let cdata = CDataObject::from_bytes(data, None); - let instance = PyCArray { - _base: PyCData::new(cdata.clone()), - typ: PyRwLock::new(element_type.into()), - length: AtomicCell::new(length), - element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(cdata), - } - .into_pyobject(vm); +// wchar_t helpers - Platform-independent wide character handling +// Windows: sizeof(wchar_t) == 2 (UTF-16) +// Linux/macOS: sizeof(wchar_t) == 4 (UTF-32) - // Store base reference to keep dll alive - if let Ok(array_ref) = instance.clone().downcast::<PyCArray>() { - array_ref.cdata.write().base = Some(dll); - } +/// Size of wchar_t on this platform +pub(super) const WCHAR_SIZE: usize = std::mem::size_of::<libc::wchar_t>(); - Ok(instance) +/// Read a single wchar_t from bytes (platform-endian) +#[inline] +pub(super) fn wchar_from_bytes(bytes: &[u8]) -> Option<u32> { + if bytes.len() < WCHAR_SIZE { + return None; } + Some(if WCHAR_SIZE == 2 { + u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 + } else { + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + }) } -impl PyCArray { - #[allow(unused)] - pub fn to_arg(&self, _vm: &VirtualMachine) -> PyResult<libffi::middle::Arg> { - let cdata = self.cdata.read(); - Ok(libffi::middle::Arg::new(&cdata.buffer)) +/// Write a single wchar_t to bytes (platform-endian) +#[inline] +pub(super) fn wchar_to_bytes(ch: u32, buffer: &mut [u8]) { + if WCHAR_SIZE == 2 { + if buffer.len() >= 2 { + buffer[..2].copy_from_slice(&(ch as u16).to_ne_bytes()); + } + } else if buffer.len() >= 4 { + buffer[..4].copy_from_slice(&ch.to_ne_bytes()); } } -static ARRAY_BUFFER_METHODS: BufferMethods = BufferMethods { - obj_bytes: |buffer| { - rustpython_common::lock::PyMappedRwLockReadGuard::map( - rustpython_common::lock::PyRwLockReadGuard::map( - buffer.obj_as::<PyCArray>().cdata.read(), - |x: &CDataObject| x, - ), - |x: &CDataObject| x.buffer.as_slice(), - ) - .into() - }, - obj_bytes_mut: |buffer| { - rustpython_common::lock::PyMappedRwLockWriteGuard::map( - rustpython_common::lock::PyRwLockWriteGuard::map( - buffer.obj_as::<PyCArray>().cdata.write(), - |x: &mut CDataObject| x, - ), - |x: &mut CDataObject| x.buffer.as_mut_slice(), - ) - .into() - }, - release: |_| {}, - retain: |_| {}, -}; - -impl AsBuffer for PyCArray { - fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { - let buffer_len = zelf.cdata.read().buffer.len(); - let buf = PyBuffer::new( - zelf.to_owned().into(), - BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes - &ARRAY_BUFFER_METHODS, - ); - Ok(buf) +/// Read a null-terminated wchar_t string from bytes, returns String +fn wstring_from_bytes(buffer: &[u8]) -> String { + let mut chars = Vec::new(); + for chunk in buffer.chunks(WCHAR_SIZE) { + if chunk.len() < WCHAR_SIZE { + break; + } + let code = if WCHAR_SIZE == 2 { + u16::from_ne_bytes([chunk[0], chunk[1]]) as u32 + } else { + u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]) + }; + if code == 0 { + break; // null terminator + } + if let Some(ch) = char::from_u32(code) { + chars.push(ch); + } } + chars.into_iter().collect() } diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index e45ff0b3b70..38c371346e0 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -1,871 +1,1113 @@ -use super::_ctypes::bytes_to_pyobject; -use super::util::StgInfo; -use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyStrRef, PyType, PyTypeRef}; -use crate::function::{ArgBytesLike, Either, FuncArgs, KwArgs, OptionalArg}; -use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; -use crate::stdlib::ctypes::_ctypes::new_simple_type; -use crate::types::{AsBuffer, AsNumber, Constructor}; +use super::array::{WCHAR_SIZE, wchar_from_bytes, wchar_to_bytes}; +use crate::builtins::{PyBytes, PyDict, PyMemoryView, PyStr, PyType, PyTypeRef}; +use crate::class::StaticType; +use crate::function::{ArgBytesLike, OptionalArg, PySetterValue}; +use crate::protocol::{BufferMethods, PyBuffer}; +use crate::types::{GetDescriptor, Representable}; use crate::{ - AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, }; use crossbeam_utils::atomic::AtomicCell; -use num_traits::ToPrimitive; +use num_traits::{Signed, ToPrimitive}; use rustpython_common::lock::PyRwLock; -use std::ffi::{c_uint, c_ulong, c_ulonglong, c_ushort}; +use std::borrow::Cow; +use std::ffi::{ + c_double, c_float, c_int, c_long, c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort, +}; use std::fmt::Debug; +use std::mem; +use widestring::WideChar; + +// StgInfo - Storage information for ctypes types +// Stored in TypeDataSlot of heap types (PyType::init_type_data/get_type_data) + +// Flag constants +bitflags::bitflags! { + #[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] + pub struct StgInfoFlags: u32 { + // Function calling convention flags + /// Standard call convention (Windows) + const FUNCFLAG_STDCALL = 0x0; + /// C calling convention + const FUNCFLAG_CDECL = 0x1; + /// Function returns HRESULT + const FUNCFLAG_HRESULT = 0x2; + /// Use Python API calling convention + const FUNCFLAG_PYTHONAPI = 0x4; + /// Capture errno after call + const FUNCFLAG_USE_ERRNO = 0x8; + /// Capture last error after call (Windows) + const FUNCFLAG_USE_LASTERROR = 0x10; + + // Type flags + /// Type is a pointer type + const TYPEFLAG_ISPOINTER = 0x100; + /// Type contains pointer fields + const TYPEFLAG_HASPOINTER = 0x200; + /// Type is or contains a union + const TYPEFLAG_HASUNION = 0x400; + /// Type contains bitfield members + const TYPEFLAG_HASBITFIELD = 0x800; + + // Dict flags + /// Type is finalized (_fields_ has been set) + const DICTFLAG_FINAL = 0x1000; + } +} -/// Get the type code string from a ctypes type (e.g., "i" for c_int) -pub fn get_type_code(cls: &PyTypeRef, vm: &VirtualMachine) -> Option<String> { - cls.as_object() - .get_attr("_type_", vm) - .ok() - .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())) +/// ParamFunc - determines how a type is passed to foreign functions +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub(super) enum ParamFunc { + #[default] + None, + /// Array types are passed as pointers (tag = 'P') + Array, + /// Simple types use their specific conversion (tag = type code) + Simple, + /// Pointer types (tag = 'P') + Pointer, + /// Structure types (tag = 'V' for value) + Structure, + /// Union types (tag = 'V' for value) + Union, +} + +#[derive(Clone)] +pub struct StgInfo { + pub initialized: bool, + pub size: usize, // number of bytes + pub align: usize, // alignment requirements + pub length: usize, // number of fields (for arrays/structures) + pub proto: Option<PyTypeRef>, // Only for Pointer/ArrayObject + pub flags: StgInfoFlags, // type flags (TYPEFLAG_*, DICTFLAG_*) + + // Array-specific fields + pub element_type: Option<PyTypeRef>, // _type_ for arrays + pub element_size: usize, // size of each element + + // PEP 3118 buffer protocol fields + pub format: Option<String>, // struct format string (e.g., "i", "(5)i") + pub shape: Vec<usize>, // shape for multi-dimensional arrays + + // Function parameter conversion + pub(super) paramfunc: ParamFunc, // how to pass to foreign functions + + // Byte order (for _swappedbytes_) + pub big_endian: bool, // true if big endian, false if little endian + + // FFI field types for structure/union passing (inherited from base class) + pub ffi_field_types: Vec<libffi::middle::Type>, } -pub fn ffi_type_from_str(_type_: &str) -> Option<libffi::middle::Type> { - match _type_ { - "c" => Some(libffi::middle::Type::u8()), - "u" => Some(libffi::middle::Type::u32()), - "b" => Some(libffi::middle::Type::i8()), - "B" => Some(libffi::middle::Type::u8()), - "h" => Some(libffi::middle::Type::i16()), - "H" => Some(libffi::middle::Type::u16()), - "i" => Some(libffi::middle::Type::i32()), - "I" => Some(libffi::middle::Type::u32()), - "l" => Some(libffi::middle::Type::i32()), - "L" => Some(libffi::middle::Type::u32()), - "q" => Some(libffi::middle::Type::i64()), - "Q" => Some(libffi::middle::Type::u64()), - "f" => Some(libffi::middle::Type::f32()), - "d" => Some(libffi::middle::Type::f64()), - "g" => Some(libffi::middle::Type::f64()), - "?" => Some(libffi::middle::Type::u8()), - "z" => Some(libffi::middle::Type::u64()), - "Z" => Some(libffi::middle::Type::u64()), - "P" => Some(libffi::middle::Type::u64()), - _ => None, +// StgInfo is stored in type_data which requires Send + Sync. +// The PyTypeRef in proto/element_type fields is protected by the type system's locking mechanism. +// ctypes objects are not thread-safe by design; users must synchronize access. +unsafe impl Send for StgInfo {} +unsafe impl Sync for StgInfo {} + +impl std::fmt::Debug for StgInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("StgInfo") + .field("initialized", &self.initialized) + .field("size", &self.size) + .field("align", &self.align) + .field("length", &self.length) + .field("proto", &self.proto) + .field("flags", &self.flags) + .field("element_type", &self.element_type) + .field("element_size", &self.element_size) + .field("format", &self.format) + .field("shape", &self.shape) + .field("paramfunc", &self.paramfunc) + .field("big_endian", &self.big_endian) + .field("ffi_field_types", &self.ffi_field_types.len()) + .finish() } } -#[allow(dead_code)] -fn set_primitive(_type_: &str, value: &PyObject, vm: &VirtualMachine) -> PyResult { - match _type_ { - "c" => { - if value - .to_owned() - .downcast_exact::<PyBytes>(vm) - .is_ok_and(|v| v.len() == 1) - || value - .to_owned() - .downcast_exact::<PyBytes>(vm) - .is_ok_and(|v| v.len() == 1) - || value - .to_owned() - .downcast_exact::<PyInt>(vm) - .map_or(Ok(false), |v| { - let n = v.as_bigint().to_i64(); - if let Some(n) = n { - Ok((0..=255).contains(&n)) - } else { - Ok(false) - } - })? - { - Ok(value.to_owned()) - } else { - Err(vm.new_type_error("one character bytes, bytearray or integer expected")) - } +impl Default for StgInfo { + fn default() -> Self { + StgInfo { + initialized: false, + size: 0, + align: 1, + length: 0, + proto: None, + flags: StgInfoFlags::empty(), + element_type: None, + element_size: 0, + format: None, + shape: Vec::new(), + paramfunc: ParamFunc::None, + big_endian: cfg!(target_endian = "big"), // native endian by default + ffi_field_types: Vec::new(), } - "u" => { - if let Ok(b) = value.str(vm).map(|v| v.to_string().chars().count() == 1) { - if b { - Ok(value.to_owned()) + } +} + +impl StgInfo { + pub fn new(size: usize, align: usize) -> Self { + StgInfo { + initialized: true, + size, + align, + length: 0, + proto: None, + flags: StgInfoFlags::empty(), + element_type: None, + element_size: 0, + format: None, + shape: Vec::new(), + paramfunc: ParamFunc::None, + big_endian: cfg!(target_endian = "big"), // native endian by default + ffi_field_types: Vec::new(), + } + } + + /// Create StgInfo for an array type + /// item_format: the innermost element's format string (kept as-is, e.g., "<i") + /// item_shape: the element's shape (will be prepended with length) + /// item_flags: the element type's flags (for HASPOINTER inheritance) + #[allow(clippy::too_many_arguments)] + pub fn new_array( + size: usize, + align: usize, + length: usize, + element_type: PyTypeRef, + element_size: usize, + item_format: Option<&str>, + item_shape: &[usize], + item_flags: StgInfoFlags, + ) -> Self { + // Format is kept from innermost element (e.g., "<i" for c_int arrays) + // The array dimensions go into shape only, not format + let format = item_format.map(|f| f.to_owned()); + + // Build shape: [length, ...item_shape] + let mut shape = vec![length]; + shape.extend_from_slice(item_shape); + + // Inherit HASPOINTER flag from element type + // if (iteminfo->flags & (TYPEFLAG_ISPOINTER | TYPEFLAG_HASPOINTER)) + // stginfo->flags |= TYPEFLAG_HASPOINTER; + let flags = if item_flags + .intersects(StgInfoFlags::TYPEFLAG_ISPOINTER | StgInfoFlags::TYPEFLAG_HASPOINTER) + { + StgInfoFlags::TYPEFLAG_HASPOINTER + } else { + StgInfoFlags::empty() + }; + + StgInfo { + initialized: true, + size, + align, + length, + proto: None, + flags, + element_type: Some(element_type), + element_size, + format, + shape, + paramfunc: ParamFunc::Array, + big_endian: cfg!(target_endian = "big"), // native endian by default + ffi_field_types: Vec::new(), + } + } + + /// Get libffi type for this StgInfo + /// Note: For very large types, returns pointer type to avoid overflow + pub fn to_ffi_type(&self) -> libffi::middle::Type { + // Limit to avoid overflow in libffi (MAX_STRUCT_SIZE is platform-dependent) + const MAX_FFI_STRUCT_SIZE: usize = 1024 * 1024; // 1MB limit for safety + + match self.paramfunc { + ParamFunc::Structure | ParamFunc::Union => { + if !self.ffi_field_types.is_empty() { + libffi::middle::Type::structure(self.ffi_field_types.iter().cloned()) + } else if self.size <= MAX_FFI_STRUCT_SIZE { + // Small struct without field types: use bytes array + libffi::middle::Type::structure(std::iter::repeat_n( + libffi::middle::Type::u8(), + self.size, + )) } else { - Err(vm.new_type_error("one character unicode string expected")) + // Large struct: treat as pointer (passed by reference) + libffi::middle::Type::pointer() } - } else { - Err(vm.new_type_error(format!( - "unicode string expected instead of {} instance", - value.class().name() - ))) - } - } - "b" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => { - if value.to_owned().downcast_exact::<PyInt>(vm).is_ok() { - Ok(value.to_owned()) - } else { - Err(vm.new_type_error(format!( - "an integer is required (got type {})", - value.class().name() - ))) } - } - "f" | "d" | "g" => { - // float allows int - if value.to_owned().downcast_exact::<PyFloat>(vm).is_ok() - || value.to_owned().downcast_exact::<PyInt>(vm).is_ok() - { - Ok(value.to_owned()) - } else { - Err(vm.new_type_error(format!("must be real number, not {}", value.class().name()))) + ParamFunc::Array => { + if self.size > MAX_FFI_STRUCT_SIZE || self.length > MAX_FFI_STRUCT_SIZE { + // Large array: treat as pointer + libffi::middle::Type::pointer() + } else if let Some(ref fmt) = self.format { + let elem_type = Self::format_to_ffi_type(fmt); + libffi::middle::Type::structure(std::iter::repeat_n(elem_type, self.length)) + } else { + libffi::middle::Type::structure(std::iter::repeat_n( + libffi::middle::Type::u8(), + self.size, + )) + } } - } - "?" => Ok(PyObjectRef::from( - vm.ctx.new_bool(value.to_owned().try_to_bool(vm)?), - )), - "B" => { - if value.to_owned().downcast_exact::<PyInt>(vm).is_ok() { - // Store as-is, conversion to unsigned happens in the getter - Ok(value.to_owned()) - } else { - Err(vm.new_type_error(format!("int expected instead of {}", value.class().name()))) + ParamFunc::Pointer => libffi::middle::Type::pointer(), + _ => { + // Simple type: derive from format + if let Some(ref fmt) = self.format { + Self::format_to_ffi_type(fmt) + } else { + libffi::middle::Type::u8() + } } } - "z" => { - if value.to_owned().downcast_exact::<PyInt>(vm).is_ok() - || value.to_owned().downcast_exact::<PyBytes>(vm).is_ok() - { - Ok(value.to_owned()) - } else { - Err(vm.new_type_error(format!( - "bytes or integer address expected instead of {} instance", - value.class().name() - ))) - } + } + + /// Convert format string to libffi type + fn format_to_ffi_type(fmt: &str) -> libffi::middle::Type { + // Strip endian prefix if present + let code = fmt.trim_start_matches(['<', '>', '!', '@', '=']); + match code { + "b" => libffi::middle::Type::i8(), + "B" => libffi::middle::Type::u8(), + "h" => libffi::middle::Type::i16(), + "H" => libffi::middle::Type::u16(), + "i" | "l" => libffi::middle::Type::i32(), + "I" | "L" => libffi::middle::Type::u32(), + "q" => libffi::middle::Type::i64(), + "Q" => libffi::middle::Type::u64(), + "f" => libffi::middle::Type::f32(), + "d" => libffi::middle::Type::f64(), + "P" | "z" | "Z" | "O" => libffi::middle::Type::pointer(), + _ => libffi::middle::Type::u8(), // default } - "Z" => { - if value.to_owned().downcast_exact::<PyStr>(vm).is_ok() { - Ok(value.to_owned()) - } else { - Err(vm.new_type_error(format!( - "unicode string or integer address expected instead of {} instance", - value.class().name() - ))) - } + } + + /// Check if this type is finalized (cannot set _fields_ again) + pub fn is_final(&self) -> bool { + self.flags.contains(StgInfoFlags::DICTFLAG_FINAL) + } + + /// Get proto type reference (for Pointer/Array types) + pub fn proto(&self) -> &Py<PyType> { + self.proto.as_deref().expect("type has proto") + } +} + +/// Get PEP3118 format string for a field type +/// Returns the format string considering byte order +pub(super) fn get_field_format( + field_type: &PyObject, + big_endian: bool, + vm: &VirtualMachine, +) -> String { + // 1. Check StgInfo for format + if let Some(type_obj) = field_type.downcast_ref::<PyType>() + && let Some(stg_info) = type_obj.stg_info_opt() + && let Some(fmt) = &stg_info.format + { + // Handle endian prefix for simple types + if fmt.len() == 1 { + let endian_prefix = if big_endian { ">" } else { "<" }; + return format!("{}{}", endian_prefix, fmt); } - _ => { - // "P" - if value.to_owned().downcast_exact::<PyInt>(vm).is_ok() - || value.to_owned().downcast_exact::<PyNone>(vm).is_ok() - { - Ok(value.to_owned()) - } else { - Err(vm.new_type_error("cannot be converted to pointer")) - } + return fmt.clone(); + } + + // 2. Try to get _type_ attribute for simple types + if let Ok(type_attr) = field_type.get_attr("_type_", vm) + && let Some(type_str) = type_attr.downcast_ref::<PyStr>() + { + let s = type_str.as_str(); + if s.len() == 1 { + let endian_prefix = if big_endian { ">" } else { "<" }; + return format!("{}{}", endian_prefix, s); } + return s.to_string(); } + + // Default: single byte + "B".to_string() } -/// Common data object for all ctypes types -#[derive(Debug, Clone)] -pub struct CDataObject { - /// pointer to memory block (b_ptr + b_size) - pub buffer: Vec<u8>, +/// Compute byte order based on swapped flag +#[inline] +pub(super) fn is_big_endian(is_swapped: bool) -> bool { + if is_swapped { + !cfg!(target_endian = "big") + } else { + cfg!(target_endian = "big") + } +} + +/// Shared BufferMethods for all ctypes types (PyCArray, PyCSimple, PyCStructure, PyCUnion) +/// All these types are #[repr(transparent)] wrappers around PyCData +pub(super) static CDATA_BUFFER_METHODS: BufferMethods = BufferMethods { + obj_bytes: |buffer| { + rustpython_common::lock::PyRwLockReadGuard::map( + buffer.obj_as::<PyCData>().buffer.read(), + |x| &**x, + ) + .into() + }, + obj_bytes_mut: |buffer| { + rustpython_common::lock::PyRwLockWriteGuard::map( + buffer.obj_as::<PyCData>().buffer.write(), + |x| x.to_mut().as_mut_slice(), + ) + .into() + }, + release: |_| {}, + retain: |_| {}, +}; + +/// Convert Vec<T> to Vec<u8> by reinterpreting the memory (same allocation). +fn vec_to_bytes<T>(vec: Vec<T>) -> Vec<u8> { + let len = vec.len() * std::mem::size_of::<T>(); + let cap = vec.capacity() * std::mem::size_of::<T>(); + let ptr = vec.as_ptr() as *mut u8; + std::mem::forget(vec); + unsafe { Vec::from_raw_parts(ptr, len, cap) } +} + +/// Ensure PyBytes is null-terminated. Returns (PyBytes to keep, pointer). +/// If already contains null, returns original. Otherwise creates new with null appended. +pub(super) fn ensure_z_null_terminated( + bytes: &PyBytes, + vm: &VirtualMachine, +) -> (PyObjectRef, usize) { + let data = bytes.as_bytes(); + if data.contains(&0) { + // Already has null, use original + let original: PyObjectRef = vm.ctx.new_bytes(data.to_vec()).into(); + (original, data.as_ptr() as usize) + } else { + // Create new with null appended + let mut buffer = data.to_vec(); + buffer.push(0); + let ptr = buffer.as_ptr() as usize; + let new_bytes: PyObjectRef = vm.ctx.new_bytes(buffer).into(); + (new_bytes, ptr) + } +} + +/// Convert str to null-terminated wchar_t buffer. Returns (PyBytes holder, pointer). +pub(super) fn str_to_wchar_bytes(s: &str, vm: &VirtualMachine) -> (PyObjectRef, usize) { + let wchars: Vec<libc::wchar_t> = s + .chars() + .map(|c| c as libc::wchar_t) + .chain(std::iter::once(0)) + .collect(); + let ptr = wchars.as_ptr() as usize; + let bytes = vec_to_bytes(wchars); + let holder: PyObjectRef = vm.ctx.new_bytes(bytes).into(); + (holder, ptr) +} + +/// PyCData - base type for all ctypes data types +#[pyclass(name = "_CData", module = "_ctypes")] +#[derive(Debug, PyPayload)] +pub struct PyCData { + /// Memory buffer - Owned (self-owned) or Borrowed (external reference) + /// + /// SAFETY: Borrowed variant's 'static lifetime is not actually static. + /// When created via from_address or from_base_obj, only valid for the lifetime of the source memory. + /// Same behavior as CPython's b_ptr (user responsibility, kept alive via b_base). + pub buffer: PyRwLock<Cow<'static, [u8]>>, /// pointer to base object or None (b_base) - #[allow(dead_code)] - pub base: Option<PyObjectRef>, + pub base: PyRwLock<Option<PyObjectRef>>, + /// byte offset within base's buffer (for field access) + pub base_offset: AtomicCell<usize>, /// index into base's b_objects list (b_index) - #[allow(dead_code)] - pub index: usize, + pub index: AtomicCell<usize>, /// dictionary of references we need to keep (b_objects) - pub objects: Option<PyObjectRef>, + pub objects: PyRwLock<Option<PyObjectRef>>, + /// number of references we need (b_length) + pub length: AtomicCell<usize>, } -impl CDataObject { +impl PyCData { /// Create from StgInfo (PyCData_MallocBuffer pattern) pub fn from_stg_info(stg_info: &StgInfo) -> Self { - CDataObject { - buffer: vec![0u8; stg_info.size], - base: None, - index: 0, - objects: None, + PyCData { + buffer: PyRwLock::new(Cow::Owned(vec![0u8; stg_info.size])), + base: PyRwLock::new(None), + base_offset: AtomicCell::new(0), + index: AtomicCell::new(0), + objects: PyRwLock::new(None), + length: AtomicCell::new(stg_info.length), } } /// Create from existing bytes (copies data) pub fn from_bytes(data: Vec<u8>, objects: Option<PyObjectRef>) -> Self { - CDataObject { - buffer: data, - base: None, - index: 0, - objects, + PyCData { + buffer: PyRwLock::new(Cow::Owned(data)), + base: PyRwLock::new(None), + base_offset: AtomicCell::new(0), + index: AtomicCell::new(0), + objects: PyRwLock::new(objects), + length: AtomicCell::new(0), } } - /// Create from base object (copies data from base's buffer at offset) - #[allow(dead_code)] - pub fn from_base( - base: PyObjectRef, - _offset: usize, - size: usize, - index: usize, + /// Create from bytes with specified length (for arrays) + pub fn from_bytes_with_length( + data: Vec<u8>, objects: Option<PyObjectRef>, + length: usize, ) -> Self { - CDataObject { - buffer: vec![0u8; size], - base: Some(base), - index, - objects, + PyCData { + buffer: PyRwLock::new(Cow::Owned(data)), + base: PyRwLock::new(None), + base_offset: AtomicCell::new(0), + index: AtomicCell::new(0), + objects: PyRwLock::new(objects), + length: AtomicCell::new(length), } } - #[inline] - pub fn size(&self) -> usize { - self.buffer.len() + /// Create from external memory address + /// + /// # Safety + /// The returned slice's 'static lifetime is a lie. + /// Actually only valid for the lifetime of the memory pointed to by ptr. + /// PyCData_AtAddress + pub unsafe fn at_address(ptr: *const u8, size: usize) -> Self { + // = PyCData_AtAddress + // SAFETY: Caller must ensure ptr is valid for the lifetime of returned PyCData + let slice: &'static [u8] = unsafe { std::slice::from_raw_parts(ptr, size) }; + PyCData { + buffer: PyRwLock::new(Cow::Borrowed(slice)), + base: PyRwLock::new(None), + base_offset: AtomicCell::new(0), + index: AtomicCell::new(0), + objects: PyRwLock::new(None), + length: AtomicCell::new(0), + } } -} - -#[pyclass(name = "_CData", module = "_ctypes")] -#[derive(Debug, PyPayload)] -pub struct PyCData { - pub cdata: PyRwLock<CDataObject>, -} -impl PyCData { - pub fn new(cdata: CDataObject) -> Self { - Self { - cdata: PyRwLock::new(cdata), + /// Create from base object with offset and data copy + /// + /// Similar to from_base_with_offset, but also stores a copy of the data. + /// This is used for arrays where we need our own buffer for the buffer protocol, + /// but still maintain the base reference for KeepRef and tracking. + pub fn from_base_with_data( + base_obj: PyObjectRef, + offset: usize, + idx: usize, + length: usize, + data: Vec<u8>, + ) -> Self { + PyCData { + buffer: PyRwLock::new(Cow::Owned(data)), // Has its own buffer copy + base: PyRwLock::new(Some(base_obj)), // But still tracks base + base_offset: AtomicCell::new(offset), // And offset for writes + index: AtomicCell::new(idx), + objects: PyRwLock::new(None), + length: AtomicCell::new(length), } } -} -#[pyclass(flags(BASETYPE))] -impl PyCData { - #[pygetset] - fn _objects(&self) -> Option<PyObjectRef> { - self.cdata.read().objects.clone() + /// Create from base object's buffer + /// + /// This creates a borrowed view into the base's buffer at the given address. + /// The base object is stored in b_base to keep the memory alive. + /// + /// # Safety + /// ptr must point into base_obj's buffer and remain valid as long as base_obj is alive. + pub unsafe fn from_base_obj( + ptr: *mut u8, + size: usize, + base_obj: PyObjectRef, + idx: usize, + ) -> Self { + // = PyCData_FromBaseObj + // SAFETY: ptr points into base_obj's buffer, kept alive via base reference + let slice: &'static [u8] = unsafe { std::slice::from_raw_parts(ptr, size) }; + PyCData { + buffer: PyRwLock::new(Cow::Borrowed(slice)), + base: PyRwLock::new(Some(base_obj)), + base_offset: AtomicCell::new(0), + index: AtomicCell::new(idx), + objects: PyRwLock::new(None), + length: AtomicCell::new(0), + } } -} -#[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)] -#[derive(Debug)] -#[repr(transparent)] -pub struct PyCSimpleType(PyType); - -#[pyclass(flags(BASETYPE), with(AsNumber))] -impl PyCSimpleType { - /// Get stg_info for a simple type by reading _type_ attribute - pub fn get_stg_info(cls: &PyTypeRef, vm: &VirtualMachine) -> StgInfo { - if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) - && let Ok(type_str) = type_attr.str(vm) - { - let tp_str = type_str.to_string(); - if tp_str.len() == 1 { - let size = super::_ctypes::get_size(&tp_str); - let align = super::_ctypes::get_align(&tp_str); - return StgInfo::new(size, align); - } + /// Create from buffer protocol object (for from_buffer method) + /// + /// Unlike from_bytes, this shares memory with the source buffer. + /// The source object is stored in objects dict to keep the buffer alive. + /// Python stores with key -1 via KeepRef(result, -1, mv). + /// + /// # Safety + /// ptr must point to valid memory that remains valid as long as source is alive. + pub unsafe fn from_buffer_shared( + ptr: *const u8, + size: usize, + length: usize, + source: PyObjectRef, + vm: &VirtualMachine, + ) -> Self { + // SAFETY: Caller must ensure ptr is valid for the lifetime of source + let slice: &'static [u8] = unsafe { std::slice::from_raw_parts(ptr, size) }; + + // Python stores the reference in a dict with key "-1" (unique_key pattern) + let objects_dict = vm.ctx.new_dict(); + objects_dict + .set_item("-1", source, vm) + .expect("Failed to store buffer reference"); + + PyCData { + buffer: PyRwLock::new(Cow::Borrowed(slice)), + base: PyRwLock::new(None), + base_offset: AtomicCell::new(0), + index: AtomicCell::new(0), + objects: PyRwLock::new(Some(objects_dict.into())), + length: AtomicCell::new(length), } - StgInfo::default() - } - #[allow(clippy::new_ret_no_self)] - #[pymethod] - fn new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult { - Ok(PyObjectRef::from( - new_simple_type(Either::B(&cls), vm)? - .into_ref_with_type(vm, cls)? - .clone(), - )) } - #[pyclassmethod] - fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // 1. If the value is already an instance of the requested type, return it - if value.fast_isinstance(&cls) { - return Ok(value); + /// Common implementation for from_buffer class method. + /// Validates buffer, creates memoryview, and returns PyCData sharing memory with source. + /// + /// CDataType_from_buffer_impl + pub fn from_buffer_impl( + cls: &Py<PyType>, + source: PyObjectRef, + offset: isize, + vm: &VirtualMachine, + ) -> PyResult<Self> { + let (size, length) = { + let stg_info = cls + .stg_info_opt() + .ok_or_else(|| vm.new_type_error("not a ctypes type"))?; + (stg_info.size, stg_info.length) + }; + + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative")); } + let offset = offset as usize; - // 2. Get the type code to determine conversion rules - let type_code = get_type_code(&cls, vm); + // Get buffer from source (this exports the buffer) + let buffer = PyBuffer::try_from_object(vm, source)?; - // 3. Handle None for pointer types (c_char_p, c_wchar_p, c_void_p) - if vm.is_none(&value) && matches!(type_code.as_deref(), Some("z") | Some("Z") | Some("P")) { - return Ok(value); + // Check if buffer is writable + if buffer.desc.readonly { + return Err(vm.new_type_error("underlying buffer is not writable")); } - // 4. Try to convert value based on type code - match type_code.as_deref() { - // Integer types: accept integers - Some("b" | "B" | "h" | "H" | "i" | "I" | "l" | "L" | "q" | "Q") => { - if value.try_int(vm).is_ok() { - let simple = new_simple_type(Either::B(&cls), vm)?; - simple.value.store(value.clone()); - return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); - } - } - // Float types: accept numbers - Some("f" | "d" | "g") => { - if value.try_float(vm).is_ok() || value.try_int(vm).is_ok() { - let simple = new_simple_type(Either::B(&cls), vm)?; - simple.value.store(value.clone()); - return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); - } - } - // c_char: 1 byte character - Some("c") => { - if let Some(bytes) = value.downcast_ref::<PyBytes>() - && bytes.len() == 1 - { - let simple = new_simple_type(Either::B(&cls), vm)?; - simple.value.store(value.clone()); - return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); - } - if let Ok(int_val) = value.try_int(vm) - && int_val.as_bigint().to_u8().is_some() - { - let simple = new_simple_type(Either::B(&cls), vm)?; - simple.value.store(value.clone()); - return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); - } - return Err(vm.new_type_error( - "one character bytes, bytearray or integer expected".to_string(), - )); - } - // c_wchar: 1 unicode character - Some("u") => { - if let Some(s) = value.downcast_ref::<PyStr>() - && s.as_str().chars().count() == 1 - { - let simple = new_simple_type(Either::B(&cls), vm)?; - simple.value.store(value.clone()); - return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); - } - return Err(vm.new_type_error("one character unicode string expected".to_string())); - } - // c_char_p: bytes pointer - Some("z") => { - if value.downcast_ref::<PyBytes>().is_some() { - let simple = new_simple_type(Either::B(&cls), vm)?; - simple.value.store(value.clone()); - return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); - } - } - // c_wchar_p: unicode pointer - Some("Z") => { - if value.downcast_ref::<PyStr>().is_some() { - let simple = new_simple_type(Either::B(&cls), vm)?; - simple.value.store(value.clone()); - return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); - } - } - // c_void_p: most flexible - accepts int, bytes, str - Some("P") => { - if value.try_int(vm).is_ok() - || value.downcast_ref::<PyBytes>().is_some() - || value.downcast_ref::<PyStr>().is_some() - { - let simple = new_simple_type(Either::B(&cls), vm)?; - simple.value.store(value.clone()); - return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); - } - } - // c_bool - Some("?") => { - let bool_val = value.is_true(vm)?; - let simple = new_simple_type(Either::B(&cls), vm)?; - simple.value.store(vm.ctx.new_bool(bool_val).into()); - return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); - } - _ => {} + // Check if buffer is C contiguous + if !buffer.desc.is_contiguous() { + return Err(vm.new_type_error("underlying buffer is not C contiguous")); } - // 5. Check for _as_parameter_ attribute - if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) { - return PyCSimpleType::from_param(cls, as_parameter, vm); + // Check if buffer is large enough + let buffer_len = buffer.desc.len; + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); } - // 6. Type-specific error messages - match type_code.as_deref() { - Some("z") => Err(vm.new_type_error(format!( - "'{}' object cannot be interpreted as ctypes.c_char_p", - value.class().name() - ))), - Some("Z") => Err(vm.new_type_error(format!( - "'{}' object cannot be interpreted as ctypes.c_wchar_p", - value.class().name() - ))), - _ => Err(vm.new_type_error("wrong type".to_string())), - } - } + // Get buffer pointer - the memory is owned by source + let ptr = { + let bytes = buffer.obj_bytes(); + bytes.as_ptr().wrapping_add(offset) + }; - #[pymethod] - fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - PyCSimple::repeat(cls, n, vm) + // Create memoryview to keep buffer exported (prevents source from being modified) + // mv = PyMemoryView_FromObject(obj); KeepRef(result, -1, mv); + let memoryview = PyMemoryView::from_buffer(buffer, vm)?; + let mv_obj = memoryview.into_pyobject(vm); + + // Create CData that shares memory with the buffer + Ok(unsafe { Self::from_buffer_shared(ptr, size, length, mv_obj, vm) }) } -} -impl AsNumber for PyCSimpleType { - fn as_number() -> &'static PyNumberMethods { - static AS_NUMBER: PyNumberMethods = PyNumberMethods { - multiply: Some(|a, b, vm| { - // a is a PyCSimpleType instance (type object like c_char) - // b is int (array size) - let cls = a - .downcast_ref::<PyType>() - .ok_or_else(|| vm.new_type_error("expected type".to_owned()))?; - let n = b - .try_index(vm)? - .as_bigint() - .to_isize() - .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; - PyCSimple::repeat(cls.to_owned(), n, vm) - }), - ..PyNumberMethods::NOT_IMPLEMENTED + /// Common implementation for from_buffer_copy class method. + /// Copies data from buffer and creates new independent instance. + /// + /// CDataType_from_buffer_copy_impl + pub fn from_buffer_copy_impl( + cls: &Py<PyType>, + source: &[u8], + offset: isize, + vm: &VirtualMachine, + ) -> PyResult<Self> { + let (size, length) = { + let stg_info = cls + .stg_info_opt() + .ok_or_else(|| vm.new_type_error("not a ctypes type"))?; + (stg_info.size, stg_info.length) }; - &AS_NUMBER - } -} -#[pyclass( - module = "_ctypes", - name = "_SimpleCData", - base = PyCData, - metaclass = "PyCSimpleType" -)] -pub struct PyCSimple { - pub _base: PyCData, - pub _type_: String, - pub value: AtomicCell<PyObjectRef>, - pub cdata: PyRwLock<CDataObject>, -} + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative")); + } + let offset = offset as usize; -impl Debug for PyCSimple { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PyCSimple") - .field("_type_", &self._type_) - .finish() + // Check if buffer is large enough + if offset + size > source.len() { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + source.len(), + offset + size + ))); + } + + // Copy bytes from buffer at offset + let data = source[offset..offset + size].to_vec(); + + Ok(Self::from_bytes_with_length(data, None, length)) } -} -fn value_to_bytes_endian( - _type_: &str, - value: &PyObject, - swapped: bool, - vm: &VirtualMachine, -) -> Vec<u8> { - // Helper macro for endian conversion - macro_rules! to_bytes { - ($val:expr) => { - if swapped { - // Use opposite endianness - #[cfg(target_endian = "little")] - { - $val.to_be_bytes().to_vec() - } - #[cfg(target_endian = "big")] - { - $val.to_le_bytes().to_vec() - } - } else { - $val.to_ne_bytes().to_vec() - } - }; + #[inline] + pub fn size(&self) -> usize { + self.buffer.read().len() } - match _type_ { - "c" => { - // c_char - single byte - if let Some(bytes) = value.downcast_ref::<PyBytes>() - && !bytes.is_empty() - { - return vec![bytes.as_bytes()[0]]; - } - if let Ok(int_val) = value.try_int(vm) - && let Some(v) = int_val.as_bigint().to_u8() - { - return vec![v]; - } - vec![0] - } - "u" => { - // c_wchar - 4 bytes (wchar_t on most platforms) - if let Ok(s) = value.str(vm) - && let Some(c) = s.as_str().chars().next() - { - return to_bytes!(c as u32); - } - vec![0; 4] + /// Check if this buffer is borrowed (external memory reference) + #[inline] + pub fn is_borrowed(&self) -> bool { + matches!(&*self.buffer.read(), Cow::Borrowed(_)) + } + + /// Write bytes at offset - handles both borrowed and owned buffers + /// + /// For borrowed buffers (from from_address), writes directly to external memory. + /// For owned buffers, writes through to_mut() as normal. + /// + /// # Safety + /// For borrowed buffers, caller must ensure the memory is writable. + pub fn write_bytes_at_offset(&self, offset: usize, bytes: &[u8]) { + let buffer = self.buffer.read(); + if offset + bytes.len() > buffer.len() { + return; // Out of bounds } - "b" => { - // c_byte - signed char (1 byte) - if let Ok(int_val) = value.try_int(vm) - && let Some(v) = int_val.as_bigint().to_i8() - { - return vec![v as u8]; + + match &*buffer { + Cow::Borrowed(slice) => { + // For borrowed memory, write directly + // SAFETY: We assume the caller knows this memory is writable + // (e.g., from from_address pointing to a ctypes buffer) + unsafe { + let ptr = slice.as_ptr() as *mut u8; + std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(offset), bytes.len()); + } } - vec![0] - } - "B" => { - // c_ubyte - unsigned char (1 byte) - if let Ok(int_val) = value.try_int(vm) - && let Some(v) = int_val.as_bigint().to_u8() - { - return vec![v]; + Cow::Owned(_) => { + // For owned memory, use to_mut() through write lock + drop(buffer); + let mut buffer = self.buffer.write(); + buffer.to_mut()[offset..offset + bytes.len()].copy_from_slice(bytes); } - vec![0] } - "h" => { - // c_short (2 bytes) - if let Ok(int_val) = value.try_int(vm) - && let Some(v) = int_val.as_bigint().to_i16() - { - return to_bytes!(v); - } - vec![0; 2] + } + + /// Generate unique key for nested references (unique_key) + /// Creates a hierarchical key by walking up the b_base chain. + /// Format: "index:parent_index:grandparent_index:..." + pub fn unique_key(&self, index: usize) -> String { + let mut key = format!("{index:x}"); + // Walk up the base chain to build hierarchical key + if self.base.read().is_some() { + let parent_index = self.index.load(); + key.push_str(&format!(":{parent_index:x}")); } - "H" => { - // c_ushort (2 bytes) - if let Ok(int_val) = value.try_int(vm) - && let Some(v) = int_val.as_bigint().to_u16() - { - return to_bytes!(v); - } - vec![0; 2] + key + } + + /// Keep a reference in the objects dictionary (KeepRef) + /// + /// Stores 'keep' in this object's b_objects dict at key 'index'. + /// If keep is None, does nothing (optimization). + /// This function stores the value directly - caller should use get_kept_objects() + /// first if they want to store the _objects of a CData instead of the object itself. + /// + /// If this object has a base (is embedded in another structure/union/array), + /// the reference is stored in the root object's b_objects with a hierarchical key. + pub fn keep_ref(&self, index: usize, keep: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // Optimization: no need to store None + if vm.is_none(&keep) { + return Ok(()); } - "i" => { - // c_int (4 bytes) - if let Ok(int_val) = value.try_int(vm) - && let Some(v) = int_val.as_bigint().to_i32() - { - return to_bytes!(v); - } - vec![0; 4] + + // Build hierarchical key + let key = self.unique_key(index); + + // If we have a base object, find root and store there + if let Some(base_obj) = self.base.read().clone() { + // Find root by walking up the base chain + let root_obj = Self::find_root_object(&base_obj); + Self::store_in_object(&root_obj, &key, keep, vm)?; + return Ok(()); } - "I" => { - // c_uint (4 bytes) - if let Ok(int_val) = value.try_int(vm) - && let Some(v) = int_val.as_bigint().to_u32() - { - return to_bytes!(v); + + // No base - store in own objects dict + let mut objects = self.objects.write(); + + // Initialize b_objects if needed + if objects.is_none() { + if self.length.load() > 0 { + // Need to store multiple references - create a dict + *objects = Some(vm.ctx.new_dict().into()); + } else { + // Only one reference needed - store directly + *objects = Some(keep); + return Ok(()); } - vec![0; 4] } - "l" => { - // c_long (platform dependent) - if let Ok(int_val) = value.try_to_value::<libc::c_long>(vm) { - return to_bytes!(int_val); - } - const SIZE: usize = std::mem::size_of::<libc::c_long>(); - vec![0; SIZE] + + // If b_objects is not a dict, convert it to a dict first + // This preserves the existing reference (e.g., from cast) when adding new references + if let Some(obj) = objects.as_ref() + && obj.downcast_ref::<PyDict>().is_none() + { + // Convert existing single reference to a dict + let dict = vm.ctx.new_dict(); + // Store the original object with a special key (id-based) + let id_key: PyObjectRef = vm.ctx.new_int(obj.get_id() as i64).into(); + dict.set_item(&*id_key, obj.clone(), vm)?; + *objects = Some(dict.into()); } - "L" => { - // c_ulong (platform dependent) - if let Ok(int_val) = value.try_to_value::<libc::c_ulong>(vm) { - return to_bytes!(int_val); - } - const SIZE: usize = std::mem::size_of::<libc::c_ulong>(); - vec![0; SIZE] + + // Store in dict with unique key + if let Some(dict_obj) = objects.as_ref() + && let Some(dict) = dict_obj.downcast_ref::<PyDict>() + { + let key_obj: PyObjectRef = vm.ctx.new_str(key).into(); + dict.set_item(&*key_obj, keep, vm)?; } - "q" => { - // c_longlong (8 bytes) - if let Ok(int_val) = value.try_int(vm) - && let Some(v) = int_val.as_bigint().to_i64() - { - return to_bytes!(v); - } - vec![0; 8] + + Ok(()) + } + + /// Find the root object (one without a base) by walking up the base chain + fn find_root_object(obj: &PyObject) -> PyObjectRef { + // Try to get base from different ctypes types + let base = if let Some(cdata) = obj.downcast_ref::<PyCData>() { + cdata.base.read().clone() + } else { + None + }; + + // Recurse if there's a base, otherwise this is the root + if let Some(base_obj) = base { + Self::find_root_object(&base_obj) + } else { + obj.to_owned() } - "Q" => { - // c_ulonglong (8 bytes) - if let Ok(int_val) = value.try_int(vm) - && let Some(v) = int_val.as_bigint().to_u64() - { - return to_bytes!(v); - } - vec![0; 8] - } - "f" => { - // c_float (4 bytes) - int도 허용 - if let Ok(float_val) = value.try_float(vm) { - return to_bytes!(float_val.to_f64() as f32); - } - if let Ok(int_val) = value.try_int(vm) - && let Some(v) = int_val.as_bigint().to_f64() - { - return to_bytes!(v as f32); - } - vec![0; 4] - } - "d" | "g" => { - // c_double (8 bytes) - int도 허용 - if let Ok(float_val) = value.try_float(vm) { - return to_bytes!(float_val.to_f64()); - } - if let Ok(int_val) = value.try_int(vm) - && let Some(v) = int_val.as_bigint().to_f64() - { - return to_bytes!(v); - } - vec![0; 8] - } - "?" => { - // c_bool (1 byte) - if let Ok(b) = value.to_owned().try_to_bool(vm) { - return vec![if b { 1 } else { 0 }]; - } - vec![0] - } - "P" | "z" | "Z" => { - // Pointer types (platform pointer size) - vec![0; std::mem::size_of::<usize>()] - } - _ => vec![0], } -} -impl Constructor for PyCSimple { - type Args = (OptionalArg,); - - fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - let args: Self::Args = args.bind(vm)?; - let attributes = cls.get_attributes(); - let _type_ = attributes - .iter() - .find(|(k, _)| { - k.to_object() - .str(vm) - .map(|s| s.to_string() == "_type_") - .unwrap_or(false) - }) - .ok_or_else(|| { - vm.new_type_error(format!( - "cannot create '{}' instances: no _type_ attribute", - cls.name() - )) - })? - .1 - .str(vm)? - .to_string(); - let value = if let Some(ref v) = args.0.into_option() { - set_primitive(_type_.as_str(), v, vm)? + /// Store a value in an object's _objects dict with the given key + fn store_in_object( + obj: &PyObject, + key: &str, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Get the objects dict from the object + let objects_lock = if let Some(cdata) = obj.downcast_ref::<PyCData>() { + &cdata.objects } else { - match _type_.as_str() { - "c" | "u" => PyObjectRef::from(vm.ctx.new_bytes(vec![0])), - "b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => { - PyObjectRef::from(vm.ctx.new_int(0)) - } - "f" | "d" | "g" => PyObjectRef::from(vm.ctx.new_float(0.0)), - "?" => PyObjectRef::from(vm.ctx.new_bool(false)), - _ => vm.ctx.none(), // "z" | "Z" | "P" - } + return Ok(()); // Unknown type, skip }; - // Check if this is a swapped endian type - let swapped = cls - .as_object() - .get_attr("_swappedbytes_", vm) - .map(|v| v.is_true(vm).unwrap_or(false)) - .unwrap_or(false); + let mut objects = objects_lock.write(); - let buffer = value_to_bytes_endian(&_type_, &value, swapped, vm); - let cdata = CDataObject::from_bytes(buffer, None); - PyCSimple { - _base: PyCData::new(cdata.clone()), - _type_, - value: AtomicCell::new(value), - cdata: PyRwLock::new(cdata), + // Initialize if needed + if objects.is_none() { + *objects = Some(vm.ctx.new_dict().into()); } - .into_ref_with_type(vm, cls) - .map(Into::into) - } - fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unimplemented!("use slot_new") - } -} + // If not a dict, convert to dict + if let Some(obj) = objects.as_ref() + && obj.downcast_ref::<PyDict>().is_none() + { + let dict = vm.ctx.new_dict(); + let id_key: PyObjectRef = vm.ctx.new_int(obj.get_id() as i64).into(); + dict.set_item(&*id_key, obj.clone(), vm)?; + *objects = Some(dict.into()); + } -#[pyclass(flags(BASETYPE), with(Constructor, AsBuffer))] -impl PyCSimple { - #[pygetset] - fn _objects(&self) -> Option<PyObjectRef> { - self.cdata.read().objects.clone() - } - - #[pygetset(name = "value")] - pub fn value(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> { - let zelf: &Py<Self> = instance - .downcast_ref() - .ok_or_else(|| vm.new_type_error("cannot get value of instance"))?; - let raw_value = unsafe { (*zelf.value.as_ptr()).clone() }; - - // Convert to unsigned if needed for unsigned types - match zelf._type_.as_str() { - "B" | "H" | "I" | "L" | "Q" => { - if let Ok(int_val) = raw_value.try_int(vm) { - let n = int_val.as_bigint(); - // Use platform-specific C types for correct unsigned conversion - match zelf._type_.as_str() { - "B" => { - if let Some(v) = n.to_i64() { - return Ok(vm.ctx.new_int((v as u8) as u64).into()); - } - } - "H" => { - if let Some(v) = n.to_i64() { - return Ok(vm.ctx.new_int((v as c_ushort) as u64).into()); - } - } - "I" => { - if let Some(v) = n.to_i64() { - return Ok(vm.ctx.new_int((v as c_uint) as u64).into()); - } - } - "L" => { - if let Some(v) = n.to_i128() { - return Ok(vm.ctx.new_int(v as c_ulong).into()); - } - } - "Q" => { - if let Some(v) = n.to_i128() { - return Ok(vm.ctx.new_int(v as c_ulonglong).into()); - } - } - _ => {} - }; - } - Ok(raw_value) - } - _ => Ok(raw_value), + // Store in dict + if let Some(dict_obj) = objects.as_ref() + && let Some(dict) = dict_obj.downcast_ref::<PyDict>() + { + let key_obj: PyObjectRef = vm.ctx.new_str(key).into(); + dict.set_item(&*key_obj, value, vm)?; } + + Ok(()) } - #[pygetset(name = "value", setter)] - fn set_value(instance: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - let zelf: PyRef<Self> = instance - .clone() - .downcast() - .map_err(|_| vm.new_type_error("cannot set value of instance"))?; - let content = set_primitive(zelf._type_.as_str(), &value, vm)?; + /// Get kept objects from a CData instance + /// Returns the _objects of the CData, or an empty dict if None. + pub fn get_kept_objects(value: &PyObject, vm: &VirtualMachine) -> PyObjectRef { + value + .downcast_ref::<PyCData>() + .and_then(|cdata| cdata.objects.read().clone()) + .unwrap_or_else(|| vm.ctx.new_dict().into()) + } - // Check if this is a swapped endian type - let swapped = instance - .class() - .as_object() - .get_attr("_swappedbytes_", vm) - .map(|v| v.is_true(vm).unwrap_or(false)) - .unwrap_or(false); - - // Update buffer when value changes - let buffer_bytes = value_to_bytes_endian(&zelf._type_, &content, swapped, vm); - zelf.cdata.write().buffer = buffer_bytes; - zelf.value.store(content); - Ok(()) + /// Check if a value should be stored in _objects + /// Returns true for ctypes objects and bytes (for c_char_p) + pub fn should_keep_ref(value: &PyObject) -> bool { + value.downcast_ref::<PyCData>().is_some() || value.downcast_ref::<PyBytes>().is_some() } - #[pyclassmethod] - fn repeat(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - use super::_ctypes::get_size; - use super::array::create_array_type_with_stg_info; - - if n < 0 { - return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); - } - // Get element size from cls - let element_size = if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) { - if let Ok(s) = type_attr.str(vm) { - let s = s.to_string(); - if s.len() == 1 { - get_size(&s) - } else { - std::mem::size_of::<usize>() - } + /// PyCData_set + /// Sets a field value at the given offset, handling type conversion and KeepRef + #[allow(clippy::too_many_arguments)] + pub fn set_field( + &self, + proto: &PyObject, + value: PyObjectRef, + index: usize, + size: usize, + offset: usize, + needs_swap: bool, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Check if this is a c_char or c_wchar array field + let is_char_array = PyCField::is_char_array(proto, vm); + let is_wchar_array = PyCField::is_wchar_array(proto, vm); + + // For c_char arrays with bytes input, copy only up to first null + if is_char_array { + if let Some(bytes_val) = value.downcast_ref::<PyBytes>() { + let src = bytes_val.as_bytes(); + let to_copy = PyCField::bytes_for_char_array(src); + let copy_len = std::cmp::min(to_copy.len(), size); + self.write_bytes_at_offset(offset, &to_copy[..copy_len]); + self.keep_ref(index, value, vm)?; + return Ok(()); } else { - std::mem::size_of::<usize>() + return Err(vm.new_type_error("bytes expected instead of str instance")); } - } else { - std::mem::size_of::<usize>() - }; - let total_size = element_size * (n as usize); - let stg_info = super::util::StgInfo::new_array( - total_size, - element_size, - n as usize, - cls.clone().into(), - element_size, - ); - create_array_type_with_stg_info(stg_info, vm) - } + } - #[pyclassmethod] - fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { - use super::_ctypes::get_size; - // Get _type_ attribute directly - let type_attr = cls - .as_object() - .get_attr("_type_", vm) - .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; - let type_str = type_attr.str(vm)?.to_string(); - let size = get_size(&type_str); - - // Create instance with value read from address - let value = if address != 0 && size > 0 { - // Safety: This is inherently unsafe - reading from arbitrary memory address - unsafe { - let ptr = address as *const u8; - let bytes = std::slice::from_raw_parts(ptr, size); - // Convert bytes to appropriate Python value based on type - bytes_to_pyobject(&cls, bytes, vm)? + // For c_wchar arrays with str input, convert to wchar_t + if is_wchar_array { + if let Some(str_val) = value.downcast_ref::<PyStr>() { + // Convert str to wchar_t bytes (platform-dependent size) + let mut wchar_bytes = Vec::with_capacity(size); + for ch in str_val.as_str().chars().take(size / WCHAR_SIZE) { + let mut bytes = [0u8; 4]; + wchar_to_bytes(ch as u32, &mut bytes); + wchar_bytes.extend_from_slice(&bytes[..WCHAR_SIZE]); + } + // Pad with nulls to fill the array + while wchar_bytes.len() < size { + wchar_bytes.push(0); + } + self.write_bytes_at_offset(offset, &wchar_bytes); + self.keep_ref(index, value, vm)?; + return Ok(()); + } else if value.downcast_ref::<PyBytes>().is_some() { + return Err(vm.new_type_error("str expected instead of bytes instance")); } + } + + // Special handling for Pointer fields with Array values + if let Some(proto_type) = proto.downcast_ref::<PyType>() + && proto_type + .class() + .fast_issubclass(super::pointer::PyCPointerType::static_type()) + && let Some(array) = value.downcast_ref::<super::array::PyCArray>() + { + let buffer_addr = { + let array_buffer = array.0.buffer.read(); + array_buffer.as_ptr() as usize + }; + let addr_bytes = buffer_addr.to_ne_bytes(); + let len = std::cmp::min(addr_bytes.len(), size); + self.write_bytes_at_offset(offset, &addr_bytes[..len]); + self.keep_ref(index, value, vm)?; + return Ok(()); + } + + // Get field type code for special handling + let field_type_code = proto + .get_attr("_type_", vm) + .ok() + .and_then(|attr| attr.downcast_ref::<PyStr>().map(|s| s.to_string())); + + let (mut bytes, converted_value) = if let Some(type_code) = &field_type_code { + PyCField::value_to_bytes_for_type(type_code, &value, size, vm)? } else { - vm.ctx.none() + (PyCField::value_to_bytes(&value, size, vm)?, None) }; - // Create instance using the type's constructor - let args = FuncArgs::new(vec![value], KwArgs::default()); - PyCSimple::slot_new(cls.clone(), args, vm) + // Swap bytes for opposite endianness + if needs_swap { + bytes.reverse(); + } + + self.write_bytes_at_offset(offset, &bytes); + + // KeepRef: for z/Z types use converted value, otherwise use original + if let Some(converted) = converted_value { + self.keep_ref(index, converted, vm)?; + } else if Self::should_keep_ref(&value) { + let to_keep = Self::get_kept_objects(&value, vm); + self.keep_ref(index, to_keep, vm)?; + } + + Ok(()) } - #[pyclassmethod] - fn from_buffer( - cls: PyTypeRef, - source: PyObjectRef, - offset: OptionalArg<isize>, + /// PyCData_get + /// Gets a field value at the given offset + pub fn get_field( + &self, + proto: &PyObject, + index: usize, + size: usize, + offset: usize, + base_obj: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - use super::_ctypes::get_size; - let offset = offset.unwrap_or(0); - if offset < 0 { - return Err(vm.new_value_error("offset cannot be negative".to_owned())); + // Get buffer data at offset + let buffer = self.buffer.read(); + if offset + size > buffer.len() { + return Ok(vm.ctx.new_int(0).into()); } - let offset = offset as usize; - // Get buffer from source - let buffer = PyBuffer::try_from_object(vm, source.clone())?; + // Check if field type is an array type + if let Some(type_ref) = proto.downcast_ref::<PyType>() + && let Some(stg) = type_ref.stg_info_opt() + && stg.element_type.is_some() + { + // c_char array → return bytes + if PyCField::is_char_array(proto, vm) { + let data = &buffer[offset..offset + size]; + // Find first null terminator (or use full length) + let end = data.iter().position(|&b| b == 0).unwrap_or(data.len()); + return Ok(vm.ctx.new_bytes(data[..end].to_vec()).into()); + } - // Check if buffer is writable - if buffer.desc.readonly { - return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + // c_wchar array → return str + if PyCField::is_wchar_array(proto, vm) { + let data = &buffer[offset..offset + size]; + // wchar_t → char conversion, skip null + let chars: String = data + .chunks(WCHAR_SIZE) + .filter_map(|chunk| { + wchar_from_bytes(chunk) + .filter(|&wchar| wchar != 0) + .and_then(char::from_u32) + }) + .collect(); + return Ok(vm.ctx.new_str(chars).into()); + } + + // Other array types - create array with a copy of data from the base's buffer + // The array also keeps a reference to the base for keeping it alive and for writes + let array_data = buffer[offset..offset + size].to_vec(); + drop(buffer); + + let cdata_obj = + Self::from_base_with_data(base_obj, offset, index, stg.length, array_data); + let array_type: PyTypeRef = proto + .to_owned() + .downcast() + .map_err(|_| vm.new_type_error("expected array type"))?; + + return super::array::PyCArray(cdata_obj) + .into_ref_with_type(vm, array_type) + .map(Into::into); } - // Get _type_ attribute directly - let type_attr = cls - .as_object() - .get_attr("_type_", vm) - .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; - let type_str = type_attr.str(vm)?.to_string(); - let size = get_size(&type_str); + let buffer_data = buffer[offset..offset + size].to_vec(); + drop(buffer); - // Check if buffer is large enough - let buffer_len = buffer.desc.len; - if offset + size > buffer_len { - return Err(vm.new_value_error(format!( - "Buffer size too small ({} instead of at least {} bytes)", - buffer_len, - offset + size - ))); + // Get proto as type + let proto_type: PyTypeRef = proto + .to_owned() + .downcast() + .map_err(|_| vm.new_type_error("field proto is not a type"))?; + + let proto_metaclass = proto_type.class(); + + // Simple types: return primitive value + if proto_metaclass.fast_issubclass(super::simple::PyCSimpleType::static_type()) { + // Check for byte swapping + let needs_swap = base_obj + .class() + .as_object() + .get_attr("_swappedbytes_", vm) + .is_ok() + || proto_type + .as_object() + .get_attr("_swappedbytes_", vm) + .is_ok(); + + let data = if needs_swap && size > 1 { + let mut swapped = buffer_data.clone(); + swapped.reverse(); + swapped + } else { + buffer_data + }; + + return bytes_to_pyobject(&proto_type, &data, vm); } - // Read bytes from buffer at offset - let bytes = buffer.obj_bytes(); - let data = &bytes[offset..offset + size]; - let value = bytes_to_pyobject(&cls, data, vm)?; + // Complex types: create ctypes instance via PyCData_FromBaseObj + let ptr = self.buffer.read().as_ptr().wrapping_add(offset) as *mut u8; + let cdata_obj = unsafe { Self::from_base_obj(ptr, size, base_obj.clone(), index) }; - // Create instance - let args = FuncArgs::new(vec![value], KwArgs::default()); - let instance = PyCSimple::slot_new(cls.clone(), args, vm)?; + if proto_metaclass.fast_issubclass(super::structure::PyCStructType::static_type()) + || proto_metaclass.fast_issubclass(super::union::PyCUnionType::static_type()) + || proto_metaclass.fast_issubclass(super::pointer::PyCPointerType::static_type()) + { + cdata_obj.into_ref_with_type(vm, proto_type).map(Into::into) + } else { + // Fallback + Ok(vm.ctx.new_int(0).into()) + } + } +} - // TODO: Store reference to source in _objects to keep buffer alive - Ok(instance) +#[pyclass(flags(BASETYPE))] +impl PyCData { + #[pygetset] + fn _objects(&self) -> Option<PyObjectRef> { + self.objects.read().clone() + } + + #[pygetset] + fn _b_base_(&self) -> Option<PyObjectRef> { + self.base.read().clone() + } + + #[pygetset] + fn _b_needsfree_(&self) -> i32 { + // Borrowed (from_address) or has base object → 0 (don't free) + // Owned and no base → 1 (need to free) + if self.is_borrowed() || self.base.read().is_some() { + 0 + } else { + 1 + } + } + + // CDataType_methods - shared across all ctypes types + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + source: PyObjectRef, + offset: OptionalArg<isize>, + vm: &VirtualMachine, + ) -> PyResult { + let cdata = Self::from_buffer_impl(&cls, source, offset.unwrap_or(0), vm)?; + cdata.into_ref_with_type(vm, cls).map(Into::into) } #[pyclassmethod] @@ -875,191 +1117,1237 @@ impl PyCSimple { offset: OptionalArg<isize>, vm: &VirtualMachine, ) -> PyResult { - use super::_ctypes::get_size; - let offset = offset.unwrap_or(0); - if offset < 0 { - return Err(vm.new_value_error("offset cannot be negative".to_owned())); - } - let offset = offset as usize; - - // Get _type_ attribute directly for simple types - let type_attr = cls - .as_object() - .get_attr("_type_", vm) - .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; - let type_str = type_attr.str(vm)?.to_string(); - let size = get_size(&type_str); + let cdata = + Self::from_buffer_copy_impl(&cls, &source.borrow_buf(), offset.unwrap_or(0), vm)?; + cdata.into_ref_with_type(vm, cls).map(Into::into) + } - // Borrow bytes from source - let source_bytes = source.borrow_buf(); - let buffer_len = source_bytes.len(); + #[pyclassmethod] + fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { + let size = { + let stg_info = cls.stg_info(vm)?; + stg_info.size + }; - // Check if buffer is large enough - if offset + size > buffer_len { - return Err(vm.new_value_error(format!( - "Buffer size too small ({} instead of at least {} bytes)", - buffer_len, - offset + size - ))); + if size == 0 { + return Err(vm.new_type_error("abstract class")); } - // Copy bytes from buffer at offset - let data = &source_bytes[offset..offset + size]; - let value = bytes_to_pyobject(&cls, data, vm)?; - - // Create instance (independent copy, no reference tracking) - let args = FuncArgs::new(vec![value], KwArgs::default()); - PyCSimple::slot_new(cls.clone(), args, vm) + // PyCData_AtAddress + let cdata = unsafe { Self::at_address(address as *const u8, size) }; + cdata.into_ref_with_type(vm, cls).map(Into::into) } #[pyclassmethod] - fn in_dll(cls: PyTypeRef, dll: PyObjectRef, name: PyStrRef, vm: &VirtualMachine) -> PyResult { - use super::_ctypes::get_size; - use libloading::Symbol; + fn in_dll( + cls: PyTypeRef, + dll: PyObjectRef, + name: crate::builtins::PyStrRef, + vm: &VirtualMachine, + ) -> PyResult { + let size = { + let stg_info = cls.stg_info(vm)?; + stg_info.size + }; + + if size == 0 { + return Err(vm.new_type_error("abstract class")); + } // Get the library handle from dll object let handle = if let Ok(int_handle) = dll.try_int(vm) { - // dll is an integer handle int_handle .as_bigint() .to_usize() - .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + .ok_or_else(|| vm.new_value_error("Invalid library handle"))? } else { - // dll is a CDLL/PyDLL/WinDLL object with _handle attribute dll.get_attr("_handle", vm)? .try_int(vm)? .as_bigint() .to_usize() - .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + .ok_or_else(|| vm.new_value_error("Invalid library handle"))? }; - // Get the library from cache - let library_cache = crate::stdlib::ctypes::library::libcache().read(); - let library = library_cache - .get_lib(handle) - .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; - - // Get symbol address from library - let symbol_name = format!("{}\0", name.as_str()); - let inner_lib = library.lib.lock(); - - let symbol_address = if let Some(lib) = &*inner_lib { - unsafe { - // Try to get the symbol from the library - let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { - vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) - })?; - *symbol as usize + // Get symbol address using platform-specific API + let symbol_name = std::ffi::CString::new(name.as_str()) + .map_err(|_| vm.new_value_error("Invalid symbol name"))?; + + #[cfg(windows)] + let ptr: *const u8 = unsafe { + match windows_sys::Win32::System::LibraryLoader::GetProcAddress( + handle as windows_sys::Win32::Foundation::HMODULE, + symbol_name.as_ptr() as *const u8, + ) { + Some(p) => p as *const u8, + None => std::ptr::null(), } + }; + + #[cfg(not(windows))] + let ptr: *const u8 = + unsafe { libc::dlsym(handle as *mut libc::c_void, symbol_name.as_ptr()) as *const u8 }; + + if ptr.is_null() { + return Err( + vm.new_value_error(format!("symbol '{}' not found in library", name.as_str())) + ); + } + + // PyCData_AtAddress + let cdata = unsafe { Self::at_address(ptr, size) }; + cdata.into_ref_with_type(vm, cls).map(Into::into) + } +} + +// PyCField - Field descriptor for Structure/Union types + +/// CField descriptor for Structure/Union field access +#[pyclass(name = "CField", module = "_ctypes")] +#[derive(Debug, PyPayload)] +pub struct PyCField { + /// Byte offset of the field within the structure/union + pub(crate) offset: isize, + /// Encoded size: for bitfields (bit_size << 16) | bit_offset, otherwise byte size + pub(crate) size: isize, + /// Index into PyCData's object array + pub(crate) index: usize, + /// The ctypes type for this field + pub(crate) proto: PyTypeRef, + /// Flag indicating if the field is anonymous (MakeAnonFields sets this) + pub(crate) anonymous: bool, +} + +#[inline(always)] +const fn num_bits(size: isize) -> isize { + size >> 16 +} + +#[inline(always)] +const fn field_size(size: isize) -> isize { + size & 0xFFFF +} + +#[inline(always)] +const fn is_bitfield(size: isize) -> bool { + (size >> 16) != 0 +} + +impl PyCField { + /// Create a new CField descriptor (non-bitfield) + pub fn new(proto: PyTypeRef, offset: isize, size: isize, index: usize) -> Self { + Self { + offset, + size, + index, + proto, + anonymous: false, + } + } + + /// Create a new CField descriptor for a bitfield + #[allow(dead_code)] + pub fn new_bitfield( + proto: PyTypeRef, + offset: isize, + bit_size: u16, + bit_offset: u16, + index: usize, + ) -> Self { + let encoded_size = ((bit_size as isize) << 16) | (bit_offset as isize); + Self { + offset, + size: encoded_size, + index, + proto, + anonymous: false, + } + } + + /// Get the actual byte size (for non-bitfields) or bit storage size (for bitfields) + pub fn byte_size(&self) -> usize { + field_size(self.size) as usize + } + + /// Create a new CField from an existing field with adjusted offset and index + /// Used by MakeFields to promote anonymous fields + pub fn new_from_field(fdescr: &PyCField, index_offset: usize, offset_delta: isize) -> Self { + Self { + offset: fdescr.offset + offset_delta, + size: fdescr.size, + index: fdescr.index + index_offset, + proto: fdescr.proto.clone(), + anonymous: false, // promoted fields are not anonymous themselves + } + } + + /// Set anonymous flag + pub fn set_anonymous(&mut self, anonymous: bool) { + self.anonymous = anonymous; + } +} + +impl Representable for PyCField { + fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { + // Get type name from proto (which is always PyTypeRef) + let tp_name = zelf.proto.name().to_string(); + + // Bitfield: <Field type=TYPE, ofs=OFFSET:BIT_OFFSET, bits=NUM_BITS> + // Regular: <Field type=TYPE, ofs=OFFSET, size=SIZE> + if is_bitfield(zelf.size) { + let bit_offset = field_size(zelf.size); + let bits = num_bits(zelf.size); + Ok(format!( + "<Field type={}, ofs={}:{}, bits={}>", + tp_name, zelf.offset, bit_offset, bits + )) } else { - return Err(vm.new_attribute_error("Library is closed".to_owned())); + Ok(format!( + "<Field type={}, ofs={}, size={}>", + tp_name, zelf.offset, zelf.size + )) + } + } +} + +/// PyCField_get +impl GetDescriptor for PyCField { + fn descr_get( + zelf: PyObjectRef, + obj: Option<PyObjectRef>, + _cls: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult { + let zelf = zelf + .downcast::<PyCField>() + .map_err(|_| vm.new_type_error("expected CField"))?; + + // If obj is None, return the descriptor itself (class attribute access) + let obj = match obj { + Some(obj) if !vm.is_none(&obj) => obj, + _ => return Ok(zelf.into()), }; - // Get _type_ attribute and size - let type_attr = cls - .as_object() - .get_attr("_type_", vm) - .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; - let type_str = type_attr.str(vm)?.to_string(); - let size = get_size(&type_str); - - // Read value from symbol address - let value = if symbol_address != 0 && size > 0 { - // Safety: Reading from a symbol address provided by dlsym - unsafe { - let ptr = symbol_address as *const u8; - let bytes = std::slice::from_raw_parts(ptr, size); - bytes_to_pyobject(&cls, bytes, vm)? + let offset = zelf.offset as usize; + let size = zelf.byte_size(); + + // Get PyCData from obj (works for both Structure and Union) + let cdata = PyCField::get_cdata_from_obj(&obj, vm)?; + + // PyCData_get + cdata.get_field( + zelf.proto.as_object(), + zelf.index, + size, + offset, + obj.clone(), + vm, + ) + } +} + +impl PyCField { + /// Convert a Python value to bytes + fn value_to_bytes(value: &PyObject, size: usize, vm: &VirtualMachine) -> PyResult<Vec<u8>> { + // 1. Handle bytes objects + if let Some(bytes) = value.downcast_ref::<PyBytes>() { + let src = bytes.as_bytes(); + let mut result = vec![0u8; size]; + let len = std::cmp::min(src.len(), size); + result[..len].copy_from_slice(&src[..len]); + Ok(result) + } + // 2. Handle ctypes array instances (copy their buffer) + else if let Some(cdata) = value.downcast_ref::<super::PyCData>() { + let buffer = cdata.buffer.read(); + let mut result = vec![0u8; size]; + let len = std::cmp::min(buffer.len(), size); + result[..len].copy_from_slice(&buffer[..len]); + Ok(result) + } + // 4. Handle float values (check before int, since float.try_int would truncate) + else if let Some(float_val) = value.downcast_ref::<crate::builtins::PyFloat>() { + let f = float_val.to_f64(); + match size { + 4 => { + let val = f as f32; + Ok(val.to_ne_bytes().to_vec()) + } + 8 => Ok(f.to_ne_bytes().to_vec()), + _ => unreachable!("wrong payload size"), + } + } + // 4. Handle integer values + else if let Ok(int_val) = value.try_int(vm) { + let i = int_val.as_bigint(); + match size { + 1 => { + let val = i.to_i8().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 2 => { + let val = i.to_i16().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 4 => { + let val = i.to_i32().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 8 => { + let val = i.to_i64().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + _ => Ok(vec![0u8; size]), } } else { - vm.ctx.none() - }; + Ok(vec![0u8; size]) + } + } - // Create instance - let args = FuncArgs::new(vec![value], KwArgs::default()); - let instance = PyCSimple::slot_new(cls.clone(), args, vm)?; + /// Convert a Python value to bytes with type-specific handling for pointer types. + /// Returns (bytes, optional holder for wchar buffer). + fn value_to_bytes_for_type( + type_code: &str, + value: &PyObject, + size: usize, + vm: &VirtualMachine, + ) -> PyResult<(Vec<u8>, Option<PyObjectRef>)> { + match type_code { + // c_float: always convert to float first (f_set) + "f" => { + let f = if let Some(float_val) = value.downcast_ref::<crate::builtins::PyFloat>() { + float_val.to_f64() + } else if let Ok(int_val) = value.try_int(vm) { + int_val.as_bigint().to_i64().unwrap_or(0) as f64 + } else { + return Err(vm.new_type_error(format!( + "float expected instead of {}", + value.class().name() + ))); + }; + let val = f as f32; + Ok((val.to_ne_bytes().to_vec(), None)) + } + // c_double: always convert to float first (d_set) + "d" => { + let f = if let Some(float_val) = value.downcast_ref::<crate::builtins::PyFloat>() { + float_val.to_f64() + } else if let Ok(int_val) = value.try_int(vm) { + int_val.as_bigint().to_i64().unwrap_or(0) as f64 + } else { + return Err(vm.new_type_error(format!( + "float expected instead of {}", + value.class().name() + ))); + }; + Ok((f.to_ne_bytes().to_vec(), None)) + } + // c_longdouble: convert to float (treated as f64 in RustPython) + "g" => { + let f = if let Some(float_val) = value.downcast_ref::<crate::builtins::PyFloat>() { + float_val.to_f64() + } else if let Ok(int_val) = value.try_int(vm) { + int_val.as_bigint().to_i64().unwrap_or(0) as f64 + } else { + return Err(vm.new_type_error(format!( + "float expected instead of {}", + value.class().name() + ))); + }; + Ok((f.to_ne_bytes().to_vec(), None)) + } + "z" => { + // c_char_p: store pointer to null-terminated bytes + if let Some(bytes) = value.downcast_ref::<PyBytes>() { + let (converted, ptr) = ensure_z_null_terminated(bytes, vm); + let mut result = vec![0u8; size]; + let addr_bytes = ptr.to_ne_bytes(); + let len = std::cmp::min(addr_bytes.len(), size); + result[..len].copy_from_slice(&addr_bytes[..len]); + return Ok((result, Some(converted))); + } + // Integer address + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_usize().unwrap_or(0); + let mut result = vec![0u8; size]; + let bytes = v.to_ne_bytes(); + let len = std::cmp::min(bytes.len(), size); + result[..len].copy_from_slice(&bytes[..len]); + return Ok((result, None)); + } + // None -> NULL pointer + if vm.is_none(value) { + return Ok((vec![0u8; size], None)); + } + Ok((PyCField::value_to_bytes(value, size, vm)?, None)) + } + "Z" => { + // c_wchar_p: store pointer to null-terminated wchar_t buffer + if let Some(s) = value.downcast_ref::<PyStr>() { + let (holder, ptr) = str_to_wchar_bytes(s.as_str(), vm); + let mut result = vec![0u8; size]; + let addr_bytes = ptr.to_ne_bytes(); + let len = std::cmp::min(addr_bytes.len(), size); + result[..len].copy_from_slice(&addr_bytes[..len]); + return Ok((result, Some(holder))); + } + // Integer address + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_usize().unwrap_or(0); + let mut result = vec![0u8; size]; + let bytes = v.to_ne_bytes(); + let len = std::cmp::min(bytes.len(), size); + result[..len].copy_from_slice(&bytes[..len]); + return Ok((result, None)); + } + // None -> NULL pointer + if vm.is_none(value) { + return Ok((vec![0u8; size], None)); + } + Ok((PyCField::value_to_bytes(value, size, vm)?, None)) + } + "P" => { + // c_void_p: store integer as pointer + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_usize().unwrap_or(0); + let mut result = vec![0u8; size]; + let bytes = v.to_ne_bytes(); + let len = std::cmp::min(bytes.len(), size); + result[..len].copy_from_slice(&bytes[..len]); + return Ok((result, None)); + } + // None -> NULL pointer + if vm.is_none(value) { + return Ok((vec![0u8; size], None)); + } + Ok((PyCField::value_to_bytes(value, size, vm)?, None)) + } + _ => Ok((PyCField::value_to_bytes(value, size, vm)?, None)), + } + } + + /// Check if the field type is a c_char array (element type has _type_ == 'c') + fn is_char_array(proto: &PyObject, vm: &VirtualMachine) -> bool { + // Get element_type from StgInfo (for array types) + if let Some(proto_type) = proto.downcast_ref::<PyType>() + && let Some(stg) = proto_type.stg_info_opt() + && let Some(element_type) = &stg.element_type + { + // Check if element type has _type_ == "c" + if let Ok(type_code) = element_type.as_object().get_attr("_type_", vm) + && let Some(s) = type_code.downcast_ref::<PyStr>() + { + return s.as_str() == "c"; + } + } + false + } - // Store base reference to keep dll alive - if let Ok(simple_ref) = instance.clone().downcast::<PyCSimple>() { - simple_ref.cdata.write().base = Some(dll); + /// Check if the field type is a c_wchar array (element type has _type_ == 'u') + fn is_wchar_array(proto: &PyObject, vm: &VirtualMachine) -> bool { + // Get element_type from StgInfo (for array types) + if let Some(proto_type) = proto.downcast_ref::<PyType>() + && let Some(stg) = proto_type.stg_info_opt() + && let Some(element_type) = &stg.element_type + { + // Check if element type has _type_ == "u" + if let Ok(type_code) = element_type.as_object().get_attr("_type_", vm) + && let Some(s) = type_code.downcast_ref::<PyStr>() + { + return s.as_str() == "u"; + } } + false + } - Ok(instance) + /// Convert bytes for c_char array assignment (stops at first null terminator) + /// Returns (bytes_to_copy, copy_len) + fn bytes_for_char_array(src: &[u8]) -> &[u8] { + // Find first null terminator and include it + if let Some(null_pos) = src.iter().position(|&b| b == 0) { + &src[..=null_pos] + } else { + src + } } } -impl PyCSimple { - pub fn to_arg( - &self, - ty: libffi::middle::Type, +#[pyclass( + flags(DISALLOW_INSTANTIATION, IMMUTABLETYPE), + with(Representable, GetDescriptor) +)] +impl PyCField { + /// Get PyCData from object (works for both Structure and Union) + fn get_cdata_from_obj<'a>(obj: &'a PyObjectRef, vm: &VirtualMachine) -> PyResult<&'a PyCData> { + if let Some(s) = obj.downcast_ref::<super::structure::PyCStructure>() { + Ok(&s.0) + } else if let Some(u) = obj.downcast_ref::<super::union::PyCUnion>() { + Ok(&u.0) + } else { + Err(vm.new_type_error(format!( + "descriptor works only on Structure or Union instances, got {}", + obj.class().name() + ))) + } + } + + /// PyCField_set + #[pyslot] + fn descr_set( + zelf: &crate::PyObject, + obj: PyObjectRef, + value: PySetterValue<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + let zelf = zelf + .downcast_ref::<PyCField>() + .ok_or_else(|| vm.new_type_error("expected CField"))?; + + let offset = zelf.offset as usize; + let size = zelf.byte_size(); + + // Get PyCData from obj (works for both Structure and Union) + let cdata = Self::get_cdata_from_obj(&obj, vm)?; + + match value { + PySetterValue::Assign(value) => { + // Check if needs byte swapping + let needs_swap = (obj + .class() + .as_object() + .get_attr("_swappedbytes_", vm) + .is_ok() + || zelf + .proto + .as_object() + .get_attr("_swappedbytes_", vm) + .is_ok()) + && size > 1; + + // PyCData_set + cdata.set_field( + zelf.proto.as_object(), + value, + zelf.index, + size, + offset, + needs_swap, + vm, + ) + } + PySetterValue::Delete => Err(vm.new_type_error("cannot delete field")), + } + } + + #[pymethod] + fn __set__( + zelf: PyObjectRef, + obj: PyObjectRef, + value: PyObjectRef, vm: &VirtualMachine, - ) -> Option<libffi::middle::Arg> { - let value = unsafe { (*self.value.as_ptr()).clone() }; - if let Ok(i) = value.try_int(vm) { - let i = i.as_bigint(); - return if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u8().as_raw_ptr()) { - i.to_u8().map(|r: u8| libffi::middle::Arg::new(&r)) - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i8().as_raw_ptr()) { - i.to_i8().map(|r: i8| libffi::middle::Arg::new(&r)) - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u16().as_raw_ptr()) { - i.to_u16().map(|r: u16| libffi::middle::Arg::new(&r)) - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i16().as_raw_ptr()) { - i.to_i16().map(|r: i16| libffi::middle::Arg::new(&r)) - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u32().as_raw_ptr()) { - i.to_u32().map(|r: u32| libffi::middle::Arg::new(&r)) - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i32().as_raw_ptr()) { - i.to_i32().map(|r: i32| libffi::middle::Arg::new(&r)) - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u64().as_raw_ptr()) { - i.to_u64().map(|r: u64| libffi::middle::Arg::new(&r)) - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i64().as_raw_ptr()) { - i.to_i64().map(|r: i64| libffi::middle::Arg::new(&r)) + ) -> PyResult<()> { + Self::descr_set(&zelf, obj, PySetterValue::Assign(value), vm) + } + + #[pymethod] + fn __delete__(zelf: PyObjectRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + Self::descr_set(&zelf, obj, PySetterValue::Delete, vm) + } + + #[pygetset] + fn offset(&self) -> isize { + self.offset + } + + #[pygetset] + fn size(&self) -> isize { + self.size + } +} + +// ParamFunc implementations (PyCArgObject creation) + +use super::_ctypes::CArgObject; + +/// Call the appropriate paramfunc based on StgInfo.paramfunc +/// info->paramfunc(st, obj) +pub(super) fn call_paramfunc(obj: &PyObject, vm: &VirtualMachine) -> PyResult<CArgObject> { + let cls = obj.class(); + let stg_info = cls + .stg_info_opt() + .ok_or_else(|| vm.new_type_error("not a ctypes type"))?; + + match stg_info.paramfunc { + ParamFunc::Simple => simple_paramfunc(obj, vm), + ParamFunc::Array => array_paramfunc(obj, vm), + ParamFunc::Pointer => pointer_paramfunc(obj, vm), + ParamFunc::Structure | ParamFunc::Union => struct_union_paramfunc(obj, &stg_info, vm), + ParamFunc::None => Err(vm.new_type_error("no paramfunc")), + } +} + +/// PyCSimpleType_paramfunc +fn simple_paramfunc(obj: &PyObject, vm: &VirtualMachine) -> PyResult<CArgObject> { + use super::simple::PyCSimple; + + let simple = obj + .downcast_ref::<PyCSimple>() + .ok_or_else(|| vm.new_type_error("expected simple type"))?; + + // Get type code from _type_ attribute + let cls = obj.class().to_owned(); + let type_code = cls + .type_code(vm) + .ok_or_else(|| vm.new_type_error("no _type_ attribute"))?; + let tag = type_code.as_bytes().first().copied().unwrap_or(b'?'); + + // Read value from buffer: memcpy(&parg->value, self->b_ptr, self->b_size) + let buffer = simple.0.buffer.read(); + let ffi_value = buffer_to_ffi_value(&type_code, &buffer); + + Ok(CArgObject { + tag, + value: ffi_value, + obj: obj.to_owned(), + size: 0, + offset: 0, + }) +} + +/// PyCArrayType_paramfunc +fn array_paramfunc(obj: &PyObject, vm: &VirtualMachine) -> PyResult<CArgObject> { + use super::array::PyCArray; + + let array = obj + .downcast_ref::<PyCArray>() + .ok_or_else(|| vm.new_type_error("expected array"))?; + + // p->value.p = (char *)self->b_ptr + let buffer = array.0.buffer.read(); + let ptr_val = buffer.as_ptr() as usize; + + Ok(CArgObject { + tag: b'P', + value: FfiArgValue::Pointer(ptr_val), + obj: obj.to_owned(), + size: 0, + offset: 0, + }) +} + +/// PyCPointerType_paramfunc +fn pointer_paramfunc(obj: &PyObject, vm: &VirtualMachine) -> PyResult<CArgObject> { + use super::pointer::PyCPointer; + + let ptr = obj + .downcast_ref::<PyCPointer>() + .ok_or_else(|| vm.new_type_error("expected pointer"))?; + + // parg->value.p = *(void **)self->b_ptr + let ptr_val = ptr.get_ptr_value(); + + Ok(CArgObject { + tag: b'P', + value: FfiArgValue::Pointer(ptr_val), + obj: obj.to_owned(), + size: 0, + offset: 0, + }) +} + +/// StructUnionType_paramfunc (for both Structure and Union) +fn struct_union_paramfunc( + obj: &PyObject, + stg_info: &StgInfo, + _vm: &VirtualMachine, +) -> PyResult<CArgObject> { + // Get buffer pointer + // For large structs (> sizeof(void*)), we'd need to allocate and copy. + // For now, just point to buffer directly and keep obj reference for memory safety. + let buffer = if let Some(cdata) = obj.downcast_ref::<PyCData>() { + cdata.buffer.read() + } else { + return Ok(CArgObject { + tag: b'V', + value: FfiArgValue::Pointer(0), + obj: obj.to_owned(), + size: stg_info.size, + offset: 0, + }); + }; + + let ptr_val = buffer.as_ptr() as usize; + let size = buffer.len(); + + Ok(CArgObject { + tag: b'V', + value: FfiArgValue::Pointer(ptr_val), + obj: obj.to_owned(), + size, + offset: 0, + }) +} + +// FfiArgValue - Owned FFI argument value + +/// Owned FFI argument value. Keeps the value alive for the duration of the FFI call. +#[derive(Debug, Clone)] +pub enum FfiArgValue { + U8(u8), + I8(i8), + U16(u16), + I16(i16), + U32(u32), + I32(i32), + U64(u64), + I64(i64), + F32(f32), + F64(f64), + Pointer(usize), + /// Pointer with owned data. The PyObjectRef keeps the pointed data alive. + OwnedPointer(usize, #[allow(dead_code)] crate::PyObjectRef), +} + +impl FfiArgValue { + /// Create an Arg reference to this owned value + pub fn as_arg(&self) -> libffi::middle::Arg { + match self { + FfiArgValue::U8(v) => libffi::middle::Arg::new(v), + FfiArgValue::I8(v) => libffi::middle::Arg::new(v), + FfiArgValue::U16(v) => libffi::middle::Arg::new(v), + FfiArgValue::I16(v) => libffi::middle::Arg::new(v), + FfiArgValue::U32(v) => libffi::middle::Arg::new(v), + FfiArgValue::I32(v) => libffi::middle::Arg::new(v), + FfiArgValue::U64(v) => libffi::middle::Arg::new(v), + FfiArgValue::I64(v) => libffi::middle::Arg::new(v), + FfiArgValue::F32(v) => libffi::middle::Arg::new(v), + FfiArgValue::F64(v) => libffi::middle::Arg::new(v), + FfiArgValue::Pointer(v) => libffi::middle::Arg::new(v), + FfiArgValue::OwnedPointer(v, _) => libffi::middle::Arg::new(v), + } + } +} + +/// Convert buffer bytes to FfiArgValue based on type code +pub(super) fn buffer_to_ffi_value(type_code: &str, buffer: &[u8]) -> FfiArgValue { + match type_code { + "c" | "b" => { + let v = buffer.first().map(|&b| b as i8).unwrap_or(0); + FfiArgValue::I8(v) + } + "B" => { + let v = buffer.first().copied().unwrap_or(0); + FfiArgValue::U8(v) + } + "h" => { + let v = if buffer.len() >= 2 { + i16::from_ne_bytes(buffer[..2].try_into().unwrap()) + } else { + 0 + }; + FfiArgValue::I16(v) + } + "H" => { + let v = if buffer.len() >= 2 { + u16::from_ne_bytes(buffer[..2].try_into().unwrap()) + } else { + 0 + }; + FfiArgValue::U16(v) + } + "i" => { + let v = if buffer.len() >= 4 { + i32::from_ne_bytes(buffer[..4].try_into().unwrap()) + } else { + 0 + }; + FfiArgValue::I32(v) + } + "I" => { + let v = if buffer.len() >= 4 { + u32::from_ne_bytes(buffer[..4].try_into().unwrap()) + } else { + 0 + }; + FfiArgValue::U32(v) + } + "l" | "q" => { + let v = if buffer.len() >= 8 { + i64::from_ne_bytes(buffer[..8].try_into().unwrap()) + } else if buffer.len() >= 4 { + i32::from_ne_bytes(buffer[..4].try_into().unwrap()) as i64 + } else { + 0 + }; + FfiArgValue::I64(v) + } + "L" | "Q" => { + let v = if buffer.len() >= 8 { + u64::from_ne_bytes(buffer[..8].try_into().unwrap()) + } else if buffer.len() >= 4 { + u32::from_ne_bytes(buffer[..4].try_into().unwrap()) as u64 + } else { + 0 + }; + FfiArgValue::U64(v) + } + "f" => { + let v = if buffer.len() >= 4 { + f32::from_ne_bytes(buffer[..4].try_into().unwrap()) + } else { + 0.0 + }; + FfiArgValue::F32(v) + } + "d" | "g" => { + let v = if buffer.len() >= 8 { + f64::from_ne_bytes(buffer[..8].try_into().unwrap()) } else { - None + 0.0 }; + FfiArgValue::F64(v) } - if let Ok(_f) = value.try_float(vm) { - todo!(); + "z" | "Z" | "P" | "O" => FfiArgValue::Pointer(read_ptr_from_buffer(buffer)), + "?" => { + let v = buffer.first().map(|&b| b != 0).unwrap_or(false); + FfiArgValue::U8(if v { 1 } else { 0 }) } - if let Ok(_b) = value.try_to_bool(vm) { - todo!(); + "u" => { + // wchar_t - 4 bytes on most platforms + let v = if buffer.len() >= 4 { + u32::from_ne_bytes(buffer[..4].try_into().unwrap()) + } else { + 0 + }; + FfiArgValue::U32(v) } - None + _ => FfiArgValue::Pointer(0), } } -static SIMPLE_BUFFER_METHODS: BufferMethods = BufferMethods { - obj_bytes: |buffer| { - rustpython_common::lock::PyMappedRwLockReadGuard::map( - rustpython_common::lock::PyRwLockReadGuard::map( - buffer.obj_as::<PyCSimple>().cdata.read(), - |x: &CDataObject| x, - ), - |x: &CDataObject| x.buffer.as_slice(), - ) - .into() - }, - obj_bytes_mut: |buffer| { - rustpython_common::lock::PyMappedRwLockWriteGuard::map( - rustpython_common::lock::PyRwLockWriteGuard::map( - buffer.obj_as::<PyCSimple>().cdata.write(), - |x: &mut CDataObject| x, - ), - |x: &mut CDataObject| x.buffer.as_mut_slice(), - ) - .into() - }, - release: |_| {}, - retain: |_| {}, -}; +/// Convert bytes to appropriate Python object based on ctypes type +pub(super) fn bytes_to_pyobject( + cls: &Py<PyType>, + bytes: &[u8], + vm: &VirtualMachine, +) -> PyResult<PyObjectRef> { + // Try to get _type_ attribute + if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) + && let Ok(s) = type_attr.str(vm) + { + let ty = s.to_string(); + return match ty.as_str() { + "c" => Ok(vm.ctx.new_bytes(bytes.to_vec()).into()), + "b" => { + let val = if !bytes.is_empty() { bytes[0] as i8 } else { 0 }; + Ok(vm.ctx.new_int(val).into()) + } + "B" => { + let val = if !bytes.is_empty() { bytes[0] } else { 0 }; + Ok(vm.ctx.new_int(val).into()) + } + "h" => { + const SIZE: usize = mem::size_of::<c_short>(); + let val = if bytes.len() >= SIZE { + c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "H" => { + const SIZE: usize = mem::size_of::<c_ushort>(); + let val = if bytes.len() >= SIZE { + c_ushort::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "i" => { + const SIZE: usize = mem::size_of::<c_int>(); + let val = if bytes.len() >= SIZE { + c_int::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "I" => { + const SIZE: usize = mem::size_of::<c_uint>(); + let val = if bytes.len() >= SIZE { + c_uint::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "l" => { + const SIZE: usize = mem::size_of::<c_long>(); + let val = if bytes.len() >= SIZE { + c_long::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "L" => { + const SIZE: usize = mem::size_of::<c_ulong>(); + let val = if bytes.len() >= SIZE { + c_ulong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "q" => { + const SIZE: usize = mem::size_of::<c_longlong>(); + let val = if bytes.len() >= SIZE { + c_longlong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "Q" => { + const SIZE: usize = mem::size_of::<c_ulonglong>(); + let val = if bytes.len() >= SIZE { + c_ulonglong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "f" => { + const SIZE: usize = mem::size_of::<c_float>(); + let val = if bytes.len() >= SIZE { + c_float::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0.0 + }; + Ok(vm.ctx.new_float(val as f64).into()) + } + "d" => { + const SIZE: usize = mem::size_of::<c_double>(); + let val = if bytes.len() >= SIZE { + c_double::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0.0 + }; + Ok(vm.ctx.new_float(val).into()) + } + "g" => { + // long double - read as f64 for now since Rust doesn't have native long double + // This may lose precision on platforms where long double > 64 bits + const SIZE: usize = mem::size_of::<c_double>(); + let val = if bytes.len() >= SIZE { + c_double::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0.0 + }; + Ok(vm.ctx.new_float(val).into()) + } + "?" => { + let val = !bytes.is_empty() && bytes[0] != 0; + Ok(vm.ctx.new_bool(val).into()) + } + "v" => { + // VARIANT_BOOL: non-zero = True, zero = False + const SIZE: usize = mem::size_of::<c_short>(); + let val = if bytes.len() >= SIZE { + c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_bool(val != 0).into()) + } + "z" => { + // c_char_p: read NULL-terminated string from pointer + let ptr = read_ptr_from_buffer(bytes); + if ptr == 0 { + return Ok(vm.ctx.none()); + } + let c_str = unsafe { std::ffi::CStr::from_ptr(ptr as _) }; + Ok(vm.ctx.new_bytes(c_str.to_bytes().to_vec()).into()) + } + "Z" => { + // c_wchar_p: read NULL-terminated wide string from pointer + let ptr = read_ptr_from_buffer(bytes); + if ptr == 0 { + return Ok(vm.ctx.none()); + } + let len = unsafe { libc::wcslen(ptr as *const libc::wchar_t) }; + let wchars = + unsafe { std::slice::from_raw_parts(ptr as *const libc::wchar_t, len) }; + let s: String = wchars + .iter() + .filter_map(|&c| char::from_u32(c as u32)) + .collect(); + Ok(vm.ctx.new_str(s).into()) + } + "P" => { + // c_void_p: return pointer value as integer + let val = read_ptr_from_buffer(bytes); + if val == 0 { + return Ok(vm.ctx.none()); + } + Ok(vm.ctx.new_int(val).into()) + } + "u" => { + let val = if bytes.len() >= mem::size_of::<WideChar>() { + let wc = if mem::size_of::<WideChar>() == 2 { + u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 + } else { + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + }; + char::from_u32(wc).unwrap_or('\0') + } else { + '\0' + }; + Ok(vm.ctx.new_str(val).into()) + } + _ => Ok(vm.ctx.none()), + }; + } + // Default: return bytes as-is + Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) +} -impl AsBuffer for PyCSimple { - fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { - let buffer_len = zelf.cdata.read().buffer.len(); - let buf = PyBuffer::new( - zelf.to_owned().into(), - BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes - &SIMPLE_BUFFER_METHODS, - ); - Ok(buf) +// Shared functions for Structure and Union types + +/// Parse a non-negative integer attribute, returning default if not present +pub(super) fn get_usize_attr( + obj: &PyObject, + attr: &str, + default: usize, + vm: &VirtualMachine, +) -> PyResult<usize> { + let Ok(attr_val) = obj.get_attr(vm.ctx.intern_str(attr), vm) else { + return Ok(default); + }; + let n = attr_val + .try_int(vm) + .map_err(|_| vm.new_value_error(format!("{attr} must be a non-negative integer")))?; + let val = n.as_bigint(); + if val.is_negative() { + return Err(vm.new_value_error(format!("{attr} must be a non-negative integer"))); + } + Ok(val.to_usize().unwrap_or(default)) +} + +/// Read a pointer value from buffer +#[inline] +pub(super) fn read_ptr_from_buffer(buffer: &[u8]) -> usize { + const PTR_SIZE: usize = std::mem::size_of::<usize>(); + if buffer.len() >= PTR_SIZE { + usize::from_ne_bytes(buffer[..PTR_SIZE].try_into().unwrap()) + } else { + 0 + } +} + +/// Set or initialize StgInfo on a type +pub(super) fn set_or_init_stginfo(type_ref: &PyType, stg_info: StgInfo) { + if type_ref.init_type_data(stg_info.clone()).is_err() + && let Some(mut existing) = type_ref.get_type_data_mut::<StgInfo>() + { + *existing = stg_info; + } +} + +/// Check if a field type supports byte order swapping +pub(super) fn check_other_endian_support( + field_type: &PyObject, + vm: &VirtualMachine, +) -> PyResult<()> { + let other_endian_attr = if cfg!(target_endian = "little") { + "__ctype_be__" + } else { + "__ctype_le__" + }; + + if field_type.get_attr(other_endian_attr, vm).is_ok() { + return Ok(()); + } + + // Array type: recursively check element type + if let Ok(elem_type) = field_type.get_attr("_type_", vm) + && field_type.get_attr("_length_", vm).is_ok() + { + return check_other_endian_support(&elem_type, vm); } + + // Structure/Union: has StgInfo but no _type_ attribute + if let Some(type_obj) = field_type.downcast_ref::<PyType>() + && type_obj.stg_info_opt().is_some() + && field_type.get_attr("_type_", vm).is_err() + { + return Ok(()); + } + + Err(vm.new_type_error(format!( + "This type does not support other endian: {}", + field_type.class().name() + ))) +} + +/// Get the size of a ctypes field type +pub(super) fn get_field_size(field_type: &PyObject, vm: &VirtualMachine) -> PyResult<usize> { + if let Some(type_obj) = field_type.downcast_ref::<PyType>() + && let Some(stg_info) = type_obj.stg_info_opt() + { + return Ok(stg_info.size); + } + + if let Some(size) = field_type + .get_attr("_type_", vm) + .ok() + .and_then(|type_attr| type_attr.str(vm).ok()) + .and_then(|type_str| { + let s = type_str.to_string(); + (s.len() == 1).then(|| super::get_size(&s)) + }) + { + return Ok(size); + } + + if let Some(s) = field_type + .get_attr("size_of_instances", vm) + .ok() + .and_then(|size_method| size_method.call((), vm).ok()) + .and_then(|size| size.try_int(vm).ok()) + .and_then(|n| n.as_bigint().to_usize()) + { + return Ok(s); + } + + Ok(std::mem::size_of::<usize>()) +} + +/// Get the alignment of a ctypes field type +pub(super) fn get_field_align(field_type: &PyObject, vm: &VirtualMachine) -> usize { + if let Some(type_obj) = field_type.downcast_ref::<PyType>() + && let Some(stg_info) = type_obj.stg_info_opt() + && stg_info.align > 0 + { + return stg_info.align; + } + + if let Some(align) = field_type + .get_attr("_type_", vm) + .ok() + .and_then(|type_attr| type_attr.str(vm).ok()) + .and_then(|type_str| { + let s = type_str.to_string(); + (s.len() == 1).then(|| super::get_size(&s)) + }) + { + return align; + } + + 1 +} + +/// Promote fields from anonymous struct/union to parent type +fn make_fields( + cls: &Py<PyType>, + descr: &super::PyCField, + index: usize, + offset: isize, + vm: &VirtualMachine, +) -> PyResult<()> { + use crate::builtins::{PyList, PyTuple}; + use crate::convert::ToPyObject; + + let fields = descr.proto.as_object().get_attr("_fields_", vm)?; + let fieldlist: Vec<PyObjectRef> = if let Some(list) = fields.downcast_ref::<PyList>() { + list.borrow_vec().to_vec() + } else if let Some(tuple) = fields.downcast_ref::<PyTuple>() { + tuple.to_vec() + } else { + return Err(vm.new_type_error("_fields_ must be a sequence")); + }; + + for pair in fieldlist.iter() { + let field_tuple = pair + .downcast_ref::<PyTuple>() + .ok_or_else(|| vm.new_type_error("_fields_ must contain tuples"))?; + + if field_tuple.len() < 2 { + continue; + } + + let fname = field_tuple + .first() + .expect("len checked") + .downcast_ref::<PyStr>() + .ok_or_else(|| vm.new_type_error("field name must be a string"))?; + + let fdescr_obj = descr + .proto + .as_object() + .get_attr(vm.ctx.intern_str(fname.as_str()), vm)?; + let fdescr = fdescr_obj + .downcast_ref::<super::PyCField>() + .ok_or_else(|| vm.new_type_error("unexpected type"))?; + + if fdescr.anonymous { + make_fields( + cls, + fdescr, + index + fdescr.index, + offset + fdescr.offset, + vm, + )?; + continue; + } + + let new_descr = super::PyCField::new_from_field(fdescr, index, offset); + cls.set_attr(vm.ctx.intern_str(fname.as_str()), new_descr.to_pyobject(vm)); + } + + Ok(()) +} + +/// Process _anonymous_ attribute for struct/union +pub(super) fn make_anon_fields(cls: &Py<PyType>, vm: &VirtualMachine) -> PyResult<()> { + use crate::builtins::{PyList, PyTuple}; + use crate::convert::ToPyObject; + + let anon = match cls.as_object().get_attr("_anonymous_", vm) { + Ok(anon) => anon, + Err(_) => return Ok(()), + }; + + let anon_names: Vec<PyObjectRef> = if let Some(list) = anon.downcast_ref::<PyList>() { + list.borrow_vec().to_vec() + } else if let Some(tuple) = anon.downcast_ref::<PyTuple>() { + tuple.to_vec() + } else { + return Err(vm.new_type_error("_anonymous_ must be a sequence")); + }; + + for fname_obj in anon_names.iter() { + let fname = fname_obj + .downcast_ref::<PyStr>() + .ok_or_else(|| vm.new_type_error("_anonymous_ items must be strings"))?; + + let descr_obj = cls + .as_object() + .get_attr(vm.ctx.intern_str(fname.as_str()), vm)?; + + let descr = descr_obj.downcast_ref::<super::PyCField>().ok_or_else(|| { + vm.new_attribute_error(format!( + "'{}' is specified in _anonymous_ but not in _fields_", + fname.as_str() + )) + })?; + + let mut new_descr = super::PyCField::new_from_field(descr, 0, 0); + new_descr.set_anonymous(true); + cls.set_attr(vm.ctx.intern_str(fname.as_str()), new_descr.to_pyobject(vm)); + + make_fields(cls, descr, descr.index, descr.offset, vm)?; + } + + Ok(()) } diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index b4e600f77ba..9bddb0ef0e8 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -1,67 +1,310 @@ // spell-checker:disable -use crate::builtins::{PyNone, PyStr, PyTuple, PyTupleRef, PyType, PyTypeRef}; -use crate::convert::ToPyObject; -use crate::function::FuncArgs; -use crate::stdlib::ctypes::PyCData; -use crate::stdlib::ctypes::base::{CDataObject, PyCSimple, ffi_type_from_str}; -use crate::stdlib::ctypes::thunk::PyCThunk; -use crate::types::Representable; -use crate::types::{Callable, Constructor}; -use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; -use crossbeam_utils::atomic::AtomicCell; -use libffi::middle::{Arg, Cif, CodePtr, Type}; +use super::{ + _ctypes::CArgObject, PyCArray, PyCData, PyCPointer, PyCStructure, base::FfiArgValue, + simple::PyCSimple, type_info, +}; +use crate::{ + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + builtins::{PyBytes, PyDict, PyNone, PyStr, PyTuple, PyType, PyTypeRef}, + class::StaticType, + convert::ToPyObject, + function::FuncArgs, + types::{Callable, Constructor, Representable}, + vm::thread::with_current_vm, +}; +use libffi::{ + low, + middle::{Arg, Cif, Closure, CodePtr, Type}, +}; use libloading::Symbol; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; use std::ffi::{self, c_void}; use std::fmt::Debug; -// See also: https://github.com/python/cpython/blob/4f8bb3947cfbc20f970ff9d9531e1132a9e95396/Modules/_ctypes/callproc.c#L15 +// Internal function addresses for special ctypes functions +pub(super) const INTERNAL_CAST_ADDR: usize = 1; +pub(super) const INTERNAL_STRING_AT_ADDR: usize = 2; +pub(super) const INTERNAL_WSTRING_AT_ADDR: usize = 3; type FP = unsafe extern "C" fn(); -pub trait ArgumentType { +/// Get FFI type for a ctypes type code +fn get_ffi_type(ty: &str) -> Option<libffi::middle::Type> { + type_info(ty).map(|t| (t.ffi_type_fn)()) +} + +// PyCFuncPtr - Function pointer implementation + +/// Get FFI type from CArgObject tag character +fn ffi_type_from_tag(tag: u8) -> Type { + match tag { + b'c' | b'b' => Type::i8(), + b'B' => Type::u8(), + b'h' => Type::i16(), + b'H' => Type::u16(), + b'i' => Type::i32(), + b'I' => Type::u32(), + b'l' => { + if std::mem::size_of::<libc::c_long>() == 8 { + Type::i64() + } else { + Type::i32() + } + } + b'L' => { + if std::mem::size_of::<libc::c_ulong>() == 8 { + Type::u64() + } else { + Type::u32() + } + } + b'q' => Type::i64(), + b'Q' => Type::u64(), + b'f' => Type::f32(), + b'd' | b'g' => Type::f64(), + b'?' => Type::u8(), + b'u' => { + if std::mem::size_of::<super::WideChar>() == 2 { + Type::u16() + } else { + Type::u32() + } + } + _ => Type::pointer(), // 'P', 'V', 'z', 'Z', 'O', etc. + } +} + +/// Convert any object to a pointer value for c_void_p arguments +/// Follows ConvParam logic for pointer types +fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult<FfiArgValue> { + // 0. CArgObject (from byref()) -> buffer address + offset + if let Some(carg) = value.downcast_ref::<CArgObject>() { + // Get buffer address from the underlying object + let base_addr = if let Some(cdata) = carg.obj.downcast_ref::<PyCData>() { + cdata.buffer.read().as_ptr() as usize + } else { + return Err(vm.new_type_error(format!( + "byref() argument must be a ctypes instance, not '{}'", + carg.obj.class().name() + ))); + }; + let addr = (base_addr as isize + carg.offset) as usize; + return Ok(FfiArgValue::Pointer(addr)); + } + + // 1. None -> NULL + if value.is(&vm.ctx.none) { + return Ok(FfiArgValue::Pointer(0)); + } + + // 2. PyCArray -> buffer address (PyCArrayType_paramfunc) + if let Some(array) = value.downcast_ref::<PyCArray>() { + let addr = array.0.buffer.read().as_ptr() as usize; + return Ok(FfiArgValue::Pointer(addr)); + } + + // 3. PyCPointer -> stored pointer value + if let Some(ptr) = value.downcast_ref::<PyCPointer>() { + return Ok(FfiArgValue::Pointer(ptr.get_ptr_value())); + } + + // 4. PyCStructure -> buffer address + if let Some(struct_obj) = value.downcast_ref::<PyCStructure>() { + let addr = struct_obj.0.buffer.read().as_ptr() as usize; + return Ok(FfiArgValue::Pointer(addr)); + } + + // 5. PyCSimple (c_void_p, c_char_p, etc.) -> value from buffer + if let Some(simple) = value.downcast_ref::<PyCSimple>() { + let buffer = simple.0.buffer.read(); + if buffer.len() >= std::mem::size_of::<usize>() { + let addr = super::base::read_ptr_from_buffer(&buffer); + return Ok(FfiArgValue::Pointer(addr)); + } + } + + // 6. bytes -> buffer address (PyBytes_AsString) + if let Some(bytes) = value.downcast_ref::<crate::builtins::PyBytes>() { + let addr = bytes.as_bytes().as_ptr() as usize; + return Ok(FfiArgValue::Pointer(addr)); + } + + // 7. Integer -> direct value + if let Ok(int_val) = value.try_int(vm) { + return Ok(FfiArgValue::Pointer( + int_val.as_bigint().to_usize().unwrap_or(0), + )); + } + + // 8. Check _as_parameter_ attribute ( recursive ConvParam) + if let Ok(as_param) = value.get_attr("_as_parameter_", vm) { + return convert_to_pointer(&as_param, vm); + } + + Err(vm.new_type_error(format!( + "cannot convert '{}' to c_void_p", + value.class().name() + ))) +} + +/// ConvParam-like conversion for when argtypes is None +/// Returns both the FFI type and the converted value +fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult<(Type, FfiArgValue)> { + // 1. CArgObject (from byref() or paramfunc) -> use stored type and value + if let Some(carg) = value.downcast_ref::<CArgObject>() { + let ffi_type = ffi_type_from_tag(carg.tag); + return Ok((ffi_type, carg.value.clone())); + } + + // 2. None -> NULL pointer + if value.is(&vm.ctx.none) { + return Ok((Type::pointer(), FfiArgValue::Pointer(0))); + } + + // 3. ctypes objects -> use paramfunc + if let Ok(carg) = super::base::call_paramfunc(value, vm) { + let ffi_type = ffi_type_from_tag(carg.tag); + return Ok((ffi_type, carg.value.clone())); + } + + // 4. Python str -> pointer (use internal UTF-8 buffer) + if let Some(s) = value.downcast_ref::<PyStr>() { + let addr = s.as_str().as_ptr() as usize; + return Ok((Type::pointer(), FfiArgValue::Pointer(addr))); + } + + // 9. Python bytes -> pointer to buffer + if let Some(bytes) = value.downcast_ref::<PyBytes>() { + let addr = bytes.as_bytes().as_ptr() as usize; + return Ok((Type::pointer(), FfiArgValue::Pointer(addr))); + } + + // 10. Python int -> i32 (default integer type) + if let Ok(int_val) = value.try_int(vm) { + let val = int_val.as_bigint().to_i32().unwrap_or(0); + return Ok((Type::i32(), FfiArgValue::I32(val))); + } + + // 11. Python float -> f64 + if let Ok(float_val) = value.try_float(vm) { + return Ok((Type::f64(), FfiArgValue::F64(float_val.to_f64()))); + } + + // 12. Check _as_parameter_ attribute + if let Ok(as_param) = value.get_attr("_as_parameter_", vm) { + return conv_param(&as_param, vm); + } + + Err(vm.new_type_error(format!( + "Don't know how to convert parameter {}", + value.class().name() + ))) +} + +trait ArgumentType { fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult<Type>; - fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<Arg>; + fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<FfiArgValue>; } impl ArgumentType for PyTypeRef { fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult<Type> { + use super::pointer::PyCPointer; + use super::structure::PyCStructure; + + // CArgObject (from byref()) should be treated as pointer + if self.fast_issubclass(CArgObject::static_type()) { + return Ok(Type::pointer()); + } + + // Pointer types (POINTER(T)) are always pointer FFI type + // Check if type is a subclass of _Pointer (PyCPointer) + if self.fast_issubclass(PyCPointer::static_type()) { + return Ok(Type::pointer()); + } + + // Structure types are passed as pointers + if self.fast_issubclass(PyCStructure::static_type()) { + return Ok(Type::pointer()); + } + + // Use get_attr to traverse MRO (for subclasses like MyInt(c_int)) let typ = self - .get_class_attr(vm.ctx.intern_str("_type_")) - .ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; + .as_object() + .get_attr(vm.ctx.intern_str("_type_"), vm) + .ok() + .ok_or(vm.new_type_error("Unsupported argument type"))?; let typ = typ .downcast_ref::<PyStr>() - .ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; + .ok_or(vm.new_type_error("Unsupported argument type"))?; let typ = typ.to_string(); let typ = typ.as_str(); - let converted_typ = ffi_type_from_str(typ); - if let Some(typ) = converted_typ { - Ok(typ) - } else { - Err(vm.new_type_error(format!("Unsupported argument type: {}", typ))) - } + get_ffi_type(typ) + .ok_or_else(|| vm.new_type_error(format!("Unsupported argument type: {}", typ))) } - fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<Arg> { - // if self.fast_isinstance::<PyCArray>(vm) { - // let array = value.downcast::<PyCArray>()?; - // return Ok(Arg::from(array.as_ptr())); - // } - if let Ok(simple) = value.downcast::<PyCSimple>() { + fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<FfiArgValue> { + // Call from_param first to convert the value (like CPython's callproc.c:1235) + // converter = PyTuple_GET_ITEM(argtypes, i); + // v = PyObject_CallOneArg(converter, arg); + let from_param = self + .as_object() + .get_attr(vm.ctx.intern_str("from_param"), vm)?; + let converted = from_param.call((value.clone(),), vm)?; + + // Then pass the converted value to ConvParam logic + // CArgObject (from from_param) -> use stored value directly + if let Some(carg) = converted.downcast_ref::<CArgObject>() { + return Ok(carg.value.clone()); + } + + // None -> NULL pointer + if vm.is_none(&converted) { + return Ok(FfiArgValue::Pointer(0)); + } + + // For pointer types (POINTER(T)), we need to pass the ADDRESS of the value's buffer + if self.fast_issubclass(PyCPointer::static_type()) { + if let Some(cdata) = converted.downcast_ref::<PyCData>() { + let addr = cdata.buffer.read().as_ptr() as usize; + return Ok(FfiArgValue::Pointer(addr)); + } + return convert_to_pointer(&converted, vm); + } + + // For structure types, convert to pointer to structure + if self.fast_issubclass(PyCStructure::static_type()) { + return convert_to_pointer(&converted, vm); + } + + // Get the type code for this argument type + let type_code = self + .as_object() + .get_attr(vm.ctx.intern_str("_type_"), vm) + .ok() + .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())); + + // For pointer types (c_void_p, c_char_p, c_wchar_p), handle as pointer + if matches!(type_code.as_deref(), Some("P") | Some("z") | Some("Z")) { + return convert_to_pointer(&converted, vm); + } + + // PyCSimple (already a ctypes instance from from_param) + if let Ok(simple) = converted.clone().downcast::<PyCSimple>() { let typ = ArgumentType::to_ffi_type(self, vm)?; - let arg = simple - .to_arg(typ, vm) - .ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; - return Ok(arg); + let ffi_value = simple + .to_ffi_value(typ, vm) + .ok_or(vm.new_type_error("Unsupported argument type"))?; + return Ok(ffi_value); } - Err(vm.new_type_error("Unsupported argument type".to_string())) + + Err(vm.new_type_error("Unsupported argument type")) } } -pub trait ReturnType { - fn to_ffi_type(&self) -> Option<Type>; +trait ReturnType { + fn to_ffi_type(&self, vm: &VirtualMachine) -> Option<Type>; #[allow(clippy::wrong_self_convention)] fn from_ffi_type( &self, @@ -71,8 +314,34 @@ pub trait ReturnType { } impl ReturnType for PyTypeRef { - fn to_ffi_type(&self) -> Option<Type> { - ffi_type_from_str(self.name().to_string().as_str()) + fn to_ffi_type(&self, vm: &VirtualMachine) -> Option<Type> { + // Try to get _type_ attribute first (for ctypes types like c_void_p) + if let Ok(type_attr) = self.as_object().get_attr(vm.ctx.intern_str("_type_"), vm) + && let Some(s) = type_attr.downcast_ref::<PyStr>() + && let Some(ffi_type) = get_ffi_type(s.as_str()) + { + return Some(ffi_type); + } + + // Check for Structure/Array types (have StgInfo but no _type_) + // _ctypes_get_ffi_type: returns appropriately sized type for struct returns + if let Some(stg_info) = self.stg_info_opt() { + let size = stg_info.size; + // Small structs can be returned in registers + // Match can_return_struct_as_int/can_return_struct_as_sint64 + return Some(if size <= 4 { + Type::i32() + } else if size <= 8 { + Type::i64() + } else { + // Large structs: use pointer-sized return + // (ABI typically returns via hidden pointer parameter) + Type::pointer() + }); + } + + // Fallback to class name + get_ffi_type(self.name().to_string().as_str()) } fn from_ffi_type( @@ -80,9 +349,11 @@ impl ReturnType for PyTypeRef { value: *mut ffi::c_void, vm: &VirtualMachine, ) -> PyResult<Option<PyObjectRef>> { - // Get the type code from _type_ attribute + // Get the type code from _type_ attribute (use get_attr to traverse MRO) let type_code = self - .get_class_attr(vm.ctx.intern_str("_type_")) + .as_object() + .get_attr(vm.ctx.intern_str("_type_"), vm) + .ok() .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())); let result = match type_code.as_deref() { @@ -129,27 +400,59 @@ impl ReturnType for PyTypeRef { .new_float(unsafe { *(value as *const f32) } as f64) .into(), Some("d") => vm.ctx.new_float(unsafe { *(value as *const f64) }).into(), - Some("P") | Some("z") | Some("Z") => vm.ctx.new_int(value as usize).into(), + Some("P") | Some("z") | Some("Z") => { + vm.ctx.new_int(unsafe { *(value as *const usize) }).into() + } Some("?") => vm .ctx .new_bool(unsafe { *(value as *const u8) } != 0) .into(), None => { - // No _type_ attribute, try to create an instance of the type - // This handles cases like Structure or Array return types - return Ok(Some( - vm.ctx.new_int(unsafe { *(value as *const i32) }).into(), - )); + // No _type_ attribute - check for Structure/Array types + // GetResult: PyCData_FromBaseObj creates instance from memory + if let Some(stg_info) = self.stg_info_opt() { + let size = stg_info.size; + // Create instance of the ctypes type + let instance = self.as_object().call((), vm)?; + + // Copy return value memory into instance buffer + // Use a block to properly scope the borrow + { + let src = unsafe { std::slice::from_raw_parts(value as *const u8, size) }; + if let Some(cdata) = instance.downcast_ref::<PyCData>() { + let mut buffer = cdata.buffer.write(); + if buffer.len() >= size { + buffer.to_mut()[..size].copy_from_slice(src); + } + } else if let Some(structure) = instance.downcast_ref::<PyCStructure>() { + let mut buffer = structure.0.buffer.write(); + if buffer.len() >= size { + buffer.to_mut()[..size].copy_from_slice(src); + } + } else if let Some(array) = instance.downcast_ref::<PyCArray>() { + let mut buffer = array.0.buffer.write(); + if buffer.len() >= size { + buffer.to_mut()[..size].copy_from_slice(src); + } + } + } + return Ok(Some(instance)); + } + // Not a ctypes type - call type with int result + return self + .as_object() + .call((unsafe { *(value as *const i32) },), vm) + .map(Some); } - _ => return Err(vm.new_type_error("Unsupported return type".to_string())), + _ => return Err(vm.new_type_error("Unsupported return type")), }; Ok(Some(result)) } } impl ReturnType for PyNone { - fn to_ffi_type(&self) -> Option<Type> { - ffi_type_from_str("void") + fn to_ffi_type(&self, _vm: &VirtualMachine) -> Option<Type> { + get_ffi_type("void") } fn from_ffi_type( @@ -161,47 +464,319 @@ impl ReturnType for PyNone { } } +/// PyCFuncPtr - Function pointer instance +/// Saved in _base.buffer #[pyclass(module = "_ctypes", name = "CFuncPtr", base = PyCData)] -pub struct PyCFuncPtr { - _base: PyCData, - pub name: PyRwLock<Option<String>>, - pub ptr: PyRwLock<Option<CodePtr>>, - #[allow(dead_code)] - pub needs_free: AtomicCell<bool>, - pub arg_types: PyRwLock<Option<Vec<PyTypeRef>>>, - pub res_type: PyRwLock<Option<PyObjectRef>>, - pub _flags_: AtomicCell<i32>, - #[allow(dead_code)] - pub handler: PyObjectRef, +#[repr(C)] +pub(super) struct PyCFuncPtr { + pub _base: PyCData, + /// Thunk for callbacks (keeps thunk alive) + pub thunk: PyRwLock<Option<PyRef<PyCThunk>>>, + /// Original Python callable (for callbacks) + pub callable: PyRwLock<Option<PyObjectRef>>, + /// Converters cache + pub converters: PyRwLock<Option<PyObjectRef>>, + /// Instance-level argtypes override + pub argtypes: PyRwLock<Option<PyObjectRef>>, + /// Instance-level restype override + pub restype: PyRwLock<Option<PyObjectRef>>, + /// Checker function + pub checker: PyRwLock<Option<PyObjectRef>>, + /// Error checking function + pub errcheck: PyRwLock<Option<PyObjectRef>>, + /// COM method vtable index + /// When set, the function reads the function pointer from the vtable at call time + #[cfg(windows)] + pub index: PyRwLock<Option<usize>>, + /// COM method IID (interface ID) for error handling + #[cfg(windows)] + pub iid: PyRwLock<Option<PyObjectRef>>, + /// Parameter flags for COM methods (direction: IN=1, OUT=2, IN|OUT=4) + /// Each element is (direction, name, default) tuple + pub paramflags: PyRwLock<Option<PyObjectRef>>, } impl Debug for PyCFuncPtr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PyCFuncPtr") - .field("flags", &self._flags_) + .field("func_ptr", &self.get_func_ptr()) .finish() } } +/// Extract pointer value from a ctypes argument (c_void_p conversion) +fn extract_ptr_from_arg(arg: &PyObject, vm: &VirtualMachine) -> PyResult<usize> { + // Try to get pointer value from various ctypes types + if let Some(ptr) = arg.downcast_ref::<PyCPointer>() { + return Ok(ptr.get_ptr_value()); + } + if let Some(simple) = arg.downcast_ref::<PyCSimple>() { + let buffer = simple.0.buffer.read(); + if buffer.len() >= std::mem::size_of::<usize>() { + return Ok(usize::from_ne_bytes( + buffer[..std::mem::size_of::<usize>()].try_into().unwrap(), + )); + } + } + if let Some(cdata) = arg.downcast_ref::<PyCData>() { + // For arrays/structures, return address of buffer + return Ok(cdata.buffer.read().as_ptr() as usize); + } + // PyStr: return internal buffer address + if let Some(s) = arg.downcast_ref::<PyStr>() { + return Ok(s.as_str().as_ptr() as usize); + } + // PyBytes: return internal buffer address + if let Some(bytes) = arg.downcast_ref::<PyBytes>() { + return Ok(bytes.as_bytes().as_ptr() as usize); + } + // Try as integer + if let Ok(int_val) = arg.try_int(vm) { + return Ok(int_val.as_bigint().to_usize().unwrap_or(0)); + } + Err(vm.new_type_error(format!( + "cannot convert '{}' to pointer", + arg.class().name() + ))) +} + +/// string_at implementation - read bytes from memory at ptr +fn string_at_impl(ptr: usize, size: isize, vm: &VirtualMachine) -> PyResult { + if ptr == 0 { + return Err(vm.new_value_error("NULL pointer access")); + } + let ptr = ptr as *const u8; + let len = if size < 0 { + // size == -1 means use strlen + unsafe { libc::strlen(ptr as _) } + } else { + // Overflow check for huge size values + let size_usize = size as usize; + if size_usize > isize::MAX as usize / 2 { + return Err(vm.new_overflow_error("string too long")); + } + size_usize + }; + let bytes = unsafe { std::slice::from_raw_parts(ptr, len) }; + Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) +} + +/// wstring_at implementation - read wide string from memory at ptr +fn wstring_at_impl(ptr: usize, size: isize, vm: &VirtualMachine) -> PyResult { + if ptr == 0 { + return Err(vm.new_value_error("NULL pointer access")); + } + let w_ptr = ptr as *const libc::wchar_t; + let len = if size < 0 { + unsafe { libc::wcslen(w_ptr) } + } else { + // Overflow check for huge size values + let size_usize = size as usize; + if size_usize > isize::MAX as usize / std::mem::size_of::<libc::wchar_t>() { + return Err(vm.new_overflow_error("string too long")); + } + size_usize + }; + let wchars = unsafe { std::slice::from_raw_parts(w_ptr, len) }; + + // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide + // macOS/Linux: wchar_t = i32 (UTF-32) -> convert via char::from_u32 + #[cfg(windows)] + { + use rustpython_common::wtf8::Wtf8Buf; + let wide: Vec<u16> = wchars.to_vec(); + let wtf8 = Wtf8Buf::from_wide(&wide); + Ok(vm.ctx.new_str(wtf8).into()) + } + #[cfg(not(windows))] + { + let s: String = wchars + .iter() + .filter_map(|&c| char::from_u32(c as u32)) + .collect(); + Ok(vm.ctx.new_str(s).into()) + } +} + +// cast_check_pointertype +fn cast_check_pointertype(ctype: &PyObject, vm: &VirtualMachine) -> bool { + use super::pointer::PyCPointerType; + + // PyCPointerTypeObject_Check + if ctype.class().fast_issubclass(PyCPointerType::static_type()) { + return true; + } + + // PyCFuncPtrTypeObject_Check - TODO + + // simple pointer types via StgInfo.proto (c_void_p, c_char_p, etc.) + if let Ok(type_attr) = ctype.get_attr("_type_", vm) + && let Some(s) = type_attr.downcast_ref::<PyStr>() + { + let c = s.as_str(); + if c.len() == 1 && "sPzUZXO".contains(c) { + return true; + } + } + + false +} + +/// cast implementation +/// _ctypes.c cast() +pub(super) fn cast_impl( + obj: PyObjectRef, + src: PyObjectRef, + ctype: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + // 1. cast_check_pointertype + if !cast_check_pointertype(&ctype, vm) { + return Err(vm.new_type_error(format!( + "cast() argument 2 must be a pointer type, not {}", + ctype.class().name() + ))); + } + + // 2. Extract pointer value - matches c_void_p_from_param_impl order + let ptr_value: usize = if vm.is_none(&obj) { + // None → NULL pointer + 0 + } else if let Ok(int_val) = obj.try_int(vm) { + // int/long → direct pointer value + int_val.as_bigint().to_usize().unwrap_or(0) + } else if let Some(bytes) = obj.downcast_ref::<PyBytes>() { + // bytes → buffer address (c_void_p_from_param: PyBytes_Check) + bytes.as_bytes().as_ptr() as usize + } else if let Some(s) = obj.downcast_ref::<PyStr>() { + // unicode/str → buffer address (c_void_p_from_param: PyUnicode_Check) + s.as_str().as_ptr() as usize + } else if let Some(ptr) = obj.downcast_ref::<PyCPointer>() { + // Pointer instance → contained pointer value + ptr.get_ptr_value() + } else if let Some(simple) = obj.downcast_ref::<PyCSimple>() { + // Simple type (c_void_p, c_char_p, etc.) → value from buffer + let buffer = simple.0.buffer.read(); + super::base::read_ptr_from_buffer(&buffer) + } else if let Some(cdata) = obj.downcast_ref::<PyCData>() { + // Array, Structure, Union → buffer address (b_ptr) + cdata.buffer.read().as_ptr() as usize + } else { + return Err(vm.new_type_error(format!( + "cast() argument 1 must be a ctypes instance, not {}", + obj.class().name() + ))); + }; + + // 3. Create result instance + let result = ctype.call((), vm)?; + + // 4. _objects reference tracking + // Share _objects dict between source and result, add id(src): src + if src.class().fast_issubclass(PyCData::static_type()) { + // Get the source's _objects, create dict if needed + let shared_objects: PyObjectRef = if let Some(src_cdata) = src.downcast_ref::<PyCData>() { + let mut src_objects = src_cdata.objects.write(); + if src_objects.is_none() { + // Create new dict + let dict = vm.ctx.new_dict(); + *src_objects = Some(dict.clone().into()); + dict.into() + } else if let Some(obj) = src_objects.as_ref() { + if obj.downcast_ref::<PyDict>().is_none() { + // Convert to dict (keep existing reference) + let dict = vm.ctx.new_dict(); + let id_key: PyObjectRef = vm.ctx.new_int(obj.get_id() as i64).into(); + let _ = dict.set_item(&*id_key, obj.clone(), vm); + *src_objects = Some(dict.clone().into()); + dict.into() + } else { + obj.clone() + } + } else { + vm.ctx.new_dict().into() + } + } else { + vm.ctx.new_dict().into() + }; + + // Add id(src): src to the shared dict + if let Some(dict) = shared_objects.downcast_ref::<PyDict>() { + let id_key: PyObjectRef = vm.ctx.new_int(src.get_id() as i64).into(); + let _ = dict.set_item(&*id_key, src.clone(), vm); + } + + // Set result's _objects to the shared dict + if let Some(result_cdata) = result.downcast_ref::<PyCData>() { + *result_cdata.objects.write() = Some(shared_objects); + } + } + + // 5. Store pointer value + if let Some(ptr) = result.downcast_ref::<PyCPointer>() { + ptr.set_ptr_value(ptr_value); + } else if let Some(cdata) = result.downcast_ref::<PyCData>() { + let bytes = ptr_value.to_ne_bytes(); + let mut buffer = cdata.buffer.write(); + let buf = buffer.to_mut(); + if buf.len() >= bytes.len() { + buf[..bytes.len()].copy_from_slice(&bytes); + } + } + + Ok(result) +} + +impl PyCFuncPtr { + /// Get function pointer address from buffer + fn get_func_ptr(&self) -> usize { + let buffer = self._base.buffer.read(); + super::base::read_ptr_from_buffer(&buffer) + } + + /// Get CodePtr from buffer for FFI calls + fn get_code_ptr(&self) -> Option<CodePtr> { + let addr = self.get_func_ptr(); + if addr != 0 { + Some(CodePtr(addr as *mut _)) + } else { + None + } + } + + /// Create buffer with function pointer address + fn make_ptr_buffer(addr: usize) -> Vec<u8> { + addr.to_ne_bytes().to_vec() + } +} + impl Constructor for PyCFuncPtr { type Args = FuncArgs; fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // Handle different argument forms like CPython: - // 1. Empty args: create uninitialized + // Handle different argument forms: + // 1. Empty args: create uninitialized (NULL pointer) // 2. One integer argument: function address // 3. Tuple argument: (name, dll) form + // 4. Callable: callback creation + + let ptr_size = std::mem::size_of::<usize>(); if args.args.is_empty() { return PyCFuncPtr { - _base: PyCData::new(CDataObject::from_bytes(vec![], None)), - ptr: PyRwLock::new(None), - needs_free: AtomicCell::new(false), - arg_types: PyRwLock::new(None), - _flags_: AtomicCell::new(0), - res_type: PyRwLock::new(None), - name: PyRwLock::new(None), - handler: vm.ctx.none(), + _base: PyCData::from_bytes(vec![0u8; ptr_size], None), + thunk: PyRwLock::new(None), + callable: PyRwLock::new(None), + converters: PyRwLock::new(None), + argtypes: PyRwLock::new(None), + restype: PyRwLock::new(None), + checker: PyRwLock::new(None), + errcheck: PyRwLock::new(None), + #[cfg(windows)] + index: PyRwLock::new(None), + #[cfg(windows)] + iid: PyRwLock::new(None), + paramflags: PyRwLock::new(None), } .into_ref_with_type(vm, cls) .map(Into::into); @@ -209,18 +784,68 @@ impl Constructor for PyCFuncPtr { let first_arg = &args.args[0]; + // Check for COM method form: (index, name, [paramflags], [iid]) + // First arg is integer (vtable index), second arg is string (method name) + if args.args.len() >= 2 + && first_arg.try_int(vm).is_ok() + && args.args[1].downcast_ref::<PyStr>().is_some() + { + #[cfg(windows)] + let index = first_arg.try_int(vm)?.as_bigint().to_usize().unwrap_or(0); + + // args[3] is iid (GUID struct, optional) + // Also check if args[2] is a GUID (has Data1 attribute) when args[3] is not present + #[cfg(windows)] + let iid = args.args.get(3).cloned().or_else(|| { + args.args.get(2).and_then(|arg| { + // If it's a GUID struct (has Data1 attribute), use it as IID + if arg.get_attr("Data1", vm).is_ok() { + Some(arg.clone()) + } else { + None + } + }) + }); + + // args[2] is paramflags (tuple or None) + let paramflags = args.args.get(2).filter(|arg| !vm.is_none(arg)).cloned(); + + return PyCFuncPtr { + _base: PyCData::from_bytes(vec![0u8; ptr_size], None), + thunk: PyRwLock::new(None), + callable: PyRwLock::new(None), + converters: PyRwLock::new(None), + argtypes: PyRwLock::new(None), + restype: PyRwLock::new(None), + checker: PyRwLock::new(None), + errcheck: PyRwLock::new(None), + #[cfg(windows)] + index: PyRwLock::new(Some(index)), + #[cfg(windows)] + iid: PyRwLock::new(iid), + paramflags: PyRwLock::new(paramflags), + } + .into_ref_with_type(vm, cls) + .map(Into::into); + } + // Check if first argument is an integer (function address) if let Ok(addr) = first_arg.try_int(vm) { let ptr_val = addr.as_bigint().to_usize().unwrap_or(0); return PyCFuncPtr { - _base: PyCData::new(CDataObject::from_bytes(vec![], None)), - ptr: PyRwLock::new(Some(CodePtr(ptr_val as *mut _))), - needs_free: AtomicCell::new(false), - arg_types: PyRwLock::new(None), - _flags_: AtomicCell::new(0), - res_type: PyRwLock::new(None), - name: PyRwLock::new(Some(format!("CFuncPtr@{:#x}", ptr_val))), - handler: vm.ctx.new_int(ptr_val).into(), + _base: PyCData::from_bytes(Self::make_ptr_buffer(ptr_val), None), + thunk: PyRwLock::new(None), + callable: PyRwLock::new(None), + converters: PyRwLock::new(None), + argtypes: PyRwLock::new(None), + restype: PyRwLock::new(None), + checker: PyRwLock::new(None), + errcheck: PyRwLock::new(None), + #[cfg(windows)] + index: PyRwLock::new(None), + #[cfg(windows)] + iid: PyRwLock::new(None), + paramflags: PyRwLock::new(None), } .into_ref_with_type(vm, cls) .map(Into::into); @@ -234,53 +859,58 @@ impl Constructor for PyCFuncPtr { .downcast_ref::<PyStr>() .ok_or(vm.new_type_error("Expected a string"))? .to_string(); - let handler = tuple + let dll = tuple .iter() .nth(1) .ok_or(vm.new_type_error("Expected a tuple with at least 2 elements"))? .clone(); // Get library handle and load function - let handle = handler.try_int(vm); + let handle = dll.try_int(vm); let handle = match handle { Ok(handle) => handle.as_bigint().clone(), - Err(_) => handler + Err(_) => dll .get_attr("_handle", vm)? .try_int(vm)? .as_bigint() .clone(), }; - let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library_cache = super::library::libcache().read(); let library = library_cache .get_lib( handle .to_usize() - .ok_or(vm.new_value_error("Invalid handle".to_string()))?, + .ok_or(vm.new_value_error("Invalid handle"))?, ) - .ok_or_else(|| vm.new_value_error("Library not found".to_string()))?; + .ok_or_else(|| vm.new_value_error("Library not found"))?; let inner_lib = library.lib.lock(); let terminated = format!("{}\0", &name); - let code_ptr = if let Some(lib) = &*inner_lib { + let ptr_val = if let Some(lib) = &*inner_lib { let pointer: Symbol<'_, FP> = unsafe { lib.get(terminated.as_bytes()) .map_err(|err| err.to_string()) .map_err(|err| vm.new_attribute_error(err))? }; - Some(CodePtr(*pointer as *mut _)) + *pointer as usize } else { - None + 0 }; return PyCFuncPtr { - _base: PyCData::new(CDataObject::from_bytes(vec![], None)), - ptr: PyRwLock::new(code_ptr), - needs_free: AtomicCell::new(false), - arg_types: PyRwLock::new(None), - _flags_: AtomicCell::new(0), - res_type: PyRwLock::new(None), - name: PyRwLock::new(Some(name)), - handler, + _base: PyCData::from_bytes(Self::make_ptr_buffer(ptr_val), None), + thunk: PyRwLock::new(None), + callable: PyRwLock::new(None), + converters: PyRwLock::new(None), + argtypes: PyRwLock::new(None), + restype: PyRwLock::new(None), + checker: PyRwLock::new(None), + errcheck: PyRwLock::new(None), + #[cfg(windows)] + index: PyRwLock::new(None), + #[cfg(windows)] + iid: PyRwLock::new(None), + paramflags: PyRwLock::new(None), } .into_ref_with_type(vm, cls) .map(Into::into); @@ -289,42 +919,36 @@ impl Constructor for PyCFuncPtr { // Check if first argument is a Python callable (callback creation) if first_arg.is_callable() { // Get argument types and result type from the class - let argtypes = cls.get_attr(vm.ctx.intern_str("_argtypes_")); - let restype = cls.get_attr(vm.ctx.intern_str("_restype_")); + let class_argtypes = cls.get_attr(vm.ctx.intern_str("_argtypes_")); + let class_restype = cls.get_attr(vm.ctx.intern_str("_restype_")); // Create the thunk (C-callable wrapper for the Python function) - let thunk = PyCThunk::new(first_arg.clone(), argtypes.clone(), restype.clone(), vm)?; + let thunk = PyCThunk::new( + first_arg.clone(), + class_argtypes.clone(), + class_restype.clone(), + vm, + )?; let code_ptr = thunk.code_ptr(); - - // Parse argument types for storage - let arg_type_vec: Option<Vec<PyTypeRef>> = if let Some(ref args) = argtypes { - if vm.is_none(args) { - None - } else { - let mut types = Vec::new(); - for item in args.try_to_value::<Vec<PyObjectRef>>(vm)? { - types.push(item.downcast::<PyType>().map_err(|_| { - vm.new_type_error("_argtypes_ must be a sequence of types".to_string()) - })?); - } - Some(types) - } - } else { - None - }; + let ptr_val = code_ptr.0 as usize; // Store the thunk as a Python object to keep it alive let thunk_ref: PyRef<PyCThunk> = thunk.into_ref(&vm.ctx); return PyCFuncPtr { - _base: PyCData::new(CDataObject::from_bytes(vec![], None)), - ptr: PyRwLock::new(Some(code_ptr)), - needs_free: AtomicCell::new(true), - arg_types: PyRwLock::new(arg_type_vec), - _flags_: AtomicCell::new(0), - res_type: PyRwLock::new(restype), - name: PyRwLock::new(Some("<callback>".to_string())), - handler: thunk_ref.into(), + _base: PyCData::from_bytes(Self::make_ptr_buffer(ptr_val), None), + thunk: PyRwLock::new(Some(thunk_ref)), + callable: PyRwLock::new(Some(first_arg.clone())), + converters: PyRwLock::new(None), + argtypes: PyRwLock::new(class_argtypes), + restype: PyRwLock::new(class_restype), + checker: PyRwLock::new(None), + errcheck: PyRwLock::new(None), + #[cfg(windows)] + index: PyRwLock::new(None), + #[cfg(windows)] + iid: PyRwLock::new(None), + paramflags: PyRwLock::new(None), } .into_ref_with_type(vm, cls) .map(Into::into); @@ -338,142 +962,1054 @@ impl Constructor for PyCFuncPtr { } } -impl Callable for PyCFuncPtr { - type Args = FuncArgs; - fn call(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult { - // This is completely seperate from the C python implementation - - // Cif init - let arg_types: Vec<_> = match zelf.arg_types.read().clone() { - Some(tys) => tys, - None => args - .args - .clone() - .into_iter() - .map(|a| a.class().as_object().to_pyobject(vm).downcast().unwrap()) - .collect(), +// PyCFuncPtr call helpers (similar to callproc.c flow) + +/// Handle internal function addresses (PYFUNCTYPE special cases) +/// Returns Some(result) if handled, None if should continue with normal call +fn handle_internal_func(addr: usize, args: &FuncArgs, vm: &VirtualMachine) -> Option<PyResult> { + if addr == INTERNAL_CAST_ADDR { + let result: PyResult<(PyObjectRef, PyObjectRef, PyObjectRef)> = args.clone().bind(vm); + return Some(result.and_then(|(obj, src, ctype)| cast_impl(obj, src, ctype, vm))); + } + + if addr == INTERNAL_STRING_AT_ADDR { + let result: PyResult<(PyObjectRef, Option<PyObjectRef>)> = args.clone().bind(vm); + return Some(result.and_then(|(ptr_arg, size_arg)| { + let ptr = extract_ptr_from_arg(&ptr_arg, vm)?; + let size = size_arg + .and_then(|s| s.try_int(vm).ok()) + .and_then(|i| i.as_bigint().to_isize()) + .unwrap_or(-1); + string_at_impl(ptr, size, vm) + })); + } + + if addr == INTERNAL_WSTRING_AT_ADDR { + let result: PyResult<(PyObjectRef, Option<PyObjectRef>)> = args.clone().bind(vm); + return Some(result.and_then(|(ptr_arg, size_arg)| { + let ptr = extract_ptr_from_arg(&ptr_arg, vm)?; + let size = size_arg + .and_then(|s| s.try_int(vm).ok()) + .and_then(|i| i.as_bigint().to_isize()) + .unwrap_or(-1); + wstring_at_impl(ptr, size, vm) + })); + } + + None +} + +/// Call information extracted from PyCFuncPtr (argtypes, restype, etc.) +struct CallInfo { + explicit_arg_types: Option<Vec<PyTypeRef>>, + restype_obj: Option<PyObjectRef>, + restype_is_none: bool, + ffi_return_type: Type, + is_pointer_return: bool, +} + +/// Extract call information (argtypes, restype) from PyCFuncPtr +fn extract_call_info(zelf: &Py<PyCFuncPtr>, vm: &VirtualMachine) -> PyResult<CallInfo> { + // Get argtypes - first from instance, then from type's _argtypes_ + let explicit_arg_types: Option<Vec<PyTypeRef>> = + if let Some(argtypes_obj) = zelf.argtypes.read().as_ref() { + if !vm.is_none(argtypes_obj) { + Some( + argtypes_obj + .try_to_value::<Vec<PyObjectRef>>(vm)? + .into_iter() + .filter_map(|obj| obj.downcast::<PyType>().ok()) + .collect(), + ) + } else { + None // argtypes is None -> use ConvParam + } + } else if let Some(class_argtypes) = zelf + .as_object() + .class() + .get_attr(vm.ctx.intern_str("_argtypes_")) + && !vm.is_none(&class_argtypes) + { + Some( + class_argtypes + .try_to_value::<Vec<PyObjectRef>>(vm)? + .into_iter() + .filter_map(|obj| obj.downcast::<PyType>().ok()) + .collect(), + ) + } else { + None // No argtypes -> use ConvParam }; - let ffi_arg_types = arg_types - .clone() - .iter() - .map(|t| ArgumentType::to_ffi_type(t, vm)) - .collect::<PyResult<Vec<_>>>()?; - let return_type = zelf.res_type.read(); - let ffi_return_type = return_type + + // Get restype - first from instance, then from class's _restype_ + let restype_obj = zelf.restype.read().clone().or_else(|| { + zelf.as_object() + .class() + .get_attr(vm.ctx.intern_str("_restype_")) + }); + + // Check if restype is explicitly None (return void) + let restype_is_none = restype_obj.as_ref().is_some_and(|t| vm.is_none(t)); + let ffi_return_type = if restype_is_none { + Type::void() + } else { + restype_obj .as_ref() .and_then(|t| t.clone().downcast::<PyType>().ok()) - .and_then(|t| ReturnType::to_ffi_type(&t)) - .unwrap_or_else(Type::i32); - let cif = Cif::new(ffi_arg_types, ffi_return_type); - - // Call the function - let ffi_args = args - .args - .into_iter() - .enumerate() - .map(|(n, arg)| { - let arg_type = arg_types - .get(n) - .ok_or_else(|| vm.new_type_error("argument amount mismatch".to_string()))?; - arg_type.convert_object(arg, vm) - }) - .collect::<Result<Vec<_>, _>>()?; - let pointer = zelf.ptr.read(); - let code_ptr = pointer - .as_ref() - .ok_or_else(|| vm.new_type_error("Function pointer not set".to_string()))?; - let mut output: c_void = unsafe { cif.call(*code_ptr, &ffi_args) }; - let return_type = return_type + .and_then(|t| ReturnType::to_ffi_type(&t, vm)) + .unwrap_or_else(Type::i32) + }; + + // Check if return type is a pointer type (P, z, Z) - need special handling on 64-bit + let is_pointer_return = restype_obj + .as_ref() + .and_then(|t| t.clone().downcast::<PyType>().ok()) + .and_then(|t| t.as_object().get_attr(vm.ctx.intern_str("_type_"), vm).ok()) + .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())) + .is_some_and(|tc| matches!(tc.as_str(), "P" | "z" | "Z")); + + Ok(CallInfo { + explicit_arg_types, + restype_obj, + restype_is_none, + ffi_return_type, + is_pointer_return, + }) +} + +/// Parsed paramflags: (direction, name, default) tuples +/// direction: 1=IN, 2=OUT, 4=IN|OUT (or 1|2=3) +type ParsedParamFlags = Vec<(u32, Option<String>, Option<PyObjectRef>)>; + +/// Parse paramflags from PyCFuncPtr +fn parse_paramflags( + zelf: &Py<PyCFuncPtr>, + vm: &VirtualMachine, +) -> PyResult<Option<ParsedParamFlags>> { + let Some(pf) = zelf.paramflags.read().as_ref().cloned() else { + return Ok(None); + }; + + let pf_vec = pf.try_to_value::<Vec<PyObjectRef>>(vm)?; + let parsed = pf_vec + .into_iter() + .map(|item| { + let Some(tuple) = item.downcast_ref::<PyTuple>() else { + // Single value means just the direction + let direction = item + .try_int(vm) + .ok() + .and_then(|i| i.as_bigint().to_u32()) + .unwrap_or(1); + return (direction, None, None); + }; + let direction = tuple + .first() + .and_then(|d| d.try_int(vm).ok()) + .and_then(|i| i.as_bigint().to_u32()) + .unwrap_or(1); + let name = tuple + .get(1) + .and_then(|n| n.downcast_ref::<PyStr>().map(|s| s.to_string())); + let default = tuple.get(2).cloned(); + (direction, name, default) + }) + .collect(); + Ok(Some(parsed)) +} + +/// Resolve COM method pointer from vtable (Windows only) +/// Returns (Some(CodePtr), true) if this is a COM method call, (None, false) otherwise +#[cfg(windows)] +fn resolve_com_method( + zelf: &Py<PyCFuncPtr>, + args: &FuncArgs, + vm: &VirtualMachine, +) -> PyResult<(Option<CodePtr>, bool)> { + let com_index = zelf.index.read(); + let Some(idx) = *com_index else { + return Ok((None, false)); + }; + + // First arg must be the COM object pointer + if args.args.is_empty() { + return Err( + vm.new_type_error("COM method requires at least one argument (self)".to_string()) + ); + } + + // Extract COM pointer value from first argument + let self_arg = &args.args[0]; + let com_ptr = if let Some(simple) = self_arg.downcast_ref::<PyCSimple>() { + let buffer = simple.0.buffer.read(); + if buffer.len() >= std::mem::size_of::<usize>() { + super::base::read_ptr_from_buffer(&buffer) + } else { + 0 + } + } else if let Ok(int_val) = self_arg.try_int(vm) { + int_val.as_bigint().to_usize().unwrap_or(0) + } else { + return Err( + vm.new_type_error("COM method first argument must be a COM pointer".to_string()) + ); + }; + + if com_ptr == 0 { + return Err(vm.new_value_error("NULL COM pointer access")); + } + + // Read vtable pointer from COM object: vtable = *(void**)com_ptr + let vtable_ptr = unsafe { *(com_ptr as *const usize) }; + if vtable_ptr == 0 { + return Err(vm.new_value_error("NULL vtable pointer")); + } + + // Read function pointer from vtable: func = vtable[index] + let fptr = unsafe { + let vtable = vtable_ptr as *const usize; + *vtable.add(idx) + }; + + if fptr == 0 { + return Err(vm.new_value_error("NULL function pointer in vtable")); + } + + Ok((Some(CodePtr(fptr as *mut _)), true)) +} + +/// Prepared arguments for FFI call +struct PreparedArgs { + ffi_arg_types: Vec<Type>, + ffi_values: Vec<FfiArgValue>, + out_buffers: Vec<(usize, PyObjectRef)>, +} + +/// Get buffer address from a ctypes object +fn get_buffer_addr(obj: &PyObjectRef) -> Option<usize> { + obj.downcast_ref::<PyCSimple>() + .map(|s| s.0.buffer.read().as_ptr() as usize) + .or_else(|| { + obj.downcast_ref::<super::structure::PyCStructure>() + .map(|s| s.0.buffer.read().as_ptr() as usize) + }) + .or_else(|| { + obj.downcast_ref::<PyCPointer>() + .map(|s| s.0.buffer.read().as_ptr() as usize) + }) +} + +/// Create OUT buffer for a parameter type +fn create_out_buffer(arg_type: &PyTypeRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // For POINTER(T) types, create T instance (the pointed-to type) + if arg_type.fast_issubclass(PyCPointer::static_type()) + && let Some(stg_info) = arg_type.stg_info_opt() + && let Some(ref proto) = stg_info.proto + { + return proto.as_object().call((), vm); + } + // Not a pointer type or no proto, create instance directly + arg_type.as_object().call((), vm) +} + +/// Build callargs when no argtypes specified (use ConvParam) +fn build_callargs_no_argtypes(args: &FuncArgs, vm: &VirtualMachine) -> PyResult<PreparedArgs> { + let results: Vec<(Type, FfiArgValue)> = args + .args + .iter() + .map(|arg| conv_param(arg, vm)) + .collect::<PyResult<Vec<_>>>()?; + let (ffi_arg_types, ffi_values) = results.into_iter().unzip(); + Ok(PreparedArgs { + ffi_arg_types, + ffi_values, + out_buffers: Vec::new(), + }) +} + +/// Build callargs for regular function with argtypes (no paramflags) +fn build_callargs_simple( + args: &FuncArgs, + arg_types: &[PyTypeRef], + vm: &VirtualMachine, +) -> PyResult<PreparedArgs> { + let ffi_arg_types = arg_types + .iter() + .map(|t| ArgumentType::to_ffi_type(t, vm)) + .collect::<PyResult<Vec<_>>>()?; + let ffi_values = args + .args + .iter() + .enumerate() + .map(|(n, arg)| { + let arg_type = arg_types + .get(n) + .ok_or_else(|| vm.new_type_error("argument amount mismatch"))?; + arg_type.convert_object(arg.clone(), vm) + }) + .collect::<Result<Vec<_>, _>>()?; + Ok(PreparedArgs { + ffi_arg_types, + ffi_values, + out_buffers: Vec::new(), + }) +} + +/// Build callargs with paramflags (handles IN/OUT parameters) +fn build_callargs_with_paramflags( + args: &FuncArgs, + arg_types: &[PyTypeRef], + paramflags: &ParsedParamFlags, + skip_first_arg: bool, // true for COM methods + vm: &VirtualMachine, +) -> PyResult<PreparedArgs> { + let mut ffi_arg_types = Vec::new(); + let mut ffi_values = Vec::new(); + let mut out_buffers = Vec::new(); + + // For COM methods, first arg is self (pointer) + let mut caller_arg_idx = if skip_first_arg { + ffi_arg_types.push(Type::pointer()); + if !args.args.is_empty() { + ffi_values.push(conv_param(&args.args[0], vm)?.1); + } + 1usize + } else { + 0usize + }; + + // Add FFI types for all argtypes + for arg_type in arg_types { + ffi_arg_types.push(ArgumentType::to_ffi_type(arg_type, vm)?); + } + + // Process parameters based on paramflags + for (param_idx, (direction, _name, default)) in paramflags.iter().enumerate() { + let arg_type = arg_types + .get(param_idx) + .ok_or_else(|| vm.new_type_error("paramflags/argtypes mismatch"))?; + + let is_out = (*direction & 2) != 0; // OUT flag + let is_in = (*direction & 1) != 0 || *direction == 0; // IN flag or default + + if is_out && !is_in { + // Pure OUT parameter: create buffer, don't consume caller arg + let buffer = create_out_buffer(arg_type, vm)?; + let addr = get_buffer_addr(&buffer).ok_or_else(|| { + vm.new_type_error("Cannot create OUT buffer for this type".to_string()) + })?; + ffi_values.push(FfiArgValue::Pointer(addr)); + out_buffers.push((param_idx, buffer)); + } else { + // IN or IN|OUT: get from caller args or default + let arg = if caller_arg_idx < args.args.len() { + caller_arg_idx += 1; + args.args[caller_arg_idx - 1].clone() + } else if let Some(def) = default { + def.clone() + } else { + return Err(vm.new_type_error(format!("required argument {} missing", param_idx))); + }; + + if is_out { + // IN|OUT: track for return + out_buffers.push((param_idx, arg.clone())); + } + ffi_values.push(arg_type.convert_object(arg, vm)?); + } + } + + Ok(PreparedArgs { + ffi_arg_types, + ffi_values, + out_buffers, + }) +} + +/// Build call arguments (main dispatcher) +fn build_callargs( + args: &FuncArgs, + call_info: &CallInfo, + paramflags: Option<&ParsedParamFlags>, + is_com_method: bool, + vm: &VirtualMachine, +) -> PyResult<PreparedArgs> { + let Some(ref arg_types) = call_info.explicit_arg_types else { + // No argtypes: use ConvParam + return build_callargs_no_argtypes(args, vm); + }; + + if let Some(pflags) = paramflags { + // Has paramflags: handle IN/OUT + build_callargs_with_paramflags(args, arg_types, pflags, is_com_method, vm) + } else if is_com_method { + // COM method without paramflags + let mut ffi_types = vec![Type::pointer()]; + ffi_types.extend( + arg_types + .iter() + .map(|t| ArgumentType::to_ffi_type(t, vm)) + .collect::<PyResult<Vec<_>>>()?, + ); + let mut ffi_vals = Vec::new(); + if !args.args.is_empty() { + ffi_vals.push(conv_param(&args.args[0], vm)?.1); + } + for (n, arg) in args.args.iter().skip(1).enumerate() { + let arg_type = arg_types + .get(n) + .ok_or_else(|| vm.new_type_error("argument amount mismatch"))?; + ffi_vals.push(arg_type.convert_object(arg.clone(), vm)?); + } + Ok(PreparedArgs { + ffi_arg_types: ffi_types, + ffi_values: ffi_vals, + out_buffers: Vec::new(), + }) + } else { + // Regular function + build_callargs_simple(args, arg_types, vm) + } +} + +/// Raw result from FFI call +enum RawResult { + Void, + Pointer(usize), + Value(libffi::low::ffi_arg), +} + +/// Execute FFI call +fn ctypes_callproc(code_ptr: CodePtr, prepared: &PreparedArgs, call_info: &CallInfo) -> RawResult { + let cif = Cif::new( + prepared.ffi_arg_types.clone(), + call_info.ffi_return_type.clone(), + ); + let ffi_args: Vec<Arg> = prepared.ffi_values.iter().map(|v| v.as_arg()).collect(); + + if call_info.restype_is_none { + unsafe { cif.call::<()>(code_ptr, &ffi_args) }; + RawResult::Void + } else if call_info.is_pointer_return { + let result = unsafe { cif.call::<usize>(code_ptr, &ffi_args) }; + RawResult::Pointer(result) + } else { + let result = unsafe { cif.call::<libffi::low::ffi_arg>(code_ptr, &ffi_args) }; + RawResult::Value(result) + } +} + +/// Check and handle HRESULT errors (Windows) +#[cfg(windows)] +fn check_hresult(hresult: i32, zelf: &Py<PyCFuncPtr>, vm: &VirtualMachine) -> PyResult<()> { + if hresult >= 0 { + return Ok(()); + } + + if zelf.iid.read().is_some() { + // Raise COMError + let ctypes_module = vm.import("_ctypes", 0)?; + let com_error_type = ctypes_module.get_attr("COMError", vm)?; + let com_error_type = com_error_type + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("COMError is not a type"))?; + let hresult_obj: PyObjectRef = vm.ctx.new_int(hresult).into(); + let text: PyObjectRef = vm + .ctx + .new_str(format!("HRESULT: 0x{:08X}", hresult as u32)) + .into(); + let details: PyObjectRef = vm.ctx.none(); + let exc = vm.invoke_exception( + com_error_type.to_owned(), + vec![text.clone(), details.clone()], + )?; + let _ = exc.as_object().set_attr("hresult", hresult_obj, vm); + let _ = exc.as_object().set_attr("text", text, vm); + let _ = exc.as_object().set_attr("details", details, vm); + Err(exc) + } else { + // Raise OSError + let exc = vm.new_os_error(format!("HRESULT: 0x{:08X}", hresult as u32)); + let _ = exc + .as_object() + .set_attr("winerror", vm.ctx.new_int(hresult), vm); + Err(exc) + } +} + +/// Convert raw FFI result to Python object +fn convert_raw_result( + raw_result: &mut RawResult, + call_info: &CallInfo, + vm: &VirtualMachine, +) -> Option<PyObjectRef> { + match raw_result { + RawResult::Void => None, + RawResult::Pointer(ptr) => { + // Get type code from restype to determine conversion method + let type_code = call_info + .restype_obj + .as_ref() + .and_then(|t| t.clone().downcast::<PyType>().ok()) + .and_then(|t| t.as_object().get_attr(vm.ctx.intern_str("_type_"), vm).ok()) + .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())); + + match type_code.as_deref() { + Some("z") => { + // c_char_p: NULL -> None, otherwise read C string -> bytes + if *ptr == 0 { + Some(vm.ctx.none()) + } else { + let cstr = unsafe { std::ffi::CStr::from_ptr(*ptr as _) }; + Some(vm.ctx.new_bytes(cstr.to_bytes().to_vec()).into()) + } + } + Some("Z") => { + // c_wchar_p: NULL -> None, otherwise read wide string -> str + if *ptr == 0 { + Some(vm.ctx.none()) + } else { + let wstr_ptr = *ptr as *const libc::wchar_t; + let mut len = 0; + unsafe { + while *wstr_ptr.add(len) != 0 { + len += 1; + } + } + let slice = unsafe { std::slice::from_raw_parts(wstr_ptr, len) }; + let s: String = slice + .iter() + .filter_map(|&c| char::from_u32(c as u32)) + .collect(); + Some(vm.ctx.new_str(s).into()) + } + } + _ => { + // c_void_p ("P") and other pointer types: NULL -> None, otherwise int + if *ptr == 0 { + Some(vm.ctx.none()) + } else { + Some(vm.ctx.new_int(*ptr).into()) + } + } + } + } + RawResult::Value(val) => call_info + .restype_obj .as_ref() .and_then(|f| f.clone().downcast::<PyType>().ok()) - .map(|f| f.from_ffi_type(&mut output, vm).ok().flatten()) - .unwrap_or_else(|| Some(vm.ctx.new_int(output as i32).as_object().to_pyobject(vm))); - if let Some(return_type) = return_type { - Ok(return_type) - } else { - Ok(vm.ctx.none()) + .map(|f| { + f.from_ffi_type(val as *mut _ as *mut c_void, vm) + .ok() + .flatten() + }) + .unwrap_or_else(|| Some(vm.ctx.new_int(*val as usize).as_object().to_pyobject(vm))), + } +} + +/// Extract values from OUT buffers +fn extract_out_values( + out_buffers: Vec<(usize, PyObjectRef)>, + vm: &VirtualMachine, +) -> Vec<PyObjectRef> { + out_buffers + .into_iter() + .map(|(_, buffer)| buffer.get_attr("value", vm).unwrap_or(buffer)) + .collect() +} + +/// Build final result (main function) +fn build_result( + mut raw_result: RawResult, + call_info: &CallInfo, + prepared: PreparedArgs, + zelf: &Py<PyCFuncPtr>, + args: &FuncArgs, + vm: &VirtualMachine, +) -> PyResult { + // Check HRESULT on Windows + #[cfg(windows)] + if let RawResult::Value(val) = raw_result { + let is_hresult = call_info + .restype_obj + .as_ref() + .and_then(|t| t.clone().downcast::<PyType>().ok()) + .is_some_and(|t| t.name().to_string() == "HRESULT"); + if is_hresult { + check_hresult(val as i32, zelf, vm)?; } } + + // Convert raw result to Python object + let mut result = convert_raw_result(&mut raw_result, call_info, vm); + + // Apply errcheck if set + if let Some(errcheck) = zelf.errcheck.read().as_ref() { + let args_tuple = PyTuple::new_ref(args.args.clone(), &vm.ctx); + let func_obj = zelf.as_object().to_owned(); + let result_obj = result.clone().unwrap_or_else(|| vm.ctx.none()); + result = Some(errcheck.call((result_obj, func_obj, args_tuple), vm)?); + } + + // Handle OUT parameter return values + if prepared.out_buffers.is_empty() { + return result.map(Ok).unwrap_or_else(|| Ok(vm.ctx.none())); + } + + let out_values = extract_out_values(prepared.out_buffers, vm); + Ok(match <[PyObjectRef; 1]>::try_from(out_values) { + Ok([single]) => single, + Err(v) => PyTuple::new_ref(v, &vm.ctx).into(), + }) } +impl Callable for PyCFuncPtr { + type Args = FuncArgs; + fn call(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // 1. Check for internal PYFUNCTYPE addresses + if let Some(result) = handle_internal_func(zelf.get_func_ptr(), &args, vm) { + return result; + } + + // 2. Resolve function pointer (COM or direct) + #[cfg(windows)] + let (func_ptr, is_com_method) = resolve_com_method(zelf, &args, vm)?; + #[cfg(not(windows))] + let (func_ptr, is_com_method) = (None::<CodePtr>, false); + + // 3. Extract call info (argtypes, restype) + let call_info = extract_call_info(zelf, vm)?; + + // 4. Parse paramflags + let paramflags = parse_paramflags(zelf, vm)?; + + // 5. Build call arguments + let prepared = build_callargs(&args, &call_info, paramflags.as_ref(), is_com_method, vm)?; + + // 6. Get code pointer + let code_ptr = match func_ptr.or_else(|| zelf.get_code_ptr()) { + Some(cp) => cp, + None => { + debug_assert!(false, "NULL function pointer"); + // In release mode, this will crash like CPython + CodePtr(std::ptr::null_mut()) + } + }; + + // 7. Call the function + let raw_result = ctypes_callproc(code_ptr, &prepared, &call_info); + + // 8. Build result + build_result(raw_result, &call_info, prepared, zelf, &args, vm) + } +} + +// PyCFuncPtr_repr impl Representable for PyCFuncPtr { fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { - let index = zelf.ptr.read(); - let index = index.map(|ptr| ptr.0 as usize).unwrap_or(0); let type_name = zelf.class().name(); - if cfg!(windows) { - let index = index - 0x1000; - Ok(format!("<COM method offset {index:#x} {type_name}>")) - } else { - Ok(format!("<{type_name} object at {index:#x}>")) - } + // Use object id, not function pointer address + let addr = zelf.get_id(); + Ok(format!("<{} object at {:#x}>", type_name, addr)) } } -// TODO: fix -unsafe impl Send for PyCFuncPtr {} -unsafe impl Sync for PyCFuncPtr {} - #[pyclass(flags(BASETYPE), with(Callable, Constructor, Representable))] impl PyCFuncPtr { - #[pygetset(name = "_restype_")] + // restype getter/setter + #[pygetset] fn restype(&self) -> Option<PyObjectRef> { - self.res_type.read().as_ref().cloned() + self.restype.read().clone() } - #[pygetset(name = "_restype_", setter)] - fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - // has to be type, callable, or none - // TODO: Callable support - if vm.is_none(&restype) || restype.downcast_ref::<PyType>().is_some() { - *self.res_type.write() = Some(restype); - Ok(()) + #[pygetset(setter)] + fn set_restype(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // Must be type, callable, or None + if vm.is_none(&value) { + *self.restype.write() = None; + } else if value.downcast_ref::<PyType>().is_some() || value.is_callable() { + *self.restype.write() = Some(value); } else { - Err(vm.new_type_error("restype must be a type, a callable, or None".to_string())) + return Err(vm.new_type_error("restype must be a type, a callable, or None")); } + Ok(()) } - #[pygetset(name = "argtypes")] - fn argtypes(&self, vm: &VirtualMachine) -> PyTupleRef { - PyTuple::new_ref( - self.arg_types - .read() - .clone() - .unwrap_or_default() - .into_iter() - .map(|t| t.to_pyobject(vm)) - .collect(), - &vm.ctx, - ) + // argtypes getter/setter + #[pygetset] + fn argtypes(&self, vm: &VirtualMachine) -> PyObjectRef { + self.argtypes + .read() + .clone() + .unwrap_or_else(|| vm.ctx.empty_tuple.clone().into()) } #[pygetset(name = "argtypes", setter)] - fn set_argtypes(&self, argtypes: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - let none = vm.is_none(&argtypes); - if none { - *self.arg_types.write() = None; - Ok(()) + fn set_argtypes(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + if vm.is_none(&value) { + *self.argtypes.write() = None; } else { - let tuple = argtypes.downcast::<PyTuple>().unwrap(); - *self.arg_types.write() = Some( - tuple - .iter() - .map(|obj| obj.clone().downcast::<PyType>().unwrap()) - .collect::<Vec<_>>(), - ); - Ok(()) + // Store the argtypes object directly as it is + *self.argtypes.write() = Some(value); } + Ok(()) } + // errcheck getter/setter #[pygetset] - fn __name__(&self) -> Option<String> { - self.name.read().clone() + fn errcheck(&self) -> Option<PyObjectRef> { + self.errcheck.read().clone() } #[pygetset(setter)] - fn set___name__(&self, name: String) -> PyResult<()> { - *self.name.write() = Some(name); - // TODO: update handle and stuff + fn set_errcheck(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + if vm.is_none(&value) { + *self.errcheck.write() = None; + } else if value.is_callable() { + *self.errcheck.write() = Some(value); + } else { + return Err(vm.new_type_error("errcheck must be a callable or None")); + } Ok(()) } + + // _flags_ getter (read-only, from type's class attribute or StgInfo) + #[pygetset] + fn _flags_(zelf: &Py<Self>, vm: &VirtualMachine) -> u32 { + // First try to get _flags_ from type's class attribute (for dynamically created types) + // This is how CDLL sets use_errno: class _FuncPtr(_CFuncPtr): _flags_ = flags + if let Ok(flags_attr) = zelf.class().as_object().get_attr("_flags_", vm) + && let Ok(flags_int) = flags_attr.try_to_value::<u32>(vm) + { + return flags_int; + } + + // Fallback to StgInfo for native types + use super::base::StgInfoFlags; + zelf.class() + .stg_info_opt() + .map(|stg| stg.flags.bits()) + .unwrap_or(StgInfoFlags::empty().bits()) + } + + // bool conversion - check if function pointer is set + #[pymethod] + fn __bool__(&self) -> bool { + self.get_func_ptr() != 0 + } +} + +// CThunkObject - FFI callback (thunk) implementation + +/// Userdata passed to the libffi callback. +struct ThunkUserData { + /// The Python callable to invoke + callable: PyObjectRef, + /// Argument types for conversion + arg_types: Vec<PyTypeRef>, + /// Result type for conversion (None means void) + res_type: Option<PyTypeRef>, +} + +/// Check if ty is a subclass of a simple type (like MyInt(c_int)). +fn is_simple_subclass(ty: &Py<PyType>, vm: &VirtualMachine) -> bool { + let Ok(base) = ty.as_object().get_attr(vm.ctx.intern_str("__base__"), vm) else { + return false; + }; + base.get_attr(vm.ctx.intern_str("_type_"), vm).is_ok() +} + +/// Convert a C value to a Python object based on the type code. +fn ffi_to_python(ty: &Py<PyType>, ptr: *const c_void, vm: &VirtualMachine) -> PyObjectRef { + let type_code = ty.type_code(vm); + let raw_value: PyObjectRef = unsafe { + match type_code.as_deref() { + Some("b") => vm.ctx.new_int(*(ptr as *const i8) as i32).into(), + Some("B") => vm.ctx.new_int(*(ptr as *const u8) as i32).into(), + Some("c") => vm.ctx.new_bytes(vec![*(ptr as *const u8)]).into(), + Some("h") => vm.ctx.new_int(*(ptr as *const i16) as i32).into(), + Some("H") => vm.ctx.new_int(*(ptr as *const u16) as i32).into(), + Some("i") => vm.ctx.new_int(*(ptr as *const i32)).into(), + Some("I") => vm.ctx.new_int(*(ptr as *const u32)).into(), + Some("l") => vm.ctx.new_int(*(ptr as *const libc::c_long)).into(), + Some("L") => vm.ctx.new_int(*(ptr as *const libc::c_ulong)).into(), + Some("q") => vm.ctx.new_int(*(ptr as *const libc::c_longlong)).into(), + Some("Q") => vm.ctx.new_int(*(ptr as *const libc::c_ulonglong)).into(), + Some("f") => vm.ctx.new_float(*(ptr as *const f32) as f64).into(), + Some("d") => vm.ctx.new_float(*(ptr as *const f64)).into(), + Some("z") => { + // c_char_p: C string pointer → Python bytes + let cstr_ptr = *(ptr as *const *const libc::c_char); + if cstr_ptr.is_null() { + vm.ctx.none() + } else { + let cstr = std::ffi::CStr::from_ptr(cstr_ptr); + vm.ctx.new_bytes(cstr.to_bytes().to_vec()).into() + } + } + Some("Z") => { + // c_wchar_p: wchar_t* → Python str + let wstr_ptr = *(ptr as *const *const libc::wchar_t); + if wstr_ptr.is_null() { + vm.ctx.none() + } else { + let mut len = 0; + while *wstr_ptr.add(len) != 0 { + len += 1; + } + let slice = std::slice::from_raw_parts(wstr_ptr, len); + let s: String = slice + .iter() + .filter_map(|&c| char::from_u32(c as u32)) + .collect(); + vm.ctx.new_str(s).into() + } + } + Some("P") => vm.ctx.new_int(*(ptr as *const usize)).into(), + Some("?") => vm.ctx.new_bool(*(ptr as *const u8) != 0).into(), + _ => return vm.ctx.none(), + } + }; + + if !is_simple_subclass(ty, vm) { + return raw_value; + } + ty.as_object() + .call((raw_value.clone(),), vm) + .unwrap_or(raw_value) +} + +/// Convert a Python object to a C value and store it at the result pointer +fn python_to_ffi(obj: PyResult, ty: &Py<PyType>, result: *mut c_void, vm: &VirtualMachine) { + let Ok(obj) = obj else { return }; + + let type_code = ty.type_code(vm); + unsafe { + match type_code.as_deref() { + Some("b") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut i8) = i.as_bigint().to_i8().unwrap_or(0); + } + } + Some("B") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0); + } + } + Some("c") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0); + } + } + Some("h") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut i16) = i.as_bigint().to_i16().unwrap_or(0); + } + } + Some("H") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u16) = i.as_bigint().to_u16().unwrap_or(0); + } + } + Some("i") => { + if let Ok(i) = obj.try_int(vm) { + let val = i.as_bigint().to_i32().unwrap_or(0); + *(result as *mut libffi::low::ffi_arg) = val as libffi::low::ffi_arg; + } + } + Some("I") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u32) = i.as_bigint().to_u32().unwrap_or(0); + } + } + Some("l") | Some("q") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut i64) = i.as_bigint().to_i64().unwrap_or(0); + } + } + Some("L") | Some("Q") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u64) = i.as_bigint().to_u64().unwrap_or(0); + } + } + Some("f") => { + if let Ok(f) = obj.try_float(vm) { + *(result as *mut f32) = f.to_f64() as f32; + } + } + Some("d") => { + if let Ok(f) = obj.try_float(vm) { + *(result as *mut f64) = f.to_f64(); + } + } + Some("P") | Some("z") | Some("Z") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut usize) = i.as_bigint().to_usize().unwrap_or(0); + } + } + Some("?") => { + if let Ok(b) = obj.is_true(vm) { + *(result as *mut u8) = u8::from(b); + } + } + _ => {} + } + } +} + +/// The callback function that libffi calls when the closure is invoked. +unsafe extern "C" fn thunk_callback( + _cif: &low::ffi_cif, + result: &mut c_void, + args: *const *const c_void, + userdata: &ThunkUserData, +) { + with_current_vm(|vm| { + let py_args: Vec<PyObjectRef> = userdata + .arg_types + .iter() + .enumerate() + .map(|(i, ty)| { + let arg_ptr = unsafe { *args.add(i) }; + ffi_to_python(ty, arg_ptr, vm) + }) + .collect(); + + let py_result = userdata.callable.call(py_args, vm); + + // Call unraisable hook if exception occurred + if let Err(exc) = &py_result { + let repr = userdata + .callable + .repr(vm) + .map(|s| s.to_string()) + .unwrap_or_else(|_| "<unknown>".to_string()); + let msg = format!( + "Exception ignored on calling ctypes callback function {}", + repr + ); + vm.run_unraisable(exc.clone(), Some(msg), vm.ctx.none()); + } + + if let Some(ref res_type) = userdata.res_type { + python_to_ffi(py_result, res_type, result as *mut c_void, vm); + } + }); +} + +/// Holds the closure and userdata together to ensure proper lifetime. +struct ThunkData { + #[allow(dead_code)] + closure: Closure<'static>, + userdata_ptr: *mut ThunkUserData, +} + +impl Drop for ThunkData { + fn drop(&mut self) { + unsafe { + drop(Box::from_raw(self.userdata_ptr)); + } + } +} + +/// CThunkObject wraps a Python callable to make it callable from C code. +#[pyclass(name = "CThunkObject", module = "_ctypes")] +#[derive(PyPayload)] +pub(super) struct PyCThunk { + callable: PyObjectRef, + #[allow(dead_code)] + thunk_data: PyRwLock<Option<ThunkData>>, + code_ptr: CodePtr, +} + +impl Debug for PyCThunk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyCThunk") + .field("callable", &self.callable) + .finish() + } +} + +impl PyCThunk { + pub fn new( + callable: PyObjectRef, + arg_types: Option<PyObjectRef>, + res_type: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<Self> { + let arg_type_vec: Vec<PyTypeRef> = match arg_types { + Some(args) if !vm.is_none(&args) => args + .try_to_value::<Vec<PyObjectRef>>(vm)? + .into_iter() + .map(|item| { + item.downcast::<PyType>() + .map_err(|_| vm.new_type_error("_argtypes_ must be a sequence of types")) + }) + .collect::<PyResult<Vec<_>>>()?, + _ => Vec::new(), + }; + + let res_type_ref: Option<PyTypeRef> = match res_type { + Some(ref rt) if !vm.is_none(rt) => Some( + rt.clone() + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("restype must be a ctypes type"))?, + ), + _ => None, + }; + + let ffi_arg_types: Vec<Type> = arg_type_vec + .iter() + .map(|ty| { + ty.type_code(vm) + .and_then(|code| get_ffi_type(&code)) + .unwrap_or(Type::pointer()) + }) + .collect(); + + let ffi_res_type = res_type_ref + .as_ref() + .and_then(|ty| ty.type_code(vm)) + .and_then(|code| get_ffi_type(&code)) + .unwrap_or(Type::void()); + + let cif = Cif::new(ffi_arg_types, ffi_res_type); + + let userdata = Box::new(ThunkUserData { + callable: callable.clone(), + arg_types: arg_type_vec, + res_type: res_type_ref, + }); + let userdata_ptr = Box::into_raw(userdata); + let userdata_ref: &'static ThunkUserData = unsafe { &*userdata_ptr }; + + let closure = Closure::new(cif, thunk_callback, userdata_ref); + let code_ptr = CodePtr(*closure.code_ptr() as *mut _); + + let thunk_data = ThunkData { + closure, + userdata_ptr, + }; + + Ok(Self { + callable, + thunk_data: PyRwLock::new(Some(thunk_data)), + code_ptr, + }) + } + + pub fn code_ptr(&self) -> CodePtr { + self.code_ptr + } +} + +unsafe impl Send for PyCThunk {} +unsafe impl Sync for PyCThunk {} + +#[pyclass] +impl PyCThunk { + #[pygetset] + fn callable(&self) -> PyObjectRef { + self.callable.clone() + } } diff --git a/crates/vm/src/stdlib/ctypes/library.rs b/crates/vm/src/stdlib/ctypes/library.rs index e918470b6c8..ec8ca91af0d 100644 --- a/crates/vm/src/stdlib/ctypes/library.rs +++ b/crates/vm/src/stdlib/ctypes/library.rs @@ -2,12 +2,12 @@ use crate::VirtualMachine; use libloading::Library; use rustpython_common::lock::{PyMutex, PyRwLock}; use std::collections::HashMap; -use std::ffi::c_void; +use std::ffi::{OsStr, c_void}; use std::fmt; use std::ptr::null; -pub struct SharedLibrary { - pub(crate) lib: PyMutex<Option<Library>>, +pub(super) struct SharedLibrary { + pub(super) lib: PyMutex<Option<Library>>, } impl fmt::Debug for SharedLibrary { @@ -17,13 +17,13 @@ impl fmt::Debug for SharedLibrary { } impl SharedLibrary { - pub fn new(name: &str) -> Result<SharedLibrary, libloading::Error> { + fn new(name: impl AsRef<OsStr>) -> Result<SharedLibrary, libloading::Error> { Ok(SharedLibrary { - lib: PyMutex::new(unsafe { Some(Library::new(name)?) }), + lib: PyMutex::new(unsafe { Some(Library::new(name.as_ref())?) }), }) } - pub fn get_pointer(&self) -> usize { + fn get_pointer(&self) -> usize { let lib_lock = self.lib.lock(); if let Some(l) = &*lib_lock { l as *const Library as usize @@ -32,12 +32,12 @@ impl SharedLibrary { } } - pub fn is_closed(&self) -> bool { + fn is_closed(&self) -> bool { let lib_lock = self.lib.lock(); lib_lock.is_none() } - pub fn close(&self) { + fn close(&self) { *self.lib.lock() = None; } } @@ -48,25 +48,24 @@ impl Drop for SharedLibrary { } } -pub struct ExternalLibs { +pub(super) struct ExternalLibs { libraries: HashMap<usize, SharedLibrary>, } impl ExternalLibs { - pub fn new() -> Self { + fn new() -> Self { Self { libraries: HashMap::new(), } } - #[allow(dead_code)] pub fn get_lib(&self, key: usize) -> Option<&SharedLibrary> { self.libraries.get(&key) } pub fn get_or_insert_lib( &mut self, - library_path: &str, + library_path: impl AsRef<OsStr>, _vm: &VirtualMachine, ) -> Result<(usize, &SharedLibrary), libloading::Error> { let new_lib = SharedLibrary::new(library_path)?; @@ -83,7 +82,7 @@ impl ExternalLibs { } }; - Ok((key, self.libraries.get(&key).unwrap())) + Ok((key, self.libraries.get(&key).expect("just inserted"))) } pub fn drop_lib(&mut self, key: usize) { @@ -91,10 +90,9 @@ impl ExternalLibs { } } -rustpython_common::static_cell! { - static LIBCACHE: PyRwLock<ExternalLibs>; -} - -pub fn libcache() -> &'static PyRwLock<ExternalLibs> { +pub(super) fn libcache() -> &'static PyRwLock<ExternalLibs> { + rustpython_common::static_cell! { + static LIBCACHE: PyRwLock<ExternalLibs>; + } LIBCACHE.get_or_init(|| PyRwLock::new(ExternalLibs::new())) } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 735034e7936..3ee39af3a7a 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -1,37 +1,164 @@ -use num_traits::ToPrimitive; -use rustpython_common::lock::PyRwLock; - -use crate::builtins::{PyType, PyTypeRef}; -use crate::function::FuncArgs; +use super::{PyCArray, PyCData, PyCSimple, PyCStructure, StgInfo, StgInfoFlags}; use crate::protocol::PyNumberMethods; -use crate::stdlib::ctypes::{CDataObject, PyCData}; -use crate::types::{AsNumber, Constructor}; -use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::types::{AsNumber, Constructor, Initializer}; +use crate::{ + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + builtins::{PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef}, + class::StaticType, + function::{FuncArgs, OptionalArg}, +}; +use num_traits::ToPrimitive; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] #[derive(Debug)] #[repr(transparent)] -pub struct PyCPointerType(PyType); +pub(super) struct PyCPointerType(PyType); + +impl Initializer for PyCPointerType { + type Args = FuncArgs; + + fn init(zelf: crate::PyRef<Self>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + // Get the type as PyTypeRef + let obj: PyObjectRef = zelf.clone().into(); + let new_type: PyTypeRef = obj + .downcast() + .map_err(|_| vm.new_type_error("expected type"))?; + + new_type.check_not_initialized(vm)?; + + // Get the _type_ attribute (element type) + // PyCPointerType_init gets the element type from _type_ attribute + let proto = new_type + .as_object() + .get_attr("_type_", vm) + .ok() + .and_then(|obj| obj.downcast::<PyType>().ok()); + + // Initialize StgInfo for pointer type + let pointer_size = std::mem::size_of::<usize>(); + let mut stg_info = StgInfo::new(pointer_size, pointer_size); + stg_info.proto = proto; + stg_info.paramfunc = super::base::ParamFunc::Pointer; + stg_info.length = 1; + stg_info.flags |= StgInfoFlags::TYPEFLAG_ISPOINTER; + + // Set format string: "&<element_format>" + if let Some(ref proto) = stg_info.proto { + let item_info = proto.stg_info_opt().expect("proto has StgInfo"); + let current_format = item_info.format.as_deref().unwrap_or("B"); + stg_info.format = Some(format!("&{}", current_format)); + } + + let _ = new_type.init_type_data(stg_info); -#[pyclass(flags(IMMUTABLETYPE), with(AsNumber))] + Ok(()) + } +} + +#[pyclass(flags(IMMUTABLETYPE), with(AsNumber, Initializer))] impl PyCPointerType { + #[pymethod] + fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // zelf is the pointer type class that from_param was called on + let cls = zelf + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("from_param: expected a type"))?; + + // 1. None is allowed for pointer types + if vm.is_none(&value) { + return Ok(value); + } + + // 2. If already an instance of the requested type, return it + if value.is_instance(cls.as_object(), vm)? { + return Ok(value); + } + + // 3. If value is an instance of _type_ (the pointed-to type), wrap with byref + if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) + && let Ok(type_ref) = type_attr.downcast::<PyType>() + && value.is_instance(type_ref.as_object(), vm)? + { + // Return byref(value) + return super::_ctypes::byref(value, crate::function::OptionalArg::Missing, vm); + } + + // 4. Array/Pointer instances with compatible proto + // "Array instances are also pointers when the item types are the same." + let is_pointer_or_array = value.downcast_ref::<PyCPointer>().is_some() + || value.downcast_ref::<super::array::PyCArray>().is_some(); + + if is_pointer_or_array { + let is_compatible = { + if let Some(value_stginfo) = value.class().stg_info_opt() + && let Some(ref value_proto) = value_stginfo.proto + && let Some(cls_stginfo) = cls.stg_info_opt() + && let Some(ref cls_proto) = cls_stginfo.proto + { + // Check if value's proto is a subclass of target's proto + value_proto.fast_issubclass(cls_proto) + } else { + false + } + }; + if is_compatible { + return Ok(value); + } + } + + // 5. Check for _as_parameter_ attribute + if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) { + return PyCPointerType::from_param(cls.as_object().to_owned(), as_parameter, vm); + } + + Err(vm.new_type_error(format!( + "expected {} instance instead of {}", + cls.name(), + value.class().name() + ))) + } + #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - use super::array::create_array_type_with_stg_info; + use super::array::array_type_from_ctype; + if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } - // Pointer size - let element_size = std::mem::size_of::<usize>(); - let total_size = element_size * (n as usize); - let stg_info = super::util::StgInfo::new_array( - total_size, - element_size, - n as usize, - cls.as_object().to_owned(), - element_size, - ); - create_array_type_with_stg_info(stg_info, vm) + // Use cached array type creation + array_type_from_ctype(cls.into(), n as usize, vm) + } + + // PyCPointerType_set_type: Complete an incomplete pointer type + #[pymethod] + fn set_type(zelf: PyTypeRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + use crate::AsObject; + + // 1. Validate that typ is a type + let typ_type = typ + .clone() + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("_type_ must be a type"))?; + + // 2. Validate that typ has storage info + if typ_type.stg_info_opt().is_none() { + return Err(vm.new_type_error("_type_ must have storage info")); + } + + // 3. Update StgInfo.proto and format using mutable access + if let Some(mut stg_info) = zelf.get_type_data_mut::<StgInfo>() { + stg_info.proto = Some(typ_type.clone()); + + // Update format string: "&<element_format>" + let item_info = typ_type.stg_info_opt().expect("proto has StgInfo"); + let current_format = item_info.format.as_deref().unwrap_or("B"); + stg_info.format = Some(format!("&{}", current_format)); + } + + // 4. Set _type_ attribute on the pointer type + zelf.as_object().set_attr("_type_", typ_type, vm)?; + + Ok(()) } } @@ -41,12 +168,12 @@ impl AsNumber for PyCPointerType { multiply: Some(|a, b, vm| { let cls = a .downcast_ref::<PyType>() - .ok_or_else(|| vm.new_type_error("expected type".to_owned()))?; + .ok_or_else(|| vm.new_type_error("expected type"))?; let n = b .try_index(vm)? .as_bigint() .to_isize() - .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; + .ok_or_else(|| vm.new_overflow_error("array size too large"))?; PyCPointerType::__mul__(cls.to_owned(), n, vm) }), ..PyNumberMethods::NOT_IMPLEMENTED @@ -55,6 +182,8 @@ impl AsNumber for PyCPointerType { } } +/// PyCPointer - Pointer instance +/// `contents` is a computed property, not a stored field. #[pyclass( name = "_Pointer", base = PyCData, @@ -62,26 +191,27 @@ impl AsNumber for PyCPointerType { module = "_ctypes" )] #[derive(Debug)] -pub struct PyCPointer { - _base: PyCData, - contents: PyRwLock<PyObjectRef>, -} +#[repr(transparent)] +pub struct PyCPointer(pub PyCData); impl Constructor for PyCPointer { - type Args = (crate::function::OptionalArg<PyObjectRef>,); - - fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - let args: Self::Args = args.bind(vm)?; - // Get the initial contents value if provided - let initial_contents = args.0.into_option().unwrap_or_else(|| vm.ctx.none()); + type Args = FuncArgs; - // Create a new PyCPointer instance with the provided value - PyCPointer { - _base: PyCData::new(CDataObject::from_bytes(vec![], None)), - contents: PyRwLock::new(initial_contents), + fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // Pointer_new: Check if _type_ is defined + let has_type = cls.stg_info_opt().is_some_and(|info| info.proto.is_some()); + if !has_type { + return Err(vm.new_type_error("Cannot create instance: has no _type_")); } - .into_ref_with_type(vm, cls) - .map(Into::into) + + // Create a new PyCPointer instance with NULL pointer (all zeros) + // Initial contents is set via __init__ if provided + let cdata = PyCData::from_bytes(vec![0u8; std::mem::size_of::<usize>()], None); + // pointer instance has b_length set to 2 (for index 0 and 1) + cdata.length.store(2); + PyCPointer(cdata) + .into_ref_with_type(vm, cls) + .map(Into::into) } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { @@ -89,186 +219,496 @@ impl Constructor for PyCPointer { } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] +impl Initializer for PyCPointer { + type Args = (OptionalArg<PyObjectRef>,); + + fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + let (value,) = args; + if let OptionalArg::Present(val) = value + && !vm.is_none(&val) + { + Self::set_contents(&zelf, val, vm)?; + } + Ok(()) + } +} + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, Initializer))] impl PyCPointer { - // TODO: not correct + /// Get the pointer value stored in buffer as usize + pub fn get_ptr_value(&self) -> usize { + let buffer = self.0.buffer.read(); + super::base::read_ptr_from_buffer(&buffer) + } + + /// Set the pointer value in buffer + pub fn set_ptr_value(&self, value: usize) { + let mut buffer = self.0.buffer.write(); + let bytes = value.to_ne_bytes(); + if buffer.len() >= bytes.len() { + buffer.to_mut()[..bytes.len()].copy_from_slice(&bytes); + } + } + + /// Pointer_bool: returns True if pointer is not NULL + #[pymethod] + fn __bool__(&self) -> bool { + self.get_ptr_value() != 0 + } + + /// contents getter - reads address from b_ptr and creates an instance of the pointed-to type #[pygetset] - fn contents(&self) -> PyResult<PyObjectRef> { - let contents = self.contents.read().clone(); - Ok(contents) + fn contents(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // Pointer_get_contents + let ptr_val = zelf.get_ptr_value(); + if ptr_val == 0 { + return Err(vm.new_value_error("NULL pointer access")); + } + + // Get element type from StgInfo.proto + let stg_info = zelf.class().stg_info(vm)?; + let proto_type = stg_info.proto(); + let element_size = proto_type + .stg_info_opt() + .map_or(std::mem::size_of::<usize>(), |info| info.size); + + // Create instance that references the memory directly + // PyCData.into_ref_with_type works for all ctypes (simple, structure, union, array, pointer) + let cdata = unsafe { super::base::PyCData::at_address(ptr_val as *const u8, element_size) }; + cdata + .into_ref_with_type(vm, proto_type.to_owned()) + .map(Into::into) } + + /// contents setter - stores address in b_ptr and keeps reference + /// Pointer_set_contents #[pygetset(setter)] - fn set_contents(&self, contents: PyObjectRef, _vm: &VirtualMachine) -> PyResult<()> { - // Validate that the contents is a CData instance if we have a _type_ - // For now, just store it - *self.contents.write() = contents; + fn set_contents(zelf: &Py<Self>, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // Get stginfo and proto for type validation + let stg_info = zelf.class().stg_info(vm)?; + let proto = stg_info.proto(); + + // Check if value is CData, or isinstance(value, proto) + let cdata = if let Some(c) = value.downcast_ref::<PyCData>() { + c + } else if value.is_instance(proto.as_object(), vm)? { + value + .downcast_ref::<PyCData>() + .ok_or_else(|| vm.new_type_error("expected ctypes instance"))? + } else { + return Err(vm.new_type_error(format!( + "expected {} instead of {}", + proto.name(), + value.class().name() + ))); + }; + + // Set pointer value + { + let buffer = cdata.buffer.read(); + let addr = buffer.as_ptr() as usize; + drop(buffer); + zelf.set_ptr_value(addr); + } + + // KeepRef: store the object directly with index 1 + zelf.0.keep_ref(1, value.clone(), vm)?; + + // KeepRef: store GetKeepedObjects(dst) at index 0 + if let Some(kept) = cdata.objects.read().clone() { + zelf.0.keep_ref(0, kept, vm)?; + } + Ok(()) } + // Pointer_subscript #[pymethod] - fn __init__( - &self, - value: crate::function::OptionalArg<PyObjectRef>, - _vm: &VirtualMachine, - ) -> PyResult<()> { - // Pointer can be initialized with 0 or 1 argument - // If 1 argument is provided, it should be a CData instance - if let crate::function::OptionalArg::Present(val) = value { - *self.contents.write() = val; + fn __getitem__(zelf: &Py<Self>, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // PyIndex_Check + if let Some(i) = item.downcast_ref::<PyInt>() { + let i = i.as_bigint().to_isize().ok_or_else(|| { + vm.new_index_error("cannot fit index into an index-sized integer") + })?; + // Note: Pointer does NOT adjust negative indices (no length) + Self::getitem_by_index(zelf, i, vm) + } + // PySlice_Check + else if let Some(slice) = item.downcast_ref::<PySlice>() { + Self::getitem_by_slice(zelf, slice, vm) + } else { + Err(vm.new_type_error("Pointer indices must be integer")) } - - Ok(()) } - #[pyclassmethod] - fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { - if address == 0 { - return Err(vm.new_value_error("NULL pointer access".to_owned())); + // Pointer_item + fn getitem_by_index(zelf: &Py<Self>, index: isize, vm: &VirtualMachine) -> PyResult { + // if (*(void **)self->b_ptr == NULL) { PyErr_SetString(...); } + let ptr_value = zelf.get_ptr_value(); + if ptr_value == 0 { + return Err(vm.new_value_error("NULL pointer access")); } - // Pointer just stores the address value - Ok(PyCPointer { - _base: PyCData::new(CDataObject::from_bytes(vec![], None)), - contents: PyRwLock::new(vm.ctx.new_int(address).into()), + + // Get element type and size from StgInfo.proto + let stg_info = zelf.class().stg_info(vm)?; + let proto_type = stg_info.proto(); + let element_size = proto_type + .stg_info_opt() + .map_or(std::mem::size_of::<usize>(), |info| info.size); + + // offset = index * iteminfo->size + let offset = index * element_size as isize; + let addr = (ptr_value as isize + offset) as usize; + + // Check if it's a simple type (has _type_ attribute) + if let Ok(type_attr) = proto_type.as_object().get_attr("_type_", vm) + && let Ok(type_str) = type_attr.str(vm) + { + let type_code = type_str.to_string(); + return Self::read_value_at_address(addr, element_size, Some(&type_code), vm); } - .into_ref_with_type(vm, cls)? - .into()) + + // Complex type: create instance that references the memory directly (not a copy) + // This allows p[i].val = x to modify the original memory + // PyCData.into_ref_with_type works for all ctypes (array, structure, union, pointer) + let cdata = unsafe { super::base::PyCData::at_address(addr as *const u8, element_size) }; + cdata + .into_ref_with_type(vm, proto_type.to_owned()) + .map(Into::into) } - #[pyclassmethod] - fn from_buffer( - cls: PyTypeRef, - source: PyObjectRef, - offset: crate::function::OptionalArg<isize>, - vm: &VirtualMachine, - ) -> PyResult { - use crate::TryFromObject; - use crate::protocol::PyBuffer; + // Pointer_subscript slice handling (manual parsing, not PySlice_Unpack) + fn getitem_by_slice(zelf: &Py<Self>, slice: &PySlice, vm: &VirtualMachine) -> PyResult { + // Since pointers have no length, we have to dissect the slice ourselves + + // step: defaults to 1, step == 0 is error + let step: isize = if let Some(ref step_obj) = slice.step + && !vm.is_none(step_obj) + { + let s = step_obj + .try_int(vm)? + .as_bigint() + .to_isize() + .ok_or_else(|| vm.new_value_error("slice step too large"))?; + if s == 0 { + return Err(vm.new_value_error("slice step cannot be zero")); + } + s + } else { + 1 + }; + + // start: defaults to 0, but required if step < 0 + let start: isize = if let Some(ref start_obj) = slice.start + && !vm.is_none(start_obj) + { + start_obj + .try_int(vm)? + .as_bigint() + .to_isize() + .ok_or_else(|| vm.new_value_error("slice start too large"))? + } else { + if step < 0 { + return Err(vm.new_value_error("slice start is required for step < 0")); + } + 0 + }; - let offset = offset.unwrap_or(0); - if offset < 0 { - return Err(vm.new_value_error("offset cannot be negative".to_owned())); + // stop: ALWAYS required for pointers + if vm.is_none(&slice.stop) { + return Err(vm.new_value_error("slice stop is required")); } - let offset = offset as usize; - let size = std::mem::size_of::<usize>(); + let stop: isize = slice + .stop + .try_int(vm)? + .as_bigint() + .to_isize() + .ok_or_else(|| vm.new_value_error("slice stop too large"))?; - let buffer = PyBuffer::try_from_object(vm, source.clone())?; + // calculate length + let len: usize = if (step > 0 && start > stop) || (step < 0 && start < stop) { + 0 + } else if step > 0 { + ((stop - start - 1) / step + 1) as usize + } else { + ((stop - start + 1) / step + 1) as usize + }; + + // Get element info + let stg_info = zelf.class().stg_info(vm)?; + let element_size = if let Some(ref proto_type) = stg_info.proto { + proto_type.stg_info_opt().expect("proto has StgInfo").size + } else { + std::mem::size_of::<usize>() + }; + let type_code = stg_info + .proto + .as_ref() + .and_then(|p| p.as_object().get_attr("_type_", vm).ok()) + .and_then(|t| t.str(vm).ok()) + .map(|s| s.to_string()); - if buffer.desc.readonly { - return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + let ptr_value = zelf.get_ptr_value(); + + // c_char → bytes + if type_code.as_deref() == Some("c") { + if len == 0 { + return Ok(vm.ctx.new_bytes(vec![]).into()); + } + let mut result = Vec::with_capacity(len); + if step == 1 { + // Optimized contiguous copy + let start_addr = (ptr_value as isize + start * element_size as isize) as *const u8; + unsafe { + result.extend_from_slice(std::slice::from_raw_parts(start_addr, len)); + } + } else { + let mut cur = start; + for _ in 0..len { + let addr = (ptr_value as isize + cur * element_size as isize) as *const u8; + unsafe { + result.push(*addr); + } + cur += step; + } + } + return Ok(vm.ctx.new_bytes(result).into()); } - let buffer_len = buffer.desc.len; - if offset + size > buffer_len { - return Err(vm.new_value_error(format!( - "Buffer size too small ({} instead of at least {} bytes)", - buffer_len, - offset + size - ))); + // c_wchar → str + if type_code.as_deref() == Some("u") { + if len == 0 { + return Ok(vm.ctx.new_str("").into()); + } + let mut result = String::with_capacity(len); + let wchar_size = std::mem::size_of::<libc::wchar_t>(); + let mut cur = start; + for _ in 0..len { + let addr = (ptr_value as isize + cur * wchar_size as isize) as *const libc::wchar_t; + unsafe { + if let Some(c) = char::from_u32(*addr as u32) { + result.push(c); + } + } + cur += step; + } + return Ok(vm.ctx.new_str(result).into()); } - // Read pointer value from buffer - let bytes = buffer.obj_bytes(); - let ptr_bytes = &bytes[offset..offset + size]; - let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above")); + // other types → list with Pointer_item for each + let mut items = Vec::with_capacity(len); + let mut cur = start; + for _ in 0..len { + items.push(Self::getitem_by_index(zelf, cur, vm)?); + cur += step; + } + Ok(PyList::from(items).into_ref(&vm.ctx).into()) + } - Ok(PyCPointer { - _base: PyCData::new(CDataObject::from_bytes(vec![], None)), - contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()), + // Pointer_ass_item + #[pymethod] + fn __setitem__( + zelf: &Py<Self>, + item: PyObjectRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Pointer does not support item deletion (value always provided) + // only integer indices supported for setitem + if let Some(i) = item.downcast_ref::<PyInt>() { + let i = i.as_bigint().to_isize().ok_or_else(|| { + vm.new_index_error("cannot fit index into an index-sized integer") + })?; + Self::setitem_by_index(zelf, i, value, vm) + } else { + Err(vm.new_type_error("Pointer indices must be integer")) } - .into_ref_with_type(vm, cls)? - .into()) } - #[pyclassmethod] - fn from_buffer_copy( - cls: PyTypeRef, - source: crate::function::ArgBytesLike, - offset: crate::function::OptionalArg<isize>, + fn setitem_by_index( + zelf: &Py<Self>, + index: isize, + value: PyObjectRef, vm: &VirtualMachine, - ) -> PyResult { - let offset = offset.unwrap_or(0); - if offset < 0 { - return Err(vm.new_value_error("offset cannot be negative".to_owned())); + ) -> PyResult<()> { + let ptr_value = zelf.get_ptr_value(); + if ptr_value == 0 { + return Err(vm.new_value_error("NULL pointer access")); } - let offset = offset as usize; - let size = std::mem::size_of::<usize>(); - let source_bytes = source.borrow_buf(); - let buffer_len = source_bytes.len(); + // Get element type, size and type_code from StgInfo.proto + let stg_info = zelf.class().stg_info(vm)?; + let proto_type = stg_info.proto(); - if offset + size > buffer_len { - return Err(vm.new_value_error(format!( - "Buffer size too small ({} instead of at least {} bytes)", - buffer_len, - offset + size - ))); - } + // Get type code from proto's _type_ attribute + let type_code: Option<String> = proto_type + .as_object() + .get_attr("_type_", vm) + .ok() + .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())); - // Read pointer value from buffer - let ptr_bytes = &source_bytes[offset..offset + size]; - let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above")); + let element_size = proto_type + .stg_info_opt() + .map_or(std::mem::size_of::<usize>(), |info| info.size); - Ok(PyCPointer { - _base: PyCData::new(CDataObject::from_bytes(vec![], None)), - contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()), + // Calculate address + let offset = index * element_size as isize; + let addr = (ptr_value as isize + offset) as usize; + + // Write value at address + // Handle Structure/Array types by copying their buffer + if let Some(cdata) = value.downcast_ref::<super::PyCData>() + && (cdata.fast_isinstance(PyCStructure::static_type()) + || cdata.fast_isinstance(PyCArray::static_type()) + || cdata.fast_isinstance(PyCSimple::static_type())) + { + let src_buffer = cdata.buffer.read(); + let copy_len = src_buffer.len().min(element_size); + unsafe { + let dest_ptr = addr as *mut u8; + std::ptr::copy_nonoverlapping(src_buffer.as_ptr(), dest_ptr, copy_len); + } + } else { + // Handle z/Z specially to store converted value + if type_code.as_deref() == Some("z") + && let Some(bytes) = value.downcast_ref::<PyBytes>() + { + let (converted, ptr_val) = super::base::ensure_z_null_terminated(bytes, vm); + unsafe { + *(addr as *mut usize) = ptr_val; + } + return zelf.0.keep_ref(index as usize, converted, vm); + } else if type_code.as_deref() == Some("Z") + && let Some(s) = value.downcast_ref::<PyStr>() + { + let (holder, ptr_val) = super::base::str_to_wchar_bytes(s.as_str(), vm); + unsafe { + *(addr as *mut usize) = ptr_val; + } + return zelf.0.keep_ref(index as usize, holder, vm); + } else { + Self::write_value_at_address(addr, element_size, &value, type_code.as_deref(), vm)?; + } } - .into_ref_with_type(vm, cls)? - .into()) + + // KeepRef: store reference to keep value alive using actual index + zelf.0.keep_ref(index as usize, value, vm) } - #[pyclassmethod] - fn in_dll( - cls: PyTypeRef, - dll: PyObjectRef, - name: crate::builtins::PyStrRef, + /// Read a value from memory address + fn read_value_at_address( + addr: usize, + size: usize, + type_code: Option<&str>, vm: &VirtualMachine, ) -> PyResult { - use libloading::Symbol; + unsafe { + let ptr = addr as *const u8; + match type_code { + Some("c") => Ok(vm.ctx.new_bytes(vec![*ptr]).into()), + Some("b") => Ok(vm.ctx.new_int(*(ptr as *const i8) as i32).into()), + Some("B") => Ok(vm.ctx.new_int(*ptr as i32).into()), + Some("h") => Ok(vm.ctx.new_int(*(ptr as *const i16) as i32).into()), + Some("H") => Ok(vm.ctx.new_int(*(ptr as *const u16) as i32).into()), + Some("i") | Some("l") => Ok(vm.ctx.new_int(*(ptr as *const i32)).into()), + Some("I") | Some("L") => Ok(vm.ctx.new_int(*(ptr as *const u32)).into()), + Some("q") => Ok(vm.ctx.new_int(*(ptr as *const i64)).into()), + Some("Q") => Ok(vm.ctx.new_int(*(ptr as *const u64)).into()), + Some("f") => Ok(vm.ctx.new_float(*(ptr as *const f32) as f64).into()), + Some("d") | Some("g") => Ok(vm.ctx.new_float(*(ptr as *const f64)).into()), + Some("P") | Some("z") | Some("Z") => { + Ok(vm.ctx.new_int(*(ptr as *const usize)).into()) + } + _ => { + // Default: read as bytes + let bytes = std::slice::from_raw_parts(ptr, size).to_vec(); + Ok(vm.ctx.new_bytes(bytes).into()) + } + } + } + } - // Get the library handle from dll object - let handle = if let Ok(int_handle) = dll.try_int(vm) { - // dll is an integer handle - int_handle - .as_bigint() - .to_usize() - .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? - } else { - // dll is a CDLL/PyDLL/WinDLL object with _handle attribute - dll.get_attr("_handle", vm)? - .try_int(vm)? - .as_bigint() - .to_usize() - .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? - }; + /// Write a value to memory address + fn write_value_at_address( + addr: usize, + size: usize, + value: &PyObject, + type_code: Option<&str>, + vm: &VirtualMachine, + ) -> PyResult<()> { + unsafe { + let ptr = addr as *mut u8; - // Get the library from cache - let library_cache = crate::stdlib::ctypes::library::libcache().read(); - let library = library_cache - .get_lib(handle) - .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; + // Handle c_char_p (z) and c_wchar_p (Z) - store pointer address + // Note: PyBytes/PyStr cases are handled by caller (setitem_by_index) + match type_code { + Some("z") | Some("Z") => { + let ptr_val = if vm.is_none(value) { + 0usize + } else if let Ok(int_val) = value.try_index(vm) { + int_val.as_bigint().to_usize().unwrap_or(0) + } else { + return Err(vm.new_type_error( + "bytes/string or integer address expected".to_owned(), + )); + }; + *(ptr as *mut usize) = ptr_val; + return Ok(()); + } + _ => {} + } - // Get symbol address from library - let symbol_name = format!("{}\0", name.as_str()); - let inner_lib = library.lib.lock(); + // Try to get value as integer + if let Ok(int_val) = value.try_int(vm) { + let i = int_val.as_bigint(); + match size { + 1 => { + *ptr = i.to_u8().unwrap_or(0); + } + 2 => { + *(ptr as *mut i16) = i.to_i16().unwrap_or(0); + } + 4 => { + *(ptr as *mut i32) = i.to_i32().unwrap_or(0); + } + 8 => { + *(ptr as *mut i64) = i.to_i64().unwrap_or(0); + } + _ => { + let bytes = i.to_signed_bytes_le(); + let copy_len = bytes.len().min(size); + std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, copy_len); + } + } + return Ok(()); + } - let symbol_address = if let Some(lib) = &*inner_lib { - unsafe { - // Try to get the symbol from the library - let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { - vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) - })?; - *symbol as usize + // Try to get value as float + if let Ok(float_val) = value.try_float(vm) { + let f = float_val.to_f64(); + match size { + 4 => { + *(ptr as *mut f32) = f as f32; + } + 8 => { + *(ptr as *mut f64) = f; + } + _ => {} + } + return Ok(()); + } + + // Try bytes + if let Ok(bytes) = value.try_bytes_like(vm, |b| b.to_vec()) { + let copy_len = bytes.len().min(size); + std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, copy_len); + return Ok(()); } - } else { - return Err(vm.new_attribute_error("Library is closed".to_owned())); - }; - // For pointer types, we return a pointer to the symbol address - Ok(PyCPointer { - _base: PyCData::new(CDataObject::from_bytes(vec![], None)), - contents: PyRwLock::new(vm.ctx.new_int(symbol_address).into()), + Err(vm.new_type_error(format!( + "cannot convert {} to ctypes data", + value.class().name() + ))) } - .into_ref_with_type(vm, cls)? - .into()) } } diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs new file mode 100644 index 00000000000..1c0ec250d72 --- /dev/null +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -0,0 +1,1379 @@ +use super::_ctypes::CArgObject; +use super::array::{PyCArray, WCHAR_SIZE, wchar_to_bytes}; +use super::base::{ + CDATA_BUFFER_METHODS, FfiArgValue, PyCData, StgInfo, StgInfoFlags, buffer_to_ffi_value, + bytes_to_pyobject, +}; +use super::function::PyCFuncPtr; +use super::get_size; +use super::pointer::PyCPointer; +use crate::builtins::{PyByteArray, PyBytes, PyInt, PyNone, PyStr, PyType, PyTypeRef}; +use crate::convert::ToPyObject; +use crate::function::{Either, FuncArgs, OptionalArg}; +use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}; +use crate::types::{AsBuffer, AsNumber, Constructor, Initializer, Representable}; +use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; +use num_traits::ToPrimitive; +use std::fmt::Debug; + +/// Valid type codes for ctypes simple types +// spell-checker: disable-next-line +pub(super) const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQPXOv?g"; + +/// Create a new simple type instance from a class +fn new_simple_type( + cls: Either<&PyObject, &Py<PyType>>, + vm: &VirtualMachine, +) -> PyResult<PyCSimple> { + let cls = match cls { + Either::A(obj) => obj, + Either::B(typ) => typ.as_object(), + }; + + let _type_ = cls + .get_attr("_type_", vm) + .map_err(|_| vm.new_attribute_error("class must define a '_type_' attribute"))?; + + if !_type_.is_instance((&vm.ctx.types.str_type).as_ref(), vm)? { + return Err(vm.new_type_error("class must define a '_type_' string attribute")); + } + + let tp_str = _type_.str(vm)?.to_string(); + + if tp_str.len() != 1 { + return Err(vm.new_value_error(format!( + "class must define a '_type_' attribute which must be a string of length 1, str: {tp_str}" + ))); + } + + if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) { + return Err(vm.new_attribute_error(format!( + "class must define a '_type_' attribute which must be\n a single character string containing one of {SIMPLE_TYPE_CHARS}, currently it is {tp_str}." + ))); + } + + let size = get_size(&tp_str); + Ok(PyCSimple(PyCData::from_bytes(vec![0u8; size], None))) +} + +fn set_primitive(_type_: &str, value: &PyObject, vm: &VirtualMachine) -> PyResult { + match _type_ { + "c" => { + // c_set: accepts bytes(len=1), bytearray(len=1), or int(0-255) + if value + .downcast_ref_if_exact::<PyBytes>(vm) + .is_some_and(|v| v.len() == 1) + || value + .downcast_ref_if_exact::<PyByteArray>(vm) + .is_some_and(|v| v.borrow_buf().len() == 1) + || value.downcast_ref_if_exact::<PyInt>(vm).is_some_and(|v| { + v.as_bigint() + .to_i64() + .is_some_and(|n| (0..=255).contains(&n)) + }) + { + Ok(value.to_owned()) + } else { + Err(vm.new_type_error("one character bytes, bytearray or integer expected")) + } + } + "u" => { + if let Ok(b) = value.str(vm).map(|v| v.to_string().chars().count() == 1) { + if b { + Ok(value.to_owned()) + } else { + Err(vm.new_type_error("one character unicode string expected")) + } + } else { + Err(vm.new_type_error(format!( + "unicode string expected instead of {} instance", + value.class().name() + ))) + } + } + "b" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => { + // Support __index__ protocol + if value.try_index(vm).is_ok() { + Ok(value.to_owned()) + } else { + Err(vm.new_type_error(format!( + "an integer is required (got type {})", + value.class().name() + ))) + } + } + "f" | "d" | "g" => { + // Handle int specially to check overflow + if let Some(int_obj) = value.downcast_ref_if_exact::<PyInt>(vm) { + // Check if int can fit in f64 + if int_obj.as_bigint().to_f64().is_some() { + return Ok(value.to_owned()); + } else { + return Err(vm.new_overflow_error("int too large to convert to float")); + } + } + // __float__ protocol + if value.try_float(vm).is_ok() { + Ok(value.to_owned()) + } else { + Err(vm.new_type_error(format!("must be real number, not {}", value.class().name()))) + } + } + "?" => Ok(PyObjectRef::from( + vm.ctx.new_bool(value.to_owned().try_to_bool(vm)?), + )), + "v" => { + // VARIANT_BOOL: any truthy → True + Ok(PyObjectRef::from( + vm.ctx.new_bool(value.to_owned().try_to_bool(vm)?), + )) + } + "B" => { + // Support __index__ protocol + if value.try_index(vm).is_ok() { + // Store as-is, conversion to unsigned happens in the getter + Ok(value.to_owned()) + } else { + Err(vm.new_type_error(format!("int expected instead of {}", value.class().name()))) + } + } + "z" => { + if value.is(&vm.ctx.none) + || value.downcast_ref_if_exact::<PyInt>(vm).is_some() + || value.downcast_ref_if_exact::<PyBytes>(vm).is_some() + { + Ok(value.to_owned()) + } else { + Err(vm.new_type_error(format!( + "bytes or integer address expected instead of {} instance", + value.class().name() + ))) + } + } + "Z" => { + if value.is(&vm.ctx.none) + || value.downcast_ref_if_exact::<PyInt>(vm).is_some() + || value.downcast_ref_if_exact::<PyStr>(vm).is_some() + { + Ok(value.to_owned()) + } else { + Err(vm.new_type_error(format!( + "unicode string or integer address expected instead of {} instance", + value.class().name() + ))) + } + } + // O_set: py_object accepts any Python object + "O" => Ok(value.to_owned()), + _ => { + // "P" + if value.downcast_ref_if_exact::<PyInt>(vm).is_some() + || value.downcast_ref_if_exact::<PyNone>(vm).is_some() + { + Ok(value.to_owned()) + } else { + Err(vm.new_type_error("cannot be converted to pointer")) + } + } + } +} + +#[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)] +#[derive(Debug)] +#[repr(transparent)] +pub struct PyCSimpleType(PyType); + +#[pyclass(flags(BASETYPE), with(AsNumber, Initializer))] +impl PyCSimpleType { + #[allow(clippy::new_ret_no_self)] + #[pymethod] + fn new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult { + Ok(PyObjectRef::from( + new_simple_type(Either::B(&cls), vm)? + .into_ref_with_type(vm, cls)? + .clone(), + )) + } + + #[pymethod] + fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // zelf is the class (e.g., c_int) that from_param was called on + let cls = zelf + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("from_param: expected a type"))?; + + // 1. If the value is already an instance of the requested type, return it + if value.is_instance(cls.as_object(), vm)? { + return Ok(value); + } + + // 2. Get the type code to determine conversion rules + let type_code = cls.type_code(vm); + + // 3. Handle None for pointer types (c_char_p, c_wchar_p, c_void_p) + if vm.is_none(&value) && matches!(type_code.as_deref(), Some("z") | Some("Z") | Some("P")) { + return Ok(value); + } + + // Helper to create CArgObject wrapping a simple instance + let create_simple_with_value = |type_str: &str, val: &PyObject| -> PyResult { + let simple = new_simple_type(Either::B(&cls), vm)?; + let buffer_bytes = value_to_bytes_endian(type_str, val, false, vm); + *simple.0.buffer.write() = std::borrow::Cow::Owned(buffer_bytes.clone()); + let simple_obj: PyObjectRef = simple.into_ref_with_type(vm, cls.clone())?.into(); + // from_param returns CArgObject, not the simple type itself + let tag = type_str.as_bytes().first().copied().unwrap_or(b'?'); + let ffi_value = buffer_to_ffi_value(type_str, &buffer_bytes); + Ok(CArgObject { + tag, + value: ffi_value, + obj: simple_obj, + size: 0, + offset: 0, + } + .to_pyobject(vm)) + }; + + // 4. Try to convert value based on type code + match type_code.as_deref() { + // Integer types: accept integers + Some(tc @ ("b" | "B" | "h" | "H" | "i" | "I" | "l" | "L" | "q" | "Q")) => { + if value.try_int(vm).is_ok() { + return create_simple_with_value(tc, &value); + } + } + // Float types: accept numbers + Some(tc @ ("f" | "d" | "g")) => { + if value.try_float(vm).is_ok() || value.try_int(vm).is_ok() { + return create_simple_with_value(tc, &value); + } + } + // c_char: 1 byte character + Some("c") => { + if let Some(bytes) = value.downcast_ref::<PyBytes>() + && bytes.len() == 1 + { + return create_simple_with_value("c", &value); + } + if let Ok(int_val) = value.try_int(vm) + && int_val.as_bigint().to_u8().is_some() + { + return create_simple_with_value("c", &value); + } + return Err(vm.new_type_error( + "one character bytes, bytearray or integer expected".to_string(), + )); + } + // c_wchar: 1 unicode character + Some("u") => { + if let Some(s) = value.downcast_ref::<PyStr>() + && s.as_str().chars().count() == 1 + { + return create_simple_with_value("u", &value); + } + return Err(vm.new_type_error("one character unicode string expected")); + } + // c_char_p: bytes pointer + Some("z") => { + // 1. bytes → create CArgObject with null-terminated buffer + if let Some(bytes) = value.downcast_ref::<PyBytes>() { + let (holder, ptr) = super::base::ensure_z_null_terminated(bytes, vm); + return Ok(CArgObject { + tag: b'z', + value: FfiArgValue::OwnedPointer(ptr, holder), + obj: value.clone(), + size: 0, + offset: 0, + } + .to_pyobject(vm)); + } + // 2. Array/Pointer with c_char element type + if is_cchar_array_or_pointer(&value, vm) { + return Ok(value); + } + // 3. CArgObject (byref(c_char(...))) + if let Some(carg) = value.downcast_ref::<CArgObject>() + && carg.tag == b'c' + { + return Ok(value.clone()); + } + } + // c_wchar_p: unicode pointer + Some("Z") => { + // 1. str → create CArgObject with null-terminated wchar buffer + if let Some(s) = value.downcast_ref::<PyStr>() { + let (holder, ptr) = super::base::str_to_wchar_bytes(s.as_str(), vm); + return Ok(CArgObject { + tag: b'Z', + value: FfiArgValue::OwnedPointer(ptr, holder), + obj: value.clone(), + size: 0, + offset: 0, + } + .to_pyobject(vm)); + } + // 2. Array/Pointer with c_wchar element type + if is_cwchar_array_or_pointer(&value, vm) { + return Ok(value); + } + // 3. CArgObject (byref(c_wchar(...))) + if let Some(carg) = value.downcast_ref::<CArgObject>() + && carg.tag == b'u' + { + return Ok(value.clone()); + } + } + // c_void_p: most flexible - accepts int, bytes, str, any array/pointer, funcptr + Some("P") => { + // 1. int → create c_void_p with that address + if value.try_int(vm).is_ok() { + return create_simple_with_value("P", &value); + } + // 2. bytes → create CArgObject with null-terminated buffer + if let Some(bytes) = value.downcast_ref::<PyBytes>() { + let (holder, ptr) = super::base::ensure_z_null_terminated(bytes, vm); + return Ok(CArgObject { + tag: b'z', + value: FfiArgValue::OwnedPointer(ptr, holder), + obj: value.clone(), + size: 0, + offset: 0, + } + .to_pyobject(vm)); + } + // 3. str → create CArgObject with null-terminated wchar buffer + if let Some(s) = value.downcast_ref::<PyStr>() { + let (holder, ptr) = super::base::str_to_wchar_bytes(s.as_str(), vm); + return Ok(CArgObject { + tag: b'Z', + value: FfiArgValue::OwnedPointer(ptr, holder), + obj: value.clone(), + size: 0, + offset: 0, + } + .to_pyobject(vm)); + } + // 4. Any Array or Pointer → accept directly + if value.downcast_ref::<PyCArray>().is_some() + || value.downcast_ref::<PyCPointer>().is_some() + { + return Ok(value); + } + // 5. CArgObject with 'P' tag (byref(c_void_p(...))) + if let Some(carg) = value.downcast_ref::<CArgObject>() + && carg.tag == b'P' + { + return Ok(value.clone()); + } + // 6. PyCFuncPtr → extract function pointer address + if let Some(funcptr) = value.downcast_ref::<PyCFuncPtr>() { + let ptr_val = { + let buffer = funcptr._base.buffer.read(); + if buffer.len() >= std::mem::size_of::<usize>() { + usize::from_ne_bytes( + buffer[..std::mem::size_of::<usize>()].try_into().unwrap(), + ) + } else { + 0 + } + }; + return Ok(CArgObject { + tag: b'P', + value: FfiArgValue::Pointer(ptr_val), + obj: value.clone(), + size: 0, + offset: 0, + } + .to_pyobject(vm)); + } + // 7. c_char_p or c_wchar_p instance → extract pointer value + if let Some(simple) = value.downcast_ref::<PyCSimple>() { + let value_type_code = value.class().type_code(vm); + if matches!(value_type_code.as_deref(), Some("z") | Some("Z")) { + let ptr_val = { + let buffer = simple.0.buffer.read(); + if buffer.len() >= std::mem::size_of::<usize>() { + usize::from_ne_bytes( + buffer[..std::mem::size_of::<usize>()].try_into().unwrap(), + ) + } else { + 0 + } + }; + return Ok(CArgObject { + tag: b'Z', + value: FfiArgValue::Pointer(ptr_val), + obj: value.clone(), + size: 0, + offset: 0, + } + .to_pyobject(vm)); + } + } + } + // c_bool + Some("?") => { + let bool_val = value.is_true(vm)?; + let bool_obj: PyObjectRef = vm.ctx.new_bool(bool_val).into(); + return create_simple_with_value("?", &bool_obj); + } + _ => {} + } + + // 5. Check for _as_parameter_ attribute + if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) { + return PyCSimpleType::from_param(cls.as_object().to_owned(), as_parameter, vm); + } + + // 6. Type-specific error messages + match type_code.as_deref() { + Some("z") => Err(vm.new_type_error(format!( + "'{}' object cannot be interpreted as ctypes.c_char_p", + value.class().name() + ))), + Some("Z") => Err(vm.new_type_error(format!( + "'{}' object cannot be interpreted as ctypes.c_wchar_p", + value.class().name() + ))), + _ => Err(vm.new_type_error("wrong type")), + } + } + + #[pymethod] + fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { + PyCSimple::repeat(cls, n, vm) + } +} + +impl AsNumber for PyCSimpleType { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + multiply: Some(|a, b, vm| { + // a is a PyCSimpleType instance (type object like c_char) + // b is int (array size) + let cls = a + .downcast_ref::<PyType>() + .ok_or_else(|| vm.new_type_error("expected type"))?; + let n = b + .try_index(vm)? + .as_bigint() + .to_isize() + .ok_or_else(|| vm.new_overflow_error("array size too large"))?; + PyCSimple::repeat(cls.to_owned(), n, vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + +impl Initializer for PyCSimpleType { + type Args = FuncArgs; + + fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + // type_init requires exactly 3 positional arguments: name, bases, dict + if args.args.len() != 3 { + return Err(vm.new_type_error(format!( + "type.__init__() takes 3 positional arguments but {} were given", + args.args.len() + ))); + } + + // Get the type from the metatype instance + let type_ref: PyTypeRef = zelf + .as_object() + .to_owned() + .downcast() + .map_err(|_| vm.new_type_error("expected type"))?; + + type_ref.check_not_initialized(vm)?; + + // Get _type_ attribute + let type_attr = match type_ref.as_object().get_attr("_type_", vm) { + Ok(attr) => attr, + Err(_) => { + return Err(vm.new_attribute_error("class must define a '_type_' attribute")); + } + }; + + // Validate _type_ is a string + let type_str = type_attr.str(vm)?.to_string(); + + // Validate _type_ is a single character + if type_str.len() != 1 { + return Err(vm.new_value_error( + "class must define a '_type_' attribute which must be a string of length 1" + .to_owned(), + )); + } + + // Validate _type_ is a valid type character + if !SIMPLE_TYPE_CHARS.contains(type_str.as_str()) { + return Err(vm.new_attribute_error(format!( + "class must define a '_type_' attribute which must be a single character string containing one of '{}', currently it is '{}'.", + SIMPLE_TYPE_CHARS, type_str + ))); + } + + // Initialize StgInfo + let size = super::get_size(&type_str); + let align = super::get_align(&type_str); + let mut stg_info = StgInfo::new(size, align); + + // Set format for PEP 3118 buffer protocol + // Format is endian prefix + type code (e.g., "<i" for little-endian int) + let endian_prefix = if cfg!(target_endian = "little") { + "<" + } else { + ">" + }; + stg_info.format = Some(format!("{}{}", endian_prefix, type_str)); + stg_info.paramfunc = super::base::ParamFunc::Simple; + + // Set TYPEFLAG_ISPOINTER for pointer types: z (c_char_p), Z (c_wchar_p), + // P (c_void_p), s (char array), X (BSTR), O (py_object) + if matches!(type_str.as_str(), "z" | "Z" | "P" | "s" | "X" | "O") { + stg_info.flags |= StgInfoFlags::TYPEFLAG_ISPOINTER; + } + + super::base::set_or_init_stginfo(&type_ref, stg_info); + + // Create __ctype_le__ and __ctype_be__ swapped types + create_swapped_types(&type_ref, &type_str, vm)?; + + Ok(()) + } +} + +/// Create __ctype_le__ and __ctype_be__ swapped byte order types +/// On little-endian systems: __ctype_le__ = self, __ctype_be__ = swapped type +/// On big-endian systems: __ctype_be__ = self, __ctype_le__ = swapped type +/// +/// - Single-byte types (c, b, B): __ctype_le__ = __ctype_be__ = self +/// - Pointer/unsupported types (z, Z, P, u, O): NO __ctype_le__/__ctype_be__ attributes +/// - Multi-byte numeric types (h, H, i, I, l, L, q, Q, f, d, g, ?): create swapped types +fn create_swapped_types( + type_ref: &Py<PyType>, + type_str: &str, + vm: &VirtualMachine, +) -> PyResult<()> { + use crate::builtins::PyDict; + + // Avoid infinite recursion - if __ctype_le__ already exists, skip + if type_ref.as_object().get_attr("__ctype_le__", vm).is_ok() { + return Ok(()); + } + + // Types that don't support byte order swapping - no __ctype_le__/__ctype_be__ + // c_void_p (P), c_char_p (z), c_wchar_p (Z), c_wchar (u), py_object (O) + let unsupported_types = ["P", "z", "Z", "u", "O"]; + if unsupported_types.contains(&type_str) { + return Ok(()); + } + + // Single-byte types - __ctype_le__ = __ctype_be__ = self (no swapping needed) + // c_char (c), c_byte (b), c_ubyte (B) + let single_byte_types = ["c", "b", "B"]; + if single_byte_types.contains(&type_str) { + type_ref + .as_object() + .set_attr("__ctype_le__", type_ref.as_object().to_owned(), vm)?; + type_ref + .as_object() + .set_attr("__ctype_be__", type_ref.as_object().to_owned(), vm)?; + return Ok(()); + } + + // Multi-byte types - create swapped type + // Check system byte order at compile time + let is_little_endian = cfg!(target_endian = "little"); + + // Create dict for the swapped (non-native) type + let swapped_dict: crate::PyRef<crate::builtins::PyDict> = PyDict::default().into_ref(&vm.ctx); + swapped_dict.set_item("_type_", vm.ctx.new_str(type_str).into(), vm)?; + + // Create the swapped type using the same metaclass + let metaclass = type_ref.class(); + let bases = vm.ctx.new_tuple(vec![type_ref.as_object().to_owned()]); + + // Set placeholder first to prevent recursion + type_ref + .as_object() + .set_attr("__ctype_le__", vm.ctx.none(), vm)?; + type_ref + .as_object() + .set_attr("__ctype_be__", vm.ctx.none(), vm)?; + + // Create only the non-native endian type + let suffix = if is_little_endian { "_be" } else { "_le" }; + let swapped_type = metaclass.as_object().call( + ( + vm.ctx.new_str(format!("{}{}", type_ref.name(), suffix)), + bases, + swapped_dict.as_object().to_owned(), + ), + vm, + )?; + + // Set _swappedbytes_ on the swapped type to indicate byte swapping is needed + swapped_type.set_attr("_swappedbytes_", vm.ctx.none(), vm)?; + + // Update swapped type's StgInfo format to use opposite endian prefix + // Native uses '<' on little-endian, '>' on big-endian + // Swapped uses the opposite + if let Ok(swapped_type_ref) = swapped_type.clone().downcast::<PyType>() + && let Some(mut sw_stg) = swapped_type_ref.get_type_data_mut::<StgInfo>() + { + let swapped_prefix = if is_little_endian { ">" } else { "<" }; + sw_stg.format = Some(format!("{}{}", swapped_prefix, type_str)); + } + + // Set attributes based on system byte order + // Native endian attribute points to self, non-native points to swapped type + if is_little_endian { + // Little-endian system: __ctype_le__ = self, __ctype_be__ = swapped + type_ref + .as_object() + .set_attr("__ctype_le__", type_ref.as_object().to_owned(), vm)?; + type_ref + .as_object() + .set_attr("__ctype_be__", swapped_type.clone(), vm)?; + swapped_type.set_attr("__ctype_le__", type_ref.as_object().to_owned(), vm)?; + swapped_type.set_attr("__ctype_be__", swapped_type.clone(), vm)?; + } else { + // Big-endian system: __ctype_be__ = self, __ctype_le__ = swapped + type_ref + .as_object() + .set_attr("__ctype_be__", type_ref.as_object().to_owned(), vm)?; + type_ref + .as_object() + .set_attr("__ctype_le__", swapped_type.clone(), vm)?; + swapped_type.set_attr("__ctype_be__", type_ref.as_object().to_owned(), vm)?; + swapped_type.set_attr("__ctype_le__", swapped_type.clone(), vm)?; + } + + Ok(()) +} + +#[pyclass( + module = "_ctypes", + name = "_SimpleCData", + base = PyCData, + metaclass = "PyCSimpleType" +)] +#[repr(transparent)] +pub struct PyCSimple(pub PyCData); + +impl Debug for PyCSimple { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyCSimple") + .field("size", &self.0.buffer.read().len()) + .finish() + } +} + +fn value_to_bytes_endian( + _type_: &str, + value: &PyObject, + swapped: bool, + vm: &VirtualMachine, +) -> Vec<u8> { + // Helper macro for endian conversion + macro_rules! to_bytes { + ($val:expr) => { + if swapped { + // Use opposite endianness + #[cfg(target_endian = "little")] + { + $val.to_be_bytes().to_vec() + } + #[cfg(target_endian = "big")] + { + $val.to_le_bytes().to_vec() + } + } else { + $val.to_ne_bytes().to_vec() + } + }; + } + + match _type_ { + "c" => { + // c_char - single byte (bytes, bytearray, or int 0-255) + if let Some(bytes) = value.downcast_ref::<PyBytes>() + && !bytes.is_empty() + { + return vec![bytes.as_bytes()[0]]; + } + if let Some(bytearray) = value.downcast_ref::<PyByteArray>() { + let buf = bytearray.borrow_buf(); + if !buf.is_empty() { + return vec![buf[0]]; + } + } + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_u8() + { + return vec![v]; + } + vec![0] + } + "u" => { + // c_wchar - platform-dependent size (2 on Windows, 4 on Unix) + if let Ok(s) = value.str(vm) + && let Some(c) = s.as_str().chars().next() + { + let mut buffer = vec![0u8; WCHAR_SIZE]; + wchar_to_bytes(c as u32, &mut buffer); + if swapped { + buffer.reverse(); + } + return buffer; + } + vec![0; WCHAR_SIZE] + } + "b" => { + // c_byte - signed char (1 byte) + // PyLong_AsLongMask pattern: wrapping for overflow values + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_i128().unwrap_or(0) as i8; + return vec![v as u8]; + } + vec![0] + } + "B" => { + // c_ubyte - unsigned char (1 byte) + // PyLong_AsUnsignedLongMask: wrapping for negative values + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_i128().map(|n| n as u8).unwrap_or(0); + return vec![v]; + } + vec![0] + } + "h" => { + // c_short (2 bytes) + // PyLong_AsLongMask pattern: wrapping for overflow values + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_i128().unwrap_or(0) as i16; + return to_bytes!(v); + } + vec![0; 2] + } + "H" => { + // c_ushort (2 bytes) + // PyLong_AsUnsignedLongMask: wrapping for negative values + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_i128().map(|n| n as u16).unwrap_or(0); + return to_bytes!(v); + } + vec![0; 2] + } + "i" => { + // c_int (4 bytes) + // PyLong_AsLongMask pattern: wrapping for overflow values + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_i128().unwrap_or(0) as i32; + return to_bytes!(v); + } + vec![0; 4] + } + "I" => { + // c_uint (4 bytes) + // PyLong_AsUnsignedLongMask: wrapping for negative values + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_i128().map(|n| n as u32).unwrap_or(0); + return to_bytes!(v); + } + vec![0; 4] + } + "l" => { + // c_long (platform dependent) + // PyLong_AsLongMask pattern: wrapping for overflow values + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_i128().unwrap_or(0) as libc::c_long; + return to_bytes!(v); + } + const SIZE: usize = std::mem::size_of::<libc::c_long>(); + vec![0; SIZE] + } + "L" => { + // c_ulong (platform dependent) + // PyLong_AsUnsignedLongMask: wrapping for negative values + if let Ok(int_val) = value.try_index(vm) { + let v = int_val + .as_bigint() + .to_i128() + .map(|n| n as libc::c_ulong) + .unwrap_or(0); + return to_bytes!(v); + } + const SIZE: usize = std::mem::size_of::<libc::c_ulong>(); + vec![0; SIZE] + } + "q" => { + // c_longlong (8 bytes) + // PyLong_AsLongMask pattern: wrapping for overflow values + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_i128().unwrap_or(0) as i64; + return to_bytes!(v); + } + vec![0; 8] + } + "Q" => { + // c_ulonglong (8 bytes) + // PyLong_AsUnsignedLongLongMask: wrapping for negative values + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_i128().map(|n| n as u64).unwrap_or(0); + return to_bytes!(v); + } + vec![0; 8] + } + "f" => { + // c_float (4 bytes) - also accepts int + if let Ok(float_val) = value.try_float(vm) { + return to_bytes!(float_val.to_f64() as f32); + } + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_f64() + { + return to_bytes!(v as f32); + } + vec![0; 4] + } + "d" => { + // c_double (8 bytes) - also accepts int + if let Ok(float_val) = value.try_float(vm) { + return to_bytes!(float_val.to_f64()); + } + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_f64() + { + return to_bytes!(v); + } + vec![0; 8] + } + "g" => { + // long double - platform dependent size + // Store as f64, zero-pad to platform long double size + // Note: This may lose precision on platforms where long double > 64 bits + let f64_val = if let Ok(float_val) = value.try_float(vm) { + float_val.to_f64() + } else if let Ok(int_val) = value.try_int(vm) { + int_val.as_bigint().to_f64().unwrap_or(0.0) + } else { + 0.0 + }; + let f64_bytes = if swapped { + #[cfg(target_endian = "little")] + { + f64_val.to_be_bytes().to_vec() + } + #[cfg(target_endian = "big")] + { + f64_val.to_le_bytes().to_vec() + } + } else { + f64_val.to_ne_bytes().to_vec() + }; + // Pad to long double size + let long_double_size = super::get_size("g"); + let mut result = f64_bytes; + result.resize(long_double_size, 0); + result + } + "?" => { + // c_bool (1 byte) + if let Ok(b) = value.to_owned().try_to_bool(vm) { + return vec![if b { 1 } else { 0 }]; + } + vec![0] + } + "v" => { + // VARIANT_BOOL: True = 0xFFFF (-1 as i16), False = 0x0000 + if let Ok(b) = value.to_owned().try_to_bool(vm) { + let val: i16 = if b { -1 } else { 0 }; + return to_bytes!(val); + } + vec![0; 2] + } + "P" => { + // c_void_p - pointer type (platform pointer size) + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_usize().unwrap_or(0); + return to_bytes!(v); + } + vec![0; std::mem::size_of::<usize>()] + } + "z" => { + // c_char_p - pointer to char (stores pointer value from int) + // PyBytes case is handled in slot_new/set_value with make_z_buffer() + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_usize().unwrap_or(0); + return to_bytes!(v); + } + vec![0; std::mem::size_of::<usize>()] + } + "Z" => { + // c_wchar_p - pointer to wchar_t (stores pointer value from int) + // PyStr case is handled in slot_new/set_value with make_wchar_buffer() + if let Ok(int_val) = value.try_index(vm) { + let v = int_val.as_bigint().to_usize().unwrap_or(0); + return to_bytes!(v); + } + vec![0; std::mem::size_of::<usize>()] + } + "O" => { + // py_object - store object id as non-zero marker + // The actual object is stored in _objects + // Use object's id as a non-zero placeholder (indicates non-NULL) + let id = value.get_id(); + to_bytes!(id) + } + _ => vec![0], + } +} + +/// Check if value is a c_char array or pointer(c_char) +fn is_cchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { + // Check Array with c_char element type + if let Some(arr) = value.downcast_ref::<PyCArray>() + && let Some(info) = arr.class().stg_info_opt() + && let Some(ref elem_type) = info.element_type + && let Some(elem_code) = elem_type.class().type_code(vm) + { + return elem_code == "c"; + } + // Check Pointer to c_char + if let Some(ptr) = value.downcast_ref::<PyCPointer>() + && let Some(info) = ptr.class().stg_info_opt() + && let Some(ref proto) = info.proto + && let Some(proto_code) = proto.class().type_code(vm) + { + return proto_code == "c"; + } + false +} + +/// Check if value is a c_wchar array or pointer(c_wchar) +fn is_cwchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { + // Check Array with c_wchar element type + if let Some(arr) = value.downcast_ref::<PyCArray>() { + let info = arr.class().stg_info_opt().expect("array has StgInfo"); + let elem_type = info.element_type.as_ref().expect("array has element_type"); + if let Some(elem_code) = elem_type.class().type_code(vm) { + return elem_code == "u"; + } + } + // Check Pointer to c_wchar + if let Some(ptr) = value.downcast_ref::<PyCPointer>() { + let info = ptr.class().stg_info_opt().expect("pointer has StgInfo"); + if let Some(ref proto) = info.proto + && let Some(proto_code) = proto.class().type_code(vm) + { + return proto_code == "u"; + } + } + false +} + +impl Constructor for PyCSimple { + type Args = (OptionalArg,); + + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let args: Self::Args = args.bind(vm)?; + let _type_ = cls + .type_code(vm) + .ok_or_else(|| vm.new_type_error("abstract class"))?; + // Save the initial argument for c_char_p/c_wchar_p _objects + let init_arg = args.0.into_option(); + + // Handle z/Z types with PyBytes/PyStr separately to avoid memory leak + if let Some(ref v) = init_arg { + if _type_ == "z" { + if let Some(bytes) = v.downcast_ref::<PyBytes>() { + let (converted, ptr) = super::base::ensure_z_null_terminated(bytes, vm); + let buffer = ptr.to_ne_bytes().to_vec(); + let cdata = PyCData::from_bytes(buffer, Some(converted)); + return PyCSimple(cdata).into_ref_with_type(vm, cls).map(Into::into); + } + } else if _type_ == "Z" + && let Some(s) = v.downcast_ref::<PyStr>() + { + let (holder, ptr) = super::base::str_to_wchar_bytes(s.as_str(), vm); + let buffer = ptr.to_ne_bytes().to_vec(); + let cdata = PyCData::from_bytes(buffer, Some(holder)); + return PyCSimple(cdata).into_ref_with_type(vm, cls).map(Into::into); + } + } + + let value = if let Some(ref v) = init_arg { + set_primitive(_type_.as_str(), v, vm)? + } else { + match _type_.as_str() { + "c" | "u" => PyObjectRef::from(vm.ctx.new_bytes(vec![0])), + "b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => { + PyObjectRef::from(vm.ctx.new_int(0)) + } + "f" | "d" | "g" => PyObjectRef::from(vm.ctx.new_float(0.0)), + "?" => PyObjectRef::from(vm.ctx.new_bool(false)), + _ => vm.ctx.none(), // "z" | "Z" | "P" + } + }; + + // Check if this is a swapped endian type (presence of attribute indicates swapping) + let swapped = cls.as_object().get_attr("_swappedbytes_", vm).is_ok(); + + let buffer = value_to_bytes_endian(&_type_, &value, swapped, vm); + + // For c_char_p (type "z"), c_wchar_p (type "Z"), and py_object (type "O"), + // store the initial value in _objects + let objects = if (_type_ == "z" || _type_ == "Z" || _type_ == "O") && init_arg.is_some() { + init_arg + } else { + None + }; + + PyCSimple(PyCData::from_bytes(buffer, objects)) + .into_ref_with_type(vm, cls) + .map(Into::into) + } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unimplemented!("use slot_new") + } +} + +// Simple_repr +impl Representable for PyCSimple { + fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> { + let cls = zelf.class(); + let type_name = cls.name(); + + // Check if base is _SimpleCData (direct simple type like c_int, c_char) + // vs subclass of simple type (like class X(c_int): pass) + let bases = cls.bases.read(); + let is_direct_simple = bases + .iter() + .any(|base| base.name().to_string() == "_SimpleCData"); + + if is_direct_simple { + // Direct SimpleCData: "typename(repr(value))" + let value = PyCSimple::value(zelf.to_owned().into(), vm)?; + let value_repr = value.repr(vm)?.to_string(); + Ok(format!("{}({})", type_name, value_repr)) + } else { + // Subclass: "<typename object at addr>" + let addr = zelf.get_id(); + Ok(format!("<{} object at {:#x}>", type_name, addr)) + } + } +} + +#[pyclass(flags(BASETYPE), with(Constructor, AsBuffer, AsNumber, Representable))] +impl PyCSimple { + #[pygetset] + fn _b0_(&self) -> Option<PyObjectRef> { + self.0.base.read().clone() + } + + /// return True if any byte in buffer is non-zero + #[pymethod] + fn __bool__(&self) -> bool { + let buffer = self.0.buffer.read(); + // Simple_bool: memcmp(self->b_ptr, zeros, self->b_size) + buffer.iter().any(|&b| b != 0) + } + + #[pygetset] + pub fn value(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + let zelf: &Py<Self> = instance + .downcast_ref() + .ok_or_else(|| vm.new_type_error("cannot get value of instance"))?; + + // Get _type_ from class + let cls = zelf.class(); + let type_attr = cls + .as_object() + .get_attr("_type_", vm) + .map_err(|_| vm.new_type_error("no _type_ attribute"))?; + let type_code = type_attr.str(vm)?.to_string(); + + // Special handling for c_char_p (z) and c_wchar_p (Z) + // z_get, Z_get - dereference pointer to get string + if type_code == "z" { + // c_char_p: read pointer from buffer, dereference to get bytes string + let buffer = zelf.0.buffer.read(); + let ptr = super::base::read_ptr_from_buffer(&buffer); + if ptr == 0 { + return Ok(vm.ctx.none()); + } + // Read null-terminated string at the address + unsafe { + let cstr = std::ffi::CStr::from_ptr(ptr as _); + return Ok(vm.ctx.new_bytes(cstr.to_bytes().to_vec()).into()); + } + } + if type_code == "Z" { + // c_wchar_p: read pointer from buffer, dereference to get wide string + let buffer = zelf.0.buffer.read(); + let ptr = super::base::read_ptr_from_buffer(&buffer); + if ptr == 0 { + return Ok(vm.ctx.none()); + } + // Read null-terminated wide string at the address + unsafe { + let w_ptr = ptr as *const libc::wchar_t; + let len = libc::wcslen(w_ptr); + let wchars = std::slice::from_raw_parts(w_ptr, len); + let s: String = wchars + .iter() + .filter_map(|&c| char::from_u32(c as u32)) + .collect(); + return Ok(vm.ctx.new_str(s).into()); + } + } + + // O_get: py_object - read PyObject pointer from buffer + if type_code == "O" { + let buffer = zelf.0.buffer.read(); + let ptr = super::base::read_ptr_from_buffer(&buffer); + if ptr == 0 { + return Err(vm.new_value_error("PyObject is NULL")); + } + // Non-NULL: return stored object from _objects if available + if let Some(obj) = zelf.0.objects.read().as_ref() { + return Ok(obj.clone()); + } + return Err(vm.new_value_error("PyObject is NULL")); + } + + // Check if this is a swapped endian type (presence of attribute indicates swapping) + let swapped = cls.as_object().get_attr("_swappedbytes_", vm).is_ok(); + + // Read value from buffer, swap bytes if needed + let buffer = zelf.0.buffer.read(); + let buffer_data: std::borrow::Cow<'_, [u8]> = if swapped { + // Reverse bytes for swapped endian types + let mut swapped_bytes = buffer.to_vec(); + swapped_bytes.reverse(); + std::borrow::Cow::Owned(swapped_bytes) + } else { + std::borrow::Cow::Borrowed(&*buffer) + }; + + let cls_ref = cls.to_owned(); + bytes_to_pyobject(&cls_ref, &buffer_data, vm).or_else(|_| { + // Fallback: return bytes as integer based on type + match type_code.as_str() { + "c" => { + if !buffer.is_empty() { + Ok(vm.ctx.new_bytes(vec![buffer[0]]).into()) + } else { + Ok(vm.ctx.new_bytes(vec![0]).into()) + } + } + "?" => { + let val = buffer.first().copied().unwrap_or(0); + Ok(vm.ctx.new_bool(val != 0).into()) + } + _ => Ok(vm.ctx.new_int(0).into()), + } + }) + } + + #[pygetset(setter)] + fn set_value(instance: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let zelf: PyRef<Self> = instance + .clone() + .downcast() + .map_err(|_| vm.new_type_error("cannot set value of instance"))?; + + // Get _type_ from class + let cls = zelf.class(); + let type_attr = cls + .as_object() + .get_attr("_type_", vm) + .map_err(|_| vm.new_type_error("no _type_ attribute"))?; + let type_code = type_attr.str(vm)?.to_string(); + + // Handle z/Z types with PyBytes/PyStr separately to avoid memory leak + if type_code == "z" { + if let Some(bytes) = value.downcast_ref::<PyBytes>() { + let (converted, ptr) = super::base::ensure_z_null_terminated(bytes, vm); + *zelf.0.buffer.write() = std::borrow::Cow::Owned(ptr.to_ne_bytes().to_vec()); + *zelf.0.objects.write() = Some(converted); + return Ok(()); + } + } else if type_code == "Z" + && let Some(s) = value.downcast_ref::<PyStr>() + { + let (holder, ptr) = super::base::str_to_wchar_bytes(s.as_str(), vm); + *zelf.0.buffer.write() = std::borrow::Cow::Owned(ptr.to_ne_bytes().to_vec()); + *zelf.0.objects.write() = Some(holder); + return Ok(()); + } + + let content = set_primitive(&type_code, &value, vm)?; + + // Check if this is a swapped endian type (presence of attribute indicates swapping) + let swapped = instance + .class() + .as_object() + .get_attr("_swappedbytes_", vm) + .is_ok(); + + // Update buffer when value changes + let buffer_bytes = value_to_bytes_endian(&type_code, &content, swapped, vm); + *zelf.0.buffer.write() = std::borrow::Cow::Owned(buffer_bytes); + + // For c_char_p (type "z"), c_wchar_p (type "Z"), and py_object (type "O"), + // keep the reference in _objects + if type_code == "z" || type_code == "Z" || type_code == "O" { + *zelf.0.objects.write() = Some(value); + } + Ok(()) + } + + #[pyclassmethod] + fn repeat(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { + use super::array::array_type_from_ctype; + + if n < 0 { + return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); + } + // Use cached array type creation + array_type_from_ctype(cls.into(), n as usize, vm) + } + + /// Simple_from_outparm - convert output parameter back to Python value + /// For direct subclasses of _SimpleCData (e.g., c_int), returns the value. + /// For subclasses of those (e.g., class MyInt(c_int)), returns self. + #[pymethod] + fn __ctypes_from_outparam__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // _ctypes_simple_instance: returns true if NOT a direct subclass of Simple_Type + // i.e., c_int (direct) -> false, MyInt(c_int) (subclass) -> true + let is_subclass_of_simple = { + let cls = zelf.class(); + let bases = cls.bases.read(); + // If base is NOT _SimpleCData, then it's a subclass of a subclass + !bases + .iter() + .any(|base| base.name().to_string() == "_SimpleCData") + }; + + if is_subclass_of_simple { + // Subclass of simple type (e.g., MyInt(c_int)): return self + Ok(zelf.into()) + } else { + // Direct simple type (e.g., c_int): return value + PyCSimple::value(zelf.into(), vm) + } + } +} + +impl PyCSimple { + /// Extract the value from this ctypes object as an owned FfiArgValue. + /// The value must be kept alive until after the FFI call completes. + pub fn to_ffi_value( + &self, + ty: libffi::middle::Type, + _vm: &VirtualMachine, + ) -> Option<FfiArgValue> { + let buffer = self.0.buffer.read(); + let bytes: &[u8] = &buffer; + + if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u8().as_raw_ptr()) { + if !bytes.is_empty() { + return Some(FfiArgValue::U8(bytes[0])); + } + } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i8().as_raw_ptr()) { + if !bytes.is_empty() { + return Some(FfiArgValue::I8(bytes[0] as i8)); + } + } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u16().as_raw_ptr()) { + if bytes.len() >= 2 { + return Some(FfiArgValue::U16(u16::from_ne_bytes([bytes[0], bytes[1]]))); + } + } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i16().as_raw_ptr()) { + if bytes.len() >= 2 { + return Some(FfiArgValue::I16(i16::from_ne_bytes([bytes[0], bytes[1]]))); + } + } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u32().as_raw_ptr()) { + if bytes.len() >= 4 { + return Some(FfiArgValue::U32(u32::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], + ]))); + } + } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i32().as_raw_ptr()) { + if bytes.len() >= 4 { + return Some(FfiArgValue::I32(i32::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], + ]))); + } + } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u64().as_raw_ptr()) { + if bytes.len() >= 8 { + return Some(FfiArgValue::U64(u64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ]))); + } + } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i64().as_raw_ptr()) { + if bytes.len() >= 8 { + return Some(FfiArgValue::I64(i64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ]))); + } + } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::f32().as_raw_ptr()) { + if bytes.len() >= 4 { + return Some(FfiArgValue::F32(f32::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], + ]))); + } + } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::f64().as_raw_ptr()) { + if bytes.len() >= 8 { + return Some(FfiArgValue::F64(f64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ]))); + } + } else if std::ptr::eq( + ty.as_raw_ptr(), + libffi::middle::Type::pointer().as_raw_ptr(), + ) && bytes.len() >= std::mem::size_of::<usize>() + { + let val = + usize::from_ne_bytes(bytes[..std::mem::size_of::<usize>()].try_into().unwrap()); + return Some(FfiArgValue::Pointer(val)); + } + None + } +} + +impl AsBuffer for PyCSimple { + fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { + let buffer_len = zelf.0.buffer.read().len(); + let buf = PyBuffer::new( + zelf.to_owned().into(), + BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes + &CDATA_BUFFER_METHODS, + ); + Ok(buf) + } +} + +/// Simple_bool: return non-zero if any byte in buffer is non-zero +impl AsNumber for PyCSimple { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|obj, _vm| { + let zelf = obj + .downcast_ref::<PyCSimple>() + .expect("PyCSimple::as_number called on non-PyCSimple"); + let buffer = zelf.0.buffer.read(); + // Simple_bool: memcmp(self->b_ptr, zeros, self->b_size) + // Returns true if any byte is non-zero + Ok(buffer.iter().any(|&b| b != 0)) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index ca67a2fe7d6..10b8812e42c 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -1,42 +1,60 @@ -use super::base::{CDataObject, PyCData}; -use super::field::PyCField; -use super::util::StgInfo; +use super::base::{CDATA_BUFFER_METHODS, PyCData, PyCField, StgInfo, StgInfoFlags}; use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; -use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; -use crate::stdlib::ctypes::_ctypes::get_size; -use crate::types::{AsBuffer, AsNumber, Constructor}; -use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; -use indexmap::IndexMap; +use crate::function::PySetterValue; +use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}; +use crate::types::{AsBuffer, AsNumber, Constructor, Initializer, SetAttr}; +use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use num_traits::ToPrimitive; -use rustpython_common::lock::PyRwLock; +use std::borrow::Cow; use std::fmt::Debug; +/// Calculate Structure type size from _fields_ (sum of field sizes) +pub(super) fn calculate_struct_size(cls: &Py<PyType>, vm: &VirtualMachine) -> PyResult<usize> { + if let Ok(fields_attr) = cls.as_object().get_attr("_fields_", vm) { + let fields: Vec<PyObjectRef> = fields_attr.try_to_value(vm)?; + let mut total_size = 0usize; + + for field in fields.iter() { + if let Some(tuple) = field.downcast_ref::<PyTuple>() + && let Some(field_type) = tuple.get(1) + { + total_size += super::_ctypes::sizeof(field_type.clone(), vm)?; + } + } + return Ok(total_size); + } + Ok(0) +} + /// PyCStructType - metaclass for Structure #[pyclass(name = "PyCStructType", base = PyType, module = "_ctypes")] #[derive(Debug)] #[repr(transparent)] -pub struct PyCStructType(PyType); +pub(super) struct PyCStructType(PyType); impl Constructor for PyCStructType { type Args = FuncArgs; fn slot_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // 1. Create the new class using PyType::py_new + // 1. Create the new class using PyType::slot_new let new_class = crate::builtins::type_::PyType::slot_new(metatype, args, vm)?; - // 2. Process _fields_ if defined on the new class + // 2. Get the new type let new_type = new_class .clone() .downcast::<PyType>() .map_err(|_| vm.new_type_error("expected type"))?; - // Only process _fields_ if defined directly on this class (not inherited) - if let Some(fields_attr) = new_type.get_direct_attr(vm.ctx.intern_str("_fields_")) { - Self::process_fields(&new_type, fields_attr, vm)?; - } + // 3. Mark base classes as finalized (subclassing finalizes the parent) + new_type.mark_bases_final(); + + // 4. Initialize StgInfo for the new type (initialized=false, to be set in init) + let stg_info = StgInfo::default(); + let _ = new_type.init_type_data(stg_info); + // Note: _fields_ processing moved to Initializer::init() Ok(new_class) } @@ -45,11 +63,102 @@ impl Constructor for PyCStructType { } } -#[pyclass(flags(BASETYPE), with(AsNumber, Constructor))] +impl Initializer for PyCStructType { + type Args = FuncArgs; + + fn init(zelf: crate::PyRef<Self>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + // Get the type as PyTypeRef by converting PyRef<Self> -> PyObjectRef -> PyRef<PyType> + let obj: PyObjectRef = zelf.clone().into(); + let new_type: PyTypeRef = obj + .downcast() + .map_err(|_| vm.new_type_error("expected type"))?; + + // Backward compatibility: skip initialization for abstract types + if new_type + .get_direct_attr(vm.ctx.intern_str("_abstract_")) + .is_some() + { + return Ok(()); + } + + new_type.check_not_initialized(vm)?; + + // Process _fields_ if defined directly on this class (not inherited) + if let Some(fields_attr) = new_type.get_direct_attr(vm.ctx.intern_str("_fields_")) { + Self::process_fields(&new_type, fields_attr, vm)?; + } else { + // No _fields_ defined - try to copy from base class (PyCStgInfo_clone) + let (has_base_info, base_clone) = { + let bases = new_type.bases.read(); + if let Some(base) = bases.first() { + (base.stg_info_opt().is_some(), Some(base.clone())) + } else { + (false, None) + } + }; + + if has_base_info && let Some(ref base) = base_clone { + // Clone base StgInfo (release guard before getting mutable reference) + let stg_info_opt = base.stg_info_opt().map(|baseinfo| { + let mut stg_info = baseinfo.clone(); + stg_info.flags &= !StgInfoFlags::DICTFLAG_FINAL; // Clear FINAL in subclass + stg_info.initialized = true; + stg_info + }); + + if let Some(stg_info) = stg_info_opt { + // Mark base as FINAL (now guard is released) + if let Some(mut base_stg) = base.get_type_data_mut::<StgInfo>() { + base_stg.flags |= StgInfoFlags::DICTFLAG_FINAL; + } + + super::base::set_or_init_stginfo(&new_type, stg_info); + return Ok(()); + } + } + + // No base StgInfo - create default + let mut stg_info = StgInfo::new(0, 1); + stg_info.paramfunc = super::base::ParamFunc::Structure; + stg_info.format = Some("B".to_string()); + super::base::set_or_init_stginfo(&new_type, stg_info); + } + + Ok(()) + } +} + +#[pyclass(flags(BASETYPE), with(AsNumber, Constructor, Initializer, SetAttr))] impl PyCStructType { + #[pymethod] + fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // zelf is the structure type class that from_param was called on + let cls = zelf + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("from_param: expected a type"))?; + + // 1. If already an instance of the requested type, return it + if value.is_instance(cls.as_object(), vm)? { + return Ok(value); + } + + // 2. Check for _as_parameter_ attribute + if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) { + return PyCStructType::from_param(cls.as_object().to_owned(), as_parameter, vm); + } + + Err(vm.new_type_error(format!( + "expected {} instance instead of {}", + cls.name(), + value.class().name() + ))) + } + /// Called when a new Structure subclass is created #[pyclassmethod] fn __init_subclass__(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult<()> { + cls.mark_bases_final(); + // Check if _fields_ is defined if let Some(fields_attr) = cls.get_direct_attr(vm.ctx.intern_str("_fields_")) { Self::process_fields(&cls, fields_attr, vm)?; @@ -59,24 +168,63 @@ impl PyCStructType { /// Process _fields_ and create CField descriptors fn process_fields( - cls: &PyTypeRef, + cls: &Py<PyType>, fields_attr: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { + // Check if this is a swapped byte order structure + let is_swapped = cls.as_object().get_attr("_swappedbytes_", vm).is_ok(); + // Try to downcast to list or tuple let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>() { list.borrow_vec().to_vec() } else if let Some(tuple) = fields_attr.downcast_ref::<PyTuple>() { tuple.to_vec() } else { - return Err(vm.new_type_error("_fields_ must be a list or tuple".to_string())); + return Err(vm.new_type_error("_fields_ must be a list or tuple")); + }; + + let pack = super::base::get_usize_attr(cls.as_object(), "_pack_", 0, vm)?; + let forced_alignment = + super::base::get_usize_attr(cls.as_object(), "_align_", 1, vm)?.max(1); + + // Determine byte order for format string + let big_endian = super::base::is_big_endian(is_swapped); + + // Initialize offset, alignment, type flags, and ffi_field_types from base class + let ( + mut offset, + mut max_align, + mut has_pointer, + mut has_union, + mut has_bitfield, + mut ffi_field_types, + ) = { + let bases = cls.bases.read(); + if let Some(base) = bases.first() + && let Some(baseinfo) = base.stg_info_opt() + { + ( + baseinfo.size, + std::cmp::max(baseinfo.align, forced_alignment), + baseinfo.flags.contains(StgInfoFlags::TYPEFLAG_HASPOINTER), + baseinfo.flags.contains(StgInfoFlags::TYPEFLAG_HASUNION), + baseinfo.flags.contains(StgInfoFlags::TYPEFLAG_HASBITFIELD), + baseinfo.ffi_field_types.clone(), + ) + } else { + (0, forced_alignment, false, false, false, Vec::new()) + } }; - let mut offset = 0usize; + // Initialize PEP3118 format string + let mut format = String::from("T{"); + let mut last_end = 0usize; // Track end of last field for padding calculation + for (index, field) in fields.iter().enumerate() { let field_tuple = field .downcast_ref::<PyTuple>() - .ok_or_else(|| vm.new_type_error("_fields_ must contain tuples".to_string()))?; + .ok_or_else(|| vm.new_type_error("_fields_ must contain tuples"))?; if field_tuple.len() < 2 { return Err(vm.new_type_error( @@ -86,99 +234,173 @@ impl PyCStructType { let name = field_tuple .first() - .unwrap() + .expect("len checked") .downcast_ref::<PyStr>() - .ok_or_else(|| vm.new_type_error("field name must be a string".to_string()))? + .ok_or_else(|| vm.new_type_error("field name must be a string"))? .to_string(); - let field_type = field_tuple.get(1).unwrap().clone(); + let field_type = field_tuple.get(1).expect("len checked").clone(); + + // For swapped byte order structures, validate field type supports byte swapping + if is_swapped { + super::base::check_other_endian_support(&field_type, vm)?; + } + + // Get size and alignment of the field type + let size = super::base::get_field_size(&field_type, vm)?; + let field_align = super::base::get_field_align(&field_type, vm); + + // Calculate effective alignment (PyCField_FromDesc) + let effective_align = if pack > 0 { + std::cmp::min(pack, field_align) + } else { + field_align + }; + + // Apply padding to align offset (cfield.c NO_BITFIELD case) + if effective_align > 0 && offset % effective_align != 0 { + let delta = effective_align - (offset % effective_align); + offset += delta; + } + + max_align = max_align.max(effective_align); + + // Propagate type flags from field type (HASPOINTER, HASUNION, HASBITFIELD) + if let Some(type_obj) = field_type.downcast_ref::<PyType>() + && let Some(field_stg) = type_obj.stg_info_opt() + { + // HASPOINTER: propagate if field is pointer or contains pointer + if field_stg.flags.intersects( + StgInfoFlags::TYPEFLAG_ISPOINTER | StgInfoFlags::TYPEFLAG_HASPOINTER, + ) { + has_pointer = true; + } + // HASUNION, HASBITFIELD: propagate directly + if field_stg.flags.contains(StgInfoFlags::TYPEFLAG_HASUNION) { + has_union = true; + } + if field_stg.flags.contains(StgInfoFlags::TYPEFLAG_HASBITFIELD) { + has_bitfield = true; + } + // Collect FFI type for this field + ffi_field_types.push(field_stg.to_ffi_type()); + } + + // Mark field type as finalized (using type as field finalizes it) + if let Some(type_obj) = field_type.downcast_ref::<PyType>() { + if let Some(mut stg_info) = type_obj.get_type_data_mut::<StgInfo>() { + stg_info.flags |= StgInfoFlags::DICTFLAG_FINAL; + } else { + // Create StgInfo with FINAL flag if it doesn't exist + let mut stg_info = StgInfo::new(size, field_align); + stg_info.flags |= StgInfoFlags::DICTFLAG_FINAL; + let _ = type_obj.init_type_data(stg_info); + } + } + + // Build format string: add padding before field + let padding = offset - last_end; + if padding > 0 { + if padding != 1 { + format.push_str(&padding.to_string()); + } + format.push('x'); + } + + // Get field format and add to format string + let field_format = super::base::get_field_format(&field_type, big_endian, vm); + + // Handle arrays: prepend shape + if let Some(type_obj) = field_type.downcast_ref::<PyType>() + && let Some(field_stg) = type_obj.stg_info_opt() + && !field_stg.shape.is_empty() + { + let shape_str = field_stg + .shape + .iter() + .map(|d| d.to_string()) + .collect::<Vec<_>>() + .join(","); + format.push_str(&std::format!("({}){}", shape_str, field_format)); + } else { + format.push_str(&field_format); + } - // Get size of the field type - let size = Self::get_field_size(&field_type, vm)?; + // Add field name + format.push(':'); + format.push_str(&name); + format.push(':'); - // Create CField descriptor (accepts any ctypes type including arrays) - let c_field = PyCField::new(name.clone(), field_type, offset, size, index); + // Create CField descriptor with padding-adjusted offset + let field_type_ref = field_type + .clone() + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("_fields_ type must be a ctypes type"))?; + let c_field = PyCField::new(field_type_ref, offset as isize, size as isize, index); // Set the CField as a class attribute - cls.set_attr(vm.ctx.intern_str(name), c_field.to_pyobject(vm)); + cls.set_attr(vm.ctx.intern_str(name.clone()), c_field.to_pyobject(vm)); + // Update tracking + last_end = offset + size; offset += size; } - Ok(()) - } + // Calculate total_align = max(max_align, forced_alignment) + let total_align = std::cmp::max(max_align, forced_alignment); - /// Get the size of a ctypes type - fn get_field_size(field_type: &PyObject, vm: &VirtualMachine) -> PyResult<usize> { - // Try to get _type_ attribute for simple types - if let Some(size) = field_type - .get_attr("_type_", vm) - .ok() - .and_then(|type_attr| type_attr.str(vm).ok()) - .and_then(|type_str| { - let s = type_str.to_string(); - (s.len() == 1).then(|| get_size(&s)) - }) - { - return Ok(size); - } + // Calculate aligned_size (PyCStructUnionType_update_stginfo) + let aligned_size = if total_align > 0 { + offset.div_ceil(total_align) * total_align + } else { + offset + }; - // Try sizeof for other types - if let Some(s) = field_type - .get_attr("size_of_instances", vm) - .ok() - .and_then(|size_method| size_method.call((), vm).ok()) - .and_then(|size| size.try_int(vm).ok()) - .and_then(|n| n.as_bigint().to_usize()) - { - return Ok(s); + // Complete format string: add final padding and close + let final_padding = aligned_size - last_end; + if final_padding > 0 { + if final_padding != 1 { + format.push_str(&final_padding.to_string()); + } + format.push('x'); + } + format.push('}'); + + // Store StgInfo with aligned size and total alignment + let mut stg_info = StgInfo::new(aligned_size, total_align); + stg_info.format = Some(format); + stg_info.flags |= StgInfoFlags::DICTFLAG_FINAL; // Mark as finalized + if has_pointer { + stg_info.flags |= StgInfoFlags::TYPEFLAG_HASPOINTER; + } + if has_union { + stg_info.flags |= StgInfoFlags::TYPEFLAG_HASUNION; } + if has_bitfield { + stg_info.flags |= StgInfoFlags::TYPEFLAG_HASBITFIELD; + } + stg_info.paramfunc = super::base::ParamFunc::Structure; + // Set byte order: swap if _swappedbytes_ is defined + stg_info.big_endian = super::base::is_big_endian(is_swapped); + // Store FFI field types for structure passing + stg_info.ffi_field_types = ffi_field_types; + super::base::set_or_init_stginfo(cls, stg_info); - // Default to pointer size for unknown types - Ok(std::mem::size_of::<usize>()) - } + // Process _anonymous_ fields + super::base::make_anon_fields(cls, vm)?; - /// Get the alignment of a ctypes type - fn get_field_align(field_type: &PyObject, vm: &VirtualMachine) -> usize { - // Try to get _type_ attribute for simple types - if let Some(align) = field_type - .get_attr("_type_", vm) - .ok() - .and_then(|type_attr| type_attr.str(vm).ok()) - .and_then(|type_str| { - let s = type_str.to_string(); - (s.len() == 1).then(|| get_size(&s)) // alignment == size for simple types - }) - { - return align; - } - // Default alignment - 1 + Ok(()) } #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - use super::array::create_array_type_with_stg_info; - use crate::stdlib::ctypes::_ctypes::size_of; + use super::array::array_type_from_ctype; if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } - - // Calculate element size from the Structure type - let element_size = size_of(cls.clone().into(), vm)?; - - let total_size = element_size - .checked_mul(n as usize) - .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; - let stg_info = super::util::StgInfo::new_array( - total_size, - element_size, - n as usize, - cls.clone().into(), - element_size, - ); - create_array_type_with_stg_info(stg_info, vm) + // Use cached array type creation + array_type_from_ctype(cls.into(), n as usize, vm) } } @@ -188,12 +410,12 @@ impl AsNumber for PyCStructType { multiply: Some(|a, b, vm| { let cls = a .downcast_ref::<PyType>() - .ok_or_else(|| vm.new_type_error("expected type".to_owned()))?; + .ok_or_else(|| vm.new_type_error("expected type"))?; let n = b .try_index(vm)? .as_bigint() .to_isize() - .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; + .ok_or_else(|| vm.new_overflow_error("array size too large"))?; PyCStructType::__mul__(cls.to_owned(), n, vm) }), ..PyNumberMethods::NOT_IMPLEMENTED @@ -202,14 +424,70 @@ impl AsNumber for PyCStructType { } } -/// Structure field info stored in instance -#[allow(dead_code)] -#[derive(Debug, Clone)] -pub struct FieldInfo { - pub name: String, - pub offset: usize, - pub size: usize, - pub type_ref: PyTypeRef, +impl SetAttr for PyCStructType { + fn setattro( + zelf: &Py<Self>, + attr_name: &Py<PyStr>, + value: PySetterValue, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Check if _fields_ is being set + if attr_name.as_str() == "_fields_" { + let pytype: &Py<PyType> = zelf.to_base(); + + // Check finalization in separate scope to release read lock before process_fields + // This prevents deadlock: process_fields needs write lock on the same RwLock + let is_final = { + let Some(stg_info) = pytype.get_type_data::<StgInfo>() else { + return Err(vm.new_type_error("ctypes state is not initialized")); + }; + stg_info.is_final() + }; // Read lock released here + + if is_final { + return Err(vm.new_attribute_error("_fields_ is final")); + } + + // Process _fields_ and set attribute + let PySetterValue::Assign(fields_value) = value else { + return Err(vm.new_attribute_error("cannot delete _fields_")); + }; + // Process fields (this will also set DICTFLAG_FINAL) + PyCStructType::process_fields(pytype, fields_value.clone(), vm)?; + // Set the _fields_ attribute on the type + pytype + .attributes + .write() + .insert(vm.ctx.intern_str("_fields_"), fields_value); + return Ok(()); + } + // Delegate to PyType's setattro logic for type attributes + let attr_name_interned = vm.ctx.intern_str(attr_name.as_str()); + let pytype: &Py<PyType> = zelf.to_base(); + + // Check for data descriptor first + if let Some(attr) = pytype.get_class_attr(attr_name_interned) { + let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load()); + if let Some(descriptor) = descr_set { + return descriptor(&attr, pytype.to_owned().into(), value, vm); + } + } + + // Store in type's attributes dict + if let PySetterValue::Assign(value) = value { + pytype.attributes.write().insert(attr_name_interned, value); + } else { + let prev = pytype.attributes.write().shift_remove(attr_name_interned); + if prev.is_none() { + return Err(vm.new_attribute_error(format!( + "type object '{}' has no attribute '{}'", + pytype.name(), + attr_name.as_str(), + ))); + } + } + Ok(()) + } } /// PyCStructure - base class for Structure instances @@ -219,19 +497,13 @@ pub struct FieldInfo { base = PyCData, metaclass = "PyCStructType" )] -pub struct PyCStructure { - _base: PyCData, - /// Common CDataObject for memory buffer - pub(super) cdata: PyRwLock<CDataObject>, - /// Field information (name -> FieldInfo) - #[allow(dead_code)] - pub(super) fields: PyRwLock<IndexMap<String, FieldInfo>>, -} +#[repr(transparent)] +pub struct PyCStructure(pub PyCData); impl Debug for PyCStructure { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PyCStructure") - .field("size", &self.cdata.read().size()) + .field("size", &self.0.size()) .finish() } } @@ -240,13 +512,22 @@ impl Constructor for PyCStructure { type Args = FuncArgs; fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // Check for abstract class and extract values in a block to drop the borrow + let (total_size, total_align, length) = { + let stg_info = cls.stg_info(vm)?; + (stg_info.size, stg_info.align, stg_info.length) + }; + + // Mark the class as finalized (instance creation finalizes the type) + if let Some(mut stg_info_mut) = cls.get_type_data_mut::<StgInfo>() { + stg_info_mut.flags |= StgInfoFlags::DICTFLAG_FINAL; + } + // Get _fields_ from the class using get_attr to properly search MRO let fields_attr = cls.as_object().get_attr("_fields_", vm).ok(); - let mut fields_map = IndexMap::new(); - let mut total_size = 0usize; - let mut max_align = 1usize; - + // Collect field names for initialization + let mut field_names: Vec<String> = Vec::new(); if let Some(fields_attr) = fields_attr { let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>() { @@ -257,7 +538,6 @@ impl Constructor for PyCStructure { vec![] }; - let mut offset = 0usize; for field in fields.iter() { let Some(field_tuple) = field.downcast_ref::<PyTuple>() else { continue; @@ -265,43 +545,21 @@ impl Constructor for PyCStructure { if field_tuple.len() < 2 { continue; } - let Some(name) = field_tuple.first().unwrap().downcast_ref::<PyStr>() else { - continue; - }; - let name = name.to_string(); - let field_type = field_tuple.get(1).unwrap().clone(); - let size = PyCStructType::get_field_size(&field_type, vm)?; - let field_align = PyCStructType::get_field_align(&field_type, vm); - max_align = max_align.max(field_align); - - let type_ref = field_type - .downcast::<PyType>() - .unwrap_or_else(|_| vm.ctx.types.object_type.to_owned()); - - fields_map.insert( - name.clone(), - FieldInfo { - name, - offset, - size, - type_ref, - }, - ); - - offset += size; + if let Some(name) = field_tuple.first().unwrap().downcast_ref::<PyStr>() { + field_names.push(name.to_string()); + } } - total_size = offset; } - // Initialize buffer with zeros - let mut stg_info = StgInfo::new(total_size, max_align); - stg_info.length = fields_map.len(); - let cdata = CDataObject::from_stg_info(&stg_info); - let instance = PyCStructure { - _base: PyCData::new(cdata.clone()), - cdata: PyRwLock::new(cdata), - fields: PyRwLock::new(fields_map.clone()), + // Initialize buffer with zeros using computed size + let mut stg_info = StgInfo::new(total_size, total_align); + stg_info.length = if length > 0 { + length + } else { + field_names.len() }; + stg_info.paramfunc = super::base::ParamFunc::Structure; + let instance = PyCStructure(PyCData::from_stg_info(&stg_info)); // Handle keyword arguments for field initialization let py_instance = instance.into_ref_with_type(vm, cls.clone())?; @@ -309,21 +567,21 @@ impl Constructor for PyCStructure { // Set field values from kwargs using standard attribute setting for (key, value) in args.kwargs.iter() { - if fields_map.contains_key(key.as_str()) { + if field_names.iter().any(|n| n == key.as_str()) { py_obj.set_attr(vm.ctx.intern_str(key.as_str()), value.clone(), vm)?; } } // Set field values from positional args - let field_names: Vec<String> = fields_map.keys().cloned().collect(); + if args.args.len() > field_names.len() { + return Err(vm.new_type_error("too many initializers".to_string())); + } for (i, value) in args.args.iter().enumerate() { - if i < field_names.len() { - py_obj.set_attr( - vm.ctx.intern_str(field_names[i].as_str()), - value.clone(), - vm, - )?; - } + py_obj.set_attr( + vm.ctx.intern_str(field_names[i].as_str()), + value.clone(), + vm, + )?; } Ok(py_instance.into()) @@ -337,11 +595,11 @@ impl Constructor for PyCStructure { // Note: GetAttr and SetAttr are not implemented here. // Field access is handled by CField descriptors registered on the class. -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsBuffer))] impl PyCStructure { #[pygetset] - fn _objects(&self) -> Option<PyObjectRef> { - self.cdata.read().objects.clone() + fn _b0_(&self) -> Option<PyObjectRef> { + self.0.base.read().clone() } #[pygetset] @@ -349,165 +607,30 @@ impl PyCStructure { // Return the _fields_ from the class, not instance vm.ctx.none() } - - #[pyclassmethod] - fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { - use crate::stdlib::ctypes::_ctypes::size_of; - - // Get size from cls - let size = size_of(cls.clone().into(), vm)?; - - // Read data from address - if address == 0 || size == 0 { - return Err(vm.new_value_error("NULL pointer access".to_owned())); - } - let data = unsafe { - let ptr = address as *const u8; - std::slice::from_raw_parts(ptr, size).to_vec() - }; - - // Create instance - let cdata = CDataObject::from_bytes(data, None); - Ok(PyCStructure { - _base: PyCData::new(cdata.clone()), - cdata: PyRwLock::new(cdata), - fields: PyRwLock::new(IndexMap::new()), - } - .into_ref_with_type(vm, cls)? - .into()) - } - - #[pyclassmethod] - fn from_buffer( - cls: PyTypeRef, - source: PyObjectRef, - offset: crate::function::OptionalArg<isize>, - vm: &VirtualMachine, - ) -> PyResult { - use crate::TryFromObject; - use crate::protocol::PyBuffer; - use crate::stdlib::ctypes::_ctypes::size_of; - - let offset = offset.unwrap_or(0); - if offset < 0 { - return Err(vm.new_value_error("offset cannot be negative".to_owned())); - } - let offset = offset as usize; - - // Get buffer from source - let buffer = PyBuffer::try_from_object(vm, source.clone())?; - - // Check if buffer is writable - if buffer.desc.readonly { - return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); - } - - // Get size from cls - let size = size_of(cls.clone().into(), vm)?; - - // Check if buffer is large enough - let buffer_len = buffer.desc.len; - if offset + size > buffer_len { - return Err(vm.new_value_error(format!( - "Buffer size too small ({} instead of at least {} bytes)", - buffer_len, - offset + size - ))); - } - - // Read bytes from buffer at offset - let bytes = buffer.obj_bytes(); - let data = bytes[offset..offset + size].to_vec(); - - // Create instance - let cdata = CDataObject::from_bytes(data, Some(source)); - Ok(PyCStructure { - _base: PyCData::new(cdata.clone()), - cdata: PyRwLock::new(cdata), - fields: PyRwLock::new(IndexMap::new()), - } - .into_ref_with_type(vm, cls)? - .into()) - } - - #[pyclassmethod] - fn from_buffer_copy( - cls: PyTypeRef, - source: crate::function::ArgBytesLike, - offset: crate::function::OptionalArg<isize>, - vm: &VirtualMachine, - ) -> PyResult { - use crate::stdlib::ctypes::_ctypes::size_of; - - let offset = offset.unwrap_or(0); - if offset < 0 { - return Err(vm.new_value_error("offset cannot be negative".to_owned())); - } - let offset = offset as usize; - - // Get size from cls - let size = size_of(cls.clone().into(), vm)?; - - // Borrow bytes from source - let source_bytes = source.borrow_buf(); - let buffer_len = source_bytes.len(); - - // Check if buffer is large enough - if offset + size > buffer_len { - return Err(vm.new_value_error(format!( - "Buffer size too small ({} instead of at least {} bytes)", - buffer_len, - offset + size - ))); - } - - // Copy bytes from buffer at offset - let data = source_bytes[offset..offset + size].to_vec(); - - // Create instance - let cdata = CDataObject::from_bytes(data, None); - Ok(PyCStructure { - _base: PyCData::new(cdata.clone()), - cdata: PyRwLock::new(cdata), - fields: PyRwLock::new(IndexMap::new()), - } - .into_ref_with_type(vm, cls)? - .into()) - } } -static STRUCTURE_BUFFER_METHODS: BufferMethods = BufferMethods { - obj_bytes: |buffer| { - rustpython_common::lock::PyMappedRwLockReadGuard::map( - rustpython_common::lock::PyRwLockReadGuard::map( - buffer.obj_as::<PyCStructure>().cdata.read(), - |x: &CDataObject| x, - ), - |x: &CDataObject| x.buffer.as_slice(), - ) - .into() - }, - obj_bytes_mut: |buffer| { - rustpython_common::lock::PyMappedRwLockWriteGuard::map( - rustpython_common::lock::PyRwLockWriteGuard::map( - buffer.obj_as::<PyCStructure>().cdata.write(), - |x: &mut CDataObject| x, - ), - |x: &mut CDataObject| x.buffer.as_mut_slice(), - ) - .into() - }, - release: |_| {}, - retain: |_| {}, -}; - impl AsBuffer for PyCStructure { fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { - let buffer_len = zelf.cdata.read().buffer.len(); + let buffer_len = zelf.0.buffer.read().len(); + + // PyCData_NewGetBuffer: use info->format if available, otherwise "B" + let format = zelf + .class() + .stg_info_opt() + .and_then(|info| info.format.clone()) + .unwrap_or_else(|| "B".to_string()); + + // Structure: ndim=0, shape=(), itemsize=struct_size let buf = PyBuffer::new( zelf.to_owned().into(), - BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes - &STRUCTURE_BUFFER_METHODS, + BufferDescriptor { + len: buffer_len, + readonly: false, + itemsize: buffer_len, + format: Cow::Owned(format), + dim_desc: vec![], // ndim=0 means empty dim_desc + }, + &CDATA_BUFFER_METHODS, ); Ok(buf) } diff --git a/crates/vm/src/stdlib/ctypes/thunk.rs b/crates/vm/src/stdlib/ctypes/thunk.rs deleted file mode 100644 index 2de2308e1a3..00000000000 --- a/crates/vm/src/stdlib/ctypes/thunk.rs +++ /dev/null @@ -1,319 +0,0 @@ -//! FFI callback (thunk) implementation for ctypes. -//! -//! This module implements CThunkObject which wraps Python callables -//! to be callable from C code via libffi closures. - -use crate::builtins::{PyStr, PyType, PyTypeRef}; -use crate::vm::thread::with_current_vm; -use crate::{PyObjectRef, PyPayload, PyResult, VirtualMachine}; -use libffi::low; -use libffi::middle::{Cif, Closure, CodePtr, Type}; -use num_traits::ToPrimitive; -use rustpython_common::lock::PyRwLock; -use std::ffi::c_void; -use std::fmt::Debug; - -use super::base::ffi_type_from_str; -/// Userdata passed to the libffi callback. -/// This contains everything needed to invoke the Python callable. -pub struct ThunkUserData { - /// The Python callable to invoke - pub callable: PyObjectRef, - /// Argument types for conversion - pub arg_types: Vec<PyTypeRef>, - /// Result type for conversion (None means void) - pub res_type: Option<PyTypeRef>, -} - -/// Get the type code string from a ctypes type -fn get_type_code(ty: &PyTypeRef, vm: &VirtualMachine) -> Option<String> { - ty.get_attr(vm.ctx.intern_str("_type_")) - .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())) -} - -/// Convert a C value to a Python object based on the type code -fn ffi_to_python(ty: &PyTypeRef, ptr: *const c_void, vm: &VirtualMachine) -> PyObjectRef { - let type_code = get_type_code(ty, vm); - // SAFETY: ptr is guaranteed to be valid by libffi calling convention - unsafe { - match type_code.as_deref() { - Some("b") => vm.ctx.new_int(*(ptr as *const i8) as i32).into(), - Some("B") => vm.ctx.new_int(*(ptr as *const u8) as i32).into(), - Some("c") => vm.ctx.new_bytes(vec![*(ptr as *const u8)]).into(), - Some("h") => vm.ctx.new_int(*(ptr as *const i16) as i32).into(), - Some("H") => vm.ctx.new_int(*(ptr as *const u16) as i32).into(), - Some("i") => vm.ctx.new_int(*(ptr as *const i32)).into(), - Some("I") => vm.ctx.new_int(*(ptr as *const u32)).into(), - Some("l") => vm.ctx.new_int(*(ptr as *const libc::c_long)).into(), - Some("L") => vm.ctx.new_int(*(ptr as *const libc::c_ulong)).into(), - Some("q") => vm.ctx.new_int(*(ptr as *const libc::c_longlong)).into(), - Some("Q") => vm.ctx.new_int(*(ptr as *const libc::c_ulonglong)).into(), - Some("f") => vm.ctx.new_float(*(ptr as *const f32) as f64).into(), - Some("d") => vm.ctx.new_float(*(ptr as *const f64)).into(), - Some("P") | Some("z") | Some("Z") => vm.ctx.new_int(ptr as usize).into(), - _ => vm.ctx.none(), - } - } -} - -/// Convert a Python object to a C value and store it at the result pointer -fn python_to_ffi(obj: PyResult, ty: &PyTypeRef, result: *mut c_void, vm: &VirtualMachine) { - let obj = match obj { - Ok(o) => o, - Err(_) => return, // Exception occurred, leave result as-is - }; - - let type_code = get_type_code(ty, vm); - // SAFETY: result is guaranteed to be valid by libffi calling convention - unsafe { - match type_code.as_deref() { - Some("b") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut i8) = i.as_bigint().to_i8().unwrap_or(0); - } - } - Some("B") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0); - } - } - Some("c") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0); - } - } - Some("h") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut i16) = i.as_bigint().to_i16().unwrap_or(0); - } - } - Some("H") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut u16) = i.as_bigint().to_u16().unwrap_or(0); - } - } - Some("i") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut i32) = i.as_bigint().to_i32().unwrap_or(0); - } - } - Some("I") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut u32) = i.as_bigint().to_u32().unwrap_or(0); - } - } - Some("l") | Some("q") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut i64) = i.as_bigint().to_i64().unwrap_or(0); - } - } - Some("L") | Some("Q") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut u64) = i.as_bigint().to_u64().unwrap_or(0); - } - } - Some("f") => { - if let Ok(f) = obj.try_float(vm) { - *(result as *mut f32) = f.to_f64() as f32; - } - } - Some("d") => { - if let Ok(f) = obj.try_float(vm) { - *(result as *mut f64) = f.to_f64(); - } - } - Some("P") | Some("z") | Some("Z") => { - if let Ok(i) = obj.try_int(vm) { - *(result as *mut usize) = i.as_bigint().to_usize().unwrap_or(0); - } - } - _ => {} - } - } -} - -/// The callback function that libffi calls when the closure is invoked. -/// This function converts C arguments to Python objects, calls the Python -/// callable, and converts the result back to C. -unsafe extern "C" fn thunk_callback( - _cif: &low::ffi_cif, - result: &mut c_void, - args: *const *const c_void, - userdata: &ThunkUserData, -) { - with_current_vm(|vm| { - // Convert C arguments to Python objects - let py_args: Vec<PyObjectRef> = userdata - .arg_types - .iter() - .enumerate() - .map(|(i, ty)| { - let arg_ptr = unsafe { *args.add(i) }; - ffi_to_python(ty, arg_ptr, vm) - }) - .collect(); - - // Call the Python callable - let py_result = userdata.callable.call(py_args, vm); - - // Convert result back to C type - if let Some(ref res_type) = userdata.res_type { - python_to_ffi(py_result, res_type, result as *mut c_void, vm); - } - }); -} - -/// Holds the closure and userdata together to ensure proper lifetime. -/// The userdata is leaked to create a 'static reference that the closure can use. -struct ThunkData { - #[allow(dead_code)] - closure: Closure<'static>, - /// Raw pointer to the leaked userdata, for cleanup - userdata_ptr: *mut ThunkUserData, -} - -impl Drop for ThunkData { - fn drop(&mut self) { - // SAFETY: We created this with Box::into_raw, so we can reclaim it - unsafe { - drop(Box::from_raw(self.userdata_ptr)); - } - } -} - -/// CThunkObject wraps a Python callable to make it callable from C code. -#[pyclass(name = "CThunkObject", module = "_ctypes")] -#[derive(PyPayload)] -pub struct PyCThunk { - /// The Python callable - callable: PyObjectRef, - /// The libffi closure (must be kept alive) - #[allow(dead_code)] - thunk_data: PyRwLock<Option<ThunkData>>, - /// The code pointer for the closure - code_ptr: CodePtr, -} - -impl Debug for PyCThunk { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PyCThunk") - .field("callable", &self.callable) - .finish() - } -} - -impl PyCThunk { - /// Create a new thunk wrapping a Python callable. - /// - /// # Arguments - /// * `callable` - The Python callable to wrap - /// * `arg_types` - Optional sequence of argument types - /// * `res_type` - Optional result type - /// * `vm` - The virtual machine - pub fn new( - callable: PyObjectRef, - arg_types: Option<PyObjectRef>, - res_type: Option<PyObjectRef>, - vm: &VirtualMachine, - ) -> PyResult<Self> { - // Parse argument types - let arg_type_vec: Vec<PyTypeRef> = if let Some(args) = arg_types { - if vm.is_none(&args) { - Vec::new() - } else { - let mut types = Vec::new(); - for item in args.try_to_value::<Vec<PyObjectRef>>(vm)? { - types.push(item.downcast::<PyType>().map_err(|_| { - vm.new_type_error("_argtypes_ must be a sequence of types".to_string()) - })?); - } - types - } - } else { - Vec::new() - }; - - // Parse result type - let res_type_ref: Option<PyTypeRef> = - if let Some(ref rt) = res_type { - if vm.is_none(rt) { - None - } else { - Some(rt.clone().downcast::<PyType>().map_err(|_| { - vm.new_type_error("restype must be a ctypes type".to_string()) - })?) - } - } else { - None - }; - - // Build FFI types - let ffi_arg_types: Vec<Type> = arg_type_vec - .iter() - .map(|ty| { - get_type_code(ty, vm) - .and_then(|code| ffi_type_from_str(&code)) - .unwrap_or(Type::pointer()) - }) - .collect(); - - let ffi_res_type = res_type_ref - .as_ref() - .and_then(|ty| get_type_code(ty, vm)) - .and_then(|code| ffi_type_from_str(&code)) - .unwrap_or(Type::void()); - - // Create the CIF - let cif = Cif::new(ffi_arg_types, ffi_res_type); - - // Create userdata and leak it to get a 'static reference - let userdata = Box::new(ThunkUserData { - callable: callable.clone(), - arg_types: arg_type_vec, - res_type: res_type_ref, - }); - let userdata_ptr = Box::into_raw(userdata); - - // SAFETY: We maintain the userdata lifetime by storing it in ThunkData - // and cleaning it up in Drop - let userdata_ref: &'static ThunkUserData = unsafe { &*userdata_ptr }; - - // Create the closure - let closure = Closure::new(cif, thunk_callback, userdata_ref); - - // Get the code pointer - let code_ptr = CodePtr(*closure.code_ptr() as *mut _); - - // Store closure and userdata together - let thunk_data = ThunkData { - closure, - userdata_ptr, - }; - - Ok(Self { - callable, - thunk_data: PyRwLock::new(Some(thunk_data)), - code_ptr, - }) - } - - /// Get the code pointer for this thunk - pub fn code_ptr(&self) -> CodePtr { - self.code_ptr - } -} - -// SAFETY: PyCThunk is safe to send/sync because: -// - callable is a PyObjectRef which is Send+Sync -// - thunk_data contains the libffi closure which is heap-allocated -// - code_ptr is just a pointer to executable memory -unsafe impl Send for PyCThunk {} -unsafe impl Sync for PyCThunk {} - -#[pyclass] -impl PyCThunk { - #[pygetset] - fn callable(&self) -> PyObjectRef { - self.callable.clone() - } -} diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 308a5e4e98f..500aa8e6244 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -1,40 +1,60 @@ -use super::base::{CDataObject, PyCData}; -use super::field::PyCField; -use super::util::StgInfo; +use super::base::{CDATA_BUFFER_METHODS, StgInfoFlags}; +use super::{PyCData, PyCField, StgInfo}; use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; -use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer as ProtocolPyBuffer}; -use crate::stdlib::ctypes::_ctypes::get_size; -use crate::types::{AsBuffer, Constructor}; -use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; -use num_traits::ToPrimitive; -use rustpython_common::lock::PyRwLock; +use crate::function::PySetterValue; +use crate::protocol::{BufferDescriptor, PyBuffer}; +use crate::types::{AsBuffer, Constructor, Initializer, SetAttr}; +use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use std::borrow::Cow; + +/// Calculate Union type size from _fields_ (max field size) +pub(super) fn calculate_union_size(cls: &Py<PyType>, vm: &VirtualMachine) -> PyResult<usize> { + if let Ok(fields_attr) = cls.as_object().get_attr("_fields_", vm) { + let fields: Vec<PyObjectRef> = fields_attr.try_to_value(vm)?; + let mut max_size = 0usize; + + for field in fields.iter() { + if let Some(tuple) = field.downcast_ref::<PyTuple>() + && let Some(field_type) = tuple.get(1) + { + let field_size = super::_ctypes::sizeof(field_type.clone(), vm)?; + max_size = max_size.max(field_size); + } + } + return Ok(max_size); + } + Ok(0) +} /// PyCUnionType - metaclass for Union #[pyclass(name = "UnionType", base = PyType, module = "_ctypes")] #[derive(Debug)] #[repr(transparent)] -pub struct PyCUnionType(PyType); +pub(super) struct PyCUnionType(PyType); impl Constructor for PyCUnionType { type Args = FuncArgs; fn slot_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // 1. Create the new class using PyType::py_new - let new_class = crate::builtins::type_::PyType::slot_new(metatype, args, vm)?; + // 1. Create the new class using PyType::slot_new + let new_class = crate::builtins::PyType::slot_new(metatype, args, vm)?; - // 2. Process _fields_ if defined on the new class + // 2. Get the new type let new_type = new_class .clone() .downcast::<PyType>() .map_err(|_| vm.new_type_error("expected type"))?; - // Only process _fields_ if defined directly on this class (not inherited) - if let Some(fields_attr) = new_type.get_direct_attr(vm.ctx.intern_str("_fields_")) { - Self::process_fields(&new_type, fields_attr, vm)?; - } + // 3. Mark base classes as finalized (subclassing finalizes the parent) + new_type.mark_bases_final(); + + // 4. Initialize StgInfo for the new type (initialized=false, to be set in init) + let stg_info = StgInfo::default(); + let _ = new_type.init_type_data(stg_info); + // Note: _fields_ processing moved to Initializer::init() Ok(new_class) } @@ -43,26 +63,132 @@ impl Constructor for PyCUnionType { } } +impl Initializer for PyCUnionType { + type Args = FuncArgs; + + fn init(zelf: crate::PyRef<Self>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + // Get the type as PyTypeRef by converting PyRef<Self> -> PyObjectRef -> PyRef<PyType> + let obj: PyObjectRef = zelf.clone().into(); + let new_type: PyTypeRef = obj + .downcast() + .map_err(|_| vm.new_type_error("expected type"))?; + + // Check for _abstract_ attribute - skip initialization if present + if new_type + .get_direct_attr(vm.ctx.intern_str("_abstract_")) + .is_some() + { + return Ok(()); + } + + new_type.check_not_initialized(vm)?; + + // Process _fields_ if defined directly on this class (not inherited) + // Use set_attr to trigger setattro + if let Some(fields_attr) = new_type.get_direct_attr(vm.ctx.intern_str("_fields_")) { + new_type + .as_object() + .set_attr(vm.ctx.intern_str("_fields_"), fields_attr, vm)?; + } else { + // No _fields_ defined - try to copy from base class + let (has_base_info, base_clone) = { + let bases = new_type.bases.read(); + if let Some(base) = bases.first() { + (base.stg_info_opt().is_some(), Some(base.clone())) + } else { + (false, None) + } + }; + + if has_base_info && let Some(ref base) = base_clone { + // Clone base StgInfo (release guard before getting mutable reference) + let stg_info_opt = base.stg_info_opt().map(|baseinfo| { + let mut stg_info = baseinfo.clone(); + stg_info.flags &= !StgInfoFlags::DICTFLAG_FINAL; // Clear FINAL flag in subclass + stg_info.initialized = true; + stg_info + }); + + if let Some(stg_info) = stg_info_opt { + // Mark base as FINAL (now guard is released) + if let Some(mut base_stg) = base.get_type_data_mut::<StgInfo>() { + base_stg.flags |= StgInfoFlags::DICTFLAG_FINAL; + } + + super::base::set_or_init_stginfo(&new_type, stg_info); + return Ok(()); + } + } + + // No base StgInfo - create default + let mut stg_info = StgInfo::new(0, 1); + stg_info.flags |= StgInfoFlags::TYPEFLAG_HASUNION; + stg_info.paramfunc = super::base::ParamFunc::Union; + // PEP 3118 doesn't support union. Use 'B' for bytes. + stg_info.format = Some("B".to_string()); + super::base::set_or_init_stginfo(&new_type, stg_info); + } + + Ok(()) + } +} + impl PyCUnionType { /// Process _fields_ and create CField descriptors /// For Union, all fields start at offset 0 fn process_fields( - cls: &PyTypeRef, + cls: &Py<PyType>, fields_attr: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { + // Check if already finalized + { + let Some(stg_info) = cls.get_type_data::<StgInfo>() else { + return Err(vm.new_type_error("ctypes state is not initialized")); + }; + if stg_info.is_final() { + return Err(vm.new_attribute_error("_fields_ is final")); + } + } // Read lock released here + + // Check if this is a swapped byte order union + let is_swapped = cls.as_object().get_attr("_swappedbytes_", vm).is_ok(); + let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>() { list.borrow_vec().to_vec() } else if let Some(tuple) = fields_attr.downcast_ref::<PyTuple>() { tuple.to_vec() } else { - return Err(vm.new_type_error("_fields_ must be a list or tuple".to_string())); + return Err(vm.new_type_error("_fields_ must be a list or tuple")); + }; + + let pack = super::base::get_usize_attr(cls.as_object(), "_pack_", 0, vm)?; + let forced_alignment = + super::base::get_usize_attr(cls.as_object(), "_align_", 1, vm)?.max(1); + + // Initialize size, alignment, type flags, and ffi_field_types from base class + // Note: Union fields always start at offset 0, but we inherit base size/align + let (mut max_size, mut max_align, mut has_pointer, mut has_bitfield, mut ffi_field_types) = { + let bases = cls.bases.read(); + if let Some(base) = bases.first() + && let Some(baseinfo) = base.stg_info_opt() + { + ( + baseinfo.size, + std::cmp::max(baseinfo.align, forced_alignment), + baseinfo.flags.contains(StgInfoFlags::TYPEFLAG_HASPOINTER), + baseinfo.flags.contains(StgInfoFlags::TYPEFLAG_HASBITFIELD), + baseinfo.ffi_field_types.clone(), + ) + } else { + (0, forced_alignment, false, false, Vec::new()) + } }; for (index, field) in fields.iter().enumerate() { let field_tuple = field .downcast_ref::<PyTuple>() - .ok_or_else(|| vm.new_type_error("_fields_ must contain tuples".to_string()))?; + .ok_or_else(|| vm.new_type_error("_fields_ must contain tuples"))?; if field_tuple.len() < 2 { return Err(vm.new_type_error( @@ -72,66 +198,230 @@ impl PyCUnionType { let name = field_tuple .first() - .unwrap() + .expect("len checked") .downcast_ref::<PyStr>() - .ok_or_else(|| vm.new_type_error("field name must be a string".to_string()))? + .ok_or_else(|| vm.new_type_error("field name must be a string"))? .to_string(); - let field_type = field_tuple.get(1).unwrap().clone(); - let size = Self::get_field_size(&field_type, vm)?; + let field_type = field_tuple.get(1).expect("len checked").clone(); + + // For swapped byte order unions, validate field type supports byte swapping + if is_swapped { + super::base::check_other_endian_support(&field_type, vm)?; + } + + let size = super::base::get_field_size(&field_type, vm)?; + let field_align = super::base::get_field_align(&field_type, vm); + + // Calculate effective alignment + let effective_align = if pack > 0 { + std::cmp::min(pack, field_align) + } else { + field_align + }; + + max_size = max_size.max(size); + max_align = max_align.max(effective_align); + + // Propagate type flags from field type (HASPOINTER, HASBITFIELD) + if let Some(type_obj) = field_type.downcast_ref::<PyType>() + && let Some(field_stg) = type_obj.stg_info_opt() + { + // HASPOINTER: propagate if field is pointer or contains pointer + if field_stg.flags.intersects( + StgInfoFlags::TYPEFLAG_ISPOINTER | StgInfoFlags::TYPEFLAG_HASPOINTER, + ) { + has_pointer = true; + } + // HASBITFIELD: propagate directly + if field_stg.flags.contains(StgInfoFlags::TYPEFLAG_HASBITFIELD) { + has_bitfield = true; + } + // Collect FFI type for this field + ffi_field_types.push(field_stg.to_ffi_type()); + } + + // Mark field type as finalized (using type as field finalizes it) + if let Some(type_obj) = field_type.downcast_ref::<PyType>() { + if let Some(mut stg_info) = type_obj.get_type_data_mut::<StgInfo>() { + stg_info.flags |= StgInfoFlags::DICTFLAG_FINAL; + } else { + // Create StgInfo with FINAL flag if it doesn't exist + let mut stg_info = StgInfo::new(size, field_align); + stg_info.flags |= StgInfoFlags::DICTFLAG_FINAL; + let _ = type_obj.init_type_data(stg_info); + } + } // For Union, all fields start at offset 0 - // Create CField descriptor (accepts any ctypes type including arrays) - let c_field = PyCField::new(name.clone(), field_type, 0, size, index); + let field_type_ref = field_type + .clone() + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("_fields_ type must be a ctypes type"))?; + let c_field = PyCField::new(field_type_ref, 0, size as isize, index); cls.set_attr(vm.ctx.intern_str(name), c_field.to_pyobject(vm)); } + // Calculate total_align and aligned_size + let total_align = std::cmp::max(max_align, forced_alignment); + let aligned_size = if total_align > 0 { + max_size.div_ceil(total_align) * total_align + } else { + max_size + }; + + // Store StgInfo with aligned size + let mut stg_info = StgInfo::new(aligned_size, total_align); + stg_info.flags |= StgInfoFlags::DICTFLAG_FINAL | StgInfoFlags::TYPEFLAG_HASUNION; + // PEP 3118 doesn't support union. Use 'B' for bytes. + stg_info.format = Some("B".to_string()); + if has_pointer { + stg_info.flags |= StgInfoFlags::TYPEFLAG_HASPOINTER; + } + if has_bitfield { + stg_info.flags |= StgInfoFlags::TYPEFLAG_HASBITFIELD; + } + stg_info.paramfunc = super::base::ParamFunc::Union; + // Set byte order: swap if _swappedbytes_ is defined + stg_info.big_endian = super::base::is_big_endian(is_swapped); + // Store FFI field types for union passing + stg_info.ffi_field_types = ffi_field_types; + super::base::set_or_init_stginfo(cls, stg_info); + + // Process _anonymous_ fields + super::base::make_anon_fields(cls, vm)?; + Ok(()) } +} - fn get_field_size(field_type: &PyObject, vm: &VirtualMachine) -> PyResult<usize> { - if let Some(size) = field_type - .get_attr("_type_", vm) - .ok() - .and_then(|type_attr| type_attr.str(vm).ok()) - .and_then(|type_str| { - let s = type_str.to_string(); - (s.len() == 1).then(|| get_size(&s)) - }) - { - return Ok(size); +#[pyclass(flags(BASETYPE), with(Constructor, Initializer, SetAttr))] +impl PyCUnionType { + #[pymethod] + fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // zelf is the union type class that from_param was called on + let cls = zelf + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("from_param: expected a type"))?; + + // 1. If already an instance of the requested type, return it + if value.is_instance(cls.as_object(), vm)? { + return Ok(value); } - if let Some(s) = field_type - .get_attr("size_of_instances", vm) - .ok() - .and_then(|size_method| size_method.call((), vm).ok()) - .and_then(|size| size.try_int(vm).ok()) - .and_then(|n| n.as_bigint().to_usize()) - { - return Ok(s); + // 2. Check for CArgObject (PyCArg_CheckExact) + if let Some(carg) = value.downcast_ref::<super::_ctypes::CArgObject>() { + // Check against proto (for pointer types) + if let Some(stg_info) = cls.stg_info_opt() + && let Some(ref proto) = stg_info.proto + && carg.obj.is_instance(proto.as_object(), vm)? + { + return Ok(value); + } + // Fallback: check if the wrapped object is an instance of the requested type + if carg.obj.is_instance(cls.as_object(), vm)? { + return Ok(value); // Return the CArgObject as-is + } + // CArgObject but wrong type + return Err(vm.new_type_error(format!( + "expected {} instance instead of pointer to {}", + cls.name(), + carg.obj.class().name() + ))); } - Ok(std::mem::size_of::<usize>()) + // 3. Check for _as_parameter_ attribute + if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) { + return PyCUnionType::from_param(cls.as_object().to_owned(), as_parameter, vm); + } + + Err(vm.new_type_error(format!( + "expected {} instance instead of {}", + cls.name(), + value.class().name() + ))) + } + + /// Called when a new Union subclass is created + #[pyclassmethod] + fn __init_subclass__(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult<()> { + cls.mark_bases_final(); + + // Check if _fields_ is defined + if let Some(fields_attr) = cls.get_direct_attr(vm.ctx.intern_str("_fields_")) { + Self::process_fields(&cls, fields_attr, vm)?; + } + Ok(()) } } -#[pyclass(flags(BASETYPE), with(Constructor))] -impl PyCUnionType {} +impl SetAttr for PyCUnionType { + fn setattro( + zelf: &Py<Self>, + attr_name: &Py<PyStr>, + value: PySetterValue, + vm: &VirtualMachine, + ) -> PyResult<()> { + let pytype: &Py<PyType> = zelf.to_base(); + let attr_name_interned = vm.ctx.intern_str(attr_name.as_str()); + + // 1. First, do PyType's setattro (PyType_Type.tp_setattro first) + // Check for data descriptor first + if let Some(attr) = pytype.get_class_attr(attr_name_interned) { + let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load()); + if let Some(descriptor) = descr_set { + descriptor(&attr, pytype.to_owned().into(), value.clone(), vm)?; + // After successful setattro, check if _fields_ and call process_fields + if attr_name.as_str() == "_fields_" + && let PySetterValue::Assign(fields_value) = value + { + PyCUnionType::process_fields(pytype, fields_value, vm)?; + } + return Ok(()); + } + } + + // Store in type's attributes dict + match &value { + PySetterValue::Assign(v) => { + pytype + .attributes + .write() + .insert(attr_name_interned, v.clone()); + } + PySetterValue::Delete => { + let prev = pytype.attributes.write().shift_remove(attr_name_interned); + if prev.is_none() { + return Err(vm.new_attribute_error(format!( + "type object '{}' has no attribute '{}'", + pytype.name(), + attr_name.as_str(), + ))); + } + } + } + + // 2. If _fields_, call process_fields (which checks FINAL internally) + if attr_name.as_str() == "_fields_" + && let PySetterValue::Assign(fields_value) = value + { + PyCUnionType::process_fields(pytype, fields_value, vm)?; + } + + Ok(()) + } +} /// PyCUnion - base class for Union #[pyclass(module = "_ctypes", name = "Union", base = PyCData, metaclass = "PyCUnionType")] -pub struct PyCUnion { - _base: PyCData, - /// Common CDataObject for memory buffer - pub(super) cdata: PyRwLock<CDataObject>, -} +#[repr(transparent)] +pub struct PyCUnion(pub PyCData); impl std::fmt::Debug for PyCUnion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PyCUnion") - .field("size", &self.cdata.read().size()) + .field("size", &self.0.size()) .finish() } } @@ -140,47 +430,22 @@ impl Constructor for PyCUnion { type Args = FuncArgs; fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // Get _fields_ from the class - let fields_attr = cls.as_object().get_attr("_fields_", vm).ok(); - - // Calculate union size (max of all field sizes) and alignment - let mut max_size = 0usize; - let mut max_align = 1usize; - - if let Some(fields_attr) = fields_attr { - let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>() - { - list.borrow_vec().to_vec() - } else if let Some(tuple) = fields_attr.downcast_ref::<PyTuple>() { - tuple.to_vec() - } else { - vec![] - }; + // Check for abstract class and extract values in a block to drop the borrow + let (total_size, total_align) = { + let stg_info = cls.stg_info(vm)?; + (stg_info.size, stg_info.align) + }; - for field in fields.iter() { - let Some(field_tuple) = field.downcast_ref::<PyTuple>() else { - continue; - }; - if field_tuple.len() < 2 { - continue; - } - let field_type = field_tuple.get(1).unwrap().clone(); - let size = PyCUnionType::get_field_size(&field_type, vm)?; - max_size = max_size.max(size); - // For simple types, alignment == size - max_align = max_align.max(size); - } + // Mark the class as finalized (instance creation finalizes the type) + if let Some(mut stg_info_mut) = cls.get_type_data_mut::<StgInfo>() { + stg_info_mut.flags |= StgInfoFlags::DICTFLAG_FINAL; } - // Initialize buffer with zeros - let stg_info = StgInfo::new(max_size, max_align); - let cdata = CDataObject::from_stg_info(&stg_info); - PyCUnion { - _base: PyCData::new(cdata.clone()), - cdata: PyRwLock::new(cdata), - } - .into_ref_with_type(vm, cls) - .map(Into::into) + // Initialize buffer with zeros using computed size + let new_stg_info = StgInfo::new(total_size, total_align); + PyCUnion(PyCData::from_stg_info(&new_stg_info)) + .into_ref_with_type(vm, cls) + .map(Into::into) } fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { @@ -188,147 +453,125 @@ impl Constructor for PyCUnion { } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsBuffer))] impl PyCUnion { - #[pygetset] - fn _objects(&self) -> Option<PyObjectRef> { - self.cdata.read().objects.clone() - } - - #[pyclassmethod] - fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { - use crate::stdlib::ctypes::_ctypes::size_of; - - // Get size from cls - let size = size_of(cls.clone().into(), vm)?; - - // Create instance with data from address - if address == 0 || size == 0 { - return Err(vm.new_value_error("NULL pointer access".to_owned())); - } - let stg_info = StgInfo::new(size, 1); - let cdata = CDataObject::from_stg_info(&stg_info); - Ok(PyCUnion { - _base: PyCData::new(cdata.clone()), - cdata: PyRwLock::new(cdata), - } - .into_ref_with_type(vm, cls)? - .into()) - } - - #[pyclassmethod] - fn from_buffer( - cls: PyTypeRef, - source: PyObjectRef, - offset: crate::function::OptionalArg<isize>, + /// Recursively initialize positional arguments through inheritance chain + /// Returns the number of arguments consumed + fn init_pos_args( + self_obj: &Py<Self>, + type_obj: &Py<PyType>, + args: &[PyObjectRef], + kwargs: &indexmap::IndexMap<String, PyObjectRef>, + index: usize, vm: &VirtualMachine, - ) -> PyResult { - use crate::TryFromObject; - use crate::protocol::PyBuffer; - use crate::stdlib::ctypes::_ctypes::size_of; - - let offset = offset.unwrap_or(0); - if offset < 0 { - return Err(vm.new_value_error("offset cannot be negative".to_owned())); - } - let offset = offset as usize; - - let buffer = PyBuffer::try_from_object(vm, source.clone())?; + ) -> PyResult<usize> { + let mut current_index = index; + + // 1. First process base class fields recursively + // Recurse if base has StgInfo + let base_clone = { + let bases = type_obj.bases.read(); + if let Some(base) = bases.first() && + // Check if base has StgInfo + base.stg_info_opt().is_some() + { + Some(base.clone()) + } else { + None + } + }; - if buffer.desc.readonly { - return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + if let Some(ref base) = base_clone { + current_index = Self::init_pos_args(self_obj, base, args, kwargs, current_index, vm)?; } - let size = size_of(cls.clone().into(), vm)?; - let buffer_len = buffer.desc.len; + // 2. Process this class's _fields_ + if let Some(fields_attr) = type_obj.get_direct_attr(vm.ctx.intern_str("_fields_")) { + let fields: Vec<PyObjectRef> = fields_attr.try_to_value(vm)?; - if offset + size > buffer_len { - return Err(vm.new_value_error(format!( - "Buffer size too small ({} instead of at least {} bytes)", - buffer_len, - offset + size - ))); + for field in fields.iter() { + if current_index >= args.len() { + break; + } + if let Some(tuple) = field.downcast_ref::<PyTuple>() + && let Some(name) = tuple.first() + && let Some(name_str) = name.downcast_ref::<PyStr>() + { + let field_name = name_str.as_str().to_owned(); + // Check for duplicate in kwargs + if kwargs.contains_key(&field_name) { + return Err(vm.new_type_error(format!( + "duplicate values for field {:?}", + field_name + ))); + } + self_obj.as_object().set_attr( + vm.ctx.intern_str(field_name), + args[current_index].clone(), + vm, + )?; + current_index += 1; + } + } } - // Copy data from source buffer - let bytes = buffer.obj_bytes(); - let data = bytes[offset..offset + size].to_vec(); - - let cdata = CDataObject::from_bytes(data, None); - Ok(PyCUnion { - _base: PyCData::new(cdata.clone()), - cdata: PyRwLock::new(cdata), - } - .into_ref_with_type(vm, cls)? - .into()) + Ok(current_index) } +} - #[pyclassmethod] - fn from_buffer_copy( - cls: PyTypeRef, - source: crate::function::ArgBytesLike, - offset: crate::function::OptionalArg<isize>, - vm: &VirtualMachine, - ) -> PyResult { - use crate::stdlib::ctypes::_ctypes::size_of; +impl Initializer for PyCUnion { + type Args = FuncArgs; - let offset = offset.unwrap_or(0); - if offset < 0 { - return Err(vm.new_value_error("offset cannot be negative".to_owned())); - } - let offset = offset as usize; + fn init(zelf: crate::PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + // Struct_init: handle positional and keyword arguments + let cls = zelf.class().to_owned(); - let size = size_of(cls.clone().into(), vm)?; - let source_bytes = source.borrow_buf(); - let buffer_len = source_bytes.len(); + // 1. Process positional arguments recursively through inheritance chain + if !args.args.is_empty() { + let consumed = PyCUnion::init_pos_args(&zelf, &cls, &args.args, &args.kwargs, 0, vm)?; - if offset + size > buffer_len { - return Err(vm.new_value_error(format!( - "Buffer size too small ({} instead of at least {} bytes)", - buffer_len, - offset + size - ))); + if consumed < args.args.len() { + return Err(vm.new_type_error("too many initializers")); + } } - // Copy data from source - let data = source_bytes[offset..offset + size].to_vec(); - - let cdata = CDataObject::from_bytes(data, None); - Ok(PyCUnion { - _base: PyCData::new(cdata.clone()), - cdata: PyRwLock::new(cdata), + // 2. Process keyword arguments + for (key, value) in args.kwargs.iter() { + zelf.as_object() + .set_attr(vm.ctx.intern_str(key.as_str()), value.clone(), vm)?; } - .into_ref_with_type(vm, cls)? - .into()) + + Ok(()) } } -static UNION_BUFFER_METHODS: BufferMethods = BufferMethods { - obj_bytes: |buffer| { - rustpython_common::lock::PyRwLockReadGuard::map( - buffer.obj_as::<PyCUnion>().cdata.read(), - |x: &CDataObject| x.buffer.as_slice(), - ) - .into() - }, - obj_bytes_mut: |buffer| { - rustpython_common::lock::PyRwLockWriteGuard::map( - buffer.obj_as::<PyCUnion>().cdata.write(), - |x: &mut CDataObject| x.buffer.as_mut_slice(), - ) - .into() - }, - release: |_| {}, - retain: |_| {}, -}; +#[pyclass( + flags(BASETYPE, IMMUTABLETYPE), + with(Constructor, Initializer, AsBuffer) +)] +impl PyCUnion {} impl AsBuffer for PyCUnion { - fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<ProtocolPyBuffer> { - let buffer_len = zelf.cdata.read().buffer.len(); - let buf = ProtocolPyBuffer::new( + fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { + let buffer_len = zelf.0.buffer.read().len(); + + // PyCData_NewGetBuffer: use info->format if available, otherwise "B" + let format = zelf + .class() + .stg_info_opt() + .and_then(|info| info.format.clone()) + .unwrap_or_else(|| "B".to_string()); + + // Union: ndim=0, shape=(), itemsize=union_size + let buf = PyBuffer::new( zelf.to_owned().into(), - BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes - &UNION_BUFFER_METHODS, + BufferDescriptor { + len: buffer_len, + readonly: false, + itemsize: buffer_len, + format: Cow::Owned(format), + dim_desc: vec![], // ndim=0 means empty dim_desc + }, + &CDATA_BUFFER_METHODS, ); Ok(buf) } diff --git a/crates/vm/src/stdlib/ctypes/util.rs b/crates/vm/src/stdlib/ctypes/util.rs deleted file mode 100644 index b8c6def63ca..00000000000 --- a/crates/vm/src/stdlib/ctypes/util.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::PyObjectRef; - -/// Storage information for ctypes types -/// Stored in TypeDataSlot of heap types (PyType::init_type_data/get_type_data) -#[derive(Clone)] -pub struct StgInfo { - pub initialized: bool, - pub size: usize, // number of bytes - pub align: usize, // alignment requirements - pub length: usize, // number of fields (for arrays/structures) - pub proto: Option<PyObjectRef>, // Only for Pointer/ArrayObject - pub flags: i32, // calling convention and such - - // Array-specific fields (moved from PyCArrayType) - pub element_type: Option<PyObjectRef>, // _type_ for arrays - pub element_size: usize, // size of each element -} - -// StgInfo is stored in type_data which requires Send + Sync. -// The PyObjectRef in proto/element_type fields is protected by the type system's locking mechanism. -// CPython: ctypes objects are not thread-safe by design; users must synchronize access. -unsafe impl Send for StgInfo {} -unsafe impl Sync for StgInfo {} - -impl std::fmt::Debug for StgInfo { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("StgInfo") - .field("initialized", &self.initialized) - .field("size", &self.size) - .field("align", &self.align) - .field("length", &self.length) - .field("proto", &self.proto) - .field("flags", &self.flags) - .field("element_type", &self.element_type) - .field("element_size", &self.element_size) - .finish() - } -} - -impl Default for StgInfo { - fn default() -> Self { - StgInfo { - initialized: false, - size: 0, - align: 1, - length: 0, - proto: None, - flags: 0, - element_type: None, - element_size: 0, - } - } -} - -impl StgInfo { - pub fn new(size: usize, align: usize) -> Self { - StgInfo { - initialized: true, - size, - align, - length: 0, - proto: None, - flags: 0, - element_type: None, - element_size: 0, - } - } - - /// Create StgInfo for an array type - pub fn new_array( - size: usize, - align: usize, - length: usize, - element_type: PyObjectRef, - element_size: usize, - ) -> Self { - StgInfo { - initialized: true, - size, - align, - length, - proto: None, - flags: 0, - element_type: Some(element_type), - element_size, - } - } -} diff --git a/crates/vm/src/stdlib/functools.rs b/crates/vm/src/stdlib/functools.rs index d5a42739e96..26dff8b4426 100644 --- a/crates/vm/src/stdlib/functools.rs +++ b/crates/vm/src/stdlib/functools.rs @@ -73,8 +73,8 @@ mod _functools { self.inner.read().keywords.clone() } - #[pymethod(name = "__reduce__")] - fn reduce(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn __reduce__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult { let inner = zelf.inner.read(); let partial_type = zelf.class(); diff --git a/crates/vm/src/stdlib/operator.rs b/crates/vm/src/stdlib/operator.rs index 0c048ea2a3f..7877ddb0114 100644 --- a/crates/vm/src/stdlib/operator.rs +++ b/crates/vm/src/stdlib/operator.rs @@ -323,7 +323,7 @@ mod _operator { ) -> PyResult<bool> { let res = match (a, b) { (Either::A(a), Either::A(b)) => { - if !a.is_ascii() || !b.is_ascii() { + if !a.isascii() || !b.isascii() { return Err(vm.new_type_error( "comparing strings with non-ASCII characters is not supported", )); diff --git a/crates/vm/src/types/structseq.rs b/crates/vm/src/types/structseq.rs index be0a1c9a70c..2b6a2530b02 100644 --- a/crates/vm/src/types/structseq.rs +++ b/crates/vm/src/types/structseq.rs @@ -199,7 +199,7 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { .ok_or_else(|| vm.new_type_error("unexpected payload for __repr__"))?; let field_names = Self::Data::REQUIRED_FIELD_NAMES; - let format_field = |(value, name): (&PyObjectRef, _)| { + let format_field = |(value, name): (&PyObject, _)| { let s = value.repr(vm)?; Ok(format!("{name}={s}")) }; @@ -212,6 +212,7 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { } else { let fields: PyResult<Vec<_>> = zelf .iter() + .map(|value| value.as_ref()) .zip(field_names.iter().copied()) .map(format_field) .collect(); From 6c186e389381cb1b9d99e74e085a9c53ddd24624 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:51:09 +0100 Subject: [PATCH 527/819] Update `smptlib` and `test_smtpnet.py` from 3.13.11 (#6435) * Update `test_smtpnet.py` from 3.13.11 * Update `test_smtplib.py` from 3.13.11 * Update `smtplib.py` from 3.13.11 * Catch AttributeError --- Lib/smtplib.py | 43 ++++++++++++++------- Lib/test/test_smtplib.py | 83 +++++++++++++++++++++++++++++++--------- Lib/test/test_smtpnet.py | 9 +++-- 3 files changed, 100 insertions(+), 35 deletions(-) mode change 100644 => 100755 Lib/smtplib.py diff --git a/Lib/smtplib.py b/Lib/smtplib.py old mode 100644 new mode 100755 index 912233d8176..9b81bcfbc41 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -171,7 +171,7 @@ def quotedata(data): internet CRLF end-of-line. """ return re.sub(r'(?m)^\.', '..', - re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) + re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) def _quote_periods(bindata): return re.sub(br'(?m)^\.', b'..', bindata) @@ -179,6 +179,16 @@ def _quote_periods(bindata): def _fix_eols(data): return re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data) + +try: + hmac.digest(b'', b'', 'md5') +# except ValueError: +except (ValueError, AttributeError): # TODO: RUSTPYTHON + _have_cram_md5_support = False +else: + _have_cram_md5_support = True + + try: import ssl except ImportError: @@ -475,7 +485,7 @@ def ehlo(self, name=''): if auth_match: # This doesn't remove duplicates, but that's no problem self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \ - + " " + auth_match.groups(0)[0] + + " " + auth_match.groups(0)[0] continue # RFC 1869 requires a space between ehlo keyword and parameters. @@ -488,7 +498,7 @@ def ehlo(self, name=''): params = m.string[m.end("feature"):].strip() if feature == "auth": self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \ - + " " + params + + " " + params else: self.esmtp_features[feature] = params return (code, msg) @@ -542,7 +552,7 @@ def mail(self, sender, options=()): raise SMTPNotSupportedError( 'SMTPUTF8 not supported by server') optionlist = ' ' + ' '.join(options) - self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist)) + self.putcmd("mail", "from:%s%s" % (quoteaddr(sender), optionlist)) return self.getreply() def rcpt(self, recip, options=()): @@ -550,7 +560,7 @@ def rcpt(self, recip, options=()): optionlist = '' if options and self.does_esmtp: optionlist = ' ' + ' '.join(options) - self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist)) + self.putcmd("rcpt", "to:%s%s" % (quoteaddr(recip), optionlist)) return self.getreply() def data(self, msg): @@ -667,8 +677,11 @@ def auth_cram_md5(self, challenge=None): # CRAM-MD5 does not support initial-response. if challenge is None: return None - return self.user + " " + hmac.HMAC( - self.password.encode('ascii'), challenge, 'md5').hexdigest() + if not _have_cram_md5_support: + raise SMTPException("CRAM-MD5 is not supported") + password = self.password.encode('ascii') + authcode = hmac.HMAC(password, challenge, 'md5') + return f"{self.user} {authcode.hexdigest()}" def auth_plain(self, challenge=None): """ Authobject to use with PLAIN authentication. Requires self.user and @@ -720,8 +733,10 @@ def login(self, user, password, *, initial_response_ok=True): advertised_authlist = self.esmtp_features["auth"].split() # Authentication methods we can handle in our preferred order: - preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN'] - + if _have_cram_md5_support: + preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN'] + else: + preferred_auths = ['PLAIN', 'LOGIN'] # We try the supported authentications in our preferred order, if # the server supports them. authlist = [auth for auth in preferred_auths @@ -905,7 +920,7 @@ def send_message(self, msg, from_addr=None, to_addrs=None, The arguments are as for sendmail, except that msg is an email.message.Message object. If from_addr is None or to_addrs is None, these arguments are taken from the headers of the Message as - described in RFC 2822 (a ValueError is raised if there is more than + described in RFC 5322 (a ValueError is raised if there is more than one set of 'Resent-' headers). Regardless of the values of from_addr and to_addr, any Bcc field (or Resent-Bcc field, when the Message is a resent) of the Message object won't be transmitted. The Message @@ -919,7 +934,7 @@ def send_message(self, msg, from_addr=None, to_addrs=None, policy. """ - # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822 + # 'Resent-Date' is a mandatory field if the Message is resent (RFC 5322 # Section 3.6.6). In such a case, we use the 'Resent-*' fields. However, # if there is more than one 'Resent-' block there's no way to # unambiguously determine which one is the most recent in all cases, @@ -938,10 +953,10 @@ def send_message(self, msg, from_addr=None, to_addrs=None, else: raise ValueError("message has more than one 'Resent-' header block") if from_addr is None: - # Prefer the sender field per RFC 2822:3.6.2. + # Prefer the sender field per RFC 5322 section 3.6.2. from_addr = (msg[header_prefix + 'Sender'] - if (header_prefix + 'Sender') in msg - else msg[header_prefix + 'From']) + if (header_prefix + 'Sender') in msg + else msg[header_prefix + 'From']) from_addr = email.utils.getaddresses([from_addr])[0][1] if to_addrs is None: addr_fields = [f for f in (msg[header_prefix + 'To'], diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py index 9b787950fc2..ade0dc1308c 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -17,6 +17,7 @@ import threading import unittest +import unittest.mock as mock from test import support, mock_socket from test.support import hashlib_helper from test.support import socket_helper @@ -350,7 +351,7 @@ def testVRFY(self): timeout=support.LOOPBACK_TIMEOUT) self.addCleanup(smtp.close) expected = (252, b'Cannot VRFY user, but will accept message ' + \ - b'and attempt delivery') + b'and attempt delivery') self.assertEqual(smtp.vrfy('nobody@nowhere.com'), expected) self.assertEqual(smtp.verify('nobody@nowhere.com'), expected) smtp.quit() @@ -371,7 +372,7 @@ def testHELP(self): timeout=support.LOOPBACK_TIMEOUT) self.addCleanup(smtp.close) self.assertEqual(smtp.help(), b'Supported commands: EHLO HELO MAIL ' + \ - b'RCPT DATA RSET NOOP QUIT VRFY') + b'RCPT DATA RSET NOOP QUIT VRFY') smtp.quit() def testSend(self): @@ -527,7 +528,7 @@ def testSendMessageWithAddresses(self): smtp.quit() # make sure the Bcc header is still in the message. self.assertEqual(m['Bcc'], 'John Root <root@localhost>, "Dinsdale" ' - '<warped@silly.walks.com>') + '<warped@silly.walks.com>') self.client_evt.set() self.serv_evt.wait() @@ -766,7 +767,7 @@ def tearDown(self): def testFailingHELO(self): self.assertRaises(smtplib.SMTPConnectError, smtplib.SMTP, - HOST, self.port, 'localhost', 3) + HOST, self.port, 'localhost', 3) class TooLongLineTests(unittest.TestCase): @@ -804,14 +805,14 @@ def testLineTooLong(self): sim_users = {'Mr.A@somewhere.com':'John A', 'Ms.B@xn--fo-fka.com':'Sally B', 'Mrs.C@somewhereesle.com':'Ruth C', - } + } sim_auth = ('Mr.A@somewhere.com', 'somepassword') sim_cram_md5_challenge = ('PENCeUxFREJoU0NnbmhNWitOMjNGNn' 'dAZWx3b29kLmlubm9zb2Z0LmNvbT4=') sim_lists = {'list-1':['Mr.A@somewhere.com','Mrs.C@somewhereesle.com'], 'list-2':['Ms.B@xn--fo-fka.com',], - } + } # Simulated SMTP channel & server class ResponseException(Exception): pass @@ -830,6 +831,7 @@ class SimSMTPChannel(smtpd.SMTPChannel): def __init__(self, extra_features, *args, **kw): self._extrafeatures = ''.join( [ "250-{0}\r\n".format(x) for x in extra_features ]) + self.all_received_lines = [] super(SimSMTPChannel, self).__init__(*args, **kw) # AUTH related stuff. It would be nice if support for this were in smtpd. @@ -844,6 +846,7 @@ def found_terminator(self): self.smtp_state = self.COMMAND self.push('%s %s' % (e.smtp_code, e.smtp_error)) return + self.all_received_lines.append(self.received_lines) super().found_terminator() @@ -924,11 +927,14 @@ def _auth_cram_md5(self, arg=None): except ValueError as e: self.push('535 Splitting response {!r} into user and password ' 'failed: {}'.format(logpass, e)) - return False - valid_hashed_pass = hmac.HMAC( - sim_auth[1].encode('ascii'), - self._decode_base64(sim_cram_md5_challenge).encode('ascii'), - 'md5').hexdigest() + return + pwd = sim_auth[1].encode('ascii') + msg = self._decode_base64(sim_cram_md5_challenge).encode('ascii') + try: + valid_hashed_pass = hmac.HMAC(pwd, msg, 'md5').hexdigest() + except ValueError: + self.push('504 CRAM-MD5 is not supported') + return self._authenticated(user, hashed_pass == valid_hashed_pass) # end AUTH related stuff. @@ -1170,8 +1176,7 @@ def auth_buggy(challenge=None): finally: smtp.close() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @hashlib_helper.requires_hashdigest('md5', openssl=True) def testAUTH_CRAM_MD5(self): self.serv.add_feature("AUTH CRAM-MD5") @@ -1181,8 +1186,39 @@ def testAUTH_CRAM_MD5(self): self.assertEqual(resp, (235, b'Authentication Succeeded')) smtp.close() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @mock.patch("hmac.HMAC") + @mock.patch("smtplib._have_cram_md5_support", False) + def testAUTH_CRAM_MD5_blocked(self, hmac_constructor): + # CRAM-MD5 is the only "known" method by the server, + # but it is not supported by the client. In particular, + # no challenge will ever be sent. + self.serv.add_feature("AUTH CRAM-MD5") + smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', + timeout=support.LOOPBACK_TIMEOUT) + self.addCleanup(smtp.close) + msg = re.escape("No suitable authentication method found.") + with self.assertRaisesRegex(smtplib.SMTPException, msg): + smtp.login(sim_auth[0], sim_auth[1]) + hmac_constructor.assert_not_called() # call has been bypassed + + @mock.patch("smtplib._have_cram_md5_support", False) + def testAUTH_CRAM_MD5_blocked_and_fallback(self): + # Test that PLAIN is tried after CRAM-MD5 failed + self.serv.add_feature("AUTH CRAM-MD5 PLAIN") + smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', + timeout=support.LOOPBACK_TIMEOUT) + self.addCleanup(smtp.close) + with ( + mock.patch.object(smtp, "auth_cram_md5") as smtp_auth_cram_md5, + mock.patch.object( + smtp, "auth_plain", wraps=smtp.auth_plain + ) as smtp_auth_plain + ): + resp = smtp.login(sim_auth[0], sim_auth[1]) + smtp_auth_plain.assert_called_once() + smtp_auth_cram_md5.assert_not_called() # no call to HMAC constructor + self.assertEqual(resp, (235, b'Authentication Succeeded')) + @hashlib_helper.requires_hashdigest('md5', openssl=True) def testAUTH_multiple(self): # Test that multiple authentication methods are tried. @@ -1193,8 +1229,7 @@ def testAUTH_multiple(self): self.assertEqual(resp, (235, b'Authentication Succeeded')) smtp.close() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_auth_function(self): supported = {'PLAIN', 'LOGIN'} try: @@ -1354,6 +1389,18 @@ def test_name_field_not_included_in_envelop_addresses(self): self.assertEqual(self.serv._addresses['from'], 'michael@example.com') self.assertEqual(self.serv._addresses['tos'], ['rene@example.com']) + def test_lowercase_mail_from_rcpt_to(self): + m = 'A test message' + smtp = smtplib.SMTP( + HOST, self.port, local_hostname='localhost', + timeout=support.LOOPBACK_TIMEOUT) + self.addCleanup(smtp.close) + + smtp.sendmail('John', 'Sally', m) + + self.assertIn(['mail from:<John> size=14'], self.serv._SMTPchannel.all_received_lines) + self.assertIn(['rcpt to:<Sally>'], self.serv._SMTPchannel.all_received_lines) + class SimSMTPUTF8Server(SimSMTPServer): @@ -1372,7 +1419,7 @@ def handle_accepted(self, conn, addr): ) def process_message(self, peer, mailfrom, rcpttos, data, mail_options=None, - rcpt_options=None): + rcpt_options=None): self.last_peer = peer self.last_mailfrom = mailfrom self.last_rcpttos = rcpttos diff --git a/Lib/test/test_smtpnet.py b/Lib/test/test_smtpnet.py index be25e961f74..d765746987b 100644 --- a/Lib/test/test_smtpnet.py +++ b/Lib/test/test_smtpnet.py @@ -2,6 +2,7 @@ from test import support from test.support import import_helper from test.support import socket_helper +import os import smtplib import socket @@ -9,6 +10,8 @@ support.requires("network") +SMTP_TEST_SERVER = os.getenv('CPYTHON_TEST_SMTP_SERVER', 'smtp.gmail.com') + def check_ssl_verifiy(host, port): context = ssl.create_default_context() with socket.create_connection((host, port)) as sock: @@ -22,7 +25,7 @@ def check_ssl_verifiy(host, port): class SmtpTest(unittest.TestCase): - testServer = 'smtp.gmail.com' + testServer = SMTP_TEST_SERVER remotePort = 587 def test_connect_starttls(self): @@ -44,7 +47,7 @@ def test_connect_starttls(self): class SmtpSSLTest(unittest.TestCase): - testServer = 'smtp.gmail.com' + testServer = SMTP_TEST_SERVER remotePort = 465 def test_connect(self): @@ -87,4 +90,4 @@ def test_connect_using_sslcontext_verified(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() From ab1105a61debb068b104ebc03e7d37b8e7b1edad Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 20 Dec 2025 09:55:28 +0900 Subject: [PATCH 528/819] Fix fix_test.py (#6415) --- scripts/fix_test.py | 165 ++++++++++++++++++++++++++------------------ 1 file changed, 96 insertions(+), 69 deletions(-) diff --git a/scripts/fix_test.py b/scripts/fix_test.py index a5663e3eee3..9716bd0b008 100644 --- a/scripts/fix_test.py +++ b/scripts/fix_test.py @@ -5,16 +5,20 @@ How to use: 1. Copy a specific test from the CPython repository to the RustPython repository. -2. Remove all unexpected failures from the test and skip the tests that hang -3. Run python ./scripts/fix_test.py --test test_venv --path ./Lib/test/test_venv.py or equivalent for the test from the project root. -4. Ensure that there are no unexpected successes in the test. -5. Actually fix the test. +2. Remove all unexpected failures from the test and skip the tests that hang. +3. Build RustPython: cargo build --release +4. Run from the project root: + - For single-file tests: python ./scripts/fix_test.py --path ./Lib/test/test_venv.py + - For package tests: python ./scripts/fix_test.py --path ./Lib/test/test_inspect/test_inspect.py +5. Verify: cargo run --release -- -m test test_venv (should pass with expected failures) +6. Actually fix the tests marked with # TODO: RUSTPYTHON """ import argparse import ast import itertools import platform +import sys from pathlib import Path @@ -58,85 +62,87 @@ def parse_results(result): in_test_results = True elif line.startswith("-----------"): in_test_results = False - if ( - in_test_results - and not line.startswith("tests") - and not line.startswith("[") - ): - line = line.split(" ") - if line != [] and len(line) > 3: - test = Test() - test.name = line[0] - test.path = line[1].strip("(").strip(")") - test.result = " ".join(line[3:]).lower() - test_results.tests.append(test) - else: - if "== Tests result: " in line: - res = line.split("== Tests result: ")[1] - res = res.split(" ")[0] - test_results.tests_result = res + if in_test_results and " ... " in line: + line = line.strip() + # Skip lines that don't look like test results + if line.startswith("tests") or line.startswith("["): + continue + # Parse: "test_name (path) [subtest] ... RESULT" + parts = line.split(" ... ") + if len(parts) >= 2: + test_info = parts[0] + result_str = parts[-1].lower() + # Only process FAIL or ERROR + if result_str not in ("fail", "error"): + continue + # Extract test name (first word) + first_space = test_info.find(" ") + if first_space > 0: + test = Test() + test.name = test_info[:first_space] + # Extract path from (path) + rest = test_info[first_space:].strip() + if rest.startswith("("): + end_paren = rest.find(")") + if end_paren > 0: + test.path = rest[1:end_paren] + test.result = result_str + test_results.tests.append(test) + elif "== Tests result: " in line: + res = line.split("== Tests result: ")[1] + res = res.split(" ")[0] + test_results.tests_result = res return test_results def path_to_test(path) -> list[str]: - return path.split(".")[2:] + # path format: test.module_name[.submodule].ClassName.test_method + # We need [ClassName, test_method] - always the last 2 elements + parts = path.split(".") + return parts[-2:] # Get class name and method name -def modify_test(file: str, test: list[str], for_platform: bool = False) -> str: +def find_test_lineno(file: str, test: list[str]) -> tuple[int, int] | None: + """Find the line number and column offset of a test function. + Returns (lineno, col_offset) or None if not found. + """ a = ast.parse(file) - lines = file.splitlines() - fixture = "@unittest.expectedFailure" - for node in ast.walk(a): - if isinstance(node, ast.FunctionDef): - if node.name == test[-1]: - assert not for_platform - indent = " " * node.col_offset - lines.insert(node.lineno - 1, indent + fixture) - lines.insert(node.lineno - 1, indent + "# TODO: RUSTPYTHON") - break - return "\n".join(lines) - - -def modify_test_v2(file: str, test: list[str], for_platform: bool = False) -> str: - a = ast.parse(file) - lines = file.splitlines() - fixture = "@unittest.expectedFailure" for key, node in ast.iter_fields(a): if key == "body": - for i, n in enumerate(node): + for n in node: match n: case ast.ClassDef(): if len(test) == 2 and test[0] == n.name: - # look through body for function def - for i, fn in enumerate(n.body): + for fn in n.body: match fn: - case ast.FunctionDef(): + case ast.FunctionDef() | ast.AsyncFunctionDef(): if fn.name == test[-1]: - assert not for_platform - indent = " " * fn.col_offset - lines.insert( - fn.lineno - 1, indent + fixture - ) - lines.insert( - fn.lineno - 1, - indent + "# TODO: RUSTPYTHON", - ) - break - case ast.FunctionDef(): + return (fn.lineno, fn.col_offset) + case ast.FunctionDef() | ast.AsyncFunctionDef(): if n.name == test[0] and len(test) == 1: - assert not for_platform - indent = " " * n.col_offset - lines.insert(n.lineno - 1, indent + fixture) - lines.insert(n.lineno - 1, indent + "# TODO: RUSTPYTHON") - break - if i > 500: - exit() + return (n.lineno, n.col_offset) + return None + + +def apply_modifications(file: str, modifications: list[tuple[int, int]]) -> str: + """Apply all modifications in reverse order to avoid line number offset issues.""" + lines = file.splitlines() + fixture = "@unittest.expectedFailure" + # Sort by line number in descending order + modifications.sort(key=lambda x: x[0], reverse=True) + for lineno, col_offset in modifications: + indent = " " * col_offset + lines.insert(lineno - 1, indent + fixture) + lines.insert(lineno - 1, indent + "# TODO: RUSTPYTHON") return "\n".join(lines) def run_test(test_name): print(f"Running test: {test_name}") rustpython_location = "./target/release/rustpython" + if sys.platform == "win32": + rustpython_location += ".exe" + import subprocess result = subprocess.run( @@ -149,13 +155,34 @@ def run_test(test_name): if __name__ == "__main__": args = parse_args() - test_name = args.path.stem + test_path = args.path.resolve() + if not test_path.exists(): + print(f"Error: File not found: {test_path}") + sys.exit(1) + test_name = test_path.stem tests = run_test(test_name) - f = open(args.path).read() + f = test_path.read_text(encoding="utf-8") + + # Collect all modifications first (with deduplication for subtests) + modifications = [] + seen_tests = set() # Track (class_name, method_name) to avoid duplicates for test in tests.tests: if test.result == "fail" or test.result == "error": - print("Modifying test:", test.name) - f = modify_test_v2(f, path_to_test(test.path), args.platform) - with open(args.path, "w") as file: - # TODO: Find validation method, and make --force override it - file.write(f) + test_parts = path_to_test(test.path) + test_key = tuple(test_parts) + if test_key in seen_tests: + continue # Skip duplicate (same test, different subtest) + seen_tests.add(test_key) + location = find_test_lineno(f, test_parts) + if location: + print(f"Modifying test: {test.name} at line {location[0]}") + modifications.append(location) + else: + print(f"Warning: Could not find test: {test.name} ({test_parts})") + + # Apply all modifications in reverse order + if modifications: + f = apply_modifications(f, modifications) + test_path.write_text(f, encoding="utf-8") + + print(f"Modified {len(modifications)} tests") From 46c61a65a6773de5583860e90857e619bed4aaf6 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 20 Dec 2025 12:17:11 +0900 Subject: [PATCH 529/819] Upgrade venv to v3.13.11 (#6459) * Upgrade venv * get_venv_base_executable * mark failing tests --- Lib/test/test_venv.py | 615 ++++++++++++++++-- Lib/venv/__init__.py | 607 +++++++++++------ Lib/venv/__main__.py | 2 +- Lib/venv/scripts/common/Activate.ps1 | 3 +- Lib/venv/scripts/common/activate | 43 +- .../scripts/{posix => common}/activate.fish | 19 +- Lib/venv/scripts/nt/activate.bat | 10 +- Lib/venv/scripts/posix/activate.csh | 9 +- crates/vm/src/stdlib/sys.rs | 65 +- 9 files changed, 1065 insertions(+), 308 deletions(-) rename Lib/venv/scripts/{posix => common}/activate.fish (76%) diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 94d626598ba..19b12070531 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -5,21 +5,31 @@ Licensed to the PSF under a contributor agreement. """ +import contextlib import ensurepip import os import os.path +import pathlib import re import shutil import struct import subprocess import sys +import sysconfig import tempfile -from test.support import (captured_stdout, captured_stderr, requires_zlib, - skip_if_broken_multiprocessing_synchronize) -from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree) +import shlex +from test.support import (captured_stdout, captured_stderr, + skip_if_broken_multiprocessing_synchronize, verbose, + requires_subprocess, is_android, is_apple_mobile, + is_emscripten, is_wasi, + requires_venv_with_pip, TEST_HOME_DIR, + requires_resource, copy_python_src_ignore) +from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree, + TESTFN, FakePath) +from test.support.testcase import ExtraAssertions import unittest import venv -from unittest.mock import patch +from unittest.mock import patch, Mock try: import ctypes @@ -33,18 +43,29 @@ or sys._base_executable != sys.executable, 'cannot run venv.create from within a venv on this platform') +if is_android or is_apple_mobile or is_emscripten or is_wasi: + raise unittest.SkipTest("venv is not available on this platform") + +@requires_subprocess() def check_output(cmd, encoding=None): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - encoding=encoding) + env={**os.environ, "PYTHONHOME": ""}) out, err = p.communicate() if p.returncode: + if verbose and err: + print(err.decode(encoding or 'utf-8', 'backslashreplace')) raise subprocess.CalledProcessError( p.returncode, cmd, out, err) + if encoding: + return ( + out.decode(encoding, 'backslashreplace'), + err.decode(encoding, 'backslashreplace'), + ) return out, err -class BaseTest(unittest.TestCase): +class BaseTest(unittest.TestCase, ExtraAssertions): """Base class for venv tests.""" maxDiff = 80 * 50 @@ -56,7 +77,7 @@ def setUp(self): self.include = 'Include' else: self.bindir = 'bin' - self.lib = ('lib', 'python%d.%d' % sys.version_info[:2]) + self.lib = ('lib', f'python{sysconfig._get_python_version_abi()}') self.include = 'include' executable = sys._base_executable self.exe = os.path.split(executable)[-1] @@ -70,6 +91,13 @@ def setUp(self): def tearDown(self): rmtree(self.env_dir) + def envpy(self, *, real_env_dir=False): + if real_env_dir: + env_dir = os.path.realpath(self.env_dir) + else: + env_dir = self.env_dir + return os.path.join(env_dir, self.bindir, self.exe) + def run_with_capture(self, func, *args, **kwargs): with captured_stdout() as output: with captured_stderr() as error: @@ -91,12 +119,27 @@ def isdir(self, *args): fn = self.get_env_file(*args) self.assertTrue(os.path.isdir(fn)) - def test_defaults(self): + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_defaults_with_str_path(self): """ - Test the create function with default arguments. + Test the create function with default arguments and a str path. """ rmtree(self.env_dir) self.run_with_capture(venv.create, self.env_dir) + self._check_output_of_default_create() + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_defaults_with_pathlike(self): + """ + Test the create function with default arguments and a path-like path. + """ + rmtree(self.env_dir) + self.run_with_capture(venv.create, FakePath(self.env_dir)) + self._check_output_of_default_create() + + def _check_output_of_default_create(self): self.isdir(self.bindir) self.isdir(self.include) self.isdir(*self.lib) @@ -112,6 +155,12 @@ def test_defaults(self): executable = sys._base_executable path = os.path.dirname(executable) self.assertIn('home = %s' % path, data) + self.assertIn('executable = %s' % + os.path.realpath(sys.executable), data) + copies = '' if os.name=='nt' else ' --copies' + cmd = (f'command = {sys.executable} -m venv{copies} --without-pip ' + f'--without-scm-ignore-files {self.env_dir}') + self.assertIn(cmd, data) fn = self.get_env_file(self.bindir, self.exe) if not os.path.exists(fn): # diagnostics for Windows buildbot failures bd = self.get_env_file(self.bindir) @@ -119,6 +168,39 @@ def test_defaults(self): print(' %r' % os.listdir(bd)) self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn) + def test_config_file_command_key(self): + options = [ + (None, None, None), # Default case. + ('--copies', 'symlinks', False), + ('--without-pip', 'with_pip', False), + ('--system-site-packages', 'system_site_packages', True), + ('--clear', 'clear', True), + ('--upgrade', 'upgrade', True), + ('--upgrade-deps', 'upgrade_deps', True), + ('--prompt="foobar"', 'prompt', 'foobar'), + ('--without-scm-ignore-files', 'scm_ignore_files', frozenset()), + ] + for opt, attr, value in options: + with self.subTest(opt=opt, attr=attr, value=value): + rmtree(self.env_dir) + if not attr: + kwargs = {} + else: + kwargs = {attr: value} + b = venv.EnvBuilder(**kwargs) + b.upgrade_dependencies = Mock() # avoid pip command to upgrade deps + b._setup_pip = Mock() # avoid pip setup + self.run_with_capture(b.create, self.env_dir) + data = self.get_text_file_contents('pyvenv.cfg') + if not attr or opt.endswith('git'): + for opt in ('--system-site-packages', '--clear', '--upgrade', + '--upgrade-deps', '--prompt'): + self.assertNotRegex(data, rf'command = .* {opt}') + elif os.name=='nt' and attr=='symlinks': + pass + else: + self.assertRegex(data, rf'command = .* {opt}') + def test_prompt(self): env_name = os.path.split(self.env_dir)[1] @@ -127,7 +209,7 @@ def test_prompt(self): self.run_with_capture(builder.create, self.env_dir) context = builder.ensure_directories(self.env_dir) data = self.get_text_file_contents('pyvenv.cfg') - self.assertEqual(context.prompt, '(%s) ' % env_name) + self.assertEqual(context.prompt, env_name) self.assertNotIn("prompt = ", data) rmtree(self.env_dir) @@ -135,7 +217,7 @@ def test_prompt(self): self.run_with_capture(builder.create, self.env_dir) context = builder.ensure_directories(self.env_dir) data = self.get_text_file_contents('pyvenv.cfg') - self.assertEqual(context.prompt, '(My prompt) ') + self.assertEqual(context.prompt, 'My prompt') self.assertIn("prompt = 'My prompt'\n", data) rmtree(self.env_dir) @@ -144,13 +226,19 @@ def test_prompt(self): self.run_with_capture(builder.create, self.env_dir) context = builder.ensure_directories(self.env_dir) data = self.get_text_file_contents('pyvenv.cfg') - self.assertEqual(context.prompt, '(%s) ' % cwd) + self.assertEqual(context.prompt, cwd) self.assertIn("prompt = '%s'\n" % cwd, data) def test_upgrade_dependencies(self): builder = venv.EnvBuilder() - bin_path = 'Scripts' if sys.platform == 'win32' else 'bin' + bin_path = 'bin' python_exe = os.path.split(sys.executable)[1] + if sys.platform == 'win32': + bin_path = 'Scripts' + if os.path.normcase(os.path.splitext(python_exe)[0]).endswith('_d'): + python_exe = 'python_d.exe' + else: + python_exe = 'python.exe' with tempfile.TemporaryDirectory() as fake_env_dir: expect_exe = os.path.normcase( os.path.join(fake_env_dir, bin_path, python_exe) @@ -158,7 +246,7 @@ def test_upgrade_dependencies(self): if sys.platform == 'win32': expect_exe = os.path.normcase(os.path.realpath(expect_exe)) - def pip_cmd_checker(cmd): + def pip_cmd_checker(cmd, **kwargs): cmd[0] = os.path.normcase(cmd[0]) self.assertEqual( cmd, @@ -169,12 +257,11 @@ def pip_cmd_checker(cmd): 'install', '--upgrade', 'pip', - 'setuptools' ] ) fake_context = builder.ensure_directories(fake_env_dir) - with patch('venv.subprocess.check_call', pip_cmd_checker): + with patch('venv.subprocess.check_output', pip_cmd_checker): builder.upgrade_dependencies(fake_context) @requireVenvCreate @@ -185,8 +272,7 @@ def test_prefixes(self): # check a venv's prefixes rmtree(self.env_dir) self.run_with_capture(venv.create, self.env_dir) - envpy = os.path.join(self.env_dir, self.bindir, self.exe) - cmd = [envpy, '-c', None] + cmd = [self.envpy(), '-c', None] for prefix, expected in ( ('prefix', self.env_dir), ('exec_prefix', self.env_dir), @@ -194,7 +280,76 @@ def test_prefixes(self): ('base_exec_prefix', sys.base_exec_prefix)): cmd[2] = 'import sys; print(sys.%s)' % prefix out, err = check_output(cmd) - self.assertEqual(out.strip(), expected.encode()) + self.assertEqual(pathlib.Path(out.strip().decode()), + pathlib.Path(expected), prefix) + + @requireVenvCreate + def test_sysconfig(self): + """ + Test that the sysconfig functions work in a virtual environment. + """ + rmtree(self.env_dir) + self.run_with_capture(venv.create, self.env_dir, symlinks=False) + cmd = [self.envpy(), '-c', None] + for call, expected in ( + # installation scheme + ('get_preferred_scheme("prefix")', 'venv'), + ('get_default_scheme()', 'venv'), + # build environment + ('is_python_build()', str(sysconfig.is_python_build())), + ('get_makefile_filename()', sysconfig.get_makefile_filename()), + ('get_config_h_filename()', sysconfig.get_config_h_filename()), + ('get_config_var("Py_GIL_DISABLED")', + str(sysconfig.get_config_var("Py_GIL_DISABLED")))): + with self.subTest(call): + cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) + for attr, expected in ( + ('executable', self.envpy()), + # Usually compare to sys.executable, but if we're running in our own + # venv then we really need to compare to our base executable + ('_base_executable', sys._base_executable), + ): + with self.subTest(attr): + cmd[2] = f'import sys; print(sys.{attr})' + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) + + @requireVenvCreate + @unittest.skipUnless(can_symlink(), 'Needs symlinks') + def test_sysconfig_symlinks(self): + """ + Test that the sysconfig functions work in a virtual environment. + """ + rmtree(self.env_dir) + self.run_with_capture(venv.create, self.env_dir, symlinks=True) + cmd = [self.envpy(), '-c', None] + for call, expected in ( + # installation scheme + ('get_preferred_scheme("prefix")', 'venv'), + ('get_default_scheme()', 'venv'), + # build environment + ('is_python_build()', str(sysconfig.is_python_build())), + ('get_makefile_filename()', sysconfig.get_makefile_filename()), + ('get_config_h_filename()', sysconfig.get_config_h_filename()), + ('get_config_var("Py_GIL_DISABLED")', + str(sysconfig.get_config_var("Py_GIL_DISABLED")))): + with self.subTest(call): + cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) + for attr, expected in ( + ('executable', self.envpy()), + # Usually compare to sys.executable, but if we're running in our own + # venv then we really need to compare to our base executable + # HACK: Test fails on POSIX with unversioned binary (PR gh-113033) + #('_base_executable', sys._base_executable), + ): + with self.subTest(attr): + cmd[2] = f'import sys; print(sys.{attr})' + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) if sys.platform == 'win32': ENV_SUBDIRS = ( @@ -259,6 +414,8 @@ def test_unoverwritable_fails(self): self.assertRaises((ValueError, OSError), venv.create, self.env_dir) self.clear_directory(self.env_dir) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_upgrade(self): """ Test upgrading an existing environment directory. @@ -321,8 +478,7 @@ def test_executable(self): """ rmtree(self.env_dir) self.run_with_capture(venv.create, self.env_dir) - envpy = os.path.join(os.path.realpath(self.env_dir), - self.bindir, self.exe) + envpy = self.envpy(real_env_dir=True) out, err = check_output([envpy, '-c', 'import sys; print(sys.executable)']) self.assertEqual(out.strip(), envpy.encode()) @@ -335,12 +491,89 @@ def test_executable_symlinks(self): rmtree(self.env_dir) builder = venv.EnvBuilder(clear=True, symlinks=True) builder.create(self.env_dir) - envpy = os.path.join(os.path.realpath(self.env_dir), - self.bindir, self.exe) + envpy = self.envpy(real_env_dir=True) out, err = check_output([envpy, '-c', 'import sys; print(sys.executable)']) self.assertEqual(out.strip(), envpy.encode()) + # gh-124651: test quoted strings + @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows') + def test_special_chars_bash(self): + """ + Test that the template strings are quoted properly (bash) + """ + rmtree(self.env_dir) + bash = shutil.which('bash') + if bash is None: + self.skipTest('bash required for this test') + env_name = '"\';&&$e|\'"' + env_dir = os.path.join(os.path.realpath(self.env_dir), env_name) + builder = venv.EnvBuilder(clear=True) + builder.create(env_dir) + activate = os.path.join(env_dir, self.bindir, 'activate') + test_script = os.path.join(self.env_dir, 'test_special_chars.sh') + with open(test_script, "w") as f: + f.write(f'source {shlex.quote(activate)}\n' + 'python -c \'import sys; print(sys.executable)\'\n' + 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n' + 'deactivate\n') + out, err = check_output([bash, test_script]) + lines = out.splitlines() + self.assertTrue(env_name.encode() in lines[0]) + self.assertEndsWith(lines[1], env_name.encode()) + + # gh-124651: test quoted strings + @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows') + @unittest.skipIf(sys.platform.startswith('netbsd'), + "NetBSD csh fails with quoted special chars; see gh-139308") + def test_special_chars_csh(self): + """ + Test that the template strings are quoted properly (csh) + """ + rmtree(self.env_dir) + csh = shutil.which('tcsh') or shutil.which('csh') + if csh is None: + self.skipTest('csh required for this test') + env_name = '"\';&&$e|\'"' + env_dir = os.path.join(os.path.realpath(self.env_dir), env_name) + builder = venv.EnvBuilder(clear=True) + builder.create(env_dir) + activate = os.path.join(env_dir, self.bindir, 'activate.csh') + test_script = os.path.join(self.env_dir, 'test_special_chars.csh') + with open(test_script, "w") as f: + f.write(f'source {shlex.quote(activate)}\n' + 'python -c \'import sys; print(sys.executable)\'\n' + 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n' + 'deactivate\n') + out, err = check_output([csh, test_script]) + lines = out.splitlines() + self.assertTrue(env_name.encode() in lines[0]) + self.assertEndsWith(lines[1], env_name.encode()) + + # gh-124651: test quoted strings on Windows + @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows') + def test_special_chars_windows(self): + """ + Test that the template strings are quoted properly on Windows + """ + rmtree(self.env_dir) + env_name = "'&&^$e" + env_dir = os.path.join(os.path.realpath(self.env_dir), env_name) + builder = venv.EnvBuilder(clear=True) + builder.create(env_dir) + activate = os.path.join(env_dir, self.bindir, 'activate.bat') + test_batch = os.path.join(self.env_dir, 'test_special_chars.bat') + with open(test_batch, "w") as f: + f.write('@echo off\n' + f'"{activate}" & ' + f'{self.exe} -c "import sys; print(sys.executable)" & ' + f'{self.exe} -c "import os; print(os.environ[\'VIRTUAL_ENV\'])" & ' + 'deactivate') + out, err = check_output([test_batch]) + lines = out.splitlines() + self.assertTrue(env_name.encode() in lines[0]) + self.assertEndsWith(lines[1], env_name.encode()) + @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows') def test_unicode_in_batch_file(self): """ @@ -351,13 +584,27 @@ def test_unicode_in_batch_file(self): builder = venv.EnvBuilder(clear=True) builder.create(env_dir) activate = os.path.join(env_dir, self.bindir, 'activate.bat') - envpy = os.path.join(env_dir, self.bindir, self.exe) out, err = check_output( [activate, '&', self.exe, '-c', 'print(0)'], encoding='oem', ) self.assertEqual(out.strip(), '0') + @unittest.skipUnless(os.name == 'nt' and can_symlink(), + 'symlinks on Windows') + def test_failed_symlink(self): + """ + Test handling of failed symlinks on Windows. + """ + rmtree(self.env_dir) + env_dir = os.path.join(os.path.realpath(self.env_dir), 'venv') + with patch('os.symlink') as mock_symlink: + mock_symlink.side_effect = OSError() + builder = venv.EnvBuilder(clear=True, symlinks=True) + _, err = self.run_with_capture(builder.create, env_dir) + filepath_regex = r"'[A-Z]:\\\\(?:[^\\\\]+\\\\)*[^\\\\]+'" + self.assertRegex(err, rf"Unable to symlink {filepath_regex} to {filepath_regex}") + @requireVenvCreate def test_multiprocessing(self): """ @@ -370,15 +617,25 @@ def test_multiprocessing(self): rmtree(self.env_dir) self.run_with_capture(venv.create, self.env_dir) - envpy = os.path.join(os.path.realpath(self.env_dir), - self.bindir, self.exe) - out, err = check_output([envpy, '-c', + out, err = check_output([self.envpy(real_env_dir=True), '-c', 'from multiprocessing import Pool; ' 'pool = Pool(1); ' 'print(pool.apply_async("Python".lower).get(3)); ' 'pool.terminate()']) self.assertEqual(out.strip(), "python".encode()) + @requireVenvCreate + def test_multiprocessing_recursion(self): + """ + Test that the multiprocessing is able to spawn itself + """ + skip_if_broken_multiprocessing_synchronize() + + rmtree(self.env_dir) + self.run_with_capture(venv.create, self.env_dir) + script = os.path.join(TEST_HOME_DIR, '_test_venv_multiprocessing.py') + subprocess.check_call([self.envpy(real_env_dir=True), "-I", script]) + @unittest.skipIf(os.name == 'nt', 'not relevant on Windows') def test_deactivate_with_strict_bash_opts(self): bash = shutil.which("bash") @@ -404,19 +661,250 @@ def test_macos_env(self): builder = venv.EnvBuilder() builder.create(self.env_dir) - envpy = os.path.join(os.path.realpath(self.env_dir), - self.bindir, self.exe) - out, err = check_output([envpy, '-c', + out, err = check_output([self.envpy(real_env_dir=True), '-c', 'import os; print("__PYVENV_LAUNCHER__" in os.environ)']) self.assertEqual(out.strip(), 'False'.encode()) + def test_pathsep_error(self): + """ + Test that venv creation fails when the target directory contains + the path separator. + """ + rmtree(self.env_dir) + bad_itempath = self.env_dir + os.pathsep + self.assertRaises(ValueError, venv.create, bad_itempath) + self.assertRaises(ValueError, venv.create, FakePath(bad_itempath)) + + @unittest.skipIf(os.name == 'nt', 'not relevant on Windows') + @requireVenvCreate + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_zippath_from_non_installed_posix(self): + """ + Test that when create venv from non-installed python, the zip path + value is as expected. + """ + rmtree(self.env_dir) + # First try to create a non-installed python. It's not a real full + # functional non-installed python, but enough for this test. + platlibdir = sys.platlibdir + non_installed_dir = os.path.realpath(tempfile.mkdtemp()) + self.addCleanup(rmtree, non_installed_dir) + bindir = os.path.join(non_installed_dir, self.bindir) + os.mkdir(bindir) + shutil.copy2(sys.executable, bindir) + libdir = os.path.join(non_installed_dir, platlibdir, self.lib[1]) + os.makedirs(libdir) + landmark = os.path.join(libdir, "os.py") + abi_thread = "t" if sysconfig.get_config_var("Py_GIL_DISABLED") else "" + stdlib_zip = f"python{sys.version_info.major}{sys.version_info.minor}{abi_thread}" + zip_landmark = os.path.join(non_installed_dir, + platlibdir, + stdlib_zip) + additional_pythonpath_for_non_installed = [] + + # Copy stdlib files to the non-installed python so venv can + # correctly calculate the prefix. + for eachpath in sys.path: + if eachpath.endswith(".zip"): + if os.path.isfile(eachpath): + shutil.copyfile( + eachpath, + os.path.join(non_installed_dir, platlibdir)) + elif os.path.isfile(os.path.join(eachpath, "os.py")): + names = os.listdir(eachpath) + ignored_names = copy_python_src_ignore(eachpath, names) + for name in names: + if name in ignored_names: + continue + if name == "site-packages": + continue + fn = os.path.join(eachpath, name) + if os.path.isfile(fn): + shutil.copy(fn, libdir) + elif os.path.isdir(fn): + shutil.copytree(fn, os.path.join(libdir, name), + ignore=copy_python_src_ignore) + else: + additional_pythonpath_for_non_installed.append( + eachpath) + cmd = [os.path.join(non_installed_dir, self.bindir, self.exe), + "-m", + "venv", + "--without-pip", + "--without-scm-ignore-files", + self.env_dir] + # Our fake non-installed python is not fully functional because + # it cannot find the extensions. Set PYTHONPATH so it can run the + # venv module correctly. + pythonpath = os.pathsep.join( + additional_pythonpath_for_non_installed) + # For python built with shared enabled. We need to set + # LD_LIBRARY_PATH so the non-installed python can find and link + # libpython.so + ld_library_path = sysconfig.get_config_var("LIBDIR") + if not ld_library_path or sysconfig.is_python_build(): + ld_library_path = os.path.abspath(os.path.dirname(sys.executable)) + if sys.platform == 'darwin': + ld_library_path_env = "DYLD_LIBRARY_PATH" + else: + ld_library_path_env = "LD_LIBRARY_PATH" + child_env = { + "PYTHONPATH": pythonpath, + ld_library_path_env: ld_library_path, + } + if asan_options := os.environ.get("ASAN_OPTIONS"): + # prevent https://github.com/python/cpython/issues/104839 + child_env["ASAN_OPTIONS"] = asan_options + subprocess.check_call(cmd, env=child_env) + # Now check the venv created from the non-installed python has + # correct zip path in pythonpath. + cmd = [self.envpy(), '-S', '-c', 'import sys; print(sys.path)'] + out, err = check_output(cmd) + self.assertTrue(zip_landmark.encode() in out) + + @requireVenvCreate + def test_activate_shell_script_has_no_dos_newlines(self): + """ + Test that the `activate` shell script contains no CR LF. + This is relevant for Cygwin, as the Windows build might have + converted line endings accidentally. + """ + venv_dir = pathlib.Path(self.env_dir) + rmtree(venv_dir) + [[scripts_dir], *_] = self.ENV_SUBDIRS + script_path = venv_dir / scripts_dir / "activate" + venv.create(venv_dir) + with open(script_path, 'rb') as script: + for i, line in enumerate(script, 1): + error_message = f"CR LF found in line {i}" + self.assertFalse(line.endswith(b'\r\n'), error_message) + + @requireVenvCreate + def test_scm_ignore_files_git(self): + """ + Test that a .gitignore file is created when "git" is specified. + The file should contain a `*\n` line. + """ + self.run_with_capture(venv.create, self.env_dir, + scm_ignore_files={'git'}) + file_lines = self.get_text_file_contents('.gitignore').splitlines() + self.assertIn('*', file_lines) + + @requireVenvCreate + def test_create_scm_ignore_files_multiple(self): + """ + Test that ``scm_ignore_files`` can work with multiple SCMs. + """ + bzrignore_name = ".bzrignore" + contents = "# For Bazaar.\n*\n" + + class BzrEnvBuilder(venv.EnvBuilder): + def create_bzr_ignore_file(self, context): + gitignore_path = os.path.join(context.env_dir, bzrignore_name) + with open(gitignore_path, 'w', encoding='utf-8') as file: + file.write(contents) + + builder = BzrEnvBuilder(scm_ignore_files={'git', 'bzr'}) + self.run_with_capture(builder.create, self.env_dir) + + gitignore_lines = self.get_text_file_contents('.gitignore').splitlines() + self.assertIn('*', gitignore_lines) + + bzrignore = self.get_text_file_contents(bzrignore_name) + self.assertEqual(bzrignore, contents) + + @requireVenvCreate + def test_create_scm_ignore_files_empty(self): + """ + Test that no default ignore files are created when ``scm_ignore_files`` + is empty. + """ + # scm_ignore_files is set to frozenset() by default. + self.run_with_capture(venv.create, self.env_dir) + with self.assertRaises(FileNotFoundError): + self.get_text_file_contents('.gitignore') + + self.assertIn("--without-scm-ignore-files", + self.get_text_file_contents('pyvenv.cfg')) + + @requireVenvCreate + def test_cli_with_scm_ignore_files(self): + """ + Test that default SCM ignore files are created by default via the CLI. + """ + self.run_with_capture(venv.main, ['--without-pip', self.env_dir]) + + gitignore_lines = self.get_text_file_contents('.gitignore').splitlines() + self.assertIn('*', gitignore_lines) + + @requireVenvCreate + def test_cli_without_scm_ignore_files(self): + """ + Test that ``--without-scm-ignore-files`` doesn't create SCM ignore files. + """ + args = ['--without-pip', '--without-scm-ignore-files', self.env_dir] + self.run_with_capture(venv.main, args) + + with self.assertRaises(FileNotFoundError): + self.get_text_file_contents('.gitignore') + + def test_venv_same_path(self): + same_path = venv.EnvBuilder._same_path + if sys.platform == 'win32': + # Case-insensitive, and handles short/long names + tests = [ + (True, TESTFN, TESTFN), + (True, TESTFN.lower(), TESTFN.upper()), + ] + import _winapi + # ProgramFiles is the most reliable path that will have short/long + progfiles = os.getenv('ProgramFiles') + if progfiles: + tests = [ + *tests, + (True, progfiles, progfiles), + (True, _winapi.GetShortPathName(progfiles), _winapi.GetLongPathName(progfiles)), + ] + else: + # Just a simple case-sensitive comparison + tests = [ + (True, TESTFN, TESTFN), + (False, TESTFN.lower(), TESTFN.upper()), + ] + for r, path1, path2 in tests: + with self.subTest(f"{path1}-{path2}"): + if r: + self.assertTrue(same_path(path1, path2)) + else: + self.assertFalse(same_path(path1, path2)) + + # gh-126084: venvwlauncher should run pythonw, not python + @requireVenvCreate + @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows') + def test_venvwlauncher(self): + """ + Test that the GUI launcher runs the GUI python. + """ + rmtree(self.env_dir) + venv.create(self.env_dir) + exename = self.exe + # Retain the debug suffix if present + if "python" in exename and not "pythonw" in exename: + exename = exename.replace("python", "pythonw") + envpyw = os.path.join(self.env_dir, self.bindir, exename) + try: + subprocess.check_call([envpyw, "-c", "import sys; " + "assert sys._base_executable.endswith('%s')" % exename]) + except subprocess.CalledProcessError: + self.fail("venvwlauncher.exe did not run %s" % exename) + + @requireVenvCreate class EnsurePipTest(BaseTest): """Test venv module installation of pip.""" def assert_pip_not_installed(self): - envpy = os.path.join(os.path.realpath(self.env_dir), - self.bindir, self.exe) - out, err = check_output([envpy, '-c', + out, err = check_output([self.envpy(real_env_dir=True), '-c', 'try:\n import pip\nexcept ImportError:\n print("OK")']) # We force everything to text, so unittest gives the detailed diff # if we get unexpected results @@ -478,20 +966,14 @@ def do_test_with_pip(self, system_site_packages): # Actually run the create command with all that unhelpful # config in place to ensure we ignore it - try: + with self.nicer_error(): self.run_with_capture(venv.create, self.env_dir, system_site_packages=system_site_packages, with_pip=True) - except subprocess.CalledProcessError as exc: - # The output this produces can be a little hard to read, - # but at least it has all the details - details = exc.output.decode(errors="replace") - msg = "{}\n\n**Subprocess Output**\n{}" - self.fail(msg.format(exc, details)) # Ensure pip is available in the virtual environment - envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe) # Ignore DeprecationWarning since pip code is not part of Python - out, err = check_output([envpy, '-W', 'ignore::DeprecationWarning', + out, err = check_output([self.envpy(real_env_dir=True), + '-W', 'ignore::DeprecationWarning', '-W', 'ignore::ImportWarning', '-I', '-m', 'pip', '--version']) # We force everything to text, so unittest gives the detailed diff @@ -508,13 +990,14 @@ def do_test_with_pip(self, system_site_packages): # Check the private uninstall command provided for the Windows # installers works (at least in a virtual environment) with EnvironmentVarGuard() as envvars: - # It seems ensurepip._uninstall calls subprocesses which do not - # inherit the interpreter settings. - envvars["PYTHONWARNINGS"] = "ignore" - out, err = check_output([envpy, - '-W', 'ignore::DeprecationWarning', - '-W', 'ignore::ImportWarning', '-I', - '-m', 'ensurepip._uninstall']) + with self.nicer_error(): + # It seems ensurepip._uninstall calls subprocesses which do not + # inherit the interpreter settings. + envvars["PYTHONWARNINGS"] = "ignore" + out, err = check_output([self.envpy(real_env_dir=True), + '-W', 'ignore::DeprecationWarning', + '-W', 'ignore::ImportWarning', '-I', + '-m', 'ensurepip._uninstall']) # We force everything to text, so unittest gives the detailed diff # if we get unexpected results err = err.decode("latin-1") # Force to text, prevent decoding errors @@ -527,25 +1010,51 @@ def do_test_with_pip(self, system_site_packages): err = re.sub("^(WARNING: )?The directory .* or its parent directory " "is not owned or is not writable by the current user.*$", "", err, flags=re.MULTILINE) + # Ignore warning about missing optional module: + try: + import ssl + except ImportError: + err = re.sub( + "^WARNING: Disabling truststore since ssl support is missing$", + "", + err, flags=re.MULTILINE) self.assertEqual(err.rstrip(), "") # Being fairly specific regarding the expected behaviour for the # initial bundling phase in Python 3.4. If the output changes in # future pip versions, this test can likely be relaxed further. out = out.decode("latin-1") # Force to text, prevent decoding errors self.assertIn("Successfully uninstalled pip", out) - self.assertIn("Successfully uninstalled setuptools", out) # Check pip is now gone from the virtual environment. This only # applies in the system_site_packages=False case, because in the # other case, pip may still be available in the system site-packages if not system_site_packages: self.assert_pip_not_installed() - # Issue #26610: pip/pep425tags.py requires ctypes - @unittest.skipUnless(ctypes, 'pip requires ctypes') - @requires_zlib() + @contextlib.contextmanager + def nicer_error(self): + """ + Capture output from a failed subprocess for easier debugging. + + The output this handler produces can be a little hard to read, + but at least it has all the details. + """ + try: + yield + except subprocess.CalledProcessError as exc: + out = (exc.output or b'').decode(errors="replace") + err = (exc.stderr or b'').decode(errors="replace") + self.fail( + f"{exc}\n\n" + f"**Subprocess Output**\n{out}\n\n" + f"**Subprocess Error**\n{err}" + ) + + @requires_venv_with_pip() + @requires_resource('cpu') def test_with_pip(self): self.do_test_with_pip(False) self.do_test_with_pip(True) + if __name__ == "__main__": unittest.main() diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py index 6f1af294ae6..f7a6d261401 100644 --- a/Lib/venv/__init__.py +++ b/Lib/venv/__init__.py @@ -11,9 +11,10 @@ import sys import sysconfig import types +import shlex -CORE_VENV_DEPS = ('pip', 'setuptools') +CORE_VENV_DEPS = ('pip',) logger = logging.getLogger(__name__) @@ -41,20 +42,24 @@ class EnvBuilder: environment :param prompt: Alternative terminal prefix for the environment. :param upgrade_deps: Update the base venv modules to the latest on PyPI + :param scm_ignore_files: Create ignore files for the SCMs specified by the + iterable. """ def __init__(self, system_site_packages=False, clear=False, symlinks=False, upgrade=False, with_pip=False, prompt=None, - upgrade_deps=False): + upgrade_deps=False, *, scm_ignore_files=frozenset()): self.system_site_packages = system_site_packages self.clear = clear self.symlinks = symlinks self.upgrade = upgrade self.with_pip = with_pip + self.orig_prompt = prompt if prompt == '.': # see bpo-38901 prompt = os.path.basename(os.getcwd()) self.prompt = prompt self.upgrade_deps = upgrade_deps + self.scm_ignore_files = frozenset(map(str.lower, scm_ignore_files)) def create(self, env_dir): """ @@ -65,6 +70,8 @@ def create(self, env_dir): """ env_dir = os.path.abspath(env_dir) context = self.ensure_directories(env_dir) + for scm in self.scm_ignore_files: + getattr(self, f"create_{scm}_ignore_file")(context) # See issue 24875. We need system_site_packages to be False # until after pip is installed. true_system_site_packages = self.system_site_packages @@ -92,6 +99,42 @@ def clear_directory(self, path): elif os.path.isdir(fn): shutil.rmtree(fn) + def _venv_path(self, env_dir, name): + vars = { + 'base': env_dir, + 'platbase': env_dir, + 'installed_base': env_dir, + 'installed_platbase': env_dir, + } + return sysconfig.get_path(name, scheme='venv', vars=vars) + + @classmethod + def _same_path(cls, path1, path2): + """Check whether two paths appear the same. + + Whether they refer to the same file is irrelevant; we're testing for + whether a human reader would look at the path string and easily tell + that they're the same file. + """ + if sys.platform == 'win32': + if os.path.normcase(path1) == os.path.normcase(path2): + return True + # gh-90329: Don't display a warning for short/long names + import _winapi + try: + path1 = _winapi.GetLongPathName(os.fsdecode(path1)) + except OSError: + pass + try: + path2 = _winapi.GetLongPathName(os.fsdecode(path2)) + except OSError: + pass + if os.path.normcase(path1) == os.path.normcase(path2): + return True + return False + else: + return path1 == path2 + def ensure_directories(self, env_dir): """ Create the directories for the environment. @@ -106,31 +149,38 @@ def create_if_needed(d): elif os.path.islink(d) or os.path.isfile(d): raise ValueError('Unable to create directory %r' % d) + if os.pathsep in os.fspath(env_dir): + raise ValueError(f'Refusing to create a venv in {env_dir} because ' + f'it contains the PATH separator {os.pathsep}.') if os.path.exists(env_dir) and self.clear: self.clear_directory(env_dir) context = types.SimpleNamespace() context.env_dir = env_dir context.env_name = os.path.split(env_dir)[1] - prompt = self.prompt if self.prompt is not None else context.env_name - context.prompt = '(%s) ' % prompt + context.prompt = self.prompt if self.prompt is not None else context.env_name create_if_needed(env_dir) executable = sys._base_executable + if not executable: # see gh-96861 + raise ValueError('Unable to determine path to the running ' + 'Python interpreter. Provide an explicit path or ' + 'check that your PATH environment variable is ' + 'correctly set.') dirname, exename = os.path.split(os.path.abspath(executable)) + if sys.platform == 'win32': + # Always create the simplest name in the venv. It will either be a + # link back to executable, or a copy of the appropriate launcher + _d = '_d' if os.path.splitext(exename)[0].endswith('_d') else '' + exename = f'python{_d}.exe' context.executable = executable context.python_dir = dirname context.python_exe = exename - if sys.platform == 'win32': - binname = 'Scripts' - incpath = 'Include' - libpath = os.path.join(env_dir, 'Lib', 'site-packages') - else: - binname = 'bin' - incpath = 'include' - libpath = os.path.join(env_dir, 'lib', - 'python%d.%d' % sys.version_info[:2], - 'site-packages') - context.inc_path = path = os.path.join(env_dir, incpath) - create_if_needed(path) + binpath = self._venv_path(env_dir, 'scripts') + incpath = self._venv_path(env_dir, 'include') + libpath = self._venv_path(env_dir, 'purelib') + + context.inc_path = incpath + create_if_needed(incpath) + context.lib_path = libpath create_if_needed(libpath) # Issue 21197: create lib64 as a symlink to lib on 64-bit non-OS X POSIX if ((sys.maxsize > 2**32) and (os.name == 'posix') and @@ -138,8 +188,8 @@ def create_if_needed(d): link_path = os.path.join(env_dir, 'lib64') if not os.path.exists(link_path): # Issue #21643 os.symlink('lib', link_path) - context.bin_path = binpath = os.path.join(env_dir, binname) - context.bin_name = binname + context.bin_path = binpath + context.bin_name = os.path.relpath(binpath, env_dir) context.env_exe = os.path.join(binpath, exename) create_if_needed(binpath) # Assign and update the command to use when launching the newly created @@ -149,7 +199,7 @@ def create_if_needed(d): # bpo-45337: Fix up env_exec_cmd to account for file system redirections. # Some redirects only apply to CreateFile and not CreateProcess real_env_exe = os.path.realpath(context.env_exe) - if os.path.normcase(real_env_exe) != os.path.normcase(context.env_exe): + if not self._same_path(real_env_exe, context.env_exe): logger.warning('Actual environment location may have moved due to ' 'redirects, links or junctions.\n' ' Requested location: "%s"\n' @@ -178,86 +228,84 @@ def create_configuration(self, context): f.write('version = %d.%d.%d\n' % sys.version_info[:3]) if self.prompt is not None: f.write(f'prompt = {self.prompt!r}\n') - - if os.name != 'nt': - def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): - """ - Try symlinking a file, and if that fails, fall back to copying. - """ - force_copy = not self.symlinks - if not force_copy: - try: - if not os.path.islink(dst): # can't link to itself! - if relative_symlinks_ok: - assert os.path.dirname(src) == os.path.dirname(dst) - os.symlink(os.path.basename(src), dst) - else: - os.symlink(src, dst) - except Exception: # may need to use a more specific exception - logger.warning('Unable to symlink %r to %r', src, dst) - force_copy = True - if force_copy: - shutil.copyfile(src, dst) - else: - def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): - """ - Try symlinking a file, and if that fails, fall back to copying. - """ - bad_src = os.path.lexists(src) and not os.path.exists(src) - if self.symlinks and not bad_src and not os.path.islink(dst): - try: + f.write('executable = %s\n' % os.path.realpath(sys.executable)) + args = [] + nt = os.name == 'nt' + if nt and self.symlinks: + args.append('--symlinks') + if not nt and not self.symlinks: + args.append('--copies') + if not self.with_pip: + args.append('--without-pip') + if self.system_site_packages: + args.append('--system-site-packages') + if self.clear: + args.append('--clear') + if self.upgrade: + args.append('--upgrade') + if self.upgrade_deps: + args.append('--upgrade-deps') + if self.orig_prompt is not None: + args.append(f'--prompt="{self.orig_prompt}"') + if not self.scm_ignore_files: + args.append('--without-scm-ignore-files') + + args.append(context.env_dir) + args = ' '.join(args) + f.write(f'command = {sys.executable} -m venv {args}\n') + + def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): + """ + Try symlinking a file, and if that fails, fall back to copying. + (Unused on Windows, because we can't just copy a failed symlink file: we + switch to a different set of files instead.) + """ + assert os.name != 'nt' + force_copy = not self.symlinks + if not force_copy: + try: + if not os.path.islink(dst): # can't link to itself! if relative_symlinks_ok: assert os.path.dirname(src) == os.path.dirname(dst) os.symlink(os.path.basename(src), dst) else: os.symlink(src, dst) - return - except Exception: # may need to use a more specific exception - logger.warning('Unable to symlink %r to %r', src, dst) - - # On Windows, we rewrite symlinks to our base python.exe into - # copies of venvlauncher.exe - basename, ext = os.path.splitext(os.path.basename(src)) - srcfn = os.path.join(os.path.dirname(__file__), - "scripts", - "nt", - basename + ext) - # Builds or venv's from builds need to remap source file - # locations, as we do not put them into Lib/venv/scripts - if sysconfig.is_python_build(True) or not os.path.isfile(srcfn): - if basename.endswith('_d'): - ext = '_d' + ext - basename = basename[:-2] - if basename == 'python': - basename = 'venvlauncher' - elif basename == 'pythonw': - basename = 'venvwlauncher' - src = os.path.join(os.path.dirname(src), basename + ext) - else: - src = srcfn - if not os.path.exists(src): - if not bad_src: - logger.warning('Unable to copy %r', src) - return - + except Exception: # may need to use a more specific exception + logger.warning('Unable to symlink %r to %r', src, dst) + force_copy = True + if force_copy: shutil.copyfile(src, dst) - def setup_python(self, context): + def create_git_ignore_file(self, context): """ - Set up a Python executable in the environment. + Create a .gitignore file in the environment directory. - :param context: The information for the environment creation request - being processed. + The contents of the file cause the entire environment directory to be + ignored by git. """ - binpath = context.bin_path - path = context.env_exe - copier = self.symlink_or_copy - dirname = context.python_dir - if os.name != 'nt': + gitignore_path = os.path.join(context.env_dir, '.gitignore') + with open(gitignore_path, 'w', encoding='utf-8') as file: + file.write('# Created by venv; ' + 'see https://docs.python.org/3/library/venv.html\n') + file.write('*\n') + + if os.name != 'nt': + def setup_python(self, context): + """ + Set up a Python executable in the environment. + + :param context: The information for the environment creation request + being processed. + """ + binpath = context.bin_path + path = context.env_exe + copier = self.symlink_or_copy + dirname = context.python_dir copier(context.executable, path) if not os.path.islink(path): os.chmod(path, 0o755) - for suffix in ('python', 'python3', f'python3.{sys.version_info[1]}'): + for suffix in ('python', 'python3', + f'python3.{sys.version_info[1]}'): path = os.path.join(binpath, suffix) if not os.path.exists(path): # Issue 18807: make copies if @@ -265,32 +313,107 @@ def setup_python(self, context): copier(context.env_exe, path, relative_symlinks_ok=True) if not os.path.islink(path): os.chmod(path, 0o755) - else: - if self.symlinks: - # For symlinking, we need a complete copy of the root directory - # If symlinks fail, you'll get unnecessary copies of files, but - # we assume that if you've opted into symlinks on Windows then - # you know what you're doing. - suffixes = [ - f for f in os.listdir(dirname) if - os.path.normcase(os.path.splitext(f)[1]) in ('.exe', '.dll') - ] - if sysconfig.is_python_build(True): - suffixes = [ - f for f in suffixes if - os.path.normcase(f).startswith(('python', 'vcruntime')) - ] + + else: + def setup_python(self, context): + """ + Set up a Python executable in the environment. + + :param context: The information for the environment creation request + being processed. + """ + binpath = context.bin_path + dirname = context.python_dir + exename = os.path.basename(context.env_exe) + exe_stem = os.path.splitext(exename)[0] + exe_d = '_d' if os.path.normcase(exe_stem).endswith('_d') else '' + if sysconfig.is_python_build(): + scripts = dirname else: - suffixes = {'python.exe', 'python_d.exe', 'pythonw.exe', 'pythonw_d.exe'} - base_exe = os.path.basename(context.env_exe) - suffixes.add(base_exe) + scripts = os.path.join(os.path.dirname(__file__), + 'scripts', 'nt') + if not sysconfig.get_config_var("Py_GIL_DISABLED"): + python_exe = os.path.join(dirname, f'python{exe_d}.exe') + pythonw_exe = os.path.join(dirname, f'pythonw{exe_d}.exe') + link_sources = { + 'python.exe': python_exe, + f'python{exe_d}.exe': python_exe, + 'pythonw.exe': pythonw_exe, + f'pythonw{exe_d}.exe': pythonw_exe, + } + python_exe = os.path.join(scripts, f'venvlauncher{exe_d}.exe') + pythonw_exe = os.path.join(scripts, f'venvwlauncher{exe_d}.exe') + copy_sources = { + 'python.exe': python_exe, + f'python{exe_d}.exe': python_exe, + 'pythonw.exe': pythonw_exe, + f'pythonw{exe_d}.exe': pythonw_exe, + } + else: + exe_t = f'3.{sys.version_info[1]}t' + python_exe = os.path.join(dirname, f'python{exe_t}{exe_d}.exe') + pythonw_exe = os.path.join(dirname, f'pythonw{exe_t}{exe_d}.exe') + link_sources = { + 'python.exe': python_exe, + f'python{exe_d}.exe': python_exe, + f'python{exe_t}.exe': python_exe, + f'python{exe_t}{exe_d}.exe': python_exe, + 'pythonw.exe': pythonw_exe, + f'pythonw{exe_d}.exe': pythonw_exe, + f'pythonw{exe_t}.exe': pythonw_exe, + f'pythonw{exe_t}{exe_d}.exe': pythonw_exe, + } + python_exe = os.path.join(scripts, f'venvlaunchert{exe_d}.exe') + pythonw_exe = os.path.join(scripts, f'venvwlaunchert{exe_d}.exe') + copy_sources = { + 'python.exe': python_exe, + f'python{exe_d}.exe': python_exe, + f'python{exe_t}.exe': python_exe, + f'python{exe_t}{exe_d}.exe': python_exe, + 'pythonw.exe': pythonw_exe, + f'pythonw{exe_d}.exe': pythonw_exe, + f'pythonw{exe_t}.exe': pythonw_exe, + f'pythonw{exe_t}{exe_d}.exe': pythonw_exe, + } + + do_copies = True + if self.symlinks: + do_copies = False + # For symlinking, we need all the DLLs to be available alongside + # the executables. + link_sources.update({ + f: os.path.join(dirname, f) for f in os.listdir(dirname) + if os.path.normcase(f).startswith(('python', 'vcruntime')) + and os.path.normcase(os.path.splitext(f)[1]) == '.dll' + }) + + to_unlink = [] + for dest, src in link_sources.items(): + dest = os.path.join(binpath, dest) + try: + os.symlink(src, dest) + to_unlink.append(dest) + except OSError: + logger.warning('Unable to symlink %r to %r', src, dest) + do_copies = True + for f in to_unlink: + try: + os.unlink(f) + except OSError: + logger.warning('Failed to clean up symlink %r', + f) + logger.warning('Retrying with copies') + break - for suffix in suffixes: - src = os.path.join(dirname, suffix) - if os.path.lexists(src): - copier(src, os.path.join(binpath, suffix)) + if do_copies: + for dest, src in copy_sources.items(): + dest = os.path.join(binpath, dest) + try: + shutil.copy2(src, dest) + except OSError: + logger.warning('Unable to copy %r to %r', src, dest) - if sysconfig.is_python_build(True): + if sysconfig.is_python_build(): # copy init.tcl for root, dirs, files in os.walk(context.python_dir): if 'init.tcl' in files: @@ -303,14 +426,25 @@ def setup_python(self, context): shutil.copyfile(src, dst) break + def _call_new_python(self, context, *py_args, **kwargs): + """Executes the newly created Python using safe-ish options""" + # gh-98251: We do not want to just use '-I' because that masks + # legitimate user preferences (such as not writing bytecode). All we + # really need is to ensure that the path variables do not overrule + # normal venv handling. + args = [context.env_exec_cmd, *py_args] + kwargs['env'] = env = os.environ.copy() + env['VIRTUAL_ENV'] = context.env_dir + env.pop('PYTHONHOME', None) + env.pop('PYTHONPATH', None) + kwargs['cwd'] = context.env_dir + kwargs['executable'] = context.env_exec_cmd + subprocess.check_output(args, **kwargs) + def _setup_pip(self, context): """Installs or upgrades pip in a virtual environment""" - # We run ensurepip in isolated mode to avoid side effects from - # environment vars, the current directory and anything else - # intended for the global Python environment - cmd = [context.env_exec_cmd, '-Im', 'ensurepip', '--upgrade', - '--default-pip'] - subprocess.check_output(cmd, stderr=subprocess.STDOUT) + self._call_new_python(context, '-m', 'ensurepip', '--upgrade', + '--default-pip', stderr=subprocess.STDOUT) def setup_scripts(self, context): """ @@ -348,11 +482,41 @@ def replace_variables(self, text, context): :param context: The information for the environment creation request being processed. """ - text = text.replace('__VENV_DIR__', context.env_dir) - text = text.replace('__VENV_NAME__', context.env_name) - text = text.replace('__VENV_PROMPT__', context.prompt) - text = text.replace('__VENV_BIN_NAME__', context.bin_name) - text = text.replace('__VENV_PYTHON__', context.env_exe) + replacements = { + '__VENV_DIR__': context.env_dir, + '__VENV_NAME__': context.env_name, + '__VENV_PROMPT__': context.prompt, + '__VENV_BIN_NAME__': context.bin_name, + '__VENV_PYTHON__': context.env_exe, + } + + def quote_ps1(s): + """ + This should satisfy PowerShell quoting rules [1], unless the quoted + string is passed directly to Windows native commands [2]. + [1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules + [2]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters + """ + s = s.replace("'", "''") + return f"'{s}'" + + def quote_bat(s): + return s + + # gh-124651: need to quote the template strings properly + quote = shlex.quote + script_path = context.script_path + if script_path.endswith('.ps1'): + quote = quote_ps1 + elif script_path.endswith('.bat'): + quote = quote_bat + else: + # fallbacks to POSIX shell compliant quote + quote = shlex.quote + + replacements = {key: quote(s) for key, s in replacements.items()} + for key, quoted in replacements.items(): + text = text.replace(key, quoted) return text def install_scripts(self, context, path): @@ -370,15 +534,22 @@ def install_scripts(self, context, path): """ binpath = context.bin_path plen = len(path) + if os.name == 'nt': + def skip_file(f): + f = os.path.normcase(f) + return (f.startswith(('python', 'venv')) + and f.endswith(('.exe', '.pdb'))) + else: + def skip_file(f): + return False for root, dirs, files in os.walk(path): - if root == path: # at top-level, remove irrelevant dirs + if root == path: # at top-level, remove irrelevant dirs for d in dirs[:]: if d not in ('common', os.name): dirs.remove(d) - continue # ignore files in top level + continue # ignore files in top level for f in files: - if (os.name == 'nt' and f.startswith('python') - and f.endswith(('.exe', '.pdb'))): + if skip_file(f): continue srcfile = os.path.join(root, f) suffix = root[plen:].split(os.sep)[2:] @@ -389,116 +560,122 @@ def install_scripts(self, context, path): if not os.path.exists(dstdir): os.makedirs(dstdir) dstfile = os.path.join(dstdir, f) + if os.name == 'nt' and srcfile.endswith(('.exe', '.pdb')): + shutil.copy2(srcfile, dstfile) + continue with open(srcfile, 'rb') as f: data = f.read() - if not srcfile.endswith(('.exe', '.pdb')): - try: - data = data.decode('utf-8') - data = self.replace_variables(data, context) - data = data.encode('utf-8') - except UnicodeError as e: - data = None - logger.warning('unable to copy script %r, ' - 'may be binary: %s', srcfile, e) - if data is not None: + try: + context.script_path = srcfile + new_data = ( + self.replace_variables(data.decode('utf-8'), context) + .encode('utf-8') + ) + except UnicodeError as e: + logger.warning('unable to copy script %r, ' + 'may be binary: %s', srcfile, e) + continue + if new_data == data: + shutil.copy2(srcfile, dstfile) + else: with open(dstfile, 'wb') as f: - f.write(data) + f.write(new_data) shutil.copymode(srcfile, dstfile) def upgrade_dependencies(self, context): logger.debug( f'Upgrading {CORE_VENV_DEPS} packages in {context.bin_path}' ) - cmd = [context.env_exec_cmd, '-m', 'pip', 'install', '--upgrade'] - cmd.extend(CORE_VENV_DEPS) - subprocess.check_call(cmd) + self._call_new_python(context, '-m', 'pip', 'install', '--upgrade', + *CORE_VENV_DEPS) def create(env_dir, system_site_packages=False, clear=False, - symlinks=False, with_pip=False, prompt=None, upgrade_deps=False): + symlinks=False, with_pip=False, prompt=None, upgrade_deps=False, + *, scm_ignore_files=frozenset()): """Create a virtual environment in a directory.""" builder = EnvBuilder(system_site_packages=system_site_packages, clear=clear, symlinks=symlinks, with_pip=with_pip, - prompt=prompt, upgrade_deps=upgrade_deps) + prompt=prompt, upgrade_deps=upgrade_deps, + scm_ignore_files=scm_ignore_files) builder.create(env_dir) + def main(args=None): - compatible = True - if sys.version_info < (3, 3): - compatible = False - elif not hasattr(sys, 'base_prefix'): - compatible = False - if not compatible: - raise ValueError('This script is only for use with Python >= 3.3') + import argparse + + parser = argparse.ArgumentParser(prog=__name__, + description='Creates virtual Python ' + 'environments in one or ' + 'more target ' + 'directories.', + epilog='Once an environment has been ' + 'created, you may wish to ' + 'activate it, e.g. by ' + 'sourcing an activate script ' + 'in its bin directory.') + parser.add_argument('dirs', metavar='ENV_DIR', nargs='+', + help='A directory to create the environment in.') + parser.add_argument('--system-site-packages', default=False, + action='store_true', dest='system_site', + help='Give the virtual environment access to the ' + 'system site-packages dir.') + if os.name == 'nt': + use_symlinks = False else: - import argparse - - parser = argparse.ArgumentParser(prog=__name__, - description='Creates virtual Python ' - 'environments in one or ' - 'more target ' - 'directories.', - epilog='Once an environment has been ' - 'created, you may wish to ' - 'activate it, e.g. by ' - 'sourcing an activate script ' - 'in its bin directory.') - parser.add_argument('dirs', metavar='ENV_DIR', nargs='+', - help='A directory to create the environment in.') - parser.add_argument('--system-site-packages', default=False, - action='store_true', dest='system_site', - help='Give the virtual environment access to the ' - 'system site-packages dir.') - if os.name == 'nt': - use_symlinks = False - else: - use_symlinks = True - group = parser.add_mutually_exclusive_group() - group.add_argument('--symlinks', default=use_symlinks, - action='store_true', dest='symlinks', - help='Try to use symlinks rather than copies, ' - 'when symlinks are not the default for ' - 'the platform.') - group.add_argument('--copies', default=not use_symlinks, - action='store_false', dest='symlinks', - help='Try to use copies rather than symlinks, ' - 'even when symlinks are the default for ' - 'the platform.') - parser.add_argument('--clear', default=False, action='store_true', - dest='clear', help='Delete the contents of the ' - 'environment directory if it ' - 'already exists, before ' - 'environment creation.') - parser.add_argument('--upgrade', default=False, action='store_true', - dest='upgrade', help='Upgrade the environment ' - 'directory to use this version ' - 'of Python, assuming Python ' - 'has been upgraded in-place.') - parser.add_argument('--without-pip', dest='with_pip', - default=True, action='store_false', - help='Skips installing or upgrading pip in the ' - 'virtual environment (pip is bootstrapped ' - 'by default)') - parser.add_argument('--prompt', - help='Provides an alternative prompt prefix for ' - 'this environment.') - parser.add_argument('--upgrade-deps', default=False, action='store_true', - dest='upgrade_deps', - help='Upgrade core dependencies: {} to the latest ' - 'version in PyPI'.format( - ' '.join(CORE_VENV_DEPS))) - options = parser.parse_args(args) - if options.upgrade and options.clear: - raise ValueError('you cannot supply --upgrade and --clear together.') - builder = EnvBuilder(system_site_packages=options.system_site, - clear=options.clear, - symlinks=options.symlinks, - upgrade=options.upgrade, - with_pip=options.with_pip, - prompt=options.prompt, - upgrade_deps=options.upgrade_deps) - for d in options.dirs: - builder.create(d) + use_symlinks = True + group = parser.add_mutually_exclusive_group() + group.add_argument('--symlinks', default=use_symlinks, + action='store_true', dest='symlinks', + help='Try to use symlinks rather than copies, ' + 'when symlinks are not the default for ' + 'the platform.') + group.add_argument('--copies', default=not use_symlinks, + action='store_false', dest='symlinks', + help='Try to use copies rather than symlinks, ' + 'even when symlinks are the default for ' + 'the platform.') + parser.add_argument('--clear', default=False, action='store_true', + dest='clear', help='Delete the contents of the ' + 'environment directory if it ' + 'already exists, before ' + 'environment creation.') + parser.add_argument('--upgrade', default=False, action='store_true', + dest='upgrade', help='Upgrade the environment ' + 'directory to use this version ' + 'of Python, assuming Python ' + 'has been upgraded in-place.') + parser.add_argument('--without-pip', dest='with_pip', + default=True, action='store_false', + help='Skips installing or upgrading pip in the ' + 'virtual environment (pip is bootstrapped ' + 'by default)') + parser.add_argument('--prompt', + help='Provides an alternative prompt prefix for ' + 'this environment.') + parser.add_argument('--upgrade-deps', default=False, action='store_true', + dest='upgrade_deps', + help=f'Upgrade core dependencies ({", ".join(CORE_VENV_DEPS)}) ' + 'to the latest version in PyPI') + parser.add_argument('--without-scm-ignore-files', dest='scm_ignore_files', + action='store_const', const=frozenset(), + default=frozenset(['git']), + help='Skips adding SCM ignore files to the environment ' + 'directory (Git is supported by default).') + options = parser.parse_args(args) + if options.upgrade and options.clear: + raise ValueError('you cannot supply --upgrade and --clear together.') + builder = EnvBuilder(system_site_packages=options.system_site, + clear=options.clear, + symlinks=options.symlinks, + upgrade=options.upgrade, + with_pip=options.with_pip, + prompt=options.prompt, + upgrade_deps=options.upgrade_deps, + scm_ignore_files=options.scm_ignore_files) + for d in options.dirs: + builder.create(d) + if __name__ == '__main__': rc = 1 diff --git a/Lib/venv/__main__.py b/Lib/venv/__main__.py index 912423e4a78..88f55439dc2 100644 --- a/Lib/venv/__main__.py +++ b/Lib/venv/__main__.py @@ -6,5 +6,5 @@ main() rc = 0 except Exception as e: - print('Error: %s' % e, file=sys.stderr) + print('Error:', e, file=sys.stderr) sys.exit(rc) diff --git a/Lib/venv/scripts/common/Activate.ps1 b/Lib/venv/scripts/common/Activate.ps1 index b49d77ba44b..16ba5290fae 100644 --- a/Lib/venv/scripts/common/Activate.ps1 +++ b/Lib/venv/scripts/common/Activate.ps1 @@ -219,6 +219,8 @@ deactivate -nondestructive # that there is an activated venv. $env:VIRTUAL_ENV = $VenvDir +$env:VIRTUAL_ENV_PROMPT = $Prompt + if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { Write-Verbose "Setting prompt to '$Prompt'" @@ -233,7 +235,6 @@ if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " _OLD_VIRTUAL_PROMPT } - $env:VIRTUAL_ENV_PROMPT = $Prompt } # Clear PYTHONHOME diff --git a/Lib/venv/scripts/common/activate b/Lib/venv/scripts/common/activate index 6fbc2b8801d..70673a265d4 100644 --- a/Lib/venv/scripts/common/activate +++ b/Lib/venv/scripts/common/activate @@ -1,5 +1,5 @@ # This file must be used with "source bin/activate" *from bash* -# you cannot run it directly +# You cannot run it directly deactivate () { # reset old environment variables @@ -14,12 +14,10 @@ deactivate () { unset _OLD_VIRTUAL_PYTHONHOME fi - # This should detect bash and zsh, which have a hash command that must - # be called to get it to forget past commands. Without forgetting - # past commands the $PATH changes we made may not be respected - if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then - hash -r 2> /dev/null - fi + # Call hash to forget past locations. Without forgetting + # past locations the $PATH changes we made may not be respected. + # See "man bash" for more details. hash is usually a builtin of your shell + hash -r 2> /dev/null if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then PS1="${_OLD_VIRTUAL_PS1:-}" @@ -38,13 +36,27 @@ deactivate () { # unset irrelevant variables deactivate nondestructive -VIRTUAL_ENV="__VENV_DIR__" -export VIRTUAL_ENV +# on Windows, a path can contain colons and backslashes and has to be converted: +case "$(uname)" in + CYGWIN*|MSYS*|MINGW*) + # transform D:\path\to\venv to /d/path/to/venv on MSYS and MINGW + # and to /cygdrive/d/path/to/venv on Cygwin + VIRTUAL_ENV=$(cygpath __VENV_DIR__) + export VIRTUAL_ENV + ;; + *) + # use the path as-is + export VIRTUAL_ENV=__VENV_DIR__ + ;; +esac _OLD_VIRTUAL_PATH="$PATH" -PATH="$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH" +PATH="$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH" export PATH +VIRTUAL_ENV_PROMPT=__VENV_PROMPT__ +export VIRTUAL_ENV_PROMPT + # unset PYTHONHOME if set # this will fail if PYTHONHOME is set to the empty string (which is bad anyway) # could use `if (set -u; : $PYTHONHOME) ;` in bash @@ -55,15 +67,10 @@ fi if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then _OLD_VIRTUAL_PS1="${PS1:-}" - PS1="__VENV_PROMPT__${PS1:-}" + PS1="("__VENV_PROMPT__") ${PS1:-}" export PS1 - VIRTUAL_ENV_PROMPT="__VENV_PROMPT__" - export VIRTUAL_ENV_PROMPT fi -# This should detect bash and zsh, which have a hash command that must -# be called to get it to forget past commands. Without forgetting +# Call hash to forget past commands. Without forgetting # past commands the $PATH changes we made may not be respected -if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then - hash -r 2> /dev/null -fi +hash -r 2> /dev/null diff --git a/Lib/venv/scripts/posix/activate.fish b/Lib/venv/scripts/common/activate.fish similarity index 76% rename from Lib/venv/scripts/posix/activate.fish rename to Lib/venv/scripts/common/activate.fish index e40a1d71489..284a7469c99 100644 --- a/Lib/venv/scripts/posix/activate.fish +++ b/Lib/venv/scripts/common/activate.fish @@ -1,5 +1,5 @@ # This file must be used with "source <venv>/bin/activate.fish" *from fish* -# (https://fishshell.com/); you cannot run it directly. +# (https://fishshell.com/). You cannot run it directly. function deactivate -d "Exit virtual environment and return to normal shell environment" # reset old environment variables @@ -13,10 +13,13 @@ function deactivate -d "Exit virtual environment and return to normal shell env end if test -n "$_OLD_FISH_PROMPT_OVERRIDE" - functions -e fish_prompt set -e _OLD_FISH_PROMPT_OVERRIDE - functions -c _old_fish_prompt fish_prompt - functions -e _old_fish_prompt + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end end set -e VIRTUAL_ENV @@ -30,10 +33,11 @@ end # Unset irrelevant variables. deactivate nondestructive -set -gx VIRTUAL_ENV "__VENV_DIR__" +set -gx VIRTUAL_ENV __VENV_DIR__ set -gx _OLD_VIRTUAL_PATH $PATH -set -gx PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__" $PATH +set -gx PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__ $PATH +set -gx VIRTUAL_ENV_PROMPT __VENV_PROMPT__ # Unset PYTHONHOME if set. if set -q PYTHONHOME @@ -53,7 +57,7 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" set -l old_status $status # Output the venv prompt; color taken from the blue of the Python logo. - printf "%s%s%s" (set_color 4B8BBE) "__VENV_PROMPT__" (set_color normal) + printf "%s(%s)%s " (set_color 4B8BBE) __VENV_PROMPT__ (set_color normal) # Restore the return status of the previous command. echo "exit $old_status" | . @@ -62,5 +66,4 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" end set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" - set -gx VIRTUAL_ENV_PROMPT "__VENV_PROMPT__" end diff --git a/Lib/venv/scripts/nt/activate.bat b/Lib/venv/scripts/nt/activate.bat index 5daa45afc9f..9ac5c20b477 100644 --- a/Lib/venv/scripts/nt/activate.bat +++ b/Lib/venv/scripts/nt/activate.bat @@ -8,15 +8,15 @@ if defined _OLD_CODEPAGE ( "%SystemRoot%\System32\chcp.com" 65001 > nul ) -set VIRTUAL_ENV=__VENV_DIR__ +set "VIRTUAL_ENV=__VENV_DIR__" if not defined PROMPT set PROMPT=$P$G if defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT% if defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME% -set _OLD_VIRTUAL_PROMPT=%PROMPT% -set PROMPT=__VENV_PROMPT__%PROMPT% +set "_OLD_VIRTUAL_PROMPT=%PROMPT%" +set "PROMPT=(__VENV_PROMPT__) %PROMPT%" if defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME% set PYTHONHOME= @@ -24,8 +24,8 @@ set PYTHONHOME= if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH% if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH% -set PATH=%VIRTUAL_ENV%\__VENV_BIN_NAME__;%PATH% -set VIRTUAL_ENV_PROMPT=__VENV_PROMPT__ +set "PATH=%VIRTUAL_ENV%\__VENV_BIN_NAME__;%PATH%" +set "VIRTUAL_ENV_PROMPT=__VENV_PROMPT__" :END if defined _OLD_CODEPAGE ( diff --git a/Lib/venv/scripts/posix/activate.csh b/Lib/venv/scripts/posix/activate.csh index d6f697c55ed..2a3fa835476 100644 --- a/Lib/venv/scripts/posix/activate.csh +++ b/Lib/venv/scripts/posix/activate.csh @@ -1,5 +1,6 @@ # This file must be used with "source bin/activate.csh" *from csh*. # You cannot run it directly. + # Created by Davide Di Blasi <davidedb@gmail.com>. # Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com> @@ -8,17 +9,17 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA # Unset irrelevant variables. deactivate nondestructive -setenv VIRTUAL_ENV "__VENV_DIR__" +setenv VIRTUAL_ENV __VENV_DIR__ set _OLD_VIRTUAL_PATH="$PATH" -setenv PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH" +setenv PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH" +setenv VIRTUAL_ENV_PROMPT __VENV_PROMPT__ set _OLD_VIRTUAL_PROMPT="$prompt" if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then - set prompt = "__VENV_PROMPT__$prompt" - setenv VIRTUAL_ENV_PROMPT "__VENV_PROMPT__" + set prompt = "("__VENV_PROMPT__") $prompt:q" endif alias pydoc python -m pydoc diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 52ecf927d5c..f9bd2b59456 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -164,11 +164,70 @@ mod sys { #[pyattr] fn _base_executable(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; + // First check __PYVENV_LAUNCHER__ environment variable if let Ok(var) = env::var("__PYVENV_LAUNCHER__") { - ctx.new_str(var).into() - } else { - executable(vm) + return ctx.new_str(var).into(); + } + + // Try to detect if we're running from a venv by looking for pyvenv.cfg + if let Some(base_exe) = get_venv_base_executable() { + return ctx.new_str(base_exe).into(); } + + executable(vm) + } + + /// Try to find base executable from pyvenv.cfg (see getpath.py) + fn get_venv_base_executable() -> Option<String> { + // TODO: This is a minimal implementation of getpath.py + // To fully support all cases, `getpath.py` should be placed in @crates/vm/Lib/python_builtins/ + + // Get current executable path + #[cfg(not(target_arch = "wasm32"))] + let exe_path = { + let exec_arg = env::args_os().next()?; + which::which(exec_arg).ok()? + }; + #[cfg(target_arch = "wasm32")] + let exe_path = { + let exec_arg = env::args().next()?; + path::PathBuf::from(exec_arg) + }; + + let exe_dir = exe_path.parent()?; + let exe_name = exe_path.file_name()?; + + // Look for pyvenv.cfg in parent directory (typical venv layout: venv/bin/python) + let venv_dir = exe_dir.parent()?; + let pyvenv_cfg = venv_dir.join("pyvenv.cfg"); + + if !pyvenv_cfg.exists() { + return None; + } + + // Parse pyvenv.cfg and extract home directory + let content = std::fs::read_to_string(&pyvenv_cfg).ok()?; + + for line in content.lines() { + if let Some((key, value)) = line.split_once('=') { + let key = key.trim().to_lowercase(); + let value = value.trim(); + + if key == "home" { + // First try to resolve symlinks (getpath.py line 373-377) + if let Ok(resolved) = std::fs::canonicalize(&exe_path) + && resolved != exe_path + { + return Some(resolved.to_string_lossy().into_owned()); + } + // Fallback: home_dir + executable_name (getpath.py line 381) + let base_exe = path::Path::new(value).join(exe_name); + return Some(base_exe.to_string_lossy().into_owned()); + } + } + } + + None } #[pyattr] From 7bfa5d9ceda6c5a3fb402dcf89d37b2c3e47f9f4 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 20 Dec 2025 18:31:00 +0900 Subject: [PATCH 530/819] Buffer LongDouble + ndim (#6460) * fix fix_test * ndim buffer --- crates/vm/src/buffer.rs | 20 +++++++++++++++++--- crates/vm/src/protocol/buffer.rs | 11 +++++++++-- scripts/fix_test.py | 6 +++++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/crates/vm/src/buffer.rs b/crates/vm/src/buffer.rs index eeb6a676542..5c67f87521d 100644 --- a/crates/vm/src/buffer.rs +++ b/crates/vm/src/buffer.rs @@ -88,7 +88,9 @@ pub(crate) enum FormatType { Half = b'e', Float = b'f', Double = b'd', + LongDouble = b'g', VoidP = b'P', + PyObject = b'O', } impl fmt::Debug for FormatType { @@ -148,7 +150,9 @@ impl FormatType { Half => nonnative_info!(f16, $end), Float => nonnative_info!(f32, $end), Double => nonnative_info!(f64, $end), - _ => unreachable!(), // size_t or void* + LongDouble => nonnative_info!(f64, $end), // long double same as double + PyObject => nonnative_info!(usize, $end), // pointer size + _ => unreachable!(), // size_t or void* } }}; } @@ -183,7 +187,9 @@ impl FormatType { Half => native_info!(f16), Float => native_info!(raw::c_float), Double => native_info!(raw::c_double), + LongDouble => native_info!(raw::c_double), // long double same as double for now VoidP => native_info!(*mut raw::c_void), + PyObject => native_info!(*mut raw::c_void), // pointer to PyObject }, Endianness::Big => match_nonnative!(self, BigEndian), Endianness::Little => match_nonnative!(self, LittleEndian), @@ -306,8 +312,16 @@ impl FormatCode { continue; } - if c == b'{' || c == b'}' { - // Skip standalone braces (pointer targets, etc.) + if c == b'{' + || c == b'}' + || c == b'&' + || c == b'<' + || c == b'>' + || c == b'@' + || c == b'=' + || c == b'!' + { + // Skip standalone braces (pointer targets, etc.), pointer prefix, and nested endianness markers continue; } diff --git a/crates/vm/src/protocol/buffer.rs b/crates/vm/src/protocol/buffer.rs index 948ec763dc6..0a34af59080 100644 --- a/crates/vm/src/protocol/buffer.rs +++ b/crates/vm/src/protocol/buffer.rs @@ -201,16 +201,23 @@ impl BufferDescriptor { #[cfg(debug_assertions)] pub fn validate(self) -> Self { - assert!(self.itemsize != 0); // ndim=0 is valid for scalar types (e.g., ctypes Structure) if self.ndim() == 0 { + // Empty structures (len=0) can have itemsize=0 + if self.len > 0 { + assert!(self.itemsize != 0); + } assert!(self.itemsize == self.len); } else { let mut shape_product = 1; + let has_zero_dim = self.dim_desc.iter().any(|(s, _, _)| *s == 0); for (shape, stride, suboffset) in self.dim_desc.iter().cloned() { shape_product *= shape; assert!(suboffset >= 0); - assert!(stride != 0); + // For empty arrays (any dimension is 0), strides can be 0 + if !has_zero_dim { + assert!(stride != 0); + } } assert!(shape_product * self.itemsize == self.len); } diff --git a/scripts/fix_test.py b/scripts/fix_test.py index 9716bd0b008..53b10d63834 100644 --- a/scripts/fix_test.py +++ b/scripts/fix_test.py @@ -159,7 +159,11 @@ def run_test(test_name): if not test_path.exists(): print(f"Error: File not found: {test_path}") sys.exit(1) - test_name = test_path.stem + # Detect package tests (e.g., test_ctypes/test_random_things.py) + if test_path.parent.name.startswith("test_"): + test_name = f"{test_path.parent.name}.{test_path.stem}" + else: + test_name = test_path.stem tests = run_test(test_name) f = test_path.read_text(encoding="utf-8") From 6660170bf838b239b74a5c0fe9dab7615f3c7e41 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 20 Dec 2025 22:06:22 +0900 Subject: [PATCH 531/819] Share more ssl consts and fix openssl (#6462) * Reuse SSL error names * Share ssl error codes * fix ssl * fix openssl --- crates/stdlib/src/openssl.rs | 429 +++++++++++++++++++++++--------- crates/stdlib/src/ssl.rs | 22 -- crates/stdlib/src/ssl/compat.rs | 15 +- crates/stdlib/src/ssl/error.rs | 24 +- 4 files changed, 347 insertions(+), 143 deletions(-) diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index e07ad552f17..b6d5f5c2035 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -53,11 +53,10 @@ cfg_if::cfg_if! { mod _ssl { use super::{bio, probe}; - // Import error types used in this module (others are exposed via pymodule(with(...))) + // Import error types and helpers used in this module (others are exposed via pymodule(with(...))) use super::ssl_error::{ - PySSLCertVerificationError as PySslCertVerificationError, PySSLEOFError as PySslEOFError, - PySSLError as PySslError, PySSLWantReadError as PySslWantReadError, - PySSLWantWriteError as PySslWantWriteError, + PySSLCertVerificationError, PySSLError, create_ssl_eof_error, create_ssl_want_read_error, + create_ssl_want_write_error, }; use crate::{ common::lock::{ @@ -249,8 +248,6 @@ mod _ssl { #[pyattr] const VERIFY_DEFAULT: u32 = 0; #[pyattr] - const SSL_ERROR_EOF: u32 = 8; // custom for python - #[pyattr] const HAS_SNI: bool = true; #[pyattr] const HAS_ECDH: bool = true; @@ -709,21 +706,124 @@ mod _ssl { } } + // OpenSSL record type constants for msg_callback + const SSL3_RT_CHANGE_CIPHER_SPEC: i32 = 20; + const SSL3_RT_ALERT: i32 = 21; + const SSL3_RT_HANDSHAKE: i32 = 22; + const SSL3_RT_HEADER: i32 = 256; + const SSL3_RT_INNER_CONTENT_TYPE: i32 = 257; + // Special value for change cipher spec (CPython compatibility) + const SSL3_MT_CHANGE_CIPHER_SPEC: i32 = 0x0101; + // Message callback function called by OpenSSL - // NOTE: This callback is intentionally a no-op to avoid deadlocks. - // The msg_callback can be called during various SSL operations (read, write, handshake), - // and invoking Python code from within these operations can cause deadlocks - // (see CPython bpo-43577). A proper implementation would require careful lock ordering. + // Called during SSL operations to report protocol messages. + // debughelpers.c:_PySSL_msg_callback unsafe extern "C" fn _msg_callback( - _write_p: libc::c_int, - _version: libc::c_int, - _content_type: libc::c_int, - _buf: *const libc::c_void, - _len: usize, - _ssl_ptr: *mut sys::SSL, + write_p: libc::c_int, + mut version: libc::c_int, + content_type: libc::c_int, + buf: *const libc::c_void, + len: usize, + ssl_ptr: *mut sys::SSL, _arg: *mut libc::c_void, ) { - // Intentionally empty to avoid deadlocks + if ssl_ptr.is_null() { + return; + } + + unsafe { + // Get SSL socket from SSL_get_ex_data (index 0) + let ssl_socket_ptr = sys::SSL_get_ex_data(ssl_ptr, 0); + if ssl_socket_ptr.is_null() { + return; + } + + // ssl_socket_ptr is a pointer to Py<PySslSocket>, set in _wrap_socket/_wrap_bio + let ssl_socket: &Py<PySslSocket> = &*(ssl_socket_ptr as *const Py<PySslSocket>); + + // Get the callback from the context + let callback_opt = ssl_socket.ctx.read().msg_callback.lock().clone(); + let Some(callback) = callback_opt else { + return; + }; + + // Get VM from thread-local storage (set by HandshakeVmGuard in do_handshake) + let Some(vm_ptr) = HANDSHAKE_VM.with(|cell| cell.get()) else { + // VM not available - this shouldn't happen during handshake + return; + }; + let vm = &*vm_ptr; + + // Get SSL socket owner object + let ssl_socket_obj = ssl_socket + .owner + .read() + .as_ref() + .and_then(|weak| weak.upgrade()) + .unwrap_or_else(|| vm.ctx.none()); + + // Create the message bytes + let buf_slice = std::slice::from_raw_parts(buf as *const u8, len); + let msg_bytes = vm.ctx.new_bytes(buf_slice.to_vec()); + + // Determine direction string + let direction_str = if write_p != 0 { "write" } else { "read" }; + + // Calculate msg_type based on content_type (debughelpers.c behavior) + let msg_type = match content_type { + SSL3_RT_CHANGE_CIPHER_SPEC => SSL3_MT_CHANGE_CIPHER_SPEC, + SSL3_RT_ALERT => { + // byte 1 is alert type + if len >= 2 { buf_slice[1] as i32 } else { -1 } + } + SSL3_RT_HANDSHAKE => { + // byte 0 is handshake type + if !buf_slice.is_empty() { + buf_slice[0] as i32 + } else { + -1 + } + } + SSL3_RT_HEADER => { + // Frame header: version in bytes 1..2, type in byte 0 + if len >= 3 { + version = ((buf_slice[1] as i32) << 8) | (buf_slice[2] as i32); + buf_slice[0] as i32 + } else { + -1 + } + } + SSL3_RT_INNER_CONTENT_TYPE => { + // Inner content type in byte 0 + if !buf_slice.is_empty() { + buf_slice[0] as i32 + } else { + -1 + } + } + _ => -1, + }; + + // Call the Python callback + // Signature: callback(conn, direction, version, content_type, msg_type, data) + match callback.call( + ( + ssl_socket_obj, + vm.ctx.new_str(direction_str), + vm.ctx.new_int(version), + vm.ctx.new_int(content_type), + vm.ctx.new_int(msg_type), + msg_bytes, + ), + vm, + ) { + Ok(_) => {} + Err(exc) => { + // Log the exception but don't propagate it + vm.run_unraisable(exc, None, vm.ctx.none()); + } + } + } } #[pyfunction(name = "RAND_pseudo_bytes")] @@ -794,6 +894,7 @@ mod _ssl { SslVerifyMode::NONE }); + // Start with OP_ALL but remove options that CPython doesn't include by default let mut options = SslOptions::ALL & !SslOptions::DONT_INSERT_EMPTY_FRAGMENTS; if proto != SslVersion::Ssl2 { options |= SslOptions::NO_SSLV2; @@ -807,6 +908,8 @@ mod _ssl { options |= SslOptions::SINGLE_ECDH_USE; options |= SslOptions::ENABLE_MIDDLEBOX_COMPAT; builder.set_options(options); + // Remove NO_TLSv1 and NO_TLSv1_1 which newer OpenSSL adds to OP_ALL + builder.clear_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1); let mode = ssl::SslMode::ACCEPT_MOVING_WRITE_BUFFER | ssl::SslMode::AUTO_RETRY; builder.set_mode(mode); @@ -1242,7 +1345,7 @@ mod _ssl { if let Some(cadata) = args.cadata { let certs = match cadata { Either::A(s) => { - if !s.is_ascii() { + if !s.as_str().is_ascii() { return Err(invalid_cadata(vm)); } X509::stack_from_pem(s.as_bytes()) @@ -1261,6 +1364,29 @@ mod _ssl { if args.cafile.is_some() || args.capath.is_some() { let cafile_path = args.cafile.map(|p| p.to_path_buf(vm)).transpose()?; let capath_path = args.capath.map(|p| p.to_path_buf(vm)).transpose()?; + // Check file/directory existence before calling OpenSSL to get proper errno + if let Some(ref path) = cafile_path { + if !path.exists() { + return Err(vm + .new_os_subtype_error( + vm.ctx.exceptions.file_not_found_error.to_owned(), + Some(libc::ENOENT), + format!("No such file or directory: '{}'", path.display()), + ) + .upcast()); + } + } + if let Some(ref path) = capath_path { + if !path.exists() { + return Err(vm + .new_os_subtype_error( + vm.ctx.exceptions.file_not_found_error.to_owned(), + Some(libc::ENOENT), + format!("No such file or directory: '{}'", path.display()), + ) + .upcast()); + } + } ctx.load_verify_locations(cafile_path.as_deref(), capath_path.as_deref()) .map_err(|e| convert_openssl_error(vm, e))?; } @@ -1387,7 +1513,7 @@ mod _ssl { std::io::ErrorKind::NotFound => vm .new_os_subtype_error( vm.ctx.exceptions.file_not_found_error.to_owned(), - None, + Some(libc::ENOENT), e.to_string(), ) .upcast(), @@ -1559,6 +1685,27 @@ mod _ssl { let mut ctx = self.builder(); let key_path = keyfile.map(|path| path.to_path_buf(vm)).transpose()?; let cert_path = certfile.to_path_buf(vm)?; + // Check file existence before calling OpenSSL to get proper errno + if !cert_path.exists() { + return Err(vm + .new_os_subtype_error( + vm.ctx.exceptions.file_not_found_error.to_owned(), + Some(libc::ENOENT), + format!("No such file or directory: '{}'", cert_path.display()), + ) + .upcast()); + } + if let Some(ref kp) = key_path { + if !kp.exists() { + return Err(vm + .new_os_subtype_error( + vm.ctx.exceptions.file_not_found_error.to_owned(), + Some(libc::ENOENT), + format!("No such file or directory: '{}'", kp.display()), + ) + .upcast()); + } + } ctx.set_certificate_chain_file(&cert_path) .and_then(|()| { ctx.set_private_key_file( @@ -1623,7 +1770,7 @@ mod _ssl { )); } if hostname_str.contains('\0') { - return Err(vm.new_value_error("embedded null byte in server_hostname")); + return Err(vm.new_type_error("embedded null character")); } let ip = hostname_str.parse::<std::net::IpAddr>(); if ip.is_err() { @@ -1704,12 +1851,19 @@ mod _ssl { // Check if SNI callback is configured (minimize lock time) let has_sni_callback = zelf.sni_callback.lock().is_some(); - // Set SNI callback data if needed (after releasing the lock) - if has_sni_callback { - let ssl_socket_weak = py_ref.as_object().downgrade(None, vm)?; - unsafe { - let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); + // Set up ex_data for callbacks + unsafe { + let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); + + // Store ssl_socket pointer in index 0 for msg_callback (like CPython's SSL_set_app_data) + // This is safe because ssl_socket owns the SSL object and outlives it + // We store a pointer to Py<PySslSocket>, which msg_callback can dereference + let py_ptr: *const Py<PySslSocket> = &*py_ref; + sys::SSL_set_ex_data(ssl_ptr, 0, py_ptr as *mut _); + // Set SNI callback data if needed + if has_sni_callback { + let ssl_socket_weak = py_ref.as_object().downgrade(None, vm)?; // Store callback data in SSL ex_data - use weak reference to avoid cycle let callback_data = Box::new(SniCallbackData { ssl_context: zelf.clone(), @@ -1765,12 +1919,19 @@ mod _ssl { // Check if SNI callback is configured (minimize lock time) let has_sni_callback = zelf.sni_callback.lock().is_some(); - // Set SNI callback data if needed (after releasing the lock) - if has_sni_callback { - let ssl_socket_weak = py_ref.as_object().downgrade(None, vm)?; - unsafe { - let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); + // Set up ex_data for callbacks + unsafe { + let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); + // Store ssl_socket pointer in index 0 for msg_callback (like CPython's SSL_set_app_data) + // This is safe because ssl_socket owns the SSL object and outlives it + // We store a pointer to Py<PySslSocket>, which msg_callback can dereference + let py_ptr: *const Py<PySslSocket> = &*py_ref; + sys::SSL_set_ex_data(ssl_ptr, 0, py_ptr as *mut _); + + // Set SNI callback data if needed + if has_sni_callback { + let ssl_socket_weak = py_ref.as_object().downgrade(None, vm)?; // Store callback data in SSL ex_data - use weak reference to avoid cycle let callback_data = Box::new(SniCallbackData { ssl_context: zelf.clone(), @@ -1866,12 +2027,14 @@ mod _ssl { Some(s) => s, None => return SelectRet::Closed, }; - let deadline = match &deadline { + // For blocking sockets without timeout, call sock_select with None timeout + // to actually block waiting for data instead of busy-looping + let timeout = match &deadline { Ok(deadline) => match deadline.checked_duration_since(Instant::now()) { - Some(deadline) => deadline, + Some(d) => Some(d), None => return SelectRet::TimedOut, }, - Err(true) => return SelectRet::IsBlocking, + Err(true) => None, // Blocking: no timeout, wait indefinitely Err(false) => return SelectRet::Nonblocking, }; let res = socket::sock_select( @@ -1880,7 +2043,7 @@ mod _ssl { SslNeeds::Read => socket::SelectKind::Read, SslNeeds::Write => socket::SelectKind::Write, }, - Some(deadline), + timeout, ); match res { Ok(true) => SelectRet::TimedOut, @@ -2017,6 +2180,14 @@ mod _ssl { SslConnection::Bio(stream) => stream.get_shutdown(), } } + + // Check if incoming BIO has EOF (for BIO mode only) + fn is_bio_eof(&self) -> bool { + match self { + SslConnection::Socket(_) => false, + SslConnection::Bio(stream) => stream.get_ref().inbio.eof_written.load(), + } + } } #[pyattr] @@ -2172,17 +2343,21 @@ mod _ssl { #[pymethod] fn version(&self) -> Option<&'static str> { - let v = self.connection.read().ssl().version_str(); + // Use thread-local SSL pointer during handshake to avoid deadlock + let ssl_ptr = get_ssl_ptr_for_context_change(&self.connection); + // Return None if handshake is not complete (CPython behavior) + if unsafe { sys::SSL_is_init_finished(ssl_ptr) } == 0 { + return None; + } + let v = unsafe { ssl::SslRef::from_ptr(ssl_ptr).version_str() }; if v == "unknown" { None } else { Some(v) } } #[pymethod] fn cipher(&self) -> Option<CipherTuple> { - self.connection - .read() - .ssl() - .current_cipher() - .map(cipher_to_tuple) + // Use thread-local SSL pointer during handshake to avoid deadlock + let ssl_ptr = get_ssl_ptr_for_context_change(&self.connection); + unsafe { ssl::SslRef::from_ptr(ssl_ptr).current_cipher() }.map(cipher_to_tuple) } #[pymethod] @@ -2195,14 +2370,15 @@ mod _ssl { fn shared_ciphers(&self, vm: &VirtualMachine) -> Option<PyListRef> { #[cfg(ossl110)] { - let stream = self.connection.read(); + // Use thread-local SSL pointer during handshake to avoid deadlock + let ssl_ptr = get_ssl_ptr_for_context_change(&self.connection); unsafe { - let server_ciphers = SSL_get_ciphers(stream.ssl().as_ptr()); + let server_ciphers = SSL_get_ciphers(ssl_ptr); if server_ciphers.is_null() { return None; } - let client_ciphers = SSL_get_client_ciphers(stream.ssl().as_ptr()); + let client_ciphers = SSL_get_client_ciphers(ssl_ptr); if client_ciphers.is_null() { return None; } @@ -2258,12 +2434,13 @@ mod _ssl { fn selected_alpn_protocol(&self) -> Option<String> { #[cfg(ossl102)] { - let stream = self.connection.read(); + // Use thread-local SSL pointer during handshake to avoid deadlock + let ssl_ptr = get_ssl_ptr_for_context_change(&self.connection); unsafe { let mut out: *const libc::c_uchar = std::ptr::null(); let mut outlen: libc::c_uint = 0; - sys::SSL_get0_alpn_selected(stream.ssl().as_ptr(), &mut out, &mut outlen); + sys::SSL_get0_alpn_selected(ssl_ptr, &mut out, &mut outlen); if out.is_null() { None @@ -2343,42 +2520,53 @@ mod _ssl { } #[pymethod] - fn shutdown(&self, vm: &VirtualMachine) -> PyResult<PyRef<PySocket>> { + fn shutdown(&self, vm: &VirtualMachine) -> PyResult<Option<PyRef<PySocket>>> { let stream = self.connection.read(); - - // BIO mode doesn't have an underlying socket - if stream.is_bio() { - return Err(vm.new_not_implemented_error( - "shutdown() is not supported for BIO-based SSL objects".to_owned(), - )); - } - let ssl_ptr = stream.ssl().as_ptr(); - // Perform SSL shutdown - let ret = unsafe { sys::SSL_shutdown(ssl_ptr) }; + // Perform SSL shutdown - may need to be called twice: + // 1st call: sends close-notify, returns 0 + // 2nd call: reads peer's close-notify, returns 1 + let mut ret = unsafe { sys::SSL_shutdown(ssl_ptr) }; + + // If ret == 0, try once more to complete the bidirectional shutdown + // This handles the case where peer's close-notify is already available + if ret == 0 { + ret = unsafe { sys::SSL_shutdown(ssl_ptr) }; + } if ret < 0 { // Error occurred let err = unsafe { sys::SSL_get_error(ssl_ptr, ret) }; - if err == sys::SSL_ERROR_WANT_READ || err == sys::SSL_ERROR_WANT_WRITE { - // Non-blocking would block - this is okay for shutdown - // Return the underlying socket + if err == sys::SSL_ERROR_WANT_READ { + return Err(create_ssl_want_read_error(vm).upcast()); + } else if err == sys::SSL_ERROR_WANT_WRITE { + return Err(create_ssl_want_write_error(vm).upcast()); } else { return Err(new_ssl_error( vm, format!("SSL shutdown failed: error code {}", err), )); } + } else if ret == 0 { + // Still waiting for peer's close-notify after retry + // In BIO mode, raise SSLWantReadError + if stream.is_bio() { + return Err(create_ssl_want_read_error(vm).upcast()); + } + } + + // BIO mode doesn't have an underlying socket to return + if stream.is_bio() { + return Ok(None); } - // Return the underlying socket - // Get the socket from the stream (SocketStream wraps PyRef<PySocket>) + // Return the underlying socket for socket mode let socket = stream .get_ref() .expect("unwrap() called on bio mode; should only be called in socket mode"); - Ok(socket.0.clone()) + Ok(Some(socket.0.clone())) } #[cfg(osslconf = "OPENSSL_NO_COMP")] @@ -2389,8 +2577,9 @@ mod _ssl { #[cfg(not(osslconf = "OPENSSL_NO_COMP"))] #[pymethod] fn compression(&self) -> Option<&'static str> { - let stream = self.connection.read(); - let comp_method = unsafe { sys::SSL_get_current_compression(stream.ssl().as_ptr()) }; + // Use thread-local SSL pointer during handshake to avoid deadlock + let ssl_ptr = get_ssl_ptr_for_context_change(&self.connection); + let comp_method = unsafe { sys::SSL_get_current_compression(ssl_ptr) }; if comp_method.is_null() { return None; } @@ -2416,7 +2605,7 @@ mod _ssl { let result = stream.do_handshake().map_err(|e| { let exc = convert_ssl_error(vm, e); // If it's a cert verification error, set verify info - if exc.class().is(PySslCertVerificationError::class(&vm.ctx)) { + if exc.class().is(PySSLCertVerificationError::class(&vm.ctx)) { set_verify_error_info(&exc, ssl_ptr, vm); } exc @@ -2473,7 +2662,7 @@ mod _ssl { } let exc = convert_ssl_error(vm, err); // If it's a cert verification error, set verify info - if exc.class().is(PySslCertVerificationError::class(&vm.ctx)) { + if exc.class().is(PySSLCertVerificationError::class(&vm.ctx)) { set_verify_error_info(&exc, ssl_ptr, vm); } // Clean up SNI ex_data before returning error @@ -2603,19 +2792,41 @@ mod _ssl { #[pygetset] fn session_reused(&self) -> bool { - let stream = self.connection.read(); - unsafe { sys::SSL_session_reused(stream.ssl().as_ptr()) != 0 } + // Use thread-local SSL pointer during handshake to avoid deadlock + let ssl_ptr = get_ssl_ptr_for_context_change(&self.connection); + unsafe { sys::SSL_session_reused(ssl_ptr) != 0 } } #[pymethod] fn read( &self, - n: usize, + n: isize, buffer: OptionalArg<ArgMemoryBuffer>, vm: &VirtualMachine, ) -> PyResult { + // Handle negative n: + // - If buffer is None and n < 0: raise ValueError + // - If buffer is present and n <= 0: use buffer length + // This matches _ssl__SSLSocket_read_impl in CPython + let read_len: usize = match &buffer { + OptionalArg::Present(buf) => { + let buf_len = buf.borrow_buf_mut().len(); + if n <= 0 || (n as usize) > buf_len { + buf_len + } else { + n as usize + } + } + OptionalArg::Missing => { + if n < 0 { + return Err(vm.new_value_error("size should not be negative".to_owned())); + } + n as usize + } + }; + // Special case: reading 0 bytes should return empty bytes immediately - if n == 0 { + if read_len == 0 { return if buffer.is_present() { Ok(vm.ctx.new_int(0).into()) } else { @@ -2627,13 +2838,13 @@ mod _ssl { let mut inner_buffer = if let OptionalArg::Present(buffer) = &buffer { Either::A(buffer.borrow_buf_mut()) } else { - Either::B(vec![0u8; n]) + Either::B(vec![0u8; read_len]) }; let buf = match &mut inner_buffer { Either::A(b) => &mut **b, Either::B(b) => b.as_mut_slice(), }; - let buf = match buf.get_mut(..n) { + let buf = match buf.get_mut(..read_len) { Some(b) => b, None => buf, }; @@ -2642,7 +2853,18 @@ mod _ssl { let count = if stream.is_bio() { match stream.ssl_read(buf) { Ok(count) => count, - Err(e) => return Err(convert_ssl_error(vm, e)), + Err(e) => { + // Handle ZERO_RETURN (EOF) - raise SSLEOFError + if e.code() == ssl::ErrorCode::ZERO_RETURN { + return Err(create_ssl_eof_error(vm).upcast()); + } + // If WANT_READ and the incoming BIO has EOF written, + // this is an unexpected EOF (transport closed without TLS close_notify) + if e.code() == ssl::ErrorCode::WANT_READ && stream.is_bio_eof() { + return Err(create_ssl_eof_error(vm).upcast()); + } + return Err(convert_ssl_error(vm, e)); + } } } else { // Socket mode: handle timeout and blocking @@ -3051,22 +3273,10 @@ mod _ssl { let len = size.unwrap_or(-1); let len = if len < 0 || len > avail { avail } else { len }; - // Check if EOF has been written and no data available - // This matches CPython's behavior where read() returns b'' when EOF is set - if len == 0 && self.eof_written.load() { - return Ok(Vec::new()); - } - + // When no data available, return empty bytes (CPython behavior) + // CPython returns empty bytes directly without calling BIO_read() if len == 0 { - // No data available and no EOF - would block - // Call BIO_read() to get the proper error (SSL_ERROR_WANT_READ) - let mut test_buf = [0u8; 1]; - let nbytes = sys::BIO_read(self.bio, test_buf.as_mut_ptr() as *mut _, 1); - if nbytes < 0 { - return Err(convert_openssl_error(vm, ErrorStack::get())); - } - // Shouldn't reach here, but if we do, return what we got - return Ok(test_buf[..nbytes as usize].to_vec()); + return Ok(Vec::new()); } let mut buf = vec![0u8; len as usize]; @@ -3175,7 +3385,7 @@ mod _ssl { /// Helper function to create SSL error with proper OSError subtype handling fn new_ssl_error(vm: &VirtualMachine, msg: impl ToString) -> PyBaseExceptionRef { - vm.new_os_subtype_error(PySslError::class(&vm.ctx).to_owned(), None, msg.to_string()) + vm.new_os_subtype_error(PySSLError::class(&vm.ctx).to_owned(), None, msg.to_string()) .upcast() } @@ -3235,9 +3445,9 @@ mod _ssl { // Use SSLCertVerificationError for certificate verification failures let cls = if is_cert_verify_error { - PySslCertVerificationError::class(&vm.ctx).to_owned() + PySSLCertVerificationError::class(&vm.ctx).to_owned() } else { - PySslError::class(&vm.ctx).to_owned() + PySSLError::class(&vm.ctx).to_owned() }; // Build message @@ -3278,7 +3488,7 @@ mod _ssl { ) } None => { - let cls = PySslError::class(&vm.ctx).to_owned(); + let cls = PySSLError::class(&vm.ctx).to_owned(); vm.new_os_subtype_error(cls, None, "unknown SSL error") .upcast() } @@ -3317,27 +3527,18 @@ mod _ssl { ) -> PyBaseExceptionRef { let e = e.borrow(); let (cls, msg) = match e.code() { - ssl::ErrorCode::WANT_READ => ( - PySslWantReadError::class(&vm.ctx).to_owned(), - "The operation did not complete (read)", - ), - ssl::ErrorCode::WANT_WRITE => ( - PySslWantWriteError::class(&vm.ctx).to_owned(), - "The operation did not complete (write)", - ), + ssl::ErrorCode::WANT_READ => { + return create_ssl_want_read_error(vm).upcast(); + } + ssl::ErrorCode::WANT_WRITE => { + return create_ssl_want_write_error(vm).upcast(); + } ssl::ErrorCode::SYSCALL => match e.io_error() { Some(io_err) => return io_err.to_pyexception(vm), // When no I/O error and OpenSSL error queue is empty, // this is an EOF in violation of protocol -> SSLEOFError - // Need to set args[0] = SSL_ERROR_EOF for suppress_ragged_eofs check None => { - return vm - .new_os_subtype_error( - PySslEOFError::class(&vm.ctx).to_owned(), - Some(SSL_ERROR_EOF as i32), - "EOF occurred in violation of protocol", - ) - .upcast(); + return create_ssl_eof_error(vm).upcast(); } }, ssl::ErrorCode::SSL => { @@ -3350,24 +3551,18 @@ mod _ssl { let reason = sys::ERR_GET_REASON(err_code); let lib = sys::ERR_GET_LIB(err_code); if lib == ERR_LIB_SSL && reason == SSL_R_UNEXPECTED_EOF_WHILE_READING { - return vm - .new_os_subtype_error( - PySslEOFError::class(&vm.ctx).to_owned(), - Some(SSL_ERROR_EOF as i32), - "EOF occurred in violation of protocol", - ) - .upcast(); + return create_ssl_eof_error(vm).upcast(); } } return convert_openssl_error(vm, ssl_err.clone()); } ( - PySslError::class(&vm.ctx).to_owned(), + PySSLError::class(&vm.ctx).to_owned(), "A failure in the SSL library occurred", ) } _ => ( - PySslError::class(&vm.ctx).to_owned(), + PySSLError::class(&vm.ctx).to_owned(), "A failure in the SSL library occurred", ), }; diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 992b32e00ea..bf25260ed3a 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -207,28 +207,6 @@ mod _ssl { #[pyattr] const OP_ALL: i32 = 0x00000BFB; // Combined "safe" options (reduced for i32, excluding OP_LEGACY_SERVER_CONNECT for OpenSSL 3.0.0+ compatibility) - // Error types - #[pyattr] - const SSL_ERROR_NONE: i32 = 0; - #[pyattr] - const SSL_ERROR_SSL: i32 = 1; - #[pyattr] - const SSL_ERROR_WANT_READ: i32 = 2; - #[pyattr] - const SSL_ERROR_WANT_WRITE: i32 = 3; - #[pyattr] - const SSL_ERROR_WANT_X509_LOOKUP: i32 = 4; - #[pyattr] - const SSL_ERROR_SYSCALL: i32 = 5; - #[pyattr] - const SSL_ERROR_ZERO_RETURN: i32 = 6; - #[pyattr] - const SSL_ERROR_WANT_CONNECT: i32 = 7; - #[pyattr] - const SSL_ERROR_EOF: i32 = 8; - #[pyattr] - const SSL_ERROR_INVALID_ERROR_CODE: i32 = 10; - // Alert types (matching _TLSAlertType enum) #[pyattr] const ALERT_DESCRIPTION_CLOSE_NOTIFY: i32 = 0; diff --git a/crates/stdlib/src/ssl/compat.rs b/crates/stdlib/src/ssl/compat.rs index ab3c81b7a4e..cd927a0e410 100644 --- a/crates/stdlib/src/ssl/compat.rs +++ b/crates/stdlib/src/ssl/compat.rs @@ -1397,8 +1397,21 @@ pub(super) fn ssl_read( return Ok(n); } - // No plaintext available and cannot read more TLS records + // No plaintext available and rustls doesn't want to read more TLS records if !needs_more_tls { + // Check if connection needs to write data first (e.g., TLS key update, renegotiation) + // This mirrors the handshake logic which checks both wants_read() and wants_write() + if conn.wants_write() && !is_bio { + // Flush pending TLS data before continuing + let tls_data = ssl_write_tls_records(conn)?; + if !tls_data.is_empty() { + socket.sock_send(tls_data, vm).map_err(SslError::Py)?; + } + // After flushing, rustls may want to read again - continue loop + continue; + } + + // BIO mode: check for EOF if is_bio && let Some(bio_obj) = socket.incoming_bio() { let is_eof = bio_obj .get_attr("eof", vm) diff --git a/crates/stdlib/src/ssl/error.rs b/crates/stdlib/src/ssl/error.rs index e31683ec72d..bef9ba513d7 100644 --- a/crates/stdlib/src/ssl/error.rs +++ b/crates/stdlib/src/ssl/error.rs @@ -10,9 +10,27 @@ pub(crate) mod ssl_error { types::Constructor, }; - // Error type constants (needed for create_ssl_want_read_error etc.) + // Error type constants - exposed as pyattr and available for internal use + #[pyattr] + pub(crate) const SSL_ERROR_NONE: i32 = 0; + #[pyattr] + pub(crate) const SSL_ERROR_SSL: i32 = 1; + #[pyattr] pub(crate) const SSL_ERROR_WANT_READ: i32 = 2; + #[pyattr] pub(crate) const SSL_ERROR_WANT_WRITE: i32 = 3; + #[pyattr] + pub(crate) const SSL_ERROR_WANT_X509_LOOKUP: i32 = 4; + #[pyattr] + pub(crate) const SSL_ERROR_SYSCALL: i32 = 5; + #[pyattr] + pub(crate) const SSL_ERROR_ZERO_RETURN: i32 = 6; + #[pyattr] + pub(crate) const SSL_ERROR_WANT_CONNECT: i32 = 7; + #[pyattr] + pub(crate) const SSL_ERROR_EOF: i32 = 8; + #[pyattr] + pub(crate) const SSL_ERROR_INVALID_ERROR_CODE: i32 = 10; #[pyattr] #[pyexception(name = "SSLError", base = PyOSError)] @@ -102,7 +120,7 @@ pub(crate) mod ssl_error { pub fn create_ssl_eof_error(vm: &VirtualMachine) -> PyRef<PyOSError> { vm.new_os_subtype_error( PySSLEOFError::class(&vm.ctx).to_owned(), - None, + Some(SSL_ERROR_EOF), "EOF occurred in violation of protocol", ) } @@ -110,7 +128,7 @@ pub(crate) mod ssl_error { pub fn create_ssl_zero_return_error(vm: &VirtualMachine) -> PyRef<PyOSError> { vm.new_os_subtype_error( PySSLZeroReturnError::class(&vm.ctx).to_owned(), - None, + Some(SSL_ERROR_ZERO_RETURN), "TLS/SSL connection has been closed (EOF)", ) } From 898fe85f40b1c38b117688f96b2f02786a524194 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 21 Dec 2025 00:38:50 +0900 Subject: [PATCH 532/819] More openssl impl (#6464) * Add openssl build to CI * more openssl impls --- .cspell.dict/cpython.txt | 1 + .github/workflows/ci.yaml | 5 + crates/stdlib/src/openssl.rs | 450 ++++++++++++++++++++++++------ crates/stdlib/src/openssl/cert.rs | 168 +++++++++-- 4 files changed, 520 insertions(+), 104 deletions(-) diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 8acb1468f66..8ccd6d6b641 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -17,6 +17,7 @@ cmpop denom DICTFLAG dictoffset +distpoint elts excepthandler fileutils diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8539db1a27e..783e3dfa6b3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -147,6 +147,11 @@ jobs: - name: check compilation without threading run: cargo check ${{ env.CARGO_ARGS }} + - name: Test openssl build + run: + cargo build --no-default-features --features ssl-openssl + if: runner.os == 'Linux' + - name: Test example projects run: cargo run --manifest-path example_projects/barebone/Cargo.toml diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index b6d5f5c2035..4d420e7d539 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -64,7 +64,7 @@ mod _ssl { }, socket::{self, PySocket}, vm::{ - AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{ PyBaseException, PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyType, PyWeak, @@ -73,8 +73,8 @@ mod _ssl { convert::ToPyException, exceptions, function::{ - ArgBytesLike, ArgCallable, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath, - OptionalArg, PyComparisonValue, + ArgBytesLike, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath, OptionalArg, + PyComparisonValue, }, types::{Comparable, Constructor, PyComparisonOp}, utils::ToCString, @@ -602,6 +602,39 @@ mod _ssl { } } + // Get or create an ex_data index for msg_callback data + fn get_msg_callback_ex_data_index() -> libc::c_int { + use std::sync::LazyLock; + static MSG_CB_EX_DATA_IDX: LazyLock<libc::c_int> = LazyLock::new(|| unsafe { + sys::SSL_get_ex_new_index( + 0, + std::ptr::null_mut(), + None, + None, + Some(msg_callback_data_free), + ) + }); + *MSG_CB_EX_DATA_IDX + } + + // Free function for msg_callback data - called by OpenSSL when SSL is freed + unsafe extern "C" fn msg_callback_data_free( + _parent: *mut libc::c_void, + ptr: *mut libc::c_void, + _ad: *mut sys::CRYPTO_EX_DATA, + _idx: libc::c_int, + _argl: libc::c_long, + _argp: *mut libc::c_void, + ) { + if !ptr.is_null() { + unsafe { + // Reconstruct PyObjectRef and drop to decrement reference count + let raw = std::ptr::NonNull::new_unchecked(ptr as *mut PyObject); + let _ = PyObjectRef::from_raw(raw); + } + } + } + // SNI callback function called by OpenSSL unsafe extern "C" fn _servername_callback( ssl_ptr: *mut sys::SSL, @@ -732,13 +765,14 @@ mod _ssl { } unsafe { - // Get SSL socket from SSL_get_ex_data (index 0) - let ssl_socket_ptr = sys::SSL_get_ex_data(ssl_ptr, 0); + // Get SSL socket from ex_data using the dedicated index + let idx = get_msg_callback_ex_data_index(); + let ssl_socket_ptr = sys::SSL_get_ex_data(ssl_ptr, idx); if ssl_socket_ptr.is_null() { return; } - // ssl_socket_ptr is a pointer to Py<PySslSocket>, set in _wrap_socket/_wrap_bio + // ssl_socket_ptr is a pointer to Box<Py<PySslSocket>>, set in _wrap_socket/_wrap_bio let ssl_socket: &Py<PySslSocket> = &*(ssl_socket_ptr as *const Py<PySslSocket>); // Get the callback from the context @@ -849,6 +883,9 @@ mod _ssl { post_handshake_auth: PyMutex<bool>, sni_callback: PyMutex<Option<PyObjectRef>>, msg_callback: PyMutex<Option<PyObjectRef>>, + psk_client_callback: PyMutex<Option<PyObjectRef>>, + psk_server_callback: PyMutex<Option<PyObjectRef>>, + psk_identity_hint: PyMutex<Option<String>>, } impl fmt::Debug for PySslContext { @@ -960,6 +997,9 @@ mod _ssl { post_handshake_auth: PyMutex::new(false), sni_callback: PyMutex::new(None), msg_callback: PyMutex::new(None), + psk_client_callback: PyMutex::new(None), + psk_server_callback: PyMutex::new(None), + psk_identity_hint: PyMutex::new(None), }) } } @@ -1083,9 +1123,26 @@ mod _ssl { self.ctx.read().options().bits() as _ } #[pygetset(setter)] - fn set_options(&self, opts: libc::c_ulong) { - self.builder() - .set_options(SslOptions::from_bits_truncate(opts as _)); + fn set_options(&self, new_opts: libc::c_ulong) { + let mut ctx = self.builder(); + // Get current options + let current = ctx.options().bits() as libc::c_ulong; + + // Calculate options to clear and set + let clear = current & !new_opts; + let set = !current & new_opts; + + // Clear options first (using raw FFI since openssl crate doesn't expose clear_options) + if clear != 0 { + unsafe { + sys::SSL_CTX_clear_options(ctx.as_ptr(), clear); + } + } + + // Then set new options + if set != 0 { + ctx.set_options(SslOptions::from_bits_truncate(set as _)); + } } #[pygetset] fn protocol(&self) -> i32 { @@ -1312,7 +1369,7 @@ mod _ssl { ssl::select_next_proto(&server, client).ok_or(ssl::AlpnError::NOACK)?; let pos = memchr::memmem::find(client, proto) .expect("selected alpn proto should be present in client protos"); - Ok(&client[pos..proto.len()]) + Ok(&client[pos..pos + proto.len()]) }); Ok(()) } @@ -1324,6 +1381,78 @@ mod _ssl { } } + #[pymethod] + fn set_psk_client_callback( + &self, + callback: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Cannot add PSK client callback to a server context + if self.protocol == SslVersion::TlsServer { + return Err(vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + "Cannot add PSK client callback to a PROTOCOL_TLS_SERVER context" + .to_owned(), + ) + .upcast()); + } + + if vm.is_none(&callback) { + *self.psk_client_callback.lock() = None; + unsafe { + sys::SSL_CTX_set_psk_client_callback(self.builder().as_ptr(), None); + } + } else { + if !callback.is_callable() { + return Err(vm.new_type_error("callback must be callable".to_owned())); + } + *self.psk_client_callback.lock() = Some(callback); + // Note: The actual callback will be invoked via SSL app_data mechanism + // when do_handshake is called + } + Ok(()) + } + + #[pymethod] + fn set_psk_server_callback( + &self, + callback: PyObjectRef, + identity_hint: OptionalArg<PyStrRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Cannot add PSK server callback to a client context + if self.protocol == SslVersion::TlsClient { + return Err(vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + "Cannot add PSK server callback to a PROTOCOL_TLS_CLIENT context" + .to_owned(), + ) + .upcast()); + } + + if vm.is_none(&callback) { + *self.psk_server_callback.lock() = None; + *self.psk_identity_hint.lock() = None; + unsafe { + sys::SSL_CTX_set_psk_server_callback(self.builder().as_ptr(), None); + } + } else { + if !callback.is_callable() { + return Err(vm.new_type_error("callback must be callable".to_owned())); + } + *self.psk_server_callback.lock() = Some(callback); + if let OptionalArg::Present(hint) = identity_hint { + *self.psk_identity_hint.lock() = Some(hint.as_str().to_owned()); + } + // Note: The actual callback will be invoked via SSL app_data mechanism + } + Ok(()) + } + #[pymethod] fn load_verify_locations( &self, @@ -1343,16 +1472,33 @@ mod _ssl { // validate cadata type and load cadata if let Some(cadata) = args.cadata { - let certs = match cadata { + let (certs, is_pem) = match cadata { Either::A(s) => { if !s.as_str().is_ascii() { return Err(invalid_cadata(vm)); } - X509::stack_from_pem(s.as_bytes()) + (X509::stack_from_pem(s.as_bytes()), true) } - Either::B(b) => b.with_ref(x509_stack_from_der), + Either::B(b) => (b.with_ref(x509_stack_from_der), false), }; let certs = certs.map_err(|e| convert_openssl_error(vm, e))?; + + // If no certificates were loaded, raise an error + if certs.is_empty() { + let msg = if is_pem { + "no start line: cadata does not contain a certificate" + } else { + "not enough data: cadata does not contain a certificate" + }; + return Err(vm + .new_os_subtype_error( + PySSLError::class(&vm.ctx).to_owned(), + None, + msg.to_owned(), + ) + .upcast()); + } + let store = ctx.cert_store_mut(); for cert in certs { store @@ -1365,27 +1511,27 @@ mod _ssl { let cafile_path = args.cafile.map(|p| p.to_path_buf(vm)).transpose()?; let capath_path = args.capath.map(|p| p.to_path_buf(vm)).transpose()?; // Check file/directory existence before calling OpenSSL to get proper errno - if let Some(ref path) = cafile_path { - if !path.exists() { - return Err(vm - .new_os_subtype_error( - vm.ctx.exceptions.file_not_found_error.to_owned(), - Some(libc::ENOENT), - format!("No such file or directory: '{}'", path.display()), - ) - .upcast()); - } + if let Some(ref path) = cafile_path + && !path.exists() + { + return Err(vm + .new_os_subtype_error( + vm.ctx.exceptions.file_not_found_error.to_owned(), + Some(libc::ENOENT), + format!("No such file or directory: '{}'", path.display()), + ) + .upcast()); } - if let Some(ref path) = capath_path { - if !path.exists() { - return Err(vm - .new_os_subtype_error( - vm.ctx.exceptions.file_not_found_error.to_owned(), - Some(libc::ENOENT), - format!("No such file or directory: '{}'", path.display()), - ) - .upcast()); - } + if let Some(ref path) = capath_path + && !path.exists() + { + return Err(vm + .new_os_subtype_error( + vm.ctx.exceptions.file_not_found_error.to_owned(), + Some(libc::ENOENT), + format!("No such file or directory: '{}'", path.display()), + ) + .upcast()); } ctx.load_verify_locations(cafile_path.as_deref(), capath_path.as_deref()) .map_err(|e| convert_openssl_error(vm, e))?; @@ -1450,7 +1596,8 @@ mod _ssl { X509_LU_X509 => { x509_count += 1; let x509_ptr = sys::X509_OBJECT_get0_X509(obj_ptr); - if !x509_ptr.is_null() && X509_check_ca(x509_ptr) == 1 { + // X509_check_ca returns non-zero for any CA type + if !x509_ptr.is_null() && X509_check_ca(x509_ptr) != 0 { ca_count += 1; } } @@ -1673,18 +1820,19 @@ mod _ssl { #[pymethod] fn load_cert_chain(&self, args: LoadCertChainArgs, vm: &VirtualMachine) -> PyResult<()> { + use openssl::pkey::PKey; + use std::cell::RefCell; + let LoadCertChainArgs { certfile, keyfile, password, } = args; - // TODO: requires passing a callback to C - if password.is_some() { - return Err(vm.new_not_implemented_error("password arg not yet supported")); - } + let mut ctx = self.builder(); let key_path = keyfile.map(|path| path.to_path_buf(vm)).transpose()?; let cert_path = certfile.to_path_buf(vm)?; + // Check file existence before calling OpenSSL to get proper errno if !cert_path.exists() { return Err(vm @@ -1695,28 +1843,139 @@ mod _ssl { ) .upcast()); } - if let Some(ref kp) = key_path { - if !kp.exists() { - return Err(vm - .new_os_subtype_error( - vm.ctx.exceptions.file_not_found_error.to_owned(), - Some(libc::ENOENT), - format!("No such file or directory: '{}'", kp.display()), - ) - .upcast()); - } + if let Some(ref kp) = key_path + && !kp.exists() + { + return Err(vm + .new_os_subtype_error( + vm.ctx.exceptions.file_not_found_error.to_owned(), + Some(libc::ENOENT), + format!("No such file or directory: '{}'", kp.display()), + ) + .upcast()); } + + // Load certificate chain ctx.set_certificate_chain_file(&cert_path) - .and_then(|()| { - ctx.set_private_key_file( - key_path.as_ref().unwrap_or(&cert_path), - ssl::SslFiletype::PEM, - ) - }) - .and_then(|()| ctx.check_private_key()) + .map_err(|e| convert_openssl_error(vm, e))?; + + // Load private key - handle password if provided + let key_file_path = key_path.as_ref().unwrap_or(&cert_path); + + // PEM_BUFSIZE = 1024 (maximum password length in OpenSSL) + const PEM_BUFSIZE: usize = 1024; + + // Read key file data + let key_data = std::fs::read(key_file_path) + .map_err(|e| crate::vm::convert::ToPyException::to_pyexception(&e, vm))?; + + let pkey = if let Some(ref pw_obj) = password { + if pw_obj.is_callable() { + // Callable password - use callback that calls Python function + // Store any Python error that occurs in the callback + let py_error: RefCell<Option<PyBaseExceptionRef>> = RefCell::new(None); + + let result = PKey::private_key_from_pem_callback(&key_data, |buf| { + // Call the Python password callback + let pw_result = pw_obj.call((), vm); + match pw_result { + Ok(result) => { + // Extract password bytes + match Self::extract_password_bytes( + &result, + "password callback must return a string", + vm, + ) { + Ok(pw) => { + // Check password length + if pw.len() > PEM_BUFSIZE { + *py_error.borrow_mut() = + Some(vm.new_value_error(format!( + "password cannot be longer than {} bytes", + PEM_BUFSIZE + ))); + return Err(openssl::error::ErrorStack::get()); + } + let len = std::cmp::min(pw.len(), buf.len()); + buf[..len].copy_from_slice(&pw[..len]); + Ok(len) + } + Err(e) => { + *py_error.borrow_mut() = Some(e); + Err(openssl::error::ErrorStack::get()) + } + } + } + Err(e) => { + *py_error.borrow_mut() = Some(e); + Err(openssl::error::ErrorStack::get()) + } + } + }); + + // Check for Python error first + if let Some(py_err) = py_error.into_inner() { + return Err(py_err); + } + + result.map_err(|e| convert_openssl_error(vm, e))? + } else { + // Direct password (string/bytes) + let pw = Self::extract_password_bytes( + pw_obj, + "password should be a string or bytes", + vm, + )?; + + // Check password length + if pw.len() > PEM_BUFSIZE { + return Err(vm.new_value_error(format!( + "password cannot be longer than {} bytes", + PEM_BUFSIZE + ))); + } + + PKey::private_key_from_pem_passphrase(&key_data, &pw) + .map_err(|e| convert_openssl_error(vm, e))? + } + } else { + // No password - use SSL_CTX_use_PrivateKey_file directly for correct error messages + ctx.set_private_key_file(key_file_path, ssl::SslFiletype::PEM) + .map_err(|e| convert_openssl_error(vm, e))?; + + // Verify key matches certificate and return early + return ctx + .check_private_key() + .map_err(|e| convert_openssl_error(vm, e)); + }; + + ctx.set_private_key(&pkey) + .map_err(|e| convert_openssl_error(vm, e))?; + + // Verify key matches certificate + ctx.check_private_key() .map_err(|e| convert_openssl_error(vm, e)) } + // Helper to extract password bytes from string/bytes/bytearray + fn extract_password_bytes( + obj: &PyObject, + bad_type_error: &str, + vm: &VirtualMachine, + ) -> PyResult<Vec<u8>> { + use crate::vm::builtins::{PyByteArray, PyBytes, PyStr}; + + if let Some(s) = obj.downcast_ref::<PyStr>() { + Ok(s.as_str().as_bytes().to_vec()) + } else if let Some(b) = obj.downcast_ref::<PyBytes>() { + Ok(b.as_bytes().to_vec()) + } else if let Some(ba) = obj.downcast_ref::<PyByteArray>() { + Ok(ba.borrow_buf().to_vec()) + } else { + Err(vm.new_type_error(bad_type_error.to_owned())) + } + } + // Helper function to create SSL socket // = CPython's newPySSLSocket() fn new_py_ssl_socket( @@ -1855,11 +2114,12 @@ mod _ssl { unsafe { let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); - // Store ssl_socket pointer in index 0 for msg_callback (like CPython's SSL_set_app_data) - // This is safe because ssl_socket owns the SSL object and outlives it - // We store a pointer to Py<PySslSocket>, which msg_callback can dereference - let py_ptr: *const Py<PySslSocket> = &*py_ref; - sys::SSL_set_ex_data(ssl_ptr, 0, py_ptr as *mut _); + // Clone and store via into_raw() - increments refcount and returns stable pointer + // The refcount will be decremented by msg_callback_data_free when SSL is freed + let cloned: PyObjectRef = py_ref.clone().into(); + let raw_ptr = cloned.into_raw(); + let msg_cb_idx = get_msg_callback_ex_data_index(); + sys::SSL_set_ex_data(ssl_ptr, msg_cb_idx, raw_ptr.as_ptr() as *mut _); // Set SNI callback data if needed if has_sni_callback { @@ -1869,8 +2129,8 @@ mod _ssl { ssl_context: zelf.clone(), ssl_socket_weak, }); - let idx = get_sni_ex_data_index(); - sys::SSL_set_ex_data(ssl_ptr, idx, Box::into_raw(callback_data) as *mut _); + let sni_idx = get_sni_ex_data_index(); + sys::SSL_set_ex_data(ssl_ptr, sni_idx, Box::into_raw(callback_data) as *mut _); } } @@ -1923,11 +2183,12 @@ mod _ssl { unsafe { let ssl_ptr = py_ref.connection.read().ssl().as_ptr(); - // Store ssl_socket pointer in index 0 for msg_callback (like CPython's SSL_set_app_data) - // This is safe because ssl_socket owns the SSL object and outlives it - // We store a pointer to Py<PySslSocket>, which msg_callback can dereference - let py_ptr: *const Py<PySslSocket> = &*py_ref; - sys::SSL_set_ex_data(ssl_ptr, 0, py_ptr as *mut _); + // Clone and store via into_raw() - increments refcount and returns stable pointer + // The refcount will be decremented by msg_callback_data_free when SSL is freed + let cloned: PyObjectRef = py_ref.clone().into(); + let raw_ptr = cloned.into_raw(); + let msg_cb_idx = get_msg_callback_ex_data_index(); + sys::SSL_set_ex_data(ssl_ptr, msg_cb_idx, raw_ptr.as_ptr() as *mut _); // Set SNI callback data if needed if has_sni_callback { @@ -1937,8 +2198,8 @@ mod _ssl { ssl_context: zelf.clone(), ssl_socket_weak, }); - let idx = get_sni_ex_data_index(); - sys::SSL_set_ex_data(ssl_ptr, idx, Box::into_raw(callback_data) as *mut _); + let sni_idx = get_sni_ex_data_index(); + sys::SSL_set_ex_data(ssl_ptr, sni_idx, Box::into_raw(callback_data) as *mut _); } } @@ -1995,7 +2256,7 @@ mod _ssl { #[pyarg(any, optional)] keyfile: Option<FsPath>, #[pyarg(any, optional)] - password: Option<Either<PyStrRef, ArgCallable>>, + password: Option<PyObjectRef>, } // Err is true if the socket is blocking @@ -2004,7 +2265,6 @@ mod _ssl { enum SelectRet { Nonblocking, TimedOut, - IsBlocking, Closed, Ok, } @@ -2292,12 +2552,13 @@ mod _ssl { #[pymethod] fn get_unverified_chain(&self, vm: &VirtualMachine) -> PyResult<Option<PyListRef>> { let stream = self.connection.read(); - let Some(chain) = stream.ssl().peer_cert_chain() else { + let ssl = stream.ssl(); + let Some(chain) = ssl.peer_cert_chain() else { return Ok(None); }; // Return Certificate objects - let certs: Vec<PyObjectRef> = chain + let mut certs: Vec<PyObjectRef> = chain .iter() .map(|cert| unsafe { sys::X509_up_ref(cert.as_ptr()); @@ -2305,6 +2566,16 @@ mod _ssl { cert_to_certificate(vm, owned) }) .collect::<PyResult<_>>()?; + + // SSL_get_peer_cert_chain does not include peer cert for server-side sockets + // Add it manually at the beginning + if matches!(self.socket_type, SslServerOrClient::Server) + && let Some(peer_cert) = ssl.peer_certificate() + { + let peer_obj = cert_to_certificate(vm, peer_cert)?; + certs.insert(0, peer_obj); + } + Ok(Some(vm.ctx.new_list(certs))) } @@ -2652,7 +2923,7 @@ mod _ssl { return Err(socket_closed_error(vm)); } SelectRet::Nonblocking => {} - SelectRet::IsBlocking | SelectRet::Ok => { + SelectRet::Ok => { // For blocking sockets, select() has completed successfully // Continue the handshake loop (matches CPython's SOCKET_IS_BLOCKING behavior) if needs.is_some() { @@ -2719,7 +2990,7 @@ mod _ssl { } SelectRet::Closed => return Err(socket_closed_error(vm)), SelectRet::Nonblocking => {} - SelectRet::IsBlocking | SelectRet::Ok => { + SelectRet::Ok => { // For blocking sockets, select() has completed successfully // Continue the write loop (matches CPython's SOCKET_IS_BLOCKING behavior) if needs.is_some() { @@ -2896,7 +3167,7 @@ mod _ssl { } SelectRet::Closed => return Err(socket_closed_error(vm)), SelectRet::Nonblocking => {} - SelectRet::IsBlocking | SelectRet::Ok => { + SelectRet::Ok => { // For blocking sockets, select() has completed successfully // Continue the read loop (matches CPython's SOCKET_IS_BLOCKING behavior) if needs.is_some() { @@ -3576,30 +3847,33 @@ mod _ssl { let bio = bio::MemBioSlice::new(der)?; let mut certs = vec![]; + let mut was_bio_eof = false; loop { + // Check for EOF before attempting to parse (like CPython's _add_ca_certs) + // BIO_ctrl with BIO_CTRL_EOF returns 1 if EOF, 0 otherwise + if sys::BIO_ctrl(bio.as_ptr(), sys::BIO_CTRL_EOF, 0, std::ptr::null_mut()) != 0 { + was_bio_eof = true; + break; + } + let cert = sys::d2i_X509_bio(bio.as_ptr(), std::ptr::null_mut()); if cert.is_null() { + // Parse error (not just EOF) break; } certs.push(X509::from_ptr(cert)); } - if certs.is_empty() { - // No certificates loaded at all + // If we loaded some certs but didn't reach EOF, there's garbage data + // (like cacert_der + b"A") - this is an error + if !certs.is_empty() && !was_bio_eof { + // Return the error from the last failed parse attempt return Err(ErrorStack::get()); } - // Successfully loaded at least one certificate from DER data. - // Clear any trailing errors from EOF. - // CPython clears errors when: - // - DER: was_bio_eof is set (EOF reached) - // - PEM: PEM_R_NO_START_LINE error (normal EOF) - // Both cases mean successful completion with loaded certs. - eprintln!( - "[x509_stack_from_der] SUCCESS: Clearing errors and returning {} certs", - certs.len() - ); + // Clear any errors (including parse errors when no certs loaded) + // Let the caller decide how to handle empty results sys::ERR_clear_error(); Ok(certs) } diff --git a/crates/stdlib/src/openssl/cert.rs b/crates/stdlib/src/openssl/cert.rs index 1197bf4aa46..b63d824a837 100644 --- a/crates/stdlib/src/openssl/cert.rs +++ b/crates/stdlib/src/openssl/cert.rs @@ -5,16 +5,19 @@ pub(super) use ssl_cert::{PySSLCertificate, cert_to_certificate, cert_to_py, obj #[pymodule(sub)] pub(crate) mod ssl_cert { use crate::{ - common::ascii, + common::{ascii, hash::PyHash}, vm::{ - PyObjectRef, PyPayload, PyResult, VirtualMachine, + Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, + class_or_notimplemented, convert::{ToPyException, ToPyObject}, - function::{FsPath, OptionalArg}, + function::{FsPath, OptionalArg, PyComparisonValue}, + types::{Comparable, Hashable, PyComparisonOp, Representable}, }, }; use foreign_types_shared::ForeignTypeRef; use openssl::{ asn1::Asn1ObjectRef, + nid::Nid, x509::{self, X509, X509Ref}, }; use openssl_sys as sys; @@ -54,7 +57,7 @@ pub(crate) mod ssl_cert { #[pyclass(module = "ssl", name = "Certificate")] #[derive(PyPayload)] pub(crate) struct PySSLCertificate { - cert: X509, + pub(crate) cert: X509, } impl fmt::Debug for PySSLCertificate { @@ -63,7 +66,7 @@ pub(crate) mod ssl_cert { } } - #[pyclass] + #[pyclass(with(Comparable, Hashable, Representable))] impl PySSLCertificate { #[pymethod] fn public_bytes( @@ -83,12 +86,14 @@ pub(crate) mod ssl_cert { Ok(vm.ctx.new_bytes(der).into()) } ENCODING_PEM => { - // PEM encoding + // PEM encoding - returns string let pem = self .cert .to_pem() .map_err(|e| convert_openssl_error(vm, e))?; - Ok(vm.ctx.new_bytes(pem).into()) + let pem_str = String::from_utf8(pem) + .map_err(|_| vm.new_value_error("Invalid UTF-8 in PEM"))?; + Ok(vm.ctx.new_str(pem_str).into()) } _ => Err(vm.new_value_error("Unsupported format")), } @@ -100,6 +105,66 @@ pub(crate) mod ssl_cert { } } + impl Comparable for PySSLCertificate { + fn cmp( + zelf: &Py<Self>, + other: &PyObject, + op: PyComparisonOp, + vm: &VirtualMachine, + ) -> PyResult<PyComparisonValue> { + let other = class_or_notimplemented!(Self, other); + + // Only support equality comparison + if !matches!(op, PyComparisonOp::Eq | PyComparisonOp::Ne) { + return Ok(PyComparisonValue::NotImplemented); + } + + // Compare DER encodings + let self_der = zelf + .cert + .to_der() + .map_err(|e| convert_openssl_error(vm, e))?; + let other_der = other + .cert + .to_der() + .map_err(|e| convert_openssl_error(vm, e))?; + + let eq = self_der == other_der; + Ok(op.eval_ord(eq.cmp(&true)).into()) + } + } + + impl Hashable for PySSLCertificate { + fn hash(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyHash> { + // Use subject name hash as certificate hash + let hash = unsafe { sys::X509_subject_name_hash(zelf.cert.as_ptr()) }; + Ok(hash as PyHash) + } + } + + impl Representable for PySSLCertificate { + fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { + // Build subject string like "CN=localhost, O=Python" + let subject = zelf.cert.subject_name(); + let mut parts: Vec<String> = Vec::new(); + for entry in subject.entries() { + // Use short name (SN) if available, otherwise use OID + let name = match entry.object().nid().short_name() { + Ok(sn) => sn.to_string(), + Err(_) => obj2txt(entry.object(), true).unwrap_or_default(), + }; + if let Ok(value) = entry.data().as_utf8() { + parts.push(format!("{}={}", name, value)); + } + } + if parts.is_empty() { + Ok("<Certificate>".to_string()) + } else { + Ok(format!("<Certificate '{}'>", parts.join(", "))) + } + } + } + fn name_to_py(vm: &VirtualMachine, name: &x509::X509NameRef) -> PyResult { let list = name .entries() @@ -133,11 +198,15 @@ pub(crate) mod ssl_cert { .to_bn() .and_then(|bn| bn.to_hex_str()) .map_err(|e| convert_openssl_error(vm, e))?; - dict.set_item( - "serialNumber", - vm.ctx.new_str(serial_num.to_owned()).into(), - vm, - )?; + // Serial number must have even length (each byte = 2 hex chars) + // BigNum::to_hex_str() strips leading zeros, so we need to pad + let serial_str = serial_num.to_string(); + let serial_str = if serial_str.len() % 2 == 1 { + format!("0{}", serial_str) + } else { + serial_str + }; + dict.set_item("serialNumber", vm.ctx.new_str(serial_str).into(), vm)?; dict.set_item( "notBefore", @@ -188,10 +257,23 @@ pub(crate) mod ssl_cert { return vm.new_tuple((ascii!("DirName"), py_name)).into(); } - // TODO: Handle Registered ID (GEN_RID) - // CPython implementation uses i2t_ASN1_OBJECT to convert OID - // This requires accessing GENERAL_NAME union which is complex in Rust - // For now, we return <unsupported> for unhandled types + // Check for Registered ID (GEN_RID) + // Access raw GENERAL_NAME to check type + let ptr = gen_name.as_ptr(); + unsafe { + if (*ptr).type_ == sys::GEN_RID { + // d is ASN1_OBJECT* for GEN_RID + let oid_ptr = (*ptr).d as *const sys::ASN1_OBJECT; + if !oid_ptr.is_null() { + let oid_ref = Asn1ObjectRef::from_ptr(oid_ptr as *mut _); + if let Some(oid_str) = obj2txt(oid_ref, true) { + return vm + .new_tuple((ascii!("Registered ID"), oid_str)) + .into(); + } + } + } + } // For othername and other unsupported types vm.new_tuple((ascii!("othername"), ascii!("<unsupported>"))) @@ -202,6 +284,60 @@ pub(crate) mod ssl_cert { dict.set_item("subjectAltName", vm.ctx.new_tuple(san).into(), vm)?; }; + // Authority Information Access: OCSP URIs + if let Ok(ocsp_list) = cert.ocsp_responders() + && !ocsp_list.is_empty() + { + let uris: Vec<PyObjectRef> = ocsp_list + .iter() + .map(|s| vm.ctx.new_str(s.to_string()).into()) + .collect(); + dict.set_item("OCSP", vm.ctx.new_tuple(uris).into(), vm)?; + } + + // Authority Information Access: CA Issuers URIs + if let Some(aia) = cert.authority_info() { + let ca_issuers: Vec<PyObjectRef> = aia + .iter() + .filter_map(|ad| { + // Check if method is CA Issuers (NID_ad_ca_issuers) + if ad.method().nid() != Nid::AD_CA_ISSUERS { + return None; + } + // Get URI from location + ad.location() + .uri() + .map(|uri| vm.ctx.new_str(uri.to_owned()).into()) + }) + .collect(); + if !ca_issuers.is_empty() { + dict.set_item("caIssuers", vm.ctx.new_tuple(ca_issuers).into(), vm)?; + } + } + + // CRL Distribution Points + if let Some(crl_dps) = cert.crl_distribution_points() { + let mut crl_uris: Vec<PyObjectRef> = Vec::new(); + for dp in crl_dps.iter() { + if let Some(dp_name) = dp.distpoint() + && let Some(fullname) = dp_name.fullname() + { + for gn in fullname.iter() { + if let Some(uri) = gn.uri() { + crl_uris.push(vm.ctx.new_str(uri.to_owned()).into()); + } + } + } + } + if !crl_uris.is_empty() { + dict.set_item( + "crlDistributionPoints", + vm.ctx.new_tuple(crl_uris).into(), + vm, + )?; + } + } + Ok(dict.into()) } From 09fa97d1b91170d3b67c63a778aea19aacaff02d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 21 Dec 2025 00:54:05 +0900 Subject: [PATCH 533/819] CI to test Apple Intel (#6465) --- .github/workflows/ci.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 783e3dfa6b3..a0947af853b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -158,13 +158,13 @@ jobs: cargo run --manifest-path example_projects/frozen_stdlib/Cargo.toml if: runner.os == 'Linux' - - name: prepare AppleSilicon build + - name: prepare Intel MacOS build uses: dtolnay/rust-toolchain@stable with: - target: aarch64-apple-darwin + target: x86_64-apple-darwin if: runner.os == 'macOS' - - name: Check compilation for Apple Silicon - run: cargo check --target aarch64-apple-darwin + - name: Check compilation for Intel MacOS + run: cargo check --target x86_64-apple-darwin if: runner.os == 'macOS' - name: prepare iOS build uses: dtolnay/rust-toolchain@stable From 8b3bf558fc9fb74bfea2344012b0fe5d52aa594f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 21 Dec 2025 08:25:17 +0900 Subject: [PATCH 534/819] Introduce PyConfig to implement Paths correctly (#6461) * getpath * introduce PyConfig * landmark in getpath --- .cspell.dict/cpython.txt | 1 + crates/stdlib/src/syslog.rs | 2 +- crates/vm/src/getpath.rs | 385 +++++++++++++++++++++++++++++++ crates/vm/src/import.rs | 2 +- crates/vm/src/lib.rs | 1 + crates/vm/src/stdlib/builtins.rs | 4 +- crates/vm/src/stdlib/imp.rs | 2 +- crates/vm/src/stdlib/io.rs | 4 +- crates/vm/src/stdlib/signal.rs | 2 +- crates/vm/src/stdlib/sys.rs | 142 ++---------- crates/vm/src/vm/compile.rs | 2 +- crates/vm/src/vm/interpreter.rs | 10 +- crates/vm/src/vm/mod.rs | 28 +-- crates/vm/src/vm/setting.rs | 36 ++- src/interpreter.rs | 16 +- src/lib.rs | 12 +- 16 files changed, 487 insertions(+), 162 deletions(-) create mode 100644 crates/vm/src/getpath.rs diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 8ccd6d6b641..fbb467c94db 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -43,6 +43,7 @@ numer orelse pathconfig patma +platstdlib posonlyarg posonlyargs prec diff --git a/crates/stdlib/src/syslog.rs b/crates/stdlib/src/syslog.rs index adba6f297ce..d0ed3f60949 100644 --- a/crates/stdlib/src/syslog.rs +++ b/crates/stdlib/src/syslog.rs @@ -26,7 +26,7 @@ mod syslog { use libc::{LOG_AUTHPRIV, LOG_CRON, LOG_PERROR}; fn get_argv(vm: &VirtualMachine) -> Option<PyStrRef> { - if let Some(argv) = vm.state.settings.argv.first() + if let Some(argv) = vm.state.config.settings.argv.first() && !argv.is_empty() { return Some( diff --git a/crates/vm/src/getpath.rs b/crates/vm/src/getpath.rs new file mode 100644 index 00000000000..423b9b54136 --- /dev/null +++ b/crates/vm/src/getpath.rs @@ -0,0 +1,385 @@ +//! Path configuration for RustPython (ref: Modules/getpath.py) +//! +//! This module implements Python path calculation logic following getpath.py. +//! It uses landmark-based search to locate prefix, exec_prefix, and stdlib directories. +//! +//! The main entry point is `init_path_config()` which computes Paths from Settings. + +use crate::vm::{Paths, Settings}; +use std::env; +use std::path::{Path, PathBuf}; + +// Platform-specific landmarks (ref: getpath.py PLATFORM CONSTANTS) + +#[cfg(not(windows))] +mod platform { + use crate::version; + + pub const BUILDDIR_TXT: &str = "pybuilddir.txt"; + pub const BUILD_LANDMARK: &str = "Modules/Setup.local"; + pub const VENV_LANDMARK: &str = "pyvenv.cfg"; + pub const BUILDSTDLIB_LANDMARK: &str = "Lib/os.py"; + + pub fn stdlib_subdir() -> String { + format!("lib/python{}.{}", version::MAJOR, version::MINOR) + } + + pub fn stdlib_landmarks() -> [String; 2] { + let subdir = stdlib_subdir(); + [format!("{}/os.py", subdir), format!("{}/os.pyc", subdir)] + } + + pub fn platstdlib_landmark() -> String { + format!( + "lib/python{}.{}/lib-dynload", + version::MAJOR, + version::MINOR + ) + } + + pub fn zip_landmark() -> String { + format!("lib/python{}{}.zip", version::MAJOR, version::MINOR) + } +} + +#[cfg(windows)] +mod platform { + use crate::version; + + pub const BUILDDIR_TXT: &str = "pybuilddir.txt"; + pub const BUILD_LANDMARK: &str = "Modules\\Setup.local"; + pub const VENV_LANDMARK: &str = "pyvenv.cfg"; + pub const BUILDSTDLIB_LANDMARK: &str = "Lib\\os.py"; + pub const STDLIB_SUBDIR: &str = "Lib"; + + pub fn stdlib_landmarks() -> [String; 2] { + ["Lib\\os.py".into(), "Lib\\os.pyc".into()] + } + + pub fn platstdlib_landmark() -> String { + "DLLs".into() + } + + pub fn zip_landmark() -> String { + format!("python{}{}.zip", version::MAJOR, version::MINOR) + } +} + +// Helper functions (ref: getpath.py HELPER FUNCTIONS) + +/// Search upward from a directory for landmark files/directories +/// Returns the directory where a landmark was found +fn search_up<P, F>(start: P, landmarks: &[&str], test: F) -> Option<PathBuf> +where + P: AsRef<Path>, + F: Fn(&Path) -> bool, +{ + let mut current = start.as_ref().to_path_buf(); + loop { + for landmark in landmarks { + let path = current.join(landmark); + if test(&path) { + return Some(current); + } + } + if !current.pop() { + return None; + } + } +} + +/// Search upward for a file landmark +fn search_up_file<P: AsRef<Path>>(start: P, landmarks: &[&str]) -> Option<PathBuf> { + search_up(start, landmarks, |p| p.is_file()) +} + +/// Search upward for a directory landmark +#[cfg(not(windows))] +fn search_up_dir<P: AsRef<Path>>(start: P, landmarks: &[&str]) -> Option<PathBuf> { + search_up(start, landmarks, |p| p.is_dir()) +} + +// Path computation functions + +/// Compute path configuration from Settings +/// +/// This function should be called before interpreter initialization. +/// It returns a Paths struct with all computed path values. +pub fn init_path_config(settings: &Settings) -> Paths { + let mut paths = Paths::default(); + + // Step 0: Get executable path + let executable = get_executable_path(); + paths.executable = executable + .as_ref() + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or_default(); + + let exe_dir = executable + .as_ref() + .and_then(|p| p.parent().map(PathBuf::from)); + + // Step 1: Check for __PYVENV_LAUNCHER__ environment variable + if let Ok(launcher) = env::var("__PYVENV_LAUNCHER__") { + paths.base_executable = launcher; + } + + // Step 2: Check for venv (pyvenv.cfg) and get 'home' + let (venv_prefix, home_dir) = detect_venv(&exe_dir); + let search_dir = home_dir.clone().or(exe_dir.clone()); + + // Step 3: Check for build directory + let build_prefix = detect_build_directory(&search_dir); + + // Step 4: Calculate prefix via landmark search + // When in venv, search_dir is home_dir, so this gives us the base Python's prefix + let calculated_prefix = calculate_prefix(&search_dir, &build_prefix); + + // Step 5: Set prefix and base_prefix + if venv_prefix.is_some() { + // In venv: prefix = venv directory, base_prefix = original Python's prefix + paths.prefix = venv_prefix + .as_ref() + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or_else(|| calculated_prefix.clone()); + paths.base_prefix = calculated_prefix; + } else { + // Not in venv: prefix == base_prefix + paths.prefix = calculated_prefix.clone(); + paths.base_prefix = calculated_prefix; + } + + // Step 6: Calculate exec_prefix + paths.exec_prefix = if venv_prefix.is_some() { + // In venv: exec_prefix = prefix (venv directory) + paths.prefix.clone() + } else { + calculate_exec_prefix(&search_dir, &paths.prefix) + }; + paths.base_exec_prefix = paths.base_prefix.clone(); + + // Step 7: Calculate base_executable (if not already set by __PYVENV_LAUNCHER__) + if paths.base_executable.is_empty() { + paths.base_executable = calculate_base_executable(executable.as_ref(), &home_dir); + } + + // Step 8: Build module_search_paths + paths.module_search_paths = + build_module_search_paths(settings, &paths.prefix, &paths.exec_prefix); + + paths +} + +/// Get default prefix value +fn default_prefix() -> String { + std::option_env!("RUSTPYTHON_PREFIX") + .map(String::from) + .unwrap_or_else(|| { + if cfg!(windows) { + "C:".to_owned() + } else { + "/usr/local".to_owned() + } + }) +} + +/// Detect virtual environment by looking for pyvenv.cfg +/// Returns (venv_prefix, home_dir from pyvenv.cfg) +fn detect_venv(exe_dir: &Option<PathBuf>) -> (Option<PathBuf>, Option<PathBuf>) { + // Try exe_dir/../pyvenv.cfg first (standard venv layout: venv/bin/python) + if let Some(dir) = exe_dir + && let Some(venv_dir) = dir.parent() + { + let cfg = venv_dir.join(platform::VENV_LANDMARK); + if cfg.exists() + && let Some(home) = parse_pyvenv_home(&cfg) + { + return (Some(venv_dir.to_path_buf()), Some(PathBuf::from(home))); + } + } + + // Try exe_dir/pyvenv.cfg (alternative layout) + if let Some(dir) = exe_dir { + let cfg = dir.join(platform::VENV_LANDMARK); + if cfg.exists() + && let Some(home) = parse_pyvenv_home(&cfg) + { + return (Some(dir.clone()), Some(PathBuf::from(home))); + } + } + + (None, None) +} + +/// Detect if running from a build directory +fn detect_build_directory(exe_dir: &Option<PathBuf>) -> Option<PathBuf> { + let dir = exe_dir.as_ref()?; + + // Check for pybuilddir.txt (indicates build directory) + if dir.join(platform::BUILDDIR_TXT).exists() { + return Some(dir.clone()); + } + + // Check for Modules/Setup.local (build landmark) + if dir.join(platform::BUILD_LANDMARK).exists() { + return Some(dir.clone()); + } + + // Search up for Lib/os.py (build stdlib landmark) + search_up_file(dir, &[platform::BUILDSTDLIB_LANDMARK]) +} + +/// Calculate prefix by searching for landmarks +fn calculate_prefix(exe_dir: &Option<PathBuf>, build_prefix: &Option<PathBuf>) -> String { + // 1. If build directory detected, use it + if let Some(bp) = build_prefix { + return bp.to_string_lossy().into_owned(); + } + + if let Some(dir) = exe_dir { + // 2. Search for ZIP landmark + let zip = platform::zip_landmark(); + if let Some(prefix) = search_up_file(dir, &[&zip]) { + return prefix.to_string_lossy().into_owned(); + } + + // 3. Search for stdlib landmarks (os.py) + let landmarks = platform::stdlib_landmarks(); + let refs: Vec<&str> = landmarks.iter().map(|s| s.as_str()).collect(); + if let Some(prefix) = search_up_file(dir, &refs) { + return prefix.to_string_lossy().into_owned(); + } + } + + // 4. Fallback to default + default_prefix() +} + +/// Calculate exec_prefix +fn calculate_exec_prefix(exe_dir: &Option<PathBuf>, prefix: &str) -> String { + #[cfg(windows)] + { + // Windows: exec_prefix == prefix + let _ = exe_dir; // silence unused warning + prefix.to_owned() + } + + #[cfg(not(windows))] + { + // POSIX: search for lib-dynload directory + if let Some(dir) = exe_dir { + let landmark = platform::platstdlib_landmark(); + if let Some(exec_prefix) = search_up_dir(dir, &[&landmark]) { + return exec_prefix.to_string_lossy().into_owned(); + } + } + // Fallback: same as prefix + prefix.to_owned() + } +} + +/// Calculate base_executable +fn calculate_base_executable(executable: Option<&PathBuf>, home_dir: &Option<PathBuf>) -> String { + // If in venv and we have home, construct base_executable from home + if let (Some(exe), Some(home)) = (executable, home_dir) + && let Some(exe_name) = exe.file_name() + { + let base = home.join(exe_name); + return base.to_string_lossy().into_owned(); + } + + // Otherwise, base_executable == executable + executable + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or_default() +} + +/// Build the complete module_search_paths (sys.path) +fn build_module_search_paths(settings: &Settings, prefix: &str, exec_prefix: &str) -> Vec<String> { + let mut paths = Vec::new(); + + // 1. PYTHONPATH/RUSTPYTHONPATH from settings + paths.extend(settings.path_list.iter().cloned()); + + // 2. ZIP file path + let zip_path = PathBuf::from(prefix).join(platform::zip_landmark()); + paths.push(zip_path.to_string_lossy().into_owned()); + + // 3. stdlib and platstdlib directories + #[cfg(not(windows))] + { + // POSIX: stdlib first, then lib-dynload + let stdlib_dir = PathBuf::from(prefix).join(platform::stdlib_subdir()); + paths.push(stdlib_dir.to_string_lossy().into_owned()); + + let platstdlib = PathBuf::from(exec_prefix).join(platform::platstdlib_landmark()); + paths.push(platstdlib.to_string_lossy().into_owned()); + } + + #[cfg(windows)] + { + // Windows: DLLs first, then Lib + let platstdlib = PathBuf::from(exec_prefix).join(platform::platstdlib_landmark()); + paths.push(platstdlib.to_string_lossy().into_owned()); + + let stdlib_dir = PathBuf::from(prefix).join(platform::STDLIB_SUBDIR); + paths.push(stdlib_dir.to_string_lossy().into_owned()); + } + + paths +} + +/// Get the current executable path +fn get_executable_path() -> Option<PathBuf> { + #[cfg(not(target_arch = "wasm32"))] + { + let exec_arg = env::args_os().next()?; + which::which(exec_arg).ok() + } + #[cfg(target_arch = "wasm32")] + { + let exec_arg = env::args().next()?; + Some(PathBuf::from(exec_arg)) + } +} + +/// Parse pyvenv.cfg and extract the 'home' key value +fn parse_pyvenv_home(pyvenv_cfg: &Path) -> Option<String> { + let content = std::fs::read_to_string(pyvenv_cfg).ok()?; + + for line in content.lines() { + if let Some((key, value)) = line.split_once('=') + && key.trim().to_lowercase() == "home" + { + return Some(value.trim().to_string()); + } + } + + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_init_path_config() { + let settings = Settings::default(); + let paths = init_path_config(&settings); + // Just verify it doesn't panic and returns valid paths + assert!(!paths.prefix.is_empty()); + } + + #[test] + fn test_search_up() { + // Test with a path that doesn't have any landmarks + let result = search_up_file(std::env::temp_dir(), &["nonexistent_landmark_xyz"]); + assert!(result.is_none()); + } + + #[test] + fn test_default_prefix() { + let prefix = default_prefix(); + assert!(!prefix.is_empty()); + } +} diff --git a/crates/vm/src/import.rs b/crates/vm/src/import.rs index 3f4a437c599..39748655e0f 100644 --- a/crates/vm/src/import.rs +++ b/crates/vm/src/import.rs @@ -206,7 +206,7 @@ fn remove_importlib_frames_inner( // TODO: This function should do nothing on verbose mode. // TODO: Fix this function after making PyTraceback.next mutable pub fn remove_importlib_frames(vm: &VirtualMachine, exc: &Py<PyBaseException>) { - if vm.state.settings.verbose != 0 { + if vm.state.config.settings.verbose != 0 { return; } diff --git a/crates/vm/src/lib.rs b/crates/vm/src/lib.rs index 923b33d2acc..f461c612955 100644 --- a/crates/vm/src/lib.rs +++ b/crates/vm/src/lib.rs @@ -60,6 +60,7 @@ pub mod exceptions; pub mod format; pub mod frame; pub mod function; +pub mod getpath; pub mod import; mod intern; pub mod iter; diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 442bb79b94e..72d2c724159 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -132,7 +132,7 @@ mod builtins { let optimize: i32 = args.optimize.map_or(Ok(-1), |v| v.try_to_primitive(vm))?; let optimize: u8 = if optimize == -1 { - vm.state.settings.optimize + vm.state.config.settings.optimize } else { optimize .try_into() @@ -1080,7 +1080,7 @@ pub fn init_module(vm: &VirtualMachine, module: &Py<PyModule>) { builtins::extend_module(vm, module).unwrap(); - let debug_mode: bool = vm.state.settings.optimize == 0; + let debug_mode: bool = vm.state.config.settings.optimize == 0; // Create dynamic ExceptionGroup with multiple inheritance (BaseExceptionGroup + Exception) let exception_group = crate::exception_group::exception_group(); diff --git a/crates/vm/src/stdlib/imp.rs b/crates/vm/src/stdlib/imp.rs index 596847776ff..76b3bfd124c 100644 --- a/crates/vm/src/stdlib/imp.rs +++ b/crates/vm/src/stdlib/imp.rs @@ -91,7 +91,7 @@ mod _imp { #[pyattr] fn check_hash_based_pycs(vm: &VirtualMachine) -> PyStrRef { vm.ctx - .new_str(vm.state.settings.check_hash_pycs_mode.to_string()) + .new_str(vm.state.config.settings.check_hash_pycs_mode.to_string()) } #[pyfunction] diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 547e482f13a..ba3576a176c 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -2398,7 +2398,9 @@ mod _io { *data = None; let encoding = match args.encoding { - None if vm.state.settings.utf8_mode > 0 => identifier_utf8!(vm, utf_8).to_owned(), + None if vm.state.config.settings.utf8_mode > 0 => { + identifier_utf8!(vm, utf_8).to_owned() + } Some(enc) if enc.as_str() != "locale" => enc, _ => { // None without utf8_mode or "locale" encoding diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index 4eacb10154c..6771a950400 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -114,7 +114,7 @@ pub(crate) mod _signal { module: &Py<crate::builtins::PyModule>, vm: &VirtualMachine, ) { - if vm.state.settings.install_signal_handlers { + if vm.state.config.settings.install_signal_handlers { let sig_dfl = vm.new_pyobj(SIG_DFL as u8); let sig_ign = vm.new_pyobj(SIG_IGN as u8); diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index f9bd2b59456..cfe6f9f5e61 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -25,7 +25,6 @@ mod sys { use std::{ env::{self, VarError}, io::Read, - path, sync::atomic::Ordering, }; @@ -96,25 +95,20 @@ mod sys { const DLLHANDLE: usize = 0; #[pyattr] - const fn default_prefix(_vm: &VirtualMachine) -> &'static str { - // TODO: the windows one doesn't really make sense - if cfg!(windows) { "C:" } else { "/usr/local" } + fn prefix(vm: &VirtualMachine) -> String { + vm.state.config.paths.prefix.clone() } #[pyattr] - fn prefix(vm: &VirtualMachine) -> &'static str { - option_env!("RUSTPYTHON_PREFIX").unwrap_or_else(|| default_prefix(vm)) + fn base_prefix(vm: &VirtualMachine) -> String { + vm.state.config.paths.base_prefix.clone() } #[pyattr] - fn base_prefix(vm: &VirtualMachine) -> &'static str { - option_env!("RUSTPYTHON_BASEPREFIX").unwrap_or_else(|| prefix(vm)) + fn exec_prefix(vm: &VirtualMachine) -> String { + vm.state.config.paths.exec_prefix.clone() } #[pyattr] - fn exec_prefix(vm: &VirtualMachine) -> &'static str { - option_env!("RUSTPYTHON_BASEPREFIX").unwrap_or_else(|| prefix(vm)) - } - #[pyattr] - fn base_exec_prefix(vm: &VirtualMachine) -> &'static str { - option_env!("RUSTPYTHON_BASEPREFIX").unwrap_or_else(|| exec_prefix(vm)) + fn base_exec_prefix(vm: &VirtualMachine) -> String { + vm.state.config.paths.base_exec_prefix.clone() } #[pyattr] fn platlibdir(_vm: &VirtualMachine) -> &'static str { @@ -126,6 +120,7 @@ mod sys { #[pyattr] fn argv(vm: &VirtualMachine) -> Vec<PyObjectRef> { vm.state + .config .settings .argv .iter() @@ -162,117 +157,18 @@ mod sys { } #[pyattr] - fn _base_executable(vm: &VirtualMachine) -> PyObjectRef { - let ctx = &vm.ctx; - // First check __PYVENV_LAUNCHER__ environment variable - if let Ok(var) = env::var("__PYVENV_LAUNCHER__") { - return ctx.new_str(var).into(); - } - - // Try to detect if we're running from a venv by looking for pyvenv.cfg - if let Some(base_exe) = get_venv_base_executable() { - return ctx.new_str(base_exe).into(); - } - - executable(vm) - } - - /// Try to find base executable from pyvenv.cfg (see getpath.py) - fn get_venv_base_executable() -> Option<String> { - // TODO: This is a minimal implementation of getpath.py - // To fully support all cases, `getpath.py` should be placed in @crates/vm/Lib/python_builtins/ - - // Get current executable path - #[cfg(not(target_arch = "wasm32"))] - let exe_path = { - let exec_arg = env::args_os().next()?; - which::which(exec_arg).ok()? - }; - #[cfg(target_arch = "wasm32")] - let exe_path = { - let exec_arg = env::args().next()?; - path::PathBuf::from(exec_arg) - }; - - let exe_dir = exe_path.parent()?; - let exe_name = exe_path.file_name()?; - - // Look for pyvenv.cfg in parent directory (typical venv layout: venv/bin/python) - let venv_dir = exe_dir.parent()?; - let pyvenv_cfg = venv_dir.join("pyvenv.cfg"); - - if !pyvenv_cfg.exists() { - return None; - } - - // Parse pyvenv.cfg and extract home directory - let content = std::fs::read_to_string(&pyvenv_cfg).ok()?; - - for line in content.lines() { - if let Some((key, value)) = line.split_once('=') { - let key = key.trim().to_lowercase(); - let value = value.trim(); - - if key == "home" { - // First try to resolve symlinks (getpath.py line 373-377) - if let Ok(resolved) = std::fs::canonicalize(&exe_path) - && resolved != exe_path - { - return Some(resolved.to_string_lossy().into_owned()); - } - // Fallback: home_dir + executable_name (getpath.py line 381) - let base_exe = path::Path::new(value).join(exe_name); - return Some(base_exe.to_string_lossy().into_owned()); - } - } - } - - None + fn _base_executable(vm: &VirtualMachine) -> String { + vm.state.config.paths.base_executable.clone() } #[pyattr] fn dont_write_bytecode(vm: &VirtualMachine) -> bool { - !vm.state.settings.write_bytecode + !vm.state.config.settings.write_bytecode } #[pyattr] - fn executable(vm: &VirtualMachine) -> PyObjectRef { - let ctx = &vm.ctx; - #[cfg(not(target_arch = "wasm32"))] - { - if let Some(exec_path) = env::args_os().next() - && let Ok(path) = which::which(exec_path) - { - return ctx - .new_str( - path.into_os_string() - .into_string() - .unwrap_or_else(|p| p.to_string_lossy().into_owned()), - ) - .into(); - } - } - if let Some(exec_path) = env::args().next() { - let path = path::Path::new(&exec_path); - if !path.exists() { - return ctx.new_str(ascii!("")).into(); - } - if path.is_absolute() { - return ctx.new_str(exec_path).into(); - } - if let Ok(dir) = env::current_dir() - && let Ok(dir) = dir.into_os_string().into_string() - { - return ctx - .new_str(format!( - "{}/{}", - dir, - exec_path.strip_prefix("./").unwrap_or(&exec_path) - )) - .into(); - } - } - ctx.none() + fn executable(vm: &VirtualMachine) -> String { + vm.state.config.paths.executable.clone() } #[pyattr] @@ -312,8 +208,9 @@ mod sys { #[pyattr] fn path(vm: &VirtualMachine) -> Vec<PyObjectRef> { vm.state - .settings - .path_list + .config + .paths + .module_search_paths .iter() .map(|path| vm.ctx.new_str(path.clone()).into()) .collect() @@ -350,7 +247,7 @@ mod sys { fn _xoptions(vm: &VirtualMachine) -> PyDictRef { let ctx = &vm.ctx; let xopts = ctx.new_dict(); - for (key, value) in &vm.state.settings.xoptions { + for (key, value) in &vm.state.config.settings.xoptions { let value = value.as_ref().map_or_else( || ctx.new_bool(true).into(), |s| ctx.new_str(s.clone()).into(), @@ -363,6 +260,7 @@ mod sys { #[pyattr] fn warnoptions(vm: &VirtualMachine) -> Vec<PyObjectRef> { vm.state + .config .settings .warnoptions .iter() @@ -507,7 +405,7 @@ mod sys { #[pyattr] fn flags(vm: &VirtualMachine) -> PyTupleRef { - PyFlags::from_data(FlagsData::from_settings(&vm.state.settings), vm) + PyFlags::from_data(FlagsData::from_settings(&vm.state.config.settings), vm) } #[pyattr] diff --git a/crates/vm/src/vm/compile.rs b/crates/vm/src/vm/compile.rs index 6f1ea734926..44332cda838 100644 --- a/crates/vm/src/vm/compile.rs +++ b/crates/vm/src/vm/compile.rs @@ -38,7 +38,7 @@ impl VirtualMachine { } // TODO: check if this is proper place - if !self.state.settings.safe_path { + if !self.state.config.settings.safe_path { let dir = std::path::Path::new(path) .parent() .unwrap() diff --git a/crates/vm/src/vm/interpreter.rs b/crates/vm/src/vm/interpreter.rs index 05613d43384..503feb3dc7f 100644 --- a/crates/vm/src/vm/interpreter.rs +++ b/crates/vm/src/vm/interpreter.rs @@ -1,5 +1,5 @@ -use super::{Context, VirtualMachine, setting::Settings, thread}; -use crate::{PyResult, stdlib::atexit, vm::PyBaseExceptionRef}; +use super::{Context, PyConfig, VirtualMachine, setting::Settings, thread}; +use crate::{PyResult, getpath, stdlib::atexit, vm::PyBaseExceptionRef}; use std::sync::atomic::Ordering; /// The general interface for the VM @@ -47,10 +47,14 @@ impl Interpreter { where F: FnOnce(&mut VirtualMachine), { + // Compute path configuration from settings + let paths = getpath::init_path_config(&settings); + let config = PyConfig::new(settings, paths); + let ctx = Context::genesis(); crate::types::TypeZoo::extend(ctx); crate::exceptions::ExceptionZoo::extend(ctx); - let mut vm = VirtualMachine::new(settings, ctx.clone()); + let mut vm = VirtualMachine::new(config, ctx.clone()); init(&mut vm); vm.initialize(); Self { vm } diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 4574b2de370..34092454059 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -50,7 +50,7 @@ use std::{ pub use context::Context; pub use interpreter::Interpreter; pub(crate) use method::PyMethod; -pub use setting::{CheckHashPycsMode, Settings}; +pub use setting::{CheckHashPycsMode, Paths, PyConfig, Settings}; pub const MAX_MEMORY_SIZE: usize = isize::MAX as usize; @@ -87,7 +87,7 @@ struct ExceptionStack { } pub struct PyGlobalState { - pub settings: Settings, + pub config: PyConfig, pub module_inits: stdlib::StdlibMap, pub frozen: HashMap<&'static str, FrozenModule, ahash::RandomState>, pub stacksize: AtomicCell<usize>, @@ -114,7 +114,7 @@ pub fn process_hash_secret_seed() -> u32 { impl VirtualMachine { /// Create a new `VirtualMachine` structure. - fn new(settings: Settings, ctx: PyRc<Context>) -> Self { + fn new(config: PyConfig, ctx: PyRc<Context>) -> Self { flame_guard!("new VirtualMachine"); // make a new module without access to the vm; doesn't @@ -141,7 +141,7 @@ impl VirtualMachine { let module_inits = stdlib::get_module_inits(); - let seed = match settings.hash_seed { + let seed = match config.settings.hash_seed { Some(seed) => seed, None => process_hash_secret_seed(), }; @@ -151,7 +151,7 @@ impl VirtualMachine { let warnings = WarningsState::init_state(&ctx); - let int_max_str_digits = AtomicCell::new(match settings.int_max_str_digits { + let int_max_str_digits = AtomicCell::new(match config.settings.int_max_str_digits { -1 => 4300, other => other, } as usize); @@ -171,7 +171,7 @@ impl VirtualMachine { signal_rx: None, repr_guards: RefCell::default(), state: PyRc::new(PyGlobalState { - settings, + config, module_inits, frozen: HashMap::default(), stacksize: AtomicCell::new(0), @@ -227,7 +227,7 @@ impl VirtualMachine { let rustpythonpath_env = std::env::var("RUSTPYTHONPATH").ok(); let pythonpath_env = std::env::var("PYTHONPATH").ok(); let env_set = rustpythonpath_env.as_ref().is_some() || pythonpath_env.as_ref().is_some(); - let path_contains_env = self.state.settings.path_list.iter().any(|s| { + let path_contains_env = self.state.config.paths.module_search_paths.iter().any(|s| { Some(s.as_str()) == rustpythonpath_env.as_deref() || Some(s.as_str()) == pythonpath_env.as_deref() }); @@ -238,7 +238,7 @@ impl VirtualMachine { } else if path_contains_env { "RUSTPYTHONPATH or PYTHONPATH is set, but it doesn't contain the encodings library. If you are customizing the RustPython vm/interpreter, try adding the stdlib directory to the path. If you are developing the RustPython interpreter, it might be a bug during development." } else { - "RUSTPYTHONPATH or PYTHONPATH is set, but it wasn't loaded to `Settings::path_list`. If you are going to customize the RustPython vm/interpreter, those environment variables are not loaded in the Settings struct by default. Please try creating a customized instance of the Settings struct. If you are developing the RustPython interpreter, it might be a bug during development." + "RUSTPYTHONPATH or PYTHONPATH is set, but it wasn't loaded to `PyConfig::paths::module_search_paths`. If you are going to customize the RustPython vm/interpreter, those environment variables are not loaded in the Settings struct by default. Please try creating a customized instance of the Settings struct. If you are developing the RustPython interpreter, it might be a bug during development." }; let mut msg = format!( @@ -303,7 +303,7 @@ impl VirtualMachine { let io = import::import_builtin(self, "_io")?; #[cfg(feature = "stdio")] let make_stdio = |name, fd, write| { - let buffered_stdio = self.state.settings.buffered_stdio; + let buffered_stdio = self.state.config.settings.buffered_stdio; let unbuffered = write && !buffered_stdio; let buf = crate::stdlib::io::open( self.ctx.new_int(fd).into(), @@ -364,7 +364,7 @@ impl VirtualMachine { let res = essential_init(); let importlib = self.expect_pyresult(res, "essential initialization failed"); - if self.state.settings.allow_external_library + if self.state.config.settings.allow_external_library && cfg!(feature = "rustpython-compiler") && let Err(e) = import::init_importlib_package(self, importlib) { @@ -374,8 +374,8 @@ impl VirtualMachine { self.print_exception(e); } - let _expect_stdlib = - cfg!(feature = "freeze-stdlib") || !self.state.settings.path_list.is_empty(); + let _expect_stdlib = cfg!(feature = "freeze-stdlib") + || !self.state.config.paths.module_search_paths.is_empty(); #[cfg(feature = "encodings")] if _expect_stdlib { @@ -389,7 +389,7 @@ impl VirtualMachine { // Here may not be the best place to give general `path_list` advice, // but bare rustpython_vm::VirtualMachine users skipped proper settings must hit here while properly setup vm never enters here. eprintln!( - "feature `encodings` is enabled but `settings.path_list` is empty. \ + "feature `encodings` is enabled but `paths.module_search_paths` is empty. \ Please add the library path to `settings.path_list`. If you intended to disable the entire standard library (including the `encodings` feature), please also make sure to disable the `encodings` feature.\n\ Tip: You may also want to add `\"\"` to `settings.path_list` in order to enable importing from the current working directory." ); @@ -505,7 +505,7 @@ impl VirtualMachine { #[cfg(feature = "rustpython-codegen")] pub fn compile_opts(&self) -> crate::compiler::CompileOpts { crate::compiler::CompileOpts { - optimize: self.state.settings.optimize, + optimize: self.state.config.settings.optimize, } } diff --git a/crates/vm/src/vm/setting.rs b/crates/vm/src/vm/setting.rs index deaca705c47..53e2cef1160 100644 --- a/crates/vm/src/vm/setting.rs +++ b/crates/vm/src/vm/setting.rs @@ -1,8 +1,40 @@ #[cfg(feature = "flame-it")] use std::ffi::OsString; -/// Struct containing all kind of settings for the python vm. -/// Mostly `PyConfig` in CPython. +/// Path configuration computed at runtime (like PyConfig path outputs) +#[derive(Debug, Clone, Default)] +pub struct Paths { + /// sys.executable + pub executable: String, + /// sys._base_executable (original interpreter in venv) + pub base_executable: String, + /// sys.prefix + pub prefix: String, + /// sys.base_prefix + pub base_prefix: String, + /// sys.exec_prefix + pub exec_prefix: String, + /// sys.base_exec_prefix + pub base_exec_prefix: String, + /// Computed module_search_paths (complete sys.path) + pub module_search_paths: Vec<String>, +} + +/// Combined configuration: user settings + computed paths +/// CPython directly exposes every fields under both of them. +/// We separate them to maintain better ownership discipline. +pub struct PyConfig { + pub settings: Settings, + pub paths: Paths, +} + +impl PyConfig { + pub fn new(settings: Settings, paths: Paths) -> Self { + Self { settings, paths } + } +} + +/// User-configurable settings for the python vm. #[non_exhaustive] pub struct Settings { /// -I diff --git a/src/interpreter.rs b/src/interpreter.rs index b79a1a0ffb4..b4fd319cdae 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -104,23 +104,25 @@ pub fn init_stdlib(vm: &mut VirtualMachine) { use rustpython_vm::common::rc::PyRc; let state = PyRc::get_mut(&mut vm.state).unwrap(); - let settings = &mut state.settings; - let path_list = std::mem::take(&mut settings.path_list); + // Collect additional paths to add + let mut additional_paths = Vec::new(); // BUILDTIME_RUSTPYTHONPATH should be set when distributing if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { - settings.path_list.extend( + additional_paths.extend( crate::settings::split_paths(paths) .map(|path| path.into_os_string().into_string().unwrap()), ) } else { #[cfg(feature = "rustpython-pylib")] - settings - .path_list - .push(rustpython_pylib::LIB_PATH.to_owned()) + additional_paths.push(rustpython_pylib::LIB_PATH.to_owned()) } - settings.path_list.extend(path_list); + // Add to both path_list (for compatibility) and module_search_paths (for sys.path) + // Insert at the beginning so stdlib comes before user paths + for path in additional_paths.into_iter().rev() { + state.config.paths.module_search_paths.insert(0, path); + } } } diff --git a/src/lib.rs b/src/lib.rs index 84a774ab029..8d278058933 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,7 +169,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { let scope = setup_main_module(vm)?; - if !vm.state.settings.safe_path { + if !vm.state.config.settings.safe_path { // TODO: The prepending path depends on running mode // See https://docs.python.org/3/using/cmdline.html#cmdoption-P vm.run_code_string( @@ -189,7 +189,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { // Enable faulthandler if -X faulthandler, PYTHONFAULTHANDLER or -X dev is set // _PyFaulthandler_Init() - if vm.state.settings.faulthandler { + if vm.state.config.settings.faulthandler { let _ = vm.run_code_string( vm.new_scope_with_builtins(), "import faulthandler; faulthandler.enable()", @@ -198,8 +198,8 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { } let is_repl = matches!(run_mode, RunMode::Repl); - if !vm.state.settings.quiet - && (vm.state.settings.verbose > 0 || (is_repl && std::io::stdin().is_terminal())) + if !vm.state.config.settings.quiet + && (vm.state.config.settings.verbose > 0 || (is_repl && std::io::stdin().is_terminal())) { eprintln!( "Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}", @@ -232,7 +232,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { } RunMode::Repl => Ok(()), }; - if is_repl || vm.state.settings.inspect { + if is_repl || vm.state.config.settings.inspect { shell::run_shell(vm, scope)?; } else { res?; @@ -241,7 +241,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { #[cfg(feature = "flame-it")] { main_guard.end(); - if let Err(e) = write_profile(&vm.state.as_ref().settings) { + if let Err(e) = write_profile(&vm.state.as_ref().config.settings) { error!("Error writing profile information: {}", e); } } From 569bee103ff528ac8d70c3b5b78c70378e6cd115 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 22 Dec 2025 04:43:58 +0100 Subject: [PATCH 535/819] Use `ruff_python_ast::visitor::Visitor` for detecting `await` (#6466) --- crates/codegen/src/compile.rs | 158 +++++++--------------------------- 1 file changed, 30 insertions(+), 128 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 9620edbd107..4f7174c8bad 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -24,12 +24,13 @@ use ruff_python_ast::{ Alias, Arguments, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText, Decorator, DictItem, ExceptHandler, ExceptHandlerExceptHandler, Expr, ExprAttribute, ExprBoolOp, ExprContext, ExprFString, ExprList, ExprName, ExprSlice, ExprStarred, ExprSubscript, ExprTuple, ExprUnaryOp, - FString, FStringFlags, FStringPart, Identifier, Int, InterpolatedElement, - InterpolatedStringElement, InterpolatedStringElements, Keyword, MatchCase, ModExpression, - ModModule, Operator, Parameters, Pattern, PatternMatchAs, PatternMatchClass, - PatternMatchMapping, PatternMatchOr, PatternMatchSequence, PatternMatchSingleton, - PatternMatchStar, PatternMatchValue, Singleton, Stmt, StmtExpr, TypeParam, TypeParamParamSpec, - TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem, + FString, FStringFlags, FStringPart, Identifier, Int, InterpolatedStringElement, + InterpolatedStringElements, Keyword, MatchCase, ModExpression, ModModule, Operator, Parameters, + Pattern, PatternMatchAs, PatternMatchClass, PatternMatchMapping, PatternMatchOr, + PatternMatchSequence, PatternMatchSingleton, PatternMatchStar, PatternMatchValue, Singleton, + Stmt, StmtExpr, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, + TypeParams, UnaryOp, WithItem, + visitor::{Visitor, walk_expr}, }; use ruff_text_size::{Ranged, TextRange}; use rustpython_compiler_core::{ @@ -5435,134 +5436,35 @@ impl Compiler { /// Whether the expression contains an await expression and /// thus requires the function to be async. - /// Async with and async for are statements, so I won't check for them here + /// + /// Both: + /// ```py + /// async with: ... + /// async for: ... + /// ``` + /// are statements, so we won't check for them here fn contains_await(expression: &Expr) -> bool { - use ruff_python_ast::*; + #[derive(Default)] + struct AwaitVisitor { + found: bool, + } - match &expression { - Expr::Call(ExprCall { - func, arguments, .. - }) => { - Self::contains_await(func) - || arguments.args.iter().any(Self::contains_await) - || arguments - .keywords - .iter() - .any(|kw| Self::contains_await(&kw.value)) - } - Expr::BoolOp(ExprBoolOp { values, .. }) => values.iter().any(Self::contains_await), - Expr::BinOp(ExprBinOp { left, right, .. }) => { - Self::contains_await(left) || Self::contains_await(right) - } - Expr::Subscript(ExprSubscript { value, slice, .. }) => { - Self::contains_await(value) || Self::contains_await(slice) - } - Expr::UnaryOp(ExprUnaryOp { operand, .. }) => Self::contains_await(operand), - Expr::Attribute(ExprAttribute { value, .. }) => Self::contains_await(value), - Expr::Compare(ExprCompare { - left, comparators, .. - }) => Self::contains_await(left) || comparators.iter().any(Self::contains_await), - Expr::List(ExprList { elts, .. }) => elts.iter().any(Self::contains_await), - Expr::Tuple(ExprTuple { elts, .. }) => elts.iter().any(Self::contains_await), - Expr::Set(ExprSet { elts, .. }) => elts.iter().any(Self::contains_await), - Expr::Dict(ExprDict { items, .. }) => items - .iter() - .flat_map(|item| &item.key) - .any(Self::contains_await), - Expr::Slice(ExprSlice { - lower, upper, step, .. - }) => { - lower.as_deref().is_some_and(Self::contains_await) - || upper.as_deref().is_some_and(Self::contains_await) - || step.as_deref().is_some_and(Self::contains_await) - } - Expr::Yield(ExprYield { value, .. }) => { - value.as_deref().is_some_and(Self::contains_await) - } - Expr::Await(ExprAwait { .. }) => true, - Expr::YieldFrom(ExprYieldFrom { value, .. }) => Self::contains_await(value), - Expr::Name(ExprName { .. }) => false, - Expr::Lambda(ExprLambda { body, .. }) => Self::contains_await(body), - Expr::ListComp(ExprListComp { - elt, generators, .. - }) => { - Self::contains_await(elt) - || generators.iter().any(|jen| Self::contains_await(&jen.iter)) - } - Expr::SetComp(ExprSetComp { - elt, generators, .. - }) => { - Self::contains_await(elt) - || generators.iter().any(|jen| Self::contains_await(&jen.iter)) - } - Expr::DictComp(ExprDictComp { - key, - value, - generators, - .. - }) => { - Self::contains_await(key) - || Self::contains_await(value) - || generators.iter().any(|jen| Self::contains_await(&jen.iter)) - } - Expr::Generator(ExprGenerator { - elt, generators, .. - }) => { - Self::contains_await(elt) - || generators.iter().any(|jen| Self::contains_await(&jen.iter)) - } - Expr::Starred(expr) => Self::contains_await(&expr.value), - Expr::If(ExprIf { - test, body, orelse, .. - }) => { - Self::contains_await(test) - || Self::contains_await(body) - || Self::contains_await(orelse) - } + impl Visitor<'_> for AwaitVisitor { + fn visit_expr(&mut self, expr: &Expr) { + if self.found { + return; + } - Expr::Named(ExprNamed { - target, - value, - node_index: _, - range: _, - }) => Self::contains_await(target) || Self::contains_await(value), - Expr::FString(fstring) => { - Self::interpolated_string_contains_await(fstring.value.elements()) - } - Expr::TString(tstring) => { - Self::interpolated_string_contains_await(tstring.value.elements()) + match expr { + Expr::Await(_) => self.found = true, + _ => walk_expr(self, expr), + } } - Expr::StringLiteral(_) - | Expr::BytesLiteral(_) - | Expr::NumberLiteral(_) - | Expr::BooleanLiteral(_) - | Expr::NoneLiteral(_) - | Expr::EllipsisLiteral(_) - | Expr::IpyEscapeCommand(_) => false, - } - } - - fn interpolated_string_contains_await<'a>( - mut elements: impl Iterator<Item = &'a InterpolatedStringElement>, - ) -> bool { - fn interpolated_element_contains_await<F: Copy + Fn(&Expr) -> bool>( - expr_element: &InterpolatedElement, - contains_await: F, - ) -> bool { - contains_await(&expr_element.expression) - || expr_element - .format_spec - .iter() - .flat_map(|spec| spec.elements.interpolations()) - .any(|element| interpolated_element_contains_await(element, contains_await)) } - elements.any(|element| match element { - InterpolatedStringElement::Interpolation(expr_element) => { - interpolated_element_contains_await(expr_element, Self::contains_await) - } - InterpolatedStringElement::Literal(_) => false, - }) + let mut visitor = AwaitVisitor::default(); + visitor.visit_expr(expression); + visitor.found } fn compile_expr_fstring(&mut self, fstring: &ExprFString) -> CompileResult<()> { From fdd2ac3b30827c1d44c5c5040eb184e65cf2534e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 22 Dec 2025 21:10:43 +0900 Subject: [PATCH 536/819] disallow __new__, __init__ (#6446) * disallow __new__, __init__ * migrate to Initializer * apply review --- crates/derive-impl/src/pyclass.rs | 125 +++++++----- crates/stdlib/src/sqlite.rs | 41 ++-- crates/stdlib/src/ssl/error.rs | 2 +- crates/vm/src/builtins/object.rs | 32 +-- crates/vm/src/exception_group.rs | 244 ++++++++++++----------- crates/vm/src/exceptions.rs | 304 ++++++++++++++++------------- crates/vm/src/stdlib/ast/python.rs | 79 ++++---- 7 files changed, 458 insertions(+), 369 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index f784a2e2a76..d1fe5398634 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -63,6 +63,7 @@ impl FromStr for AttrName { #[derive(Default)] struct ImplContext { + is_trait: bool, attribute_items: ItemNursery, method_items: MethodNursery, getset_items: GetSetNursery, @@ -232,7 +233,10 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul } } Item::Trait(mut trai) => { - let mut context = ImplContext::default(); + let mut context = ImplContext { + is_trait: true, + ..Default::default() + }; let mut has_extend_slots = false; for item in &trai.items { let has = match item { @@ -710,21 +714,16 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R }; // Check if with(Constructor) is specified. If Constructor trait is used, don't generate slot_new - let mut has_slot_new = false; - let mut extra_attrs = Vec::new(); + let mut with_items = vec![]; for nested in &attr { if let NestedMeta::Meta(Meta::List(MetaList { path, nested, .. })) = nested { // If we already found the constructor trait, no need to keep looking for it - if !has_slot_new && path.is_ident("with") { - // Check if Constructor is in the list + if path.is_ident("with") { for meta in nested { - if let NestedMeta::Meta(Meta::Path(p)) = meta - && p.is_ident("Constructor") - { - has_slot_new = true; - } + with_items.push(meta.get_ident().expect("with() has non-ident item").clone()); } + continue; } extra_attrs.push(NestedMeta::Meta(Meta::List(MetaList { path: path.clone(), @@ -734,43 +733,40 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R } } - let mut has_slot_init = false; + let with_contains = |with_items: &[Ident], s: &str| { + // Check if Constructor is in the list + with_items.iter().any(|ident| ident == s) + }; + let syn::ItemImpl { generics, self_ty, items, .. } = &imp; - for item in items { - // FIXME: better detection or correct wrapper implementation - let Some(ident) = item.get_ident() else { - continue; - }; - let item_name = ident.to_string(); - match item_name.as_str() { - "slot_new" => { - has_slot_new = true; - } - "slot_init" => { - has_slot_init = true; - } - _ => continue, - } - } - - // TODO: slot_new, slot_init must be Constructor or Initializer later - let slot_new = if has_slot_new { + let slot_new = if with_contains(&with_items, "Constructor") { quote!() } else { + with_items.push(Ident::new("Constructor", Span::call_site())); quote! { - #[pyslot] - pub fn slot_new( - cls: ::rustpython_vm::builtins::PyTypeRef, - args: ::rustpython_vm::function::FuncArgs, - vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult { - <Self as ::rustpython_vm::class::PyClassDef>::Base::slot_new(cls, args, vm) + impl ::rustpython_vm::types::Constructor for #self_ty { + type Args = ::rustpython_vm::function::FuncArgs; + + fn slot_new( + cls: ::rustpython_vm::builtins::PyTypeRef, + args: ::rustpython_vm::function::FuncArgs, + vm: &::rustpython_vm::VirtualMachine, + ) -> ::rustpython_vm::PyResult { + <Self as ::rustpython_vm::class::PyClassDef>::Base::slot_new(cls, args, vm) + } + fn py_new( + _cls: &::rustpython_vm::Py<::rustpython_vm::builtins::PyType>, + _args: Self::Args, + _vm: &::rustpython_vm::VirtualMachine + ) -> ::rustpython_vm::PyResult<Self> { + unreachable!("slot_new is defined") + } } } }; @@ -779,19 +775,29 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R // from `BaseException` in `SimpleExtendsException` macro. // See: `(initproc)BaseException_init` // spell-checker:ignore initproc - let slot_init = if has_slot_init { + let slot_init = if with_contains(&with_items, "Initializer") { quote!() } else { - // FIXME: this is a generic logic for types not only for exceptions + with_items.push(Ident::new("Initializer", Span::call_site())); quote! { - #[pyslot] - #[pymethod(name="__init__")] - pub fn slot_init( - zelf: ::rustpython_vm::PyObjectRef, - args: ::rustpython_vm::function::FuncArgs, - vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult<()> { - <Self as ::rustpython_vm::class::PyClassDef>::Base::slot_init(zelf, args, vm) + impl ::rustpython_vm::types::Initializer for #self_ty { + type Args = ::rustpython_vm::function::FuncArgs; + + fn slot_init( + zelf: ::rustpython_vm::PyObjectRef, + args: ::rustpython_vm::function::FuncArgs, + vm: &::rustpython_vm::VirtualMachine, + ) -> ::rustpython_vm::PyResult<()> { + <Self as ::rustpython_vm::class::PyClassDef>::Base::slot_init(zelf, args, vm) + } + + fn init( + _zelf: ::rustpython_vm::PyRef<Self>, + _args: Self::Args, + _vm: &::rustpython_vm::VirtualMachine + ) -> ::rustpython_vm::PyResult<()> { + unreachable!("slot_init is defined") + } } } }; @@ -803,13 +809,13 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R }; Ok(quote! { - #[pyclass(flags(BASETYPE, HAS_DICT) #extra_attrs_tokens)] + #[pyclass(flags(BASETYPE, HAS_DICT), with(#(#with_items),*) #extra_attrs_tokens)] impl #generics #self_ty { #(#items)* - - #slot_new - #slot_init } + + #slot_new + #slot_init }) } @@ -892,6 +898,23 @@ where let item_meta = MethodItemMeta::from_attr(ident.clone(), &item_attr)?; let py_name = item_meta.method_name()?; + + // Disallow __new__ and __init__ as pymethod in impl blocks (not in traits) + if !args.context.is_trait { + if py_name == "__new__" { + return Err(syn::Error::new( + ident.span(), + "#[pymethod] cannot define '__new__'. Use #[pyclass(with(Constructor))] instead.", + )); + } + if py_name == "__init__" { + return Err(syn::Error::new( + ident.span(), + "#[pymethod] cannot define '__init__'. Use #[pyclass(with(Initializer))] instead.", + )); + } + } + let raw = item_meta.raw()?; let sig_doc = text_signature(func.sig(), &py_name); diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index bc84cffbf80..ffdc9eb3831 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -1540,7 +1540,7 @@ mod _sqlite { size: Option<c_int>, } - #[pyclass(with(Constructor, IterNext, Iterable), flags(BASETYPE))] + #[pyclass(with(Constructor, Initializer, IterNext, Iterable), flags(BASETYPE))] impl Cursor { fn new( connection: PyRef<Connection>, @@ -1571,24 +1571,6 @@ mod _sqlite { } } - #[pymethod] - fn __init__(&self, _connection: PyRef<Connection>, _vm: &VirtualMachine) -> PyResult<()> { - let mut guard = self.inner.lock(); - if guard.is_some() { - // Already initialized (e.g., from a call to super().__init__) - return Ok(()); - } - *guard = Some(CursorInner { - description: None, - row_cast_map: vec![], - lastrowid: -1, - rowcount: -1, - statement: None, - closed: false, - }); - Ok(()) - } - fn check_cursor_state(inner: Option<&CursorInner>, vm: &VirtualMachine) -> PyResult<()> { match inner { Some(inner) if inner.closed => Err(new_programming_error( @@ -1949,6 +1931,27 @@ mod _sqlite { } } + impl Initializer for Cursor { + type Args = PyRef<Connection>; + + fn init(zelf: PyRef<Self>, _connection: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + let mut guard = zelf.inner.lock(); + if guard.is_some() { + // Already initialized (e.g., from a call to super().__init__) + return Ok(()); + } + *guard = Some(CursorInner { + description: None, + row_cast_map: vec![], + lastrowid: -1, + rowcount: -1, + statement: None, + closed: false, + }); + Ok(()) + } + } + impl SelfIter for Cursor {} impl IterNext for Cursor { fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> { diff --git a/crates/stdlib/src/ssl/error.rs b/crates/stdlib/src/ssl/error.rs index bef9ba513d7..879275228ec 100644 --- a/crates/stdlib/src/ssl/error.rs +++ b/crates/stdlib/src/ssl/error.rs @@ -7,7 +7,7 @@ pub(crate) mod ssl_error { use crate::vm::{ PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyBaseExceptionRef, PyOSError, PyStrRef}, - types::Constructor, + types::{Constructor, Initializer}, }; // Error type constants - exposed as pyattr and available for internal use diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index cb95652f937..0970496c7b1 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -2,11 +2,11 @@ use super::{PyDictRef, PyList, PyStr, PyStrRef, PyType, PyTypeRef}; use crate::common::hash::PyHash; use crate::types::PyTypeFlags; use crate::{ - AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, + AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, convert::ToPyResult, function::{Either, FuncArgs, PyArithmeticValue, PyComparisonValue, PySetterValue}, - types::{Constructor, PyComparisonOp}, + types::{Constructor, Initializer, PyComparisonOp}, }; use itertools::Itertools; @@ -115,6 +115,18 @@ impl Constructor for PyBaseObject { } } +impl Initializer for PyBaseObject { + type Args = FuncArgs; + + fn slot_init(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> { + Ok(()) + } + + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } +} + // TODO: implement _PyType_GetSlotNames properly fn type_slot_names(typ: &Py<PyType>, vm: &VirtualMachine) -> PyResult<Option<super::PyListRef>> { // let attributes = typ.attributes.read(); @@ -235,7 +247,7 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine) // getstate.call((), vm) // } -#[pyclass(with(Constructor), flags(BASETYPE))] +#[pyclass(with(Constructor, Initializer), flags(BASETYPE))] impl PyBaseObject { #[pymethod(raw)] fn __getstate__(vm: &VirtualMachine, args: FuncArgs) -> PyResult { @@ -444,19 +456,17 @@ impl PyBaseObject { obj.str(vm) } - #[pyslot] - #[pymethod] - fn __init__(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> { - Ok(()) - } - #[pygetset] fn __class__(obj: PyObjectRef) -> PyTypeRef { obj.class().to_owned() } - #[pygetset(name = "__class__", setter)] - fn set_class(instance: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + #[pygetset(setter)] + fn set___class__( + instance: PyObjectRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { match value.downcast::<PyType>() { Ok(cls) => { let both_module = instance.class().fast_issubclass(vm.ctx.types.module_type) diff --git a/crates/vm/src/exception_group.rs b/crates/vm/src/exception_group.rs index cd943ae1bd9..e19dbceb8da 100644 --- a/crates/vm/src/exception_group.rs +++ b/crates/vm/src/exception_group.rs @@ -43,13 +43,14 @@ pub(super) mod types { use super::*; use crate::PyPayload; use crate::builtins::PyGenericAlias; + use crate::types::{Constructor, Initializer}; #[pyexception(name, base = PyBaseException, ctx = "base_exception_group")] #[derive(Debug)] #[repr(transparent)] pub struct PyBaseExceptionGroup(PyBaseException); - #[pyexception] + #[pyexception(with(Constructor, Initializer))] impl PyBaseExceptionGroup { #[pyclassmethod] fn __class_getitem__( @@ -60,117 +61,6 @@ pub(super) mod types { PyGenericAlias::from_args(cls, args, vm) } - #[pyslot] - fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // Validate exactly 2 positional arguments - if args.args.len() != 2 { - return Err(vm.new_type_error(format!( - "BaseExceptionGroup.__new__() takes exactly 2 positional arguments ({} given)", - args.args.len() - ))); - } - - // Validate message is str - let message = args.args[0].clone(); - if !message.fast_isinstance(vm.ctx.types.str_type) { - return Err(vm.new_type_error(format!( - "argument 1 must be str, not {}", - message.class().name() - ))); - } - - // Validate exceptions is a sequence (not set or None) - let exceptions_arg = &args.args[1]; - - // Check for set/frozenset (not a sequence - unordered) - if exceptions_arg.fast_isinstance(vm.ctx.types.set_type) - || exceptions_arg.fast_isinstance(vm.ctx.types.frozenset_type) - { - return Err(vm.new_type_error("second argument (exceptions) must be a sequence")); - } - - // Check for None - if exceptions_arg.is(&vm.ctx.none) { - return Err(vm.new_type_error("second argument (exceptions) must be a sequence")); - } - - let exceptions: Vec<PyObjectRef> = exceptions_arg.try_to_value(vm).map_err(|_| { - vm.new_type_error("second argument (exceptions) must be a sequence") - })?; - - // Validate non-empty - if exceptions.is_empty() { - return Err(vm.new_value_error( - "second argument (exceptions) must be a non-empty sequence".to_owned(), - )); - } - - // Validate all items are BaseException instances - let mut has_non_exception = false; - for (i, exc) in exceptions.iter().enumerate() { - if !exc.fast_isinstance(vm.ctx.exceptions.base_exception_type) { - return Err(vm.new_value_error(format!( - "Item {} of second argument (exceptions) is not an exception", - i - ))); - } - // Check if any exception is not an Exception subclass - // With dynamic ExceptionGroup (inherits from both BaseExceptionGroup and Exception), - // ExceptionGroup instances are automatically instances of Exception - if !exc.fast_isinstance(vm.ctx.exceptions.exception_type) { - has_non_exception = true; - } - } - - // Get the dynamic ExceptionGroup type - let exception_group_type = crate::exception_group::exception_group(); - - // Determine the actual class to use - let actual_cls = if cls.is(exception_group_type) { - // ExceptionGroup cannot contain BaseExceptions that are not Exception - if has_non_exception { - return Err( - vm.new_type_error("Cannot nest BaseExceptions in an ExceptionGroup") - ); - } - cls - } else if cls.is(vm.ctx.exceptions.base_exception_group) { - // Auto-convert to ExceptionGroup if all are Exception subclasses - if !has_non_exception { - exception_group_type.to_owned() - } else { - cls - } - } else { - // User-defined subclass - if has_non_exception && cls.fast_issubclass(vm.ctx.exceptions.exception_type) { - return Err(vm.new_type_error(format!( - "Cannot nest BaseExceptions in '{}'", - cls.name() - ))); - } - cls - }; - - // Create the exception with (message, exceptions_tuple) as args - let exceptions_tuple = vm.ctx.new_tuple(exceptions); - let init_args = vec![message, exceptions_tuple.into()]; - PyBaseException::new(init_args, vm) - .into_ref_with_type(vm, actual_cls) - .map(Into::into) - } - - #[pyslot] - #[pymethod(name = "__init__")] - fn slot_init(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> { - // CPython's BaseExceptionGroup.__init__ just calls BaseException.__init__ - // which stores args as-is. Since __new__ already set up the correct args - // (message, exceptions_tuple), we don't need to do anything here. - // This also allows subclasses to pass extra arguments to __new__ without - // __init__ complaining about argument count. - Ok(()) - } - #[pymethod] fn derive( zelf: PyRef<PyBaseException>, @@ -351,6 +241,136 @@ pub(super) mod types { } } + impl Constructor for PyBaseExceptionGroup { + type Args = crate::function::PosArgs; + + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let args: Self::Args = args.bind(vm)?; + let args = args.into_vec(); + // Validate exactly 2 positional arguments + if args.len() != 2 { + return Err(vm.new_type_error(format!( + "BaseExceptionGroup.__new__() takes exactly 2 positional arguments ({} given)", + args.len() + ))); + } + + // Validate message is str + let message = args[0].clone(); + if !message.fast_isinstance(vm.ctx.types.str_type) { + return Err(vm.new_type_error(format!( + "argument 1 must be str, not {}", + message.class().name() + ))); + } + + // Validate exceptions is a sequence (not set or None) + let exceptions_arg = &args[1]; + + // Check for set/frozenset (not a sequence - unordered) + if exceptions_arg.fast_isinstance(vm.ctx.types.set_type) + || exceptions_arg.fast_isinstance(vm.ctx.types.frozenset_type) + { + return Err(vm.new_type_error("second argument (exceptions) must be a sequence")); + } + + // Check for None + if exceptions_arg.is(&vm.ctx.none) { + return Err(vm.new_type_error("second argument (exceptions) must be a sequence")); + } + + let exceptions: Vec<PyObjectRef> = exceptions_arg.try_to_value(vm).map_err(|_| { + vm.new_type_error("second argument (exceptions) must be a sequence") + })?; + + // Validate non-empty + if exceptions.is_empty() { + return Err(vm.new_value_error( + "second argument (exceptions) must be a non-empty sequence".to_owned(), + )); + } + + // Validate all items are BaseException instances + let mut has_non_exception = false; + for (i, exc) in exceptions.iter().enumerate() { + if !exc.fast_isinstance(vm.ctx.exceptions.base_exception_type) { + return Err(vm.new_value_error(format!( + "Item {} of second argument (exceptions) is not an exception", + i + ))); + } + // Check if any exception is not an Exception subclass + // With dynamic ExceptionGroup (inherits from both BaseExceptionGroup and Exception), + // ExceptionGroup instances are automatically instances of Exception + if !exc.fast_isinstance(vm.ctx.exceptions.exception_type) { + has_non_exception = true; + } + } + + // Get the dynamic ExceptionGroup type + let exception_group_type = crate::exception_group::exception_group(); + + // Determine the actual class to use + let actual_cls = if cls.is(exception_group_type) { + // ExceptionGroup cannot contain BaseExceptions that are not Exception + if has_non_exception { + return Err( + vm.new_type_error("Cannot nest BaseExceptions in an ExceptionGroup") + ); + } + cls + } else if cls.is(vm.ctx.exceptions.base_exception_group) { + // Auto-convert to ExceptionGroup if all are Exception subclasses + if !has_non_exception { + exception_group_type.to_owned() + } else { + cls + } + } else { + // User-defined subclass + if has_non_exception && cls.fast_issubclass(vm.ctx.exceptions.exception_type) { + return Err(vm.new_type_error(format!( + "Cannot nest BaseExceptions in '{}'", + cls.name() + ))); + } + cls + }; + + // Create the exception with (message, exceptions_tuple) as args + let exceptions_tuple = vm.ctx.new_tuple(exceptions); + let init_args = vec![message, exceptions_tuple.into()]; + PyBaseException::new(init_args, vm) + .into_ref_with_type(vm, actual_cls) + .map(Into::into) + } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unimplemented!("use slot_new") + } + } + + impl Initializer for PyBaseExceptionGroup { + type Args = FuncArgs; + + fn slot_init( + _zelf: PyObjectRef, + _args: ::rustpython_vm::function::FuncArgs, + _vm: &::rustpython_vm::VirtualMachine, + ) -> ::rustpython_vm::PyResult<()> { + // CPython's BaseExceptionGroup.__init__ just calls BaseException.__init__ + // which stores args as-is. Since __new__ already set up the correct args + // (message, exceptions_tuple), we don't need to do anything here. + // This also allows subclasses to pass extra arguments to __new__ without + // __init__ complaining about argument count. + Ok(()) + } + + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + // Helper functions for ExceptionGroup fn is_base_exception_group(obj: &PyObject, vm: &VirtualMachine) -> bool { obj.fast_isinstance(vm.ctx.exceptions.base_exception_group) diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index bb10ca02c2c..2c36aa13bd5 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1371,18 +1371,19 @@ pub(super) mod types { #[repr(transparent)] pub struct PyStopIteration(PyException); - #[pyexception] - impl PyStopIteration { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: ::rustpython_vm::function::FuncArgs, - vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult<()> { + #[pyexception(with(Initializer))] + impl PyStopIteration {} + + impl Initializer for PyStopIteration { + type Args = FuncArgs; + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { zelf.set_attr("value", vm.unwrap_or_none(args.args.first().cloned()), vm)?; Ok(()) } + + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } } #[pyexception(name, base = PyException, ctx = "stop_async_iteration", impl)] @@ -1419,15 +1420,13 @@ pub(super) mod types { #[repr(transparent)] pub struct PyAttributeError(PyException); - #[pyexception] - impl PyAttributeError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: ::rustpython_vm::function::FuncArgs, - vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult<()> { + #[pyexception(with(Initializer))] + impl PyAttributeError {} + + impl Initializer for PyAttributeError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { zelf.set_attr( "name", vm.unwrap_or_none(args.kwargs.get("name").cloned()), @@ -1440,6 +1439,10 @@ pub(super) mod types { )?; Ok(()) } + + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } } #[pyexception(name, base = PyException, ctx = "buffer_error", impl)] @@ -1457,15 +1460,28 @@ pub(super) mod types { #[repr(transparent)] pub struct PyImportError(PyException); - #[pyexception] + #[pyexception(with(Initializer))] impl PyImportError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: ::rustpython_vm::function::FuncArgs, - vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult<()> { + #[pymethod] + fn __reduce__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyTupleRef { + let obj = exc.as_object().to_owned(); + let mut result: Vec<PyObjectRef> = vec![ + obj.class().to_owned().into(), + vm.new_tuple((exc.get_arg(0).unwrap(),)).into(), + ]; + + if let Some(dict) = obj.dict().filter(|x| !x.is_empty()) { + result.push(dict.into()); + } + + result.into_pytuple(vm) + } + } + + impl Initializer for PyImportError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { let mut kwargs = args.kwargs.clone(); let name = kwargs.swap_remove("name"); let path = kwargs.swap_remove("path"); @@ -1482,19 +1498,9 @@ pub(super) mod types { dict.set_item("path", vm.unwrap_or_none(path), vm)?; PyBaseException::slot_init(zelf, args, vm) } - #[pymethod] - fn __reduce__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyTupleRef { - let obj = exc.as_object().to_owned(); - let mut result: Vec<PyObjectRef> = vec![ - obj.class().to_owned().into(), - vm.new_tuple((exc.get_arg(0).unwrap(),)).into(), - ]; - if let Some(dict) = obj.dict().filter(|x| !x.is_empty()) { - result.push(dict.into()); - } - - result.into_pytuple(vm) + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") } } @@ -1660,11 +1666,10 @@ pub(super) mod types { } } - #[pyexception(with(Constructor))] - impl PyOSError { - #[pyslot] - #[pymethod(name = "__init__")] - pub fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + impl Initializer for PyOSError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { let len = args.args.len(); let mut new_args = args; @@ -1718,6 +1723,13 @@ pub(super) mod types { PyBaseException::slot_init(zelf, new_args, vm) } + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + + #[pyexception(with(Constructor, Initializer))] + impl PyOSError { #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { let obj = exc.as_object().to_owned(); @@ -2011,44 +2023,8 @@ pub(super) mod types { #[repr(transparent)] pub struct PySyntaxError(PyException); - #[pyexception] + #[pyexception(with(Initializer))] impl PySyntaxError { - #[pyslot] - #[pymethod(name = "__init__")] - fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - let len = args.args.len(); - let new_args = args; - - zelf.set_attr("print_file_and_line", vm.ctx.none(), vm)?; - - if len == 2 - && let Ok(location_tuple) = new_args.args[1] - .clone() - .downcast::<crate::builtins::PyTuple>() - { - let location_tup_len = location_tuple.len(); - for (i, &attr) in [ - "filename", - "lineno", - "offset", - "text", - "end_lineno", - "end_offset", - ] - .iter() - .enumerate() - { - if location_tup_len > i { - zelf.set_attr(attr, location_tuple[i].to_owned(), vm)?; - } else { - break; - } - } - } - - PyBaseException::slot_init(zelf, new_args, vm) - } - #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyStrRef { fn basename(filename: &str) -> &str { @@ -2097,6 +2073,48 @@ pub(super) mod types { } } + impl Initializer for PySyntaxError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + let len = args.args.len(); + let new_args = args; + + zelf.set_attr("print_file_and_line", vm.ctx.none(), vm)?; + + if len == 2 + && let Ok(location_tuple) = new_args.args[1] + .clone() + .downcast::<crate::builtins::PyTuple>() + { + let location_tup_len = location_tuple.len(); + for (i, &attr) in [ + "filename", + "lineno", + "offset", + "text", + "end_lineno", + "end_offset", + ] + .iter() + .enumerate() + { + if location_tup_len > i { + zelf.set_attr(attr, location_tuple[i].to_owned(), vm)?; + } else { + break; + } + } + } + + PyBaseException::slot_init(zelf, new_args, vm) + } + + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + #[pyexception( name = "_IncompleteInputError", base = PySyntaxError, @@ -2106,17 +2124,19 @@ pub(super) mod types { #[repr(transparent)] pub struct PyIncompleteInputError(PySyntaxError); - #[pyexception] - impl PyIncompleteInputError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - _args: FuncArgs, - vm: &VirtualMachine, - ) -> PyResult<()> { + #[pyexception(with(Initializer))] + impl PyIncompleteInputError {} + + impl Initializer for PyIncompleteInputError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { zelf.set_attr("name", vm.ctx.new_str("SyntaxError"), vm)?; - Ok(()) + PySyntaxError::slot_init(zelf, args, vm) + } + + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") } } @@ -2155,26 +2175,8 @@ pub(super) mod types { #[repr(transparent)] pub struct PyUnicodeDecodeError(PyUnicodeError); - #[pyexception] + #[pyexception(with(Initializer))] impl PyUnicodeDecodeError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: FuncArgs, - vm: &VirtualMachine, - ) -> PyResult<()> { - type Args = (PyStrRef, ArgBytesLike, isize, isize, PyStrRef); - let (encoding, object, start, end, reason): Args = args.bind(vm)?; - zelf.set_attr("encoding", encoding, vm)?; - let object_as_bytes = vm.ctx.new_bytes(object.borrow_buf().to_vec()); - zelf.set_attr("object", object_as_bytes, vm)?; - zelf.set_attr("start", vm.ctx.new_int(start), vm)?; - zelf.set_attr("end", vm.ctx.new_int(end), vm)?; - zelf.set_attr("reason", reason, vm)?; - Ok(()) - } - #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<String> { let Ok(object) = exc.as_object().get_attr("object", vm) else { @@ -2202,30 +2204,33 @@ pub(super) mod types { } } - #[pyexception(name, base = PyUnicodeError, ctx = "unicode_encode_error")] - #[derive(Debug)] - #[repr(transparent)] - pub struct PyUnicodeEncodeError(PyUnicodeError); + impl Initializer for PyUnicodeDecodeError { + type Args = FuncArgs; - #[pyexception] - impl PyUnicodeEncodeError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: FuncArgs, - vm: &VirtualMachine, - ) -> PyResult<()> { - type Args = (PyStrRef, PyStrRef, isize, isize, PyStrRef); + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + type Args = (PyStrRef, ArgBytesLike, isize, isize, PyStrRef); let (encoding, object, start, end, reason): Args = args.bind(vm)?; zelf.set_attr("encoding", encoding, vm)?; - zelf.set_attr("object", object, vm)?; + let object_as_bytes = vm.ctx.new_bytes(object.borrow_buf().to_vec()); + zelf.set_attr("object", object_as_bytes, vm)?; zelf.set_attr("start", vm.ctx.new_int(start), vm)?; zelf.set_attr("end", vm.ctx.new_int(end), vm)?; zelf.set_attr("reason", reason, vm)?; Ok(()) } + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + + #[pyexception(name, base = PyUnicodeError, ctx = "unicode_encode_error")] + #[derive(Debug)] + #[repr(transparent)] + pub struct PyUnicodeEncodeError(PyUnicodeError); + + #[pyexception(with(Initializer))] + impl PyUnicodeEncodeError { #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<String> { let Ok(object) = exc.as_object().get_attr("object", vm) else { @@ -2254,22 +2259,13 @@ pub(super) mod types { } } - #[pyexception(name, base = PyUnicodeError, ctx = "unicode_translate_error")] - #[derive(Debug)] - #[repr(transparent)] - pub struct PyUnicodeTranslateError(PyUnicodeError); + impl Initializer for PyUnicodeEncodeError { + type Args = FuncArgs; - #[pyexception] - impl PyUnicodeTranslateError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: FuncArgs, - vm: &VirtualMachine, - ) -> PyResult<()> { - type Args = (PyStrRef, isize, isize, PyStrRef); - let (object, start, end, reason): Args = args.bind(vm)?; + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + type Args = (PyStrRef, PyStrRef, isize, isize, PyStrRef); + let (encoding, object, start, end, reason): Args = args.bind(vm)?; + zelf.set_attr("encoding", encoding, vm)?; zelf.set_attr("object", object, vm)?; zelf.set_attr("start", vm.ctx.new_int(start), vm)?; zelf.set_attr("end", vm.ctx.new_int(end), vm)?; @@ -2277,6 +2273,18 @@ pub(super) mod types { Ok(()) } + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + + #[pyexception(name, base = PyUnicodeError, ctx = "unicode_translate_error")] + #[derive(Debug)] + #[repr(transparent)] + pub struct PyUnicodeTranslateError(PyUnicodeError); + + #[pyexception(with(Initializer))] + impl PyUnicodeTranslateError { #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<String> { let Ok(object) = exc.as_object().get_attr("object", vm) else { @@ -2301,6 +2309,24 @@ pub(super) mod types { } } + impl Initializer for PyUnicodeTranslateError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + type Args = (PyStrRef, isize, isize, PyStrRef); + let (object, start, end, reason): Args = args.bind(vm)?; + zelf.set_attr("object", object, vm)?; + zelf.set_attr("start", vm.ctx.new_int(start), vm)?; + zelf.set_attr("end", vm.ctx.new_int(end), vm)?; + zelf.set_attr("reason", reason, vm)?; + Ok(()) + } + + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + /// JIT error. #[cfg(feature = "jit")] #[pyexception(name, base = PyException, ctx = "jit_error", impl)] diff --git a/crates/vm/src/stdlib/ast/python.rs b/crates/vm/src/stdlib/ast/python.rs index aa21d8b034a..35fe561527b 100644 --- a/crates/vm/src/stdlib/ast/python.rs +++ b/crates/vm/src/stdlib/ast/python.rs @@ -3,50 +3,18 @@ use super::{PY_CF_OPTIMIZED_AST, PY_CF_TYPE_COMMENTS, PY_COMPILE_FLAG_AST_ONLY}; #[pymodule] pub(crate) mod _ast { use crate::{ - AsObject, Context, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, + AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyStrRef, PyTupleRef, PyType, PyTypeRef}, function::FuncArgs, - types::Constructor, + types::{Constructor, Initializer}, }; #[pyattr] #[pyclass(module = "_ast", name = "AST")] #[derive(Debug, PyPayload)] pub(crate) struct NodeAst; - #[pyclass(with(Constructor), flags(BASETYPE, HAS_DICT))] + #[pyclass(with(Constructor, Initializer), flags(BASETYPE, HAS_DICT))] impl NodeAst { - #[pyslot] - #[pymethod] - fn __init__(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - let fields = zelf.get_attr("_fields", vm)?; - let fields: Vec<PyStrRef> = fields.try_to_value(vm)?; - let n_args = args.args.len(); - if n_args > fields.len() { - return Err(vm.new_type_error(format!( - "{} constructor takes at most {} positional argument{}", - zelf.class().name(), - fields.len(), - if fields.len() == 1 { "" } else { "s" }, - ))); - } - for (name, arg) in fields.iter().zip(args.args) { - zelf.set_attr(name, arg, vm)?; - } - for (key, value) in args.kwargs { - if let Some(pos) = fields.iter().position(|f| f.as_str() == key) - && pos < n_args - { - return Err(vm.new_type_error(format!( - "{} got multiple values for argument '{}'", - zelf.class().name(), - key - ))); - } - zelf.set_attr(vm.ctx.intern_str(key), value, vm)?; - } - Ok(()) - } - #[pyattr] fn _fields(ctx: &Context) -> PyTupleRef { ctx.empty_tuple.clone() @@ -71,7 +39,8 @@ pub(crate) mod _ast { let zelf = vm.ctx.new_base_object(cls, dict); // Initialize the instance with the provided arguments - Self::__init__(zelf.clone(), args, vm)?; + // FIXME: This is probably incorrect. Please check if init should be called outside of __new__ + Self::slot_init(zelf.clone(), args, vm)?; Ok(zelf) } @@ -81,6 +50,44 @@ pub(crate) mod _ast { } } + impl Initializer for NodeAst { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + let fields = zelf.get_attr("_fields", vm)?; + let fields: Vec<PyStrRef> = fields.try_to_value(vm)?; + let n_args = args.args.len(); + if n_args > fields.len() { + return Err(vm.new_type_error(format!( + "{} constructor takes at most {} positional argument{}", + zelf.class().name(), + fields.len(), + if fields.len() == 1 { "" } else { "s" }, + ))); + } + for (name, arg) in fields.iter().zip(args.args) { + zelf.set_attr(name, arg, vm)?; + } + for (key, value) in args.kwargs { + if let Some(pos) = fields.iter().position(|f| f.as_str() == key) + && pos < n_args + { + return Err(vm.new_type_error(format!( + "{} got multiple values for argument '{}'", + zelf.class().name(), + key + ))); + } + zelf.set_attr(vm.ctx.intern_str(key), value, vm)?; + } + Ok(()) + } + + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + #[pyattr(name = "PyCF_ONLY_AST")] use super::PY_COMPILE_FLAG_AST_ONLY; From 5925f1483c197fcde5300b0f3058b0977c03cd58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 22:25:09 +0900 Subject: [PATCH 537/819] Bump insta from 1.44.3 to 1.45.0 (#6468) Bumps [insta](https://github.com/mitsuhiko/insta) from 1.44.3 to 1.45.0. - [Release notes](https://github.com/mitsuhiko/insta/releases) - [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md) - [Commits](https://github.com/mitsuhiko/insta/compare/1.44.3...1.45.0) --- updated-dependencies: - dependency-name: insta dependency-version: 1.45.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 20 +++++++++++++++++--- Cargo.toml | 2 +- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2873f3a529f..a57a36a40f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,7 +1181,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1487,13 +1487,14 @@ dependencies = [ [[package]] name = "insta" -version = "1.44.3" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698" +checksum = "b76866be74d68b1595eb8060cb9191dca9c021db2316558e52ddc5d55d41b66c" dependencies = [ "console", "once_cell", "similar", + "tempfile", ] [[package]] @@ -3721,6 +3722,19 @@ dependencies = [ "shared-build", ] +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "termios" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index f68e6e68157..fad506ddfaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,7 +173,7 @@ getrandom = { version = "0.3", features = ["std"] } glob = "0.3" hex = "0.4.3" indexmap = { version = "2.11.3", features = ["std"] } -insta = "1.44" +insta = "1.45" itertools = "0.14.0" is-macro = "0.3.7" junction = "1.3.0" From 2342006b37ccfef8633bd937c1758ac1101f99a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 08:57:03 +0900 Subject: [PATCH 538/819] Bump aws-lc-rs from 1.15.1 to 1.15.2 (#6469) Bumps [aws-lc-rs](https://github.com/aws/aws-lc-rs) from 1.15.1 to 1.15.2. - [Release notes](https://github.com/aws/aws-lc-rs/releases) - [Commits](https://github.com/aws/aws-lc-rs/compare/v1.15.1...v1.15.2) --- updated-dependencies: - dependency-name: aws-lc-rs dependency-version: 1.15.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a57a36a40f9..e3296732cb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,9 +254,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" dependencies = [ "aws-lc-fips-sys", "aws-lc-sys", @@ -266,9 +266,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" dependencies = [ "cc", "cmake", diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index a5328697ca8..0cd853223e2 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -127,7 +127,7 @@ x509-parser = { version = "0.18", optional = true } der = { version = "0.7", features = ["alloc", "oid"], optional = true } pem-rfc7468 = { version = "1.0", features = ["alloc"], optional = true } webpki-roots = { version = "1.0", optional = true } -aws-lc-rs = { version = "1.14.1", optional = true } +aws-lc-rs = { version = "1.15.2", optional = true } oid-registry = { version = "0.8", features = ["x509", "pkcs1", "nist_algs"], optional = true } pkcs8 = { version = "0.10", features = ["encryption", "pkcs5", "pem"], optional = true } From d919fe516e65bd259d05295773d15859d597e7c7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Fri, 19 Dec 2025 16:49:07 +0900 Subject: [PATCH 539/819] mark test_threading --- Lib/test/test_threading.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 94f21d1c38f..8f86792e820 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -229,6 +229,7 @@ def f(mutex): # PyThreadState_SetAsyncExc() is a CPython-only gimmick, not (currently) # exposed at the Python level. This test relies on ctypes to get at it. + @unittest.skip("TODO: RUSTPYTHON; expects @cpython_only") def test_PyThreadState_SetAsyncExc(self): ctypes = import_module("ctypes") @@ -332,6 +333,7 @@ def fail_new_thread(*args): finally: threading._start_new_thread = _start_new_thread + @unittest.skip("TODO: RUSTPYTHON; ctypes.pythonapi is not supported") def test_finalize_running_thread(self): # Issue 1402: the PyGILState_Ensure / _Release functions may be called # very late on python exit: on deallocation of a running thread for From 73e1c3816e737b0559063c44f52537d6963aa267 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Tue, 23 Dec 2025 11:55:59 +0900 Subject: [PATCH 540/819] fix ssl --- crates/stdlib/src/openssl.rs | 14 ++++++++++---- crates/stdlib/src/ssl.rs | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index 4d420e7d539..d352d15a614 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -1543,10 +1543,10 @@ mod _ssl { #[pymethod] fn get_ca_certs( &self, - binary_form: OptionalArg<bool>, + args: GetCertArgs, vm: &VirtualMachine, ) -> PyResult<Vec<PyObjectRef>> { - let binary_form = binary_form.unwrap_or(false); + let binary_form = args.binary_form.unwrap_or(false); let ctx = self.ctx(); #[cfg(ossl300)] let certs = ctx.cert_store().all_certificates(); @@ -2259,6 +2259,12 @@ mod _ssl { password: Option<PyObjectRef>, } + #[derive(FromArgs)] + struct GetCertArgs { + #[pyarg(any, optional)] + binary_form: OptionalArg<bool>, + } + // Err is true if the socket is blocking type SocketDeadline = Result<Instant, bool>; @@ -2516,10 +2522,10 @@ mod _ssl { #[pymethod] fn getpeercert( &self, - binary: OptionalArg<bool>, + args: GetCertArgs, vm: &VirtualMachine, ) -> PyResult<Option<PyObjectRef>> { - let binary = binary.unwrap_or(false); + let binary = args.binary_form.unwrap_or(false); let stream = self.connection.read(); if !stream.ssl().is_init_finished() { return Err(vm.new_value_error("handshake not done yet")); diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index bf25260ed3a..16449e2d019 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -841,6 +841,12 @@ mod _ssl { password: OptionalArg<PyObjectRef>, } + #[derive(FromArgs)] + struct GetCertArgs { + #[pyarg(any, optional)] + binary_form: OptionalArg<bool>, + } + #[pyclass(with(Constructor), flags(BASETYPE))] impl PySSLContext { // Helper method to convert DER certificate bytes to Python dict @@ -1688,12 +1694,8 @@ mod _ssl { } #[pymethod] - fn get_ca_certs( - &self, - binary_form: OptionalArg<bool>, - vm: &VirtualMachine, - ) -> PyResult<PyListRef> { - let binary_form = binary_form.unwrap_or(false); + fn get_ca_certs(&self, args: GetCertArgs, vm: &VirtualMachine) -> PyResult<PyListRef> { + let binary_form = args.binary_form.unwrap_or(false); let ca_certs_der = self.ca_certs_der.read(); let mut certs = Vec::new(); @@ -3444,10 +3446,10 @@ mod _ssl { #[pymethod] fn getpeercert( &self, - binary_form: OptionalArg<bool>, + args: GetCertArgs, vm: &VirtualMachine, ) -> PyResult<Option<PyObjectRef>> { - let binary = binary_form.unwrap_or(false); + let binary = args.binary_form.unwrap_or(false); // Check if handshake is complete if !*self.handshake_done.lock() { From a84452ab457c71880e1a90cc488c314596d7d184 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Tue, 23 Dec 2025 11:52:31 +0900 Subject: [PATCH 541/819] fix sysconfigdata --- Lib/test/test_sysconfig.py | 1 - crates/vm/src/stdlib/sysconfigdata.rs | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 35e62d54635..965780668ca 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -447,7 +447,6 @@ def test_main(self): _main() self.assertTrue(len(output.getvalue().split('\n')) > 0) - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.platform == "win32", "Does not apply to Windows") def test_ldshared_value(self): ldflags = sysconfig.get_config_var('LDFLAGS') diff --git a/crates/vm/src/stdlib/sysconfigdata.rs b/crates/vm/src/stdlib/sysconfigdata.rs index 90e46b83b97..ee40b693aa2 100644 --- a/crates/vm/src/stdlib/sysconfigdata.rs +++ b/crates/vm/src/stdlib/sysconfigdata.rs @@ -1,3 +1,5 @@ +// spell-checker: words LDSHARED ARFLAGS CPPFLAGS CCSHARED BASECFLAGS BLDSHARED + pub(crate) use _sysconfigdata::make_module; #[pymodule] @@ -18,6 +20,21 @@ pub(crate) mod _sysconfigdata { "MULTIARCH" => MULTIARCH, // enough for tests to stop expecting urandom() to fail after restricting file resources "HAVE_GETRANDOM" => 1, + // Compiler configuration for native extension builds + "CC" => "cc", + "CXX" => "c++", + "CFLAGS" => "", + "CPPFLAGS" => "", + "LDFLAGS" => "", + "LDSHARED" => "cc -shared", + "CCSHARED" => "", + "SHLIB_SUFFIX" => ".so", + "SO" => ".so", + "AR" => "ar", + "ARFLAGS" => "rcs", + "OPT" => "", + "BASECFLAGS" => "", + "BLDSHARED" => "cc -shared", } include!(concat!(env!("OUT_DIR"), "/env_vars.rs")); vars From df523cb58c42a9a10a539ca1598dd264876da4f4 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Tue, 23 Dec 2025 14:58:42 +0900 Subject: [PATCH 542/819] skip spawnve on windows --- Lib/test/test_os.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 105629bda19..939315379f2 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3491,6 +3491,7 @@ def test_spawnl(self): self.assertEqual(exitcode, self.exitcode) @requires_os_func('spawnle') + @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; fix spawnve on Windows") def test_spawnle(self): program, args = self.create_args(with_env=True) exitcode = os.spawnle(os.P_WAIT, program, *args, self.env) @@ -3519,6 +3520,7 @@ def test_spawnv(self): self.assertEqual(exitcode, self.exitcode) @requires_os_func('spawnve') + @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; fix spawnve on Windows") def test_spawnve(self): program, args = self.create_args(with_env=True) exitcode = os.spawnve(os.P_WAIT, program, args, self.env) @@ -3627,6 +3629,7 @@ def _test_invalid_env(self, spawn): self.assertEqual(exitcode, 0) @requires_os_func('spawnve') + @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; fix spawnve on Windows") def test_spawnve_invalid_env(self): self._test_invalid_env(os.spawnve) From 9760249a750c272733369ff886aac22ffbe25d31 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Tue, 23 Dec 2025 15:24:07 +0900 Subject: [PATCH 543/819] fix multiarch --- Lib/test/test_sysconfig.py | 1 - crates/vm/src/stdlib/sys.rs | 20 +++++++++++++------- crates/vm/src/stdlib/sysconfigdata.rs | 11 +++++++---- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 965780668ca..82c11bdf7e2 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -598,7 +598,6 @@ def test_android_ext_suffix(self): self.assertTrue(suffix.endswith(f"-{expected_triplet}.so"), f"{machine=}, {suffix=}") - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(sys.platform == 'darwin', 'OS X-specific test') def test_osx_ext_suffix(self): suffix = sysconfig.get_config_var('EXT_SUFFIX') diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index cfe6f9f5e61..0e46ec18a01 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -1,6 +1,8 @@ use crate::{Py, PyResult, VirtualMachine, builtins::PyModule, convert::ToPyObject}; -pub(crate) use sys::{__module_def, DOC, MAXSIZE, MULTIARCH, UnraisableHookArgsData}; +pub(crate) use sys::{ + __module_def, DOC, MAXSIZE, RUST_MULTIARCH, UnraisableHookArgsData, multiarch, +}; #[pymodule] mod sys { @@ -37,10 +39,14 @@ mod sys { System::LibraryLoader::{GetModuleFileNameW, GetModuleHandleW}, }; - // not the same as CPython (e.g. rust's x86_x64-unknown-linux-gnu is just x86_64-linux-gnu) - // but hopefully that's just an implementation detail? TODO: copy CPython's multiarch exactly, - // https://github.com/python/cpython/blob/3.8/configure.ac#L725 - pub(crate) const MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE"); + // Rust target triple (e.g., "x86_64-unknown-linux-gnu") + pub(crate) const RUST_MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE"); + + /// Convert Rust target triple to CPython-style multiarch + /// e.g., "x86_64-unknown-linux-gnu" -> "x86_64-linux-gnu" + pub(crate) fn multiarch() -> String { + RUST_MULTIARCH.replace("-unknown", "") + } #[pyattr(name = "_rustpython_debugbuild")] const RUSTPYTHON_DEBUGBUILD: bool = cfg!(debug_assertions); @@ -189,7 +195,7 @@ mod sys { py_namespace!(vm, { "name" => ctx.new_str(NAME), "cache_tag" => ctx.new_str(cache_tag), - "_multiarch" => ctx.new_str(MULTIARCH.to_owned()), + "_multiarch" => ctx.new_str(multiarch()), "version" => version_info(vm), "hexversion" => ctx.new_int(version::VERSION_HEX), }) @@ -1249,6 +1255,6 @@ pub(crate) fn sysconfigdata_name() -> String { "_sysconfigdata_{}_{}_{}", sys::ABIFLAGS, sys::PLATFORM, - sys::MULTIARCH + sys::multiarch() ) } diff --git a/crates/vm/src/stdlib/sysconfigdata.rs b/crates/vm/src/stdlib/sysconfigdata.rs index ee40b693aa2..5e954f7fe7a 100644 --- a/crates/vm/src/stdlib/sysconfigdata.rs +++ b/crates/vm/src/stdlib/sysconfigdata.rs @@ -4,20 +4,23 @@ pub(crate) use _sysconfigdata::make_module; #[pymodule] pub(crate) mod _sysconfigdata { - use crate::{VirtualMachine, builtins::PyDictRef, convert::ToPyObject, stdlib::sys::MULTIARCH}; + use crate::stdlib::sys::{RUST_MULTIARCH, multiarch}; + use crate::{VirtualMachine, builtins::PyDictRef, convert::ToPyObject}; #[pyattr] fn build_time_vars(vm: &VirtualMachine) -> PyDictRef { let vars = vm.ctx.new_dict(); + let multiarch = multiarch(); macro_rules! sysvars { ($($key:literal => $value:expr),*$(,)?) => {{ $(vars.set_item($key, $value.to_pyobject(vm), vm).unwrap();)* }}; } sysvars! { - // fake shared module extension - "EXT_SUFFIX" => format!(".rustpython-{MULTIARCH}"), - "MULTIARCH" => MULTIARCH, + // Extension module suffix in CPython-compatible format + "EXT_SUFFIX" => format!(".rustpython313-{multiarch}.so"), + "MULTIARCH" => multiarch.clone(), + "RUST_MULTIARCH" => RUST_MULTIARCH, // enough for tests to stop expecting urandom() to fail after restricting file resources "HAVE_GETRANDOM" => 1, // Compiler configuration for native extension builds From b44229f7ca4f7718f405c4a0f397c2a70c9178ec Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Tue, 23 Dec 2025 21:57:05 +0900 Subject: [PATCH 544/819] debug whats_left --- whats_left.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/whats_left.py b/whats_left.py index 91e46bef7ef..7d02a4cea4d 100755 --- a/whats_left.py +++ b/whats_left.py @@ -361,7 +361,7 @@ def method_incompatibility_reason(typ, method_name, real_method_value): if platform.python_implementation() == "CPython": if not_implementeds: - sys.exit("ERROR: CPython should have all the methods") + sys.exit(f"ERROR: CPython should have all the methods but missing: {not_implementeds}") mod_names = [ name.decode() @@ -455,6 +455,7 @@ def remove_one_indent(s): ) # The last line should be json output, the rest of the lines can contain noise # because importing certain modules can print stuff to stdout/stderr +print(result.stderr, file=sys.stderr) result = json.loads(result.stdout.splitlines()[-1]) if args.json: From fc0a34a5a53730a0f1b59c432e568fea9f84b8f3 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Sat, 20 Dec 2025 14:31:33 +0900 Subject: [PATCH 545/819] Remove unused ctypes/field.rs --- crates/vm/src/stdlib/ctypes/field.rs | 306 --------------------------- 1 file changed, 306 deletions(-) delete mode 100644 crates/vm/src/stdlib/ctypes/field.rs diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs deleted file mode 100644 index ea57d68065a..00000000000 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ /dev/null @@ -1,306 +0,0 @@ -use crate::builtins::PyType; -use crate::function::PySetterValue; -use crate::types::{GetDescriptor, Representable}; -use crate::{AsObject, Py, PyObject, PyObjectRef, PyResult, VirtualMachine}; -use num_traits::ToPrimitive; - -use super::structure::PyCStructure; -use super::union::PyCUnion; - -#[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")] -#[derive(Debug)] -pub struct PyCFieldType { - pub _base: PyType, - #[allow(dead_code)] - pub(super) inner: PyCField, -} - -#[pyclass] -impl PyCFieldType {} - -#[pyclass(name = "CField", module = "_ctypes")] -#[derive(Debug, PyPayload)] -pub struct PyCField { - pub(super) byte_offset: usize, - pub(super) byte_size: usize, - #[allow(unused)] - pub(super) index: usize, - /// The ctypes type for this field (can be any ctypes type including arrays) - pub(super) proto: PyObjectRef, - pub(super) anonymous: bool, - pub(super) bitfield_size: bool, - pub(super) bit_offset: u8, - pub(super) name: String, -} - -impl PyCField { - pub fn new( - name: String, - proto: PyObjectRef, - byte_offset: usize, - byte_size: usize, - index: usize, - ) -> Self { - Self { - name, - proto, - byte_offset, - byte_size, - index, - anonymous: false, - bitfield_size: false, - bit_offset: 0, - } - } -} - -impl Representable for PyCField { - fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> { - // Get type name from the proto object - let tp_name = if let Some(name_attr) = vm - .ctx - .interned_str("__name__") - .and_then(|s| zelf.proto.get_attr(s, vm).ok()) - { - name_attr.str(vm)?.to_string() - } else { - zelf.proto.class().name().to_string() - }; - - if zelf.bitfield_size { - Ok(format!( - "<{} type={}, ofs={byte_offset}, bit_size={bitfield_size}, bit_offset={bit_offset}", - zelf.name, - tp_name, - byte_offset = zelf.byte_offset, - bitfield_size = zelf.bitfield_size, - bit_offset = zelf.bit_offset - )) - } else { - Ok(format!( - "<{} type={tp_name}, ofs={}, size={}", - zelf.name, zelf.byte_offset, zelf.byte_size - )) - } - } -} - -impl GetDescriptor for PyCField { - fn descr_get( - zelf: PyObjectRef, - obj: Option<PyObjectRef>, - _cls: Option<PyObjectRef>, - vm: &VirtualMachine, - ) -> PyResult { - let zelf = zelf - .downcast::<PyCField>() - .map_err(|_| vm.new_type_error("expected CField".to_owned()))?; - - // If obj is None, return the descriptor itself (class attribute access) - let obj = match obj { - Some(obj) if !vm.is_none(&obj) => obj, - _ => return Ok(zelf.into()), - }; - - // Instance attribute access - read value from the structure/union's buffer - if let Some(structure) = obj.downcast_ref::<PyCStructure>() { - let cdata = structure.cdata.read(); - let offset = zelf.byte_offset; - let size = zelf.byte_size; - - if offset + size <= cdata.buffer.len() { - let bytes = &cdata.buffer[offset..offset + size]; - return PyCField::bytes_to_value(bytes, size, vm); - } - } else if let Some(union) = obj.downcast_ref::<PyCUnion>() { - let cdata = union.cdata.read(); - let offset = zelf.byte_offset; - let size = zelf.byte_size; - - if offset + size <= cdata.buffer.len() { - let bytes = &cdata.buffer[offset..offset + size]; - return PyCField::bytes_to_value(bytes, size, vm); - } - } - - // Fallback: return 0 for uninitialized or unsupported types - Ok(vm.ctx.new_int(0).into()) - } -} - -impl PyCField { - /// Convert bytes to a Python value based on size - fn bytes_to_value(bytes: &[u8], size: usize, vm: &VirtualMachine) -> PyResult { - match size { - 1 => Ok(vm.ctx.new_int(bytes[0] as i8).into()), - 2 => { - let val = i16::from_ne_bytes([bytes[0], bytes[1]]); - Ok(vm.ctx.new_int(val).into()) - } - 4 => { - let val = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); - Ok(vm.ctx.new_int(val).into()) - } - 8 => { - let val = i64::from_ne_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], - ]); - Ok(vm.ctx.new_int(val).into()) - } - _ => Ok(vm.ctx.new_int(0).into()), - } - } - - /// Convert a Python value to bytes - fn value_to_bytes(value: &PyObject, size: usize, vm: &VirtualMachine) -> PyResult<Vec<u8>> { - if let Ok(int_val) = value.try_int(vm) { - let i = int_val.as_bigint(); - match size { - 1 => { - let val = i.to_i8().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - 2 => { - let val = i.to_i16().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - 4 => { - let val = i.to_i32().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - 8 => { - let val = i.to_i64().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - _ => Ok(vec![0u8; size]), - } - } else { - Ok(vec![0u8; size]) - } - } -} - -#[pyclass( - flags(DISALLOW_INSTANTIATION, IMMUTABLETYPE), - with(Representable, GetDescriptor) -)] -impl PyCField { - #[pyslot] - fn descr_set( - zelf: &crate::PyObject, - obj: PyObjectRef, - value: PySetterValue<PyObjectRef>, - vm: &VirtualMachine, - ) -> PyResult<()> { - let zelf = zelf - .downcast_ref::<PyCField>() - .ok_or_else(|| vm.new_type_error("expected CField".to_owned()))?; - - // Get the structure/union instance - use downcast_ref() to access the struct data - if let Some(structure) = obj.downcast_ref::<PyCStructure>() { - match value { - PySetterValue::Assign(value) => { - let offset = zelf.byte_offset; - let size = zelf.byte_size; - let bytes = PyCField::value_to_bytes(&value, size, vm)?; - - let mut cdata = structure.cdata.write(); - if offset + size <= cdata.buffer.len() { - cdata.buffer[offset..offset + size].copy_from_slice(&bytes); - } - Ok(()) - } - PySetterValue::Delete => { - Err(vm.new_type_error("cannot delete structure field".to_owned())) - } - } - } else if let Some(union) = obj.downcast_ref::<PyCUnion>() { - match value { - PySetterValue::Assign(value) => { - let offset = zelf.byte_offset; - let size = zelf.byte_size; - let bytes = PyCField::value_to_bytes(&value, size, vm)?; - - let mut cdata = union.cdata.write(); - if offset + size <= cdata.buffer.len() { - cdata.buffer[offset..offset + size].copy_from_slice(&bytes); - } - Ok(()) - } - PySetterValue::Delete => { - Err(vm.new_type_error("cannot delete union field".to_owned())) - } - } - } else { - Err(vm.new_type_error(format!( - "descriptor works only on Structure or Union instances, got {}", - obj.class().name() - ))) - } - } - - #[pymethod] - fn __set__( - zelf: PyObjectRef, - obj: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<()> { - Self::descr_set(&zelf, obj, PySetterValue::Assign(value), vm) - } - - #[pymethod] - fn __delete__(zelf: PyObjectRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - Self::descr_set(&zelf, obj, PySetterValue::Delete, vm) - } - - #[pygetset] - fn size(&self) -> usize { - self.byte_size - } - - #[pygetset] - fn bit_size(&self) -> bool { - self.bitfield_size - } - - #[pygetset] - fn is_bitfield(&self) -> bool { - self.bitfield_size - } - - #[pygetset] - fn is_anonymous(&self) -> bool { - self.anonymous - } - - #[pygetset] - fn name(&self) -> String { - self.name.clone() - } - - #[pygetset(name = "type")] - fn type_(&self) -> PyObjectRef { - self.proto.clone() - } - - #[pygetset] - fn offset(&self) -> usize { - self.byte_offset - } - - #[pygetset] - fn byte_offset(&self) -> usize { - self.byte_offset - } - - #[pygetset] - fn byte_size(&self) -> usize { - self.byte_size - } - - #[pygetset] - fn bit_offset(&self) -> u8 { - self.bit_offset - } -} From 286a5b5b8f8759808ed5a46f0256965bddcaf45d Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Tue, 23 Dec 2025 23:28:58 +0900 Subject: [PATCH 546/819] msc_info in get_version --- Lib/platform.py | 2 +- crates/vm/src/version.rs | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 58b66078e1a..c64c6d2c6f5 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -1080,7 +1080,7 @@ def _sys_version(sys_version=None): match.groups() # XXX: RUSTPYTHON support - if "rustc" in sys_version: + if "RustPython" in sys_version: name = "RustPython" else: name = 'CPython' diff --git a/crates/vm/src/version.rs b/crates/vm/src/version.rs index 9d472e8be0a..deb3dccd535 100644 --- a/crates/vm/src/version.rs +++ b/crates/vm/src/version.rs @@ -15,12 +15,29 @@ pub const VERSION_HEX: usize = (MAJOR << 24) | (MINOR << 16) | (MICRO << 8) | (RELEASELEVEL_N << 4) | SERIAL; pub fn get_version() -> String { + // Windows: include MSC v. for compatibility with ctypes.util.find_library + // MSC v.1929 = VS 2019, version 14+ makes find_msvcrt() return None + #[cfg(windows)] + let msc_info = { + let arch = if cfg!(target_pointer_width = "64") { + "64 bit (AMD64)" + } else { + "32 bit (Intel)" + }; + // Include both RustPython identifier and MSC v. for compatibility + format!(" MSC v.1929 {arch}",) + }; + + #[cfg(not(windows))] + let msc_info = String::new(); + format!( - "{:.80} ({:.80}) \n[RustPython {} with {:.80}]", // \n is PyPy convention + "{:.80} ({:.80}) \n[RustPython {} with {:.80}{}]", // \n is PyPy convention get_version_number(), get_build_info(), env!("CARGO_PKG_VERSION"), COMPILER, + msc_info, ) } From 4bf0bac52d0270c0efcbc248a3888789624a2645 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Tue, 23 Dec 2025 23:29:15 +0900 Subject: [PATCH 547/819] debuggable whats_left --- whats_left.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/whats_left.py b/whats_left.py index 7d02a4cea4d..c5b0be6eadc 100755 --- a/whats_left.py +++ b/whats_left.py @@ -361,7 +361,9 @@ def method_incompatibility_reason(typ, method_name, real_method_value): if platform.python_implementation() == "CPython": if not_implementeds: - sys.exit(f"ERROR: CPython should have all the methods but missing: {not_implementeds}") + sys.exit( + f"ERROR: CPython should have all the methods but missing: {not_implementeds}" + ) mod_names = [ name.decode() From 79abbf0b29d5183b0885e45ea9991bd8d046d702 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Tue, 23 Dec 2025 23:36:38 +0900 Subject: [PATCH 548/819] fix ctypes --- crates/vm/src/stdlib/ctypes.rs | 102 ++- crates/vm/src/stdlib/ctypes/array.rs | 44 +- crates/vm/src/stdlib/ctypes/base.rs | 76 ++- crates/vm/src/stdlib/ctypes/function.rs | 776 ++++++++++++++--------- crates/vm/src/stdlib/ctypes/library.rs | 108 +++- crates/vm/src/stdlib/ctypes/pointer.rs | 147 ++++- crates/vm/src/stdlib/ctypes/simple.rs | 187 ++++-- crates/vm/src/stdlib/ctypes/structure.rs | 6 - 8 files changed, 973 insertions(+), 473 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 3fdb2df6104..9b922230431 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -95,6 +95,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { pointer::PyCPointerType::make_class(ctx); structure::PyCStructType::make_class(ctx); union::PyCUnionType::make_class(ctx); + function::PyCFuncPtrType::make_class(ctx); extend_module!(vm, &module, { "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PyCSimple::make_class(ctx), @@ -385,12 +386,8 @@ pub(crate) mod _ctypes { #[pyattr] const RTLD_GLOBAL: i32 = 0; - #[cfg(target_os = "windows")] - #[pyattr] - const SIZEOF_TIME_T: usize = 8; - #[cfg(not(target_os = "windows"))] #[pyattr] - const SIZEOF_TIME_T: usize = 4; + const SIZEOF_TIME_T: usize = std::mem::size_of::<libc::time_t>(); #[pyattr] const CTYPES_MAX_ARGCOUNT: usize = 1024; @@ -578,30 +575,42 @@ pub(crate) mod _ctypes { #[pyfunction(name = "dlopen")] fn load_library_unix( name: Option<crate::function::FsPath>, - _load_flags: OptionalArg<i32>, + load_flags: OptionalArg<i32>, vm: &VirtualMachine, ) -> PyResult<usize> { - // TODO: audit functions first - // TODO: load_flags + // Default mode: RTLD_NOW | RTLD_LOCAL, always force RTLD_NOW + let mode = load_flags.unwrap_or(libc::RTLD_NOW | libc::RTLD_LOCAL) | libc::RTLD_NOW; + match name { Some(name) => { let cache = library::libcache(); let mut cache_write = cache.write(); let os_str = name.as_os_str(vm)?; - let (id, _) = cache_write.get_or_insert_lib(&*os_str, vm).map_err(|e| { - // Include filename in error message for better diagnostics - let name_str = os_str.to_string_lossy(); - vm.new_os_error(format!("{}: {}", name_str, e)) - })?; + let (id, _) = cache_write + .get_or_insert_lib_with_mode(&*os_str, mode, vm) + .map_err(|e| { + let name_str = os_str.to_string_lossy(); + vm.new_os_error(format!("{}: {}", name_str, e)) + })?; Ok(id) } None => { - // If None, call libc::dlopen(null, mode) to get the current process handle - let handle = unsafe { libc::dlopen(std::ptr::null(), libc::RTLD_NOW) }; + // dlopen(NULL, mode) to get the current process handle (for pythonapi) + let handle = unsafe { libc::dlopen(std::ptr::null(), mode) }; if handle.is_null() { - return Err(vm.new_os_error("dlopen() error")); + let err = unsafe { libc::dlerror() }; + let msg = if err.is_null() { + "dlopen() error".to_string() + } else { + unsafe { std::ffi::CStr::from_ptr(err).to_string_lossy().into_owned() } + }; + return Err(vm.new_os_error(msg)); } - Ok(handle as usize) + // Add to library cache so symbol lookup works + let cache = library::libcache(); + let mut cache_write = cache.write(); + let id = cache_write.insert_raw_handle(handle); + Ok(id) } } } @@ -614,6 +623,48 @@ pub(crate) mod _ctypes { Ok(()) } + #[cfg(not(windows))] + #[pyfunction] + fn dlclose(handle: usize, _vm: &VirtualMachine) -> PyResult<()> { + // Remove from cache, which triggers SharedLibrary drop. + // libloading::Library calls dlclose automatically on Drop. + let cache = library::libcache(); + let mut cache_write = cache.write(); + cache_write.drop_lib(handle); + Ok(()) + } + + #[cfg(not(windows))] + #[pyfunction] + fn dlsym( + handle: usize, + name: crate::builtins::PyStrRef, + vm: &VirtualMachine, + ) -> PyResult<usize> { + let symbol_name = std::ffi::CString::new(name.as_str()) + .map_err(|_| vm.new_value_error("symbol name contains null byte"))?; + + // Clear previous error + unsafe { libc::dlerror() }; + + let ptr = unsafe { libc::dlsym(handle as *mut libc::c_void, symbol_name.as_ptr()) }; + + // Check for error via dlerror first + let err = unsafe { libc::dlerror() }; + if !err.is_null() { + let msg = unsafe { std::ffi::CStr::from_ptr(err).to_string_lossy().into_owned() }; + return Err(vm.new_os_error(msg)); + } + + // Treat NULL symbol address as error + // This handles cases like GNU IFUNCs that resolve to NULL + if ptr.is_null() { + return Err(vm.new_os_error(format!("symbol '{}' not found", name.as_str()))); + } + + Ok(ptr as usize) + } + #[pyfunction(name = "POINTER")] fn create_pointer_type(cls: PyObjectRef, vm: &VirtualMachine) -> PyResult { use crate::builtins::PyStr; @@ -905,25 +956,24 @@ pub(crate) mod _ctypes { #[pyfunction] fn get_errno() -> i32 { - errno::errno().0 + super::function::get_errno_value() } #[pyfunction] - fn set_errno(value: i32) { - errno::set_errno(errno::Errno(value)); + fn set_errno(value: i32) -> i32 { + super::function::set_errno_value(value) } #[cfg(windows)] #[pyfunction] fn get_last_error() -> PyResult<u32> { - Ok(unsafe { windows_sys::Win32::Foundation::GetLastError() }) + Ok(super::function::get_last_error_value()) } #[cfg(windows)] #[pyfunction] - fn set_last_error(value: u32) -> PyResult<()> { - unsafe { windows_sys::Win32::Foundation::SetLastError(value) }; - Ok(()) + fn set_last_error(value: u32) -> u32 { + super::function::set_last_error_value(value) } #[pyattr] @@ -1084,9 +1134,9 @@ pub(crate) mod _ctypes { ffi_args.push(Arg::new(val)); } - let cif = Cif::new(arg_types, Type::isize()); + let cif = Cif::new(arg_types, Type::c_int()); let code_ptr = CodePtr::from_ptr(func_addr as *const _); - let result: isize = unsafe { cif.call(code_ptr, &ffi_args) }; + let result: libc::c_int = unsafe { cif.call(code_ptr, &ffi_args) }; Ok(vm.ctx.new_int(result).into()) } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 60e6516bfe0..208b3e3f4d3 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -1,9 +1,12 @@ use super::StgInfo; use super::base::{CDATA_BUFFER_METHODS, PyCData}; +use super::type_info; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, atomic_func, - builtins::{PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef}, + builtins::{ + PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef, genericalias::PyGenericAlias, + }, class::StaticType, function::{ArgBytesLike, FuncArgs, PySetterValue}, protocol::{BufferDescriptor, PyBuffer, PyNumberMethods, PySequenceMethods}, @@ -11,6 +14,19 @@ use crate::{ }; use num_traits::{Signed, ToPrimitive}; +/// Get itemsize from a PEP 3118 format string +/// Extracts the type code (last char after endianness prefix) and returns its size +fn get_size_from_format(fmt: &str) -> usize { + // Format is like "<f", ">q", etc. - strip endianness prefix and get type code + let code = fmt + .trim_start_matches(['<', '>', '@', '=', '!', '&']) + .chars() + .next() + .map(|c| c.to_string()); + code.map(|c| type_info(&c).map(|t| t.size).unwrap_or(1)) + .unwrap_or(1) +} + /// Creates array type for (element_type, length) /// Uses _array_type_cache to ensure identical calls return the same type object pub(super) fn array_type_from_ctype( @@ -444,6 +460,11 @@ impl AsSequence for PyCArray { with(Constructor, AsSequence, AsBuffer) )] impl PyCArray { + #[pyclassmethod] + fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::from_args(cls, args, vm) + } + fn int_to_bytes(i: &malachite_bigint::BigInt, size: usize) -> Vec<u8> { // Try unsigned first (handles values like 0xFFFFFFFF that overflow signed) // then fall back to signed (handles negative values) @@ -1056,19 +1077,30 @@ impl AsBuffer for PyCArray { .expect("PyCArray type must have StgInfo"); let format = stg_info.format.clone(); let shape = stg_info.shape.clone(); - let element_size = stg_info.element_size; let desc = if let Some(fmt) = format && !shape.is_empty() { + // itemsize is the size of the base element type (item_info->size) + // For empty arrays, we still need the element size, not 0 + let total_elements: usize = shape.iter().product(); + let has_zero_dim = shape.contains(&0); + let itemsize = if total_elements > 0 && buffer_len > 0 { + buffer_len / total_elements + } else { + // For empty arrays, get itemsize from format type code + get_size_from_format(&fmt) + }; + // Build dim_desc from shape (C-contiguous: row-major order) // stride[i] = product(shape[i+1:]) * itemsize + // For empty arrays (any dimension is 0), all strides are 0 let mut dim_desc = Vec::with_capacity(shape.len()); - let mut stride = element_size as isize; + let mut stride = itemsize as isize; - // Calculate strides from innermost to outermost dimension for &dim_size in shape.iter().rev() { - dim_desc.push((dim_size, stride, 0)); + let current_stride = if has_zero_dim { 0 } else { stride }; + dim_desc.push((dim_size, current_stride, 0)); stride *= dim_size as isize; } dim_desc.reverse(); @@ -1076,7 +1108,7 @@ impl AsBuffer for PyCArray { BufferDescriptor { len: buffer_len, readonly: false, - itemsize: element_size, + itemsize, format: std::borrow::Cow::Owned(fmt), dim_desc, } diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 38c371346e0..44793a21561 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -300,15 +300,27 @@ pub(super) fn get_field_format( big_endian: bool, vm: &VirtualMachine, ) -> String { + let endian_prefix = if big_endian { ">" } else { "<" }; + // 1. Check StgInfo for format if let Some(type_obj) = field_type.downcast_ref::<PyType>() && let Some(stg_info) = type_obj.stg_info_opt() && let Some(fmt) = &stg_info.format { - // Handle endian prefix for simple types - if fmt.len() == 1 { - let endian_prefix = if big_endian { ">" } else { "<" }; - return format!("{}{}", endian_prefix, fmt); + // For structures (T{...}), arrays ((n)...), and pointers (&...), return as-is + // These complex types have their own endianness markers inside + if fmt.starts_with('T') + || fmt.starts_with('(') + || fmt.starts_with('&') + || fmt.starts_with("X{") + { + return fmt.clone(); + } + + // For simple types, replace existing endian prefix with the correct one + let base_fmt = fmt.trim_start_matches(['<', '>', '@', '=', '!']); + if !base_fmt.is_empty() { + return format!("{}{}", endian_prefix, base_fmt); } return fmt.clone(); } @@ -318,8 +330,7 @@ pub(super) fn get_field_format( && let Some(type_str) = type_attr.downcast_ref::<PyStr>() { let s = type_str.as_str(); - if s.len() == 1 { - let endian_prefix = if big_endian { ">" } else { "<" }; + if !s.is_empty() { return format!("{}{}", endian_prefix, s); } return s.to_string(); @@ -1168,29 +1179,30 @@ impl PyCData { .ok_or_else(|| vm.new_value_error("Invalid library handle"))? }; - // Get symbol address using platform-specific API - let symbol_name = std::ffi::CString::new(name.as_str()) - .map_err(|_| vm.new_value_error("Invalid symbol name"))?; - - #[cfg(windows)] - let ptr: *const u8 = unsafe { - match windows_sys::Win32::System::LibraryLoader::GetProcAddress( - handle as windows_sys::Win32::Foundation::HMODULE, - symbol_name.as_ptr() as *const u8, - ) { - Some(p) => p as *const u8, - None => std::ptr::null(), + // Look up the library in the cache and use lib.get() for symbol lookup + let library_cache = super::library::libcache().read(); + let library = library_cache + .get_lib(handle) + .ok_or_else(|| vm.new_value_error("Library not found"))?; + let inner_lib = library.lib.lock(); + + let symbol_name_with_nul = format!("{}\0", name.as_str()); + let ptr: *const u8 = if let Some(lib) = &*inner_lib { + unsafe { + lib.get::<*const u8>(symbol_name_with_nul.as_bytes()) + .map(|sym| *sym) + .map_err(|_| { + vm.new_value_error(format!("symbol '{}' not found", name.as_str())) + })? } + } else { + return Err(vm.new_value_error("Library closed")); }; - #[cfg(not(windows))] - let ptr: *const u8 = - unsafe { libc::dlsym(handle as *mut libc::c_void, symbol_name.as_ptr()) as *const u8 }; - + // dlsym can return NULL for symbols that resolve to NULL (e.g., GNU IFUNC) + // Treat NULL addresses as errors if ptr.is_null() { - return Err( - vm.new_value_error(format!("symbol '{}' not found in library", name.as_str())) - ); + return Err(vm.new_value_error(format!("symbol '{}' not found", name.as_str()))); } // PyCData_AtAddress @@ -1593,7 +1605,7 @@ impl PyCField { /// PyCField_set #[pyslot] fn descr_set( - zelf: &crate::PyObject, + zelf: &PyObject, obj: PyObjectRef, value: PySetterValue<PyObjectRef>, vm: &VirtualMachine, @@ -1804,7 +1816,7 @@ pub enum FfiArgValue { F64(f64), Pointer(usize), /// Pointer with owned data. The PyObjectRef keeps the pointed data alive. - OwnedPointer(usize, #[allow(dead_code)] crate::PyObjectRef), + OwnedPointer(usize, #[allow(dead_code)] PyObjectRef), } impl FfiArgValue { @@ -2145,6 +2157,16 @@ pub(super) fn read_ptr_from_buffer(buffer: &[u8]) -> usize { } } +/// Check if a type is a "simple instance" (direct subclass of a simple type) +/// Returns TRUE for c_int, c_void_p, etc. (simple types with _type_ attribute) +/// Returns FALSE for Structure, Array, POINTER(T), etc. +pub(super) fn is_simple_instance(typ: &Py<PyType>) -> bool { + // _ctypes_simple_instance + // Check if the type's metaclass is PyCSimpleType + let metaclass = typ.class(); + metaclass.fast_issubclass(super::simple::PyCSimpleType::static_type()) +} + /// Set or initialize StgInfo on a type pub(super) fn set_or_init_stginfo(type_ref: &PyType, stg_info: StgInfo) { if type_ref.init_type_data(stg_info.clone()).is_err() diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 9bddb0ef0e8..04ff238ebcf 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -1,16 +1,19 @@ // spell-checker:disable use super::{ - _ctypes::CArgObject, PyCArray, PyCData, PyCPointer, PyCStructure, base::FfiArgValue, - simple::PyCSimple, type_info, + _ctypes::CArgObject, + PyCArray, PyCData, PyCPointer, PyCStructure, StgInfo, + base::{CDATA_BUFFER_METHODS, FfiArgValue, ParamFunc, StgInfoFlags}, + simple::PyCSimple, + type_info, }; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyBytes, PyDict, PyNone, PyStr, PyTuple, PyType, PyTypeRef}, class::StaticType, - convert::ToPyObject, function::FuncArgs, - types::{Callable, Constructor, Representable}, + protocol::{BufferDescriptor, PyBuffer}, + types::{AsBuffer, Callable, Constructor, Initializer, Representable}, vm::thread::with_current_vm, }; use libffi::{ @@ -18,9 +21,10 @@ use libffi::{ middle::{Arg, Cif, Closure, CodePtr, Type}, }; use libloading::Symbol; -use num_traits::ToPrimitive; +use num_traits::{Signed, ToPrimitive}; use rustpython_common::lock::PyRwLock; -use std::ffi::{self, c_void}; +use std::borrow::Cow; +use std::ffi::c_void; use std::fmt::Debug; // Internal function addresses for special ctypes functions @@ -28,6 +32,90 @@ pub(super) const INTERNAL_CAST_ADDR: usize = 1; pub(super) const INTERNAL_STRING_AT_ADDR: usize = 2; pub(super) const INTERNAL_WSTRING_AT_ADDR: usize = 3; +// Thread-local errno storage for ctypes +std::thread_local! { + /// Thread-local storage for ctypes errno + /// This is separate from the system errno - ctypes swaps them during FFI calls + /// when use_errno=True is specified. + static CTYPES_LOCAL_ERRNO: std::cell::Cell<i32> = const { std::cell::Cell::new(0) }; +} + +/// Get ctypes thread-local errno value +pub(super) fn get_errno_value() -> i32 { + CTYPES_LOCAL_ERRNO.with(|e| e.get()) +} + +/// Set ctypes thread-local errno value, returns old value +pub(super) fn set_errno_value(value: i32) -> i32 { + CTYPES_LOCAL_ERRNO.with(|e| { + let old = e.get(); + e.set(value); + old + }) +} + +/// Save and restore errno around FFI call (called when use_errno=True) +/// Before: restore thread-local errno to system +/// After: save system errno to thread-local +#[cfg(not(windows))] +fn swap_errno<F, R>(f: F) -> R +where + F: FnOnce() -> R, +{ + // Before call: restore thread-local errno to system + let saved = CTYPES_LOCAL_ERRNO.with(|e| e.get()); + errno::set_errno(errno::Errno(saved)); + + // Call the function + let result = f(); + + // After call: save system errno to thread-local + let new_error = errno::errno().0; + CTYPES_LOCAL_ERRNO.with(|e| e.set(new_error)); + + result +} + +#[cfg(windows)] +std::thread_local! { + /// Thread-local storage for ctypes last_error (Windows only) + static CTYPES_LOCAL_LAST_ERROR: std::cell::Cell<u32> = const { std::cell::Cell::new(0) }; +} + +#[cfg(windows)] +pub(super) fn get_last_error_value() -> u32 { + CTYPES_LOCAL_LAST_ERROR.with(|e| e.get()) +} + +#[cfg(windows)] +pub(super) fn set_last_error_value(value: u32) -> u32 { + CTYPES_LOCAL_LAST_ERROR.with(|e| { + let old = e.get(); + e.set(value); + old + }) +} + +/// Save and restore last_error around FFI call (called when use_last_error=True) +#[cfg(windows)] +fn save_and_restore_last_error<F, R>(f: F) -> R +where + F: FnOnce() -> R, +{ + // Before call: restore thread-local last_error to Windows + let saved = CTYPES_LOCAL_LAST_ERROR.with(|e| e.get()); + unsafe { windows_sys::Win32::Foundation::SetLastError(saved) }; + + // Call the function + let result = f(); + + // After call: save Windows last_error to thread-local + let new_error = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + CTYPES_LOCAL_LAST_ERROR.with(|e| e.set(new_error)); + + result +} + type FP = unsafe extern "C" fn(); /// Get FFI type for a ctypes type code @@ -131,11 +219,19 @@ fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult<FfiArgV return Ok(FfiArgValue::Pointer(addr)); } - // 7. Integer -> direct value + // 7. Integer -> direct value (PyLong_AsVoidPtr behavior) if let Ok(int_val) = value.try_int(vm) { - return Ok(FfiArgValue::Pointer( - int_val.as_bigint().to_usize().unwrap_or(0), - )); + let bigint = int_val.as_bigint(); + // Negative values: use signed conversion (allows -1 as 0xFFFF...) + if bigint.is_negative() { + if let Some(signed_val) = bigint.to_isize() { + return Ok(FfiArgValue::Pointer(signed_val as usize)); + } + } else if let Some(unsigned_val) = bigint.to_usize() { + return Ok(FfiArgValue::Pointer(unsigned_val)); + } + // Value out of range - raise OverflowError + return Err(vm.new_overflow_error("int too large to convert to pointer".to_string())); } // 8. Check _as_parameter_ attribute ( recursive ConvParam) @@ -150,46 +246,86 @@ fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult<FfiArgV } /// ConvParam-like conversion for when argtypes is None -/// Returns both the FFI type and the converted value -fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult<(Type, FfiArgValue)> { +/// Returns an Argument with FFI type, value, and optional keep object +fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult<Argument> { // 1. CArgObject (from byref() or paramfunc) -> use stored type and value if let Some(carg) = value.downcast_ref::<CArgObject>() { let ffi_type = ffi_type_from_tag(carg.tag); - return Ok((ffi_type, carg.value.clone())); + return Ok(Argument { + ffi_type, + keep: None, + value: carg.value.clone(), + }); } // 2. None -> NULL pointer if value.is(&vm.ctx.none) { - return Ok((Type::pointer(), FfiArgValue::Pointer(0))); + return Ok(Argument { + ffi_type: Type::pointer(), + keep: None, + value: FfiArgValue::Pointer(0), + }); } // 3. ctypes objects -> use paramfunc if let Ok(carg) = super::base::call_paramfunc(value, vm) { let ffi_type = ffi_type_from_tag(carg.tag); - return Ok((ffi_type, carg.value.clone())); + return Ok(Argument { + ffi_type, + keep: None, + value: carg.value.clone(), + }); } - // 4. Python str -> pointer (use internal UTF-8 buffer) + // 4. Python str -> wide string pointer (like PyUnicode_AsWideCharString) if let Some(s) = value.downcast_ref::<PyStr>() { - let addr = s.as_str().as_ptr() as usize; - return Ok((Type::pointer(), FfiArgValue::Pointer(addr))); + // Convert to null-terminated UTF-16 (wide string) + let wide: Vec<u16> = s + .as_str() + .encode_utf16() + .chain(std::iter::once(0)) + .collect(); + let wide_bytes: Vec<u8> = wide.iter().flat_map(|&x| x.to_ne_bytes()).collect(); + let keep = vm.ctx.new_bytes(wide_bytes); + let addr = keep.as_bytes().as_ptr() as usize; + return Ok(Argument { + ffi_type: Type::pointer(), + keep: Some(keep.into()), + value: FfiArgValue::Pointer(addr), + }); } - // 9. Python bytes -> pointer to buffer + // 9. Python bytes -> null-terminated buffer pointer + // Need to ensure null termination like c_char_p if let Some(bytes) = value.downcast_ref::<PyBytes>() { - let addr = bytes.as_bytes().as_ptr() as usize; - return Ok((Type::pointer(), FfiArgValue::Pointer(addr))); + let mut buffer = bytes.as_bytes().to_vec(); + buffer.push(0); // Add null terminator + let keep = vm.ctx.new_bytes(buffer); + let addr = keep.as_bytes().as_ptr() as usize; + return Ok(Argument { + ffi_type: Type::pointer(), + keep: Some(keep.into()), + value: FfiArgValue::Pointer(addr), + }); } // 10. Python int -> i32 (default integer type) if let Ok(int_val) = value.try_int(vm) { let val = int_val.as_bigint().to_i32().unwrap_or(0); - return Ok((Type::i32(), FfiArgValue::I32(val))); + return Ok(Argument { + ffi_type: Type::i32(), + keep: None, + value: FfiArgValue::I32(val), + }); } // 11. Python float -> f64 if let Ok(float_val) = value.try_float(vm) { - return Ok((Type::f64(), FfiArgValue::F64(float_val.to_f64()))); + return Ok(Argument { + ffi_type: Type::f64(), + keep: None, + value: FfiArgValue::F64(float_val.to_f64()), + }); } // 12. Check _as_parameter_ attribute @@ -245,7 +381,7 @@ impl ArgumentType for PyTypeRef { } fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<FfiArgValue> { - // Call from_param first to convert the value (like CPython's callproc.c:1235) + // Call from_param first to convert the value // converter = PyTuple_GET_ITEM(argtypes, i); // v = PyObject_CallOneArg(converter, arg); let from_param = self @@ -264,11 +400,10 @@ impl ArgumentType for PyTypeRef { return Ok(FfiArgValue::Pointer(0)); } - // For pointer types (POINTER(T)), we need to pass the ADDRESS of the value's buffer + // For pointer types (POINTER(T)), we need to pass the pointer VALUE stored in buffer if self.fast_issubclass(PyCPointer::static_type()) { - if let Some(cdata) = converted.downcast_ref::<PyCData>() { - let addr = cdata.buffer.read().as_ptr() as usize; - return Ok(FfiArgValue::Pointer(addr)); + if let Some(pointer) = converted.downcast_ref::<PyCPointer>() { + return Ok(FfiArgValue::Pointer(pointer.get_ptr_value())); } return convert_to_pointer(&converted, vm); } @@ -305,12 +440,6 @@ impl ArgumentType for PyTypeRef { trait ReturnType { fn to_ffi_type(&self, vm: &VirtualMachine) -> Option<Type>; - #[allow(clippy::wrong_self_convention)] - fn from_ffi_type( - &self, - value: *mut ffi::c_void, - vm: &VirtualMachine, - ) -> PyResult<Option<PyObjectRef>>; } impl ReturnType for PyTypeRef { @@ -343,130 +472,56 @@ impl ReturnType for PyTypeRef { // Fallback to class name get_ffi_type(self.name().to_string().as_str()) } - - fn from_ffi_type( - &self, - value: *mut ffi::c_void, - vm: &VirtualMachine, - ) -> PyResult<Option<PyObjectRef>> { - // Get the type code from _type_ attribute (use get_attr to traverse MRO) - let type_code = self - .as_object() - .get_attr(vm.ctx.intern_str("_type_"), vm) - .ok() - .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())); - - let result = match type_code.as_deref() { - Some("b") => vm - .ctx - .new_int(unsafe { *(value as *const i8) } as i32) - .into(), - Some("B") => vm - .ctx - .new_int(unsafe { *(value as *const u8) } as i32) - .into(), - Some("c") => vm - .ctx - .new_bytes(vec![unsafe { *(value as *const u8) }]) - .into(), - Some("h") => vm - .ctx - .new_int(unsafe { *(value as *const i16) } as i32) - .into(), - Some("H") => vm - .ctx - .new_int(unsafe { *(value as *const u16) } as i32) - .into(), - Some("i") => vm.ctx.new_int(unsafe { *(value as *const i32) }).into(), - Some("I") => vm.ctx.new_int(unsafe { *(value as *const u32) }).into(), - Some("l") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_long) }) - .into(), - Some("L") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_ulong) }) - .into(), - Some("q") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_longlong) }) - .into(), - Some("Q") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_ulonglong) }) - .into(), - Some("f") => vm - .ctx - .new_float(unsafe { *(value as *const f32) } as f64) - .into(), - Some("d") => vm.ctx.new_float(unsafe { *(value as *const f64) }).into(), - Some("P") | Some("z") | Some("Z") => { - vm.ctx.new_int(unsafe { *(value as *const usize) }).into() - } - Some("?") => vm - .ctx - .new_bool(unsafe { *(value as *const u8) } != 0) - .into(), - None => { - // No _type_ attribute - check for Structure/Array types - // GetResult: PyCData_FromBaseObj creates instance from memory - if let Some(stg_info) = self.stg_info_opt() { - let size = stg_info.size; - // Create instance of the ctypes type - let instance = self.as_object().call((), vm)?; - - // Copy return value memory into instance buffer - // Use a block to properly scope the borrow - { - let src = unsafe { std::slice::from_raw_parts(value as *const u8, size) }; - if let Some(cdata) = instance.downcast_ref::<PyCData>() { - let mut buffer = cdata.buffer.write(); - if buffer.len() >= size { - buffer.to_mut()[..size].copy_from_slice(src); - } - } else if let Some(structure) = instance.downcast_ref::<PyCStructure>() { - let mut buffer = structure.0.buffer.write(); - if buffer.len() >= size { - buffer.to_mut()[..size].copy_from_slice(src); - } - } else if let Some(array) = instance.downcast_ref::<PyCArray>() { - let mut buffer = array.0.buffer.write(); - if buffer.len() >= size { - buffer.to_mut()[..size].copy_from_slice(src); - } - } - } - return Ok(Some(instance)); - } - // Not a ctypes type - call type with int result - return self - .as_object() - .call((unsafe { *(value as *const i32) },), vm) - .map(Some); - } - _ => return Err(vm.new_type_error("Unsupported return type")), - }; - Ok(Some(result)) - } } impl ReturnType for PyNone { fn to_ffi_type(&self, _vm: &VirtualMachine) -> Option<Type> { get_ffi_type("void") } +} + +// PyCFuncPtrType - Metaclass for function pointer types +// PyCFuncPtrType_init + +#[pyclass(name = "PyCFuncPtrType", base = PyType, module = "_ctypes")] +#[derive(Debug)] +#[repr(transparent)] +pub(super) struct PyCFuncPtrType(PyType); - fn from_ffi_type( - &self, - _value: *mut ffi::c_void, - _vm: &VirtualMachine, - ) -> PyResult<Option<PyObjectRef>> { - Ok(None) +impl Initializer for PyCFuncPtrType { + type Args = FuncArgs; + + fn init(zelf: PyRef<Self>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + let obj: PyObjectRef = zelf.clone().into(); + let new_type: PyTypeRef = obj + .downcast() + .map_err(|_| vm.new_type_error("expected type"))?; + + new_type.check_not_initialized(vm)?; + + let ptr_size = std::mem::size_of::<usize>(); + let mut stg_info = StgInfo::new(ptr_size, ptr_size); + stg_info.format = Some("X{}".to_string()); + stg_info.length = 1; + stg_info.flags |= StgInfoFlags::TYPEFLAG_ISPOINTER; + stg_info.paramfunc = ParamFunc::Pointer; // CFuncPtr is passed as a pointer + + let _ = new_type.init_type_data(stg_info); + Ok(()) } } +#[pyclass(flags(IMMUTABLETYPE), with(Initializer))] +impl PyCFuncPtrType {} + /// PyCFuncPtr - Function pointer instance /// Saved in _base.buffer -#[pyclass(module = "_ctypes", name = "CFuncPtr", base = PyCData)] +#[pyclass( + module = "_ctypes", + name = "CFuncPtr", + base = PyCData, + metaclass = "PyCFuncPtrType" +)] #[repr(C)] pub(super) struct PyCFuncPtr { pub _base: PyCData, @@ -892,7 +947,13 @@ impl Constructor for PyCFuncPtr { .map_err(|err| err.to_string()) .map_err(|err| vm.new_attribute_error(err))? }; - *pointer as usize + let addr = *pointer as usize; + // dlsym can return NULL for symbols that resolve to NULL (e.g., GNU IFUNC) + // Treat NULL addresses as errors + if addr == 0 { + return Err(vm.new_attribute_error(format!("function '{}' not found", name))); + } + addr } else { 0 }; @@ -921,12 +982,17 @@ impl Constructor for PyCFuncPtr { // Get argument types and result type from the class let class_argtypes = cls.get_attr(vm.ctx.intern_str("_argtypes_")); let class_restype = cls.get_attr(vm.ctx.intern_str("_restype_")); + let class_flags = cls + .get_attr(vm.ctx.intern_str("_flags_")) + .and_then(|f| f.try_to_value::<u32>(vm).ok()) + .unwrap_or(0); // Create the thunk (C-callable wrapper for the Python function) let thunk = PyCThunk::new( first_arg.clone(), class_argtypes.clone(), class_restype.clone(), + class_flags, vm, )?; let code_ptr = thunk.code_ptr(); @@ -1060,13 +1126,16 @@ fn extract_call_info(zelf: &Py<PyCFuncPtr>, vm: &VirtualMachine) -> PyResult<Cal .unwrap_or_else(Type::i32) }; - // Check if return type is a pointer type (P, z, Z) - need special handling on 64-bit + // Check if return type is a pointer type via TYPEFLAG_ISPOINTER + // This handles c_void_p, c_char_p, c_wchar_p, and POINTER(T) types let is_pointer_return = restype_obj .as_ref() .and_then(|t| t.clone().downcast::<PyType>().ok()) - .and_then(|t| t.as_object().get_attr(vm.ctx.intern_str("_type_"), vm).ok()) - .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())) - .is_some_and(|tc| matches!(tc.as_str(), "P" | "z" | "Z")); + .and_then(|t| { + t.stg_info_opt() + .map(|info| info.flags.contains(StgInfoFlags::TYPEFLAG_ISPOINTER)) + }) + .unwrap_or(false); Ok(CallInfo { explicit_arg_types, @@ -1178,13 +1247,18 @@ fn resolve_com_method( Ok((Some(CodePtr(fptr as *mut _)), true)) } -/// Prepared arguments for FFI call -struct PreparedArgs { - ffi_arg_types: Vec<Type>, - ffi_values: Vec<FfiArgValue>, - out_buffers: Vec<(usize, PyObjectRef)>, +/// Single argument for FFI call +// struct argument +struct Argument { + ffi_type: Type, + value: FfiArgValue, + #[allow(dead_code)] + keep: Option<PyObjectRef>, // Object to keep alive during call } +/// Out buffers for paramflags OUT parameters +type OutBuffers = Vec<(usize, PyObjectRef)>; + /// Get buffer address from a ctypes object fn get_buffer_addr(obj: &PyObjectRef) -> Option<usize> { obj.downcast_ref::<PyCSimple>() @@ -1213,18 +1287,16 @@ fn create_out_buffer(arg_type: &PyTypeRef, vm: &VirtualMachine) -> PyResult<PyOb } /// Build callargs when no argtypes specified (use ConvParam) -fn build_callargs_no_argtypes(args: &FuncArgs, vm: &VirtualMachine) -> PyResult<PreparedArgs> { - let results: Vec<(Type, FfiArgValue)> = args +fn build_callargs_no_argtypes( + args: &FuncArgs, + vm: &VirtualMachine, +) -> PyResult<(Vec<Argument>, OutBuffers)> { + let arguments: Vec<Argument> = args .args .iter() .map(|arg| conv_param(arg, vm)) .collect::<PyResult<Vec<_>>>()?; - let (ffi_arg_types, ffi_values) = results.into_iter().unzip(); - Ok(PreparedArgs { - ffi_arg_types, - ffi_values, - out_buffers: Vec::new(), - }) + Ok((arguments, Vec::new())) } /// Build callargs for regular function with argtypes (no paramflags) @@ -1232,12 +1304,8 @@ fn build_callargs_simple( args: &FuncArgs, arg_types: &[PyTypeRef], vm: &VirtualMachine, -) -> PyResult<PreparedArgs> { - let ffi_arg_types = arg_types - .iter() - .map(|t| ArgumentType::to_ffi_type(t, vm)) - .collect::<PyResult<Vec<_>>>()?; - let ffi_values = args +) -> PyResult<(Vec<Argument>, OutBuffers)> { + let arguments: Vec<Argument> = args .args .iter() .enumerate() @@ -1245,14 +1313,16 @@ fn build_callargs_simple( let arg_type = arg_types .get(n) .ok_or_else(|| vm.new_type_error("argument amount mismatch"))?; - arg_type.convert_object(arg.clone(), vm) + let ffi_type = ArgumentType::to_ffi_type(arg_type, vm)?; + let value = arg_type.convert_object(arg.clone(), vm)?; + Ok(Argument { + ffi_type, + keep: None, + value, + }) }) - .collect::<Result<Vec<_>, _>>()?; - Ok(PreparedArgs { - ffi_arg_types, - ffi_values, - out_buffers: Vec::new(), - }) + .collect::<PyResult<Vec<_>>>()?; + Ok((arguments, Vec::new())) } /// Build callargs with paramflags (handles IN/OUT parameters) @@ -1262,27 +1332,21 @@ fn build_callargs_with_paramflags( paramflags: &ParsedParamFlags, skip_first_arg: bool, // true for COM methods vm: &VirtualMachine, -) -> PyResult<PreparedArgs> { - let mut ffi_arg_types = Vec::new(); - let mut ffi_values = Vec::new(); +) -> PyResult<(Vec<Argument>, OutBuffers)> { + let mut arguments = Vec::new(); let mut out_buffers = Vec::new(); // For COM methods, first arg is self (pointer) let mut caller_arg_idx = if skip_first_arg { - ffi_arg_types.push(Type::pointer()); if !args.args.is_empty() { - ffi_values.push(conv_param(&args.args[0], vm)?.1); + let arg = conv_param(&args.args[0], vm)?; + arguments.push(arg); } 1usize } else { 0usize }; - // Add FFI types for all argtypes - for arg_type in arg_types { - ffi_arg_types.push(ArgumentType::to_ffi_type(arg_type, vm)?); - } - // Process parameters based on paramflags for (param_idx, (direction, _name, default)) in paramflags.iter().enumerate() { let arg_type = arg_types @@ -1292,13 +1356,19 @@ fn build_callargs_with_paramflags( let is_out = (*direction & 2) != 0; // OUT flag let is_in = (*direction & 1) != 0 || *direction == 0; // IN flag or default + let ffi_type = ArgumentType::to_ffi_type(arg_type, vm)?; + if is_out && !is_in { // Pure OUT parameter: create buffer, don't consume caller arg let buffer = create_out_buffer(arg_type, vm)?; let addr = get_buffer_addr(&buffer).ok_or_else(|| { vm.new_type_error("Cannot create OUT buffer for this type".to_string()) })?; - ffi_values.push(FfiArgValue::Pointer(addr)); + arguments.push(Argument { + ffi_type, + keep: None, + value: FfiArgValue::Pointer(addr), + }); out_buffers.push((param_idx, buffer)); } else { // IN or IN|OUT: get from caller args or default @@ -1315,15 +1385,16 @@ fn build_callargs_with_paramflags( // IN|OUT: track for return out_buffers.push((param_idx, arg.clone())); } - ffi_values.push(arg_type.convert_object(arg, vm)?); + let value = arg_type.convert_object(arg, vm)?; + arguments.push(Argument { + ffi_type, + keep: None, + value, + }); } } - Ok(PreparedArgs { - ffi_arg_types, - ffi_values, - out_buffers, - }) + Ok((arguments, out_buffers)) } /// Build call arguments (main dispatcher) @@ -1333,7 +1404,7 @@ fn build_callargs( paramflags: Option<&ParsedParamFlags>, is_com_method: bool, vm: &VirtualMachine, -) -> PyResult<PreparedArgs> { +) -> PyResult<(Vec<Argument>, OutBuffers)> { let Some(ref arg_types) = call_info.explicit_arg_types else { // No argtypes: use ConvParam return build_callargs_no_argtypes(args, vm); @@ -1344,28 +1415,23 @@ fn build_callargs( build_callargs_with_paramflags(args, arg_types, pflags, is_com_method, vm) } else if is_com_method { // COM method without paramflags - let mut ffi_types = vec![Type::pointer()]; - ffi_types.extend( - arg_types - .iter() - .map(|t| ArgumentType::to_ffi_type(t, vm)) - .collect::<PyResult<Vec<_>>>()?, - ); - let mut ffi_vals = Vec::new(); + let mut arguments = Vec::new(); if !args.args.is_empty() { - ffi_vals.push(conv_param(&args.args[0], vm)?.1); + arguments.push(conv_param(&args.args[0], vm)?); } for (n, arg) in args.args.iter().skip(1).enumerate() { let arg_type = arg_types .get(n) .ok_or_else(|| vm.new_type_error("argument amount mismatch"))?; - ffi_vals.push(arg_type.convert_object(arg.clone(), vm)?); + let ffi_type = ArgumentType::to_ffi_type(arg_type, vm)?; + let value = arg_type.convert_object(arg.clone(), vm)?; + arguments.push(Argument { + ffi_type, + keep: None, + value, + }); } - Ok(PreparedArgs { - ffi_arg_types: ffi_types, - ffi_values: ffi_vals, - out_buffers: Vec::new(), - }) + Ok((arguments, Vec::new())) } else { // Regular function build_callargs_simple(args, arg_types, vm) @@ -1380,12 +1446,10 @@ enum RawResult { } /// Execute FFI call -fn ctypes_callproc(code_ptr: CodePtr, prepared: &PreparedArgs, call_info: &CallInfo) -> RawResult { - let cif = Cif::new( - prepared.ffi_arg_types.clone(), - call_info.ffi_return_type.clone(), - ); - let ffi_args: Vec<Arg> = prepared.ffi_values.iter().map(|v| v.as_arg()).collect(); +fn ctypes_callproc(code_ptr: CodePtr, arguments: &[Argument], call_info: &CallInfo) -> RawResult { + let ffi_arg_types: Vec<Type> = arguments.iter().map(|a| a.ffi_type.clone()).collect(); + let cif = Cif::new(ffi_arg_types, call_info.ffi_return_type.clone()); + let ffi_args: Vec<Arg> = arguments.iter().map(|a| a.value.as_arg()).collect(); if call_info.restype_is_none { unsafe { cif.call::<()>(code_ptr, &ffi_args) }; @@ -1438,73 +1502,118 @@ fn check_hresult(hresult: i32, zelf: &Py<PyCFuncPtr>, vm: &VirtualMachine) -> Py } /// Convert raw FFI result to Python object +// = GetResult fn convert_raw_result( raw_result: &mut RawResult, call_info: &CallInfo, vm: &VirtualMachine, ) -> Option<PyObjectRef> { - match raw_result { - RawResult::Void => None, + // Get result as bytes for type conversion + let (result_bytes, result_size) = match raw_result { + RawResult::Void => return None, RawResult::Pointer(ptr) => { - // Get type code from restype to determine conversion method - let type_code = call_info - .restype_obj - .as_ref() - .and_then(|t| t.clone().downcast::<PyType>().ok()) - .and_then(|t| t.as_object().get_attr(vm.ctx.intern_str("_type_"), vm).ok()) - .and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string())); - - match type_code.as_deref() { - Some("z") => { - // c_char_p: NULL -> None, otherwise read C string -> bytes - if *ptr == 0 { - Some(vm.ctx.none()) - } else { - let cstr = unsafe { std::ffi::CStr::from_ptr(*ptr as _) }; - Some(vm.ctx.new_bytes(cstr.to_bytes().to_vec()).into()) - } - } - Some("Z") => { - // c_wchar_p: NULL -> None, otherwise read wide string -> str - if *ptr == 0 { - Some(vm.ctx.none()) - } else { - let wstr_ptr = *ptr as *const libc::wchar_t; - let mut len = 0; - unsafe { - while *wstr_ptr.add(len) != 0 { - len += 1; - } - } - let slice = unsafe { std::slice::from_raw_parts(wstr_ptr, len) }; - let s: String = slice - .iter() - .filter_map(|&c| char::from_u32(c as u32)) - .collect(); - Some(vm.ctx.new_str(s).into()) - } - } - _ => { - // c_void_p ("P") and other pointer types: NULL -> None, otherwise int - if *ptr == 0 { - Some(vm.ctx.none()) - } else { - Some(vm.ctx.new_int(*ptr).into()) - } - } - } + let bytes = ptr.to_ne_bytes(); + (bytes.to_vec(), std::mem::size_of::<usize>()) } - RawResult::Value(val) => call_info - .restype_obj - .as_ref() - .and_then(|f| f.clone().downcast::<PyType>().ok()) - .map(|f| { - f.from_ffi_type(val as *mut _ as *mut c_void, vm) - .ok() - .flatten() - }) - .unwrap_or_else(|| Some(vm.ctx.new_int(*val as usize).as_object().to_pyobject(vm))), + RawResult::Value(val) => { + let bytes = val.to_ne_bytes(); + (bytes.to_vec(), std::mem::size_of::<i64>()) + } + }; + + // 1. No restype → return as int + let restype = match &call_info.restype_obj { + None => { + // Default: return as int + let val = match raw_result { + RawResult::Pointer(p) => *p as isize, + RawResult::Value(v) => *v as isize, + RawResult::Void => return None, + }; + return Some(vm.ctx.new_int(val).into()); + } + Some(r) => r, + }; + + // 2. restype is None → return None + if restype.is(&vm.ctx.none()) { + return None; + } + + // 3. Get restype as PyType + let restype_type = match restype.clone().downcast::<PyType>() { + Ok(t) => t, + Err(_) => { + // Not a type, call it with int result + let val = match raw_result { + RawResult::Pointer(p) => *p as isize, + RawResult::Value(v) => *v as isize, + RawResult::Void => return None, + }; + return restype.call((val,), vm).ok(); + } + }; + + // 4. Get StgInfo + let stg_info = restype_type.stg_info_opt(); + + // No StgInfo → call restype with int + if stg_info.is_none() { + let val = match raw_result { + RawResult::Pointer(p) => *p as isize, + RawResult::Value(v) => *v as isize, + RawResult::Void => return None, + }; + return restype_type.as_object().call((val,), vm).ok(); + } + + let info = stg_info.unwrap(); + + // 5. Simple type with getfunc → use bytes_to_pyobject (info->getfunc) + // is_simple_instance returns TRUE for c_int, c_void_p, etc. + if super::base::is_simple_instance(&restype_type) { + return super::base::bytes_to_pyobject(&restype_type, &result_bytes, vm).ok(); + } + + // 6. Complex type → create ctypes instance (PyCData_FromBaseObj) + // This handles POINTER(T), Structure, Array, etc. + + // Special handling for POINTER(T) types - set pointer value directly + if info.flags.contains(StgInfoFlags::TYPEFLAG_ISPOINTER) + && info.proto.is_some() + && let RawResult::Pointer(ptr) = raw_result + && let Ok(instance) = restype_type.as_object().call((), vm) + { + if let Some(pointer) = instance.downcast_ref::<PyCPointer>() { + pointer.set_ptr_value(*ptr); + } + return Some(instance); } + + // Create instance and copy result data + pycdata_from_ffi_result(&restype_type, &result_bytes, result_size, vm).ok() +} + +/// Create a ctypes instance from FFI result (PyCData_FromBaseObj equivalent) +fn pycdata_from_ffi_result( + typ: &PyTypeRef, + result_bytes: &[u8], + size: usize, + vm: &VirtualMachine, +) -> PyResult { + // Create instance + let instance = PyType::call(typ, ().into(), vm)?; + + // Copy result data into instance buffer + if let Some(cdata) = instance.downcast_ref::<PyCData>() { + let mut buffer = cdata.buffer.write(); + let copy_size = size.min(buffer.len()).min(result_bytes.len()); + if copy_size > 0 { + buffer.to_mut()[..copy_size].copy_from_slice(&result_bytes[..copy_size]); + } + } + + Ok(instance) } /// Extract values from OUT buffers @@ -1522,7 +1631,7 @@ fn extract_out_values( fn build_result( mut raw_result: RawResult, call_info: &CallInfo, - prepared: PreparedArgs, + out_buffers: OutBuffers, zelf: &Py<PyCFuncPtr>, args: &FuncArgs, vm: &VirtualMachine, @@ -1552,11 +1661,11 @@ fn build_result( } // Handle OUT parameter return values - if prepared.out_buffers.is_empty() { + if out_buffers.is_empty() { return result.map(Ok).unwrap_or_else(|| Ok(vm.ctx.none())); } - let out_values = extract_out_values(prepared.out_buffers, vm); + let out_values = extract_out_values(out_buffers, vm); Ok(match <[PyObjectRef; 1]>::try_from(out_values) { Ok([single]) => single, Err(v) => PyTuple::new_ref(v, &vm.ctx).into(), @@ -1584,23 +1693,43 @@ impl Callable for PyCFuncPtr { let paramflags = parse_paramflags(zelf, vm)?; // 5. Build call arguments - let prepared = build_callargs(&args, &call_info, paramflags.as_ref(), is_com_method, vm)?; + let (arguments, out_buffers) = + build_callargs(&args, &call_info, paramflags.as_ref(), is_com_method, vm)?; // 6. Get code pointer let code_ptr = match func_ptr.or_else(|| zelf.get_code_ptr()) { Some(cp) => cp, None => { debug_assert!(false, "NULL function pointer"); - // In release mode, this will crash like CPython + // In release mode, this will crash CodePtr(std::ptr::null_mut()) } }; - // 7. Call the function - let raw_result = ctypes_callproc(code_ptr, &prepared, &call_info); + // 7. Get flags to check for use_last_error/use_errno + let flags = PyCFuncPtr::_flags_(zelf, vm); - // 8. Build result - build_result(raw_result, &call_info, prepared, zelf, &args, vm) + // 8. Call the function (with use_last_error/use_errno handling) + #[cfg(not(windows))] + let raw_result = { + if flags & super::base::StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0 { + swap_errno(|| ctypes_callproc(code_ptr, &arguments, &call_info)) + } else { + ctypes_callproc(code_ptr, &arguments, &call_info) + } + }; + + #[cfg(windows)] + let raw_result = { + if flags & super::base::StgInfoFlags::FUNCFLAG_USE_LASTERROR.bits() != 0 { + save_and_restore_last_error(|| ctypes_callproc(code_ptr, &arguments, &call_info)) + } else { + ctypes_callproc(code_ptr, &arguments, &call_info) + } + }; + + // 9. Build result + build_result(raw_result, &call_info, out_buffers, zelf, &args, vm) } } @@ -1614,7 +1743,36 @@ impl Representable for PyCFuncPtr { } } -#[pyclass(flags(BASETYPE), with(Callable, Constructor, Representable))] +// PyCData_NewGetBuffer +impl AsBuffer for PyCFuncPtr { + fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { + // CFuncPtr types may not have StgInfo if PyCFuncPtrType metaclass is not used + // Use default values for function pointers: format="X{}", size=sizeof(pointer) + let (format, itemsize) = if let Some(stg_info) = zelf.class().stg_info_opt() { + ( + stg_info + .format + .clone() + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed("X{}")), + stg_info.size, + ) + } else { + (Cow::Borrowed("X{}"), std::mem::size_of::<usize>()) + }; + let desc = BufferDescriptor { + len: itemsize, + readonly: false, + itemsize, + format, + dim_desc: vec![], + }; + let buf = PyBuffer::new(zelf.to_owned().into(), desc, &CDATA_BUFFER_METHODS); + Ok(buf) + } +} + +#[pyclass(flags(BASETYPE), with(Callable, Constructor, Representable, AsBuffer))] impl PyCFuncPtr { // restype getter/setter #[pygetset] @@ -1685,7 +1843,6 @@ impl PyCFuncPtr { } // Fallback to StgInfo for native types - use super::base::StgInfoFlags; zelf.class() .stg_info_opt() .map(|stg| stg.flags.bits()) @@ -1708,7 +1865,9 @@ struct ThunkUserData { /// Argument types for conversion arg_types: Vec<PyTypeRef>, /// Result type for conversion (None means void) - res_type: Option<PyTypeRef>, + pub res_type: Option<PyTypeRef>, + /// Function flags (FUNCFLAG_USE_ERRNO, etc.) + pub flags: u32, } /// Check if ty is a subclass of a simple type (like MyInt(c_int)). @@ -1758,11 +1917,23 @@ fn ffi_to_python(ty: &Py<PyType>, ptr: *const c_void, vm: &VirtualMachine) -> Py len += 1; } let slice = std::slice::from_raw_parts(wstr_ptr, len); - let s: String = slice - .iter() - .filter_map(|&c| char::from_u32(c as u32)) - .collect(); - vm.ctx.new_str(s).into() + // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide + // Unix: wchar_t = i32 (UTF-32) -> convert via char::from_u32 + #[cfg(windows)] + { + use rustpython_common::wtf8::Wtf8Buf; + let wide: Vec<u16> = slice.to_vec(); + let wtf8 = Wtf8Buf::from_wide(&wide); + vm.ctx.new_str(wtf8).into() + } + #[cfg(not(windows))] + { + let s: String = slice + .iter() + .filter_map(|&c| char::from_u32(c as u32)) + .collect(); + vm.ctx.new_str(s).into() + } } } Some("P") => vm.ctx.new_int(*(ptr as *const usize)).into(), @@ -1865,6 +2036,16 @@ unsafe extern "C" fn thunk_callback( userdata: &ThunkUserData, ) { with_current_vm(|vm| { + // Swap errno before call if FUNCFLAG_USE_ERRNO is set + let use_errno = userdata.flags & StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0; + let saved_errno = if use_errno { + let current = rustpython_common::os::get_errno(); + // TODO: swap with ctypes stored errno (thread-local) + Some(current) + } else { + None + }; + let py_args: Vec<PyObjectRef> = userdata .arg_types .iter() @@ -1877,6 +2058,15 @@ unsafe extern "C" fn thunk_callback( let py_result = userdata.callable.call(py_args, vm); + // Swap errno back after call + if use_errno { + let _current = rustpython_common::os::get_errno(); + // TODO: store current errno to ctypes storage + if let Some(saved) = saved_errno { + rustpython_common::os::set_errno(saved); + } + } + // Call unraisable hook if exception occurred if let Err(exc) = &py_result { let repr = userdata @@ -1935,6 +2125,7 @@ impl PyCThunk { callable: PyObjectRef, arg_types: Option<PyObjectRef>, res_type: Option<PyObjectRef>, + flags: u32, vm: &VirtualMachine, ) -> PyResult<Self> { let arg_type_vec: Vec<PyTypeRef> = match arg_types { @@ -1979,6 +2170,7 @@ impl PyCThunk { callable: callable.clone(), arg_types: arg_type_vec, res_type: res_type_ref, + flags, }); let userdata_ptr = Box::into_raw(userdata); let userdata_ref: &'static ThunkUserData = unsafe { &*userdata_ptr }; diff --git a/crates/vm/src/stdlib/ctypes/library.rs b/crates/vm/src/stdlib/ctypes/library.rs index ec8ca91af0d..7512ce29d8a 100644 --- a/crates/vm/src/stdlib/ctypes/library.rs +++ b/crates/vm/src/stdlib/ctypes/library.rs @@ -2,12 +2,14 @@ use crate::VirtualMachine; use libloading::Library; use rustpython_common::lock::{PyMutex, PyRwLock}; use std::collections::HashMap; -use std::ffi::{OsStr, c_void}; +use std::ffi::OsStr; use std::fmt; -use std::ptr::null; -pub(super) struct SharedLibrary { - pub(super) lib: PyMutex<Option<Library>>, +#[cfg(unix)] +use libloading::os::unix::Library as UnixLibrary; + +pub struct SharedLibrary { + pub(crate) lib: PyMutex<Option<Library>>, } impl fmt::Debug for SharedLibrary { @@ -17,18 +19,44 @@ impl fmt::Debug for SharedLibrary { } impl SharedLibrary { - fn new(name: impl AsRef<OsStr>) -> Result<SharedLibrary, libloading::Error> { + #[cfg(windows)] + pub fn new(name: impl AsRef<OsStr>) -> Result<SharedLibrary, libloading::Error> { Ok(SharedLibrary { lib: PyMutex::new(unsafe { Some(Library::new(name.as_ref())?) }), }) } - fn get_pointer(&self) -> usize { + #[cfg(unix)] + pub fn new_with_mode( + name: impl AsRef<OsStr>, + mode: i32, + ) -> Result<SharedLibrary, libloading::Error> { + Ok(SharedLibrary { + lib: PyMutex::new(Some(unsafe { + UnixLibrary::open(Some(name.as_ref()), mode)?.into() + })), + }) + } + + /// Create a SharedLibrary from a raw dlopen handle (for pythonapi / dlopen(NULL)) + #[cfg(unix)] + pub fn from_raw_handle(handle: *mut libc::c_void) -> SharedLibrary { + SharedLibrary { + lib: PyMutex::new(Some(unsafe { UnixLibrary::from_raw(handle).into() })), + } + } + + /// Get the underlying OS handle (HMODULE on Windows, dlopen handle on Unix) + pub fn get_pointer(&self) -> usize { let lib_lock = self.lib.lock(); if let Some(l) = &*lib_lock { - l as *const Library as usize + // libloading::Library internally stores the OS handle directly + // On Windows: HMODULE (*mut c_void) + // On Unix: *mut c_void from dlopen + // We use transmute_copy to read the handle without consuming the Library + unsafe { std::mem::transmute_copy::<Library, usize>(l) } } else { - null::<c_void>() as usize + 0 } } @@ -36,16 +64,6 @@ impl SharedLibrary { let lib_lock = self.lib.lock(); lib_lock.is_none() } - - fn close(&self) { - *self.lib.lock() = None; - } -} - -impl Drop for SharedLibrary { - fn drop(&mut self) { - self.close(); - } } pub(super) struct ExternalLibs { @@ -63,6 +81,7 @@ impl ExternalLibs { self.libraries.get(&key) } + #[cfg(windows)] pub fn get_or_insert_lib( &mut self, library_path: impl AsRef<OsStr>, @@ -71,20 +90,53 @@ impl ExternalLibs { let new_lib = SharedLibrary::new(library_path)?; let key = new_lib.get_pointer(); - match self.libraries.get(&key) { - Some(l) => { - if l.is_closed() { - self.libraries.insert(key, new_lib); - } - } - _ => { - self.libraries.insert(key, new_lib); - } - }; + // Check if library already exists and is not closed + let should_use_cached = self.libraries.get(&key).is_some_and(|l| !l.is_closed()); + + if should_use_cached { + // new_lib will be dropped, calling FreeLibrary (decrements refcount) + // But library stays loaded because cached version maintains refcount + drop(new_lib); + return Ok((key, self.libraries.get(&key).expect("just checked"))); + } + self.libraries.insert(key, new_lib); Ok((key, self.libraries.get(&key).expect("just inserted"))) } + #[cfg(unix)] + pub fn get_or_insert_lib_with_mode( + &mut self, + library_path: impl AsRef<OsStr>, + mode: i32, + _vm: &VirtualMachine, + ) -> Result<(usize, &SharedLibrary), libloading::Error> { + let new_lib = SharedLibrary::new_with_mode(library_path, mode)?; + let key = new_lib.get_pointer(); + + // Check if library already exists and is not closed + let should_use_cached = self.libraries.get(&key).is_some_and(|l| !l.is_closed()); + + if should_use_cached { + // new_lib will be dropped, calling dlclose (decrements refcount) + // But library stays loaded because cached version maintains refcount + drop(new_lib); + return Ok((key, self.libraries.get(&key).expect("just checked"))); + } + + self.libraries.insert(key, new_lib); + Ok((key, self.libraries.get(&key).expect("just inserted"))) + } + + /// Insert a raw dlopen handle into the cache (for pythonapi / dlopen(NULL)) + #[cfg(unix)] + pub fn insert_raw_handle(&mut self, handle: *mut libc::c_void) -> usize { + let shared_lib = SharedLibrary::from_raw_handle(handle); + let key = handle as usize; + self.libraries.insert(key, shared_lib); + key + } + pub fn drop_lib(&mut self, key: usize) { self.libraries.remove(&key); } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 3ee39af3a7a..ae97b741b3c 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -1,6 +1,7 @@ +use super::base::CDATA_BUFFER_METHODS; use super::{PyCArray, PyCData, PyCSimple, PyCStructure, StgInfo, StgInfoFlags}; -use crate::protocol::PyNumberMethods; -use crate::types::{AsNumber, Constructor, Initializer}; +use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}; +use crate::types::{AsBuffer, AsNumber, Constructor, Initializer}; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef}, @@ -8,6 +9,7 @@ use crate::{ function::{FuncArgs, OptionalArg}, }; use num_traits::ToPrimitive; +use std::borrow::Cow; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] #[derive(Debug)] @@ -42,11 +44,19 @@ impl Initializer for PyCPointerType { stg_info.length = 1; stg_info.flags |= StgInfoFlags::TYPEFLAG_ISPOINTER; - // Set format string: "&<element_format>" - if let Some(ref proto) = stg_info.proto { - let item_info = proto.stg_info_opt().expect("proto has StgInfo"); + // Set format string: "&<element_format>" or "&(shape)<element_format>" for arrays + if let Some(ref proto) = stg_info.proto + && let Some(item_info) = proto.stg_info_opt() + { let current_format = item_info.format.as_deref().unwrap_or("B"); - stg_info.format = Some(format!("&{}", current_format)); + // Include shape for array types in the pointer format + let shape_str = if !item_info.shape.is_empty() { + let dims: Vec<String> = item_info.shape.iter().map(|d| d.to_string()).collect(); + format!("({})", dims.join(",")) + } else { + String::new() + }; + stg_info.format = Some(format!("&{}{}", shape_str, current_format)); } let _ = new_type.init_type_data(stg_info); @@ -69,6 +79,15 @@ impl PyCPointerType { return Ok(value); } + // 1.5 CArgObject (from byref()) - check if underlying obj is instance of _type_ + if let Some(carg) = value.downcast_ref::<super::_ctypes::CArgObject>() + && let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) + && let Ok(type_ref) = type_attr.downcast::<PyType>() + && carg.obj.is_instance(type_ref.as_object(), vm)? + { + return Ok(value); + } + // 2. If already an instance of the requested type, return it if value.is_instance(cls.as_object(), vm)? { return Ok(value); @@ -149,10 +168,17 @@ impl PyCPointerType { if let Some(mut stg_info) = zelf.get_type_data_mut::<StgInfo>() { stg_info.proto = Some(typ_type.clone()); - // Update format string: "&<element_format>" + // Update format string: "&<element_format>" or "&(shape)<element_format>" for arrays let item_info = typ_type.stg_info_opt().expect("proto has StgInfo"); let current_format = item_info.format.as_deref().unwrap_or("B"); - stg_info.format = Some(format!("&{}", current_format)); + // Include shape for array types in the pointer format + let shape_str = if !item_info.shape.is_empty() { + let dims: Vec<String> = item_info.shape.iter().map(|d| d.to_string()).collect(); + format!("({})", dims.join(",")) + } else { + String::new() + }; + stg_info.format = Some(format!("&{}{}", shape_str, current_format)); } // 4. Set _type_ attribute on the pointer type @@ -233,7 +259,10 @@ impl Initializer for PyCPointer { } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, Initializer))] +#[pyclass( + flags(BASETYPE, IMMUTABLETYPE), + with(Constructor, Initializer, AsBuffer) +)] impl PyCPointer { /// Get the pointer value stored in buffer as usize pub fn get_ptr_value(&self) -> usize { @@ -605,20 +634,47 @@ impl PyCPointer { unsafe { let ptr = addr as *const u8; match type_code { + // Single-byte types don't need read_unaligned Some("c") => Ok(vm.ctx.new_bytes(vec![*ptr]).into()), - Some("b") => Ok(vm.ctx.new_int(*(ptr as *const i8) as i32).into()), + Some("b") => Ok(vm.ctx.new_int(*ptr as i8 as i32).into()), Some("B") => Ok(vm.ctx.new_int(*ptr as i32).into()), - Some("h") => Ok(vm.ctx.new_int(*(ptr as *const i16) as i32).into()), - Some("H") => Ok(vm.ctx.new_int(*(ptr as *const u16) as i32).into()), - Some("i") | Some("l") => Ok(vm.ctx.new_int(*(ptr as *const i32)).into()), - Some("I") | Some("L") => Ok(vm.ctx.new_int(*(ptr as *const u32)).into()), - Some("q") => Ok(vm.ctx.new_int(*(ptr as *const i64)).into()), - Some("Q") => Ok(vm.ctx.new_int(*(ptr as *const u64)).into()), - Some("f") => Ok(vm.ctx.new_float(*(ptr as *const f32) as f64).into()), - Some("d") | Some("g") => Ok(vm.ctx.new_float(*(ptr as *const f64)).into()), - Some("P") | Some("z") | Some("Z") => { - Ok(vm.ctx.new_int(*(ptr as *const usize)).into()) - } + // Multi-byte types need read_unaligned for safety on strict-alignment architectures + Some("h") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const i16) as i32) + .into()), + Some("H") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const u16) as i32) + .into()), + Some("i") | Some("l") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const i32)) + .into()), + Some("I") | Some("L") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const u32)) + .into()), + Some("q") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const i64)) + .into()), + Some("Q") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const u64)) + .into()), + Some("f") => Ok(vm + .ctx + .new_float(std::ptr::read_unaligned(ptr as *const f32) as f64) + .into()), + Some("d") | Some("g") => Ok(vm + .ctx + .new_float(std::ptr::read_unaligned(ptr as *const f64)) + .into()), + Some("P") | Some("z") | Some("Z") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const usize)) + .into()), _ => { // Default: read as bytes let bytes = std::slice::from_raw_parts(ptr, size).to_vec(); @@ -652,27 +708,37 @@ impl PyCPointer { "bytes/string or integer address expected".to_owned(), )); }; - *(ptr as *mut usize) = ptr_val; + std::ptr::write_unaligned(ptr as *mut usize, ptr_val); return Ok(()); } _ => {} } // Try to get value as integer + // Use write_unaligned for safety on strict-alignment architectures if let Ok(int_val) = value.try_int(vm) { let i = int_val.as_bigint(); match size { 1 => { - *ptr = i.to_u8().unwrap_or(0); + *ptr = i.to_u8().expect("int too large"); } 2 => { - *(ptr as *mut i16) = i.to_i16().unwrap_or(0); + std::ptr::write_unaligned( + ptr as *mut i16, + i.to_i16().expect("int too large"), + ); } 4 => { - *(ptr as *mut i32) = i.to_i32().unwrap_or(0); + std::ptr::write_unaligned( + ptr as *mut i32, + i.to_i32().expect("int too large"), + ); } 8 => { - *(ptr as *mut i64) = i.to_i64().unwrap_or(0); + std::ptr::write_unaligned( + ptr as *mut i64, + i.to_i64().expect("int too large"), + ); } _ => { let bytes = i.to_signed_bytes_le(); @@ -688,10 +754,10 @@ impl PyCPointer { let f = float_val.to_f64(); match size { 4 => { - *(ptr as *mut f32) = f as f32; + std::ptr::write_unaligned(ptr as *mut f32, f as f32); } 8 => { - *(ptr as *mut f64) = f; + std::ptr::write_unaligned(ptr as *mut f64, f); } _ => {} } @@ -712,3 +778,28 @@ impl PyCPointer { } } } + +impl AsBuffer for PyCPointer { + fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { + let stg_info = zelf + .class() + .stg_info_opt() + .expect("PyCPointer type must have StgInfo"); + let format = stg_info + .format + .clone() + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed("&B")); + let itemsize = stg_info.size; + // Pointer types are scalars with ndim=0, shape=() + let desc = BufferDescriptor { + len: itemsize, + readonly: false, + itemsize, + format, + dim_desc: vec![], + }; + let buf = PyBuffer::new(zelf.to_owned().into(), desc, &CDATA_BUFFER_METHODS); + Ok(buf) + } +} diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs index 1c0ec250d72..803b38d6e05 100644 --- a/crates/vm/src/stdlib/ctypes/simple.rs +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -14,12 +14,47 @@ use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}; use crate::types::{AsBuffer, AsNumber, Constructor, Initializer, Representable}; use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; use num_traits::ToPrimitive; +use std::borrow::Cow; use std::fmt::Debug; /// Valid type codes for ctypes simple types // spell-checker: disable-next-line pub(super) const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQPXOv?g"; +/// Convert ctypes type code to PEP 3118 format code. +/// Some ctypes codes need to be mapped to standard-size codes based on platform. +/// _ctypes_alloc_format_string_for_type +fn ctypes_code_to_pep3118(code: char) -> char { + match code { + // c_int: map based on sizeof(int) + 'i' if std::mem::size_of::<std::ffi::c_int>() == 2 => 'h', + 'i' if std::mem::size_of::<std::ffi::c_int>() == 4 => 'i', + 'i' if std::mem::size_of::<std::ffi::c_int>() == 8 => 'q', + 'I' if std::mem::size_of::<std::ffi::c_int>() == 2 => 'H', + 'I' if std::mem::size_of::<std::ffi::c_int>() == 4 => 'I', + 'I' if std::mem::size_of::<std::ffi::c_int>() == 8 => 'Q', + // c_long: map based on sizeof(long) + 'l' if std::mem::size_of::<std::ffi::c_long>() == 4 => 'l', + 'l' if std::mem::size_of::<std::ffi::c_long>() == 8 => 'q', + 'L' if std::mem::size_of::<std::ffi::c_long>() == 4 => 'L', + 'L' if std::mem::size_of::<std::ffi::c_long>() == 8 => 'Q', + // c_bool: map based on sizeof(bool) - typically 1 byte on all platforms + '?' if std::mem::size_of::<bool>() == 1 => '?', + '?' if std::mem::size_of::<bool>() == 2 => 'H', + '?' if std::mem::size_of::<bool>() == 4 => 'L', + '?' if std::mem::size_of::<bool>() == 8 => 'Q', + // Default: use the same code + _ => code, + } +} + +/// _ctypes_alloc_format_string_for_type +fn alloc_format_string_for_type(code: char, big_endian: bool) -> String { + let prefix = if big_endian { ">" } else { "<" }; + let pep_code = ctypes_code_to_pep3118(code); + format!("{}{}", prefix, pep_code) +} + /// Create a new simple type instance from a class fn new_simple_type( cls: Either<&PyObject, &Py<PyType>>, @@ -165,6 +200,20 @@ fn set_primitive(_type_: &str, value: &PyObject, vm: &VirtualMachine) -> PyResul } // O_set: py_object accepts any Python object "O" => Ok(value.to_owned()), + // X_set: BSTR - same as Z (c_wchar_p), accepts None, int, or str + "X" => { + if value.is(&vm.ctx.none) + || value.downcast_ref_if_exact::<PyInt>(vm).is_some() + || value.downcast_ref_if_exact::<PyStr>(vm).is_some() + { + Ok(value.to_owned()) + } else { + Err(vm.new_type_error(format!( + "unicode string or integer address expected instead of {} instance", + value.class().name() + ))) + } + } _ => { // "P" if value.downcast_ref_if_exact::<PyInt>(vm).is_some() @@ -313,7 +362,7 @@ impl PyCSimpleType { .to_pyobject(vm)); } // 2. Array/Pointer with c_wchar element type - if is_cwchar_array_or_pointer(&value, vm) { + if is_cwchar_array_or_pointer(&value, vm)? { return Ok(value); } // 3. CArgObject (byref(c_wchar(...))) @@ -521,13 +570,10 @@ impl Initializer for PyCSimpleType { let mut stg_info = StgInfo::new(size, align); // Set format for PEP 3118 buffer protocol - // Format is endian prefix + type code (e.g., "<i" for little-endian int) - let endian_prefix = if cfg!(target_endian = "little") { - "<" - } else { - ">" - }; - stg_info.format = Some(format!("{}{}", endian_prefix, type_str)); + stg_info.format = Some(alloc_format_string_for_type( + type_str.chars().next().unwrap_or('?'), + cfg!(target_endian = "big"), + )); stg_info.paramfunc = super::base::ParamFunc::Simple; // Set TYPEFLAG_ISPOINTER for pointer types: z (c_char_p), Z (c_wchar_p), @@ -619,13 +665,14 @@ fn create_swapped_types( swapped_type.set_attr("_swappedbytes_", vm.ctx.none(), vm)?; // Update swapped type's StgInfo format to use opposite endian prefix - // Native uses '<' on little-endian, '>' on big-endian - // Swapped uses the opposite if let Ok(swapped_type_ref) = swapped_type.clone().downcast::<PyType>() && let Some(mut sw_stg) = swapped_type_ref.get_type_data_mut::<StgInfo>() { - let swapped_prefix = if is_little_endian { ">" } else { "<" }; - sw_stg.format = Some(format!("{}{}", swapped_prefix, type_str)); + // Swapped: little-endian system uses big-endian prefix and vice versa + sw_stg.format = Some(alloc_format_string_for_type( + type_str.chars().next().unwrap_or('?'), + is_little_endian, + )); } // Set attributes based on system byte order @@ -734,63 +781,56 @@ fn value_to_bytes_endian( } "b" => { // c_byte - signed char (1 byte) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i8; + let v = int_val.as_bigint().to_i128().expect("int too large") as i8; return vec![v as u8]; } vec![0] } "B" => { // c_ubyte - unsigned char (1 byte) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u8).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u8; return vec![v]; } vec![0] } "h" => { // c_short (2 bytes) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i16; + let v = int_val.as_bigint().to_i128().expect("int too large") as i16; return to_bytes!(v); } vec![0; 2] } "H" => { // c_ushort (2 bytes) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u16).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u16; return to_bytes!(v); } vec![0; 2] } "i" => { // c_int (4 bytes) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i32; + let v = int_val.as_bigint().to_i128().expect("int too large") as i32; return to_bytes!(v); } vec![0; 4] } "I" => { // c_uint (4 bytes) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u32).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u32; return to_bytes!(v); } vec![0; 4] } "l" => { // c_long (platform dependent) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as libc::c_long; + let v = int_val.as_bigint().to_i128().expect("int too large") as libc::c_long; return to_bytes!(v); } const SIZE: usize = std::mem::size_of::<libc::c_long>(); @@ -798,13 +838,8 @@ fn value_to_bytes_endian( } "L" => { // c_ulong (platform dependent) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val - .as_bigint() - .to_i128() - .map(|n| n as libc::c_ulong) - .unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as libc::c_ulong; return to_bytes!(v); } const SIZE: usize = std::mem::size_of::<libc::c_ulong>(); @@ -812,18 +847,16 @@ fn value_to_bytes_endian( } "q" => { // c_longlong (8 bytes) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i64; + let v = int_val.as_bigint().to_i128().expect("int too large") as i64; return to_bytes!(v); } vec![0; 8] } "Q" => { // c_ulonglong (8 bytes) - // PyLong_AsUnsignedLongLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u64).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u64; return to_bytes!(v); } vec![0; 8] @@ -899,7 +932,10 @@ fn value_to_bytes_endian( "P" => { // c_void_p - pointer type (platform pointer size) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_usize().unwrap_or(0); + let v = int_val + .as_bigint() + .to_usize() + .expect("int too large for pointer"); return to_bytes!(v); } vec![0; std::mem::size_of::<usize>()] @@ -908,7 +944,10 @@ fn value_to_bytes_endian( // c_char_p - pointer to char (stores pointer value from int) // PyBytes case is handled in slot_new/set_value with make_z_buffer() if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_usize().unwrap_or(0); + let v = int_val + .as_bigint() + .to_usize() + .expect("int too large for pointer"); return to_bytes!(v); } vec![0; std::mem::size_of::<usize>()] @@ -917,7 +956,10 @@ fn value_to_bytes_endian( // c_wchar_p - pointer to wchar_t (stores pointer value from int) // PyStr case is handled in slot_new/set_value with make_wchar_buffer() if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_usize().unwrap_or(0); + let v = int_val + .as_bigint() + .to_usize() + .expect("int too large for pointer"); return to_bytes!(v); } vec![0; std::mem::size_of::<usize>()] @@ -939,7 +981,7 @@ fn is_cchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { if let Some(arr) = value.downcast_ref::<PyCArray>() && let Some(info) = arr.class().stg_info_opt() && let Some(ref elem_type) = info.element_type - && let Some(elem_code) = elem_type.class().type_code(vm) + && let Some(elem_code) = elem_type.type_code(vm) { return elem_code == "c"; } @@ -947,7 +989,7 @@ fn is_cchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { if let Some(ptr) = value.downcast_ref::<PyCPointer>() && let Some(info) = ptr.class().stg_info_opt() && let Some(ref proto) = info.proto - && let Some(proto_code) = proto.class().type_code(vm) + && let Some(proto_code) = proto.type_code(vm) { return proto_code == "c"; } @@ -955,25 +997,25 @@ fn is_cchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { } /// Check if value is a c_wchar array or pointer(c_wchar) -fn is_cwchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { +fn is_cwchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult<bool> { // Check Array with c_wchar element type if let Some(arr) = value.downcast_ref::<PyCArray>() { - let info = arr.class().stg_info_opt().expect("array has StgInfo"); + let info = arr.class().stg_info(vm)?; let elem_type = info.element_type.as_ref().expect("array has element_type"); - if let Some(elem_code) = elem_type.class().type_code(vm) { - return elem_code == "u"; + if let Some(elem_code) = elem_type.type_code(vm) { + return Ok(elem_code == "u"); } } // Check Pointer to c_wchar if let Some(ptr) = value.downcast_ref::<PyCPointer>() { - let info = ptr.class().stg_info_opt().expect("pointer has StgInfo"); + let info = ptr.class().stg_info(vm)?; if let Some(ref proto) = info.proto - && let Some(proto_code) = proto.class().type_code(vm) + && let Some(proto_code) = proto.type_code(vm) { - return proto_code == "u"; + return Ok(proto_code == "u"); } } - false + Ok(false) } impl Constructor for PyCSimple { @@ -1121,15 +1163,27 @@ impl PyCSimple { return Ok(vm.ctx.none()); } // Read null-terminated wide string at the address + // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide for surrogate pairs + // Unix: wchar_t = i32 (UTF-32) -> convert via char::from_u32 unsafe { let w_ptr = ptr as *const libc::wchar_t; let len = libc::wcslen(w_ptr); let wchars = std::slice::from_raw_parts(w_ptr, len); - let s: String = wchars - .iter() - .filter_map(|&c| char::from_u32(c as u32)) - .collect(); - return Ok(vm.ctx.new_str(s).into()); + #[cfg(windows)] + { + use rustpython_common::wtf8::Wtf8Buf; + let wide: Vec<u16> = wchars.to_vec(); + let wtf8 = Wtf8Buf::from_wide(&wide); + return Ok(vm.ctx.new_str(wtf8).into()); + } + #[cfg(not(windows))] + { + let s: String = wchars + .iter() + .filter_map(|&c| char::from_u32(c as u32)) + .collect(); + return Ok(vm.ctx.new_str(s).into()); + } } } @@ -1349,12 +1403,25 @@ impl PyCSimple { impl AsBuffer for PyCSimple { fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { - let buffer_len = zelf.0.buffer.read().len(); - let buf = PyBuffer::new( - zelf.to_owned().into(), - BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes - &CDATA_BUFFER_METHODS, - ); + let stg_info = zelf + .class() + .stg_info_opt() + .expect("PyCSimple type must have StgInfo"); + let format = stg_info + .format + .clone() + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed("B")); + let itemsize = stg_info.size; + // Simple types are scalars with ndim=0, shape=() + let desc = BufferDescriptor { + len: itemsize, + readonly: false, + itemsize, + format, + dim_desc: vec![], + }; + let buf = PyBuffer::new(zelf.to_owned().into(), desc, &CDATA_BUFFER_METHODS); Ok(buf) } } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 10b8812e42c..1ca428669d4 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -601,12 +601,6 @@ impl PyCStructure { fn _b0_(&self) -> Option<PyObjectRef> { self.0.base.read().clone() } - - #[pygetset] - fn _fields_(&self, vm: &VirtualMachine) -> PyObjectRef { - // Return the _fields_ from the class, not instance - vm.ctx.none() - } } impl AsBuffer for PyCStructure { From 987216bcb8df3b3ce73056d66bee7dff66f6831c Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Tue, 16 Dec 2025 20:38:32 +0900 Subject: [PATCH 549/819] enable ctypes --- Lib/ctypes/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 3599e13ed28..2ef5e9a4fe7 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -36,9 +36,6 @@ FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \ FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR -# TODO: RUSTPYTHON remove this -from _ctypes import _non_existing_function - # WINOLEAPI -> HRESULT # WINOLEAPI_(type) # From 03d6f4634f86a1b9d12e85159bdd14361921c7ed Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Sat, 20 Dec 2025 14:31:01 +0900 Subject: [PATCH 550/819] Upgrade ctypes to Python v3.13.11 --- Lib/ctypes/__init__.py | 94 ++- Lib/ctypes/_endian.py | 8 +- Lib/ctypes/test/__init__.py | 16 - Lib/ctypes/test/__main__.py | 4 - Lib/ctypes/test/test_anon.py | 73 -- Lib/ctypes/test/test_array_in_pointer.py | 64 -- Lib/ctypes/test/test_arrays.py | 238 ------ Lib/ctypes/test/test_as_parameter.py | 231 ------ Lib/ctypes/test/test_bitfields.py | 297 ------- Lib/ctypes/test/test_buffers.py | 73 -- Lib/ctypes/test/test_bytes.py | 66 -- Lib/ctypes/test/test_byteswap.py | 364 --------- Lib/ctypes/test/test_callbacks.py | 333 -------- Lib/ctypes/test/test_cast.py | 99 --- Lib/ctypes/test/test_cfuncs.py | 218 ----- Lib/ctypes/test/test_checkretval.py | 36 - Lib/ctypes/test/test_delattr.py | 21 - Lib/ctypes/test/test_errno.py | 76 -- Lib/ctypes/test/test_find.py | 127 --- Lib/ctypes/test/test_frombuffer.py | 141 ---- Lib/ctypes/test/test_funcptr.py | 132 --- Lib/ctypes/test/test_functions.py | 384 --------- Lib/ctypes/test/test_incomplete.py | 42 - Lib/ctypes/test/test_init.py | 40 - Lib/ctypes/test/test_internals.py | 100 --- Lib/ctypes/test/test_keeprefs.py | 153 ---- Lib/ctypes/test/test_libc.py | 33 - Lib/ctypes/test/test_loading.py | 182 ----- Lib/ctypes/test/test_macholib.py | 110 --- Lib/ctypes/test/test_memfunctions.py | 79 -- Lib/ctypes/test/test_numbers.py | 218 ----- Lib/ctypes/test/test_objects.py | 67 -- Lib/ctypes/test/test_parameters.py | 250 ------ Lib/ctypes/test/test_pep3118.py | 235 ------ Lib/ctypes/test/test_pickling.py | 81 -- Lib/ctypes/test/test_pointers.py | 223 ----- Lib/ctypes/test/test_prototypes.py | 222 ----- Lib/ctypes/test/test_python_api.py | 85 -- Lib/ctypes/test/test_random_things.py | 77 -- Lib/ctypes/test/test_refcounts.py | 116 --- Lib/ctypes/test/test_repr.py | 29 - Lib/ctypes/test/test_returnfuncptrs.py | 66 -- Lib/ctypes/test/test_simplesubclasses.py | 55 -- Lib/ctypes/test/test_sizes.py | 33 - Lib/ctypes/test/test_slicing.py | 167 ---- Lib/ctypes/test/test_stringptr.py | 77 -- Lib/ctypes/test/test_strings.py | 145 ---- Lib/ctypes/test/test_struct_fields.py | 97 --- Lib/ctypes/test/test_structures.py | 812 ------------------- Lib/ctypes/test/test_unaligned_structures.py | 43 - Lib/ctypes/test/test_unicode.py | 64 -- Lib/ctypes/test/test_values.py | 103 --- Lib/ctypes/test/test_varsize_struct.py | 50 -- Lib/ctypes/test/test_win32.py | 136 ---- Lib/ctypes/test/test_wintypes.py | 43 - Lib/ctypes/util.py | 18 +- 56 files changed, 70 insertions(+), 7276 deletions(-) delete mode 100644 Lib/ctypes/test/__init__.py delete mode 100644 Lib/ctypes/test/__main__.py delete mode 100644 Lib/ctypes/test/test_anon.py delete mode 100644 Lib/ctypes/test/test_array_in_pointer.py delete mode 100644 Lib/ctypes/test/test_arrays.py delete mode 100644 Lib/ctypes/test/test_as_parameter.py delete mode 100644 Lib/ctypes/test/test_bitfields.py delete mode 100644 Lib/ctypes/test/test_buffers.py delete mode 100644 Lib/ctypes/test/test_bytes.py delete mode 100644 Lib/ctypes/test/test_byteswap.py delete mode 100644 Lib/ctypes/test/test_callbacks.py delete mode 100644 Lib/ctypes/test/test_cast.py delete mode 100644 Lib/ctypes/test/test_cfuncs.py delete mode 100644 Lib/ctypes/test/test_checkretval.py delete mode 100644 Lib/ctypes/test/test_delattr.py delete mode 100644 Lib/ctypes/test/test_errno.py delete mode 100644 Lib/ctypes/test/test_find.py delete mode 100644 Lib/ctypes/test/test_frombuffer.py delete mode 100644 Lib/ctypes/test/test_funcptr.py delete mode 100644 Lib/ctypes/test/test_functions.py delete mode 100644 Lib/ctypes/test/test_incomplete.py delete mode 100644 Lib/ctypes/test/test_init.py delete mode 100644 Lib/ctypes/test/test_internals.py delete mode 100644 Lib/ctypes/test/test_keeprefs.py delete mode 100644 Lib/ctypes/test/test_libc.py delete mode 100644 Lib/ctypes/test/test_loading.py delete mode 100644 Lib/ctypes/test/test_macholib.py delete mode 100644 Lib/ctypes/test/test_memfunctions.py delete mode 100644 Lib/ctypes/test/test_numbers.py delete mode 100644 Lib/ctypes/test/test_objects.py delete mode 100644 Lib/ctypes/test/test_parameters.py delete mode 100644 Lib/ctypes/test/test_pep3118.py delete mode 100644 Lib/ctypes/test/test_pickling.py delete mode 100644 Lib/ctypes/test/test_pointers.py delete mode 100644 Lib/ctypes/test/test_prototypes.py delete mode 100644 Lib/ctypes/test/test_python_api.py delete mode 100644 Lib/ctypes/test/test_random_things.py delete mode 100644 Lib/ctypes/test/test_refcounts.py delete mode 100644 Lib/ctypes/test/test_repr.py delete mode 100644 Lib/ctypes/test/test_returnfuncptrs.py delete mode 100644 Lib/ctypes/test/test_simplesubclasses.py delete mode 100644 Lib/ctypes/test/test_sizes.py delete mode 100644 Lib/ctypes/test/test_slicing.py delete mode 100644 Lib/ctypes/test/test_stringptr.py delete mode 100644 Lib/ctypes/test/test_strings.py delete mode 100644 Lib/ctypes/test/test_struct_fields.py delete mode 100644 Lib/ctypes/test/test_structures.py delete mode 100644 Lib/ctypes/test/test_unaligned_structures.py delete mode 100644 Lib/ctypes/test/test_unicode.py delete mode 100644 Lib/ctypes/test/test_values.py delete mode 100644 Lib/ctypes/test/test_varsize_struct.py delete mode 100644 Lib/ctypes/test/test_win32.py delete mode 100644 Lib/ctypes/test/test_wintypes.py diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 2ef5e9a4fe7..80651dc64ce 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -1,6 +1,8 @@ """create and manipulate C data types in Python""" -import os as _os, sys as _sys +import os as _os +import sys as _sys +import sysconfig as _sysconfig import types as _types __version__ = "1.1.0" @@ -107,7 +109,7 @@ class CFunctionType(_CFuncPtr): return CFunctionType if _os.name == "nt": - from _ctypes import LoadLibrary as _dlopen + from _ctypes import LoadLibrary as _LoadLibrary from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL _win_functype_cache = {} @@ -302,8 +304,9 @@ def create_unicode_buffer(init, size=None): raise TypeError(init) -# XXX Deprecated def SetPointerType(pointer, cls): + import warnings + warnings._deprecated("ctypes.SetPointerType", remove=(3, 15)) if _pointer_type_cache.get(cls, None) is not None: raise RuntimeError("This type already exists in the cache") if id(pointer) not in _pointer_type_cache: @@ -312,7 +315,6 @@ def SetPointerType(pointer, cls): _pointer_type_cache[cls] = pointer del _pointer_type_cache[id(pointer)] -# XXX Deprecated def ARRAY(typ, len): return typ * len @@ -344,52 +346,59 @@ def __init__(self, name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=None): + class _FuncPtr(_CFuncPtr): + _flags_ = self._func_flags_ + _restype_ = self._func_restype_ + if use_errno: + _flags_ |= _FUNCFLAG_USE_ERRNO + if use_last_error: + _flags_ |= _FUNCFLAG_USE_LASTERROR + + self._FuncPtr = _FuncPtr if name: name = _os.fspath(name) + self._handle = self._load_library(name, mode, handle, winmode) + + if _os.name == "nt": + def _load_library(self, name, mode, handle, winmode): + if winmode is None: + import nt as _nt + winmode = _nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS + # WINAPI LoadLibrary searches for a DLL if the given name + # is not fully qualified with an explicit drive. For POSIX + # compatibility, and because the DLL search path no longer + # contains the working directory, begin by fully resolving + # any name that contains a path separator. + if name is not None and ('/' in name or '\\' in name): + name = _nt._getfullpathname(name) + winmode |= _nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR + self._name = name + if handle is not None: + return handle + return _LoadLibrary(self._name, winmode) + + else: + def _load_library(self, name, mode, handle, winmode): # If the filename that has been provided is an iOS/tvOS/watchOS # .fwork file, dereference the location to the true origin of the # binary. - if name.endswith(".fwork"): + if name and name.endswith(".fwork"): with open(name) as f: name = _os.path.join( _os.path.dirname(_sys.executable), f.read().strip() ) - - self._name = name - flags = self._func_flags_ - if use_errno: - flags |= _FUNCFLAG_USE_ERRNO - if use_last_error: - flags |= _FUNCFLAG_USE_LASTERROR - if _sys.platform.startswith("aix"): - """When the name contains ".a(" and ends with ")", - e.g., "libFOO.a(libFOO.so)" - this is taken to be an - archive(member) syntax for dlopen(), and the mode is adjusted. - Otherwise, name is presented to dlopen() as a file argument. - """ - if name and name.endswith(")") and ".a(" in name: - mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW ) - if _os.name == "nt": - if winmode is not None: - mode = winmode - else: - import nt - mode = nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS - if '/' in name or '\\' in name: - self._name = nt._getfullpathname(self._name) - mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR - - class _FuncPtr(_CFuncPtr): - _flags_ = flags - _restype_ = self._func_restype_ - self._FuncPtr = _FuncPtr - - if handle is None: - self._handle = _dlopen(self._name, mode) - else: - self._handle = handle + if _sys.platform.startswith("aix"): + """When the name contains ".a(" and ends with ")", + e.g., "libFOO.a(libFOO.so)" - this is taken to be an + archive(member) syntax for dlopen(), and the mode is adjusted. + Otherwise, name is presented to dlopen() as a file argument. + """ + if name and name.endswith(")") and ".a(" in name: + mode |= _os.RTLD_MEMBER | _os.RTLD_NOW + self._name = name + return _dlopen(name, mode) def __repr__(self): return "<%s '%s', handle %x at %#x>" % \ @@ -477,10 +486,9 @@ def LoadLibrary(self, name): if _os.name == "nt": pythonapi = PyDLL("python dll", None, _sys.dllhandle) -elif _sys.platform == "android": - pythonapi = PyDLL("libpython%d.%d.so" % _sys.version_info[:2]) -elif _sys.platform == "cygwin": - pythonapi = PyDLL("libpython%d.%d.dll" % _sys.version_info[:2]) +elif _sys.platform in ["android", "cygwin"]: + # These are Unix-like platforms which use a dynamically-linked libpython. + pythonapi = PyDLL(_sysconfig.get_config_var("LDLIBRARY")) else: pythonapi = PyDLL(None) diff --git a/Lib/ctypes/_endian.py b/Lib/ctypes/_endian.py index 34dee64b1a6..6382dd22b8a 100644 --- a/Lib/ctypes/_endian.py +++ b/Lib/ctypes/_endian.py @@ -1,5 +1,5 @@ import sys -from ctypes import * +from ctypes import Array, Structure, Union _array_type = type(Array) @@ -15,8 +15,8 @@ def _other_endian(typ): # if typ is array if isinstance(typ, _array_type): return _other_endian(typ._type_) * typ._length_ - # if typ is structure - if issubclass(typ, Structure): + # if typ is structure or union + if issubclass(typ, (Structure, Union)): return typ raise TypeError("This type does not support other endian: %s" % typ) @@ -37,7 +37,7 @@ class _swapped_union_meta(_swapped_meta, type(Union)): pass ################################################################ # Note: The Structure metaclass checks for the *presence* (not the -# value!) of a _swapped_bytes_ attribute to determine the bit order in +# value!) of a _swappedbytes_ attribute to determine the bit order in # structures containing bit fields. if sys.byteorder == "little": diff --git a/Lib/ctypes/test/__init__.py b/Lib/ctypes/test/__init__.py deleted file mode 100644 index 6e496fa5a52..00000000000 --- a/Lib/ctypes/test/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -import os -import unittest -from test import support -from test.support import import_helper - - -# skip tests if _ctypes was not built -ctypes = import_helper.import_module('ctypes') -ctypes_symbols = dir(ctypes) - -def need_symbol(name): - return unittest.skipUnless(name in ctypes_symbols, - '{!r} is required'.format(name)) - -def load_tests(*args): - return support.load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/ctypes/test/__main__.py b/Lib/ctypes/test/__main__.py deleted file mode 100644 index 362a9ec8cff..00000000000 --- a/Lib/ctypes/test/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -from ctypes.test import load_tests -import unittest - -unittest.main() diff --git a/Lib/ctypes/test/test_anon.py b/Lib/ctypes/test/test_anon.py deleted file mode 100644 index d378392ebe2..00000000000 --- a/Lib/ctypes/test/test_anon.py +++ /dev/null @@ -1,73 +0,0 @@ -import unittest -import test.support -from ctypes import * - -class AnonTest(unittest.TestCase): - - def test_anon(self): - class ANON(Union): - _fields_ = [("a", c_int), - ("b", c_int)] - - class Y(Structure): - _fields_ = [("x", c_int), - ("_", ANON), - ("y", c_int)] - _anonymous_ = ["_"] - - self.assertEqual(Y.a.offset, sizeof(c_int)) - self.assertEqual(Y.b.offset, sizeof(c_int)) - - self.assertEqual(ANON.a.offset, 0) - self.assertEqual(ANON.b.offset, 0) - - def test_anon_nonseq(self): - # TypeError: _anonymous_ must be a sequence - self.assertRaises(TypeError, - lambda: type(Structure)("Name", - (Structure,), - {"_fields_": [], "_anonymous_": 42})) - - def test_anon_nonmember(self): - # AttributeError: type object 'Name' has no attribute 'x' - self.assertRaises(AttributeError, - lambda: type(Structure)("Name", - (Structure,), - {"_fields_": [], - "_anonymous_": ["x"]})) - - @test.support.cpython_only - def test_issue31490(self): - # There shouldn't be an assertion failure in case the class has an - # attribute whose name is specified in _anonymous_ but not in _fields_. - - # AttributeError: 'x' is specified in _anonymous_ but not in _fields_ - with self.assertRaises(AttributeError): - class Name(Structure): - _fields_ = [] - _anonymous_ = ["x"] - x = 42 - - def test_nested(self): - class ANON_S(Structure): - _fields_ = [("a", c_int)] - - class ANON_U(Union): - _fields_ = [("_", ANON_S), - ("b", c_int)] - _anonymous_ = ["_"] - - class Y(Structure): - _fields_ = [("x", c_int), - ("_", ANON_U), - ("y", c_int)] - _anonymous_ = ["_"] - - self.assertEqual(Y.x.offset, 0) - self.assertEqual(Y.a.offset, sizeof(c_int)) - self.assertEqual(Y.b.offset, sizeof(c_int)) - self.assertEqual(Y._.offset, sizeof(c_int)) - self.assertEqual(Y.y.offset, sizeof(c_int) * 2) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_array_in_pointer.py b/Lib/ctypes/test/test_array_in_pointer.py deleted file mode 100644 index ca1edcf6210..00000000000 --- a/Lib/ctypes/test/test_array_in_pointer.py +++ /dev/null @@ -1,64 +0,0 @@ -import unittest -from ctypes import * -from binascii import hexlify -import re - -def dump(obj): - # helper function to dump memory contents in hex, with a hyphen - # between the bytes. - h = hexlify(memoryview(obj)).decode() - return re.sub(r"(..)", r"\1-", h)[:-1] - - -class Value(Structure): - _fields_ = [("val", c_byte)] - -class Container(Structure): - _fields_ = [("pvalues", POINTER(Value))] - -class Test(unittest.TestCase): - def test(self): - # create an array of 4 values - val_array = (Value * 4)() - - # create a container, which holds a pointer to the pvalues array. - c = Container() - c.pvalues = val_array - - # memory contains 4 NUL bytes now, that's correct - self.assertEqual("00-00-00-00", dump(val_array)) - - # set the values of the array through the pointer: - for i in range(4): - c.pvalues[i].val = i + 1 - - values = [c.pvalues[i].val for i in range(4)] - - # These are the expected results: here s the bug! - self.assertEqual( - (values, dump(val_array)), - ([1, 2, 3, 4], "01-02-03-04") - ) - - def test_2(self): - - val_array = (Value * 4)() - - # memory contains 4 NUL bytes now, that's correct - self.assertEqual("00-00-00-00", dump(val_array)) - - ptr = cast(val_array, POINTER(Value)) - # set the values of the array through the pointer: - for i in range(4): - ptr[i].val = i + 1 - - values = [ptr[i].val for i in range(4)] - - # These are the expected results: here s the bug! - self.assertEqual( - (values, dump(val_array)), - ([1, 2, 3, 4], "01-02-03-04") - ) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_arrays.py b/Lib/ctypes/test/test_arrays.py deleted file mode 100644 index 14603b7049c..00000000000 --- a/Lib/ctypes/test/test_arrays.py +++ /dev/null @@ -1,238 +0,0 @@ -import unittest -from test.support import bigmemtest, _2G -import sys -from ctypes import * - -from ctypes.test import need_symbol - -formats = "bBhHiIlLqQfd" - -formats = c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, \ - c_long, c_ulonglong, c_float, c_double, c_longdouble - -class ArrayTestCase(unittest.TestCase): - def test_simple(self): - # create classes holding simple numeric types, and check - # various properties. - - init = list(range(15, 25)) - - for fmt in formats: - alen = len(init) - int_array = ARRAY(fmt, alen) - - ia = int_array(*init) - # length of instance ok? - self.assertEqual(len(ia), alen) - - # slot values ok? - values = [ia[i] for i in range(alen)] - self.assertEqual(values, init) - - # out-of-bounds accesses should be caught - with self.assertRaises(IndexError): ia[alen] - with self.assertRaises(IndexError): ia[-alen-1] - - # change the items - from operator import setitem - new_values = list(range(42, 42+alen)) - [setitem(ia, n, new_values[n]) for n in range(alen)] - values = [ia[i] for i in range(alen)] - self.assertEqual(values, new_values) - - # are the items initialized to 0? - ia = int_array() - values = [ia[i] for i in range(alen)] - self.assertEqual(values, [0] * alen) - - # Too many initializers should be caught - self.assertRaises(IndexError, int_array, *range(alen*2)) - - CharArray = ARRAY(c_char, 3) - - ca = CharArray(b"a", b"b", b"c") - - # Should this work? It doesn't: - # CharArray("abc") - self.assertRaises(TypeError, CharArray, "abc") - - self.assertEqual(ca[0], b"a") - self.assertEqual(ca[1], b"b") - self.assertEqual(ca[2], b"c") - self.assertEqual(ca[-3], b"a") - self.assertEqual(ca[-2], b"b") - self.assertEqual(ca[-1], b"c") - - self.assertEqual(len(ca), 3) - - # cannot delete items - from operator import delitem - self.assertRaises(TypeError, delitem, ca, 0) - - def test_step_overflow(self): - a = (c_int * 5)() - a[3::sys.maxsize] = (1,) - self.assertListEqual(a[3::sys.maxsize], [1]) - a = (c_char * 5)() - a[3::sys.maxsize] = b"A" - self.assertEqual(a[3::sys.maxsize], b"A") - a = (c_wchar * 5)() - a[3::sys.maxsize] = u"X" - self.assertEqual(a[3::sys.maxsize], u"X") - - def test_numeric_arrays(self): - - alen = 5 - - numarray = ARRAY(c_int, alen) - - na = numarray() - values = [na[i] for i in range(alen)] - self.assertEqual(values, [0] * alen) - - na = numarray(*[c_int()] * alen) - values = [na[i] for i in range(alen)] - self.assertEqual(values, [0]*alen) - - na = numarray(1, 2, 3, 4, 5) - values = [i for i in na] - self.assertEqual(values, [1, 2, 3, 4, 5]) - - na = numarray(*map(c_int, (1, 2, 3, 4, 5))) - values = [i for i in na] - self.assertEqual(values, [1, 2, 3, 4, 5]) - - def test_classcache(self): - self.assertIsNot(ARRAY(c_int, 3), ARRAY(c_int, 4)) - self.assertIs(ARRAY(c_int, 3), ARRAY(c_int, 3)) - - def test_from_address(self): - # Failed with 0.9.8, reported by JUrner - p = create_string_buffer(b"foo") - sz = (c_char * 3).from_address(addressof(p)) - self.assertEqual(sz[:], b"foo") - self.assertEqual(sz[::], b"foo") - self.assertEqual(sz[::-1], b"oof") - self.assertEqual(sz[::3], b"f") - self.assertEqual(sz[1:4:2], b"o") - self.assertEqual(sz.value, b"foo") - - @need_symbol('create_unicode_buffer') - def test_from_addressW(self): - p = create_unicode_buffer("foo") - sz = (c_wchar * 3).from_address(addressof(p)) - self.assertEqual(sz[:], "foo") - self.assertEqual(sz[::], "foo") - self.assertEqual(sz[::-1], "oof") - self.assertEqual(sz[::3], "f") - self.assertEqual(sz[1:4:2], "o") - self.assertEqual(sz.value, "foo") - - def test_cache(self): - # Array types are cached internally in the _ctypes extension, - # in a WeakValueDictionary. Make sure the array type is - # removed from the cache when the itemtype goes away. This - # test will not fail, but will show a leak in the testsuite. - - # Create a new type: - class my_int(c_int): - pass - # Create a new array type based on it: - t1 = my_int * 1 - t2 = my_int * 1 - self.assertIs(t1, t2) - - def test_subclass(self): - class T(Array): - _type_ = c_int - _length_ = 13 - class U(T): - pass - class V(U): - pass - class W(V): - pass - class X(T): - _type_ = c_short - class Y(T): - _length_ = 187 - - for c in [T, U, V, W]: - self.assertEqual(c._type_, c_int) - self.assertEqual(c._length_, 13) - self.assertEqual(c()._type_, c_int) - self.assertEqual(c()._length_, 13) - - self.assertEqual(X._type_, c_short) - self.assertEqual(X._length_, 13) - self.assertEqual(X()._type_, c_short) - self.assertEqual(X()._length_, 13) - - self.assertEqual(Y._type_, c_int) - self.assertEqual(Y._length_, 187) - self.assertEqual(Y()._type_, c_int) - self.assertEqual(Y()._length_, 187) - - def test_bad_subclass(self): - with self.assertRaises(AttributeError): - class T(Array): - pass - with self.assertRaises(AttributeError): - class T(Array): - _type_ = c_int - with self.assertRaises(AttributeError): - class T(Array): - _length_ = 13 - - def test_bad_length(self): - with self.assertRaises(ValueError): - class T(Array): - _type_ = c_int - _length_ = - sys.maxsize * 2 - with self.assertRaises(ValueError): - class T(Array): - _type_ = c_int - _length_ = -1 - with self.assertRaises(TypeError): - class T(Array): - _type_ = c_int - _length_ = 1.87 - with self.assertRaises(OverflowError): - class T(Array): - _type_ = c_int - _length_ = sys.maxsize * 2 - - def test_zero_length(self): - # _length_ can be zero. - class T(Array): - _type_ = c_int - _length_ = 0 - - def test_empty_element_struct(self): - class EmptyStruct(Structure): - _fields_ = [] - - obj = (EmptyStruct * 2)() # bpo37188: Floating point exception - self.assertEqual(sizeof(obj), 0) - - def test_empty_element_array(self): - class EmptyArray(Array): - _type_ = c_int - _length_ = 0 - - obj = (EmptyArray * 2)() # bpo37188: Floating point exception - self.assertEqual(sizeof(obj), 0) - - def test_bpo36504_signed_int_overflow(self): - # The overflow check in PyCArrayType_new() could cause signed integer - # overflow. - with self.assertRaises(OverflowError): - c_char * sys.maxsize * 2 - - @unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform') - @bigmemtest(size=_2G, memuse=1, dry_run=False) - def test_large_array(self, size): - c_char * size - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_as_parameter.py b/Lib/ctypes/test/test_as_parameter.py deleted file mode 100644 index 9c39179d2a4..00000000000 --- a/Lib/ctypes/test/test_as_parameter.py +++ /dev/null @@ -1,231 +0,0 @@ -import unittest -from ctypes import * -from ctypes.test import need_symbol -import _ctypes_test - -dll = CDLL(_ctypes_test.__file__) - -try: - CALLBACK_FUNCTYPE = WINFUNCTYPE -except NameError: - # fake to enable this test on Linux - CALLBACK_FUNCTYPE = CFUNCTYPE - -class POINT(Structure): - _fields_ = [("x", c_int), ("y", c_int)] - -class BasicWrapTestCase(unittest.TestCase): - def wrap(self, param): - return param - - @need_symbol('c_wchar') - def test_wchar_parm(self): - f = dll._testfunc_i_bhilfd - f.argtypes = [c_byte, c_wchar, c_int, c_long, c_float, c_double] - result = f(self.wrap(1), self.wrap("x"), self.wrap(3), self.wrap(4), self.wrap(5.0), self.wrap(6.0)) - self.assertEqual(result, 139) - self.assertIs(type(result), int) - - def test_pointers(self): - f = dll._testfunc_p_p - f.restype = POINTER(c_int) - f.argtypes = [POINTER(c_int)] - - # This only works if the value c_int(42) passed to the - # function is still alive while the pointer (the result) is - # used. - - v = c_int(42) - - self.assertEqual(pointer(v).contents.value, 42) - result = f(self.wrap(pointer(v))) - self.assertEqual(type(result), POINTER(c_int)) - self.assertEqual(result.contents.value, 42) - - # This on works... - result = f(self.wrap(pointer(v))) - self.assertEqual(result.contents.value, v.value) - - p = pointer(c_int(99)) - result = f(self.wrap(p)) - self.assertEqual(result.contents.value, 99) - - def test_shorts(self): - f = dll._testfunc_callback_i_if - - args = [] - expected = [262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, - 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1] - - def callback(v): - args.append(v) - return v - - CallBack = CFUNCTYPE(c_int, c_int) - - cb = CallBack(callback) - f(self.wrap(2**18), self.wrap(cb)) - self.assertEqual(args, expected) - - ################################################################ - - def test_callbacks(self): - f = dll._testfunc_callback_i_if - f.restype = c_int - f.argtypes = None - - MyCallback = CFUNCTYPE(c_int, c_int) - - def callback(value): - #print "called back with", value - return value - - cb = MyCallback(callback) - - result = f(self.wrap(-10), self.wrap(cb)) - self.assertEqual(result, -18) - - # test with prototype - f.argtypes = [c_int, MyCallback] - cb = MyCallback(callback) - - result = f(self.wrap(-10), self.wrap(cb)) - self.assertEqual(result, -18) - - result = f(self.wrap(-10), self.wrap(cb)) - self.assertEqual(result, -18) - - AnotherCallback = CALLBACK_FUNCTYPE(c_int, c_int, c_int, c_int, c_int) - - # check that the prototype works: we call f with wrong - # argument types - cb = AnotherCallback(callback) - self.assertRaises(ArgumentError, f, self.wrap(-10), self.wrap(cb)) - - def test_callbacks_2(self): - # Can also use simple datatypes as argument type specifiers - # for the callback function. - # In this case the call receives an instance of that type - f = dll._testfunc_callback_i_if - f.restype = c_int - - MyCallback = CFUNCTYPE(c_int, c_int) - - f.argtypes = [c_int, MyCallback] - - def callback(value): - #print "called back with", value - self.assertEqual(type(value), int) - return value - - cb = MyCallback(callback) - result = f(self.wrap(-10), self.wrap(cb)) - self.assertEqual(result, -18) - - @need_symbol('c_longlong') - def test_longlong_callbacks(self): - - f = dll._testfunc_callback_q_qf - f.restype = c_longlong - - MyCallback = CFUNCTYPE(c_longlong, c_longlong) - - f.argtypes = [c_longlong, MyCallback] - - def callback(value): - self.assertIsInstance(value, int) - return value & 0x7FFFFFFF - - cb = MyCallback(callback) - - self.assertEqual(13577625587, int(f(self.wrap(1000000000000), self.wrap(cb)))) - - def test_byval(self): - # without prototype - ptin = POINT(1, 2) - ptout = POINT() - # EXPORT int _testfunc_byval(point in, point *pout) - result = dll._testfunc_byval(ptin, byref(ptout)) - got = result, ptout.x, ptout.y - expected = 3, 1, 2 - self.assertEqual(got, expected) - - # with prototype - ptin = POINT(101, 102) - ptout = POINT() - dll._testfunc_byval.argtypes = (POINT, POINTER(POINT)) - dll._testfunc_byval.restype = c_int - result = dll._testfunc_byval(self.wrap(ptin), byref(ptout)) - got = result, ptout.x, ptout.y - expected = 203, 101, 102 - self.assertEqual(got, expected) - - def test_struct_return_2H(self): - class S2H(Structure): - _fields_ = [("x", c_short), - ("y", c_short)] - dll.ret_2h_func.restype = S2H - dll.ret_2h_func.argtypes = [S2H] - inp = S2H(99, 88) - s2h = dll.ret_2h_func(self.wrap(inp)) - self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) - - # Test also that the original struct was unmodified (i.e. was passed by - # value) - self.assertEqual((inp.x, inp.y), (99, 88)) - - def test_struct_return_8H(self): - class S8I(Structure): - _fields_ = [("a", c_int), - ("b", c_int), - ("c", c_int), - ("d", c_int), - ("e", c_int), - ("f", c_int), - ("g", c_int), - ("h", c_int)] - dll.ret_8i_func.restype = S8I - dll.ret_8i_func.argtypes = [S8I] - inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) - s8i = dll.ret_8i_func(self.wrap(inp)) - self.assertEqual((s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), - (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) - - def test_recursive_as_param(self): - from ctypes import c_int - - class A(object): - pass - - a = A() - a._as_parameter_ = a - with self.assertRaises(RecursionError): - c_int.from_param(a) - - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class AsParamWrapper(object): - def __init__(self, param): - self._as_parameter_ = param - -class AsParamWrapperTestCase(BasicWrapTestCase): - wrap = AsParamWrapper - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class AsParamPropertyWrapper(object): - def __init__(self, param): - self._param = param - - def getParameter(self): - return self._param - _as_parameter_ = property(getParameter) - -class AsParamPropertyWrapperTestCase(BasicWrapTestCase): - wrap = AsParamPropertyWrapper - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_bitfields.py b/Lib/ctypes/test/test_bitfields.py deleted file mode 100644 index 66acd62e685..00000000000 --- a/Lib/ctypes/test/test_bitfields.py +++ /dev/null @@ -1,297 +0,0 @@ -from ctypes import * -from ctypes.test import need_symbol -from test import support -import unittest -import os - -import _ctypes_test - -class BITS(Structure): - _fields_ = [("A", c_int, 1), - ("B", c_int, 2), - ("C", c_int, 3), - ("D", c_int, 4), - ("E", c_int, 5), - ("F", c_int, 6), - ("G", c_int, 7), - ("H", c_int, 8), - ("I", c_int, 9), - - ("M", c_short, 1), - ("N", c_short, 2), - ("O", c_short, 3), - ("P", c_short, 4), - ("Q", c_short, 5), - ("R", c_short, 6), - ("S", c_short, 7)] - -func = CDLL(_ctypes_test.__file__).unpack_bitfields -func.argtypes = POINTER(BITS), c_char - -##for n in "ABCDEFGHIMNOPQRS": -## print n, hex(getattr(BITS, n).size), getattr(BITS, n).offset - -class C_Test(unittest.TestCase): - - def test_ints(self): - for i in range(512): - for name in "ABCDEFGHI": - b = BITS() - setattr(b, name, i) - self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii'))) - - # bpo-46913: _ctypes/cfield.c h_get() has an undefined behavior - @support.skip_if_sanitizer(ub=True) - def test_shorts(self): - b = BITS() - name = "M" - if func(byref(b), name.encode('ascii')) == 999: - self.skipTest("Compiler does not support signed short bitfields") - for i in range(256): - for name in "MNOPQRS": - b = BITS() - setattr(b, name, i) - self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii'))) - -signed_int_types = (c_byte, c_short, c_int, c_long, c_longlong) -unsigned_int_types = (c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong) -int_types = unsigned_int_types + signed_int_types - -class BitFieldTest(unittest.TestCase): - - def test_longlong(self): - class X(Structure): - _fields_ = [("a", c_longlong, 1), - ("b", c_longlong, 62), - ("c", c_longlong, 1)] - - self.assertEqual(sizeof(X), sizeof(c_longlong)) - x = X() - x.a, x.b, x.c = -1, 7, -1 - self.assertEqual((x.a, x.b, x.c), (-1, 7, -1)) - - def test_ulonglong(self): - class X(Structure): - _fields_ = [("a", c_ulonglong, 1), - ("b", c_ulonglong, 62), - ("c", c_ulonglong, 1)] - - self.assertEqual(sizeof(X), sizeof(c_longlong)) - x = X() - self.assertEqual((x.a, x.b, x.c), (0, 0, 0)) - x.a, x.b, x.c = 7, 7, 7 - self.assertEqual((x.a, x.b, x.c), (1, 7, 1)) - - def test_signed(self): - for c_typ in signed_int_types: - class X(Structure): - _fields_ = [("dummy", c_typ), - ("a", c_typ, 3), - ("b", c_typ, 3), - ("c", c_typ, 1)] - self.assertEqual(sizeof(X), sizeof(c_typ)*2) - - x = X() - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0)) - x.a = -1 - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, -1, 0, 0)) - x.a, x.b = 0, -1 - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, -1, 0)) - - - def test_unsigned(self): - for c_typ in unsigned_int_types: - class X(Structure): - _fields_ = [("a", c_typ, 3), - ("b", c_typ, 3), - ("c", c_typ, 1)] - self.assertEqual(sizeof(X), sizeof(c_typ)) - - x = X() - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0)) - x.a = -1 - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 7, 0, 0)) - x.a, x.b = 0, -1 - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 7, 0)) - - - def fail_fields(self, *fields): - return self.get_except(type(Structure), "X", (), - {"_fields_": fields}) - - def test_nonint_types(self): - # bit fields are not allowed on non-integer types. - result = self.fail_fields(("a", c_char_p, 1)) - self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_char_p')) - - result = self.fail_fields(("a", c_void_p, 1)) - self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_void_p')) - - if c_int != c_long: - result = self.fail_fields(("a", POINTER(c_int), 1)) - self.assertEqual(result, (TypeError, 'bit fields not allowed for type LP_c_int')) - - result = self.fail_fields(("a", c_char, 1)) - self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_char')) - - class Dummy(Structure): - _fields_ = [] - - result = self.fail_fields(("a", Dummy, 1)) - self.assertEqual(result, (TypeError, 'bit fields not allowed for type Dummy')) - - @need_symbol('c_wchar') - def test_c_wchar(self): - result = self.fail_fields(("a", c_wchar, 1)) - self.assertEqual(result, - (TypeError, 'bit fields not allowed for type c_wchar')) - - def test_single_bitfield_size(self): - for c_typ in int_types: - result = self.fail_fields(("a", c_typ, -1)) - self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) - - result = self.fail_fields(("a", c_typ, 0)) - self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) - - class X(Structure): - _fields_ = [("a", c_typ, 1)] - self.assertEqual(sizeof(X), sizeof(c_typ)) - - class X(Structure): - _fields_ = [("a", c_typ, sizeof(c_typ)*8)] - self.assertEqual(sizeof(X), sizeof(c_typ)) - - result = self.fail_fields(("a", c_typ, sizeof(c_typ)*8 + 1)) - self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) - - def test_multi_bitfields_size(self): - class X(Structure): - _fields_ = [("a", c_short, 1), - ("b", c_short, 14), - ("c", c_short, 1)] - self.assertEqual(sizeof(X), sizeof(c_short)) - - class X(Structure): - _fields_ = [("a", c_short, 1), - ("a1", c_short), - ("b", c_short, 14), - ("c", c_short, 1)] - self.assertEqual(sizeof(X), sizeof(c_short)*3) - self.assertEqual(X.a.offset, 0) - self.assertEqual(X.a1.offset, sizeof(c_short)) - self.assertEqual(X.b.offset, sizeof(c_short)*2) - self.assertEqual(X.c.offset, sizeof(c_short)*2) - - class X(Structure): - _fields_ = [("a", c_short, 3), - ("b", c_short, 14), - ("c", c_short, 14)] - self.assertEqual(sizeof(X), sizeof(c_short)*3) - self.assertEqual(X.a.offset, sizeof(c_short)*0) - self.assertEqual(X.b.offset, sizeof(c_short)*1) - self.assertEqual(X.c.offset, sizeof(c_short)*2) - - - def get_except(self, func, *args, **kw): - try: - func(*args, **kw) - except Exception as detail: - return detail.__class__, str(detail) - - def test_mixed_1(self): - class X(Structure): - _fields_ = [("a", c_byte, 4), - ("b", c_int, 4)] - if os.name == "nt": - self.assertEqual(sizeof(X), sizeof(c_int)*2) - else: - self.assertEqual(sizeof(X), sizeof(c_int)) - - def test_mixed_2(self): - class X(Structure): - _fields_ = [("a", c_byte, 4), - ("b", c_int, 32)] - self.assertEqual(sizeof(X), alignment(c_int)+sizeof(c_int)) - - def test_mixed_3(self): - class X(Structure): - _fields_ = [("a", c_byte, 4), - ("b", c_ubyte, 4)] - self.assertEqual(sizeof(X), sizeof(c_byte)) - - def test_mixed_4(self): - class X(Structure): - _fields_ = [("a", c_short, 4), - ("b", c_short, 4), - ("c", c_int, 24), - ("d", c_short, 4), - ("e", c_short, 4), - ("f", c_int, 24)] - # MSVC does NOT combine c_short and c_int into one field, GCC - # does (unless GCC is run with '-mms-bitfields' which - # produces code compatible with MSVC). - if os.name == "nt": - self.assertEqual(sizeof(X), sizeof(c_int) * 4) - else: - self.assertEqual(sizeof(X), sizeof(c_int) * 2) - - def test_anon_bitfields(self): - # anonymous bit-fields gave a strange error message - class X(Structure): - _fields_ = [("a", c_byte, 4), - ("b", c_ubyte, 4)] - class Y(Structure): - _anonymous_ = ["_"] - _fields_ = [("_", X)] - - @need_symbol('c_uint32') - def test_uint32(self): - class X(Structure): - _fields_ = [("a", c_uint32, 32)] - x = X() - x.a = 10 - self.assertEqual(x.a, 10) - x.a = 0xFDCBA987 - self.assertEqual(x.a, 0xFDCBA987) - - @need_symbol('c_uint64') - def test_uint64(self): - class X(Structure): - _fields_ = [("a", c_uint64, 64)] - x = X() - x.a = 10 - self.assertEqual(x.a, 10) - x.a = 0xFEDCBA9876543211 - self.assertEqual(x.a, 0xFEDCBA9876543211) - - @need_symbol('c_uint32') - def test_uint32_swap_little_endian(self): - # Issue #23319 - class Little(LittleEndianStructure): - _fields_ = [("a", c_uint32, 24), - ("b", c_uint32, 4), - ("c", c_uint32, 4)] - b = bytearray(4) - x = Little.from_buffer(b) - x.a = 0xabcdef - x.b = 1 - x.c = 2 - self.assertEqual(b, b'\xef\xcd\xab\x21') - - @need_symbol('c_uint32') - def test_uint32_swap_big_endian(self): - # Issue #23319 - class Big(BigEndianStructure): - _fields_ = [("a", c_uint32, 24), - ("b", c_uint32, 4), - ("c", c_uint32, 4)] - b = bytearray(4) - x = Big.from_buffer(b) - x.a = 0xabcdef - x.b = 1 - x.c = 2 - self.assertEqual(b, b'\xab\xcd\xef\x12') - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_buffers.py b/Lib/ctypes/test/test_buffers.py deleted file mode 100644 index 15782be757c..00000000000 --- a/Lib/ctypes/test/test_buffers.py +++ /dev/null @@ -1,73 +0,0 @@ -from ctypes import * -from ctypes.test import need_symbol -import unittest - -class StringBufferTestCase(unittest.TestCase): - - def test_buffer(self): - b = create_string_buffer(32) - self.assertEqual(len(b), 32) - self.assertEqual(sizeof(b), 32 * sizeof(c_char)) - self.assertIs(type(b[0]), bytes) - - b = create_string_buffer(b"abc") - self.assertEqual(len(b), 4) # trailing nul char - self.assertEqual(sizeof(b), 4 * sizeof(c_char)) - self.assertIs(type(b[0]), bytes) - self.assertEqual(b[0], b"a") - self.assertEqual(b[:], b"abc\0") - self.assertEqual(b[::], b"abc\0") - self.assertEqual(b[::-1], b"\0cba") - self.assertEqual(b[::2], b"ac") - self.assertEqual(b[::5], b"a") - - self.assertRaises(TypeError, create_string_buffer, "abc") - - def test_buffer_interface(self): - self.assertEqual(len(bytearray(create_string_buffer(0))), 0) - self.assertEqual(len(bytearray(create_string_buffer(1))), 1) - - @need_symbol('c_wchar') - def test_unicode_buffer(self): - b = create_unicode_buffer(32) - self.assertEqual(len(b), 32) - self.assertEqual(sizeof(b), 32 * sizeof(c_wchar)) - self.assertIs(type(b[0]), str) - - b = create_unicode_buffer("abc") - self.assertEqual(len(b), 4) # trailing nul char - self.assertEqual(sizeof(b), 4 * sizeof(c_wchar)) - self.assertIs(type(b[0]), str) - self.assertEqual(b[0], "a") - self.assertEqual(b[:], "abc\0") - self.assertEqual(b[::], "abc\0") - self.assertEqual(b[::-1], "\0cba") - self.assertEqual(b[::2], "ac") - self.assertEqual(b[::5], "a") - - self.assertRaises(TypeError, create_unicode_buffer, b"abc") - - @need_symbol('c_wchar') - def test_unicode_conversion(self): - b = create_unicode_buffer("abc") - self.assertEqual(len(b), 4) # trailing nul char - self.assertEqual(sizeof(b), 4 * sizeof(c_wchar)) - self.assertIs(type(b[0]), str) - self.assertEqual(b[0], "a") - self.assertEqual(b[:], "abc\0") - self.assertEqual(b[::], "abc\0") - self.assertEqual(b[::-1], "\0cba") - self.assertEqual(b[::2], "ac") - self.assertEqual(b[::5], "a") - - @need_symbol('c_wchar') - def test_create_unicode_buffer_non_bmp(self): - expected = 5 if sizeof(c_wchar) == 2 else 3 - for s in '\U00010000\U00100000', '\U00010000\U0010ffff': - b = create_unicode_buffer(s) - self.assertEqual(len(b), expected) - self.assertEqual(b[-1], '\0') - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_bytes.py b/Lib/ctypes/test/test_bytes.py deleted file mode 100644 index 092ec5af052..00000000000 --- a/Lib/ctypes/test/test_bytes.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Test where byte objects are accepted""" -import unittest -import sys -from ctypes import * - -class BytesTest(unittest.TestCase): - def test_c_char(self): - x = c_char(b"x") - self.assertRaises(TypeError, c_char, "x") - x.value = b"y" - with self.assertRaises(TypeError): - x.value = "y" - c_char.from_param(b"x") - self.assertRaises(TypeError, c_char.from_param, "x") - self.assertIn('xbd', repr(c_char.from_param(b"\xbd"))) - (c_char * 3)(b"a", b"b", b"c") - self.assertRaises(TypeError, c_char * 3, "a", "b", "c") - - def test_c_wchar(self): - x = c_wchar("x") - self.assertRaises(TypeError, c_wchar, b"x") - x.value = "y" - with self.assertRaises(TypeError): - x.value = b"y" - c_wchar.from_param("x") - self.assertRaises(TypeError, c_wchar.from_param, b"x") - (c_wchar * 3)("a", "b", "c") - self.assertRaises(TypeError, c_wchar * 3, b"a", b"b", b"c") - - def test_c_char_p(self): - c_char_p(b"foo bar") - self.assertRaises(TypeError, c_char_p, "foo bar") - - def test_c_wchar_p(self): - c_wchar_p("foo bar") - self.assertRaises(TypeError, c_wchar_p, b"foo bar") - - def test_struct(self): - class X(Structure): - _fields_ = [("a", c_char * 3)] - - x = X(b"abc") - self.assertRaises(TypeError, X, "abc") - self.assertEqual(x.a, b"abc") - self.assertEqual(type(x.a), bytes) - - def test_struct_W(self): - class X(Structure): - _fields_ = [("a", c_wchar * 3)] - - x = X("abc") - self.assertRaises(TypeError, X, b"abc") - self.assertEqual(x.a, "abc") - self.assertEqual(type(x.a), str) - - @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') - def test_BSTR(self): - from _ctypes import _SimpleCData - class BSTR(_SimpleCData): - _type_ = "X" - - BSTR("abc") - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_byteswap.py b/Lib/ctypes/test/test_byteswap.py deleted file mode 100644 index 7e98559dfbc..00000000000 --- a/Lib/ctypes/test/test_byteswap.py +++ /dev/null @@ -1,364 +0,0 @@ -import sys, unittest, struct, math, ctypes -from binascii import hexlify - -from ctypes import * - -def bin(s): - return hexlify(memoryview(s)).decode().upper() - -# Each *simple* type that supports different byte orders has an -# __ctype_be__ attribute that specifies the same type in BIG ENDIAN -# byte order, and a __ctype_le__ attribute that is the same type in -# LITTLE ENDIAN byte order. -# -# For Structures and Unions, these types are created on demand. - -class Test(unittest.TestCase): - @unittest.skip('test disabled') - def test_X(self): - print(sys.byteorder, file=sys.stderr) - for i in range(32): - bits = BITS() - setattr(bits, "i%s" % i, 1) - dump(bits) - - def test_slots(self): - class BigPoint(BigEndianStructure): - __slots__ = () - _fields_ = [("x", c_int), ("y", c_int)] - - class LowPoint(LittleEndianStructure): - __slots__ = () - _fields_ = [("x", c_int), ("y", c_int)] - - big = BigPoint() - little = LowPoint() - big.x = 4 - big.y = 2 - little.x = 2 - little.y = 4 - with self.assertRaises(AttributeError): - big.z = 42 - with self.assertRaises(AttributeError): - little.z = 24 - - def test_endian_short(self): - if sys.byteorder == "little": - self.assertIs(c_short.__ctype_le__, c_short) - self.assertIs(c_short.__ctype_be__.__ctype_le__, c_short) - else: - self.assertIs(c_short.__ctype_be__, c_short) - self.assertIs(c_short.__ctype_le__.__ctype_be__, c_short) - s = c_short.__ctype_be__(0x1234) - self.assertEqual(bin(struct.pack(">h", 0x1234)), "1234") - self.assertEqual(bin(s), "1234") - self.assertEqual(s.value, 0x1234) - - s = c_short.__ctype_le__(0x1234) - self.assertEqual(bin(struct.pack("<h", 0x1234)), "3412") - self.assertEqual(bin(s), "3412") - self.assertEqual(s.value, 0x1234) - - s = c_ushort.__ctype_be__(0x1234) - self.assertEqual(bin(struct.pack(">h", 0x1234)), "1234") - self.assertEqual(bin(s), "1234") - self.assertEqual(s.value, 0x1234) - - s = c_ushort.__ctype_le__(0x1234) - self.assertEqual(bin(struct.pack("<h", 0x1234)), "3412") - self.assertEqual(bin(s), "3412") - self.assertEqual(s.value, 0x1234) - - def test_endian_int(self): - if sys.byteorder == "little": - self.assertIs(c_int.__ctype_le__, c_int) - self.assertIs(c_int.__ctype_be__.__ctype_le__, c_int) - else: - self.assertIs(c_int.__ctype_be__, c_int) - self.assertIs(c_int.__ctype_le__.__ctype_be__, c_int) - - s = c_int.__ctype_be__(0x12345678) - self.assertEqual(bin(struct.pack(">i", 0x12345678)), "12345678") - self.assertEqual(bin(s), "12345678") - self.assertEqual(s.value, 0x12345678) - - s = c_int.__ctype_le__(0x12345678) - self.assertEqual(bin(struct.pack("<i", 0x12345678)), "78563412") - self.assertEqual(bin(s), "78563412") - self.assertEqual(s.value, 0x12345678) - - s = c_uint.__ctype_be__(0x12345678) - self.assertEqual(bin(struct.pack(">I", 0x12345678)), "12345678") - self.assertEqual(bin(s), "12345678") - self.assertEqual(s.value, 0x12345678) - - s = c_uint.__ctype_le__(0x12345678) - self.assertEqual(bin(struct.pack("<I", 0x12345678)), "78563412") - self.assertEqual(bin(s), "78563412") - self.assertEqual(s.value, 0x12345678) - - def test_endian_longlong(self): - if sys.byteorder == "little": - self.assertIs(c_longlong.__ctype_le__, c_longlong) - self.assertIs(c_longlong.__ctype_be__.__ctype_le__, c_longlong) - else: - self.assertIs(c_longlong.__ctype_be__, c_longlong) - self.assertIs(c_longlong.__ctype_le__.__ctype_be__, c_longlong) - - s = c_longlong.__ctype_be__(0x1234567890ABCDEF) - self.assertEqual(bin(struct.pack(">q", 0x1234567890ABCDEF)), "1234567890ABCDEF") - self.assertEqual(bin(s), "1234567890ABCDEF") - self.assertEqual(s.value, 0x1234567890ABCDEF) - - s = c_longlong.__ctype_le__(0x1234567890ABCDEF) - self.assertEqual(bin(struct.pack("<q", 0x1234567890ABCDEF)), "EFCDAB9078563412") - self.assertEqual(bin(s), "EFCDAB9078563412") - self.assertEqual(s.value, 0x1234567890ABCDEF) - - s = c_ulonglong.__ctype_be__(0x1234567890ABCDEF) - self.assertEqual(bin(struct.pack(">Q", 0x1234567890ABCDEF)), "1234567890ABCDEF") - self.assertEqual(bin(s), "1234567890ABCDEF") - self.assertEqual(s.value, 0x1234567890ABCDEF) - - s = c_ulonglong.__ctype_le__(0x1234567890ABCDEF) - self.assertEqual(bin(struct.pack("<Q", 0x1234567890ABCDEF)), "EFCDAB9078563412") - self.assertEqual(bin(s), "EFCDAB9078563412") - self.assertEqual(s.value, 0x1234567890ABCDEF) - - def test_endian_float(self): - if sys.byteorder == "little": - self.assertIs(c_float.__ctype_le__, c_float) - self.assertIs(c_float.__ctype_be__.__ctype_le__, c_float) - else: - self.assertIs(c_float.__ctype_be__, c_float) - self.assertIs(c_float.__ctype_le__.__ctype_be__, c_float) - s = c_float(math.pi) - self.assertEqual(bin(struct.pack("f", math.pi)), bin(s)) - # Hm, what's the precision of a float compared to a double? - self.assertAlmostEqual(s.value, math.pi, places=6) - s = c_float.__ctype_le__(math.pi) - self.assertAlmostEqual(s.value, math.pi, places=6) - self.assertEqual(bin(struct.pack("<f", math.pi)), bin(s)) - s = c_float.__ctype_be__(math.pi) - self.assertAlmostEqual(s.value, math.pi, places=6) - self.assertEqual(bin(struct.pack(">f", math.pi)), bin(s)) - - def test_endian_double(self): - if sys.byteorder == "little": - self.assertIs(c_double.__ctype_le__, c_double) - self.assertIs(c_double.__ctype_be__.__ctype_le__, c_double) - else: - self.assertIs(c_double.__ctype_be__, c_double) - self.assertIs(c_double.__ctype_le__.__ctype_be__, c_double) - s = c_double(math.pi) - self.assertEqual(s.value, math.pi) - self.assertEqual(bin(struct.pack("d", math.pi)), bin(s)) - s = c_double.__ctype_le__(math.pi) - self.assertEqual(s.value, math.pi) - self.assertEqual(bin(struct.pack("<d", math.pi)), bin(s)) - s = c_double.__ctype_be__(math.pi) - self.assertEqual(s.value, math.pi) - self.assertEqual(bin(struct.pack(">d", math.pi)), bin(s)) - - def test_endian_other(self): - self.assertIs(c_byte.__ctype_le__, c_byte) - self.assertIs(c_byte.__ctype_be__, c_byte) - - self.assertIs(c_ubyte.__ctype_le__, c_ubyte) - self.assertIs(c_ubyte.__ctype_be__, c_ubyte) - - self.assertIs(c_char.__ctype_le__, c_char) - self.assertIs(c_char.__ctype_be__, c_char) - - def test_struct_fields_unsupported_byte_order(self): - - fields = [ - ("a", c_ubyte), - ("b", c_byte), - ("c", c_short), - ("d", c_ushort), - ("e", c_int), - ("f", c_uint), - ("g", c_long), - ("h", c_ulong), - ("i", c_longlong), - ("k", c_ulonglong), - ("l", c_float), - ("m", c_double), - ("n", c_char), - ("b1", c_byte, 3), - ("b2", c_byte, 3), - ("b3", c_byte, 2), - ("a", c_int * 3 * 3 * 3) - ] - - # these fields do not support different byte order: - for typ in c_wchar, c_void_p, POINTER(c_int): - with self.assertRaises(TypeError): - class T(BigEndianStructure if sys.byteorder == "little" else LittleEndianStructure): - _fields_ = fields + [("x", typ)] - - - def test_struct_struct(self): - # nested structures with different byteorders - - # create nested structures with given byteorders and set memory to data - - for nested, data in ( - (BigEndianStructure, b'\0\0\0\1\0\0\0\2'), - (LittleEndianStructure, b'\1\0\0\0\2\0\0\0'), - ): - for parent in ( - BigEndianStructure, - LittleEndianStructure, - Structure, - ): - class NestedStructure(nested): - _fields_ = [("x", c_uint32), - ("y", c_uint32)] - - class TestStructure(parent): - _fields_ = [("point", NestedStructure)] - - self.assertEqual(len(data), sizeof(TestStructure)) - ptr = POINTER(TestStructure) - s = cast(data, ptr)[0] - del ctypes._pointer_type_cache[TestStructure] - self.assertEqual(s.point.x, 1) - self.assertEqual(s.point.y, 2) - - def test_struct_field_alignment(self): - # standard packing in struct uses no alignment. - # So, we have to align using pad bytes. - # - # Unaligned accesses will crash Python (on those platforms that - # don't allow it, like sparc solaris). - if sys.byteorder == "little": - base = BigEndianStructure - fmt = ">bxhid" - else: - base = LittleEndianStructure - fmt = "<bxhid" - - class S(base): - _fields_ = [("b", c_byte), - ("h", c_short), - ("i", c_int), - ("d", c_double)] - - s1 = S(0x12, 0x1234, 0x12345678, 3.14) - s2 = struct.pack(fmt, 0x12, 0x1234, 0x12345678, 3.14) - self.assertEqual(bin(s1), bin(s2)) - - def test_unaligned_nonnative_struct_fields(self): - if sys.byteorder == "little": - base = BigEndianStructure - fmt = ">b h xi xd" - else: - base = LittleEndianStructure - fmt = "<b h xi xd" - - class S(base): - _pack_ = 1 - _fields_ = [("b", c_byte), - ("h", c_short), - - ("_1", c_byte), - ("i", c_int), - - ("_2", c_byte), - ("d", c_double)] - - s1 = S() - s1.b = 0x12 - s1.h = 0x1234 - s1.i = 0x12345678 - s1.d = 3.14 - s2 = struct.pack(fmt, 0x12, 0x1234, 0x12345678, 3.14) - self.assertEqual(bin(s1), bin(s2)) - - def test_unaligned_native_struct_fields(self): - if sys.byteorder == "little": - fmt = "<b h xi xd" - else: - base = LittleEndianStructure - fmt = ">b h xi xd" - - class S(Structure): - _pack_ = 1 - _fields_ = [("b", c_byte), - - ("h", c_short), - - ("_1", c_byte), - ("i", c_int), - - ("_2", c_byte), - ("d", c_double)] - - s1 = S() - s1.b = 0x12 - s1.h = 0x1234 - s1.i = 0x12345678 - s1.d = 3.14 - s2 = struct.pack(fmt, 0x12, 0x1234, 0x12345678, 3.14) - self.assertEqual(bin(s1), bin(s2)) - - def test_union_fields_unsupported_byte_order(self): - - fields = [ - ("a", c_ubyte), - ("b", c_byte), - ("c", c_short), - ("d", c_ushort), - ("e", c_int), - ("f", c_uint), - ("g", c_long), - ("h", c_ulong), - ("i", c_longlong), - ("k", c_ulonglong), - ("l", c_float), - ("m", c_double), - ("n", c_char), - ("b1", c_byte, 3), - ("b2", c_byte, 3), - ("b3", c_byte, 2), - ("a", c_int * 3 * 3 * 3) - ] - - # these fields do not support different byte order: - for typ in c_wchar, c_void_p, POINTER(c_int): - with self.assertRaises(TypeError): - class T(BigEndianUnion if sys.byteorder == "little" else LittleEndianUnion): - _fields_ = fields + [("x", typ)] - - def test_union_struct(self): - # nested structures in unions with different byteorders - - # create nested structures in unions with given byteorders and set memory to data - - for nested, data in ( - (BigEndianStructure, b'\0\0\0\1\0\0\0\2'), - (LittleEndianStructure, b'\1\0\0\0\2\0\0\0'), - ): - for parent in ( - BigEndianUnion, - LittleEndianUnion, - Union, - ): - class NestedStructure(nested): - _fields_ = [("x", c_uint32), - ("y", c_uint32)] - - class TestUnion(parent): - _fields_ = [("point", NestedStructure)] - - self.assertEqual(len(data), sizeof(TestUnion)) - ptr = POINTER(TestUnion) - s = cast(data, ptr)[0] - del ctypes._pointer_type_cache[TestUnion] - self.assertEqual(s.point.x, 1) - self.assertEqual(s.point.y, 2) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_callbacks.py b/Lib/ctypes/test/test_callbacks.py deleted file mode 100644 index 8f95a244439..00000000000 --- a/Lib/ctypes/test/test_callbacks.py +++ /dev/null @@ -1,333 +0,0 @@ -import functools -import unittest -from test import support - -from ctypes import * -from ctypes.test import need_symbol -from _ctypes import CTYPES_MAX_ARGCOUNT -import _ctypes_test - -class Callbacks(unittest.TestCase): - functype = CFUNCTYPE - -## def tearDown(self): -## import gc -## gc.collect() - - def callback(self, *args): - self.got_args = args - return args[-1] - - def check_type(self, typ, arg): - PROTO = self.functype.__func__(typ, typ) - result = PROTO(self.callback)(arg) - if typ == c_float: - self.assertAlmostEqual(result, arg, places=5) - else: - self.assertEqual(self.got_args, (arg,)) - self.assertEqual(result, arg) - - PROTO = self.functype.__func__(typ, c_byte, typ) - result = PROTO(self.callback)(-3, arg) - if typ == c_float: - self.assertAlmostEqual(result, arg, places=5) - else: - self.assertEqual(self.got_args, (-3, arg)) - self.assertEqual(result, arg) - - ################ - - def test_byte(self): - self.check_type(c_byte, 42) - self.check_type(c_byte, -42) - - def test_ubyte(self): - self.check_type(c_ubyte, 42) - - def test_short(self): - self.check_type(c_short, 42) - self.check_type(c_short, -42) - - def test_ushort(self): - self.check_type(c_ushort, 42) - - def test_int(self): - self.check_type(c_int, 42) - self.check_type(c_int, -42) - - def test_uint(self): - self.check_type(c_uint, 42) - - def test_long(self): - self.check_type(c_long, 42) - self.check_type(c_long, -42) - - def test_ulong(self): - self.check_type(c_ulong, 42) - - @need_symbol('c_longlong') - def test_longlong(self): - self.check_type(c_longlong, 42) - self.check_type(c_longlong, -42) - - @need_symbol('c_ulonglong') - def test_ulonglong(self): - self.check_type(c_ulonglong, 42) - - def test_float(self): - # only almost equal: double -> float -> double - import math - self.check_type(c_float, math.e) - self.check_type(c_float, -math.e) - - def test_double(self): - self.check_type(c_double, 3.14) - self.check_type(c_double, -3.14) - - @need_symbol('c_longdouble') - def test_longdouble(self): - self.check_type(c_longdouble, 3.14) - self.check_type(c_longdouble, -3.14) - - def test_char(self): - self.check_type(c_char, b"x") - self.check_type(c_char, b"a") - - # disabled: would now (correctly) raise a RuntimeWarning about - # a memory leak. A callback function cannot return a non-integral - # C type without causing a memory leak. - @unittest.skip('test disabled') - def test_char_p(self): - self.check_type(c_char_p, "abc") - self.check_type(c_char_p, "def") - - def test_pyobject(self): - o = () - from sys import getrefcount as grc - for o in (), [], object(): - initial = grc(o) - # This call leaks a reference to 'o'... - self.check_type(py_object, o) - before = grc(o) - # ...but this call doesn't leak any more. Where is the refcount? - self.check_type(py_object, o) - after = grc(o) - self.assertEqual((after, o), (before, o)) - - def test_unsupported_restype_1(self): - # Only "fundamental" result types are supported for callback - # functions, the type must have a non-NULL stgdict->setfunc. - # POINTER(c_double), for example, is not supported. - - prototype = self.functype.__func__(POINTER(c_double)) - # The type is checked when the prototype is called - self.assertRaises(TypeError, prototype, lambda: None) - - def test_unsupported_restype_2(self): - prototype = self.functype.__func__(object) - self.assertRaises(TypeError, prototype, lambda: None) - - def test_issue_7959(self): - proto = self.functype.__func__(None) - - class X(object): - def func(self): pass - def __init__(self): - self.v = proto(self.func) - - import gc - for i in range(32): - X() - gc.collect() - live = [x for x in gc.get_objects() - if isinstance(x, X)] - self.assertEqual(len(live), 0) - - def test_issue12483(self): - import gc - class Nasty: - def __del__(self): - gc.collect() - CFUNCTYPE(None)(lambda x=Nasty(): None) - - -@need_symbol('WINFUNCTYPE') -class StdcallCallbacks(Callbacks): - try: - functype = WINFUNCTYPE - except NameError: - pass - -################################################################ - -class SampleCallbacksTestCase(unittest.TestCase): - - def test_integrate(self): - # Derived from some then non-working code, posted by David Foster - dll = CDLL(_ctypes_test.__file__) - - # The function prototype called by 'integrate': double func(double); - CALLBACK = CFUNCTYPE(c_double, c_double) - - # The integrate function itself, exposed from the _ctypes_test dll - integrate = dll.integrate - integrate.argtypes = (c_double, c_double, CALLBACK, c_long) - integrate.restype = c_double - - def func(x): - return x**2 - - result = integrate(0.0, 1.0, CALLBACK(func), 10) - diff = abs(result - 1./3.) - - self.assertLess(diff, 0.01, "%s not less than 0.01" % diff) - - def test_issue_8959_a(self): - from ctypes.util import find_library - libc_path = find_library("c") - if not libc_path: - self.skipTest('could not find libc') - libc = CDLL(libc_path) - - @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int)) - def cmp_func(a, b): - return a[0] - b[0] - - array = (c_int * 5)(5, 1, 99, 7, 33) - - libc.qsort(array, len(array), sizeof(c_int), cmp_func) - self.assertEqual(array[:], [1, 5, 7, 33, 99]) - - @need_symbol('WINFUNCTYPE') - def test_issue_8959_b(self): - from ctypes.wintypes import BOOL, HWND, LPARAM - global windowCount - windowCount = 0 - - @WINFUNCTYPE(BOOL, HWND, LPARAM) - def EnumWindowsCallbackFunc(hwnd, lParam): - global windowCount - windowCount += 1 - return True #Allow windows to keep enumerating - - windll.user32.EnumWindows(EnumWindowsCallbackFunc, 0) - - def test_callback_register_int(self): - # Issue #8275: buggy handling of callback args under Win64 - # NOTE: should be run on release builds as well - dll = CDLL(_ctypes_test.__file__) - CALLBACK = CFUNCTYPE(c_int, c_int, c_int, c_int, c_int, c_int) - # All this function does is call the callback with its args squared - func = dll._testfunc_cbk_reg_int - func.argtypes = (c_int, c_int, c_int, c_int, c_int, CALLBACK) - func.restype = c_int - - def callback(a, b, c, d, e): - return a + b + c + d + e - - result = func(2, 3, 4, 5, 6, CALLBACK(callback)) - self.assertEqual(result, callback(2*2, 3*3, 4*4, 5*5, 6*6)) - - def test_callback_register_double(self): - # Issue #8275: buggy handling of callback args under Win64 - # NOTE: should be run on release builds as well - dll = CDLL(_ctypes_test.__file__) - CALLBACK = CFUNCTYPE(c_double, c_double, c_double, c_double, - c_double, c_double) - # All this function does is call the callback with its args squared - func = dll._testfunc_cbk_reg_double - func.argtypes = (c_double, c_double, c_double, - c_double, c_double, CALLBACK) - func.restype = c_double - - def callback(a, b, c, d, e): - return a + b + c + d + e - - result = func(1.1, 2.2, 3.3, 4.4, 5.5, CALLBACK(callback)) - self.assertEqual(result, - callback(1.1*1.1, 2.2*2.2, 3.3*3.3, 4.4*4.4, 5.5*5.5)) - - def test_callback_large_struct(self): - class Check: pass - - # This should mirror the structure in Modules/_ctypes/_ctypes_test.c - class X(Structure): - _fields_ = [ - ('first', c_ulong), - ('second', c_ulong), - ('third', c_ulong), - ] - - def callback(check, s): - check.first = s.first - check.second = s.second - check.third = s.third - # See issue #29565. - # The structure should be passed by value, so - # any changes to it should not be reflected in - # the value passed - s.first = s.second = s.third = 0x0badf00d - - check = Check() - s = X() - s.first = 0xdeadbeef - s.second = 0xcafebabe - s.third = 0x0bad1dea - - CALLBACK = CFUNCTYPE(None, X) - dll = CDLL(_ctypes_test.__file__) - func = dll._testfunc_cbk_large_struct - func.argtypes = (X, CALLBACK) - func.restype = None - # the function just calls the callback with the passed structure - func(s, CALLBACK(functools.partial(callback, check))) - self.assertEqual(check.first, s.first) - self.assertEqual(check.second, s.second) - self.assertEqual(check.third, s.third) - self.assertEqual(check.first, 0xdeadbeef) - self.assertEqual(check.second, 0xcafebabe) - self.assertEqual(check.third, 0x0bad1dea) - # See issue #29565. - # Ensure that the original struct is unchanged. - self.assertEqual(s.first, check.first) - self.assertEqual(s.second, check.second) - self.assertEqual(s.third, check.third) - - def test_callback_too_many_args(self): - def func(*args): - return len(args) - - # valid call with nargs <= CTYPES_MAX_ARGCOUNT - proto = CFUNCTYPE(c_int, *(c_int,) * CTYPES_MAX_ARGCOUNT) - cb = proto(func) - args1 = (1,) * CTYPES_MAX_ARGCOUNT - self.assertEqual(cb(*args1), CTYPES_MAX_ARGCOUNT) - - # invalid call with nargs > CTYPES_MAX_ARGCOUNT - args2 = (1,) * (CTYPES_MAX_ARGCOUNT + 1) - with self.assertRaises(ArgumentError): - cb(*args2) - - # error when creating the type with too many arguments - with self.assertRaises(ArgumentError): - CFUNCTYPE(c_int, *(c_int,) * (CTYPES_MAX_ARGCOUNT + 1)) - - def test_convert_result_error(self): - def func(): - return ("tuple",) - - proto = CFUNCTYPE(c_int) - ctypes_func = proto(func) - with support.catch_unraisable_exception() as cm: - # don't test the result since it is an uninitialized value - result = ctypes_func() - - self.assertIsInstance(cm.unraisable.exc_value, TypeError) - self.assertEqual(cm.unraisable.err_msg, - "Exception ignored on converting result " - "of ctypes callback function") - self.assertIs(cm.unraisable.object, func) - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_cast.py b/Lib/ctypes/test/test_cast.py deleted file mode 100644 index 6878f973282..00000000000 --- a/Lib/ctypes/test/test_cast.py +++ /dev/null @@ -1,99 +0,0 @@ -from ctypes import * -from ctypes.test import need_symbol -import unittest -import sys - -class Test(unittest.TestCase): - - def test_array2pointer(self): - array = (c_int * 3)(42, 17, 2) - - # casting an array to a pointer works. - ptr = cast(array, POINTER(c_int)) - self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) - - if 2*sizeof(c_short) == sizeof(c_int): - ptr = cast(array, POINTER(c_short)) - if sys.byteorder == "little": - self.assertEqual([ptr[i] for i in range(6)], - [42, 0, 17, 0, 2, 0]) - else: - self.assertEqual([ptr[i] for i in range(6)], - [0, 42, 0, 17, 0, 2]) - - def test_address2pointer(self): - array = (c_int * 3)(42, 17, 2) - - address = addressof(array) - ptr = cast(c_void_p(address), POINTER(c_int)) - self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) - - ptr = cast(address, POINTER(c_int)) - self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) - - def test_p2a_objects(self): - array = (c_char_p * 5)() - self.assertEqual(array._objects, None) - array[0] = b"foo bar" - self.assertEqual(array._objects, {'0': b"foo bar"}) - - p = cast(array, POINTER(c_char_p)) - # array and p share a common _objects attribute - self.assertIs(p._objects, array._objects) - self.assertEqual(array._objects, {'0': b"foo bar", id(array): array}) - p[0] = b"spam spam" - self.assertEqual(p._objects, {'0': b"spam spam", id(array): array}) - self.assertIs(array._objects, p._objects) - p[1] = b"foo bar" - self.assertEqual(p._objects, {'1': b'foo bar', '0': b"spam spam", id(array): array}) - self.assertIs(array._objects, p._objects) - - def test_other(self): - p = cast((c_int * 4)(1, 2, 3, 4), POINTER(c_int)) - self.assertEqual(p[:4], [1,2, 3, 4]) - self.assertEqual(p[:4:], [1, 2, 3, 4]) - self.assertEqual(p[3:-1:-1], [4, 3, 2, 1]) - self.assertEqual(p[:4:3], [1, 4]) - c_int() - self.assertEqual(p[:4], [1, 2, 3, 4]) - self.assertEqual(p[:4:], [1, 2, 3, 4]) - self.assertEqual(p[3:-1:-1], [4, 3, 2, 1]) - self.assertEqual(p[:4:3], [1, 4]) - p[2] = 96 - self.assertEqual(p[:4], [1, 2, 96, 4]) - self.assertEqual(p[:4:], [1, 2, 96, 4]) - self.assertEqual(p[3:-1:-1], [4, 96, 2, 1]) - self.assertEqual(p[:4:3], [1, 4]) - c_int() - self.assertEqual(p[:4], [1, 2, 96, 4]) - self.assertEqual(p[:4:], [1, 2, 96, 4]) - self.assertEqual(p[3:-1:-1], [4, 96, 2, 1]) - self.assertEqual(p[:4:3], [1, 4]) - - def test_char_p(self): - # This didn't work: bad argument to internal function - s = c_char_p(b"hiho") - self.assertEqual(cast(cast(s, c_void_p), c_char_p).value, - b"hiho") - - @need_symbol('c_wchar_p') - def test_wchar_p(self): - s = c_wchar_p("hiho") - self.assertEqual(cast(cast(s, c_void_p), c_wchar_p).value, - "hiho") - - def test_bad_type_arg(self): - # The type argument must be a ctypes pointer type. - array_type = c_byte * sizeof(c_int) - array = array_type() - self.assertRaises(TypeError, cast, array, None) - self.assertRaises(TypeError, cast, array, array_type) - class Struct(Structure): - _fields_ = [("a", c_int)] - self.assertRaises(TypeError, cast, array, Struct) - class MyUnion(Union): - _fields_ = [("a", c_int)] - self.assertRaises(TypeError, cast, array, MyUnion) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_cfuncs.py b/Lib/ctypes/test/test_cfuncs.py deleted file mode 100644 index 09b06840bf5..00000000000 --- a/Lib/ctypes/test/test_cfuncs.py +++ /dev/null @@ -1,218 +0,0 @@ -# A lot of failures in these tests on Mac OS X. -# Byte order related? - -import unittest -from ctypes import * -from ctypes.test import need_symbol - -import _ctypes_test - -class CFunctions(unittest.TestCase): - _dll = CDLL(_ctypes_test.__file__) - - def S(self): - return c_longlong.in_dll(self._dll, "last_tf_arg_s").value - def U(self): - return c_ulonglong.in_dll(self._dll, "last_tf_arg_u").value - - def test_byte(self): - self._dll.tf_b.restype = c_byte - self._dll.tf_b.argtypes = (c_byte,) - self.assertEqual(self._dll.tf_b(-126), -42) - self.assertEqual(self.S(), -126) - - def test_byte_plus(self): - self._dll.tf_bb.restype = c_byte - self._dll.tf_bb.argtypes = (c_byte, c_byte) - self.assertEqual(self._dll.tf_bb(0, -126), -42) - self.assertEqual(self.S(), -126) - - def test_ubyte(self): - self._dll.tf_B.restype = c_ubyte - self._dll.tf_B.argtypes = (c_ubyte,) - self.assertEqual(self._dll.tf_B(255), 85) - self.assertEqual(self.U(), 255) - - def test_ubyte_plus(self): - self._dll.tf_bB.restype = c_ubyte - self._dll.tf_bB.argtypes = (c_byte, c_ubyte) - self.assertEqual(self._dll.tf_bB(0, 255), 85) - self.assertEqual(self.U(), 255) - - def test_short(self): - self._dll.tf_h.restype = c_short - self._dll.tf_h.argtypes = (c_short,) - self.assertEqual(self._dll.tf_h(-32766), -10922) - self.assertEqual(self.S(), -32766) - - def test_short_plus(self): - self._dll.tf_bh.restype = c_short - self._dll.tf_bh.argtypes = (c_byte, c_short) - self.assertEqual(self._dll.tf_bh(0, -32766), -10922) - self.assertEqual(self.S(), -32766) - - def test_ushort(self): - self._dll.tf_H.restype = c_ushort - self._dll.tf_H.argtypes = (c_ushort,) - self.assertEqual(self._dll.tf_H(65535), 21845) - self.assertEqual(self.U(), 65535) - - def test_ushort_plus(self): - self._dll.tf_bH.restype = c_ushort - self._dll.tf_bH.argtypes = (c_byte, c_ushort) - self.assertEqual(self._dll.tf_bH(0, 65535), 21845) - self.assertEqual(self.U(), 65535) - - def test_int(self): - self._dll.tf_i.restype = c_int - self._dll.tf_i.argtypes = (c_int,) - self.assertEqual(self._dll.tf_i(-2147483646), -715827882) - self.assertEqual(self.S(), -2147483646) - - def test_int_plus(self): - self._dll.tf_bi.restype = c_int - self._dll.tf_bi.argtypes = (c_byte, c_int) - self.assertEqual(self._dll.tf_bi(0, -2147483646), -715827882) - self.assertEqual(self.S(), -2147483646) - - def test_uint(self): - self._dll.tf_I.restype = c_uint - self._dll.tf_I.argtypes = (c_uint,) - self.assertEqual(self._dll.tf_I(4294967295), 1431655765) - self.assertEqual(self.U(), 4294967295) - - def test_uint_plus(self): - self._dll.tf_bI.restype = c_uint - self._dll.tf_bI.argtypes = (c_byte, c_uint) - self.assertEqual(self._dll.tf_bI(0, 4294967295), 1431655765) - self.assertEqual(self.U(), 4294967295) - - def test_long(self): - self._dll.tf_l.restype = c_long - self._dll.tf_l.argtypes = (c_long,) - self.assertEqual(self._dll.tf_l(-2147483646), -715827882) - self.assertEqual(self.S(), -2147483646) - - def test_long_plus(self): - self._dll.tf_bl.restype = c_long - self._dll.tf_bl.argtypes = (c_byte, c_long) - self.assertEqual(self._dll.tf_bl(0, -2147483646), -715827882) - self.assertEqual(self.S(), -2147483646) - - def test_ulong(self): - self._dll.tf_L.restype = c_ulong - self._dll.tf_L.argtypes = (c_ulong,) - self.assertEqual(self._dll.tf_L(4294967295), 1431655765) - self.assertEqual(self.U(), 4294967295) - - def test_ulong_plus(self): - self._dll.tf_bL.restype = c_ulong - self._dll.tf_bL.argtypes = (c_char, c_ulong) - self.assertEqual(self._dll.tf_bL(b' ', 4294967295), 1431655765) - self.assertEqual(self.U(), 4294967295) - - @need_symbol('c_longlong') - def test_longlong(self): - self._dll.tf_q.restype = c_longlong - self._dll.tf_q.argtypes = (c_longlong, ) - self.assertEqual(self._dll.tf_q(-9223372036854775806), -3074457345618258602) - self.assertEqual(self.S(), -9223372036854775806) - - @need_symbol('c_longlong') - def test_longlong_plus(self): - self._dll.tf_bq.restype = c_longlong - self._dll.tf_bq.argtypes = (c_byte, c_longlong) - self.assertEqual(self._dll.tf_bq(0, -9223372036854775806), -3074457345618258602) - self.assertEqual(self.S(), -9223372036854775806) - - @need_symbol('c_ulonglong') - def test_ulonglong(self): - self._dll.tf_Q.restype = c_ulonglong - self._dll.tf_Q.argtypes = (c_ulonglong, ) - self.assertEqual(self._dll.tf_Q(18446744073709551615), 6148914691236517205) - self.assertEqual(self.U(), 18446744073709551615) - - @need_symbol('c_ulonglong') - def test_ulonglong_plus(self): - self._dll.tf_bQ.restype = c_ulonglong - self._dll.tf_bQ.argtypes = (c_byte, c_ulonglong) - self.assertEqual(self._dll.tf_bQ(0, 18446744073709551615), 6148914691236517205) - self.assertEqual(self.U(), 18446744073709551615) - - def test_float(self): - self._dll.tf_f.restype = c_float - self._dll.tf_f.argtypes = (c_float,) - self.assertEqual(self._dll.tf_f(-42.), -14.) - self.assertEqual(self.S(), -42) - - def test_float_plus(self): - self._dll.tf_bf.restype = c_float - self._dll.tf_bf.argtypes = (c_byte, c_float) - self.assertEqual(self._dll.tf_bf(0, -42.), -14.) - self.assertEqual(self.S(), -42) - - def test_double(self): - self._dll.tf_d.restype = c_double - self._dll.tf_d.argtypes = (c_double,) - self.assertEqual(self._dll.tf_d(42.), 14.) - self.assertEqual(self.S(), 42) - - def test_double_plus(self): - self._dll.tf_bd.restype = c_double - self._dll.tf_bd.argtypes = (c_byte, c_double) - self.assertEqual(self._dll.tf_bd(0, 42.), 14.) - self.assertEqual(self.S(), 42) - - @need_symbol('c_longdouble') - def test_longdouble(self): - self._dll.tf_D.restype = c_longdouble - self._dll.tf_D.argtypes = (c_longdouble,) - self.assertEqual(self._dll.tf_D(42.), 14.) - self.assertEqual(self.S(), 42) - - @need_symbol('c_longdouble') - def test_longdouble_plus(self): - self._dll.tf_bD.restype = c_longdouble - self._dll.tf_bD.argtypes = (c_byte, c_longdouble) - self.assertEqual(self._dll.tf_bD(0, 42.), 14.) - self.assertEqual(self.S(), 42) - - def test_callwithresult(self): - def process_result(result): - return result * 2 - self._dll.tf_i.restype = process_result - self._dll.tf_i.argtypes = (c_int,) - self.assertEqual(self._dll.tf_i(42), 28) - self.assertEqual(self.S(), 42) - self.assertEqual(self._dll.tf_i(-42), -28) - self.assertEqual(self.S(), -42) - - def test_void(self): - self._dll.tv_i.restype = None - self._dll.tv_i.argtypes = (c_int,) - self.assertEqual(self._dll.tv_i(42), None) - self.assertEqual(self.S(), 42) - self.assertEqual(self._dll.tv_i(-42), None) - self.assertEqual(self.S(), -42) - -# The following repeats the above tests with stdcall functions (where -# they are available) -try: - WinDLL -except NameError: - def stdcall_dll(*_): pass -else: - class stdcall_dll(WinDLL): - def __getattr__(self, name): - if name[:2] == '__' and name[-2:] == '__': - raise AttributeError(name) - func = self._FuncPtr(("s_" + name, self)) - setattr(self, name, func) - return func - -@need_symbol('WinDLL') -class stdcallCFunctions(CFunctions): - _dll = stdcall_dll(_ctypes_test.__file__) - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_checkretval.py b/Lib/ctypes/test/test_checkretval.py deleted file mode 100644 index e9567dc3912..00000000000 --- a/Lib/ctypes/test/test_checkretval.py +++ /dev/null @@ -1,36 +0,0 @@ -import unittest - -from ctypes import * -from ctypes.test import need_symbol - -class CHECKED(c_int): - def _check_retval_(value): - # Receives a CHECKED instance. - return str(value.value) - _check_retval_ = staticmethod(_check_retval_) - -class Test(unittest.TestCase): - - def test_checkretval(self): - - import _ctypes_test - dll = CDLL(_ctypes_test.__file__) - self.assertEqual(42, dll._testfunc_p_p(42)) - - dll._testfunc_p_p.restype = CHECKED - self.assertEqual("42", dll._testfunc_p_p(42)) - - dll._testfunc_p_p.restype = None - self.assertEqual(None, dll._testfunc_p_p(42)) - - del dll._testfunc_p_p.restype - self.assertEqual(42, dll._testfunc_p_p(42)) - - @need_symbol('oledll') - def test_oledll(self): - self.assertRaises(OSError, - oledll.oleaut32.CreateTypeLib2, - 0, None, None) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_delattr.py b/Lib/ctypes/test/test_delattr.py deleted file mode 100644 index 0f4d58691b5..00000000000 --- a/Lib/ctypes/test/test_delattr.py +++ /dev/null @@ -1,21 +0,0 @@ -import unittest -from ctypes import * - -class X(Structure): - _fields_ = [("foo", c_int)] - -class TestCase(unittest.TestCase): - def test_simple(self): - self.assertRaises(TypeError, - delattr, c_int(42), "value") - - def test_chararray(self): - self.assertRaises(TypeError, - delattr, (c_char * 5)(), "value") - - def test_struct(self): - self.assertRaises(TypeError, - delattr, X(), "foo") - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_errno.py b/Lib/ctypes/test/test_errno.py deleted file mode 100644 index 3685164dde6..00000000000 --- a/Lib/ctypes/test/test_errno.py +++ /dev/null @@ -1,76 +0,0 @@ -import unittest, os, errno -import threading - -from ctypes import * -from ctypes.util import find_library - -class Test(unittest.TestCase): - def test_open(self): - libc_name = find_library("c") - if libc_name is None: - raise unittest.SkipTest("Unable to find C library") - libc = CDLL(libc_name, use_errno=True) - if os.name == "nt": - libc_open = libc._open - else: - libc_open = libc.open - - libc_open.argtypes = c_char_p, c_int - - self.assertEqual(libc_open(b"", 0), -1) - self.assertEqual(get_errno(), errno.ENOENT) - - self.assertEqual(set_errno(32), errno.ENOENT) - self.assertEqual(get_errno(), 32) - - def _worker(): - set_errno(0) - - libc = CDLL(libc_name, use_errno=False) - if os.name == "nt": - libc_open = libc._open - else: - libc_open = libc.open - libc_open.argtypes = c_char_p, c_int - self.assertEqual(libc_open(b"", 0), -1) - self.assertEqual(get_errno(), 0) - - t = threading.Thread(target=_worker) - t.start() - t.join() - - self.assertEqual(get_errno(), 32) - set_errno(0) - - @unittest.skipUnless(os.name == "nt", 'Test specific to Windows') - def test_GetLastError(self): - dll = WinDLL("kernel32", use_last_error=True) - GetModuleHandle = dll.GetModuleHandleA - GetModuleHandle.argtypes = [c_wchar_p] - - self.assertEqual(0, GetModuleHandle("foo")) - self.assertEqual(get_last_error(), 126) - - self.assertEqual(set_last_error(32), 126) - self.assertEqual(get_last_error(), 32) - - def _worker(): - set_last_error(0) - - dll = WinDLL("kernel32", use_last_error=False) - GetModuleHandle = dll.GetModuleHandleW - GetModuleHandle.argtypes = [c_wchar_p] - GetModuleHandle("bar") - - self.assertEqual(get_last_error(), 0) - - t = threading.Thread(target=_worker) - t.start() - t.join() - - self.assertEqual(get_last_error(), 32) - - set_last_error(0) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_find.py b/Lib/ctypes/test/test_find.py deleted file mode 100644 index 1ff9d019b13..00000000000 --- a/Lib/ctypes/test/test_find.py +++ /dev/null @@ -1,127 +0,0 @@ -import unittest -import unittest.mock -import os.path -import sys -import test.support -from test.support import os_helper -from ctypes import * -from ctypes.util import find_library - -# On some systems, loading the OpenGL libraries needs the RTLD_GLOBAL mode. -class Test_OpenGL_libs(unittest.TestCase): - @classmethod - def setUpClass(cls): - lib_gl = lib_glu = lib_gle = None - if sys.platform == "win32": - lib_gl = find_library("OpenGL32") - lib_glu = find_library("Glu32") - elif sys.platform == "darwin": - lib_gl = lib_glu = find_library("OpenGL") - else: - lib_gl = find_library("GL") - lib_glu = find_library("GLU") - lib_gle = find_library("gle") - - ## print, for debugging - if test.support.verbose: - print("OpenGL libraries:") - for item in (("GL", lib_gl), - ("GLU", lib_glu), - ("gle", lib_gle)): - print("\t", item) - - cls.gl = cls.glu = cls.gle = None - if lib_gl: - try: - cls.gl = CDLL(lib_gl, mode=RTLD_GLOBAL) - except OSError: - pass - if lib_glu: - try: - cls.glu = CDLL(lib_glu, RTLD_GLOBAL) - except OSError: - pass - if lib_gle: - try: - cls.gle = CDLL(lib_gle) - except OSError: - pass - - @classmethod - def tearDownClass(cls): - cls.gl = cls.glu = cls.gle = None - - def test_gl(self): - if self.gl is None: - self.skipTest('lib_gl not available') - self.gl.glClearIndex - - def test_glu(self): - if self.glu is None: - self.skipTest('lib_glu not available') - self.glu.gluBeginCurve - - def test_gle(self): - if self.gle is None: - self.skipTest('lib_gle not available') - self.gle.gleGetJoinStyle - - def test_shell_injection(self): - result = find_library('; echo Hello shell > ' + os_helper.TESTFN) - self.assertFalse(os.path.lexists(os_helper.TESTFN)) - self.assertIsNone(result) - - -@unittest.skipUnless(sys.platform.startswith('linux'), - 'Test only valid for Linux') -class FindLibraryLinux(unittest.TestCase): - def test_find_on_libpath(self): - import subprocess - import tempfile - - try: - p = subprocess.Popen(['gcc', '--version'], stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL) - out, _ = p.communicate() - except OSError: - raise unittest.SkipTest('gcc, needed for test, not available') - with tempfile.TemporaryDirectory() as d: - # create an empty temporary file - srcname = os.path.join(d, 'dummy.c') - libname = 'py_ctypes_test_dummy' - dstname = os.path.join(d, 'lib%s.so' % libname) - with open(srcname, 'wb') as f: - pass - self.assertTrue(os.path.exists(srcname)) - # compile the file to a shared library - cmd = ['gcc', '-o', dstname, '--shared', - '-Wl,-soname,lib%s.so' % libname, srcname] - out = subprocess.check_output(cmd) - self.assertTrue(os.path.exists(dstname)) - # now check that the .so can't be found (since not in - # LD_LIBRARY_PATH) - self.assertIsNone(find_library(libname)) - # now add the location to LD_LIBRARY_PATH - with os_helper.EnvironmentVarGuard() as env: - KEY = 'LD_LIBRARY_PATH' - if KEY not in env: - v = d - else: - v = '%s:%s' % (env[KEY], d) - env.set(KEY, v) - # now check that the .so can be found (since in - # LD_LIBRARY_PATH) - self.assertEqual(find_library(libname), 'lib%s.so' % libname) - - def test_find_library_with_gcc(self): - with unittest.mock.patch("ctypes.util._findSoname_ldconfig", lambda *args: None): - self.assertNotEqual(find_library('c'), None) - - def test_find_library_with_ld(self): - with unittest.mock.patch("ctypes.util._findSoname_ldconfig", lambda *args: None), \ - unittest.mock.patch("ctypes.util._findLib_gcc", lambda *args: None): - self.assertNotEqual(find_library('c'), None) - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_frombuffer.py b/Lib/ctypes/test/test_frombuffer.py deleted file mode 100644 index 55c244356b3..00000000000 --- a/Lib/ctypes/test/test_frombuffer.py +++ /dev/null @@ -1,141 +0,0 @@ -from ctypes import * -import array -import gc -import unittest - -class X(Structure): - _fields_ = [("c_int", c_int)] - init_called = False - def __init__(self): - self._init_called = True - -class Test(unittest.TestCase): - def test_from_buffer(self): - a = array.array("i", range(16)) - x = (c_int * 16).from_buffer(a) - - y = X.from_buffer(a) - self.assertEqual(y.c_int, a[0]) - self.assertFalse(y.init_called) - - self.assertEqual(x[:], a.tolist()) - - a[0], a[-1] = 200, -200 - self.assertEqual(x[:], a.tolist()) - - self.assertRaises(BufferError, a.append, 100) - self.assertRaises(BufferError, a.pop) - - del x; del y; gc.collect(); gc.collect(); gc.collect() - a.append(100) - a.pop() - x = (c_int * 16).from_buffer(a) - - self.assertIn(a, [obj.obj if isinstance(obj, memoryview) else obj - for obj in x._objects.values()]) - - expected = x[:] - del a; gc.collect(); gc.collect(); gc.collect() - self.assertEqual(x[:], expected) - - with self.assertRaisesRegex(TypeError, "not writable"): - (c_char * 16).from_buffer(b"a" * 16) - with self.assertRaisesRegex(TypeError, "not writable"): - (c_char * 16).from_buffer(memoryview(b"a" * 16)) - with self.assertRaisesRegex(TypeError, "not C contiguous"): - (c_char * 16).from_buffer(memoryview(bytearray(b"a" * 16))[::-1]) - msg = "bytes-like object is required" - with self.assertRaisesRegex(TypeError, msg): - (c_char * 16).from_buffer("a" * 16) - - def test_fortran_contiguous(self): - try: - import _testbuffer - except ImportError as err: - self.skipTest(str(err)) - flags = _testbuffer.ND_WRITABLE | _testbuffer.ND_FORTRAN - array = _testbuffer.ndarray( - [97] * 16, format="B", shape=[4, 4], flags=flags) - with self.assertRaisesRegex(TypeError, "not C contiguous"): - (c_char * 16).from_buffer(array) - array = memoryview(array) - self.assertTrue(array.f_contiguous) - self.assertFalse(array.c_contiguous) - with self.assertRaisesRegex(TypeError, "not C contiguous"): - (c_char * 16).from_buffer(array) - - def test_from_buffer_with_offset(self): - a = array.array("i", range(16)) - x = (c_int * 15).from_buffer(a, sizeof(c_int)) - - self.assertEqual(x[:], a.tolist()[1:]) - with self.assertRaises(ValueError): - c_int.from_buffer(a, -1) - with self.assertRaises(ValueError): - (c_int * 16).from_buffer(a, sizeof(c_int)) - with self.assertRaises(ValueError): - (c_int * 1).from_buffer(a, 16 * sizeof(c_int)) - - def test_from_buffer_memoryview(self): - a = [c_char.from_buffer(memoryview(bytearray(b'a')))] - a.append(a) - del a - gc.collect() # Should not crash - - def test_from_buffer_copy(self): - a = array.array("i", range(16)) - x = (c_int * 16).from_buffer_copy(a) - - y = X.from_buffer_copy(a) - self.assertEqual(y.c_int, a[0]) - self.assertFalse(y.init_called) - - self.assertEqual(x[:], list(range(16))) - - a[0], a[-1] = 200, -200 - self.assertEqual(x[:], list(range(16))) - - a.append(100) - self.assertEqual(x[:], list(range(16))) - - self.assertEqual(x._objects, None) - - del a; gc.collect(); gc.collect(); gc.collect() - self.assertEqual(x[:], list(range(16))) - - x = (c_char * 16).from_buffer_copy(b"a" * 16) - self.assertEqual(x[:], b"a" * 16) - with self.assertRaises(TypeError): - (c_char * 16).from_buffer_copy("a" * 16) - - def test_from_buffer_copy_with_offset(self): - a = array.array("i", range(16)) - x = (c_int * 15).from_buffer_copy(a, sizeof(c_int)) - - self.assertEqual(x[:], a.tolist()[1:]) - with self.assertRaises(ValueError): - c_int.from_buffer_copy(a, -1) - with self.assertRaises(ValueError): - (c_int * 16).from_buffer_copy(a, sizeof(c_int)) - with self.assertRaises(ValueError): - (c_int * 1).from_buffer_copy(a, 16 * sizeof(c_int)) - - def test_abstract(self): - from ctypes import _Pointer, _SimpleCData, _CFuncPtr - - self.assertRaises(TypeError, Array.from_buffer, bytearray(10)) - self.assertRaises(TypeError, Structure.from_buffer, bytearray(10)) - self.assertRaises(TypeError, Union.from_buffer, bytearray(10)) - self.assertRaises(TypeError, _CFuncPtr.from_buffer, bytearray(10)) - self.assertRaises(TypeError, _Pointer.from_buffer, bytearray(10)) - self.assertRaises(TypeError, _SimpleCData.from_buffer, bytearray(10)) - - self.assertRaises(TypeError, Array.from_buffer_copy, b"123") - self.assertRaises(TypeError, Structure.from_buffer_copy, b"123") - self.assertRaises(TypeError, Union.from_buffer_copy, b"123") - self.assertRaises(TypeError, _CFuncPtr.from_buffer_copy, b"123") - self.assertRaises(TypeError, _Pointer.from_buffer_copy, b"123") - self.assertRaises(TypeError, _SimpleCData.from_buffer_copy, b"123") - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_funcptr.py b/Lib/ctypes/test/test_funcptr.py deleted file mode 100644 index e0b9b54e97f..00000000000 --- a/Lib/ctypes/test/test_funcptr.py +++ /dev/null @@ -1,132 +0,0 @@ -import unittest -from ctypes import * - -try: - WINFUNCTYPE -except NameError: - # fake to enable this test on Linux - WINFUNCTYPE = CFUNCTYPE - -import _ctypes_test -lib = CDLL(_ctypes_test.__file__) - -class CFuncPtrTestCase(unittest.TestCase): - def test_basic(self): - X = WINFUNCTYPE(c_int, c_int, c_int) - - def func(*args): - return len(args) - - x = X(func) - self.assertEqual(x.restype, c_int) - self.assertEqual(x.argtypes, (c_int, c_int)) - self.assertEqual(sizeof(x), sizeof(c_voidp)) - self.assertEqual(sizeof(X), sizeof(c_voidp)) - - def test_first(self): - StdCallback = WINFUNCTYPE(c_int, c_int, c_int) - CdeclCallback = CFUNCTYPE(c_int, c_int, c_int) - - def func(a, b): - return a + b - - s = StdCallback(func) - c = CdeclCallback(func) - - self.assertEqual(s(1, 2), 3) - self.assertEqual(c(1, 2), 3) - # The following no longer raises a TypeError - it is now - # possible, as in C, to call cdecl functions with more parameters. - #self.assertRaises(TypeError, c, 1, 2, 3) - self.assertEqual(c(1, 2, 3, 4, 5, 6), 3) - if not WINFUNCTYPE is CFUNCTYPE: - self.assertRaises(TypeError, s, 1, 2, 3) - - def test_structures(self): - WNDPROC = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int) - - def wndproc(hwnd, msg, wParam, lParam): - return hwnd + msg + wParam + lParam - - HINSTANCE = c_int - HICON = c_int - HCURSOR = c_int - LPCTSTR = c_char_p - - class WNDCLASS(Structure): - _fields_ = [("style", c_uint), - ("lpfnWndProc", WNDPROC), - ("cbClsExtra", c_int), - ("cbWndExtra", c_int), - ("hInstance", HINSTANCE), - ("hIcon", HICON), - ("hCursor", HCURSOR), - ("lpszMenuName", LPCTSTR), - ("lpszClassName", LPCTSTR)] - - wndclass = WNDCLASS() - wndclass.lpfnWndProc = WNDPROC(wndproc) - - WNDPROC_2 = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int) - - # This is no longer true, now that WINFUNCTYPE caches created types internally. - ## # CFuncPtr subclasses are compared by identity, so this raises a TypeError: - ## self.assertRaises(TypeError, setattr, wndclass, - ## "lpfnWndProc", WNDPROC_2(wndproc)) - # instead: - - self.assertIs(WNDPROC, WNDPROC_2) - # 'wndclass.lpfnWndProc' leaks 94 references. Why? - self.assertEqual(wndclass.lpfnWndProc(1, 2, 3, 4), 10) - - - f = wndclass.lpfnWndProc - - del wndclass - del wndproc - - self.assertEqual(f(10, 11, 12, 13), 46) - - def test_dllfunctions(self): - - def NoNullHandle(value): - if not value: - raise WinError() - return value - - strchr = lib.my_strchr - strchr.restype = c_char_p - strchr.argtypes = (c_char_p, c_char) - self.assertEqual(strchr(b"abcdefghi", b"b"), b"bcdefghi") - self.assertEqual(strchr(b"abcdefghi", b"x"), None) - - - strtok = lib.my_strtok - strtok.restype = c_char_p - # Neither of this does work: strtok changes the buffer it is passed -## strtok.argtypes = (c_char_p, c_char_p) -## strtok.argtypes = (c_string, c_char_p) - - def c_string(init): - size = len(init) + 1 - return (c_char*size)(*init) - - s = b"a\nb\nc" - b = c_string(s) - -## b = (c_char * (len(s)+1))() -## b.value = s - -## b = c_string(s) - self.assertEqual(strtok(b, b"\n"), b"a") - self.assertEqual(strtok(None, b"\n"), b"b") - self.assertEqual(strtok(None, b"\n"), b"c") - self.assertEqual(strtok(None, b"\n"), None) - - def test_abstract(self): - from ctypes import _CFuncPtr - - self.assertRaises(TypeError, _CFuncPtr, 13, "name", 42, "iid") - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_functions.py b/Lib/ctypes/test/test_functions.py deleted file mode 100644 index fc571700ce3..00000000000 --- a/Lib/ctypes/test/test_functions.py +++ /dev/null @@ -1,384 +0,0 @@ -""" -Here is probably the place to write the docs, since the test-cases -show how the type behave. - -Later... -""" - -from ctypes import * -from ctypes.test import need_symbol -import sys, unittest - -try: - WINFUNCTYPE -except NameError: - # fake to enable this test on Linux - WINFUNCTYPE = CFUNCTYPE - -import _ctypes_test -dll = CDLL(_ctypes_test.__file__) -if sys.platform == "win32": - windll = WinDLL(_ctypes_test.__file__) - -class POINT(Structure): - _fields_ = [("x", c_int), ("y", c_int)] -class RECT(Structure): - _fields_ = [("left", c_int), ("top", c_int), - ("right", c_int), ("bottom", c_int)] -class FunctionTestCase(unittest.TestCase): - - def test_mro(self): - # in Python 2.3, this raises TypeError: MRO conflict among bases classes, - # in Python 2.2 it works. - # - # But in early versions of _ctypes.c, the result of tp_new - # wasn't checked, and it even crashed Python. - # Found by Greg Chapman. - - with self.assertRaises(TypeError): - class X(object, Array): - _length_ = 5 - _type_ = "i" - - from _ctypes import _Pointer - with self.assertRaises(TypeError): - class X(object, _Pointer): - pass - - from _ctypes import _SimpleCData - with self.assertRaises(TypeError): - class X(object, _SimpleCData): - _type_ = "i" - - with self.assertRaises(TypeError): - class X(object, Structure): - _fields_ = [] - - @need_symbol('c_wchar') - def test_wchar_parm(self): - f = dll._testfunc_i_bhilfd - f.argtypes = [c_byte, c_wchar, c_int, c_long, c_float, c_double] - result = f(1, "x", 3, 4, 5.0, 6.0) - self.assertEqual(result, 139) - self.assertEqual(type(result), int) - - @need_symbol('c_wchar') - def test_wchar_result(self): - f = dll._testfunc_i_bhilfd - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] - f.restype = c_wchar - result = f(0, 0, 0, 0, 0, 0) - self.assertEqual(result, '\x00') - - def test_voidresult(self): - f = dll._testfunc_v - f.restype = None - f.argtypes = [c_int, c_int, POINTER(c_int)] - result = c_int() - self.assertEqual(None, f(1, 2, byref(result))) - self.assertEqual(result.value, 3) - - def test_intresult(self): - f = dll._testfunc_i_bhilfd - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] - f.restype = c_int - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), int) - - result = f(-1, -2, -3, -4, -5.0, -6.0) - self.assertEqual(result, -21) - self.assertEqual(type(result), int) - - # If we declare the function to return a short, - # is the high part split off? - f.restype = c_short - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), int) - - result = f(1, 2, 3, 0x10004, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), int) - - # You cannot assign character format codes as restype any longer - self.assertRaises(TypeError, setattr, f, "restype", "i") - - def test_floatresult(self): - f = dll._testfunc_f_bhilfd - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] - f.restype = c_float - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), float) - - result = f(-1, -2, -3, -4, -5.0, -6.0) - self.assertEqual(result, -21) - self.assertEqual(type(result), float) - - def test_doubleresult(self): - f = dll._testfunc_d_bhilfd - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] - f.restype = c_double - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), float) - - result = f(-1, -2, -3, -4, -5.0, -6.0) - self.assertEqual(result, -21) - self.assertEqual(type(result), float) - - @need_symbol('c_longdouble') - def test_longdoubleresult(self): - f = dll._testfunc_D_bhilfD - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_longdouble] - f.restype = c_longdouble - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), float) - - result = f(-1, -2, -3, -4, -5.0, -6.0) - self.assertEqual(result, -21) - self.assertEqual(type(result), float) - - @need_symbol('c_longlong') - def test_longlongresult(self): - f = dll._testfunc_q_bhilfd - f.restype = c_longlong - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - - f = dll._testfunc_q_bhilfdq - f.restype = c_longlong - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double, c_longlong] - result = f(1, 2, 3, 4, 5.0, 6.0, 21) - self.assertEqual(result, 42) - - def test_stringresult(self): - f = dll._testfunc_p_p - f.argtypes = None - f.restype = c_char_p - result = f(b"123") - self.assertEqual(result, b"123") - - result = f(None) - self.assertEqual(result, None) - - def test_pointers(self): - f = dll._testfunc_p_p - f.restype = POINTER(c_int) - f.argtypes = [POINTER(c_int)] - - # This only works if the value c_int(42) passed to the - # function is still alive while the pointer (the result) is - # used. - - v = c_int(42) - - self.assertEqual(pointer(v).contents.value, 42) - result = f(pointer(v)) - self.assertEqual(type(result), POINTER(c_int)) - self.assertEqual(result.contents.value, 42) - - # This on works... - result = f(pointer(v)) - self.assertEqual(result.contents.value, v.value) - - p = pointer(c_int(99)) - result = f(p) - self.assertEqual(result.contents.value, 99) - - arg = byref(v) - result = f(arg) - self.assertNotEqual(result.contents, v.value) - - self.assertRaises(ArgumentError, f, byref(c_short(22))) - - # It is dangerous, however, because you don't control the lifetime - # of the pointer: - result = f(byref(c_int(99))) - self.assertNotEqual(result.contents, 99) - - ################################################################ - def test_shorts(self): - f = dll._testfunc_callback_i_if - - args = [] - expected = [262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, - 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1] - - def callback(v): - args.append(v) - return v - - CallBack = CFUNCTYPE(c_int, c_int) - - cb = CallBack(callback) - f(2**18, cb) - self.assertEqual(args, expected) - - ################################################################ - - - def test_callbacks(self): - f = dll._testfunc_callback_i_if - f.restype = c_int - f.argtypes = None - - MyCallback = CFUNCTYPE(c_int, c_int) - - def callback(value): - #print "called back with", value - return value - - cb = MyCallback(callback) - result = f(-10, cb) - self.assertEqual(result, -18) - - # test with prototype - f.argtypes = [c_int, MyCallback] - cb = MyCallback(callback) - result = f(-10, cb) - self.assertEqual(result, -18) - - AnotherCallback = WINFUNCTYPE(c_int, c_int, c_int, c_int, c_int) - - # check that the prototype works: we call f with wrong - # argument types - cb = AnotherCallback(callback) - self.assertRaises(ArgumentError, f, -10, cb) - - - def test_callbacks_2(self): - # Can also use simple datatypes as argument type specifiers - # for the callback function. - # In this case the call receives an instance of that type - f = dll._testfunc_callback_i_if - f.restype = c_int - - MyCallback = CFUNCTYPE(c_int, c_int) - - f.argtypes = [c_int, MyCallback] - - def callback(value): - #print "called back with", value - self.assertEqual(type(value), int) - return value - - cb = MyCallback(callback) - result = f(-10, cb) - self.assertEqual(result, -18) - - @need_symbol('c_longlong') - def test_longlong_callbacks(self): - - f = dll._testfunc_callback_q_qf - f.restype = c_longlong - - MyCallback = CFUNCTYPE(c_longlong, c_longlong) - - f.argtypes = [c_longlong, MyCallback] - - def callback(value): - self.assertIsInstance(value, int) - return value & 0x7FFFFFFF - - cb = MyCallback(callback) - - self.assertEqual(13577625587, f(1000000000000, cb)) - - def test_errors(self): - self.assertRaises(AttributeError, getattr, dll, "_xxx_yyy") - self.assertRaises(ValueError, c_int.in_dll, dll, "_xxx_yyy") - - def test_byval(self): - - # without prototype - ptin = POINT(1, 2) - ptout = POINT() - # EXPORT int _testfunc_byval(point in, point *pout) - result = dll._testfunc_byval(ptin, byref(ptout)) - got = result, ptout.x, ptout.y - expected = 3, 1, 2 - self.assertEqual(got, expected) - - # with prototype - ptin = POINT(101, 102) - ptout = POINT() - dll._testfunc_byval.argtypes = (POINT, POINTER(POINT)) - dll._testfunc_byval.restype = c_int - result = dll._testfunc_byval(ptin, byref(ptout)) - got = result, ptout.x, ptout.y - expected = 203, 101, 102 - self.assertEqual(got, expected) - - def test_struct_return_2H(self): - class S2H(Structure): - _fields_ = [("x", c_short), - ("y", c_short)] - dll.ret_2h_func.restype = S2H - dll.ret_2h_func.argtypes = [S2H] - inp = S2H(99, 88) - s2h = dll.ret_2h_func(inp) - self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) - - @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') - def test_struct_return_2H_stdcall(self): - class S2H(Structure): - _fields_ = [("x", c_short), - ("y", c_short)] - - windll.s_ret_2h_func.restype = S2H - windll.s_ret_2h_func.argtypes = [S2H] - s2h = windll.s_ret_2h_func(S2H(99, 88)) - self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) - - def test_struct_return_8H(self): - class S8I(Structure): - _fields_ = [("a", c_int), - ("b", c_int), - ("c", c_int), - ("d", c_int), - ("e", c_int), - ("f", c_int), - ("g", c_int), - ("h", c_int)] - dll.ret_8i_func.restype = S8I - dll.ret_8i_func.argtypes = [S8I] - inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) - s8i = dll.ret_8i_func(inp) - self.assertEqual((s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), - (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) - - @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') - def test_struct_return_8H_stdcall(self): - class S8I(Structure): - _fields_ = [("a", c_int), - ("b", c_int), - ("c", c_int), - ("d", c_int), - ("e", c_int), - ("f", c_int), - ("g", c_int), - ("h", c_int)] - windll.s_ret_8i_func.restype = S8I - windll.s_ret_8i_func.argtypes = [S8I] - inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) - s8i = windll.s_ret_8i_func(inp) - self.assertEqual( - (s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), - (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) - - def test_sf1651235(self): - # see https://www.python.org/sf/1651235 - - proto = CFUNCTYPE(c_int, RECT, POINT) - def callback(*args): - return 0 - - callback = proto(callback) - self.assertRaises(ArgumentError, lambda: callback((1, 2, 3, 4), POINT())) - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_incomplete.py b/Lib/ctypes/test/test_incomplete.py deleted file mode 100644 index 00c430ef53c..00000000000 --- a/Lib/ctypes/test/test_incomplete.py +++ /dev/null @@ -1,42 +0,0 @@ -import unittest -from ctypes import * - -################################################################ -# -# The incomplete pointer example from the tutorial -# - -class MyTestCase(unittest.TestCase): - - def test_incomplete_example(self): - lpcell = POINTER("cell") - class cell(Structure): - _fields_ = [("name", c_char_p), - ("next", lpcell)] - - SetPointerType(lpcell, cell) - - c1 = cell() - c1.name = b"foo" - c2 = cell() - c2.name = b"bar" - - c1.next = pointer(c2) - c2.next = pointer(c1) - - p = c1 - - result = [] - for i in range(8): - result.append(p.name) - p = p.next[0] - self.assertEqual(result, [b"foo", b"bar"] * 4) - - # to not leak references, we must clean _pointer_type_cache - from ctypes import _pointer_type_cache - del _pointer_type_cache[cell] - -################################################################ - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_init.py b/Lib/ctypes/test/test_init.py deleted file mode 100644 index 75fad112a01..00000000000 --- a/Lib/ctypes/test/test_init.py +++ /dev/null @@ -1,40 +0,0 @@ -from ctypes import * -import unittest - -class X(Structure): - _fields_ = [("a", c_int), - ("b", c_int)] - new_was_called = False - - def __new__(cls): - result = super().__new__(cls) - result.new_was_called = True - return result - - def __init__(self): - self.a = 9 - self.b = 12 - -class Y(Structure): - _fields_ = [("x", X)] - - -class InitTest(unittest.TestCase): - def test_get(self): - # make sure the only accessing a nested structure - # doesn't call the structure's __new__ and __init__ - y = Y() - self.assertEqual((y.x.a, y.x.b), (0, 0)) - self.assertEqual(y.x.new_was_called, False) - - # But explicitly creating an X structure calls __new__ and __init__, of course. - x = X() - self.assertEqual((x.a, x.b), (9, 12)) - self.assertEqual(x.new_was_called, True) - - y.x = x - self.assertEqual((y.x.a, y.x.b), (9, 12)) - self.assertEqual(y.x.new_was_called, False) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_internals.py b/Lib/ctypes/test/test_internals.py deleted file mode 100644 index 271e3f57f81..00000000000 --- a/Lib/ctypes/test/test_internals.py +++ /dev/null @@ -1,100 +0,0 @@ -# This tests the internal _objects attribute -import unittest -from ctypes import * -from sys import getrefcount as grc - -# XXX This test must be reviewed for correctness!!! - -# ctypes' types are container types. -# -# They have an internal memory block, which only consists of some bytes, -# but it has to keep references to other objects as well. This is not -# really needed for trivial C types like int or char, but it is important -# for aggregate types like strings or pointers in particular. -# -# What about pointers? - -class ObjectsTestCase(unittest.TestCase): - def assertSame(self, a, b): - self.assertEqual(id(a), id(b)) - - def test_ints(self): - i = 42000123 - refcnt = grc(i) - ci = c_int(i) - self.assertEqual(refcnt, grc(i)) - self.assertEqual(ci._objects, None) - - def test_c_char_p(self): - s = b"Hello, World" - refcnt = grc(s) - cs = c_char_p(s) - self.assertEqual(refcnt + 1, grc(s)) - self.assertSame(cs._objects, s) - - def test_simple_struct(self): - class X(Structure): - _fields_ = [("a", c_int), ("b", c_int)] - - a = 421234 - b = 421235 - x = X() - self.assertEqual(x._objects, None) - x.a = a - x.b = b - self.assertEqual(x._objects, None) - - def test_embedded_structs(self): - class X(Structure): - _fields_ = [("a", c_int), ("b", c_int)] - - class Y(Structure): - _fields_ = [("x", X), ("y", X)] - - y = Y() - self.assertEqual(y._objects, None) - - x1, x2 = X(), X() - y.x, y.y = x1, x2 - self.assertEqual(y._objects, {"0": {}, "1": {}}) - x1.a, x2.b = 42, 93 - self.assertEqual(y._objects, {"0": {}, "1": {}}) - - def test_xxx(self): - class X(Structure): - _fields_ = [("a", c_char_p), ("b", c_char_p)] - - class Y(Structure): - _fields_ = [("x", X), ("y", X)] - - s1 = b"Hello, World" - s2 = b"Hallo, Welt" - - x = X() - x.a = s1 - x.b = s2 - self.assertEqual(x._objects, {"0": s1, "1": s2}) - - y = Y() - y.x = x - self.assertEqual(y._objects, {"0": {"0": s1, "1": s2}}) -## x = y.x -## del y -## print x._b_base_._objects - - def test_ptr_struct(self): - class X(Structure): - _fields_ = [("data", POINTER(c_int))] - - A = c_int*4 - a = A(11, 22, 33, 44) - self.assertEqual(a._objects, None) - - x = X() - x.data = a -##XXX print x._objects -##XXX print x.data[0] -##XXX print x.data._objects - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_keeprefs.py b/Lib/ctypes/test/test_keeprefs.py deleted file mode 100644 index 94c02573fa1..00000000000 --- a/Lib/ctypes/test/test_keeprefs.py +++ /dev/null @@ -1,153 +0,0 @@ -from ctypes import * -import unittest - -class SimpleTestCase(unittest.TestCase): - def test_cint(self): - x = c_int() - self.assertEqual(x._objects, None) - x.value = 42 - self.assertEqual(x._objects, None) - x = c_int(99) - self.assertEqual(x._objects, None) - - def test_ccharp(self): - x = c_char_p() - self.assertEqual(x._objects, None) - x.value = b"abc" - self.assertEqual(x._objects, b"abc") - x = c_char_p(b"spam") - self.assertEqual(x._objects, b"spam") - -class StructureTestCase(unittest.TestCase): - def test_cint_struct(self): - class X(Structure): - _fields_ = [("a", c_int), - ("b", c_int)] - - x = X() - self.assertEqual(x._objects, None) - x.a = 42 - x.b = 99 - self.assertEqual(x._objects, None) - - def test_ccharp_struct(self): - class X(Structure): - _fields_ = [("a", c_char_p), - ("b", c_char_p)] - x = X() - self.assertEqual(x._objects, None) - - x.a = b"spam" - x.b = b"foo" - self.assertEqual(x._objects, {"0": b"spam", "1": b"foo"}) - - def test_struct_struct(self): - class POINT(Structure): - _fields_ = [("x", c_int), ("y", c_int)] - class RECT(Structure): - _fields_ = [("ul", POINT), ("lr", POINT)] - - r = RECT() - r.ul.x = 0 - r.ul.y = 1 - r.lr.x = 2 - r.lr.y = 3 - self.assertEqual(r._objects, None) - - r = RECT() - pt = POINT(1, 2) - r.ul = pt - self.assertEqual(r._objects, {'0': {}}) - r.ul.x = 22 - r.ul.y = 44 - self.assertEqual(r._objects, {'0': {}}) - r.lr = POINT() - self.assertEqual(r._objects, {'0': {}, '1': {}}) - -class ArrayTestCase(unittest.TestCase): - def test_cint_array(self): - INTARR = c_int * 3 - - ia = INTARR() - self.assertEqual(ia._objects, None) - ia[0] = 1 - ia[1] = 2 - ia[2] = 3 - self.assertEqual(ia._objects, None) - - class X(Structure): - _fields_ = [("x", c_int), - ("a", INTARR)] - - x = X() - x.x = 1000 - x.a[0] = 42 - x.a[1] = 96 - self.assertEqual(x._objects, None) - x.a = ia - self.assertEqual(x._objects, {'1': {}}) - -class PointerTestCase(unittest.TestCase): - def test_p_cint(self): - i = c_int(42) - x = pointer(i) - self.assertEqual(x._objects, {'1': i}) - -class DeletePointerTestCase(unittest.TestCase): - @unittest.skip('test disabled') - def test_X(self): - class X(Structure): - _fields_ = [("p", POINTER(c_char_p))] - x = X() - i = c_char_p("abc def") - from sys import getrefcount as grc - print("2?", grc(i)) - x.p = pointer(i) - print("3?", grc(i)) - for i in range(320): - c_int(99) - x.p[0] - print(x.p[0]) -## del x -## print "2?", grc(i) -## del i - import gc - gc.collect() - for i in range(320): - c_int(99) - x.p[0] - print(x.p[0]) - print(x.p.contents) -## print x._objects - - x.p[0] = "spam spam" -## print x.p[0] - print("+" * 42) - print(x._objects) - -class PointerToStructure(unittest.TestCase): - def test(self): - class POINT(Structure): - _fields_ = [("x", c_int), ("y", c_int)] - class RECT(Structure): - _fields_ = [("a", POINTER(POINT)), - ("b", POINTER(POINT))] - r = RECT() - p1 = POINT(1, 2) - - r.a = pointer(p1) - r.b = pointer(p1) -## from pprint import pprint as pp -## pp(p1._objects) -## pp(r._objects) - - r.a[0].x = 42 - r.a[0].y = 99 - - # to avoid leaking when tests are run several times - # clean up the types left in the cache. - from ctypes import _pointer_type_cache - del _pointer_type_cache[POINT] - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_libc.py b/Lib/ctypes/test/test_libc.py deleted file mode 100644 index 56285b5ff81..00000000000 --- a/Lib/ctypes/test/test_libc.py +++ /dev/null @@ -1,33 +0,0 @@ -import unittest - -from ctypes import * -import _ctypes_test - -lib = CDLL(_ctypes_test.__file__) - -def three_way_cmp(x, y): - """Return -1 if x < y, 0 if x == y and 1 if x > y""" - return (x > y) - (x < y) - -class LibTest(unittest.TestCase): - def test_sqrt(self): - lib.my_sqrt.argtypes = c_double, - lib.my_sqrt.restype = c_double - self.assertEqual(lib.my_sqrt(4.0), 2.0) - import math - self.assertEqual(lib.my_sqrt(2.0), math.sqrt(2.0)) - - def test_qsort(self): - comparefunc = CFUNCTYPE(c_int, POINTER(c_char), POINTER(c_char)) - lib.my_qsort.argtypes = c_void_p, c_size_t, c_size_t, comparefunc - lib.my_qsort.restype = None - - def sort(a, b): - return three_way_cmp(a[0], b[0]) - - chars = create_string_buffer(b"spam, spam, and spam") - lib.my_qsort(chars, len(chars)-1, sizeof(c_char), comparefunc(sort)) - self.assertEqual(chars.raw, b" ,,aaaadmmmnpppsss\x00") - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_loading.py b/Lib/ctypes/test/test_loading.py deleted file mode 100644 index ea892277c4e..00000000000 --- a/Lib/ctypes/test/test_loading.py +++ /dev/null @@ -1,182 +0,0 @@ -from ctypes import * -import os -import shutil -import subprocess -import sys -import unittest -import test.support -from test.support import import_helper -from test.support import os_helper -from ctypes.util import find_library - -libc_name = None - -def setUpModule(): - global libc_name - if os.name == "nt": - libc_name = find_library("c") - elif sys.platform == "cygwin": - libc_name = "cygwin1.dll" - else: - libc_name = find_library("c") - - if test.support.verbose: - print("libc_name is", libc_name) - -class LoaderTest(unittest.TestCase): - - unknowndll = "xxrandomnamexx" - - def test_load(self): - if libc_name is None: - self.skipTest('could not find libc') - CDLL(libc_name) - CDLL(os.path.basename(libc_name)) - self.assertRaises(OSError, CDLL, self.unknowndll) - - def test_load_version(self): - if libc_name is None: - self.skipTest('could not find libc') - if os.path.basename(libc_name) != 'libc.so.6': - self.skipTest('wrong libc path for test') - cdll.LoadLibrary("libc.so.6") - # linux uses version, libc 9 should not exist - self.assertRaises(OSError, cdll.LoadLibrary, "libc.so.9") - self.assertRaises(OSError, cdll.LoadLibrary, self.unknowndll) - - def test_find(self): - for name in ("c", "m"): - lib = find_library(name) - if lib: - cdll.LoadLibrary(lib) - CDLL(lib) - - @unittest.skipUnless(os.name == "nt", - 'test specific to Windows') - def test_load_library(self): - # CRT is no longer directly loadable. See issue23606 for the - # discussion about alternative approaches. - #self.assertIsNotNone(libc_name) - if test.support.verbose: - print(find_library("kernel32")) - print(find_library("user32")) - - if os.name == "nt": - windll.kernel32.GetModuleHandleW - windll["kernel32"].GetModuleHandleW - windll.LoadLibrary("kernel32").GetModuleHandleW - WinDLL("kernel32").GetModuleHandleW - # embedded null character - self.assertRaises(ValueError, windll.LoadLibrary, "kernel32\0") - - @unittest.skipUnless(os.name == "nt", - 'test specific to Windows') - def test_load_ordinal_functions(self): - import _ctypes_test - dll = WinDLL(_ctypes_test.__file__) - # We load the same function both via ordinal and name - func_ord = dll[2] - func_name = dll.GetString - # addressof gets the address where the function pointer is stored - a_ord = addressof(func_ord) - a_name = addressof(func_name) - f_ord_addr = c_void_p.from_address(a_ord).value - f_name_addr = c_void_p.from_address(a_name).value - self.assertEqual(hex(f_ord_addr), hex(f_name_addr)) - - self.assertRaises(AttributeError, dll.__getitem__, 1234) - - @unittest.skipUnless(os.name == "nt", 'Windows-specific test') - def test_1703286_A(self): - from _ctypes import LoadLibrary, FreeLibrary - # On winXP 64-bit, advapi32 loads at an address that does - # NOT fit into a 32-bit integer. FreeLibrary must be able - # to accept this address. - - # These are tests for https://www.python.org/sf/1703286 - handle = LoadLibrary("advapi32") - FreeLibrary(handle) - - @unittest.skipUnless(os.name == "nt", 'Windows-specific test') - def test_1703286_B(self): - # Since on winXP 64-bit advapi32 loads like described - # above, the (arbitrarily selected) CloseEventLog function - # also has a high address. 'call_function' should accept - # addresses so large. - from _ctypes import call_function - advapi32 = windll.advapi32 - # Calling CloseEventLog with a NULL argument should fail, - # but the call should not segfault or so. - self.assertEqual(0, advapi32.CloseEventLog(None)) - windll.kernel32.GetProcAddress.argtypes = c_void_p, c_char_p - windll.kernel32.GetProcAddress.restype = c_void_p - proc = windll.kernel32.GetProcAddress(advapi32._handle, - b"CloseEventLog") - self.assertTrue(proc) - # This is the real test: call the function via 'call_function' - self.assertEqual(0, call_function(proc, (None,))) - - @unittest.skipUnless(os.name == "nt", - 'test specific to Windows') - def test_load_dll_with_flags(self): - _sqlite3 = import_helper.import_module("_sqlite3") - src = _sqlite3.__file__ - if src.lower().endswith("_d.pyd"): - ext = "_d.dll" - else: - ext = ".dll" - - with os_helper.temp_dir() as tmp: - # We copy two files and load _sqlite3.dll (formerly .pyd), - # which has a dependency on sqlite3.dll. Then we test - # loading it in subprocesses to avoid it starting in memory - # for each test. - target = os.path.join(tmp, "_sqlite3.dll") - shutil.copy(src, target) - shutil.copy(os.path.join(os.path.dirname(src), "sqlite3" + ext), - os.path.join(tmp, "sqlite3" + ext)) - - def should_pass(command): - with self.subTest(command): - subprocess.check_output( - [sys.executable, "-c", - "from ctypes import *; import nt;" + command], - cwd=tmp - ) - - def should_fail(command): - with self.subTest(command): - with self.assertRaises(subprocess.CalledProcessError): - subprocess.check_output( - [sys.executable, "-c", - "from ctypes import *; import nt;" + command], - cwd=tmp, stderr=subprocess.STDOUT, - ) - - # Default load should not find this in CWD - should_fail("WinDLL('_sqlite3.dll')") - - # Relative path (but not just filename) should succeed - should_pass("WinDLL('./_sqlite3.dll')") - - # Insecure load flags should succeed - # Clear the DLL directory to avoid safe search settings propagating - should_pass("windll.kernel32.SetDllDirectoryW(None); WinDLL('_sqlite3.dll', winmode=0)") - - # Full path load without DLL_LOAD_DIR shouldn't find dependency - should_fail("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + - "winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32)") - - # Full path load with DLL_LOAD_DIR should succeed - should_pass("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + - "winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32|" + - "nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)") - - # User-specified directory should succeed - should_pass("import os; p = os.add_dll_directory(os.getcwd());" + - "WinDLL('_sqlite3.dll'); p.close()") - - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_macholib.py b/Lib/ctypes/test/test_macholib.py deleted file mode 100644 index bc75f1a05a8..00000000000 --- a/Lib/ctypes/test/test_macholib.py +++ /dev/null @@ -1,110 +0,0 @@ -import os -import sys -import unittest - -# Bob Ippolito: -# -# Ok.. the code to find the filename for __getattr__ should look -# something like: -# -# import os -# from macholib.dyld import dyld_find -# -# def find_lib(name): -# possible = ['lib'+name+'.dylib', name+'.dylib', -# name+'.framework/'+name] -# for dylib in possible: -# try: -# return os.path.realpath(dyld_find(dylib)) -# except ValueError: -# pass -# raise ValueError, "%s not found" % (name,) -# -# It'll have output like this: -# -# >>> find_lib('pthread') -# '/usr/lib/libSystem.B.dylib' -# >>> find_lib('z') -# '/usr/lib/libz.1.dylib' -# >>> find_lib('IOKit') -# '/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit' -# -# -bob - -from ctypes.macholib.dyld import dyld_find -from ctypes.macholib.dylib import dylib_info -from ctypes.macholib.framework import framework_info - -def find_lib(name): - possible = ['lib'+name+'.dylib', name+'.dylib', name+'.framework/'+name] - for dylib in possible: - try: - return os.path.realpath(dyld_find(dylib)) - except ValueError: - pass - raise ValueError("%s not found" % (name,)) - - -def d(location=None, name=None, shortname=None, version=None, suffix=None): - return {'location': location, 'name': name, 'shortname': shortname, - 'version': version, 'suffix': suffix} - - -class MachOTest(unittest.TestCase): - @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') - def test_find(self): - self.assertEqual(dyld_find('libSystem.dylib'), - '/usr/lib/libSystem.dylib') - self.assertEqual(dyld_find('System.framework/System'), - '/System/Library/Frameworks/System.framework/System') - - # On Mac OS 11, system dylibs are only present in the shared cache, - # so symlinks like libpthread.dylib -> libSystem.B.dylib will not - # be resolved by dyld_find - self.assertIn(find_lib('pthread'), - ('/usr/lib/libSystem.B.dylib', '/usr/lib/libpthread.dylib')) - - result = find_lib('z') - # Issue #21093: dyld default search path includes $HOME/lib and - # /usr/local/lib before /usr/lib, which caused test failures if - # a local copy of libz exists in one of them. Now ignore the head - # of the path. - self.assertRegex(result, r".*/lib/libz.*\.dylib") - - self.assertIn(find_lib('IOKit'), - ('/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit', - '/System/Library/Frameworks/IOKit.framework/IOKit')) - - @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') - def test_info(self): - self.assertIsNone(dylib_info('completely/invalid')) - self.assertIsNone(dylib_info('completely/invalide_debug')) - self.assertEqual(dylib_info('P/Foo.dylib'), d('P', 'Foo.dylib', 'Foo')) - self.assertEqual(dylib_info('P/Foo_debug.dylib'), - d('P', 'Foo_debug.dylib', 'Foo', suffix='debug')) - self.assertEqual(dylib_info('P/Foo.A.dylib'), - d('P', 'Foo.A.dylib', 'Foo', 'A')) - self.assertEqual(dylib_info('P/Foo_debug.A.dylib'), - d('P', 'Foo_debug.A.dylib', 'Foo_debug', 'A')) - self.assertEqual(dylib_info('P/Foo.A_debug.dylib'), - d('P', 'Foo.A_debug.dylib', 'Foo', 'A', 'debug')) - - @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') - def test_framework_info(self): - self.assertIsNone(framework_info('completely/invalid')) - self.assertIsNone(framework_info('completely/invalid/_debug')) - self.assertIsNone(framework_info('P/F.framework')) - self.assertIsNone(framework_info('P/F.framework/_debug')) - self.assertEqual(framework_info('P/F.framework/F'), - d('P', 'F.framework/F', 'F')) - self.assertEqual(framework_info('P/F.framework/F_debug'), - d('P', 'F.framework/F_debug', 'F', suffix='debug')) - self.assertIsNone(framework_info('P/F.framework/Versions')) - self.assertIsNone(framework_info('P/F.framework/Versions/A')) - self.assertEqual(framework_info('P/F.framework/Versions/A/F'), - d('P', 'F.framework/Versions/A/F', 'F', 'A')) - self.assertEqual(framework_info('P/F.framework/Versions/A/F_debug'), - d('P', 'F.framework/Versions/A/F_debug', 'F', 'A', 'debug')) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_memfunctions.py b/Lib/ctypes/test/test_memfunctions.py deleted file mode 100644 index e784b9a7068..00000000000 --- a/Lib/ctypes/test/test_memfunctions.py +++ /dev/null @@ -1,79 +0,0 @@ -import sys -from test import support -import unittest -from ctypes import * -from ctypes.test import need_symbol - -class MemFunctionsTest(unittest.TestCase): - @unittest.skip('test disabled') - def test_overflow(self): - # string_at and wstring_at must use the Python calling - # convention (which acquires the GIL and checks the Python - # error flag). Provoke an error and catch it; see also issue - # #3554: <http://bugs.python.org/issue3554> - self.assertRaises((OverflowError, MemoryError, SystemError), - lambda: wstring_at(u"foo", sys.maxint - 1)) - self.assertRaises((OverflowError, MemoryError, SystemError), - lambda: string_at("foo", sys.maxint - 1)) - - def test_memmove(self): - # large buffers apparently increase the chance that the memory - # is allocated in high address space. - a = create_string_buffer(1000000) - p = b"Hello, World" - result = memmove(a, p, len(p)) - self.assertEqual(a.value, b"Hello, World") - - self.assertEqual(string_at(result), b"Hello, World") - self.assertEqual(string_at(result, 5), b"Hello") - self.assertEqual(string_at(result, 16), b"Hello, World\0\0\0\0") - self.assertEqual(string_at(result, 0), b"") - - def test_memset(self): - a = create_string_buffer(1000000) - result = memset(a, ord('x'), 16) - self.assertEqual(a.value, b"xxxxxxxxxxxxxxxx") - - self.assertEqual(string_at(result), b"xxxxxxxxxxxxxxxx") - self.assertEqual(string_at(a), b"xxxxxxxxxxxxxxxx") - self.assertEqual(string_at(a, 20), b"xxxxxxxxxxxxxxxx\0\0\0\0") - - def test_cast(self): - a = (c_ubyte * 32)(*map(ord, "abcdef")) - self.assertEqual(cast(a, c_char_p).value, b"abcdef") - self.assertEqual(cast(a, POINTER(c_byte))[:7], - [97, 98, 99, 100, 101, 102, 0]) - self.assertEqual(cast(a, POINTER(c_byte))[:7:], - [97, 98, 99, 100, 101, 102, 0]) - self.assertEqual(cast(a, POINTER(c_byte))[6:-1:-1], - [0, 102, 101, 100, 99, 98, 97]) - self.assertEqual(cast(a, POINTER(c_byte))[:7:2], - [97, 99, 101, 0]) - self.assertEqual(cast(a, POINTER(c_byte))[:7:7], - [97]) - - @support.refcount_test - def test_string_at(self): - s = string_at(b"foo bar") - # XXX The following may be wrong, depending on how Python - # manages string instances - self.assertEqual(2, sys.getrefcount(s)) - self.assertTrue(s, "foo bar") - - self.assertEqual(string_at(b"foo bar", 7), b"foo bar") - self.assertEqual(string_at(b"foo bar", 3), b"foo") - - @need_symbol('create_unicode_buffer') - def test_wstring_at(self): - p = create_unicode_buffer("Hello, World") - a = create_unicode_buffer(1000000) - result = memmove(a, p, len(p) * sizeof(c_wchar)) - self.assertEqual(a.value, "Hello, World") - - self.assertEqual(wstring_at(a), "Hello, World") - self.assertEqual(wstring_at(a, 5), "Hello") - self.assertEqual(wstring_at(a, 16), "Hello, World\0\0\0\0") - self.assertEqual(wstring_at(a, 0), "") - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_numbers.py b/Lib/ctypes/test/test_numbers.py deleted file mode 100644 index a5c661b0e97..00000000000 --- a/Lib/ctypes/test/test_numbers.py +++ /dev/null @@ -1,218 +0,0 @@ -from ctypes import * -import unittest -import struct - -def valid_ranges(*types): - # given a sequence of numeric types, collect their _type_ - # attribute, which is a single format character compatible with - # the struct module, use the struct module to calculate the - # minimum and maximum value allowed for this format. - # Returns a list of (min, max) values. - result = [] - for t in types: - fmt = t._type_ - size = struct.calcsize(fmt) - a = struct.unpack(fmt, (b"\x00"*32)[:size])[0] - b = struct.unpack(fmt, (b"\xFF"*32)[:size])[0] - c = struct.unpack(fmt, (b"\x7F"+b"\x00"*32)[:size])[0] - d = struct.unpack(fmt, (b"\x80"+b"\xFF"*32)[:size])[0] - result.append((min(a, b, c, d), max(a, b, c, d))) - return result - -ArgType = type(byref(c_int(0))) - -unsigned_types = [c_ubyte, c_ushort, c_uint, c_ulong] -signed_types = [c_byte, c_short, c_int, c_long, c_longlong] - -bool_types = [] - -float_types = [c_double, c_float] - -try: - c_ulonglong - c_longlong -except NameError: - pass -else: - unsigned_types.append(c_ulonglong) - signed_types.append(c_longlong) - -try: - c_bool -except NameError: - pass -else: - bool_types.append(c_bool) - -unsigned_ranges = valid_ranges(*unsigned_types) -signed_ranges = valid_ranges(*signed_types) -bool_values = [True, False, 0, 1, -1, 5000, 'test', [], [1]] - -################################################################ - -class NumberTestCase(unittest.TestCase): - - def test_default_init(self): - # default values are set to zero - for t in signed_types + unsigned_types + float_types: - self.assertEqual(t().value, 0) - - def test_unsigned_values(self): - # the value given to the constructor is available - # as the 'value' attribute - for t, (l, h) in zip(unsigned_types, unsigned_ranges): - self.assertEqual(t(l).value, l) - self.assertEqual(t(h).value, h) - - def test_signed_values(self): - # see above - for t, (l, h) in zip(signed_types, signed_ranges): - self.assertEqual(t(l).value, l) - self.assertEqual(t(h).value, h) - - def test_bool_values(self): - from operator import truth - for t, v in zip(bool_types, bool_values): - self.assertEqual(t(v).value, truth(v)) - - def test_typeerror(self): - # Only numbers are allowed in the constructor, - # otherwise TypeError is raised - for t in signed_types + unsigned_types + float_types: - self.assertRaises(TypeError, t, "") - self.assertRaises(TypeError, t, None) - - def test_from_param(self): - # the from_param class method attribute always - # returns PyCArgObject instances - for t in signed_types + unsigned_types + float_types: - self.assertEqual(ArgType, type(t.from_param(0))) - - def test_byref(self): - # calling byref returns also a PyCArgObject instance - for t in signed_types + unsigned_types + float_types + bool_types: - parm = byref(t()) - self.assertEqual(ArgType, type(parm)) - - - def test_floats(self): - # c_float and c_double can be created from - # Python int and float - class FloatLike: - def __float__(self): - return 2.0 - f = FloatLike() - for t in float_types: - self.assertEqual(t(2.0).value, 2.0) - self.assertEqual(t(2).value, 2.0) - self.assertEqual(t(2).value, 2.0) - self.assertEqual(t(f).value, 2.0) - - def test_integers(self): - class FloatLike: - def __float__(self): - return 2.0 - f = FloatLike() - class IntLike: - def __int__(self): - return 2 - d = IntLike() - class IndexLike: - def __index__(self): - return 2 - i = IndexLike() - # integers cannot be constructed from floats, - # but from integer-like objects - for t in signed_types + unsigned_types: - self.assertRaises(TypeError, t, 3.14) - self.assertRaises(TypeError, t, f) - self.assertRaises(TypeError, t, d) - self.assertEqual(t(i).value, 2) - - def test_sizes(self): - for t in signed_types + unsigned_types + float_types + bool_types: - try: - size = struct.calcsize(t._type_) - except struct.error: - continue - # sizeof of the type... - self.assertEqual(sizeof(t), size) - # and sizeof of an instance - self.assertEqual(sizeof(t()), size) - - def test_alignments(self): - for t in signed_types + unsigned_types + float_types: - code = t._type_ # the typecode - align = struct.calcsize("c%c" % code) - struct.calcsize(code) - - # alignment of the type... - self.assertEqual((code, alignment(t)), - (code, align)) - # and alignment of an instance - self.assertEqual((code, alignment(t())), - (code, align)) - - def test_int_from_address(self): - from array import array - for t in signed_types + unsigned_types: - # the array module doesn't support all format codes - # (no 'q' or 'Q') - try: - array(t._type_) - except ValueError: - continue - a = array(t._type_, [100]) - - # v now is an integer at an 'external' memory location - v = t.from_address(a.buffer_info()[0]) - self.assertEqual(v.value, a[0]) - self.assertEqual(type(v), t) - - # changing the value at the memory location changes v's value also - a[0] = 42 - self.assertEqual(v.value, a[0]) - - - def test_float_from_address(self): - from array import array - for t in float_types: - a = array(t._type_, [3.14]) - v = t.from_address(a.buffer_info()[0]) - self.assertEqual(v.value, a[0]) - self.assertIs(type(v), t) - a[0] = 2.3456e17 - self.assertEqual(v.value, a[0]) - self.assertIs(type(v), t) - - def test_char_from_address(self): - from ctypes import c_char - from array import array - - a = array('b', [0]) - a[0] = ord('x') - v = c_char.from_address(a.buffer_info()[0]) - self.assertEqual(v.value, b'x') - self.assertIs(type(v), c_char) - - a[0] = ord('?') - self.assertEqual(v.value, b'?') - - def test_init(self): - # c_int() can be initialized from Python's int, and c_int. - # Not from c_long or so, which seems strange, abc should - # probably be changed: - self.assertRaises(TypeError, c_int, c_long(42)) - - def test_float_overflow(self): - import sys - big_int = int(sys.float_info.max) * 2 - for t in float_types + [c_longdouble]: - self.assertRaises(OverflowError, t, big_int) - if (hasattr(t, "__ctype_be__")): - self.assertRaises(OverflowError, t.__ctype_be__, big_int) - if (hasattr(t, "__ctype_le__")): - self.assertRaises(OverflowError, t.__ctype_le__, big_int) - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_objects.py b/Lib/ctypes/test/test_objects.py deleted file mode 100644 index 19e3dc1f2d7..00000000000 --- a/Lib/ctypes/test/test_objects.py +++ /dev/null @@ -1,67 +0,0 @@ -r''' -This tests the '_objects' attribute of ctypes instances. '_objects' -holds references to objects that must be kept alive as long as the -ctypes instance, to make sure that the memory buffer is valid. - -WARNING: The '_objects' attribute is exposed ONLY for debugging ctypes itself, -it MUST NEVER BE MODIFIED! - -'_objects' is initialized to a dictionary on first use, before that it -is None. - -Here is an array of string pointers: - ->>> from ctypes import * ->>> array = (c_char_p * 5)() ->>> print(array._objects) -None ->>> - -The memory block stores pointers to strings, and the strings itself -assigned from Python must be kept. - ->>> array[4] = b'foo bar' ->>> array._objects -{'4': b'foo bar'} ->>> array[4] -b'foo bar' ->>> - -It gets more complicated when the ctypes instance itself is contained -in a 'base' object. - ->>> class X(Structure): -... _fields_ = [("x", c_int), ("y", c_int), ("array", c_char_p * 5)] -... ->>> x = X() ->>> print(x._objects) -None ->>> - -The'array' attribute of the 'x' object shares part of the memory buffer -of 'x' ('_b_base_' is either None, or the root object owning the memory block): - ->>> print(x.array._b_base_) # doctest: +ELLIPSIS -<ctypes.test.test_objects.X object at 0x...> ->>> - ->>> x.array[0] = b'spam spam spam' ->>> x._objects -{'0:2': b'spam spam spam'} ->>> x.array._b_base_._objects -{'0:2': b'spam spam spam'} ->>> - -''' - -import unittest, doctest - -import ctypes.test.test_objects - -class TestCase(unittest.TestCase): - def test(self): - failures, tests = doctest.testmod(ctypes.test.test_objects) - self.assertFalse(failures, 'doctests failed, see output above') - -if __name__ == '__main__': - doctest.testmod(ctypes.test.test_objects) diff --git a/Lib/ctypes/test/test_parameters.py b/Lib/ctypes/test/test_parameters.py deleted file mode 100644 index 38af7ac13d7..00000000000 --- a/Lib/ctypes/test/test_parameters.py +++ /dev/null @@ -1,250 +0,0 @@ -import unittest -from ctypes.test import need_symbol -import test.support - -class SimpleTypesTestCase(unittest.TestCase): - - def setUp(self): - import ctypes - try: - from _ctypes import set_conversion_mode - except ImportError: - pass - else: - self.prev_conv_mode = set_conversion_mode("ascii", "strict") - - def tearDown(self): - try: - from _ctypes import set_conversion_mode - except ImportError: - pass - else: - set_conversion_mode(*self.prev_conv_mode) - - def test_subclasses(self): - from ctypes import c_void_p, c_char_p - # ctypes 0.9.5 and before did overwrite from_param in SimpleType_new - class CVOIDP(c_void_p): - def from_param(cls, value): - return value * 2 - from_param = classmethod(from_param) - - class CCHARP(c_char_p): - def from_param(cls, value): - return value * 4 - from_param = classmethod(from_param) - - self.assertEqual(CVOIDP.from_param("abc"), "abcabc") - self.assertEqual(CCHARP.from_param("abc"), "abcabcabcabc") - - @need_symbol('c_wchar_p') - def test_subclasses_c_wchar_p(self): - from ctypes import c_wchar_p - - class CWCHARP(c_wchar_p): - def from_param(cls, value): - return value * 3 - from_param = classmethod(from_param) - - self.assertEqual(CWCHARP.from_param("abc"), "abcabcabc") - - # XXX Replace by c_char_p tests - def test_cstrings(self): - from ctypes import c_char_p - - # c_char_p.from_param on a Python String packs the string - # into a cparam object - s = b"123" - self.assertIs(c_char_p.from_param(s)._obj, s) - - # new in 0.9.1: convert (encode) unicode to ascii - self.assertEqual(c_char_p.from_param(b"123")._obj, b"123") - self.assertRaises(TypeError, c_char_p.from_param, "123\377") - self.assertRaises(TypeError, c_char_p.from_param, 42) - - # calling c_char_p.from_param with a c_char_p instance - # returns the argument itself: - a = c_char_p(b"123") - self.assertIs(c_char_p.from_param(a), a) - - @need_symbol('c_wchar_p') - def test_cw_strings(self): - from ctypes import c_wchar_p - - c_wchar_p.from_param("123") - - self.assertRaises(TypeError, c_wchar_p.from_param, 42) - self.assertRaises(TypeError, c_wchar_p.from_param, b"123\377") - - pa = c_wchar_p.from_param(c_wchar_p("123")) - self.assertEqual(type(pa), c_wchar_p) - - def test_int_pointers(self): - from ctypes import c_short, c_uint, c_int, c_long, POINTER, pointer - LPINT = POINTER(c_int) - -## p = pointer(c_int(42)) -## x = LPINT.from_param(p) - x = LPINT.from_param(pointer(c_int(42))) - self.assertEqual(x.contents.value, 42) - self.assertEqual(LPINT(c_int(42)).contents.value, 42) - - self.assertEqual(LPINT.from_param(None), None) - - if c_int != c_long: - self.assertRaises(TypeError, LPINT.from_param, pointer(c_long(42))) - self.assertRaises(TypeError, LPINT.from_param, pointer(c_uint(42))) - self.assertRaises(TypeError, LPINT.from_param, pointer(c_short(42))) - - def test_byref_pointer(self): - # The from_param class method of POINTER(typ) classes accepts what is - # returned by byref(obj), it type(obj) == typ - from ctypes import c_short, c_uint, c_int, c_long, POINTER, byref - LPINT = POINTER(c_int) - - LPINT.from_param(byref(c_int(42))) - - self.assertRaises(TypeError, LPINT.from_param, byref(c_short(22))) - if c_int != c_long: - self.assertRaises(TypeError, LPINT.from_param, byref(c_long(22))) - self.assertRaises(TypeError, LPINT.from_param, byref(c_uint(22))) - - def test_byref_pointerpointer(self): - # See above - from ctypes import c_short, c_uint, c_int, c_long, pointer, POINTER, byref - - LPLPINT = POINTER(POINTER(c_int)) - LPLPINT.from_param(byref(pointer(c_int(42)))) - - self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_short(22)))) - if c_int != c_long: - self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_long(22)))) - self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_uint(22)))) - - def test_array_pointers(self): - from ctypes import c_short, c_uint, c_int, c_long, POINTER - INTARRAY = c_int * 3 - ia = INTARRAY() - self.assertEqual(len(ia), 3) - self.assertEqual([ia[i] for i in range(3)], [0, 0, 0]) - - # Pointers are only compatible with arrays containing items of - # the same type! - LPINT = POINTER(c_int) - LPINT.from_param((c_int*3)()) - self.assertRaises(TypeError, LPINT.from_param, c_short*3) - self.assertRaises(TypeError, LPINT.from_param, c_long*3) - self.assertRaises(TypeError, LPINT.from_param, c_uint*3) - - def test_noctypes_argtype(self): - import _ctypes_test - from ctypes import CDLL, c_void_p, ArgumentError - - func = CDLL(_ctypes_test.__file__)._testfunc_p_p - func.restype = c_void_p - # TypeError: has no from_param method - self.assertRaises(TypeError, setattr, func, "argtypes", (object,)) - - class Adapter(object): - def from_param(cls, obj): - return None - - func.argtypes = (Adapter(),) - self.assertEqual(func(None), None) - self.assertEqual(func(object()), None) - - class Adapter(object): - def from_param(cls, obj): - return obj - - func.argtypes = (Adapter(),) - # don't know how to convert parameter 1 - self.assertRaises(ArgumentError, func, object()) - self.assertEqual(func(c_void_p(42)), 42) - - class Adapter(object): - def from_param(cls, obj): - raise ValueError(obj) - - func.argtypes = (Adapter(),) - # ArgumentError: argument 1: ValueError: 99 - self.assertRaises(ArgumentError, func, 99) - - def test_abstract(self): - from ctypes import (Array, Structure, Union, _Pointer, - _SimpleCData, _CFuncPtr) - - self.assertRaises(TypeError, Array.from_param, 42) - self.assertRaises(TypeError, Structure.from_param, 42) - self.assertRaises(TypeError, Union.from_param, 42) - self.assertRaises(TypeError, _CFuncPtr.from_param, 42) - self.assertRaises(TypeError, _Pointer.from_param, 42) - self.assertRaises(TypeError, _SimpleCData.from_param, 42) - - @test.support.cpython_only - def test_issue31311(self): - # __setstate__ should neither raise a SystemError nor crash in case - # of a bad __dict__. - from ctypes import Structure - - class BadStruct(Structure): - @property - def __dict__(self): - pass - with self.assertRaises(TypeError): - BadStruct().__setstate__({}, b'foo') - - class WorseStruct(Structure): - @property - def __dict__(self): - 1/0 - with self.assertRaises(ZeroDivisionError): - WorseStruct().__setstate__({}, b'foo') - - def test_parameter_repr(self): - from ctypes import ( - c_bool, - c_char, - c_wchar, - c_byte, - c_ubyte, - c_short, - c_ushort, - c_int, - c_uint, - c_long, - c_ulong, - c_longlong, - c_ulonglong, - c_float, - c_double, - c_longdouble, - c_char_p, - c_wchar_p, - c_void_p, - ) - self.assertRegex(repr(c_bool.from_param(True)), r"^<cparam '\?' at 0x[A-Fa-f0-9]+>$") - self.assertEqual(repr(c_char.from_param(97)), "<cparam 'c' ('a')>") - self.assertRegex(repr(c_wchar.from_param('a')), r"^<cparam 'u' at 0x[A-Fa-f0-9]+>$") - self.assertEqual(repr(c_byte.from_param(98)), "<cparam 'b' (98)>") - self.assertEqual(repr(c_ubyte.from_param(98)), "<cparam 'B' (98)>") - self.assertEqual(repr(c_short.from_param(511)), "<cparam 'h' (511)>") - self.assertEqual(repr(c_ushort.from_param(511)), "<cparam 'H' (511)>") - self.assertRegex(repr(c_int.from_param(20000)), r"^<cparam '[li]' \(20000\)>$") - self.assertRegex(repr(c_uint.from_param(20000)), r"^<cparam '[LI]' \(20000\)>$") - self.assertRegex(repr(c_long.from_param(20000)), r"^<cparam '[li]' \(20000\)>$") - self.assertRegex(repr(c_ulong.from_param(20000)), r"^<cparam '[LI]' \(20000\)>$") - self.assertRegex(repr(c_longlong.from_param(20000)), r"^<cparam '[liq]' \(20000\)>$") - self.assertRegex(repr(c_ulonglong.from_param(20000)), r"^<cparam '[LIQ]' \(20000\)>$") - self.assertEqual(repr(c_float.from_param(1.5)), "<cparam 'f' (1.5)>") - self.assertEqual(repr(c_double.from_param(1.5)), "<cparam 'd' (1.5)>") - self.assertEqual(repr(c_double.from_param(1e300)), "<cparam 'd' (1e+300)>") - self.assertRegex(repr(c_longdouble.from_param(1.5)), r"^<cparam ('d' \(1.5\)|'g' at 0x[A-Fa-f0-9]+)>$") - self.assertRegex(repr(c_char_p.from_param(b'hihi')), r"^<cparam 'z' \(0x[A-Fa-f0-9]+\)>$") - self.assertRegex(repr(c_wchar_p.from_param('hihi')), r"^<cparam 'Z' \(0x[A-Fa-f0-9]+\)>$") - self.assertRegex(repr(c_void_p.from_param(0x12)), r"^<cparam 'P' \(0x0*12\)>$") - -################################################################ - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_pep3118.py b/Lib/ctypes/test/test_pep3118.py deleted file mode 100644 index efffc80a66f..00000000000 --- a/Lib/ctypes/test/test_pep3118.py +++ /dev/null @@ -1,235 +0,0 @@ -import unittest -from ctypes import * -import re, sys - -if sys.byteorder == "little": - THIS_ENDIAN = "<" - OTHER_ENDIAN = ">" -else: - THIS_ENDIAN = ">" - OTHER_ENDIAN = "<" - -def normalize(format): - # Remove current endian specifier and white space from a format - # string - if format is None: - return "" - format = format.replace(OTHER_ENDIAN, THIS_ENDIAN) - return re.sub(r"\s", "", format) - -class Test(unittest.TestCase): - - def test_native_types(self): - for tp, fmt, shape, itemtp in native_types: - ob = tp() - v = memoryview(ob) - try: - self.assertEqual(normalize(v.format), normalize(fmt)) - if shape: - self.assertEqual(len(v), shape[0]) - else: - self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob)) - self.assertEqual(v.itemsize, sizeof(itemtp)) - self.assertEqual(v.shape, shape) - # XXX Issue #12851: PyCData_NewGetBuffer() must provide strides - # if requested. memoryview currently reconstructs missing - # stride information, so this assert will fail. - # self.assertEqual(v.strides, ()) - - # they are always read/write - self.assertFalse(v.readonly) - - if v.shape: - n = 1 - for dim in v.shape: - n = n * dim - self.assertEqual(n * v.itemsize, len(v.tobytes())) - except: - # so that we can see the failing type - print(tp) - raise - - def test_endian_types(self): - for tp, fmt, shape, itemtp in endian_types: - ob = tp() - v = memoryview(ob) - try: - self.assertEqual(v.format, fmt) - if shape: - self.assertEqual(len(v), shape[0]) - else: - self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob)) - self.assertEqual(v.itemsize, sizeof(itemtp)) - self.assertEqual(v.shape, shape) - # XXX Issue #12851 - # self.assertEqual(v.strides, ()) - - # they are always read/write - self.assertFalse(v.readonly) - - if v.shape: - n = 1 - for dim in v.shape: - n = n * dim - self.assertEqual(n, len(v)) - except: - # so that we can see the failing type - print(tp) - raise - -# define some structure classes - -class Point(Structure): - _fields_ = [("x", c_long), ("y", c_long)] - -class PackedPoint(Structure): - _pack_ = 2 - _fields_ = [("x", c_long), ("y", c_long)] - -class Point2(Structure): - pass -Point2._fields_ = [("x", c_long), ("y", c_long)] - -class EmptyStruct(Structure): - _fields_ = [] - -class aUnion(Union): - _fields_ = [("a", c_int)] - -class StructWithArrays(Structure): - _fields_ = [("x", c_long * 3 * 2), ("y", Point * 4)] - -class Incomplete(Structure): - pass - -class Complete(Structure): - pass -PComplete = POINTER(Complete) -Complete._fields_ = [("a", c_long)] - -################################################################ -# -# This table contains format strings as they look on little endian -# machines. The test replaces '<' with '>' on big endian machines. -# - -# Platform-specific type codes -s_bool = {1: '?', 2: 'H', 4: 'L', 8: 'Q'}[sizeof(c_bool)] -s_short = {2: 'h', 4: 'l', 8: 'q'}[sizeof(c_short)] -s_ushort = {2: 'H', 4: 'L', 8: 'Q'}[sizeof(c_ushort)] -s_int = {2: 'h', 4: 'i', 8: 'q'}[sizeof(c_int)] -s_uint = {2: 'H', 4: 'I', 8: 'Q'}[sizeof(c_uint)] -s_long = {4: 'l', 8: 'q'}[sizeof(c_long)] -s_ulong = {4: 'L', 8: 'Q'}[sizeof(c_ulong)] -s_longlong = "q" -s_ulonglong = "Q" -s_float = "f" -s_double = "d" -s_longdouble = "g" - -# Alias definitions in ctypes/__init__.py -if c_int is c_long: - s_int = s_long -if c_uint is c_ulong: - s_uint = s_ulong -if c_longlong is c_long: - s_longlong = s_long -if c_ulonglong is c_ulong: - s_ulonglong = s_ulong -if c_longdouble is c_double: - s_longdouble = s_double - - -native_types = [ - # type format shape calc itemsize - - ## simple types - - (c_char, "<c", (), c_char), - (c_byte, "<b", (), c_byte), - (c_ubyte, "<B", (), c_ubyte), - (c_short, "<" + s_short, (), c_short), - (c_ushort, "<" + s_ushort, (), c_ushort), - - (c_int, "<" + s_int, (), c_int), - (c_uint, "<" + s_uint, (), c_uint), - - (c_long, "<" + s_long, (), c_long), - (c_ulong, "<" + s_ulong, (), c_ulong), - - (c_longlong, "<" + s_longlong, (), c_longlong), - (c_ulonglong, "<" + s_ulonglong, (), c_ulonglong), - - (c_float, "<f", (), c_float), - (c_double, "<d", (), c_double), - - (c_longdouble, "<" + s_longdouble, (), c_longdouble), - - (c_bool, "<" + s_bool, (), c_bool), - (py_object, "<O", (), py_object), - - ## pointers - - (POINTER(c_byte), "&<b", (), POINTER(c_byte)), - (POINTER(POINTER(c_long)), "&&<" + s_long, (), POINTER(POINTER(c_long))), - - ## arrays and pointers - - (c_double * 4, "<d", (4,), c_double), - (c_double * 0, "<d", (0,), c_double), - (c_float * 4 * 3 * 2, "<f", (2,3,4), c_float), - (c_float * 4 * 0 * 2, "<f", (2,0,4), c_float), - (POINTER(c_short) * 2, "&<" + s_short, (2,), POINTER(c_short)), - (POINTER(c_short) * 2 * 3, "&<" + s_short, (3,2,), POINTER(c_short)), - (POINTER(c_short * 2), "&(2)<" + s_short, (), POINTER(c_short)), - - ## structures and unions - - (Point, "T{<l:x:<l:y:}".replace('l', s_long), (), Point), - # packed structures do not implement the pep - (PackedPoint, "B", (), PackedPoint), - (Point2, "T{<l:x:<l:y:}".replace('l', s_long), (), Point2), - (EmptyStruct, "T{}", (), EmptyStruct), - # the pep doesn't support unions - (aUnion, "B", (), aUnion), - # structure with sub-arrays - (StructWithArrays, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (), StructWithArrays), - (StructWithArrays * 3, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (3,), StructWithArrays), - - ## pointer to incomplete structure - (Incomplete, "B", (), Incomplete), - (POINTER(Incomplete), "&B", (), POINTER(Incomplete)), - - # 'Complete' is a structure that starts incomplete, but is completed after the - # pointer type to it has been created. - (Complete, "T{<l:a:}".replace('l', s_long), (), Complete), - # Unfortunately the pointer format string is not fixed... - (POINTER(Complete), "&B", (), POINTER(Complete)), - - ## other - - # function signatures are not implemented - (CFUNCTYPE(None), "X{}", (), CFUNCTYPE(None)), - - ] - -class BEPoint(BigEndianStructure): - _fields_ = [("x", c_long), ("y", c_long)] - -class LEPoint(LittleEndianStructure): - _fields_ = [("x", c_long), ("y", c_long)] - -################################################################ -# -# This table contains format strings as they really look, on both big -# and little endian machines. -# -endian_types = [ - (BEPoint, "T{>l:x:>l:y:}".replace('l', s_long), (), BEPoint), - (LEPoint, "T{<l:x:<l:y:}".replace('l', s_long), (), LEPoint), - (POINTER(BEPoint), "&T{>l:x:>l:y:}".replace('l', s_long), (), POINTER(BEPoint)), - (POINTER(LEPoint), "&T{<l:x:<l:y:}".replace('l', s_long), (), POINTER(LEPoint)), - ] - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_pickling.py b/Lib/ctypes/test/test_pickling.py deleted file mode 100644 index c4a79b97793..00000000000 --- a/Lib/ctypes/test/test_pickling.py +++ /dev/null @@ -1,81 +0,0 @@ -import unittest -import pickle -from ctypes import * -import _ctypes_test -dll = CDLL(_ctypes_test.__file__) - -class X(Structure): - _fields_ = [("a", c_int), ("b", c_double)] - init_called = 0 - def __init__(self, *args, **kw): - X.init_called += 1 - self.x = 42 - -class Y(X): - _fields_ = [("str", c_char_p)] - -class PickleTest: - def dumps(self, item): - return pickle.dumps(item, self.proto) - - def loads(self, item): - return pickle.loads(item) - - def test_simple(self): - for src in [ - c_int(42), - c_double(3.14), - ]: - dst = self.loads(self.dumps(src)) - self.assertEqual(src.__dict__, dst.__dict__) - self.assertEqual(memoryview(src).tobytes(), - memoryview(dst).tobytes()) - - def test_struct(self): - X.init_called = 0 - - x = X() - x.a = 42 - self.assertEqual(X.init_called, 1) - - y = self.loads(self.dumps(x)) - - # loads must NOT call __init__ - self.assertEqual(X.init_called, 1) - - # ctypes instances are identical when the instance __dict__ - # and the memory buffer are identical - self.assertEqual(y.__dict__, x.__dict__) - self.assertEqual(memoryview(y).tobytes(), - memoryview(x).tobytes()) - - def test_unpickable(self): - # ctypes objects that are pointers or contain pointers are - # unpickable. - self.assertRaises(ValueError, lambda: self.dumps(Y())) - - prototype = CFUNCTYPE(c_int) - - for item in [ - c_char_p(), - c_wchar_p(), - c_void_p(), - pointer(c_int(42)), - dll._testfunc_p_p, - prototype(lambda: 42), - ]: - self.assertRaises(ValueError, lambda: self.dumps(item)) - - def test_wchar(self): - self.dumps(c_char(b"x")) - # Issue 5049 - self.dumps(c_wchar("x")) - -for proto in range(pickle.HIGHEST_PROTOCOL + 1): - name = 'PickleTest_%s' % proto - globals()[name] = type(name, - (PickleTest, unittest.TestCase), - {'proto': proto}) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_pointers.py b/Lib/ctypes/test/test_pointers.py deleted file mode 100644 index e97515879f1..00000000000 --- a/Lib/ctypes/test/test_pointers.py +++ /dev/null @@ -1,223 +0,0 @@ -import unittest, sys - -from ctypes import * -import _ctypes_test - -ctype_types = [c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, - c_long, c_ulong, c_longlong, c_ulonglong, c_double, c_float] -python_types = [int, int, int, int, int, int, - int, int, int, int, float, float] - -class PointersTestCase(unittest.TestCase): - - def test_pointer_crash(self): - - class A(POINTER(c_ulong)): - pass - - POINTER(c_ulong)(c_ulong(22)) - # Pointer can't set contents: has no _type_ - self.assertRaises(TypeError, A, c_ulong(33)) - - def test_pass_pointers(self): - dll = CDLL(_ctypes_test.__file__) - func = dll._testfunc_p_p - if sizeof(c_longlong) == sizeof(c_void_p): - func.restype = c_longlong - else: - func.restype = c_long - - i = c_int(12345678) -## func.argtypes = (POINTER(c_int),) - address = func(byref(i)) - self.assertEqual(c_int.from_address(address).value, 12345678) - - func.restype = POINTER(c_int) - res = func(pointer(i)) - self.assertEqual(res.contents.value, 12345678) - self.assertEqual(res[0], 12345678) - - def test_change_pointers(self): - dll = CDLL(_ctypes_test.__file__) - func = dll._testfunc_p_p - - i = c_int(87654) - func.restype = POINTER(c_int) - func.argtypes = (POINTER(c_int),) - - res = func(pointer(i)) - self.assertEqual(res[0], 87654) - self.assertEqual(res.contents.value, 87654) - - # C code: *res = 54345 - res[0] = 54345 - self.assertEqual(i.value, 54345) - - # C code: - # int x = 12321; - # res = &x - x = c_int(12321) - res.contents = x - self.assertEqual(i.value, 54345) - - x.value = -99 - self.assertEqual(res.contents.value, -99) - - def test_callbacks_with_pointers(self): - # a function type receiving a pointer - PROTOTYPE = CFUNCTYPE(c_int, POINTER(c_int)) - - self.result = [] - - def func(arg): - for i in range(10): -## print arg[i], - self.result.append(arg[i]) -## print - return 0 - callback = PROTOTYPE(func) - - dll = CDLL(_ctypes_test.__file__) - # This function expects a function pointer, - # and calls this with an integer pointer as parameter. - # The int pointer points to a table containing the numbers 1..10 - doit = dll._testfunc_callback_with_pointer - -## i = c_int(42) -## callback(byref(i)) -## self.assertEqual(i.value, 84) - - doit(callback) -## print self.result - doit(callback) -## print self.result - - def test_basics(self): - from operator import delitem - for ct, pt in zip(ctype_types, python_types): - i = ct(42) - p = pointer(i) -## print type(p.contents), ct - self.assertIs(type(p.contents), ct) - # p.contents is the same as p[0] -## print p.contents -## self.assertEqual(p.contents, 42) -## self.assertEqual(p[0], 42) - - self.assertRaises(TypeError, delitem, p, 0) - - def test_from_address(self): - from array import array - a = array('i', [100, 200, 300, 400, 500]) - addr = a.buffer_info()[0] - - p = POINTER(POINTER(c_int)) -## print dir(p) -## print p.from_address -## print p.from_address(addr)[0][0] - - def test_other(self): - class Table(Structure): - _fields_ = [("a", c_int), - ("b", c_int), - ("c", c_int)] - - pt = pointer(Table(1, 2, 3)) - - self.assertEqual(pt.contents.a, 1) - self.assertEqual(pt.contents.b, 2) - self.assertEqual(pt.contents.c, 3) - - pt.contents.c = 33 - - from ctypes import _pointer_type_cache - del _pointer_type_cache[Table] - - def test_basic(self): - p = pointer(c_int(42)) - # Although a pointer can be indexed, it has no length - self.assertRaises(TypeError, len, p) - self.assertEqual(p[0], 42) - self.assertEqual(p[0:1], [42]) - self.assertEqual(p.contents.value, 42) - - def test_charpp(self): - """Test that a character pointer-to-pointer is correctly passed""" - dll = CDLL(_ctypes_test.__file__) - func = dll._testfunc_c_p_p - func.restype = c_char_p - argv = (c_char_p * 2)() - argc = c_int( 2 ) - argv[0] = b'hello' - argv[1] = b'world' - result = func( byref(argc), argv ) - self.assertEqual(result, b'world') - - def test_bug_1467852(self): - # http://sourceforge.net/tracker/?func=detail&atid=532154&aid=1467852&group_id=71702 - x = c_int(5) - dummy = [] - for i in range(32000): - dummy.append(c_int(i)) - y = c_int(6) - p = pointer(x) - pp = pointer(p) - q = pointer(y) - pp[0] = q # <== - self.assertEqual(p[0], 6) - def test_c_void_p(self): - # http://sourceforge.net/tracker/?func=detail&aid=1518190&group_id=5470&atid=105470 - if sizeof(c_void_p) == 4: - self.assertEqual(c_void_p(0xFFFFFFFF).value, - c_void_p(-1).value) - self.assertEqual(c_void_p(0xFFFFFFFFFFFFFFFF).value, - c_void_p(-1).value) - elif sizeof(c_void_p) == 8: - self.assertEqual(c_void_p(0xFFFFFFFF).value, - 0xFFFFFFFF) - self.assertEqual(c_void_p(0xFFFFFFFFFFFFFFFF).value, - c_void_p(-1).value) - self.assertEqual(c_void_p(0xFFFFFFFFFFFFFFFFFFFFFFFF).value, - c_void_p(-1).value) - - self.assertRaises(TypeError, c_void_p, 3.14) # make sure floats are NOT accepted - self.assertRaises(TypeError, c_void_p, object()) # nor other objects - - def test_pointers_bool(self): - # NULL pointers have a boolean False value, non-NULL pointers True. - self.assertEqual(bool(POINTER(c_int)()), False) - self.assertEqual(bool(pointer(c_int())), True) - - self.assertEqual(bool(CFUNCTYPE(None)(0)), False) - self.assertEqual(bool(CFUNCTYPE(None)(42)), True) - - # COM methods are boolean True: - if sys.platform == "win32": - mth = WINFUNCTYPE(None)(42, "name", (), None) - self.assertEqual(bool(mth), True) - - def test_pointer_type_name(self): - LargeNamedType = type('T' * 2 ** 25, (Structure,), {}) - self.assertTrue(POINTER(LargeNamedType)) - - # to not leak references, we must clean _pointer_type_cache - from ctypes import _pointer_type_cache - del _pointer_type_cache[LargeNamedType] - - def test_pointer_type_str_name(self): - large_string = 'T' * 2 ** 25 - P = POINTER(large_string) - self.assertTrue(P) - - # to not leak references, we must clean _pointer_type_cache - from ctypes import _pointer_type_cache - del _pointer_type_cache[id(P)] - - def test_abstract(self): - from ctypes import _Pointer - - self.assertRaises(TypeError, _Pointer.set_type, 42) - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_prototypes.py b/Lib/ctypes/test/test_prototypes.py deleted file mode 100644 index cd0c649de3e..00000000000 --- a/Lib/ctypes/test/test_prototypes.py +++ /dev/null @@ -1,222 +0,0 @@ -from ctypes import * -from ctypes.test import need_symbol -import unittest - -# IMPORTANT INFO: -# -# Consider this call: -# func.restype = c_char_p -# func(c_char_p("123")) -# It returns -# "123" -# -# WHY IS THIS SO? -# -# argument tuple (c_char_p("123"), ) is destroyed after the function -# func is called, but NOT before the result is actually built. -# -# If the arglist would be destroyed BEFORE the result has been built, -# the c_char_p("123") object would already have a zero refcount, -# and the pointer passed to (and returned by) the function would -# probably point to deallocated space. -# -# In this case, there would have to be an additional reference to the argument... - -import _ctypes_test -testdll = CDLL(_ctypes_test.__file__) - -# Return machine address `a` as a (possibly long) non-negative integer. -# Starting with Python 2.5, id(anything) is always non-negative, and -# the ctypes addressof() inherits that via PyLong_FromVoidPtr(). -def positive_address(a): - if a >= 0: - return a - # View the bits in `a` as unsigned instead. - import struct - num_bits = struct.calcsize("P") * 8 # num bits in native machine address - a += 1 << num_bits - assert a >= 0 - return a - -def c_wbuffer(init): - n = len(init) + 1 - return (c_wchar * n)(*init) - -class CharPointersTestCase(unittest.TestCase): - - def setUp(self): - func = testdll._testfunc_p_p - func.restype = c_long - func.argtypes = None - - def test_paramflags(self): - # function returns c_void_p result, - # and has a required parameter named 'input' - prototype = CFUNCTYPE(c_void_p, c_void_p) - func = prototype(("_testfunc_p_p", testdll), - ((1, "input"),)) - - try: - func() - except TypeError as details: - self.assertEqual(str(details), "required argument 'input' missing") - else: - self.fail("TypeError not raised") - - self.assertEqual(func(None), None) - self.assertEqual(func(input=None), None) - - - def test_int_pointer_arg(self): - func = testdll._testfunc_p_p - if sizeof(c_longlong) == sizeof(c_void_p): - func.restype = c_longlong - else: - func.restype = c_long - self.assertEqual(0, func(0)) - - ci = c_int(0) - - func.argtypes = POINTER(c_int), - self.assertEqual(positive_address(addressof(ci)), - positive_address(func(byref(ci)))) - - func.argtypes = c_char_p, - self.assertRaises(ArgumentError, func, byref(ci)) - - func.argtypes = POINTER(c_short), - self.assertRaises(ArgumentError, func, byref(ci)) - - func.argtypes = POINTER(c_double), - self.assertRaises(ArgumentError, func, byref(ci)) - - def test_POINTER_c_char_arg(self): - func = testdll._testfunc_p_p - func.restype = c_char_p - func.argtypes = POINTER(c_char), - - self.assertEqual(None, func(None)) - self.assertEqual(b"123", func(b"123")) - self.assertEqual(None, func(c_char_p(None))) - self.assertEqual(b"123", func(c_char_p(b"123"))) - - self.assertEqual(b"123", func(c_buffer(b"123"))) - ca = c_char(b"a") - self.assertEqual(ord(b"a"), func(pointer(ca))[0]) - self.assertEqual(ord(b"a"), func(byref(ca))[0]) - - def test_c_char_p_arg(self): - func = testdll._testfunc_p_p - func.restype = c_char_p - func.argtypes = c_char_p, - - self.assertEqual(None, func(None)) - self.assertEqual(b"123", func(b"123")) - self.assertEqual(None, func(c_char_p(None))) - self.assertEqual(b"123", func(c_char_p(b"123"))) - - self.assertEqual(b"123", func(c_buffer(b"123"))) - ca = c_char(b"a") - self.assertEqual(ord(b"a"), func(pointer(ca))[0]) - self.assertEqual(ord(b"a"), func(byref(ca))[0]) - - def test_c_void_p_arg(self): - func = testdll._testfunc_p_p - func.restype = c_char_p - func.argtypes = c_void_p, - - self.assertEqual(None, func(None)) - self.assertEqual(b"123", func(b"123")) - self.assertEqual(b"123", func(c_char_p(b"123"))) - self.assertEqual(None, func(c_char_p(None))) - - self.assertEqual(b"123", func(c_buffer(b"123"))) - ca = c_char(b"a") - self.assertEqual(ord(b"a"), func(pointer(ca))[0]) - self.assertEqual(ord(b"a"), func(byref(ca))[0]) - - func(byref(c_int())) - func(pointer(c_int())) - func((c_int * 3)()) - - @need_symbol('c_wchar_p') - def test_c_void_p_arg_with_c_wchar_p(self): - func = testdll._testfunc_p_p - func.restype = c_wchar_p - func.argtypes = c_void_p, - - self.assertEqual(None, func(c_wchar_p(None))) - self.assertEqual("123", func(c_wchar_p("123"))) - - def test_instance(self): - func = testdll._testfunc_p_p - func.restype = c_void_p - - class X: - _as_parameter_ = None - - func.argtypes = c_void_p, - self.assertEqual(None, func(X())) - - func.argtypes = None - self.assertEqual(None, func(X())) - -@need_symbol('c_wchar') -class WCharPointersTestCase(unittest.TestCase): - - def setUp(self): - func = testdll._testfunc_p_p - func.restype = c_int - func.argtypes = None - - - def test_POINTER_c_wchar_arg(self): - func = testdll._testfunc_p_p - func.restype = c_wchar_p - func.argtypes = POINTER(c_wchar), - - self.assertEqual(None, func(None)) - self.assertEqual("123", func("123")) - self.assertEqual(None, func(c_wchar_p(None))) - self.assertEqual("123", func(c_wchar_p("123"))) - - self.assertEqual("123", func(c_wbuffer("123"))) - ca = c_wchar("a") - self.assertEqual("a", func(pointer(ca))[0]) - self.assertEqual("a", func(byref(ca))[0]) - - def test_c_wchar_p_arg(self): - func = testdll._testfunc_p_p - func.restype = c_wchar_p - func.argtypes = c_wchar_p, - - c_wchar_p.from_param("123") - - self.assertEqual(None, func(None)) - self.assertEqual("123", func("123")) - self.assertEqual(None, func(c_wchar_p(None))) - self.assertEqual("123", func(c_wchar_p("123"))) - - # XXX Currently, these raise TypeErrors, although they shouldn't: - self.assertEqual("123", func(c_wbuffer("123"))) - ca = c_wchar("a") - self.assertEqual("a", func(pointer(ca))[0]) - self.assertEqual("a", func(byref(ca))[0]) - -class ArrayTest(unittest.TestCase): - def test(self): - func = testdll._testfunc_ai8 - func.restype = POINTER(c_int) - func.argtypes = c_int * 8, - - func((c_int * 8)(1, 2, 3, 4, 5, 6, 7, 8)) - - # This did crash before: - - def func(): pass - CFUNCTYPE(None, c_int * 3)(func) - -################################################################ - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_python_api.py b/Lib/ctypes/test/test_python_api.py deleted file mode 100644 index 49571f97bbe..00000000000 --- a/Lib/ctypes/test/test_python_api.py +++ /dev/null @@ -1,85 +0,0 @@ -from ctypes import * -import unittest -from test import support - -################################################################ -# This section should be moved into ctypes\__init__.py, when it's ready. - -from _ctypes import PyObj_FromPtr - -################################################################ - -from sys import getrefcount as grc - -class PythonAPITestCase(unittest.TestCase): - - def test_PyBytes_FromStringAndSize(self): - PyBytes_FromStringAndSize = pythonapi.PyBytes_FromStringAndSize - - PyBytes_FromStringAndSize.restype = py_object - PyBytes_FromStringAndSize.argtypes = c_char_p, c_size_t - - self.assertEqual(PyBytes_FromStringAndSize(b"abcdefghi", 3), b"abc") - - @support.refcount_test - def test_PyString_FromString(self): - pythonapi.PyBytes_FromString.restype = py_object - pythonapi.PyBytes_FromString.argtypes = (c_char_p,) - - s = b"abc" - refcnt = grc(s) - pyob = pythonapi.PyBytes_FromString(s) - self.assertEqual(grc(s), refcnt) - self.assertEqual(s, pyob) - del pyob - self.assertEqual(grc(s), refcnt) - - @support.refcount_test - def test_PyLong_Long(self): - ref42 = grc(42) - pythonapi.PyLong_FromLong.restype = py_object - self.assertEqual(pythonapi.PyLong_FromLong(42), 42) - - self.assertEqual(grc(42), ref42) - - pythonapi.PyLong_AsLong.argtypes = (py_object,) - pythonapi.PyLong_AsLong.restype = c_long - - res = pythonapi.PyLong_AsLong(42) - self.assertEqual(grc(res), ref42 + 1) - del res - self.assertEqual(grc(42), ref42) - - @support.refcount_test - def test_PyObj_FromPtr(self): - s = "abc def ghi jkl" - ref = grc(s) - # id(python-object) is the address - pyobj = PyObj_FromPtr(id(s)) - self.assertIs(s, pyobj) - - self.assertEqual(grc(s), ref + 1) - del pyobj - self.assertEqual(grc(s), ref) - - def test_PyOS_snprintf(self): - PyOS_snprintf = pythonapi.PyOS_snprintf - PyOS_snprintf.argtypes = POINTER(c_char), c_size_t, c_char_p - - buf = c_buffer(256) - PyOS_snprintf(buf, sizeof(buf), b"Hello from %s", b"ctypes") - self.assertEqual(buf.value, b"Hello from ctypes") - - PyOS_snprintf(buf, sizeof(buf), b"Hello from %s (%d, %d, %d)", b"ctypes", 1, 2, 3) - self.assertEqual(buf.value, b"Hello from ctypes (1, 2, 3)") - - # not enough arguments - self.assertRaises(TypeError, PyOS_snprintf, buf) - - def test_pyobject_repr(self): - self.assertEqual(repr(py_object()), "py_object(<NULL>)") - self.assertEqual(repr(py_object(42)), "py_object(42)") - self.assertEqual(repr(py_object(object)), "py_object(%r)" % object) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_random_things.py b/Lib/ctypes/test/test_random_things.py deleted file mode 100644 index 2988e275cf4..00000000000 --- a/Lib/ctypes/test/test_random_things.py +++ /dev/null @@ -1,77 +0,0 @@ -from ctypes import * -import contextlib -from test import support -import unittest -import sys - - -def callback_func(arg): - 42 / arg - raise ValueError(arg) - -@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') -class call_function_TestCase(unittest.TestCase): - # _ctypes.call_function is deprecated and private, but used by - # Gary Bishp's readline module. If we have it, we must test it as well. - - def test(self): - from _ctypes import call_function - windll.kernel32.LoadLibraryA.restype = c_void_p - windll.kernel32.GetProcAddress.argtypes = c_void_p, c_char_p - windll.kernel32.GetProcAddress.restype = c_void_p - - hdll = windll.kernel32.LoadLibraryA(b"kernel32") - funcaddr = windll.kernel32.GetProcAddress(hdll, b"GetModuleHandleA") - - self.assertEqual(call_function(funcaddr, (None,)), - windll.kernel32.GetModuleHandleA(None)) - -class CallbackTracbackTestCase(unittest.TestCase): - # When an exception is raised in a ctypes callback function, the C - # code prints a traceback. - # - # This test makes sure the exception types *and* the exception - # value is printed correctly. - # - # Changed in 0.9.3: No longer is '(in callback)' prepended to the - # error message - instead an additional frame for the C code is - # created, then a full traceback printed. When SystemExit is - # raised in a callback function, the interpreter exits. - - @contextlib.contextmanager - def expect_unraisable(self, exc_type, exc_msg=None): - with support.catch_unraisable_exception() as cm: - yield - - self.assertIsInstance(cm.unraisable.exc_value, exc_type) - if exc_msg is not None: - self.assertEqual(str(cm.unraisable.exc_value), exc_msg) - self.assertEqual(cm.unraisable.err_msg, - "Exception ignored on calling ctypes " - "callback function") - self.assertIs(cm.unraisable.object, callback_func) - - def test_ValueError(self): - cb = CFUNCTYPE(c_int, c_int)(callback_func) - with self.expect_unraisable(ValueError, '42'): - cb(42) - - def test_IntegerDivisionError(self): - cb = CFUNCTYPE(c_int, c_int)(callback_func) - with self.expect_unraisable(ZeroDivisionError): - cb(0) - - def test_FloatDivisionError(self): - cb = CFUNCTYPE(c_int, c_double)(callback_func) - with self.expect_unraisable(ZeroDivisionError): - cb(0.0) - - def test_TypeErrorDivisionError(self): - cb = CFUNCTYPE(c_int, c_char_p)(callback_func) - err_msg = "unsupported operand type(s) for /: 'int' and 'bytes'" - with self.expect_unraisable(TypeError, err_msg): - cb(b"spam") - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_refcounts.py b/Lib/ctypes/test/test_refcounts.py deleted file mode 100644 index 48958cd2a60..00000000000 --- a/Lib/ctypes/test/test_refcounts.py +++ /dev/null @@ -1,116 +0,0 @@ -import unittest -from test import support -import ctypes -import gc - -MyCallback = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int) -OtherCallback = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_ulonglong) - -import _ctypes_test -dll = ctypes.CDLL(_ctypes_test.__file__) - -class RefcountTestCase(unittest.TestCase): - - @support.refcount_test - def test_1(self): - from sys import getrefcount as grc - - f = dll._testfunc_callback_i_if - f.restype = ctypes.c_int - f.argtypes = [ctypes.c_int, MyCallback] - - def callback(value): - #print "called back with", value - return value - - self.assertEqual(grc(callback), 2) - cb = MyCallback(callback) - - self.assertGreater(grc(callback), 2) - result = f(-10, cb) - self.assertEqual(result, -18) - cb = None - - gc.collect() - - self.assertEqual(grc(callback), 2) - - - @support.refcount_test - def test_refcount(self): - from sys import getrefcount as grc - def func(*args): - pass - # this is the standard refcount for func - self.assertEqual(grc(func), 2) - - # the CFuncPtr instance holds at least one refcount on func: - f = OtherCallback(func) - self.assertGreater(grc(func), 2) - - # and may release it again - del f - self.assertGreaterEqual(grc(func), 2) - - # but now it must be gone - gc.collect() - self.assertEqual(grc(func), 2) - - class X(ctypes.Structure): - _fields_ = [("a", OtherCallback)] - x = X() - x.a = OtherCallback(func) - - # the CFuncPtr instance holds at least one refcount on func: - self.assertGreater(grc(func), 2) - - # and may release it again - del x - self.assertGreaterEqual(grc(func), 2) - - # and now it must be gone again - gc.collect() - self.assertEqual(grc(func), 2) - - f = OtherCallback(func) - - # the CFuncPtr instance holds at least one refcount on func: - self.assertGreater(grc(func), 2) - - # create a cycle - f.cycle = f - - del f - gc.collect() - self.assertEqual(grc(func), 2) - -class AnotherLeak(unittest.TestCase): - def test_callback(self): - import sys - - proto = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int) - def func(a, b): - return a * b * 2 - f = proto(func) - - a = sys.getrefcount(ctypes.c_int) - f(1, 2) - self.assertEqual(sys.getrefcount(ctypes.c_int), a) - - @support.refcount_test - def test_callback_py_object_none_return(self): - # bpo-36880: test that returning None from a py_object callback - # does not decrement the refcount of None. - - for FUNCTYPE in (ctypes.CFUNCTYPE, ctypes.PYFUNCTYPE): - with self.subTest(FUNCTYPE=FUNCTYPE): - @FUNCTYPE(ctypes.py_object) - def func(): - return None - - # Check that calling func does not affect None's refcount. - for _ in range(10000): - func() - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_repr.py b/Lib/ctypes/test/test_repr.py deleted file mode 100644 index 60a2c803453..00000000000 --- a/Lib/ctypes/test/test_repr.py +++ /dev/null @@ -1,29 +0,0 @@ -from ctypes import * -import unittest - -subclasses = [] -for base in [c_byte, c_short, c_int, c_long, c_longlong, - c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong, - c_float, c_double, c_longdouble, c_bool]: - class X(base): - pass - subclasses.append(X) - -class X(c_char): - pass - -# This test checks if the __repr__ is correct for subclasses of simple types - -class ReprTest(unittest.TestCase): - def test_numbers(self): - for typ in subclasses: - base = typ.__bases__[0] - self.assertTrue(repr(base(42)).startswith(base.__name__)) - self.assertEqual("<X object at", repr(typ(42))[:12]) - - def test_char(self): - self.assertEqual("c_char(b'x')", repr(c_char(b'x'))) - self.assertEqual("<X object at", repr(X(b'x'))[:12]) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_returnfuncptrs.py b/Lib/ctypes/test/test_returnfuncptrs.py deleted file mode 100644 index 1974f40df64..00000000000 --- a/Lib/ctypes/test/test_returnfuncptrs.py +++ /dev/null @@ -1,66 +0,0 @@ -import unittest -from ctypes import * - -import _ctypes_test - -class ReturnFuncPtrTestCase(unittest.TestCase): - - def test_with_prototype(self): - # The _ctypes_test shared lib/dll exports quite some functions for testing. - # The get_strchr function returns a *pointer* to the C strchr function. - dll = CDLL(_ctypes_test.__file__) - get_strchr = dll.get_strchr - get_strchr.restype = CFUNCTYPE(c_char_p, c_char_p, c_char) - strchr = get_strchr() - self.assertEqual(strchr(b"abcdef", b"b"), b"bcdef") - self.assertEqual(strchr(b"abcdef", b"x"), None) - self.assertEqual(strchr(b"abcdef", 98), b"bcdef") - self.assertEqual(strchr(b"abcdef", 107), None) - self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) - self.assertRaises(TypeError, strchr, b"abcdef") - - def test_without_prototype(self): - dll = CDLL(_ctypes_test.__file__) - get_strchr = dll.get_strchr - # the default 'c_int' would not work on systems where sizeof(int) != sizeof(void *) - get_strchr.restype = c_void_p - addr = get_strchr() - # _CFuncPtr instances are now callable with an integer argument - # which denotes a function address: - strchr = CFUNCTYPE(c_char_p, c_char_p, c_char)(addr) - self.assertTrue(strchr(b"abcdef", b"b"), "bcdef") - self.assertEqual(strchr(b"abcdef", b"x"), None) - self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) - self.assertRaises(TypeError, strchr, b"abcdef") - - def test_from_dll(self): - dll = CDLL(_ctypes_test.__file__) - # _CFuncPtr instances are now callable with a tuple argument - # which denotes a function name and a dll: - strchr = CFUNCTYPE(c_char_p, c_char_p, c_char)(("my_strchr", dll)) - self.assertTrue(strchr(b"abcdef", b"b"), "bcdef") - self.assertEqual(strchr(b"abcdef", b"x"), None) - self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) - self.assertRaises(TypeError, strchr, b"abcdef") - - # Issue 6083: Reference counting bug - def test_from_dll_refcount(self): - class BadSequence(tuple): - def __getitem__(self, key): - if key == 0: - return "my_strchr" - if key == 1: - return CDLL(_ctypes_test.__file__) - raise IndexError - - # _CFuncPtr instances are now callable with a tuple argument - # which denotes a function name and a dll: - strchr = CFUNCTYPE(c_char_p, c_char_p, c_char)( - BadSequence(("my_strchr", CDLL(_ctypes_test.__file__)))) - self.assertTrue(strchr(b"abcdef", b"b"), "bcdef") - self.assertEqual(strchr(b"abcdef", b"x"), None) - self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) - self.assertRaises(TypeError, strchr, b"abcdef") - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_simplesubclasses.py b/Lib/ctypes/test/test_simplesubclasses.py deleted file mode 100644 index 3da2794a794..00000000000 --- a/Lib/ctypes/test/test_simplesubclasses.py +++ /dev/null @@ -1,55 +0,0 @@ -import unittest -from ctypes import * - -class MyInt(c_int): - def __eq__(self, other): - if type(other) != MyInt: - return NotImplementedError - return self.value == other.value - -class Test(unittest.TestCase): - - def test_compare(self): - self.assertEqual(MyInt(3), MyInt(3)) - self.assertNotEqual(MyInt(42), MyInt(43)) - - def test_ignore_retval(self): - # Test if the return value of a callback is ignored - # if restype is None - proto = CFUNCTYPE(None) - def func(): - return (1, "abc", None) - - cb = proto(func) - self.assertEqual(None, cb()) - - - def test_int_callback(self): - args = [] - def func(arg): - args.append(arg) - return arg - - cb = CFUNCTYPE(None, MyInt)(func) - - self.assertEqual(None, cb(42)) - self.assertEqual(type(args[-1]), MyInt) - - cb = CFUNCTYPE(c_int, c_int)(func) - - self.assertEqual(42, cb(42)) - self.assertEqual(type(args[-1]), int) - - def test_int_struct(self): - class X(Structure): - _fields_ = [("x", MyInt)] - - self.assertEqual(X().x, MyInt()) - - s = X() - s.x = MyInt(42) - - self.assertEqual(s.x, MyInt(42)) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_sizes.py b/Lib/ctypes/test/test_sizes.py deleted file mode 100644 index 4ceacbc2900..00000000000 --- a/Lib/ctypes/test/test_sizes.py +++ /dev/null @@ -1,33 +0,0 @@ -# Test specifically-sized containers. - -from ctypes import * - -import unittest - - -class SizesTestCase(unittest.TestCase): - def test_8(self): - self.assertEqual(1, sizeof(c_int8)) - self.assertEqual(1, sizeof(c_uint8)) - - def test_16(self): - self.assertEqual(2, sizeof(c_int16)) - self.assertEqual(2, sizeof(c_uint16)) - - def test_32(self): - self.assertEqual(4, sizeof(c_int32)) - self.assertEqual(4, sizeof(c_uint32)) - - def test_64(self): - self.assertEqual(8, sizeof(c_int64)) - self.assertEqual(8, sizeof(c_uint64)) - - def test_size_t(self): - self.assertEqual(sizeof(c_void_p), sizeof(c_size_t)) - - def test_ssize_t(self): - self.assertEqual(sizeof(c_void_p), sizeof(c_ssize_t)) - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_slicing.py b/Lib/ctypes/test/test_slicing.py deleted file mode 100644 index a3932f17672..00000000000 --- a/Lib/ctypes/test/test_slicing.py +++ /dev/null @@ -1,167 +0,0 @@ -import unittest -from ctypes import * -from ctypes.test import need_symbol - -import _ctypes_test - -class SlicesTestCase(unittest.TestCase): - def test_getslice_cint(self): - a = (c_int * 100)(*range(1100, 1200)) - b = list(range(1100, 1200)) - self.assertEqual(a[0:2], b[0:2]) - self.assertEqual(a[0:2:], b[0:2:]) - self.assertEqual(len(a), len(b)) - self.assertEqual(a[5:7], b[5:7]) - self.assertEqual(a[5:7:], b[5:7:]) - self.assertEqual(a[-1], b[-1]) - self.assertEqual(a[:], b[:]) - self.assertEqual(a[::], b[::]) - self.assertEqual(a[10::-1], b[10::-1]) - self.assertEqual(a[30:20:-1], b[30:20:-1]) - self.assertEqual(a[:12:6], b[:12:6]) - self.assertEqual(a[2:6:4], b[2:6:4]) - - a[0:5] = range(5, 10) - self.assertEqual(a[0:5], list(range(5, 10))) - self.assertEqual(a[0:5:], list(range(5, 10))) - self.assertEqual(a[4::-1], list(range(9, 4, -1))) - - def test_setslice_cint(self): - a = (c_int * 100)(*range(1100, 1200)) - b = list(range(1100, 1200)) - - a[32:47] = list(range(32, 47)) - self.assertEqual(a[32:47], list(range(32, 47))) - a[32:47] = range(132, 147) - self.assertEqual(a[32:47:], list(range(132, 147))) - a[46:31:-1] = range(232, 247) - self.assertEqual(a[32:47:1], list(range(246, 231, -1))) - - a[32:47] = range(1132, 1147) - self.assertEqual(a[:], b) - a[32:47:7] = range(3) - b[32:47:7] = range(3) - self.assertEqual(a[:], b) - a[33::-3] = range(12) - b[33::-3] = range(12) - self.assertEqual(a[:], b) - - from operator import setitem - - # TypeError: int expected instead of str instance - self.assertRaises(TypeError, setitem, a, slice(0, 5), "abcde") - # TypeError: int expected instead of str instance - self.assertRaises(TypeError, setitem, a, slice(0, 5), - ["a", "b", "c", "d", "e"]) - # TypeError: int expected instead of float instance - self.assertRaises(TypeError, setitem, a, slice(0, 5), - [1, 2, 3, 4, 3.14]) - # ValueError: Can only assign sequence of same size - self.assertRaises(ValueError, setitem, a, slice(0, 5), range(32)) - - def test_char_ptr(self): - s = b"abcdefghijklmnopqrstuvwxyz" - - dll = CDLL(_ctypes_test.__file__) - dll.my_strdup.restype = POINTER(c_char) - dll.my_free.restype = None - res = dll.my_strdup(s) - self.assertEqual(res[:len(s)], s) - self.assertEqual(res[:3], s[:3]) - self.assertEqual(res[:len(s):], s) - self.assertEqual(res[len(s)-1:-1:-1], s[::-1]) - self.assertEqual(res[len(s)-1:5:-7], s[:5:-7]) - self.assertEqual(res[0:-1:-1], s[0::-1]) - - import operator - self.assertRaises(ValueError, operator.getitem, - res, slice(None, None, None)) - self.assertRaises(ValueError, operator.getitem, - res, slice(0, None, None)) - self.assertRaises(ValueError, operator.getitem, - res, slice(None, 5, -1)) - self.assertRaises(ValueError, operator.getitem, - res, slice(-5, None, None)) - - self.assertRaises(TypeError, operator.setitem, - res, slice(0, 5), "abcde") - dll.my_free(res) - - dll.my_strdup.restype = POINTER(c_byte) - res = dll.my_strdup(s) - self.assertEqual(res[:len(s)], list(range(ord("a"), ord("z")+1))) - self.assertEqual(res[:len(s):], list(range(ord("a"), ord("z")+1))) - dll.my_free(res) - - def test_char_ptr_with_free(self): - dll = CDLL(_ctypes_test.__file__) - s = b"abcdefghijklmnopqrstuvwxyz" - - class allocated_c_char_p(c_char_p): - pass - - dll.my_free.restype = None - def errcheck(result, func, args): - retval = result.value - dll.my_free(result) - return retval - - dll.my_strdup.restype = allocated_c_char_p - dll.my_strdup.errcheck = errcheck - try: - res = dll.my_strdup(s) - self.assertEqual(res, s) - finally: - del dll.my_strdup.errcheck - - - def test_char_array(self): - s = b"abcdefghijklmnopqrstuvwxyz\0" - - p = (c_char * 27)(*s) - self.assertEqual(p[:], s) - self.assertEqual(p[::], s) - self.assertEqual(p[::-1], s[::-1]) - self.assertEqual(p[5::-2], s[5::-2]) - self.assertEqual(p[2:5:-3], s[2:5:-3]) - - - @need_symbol('c_wchar') - def test_wchar_ptr(self): - s = "abcdefghijklmnopqrstuvwxyz\0" - - dll = CDLL(_ctypes_test.__file__) - dll.my_wcsdup.restype = POINTER(c_wchar) - dll.my_wcsdup.argtypes = POINTER(c_wchar), - dll.my_free.restype = None - res = dll.my_wcsdup(s[:-1]) - self.assertEqual(res[:len(s)], s) - self.assertEqual(res[:len(s):], s) - self.assertEqual(res[len(s)-1:-1:-1], s[::-1]) - self.assertEqual(res[len(s)-1:5:-7], s[:5:-7]) - - import operator - self.assertRaises(TypeError, operator.setitem, - res, slice(0, 5), "abcde") - dll.my_free(res) - - if sizeof(c_wchar) == sizeof(c_short): - dll.my_wcsdup.restype = POINTER(c_short) - elif sizeof(c_wchar) == sizeof(c_int): - dll.my_wcsdup.restype = POINTER(c_int) - elif sizeof(c_wchar) == sizeof(c_long): - dll.my_wcsdup.restype = POINTER(c_long) - else: - self.skipTest('Pointers to c_wchar are not supported') - res = dll.my_wcsdup(s[:-1]) - tmpl = list(range(ord("a"), ord("z")+1)) - self.assertEqual(res[:len(s)-1], tmpl) - self.assertEqual(res[:len(s)-1:], tmpl) - self.assertEqual(res[len(s)-2:-1:-1], tmpl[::-1]) - self.assertEqual(res[len(s)-2:5:-7], tmpl[:5:-7]) - dll.my_free(res) - -################################################################ - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_stringptr.py b/Lib/ctypes/test/test_stringptr.py deleted file mode 100644 index c20951f4ce0..00000000000 --- a/Lib/ctypes/test/test_stringptr.py +++ /dev/null @@ -1,77 +0,0 @@ -import unittest -from test import support -from ctypes import * - -import _ctypes_test - -lib = CDLL(_ctypes_test.__file__) - -class StringPtrTestCase(unittest.TestCase): - - @support.refcount_test - def test__POINTER_c_char(self): - class X(Structure): - _fields_ = [("str", POINTER(c_char))] - x = X() - - # NULL pointer access - self.assertRaises(ValueError, getattr, x.str, "contents") - b = c_buffer(b"Hello, World") - from sys import getrefcount as grc - self.assertEqual(grc(b), 2) - x.str = b - self.assertEqual(grc(b), 3) - - # POINTER(c_char) and Python string is NOT compatible - # POINTER(c_char) and c_buffer() is compatible - for i in range(len(b)): - self.assertEqual(b[i], x.str[i]) - - self.assertRaises(TypeError, setattr, x, "str", "Hello, World") - - def test__c_char_p(self): - class X(Structure): - _fields_ = [("str", c_char_p)] - x = X() - - # c_char_p and Python string is compatible - # c_char_p and c_buffer is NOT compatible - self.assertEqual(x.str, None) - x.str = b"Hello, World" - self.assertEqual(x.str, b"Hello, World") - b = c_buffer(b"Hello, World") - self.assertRaises(TypeError, setattr, x, b"str", b) - - - def test_functions(self): - strchr = lib.my_strchr - strchr.restype = c_char_p - - # c_char_p and Python string is compatible - # c_char_p and c_buffer are now compatible - strchr.argtypes = c_char_p, c_char - self.assertEqual(strchr(b"abcdef", b"c"), b"cdef") - self.assertEqual(strchr(c_buffer(b"abcdef"), b"c"), b"cdef") - - # POINTER(c_char) and Python string is NOT compatible - # POINTER(c_char) and c_buffer() is compatible - strchr.argtypes = POINTER(c_char), c_char - buf = c_buffer(b"abcdef") - self.assertEqual(strchr(buf, b"c"), b"cdef") - self.assertEqual(strchr(b"abcdef", b"c"), b"cdef") - - # XXX These calls are dangerous, because the first argument - # to strchr is no longer valid after the function returns! - # So we must keep a reference to buf separately - - strchr.restype = POINTER(c_char) - buf = c_buffer(b"abcdef") - r = strchr(buf, b"c") - x = r[0], r[1], r[2], r[3], r[4] - self.assertEqual(x, (b"c", b"d", b"e", b"f", b"\000")) - del buf - # Because r is a pointer to memory that is freed after deleting buf, - # the pointer is hanging and using it would reference freed memory. - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_strings.py b/Lib/ctypes/test/test_strings.py deleted file mode 100644 index 12e208828a7..00000000000 --- a/Lib/ctypes/test/test_strings.py +++ /dev/null @@ -1,145 +0,0 @@ -import unittest -from ctypes import * -from ctypes.test import need_symbol - -class StringArrayTestCase(unittest.TestCase): - def test(self): - BUF = c_char * 4 - - buf = BUF(b"a", b"b", b"c") - self.assertEqual(buf.value, b"abc") - self.assertEqual(buf.raw, b"abc\000") - - buf.value = b"ABCD" - self.assertEqual(buf.value, b"ABCD") - self.assertEqual(buf.raw, b"ABCD") - - buf.value = b"x" - self.assertEqual(buf.value, b"x") - self.assertEqual(buf.raw, b"x\000CD") - - buf[1] = b"Z" - self.assertEqual(buf.value, b"xZCD") - self.assertEqual(buf.raw, b"xZCD") - - self.assertRaises(ValueError, setattr, buf, "value", b"aaaaaaaa") - self.assertRaises(TypeError, setattr, buf, "value", 42) - - def test_c_buffer_value(self): - buf = c_buffer(32) - - buf.value = b"Hello, World" - self.assertEqual(buf.value, b"Hello, World") - - self.assertRaises(TypeError, setattr, buf, "value", memoryview(b"Hello, World")) - self.assertRaises(TypeError, setattr, buf, "value", memoryview(b"abc")) - self.assertRaises(ValueError, setattr, buf, "raw", memoryview(b"x" * 100)) - - def test_c_buffer_raw(self): - buf = c_buffer(32) - - buf.raw = memoryview(b"Hello, World") - self.assertEqual(buf.value, b"Hello, World") - self.assertRaises(TypeError, setattr, buf, "value", memoryview(b"abc")) - self.assertRaises(ValueError, setattr, buf, "raw", memoryview(b"x" * 100)) - - def test_param_1(self): - BUF = c_char * 4 - buf = BUF() -## print c_char_p.from_param(buf) - - def test_param_2(self): - BUF = c_char * 4 - buf = BUF() -## print BUF.from_param(c_char_p("python")) -## print BUF.from_param(BUF(*"pyth")) - - def test_del_segfault(self): - BUF = c_char * 4 - buf = BUF() - with self.assertRaises(AttributeError): - del buf.raw - - -@need_symbol('c_wchar') -class WStringArrayTestCase(unittest.TestCase): - def test(self): - BUF = c_wchar * 4 - - buf = BUF("a", "b", "c") - self.assertEqual(buf.value, "abc") - - buf.value = "ABCD" - self.assertEqual(buf.value, "ABCD") - - buf.value = "x" - self.assertEqual(buf.value, "x") - - buf[1] = "Z" - self.assertEqual(buf.value, "xZCD") - - @unittest.skipIf(sizeof(c_wchar) < 4, - "sizeof(wchar_t) is smaller than 4 bytes") - def test_nonbmp(self): - u = chr(0x10ffff) - w = c_wchar(u) - self.assertEqual(w.value, u) - - -@need_symbol('c_wchar') -class WStringTestCase(unittest.TestCase): - def test_wchar(self): - c_wchar("x") - repr(byref(c_wchar("x"))) - c_wchar("x") - - - @unittest.skip('test disabled') - def test_basic_wstrings(self): - cs = c_wstring("abcdef") - - # XXX This behaviour is about to change: - # len returns the size of the internal buffer in bytes. - # This includes the terminating NUL character. - self.assertEqual(sizeof(cs), 14) - - # The value property is the string up to the first terminating NUL. - self.assertEqual(cs.value, "abcdef") - self.assertEqual(c_wstring("abc\000def").value, "abc") - - self.assertEqual(c_wstring("abc\000def").value, "abc") - - # The raw property is the total buffer contents: - self.assertEqual(cs.raw, "abcdef\000") - self.assertEqual(c_wstring("abc\000def").raw, "abc\000def\000") - - # We can change the value: - cs.value = "ab" - self.assertEqual(cs.value, "ab") - self.assertEqual(cs.raw, "ab\000\000\000\000\000") - - self.assertRaises(TypeError, c_wstring, "123") - self.assertRaises(ValueError, c_wstring, 0) - - @unittest.skip('test disabled') - def test_toolong(self): - cs = c_wstring("abcdef") - # Much too long string: - self.assertRaises(ValueError, setattr, cs, "value", "123456789012345") - - # One char too long values: - self.assertRaises(ValueError, setattr, cs, "value", "1234567") - - -def run_test(rep, msg, func, arg): - items = range(rep) - from time import perf_counter as clock - start = clock() - for i in items: - func(arg); func(arg); func(arg); func(arg); func(arg) - stop = clock() - print("%20s: %.2f us" % (msg, ((stop-start)*1e6/5/rep))) - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_struct_fields.py b/Lib/ctypes/test/test_struct_fields.py deleted file mode 100644 index e444f5e1f77..00000000000 --- a/Lib/ctypes/test/test_struct_fields.py +++ /dev/null @@ -1,97 +0,0 @@ -import unittest -from ctypes import * - -class StructFieldsTestCase(unittest.TestCase): - # Structure/Union classes must get 'finalized' sooner or - # later, when one of these things happen: - # - # 1. _fields_ is set. - # 2. An instance is created. - # 3. The type is used as field of another Structure/Union. - # 4. The type is subclassed - # - # When they are finalized, assigning _fields_ is no longer allowed. - - def test_1_A(self): - class X(Structure): - pass - self.assertEqual(sizeof(X), 0) # not finalized - X._fields_ = [] # finalized - self.assertRaises(AttributeError, setattr, X, "_fields_", []) - - def test_1_B(self): - class X(Structure): - _fields_ = [] # finalized - self.assertRaises(AttributeError, setattr, X, "_fields_", []) - - def test_2(self): - class X(Structure): - pass - X() - self.assertRaises(AttributeError, setattr, X, "_fields_", []) - - def test_3(self): - class X(Structure): - pass - class Y(Structure): - _fields_ = [("x", X)] # finalizes X - self.assertRaises(AttributeError, setattr, X, "_fields_", []) - - def test_4(self): - class X(Structure): - pass - class Y(X): - pass - self.assertRaises(AttributeError, setattr, X, "_fields_", []) - Y._fields_ = [] - self.assertRaises(AttributeError, setattr, X, "_fields_", []) - - def test_5(self): - class X(Structure): - _fields_ = (("char", c_char * 5),) - - x = X(b'#' * 5) - x.char = b'a\0b\0' - self.assertEqual(bytes(x), b'a\x00###') - - def test_6(self): - class X(Structure): - _fields_ = [("x", c_int)] - CField = type(X.x) - self.assertRaises(TypeError, CField) - - def test_gh99275(self): - class BrokenStructure(Structure): - def __init_subclass__(cls, **kwargs): - cls._fields_ = [] # This line will fail, `stgdict` is not ready - - with self.assertRaisesRegex(TypeError, - 'ctypes state is not initialized'): - class Subclass(BrokenStructure): ... - - # __set__ and __get__ should raise a TypeError in case their self - # argument is not a ctype instance. - def test___set__(self): - class MyCStruct(Structure): - _fields_ = (("field", c_int),) - self.assertRaises(TypeError, - MyCStruct.field.__set__, 'wrong type self', 42) - - class MyCUnion(Union): - _fields_ = (("field", c_int),) - self.assertRaises(TypeError, - MyCUnion.field.__set__, 'wrong type self', 42) - - def test___get__(self): - class MyCStruct(Structure): - _fields_ = (("field", c_int),) - self.assertRaises(TypeError, - MyCStruct.field.__get__, 'wrong type self', 42) - - class MyCUnion(Union): - _fields_ = (("field", c_int),) - self.assertRaises(TypeError, - MyCUnion.field.__get__, 'wrong type self', 42) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_structures.py b/Lib/ctypes/test/test_structures.py deleted file mode 100644 index f95d5a99a3a..00000000000 --- a/Lib/ctypes/test/test_structures.py +++ /dev/null @@ -1,812 +0,0 @@ -import platform -import sys -import unittest -from ctypes import * -from ctypes.test import need_symbol -from struct import calcsize -import _ctypes_test -from test import support - -# The following definition is meant to be used from time to time to assist -# temporarily disabling tests on specific architectures while investigations -# are in progress, to keep buildbots happy. -MACHINE = platform.machine() - -class SubclassesTest(unittest.TestCase): - def test_subclass(self): - class X(Structure): - _fields_ = [("a", c_int)] - - class Y(X): - _fields_ = [("b", c_int)] - - class Z(X): - pass - - self.assertEqual(sizeof(X), sizeof(c_int)) - self.assertEqual(sizeof(Y), sizeof(c_int)*2) - self.assertEqual(sizeof(Z), sizeof(c_int)) - self.assertEqual(X._fields_, [("a", c_int)]) - self.assertEqual(Y._fields_, [("b", c_int)]) - self.assertEqual(Z._fields_, [("a", c_int)]) - - def test_subclass_delayed(self): - class X(Structure): - pass - self.assertEqual(sizeof(X), 0) - X._fields_ = [("a", c_int)] - - class Y(X): - pass - self.assertEqual(sizeof(Y), sizeof(X)) - Y._fields_ = [("b", c_int)] - - class Z(X): - pass - - self.assertEqual(sizeof(X), sizeof(c_int)) - self.assertEqual(sizeof(Y), sizeof(c_int)*2) - self.assertEqual(sizeof(Z), sizeof(c_int)) - self.assertEqual(X._fields_, [("a", c_int)]) - self.assertEqual(Y._fields_, [("b", c_int)]) - self.assertEqual(Z._fields_, [("a", c_int)]) - -class StructureTestCase(unittest.TestCase): - formats = {"c": c_char, - "b": c_byte, - "B": c_ubyte, - "h": c_short, - "H": c_ushort, - "i": c_int, - "I": c_uint, - "l": c_long, - "L": c_ulong, - "q": c_longlong, - "Q": c_ulonglong, - "f": c_float, - "d": c_double, - } - - def test_simple_structs(self): - for code, tp in self.formats.items(): - class X(Structure): - _fields_ = [("x", c_char), - ("y", tp)] - self.assertEqual((sizeof(X), code), - (calcsize("c%c0%c" % (code, code)), code)) - - def test_unions(self): - for code, tp in self.formats.items(): - class X(Union): - _fields_ = [("x", c_char), - ("y", tp)] - self.assertEqual((sizeof(X), code), - (calcsize("%c" % (code)), code)) - - def test_struct_alignment(self): - class X(Structure): - _fields_ = [("x", c_char * 3)] - self.assertEqual(alignment(X), calcsize("s")) - self.assertEqual(sizeof(X), calcsize("3s")) - - class Y(Structure): - _fields_ = [("x", c_char * 3), - ("y", c_int)] - self.assertEqual(alignment(Y), alignment(c_int)) - self.assertEqual(sizeof(Y), calcsize("3si")) - - class SI(Structure): - _fields_ = [("a", X), - ("b", Y)] - self.assertEqual(alignment(SI), max(alignment(Y), alignment(X))) - self.assertEqual(sizeof(SI), calcsize("3s0i 3si 0i")) - - class IS(Structure): - _fields_ = [("b", Y), - ("a", X)] - - self.assertEqual(alignment(SI), max(alignment(X), alignment(Y))) - self.assertEqual(sizeof(IS), calcsize("3si 3s 0i")) - - class XX(Structure): - _fields_ = [("a", X), - ("b", X)] - self.assertEqual(alignment(XX), alignment(X)) - self.assertEqual(sizeof(XX), calcsize("3s 3s 0s")) - - def test_empty(self): - # I had problems with these - # - # Although these are pathological cases: Empty Structures! - class X(Structure): - _fields_ = [] - - class Y(Union): - _fields_ = [] - - # Is this really the correct alignment, or should it be 0? - self.assertTrue(alignment(X) == alignment(Y) == 1) - self.assertTrue(sizeof(X) == sizeof(Y) == 0) - - class XX(Structure): - _fields_ = [("a", X), - ("b", X)] - - self.assertEqual(alignment(XX), 1) - self.assertEqual(sizeof(XX), 0) - - def test_fields(self): - # test the offset and size attributes of Structure/Union fields. - class X(Structure): - _fields_ = [("x", c_int), - ("y", c_char)] - - self.assertEqual(X.x.offset, 0) - self.assertEqual(X.x.size, sizeof(c_int)) - - self.assertEqual(X.y.offset, sizeof(c_int)) - self.assertEqual(X.y.size, sizeof(c_char)) - - # readonly - self.assertRaises((TypeError, AttributeError), setattr, X.x, "offset", 92) - self.assertRaises((TypeError, AttributeError), setattr, X.x, "size", 92) - - class X(Union): - _fields_ = [("x", c_int), - ("y", c_char)] - - self.assertEqual(X.x.offset, 0) - self.assertEqual(X.x.size, sizeof(c_int)) - - self.assertEqual(X.y.offset, 0) - self.assertEqual(X.y.size, sizeof(c_char)) - - # readonly - self.assertRaises((TypeError, AttributeError), setattr, X.x, "offset", 92) - self.assertRaises((TypeError, AttributeError), setattr, X.x, "size", 92) - - # XXX Should we check nested data types also? - # offset is always relative to the class... - - def test_packed(self): - class X(Structure): - _fields_ = [("a", c_byte), - ("b", c_longlong)] - _pack_ = 1 - - self.assertEqual(sizeof(X), 9) - self.assertEqual(X.b.offset, 1) - - class X(Structure): - _fields_ = [("a", c_byte), - ("b", c_longlong)] - _pack_ = 2 - self.assertEqual(sizeof(X), 10) - self.assertEqual(X.b.offset, 2) - - import struct - longlong_size = struct.calcsize("q") - longlong_align = struct.calcsize("bq") - longlong_size - - class X(Structure): - _fields_ = [("a", c_byte), - ("b", c_longlong)] - _pack_ = 4 - self.assertEqual(sizeof(X), min(4, longlong_align) + longlong_size) - self.assertEqual(X.b.offset, min(4, longlong_align)) - - class X(Structure): - _fields_ = [("a", c_byte), - ("b", c_longlong)] - _pack_ = 8 - - self.assertEqual(sizeof(X), min(8, longlong_align) + longlong_size) - self.assertEqual(X.b.offset, min(8, longlong_align)) - - - d = {"_fields_": [("a", "b"), - ("b", "q")], - "_pack_": -1} - self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) - - @support.cpython_only - def test_packed_c_limits(self): - # Issue 15989 - import _testcapi - d = {"_fields_": [("a", c_byte)], - "_pack_": _testcapi.INT_MAX + 1} - self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) - d = {"_fields_": [("a", c_byte)], - "_pack_": _testcapi.UINT_MAX + 2} - self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) - - def test_initializers(self): - class Person(Structure): - _fields_ = [("name", c_char*6), - ("age", c_int)] - - self.assertRaises(TypeError, Person, 42) - self.assertRaises(ValueError, Person, b"asldkjaslkdjaslkdj") - self.assertRaises(TypeError, Person, "Name", "HI") - - # short enough - self.assertEqual(Person(b"12345", 5).name, b"12345") - # exact fit - self.assertEqual(Person(b"123456", 5).name, b"123456") - # too long - self.assertRaises(ValueError, Person, b"1234567", 5) - - def test_conflicting_initializers(self): - class POINT(Structure): - _fields_ = [("phi", c_float), ("rho", c_float)] - # conflicting positional and keyword args - self.assertRaisesRegex(TypeError, "phi", POINT, 2, 3, phi=4) - self.assertRaisesRegex(TypeError, "rho", POINT, 2, 3, rho=4) - - # too many initializers - self.assertRaises(TypeError, POINT, 2, 3, 4) - - def test_keyword_initializers(self): - class POINT(Structure): - _fields_ = [("x", c_int), ("y", c_int)] - pt = POINT(1, 2) - self.assertEqual((pt.x, pt.y), (1, 2)) - - pt = POINT(y=2, x=1) - self.assertEqual((pt.x, pt.y), (1, 2)) - - def test_invalid_field_types(self): - class POINT(Structure): - pass - self.assertRaises(TypeError, setattr, POINT, "_fields_", [("x", 1), ("y", 2)]) - - def test_invalid_name(self): - # field name must be string - def declare_with_name(name): - class S(Structure): - _fields_ = [(name, c_int)] - - self.assertRaises(TypeError, declare_with_name, b"x") - - def test_intarray_fields(self): - class SomeInts(Structure): - _fields_ = [("a", c_int * 4)] - - # can use tuple to initialize array (but not list!) - self.assertEqual(SomeInts((1, 2)).a[:], [1, 2, 0, 0]) - self.assertEqual(SomeInts((1, 2)).a[::], [1, 2, 0, 0]) - self.assertEqual(SomeInts((1, 2)).a[::-1], [0, 0, 2, 1]) - self.assertEqual(SomeInts((1, 2)).a[::2], [1, 0]) - self.assertEqual(SomeInts((1, 2)).a[1:5:6], [2]) - self.assertEqual(SomeInts((1, 2)).a[6:4:-1], []) - self.assertEqual(SomeInts((1, 2, 3, 4)).a[:], [1, 2, 3, 4]) - self.assertEqual(SomeInts((1, 2, 3, 4)).a[::], [1, 2, 3, 4]) - # too long - # XXX Should raise ValueError?, not RuntimeError - self.assertRaises(RuntimeError, SomeInts, (1, 2, 3, 4, 5)) - - def test_nested_initializers(self): - # test initializing nested structures - class Phone(Structure): - _fields_ = [("areacode", c_char*6), - ("number", c_char*12)] - - class Person(Structure): - _fields_ = [("name", c_char * 12), - ("phone", Phone), - ("age", c_int)] - - p = Person(b"Someone", (b"1234", b"5678"), 5) - - self.assertEqual(p.name, b"Someone") - self.assertEqual(p.phone.areacode, b"1234") - self.assertEqual(p.phone.number, b"5678") - self.assertEqual(p.age, 5) - - @need_symbol('c_wchar') - def test_structures_with_wchar(self): - class PersonW(Structure): - _fields_ = [("name", c_wchar * 12), - ("age", c_int)] - - p = PersonW("Someone \xe9") - self.assertEqual(p.name, "Someone \xe9") - - self.assertEqual(PersonW("1234567890").name, "1234567890") - self.assertEqual(PersonW("12345678901").name, "12345678901") - # exact fit - self.assertEqual(PersonW("123456789012").name, "123456789012") - #too long - self.assertRaises(ValueError, PersonW, "1234567890123") - - def test_init_errors(self): - class Phone(Structure): - _fields_ = [("areacode", c_char*6), - ("number", c_char*12)] - - class Person(Structure): - _fields_ = [("name", c_char * 12), - ("phone", Phone), - ("age", c_int)] - - cls, msg = self.get_except(Person, b"Someone", (1, 2)) - self.assertEqual(cls, RuntimeError) - self.assertEqual(msg, - "(Phone) TypeError: " - "expected bytes, int found") - - cls, msg = self.get_except(Person, b"Someone", (b"a", b"b", b"c")) - self.assertEqual(cls, RuntimeError) - self.assertEqual(msg, - "(Phone) TypeError: too many initializers") - - def test_huge_field_name(self): - # issue12881: segfault with large structure field names - def create_class(length): - class S(Structure): - _fields_ = [('x' * length, c_int)] - - for length in [10 ** i for i in range(0, 8)]: - try: - create_class(length) - except MemoryError: - # MemoryErrors are OK, we just don't want to segfault - pass - - def get_except(self, func, *args): - try: - func(*args) - except Exception as detail: - return detail.__class__, str(detail) - - @unittest.skip('test disabled') - def test_subclass_creation(self): - meta = type(Structure) - # same as 'class X(Structure): pass' - # fails, since we need either a _fields_ or a _abstract_ attribute - cls, msg = self.get_except(meta, "X", (Structure,), {}) - self.assertEqual((cls, msg), - (AttributeError, "class must define a '_fields_' attribute")) - - def test_abstract_class(self): - class X(Structure): - _abstract_ = "something" - # try 'X()' - cls, msg = self.get_except(eval, "X()", locals()) - self.assertEqual((cls, msg), (TypeError, "abstract class")) - - def test_methods(self): -## class X(Structure): -## _fields_ = [] - - self.assertIn("in_dll", dir(type(Structure))) - self.assertIn("from_address", dir(type(Structure))) - self.assertIn("in_dll", dir(type(Structure))) - - def test_positional_args(self): - # see also http://bugs.python.org/issue5042 - class W(Structure): - _fields_ = [("a", c_int), ("b", c_int)] - class X(W): - _fields_ = [("c", c_int)] - class Y(X): - pass - class Z(Y): - _fields_ = [("d", c_int), ("e", c_int), ("f", c_int)] - - z = Z(1, 2, 3, 4, 5, 6) - self.assertEqual((z.a, z.b, z.c, z.d, z.e, z.f), - (1, 2, 3, 4, 5, 6)) - z = Z(1) - self.assertEqual((z.a, z.b, z.c, z.d, z.e, z.f), - (1, 0, 0, 0, 0, 0)) - self.assertRaises(TypeError, lambda: Z(1, 2, 3, 4, 5, 6, 7)) - - def test_pass_by_value(self): - # This should mirror the Test structure - # in Modules/_ctypes/_ctypes_test.c - class Test(Structure): - _fields_ = [ - ('first', c_ulong), - ('second', c_ulong), - ('third', c_ulong), - ] - - s = Test() - s.first = 0xdeadbeef - s.second = 0xcafebabe - s.third = 0x0bad1dea - dll = CDLL(_ctypes_test.__file__) - func = dll._testfunc_large_struct_update_value - func.argtypes = (Test,) - func.restype = None - func(s) - self.assertEqual(s.first, 0xdeadbeef) - self.assertEqual(s.second, 0xcafebabe) - self.assertEqual(s.third, 0x0bad1dea) - - def test_pass_by_value_finalizer(self): - # bpo-37140: Similar to test_pass_by_value(), but the Python structure - # has a finalizer (__del__() method): the finalizer must only be called - # once. - - finalizer_calls = [] - - class Test(Structure): - _fields_ = [ - ('first', c_ulong), - ('second', c_ulong), - ('third', c_ulong), - ] - def __del__(self): - finalizer_calls.append("called") - - s = Test(1, 2, 3) - # Test the StructUnionType_paramfunc() code path which copies the - # structure: if the structure is larger than sizeof(void*). - self.assertGreater(sizeof(s), sizeof(c_void_p)) - - dll = CDLL(_ctypes_test.__file__) - func = dll._testfunc_large_struct_update_value - func.argtypes = (Test,) - func.restype = None - func(s) - # bpo-37140: Passing the structure by reference must not call - # its finalizer! - self.assertEqual(finalizer_calls, []) - self.assertEqual(s.first, 1) - self.assertEqual(s.second, 2) - self.assertEqual(s.third, 3) - - # The finalizer must be called exactly once - s = None - support.gc_collect() - self.assertEqual(finalizer_calls, ["called"]) - - def test_pass_by_value_in_register(self): - class X(Structure): - _fields_ = [ - ('first', c_uint), - ('second', c_uint) - ] - - s = X() - s.first = 0xdeadbeef - s.second = 0xcafebabe - dll = CDLL(_ctypes_test.__file__) - func = dll._testfunc_reg_struct_update_value - func.argtypes = (X,) - func.restype = None - func(s) - self.assertEqual(s.first, 0xdeadbeef) - self.assertEqual(s.second, 0xcafebabe) - got = X.in_dll(dll, "last_tfrsuv_arg") - self.assertEqual(s.first, got.first) - self.assertEqual(s.second, got.second) - - def test_array_in_struct(self): - # See bpo-22273 - - # These should mirror the structures in Modules/_ctypes/_ctypes_test.c - class Test2(Structure): - _fields_ = [ - ('data', c_ubyte * 16), - ] - - class Test3(Structure): - _fields_ = [ - ('data', c_double * 2), - ] - - class Test3A(Structure): - _fields_ = [ - ('data', c_float * 2), - ] - - class Test3B(Test3A): - _fields_ = [ - ('more_data', c_float * 2), - ] - - s = Test2() - expected = 0 - for i in range(16): - s.data[i] = i - expected += i - dll = CDLL(_ctypes_test.__file__) - func = dll._testfunc_array_in_struct1 - func.restype = c_int - func.argtypes = (Test2,) - result = func(s) - self.assertEqual(result, expected) - # check the passed-in struct hasn't changed - for i in range(16): - self.assertEqual(s.data[i], i) - - s = Test3() - s.data[0] = 3.14159 - s.data[1] = 2.71828 - expected = 3.14159 + 2.71828 - func = dll._testfunc_array_in_struct2 - func.restype = c_double - func.argtypes = (Test3,) - result = func(s) - self.assertEqual(result, expected) - # check the passed-in struct hasn't changed - self.assertEqual(s.data[0], 3.14159) - self.assertEqual(s.data[1], 2.71828) - - s = Test3B() - s.data[0] = 3.14159 - s.data[1] = 2.71828 - s.more_data[0] = -3.0 - s.more_data[1] = -2.0 - - expected = 3.14159 + 2.71828 - 5.0 - func = dll._testfunc_array_in_struct2a - func.restype = c_double - func.argtypes = (Test3B,) - result = func(s) - self.assertAlmostEqual(result, expected, places=6) - # check the passed-in struct hasn't changed - self.assertAlmostEqual(s.data[0], 3.14159, places=6) - self.assertAlmostEqual(s.data[1], 2.71828, places=6) - self.assertAlmostEqual(s.more_data[0], -3.0, places=6) - self.assertAlmostEqual(s.more_data[1], -2.0, places=6) - - def test_38368(self): - class U(Union): - _fields_ = [ - ('f1', c_uint8 * 16), - ('f2', c_uint16 * 8), - ('f3', c_uint32 * 4), - ] - u = U() - u.f3[0] = 0x01234567 - u.f3[1] = 0x89ABCDEF - u.f3[2] = 0x76543210 - u.f3[3] = 0xFEDCBA98 - f1 = [u.f1[i] for i in range(16)] - f2 = [u.f2[i] for i in range(8)] - if sys.byteorder == 'little': - self.assertEqual(f1, [0x67, 0x45, 0x23, 0x01, - 0xef, 0xcd, 0xab, 0x89, - 0x10, 0x32, 0x54, 0x76, - 0x98, 0xba, 0xdc, 0xfe]) - self.assertEqual(f2, [0x4567, 0x0123, 0xcdef, 0x89ab, - 0x3210, 0x7654, 0xba98, 0xfedc]) - - @unittest.skipIf(True, 'Test disabled for now - see bpo-16575/bpo-16576') - def test_union_by_value(self): - # See bpo-16575 - - # These should mirror the structures in Modules/_ctypes/_ctypes_test.c - - class Nested1(Structure): - _fields_ = [ - ('an_int', c_int), - ('another_int', c_int), - ] - - class Test4(Union): - _fields_ = [ - ('a_long', c_long), - ('a_struct', Nested1), - ] - - class Nested2(Structure): - _fields_ = [ - ('an_int', c_int), - ('a_union', Test4), - ] - - class Test5(Structure): - _fields_ = [ - ('an_int', c_int), - ('nested', Nested2), - ('another_int', c_int), - ] - - test4 = Test4() - dll = CDLL(_ctypes_test.__file__) - with self.assertRaises(TypeError) as ctx: - func = dll._testfunc_union_by_value1 - func.restype = c_long - func.argtypes = (Test4,) - result = func(test4) - self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes ' - 'a union by value, which is unsupported.') - test5 = Test5() - with self.assertRaises(TypeError) as ctx: - func = dll._testfunc_union_by_value2 - func.restype = c_long - func.argtypes = (Test5,) - result = func(test5) - self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes ' - 'a union by value, which is unsupported.') - - # passing by reference should be OK - test4.a_long = 12345; - func = dll._testfunc_union_by_reference1 - func.restype = c_long - func.argtypes = (POINTER(Test4),) - result = func(byref(test4)) - self.assertEqual(result, 12345) - self.assertEqual(test4.a_long, 0) - self.assertEqual(test4.a_struct.an_int, 0) - self.assertEqual(test4.a_struct.another_int, 0) - test4.a_struct.an_int = 0x12340000 - test4.a_struct.another_int = 0x5678 - func = dll._testfunc_union_by_reference2 - func.restype = c_long - func.argtypes = (POINTER(Test4),) - result = func(byref(test4)) - self.assertEqual(result, 0x12345678) - self.assertEqual(test4.a_long, 0) - self.assertEqual(test4.a_struct.an_int, 0) - self.assertEqual(test4.a_struct.another_int, 0) - test5.an_int = 0x12000000 - test5.nested.an_int = 0x345600 - test5.another_int = 0x78 - func = dll._testfunc_union_by_reference3 - func.restype = c_long - func.argtypes = (POINTER(Test5),) - result = func(byref(test5)) - self.assertEqual(result, 0x12345678) - self.assertEqual(test5.an_int, 0) - self.assertEqual(test5.nested.an_int, 0) - self.assertEqual(test5.another_int, 0) - - @unittest.skipIf(True, 'Test disabled for now - see bpo-16575/bpo-16576') - def test_bitfield_by_value(self): - # See bpo-16576 - - # These should mirror the structures in Modules/_ctypes/_ctypes_test.c - - class Test6(Structure): - _fields_ = [ - ('A', c_int, 1), - ('B', c_int, 2), - ('C', c_int, 3), - ('D', c_int, 2), - ] - - test6 = Test6() - # As these are signed int fields, all are logically -1 due to sign - # extension. - test6.A = 1 - test6.B = 3 - test6.C = 7 - test6.D = 3 - dll = CDLL(_ctypes_test.__file__) - with self.assertRaises(TypeError) as ctx: - func = dll._testfunc_bitfield_by_value1 - func.restype = c_long - func.argtypes = (Test6,) - result = func(test6) - self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes ' - 'a struct/union with a bitfield by value, which is ' - 'unsupported.') - # passing by reference should be OK - func = dll._testfunc_bitfield_by_reference1 - func.restype = c_long - func.argtypes = (POINTER(Test6),) - result = func(byref(test6)) - self.assertEqual(result, -4) - self.assertEqual(test6.A, 0) - self.assertEqual(test6.B, 0) - self.assertEqual(test6.C, 0) - self.assertEqual(test6.D, 0) - - class Test7(Structure): - _fields_ = [ - ('A', c_uint, 1), - ('B', c_uint, 2), - ('C', c_uint, 3), - ('D', c_uint, 2), - ] - test7 = Test7() - test7.A = 1 - test7.B = 3 - test7.C = 7 - test7.D = 3 - func = dll._testfunc_bitfield_by_reference2 - func.restype = c_long - func.argtypes = (POINTER(Test7),) - result = func(byref(test7)) - self.assertEqual(result, 14) - self.assertEqual(test7.A, 0) - self.assertEqual(test7.B, 0) - self.assertEqual(test7.C, 0) - self.assertEqual(test7.D, 0) - - # for a union with bitfields, the union check happens first - class Test8(Union): - _fields_ = [ - ('A', c_int, 1), - ('B', c_int, 2), - ('C', c_int, 3), - ('D', c_int, 2), - ] - - test8 = Test8() - with self.assertRaises(TypeError) as ctx: - func = dll._testfunc_bitfield_by_value2 - func.restype = c_long - func.argtypes = (Test8,) - result = func(test8) - self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes ' - 'a union by value, which is unsupported.') - -class PointerMemberTestCase(unittest.TestCase): - - def test(self): - # a Structure with a POINTER field - class S(Structure): - _fields_ = [("array", POINTER(c_int))] - - s = S() - # We can assign arrays of the correct type - s.array = (c_int * 3)(1, 2, 3) - items = [s.array[i] for i in range(3)] - self.assertEqual(items, [1, 2, 3]) - - # The following are bugs, but are included here because the unittests - # also describe the current behaviour. - # - # This fails with SystemError: bad arg to internal function - # or with IndexError (with a patch I have) - - s.array[0] = 42 - - items = [s.array[i] for i in range(3)] - self.assertEqual(items, [42, 2, 3]) - - s.array[0] = 1 - -## s.array[1] = 42 - - items = [s.array[i] for i in range(3)] - self.assertEqual(items, [1, 2, 3]) - - def test_none_to_pointer_fields(self): - class S(Structure): - _fields_ = [("x", c_int), - ("p", POINTER(c_int))] - - s = S() - s.x = 12345678 - s.p = None - self.assertEqual(s.x, 12345678) - -class TestRecursiveStructure(unittest.TestCase): - def test_contains_itself(self): - class Recursive(Structure): - pass - - try: - Recursive._fields_ = [("next", Recursive)] - except AttributeError as details: - self.assertIn("Structure or union cannot contain itself", - str(details)) - else: - self.fail("Structure or union cannot contain itself") - - - def test_vice_versa(self): - class First(Structure): - pass - class Second(Structure): - pass - - First._fields_ = [("second", Second)] - - try: - Second._fields_ = [("first", First)] - except AttributeError as details: - self.assertIn("_fields_ is final", str(details)) - else: - self.fail("AttributeError not raised") - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_unaligned_structures.py b/Lib/ctypes/test/test_unaligned_structures.py deleted file mode 100644 index ee7fb45809b..00000000000 --- a/Lib/ctypes/test/test_unaligned_structures.py +++ /dev/null @@ -1,43 +0,0 @@ -import sys, unittest -from ctypes import * - -structures = [] -byteswapped_structures = [] - - -if sys.byteorder == "little": - SwappedStructure = BigEndianStructure -else: - SwappedStructure = LittleEndianStructure - -for typ in [c_short, c_int, c_long, c_longlong, - c_float, c_double, - c_ushort, c_uint, c_ulong, c_ulonglong]: - class X(Structure): - _pack_ = 1 - _fields_ = [("pad", c_byte), - ("value", typ)] - class Y(SwappedStructure): - _pack_ = 1 - _fields_ = [("pad", c_byte), - ("value", typ)] - structures.append(X) - byteswapped_structures.append(Y) - -class TestStructures(unittest.TestCase): - def test_native(self): - for typ in structures: - self.assertEqual(typ.value.offset, 1) - o = typ() - o.value = 4 - self.assertEqual(o.value, 4) - - def test_swapped(self): - for typ in byteswapped_structures: - self.assertEqual(typ.value.offset, 1) - o = typ() - o.value = 4 - self.assertEqual(o.value, 4) - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_unicode.py b/Lib/ctypes/test/test_unicode.py deleted file mode 100644 index 60c75424b76..00000000000 --- a/Lib/ctypes/test/test_unicode.py +++ /dev/null @@ -1,64 +0,0 @@ -import unittest -import ctypes -from ctypes.test import need_symbol - -import _ctypes_test - -@need_symbol('c_wchar') -class UnicodeTestCase(unittest.TestCase): - def test_wcslen(self): - dll = ctypes.CDLL(_ctypes_test.__file__) - wcslen = dll.my_wcslen - wcslen.argtypes = [ctypes.c_wchar_p] - - self.assertEqual(wcslen("abc"), 3) - self.assertEqual(wcslen("ab\u2070"), 3) - self.assertRaises(ctypes.ArgumentError, wcslen, b"ab\xe4") - - def test_buffers(self): - buf = ctypes.create_unicode_buffer("abc") - self.assertEqual(len(buf), 3+1) - - buf = ctypes.create_unicode_buffer("ab\xe4\xf6\xfc") - self.assertEqual(buf[:], "ab\xe4\xf6\xfc\0") - self.assertEqual(buf[::], "ab\xe4\xf6\xfc\0") - self.assertEqual(buf[::-1], '\x00\xfc\xf6\xe4ba') - self.assertEqual(buf[::2], 'a\xe4\xfc') - self.assertEqual(buf[6:5:-1], "") - - def test_embedded_null(self): - class TestStruct(ctypes.Structure): - _fields_ = [("unicode", ctypes.c_wchar_p)] - t = TestStruct() - # This would raise a ValueError: - t.unicode = "foo\0bar\0\0" - - -func = ctypes.CDLL(_ctypes_test.__file__)._testfunc_p_p - -class StringTestCase(UnicodeTestCase): - def setUp(self): - func.argtypes = [ctypes.c_char_p] - func.restype = ctypes.c_char_p - - def tearDown(self): - func.argtypes = None - func.restype = ctypes.c_int - - def test_func(self): - self.assertEqual(func(b"abc\xe4"), b"abc\xe4") - - def test_buffers(self): - buf = ctypes.create_string_buffer(b"abc") - self.assertEqual(len(buf), 3+1) - - buf = ctypes.create_string_buffer(b"ab\xe4\xf6\xfc") - self.assertEqual(buf[:], b"ab\xe4\xf6\xfc\0") - self.assertEqual(buf[::], b"ab\xe4\xf6\xfc\0") - self.assertEqual(buf[::-1], b'\x00\xfc\xf6\xe4ba') - self.assertEqual(buf[::2], b'a\xe4\xfc') - self.assertEqual(buf[6:5:-1], b"") - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_values.py b/Lib/ctypes/test/test_values.py deleted file mode 100644 index 435fdd22ea2..00000000000 --- a/Lib/ctypes/test/test_values.py +++ /dev/null @@ -1,103 +0,0 @@ -""" -A testcase which accesses *values* in a dll. -""" - -import _imp -import importlib.util -import unittest -import sys -from ctypes import * -from test.support import import_helper - -import _ctypes_test - -class ValuesTestCase(unittest.TestCase): - - def test_an_integer(self): - # This test checks and changes an integer stored inside the - # _ctypes_test dll/shared lib. - ctdll = CDLL(_ctypes_test.__file__) - an_integer = c_int.in_dll(ctdll, "an_integer") - x = an_integer.value - self.assertEqual(x, ctdll.get_an_integer()) - an_integer.value *= 2 - self.assertEqual(x*2, ctdll.get_an_integer()) - # To avoid test failures when this test is repeated several - # times the original value must be restored - an_integer.value = x - self.assertEqual(x, ctdll.get_an_integer()) - - def test_undefined(self): - ctdll = CDLL(_ctypes_test.__file__) - self.assertRaises(ValueError, c_int.in_dll, ctdll, "Undefined_Symbol") - -class PythonValuesTestCase(unittest.TestCase): - """This test only works when python itself is a dll/shared library""" - - def test_optimizeflag(self): - # This test accesses the Py_OptimizeFlag integer, which is - # exported by the Python dll and should match the sys.flags value - - opt = c_int.in_dll(pythonapi, "Py_OptimizeFlag").value - self.assertEqual(opt, sys.flags.optimize) - - def test_frozentable(self): - # Python exports a PyImport_FrozenModules symbol. This is a - # pointer to an array of struct _frozen entries. The end of the - # array is marked by an entry containing a NULL name and zero - # size. - - # In standard Python, this table contains a __hello__ - # module, and a __phello__ package containing a spam - # module. - class struct_frozen(Structure): - _fields_ = [("name", c_char_p), - ("code", POINTER(c_ubyte)), - ("size", c_int), - ("is_package", c_int), - ("get_code", POINTER(c_ubyte)), # Function ptr - ] - FrozenTable = POINTER(struct_frozen) - - modules = [] - for group in ["Bootstrap", "Stdlib", "Test"]: - ft = FrozenTable.in_dll(pythonapi, f"_PyImport_Frozen{group}") - # ft is a pointer to the struct_frozen entries: - for entry in ft: - # This is dangerous. We *can* iterate over a pointer, but - # the loop will not terminate (maybe with an access - # violation;-) because the pointer instance has no size. - if entry.name is None: - break - modname = entry.name.decode("ascii") - modules.append(modname) - with self.subTest(modname): - if entry.size != 0: - # Do a sanity check on entry.size and entry.code. - self.assertGreater(abs(entry.size), 10) - self.assertTrue([entry.code[i] for i in range(abs(entry.size))]) - # Check the module's package-ness. - with import_helper.frozen_modules(): - spec = importlib.util.find_spec(modname) - if entry.is_package: - # It's a package. - self.assertIsNotNone(spec.submodule_search_locations) - else: - self.assertIsNone(spec.submodule_search_locations) - - with import_helper.frozen_modules(): - expected = _imp._frozen_module_names() - self.maxDiff = None - self.assertEqual(modules, expected, - "_PyImport_FrozenBootstrap example " - "in Doc/library/ctypes.rst may be out of date") - - from ctypes import _pointer_type_cache - del _pointer_type_cache[struct_frozen] - - def test_undefined(self): - self.assertRaises(ValueError, c_int.in_dll, pythonapi, - "Undefined_Symbol") - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_varsize_struct.py b/Lib/ctypes/test/test_varsize_struct.py deleted file mode 100644 index f409500f013..00000000000 --- a/Lib/ctypes/test/test_varsize_struct.py +++ /dev/null @@ -1,50 +0,0 @@ -from ctypes import * -import unittest - -class VarSizeTest(unittest.TestCase): - def test_resize(self): - class X(Structure): - _fields_ = [("item", c_int), - ("array", c_int * 1)] - - self.assertEqual(sizeof(X), sizeof(c_int) * 2) - x = X() - x.item = 42 - x.array[0] = 100 - self.assertEqual(sizeof(x), sizeof(c_int) * 2) - - # make room for one additional item - new_size = sizeof(X) + sizeof(c_int) * 1 - resize(x, new_size) - self.assertEqual(sizeof(x), new_size) - self.assertEqual((x.item, x.array[0]), (42, 100)) - - # make room for 10 additional items - new_size = sizeof(X) + sizeof(c_int) * 9 - resize(x, new_size) - self.assertEqual(sizeof(x), new_size) - self.assertEqual((x.item, x.array[0]), (42, 100)) - - # make room for one additional item - new_size = sizeof(X) + sizeof(c_int) * 1 - resize(x, new_size) - self.assertEqual(sizeof(x), new_size) - self.assertEqual((x.item, x.array[0]), (42, 100)) - - def test_array_invalid_length(self): - # cannot create arrays with non-positive size - self.assertRaises(ValueError, lambda: c_int * -1) - self.assertRaises(ValueError, lambda: c_int * -3) - - def test_zerosized_array(self): - array = (c_int * 0)() - # accessing elements of zero-sized arrays raise IndexError - self.assertRaises(IndexError, array.__setitem__, 0, None) - self.assertRaises(IndexError, array.__getitem__, 0) - self.assertRaises(IndexError, array.__setitem__, 1, None) - self.assertRaises(IndexError, array.__getitem__, 1) - self.assertRaises(IndexError, array.__setitem__, -1, None) - self.assertRaises(IndexError, array.__getitem__, -1) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_win32.py b/Lib/ctypes/test/test_win32.py deleted file mode 100644 index e51bdc8ad6b..00000000000 --- a/Lib/ctypes/test/test_win32.py +++ /dev/null @@ -1,136 +0,0 @@ -# Windows specific tests - -from ctypes import * -import unittest, sys -from test import support - -import _ctypes_test - -@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') -class FunctionCallTestCase(unittest.TestCase): - @unittest.skipUnless('MSC' in sys.version, "SEH only supported by MSC") - @unittest.skipIf(sys.executable.lower().endswith('_d.exe'), - "SEH not enabled in debug builds") - def test_SEH(self): - # Disable faulthandler to prevent logging the warning: - # "Windows fatal exception: access violation" - with support.disable_faulthandler(): - # Call functions with invalid arguments, and make sure - # that access violations are trapped and raise an - # exception. - self.assertRaises(OSError, windll.kernel32.GetModuleHandleA, 32) - - def test_noargs(self): - # This is a special case on win32 x64 - windll.user32.GetDesktopWindow() - - -@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') -class ReturnStructSizesTestCase(unittest.TestCase): - def test_sizes(self): - dll = CDLL(_ctypes_test.__file__) - for i in range(1, 11): - fields = [ (f"f{f}", c_char) for f in range(1, i + 1)] - class S(Structure): - _fields_ = fields - f = getattr(dll, f"TestSize{i}") - f.restype = S - res = f() - for i, f in enumerate(fields): - value = getattr(res, f[0]) - expected = bytes([ord('a') + i]) - self.assertEqual(value, expected) - - - -@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') -class TestWintypes(unittest.TestCase): - def test_HWND(self): - from ctypes import wintypes - self.assertEqual(sizeof(wintypes.HWND), sizeof(c_void_p)) - - def test_PARAM(self): - from ctypes import wintypes - self.assertEqual(sizeof(wintypes.WPARAM), - sizeof(c_void_p)) - self.assertEqual(sizeof(wintypes.LPARAM), - sizeof(c_void_p)) - - def test_COMError(self): - from _ctypes import COMError - if support.HAVE_DOCSTRINGS: - self.assertEqual(COMError.__doc__, - "Raised when a COM method call failed.") - - ex = COMError(-1, "text", ("details",)) - self.assertEqual(ex.hresult, -1) - self.assertEqual(ex.text, "text") - self.assertEqual(ex.details, ("details",)) - -@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') -class TestWinError(unittest.TestCase): - def test_winerror(self): - # see Issue 16169 - import errno - ERROR_INVALID_PARAMETER = 87 - msg = FormatError(ERROR_INVALID_PARAMETER).strip() - args = (errno.EINVAL, msg, None, ERROR_INVALID_PARAMETER) - - e = WinError(ERROR_INVALID_PARAMETER) - self.assertEqual(e.args, args) - self.assertEqual(e.errno, errno.EINVAL) - self.assertEqual(e.winerror, ERROR_INVALID_PARAMETER) - - windll.kernel32.SetLastError(ERROR_INVALID_PARAMETER) - try: - raise WinError() - except OSError as exc: - e = exc - self.assertEqual(e.args, args) - self.assertEqual(e.errno, errno.EINVAL) - self.assertEqual(e.winerror, ERROR_INVALID_PARAMETER) - -class Structures(unittest.TestCase): - def test_struct_by_value(self): - class POINT(Structure): - _fields_ = [("x", c_long), - ("y", c_long)] - - class RECT(Structure): - _fields_ = [("left", c_long), - ("top", c_long), - ("right", c_long), - ("bottom", c_long)] - - dll = CDLL(_ctypes_test.__file__) - - pt = POINT(15, 25) - left = c_long.in_dll(dll, 'left') - top = c_long.in_dll(dll, 'top') - right = c_long.in_dll(dll, 'right') - bottom = c_long.in_dll(dll, 'bottom') - rect = RECT(left, top, right, bottom) - PointInRect = dll.PointInRect - PointInRect.argtypes = [POINTER(RECT), POINT] - self.assertEqual(1, PointInRect(byref(rect), pt)) - - ReturnRect = dll.ReturnRect - ReturnRect.argtypes = [c_int, RECT, POINTER(RECT), POINT, RECT, - POINTER(RECT), POINT, RECT] - ReturnRect.restype = RECT - for i in range(4): - ret = ReturnRect(i, rect, pointer(rect), pt, rect, - byref(rect), pt, rect) - # the c function will check and modify ret if something is - # passed in improperly - self.assertEqual(ret.left, left.value) - self.assertEqual(ret.right, right.value) - self.assertEqual(ret.top, top.value) - self.assertEqual(ret.bottom, bottom.value) - - # to not leak references, we must clean _pointer_type_cache - from ctypes import _pointer_type_cache - del _pointer_type_cache[RECT] - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_wintypes.py b/Lib/ctypes/test/test_wintypes.py deleted file mode 100644 index 243d5962ffa..00000000000 --- a/Lib/ctypes/test/test_wintypes.py +++ /dev/null @@ -1,43 +0,0 @@ -import unittest - -# also work on POSIX - -from ctypes import * -from ctypes import wintypes - - -class WinTypesTest(unittest.TestCase): - def test_variant_bool(self): - # reads 16-bits from memory, anything non-zero is True - for true_value in (1, 32767, 32768, 65535, 65537): - true = POINTER(c_int16)(c_int16(true_value)) - value = cast(true, POINTER(wintypes.VARIANT_BOOL)) - self.assertEqual(repr(value.contents), 'VARIANT_BOOL(True)') - - vb = wintypes.VARIANT_BOOL() - self.assertIs(vb.value, False) - vb.value = True - self.assertIs(vb.value, True) - vb.value = true_value - self.assertIs(vb.value, True) - - for false_value in (0, 65536, 262144, 2**33): - false = POINTER(c_int16)(c_int16(false_value)) - value = cast(false, POINTER(wintypes.VARIANT_BOOL)) - self.assertEqual(repr(value.contents), 'VARIANT_BOOL(False)') - - # allow any bool conversion on assignment to value - for set_value in (65536, 262144, 2**33): - vb = wintypes.VARIANT_BOOL() - vb.value = set_value - self.assertIs(vb.value, True) - - vb = wintypes.VARIANT_BOOL() - vb.value = [2, 3] - self.assertIs(vb.value, True) - vb.value = [] - self.assertIs(vb.value, False) - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py index 0c2510e1619..117bf06cb01 100644 --- a/Lib/ctypes/util.py +++ b/Lib/ctypes/util.py @@ -67,7 +67,7 @@ def find_library(name): return fname return None -elif os.name == "posix" and sys.platform == "darwin": +elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}: from ctypes.macholib.dyld import dyld_find as _dyld_find def find_library(name): possible = ['lib%s.dylib' % name, @@ -89,6 +89,15 @@ def find_library(name): from ctypes._aix import find_library +elif sys.platform == "android": + def find_library(name): + directory = "/system/lib" + if "64" in os.uname().machine: + directory += "64" + + fname = f"{directory}/lib{name}.so" + return fname if os.path.isfile(fname) else None + elif os.name == "posix": # Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump import re, tempfile @@ -96,8 +105,11 @@ def find_library(name): def _is_elf(filename): "Return True if the given file is an ELF file" elf_header = b'\x7fELF' - with open(filename, 'br') as thefile: - return thefile.read(4) == elf_header + try: + with open(filename, 'br') as thefile: + return thefile.read(4) == elf_header + except FileNotFoundError: + return False def _findLib_gcc(name): # Run GCC's linker with the -t (aka --trace) option and examine the From 7e6e0f66c8b1ffb63b45f778c7e2b1857eaaa9e1 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Sat, 20 Dec 2025 14:32:07 +0900 Subject: [PATCH 551/819] test_ctypes from CPython 3.13.11 --- Lib/test/test_ctypes.py | 10 - Lib/test/test_ctypes/__init__.py | 10 + Lib/test/test_ctypes/__main__.py | 4 + Lib/test/test_ctypes/_support.py | 24 + .../test_ctypes/test_aligned_structures.py | 321 ++++++ Lib/test/test_ctypes/test_anon.py | 75 ++ Lib/test/test_ctypes/test_array_in_pointer.py | 68 ++ Lib/test/test_ctypes/test_arrays.py | 272 +++++ Lib/test/test_ctypes/test_as_parameter.py | 245 +++++ Lib/test/test_ctypes/test_bitfields.py | 294 ++++++ Lib/test/test_ctypes/test_buffers.py | 70 ++ Lib/test/test_ctypes/test_bytes.py | 67 ++ Lib/test/test_ctypes/test_byteswap.py | 386 +++++++ .../test_ctypes/test_c_simple_type_meta.py | 152 +++ Lib/test/test_ctypes/test_callbacks.py | 333 ++++++ Lib/test/test_ctypes/test_cast.py | 100 ++ Lib/test/test_ctypes/test_cfuncs.py | 211 ++++ Lib/test/test_ctypes/test_checkretval.py | 37 + Lib/test/test_ctypes/test_delattr.py | 26 + Lib/test/test_ctypes/test_dlerror.py | 179 ++++ Lib/test/test_ctypes/test_errno.py | 81 ++ Lib/test/test_ctypes/test_find.py | 156 +++ Lib/test/test_ctypes/test_frombuffer.py | 144 +++ Lib/test/test_ctypes/test_funcptr.py | 134 +++ Lib/test/test_ctypes/test_functions.py | 454 +++++++++ Lib/test/test_ctypes/test_incomplete.py | 50 + Lib/test/test_ctypes/test_init.py | 43 + Lib/test/test_ctypes/test_internals.py | 97 ++ Lib/test/test_ctypes/test_keeprefs.py | 124 +++ Lib/test/test_ctypes/test_libc.py | 38 + Lib/test/test_ctypes/test_loading.py | 208 ++++ Lib/test/test_ctypes/test_macholib.py | 112 ++ Lib/test/test_ctypes/test_memfunctions.py | 82 ++ Lib/test/test_ctypes/test_numbers.py | 200 ++++ Lib/test/test_ctypes/test_objects.py | 66 ++ Lib/test/test_ctypes/test_parameters.py | 288 ++++++ Lib/test/test_ctypes/test_pep3118.py | 250 +++++ Lib/test/test_ctypes/test_pickling.py | 91 ++ Lib/test/test_ctypes/test_pointers.py | 240 +++++ Lib/test/test_ctypes/test_prototypes.py | 252 +++++ Lib/test/test_ctypes/test_python_api.py | 81 ++ Lib/test/test_ctypes/test_random_things.py | 81 ++ Lib/test/test_ctypes/test_refcounts.py | 143 +++ Lib/test/test_ctypes/test_repr.py | 34 + Lib/test/test_ctypes/test_returnfuncptrs.py | 67 ++ Lib/test/test_ctypes/test_simplesubclasses.py | 96 ++ Lib/test/test_ctypes/test_sizes.py | 38 + Lib/test/test_ctypes/test_slicing.py | 170 ++++ Lib/test/test_ctypes/test_stringptr.py | 81 ++ Lib/test/test_ctypes/test_strings.py | 134 +++ Lib/test/test_ctypes/test_struct_fields.py | 126 +++ Lib/test/test_ctypes/test_structures.py | 956 ++++++++++++++++++ .../test_ctypes/test_unaligned_structures.py | 49 + Lib/test/test_ctypes/test_unicode.py | 63 ++ Lib/test/test_ctypes/test_unions.py | 35 + Lib/test/test_ctypes/test_values.py | 107 ++ Lib/test/test_ctypes/test_varsize_struct.py | 52 + Lib/test/test_ctypes/test_win32.py | 152 +++ .../test_win32_com_foreign_func.py | 286 ++++++ Lib/test/test_ctypes/test_wintypes.py | 61 ++ 60 files changed, 8796 insertions(+), 10 deletions(-) delete mode 100644 Lib/test/test_ctypes.py create mode 100644 Lib/test/test_ctypes/__init__.py create mode 100644 Lib/test/test_ctypes/__main__.py create mode 100644 Lib/test/test_ctypes/_support.py create mode 100644 Lib/test/test_ctypes/test_aligned_structures.py create mode 100644 Lib/test/test_ctypes/test_anon.py create mode 100644 Lib/test/test_ctypes/test_array_in_pointer.py create mode 100644 Lib/test/test_ctypes/test_arrays.py create mode 100644 Lib/test/test_ctypes/test_as_parameter.py create mode 100644 Lib/test/test_ctypes/test_bitfields.py create mode 100644 Lib/test/test_ctypes/test_buffers.py create mode 100644 Lib/test/test_ctypes/test_bytes.py create mode 100644 Lib/test/test_ctypes/test_byteswap.py create mode 100644 Lib/test/test_ctypes/test_c_simple_type_meta.py create mode 100644 Lib/test/test_ctypes/test_callbacks.py create mode 100644 Lib/test/test_ctypes/test_cast.py create mode 100644 Lib/test/test_ctypes/test_cfuncs.py create mode 100644 Lib/test/test_ctypes/test_checkretval.py create mode 100644 Lib/test/test_ctypes/test_delattr.py create mode 100644 Lib/test/test_ctypes/test_dlerror.py create mode 100644 Lib/test/test_ctypes/test_errno.py create mode 100644 Lib/test/test_ctypes/test_find.py create mode 100644 Lib/test/test_ctypes/test_frombuffer.py create mode 100644 Lib/test/test_ctypes/test_funcptr.py create mode 100644 Lib/test/test_ctypes/test_functions.py create mode 100644 Lib/test/test_ctypes/test_incomplete.py create mode 100644 Lib/test/test_ctypes/test_init.py create mode 100644 Lib/test/test_ctypes/test_internals.py create mode 100644 Lib/test/test_ctypes/test_keeprefs.py create mode 100644 Lib/test/test_ctypes/test_libc.py create mode 100644 Lib/test/test_ctypes/test_loading.py create mode 100644 Lib/test/test_ctypes/test_macholib.py create mode 100644 Lib/test/test_ctypes/test_memfunctions.py create mode 100644 Lib/test/test_ctypes/test_numbers.py create mode 100644 Lib/test/test_ctypes/test_objects.py create mode 100644 Lib/test/test_ctypes/test_parameters.py create mode 100644 Lib/test/test_ctypes/test_pep3118.py create mode 100644 Lib/test/test_ctypes/test_pickling.py create mode 100644 Lib/test/test_ctypes/test_pointers.py create mode 100644 Lib/test/test_ctypes/test_prototypes.py create mode 100644 Lib/test/test_ctypes/test_python_api.py create mode 100644 Lib/test/test_ctypes/test_random_things.py create mode 100644 Lib/test/test_ctypes/test_refcounts.py create mode 100644 Lib/test/test_ctypes/test_repr.py create mode 100644 Lib/test/test_ctypes/test_returnfuncptrs.py create mode 100644 Lib/test/test_ctypes/test_simplesubclasses.py create mode 100644 Lib/test/test_ctypes/test_sizes.py create mode 100644 Lib/test/test_ctypes/test_slicing.py create mode 100644 Lib/test/test_ctypes/test_stringptr.py create mode 100644 Lib/test/test_ctypes/test_strings.py create mode 100644 Lib/test/test_ctypes/test_struct_fields.py create mode 100644 Lib/test/test_ctypes/test_structures.py create mode 100644 Lib/test/test_ctypes/test_unaligned_structures.py create mode 100644 Lib/test/test_ctypes/test_unicode.py create mode 100644 Lib/test/test_ctypes/test_unions.py create mode 100644 Lib/test/test_ctypes/test_values.py create mode 100644 Lib/test/test_ctypes/test_varsize_struct.py create mode 100644 Lib/test/test_ctypes/test_win32.py create mode 100644 Lib/test/test_ctypes/test_win32_com_foreign_func.py create mode 100644 Lib/test/test_ctypes/test_wintypes.py diff --git a/Lib/test/test_ctypes.py b/Lib/test/test_ctypes.py deleted file mode 100644 index b0a12c97347..00000000000 --- a/Lib/test/test_ctypes.py +++ /dev/null @@ -1,10 +0,0 @@ -import unittest -from test.support.import_helper import import_module - - -ctypes_test = import_module('ctypes.test') - -load_tests = ctypes_test.load_tests - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/test/test_ctypes/__init__.py b/Lib/test/test_ctypes/__init__.py new file mode 100644 index 00000000000..eb9126cbe18 --- /dev/null +++ b/Lib/test/test_ctypes/__init__.py @@ -0,0 +1,10 @@ +import os +from test import support +from test.support import import_helper + + +# skip tests if the _ctypes extension was not built +import_helper.import_module('ctypes') + +def load_tests(*args): + return support.load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_ctypes/__main__.py b/Lib/test/test_ctypes/__main__.py new file mode 100644 index 00000000000..3003d4db890 --- /dev/null +++ b/Lib/test/test_ctypes/__main__.py @@ -0,0 +1,4 @@ +from test.test_ctypes import load_tests +import unittest + +unittest.main() diff --git a/Lib/test/test_ctypes/_support.py b/Lib/test/test_ctypes/_support.py new file mode 100644 index 00000000000..e4c2b33825a --- /dev/null +++ b/Lib/test/test_ctypes/_support.py @@ -0,0 +1,24 @@ +# Some classes and types are not export to _ctypes module directly. + +import ctypes +from _ctypes import Structure, Union, _Pointer, Array, _SimpleCData, CFuncPtr + + +_CData = Structure.__base__ +assert _CData.__name__ == "_CData" + +class _X(Structure): + _fields_ = [("x", ctypes.c_int)] +CField = type(_X.x) + +# metaclasses +PyCStructType = type(Structure) +UnionType = type(Union) +PyCPointerType = type(_Pointer) +PyCArrayType = type(Array) +PyCSimpleType = type(_SimpleCData) +PyCFuncPtrType = type(CFuncPtr) + +# type flags +Py_TPFLAGS_DISALLOW_INSTANTIATION = 1 << 7 +Py_TPFLAGS_IMMUTABLETYPE = 1 << 8 diff --git a/Lib/test/test_ctypes/test_aligned_structures.py b/Lib/test/test_ctypes/test_aligned_structures.py new file mode 100644 index 00000000000..8e8ac429900 --- /dev/null +++ b/Lib/test/test_ctypes/test_aligned_structures.py @@ -0,0 +1,321 @@ +from ctypes import ( + c_char, c_uint32, c_uint16, c_ubyte, c_byte, alignment, sizeof, + BigEndianStructure, LittleEndianStructure, + BigEndianUnion, LittleEndianUnion, Structure +) +import struct +import unittest + + +class TestAlignedStructures(unittest.TestCase): + def test_aligned_string(self): + for base, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), + ): + data = bytearray(struct.pack(f"{e}i12x16s", 7, b"hello world!")) + class Aligned(base): + _align_ = 16 + _fields_ = [ + ('value', c_char * 12) + ] + + class Main(base): + _fields_ = [ + ('first', c_uint32), + ('string', Aligned), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.first, 7) + self.assertEqual(main.string.value, b'hello world!') + self.assertEqual(bytes(main.string), b'hello world!\0\0\0\0') + self.assertEqual(Main.string.offset, 16) + self.assertEqual(Main.string.size, 16) + self.assertEqual(alignment(main.string), 16) + self.assertEqual(alignment(main), 16) + + def test_aligned_structures(self): + for base, data in ( + (LittleEndianStructure, bytearray(b"\1\0\0\0\1\0\0\0\7\0\0\0")), + (BigEndianStructure, bytearray(b"\1\0\0\0\1\0\0\0\7\0\0\0")), + ): + class SomeBools(base): + _align_ = 4 + _fields_ = [ + ("bool1", c_ubyte), + ("bool2", c_ubyte), + ] + class Main(base): + _fields_ = [ + ("x", c_ubyte), + ("y", SomeBools), + ("z", c_ubyte), + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(SomeBools), 4) + self.assertEqual(alignment(main), 4) + self.assertEqual(alignment(main.y), 4) + self.assertEqual(Main.x.size, 1) + self.assertEqual(Main.y.offset, 4) + self.assertEqual(Main.y.size, 4) + self.assertEqual(main.y.bool1, True) + self.assertEqual(main.y.bool2, False) + self.assertEqual(Main.z.offset, 8) + self.assertEqual(main.z, 7) + + def test_oversized_structure(self): + data = bytearray(b"\0" * 8) + for base in (LittleEndianStructure, BigEndianStructure): + class SomeBoolsTooBig(base): + _align_ = 8 + _fields_ = [ + ("bool1", c_ubyte), + ("bool2", c_ubyte), + ("bool3", c_ubyte), + ] + class Main(base): + _fields_ = [ + ("y", SomeBoolsTooBig), + ("z", c_uint32), + ] + with self.assertRaises(ValueError) as ctx: + Main.from_buffer(data) + self.assertEqual( + ctx.exception.args[0], + 'Buffer size too small (4 instead of at least 8 bytes)' + ) + + def test_aligned_subclasses(self): + for base, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), + ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) + class UnalignedSub(base): + x: c_uint32 + _fields_ = [ + ("x", c_uint32), + ] + + class AlignedStruct(UnalignedSub): + _align_ = 8 + _fields_ = [ + ("y", c_uint32), + ] + + class Main(base): + _fields_ = [ + ("a", c_uint32), + ("b", AlignedStruct) + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(main.b), 8) + self.assertEqual(alignment(main), 8) + self.assertEqual(sizeof(main.b), 8) + self.assertEqual(sizeof(main), 16) + self.assertEqual(main.a, 1) + self.assertEqual(main.b.x, 3) + self.assertEqual(main.b.y, 4) + self.assertEqual(Main.b.offset, 8) + self.assertEqual(Main.b.size, 8) + + def test_aligned_union(self): + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) + class AlignedUnion(ubase): + _align_ = 8 + _fields_ = [ + ("a", c_uint32), + ("b", c_ubyte * 7), + ] + + class Main(sbase): + _fields_ = [ + ("first", c_uint32), + ("union", AlignedUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.first, 1) + self.assertEqual(main.union.a, 3) + self.assertEqual(bytes(main.union.b), data[8:-1]) + self.assertEqual(Main.union.offset, 8) + self.assertEqual(Main.union.size, 8) + self.assertEqual(alignment(main.union), 8) + self.assertEqual(alignment(main), 8) + + def test_aligned_struct_in_union(self): + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) + class Sub(sbase): + _align_ = 8 + _fields_ = [ + ("x", c_uint32), + ("y", c_uint32), + ] + + class MainUnion(ubase): + _fields_ = [ + ("a", c_uint32), + ("b", Sub), + ] + + class Main(sbase): + _fields_ = [ + ("first", c_uint32), + ("union", MainUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(Main.first.size, 4) + self.assertEqual(alignment(main.union), 8) + self.assertEqual(alignment(main), 8) + self.assertEqual(Main.union.offset, 8) + self.assertEqual(Main.union.size, 8) + self.assertEqual(main.first, 1) + self.assertEqual(main.union.a, 3) + self.assertEqual(main.union.b.x, 3) + self.assertEqual(main.union.b.y, 4) + + def test_smaller_aligned_subclassed_union(self): + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}H2xI", 1, 0xD60102D7)) + class SubUnion(ubase): + _align_ = 2 + _fields_ = [ + ("unsigned", c_ubyte), + ("signed", c_byte), + ] + + class MainUnion(SubUnion): + _fields_ = [ + ("num", c_uint32) + ] + + class Main(sbase): + _fields_ = [ + ("first", c_uint16), + ("union", MainUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.union.num, 0xD60102D7) + self.assertEqual(main.union.unsigned, data[4]) + self.assertEqual(main.union.signed, data[4] - 256) + self.assertEqual(alignment(main), 4) + self.assertEqual(alignment(main.union), 4) + self.assertEqual(Main.union.offset, 4) + self.assertEqual(Main.union.size, 4) + self.assertEqual(Main.first.size, 2) + + def test_larger_aligned_subclassed_union(self): + for ubase, e in ( + (LittleEndianUnion, "<"), + (BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}I4x", 0xD60102D6)) + class SubUnion(ubase): + _align_ = 8 + _fields_ = [ + ("unsigned", c_ubyte), + ("signed", c_byte), + ] + + class Main(SubUnion): + _fields_ = [ + ("num", c_uint32) + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(main), 8) + self.assertEqual(sizeof(main), 8) + self.assertEqual(main.num, 0xD60102D6) + self.assertEqual(main.unsigned, 0xD6) + self.assertEqual(main.signed, -42) + + def test_aligned_packed_structures(self): + for sbase, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), + ): + data = bytearray(struct.pack(f"{e}B2H4xB", 1, 2, 3, 4)) + + class Inner(sbase): + _align_ = 8 + _fields_ = [ + ("x", c_uint16), + ("y", c_uint16), + ] + + class Main(sbase): + _pack_ = 1 + _fields_ = [ + ("a", c_ubyte), + ("b", Inner), + ("c", c_ubyte), + ] + + main = Main.from_buffer(data) + self.assertEqual(sizeof(main), 10) + self.assertEqual(Main.b.offset, 1) + # Alignment == 8 because _pack_ wins out. + self.assertEqual(alignment(main.b), 8) + # Size is still 8 though since inside this Structure, it will have + # effect. + self.assertEqual(sizeof(main.b), 8) + self.assertEqual(Main.c.offset, 9) + self.assertEqual(main.a, 1) + self.assertEqual(main.b.x, 2) + self.assertEqual(main.b.y, 3) + self.assertEqual(main.c, 4) + + def test_negative_align(self): + for base in (Structure, LittleEndianStructure, BigEndianStructure): + with ( + self.subTest(base=base), + self.assertRaisesRegex( + ValueError, + '_align_ must be a non-negative integer', + ) + ): + class MyStructure(base): + _align_ = -1 + _fields_ = [] + + def test_zero_align_no_fields(self): + for base in (Structure, LittleEndianStructure, BigEndianStructure): + with self.subTest(base=base): + class MyStructure(base): + _align_ = 0 + _fields_ = [] + + self.assertEqual(alignment(MyStructure), 1) + self.assertEqual(alignment(MyStructure()), 1) + + def test_zero_align_with_fields(self): + for base in (Structure, LittleEndianStructure, BigEndianStructure): + with self.subTest(base=base): + class MyStructure(base): + _align_ = 0 + _fields_ = [ + ("x", c_ubyte), + ] + + self.assertEqual(alignment(MyStructure), 1) + self.assertEqual(alignment(MyStructure()), 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_anon.py b/Lib/test/test_ctypes/test_anon.py new file mode 100644 index 00000000000..b36397b510f --- /dev/null +++ b/Lib/test/test_ctypes/test_anon.py @@ -0,0 +1,75 @@ +import unittest +import test.support +from ctypes import c_int, Union, Structure, sizeof + + +class AnonTest(unittest.TestCase): + + def test_anon(self): + class ANON(Union): + _fields_ = [("a", c_int), + ("b", c_int)] + + class Y(Structure): + _fields_ = [("x", c_int), + ("_", ANON), + ("y", c_int)] + _anonymous_ = ["_"] + + self.assertEqual(Y.a.offset, sizeof(c_int)) + self.assertEqual(Y.b.offset, sizeof(c_int)) + + self.assertEqual(ANON.a.offset, 0) + self.assertEqual(ANON.b.offset, 0) + + def test_anon_nonseq(self): + # TypeError: _anonymous_ must be a sequence + self.assertRaises(TypeError, + lambda: type(Structure)("Name", + (Structure,), + {"_fields_": [], "_anonymous_": 42})) + + def test_anon_nonmember(self): + # AttributeError: type object 'Name' has no attribute 'x' + self.assertRaises(AttributeError, + lambda: type(Structure)("Name", + (Structure,), + {"_fields_": [], + "_anonymous_": ["x"]})) + + @test.support.cpython_only + def test_issue31490(self): + # There shouldn't be an assertion failure in case the class has an + # attribute whose name is specified in _anonymous_ but not in _fields_. + + # AttributeError: 'x' is specified in _anonymous_ but not in _fields_ + with self.assertRaises(AttributeError): + class Name(Structure): + _fields_ = [] + _anonymous_ = ["x"] + x = 42 + + def test_nested(self): + class ANON_S(Structure): + _fields_ = [("a", c_int)] + + class ANON_U(Union): + _fields_ = [("_", ANON_S), + ("b", c_int)] + _anonymous_ = ["_"] + + class Y(Structure): + _fields_ = [("x", c_int), + ("_", ANON_U), + ("y", c_int)] + _anonymous_ = ["_"] + + self.assertEqual(Y.x.offset, 0) + self.assertEqual(Y.a.offset, sizeof(c_int)) + self.assertEqual(Y.b.offset, sizeof(c_int)) + self.assertEqual(Y._.offset, sizeof(c_int)) + self.assertEqual(Y.y.offset, sizeof(c_int) * 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_array_in_pointer.py b/Lib/test/test_ctypes/test_array_in_pointer.py new file mode 100644 index 00000000000..b7c96b2fa49 --- /dev/null +++ b/Lib/test/test_ctypes/test_array_in_pointer.py @@ -0,0 +1,68 @@ +import binascii +import re +import unittest +from ctypes import c_byte, Structure, POINTER, cast + + +def dump(obj): + # helper function to dump memory contents in hex, with a hyphen + # between the bytes. + h = binascii.hexlify(memoryview(obj)).decode() + return re.sub(r"(..)", r"\1-", h)[:-1] + + +class Value(Structure): + _fields_ = [("val", c_byte)] + + +class Container(Structure): + _fields_ = [("pvalues", POINTER(Value))] + + +class Test(unittest.TestCase): + def test(self): + # create an array of 4 values + val_array = (Value * 4)() + + # create a container, which holds a pointer to the pvalues array. + c = Container() + c.pvalues = val_array + + # memory contains 4 NUL bytes now, that's correct + self.assertEqual("00-00-00-00", dump(val_array)) + + # set the values of the array through the pointer: + for i in range(4): + c.pvalues[i].val = i + 1 + + values = [c.pvalues[i].val for i in range(4)] + + # These are the expected results: here s the bug! + self.assertEqual( + (values, dump(val_array)), + ([1, 2, 3, 4], "01-02-03-04") + ) + + def test_2(self): + + val_array = (Value * 4)() + + # memory contains 4 NUL bytes now, that's correct + self.assertEqual("00-00-00-00", dump(val_array)) + + ptr = cast(val_array, POINTER(Value)) + # set the values of the array through the pointer: + for i in range(4): + ptr[i].val = i + 1 + + values = [ptr[i].val for i in range(4)] + + # These are the expected results: here s the bug! + self.assertEqual( + (values, dump(val_array)), + ([1, 2, 3, 4], "01-02-03-04") + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_arrays.py b/Lib/test/test_ctypes/test_arrays.py new file mode 100644 index 00000000000..c80fdff5de6 --- /dev/null +++ b/Lib/test/test_ctypes/test_arrays.py @@ -0,0 +1,272 @@ +import ctypes +import sys +import unittest +from ctypes import (Structure, Array, ARRAY, sizeof, addressof, + create_string_buffer, create_unicode_buffer, + c_char, c_wchar, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, + c_long, c_ulonglong, c_float, c_double, c_longdouble) +from test.support import bigmemtest, _2G +from ._support import (_CData, PyCArrayType, Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_IMMUTABLETYPE) + + +formats = "bBhHiIlLqQfd" + +formats = c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, \ + c_long, c_ulonglong, c_float, c_double, c_longdouble + + +class ArrayTestCase(unittest.TestCase): + def test_inheritance_hierarchy(self): + self.assertEqual(Array.mro(), [Array, _CData, object]) + + self.assertEqual(PyCArrayType.__name__, "PyCArrayType") + self.assertEqual(type(PyCArrayType), type) + + def test_type_flags(self): + for cls in Array, PyCArrayType: + with self.subTest(cls=cls): + self.assertTrue(cls.__flags__ & Py_TPFLAGS_IMMUTABLETYPE) + self.assertFalse(cls.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION) + + def test_metaclass_details(self): + # Abstract classes (whose metaclass __init__ was not called) can't be + # instantiated directly + NewArray = PyCArrayType.__new__(PyCArrayType, 'NewArray', (Array,), {}) + for cls in Array, NewArray: + with self.subTest(cls=cls): + with self.assertRaisesRegex(TypeError, "abstract class"): + obj = cls() + + # Cannot call the metaclass __init__ more than once + class T(Array): + _type_ = c_int + _length_ = 13 + with self.assertRaisesRegex(SystemError, "already initialized"): + PyCArrayType.__init__(T, 'ptr', (), {}) + + def test_simple(self): + # create classes holding simple numeric types, and check + # various properties. + + init = list(range(15, 25)) + + for fmt in formats: + alen = len(init) + int_array = ARRAY(fmt, alen) + + ia = int_array(*init) + # length of instance ok? + self.assertEqual(len(ia), alen) + + # slot values ok? + values = [ia[i] for i in range(alen)] + self.assertEqual(values, init) + + # out-of-bounds accesses should be caught + with self.assertRaises(IndexError): ia[alen] + with self.assertRaises(IndexError): ia[-alen-1] + + # change the items + new_values = list(range(42, 42+alen)) + for n in range(alen): + ia[n] = new_values[n] + values = [ia[i] for i in range(alen)] + self.assertEqual(values, new_values) + + # are the items initialized to 0? + ia = int_array() + values = [ia[i] for i in range(alen)] + self.assertEqual(values, [0] * alen) + + # Too many initializers should be caught + self.assertRaises(IndexError, int_array, *range(alen*2)) + + CharArray = ARRAY(c_char, 3) + + ca = CharArray(b"a", b"b", b"c") + + # Should this work? It doesn't: + # CharArray("abc") + self.assertRaises(TypeError, CharArray, "abc") + + self.assertEqual(ca[0], b"a") + self.assertEqual(ca[1], b"b") + self.assertEqual(ca[2], b"c") + self.assertEqual(ca[-3], b"a") + self.assertEqual(ca[-2], b"b") + self.assertEqual(ca[-1], b"c") + + self.assertEqual(len(ca), 3) + + # cannot delete items + with self.assertRaises(TypeError): + del ca[0] + + def test_step_overflow(self): + a = (c_int * 5)() + a[3::sys.maxsize] = (1,) + self.assertListEqual(a[3::sys.maxsize], [1]) + a = (c_char * 5)() + a[3::sys.maxsize] = b"A" + self.assertEqual(a[3::sys.maxsize], b"A") + a = (c_wchar * 5)() + a[3::sys.maxsize] = u"X" + self.assertEqual(a[3::sys.maxsize], u"X") + + def test_numeric_arrays(self): + + alen = 5 + + numarray = ARRAY(c_int, alen) + + na = numarray() + values = [na[i] for i in range(alen)] + self.assertEqual(values, [0] * alen) + + na = numarray(*[c_int()] * alen) + values = [na[i] for i in range(alen)] + self.assertEqual(values, [0]*alen) + + na = numarray(1, 2, 3, 4, 5) + values = [i for i in na] + self.assertEqual(values, [1, 2, 3, 4, 5]) + + na = numarray(*map(c_int, (1, 2, 3, 4, 5))) + values = [i for i in na] + self.assertEqual(values, [1, 2, 3, 4, 5]) + + def test_classcache(self): + self.assertIsNot(ARRAY(c_int, 3), ARRAY(c_int, 4)) + self.assertIs(ARRAY(c_int, 3), ARRAY(c_int, 3)) + + def test_from_address(self): + # Failed with 0.9.8, reported by JUrner + p = create_string_buffer(b"foo") + sz = (c_char * 3).from_address(addressof(p)) + self.assertEqual(sz[:], b"foo") + self.assertEqual(sz[::], b"foo") + self.assertEqual(sz[::-1], b"oof") + self.assertEqual(sz[::3], b"f") + self.assertEqual(sz[1:4:2], b"o") + self.assertEqual(sz.value, b"foo") + + def test_from_addressW(self): + p = create_unicode_buffer("foo") + sz = (c_wchar * 3).from_address(addressof(p)) + self.assertEqual(sz[:], "foo") + self.assertEqual(sz[::], "foo") + self.assertEqual(sz[::-1], "oof") + self.assertEqual(sz[::3], "f") + self.assertEqual(sz[1:4:2], "o") + self.assertEqual(sz.value, "foo") + + def test_cache(self): + # Array types are cached internally in the _ctypes extension, + # in a WeakValueDictionary. Make sure the array type is + # removed from the cache when the itemtype goes away. This + # test will not fail, but will show a leak in the testsuite. + + # Create a new type: + class my_int(c_int): + pass + # Create a new array type based on it: + t1 = my_int * 1 + t2 = my_int * 1 + self.assertIs(t1, t2) + + def test_subclass(self): + class T(Array): + _type_ = c_int + _length_ = 13 + class U(T): + pass + class V(U): + pass + class W(V): + pass + class X(T): + _type_ = c_short + class Y(T): + _length_ = 187 + + for c in [T, U, V, W]: + self.assertEqual(c._type_, c_int) + self.assertEqual(c._length_, 13) + self.assertEqual(c()._type_, c_int) + self.assertEqual(c()._length_, 13) + + self.assertEqual(X._type_, c_short) + self.assertEqual(X._length_, 13) + self.assertEqual(X()._type_, c_short) + self.assertEqual(X()._length_, 13) + + self.assertEqual(Y._type_, c_int) + self.assertEqual(Y._length_, 187) + self.assertEqual(Y()._type_, c_int) + self.assertEqual(Y()._length_, 187) + + def test_bad_subclass(self): + with self.assertRaises(AttributeError): + class T(Array): + pass + with self.assertRaises(AttributeError): + class T2(Array): + _type_ = c_int + with self.assertRaises(AttributeError): + class T3(Array): + _length_ = 13 + + def test_bad_length(self): + with self.assertRaises(ValueError): + class T(Array): + _type_ = c_int + _length_ = - sys.maxsize * 2 + with self.assertRaises(ValueError): + class T2(Array): + _type_ = c_int + _length_ = -1 + with self.assertRaises(TypeError): + class T3(Array): + _type_ = c_int + _length_ = 1.87 + with self.assertRaises(OverflowError): + class T4(Array): + _type_ = c_int + _length_ = sys.maxsize * 2 + + def test_zero_length(self): + # _length_ can be zero. + class T(Array): + _type_ = c_int + _length_ = 0 + + def test_empty_element_struct(self): + class EmptyStruct(Structure): + _fields_ = [] + + obj = (EmptyStruct * 2)() # bpo37188: Floating-point exception + self.assertEqual(sizeof(obj), 0) + + def test_empty_element_array(self): + class EmptyArray(Array): + _type_ = c_int + _length_ = 0 + + obj = (EmptyArray * 2)() # bpo37188: Floating-point exception + self.assertEqual(sizeof(obj), 0) + + def test_bpo36504_signed_int_overflow(self): + # The overflow check in PyCArrayType_new() could cause signed integer + # overflow. + with self.assertRaises(OverflowError): + c_char * sys.maxsize * 2 + + @unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform') + @bigmemtest(size=_2G, memuse=1, dry_run=False) + def test_large_array(self, size): + c_char * size + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_as_parameter.py b/Lib/test/test_ctypes/test_as_parameter.py new file mode 100644 index 00000000000..c5e1840b0eb --- /dev/null +++ b/Lib/test/test_ctypes/test_as_parameter.py @@ -0,0 +1,245 @@ +import ctypes +import unittest +from ctypes import (Structure, CDLL, CFUNCTYPE, + POINTER, pointer, byref, + c_short, c_int, c_long, c_longlong, + c_byte, c_wchar, c_float, c_double, + ArgumentError) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +dll = CDLL(_ctypes_test.__file__) + +try: + CALLBACK_FUNCTYPE = ctypes.WINFUNCTYPE +except AttributeError: + # fake to enable this test on Linux + CALLBACK_FUNCTYPE = CFUNCTYPE + + +class POINT(Structure): + _fields_ = [("x", c_int), ("y", c_int)] + + +class BasicWrapTestCase(unittest.TestCase): + def wrap(self, param): + return param + + def test_wchar_parm(self): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_wchar, c_int, c_long, c_float, c_double] + result = f(self.wrap(1), self.wrap("x"), self.wrap(3), self.wrap(4), self.wrap(5.0), self.wrap(6.0)) + self.assertEqual(result, 139) + self.assertIs(type(result), int) + + def test_pointers(self): + f = dll._testfunc_p_p + f.restype = POINTER(c_int) + f.argtypes = [POINTER(c_int)] + + # This only works if the value c_int(42) passed to the + # function is still alive while the pointer (the result) is + # used. + + v = c_int(42) + + self.assertEqual(pointer(v).contents.value, 42) + result = f(self.wrap(pointer(v))) + self.assertEqual(type(result), POINTER(c_int)) + self.assertEqual(result.contents.value, 42) + + # This on works... + result = f(self.wrap(pointer(v))) + self.assertEqual(result.contents.value, v.value) + + p = pointer(c_int(99)) + result = f(self.wrap(p)) + self.assertEqual(result.contents.value, 99) + + def test_shorts(self): + f = dll._testfunc_callback_i_if + + args = [] + expected = [262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, + 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1] + + def callback(v): + args.append(v) + return v + + CallBack = CFUNCTYPE(c_int, c_int) + + cb = CallBack(callback) + f(self.wrap(2**18), self.wrap(cb)) + self.assertEqual(args, expected) + + def test_callbacks(self): + f = dll._testfunc_callback_i_if + f.restype = c_int + f.argtypes = None + + MyCallback = CFUNCTYPE(c_int, c_int) + + def callback(value): + return value + + cb = MyCallback(callback) + + result = f(self.wrap(-10), self.wrap(cb)) + self.assertEqual(result, -18) + + # test with prototype + f.argtypes = [c_int, MyCallback] + cb = MyCallback(callback) + + result = f(self.wrap(-10), self.wrap(cb)) + self.assertEqual(result, -18) + + result = f(self.wrap(-10), self.wrap(cb)) + self.assertEqual(result, -18) + + AnotherCallback = CALLBACK_FUNCTYPE(c_int, c_int, c_int, c_int, c_int) + + # check that the prototype works: we call f with wrong + # argument types + cb = AnotherCallback(callback) + self.assertRaises(ArgumentError, f, self.wrap(-10), self.wrap(cb)) + + def test_callbacks_2(self): + # Can also use simple datatypes as argument type specifiers + # for the callback function. + # In this case the call receives an instance of that type + f = dll._testfunc_callback_i_if + f.restype = c_int + + MyCallback = CFUNCTYPE(c_int, c_int) + + f.argtypes = [c_int, MyCallback] + + def callback(value): + self.assertEqual(type(value), int) + return value + + cb = MyCallback(callback) + result = f(self.wrap(-10), self.wrap(cb)) + self.assertEqual(result, -18) + + def test_longlong_callbacks(self): + f = dll._testfunc_callback_q_qf + f.restype = c_longlong + + MyCallback = CFUNCTYPE(c_longlong, c_longlong) + + f.argtypes = [c_longlong, MyCallback] + + def callback(value): + self.assertIsInstance(value, int) + return value & 0x7FFFFFFF + + cb = MyCallback(callback) + + self.assertEqual(13577625587, int(f(self.wrap(1000000000000), self.wrap(cb)))) + + def test_byval(self): + # without prototype + ptin = POINT(1, 2) + ptout = POINT() + # EXPORT int _testfunc_byval(point in, point *pout) + result = dll._testfunc_byval(ptin, byref(ptout)) + got = result, ptout.x, ptout.y + expected = 3, 1, 2 + self.assertEqual(got, expected) + + # with prototype + ptin = POINT(101, 102) + ptout = POINT() + dll._testfunc_byval.argtypes = (POINT, POINTER(POINT)) + dll._testfunc_byval.restype = c_int + result = dll._testfunc_byval(self.wrap(ptin), byref(ptout)) + got = result, ptout.x, ptout.y + expected = 203, 101, 102 + self.assertEqual(got, expected) + + def test_struct_return_2H(self): + class S2H(Structure): + _fields_ = [("x", c_short), + ("y", c_short)] + dll.ret_2h_func.restype = S2H + dll.ret_2h_func.argtypes = [S2H] + inp = S2H(99, 88) + s2h = dll.ret_2h_func(self.wrap(inp)) + self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) + + # Test also that the original struct was unmodified (i.e. was passed by + # value) + self.assertEqual((inp.x, inp.y), (99, 88)) + + def test_struct_return_8H(self): + class S8I(Structure): + _fields_ = [("a", c_int), + ("b", c_int), + ("c", c_int), + ("d", c_int), + ("e", c_int), + ("f", c_int), + ("g", c_int), + ("h", c_int)] + dll.ret_8i_func.restype = S8I + dll.ret_8i_func.argtypes = [S8I] + inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) + s8i = dll.ret_8i_func(self.wrap(inp)) + self.assertEqual((s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), + (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) + + def test_recursive_as_param(self): + class A: + pass + + a = A() + a._as_parameter_ = a + for c_type in ( + ctypes.c_wchar_p, + ctypes.c_char_p, + ctypes.c_void_p, + ctypes.c_int, # PyCSimpleType + POINT, # CDataType + ): + with self.subTest(c_type=c_type): + with self.assertRaises(RecursionError): + c_type.from_param(a) + + +class AsParamWrapper: + def __init__(self, param): + self._as_parameter_ = param + +class AsParamWrapperTestCase(BasicWrapTestCase): + wrap = AsParamWrapper + + +class AsParamPropertyWrapper: + def __init__(self, param): + self._param = param + + def getParameter(self): + return self._param + _as_parameter_ = property(getParameter) + +class AsParamPropertyWrapperTestCase(BasicWrapTestCase): + wrap = AsParamPropertyWrapper + + +class AsParamNestedWrapperTestCase(BasicWrapTestCase): + """Test that _as_parameter_ is evaluated recursively. + + The _as_parameter_ attribute can be another object which + defines its own _as_parameter_ attribute. + """ + + def wrap(self, param): + return AsParamWrapper(AsParamWrapper(AsParamWrapper(param))) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_bitfields.py b/Lib/test/test_ctypes/test_bitfields.py new file mode 100644 index 00000000000..0332544b582 --- /dev/null +++ b/Lib/test/test_ctypes/test_bitfields.py @@ -0,0 +1,294 @@ +import os +import unittest +from ctypes import (CDLL, Structure, sizeof, POINTER, byref, alignment, + LittleEndianStructure, BigEndianStructure, + c_byte, c_ubyte, c_char, c_char_p, c_void_p, c_wchar, + c_uint32, c_uint64, + c_short, c_ushort, c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong) +from test import support +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class BITS(Structure): + _fields_ = [("A", c_int, 1), + ("B", c_int, 2), + ("C", c_int, 3), + ("D", c_int, 4), + ("E", c_int, 5), + ("F", c_int, 6), + ("G", c_int, 7), + ("H", c_int, 8), + ("I", c_int, 9), + + ("M", c_short, 1), + ("N", c_short, 2), + ("O", c_short, 3), + ("P", c_short, 4), + ("Q", c_short, 5), + ("R", c_short, 6), + ("S", c_short, 7)] + +func = CDLL(_ctypes_test.__file__).unpack_bitfields +func.argtypes = POINTER(BITS), c_char + + +class C_Test(unittest.TestCase): + + def test_ints(self): + for i in range(512): + for name in "ABCDEFGHI": + b = BITS() + setattr(b, name, i) + self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii'))) + + # bpo-46913: _ctypes/cfield.c h_get() has an undefined behavior + @support.skip_if_sanitizer(ub=True) + def test_shorts(self): + b = BITS() + name = "M" + if func(byref(b), name.encode('ascii')) == 999: + self.skipTest("Compiler does not support signed short bitfields") + for i in range(256): + for name in "MNOPQRS": + b = BITS() + setattr(b, name, i) + self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii'))) + + +signed_int_types = (c_byte, c_short, c_int, c_long, c_longlong) +unsigned_int_types = (c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong) +int_types = unsigned_int_types + signed_int_types + +class BitFieldTest(unittest.TestCase): + + def test_longlong(self): + class X(Structure): + _fields_ = [("a", c_longlong, 1), + ("b", c_longlong, 62), + ("c", c_longlong, 1)] + + self.assertEqual(sizeof(X), sizeof(c_longlong)) + x = X() + x.a, x.b, x.c = -1, 7, -1 + self.assertEqual((x.a, x.b, x.c), (-1, 7, -1)) + + def test_ulonglong(self): + class X(Structure): + _fields_ = [("a", c_ulonglong, 1), + ("b", c_ulonglong, 62), + ("c", c_ulonglong, 1)] + + self.assertEqual(sizeof(X), sizeof(c_longlong)) + x = X() + self.assertEqual((x.a, x.b, x.c), (0, 0, 0)) + x.a, x.b, x.c = 7, 7, 7 + self.assertEqual((x.a, x.b, x.c), (1, 7, 1)) + + def test_signed(self): + for c_typ in signed_int_types: + class X(Structure): + _fields_ = [("dummy", c_typ), + ("a", c_typ, 3), + ("b", c_typ, 3), + ("c", c_typ, 1)] + self.assertEqual(sizeof(X), sizeof(c_typ)*2) + + x = X() + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0)) + x.a = -1 + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, -1, 0, 0)) + x.a, x.b = 0, -1 + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, -1, 0)) + + + def test_unsigned(self): + for c_typ in unsigned_int_types: + class X(Structure): + _fields_ = [("a", c_typ, 3), + ("b", c_typ, 3), + ("c", c_typ, 1)] + self.assertEqual(sizeof(X), sizeof(c_typ)) + + x = X() + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0)) + x.a = -1 + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 7, 0, 0)) + x.a, x.b = 0, -1 + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 7, 0)) + + def fail_fields(self, *fields): + return self.get_except(type(Structure), "X", (), + {"_fields_": fields}) + + def test_nonint_types(self): + # bit fields are not allowed on non-integer types. + result = self.fail_fields(("a", c_char_p, 1)) + self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_char_p')) + + result = self.fail_fields(("a", c_void_p, 1)) + self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_void_p')) + + if c_int != c_long: + result = self.fail_fields(("a", POINTER(c_int), 1)) + self.assertEqual(result, (TypeError, 'bit fields not allowed for type LP_c_int')) + + result = self.fail_fields(("a", c_char, 1)) + self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_char')) + + class Dummy(Structure): + _fields_ = [] + + result = self.fail_fields(("a", Dummy, 1)) + self.assertEqual(result, (TypeError, 'bit fields not allowed for type Dummy')) + + def test_c_wchar(self): + result = self.fail_fields(("a", c_wchar, 1)) + self.assertEqual(result, + (TypeError, 'bit fields not allowed for type c_wchar')) + + def test_single_bitfield_size(self): + for c_typ in int_types: + result = self.fail_fields(("a", c_typ, -1)) + self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) + + result = self.fail_fields(("a", c_typ, 0)) + self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) + + class X(Structure): + _fields_ = [("a", c_typ, 1)] + self.assertEqual(sizeof(X), sizeof(c_typ)) + + class X(Structure): + _fields_ = [("a", c_typ, sizeof(c_typ)*8)] + self.assertEqual(sizeof(X), sizeof(c_typ)) + + result = self.fail_fields(("a", c_typ, sizeof(c_typ)*8 + 1)) + self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) + + def test_multi_bitfields_size(self): + class X(Structure): + _fields_ = [("a", c_short, 1), + ("b", c_short, 14), + ("c", c_short, 1)] + self.assertEqual(sizeof(X), sizeof(c_short)) + + class X(Structure): + _fields_ = [("a", c_short, 1), + ("a1", c_short), + ("b", c_short, 14), + ("c", c_short, 1)] + self.assertEqual(sizeof(X), sizeof(c_short)*3) + self.assertEqual(X.a.offset, 0) + self.assertEqual(X.a1.offset, sizeof(c_short)) + self.assertEqual(X.b.offset, sizeof(c_short)*2) + self.assertEqual(X.c.offset, sizeof(c_short)*2) + + class X(Structure): + _fields_ = [("a", c_short, 3), + ("b", c_short, 14), + ("c", c_short, 14)] + self.assertEqual(sizeof(X), sizeof(c_short)*3) + self.assertEqual(X.a.offset, sizeof(c_short)*0) + self.assertEqual(X.b.offset, sizeof(c_short)*1) + self.assertEqual(X.c.offset, sizeof(c_short)*2) + + def get_except(self, func, *args, **kw): + try: + func(*args, **kw) + except Exception as detail: + return detail.__class__, str(detail) + + def test_mixed_1(self): + class X(Structure): + _fields_ = [("a", c_byte, 4), + ("b", c_int, 4)] + if os.name == "nt": + self.assertEqual(sizeof(X), sizeof(c_int)*2) + else: + self.assertEqual(sizeof(X), sizeof(c_int)) + + def test_mixed_2(self): + class X(Structure): + _fields_ = [("a", c_byte, 4), + ("b", c_int, 32)] + self.assertEqual(sizeof(X), alignment(c_int)+sizeof(c_int)) + + def test_mixed_3(self): + class X(Structure): + _fields_ = [("a", c_byte, 4), + ("b", c_ubyte, 4)] + self.assertEqual(sizeof(X), sizeof(c_byte)) + + def test_mixed_4(self): + class X(Structure): + _fields_ = [("a", c_short, 4), + ("b", c_short, 4), + ("c", c_int, 24), + ("d", c_short, 4), + ("e", c_short, 4), + ("f", c_int, 24)] + # MSVC does NOT combine c_short and c_int into one field, GCC + # does (unless GCC is run with '-mms-bitfields' which + # produces code compatible with MSVC). + if os.name == "nt": + self.assertEqual(sizeof(X), sizeof(c_int) * 4) + else: + self.assertEqual(sizeof(X), sizeof(c_int) * 2) + + def test_anon_bitfields(self): + # anonymous bit-fields gave a strange error message + class X(Structure): + _fields_ = [("a", c_byte, 4), + ("b", c_ubyte, 4)] + class Y(Structure): + _anonymous_ = ["_"] + _fields_ = [("_", X)] + + def test_uint32(self): + class X(Structure): + _fields_ = [("a", c_uint32, 32)] + x = X() + x.a = 10 + self.assertEqual(x.a, 10) + x.a = 0xFDCBA987 + self.assertEqual(x.a, 0xFDCBA987) + + def test_uint64(self): + class X(Structure): + _fields_ = [("a", c_uint64, 64)] + x = X() + x.a = 10 + self.assertEqual(x.a, 10) + x.a = 0xFEDCBA9876543211 + self.assertEqual(x.a, 0xFEDCBA9876543211) + + def test_uint32_swap_little_endian(self): + # Issue #23319 + class Little(LittleEndianStructure): + _fields_ = [("a", c_uint32, 24), + ("b", c_uint32, 4), + ("c", c_uint32, 4)] + b = bytearray(4) + x = Little.from_buffer(b) + x.a = 0xabcdef + x.b = 1 + x.c = 2 + self.assertEqual(b, b'\xef\xcd\xab\x21') + + def test_uint32_swap_big_endian(self): + # Issue #23319 + class Big(BigEndianStructure): + _fields_ = [("a", c_uint32, 24), + ("b", c_uint32, 4), + ("c", c_uint32, 4)] + b = bytearray(4) + x = Big.from_buffer(b) + x.a = 0xabcdef + x.b = 1 + x.c = 2 + self.assertEqual(b, b'\xab\xcd\xef\x12') + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_buffers.py b/Lib/test/test_ctypes/test_buffers.py new file mode 100644 index 00000000000..468f41eb7cf --- /dev/null +++ b/Lib/test/test_ctypes/test_buffers.py @@ -0,0 +1,70 @@ +import unittest +from ctypes import (create_string_buffer, create_unicode_buffer, sizeof, + c_char, c_wchar) + + +class StringBufferTestCase(unittest.TestCase): + def test_buffer(self): + b = create_string_buffer(32) + self.assertEqual(len(b), 32) + self.assertEqual(sizeof(b), 32 * sizeof(c_char)) + self.assertIs(type(b[0]), bytes) + + b = create_string_buffer(b"abc") + self.assertEqual(len(b), 4) # trailing nul char + self.assertEqual(sizeof(b), 4 * sizeof(c_char)) + self.assertIs(type(b[0]), bytes) + self.assertEqual(b[0], b"a") + self.assertEqual(b[:], b"abc\0") + self.assertEqual(b[::], b"abc\0") + self.assertEqual(b[::-1], b"\0cba") + self.assertEqual(b[::2], b"ac") + self.assertEqual(b[::5], b"a") + + self.assertRaises(TypeError, create_string_buffer, "abc") + + def test_buffer_interface(self): + self.assertEqual(len(bytearray(create_string_buffer(0))), 0) + self.assertEqual(len(bytearray(create_string_buffer(1))), 1) + + def test_unicode_buffer(self): + b = create_unicode_buffer(32) + self.assertEqual(len(b), 32) + self.assertEqual(sizeof(b), 32 * sizeof(c_wchar)) + self.assertIs(type(b[0]), str) + + b = create_unicode_buffer("abc") + self.assertEqual(len(b), 4) # trailing nul char + self.assertEqual(sizeof(b), 4 * sizeof(c_wchar)) + self.assertIs(type(b[0]), str) + self.assertEqual(b[0], "a") + self.assertEqual(b[:], "abc\0") + self.assertEqual(b[::], "abc\0") + self.assertEqual(b[::-1], "\0cba") + self.assertEqual(b[::2], "ac") + self.assertEqual(b[::5], "a") + + self.assertRaises(TypeError, create_unicode_buffer, b"abc") + + def test_unicode_conversion(self): + b = create_unicode_buffer("abc") + self.assertEqual(len(b), 4) # trailing nul char + self.assertEqual(sizeof(b), 4 * sizeof(c_wchar)) + self.assertIs(type(b[0]), str) + self.assertEqual(b[0], "a") + self.assertEqual(b[:], "abc\0") + self.assertEqual(b[::], "abc\0") + self.assertEqual(b[::-1], "\0cba") + self.assertEqual(b[::2], "ac") + self.assertEqual(b[::5], "a") + + def test_create_unicode_buffer_non_bmp(self): + expected = 5 if sizeof(c_wchar) == 2 else 3 + for s in '\U00010000\U00100000', '\U00010000\U0010ffff': + b = create_unicode_buffer(s) + self.assertEqual(len(b), expected) + self.assertEqual(b[-1], '\0') + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_bytes.py b/Lib/test/test_ctypes/test_bytes.py new file mode 100644 index 00000000000..fa11e1bbd49 --- /dev/null +++ b/Lib/test/test_ctypes/test_bytes.py @@ -0,0 +1,67 @@ +"""Test where byte objects are accepted""" +import sys +import unittest +from _ctypes import _SimpleCData +from ctypes import Structure, c_char, c_char_p, c_wchar, c_wchar_p + + +class BytesTest(unittest.TestCase): + def test_c_char(self): + x = c_char(b"x") + self.assertRaises(TypeError, c_char, "x") + x.value = b"y" + with self.assertRaises(TypeError): + x.value = "y" + c_char.from_param(b"x") + self.assertRaises(TypeError, c_char.from_param, "x") + self.assertIn('xbd', repr(c_char.from_param(b"\xbd"))) + (c_char * 3)(b"a", b"b", b"c") + self.assertRaises(TypeError, c_char * 3, "a", "b", "c") + + def test_c_wchar(self): + x = c_wchar("x") + self.assertRaises(TypeError, c_wchar, b"x") + x.value = "y" + with self.assertRaises(TypeError): + x.value = b"y" + c_wchar.from_param("x") + self.assertRaises(TypeError, c_wchar.from_param, b"x") + (c_wchar * 3)("a", "b", "c") + self.assertRaises(TypeError, c_wchar * 3, b"a", b"b", b"c") + + def test_c_char_p(self): + c_char_p(b"foo bar") + self.assertRaises(TypeError, c_char_p, "foo bar") + + def test_c_wchar_p(self): + c_wchar_p("foo bar") + self.assertRaises(TypeError, c_wchar_p, b"foo bar") + + def test_struct(self): + class X(Structure): + _fields_ = [("a", c_char * 3)] + + x = X(b"abc") + self.assertRaises(TypeError, X, "abc") + self.assertEqual(x.a, b"abc") + self.assertEqual(type(x.a), bytes) + + def test_struct_W(self): + class X(Structure): + _fields_ = [("a", c_wchar * 3)] + + x = X("abc") + self.assertRaises(TypeError, X, b"abc") + self.assertEqual(x.a, "abc") + self.assertEqual(type(x.a), str) + + @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') + def test_BSTR(self): + class BSTR(_SimpleCData): + _type_ = "X" + + BSTR("abc") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_byteswap.py b/Lib/test/test_ctypes/test_byteswap.py new file mode 100644 index 00000000000..78eff0392c4 --- /dev/null +++ b/Lib/test/test_ctypes/test_byteswap.py @@ -0,0 +1,386 @@ +import binascii +import ctypes +import math +import struct +import sys +import unittest +from ctypes import (Structure, Union, LittleEndianUnion, BigEndianUnion, + BigEndianStructure, LittleEndianStructure, + POINTER, sizeof, cast, + c_byte, c_ubyte, c_char, c_wchar, c_void_p, + c_short, c_ushort, c_int, c_uint, + c_long, c_ulong, c_longlong, c_ulonglong, + c_uint32, c_float, c_double) + + +def bin(s): + return binascii.hexlify(memoryview(s)).decode().upper() + + +# Each *simple* type that supports different byte orders has an +# __ctype_be__ attribute that specifies the same type in BIG ENDIAN +# byte order, and a __ctype_le__ attribute that is the same type in +# LITTLE ENDIAN byte order. +# +# For Structures and Unions, these types are created on demand. + +class Test(unittest.TestCase): + def test_slots(self): + class BigPoint(BigEndianStructure): + __slots__ = () + _fields_ = [("x", c_int), ("y", c_int)] + + class LowPoint(LittleEndianStructure): + __slots__ = () + _fields_ = [("x", c_int), ("y", c_int)] + + big = BigPoint() + little = LowPoint() + big.x = 4 + big.y = 2 + little.x = 2 + little.y = 4 + with self.assertRaises(AttributeError): + big.z = 42 + with self.assertRaises(AttributeError): + little.z = 24 + + def test_endian_short(self): + if sys.byteorder == "little": + self.assertIs(c_short.__ctype_le__, c_short) + self.assertIs(c_short.__ctype_be__.__ctype_le__, c_short) + else: + self.assertIs(c_short.__ctype_be__, c_short) + self.assertIs(c_short.__ctype_le__.__ctype_be__, c_short) + s = c_short.__ctype_be__(0x1234) + self.assertEqual(bin(struct.pack(">h", 0x1234)), "1234") + self.assertEqual(bin(s), "1234") + self.assertEqual(s.value, 0x1234) + + s = c_short.__ctype_le__(0x1234) + self.assertEqual(bin(struct.pack("<h", 0x1234)), "3412") + self.assertEqual(bin(s), "3412") + self.assertEqual(s.value, 0x1234) + + s = c_ushort.__ctype_be__(0x1234) + self.assertEqual(bin(struct.pack(">h", 0x1234)), "1234") + self.assertEqual(bin(s), "1234") + self.assertEqual(s.value, 0x1234) + + s = c_ushort.__ctype_le__(0x1234) + self.assertEqual(bin(struct.pack("<h", 0x1234)), "3412") + self.assertEqual(bin(s), "3412") + self.assertEqual(s.value, 0x1234) + + def test_endian_int(self): + if sys.byteorder == "little": + self.assertIs(c_int.__ctype_le__, c_int) + self.assertIs(c_int.__ctype_be__.__ctype_le__, c_int) + else: + self.assertIs(c_int.__ctype_be__, c_int) + self.assertIs(c_int.__ctype_le__.__ctype_be__, c_int) + + s = c_int.__ctype_be__(0x12345678) + self.assertEqual(bin(struct.pack(">i", 0x12345678)), "12345678") + self.assertEqual(bin(s), "12345678") + self.assertEqual(s.value, 0x12345678) + + s = c_int.__ctype_le__(0x12345678) + self.assertEqual(bin(struct.pack("<i", 0x12345678)), "78563412") + self.assertEqual(bin(s), "78563412") + self.assertEqual(s.value, 0x12345678) + + s = c_uint.__ctype_be__(0x12345678) + self.assertEqual(bin(struct.pack(">I", 0x12345678)), "12345678") + self.assertEqual(bin(s), "12345678") + self.assertEqual(s.value, 0x12345678) + + s = c_uint.__ctype_le__(0x12345678) + self.assertEqual(bin(struct.pack("<I", 0x12345678)), "78563412") + self.assertEqual(bin(s), "78563412") + self.assertEqual(s.value, 0x12345678) + + def test_endian_longlong(self): + if sys.byteorder == "little": + self.assertIs(c_longlong.__ctype_le__, c_longlong) + self.assertIs(c_longlong.__ctype_be__.__ctype_le__, c_longlong) + else: + self.assertIs(c_longlong.__ctype_be__, c_longlong) + self.assertIs(c_longlong.__ctype_le__.__ctype_be__, c_longlong) + + s = c_longlong.__ctype_be__(0x1234567890ABCDEF) + self.assertEqual(bin(struct.pack(">q", 0x1234567890ABCDEF)), "1234567890ABCDEF") + self.assertEqual(bin(s), "1234567890ABCDEF") + self.assertEqual(s.value, 0x1234567890ABCDEF) + + s = c_longlong.__ctype_le__(0x1234567890ABCDEF) + self.assertEqual(bin(struct.pack("<q", 0x1234567890ABCDEF)), "EFCDAB9078563412") + self.assertEqual(bin(s), "EFCDAB9078563412") + self.assertEqual(s.value, 0x1234567890ABCDEF) + + s = c_ulonglong.__ctype_be__(0x1234567890ABCDEF) + self.assertEqual(bin(struct.pack(">Q", 0x1234567890ABCDEF)), "1234567890ABCDEF") + self.assertEqual(bin(s), "1234567890ABCDEF") + self.assertEqual(s.value, 0x1234567890ABCDEF) + + s = c_ulonglong.__ctype_le__(0x1234567890ABCDEF) + self.assertEqual(bin(struct.pack("<Q", 0x1234567890ABCDEF)), "EFCDAB9078563412") + self.assertEqual(bin(s), "EFCDAB9078563412") + self.assertEqual(s.value, 0x1234567890ABCDEF) + + def test_endian_float(self): + if sys.byteorder == "little": + self.assertIs(c_float.__ctype_le__, c_float) + self.assertIs(c_float.__ctype_be__.__ctype_le__, c_float) + else: + self.assertIs(c_float.__ctype_be__, c_float) + self.assertIs(c_float.__ctype_le__.__ctype_be__, c_float) + s = c_float(math.pi) + self.assertEqual(bin(struct.pack("f", math.pi)), bin(s)) + # Hm, what's the precision of a float compared to a double? + self.assertAlmostEqual(s.value, math.pi, places=6) + s = c_float.__ctype_le__(math.pi) + self.assertAlmostEqual(s.value, math.pi, places=6) + self.assertEqual(bin(struct.pack("<f", math.pi)), bin(s)) + s = c_float.__ctype_be__(math.pi) + self.assertAlmostEqual(s.value, math.pi, places=6) + self.assertEqual(bin(struct.pack(">f", math.pi)), bin(s)) + + def test_endian_double(self): + if sys.byteorder == "little": + self.assertIs(c_double.__ctype_le__, c_double) + self.assertIs(c_double.__ctype_be__.__ctype_le__, c_double) + else: + self.assertIs(c_double.__ctype_be__, c_double) + self.assertIs(c_double.__ctype_le__.__ctype_be__, c_double) + s = c_double(math.pi) + self.assertEqual(s.value, math.pi) + self.assertEqual(bin(struct.pack("d", math.pi)), bin(s)) + s = c_double.__ctype_le__(math.pi) + self.assertEqual(s.value, math.pi) + self.assertEqual(bin(struct.pack("<d", math.pi)), bin(s)) + s = c_double.__ctype_be__(math.pi) + self.assertEqual(s.value, math.pi) + self.assertEqual(bin(struct.pack(">d", math.pi)), bin(s)) + + def test_endian_other(self): + self.assertIs(c_byte.__ctype_le__, c_byte) + self.assertIs(c_byte.__ctype_be__, c_byte) + + self.assertIs(c_ubyte.__ctype_le__, c_ubyte) + self.assertIs(c_ubyte.__ctype_be__, c_ubyte) + + self.assertIs(c_char.__ctype_le__, c_char) + self.assertIs(c_char.__ctype_be__, c_char) + + def test_struct_fields_unsupported_byte_order(self): + + fields = [ + ("a", c_ubyte), + ("b", c_byte), + ("c", c_short), + ("d", c_ushort), + ("e", c_int), + ("f", c_uint), + ("g", c_long), + ("h", c_ulong), + ("i", c_longlong), + ("k", c_ulonglong), + ("l", c_float), + ("m", c_double), + ("n", c_char), + ("b1", c_byte, 3), + ("b2", c_byte, 3), + ("b3", c_byte, 2), + ("a", c_int * 3 * 3 * 3) + ] + + # these fields do not support different byte order: + for typ in c_wchar, c_void_p, POINTER(c_int): + with self.assertRaises(TypeError): + class T(BigEndianStructure if sys.byteorder == "little" else LittleEndianStructure): + _fields_ = fields + [("x", typ)] + + + def test_struct_struct(self): + # nested structures with different byteorders + + # create nested structures with given byteorders and set memory to data + + for nested, data in ( + (BigEndianStructure, b'\0\0\0\1\0\0\0\2'), + (LittleEndianStructure, b'\1\0\0\0\2\0\0\0'), + ): + for parent in ( + BigEndianStructure, + LittleEndianStructure, + Structure, + ): + class NestedStructure(nested): + _fields_ = [("x", c_uint32), + ("y", c_uint32)] + + class TestStructure(parent): + _fields_ = [("point", NestedStructure)] + + self.assertEqual(len(data), sizeof(TestStructure)) + ptr = POINTER(TestStructure) + s = cast(data, ptr)[0] + del ctypes._pointer_type_cache[TestStructure] + self.assertEqual(s.point.x, 1) + self.assertEqual(s.point.y, 2) + + def test_struct_field_alignment(self): + # standard packing in struct uses no alignment. + # So, we have to align using pad bytes. + # + # Unaligned accesses will crash Python (on those platforms that + # don't allow it, like sparc solaris). + if sys.byteorder == "little": + base = BigEndianStructure + fmt = ">bxhid" + else: + base = LittleEndianStructure + fmt = "<bxhid" + + class S(base): + _fields_ = [("b", c_byte), + ("h", c_short), + ("i", c_int), + ("d", c_double)] + + s1 = S(0x12, 0x1234, 0x12345678, 3.14) + s2 = struct.pack(fmt, 0x12, 0x1234, 0x12345678, 3.14) + self.assertEqual(bin(s1), bin(s2)) + + def test_unaligned_nonnative_struct_fields(self): + if sys.byteorder == "little": + base = BigEndianStructure + fmt = ">b h xi xd" + else: + base = LittleEndianStructure + fmt = "<b h xi xd" + + class S(base): + _pack_ = 1 + _fields_ = [("b", c_byte), + ("h", c_short), + + ("_1", c_byte), + ("i", c_int), + + ("_2", c_byte), + ("d", c_double)] + + s1 = S() + s1.b = 0x12 + s1.h = 0x1234 + s1.i = 0x12345678 + s1.d = 3.14 + s2 = struct.pack(fmt, 0x12, 0x1234, 0x12345678, 3.14) + self.assertEqual(bin(s1), bin(s2)) + + def test_unaligned_native_struct_fields(self): + if sys.byteorder == "little": + fmt = "<b h xi xd" + else: + base = LittleEndianStructure + fmt = ">b h xi xd" + + class S(Structure): + _pack_ = 1 + _fields_ = [("b", c_byte), + + ("h", c_short), + + ("_1", c_byte), + ("i", c_int), + + ("_2", c_byte), + ("d", c_double)] + + s1 = S() + s1.b = 0x12 + s1.h = 0x1234 + s1.i = 0x12345678 + s1.d = 3.14 + s2 = struct.pack(fmt, 0x12, 0x1234, 0x12345678, 3.14) + self.assertEqual(bin(s1), bin(s2)) + + def test_union_fields_unsupported_byte_order(self): + + fields = [ + ("a", c_ubyte), + ("b", c_byte), + ("c", c_short), + ("d", c_ushort), + ("e", c_int), + ("f", c_uint), + ("g", c_long), + ("h", c_ulong), + ("i", c_longlong), + ("k", c_ulonglong), + ("l", c_float), + ("m", c_double), + ("n", c_char), + ("b1", c_byte, 3), + ("b2", c_byte, 3), + ("b3", c_byte, 2), + ("a", c_int * 3 * 3 * 3) + ] + + # these fields do not support different byte order: + for typ in c_wchar, c_void_p, POINTER(c_int): + with self.assertRaises(TypeError): + class T(BigEndianUnion if sys.byteorder == "little" else LittleEndianUnion): + _fields_ = fields + [("x", typ)] + + def test_union_struct(self): + # nested structures in unions with different byteorders + + # create nested structures in unions with given byteorders and set memory to data + + for nested, data in ( + (BigEndianStructure, b'\0\0\0\1\0\0\0\2'), + (LittleEndianStructure, b'\1\0\0\0\2\0\0\0'), + ): + for parent in ( + BigEndianUnion, + LittleEndianUnion, + Union, + ): + class NestedStructure(nested): + _fields_ = [("x", c_uint32), + ("y", c_uint32)] + + class TestUnion(parent): + _fields_ = [("point", NestedStructure)] + + self.assertEqual(len(data), sizeof(TestUnion)) + ptr = POINTER(TestUnion) + s = cast(data, ptr)[0] + del ctypes._pointer_type_cache[TestUnion] + self.assertEqual(s.point.x, 1) + self.assertEqual(s.point.y, 2) + + def test_build_struct_union_opposite_system_byteorder(self): + # gh-105102 + if sys.byteorder == "little": + _Structure = BigEndianStructure + _Union = BigEndianUnion + else: + _Structure = LittleEndianStructure + _Union = LittleEndianUnion + + class S1(_Structure): + _fields_ = [("a", c_byte), ("b", c_byte)] + + class U1(_Union): + _fields_ = [("s1", S1), ("ab", c_short)] + + class S2(_Structure): + _fields_ = [("u1", U1), ("c", c_byte)] + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_c_simple_type_meta.py b/Lib/test/test_ctypes/test_c_simple_type_meta.py new file mode 100644 index 00000000000..eb77d6d7782 --- /dev/null +++ b/Lib/test/test_ctypes/test_c_simple_type_meta.py @@ -0,0 +1,152 @@ +import unittest +import ctypes +from ctypes import POINTER, c_void_p + +from ._support import PyCSimpleType + + +class PyCSimpleTypeAsMetaclassTest(unittest.TestCase): + def tearDown(self): + # to not leak references, we must clean _pointer_type_cache + ctypes._reset_cache() + + def test_creating_pointer_in_dunder_new_1(self): + # Test metaclass whose instances are C types; when the type is + # created it automatically creates a pointer type for itself. + # The pointer type is also an instance of the metaclass. + # Such an implementation is used in `IUnknown` of the `comtypes` + # project. See gh-124520. + + class ct_meta(type): + def __new__(cls, name, bases, namespace): + self = super().__new__(cls, name, bases, namespace) + + # Avoid recursion: don't set up a pointer to + # a pointer (to a pointer...) + if bases == (c_void_p,): + # When creating PtrBase itself, the name + # is not yet available + return self + if issubclass(self, PtrBase): + return self + + if bases == (object,): + ptr_bases = (self, PtrBase) + else: + ptr_bases = (self, POINTER(bases[0])) + p = p_meta(f"POINTER({self.__name__})", ptr_bases, {}) + ctypes._pointer_type_cache[self] = p + return self + + class p_meta(PyCSimpleType, ct_meta): + pass + + class PtrBase(c_void_p, metaclass=p_meta): + pass + + class CtBase(object, metaclass=ct_meta): + pass + + class Sub(CtBase): + pass + + class Sub2(Sub): + pass + + self.assertIsInstance(POINTER(Sub2), p_meta) + self.assertTrue(issubclass(POINTER(Sub2), Sub2)) + self.assertTrue(issubclass(POINTER(Sub2), POINTER(Sub))) + self.assertTrue(issubclass(POINTER(Sub), POINTER(CtBase))) + + def test_creating_pointer_in_dunder_new_2(self): + # A simpler variant of the above, used in `CoClass` of the `comtypes` + # project. + + class ct_meta(type): + def __new__(cls, name, bases, namespace): + self = super().__new__(cls, name, bases, namespace) + if isinstance(self, p_meta): + return self + p = p_meta(f"POINTER({self.__name__})", (self, c_void_p), {}) + ctypes._pointer_type_cache[self] = p + return self + + class p_meta(PyCSimpleType, ct_meta): + pass + + class Core(object): + pass + + class CtBase(Core, metaclass=ct_meta): + pass + + class Sub(CtBase): + pass + + self.assertIsInstance(POINTER(Sub), p_meta) + self.assertTrue(issubclass(POINTER(Sub), Sub)) + + def test_creating_pointer_in_dunder_init_1(self): + class ct_meta(type): + def __init__(self, name, bases, namespace): + super().__init__(name, bases, namespace) + + # Avoid recursion. + # (See test_creating_pointer_in_dunder_new_1) + if bases == (c_void_p,): + return + if issubclass(self, PtrBase): + return + if bases == (object,): + ptr_bases = (self, PtrBase) + else: + ptr_bases = (self, POINTER(bases[0])) + p = p_meta(f"POINTER({self.__name__})", ptr_bases, {}) + ctypes._pointer_type_cache[self] = p + + class p_meta(PyCSimpleType, ct_meta): + pass + + class PtrBase(c_void_p, metaclass=p_meta): + pass + + class CtBase(object, metaclass=ct_meta): + pass + + class Sub(CtBase): + pass + + class Sub2(Sub): + pass + + self.assertIsInstance(POINTER(Sub2), p_meta) + self.assertTrue(issubclass(POINTER(Sub2), Sub2)) + self.assertTrue(issubclass(POINTER(Sub2), POINTER(Sub))) + self.assertTrue(issubclass(POINTER(Sub), POINTER(CtBase))) + + def test_creating_pointer_in_dunder_init_2(self): + class ct_meta(type): + def __init__(self, name, bases, namespace): + super().__init__(name, bases, namespace) + + # Avoid recursion. + # (See test_creating_pointer_in_dunder_new_2) + if isinstance(self, p_meta): + return + p = p_meta(f"POINTER({self.__name__})", (self, c_void_p), {}) + ctypes._pointer_type_cache[self] = p + + class p_meta(PyCSimpleType, ct_meta): + pass + + class Core(object): + pass + + class CtBase(Core, metaclass=ct_meta): + pass + + class Sub(CtBase): + pass + + self.assertIsInstance(POINTER(Sub), p_meta) + self.assertTrue(issubclass(POINTER(Sub), Sub)) diff --git a/Lib/test/test_ctypes/test_callbacks.py b/Lib/test/test_ctypes/test_callbacks.py new file mode 100644 index 00000000000..8f483dfe1db --- /dev/null +++ b/Lib/test/test_ctypes/test_callbacks.py @@ -0,0 +1,333 @@ +import ctypes +import functools +import gc +import math +import sys +import unittest +from _ctypes import CTYPES_MAX_ARGCOUNT +from ctypes import (CDLL, cdll, Structure, CFUNCTYPE, + ArgumentError, POINTER, sizeof, + c_byte, c_ubyte, c_char, + c_short, c_ushort, c_int, c_uint, + c_long, c_longlong, c_ulonglong, c_ulong, + c_float, c_double, c_longdouble, py_object) +from ctypes.util import find_library +from test import support +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class Callbacks(unittest.TestCase): + functype = CFUNCTYPE + + def callback(self, *args): + self.got_args = args + return args[-1] + + def check_type(self, typ, arg): + PROTO = self.functype.__func__(typ, typ) + result = PROTO(self.callback)(arg) + if typ == c_float: + self.assertAlmostEqual(result, arg, places=5) + else: + self.assertEqual(self.got_args, (arg,)) + self.assertEqual(result, arg) + + PROTO = self.functype.__func__(typ, c_byte, typ) + result = PROTO(self.callback)(-3, arg) + if typ == c_float: + self.assertAlmostEqual(result, arg, places=5) + else: + self.assertEqual(self.got_args, (-3, arg)) + self.assertEqual(result, arg) + + def test_byte(self): + self.check_type(c_byte, 42) + self.check_type(c_byte, -42) + + def test_ubyte(self): + self.check_type(c_ubyte, 42) + + def test_short(self): + self.check_type(c_short, 42) + self.check_type(c_short, -42) + + def test_ushort(self): + self.check_type(c_ushort, 42) + + def test_int(self): + self.check_type(c_int, 42) + self.check_type(c_int, -42) + + def test_uint(self): + self.check_type(c_uint, 42) + + def test_long(self): + self.check_type(c_long, 42) + self.check_type(c_long, -42) + + def test_ulong(self): + self.check_type(c_ulong, 42) + + def test_longlong(self): + self.check_type(c_longlong, 42) + self.check_type(c_longlong, -42) + + def test_ulonglong(self): + self.check_type(c_ulonglong, 42) + + def test_float(self): + # only almost equal: double -> float -> double + self.check_type(c_float, math.e) + self.check_type(c_float, -math.e) + + def test_double(self): + self.check_type(c_double, 3.14) + self.check_type(c_double, -3.14) + + def test_longdouble(self): + self.check_type(c_longdouble, 3.14) + self.check_type(c_longdouble, -3.14) + + def test_char(self): + self.check_type(c_char, b"x") + self.check_type(c_char, b"a") + + def test_pyobject(self): + o = () + for o in (), [], object(): + initial = sys.getrefcount(o) + # This call leaks a reference to 'o'... + self.check_type(py_object, o) + before = sys.getrefcount(o) + # ...but this call doesn't leak any more. Where is the refcount? + self.check_type(py_object, o) + after = sys.getrefcount(o) + self.assertEqual((after, o), (before, o)) + + def test_unsupported_restype_1(self): + # Only "fundamental" result types are supported for callback + # functions, the type must have a non-NULL stginfo->setfunc. + # POINTER(c_double), for example, is not supported. + + prototype = self.functype.__func__(POINTER(c_double)) + # The type is checked when the prototype is called + self.assertRaises(TypeError, prototype, lambda: None) + + def test_unsupported_restype_2(self): + prototype = self.functype.__func__(object) + self.assertRaises(TypeError, prototype, lambda: None) + + def test_issue_7959(self): + proto = self.functype.__func__(None) + + class X: + def func(self): pass + def __init__(self): + self.v = proto(self.func) + + for i in range(32): + X() + gc.collect() + live = [x for x in gc.get_objects() + if isinstance(x, X)] + self.assertEqual(len(live), 0) + + def test_issue12483(self): + class Nasty: + def __del__(self): + gc.collect() + CFUNCTYPE(None)(lambda x=Nasty(): None) + + @unittest.skipUnless(hasattr(ctypes, 'WINFUNCTYPE'), + 'ctypes.WINFUNCTYPE is required') + def test_i38748_stackCorruption(self): + callback_funcType = ctypes.WINFUNCTYPE(c_long, c_long, c_longlong) + @callback_funcType + def callback(a, b): + c = a + b + print(f"a={a}, b={b}, c={c}") + return c + dll = cdll[_ctypes_test.__file__] + with support.captured_stdout() as out: + # With no fix for i38748, the next line will raise OSError and cause the test to fail. + self.assertEqual(dll._test_i38748_runCallback(callback, 5, 10), 15) + self.assertEqual(out.getvalue(), "a=5, b=10, c=15\n") + +if hasattr(ctypes, 'WINFUNCTYPE'): + class StdcallCallbacks(Callbacks): + functype = ctypes.WINFUNCTYPE + + +class SampleCallbacksTestCase(unittest.TestCase): + + def test_integrate(self): + # Derived from some then non-working code, posted by David Foster + dll = CDLL(_ctypes_test.__file__) + + # The function prototype called by 'integrate': double func(double); + CALLBACK = CFUNCTYPE(c_double, c_double) + + # The integrate function itself, exposed from the _ctypes_test dll + integrate = dll.integrate + integrate.argtypes = (c_double, c_double, CALLBACK, c_long) + integrate.restype = c_double + + def func(x): + return x**2 + + result = integrate(0.0, 1.0, CALLBACK(func), 10) + diff = abs(result - 1./3.) + + self.assertLess(diff, 0.01, "%s not less than 0.01" % diff) + + def test_issue_8959_a(self): + libc_path = find_library("c") + if not libc_path: + self.skipTest('could not find libc') + libc = CDLL(libc_path) + + @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int)) + def cmp_func(a, b): + return a[0] - b[0] + + array = (c_int * 5)(5, 1, 99, 7, 33) + + libc.qsort(array, len(array), sizeof(c_int), cmp_func) + self.assertEqual(array[:], [1, 5, 7, 33, 99]) + + @unittest.skipUnless(hasattr(ctypes, 'WINFUNCTYPE'), + 'ctypes.WINFUNCTYPE is required') + def test_issue_8959_b(self): + from ctypes.wintypes import BOOL, HWND, LPARAM + global windowCount + windowCount = 0 + + @ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM) + def EnumWindowsCallbackFunc(hwnd, lParam): + global windowCount + windowCount += 1 + return True #Allow windows to keep enumerating + + user32 = ctypes.windll.user32 + user32.EnumWindows(EnumWindowsCallbackFunc, 0) + + def test_callback_register_int(self): + # Issue #8275: buggy handling of callback args under Win64 + # NOTE: should be run on release builds as well + dll = CDLL(_ctypes_test.__file__) + CALLBACK = CFUNCTYPE(c_int, c_int, c_int, c_int, c_int, c_int) + # All this function does is call the callback with its args squared + func = dll._testfunc_cbk_reg_int + func.argtypes = (c_int, c_int, c_int, c_int, c_int, CALLBACK) + func.restype = c_int + + def callback(a, b, c, d, e): + return a + b + c + d + e + + result = func(2, 3, 4, 5, 6, CALLBACK(callback)) + self.assertEqual(result, callback(2*2, 3*3, 4*4, 5*5, 6*6)) + + def test_callback_register_double(self): + # Issue #8275: buggy handling of callback args under Win64 + # NOTE: should be run on release builds as well + dll = CDLL(_ctypes_test.__file__) + CALLBACK = CFUNCTYPE(c_double, c_double, c_double, c_double, + c_double, c_double) + # All this function does is call the callback with its args squared + func = dll._testfunc_cbk_reg_double + func.argtypes = (c_double, c_double, c_double, + c_double, c_double, CALLBACK) + func.restype = c_double + + def callback(a, b, c, d, e): + return a + b + c + d + e + + result = func(1.1, 2.2, 3.3, 4.4, 5.5, CALLBACK(callback)) + self.assertEqual(result, + callback(1.1*1.1, 2.2*2.2, 3.3*3.3, 4.4*4.4, 5.5*5.5)) + + def test_callback_large_struct(self): + class Check: pass + + # This should mirror the structure in Modules/_ctypes/_ctypes_test.c + class X(Structure): + _fields_ = [ + ('first', c_ulong), + ('second', c_ulong), + ('third', c_ulong), + ] + + def callback(check, s): + check.first = s.first + check.second = s.second + check.third = s.third + # See issue #29565. + # The structure should be passed by value, so + # any changes to it should not be reflected in + # the value passed + s.first = s.second = s.third = 0x0badf00d + + check = Check() + s = X() + s.first = 0xdeadbeef + s.second = 0xcafebabe + s.third = 0x0bad1dea + + CALLBACK = CFUNCTYPE(None, X) + dll = CDLL(_ctypes_test.__file__) + func = dll._testfunc_cbk_large_struct + func.argtypes = (X, CALLBACK) + func.restype = None + # the function just calls the callback with the passed structure + func(s, CALLBACK(functools.partial(callback, check))) + self.assertEqual(check.first, s.first) + self.assertEqual(check.second, s.second) + self.assertEqual(check.third, s.third) + self.assertEqual(check.first, 0xdeadbeef) + self.assertEqual(check.second, 0xcafebabe) + self.assertEqual(check.third, 0x0bad1dea) + # See issue #29565. + # Ensure that the original struct is unchanged. + self.assertEqual(s.first, check.first) + self.assertEqual(s.second, check.second) + self.assertEqual(s.third, check.third) + + def test_callback_too_many_args(self): + def func(*args): + return len(args) + + # valid call with nargs <= CTYPES_MAX_ARGCOUNT + proto = CFUNCTYPE(c_int, *(c_int,) * CTYPES_MAX_ARGCOUNT) + cb = proto(func) + args1 = (1,) * CTYPES_MAX_ARGCOUNT + self.assertEqual(cb(*args1), CTYPES_MAX_ARGCOUNT) + + # invalid call with nargs > CTYPES_MAX_ARGCOUNT + args2 = (1,) * (CTYPES_MAX_ARGCOUNT + 1) + with self.assertRaises(ArgumentError): + cb(*args2) + + # error when creating the type with too many arguments + with self.assertRaises(ArgumentError): + CFUNCTYPE(c_int, *(c_int,) * (CTYPES_MAX_ARGCOUNT + 1)) + + def test_convert_result_error(self): + def func(): + return ("tuple",) + + proto = CFUNCTYPE(c_int) + ctypes_func = proto(func) + with support.catch_unraisable_exception() as cm: + # don't test the result since it is an uninitialized value + result = ctypes_func() + + self.assertIsInstance(cm.unraisable.exc_value, TypeError) + self.assertEqual(cm.unraisable.err_msg, + f"Exception ignored on converting result " + f"of ctypes callback function {func!r}") + self.assertIsNone(cm.unraisable.object) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_cast.py b/Lib/test/test_ctypes/test_cast.py new file mode 100644 index 00000000000..604f44f03d6 --- /dev/null +++ b/Lib/test/test_ctypes/test_cast.py @@ -0,0 +1,100 @@ +import sys +import unittest +from ctypes import (Structure, Union, POINTER, cast, sizeof, addressof, + c_void_p, c_char_p, c_wchar_p, + c_byte, c_short, c_int) + + +class Test(unittest.TestCase): + def test_array2pointer(self): + array = (c_int * 3)(42, 17, 2) + + # casting an array to a pointer works. + ptr = cast(array, POINTER(c_int)) + self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) + + if 2 * sizeof(c_short) == sizeof(c_int): + ptr = cast(array, POINTER(c_short)) + if sys.byteorder == "little": + self.assertEqual([ptr[i] for i in range(6)], + [42, 0, 17, 0, 2, 0]) + else: + self.assertEqual([ptr[i] for i in range(6)], + [0, 42, 0, 17, 0, 2]) + + def test_address2pointer(self): + array = (c_int * 3)(42, 17, 2) + + address = addressof(array) + ptr = cast(c_void_p(address), POINTER(c_int)) + self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) + + ptr = cast(address, POINTER(c_int)) + self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) + + def test_p2a_objects(self): + array = (c_char_p * 5)() + self.assertEqual(array._objects, None) + array[0] = b"foo bar" + self.assertEqual(array._objects, {'0': b"foo bar"}) + + p = cast(array, POINTER(c_char_p)) + # array and p share a common _objects attribute + self.assertIs(p._objects, array._objects) + self.assertEqual(array._objects, {'0': b"foo bar", id(array): array}) + p[0] = b"spam spam" + self.assertEqual(p._objects, {'0': b"spam spam", id(array): array}) + self.assertIs(array._objects, p._objects) + p[1] = b"foo bar" + self.assertEqual(p._objects, {'1': b'foo bar', '0': b"spam spam", id(array): array}) + self.assertIs(array._objects, p._objects) + + def test_other(self): + p = cast((c_int * 4)(1, 2, 3, 4), POINTER(c_int)) + self.assertEqual(p[:4], [1,2, 3, 4]) + self.assertEqual(p[:4:], [1, 2, 3, 4]) + self.assertEqual(p[3:-1:-1], [4, 3, 2, 1]) + self.assertEqual(p[:4:3], [1, 4]) + c_int() + self.assertEqual(p[:4], [1, 2, 3, 4]) + self.assertEqual(p[:4:], [1, 2, 3, 4]) + self.assertEqual(p[3:-1:-1], [4, 3, 2, 1]) + self.assertEqual(p[:4:3], [1, 4]) + p[2] = 96 + self.assertEqual(p[:4], [1, 2, 96, 4]) + self.assertEqual(p[:4:], [1, 2, 96, 4]) + self.assertEqual(p[3:-1:-1], [4, 96, 2, 1]) + self.assertEqual(p[:4:3], [1, 4]) + c_int() + self.assertEqual(p[:4], [1, 2, 96, 4]) + self.assertEqual(p[:4:], [1, 2, 96, 4]) + self.assertEqual(p[3:-1:-1], [4, 96, 2, 1]) + self.assertEqual(p[:4:3], [1, 4]) + + def test_char_p(self): + # This didn't work: bad argument to internal function + s = c_char_p(b"hiho") + self.assertEqual(cast(cast(s, c_void_p), c_char_p).value, + b"hiho") + + def test_wchar_p(self): + s = c_wchar_p("hiho") + self.assertEqual(cast(cast(s, c_void_p), c_wchar_p).value, + "hiho") + + def test_bad_type_arg(self): + # The type argument must be a ctypes pointer type. + array_type = c_byte * sizeof(c_int) + array = array_type() + self.assertRaises(TypeError, cast, array, None) + self.assertRaises(TypeError, cast, array, array_type) + class Struct(Structure): + _fields_ = [("a", c_int)] + self.assertRaises(TypeError, cast, array, Struct) + class MyUnion(Union): + _fields_ = [("a", c_int)] + self.assertRaises(TypeError, cast, array, MyUnion) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_cfuncs.py b/Lib/test/test_ctypes/test_cfuncs.py new file mode 100644 index 00000000000..48330c4b0a7 --- /dev/null +++ b/Lib/test/test_ctypes/test_cfuncs.py @@ -0,0 +1,211 @@ +import ctypes +import unittest +from ctypes import (CDLL, + c_byte, c_ubyte, c_char, + c_short, c_ushort, c_int, c_uint, + c_long, c_ulong, c_longlong, c_ulonglong, + c_float, c_double, c_longdouble) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class CFunctions(unittest.TestCase): + _dll = CDLL(_ctypes_test.__file__) + + def S(self): + return c_longlong.in_dll(self._dll, "last_tf_arg_s").value + def U(self): + return c_ulonglong.in_dll(self._dll, "last_tf_arg_u").value + + def test_byte(self): + self._dll.tf_b.restype = c_byte + self._dll.tf_b.argtypes = (c_byte,) + self.assertEqual(self._dll.tf_b(-126), -42) + self.assertEqual(self.S(), -126) + + def test_byte_plus(self): + self._dll.tf_bb.restype = c_byte + self._dll.tf_bb.argtypes = (c_byte, c_byte) + self.assertEqual(self._dll.tf_bb(0, -126), -42) + self.assertEqual(self.S(), -126) + + def test_ubyte(self): + self._dll.tf_B.restype = c_ubyte + self._dll.tf_B.argtypes = (c_ubyte,) + self.assertEqual(self._dll.tf_B(255), 85) + self.assertEqual(self.U(), 255) + + def test_ubyte_plus(self): + self._dll.tf_bB.restype = c_ubyte + self._dll.tf_bB.argtypes = (c_byte, c_ubyte) + self.assertEqual(self._dll.tf_bB(0, 255), 85) + self.assertEqual(self.U(), 255) + + def test_short(self): + self._dll.tf_h.restype = c_short + self._dll.tf_h.argtypes = (c_short,) + self.assertEqual(self._dll.tf_h(-32766), -10922) + self.assertEqual(self.S(), -32766) + + def test_short_plus(self): + self._dll.tf_bh.restype = c_short + self._dll.tf_bh.argtypes = (c_byte, c_short) + self.assertEqual(self._dll.tf_bh(0, -32766), -10922) + self.assertEqual(self.S(), -32766) + + def test_ushort(self): + self._dll.tf_H.restype = c_ushort + self._dll.tf_H.argtypes = (c_ushort,) + self.assertEqual(self._dll.tf_H(65535), 21845) + self.assertEqual(self.U(), 65535) + + def test_ushort_plus(self): + self._dll.tf_bH.restype = c_ushort + self._dll.tf_bH.argtypes = (c_byte, c_ushort) + self.assertEqual(self._dll.tf_bH(0, 65535), 21845) + self.assertEqual(self.U(), 65535) + + def test_int(self): + self._dll.tf_i.restype = c_int + self._dll.tf_i.argtypes = (c_int,) + self.assertEqual(self._dll.tf_i(-2147483646), -715827882) + self.assertEqual(self.S(), -2147483646) + + def test_int_plus(self): + self._dll.tf_bi.restype = c_int + self._dll.tf_bi.argtypes = (c_byte, c_int) + self.assertEqual(self._dll.tf_bi(0, -2147483646), -715827882) + self.assertEqual(self.S(), -2147483646) + + def test_uint(self): + self._dll.tf_I.restype = c_uint + self._dll.tf_I.argtypes = (c_uint,) + self.assertEqual(self._dll.tf_I(4294967295), 1431655765) + self.assertEqual(self.U(), 4294967295) + + def test_uint_plus(self): + self._dll.tf_bI.restype = c_uint + self._dll.tf_bI.argtypes = (c_byte, c_uint) + self.assertEqual(self._dll.tf_bI(0, 4294967295), 1431655765) + self.assertEqual(self.U(), 4294967295) + + def test_long(self): + self._dll.tf_l.restype = c_long + self._dll.tf_l.argtypes = (c_long,) + self.assertEqual(self._dll.tf_l(-2147483646), -715827882) + self.assertEqual(self.S(), -2147483646) + + def test_long_plus(self): + self._dll.tf_bl.restype = c_long + self._dll.tf_bl.argtypes = (c_byte, c_long) + self.assertEqual(self._dll.tf_bl(0, -2147483646), -715827882) + self.assertEqual(self.S(), -2147483646) + + def test_ulong(self): + self._dll.tf_L.restype = c_ulong + self._dll.tf_L.argtypes = (c_ulong,) + self.assertEqual(self._dll.tf_L(4294967295), 1431655765) + self.assertEqual(self.U(), 4294967295) + + def test_ulong_plus(self): + self._dll.tf_bL.restype = c_ulong + self._dll.tf_bL.argtypes = (c_char, c_ulong) + self.assertEqual(self._dll.tf_bL(b' ', 4294967295), 1431655765) + self.assertEqual(self.U(), 4294967295) + + def test_longlong(self): + self._dll.tf_q.restype = c_longlong + self._dll.tf_q.argtypes = (c_longlong, ) + self.assertEqual(self._dll.tf_q(-9223372036854775806), -3074457345618258602) + self.assertEqual(self.S(), -9223372036854775806) + + def test_longlong_plus(self): + self._dll.tf_bq.restype = c_longlong + self._dll.tf_bq.argtypes = (c_byte, c_longlong) + self.assertEqual(self._dll.tf_bq(0, -9223372036854775806), -3074457345618258602) + self.assertEqual(self.S(), -9223372036854775806) + + def test_ulonglong(self): + self._dll.tf_Q.restype = c_ulonglong + self._dll.tf_Q.argtypes = (c_ulonglong, ) + self.assertEqual(self._dll.tf_Q(18446744073709551615), 6148914691236517205) + self.assertEqual(self.U(), 18446744073709551615) + + def test_ulonglong_plus(self): + self._dll.tf_bQ.restype = c_ulonglong + self._dll.tf_bQ.argtypes = (c_byte, c_ulonglong) + self.assertEqual(self._dll.tf_bQ(0, 18446744073709551615), 6148914691236517205) + self.assertEqual(self.U(), 18446744073709551615) + + def test_float(self): + self._dll.tf_f.restype = c_float + self._dll.tf_f.argtypes = (c_float,) + self.assertEqual(self._dll.tf_f(-42.), -14.) + self.assertEqual(self.S(), -42) + + def test_float_plus(self): + self._dll.tf_bf.restype = c_float + self._dll.tf_bf.argtypes = (c_byte, c_float) + self.assertEqual(self._dll.tf_bf(0, -42.), -14.) + self.assertEqual(self.S(), -42) + + def test_double(self): + self._dll.tf_d.restype = c_double + self._dll.tf_d.argtypes = (c_double,) + self.assertEqual(self._dll.tf_d(42.), 14.) + self.assertEqual(self.S(), 42) + + def test_double_plus(self): + self._dll.tf_bd.restype = c_double + self._dll.tf_bd.argtypes = (c_byte, c_double) + self.assertEqual(self._dll.tf_bd(0, 42.), 14.) + self.assertEqual(self.S(), 42) + + def test_longdouble(self): + self._dll.tf_D.restype = c_longdouble + self._dll.tf_D.argtypes = (c_longdouble,) + self.assertEqual(self._dll.tf_D(42.), 14.) + self.assertEqual(self.S(), 42) + + def test_longdouble_plus(self): + self._dll.tf_bD.restype = c_longdouble + self._dll.tf_bD.argtypes = (c_byte, c_longdouble) + self.assertEqual(self._dll.tf_bD(0, 42.), 14.) + self.assertEqual(self.S(), 42) + + def test_callwithresult(self): + def process_result(result): + return result * 2 + self._dll.tf_i.restype = process_result + self._dll.tf_i.argtypes = (c_int,) + self.assertEqual(self._dll.tf_i(42), 28) + self.assertEqual(self.S(), 42) + self.assertEqual(self._dll.tf_i(-42), -28) + self.assertEqual(self.S(), -42) + + def test_void(self): + self._dll.tv_i.restype = None + self._dll.tv_i.argtypes = (c_int,) + self.assertEqual(self._dll.tv_i(42), None) + self.assertEqual(self.S(), 42) + self.assertEqual(self._dll.tv_i(-42), None) + self.assertEqual(self.S(), -42) + + +# The following repeats the above tests with stdcall functions (where +# they are available) +if hasattr(ctypes, 'WinDLL'): + class stdcall_dll(ctypes.WinDLL): + def __getattr__(self, name): + if name[:2] == '__' and name[-2:] == '__': + raise AttributeError(name) + func = self._FuncPtr(("s_" + name, self)) + setattr(self, name, func) + return func + + class stdcallCFunctions(CFunctions): + _dll = stdcall_dll(_ctypes_test.__file__) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_checkretval.py b/Lib/test/test_ctypes/test_checkretval.py new file mode 100644 index 00000000000..9d6bfdb845e --- /dev/null +++ b/Lib/test/test_ctypes/test_checkretval.py @@ -0,0 +1,37 @@ +import ctypes +import unittest +from ctypes import CDLL, c_int +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class CHECKED(c_int): + def _check_retval_(value): + # Receives a CHECKED instance. + return str(value.value) + _check_retval_ = staticmethod(_check_retval_) + + +class Test(unittest.TestCase): + def test_checkretval(self): + dll = CDLL(_ctypes_test.__file__) + self.assertEqual(42, dll._testfunc_p_p(42)) + + dll._testfunc_p_p.restype = CHECKED + self.assertEqual("42", dll._testfunc_p_p(42)) + + dll._testfunc_p_p.restype = None + self.assertEqual(None, dll._testfunc_p_p(42)) + + del dll._testfunc_p_p.restype + self.assertEqual(42, dll._testfunc_p_p(42)) + + @unittest.skipUnless(hasattr(ctypes, 'oledll'), + 'ctypes.oledll is required') + def test_oledll(self): + oleaut32 = ctypes.oledll.oleaut32 + self.assertRaises(OSError, oleaut32.CreateTypeLib2, 0, None, None) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_delattr.py b/Lib/test/test_ctypes/test_delattr.py new file mode 100644 index 00000000000..e80b5fa6efb --- /dev/null +++ b/Lib/test/test_ctypes/test_delattr.py @@ -0,0 +1,26 @@ +import unittest +from ctypes import Structure, c_char, c_int + + +class X(Structure): + _fields_ = [("foo", c_int)] + + +class TestCase(unittest.TestCase): + def test_simple(self): + with self.assertRaises(TypeError): + del c_int(42).value + + def test_chararray(self): + chararray = (c_char * 5)() + with self.assertRaises(TypeError): + del chararray.value + + def test_struct(self): + struct = X() + with self.assertRaises(TypeError): + del struct.foo + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_dlerror.py b/Lib/test/test_ctypes/test_dlerror.py new file mode 100644 index 00000000000..1c1b2aab3d5 --- /dev/null +++ b/Lib/test/test_ctypes/test_dlerror.py @@ -0,0 +1,179 @@ +import _ctypes +import os +import platform +import sys +import test.support +import unittest +from ctypes import CDLL, c_int +from ctypes.util import find_library + + +FOO_C = r""" +#include <unistd.h> + +/* This is a 'GNU indirect function' (IFUNC) that will be called by + dlsym() to resolve the symbol "foo" to an address. Typically, such + a function would return the address of an actual function, but it + can also just return NULL. For some background on IFUNCs, see + https://willnewton.name/uncategorized/using-gnu-indirect-functions. + + Adapted from Michael Kerrisk's answer: https://stackoverflow.com/a/53590014. +*/ + +asm (".type foo STT_GNU_IFUNC"); + +void *foo(void) +{ + write($DESCRIPTOR, "OK", 2); + return NULL; +} +""" + + +@unittest.skipUnless(sys.platform.startswith('linux'), + 'test requires GNU IFUNC support') +class TestNullDlsym(unittest.TestCase): + """GH-126554: Ensure that we catch NULL dlsym return values + + In rare cases, such as when using GNU IFUNCs, dlsym(), + the C function that ctypes' CDLL uses to get the address + of symbols, can return NULL. + + The objective way of telling if an error during symbol + lookup happened is to call glibc's dlerror() and check + for a non-NULL return value. + + However, there can be cases where dlsym() returns NULL + and dlerror() is also NULL, meaning that glibc did not + encounter any error. + + In the case of ctypes, we subjectively treat that as + an error, and throw a relevant exception. + + This test case ensures that we correctly enforce + this 'dlsym returned NULL -> throw Error' rule. + """ + + def test_null_dlsym(self): + import subprocess + import tempfile + + try: + retcode = subprocess.call(["gcc", "--version"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + except OSError: + self.skipTest("gcc is missing") + if retcode != 0: + self.skipTest("gcc --version failed") + + pipe_r, pipe_w = os.pipe() + self.addCleanup(os.close, pipe_r) + self.addCleanup(os.close, pipe_w) + + with tempfile.TemporaryDirectory() as d: + # Create a C file with a GNU Indirect Function (FOO_C) + # and compile it into a shared library. + srcname = os.path.join(d, 'foo.c') + dstname = os.path.join(d, 'libfoo.so') + with open(srcname, 'w') as f: + f.write(FOO_C.replace('$DESCRIPTOR', str(pipe_w))) + args = ['gcc', '-fPIC', '-shared', '-o', dstname, srcname] + p = subprocess.run(args, capture_output=True) + + if p.returncode != 0: + # IFUNC is not supported on all architectures. + if platform.machine() == 'x86_64': + # It should be supported here. Something else went wrong. + p.check_returncode() + else: + # IFUNC might not be supported on this machine. + self.skipTest(f"could not compile indirect function: {p}") + + # Case #1: Test 'PyCFuncPtr_FromDll' from Modules/_ctypes/_ctypes.c + L = CDLL(dstname) + with self.assertRaisesRegex(AttributeError, "function 'foo' not found"): + # Try accessing the 'foo' symbol. + # It should resolve via dlsym() to NULL, + # and since we subjectively treat NULL + # addresses as errors, we should get + # an error. + L.foo + + # Assert that the IFUNC was called + self.assertEqual(os.read(pipe_r, 2), b'OK') + + # Case #2: Test 'CDataType_in_dll_impl' from Modules/_ctypes/_ctypes.c + with self.assertRaisesRegex(ValueError, "symbol 'foo' not found"): + c_int.in_dll(L, "foo") + + # Assert that the IFUNC was called + self.assertEqual(os.read(pipe_r, 2), b'OK') + + # Case #3: Test 'py_dl_sym' from Modules/_ctypes/callproc.c + dlopen = test.support.get_attribute(_ctypes, 'dlopen') + dlsym = test.support.get_attribute(_ctypes, 'dlsym') + L = dlopen(dstname) + with self.assertRaisesRegex(OSError, "symbol 'foo' not found"): + dlsym(L, "foo") + + # Assert that the IFUNC was called + self.assertEqual(os.read(pipe_r, 2), b'OK') + + +@unittest.skipUnless(os.name != 'nt', 'test requires dlerror() calls') +class TestLocalization(unittest.TestCase): + + @staticmethod + def configure_locales(func): + return test.support.run_with_locale( + 'LC_ALL', + 'fr_FR.iso88591', 'ja_JP.sjis', 'zh_CN.gbk', + 'fr_FR.utf8', 'en_US.utf8', + '', + )(func) + + @classmethod + def setUpClass(cls): + cls.libc_filename = find_library("c") + if cls.libc_filename is None: + raise unittest.SkipTest('cannot find libc') + + @configure_locales + def test_localized_error_from_dll(self): + dll = CDLL(self.libc_filename) + with self.assertRaises(AttributeError): + dll.this_name_does_not_exist + + @configure_locales + def test_localized_error_in_dll(self): + dll = CDLL(self.libc_filename) + with self.assertRaises(ValueError): + c_int.in_dll(dll, 'this_name_does_not_exist') + + @unittest.skipUnless(hasattr(_ctypes, 'dlopen'), + 'test requires _ctypes.dlopen()') + @configure_locales + def test_localized_error_dlopen(self): + missing_filename = b'missing\xff.so' + # Depending whether the locale, we may encode '\xff' differently + # but we are only interested in avoiding a UnicodeDecodeError + # when reporting the dlerror() error message which contains + # the localized filename. + filename_pattern = r'missing.*?\.so' + with self.assertRaisesRegex(OSError, filename_pattern): + _ctypes.dlopen(missing_filename, 2) + + @unittest.skipUnless(hasattr(_ctypes, 'dlopen'), + 'test requires _ctypes.dlopen()') + @unittest.skipUnless(hasattr(_ctypes, 'dlsym'), + 'test requires _ctypes.dlsym()') + @configure_locales + def test_localized_error_dlsym(self): + dll = _ctypes.dlopen(self.libc_filename) + with self.assertRaises(OSError): + _ctypes.dlsym(dll, 'this_name_does_not_exist') + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_errno.py b/Lib/test/test_ctypes/test_errno.py new file mode 100644 index 00000000000..65d99c1e492 --- /dev/null +++ b/Lib/test/test_ctypes/test_errno.py @@ -0,0 +1,81 @@ +import ctypes +import errno +import os +import threading +import unittest +from ctypes import CDLL, c_int, c_char_p, c_wchar_p, get_errno, set_errno +from ctypes.util import find_library + + +class Test(unittest.TestCase): + def test_open(self): + libc_name = find_library("c") + if libc_name is None: + self.skipTest("Unable to find C library") + + libc = CDLL(libc_name, use_errno=True) + if os.name == "nt": + libc_open = libc._open + else: + libc_open = libc.open + + libc_open.argtypes = c_char_p, c_int + + self.assertEqual(libc_open(b"", 0), -1) + self.assertEqual(get_errno(), errno.ENOENT) + + self.assertEqual(set_errno(32), errno.ENOENT) + self.assertEqual(get_errno(), 32) + + def _worker(): + set_errno(0) + + libc = CDLL(libc_name, use_errno=False) + if os.name == "nt": + libc_open = libc._open + else: + libc_open = libc.open + libc_open.argtypes = c_char_p, c_int + self.assertEqual(libc_open(b"", 0), -1) + self.assertEqual(get_errno(), 0) + + t = threading.Thread(target=_worker) + t.start() + t.join() + + self.assertEqual(get_errno(), 32) + set_errno(0) + + @unittest.skipUnless(os.name == "nt", 'Test specific to Windows') + def test_GetLastError(self): + dll = ctypes.WinDLL("kernel32", use_last_error=True) + GetModuleHandle = dll.GetModuleHandleA + GetModuleHandle.argtypes = [c_wchar_p] + + self.assertEqual(0, GetModuleHandle("foo")) + self.assertEqual(ctypes.get_last_error(), 126) + + self.assertEqual(ctypes.set_last_error(32), 126) + self.assertEqual(ctypes.get_last_error(), 32) + + def _worker(): + ctypes.set_last_error(0) + + dll = ctypes.WinDLL("kernel32", use_last_error=False) + GetModuleHandle = dll.GetModuleHandleW + GetModuleHandle.argtypes = [c_wchar_p] + GetModuleHandle("bar") + + self.assertEqual(ctypes.get_last_error(), 0) + + t = threading.Thread(target=_worker) + t.start() + t.join() + + self.assertEqual(ctypes.get_last_error(), 32) + + ctypes.set_last_error(0) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_find.py b/Lib/test/test_ctypes/test_find.py new file mode 100644 index 00000000000..85b28617d2d --- /dev/null +++ b/Lib/test/test_ctypes/test_find.py @@ -0,0 +1,156 @@ +import os.path +import sys +import test.support +import unittest +import unittest.mock +from ctypes import CDLL, RTLD_GLOBAL +from ctypes.util import find_library +from test.support import os_helper + + +# On some systems, loading the OpenGL libraries needs the RTLD_GLOBAL mode. +class Test_OpenGL_libs(unittest.TestCase): + @classmethod + def setUpClass(cls): + lib_gl = lib_glu = lib_gle = None + if sys.platform == "win32": + lib_gl = find_library("OpenGL32") + lib_glu = find_library("Glu32") + elif sys.platform == "darwin": + lib_gl = lib_glu = find_library("OpenGL") + else: + lib_gl = find_library("GL") + lib_glu = find_library("GLU") + lib_gle = find_library("gle") + + # print, for debugging + if test.support.verbose: + print("OpenGL libraries:") + for item in (("GL", lib_gl), + ("GLU", lib_glu), + ("gle", lib_gle)): + print("\t", item) + + cls.gl = cls.glu = cls.gle = None + if lib_gl: + try: + cls.gl = CDLL(lib_gl, mode=RTLD_GLOBAL) + except OSError: + pass + + if lib_glu: + try: + cls.glu = CDLL(lib_glu, RTLD_GLOBAL) + except OSError: + pass + + if lib_gle: + try: + cls.gle = CDLL(lib_gle) + except OSError: + pass + + @classmethod + def tearDownClass(cls): + cls.gl = cls.glu = cls.gle = None + + def test_gl(self): + if self.gl is None: + self.skipTest('lib_gl not available') + self.gl.glClearIndex + + def test_glu(self): + if self.glu is None: + self.skipTest('lib_glu not available') + self.glu.gluBeginCurve + + def test_gle(self): + if self.gle is None: + self.skipTest('lib_gle not available') + self.gle.gleGetJoinStyle + + def test_shell_injection(self): + result = find_library('; echo Hello shell > ' + os_helper.TESTFN) + self.assertFalse(os.path.lexists(os_helper.TESTFN)) + self.assertIsNone(result) + + +@unittest.skipUnless(sys.platform.startswith('linux'), + 'Test only valid for Linux') +class FindLibraryLinux(unittest.TestCase): + def test_find_on_libpath(self): + import subprocess + import tempfile + + try: + p = subprocess.Popen(['gcc', '--version'], stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + out, _ = p.communicate() + except OSError: + raise unittest.SkipTest('gcc, needed for test, not available') + with tempfile.TemporaryDirectory() as d: + # create an empty temporary file + srcname = os.path.join(d, 'dummy.c') + libname = 'py_ctypes_test_dummy' + dstname = os.path.join(d, 'lib%s.so' % libname) + with open(srcname, 'wb') as f: + pass + self.assertTrue(os.path.exists(srcname)) + # compile the file to a shared library + cmd = ['gcc', '-o', dstname, '--shared', + '-Wl,-soname,lib%s.so' % libname, srcname] + out = subprocess.check_output(cmd) + self.assertTrue(os.path.exists(dstname)) + # now check that the .so can't be found (since not in + # LD_LIBRARY_PATH) + self.assertIsNone(find_library(libname)) + # now add the location to LD_LIBRARY_PATH + with os_helper.EnvironmentVarGuard() as env: + KEY = 'LD_LIBRARY_PATH' + if KEY not in env: + v = d + else: + v = '%s:%s' % (env[KEY], d) + env.set(KEY, v) + # now check that the .so can be found (since in + # LD_LIBRARY_PATH) + self.assertEqual(find_library(libname), 'lib%s.so' % libname) + + def test_find_library_with_gcc(self): + with unittest.mock.patch("ctypes.util._findSoname_ldconfig", lambda *args: None): + self.assertNotEqual(find_library('c'), None) + + def test_find_library_with_ld(self): + with unittest.mock.patch("ctypes.util._findSoname_ldconfig", lambda *args: None), \ + unittest.mock.patch("ctypes.util._findLib_gcc", lambda *args: None): + self.assertNotEqual(find_library('c'), None) + + def test_gh114257(self): + self.assertIsNone(find_library("libc")) + + +@unittest.skipUnless(sys.platform == 'android', 'Test only valid for Android') +class FindLibraryAndroid(unittest.TestCase): + def test_find(self): + for name in [ + "c", "m", # POSIX + "z", # Non-POSIX, but present on Linux + "log", # Not present on Linux + ]: + with self.subTest(name=name): + path = find_library(name) + self.assertIsInstance(path, str) + self.assertEqual( + os.path.dirname(path), + "/system/lib64" if "64" in os.uname().machine + else "/system/lib") + self.assertEqual(os.path.basename(path), f"lib{name}.so") + self.assertTrue(os.path.isfile(path), path) + + for name in ["libc", "nonexistent"]: + with self.subTest(name=name): + self.assertIsNone(find_library(name)) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_frombuffer.py b/Lib/test/test_ctypes/test_frombuffer.py new file mode 100644 index 00000000000..d4e161f864d --- /dev/null +++ b/Lib/test/test_ctypes/test_frombuffer.py @@ -0,0 +1,144 @@ +import array +import gc +import unittest +from ctypes import (Structure, Union, Array, sizeof, + _Pointer, _SimpleCData, _CFuncPtr, + c_char, c_int) + + +class X(Structure): + _fields_ = [("c_int", c_int)] + init_called = False + def __init__(self): + self._init_called = True + + +class Test(unittest.TestCase): + def test_from_buffer(self): + a = array.array("i", range(16)) + x = (c_int * 16).from_buffer(a) + + y = X.from_buffer(a) + self.assertEqual(y.c_int, a[0]) + self.assertFalse(y.init_called) + + self.assertEqual(x[:], a.tolist()) + + a[0], a[-1] = 200, -200 + self.assertEqual(x[:], a.tolist()) + + self.assertRaises(BufferError, a.append, 100) + self.assertRaises(BufferError, a.pop) + + del x; del y; gc.collect(); gc.collect(); gc.collect() + a.append(100) + a.pop() + x = (c_int * 16).from_buffer(a) + + self.assertIn(a, [obj.obj if isinstance(obj, memoryview) else obj + for obj in x._objects.values()]) + + expected = x[:] + del a; gc.collect(); gc.collect(); gc.collect() + self.assertEqual(x[:], expected) + + with self.assertRaisesRegex(TypeError, "not writable"): + (c_char * 16).from_buffer(b"a" * 16) + with self.assertRaisesRegex(TypeError, "not writable"): + (c_char * 16).from_buffer(memoryview(b"a" * 16)) + with self.assertRaisesRegex(TypeError, "not C contiguous"): + (c_char * 16).from_buffer(memoryview(bytearray(b"a" * 16))[::-1]) + msg = "bytes-like object is required" + with self.assertRaisesRegex(TypeError, msg): + (c_char * 16).from_buffer("a" * 16) + + def test_fortran_contiguous(self): + try: + import _testbuffer + except ImportError as err: + self.skipTest(str(err)) + flags = _testbuffer.ND_WRITABLE | _testbuffer.ND_FORTRAN + array = _testbuffer.ndarray( + [97] * 16, format="B", shape=[4, 4], flags=flags) + with self.assertRaisesRegex(TypeError, "not C contiguous"): + (c_char * 16).from_buffer(array) + array = memoryview(array) + self.assertTrue(array.f_contiguous) + self.assertFalse(array.c_contiguous) + with self.assertRaisesRegex(TypeError, "not C contiguous"): + (c_char * 16).from_buffer(array) + + def test_from_buffer_with_offset(self): + a = array.array("i", range(16)) + x = (c_int * 15).from_buffer(a, sizeof(c_int)) + + self.assertEqual(x[:], a.tolist()[1:]) + with self.assertRaises(ValueError): + c_int.from_buffer(a, -1) + with self.assertRaises(ValueError): + (c_int * 16).from_buffer(a, sizeof(c_int)) + with self.assertRaises(ValueError): + (c_int * 1).from_buffer(a, 16 * sizeof(c_int)) + + def test_from_buffer_memoryview(self): + a = [c_char.from_buffer(memoryview(bytearray(b'a')))] + a.append(a) + del a + gc.collect() # Should not crash + + def test_from_buffer_copy(self): + a = array.array("i", range(16)) + x = (c_int * 16).from_buffer_copy(a) + + y = X.from_buffer_copy(a) + self.assertEqual(y.c_int, a[0]) + self.assertFalse(y.init_called) + + self.assertEqual(x[:], list(range(16))) + + a[0], a[-1] = 200, -200 + self.assertEqual(x[:], list(range(16))) + + a.append(100) + self.assertEqual(x[:], list(range(16))) + + self.assertEqual(x._objects, None) + + del a; gc.collect(); gc.collect(); gc.collect() + self.assertEqual(x[:], list(range(16))) + + x = (c_char * 16).from_buffer_copy(b"a" * 16) + self.assertEqual(x[:], b"a" * 16) + with self.assertRaises(TypeError): + (c_char * 16).from_buffer_copy("a" * 16) + + def test_from_buffer_copy_with_offset(self): + a = array.array("i", range(16)) + x = (c_int * 15).from_buffer_copy(a, sizeof(c_int)) + + self.assertEqual(x[:], a.tolist()[1:]) + with self.assertRaises(ValueError): + c_int.from_buffer_copy(a, -1) + with self.assertRaises(ValueError): + (c_int * 16).from_buffer_copy(a, sizeof(c_int)) + with self.assertRaises(ValueError): + (c_int * 1).from_buffer_copy(a, 16 * sizeof(c_int)) + + def test_abstract(self): + self.assertRaises(TypeError, Array.from_buffer, bytearray(10)) + self.assertRaises(TypeError, Structure.from_buffer, bytearray(10)) + self.assertRaises(TypeError, Union.from_buffer, bytearray(10)) + self.assertRaises(TypeError, _CFuncPtr.from_buffer, bytearray(10)) + self.assertRaises(TypeError, _Pointer.from_buffer, bytearray(10)) + self.assertRaises(TypeError, _SimpleCData.from_buffer, bytearray(10)) + + self.assertRaises(TypeError, Array.from_buffer_copy, b"123") + self.assertRaises(TypeError, Structure.from_buffer_copy, b"123") + self.assertRaises(TypeError, Union.from_buffer_copy, b"123") + self.assertRaises(TypeError, _CFuncPtr.from_buffer_copy, b"123") + self.assertRaises(TypeError, _Pointer.from_buffer_copy, b"123") + self.assertRaises(TypeError, _SimpleCData.from_buffer_copy, b"123") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_funcptr.py b/Lib/test/test_ctypes/test_funcptr.py new file mode 100644 index 00000000000..8362fb16d94 --- /dev/null +++ b/Lib/test/test_ctypes/test_funcptr.py @@ -0,0 +1,134 @@ +import ctypes +import unittest +from ctypes import (CDLL, Structure, CFUNCTYPE, sizeof, _CFuncPtr, + c_void_p, c_char_p, c_char, c_int, c_uint, c_long) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") +from ._support import (_CData, PyCFuncPtrType, Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_IMMUTABLETYPE) + + +try: + WINFUNCTYPE = ctypes.WINFUNCTYPE +except AttributeError: + # fake to enable this test on Linux + WINFUNCTYPE = CFUNCTYPE + +lib = CDLL(_ctypes_test.__file__) + + +class CFuncPtrTestCase(unittest.TestCase): + def test_inheritance_hierarchy(self): + self.assertEqual(_CFuncPtr.mro(), [_CFuncPtr, _CData, object]) + + self.assertEqual(PyCFuncPtrType.__name__, "PyCFuncPtrType") + self.assertEqual(type(PyCFuncPtrType), type) + + def test_type_flags(self): + for cls in _CFuncPtr, PyCFuncPtrType: + with self.subTest(cls=cls): + self.assertTrue(_CFuncPtr.__flags__ & Py_TPFLAGS_IMMUTABLETYPE) + self.assertFalse(_CFuncPtr.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION) + + def test_metaclass_details(self): + # Cannot call the metaclass __init__ more than once + CdeclCallback = CFUNCTYPE(c_int, c_int, c_int) + with self.assertRaisesRegex(SystemError, "already initialized"): + PyCFuncPtrType.__init__(CdeclCallback, 'ptr', (), {}) + + def test_basic(self): + X = WINFUNCTYPE(c_int, c_int, c_int) + + def func(*args): + return len(args) + + x = X(func) + self.assertEqual(x.restype, c_int) + self.assertEqual(x.argtypes, (c_int, c_int)) + self.assertEqual(sizeof(x), sizeof(c_void_p)) + self.assertEqual(sizeof(X), sizeof(c_void_p)) + + def test_first(self): + StdCallback = WINFUNCTYPE(c_int, c_int, c_int) + CdeclCallback = CFUNCTYPE(c_int, c_int, c_int) + + def func(a, b): + return a + b + + s = StdCallback(func) + c = CdeclCallback(func) + + self.assertEqual(s(1, 2), 3) + self.assertEqual(c(1, 2), 3) + # The following no longer raises a TypeError - it is now + # possible, as in C, to call cdecl functions with more parameters. + #self.assertRaises(TypeError, c, 1, 2, 3) + self.assertEqual(c(1, 2, 3, 4, 5, 6), 3) + if WINFUNCTYPE is not CFUNCTYPE: + self.assertRaises(TypeError, s, 1, 2, 3) + + def test_structures(self): + WNDPROC = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int) + + def wndproc(hwnd, msg, wParam, lParam): + return hwnd + msg + wParam + lParam + + HINSTANCE = c_int + HICON = c_int + HCURSOR = c_int + LPCTSTR = c_char_p + + class WNDCLASS(Structure): + _fields_ = [("style", c_uint), + ("lpfnWndProc", WNDPROC), + ("cbClsExtra", c_int), + ("cbWndExtra", c_int), + ("hInstance", HINSTANCE), + ("hIcon", HICON), + ("hCursor", HCURSOR), + ("lpszMenuName", LPCTSTR), + ("lpszClassName", LPCTSTR)] + + wndclass = WNDCLASS() + wndclass.lpfnWndProc = WNDPROC(wndproc) + + WNDPROC_2 = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int) + + self.assertIs(WNDPROC, WNDPROC_2) + self.assertEqual(wndclass.lpfnWndProc(1, 2, 3, 4), 10) + + f = wndclass.lpfnWndProc + + del wndclass + del wndproc + + self.assertEqual(f(10, 11, 12, 13), 46) + + def test_dllfunctions(self): + strchr = lib.my_strchr + strchr.restype = c_char_p + strchr.argtypes = (c_char_p, c_char) + self.assertEqual(strchr(b"abcdefghi", b"b"), b"bcdefghi") + self.assertEqual(strchr(b"abcdefghi", b"x"), None) + + strtok = lib.my_strtok + strtok.restype = c_char_p + + def c_string(init): + size = len(init) + 1 + return (c_char*size)(*init) + + s = b"a\nb\nc" + b = c_string(s) + + self.assertEqual(strtok(b, b"\n"), b"a") + self.assertEqual(strtok(None, b"\n"), b"b") + self.assertEqual(strtok(None, b"\n"), b"c") + self.assertEqual(strtok(None, b"\n"), None) + + def test_abstract(self): + self.assertRaises(TypeError, _CFuncPtr, 13, "name", 42, "iid") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_functions.py b/Lib/test/test_ctypes/test_functions.py new file mode 100644 index 00000000000..63e393f7b7c --- /dev/null +++ b/Lib/test/test_ctypes/test_functions.py @@ -0,0 +1,454 @@ +import ctypes +import sys +import unittest +from ctypes import (CDLL, Structure, Array, CFUNCTYPE, + byref, POINTER, pointer, ArgumentError, + c_char, c_wchar, c_byte, c_char_p, c_wchar_p, + c_short, c_int, c_long, c_longlong, c_void_p, + c_float, c_double, c_longdouble) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") +from _ctypes import _Pointer, _SimpleCData + + +try: + WINFUNCTYPE = ctypes.WINFUNCTYPE +except AttributeError: + # fake to enable this test on Linux + WINFUNCTYPE = CFUNCTYPE + +dll = CDLL(_ctypes_test.__file__) +if sys.platform == "win32": + windll = ctypes.WinDLL(_ctypes_test.__file__) + + +class POINT(Structure): + _fields_ = [("x", c_int), ("y", c_int)] + + +class RECT(Structure): + _fields_ = [("left", c_int), ("top", c_int), + ("right", c_int), ("bottom", c_int)] + + +class FunctionTestCase(unittest.TestCase): + + def test_mro(self): + # in Python 2.3, this raises TypeError: MRO conflict among bases classes, + # in Python 2.2 it works. + # + # But in early versions of _ctypes.c, the result of tp_new + # wasn't checked, and it even crashed Python. + # Found by Greg Chapman. + + with self.assertRaises(TypeError): + class X(object, Array): + _length_ = 5 + _type_ = "i" + + with self.assertRaises(TypeError): + class X2(object, _Pointer): + pass + + with self.assertRaises(TypeError): + class X3(object, _SimpleCData): + _type_ = "i" + + with self.assertRaises(TypeError): + class X4(object, Structure): + _fields_ = [] + + def test_c_char_parm(self): + proto = CFUNCTYPE(c_int, c_char) + def callback(*args): + return 0 + + callback = proto(callback) + + self.assertEqual(callback(b"a"), 0) + + with self.assertRaises(ArgumentError) as cm: + callback(b"abc") + + self.assertEqual(str(cm.exception), + "argument 1: TypeError: one character bytes, " + "bytearray or integer expected") + + def test_wchar_parm(self): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_wchar, c_int, c_long, c_float, c_double] + result = f(1, "x", 3, 4, 5.0, 6.0) + self.assertEqual(result, 139) + self.assertEqual(type(result), int) + + with self.assertRaises(ArgumentError) as cm: + f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(str(cm.exception), + "argument 2: TypeError: unicode string expected " + "instead of int instance") + + with self.assertRaises(ArgumentError) as cm: + f(1, "abc", 3, 4, 5.0, 6.0) + self.assertEqual(str(cm.exception), + "argument 2: TypeError: one character unicode string " + "expected") + + def test_c_char_p_parm(self): + """Test the error message when converting an incompatible type to c_char_p.""" + proto = CFUNCTYPE(c_int, c_char_p) + def callback(*args): + return 0 + + callback = proto(callback) + self.assertEqual(callback(b"abc"), 0) + + with self.assertRaises(ArgumentError) as cm: + callback(10) + + self.assertEqual(str(cm.exception), + "argument 1: TypeError: 'int' object cannot be " + "interpreted as ctypes.c_char_p") + + def test_c_wchar_p_parm(self): + """Test the error message when converting an incompatible type to c_wchar_p.""" + proto = CFUNCTYPE(c_int, c_wchar_p) + def callback(*args): + return 0 + + callback = proto(callback) + self.assertEqual(callback("abc"), 0) + + with self.assertRaises(ArgumentError) as cm: + callback(10) + + self.assertEqual(str(cm.exception), + "argument 1: TypeError: 'int' object cannot be " + "interpreted as ctypes.c_wchar_p") + + def test_c_void_p_parm(self): + """Test the error message when converting an incompatible type to c_void_p.""" + proto = CFUNCTYPE(c_int, c_void_p) + def callback(*args): + return 0 + + callback = proto(callback) + self.assertEqual(callback(5), 0) + + with self.assertRaises(ArgumentError) as cm: + callback(2.5) + + self.assertEqual(str(cm.exception), + "argument 1: TypeError: 'float' object cannot be " + "interpreted as ctypes.c_void_p") + + def test_wchar_result(self): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_wchar + result = f(0, 0, 0, 0, 0, 0) + self.assertEqual(result, '\x00') + + def test_voidresult(self): + f = dll._testfunc_v + f.restype = None + f.argtypes = [c_int, c_int, POINTER(c_int)] + result = c_int() + self.assertEqual(None, f(1, 2, byref(result))) + self.assertEqual(result.value, 3) + + def test_intresult(self): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_int + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), int) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), int) + + # If we declare the function to return a short, + # is the high part split off? + f.restype = c_short + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), int) + + result = f(1, 2, 3, 0x10004, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), int) + + # You cannot assign character format codes as restype any longer + self.assertRaises(TypeError, setattr, f, "restype", "i") + + def test_floatresult(self): + f = dll._testfunc_f_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_float + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), float) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), float) + + def test_doubleresult(self): + f = dll._testfunc_d_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_double + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), float) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), float) + + def test_longdoubleresult(self): + f = dll._testfunc_D_bhilfD + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_longdouble] + f.restype = c_longdouble + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), float) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), float) + + def test_longlongresult(self): + f = dll._testfunc_q_bhilfd + f.restype = c_longlong + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + + f = dll._testfunc_q_bhilfdq + f.restype = c_longlong + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double, c_longlong] + result = f(1, 2, 3, 4, 5.0, 6.0, 21) + self.assertEqual(result, 42) + + def test_stringresult(self): + f = dll._testfunc_p_p + f.argtypes = None + f.restype = c_char_p + result = f(b"123") + self.assertEqual(result, b"123") + + result = f(None) + self.assertEqual(result, None) + + def test_pointers(self): + f = dll._testfunc_p_p + f.restype = POINTER(c_int) + f.argtypes = [POINTER(c_int)] + + # This only works if the value c_int(42) passed to the + # function is still alive while the pointer (the result) is + # used. + + v = c_int(42) + + self.assertEqual(pointer(v).contents.value, 42) + result = f(pointer(v)) + self.assertEqual(type(result), POINTER(c_int)) + self.assertEqual(result.contents.value, 42) + + # This on works... + result = f(pointer(v)) + self.assertEqual(result.contents.value, v.value) + + p = pointer(c_int(99)) + result = f(p) + self.assertEqual(result.contents.value, 99) + + arg = byref(v) + result = f(arg) + self.assertNotEqual(result.contents, v.value) + + self.assertRaises(ArgumentError, f, byref(c_short(22))) + + # It is dangerous, however, because you don't control the lifetime + # of the pointer: + result = f(byref(c_int(99))) + self.assertNotEqual(result.contents, 99) + + def test_shorts(self): + f = dll._testfunc_callback_i_if + + args = [] + expected = [262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, + 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1] + + def callback(v): + args.append(v) + return v + + CallBack = CFUNCTYPE(c_int, c_int) + + cb = CallBack(callback) + f(2**18, cb) + self.assertEqual(args, expected) + + def test_callbacks(self): + f = dll._testfunc_callback_i_if + f.restype = c_int + f.argtypes = None + + MyCallback = CFUNCTYPE(c_int, c_int) + + def callback(value): + return value + + cb = MyCallback(callback) + result = f(-10, cb) + self.assertEqual(result, -18) + + # test with prototype + f.argtypes = [c_int, MyCallback] + cb = MyCallback(callback) + result = f(-10, cb) + self.assertEqual(result, -18) + + AnotherCallback = WINFUNCTYPE(c_int, c_int, c_int, c_int, c_int) + + # check that the prototype works: we call f with wrong + # argument types + cb = AnotherCallback(callback) + self.assertRaises(ArgumentError, f, -10, cb) + + + def test_callbacks_2(self): + # Can also use simple datatypes as argument type specifiers + # for the callback function. + # In this case the call receives an instance of that type + f = dll._testfunc_callback_i_if + f.restype = c_int + + MyCallback = CFUNCTYPE(c_int, c_int) + + f.argtypes = [c_int, MyCallback] + + def callback(value): + self.assertEqual(type(value), int) + return value + + cb = MyCallback(callback) + result = f(-10, cb) + self.assertEqual(result, -18) + + def test_longlong_callbacks(self): + + f = dll._testfunc_callback_q_qf + f.restype = c_longlong + + MyCallback = CFUNCTYPE(c_longlong, c_longlong) + + f.argtypes = [c_longlong, MyCallback] + + def callback(value): + self.assertIsInstance(value, int) + return value & 0x7FFFFFFF + + cb = MyCallback(callback) + + self.assertEqual(13577625587, f(1000000000000, cb)) + + def test_errors(self): + self.assertRaises(AttributeError, getattr, dll, "_xxx_yyy") + self.assertRaises(ValueError, c_int.in_dll, dll, "_xxx_yyy") + + def test_byval(self): + + # without prototype + ptin = POINT(1, 2) + ptout = POINT() + # EXPORT int _testfunc_byval(point in, point *pout) + result = dll._testfunc_byval(ptin, byref(ptout)) + got = result, ptout.x, ptout.y + expected = 3, 1, 2 + self.assertEqual(got, expected) + + # with prototype + ptin = POINT(101, 102) + ptout = POINT() + dll._testfunc_byval.argtypes = (POINT, POINTER(POINT)) + dll._testfunc_byval.restype = c_int + result = dll._testfunc_byval(ptin, byref(ptout)) + got = result, ptout.x, ptout.y + expected = 203, 101, 102 + self.assertEqual(got, expected) + + def test_struct_return_2H(self): + class S2H(Structure): + _fields_ = [("x", c_short), + ("y", c_short)] + dll.ret_2h_func.restype = S2H + dll.ret_2h_func.argtypes = [S2H] + inp = S2H(99, 88) + s2h = dll.ret_2h_func(inp) + self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) + + @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') + def test_struct_return_2H_stdcall(self): + class S2H(Structure): + _fields_ = [("x", c_short), + ("y", c_short)] + + windll.s_ret_2h_func.restype = S2H + windll.s_ret_2h_func.argtypes = [S2H] + s2h = windll.s_ret_2h_func(S2H(99, 88)) + self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) + + def test_struct_return_8H(self): + class S8I(Structure): + _fields_ = [("a", c_int), + ("b", c_int), + ("c", c_int), + ("d", c_int), + ("e", c_int), + ("f", c_int), + ("g", c_int), + ("h", c_int)] + dll.ret_8i_func.restype = S8I + dll.ret_8i_func.argtypes = [S8I] + inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) + s8i = dll.ret_8i_func(inp) + self.assertEqual((s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), + (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) + + @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') + def test_struct_return_8H_stdcall(self): + class S8I(Structure): + _fields_ = [("a", c_int), + ("b", c_int), + ("c", c_int), + ("d", c_int), + ("e", c_int), + ("f", c_int), + ("g", c_int), + ("h", c_int)] + windll.s_ret_8i_func.restype = S8I + windll.s_ret_8i_func.argtypes = [S8I] + inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) + s8i = windll.s_ret_8i_func(inp) + self.assertEqual( + (s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), + (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) + + def test_sf1651235(self): + # see https://bugs.python.org/issue1651235 + + proto = CFUNCTYPE(c_int, RECT, POINT) + def callback(*args): + return 0 + + callback = proto(callback) + self.assertRaises(ArgumentError, lambda: callback((1, 2, 3, 4), POINT())) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_incomplete.py b/Lib/test/test_ctypes/test_incomplete.py new file mode 100644 index 00000000000..9f859793d88 --- /dev/null +++ b/Lib/test/test_ctypes/test_incomplete.py @@ -0,0 +1,50 @@ +import ctypes +import unittest +import warnings +from ctypes import Structure, POINTER, pointer, c_char_p + + +# The incomplete pointer example from the tutorial +class TestSetPointerType(unittest.TestCase): + def tearDown(self): + # to not leak references, we must clean _pointer_type_cache + ctypes._reset_cache() + + def test_incomplete_example(self): + lpcell = POINTER("cell") + class cell(Structure): + _fields_ = [("name", c_char_p), + ("next", lpcell)] + + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + ctypes.SetPointerType(lpcell, cell) + + c1 = cell() + c1.name = b"foo" + c2 = cell() + c2.name = b"bar" + + c1.next = pointer(c2) + c2.next = pointer(c1) + + p = c1 + + result = [] + for i in range(8): + result.append(p.name) + p = p.next[0] + self.assertEqual(result, [b"foo", b"bar"] * 4) + + def test_deprecation(self): + lpcell = POINTER("cell") + class cell(Structure): + _fields_ = [("name", c_char_p), + ("next", lpcell)] + + with self.assertWarns(DeprecationWarning): + ctypes.SetPointerType(lpcell, cell) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_init.py b/Lib/test/test_ctypes/test_init.py new file mode 100644 index 00000000000..113425e5823 --- /dev/null +++ b/Lib/test/test_ctypes/test_init.py @@ -0,0 +1,43 @@ +import unittest +from ctypes import Structure, c_int + + +class X(Structure): + _fields_ = [("a", c_int), + ("b", c_int)] + new_was_called = False + + def __new__(cls): + result = super().__new__(cls) + result.new_was_called = True + return result + + def __init__(self): + self.a = 9 + self.b = 12 + + +class Y(Structure): + _fields_ = [("x", X)] + + +class InitTest(unittest.TestCase): + def test_get(self): + # make sure the only accessing a nested structure + # doesn't call the structure's __new__ and __init__ + y = Y() + self.assertEqual((y.x.a, y.x.b), (0, 0)) + self.assertEqual(y.x.new_was_called, False) + + # But explicitly creating an X structure calls __new__ and __init__, of course. + x = X() + self.assertEqual((x.a, x.b), (9, 12)) + self.assertEqual(x.new_was_called, True) + + y.x = x + self.assertEqual((y.x.a, y.x.b), (9, 12)) + self.assertEqual(y.x.new_was_called, False) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_internals.py b/Lib/test/test_ctypes/test_internals.py new file mode 100644 index 00000000000..778da6573da --- /dev/null +++ b/Lib/test/test_ctypes/test_internals.py @@ -0,0 +1,97 @@ +# This tests the internal _objects attribute + +# XXX This test must be reviewed for correctness!!! + +# ctypes' types are container types. +# +# They have an internal memory block, which only consists of some bytes, +# but it has to keep references to other objects as well. This is not +# really needed for trivial C types like int or char, but it is important +# for aggregate types like strings or pointers in particular. +# +# What about pointers? + +import sys +import unittest +from ctypes import Structure, POINTER, c_char_p, c_int + + +class ObjectsTestCase(unittest.TestCase): + def assertSame(self, a, b): + self.assertEqual(id(a), id(b)) + + def test_ints(self): + i = 42000123 + refcnt = sys.getrefcount(i) + ci = c_int(i) + self.assertEqual(refcnt, sys.getrefcount(i)) + self.assertEqual(ci._objects, None) + + def test_c_char_p(self): + s = "Hello, World".encode("ascii") + refcnt = sys.getrefcount(s) + cs = c_char_p(s) + self.assertEqual(refcnt + 1, sys.getrefcount(s)) + self.assertSame(cs._objects, s) + + def test_simple_struct(self): + class X(Structure): + _fields_ = [("a", c_int), ("b", c_int)] + + a = 421234 + b = 421235 + x = X() + self.assertEqual(x._objects, None) + x.a = a + x.b = b + self.assertEqual(x._objects, None) + + def test_embedded_structs(self): + class X(Structure): + _fields_ = [("a", c_int), ("b", c_int)] + + class Y(Structure): + _fields_ = [("x", X), ("y", X)] + + y = Y() + self.assertEqual(y._objects, None) + + x1, x2 = X(), X() + y.x, y.y = x1, x2 + self.assertEqual(y._objects, {"0": {}, "1": {}}) + x1.a, x2.b = 42, 93 + self.assertEqual(y._objects, {"0": {}, "1": {}}) + + def test_xxx(self): + class X(Structure): + _fields_ = [("a", c_char_p), ("b", c_char_p)] + + class Y(Structure): + _fields_ = [("x", X), ("y", X)] + + s1 = b"Hello, World" + s2 = b"Hallo, Welt" + + x = X() + x.a = s1 + x.b = s2 + self.assertEqual(x._objects, {"0": s1, "1": s2}) + + y = Y() + y.x = x + self.assertEqual(y._objects, {"0": {"0": s1, "1": s2}}) + + def test_ptr_struct(self): + class X(Structure): + _fields_ = [("data", POINTER(c_int))] + + A = c_int*4 + a = A(11, 22, 33, 44) + self.assertEqual(a._objects, None) + + x = X() + x.data = a + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_keeprefs.py b/Lib/test/test_ctypes/test_keeprefs.py new file mode 100644 index 00000000000..23b03b64b4a --- /dev/null +++ b/Lib/test/test_ctypes/test_keeprefs.py @@ -0,0 +1,124 @@ +import unittest +from ctypes import (Structure, POINTER, pointer, _pointer_type_cache, + c_char_p, c_int) + + +class SimpleTestCase(unittest.TestCase): + def test_cint(self): + x = c_int() + self.assertEqual(x._objects, None) + x.value = 42 + self.assertEqual(x._objects, None) + x = c_int(99) + self.assertEqual(x._objects, None) + + def test_ccharp(self): + x = c_char_p() + self.assertEqual(x._objects, None) + x.value = b"abc" + self.assertEqual(x._objects, b"abc") + x = c_char_p(b"spam") + self.assertEqual(x._objects, b"spam") + + +class StructureTestCase(unittest.TestCase): + def test_cint_struct(self): + class X(Structure): + _fields_ = [("a", c_int), + ("b", c_int)] + + x = X() + self.assertEqual(x._objects, None) + x.a = 42 + x.b = 99 + self.assertEqual(x._objects, None) + + def test_ccharp_struct(self): + class X(Structure): + _fields_ = [("a", c_char_p), + ("b", c_char_p)] + x = X() + self.assertEqual(x._objects, None) + + x.a = b"spam" + x.b = b"foo" + self.assertEqual(x._objects, {"0": b"spam", "1": b"foo"}) + + def test_struct_struct(self): + class POINT(Structure): + _fields_ = [("x", c_int), ("y", c_int)] + class RECT(Structure): + _fields_ = [("ul", POINT), ("lr", POINT)] + + r = RECT() + r.ul.x = 0 + r.ul.y = 1 + r.lr.x = 2 + r.lr.y = 3 + self.assertEqual(r._objects, None) + + r = RECT() + pt = POINT(1, 2) + r.ul = pt + self.assertEqual(r._objects, {'0': {}}) + r.ul.x = 22 + r.ul.y = 44 + self.assertEqual(r._objects, {'0': {}}) + r.lr = POINT() + self.assertEqual(r._objects, {'0': {}, '1': {}}) + + +class ArrayTestCase(unittest.TestCase): + def test_cint_array(self): + INTARR = c_int * 3 + + ia = INTARR() + self.assertEqual(ia._objects, None) + ia[0] = 1 + ia[1] = 2 + ia[2] = 3 + self.assertEqual(ia._objects, None) + + class X(Structure): + _fields_ = [("x", c_int), + ("a", INTARR)] + + x = X() + x.x = 1000 + x.a[0] = 42 + x.a[1] = 96 + self.assertEqual(x._objects, None) + x.a = ia + self.assertEqual(x._objects, {'1': {}}) + + +class PointerTestCase(unittest.TestCase): + def test_p_cint(self): + i = c_int(42) + x = pointer(i) + self.assertEqual(x._objects, {'1': i}) + + +class PointerToStructure(unittest.TestCase): + def test(self): + class POINT(Structure): + _fields_ = [("x", c_int), ("y", c_int)] + class RECT(Structure): + _fields_ = [("a", POINTER(POINT)), + ("b", POINTER(POINT))] + r = RECT() + p1 = POINT(1, 2) + + r.a = pointer(p1) + r.b = pointer(p1) + + r.a[0].x = 42 + r.a[0].y = 99 + + # to avoid leaking when tests are run several times + # clean up the types left in the cache. + del _pointer_type_cache[POINT] + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_libc.py b/Lib/test/test_ctypes/test_libc.py new file mode 100644 index 00000000000..7716100b08f --- /dev/null +++ b/Lib/test/test_ctypes/test_libc.py @@ -0,0 +1,38 @@ +import math +import unittest +from ctypes import (CDLL, CFUNCTYPE, POINTER, create_string_buffer, sizeof, + c_void_p, c_char, c_int, c_double, c_size_t) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +lib = CDLL(_ctypes_test.__file__) + + +def three_way_cmp(x, y): + """Return -1 if x < y, 0 if x == y and 1 if x > y""" + return (x > y) - (x < y) + + +class LibTest(unittest.TestCase): + def test_sqrt(self): + lib.my_sqrt.argtypes = c_double, + lib.my_sqrt.restype = c_double + self.assertEqual(lib.my_sqrt(4.0), 2.0) + self.assertEqual(lib.my_sqrt(2.0), math.sqrt(2.0)) + + def test_qsort(self): + comparefunc = CFUNCTYPE(c_int, POINTER(c_char), POINTER(c_char)) + lib.my_qsort.argtypes = c_void_p, c_size_t, c_size_t, comparefunc + lib.my_qsort.restype = None + + def sort(a, b): + return three_way_cmp(a[0], b[0]) + + chars = create_string_buffer(b"spam, spam, and spam") + lib.my_qsort(chars, len(chars)-1, sizeof(c_char), comparefunc(sort)) + self.assertEqual(chars.raw, b" ,,aaaadmmmnpppsss\x00") + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_loading.py b/Lib/test/test_ctypes/test_loading.py new file mode 100644 index 00000000000..a4d54f676a6 --- /dev/null +++ b/Lib/test/test_ctypes/test_loading.py @@ -0,0 +1,208 @@ +import _ctypes +import ctypes +import os +import shutil +import subprocess +import sys +import test.support +import unittest +from ctypes import CDLL, cdll, addressof, c_void_p, c_char_p +from ctypes.util import find_library +from test.support import import_helper, os_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +libc_name = None + + +def setUpModule(): + global libc_name + if os.name == "nt": + libc_name = find_library("c") + elif sys.platform == "cygwin": + libc_name = "cygwin1.dll" + else: + libc_name = find_library("c") + + if test.support.verbose: + print("libc_name is", libc_name) + + +class LoaderTest(unittest.TestCase): + + unknowndll = "xxrandomnamexx" + + def test_load(self): + if libc_name is not None: + test_lib = libc_name + else: + if os.name == "nt": + test_lib = _ctypes_test.__file__ + else: + self.skipTest('could not find library to load') + CDLL(test_lib) + CDLL(os.path.basename(test_lib)) + CDLL(os_helper.FakePath(test_lib)) + self.assertRaises(OSError, CDLL, self.unknowndll) + + def test_load_version(self): + if libc_name is None: + self.skipTest('could not find libc') + if os.path.basename(libc_name) != 'libc.so.6': + self.skipTest('wrong libc path for test') + cdll.LoadLibrary("libc.so.6") + # linux uses version, libc 9 should not exist + self.assertRaises(OSError, cdll.LoadLibrary, "libc.so.9") + self.assertRaises(OSError, cdll.LoadLibrary, self.unknowndll) + + def test_find(self): + found = False + for name in ("c", "m"): + lib = find_library(name) + if lib: + found = True + cdll.LoadLibrary(lib) + CDLL(lib) + if not found: + self.skipTest("Could not find c and m libraries") + + @unittest.skipUnless(os.name == "nt", + 'test specific to Windows') + def test_load_library(self): + # CRT is no longer directly loadable. See issue23606 for the + # discussion about alternative approaches. + #self.assertIsNotNone(libc_name) + if test.support.verbose: + print(find_library("kernel32")) + print(find_library("user32")) + + if os.name == "nt": + ctypes.windll.kernel32.GetModuleHandleW + ctypes.windll["kernel32"].GetModuleHandleW + ctypes.windll.LoadLibrary("kernel32").GetModuleHandleW + ctypes.WinDLL("kernel32").GetModuleHandleW + # embedded null character + self.assertRaises(ValueError, ctypes.windll.LoadLibrary, "kernel32\0") + + @unittest.skipUnless(os.name == "nt", + 'test specific to Windows') + def test_load_ordinal_functions(self): + dll = ctypes.WinDLL(_ctypes_test.__file__) + # We load the same function both via ordinal and name + func_ord = dll[2] + func_name = dll.GetString + # addressof gets the address where the function pointer is stored + a_ord = addressof(func_ord) + a_name = addressof(func_name) + f_ord_addr = c_void_p.from_address(a_ord).value + f_name_addr = c_void_p.from_address(a_name).value + self.assertEqual(hex(f_ord_addr), hex(f_name_addr)) + + self.assertRaises(AttributeError, dll.__getitem__, 1234) + + @unittest.skipUnless(os.name == "nt", 'Windows-specific test') + def test_load_without_name_and_with_handle(self): + handle = ctypes.windll.kernel32._handle + lib = ctypes.WinDLL(name=None, handle=handle) + self.assertIs(handle, lib._handle) + + @unittest.skipUnless(os.name == "nt", 'Windows-specific test') + def test_1703286_A(self): + # On winXP 64-bit, advapi32 loads at an address that does + # NOT fit into a 32-bit integer. FreeLibrary must be able + # to accept this address. + + # These are tests for https://bugs.python.org/issue1703286 + handle = _ctypes.LoadLibrary("advapi32") + _ctypes.FreeLibrary(handle) + + @unittest.skipUnless(os.name == "nt", 'Windows-specific test') + def test_1703286_B(self): + # Since on winXP 64-bit advapi32 loads like described + # above, the (arbitrarily selected) CloseEventLog function + # also has a high address. 'call_function' should accept + # addresses so large. + + advapi32 = ctypes.windll.advapi32 + # Calling CloseEventLog with a NULL argument should fail, + # but the call should not segfault or so. + self.assertEqual(0, advapi32.CloseEventLog(None)) + + kernel32 = ctypes.windll.kernel32 + kernel32.GetProcAddress.argtypes = c_void_p, c_char_p + kernel32.GetProcAddress.restype = c_void_p + proc = kernel32.GetProcAddress(advapi32._handle, b"CloseEventLog") + self.assertTrue(proc) + + # This is the real test: call the function via 'call_function' + self.assertEqual(0, _ctypes.call_function(proc, (None,))) + + @unittest.skipUnless(os.name == "nt", + 'test specific to Windows') + def test_load_hasattr(self): + # bpo-34816: shouldn't raise OSError + self.assertFalse(hasattr(ctypes.windll, 'test')) + + @unittest.skipUnless(os.name == "nt", + 'test specific to Windows') + def test_load_dll_with_flags(self): + _sqlite3 = import_helper.import_module("_sqlite3") + src = _sqlite3.__file__ + if os.path.basename(src).partition(".")[0].lower().endswith("_d"): + ext = "_d.dll" + else: + ext = ".dll" + + with os_helper.temp_dir() as tmp: + # We copy two files and load _sqlite3.dll (formerly .pyd), + # which has a dependency on sqlite3.dll. Then we test + # loading it in subprocesses to avoid it starting in memory + # for each test. + target = os.path.join(tmp, "_sqlite3.dll") + shutil.copy(src, target) + shutil.copy(os.path.join(os.path.dirname(src), "sqlite3" + ext), + os.path.join(tmp, "sqlite3" + ext)) + + def should_pass(command): + with self.subTest(command): + subprocess.check_output( + [sys.executable, "-c", + "from ctypes import *; import nt;" + command], + cwd=tmp + ) + + def should_fail(command): + with self.subTest(command): + with self.assertRaises(subprocess.CalledProcessError): + subprocess.check_output( + [sys.executable, "-c", + "from ctypes import *; import nt;" + command], + cwd=tmp, stderr=subprocess.STDOUT, + ) + + # Default load should not find this in CWD + should_fail("WinDLL('_sqlite3.dll')") + + # Relative path (but not just filename) should succeed + should_pass("WinDLL('./_sqlite3.dll')") + + # Insecure load flags should succeed + # Clear the DLL directory to avoid safe search settings propagating + should_pass("windll.kernel32.SetDllDirectoryW(None); WinDLL('_sqlite3.dll', winmode=0)") + + # Full path load without DLL_LOAD_DIR shouldn't find dependency + should_fail("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + + "winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32)") + + # Full path load with DLL_LOAD_DIR should succeed + should_pass("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + + "winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32|" + + "nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)") + + # User-specified directory should succeed + should_pass("import os; p = os.add_dll_directory(os.getcwd());" + + "WinDLL('_sqlite3.dll'); p.close()") + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_macholib.py b/Lib/test/test_ctypes/test_macholib.py new file mode 100644 index 00000000000..9d906179956 --- /dev/null +++ b/Lib/test/test_ctypes/test_macholib.py @@ -0,0 +1,112 @@ +# Bob Ippolito: +# +# Ok.. the code to find the filename for __getattr__ should look +# something like: +# +# import os +# from macholib.dyld import dyld_find +# +# def find_lib(name): +# possible = ['lib'+name+'.dylib', name+'.dylib', +# name+'.framework/'+name] +# for dylib in possible: +# try: +# return os.path.realpath(dyld_find(dylib)) +# except ValueError: +# pass +# raise ValueError, "%s not found" % (name,) +# +# It'll have output like this: +# +# >>> find_lib('pthread') +# '/usr/lib/libSystem.B.dylib' +# >>> find_lib('z') +# '/usr/lib/libz.1.dylib' +# >>> find_lib('IOKit') +# '/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit' +# +# -bob + +import os +import sys +import unittest + +from ctypes.macholib.dyld import dyld_find +from ctypes.macholib.dylib import dylib_info +from ctypes.macholib.framework import framework_info + + +def find_lib(name): + possible = ['lib'+name+'.dylib', name+'.dylib', name+'.framework/'+name] + for dylib in possible: + try: + return os.path.realpath(dyld_find(dylib)) + except ValueError: + pass + raise ValueError("%s not found" % (name,)) + + +def d(location=None, name=None, shortname=None, version=None, suffix=None): + return {'location': location, 'name': name, 'shortname': shortname, + 'version': version, 'suffix': suffix} + + +class MachOTest(unittest.TestCase): + @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') + def test_find(self): + self.assertEqual(dyld_find('libSystem.dylib'), + '/usr/lib/libSystem.dylib') + self.assertEqual(dyld_find('System.framework/System'), + '/System/Library/Frameworks/System.framework/System') + + # On Mac OS 11, system dylibs are only present in the shared cache, + # so symlinks like libpthread.dylib -> libSystem.B.dylib will not + # be resolved by dyld_find + self.assertIn(find_lib('pthread'), + ('/usr/lib/libSystem.B.dylib', '/usr/lib/libpthread.dylib')) + + result = find_lib('z') + # Issue #21093: dyld default search path includes $HOME/lib and + # /usr/local/lib before /usr/lib, which caused test failures if + # a local copy of libz exists in one of them. Now ignore the head + # of the path. + self.assertRegex(result, r".*/lib/libz.*\.dylib") + + self.assertIn(find_lib('IOKit'), + ('/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit', + '/System/Library/Frameworks/IOKit.framework/IOKit')) + + @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') + def test_info(self): + self.assertIsNone(dylib_info('completely/invalid')) + self.assertIsNone(dylib_info('completely/invalide_debug')) + self.assertEqual(dylib_info('P/Foo.dylib'), d('P', 'Foo.dylib', 'Foo')) + self.assertEqual(dylib_info('P/Foo_debug.dylib'), + d('P', 'Foo_debug.dylib', 'Foo', suffix='debug')) + self.assertEqual(dylib_info('P/Foo.A.dylib'), + d('P', 'Foo.A.dylib', 'Foo', 'A')) + self.assertEqual(dylib_info('P/Foo_debug.A.dylib'), + d('P', 'Foo_debug.A.dylib', 'Foo_debug', 'A')) + self.assertEqual(dylib_info('P/Foo.A_debug.dylib'), + d('P', 'Foo.A_debug.dylib', 'Foo', 'A', 'debug')) + + @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') + def test_framework_info(self): + self.assertIsNone(framework_info('completely/invalid')) + self.assertIsNone(framework_info('completely/invalid/_debug')) + self.assertIsNone(framework_info('P/F.framework')) + self.assertIsNone(framework_info('P/F.framework/_debug')) + self.assertEqual(framework_info('P/F.framework/F'), + d('P', 'F.framework/F', 'F')) + self.assertEqual(framework_info('P/F.framework/F_debug'), + d('P', 'F.framework/F_debug', 'F', suffix='debug')) + self.assertIsNone(framework_info('P/F.framework/Versions')) + self.assertIsNone(framework_info('P/F.framework/Versions/A')) + self.assertEqual(framework_info('P/F.framework/Versions/A/F'), + d('P', 'F.framework/Versions/A/F', 'F', 'A')) + self.assertEqual(framework_info('P/F.framework/Versions/A/F_debug'), + d('P', 'F.framework/Versions/A/F_debug', 'F', 'A', 'debug')) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_memfunctions.py b/Lib/test/test_ctypes/test_memfunctions.py new file mode 100644 index 00000000000..112b27ba48e --- /dev/null +++ b/Lib/test/test_ctypes/test_memfunctions.py @@ -0,0 +1,82 @@ +import sys +import unittest +from test import support +from ctypes import (POINTER, sizeof, cast, + create_string_buffer, string_at, + create_unicode_buffer, wstring_at, + memmove, memset, + c_char_p, c_byte, c_ubyte, c_wchar) + + +class MemFunctionsTest(unittest.TestCase): + def test_overflow(self): + # string_at and wstring_at must use the Python calling + # convention (which acquires the GIL and checks the Python + # error flag). Provoke an error and catch it; see also issue + # gh-47804. + self.assertRaises((OverflowError, MemoryError, SystemError), + lambda: wstring_at(u"foo", sys.maxsize - 1)) + self.assertRaises((OverflowError, MemoryError, SystemError), + lambda: string_at("foo", sys.maxsize - 1)) + + def test_memmove(self): + # large buffers apparently increase the chance that the memory + # is allocated in high address space. + a = create_string_buffer(1000000) + p = b"Hello, World" + result = memmove(a, p, len(p)) + self.assertEqual(a.value, b"Hello, World") + + self.assertEqual(string_at(result), b"Hello, World") + self.assertEqual(string_at(result, 5), b"Hello") + self.assertEqual(string_at(result, 16), b"Hello, World\0\0\0\0") + self.assertEqual(string_at(result, 0), b"") + + def test_memset(self): + a = create_string_buffer(1000000) + result = memset(a, ord('x'), 16) + self.assertEqual(a.value, b"xxxxxxxxxxxxxxxx") + + self.assertEqual(string_at(result), b"xxxxxxxxxxxxxxxx") + self.assertEqual(string_at(a), b"xxxxxxxxxxxxxxxx") + self.assertEqual(string_at(a, 20), b"xxxxxxxxxxxxxxxx\0\0\0\0") + + def test_cast(self): + a = (c_ubyte * 32)(*map(ord, "abcdef")) + self.assertEqual(cast(a, c_char_p).value, b"abcdef") + self.assertEqual(cast(a, POINTER(c_byte))[:7], + [97, 98, 99, 100, 101, 102, 0]) + self.assertEqual(cast(a, POINTER(c_byte))[:7:], + [97, 98, 99, 100, 101, 102, 0]) + self.assertEqual(cast(a, POINTER(c_byte))[6:-1:-1], + [0, 102, 101, 100, 99, 98, 97]) + self.assertEqual(cast(a, POINTER(c_byte))[:7:2], + [97, 99, 101, 0]) + self.assertEqual(cast(a, POINTER(c_byte))[:7:7], + [97]) + + @support.refcount_test + def test_string_at(self): + s = string_at(b"foo bar") + # XXX The following may be wrong, depending on how Python + # manages string instances + self.assertEqual(2, sys.getrefcount(s)) + self.assertTrue(s, "foo bar") + + self.assertEqual(string_at(b"foo bar", 7), b"foo bar") + self.assertEqual(string_at(b"foo bar", 3), b"foo") + + def test_wstring_at(self): + p = create_unicode_buffer("Hello, World") + a = create_unicode_buffer(1000000) + result = memmove(a, p, len(p) * sizeof(c_wchar)) + self.assertEqual(a.value, "Hello, World") + + self.assertEqual(wstring_at(a), "Hello, World") + self.assertEqual(wstring_at(a, 5), "Hello") + self.assertEqual(wstring_at(a, 16), "Hello, World\0\0\0\0") + self.assertEqual(wstring_at(a, 0), "") + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_numbers.py b/Lib/test/test_ctypes/test_numbers.py new file mode 100644 index 00000000000..29108a28ec1 --- /dev/null +++ b/Lib/test/test_ctypes/test_numbers.py @@ -0,0 +1,200 @@ +import array +import struct +import sys +import unittest +from operator import truth +from ctypes import (byref, sizeof, alignment, + c_char, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, + c_long, c_ulong, c_longlong, c_ulonglong, + c_float, c_double, c_longdouble, c_bool) + + +def valid_ranges(*types): + # given a sequence of numeric types, collect their _type_ + # attribute, which is a single format character compatible with + # the struct module, use the struct module to calculate the + # minimum and maximum value allowed for this format. + # Returns a list of (min, max) values. + result = [] + for t in types: + fmt = t._type_ + size = struct.calcsize(fmt) + a = struct.unpack(fmt, (b"\x00"*32)[:size])[0] + b = struct.unpack(fmt, (b"\xFF"*32)[:size])[0] + c = struct.unpack(fmt, (b"\x7F"+b"\x00"*32)[:size])[0] + d = struct.unpack(fmt, (b"\x80"+b"\xFF"*32)[:size])[0] + result.append((min(a, b, c, d), max(a, b, c, d))) + return result + + +ArgType = type(byref(c_int(0))) + +unsigned_types = [c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong] +signed_types = [c_byte, c_short, c_int, c_long, c_longlong] +bool_types = [c_bool] +float_types = [c_double, c_float] + +unsigned_ranges = valid_ranges(*unsigned_types) +signed_ranges = valid_ranges(*signed_types) +bool_values = [True, False, 0, 1, -1, 5000, 'test', [], [1]] + + +class NumberTestCase(unittest.TestCase): + + def test_default_init(self): + # default values are set to zero + for t in signed_types + unsigned_types + float_types: + self.assertEqual(t().value, 0) + + def test_unsigned_values(self): + # the value given to the constructor is available + # as the 'value' attribute + for t, (l, h) in zip(unsigned_types, unsigned_ranges): + self.assertEqual(t(l).value, l) + self.assertEqual(t(h).value, h) + + def test_signed_values(self): + # see above + for t, (l, h) in zip(signed_types, signed_ranges): + self.assertEqual(t(l).value, l) + self.assertEqual(t(h).value, h) + + def test_bool_values(self): + for t, v in zip(bool_types, bool_values): + self.assertEqual(t(v).value, truth(v)) + + def test_typeerror(self): + # Only numbers are allowed in the constructor, + # otherwise TypeError is raised + for t in signed_types + unsigned_types + float_types: + self.assertRaises(TypeError, t, "") + self.assertRaises(TypeError, t, None) + + def test_from_param(self): + # the from_param class method attribute always + # returns PyCArgObject instances + for t in signed_types + unsigned_types + float_types: + self.assertEqual(ArgType, type(t.from_param(0))) + + def test_byref(self): + # calling byref returns also a PyCArgObject instance + for t in signed_types + unsigned_types + float_types + bool_types: + parm = byref(t()) + self.assertEqual(ArgType, type(parm)) + + + def test_floats(self): + # c_float and c_double can be created from + # Python int and float + class FloatLike: + def __float__(self): + return 2.0 + f = FloatLike() + for t in float_types: + self.assertEqual(t(2.0).value, 2.0) + self.assertEqual(t(2).value, 2.0) + self.assertEqual(t(2).value, 2.0) + self.assertEqual(t(f).value, 2.0) + + def test_integers(self): + class FloatLike: + def __float__(self): + return 2.0 + f = FloatLike() + class IntLike: + def __int__(self): + return 2 + d = IntLike() + class IndexLike: + def __index__(self): + return 2 + i = IndexLike() + # integers cannot be constructed from floats, + # but from integer-like objects + for t in signed_types + unsigned_types: + self.assertRaises(TypeError, t, 3.14) + self.assertRaises(TypeError, t, f) + self.assertRaises(TypeError, t, d) + self.assertEqual(t(i).value, 2) + + def test_sizes(self): + for t in signed_types + unsigned_types + float_types + bool_types: + try: + size = struct.calcsize(t._type_) + except struct.error: + continue + # sizeof of the type... + self.assertEqual(sizeof(t), size) + # and sizeof of an instance + self.assertEqual(sizeof(t()), size) + + def test_alignments(self): + for t in signed_types + unsigned_types + float_types: + code = t._type_ # the typecode + align = struct.calcsize("c%c" % code) - struct.calcsize(code) + + # alignment of the type... + self.assertEqual((code, alignment(t)), + (code, align)) + # and alignment of an instance + self.assertEqual((code, alignment(t())), + (code, align)) + + def test_int_from_address(self): + for t in signed_types + unsigned_types: + # the array module doesn't support all format codes + # (no 'q' or 'Q') + try: + array.array(t._type_) + except ValueError: + continue + a = array.array(t._type_, [100]) + + # v now is an integer at an 'external' memory location + v = t.from_address(a.buffer_info()[0]) + self.assertEqual(v.value, a[0]) + self.assertEqual(type(v), t) + + # changing the value at the memory location changes v's value also + a[0] = 42 + self.assertEqual(v.value, a[0]) + + + def test_float_from_address(self): + for t in float_types: + a = array.array(t._type_, [3.14]) + v = t.from_address(a.buffer_info()[0]) + self.assertEqual(v.value, a[0]) + self.assertIs(type(v), t) + a[0] = 2.3456e17 + self.assertEqual(v.value, a[0]) + self.assertIs(type(v), t) + + def test_char_from_address(self): + a = array.array('b', [0]) + a[0] = ord('x') + v = c_char.from_address(a.buffer_info()[0]) + self.assertEqual(v.value, b'x') + self.assertIs(type(v), c_char) + + a[0] = ord('?') + self.assertEqual(v.value, b'?') + + def test_init(self): + # c_int() can be initialized from Python's int, and c_int. + # Not from c_long or so, which seems strange, abc should + # probably be changed: + self.assertRaises(TypeError, c_int, c_long(42)) + + def test_float_overflow(self): + big_int = int(sys.float_info.max) * 2 + for t in float_types + [c_longdouble]: + self.assertRaises(OverflowError, t, big_int) + if (hasattr(t, "__ctype_be__")): + self.assertRaises(OverflowError, t.__ctype_be__, big_int) + if (hasattr(t, "__ctype_le__")): + self.assertRaises(OverflowError, t.__ctype_le__, big_int) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_objects.py b/Lib/test/test_ctypes/test_objects.py new file mode 100644 index 00000000000..fb01421b955 --- /dev/null +++ b/Lib/test/test_ctypes/test_objects.py @@ -0,0 +1,66 @@ +r''' +This tests the '_objects' attribute of ctypes instances. '_objects' +holds references to objects that must be kept alive as long as the +ctypes instance, to make sure that the memory buffer is valid. + +WARNING: The '_objects' attribute is exposed ONLY for debugging ctypes itself, +it MUST NEVER BE MODIFIED! + +'_objects' is initialized to a dictionary on first use, before that it +is None. + +Here is an array of string pointers: + +>>> from ctypes import Structure, c_int, c_char_p +>>> array = (c_char_p * 5)() +>>> print(array._objects) +None +>>> + +The memory block stores pointers to strings, and the strings itself +assigned from Python must be kept. + +>>> array[4] = b'foo bar' +>>> array._objects +{'4': b'foo bar'} +>>> array[4] +b'foo bar' +>>> + +It gets more complicated when the ctypes instance itself is contained +in a 'base' object. + +>>> class X(Structure): +... _fields_ = [("x", c_int), ("y", c_int), ("array", c_char_p * 5)] +... +>>> x = X() +>>> print(x._objects) +None +>>> + +The'array' attribute of the 'x' object shares part of the memory buffer +of 'x' ('_b_base_' is either None, or the root object owning the memory block): + +>>> print(x.array._b_base_) # doctest: +ELLIPSIS +<test.test_ctypes.test_objects.X object at 0x...> +>>> + +>>> x.array[0] = b'spam spam spam' +>>> x._objects +{'0:2': b'spam spam spam'} +>>> x.array._b_base_._objects +{'0:2': b'spam spam spam'} +>>> +''' + +import doctest +import unittest + + +def load_tests(loader, tests, pattern): + tests.addTest(doctest.DocTestSuite()) + return tests + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_parameters.py b/Lib/test/test_ctypes/test_parameters.py new file mode 100644 index 00000000000..1a6ddb91a7d --- /dev/null +++ b/Lib/test/test_ctypes/test_parameters.py @@ -0,0 +1,288 @@ +import sys +import unittest +import test.support +from ctypes import (CDLL, PyDLL, ArgumentError, + Structure, Array, Union, + _Pointer, _SimpleCData, _CFuncPtr, + POINTER, pointer, byref, + c_void_p, c_char_p, c_wchar_p, py_object, + c_bool, + c_char, c_wchar, + c_byte, c_ubyte, + c_short, c_ushort, + c_int, c_uint, + c_long, c_ulong, + c_longlong, c_ulonglong, + c_float, c_double, c_longdouble) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class SimpleTypesTestCase(unittest.TestCase): + def setUp(self): + try: + from _ctypes import set_conversion_mode + except ImportError: + pass + else: + self.prev_conv_mode = set_conversion_mode("ascii", "strict") + + def tearDown(self): + try: + from _ctypes import set_conversion_mode + except ImportError: + pass + else: + set_conversion_mode(*self.prev_conv_mode) + + def test_subclasses(self): + # ctypes 0.9.5 and before did overwrite from_param in SimpleType_new + class CVOIDP(c_void_p): + def from_param(cls, value): + return value * 2 + from_param = classmethod(from_param) + + class CCHARP(c_char_p): + def from_param(cls, value): + return value * 4 + from_param = classmethod(from_param) + + self.assertEqual(CVOIDP.from_param("abc"), "abcabc") + self.assertEqual(CCHARP.from_param("abc"), "abcabcabcabc") + + def test_subclasses_c_wchar_p(self): + class CWCHARP(c_wchar_p): + def from_param(cls, value): + return value * 3 + from_param = classmethod(from_param) + + self.assertEqual(CWCHARP.from_param("abc"), "abcabcabc") + + # XXX Replace by c_char_p tests + def test_cstrings(self): + # c_char_p.from_param on a Python String packs the string + # into a cparam object + s = b"123" + self.assertIs(c_char_p.from_param(s)._obj, s) + + # new in 0.9.1: convert (encode) unicode to ascii + self.assertEqual(c_char_p.from_param(b"123")._obj, b"123") + self.assertRaises(TypeError, c_char_p.from_param, "123\377") + self.assertRaises(TypeError, c_char_p.from_param, 42) + + # calling c_char_p.from_param with a c_char_p instance + # returns the argument itself: + a = c_char_p(b"123") + self.assertIs(c_char_p.from_param(a), a) + + def test_cw_strings(self): + c_wchar_p.from_param("123") + + self.assertRaises(TypeError, c_wchar_p.from_param, 42) + self.assertRaises(TypeError, c_wchar_p.from_param, b"123\377") + + pa = c_wchar_p.from_param(c_wchar_p("123")) + self.assertEqual(type(pa), c_wchar_p) + + def test_c_char(self): + with self.assertRaises(TypeError) as cm: + c_char.from_param(b"abc") + self.assertEqual(str(cm.exception), + "one character bytes, bytearray or integer expected") + + def test_c_wchar(self): + with self.assertRaises(TypeError) as cm: + c_wchar.from_param("abc") + self.assertEqual(str(cm.exception), + "one character unicode string expected") + + + with self.assertRaises(TypeError) as cm: + c_wchar.from_param(123) + self.assertEqual(str(cm.exception), + "unicode string expected instead of int instance") + + def test_int_pointers(self): + LPINT = POINTER(c_int) + + x = LPINT.from_param(pointer(c_int(42))) + self.assertEqual(x.contents.value, 42) + self.assertEqual(LPINT(c_int(42)).contents.value, 42) + + self.assertEqual(LPINT.from_param(None), None) + + if c_int != c_long: + self.assertRaises(TypeError, LPINT.from_param, pointer(c_long(42))) + self.assertRaises(TypeError, LPINT.from_param, pointer(c_uint(42))) + self.assertRaises(TypeError, LPINT.from_param, pointer(c_short(42))) + + def test_byref_pointer(self): + # The from_param class method of POINTER(typ) classes accepts what is + # returned by byref(obj), it type(obj) == typ + LPINT = POINTER(c_int) + + LPINT.from_param(byref(c_int(42))) + + self.assertRaises(TypeError, LPINT.from_param, byref(c_short(22))) + if c_int != c_long: + self.assertRaises(TypeError, LPINT.from_param, byref(c_long(22))) + self.assertRaises(TypeError, LPINT.from_param, byref(c_uint(22))) + + def test_byref_pointerpointer(self): + # See above + + LPLPINT = POINTER(POINTER(c_int)) + LPLPINT.from_param(byref(pointer(c_int(42)))) + + self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_short(22)))) + if c_int != c_long: + self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_long(22)))) + self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_uint(22)))) + + def test_array_pointers(self): + INTARRAY = c_int * 3 + ia = INTARRAY() + self.assertEqual(len(ia), 3) + self.assertEqual([ia[i] for i in range(3)], [0, 0, 0]) + + # Pointers are only compatible with arrays containing items of + # the same type! + LPINT = POINTER(c_int) + LPINT.from_param((c_int*3)()) + self.assertRaises(TypeError, LPINT.from_param, c_short*3) + self.assertRaises(TypeError, LPINT.from_param, c_long*3) + self.assertRaises(TypeError, LPINT.from_param, c_uint*3) + + def test_noctypes_argtype(self): + func = CDLL(_ctypes_test.__file__)._testfunc_p_p + func.restype = c_void_p + # TypeError: has no from_param method + self.assertRaises(TypeError, setattr, func, "argtypes", (object,)) + + class Adapter: + def from_param(cls, obj): + return None + + func.argtypes = (Adapter(),) + self.assertEqual(func(None), None) + self.assertEqual(func(object()), None) + + class Adapter: + def from_param(cls, obj): + return obj + + func.argtypes = (Adapter(),) + # don't know how to convert parameter 1 + self.assertRaises(ArgumentError, func, object()) + self.assertEqual(func(c_void_p(42)), 42) + + class Adapter: + def from_param(cls, obj): + raise ValueError(obj) + + func.argtypes = (Adapter(),) + # ArgumentError: argument 1: ValueError: 99 + self.assertRaises(ArgumentError, func, 99) + + def test_abstract(self): + self.assertRaises(TypeError, Array.from_param, 42) + self.assertRaises(TypeError, Structure.from_param, 42) + self.assertRaises(TypeError, Union.from_param, 42) + self.assertRaises(TypeError, _CFuncPtr.from_param, 42) + self.assertRaises(TypeError, _Pointer.from_param, 42) + self.assertRaises(TypeError, _SimpleCData.from_param, 42) + + @test.support.cpython_only + def test_issue31311(self): + # __setstate__ should neither raise a SystemError nor crash in case + # of a bad __dict__. + + class BadStruct(Structure): + @property + def __dict__(self): + pass + with self.assertRaises(TypeError): + BadStruct().__setstate__({}, b'foo') + + class WorseStruct(Structure): + @property + def __dict__(self): + 1/0 + with self.assertRaises(ZeroDivisionError): + WorseStruct().__setstate__({}, b'foo') + + def test_parameter_repr(self): + self.assertRegex(repr(c_bool.from_param(True)), r"^<cparam '\?' at 0x[A-Fa-f0-9]+>$") + self.assertEqual(repr(c_char.from_param(97)), "<cparam 'c' ('a')>") + self.assertRegex(repr(c_wchar.from_param('a')), r"^<cparam 'u' at 0x[A-Fa-f0-9]+>$") + self.assertEqual(repr(c_byte.from_param(98)), "<cparam 'b' (98)>") + self.assertEqual(repr(c_ubyte.from_param(98)), "<cparam 'B' (98)>") + self.assertEqual(repr(c_short.from_param(511)), "<cparam 'h' (511)>") + self.assertEqual(repr(c_ushort.from_param(511)), "<cparam 'H' (511)>") + self.assertRegex(repr(c_int.from_param(20000)), r"^<cparam '[li]' \(20000\)>$") + self.assertRegex(repr(c_uint.from_param(20000)), r"^<cparam '[LI]' \(20000\)>$") + self.assertRegex(repr(c_long.from_param(20000)), r"^<cparam '[li]' \(20000\)>$") + self.assertRegex(repr(c_ulong.from_param(20000)), r"^<cparam '[LI]' \(20000\)>$") + self.assertRegex(repr(c_longlong.from_param(20000)), r"^<cparam '[liq]' \(20000\)>$") + self.assertRegex(repr(c_ulonglong.from_param(20000)), r"^<cparam '[LIQ]' \(20000\)>$") + self.assertEqual(repr(c_float.from_param(1.5)), "<cparam 'f' (1.5)>") + self.assertEqual(repr(c_double.from_param(1.5)), "<cparam 'd' (1.5)>") + if sys.float_repr_style == 'short': + self.assertEqual(repr(c_double.from_param(1e300)), "<cparam 'd' (1e+300)>") + self.assertRegex(repr(c_longdouble.from_param(1.5)), r"^<cparam ('d' \(1.5\)|'g' at 0x[A-Fa-f0-9]+)>$") + self.assertRegex(repr(c_char_p.from_param(b'hihi')), r"^<cparam 'z' \(0x[A-Fa-f0-9]+\)>$") + self.assertRegex(repr(c_wchar_p.from_param('hihi')), r"^<cparam 'Z' \(0x[A-Fa-f0-9]+\)>$") + self.assertRegex(repr(c_void_p.from_param(0x12)), r"^<cparam 'P' \(0x0*12\)>$") + + @test.support.cpython_only + def test_from_param_result_refcount(self): + # Issue #99952 + class X(Structure): + """This struct size is <= sizeof(void*).""" + _fields_ = [("a", c_void_p)] + + def __del__(self): + trace.append(4) + + @classmethod + def from_param(cls, value): + trace.append(2) + return cls() + + PyList_Append = PyDLL(_ctypes_test.__file__)._testfunc_pylist_append + PyList_Append.restype = c_int + PyList_Append.argtypes = [py_object, py_object, X] + + trace = [] + trace.append(1) + PyList_Append(trace, 3, "dummy") + trace.append(5) + + self.assertEqual(trace, [1, 2, 3, 4, 5]) + + class Y(Structure): + """This struct size is > sizeof(void*).""" + _fields_ = [("a", c_void_p), ("b", c_void_p)] + + def __del__(self): + trace.append(4) + + @classmethod + def from_param(cls, value): + trace.append(2) + return cls() + + PyList_Append = PyDLL(_ctypes_test.__file__)._testfunc_pylist_append + PyList_Append.restype = c_int + PyList_Append.argtypes = [py_object, py_object, Y] + + trace = [] + trace.append(1) + PyList_Append(trace, 3, "dummy") + trace.append(5) + + self.assertEqual(trace, [1, 2, 3, 4, 5]) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_pep3118.py b/Lib/test/test_ctypes/test_pep3118.py new file mode 100644 index 00000000000..06b2ccecade --- /dev/null +++ b/Lib/test/test_ctypes/test_pep3118.py @@ -0,0 +1,250 @@ +import re +import sys +import unittest +from ctypes import (CFUNCTYPE, POINTER, sizeof, Union, + Structure, LittleEndianStructure, BigEndianStructure, + c_char, c_byte, c_ubyte, + c_short, c_ushort, c_int, c_uint, + c_long, c_ulong, c_longlong, c_ulonglong, c_uint64, + c_bool, c_float, c_double, c_longdouble, py_object) + + +if sys.byteorder == "little": + THIS_ENDIAN = "<" + OTHER_ENDIAN = ">" +else: + THIS_ENDIAN = ">" + OTHER_ENDIAN = "<" + + +def normalize(format): + # Remove current endian specifier and white space from a format + # string + if format is None: + return "" + format = format.replace(OTHER_ENDIAN, THIS_ENDIAN) + return re.sub(r"\s", "", format) + + +class Test(unittest.TestCase): + def test_native_types(self): + for tp, fmt, shape, itemtp in native_types: + ob = tp() + v = memoryview(ob) + self.assertEqual(normalize(v.format), normalize(fmt)) + if shape: + self.assertEqual(len(v), shape[0]) + else: + self.assertRaises(TypeError, len, v) + self.assertEqual(v.itemsize, sizeof(itemtp)) + self.assertEqual(v.shape, shape) + # XXX Issue #12851: PyCData_NewGetBuffer() must provide strides + # if requested. memoryview currently reconstructs missing + # stride information, so this assert will fail. + # self.assertEqual(v.strides, ()) + + # they are always read/write + self.assertFalse(v.readonly) + + n = 1 + for dim in v.shape: + n = n * dim + self.assertEqual(n * v.itemsize, len(v.tobytes())) + + def test_endian_types(self): + for tp, fmt, shape, itemtp in endian_types: + ob = tp() + v = memoryview(ob) + self.assertEqual(v.format, fmt) + if shape: + self.assertEqual(len(v), shape[0]) + else: + self.assertRaises(TypeError, len, v) + self.assertEqual(v.itemsize, sizeof(itemtp)) + self.assertEqual(v.shape, shape) + # XXX Issue #12851 + # self.assertEqual(v.strides, ()) + + # they are always read/write + self.assertFalse(v.readonly) + + n = 1 + for dim in v.shape: + n = n * dim + self.assertEqual(n * v.itemsize, len(v.tobytes())) + + +# define some structure classes + +class Point(Structure): + _fields_ = [("x", c_long), ("y", c_long)] + +class PackedPoint(Structure): + _pack_ = 2 + _fields_ = [("x", c_long), ("y", c_long)] + +class PointMidPad(Structure): + _fields_ = [("x", c_byte), ("y", c_uint)] + +class PackedPointMidPad(Structure): + _pack_ = 2 + _fields_ = [("x", c_byte), ("y", c_uint64)] + +class PointEndPad(Structure): + _fields_ = [("x", c_uint), ("y", c_byte)] + +class PackedPointEndPad(Structure): + _pack_ = 2 + _fields_ = [("x", c_uint64), ("y", c_byte)] + +class Point2(Structure): + pass +Point2._fields_ = [("x", c_long), ("y", c_long)] + +class EmptyStruct(Structure): + _fields_ = [] + +class aUnion(Union): + _fields_ = [("a", c_int)] + +class StructWithArrays(Structure): + _fields_ = [("x", c_long * 3 * 2), ("y", Point * 4)] + +class Incomplete(Structure): + pass + +class Complete(Structure): + pass +PComplete = POINTER(Complete) +Complete._fields_ = [("a", c_long)] + + +################################################################ +# +# This table contains format strings as they look on little endian +# machines. The test replaces '<' with '>' on big endian machines. +# + +# Platform-specific type codes +s_bool = {1: '?', 2: 'H', 4: 'L', 8: 'Q'}[sizeof(c_bool)] +s_short = {2: 'h', 4: 'l', 8: 'q'}[sizeof(c_short)] +s_ushort = {2: 'H', 4: 'L', 8: 'Q'}[sizeof(c_ushort)] +s_int = {2: 'h', 4: 'i', 8: 'q'}[sizeof(c_int)] +s_uint = {2: 'H', 4: 'I', 8: 'Q'}[sizeof(c_uint)] +s_long = {4: 'l', 8: 'q'}[sizeof(c_long)] +s_ulong = {4: 'L', 8: 'Q'}[sizeof(c_ulong)] +s_longlong = "q" +s_ulonglong = "Q" +s_float = "f" +s_double = "d" +s_longdouble = "g" + +# Alias definitions in ctypes/__init__.py +if c_int is c_long: + s_int = s_long +if c_uint is c_ulong: + s_uint = s_ulong +if c_longlong is c_long: + s_longlong = s_long +if c_ulonglong is c_ulong: + s_ulonglong = s_ulong +if c_longdouble is c_double: + s_longdouble = s_double + + +native_types = [ + # type format shape calc itemsize + + ## simple types + + (c_char, "<c", (), c_char), + (c_byte, "<b", (), c_byte), + (c_ubyte, "<B", (), c_ubyte), + (c_short, "<" + s_short, (), c_short), + (c_ushort, "<" + s_ushort, (), c_ushort), + + (c_int, "<" + s_int, (), c_int), + (c_uint, "<" + s_uint, (), c_uint), + + (c_long, "<" + s_long, (), c_long), + (c_ulong, "<" + s_ulong, (), c_ulong), + + (c_longlong, "<" + s_longlong, (), c_longlong), + (c_ulonglong, "<" + s_ulonglong, (), c_ulonglong), + + (c_float, "<f", (), c_float), + (c_double, "<d", (), c_double), + + (c_longdouble, "<" + s_longdouble, (), c_longdouble), + + (c_bool, "<" + s_bool, (), c_bool), + (py_object, "<O", (), py_object), + + ## pointers + + (POINTER(c_byte), "&<b", (), POINTER(c_byte)), + (POINTER(POINTER(c_long)), "&&<" + s_long, (), POINTER(POINTER(c_long))), + + ## arrays and pointers + + (c_double * 4, "<d", (4,), c_double), + (c_double * 0, "<d", (0,), c_double), + (c_float * 4 * 3 * 2, "<f", (2,3,4), c_float), + (c_float * 4 * 0 * 2, "<f", (2,0,4), c_float), + (POINTER(c_short) * 2, "&<" + s_short, (2,), POINTER(c_short)), + (POINTER(c_short) * 2 * 3, "&<" + s_short, (3,2,), POINTER(c_short)), + (POINTER(c_short * 2), "&(2)<" + s_short, (), POINTER(c_short)), + + ## structures and unions + + (Point2, "T{<l:x:<l:y:}".replace('l', s_long), (), Point2), + (Point, "T{<l:x:<l:y:}".replace('l', s_long), (), Point), + (PackedPoint, "T{<l:x:<l:y:}".replace('l', s_long), (), PackedPoint), + (PointMidPad, "T{<b:x:3x<I:y:}".replace('I', s_uint), (), PointMidPad), + (PackedPointMidPad, "T{<b:x:x<Q:y:}", (), PackedPointMidPad), + (PointEndPad, "T{<I:x:<b:y:3x}".replace('I', s_uint), (), PointEndPad), + (PackedPointEndPad, "T{<Q:x:<b:y:x}", (), PackedPointEndPad), + (EmptyStruct, "T{}", (), EmptyStruct), + # the pep doesn't support unions + (aUnion, "B", (), aUnion), + # structure with sub-arrays + (StructWithArrays, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (), StructWithArrays), + (StructWithArrays * 3, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (3,), StructWithArrays), + + ## pointer to incomplete structure + (Incomplete, "B", (), Incomplete), + (POINTER(Incomplete), "&B", (), POINTER(Incomplete)), + + # 'Complete' is a structure that starts incomplete, but is completed after the + # pointer type to it has been created. + (Complete, "T{<l:a:}".replace('l', s_long), (), Complete), + # Unfortunately the pointer format string is not fixed... + (POINTER(Complete), "&B", (), POINTER(Complete)), + + ## other + + # function signatures are not implemented + (CFUNCTYPE(None), "X{}", (), CFUNCTYPE(None)), + + ] + + +class BEPoint(BigEndianStructure): + _fields_ = [("x", c_long), ("y", c_long)] + +class LEPoint(LittleEndianStructure): + _fields_ = [("x", c_long), ("y", c_long)] + + +# This table contains format strings as they really look, on both big +# and little endian machines. +endian_types = [ + (BEPoint, "T{>l:x:>l:y:}".replace('l', s_long), (), BEPoint), + (LEPoint * 1, "T{<l:x:<l:y:}".replace('l', s_long), (1,), LEPoint), + (POINTER(BEPoint), "&T{>l:x:>l:y:}".replace('l', s_long), (), POINTER(BEPoint)), + (POINTER(LEPoint), "&T{<l:x:<l:y:}".replace('l', s_long), (), POINTER(LEPoint)), + ] + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_pickling.py b/Lib/test/test_ctypes/test_pickling.py new file mode 100644 index 00000000000..9d433fc69de --- /dev/null +++ b/Lib/test/test_ctypes/test_pickling.py @@ -0,0 +1,91 @@ +import pickle +import unittest +from ctypes import (CDLL, Structure, CFUNCTYPE, pointer, + c_void_p, c_char_p, c_wchar_p, + c_char, c_wchar, c_int, c_double) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +dll = CDLL(_ctypes_test.__file__) + + +class X(Structure): + _fields_ = [("a", c_int), ("b", c_double)] + init_called = 0 + def __init__(self, *args, **kw): + X.init_called += 1 + self.x = 42 + + +class Y(X): + _fields_ = [("str", c_char_p)] + + +class PickleTest: + def dumps(self, item): + return pickle.dumps(item, self.proto) + + def loads(self, item): + return pickle.loads(item) + + def test_simple(self): + for src in [ + c_int(42), + c_double(3.14), + ]: + dst = self.loads(self.dumps(src)) + self.assertEqual(src.__dict__, dst.__dict__) + self.assertEqual(memoryview(src).tobytes(), + memoryview(dst).tobytes()) + + def test_struct(self): + X.init_called = 0 + + x = X() + x.a = 42 + self.assertEqual(X.init_called, 1) + + y = self.loads(self.dumps(x)) + + # loads must NOT call __init__ + self.assertEqual(X.init_called, 1) + + # ctypes instances are identical when the instance __dict__ + # and the memory buffer are identical + self.assertEqual(y.__dict__, x.__dict__) + self.assertEqual(memoryview(y).tobytes(), + memoryview(x).tobytes()) + + def test_unpickable(self): + # ctypes objects that are pointers or contain pointers are + # unpickable. + self.assertRaises(ValueError, lambda: self.dumps(Y())) + + prototype = CFUNCTYPE(c_int) + + for item in [ + c_char_p(), + c_wchar_p(), + c_void_p(), + pointer(c_int(42)), + dll._testfunc_p_p, + prototype(lambda: 42), + ]: + self.assertRaises(ValueError, lambda: self.dumps(item)) + + def test_wchar(self): + self.dumps(c_char(b"x")) + # Issue 5049 + self.dumps(c_wchar("x")) + + +for proto in range(pickle.HIGHEST_PROTOCOL + 1): + name = 'PickleTest_%s' % proto + globals()[name] = type(name, + (PickleTest, unittest.TestCase), + {'proto': proto}) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_pointers.py b/Lib/test/test_ctypes/test_pointers.py new file mode 100644 index 00000000000..ed4541335df --- /dev/null +++ b/Lib/test/test_ctypes/test_pointers.py @@ -0,0 +1,240 @@ +import array +import ctypes +import sys +import unittest +from ctypes import (CDLL, CFUNCTYPE, Structure, + POINTER, pointer, _Pointer, _pointer_type_cache, + byref, sizeof, + c_void_p, c_char_p, + c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, + c_long, c_ulong, c_longlong, c_ulonglong, + c_float, c_double) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") +from ._support import (_CData, PyCPointerType, Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_IMMUTABLETYPE) + + +ctype_types = [c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, + c_long, c_ulong, c_longlong, c_ulonglong, c_double, c_float] +python_types = [int, int, int, int, int, int, + int, int, int, int, float, float] + + +class PointersTestCase(unittest.TestCase): + def test_inheritance_hierarchy(self): + self.assertEqual(_Pointer.mro(), [_Pointer, _CData, object]) + + self.assertEqual(PyCPointerType.__name__, "PyCPointerType") + self.assertEqual(type(PyCPointerType), type) + + def test_type_flags(self): + for cls in _Pointer, PyCPointerType: + with self.subTest(cls=cls): + self.assertTrue(_Pointer.__flags__ & Py_TPFLAGS_IMMUTABLETYPE) + self.assertFalse(_Pointer.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION) + + def test_metaclass_details(self): + # Cannot call the metaclass __init__ more than once + with self.assertRaisesRegex(SystemError, "already initialized"): + PyCPointerType.__init__(POINTER(c_byte), 'ptr', (), {}) + + def test_pointer_crash(self): + + class A(POINTER(c_ulong)): + pass + + POINTER(c_ulong)(c_ulong(22)) + # Pointer can't set contents: has no _type_ + self.assertRaises(TypeError, A, c_ulong(33)) + + def test_pass_pointers(self): + dll = CDLL(_ctypes_test.__file__) + func = dll._testfunc_p_p + if sizeof(c_longlong) == sizeof(c_void_p): + func.restype = c_longlong + else: + func.restype = c_long + + i = c_int(12345678) + address = func(byref(i)) + self.assertEqual(c_int.from_address(address).value, 12345678) + + func.restype = POINTER(c_int) + res = func(pointer(i)) + self.assertEqual(res.contents.value, 12345678) + self.assertEqual(res[0], 12345678) + + def test_change_pointers(self): + dll = CDLL(_ctypes_test.__file__) + func = dll._testfunc_p_p + + i = c_int(87654) + func.restype = POINTER(c_int) + func.argtypes = (POINTER(c_int),) + + res = func(pointer(i)) + self.assertEqual(res[0], 87654) + self.assertEqual(res.contents.value, 87654) + + # C code: *res = 54345 + res[0] = 54345 + self.assertEqual(i.value, 54345) + + # C code: + # int x = 12321; + # res = &x + x = c_int(12321) + res.contents = x + self.assertEqual(i.value, 54345) + + x.value = -99 + self.assertEqual(res.contents.value, -99) + + def test_callbacks_with_pointers(self): + # a function type receiving a pointer + PROTOTYPE = CFUNCTYPE(c_int, POINTER(c_int)) + + self.result = [] + + def func(arg): + for i in range(10): + self.result.append(arg[i]) + return 0 + callback = PROTOTYPE(func) + + dll = CDLL(_ctypes_test.__file__) + # This function expects a function pointer, + # and calls this with an integer pointer as parameter. + # The int pointer points to a table containing the numbers 1..10 + doit = dll._testfunc_callback_with_pointer + + doit(callback) + doit(callback) + + def test_basics(self): + for ct, pt in zip(ctype_types, python_types): + i = ct(42) + p = pointer(i) + self.assertIs(type(p.contents), ct) + # p.contents is the same as p[0] + + with self.assertRaises(TypeError): + del p[0] + + def test_from_address(self): + a = array.array('i', [100, 200, 300, 400, 500]) + addr = a.buffer_info()[0] + p = POINTER(POINTER(c_int)) + + def test_other(self): + class Table(Structure): + _fields_ = [("a", c_int), + ("b", c_int), + ("c", c_int)] + + pt = pointer(Table(1, 2, 3)) + + self.assertEqual(pt.contents.a, 1) + self.assertEqual(pt.contents.b, 2) + self.assertEqual(pt.contents.c, 3) + + pt.contents.c = 33 + + del _pointer_type_cache[Table] + + def test_basic(self): + p = pointer(c_int(42)) + # Although a pointer can be indexed, it has no length + self.assertRaises(TypeError, len, p) + self.assertEqual(p[0], 42) + self.assertEqual(p[0:1], [42]) + self.assertEqual(p.contents.value, 42) + + def test_charpp(self): + """Test that a character pointer-to-pointer is correctly passed""" + dll = CDLL(_ctypes_test.__file__) + func = dll._testfunc_c_p_p + func.restype = c_char_p + argv = (c_char_p * 2)() + argc = c_int( 2 ) + argv[0] = b'hello' + argv[1] = b'world' + result = func( byref(argc), argv ) + self.assertEqual(result, b'world') + + def test_bug_1467852(self): + # http://sourceforge.net/tracker/?func=detail&atid=532154&aid=1467852&group_id=71702 + x = c_int(5) + dummy = [] + for i in range(32000): + dummy.append(c_int(i)) + y = c_int(6) + p = pointer(x) + pp = pointer(p) + q = pointer(y) + pp[0] = q # <== + self.assertEqual(p[0], 6) + def test_c_void_p(self): + # http://sourceforge.net/tracker/?func=detail&aid=1518190&group_id=5470&atid=105470 + if sizeof(c_void_p) == 4: + self.assertEqual(c_void_p(0xFFFFFFFF).value, + c_void_p(-1).value) + self.assertEqual(c_void_p(0xFFFFFFFFFFFFFFFF).value, + c_void_p(-1).value) + elif sizeof(c_void_p) == 8: + self.assertEqual(c_void_p(0xFFFFFFFF).value, + 0xFFFFFFFF) + self.assertEqual(c_void_p(0xFFFFFFFFFFFFFFFF).value, + c_void_p(-1).value) + self.assertEqual(c_void_p(0xFFFFFFFFFFFFFFFFFFFFFFFF).value, + c_void_p(-1).value) + + self.assertRaises(TypeError, c_void_p, 3.14) # make sure floats are NOT accepted + self.assertRaises(TypeError, c_void_p, object()) # nor other objects + + def test_pointers_bool(self): + # NULL pointers have a boolean False value, non-NULL pointers True. + self.assertEqual(bool(POINTER(c_int)()), False) + self.assertEqual(bool(pointer(c_int())), True) + + self.assertEqual(bool(CFUNCTYPE(None)(0)), False) + self.assertEqual(bool(CFUNCTYPE(None)(42)), True) + + # COM methods are boolean True: + if sys.platform == "win32": + mth = ctypes.WINFUNCTYPE(None)(42, "name", (), None) + self.assertEqual(bool(mth), True) + + def test_pointer_type_name(self): + LargeNamedType = type('T' * 2 ** 25, (Structure,), {}) + self.assertTrue(POINTER(LargeNamedType)) + + # to not leak references, we must clean _pointer_type_cache + del _pointer_type_cache[LargeNamedType] + + def test_pointer_type_str_name(self): + large_string = 'T' * 2 ** 25 + P = POINTER(large_string) + self.assertTrue(P) + + # to not leak references, we must clean _pointer_type_cache + del _pointer_type_cache[id(P)] + + def test_abstract(self): + self.assertRaises(TypeError, _Pointer.set_type, 42) + + def test_repeated_set_type(self): + # Regression test for gh-133290 + class C(Structure): + _fields_ = [('a', c_int)] + ptr = POINTER(C) + # Read _type_ several times to warm up cache + for i in range(5): + self.assertIs(ptr._type_, C) + ptr.set_type(c_int) + self.assertIs(ptr._type_, c_int) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_prototypes.py b/Lib/test/test_ctypes/test_prototypes.py new file mode 100644 index 00000000000..d976e8da0e2 --- /dev/null +++ b/Lib/test/test_ctypes/test_prototypes.py @@ -0,0 +1,252 @@ +# IMPORTANT INFO: +# +# Consider this call: +# func.restype = c_char_p +# func(c_char_p("123")) +# It returns +# "123" +# +# WHY IS THIS SO? +# +# argument tuple (c_char_p("123"), ) is destroyed after the function +# func is called, but NOT before the result is actually built. +# +# If the arglist would be destroyed BEFORE the result has been built, +# the c_char_p("123") object would already have a zero refcount, +# and the pointer passed to (and returned by) the function would +# probably point to deallocated space. +# +# In this case, there would have to be an additional reference to the argument... + +import unittest +from ctypes import (CDLL, CFUNCTYPE, POINTER, ArgumentError, + pointer, byref, sizeof, addressof, create_string_buffer, + c_void_p, c_char_p, c_wchar_p, c_char, c_wchar, + c_short, c_int, c_long, c_longlong, c_double) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +testdll = CDLL(_ctypes_test.__file__) + + +# Return machine address `a` as a (possibly long) non-negative integer. +# Starting with Python 2.5, id(anything) is always non-negative, and +# the ctypes addressof() inherits that via PyLong_FromVoidPtr(). +def positive_address(a): + if a >= 0: + return a + # View the bits in `a` as unsigned instead. + import struct + num_bits = struct.calcsize("P") * 8 # num bits in native machine address + a += 1 << num_bits + assert a >= 0 + return a + + +def c_wbuffer(init): + n = len(init) + 1 + return (c_wchar * n)(*init) + + +class CharPointersTestCase(unittest.TestCase): + def setUp(self): + func = testdll._testfunc_p_p + func.restype = c_long + func.argtypes = None + + def test_paramflags(self): + # function returns c_void_p result, + # and has a required parameter named 'input' + prototype = CFUNCTYPE(c_void_p, c_void_p) + func = prototype(("_testfunc_p_p", testdll), + ((1, "input"),)) + + try: + func() + except TypeError as details: + self.assertEqual(str(details), "required argument 'input' missing") + else: + self.fail("TypeError not raised") + + self.assertEqual(func(None), None) + self.assertEqual(func(input=None), None) + + def test_invalid_paramflags(self): + proto = CFUNCTYPE(c_int, c_char_p) + with self.assertRaises(ValueError): + func = proto(("myprintf", testdll), ((1, "fmt"), (1, "arg1"))) + + def test_invalid_setattr_argtypes(self): + proto = CFUNCTYPE(c_int, c_char_p) + func = proto(("myprintf", testdll), ((1, "fmt"),)) + + with self.assertRaisesRegex(TypeError, "_argtypes_ must be a sequence of types"): + func.argtypes = 123 + self.assertEqual(func.argtypes, (c_char_p,)) + + with self.assertRaisesRegex(ValueError, "paramflags must have the same length as argtypes"): + func.argtypes = (c_char_p, c_int) + self.assertEqual(func.argtypes, (c_char_p,)) + + def test_paramflags_outarg(self): + proto = CFUNCTYPE(c_int, c_char_p, c_int) + with self.assertRaisesRegex(TypeError, "must be a pointer type"): + func = proto(("myprintf", testdll), ((1, "fmt"), (2, "out"))) + + proto = CFUNCTYPE(c_int, c_char_p, c_void_p) + func = proto(("myprintf", testdll), ((1, "fmt"), (2, "out"))) + with self.assertRaisesRegex(TypeError, "must be a pointer type"): + func.argtypes = (c_char_p, c_int) + + def test_int_pointer_arg(self): + func = testdll._testfunc_p_p + if sizeof(c_longlong) == sizeof(c_void_p): + func.restype = c_longlong + else: + func.restype = c_long + self.assertEqual(0, func(0)) + + ci = c_int(0) + + func.argtypes = POINTER(c_int), + self.assertEqual(positive_address(addressof(ci)), + positive_address(func(byref(ci)))) + + func.argtypes = c_char_p, + self.assertRaises(ArgumentError, func, byref(ci)) + + func.argtypes = POINTER(c_short), + self.assertRaises(ArgumentError, func, byref(ci)) + + func.argtypes = POINTER(c_double), + self.assertRaises(ArgumentError, func, byref(ci)) + + def test_POINTER_c_char_arg(self): + func = testdll._testfunc_p_p + func.restype = c_char_p + func.argtypes = POINTER(c_char), + + self.assertEqual(None, func(None)) + self.assertEqual(b"123", func(b"123")) + self.assertEqual(None, func(c_char_p(None))) + self.assertEqual(b"123", func(c_char_p(b"123"))) + + self.assertEqual(b"123", func(create_string_buffer(b"123"))) + ca = c_char(b"a") + self.assertEqual(ord(b"a"), func(pointer(ca))[0]) + self.assertEqual(ord(b"a"), func(byref(ca))[0]) + + def test_c_char_p_arg(self): + func = testdll._testfunc_p_p + func.restype = c_char_p + func.argtypes = c_char_p, + + self.assertEqual(None, func(None)) + self.assertEqual(b"123", func(b"123")) + self.assertEqual(None, func(c_char_p(None))) + self.assertEqual(b"123", func(c_char_p(b"123"))) + + self.assertEqual(b"123", func(create_string_buffer(b"123"))) + ca = c_char(b"a") + self.assertEqual(ord(b"a"), func(pointer(ca))[0]) + self.assertEqual(ord(b"a"), func(byref(ca))[0]) + + def test_c_void_p_arg(self): + func = testdll._testfunc_p_p + func.restype = c_char_p + func.argtypes = c_void_p, + + self.assertEqual(None, func(None)) + self.assertEqual(b"123", func(b"123")) + self.assertEqual(b"123", func(c_char_p(b"123"))) + self.assertEqual(None, func(c_char_p(None))) + + self.assertEqual(b"123", func(create_string_buffer(b"123"))) + ca = c_char(b"a") + self.assertEqual(ord(b"a"), func(pointer(ca))[0]) + self.assertEqual(ord(b"a"), func(byref(ca))[0]) + + func(byref(c_int())) + func(pointer(c_int())) + func((c_int * 3)()) + + def test_c_void_p_arg_with_c_wchar_p(self): + func = testdll._testfunc_p_p + func.restype = c_wchar_p + func.argtypes = c_void_p, + + self.assertEqual(None, func(c_wchar_p(None))) + self.assertEqual("123", func(c_wchar_p("123"))) + + def test_instance(self): + func = testdll._testfunc_p_p + func.restype = c_void_p + + class X: + _as_parameter_ = None + + func.argtypes = c_void_p, + self.assertEqual(None, func(X())) + + func.argtypes = None + self.assertEqual(None, func(X())) + + +class WCharPointersTestCase(unittest.TestCase): + def setUp(self): + func = testdll._testfunc_p_p + func.restype = c_int + func.argtypes = None + + + def test_POINTER_c_wchar_arg(self): + func = testdll._testfunc_p_p + func.restype = c_wchar_p + func.argtypes = POINTER(c_wchar), + + self.assertEqual(None, func(None)) + self.assertEqual("123", func("123")) + self.assertEqual(None, func(c_wchar_p(None))) + self.assertEqual("123", func(c_wchar_p("123"))) + + self.assertEqual("123", func(c_wbuffer("123"))) + ca = c_wchar("a") + self.assertEqual("a", func(pointer(ca))[0]) + self.assertEqual("a", func(byref(ca))[0]) + + def test_c_wchar_p_arg(self): + func = testdll._testfunc_p_p + func.restype = c_wchar_p + func.argtypes = c_wchar_p, + + c_wchar_p.from_param("123") + + self.assertEqual(None, func(None)) + self.assertEqual("123", func("123")) + self.assertEqual(None, func(c_wchar_p(None))) + self.assertEqual("123", func(c_wchar_p("123"))) + + # XXX Currently, these raise TypeErrors, although they shouldn't: + self.assertEqual("123", func(c_wbuffer("123"))) + ca = c_wchar("a") + self.assertEqual("a", func(pointer(ca))[0]) + self.assertEqual("a", func(byref(ca))[0]) + + +class ArrayTest(unittest.TestCase): + def test(self): + func = testdll._testfunc_ai8 + func.restype = POINTER(c_int) + func.argtypes = c_int * 8, + + func((c_int * 8)(1, 2, 3, 4, 5, 6, 7, 8)) + + # This did crash before: + + def func(): pass + CFUNCTYPE(None, c_int * 3)(func) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_python_api.py b/Lib/test/test_ctypes/test_python_api.py new file mode 100644 index 00000000000..1072a109833 --- /dev/null +++ b/Lib/test/test_ctypes/test_python_api.py @@ -0,0 +1,81 @@ +import _ctypes +import sys +import unittest +from test import support +from ctypes import (pythonapi, POINTER, create_string_buffer, sizeof, + py_object, c_char_p, c_char, c_long, c_size_t) + + +class PythonAPITestCase(unittest.TestCase): + def test_PyBytes_FromStringAndSize(self): + PyBytes_FromStringAndSize = pythonapi.PyBytes_FromStringAndSize + + PyBytes_FromStringAndSize.restype = py_object + PyBytes_FromStringAndSize.argtypes = c_char_p, c_size_t + + self.assertEqual(PyBytes_FromStringAndSize(b"abcdefghi", 3), b"abc") + + @support.refcount_test + def test_PyString_FromString(self): + pythonapi.PyBytes_FromString.restype = py_object + pythonapi.PyBytes_FromString.argtypes = (c_char_p,) + + s = b"abc" + refcnt = sys.getrefcount(s) + pyob = pythonapi.PyBytes_FromString(s) + self.assertEqual(sys.getrefcount(s), refcnt) + self.assertEqual(s, pyob) + del pyob + self.assertEqual(sys.getrefcount(s), refcnt) + + @support.refcount_test + def test_PyLong_Long(self): + ref42 = sys.getrefcount(42) + pythonapi.PyLong_FromLong.restype = py_object + self.assertEqual(pythonapi.PyLong_FromLong(42), 42) + + self.assertEqual(sys.getrefcount(42), ref42) + + pythonapi.PyLong_AsLong.argtypes = (py_object,) + pythonapi.PyLong_AsLong.restype = c_long + + res = pythonapi.PyLong_AsLong(42) + # Small int refcnts don't change + self.assertEqual(sys.getrefcount(res), ref42) + del res + self.assertEqual(sys.getrefcount(42), ref42) + + @support.refcount_test + def test_PyObj_FromPtr(self): + s = object() + ref = sys.getrefcount(s) + # id(python-object) is the address + pyobj = _ctypes.PyObj_FromPtr(id(s)) + self.assertIs(s, pyobj) + + self.assertEqual(sys.getrefcount(s), ref + 1) + del pyobj + self.assertEqual(sys.getrefcount(s), ref) + + def test_PyOS_snprintf(self): + PyOS_snprintf = pythonapi.PyOS_snprintf + PyOS_snprintf.argtypes = POINTER(c_char), c_size_t, c_char_p + + buf = create_string_buffer(256) + PyOS_snprintf(buf, sizeof(buf), b"Hello from %s", b"ctypes") + self.assertEqual(buf.value, b"Hello from ctypes") + + PyOS_snprintf(buf, sizeof(buf), b"Hello from %s (%d, %d, %d)", b"ctypes", 1, 2, 3) + self.assertEqual(buf.value, b"Hello from ctypes (1, 2, 3)") + + # not enough arguments + self.assertRaises(TypeError, PyOS_snprintf, buf) + + def test_pyobject_repr(self): + self.assertEqual(repr(py_object()), "py_object(<NULL>)") + self.assertEqual(repr(py_object(42)), "py_object(42)") + self.assertEqual(repr(py_object(object)), "py_object(%r)" % object) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_random_things.py b/Lib/test/test_ctypes/test_random_things.py new file mode 100644 index 00000000000..630f6ed9489 --- /dev/null +++ b/Lib/test/test_ctypes/test_random_things.py @@ -0,0 +1,81 @@ +import _ctypes +import contextlib +import ctypes +import sys +import unittest +from test import support +from ctypes import CFUNCTYPE, c_void_p, c_char_p, c_int, c_double + + +def callback_func(arg): + 42 / arg + raise ValueError(arg) + + +@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') +class call_function_TestCase(unittest.TestCase): + # _ctypes.call_function is deprecated and private, but used by + # Gary Bishp's readline module. If we have it, we must test it as well. + + def test(self): + kernel32 = ctypes.windll.kernel32 + kernel32.LoadLibraryA.restype = c_void_p + kernel32.GetProcAddress.argtypes = c_void_p, c_char_p + kernel32.GetProcAddress.restype = c_void_p + + hdll = kernel32.LoadLibraryA(b"kernel32") + funcaddr = kernel32.GetProcAddress(hdll, b"GetModuleHandleA") + + self.assertEqual(_ctypes.call_function(funcaddr, (None,)), + kernel32.GetModuleHandleA(None)) + + +class CallbackTracbackTestCase(unittest.TestCase): + # When an exception is raised in a ctypes callback function, the C + # code prints a traceback. + # + # This test makes sure the exception types *and* the exception + # value is printed correctly. + # + # Changed in 0.9.3: No longer is '(in callback)' prepended to the + # error message - instead an additional frame for the C code is + # created, then a full traceback printed. When SystemExit is + # raised in a callback function, the interpreter exits. + + @contextlib.contextmanager + def expect_unraisable(self, exc_type, exc_msg=None): + with support.catch_unraisable_exception() as cm: + yield + + self.assertIsInstance(cm.unraisable.exc_value, exc_type) + if exc_msg is not None: + self.assertEqual(str(cm.unraisable.exc_value), exc_msg) + self.assertEqual(cm.unraisable.err_msg, + f"Exception ignored on calling ctypes " + f"callback function {callback_func!r}") + self.assertIsNone(cm.unraisable.object) + + def test_ValueError(self): + cb = CFUNCTYPE(c_int, c_int)(callback_func) + with self.expect_unraisable(ValueError, '42'): + cb(42) + + def test_IntegerDivisionError(self): + cb = CFUNCTYPE(c_int, c_int)(callback_func) + with self.expect_unraisable(ZeroDivisionError): + cb(0) + + def test_FloatDivisionError(self): + cb = CFUNCTYPE(c_int, c_double)(callback_func) + with self.expect_unraisable(ZeroDivisionError): + cb(0.0) + + def test_TypeErrorDivisionError(self): + cb = CFUNCTYPE(c_int, c_char_p)(callback_func) + err_msg = "unsupported operand type(s) for /: 'int' and 'bytes'" + with self.expect_unraisable(TypeError, err_msg): + cb(b"spam") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_refcounts.py b/Lib/test/test_ctypes/test_refcounts.py new file mode 100644 index 00000000000..9e87cfc661e --- /dev/null +++ b/Lib/test/test_ctypes/test_refcounts.py @@ -0,0 +1,143 @@ +import ctypes +import gc +import sys +import unittest +from test import support +from test.support import import_helper +from test.support import script_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +MyCallback = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int) +OtherCallback = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_ulonglong) + +dll = ctypes.CDLL(_ctypes_test.__file__) + + +class RefcountTestCase(unittest.TestCase): + @support.refcount_test + def test_1(self): + f = dll._testfunc_callback_i_if + f.restype = ctypes.c_int + f.argtypes = [ctypes.c_int, MyCallback] + + def callback(value): + return value + + self.assertEqual(sys.getrefcount(callback), 2) + cb = MyCallback(callback) + + self.assertGreater(sys.getrefcount(callback), 2) + result = f(-10, cb) + self.assertEqual(result, -18) + cb = None + + gc.collect() + + self.assertEqual(sys.getrefcount(callback), 2) + + @support.refcount_test + def test_refcount(self): + def func(*args): + pass + # this is the standard refcount for func + self.assertEqual(sys.getrefcount(func), 2) + + # the CFuncPtr instance holds at least one refcount on func: + f = OtherCallback(func) + self.assertGreater(sys.getrefcount(func), 2) + + # and may release it again + del f + self.assertGreaterEqual(sys.getrefcount(func), 2) + + # but now it must be gone + gc.collect() + self.assertEqual(sys.getrefcount(func), 2) + + class X(ctypes.Structure): + _fields_ = [("a", OtherCallback)] + x = X() + x.a = OtherCallback(func) + + # the CFuncPtr instance holds at least one refcount on func: + self.assertGreater(sys.getrefcount(func), 2) + + # and may release it again + del x + self.assertGreaterEqual(sys.getrefcount(func), 2) + + # and now it must be gone again + gc.collect() + self.assertEqual(sys.getrefcount(func), 2) + + f = OtherCallback(func) + + # the CFuncPtr instance holds at least one refcount on func: + self.assertGreater(sys.getrefcount(func), 2) + + # create a cycle + f.cycle = f + + del f + gc.collect() + self.assertEqual(sys.getrefcount(func), 2) + + +class AnotherLeak(unittest.TestCase): + def test_callback(self): + proto = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int) + def func(a, b): + return a * b * 2 + f = proto(func) + + a = sys.getrefcount(ctypes.c_int) + f(1, 2) + self.assertEqual(sys.getrefcount(ctypes.c_int), a) + + @support.refcount_test + def test_callback_py_object_none_return(self): + # bpo-36880: test that returning None from a py_object callback + # does not decrement the refcount of None. + + for FUNCTYPE in (ctypes.CFUNCTYPE, ctypes.PYFUNCTYPE): + with self.subTest(FUNCTYPE=FUNCTYPE): + @FUNCTYPE(ctypes.py_object) + def func(): + return None + + # Check that calling func does not affect None's refcount. + for _ in range(10000): + func() + + +class ModuleIsolationTest(unittest.TestCase): + def test_finalize(self): + # check if gc_decref() succeeds + script = ( + "import ctypes;" + "import sys;" + "del sys.modules['_ctypes'];" + "import _ctypes;" + "exit()" + ) + script_helper.assert_python_ok("-c", script) + + +class PyObjectRestypeTest(unittest.TestCase): + def test_restype_py_object_with_null_return(self): + # Test that a function which returns a NULL PyObject * + # without setting an exception does not crash. + PyErr_Occurred = ctypes.pythonapi.PyErr_Occurred + PyErr_Occurred.argtypes = [] + PyErr_Occurred.restype = ctypes.py_object + + # At this point, there's no exception set, so PyErr_Occurred + # returns NULL. Given the restype is py_object, the + # ctypes machinery will raise a custom error. + with self.assertRaisesRegex(ValueError, "PyObject is NULL"): + PyErr_Occurred() + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_repr.py b/Lib/test/test_ctypes/test_repr.py new file mode 100644 index 00000000000..e7587984a92 --- /dev/null +++ b/Lib/test/test_ctypes/test_repr.py @@ -0,0 +1,34 @@ +import unittest +from ctypes import (c_byte, c_short, c_int, c_long, c_longlong, + c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong, + c_float, c_double, c_longdouble, c_bool, c_char) + + +subclasses = [] +for base in [c_byte, c_short, c_int, c_long, c_longlong, + c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong, + c_float, c_double, c_longdouble, c_bool]: + class X(base): + pass + subclasses.append(X) + + +class X(c_char): + pass + + +# This test checks if the __repr__ is correct for subclasses of simple types +class ReprTest(unittest.TestCase): + def test_numbers(self): + for typ in subclasses: + base = typ.__bases__[0] + self.assertTrue(repr(base(42)).startswith(base.__name__)) + self.assertEqual("<X object at", repr(typ(42))[:12]) + + def test_char(self): + self.assertEqual("c_char(b'x')", repr(c_char(b'x'))) + self.assertEqual("<X object at", repr(X(b'x'))[:12]) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_returnfuncptrs.py b/Lib/test/test_ctypes/test_returnfuncptrs.py new file mode 100644 index 00000000000..337801b226a --- /dev/null +++ b/Lib/test/test_ctypes/test_returnfuncptrs.py @@ -0,0 +1,67 @@ +import unittest +from ctypes import CDLL, CFUNCTYPE, ArgumentError, c_char_p, c_void_p, c_char +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class ReturnFuncPtrTestCase(unittest.TestCase): + def test_with_prototype(self): + # The _ctypes_test shared lib/dll exports quite some functions for testing. + # The get_strchr function returns a *pointer* to the C strchr function. + dll = CDLL(_ctypes_test.__file__) + get_strchr = dll.get_strchr + get_strchr.restype = CFUNCTYPE(c_char_p, c_char_p, c_char) + strchr = get_strchr() + self.assertEqual(strchr(b"abcdef", b"b"), b"bcdef") + self.assertEqual(strchr(b"abcdef", b"x"), None) + self.assertEqual(strchr(b"abcdef", 98), b"bcdef") + self.assertEqual(strchr(b"abcdef", 107), None) + self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) + self.assertRaises(TypeError, strchr, b"abcdef") + + def test_without_prototype(self): + dll = CDLL(_ctypes_test.__file__) + get_strchr = dll.get_strchr + # the default 'c_int' would not work on systems where sizeof(int) != sizeof(void *) + get_strchr.restype = c_void_p + addr = get_strchr() + # _CFuncPtr instances are now callable with an integer argument + # which denotes a function address: + strchr = CFUNCTYPE(c_char_p, c_char_p, c_char)(addr) + self.assertTrue(strchr(b"abcdef", b"b"), "bcdef") + self.assertEqual(strchr(b"abcdef", b"x"), None) + self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) + self.assertRaises(TypeError, strchr, b"abcdef") + + def test_from_dll(self): + dll = CDLL(_ctypes_test.__file__) + # _CFuncPtr instances are now callable with a tuple argument + # which denotes a function name and a dll: + strchr = CFUNCTYPE(c_char_p, c_char_p, c_char)(("my_strchr", dll)) + self.assertTrue(strchr(b"abcdef", b"b"), "bcdef") + self.assertEqual(strchr(b"abcdef", b"x"), None) + self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) + self.assertRaises(TypeError, strchr, b"abcdef") + + # Issue 6083: Reference counting bug + def test_from_dll_refcount(self): + class BadSequence(tuple): + def __getitem__(self, key): + if key == 0: + return "my_strchr" + if key == 1: + return CDLL(_ctypes_test.__file__) + raise IndexError + + # _CFuncPtr instances are now callable with a tuple argument + # which denotes a function name and a dll: + strchr = CFUNCTYPE(c_char_p, c_char_p, c_char)( + BadSequence(("my_strchr", CDLL(_ctypes_test.__file__)))) + self.assertTrue(strchr(b"abcdef", b"b"), "bcdef") + self.assertEqual(strchr(b"abcdef", b"x"), None) + self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) + self.assertRaises(TypeError, strchr, b"abcdef") + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_simplesubclasses.py b/Lib/test/test_ctypes/test_simplesubclasses.py new file mode 100644 index 00000000000..4e4bef3690f --- /dev/null +++ b/Lib/test/test_ctypes/test_simplesubclasses.py @@ -0,0 +1,96 @@ +import unittest +from ctypes import Structure, CFUNCTYPE, c_int, _SimpleCData +from ._support import (_CData, PyCSimpleType, Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_IMMUTABLETYPE) + + +class MyInt(c_int): + def __eq__(self, other): + if type(other) != MyInt: + return NotImplementedError + return self.value == other.value + + +class Test(unittest.TestCase): + def test_inheritance_hierarchy(self): + self.assertEqual(_SimpleCData.mro(), [_SimpleCData, _CData, object]) + + self.assertEqual(PyCSimpleType.__name__, "PyCSimpleType") + self.assertEqual(type(PyCSimpleType), type) + + self.assertEqual(c_int.mro(), [c_int, _SimpleCData, _CData, object]) + + def test_type_flags(self): + for cls in _SimpleCData, PyCSimpleType: + with self.subTest(cls=cls): + self.assertTrue(_SimpleCData.__flags__ & Py_TPFLAGS_IMMUTABLETYPE) + self.assertFalse(_SimpleCData.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION) + + def test_metaclass_details(self): + # Abstract classes (whose metaclass __init__ was not called) can't be + # instantiated directly + NewT = PyCSimpleType.__new__(PyCSimpleType, 'NewT', (_SimpleCData,), {}) + for cls in _SimpleCData, NewT: + with self.subTest(cls=cls): + with self.assertRaisesRegex(TypeError, "abstract class"): + obj = cls() + + # Cannot call the metaclass __init__ more than once + class T(_SimpleCData): + _type_ = "i" + with self.assertRaisesRegex(SystemError, "already initialized"): + PyCSimpleType.__init__(T, 'ptr', (), {}) + + def test_swapped_type_creation(self): + cls = PyCSimpleType.__new__(PyCSimpleType, '', (), {'_type_': 'i'}) + with self.assertRaises(TypeError): + PyCSimpleType.__init__(cls) + PyCSimpleType.__init__(cls, '', (), {'_type_': 'i'}) + self.assertEqual(cls.__ctype_le__.__dict__.get('_type_'), 'i') + self.assertEqual(cls.__ctype_be__.__dict__.get('_type_'), 'i') + + def test_compare(self): + self.assertEqual(MyInt(3), MyInt(3)) + self.assertNotEqual(MyInt(42), MyInt(43)) + + def test_ignore_retval(self): + # Test if the return value of a callback is ignored + # if restype is None + proto = CFUNCTYPE(None) + def func(): + return (1, "abc", None) + + cb = proto(func) + self.assertEqual(None, cb()) + + + def test_int_callback(self): + args = [] + def func(arg): + args.append(arg) + return arg + + cb = CFUNCTYPE(None, MyInt)(func) + + self.assertEqual(None, cb(42)) + self.assertEqual(type(args[-1]), MyInt) + + cb = CFUNCTYPE(c_int, c_int)(func) + + self.assertEqual(42, cb(42)) + self.assertEqual(type(args[-1]), int) + + def test_int_struct(self): + class X(Structure): + _fields_ = [("x", MyInt)] + + self.assertEqual(X().x, MyInt()) + + s = X() + s.x = MyInt(42) + + self.assertEqual(s.x, MyInt(42)) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_sizes.py b/Lib/test/test_ctypes/test_sizes.py new file mode 100644 index 00000000000..e981ccfad5e --- /dev/null +++ b/Lib/test/test_ctypes/test_sizes.py @@ -0,0 +1,38 @@ +# Test specifically-sized containers. + +from ctypes import (sizeof, + c_int8, c_uint8, c_int16, c_uint16, + c_int32, c_uint32, c_int64, c_uint64, + c_void_p, c_size_t, c_ssize_t, c_time_t, SIZEOF_TIME_T) +import unittest + + +class SizesTestCase(unittest.TestCase): + def test_8(self): + self.assertEqual(1, sizeof(c_int8)) + self.assertEqual(1, sizeof(c_uint8)) + + def test_16(self): + self.assertEqual(2, sizeof(c_int16)) + self.assertEqual(2, sizeof(c_uint16)) + + def test_32(self): + self.assertEqual(4, sizeof(c_int32)) + self.assertEqual(4, sizeof(c_uint32)) + + def test_64(self): + self.assertEqual(8, sizeof(c_int64)) + self.assertEqual(8, sizeof(c_uint64)) + + def test_size_t(self): + self.assertEqual(sizeof(c_void_p), sizeof(c_size_t)) + + def test_ssize_t(self): + self.assertEqual(sizeof(c_void_p), sizeof(c_ssize_t)) + + def test_time_t(self): + self.assertEqual(sizeof(c_time_t), SIZEOF_TIME_T) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_slicing.py b/Lib/test/test_ctypes/test_slicing.py new file mode 100644 index 00000000000..66f9e530104 --- /dev/null +++ b/Lib/test/test_ctypes/test_slicing.py @@ -0,0 +1,170 @@ +import unittest +from ctypes import (CDLL, POINTER, sizeof, + c_byte, c_short, c_int, c_long, c_char, c_wchar, c_char_p) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class SlicesTestCase(unittest.TestCase): + def test_getslice_cint(self): + a = (c_int * 100)(*range(1100, 1200)) + b = list(range(1100, 1200)) + self.assertEqual(a[0:2], b[0:2]) + self.assertEqual(a[0:2:], b[0:2:]) + self.assertEqual(len(a), len(b)) + self.assertEqual(a[5:7], b[5:7]) + self.assertEqual(a[5:7:], b[5:7:]) + self.assertEqual(a[-1], b[-1]) + self.assertEqual(a[:], b[:]) + self.assertEqual(a[::], b[::]) + self.assertEqual(a[10::-1], b[10::-1]) + self.assertEqual(a[30:20:-1], b[30:20:-1]) + self.assertEqual(a[:12:6], b[:12:6]) + self.assertEqual(a[2:6:4], b[2:6:4]) + + a[0:5] = range(5, 10) + self.assertEqual(a[0:5], list(range(5, 10))) + self.assertEqual(a[0:5:], list(range(5, 10))) + self.assertEqual(a[4::-1], list(range(9, 4, -1))) + + def test_setslice_cint(self): + a = (c_int * 100)(*range(1100, 1200)) + b = list(range(1100, 1200)) + + a[32:47] = list(range(32, 47)) + self.assertEqual(a[32:47], list(range(32, 47))) + a[32:47] = range(132, 147) + self.assertEqual(a[32:47:], list(range(132, 147))) + a[46:31:-1] = range(232, 247) + self.assertEqual(a[32:47:1], list(range(246, 231, -1))) + + a[32:47] = range(1132, 1147) + self.assertEqual(a[:], b) + a[32:47:7] = range(3) + b[32:47:7] = range(3) + self.assertEqual(a[:], b) + a[33::-3] = range(12) + b[33::-3] = range(12) + self.assertEqual(a[:], b) + + # TypeError: int expected instead of str instance + with self.assertRaises(TypeError): + a[:5] = "abcde" + + # TypeError: int expected instead of str instance + with self.assertRaises(TypeError): + a[:5] = ["a", "b", "c", "d", "e"] + + # TypeError: int expected instead of float instance + with self.assertRaises(TypeError): + a[:5] = [1, 2, 3, 4, 3.14] + + # ValueError: Can only assign sequence of same size + with self.assertRaises(ValueError): + a[:5] = range(32) + + def test_char_ptr(self): + s = b"abcdefghijklmnopqrstuvwxyz" + + dll = CDLL(_ctypes_test.__file__) + dll.my_strdup.restype = POINTER(c_char) + dll.my_free.restype = None + res = dll.my_strdup(s) + self.assertEqual(res[:len(s)], s) + self.assertEqual(res[:3], s[:3]) + self.assertEqual(res[:len(s):], s) + self.assertEqual(res[len(s)-1:-1:-1], s[::-1]) + self.assertEqual(res[len(s)-1:5:-7], s[:5:-7]) + self.assertEqual(res[0:-1:-1], s[0::-1]) + + # get items + with self.assertRaises(ValueError): + res[:] + with self.assertRaises(ValueError): + res[0:] + with self.assertRaises(ValueError): + res[:5:-1] + with self.assertRaises(ValueError): + res[-5:] + + # set items + with self.assertRaises(TypeError): + res[:5] = "abcde" + + dll.my_free(res) + + dll.my_strdup.restype = POINTER(c_byte) + res = dll.my_strdup(s) + self.assertEqual(res[:len(s)], list(range(ord("a"), ord("z")+1))) + self.assertEqual(res[:len(s):], list(range(ord("a"), ord("z")+1))) + dll.my_free(res) + + def test_char_ptr_with_free(self): + dll = CDLL(_ctypes_test.__file__) + s = b"abcdefghijklmnopqrstuvwxyz" + + class allocated_c_char_p(c_char_p): + pass + + dll.my_free.restype = None + def errcheck(result, func, args): + retval = result.value + dll.my_free(result) + return retval + + dll.my_strdup.restype = allocated_c_char_p + dll.my_strdup.errcheck = errcheck + try: + res = dll.my_strdup(s) + self.assertEqual(res, s) + finally: + del dll.my_strdup.errcheck + + + def test_char_array(self): + s = b"abcdefghijklmnopqrstuvwxyz\0" + + p = (c_char * 27)(*s) + self.assertEqual(p[:], s) + self.assertEqual(p[::], s) + self.assertEqual(p[::-1], s[::-1]) + self.assertEqual(p[5::-2], s[5::-2]) + self.assertEqual(p[2:5:-3], s[2:5:-3]) + + + def test_wchar_ptr(self): + s = "abcdefghijklmnopqrstuvwxyz\0" + + dll = CDLL(_ctypes_test.__file__) + dll.my_wcsdup.restype = POINTER(c_wchar) + dll.my_wcsdup.argtypes = POINTER(c_wchar), + dll.my_free.restype = None + res = dll.my_wcsdup(s[:-1]) + self.assertEqual(res[:len(s)], s) + self.assertEqual(res[:len(s):], s) + self.assertEqual(res[len(s)-1:-1:-1], s[::-1]) + self.assertEqual(res[len(s)-1:5:-7], s[:5:-7]) + + with self.assertRaises(TypeError): + res[:5] = "abcde" + dll.my_free(res) + + if sizeof(c_wchar) == sizeof(c_short): + dll.my_wcsdup.restype = POINTER(c_short) + elif sizeof(c_wchar) == sizeof(c_int): + dll.my_wcsdup.restype = POINTER(c_int) + elif sizeof(c_wchar) == sizeof(c_long): + dll.my_wcsdup.restype = POINTER(c_long) + else: + self.skipTest('Pointers to c_wchar are not supported') + res = dll.my_wcsdup(s[:-1]) + tmpl = list(range(ord("a"), ord("z")+1)) + self.assertEqual(res[:len(s)-1], tmpl) + self.assertEqual(res[:len(s)-1:], tmpl) + self.assertEqual(res[len(s)-2:-1:-1], tmpl[::-1]) + self.assertEqual(res[len(s)-2:5:-7], tmpl[:5:-7]) + dll.my_free(res) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_stringptr.py b/Lib/test/test_ctypes/test_stringptr.py new file mode 100644 index 00000000000..bb6045b250f --- /dev/null +++ b/Lib/test/test_ctypes/test_stringptr.py @@ -0,0 +1,81 @@ +import sys +import unittest +from test import support +from ctypes import (CDLL, Structure, POINTER, create_string_buffer, + c_char, c_char_p) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +lib = CDLL(_ctypes_test.__file__) + + +class StringPtrTestCase(unittest.TestCase): + @support.refcount_test + def test__POINTER_c_char(self): + class X(Structure): + _fields_ = [("str", POINTER(c_char))] + x = X() + + # NULL pointer access + self.assertRaises(ValueError, getattr, x.str, "contents") + b = create_string_buffer(b"Hello, World") + self.assertEqual(sys.getrefcount(b), 2) + x.str = b + self.assertEqual(sys.getrefcount(b), 3) + + # POINTER(c_char) and Python string is NOT compatible + # POINTER(c_char) and create_string_buffer() is compatible + for i in range(len(b)): + self.assertEqual(b[i], x.str[i]) + + self.assertRaises(TypeError, setattr, x, "str", "Hello, World") + + def test__c_char_p(self): + class X(Structure): + _fields_ = [("str", c_char_p)] + x = X() + + # c_char_p and Python string is compatible + # c_char_p and create_string_buffer is NOT compatible + self.assertEqual(x.str, None) + x.str = b"Hello, World" + self.assertEqual(x.str, b"Hello, World") + b = create_string_buffer(b"Hello, World") + self.assertRaises(TypeError, setattr, x, b"str", b) + + + def test_functions(self): + strchr = lib.my_strchr + strchr.restype = c_char_p + + # c_char_p and Python string is compatible + # c_char_p and create_string_buffer are now compatible + strchr.argtypes = c_char_p, c_char + self.assertEqual(strchr(b"abcdef", b"c"), b"cdef") + self.assertEqual(strchr(create_string_buffer(b"abcdef"), b"c"), + b"cdef") + + # POINTER(c_char) and Python string is NOT compatible + # POINTER(c_char) and create_string_buffer() is compatible + strchr.argtypes = POINTER(c_char), c_char + buf = create_string_buffer(b"abcdef") + self.assertEqual(strchr(buf, b"c"), b"cdef") + self.assertEqual(strchr(b"abcdef", b"c"), b"cdef") + + # XXX These calls are dangerous, because the first argument + # to strchr is no longer valid after the function returns! + # So we must keep a reference to buf separately + + strchr.restype = POINTER(c_char) + buf = create_string_buffer(b"abcdef") + r = strchr(buf, b"c") + x = r[0], r[1], r[2], r[3], r[4] + self.assertEqual(x, (b"c", b"d", b"e", b"f", b"\000")) + del buf + # Because r is a pointer to memory that is freed after deleting buf, + # the pointer is hanging and using it would reference freed memory. + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_strings.py b/Lib/test/test_ctypes/test_strings.py new file mode 100644 index 00000000000..3ecc6fe180a --- /dev/null +++ b/Lib/test/test_ctypes/test_strings.py @@ -0,0 +1,134 @@ +import unittest +from ctypes import (create_string_buffer, create_unicode_buffer, + sizeof, byref, c_char, c_wchar) + + +class StringArrayTestCase(unittest.TestCase): + def test(self): + BUF = c_char * 4 + + buf = BUF(b"a", b"b", b"c") + self.assertEqual(buf.value, b"abc") + self.assertEqual(buf.raw, b"abc\000") + + buf.value = b"ABCD" + self.assertEqual(buf.value, b"ABCD") + self.assertEqual(buf.raw, b"ABCD") + + buf.value = b"x" + self.assertEqual(buf.value, b"x") + self.assertEqual(buf.raw, b"x\000CD") + + buf[1] = b"Z" + self.assertEqual(buf.value, b"xZCD") + self.assertEqual(buf.raw, b"xZCD") + + self.assertRaises(ValueError, setattr, buf, "value", b"aaaaaaaa") + self.assertRaises(TypeError, setattr, buf, "value", 42) + + def test_create_string_buffer_value(self): + buf = create_string_buffer(32) + + buf.value = b"Hello, World" + self.assertEqual(buf.value, b"Hello, World") + + self.assertRaises(TypeError, setattr, buf, "value", memoryview(b"Hello, World")) + self.assertRaises(TypeError, setattr, buf, "value", memoryview(b"abc")) + self.assertRaises(ValueError, setattr, buf, "raw", memoryview(b"x" * 100)) + + def test_create_string_buffer_raw(self): + buf = create_string_buffer(32) + + buf.raw = memoryview(b"Hello, World") + self.assertEqual(buf.value, b"Hello, World") + self.assertRaises(TypeError, setattr, buf, "value", memoryview(b"abc")) + self.assertRaises(ValueError, setattr, buf, "raw", memoryview(b"x" * 100)) + + def test_param_1(self): + BUF = c_char * 4 + buf = BUF() + + def test_param_2(self): + BUF = c_char * 4 + buf = BUF() + + def test_del_segfault(self): + BUF = c_char * 4 + buf = BUF() + with self.assertRaises(AttributeError): + del buf.raw + + +class WStringArrayTestCase(unittest.TestCase): + def test(self): + BUF = c_wchar * 4 + + buf = BUF("a", "b", "c") + self.assertEqual(buf.value, "abc") + + buf.value = "ABCD" + self.assertEqual(buf.value, "ABCD") + + buf.value = "x" + self.assertEqual(buf.value, "x") + + buf[1] = "Z" + self.assertEqual(buf.value, "xZCD") + + @unittest.skipIf(sizeof(c_wchar) < 4, + "sizeof(wchar_t) is smaller than 4 bytes") + def test_nonbmp(self): + u = chr(0x10ffff) + w = c_wchar(u) + self.assertEqual(w.value, u) + + +class WStringTestCase(unittest.TestCase): + def test_wchar(self): + c_wchar("x") + repr(byref(c_wchar("x"))) + c_wchar("x") + + def test_basic_wstrings(self): + cs = create_unicode_buffer("abcdef") + self.assertEqual(cs.value, "abcdef") + + # value can be changed + cs.value = "abc" + self.assertEqual(cs.value, "abc") + + # string is truncated at NUL character + cs.value = "def\0z" + self.assertEqual(cs.value, "def") + + self.assertEqual(create_unicode_buffer("abc\0def").value, "abc") + + # created with an empty string + cs = create_unicode_buffer(3) + self.assertEqual(cs.value, "") + + cs.value = "abc" + self.assertEqual(cs.value, "abc") + + def test_toolong(self): + cs = create_unicode_buffer("abc") + with self.assertRaises(ValueError): + cs.value = "abcdef" + + cs = create_unicode_buffer(4) + with self.assertRaises(ValueError): + cs.value = "abcdef" + + +def run_test(rep, msg, func, arg): + items = range(rep) + from time import perf_counter as clock + start = clock() + for i in items: + func(arg); func(arg); func(arg); func(arg); func(arg) + stop = clock() + print("%20s: %.2f us" % (msg, ((stop-start)*1e6/5/rep))) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_struct_fields.py b/Lib/test/test_ctypes/test_struct_fields.py new file mode 100644 index 00000000000..fd9509757a3 --- /dev/null +++ b/Lib/test/test_ctypes/test_struct_fields.py @@ -0,0 +1,126 @@ +import unittest +import sys +from ctypes import Structure, Union, sizeof, c_char, c_int +from ._support import (CField, Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_IMMUTABLETYPE) + + +class StructFieldsTestCase(unittest.TestCase): + # Structure/Union classes must get 'finalized' sooner or + # later, when one of these things happen: + # + # 1. _fields_ is set. + # 2. An instance is created. + # 3. The type is used as field of another Structure/Union. + # 4. The type is subclassed + # + # When they are finalized, assigning _fields_ is no longer allowed. + def test_1_A(self): + class X(Structure): + pass + self.assertEqual(sizeof(X), 0) # not finalized + X._fields_ = [] # finalized + self.assertRaises(AttributeError, setattr, X, "_fields_", []) + + def test_1_B(self): + class X(Structure): + _fields_ = [] # finalized + self.assertRaises(AttributeError, setattr, X, "_fields_", []) + + def test_2(self): + class X(Structure): + pass + X() + self.assertRaises(AttributeError, setattr, X, "_fields_", []) + + def test_3(self): + class X(Structure): + pass + class Y(Structure): + _fields_ = [("x", X)] # finalizes X + self.assertRaises(AttributeError, setattr, X, "_fields_", []) + + def test_4(self): + class X(Structure): + pass + class Y(X): + pass + self.assertRaises(AttributeError, setattr, X, "_fields_", []) + Y._fields_ = [] + self.assertRaises(AttributeError, setattr, X, "_fields_", []) + + def test_5(self): + class X(Structure): + _fields_ = (("char", c_char * 5),) + + x = X(b'#' * 5) + x.char = b'a\0b\0' + self.assertEqual(bytes(x), b'a\x00###') + + def test_6(self): + self.assertRaises(TypeError, CField) + + def test_cfield_type_flags(self): + self.assertTrue(CField.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION) + self.assertTrue(CField.__flags__ & Py_TPFLAGS_IMMUTABLETYPE) + + def test_cfield_inheritance_hierarchy(self): + self.assertEqual(CField.mro(), [CField, object]) + + def test_gh99275(self): + class BrokenStructure(Structure): + def __init_subclass__(cls, **kwargs): + cls._fields_ = [] # This line will fail, `stginfo` is not ready + + with self.assertRaisesRegex(TypeError, + 'ctypes state is not initialized'): + class Subclass(BrokenStructure): ... + + def test_max_field_size_gh126937(self): + # Classes for big structs should be created successfully. + # (But they most likely can't be instantiated.) + # The size must fit in Py_ssize_t. + + class X(Structure): + _fields_ = [('char', c_char),] + max_field_size = sys.maxsize + + class Y(Structure): + _fields_ = [('largeField', X * max_field_size)] + class Z(Structure): + _fields_ = [('largeField', c_char * max_field_size)] + + with self.assertRaises(OverflowError): + class TooBig(Structure): + _fields_ = [('largeField', X * (max_field_size + 1))] + with self.assertRaises(OverflowError): + class TooBig(Structure): + _fields_ = [('largeField', c_char * (max_field_size + 1))] + + # __set__ and __get__ should raise a TypeError in case their self + # argument is not a ctype instance. + def test___set__(self): + class MyCStruct(Structure): + _fields_ = (("field", c_int),) + self.assertRaises(TypeError, + MyCStruct.field.__set__, 'wrong type self', 42) + + class MyCUnion(Union): + _fields_ = (("field", c_int),) + self.assertRaises(TypeError, + MyCUnion.field.__set__, 'wrong type self', 42) + + def test___get__(self): + class MyCStruct(Structure): + _fields_ = (("field", c_int),) + self.assertRaises(TypeError, + MyCStruct.field.__get__, 'wrong type self', 42) + + class MyCUnion(Union): + _fields_ = (("field", c_int),) + self.assertRaises(TypeError, + MyCUnion.field.__get__, 'wrong type self', 42) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_structures.py b/Lib/test/test_ctypes/test_structures.py new file mode 100644 index 00000000000..7650c80273f --- /dev/null +++ b/Lib/test/test_ctypes/test_structures.py @@ -0,0 +1,956 @@ +from platform import architecture as _architecture +import struct +import sys +import unittest +from ctypes import (CDLL, Array, Structure, Union, POINTER, sizeof, byref, alignment, + c_void_p, c_char, c_wchar, c_byte, c_ubyte, + c_uint8, c_uint16, c_uint32, + c_short, c_ushort, c_int, c_uint, + c_long, c_ulong, c_longlong, c_ulonglong, c_float, c_double) +from ctypes.util import find_library +from struct import calcsize +from collections import namedtuple +from test import support +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") +from ._support import (_CData, PyCStructType, Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_IMMUTABLETYPE) + + +class SubclassesTest(unittest.TestCase): + def test_subclass(self): + class X(Structure): + _fields_ = [("a", c_int)] + + class Y(X): + _fields_ = [("b", c_int)] + + class Z(X): + pass + + self.assertEqual(sizeof(X), sizeof(c_int)) + self.assertEqual(sizeof(Y), sizeof(c_int)*2) + self.assertEqual(sizeof(Z), sizeof(c_int)) + self.assertEqual(X._fields_, [("a", c_int)]) + self.assertEqual(Y._fields_, [("b", c_int)]) + self.assertEqual(Z._fields_, [("a", c_int)]) + + def test_subclass_delayed(self): + class X(Structure): + pass + self.assertEqual(sizeof(X), 0) + X._fields_ = [("a", c_int)] + + class Y(X): + pass + self.assertEqual(sizeof(Y), sizeof(X)) + Y._fields_ = [("b", c_int)] + + class Z(X): + pass + + self.assertEqual(sizeof(X), sizeof(c_int)) + self.assertEqual(sizeof(Y), sizeof(c_int)*2) + self.assertEqual(sizeof(Z), sizeof(c_int)) + self.assertEqual(X._fields_, [("a", c_int)]) + self.assertEqual(Y._fields_, [("b", c_int)]) + self.assertEqual(Z._fields_, [("a", c_int)]) + + +class StructureTestCase(unittest.TestCase): + formats = {"c": c_char, + "b": c_byte, + "B": c_ubyte, + "h": c_short, + "H": c_ushort, + "i": c_int, + "I": c_uint, + "l": c_long, + "L": c_ulong, + "q": c_longlong, + "Q": c_ulonglong, + "f": c_float, + "d": c_double, + } + + def test_inheritance_hierarchy(self): + self.assertEqual(Structure.mro(), [Structure, _CData, object]) + + self.assertEqual(PyCStructType.__name__, "PyCStructType") + self.assertEqual(type(PyCStructType), type) + + + def test_type_flags(self): + for cls in Structure, PyCStructType: + with self.subTest(cls=cls): + self.assertTrue(Structure.__flags__ & Py_TPFLAGS_IMMUTABLETYPE) + self.assertFalse(Structure.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION) + + def test_metaclass_details(self): + # Abstract classes (whose metaclass __init__ was not called) can't be + # instantiated directly + NewStructure = PyCStructType.__new__(PyCStructType, 'NewStructure', + (Structure,), {}) + for cls in Structure, NewStructure: + with self.subTest(cls=cls): + with self.assertRaisesRegex(TypeError, "abstract class"): + obj = cls() + + # Cannot call the metaclass __init__ more than once + class T(Structure): + _fields_ = [("x", c_char), + ("y", c_char)] + with self.assertRaisesRegex(SystemError, "already initialized"): + PyCStructType.__init__(T, 'ptr', (), {}) + + def test_simple_structs(self): + for code, tp in self.formats.items(): + class X(Structure): + _fields_ = [("x", c_char), + ("y", tp)] + self.assertEqual((sizeof(X), code), + (calcsize("c%c0%c" % (code, code)), code)) + + def test_unions(self): + for code, tp in self.formats.items(): + class X(Union): + _fields_ = [("x", c_char), + ("y", tp)] + self.assertEqual((sizeof(X), code), + (calcsize("%c" % (code)), code)) + + def test_struct_alignment(self): + class X(Structure): + _fields_ = [("x", c_char * 3)] + self.assertEqual(alignment(X), calcsize("s")) + self.assertEqual(sizeof(X), calcsize("3s")) + + class Y(Structure): + _fields_ = [("x", c_char * 3), + ("y", c_int)] + self.assertEqual(alignment(Y), alignment(c_int)) + self.assertEqual(sizeof(Y), calcsize("3si")) + + class SI(Structure): + _fields_ = [("a", X), + ("b", Y)] + self.assertEqual(alignment(SI), max(alignment(Y), alignment(X))) + self.assertEqual(sizeof(SI), calcsize("3s0i 3si 0i")) + + class IS(Structure): + _fields_ = [("b", Y), + ("a", X)] + + self.assertEqual(alignment(SI), max(alignment(X), alignment(Y))) + self.assertEqual(sizeof(IS), calcsize("3si 3s 0i")) + + class XX(Structure): + _fields_ = [("a", X), + ("b", X)] + self.assertEqual(alignment(XX), alignment(X)) + self.assertEqual(sizeof(XX), calcsize("3s 3s 0s")) + + def test_empty(self): + # I had problems with these + # + # Although these are pathological cases: Empty Structures! + class X(Structure): + _fields_ = [] + + class Y(Union): + _fields_ = [] + + # Is this really the correct alignment, or should it be 0? + self.assertTrue(alignment(X) == alignment(Y) == 1) + self.assertTrue(sizeof(X) == sizeof(Y) == 0) + + class XX(Structure): + _fields_ = [("a", X), + ("b", X)] + + self.assertEqual(alignment(XX), 1) + self.assertEqual(sizeof(XX), 0) + + def test_fields(self): + # test the offset and size attributes of Structure/Union fields. + class X(Structure): + _fields_ = [("x", c_int), + ("y", c_char)] + + self.assertEqual(X.x.offset, 0) + self.assertEqual(X.x.size, sizeof(c_int)) + + self.assertEqual(X.y.offset, sizeof(c_int)) + self.assertEqual(X.y.size, sizeof(c_char)) + + # readonly + self.assertRaises((TypeError, AttributeError), setattr, X.x, "offset", 92) + self.assertRaises((TypeError, AttributeError), setattr, X.x, "size", 92) + + class X(Union): + _fields_ = [("x", c_int), + ("y", c_char)] + + self.assertEqual(X.x.offset, 0) + self.assertEqual(X.x.size, sizeof(c_int)) + + self.assertEqual(X.y.offset, 0) + self.assertEqual(X.y.size, sizeof(c_char)) + + # readonly + self.assertRaises((TypeError, AttributeError), setattr, X.x, "offset", 92) + self.assertRaises((TypeError, AttributeError), setattr, X.x, "size", 92) + + # XXX Should we check nested data types also? + # offset is always relative to the class... + + def test_packed(self): + class X(Structure): + _fields_ = [("a", c_byte), + ("b", c_longlong)] + _pack_ = 1 + + self.assertEqual(sizeof(X), 9) + self.assertEqual(X.b.offset, 1) + + class X(Structure): + _fields_ = [("a", c_byte), + ("b", c_longlong)] + _pack_ = 2 + self.assertEqual(sizeof(X), 10) + self.assertEqual(X.b.offset, 2) + + longlong_size = struct.calcsize("q") + longlong_align = struct.calcsize("bq") - longlong_size + + class X(Structure): + _fields_ = [("a", c_byte), + ("b", c_longlong)] + _pack_ = 4 + self.assertEqual(sizeof(X), min(4, longlong_align) + longlong_size) + self.assertEqual(X.b.offset, min(4, longlong_align)) + + class X(Structure): + _fields_ = [("a", c_byte), + ("b", c_longlong)] + _pack_ = 8 + + self.assertEqual(sizeof(X), min(8, longlong_align) + longlong_size) + self.assertEqual(X.b.offset, min(8, longlong_align)) + + + d = {"_fields_": [("a", "b"), + ("b", "q")], + "_pack_": -1} + self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) + + @support.cpython_only + def test_packed_c_limits(self): + # Issue 15989 + import _testcapi + d = {"_fields_": [("a", c_byte)], + "_pack_": _testcapi.INT_MAX + 1} + self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) + d = {"_fields_": [("a", c_byte)], + "_pack_": _testcapi.UINT_MAX + 2} + self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) + + def test_initializers(self): + class Person(Structure): + _fields_ = [("name", c_char*6), + ("age", c_int)] + + self.assertRaises(TypeError, Person, 42) + self.assertRaises(ValueError, Person, b"asldkjaslkdjaslkdj") + self.assertRaises(TypeError, Person, "Name", "HI") + + # short enough + self.assertEqual(Person(b"12345", 5).name, b"12345") + # exact fit + self.assertEqual(Person(b"123456", 5).name, b"123456") + # too long + self.assertRaises(ValueError, Person, b"1234567", 5) + + def test_conflicting_initializers(self): + class POINT(Structure): + _fields_ = [("phi", c_float), ("rho", c_float)] + # conflicting positional and keyword args + self.assertRaisesRegex(TypeError, "phi", POINT, 2, 3, phi=4) + self.assertRaisesRegex(TypeError, "rho", POINT, 2, 3, rho=4) + + # too many initializers + self.assertRaises(TypeError, POINT, 2, 3, 4) + + def test_keyword_initializers(self): + class POINT(Structure): + _fields_ = [("x", c_int), ("y", c_int)] + pt = POINT(1, 2) + self.assertEqual((pt.x, pt.y), (1, 2)) + + pt = POINT(y=2, x=1) + self.assertEqual((pt.x, pt.y), (1, 2)) + + def test_invalid_field_types(self): + class POINT(Structure): + pass + self.assertRaises(TypeError, setattr, POINT, "_fields_", [("x", 1), ("y", 2)]) + + def test_invalid_name(self): + # field name must be string + def declare_with_name(name): + class S(Structure): + _fields_ = [(name, c_int)] + + self.assertRaises(TypeError, declare_with_name, b"x") + + def test_intarray_fields(self): + class SomeInts(Structure): + _fields_ = [("a", c_int * 4)] + + # can use tuple to initialize array (but not list!) + self.assertEqual(SomeInts((1, 2)).a[:], [1, 2, 0, 0]) + self.assertEqual(SomeInts((1, 2)).a[::], [1, 2, 0, 0]) + self.assertEqual(SomeInts((1, 2)).a[::-1], [0, 0, 2, 1]) + self.assertEqual(SomeInts((1, 2)).a[::2], [1, 0]) + self.assertEqual(SomeInts((1, 2)).a[1:5:6], [2]) + self.assertEqual(SomeInts((1, 2)).a[6:4:-1], []) + self.assertEqual(SomeInts((1, 2, 3, 4)).a[:], [1, 2, 3, 4]) + self.assertEqual(SomeInts((1, 2, 3, 4)).a[::], [1, 2, 3, 4]) + # too long + # XXX Should raise ValueError?, not RuntimeError + self.assertRaises(RuntimeError, SomeInts, (1, 2, 3, 4, 5)) + + def test_nested_initializers(self): + # test initializing nested structures + class Phone(Structure): + _fields_ = [("areacode", c_char*6), + ("number", c_char*12)] + + class Person(Structure): + _fields_ = [("name", c_char * 12), + ("phone", Phone), + ("age", c_int)] + + p = Person(b"Someone", (b"1234", b"5678"), 5) + + self.assertEqual(p.name, b"Someone") + self.assertEqual(p.phone.areacode, b"1234") + self.assertEqual(p.phone.number, b"5678") + self.assertEqual(p.age, 5) + + def test_structures_with_wchar(self): + class PersonW(Structure): + _fields_ = [("name", c_wchar * 12), + ("age", c_int)] + + p = PersonW("Someone \xe9") + self.assertEqual(p.name, "Someone \xe9") + + self.assertEqual(PersonW("1234567890").name, "1234567890") + self.assertEqual(PersonW("12345678901").name, "12345678901") + # exact fit + self.assertEqual(PersonW("123456789012").name, "123456789012") + #too long + self.assertRaises(ValueError, PersonW, "1234567890123") + + def test_init_errors(self): + class Phone(Structure): + _fields_ = [("areacode", c_char*6), + ("number", c_char*12)] + + class Person(Structure): + _fields_ = [("name", c_char * 12), + ("phone", Phone), + ("age", c_int)] + + cls, msg = self.get_except(Person, b"Someone", (1, 2)) + self.assertEqual(cls, RuntimeError) + self.assertEqual(msg, + "(Phone) TypeError: " + "expected bytes, int found") + + cls, msg = self.get_except(Person, b"Someone", (b"a", b"b", b"c")) + self.assertEqual(cls, RuntimeError) + self.assertEqual(msg, + "(Phone) TypeError: too many initializers") + + def test_huge_field_name(self): + # issue12881: segfault with large structure field names + def create_class(length): + class S(Structure): + _fields_ = [('x' * length, c_int)] + + for length in [10 ** i for i in range(0, 8)]: + try: + create_class(length) + except MemoryError: + # MemoryErrors are OK, we just don't want to segfault + pass + + def get_except(self, func, *args): + try: + func(*args) + except Exception as detail: + return detail.__class__, str(detail) + + def test_abstract_class(self): + class X(Structure): + _abstract_ = "something" + # try 'X()' + cls, msg = self.get_except(eval, "X()", locals()) + self.assertEqual((cls, msg), (TypeError, "abstract class")) + + def test_methods(self): + self.assertIn("in_dll", dir(type(Structure))) + self.assertIn("from_address", dir(type(Structure))) + self.assertIn("in_dll", dir(type(Structure))) + + def test_positional_args(self): + # see also http://bugs.python.org/issue5042 + class W(Structure): + _fields_ = [("a", c_int), ("b", c_int)] + class X(W): + _fields_ = [("c", c_int)] + class Y(X): + pass + class Z(Y): + _fields_ = [("d", c_int), ("e", c_int), ("f", c_int)] + + z = Z(1, 2, 3, 4, 5, 6) + self.assertEqual((z.a, z.b, z.c, z.d, z.e, z.f), + (1, 2, 3, 4, 5, 6)) + z = Z(1) + self.assertEqual((z.a, z.b, z.c, z.d, z.e, z.f), + (1, 0, 0, 0, 0, 0)) + self.assertRaises(TypeError, lambda: Z(1, 2, 3, 4, 5, 6, 7)) + + def test_pass_by_value(self): + # This should mirror the Test structure + # in Modules/_ctypes/_ctypes_test.c + class Test(Structure): + _fields_ = [ + ('first', c_ulong), + ('second', c_ulong), + ('third', c_ulong), + ] + + s = Test() + s.first = 0xdeadbeef + s.second = 0xcafebabe + s.third = 0x0bad1dea + dll = CDLL(_ctypes_test.__file__) + func = dll._testfunc_large_struct_update_value + func.argtypes = (Test,) + func.restype = None + func(s) + self.assertEqual(s.first, 0xdeadbeef) + self.assertEqual(s.second, 0xcafebabe) + self.assertEqual(s.third, 0x0bad1dea) + + def test_pass_by_value_finalizer(self): + # bpo-37140: Similar to test_pass_by_value(), but the Python structure + # has a finalizer (__del__() method): the finalizer must only be called + # once. + + finalizer_calls = [] + + class Test(Structure): + _fields_ = [ + ('first', c_ulong), + ('second', c_ulong), + ('third', c_ulong), + ] + def __del__(self): + finalizer_calls.append("called") + + s = Test(1, 2, 3) + # Test the StructUnionType_paramfunc() code path which copies the + # structure: if the structure is larger than sizeof(void*). + self.assertGreater(sizeof(s), sizeof(c_void_p)) + + dll = CDLL(_ctypes_test.__file__) + func = dll._testfunc_large_struct_update_value + func.argtypes = (Test,) + func.restype = None + func(s) + # bpo-37140: Passing the structure by reference must not call + # its finalizer! + self.assertEqual(finalizer_calls, []) + self.assertEqual(s.first, 1) + self.assertEqual(s.second, 2) + self.assertEqual(s.third, 3) + + # The finalizer must be called exactly once + s = None + support.gc_collect() + self.assertEqual(finalizer_calls, ["called"]) + + def test_pass_by_value_in_register(self): + class X(Structure): + _fields_ = [ + ('first', c_uint), + ('second', c_uint) + ] + + s = X() + s.first = 0xdeadbeef + s.second = 0xcafebabe + dll = CDLL(_ctypes_test.__file__) + func = dll._testfunc_reg_struct_update_value + func.argtypes = (X,) + func.restype = None + func(s) + self.assertEqual(s.first, 0xdeadbeef) + self.assertEqual(s.second, 0xcafebabe) + got = X.in_dll(dll, "last_tfrsuv_arg") + self.assertEqual(s.first, got.first) + self.assertEqual(s.second, got.second) + + def _test_issue18060(self, Vector): + # The call to atan2() should succeed if the + # class fields were correctly cloned in the + # subclasses. Otherwise, it will segfault. + if sys.platform == 'win32': + libm = CDLL(find_library('msvcrt.dll')) + else: + libm = CDLL(find_library('m')) + + libm.atan2.argtypes = [Vector] + libm.atan2.restype = c_double + + arg = Vector(y=0.0, x=-1.0) + self.assertAlmostEqual(libm.atan2(arg), 3.141592653589793) + + @unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build") + @unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform") + def test_issue18060_a(self): + # This test case calls + # PyCStructUnionType_update_stginfo() for each + # _fields_ assignment, and PyCStgInfo_clone() + # for the Mid and Vector class definitions. + class Base(Structure): + _fields_ = [('y', c_double), + ('x', c_double)] + class Mid(Base): + pass + Mid._fields_ = [] + class Vector(Mid): pass + self._test_issue18060(Vector) + + @unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build") + @unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform") + def test_issue18060_b(self): + # This test case calls + # PyCStructUnionType_update_stginfo() for each + # _fields_ assignment. + class Base(Structure): + _fields_ = [('y', c_double), + ('x', c_double)] + class Mid(Base): + _fields_ = [] + class Vector(Mid): + _fields_ = [] + self._test_issue18060(Vector) + + @unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build") + @unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform") + def test_issue18060_c(self): + # This test case calls + # PyCStructUnionType_update_stginfo() for each + # _fields_ assignment. + class Base(Structure): + _fields_ = [('y', c_double)] + class Mid(Base): + _fields_ = [] + class Vector(Mid): + _fields_ = [('x', c_double)] + self._test_issue18060(Vector) + + def test_array_in_struct(self): + # See bpo-22273 + + # Load the shared library + dll = CDLL(_ctypes_test.__file__) + + # These should mirror the structures in Modules/_ctypes/_ctypes_test.c + class Test2(Structure): + _fields_ = [ + ('data', c_ubyte * 16), + ] + + class Test3AParent(Structure): + _fields_ = [ + ('data', c_float * 2), + ] + + class Test3A(Test3AParent): + _fields_ = [ + ('more_data', c_float * 2), + ] + + class Test3B(Structure): + _fields_ = [ + ('data', c_double * 2), + ] + + class Test3C(Structure): + _fields_ = [ + ("data", c_double * 4) + ] + + class Test3D(Structure): + _fields_ = [ + ("data", c_double * 8) + ] + + class Test3E(Structure): + _fields_ = [ + ("data", c_double * 9) + ] + + + # Tests for struct Test2 + s = Test2() + expected = 0 + for i in range(16): + s.data[i] = i + expected += i + func = dll._testfunc_array_in_struct2 + func.restype = c_int + func.argtypes = (Test2,) + result = func(s) + self.assertEqual(result, expected) + # check the passed-in struct hasn't changed + for i in range(16): + self.assertEqual(s.data[i], i) + + # Tests for struct Test3A + s = Test3A() + s.data[0] = 3.14159 + s.data[1] = 2.71828 + s.more_data[0] = -3.0 + s.more_data[1] = -2.0 + expected = 3.14159 + 2.71828 - 3.0 - 2.0 + func = dll._testfunc_array_in_struct3A + func.restype = c_double + func.argtypes = (Test3A,) + result = func(s) + self.assertAlmostEqual(result, expected, places=6) + # check the passed-in struct hasn't changed + self.assertAlmostEqual(s.data[0], 3.14159, places=6) + self.assertAlmostEqual(s.data[1], 2.71828, places=6) + self.assertAlmostEqual(s.more_data[0], -3.0, places=6) + self.assertAlmostEqual(s.more_data[1], -2.0, places=6) + + # Test3B, Test3C, Test3D, Test3E have the same logic with different + # sizes hence putting them in a loop. + StructCtype = namedtuple( + "StructCtype", + ["cls", "cfunc1", "cfunc2", "items"] + ) + structs_to_test = [ + StructCtype( + Test3B, + dll._testfunc_array_in_struct3B, + dll._testfunc_array_in_struct3B_set_defaults, + 2), + StructCtype( + Test3C, + dll._testfunc_array_in_struct3C, + dll._testfunc_array_in_struct3C_set_defaults, + 4), + StructCtype( + Test3D, + dll._testfunc_array_in_struct3D, + dll._testfunc_array_in_struct3D_set_defaults, + 8), + StructCtype( + Test3E, + dll._testfunc_array_in_struct3E, + dll._testfunc_array_in_struct3E_set_defaults, + 9), + ] + + for sut in structs_to_test: + s = sut.cls() + + # Test for cfunc1 + expected = 0 + for i in range(sut.items): + float_i = float(i) + s.data[i] = float_i + expected += float_i + func = sut.cfunc1 + func.restype = c_double + func.argtypes = (sut.cls,) + result = func(s) + self.assertEqual(result, expected) + # check the passed-in struct hasn't changed + for i in range(sut.items): + self.assertEqual(s.data[i], float(i)) + + # Test for cfunc2 + func = sut.cfunc2 + func.restype = sut.cls + result = func() + # check if the default values have been set correctly + for i in range(sut.items): + self.assertEqual(result.data[i], float(i+1)) + + def test_38368(self): + class U(Union): + _fields_ = [ + ('f1', c_uint8 * 16), + ('f2', c_uint16 * 8), + ('f3', c_uint32 * 4), + ] + u = U() + u.f3[0] = 0x01234567 + u.f3[1] = 0x89ABCDEF + u.f3[2] = 0x76543210 + u.f3[3] = 0xFEDCBA98 + f1 = [u.f1[i] for i in range(16)] + f2 = [u.f2[i] for i in range(8)] + if sys.byteorder == 'little': + self.assertEqual(f1, [0x67, 0x45, 0x23, 0x01, + 0xef, 0xcd, 0xab, 0x89, + 0x10, 0x32, 0x54, 0x76, + 0x98, 0xba, 0xdc, 0xfe]) + self.assertEqual(f2, [0x4567, 0x0123, 0xcdef, 0x89ab, + 0x3210, 0x7654, 0xba98, 0xfedc]) + + @unittest.skipIf(True, 'Test disabled for now - see bpo-16575/bpo-16576') + def test_union_by_value(self): + # See bpo-16575 + + # These should mirror the structures in Modules/_ctypes/_ctypes_test.c + + class Nested1(Structure): + _fields_ = [ + ('an_int', c_int), + ('another_int', c_int), + ] + + class Test4(Union): + _fields_ = [ + ('a_long', c_long), + ('a_struct', Nested1), + ] + + class Nested2(Structure): + _fields_ = [ + ('an_int', c_int), + ('a_union', Test4), + ] + + class Test5(Structure): + _fields_ = [ + ('an_int', c_int), + ('nested', Nested2), + ('another_int', c_int), + ] + + test4 = Test4() + dll = CDLL(_ctypes_test.__file__) + with self.assertRaises(TypeError) as ctx: + func = dll._testfunc_union_by_value1 + func.restype = c_long + func.argtypes = (Test4,) + result = func(test4) + self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes ' + 'a union by value, which is unsupported.') + test5 = Test5() + with self.assertRaises(TypeError) as ctx: + func = dll._testfunc_union_by_value2 + func.restype = c_long + func.argtypes = (Test5,) + result = func(test5) + self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes ' + 'a union by value, which is unsupported.') + + # passing by reference should be OK + test4.a_long = 12345; + func = dll._testfunc_union_by_reference1 + func.restype = c_long + func.argtypes = (POINTER(Test4),) + result = func(byref(test4)) + self.assertEqual(result, 12345) + self.assertEqual(test4.a_long, 0) + self.assertEqual(test4.a_struct.an_int, 0) + self.assertEqual(test4.a_struct.another_int, 0) + test4.a_struct.an_int = 0x12340000 + test4.a_struct.another_int = 0x5678 + func = dll._testfunc_union_by_reference2 + func.restype = c_long + func.argtypes = (POINTER(Test4),) + result = func(byref(test4)) + self.assertEqual(result, 0x12345678) + self.assertEqual(test4.a_long, 0) + self.assertEqual(test4.a_struct.an_int, 0) + self.assertEqual(test4.a_struct.another_int, 0) + test5.an_int = 0x12000000 + test5.nested.an_int = 0x345600 + test5.another_int = 0x78 + func = dll._testfunc_union_by_reference3 + func.restype = c_long + func.argtypes = (POINTER(Test5),) + result = func(byref(test5)) + self.assertEqual(result, 0x12345678) + self.assertEqual(test5.an_int, 0) + self.assertEqual(test5.nested.an_int, 0) + self.assertEqual(test5.another_int, 0) + + @unittest.skipIf(True, 'Test disabled for now - see bpo-16575/bpo-16576') + def test_bitfield_by_value(self): + # See bpo-16576 + + # These should mirror the structures in Modules/_ctypes/_ctypes_test.c + + class Test6(Structure): + _fields_ = [ + ('A', c_int, 1), + ('B', c_int, 2), + ('C', c_int, 3), + ('D', c_int, 2), + ] + + test6 = Test6() + # As these are signed int fields, all are logically -1 due to sign + # extension. + test6.A = 1 + test6.B = 3 + test6.C = 7 + test6.D = 3 + dll = CDLL(_ctypes_test.__file__) + with self.assertRaises(TypeError) as ctx: + func = dll._testfunc_bitfield_by_value1 + func.restype = c_long + func.argtypes = (Test6,) + result = func(test6) + self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes ' + 'a struct/union with a bitfield by value, which is ' + 'unsupported.') + # passing by reference should be OK + func = dll._testfunc_bitfield_by_reference1 + func.restype = c_long + func.argtypes = (POINTER(Test6),) + result = func(byref(test6)) + self.assertEqual(result, -4) + self.assertEqual(test6.A, 0) + self.assertEqual(test6.B, 0) + self.assertEqual(test6.C, 0) + self.assertEqual(test6.D, 0) + + class Test7(Structure): + _fields_ = [ + ('A', c_uint, 1), + ('B', c_uint, 2), + ('C', c_uint, 3), + ('D', c_uint, 2), + ] + test7 = Test7() + test7.A = 1 + test7.B = 3 + test7.C = 7 + test7.D = 3 + func = dll._testfunc_bitfield_by_reference2 + func.restype = c_long + func.argtypes = (POINTER(Test7),) + result = func(byref(test7)) + self.assertEqual(result, 14) + self.assertEqual(test7.A, 0) + self.assertEqual(test7.B, 0) + self.assertEqual(test7.C, 0) + self.assertEqual(test7.D, 0) + + # for a union with bitfields, the union check happens first + class Test8(Union): + _fields_ = [ + ('A', c_int, 1), + ('B', c_int, 2), + ('C', c_int, 3), + ('D', c_int, 2), + ] + + test8 = Test8() + with self.assertRaises(TypeError) as ctx: + func = dll._testfunc_bitfield_by_value2 + func.restype = c_long + func.argtypes = (Test8,) + result = func(test8) + self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes ' + 'a union by value, which is unsupported.') + + +class PointerMemberTestCase(unittest.TestCase): + + def test(self): + # a Structure with a POINTER field + class S(Structure): + _fields_ = [("array", POINTER(c_int))] + + s = S() + # We can assign arrays of the correct type + s.array = (c_int * 3)(1, 2, 3) + items = [s.array[i] for i in range(3)] + self.assertEqual(items, [1, 2, 3]) + + # The following are bugs, but are included here because the unittests + # also describe the current behaviour. + # + # This fails with SystemError: bad arg to internal function + # or with IndexError (with a patch I have) + + s.array[0] = 42 + + items = [s.array[i] for i in range(3)] + self.assertEqual(items, [42, 2, 3]) + + s.array[0] = 1 + + items = [s.array[i] for i in range(3)] + self.assertEqual(items, [1, 2, 3]) + + def test_none_to_pointer_fields(self): + class S(Structure): + _fields_ = [("x", c_int), + ("p", POINTER(c_int))] + + s = S() + s.x = 12345678 + s.p = None + self.assertEqual(s.x, 12345678) + + +class TestRecursiveStructure(unittest.TestCase): + def test_contains_itself(self): + class Recursive(Structure): + pass + + try: + Recursive._fields_ = [("next", Recursive)] + except AttributeError as details: + self.assertIn("Structure or union cannot contain itself", + str(details)) + else: + self.fail("Structure or union cannot contain itself") + + + def test_vice_versa(self): + class First(Structure): + pass + class Second(Structure): + pass + + First._fields_ = [("second", Second)] + + try: + Second._fields_ = [("first", First)] + except AttributeError as details: + self.assertIn("_fields_ is final", str(details)) + else: + self.fail("AttributeError not raised") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_unaligned_structures.py b/Lib/test/test_ctypes/test_unaligned_structures.py new file mode 100644 index 00000000000..58a00597ef5 --- /dev/null +++ b/Lib/test/test_ctypes/test_unaligned_structures.py @@ -0,0 +1,49 @@ +import sys, unittest +from ctypes import (Structure, BigEndianStructure, LittleEndianStructure, + c_byte, c_short, c_int, c_long, c_longlong, + c_float, c_double, + c_ushort, c_uint, c_ulong, c_ulonglong) + + +structures = [] +byteswapped_structures = [] + + +if sys.byteorder == "little": + SwappedStructure = BigEndianStructure +else: + SwappedStructure = LittleEndianStructure + +for typ in [c_short, c_int, c_long, c_longlong, + c_float, c_double, + c_ushort, c_uint, c_ulong, c_ulonglong]: + class X(Structure): + _pack_ = 1 + _fields_ = [("pad", c_byte), + ("value", typ)] + class Y(SwappedStructure): + _pack_ = 1 + _fields_ = [("pad", c_byte), + ("value", typ)] + structures.append(X) + byteswapped_structures.append(Y) + + +class TestStructures(unittest.TestCase): + def test_native(self): + for typ in structures: + self.assertEqual(typ.value.offset, 1) + o = typ() + o.value = 4 + self.assertEqual(o.value, 4) + + def test_swapped(self): + for typ in byteswapped_structures: + self.assertEqual(typ.value.offset, 1) + o = typ() + o.value = 4 + self.assertEqual(o.value, 4) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_unicode.py b/Lib/test/test_ctypes/test_unicode.py new file mode 100644 index 00000000000..d9e17371d13 --- /dev/null +++ b/Lib/test/test_ctypes/test_unicode.py @@ -0,0 +1,63 @@ +import ctypes +import unittest +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class UnicodeTestCase(unittest.TestCase): + def test_wcslen(self): + dll = ctypes.CDLL(_ctypes_test.__file__) + wcslen = dll.my_wcslen + wcslen.argtypes = [ctypes.c_wchar_p] + + self.assertEqual(wcslen("abc"), 3) + self.assertEqual(wcslen("ab\u2070"), 3) + self.assertRaises(ctypes.ArgumentError, wcslen, b"ab\xe4") + + def test_buffers(self): + buf = ctypes.create_unicode_buffer("abc") + self.assertEqual(len(buf), 3+1) + + buf = ctypes.create_unicode_buffer("ab\xe4\xf6\xfc") + self.assertEqual(buf[:], "ab\xe4\xf6\xfc\0") + self.assertEqual(buf[::], "ab\xe4\xf6\xfc\0") + self.assertEqual(buf[::-1], '\x00\xfc\xf6\xe4ba') + self.assertEqual(buf[::2], 'a\xe4\xfc') + self.assertEqual(buf[6:5:-1], "") + + def test_embedded_null(self): + class TestStruct(ctypes.Structure): + _fields_ = [("unicode", ctypes.c_wchar_p)] + t = TestStruct() + # This would raise a ValueError: + t.unicode = "foo\0bar\0\0" + + +func = ctypes.CDLL(_ctypes_test.__file__)._testfunc_p_p + +class StringTestCase(UnicodeTestCase): + def setUp(self): + func.argtypes = [ctypes.c_char_p] + func.restype = ctypes.c_char_p + + def tearDown(self): + func.argtypes = None + func.restype = ctypes.c_int + + def test_func(self): + self.assertEqual(func(b"abc\xe4"), b"abc\xe4") + + def test_buffers(self): + buf = ctypes.create_string_buffer(b"abc") + self.assertEqual(len(buf), 3+1) + + buf = ctypes.create_string_buffer(b"ab\xe4\xf6\xfc") + self.assertEqual(buf[:], b"ab\xe4\xf6\xfc\0") + self.assertEqual(buf[::], b"ab\xe4\xf6\xfc\0") + self.assertEqual(buf[::-1], b'\x00\xfc\xf6\xe4ba') + self.assertEqual(buf[::2], b'a\xe4\xfc') + self.assertEqual(buf[6:5:-1], b"") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_unions.py b/Lib/test/test_ctypes/test_unions.py new file mode 100644 index 00000000000..e2dff0f22a9 --- /dev/null +++ b/Lib/test/test_ctypes/test_unions.py @@ -0,0 +1,35 @@ +import unittest +from ctypes import Union, c_char +from ._support import (_CData, UnionType, Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_IMMUTABLETYPE) + + +class ArrayTestCase(unittest.TestCase): + def test_inheritance_hierarchy(self): + self.assertEqual(Union.mro(), [Union, _CData, object]) + + self.assertEqual(UnionType.__name__, "UnionType") + self.assertEqual(type(UnionType), type) + + def test_type_flags(self): + for cls in Union, UnionType: + with self.subTest(cls=Union): + self.assertTrue(Union.__flags__ & Py_TPFLAGS_IMMUTABLETYPE) + self.assertFalse(Union.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION) + + def test_metaclass_details(self): + # Abstract classes (whose metaclass __init__ was not called) can't be + # instantiated directly + NewUnion = UnionType.__new__(UnionType, 'NewUnion', + (Union,), {}) + for cls in Union, NewUnion: + with self.subTest(cls=cls): + with self.assertRaisesRegex(TypeError, "abstract class"): + obj = cls() + + # Cannot call the metaclass __init__ more than once + class T(Union): + _fields_ = [("x", c_char), + ("y", c_char)] + with self.assertRaisesRegex(SystemError, "already initialized"): + UnionType.__init__(T, 'ptr', (), {}) diff --git a/Lib/test/test_ctypes/test_values.py b/Lib/test/test_ctypes/test_values.py new file mode 100644 index 00000000000..1b757e020d5 --- /dev/null +++ b/Lib/test/test_ctypes/test_values.py @@ -0,0 +1,107 @@ +""" +A testcase which accesses *values* in a dll. +""" + +import _imp +import importlib.util +import sys +import unittest +from ctypes import (Structure, CDLL, POINTER, pythonapi, + _pointer_type_cache, + c_ubyte, c_char_p, c_int) +from test.support import import_helper + + +class ValuesTestCase(unittest.TestCase): + + def setUp(self): + _ctypes_test = import_helper.import_module("_ctypes_test") + self.ctdll = CDLL(_ctypes_test.__file__) + + def test_an_integer(self): + # This test checks and changes an integer stored inside the + # _ctypes_test dll/shared lib. + ctdll = self.ctdll + an_integer = c_int.in_dll(ctdll, "an_integer") + x = an_integer.value + self.assertEqual(x, ctdll.get_an_integer()) + an_integer.value *= 2 + self.assertEqual(x*2, ctdll.get_an_integer()) + # To avoid test failures when this test is repeated several + # times the original value must be restored + an_integer.value = x + self.assertEqual(x, ctdll.get_an_integer()) + + def test_undefined(self): + self.assertRaises(ValueError, c_int.in_dll, self.ctdll, "Undefined_Symbol") + + +class PythonValuesTestCase(unittest.TestCase): + """This test only works when python itself is a dll/shared library""" + + def test_optimizeflag(self): + # This test accesses the Py_OptimizeFlag integer, which is + # exported by the Python dll and should match the sys.flags value + + opt = c_int.in_dll(pythonapi, "Py_OptimizeFlag").value + self.assertEqual(opt, sys.flags.optimize) + + def test_frozentable(self): + # Python exports a PyImport_FrozenModules symbol. This is a + # pointer to an array of struct _frozen entries. The end of the + # array is marked by an entry containing a NULL name and zero + # size. + + # In standard Python, this table contains a __hello__ + # module, and a __phello__ package containing a spam + # module. + class struct_frozen(Structure): + _fields_ = [("name", c_char_p), + ("code", POINTER(c_ubyte)), + ("size", c_int), + ("is_package", c_int), + ] + FrozenTable = POINTER(struct_frozen) + + modules = [] + for group in ["Bootstrap", "Stdlib", "Test"]: + ft = FrozenTable.in_dll(pythonapi, f"_PyImport_Frozen{group}") + # ft is a pointer to the struct_frozen entries: + for entry in ft: + # This is dangerous. We *can* iterate over a pointer, but + # the loop will not terminate (maybe with an access + # violation;-) because the pointer instance has no size. + if entry.name is None: + break + modname = entry.name.decode("ascii") + modules.append(modname) + with self.subTest(modname): + if entry.size != 0: + # Do a sanity check on entry.size and entry.code. + self.assertGreater(abs(entry.size), 10) + self.assertTrue([entry.code[i] for i in range(abs(entry.size))]) + # Check the module's package-ness. + with import_helper.frozen_modules(): + spec = importlib.util.find_spec(modname) + if entry.is_package: + # It's a package. + self.assertIsNotNone(spec.submodule_search_locations) + else: + self.assertIsNone(spec.submodule_search_locations) + + with import_helper.frozen_modules(): + expected = _imp._frozen_module_names() + self.maxDiff = None + self.assertEqual(modules, expected, + "_PyImport_FrozenBootstrap example " + "in Doc/library/ctypes.rst may be out of date") + + del _pointer_type_cache[struct_frozen] + + def test_undefined(self): + self.assertRaises(ValueError, c_int.in_dll, pythonapi, + "Undefined_Symbol") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_varsize_struct.py b/Lib/test/test_ctypes/test_varsize_struct.py new file mode 100644 index 00000000000..3e6ba6fed07 --- /dev/null +++ b/Lib/test/test_ctypes/test_varsize_struct.py @@ -0,0 +1,52 @@ +import unittest +from ctypes import Structure, sizeof, resize, c_int + + +class VarSizeTest(unittest.TestCase): + def test_resize(self): + class X(Structure): + _fields_ = [("item", c_int), + ("array", c_int * 1)] + + self.assertEqual(sizeof(X), sizeof(c_int) * 2) + x = X() + x.item = 42 + x.array[0] = 100 + self.assertEqual(sizeof(x), sizeof(c_int) * 2) + + # make room for one additional item + new_size = sizeof(X) + sizeof(c_int) * 1 + resize(x, new_size) + self.assertEqual(sizeof(x), new_size) + self.assertEqual((x.item, x.array[0]), (42, 100)) + + # make room for 10 additional items + new_size = sizeof(X) + sizeof(c_int) * 9 + resize(x, new_size) + self.assertEqual(sizeof(x), new_size) + self.assertEqual((x.item, x.array[0]), (42, 100)) + + # make room for one additional item + new_size = sizeof(X) + sizeof(c_int) * 1 + resize(x, new_size) + self.assertEqual(sizeof(x), new_size) + self.assertEqual((x.item, x.array[0]), (42, 100)) + + def test_array_invalid_length(self): + # cannot create arrays with non-positive size + self.assertRaises(ValueError, lambda: c_int * -1) + self.assertRaises(ValueError, lambda: c_int * -3) + + def test_zerosized_array(self): + array = (c_int * 0)() + # accessing elements of zero-sized arrays raise IndexError + self.assertRaises(IndexError, array.__setitem__, 0, None) + self.assertRaises(IndexError, array.__getitem__, 0) + self.assertRaises(IndexError, array.__setitem__, 1, None) + self.assertRaises(IndexError, array.__getitem__, 1) + self.assertRaises(IndexError, array.__setitem__, -1, None) + self.assertRaises(IndexError, array.__getitem__, -1) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_win32.py b/Lib/test/test_ctypes/test_win32.py new file mode 100644 index 00000000000..31919118670 --- /dev/null +++ b/Lib/test/test_ctypes/test_win32.py @@ -0,0 +1,152 @@ +# Windows specific tests + +import ctypes +import errno +import sys +import unittest +from ctypes import (CDLL, Structure, POINTER, pointer, sizeof, byref, + _pointer_type_cache, + c_void_p, c_char, c_int, c_long) +from test import support +from test.support import import_helper +from ._support import Py_TPFLAGS_DISALLOW_INSTANTIATION, Py_TPFLAGS_IMMUTABLETYPE + + +@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') +class FunctionCallTestCase(unittest.TestCase): + @unittest.skipUnless('MSC' in sys.version, "SEH only supported by MSC") + @unittest.skipIf(sys.executable.lower().endswith('_d.exe'), + "SEH not enabled in debug builds") + def test_SEH(self): + # Disable faulthandler to prevent logging the warning: + # "Windows fatal exception: access violation" + kernel32 = ctypes.windll.kernel32 + with support.disable_faulthandler(): + # Call functions with invalid arguments, and make sure + # that access violations are trapped and raise an + # exception. + self.assertRaises(OSError, kernel32.GetModuleHandleA, 32) + + def test_noargs(self): + # This is a special case on win32 x64 + user32 = ctypes.windll.user32 + user32.GetDesktopWindow() + + +@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') +class ReturnStructSizesTestCase(unittest.TestCase): + def test_sizes(self): + _ctypes_test = import_helper.import_module("_ctypes_test") + dll = CDLL(_ctypes_test.__file__) + for i in range(1, 11): + fields = [ (f"f{f}", c_char) for f in range(1, i + 1)] + class S(Structure): + _fields_ = fields + f = getattr(dll, f"TestSize{i}") + f.restype = S + res = f() + for i, f in enumerate(fields): + value = getattr(res, f[0]) + expected = bytes([ord('a') + i]) + self.assertEqual(value, expected) + + +@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') +class TestWintypes(unittest.TestCase): + def test_HWND(self): + from ctypes import wintypes + self.assertEqual(sizeof(wintypes.HWND), sizeof(c_void_p)) + + def test_PARAM(self): + from ctypes import wintypes + self.assertEqual(sizeof(wintypes.WPARAM), + sizeof(c_void_p)) + self.assertEqual(sizeof(wintypes.LPARAM), + sizeof(c_void_p)) + + def test_COMError(self): + from _ctypes import COMError + if support.HAVE_DOCSTRINGS: + self.assertEqual(COMError.__doc__, + "Raised when a COM method call failed.") + + ex = COMError(-1, "text", ("details",)) + self.assertEqual(ex.hresult, -1) + self.assertEqual(ex.text, "text") + self.assertEqual(ex.details, ("details",)) + + self.assertEqual(COMError.mro(), + [COMError, Exception, BaseException, object]) + self.assertFalse(COMError.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION) + self.assertTrue(COMError.__flags__ & Py_TPFLAGS_IMMUTABLETYPE) + + +@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') +class TestWinError(unittest.TestCase): + def test_winerror(self): + # see Issue 16169 + ERROR_INVALID_PARAMETER = 87 + msg = ctypes.FormatError(ERROR_INVALID_PARAMETER).strip() + args = (errno.EINVAL, msg, None, ERROR_INVALID_PARAMETER) + + e = ctypes.WinError(ERROR_INVALID_PARAMETER) + self.assertEqual(e.args, args) + self.assertEqual(e.errno, errno.EINVAL) + self.assertEqual(e.winerror, ERROR_INVALID_PARAMETER) + + kernel32 = ctypes.windll.kernel32 + kernel32.SetLastError(ERROR_INVALID_PARAMETER) + try: + raise ctypes.WinError() + except OSError as exc: + e = exc + self.assertEqual(e.args, args) + self.assertEqual(e.errno, errno.EINVAL) + self.assertEqual(e.winerror, ERROR_INVALID_PARAMETER) + + +class Structures(unittest.TestCase): + def test_struct_by_value(self): + class POINT(Structure): + _fields_ = [("x", c_long), + ("y", c_long)] + + class RECT(Structure): + _fields_ = [("left", c_long), + ("top", c_long), + ("right", c_long), + ("bottom", c_long)] + + _ctypes_test = import_helper.import_module("_ctypes_test") + dll = CDLL(_ctypes_test.__file__) + + pt = POINT(15, 25) + left = c_long.in_dll(dll, 'left') + top = c_long.in_dll(dll, 'top') + right = c_long.in_dll(dll, 'right') + bottom = c_long.in_dll(dll, 'bottom') + rect = RECT(left, top, right, bottom) + PointInRect = dll.PointInRect + PointInRect.argtypes = [POINTER(RECT), POINT] + self.assertEqual(1, PointInRect(byref(rect), pt)) + + ReturnRect = dll.ReturnRect + ReturnRect.argtypes = [c_int, RECT, POINTER(RECT), POINT, RECT, + POINTER(RECT), POINT, RECT] + ReturnRect.restype = RECT + for i in range(4): + ret = ReturnRect(i, rect, pointer(rect), pt, rect, + byref(rect), pt, rect) + # the c function will check and modify ret if something is + # passed in improperly + self.assertEqual(ret.left, left.value) + self.assertEqual(ret.right, right.value) + self.assertEqual(ret.top, top.value) + self.assertEqual(ret.bottom, bottom.value) + + # to not leak references, we must clean _pointer_type_cache + del _pointer_type_cache[RECT] + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py new file mode 100644 index 00000000000..8d217fc17ef --- /dev/null +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -0,0 +1,286 @@ +import ctypes +import gc +import sys +import unittest +from ctypes import POINTER, byref, c_void_p +from ctypes.wintypes import BYTE, DWORD, WORD + +if sys.platform != "win32": + raise unittest.SkipTest("Windows-specific test") + + +from _ctypes import COMError, CopyComPointer +from ctypes import HRESULT + + +COINIT_APARTMENTTHREADED = 0x2 +CLSCTX_SERVER = 5 +S_OK = 0 +OUT = 2 +TRUE = 1 +E_NOINTERFACE = -2147467262 + + +class GUID(ctypes.Structure): + # https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid + _fields_ = [ + ("Data1", DWORD), + ("Data2", WORD), + ("Data3", WORD), + ("Data4", BYTE * 8), + ] + + +def create_proto_com_method(name, index, restype, *argtypes): + proto = ctypes.WINFUNCTYPE(restype, *argtypes) + + def make_method(*args): + foreign_func = proto(index, name, *args) + + def call(self, *args, **kwargs): + return foreign_func(self, *args, **kwargs) + + return call + + return make_method + + +def create_guid(name): + guid = GUID() + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-clsidfromstring + ole32.CLSIDFromString(name, byref(guid)) + return guid + + +def is_equal_guid(guid1, guid2): + # https://learn.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-isequalguid + return ole32.IsEqualGUID(byref(guid1), byref(guid2)) + + +ole32 = ctypes.oledll.ole32 + +IID_IUnknown = create_guid("{00000000-0000-0000-C000-000000000046}") +IID_IStream = create_guid("{0000000C-0000-0000-C000-000000000046}") +IID_IPersist = create_guid("{0000010C-0000-0000-C000-000000000046}") +CLSID_ShellLink = create_guid("{00021401-0000-0000-C000-000000000046}") + +# https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void) +proto_query_interface = create_proto_com_method( + "QueryInterface", 0, HRESULT, POINTER(GUID), POINTER(c_void_p) +) +# https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-addref +proto_add_ref = create_proto_com_method("AddRef", 1, ctypes.c_long) +# https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-release +proto_release = create_proto_com_method("Release", 2, ctypes.c_long) +# https://learn.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-ipersist-getclassid +proto_get_class_id = create_proto_com_method( + "GetClassID", 3, HRESULT, POINTER(GUID) +) + + +def create_shelllink_persist(typ): + ppst = typ() + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance + ole32.CoCreateInstance( + byref(CLSID_ShellLink), + None, + CLSCTX_SERVER, + byref(IID_IPersist), + byref(ppst), + ) + return ppst + + +class ForeignFunctionsThatWillCallComMethodsTests(unittest.TestCase): + def setUp(self): + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex + ole32.CoInitializeEx(None, COINIT_APARTMENTTHREADED) + + def tearDown(self): + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-couninitialize + ole32.CoUninitialize() + gc.collect() + + def test_without_paramflags_and_iid(self): + class IUnknown(c_void_p): + QueryInterface = proto_query_interface() + AddRef = proto_add_ref() + Release = proto_release() + + class IPersist(IUnknown): + GetClassID = proto_get_class_id() + + ppst = create_shelllink_persist(IPersist) + + clsid = GUID() + hr_getclsid = ppst.GetClassID(byref(clsid)) + self.assertEqual(S_OK, hr_getclsid) + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + self.assertEqual(2, ppst.AddRef()) + self.assertEqual(3, ppst.AddRef()) + + punk = IUnknown() + hr_qi = ppst.QueryInterface(IID_IUnknown, punk) + self.assertEqual(S_OK, hr_qi) + self.assertEqual(3, punk.Release()) + + with self.assertRaises(OSError) as e: + punk.QueryInterface(IID_IStream, IUnknown()) + self.assertEqual(E_NOINTERFACE, e.exception.winerror) + + self.assertEqual(2, ppst.Release()) + self.assertEqual(1, ppst.Release()) + self.assertEqual(0, ppst.Release()) + + def test_with_paramflags_and_without_iid(self): + class IUnknown(c_void_p): + QueryInterface = proto_query_interface(None) + AddRef = proto_add_ref() + Release = proto_release() + + class IPersist(IUnknown): + GetClassID = proto_get_class_id(((OUT, "pClassID"),)) + + ppst = create_shelllink_persist(IPersist) + + clsid = ppst.GetClassID() + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + punk = IUnknown() + hr_qi = ppst.QueryInterface(IID_IUnknown, punk) + self.assertEqual(S_OK, hr_qi) + self.assertEqual(1, punk.Release()) + + with self.assertRaises(OSError) as e: + ppst.QueryInterface(IID_IStream, IUnknown()) + self.assertEqual(E_NOINTERFACE, e.exception.winerror) + + self.assertEqual(0, ppst.Release()) + + def test_with_paramflags_and_iid(self): + class IUnknown(c_void_p): + QueryInterface = proto_query_interface(None, IID_IUnknown) + AddRef = proto_add_ref() + Release = proto_release() + + class IPersist(IUnknown): + GetClassID = proto_get_class_id(((OUT, "pClassID"),), IID_IPersist) + + ppst = create_shelllink_persist(IPersist) + + clsid = ppst.GetClassID() + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + punk = IUnknown() + hr_qi = ppst.QueryInterface(IID_IUnknown, punk) + self.assertEqual(S_OK, hr_qi) + self.assertEqual(1, punk.Release()) + + with self.assertRaises(COMError) as e: + ppst.QueryInterface(IID_IStream, IUnknown()) + self.assertEqual(E_NOINTERFACE, e.exception.hresult) + + self.assertEqual(0, ppst.Release()) + + +class CopyComPointerTests(unittest.TestCase): + def setUp(self): + ole32.CoInitializeEx(None, COINIT_APARTMENTTHREADED) + + class IUnknown(c_void_p): + QueryInterface = proto_query_interface(None, IID_IUnknown) + AddRef = proto_add_ref() + Release = proto_release() + + class IPersist(IUnknown): + GetClassID = proto_get_class_id(((OUT, "pClassID"),), IID_IPersist) + + self.IUnknown = IUnknown + self.IPersist = IPersist + + def tearDown(self): + ole32.CoUninitialize() + gc.collect() + + def test_both_are_null(self): + src = self.IPersist() + dst = self.IPersist() + + hr = CopyComPointer(src, byref(dst)) + + self.assertEqual(S_OK, hr) + + self.assertIsNone(src.value) + self.assertIsNone(dst.value) + + def test_src_is_nonnull_and_dest_is_null(self): + # The reference count of the COM pointer created by `CoCreateInstance` + # is initially 1. + src = create_shelllink_persist(self.IPersist) + dst = self.IPersist() + + # `CopyComPointer` calls `AddRef` explicitly in the C implementation. + # The refcount of `src` is incremented from 1 to 2 here. + hr = CopyComPointer(src, byref(dst)) + + self.assertEqual(S_OK, hr) + self.assertEqual(src.value, dst.value) + + # This indicates that the refcount was 2 before the `Release` call. + self.assertEqual(1, src.Release()) + + clsid = dst.GetClassID() + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + self.assertEqual(0, dst.Release()) + + def test_src_is_null_and_dest_is_nonnull(self): + src = self.IPersist() + dst_orig = create_shelllink_persist(self.IPersist) + dst = self.IPersist() + CopyComPointer(dst_orig, byref(dst)) + self.assertEqual(1, dst_orig.Release()) + + clsid = dst.GetClassID() + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + # This does NOT affects the refcount of `dst_orig`. + hr = CopyComPointer(src, byref(dst)) + + self.assertEqual(S_OK, hr) + self.assertIsNone(dst.value) + + with self.assertRaises(ValueError): + dst.GetClassID() # NULL COM pointer access + + # This indicates that the refcount was 1 before the `Release` call. + self.assertEqual(0, dst_orig.Release()) + + def test_both_are_nonnull(self): + src = create_shelllink_persist(self.IPersist) + dst_orig = create_shelllink_persist(self.IPersist) + dst = self.IPersist() + CopyComPointer(dst_orig, byref(dst)) + self.assertEqual(1, dst_orig.Release()) + + self.assertEqual(dst.value, dst_orig.value) + self.assertNotEqual(src.value, dst.value) + + hr = CopyComPointer(src, byref(dst)) + + self.assertEqual(S_OK, hr) + self.assertEqual(src.value, dst.value) + self.assertNotEqual(dst.value, dst_orig.value) + + self.assertEqual(1, src.Release()) + + clsid = dst.GetClassID() + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + self.assertEqual(0, dst.Release()) + self.assertEqual(0, dst_orig.Release()) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_wintypes.py b/Lib/test/test_ctypes/test_wintypes.py new file mode 100644 index 00000000000..a04d725a473 --- /dev/null +++ b/Lib/test/test_ctypes/test_wintypes.py @@ -0,0 +1,61 @@ +# See <https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types> +# for reference. +# +# Tests also work on POSIX + +import unittest +from ctypes import POINTER, cast, c_int16 +from ctypes import wintypes + + +class WinTypesTest(unittest.TestCase): + def test_variant_bool(self): + # reads 16-bits from memory, anything non-zero is True + for true_value in (1, 32767, 32768, 65535, 65537): + true = POINTER(c_int16)(c_int16(true_value)) + value = cast(true, POINTER(wintypes.VARIANT_BOOL)) + self.assertEqual(repr(value.contents), 'VARIANT_BOOL(True)') + + vb = wintypes.VARIANT_BOOL() + self.assertIs(vb.value, False) + vb.value = True + self.assertIs(vb.value, True) + vb.value = true_value + self.assertIs(vb.value, True) + + for false_value in (0, 65536, 262144, 2**33): + false = POINTER(c_int16)(c_int16(false_value)) + value = cast(false, POINTER(wintypes.VARIANT_BOOL)) + self.assertEqual(repr(value.contents), 'VARIANT_BOOL(False)') + + # allow any bool conversion on assignment to value + for set_value in (65536, 262144, 2**33): + vb = wintypes.VARIANT_BOOL() + vb.value = set_value + self.assertIs(vb.value, True) + + vb = wintypes.VARIANT_BOOL() + vb.value = [2, 3] + self.assertIs(vb.value, True) + vb.value = [] + self.assertIs(vb.value, False) + + def assertIsSigned(self, ctype): + self.assertLess(ctype(-1).value, 0) + + def assertIsUnsigned(self, ctype): + self.assertGreater(ctype(-1).value, 0) + + def test_signedness(self): + for ctype in (wintypes.BYTE, wintypes.WORD, wintypes.DWORD, + wintypes.BOOLEAN, wintypes.UINT, wintypes.ULONG): + with self.subTest(ctype=ctype): + self.assertIsUnsigned(ctype) + + for ctype in (wintypes.BOOL, wintypes.INT, wintypes.LONG): + with self.subTest(ctype=ctype): + self.assertIsSigned(ctype) + + +if __name__ == "__main__": + unittest.main() From 4a352344b6abf257c4050786f2bfd8036da0f715 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Sat, 20 Dec 2025 17:46:05 +0900 Subject: [PATCH 552/819] mark failing test_ctypes --- Lib/test/test_ctypes/test_cast.py | 2 ++ Lib/test/test_ctypes/test_dlerror.py | 1 + Lib/test/test_ctypes/test_internals.py | 4 ++++ Lib/test/test_ctypes/test_keeprefs.py | 4 ++++ Lib/test/test_ctypes/test_objects.py | 3 ++- Lib/test/test_ctypes/test_python_api.py | 4 ++++ Lib/test/test_ctypes/test_random_things.py | 2 ++ Lib/test/test_ctypes/test_values.py | 4 ++++ Lib/test/test_ctypes/test_win32.py | 2 ++ Lib/test/test_ctypes/test_win32_com_foreign_func.py | 2 ++ Lib/test/test_exceptions.py | 1 + Lib/test/test_os.py | 3 --- 12 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_ctypes/test_cast.py b/Lib/test/test_ctypes/test_cast.py index 604f44f03d6..db6bdc75eff 100644 --- a/Lib/test/test_ctypes/test_cast.py +++ b/Lib/test/test_ctypes/test_cast.py @@ -32,6 +32,8 @@ def test_address2pointer(self): ptr = cast(address, POINTER(c_int)) self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_p2a_objects(self): array = (c_char_p * 5)() self.assertEqual(array._objects, None) diff --git a/Lib/test/test_ctypes/test_dlerror.py b/Lib/test/test_ctypes/test_dlerror.py index 1c1b2aab3d5..cd87bad3825 100644 --- a/Lib/test/test_ctypes/test_dlerror.py +++ b/Lib/test/test_ctypes/test_dlerror.py @@ -54,6 +54,7 @@ class TestNullDlsym(unittest.TestCase): this 'dlsym returned NULL -> throw Error' rule. """ + @unittest.expectedFailure # TODO: RUSTPYTHON def test_null_dlsym(self): import subprocess import tempfile diff --git a/Lib/test/test_ctypes/test_internals.py b/Lib/test/test_ctypes/test_internals.py index 778da6573da..292633aaa4b 100644 --- a/Lib/test/test_ctypes/test_internals.py +++ b/Lib/test/test_ctypes/test_internals.py @@ -27,6 +27,8 @@ def test_ints(self): self.assertEqual(refcnt, sys.getrefcount(i)) self.assertEqual(ci._objects, None) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_c_char_p(self): s = "Hello, World".encode("ascii") refcnt = sys.getrefcount(s) @@ -62,6 +64,8 @@ class Y(Structure): x1.a, x2.b = 42, 93 self.assertEqual(y._objects, {"0": {}, "1": {}}) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_xxx(self): class X(Structure): _fields_ = [("a", c_char_p), ("b", c_char_p)] diff --git a/Lib/test/test_ctypes/test_keeprefs.py b/Lib/test/test_ctypes/test_keeprefs.py index 23b03b64b4a..5aa5b86fa45 100644 --- a/Lib/test/test_ctypes/test_keeprefs.py +++ b/Lib/test/test_ctypes/test_keeprefs.py @@ -12,6 +12,8 @@ def test_cint(self): x = c_int(99) self.assertEqual(x._objects, None) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_ccharp(self): x = c_char_p() self.assertEqual(x._objects, None) @@ -33,6 +35,8 @@ class X(Structure): x.b = 99 self.assertEqual(x._objects, None) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_ccharp_struct(self): class X(Structure): _fields_ = [("a", c_char_p), diff --git a/Lib/test/test_ctypes/test_objects.py b/Lib/test/test_ctypes/test_objects.py index fb01421b955..8db1cd873fd 100644 --- a/Lib/test/test_ctypes/test_objects.py +++ b/Lib/test/test_ctypes/test_objects.py @@ -58,7 +58,8 @@ def load_tests(loader, tests, pattern): - tests.addTest(doctest.DocTestSuite()) + # TODO: RUSTPYTHON - doctest disabled due to null terminator in _objects + # tests.addTest(doctest.DocTestSuite()) return tests diff --git a/Lib/test/test_ctypes/test_python_api.py b/Lib/test/test_ctypes/test_python_api.py index 1072a109833..2e68b35f8af 100644 --- a/Lib/test/test_ctypes/test_python_api.py +++ b/Lib/test/test_ctypes/test_python_api.py @@ -7,6 +7,8 @@ class PythonAPITestCase(unittest.TestCase): + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_PyBytes_FromStringAndSize(self): PyBytes_FromStringAndSize = pythonapi.PyBytes_FromStringAndSize @@ -57,6 +59,8 @@ def test_PyObj_FromPtr(self): del pyobj self.assertEqual(sys.getrefcount(s), ref) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_PyOS_snprintf(self): PyOS_snprintf = pythonapi.PyOS_snprintf PyOS_snprintf.argtypes = POINTER(c_char), c_size_t, c_char_p diff --git a/Lib/test/test_ctypes/test_random_things.py b/Lib/test/test_ctypes/test_random_things.py index 630f6ed9489..3908eca0926 100644 --- a/Lib/test/test_ctypes/test_random_things.py +++ b/Lib/test/test_ctypes/test_random_things.py @@ -70,6 +70,8 @@ def test_FloatDivisionError(self): with self.expect_unraisable(ZeroDivisionError): cb(0.0) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_TypeErrorDivisionError(self): cb = CFUNCTYPE(c_int, c_char_p)(callback_func) err_msg = "unsupported operand type(s) for /: 'int' and 'bytes'" diff --git a/Lib/test/test_ctypes/test_values.py b/Lib/test/test_ctypes/test_values.py index 1b757e020d5..e0d200e2101 100644 --- a/Lib/test/test_ctypes/test_values.py +++ b/Lib/test/test_ctypes/test_values.py @@ -39,6 +39,8 @@ def test_undefined(self): class PythonValuesTestCase(unittest.TestCase): """This test only works when python itself is a dll/shared library""" + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_optimizeflag(self): # This test accesses the Py_OptimizeFlag integer, which is # exported by the Python dll and should match the sys.flags value @@ -46,6 +48,8 @@ def test_optimizeflag(self): opt = c_int.in_dll(pythonapi, "Py_OptimizeFlag").value self.assertEqual(opt, sys.flags.optimize) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_frozentable(self): # Python exports a PyImport_FrozenModules symbol. This is a # pointer to an array of struct _frozen entries. The end of the diff --git a/Lib/test/test_ctypes/test_win32.py b/Lib/test/test_ctypes/test_win32.py index 31919118670..4de4f0379cf 100644 --- a/Lib/test/test_ctypes/test_win32.py +++ b/Lib/test/test_ctypes/test_win32.py @@ -17,6 +17,8 @@ class FunctionCallTestCase(unittest.TestCase): @unittest.skipUnless('MSC' in sys.version, "SEH only supported by MSC") @unittest.skipIf(sys.executable.lower().endswith('_d.exe'), "SEH not enabled in debug builds") + # TODO: RUSTPYTHON - SEH not implemented + @unittest.skipIf("RustPython" in sys.version, "SEH not implemented in RustPython") def test_SEH(self): # Disable faulthandler to prevent logging the warning: # "Windows fatal exception: access violation" diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index 8d217fc17ef..b12e09333bd 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -158,6 +158,8 @@ class IPersist(IUnknown): self.assertEqual(0, ppst.Release()) + # TODO: RUSTPYTHON - COM iid parameter handling not implemented + @unittest.expectedFailure def test_with_paramflags_and_iid(self): class IUnknown(c_void_p): QueryInterface = proto_query_interface(None, IID_IUnknown) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 61f4156dc6d..3db9203602e 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -430,6 +430,7 @@ def test_WindowsError(self): @unittest.skipUnless(sys.platform == 'win32', 'test specific to Windows') + @unittest.expectedFailure # TODO: RUSTPYTHON def test_windows_message(self): """Should fill in unknown error code in Windows error message""" ctypes = import_module('ctypes') diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 939315379f2..bb558524c24 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -939,7 +939,6 @@ def get_file_system(self, path): return buf.value # return None if the filesystem is unknown - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (ModuleNotFoundError: No module named '_ctypes')") def test_large_time(self): # Many filesystems are limited to the year 2038. At least, the test # pass with NTFS filesystem. @@ -2712,12 +2711,10 @@ def _kill(self, sig): os.kill(proc.pid, sig) self.assertEqual(proc.wait(), sig) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (ModuleNotFoundError: No module named '_ctypes')") def test_kill_sigterm(self): # SIGTERM doesn't mean anything special, but make sure it works self._kill(signal.SIGTERM) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (ModuleNotFoundError: No module named '_ctypes')") def test_kill_int(self): # os.kill on Windows can take an int which gets set as the exit code self._kill(100) From 7c7e55ffc4072288f04f41f5cb2068d5953afc79 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Tue, 23 Dec 2025 16:39:04 +0900 Subject: [PATCH 553/819] Upgrade libffi --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- crates/jit/src/lib.rs | 27 ++++++++++++------------- crates/vm/src/stdlib/ctypes.rs | 2 +- crates/vm/src/stdlib/ctypes/array.rs | 8 -------- crates/vm/src/stdlib/ctypes/base.rs | 2 +- crates/vm/src/stdlib/ctypes/function.rs | 2 +- 7 files changed, 21 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3296732cb9..3620786402c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1687,9 +1687,9 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libffi" -version = "4.1.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0feebbe0ccd382a2790f78d380540500d7b78ed7a3498b68fcfbc1593749a94" +checksum = "0444124f3ffd67e1b0b0c661a7f81a278a135eb54aaad4078e79fbc8be50c8a5" dependencies = [ "libc", "libffi-sys", @@ -1697,9 +1697,9 @@ dependencies = [ [[package]] name = "libffi-sys" -version = "3.3.3" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90c6c6e17136d4bc439d43a2f3c6ccf0731cccc016d897473a29791d3c2160c3" +checksum = "3d722da8817ea580d0669da6babe2262d7b86a1af1103da24102b8bb9c101ce7" dependencies = [ "cc", ] diff --git a/Cargo.toml b/Cargo.toml index fad506ddfaa..44f9d3190f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -178,7 +178,7 @@ itertools = "0.14.0" is-macro = "0.3.7" junction = "1.3.0" libc = "0.2.178" -libffi = "4.1" +libffi = "5" log = "0.4.29" nix = { version = "0.30", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] } malachite-bigint = "0.8" diff --git a/crates/jit/src/lib.rs b/crates/jit/src/lib.rs index 91911fd8d14..65ef87a62f6 100644 --- a/crates/jit/src/lib.rs +++ b/crates/jit/src/lib.rs @@ -157,7 +157,7 @@ impl CompiledCode { Ok(unsafe { self.invoke_raw(&cif_args) }) } - unsafe fn invoke_raw(&self, cif_args: &[libffi::middle::Arg]) -> Option<AbiValue> { + unsafe fn invoke_raw(&self, cif_args: &[libffi::middle::Arg<'_>]) -> Option<AbiValue> { unsafe { let cif = self.sig.to_cif(); let value = cif.call::<UnTypedAbiValue>( @@ -219,7 +219,7 @@ pub enum AbiValue { } impl AbiValue { - fn to_libffi_arg(&self) -> libffi::middle::Arg { + fn to_libffi_arg(&self) -> libffi::middle::Arg<'_> { match self { AbiValue::Int(i) => libffi::middle::Arg::new(i), AbiValue::Float(f) => libffi::middle::Arg::new(f), @@ -350,26 +350,25 @@ impl<'a> ArgsBuilder<'a> { } pub fn into_args(self) -> Option<Args<'a>> { - self.values - .iter() - .map(|v| v.as_ref().map(AbiValue::to_libffi_arg)) - .collect::<Option<_>>() - .map(|cif_args| Args { - _values: self.values, - cif_args, - code: self.code, - }) + // Ensure all values are set + if self.values.iter().any(|v| v.is_none()) { + return None; + } + Some(Args { + values: self.values.into_iter().map(|v| v.unwrap()).collect(), + code: self.code, + }) } } pub struct Args<'a> { - _values: Vec<Option<AbiValue>>, - cif_args: Vec<libffi::middle::Arg>, + values: Vec<AbiValue>, code: &'a CompiledCode, } impl Args<'_> { pub fn invoke(&self) -> Option<AbiValue> { - unsafe { self.code.invoke_raw(&self.cif_args) } + let cif_args: Vec<_> = self.values.iter().map(AbiValue::to_libffi_arg).collect(); + unsafe { self.code.invoke_raw(&cif_args) } } } diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 9b922230431..a9c0636bd12 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -1102,7 +1102,7 @@ pub(crate) mod _ctypes { return Err(vm.new_value_error("NULL function pointer")); } - let mut ffi_args: Vec<Arg> = Vec::with_capacity(args.len()); + let mut ffi_args: Vec<Arg<'_>> = Vec::with_capacity(args.len()); let mut arg_values: Vec<isize> = Vec::with_capacity(args.len()); let mut arg_types: Vec<Type> = Vec::with_capacity(args.len()); diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 208b3e3f4d3..f31c8284d8b 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -1058,14 +1058,6 @@ impl PyCArray { } } -impl PyCArray { - #[allow(unused)] - pub fn to_arg(&self, _vm: &VirtualMachine) -> PyResult<libffi::middle::Arg> { - let buffer = self.0.buffer.read(); - Ok(libffi::middle::Arg::new(&*buffer)) - } -} - impl AsBuffer for PyCArray { fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { let buffer_len = zelf.0.buffer.read().len(); diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 44793a21561..0f859b3d10b 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -1821,7 +1821,7 @@ pub enum FfiArgValue { impl FfiArgValue { /// Create an Arg reference to this owned value - pub fn as_arg(&self) -> libffi::middle::Arg { + pub fn as_arg(&self) -> libffi::middle::Arg<'_> { match self { FfiArgValue::U8(v) => libffi::middle::Arg::new(v), FfiArgValue::I8(v) => libffi::middle::Arg::new(v), diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 04ff238ebcf..55a42f0ba15 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -1449,7 +1449,7 @@ enum RawResult { fn ctypes_callproc(code_ptr: CodePtr, arguments: &[Argument], call_info: &CallInfo) -> RawResult { let ffi_arg_types: Vec<Type> = arguments.iter().map(|a| a.ffi_type.clone()).collect(); let cif = Cif::new(ffi_arg_types, call_info.ffi_return_type.clone()); - let ffi_args: Vec<Arg> = arguments.iter().map(|a| a.value.as_arg()).collect(); + let ffi_args: Vec<Arg<'_>> = arguments.iter().map(|a| a.value.as_arg()).collect(); if call_info.restype_is_none { unsafe { cif.call::<()>(code_ptr, &ffi_args) }; From 00205aad1408df75a4b3a5ef648b894760459920 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI <jiseok.dev@gmail.com> Date: Wed, 24 Dec 2025 13:10:57 +0900 Subject: [PATCH 554/819] Bump libsqlite3-sys from 0.28 to 0.36 (#6472) * Bump libsqlite3-sys from 0.28 to 0.36 Update libsqlite3-sys to version 0.36 and adapt to API changes by replacing sqlite3_close_v2 with sqlite3_close. The v2 variant is no longer directly exported in the newer version. Fixes #6471 * Fix clippy --- Cargo.lock | 4 ++-- crates/stdlib/Cargo.toml | 2 +- crates/stdlib/src/sqlite.rs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3620786402c..96fc9aa8d29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1742,9 +1742,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.28.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +checksum = "95b4103cffefa72eb8428cb6b47d6627161e51c2739fc5e3b734584157bc642a" dependencies = [ "cc", "pkg-config", diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 0cd853223e2..4885319ea17 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -132,7 +132,7 @@ oid-registry = { version = "0.8", features = ["x509", "pkcs1", "nist_algs"], opt pkcs8 = { version = "0.10", features = ["encryption", "pkcs5", "pem"], optional = true } [target.'cfg(not(any(target_os = "android", target_arch = "wasm32")))'.dependencies] -libsqlite3-sys = { version = "0.28", features = ["bundled"], optional = true } +libsqlite3-sys = { version = "0.36", features = ["bundled"], optional = true } lzma-sys = "0.1" xz2 = "0.1" diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index ffdc9eb3831..2328f23430a 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -30,7 +30,7 @@ mod _sqlite { sqlite3_bind_null, sqlite3_bind_parameter_count, sqlite3_bind_parameter_name, sqlite3_bind_text, sqlite3_blob, sqlite3_blob_bytes, sqlite3_blob_close, sqlite3_blob_open, sqlite3_blob_read, sqlite3_blob_write, sqlite3_busy_timeout, sqlite3_changes, - sqlite3_close_v2, sqlite3_column_blob, sqlite3_column_bytes, sqlite3_column_count, + sqlite3_close, sqlite3_column_blob, sqlite3_column_bytes, sqlite3_column_count, sqlite3_column_decltype, sqlite3_column_double, sqlite3_column_int64, sqlite3_column_name, sqlite3_column_text, sqlite3_column_type, sqlite3_complete, sqlite3_context, sqlite3_context_db_handle, sqlite3_create_collation_v2, sqlite3_create_function_v2, @@ -1349,14 +1349,14 @@ mod _sqlite { fn set_trace_callback(&self, callable: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { let db = self.db_lock(vm)?; let Some(data) = CallbackData::new(callable, vm) else { - unsafe { sqlite3_trace_v2(db.db, SQLITE_TRACE_STMT as u32, None, null_mut()) }; + unsafe { sqlite3_trace_v2(db.db, SQLITE_TRACE_STMT, None, null_mut()) }; return Ok(()); }; let ret = unsafe { sqlite3_trace_v2( db.db, - SQLITE_TRACE_STMT as u32, + SQLITE_TRACE_STMT, Some(CallbackData::trace_callback), Box::into_raw(Box::new(data)).cast(), ) @@ -2661,7 +2661,7 @@ mod _sqlite { impl Drop for Sqlite { fn drop(&mut self) { - unsafe { sqlite3_close_v2(self.raw.db) }; + unsafe { sqlite3_close(self.raw.db) }; } } From 215c5c6d7baa560e2687055443c3b8c9395d1441 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 24 Dec 2025 13:15:33 +0900 Subject: [PATCH 555/819] signal.pthread_sigmask (#6475) --- crates/vm/src/stdlib/signal.rs | 89 +++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index 6771a950400..d34a681c2da 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -69,6 +69,11 @@ pub(crate) mod _signal { #[pyattr] pub use libc::{SIG_DFL, SIG_IGN}; + // pthread_sigmask 'how' constants + #[cfg(unix)] + #[pyattr] + use libc::{SIG_BLOCK, SIG_SETMASK, SIG_UNBLOCK}; + #[cfg(not(unix))] #[pyattr] pub const SIG_DFL: sighandler_t = 0; @@ -400,14 +405,17 @@ pub(crate) mod _signal { let set = PySet::default().into_ref(&vm.ctx); #[cfg(unix)] { - // On Unix, most signals 1..NSIG are valid + // Use sigfillset to get all valid signals + let mut mask: libc::sigset_t = unsafe { std::mem::zeroed() }; + // SAFETY: mask is a valid pointer + if unsafe { libc::sigfillset(&mut mask) } != 0 { + return Err(vm.new_os_error("sigfillset failed".to_owned())); + } + // Convert the filled mask to a Python set for signum in 1..signal::NSIG { - // Skip signals that cannot be caught - #[cfg(not(target_os = "wasi"))] - if signum == libc::SIGKILL as usize || signum == libc::SIGSTOP as usize { - continue; + if unsafe { libc::sigismember(&mask, signum as i32) } == 1 { + set.add(vm.ctx.new_int(signum as i32).into(), vm)?; } - set.add(vm.ctx.new_int(signum as i32).into(), vm)?; } } #[cfg(windows)] @@ -432,6 +440,75 @@ pub(crate) mod _signal { Ok(set.into()) } + #[cfg(unix)] + fn sigset_to_pyset(mask: &libc::sigset_t, vm: &VirtualMachine) -> PyResult { + use crate::PyPayload; + use crate::builtins::PySet; + let set = PySet::default().into_ref(&vm.ctx); + for signum in 1..signal::NSIG { + // SAFETY: mask is a valid sigset_t + if unsafe { libc::sigismember(mask, signum as i32) } == 1 { + set.add(vm.ctx.new_int(signum as i32).into(), vm)?; + } + } + Ok(set.into()) + } + + #[cfg(unix)] + #[pyfunction] + fn pthread_sigmask( + how: i32, + mask: crate::function::ArgIterable, + vm: &VirtualMachine, + ) -> PyResult { + use crate::convert::IntoPyException; + + // Initialize sigset + let mut sigset: libc::sigset_t = unsafe { std::mem::zeroed() }; + // SAFETY: sigset is a valid pointer + if unsafe { libc::sigemptyset(&mut sigset) } != 0 { + return Err(std::io::Error::last_os_error().into_pyexception(vm)); + } + + // Add signals to the set + for sig in mask.iter(vm)? { + let sig = sig?; + // Convert to i32, handling overflow by returning ValueError + let signum: i32 = sig.try_to_value(vm).map_err(|_| { + vm.new_value_error(format!( + "signal number out of range [1, {}]", + signal::NSIG - 1 + )) + })?; + // Validate signal number is in range [1, NSIG) + if signum < 1 || signum >= signal::NSIG as i32 { + return Err(vm.new_value_error(format!( + "signal number {} out of range [1, {}]", + signum, + signal::NSIG - 1 + ))); + } + // SAFETY: sigset is a valid pointer and signum is validated + if unsafe { libc::sigaddset(&mut sigset, signum) } != 0 { + return Err(std::io::Error::last_os_error().into_pyexception(vm)); + } + } + + // Call pthread_sigmask + let mut old_mask: libc::sigset_t = unsafe { std::mem::zeroed() }; + // SAFETY: all pointers are valid + let err = unsafe { libc::pthread_sigmask(how, &sigset, &mut old_mask) }; + if err != 0 { + return Err(std::io::Error::from_raw_os_error(err).into_pyexception(vm)); + } + + // Check for pending signals + signal::check_signals(vm)?; + + // Convert old mask to Python set + sigset_to_pyset(&old_mask, vm) + } + #[cfg(any(unix, windows))] pub extern "C" fn run_signal(signum: i32) { signal::TRIGGERS[signum as usize].store(true, Ordering::Relaxed); From c763d67ef4d374a26b155e50bd9991ea4b4ab89c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 24 Dec 2025 13:50:26 +0900 Subject: [PATCH 556/819] Fix dict.keys behavior (#6476) --- crates/vm/src/builtins/dict.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 04915b035f8..aff7432d067 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -70,13 +70,21 @@ impl PyDict { Err(other) => other, }; let dict = &self.entries; - if let Some(keys) = vm.get_method(other.clone(), vm.ctx.intern_str("keys")) { - let keys = keys?.call((), vm)?.get_iter(vm)?; - while let PyIterReturn::Return(key) = keys.next(vm)? { - let val = other.get_item(&*key, vm)?; - dict.insert(vm, &*key, val)?; + // Use get_attr to properly invoke __getattribute__ for proxy objects + let keys_result = other.get_attr(vm.ctx.intern_str("keys"), vm); + let has_keys = match keys_result { + Ok(keys_method) => { + let keys = keys_method.call((), vm)?.get_iter(vm)?; + while let PyIterReturn::Return(key) = keys.next(vm)? { + let val = other.get_item(&*key, vm)?; + dict.insert(vm, &*key, val)?; + } + true } - } else { + Err(e) if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => false, + Err(e) => return Err(e), + }; + if !has_keys { let iter = other.get_iter(vm)?; loop { fn err(vm: &VirtualMachine) -> PyBaseExceptionRef { From 014622ac346847883c9fc94793f1dfccb01363bf Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 24 Dec 2025 14:23:33 +0900 Subject: [PATCH 557/819] Fix os.access not to raise exception when path doesn't exist (#6477) * Fix os.access not to raise exception when path doesn't exist * add test --- crates/vm/src/stdlib/posix.rs | 11 ++++++----- extra_tests/snippets/stdlib_os.py | 10 ++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index cfe605733d3..59e41782574 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -405,16 +405,17 @@ pub mod module { ) })?; - let metadata = fs::metadata(&path.path); + let metadata = match fs::metadata(&path.path) { + Ok(m) => m, + // If the file doesn't exist, return False for any access check + Err(_) => return Ok(false), + }; // if it's only checking for F_OK if flags == AccessFlags::F_OK { - return Ok(metadata.is_ok()); + return Ok(true); // File exists } - let metadata = - metadata.map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; - let user_id = metadata.uid(); let group_id = metadata.gid(); let mode = metadata.mode(); diff --git a/extra_tests/snippets/stdlib_os.py b/extra_tests/snippets/stdlib_os.py index a538365f707..d00924e10f2 100644 --- a/extra_tests/snippets/stdlib_os.py +++ b/extra_tests/snippets/stdlib_os.py @@ -518,3 +518,13 @@ def __exit__(self, exc_type, exc_val, exc_tb): if option in ["PC_MAX_CANON", "PC_MAX_INPUT", "PC_VDISABLE"]: continue assert os.pathconf("/", index) == os.pathconf("/", option) + +# os.access - test with empty path and nonexistent files +assert os.access("", os.F_OK) is False +assert os.access("", os.R_OK) is False +assert os.access("", os.W_OK) is False +assert os.access("", os.X_OK) is False +assert os.access("nonexistent_file_12345", os.F_OK) is False +assert os.access("nonexistent_file_12345", os.W_OK) is False +assert os.access("README.md", os.F_OK) is True +assert os.access("README.md", os.R_OK) is True From 309b2ad32d0cebf25144340f4f5f905147a6b532 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 24 Dec 2025 16:54:54 +0900 Subject: [PATCH 558/819] Fix ast end_location (#6478) --- Lib/test/test_ast/test_ast.py | 3 -- crates/vm/src/stdlib/ast.rs | 44 +++++++++++++++++++++++---- crates/vm/src/stdlib/ast/statement.rs | 11 ++++--- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index 09d9444d5d9..628c243f2ff 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -191,7 +191,6 @@ def test_invalid_position_information(self): with self.assertRaises(ValueError): compile(tree, "<string>", "exec") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_compilation_of_ast_nodes_with_default_end_position_values(self): tree = ast.Module( body=[ @@ -212,7 +211,6 @@ def test_compilation_of_ast_nodes_with_default_end_position_values(self): # Check that compilation doesn't crash. Note: this may crash explicitly only on debug mode. compile(tree, "<string>", "exec") - @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: required field "end_lineno" missing from alias def test_negative_locations_for_compile(self): # See https://github.com/python/cpython/issues/130775 alias = ast.alias(name='traceback', lineno=0, col_offset=0) @@ -1725,7 +1723,6 @@ def test_bad_integer(self): compile(mod, "test", "exec") self.assertIn("invalid integer value: None", str(cm.exception)) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_level_as_none(self): body = [ ast.ImportFrom( diff --git a/crates/vm/src/stdlib/ast.rs b/crates/vm/src/stdlib/ast.rs index 00aad0213f3..8e03cb225ea 100644 --- a/crates/vm/src/stdlib/ast.rs +++ b/crates/vm/src/stdlib/ast.rs @@ -160,6 +160,20 @@ fn text_range_to_source_range(source_file: &SourceFile, text_range: TextRange) - } } +fn get_opt_int_field( + vm: &VirtualMachine, + obj: &PyObject, + field: &'static str, +) -> PyResult<Option<PyRefExact<PyInt>>> { + match get_node_field_opt(vm, obj, field)? { + Some(val) => val + .downcast_exact(vm) + .map(Some) + .map_err(|_| vm.new_type_error(format!(r#"field "{field}" must have integer type"#))), + None => Ok(None), + } +} + fn range_from_object( vm: &VirtualMachine, source_file: &SourceFile, @@ -168,17 +182,35 @@ fn range_from_object( ) -> PyResult<TextRange> { let start_row = get_int_field(vm, &object, "lineno", name)?; let start_column = get_int_field(vm, &object, "col_offset", name)?; - let end_row = get_int_field(vm, &object, "end_lineno", name)?; - let end_column = get_int_field(vm, &object, "end_col_offset", name)?; + // end_lineno and end_col_offset are optional, default to start values + let end_row = + get_opt_int_field(vm, &object, "end_lineno")?.unwrap_or_else(|| start_row.clone()); + let end_column = + get_opt_int_field(vm, &object, "end_col_offset")?.unwrap_or_else(|| start_column.clone()); + + // lineno=0 or negative values as a special case (no location info). + // Use default values (line 1, col 0) when lineno <= 0. + let start_row_val: i32 = start_row.try_to_primitive(vm)?; + let end_row_val: i32 = end_row.try_to_primitive(vm)?; + let start_col_val: i32 = start_column.try_to_primitive(vm)?; + let end_col_val: i32 = end_column.try_to_primitive(vm)?; let location = PySourceRange { start: PySourceLocation { - row: Row(OneIndexed::new(start_row.try_to_primitive(vm)?).unwrap()), - column: Column(TextSize::new(start_column.try_to_primitive(vm)?)), + row: Row(if start_row_val > 0 { + OneIndexed::new(start_row_val as usize).unwrap_or(OneIndexed::MIN) + } else { + OneIndexed::MIN + }), + column: Column(TextSize::new(start_col_val.max(0) as u32)), }, end: PySourceLocation { - row: Row(OneIndexed::new(end_row.try_to_primitive(vm)?).unwrap()), - column: Column(TextSize::new(end_column.try_to_primitive(vm)?)), + row: Row(if end_row_val > 0 { + OneIndexed::new(end_row_val as usize).unwrap_or(OneIndexed::MIN) + } else { + OneIndexed::MIN + }), + column: Column(TextSize::new(end_col_val.max(0) as u32)), }, }; diff --git a/crates/vm/src/stdlib/ast/statement.rs b/crates/vm/src/stdlib/ast/statement.rs index 5925ca1fc2a..6d9b35bee79 100644 --- a/crates/vm/src/stdlib/ast/statement.rs +++ b/crates/vm/src/stdlib/ast/statement.rs @@ -1105,10 +1105,13 @@ impl Node for ruff::StmtImportFrom { source_file, get_node_field(vm, &_object, "names", "ImportFrom")?, )?, - level: get_node_field(vm, &_object, "level", "ImportFrom")? - .downcast_exact::<PyInt>(vm) - .unwrap() - .try_to_primitive::<u32>(vm)?, + level: get_node_field_opt(vm, &_object, "level")? + .map(|obj| -> PyResult<u32> { + let int: PyRef<PyInt> = obj.try_into_value(vm)?; + int.try_to_primitive(vm) + }) + .transpose()? + .unwrap_or(0), range: range_from_object(vm, source_file, _object, "ImportFrom")?, }) } From 4f0b940b160759dcef15c759971f730dfc18e3ba Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 24 Dec 2025 17:02:21 +0900 Subject: [PATCH 559/819] impl preexec_fn (#6479) --- Lib/test/test_subprocess.py | 12 ----------- crates/stdlib/src/posixsubprocess.rs | 32 ++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index e04f8b8fcc4..3917c0a76d9 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -2244,8 +2244,6 @@ def test_CalledProcessError_str_non_zero(self): error_string = str(err) self.assertIn("non-zero exit status 2.", error_string) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_preexec(self): # DISCLAIMER: Setting environment variables is *not* a good use # of a preexec_fn. This is merely a test. @@ -2257,8 +2255,6 @@ def test_preexec(self): with p: self.assertEqual(p.stdout.read(), b"apple") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_preexec_exception(self): def raise_it(): raise ValueError("What if two swallows carried a coconut?") @@ -2300,8 +2296,6 @@ def _execute_child(self, *args, **kwargs): for fd in devzero_fds: os.close(fd) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(not os.path.exists("/dev/zero"), "/dev/zero required.") def test_preexec_errpipe_does_not_double_close_pipes(self): """Issue16140: Don't double close pipes on preexec error.""" @@ -2339,8 +2333,6 @@ def test_preexec_gc_module_failure(self): if not enabled: gc.disable() - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf( sys.platform == 'darwin', 'setrlimit() seems to fail on OS X') def test_preexec_fork_failure(self): @@ -2751,8 +2743,6 @@ def test_swap_std_fds_with_one_closed(self): for to_fds in itertools.permutations(range(3), 2): self._check_swap_std_fds_with_one_closed(from_fds, to_fds) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_surrogates_error_message(self): def prepare(): raise ValueError("surrogate:\uDCff") @@ -3228,8 +3218,6 @@ def test_leak_fast_process_del_killed(self): else: self.assertNotIn(ident, [id(o) for o in subprocess._active]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_close_fds_after_preexec(self): fd_status = support.findfile("fd_status.py", subdir="subprocessdata") diff --git a/crates/stdlib/src/posixsubprocess.rs b/crates/stdlib/src/posixsubprocess.rs index 4da6a6858dd..d05b24fd6dd 100644 --- a/crates/stdlib/src/posixsubprocess.rs +++ b/crates/stdlib/src/posixsubprocess.rs @@ -33,9 +33,6 @@ mod _posixsubprocess { #[pyfunction] fn fork_exec(args: ForkExecArgs<'_>, vm: &VirtualMachine) -> PyResult<libc::pid_t> { - if args.preexec_fn.is_some() { - return Err(vm.new_not_implemented_error("preexec_fn not supported yet")); - } let extra_groups = args .groups_list .as_ref() @@ -49,7 +46,7 @@ mod _posixsubprocess { extra_groups: extra_groups.as_deref(), }; match unsafe { nix::unistd::fork() }.map_err(|err| err.into_pyexception(vm))? { - nix::unistd::ForkResult::Child => exec(&args, procargs), + nix::unistd::ForkResult::Child => exec(&args, procargs, vm), nix::unistd::ForkResult::Parent { child } => Ok(child.as_raw()), } } @@ -227,13 +224,19 @@ struct ProcArgs<'a> { extra_groups: Option<&'a [Gid]>, } -fn exec(args: &ForkExecArgs<'_>, procargs: ProcArgs<'_>) -> ! { +fn exec(args: &ForkExecArgs<'_>, procargs: ProcArgs<'_>, vm: &VirtualMachine) -> ! { let mut ctx = ExecErrorContext::NoExec; - match exec_inner(args, procargs, &mut ctx) { + match exec_inner(args, procargs, &mut ctx, vm) { Ok(x) => match x {}, Err(e) => { let mut pipe = args.errpipe_write; - let _ = write!(pipe, "OSError:{}:{}", e as i32, ctx.as_msg()); + if matches!(ctx, ExecErrorContext::PreExec) { + // For preexec_fn errors, use SubprocessError format (errno=0) + let _ = write!(pipe, "SubprocessError:0:{}", ctx.as_msg()); + } else { + // errno is written in hex format + let _ = write!(pipe, "OSError:{:x}:{}", e as i32, ctx.as_msg()); + } std::process::exit(255) } } @@ -242,6 +245,7 @@ fn exec(args: &ForkExecArgs<'_>, procargs: ProcArgs<'_>) -> ! { enum ExecErrorContext { NoExec, ChDir, + PreExec, Exec, } @@ -250,6 +254,7 @@ impl ExecErrorContext { match self { Self::NoExec => "noexec", Self::ChDir => "noexec:chdir", + Self::PreExec => "Exception occurred in preexec_fn.", Self::Exec => "", } } @@ -259,6 +264,7 @@ fn exec_inner( args: &ForkExecArgs<'_>, procargs: ProcArgs<'_>, ctx: &mut ExecErrorContext, + vm: &VirtualMachine, ) -> nix::Result<Never> { for &fd in args.fds_to_keep.as_slice() { if fd.as_raw_fd() != args.errpipe_write.as_raw_fd() { @@ -345,6 +351,18 @@ fn exec_inner( nix::Error::result(ret)?; } + // Call preexec_fn after all process setup but before closing FDs + if let Some(ref preexec_fn) = args.preexec_fn { + match preexec_fn.call((), vm) { + Ok(_) => {} + Err(_e) => { + // Cannot safely stringify exception after fork + *ctx = ExecErrorContext::PreExec; + return Err(Errno::UnknownErrno); + } + } + } + *ctx = ExecErrorContext::Exec; if args.close_fds { From 3d7e521acdc07acd9572692fc5b36873eb5118db Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 24 Dec 2025 17:29:47 +0900 Subject: [PATCH 560/819] introduce slot_wrapper (#4884) --- Lib/test/test_descr.py | 1 - Lib/test/test_inspect/test_inspect.py | 3 +- Lib/test/test_types.py | 2 + Lib/test/test_weakref.py | 2 - crates/vm/src/builtins/descriptor.rs | 204 +++++++++++++++++++++++++- crates/vm/src/builtins/object.rs | 41 +++++- crates/vm/src/builtins/weakref.rs | 24 ++- crates/vm/src/class.rs | 17 ++- crates/vm/src/stdlib/io.rs | 7 +- crates/vm/src/types/slot.rs | 6 +- crates/vm/src/types/zoo.rs | 4 + 11 files changed, 291 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 8c711207fae..88d120a427f 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4941,7 +4941,6 @@ def __init__(self): for o in gc.get_objects(): self.assertIsNot(type(o), X) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_object_new_and_init_with_parameters(self): # See issue #1683368 class OverrideNeither: diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index d0fec18250e..e403ab7b226 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -409,6 +409,7 @@ class NotFuture: pass coro.close(); gen_coro.close() # silence warnings + @unittest.expectedFailure # TODO: RUSTPYTHON def test_isroutine(self): # method self.assertTrue(inspect.isroutine(git.argue)) @@ -1483,8 +1484,6 @@ def test_getfullargspec_definition_order_preserved_on_kwonly(self): l = list(signature.kwonlyargs) self.assertEqual(l, unsorted_keyword_only_parameters) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_classify_newstyle(self): class A(object): diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index fdcb9060e83..7e9b28236c7 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -597,6 +597,7 @@ def test_internal_sizes(self): self.assertGreater(object.__basicsize__, 0) self.assertGreater(tuple.__itemsize__, 0) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_slot_wrapper_types(self): self.assertIsInstance(object.__init__, types.WrapperDescriptorType) self.assertIsInstance(object.__str__, types.WrapperDescriptorType) @@ -611,6 +612,7 @@ def test_dunder_get_signature(self): # gh-93021: Second parameter is optional self.assertIs(sig.parameters["owner"].default, None) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_method_wrapper_types(self): self.assertIsInstance(object().__init__, types.MethodWrapperType) self.assertIsInstance(object().__str__, types.MethodWrapperType) diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index e7cd5962cf9..f47c17b7234 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -906,8 +906,6 @@ def __del__(self): w = Target() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_init(self): # Issue 3634 # <weakref to class>.__init__() doesn't check errors correctly diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index cdcc456edfc..2ccd1dcc0e9 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -3,8 +3,11 @@ use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyTypeRef, builtin_func::PyNativeMethod, type_}, class::PyClassImpl, + common::hash::PyHash, function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue}, - types::{Callable, GetDescriptor, Representable}, + types::{ + Callable, Comparable, GetDescriptor, Hashable, InitFunc, PyComparisonOp, Representable, + }, }; use rustpython_common::lock::PyRwLock; @@ -219,7 +222,7 @@ impl std::fmt::Debug for PyMemberDef { } } -// PyMemberDescrObject in CPython +// = PyMemberDescrObject #[pyclass(name = "member_descriptor", module = false)] #[derive(Debug)] pub struct PyMemberDescriptor { @@ -382,4 +385,201 @@ impl GetDescriptor for PyMemberDescriptor { pub fn init(ctx: &Context) { PyMemberDescriptor::extend_class(ctx, ctx.types.member_descriptor_type); PyMethodDescriptor::extend_class(ctx, ctx.types.method_descriptor_type); + PySlotWrapper::extend_class(ctx, ctx.types.wrapper_descriptor_type); + PyMethodWrapper::extend_class(ctx, ctx.types.method_wrapper_type); +} + +// PySlotWrapper - wrapper_descriptor + +/// wrapper_descriptor: wraps a slot function as a Python method +// = PyWrapperDescrObject +#[pyclass(name = "wrapper_descriptor", module = false)] +#[derive(Debug)] +pub struct PySlotWrapper { + pub typ: &'static Py<PyType>, + pub name: &'static PyStrInterned, + pub wrapped: InitFunc, + pub doc: Option<&'static str>, +} + +impl PyPayload for PySlotWrapper { + fn class(ctx: &Context) -> &'static Py<PyType> { + ctx.types.wrapper_descriptor_type + } +} + +impl GetDescriptor for PySlotWrapper { + fn descr_get( + zelf: PyObjectRef, + obj: Option<PyObjectRef>, + _cls: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult { + match obj { + None => Ok(zelf), + Some(obj) if vm.is_none(&obj) => Ok(zelf), + Some(obj) => { + let zelf = zelf.downcast::<Self>().unwrap(); + Ok(PyMethodWrapper { wrapper: zelf, obj }.into_pyobject(vm)) + } + } + } +} + +impl Callable for PySlotWrapper { + type Args = FuncArgs; + + fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // list.__init__(l, [1,2,3]) form + let (obj, rest): (PyObjectRef, FuncArgs) = args.bind(vm)?; + + if !obj.fast_isinstance(zelf.typ) { + return Err(vm.new_type_error(format!( + "descriptor '{}' requires a '{}' object but received a '{}'", + zelf.name.as_str(), + zelf.typ.name(), + obj.class().name() + ))); + } + + (zelf.wrapped)(obj, rest, vm)?; + Ok(vm.ctx.none()) + } +} + +#[pyclass( + with(GetDescriptor, Callable, Representable), + flags(DISALLOW_INSTANTIATION) +)] +impl PySlotWrapper { + #[pygetset] + fn __name__(&self) -> &'static PyStrInterned { + self.name + } + + #[pygetset] + fn __qualname__(&self) -> String { + format!("{}.{}", self.typ.name(), self.name) + } + + #[pygetset] + fn __objclass__(&self) -> PyTypeRef { + self.typ.to_owned() + } + + #[pygetset] + fn __doc__(&self) -> Option<&'static str> { + self.doc + } +} + +impl Representable for PySlotWrapper { + #[inline] + fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { + Ok(format!( + "<slot wrapper '{}' of '{}' objects>", + zelf.name.as_str(), + zelf.typ.name() + )) + } +} + +// PyMethodWrapper - method-wrapper + +/// method-wrapper: a slot wrapper bound to an instance +/// Returned when accessing l.__init__ on an instance +#[pyclass(name = "method-wrapper", module = false, traverse)] +#[derive(Debug)] +pub struct PyMethodWrapper { + pub wrapper: PyRef<PySlotWrapper>, + #[pytraverse(skip)] + pub obj: PyObjectRef, +} + +impl PyPayload for PyMethodWrapper { + fn class(ctx: &Context) -> &'static Py<PyType> { + ctx.types.method_wrapper_type + } +} + +impl Callable for PyMethodWrapper { + type Args = FuncArgs; + + fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + (zelf.wrapper.wrapped)(zelf.obj.clone(), args, vm)?; + Ok(vm.ctx.none()) + } +} + +#[pyclass( + with(Callable, Representable, Hashable, Comparable), + flags(DISALLOW_INSTANTIATION) +)] +impl PyMethodWrapper { + #[pygetset] + fn __self__(&self) -> PyObjectRef { + self.obj.clone() + } + + #[pygetset] + fn __name__(&self) -> &'static PyStrInterned { + self.wrapper.name + } + + #[pygetset] + fn __objclass__(&self) -> PyTypeRef { + self.wrapper.typ.to_owned() + } + + #[pymethod] + fn __reduce__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult { + let builtins_getattr = vm.builtins.get_attr("getattr", vm)?; + Ok(vm + .ctx + .new_tuple(vec![ + builtins_getattr, + vm.ctx + .new_tuple(vec![ + zelf.obj.clone(), + vm.ctx.new_str(zelf.wrapper.name.as_str()).into(), + ]) + .into(), + ]) + .into()) + } +} + +impl Representable for PyMethodWrapper { + #[inline] + fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { + Ok(format!( + "<method-wrapper '{}' of {} object at {:#x}>", + zelf.wrapper.name.as_str(), + zelf.obj.class().name(), + zelf.obj.get_id() + )) + } +} + +impl Hashable for PyMethodWrapper { + fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> { + let obj_hash = zelf.obj.hash(vm)?; + let wrapper_hash = zelf.wrapper.as_object().get_id() as PyHash; + Ok(obj_hash ^ wrapper_hash) + } +} + +impl Comparable for PyMethodWrapper { + fn cmp( + zelf: &Py<Self>, + other: &PyObject, + op: PyComparisonOp, + vm: &VirtualMachine, + ) -> PyResult<crate::function::PyComparisonValue> { + op.eq_only(|| { + let other = class_or_notimplemented!(Self, other); + let eq = zelf.wrapper.is(&other.wrapper) && vm.bool_eq(&zelf.obj, &other.obj)?; + Ok(eq.into()) + }) + } } diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 0970496c7b1..854cb2701ed 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -118,7 +118,46 @@ impl Constructor for PyBaseObject { impl Initializer for PyBaseObject { type Args = FuncArgs; - fn slot_init(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> { + // object_init: excess_args validation + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + let typ = zelf.class(); + let object_type = &vm.ctx.types.object_type; + + let typ_init = typ.slots.init.load().map(|f| f as usize); + let object_init = object_type.slots.init.load().map(|f| f as usize); + let typ_new = typ.slots.new.load().map(|f| f as usize); + let object_new = object_type.slots.new.load().map(|f| f as usize); + + // For heap types (Python classes), check if __new__ is defined anywhere in MRO + // (before object) because heap types always have slots.new = new_wrapper via MRO + let is_heap_type = typ + .slots + .flags + .contains(crate::types::PyTypeFlags::HEAPTYPE); + let new_overridden = if is_heap_type { + // Check if __new__ is defined in any base class (excluding object) + let new_id = identifier!(vm, __new__); + typ.mro_collect() + .into_iter() + .take_while(|t| !std::ptr::eq(t.as_ref(), *object_type)) + .any(|t| t.attributes.read().contains_key(new_id)) + } else { + // For built-in types, use slot comparison + typ_new != object_new + }; + + // If both __init__ and __new__ are overridden, allow excess args + if typ_init != object_init && new_overridden { + return Ok(()); + } + + // Otherwise, reject excess args + if !args.is_empty() { + return Err(vm.new_type_error(format!( + "{}.__init__() takes exactly one argument (the instance to initialize)", + typ.name() + ))); + } Ok(()) } diff --git a/crates/vm/src/builtins/weakref.rs b/crates/vm/src/builtins/weakref.rs index 88d6dbac3ed..327c0fd1489 100644 --- a/crates/vm/src/builtins/weakref.rs +++ b/crates/vm/src/builtins/weakref.rs @@ -4,10 +4,12 @@ use crate::common::{ hash::{self, PyHash}, }; use crate::{ - AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, + AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, function::{FuncArgs, OptionalArg}, - types::{Callable, Comparable, Constructor, Hashable, PyComparisonOp, Representable}, + types::{ + Callable, Comparable, Constructor, Hashable, Initializer, PyComparisonOp, Representable, + }, }; pub use crate::object::PyWeak; @@ -49,8 +51,24 @@ impl Constructor for PyWeak { } } +impl Initializer for PyWeak { + type Args = WeakNewArgs; + + // weakref_tp_init: accepts args but does nothing (all init done in slot_new) + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + Ok(()) + } +} + #[pyclass( - with(Callable, Hashable, Comparable, Constructor, Representable), + with( + Callable, + Hashable, + Comparable, + Constructor, + Initializer, + Representable + ), flags(BASETYPE) )] impl PyWeak { diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 6a366385702..92e9f6a15be 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -1,7 +1,8 @@ //! Utilities to define a new Python class use crate::{ - builtins::{PyBaseObject, PyType, PyTypeRef}, + PyPayload, + builtins::{PyBaseObject, PyType, PyTypeRef, descriptor::PySlotWrapper}, function::PyMethodDef, object::Py, types::{PyTypeFlags, PyTypeSlots, hash_not_implemented}, @@ -135,6 +136,20 @@ pub trait PyClassImpl: PyClassDef { } } + // Add __init__ slot wrapper if slot exists and not already in dict + if let Some(init_func) = class.slots.init.load() { + let init_name = identifier!(ctx, __init__); + if !class.attributes.read().contains_key(init_name) { + let wrapper = PySlotWrapper { + typ: class, + name: ctx.intern_str("__init__"), + wrapped: init_func, + doc: Some("Initialize self. See help(type(self)) for accurate signature."), + }; + class.set_attr(init_name, wrapper.into_ref(ctx).into()); + } + } + if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize { class.set_attr(ctx.names.__hash__, ctx.none.clone().into()); } diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index ba3576a176c..2c3c02ec65c 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -1464,17 +1464,12 @@ mod _io { #[pyslot] fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { let zelf: PyRef<Self> = zelf.try_into_value(vm)?; - zelf.__init__(args, vm) - } - - #[pymethod] - fn __init__(&self, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { let (raw, BufferSize { buffer_size }): (PyObjectRef, _) = args.bind(vm).map_err(|e| { let msg = format!("{}() {}", Self::CLASS_NAME, *e.__str__(vm)); vm.new_exception_msg(e.class().to_owned(), msg) })?; - self.init(raw, BufferSize { buffer_size }, vm) + zelf.init(raw, BufferSize { buffer_size }, vm) } fn init( diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 5deb593818e..d09f6925eef 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -465,7 +465,10 @@ fn descr_set_wrapper( fn init_wrapper(obj: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { let res = vm.call_special_method(&obj, identifier!(vm, __init__), args)?; if !vm.is_none(&res) { - return Err(vm.new_type_error("__init__ must return None")); + return Err(vm.new_type_error(format!( + "__init__ should return None, not '{:.200}'", + res.class().name() + ))); } Ok(()) } @@ -943,7 +946,6 @@ pub trait Initializer: PyPayload { #[inline] #[pyslot] - #[pymethod(name = "__init__")] fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { #[cfg(debug_assertions)] let class_name_for_debug = zelf.class().name().to_string(); diff --git a/crates/vm/src/types/zoo.rs b/crates/vm/src/types/zoo.rs index d994ff60210..dd4631bc767 100644 --- a/crates/vm/src/types/zoo.rs +++ b/crates/vm/src/types/zoo.rs @@ -94,6 +94,8 @@ pub struct TypeZoo { pub generic_alias_type: &'static Py<PyType>, pub union_type: &'static Py<PyType>, pub member_descriptor_type: &'static Py<PyType>, + pub wrapper_descriptor_type: &'static Py<PyType>, + pub method_wrapper_type: &'static Py<PyType>, // RustPython-original types pub method_def: &'static Py<PyType>, @@ -187,6 +189,8 @@ impl TypeZoo { generic_alias_type: genericalias::PyGenericAlias::init_builtin_type(), union_type: union_::PyUnion::init_builtin_type(), member_descriptor_type: descriptor::PyMemberDescriptor::init_builtin_type(), + wrapper_descriptor_type: descriptor::PySlotWrapper::init_builtin_type(), + method_wrapper_type: descriptor::PyMethodWrapper::init_builtin_type(), method_def: crate::function::HeapMethodDef::init_builtin_type(), } From c4e77287d1be4757c3c67551199d16dc52599997 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:56:47 +0900 Subject: [PATCH 561/819] __hash__ to slot_wrapper (#6480) --- crates/vm/src/builtins/descriptor.rs | 51 ++++++++++++++++++++++++---- crates/vm/src/class.rs | 24 +++++++++++-- crates/vm/src/types/slot.rs | 6 +--- 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index 2ccd1dcc0e9..5cd54bb753d 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -6,7 +6,8 @@ use crate::{ common::hash::PyHash, function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue}, types::{ - Callable, Comparable, GetDescriptor, Hashable, InitFunc, PyComparisonOp, Representable, + Callable, Comparable, GetDescriptor, HashFunc, Hashable, InitFunc, PyComparisonOp, + Representable, }, }; use rustpython_common::lock::PyRwLock; @@ -391,6 +392,44 @@ pub fn init(ctx: &Context) { // PySlotWrapper - wrapper_descriptor +/// Type-erased slot function - mirrors CPython's void* d_wrapped +/// Each variant knows how to call the wrapped function with proper types +#[derive(Clone, Copy)] +pub enum SlotFunc { + Init(InitFunc), + Hash(HashFunc), +} + +impl std::fmt::Debug for SlotFunc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SlotFunc::Init(_) => write!(f, "SlotFunc::Init(...)"), + SlotFunc::Hash(_) => write!(f, "SlotFunc::Hash(...)"), + } + } +} + +impl SlotFunc { + /// Call the wrapped slot function with proper type handling + pub fn call(&self, obj: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + match self { + SlotFunc::Init(func) => { + func(obj, args, vm)?; + Ok(vm.ctx.none()) + } + SlotFunc::Hash(func) => { + if !args.args.is_empty() || !args.kwargs.is_empty() { + return Err( + vm.new_type_error("__hash__() takes no arguments (1 given)".to_owned()) + ); + } + let hash = func(&obj, vm)?; + Ok(vm.ctx.new_int(hash).into()) + } + } + } +} + /// wrapper_descriptor: wraps a slot function as a Python method // = PyWrapperDescrObject #[pyclass(name = "wrapper_descriptor", module = false)] @@ -398,7 +437,7 @@ pub fn init(ctx: &Context) { pub struct PySlotWrapper { pub typ: &'static Py<PyType>, pub name: &'static PyStrInterned, - pub wrapped: InitFunc, + pub wrapped: SlotFunc, pub doc: Option<&'static str>, } @@ -430,7 +469,7 @@ impl Callable for PySlotWrapper { type Args = FuncArgs; fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // list.__init__(l, [1,2,3]) form + // list.__init__(l, [1,2,3]) form - first arg is self let (obj, rest): (PyObjectRef, FuncArgs) = args.bind(vm)?; if !obj.fast_isinstance(zelf.typ) { @@ -442,8 +481,7 @@ impl Callable for PySlotWrapper { ))); } - (zelf.wrapped)(obj, rest, vm)?; - Ok(vm.ctx.none()) + zelf.wrapped.call(obj, rest, vm) } } @@ -506,8 +544,7 @@ impl Callable for PyMethodWrapper { type Args = FuncArgs; fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - (zelf.wrapper.wrapped)(zelf.obj.clone(), args, vm)?; - Ok(vm.ctx.none()) + zelf.wrapper.wrapped.call(zelf.obj.clone(), args, vm) } } diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 92e9f6a15be..f258281712f 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -2,7 +2,10 @@ use crate::{ PyPayload, - builtins::{PyBaseObject, PyType, PyTypeRef, descriptor::PySlotWrapper}, + builtins::{ + PyBaseObject, PyType, PyTypeRef, + descriptor::{PySlotWrapper, SlotFunc}, + }, function::PyMethodDef, object::Py, types::{PyTypeFlags, PyTypeSlots, hash_not_implemented}, @@ -143,13 +146,30 @@ pub trait PyClassImpl: PyClassDef { let wrapper = PySlotWrapper { typ: class, name: ctx.intern_str("__init__"), - wrapped: init_func, + wrapped: SlotFunc::Init(init_func), doc: Some("Initialize self. See help(type(self)) for accurate signature."), }; class.set_attr(init_name, wrapper.into_ref(ctx).into()); } } + // Add __hash__ slot wrapper if slot exists and not already in dict + // Note: hash_not_implemented is handled separately (sets __hash__ = None) + if let Some(hash_func) = class.slots.hash.load() + && hash_func as usize != hash_not_implemented as usize + { + let hash_name = identifier!(ctx, __hash__); + if !class.attributes.read().contains_key(hash_name) { + let wrapper = PySlotWrapper { + typ: class, + name: ctx.intern_str("__hash__"), + wrapped: SlotFunc::Hash(hash_func), + doc: Some("Return hash(self)."), + }; + class.set_attr(hash_name, wrapper.into_ref(ctx).into()); + } + } + if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize { class.set_attr(ctx.names.__hash__, ctx.none.clone().into()); } diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index d09f6925eef..2954d08abf0 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -1098,11 +1098,7 @@ pub trait Hashable: PyPayload { Self::hash(zelf, vm) } - #[inline] - #[pymethod] - fn __hash__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyHash> { - Self::slot_hash(&zelf, vm) - } + // __hash__ is now exposed via SlotFunc::Hash wrapper in extend_class() fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash>; } From 9d1477699ceecc8c57783ca04577f9e9723b5d08 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 24 Dec 2025 10:57:00 +0100 Subject: [PATCH 562/819] Update `test_threading_local.py` from 3.13.11 (#6482) * Update `test_threading_local.py` from 3.13.11 * Mark failing test --- Lib/test/test_threading_local.py | 62 ++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_threading_local.py b/Lib/test/test_threading_local.py index 3443e3875d0..99052de4c7f 100644 --- a/Lib/test/test_threading_local.py +++ b/Lib/test/test_threading_local.py @@ -3,8 +3,8 @@ from doctest import DocTestSuite from test import support from test.support import threading_helper +from test.support.import_helper import import_module import weakref -import gc # Modules under test import _thread @@ -12,6 +12,9 @@ import _threading_local +threading_helper.requires_working_threading(module=True) + + class Weak(object): pass @@ -23,7 +26,7 @@ def target(local, weaklist): class BaseLocalTest: - @unittest.skip("TODO: RUSTPYTHON, flaky test") + @unittest.skip('TODO: RUSTPYTHON; flaky test') def test_local_refs(self): self._local_refs(20) self._local_refs(50) @@ -182,8 +185,7 @@ class LocalSubclass(self._local): """To test that subclasses behave properly.""" self._test_dict_attribute(LocalSubclass) - # TODO: RUSTPYTHON, cycle detection/collection - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; cycle detection/collection def test_cycle_collection(self): class X: pass @@ -197,35 +199,57 @@ class X: self.assertIsNone(wr()) + def test_threading_local_clear_race(self): + # See https://github.com/python/cpython/issues/100892 + + _testcapi = import_module('_testcapi') + _testcapi.call_in_temporary_c_thread(lambda: None, False) + + for _ in range(1000): + _ = threading.local() + + _testcapi.join_temporary_c_thread() + + @support.cpython_only + def test_error(self): + class Loop(self._local): + attr = 1 + + # Trick the "if name == '__dict__':" test of __setattr__() + # to always be true + class NameCompareTrue: + def __eq__(self, other): + return True + + loop = Loop() + with self.assertRaisesRegex(AttributeError, 'Loop.*read-only'): + loop.__setattr__(NameCompareTrue(), 2) + + class ThreadLocalTest(unittest.TestCase, BaseLocalTest): _local = _thread._local - # TODO: RUSTPYTHON, __new__ vs __init__ cooperation - @unittest.expectedFailure - def test_arguments(): - super().test_arguments() - + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: TypeError not raised by _local + def test_arguments(self): + return super().test_arguments() class PyThreadingLocalTest(unittest.TestCase, BaseLocalTest): _local = _threading_local.local -def test_main(): - suite = unittest.TestSuite() - suite.addTest(DocTestSuite('_threading_local')) - suite.addTest(unittest.makeSuite(ThreadLocalTest)) - suite.addTest(unittest.makeSuite(PyThreadingLocalTest)) +def load_tests(loader, tests, pattern): + tests.addTest(DocTestSuite('_threading_local')) local_orig = _threading_local.local def setUp(test): _threading_local.local = _thread._local def tearDown(test): _threading_local.local = local_orig - suite.addTest(DocTestSuite('_threading_local', - setUp=setUp, tearDown=tearDown) - ) + tests.addTests(DocTestSuite('_threading_local', + setUp=setUp, tearDown=tearDown) + ) + return tests - support.run_unittest(suite) if __name__ == '__main__': - test_main() + unittest.main() From 7c3bc5ed8de973236d64e084dcc102f53fddd272 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 24 Dec 2025 11:45:48 +0100 Subject: [PATCH 563/819] Update `test_http_cookies.py` from 3.13.11 (#6481) * Update `test_http_cookies.py` from 3.13.11 * Mark failing test * Update `http/cookies.py` from 3.13.11 * Unmark passing test * Lower amount --- Lib/http/cookies.py | 42 +++++--------- Lib/test/test_http_cookies.py | 102 +++++++++++++++++++++++++++++++--- 2 files changed, 108 insertions(+), 36 deletions(-) diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py index 35ac2dc6ae2..57791c6ab08 100644 --- a/Lib/http/cookies.py +++ b/Lib/http/cookies.py @@ -184,8 +184,13 @@ def _quote(str): return '"' + str.translate(_Translator) + '"' -_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") -_QuotePatt = re.compile(r"[\\].") +_unquote_sub = re.compile(r'\\(?:([0-3][0-7][0-7])|(.))').sub + +def _unquote_replace(m): + if m[1]: + return chr(int(m[1], 8)) + else: + return m[2] def _unquote(str): # If there aren't any doublequotes, @@ -205,36 +210,13 @@ def _unquote(str): # \012 --> \n # \" --> " # - i = 0 - n = len(str) - res = [] - while 0 <= i < n: - o_match = _OctalPatt.search(str, i) - q_match = _QuotePatt.search(str, i) - if not o_match and not q_match: # Neither matched - res.append(str[i:]) - break - # else: - j = k = -1 - if o_match: - j = o_match.start(0) - if q_match: - k = q_match.start(0) - if q_match and (not o_match or k < j): # QuotePatt matched - res.append(str[i:k]) - res.append(str[k+1]) - i = k + 2 - else: # OctalPatt matched - res.append(str[i:j]) - res.append(chr(int(str[j+1:j+4], 8))) - i = j + 4 - return _nulljoin(res) + return _unquote_sub(_unquote_replace, str) # The _getdate() routine is used to set the expiration time in the cookie's HTTP # header. By default, _getdate() returns the current time in the appropriate # "expires" format for a Set-Cookie header. The one optional argument is an # offset from now, in seconds. For example, an offset of -3600 means "one hour -# ago". The offset may be a floating point number. +# ago". The offset may be a floating-point number. # _weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] @@ -442,9 +424,11 @@ def OutputString(self, attrs=None): ( # Optional group: there may not be a value. \s*=\s* # Equal Sign (?P<val> # Start of group 'val' - "(?:[^\\"]|\\.)*" # Any doublequoted string + "(?:[^\\"]|\\.)*" # Any double-quoted string | # or - \w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Special case for "expires" attr + # Special case for "expires" attr + (\w{3,6}day|\w{3}),\s # Day of the week or abbreviated day + [\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Date and time in specific format | # or [""" + _LegalValueChars + r"""]* # Any word or empty string ) # End of group 'val' diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py index 6072c7e15e9..3e0b4d1d5ca 100644 --- a/Lib/test/test_http_cookies.py +++ b/Lib/test/test_http_cookies.py @@ -1,13 +1,15 @@ # Simple test suite for http/cookies.py import copy -from test.support import run_unittest, run_doctest import unittest +import doctest from http import cookies import pickle +from test import support +from test.support.testcase import ExtraAssertions -class CookieTests(unittest.TestCase): +class CookieTests(unittest.TestCase, ExtraAssertions): def test_basic(self): cases = [ @@ -58,6 +60,90 @@ def test_basic(self): for k, v in sorted(case['dict'].items()): self.assertEqual(C[k].value, v) + def test_obsolete_rfc850_date_format(self): + # Test cases with different days and dates in obsolete RFC 850 format + test_cases = [ + # from RFC 850, change EST to GMT + # https://datatracker.ietf.org/doc/html/rfc850#section-2 + { + 'data': 'key=value; expires=Saturday, 01-Jan-83 00:00:00 GMT', + 'output': 'Saturday, 01-Jan-83 00:00:00 GMT' + }, + { + 'data': 'key=value; expires=Friday, 19-Nov-82 16:59:30 GMT', + 'output': 'Friday, 19-Nov-82 16:59:30 GMT' + }, + # from RFC 9110 + # https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.7-6 + { + 'data': 'key=value; expires=Sunday, 06-Nov-94 08:49:37 GMT', + 'output': 'Sunday, 06-Nov-94 08:49:37 GMT' + }, + # other test cases + { + 'data': 'key=value; expires=Wednesday, 09-Nov-94 08:49:37 GMT', + 'output': 'Wednesday, 09-Nov-94 08:49:37 GMT' + }, + { + 'data': 'key=value; expires=Friday, 11-Nov-94 08:49:37 GMT', + 'output': 'Friday, 11-Nov-94 08:49:37 GMT' + }, + { + 'data': 'key=value; expires=Monday, 14-Nov-94 08:49:37 GMT', + 'output': 'Monday, 14-Nov-94 08:49:37 GMT' + }, + ] + + for case in test_cases: + with self.subTest(data=case['data']): + C = cookies.SimpleCookie() + C.load(case['data']) + + # Extract the cookie name from the data string + cookie_name = case['data'].split('=')[0] + + # Check if the cookie is loaded correctly + self.assertIn(cookie_name, C) + self.assertEqual(C[cookie_name].get('expires'), case['output']) + + def test_unquote(self): + cases = [ + (r'a="b=\""', 'b="'), + (r'a="b=\\"', 'b=\\'), + (r'a="b=\="', 'b=='), + (r'a="b=\n"', 'b=n'), + (r'a="b=\042"', 'b="'), + (r'a="b=\134"', 'b=\\'), + (r'a="b=\377"', 'b=\xff'), + (r'a="b=\400"', 'b=400'), + (r'a="b=\42"', 'b=42'), + (r'a="b=\\042"', 'b=\\042'), + (r'a="b=\\134"', 'b=\\134'), + (r'a="b=\\\""', 'b=\\"'), + (r'a="b=\\\042"', 'b=\\"'), + (r'a="b=\134\""', 'b=\\"'), + (r'a="b=\134\042"', 'b=\\"'), + ] + for encoded, decoded in cases: + with self.subTest(encoded): + C = cookies.SimpleCookie() + C.load(encoded) + self.assertEqual(C['a'].value, decoded) + + @support.requires_resource('cpu') + def test_unquote_large(self): + #n = 10**6 + n = 10**4 # XXX: RUSTPYTHON; This takes more than 10 minutes to run. lower to 4 + for encoded in r'\\', r'\134': + with self.subTest(encoded): + data = 'a="b=' + encoded*n + ';"' + C = cookies.SimpleCookie() + C.load(data) + value = C['a'].value + self.assertEqual(value[:3], 'b=\\') + self.assertEqual(value[-2:], '\\;') + self.assertEqual(len(value), n + 3) + def test_load(self): C = cookies.SimpleCookie() C.load('Customer="WILE_E_COYOTE"; Version=1; Path=/acme') @@ -96,7 +182,7 @@ def test_special_attrs(self): C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"') C['Customer']['expires'] = 0 # can't test exact output, it always depends on current date/time - self.assertTrue(C.output().endswith('GMT')) + self.assertEndsWith(C.output(), 'GMT') # loading 'expires' C = cookies.SimpleCookie() @@ -479,9 +565,11 @@ def test_repr(self): r'Set-Cookie: key=coded_val; ' r'expires=\w+, \d+ \w+ \d+ \d+:\d+:\d+ \w+') -def test_main(): - run_unittest(CookieTests, MorselTests) - run_doctest(cookies) + +def load_tests(loader, tests, pattern): + tests.addTest(doctest.DocTestSuite(cookies)) + return tests + if __name__ == '__main__': - test_main() + unittest.main() From a4e60f569e6c1c9c5ff1c89d76e786f2ac08a827 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Wed, 24 Dec 2025 17:48:41 +0900 Subject: [PATCH 564/819] repr --- crates/vm/src/builtins/descriptor.rs | 14 ++++++++++++-- crates/vm/src/class.rs | 14 ++++++++++++++ crates/vm/src/types/slot.rs | 6 ------ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index 5cd54bb753d..c251ac45ee3 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -7,7 +7,7 @@ use crate::{ function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue}, types::{ Callable, Comparable, GetDescriptor, HashFunc, Hashable, InitFunc, PyComparisonOp, - Representable, + Representable, StringifyFunc, }, }; use rustpython_common::lock::PyRwLock; @@ -398,6 +398,7 @@ pub fn init(ctx: &Context) { pub enum SlotFunc { Init(InitFunc), Hash(HashFunc), + Repr(StringifyFunc), } impl std::fmt::Debug for SlotFunc { @@ -405,6 +406,7 @@ impl std::fmt::Debug for SlotFunc { match self { SlotFunc::Init(_) => write!(f, "SlotFunc::Init(...)"), SlotFunc::Hash(_) => write!(f, "SlotFunc::Hash(...)"), + SlotFunc::Repr(_) => write!(f, "SlotFunc::Repr(...)"), } } } @@ -426,6 +428,15 @@ impl SlotFunc { let hash = func(&obj, vm)?; Ok(vm.ctx.new_int(hash).into()) } + SlotFunc::Repr(func) => { + if !args.args.is_empty() || !args.kwargs.is_empty() { + return Err( + vm.new_type_error("__repr__() takes no arguments (1 given)".to_owned()) + ); + } + let s = func(&obj, vm)?; + Ok(s.into()) + } } } } @@ -456,7 +467,6 @@ impl GetDescriptor for PySlotWrapper { ) -> PyResult { match obj { None => Ok(zelf), - Some(obj) if vm.is_none(&obj) => Ok(zelf), Some(obj) => { let zelf = zelf.downcast::<Self>().unwrap(); Ok(PyMethodWrapper { wrapper: zelf, obj }.into_pyobject(vm)) diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index f258281712f..236967dd36e 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -174,6 +174,20 @@ pub trait PyClassImpl: PyClassDef { class.set_attr(ctx.names.__hash__, ctx.none.clone().into()); } + // Add __repr__ slot wrapper if slot exists and not already in dict + if let Some(repr_func) = class.slots.repr.load() { + let repr_name = identifier!(ctx, __repr__); + if !class.attributes.read().contains_key(repr_name) { + let wrapper = PySlotWrapper { + typ: class, + name: ctx.intern_str("__repr__"), + wrapped: SlotFunc::Repr(repr_func), + doc: Some("Return repr(self)."), + }; + class.set_attr(repr_name, wrapper.into_ref(ctx).into()); + } + } + class.extend_methods(class.slots.methods, ctx); } diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 2954d08abf0..6e98da173a4 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -1114,12 +1114,6 @@ pub trait Representable: PyPayload { Self::repr(zelf, vm) } - #[inline] - #[pymethod] - fn __repr__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> { - Self::slot_repr(&zelf, vm) - } - #[inline] fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> { let repr = Self::repr_str(zelf, vm)?; From c2a739319149052f3bc03f873f2e9840571ff541 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Wed, 24 Dec 2025 21:36:42 +0900 Subject: [PATCH 565/819] uniform __str__ --- crates/stdlib/src/ssl/error.rs | 6 +- crates/vm/src/builtins/str.rs | 16 ++-- crates/vm/src/builtins/weakproxy.rs | 4 +- crates/vm/src/exception_group.rs | 6 +- crates/vm/src/exceptions.rs | 138 +++++++++++++++------------- crates/vm/src/stdlib/io.rs | 6 +- crates/vm/src/stdlib/winreg.rs | 16 ++-- 7 files changed, 104 insertions(+), 88 deletions(-) diff --git a/crates/stdlib/src/ssl/error.rs b/crates/stdlib/src/ssl/error.rs index 879275228ec..d77910f6aa1 100644 --- a/crates/stdlib/src/ssl/error.rs +++ b/crates/stdlib/src/ssl/error.rs @@ -5,8 +5,8 @@ pub(crate) use ssl_error::*; #[pymodule(sub)] pub(crate) mod ssl_error { use crate::vm::{ - PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyBaseExceptionRef, PyOSError, PyStrRef}, + Py, PyPayload, PyRef, PyResult, VirtualMachine, + builtins::{PyBaseException, PyOSError, PyStrRef}, types::{Constructor, Initializer}, }; @@ -42,7 +42,7 @@ pub(crate) mod ssl_error { impl PySSLError { // Returns strerror attribute if available, otherwise str(args) #[pymethod] - fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { + fn __str__(exc: &Py<PyBaseException>, vm: &VirtualMachine) -> PyResult<PyStrRef> { use crate::vm::AsObject; // Try to get strerror attribute first (OSError compatibility) if let Ok(strerror) = exc.as_object().get_attr("strerror", vm) diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 8084c4d053e..95b41e6d55c 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -529,7 +529,6 @@ impl Py<PyStr> { #[pyclass( flags(BASETYPE, _MATCH_SELF), with( - PyRef, AsMapping, AsNumber, AsSequence, @@ -1448,15 +1447,16 @@ impl PyStr { fn __getnewargs__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyObjectRef { (zelf.as_str(),).to_pyobject(vm) } -} -#[pyclass] -impl PyRef<PyStr> { #[pymethod] - fn __str__(self, vm: &VirtualMachine) -> PyRefExact<PyStr> { - self.into_exact_or(&vm.ctx, |zelf| { - PyStr::from(zelf.data.clone()).into_exact_ref(&vm.ctx) - }) + fn __str__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> { + if zelf.class().is(vm.ctx.types.str_type) { + // Already exact str, just return a reference + Ok(zelf.to_owned()) + } else { + // Subclass, create a new exact str + Ok(PyStr::from(zelf.data.clone()).into_ref(&vm.ctx)) + } } } diff --git a/crates/vm/src/builtins/weakproxy.rs b/crates/vm/src/builtins/weakproxy.rs index a9221ec876f..6e0e8308dbc 100644 --- a/crates/vm/src/builtins/weakproxy.rs +++ b/crates/vm/src/builtins/weakproxy.rs @@ -79,8 +79,8 @@ impl PyWeakProxy { } #[pymethod] - fn __str__(&self, vm: &VirtualMachine) -> PyResult<PyStrRef> { - self.try_upgrade(vm)?.str(vm) + fn __str__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> { + zelf.try_upgrade(vm)?.str(vm) } fn len(&self, vm: &VirtualMachine) -> PyResult<usize> { diff --git a/crates/vm/src/exception_group.rs b/crates/vm/src/exception_group.rs index e19dbceb8da..645a3e779ff 100644 --- a/crates/vm/src/exception_group.rs +++ b/crates/vm/src/exception_group.rs @@ -182,7 +182,7 @@ pub(super) mod types { } #[pymethod] - fn __str__(zelf: PyRef<PyBaseException>, vm: &VirtualMachine) -> PyResult<String> { + fn __str__(zelf: &Py<PyBaseException>, vm: &VirtualMachine) -> PyResult<PyStrRef> { let message = zelf .get_arg(0) .map(|m| m.str(vm)) @@ -196,10 +196,10 @@ pub(super) mod types { .unwrap_or(0); let suffix = if num_excs == 1 { "" } else { "s" }; - Ok(format!( + Ok(vm.ctx.new_str(format!( "{} ({} sub-exception{})", message, num_excs, suffix - )) + ))) } #[pymethod] diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 2c36aa13bd5..3a932a5df54 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -560,7 +560,7 @@ impl PyBaseException { } #[pyclass( - with(PyRef, Constructor, Initializer, Representable), + with(Py, PyRef, Constructor, Initializer, Representable), flags(BASETYPE, HAS_DICT) )] impl PyBaseException { @@ -633,15 +633,18 @@ impl PyBaseException { fn set_suppress_context(&self, suppress_context: bool) { self.suppress_context.store(suppress_context); } +} +#[pyclass] +impl Py<PyBaseException> { #[pymethod] - pub(super) fn __str__(&self, vm: &VirtualMachine) -> PyStrRef { + pub(super) fn __str__(&self, vm: &VirtualMachine) -> PyResult<PyStrRef> { let str_args = vm.exception_args_as_string(self.args(), true); - match str_args.into_iter().exactly_one() { + Ok(match str_args.into_iter().exactly_one() { Err(i) if i.len() == 0 => vm.ctx.empty_str.to_owned(), Ok(s) => s, Err(i) => PyStr::from(format!("({})", i.format(", "))).into_ref(&vm.ctx), - } + }) } } @@ -1527,16 +1530,16 @@ pub(super) mod types { #[pyexception] impl PyKeyError { #[pymethod] - fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyStrRef { - let args = exc.args(); - if args.len() == 1 { + fn __str__(zelf: &Py<PyBaseException>, vm: &VirtualMachine) -> PyResult<PyStrRef> { + let args = zelf.args(); + Ok(if args.len() == 1 { vm.exception_args_as_string(args, false) .into_iter() .exactly_one() .unwrap() } else { - exc.__str__(vm) - } + zelf.__str__(vm)? + }) } } @@ -1731,8 +1734,8 @@ pub(super) mod types { #[pyexception(with(Constructor, Initializer))] impl PyOSError { #[pymethod] - fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { - let obj = exc.as_object().to_owned(); + fn __str__(zelf: &Py<PyBaseException>, vm: &VirtualMachine) -> PyResult<PyStrRef> { + let obj = zelf.as_object(); // Get OSError fields directly let errno_field = obj.get_attr("errno", vm).ok().filter(|v| !vm.is_none(v)); @@ -1819,7 +1822,7 @@ pub(super) mod types { } // fallback to BaseException.__str__ - Ok(exc.__str__(vm)) + zelf.__str__(vm) } #[pymethod] @@ -2026,7 +2029,7 @@ pub(super) mod types { #[pyexception(with(Initializer))] impl PySyntaxError { #[pymethod] - fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyStrRef { + fn __str__(zelf: &Py<PyBaseException>, vm: &VirtualMachine) -> PyResult<PyStrRef> { fn basename(filename: &str) -> &str { let splitted = if cfg!(windows) { filename.rsplit(&['/', '\\']).next() @@ -2036,16 +2039,16 @@ pub(super) mod types { splitted.unwrap_or(filename) } - let maybe_lineno = exc.as_object().get_attr("lineno", vm).ok().map(|obj| { + let maybe_lineno = zelf.as_object().get_attr("lineno", vm).ok().map(|obj| { obj.str(vm) .unwrap_or_else(|_| vm.ctx.new_str("<lineno str() failed>")) }); - let maybe_filename = exc.as_object().get_attr("filename", vm).ok().map(|obj| { + let maybe_filename = zelf.as_object().get_attr("filename", vm).ok().map(|obj| { obj.str(vm) .unwrap_or_else(|_| vm.ctx.new_str("<filename str() failed>")) }); - let args = exc.args(); + let args = zelf.args(); let msg = if args.len() == 1 { vm.exception_args_as_string(args, false) @@ -2053,7 +2056,7 @@ pub(super) mod types { .exactly_one() .unwrap() } else { - return exc.__str__(vm); + return zelf.__str__(vm); }; let msg_with_location_info: String = match (maybe_lineno, maybe_filename) { @@ -2069,7 +2072,7 @@ pub(super) mod types { (None, None) => msg.to_string(), }; - vm.ctx.new_str(msg_with_location_info) + Ok(vm.ctx.new_str(msg_with_location_info)) } } @@ -2178,29 +2181,32 @@ pub(super) mod types { #[pyexception(with(Initializer))] impl PyUnicodeDecodeError { #[pymethod] - fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<String> { - let Ok(object) = exc.as_object().get_attr("object", vm) else { - return Ok("".to_owned()); + fn __str__(zelf: &Py<PyBaseException>, vm: &VirtualMachine) -> PyResult<PyStrRef> { + let Ok(object) = zelf.as_object().get_attr("object", vm) else { + return Ok(vm.ctx.empty_str.to_owned()); }; let object: ArgBytesLike = object.try_into_value(vm)?; - let encoding: PyStrRef = exc + let encoding: PyStrRef = zelf .as_object() .get_attr("encoding", vm)? .try_into_value(vm)?; - let start: usize = exc.as_object().get_attr("start", vm)?.try_into_value(vm)?; - let end: usize = exc.as_object().get_attr("end", vm)?.try_into_value(vm)?; - let reason: PyStrRef = exc.as_object().get_attr("reason", vm)?.try_into_value(vm)?; - if start < object.len() && end <= object.len() && end == start + 1 { + let start: usize = zelf.as_object().get_attr("start", vm)?.try_into_value(vm)?; + let end: usize = zelf.as_object().get_attr("end", vm)?.try_into_value(vm)?; + let reason: PyStrRef = zelf + .as_object() + .get_attr("reason", vm)? + .try_into_value(vm)?; + Ok(vm.ctx.new_str(if start < object.len() && end <= object.len() && end == start + 1 { let b = object.borrow_buf()[start]; - Ok(format!( + format!( "'{encoding}' codec can't decode byte {b:#02x} in position {start}: {reason}" - )) + ) } else { - Ok(format!( + format!( "'{encoding}' codec can't decode bytes in position {start}-{}: {reason}", end - 1, - )) - } + ) + })) } } @@ -2232,30 +2238,33 @@ pub(super) mod types { #[pyexception(with(Initializer))] impl PyUnicodeEncodeError { #[pymethod] - fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<String> { - let Ok(object) = exc.as_object().get_attr("object", vm) else { - return Ok("".to_owned()); + fn __str__(zelf: &Py<PyBaseException>, vm: &VirtualMachine) -> PyResult<PyStrRef> { + let Ok(object) = zelf.as_object().get_attr("object", vm) else { + return Ok(vm.ctx.empty_str.to_owned()); }; let object: PyStrRef = object.try_into_value(vm)?; - let encoding: PyStrRef = exc + let encoding: PyStrRef = zelf .as_object() .get_attr("encoding", vm)? .try_into_value(vm)?; - let start: usize = exc.as_object().get_attr("start", vm)?.try_into_value(vm)?; - let end: usize = exc.as_object().get_attr("end", vm)?.try_into_value(vm)?; - let reason: PyStrRef = exc.as_object().get_attr("reason", vm)?.try_into_value(vm)?; - if start < object.char_len() && end <= object.char_len() && end == start + 1 { + let start: usize = zelf.as_object().get_attr("start", vm)?.try_into_value(vm)?; + let end: usize = zelf.as_object().get_attr("end", vm)?.try_into_value(vm)?; + let reason: PyStrRef = zelf + .as_object() + .get_attr("reason", vm)? + .try_into_value(vm)?; + Ok(vm.ctx.new_str(if start < object.char_len() && end <= object.char_len() && end == start + 1 { let ch = object.as_wtf8().code_points().nth(start).unwrap(); - Ok(format!( + format!( "'{encoding}' codec can't encode character '{}' in position {start}: {reason}", UnicodeEscapeCodepoint(ch) - )) + ) } else { - Ok(format!( + format!( "'{encoding}' codec can't encode characters in position {start}-{}: {reason}", end - 1, - )) - } + ) + })) } } @@ -2286,26 +2295,31 @@ pub(super) mod types { #[pyexception(with(Initializer))] impl PyUnicodeTranslateError { #[pymethod] - fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<String> { - let Ok(object) = exc.as_object().get_attr("object", vm) else { - return Ok("".to_owned()); + fn __str__(zelf: &Py<PyBaseException>, vm: &VirtualMachine) -> PyResult<PyStrRef> { + let Ok(object) = zelf.as_object().get_attr("object", vm) else { + return Ok(vm.ctx.empty_str.to_owned()); }; let object: PyStrRef = object.try_into_value(vm)?; - let start: usize = exc.as_object().get_attr("start", vm)?.try_into_value(vm)?; - let end: usize = exc.as_object().get_attr("end", vm)?.try_into_value(vm)?; - let reason: PyStrRef = exc.as_object().get_attr("reason", vm)?.try_into_value(vm)?; - if start < object.char_len() && end <= object.char_len() && end == start + 1 { - let ch = object.as_wtf8().code_points().nth(start).unwrap(); - Ok(format!( - "can't translate character '{}' in position {start}: {reason}", - UnicodeEscapeCodepoint(ch) - )) - } else { - Ok(format!( - "can't translate characters in position {start}-{}: {reason}", - end - 1, - )) - } + let start: usize = zelf.as_object().get_attr("start", vm)?.try_into_value(vm)?; + let end: usize = zelf.as_object().get_attr("end", vm)?.try_into_value(vm)?; + let reason: PyStrRef = zelf + .as_object() + .get_attr("reason", vm)? + .try_into_value(vm)?; + Ok(vm.ctx.new_str( + if start < object.char_len() && end <= object.char_len() && end == start + 1 { + let ch = object.as_wtf8().code_points().nth(start).unwrap(); + format!( + "can't translate character '{}' in position {start}: {reason}", + UnicodeEscapeCodepoint(ch) + ) + } else { + format!( + "can't translate characters in position {start}-{}: {reason}", + end - 1, + ) + }, + )) } } diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 2c3c02ec65c..9402660a86e 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -1466,7 +1466,11 @@ mod _io { let zelf: PyRef<Self> = zelf.try_into_value(vm)?; let (raw, BufferSize { buffer_size }): (PyObjectRef, _) = args.bind(vm).map_err(|e| { - let msg = format!("{}() {}", Self::CLASS_NAME, *e.__str__(vm)); + let str_repr = e + .__str__(vm) + .map(|s| s.as_str().to_owned()) + .unwrap_or_else(|_| "<error getting exception str>".to_owned()); + let msg = format!("{}() {}", Self::CLASS_NAME, str_repr); vm.new_exception_msg(e.class().to_owned(), msg) })?; zelf.init(raw, BufferSize { buffer_size }, vm) diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index a7619025866..b5e568fce6d 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -9,7 +9,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { #[pymodule] mod winreg { - use crate::builtins::{PyInt, PyTuple, PyTypeRef}; + use crate::builtins::{PyInt, PyStr, PyTuple, PyTypeRef}; use crate::common::hash::PyHash; use crate::common::windows::ToWideString; use crate::convert::TryFromObject; @@ -233,8 +233,8 @@ mod winreg { } #[pymethod] - fn __str__(&self) -> String { - format!("<PyHkey:{:p}>", self.hkey.load()) + fn __str__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> { + Ok(vm.ctx.new_str(format!("<PyHkey:{:p}>", zelf.hkey.load()))) } } @@ -1029,7 +1029,7 @@ mod winreg { return Ok(Some(vec![0u8, 0u8])); } let s = value - .downcast::<crate::builtins::PyStr>() + .downcast::<PyStr>() .map_err(|_| vm.new_type_error("value must be a string".to_string()))?; let wide = s.as_str().to_wide_with_nul(); // Convert Vec<u16> to Vec<u8> @@ -1047,11 +1047,9 @@ mod winreg { let mut bytes: Vec<u8> = Vec::new(); for item in list.borrow_vec().iter() { - let s = item - .downcast_ref::<crate::builtins::PyStr>() - .ok_or_else(|| { - vm.new_type_error("list items must be strings".to_string()) - })?; + let s = item.downcast_ref::<PyStr>().ok_or_else(|| { + vm.new_type_error("list items must be strings".to_string()) + })?; let wide = s.as_str().to_wide_with_nul(); bytes.extend(wide.iter().flat_map(|&c| c.to_le_bytes())); } From cbde5ce3218066e89a61f42dd429ab8c2fe77321 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 24 Dec 2025 23:35:35 +0900 Subject: [PATCH 566/819] iter with slot-wrapper (#6488) --- crates/vm/src/builtins/descriptor.rs | 25 +++++++- crates/vm/src/class.rs | 86 ++++++++++++++-------------- crates/vm/src/types/slot.rs | 17 ++---- 3 files changed, 69 insertions(+), 59 deletions(-) diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index c251ac45ee3..fcf40d0082d 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -4,10 +4,11 @@ use crate::{ builtins::{PyTypeRef, builtin_func::PyNativeMethod, type_}, class::PyClassImpl, common::hash::PyHash, + convert::ToPyResult, function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue}, types::{ - Callable, Comparable, GetDescriptor, HashFunc, Hashable, InitFunc, PyComparisonOp, - Representable, StringifyFunc, + Callable, Comparable, GetDescriptor, HashFunc, Hashable, InitFunc, IterFunc, IterNextFunc, + PyComparisonOp, Representable, StringifyFunc, }, }; use rustpython_common::lock::PyRwLock; @@ -399,6 +400,8 @@ pub enum SlotFunc { Init(InitFunc), Hash(HashFunc), Repr(StringifyFunc), + Iter(IterFunc), + IterNext(IterNextFunc), } impl std::fmt::Debug for SlotFunc { @@ -407,6 +410,8 @@ impl std::fmt::Debug for SlotFunc { SlotFunc::Init(_) => write!(f, "SlotFunc::Init(...)"), SlotFunc::Hash(_) => write!(f, "SlotFunc::Hash(...)"), SlotFunc::Repr(_) => write!(f, "SlotFunc::Repr(...)"), + SlotFunc::Iter(_) => write!(f, "SlotFunc::Iter(...)"), + SlotFunc::IterNext(_) => write!(f, "SlotFunc::IterNext(...)"), } } } @@ -437,6 +442,22 @@ impl SlotFunc { let s = func(&obj, vm)?; Ok(s.into()) } + SlotFunc::Iter(func) => { + if !args.args.is_empty() || !args.kwargs.is_empty() { + return Err( + vm.new_type_error("__iter__() takes no arguments (1 given)".to_owned()) + ); + } + func(obj, vm) + } + SlotFunc::IterNext(func) => { + if !args.args.is_empty() || !args.kwargs.is_empty() { + return Err( + vm.new_type_error("__next__() takes no arguments (1 given)".to_owned()) + ); + } + func(&obj, vm).to_pyresult(vm) + } } } } diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 236967dd36e..1addf00497d 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -139,52 +139,50 @@ pub trait PyClassImpl: PyClassDef { } } - // Add __init__ slot wrapper if slot exists and not already in dict - if let Some(init_func) = class.slots.init.load() { - let init_name = identifier!(ctx, __init__); - if !class.attributes.read().contains_key(init_name) { - let wrapper = PySlotWrapper { - typ: class, - name: ctx.intern_str("__init__"), - wrapped: SlotFunc::Init(init_func), - doc: Some("Initialize self. See help(type(self)) for accurate signature."), - }; - class.set_attr(init_name, wrapper.into_ref(ctx).into()); - } - } - - // Add __hash__ slot wrapper if slot exists and not already in dict - // Note: hash_not_implemented is handled separately (sets __hash__ = None) - if let Some(hash_func) = class.slots.hash.load() - && hash_func as usize != hash_not_implemented as usize - { - let hash_name = identifier!(ctx, __hash__); - if !class.attributes.read().contains_key(hash_name) { - let wrapper = PySlotWrapper { - typ: class, - name: ctx.intern_str("__hash__"), - wrapped: SlotFunc::Hash(hash_func), - doc: Some("Return hash(self)."), - }; - class.set_attr(hash_name, wrapper.into_ref(ctx).into()); - } - } - - if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize { - class.set_attr(ctx.names.__hash__, ctx.none.clone().into()); + // Add slot wrappers for slots that exist and are not already in dict + // This mirrors CPython's add_operators() in typeobject.c + macro_rules! add_slot_wrapper { + ($slot:ident, $name:ident, $variant:ident, $doc:expr) => { + if let Some(func) = class.slots.$slot.load() { + let attr_name = identifier!(ctx, $name); + if !class.attributes.read().contains_key(attr_name) { + let wrapper = PySlotWrapper { + typ: class, + name: ctx.intern_str(stringify!($name)), + wrapped: SlotFunc::$variant(func), + doc: Some($doc), + }; + class.set_attr(attr_name, wrapper.into_ref(ctx).into()); + } + } + }; } - // Add __repr__ slot wrapper if slot exists and not already in dict - if let Some(repr_func) = class.slots.repr.load() { - let repr_name = identifier!(ctx, __repr__); - if !class.attributes.read().contains_key(repr_name) { - let wrapper = PySlotWrapper { - typ: class, - name: ctx.intern_str("__repr__"), - wrapped: SlotFunc::Repr(repr_func), - doc: Some("Return repr(self)."), - }; - class.set_attr(repr_name, wrapper.into_ref(ctx).into()); + add_slot_wrapper!( + init, + __init__, + Init, + "Initialize self. See help(type(self)) for accurate signature." + ); + add_slot_wrapper!(repr, __repr__, Repr, "Return repr(self)."); + add_slot_wrapper!(iter, __iter__, Iter, "Implement iter(self)."); + add_slot_wrapper!(iternext, __next__, IterNext, "Implement next(self)."); + + // __hash__ needs special handling: hash_not_implemented sets __hash__ = None + if let Some(hash_func) = class.slots.hash.load() { + if hash_func as usize == hash_not_implemented as usize { + class.set_attr(ctx.names.__hash__, ctx.none.clone().into()); + } else { + let hash_name = identifier!(ctx, __hash__); + if !class.attributes.read().contains_key(hash_name) { + let wrapper = PySlotWrapper { + typ: class, + name: ctx.intern_str("__hash__"), + wrapped: SlotFunc::Hash(hash_func), + doc: Some("Return hash(self)."), + }; + class.set_attr(hash_name, wrapper.into_ref(ctx).into()); + } } } diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 6e98da173a4..26c059067e0 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -6,7 +6,7 @@ use crate::{ builtins::{PyInt, PyStr, PyStrInterned, PyStrRef, PyType, PyTypeRef, type_::PointerSlot}, bytecode::ComparisonOperator, common::hash::PyHash, - convert::{ToPyObject, ToPyResult}, + convert::ToPyObject, function::{ Either, FromArgs, FuncArgs, OptionalArg, PyComparisonValue, PyMethodDef, PySetterValue, }, @@ -1435,10 +1435,7 @@ pub trait Iterable: PyPayload { Self::iter(zelf, vm) } - #[pymethod] - fn __iter__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Self::slot_iter(zelf, vm) - } + // __iter__ is exposed via SlotFunc::Iter wrapper in extend_class() fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult; @@ -1458,11 +1455,7 @@ pub trait IterNext: PyPayload + Iterable { fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn>; - #[inline] - #[pymethod] - fn __next__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Self::slot_iternext(&zelf, vm).to_pyresult(vm) - } + // __next__ is exposed via SlotFunc::IterNext wrapper in extend_class() } pub trait SelfIter: PyPayload {} @@ -1477,9 +1470,7 @@ where unreachable!("slot must be overridden for {}", repr.as_str()); } - fn __iter__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self_iter(zelf, vm) - } + // __iter__ is exposed via SlotFunc::Iter wrapper in extend_class() #[cold] fn iter(_zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult { From be9e44aafbb5410adc26fe582613bc24dd6da277 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:08:18 +0900 Subject: [PATCH 567/819] Allow SyntaxError.msg to be writable and reflected in string formatting (#6493) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- DEVELOPMENT.md | 3 +- crates/vm/src/exceptions.rs | 45 ++++++++++++++++------ extra_tests/snippets/builtin_exceptions.py | 7 ++++ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index aa7d99eef33..d5c675faca6 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -130,7 +130,8 @@ repository's structure: - `stdlib`: Standard library parts implemented in rust. - `src`: using the other subcrates to bring rustpython to life. - `wasm`: Binary crate and resources for WebAssembly build -- `extra_tests`: extra integration test snippets as a supplement to `Lib/test` +- `extra_tests`: extra integration test snippets as a supplement to `Lib/test`. + Add new RustPython-only regression tests here; do not place new tests under `Lib/test`. ## Understanding Internals diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 3a932a5df54..8d6ce6142b4 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -9,7 +9,7 @@ use crate::{ }, class::{PyClassImpl, StaticType}, convert::{ToPyException, ToPyObject}, - function::{ArgIterable, FuncArgs, IntoFuncArgs}, + function::{ArgIterable, FuncArgs, IntoFuncArgs, PySetterValue}, py_io::{self, Write}, stdlib::sys, suggestion::offer_suggestions, @@ -994,7 +994,12 @@ impl ExceptionZoo { extend_exception!(PyRecursionError, ctx, excs.recursion_error); extend_exception!(PySyntaxError, ctx, excs.syntax_error, { - "msg" => ctx.new_readonly_getset("msg", excs.syntax_error, make_arg_getter(0)), + "msg" => ctx.new_static_getset( + "msg", + excs.syntax_error, + make_arg_getter(0), + syntax_error_set_msg, + ), // TODO: members "filename" => ctx.none(), "lineno" => ctx.none(), @@ -1041,6 +1046,25 @@ fn make_arg_getter(idx: usize) -> impl Fn(PyBaseExceptionRef) -> Option<PyObject move |exc| exc.get_arg(idx) } +fn syntax_error_set_msg( + exc: PyBaseExceptionRef, + value: PySetterValue, + vm: &VirtualMachine, +) -> PyResult<()> { + let mut args = exc.args.write(); + let mut new_args = args.as_slice().to_vec(); + // Ensure the message slot at index 0 always exists for SyntaxError.args. + if new_args.is_empty() { + new_args.push(vm.ctx.none()); + } + match value { + PySetterValue::Assign(value) => new_args[0] = value, + PySetterValue::Delete => new_args[0] = vm.ctx.none(), + } + *args = PyTuple::new_ref(new_args, &vm.ctx); + Ok(()) +} + fn system_exit_code(exc: PyBaseExceptionRef) -> Option<PyObjectRef> { exc.args.read().first().map(|code| { match_class!(match code { @@ -2048,15 +2072,14 @@ pub(super) mod types { .unwrap_or_else(|_| vm.ctx.new_str("<filename str() failed>")) }); - let args = zelf.args(); - - let msg = if args.len() == 1 { - vm.exception_args_as_string(args, false) - .into_iter() - .exactly_one() - .unwrap() - } else { - return zelf.__str__(vm); + let msg = match zelf.as_object().get_attr("msg", vm) { + Ok(obj) => obj + .str(vm) + .unwrap_or_else(|_| vm.ctx.new_str("<msg str() failed>")), + Err(_) => { + // Fallback to the base formatting if the msg attribute was deleted or attribute lookup fails for any reason. + return Py::<PyBaseException>::__str__(zelf, vm); + } }; let msg_with_location_info: String = match (maybe_lineno, maybe_filename) { diff --git a/extra_tests/snippets/builtin_exceptions.py b/extra_tests/snippets/builtin_exceptions.py index 490f831f522..0af5cf05eaf 100644 --- a/extra_tests/snippets/builtin_exceptions.py +++ b/extra_tests/snippets/builtin_exceptions.py @@ -85,6 +85,13 @@ def __init__(self, value): assert exc.offset is None assert exc.text is None +err = SyntaxError("bad bad", ("bad.py", 1, 2, "abcdefg")) +err.msg = "changed" +assert err.msg == "changed" +assert str(err) == "changed (bad.py, line 1)" +del err.msg +assert err.msg is None + # Regression to: # https://github.com/RustPython/RustPython/issues/2779 From 72cf6c36d53148903c5665cb18de04c490a2f264 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:09:58 +0900 Subject: [PATCH 568/819] Add missing xmlparser attributes: namespace_prefixes, ordered_attributes, specified_attributes, intern (#6494) * Add namespace_prefixes and other missing xmlparser attributes - Added namespace_prefixes, ordered_attributes, specified_attributes (boolean attributes) - Added intern dictionary attribute - Added stub handlers for all missing handler types to ensure compatibility - Created bool_property macro to ensure boolean attributes are converted correctly * Remove expectedFailure decorators from passing tests - Tests for buffer_text, namespace_prefixes, ordered_attributes, and specified_attributes now pass --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- Lib/test/test_pyexpat.py | 6 -- crates/stdlib/src/pyexpat.rs | 183 ++++++++++++++++++++++++++++++++++- 2 files changed, 182 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index 80485cc74b9..015e7497268 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -20,28 +20,24 @@ class SetAttributeTest(unittest.TestCase): def setUp(self): self.parser = expat.ParserCreate(namespace_separator='!') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_buffer_text(self): self.assertIs(self.parser.buffer_text, False) for x in 0, 1, 2, 0: self.parser.buffer_text = x self.assertIs(self.parser.buffer_text, bool(x)) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_namespace_prefixes(self): self.assertIs(self.parser.namespace_prefixes, False) for x in 0, 1, 2, 0: self.parser.namespace_prefixes = x self.assertIs(self.parser.namespace_prefixes, bool(x)) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_ordered_attributes(self): self.assertIs(self.parser.ordered_attributes, False) for x in 0, 1, 2, 0: self.parser.ordered_attributes = x self.assertIs(self.parser.ordered_attributes, bool(x)) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_specified_attributes(self): self.assertIs(self.parser.specified_attributes, False) for x in 0, 1, 2, 0: @@ -244,7 +240,6 @@ def test_parse_bytes(self): # Issue #6697. self.assertRaises(AttributeError, getattr, parser, '\uD800') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_str(self): out = self.Outputter() parser = expat.ParserCreate(namespace_separator='!') @@ -255,7 +250,6 @@ def test_parse_str(self): operations = out.out self._verify_parse_output(operations) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_file(self): # Try parsing a file out = self.Outputter() diff --git a/crates/stdlib/src/pyexpat.rs b/crates/stdlib/src/pyexpat.rs index 871ba7d5987..699fa21852d 100644 --- a/crates/stdlib/src/pyexpat.rs +++ b/crates/stdlib/src/pyexpat.rs @@ -25,6 +25,26 @@ macro_rules! create_property { }; } +macro_rules! create_bool_property { + ($ctx: expr, $attributes: expr, $name: expr, $class: expr, $element: ident) => { + let attr = $ctx.new_static_getset( + $name, + $class, + move |this: &PyExpatLikeXmlParser| this.$element.read().clone(), + move |this: &PyExpatLikeXmlParser, + value: PyObjectRef, + vm: &VirtualMachine| + -> PyResult<()> { + let bool_value = value.is_true(vm)?; + *this.$element.write() = vm.ctx.new_bool(bool_value).into(); + Ok(()) + }, + ); + + $attributes.insert($ctx.intern_str($name), attr.into()); + }; +} + #[pymodule(name = "pyexpat")] mod _pyexpat { use crate::vm::{ @@ -51,6 +71,29 @@ mod _pyexpat { character_data: MutableObject, entity_decl: MutableObject, buffer_text: MutableObject, + namespace_prefixes: MutableObject, + ordered_attributes: MutableObject, + specified_attributes: MutableObject, + intern: MutableObject, + // Additional handlers (stubs for compatibility) + processing_instruction: MutableObject, + unparsed_entity_decl: MutableObject, + notation_decl: MutableObject, + start_namespace_decl: MutableObject, + end_namespace_decl: MutableObject, + comment: MutableObject, + start_cdata_section: MutableObject, + end_cdata_section: MutableObject, + default: MutableObject, + default_expand: MutableObject, + not_standalone: MutableObject, + external_entity_ref: MutableObject, + start_doctype_decl: MutableObject, + end_doctype_decl: MutableObject, + xml_decl: MutableObject, + element_decl: MutableObject, + attlist_decl: MutableObject, + skipped_entity: MutableObject, } type PyExpatLikeXmlParserRef = PyRef<PyExpatLikeXmlParser>; @@ -71,6 +114,31 @@ mod _pyexpat { character_data: MutableObject::new(vm.ctx.none()), entity_decl: MutableObject::new(vm.ctx.none()), buffer_text: MutableObject::new(vm.ctx.new_bool(false).into()), + namespace_prefixes: MutableObject::new(vm.ctx.new_bool(false).into()), + ordered_attributes: MutableObject::new(vm.ctx.new_bool(false).into()), + specified_attributes: MutableObject::new(vm.ctx.new_bool(false).into()), + // String interning dictionary - used by the parser to intern element/attribute names + // for memory efficiency and faster comparisons. See CPython's pyexpat documentation. + intern: MutableObject::new(vm.ctx.new_dict().into()), + // Additional handlers (stubs for compatibility) + processing_instruction: MutableObject::new(vm.ctx.none()), + unparsed_entity_decl: MutableObject::new(vm.ctx.none()), + notation_decl: MutableObject::new(vm.ctx.none()), + start_namespace_decl: MutableObject::new(vm.ctx.none()), + end_namespace_decl: MutableObject::new(vm.ctx.none()), + comment: MutableObject::new(vm.ctx.none()), + start_cdata_section: MutableObject::new(vm.ctx.none()), + end_cdata_section: MutableObject::new(vm.ctx.none()), + default: MutableObject::new(vm.ctx.none()), + default_expand: MutableObject::new(vm.ctx.none()), + not_standalone: MutableObject::new(vm.ctx.none()), + external_entity_ref: MutableObject::new(vm.ctx.none()), + start_doctype_decl: MutableObject::new(vm.ctx.none()), + end_doctype_decl: MutableObject::new(vm.ctx.none()), + xml_decl: MutableObject::new(vm.ctx.none()), + element_decl: MutableObject::new(vm.ctx.none()), + attlist_decl: MutableObject::new(vm.ctx.none()), + skipped_entity: MutableObject::new(vm.ctx.none()), } .into_ref(&vm.ctx)) } @@ -89,7 +157,120 @@ mod _pyexpat { character_data ); create_property!(ctx, attributes, "EntityDeclHandler", class, entity_decl); - create_property!(ctx, attributes, "buffer_text", class, buffer_text); + create_bool_property!(ctx, attributes, "buffer_text", class, buffer_text); + create_bool_property!( + ctx, + attributes, + "namespace_prefixes", + class, + namespace_prefixes + ); + create_bool_property!( + ctx, + attributes, + "ordered_attributes", + class, + ordered_attributes + ); + create_bool_property!( + ctx, + attributes, + "specified_attributes", + class, + specified_attributes + ); + create_property!(ctx, attributes, "intern", class, intern); + // Additional handlers (stubs for compatibility) + create_property!( + ctx, + attributes, + "ProcessingInstructionHandler", + class, + processing_instruction + ); + create_property!( + ctx, + attributes, + "UnparsedEntityDeclHandler", + class, + unparsed_entity_decl + ); + create_property!(ctx, attributes, "NotationDeclHandler", class, notation_decl); + create_property!( + ctx, + attributes, + "StartNamespaceDeclHandler", + class, + start_namespace_decl + ); + create_property!( + ctx, + attributes, + "EndNamespaceDeclHandler", + class, + end_namespace_decl + ); + create_property!(ctx, attributes, "CommentHandler", class, comment); + create_property!( + ctx, + attributes, + "StartCdataSectionHandler", + class, + start_cdata_section + ); + create_property!( + ctx, + attributes, + "EndCdataSectionHandler", + class, + end_cdata_section + ); + create_property!(ctx, attributes, "DefaultHandler", class, default); + create_property!( + ctx, + attributes, + "DefaultHandlerExpand", + class, + default_expand + ); + create_property!( + ctx, + attributes, + "NotStandaloneHandler", + class, + not_standalone + ); + create_property!( + ctx, + attributes, + "ExternalEntityRefHandler", + class, + external_entity_ref + ); + create_property!( + ctx, + attributes, + "StartDoctypeDeclHandler", + class, + start_doctype_decl + ); + create_property!( + ctx, + attributes, + "EndDoctypeDeclHandler", + class, + end_doctype_decl + ); + create_property!(ctx, attributes, "XmlDeclHandler", class, xml_decl); + create_property!(ctx, attributes, "ElementDeclHandler", class, element_decl); + create_property!(ctx, attributes, "AttlistDeclHandler", class, attlist_decl); + create_property!( + ctx, + attributes, + "SkippedEntityHandler", + class, + skipped_entity + ); } fn create_config(&self) -> xml::ParserConfig { From 7ebb0f0c5c8e8958238e80e23f5e108dbb892db7 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:21:09 +0900 Subject: [PATCH 569/819] impl path_converter and os functions (#6484) * os.setpgrp * tcgetpgrp * impl more os functions * impl PathConverter --- Lib/test/test_os.py | 9 +- crates/vm/src/function/fspath.rs | 3 +- crates/vm/src/ospath.rs | 217 ++++++++++++++++++++++++++----- crates/vm/src/stdlib/nt.rs | 29 +++-- crates/vm/src/stdlib/os.rs | 46 +++++-- crates/vm/src/stdlib/posix.rs | 26 ++++ 6 files changed, 272 insertions(+), 58 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index bb558524c24..4755aef080e 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1725,15 +1725,15 @@ def walk(self, top, **kwargs): bdirs[:] = list(map(os.fsencode, dirs)) bfiles[:] = list(map(os.fsencode, files)) - @unittest.expectedFailure # TODO: RUSTPYTHON; (TypeError: Can't mix strings and bytes in path components) + @unittest.expectedFailure # TODO: RUSTPYTHON; WalkTests doesn't have these methods def test_compare_to_walk(self): return super().test_compare_to_walk() - @unittest.expectedFailure # TODO: RUSTPYTHON; (TypeError: Can't mix strings and bytes in path components) + @unittest.expectedFailure # TODO: RUSTPYTHON; WalkTests doesn't have these methods def test_dir_fd(self): return super().test_dir_fd() - @unittest.expectedFailure # TODO: RUSTPYTHON; (TypeError: Can't mix strings and bytes in path components) + @unittest.expectedFailure # TODO: RUSTPYTHON; WalkTests doesn't have these methods def test_yields_correct_dir_fd(self): return super().test_yields_correct_dir_fd() @@ -4502,7 +4502,6 @@ class Str(str): self.filenames = self.bytes_filenames + self.unicode_filenames - @unittest.expectedFailure # TODO: RUSTPYTHON; (AssertionError: b'@test_22106_tmp\xe7w\xf0' is not b'@test_22106_tmp\xe7w\xf0' : <built-in function chdir>) def test_oserror_filename(self): funcs = [ (self.filenames, os.chdir,), @@ -4906,7 +4905,6 @@ def setUp(self): def test_uninstantiable(self): self.assertRaises(TypeError, os.DirEntry) - @unittest.expectedFailure # TODO: RUSTPYTHON; (pickle.PicklingError: Can't pickle <class '_os.DirEntry'>: it's not found as _os.DirEntry) def test_unpickable(self): filename = create_file(os.path.join(self.path, "file.txt"), b'python') entry = [entry for entry in os.scandir(self.path)].pop() @@ -5337,7 +5335,6 @@ def __fspath__(self): return '' self.assertFalse(hasattr(A(), '__dict__')) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_fspath_set_to_None(self): class Foo: __fspath__ = None diff --git a/crates/vm/src/function/fspath.rs b/crates/vm/src/function/fspath.rs index 2bc331844c6..44d41ab7632 100644 --- a/crates/vm/src/function/fspath.rs +++ b/crates/vm/src/function/fspath.rs @@ -7,6 +7,7 @@ use crate::{ }; use std::{borrow::Cow, ffi::OsStr, path::PathBuf}; +/// Helper to implement os.fspath() #[derive(Clone)] pub enum FsPath { Str(PyStrRef), @@ -27,7 +28,7 @@ impl FsPath { ) } - // PyOS_FSPath in CPython + // PyOS_FSPath pub fn try_from( obj: PyObjectRef, check_for_nul: bool, diff --git a/crates/vm/src/ospath.rs b/crates/vm/src/ospath.rs index 9fca53d869c..77abbee2cd5 100644 --- a/crates/vm/src/ospath.rs +++ b/crates/vm/src/ospath.rs @@ -2,20 +2,181 @@ use rustpython_common::crt_fd; use crate::{ PyObjectRef, PyResult, VirtualMachine, + builtins::{PyBytes, PyStr}, convert::{IntoPyException, ToPyException, ToPyObject, TryFromObject}, function::FsPath, }; use std::path::{Path, PathBuf}; -// path_ without allow_fd in CPython +/// path_converter +#[derive(Clone, Copy, Default)] +pub struct PathConverter { + /// Function name for error messages (e.g., "rename") + pub function_name: Option<&'static str>, + /// Argument name for error messages (e.g., "src", "dst") + pub argument_name: Option<&'static str>, + /// If true, embedded null characters are allowed + pub non_strict: bool, +} + +impl PathConverter { + pub const fn new() -> Self { + Self { + function_name: None, + argument_name: None, + non_strict: false, + } + } + + pub const fn function(mut self, name: &'static str) -> Self { + self.function_name = Some(name); + self + } + + pub const fn argument(mut self, name: &'static str) -> Self { + self.argument_name = Some(name); + self + } + + pub const fn non_strict(mut self) -> Self { + self.non_strict = true; + self + } + + /// Generate error message prefix like "rename: " + fn error_prefix(&self) -> String { + match self.function_name { + Some(func) => format!("{}: ", func), + None => String::new(), + } + } + + /// Get argument name for error messages, defaults to "path" + fn arg_name(&self) -> &'static str { + self.argument_name.unwrap_or("path") + } + + /// Format a type error message + fn type_error_msg(&self, type_name: &str, allow_fd: bool) -> String { + let expected = if allow_fd { + "string, bytes, os.PathLike or integer" + } else { + "string, bytes or os.PathLike" + }; + format!( + "{}{} should be {}, not {}", + self.error_prefix(), + self.arg_name(), + expected, + type_name + ) + } + + /// Convert to OsPathOrFd (path or file descriptor) + pub(crate) fn try_path_or_fd<'fd>( + &self, + obj: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<OsPathOrFd<'fd>> { + // Handle fd (before __fspath__ check, like CPython) + if let Some(int) = obj.try_index_opt(vm) { + let fd = int?.try_to_primitive(vm)?; + return unsafe { crt_fd::Borrowed::try_borrow_raw(fd) } + .map(OsPathOrFd::Fd) + .map_err(|e| e.into_pyexception(vm)); + } + + self.try_path_inner(obj, true, vm).map(OsPathOrFd::Path) + } + + /// Convert to OsPath only (no fd support) + fn try_path_inner( + &self, + obj: PyObjectRef, + allow_fd: bool, + vm: &VirtualMachine, + ) -> PyResult<OsPath> { + // Try direct str/bytes match + let obj = match self.try_match_str_bytes(obj.clone(), vm)? { + Ok(path) => return Ok(path), + Err(obj) => obj, + }; + + // Call __fspath__ + let type_error_msg = || self.type_error_msg(&obj.class().name(), allow_fd); + let method = + vm.get_method_or_type_error(obj.clone(), identifier!(vm, __fspath__), type_error_msg)?; + if vm.is_none(&method) { + return Err(vm.new_type_error(type_error_msg())); + } + let result = method.call((), vm)?; + + // Match __fspath__ result + self.try_match_str_bytes(result.clone(), vm)?.map_err(|_| { + vm.new_type_error(format!( + "{}expected {}.__fspath__() to return str or bytes, not {}", + self.error_prefix(), + obj.class().name(), + result.class().name(), + )) + }) + } + + /// Try to match str or bytes, returns Err(obj) if neither + fn try_match_str_bytes( + &self, + obj: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<Result<OsPath, PyObjectRef>> { + let check_nul = |b: &[u8]| { + if self.non_strict || memchr::memchr(b'\0', b).is_none() { + Ok(()) + } else { + Err(vm.new_value_error(format!( + "{}embedded null character in {}", + self.error_prefix(), + self.arg_name() + ))) + } + }; + + match_class!(match obj { + s @ PyStr => { + check_nul(s.as_bytes())?; + let path = vm.fsencode(&s)?.into_owned(); + Ok(Ok(OsPath { + path, + origin: Some(s.into()), + })) + } + b @ PyBytes => { + check_nul(&b)?; + let path = FsPath::bytes_as_os_str(&b, vm)?.to_owned(); + Ok(Ok(OsPath { + path, + origin: Some(b.into()), + })) + } + obj => Ok(Err(obj)), + }) + } + + /// Convert to OsPath directly + pub fn try_path(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<OsPath> { + self.try_path_inner(obj, false, vm) + } +} + +/// path_t output - the converted path #[derive(Clone)] pub struct OsPath { pub path: std::ffi::OsString, - pub(super) mode: OutputMode, + /// Original Python object for identity preservation in OSError + pub(super) origin: Option<PyObjectRef>, } #[derive(Debug, Copy, Clone)] -pub(super) enum OutputMode { +pub enum OutputMode { String, Bytes, } @@ -38,19 +199,19 @@ impl OutputMode { impl OsPath { pub fn new_str(path: impl Into<std::ffi::OsString>) -> Self { let path = path.into(); - Self { - path, - mode: OutputMode::String, - } + Self { path, origin: None } } pub(crate) fn from_fspath(fspath: FsPath, vm: &VirtualMachine) -> PyResult<Self> { let path = fspath.as_os_str(vm)?.into_owned(); - let mode = match fspath { - FsPath::Str(_) => OutputMode::String, - FsPath::Bytes(_) => OutputMode::Bytes, + let origin = match fspath { + FsPath::Str(s) => s.into(), + FsPath::Bytes(b) => b.into(), }; - Ok(Self { path, mode }) + Ok(Self { + path, + origin: Some(origin), + }) } /// Convert an object to OsPath using the os.fspath-style error message. @@ -83,7 +244,20 @@ impl OsPath { } pub fn filename(&self, vm: &VirtualMachine) -> PyObjectRef { - self.mode.process_path(self.path.clone(), vm) + if let Some(ref origin) = self.origin { + origin.clone() + } else { + // Default to string when no origin (e.g., from new_str) + OutputMode::String.process_path(self.path.clone(), vm) + } + } + + /// Get the output mode based on origin type (bytes -> Bytes, otherwise -> String) + pub fn mode(&self) -> OutputMode { + match &self.origin { + Some(obj) if obj.downcast_ref::<PyBytes>().is_some() => OutputMode::Bytes, + _ => OutputMode::String, + } } } @@ -94,15 +268,8 @@ impl AsRef<Path> for OsPath { } impl TryFromObject for OsPath { - // TODO: path_converter with allow_fd=0 in CPython fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { - let fspath = FsPath::try_from( - obj, - true, - "should be string, bytes, os.PathLike or integer", - vm, - )?; - Self::from_fspath(fspath, vm) + PathConverter::new().try_path(obj, vm) } } @@ -115,15 +282,7 @@ pub(crate) enum OsPathOrFd<'fd> { impl TryFromObject for OsPathOrFd<'_> { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { - match obj.try_index_opt(vm) { - Some(int) => { - let fd = int?.try_to_primitive(vm)?; - unsafe { crt_fd::Borrowed::try_borrow_raw(fd) } - .map(Self::Fd) - .map_err(|e| e.into_pyexception(vm)) - } - None => obj.try_into_value(vm).map(Self::Path), - } + PathConverter::new().try_path_or_fd(obj, vm) } } diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index ada939b1549..fc68139dc88 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -17,6 +17,7 @@ pub(crate) mod module { builtins::{PyBaseExceptionRef, PyDictRef, PyListRef, PyStrRef, PyTupleRef}, common::{crt_fd, suppress_iph, windows::ToWideString}, convert::ToPyException, + exceptions::OSErrorBuilder, function::{Either, OptionalArg}, ospath::{OsPath, OsPathOrFd}, stdlib::os::{_os, DirFd, SupportFunc, TargetIsDirectory}, @@ -193,10 +194,12 @@ pub(crate) mod module { }; // Use symlink_metadata to avoid following dangling symlinks - let meta = fs::symlink_metadata(&actual_path).map_err(|err| err.to_pyexception(vm))?; + let meta = fs::symlink_metadata(&actual_path) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; let mut permissions = meta.permissions(); permissions.set_readonly(mode & S_IWRITE == 0); - fs::set_permissions(&*actual_path, permissions).map_err(|err| err.to_pyexception(vm)) + fs::set_permissions(&*actual_path, permissions) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } /// Get the real file name (with correct case) without accessing the file. @@ -925,7 +928,7 @@ pub(crate) mod module { .as_ref() .canonicalize() .map_err(|e| e.to_pyexception(vm))?; - Ok(path.mode.process_path(real, vm)) + Ok(path.mode().process_path(real, vm)) } #[pyfunction] @@ -958,7 +961,7 @@ pub(crate) mod module { } } let buffer = widestring::WideCString::from_vec_truncate(buffer); - Ok(path.mode.process_path(buffer.to_os_string(), vm)) + Ok(path.mode().process_path(buffer.to_os_string(), vm)) } #[pyfunction] @@ -973,7 +976,7 @@ pub(crate) mod module { return Err(vm.new_last_os_error()); } let buffer = widestring::WideCString::from_vec_truncate(buffer); - Ok(path.mode.process_path(buffer.to_os_string(), vm)) + Ok(path.mode().process_path(buffer.to_os_string(), vm)) } /// Implements _Py_skiproot logic for Windows paths @@ -1053,7 +1056,7 @@ pub(crate) mod module { use crate::builtins::{PyBytes, PyStr}; use rustpython_common::wtf8::Wtf8Buf; - // Handle path-like objects via os.fspath, but without null check (nonstrict=True) + // Handle path-like objects via os.fspath, but without null check (non_strict=True) let path = if let Some(fspath) = vm.get_method(path.clone(), identifier!(vm, __fspath__)) { fspath?.call((), vm)? } else { @@ -1585,7 +1588,7 @@ pub(crate) mod module { use windows_sys::Win32::System::IO::DeviceIoControl; use windows_sys::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT; - let mode = path.mode; + let mode = path.mode(); let wide_path = path.as_ref().to_wide_with_nul(); // Open the file/directory with reparse point flag @@ -1602,7 +1605,11 @@ pub(crate) mod module { }; if handle == INVALID_HANDLE_VALUE { - return Err(io::Error::last_os_error().to_pyexception(vm)); + return Err(OSErrorBuilder::with_filename( + &io::Error::last_os_error(), + path.clone(), + vm, + )); } // Buffer for reparse data - MAXIMUM_REPARSE_DATA_BUFFER_SIZE is 16384 @@ -1626,7 +1633,11 @@ pub(crate) mod module { unsafe { CloseHandle(handle) }; if result == 0 { - return Err(io::Error::last_os_error().to_pyexception(vm)); + return Err(OSErrorBuilder::with_filename( + &io::Error::last_os_error(), + path.clone(), + vm, + )); } // Parse the reparse data buffer diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 868fc727c65..87080ff8e0f 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -164,7 +164,7 @@ pub(super) mod _os { convert::{IntoPyException, ToPyObject}, exceptions::OSErrorBuilder, function::{ArgBytesLike, FsPath, FuncArgs, OptionalArg}, - ospath::{OsPath, OsPathOrFd, OutputMode}, + ospath::{OsPath, OsPathOrFd, OutputMode, PathConverter}, protocol::PyIterReturn, recursion::ReprGuard, types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter}, @@ -366,10 +366,12 @@ pub(super) mod _os { #[pyfunction] fn listdir( - path: OptionalArg<OsPathOrFd<'_>>, + path: OptionalArg<Option<OsPathOrFd<'_>>>, vm: &VirtualMachine, ) -> PyResult<Vec<PyObjectRef>> { - let path = path.unwrap_or_else(|| OsPathOrFd::Path(OsPath::new_str("."))); + let path = path + .flatten() + .unwrap_or_else(|| OsPathOrFd::Path(OsPath::new_str("."))); let list = match path { OsPathOrFd::Path(path) => { let dir_iter = match fs::read_dir(&path) { @@ -378,9 +380,10 @@ pub(super) mod _os { return Err(OSErrorBuilder::with_filename(&err, path, vm)); } }; + let mode = path.mode(); dir_iter .map(|entry| match entry { - Ok(entry_path) => Ok(path.mode.process_path(entry_path.file_name(), vm)), + Ok(entry_path) => Ok(mode.process_path(entry_path.file_name(), vm)), Err(err) => Err(OSErrorBuilder::with_filename(&err, path.clone(), vm)), }) .collect::<PyResult<_>>()? @@ -545,7 +548,7 @@ pub(super) mod _os { #[pyfunction] fn readlink(path: OsPath, dir_fd: DirFd<'_, 0>, vm: &VirtualMachine) -> PyResult { - let mode = path.mode; + let mode = path.mode(); let [] = dir_fd.0; let path = fs::read_link(&path).map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))?; @@ -640,7 +643,7 @@ pub(super) mod _os { stat( OsPath { path: self.pathval.as_os_str().to_owned(), - mode: OutputMode::String, + origin: None, } .into(), dir_fd, @@ -671,11 +674,7 @@ pub(super) mod _os { Some(ino) => Ok(ino), None => { let stat = stat_inner( - OsPath { - path: self.pathval.as_os_str().to_owned(), - mode: OutputMode::String, - } - .into(), + OsPath::new_str(self.pathval.as_os_str()).into(), DirFd::default(), FollowSymlinks(false), ) @@ -730,6 +729,11 @@ pub(super) mod _os { ) -> PyGenericAlias { PyGenericAlias::from_args(cls, args, vm) } + + #[pymethod] + fn __reduce__(&self, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("cannot pickle 'DirEntry' object".to_owned())) + } } impl Representable for DirEntry { @@ -785,6 +789,11 @@ pub(super) mod _os { fn __exit__(zelf: PyRef<Self>, _args: FuncArgs) { zelf.close() } + + #[pymethod] + fn __reduce__(&self, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("cannot pickle 'ScandirIterator' object".to_owned())) + } } impl SelfIter for ScandirIterator {} impl IterNext for ScandirIterator { @@ -861,7 +870,7 @@ pub(super) mod _os { .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; Ok(ScandirIterator { entries: PyRwLock::new(Some(entries)), - mode: path.mode, + mode: path.mode(), } .into_ref(&vm.ctx) .into()) @@ -1124,7 +1133,16 @@ pub(super) mod _os { #[pyfunction] #[pyfunction(name = "replace")] - fn rename(src: OsPath, dst: OsPath, vm: &VirtualMachine) -> PyResult<()> { + fn rename(src: PyObjectRef, dst: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let src = PathConverter::new() + .function("rename") + .argument("src") + .try_path(src, vm)?; + let dst = PathConverter::new() + .function("rename") + .argument("dst") + .try_path(dst, vm)?; + fs::rename(&src.path, &dst.path).map_err(|err| { let builder = err.to_os_error_builder(vm); let builder = builder.filename(src.filename(vm)); @@ -1708,6 +1726,8 @@ pub(super) mod _os { SupportFunc::new("fstat", Some(true), Some(STAT_DIR_FD), Some(true)), SupportFunc::new("symlink", Some(false), Some(SYMLINK_DIR_FD), Some(false)), SupportFunc::new("truncate", Some(true), Some(false), Some(false)), + SupportFunc::new("ftruncate", Some(true), Some(false), Some(false)), + SupportFunc::new("fsync", Some(true), Some(false), Some(false)), SupportFunc::new( "utime", Some(false), diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 59e41782574..15c8745ded4 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -1240,6 +1240,12 @@ pub mod module { .map_err(|err| err.into_pyexception(vm)) } + #[pyfunction] + fn setpgrp(vm: &VirtualMachine) -> PyResult<()> { + // setpgrp() is equivalent to setpgid(0, 0) + unistd::setpgid(Pid::from_raw(0), Pid::from_raw(0)).map_err(|err| err.into_pyexception(vm)) + } + #[cfg(not(any(target_os = "wasi", target_os = "redox")))] #[pyfunction] fn setsid(vm: &VirtualMachine) -> PyResult<()> { @@ -1248,6 +1254,24 @@ pub mod module { .map_err(|err| err.into_pyexception(vm)) } + #[cfg(not(any(target_os = "wasi", target_os = "redox")))] + #[pyfunction] + fn tcgetpgrp(fd: i32, vm: &VirtualMachine) -> PyResult<libc::pid_t> { + use std::os::fd::BorrowedFd; + let fd = unsafe { BorrowedFd::borrow_raw(fd) }; + unistd::tcgetpgrp(fd) + .map(|pid| pid.as_raw()) + .map_err(|err| err.into_pyexception(vm)) + } + + #[cfg(not(any(target_os = "wasi", target_os = "redox")))] + #[pyfunction] + fn tcsetpgrp(fd: i32, pgid: libc::pid_t, vm: &VirtualMachine) -> PyResult<()> { + use std::os::fd::BorrowedFd; + let fd = unsafe { BorrowedFd::borrow_raw(fd) }; + unistd::tcsetpgrp(fd, Pid::from_raw(pgid)).map_err(|err| err.into_pyexception(vm)) + } + fn try_from_id(vm: &VirtualMachine, obj: PyObjectRef, typ_name: &str) -> PyResult<u32> { use std::cmp::Ordering; let i = obj @@ -1833,6 +1857,8 @@ pub mod module { SupportFunc::new("umask", Some(false), Some(false), Some(false)), SupportFunc::new("execv", None, None, None), SupportFunc::new("pathconf", Some(true), None, None), + SupportFunc::new("fpathconf", Some(true), None, None), + SupportFunc::new("fchdir", Some(true), None, None), ] } From ebdc033c59d39405ee1d6c2b31afe8fb1b2c61bc Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:48:20 +0900 Subject: [PATCH 570/819] Bundle Lib directory in GitHub releases (#6497) * Initial plan * Add Lib directory archives to release workflow Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> * Add release notes explaining users need both binary and Lib archive Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> * Remove unnecessary --notes-start-tag parameter Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> * Only create zip archive, remove tar.gz Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- .github/workflows/release.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3efff295c37..5ad2cf3f97a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -144,6 +144,8 @@ jobs: if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }} needs: [build, build-wasm] steps: + - uses: actions/checkout@v6.0.1 + - name: Download Binary Artifacts uses: actions/download-artifact@v7.0.0 with: @@ -151,6 +153,10 @@ jobs: pattern: rustpython-* merge-multiple: true + - name: Create Lib Archive + run: | + zip -r bin/rustpython-lib.zip Lib/ + - name: List Binaries run: | ls -lah bin/ @@ -174,6 +180,7 @@ jobs: --repo="$GITHUB_REPOSITORY" \ --title="RustPython $RELEASE_TYPE_NAME $today-$tag #$run" \ --target="$tag" \ + --notes "⚠️ **Important**: To run RustPython, you must download both the binary for your platform AND the \`rustpython-lib.zip\` archive. Extract the Lib directory from the archive to the same location as the binary, or set the \`RUSTPYTHONPATH\` environment variable to point to the Lib directory." \ --generate-notes \ $PRERELEASE_ARG \ bin/rustpython-release-* From 5122aed738b4a50a4f52af30b650967be9c081ef Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 25 Dec 2025 10:32:51 +0900 Subject: [PATCH 571/819] Fix install ujson (#6502) --- crates/stdlib/src/ssl/compat.rs | 96 ++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 20 deletions(-) diff --git a/crates/stdlib/src/ssl/compat.rs b/crates/stdlib/src/ssl/compat.rs index cd927a0e410..45aa9c4fce9 100644 --- a/crates/stdlib/src/ssl/compat.rs +++ b/crates/stdlib/src/ssl/compat.rs @@ -1421,6 +1421,50 @@ pub(super) fn ssl_read( return Err(SslError::Eof); } } + + // For non-blocking sockets, return WantRead so caller can poll and retry. + // For blocking sockets (or sockets with timeout), wait for more data. + if !is_bio { + let timeout = socket.get_socket_timeout(vm).map_err(SslError::Py)?; + if let Some(t) = timeout + && t.is_zero() + { + // Non-blocking socket: return immediately + return Err(SslError::WantRead); + } + // Blocking socket or socket with timeout: try to read more data from socket. + // Even though rustls says it doesn't want to read, more TLS records may arrive. + // This handles the case where rustls processed all buffered TLS records but + // more data is coming over the network. + let data = match socket.sock_recv(2048, vm) { + Ok(data) => data, + Err(e) => { + if is_connection_closed_error(&e, vm) { + return Err(SslError::Eof); + } + return Err(SslError::Py(e)); + } + }; + + let bytes_read = data + .clone() + .try_into_value::<rustpython_vm::builtins::PyBytes>(vm) + .map(|b| b.as_bytes().len()) + .unwrap_or(0); + + if bytes_read == 0 { + // No more data available - connection might be closed + return Err(SslError::Eof); + } + + // Feed data to rustls and process + ssl_read_tls_records(conn, data, false, vm)?; + conn.process_new_packets().map_err(SslError::from_rustls)?; + + // Continue loop to try reading plaintext + continue; + } + return Err(SslError::WantRead); } @@ -1432,20 +1476,9 @@ pub(super) fn ssl_read( // Continue loop to try reading plaintext } Err(SslError::Io(ref io_err)) if io_err.to_string().contains("message buffer full") => { - // Buffer is full - we need to consume plaintext before reading more - // Try to read plaintext now - match try_read_plaintext(conn, buf)? { - Some(n) if n > 0 => { - // Have plaintext - return it - // Python will call read() again if it needs more data - return Ok(n); - } - _ => { - // No plaintext available yet - this is unusual - // Return WantRead to let Python retry - return Err(SslError::WantRead); - } - } + // This case should be rare now that ssl_read_tls_records handles buffer full + // Just continue loop to try again + continue; } Err(e) => { // Other errors - check for buffered plaintext before propagating @@ -1524,7 +1557,7 @@ fn ssl_read_tls_records( } // Feed all received data to read_tls - loop to consume all data - // read_tls may not consume all data in one call + // read_tls may not consume all data in one call, and buffer may become full let mut offset = 0; while offset < bytes_data.len() { let remaining = &bytes_data[offset..]; @@ -1533,12 +1566,33 @@ fn ssl_read_tls_records( match conn.read_tls(&mut cursor) { Ok(read_bytes) => { if read_bytes == 0 { - // No more data can be consumed - break; + // Buffer is full - process existing packets to make room + conn.process_new_packets().map_err(SslError::from_rustls)?; + + // Try again - if we still can't consume, break + let mut retry_cursor = std::io::Cursor::new(remaining); + match conn.read_tls(&mut retry_cursor) { + Ok(0) => { + // Still can't consume - break to avoid infinite loop + break; + } + Ok(n) => { + offset += n; + } + Err(e) => { + return Err(SslError::Io(e)); + } + } + } else { + offset += read_bytes; } - offset += read_bytes; } Err(e) => { + // Check if it's a buffer full error (unlikely but handle it) + if e.to_string().contains("buffer full") { + conn.process_new_packets().map_err(SslError::from_rustls)?; + continue; + } // Real error - propagate it return Err(SslError::Io(e)); } @@ -1599,8 +1653,10 @@ fn ssl_ensure_data_available( .sock_wait_for_io_impl(SelectKind::Read, vm) .map_err(SslError::Py)?; if timed_out { - // Socket not ready within timeout - return Err(SslError::WantRead); + // Socket not ready within timeout - raise socket.timeout + return Err(SslError::Timeout( + "The read operation timed out".to_string(), + )); } } // else: non-blocking socket (timeout=0) or blocking socket (timeout=None) - skip select From 6dbc8f0cfa1d0cd48b90d48c6f7768a030a091a8 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 10:33:22 +0900 Subject: [PATCH 572/819] Set SourceFileLoader for script execution in run_simple_file (#6496) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- Lib/test/test_cmd_line_script.py | 2 -- crates/vm/src/vm/compile.rs | 12 +++++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 833dc6b15d8..d773674feef 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -225,8 +225,6 @@ def test_repl_stderr_flush(self): def test_repl_stderr_flush_separate_stderr(self): self.check_repl_stderr_flush(True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basic_script(self): with os_helper.temp_dir() as script_dir: script_name = _make_test_script(script_dir, 'script') diff --git a/crates/vm/src/vm/compile.rs b/crates/vm/src/vm/compile.rs index 44332cda838..a7e31cf0377 100644 --- a/crates/vm/src/vm/compile.rs +++ b/crates/vm/src/vm/compile.rs @@ -81,7 +81,7 @@ impl VirtualMachine { todo!("running pyc is not implemented yet"); } else { if path != "<stdin>" { - // TODO: set_main_loader(dict, filename, "SourceFileLoader"); + set_main_loader(&module_dict, path, self)?; } // TODO: replace to something equivalent to py_run_file match std::fs::read_to_string(path) { @@ -125,6 +125,16 @@ impl VirtualMachine { } } +fn set_main_loader(module_dict: &PyDictRef, filename: &str, vm: &VirtualMachine) -> PyResult<()> { + vm.import("importlib.machinery", 0)?; + let sys_modules = vm.sys_module.get_attr(identifier!(vm, modules), vm)?; + let machinery = sys_modules.get_item("importlib.machinery", vm)?; + let loader_class = machinery.get_attr("SourceFileLoader", vm)?; + let loader = loader_class.call((identifier!(vm, __main__).to_owned(), filename), vm)?; + module_dict.set_item("__loader__", loader, vm)?; + Ok(()) +} + fn get_importer(path: &str, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> { let path_importer_cache = vm.sys_module.get_attr("path_importer_cache", vm)?; let path_importer_cache = PyDictRef::try_from_object(vm, path_importer_cache)?; From aae6bf566f0ef4de7beea473b1a73030834ddb16 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 25 Dec 2025 11:00:53 +0900 Subject: [PATCH 573/819] Add tp_str (#6495) * clean up hash * __str__ slot wrapper --- Lib/test/test_inspect/test_inspect.py | 1 - crates/vm/src/builtins/descriptor.rs | 13 +++++++++---- crates/vm/src/builtins/object.rs | 4 ++-- crates/vm/src/class.rs | 20 +++++--------------- crates/vm/src/types/slot.rs | 2 +- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index e403ab7b226..353d7c2e7b2 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -409,7 +409,6 @@ class NotFuture: pass coro.close(); gen_coro.close() # silence warnings - @unittest.expectedFailure # TODO: RUSTPYTHON def test_isroutine(self): # method self.assertTrue(inspect.isroutine(git.argue)) diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index fcf40d0082d..297199eee18 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -399,6 +399,7 @@ pub fn init(ctx: &Context) { pub enum SlotFunc { Init(InitFunc), Hash(HashFunc), + Str(StringifyFunc), Repr(StringifyFunc), Iter(IterFunc), IterNext(IterNextFunc), @@ -409,6 +410,7 @@ impl std::fmt::Debug for SlotFunc { match self { SlotFunc::Init(_) => write!(f, "SlotFunc::Init(...)"), SlotFunc::Hash(_) => write!(f, "SlotFunc::Hash(...)"), + SlotFunc::Str(_) => write!(f, "SlotFunc::Str(...)"), SlotFunc::Repr(_) => write!(f, "SlotFunc::Repr(...)"), SlotFunc::Iter(_) => write!(f, "SlotFunc::Iter(...)"), SlotFunc::IterNext(_) => write!(f, "SlotFunc::IterNext(...)"), @@ -433,11 +435,14 @@ impl SlotFunc { let hash = func(&obj, vm)?; Ok(vm.ctx.new_int(hash).into()) } - SlotFunc::Repr(func) => { + SlotFunc::Repr(func) | SlotFunc::Str(func) => { if !args.args.is_empty() || !args.kwargs.is_empty() { - return Err( - vm.new_type_error("__repr__() takes no arguments (1 given)".to_owned()) - ); + let name = match self { + SlotFunc::Repr(_) => "__repr__", + SlotFunc::Str(_) => "__str__", + _ => unreachable!(), + }; + return Err(vm.new_type_error(format!("{name}() takes no arguments (1 given)"))); } let s = func(&obj, vm)?; Ok(s.into()) diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 854cb2701ed..65a11d55314 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -425,8 +425,8 @@ impl PyBaseObject { } /// Return str(self). - #[pymethod] - fn __str__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { + #[pyslot] + fn slot_str(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> { // FIXME: try tp_repr first and fallback to object.__repr__ zelf.repr(vm) } diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 1addf00497d..40f01d98b00 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -165,25 +165,15 @@ pub trait PyClassImpl: PyClassDef { "Initialize self. See help(type(self)) for accurate signature." ); add_slot_wrapper!(repr, __repr__, Repr, "Return repr(self)."); + add_slot_wrapper!(str, __str__, Str, "Return str(self)."); add_slot_wrapper!(iter, __iter__, Iter, "Implement iter(self)."); add_slot_wrapper!(iternext, __next__, IterNext, "Implement next(self)."); // __hash__ needs special handling: hash_not_implemented sets __hash__ = None - if let Some(hash_func) = class.slots.hash.load() { - if hash_func as usize == hash_not_implemented as usize { - class.set_attr(ctx.names.__hash__, ctx.none.clone().into()); - } else { - let hash_name = identifier!(ctx, __hash__); - if !class.attributes.read().contains_key(hash_name) { - let wrapper = PySlotWrapper { - typ: class, - name: ctx.intern_str("__hash__"), - wrapped: SlotFunc::Hash(hash_func), - doc: Some("Return hash(self)."), - }; - class.set_attr(hash_name, wrapper.into_ref(ctx).into()); - } - } + if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize { + class.set_attr(ctx.names.__hash__, ctx.none.clone().into()); + } else { + add_slot_wrapper!(hash, __hash__, Hash, "Return hash(self)."); } class.extend_methods(class.slots.methods, ctx); diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 26c059067e0..57ac24f461a 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -140,7 +140,7 @@ pub struct PyTypeSlots { // More standard operations (here for binary compatibility) pub hash: AtomicCell<Option<HashFunc>>, pub call: AtomicCell<Option<GenericMethod>>, - // tp_str + pub str: AtomicCell<Option<StringifyFunc>>, pub repr: AtomicCell<Option<StringifyFunc>>, pub getattro: AtomicCell<Option<GetattroFunc>>, pub setattro: AtomicCell<Option<SetattroFunc>>, From 49dbbbd5b9990038bed3ee740255a56da8678492 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 25 Dec 2025 13:28:50 +0900 Subject: [PATCH 574/819] Fix SSL test_preauth_data_to_tls_server (#6508) --- crates/stdlib/src/ssl/compat.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/stdlib/src/ssl/compat.rs b/crates/stdlib/src/ssl/compat.rs index 45aa9c4fce9..fa12855e242 100644 --- a/crates/stdlib/src/ssl/compat.rs +++ b/crates/stdlib/src/ssl/compat.rs @@ -391,6 +391,8 @@ pub(super) enum SslError { ZeroReturn, /// Unexpected EOF without close_notify (protocol violation) Eof, + /// Non-TLS data received before handshake completed + PreauthData, /// Certificate verification error CertVerification(rustls::CertificateError), /// I/O error @@ -562,6 +564,15 @@ impl SslError { .upcast(), SslError::ZeroReturn => create_ssl_zero_return_error(vm).upcast(), SslError::Eof => create_ssl_eof_error(vm).upcast(), + SslError::PreauthData => { + // Non-TLS data received before handshake + Self::create_ssl_error_with_reason( + vm, + None, + "before TLS handshake with data", + "before TLS handshake with data", + ) + } SslError::CertVerification(cert_err) => { // Use the proper cert verification error creator create_ssl_cert_verification_error(vm, &cert_err).expect("unlikely to happen") @@ -1245,6 +1256,12 @@ pub(super) fn ssl_do_handshake( } } + // InvalidMessage during handshake means non-TLS data was received + // before the handshake completed (e.g., HTTP request to TLS server) + if matches!(e, rustls::Error::InvalidMessage(_)) { + return Err(SslError::PreauthData); + } + // Certificate verification errors are already handled by from_rustls return Err(SslError::from_rustls(e)); From 92acf339a1d6b30ea605fc3179b34d8c6b1fb381 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 25 Dec 2025 13:31:29 +0900 Subject: [PATCH 575/819] fix pyexpat hang (#6507) --- crates/stdlib/src/pyexpat.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/stdlib/src/pyexpat.rs b/crates/stdlib/src/pyexpat.rs index 699fa21852d..e96d6287489 100644 --- a/crates/stdlib/src/pyexpat.rs +++ b/crates/stdlib/src/pyexpat.rs @@ -102,7 +102,9 @@ mod _pyexpat { where T: IntoFuncArgs, { - handler.read().call(args, vm).ok(); + // Clone the handler while holding the read lock, then release the lock + let handler = handler.read().clone(); + handler.call(args, vm).ok(); } #[pyclass] From 8443b2c97e75af3ec14b6657cd40429ad0a78d99 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 14:54:44 +0900 Subject: [PATCH 576/819] Add POSIX shared memory module for multiprocessing on Unix (#6498) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- .cspell.json | 2 + crates/stdlib/src/lib.rs | 6 +++ crates/stdlib/src/posixshmem.rs | 48 ++++++++++++++++++++++ extra_tests/snippets/builtin_posixshmem.py | 12 ++++++ 4 files changed, 68 insertions(+) create mode 100644 crates/stdlib/src/posixshmem.rs create mode 100644 extra_tests/snippets/builtin_posixshmem.py diff --git a/.cspell.json b/.cspell.json index 3bd06fc2032..89cde1ce775 100644 --- a/.cspell.json +++ b/.cspell.json @@ -124,6 +124,8 @@ "wasi", "zelf", // unix + "posixshmem", + "shm", "CLOEXEC", "codeset", "endgrent", diff --git a/crates/stdlib/src/lib.rs b/crates/stdlib/src/lib.rs index c9b5ca32b57..4b463e09c73 100644 --- a/crates/stdlib/src/lib.rs +++ b/crates/stdlib/src/lib.rs @@ -57,6 +57,8 @@ mod faulthandler; mod fcntl; #[cfg(not(target_arch = "wasm32"))] mod multiprocessing; +#[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))] +mod posixshmem; #[cfg(unix)] mod posixsubprocess; // libc is missing constants on redox @@ -190,6 +192,10 @@ pub fn get_module_inits() -> impl Iterator<Item = (Cow<'static, str>, StdlibInit { "_posixsubprocess" => posixsubprocess::make_module, } + #[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))] + { + "_posixshmem" => posixshmem::make_module, + } #[cfg(any(unix, windows))] { "mmap" => mmap::make_module, diff --git a/crates/stdlib/src/posixshmem.rs b/crates/stdlib/src/posixshmem.rs new file mode 100644 index 00000000000..2957f16792c --- /dev/null +++ b/crates/stdlib/src/posixshmem.rs @@ -0,0 +1,48 @@ +#[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))] +pub(crate) use _posixshmem::make_module; + +#[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))] +#[pymodule] +mod _posixshmem { + use std::ffi::CString; + + use crate::{ + common::os::errno_io_error, + vm::{ + PyResult, VirtualMachine, builtins::PyStrRef, convert::IntoPyException, + function::OptionalArg, + }, + }; + + #[pyfunction] + fn shm_open( + name: PyStrRef, + flags: libc::c_int, + mode: OptionalArg<libc::mode_t>, + vm: &VirtualMachine, + ) -> PyResult<libc::c_int> { + let name = CString::new(name.as_str()).map_err(|e| e.into_pyexception(vm))?; + let mode: libc::c_uint = mode.unwrap_or(0o600) as _; + #[cfg(target_os = "freebsd")] + let mode = mode.try_into().unwrap(); + // SAFETY: `name` is a NUL-terminated string and `shm_open` does not write through it. + let fd = unsafe { libc::shm_open(name.as_ptr(), flags, mode) }; + if fd == -1 { + Err(errno_io_error().into_pyexception(vm)) + } else { + Ok(fd) + } + } + + #[pyfunction] + fn shm_unlink(name: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { + let name = CString::new(name.as_str()).map_err(|e| e.into_pyexception(vm))?; + // SAFETY: `name` is a valid NUL-terminated string and `shm_unlink` only reads it. + let ret = unsafe { libc::shm_unlink(name.as_ptr()) }; + if ret == -1 { + Err(errno_io_error().into_pyexception(vm)) + } else { + Ok(()) + } + } +} diff --git a/extra_tests/snippets/builtin_posixshmem.py b/extra_tests/snippets/builtin_posixshmem.py new file mode 100644 index 00000000000..38ace68d584 --- /dev/null +++ b/extra_tests/snippets/builtin_posixshmem.py @@ -0,0 +1,12 @@ +import os +import sys + +if os.name != "posix": + sys.exit(0) + +import _posixshmem + +name = f"/rp_posixshmem_{os.getpid()}" +fd = _posixshmem.shm_open(name, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o600) +os.close(fd) +_posixshmem.shm_unlink(name) From 7eb0fe4984b8b5604f74a9319cdadb5ea1bd2489 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 25 Dec 2025 15:49:44 +0900 Subject: [PATCH 577/819] Fix potential deadlock (#6509) --- crates/vm/src/builtins/classmethod.rs | 8 +++--- crates/vm/src/builtins/property.rs | 33 +++++++++++++----------- crates/vm/src/stdlib/functools.rs | 36 +++++++++++++++++++-------- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/crates/vm/src/builtins/classmethod.rs b/crates/vm/src/builtins/classmethod.rs index 911960bf691..21f1ae4ba10 100644 --- a/crates/vm/src/builtins/classmethod.rs +++ b/crates/vm/src/builtins/classmethod.rs @@ -57,11 +57,11 @@ impl GetDescriptor for PyClassMethod { ) -> PyResult { let (zelf, _obj) = Self::_unwrap(&zelf, obj, vm)?; let cls = cls.unwrap_or_else(|| _obj.class().to_owned().into()); - let call_descr_get: PyResult<PyObjectRef> = zelf.callable.lock().get_attr("__get__", vm); + // Clone and release lock before calling Python code to prevent deadlock + let callable = zelf.callable.lock().clone(); + let call_descr_get: PyResult<PyObjectRef> = callable.get_attr("__get__", vm); match call_descr_get { - Err(_) => Ok(PyBoundMethod::new(cls, zelf.callable.lock().clone()) - .into_ref(&vm.ctx) - .into()), + Err(_) => Ok(PyBoundMethod::new(cls, callable).into_ref(&vm.ctx).into()), Ok(call_descr_get) => call_descr_get.call((cls.clone(), cls), vm), } } diff --git a/crates/vm/src/builtins/property.rs b/crates/vm/src/builtins/property.rs index 41b05a60049..7ea36d39768 100644 --- a/crates/vm/src/builtins/property.rs +++ b/crates/vm/src/builtins/property.rs @@ -55,7 +55,8 @@ impl GetDescriptor for PyProperty { let (zelf, obj) = Self::_unwrap(&zelf_obj, obj, vm)?; if vm.is_none(&obj) { Ok(zelf_obj) - } else if let Some(getter) = zelf.getter.read().as_ref() { + } else if let Some(getter) = zelf.getter.read().clone() { + // Clone and release lock before calling Python code to prevent deadlock getter.call((obj,), vm) } else { let error_msg = zelf.format_property_error(&obj, "getter", vm)?; @@ -70,12 +71,12 @@ impl PyProperty { // Returns the name if available, None if not found, or propagates errors fn get_property_name(&self, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> { // First check if name was set via __set_name__ - if let Some(name) = self.name.read().as_ref() { - return Ok(Some(name.clone())); + if let Some(name) = self.name.read().clone() { + return Ok(Some(name)); } - let getter = self.getter.read(); - let Some(getter) = getter.as_ref() else { + // Clone and release lock before calling Python code to prevent deadlock + let Some(getter) = self.getter.read().clone() else { return Ok(None); }; @@ -105,7 +106,8 @@ impl PyProperty { let zelf = zelf.try_to_ref::<Self>(vm)?; match value { PySetterValue::Assign(value) => { - if let Some(setter) = zelf.setter.read().as_ref() { + // Clone and release lock before calling Python code to prevent deadlock + if let Some(setter) = zelf.setter.read().clone() { setter.call((obj, value), vm).map(drop) } else { let error_msg = zelf.format_property_error(&obj, "setter", vm)?; @@ -113,7 +115,8 @@ impl PyProperty { } } PySetterValue::Delete => { - if let Some(deleter) = zelf.deleter.read().as_ref() { + // Clone and release lock before calling Python code to prevent deadlock + if let Some(deleter) = zelf.deleter.read().clone() { deleter.call((obj,), vm).map(drop) } else { let error_msg = zelf.format_property_error(&obj, "deleter", vm)?; @@ -273,23 +276,24 @@ impl PyProperty { } }; + // Clone and release lock before calling Python code to prevent deadlock // Check getter - if let Some(getter) = self.getter.read().as_ref() - && is_abstract(getter)? + if let Some(getter) = self.getter.read().clone() + && is_abstract(&getter)? { return Ok(vm.ctx.new_bool(true).into()); } // Check setter - if let Some(setter) = self.setter.read().as_ref() - && is_abstract(setter)? + if let Some(setter) = self.setter.read().clone() + && is_abstract(&setter)? { return Ok(vm.ctx.new_bool(true).into()); } // Check deleter - if let Some(deleter) = self.deleter.read().as_ref() - && is_abstract(deleter)? + if let Some(deleter) = self.deleter.read().clone() + && is_abstract(&deleter)? { return Ok(vm.ctx.new_bool(true).into()); } @@ -299,7 +303,8 @@ impl PyProperty { #[pygetset(setter)] fn set___isabstractmethod__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - if let Some(getter) = self.getter.read().to_owned() { + // Clone and release lock before calling Python code to prevent deadlock + if let Some(getter) = self.getter.read().clone() { getter.set_attr("__isabstractmethod__", value, vm)?; } Ok(()) diff --git a/crates/vm/src/stdlib/functools.rs b/crates/vm/src/stdlib/functools.rs index 26dff8b4426..77f352c78fc 100644 --- a/crates/vm/src/stdlib/functools.rs +++ b/crates/vm/src/stdlib/functools.rs @@ -248,15 +248,24 @@ mod _functools { type Args = FuncArgs; fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - let inner = zelf.inner.read(); - let mut combined_args = inner.args.as_slice().to_vec(); + // Clone and release lock before calling Python code to prevent deadlock + let (func, stored_args, keywords) = { + let inner = zelf.inner.read(); + ( + inner.func.clone(), + inner.args.clone(), + inner.keywords.clone(), + ) + }; + + let mut combined_args = stored_args.as_slice().to_vec(); combined_args.extend_from_slice(&args.args); // Merge keywords from self.keywords and args.kwargs let mut final_kwargs = IndexMap::new(); // Add keywords from self.keywords - for (key, value) in &*inner.keywords { + for (key, value) in &*keywords { let key_str = key .downcast::<crate::builtins::PyStr>() .map_err(|_| vm.new_type_error("keywords must be strings"))?; @@ -268,9 +277,7 @@ mod _functools { final_kwargs.insert(key, value); } - inner - .func - .call(FuncArgs::new(combined_args, KwArgs::new(final_kwargs)), vm) + func.call(FuncArgs::new(combined_args, KwArgs::new(final_kwargs)), vm) } } @@ -280,15 +287,24 @@ mod _functools { // Check for recursive repr let obj = zelf.as_object(); if let Some(_guard) = ReprGuard::enter(vm, obj) { - let inner = zelf.inner.read(); - let func_repr = inner.func.repr(vm)?; + // Clone and release lock before calling Python code to prevent deadlock + let (func, args, keywords) = { + let inner = zelf.inner.read(); + ( + inner.func.clone(), + inner.args.clone(), + inner.keywords.clone(), + ) + }; + + let func_repr = func.repr(vm)?; let mut parts = vec![func_repr.as_str().to_owned()]; - for arg in inner.args.as_slice() { + for arg in args.as_slice() { parts.push(arg.repr(vm)?.as_str().to_owned()); } - for (key, value) in inner.keywords.clone() { + for (key, value) in &*keywords { // For string keys, use them directly without quotes let key_part = if let Ok(s) = key.clone().downcast::<crate::builtins::PyStr>() { s.as_str().to_owned() From aaecdd17470b17994a1a090be036db99fce005e6 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:55:19 +0900 Subject: [PATCH 578/819] repr for getset (#6514) --- crates/vm/src/builtins/getset.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/vm/src/builtins/getset.rs b/crates/vm/src/builtins/getset.rs index f56191f5f8b..3fa4667a997 100644 --- a/crates/vm/src/builtins/getset.rs +++ b/crates/vm/src/builtins/getset.rs @@ -7,7 +7,7 @@ use crate::{ builtins::type_::PointerSlot, class::PyClassImpl, function::{IntoPyGetterFunc, IntoPySetterFunc, PyGetterFunc, PySetterFunc, PySetterValue}, - types::GetDescriptor, + types::{GetDescriptor, Representable}, }; #[pyclass(module = false, name = "getset_descriptor")] @@ -96,7 +96,7 @@ impl PyGetSet { } } -#[pyclass(flags(DISALLOW_INSTANTIATION), with(GetDescriptor))] +#[pyclass(flags(DISALLOW_INSTANTIATION), with(GetDescriptor, Representable))] impl PyGetSet { // Descriptor methods @@ -153,6 +153,23 @@ impl PyGetSet { } } +impl Representable for PyGetSet { + #[inline] + fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> { + let class = unsafe { zelf.class.borrow_static() }; + // Special case for object type + if std::ptr::eq(class, vm.ctx.types.object_type) { + Ok(format!("<attribute '{}'>", zelf.name)) + } else { + Ok(format!( + "<attribute '{}' of '{}' objects>", + zelf.name, + class.name() + )) + } + } +} + pub(crate) fn init(context: &Context) { PyGetSet::extend_class(context, context.types.getset_type); } From 151f0746a3e4130d060879f69731b74585e21f38 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:56:41 +0900 Subject: [PATCH 579/819] Implement copyslot (#6505) --- Lib/test/test_typing.py | 1 - crates/stdlib/src/sqlite.rs | 4 +- crates/vm/src/builtins/bool.rs | 2 +- crates/vm/src/builtins/classmethod.rs | 2 +- crates/vm/src/builtins/dict.rs | 4 +- crates/vm/src/builtins/iter.rs | 18 +- crates/vm/src/builtins/list.rs | 6 +- crates/vm/src/builtins/mappingproxy.rs | 10 +- crates/vm/src/builtins/object.rs | 5 +- crates/vm/src/builtins/type.rs | 226 ++++++++++++++++++----- crates/vm/src/builtins/weakproxy.rs | 4 +- crates/vm/src/class.rs | 5 + crates/vm/src/frame.rs | 2 +- crates/vm/src/function/protocol.rs | 40 ++-- crates/vm/src/object/core.rs | 2 +- crates/vm/src/protocol/buffer.rs | 3 +- crates/vm/src/protocol/callable.rs | 2 +- crates/vm/src/protocol/iter.rs | 34 ++-- crates/vm/src/protocol/mapping.rs | 92 +++++---- crates/vm/src/protocol/mod.rs | 4 +- crates/vm/src/protocol/number.rs | 219 ++++++++++------------ crates/vm/src/protocol/object.rs | 92 ++++----- crates/vm/src/protocol/sequence.rs | 141 +++++++++----- crates/vm/src/stdlib/builtins.rs | 2 +- crates/vm/src/stdlib/ctypes/structure.rs | 2 +- crates/vm/src/stdlib/ctypes/union.rs | 2 +- crates/vm/src/stdlib/typing.rs | 7 +- crates/vm/src/types/slot.rs | 125 ++++++++----- crates/vm/src/types/structseq.rs | 25 +-- crates/vm/src/types/zoo.rs | 1 + crates/vm/src/vm/method.rs | 8 +- crates/vm/src/vm/vm_object.rs | 4 +- crates/vm/src/vm/vm_ops.rs | 40 ++-- 33 files changed, 648 insertions(+), 486 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index db0dc916f1a..74ab94eb5c3 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -10496,7 +10496,6 @@ class CustomerModel(ModelBase, init=False): class NoDefaultTests(BaseTestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickling(self): for proto in range(pickle.HIGHEST_PROTOCOL + 1): s = pickle.dumps(NoDefault, proto) diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index 2328f23430a..c760c2830d7 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -488,7 +488,7 @@ mod _sqlite { let text2 = vm.ctx.new_str(text2); let val = callable.call((text1, text2), vm)?; - let Some(val) = val.to_number().index(vm) else { + let Some(val) = val.number().index(vm) else { return Ok(0); }; @@ -2980,7 +2980,7 @@ mod _sqlite { fn bind_parameters(self, parameters: &PyObject, vm: &VirtualMachine) -> PyResult<()> { if let Some(dict) = parameters.downcast_ref::<PyDict>() { self.bind_parameters_name(dict, vm) - } else if let Ok(seq) = PySequence::try_protocol(parameters, vm) { + } else if let Ok(seq) = parameters.try_sequence(vm) { self.bind_parameters_sequence(seq, vm) } else { Err(new_programming_error( diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 6b3ddd8241a..8fee4af3834 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -43,7 +43,7 @@ impl PyObjectRef { return Ok(false); } let rs_bool = if let Some(nb_bool) = self.class().slots.as_number.boolean.load() { - nb_bool(self.as_object().to_number(), vm)? + nb_bool(self.as_object().number(), vm)? } else { // TODO: Fully implement AsNumber and remove this block match vm.get_method(self.clone(), identifier!(vm, __bool__)) { diff --git a/crates/vm/src/builtins/classmethod.rs b/crates/vm/src/builtins/classmethod.rs index 21f1ae4ba10..5b7f9218658 100644 --- a/crates/vm/src/builtins/classmethod.rs +++ b/crates/vm/src/builtins/classmethod.rs @@ -124,7 +124,7 @@ impl PyClassMethod { } #[pyclass( - with(GetDescriptor, Constructor, Representable), + with(GetDescriptor, Constructor, Initializer, Representable), flags(BASETYPE, HAS_DICT) )] impl PyClassMethod { diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index aff7432d067..567e18d6419 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -1145,7 +1145,7 @@ impl ViewSetOps for PyDictKeys {} impl PyDictKeys { #[pymethod] fn __contains__(zelf: PyObjectRef, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { - zelf.to_sequence().contains(&key, vm) + zelf.sequence_unchecked().contains(&key, vm) } #[pygetset] @@ -1210,7 +1210,7 @@ impl ViewSetOps for PyDictItems {} impl PyDictItems { #[pymethod] fn __contains__(zelf: PyObjectRef, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { - zelf.to_sequence().contains(&needle, vm) + zelf.sequence_unchecked().contains(&needle, vm) } #[pygetset] fn mapping(zelf: PyRef<Self>) -> PyMappingProxy { diff --git a/crates/vm/src/builtins/iter.rs b/crates/vm/src/builtins/iter.rs index 56dfc14d164..736303e95ee 100644 --- a/crates/vm/src/builtins/iter.rs +++ b/crates/vm/src/builtins/iter.rs @@ -8,7 +8,7 @@ use crate::{ class::PyClassImpl, function::ArgCallable, object::{Traverse, TraverseFn}, - protocol::{PyIterReturn, PySequence, PySequenceMethods}, + protocol::PyIterReturn, types::{IterNext, Iterable, SelfIter}, }; use rustpython_common::{ @@ -177,9 +177,6 @@ pub fn builtins_reversed(vm: &VirtualMachine) -> &PyObject { #[pyclass(module = false, name = "iterator", traverse)] #[derive(Debug)] pub struct PySequenceIterator { - // cached sequence methods - #[pytraverse(skip)] - seq_methods: &'static PySequenceMethods, internal: PyMutex<PositionIterInternal<PyObjectRef>>, } @@ -193,9 +190,8 @@ impl PyPayload for PySequenceIterator { #[pyclass(with(IterNext, Iterable))] impl PySequenceIterator { pub fn new(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> { - let seq = PySequence::try_protocol(&obj, vm)?; + let _seq = obj.try_sequence(vm)?; Ok(Self { - seq_methods: seq.methods, internal: PyMutex::new(PositionIterInternal::new(obj, 0)), }) } @@ -204,10 +200,7 @@ impl PySequenceIterator { fn __length_hint__(&self, vm: &VirtualMachine) -> PyObjectRef { let internal = self.internal.lock(); if let IterStatus::Active(obj) = &internal.status { - let seq = PySequence { - obj, - methods: self.seq_methods, - }; + let seq = obj.sequence_unchecked(); seq.length(vm) .map(|x| PyInt::from(x).into_pyobject(vm)) .unwrap_or_else(|_| vm.ctx.not_implemented()) @@ -231,10 +224,7 @@ impl SelfIter for PySequenceIterator {} impl IterNext for PySequenceIterator { fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> { zelf.internal.lock().next(|obj, pos| { - let seq = PySequence { - obj, - methods: zelf.seq_methods, - }; + let seq = obj.sequence_unchecked(); PyIterReturn::from_getitem_result(seq.get_item(pos as isize, vm), vm) }) } diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index 13e8864cd1f..12cab27a750 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -354,7 +354,11 @@ where } else { let iter = obj.to_owned().get_iter(vm)?; let iter = iter.iter::<PyObjectRef>(vm)?; - let len = obj.to_sequence().length_opt(vm).transpose()?.unwrap_or(0); + let len = obj + .sequence_unchecked() + .length_opt(vm) + .transpose()? + .unwrap_or(0); let mut v = Vec::with_capacity(len); for x in iter { v.push(f(x?)?); diff --git a/crates/vm/src/builtins/mappingproxy.rs b/crates/vm/src/builtins/mappingproxy.rs index fb8ff5de9cc..475f36cb5a9 100644 --- a/crates/vm/src/builtins/mappingproxy.rs +++ b/crates/vm/src/builtins/mappingproxy.rs @@ -6,7 +6,7 @@ use crate::{ convert::ToPyObject, function::{ArgMapping, OptionalArg, PyComparisonValue}, object::{Traverse, TraverseFn}, - protocol::{PyMapping, PyMappingMethods, PyNumberMethods, PySequenceMethods}, + protocol::{PyMappingMethods, PyNumberMethods, PySequenceMethods}, types::{ AsMapping, AsNumber, AsSequence, Comparable, Constructor, Iterable, PyComparisonOp, Representable, @@ -62,14 +62,12 @@ impl Constructor for PyMappingProxy { type Args = PyObjectRef; fn py_new(_cls: &Py<PyType>, mapping: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { - if let Some(methods) = PyMapping::find_methods(&mapping) + if mapping.mapping_unchecked().check() && !mapping.downcastable::<PyList>() && !mapping.downcastable::<PyTuple>() { return Ok(Self { - mapping: MappingProxyInner::Mapping(ArgMapping::with_methods(mapping, unsafe { - methods.borrow_static() - })), + mapping: MappingProxyInner::Mapping(ArgMapping::new(mapping)), }); } Err(vm.new_type_error(format!( @@ -124,7 +122,7 @@ impl PyMappingProxy { MappingProxyInner::Class(class) => Ok(key .as_interned_str(vm) .is_some_and(|key| class.attributes.read().contains_key(key))), - MappingProxyInner::Mapping(mapping) => mapping.to_sequence().contains(key, vm), + MappingProxyInner::Mapping(mapping) => mapping.sequence_unchecked().contains(key, vm), } } diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 65a11d55314..ca208790f4b 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -320,10 +320,7 @@ impl PyBaseObject { } } PyComparisonOp::Ne => { - let cmp = zelf - .class() - .mro_find_map(|cls| cls.slots.richcompare.load()) - .unwrap(); + let cmp = zelf.class().slots.richcompare.load().unwrap(); let value = match cmp(zelf, other, PyComparisonOp::Eq, vm)? { Either::A(obj) => PyArithmeticValue::from_object(vm, obj) .map(|obj| obj.try_to_bool(vm)) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 68de17f60b6..c2373f26faf 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -23,7 +23,7 @@ use crate::{ convert::ToPyResult, function::{FuncArgs, KwArgs, OptionalArg, PyMethodDef, PySetterValue}, object::{Traverse, TraverseFn}, - protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, + protocol::{PyIterReturn, PyNumberMethods}, types::{ AsNumber, Callable, Constructor, GetAttr, PyTypeFlags, PyTypeSlots, Representable, SetAttr, TypeDataRef, TypeDataRefMut, TypeDataSlot, @@ -64,8 +64,6 @@ pub struct HeapTypeExt { pub name: PyRwLock<PyStrRef>, pub qualname: PyRwLock<PyStrRef>, pub slots: Option<PyRef<PyTuple<PyStrRef>>>, - pub sequence_methods: PySequenceMethods, - pub mapping_methods: PyMappingMethods, pub type_data: PyRwLock<Option<TypeDataSlot>>, } @@ -100,17 +98,6 @@ impl<T> AsRef<T> for PointerSlot<T> { } } -impl<T> PointerSlot<T> { - pub unsafe fn from_heaptype<F>(typ: &PyType, f: F) -> Option<Self> - where - F: FnOnce(&HeapTypeExt) -> &T, - { - typ.heaptype_ext - .as_ref() - .map(|ext| Self(NonNull::from(f(ext)))) - } -} - pub type PyTypeRef = PyRef<PyType>; cfg_if::cfg_if! { @@ -206,8 +193,6 @@ impl PyType { name: PyRwLock::new(name.clone()), qualname: PyRwLock::new(name), slots: None, - sequence_methods: PySequenceMethods::default(), - mapping_methods: PyMappingMethods::default(), type_data: PyRwLock::new(None), }; let base = bases[0].clone(); @@ -331,6 +316,8 @@ impl PyType { slots.basicsize = base.slots.basicsize; } + Self::inherit_readonly_slots(&mut slots, &base); + if let Some(qualname) = attrs.get(identifier!(ctx, __qualname__)) && !qualname.fast_isinstance(ctx.types.str_type) { @@ -387,6 +374,8 @@ impl PyType { slots.basicsize = base.slots.basicsize; } + Self::inherit_readonly_slots(&mut slots, &base); + let bases = PyRwLock::new(vec![base.clone()]); let mro = base.mro_map_collect(|x| x.to_owned()); @@ -404,6 +393,9 @@ impl PyType { None, ); + // Note: inherit_slots is called in PyClassImpl::init_class after + // slots are fully initialized by make_slots() + Self::set_new(&new_type.slots, &new_type.base); let weakref_type = super::PyWeak::static_type(); @@ -420,6 +412,12 @@ impl PyType { } pub(crate) fn init_slots(&self, ctx: &Context) { + // Inherit slots from direct bases (not MRO) + for base in self.bases.read().iter() { + self.inherit_slots(base); + } + + // Wire dunder methods to slots #[allow(clippy::mutable_key_type)] let mut slot_name_set = std::collections::HashSet::new(); @@ -454,6 +452,164 @@ impl PyType { } } + /// Inherit readonly slots from base type at creation time. + /// These slots are not AtomicCell and must be set before the type is used. + fn inherit_readonly_slots(slots: &mut PyTypeSlots, base: &Self) { + if slots.as_buffer.is_none() { + slots.as_buffer = base.slots.as_buffer; + } + } + + /// Inherit slots from base type. typeobject.c: inherit_slots + pub(crate) fn inherit_slots(&self, base: &Self) { + macro_rules! copyslot { + ($slot:ident) => { + if self.slots.$slot.load().is_none() { + if let Some(base_val) = base.slots.$slot.load() { + self.slots.$slot.store(Some(base_val)); + } + } + }; + } + + // Core slots + copyslot!(hash); + copyslot!(call); + copyslot!(str); + copyslot!(repr); + copyslot!(getattro); + copyslot!(setattro); + copyslot!(richcompare); + copyslot!(iter); + copyslot!(iternext); + copyslot!(descr_get); + copyslot!(descr_set); + // Note: init is NOT inherited here because object_init has special + // handling in CPython (checks if type->tp_init != object_init). + // TODO: implement proper init inheritance with object_init check + copyslot!(del); + // new is handled by set_new() + // as_buffer is inherited at type creation time (not AtomicCell) + + // Sub-slots (number, sequence, mapping) + self.inherit_number_slots(base); + self.inherit_sequence_slots(base); + self.inherit_mapping_slots(base); + } + + /// Inherit number sub-slots from base type + fn inherit_number_slots(&self, base: &Self) { + macro_rules! copy_num_slot { + ($slot:ident) => { + if self.slots.as_number.$slot.load().is_none() { + if let Some(base_val) = base.slots.as_number.$slot.load() { + self.slots.as_number.$slot.store(Some(base_val)); + } + } + }; + } + + // Binary operations + copy_num_slot!(add); + copy_num_slot!(right_add); + copy_num_slot!(inplace_add); + copy_num_slot!(subtract); + copy_num_slot!(right_subtract); + copy_num_slot!(inplace_subtract); + copy_num_slot!(multiply); + copy_num_slot!(right_multiply); + copy_num_slot!(inplace_multiply); + copy_num_slot!(remainder); + copy_num_slot!(right_remainder); + copy_num_slot!(inplace_remainder); + copy_num_slot!(divmod); + copy_num_slot!(right_divmod); + copy_num_slot!(power); + copy_num_slot!(right_power); + copy_num_slot!(inplace_power); + + // Bitwise operations + copy_num_slot!(lshift); + copy_num_slot!(right_lshift); + copy_num_slot!(inplace_lshift); + copy_num_slot!(rshift); + copy_num_slot!(right_rshift); + copy_num_slot!(inplace_rshift); + copy_num_slot!(and); + copy_num_slot!(right_and); + copy_num_slot!(inplace_and); + copy_num_slot!(xor); + copy_num_slot!(right_xor); + copy_num_slot!(inplace_xor); + copy_num_slot!(or); + copy_num_slot!(right_or); + copy_num_slot!(inplace_or); + + // Division operations + copy_num_slot!(floor_divide); + copy_num_slot!(right_floor_divide); + copy_num_slot!(inplace_floor_divide); + copy_num_slot!(true_divide); + copy_num_slot!(right_true_divide); + copy_num_slot!(inplace_true_divide); + + // Matrix multiplication + copy_num_slot!(matrix_multiply); + copy_num_slot!(right_matrix_multiply); + copy_num_slot!(inplace_matrix_multiply); + + // Unary operations + copy_num_slot!(negative); + copy_num_slot!(positive); + copy_num_slot!(absolute); + copy_num_slot!(boolean); + copy_num_slot!(invert); + + // Conversion + copy_num_slot!(int); + copy_num_slot!(float); + copy_num_slot!(index); + } + + /// Inherit sequence sub-slots from base type + fn inherit_sequence_slots(&self, base: &Self) { + macro_rules! copy_seq_slot { + ($slot:ident) => { + if self.slots.as_sequence.$slot.load().is_none() { + if let Some(base_val) = base.slots.as_sequence.$slot.load() { + self.slots.as_sequence.$slot.store(Some(base_val)); + } + } + }; + } + + copy_seq_slot!(length); + copy_seq_slot!(concat); + copy_seq_slot!(repeat); + copy_seq_slot!(item); + copy_seq_slot!(ass_item); + copy_seq_slot!(contains); + copy_seq_slot!(inplace_concat); + copy_seq_slot!(inplace_repeat); + } + + /// Inherit mapping sub-slots from base type + fn inherit_mapping_slots(&self, base: &Self) { + macro_rules! copy_map_slot { + ($slot:ident) => { + if self.slots.as_mapping.$slot.load().is_none() { + if let Some(base_val) = base.slots.as_mapping.$slot.load() { + self.slots.as_mapping.$slot.store(Some(base_val)); + } + } + }; + } + + copy_map_slot!(length); + copy_map_slot!(subscript); + copy_map_slot!(ass_subscript); + } + // This is used for class initialization where the vm is not yet available. pub fn set_str_attr<V: Into<PyObjectRef>>( &self, @@ -663,19 +819,6 @@ impl Py<PyType> { .collect() } - pub(crate) fn mro_find_map<F, R>(&self, f: F) -> Option<R> - where - F: Fn(&Self) -> Option<R>, - { - // the hot path will be primitive types which usually hit the result from itself. - // try std::intrinsics::likely once it is stabilized - if let Some(r) = f(self) { - Some(r) - } else { - self.mro.read().iter().find_map(|cls| f(cls)) - } - } - pub fn iter_base_chain(&self) -> impl Iterator<Item = &Self> { std::iter::successors(Some(self), |cls| cls.base.as_deref()) } @@ -1228,8 +1371,6 @@ impl Constructor for PyType { name: PyRwLock::new(name), qualname: PyRwLock::new(qualname), slots: heaptype_slots.clone(), - sequence_methods: PySequenceMethods::default(), - mapping_methods: PyMappingMethods::default(), type_data: PyRwLock::new(None), }; (slots, heaptype_ext) @@ -1414,11 +1555,9 @@ impl GetAttr for PyType { if let Some(ref attr) = mcl_attr { let attr_class = attr.class(); - let has_descr_set = attr_class - .mro_find_map(|cls| cls.slots.descr_set.load()) - .is_some(); + let has_descr_set = attr_class.slots.descr_set.load().is_some(); if has_descr_set { - let descr_get = attr_class.mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = attr_class.slots.descr_get.load(); if let Some(descr_get) = descr_get { let mcl = mcl.to_owned().into(); return descr_get(attr.clone(), Some(zelf.to_owned().into()), Some(mcl), vm); @@ -1429,7 +1568,7 @@ impl GetAttr for PyType { let zelf_attr = zelf.get_attr(name); if let Some(attr) = zelf_attr { - let descr_get = attr.class().mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = attr.class().slots.descr_get.load(); if let Some(descr_get) = descr_get { descr_get(attr, None, Some(zelf.to_owned().into()), vm) } else { @@ -1467,9 +1606,7 @@ impl Py<PyType> { // CPython returns None if __doc__ is not in the type's own dict if let Some(doc_attr) = self.get_direct_attr(vm.ctx.intern_str("__doc__")) { // If it's a descriptor, call its __get__ method - let descr_get = doc_attr - .class() - .mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = doc_attr.class().slots.descr_get.load(); if let Some(descr_get) = descr_get { descr_get(doc_attr, None, Some(self.to_owned().into()), vm) } else { @@ -1545,7 +1682,7 @@ impl SetAttr for PyType { // TODO: pass PyRefExact instead of &str let attr_name = vm.ctx.intern_str(attr_name.as_str()); if let Some(attr) = zelf.get_class_attr(attr_name) { - let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load()); + let descr_set = attr.class().slots.descr_set.load(); if let Some(descriptor) = descr_set { return descriptor(&attr, zelf.to_owned().into(), value, vm); } @@ -1702,7 +1839,9 @@ fn subtype_set_dict(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) - // Call the descriptor's tp_descr_set let descr_set = descr .class() - .mro_find_map(|cls| cls.slots.descr_set.load()) + .slots + .descr_set + .load() .ok_or_else(|| raise_dict_descriptor_error(&obj, vm))?; descr_set(&descr, obj, PySetterValue::Assign(value), vm) } else { @@ -1764,8 +1903,9 @@ pub(crate) fn call_slot_new( } let slot_new = typ - .deref() - .mro_find_map(|cls| cls.slots.new.load()) + .slots + .new + .load() .expect("Should be able to find a new slot somewhere in the mro"); slot_new(subtype, args, vm) } diff --git a/crates/vm/src/builtins/weakproxy.rs b/crates/vm/src/builtins/weakproxy.rs index 6e0e8308dbc..94c54b5459e 100644 --- a/crates/vm/src/builtins/weakproxy.rs +++ b/crates/vm/src/builtins/weakproxy.rs @@ -104,7 +104,9 @@ impl PyWeakProxy { } #[pymethod] fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { - self.try_upgrade(vm)?.to_sequence().contains(&needle, vm) + self.try_upgrade(vm)? + .sequence_unchecked() + .contains(&needle, vm) } fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 40f01d98b00..da860a96289 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -176,6 +176,11 @@ pub trait PyClassImpl: PyClassDef { add_slot_wrapper!(hash, __hash__, Hash, "Return hash(self)."); } + // Inherit slots from base types after slots are fully initialized + for base in class.bases.read().iter() { + class.inherit_slots(base); + } + class.extend_methods(class.slots.methods, ctx); } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index ad50f972aef..d9cc404b47a 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1900,7 +1900,7 @@ impl ExecutingFrame<'_> { // TODO: It was PyMethod before #4873. Check if it's correct. let func = if is_method { - if let Some(descr_get) = func.class().mro_find_map(|cls| cls.slots.descr_get.load()) { + if let Some(descr_get) = func.class().slots.descr_get.load() { let cls = target.class().to_owned().into(); descr_get(func, Some(target), Some(cls), vm)? } else { diff --git a/crates/vm/src/function/protocol.rs b/crates/vm/src/function/protocol.rs index 1e670b96389..a87ef339edd 100644 --- a/crates/vm/src/function/protocol.rs +++ b/crates/vm/src/function/protocol.rs @@ -1,11 +1,11 @@ use super::IntoFuncArgs; use crate::{ AsObject, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, - builtins::{PyDict, PyDictRef, iter::PySequenceIterator}, + builtins::{PyDictRef, iter::PySequenceIterator}, convert::ToPyObject, object::{Traverse, TraverseFn}, - protocol::{PyIter, PyIterIter, PyMapping, PyMappingMethods}, - types::{AsMapping, GenericMethod}, + protocol::{PyIter, PyIterIter, PyMapping}, + types::GenericMethod, }; use std::{borrow::Borrow, marker::PhantomData, ops::Deref}; @@ -104,14 +104,11 @@ where T: TryFromObject, { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { - let iter_fn = { - let cls = obj.class(); - let iter_fn = cls.mro_find_map(|x| x.slots.iter.load()); - if iter_fn.is_none() && !cls.has_attr(identifier!(vm, __getitem__)) { - return Err(vm.new_type_error(format!("'{}' object is not iterable", cls.name()))); - } - iter_fn - }; + let cls = obj.class(); + let iter_fn = cls.slots.iter.load(); + if iter_fn.is_none() && !cls.has_attr(identifier!(vm, __getitem__)) { + return Err(vm.new_type_error(format!("'{}' object is not iterable", cls.name()))); + } Ok(Self { iterable: obj, iter_fn, @@ -123,30 +120,22 @@ where #[derive(Debug, Clone, Traverse)] pub struct ArgMapping { obj: PyObjectRef, - #[pytraverse(skip)] - methods: &'static PyMappingMethods, } impl ArgMapping { #[inline] - pub const fn with_methods(obj: PyObjectRef, methods: &'static PyMappingMethods) -> Self { - Self { obj, methods } + pub const fn new(obj: PyObjectRef) -> Self { + Self { obj } } #[inline(always)] pub fn from_dict_exact(dict: PyDictRef) -> Self { - Self { - obj: dict.into(), - methods: PyDict::as_mapping(), - } + Self { obj: dict.into() } } #[inline(always)] pub fn mapping(&self) -> PyMapping<'_> { - PyMapping { - obj: &self.obj, - methods: self.methods, - } + self.obj.mapping_unchecked() } } @@ -188,9 +177,8 @@ impl ToPyObject for ArgMapping { impl TryFromObject for ArgMapping { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { - let mapping = PyMapping::try_protocol(&obj, vm)?; - let methods = mapping.methods; - Ok(Self { obj, methods }) + let _mapping = obj.try_mapping(vm)?; + Ok(Self { obj }) } } diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 60b623ef3ed..a092d7097be 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -810,7 +810,7 @@ impl PyObject { } // CPython-compatible drop implementation - let del = self.class().mro_find_map(|cls| cls.slots.del.load()); + let del = self.class().slots.del.load(); if let Some(slot_del) = del { call_slot_del(self, slot_del)?; } diff --git a/crates/vm/src/protocol/buffer.rs b/crates/vm/src/protocol/buffer.rs index 0a34af59080..88524a9a9ee 100644 --- a/crates/vm/src/protocol/buffer.rs +++ b/crates/vm/src/protocol/buffer.rs @@ -143,8 +143,7 @@ impl PyBuffer { impl<'a> TryFromBorrowedObject<'a> for PyBuffer { fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> { let cls = obj.class(); - let as_buffer = cls.mro_find_map(|cls| cls.slots.as_buffer); - if let Some(f) = as_buffer { + if let Some(f) = cls.slots.as_buffer { return f(obj, vm); } Err(vm.new_type_error(format!( diff --git a/crates/vm/src/protocol/callable.rs b/crates/vm/src/protocol/callable.rs index 1444b6bf73a..5280e04e928 100644 --- a/crates/vm/src/protocol/callable.rs +++ b/crates/vm/src/protocol/callable.rs @@ -42,7 +42,7 @@ pub struct PyCallable<'a> { impl<'a> PyCallable<'a> { pub fn new(obj: &'a PyObject) -> Option<Self> { - let call = obj.class().mro_find_map(|cls| cls.slots.call.load())?; + let call = obj.class().slots.call.load()?; Some(PyCallable { obj, call }) } diff --git a/crates/vm/src/protocol/iter.rs b/crates/vm/src/protocol/iter.rs index 18f2b5243e2..f6146543de9 100644 --- a/crates/vm/src/protocol/iter.rs +++ b/crates/vm/src/protocol/iter.rs @@ -23,9 +23,7 @@ unsafe impl<O: Borrow<PyObject>> Traverse for PyIter<O> { impl PyIter<PyObjectRef> { pub fn check(obj: &PyObject) -> bool { - obj.class() - .mro_find_map(|x| x.slots.iternext.load()) - .is_some() + obj.class().slots.iternext.load().is_some() } } @@ -37,18 +35,19 @@ where Self(obj) } pub fn next(&self, vm: &VirtualMachine) -> PyResult<PyIterReturn> { - let iternext = { - self.0 - .borrow() - .class() - .mro_find_map(|x| x.slots.iternext.load()) - .ok_or_else(|| { - vm.new_type_error(format!( - "'{}' object is not an iterator", - self.0.borrow().class().name() - )) - })? - }; + let iternext = self + .0 + .borrow() + .class() + .slots + .iternext + .load() + .ok_or_else(|| { + vm.new_type_error(format!( + "'{}' object is not an iterator", + self.0.borrow().class().name() + )) + })?; iternext(self.0.borrow(), vm) } @@ -126,10 +125,7 @@ impl TryFromObject for PyIter<PyObjectRef> { // in the vm when a for loop is entered. Next, it is used when the builtin // function 'iter' is called. fn try_from_object(vm: &VirtualMachine, iter_target: PyObjectRef) -> PyResult<Self> { - let get_iter = { - let cls = iter_target.class(); - cls.mro_find_map(|x| x.slots.iter.load()) - }; + let get_iter = iter_target.class().slots.iter.load(); if let Some(get_iter) = get_iter { let iter = get_iter(iter_target, vm)?; if Self::check(&iter) { diff --git a/crates/vm/src/protocol/mapping.rs b/crates/vm/src/protocol/mapping.rs index a942303dbb4..36813bf1df8 100644 --- a/crates/vm/src/protocol/mapping.rs +++ b/crates/vm/src/protocol/mapping.rs @@ -3,7 +3,6 @@ use crate::{ builtins::{ PyDict, PyStrInterned, dict::{PyDictItems, PyDictKeys, PyDictValues}, - type_::PointerSlot, }, convert::ToPyResult, object::{Traverse, TraverseFn}, @@ -13,9 +12,38 @@ use crossbeam_utils::atomic::AtomicCell; // Mapping protocol // https://docs.python.org/3/c-api/mapping.html -impl PyObject { - pub fn to_mapping(&self) -> PyMapping<'_> { - PyMapping::from(self) +#[allow(clippy::type_complexity)] +#[derive(Default)] +pub struct PyMappingSlots { + pub length: AtomicCell<Option<fn(PyMapping<'_>, &VirtualMachine) -> PyResult<usize>>>, + pub subscript: AtomicCell<Option<fn(PyMapping<'_>, &PyObject, &VirtualMachine) -> PyResult>>, + pub ass_subscript: AtomicCell< + Option<fn(PyMapping<'_>, &PyObject, Option<PyObjectRef>, &VirtualMachine) -> PyResult<()>>, + >, +} + +impl std::fmt::Debug for PyMappingSlots { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("PyMappingSlots") + } +} + +impl PyMappingSlots { + pub fn has_subscript(&self) -> bool { + self.subscript.load().is_some() + } + + /// Copy from static PyMappingMethods + pub fn copy_from(&self, methods: &PyMappingMethods) { + if let Some(f) = methods.length.load() { + self.length.store(Some(f)); + } + if let Some(f) = methods.subscript.load() { + self.subscript.store(Some(f)); + } + if let Some(f) = methods.ass_subscript.load() { + self.ass_subscript.store(Some(f)); + } } } @@ -31,15 +59,11 @@ pub struct PyMappingMethods { impl std::fmt::Debug for PyMappingMethods { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "mapping methods") + f.write_str("PyMappingMethods") } } impl PyMappingMethods { - fn check(&self) -> bool { - self.subscript.load().is_some() - } - #[allow(clippy::declare_interior_mutable_const)] pub const NOT_IMPLEMENTED: Self = Self { length: AtomicCell::new(None), @@ -48,19 +72,24 @@ impl PyMappingMethods { }; } -impl<'a> From<&'a PyObject> for PyMapping<'a> { - fn from(obj: &'a PyObject) -> Self { - static GLOBAL_NOT_IMPLEMENTED: PyMappingMethods = PyMappingMethods::NOT_IMPLEMENTED; - let methods = Self::find_methods(obj) - .map_or(&GLOBAL_NOT_IMPLEMENTED, |x| unsafe { x.borrow_static() }); - Self { obj, methods } +impl PyObject { + pub fn mapping_unchecked(&self) -> PyMapping<'_> { + PyMapping { obj: self } + } + + pub fn try_mapping(&self, vm: &VirtualMachine) -> PyResult<PyMapping<'_>> { + let mapping = self.mapping_unchecked(); + if mapping.check() { + Ok(mapping) + } else { + Err(vm.new_type_error(format!("{} is not a mapping object", self.class()))) + } } } #[derive(Copy, Clone)] pub struct PyMapping<'a> { pub obj: &'a PyObject, - pub methods: &'static PyMappingMethods, } unsafe impl Traverse for PyMapping<'_> { @@ -76,34 +105,19 @@ impl AsRef<PyObject> for PyMapping<'_> { } } -impl<'a> PyMapping<'a> { - pub fn try_protocol(obj: &'a PyObject, vm: &VirtualMachine) -> PyResult<Self> { - if let Some(methods) = Self::find_methods(obj) - && methods.as_ref().check() - { - return Ok(Self { - obj, - methods: unsafe { methods.borrow_static() }, - }); - } - - Err(vm.new_type_error(format!("{} is not a mapping object", obj.class()))) - } -} - impl PyMapping<'_> { - // PyMapping::Check #[inline] - pub fn check(obj: &PyObject) -> bool { - Self::find_methods(obj).is_some_and(|x| x.as_ref().check()) + pub fn slots(&self) -> &PyMappingSlots { + &self.obj.class().slots.as_mapping } - pub fn find_methods(obj: &PyObject) -> Option<PointerSlot<PyMappingMethods>> { - obj.class().mro_find_map(|cls| cls.slots.as_mapping.load()) + #[inline] + pub fn check(&self) -> bool { + self.slots().has_subscript() } pub fn length_opt(self, vm: &VirtualMachine) -> Option<PyResult<usize>> { - self.methods.length.load().map(|f| f(self, vm)) + self.slots().length.load().map(|f| f(self, vm)) } pub fn length(self, vm: &VirtualMachine) -> PyResult<usize> { @@ -130,7 +144,7 @@ impl PyMapping<'_> { fn _subscript(self, needle: &PyObject, vm: &VirtualMachine) -> PyResult { let f = - self.methods.subscript.load().ok_or_else(|| { + self.slots().subscript.load().ok_or_else(|| { vm.new_type_error(format!("{} is not a mapping", self.obj.class())) })?; f(self, needle, vm) @@ -142,7 +156,7 @@ impl PyMapping<'_> { value: Option<PyObjectRef>, vm: &VirtualMachine, ) -> PyResult<()> { - let f = self.methods.ass_subscript.load().ok_or_else(|| { + let f = self.slots().ass_subscript.load().ok_or_else(|| { vm.new_type_error(format!( "'{}' object does not support item assignment", self.obj.class() diff --git a/crates/vm/src/protocol/mod.rs b/crates/vm/src/protocol/mod.rs index d5c7e239a24..e7be286c265 100644 --- a/crates/vm/src/protocol/mod.rs +++ b/crates/vm/src/protocol/mod.rs @@ -9,9 +9,9 @@ mod sequence; pub use buffer::{BufferDescriptor, BufferMethods, BufferResizeGuard, PyBuffer, VecBuffer}; pub use callable::PyCallable; pub use iter::{PyIter, PyIterIter, PyIterReturn}; -pub use mapping::{PyMapping, PyMappingMethods}; +pub use mapping::{PyMapping, PyMappingMethods, PyMappingSlots}; pub use number::{ PyNumber, PyNumberBinaryFunc, PyNumberBinaryOp, PyNumberMethods, PyNumberSlots, PyNumberTernaryOp, PyNumberUnaryFunc, handle_bytes_to_int_err, }; -pub use sequence::{PySequence, PySequenceMethods}; +pub use sequence::{PySequence, PySequenceMethods, PySequenceSlots}; diff --git a/crates/vm/src/protocol/number.rs b/crates/vm/src/protocol/number.rs index 1242ee52795..4f21a1e64f9 100644 --- a/crates/vm/src/protocol/number.rs +++ b/crates/vm/src/protocol/number.rs @@ -20,8 +20,8 @@ pub type PyNumberTernaryFunc = fn(&PyObject, &PyObject, &PyObject, &VirtualMachi impl PyObject { #[inline] - pub const fn to_number(&self) -> PyNumber<'_> { - PyNumber(self) + pub const fn number(&self) -> PyNumber<'_> { + PyNumber { obj: self } } pub fn try_index_opt(&self, vm: &VirtualMachine) -> Option<PyResult<PyIntRef>> { @@ -30,7 +30,7 @@ impl PyObject { } else if let Some(i) = self.downcast_ref::<PyInt>() { Some(Ok(vm.ctx.new_bigint(i.as_bigint()))) } else { - self.to_number().index(vm) + self.number().index(vm) } } @@ -56,7 +56,7 @@ impl PyObject { if let Some(i) = self.downcast_ref_if_exact::<PyInt>(vm) { Ok(i.to_owned()) - } else if let Some(i) = self.to_number().int(vm).or_else(|| self.try_index_opt(vm)) { + } else if let Some(i) = self.number().int(vm).or_else(|| self.try_index_opt(vm)) { i } else if let Ok(Some(f)) = vm.get_special_method(self, identifier!(vm, __trunc__)) { warnings::warn( @@ -92,7 +92,7 @@ impl PyObject { pub fn try_float_opt(&self, vm: &VirtualMachine) -> Option<PyResult<PyRef<PyFloat>>> { if let Some(float) = self.downcast_ref_if_exact::<PyFloat>(vm) { Some(Ok(float.to_owned())) - } else if let Some(f) = self.to_number().float(vm) { + } else if let Some(f) = self.number().float(vm) { Some(f) } else { self.try_index_opt(vm) @@ -420,11 +420,13 @@ impl PyNumberSlots { } } #[derive(Copy, Clone)] -pub struct PyNumber<'a>(&'a PyObject); +pub struct PyNumber<'a> { + pub obj: &'a PyObject, +} unsafe impl Traverse for PyNumber<'_> { fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { - self.0.traverse(tracer_fn) + self.obj.traverse(tracer_fn) } } @@ -432,36 +434,17 @@ impl Deref for PyNumber<'_> { type Target = PyObject; fn deref(&self) -> &Self::Target { - self.0 + self.obj } } impl<'a> PyNumber<'a> { - pub(crate) const fn obj(self) -> &'a PyObject { - self.0 - } - - // PyNumber_Check + // PyNumber_Check - slots are now inherited pub fn check(obj: &PyObject) -> bool { - let cls = &obj.class(); - // TODO: when we finally have a proper slot inheritance, mro_find_map can be removed - // methods.int.load().is_some() - // || methods.index.load().is_some() - // || methods.float.load().is_some() - // || obj.downcastable::<PyComplex>() - let has_number = cls - .mro_find_map(|x| { - let methods = &x.slots.as_number; - if methods.int.load().is_some() - || methods.index.load().is_some() - || methods.float.load().is_some() - { - Some(()) - } else { - None - } - }) - .is_some(); + let methods = &obj.class().slots.as_number; + let has_number = methods.int.load().is_some() + || methods.index.load().is_some() + || methods.float.load().is_some(); has_number || obj.downcastable::<PyComplex>() } } @@ -469,114 +452,106 @@ impl<'a> PyNumber<'a> { impl PyNumber<'_> { // PyIndex_Check pub fn is_index(self) -> bool { - self.class() - .mro_find_map(|x| x.slots.as_number.index.load()) - .is_some() + self.class().slots.as_number.index.load().is_some() } #[inline] pub fn int(self, vm: &VirtualMachine) -> Option<PyResult<PyIntRef>> { - self.class() - .mro_find_map(|x| x.slots.as_number.int.load()) - .map(|f| { - let ret = f(self, vm)?; - - if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) { - return Ok(ret.to_owned()); - } - - let ret_class = ret.class().to_owned(); - if let Some(ret) = ret.downcast_ref::<PyInt>() { - warnings::warn( - vm.ctx.exceptions.deprecation_warning, - format!( - "__int__ returned non-int (type {ret_class}). \ + self.class().slots.as_number.int.load().map(|f| { + let ret = f(self, vm)?; + + if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) { + return Ok(ret.to_owned()); + } + + let ret_class = ret.class().to_owned(); + if let Some(ret) = ret.downcast_ref::<PyInt>() { + warnings::warn( + vm.ctx.exceptions.deprecation_warning, + format!( + "__int__ returned non-int (type {ret_class}). \ The ability to return an instance of a strict subclass of int \ is deprecated, and may be removed in a future version of Python." - ), - 1, - vm, - )?; - - Ok(ret.to_owned()) - } else { - Err(vm.new_type_error(format!( - "{}.__int__ returned non-int(type {})", - self.class(), - ret_class - ))) - } - }) + ), + 1, + vm, + )?; + + Ok(ret.to_owned()) + } else { + Err(vm.new_type_error(format!( + "{}.__int__ returned non-int(type {})", + self.class(), + ret_class + ))) + } + }) } #[inline] pub fn index(self, vm: &VirtualMachine) -> Option<PyResult<PyIntRef>> { - self.class() - .mro_find_map(|x| x.slots.as_number.index.load()) - .map(|f| { - let ret = f(self, vm)?; - - if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) { - return Ok(ret.to_owned()); - } - - let ret_class = ret.class().to_owned(); - if let Some(ret) = ret.downcast_ref::<PyInt>() { - warnings::warn( - vm.ctx.exceptions.deprecation_warning, - format!( - "__index__ returned non-int (type {ret_class}). \ + self.class().slots.as_number.index.load().map(|f| { + let ret = f(self, vm)?; + + if let Some(ret) = ret.downcast_ref_if_exact::<PyInt>(vm) { + return Ok(ret.to_owned()); + } + + let ret_class = ret.class().to_owned(); + if let Some(ret) = ret.downcast_ref::<PyInt>() { + warnings::warn( + vm.ctx.exceptions.deprecation_warning, + format!( + "__index__ returned non-int (type {ret_class}). \ The ability to return an instance of a strict subclass of int \ is deprecated, and may be removed in a future version of Python." - ), - 1, - vm, - )?; - - Ok(ret.to_owned()) - } else { - Err(vm.new_type_error(format!( - "{}.__index__ returned non-int(type {})", - self.class(), - ret_class - ))) - } - }) + ), + 1, + vm, + )?; + + Ok(ret.to_owned()) + } else { + Err(vm.new_type_error(format!( + "{}.__index__ returned non-int(type {})", + self.class(), + ret_class + ))) + } + }) } #[inline] pub fn float(self, vm: &VirtualMachine) -> Option<PyResult<PyRef<PyFloat>>> { - self.class() - .mro_find_map(|x| x.slots.as_number.float.load()) - .map(|f| { - let ret = f(self, vm)?; - - if let Some(ret) = ret.downcast_ref_if_exact::<PyFloat>(vm) { - return Ok(ret.to_owned()); - } - - let ret_class = ret.class().to_owned(); - if let Some(ret) = ret.downcast_ref::<PyFloat>() { - warnings::warn( - vm.ctx.exceptions.deprecation_warning, - format!( - "__float__ returned non-float (type {ret_class}). \ + self.class().slots.as_number.float.load().map(|f| { + let ret = f(self, vm)?; + + if let Some(ret) = ret.downcast_ref_if_exact::<PyFloat>(vm) { + return Ok(ret.to_owned()); + } + + let ret_class = ret.class().to_owned(); + if let Some(ret) = ret.downcast_ref::<PyFloat>() { + warnings::warn( + vm.ctx.exceptions.deprecation_warning, + format!( + "__float__ returned non-float (type {ret_class}). \ The ability to return an instance of a strict subclass of float \ is deprecated, and may be removed in a future version of Python." - ), - 1, - vm, - )?; - - Ok(ret.to_owned()) - } else { - Err(vm.new_type_error(format!( - "{}.__float__ returned non-float(type {})", - self.class(), - ret_class - ))) - } - }) + ), + 1, + vm, + )?; + + Ok(ret.to_owned()) + } else { + Err(vm.new_type_error(format!( + "{}.__float__ returned non-float(type {})", + self.class(), + ret_class + ))) + } + }) } } diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index f2e52a94004..ec1a6f55969 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -12,7 +12,7 @@ use crate::{ dict_inner::DictKey, function::{Either, FuncArgs, PyArithmeticValue, PySetterValue}, object::PyPayload, - protocol::{PyIter, PyMapping, PySequence}, + protocol::PyIter, types::{Constructor, PyComparisonOp}, }; @@ -136,10 +136,7 @@ impl PyObject { #[inline] pub(crate) fn get_attr_inner(&self, attr_name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult { vm_trace!("object.__getattribute__: {:?} {:?}", self, attr_name); - let getattro = self - .class() - .mro_find_map(|cls| cls.slots.getattro.load()) - .unwrap(); + let getattro = self.class().slots.getattro.load().unwrap(); getattro(self, attr_name, vm).inspect_err(|exc| { vm.set_attribute_error_context(exc, self.to_owned(), attr_name.to_owned()); }) @@ -153,21 +150,20 @@ impl PyObject { ) -> PyResult<()> { let setattro = { let cls = self.class(); - cls.mro_find_map(|cls| cls.slots.setattro.load()) - .ok_or_else(|| { - let has_getattr = cls.mro_find_map(|cls| cls.slots.getattro.load()).is_some(); - vm.new_type_error(format!( - "'{}' object has {} attributes ({} {})", - cls.name(), - if has_getattr { "only read-only" } else { "no" }, - if attr_value.is_assign() { - "assign to" - } else { - "del" - }, - attr_name - )) - })? + cls.slots.setattro.load().ok_or_else(|| { + let has_getattr = cls.slots.getattro.load().is_some(); + vm.new_type_error(format!( + "'{}' object has {} attributes ({} {})", + cls.name(), + if has_getattr { "only read-only" } else { "no" }, + if attr_value.is_assign() { + "assign to" + } else { + "del" + }, + attr_name + )) + })? }; setattro(self, attr_name, attr_value, vm) } @@ -197,7 +193,7 @@ impl PyObject { .interned_str(attr_name) .and_then(|attr_name| self.get_class_attr(attr_name)) { - let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load()); + let descr_set = attr.class().slots.descr_set.load(); if let Some(descriptor) = descr_set { return descriptor(&attr, self.to_owned(), value, vm); } @@ -239,11 +235,9 @@ impl PyObject { let cls_attr = match cls_attr_name.and_then(|name| obj_cls.get_attr(name)) { Some(descr) => { let descr_cls = descr.class(); - let descr_get = descr_cls.mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = descr_cls.slots.descr_get.load(); if let Some(descr_get) = descr_get - && descr_cls - .mro_find_map(|cls| cls.slots.descr_set.load()) - .is_some() + && descr_cls.slots.descr_set.load().is_some() { let cls = obj_cls.to_owned().into(); return descr_get(descr, Some(self.to_owned()), Some(cls), vm).map(Some); @@ -293,10 +287,7 @@ impl PyObject { ) -> PyResult<Either<PyObjectRef, bool>> { let swapped = op.swapped(); let call_cmp = |obj: &Self, other: &Self, op| { - let cmp = obj - .class() - .mro_find_map(|cls| cls.slots.richcompare.load()) - .unwrap(); + let cmp = obj.class().slots.richcompare.load().unwrap(); let r = match cmp(obj, other, op, vm)? { Either::A(obj) => PyArithmeticValue::from_object(vm, obj).map(Either::A), Either::B(arithmetic) => arithmetic.map(Either::B), @@ -353,18 +344,15 @@ impl PyObject { pub fn repr(&self, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> { vm.with_recursion("while getting the repr of an object", || { - // TODO: RustPython does not implement type slots inheritance yet - self.class() - .mro_find_map(|cls| cls.slots.repr.load()) - .map_or_else( - || { - Err(vm.new_runtime_error(format!( + self.class().slots.repr.load().map_or_else( + || { + Err(vm.new_runtime_error(format!( "BUG: object of type '{}' has no __repr__ method. This is a bug in RustPython.", self.class().name() ))) - }, - |repr| repr(self, vm), - ) + }, + |repr| repr(self, vm), + ) }) } @@ -659,7 +647,7 @@ impl PyObject { } pub fn hash(&self, vm: &VirtualMachine) -> PyResult<PyHash> { - if let Some(hash) = self.class().mro_find_map(|cls| cls.slots.hash.load()) { + if let Some(hash) = self.class().slots.hash.load() { return hash(self, vm); } @@ -681,9 +669,9 @@ impl PyObject { } pub fn length_opt(&self, vm: &VirtualMachine) -> Option<PyResult<usize>> { - self.to_sequence() + self.sequence_unchecked() .length_opt(vm) - .or_else(|| self.to_mapping().length_opt(vm)) + .or_else(|| self.mapping_unchecked().length_opt(vm)) } pub fn length(&self, vm: &VirtualMachine) -> PyResult<usize> { @@ -702,9 +690,9 @@ impl PyObject { let needle = needle.to_pyobject(vm); - if let Ok(mapping) = PyMapping::try_protocol(self, vm) { + if let Ok(mapping) = self.try_mapping(vm) { mapping.subscript(&needle, vm) - } else if let Ok(seq) = PySequence::try_protocol(self, vm) { + } else if let Ok(seq) = self.try_sequence(vm) { let i = needle.key_as_isize(vm)?; seq.get_item(i, vm) } else { @@ -734,14 +722,14 @@ impl PyObject { return dict.set_item(needle, value, vm); } - let mapping = self.to_mapping(); - if let Some(f) = mapping.methods.ass_subscript.load() { + let mapping = self.mapping_unchecked(); + if let Some(f) = mapping.slots().ass_subscript.load() { let needle = needle.to_pyobject(vm); return f(mapping, &needle, Some(value), vm); } - let seq = self.to_sequence(); - if let Some(f) = seq.methods.ass_item.load() { + let seq = self.sequence_unchecked(); + if let Some(f) = seq.slots().ass_item.load() { let i = needle.key_as_isize(vm)?; return f(seq, i, Some(value), vm); } @@ -757,13 +745,13 @@ impl PyObject { return dict.del_item(needle, vm); } - let mapping = self.to_mapping(); - if let Some(f) = mapping.methods.ass_subscript.load() { + let mapping = self.mapping_unchecked(); + if let Some(f) = mapping.slots().ass_subscript.load() { let needle = needle.to_pyobject(vm); return f(mapping, &needle, None, vm); } - let seq = self.to_sequence(); - if let Some(f) = seq.methods.ass_item.load() { + let seq = self.sequence_unchecked(); + if let Some(f) = seq.slots().ass_item.load() { let i = needle.key_as_isize(vm)?; return f(seq, i, None, vm); } @@ -781,7 +769,7 @@ impl PyObject { let res = obj_cls.lookup_ref(attr, vm)?; // If it's a descriptor, call its __get__ method - let descr_get = res.class().mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = res.class().slots.descr_get.load(); if let Some(descr_get) = descr_get { let obj_cls = obj_cls.to_owned().into(); // CPython ignores exceptions in _PyObject_LookupSpecial and returns NULL diff --git a/crates/vm/src/protocol/sequence.rs b/crates/vm/src/protocol/sequence.rs index fb71446a5a4..a7576e63efb 100644 --- a/crates/vm/src/protocol/sequence.rs +++ b/crates/vm/src/protocol/sequence.rs @@ -1,26 +1,70 @@ use crate::{ PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyList, PyListRef, PySlice, PyTuple, PyTupleRef, type_::PointerSlot}, + builtins::{PyList, PyListRef, PySlice, PyTuple, PyTupleRef}, convert::ToPyObject, function::PyArithmeticValue, object::{Traverse, TraverseFn}, - protocol::{PyMapping, PyNumberBinaryOp}, + protocol::PyNumberBinaryOp, }; use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; -use std::fmt::Debug; // Sequence Protocol // https://docs.python.org/3/c-api/sequence.html -impl PyObject { - #[inline] - pub fn to_sequence(&self) -> PySequence<'_> { - static GLOBAL_NOT_IMPLEMENTED: PySequenceMethods = PySequenceMethods::NOT_IMPLEMENTED; - PySequence { - obj: self, - methods: PySequence::find_methods(self) - .map_or(&GLOBAL_NOT_IMPLEMENTED, |x| unsafe { x.borrow_static() }), +#[allow(clippy::type_complexity)] +#[derive(Default)] +pub struct PySequenceSlots { + pub length: AtomicCell<Option<fn(PySequence<'_>, &VirtualMachine) -> PyResult<usize>>>, + pub concat: AtomicCell<Option<fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult>>, + pub repeat: AtomicCell<Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>>, + pub item: AtomicCell<Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>>, + pub ass_item: AtomicCell< + Option<fn(PySequence<'_>, isize, Option<PyObjectRef>, &VirtualMachine) -> PyResult<()>>, + >, + pub contains: + AtomicCell<Option<fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult<bool>>>, + pub inplace_concat: + AtomicCell<Option<fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult>>, + pub inplace_repeat: AtomicCell<Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>>, +} + +impl std::fmt::Debug for PySequenceSlots { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("PySequenceSlots") + } +} + +impl PySequenceSlots { + pub fn has_item(&self) -> bool { + self.item.load().is_some() + } + + /// Copy from static PySequenceMethods + pub fn copy_from(&self, methods: &PySequenceMethods) { + if let Some(f) = methods.length.load() { + self.length.store(Some(f)); + } + if let Some(f) = methods.concat.load() { + self.concat.store(Some(f)); + } + if let Some(f) = methods.repeat.load() { + self.repeat.store(Some(f)); + } + if let Some(f) = methods.item.load() { + self.item.store(Some(f)); + } + if let Some(f) = methods.ass_item.load() { + self.ass_item.store(Some(f)); + } + if let Some(f) = methods.contains.load() { + self.contains.store(Some(f)); + } + if let Some(f) = methods.inplace_concat.load() { + self.inplace_concat.store(Some(f)); + } + if let Some(f) = methods.inplace_repeat.load() { + self.inplace_repeat.store(Some(f)); } } } @@ -42,9 +86,9 @@ pub struct PySequenceMethods { pub inplace_repeat: AtomicCell<Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>>, } -impl Debug for PySequenceMethods { +impl std::fmt::Debug for PySequenceMethods { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Sequence Methods") + f.write_str("PySequenceMethods") } } @@ -62,10 +106,25 @@ impl PySequenceMethods { }; } +impl PyObject { + #[inline] + pub fn sequence_unchecked(&self) -> PySequence<'_> { + PySequence { obj: self } + } + + pub fn try_sequence(&self, vm: &VirtualMachine) -> PyResult<PySequence<'_>> { + let seq = self.sequence_unchecked(); + if seq.check() { + Ok(seq) + } else { + Err(vm.new_type_error(format!("'{}' is not a sequence", self.class()))) + } + } +} + #[derive(Copy, Clone)] pub struct PySequence<'a> { pub obj: &'a PyObject, - pub methods: &'static PySequenceMethods, } unsafe impl Traverse for PySequence<'_> { @@ -74,34 +133,18 @@ unsafe impl Traverse for PySequence<'_> { } } -impl<'a> PySequence<'a> { +impl PySequence<'_> { #[inline] - pub const fn with_methods(obj: &'a PyObject, methods: &'static PySequenceMethods) -> Self { - Self { obj, methods } + pub fn slots(&self) -> &PySequenceSlots { + &self.obj.class().slots.as_sequence } - pub fn try_protocol(obj: &'a PyObject, vm: &VirtualMachine) -> PyResult<Self> { - let seq = obj.to_sequence(); - if seq.check() { - Ok(seq) - } else { - Err(vm.new_type_error(format!("'{}' is not a sequence", obj.class()))) - } - } -} - -impl PySequence<'_> { pub fn check(&self) -> bool { - self.methods.item.load().is_some() - } - - pub fn find_methods(obj: &PyObject) -> Option<PointerSlot<PySequenceMethods>> { - let cls = obj.class(); - cls.mro_find_map(|x| x.slots.as_sequence.load()) + self.slots().has_item() } pub fn length_opt(self, vm: &VirtualMachine) -> Option<PyResult<usize>> { - self.methods.length.load().map(|f| f(self, vm)) + self.slots().length.load().map(|f| f(self, vm)) } pub fn length(self, vm: &VirtualMachine) -> PyResult<usize> { @@ -114,12 +157,12 @@ impl PySequence<'_> { } pub fn concat(self, other: &PyObject, vm: &VirtualMachine) -> PyResult { - if let Some(f) = self.methods.concat.load() { + if let Some(f) = self.slots().concat.load() { return f(self, other, vm); } // if both arguments appear to be sequences, try fallback to __add__ - if self.check() && other.to_sequence().check() { + if self.check() && other.sequence_unchecked().check() { let ret = vm.binary_op1(self.obj, other, PyNumberBinaryOp::Add)?; if let PyArithmeticValue::Implemented(ret) = PyArithmeticValue::from_object(vm, ret) { return Ok(ret); @@ -133,7 +176,7 @@ impl PySequence<'_> { } pub fn repeat(self, n: isize, vm: &VirtualMachine) -> PyResult { - if let Some(f) = self.methods.repeat.load() { + if let Some(f) = self.slots().repeat.load() { return f(self, n, vm); } @@ -149,15 +192,15 @@ impl PySequence<'_> { } pub fn inplace_concat(self, other: &PyObject, vm: &VirtualMachine) -> PyResult { - if let Some(f) = self.methods.inplace_concat.load() { + if let Some(f) = self.slots().inplace_concat.load() { return f(self, other, vm); } - if let Some(f) = self.methods.concat.load() { + if let Some(f) = self.slots().concat.load() { return f(self, other, vm); } // if both arguments appear to be sequences, try fallback to __iadd__ - if self.check() && other.to_sequence().check() { + if self.check() && other.sequence_unchecked().check() { let ret = vm._iadd(self.obj, other)?; if let PyArithmeticValue::Implemented(ret) = PyArithmeticValue::from_object(vm, ret) { return Ok(ret); @@ -171,10 +214,10 @@ impl PySequence<'_> { } pub fn inplace_repeat(self, n: isize, vm: &VirtualMachine) -> PyResult { - if let Some(f) = self.methods.inplace_repeat.load() { + if let Some(f) = self.slots().inplace_repeat.load() { return f(self, n, vm); } - if let Some(f) = self.methods.repeat.load() { + if let Some(f) = self.slots().repeat.load() { return f(self, n, vm); } @@ -189,7 +232,7 @@ impl PySequence<'_> { } pub fn get_item(self, i: isize, vm: &VirtualMachine) -> PyResult { - if let Some(f) = self.methods.item.load() { + if let Some(f) = self.slots().item.load() { return f(self, i, vm); } Err(vm.new_type_error(format!( @@ -199,7 +242,7 @@ impl PySequence<'_> { } fn _ass_item(self, i: isize, value: Option<PyObjectRef>, vm: &VirtualMachine) -> PyResult<()> { - if let Some(f) = self.methods.ass_item.load() { + if let Some(f) = self.slots().ass_item.load() { return f(self, i, value, vm); } Err(vm.new_type_error(format!( @@ -222,7 +265,7 @@ impl PySequence<'_> { } pub fn get_slice(&self, start: isize, stop: isize, vm: &VirtualMachine) -> PyResult { - if let Ok(mapping) = PyMapping::try_protocol(self.obj, vm) { + if let Ok(mapping) = self.obj.try_mapping(vm) { let slice = PySlice { start: Some(start.to_pyobject(vm)), stop: stop.to_pyobject(vm), @@ -241,8 +284,8 @@ impl PySequence<'_> { value: Option<PyObjectRef>, vm: &VirtualMachine, ) -> PyResult<()> { - let mapping = self.obj.to_mapping(); - if let Some(f) = mapping.methods.ass_subscript.load() { + let mapping = self.obj.mapping_unchecked(); + if let Some(f) = mapping.slots().ass_subscript.load() { let slice = PySlice { start: Some(start.to_pyobject(vm)), stop: stop.to_pyobject(vm), @@ -355,7 +398,7 @@ impl PySequence<'_> { } pub fn contains(self, target: &PyObject, vm: &VirtualMachine) -> PyResult<bool> { - if let Some(f) = self.methods.contains.load() { + if let Some(f) = self.slots().contains.load() { return f(self, target, vm); } diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 72d2c724159..19f85af55da 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -268,7 +268,7 @@ mod builtins { if !globals.fast_isinstance(vm.ctx.types.dict_type) { return Err(match func_name { "eval" => { - let is_mapping = crate::protocol::PyMapping::check(globals); + let is_mapping = globals.mapping_unchecked().check(); vm.new_type_error(if is_mapping { "globals must be a real dict; try eval(expr, {}, mapping)" .to_owned() diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 1ca428669d4..d5aca392c52 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -467,7 +467,7 @@ impl SetAttr for PyCStructType { // Check for data descriptor first if let Some(attr) = pytype.get_class_attr(attr_name_interned) { - let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load()); + let descr_set = attr.class().slots.descr_set.load(); if let Some(descriptor) = descr_set { return descriptor(&attr, pytype.to_owned().into(), value, vm); } diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 500aa8e6244..41bc7492a25 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -369,7 +369,7 @@ impl SetAttr for PyCUnionType { // 1. First, do PyType's setattro (PyType_Type.tp_setattro first) // Check for data descriptor first if let Some(attr) = pytype.get_class_attr(attr_name_interned) { - let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load()); + let descr_set = attr.class().slots.descr_set.load(); if let Some(descriptor) = descr_set { descriptor(&attr, pytype.to_owned().into(), value.clone(), vm)?; // After successful setattro, check if _fields_ and call process_fields diff --git a/crates/vm/src/stdlib/typing.rs b/crates/vm/src/stdlib/typing.rs index afa8bd6eb90..469a75b010f 100644 --- a/crates/vm/src/stdlib/typing.rs +++ b/crates/vm/src/stdlib/typing.rs @@ -1,5 +1,5 @@ // spell-checker:ignore typevarobject funcobj -use crate::{PyPayload, PyRef, VirtualMachine, class::PyClassImpl, stdlib::PyModule}; +use crate::{Context, PyPayload, PyRef, VirtualMachine, class::PyClassImpl, stdlib::PyModule}; pub use crate::stdlib::typevar::{ Generic, ParamSpec, ParamSpecArgs, ParamSpecKwargs, TypeVar, TypeVarTuple, @@ -7,6 +7,11 @@ pub use crate::stdlib::typevar::{ }; pub use decl::*; +/// Initialize typing types (call extend_class) +pub fn init(ctx: &Context) { + NoDefault::extend_class(ctx, ctx.types.typing_no_default_type); +} + pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { let module = decl::make_module(vm); TypeVar::make_class(&vm.ctx); diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 57ac24f461a..bfe6e047622 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -3,7 +3,7 @@ use crate::common::lock::{ }; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyInt, PyStr, PyStrInterned, PyStrRef, PyType, PyTypeRef, type_::PointerSlot}, + builtins::{PyInt, PyStr, PyStrInterned, PyStrRef, PyType, PyTypeRef}, bytecode::ComparisonOperator, common::hash::PyHash, convert::ToPyObject, @@ -11,8 +11,8 @@ use crate::{ Either, FromArgs, FuncArgs, OptionalArg, PyComparisonValue, PyMethodDef, PySetterValue, }, protocol::{ - PyBuffer, PyIterReturn, PyMapping, PyMappingMethods, PyNumber, PyNumberMethods, - PyNumberSlots, PySequence, PySequenceMethods, + PyBuffer, PyIterReturn, PyMapping, PyMappingMethods, PyMappingSlots, PyNumber, + PyNumberMethods, PyNumberSlots, PySequence, PySequenceMethods, PySequenceSlots, }, vm::Context, }; @@ -134,8 +134,8 @@ pub struct PyTypeSlots { // Method suites for standard classes pub as_number: PyNumberSlots, - pub as_sequence: AtomicCell<Option<PointerSlot<PySequenceMethods>>>, - pub as_mapping: AtomicCell<Option<PointerSlot<PyMappingMethods>>>, + pub as_sequence: PySequenceSlots, + pub as_mapping: PyMappingSlots, // More standard operations (here for binary compatibility) pub hash: AtomicCell<Option<HashFunc>>, @@ -358,6 +358,16 @@ fn repr_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> }) } +fn str_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> { + let ret = vm.call_special_method(zelf, identifier!(vm, __str__), ())?; + ret.downcast::<PyStr>().map_err(|obj| { + vm.new_type_error(format!( + "__str__ returned non-string (type {})", + obj.class() + )) + }) +} + fn hash_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> { let hash_obj = vm.call_special_method(zelf, identifier!(vm, __hash__), ())?; let py_int = hash_obj @@ -426,6 +436,18 @@ fn iter_wrapper(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { vm.call_special_method(&zelf, identifier!(vm, __iter__), ()) } +fn bool_wrapper(num: PyNumber<'_>, vm: &VirtualMachine) -> PyResult<bool> { + let result = vm.call_special_method(num.obj, identifier!(vm, __bool__), ())?; + // __bool__ must return exactly bool, not int subclass + if !result.class().is(vm.ctx.types.bool_type) { + return Err(vm.new_type_error(format!( + "__bool__ should return bool, returned {}", + result.class().name() + ))); + } + Ok(crate::builtins::bool_::get_value(&result)) +} + // PyObject_SelfIter in CPython const fn self_iter(zelf: PyObjectRef, _vm: &VirtualMachine) -> PyResult { Ok(zelf) @@ -491,17 +513,36 @@ impl PyType { macro_rules! toggle_slot { ($name:ident, $func:expr) => {{ - self.slots.$name.store(if ADD { Some($func) } else { None }); + if ADD { + self.slots.$name.store(Some($func)); + } else { + // When deleting, re-inherit from MRO (skip self) + let inherited = self + .mro + .read() + .iter() + .skip(1) + .find_map(|cls| cls.slots.$name.load()); + self.slots.$name.store(inherited); + } }}; } macro_rules! toggle_sub_slot { - ($group:ident, $name:ident, $func:expr) => { - self.slots - .$group - .$name - .store(if ADD { Some($func) } else { None }); - }; + ($group:ident, $name:ident, $func:expr) => {{ + if ADD { + self.slots.$group.$name.store(Some($func)); + } else { + // When deleting, re-inherit from MRO (skip self) + let inherited = self + .mro + .read() + .iter() + .skip(1) + .find_map(|cls| cls.slots.$group.$name.load()); + self.slots.$group.$name.store(inherited); + } + }}; } macro_rules! update_slot { @@ -510,66 +551,41 @@ impl PyType { }}; } - macro_rules! update_pointer_slot { - ($name:ident, $pointed:ident) => {{ - self.slots - .$name - .store(unsafe { PointerSlot::from_heaptype(self, |ext| &ext.$pointed) }); - }}; - } - - macro_rules! toggle_ext_func { - ($n1:ident, $n2:ident, $func:expr) => {{ - self.heaptype_ext.as_ref().unwrap().$n1.$n2.store(if ADD { - Some($func) - } else { - None - }); - }}; - } - match name { _ if name == identifier!(ctx, __len__) => { - // update_slot!(as_mapping, slot_as_mapping); - toggle_ext_func!(sequence_methods, length, |seq, vm| len_wrapper(seq.obj, vm)); - update_pointer_slot!(as_sequence, sequence_methods); - toggle_ext_func!(mapping_methods, length, |mapping, vm| len_wrapper( + toggle_sub_slot!(as_sequence, length, |seq, vm| len_wrapper(seq.obj, vm)); + toggle_sub_slot!(as_mapping, length, |mapping, vm| len_wrapper( mapping.obj, vm )); - update_pointer_slot!(as_mapping, mapping_methods); } _ if name == identifier!(ctx, __getitem__) => { - // update_slot!(as_mapping, slot_as_mapping); - toggle_ext_func!(sequence_methods, item, |seq, i, vm| getitem_wrapper( + toggle_sub_slot!(as_sequence, item, |seq, i, vm| getitem_wrapper( seq.obj, i, vm )); - update_pointer_slot!(as_sequence, sequence_methods); - toggle_ext_func!(mapping_methods, subscript, |mapping, key, vm| { + toggle_sub_slot!(as_mapping, subscript, |mapping, key, vm| { getitem_wrapper(mapping.obj, key, vm) }); - update_pointer_slot!(as_mapping, mapping_methods); } _ if name == identifier!(ctx, __setitem__) || name == identifier!(ctx, __delitem__) => { - // update_slot!(as_mapping, slot_as_mapping); - toggle_ext_func!(sequence_methods, ass_item, |seq, i, value, vm| { + toggle_sub_slot!(as_sequence, ass_item, |seq, i, value, vm| { setitem_wrapper(seq.obj, i, value, vm) }); - update_pointer_slot!(as_sequence, sequence_methods); - toggle_ext_func!(mapping_methods, ass_subscript, |mapping, key, value, vm| { + toggle_sub_slot!(as_mapping, ass_subscript, |mapping, key, value, vm| { setitem_wrapper(mapping.obj, key, value, vm) }); - update_pointer_slot!(as_mapping, mapping_methods); } _ if name == identifier!(ctx, __contains__) => { - toggle_ext_func!(sequence_methods, contains, |seq, needle, vm| { + toggle_sub_slot!(as_sequence, contains, |seq, needle, vm| { contains_wrapper(seq.obj, needle, vm) }); - update_pointer_slot!(as_sequence, sequence_methods); } _ if name == identifier!(ctx, __repr__) => { update_slot!(repr, repr_wrapper); } + _ if name == identifier!(ctx, __str__) => { + update_slot!(str, str_wrapper); + } _ if name == identifier!(ctx, __hash__) => { let is_unhashable = self .attributes @@ -624,6 +640,9 @@ impl PyType { _ if name == identifier!(ctx, __del__) => { toggle_slot!(del, del_wrapper); } + _ if name == identifier!(ctx, __bool__) => { + toggle_sub_slot!(as_number, boolean, bool_wrapper); + } _ if name == identifier!(ctx, __int__) => { toggle_sub_slot!(as_number, int, number_unary_op_wrapper!(__int__)); } @@ -1380,24 +1399,30 @@ pub trait AsBuffer: PyPayload { #[pyclass] pub trait AsMapping: PyPayload { - #[pyslot] fn as_mapping() -> &'static PyMappingMethods; #[inline] fn mapping_downcast(mapping: PyMapping<'_>) -> &Py<Self> { unsafe { mapping.obj.downcast_unchecked_ref() } } + + fn extend_slots(slots: &mut PyTypeSlots) { + slots.as_mapping.copy_from(Self::as_mapping()); + } } #[pyclass] pub trait AsSequence: PyPayload { - #[pyslot] fn as_sequence() -> &'static PySequenceMethods; #[inline] fn sequence_downcast(seq: PySequence<'_>) -> &Py<Self> { unsafe { seq.obj.downcast_unchecked_ref() } } + + fn extend_slots(slots: &mut PyTypeSlots) { + slots.as_sequence.copy_from(Self::as_sequence()); + } } #[pyclass] @@ -1412,7 +1437,7 @@ pub trait AsNumber: PyPayload { #[inline] fn number_downcast(num: PyNumber<'_>) -> &Py<Self> { - unsafe { num.obj().downcast_unchecked_ref() } + unsafe { num.obj.downcast_unchecked_ref() } } #[inline] diff --git a/crates/vm/src/types/structseq.rs b/crates/vm/src/types/structseq.rs index 2b6a2530b02..27315749e06 100644 --- a/crates/vm/src/types/structseq.rs +++ b/crates/vm/src/types/structseq.rs @@ -1,8 +1,6 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, atomic_func, - builtins::{ - PyBaseExceptionRef, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, type_::PointerSlot, - }, + builtins::{PyBaseExceptionRef, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef}, class::{PyClassImpl, StaticType}, function::{Either, PyComparisonValue}, iter::PyExactSizeIterator, @@ -87,7 +85,10 @@ static STRUCT_SEQUENCE_AS_SEQUENCE: LazyLock<PySequenceMethods> = let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect(); let visible_tuple = PyTuple::new_ref(visible, &vm.ctx); // Use tuple's concat implementation - visible_tuple.as_object().to_sequence().concat(other, vm) + visible_tuple + .as_object() + .sequence_unchecked() + .concat(other, vm) }), repeat: atomic_func!(|seq, n, vm| { // Convert to visible-only tuple, then use regular tuple repeat @@ -96,7 +97,7 @@ static STRUCT_SEQUENCE_AS_SEQUENCE: LazyLock<PySequenceMethods> = let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect(); let visible_tuple = PyTuple::new_ref(visible, &vm.ctx); // Use tuple's repeat implementation - visible_tuple.as_object().to_sequence().repeat(n, vm) + visible_tuple.as_object().sequence_unchecked().repeat(n, vm) }), item: atomic_func!(|seq, i, vm| { let n_seq = get_visible_len(seq.obj, vm)?; @@ -306,12 +307,14 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { ); // Override as_sequence and as_mapping slots to use visible length - class.slots.as_sequence.store(Some(PointerSlot::from( - &*STRUCT_SEQUENCE_AS_SEQUENCE as &'static PySequenceMethods, - ))); - class.slots.as_mapping.store(Some(PointerSlot::from( - &*STRUCT_SEQUENCE_AS_MAPPING as &'static PyMappingMethods, - ))); + class + .slots + .as_sequence + .copy_from(&STRUCT_SEQUENCE_AS_SEQUENCE); + class + .slots + .as_mapping + .copy_from(&STRUCT_SEQUENCE_AS_MAPPING); // Override iter slot to return only visible elements class.slots.iter.store(Some(struct_sequence_iter)); diff --git a/crates/vm/src/types/zoo.rs b/crates/vm/src/types/zoo.rs index dd4631bc767..0cd04a0ac17 100644 --- a/crates/vm/src/types/zoo.rs +++ b/crates/vm/src/types/zoo.rs @@ -242,5 +242,6 @@ impl TypeZoo { genericalias::init(context); union_::init(context); descriptor::init(context); + crate::stdlib::typing::init(context); } } diff --git a/crates/vm/src/vm/method.rs b/crates/vm/src/vm/method.rs index 5df01c556ea..ba323391488 100644 --- a/crates/vm/src/vm/method.rs +++ b/crates/vm/src/vm/method.rs @@ -21,7 +21,7 @@ pub enum PyMethod { impl PyMethod { pub fn get(obj: PyObjectRef, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult<Self> { let cls = obj.class(); - let getattro = cls.mro_find_map(|cls| cls.slots.getattro.load()).unwrap(); + let getattro = cls.slots.getattro.load().unwrap(); if getattro as usize != PyBaseObject::getattro as usize { return obj.get_attr(name, vm).map(Self::Attribute); } @@ -41,11 +41,9 @@ impl PyMethod { is_method = true; None } else { - let descr_get = descr_cls.mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = descr_cls.slots.descr_get.load(); if let Some(descr_get) = descr_get - && descr_cls - .mro_find_map(|cls| cls.slots.descr_set.load()) - .is_some() + && descr_cls.slots.descr_set.load().is_some() { let cls = cls.to_owned().into(); return descr_get(descr, Some(obj), Some(cls), vm).map(Self::Attribute); diff --git a/crates/vm/src/vm/vm_object.rs b/crates/vm/src/vm/vm_object.rs index e69301820d6..0d5b286148c 100644 --- a/crates/vm/src/vm/vm_object.rs +++ b/crates/vm/src/vm/vm_object.rs @@ -96,9 +96,7 @@ impl VirtualMachine { obj: Option<PyObjectRef>, cls: Option<PyObjectRef>, ) -> Option<PyResult> { - let descr_get = descr - .class() - .mro_find_map(|cls| cls.slots.descr_get.load())?; + let descr_get = descr.class().slots.descr_get.load()?; Some(descr_get(descr.to_owned(), obj, cls, self)) } diff --git a/crates/vm/src/vm/vm_ops.rs b/crates/vm/src/vm/vm_ops.rs index e30e19981a9..635fa10e630 100644 --- a/crates/vm/src/vm/vm_ops.rs +++ b/crates/vm/src/vm/vm_ops.rs @@ -4,7 +4,7 @@ use crate::{ PyRef, builtins::{PyInt, PyStr, PyStrRef, PyUtf8Str}, object::{AsObject, PyObject, PyObjectRef, PyResult}, - protocol::{PyNumberBinaryOp, PyNumberTernaryOp, PySequence}, + protocol::{PyNumberBinaryOp, PyNumberTernaryOp}, types::PyComparisonOp, }; use num_traits::ToPrimitive; @@ -160,12 +160,12 @@ impl VirtualMachine { let class_a = a.class(); let class_b = b.class(); - // Look up number slots across MRO for inheritance - let slot_a = class_a.mro_find_map(|x| x.slots.as_number.left_binary_op(op_slot)); + // Number slots are inherited, direct access is O(1) + let slot_a = class_a.slots.as_number.left_binary_op(op_slot); let mut slot_b = None; if !class_a.is(class_b) { - let slot_bb = class_b.mro_find_map(|x| x.slots.as_number.right_binary_op(op_slot)); + let slot_bb = class_b.slots.as_number.right_binary_op(op_slot); if slot_bb.map(|x| x as usize) != slot_a.map(|x| x as usize) { slot_b = slot_bb; } @@ -231,10 +231,7 @@ impl VirtualMachine { iop_slot: PyNumberBinaryOp, op_slot: PyNumberBinaryOp, ) -> PyResult { - if let Some(slot) = a - .class() - .mro_find_map(|x| x.slots.as_number.left_binary_op(iop_slot)) - { + if let Some(slot) = a.class().slots.as_number.left_binary_op(iop_slot) { let x = slot(a, b, self)?; if !x.is(&self.ctx.not_implemented) { return Ok(x); @@ -270,12 +267,12 @@ impl VirtualMachine { let class_b = b.class(); let class_c = c.class(); - // Look up number slots across MRO for inheritance - let slot_a = class_a.mro_find_map(|x| x.slots.as_number.left_ternary_op(op_slot)); + // Number slots are inherited, direct access is O(1) + let slot_a = class_a.slots.as_number.left_ternary_op(op_slot); let mut slot_b = None; if !class_a.is(class_b) { - let slot_bb = class_b.mro_find_map(|x| x.slots.as_number.right_ternary_op(op_slot)); + let slot_bb = class_b.slots.as_number.right_ternary_op(op_slot); if slot_bb.map(|x| x as usize) != slot_a.map(|x| x as usize) { slot_b = slot_bb; } @@ -304,7 +301,7 @@ impl VirtualMachine { } } - if let Some(slot_c) = class_c.mro_find_map(|x| x.slots.as_number.left_ternary_op(op_slot)) + if let Some(slot_c) = class_c.slots.as_number.left_ternary_op(op_slot) && slot_a.is_some_and(|slot_a| !std::ptr::fn_addr_eq(slot_a, slot_c)) && slot_b.is_some_and(|slot_b| !std::ptr::fn_addr_eq(slot_b, slot_c)) { @@ -343,10 +340,7 @@ impl VirtualMachine { op_slot: PyNumberTernaryOp, op_str: &str, ) -> PyResult { - if let Some(slot) = a - .class() - .mro_find_map(|x| x.slots.as_number.left_ternary_op(iop_slot)) - { + if let Some(slot) = a.class().slots.as_number.left_ternary_op(iop_slot) { let x = slot(a, b, c, self)?; if !x.is(&self.ctx.not_implemented) { return Ok(x); @@ -386,7 +380,7 @@ impl VirtualMachine { if !result.is(&self.ctx.not_implemented) { return Ok(result); } - if let Ok(seq_a) = PySequence::try_protocol(a, self) { + if let Ok(seq_a) = a.try_sequence(self) { let result = seq_a.concat(b, self)?; if !result.is(&self.ctx.not_implemented) { return Ok(result); @@ -400,7 +394,7 @@ impl VirtualMachine { if !result.is(&self.ctx.not_implemented) { return Ok(result); } - if let Ok(seq_a) = PySequence::try_protocol(a, self) { + if let Ok(seq_a) = a.try_sequence(self) { let result = seq_a.inplace_concat(b, self)?; if !result.is(&self.ctx.not_implemented) { return Ok(result); @@ -414,14 +408,14 @@ impl VirtualMachine { if !result.is(&self.ctx.not_implemented) { return Ok(result); } - if let Ok(seq_a) = PySequence::try_protocol(a, self) { + if let Ok(seq_a) = a.try_sequence(self) { let n = b .try_index(self)? .as_bigint() .to_isize() .ok_or_else(|| self.new_overflow_error("repeated bytes are too long"))?; return seq_a.repeat(n, self); - } else if let Ok(seq_b) = PySequence::try_protocol(b, self) { + } else if let Ok(seq_b) = b.try_sequence(self) { let n = a .try_index(self)? .as_bigint() @@ -442,14 +436,14 @@ impl VirtualMachine { if !result.is(&self.ctx.not_implemented) { return Ok(result); } - if let Ok(seq_a) = PySequence::try_protocol(a, self) { + if let Ok(seq_a) = a.try_sequence(self) { let n = b .try_index(self)? .as_bigint() .to_isize() .ok_or_else(|| self.new_overflow_error("repeated bytes are too long"))?; return seq_a.inplace_repeat(n, self); - } else if let Ok(seq_b) = PySequence::try_protocol(b, self) { + } else if let Ok(seq_b) = b.try_sequence(self) { let n = a .try_index(self)? .as_bigint() @@ -530,7 +524,7 @@ impl VirtualMachine { } pub fn _contains(&self, haystack: &PyObject, needle: &PyObject) -> PyResult<bool> { - let seq = haystack.to_sequence(); + let seq = haystack.sequence_unchecked(); seq.contains(needle, self) } } From a5a1173c3f2aac724d0c48ce592670d221b1aa30 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 18:27:19 +0900 Subject: [PATCH 580/819] Disallow rebinding __module__ on immutable builtins and add regression snippet (#6513) * Initial plan * Fix __module__ setter on immutable builtin types Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> * Remove unused value parameter from immutability helper Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> * Auto-format: cargo fmt --all --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- crates/vm/src/builtins/type.rs | 11 ++++++----- extra_tests/snippets/builtin_type.py | 9 +++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index c2373f26faf..ffaad324687 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1050,10 +1050,12 @@ impl PyType { } #[pygetset(setter)] - fn set___module__(&self, value: PyObjectRef, vm: &VirtualMachine) { + fn set___module__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + self.check_set_special_type_attr(identifier!(vm, __module__), vm)?; self.attributes .write() .insert(identifier!(vm, __module__), value); + Ok(()) } #[pyclassmethod] @@ -1103,7 +1105,6 @@ impl PyType { fn check_set_special_type_attr( &self, - _value: &PyObject, name: &PyStrInterned, vm: &VirtualMachine, ) -> PyResult<()> { @@ -1119,7 +1120,7 @@ impl PyType { #[pygetset(setter)] fn set___name__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - self.check_set_special_type_attr(&value, identifier!(vm, __name__), vm)?; + self.check_set_special_type_attr(identifier!(vm, __name__), vm)?; let name = value.downcast::<PyStr>().map_err(|value| { vm.new_type_error(format!( "can only assign string to {}.__name__, not '{}'", @@ -1172,7 +1173,7 @@ impl PyType { match value { PySetterValue::Assign(ref val) => { let key = identifier!(vm, __type_params__); - self.check_set_special_type_attr(val.as_ref(), key, vm)?; + self.check_set_special_type_attr(key, vm)?; let mut attrs = self.attributes.write(); attrs.insert(key, val.clone().into()); } @@ -1628,7 +1629,7 @@ impl Py<PyType> { })?; // Check if we can set this special type attribute - self.check_set_special_type_attr(&value, identifier!(vm, __doc__), vm)?; + self.check_set_special_type_attr(identifier!(vm, __doc__), vm)?; // Set the __doc__ in the type's dict self.attributes diff --git a/extra_tests/snippets/builtin_type.py b/extra_tests/snippets/builtin_type.py index abb68f812be..7a8e4840e13 100644 --- a/extra_tests/snippets/builtin_type.py +++ b/extra_tests/snippets/builtin_type.py @@ -72,6 +72,15 @@ assert object.__qualname__ == "object" assert int.__qualname__ == "int" +with assert_raises(TypeError): + type.__module__ = "nope" + +with assert_raises(TypeError): + object.__module__ = "nope" + +with assert_raises(TypeError): + map.__module__ = "nope" + class A(type): pass From 61ddd98b893072d9b067a953318d93a22a68d3ca Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:08:23 +0900 Subject: [PATCH 581/819] Handle missing type_params on AST Function/Class/TypeAlias nodes (#6512) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- crates/vm/src/stdlib/ast/statement.rs | 6 +++--- extra_tests/snippets/stdlib_types.py | 30 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/crates/vm/src/stdlib/ast/statement.rs b/crates/vm/src/stdlib/ast/statement.rs index 6d9b35bee79..f1d36c52e2e 100644 --- a/crates/vm/src/stdlib/ast/statement.rs +++ b/crates/vm/src/stdlib/ast/statement.rs @@ -257,7 +257,7 @@ impl Node for ruff::StmtFunctionDef { type_params: Node::ast_from_object( _vm, source_file, - get_node_field(_vm, &_object, "type_params", "FunctionDef")?, + get_node_field_opt(_vm, &_object, "type_params")?.unwrap_or_else(|| _vm.ctx.none()), )?, range: range_from_object(_vm, source_file, _object, "FunctionDef")?, is_async, @@ -341,7 +341,7 @@ impl Node for ruff::StmtClassDef { type_params: Node::ast_from_object( _vm, source_file, - get_node_field(_vm, &_object, "type_params", "ClassDef")?, + get_node_field_opt(_vm, &_object, "type_params")?.unwrap_or_else(|| _vm.ctx.none()), )?, range: range_from_object(_vm, source_file, _object, "ClassDef")?, }) @@ -503,7 +503,7 @@ impl Node for ruff::StmtTypeAlias { type_params: Node::ast_from_object( _vm, source_file, - get_node_field(_vm, &_object, "type_params", "TypeAlias")?, + get_node_field_opt(_vm, &_object, "type_params")?.unwrap_or_else(|| _vm.ctx.none()), )?, value: Node::ast_from_object( _vm, diff --git a/extra_tests/snippets/stdlib_types.py b/extra_tests/snippets/stdlib_types.py index 3a3872d2f4e..14028268f0e 100644 --- a/extra_tests/snippets/stdlib_types.py +++ b/extra_tests/snippets/stdlib_types.py @@ -1,3 +1,5 @@ +import _ast +import platform import types from testutils import assert_raises @@ -8,3 +10,31 @@ assert ns.b == "Rust" with assert_raises(AttributeError): _ = ns.c + + +def _run_missing_type_params_regression(): + args = _ast.arguments( + posonlyargs=[], + args=[], + vararg=None, + kwonlyargs=[], + kw_defaults=[], + kwarg=None, + defaults=[], + ) + fn = _ast.FunctionDef("f", args, [], [], None, None) + fn.lineno = 1 + fn.col_offset = 0 + fn.end_lineno = 1 + fn.end_col_offset = 0 + mod = _ast.Module([fn], []) + mod.lineno = 1 + mod.col_offset = 0 + mod.end_lineno = 1 + mod.end_col_offset = 0 + compiled = compile(mod, "<stdlib_types_missing_type_params>", "exec") + exec(compiled, {}) + + +if platform.python_implementation() == "RustPython": + _run_missing_type_params_regression() From bcdf37bef17beacdaa41e430eb3e7e2d0bf5cb6c Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 25 Dec 2025 14:17:31 +0100 Subject: [PATCH 582/819] Align opcode names in `dis` (#6526) * opcodes dis repr like cpython. POP-> POP_TOP * Adjust frame.rs * Fix codegen * snapshots * Add doc for `PopTop` * fix jit --- crates/codegen/src/compile.rs | 46 ++-- ...thon_codegen__compile__tests__if_ands.snap | 16 +- ...hon_codegen__compile__tests__if_mixed.snap | 20 +- ...ython_codegen__compile__tests__if_ors.snap | 16 +- ...pile__tests__nested_double_async_with.snap | 142 ++++++------ crates/compiler-core/src/bytecode.rs | 206 +++++++++--------- crates/jit/src/instructions.rs | 8 +- crates/vm/src/frame.rs | 10 +- 8 files changed, 234 insertions(+), 230 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 4f7174c8bad..0131b3008e3 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -1063,7 +1063,7 @@ impl Compiler { } ); - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } else { self.compile_statement(statement)?; } @@ -1079,7 +1079,7 @@ impl Compiler { } ); - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } else { self.compile_statement(last)?; self.emit_load_const(ConstantData::None); @@ -1104,7 +1104,7 @@ impl Compiler { if let Some(last_statement) = body.last() { match last_statement { Stmt::Expr(_) => { - self.current_block().instructions.pop(); // pop Instruction::Pop + self.current_block().instructions.pop(); // pop Instruction::PopTop } Stmt::FunctionDef(_) | Stmt::ClassDef(_) => { let pop_instructions = self.current_block().instructions.pop(); @@ -1401,14 +1401,14 @@ impl Compiler { } // Pop module from stack: - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } } Stmt::Expr(StmtExpr { value, .. }) => { self.compile_expression(value)?; // Pop result of stack, since we not use it: - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } Stmt::Global(_) | Stmt::Nonlocal(_) => { // Handled during symbol table construction. @@ -2051,12 +2051,12 @@ impl Compiler { self.store_name(alias.as_str())? } else { // Drop exception from top of stack: - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } } else { // Catch all! // Drop exception from top of stack: - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } // Handler code: @@ -2950,7 +2950,7 @@ impl Compiler { self.compile_store(var)?; } None => { - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } } final_block @@ -3143,7 +3143,7 @@ impl Compiler { for &label in pc.fail_pop.iter().skip(1).rev() { self.switch_to_block(label); // Emit the POP instruction. - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } // Finally, use the first label. self.switch_to_block(pc.fail_pop[0]); @@ -3187,7 +3187,7 @@ impl Compiler { match n { // If no name is provided, simply pop the top of the stack. None => { - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); Ok(()) } Some(name) => { @@ -3313,7 +3313,7 @@ impl Compiler { } // Pop the subject off the stack. pc.on_top -= 1; - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); Ok(()) } @@ -3497,7 +3497,7 @@ impl Compiler { pc.on_top -= 1; if is_true_wildcard { - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); continue; // Don't compile wildcard patterns } @@ -3548,7 +3548,7 @@ impl Compiler { if size == 0 && star_target.is_none() { // If the pattern is just "{}", we're done! Pop the subject pc.on_top -= 1; - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); return Ok(()); } @@ -3703,8 +3703,8 @@ impl Compiler { // Non-rest pattern: just clean up the stack // Pop them as we're not using them - emit!(self, Instruction::Pop); // Pop keys_tuple - emit!(self, Instruction::Pop); // Pop subject + emit!(self, Instruction::PopTop); // Pop keys_tuple + emit!(self, Instruction::PopTop); // Pop subject } Ok(()) @@ -3792,7 +3792,7 @@ impl Compiler { // In Rust, old_pc is a local clone, so we need not worry about that. // No alternative matched: pop the subject and fail. - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); self.jump_to_fail_pop(pc, JumpOp::Jump)?; // Use the label "end". @@ -3814,7 +3814,7 @@ impl Compiler { // Old context and control will be dropped automatically. // Finally, pop the copy of the subject. - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); Ok(()) } @@ -3888,7 +3888,7 @@ impl Compiler { pc.on_top -= 1; if only_wildcard { // Patterns like: [] / [_] / [_, _] / [*_] / [_, *_] / [_, _, *_] / etc. - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } else if star_wildcard { self.pattern_helper_sequence_subscr(patterns, star.unwrap(), pc)?; } else { @@ -4012,7 +4012,7 @@ impl Compiler { } if i != case_count - 1 { - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } self.compile_statements(&m.body)?; @@ -4023,7 +4023,7 @@ impl Compiler { if has_default { let m = &cases[num_cases - 1]; if num_cases == 1 { - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } else { emit!(self, Instruction::Nop); } @@ -4125,7 +4125,7 @@ impl Compiler { // early exit left us with stack: `rhs, comparison_result`. We need to clean up rhs. self.switch_to_block(break_block); emit!(self, Instruction::Swap { index: 2 }); - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); self.switch_to_block(after_block); } @@ -4192,7 +4192,7 @@ impl Compiler { emit!(self, Instruction::StoreSubscript); } else { // Drop annotation if not assigned to simple identifier. - emit!(self, Instruction::Pop); + emit!(self, Instruction::PopTop); } Ok(()) @@ -4823,7 +4823,7 @@ impl Compiler { arg: bytecode::ResumeType::AfterYield as u32 } ); - emit!(compiler, Instruction::Pop); + emit!(compiler, Instruction::PopTop); Ok(()) }, diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap index 8b2907ef6ff..4c9c29887ee 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap @@ -1,12 +1,12 @@ --- -source: compiler/codegen/src/compile.rs +source: crates/codegen/src/compile.rs expression: "compile_exec(\"\\\nif True and False and False:\n pass\n\")" --- - 1 0 LoadConst (True) - 1 PopJumpIfFalse (6) - 2 LoadConst (False) - 3 PopJumpIfFalse (6) - 4 LoadConst (False) - 5 PopJumpIfFalse (6) + 1 0 LOAD_CONST (True) + 1 POP_JUMP_IF_FALSE (6) + 2 LOAD_CONST (False) + 3 POP_JUMP_IF_FALSE (6) + 4 LOAD_CONST (False) + 5 POP_JUMP_IF_FALSE (6) - 2 >> 6 ReturnConst (None) + 2 >> 6 RETURN_CONST (None) diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap index fc91a74283b..a93479df96e 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap @@ -1,14 +1,14 @@ --- -source: compiler/codegen/src/compile.rs +source: crates/codegen/src/compile.rs expression: "compile_exec(\"\\\nif (True and False) or (False and True):\n pass\n\")" --- - 1 0 LoadConst (True) - 1 PopJumpIfFalse (4) - 2 LoadConst (False) - 3 PopJumpIfTrue (8) - >> 4 LoadConst (False) - 5 PopJumpIfFalse (8) - 6 LoadConst (True) - 7 PopJumpIfFalse (8) + 1 0 LOAD_CONST (True) + 1 POP_JUMP_IF_FALSE (4) + 2 LOAD_CONST (False) + 3 POP_JUMP_IF_TRUE (8) + >> 4 LOAD_CONST (False) + 5 POP_JUMP_IF_FALSE (8) + 6 LOAD_CONST (True) + 7 POP_JUMP_IF_FALSE (8) - 2 >> 8 ReturnConst (None) + 2 >> 8 RETURN_CONST (None) diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap index 9be7c2af7bd..37a862cc65a 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap @@ -1,12 +1,12 @@ --- -source: compiler/codegen/src/compile.rs +source: crates/codegen/src/compile.rs expression: "compile_exec(\"\\\nif True or False or False:\n pass\n\")" --- - 1 0 LoadConst (True) - 1 PopJumpIfTrue (6) - 2 LoadConst (False) - 3 PopJumpIfTrue (6) - 4 LoadConst (False) - 5 PopJumpIfFalse (6) + 1 0 LOAD_CONST (True) + 1 POP_JUMP_IF_TRUE (6) + 2 LOAD_CONST (False) + 3 POP_JUMP_IF_TRUE (6) + 4 LOAD_CONST (False) + 5 POP_JUMP_IF_FALSE (6) - 2 >> 6 ReturnConst (None) + 2 >> 6 RETURN_CONST (None) diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index 435b73a14de..6cd7d4d523f 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap @@ -2,85 +2,85 @@ source: crates/codegen/src/compile.rs expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\")" --- - 1 0 SetupLoop - 1 LoadNameAny (0, StopIteration) - 2 LoadConst ("spam") - 3 CallFunctionPositional(1) - 4 LoadNameAny (1, StopAsyncIteration) - 5 LoadConst ("ham") - 6 CallFunctionPositional(1) - 7 BuildTuple (2) - 8 GetIter - >> 9 ForIter (71) - 10 StoreLocal (2, stop_exc) + 1 0 SETUP_LOOP + 1 LOAD_NAME_ANY (0, StopIteration) + 2 LOAD_CONST ("spam") + 3 CALL_FUNCTION_POSITIONAL(1) + 4 LOAD_NAME_ANY (1, StopAsyncIteration) + 5 LOAD_CONST ("ham") + 6 CALL_FUNCTION_POSITIONAL(1) + 7 BUILD_TUPLE (2) + 8 GET_ITER + >> 9 FOR_ITER (71) + 10 STORE_LOCAL (2, stop_exc) - 2 11 LoadNameAny (3, self) - 12 LoadMethod (4, subTest) - 13 LoadNameAny (5, type) - 14 LoadNameAny (2, stop_exc) - 15 CallFunctionPositional(1) - 16 LoadConst (("type")) - 17 CallMethodKeyword (1) - 18 SetupWith (68) - 19 Pop + 2 11 LOAD_NAME_ANY (3, self) + 12 LOAD_METHOD (4, subTest) + 13 LOAD_NAME_ANY (5, type) + 14 LOAD_NAME_ANY (2, stop_exc) + 15 CALL_FUNCTION_POSITIONAL(1) + 16 LOAD_CONST (("type")) + 17 CALL_METHOD_KEYWORD (1) + 18 SETUP_WITH (68) + 19 POP_TOP - 3 20 SetupExcept (42) + 3 20 SETUP_EXCEPT (42) - 4 21 LoadNameAny (6, egg) - 22 CallFunctionPositional(0) - 23 BeforeAsyncWith - 24 GetAwaitable - 25 LoadConst (None) - 26 YieldFrom - 27 Resume (3) - 28 SetupAsyncWith (34) - 29 Pop + 4 21 LOAD_NAME_ANY (6, egg) + 22 CALL_FUNCTION_POSITIONAL(0) + 23 BEFORE_ASYNC_WITH + 24 GET_AWAITABLE + 25 LOAD_CONST (None) + 26 YIELD_FROM + 27 RESUME (3) + 28 SETUP_ASYNC_WITH (34) + 29 POP_TOP - 5 30 LoadNameAny (2, stop_exc) - 31 Raise (Raise) + 5 30 LOAD_NAME_ANY (2, stop_exc) + 31 RAISE (Raise) - 4 32 PopBlock - 33 EnterFinally - >> 34 WithCleanupStart - 35 GetAwaitable - 36 LoadConst (None) - 37 YieldFrom - 38 Resume (3) - 39 WithCleanupFinish - 40 PopBlock - 41 Jump (58) - >> 42 CopyItem (1) + 4 32 POP_BLOCK + 33 ENTER_FINALLY + >> 34 WITH_CLEANUP_START + 35 GET_AWAITABLE + 36 LOAD_CONST (None) + 37 YIELD_FROM + 38 RESUME (3) + 39 WITH_CLEANUP_FINISH + 40 POP_BLOCK + 41 JUMP (58) + >> 42 COPY (1) - 6 43 LoadNameAny (7, Exception) + 6 43 LOAD_NAME_ANY (7, Exception) 44 JUMP_IF_NOT_EXC_MATCH(57) - 45 StoreLocal (8, ex) + 45 STORE_LOCAL (8, ex) - 7 46 LoadNameAny (3, self) - 47 LoadMethod (9, assertIs) - 48 LoadNameAny (8, ex) - 49 LoadNameAny (2, stop_exc) - 50 CallMethodPositional (2) - 51 Pop - 52 PopException - 53 LoadConst (None) - 54 StoreLocal (8, ex) - 55 DeleteLocal (8, ex) - 56 Jump (66) - >> 57 Raise (Reraise) + 7 46 LOAD_NAME_ANY (3, self) + 47 LOAD_METHOD (9, assertIs) + 48 LOAD_NAME_ANY (8, ex) + 49 LOAD_NAME_ANY (2, stop_exc) + 50 CALL_METHOD_POSITIONAL(2) + 51 POP_TOP + 52 POP_EXCEPTION + 53 LOAD_CONST (None) + 54 STORE_LOCAL (8, ex) + 55 DELETE_LOCAL (8, ex) + 56 JUMP (66) + >> 57 RAISE (Reraise) - 9 >> 58 LoadNameAny (3, self) - 59 LoadMethod (10, fail) - 60 LoadNameAny (2, stop_exc) + 9 >> 58 LOAD_NAME_ANY (3, self) + 59 LOAD_METHOD (10, fail) + 60 LOAD_NAME_ANY (2, stop_exc) 61 FORMAT_SIMPLE - 62 LoadConst (" was suppressed") - 63 BuildString (2) - 64 CallMethodPositional (1) - 65 Pop + 62 LOAD_CONST (" was suppressed") + 63 BUILD_STRING (2) + 64 CALL_METHOD_POSITIONAL(1) + 65 POP_TOP - 2 >> 66 PopBlock - 67 EnterFinally - >> 68 WithCleanupStart - 69 WithCleanupFinish - 70 Jump (9) - >> 71 PopBlock - 72 ReturnConst (None) + 2 >> 66 POP_BLOCK + 67 ENTER_FINALLY + >> 68 WITH_CLEANUP_START + 69 WITH_CLEANUP_FINISH + 70 JUMP (9) + >> 71 POP_BLOCK + 72 RETURN_CONST (None) diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 11d2a7b5f1b..dd49e679f27 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -782,7 +782,6 @@ pub enum Instruction { MatchMapping, MatchSequence, Nop, - Pop, PopBlock, PopException, /// Pop the top of the stack, and jump if this value is false. @@ -793,6 +792,11 @@ pub enum Instruction { PopJumpIfTrue { target: Arg<Label>, }, + /// Removes the top-of-stack item: + /// ```py + /// STACK.pop() + /// ``` + PopTop, Raise { kind: Arg<RaiseKind>, }, @@ -1685,7 +1689,7 @@ impl Instruction { BinaryOp { .. } | CompareOperation { .. } => -1, BinarySubscript => -1, CopyItem { .. } => 1, - Pop => -1, + PopTop => -1, Swap { .. } => 0, ToBool => 0, GetIter => 0, @@ -1860,112 +1864,112 @@ impl Instruction { }; match self { - BeforeAsyncWith => w!(BeforeAsyncWith), + BeforeAsyncWith => w!(BEFORE_ASYNC_WITH), BinaryOp { op } => write!(f, "{:pad$}({})", "BINARY_OP", op.get(arg)), - BinarySubscript => w!(BinarySubscript), - Break { target } => w!(Break, target), - BuildListFromTuples { size } => w!(BuildListFromTuples, size), - BuildList { size } => w!(BuildList, size), - BuildMapForCall { size } => w!(BuildMapForCall, size), - BuildMap { size } => w!(BuildMap, size), - BuildSetFromTuples { size } => w!(BuildSetFromTuples, size), - BuildSet { size } => w!(BuildSet, size), - BuildSlice { argc } => w!(BuildSlice, ?argc), - BuildString { size } => w!(BuildString, size), - BuildTupleFromIter => w!(BuildTupleFromIter), - BuildTupleFromTuples { size } => w!(BuildTupleFromTuples, size), - BuildTuple { size } => w!(BuildTuple, size), - CallFunctionEx { has_kwargs } => w!(CallFunctionEx, has_kwargs), - CallFunctionKeyword { nargs } => w!(CallFunctionKeyword, nargs), - CallFunctionPositional { nargs } => w!(CallFunctionPositional, nargs), - CallIntrinsic1 { func } => w!(CallIntrinsic1, ?func), - CallIntrinsic2 { func } => w!(CallIntrinsic2, ?func), - CallMethodEx { has_kwargs } => w!(CallMethodEx, has_kwargs), - CallMethodKeyword { nargs } => w!(CallMethodKeyword, nargs), - CallMethodPositional { nargs } => w!(CallMethodPositional, nargs), - CompareOperation { op } => w!(CompareOperation, ?op), + BinarySubscript => w!(BINARY_SUBSCRIPT), + Break { target } => w!(BREAK, target), + BuildListFromTuples { size } => w!(BUILD_LIST_FROM_TUPLES, size), + BuildList { size } => w!(BUILD_LIST, size), + BuildMapForCall { size } => w!(BUILD_MAP_FOR_CALL, size), + BuildMap { size } => w!(BUILD_MAP, size), + BuildSetFromTuples { size } => w!(BUILD_SET_FROM_TUPLES, size), + BuildSet { size } => w!(BUILD_SET, size), + BuildSlice { argc } => w!(BUILD_SLICE, ?argc), + BuildString { size } => w!(BUILD_STRING, size), + BuildTupleFromIter => w!(BUILD_TUPLE_FROM_ITER), + BuildTupleFromTuples { size } => w!(BUILD_TUPLE_FROM_TUPLES, size), + BuildTuple { size } => w!(BUILD_TUPLE, size), + CallFunctionEx { has_kwargs } => w!(CALL_FUNCTION_EX, has_kwargs), + CallFunctionKeyword { nargs } => w!(CALL_FUNCTION_KEYWORD, nargs), + CallFunctionPositional { nargs } => w!(CALL_FUNCTION_POSITIONAL, nargs), + CallIntrinsic1 { func } => w!(CALL_INTRINSIC_1, ?func), + CallIntrinsic2 { func } => w!(CALL_INTRINSIC_2, ?func), + CallMethodEx { has_kwargs } => w!(CALL_METHOD_EX, has_kwargs), + CallMethodKeyword { nargs } => w!(CALL_METHOD_KEYWORD, nargs), + CallMethodPositional { nargs } => w!(CALL_METHOD_POSITIONAL, nargs), + CompareOperation { op } => w!(COMPARE_OPERATION, ?op), ContainsOp(inv) => w!(CONTAINS_OP, ?inv), - Continue { target } => w!(Continue, target), + Continue { target } => w!(CONTINUE, target), ConvertValue { oparg } => write!(f, "{:pad$}{}", "CONVERT_VALUE", oparg.get(arg)), - CopyItem { index } => w!(CopyItem, index), - DeleteAttr { idx } => w!(DeleteAttr, name = idx), - DeleteDeref(idx) => w!(DeleteDeref, cell_name = idx), - DeleteFast(idx) => w!(DeleteFast, varname = idx), - DeleteGlobal(idx) => w!(DeleteGlobal, name = idx), - DeleteLocal(idx) => w!(DeleteLocal, name = idx), - DeleteSubscript => w!(DeleteSubscript), - DictUpdate { index } => w!(DictUpdate, index), - EndAsyncFor => w!(EndAsyncFor), - EndFinally => w!(EndFinally), - EnterFinally => w!(EnterFinally), - ExtendedArg => w!(ExtendedArg, Arg::<u32>::marker()), - ForIter { target } => w!(ForIter, target), + CopyItem { index } => w!(COPY, index), + DeleteAttr { idx } => w!(DELETE_ATTR, name = idx), + DeleteDeref(idx) => w!(DELETE_DEREF, cell_name = idx), + DeleteFast(idx) => w!(DELETE_FAST, varname = idx), + DeleteGlobal(idx) => w!(DELETE_GLOBAL, name = idx), + DeleteLocal(idx) => w!(DELETE_LOCAL, name = idx), + DeleteSubscript => w!(DELETE_SUBSCRIPT), + DictUpdate { index } => w!(DICT_UPDATE, index), + EndAsyncFor => w!(END_ASYNC_FOR), + EndFinally => w!(END_FINALLY), + EnterFinally => w!(ENTER_FINALLY), + ExtendedArg => w!(EXTENDED_ARG, Arg::<u32>::marker()), + ForIter { target } => w!(FOR_ITER, target), FormatSimple => w!(FORMAT_SIMPLE), FormatWithSpec => w!(FORMAT_WITH_SPEC), - GetAIter => w!(GetAIter), - GetANext => w!(GetANext), - GetAwaitable => w!(GetAwaitable), - GetIter => w!(GetIter), - GetLen => w!(GetLen), - ImportFrom { idx } => w!(ImportFrom, name = idx), - ImportName { idx } => w!(ImportName, name = idx), + GetAIter => w!(GET_AITER), + GetANext => w!(GET_ANEXT), + GetAwaitable => w!(GET_AWAITABLE), + GetIter => w!(GET_ITER), + GetLen => w!(GET_LEN), + ImportFrom { idx } => w!(IMPORT_FROM, name = idx), + ImportName { idx } => w!(IMPORT_NAME, name = idx), IsOp(inv) => w!(IS_OP, ?inv), - JumpIfFalseOrPop { target } => w!(JumpIfFalseOrPop, target), + JumpIfFalseOrPop { target } => w!(JUMP_IF_FALSE_OR_POP, target), JumpIfNotExcMatch(target) => w!(JUMP_IF_NOT_EXC_MATCH, target), - JumpIfTrueOrPop { target } => w!(JumpIfTrueOrPop, target), - Jump { target } => w!(Jump, target), - ListAppend { i } => w!(ListAppend, i), - LoadAttr { idx } => w!(LoadAttr, name = idx), - LoadBuildClass => w!(LoadBuildClass), - LoadClassDeref(idx) => w!(LoadClassDeref, cell_name = idx), - LoadClosure(i) => w!(LoadClosure, cell_name = i), - LoadConst { idx } => fmt_const("LoadConst", arg, f, idx), - LoadDeref(idx) => w!(LoadDeref, cell_name = idx), - LoadFast(idx) => w!(LoadFast, varname = idx), - LoadGlobal(idx) => w!(LoadGlobal, name = idx), - LoadMethod { idx } => w!(LoadMethod, name = idx), - LoadNameAny(idx) => w!(LoadNameAny, name = idx), - MakeFunction => w!(MakeFunction), - MapAdd { i } => w!(MapAdd, i), - MatchClass(arg) => w!(MatchClass, arg), - MatchKeys => w!(MatchKeys), - MatchMapping => w!(MatchMapping), - MatchSequence => w!(MatchSequence), - Nop => w!(Nop), - Pop => w!(Pop), - PopBlock => w!(PopBlock), - PopException => w!(PopException), - PopJumpIfFalse { target } => w!(PopJumpIfFalse, target), - PopJumpIfTrue { target } => w!(PopJumpIfTrue, target), - Raise { kind } => w!(Raise, ?kind), - Resume { arg } => w!(Resume, arg), - ReturnConst { idx } => fmt_const("ReturnConst", arg, f, idx), - ReturnValue => w!(ReturnValue), - Reverse { amount } => w!(Reverse, amount), - SetAdd { i } => w!(SetAdd, i), - SetFunctionAttribute { attr } => w!(SetFunctionAttribute, ?attr), - SetupAnnotation => w!(SetupAnnotation), - SetupAsyncWith { end } => w!(SetupAsyncWith, end), - SetupExcept { handler } => w!(SetupExcept, handler), - SetupFinally { handler } => w!(SetupFinally, handler), - SetupLoop => w!(SetupLoop), - SetupWith { end } => w!(SetupWith, end), - StoreAttr { idx } => w!(StoreAttr, name = idx), - StoreDeref(idx) => w!(StoreDeref, cell_name = idx), - StoreFast(idx) => w!(StoreFast, varname = idx), - StoreGlobal(idx) => w!(StoreGlobal, name = idx), - StoreLocal(idx) => w!(StoreLocal, name = idx), - StoreSubscript => w!(StoreSubscript), - Subscript => w!(Subscript), - Swap { index } => w!(Swap, index), - ToBool => w!(ToBool), - UnaryOperation { op } => w!(UnaryOperation, ?op), - UnpackEx { args } => w!(UnpackEx, args), - UnpackSequence { size } => w!(UnpackSequence, size), - WithCleanupFinish => w!(WithCleanupFinish), - WithCleanupStart => w!(WithCleanupStart), - YieldFrom => w!(YieldFrom), - YieldValue => w!(YieldValue), + JumpIfTrueOrPop { target } => w!(JUMP_IF_TRUE_OR_POP, target), + Jump { target } => w!(JUMP, target), + ListAppend { i } => w!(LIST_APPEND, i), + LoadAttr { idx } => w!(LOAD_ATTR, name = idx), + LoadBuildClass => w!(LOAD_BUILD_CLASS), + LoadClassDeref(idx) => w!(LOAD_CLASS_DEREF, cell_name = idx), + LoadClosure(i) => w!(LOAD_CLOSURE, cell_name = i), + LoadConst { idx } => fmt_const("LOAD_CONST", arg, f, idx), + LoadDeref(idx) => w!(LOAD_DEREF, cell_name = idx), + LoadFast(idx) => w!(LOAD_FAST, varname = idx), + LoadGlobal(idx) => w!(LOAD_GLOBAL, name = idx), + LoadMethod { idx } => w!(LOAD_METHOD, name = idx), + LoadNameAny(idx) => w!(LOAD_NAME_ANY, name = idx), + MakeFunction => w!(MAKE_FUNCTION), + MapAdd { i } => w!(MAP_ADD, i), + MatchClass(arg) => w!(MATCH_CLASS, arg), + MatchKeys => w!(MATCH_KEYS), + MatchMapping => w!(MATCH_MAPPING), + MatchSequence => w!(MATCH_SEQUENCE), + Nop => w!(NOP), + PopBlock => w!(POP_BLOCK), + PopException => w!(POP_EXCEPTION), + PopJumpIfFalse { target } => w!(POP_JUMP_IF_FALSE, target), + PopJumpIfTrue { target } => w!(POP_JUMP_IF_TRUE, target), + PopTop => w!(POP_TOP), + Raise { kind } => w!(RAISE, ?kind), + Resume { arg } => w!(RESUME, arg), + ReturnConst { idx } => fmt_const("RETURN_CONST", arg, f, idx), + ReturnValue => w!(RETURN_VALUE), + Reverse { amount } => w!(REVERSE, amount), + SetAdd { i } => w!(SET_ADD, i), + SetFunctionAttribute { attr } => w!(SET_FUNCTION_ATTRIBUTE, ?attr), + SetupAnnotation => w!(SETUP_ANNOTATION), + SetupAsyncWith { end } => w!(SETUP_ASYNC_WITH, end), + SetupExcept { handler } => w!(SETUP_EXCEPT, handler), + SetupFinally { handler } => w!(SETUP_FINALLY, handler), + SetupLoop => w!(SETUP_LOOP), + SetupWith { end } => w!(SETUP_WITH, end), + StoreAttr { idx } => w!(STORE_ATTR, name = idx), + StoreDeref(idx) => w!(STORE_DEREF, cell_name = idx), + StoreFast(idx) => w!(STORE_FAST, varname = idx), + StoreGlobal(idx) => w!(STORE_GLOBAL, name = idx), + StoreLocal(idx) => w!(STORE_LOCAL, name = idx), + StoreSubscript => w!(STORE_SUBSCRIPT), + Subscript => w!(SUBSCRIPT), + Swap { index } => w!(SWAP, index), + ToBool => w!(TO_BOOL), + UnaryOperation { op } => w!(UNARY_OPERATION, ?op), + UnpackEx { args } => w!(UNPACK_EX, args), + UnpackSequence { size } => w!(UNPACK_SEQUENCE, size), + WithCleanupFinish => w!(WITH_CLEANUP_FINISH), + WithCleanupStart => w!(WITH_CLEANUP_START), + YieldFrom => w!(YIELD_FROM), + YieldValue => w!(YIELD_VALUE), } } } diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index 8a70b9a73cb..7f51a5a8a4b 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -547,10 +547,6 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { } } Instruction::Nop => Ok(()), - Instruction::Pop => { - self.stack.pop(); - Ok(()) - } Instruction::PopBlock => { // TODO: block support Ok(()) @@ -581,6 +577,10 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { Ok(()) } + Instruction::PopTop => { + self.stack.pop(); + Ok(()) + } Instruction::Resume { arg: _resume_arg } => { // TODO: Implement the resume instruction Ok(()) diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index d9cc404b47a..e9a938ba023 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1337,11 +1337,6 @@ impl ExecutingFrame<'_> { Ok(None) } bytecode::Instruction::Nop => Ok(None), - bytecode::Instruction::Pop => { - // Pop value from stack and ignore. - self.pop_value(); - Ok(None) - } bytecode::Instruction::PopBlock => { self.pop_block(); Ok(None) @@ -1361,6 +1356,11 @@ impl ExecutingFrame<'_> { bytecode::Instruction::PopJumpIfTrue { target } => { self.pop_jump_if(vm, target.get(arg), true) } + bytecode::Instruction::PopTop => { + // Pop value from stack and ignore. + self.pop_value(); + Ok(None) + } bytecode::Instruction::Raise { kind } => self.execute_raise(vm, kind.get(arg)), bytecode::Instruction::Resume { arg: resume_arg } => { // Resume execution after yield, await, or at function start From 2063c1e0bcd92c0073af438fc151aab03b4754e0 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 25 Dec 2025 22:37:59 +0900 Subject: [PATCH 583/819] Implement winapi.GetLongPathName (#6525) --- Lib/test/test_winapi.py | 4 --- crates/vm/src/stdlib/winapi.rs | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_winapi.py b/Lib/test/test_winapi.py index 7a33f906986..846398f89d4 100644 --- a/Lib/test/test_winapi.py +++ b/Lib/test/test_winapi.py @@ -110,8 +110,6 @@ def test_max_events_waitany(self): class WinAPITests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_getlongpathname(self): testfn = pathlib.Path(os.getenv("ProgramFiles")).parents[-1] / "PROGRA~1" if not os.path.isdir(testfn): @@ -128,8 +126,6 @@ def test_getlongpathname(self): candidates = set(testfn.parent.glob("Progra*")) self.assertIn(pathlib.Path(actual), candidates) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_getshortpathname(self): testfn = pathlib.Path(os.getenv("ProgramFiles")) if not os.path.isdir(testfn): diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index 1df3ff0e42d..44f56f0112f 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -677,4 +677,52 @@ mod _winapi { Ok(WinHandle(handle)) } + + /// Helper for GetShortPathName and GetLongPathName + fn get_path_name_impl( + path: &PyStrRef, + api_fn: unsafe extern "system" fn(*const u16, *mut u16, u32) -> u32, + vm: &VirtualMachine, + ) -> PyResult<PyStrRef> { + use rustpython_common::wtf8::Wtf8Buf; + + let path_wide = path.as_wtf8().to_wide_with_nul(); + + // First call to get required buffer size + let size = unsafe { api_fn(path_wide.as_ptr(), null_mut(), 0) }; + + if size == 0 { + return Err(vm.new_last_os_error()); + } + + // Second call to get the actual path + let mut buffer: Vec<u16> = vec![0; size as usize]; + let result = + unsafe { api_fn(path_wide.as_ptr(), buffer.as_mut_ptr(), buffer.len() as u32) }; + + if result == 0 { + return Err(vm.new_last_os_error()); + } + + // Truncate to actual length (excluding null terminator) + buffer.truncate(result as usize); + + // Convert UTF-16 back to WTF-8 (handles surrogates properly) + let result_str = Wtf8Buf::from_wide(&buffer); + Ok(vm.ctx.new_str(result_str)) + } + + /// GetShortPathName - Return the short version of the provided path. + #[pyfunction] + fn GetShortPathName(path: PyStrRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { + use windows_sys::Win32::Storage::FileSystem::GetShortPathNameW; + get_path_name_impl(&path, GetShortPathNameW, vm) + } + + /// GetLongPathName - Return the long version of the provided path. + #[pyfunction] + fn GetLongPathName(path: PyStrRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { + use windows_sys::Win32::Storage::FileSystem::GetLongPathNameW; + get_path_name_impl(&path, GetLongPathNameW, vm) + } } From 40acd5529050d305e61a00834b331cc3d4c18b99 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 26 Dec 2025 01:29:58 +0900 Subject: [PATCH 584/819] venvlauncher (#6527) --- .cspell.dict/cpython.txt | 6 + .github/workflows/ci.yaml | 11 +- Cargo.lock | 7 ++ Lib/venv/scripts/nt/venvlauncher.exe | Bin 0 -> 268800 bytes Lib/venv/scripts/nt/venvlaunchert.exe | Bin 0 -> 268800 bytes Lib/venv/scripts/nt/venvwlauncher.exe | Bin 0 -> 268800 bytes Lib/venv/scripts/nt/venvwlaunchert.exe | Bin 0 -> 268800 bytes crates/venvlauncher/Cargo.toml | 39 +++++++ crates/venvlauncher/build.rs | 21 ++++ crates/venvlauncher/src/main.rs | 155 +++++++++++++++++++++++++ crates/vm/src/getpath.rs | 23 ++-- 11 files changed, 251 insertions(+), 11 deletions(-) create mode 100644 Lib/venv/scripts/nt/venvlauncher.exe create mode 100644 Lib/venv/scripts/nt/venvlaunchert.exe create mode 100644 Lib/venv/scripts/nt/venvwlauncher.exe create mode 100644 Lib/venv/scripts/nt/venvwlaunchert.exe create mode 100644 crates/venvlauncher/Cargo.toml create mode 100644 crates/venvlauncher/build.rs create mode 100644 crates/venvlauncher/src/main.rs diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index fbb467c94db..441e8af5b8f 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -48,6 +48,7 @@ posonlyarg posonlyargs prec preinitialized +pythonw PYTHREAD_NAME SA_ONSTACK SOABI @@ -65,6 +66,11 @@ unparse unparser VARKEYWORDS varkwarg +venvlauncher +venvlaunchert +venvw +venvwlauncher +venvwlaunchert wbits weakreflist webpki diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a0947af853b..6dc9b75b138 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,6 +18,11 @@ concurrency: env: CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls CARGO_ARGS_NO_SSL: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite + # Crates excluded from workspace builds: + # - rustpython_wasm: requires wasm target + # - rustpython-compiler-source: deprecated + # - rustpython-venvlauncher: Windows-only + WORKSPACE_EXCLUDES: --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher # Skip additional tests on Windows. They are checked on Linux and MacOS. # test_glob: many failing tests # test_pathlib: panic by surrogate chars @@ -135,13 +140,13 @@ jobs: if: runner.os == 'macOS' - name: run clippy - run: cargo clippy ${{ env.CARGO_ARGS }} --workspace --all-targets --exclude rustpython_wasm --exclude rustpython-compiler-source -- -Dwarnings + run: cargo clippy ${{ env.CARGO_ARGS }} --workspace --all-targets ${{ env.WORKSPACE_EXCLUDES }} -- -Dwarnings - name: run rust tests - run: cargo test --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --verbose --features threading ${{ env.CARGO_ARGS }} + run: cargo test --workspace ${{ env.WORKSPACE_EXCLUDES }} --verbose --features threading ${{ env.CARGO_ARGS }} if: runner.os != 'macOS' - name: run rust tests - run: cargo test --workspace --exclude rustpython_wasm --exclude rustpython-jit --exclude rustpython-compiler-source --verbose --features threading ${{ env.CARGO_ARGS }} + run: cargo test --workspace ${{ env.WORKSPACE_EXCLUDES }} --exclude rustpython-jit --verbose --features threading ${{ env.CARGO_ARGS }} if: runner.os == 'macOS' - name: check compilation without threading diff --git a/Cargo.lock b/Cargo.lock index 96fc9aa8d29..ffcc1e37c17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3225,6 +3225,13 @@ dependencies = [ "xz2", ] +[[package]] +name = "rustpython-venvlauncher" +version = "0.4.0" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "rustpython-vm" version = "0.4.0" diff --git a/Lib/venv/scripts/nt/venvlauncher.exe b/Lib/venv/scripts/nt/venvlauncher.exe new file mode 100644 index 0000000000000000000000000000000000000000..2439c22aa932798bbb5e7ce13c34622d7dd7aff0 GIT binary patch literal 268800 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%0AtCf+R<^y#-Lr%7=n#0l)o#BcxoT=vzPW` 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 literal 0 HcmV?d00001 diff --git a/Lib/venv/scripts/nt/venvlaunchert.exe b/Lib/venv/scripts/nt/venvlaunchert.exe new file mode 100644 index 0000000000000000000000000000000000000000..99f5f5e9fca3531bdf05b627409794529eacf0d8 GIT binary patch literal 268800 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Ξ~Uyu&!p1vJ<8j=Bggx~zx}{- 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�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 literal 0 HcmV?d00001 diff --git a/Lib/venv/scripts/nt/venvwlauncher.exe b/Lib/venv/scripts/nt/venvwlauncher.exe new file mode 100644 index 0000000000000000000000000000000000000000..6c43c2e9d9365ab5ff80adb25b143c6580d177fd GIT binary patch literal 268800 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%0AtCf+R<^y#-Lr%7=n#0l)o#BcxoT=vzPW` z8F0W8y<F~+ure(|s=4}W9{(D}y2AY0zN@w|`?tfPg7!`R-QAwZB;0|78s2INhdghD zYTt@JgozC{GedSK$y?5ZW#&|!9k7ro=PXr5q-2#RHA1a6h1WZPxKpSK*L%ub-=NcW zxvOY1?0%VcUR;m)t>OswMpm~p_+tl(jb&V%Sv?K=NPc#0e&f3^t1WdQb+c)A$Y|;c z)W6?oe7~!)?C+7!ba}>TyeMo&Q+r>NeQKjW{`4qg^`vug491F7#XWjtZQUb+9@g;= zq_9xAct?q`8U_)<d}Ud{`b!w@{3sMi23f|9HiuQAYf`<(kNd{@_iy_)HpGlp)=$|s zzu=GW^F$Wn7Sd?lCqXngwzqixNr)Jl(D93nA9)ogpu}(l6NZm{Cv|GxejLMMM_@~A z-|0px;<_T<Nc{qDZt88cgzdDPySHs3_)lY!dwFE#7c^@`Y|$vJ$>Djv$p6O?GFrxx znLssfrAF&?{f3E)_ejuWeXS^L{R1O1G*~E<(JB;EFo)4b%k@KCJj)rqKbAhtXcN}f z-~JJX#>Sr%PvZ$^5v~p7;3%HH%4icFHu+P&^7<39eHOc!>DP|`S;&JX&ARkkVQXLV zVcCYN6A`6Ba5jyChZ+$%>QAG2dZIP438z6;UwcZp1S7C5^%-OJvBv7VJh5G;HOBUw z)>yU;fQaoH9Tuavm=?x2HX{?R?KPIjy_t}Q?f4noXDW+*I3{`*<M5|3knv=gdZECn zW28Tg#<FhdAyy;1{jHpg(VMpIR2a72Zo<V*@;Fk<JkyE(BRoYHUVHS<!k!oX={M*{ zD%gIMYWqm46Rn3dCkU#>F3-s1UGSmgEKauY(tcF(>pc9Ajil@p&S+;2rVjHf7k??v zA@_wF)*1Kyh`5k_%u?!Muznu$Y)iid!5B<ctWnv<Xa)s^ra?LqtX<!77f!`Z3+<&V zVPqzOrv56!I|+5@X~%P<U%`<-QjF1zP7O!<b;%TxX=&OUJXO;p*AHp-sARW_16zM5 zs(`YA-pZ&tuUsN+8#{x*aT`O{3r?2~IGF&*G*=UxKkJa$geWn@xEBC?=f^tq^uo|~ zLiK7P{2~H^`a*drW&#tVofGClVh|smta%LO?hwSxc;Os$I)5zO1^kDYOU&~q`LXiS zy!1*st*tm`S~GlmL_EqSS6n#W!4XlO7e`iCv(j1kRa*LQcyPozLC%9CY}3JAdUu5! z&`Ou^s*vZwQMv?U{$3h#z4anSFN^Q)eu_b?(cW4hUep4?7I`o0=@5DGC-y&M>b*|Q ziCCJ>cbkZa@3zmoZWCquYhS=Zl;<)rW4P`hp%l!!7LW}%4c7^QL3)Mj#6N5L?=Gdf zPIlVLN0QWSJs(PN=r+9b)^G#nYS-hu_3vVUU<6HJ(F)?dG1#zM9Jk`IT-a0P5J(KV z4><S#NNs8#+A7N@TW4|Hx?4MLRV{bd`8@hH{CZDAOK;2-!f03JFXB0HVn4ufxa^k> z-b$%dB43-K*d&(C^E|hnWs1j`1Zv*=GcnnO^`qLE1E_xWqx#jGDF=a*v-2U5-oT-V z7|zc&4IeoL)u=eVZOgVumPPzi!v1N<r+w_@%CcyX6|F0YALlW*V~)cMZNePKj7=IN zk2Ec)%|h8at)L+3z3eUZeI!Zi+!M}Vc;5UjetQ*hd8e;6kXOW4VEdS6EMZ4Km(RM7 zOuj|xrOoj>F|ri-hir4Z*OMNTIf(0oig$>72OH2j2}X%c`%CP{8*$|u^h9^C@sX>) z7JB_3dLoag`_I3Y25_wAe+r__(){Mq=1KAKcb^}rz~($El?Q_9?odO2G?}_LXiw!* zO&=GYRikn82%$!1d(#j0r%4sWYSX=~M8PB>87`6Ck4iD%1WAE8GW6>)8l1glGSlIP z$slvt!7Y>CA$P&BVTrg+h9?h<pt$$LC*+aS3kAgOv2zF&I;;#IpQppl6uzEIZ6bs2 zb50^q82ZNH`PJXVq)+ihILZ>Koj2`!Z9V}RhTHcFUjkXf0QC@lZJ~fJhFDi$1HblB zaWuzyEz~`kt@o?!<2y)ohZhd%J&tX8p9EVs2qU+wGf=<5_<jfMTv=0D&+bXwZf|{p z2Evp6As|N@XRxV+8R&p9fZLF!<3mg4o2yh<L2U>C6b)Sqnwf(&BF=7~LO=p@!kn#I zmtlbf1>_4sC1pn_-@fB1fi=dGpHsT#noVnRnSa#?e{2HC%3QUbq2LqFa)WVC3-8He zRlwG+$qgR=Dt4S~FLp{&1YRnUm_ttP1w3hX+fJTAYgcM=*Q5qU`(2N*#Go$IYAKU- z_$3wnA(YVHHN`D&OUNSNJ(v&0u#b{L@BK9i)i`j^CpQk?VjP;<7mb30V=VS#E{t0% zipa8RZ*!`fWKg;ntdK#G`w#OOCC(gzi2&n*<MPRM=wJmX&nt!?y)#h*yoCSLQ3Wi< z!Z8E0fa=5!6zV!+Fm-{9P3*iebct4Gn`pFNE}h0R6AVv-Vt28hVl43!%1JgH=Q;Wf zj&nC{9@hb<DW^Y?vs~V59l&rHJo0H_>+iBJ4wStZIaa=rQU&7&ggxu<w{}E6pL~-1 z!o3~c)ZT%uTl5ZKlLe>agK)_!c?ny)APh;IDx{k%?)#R`V(;t9&(r<F?F(z4I$PdC zFil~!_2A;dMpIA7!$!KJEQO-(A>u{|J8~+$EIu^9)nHmz)WCDqB|i{;i?qq4yfEQ! z@>KSZO$`F0k}nVfKKFejw>sHo3CncTGy#zTg15NA>zvC8W3?sh>1^0!!U$;1&wDyy zSxT>86n~|$lyy}pCqr@dTJsf7Xe$XtfJ>d-fdL_kvihp;p=(etv<%vXLW%7=mNPS< zc!&^IIXB?j6HszgoCu@E7p64@K*Hp`?-Uv+<So8oA#23aLVjc(M99%Zh*OYRw@A{A zbm3yYgb3+#>rA(@{()oC)~x1r*5iCAF-}x$_VnF)o!h&9)f;;0_HICZ5A7X2%Un<J zHNLHS*RRe<5)Z20$?CgOahr0^qk9)h07qA|wxiF{ElfItH$@*DbWf3W5D{D@zbu`; z^?K>73dzzri^XK2m%;hehvZqjAx0u0mkbcHieAWDpI%HQx6?316LU$$_$c|qP=Bxa zLYj)NOR~%`9>!OemJ3T*C@(l<dA;hU@6&QtAZ5Xo_N9GK@|j<lE6?=L^t&9~Tv-q1 z<RA?K{T!se5`XbslQ;(L4)m*DFddLW`Bu{rN?aq#ymxrjCTARr`#Y%$wO>Y;=S;23 zvk0#E(*f%M>O5(q4RtGZz7uMA9fMy?tsHKfmZ<fNVU5`-*6A$~lLV{*KdL=MqbnuR z7kiW?@7RHD3t9**)}bw&FN+cH9T@R`8REmK^f#n|_OucbkPBnq{n+ysjh`JOQ4(qz z{eIKvby$=3FNWBl@^gA>Hu?$8-IJMl4*5~zIdDSc<kVe-<4p-81EYHbEEp&PSesO` z=oS9h2cAsdT5BMfsnw*!c6e8&_}+khkLdOIQS9+A?$N3}JG5%gbE4YANX_?BV!+x% z>ALy{m!jYNTzv0&(b4|hPka0iqn&1+hxe$qU(jeUxZnBQW%Lq(Rq{JR!6#RGVvEv6 z(Na;uxfltLL!u5jtDe79;Xw!M1>DO83cE8<w+PYT4{xFz4RxFXE>~Xzpzqv5K41-l zDt|E(B{#_*?MS^mJina<sv34Sa|+~;r{yYxZq)FlC`Cb4F8wRCzcC9m%~6fE-co&E z10g)(p7s^b@hu&8p1u`)O^ELXp(Vy*B-AoWJ(<HiqfLq6lvh6cLCK{Y^Vw_Es~pv} zb*NX4EXBbV2Fv(N_j1KhC(+R&eaw1~(Z0$qxsk$T9F?!KFkjex&V+>$ZBkj<cNQVo z@V5g!RLrc~UV^ouYEiClUm>-$uUN{jX+1noB5T?t22*_tB{0z@IcR3P`ohuLTHRna zO}6hLgmwNk6?EE_)ITVU3x?rzo+^2Cx7G8IKV!A(UAx^V;dYx;N69tntMm@a;;W9Y zuD0j-m8^5+$zNC7GrW1)?Pqz6+wEV|TOvg)>VSKPY))yEynEBWcjzzL%mO_j`(cyB zA^Uf$RNA!<@JNl#+&9JuvxOmywpDHo!W7%f<%`ud?fctYN>rz`LNhFOPAVn4!CBpH zQm*{b+!;~@S7~@Kx^sBM)(UhK!HghvzTWt5XKRn4%-nUg{Y4_^xID?L+tsFkiQ6x# zm2J{N;yUz~^q;RD{Uu|-m+pI;8eOZcPshoK5!cTbrrjJKYC`}^h6XzfKx!jP#Db;+ z>DXG;jOe1+TB)lj5}`$ISr14d-EAu+oIkYWRmT`5%2VRt?lu`lp~<37q7vD*WTTcK zM@W%|1L<}(eZaCRF72;`DXUB_B603KHMUkwar8!n&a<deN>!!ly3}hzO-INQ9XadH zYS3F42C5aQLA@jyw~?(D#*Wgyk{hJC>I#uy(x*{%H`+b}sR}h*Kg(zv%bQzeB!KC! zd9=9k5&U}XfIa8T;26Rd5{CcFW*NuKs`vq4+>lzlK?p(t*@&%OL;9lV2^nzD9usbX z$okjJB2pnmbSVTpVM~qX=Ix5EiLIpHrtX8{f2{tpnf0RQ(hHYXXbm<t@#<Sn5FGnE z2o8M{Gi}e|KOsW$%!0OO_2cr1Ui|G`eHt~D(=*YdaEGS<`uw5U%GYp3x5~sYgxT|t zEPc)^Uqc3GpF^9L<KHj^sC7UFrMMgiH;<`AZGJuM`0Zn~ah_S{3((`z?77IXLF*MX z%-Gv*dQ`DeM(Z>%Sa~ekmm2ARQn))PM6W!wf$*K@NpojE2urn!=bElS2S^a;A%#4Z zJ2swu?suL0Y?Nnncfcf-npHkya;iq!OWn8`ph=(z(Ba|Lk1Ba|1!j-v3S7mOA{{vm zLu+@3>@&Mfd-j7ld7bD5sk78APV5xf5Oi0D8up-%Vu$CL;+b*lm(!fv%<)8FE*H(^ zczljud2oarKEFnDk&{fTUe-^$^kK9&;6}^H{h-tFX$awwiAC3`uRrpIQhA0m<ti5q z6mU@oO`5+fBgompI2ng2sv^7RH~1VXUle#6qJu$moi87rxA3ZBm*(K(p!5$q$I+zh zLOslR*W$NpWSz;vMB7ScRlvfuZR8t8&ecDVWm&yr%|uvjk!Qi|pN=UMNs%3@Up(SM z$<OH{+jG1RFkJWS=Y*n<bXX-tmy6Xf04r`UR0W9?c{apm5kV`Ksvz8o?Q=G=l@;1^ zt>`|$GI7xCJiLTcJ-8z!thK|;#&So-^4SIZMS$A~?*LYeO;F!sqlgG)E`;uM{@W*_ z4IH=IXDuOG*`k8L$EYv`Lpt_c$#aYpR`g9Ok*2%X8`r8v#n(!6IpATJQk49VA2QWS zCZStU2$^S5l^91wNKFt?a~Nhz`6M(*NX-Y>m9=)5MgZ}Jt^2yq>6oZHS_7SQ{^Wj~ zCJG81dRcOqC1|<N>1?3le~nS)ZK9yZj-y8BVrzdoRY4UmOVH60G?AdZ4#XGXcdjz+ zoA(v6>P>}eEc$N}!s)2?$7r-&C5=rK^jC~Ho|DN4Lz?!DlP@_*K2fFC#?!HWO3jR? zpe_rb{t~@~(m$Le5dlI=rHQ=G0HtTS`;d90CrWK!=MX-ZLM7=VJ(9GIUx<dXZ!Cxo z9L#V!u09|Kr>B#utwl~k@MB_&FOxO_MHgnl=^4(*Eox+qwpubn-`zH<fp-@-t4%>6 z2MsR6D3NkV{ji%#MHeuP+HdR$b0{*qupQ0U8r5`6tI)N=^<lLdq|_T}-`ihya{({{ zxoO|)5>hzkMq4}Bm;63IvLC=M;BKZ<$nc;u`Ohwby;CM7tyjXZ-Ko;YRpl5iLSu+z zH=ixB`Wl+$V(eNkX}Z*XBu2@%ZXZcBg8SD1RYlGo!MKc-Wo@r3v0Pz$Lgmd2t+jj> zB}p|;qe~uFwTv*@`~;F+GGzGm=#8KOuxPidIQ9B~dR4r-LXO<fON<P?t&=F+EL5@k z0MtzT{^x4xoZ20?Nx^B~3JJ+vpw&QQsWk~uR3IckXP55y9v!qWGG>>iIKIt5Ot)$? z1u-6^JJiiB;z<y$g0B@oa~94Mq7$t`5?KTJs>@0peuCF0#q1sh4PxdBgb$?`U8icE zOA(kE8jw<rw*P_~OkOOKDm^2@Dw06Pc<01C;=5Kx*CoD9?gVFv@5z~ZI|ST?f`J(4 zkA^Ujvar6jz-aPyF5;npi!AzK0S%^Uo_d5C1MHSr(K+N(bD_?3)L6sK2cP=D{R=+f zasPsAWA5L$S25GM)W5q6m7meFtTg=HSn^p_ZeI!&#Xj)_bnj>t@V;EQbEg}>Xz_jC z`KO6__*OZC;J7`<&vAZVF~(^972(13x=_Ol(RY&T_z2m-QNj53!C>vi=*1!X;sLbd zxsEMR5yU`amLHs+xbv{V>4~Ec3t9)l;|_#9JA$K;#%>FZONTsfnWMLbj^AVMc_q~L zUi8#pywe+Q*u3DG)bY}WjQ&8yn8+8Xo0#ZMsY#M73uAb*VN>K>)xO9Ww{1I9wL$yn z5n=_x(~E~YBVNL4s{7C;a%zT1M#9>b`aI6pLek~e=nY=O6h32jQ;j>y%U#k*JwzDu zCwd)*KJk_jyVH{%6Rlxw<TN>g7cPY~5`18+xXxa8Aa&Td7gD1*B$o3CCvk#;CZ&M& zWm*7aG2SJ{l8gC>)qkFEqjd`Ji|gm!^0n*bv+W!{!QT=}`^ec;oh#s2mFIkw=x|BI zt=2U5LOY#ms?jUg?RV%^>##2ZX%pCvute=f68#t{iku(GLM{;c3SAYbQ!CVSS4wV; z*oqkVHmHz7;BEZ^H}%uLpXMbNTuJ-BPe^hskxE(T&lH*^o8=F=DA@s`X|kDofCPE` zSnv#TE3cGR036TI4dkw+r`;fk5?BLysN&f{(5t;FsE1b*K!j+h#YdBp5mL3j?#UZH z7ujY0eNdIa_MIj<T^{QWS>2DMbweom&*~%%%igb<fDCo8R%lG!3W6$FhfZu<B;<&* zFZ6I34#TUilp<f9gA8>BT+=K#q&ZMYMRnD_&$cyjTrPv{^i)a(i$MOp&UmQ|w`0=! z#KV266Po|CRAM=V?pc&2{!t(0YSILC?GDV44yG;}(iHFVD%G$Rb@EV+Qby_-o0X&| zb)$+<9%GE!6)_2{ojFhXfE;QDYs_B=Vl-wsUlP)qXj9ZFV~*lsMdNJOIEAA4|5~c1 zNhtpD{~|}hG*KBLdTBf8o-T1R+=^vz3I4q|0<=2Fc_voCW8rg6(kJvt-mlmVchOV+ zVw`VCP?p?_zsTgZyJ?#}_6NI!j0FZCgG(=R-Z)ZKD>LX^j)KFGA<Ucwu#Zx$cUKF0 z<|^mc><S<QY8DVP5uNcbH%HzRnv%=uCH|UsO4XXV_l%ZU7ZUb4FUZC^QPB3MbdW4n z?z-gsuL^<zPbl{k&X4qWKi`gY<IXZw%0gB1@})eS+f|Z>r7_7TK9D`<!+Q2s#0bh0 zymy=i>==2N`49o_PARzytzWfgO8!LduovEsDBOS7WcK@RM=NPlb{mxSP^syAZvT`M zs%P^jSP<Qr{WFAYm@<}reEtI9xOMm<3gy+GP@dpCO`0d;di?(n<*A8qDTAv{>2)cE zl7}o3AF006XHshc-*qA(2M@;~<RRJd)Er}x5`_r3rhPB5!&fB(oN3?ld>ve>3{ZAG z|Ag@ofTR5y{BrUg`Bwe;9oRcu4gX&GE<E)?<c29EPoMV?@aQh?vM2l7CAlFLG)Vhi zAmrduKeoT{q|PZ^?>so$zlR?QP33U)B&hps@N}4x{sZ`Q$7c=w8K$659v`H8YW_4a zm2Jf^#ry~Dac%NwmfG1#C;u<Y%hA1G3}4tM&;z+UtLfyWEcnAT<`eR3$w@-f)#Hb4 zLPqfCK1JQHO5~)+aDRn+|0uI%@C}(^)zw`2y@D`lO4ZfMzdRXCIaO?AIoHCVyo-pV zM9|2orrzDjO#|85$)%{_uS<z?Ia~Z;w$ZQ7$h8DIz*pMWoe6_sK~-F8qJ57NIs~3G z_puwzLtaf<f2UBcXnu)l-`Dw6qfQrwIj<V387U53QIrzDp;25+)MN@%#OegT2(Jky zX4lC*Ib1VhP9kys2*N{&6N`DI#w-oSyK2nzGm+2YVsx-5G7wBm@Mjh0alr7`)N8@` zqAGRozURXHAz8;)(01(;e{IlNf_RDD<{&pi>Jh=s`|N!7Z?`pK|GXeug0IBXS(vG6 zF}NiaLAB<>IceVp6+E{fcwXA~I~BZ8f=fvqtzgs(b-2yMw_*U=&~WDER`MPSUT>_^ zO}!9C;qpeOSxT@D=yuINJ~2faGXD#SDN^(NFH(3-f#5_MzLkr+H2g4XNj)b4n+c%F z`z|2hX$jyg?tW=o+P6c(9+er@@V<FFARpzxQ-E3l{>`Kq3ixvfE2Pv_>UJ0E&A3ID zD-cZ~8#5t^6Hg;0jwVi*YK|;ek5FW<M3+tG_ijDE$1$mHO?|kWNWEspCl*Dj)4pW5 zunBoRW;9<UR$*aYa0VaQ&F9-U5PTCS0STobBlZH+?TUO{!$k_WD_Tml$m*8Dn$d2h z!fV_LmHO_G^)G{8%%b2n7@T+ApGydwlw5neyOL&6Ng)*c_Vy&3y&C34P|QU$6(>K4 zFbr?$4w7I3jmX^i&y;(|HPHO#Cgbk^t@gTazo;&9-+oj*K%o-5th(QQyRW*}eQT}m zkT>J*C#qNJmp@mp<VD#6coFS2w(Cax2Fk)MI@)bW^DjyB3?0jsxVOI3XuFv>6>vbl zuFQPxm#_OJpFQ~_Jg}FKlz@o@n7HkzGO-_t2vWG~Fhby)f6OhAJFDwGM%y#8<4oLH zJrDa#)iIi4@|3-2$ew|?ZCQ1@yeEECy;S1jX<WE$Q&}R_b1(&UBn}geICt&HcU3ie zxI3#GLy1uJ4183CtWb4Lxb|6^TV96W_-E!%4mH47@5QtD^I^NZj{a)?Hi#4X^QYOT zo^6V|`oFTo2kozWrW&iOzc}^W>TyBq-N@fUwFgYkyQyN+(={$FHH(y@VROq*lc&I9 zN~sOc8}Xxg$DS011@I$JIU&1zTu_?$TRv#WsfSHI_tdh;i)L}88V&gOQV&NjQF?w# zZL<G(xp0YUSy%F9I>}fbz?i5JYS=M<QXzkr@k{V~mBVQLg^UTtWw311uxr6K+Lm0V zOW5z0(6R}!d$aL%{?F-nA1}~=!92N7(K$45uqi0l2ACfcZ!3)<eoW!8>f{GO;pB-3 zZ9;63Ym7J!NZ1G7ba&W3wI1U_b?A@4-|m`Z+)^@e*A(NH>n3{Tap}Y=c{EL|Yc?WX zyCxgAgwF5XHPyJ~=JWgIan<<)@(7(zqax2Y8?W{N!LM`%@V0XUec>arLw??C4j8Xu zJI8qCX*6y+qF<4~m*r=h#B7zA^(tnL1a`^K^Agh|F`X*r%R#&3bSP3Iy_kQOsN}SU zkeG1KU4Rq?R)l2EV@Qbo7(r^oK(SXv1c2LL5ZY8WAQATvp}|@r61fO;bW!q@q9mdv z8<AIQ%QsUa=@nxY_Iwm<_}BdTv?BU-3`-nmCK%R`ed?%?eO+~ZX8-1nry4dHp{|2Q z=`)l!%`Q1l`g9_l^HO4@C-qq_H@Q7>&Qd*c6Scf*o)Te3?UHlMc*g*4(wokKhJi4= zmV1V)!)To(lO69GP`wk)Ksxwsiq%7s#9C=l?9S?o5!eb_5uz)NX5zl;m|(7p$;RrY z>KPoS8m)4g5^U%-;$dD8{&_kK)E?RWj$q>;EtzwHeQHHe5b`ui6B;o~a9pFe;5PTH zri0Fc<o5VV{1$!=<r4&@*0Vq(hCmP^CRP4#(g0}EnKclBJ@fxY3_Qy;`^+yQO`TtT zfn9!zLeclB9h@&eGiZO!0}4z%xBT>=^-koa)I(;2g7*I=MaaGt*Hx#M1=G)BO3}1f zx!&=#oD4lVTb0sJ8g#uvUrYzi4V?w~dpGi$Sxjq_*8*k8Ffspud6WU8Mw4EnM$_UZ zqefF_UNphxMbmIzG+F0GQ+i%B@kd!eR)a6i&iO6EobFR}n6ZtP3Z#CE+gnFt8+G-I z+z4A(8n8B}uH?rb-+mldrXn5>>SbJO>u))egqM0oph>25(MG@Tmm8iZ6MB^gMYHWF znr%nXOcc`IKq2kL!00V0EWm6Z=^wo_C6Q=`ZKi_{Le@V+UuB1biKo3W9xcZzB|5&u z#9ND3URbh34F!<GtdyrCaso}B|2YK;i;iT8-2FOnB8dy4PkqI4s6YHu0H*bEWU92h z#DhMw^?N?X_b2~{hscNJg4+*yN6Gda$u1BrN25=kz=vn2*{})Xzlqo&vCe}kbW1Mu zixT=BLP=rp9<nw%asBCRm)5LvB790o>5ZgG5$Ie)NOtK~FYFF5JHI(}Z71D$XuAm6 zaD3cFS0I+Yvng0msLt7dQ9og4>W44@1^VoZ-<FMM?)*7WNB<}h=Q~^zZ0mr_sWRFY zkC21@({XAO?s2Z-PJ(FD26}G&YUZfDdrBE2kKk1+f#Tn>3Z|U<-O<Wm?ca^oZx9*m ze76)T1T%O3SDNU3;>2rsq(&1~CH&i!*s2%)%}ks(ls}iFo*<gL!BaT*?{rGBoO246 zmW)XpTnv^x&RSGt8k76X$1a_z{_sxIyWk8txGm#U);J9Qp35bzKL=)l7=}*!`X`e^ zpCEFqx|OfwJw$S90MU};0ET3IqdxWv{eSG;30xI*ANc#3v%q0T1;zcKxGS>B<^~FS zP;lRJ1>vA5f&s@RGeNY%6KVTuX_=W;YKy5^g1csk+Tv1)mU#>-%SzLf`}xhx0VI2# zp67Yp*S)X*y+_}C&&-+iH~VjX^BZ1urrc6`Li8@G5=%V6t3rb)UY~e?iu?^h+(hSb zJS#<F69edmaWHY*cBPQHsP>bRgPqj3mn+X;P&i@}Pq9`~;xoB)p{XiGxty-eUlH|G ziV{fy3@Kj8ic_9)xbA2aAySReP|AX{!m^~2<UL{{Hi?PYqtyN;D#K3X)oW$E;7}>V z?s&7D^|j}$O|!!&1!=PF=J#vKT@tBp)L|q_`75}sRAm=en&PANb&Vvg;^cl3xBG3} z;rY6tSDnT8Qa@$!y-eWx%DMcS{r&PI2btGM1#m%9S?!-eZ?$PZSu~02)kOhg@dyb; zs40P1v`4I$bxy>&;N9QPEGDsLMI6aQDo-0KiF8pEjH>tiM@@O1n*1-Z0-yHO22%f; zM?GKrH~Br4j-EQPdZd^f{jN}cG_kw5GQ0ia>9*^We>1#Ij=@Zia!GvK?AE*9u02a1 z$qv9dKZOgsOUf1F?c-<dFgqS+Rjm3})X(5{aUAL9+>40Zg(RwhNKuMWD!VY{^eaJ} zpCkvxC6$yp#$_t&JcVM(0$`gET~Wm_`<*g}XZPA+iD8P#sXnUAfU>7i_AhEtnA>wP zxDH^=R7*NtcjT~sC-c@3N;);uN;+M0R6eeZw_rm3Qcep~l_E~p9NtaD+Ox2dB2EtC z<$aXhXJJ%Qjz>JJw6P5<fp20ZWp*z+%j~i`F&83n)p9^$6BYZ0OAP9;^Las$zBT1{ zluFQeew{T|)B*5TU(tiJAl>UU)hsTxNRKmVl_<*0sE?S@&&7vdu2)@%dcCC;9_R2O zDoGPpyJAOKFrdy>){F75lk82X@~fNws_ifT<@R=;uT;>^Vvnr$|E*8$#MiE<Og9&x zXZfvWR+J|JS%SB(X>pWK6SoRQJ)z>N!rpETJ#i)e3Zcp*@|($9Nm4H}9C06rlPOMT zMD|FN{BOl2Q;73woaG~~#*fr3V<s{P;_9sTjEgF&uGedO(XpeZdal2zFM$1*ICA|T z`To$b-Xg_&gcFqf{r~gcA3CfTE2+K5aKeiJFTX$3LP<nzPjjN$zaLHiFMWULi?KBm z$_dmnqKW#oPIAIp+E|=%(KJdlEu!f^e1E92^Gsrw7;h#ilC)ZcYIm`<`zIy8N@U?! z><vVHyFJwBj(I4U^Ct`KNlP*nL~a6i#uI|3J6p{bt>!8BGOG-s2RWM+2R`v9)l#w~ zs8ICjJm$qY?4r_adj?m6)T*nLgRzKRp`}#G4(joEjMzGRKS8JpP_|F82Xl<B(^o-3 zvOSxl=S<#0S=!ZLDcU<-oJ$t(Fo-A>G~`UnBwhuq9%RV0q-=)Bk`_O7y#0ry^7FiC z^paAu3p?3I@|P!Z;aacni*v<UyjHnd-OD$Uam-lKt>i#*DQ}7suK?)7Bv9SM+e^h` zTS+D5RUNpTJI%Ffi$?JkRq?;?<`L9Dx^d<gq>%lj)aF4h9uK3^u^Ub%E;&8VFS&Gx zT$Q7o(AMN<CX}2iK1W5v_sLZ2$yMK(IT2(GP1coPP_Fgj-EN47kMg?FuYs=gymhzy z9G8OMxX^uq+Znen;!AE&6-nt3Q8%&M_4tzGRUS0y%g<2_@bML*%EL~-SmZ<TrTJHj zZy9|Sk7wrNi*J~z*i(Efx*-;5UTQ+g*X2K&if^@E)Rc_n@ZwvIZOP(CR#9@rc3*5j z$Hlkk>raj(#kWFi!Tc(PXun>eWlh3dJWmrV&asQsBdsPI1^7k6P?eQPP3scYhE{Pt zTYl$avPI*HDkT}obCp&oot@_!m0HC6d^o#$>F@KJr|?3<()+F!R~z%rRAt?E{z%5& zf?IKg)nyCKe4TK!@J89f)^z_TO*8*2KJQw7-i2#sx2h^%+TJR@(7MWpFE@+7@ln&z z?F>mBN4=n8a@=mIqw!f>;GEr=d?~u6q|xA#tK~nLimO{MYC`G9?#0!OZPENBHfa(l zTJ%v(0tG2wLToLHZi(~}zqO!M&Fo(ErYX78p=9}#rPCpcd_ZI&I4?uRSr$c@#i}Rr zd{_^CDL7W@6;>=RuXa~aNr{?j-dd_r8Qg94t8_Y=ET5zv8J{ILl~g>%VrfodT!~xq z`@9AzrQ>BSB|p0;W~$^WNy5+LWl||lJOmd_Q=U#F&Uby?zE(0K`p}y}bfho7;97p6 zp?KnrG`-?>qvUICXb)8Npd+!`x}7PhNyQxIw5kO^=*us7)+VlaLRfn~M_Hvry<l1z zkN9gE_pME1cREzkSky*XTpeO-TbstBlG2*QxmZaP+aI+L9n=z8O=I=`i@0gliuZMK z)9eb@joO|?oCzy;952vLocVEcCQe*Xn8{1neO&BGDaI;`y5ropiwjW4IdQRL-z_?w z;9P=YUz}NceNueMH@U<wC|P-hcKGRJE))WiyHrrS_i<~oy+u1;v6Olq&u^R=Uvy5o zoKpInL_JXP`}CqMuQ##Nuh@k#4qry0PLHy4c1-kO64H&_PdnhaUwj`dzMpWc=eyEH zNr0n_DWr6HzGJ^AmsINYC&4j+ydOs!wa2z}%oXtnrP(owmXtT9K2)z;qN8fc-#P+l z>1ZVWd`~+@)?18haj+=uTwruu5Z#Dpl^l;zmWW{~_q8VM-o~gTM>zeFBH}v8@eLh? zmoX<DNiv#u(!JwJWtgJn#VUp*x)o_9GQfRBwaX#u%!<-y;^F^DVqDnoaSs**nH@7| zSa!nh^<<(f75~AmSY(89XKm1a<04woz&!h3j#Os-D8Z4;D40R_t=0T?k%O@4V58c> z)1rfRbg<tTPv4{*=kRe`WMy2-K5{%w8^=H<*J3|mDW>c?tIz?*_l$*fXqXsY7SH=R z!Vgm6eYJm(7*^Xl-BOd_0f%@hg>Fq2yNBZ!H@eu&AB+8(<G2z79D9k$_Wc(7al6S8 z@<Vksi!+fFq^Hqrz&4{}BVB|a+blbRnHu}<s-|?&R_&rES-@tV)pR^UH>{N+%hqBS zw-Ac=giO(v$IIjktWX!t(|omm<mgM+D~P>CRF=y~2CXcRJhr04hcD_`PR?cc6j@m$ zV4&4vH?(8-#^hT}RYptC0|kEJWuih=FW&?zRQ)kbUC`73c2(<_1>?HU_(y*hj6*zB z<>=d0DKKvcE35KzgsBZ~;RhWZ_*(TnPi%=b#ktNG_AaAO$$ru{jYU0ohQmvpl8#GH z+k#kM*Ym66U8S9kTp+KvMDr_ZH^Gb{JkuxF=v5)+>_GJupGC&`bYLklE+&XZ5oxp& z_8CX>S5_u~4SO5YDmiA5D9U<FE>ii{6pD0a#u!(I+^n|Ev!r`f@P#$0R0RE@qL^Wc zWSUOd>OoJLG4TPmQOR9)QMI5SMOFhW`lCMY#dCwtvyhuq6i@XFO6qyEAizGzGr4DZ zp1ZxDNnf7avn-E|epkn;GvUWL9@vyrD!wO3S}4A`lddjAos!G}+J0);Nyk-|RroQM zkjIkKHdo67{);(|kdVxWy6-6LJy#rAmYpmxEIDTLbZqZ@tD2SjZGv-#Szg+HuekRJ zI_9eNq|Mcl!3JGLO~%Jb-CneIq|ic)%i@!8++yeLhU#umS%=<dSIu>9^RlZas0>S4 zkYc+I7;Uj%5?i&!ZcL41%H9uq=}fn0y0BP!?zYV(vqEGPik%`Ek=;YZ>XqnAb|Iy& zVp!YkWtLF!eJR;2d@oH2P@BEzQsk4BaywYO2_u~E;nejLUT)dOgz#ZVZD|Ov9UD{4 z9Sh?rZk38nWtin^1&tnJqS#(2fDvA{`>>Le&N{eNic@YFGwbsy_(S%<GA8Hn;a&XU z=wPEAIYC+!`?d>=(^6~-a(t(L(Wz+=UKV5Q#Ta>PRCI6E`Wr3ceq$^_e0Hn*z|kV> zS-h2oJI`gTg!Yr$$dw^serNVs#0L9EA9qqJnsSse7S<EyUwvlQ(VXr@W3_)d9rJXQ zgFbmeKzWSR{t<htYX39cXvJ{)QtC%(Us@`jD-~A*(PA=^?FYnZe@HSrlhRW0tf^?^ z++N(^PYyDNAFTGzSB9=Vxys#NONsLQh?o|oW&GBs#fYg%gI^xxtD{KSE2@2PrIE_m zhEaATjyOupezkuGv2;(cC*@^J3{c$6QtG(ox~1AbOiVP_A91W7eC2rKB`WkWm0I+U z?c%7<9qS4)^F@0^nz0-&hAX=<<rpL;*%ZvL%y#THbt^X6LM-W)UPZlRWvgwwUEOL& z$pN+|#XICBOq2H<FTP#ln?zM5+Eyp_O{@>>HpTkzY*%IfAdX$)uw}N`sW0%4qMqsb zvnp*t#R~!qwpR9$=A!Fn<6}(=%%zLZT+jZFDs1NX7hRX)o{<Zvws>Mth;n>1PucCr zX74PXQnODnIWpDn4)JOu?f9sUqqL>{D4mt|UP7qif=GGSic7Ws8nIZopJ3^ECXd(F zGGMWFP9X=WX8KB7PEq?ESFhjcf79>9TxImiy6J1-ab5DIISPv@@eMBvKj`yP(a$*r z*6gBRvU03R9!KpCW$P)rGSHf1^El1}H$E@f|FBJNce!u1k7d6xp%;t0km=-j%U9Om z>fU8kpI6YacdCm{DhTX7TZCrDO@I7ZU3|?vH^{=dfOc5-dAU5_FTrkX#1&e6@d57Z z8BKhxa;05$Z&^wVKNxTSInjPJ(SEY{rt7_N@ul&uVjAtgB-lUl+5U}Nz6bs3=&yp# zSrGSnZ53xMrIzbHFD2NIZ4IKKr{i9wrv&?Hc32Nkm7t}#^45N15#PkRy5G2fZ|vm7 zp<_B<eYRil%Wq9bKkYY8to<p4ultQd`OEHEbky9ZIGh16K|F|2e$H(1s1TRqW`1hn zQ~akGAUTERAeUa$lk)?OcB4Oizg1a$z_j0}<15QTDltlYi~qgcTrjoxfO)_1VyrB& z((}UwzHQ@SZn9gANBEx5_8?Qm>l2LSqN(RW8+9KuhBmumciG4cHLj<1vDZfa#?j&F z{l=Ai6;n$u5A(H($}x>g`Qo$vyC(Ub7N6~xS^ma++Qj>8Ki4RKQ0?&g#@j!NXQxqq z(YIZ-WRQx_x|A5LEMrU2!A{(Lv|K%EY3mhDua*+8P(m+x4mE|J-AQ8`ZGisyl-*|n z8Bt}Ud^bW57k*+YCI)q6E3s#@PIGn>&5G?ig-wzDb&PQ$ov?+9M&(o8t|7;k>UMt0 z-^R+{0OfBp<!=k+Z!6_*WoOalG3D<irOnsM-*L*{mz2McD}N^|f1g(V9#Z}uR{r); z{#Gb|`ze1Dl)nR%zZT{1Am#6`%2+;E{;pHnj8OiLQvQxn{;J~@uOW5LjWgC>U;HHl zNa>``@9mytSkC^apGDa<QC!O`E;CDxiu<b3SILF1^8@E;Mu$@9{}VRJ+J42i+WP4@ zdMy__Qmrn0u{ChyuH=MEeT^6;Buo8Y5nWI<z9WK7OX=TvBf%`nj4=_PsE=x=EIl~i zO?|Gpx@wYm$eS#yK(g1X{fBoDV^MA}aBGpuABKq*?DsoUJvSHPt<*b_yB4T-W&E6# z?kLsNQC+4oWjqtDG%IBjdbuQ*M2h{Pa@)gM)KZzzy7iSO%e(RhDfb|_x9iCRW6B+D zUJ~NAQ_LkdRsRq#ZDIZ#B%`Pmp%v*x4MuVSl}2&)As)mo>BESWx7AR#MR{^kDY7>D znq!$bDv9)@EQqo$Cx$5Z+#S)mwRc5o7mGv8htmCQ(fw?ycHyYVMXpF_V-OVs@bxlZ zU!z<IU$?R}%Cx0&f?in;bXh_TAvMovq-<55k5Fb@`%^o<<e2g~9z3O$QgXoYB)uoc z?3(LEv8y>I(dJh%Kv5y6^1Ikgr~??ysY;>vjWZV|iYlk6dYX|Fsj#rrIDfn{sVCIa z2yt^tnS*0w5%P5vlebRUq2A$z6-s;FEg)(bJ;!N;SReoG_}uP1zGwf#@#X!~@jY|b z9p^9aKOCP<8(;Ay=kgY3T@<TSU-wZ-N>-0n#w3>iJK;*|Z6fv56`qC6Vh{^mBybt+ z4=5`GuXPitw?;jbE9;Y4*%Xu5AC!7s;>1s_1iYWpg-G8O%5b#hDIR7oNnuHi=B|F? zWu;2-l8dxr(R$@56O>ZBXiL2&j1tqx7E<a}#<2N%hb1gtCZ3{9yK@PP{gODuDHAwM zOyIC&QIJ_#jeL_!d3Cp_>X!2wr5F+gLVlUWI?B1CnO;*Uw!({2l=Y+dQmW`Zl`+&! zzuFYhs(w)3eVBNHAvqWlD`y!sEbvWCLcMiNnFy1*{_=$TXr|tsJi1k5dC;w7kck{l z@xD=hXGLwGR_sfayIjqsnMr_tWC#u7&^eqqJU}drCd_r6?N3|YTD7M>zOs)mZ{+nI zZswV@I<>BeXZO^)-S<&z)`O#|z?=G;JT6l&lSgg-6DJkE+D1KNwu8%Bb$%|_U8drm ztom|h`xEQ<fd^GriQh{SQ(4B-S;oVXOD2tDHG3nMz5AAf$?1j%DK|um*J(jczyZ38 zqkBtIajaw;#bS!%(J|hLLMj-?jiidq67gz<#8jd$trABltpa#Y!!VMoy_#Gk2+1W* zY-*A#rNm;UpN>`cQC38UL85lT5oU(9%BPd443$bzJBhT`M#xyaE+|8{n6!~ydq^Fb zHh%gw?j5eQqP(9n{@=5e#ifp)?e4bmQ&=^QoBzC{gT>{T$**;`8pieqJ*#g_Sx#v= zZ8<uFNcz<OWc_1K%EUS$Qh|Yp{^Ladq^>y3jJ`}&B-c1^;-KSb{bTKc!nyv`-T)Fg zm($fz?^o~tCUc*<WWQY&)sr7#_!PD|z^&7eHe!+YE;4fo5m2wZP4OZ5vd`uQ1(V`S z$|;DZFWFUm&X8Cde=DBqx%19ahq!FBkvq|l9}XFmU61JAR#BxZ*~Pn&<CSt|;>L5y zsd(j5oEtWrY!~RcjYAR1c)KB}>SYGQvZW>^juz~wm^A9LOyuHf^Z`-b(-^Z|RBV-U z?E!h6p@6HK`vaA%s(O`)C71R-`!2ImYGbb2ji(###BXRVGA)=5O7bL>98hNF>G+Z| zs&7+gW@x(MffV_gn7`^u=EOI?_@Fi;2|k<MY{wHyQncBiMom#Yr#2g(YO}%1wN573 zJs3EHOei@-0#U={1Ih9=YM3lFy0iOa%cwo<QpJooeNgu|bdkcWjh^ig&nq8Z@i*mR zW~3^G_-fLCrzTRkwRj(kOk`BwUckk<?un??k63e(A*$b#d&#yY+g5U}lDXo_3W}Q3 z?7H-zs3rF@^;h_7??3XkaZ0|YrUvKIg<M*<G%!Y1nbED70a1T_FI|CYlkR>OGANJj zORUz#Yo}Y{E}xoE)yK7T`dl5;$I6(U_sf*=mkt+`^v=?QB}Z*PR=s}5=~s^doQ>Jt zivD2I``S8eRc7J#`)OBJ*uAyiU6+>eo@(0<+`ued$R4hxg;jxRf2O4*QBZQpODu#c zbG`MQNl{jk#_DaolJb%R?ZnuwmeX~d&*QriN(<Rrm&F%}Qeb7gcc3g`$u8T0-YmVo zK9BAag)61Fc<PXjUs7S)thQcqmX_)eY_m%a6&(`YsH3s1teVUu)Z7oOGyc_dRWp8d zK#$c7|FN=oQ$@j1ZTLmC!}qN>{11u_Dq~BjP&Y-j2ub}#WB1-9tI6iGRNOk*E|ON1 z7Zd53z&o6sKT>aH_0nSrCA)0rFH)rrSzPKHF_dSnjAKfbb-jYKjT17XCDywjC=gwU z3xc*}Yl-z!dz6D~fvdYjLi;Qgb?cQ>P>D}a=&`MQFI09Y{+FxcdEmbs+FL|RSdJyT z$UI>=6f{v{oNbuePn@!yM^?pe=hrwflrm)~p_bBkgEAy>d$zpVKY@XeBz5|-dDV=e zH9snwmoj%=6%UD>i}7c|_8<o75N~3xyRwS;QTD%D0j5;v-dC%?{0OfVrZg8T@v=$N zDXf?((uV<wbaJp(i6{=tB84g)k8mL3GQyF$UuvWLB2qL(zLi|Eki)I5f&;+`wNtU5 zbewxo+=%CnQBPZ?O}tU0(lOLIgns)Cv)fQX1vP%yE)Om>tVu5IwO<soT)|@*1@|y1 z^k%KJaZ2~IqiLPKQqA<m3+wq^DS8x7g<E$`<U*@>e@Bsp#vYq%9Fe}_Q|PS3v4Fa& zMA@(>n=*k~*)UQyRV~XTiiMG3RwRB@FOx*!#!6AR@wRSuE9C`h>KD@z)%=&*A4BSl zrP{wUO=6)b7k63(?&2t_?m+Cu8J%R2X(+}eYDzEV<$fjNq@fw@EhQGdrf|Y);T$vZ zvU>hf`G}Ycab{WS)s?O|)q90jZ^@2_uZfyD>#hP~OxpR|ZP#bNKEbx4UZ0(11?THO zyFUB#>g}so?VNCSTJ6a|ON|xk0RP2S_nqiMY;|wc8(f{!YX-FZF=fjB#ef!yF2sN` zv;o<T4XK8jn?ZHn%~z~cil=08*L|IXNDtn0%No|?0j>$eYbeK<c>j&pI?~kcb%?** z5j809_)D^T@&=8PajaDPic-b8#o`iAf^mO5kvHqPgqE=`af^%E^pkuoy!3<0SM0+B z#0%B(+g06DbN*|W`u9v|@Tbp`oeU$B`qDCwmu1EWmKA(>CEjNT<vd!rvprB+u#Cq+ zcIQ`ciX@H*HT&aud(WPIZ2OXU!86Za80aSNQq}s}-|Gz0XUQ~nV9J}_e3ne+t1<>k zbF^?zjBRTBjA`SjL$%x0w43NIC&u0-+ji2H*Zp;;E#IjA{0ZL4Hql)dAA6UM?+K>U zg=B6&qm&ugB#U{jyS9n_r^tS-IXH^4KBb0+6#O>sWHT0d6xWn;KV+_Vj212UViu<# zEJdXv$13(JN|uFq9T68u&Mb>zq6&Y`GA?s+a?SBIw<47h|0m;9_h^hy`J#<)!9N;b z&VMsL-#g78^?m<?`MY)LznDMsoyPYZE&qLbKK$Q|uj8G@NABJ~7+<TJ@rmP8O;KKs zx=AI+I3Dq?n^czn^rTWxSMhuWmGE3WVF^5tboH#IWWUdfGRro`BR+-6os{S8o+>w$ zt66siHTia<#7hm;OuOgFKfEo|?m5wd{cez&Y1d56wDVG5>SFUwrfNbp!_G&HoYnBJ z*>%cUZoM+ab=OCcUze`r*QLaX-3#w=vcz2HsgvuxZ&(x$czl<o)b9ypyNLP1;xpj7 zl6@y`X^Bh(cJDPAd7?6YWhvIn$WyioGV(t8UfgKXHjZ){oLlApzkhP3gc?~sfA_v# zvJA)`6`4iG?~W=*$4ahmMHb~{j>`N*j$x_MgVP$mvcEE(cvGxXiR(B^+9WQIiOQv- zdJI<&s7m`%|HO^_TuL37J19&;c1X?sT)n=k*#OoTisA6|e92`l-ndY)jiCB=7VTv2 zGbs1cjN$^cLOk7Byg;p-d4c-i@*=Lkwy?|5)^eIQ(NyjKQf)hKzCKTr^9oI5G}m69 zQ_hh4#L62Q*!<S0PuX(s@@b}s&9H^C8CL#_&G4*v>{ZzeKVTmx6?kgd=NGG#A!G+t zi$}ymQ%ZQL)t?RW2UYvKIwwu3O_!W>7h3Qz0o$d|(iiCK;;Z}-dFt6pFEiDRbb7`u z@^3^}wtFuw6$9s;Q*tFIAVc^?oCfA6`E2~!61X?1(<M5j60<=i3Fd2@GHNd+nZr%+ zB>NQ7Xo*ivik;y4m8PnWDJ9=kiF&@&VO~#HhuQiL!<*yWEBsL9mRyplHd#tUets|~ z2pw;G7*AONmBXV&JlR~R%vzbvmCe_OyYoqMMc%I*FOy6C8m7chAZuU+Sp=5eGW!Q{ z{l+m=9$K0ZCx0jomq|XH4aWSE<TLOW<t0SoWs4kAwfzS7$VCmC;{9MeTpc2dcl&V< z-NH+VEhSM*nMFBS;mK5seN<4KV>#(@I8L0)nJxD3Eq1C7aN`wMOV4X>0Ax+!SMiG+ zey}K-J9=fj7sXyqd#2?-`M8+G?oN_IJk{u@#XWkp`ExCv)8b_<{;5S@gW6piEk<i` zm=;sDI7f^3Yw>X{uF>LlEuPTgFIsdls>5%r#b7P=(c(BQ&d}mKEk3EmceMDa79Cpr zRg2y(+VpENT8qhAoT|k+T6|E8k85$g7Po8hkQTqx;xAg%xvJx7p~XloCTnrB7UyX3 zAuX=a;ubCL(c&js{8Ed*XmN<P{LI>Nm9*yXJ=F16XmN`co$E?RH}&_1T3oC3ze$Vx zv{<3V@3eSXi`TX29NvA}`th(9r)$yq`_t~~_}uGF$6~F$v%7n=>2WrDYrh}T+6QUP zVOosS;vg-K(&9R;`-io7NsC*x-#^vjWi8&);tZ|3aaz2lMHlUN=X5pJn%C89KTJ!P z=~|ql#fP=nPm9jsq#o5#z(|suvGNPGSvpZKI(u;bt~Eb>SnJODyA~@xQrkcHH@_D) zmzD9>>+Rn*?yUD|TJK}7_vxNz%P&apo?*@CkscD6o*5RJ5jHg@d}?N9Mvsi>=!mqm zNNY@FOn8LV8W9#AF)=%9YF=92{E6wgdDe;fdFd197Gy>T=jA&mY_>tKOt(dA(UQb# z!}knpetKTk?1HS^oQ|E$S^4G+>+C#hdRl=sqo>)HZ#5@m<<HJen}6DMRmk|{GcQ;? z$KG3Yz%$i3TxS%^LVPNGC1pH`d3m{cY39t_yjf`l1z9=M%msOASq0{-S+ldPv#dD< zX=0S-JZph1FUOi;PRlV{MK9*L)2%t?g6USXEhod8mpxzfnqQD-O`B!T$jUJ1<Q6z* zH7z?kH(i<6%vlA_S+yJ0`3z7eM_R@;lQ-Z0+7knH_QdaH_4<2}8R~n7_N!<ge#ha7 z1fMu7H$xq$uZubz<a{Zgujns^Z}r=MLHlmBAEu?7bGpT`Qhdx>`@j2L(tdY#m#U3- zmDavYiz~F~Y`;lsPEVVimY!8G-<&(gnwOcKJ69d8bV3_W1FiY5>6e!_cgh@VdhIN$ zUAn6CZPxm0LFBOexAP}8RM(fw+VsR}ai_><sPiG2luu1`R!pl!U%#8xk_pny>P?sa zBua&Kar(Mw((l!aT3)YSL~i4vb+Umz4YE!rl^Jvf*-a<QH(Za%Mme+_Kj`=;ca=R{ z+&yHut?cgaD_c#nyKD&L$5zcJc*J_puN)B2Mc-9+)fwG7#YTzYnGCYt)m`T$`^d4f z!C;d8bj=t?jLbN6I=x<QrPIqEqS+`jRzF>17nv>@8Qr?-Janz(-m<40ChKK}r#I?l zgJ|1Q>BdDi>il#qbTK-0$Sq}y?5daL@v_d<!)>apb90kr*CbtYrbL#byqOuD(L)Y$ zlQRvn3zMO1sx#<)4PJb8VG1XU!cY2^x>o#))5)$TS?8grr*6h@IbPRBo})MD++-L1 zX`PPo%dTRmI+KgLPVOL^f_0vLh7g&~`QOpaQ)gxdWqmZAFiHAYlTH`sGD0tV$*yAL zdR<swFS(6X;x`Yym-wgiG@5koBB_m`M$(o}eZ9g1CEeZng-l(lxw|aMK?c3iC{J>k zq?be~DtW3wblJi%Oy}KZh}=Y<Y0&FTq?SQ$sx!(@>6$h4lG~e_dIal3NMN0=f$XpA z=fbS(JY^G0(nAhs3UoRnN#0!cR$_+HO+TOMFfhKy8c4o++0(ZT&95=bF7(ls`R*=v z)Ag7AnTm7r6Gq8J?o73sdM4e_QE!l6(Mg8(ax>CG(gny7E(1J54GZK*pH8H-yFQfZ z5JPvD!}LK$nc{Vxx(GLSgHE2JXEIn8I(en6H~F(<^>RB`y>Wnxmq=`;Z?s7yXA^l8 z>j^#Rel)o7x4CFfpEKx&-es$Z2H7aN$vR1_O!5lGX^_qC9bI&+S#B;meK3oCAM-BD z2gPj3BO2>mMdIJ%LMxWOEL+5=WQxwR#zm0oV!ToV7az$}ua}HHT{)!dJL)94Zv)mA zofq>=Ut)X)DOlG;(!2TUTw58av?t0CW|F<-#zxska+Mn><1rfh1j?(tCAqgjGTzc% z5u;>@xls~rmWf@2+$y#3vLwxxT&0=~(BcsyHau4y3s5$+RX03V@&LA1X2jS<-_xyg zfNZY2>4{B6BnC4ryZ@t&k0}uwpWdB~uP6O_(q~5o|CbGrgk=j>Hb}7%){aGNi0lqX zJ(E0~jnUIIm5tHkKiCx65DhJqP0_g_y2;%bp6y?5h9T-k=x)%rS2sWxZSylmv-v$; zXXD!!C&{}5C4DKImE@8o7M;6UH;je(*G-2VpSJPn<RH0!&BoKc&Zg6ujqXR0aBMP? zudlDfkdzJMwoM}5DCtt}uravGz3XfUq?)=Z80u~aEC^*uc(Xb5S5|V7{07pbcB3Gz zS&_wpkablo^S^8wrn(!(Z?e1Lf7vj!%|g#cA=hpcYz~Iu?9xdAu@M;cE+*5z+6*$} zRj$fLV5r>)*aTc9vHGuPAHinaL~bqb=I~LWYwN0OWlWVji#-SjEasL3kp1M5vfZ>+ zZtvDfYA^P`x-M*kV{|T(hp~%JA6FJ9vB|WDNhBkm*18^I3upCpWvA|9@Z!kgTA=K4 ztzy+>pQ<-9bn#(#O&f#JXpkG2^*Sj+I`6_>)>9di6x^`Yl1z4aDSqtKr7p6!%P`5+ z(<n7_X(kOz4Kf53c(C&pdloH9V#5%7%W$voNsZkbOL0=1xv8$HG+G|W?ok|;3@mAJ ze2Hfl$CfC!5Q)GPxw0#2DmNuHZfBC*Wj_}esgIN(#><#l3vd5~%yXWx)SMA>mRNhT z@|KT2PW01U>ur;}B(>*z{r1~v&)}tykdV-ju#oVOh>#v3ks(nb(IGLRA)%q6VWHun z5urUoBSWJ?qeElDLc&7B!otGCBEouvMTSL%MTf<NhlGcQhlPiSM}+qXj|`6rj}DKC z2#E-d2#W}hh=}MB5g8E`5gifJBcw-YkFXx$JtBJa=n>f?sz-E>n8=XG(8#dJ@W_bB z9+8oeQIXM+F;O8=p;2K`;ZYG$J)$C`qN1XsVxmK$L!-l@!=odjdqhV@M@2_R$HXv+ zF^oQjL36D@N3;s%YN1DDRCG+*)btE%W^HqPzy1k{1G<{sySjJm=uYY4+dlQrOIKy9 z=WQWctcX#Y%?r%2X7d8~h3175CMm1a|AT*Sn}h$VftB-LeDw7`2Jxvgo(aLh!IP$1 zr)A}sd-XP3b27{w#g|U%MNWQpR=T*lNlm4f*oNx*D6Un+$D%bm2bZihlhLSrCTh*j z^?vI70;@TTOQv~daTUj9k~z(sK0PhZJT;dqDJ~6rn#JW;J{{#}WmwJTj-r=*CMGx2 zoGPu-hUcLTe}mR6wh8e$sx>>WOHOLdqQt8B)Sn(Prd(Tr7`QS4G>OstWjrB`)cJ0# z4CSxG`O8+!W!uErTvA%|a>SL^JgxuMTL06SARdDGOZzAOru~zD)4qU<mX3EAj%eSI z_J5hKde_^&e(HQVw;Sj6c3xV}G^?^IXbVeAB(*)4%Vx1!SVd}OTXUurOgA$!J{E0w z&gDN&Yi^_dEUEqH7o_E}kd@Uy{b7~XotS3vso$SEHnk6>?|gGwf!REjMaTN8G${R@ z(1zn&PCshRUdpeMl+M*Sm+`4Xm8^u=SX<8AytLUJJEfYr%1<+=4#~~2rkdxZW!tPu zAI|kw>s{*7Kus^5)}oA9iE^C9E}X+|e1kps7JG3X`|ut1;{p!A0a0k@B1BQ4@9_~t zzP`x+y@ZeP13tlzID(&00ZvDyqxc!e@C%OP3O<D>yZsq{!{_)NCvXiX@dr-fI!@yT zzJz!x<tyC8*SH070ORU`lWCqc0Vh_H5iZ~`D4D>yG6z0M61QSS4FICfP;dA^RHNDe z4bcdG@P{b9%L$7VfTn1M<`6Xsh;_0RTEmP$v_V@0p&i<z13H3&1rmi6IOt1KS9C)# zxK5KoAW3mj7*RZs6M-HOj~PcH20T(C9<$*g6-nw19y5}7gikqeaMdC8gD6;=fJ6+y zKv*ycgOLnT-Qo}o#V`!V2#mxijK&y@#W>uB@tA;#m;|nUr71{78m1y08L%P~(=Z)b zn1Pwd#w_F@7qf9U@{o@L*f0lkF%Kd$Vm|R6EWo{3i2G0k@tE*pVlkFrDelK8+CM;i z5G5Ex^D^RCqMi5<9>!N#j=N~Tf;fTr2=P%ohE$p#C#DmhBy##MJw=>Oe46+So<}at zD~WkTk^f~Qt|G3+OL!S;@Cw$Vkl$Y=uET36rulW^8+a2B(!7zl37aAE;@%?KiEk6% z!MoUk_wXp~w-UEu2R^_a?89Cy?k66^0WBUP9>zyn{FwL&j-UcZaSX@tDL%vJ_yQ+z z5~pw)U*c<=K_$-O9KOM~IFIje0S;Wm_o%`p{D2?v6Mn`O{G!EQiNE1@Jb|m&iEH=+ z*Kq@X;wEmP8WL&3HMT@PmBhKYWPlMo&?mXV1o0LocW@CRd4el*$s0cKMFTWMBly7| zjUkH52cRjMp*dQhC0e01%y@z6XhUp^Ahbh!bO8AQQYUmq7j#88h^OkiBLtxcLpWk# z?aF?D*bBY!EW__Z%%puhu^;-QkoF105+vdw48TBGkc2@PjAW!>2!>*|p6wEO$j2Pa z#Ufn6{dgS@;tiDGO)SGkJcPq|7$0LfKEWf{j7RYn9>d!ZRTaMj@qVFq@g%n3DZB?! zN5ED*1M#Hsvk)cppM$7c@_FpQN_>D9@F8BrPOL&1R-+uE`hndL&!6nUE7*&**oRlK zAM0=cui+rp;}AALR9t-oU*H5z;uKEfOMHc|A!@6x#95rfH~1Fk@f|Kel(WBx?@@(I z_yIrSC;SRA|5tGhf8ZBf#|_-X0^GvAsK!D_oB$7lxSKp2;%S}{5Vy%jLY!}pg1AjN z8sawM7>L_^V<B!Ejf1!ybr-~Kr|}TC(?s<h;<n#JcwrK}F&RFX0$+rn0YcFbVQ7SK z_#p!R=z+$FL=!|I0MQVIL1WMiv1pE-5VdXhLQ9Y{CbfcinO1A`g&A=OL_FG{AKIcn zf{=iANJM)KKnDy&M_AAaN$8A0=z_uMiez*{3W6~N-64v-rXmefk&X;lk%?)Tjx5Z; zOk`sga*&JJxEtcBuY451hB=svd6<uTumJaBA@0K>6ru>rV8=r!#lu*R6?g=X;xRmq zC-5Ym!qa#L&*C{ekCk`<FJcu|<0ZU|HFyPU@haBgHLS-5ypA{UCN^RdHsdY4jd$=a zw%|Q%#Wrlm``CdG@F8}h3`bCoUD%C1*o%GGj{`V}L-+`X@i9I@1&-nvj^k5&hR^W@ zPT(X?;WWO)SNIxdP>HiRhi~vL&f_~=fCCrtJ*sdCKj26Fgv<CDzu*df#c%i>S8)x0 z;5u&LPu#>UR72u=ihOHH2R#fh!Ue7{!42;4069&P7rfyEUo=2Nh^lq`;E%>=0#WBq zQ#6C9%Ao~Xq7_=hj6jH5d_?sxL1>5e=zxysgwE)KuIPqfbVn%C5QeD`)%1v}gd&iE z9<U-3nTWzPh_Wc+W#uu*LM&#WCuX7-ve6r}&<8o_i(JG(yds+{R_Si^Lmv7g9|;iG zs)?{+0OnvI=E8z`NWy#!!aW#_1xUudNWnr3!F?EtMHq%c3`Y@0U@=Cb7^AQRqp=iY za6h>JNCL9W5oMw%<)tIOOw<$C5Dmmvh(_XCq6_g=qAPJ7(L{WW=tf*mbSG{gdJtbH zdJ^9tdJ*3wdJ{JieTdxmlzfSsi4BNv5gQWUCN?6zL-ZrQOY|p-l4*^J?-832MQO?a z;x=Ma;&x&);`_wr#2v&I#1Duqi60VM5qA<>6S*HNnTh4ZK;kZ98{%$aTjCyK5OFWD z9dSRg1My>GH{z;BEcXqhAMtMDOT;|l%fx)*8e#$Q6{3x}mN<v_Dse7x9dRD<HR62Y zdg49A4a5b+*NOKM-ykj|zDc}~xRJPsxQSRu+)OMYinn1eCcaH9CcZ;lLVTCFl(>a> zKk+@{1H`Sw2Z`H=B}DO3yk*4qiFV=+;zPs_h^532i4PNZ5|<OjySP^n%ZZN=cM%^Y z?j}A)+(UevxR>|@aUbzX;(p>&!~?{qi3f?#5DyWbC4NMFj(C{(Jn>`VO5!KP7l=oQ zFA^(=tB6O5tBJ>mFA<LuUnYJ^Ttobf_zLlJ;#%St#8-(Yi0g<aiLVh)5!Vw>6E_gQ zB)(4kiueZcYvP;4GsKO=O5!HsS>k5mIpSNyZ-{Rbza_pyJWqU=_#JTz@dEKZqNtBx zEAb+68}WPMc48Irec~nJ4&o2Q4~Rb!KP3J{+)2DlyoR6g2Y$hIT)_?eia+riZlZ(& z0L!of6|BRL5RVccB_1O_Mm$b@ocJm63F2qOCyAdEpCW!ie42QI_zdwR@mb<2;&a5) z#OH}$5?2zxBECTUn)o8|3~?2)lDL|9miQ9!9Pwr1H^eo>Z;7uE&lA@Yzazd%yg*z> zbP!)7UL>w3eox#$tRlWnyhMD1_yh4x;*Z3Q#Gi<ph?H@VHWPm)zD4|n_%`th@g3r? z#CM6m5w{S3C%#9#O594kM%+gHgD74Eex3LM@h0&@;w|D%Vl`1}#r_pK=wW~nE^vhj zZg7VOJmCdz_`nwp&=8H_2Y)n169k|snxQ#bpe0(NHOvS^8?;3b+Mzu<pd&h=GrFKF zx*-_d5rR;JAsi9tfk;Fl8Zn4PPxL}>^g&<5As+qE9|=gr01Si$Nf?B|NJa{VU?_%R zI7VP3MnOERIR;}f4tHTZCSW2aVKSy56=|4?bY#GaOiaUcWMKwoA{(=igIvtU-N-{e z3Sh$=%*8y+$30kpd$ADrVG#;ZgvBVv5-i32cmNNg1j}H@Lny_=SdJBV1drk|JdP*u z0?x2{ts<_*OL!S;@Cw%ARjk8nSdR^O9dF=GY{VvP##?wB@8Df*!F$+>ZP<?Yu>&9A zL+nHu%CQT(u?Ksx5BqTd2XP1=;V?eNCpdx%9K|sl$EWxVpW_Rhz)76KX?%&V@HNh$ z5@&G^-{4!E$9K2@2QK1!RN)eSz>oL|m+>=x!4>?9-|#!G;u`+Ib=<(8xQSb+hDqG; zfE#4ELkAD&;Ryr0V1zeZz@vbYD+1t)rf7g>Xo%)$gck5aOZcNB%;<zbbVeI=L0fc1 z5W1lqg3%t`(E-s2K@36>i!k&=IC>!hz0m`G5Q)Bsf_UaQ4oQf|AoRmv^hYuhkb*>r zm#qxP1dPNajK&m<#dM@13u%~vshEj$WFrH!U_}lxk&9_~5QQi~5td;w>?p=VSb|b4 z#lyHC%kco7#tJ-xNAN5j#dCNJ&*O2d#1nV{PvS*9g>_hk*RUGv@e(%RWxS3xcmuEC zO{~R6yo#;Zgl*W2?U;@CaW{70Z5%)z4k90iP=JqM!(r^k2~^-Dj^Y&N;56pKfl6G& zS$vOsP=y7!g!8zDdH4hKaUJ*K1{UH^TtWLlriuKa&cvB)3SEfjiDAScNX2ZV;ciSt z9@3GI3>3f$8!|Bm(=Zp)F%Maoj~TcJGqC{KxEHgq5IMLHxmbi^EWwr_(gp9~D7NAl zw&6Io<5Rqk&#(iZ;{$ww4{-uJaS~-Xg>sz6E_{jI_zHXQHTL2R_MsB{aTW(~4hQiK z4&hsTg!4Fz@9;4$;1f7-1Q#KZM;iheq0k`=dW6G(2pG`=E{KFHqF_Qa+z<nI#KHqT z;fY@GLT`AZ4}8!UzKBBu#G@hlp%MDS4+-!`A{t`=nqVLTU_nzPp&16DIR>KzlF<?= zXoVqYjiE4O7y>aIZ7>3DF%m%-g?1Q?_85Z>7>kY=hfcT)oiQF=FacdL5#2Be!I+Hh zn1WQKVJc>l&z(+ONFn145ow-AoQ^Eaz)WOg7KZbC4)JbcE|KD2(rn@gVjeLcHq60& z=u7)W#5iIhu?UM%j3ro#`|$uCL<yF`j)zc+hp`+h@CY8oV|W}-;7L4%r|}F#?T^pl zd91_>coD0x8ZY5xtidZ-i&wD@uVFnl;B~x#H?a|$uo-XRZM=hbu?6p8E4E=f-p3An zfDf?~Whlok?8YAK#XjuE0UX33e1yaJ7@yz>DsU9Xa2%iFGklIOZ~`ZB3a9ZUzQWfy zgG!vmIeddET*43d5x?LH;?}ZVpdb1p0f`uZfv_M6gD@D$NWl;c#V`!V2#mxijK&y@ z#W>uB@tA;#n1sogf>fkoD$<bwD>5++(~*T4n2Bu6LLrK<7{yqErMMpt;6apN8SHon zrFa<2u>z0aQ9Opn@dTd4Q+OKB;8{F}Hawx%7C~r-_UM3)=!DMbg0AR>V01?q!V!TU zh(r{k5rbItL@)G4AM`~W;?WQNk$^-Dz(81#gh3dLWTapShGH0oV+2NG6h>nV#$p`q z!gx%;L`=eDOhGErFcs;@fEAgThUv({49r9}W+4Z;n2&p~0QX`c=Cj|qkGKehD8gbC zV+oexemnp>9zrRs>`zt@AHkz|3{T=IJdJ1YES|&jScw<#B35BFUc$>*gIBN?uVNiu z!+K;go(;r_#Mg;w#5ai9#5ajEh?|Hn5H}MaAihOhNqn0)lKhT$i0@(x-osXG!*;xn z9ryqrVkgQ_j$PP|J$M{@v6$iRBR)*J?I#|<K^($I$l&+G#E<a_j-UcZaSX@tDL%vJ z_yQ+z5~pw)U*b8LYYF1-2&`tkLmTu%Tl7Z|63`BbXpaHtfPv@;3pybQoiPYqFc@8t zjBZFlFovKzh9U&R5Q^al!w7_9BqA^hJun)P7=tK`MKs1C26rJA;~^Pj?rF&qB}BQW zMfp&41|?FtrzK02@a3KsmA9fBD9Xw`Eee^VJ9t2Y`*||=17+^#$=nZ=xt}L<KTzg= zp3MC~nfrM%_XB0_=gHg;l)0ZrU8snHFQU-^F=&WbG(u1Kp%?to8;#KiP0$w<UFUwD z%>6)_`*||=17+^#$=nZ=xt}L<KTzg=p3MC~nfrM%_XB0_=gHg;l)0ZLb3c&URS<|I zw80>>#b5*>8SRjQ_85W=7>Z3efz3FHw{Qw?<22sEmv|Rnfp=7LuSn)zmdyPk@w!Wy zdqz}=i?i5{b9f)$U<ba%2RM%p@f~*J0?Ob(IWA%szQ=A<VGk~0FMhy2{D}Sd2?uZ) z2k|ow;TL>_D>#f_@iBhGC-@yla1|A}hNJic$8a6TaRalBvh)=0#?#2dGswrYD8O^D z;d#u#O3cLzn1>fJAFFT=R^wj0jD=W(`|t`DVJ!;rDvGcUi}4zYu^vmX0ZZ{Z?#CN= z0B_<!Y(xn*VHq~#A-s)Jyn~1FE|y~pR^UB6f~|NI+weHv#}n9rLQj?*icpTl*o9*3 zhIl2-9xTOP+>d>D0Q>PE4xj`Fu?&Y`$47Vwhf#`;@i0EYavZ@5RNxUD#iKZe$8a2v z<5N6=&+sHZ$5Z$MPvZoh!AU%eQ+N)i@jSl7N_>SE@HJk<8LUDjR^u#Q!a2N*Z?Fd6 z;uW07T6~9BaRKY#z-zdO_4pneP=(iV32)#Byon#N5kFxQE@Lx(##{IW!@O8$F&uYe z1oAKv`51))jD`(kFb88X7vnGwcVRxp;~q@F0!+lcn1qFxjQcPJi;#*!q@f5?u^8zP zFE!7=5?HYms{&Y$uo~rf3A^wzc4H0p;1%q}TI|EC*pGEMfY)#k>v0Gh@DX0eVZ4El z@g_dOMjXK=RA4iX;w>D*+c=JQ@G0KKXV`+z@gBawR-C{#oWypV!uvRl9rzL-;46HH zudx$n5SK%mAs%VyhpFg~bR-}HiLhb-GBFU-V8L`GAq#^r1A{RW$;d_uW?=|&Fci5M zhS?a7yD<WJ7>Rt0LIFm@hB26fv6zc-n1{PCALDTkCSU<3;$BR`LQKYen1V%Eh4--< zJMa=dz{~g$Yp@fqpbTqKj#se@>#!TIVGq`0FE(HwUdMjCfdhCG2eA={un8YwGY;b| ze2lm83EsgGyo(BK!BM=2W7vx05QYERKt@~W5ClEi!GQKKq61ve5w7S26FS2UUEq$c z@IW_sA{bui4sV3O2chsq7#biP4H1Dx=m9@O!XHs+jA%4L3<3~~rs#=g=!NF!jTY#G zmgtLCh(l|{!;F3iM1TCHJiIL@D?K;EI#G*LGSUjt>O604?yr{j7G><kM@L>Br^V7H z&VeU2N|hTnsWgAk+8lUqS95n`NNTwMs>+CKYf>XU*RG0eb)zy;x>eclrkvKl&^2(N z?0$A&kk@L<2JgT@-2(y#_iTE0{FPQ|6G9?Zr?*NAv|gUE+8Q@2Fmq_`+04ru)26N5 zv3k06BrR*&(X(07sk9kV<?5Mlo(r5+^=)8og=2N@jVoz)v-iFGvfMXMV!DbAW{!`k zOxG%@VH0y}PjhPPpSq@Y=^RqoC3o$ruDW&RZmn*ZgQZ)kkuOxQio7WYM#=7JQ9)jT z(Hp#1$1qK?S6T%Qj*D15vQ^sIQI}_=jb>U#uiTL~hUpk<J-T|VbSiKh({UHmG2zm; zXD3!T(k7XuA17ay7flgmCRQ~{bt!DI%C&K8vuR6EoSW2jRl~Tol?_8Sr20uW%#8!9 zD;rCOv?hhFXPXpyu5Pv}V0H7j9PW00dbY*d&sVpU&cwF~sSIo<T|C<^<mbTl%YM#l zFDZ|c8FWThSDne^rgJxX=)4WSJh0i&*r<gcPtG>cHT7y?Y-wsO2gx%GGj%z-jrz^H z9l9gBqq<K$KXdzB_l541TxmS3`%b=KaOi$CUp8FRT{qs4Jv;R5Gi2!U7hZUAf&H;3 zUwUi%{hM7}-6DJU8FQuLsKGBFGHT4&MeE<(_(6}#h7XiH{DQ&DyFtTFp%Jl(1Cj;} z8Jb~T_E72aE#<rS?EC2RiCf-l-on-7?%~%YGA4H2YbQ>+MXh*bovVA#KABm|A8n93 zW#^ASji0)0`>%gij~w;vbHUv^bR4~M)#{hmyt?j<?K}3kczF7^jP08^eC?};KVIe9 zv{{>WefplcaOtP&z55L2w(Z(?43Cbr3`!n0a`c#S<0no|O}A#w%%8V#(Xut`H*PAc zcynV;?*6a7er$4^1xCH0n?6%7cMmRH+)^Lv)4~ws*2>t$m|*bkT)5ta$KMPcO%WbL z^bwv#QErXhO#wX<WAy1Jw~)rhKz(y#^Y+n(!N%?ecUL#pICBSsr(2{x*4WI|;OROn zDT*iLT!T&SF75R#O}fI05xy=ax4>Xi=f=(2xA1G?HiVHScr|r(cd?i{xY<1V_37-= z)9CIpoCm{w^+u1oOuCj+TUku*g=;6bN%U}c@oE(7;_l+tqN|}v;kI5GBRwr{?gJ8= zTTCOpl3d-#n8vm7c6S-z-cmm>DN66nh+`ObSA%=u?@e8M>RXPMeZstopUbp)6z*A; zobI(G#J92g^5=|;2R^$k_Wpf~V_Z8MCc3nDAK>26*r;ezY~j8M#)z(i4Kc0_;*1`y zZqI+-$()wI`esqMkKEG5+h8hssKhYS=%sga^<AD?cvD~Wj!P4t20jDb3VId(?w)U& z?LXiiKTki;v2INZA1E5AU)s;df61`cE-r<ibrFvb&+evgX3!PIwQdk=l#41l7k=Kw zAQzTJl3106S34yec&56zK|*rx!d<;wWW#7<^9Ws$cUMD(=NR|GH=|p6bv3xT>bzYF zpDjMAZ@@F?g{NG&;Pde`L^JapO<fH-Z?_Ib<4i4iM!lz@6^p6x2BnyIHqz+gqH}dI zxw<uQZ{g9@v#FP_x2KQ6SKqK<Be%wK6GMR9RNu_4x!gk6y0KZ`Mc2(ESPn6S>cZsL zbn6WpOxJZcjJNdFZg0$+Z!di*Wb8QmLn~T*<?S;#`Np5Y-TO|QH09iq(uW^;^tH{~ z-`~6M;GwU-J<kh2luaTkwrB68L6ep|%#ZJEe}CVhPmX+hUaHw(dWtP(a)x!uBhRfo z_{kBk2AyJiCMJ!YFnLObwe*qK=wt7}GvA)S?A0JKDZ^U0<gFba?)>bF%Rd({wXa$G z;m*DLkDNMfdHREo_kD6CX~@vAcTJvB^6>J_Ti)AQzHk2*4H`F@F!A@Rx2g+g-Tn0$ z@76iFEm}@lc;A~Fzdn=l{*J~?TD2Z9aL7=x(cHJ_-MycF_T}ZDf6L2XUSMk<-2K&! z@9o@w<cl-UN^ws;9kRUjC)GoSj-TLa^6~A^{l}kja-(|p?Uy)mn(e^BilZk^Ubs~) znWwZ_e8#Xi!PMN~(xB*d@4^j6vujZceN&Tc=x&HGxbo1di)#b-VLlCANAn<S3wJlY zN$;wsVg<dY!Kn9ek-hzmLtLA?j^)|%0MB8D{(7FGHt-C)XRM)RyD8>bh8gV&4;UA3 z(l>Kid{cjyYh$+nH?hjiU_)u<a+hlt;{f-r2387vs7F^rGZzni;p_aA>{s}!sh8eI z--k!5yBHT&HwZ9wZ_rKO#yilb@FBzEXB&I?Klr$@J5R~$yaU_{KWtOrS@?OgB41<S zS+`$a&_}ryP4p|=YAXE7ub1B4CE7H=<mpo2(MmtwaF<(QaX<_A#%{@m!euTS)_68C zgswIeoodfhwZ_G7n>)MkL#d(gLw$3-kJsNGXJ_YSQK`z*Y^!>ZeQ=O^d~K&3UAfOe zj70tRJl-mG(Yg`Aq<wcrDO7!~^DpC_oyGxvCeJTw2lc_~_{1?>e7*z4{WwPzk0jpq zBw(yky$esbiDv|rC(T54FKUOhp(j4k+VIRu6L~>ev-3H&-n|2RQ?pF^bZdTEe!ewN zsZAy|+nQNmHs@tcn_iG=&P>bVku-NR57K28#G2jJ_Oa>^pVNjXipq%32CZ4V7fgJ1 zY0YkGqjdW~#n80DYQNVXo|LSnhjaM8TC;O_^@pz{Cy8KAGiTaz(v{gMn9iek>1jFQ zVNRZ>BZb;bv@K9a_<%M%PEV9iNCS0zoS7=0`p+M}NZTH$-+l}2dLuY^gf+9jH9Om! zmQ7_l8S{B;llpk(S~JXi>O2EWw}1Z;PpR-x|Nebv^?&(i-8*Nke&00zi=Xb$^?%c9 z1+OIF)iBkML-g;QWM_2M2HNDeKdTpT9@1Qk7kTBD|Mfqsn?ZbE>2b4q%fIoS=gsOB zaCY}bo$t<e|7$G2!!Vzv-JYg5t6Meu*Ur<;(SX`+PW^*^{@pO^|9<%n!#qMe=YB}p zUZ$v9g;EDpzID3T6FoRo-40r7%VQC%MjO6|C>3f^pVsXBiAvR4-7>7RcosL?+ASnF zG&n3$8OWRv=Os$HHk{7da9S~(P`<O1<;ml;Y_+zkSRRt4nT^Dj!&bxd*6e`I;<~4P z|Dp8XnEvaxkD&d)|6sVWqB5-5*v!1#SyOD(I7Ovhz0KyiY58XByxG=ts_ilt<eI0a z&9Rz8mFK^^DtlPvYrc6d(@LFOJP@8bOid?ICzbd-qct1UMroziEYePVc52Pe`k?C1 z$3*5s9Al>D=4Ok>&Y8-NowA~{LL;-n!?GeHvO>e6v+}d<vD%_-p^>)mFk57VjdnKC zE;BqVGcqD@3XjXzAMP@S>4p03cQQV)jO(`-&#h5B>M!lZvuk2r>UXa^&qn(RlNMy< z@OW}ohB<#e2hw>97f=nFEiYfxQkI=Hi)ZJR1#Hb&*f*BXg7oa%d|RG1e|pxe*tQFM zaiVMP)79MGJc}o%JE!O7<QJHGnmcywlrcZYZ0@2wl;7W$lb@E!)8F&NI%HOow*K^d z$_|}k-clQLx6GW5=5BpBjAiFr3-WB%1=OuzUKnd;frrei-=BCUL-dzQEf=w|O6_Z@ z`R4rTxwh;K^HdJ6vu5Y!=Vyu4b8c1vtwsF+YR3cAI@+vxsq|lexZ<9eSmqsgG`&|J zb8v8P^S$Q499woaE&0^%K8$u0`~6G%?tjx>oO|4<`@ysqbs^Lr{&?DnJcEC<IcHm^ zrKQhTHt7{3)%87?e#Ivf6qS|gw-?7kk2|!F{hRjUoJw>boSi*qR`5TYs;sHby$tu= zMyb;!(m{NtqB-if&!U}3C+Ej`e&&pG-crxg{^xoFbJ;;U&p>J?Q(C7@=P+#oxP`8q z-~G?yt5=^xz-To+hHB$E&q<^o-|y5u@DA-y(!FS3Td#>)&xtm;n!xTu`Rv@Hrd!Pq z+85{V(I4FYyR*GBuG*pgp03q@Fv6N|%VrC_z5at7b1Iu;-TDvyZhR}X<*01IbmSb5 zvwJqj+dlQ1oj;nT!b<Udf!M#++kw^HN0`Nt#GE>ku9C%G&YCe`RzZA5+H7`FsmgIB zBP%~WEsu&Ql|k2EFGn(gVt+f^mNQ=*_ZB41nq4qIN!^tuWKGK|7?ztqRI6KXh;^Ft z>v(Hkt};OFk8?eC#sOM74C7E@P0A@y(u2#5jG8G;oHsp<Ix@BCs=pnJfp<Xt_Oodx zu2Jf@&!b)7-?X>fp?yB>#PP2FaPFhsoreDo?c3j>`*&$~r}2v8UeX=9x6@7>_v(*V zv={r>IEYXE-&fL3od4HvznFFu8~RK8CA1UQj?VpM{r(=%ekUj&XFVs*<v0JZx?PNY zTa6vwQDgn>IE&*}5JRZn{u9=#DB3&MZ#q*x&gi`EsUNk@YWfq`pyK0<Y7^xZwJ*-- z9Ii7m`a6D{-8-Xm`kc+qo}A6j=p0^;;p+aaqZYerF;$BpTAZXs=kSuW<^(N{tM_}X z);vUuaatUu#SASvhv$q@TKmCT4A-KyUiZDV=HXgQ*P=6BJ+%RfW1;vsqcc4(@|vO_ zaUI4#tE+wC(cxzG+)g*Edvv~8{fHKqXi>ELJFn26a781Av%X_(_Yn-E0WXmMx%<s( zm(ZKly&&RwEsF2|&duPP)%Vf=iws9}`&w9SKasQ-zl$h-bAETm6I%PazttU|vpY4V z)V~v@+y6O#bJf1I;q0~Uzgd$$`n>C~s`PpBYscQ*+`jFreyP9Tb?}%jFygDY`QZ<7 z)>yzrN}esJAZr$9oT6&oSn~+>muBV4DWCIHzH?$o4ZPF2&d=#;PUDJS)YL1ogrrRq z)%CQOHvYdKo|Zw8E;3_8eZSVMY1HLAFWsut1e9NIxY8tbxC#GixJ(gQ5N4|PWggUw zCa!BZ5zolWoqhWN$7#b8=WXKi-=2@uRwkUDOHD>f22B3MsZ=~{otTq0Cu<rtB8$_Z zb=vr*sH2ls6N8ZcH}&Y6C#&=8yl!${zvdTYC|8{7tk%{`_wSagb3dYuzGgq?Y<B+c zjL!CEigAnY=DHuUm&AFk_?*zD)7hW9mQUtvR=eVv6|Fnxe$N@5zf(Y~_G_JQX6N|3 z@r_UY;aaAs>EzsBs*~BvmNPSlm9x)Z=JT)PRR$|L&wKv&^`K7O#`PCkaPUO_Pfbgo zNu}25YJJ-oQ>RV0X3rMuNNt9sngtn4R$-3050`B`!=ufo^SC;Xgb>%a_1lX&Z{ld5 zEVhA(V#Dp{99@d7z&elm2$hdI*MIF_)JY|V>zu3%F4#uSpEb~0pw`fpFPcW))|8Z! zRglFM@IBVCTBjqeX?f|>hou!vSK82dy*9&b);v!3vUyMaD3KDPzX6<3s;&Cxk~uQ3 zU@V0hY_qI+Y(dGnX`D!=<Yw5it@&dkBSiP{85wy=IqBK949@dz8xj+n%#iOez@K)0 z@K2UW{`^@}bHxnRZeNe2sq0yUmT=QZXBJD%Cr<n3JdfEFpo^muY1jc-iS<XDmFpZf z7czA!DJK(%Tm<rS)jWmKqXu+~7K3gxRULmDZ9M*3G`Cd8<Eu40#~=5g+P<){8do$? zBiY3NH&m_8d7w6ZW1Q2WH8<6oMV(G!U-q}t`+z!qm;KbZNt@ok>weSyYIhZl)VS*3 zb+4tz?1pOhg<AKfdeiw93oVrCRJTBpCpI^as~c)w%q0tCsx33qDo**Ot?BCW5P1RO z(?ktY{o#djAQt=if13X{R9qm``8fBA&RF52PQSQjs?%A`eo?G*;uCVO+MT%c5zWJV zwex7vz{k0sNLu?@*8KdmY1YC2S9|XQA6HTS|DR2INpG;dL5f8eC=#SC3lywew>NCH zv`d>p!Adr5woPc7EJ@m?fEp1nO3{djRr<y4g*LrMM6C!Cpg;wLsC+SKl>mwb5dsvy zMQHPXf6mM#o6}7}fBN$O`~SbwKKVTJoSAu^xt!TKbLPwt+&x12;ezd|<p`6me=#E+ zmtMQQK8c>ElAD+D15=&aC|++P{3*m#vT8{gb4vPMPF9p9OK9>H=9iOE#X=4b=q_Ov ztF)3qN5)iXMY3jjW!2(LQAQ&EfFxNWg;ts@DW<m|i+WRn0_XF$yIjWm9?ieHL`(SL zm|s=7n7%hvmP%Ul-Rq5HZCQ0qbtRcFClM?+Gi8i9_{GuWSV<WZfHjp>D|jcB;p-Ip z1(DR#t_CY(Dl5uYL>BVKsd@zurAs1}?Bqyn>LRivSyHxiNyfGE%F2`iP)WX$t~thn z<k&@HnSoqFN4S^)P-W_5c4ef+x=bk*rgtk!%9C!svaBM-Y+kCks(49~r*<dSf*TF~ z7&tAhF^&If9=a<kJMF4w>;GhRGPzhoE9NHQ{P}C?xN2!-O|d_WOVVTeT5BpRBjv?a zi<m>Mu3TERK(|r#iexR*r2dOXKli?@gaT1PE~sM8k%{(Y+R@U_5t*hP$xsqw{(Xfn zu68y{kFKJHUyO<xuF_z9#g!F{x+=@R3#*ngNl}8`OH(OEu3|gqz}dS}TWBgh`|fc` za*67awop~MNKL4cw~ke2WXuaUew-FnR`F9&VnkW`d4_CdmHK^$;f64GPWvL$e`wB5 z?nU}X&C}U_$A2rUO<w7MblHoRn(zuE7aUIo)4Q1tL+=cO_Rz}a@!L~YS>*5FIZtl~ zE0<UM!;g|>#TEX}vSrk@b`z~;pueJ$A|cCVRT_=3v8b=!+hXlG$#@Ux_fGF)#u3Gx ziB7AW?e9=(lPs&L?v`Mg$=?Y!y*it|dyk^u5sgcN{c99G5#+)CTCavpZ%xgpNfSn{ z)ap6Tcsu*;tj{TBZe(L_bsFJ<@?>$v(iBNA>i#^@Xw!Ai6Is*KV~CoUj59B8Q^hOF z8QXL|taMKl8~59ECt7=h^;u<4Fo|Dj%Fm4pZYI3SD;R#^!m@dl)kO?CROOymXX71f z)7Rd3ku34lZp<@Bk^gu#j{{(MxuA}&nnPr&-9ailZF+8;q;xg!l(jrm4vWew=kv7Y zKSvdl8h5w(t<WJdh0|yIuO0ZI=+xG|i1HSje9u9Lod0-Q%^R(<g)6ipE%ilDz$UUP zcXha<%@6u$^`ph9ly3Oq1vGVzvq@6T+;UtXXS%iJD>mIz6+Ih$F0aD;;Xp95mm4zr z-CwmwUh_m2CCw=|$%<uq1E&=InbVoG&4bc@YyT)4F<4(Jcj9K=r+&z2xykqOHr_%| z_01cWh`*<^|5K}<JNm6<Y}{<`f|QM?I*}V@;E;AAy(yj`Z2#Z}t6rmx|4|$72=ZM3 zACwP3uJK-f`%He)NfBN-mh!gC9T<Z}>I3+hTCkX@gC!<EZk<Cv>(8}%23o)CFL|aY z^q=-8cO69JE#~!DviywGy%}DiH`$xczp)nix@dO9AK9>jwRv-Oks0z_id~(@3Hbw- z$jN*!7_jG?*6+&uht}_^JzKqHmS3gc#quy~<DF>prTbd<?BR@ZzjAsQIPENq!v*at zs4(SsihuP`J?A4=oh|W-y)v%?uB*r7_k6j|PSWi$)jp@Y{@{1?2jbr^e;O&NN7-%d zaK8(W5?AlK=6M(JZ=M%tb~^4&firH1ZGm}}#{R7R#I#dqx~ZtH-|{Z|7gXwSC<f`g zfnC6)R7K^|>hcxc`-!Mc-}MvbUaY3$WfhSB?)%cRiV5*)ZrzM~44W}$x<4XcW#enk zTh84#^G+D`u~Wv3J@xDhV)Kf!6%s>|YIhf@fEFw%8Ozuwa%%GInKL4%E<1bXg%?In zEs31EAaeYCGZ&_znj6V5hNKs<Edae^Ql^|DG6Lu9z<T|To6br@A!h1qN9wYYIG#<l z8M)Bo=~ZqdF)zXIp-oS9B<IrmtIe<bJ-(WTuHWLIe5I9V%R1Ywm%6MMaXQ$-vRd<_ zLppn{UibTcIx@A-pj`7zvmwoLs{J^e!#zF;-^>bzh-^(?!_Y+oTxzt6aiiY?7`<o= zUs+a7|L;Eq@t*rCQ+`fw=XkQ1sXG=w=pN7n;$k|gipZ?x(_vBXrER>!_0wv8^Qx9B zHm^dN!syH`^JF%@`bxR4L+Y;&o%1QHW_gOzNR||kt(v5N!w2K5-&J3kt=#<qrz?2b z5c&^0?EF7;84Jm>uz}+ga)ak7xSPW-82u)4OH}K5o@3XNk&;So7CpP!2Lk^mUO&_o zl@(+B2^jN>TFnq3Nh|aFgBz+%JDO=T=Ehx#DkFF8<R@I$TzEFQ`p7w5&mK&6&F9I2 zaf&%%fr%Py?d;r)C3^0sMOV~}HHsrOMxUFH4eHT*MYWTl{9DNM`y7;iy9fC<kgJc# zu-E)nQkoa>=DKVmr$Q7~Udl*g63ckJ7HgOL9SG`aA+2n7J&#-sJ{bPj$c}*G6AGvL zPh!PO=)38~bj$N>>OX0!-y@7%;ZjpB?sv@fYwCMleAT%>Od|ZPHoVFt(`CEAh_3-- z<{d_=^K8#PZ{FB>`qj|5Cek_Adp1KHxyTqZf8zRqJR{~srjbYOd?B_a&yLJy3Wc#R z6N2UDhoF*I?L4uL)*Bkzi_e-fyRc~Dgh`(+oHb$6l=Bv{#D+Dj8lXmaeLlaEVRWYz zWBV5sX%$-$KLSN8@~NsR>bz#J%-69a!E3CymD9N-5o<z(aV*8n%AT>GC2x-|Gvz(h z=I6K8-^cpf0)GAes|k->ZsO6M`OcyB9@4+5&ef&GY&<vb#tCcwd;6gL*Nk6|Be9_T zJ>>em3CjPDT)*)_dA}agKNW)&ek={ECdm;g{A##2TXrVbZ>H5rO|<A~$&CA7vG%*) zp`gA}($(*SD?iwO$ms`!ZjSeOoda%le7u&b;N1gK)|(|e&pM)GG>akqUoaO&PkM{( zZ`d8Rrk+%0awlNl81T^hgWtePddZC6K81Jf++p+UW?tN3n@xYD0^^>AbkAm!pQZrT zx%3>i9BuBSSi4C-82=99X{=$Ub=+g8YK?x^b-ZQ)xUXT=3X^Zwzi+dC^+j^s@4h9> z+y_g<jH~BSmc`{=h4Q9Nr}H_CcBeTG+jh(&DYvkB$caq%Uun*^6NCP7D3Ow-{^O+X z^o7jdbv`4LnX@C&tBu`LeJjZ?mU*3n9eR%n{lXdB{8>@MMyeMu!Y0nmHhzJP-$Sna z;P&WJKjdy3FJj~A<%QqRA7|^u%3Xam+kP+WxWAh2uclOnGUdiFkNGGj2w7t8&(0o~ zIl?cv#^m<`o4#K!7fqW{G`o<lvFll35!1YSqr|*r89(Q?1R~QWpB<Uc<EUmfnZ4{< zA#|fn_v1F*;PcTlz07^u)BJk;Ju~lK_m07~zZ>M+ycoBT*J7`?TX|Evky~}0>rWI` zF7-K6j_JR2PsgR#{+f}u+w0Lcjo<v9^!TfBT7u_-Wo0}&`O~M%imS@}DP5)?S;wV= zd})iFKNa-%_1Bv6o?*=Lc;l*H-;(o#4aK~Mrw&cqf5XOe_xXp(n0^ON?wkVCtPQ`G zf#>hwd_ckHjDB|?o@M>qM!stgyqE5&eq8xCjW^}mZ2J$J46jFwbM3X^E4Cef)nLS8 z)cNi2dn6FA$;NAIF!90`UA;}LtUXstekW9{INvR(;0~vOR+^Ggv5@At&zpR!uaJAc ze0Q#bAiA52>69%m)uW~z&geGtuZ-<n(J{8aS7-GNH0ln~x6)oe!(O-D$O4ww@mQ@9 zv{`GPJ$3f8wDI*@B3E$oKl=v0^SnMD?K4mMGU=ZF1ygQryzbVAW|JRBl%U+L5B2BP zc-FS(rFHv2Nt#-$9=DF^ch>L5m67r?e_Fj}xt>i~3C>#ubw{1!djIL-&~(Ce#{T0? zc06^48ow5I#WV+|abjCGJLcN>?!IF}^<R2^ciQ$#O(&Z6?d}(cEuS!QegysVzGi*E z^{(!|jo5s<^b*&a^sM-h`?Stpzf`a1CTbw_&$g?>t?*e=oMLvirpl}pmn~oYqN%5Z zH7srWw||HB3Z4bks~$RskStl09LqZ!zGsV^cV47AS-#MD$3*<$Q{HvP&d*poe?-Tr z-|3*d9oa?D2Yr9dmo0j=$yih0DloAxBZ<G;>N^i!E~rQ60%$CBXkIdnKVM5>d>4$b zJ-K9mmiSc*#`5M$AH19wDd9zh?+L~)>}%F~2IVdMzMnw4LHSVZ)ieH~`m&Y1UsJX| zOa%nf%SBIi`zy;!iu^AVi!huoLd>9^;~*zZh)<X_t?;s}<q_h+VNh24l%Bq9^pj(8 zk)Pfn`QuDjKYq~8(~uwa{^|B)<{^4whqbdS-_FpWJrjDc^Qs>0^uPY(4G)j!MXIZ! zvXWX39@X>hbc$tW)hyjMXP+%k0oQ0Z=SLKVBF`+l(T7IyV+P}N=3#XI&mG#1eoZ^d zUhfsj<z4j@tnNd~@kX0YaGl{??hD;BLAlQHIyH;@5oDUPX|n6hro+p1Fwjza3d%?E zL#O+|_0#zZjC1{T#Ex@&x=zRKkAq#fy*HYEDQ)G6sOjh97M;AwUO)8y^ZN@XQS~)h zkvqh+z?5di$&rFeK6%r!l!$Mq8&~@t{xp2q=y$*0!S_7D_w19oZ}pryqq62geIFO^ zT--9*U)wUNb8QRX$!biLDPU#GR93dk>G}q6hFRE>A?XS~LF;UjC1|p;WQkb=H>Wx& zh6@wiS{9qmWR|i_^dFYuyq}q1zR;cGuV7K=$!@92wCXOtQ%DH^ql{ThiO*z}%7qLT zc`e8yl`d{ND@|r=p@~(fD^vYdCezHP9MiPO#PXAQ4PU60B{P@ST&mZo`k2E$(_YB5 z6$)qDwImm78OanD18WtD5-qH(oUSz_j2rwlBokReGNp3~i9$#+8B_IJKS>KnlGT&_ z6(rTfy@(|vtO;CPZB0=GJ+Dl#3r42%23smvEiz5NTxN~PoUSz@UGtt(Di>n^%!L>E z3qz(=#O=C}nY_)env=OA#I-4*yB{1jgu9PloZO)|&|IIxx0+MbT>bTcMN3mfHI;tz zRrjsKQ8h(4!GD462h(&h4{wa+GoR-cCs*)l$e-3Q(@$RNtEQdnw@t3u&VPQ?c)d$O z_49>QE6h4d^CgWJykCOre}n$4&-L~pf!FEG9CtnVF$r_%LhYN3oqB&M7ks=Vm;Qrg z+=i3Qa*J}=GZGAr<K1^7!TGj2!k<F;p!^HSPtGFOd41|XgW+%PL4LbUpD4ZyTXgl^ z^h?tZ2H)$?CY+v+gYsED$Te0w9tzr}Gyk&LtFr^M$#qtt%A==#yV=_7^5LFuh7d>2 z$sG<gV*d<evHC5MbI<Q?e$72+y8hS69Uf_-b;fh)x^}DiCOP+fe6;a;E?xcB$}xrE zJ7=E@=j?Il>~!xBTz$E92risU$F)Caugiy%yY!sgq07gwpXum$5!wg!`AtX1QP4-B zPe7lB=0XdhYoISfw?G@Ad!fgnXQ5Z2A*~%9pMn-ZS3@^KUx&T}ZGxJiC!syi%g_O+ z_p==x5oj!QK2!*mK{e1<p>IQLp>2>W2bXVG|E_)<y7u6%2kXJbcje&f#g$Xg@8Y}k zT>7paTsgaNE?sA@tA8hV==8aG4qf^#zSHBbyL`I(bLlyB={mp555qOzx$ArFxTD=7 z2aNjTqQMp;7EiS}!Qw27S6EzRak<6oEUvUzXP-~MWBnT}-evJ2i~nu$MT_rP?EMvE zSJ+~{#p5iFw)k<2lP$(A&a+ryahb*IEjC#Ep2fQ?-e+;Q#s9Y0X7Lq^uUp(T_drK0 zbj@W4I_kkMeC9w$-+2c*{&4w$jxhX7pFPkq{E7n|*Fkr{uUc@R<I&`SjwvMvIwpXB zt31$=mpah#08|TIwARp)bq6}0gM^|r8N8D@x#Ea_+=h9whp?IP{t<rtKbfAh>mSjj zu>VZFI}T&xa$7$Cs2%SY{`L%=?^i}o6ZRGx{_>1?J?VJx1B402-ThC*$xP#e*meIu zq2q9Mld1oo;XjDY@0aewA0SVeb^U(n{hwTSZRBui{1SVA{5|@R_o110ynmQr8ee;# zG=t&gzX7@qx)n-6lGQ`su)O>?W$;(Re<8!K>t7Anx&&FDPjz&B2HFCh0j+L5&{6g5 zfsTXVG;kew1K97q10Cl;W&a^^`ako|fsQ(8{eOs@{$b}F>^KMd3?%-;P!ei6`(VfP z3l4T%2F-%RFNBJq*UvxLF*$ay<5SQTkoY9D0&0LBfF6Z@XZaVvzd_!FgB=x64ODOW zuY>ELd!XYd9_%Q9&V|HJf-Z$BpxdS#>{tyoLE?W5Jr4Z=`tF4XJJQhIkoccMPeCt0 zH+<?~$4$`pAn_ZZd!Q$w@TU)UoDJOxiT^NE4o!ouhHkce0{k<SH~nBo2onESXe9h+ zpxb5~>^Lrdu;WZf{JCHpS_~D<KG^X!s0#WbR0i#W9)td1`9FbQnsu<Fn6xe>{$fac z6?h}G1{yN+V8<EIXh?iM^hu}!nhsqBU1|ACz#E_!la%?wgB{00324CY4|ZJm2g(?H z1X}(4!H!nw=oh;4v6smMblP7Ic8LGdD~6v2|1o5Xpdv{8QfLX(0Ih(&VELQD8=>#- zqduU&LE?X7`QzWFjcqsc32q$guw|K%_jujDw{hX>ziY;S4K`l%2PS^lV%*9j)_<Wt z>7cimtc2&ydgn(!nte&F;LEOx<;!@8Uv7ARd6toi?H+r!|Hi7i>kF&ymz7M{wbTmv z$1Yg7sEhO8nE5Nd>?{a-^xkgr<DMJhzVbr@Hra6Qd7!8$e%U2cW?WJ<eZrg>lcr9Y zRa7K><;8qB(D{83+x`8;#&ge)9oFyWQBEpl{^q0z6Es~qwNR(a=*!QZ$GVMjv;El8 zl<#3~lQpmNSYyU_Yh!B1pH+0$xG|{(W9(}EF-xkKE#Rw+`TkocVRh94pO>(TSA_F= zdRsbiwJDD!_W8uUC(R~5jxa&Fdr$g7-!I;E)-kuA{qmEO?)c1-vw!xZUwtf?o|~_+ z<{UDQ7P;TFzaW~)F10%*S-Z`Jb*^nlBy>5E@Y_P|f0=x1E?aK3_469RcWbSmX%XN3 zduqk4!&1xT#U8HJ>Mb#9y)qh(o?-tz`VaT6Gjlfs_SL+@loziweCOUX1m*61Lctmn zKK8h&FB{G42sr<t*IoQq;^1CclWxd0cBL&IqSD={hg|sg2v*y23Z}lw+Sh2Y$>L6n z%@!5!yfZE;6Y`43U$eh&O|G}^%ckYJYns1MpA=b}>oK<g*A-855>Z6sxHisS^8@@7 zf9mfv<=bwtc8IBmyk16bl8kwo#XNU#FE5VGw3!|?Dtu|i_1X+xk(7pWYGUGJkkWNd z@2Ya^a_jC!Rsg>Tl<cx6bG%BZ<Eb1k`Kui7+t8N(&hhSnMn03{l|%PIv}OO@T8oeg zp4;EZ@z+Stnmu`T-LO|4I{Q1bC*H90;*<8gwPTR%^?&EwFT~hX$$Vs*|I&MJk(@(a zi2XFj8>^iVkV{tsmFD|LEvZo0mfv6l=wzd!Q7_z@<K4vMr;+g?LD}MD`HC*vjJ%du zxn2WBF}6$}M~*rZ0sdT1^QU8@QGPagXOCawgt3!m6^@!aX@bJE1Y{Dnc9&hQaBbaW z)BL!512PHQ1F}iT_IH>0>3O{}ZJF+;M;pqpZ~8>(4R@FMar3*$;(pvncbOlqpqs4F z4;MwIIx=NY=!c6TYhI14%6?dC+mG>&DwG{+yQ()UtCEa++nd`ZTUJ?Kvqb&6?#~+4 zg`eK2tFOMA-8Ybz-f8n!RkL8!v<bdFscy2%eS2y%WrZeOT{oEt$GvFEtESxlZE3)t zkUjYIyT?7+d41I7CeD6jy)Gk6t;y?q2Xno<700xj(_y`)4s-ul*%^|xBFkuB<0LC( zkwXqYUN+oWl2Kva+z>Kbl)7(qFZ%vb<~OhoS%bpNulDWcFe@*8EdG>@<R|p6`QygL z|EH;i2J|{{KyIIjIf+m_bY$NXkIe0VdY@?TRIfyCTPV)^iJ^UO7@9jEG}!AE%}ps3 zYrVOjCvV90Lc@A{gC_Qi=B4^3`XE2P_cuq7ea<|jkyt0L27jIP>$>{6@G#bhGDsDF z`%rW79Z>!e9~yAOhjNF8hI#{|15*7H{hIr>^lr;-ce-~=_sD+U@QH(?;nb0dBbo=c z^l!^+&$OXE!v@V^@5kQ|utDef#<4>>pS1&PokJ|Y&J~t>*!o8d?>}dFZg_3bB9+D5 zjo35d@b=UZ#yQzjkDns*ohuV9P>4i~{+Ew8*FJQD@h=4BUbOzh*|YZp*b{E-vPWD# z{-8Z|EU;+7CY8q!Ew;eLK>0)cb5HD_+xv9CjST8@=AhiX`L2biOiD;EG&p<vSQ*fL zEB>fS-v517`gPn~EmqgXy3uf@pstO`qVMU$=zA}quh*tS^+iwS-Lg&p2za>@L0{kS z{!^&?>$25-A!)r|-Cs)>=fqu)KV}84?!%+GAFRHTQ_RIGQ2HtBIh6G+D(ld|-cttV z_N{XFVyJhHM=c~m!}~AA#utJ%a!;ShJv}S)o+iCBNpF^)UjN)F@Krv4WZx--t?Cx` z$i8!s-O^3gLwR6B)_VjSlB980w=@RlojEvn;QYL3-&CK3xx0yfCV7~DNFKtyZV2b* zg+A=jN1ZjyJFAqvZ`fGs6gKM_wJO&;wy~FY)Xv`Ck#`M<_D}Uo<Tdwc>D89g9_riM z>l^Kp>Yb2}vg@C_l)V1R&uchWX};<6y?c4RqrFnOiJZ2O)ip%A2K70Sc0JLxYvLz~ zf0yEn$Vf}=y>LseH{ysKkKQxx{OS*LsLE@(rg~lp_5$w)$%5}({mY}eHd6Cf*F<T; z$BfAQ{X>pF{%QI>V?W$qLAk$KzsGeyuI)z$XX!^N>k}#KiHDT+z}_d)zD{@Ti+pyr z73J&Pt=;n}zsqL{y!6(9%I9j3TgrEmyZkw{myE4=>IolnLNJ`-oCL6`(!_lSl<SRL zY3RDr4cgc$J8WIJ-z0Ba_j@o8f6%t@RrEPF?RP-AgVsM{K(90Tot&><K$Sy`_@N_n zy#W)u|Mryz^rY}dO;WzCvOA5QC?viRl)K#eL&NiK7@j*Qbd=Y1w}d!$AScB=KCt(h z)XgkcH=(2Z-Eeg7k^D|(-bIwj(0*qQ%{^jP|7gEdUZQWa`|S+b_HcBz_AsbVlD7U} zXImfGJBhBlI&}^2pQOD!cu0G(X?`eMnw&S;i{JQAmNA60<)SR{DC?z^^$V`7cW&=q z*1>XD>u)2Bb836-_-z$-mV4VTOo%$L`3Y10AuBr@R9lLHs+XV*=k9oa8%hY{oNTDU zAG9I%lnK%Hq~}E-=_&^0z6;8Rw3Uw-oHvC&^1960RomMoTZZ@b22UIm9he%B2#$ST z&9Flj?ZY1f<=lOk_@(F7VT<&Sf|rYe@`pzBzhOk~;84ETy#*a^%u&)rztF=NNH*)b z>nB#i%Z}Bc=B;*taT$Pf_KBA*Lg#c%)I|806M~Ue&#MKa&zLw@|HfRr4pbVCTmKNk z$*=H!S_Ok{C@38nIxcra=$xEj%cftkZF_i@HmW?;vG}N`ap*rPSJ|kp8nvLwI<3KI z#-Fx+S2l^~ji4D+e!?#>cGzS1pa0%m+Y9R2=btnF?V$W;wwb)256XW(sQYKMO>?qM zbHwq3ZaBVlsHbuG$vx!1@h<Xz+yL*m(q7*1wTF(y{~gy<PP(^R(CwVsP&@vDkdX+K zx5!@OZv!QZzij*|{Bg=mVa|Ke?3Fy~k0#8vmrQva__OgJO<0Aa?PncdsU6Jy0p-v` z+jqY|b%b$FTs{6n`i8%7?_;C%UGs_w{YCKo^tXNh{odW(_2=Oa>Th_Ba>NGdyZ5g~ z|AU~_-{Tl~E@{1Q{sa2!@yCn`ziy->-j0535dX~U=Hebu{t+4DHNOv(&cSB9<<Rvn zuV&~{+jtMZ`Ug4JHoP}Yx=nA;pP*MdT0yxpkty6cpyb~M<u+P>(3bcQd$y&PYtG4* zdi+6K>fSPXq9ETv`a0)=az6xR%TKNRXu`;yWaEs;GG<r#?2}DH`*=s5ZpXxJed0mC zE3c71>atPy)@c0WeJx$(mELFUY6F|B?h<&p&w;YB!G_`atlL;Z_SBGOPtSv^GxY1( zcs>3EDCg=c_73C0w~Y<g{moqa6R7%mbieWc29!TIR(n;tk34j&Htfe;`4%@4f7B$^ zski+AV**I&J_O1=WBnuSSZ#>cHC9tzQ=}6*Dp2+TzbpGy88)cw8}ZlsTDtO{_N~DV zFnZ9~61KmL8$p%PHqg&|56@t-D}N_;_0UiCBzJbki0ho}jN^}6fvcmo9C4l($>l6( ztdcz?AtU<>DDDl=w{u9J8;0ce4-NCo2vhbA{0U=-p}o9;v&@Xr;bftVa33_z2Mwn@ z_r=z6*rQyRgM*Z>dk&q)Lfq+;zi=Ou-%eo$qkPM&5c<tk+QLTCSDA{`{v~TcCMUo8 z4axQ+%L5yji(Cc9(KQ#0LC)1#nG4sz1)YH&4A)|1E}Yt2Q<n7FtgMYXmHiDG(-U?Z zD5vx8Wt+~i*YzCImD9QLet1xMkUpVoR?cLnP&$=A=@Ggy*Ef0?bL^rHdKz1)-lY9i z!Z;^xpXyCU<8)nirTcNla9?BN=ktusw<6OuXT!I8u%Y3nJ=@TeZU3lTd6CIG(dC@V zrxkxXke50(S(4rDpmGvB(qKfJFZDOqt_J1$4m9Piv(Hs;FM`rA*rqY!BZF@ENa+B7 z+&7|!{D<!&|Ngiy#~VJsr*Yr^-Pct9x;Ms?SLgIDvKD`%6{tj2?qR>x(U$W~8Mho| z%C_NXQ?_yBN~ImY$~+3nB?#9<T`7&5!{*w<pz67Fu!-|UQ1{KEA;v#vn2EasRGL2r z)g0o(O`UtBslKZZsC4-)UdnU*3+}mIeX92}>aT~n*#fRPCoYOVWd$z3@ehT(#4#rQ zhd{X&>mL!AqmaHD(ilOXHSqMVeqAz^vC3mLyi(Z;Dlboi1)!YMDPH*zI;U%*X2Qpu z5NsOBIr3l~b}CLDez{?w{JJhlaNa2Ye(bRGMh=~gF|Il1mpA^PjfrE8o+#LEb@hdp z^mu#iBv3Yt7}8t6e{Z_^6_wMi(y!k&TTargxc6r0mKD43*IHwAUFjtvoIU>$llHOj za#7INW#?ax^;|13?{M;((bvN}oG90v6E_}zn-w^n?Z=0_=HrZx+3<4Jpme&qsX2kU zsS$&F-!M40AMcZLm`O8stdt!?4jrG}9kAhE{B^#Tu6B_+5gSGso#(^L%>ey;XPLY9 z+y4V9=kDV(SJnkwb52|ozuJ<V%Xj*t+_zT8+g9g5WWG*2XYkD)`p&zhyN7pI|EOFx z%f>w!Hs|5rhrdCM6xV6<$4uEoPciMW0c^82Pqq)v8%{RYegG;jzXx@Ho^9iP2~;ZI z20ut%(+~dZ^SYZfol{=h@CWnSq|L5yv8b^(4YreC*<3irT)TU$x%L1k`CmZg^&%Vh zHc%lqgSsB<D<(hGeI8tX*H@IoEAF+Rs$o4C>?;ua=?a}w98m+|V@?RRoE9?AW1wuA z1<E~S{kks62s@@eDq~E&SGHv7Q^LEt%28ZC{zfH=Q(Dr~a0caObwuIi&H{DKjgfj9 zr<IdlPwm;=Yqth$SdG6f&~97ChrHNXMrYH<jSahy85{Ea_r&=*-JieOq+8EhRb9`% zPG{c3-SZvBzccJ-tux=v=klEO38SM5UTzgAo&LOa=@Fjh-9HlO+xSiO`?jH8j^DR2 ze>;cy+qJ>@+w0MJxcS?y0UPhePgm(XS2u}?Auk>?HoXSQy*a_!=#Sz0`eV4P?|ug7 zorql%GwpKq*;BuD?jyE7vyAs#*_IH-IgMG$@dxkC_-w}A7a2P*o@wl^0#&v@x!Cw8 z6GndK+_JuNP#^w;?CIfI*4eW!!yZ}o9{z+i(3NA;^EqDobLKtT&)U=ju$~B%{?M`g zZa6k~cxX_s%#z|hv(Np<1fKinK5E<DEr+(dI<7e<t{(qxD{!`iKNIp2mzuo4bh#-D z51C&U1A0y2oyc`+S;~L2bY+>-*c;H(jz1RA(?Xgtn{G8I_j#wMhu_S}yV0lL%voJ| z3;I>=D#3EXI;Y$DTKrS3z~wdiS(C2>7@24IZJ@4?L?(Bt4a1mnDet0wl{r>wmOU9` zN>}H5Gjz*}cKoG&YF+(W^A(I4Y}ymx<vt1e<?PRUf7riwF6iMmeDq`J{{Q&%&*Ac? zvhGKj6;ck)sS90=ztt3{Uymx!*p)_*C^s0k*tFRAV@u5Qi-%u*MuQFSAy@o5<l!R2 zKLDyd9h)%ENM8UI=VJ>@TKAQha61+n|0AH%c)HZIt5eI&Gt(4MA)W&Dtn@sncKa@v z2llfz6o88F?)Tt4-_*xx2U+Izly-`1&WWqVUvCAvu5{ZgLSBQ-=b7+w=YYP>q5YD) z!@aBf8#psw-X*<9=Xpa<KQekmYEWWevv~vP?txbWy7%GN{EwW|-TV^slz%e)+>b%I zxyX!ev*zoh!1Kb+C(x_sh521;zW&pG*B0Bb&AB6B3Vy*{t|`*4JTEtbwxtH^@GIw0 zP_bK<nf6r&s$GSxKMpEf1M%XOrhe+KGO~L>wV7k8O&Xu6F>!wc$~_G#?jJz4wKqXM z-wz@!$xjBQD+a1<F8~$uCeW{o0lkv+`w!;Wet#$V^85Yy-PawoA$LyPUi=9wP<l!$ zvV!p(c@@97*4TFyD8G5%SK{}3kN42;#eaovzu)Wa4S%qwd7uBi7F?6~zh(BnZQ}oL zZ9@I>N9lKJ=`)<W5T^0hyjxTUh;#i?+asngN`c~99yWbr6jbb%U8Zkr`-$l*qo9&# z`?2k>EjIkrgikex{C!hm_y-U!aZkwKSEY`4n!}9{UVU%){*ZU1(&4(W>1ReBvlxBQ z`0E}rc7;h-`=zwqXZ&fdYd@BD^vLe$PJ>MrQ-st0D^2&>_@r|z{<!rg9x(D|i*?v@ z9M{7({W|h}0{(WZ-y>X#K3x6v&q2BISBLy_tUq?GNvra6=K9^A5BdA+yaOuDv+B(C zpIjI6BKTK+Ddc~{ao+Vtej9iU^6!C4fA9^)_DjH#$ZrQF|MQI|56Lfw{BzN7t`GTp z*xUx{`s-gYWwPR{A^#hdRX3URFW>+UmEAR<mRdguD*R_}HugRKHItvRhLHcg&zEiq z`R9yX|4mZ|({43&JMLRXJ^@txcWw*$d-c5bosfS{z?t7=yh!@5fLcFOe!FP{HK4*D z_g|(B^t~hGztcMglspcqZF~;Y_~8yKANxI%&KYSF?(sDy{bRS7ws<M1w%7pb_k9bf z^nUhz(>8CvE98F%_wEnOI3fJQkiQSzIiT!Z0S*Ii0o69PfNE2_z@gxIKQh-Z2X%cp zsCajQDzS$^h5tRM@V#i;YRf}G+0oPbfL_n=Ti(MQ=xDAvCvF`6@m3(6O1J${=13s% z-vQ;;SpNvSe(adQ`Y}DX)=BR%j9q7CAG<bY=+?NX3IA?C^{)OR`Z(jT-9~ru&yDWe zK-tpznDOre<#%KEVS#7$UcbSP9>(sYx#paofBdTgHY9!#@>;M%I!d1~Ixk1&>-5)^ z_4C)24bQT!Oy#gu`v1viNcFAizn;#pQ)Tr>{Pk)aI9ViH+FOimv0pOZYwf!jnWUG4 z3iERt=6UPy{3ayI8fZJ8o$Xy9>rv<MTz-ptE_Y$GJvWE!++@C;n{<Bn{-3!VtvP%% z_`Kuhr9RQan6%fkl;gj6K7BN4I;ZE{$@rU1F?N<~_;piu2~d|BUSm$-1tYuo&nEEq zL2-|QD(7onG5&X7HGSIpzZ&@!#8tQ&P~n(s?p*J$wz5-s47`_eezgTz+m>Oo+QNSP zQ$abUt#lJ_8oNA;F_7~I{A-_m!`M&;D(s&?wXrv>-_0!#vGpgrCjS<@vdk@)BX>?* z4gTP>Z{1rVuMYjvbqc)PX`uY9yPv{4%j?|Rsu9EcYEAT;U9-IAIe5SH2kzm2)UWc> zb64bdT{i1^E`~q&jxGH^#?F|<w!fJ&Y5>D;8{4k}Rc>ieDc)=S58LY(5>~0yfO4M$ zmFL6Fbw>A)&mQJF|5I|6n|}A^Vw-a+w|e{uD^L!Vx5VE~Uc;d8f7g^z1XS!c<bIif zZIrFT)bBUtlm_J<2Nh=CJ0{MJpz3WOsP;a<rcK|~xlZ4GyEZg1j}iVo<r#SI8T7k8 zc06I7lfJ3=8?3<PFL}_|xEg%-J!Ai?2aNq;du;@$d}Vtd-ynN>cptwrL%%F(#-9r4 zFQ#OEZqq-#!`LznnXk|G-^m{O&ze7=`(M<5{-f!rj8%85vB5cw*LUHMSdlB^R9};y zhCT*8>yKD}6W>8K*gTKndkMMQ^xXvbJy2!QoSWmfx1WRZSNF>C+t6#E^!yJf`OTyy zmj>l$J>eYYVAkf?Ihb>wqs+3*y_X<&PFy+uxD|BfBagXzYx||}a*IJ<PnK^#)Xz3a zU!b2;*(uG|fX+7jjee?K{r>j{&{y{}Ivxe(PU&y_p}~1c-UU6F`G#Y}(0)^xd#non z7R#1c8@3!~?y;2X&WTIm4~`uhhBEg!#ON=Em%9?QW!>#PN2By*DeIPio>u%ZKc%jG zHxJA4ICtGo_gnCCcYxXSjC{Vko}f;}o=h0$)E5-uk6D4M-zXbaEB!dg8Sy^9eYnxR zA5^%I4a4tTx9=u-&tb+>QMEMl8`mhi%zKWgX8c^ew`ABNt6K3-4cJnbk6uXjOcRg0 z*!tbLI6p8hzV-$51=`yC`9}o8tDPvFp5(#wlqda`te0-^X;9;?@Qb-#6Da4(H~b-! zkEW3p@hg7{qjG2u_*GBpV-$KX>FPI1>nC@DQPP+T#vtcXR>pZhe!5kRv=z7AA91sX z_%Wu>SO&_bR#5JFP<ALy=~FsFp1%(H_EF_?D|rh(e+B(27o~nLVVx8A1b*2q=gK8= zoUx-WLO=KsW6KbDUC#$)Tek0JNB<E!0>3@)=a*fwqma1HDX-=DqgLSTN*!<Pih+`O zU;<lZ&t_0p9|m>pF`I{9+v~w~+;_{?EPcVA3_E1eAG6pIJ%N4{-O~GKYtvt?pE^3^ z8&}oQ_&<H{I!X}MIdLic!TT{W%9KSDn8F^}@+PRf@3(&P+`a#k4O#Ns642L*KV}VZ zWgq`2c|OtT*$>JcwEp0FmVqy!C-BYWIAqR=I~RX}6*wJ<f*j5SHagA#<<7N!*T)2( zz1yTK&}U@pD-^CLc`*L_`CT8A_h)ot<9JZ>H8o&^jK;Y<MNYBxWKq{7iGR$<<yYOM zxGssxM*WQV>7?iSm{s`G#0mB>ZC2J`%V{sDG`@4P=`$i+mn|27(xWt#zTyd~yUuU6 z)lOTKr@-@}>pS*y%{j%*+k5EmabmQ|R}^d`Pty4=C>xHl*FFSF-uWAq>iUhEOPZPQ z;xl<?M;&3DlN}BCgZD=`YV2q}6+6aSJ3zTBkjb7BP<DiJS(_i#SFzOFtPkBVi#5v^ z)eiKg)(`R~t~$ayxA92tEEe{R**(}B)jGs~uXR$EF|eCo$o3to%68w&<n2E+KK^&> zRo&=!cH}Evbu(CHj6Y$Dz4M-kUt|Pr(@j0pSqy`U9s9JAH+;&3YX?<N?Z|bXrNOpO z7};58=lFA`ubyk#dk3hn&z)!dm!EI^Cr&W;*Ec|2|LG*-e-f1cPt%M)2VH6#qe0m+ z0hG>Jpzg&*pxWGxpyKhYopql9)%U2sP~SZ~YpcAZXF6e=6IY1;kl($F>3b<N>6;2K zR|xw0?K4@nwOO*EQ8x6@=9~>J0UKKJ*ZEqy`mxw-#sXI7G4OI{f|)iPo8_L64f9^@ z*@m8M>q#z~RZiEFrgIwat;XMG1u9qB)pm)oGi@;jHd&jWnq%tu6;NT{1r<7<Fp578 zl-$ijo|q+X?%D~5xt8sFU)N7$o7?ou>#seRm#eeu3FDl`ODpj+{pLHD|CVybM+;0o zUMMkT@l4YAXDl}UhX|v5I-7cGN9`ZLru^5sY!Vm2UuR6}y7wz`yzmmE^9*>oNuX~- z#(ay)VJ`8rjaTXddg}29@0qr%cs~Fs-S2^N>#d*Poo@S{NLPz=Wvl_q<W=TMVSk2R z^#OUWANu<jt2VmYsu;^a(q9OwPS$~PjaJ^%oKhj_W}8D&8rO2oIdRwH4~{t!wd7}+ z(S7rBqw_XUwm6-^J(%`p(HXQM=y&&GxV_6J-HQeI>#UKkEk>^~cC=h=Fb$@$Q#Q?C zVeY+`L4|)ElsrWkg{}kTXMW+3HLyH~%wc|at()IfUEYfR;B$w&Z|=>oRaWo99}n0X ztE2orZ*&fUmpdDj4a{fEVLs!QZ1Wl6H_&^S`Hb;gcTQXke{kHA_#*Ar>iQ_W+-T6( z9k@SgrSHf?zja6l^fclR-XGyF(-+=o()}|i*KYm6XQfvIdV)F?d)S*@dDFcy3V%>% zJ35t*aD9#!vwRIGcMqt#f5^%y=dAmVsD5in`*7u)&ag#RHR3Oj5}a#~@vo47?2tXj zz{{Ng%J1Ge9+l;t<38!uJI5Z@%Z+@is~prXkHX*Rr{1MIyprG2uNqw|ZZf*-LFxXR z^$#G7Z%bhRl5)}umcR0JYldD~uo{0Vpf}w>-wP?-5>T$z`g?jW(k^|0=c}OK^^f`c zy7I0zP=LSI*V2{uwr?2y(OVe9S=|$n$vprnFReDr;of7^k@owItr|1zk!4Nz6QG=H z2hF$g{ERIslQHmeSAg=n-^8BQu#I~g8#K1MF3bA0e^BoF(s`umoW`3q_~YtSan9z3 z?-={M@0#*TfZ=Z$*|$Nt$+wwng`o2C$8Q_|q*bO*y^J)JUYSj6gx&uq_#J}oxA&CK z{6qJM$tH7cVbtHc@}c`+9R7A0hI9F-TWxHOgK6?0J2%~KWRHUKpUgFd9}mj!>SRim zF_&!3-*4+=Vh?rlzw#cR2YMQF|EuI`8+s;;QAf^=fZ2^dVyeBfZM1JS^&Q`C>OTxB zNXs_!To&G9unxc4SD5g^_-6CG=7G9@>NXjFGpOrP8@`43@}~)>c9kZ+*6Jjz-W1oH zY@56Azl;qvplnzPDhJJ0{<ig(e$S+_8w_*(m!Q)Ae%d@Uz646=q%}GIdZFo{uD=XQ zzGi)nzsBq*cbdW&w;{)0fAdLD;YvY0i*5$B9_d9;dAzML$DiXKM;_Hx%m%goXg;WT z!Fj-@ceqEgj2l{!J14FUf6NNpJ)7FW*oeANy7};O9|nCrhj|u`{vDm~^DI1<FwTif z;17-=yzleQ%j#R$Wb{{p(jPi9uWOHzX0vZ!a4fS`c4W!>(*fOkvgmHPi{~jwx~>Q1 zzH0rU;$ytx(*EAu+5z6o`hnh*RfD{98jtW!+j*pS((bT#Z0lfeXvlwxjCb2tuov&1 z=Q9sypX0Obr>1^w=ez&@eognMez)7u<(%epy!Q@$chdGVlm9rVwvo2}Fc|%*DWh^w zZmIPjbFaB?o(6R#|2`A<GEimuHYj({`a>$4>nI!h&2jME`?c7W?U_XOtmm3@;<n;% zumabX!>=>ee$DCz<$5ENExzuF=<Z?PwD+X@F#D#BI?!GJX#6o>L)Y(LteJ7({YGCd zyxb7b*FUIl*M7LocF#$*Z}%OdYu|^t=hmb1{qDIZ0(L%)KX}hIJ<R*Z2aH|y@N&0; zzMVt+)2{tlgFw6PrIiwK&8mjN{XBnn$ZjDhhd%7tcAcGf?L*&ndXBH!FZ&^UJ98x8 zh?Vs7jvPn6XAMY=;y#e}-#?M#z3_`1fA4YOQBUT01(5b4mp^3EiuY6dIi4{%@l@xR zK9%D=5B-F2!cY$DK;qajsFycrTz_xSEcP`T&ie9k_jtoYJH0--*1wn6e_S8!#hD5X z%kzen^!0`n_VI>|>+KC2qFp0ReL9Th=ngV&Z7%yf=kOg<M^{@Im#6E@8*t6J)yNXe zLAQmvWEb}9<>e8#J|n#$1A2K)$Z9iW<B+A1MKaPWL^hT5N;AUMAZs98JVSOHvRY&x z)b;Ug=Qnhqv2&QkT~_xq7GJQ~Zt*>f;a(=*@fOEhj9W}tTxRh`i?>=_Z}A?Btrp+3 z*oVDW<&LsA*5dgV=UTkdVy(s7EZ%MLX^VR;?z4DAA8WtG@fN3BoNsZN#d?dkTHI{$ z9*d7z++*=gi#dIbo{<*MvN+x16&9CSTxoH=#UEMRW${Uidn~?bafYqGh^<%8`d^`4 z$hBDfkwss~p90#Z+z^W!Z2TsRk63K6_>#qTi+{K1C&Hi4+xGD_i`p|i)2-qjl<Oa` z<2oyMVXwCKIR6p$`eRl;%KA^Y7_;~(i-i^&ZTPQQeAVJkd;MvP?G_JOywZl7YjMBD zKK8n^>nQ7Q49MfQTuLo2v-mZO6D_)Q=+TN7Cnv7ZLD<Ra>e-xtT%P3OrNw16Gb^mr z_h*EfP+snN#i=sR%hAy}V{|CanCcar;4`NDv@xfTiH<E%h!=A{nXKWM=LIA@HCaqQ z_k~>Hg;hyZT<7z%I9;2Qr@fo|lvmeOaW?a%o)=TRNlUA$k`*qxx5uT`Y0{_rTv}F= zoWyD6g_Rd^9&*{l6*Wok#6Gh8!pbU>lbQ3cOfIOIQtQ2yJH4{FWV$)UX9DkJ4%IQO zgiZJ+RV9mSk{2!I_y|I+_9m5AR@;-gy+8Gul`KydSDVZHCS=pa>Um7O1g#WGiH_n< z)s$6Mn8UrRyv@C5Cu<5z(NZ$Irg*{PPcAL4Dz2!ZKqhBcWCF0dUZJNKSKC9Ry-#F> z={#?o5?d*GMz{s?PpMc|R#jQCgi^eOqpfx1(WPETNM&2OWC@3jbFep(KiNF%XzxqC zq%?}1R<W?MYDuwdpSZ$U?|D}#-5#%29p)-k;H?c^SXRNQ<W<#v8dGYqe6=t0OWC_j zy0cn+y`M5ioyUu7N@s8a7BQztW?P9VFDlSmBk4tzB}?6T>eOPoOD5ZG{PkT@lReUW zDn!d6(-+ZVix)}DHGV5KZS?qFo$ai!(ltKsia!3C?3^uMU8xex$;E=%H6^ahM)`J{ zs~AShFUXK^BsvGZPhymCM98!~SNFvVl~EeAYjiw$H8%Y!q&q0vrAoz79w*cgdOodK z;b(*<FHMz~EudjrqrUIa1UT<JX%d}TRZ><_%pDu{ZTHWr&uCurB)ycXt6W~4A@)Mn zVtb3DX%lxyv48Ho>ie<Yv&)mo6l}tjku1Zv(9c<I8SStnX(RI{r1z|v@=3+1nx$1q z3e!KoB`n$W%F4w{Qx|eTm>T1hikhkwo_DmaTvAq5v$VK;PKD0z^1M$^nKfg|^fOL3 zy-$A5j2dSR^WO?TgL)~hS<1oolPjzJW8b~)p&2zZQ^^XMMksuy7Su>e;$^9%e_<nO z`L*o(Dr)H7d?n|4OOi_#q_F1*uc&BJZS93s#509lbp~Hg``4Gy^MyQJrfw^jclHUM zcfPJyCu{hwM%<FhWl5gKNvWthm0VD^uxvq5DK~0)vdZJjt)ij@rc*3Rx^9JUp|VJe z7TEH;WYVlTGYY3&H088$woi_EMb*jDqJ?GUHOVT{NqQ3{sVOQ>rHX1+q>{`66iKkO zI;lH>GuhCc?<vWJWs9hrqKe`wa#2)VwP=~gS@A_>6=gML#pPvJcV0?%^*pq8uV@kJ z+fXicW{|T?8W^<1`*;^$l=S9!Nty|>>K>-T6dGF>F7fZ9URatcDv~7&w5qPthh0Zu zv57kz&cr^?E2>H^Dyvq`Yy2*_h@YO~auq`nHwPMu@RrY~U#2~}Je#0?$fVL$X;mbb z7x_j|ud-nQJ*schxNhZ9S>Y$@A2;WD=Vs`)R~Fh<&9`es3#zCE-wJP$r^4W#<QFa9 zTj>8@X8zt*)r_fHT2WKBB&i?V)9ep!AcSf!^K!ReY*OIRw6e;~0D<@>$C&|wDU|YK z-tBr_uZx!^t5(F5RsN4h#RBeR?4<dQ=;~PtF?&L_?j&*EcXFq7HlW$lsU3z0CEkwQ zY1NaH^Or7Kl&p#~GT}$e`+M&>73Oy6`duX%CGY)Mza2eZ{xPZ9j;6BeLVn?C$1c^b zk<kBg|L-gT%@N45{0#HL-^-2ZdEB|GrPVb>sp5(<dIGu3Yew>q{S7|RzMno9zUj=6 zZ&y&iKhJ>)LOESGvmly%^_}rr&*^KQ8}pr0tmmJAUwk(5tA$T@?N8|J3Fc$$1rsh& zw@?4Pure0=Cp7(gqQt{|y94PQt#+`3N&j9OC}rk!iXrhjuPKj@RrBF>e$z$JIC!0* z^vFjT6~pUHrDG$!w}X#>Z=T?Jk&UD?%JV)2#o*)M<B-zS`9jN2B0Tas@G+<bUT6O- zFQ5-^L=OHKDuizaKYB8AgYY`DXA!guUT5bVF`Bh(oUNm?bS{U^g-?Jhp`Gvz;2BZg z0l>$=r=eDbKaKfQ=v8>{blyEf`{A3wml;*`+Dw^%{WyJ2d>B0OEXn~s3cmAk!|Owb z6r{MqHp>eOKVjqvu*LFi;H&2_3R3#u((`x+2ww+&>wLa-f=`1TkY5)Uc;4S9nK0gD z$^c3crV0GhRMwR!9q{z&jPv1R;8nB84}2ZCgAt>`G=V?4jQqnlg9GLfM&$s$4ZVll z`z-&jVBUzbO%zk+C9D_NLVm&Og{BPazz<(ZI>;lSSI&9_g#mAaWOEw)Sf#N!3f>6i zZKW>24Up2;`7Q%flp%7>&W(ab!$-l1kix{lCn1$>3z&Nq>*5t290j!~Ja`)v*@j;5 zzoBvPt>9}lv~PIf+e=A@c7815mY+eZkqcAHO?wbN53NTod~AiuyD)aO3G;Pu@)tSF zhcLqW8(EhRFYNnO)}X@+XEm7c!tvjrjJ7k62p)Y4qknke0Z4M;SHEe>P56;p4KIA> zTjYx{!i!cJUikIzm^L6h{<|i;u;z9n7d{0kFTxSuGj%8Q(k6`XsWqk@3XfW6Y!hC; z-h>gpey8DuPi{12A{=)YdBe85z)OE%!U#{g+uT!Uf!iUKzwpmLH0@Bh?MK`j#QiOJ z-cH&)yfA$)>A(x$fK>0o@%K?*@Uy{h>^5yrIQa=vCQYEuc2S!VUc-4VD@jLZzWf$S z!|NQFx1p`@-jkFwr1W(j%kQA@4#I$EKSkSu*O@JIpeTF-Tp^zHb$-fazhqura&Rrw z1m6OVd4@LKM4JRZ)Ji(=5%2<NGJFhNw1@8(;dOS$70*+i-zOdLK4?6AGZ=n>_oMK_ zsZc3=9Q?>$uH8kNU@<fvJ_Y_3DumaW7oU8YGKAN;7vF_e!s~pAbN)hE{Qw)lJD?c6 z&VBgGYm^VX&VhIhYL*<lsGab4lMc8Yioxs5gvK`rFCKgsx*J~SDilEX!s{%9g12Z_ zKZFObfnxAF@1PNy3a_&SzOj$~8D3`${0@2=Ugr$tz0G|DuYLc|eTQ|oKf-44YN!Oh z4t)0Sw%-FMzi0ZNIQS6Ms=fl${{7;GuR>1{SNr-OcaT0BUVG|)+zWZ*eoPqfTTmQ6 z4L%Pg;I*H9EAJk5!)u@W{M?YY7d`@h9`bf#5BOVX6n$VDcztj7>p-sk*B^rF;k6HX zs87gS4Ic(S1vSBIU-WCBd*QV=`X;CaUi+i}4*Da!_DJv7H{|Vy*WTsd>PMRQU?=z% zv=v_aeGfT;y)1s3>uLY*YA6a{2mTeB3a@>_wMTdYUbqdah1Xux+LJo(UfLn}7!-xq ze%2p7I^<1<*WT4PLZ$Ex;7ic;@a^D*`RqXqul=q!K=;CHPi&6Y@mk@vPj(#Ir!e3p z$B-|6YqXd2r#?iO`;dcILGAF`zqxTFHnYF8_H2FxO2BJx<uS*S4!rhSE`svzCmpZ` z3d7fdt3N`$s1E@zi%=H|1FnMV;nUzf#}S?|+Q;~%<3rva<l5Kx=o2Vs_z3u;QP|x~ z7;y24<Q-o74sU?!;I$X<cRw2Po`%=nz`ujq;kCc+s*hp+1IWQqr-Zz6c<sS^BXm8y z_ThaIS`Du~YPXz9JA9Bdg;DnHhmV1`K`Hn&_!d+T@0~^+L6L{3Bk+&VIC$;Z`lT~M z-Y$6U;rbl32cEIAH{>kJir>lzsC`w{j|m?h55J3az!N?Z@(SU#2kQ6F4S9FNYfsc( z=g}r52T!^n<P|&&4}LNh@+QM;Khn=mBrd%6CtVAr;G4k8PlmiF;8WlQQz={BkKkL= zL*A>%wI}G5+0@q~qyz4TM!~m%>k3VnCLtvI+d=J>IqXq%fq9<^d86UAhvvD^RCw)i z`4p6f*PfS0&m%AJ+HX?(Pv-5$esCT%3SN6r-UW?=*Zz?AUO~Nx2L~1r7hZc2o_8g2 ze@^<~m!K%T_APu1nhvkM3_k~z!fXG+b<p+j+VikqIrhM7AH><vz3|!_@c`5cuYC`- zC*nSM;TNIsW26uMrGmY99w+bMluGslhSz>{E0<#56NCYufePTY$6eoLgn`#Sc9Ws0 z@Y>66IW!ku``kSOmBP1xW0unoz-vFeYN%dez<Z!o@Xg>}s1aWK$Z0>hRllG-!7HJy z@Y<i~!#}1TpF|GUK?U%_J<w$MHgMEV>JMIfCT)YRh1b4GV}3%Kl7ltS-SFDCNqaxF zzzZ+Fhjsw3eVtmNyr+<Z=l+y*;kED6m!TNE_Jn!~ng_3anx@}N9^thI(^lwK_$F}B zee}cd+IwjU^aQ;2Z`uRxh1Z@>XZ?(OpoR3o%}@khdq(xSAKmcUPii?d7hZcg^=l>% zl7rflssUd3CbSh^`#D_#?S@Z)_d$PzZw60zkTU!w<p6#c8VRrcrg9&mPT|8~HB<<% z{i$AoQt;X<YQ-+vgyi6(P$Rtd(9(WdyWoY7Lv50S#gBx%_u#ev)*dMTSL7EQ^C<TM zy!QEd6>9x&(g#mr@11?{+Jooh$0^fi2m=;F<?t!+2}pgr_Q<*77sN%bJ#*GU`{0|v zX2>6>fzMc8d(^zpLYlup7pQ$}YT$)Gf*RnP!BM|5aid_vuWA3S)HV3%)AR}a{<eT; zw2~L(F|h7g@&Ye>`}ed%c<&GFMfDu*5IzE))<zjBJou9rhy$-ZU2b@dHVLmiUIx6* zJqxe>Up@;x4X-_5?uA}ec<|hI+rNQtzCm3)i!Sy$ISop~Yu}YCphoxvcs0}nul-PJ z-wJt=-@=1WLF3_Dz;_{)mA5bC{SsP<Tzih(^f&q_c<n**OK2y&_9glF|In`BwO7fl zP%FIlFZmU;S7E?`e<zQ>BOP!yl!n({9k)U|;nQF%)B>+PI`ZEoF1+^bD1^K{ga>bh z^5N6q-S1H*l)v`D(7qT?BNu9KjCOdT_NwrHpX;Oct|)~U-UQXbYafR-y>q-)c<t+O zENesFgV$aUH$%hNA42;;9Dqi{YcGf;&}4Y+6LB{*4_^B$l;`DmBcDSK-UUVBn?db| zpl2eX_D8rLxv&*V!+QgBy!fCTZ$G?n*AeiWXy-@fcn?5%YGdHn!$#f!mJMMoD{WqT z8>}44dcHR71b0EB;k6gR57`r-9$tGBJP)mg*FFI!9>sbcc<moB1M2lWdcmupFnk@j z^k~k~fNufcfc*IizEjtC{?{uExE5LsukY7GA0kb7eKUV2H1Gw=6MPjK39oPE$Bx7{ zczw5C2AvD9Z`U_Mg$fT2J(hBWkAbBh&hhIv1<v^hddaW8H@_;vw?8jp133IR?1a}h z=~qD4!|R*xccDMR>l^Shq4(hRU3dvJ@Q>&PH$Wrd^{w{FC-7YeyuRH|LX8Ro7V`b{ zd+_?6`c5eSPlN}joXDCY_&B%`(lfrkfBxi0DRare??S8L)8NaHKb8P5ILU-hfz6f| z9$R3-=o{xNpmyTwo9C}Xy<Q^k;3q$ZP4N0Y_;x4-uWxprh3eq--R{w+<an##^=<K$ zP!qhqA8vx0;f2pZEea17jAoCpKa+Q`2x^Acce<a4cEi_!kulgqI~2}?l;0Hi7^G*A z7VxC8#{LAj$MX8N_Wo1Rv6prN4vgk_F?fA{I}@4?p8`)jjWSo82Um{I@$N<51U~r* z?i1wtp7YC}LN9WC_xUn3@@3i;c>bqzym9dQE_5|C9bVsnz66yh40yzJ_KASkH>As; zTjBLx=^kh+yuLM^c@g&yyuLr(2JL~@ca`%mrX9b6o#0K-z`tMvc-Cy%D!jgZY=j!% z_5I+Pmrz!cgWrUPy^1~HZ=lieZQu!)(l5YA!2zFPk9qhoIH@Sd+Y28DuUJ66|CPD` zca>8I@cL%2?-Js|>pQv074$*yaqt&VtHOX&Q>MP+;2OwppJ4t~wvB=M{!MLC_#0^a zYuE;UxQa4_*Y|0^htiURAFpPeDZIX6tAlpI3#TnJ_huaYBBbAr2JivP>szzAwX}iP z$qV>j&}4Xh-*p?`Zmkv%J_+rF*Ee9tT|?W0*LPu;Lap!#@RVz<&0z88Oq)rAkAEH= z?ZgGosiR)t^_|w$(Dm^8X6mzFq)y@W4b}b7d+^QR*6WP@P2i>*Ef4nnGHJd+IfJJ` z<?#A0>CaFzyuMdDqMklVVZhU%HiZH8?U357aLQN6@0-*Scs*1C-vkc(DtRYN47>?a zUFf@?fh$R$F!~PYGN=tc0p>IyC%nGV`5rX=Ey@ji21>x|dzojyL3<Dn9(N1(IJ~~4 zseszx^)1T}Z>4?gBky2;z6U9V*Y_dU^R35jczr)|0D2W(-;m6OdcBPe;3{Y&yuQcy z<Z5)m>-&r^Ku^Q#n~fdNUU;pKU%rX{;%~^od!Raat+hW0t%BD&{0UGKyw>J_1KJI* zb@zj|U^Bed@-Kk)!)x9B!%+BtsB5tQR@xQ3*7s{|ehgmtZD=mM*6lwJrQr3=z{j@H zKf`OC|5Z>LUf&2j0PTd=_X9_4r`+K6O~DLkFTB1p_y*+dCr$84XeV`{weE{{aPPl^ zZQwo7dU$^gdJ{IoYfbt}DF5%2E!YexuJDSx_+AaU){Q>`t%TP)^4>qdHuxC$;yvsQ z0N)NC|5N&qcd;KF_%r%Fc&(}a6f_rJ>#e^DrQo&Z`ZrKLyw+oX_<quakAh2~sqc|@ za3!=0UTcldZ>BGT*V^OjpuO--;EV@o!v{zo+zZWx*P7q84|89@YYp(@&`Qa{F^|%I z;G^KCpVNL05(XUfICV`Q9tLBO%0ajo8b_Eq@JT2J-vZwFgpt>St037a{J!Py2BS}s z7s3lCLJBh-yutGI;DeUm1<riRq!|bILiNNI4s0=U;hB~n4^~;e2D}MU`YXY6eo0=4 zTLXR_l6)ta`)hu`kPif}fFw_WzlNSb{xleV+VH~ZmM;WfeujR7Fm1nKtuoXO&t#z2 z2igxG22Y06#|kG|J`UatsXu7|Pkfd#-$0pwX-L1V!k<7I%Qb^7kkS#J_*=$HjIW~L zVo357_#D)ZybYZBJHyAp`z_xL9=pfHjer+GiW>tTfHZa$c0jVt`#tRl(wITG8B+R9 z;Qv5T<lY}N?j{_(@RH|<OBi7lr1a~+pFv8e8GHfSPx?AvWykaUzCS{m;6CVXc<%)e zYE|CBJN{_K&uMV}pNOmR40zv5+{=#>2JG`^>K9&E0?Gaq_%P%@Z-T@28hHf#tmVG| zwm`f0P`2Q&UN&vM4V?Z8ZH#BPIC!t+o59h4G4d$5+VW{|;HyR+2A_d6f7J$-{nf}* zVAX3TeSNFY=XIVXXm9#<;cHMid;|DyJ8e#F=?$|Ez5rh9;kDMi4qo_6s8M15?T-AT zqhZ~ub?J4D>zdXzuWMPicU}9sy!FlN3+`;Zv+d6PcSbi%-H_N&yP<BwstxH4jT@Ra z?A*}2VfTiX4ec8WHpVv2-I&-|y0K-)-W@D$V=kR{6l?O=MAjtMrq<T2ZCKm3wtcO) zF1#+XZtA+Z>q^npv~Kshwsrg0Mc3D@Z&<%`{qFVNo#8tZcX~0}J@Mn~nl|s=ynl0K zOKMBqmX<ATTiUlownn$cw#K(MY)x-%+PZsd<Bs+nUOd;koAA-})O1U_E#02>)`ZuT zuBltIYE9Fc-D~Eeeec@*bx|}Y*42``RzGiZ*QeH}(OG(D-JPrMY$cER8%j6iZA@)U zZ``@DW#itBUSob^v~g-<qOrEIp|PoPcVk<lw<)@5{HEBZshbj;rf!aJp1V1*xpZ@C zbKU0p%?+E=n;SPbZQi-Ld2{RLw#|Dtw{P~g<ZTIWY2P|^+p2AO+sAKj+n%>0v4al? zyj!t(YC4flr+2T}zs6fTb!}p8EoD`YwfonG*A-y#_;oQ#D?xds*43{wH6-if>ucB7 zV|~l|_VxK#IJdF1v9)n;V*$3sH#Kb9yD5fMY4j&3v$>R4%htVH3$~5lHg((1ZM(O% zZVPXpyS;RK?e?ba?b{1>)bH56!%OkLROOCEJ1NQhHPJN<lwTf}nG#D-V$!;E{r>fN zcSi0Ue`hVNBfOztLv+L54f`p(2<;%XvEG(oeq#Y;mcJ>oselqoZK~Z=x2btk%clIz zwUkl&=DaPDEu~wUsoT9<ysdd#!&~#WMz$7E$8l^<Y%SfI+FHA{Zfon-uy6U?ZKd04 zx7BY;Z)@Dvw5^3)v~AnFt(|fWZ_nQz*&g4X*q++nyuF1|j*zGHj-6^Bb#@Qu^mtlG zZMr_amo}2OCbnkon%Xt(YgVmoTAQ~npK>;BGJk#P`bJuQ_|7<OBfMemhSY|7+Wb!1 z`rZxRM&+_#V|3$qTK&|GadO<Wv6=Rgzj^%TRn&hQIg636x-F}=G;P^UyWPJ<d74U& zYN?6Ft<B_!f^Oh904PXDu`Gsd@$_8ULuopNm37$JkX}VyG^U%VkLL7l>ZFzSE$jEw z#`9?9y2A>nr}5-w>Y6w?O3?CCYwG=$*v$RY(%BOCZ%$*4M=9&hY1<mzmY_USl%-lk vY<nv$A#X?6FSo`W7?#fU_kWLOSmv~P?7a~tNCX>qrsuAK{a^QgX$ky4I$H)T literal 0 HcmV?d00001 diff --git a/Lib/venv/scripts/nt/venvwlaunchert.exe b/Lib/venv/scripts/nt/venvwlaunchert.exe new file mode 100644 index 0000000000000000000000000000000000000000..74f40deb0467b055fe42c80480144f3ecd542da5 GIT binary patch literal 268800 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�+-9h<+(lSa4za@5;05&iooC-s{2ycZS;G5ht5}znrS6V?cLS== z*A!kC$}6+?fvlI~^~0hIFqUkp9UX^k4BFKVA=ubL`Ma`+r}iPuFCD}(V817NmE0v^ zWm<$(bM-eo{<VsAh55C8*Y05UgK(%&herS2E>C14?m$BIZ#9QQo;O0ZZ$%%$#0Hz0 zLA#UW?dQTWbE?h`Sjd!9s>+Cztn#FWsnw?NdIu19236sDPnqi*a9Xc&6>WyxFVoJ; z#$tY}ID);A)y?((*r8%$85d_(PsKixpIw{Z_yNpnb6rT?Y}y+#8ao4H-)}H})Y(w> zx5(!@J)<;U6gH!=t+&xWv%w#KW`wbN;)OT{V@0as9zC+Qu3<qB>v%g-Sg2gQy~J1z zg9u^1rYvCnIShAx910`@EaOJ2!>Z6ZajeIW`^K^F-}yakh#4)cpR#R!!5=^1i7dn| zq|tIff@pATfARd&5HU2O;};t@{8~;xiQxz)3?KVW>dd_TIEKZJ!Is#8vyB$Sbw#|9 z`UT$H)H`Si+i5v>Z{1ArA4etk^T^6CXx6aUq7g+#E1u_z{C^lGqh&0a2~_h|YP3w# zZ<x4vj|7d@H;Tg6-!URXgM~sFEkZE`a~N&3TtC9avz*cUW9hSuR$*=ZZ69N3Y}{$_ zG@fu4;o3kBj^gQSjaK1dlmEe2UVlQi&tf+-?S^r`2zk(?S)YC@Y#m5GD%((XBBC@1 z&ZklEP{Sg}{c$8uPqZdB{w&DqYflT8U<7ugK4+{x(O7-2C${IThS<Kd8p?J65V1WY z!(#Ln)57@1W@N&(J;oBbHxu%(9Y2TrOl7f;Mn&&s9R4^8GM)@mFBCX+g7l}sSk@&y z#A;-hzlD=Adegd-3d7dhjkwrJo<wSyXFAb$jHl?58;<`)*z=-4{RaI=1>25OZ68i` zqV<sG1VPo<=^3897e17n#mQz~+Kx+plZXHD;gp@i8STu0)KPxr;xENH<biPgdgFng z5*M;hSV}z%)-OYz9qG3q7z3$_wJO^v&7h#rG(bm!wHuo6#i_Vyp}lksjLbyP)L&(I zC!!8L^<<9pD>(8;iZPPWso`k9E}23yElqoar)qlSra{dfk?c}&VC(Nh6;L+N+Zk2o zmCL1Vqh}B}W>d&|!RgcirxO5~=4yiTCmk}I5G95f^8$eH{7i?QT^QO%s9r6EUqnDK zwoqP*nZU$I=ajjS7{rICYaT<nI|MN^UN{Gwj-Ls40skT967xJtexkfIFTIjZYb(x~ z)(qVq5s$LT6&H?ob3~Nq#gWz3taKKBotFL+9vrbwkn`XO+jMxB-d!OFw9+NKD&%=^ zlrF)Tzn6wwZ@-k$%i_DcpJEVew6_+B7qvjJMc&JLI!Ippsr}EGdaqM+B9^A}-6kU9 zyYq{#+eF#^+S6Hx@?0in4A&hdl!AHJ0<wOm{zf4%NUw06_$N*O-KA95$xd7ONRqm( z=R*k&-G+AF8m`A&?FO8;{!I)J45KM5T0y)w2J82V<5nD&3wx>@0*OKQe&;(sRh!yJ zw#)L#)>$04?$wT4Rm<IVK97D4z24K%(pz$cFxpl5i+B#4*bi_VF8ig!w^AyV$k%2l zHi>2PBF`P?nc^`fftok}OiVUm{it?kKdN7SsDAZi%0b}dbUY%`8#ojZ!}-~!;UlM@ z8WpFvZP^ydvWS05*gp;Vw2!@9Sr!elqID(l<2>dr%yD?3O_<}Du~}o}v8DyJStwhl z6%-`Bm%XLFk0fcG`@<Ow&zs-HZ?7UQ@AS0>@{0HhY#-HxCG6-I@>%!c$+t+ov?+cM zMwTLfmu+s>2GV0P2XUQH@ph5#U;|nw!6>n5e}(;c1Fn37p6D(%K63TfLa+ZLPh^d{ z|NI+i0LN<nry<%b&2KJkniwB<-^Gy%Y|f)nc_^6f3f1>Tlc@)S_7pDF^m5@@H5w<6 z5Nc$$xBmEGnp8ooHa*Zn6igD5;S$;Xs1*H9kQA6BL%$xQ!P#3TGaYW23^14N+%oAI zbQcU8mWbPAc=Esqiu)b-ggkP3iGa90dJdsNhn3;ui*(qz!q;=DO=Qr$&S?Y+L*Fno zzxv-X=~H|Wj<Q5*=S}-wn@>Q7;r6}4mq6AKKs|_GTPUE5A=cH`!>@f@9L;fF3w2Ls z>-{SG_;ym=?S(^nk7HZjC&JbZz{oA@2#nom{HPsvuB@@Fd+$VUx3@e=1K~-3ACM!B zbJ$eE479@-z->s=@u4O2%~dL_pw<Tfiu%q4P0Ybs5ofneCLn=1Va`^qE3rU=0`euG zlCmR|Z{P8Zz*=L;FDYGf&89V}%)e@wKQ<m@Wv*J!QSb?8xzV`4nfK(0Dqwr(q<W8k z6+2G07rP}X0xy+F%poWD1D>+`-OV#-?MY4QoLJ9jzw0rU7}RB2&1JF<zoeo+f)e^W zC%ffs3t0rb2lJsA_7PI(1HU1m8VBzA<i_E<j6-AFq7iU#jKx9Bg>h>|5m{F49Zq$l z3`*C66*4Gt|6x9(#F;}d5nx<!Tt2A|9jpN5dBqT<cP46pm-Bxbs({5<IA&lLP@UL; zLS07;rY?}NiCr{`F44+t6OERuq|+m4r1VqRUF@eAOZ<fL2FH1UeuLxOOPj~E!)eOt zPvkt8_gW7y90iYjR@nNR?27|sFGfz3Z=_Vg_#t7>+Wjr<kuN5nBEN8N$2YdMW9t^Z z1K4E2>G%L#@=9L9)*f1r#Hm8M(c->u=`8lXuKYaRC)~cU_NnvbEd<jPMq3XqE^IV* zhdgYg+sjfY>K-9(n6M*f(#zr_^IP?%b#)CqS6%W$;kQVeOv(!r{w7al|Jc+ZFe3Q^ zA>ecG$8xKaZI-Z1w@wuh=_h!b8@%4RiZE7N!k)>7O(Kkdmi)YD6PBg)`bF_q8cSJM zm2xr^SFbf+<Ak=7Pz1Qt*%jy)qA07c`X0On^+3y@T_}{;z7sh!6N-ljVU<&lZ%;tU zQE?)S7GIdwWB>`1_r5b|ppdut#)YgAOAGmtc@QB-6CqAPX5B7HGt!02_!1(d&#g1v z%K8V6Nn5j;*IAGAp~M(bvDw#q$Bl09`c!Y|rQ5rH^*y+E^el5d!PoG%>Rq2YBl*h1 zs&}&bu2kHnob%}3g%ZHgb*$~^b94!l&frbf2M1k~WgSEWSII9+r*DH^I;%pmbk1Ti zS?FbOKJyWI7H^D^NXR7vgsh?m^46;tQ_1brPu9d-QZYVC{xI0zYrc@C;+v8zGmJ;^ zm8Ipv5*Eq}j#yrgy6O9joE1n}aHYLz-&1_%7v{<{{WJY82RB#NgE=`!gFtVA)K}s! zzH=hSpk0AJ)eEKrQYhbQIzox-MVa?*uiE5{VR3&aRiXCF=<=Mab$J%S6@NBh9YUQa zZM33prOtOk^{->_i>Z~vZPOC9o>8nZyTv-aC1R3*)$d2OhiG)AB>FOsvg92*v|~Xt zp~X71ne$~a;=Kza-Y-LZIF<gEG|-+}LIQGO?7I(p-lFldYdA_mjU(T09JwBA(*DH| z8&rPIPR&L?p{Z*UGtVJEYCMNdiJYFgw{W~EVR&F<Pk;pjMF4BFN*2A^AN$ag>03(; z1T(dol-LgM%oN`fu<sYW9zTjb{>9x|wP%-B?b#x#J&e?RFC_-7eUz@Ne|RbS&0FI8 zFN%)z?|sJOe-!OB^CG-Qwf>4mgTZ~y7p|n22&|Id7YaV9(i2;hE{c|l63%5vcpMV7 z%USjOr3w$)SufyTrYr2uK;0rlgFm^Iax~O&3b<T-^@F~13;BSx466LaOqARtf3!XI z^3eQt7N~02*~}@BL!OnZ47yRnm!cE}Rk`%9(Ei3O&@@LiT6;?MeGP>0h<n;rY~fov z>^yS^_?i&k4MIzd#Ym`Sl)5vAc}A-e!6~nN_QR4(Ip(w1s#iIxYi(Dr99fEkEew|N zneO3=p-!TsMf#Za9;1DgU49FN$v7%sWnsRs`<w|2C0eDjwC_AZu;Fh9x~Z61x4i^w zL)D^O-?l<(X<M<BU(<SYo<!EPN(`p@7D`~ERdUeGHuZ(0wRO6|Y?^G}BM9sKYb)rq zE2)227#9q~={!~P=xQCyL;j4_s&{R6gM{0yQXM7NsIStyDT}W<zB*f<=U1}Ml_!6l zt<Uo2X|tc>F=nrSZBL05v8WyH9kMyOLGtcT``)3yXfq4+gzQI65{K;Htx{>%J;Wn5 zI&<F`Bg__tFj`l+H3(B|FPAS?*R=1ib16}s(hAM6*g2_`>;`9bwMx12M{{RL6<np^ z!RXH65nCtFQ3NxB)cGdk2OTZlhB9;4+4^URpyTo+uWnbX0w!+1s8+U02Z?LfU($cR zy7iZg0bjcBt!i|w)?OVaBSu^wUzm1tc&H5lEEyW?FaW7dED;Ovx)xifnh{+TTPJlD zMIyAwE$bmEq^otMg!6}%yy_UEM0rXa+|??>C^T8rNmL@+mTc4#<OnHJe<<CirVm(F z#ijj~FlCj=MI_Fhr^eQ)DURNP(0LYBN~x+eU6*=IsObn<q9bSBQw@3x!$7qnH5e;N z#%yA%g|VZwujFQFuDU`bnDkjx-Hq1IL8?OaH_b9yNAu=Z83|zeYaT6bSc6}$U9jhz z8Js}aLc;KW*(BqbSrtFziyKmlHwr;0ARDoDYe`=eJtYI~*<-?O5Ly4)Swt$Nh%SYI zCv2(F+`3cIHL;cS+th<l{7=+hHnU#zTzcWs3a!D$CSHBZ34&uI#0UQ~ZO`F9Awu%Z zg0^S%<MN4K{Ow$Q8a0*EGtr}Pho<kQ{Gr*-*HA^b$iy&&+4GMqea<UiLk4G`gPWG) z-!KKJbwCEBxEu#JkEuj$ego|IU8A&do>><Q(Bsl<OXS3$^$Hqh>}@wcrdTPXWhxk~ zJQf{D4fj7K+#M96M;_Wh`0k6Oxw9XJrCP;vZD*hzBnWhqLY~SU8_!<%yUu+!$g`;{ zV3JDBDxWbqRU_>cZrlveBv1tC@bKzKl{`8FvxjvCu4PM+j+}*|wfjQ$xm~6``{A6t zPV|D*S?V??c7|*Sx++8U`_M<R!*fjW%((rlY0hotc%m?ui{^4XKF6;-I6@AeU#q#u ziKaDH)=#_iQM5PUM$5?ku+#oo2;s4bMK`LiKk$W8d4@CPDi;kDa8U<Mn!hX~$l1m? z8AmCqBD?1|_#7->6nGk>gF$nhuO6MZ@Ty{$=HTP7^bb2H(WLA`J;Zs};<syLoyo#P z>q=%-z{0d`<QqlKbw8G6S-oS;L|AQ+XTj{BjVTmKksYdEt#P5`m-LbCIo<~tuKN#i zLeWb)tdgS3#cCLU6}K0vf<%fu8)CDFpcPA15N^c|IGfnY3hlW;bRS@uIB0erUBamz z+>sL2+F@p6xg%rw+=7E5z-@qc04qi(sPFL+M1(RILU%j=?i0}lj@#|?mJqFMQ9<Bi zRG5Mx9ettXIYA05`ZkqF)7|Th>r|uS>!i6H@UTlMN`Ax-nQA4I&@Cv0%(JLUjG-c= zCWxpx46~(t5*j3==0ohtTG~w`fcV1JeLdi`PtYB$flfMqbU)4#1qBYhEIG^)v^?N+ z)Kl>Xqf~jDDd_Q&sL{F1I+#vXP{qp<bi4#jASkZ`@kRKZt4#ameT}SoQ=uA*{+onw zI;#CK8m-q#V-p4a6(f!pWHQ2#rhQ}NOHPtcRjIY{bgZ9JGvg_!%L1ssL~o(=_oqoj zfY4HDBCj(*=~?bRWFF~>Qrp)#gwLf=NxG(6lD6^-(NOk{1<`?n8BY6khveY&Y*Mv0 z%Si}+Ol<LG(kh_n!YnvF!x_F!jjYjHOJ?Z1+eS6;?&4;(Dk$Wj!DSdFQVyvfbyKP6 z0)|ohjXh-!MP?Va<M~>vnvQ7|x>mS8tX6}RdL!+7`>Sp)07f7;?R#B93dh`NZ3Fv~ zKjKIB1K0)J%XA7E9&jf8$wjbt%A}+XN*J~`Rr-Xg9K%Iu43X@%^CebaL$h3rUFRiD zr@D{CDEZbOAc;nB|5~65{Sm>qjFn|=uPd=!VS7U5%?z!zd=@21HP4_+9#^#tGg|!w zl3g-n`1R;5paHOGx2ZVw`jC26yt+b;+|Wym485(BDBLVmvHK8J(zNftu9MEG-EpfF zoc67dkjw>I4K$WolK@2pLIQMl>8>BrK?@^ec4>;^+YH2XsWwv(<3YM3-P|Ic1mP<9 zS^+d?;XENa(JCa7HIT2mtkmJ>czsgL?s3o{X0AZ^P<rW&s^+;AftjIxDb;BGH@Ly% zWg@B4Ga{@a31p0S<8YEOQLvp@C!^~U-$r+Wv&8rGOug*_?n1#pjEhG?m`GVz-#TD4 z`8pTzP{2hN{iuKj(=<;#!i)iS%dF@e@~OE{XF6)E;pT%+f9U=NpY*tY!F4hBZ_KNh z>0IjH+lk80NLg0u|7I-tyehXhg^FUYcmjH0qzZUnF5J1(jbF6*0q^{?#5{b9oI!Bh zp5x~@zpof&wEUXzV0wM1{)Ol}$@P4M?BIxCeCI&0c2o4SkbPM{+VNb+7N`hfpfSr2 zPD|W#)WEdF@ka%%L*X%p!k%5hkw{~AgvO*pp0~`AJ3=S#Gxxm`YJD$yW-#924cBj7 z@NDX2X+uVTpkh?yOVmwFbhp$bNtT5%yjj0Fa-nKpWR%;s-KpB3ef%)70^#Yw!<`W? zVKvo#Xcaj%LnI?%?MQtQ=W8M9@@w=4uVD(GvAe0p9p&Xt>7*VajQJD29z&ma%ZT0M zNso%wur_j<9Kj2hLK+D^G+JC|uRoMJYRn6%5gZcBd4!WVMM0BN!1^jJ0J0eG5@X3_ ze8k3nk#D1AGVhDW&b|E`H_2z~1$=_PC6xBDv$;A~z=<l)#VXO!l89TaY3zk|IMr06 zSFXG6)~nW0Uk1{~vmIfH+KVLmaZ(gHKb3`CAoLZwDp03Zs4dq>ZjIQA82C1-kV4>X z`4Ttv)4u=6ODwpO_Wg*E<Y*$5vd*6<G)p$iA8}E#9YoV)Gx-n+^7ygfIpkJeDXjoF zo}nAaT}#ipSr8?#7V=QVvz?$<dsI+2uO@&9(NGAgNy#v&T3`3%jh>6_GXFlPN?`lW zlAJD&b+@eU$I`k%l>8TUl7?mPH%vf=I#??-rfvs86|6%iHZ2lz#Mu{mxD1EkRaZ)p zug*b+Is>k0797$XsAP=TjO#H?9GA;rJKdF1!6J}<zcWrM!|j-~KJjR;>VzgimP#y# z(A|rRkzTuqvwu3do{(!tb~gPP)9UZongqD~&FQOZa?W3!U<q;60V%43XCyCNom zwKL~QACN=MV2$}RL5#*M=PN>56RnCmWz11Ls%V_;8mCYc|AVD!nuOw?`~f)%risc3 z(M#K5_jHMq;Z`hzOYk4K1)$YI&a<%s9t)pql0KnF@;i#%a2Gx0FUI+n1ZBznc$Tr` z4SQ*uJ^IIcgp36S9*0XWa^5&rRVy>-T!n(epdrkh1+b4&t#?-od*&+VH|z=^18No! zGZCGc<T>)5(3D(GFY(vBTdLN~y=SDv>h{BBz91XxL_yo1)<LpVx$BbiKPm_YJfYlE zI6u|jeSAC8jeE*eDGOE2%a`(S?ovq}mBu8W{809skLuZ55hEy1@ZNqFuw&$5<|72S zJEi0vw0_l|Dfx4`!(MnlqHzCRliBaP9j&BI*=<nPL#3wgx&2d4sGiNAU_o?e_D>M9 zA<9_#$@vR_<Bp+=D3n)!N_m3wG-;lU>&gE=l&2=br3|h*rPrktN*=Okqwrm4&!pA@ zzUx#%4j+yq$V0N@sX4|XB?=L6P5WMAhp$QoIMcr8`8vE*8KCTX{wd=l07v^Z_~qm~ z@~!&wyA4{0qo#mQ|7Xj0;i(rQH$*e@^m&f}kM80wd$PY?o*Pm@gS77jLJlwW6Z;EK z>YTy#&cn0)bNG?aR1Q~9g1X-WPlqV!Uw}_{eAd#RAqx8R@j<$$=1&t-*;Wiu%)ihc z*Cvl<sh!<)^8d2D9NqiX(1m>pJ&?PznoeH9f<HuKJ|(}FoFp_|J$c9`WCVZiQ`G&c zL{54F_gA?0k1|^Z-;fzrUB{K*s|k~)R9&b1%ag&BQ^f|Bb1e+YyNEbK1dW_(>fN2( zG?1;GT#6d{x|ArFv&A1`8~y5xTuYz>e5HL|nJ^d@RK=wx+V>ctgWx%HAG^Uk;?<-L zPYdOW=9ifEeUnc$>U3d<^Qys`k>b!5MJe$c8pXv#O{OqKtWM#J@S0#^cAeal!!;x3 zBoY@7BRrHiwU|e0)Y4$Qv&Kw68~HpgMhA)_{lUa|e^zlG2Mm8ny%vlws#5pvyD!Nf zlJ$HAZPz~WHwK&~h?m%H4sbJMEF!phpP$eE?al`5pBH3H@RgV{3o}*C2DhXlsMcIE zC+*v)g69?l&rAD$r-Bzsa4D&y6^weJ4!4>3ZVW&h9L~JlLEa<5>n(M<sh7YgT-D$- zNeR{=-LCm3Cnifn=6@+MS!$mDWeP7O$kOobT->GMM^Q^^iv(;XfF>W9PQWt~z**dP zq-|;6E(v=~W>mxb)}4TSlmkxzY6bYWkzz35&mpXkQdg<lU8pzX7Fn)9G=^-<gd|Qq zi<CH;I9sYYwqQL%k-ZXKHksf1^!%R0q`Ecr;&LMOni-!^6sb=8lHtN8<Sjvr=8MEC zEX)he;3K>FeESB1Z{j2%p%i4qUVyq?k&kP*Na1!xbBPvN-CkHT+O1T0y<4GD-yO8R zX7Gzy6x;@b^RD}I34xQ68=iDm(kv<|go5ARnPjt9!@LNJxoD>1<OdOk;Vs=o5=@{G znH&F^a__hXn%~rD-1k4#Uia-+)kW^xPpkVWRAQG^_qlHmR`<AXE!FMvX59B=^(y`H zr|OlwC|dw8qP@m;-H6{zS-3?<yA5gn<!PRw6WJ2?jO{R5ZzE0x9FnhVGG7Pf>pPOq zp7b#u*vp4YzytzJ+;&u%*pEa6Dcp4!A@I1LaSP<0>aiZ9^;y|*Chn=8hkd5%7)>#G z%HA_%&p@!Vth!C!6F;q9Dsk~NE?jn~ERpItn1VVMhlxg<yLRNewwgWMJ=G1NM5uZO zJ}N?1sJbRx`y9<JFT-#Av-2l~>S3(+<5~Rquw7n9e>Hy_#EJaHQ|&X)H^p83Us&RU z_BTCKjMdd&o^oOJn4tA;<gcOHL#F55RI%yl9FvxsMM_b>wfR4iXTV}gsrAnr@#A^N zo)m@!@FPz-A-jA`P@4E#K4{39M@_o$%(BRfW^tq%4fyv`k47(7dVWf6vhQTMaEWSJ zXYyq_$ygr3n5Yq|-!*??A%B<gOYpxchtcvY854}lVA-aA&w?GaExAmWaL_HGc{5`7 zCgYp@pVR(6UZ4Skd2*kkV{qU=V^FRQFh3^VRvJS5n8IPz$q$0UNfQv-gg6?lG2%EN zVIO$wePR2|u^1PsLw^MRcF#oP_L2#ECL6ckIKeBAD<)LQqj5rAlM(6MGs(C;baBs~ zDaP%$UEC*+YcKAXN9bZ26?wkNc(oe{ex)OTx1F2m3m=hP^7CF(z<3qgImRo`pmEb4 z{h9>6EI&IWX1l~}P%(2Puv31XmzZve=}<9W4caAVLy;Qj#r%6kC8s5X#Dsh90;DLg zA|!JjM?&n!2vREsioGf#0NnnH(8jWUiMXE#4b~Eo$VH%|i;|}lB@xZph`d^xznvOR zuNbSa=i^}g2lMCCis&~nEOC;VU|2);nIl5>jn!i_`!{zy)v(D3bsjcKpQF5~cF9H3 zr&H;iml7l0sn2t{$?cJIp6ZdCsQFd%j0iJomt0`R+xu~o-gp5t420pe+%r@iM$1H* z?09Ft>YZo?(!uXhtR9ji)=G<F_f%ho!1f;4iV$6CFcS|}#{_d-Og2_GR?pxt)o78^ zlwf_25fAf%@Xyn3p!Ue_cNiN7X~~@F_L&tyLCCWxO=!d{!Eue=g4^7)ng%)xlH21d z@mu%>lur<pTF(KE7y?0vm{j?{O#`4w=hi?3_Raq*G4L!??Q_43G<AOUbi4cvg`)3M zJ2+o{ZqWXQ2NalcVfooX>z&9;sYlFu1?~S%ijaK=uB*;03#OmLl%i>|a=qheJ{@{; zzAB}UH0XMTzMKwR7&;H~_ip4hvzXQ<Zve`WVPgIR^C<mBjV8TDji$woMvbP<yl8^W zi>BecXtK_Wru4jM;*YX`tOj41o%36SIo+q}Fk>4n6-fOqx3`YRHtOmZxe>OoG+=E_ zUBiz*zVjrmOhr5%)XTWm*4KP439s-BLz7JDqK$mtFE>0-C-hnmie@`eG~0=ynJA>a zfkN7gfsxx%Sb*6%+&^-6N+Qt=+e!x?hOB>%zRC^<6HohNJep5bN_2dQiMM92ys%`6 z8VVqVSt(C@<P@4b{|gEf79GnHx%YMAL<1v;KK0cnq5kj>0hpF2kg3x05)b;!*8lP` zt}pptJVZV$7u<frJ4&_}NOpl}IU0TH6h1t=&HBv{|INe(iFF=Uq1$qyUzX7C6G{q$ z_mH*8iR({ir?h6h6X8=zN^c@fia_UjLb6M@dSQ2f+4-%Z8#?I5BRfUNhU4Q-x&pEE zoln7nLUqnYjQR;XGj<RIP@vDg_-)yE=FXo3b@Y!CalXSf!Pa)ToGPPr@h~~)KO3hu z;cn+z?j(pdZJ_&(uV;?hyC#=0@(5nF5-9#1t6<8x-yN+C*8a_C`4*AEj(1C;LNId| zf31<;Cr-VdM`|QtRl>hrgROeu-^|2CgZXng>ItH`8$5$^{|=`V%Q<IYX~~$#!Nowy z6Rbr=rZK74eEf=;>JRTUJqyl}gWEDrWsRfY@3~yk@=IVQh+*iouWu4L^a&!zs@wTW z-cKZ_1`sVd4q!;eHRxl%5U$SDk<xO>E~dl+y|@akM!i1JjUoS%Y7Wu4j&~C(v4Q`O zy*q)cqV5BKKXVp1?5Lo)9~2dLHrd=jK@SS<Tdp7+6h$E5xMU`XW=N#AZ<dysX{EN9 znkA^YWr^D2Qi+y%3^U7~G)=jm-^?69vZtQsdEM8&um8PA-+a%^ne{jOZ+`O|Ke}NY zOdPjeDP%6H{iNhzNA>OH$}<=gj@Zmotd*4bOe|ess!CEWr)%?9L_L+FM3NsvidC}W zl&2hS*c*k3RAV%hvf!++ETK4Yub7C<Vj}h`wZDnVu;Y34S{W}mR0^@%-z;Z+?Kx|6 zP6(wSO}0J!em${sJoSy*jYKJL1-F%|O2n0>_-K7yCrPU~xu3-Cej9gqzRB-ZXYsw% zPg#5~6S%%|EWc)dz5K{Q<~34&T#!^&`=-%bZQ4&3O`>{rQNUO{LP8O0N+1^Q73*c4 z6LBth_xCf4Nvv5BM>3Jh(}qeSUG!kht@r##O?jP~{4cQrpLW*<QvaGqJzx8q{GLjC zPn}pjl1%n~S1CUl-(6gp-FfkJ$MwnI3~#f2Fw>)465lbq^{%&T&(cS-18~ew!Gi9R za>aP(_*py5_QzQjtG*NUGq_wFN4h!oA|iJofodR<lwy?1E=)Q7iWBE2i2*SQ#bx$! z>B>4!p;)p2*d|0*R58qcr_Ao&y>?h)m|}9Ok18{u>}iz!i&_-s&Rh(x16VWFl1?}5 zS*+iQymf?<PR+EEPS@>~k1ONNpHRP)(}HBBh|_huXA`mZEU2W2lbv{FA7%Gh7?qUc z5zi`ZYQsw49bZYA-7AhVyR1&kg-Ber9FW*V#lGPZgF5VZUQncOP5B+A5;UG)XN?tg z0KC;#^x!B+_c~2Ai%Tui<49U1iZV0mBWCn-aq`RcstZxCx3t3J96m%PY2s>E>?rdG z)Y-~<F&=i3y$Mx5b@NBH{pG*i-X8Fl^4nQT!mEA%)u(piYgbgJn+wph{8lq7%9DUB z!8_Nq7|N%KTZN*YP;pgJ(rzt1aV7o=p~@uko5@>AP%ks=F&~JNDNbiZ_DGYw@5Cij zkmG8c<s+`fkJK$=CNc<O>a6#Siz=#a)N6auzO$x!uD_@+fc=*^a{V9q{?L%#BE@@z z5|sS?|MT7-I;<BfX-SXagcbi^et)Qil8D-#=0vq`Kbrnu`u@-tqiZIV6R2lI6ZLDI z<b<`fu{h(RX_RPMNYj7#{!nG-nZPbF)=W|)XtfB{?qX~APfC83$ilIdG!XUe_E4WY z=AmHDpDeT|Ey+|6xe44Ej|-UYXq6*c%~kGYRvCg1ayBateBw{CrFd~bf#}n5%!{#? zh)T0d(zp_&R$ZkWj797UEu~6!P>;u>#MasS2||^hvVDp@n0<7ezVh=EOENin&g3nW zrCkh`!jGnlbIGEe1`(zFhMZ}c#H)bSgAAFLl+6%X(&C4}JAX(hKhKLsFDW&<u#;Vx zw>*Ih*Lr<loFmTSwaV4%UcQlxW5$wh#Rn2gc~hKt1wbDrf$ARKULqdbN+>R`>cHjP zX|7dUG>WaLiv4X5kD&U~jU&GxiR>q(HV<;~co>zAU2rmf@#(ofiKRp2sx0M%wkAI_ zuJ}~ZIVu`{K&DzxuKLc*4kKe|vabAsa;+EZa#K8fl-rel4RodFt-IytxD@=>iSFZE z&bWLTTYQtMNJ@u@x`|zG#1<c~a-&IKevWE@kFO9_9(Mf2A|Hw^&AV1~+vv4uJTo6# zbkj`5p5j~KO|d|8ljDlNDZgwgy4`wVQ!<uAi*7fzC5j(eMadQ0bFl#(7u}|>KRA*U z-43#K<5ww2`}Ha<YZB(-d74;pj$Nc4X*Jm>z%LSps;oq6S{Ji6w2JZC`WqLMEgDx; zDalBltF%Jt>^$G7)FRgF!<_0Rzs+r)#0w2eAGlUjZOlDWm2t=UBN==1Z^sl=mn|^! zb;7NJn`H}H)BPVb&HSV2ymR?^C$5=Ys;azcd%NgD>nbn4+$#FkOHD(UGbD8k^@57Y zak;II#%oc&V|HiqrSP_rMuUs5mH%Was&2io38f#q7gaa5Me>u_q)DJi(ML526rg+w zvb89@Ez(E))`C_wvwP8#ro@hi66I5tj)yGr0g;8^xC|9%SrlOwtDea7VLkMw;8>|g zNRha_+EYa(C2Fd9YN<wLaCg+N((!1be3E)(ycXY5Qt=dvr8$XlHD>W|a~mX;j+eER z{QRPrsp4xS2|tgQNu?O^5L_fpxjK<J-}iO-M#+fiLvIGrk-q4HbNPja;)yrX^s39v z;%~H}Ev@Q7M`E>gIa6GdidoEQRSSO5mtSzNO<eJWu=ae8vPy}1!L&3U@#i$|TbstN zbf~1UsEx3wI>^?xHjPCkr8S9jv63dXKWZO3s3o$R#_IhSanr07@9W~G*;TF^wLObC z6ISjxUZ9;g^W)}BjJTjMlb5jPxY(0Yj8ztO$GPqh7ohfY;$o*{kLYxQV+o3VaeD3b zNzo<m#A2U-MCBFQp{EnMQ1DCaTtV&L$E}GaE!ugDrPT9yUgPxG!gJD<q|)ak>Vb;g zrx$H`y-5lEid`7v@MaY1^e8)L`$RV;A=SwJv;+1B#rMJD`w9C7zAIf6``OEwLQ0qC z+4qZbNu?ft5bP7k`?0rCdu&U`ToI2@n(dQlNqJ-HL-n{VI;y7pt=*57_D15*542-s zy~W5DbrYqX^Nsckq8st7lKnBt5-}|0zSe|2+ZmN)52Zg+L|g~izonzlGUg<ZB%^s3 z-P@m3hACQJtYS!_Tai{G1Ke9wyBws>tSEgZ9{vv}#)SM9^H6?(**=4YWhY8Jo{X2J zqTksSi;Ph2tPR?4Tu3V#m}lQB;mWKZCD;=g1vBWnt(xC1au60BY*IUTT6EBk4)z;k z>6?_}96n~Ntc+{f$M&abV;{)mT1rk>iYU9zDs;g917jf_8pel~#qxfR(1TQXU+o(p zhSj!Cx6~wfz%HIjp<9!s#LfPT3tepCkEP_g{kRhS>>m*mOZHnzj+dD1K|fYkvpC~P zL3$d^25d9hH_=7tu`RN_8&gxVr>ZGkv{k$4Nfxk~XEp6B>4vpZWZ7Cu#4UuPy+Kp7 z<?%B40xQ%-^E6*eKDPIz>lMV_A}Y&eB!gBKNG@B^;lmg8EGNe@e2T0r642jjDKWHT z_r~N~OjSlp&ja~Bp=F{%RS)ktDpb83qAqA^0K2C3%Yt#<XZ*cC3&t)Us<QX(q7;}n zgp^hJ*hAC?m(YXu4t%ZpfhV@an&MdJ3woE)r&My%HjPC+XNKKFosz&Mr)>eOuN(N) z{;tx_MlO)YTcY_DwVQ5?AvE19+vrgt=IlWARj-A{d30bYHZCHFMiFT&A?!1b=C7<w z02}sprd6`fAW@X{m|UdtZAldAOph|I3c6Kon`=q+sNf50QmF{~LxoYp;>k3fvdxX2 z(xYPiY@-r8@1|-&ABwE{S@cJ}-izf1pL+o}sVJW66OhpJXue;`Aos+c<+-jU{Y?7u z#GYljZ1lSWtImWT<9J|GQmN>^0BM2v=1RIc5p_y3`)T{BWhd>|SXQCOSVC@#PurX= zOMMq{93df@4|U&B(0h(JvMf89Z&-ZH=5F87>2@_M_uB-=470qn{a#V;5p>K|>q(ol zJ&g^zikgg%le)cVZBL?w7?;H>?zp9dw;QUvL1i6!pItTAxy{S2ouD!-WkHJV+HbU_ z<dWE`EhWa}7^dv~keAMMeWo*urRN^o95O3JMxod#k`dWGSgc<0-eeb2`YMXGt)$En zEWR%xn}zSCNq%ax2VIJMvQlmbi#K6}@;#KgenQJF+nEqP45=**p|xXUs<~rfJjJb2 zv8fERT&tkbO-vNq3k5Jj%k~^ra?)7`w@ER|4P$0~9tD5M9$3cY96r38KO7xwv?C`- zi(=n)fpJ=jOab=q)h{|V4MNMJjJ+5mkBy4%ty+JhMci+UCWy}-bssodWIc<w(s1Xw zjFqtD<aTmpNSNQ4eHO8y<l|2|DiuxH%NPsm3G=T$Giz^7_oA`dx15f7I?7I;JRzVw zMq2VQd#h^SGhJ!LaQaf}M`>SLDxND9R|An^G7?J;h|~U{M0O^nrQ%sr(a5>IxWS(o zU=BT4?VG0zU3+qsyT6uV<@pgYElSJSZBL64Q<Db2Jj7Rfp|V$0``}6=nXwI{>_`l8 zl$iZ$-wtBwo?uVP%a$0RxS6HYam{s0wQq=+Xs$nESU>p6@y0_`=wm9i=<PeiQJ*{3 z6=LQK_lh)QIbIA^c4NvhNKCS+8^1E!(c9In*klW`q*{6v_L7yYw(Smes~si#*_ssX zlovBip0hppcAak$Rh4L4o!B?AKCs&q>%+5Ml>LJ^c8SB5*-}D%fxj2_OwF5BX$vTt z?`N>JDj8`mykRyz)->N-y6DV}%<rkfX0~tP4JqasIiG5aCk6y5$4B#&J@!oY&f+Px zk|`#8y87KNUTvfuAJuV`wk$bHXQjOt6RNl%Qr@-VRPDP~EEeu3SbCnx<+Zg8SZtkB z$U&-^zS5Rc)PBd+>v#HJ^m{Q|8NIS@dRus0mwaiC!eUCiL(4)Bdc9Qmb5_1Jv+$RU zENg<>(Gt6|^%PzmXw9;@9p`}?ua`=Gw@q$$rEj&DWxp}57mK@q>Ew9JSJvR_-epvu zmmk<W*-0nm`}fWfp_y^>?|)PmT{q7Ouy8J*9oD^GF3<CcD={|W3N5zi0QdEbCcaiV z)2_O=EX9W&j4k;&zT{|p$;qNy&iBW}mc}}ZX)O6AuH<8{9pAd-xzV4F{wnC41#!Q} zHgU#MYPsR{Qe4TgZ2>g&wBN7v6jySZ9oAB+60{Um-rjF4<eOMm_Z#Q)jh(zWbWG=~ z*NzK5d9CT_r~Sr>wLc~Cb-!^af7v~Yj+%QFg)#sphzBvs&zUW572<N-%ug-6ivAD- zB&X0E;M9wHa(<+-#OO=kZ&el@Fzq+$_{y@7ij5NAVt*?)=T9v<VBT-M7%fYz^!#vv zZ`*m8o9tHO5x&Q@J;+q?`UGRSXzF>;M%~AZq0R2--8M2qjT>lP<gtmrF?4u(zi|~` z#njTvBYdr*a!lhAzIg5UzDb_D#cRhEmcKENHnCni&Na##R6D%Bu_YhJvePKP=-sYb zGDt;dor;ZCma(PqU`OsgTCN?nwDpLjS4*)+Frk+`o0`JU?xL}cHbCDz%I-6PjHt3v zo(rLe6F)H(69WR-N=h<Wr#ZWcWX1NL!luanI?6baPS`?4qw=Y4*N|gNbvqyBZ)4@J zpYpev^0$TZx0Uj@vXkiYnDY0M(&iiG?>Ob}OUmEJmA{jfzfUWF4=H~SD}Vbae=C%~ z{gl6P%HILXUyJg0kn;CeWh`GPf7dH*Mks$rDSyW(f7S7d*N{5q#u00;FaDGPq;yi} z_jb=REJuIT&!X&_D6VA|mzl*!#eLPtYvjV$`GNB^qg^TV|0$bfZNK7MZT)l{y_Sm| zsa6-h*cv!;S8~FozDA4^lBK?{h%Tra-yX)MrS$K(kzf{O#+Zmt)kn3HmK>btqCVGL zT{THO<V}{9KiTWmzQa3+u_!kfxV1>-55q(Y_WPZvo|_BtR_dL|-SgGEGCqz<ca&=C zs4i2PGM<T6nw7E%y_^z@!^Qqkx$WU7YN^a<-TKNC<=wf1lzR}|+jZxGG35?6F9~tk zCFYWws=teuwlIHol2O!((2De;1|vD2N~1XY5D#J(_hCfJ+iED=qC7dN6j>X6-M&m5 zl|*_{7DQQ><AaoY?sijl?Ol=D#bOupp>&@ky3e6%7mkWt<cfqg{<=~;_Q5zyjjvHI zgs<CJ8fDs2IYF;12f8e#hLD<PG?KO{&qpXTuKlSUUt(1GY&V|LN-92Jf0Eu4qjt~n zpxD)HlW6m+7@(*SRQX-(Ce#6p=2WFX{KlD!5=E8MR6WheiBwp~{r~aGq@GYuBgD-q zWe$#!Mab9HOx}8BhkBP6Rw(Uxw}7Z&^c<%VVtxGE@twTquH$?5KOA4~KOJAj-N%>v zAC6C_jj!mEV|k0SE{avEuluMZC6A0&=1eUAcS4oa+f3@ID?AIC#UK{CNZ>NsA5c~X zUh5`OZ>@SLSJo%9vMDC8KPdIM#EG9;33xxH3z5Dnl;LR0Q#{OGoWznE&0YQYD@v8( z#TRMCqV?#bOi)toqNSeaQDPd|LP|Z#7&c$;vV_IU#FLb1cPwGCUlNBnWdetZ2^^Lv z3NkCJk#}M#ukIFA-Ev-|6hopw$S<>4M>$tC(`ypNR(Mg0vVIg@N*28*Glsh9SDPYQ z)ep+M593cTBs)W5<t(Fy1-^+%sJD(O6Jb)<U!HIu&D6V+N0&2}2i=MXnaJT3?;GWJ zR@4S+#lBRz%hg<pnFQ!ZhR`4mox_R4{lvm(!d%zc{<P(-ReS2?Eqi(MMqcmXW}Z2# zQ|p>|c2BL_eIK=EJv5pMys5v*?F#iWxz*-BaZ=%}ZPYVz>|EBW^K+%{G8Ok^)t57u zJh7f1cu;k<_`NtjnPohkWjrjgc+xmlvp2HYyKg<1m}+>4aznIuofhN-9H6@xy0;`0 zMN76(ET$M99pjBCq=GSUBvoV<i&rbeClkGC<vT)Y<;Qy(hLK$D)#M^UNG@?=Q<Gdt z#TGOD1XevjSrHuuiP{NAm>Je;ua2TJR4PU7DAHORA!G4)PZ_$!q>b#r!|KSi@zbyI ziQ!5s%KItf|1DElT<ZAQ?(P^rg;is?`OiB#SX}m*{90$LVQjzCv--xA<&>7wmZKwx zq)+}&)<5Q?Oso?k6&Q%<KSuOV>Wah6=qpr3a-H)g4m$SMmun9cj`gSZ29U_PoUV>~ zzk2^Sk^9ue`%7d|J^2xaPhpz_+&T?vBNln@LNk{Te)Y=R6djVUcx`ErKPk4joPucj z;@w5(4DqG0w_~ZEJNGPgh|4w`xf2cf;gCVu^@#3m6;-<8-Mkw)Rw-vDZaf#Cid8Pf zxnaY}cD|n5I24hLEinXCz06=(w$!A=(SjWnlSW;Z@mySuJ|L=l8e=XI6<Z};Un;LR z<a2fNpudt;Rj)F!#M0hp-(^-xZOm1>@pQwT_>HYarUkP>NuIdk1Io-i9a~JTl$Gkt z3{5pGO_HyR`KzvEPP}7_4r((J=e5Pfc08^)Nt+F7)D+coYP0c~HXFQL>ttMs8v|#M zam9y7AZnOgnkZkVhRIT+E4xp&jM~F4Rm_Of2X%i#7fH<8=p4IvUit8fzbFqgBUvfL zSCa-jHIc-v#RphqBBT1w0xphqPeiSL#F~=~QT?9WOSU!HzKU~|^c7cEP}H1e=OqV4 zExA{yzrt60|B<JSQ}VqvH8__p;L^IKfibemjBdpYi2Cb$>GDmRbq_j`L3wOne6=oC zJKYj@`P77}KCY$H7wVWkQO4}JU#5(|bhwzLca|J1K5DyM_4-|>Up)qJHdf+N_&bx{ z*VbX1G7ER!PrJIJ#8dm-c}W@XskZ&d4b0L7?BQBkSQVJ|XIe@U1tk~1#6qYt*IVD2 z6lEo8tlriuE-yaNPK@nZIbFwiJ-$1xw1B;JS!|&w1y;s;2g>3W@3tN2&C=`Z_2_O< zxKfIVr4H%X#TB+KYU{;kX{ipumQ#GF@Q~<69gS^O)nq22=6+zE@vot)n(?axdaP#n zkCnxmD)NtN!!N8IzIVOhe^7W(8Cz0?x+$tfNa`<|63@-DnrvQ6#I2JZB56f=F_E5e zyu;b?BlT8RFF6)hyxVsEB30^;#ihOxLwV-PIHput*DE;NI3Y7yV!aE3e9?utAZSar zmRLWvM>)6_xVBp)wAT_*w_b4tmG}e%AKS+F0%eEdd!;&-2mZ^Uy+yQ`<ygF%%oCPF zeiJ3e*oLY7#3<W&cvb8UevJ`BDN}|LY$=U3C_@srXUnU7;}{4@Ql~GQN6i>o^P{qP zDRbvh@vzvr7=I@02w;$Q@h0ZFE323vW&f)cU`lrEeYN__kMLSyN^`LiFPk)-!ivcv zeHf5PCp&AEh~mI3QmE3tk^>Q!5%%=`QXAzLk)lcR?Zn~*9ByqD90*RRor?XW{oF(1 zMm%?ndfF;&;*BDe_Mwg;^xJR9X+s4S)c9e$GPu;RHnFtVeo@SF1&?Lq-^ZlTo3+x$ zDc#TZrgi#CHq#d`tmk*7=us>cZrwAH3$3F4fg%fyJvP@kB7McDz)^`~K6O=zvSCj) zWdgObVWeuZT9!!^3nRm<Q2eM~CW*q0m7;Lt9o_6v$_v!gFQz4``A@Y!2Gto$wQnby z#6nXp?z9Tr#Zgq<fs`0$bd*J=p%|B_DZP}J`xT3mhGw+46kGV3#0jf~bIkZF>iJ9M zN--DW%(B#@3te%l_X@4vlI@RN7d3O%T?NFLwDY$+ug{)fTT!pi&a#5@^`Bjz{c-Ki zRjhVSI6JL&XP~9V3Uz@0W~=*NbRo97H|h<p&gnG+TK<?aW&dVC3q%)UKxx{5N{kJu zhMJo}b>7Waq*RKhWO3Jhor6dZ-gL_v)?_Ky1mZQ6V@$mN#$!EcTH<ktzuXZuDDU`7 zC~@Zv8pY#SsrD5ni*<{|C7uN1{&+lZ)^iFjV_o7F7q#gpcw2bs2bHgs4EGZ+RLg5u zbzjZ-Z;8~uXIz6nycX|b80plPmVUe}J=VW0|HG@XUOOr0(ZZGOfzpCyJPxuauYyw~ zaYU%uAIFyT?Agb*FM$_4^ZbQ@ZgMYGZK(ad&LF)OPh$tByxGlb@npU#W1uug3)lGQ zrnb+SHjX+}yIoDYiLP>d^gXg|7j1dnUw7K_jq1;z;GJv}U3Ib1_vrW@XF6R#=Jqp6 znSsr+nCH4{o7jJf?AMxuqbTcBYG_EoZ{sdDW06O3T`BiN=6YbHXu%h=IQ?KLDit|a zu~$*DEX3=GxIl7bSqu|Z_;Z$Vg_D!(_HVcqsf_qP8K1gGV|>aNZG7|p(fG3doAG(y zZT_h5`yb5T?Mwf~{F(1IzUOH9_v!h_e>1+oyN!?By?-#iRyE@j$ETX2yc~5CijQ$T z;$1h%EdS{VrS8t+`3frGxpu<he<0!7SxfPLuN7sM?Tkl!3KBai&)Yp!ZYo!^?(%E$ z?M8{08mgIg&y#<6N2c9#q6Pci05#LDnVM<mp}y3`=9x&<gldMJml!##;h(eXl(XD= zWs2*rk0QS=RmrbQiV?dP-s5D6y1`Q?H+bK$C?4?mZcC}p6UufG^`*sYzzrq)PTbNG znF#FOYcldgW&Fxgte26eY!zhWefopA(WGr0<uo|9%Kv}=<VXoMvV8vPeZ6EEkUc6g zi;UkNRgR98T;GZ;$}1d|`H39EQllHEHGE}%WjyhwSf}FGbC$GOTpkmZOGWh<t{za8 z_NBh@oA|kuIxu%qn1<|-n*F(YeO0poY$y=J;pzF}D_p#Bp<)|B_3bR$$=qj9?xh*U z1!#qMy0d7$S~>H4^}*$ZTz_q4m!qxaG;OA-+V`c}cHDe@o+if?n#gFby*#I!A@_-u zH#D&MtyQ10<=*AfOc9%53uQB`{5PB7S@GDbvKfBBK2FMa*RszqRw+Zs45${5hzBPX z^HQrn8srVC_H}klno^rCIq6Qc;9&x`ORps_(AULR`6KewGnHOusvGI_wA<w0h^}n+ zUtA&v&O4{%N=`tA@QXMN%uDdv^o_;;qlAu^=#Wax1{KGduXD<%y_94&H^CE1rjSO9 zy=qeI1lO-LRRty$e_ti)`BH~@BUK${>$?nZwqvjGW0gx{ak|=MDHi$p-8exAyyIa! zWd&3Yj~4M{bAd8zWj1FvUoY;?C&(4KzjC}xEcI!a6h(ooffZyCSbEFsAH?+=$544_ zX<Ur_p*UP7cyTru^-F@+z+aS?5Q a!A$o8{8uoHEfFagYj^6kSyNq$31ilFCn%R zM=)g;<z$5?Q!OQ<0%GjTNsq%Z;#|&bDfz)tLe&8-yy9xfdF>5=tSS5|evv~D7DjSM zuZ;Jil$6t+Y57k+PA0LtlcXSbHTr0AuU>8bLW}3LctwkUXwloCcGpIWky;$4#bhnc z*5ZR&d|ZobwYWozC$#vB7M+ah@EdEfn-=?Mahw)sXmPF<pVZ<zTKr6lb}jy@MNcPf z`n4FT#Y8Pm)#7X|KBUFRwYWixJG6L6i(hH+7cJ_X)$z2@Vz?F)wK!Rev$goJ7T0QV zs}}ca@l!2+rNv*gI7C~1W^K7jTJsNX>i8?PxK)debtTY6{k@?U*J=H4*5W=bR%r2i zEnd;$4J|r`_kgy3Jfg+vT6FyWw5vKk*Lu^jNNext?mlgL9L=8E?}xPZ0a|m27GtzH zNQ<MixL)i25iMTQ;x_H~&$M_&i?_8nL+fsw7O!j3N&DR~U5&No_4V2h)6!+S7H4bm z5iR!9qGLG8M|Bi1k|am0{8DX}PSlHz9vr`G%}*cJx^w)l#fp#B_7DBV?*+|eWxVxz z``gA{^*&AOea!Vf-E(bu`KjI0tZ6+`gThnOLxR&nrbdNMO;1njkro*lmXZ=~jS7zn z4YOLqLPEnPW@b#yP05`%F*Q5aIx#Odb>f`-^vG_xd5#IoG3b@)wrDL{l6Y<So@UKU z&CSTk&&bXS>}bx&GpAW|a;>Q;`PQ_aW?P=s9G8)olbJH_wDanq@rh?%u(*%Czv_T{ zvSYZ8D3*ozRCr6uc;a(&vvX6->DjrnQu6aNvZk5yb5k<%%^9<DGOe?$S@|hql;&J( zzAZP)nr2SPGFwG2<~h@?S?2udR<kWD&6=AzPxP9XpKDE-WlqaTGiPPzJ7zT{Gc!9? znb-7L`HopFF{<<Fr%sNvjB6%OzW=!=2I}aE-^=Rt_aZaY_b%;M(?0aB!xITUaaMMk zI#6#XbvnrTQa)ePUliZ!xBrs%U1>i|OE<@Ki({qun6>tQ^}D3~?&vOA8}DkZeVG<l zXwlJrv(}uNl9Q5}kw4FzJ=>a_o|!#I9j$ai8%_hQ`OoQ>n=)t0Y-?)mEUR5QtMhHv z`fEYtu=|(uCpA>pmn+)z#AtDs$Y-eYA(@m<O>|UDt3_|0Th)>Y(yi*vm;NA11$A-y zx^U8O)eBqRs9s2J<HGf_fj$kgPA8QabOzZ)C(AdTkH|(jxGO*C_$POf-JD$AWVx;E z>gz3AO|q+O@aM-?%_q1;yV0-g=hs=^MRwL1T{=cbh~b$GvfkNM=OKH^(Xzo{l6`c| z7)O-MICMI_UT&q+%Wk6CC^J?cU1KMiE*Tl!I_unYt>oUayBs3xWrn9W>Scpy+fwPq zNjB<ybS-pII(5h`WsB^rm*w%Y&e_dns;qNyk!9xuU2~>HmLoiw8J*Eh4semv4YCuH zp=+u$=)Dade05?9CyT;Q`j)y@{EN}a&L&ysrl+T_#!xv{*G8VLH|ShsC;e%ij`7RR zVyHTkldDedAe*}B+<go|GM)23(8XP6W(H+_B%Lrx`e>6*7vnTSFMG(&V&r;VNM8@R zjaA||H@%1Wr*k)&bgm+)jlo9JmQKArLj5J(z4`@AU9!2mEXe@|z0oL7a+;)<L@6qH zszG$w!Z1wd*=C5`M4xWZ>rAAUL2jxu%1`N<HT00%o0__H(+82jI$Z<VSJ%&pS=YJC zCYGd|9Lf~vbVicAx$LRLG^2}t9@Al9e2+Ddy!EoXcN>~tW0sxhqb>8@UGA#uFZ(hT z=j11hl9SwtYBlvtx*<?+kYCYBhW2tZ(n8Yt$ze_d+=31B<#4Z#q_nF(nCTEhca=l* z0Y;hPb?&+_7gvK$o}y<mSQa{Ym8>`UvSjsgJ7>LdfRl$vY^HCtNhD_zc@*mjJ?JhQ zocP;Zw5QJ*bVKj5RYZeqlw4$;BvvMQ1>-cxX4gO`9cz}0lTP1_#lDYum*s<Ew&W3w zb<QI3?{lIROJ9~PVpK9kXIbOINOdt@sezN1<gV9C#-7d`()EElN$%T#wMFN_JkysL zpF!%TYa;1gymij43{=_^VF)qFo^oTO>?JwN4V3X1jeY#()t-{v+aMWl>#mAXvcy~{ zi8jl`E<$dVTyRB_awKP|W&^ai1&IyMS;qpD4Q<s8kCohy?UflZcGmZF>EtJy>u!2t zQxS>5Ov|qSXyao_#Kxz0W#j8fzwY!I$l(990g|w6!O8|HHp1Gmhz*h50jXz_hqE!d zo2Ifcy8Q>6A{(Nig|aC+HbfV>E5oz>+s!aY-3VO``u6Gu=%j6a#z;25r|WEd`(h+{ zkH4fZWwVl;GQ^^DHS314F#o*iu;bG<9-SN@_pjM_y4TrsI<e7R7752DBYAs!OAJZb zFz(nSVvUk6=`I_Cv)sGRhCr&Rn}VV4hQNYQmV_spLw{u@7s+oRO=>p^(wY@nEC^Xw z#WMfXreUhPVf;&WHT*9dhPGMg*(l`Nje^a=Fq~aFDIhiiqu$A6`gfZ_n!MUs*$51^ z8v&buvm{pk4eTS>jGM@<<vkoeDs*j~b*+raawo9|;ef^5k^r)gJW?((t&`ikbd=hQ z{jaVw+u#_TljLUXtkcJo#Yk*2?O_th$fvcghuFedeVy5<I~hDUvN-1}dt9qnb=jxt zjSO9U*j>}cU^E)!24=lZ3X{$|v6ppM#w2xX*lKY)ySyYHcIr}R+0$v5<m_&gnmIL- zh9w6W0`lG1d5b-Z7A3J^h`nW~N9d%+u8pM_DaPDX*HjuU4`lZ!4oe1>v^c)RvWsI& zlv{{IV2YgC6*ZNck{Wk1Nv^Vwlatg(iWB2y%&diXeuCyY&RA;Bh&fBFJz06nOCKZp zX|DCQ*;SI-^Syri?X+j`QczG(a8O84Xi!*CkD&0Nh@i-zsNkUB;NXzp(BQD(9>L+k z5y6qcQ6WJg!66|bp&?-*Jwn1mB0?fVqC$g0gF{0?Lqo$tdxVCEMubL&Mui211&4)% zg@%QN^#}_OiwKJhi|P^7Be+LMkI)`rJ$m#A?-9`>vPV>SP<U{7NO)*?Sa^@{@bHN6 z$ndC$porjzkciNTu!tTJ;SmuLkr7dmL6O0cA(5exVUax|!y_XiBO{}tn8YYXAH|@# zR-hwV1#`8~BRnE9DrIVFnl-()IksQ_xcC8G%&uKr0|Q+tU3|x<{&{Imrh48cX>oOw z+H9V0jy9X;yDl&<m@r9Mo&F#EbH^O~R}HM3|K_8w_c4f1o$*ZQ)~(y5sn%&3S>|57 z&DN|mbD;RrQN75?%gjg>S2xMY^b*}rT_44@iuhQxX2;+XwPrFJmCr=2*|FYFotJMl zXK=|h*DS8$xJ)vqm{X^x<eH~ub0x*4VNbKT{K})Fyo@xf*&HZ($zx)&)6L1!YHfIK z+VD4O&0?DnpQBo{<GSRe)+|b_ickIN5o5}><%@wU6F`#~&7Z~-)JUE0#>!CsJe)sm z#ay;c%;A#KnwurAwB~C4x7PZf#su*Y%%9pn@fYo%{EPPaT(kt<WjLaJL)!moy6RnT zd;6&K<=Ac<*W0-%S<|e_s-P__Es@mrTrQi%YGD<rm1)hImOtIh$oN>a;W?K7IIX#j z`m?0=pO>GK%R*LG1NDd1T6bca#ixFM>e$pil)m%KDfwpeR2CiUtJ0wKcS0MEV>w;c znmv?XB`KAwaW3PNhbmbK(b2Z7Ik_o0fgO|0T;->jlZRwyS(DAPQ!;H<r4Pq?tMx8* zZlI=@PHRy{tVB7^VmHoV55C1-e20&49{cb;_TvH$zz$Jp=ORQ=p&#%uM83Yr|Gk7y z@FPCOWgNjzr~s#<(oy`3WB3KfaTT9Il->Ru|H2pe4JU9NC-FN@;Ra6QCcc7rD&=e3 z!Z)}LaRB4$fs<*TH3279k`YeeFesV8xiSYnNfNhWMGXL=&QMQyK~$sK01eRyKJbMo zy~_!U<cFqchUO483W#;G6<Wg#f3!hc1fU(-qXPm#!2*dw3LNw$sSCQI8@NuBf*?sT zQV3BzkQ0U;5RVy0APPKEA|A8hAr(pL4IVR+c!W<maB$Tj^@Au_8;5udz(80q2!oLb zQQhJY48<@E#|VtXD2&D!jKw(IgYlSviI@bgeWfW#Mhd1P6=|>{9n&x!8JK~Y$iysU zAsack7rDqoK5UqcIhYHP88MG|ALiqJEWiUOgm_GN5wQr1u>=od6z!K1A3`z4(7cQ| zmRLf37?0p<EXO^xUqPHeTuFQsk0F`n$BC)LCyAW?OHUD}6Q3qNgXfV=^D1I4QRIKw zh^vWf@Dg6eTD*dFDB$;3iR<wiifDeF_y*p@Lo{z9ZpIdfytucBCB(Oh@8Df*#d~;^ z_S=Ztu@fI)FZSUhE$$~C!~rcHA|A%aTKt6gDUP55M{x|t@fkkH7x)q<a1y6*8eicX zoIxed;vBxkcQ}vlaRGK*#1E*#CH#oX_z6GbDt^)8uf%`hH#~uB*oEu(9XD_jf8Z8w zqZ$%v!Zo%;K9$6|xMYA4JkTdO!vygbCRcC~BDsSrbIB84@J0hPL?igX7mXo`%ln}z znxQ#bpe0(NHOzQ{>1ac2ivYAkdvpN#0a8bFLT7YASBR(TyCVp}2tg>KVeP_xfY=MY z@hrpdLrkZAEU_Q@qk#5t#A3wbVGO`PSdf507>q<DVF-pIN6&VNT;yRk=3pVN;z7KQ zhwui9@g|mG6CTE4Jc3WK9G_w(w%}2`g~#wVL{-J_K)he*T|9}ccna@9)Df@^&p<qB z{47KX{pTR6mV6#Nu?ipH1$>AXu?wqFhBYXMsD5A%#PcV6@d`e|I_$%%*pKx%fY)#k z8*m64Au6suf-i9bCvgg=@fE(tHxRW|SK=(r;ahx%^Y|VYAj;Wa#1E*#CH#oX_zAy4 z%>Ol9$M5(BH*gcTFdw&ZKdP|+5+}gJAnqm)hj^N21jKE!kr3zGqabcmj)u66I0oW2 z-&ly-M&lrEN8JN)+i5(+?KDw+hq&!G5gwQXPfUgvrobCPXn<feL<kxo6g~)pFM6Oc z!qEf~@Ixd-VbCZvLo}MBCq!-Az0eZmj7hB^UZ&L=ePKon{1J;b=!drGj{wA>9pcd* z1JD5j5eN%9A_1K+2%Rw)U66>bNJ2LZL3fB^ugOTkRHPyeR-|JZrXvG0FcX=Wg)C$v z2lqle^_7Qw*f1M&Fc<T1ALiqJEWiU;hyoO18A|XlO7RGmV+B^?Q9Opn@dTd4Q+OKB z;8{F}=dlVe;6<#)8oY#;u@<jj9bUzHyoL?fh}ZE3-oz$s#umJVxA6|%#a6tBZP<<- zcpp3Q0Y1bol;H@<u^W4^7aw6C_TvB!;t)Q@VSIv5QGug4hU54QpW_RBi4!=9Q#g&U z@HM`{8C2pd&f!~phx7Oz7huOl{D3N4!jHI&pKt{~;}=}TulN^!!!=yT@3?`R_yf0a z8`Y4wo+95`(m@XcjBtW8OmKlK+(1r~<N;53!5a<G5Ta@wANZm%nn2XK(-h4hs&Z(7 zmS~06FvB0B79UalO90xTJvtx|9nlG$(FI-64c!rp6og<ZL^VC4Dxol>p$Du8M>--f z4WcZHcv*QAG7ya!=!u!=g-rCuEc8JZ`XU=K5U<E4i&eT8{g8|P$U_{&wQ4+U7=YOr zh&ixeE)p;ggK!@PV?GjbKa#KjL+}8GVj+g10K-v;5m<zgD8eW##%L_T7(58>KazlK zb3~aaN_pvsFBA2|wL}B)6{3;2j_5>umFP@dPc#u<Bf1bb5M7BIiEhN#iSEQVh#tf@ ziJrtwL@y%uJtc4A7GeY9Tf~OMw~392?+|^6?-G59qGVcQ;(Np<L{Xa3kGP%Kl(>V~ zjQBpWIdLbk1@Qx7OX7#bR>WPz)<o{dN@ij?(Vw`R*oL@=*p|4L7(o1p*p9fL*n#*7 zu`6+PBbNI{(vNsA@g-s|@nvEjaV;^Q_zKZRTt}Que3dwdxSlwd_!@B@aRc!_;zr_p z;_JlwiEj`W5Z@#|K-@%JNZd>;AZ{TR62;rF7ZKkk77^beE+)Q9TteJRe31AaaVc>d z@gd@NVlh#?6mJ>vePRi5C-Gt82gFk1hr~ySyNJt);$7S;h~>nU#NEV4iF=5T5%&@w zCw@eHg1C?PBym6SDdGX*)5L?sXNZT0&k{c-K1V!Ee4h9TaTW1X;tRwh#21Mb#MQ*3 z#5KfY#FvQ2i7yjBBd#TWPJD&<1#unmOX91<6U6nzlf>7Er-&Pfr->VhUlCs?eocIX z_zm$*;u+#5VkL1i@hou*@f`6j;<v=NiQf_5A)Y6`OZ=X=m3V>p9#Pasu#I?;xSjX| zaR;%A_&)IxaVPOd;s?ab#1Dx-5qA-<5U=BB{ElC616Oeqzv2)43%5{A0f1##feO~) zmBgdOM~TOXj}ea(A18iBe1iBn@k!zr#HWZ~5}ziXAU;DpNqm-giufGyH1T=jSHxAs zuZb@ZzahRzJVRVftR$`>o+Z9SJV$()_$_fQ@jK!x#Ph^;#P5l(5-$+f6Ya#;h!=?) zh(8cF603->6E6|pApS^vlX#i9iTD$7Gm$b5(iY;+#J7mQ5Z@+VCB8%amH004U&O7% z--z!KuMxKquM@Wue<z9;f!`p0K)gl#ka(N8i&#yRTCsnH4tf}1gcF=$f(u;X26uSC z6JGE}12jY<_`ny9(FA^Iie_kz7HEl9Xbm&`(FSc1fOcq)4hTd?bV6rzL05D`cLX6A zAqYhndLSGTh(r{k(G$JU8-36hF^EM!^hX@xF#rQ$K>`M0FcOi3AsC8b7>*GbiBS*_ zYmUKKjKe(`j|rHFNtlc&NJa{#A{A+{A|2B(9T}K`naIQ}WFZ?lxEHy|Lq2SnjX9W$ zdAJYraX%K|0W3rT3b6=9Sd1lj5KHk8im?nOco?O41k14kEAc2E!{c}YFW?NT*J|P# zyo8sr7O!9(Ud4L6h7H(=*YO74#3pRU7QBVG@eba_R=kI8*p3}|A3N~@KEy7Rp&Yxh z2Yc}m_F+E`;2;j+V;sgO_!LJ_fulHv<M<4p;|qL=6F7-eIE}CHHNL?aRN^eo;ahx% z^Y|VYV8=!LfGS+VkGPDVa0Nf(7hJ`!_!oY|HC)H<xPhDa1GjJ+)i8-09&mvSSLomd zJ=|e{2aNE96L=I*a)uwg(G(5P3=Pp7jnD!<XbE2g!i<jaM<=vFXS78Z1fVP0p&Qzx zJ31f|L5M;yq7j0g2t_Z1p*MP<55mzG5fIND#~=Z*7=(TpjQ&VO9Fh<Z@v@cSn1GR( zgwdFSv6zlzWFQ4IFcmYAicF+o7OcoZI<heh51|0XD8w=>LJ5lSFczZ}OYjID#Bwaf z(^!FLuoBPWQ9Oso@H`&JDm;M~@FZTuQ&^AHcnxc?0WV=AUdHQKi#PBJ-o!d=!mHSZ z&Df4D*nu3pk9)BbZ{q-RaS(YpgnWDq8xCU+PM`uOaTKR88>cY`c2wdb&f*8$hbqj+ zC7j1~%*F4Rha0#bH?aVJ;40etGfm_Vbt2AWQ|L@QPYfXrK{9fXf_pI)xkyDG(vS}; zY)Hp!Ov4;Z$6RD!9%kS^%*1?T;(pA+0%YL<WMd(UuozndNEf_^qu7RH*pB1afzR+h zKF3acfe-K{KEw&^!bz0j6v}ZLyYUtF;A`x~H~0u=un(2kkFz*{b2x}^aR}exW1Pof ze2-6X0iVK-Be)2OJlY`02!;+J&?6KEgu#d&a6&ko5djk-;esf*A{uV!33v2@2YSO3 zec*+@@J0+8AQlbL4~@_tK8S-a;?Wob&;$eF2Md}a0nIQ7%`q4)kcgH@LMseGYYc@M z!{CqMXoC@Gi;)PxD73?9w8t29z*q!g96I72bi#Oa#sqZ1M0CX@bi-tH#}p(Z1yeDT zeC|}@0ty+YiAeJ_;&fzS24*4?voM_BvxxT+vxyY{l5&V6h`Gc(*f1LppfBwg5@U!3 z#6m1W5f)<!9>h{Sgkmg12_8l%9>H>~z)C!d$M86wz>|0iPvaSg+8>|8^H_x!@FG@Y z4PL^_Sc_M%4zFT8Uc&}##OrtiZ(<WRV+-EG+js}>Vk_RmHf+ZZypNsu03Tu(%21Bo z*n_?J2>Y-f2XGLF@G%bK6MTvzsK8Mi!*P6u&+!Gm#0i|lDV)Yv_!{5f3@ULJ=kP76 za0x%+GJe5T#H?exKtJ?H9O5wm17Se|24OG~k%S=_ieVUz5g3V47>zL)i*dLI<1qmf zF$t3~1<6RkRHPyeR-|JZrXvG0FcX=Wg#r{}5sI)FOYk6;;vp1c8A|XlO7RGmV+B^? zQ9Opn@dTd4Q+OKB;8{F}Hawx%76E97_UM2>bVMg~Mi+ENH*`k`LJ@`@2uB1W5rt^< zL@)G4AM`~GV$l!%5r=pTz(81#fI%3HL?mGdhGH0oV+2NG6h>nV#$p`q!FWu-L`=eD zOhGbIFcqmtgB9tRhUv(_49r9(W+4mNn1}l?ANOMc=CR*-fVdC^D8wQZVKJ8AK`ccH z9!4pw>`zt@SK?7Th9~h9p2jnH7SG{%tilU;5v#EVFX3gZ#Vc5cSFs+iVFS_`&qm@z z;_Ji|;v2+F;+w=7#LdJPh+BwDiEj~C5#J_`B){Vw;=9<2_plAyu><d8CqBT3*o88b zV>k9-FCNE7Sj2Gm5g#Gl_7e}_AP(VUr1AS<;wShNM^J&IIELf+44>l*e2Eh{iBmX@ zukf7AwFL1u_*b*up$+<>E&3w>acGBlw8sE+z(54Tf{sW)Ck#So3`Q3uqAQZn4MWf! zLlJ~w2*z-PU<5)j5@8sH9vF>qj6np(A`;^eg?kW<@sJEM_q1e*5~AGGqI@ViffA|Q z(~>1h_;OE+%3IMD6lLX}7KO~w9Xz1H{XCibfim~=WbOyb+|QG_A1HG_Pv(B0%>6u> z`++j|^JMM^%G}SRE>uLo8<A*$C^SSg8lfkA&<nokjmGGMCg=-_u5&+6=6;~e{XCib zfim~=WbOyb+|QG_A1HG_Pv(B0%>6u>`++j|^JMM^%G}SBxgSXFD)2`F+F%gcVlV=b zh;~RqdkjGb48>-gz!sduTR4TcaT@R7E4+)Z!8<CsS0r;UOXhx&c-^JUJtL~b#aZmY zIlPZ=u@m3n1DwZ)_#V4(0cEhG92c=0KVT24uosu`5q`uzT*iL<gaf#OgZLST@C!c1 zRUF2z_yqsLr}zy=a19l>j-&V;$8ZD3aT7U4S$Ych;%VgK8RX$v<l{Nm@H}Q?73Sat z%*BhCht;?bYj8ha#saLx19$}su?_`z6@^%jMR*NG*nq{@h$VO(58@3h#hZ8tn^26+ zScWZl7;mE#@8A)<i{;pg6?hLTu?>%6J08dTcmg|7;Lfr`A<D4`yHSKa5U-@!izWC7 z4`LsdVm}_j0TkmPmf;Xe@G&07VU*$%Jc3WL97nJM6<CR*cofI*7>?s{e1<3RIiAE9 zcnV+QX`H|_IEiO*3eVv*p2t^Mg|G1fzQKz)gVm_S8l1&TIER<<E!N^Yyn^#shwt$! zE?_<Ecnue^0Y6|Ps_;54;SKzVH*p!8@Dn!U3bx>9yoFyd%!73n!*MT0AQvN%hf&DK zXxK0YvoRKPFb;EZ59VP!?!yGk$3)zZNmzi%cmPwd5XmS&3JNh5i;xQOQu8z{h80V& z+K=@JYfz4tup2L957uHYUcpCLhkbYz`>`Ge@EQ(c0}f#$KE~@fj5qKJ-o&Tagd^CD z3T(kqyoF<U8^`euKEu2C99!`P-ouyJh7;J1lh}b%cps;+6JOy2e2owB4R+xSVzNjx z#3BX#Fctlgia4Yp9##xMItF4IESQc2WMB|xU@&GP5t&HBEDS*wh9Vookb~j47bB31 zk;ua+<YP2!7=zgui#ZsFxwr@OFdp||0_I~P?#Co7z+^mtDOiZrcpq!96EEQdyo?XA z7Q65Y%CHXQcon;`9((W__F@A*!ba@F>)4MsZ~$-OAU5F;HsfP#!C}0GPw+NA#XC5H zcTs_@IEwdh4BK!VqVQiE$Y={40-#4b7|<R@bbu2A;f#(jp%Yxt8LsF8H*|$Ny1@h8 z;fWx4AsF5WK?8)MA;QoIJ>Y|I_#y(05s4;<f*+#M6g|-lz0e%J(E@$Y5`EDMF=&lg zn9&dZ=#M{@hqq;Aq-LjCCu(s@T1tLOo#(C1{nhf`qKv)x=*Y|Cv{>5AIq;-L$#TOc zmF5pxoBi+aV(xAXN)Gj1T^V+LZF0E#y4B&WZdQg%w=4VIl2iH@IQtKjUC#~-@K|Hn z=;=SGyPyByo=wk=zuGEgLQvS6)K)3})+;mCSYu}Srw`3On|@_e%CuEG*G!j=q-0Dx zdNxBkl`=!BTr=~{bN;iczVpwnu&>F!c{Sx;_P+OCk^AOKOjnV?%<(aq=~^u{Y+`Qh zZcc9fQ<vn<oq{SmXRlk`MYrDEwbe~?H|chA_zTsm!*9v{5wdGaM1Y5X<VMdmQA|_x z)mHw4W5U*qY?X3$)Rh@2qnVb`t9GW0VLHZIkFFUjo$?>Yblk&qOt|#j*@+eQlu2gk z^5iS>!YQK6#Og-LP6aJiJ2!4^Hf;@vagn;LZWyz!vSHB1WFP6Kxv_tBWn;;Z(xkxo zY?A`_HO*H0t!W;U#of-&&bC<h#hRAVnb<Z#mHzFdi)Y&f{p{a<+0VJ{CFOB4gU;ye ztTUNhbgo7>ou|Q@2R0iT8@2G^$=N2lrXDSfElsWE0C|RCrY=jjNxwz6Q+GsnRQH+t z=PqC9zSNzPD~)G$-^&*acHL$36~lGi4dYGOy+hAFLxwJY;e{9Hmpu05OK<IXaEp_( zOL*@-W3E;lHTd|2M~oS}aKoFMKIl=|aB1-)FBm*L8#L?~92OlvAYssup=s7-50@_A zTE1uRzK_3{xb?l}Eu2lRZaz)IqoUWpcH*Q<#EO;con3qONzYjRXoKu2yDtAUe(Lre zzy47@a@4cWb?e?CaP+FxYhGUa>iRc!?A+_*=I+}vx^MjOb*~=&WVLhCW^LN_>3inF zrJt%l+GjAgZPz|9G&0&UC~?@x(PPGqpEx-=)tWvtZ|;JH%hqn#w7IO}%}rU^`@jC? zvB_=b8}){+`gFbAy<5Semil0?7KQ+qR>sc8ID==Wf(=eQ{$>a?g}DvUhq)I<xHNV( z`SpyC(x;kSf*KqB_05gV+eaD(8@n4^on4$`%pDBwF5&uUV>4%iyYsMw2%eC0?q+gz zYOil;(iK#U@OCn}_;)jPYTT@S3!f$~Ll{|{M^k54CyS|ri_NWHpH5Cajjm3^c`)2t zZ*;rIq-!~~mBr**ux@gjcsExkk4Di>u1-EJx)_=iZ10se(%s_XIv~Ee#Wd0*!P#|; zX<QpmSEm84E%gHvBJ`e&IErC+F}N1|*3`MDzU64yE5xJdxpbRb!QN$wsUC}iyc@eN zf6lmQ;IrGKAKbSn%DIzaqEma<0j`0@MunTB3-(PghIJWih;nWaV{~(NdH#!z=9IiO zw+chO<d#mJ22<g~#fF(i550@C_wwX|Tl&IxoSJww@EYin->cv^*E~~>?|}P!+<n}~ zx->0VS~yU@q@S1X;$f|woC-egEFK@u>8fvL&=tnCZV+vh3oAMme9_q;7nFsQSd|6W zIwl%;rn;y>Tw?En-MyS-!)RmkFkPW%7eku+7}tU~BU^fOF}OJEJe>-jEjp=hz%%Ft zr<}Op^Kv&tGV_6^E(V>aONYX7rj|UT-qX;E#Z+*UQcOG>X>@YZIXju0T^hKyaBJ$` z)Wh4;-OJ#uZ`iPrOJli-!B1|gZ|2fmZlP=4*sSlY>+04`4l)GmLgd$U8w?vwH*`0R zxAoO7Z_J%nQu<QR*l{HfuW0eLr`O=bn}2lc-gn}pDd!fKKC<%B*S73<|D$~e4}J69 zd0zOTY!VUCJ$ok%nzZ;4etc)g`}+=kdgQzFQq2a_Q*1Gl)2xeEKDX-Nr$;;*bd2s9 zpD=d9<SA*^(v`2#$43Xxe0TneM}zo;G;6`)w|0KG>+>(K{9Lr8WbL{ScYU<~$f?to zr$6{)-={|sh728h&*Uk^k1XG^^}Su?`}Tj?pmCE46MwsQySiZ3z2BViY@L<eqUDqY z54^eQn=?u8?`+(pRqFu*hYS@P%>xVH{phpLzq<1CzjE`I=iAzM>;CGd_jc_+^5vOl zrI@Fl4qD#&)9N8Z$4_uJd3kr}e)*@Y?1<id`^Ar(W;<}O;^>K!7j9Qe<|%C!oiQwm zGc`9jH7I=DvtXmq>|EGF-_#@<x*Ng_&OEg0<lMk@m{&vR(LBi7!qr7@(mU&^SV8Y@ zFzVf$WKUn?5a;I3V|ljR&wZGozn-V44Lrl{9&KpZZi;!9VMe=x1I9(0_060X-O}IV z+}Op>MXYi&*if1|-Q(QZIKZ`wft5lZ?AFE5%*jn(@H#&w`V{<X>ZSM6_u<j%&c;R6 z4g5^q8+6sT@$~m9c-XM$*~V_Z4?S+|&QtO_Pd}G}58LFs7ktsI(A!vW*5#KM^bszF z6MYJ{nF_x4>7{pdiZl%{xjW^%wbG9_+~ZPE<k!Nru}h+%V42g#weC#}!D|eKr`q#W zt#Q%Y=1xxhP--anP~Tkd<?)xt*__-ADpi@9X;lxh4-Ha}ukDnhEB85w;i%u9$6KY& zS~nt?wC|2s5u`rX`KR&bq;P<r$@7caL4B|~K5+~epYK6&Kh9CbBZ+rB2^g(Z@50k< z;u%5ZNi$L1i`pS==!s9HHaxS^L|%~A?0Al?cW?jR)GU)a-I|w@muJmYYLiLMw5I2q z&AA!VrspS{(^E2dB+b>#gLLWn(Pme*eY85n=d|I8qB7#MQEL|O1rwj$TC<DVDBU?w zF*I$k+VAy;Cnc)s;TXQR*6bKw{oyOgNg|k2%;~nQRAqMZr}HRYYD$)Pn3Lz}NTD_p zZS&O;F4cy|>51|QYM_pfGgIYL|M|lgY1;$!+i#^^Z*=Q6!kXUSnwe=%$)vKKw0S(X zNqszXtZ8OGb)JEx+rR#Zr&Rc;fB(9(`oH|M?j5sMzi*oV%};mf`oC$lf>#neipL@P zcTBP)I%@;Hz&XfZRB3TLuiQG1A0WQZa=TUi;NMv4eychej_wxJ`R-`<zs8k!8RiVy zJ>K+I_2s|uF2AqwtL<*X-*o?X!@Jw>Wp^3oi?nm>hm`GQin>)ObwK4=r;9z&LqpZ= zptZI<7P4xz;d_u$p%(RN<>XCNs@Ce7W}U^exS7_jLEVD8g@h{unH}V~L@C#X(@7gn zD~1!ycXqNod7P4|)>akGL$Wlpk=U}>YIxq79k5wk_tft{nEo5nfBp7hv>*5%3^!U- zh7}#1o|`>uij5klsI;rM**qsD&upEWV@;*nE^~gid3wrht2tPC{=18^hgH7jnddOA z)XBvI;mO0)bP{z^iO(}yvq5c?R%y*5?Zju7*6gScs{VXTWIn_(W@>hJrg-d}sSNCx z5t$Jjo)H?75gwKi91@w4mvNuf7HJC(w}pn-!ozH|vx#=;p&{wvVg6HiT)zHrmoZEa z)Nj9w@rh+zzrA>Fjp9*%YA>E$6Z2BPd*yjH+E17?KO>9BlQYuHdGk1s&RsB{YS3)C zd7_rG%#2w)JFhHYYubXo(R}8oW@hKva;<sOGiF7%UC@gYU2~r<=Jw`UJUQJdH9IRW z-`vw2*rj9IyezZ1v+_`We_K{wN;*$}&lT&CSxMUZ)AJcSbc%UPZOmQMvjWXs`*0Y` z%(Ld_+N|@bTfw{_+ROqEnp?j=@l1y3FPT~{qNA1C*OK$hdDF9PnQ7*!9A0PTWas5& zh}CmWMn0`Y{Qzpm1JpX&thvebUw^pbo|stX9e6aoS08h?ZoSR-oBgwFnVGcYQ@{HV z+EMKHPwl(^MSF4XakuUV(_YktP=EO2X(#dw{?X=~X`PmmI#1c8SBzBG_ipqnKIx#S ztW>|fI2O9yrG4~Yv=`@8qWf-{nX_kg`)5;?G1ak`;lA4_b-F}4h|g3sNB#C0v=iy% z_&Cnb9C7wr>UrA#TyJ1DJ4nYFNbO`w>$T||rcD61(3SJM|9O1%>T~cLt)|CNZ9L~W ziS*(7-P-%#rTs~|7wv27HBsw1(FRu&*nKFUU0c<3tNB6u;`lxCgFAnBw0FeSJJsJ) zwfYZ6So3U|Y=L*ye~@KPW|OR2|G{64Z<V$jl`WW#9OH3x&*pf?r+%~JN3&E=DZbAa z``3Cqu)6yQvpABNlSk53qS(t>(+14Sk4;O-VHcIG99PmZ@={ZBsfbb;bp7>mBoiq1 zw>h?~dE&S?KYmtD{=5WrR~nZwEhB$ecHU5}ZowheX^yYst-0CC0JT4k_1F;yXz4JF zLy0vZD_=<uE;rI@rZj%;^c3pI)TXQcb}R<o0rlJG&`w;V)Nh|lJO96EZ@EkRJlcul zUH#!aK)bsQ{~g-5zf1S;((Z2K7011VyL4YdJ8|5rKVH#Z>|<jfKJ|ZJMLTi+U%&k# z+EHxiPwf}ePFy=W_LuehTdMs|P(F@&P8`c`-eGmS82h#wJG`UD`rB~^$E^T{P`~}B ztXC1VcdXxZrhFXHaotltYMs^eC$2%o#}U;g$}4JL9MLgcM`ZMO{W!XJM91_wnjJkk znjO(GydJ~V{ac_GyJ#_4i$PkPq(#T@613(xEsm@Ad$iU(M2j(69HqrHEjothh!I-* z!CDN}qP1T4y|w1yT1?fVBVFCK0g7Xx_&A~?JumW_qRY4e;~&-4-f-)1t9nkyTh%=} z-Kt)x#l>0_?f%Lu^e0@^h~aDqtnEIGVKm?c@;`UKRqYgftGX9NJg-IZ{qNlBcB}dU z`hSt(h;CmCsqH76_TqOD#cz)9j(9?AU-!4V<8ySUrj+`3qIBm!$8XNsmo}We*8MkY z(#KzP8CI1#H+J3Fdz+VR|GHoDZ}%KLrt=T`I%Zzz!<;qdbCHs3%gWD~#TlolS~uD} zg8ijgxpK<mJeBX97*Yf8bguKWx|mbA;ukgbiYy^%^F(z$?WK+XuZO2)P^5~?7*XG^ zHDele`OZzXDm4M+)f=ufNgZz7zZ))7L>7dZ>V269HKU2^8cxL1a<g;p9N;)@c;dWG zeE!?>k=n|HQ?sebNXdZ7n>dw<hpiK{Qf6mNqef(LI<#IJ-xPIp(i&m_QvaeJUGrph zejV3Mj_cRF{50i?Q=Qe?dg=adxjObE+URTcbB<=m?~dqbZ>AWx_-?NIA$v)j*NV>x zZ8{zOxoY`jj%Kwho>|elbL{sV(eXP4v}(WB`DS*EzboJP)E};8ikeQ2{iQmYy=+-C zvsgL%{AoV_JYHq6lH<JRe_s#k)NNdUp>^vvk^fUuQfE@Bb*ft5cG}cw)2*2~VjZc? zkW{lEqsc1FGWX%Kjc0hY`E(pt=aLZO`nG<1QRhtz?Gwc|Fi~u{{T!o9vgKRnQXir6 zQRn*4{fj!O#Bd#xmBt0z$a%8{TJzN!y7ENR$UB-6vNG~BxB|Y<I#%m+q%|csb^5TB z{OL*?I<MDexXqf&$zCS!sUIa$Li9I)6H2vJ|7<cx=H`#3FoSKDHJ2?YF*}75$)xNw zTc$N{Y<QUHJ~k~aHz6xE)0W10-W@|?ViOtiT?Y8mt`GjnGRd1aYihQbq1x?hWs16< zg=q;ljdW(Q)O=#JZ;tbr&3?KVI+2DQkd;_}v>DlsVRIo<r;>6qfyhN5KUd9D7(Hr0 z*GMtwHdEE{x6#Jqt3`85bv)i$vt#@*52@`78mn<d6E%`e{C`8$>YN8^(>KO39a?i! zty$FRB=%*0IlW8O>AT{i#?9LF{#EyzA5^=mXr#u~f7iX19ytxw?hCZ;P4%YpEf!iZ z)2VKOB2R2iE>}0yyqHZE$W&W;x>cO=OWRV_<stF{#HWcGr24}P=0Gg=^ZzveZ>YFH zsPl2`6&<m{OPzjk%~Yqen*E|!=fo%IeziMs=_8tld28p<qJfWNJ(0Bbv#fb}DbuWj z>D+M%sdQI=y{hn3hpYTpXE=`GUD1B;&z>injhFKWvN}aY@#1cTl7*OK&7GA&o{~5& z|6lFB3w&Hf`Tu`5=_S3v_68{wU7$#iwk%Mva^2pr#nP^A3I!_JwAnVXX|g0~n*wS? zz$irnB39`aw-?&<N<`F(AOQ+gK!}PMv`PTQf(QYN-y*d6zdvVYlFjL+pg(>2|NZ|? zUQa&HJZEN}XD(-U&YU?j$%?XM2~EDj{Bkm?Sjgc4-6hOol~yw7$e1dvNY*T`tXiBY z%1Fc?kR(f_&`Ogf#q<_rQEy66;C%jem&<tHqxn~tXbC?Y^Q$Ts)Ay#zQb}vRd%cmY zEvv4nt|SxYB!cB;ri?KMzc`v4D=A|Fu%@zV1@ELXe4S#yAd-68)nH{zWkval$U@#Y zRj=TobV;O=og9fxT||~7OUjlm$+%WtS(#D*D#=&UHOE+x9J^>NGmuN@2p2N|s!W~4 zu8g!;mno&f^ln8-dD6{SmQ|#f%}W(m6)#Eh)b7MuaHGK=1E-}mrtx3JLw7}Gr(M-- z{hzE(CKqdH#oQ#EKYuM9S1zrrDfWkPNqTHwYfWWkq`bIl5p&4Zl}oD@=r*cek*sB! z)PM2l=iZl<P#`ME1y#&BGSR+FJ6ifVBGa@Z8A@WzzpwDc)y`(=(N(nYi&0U-RT_-1 zxUynVS7rHkVbxM5DN3+=X)49YRcz-RID1!W3r(eG-#so#E>T_57OE;2sR>o`*0IWr zjCtY4kJF;cDt;<Tj3`S#&ycOGQorvo+z{r@X<uae56#)hy+r@0c{<zg_-|#k$txX@ zE_=~Z6JBBDg5#-RdN<Kw=$&EE9$MKvetXI)i~Jot=jrWW<??EO_))T~xWeCAwv4*g zZlcu;^jB0;BxJd)N}~}r7WLJ8TdX}N8t*~<-f4ZzIHI^S(P@>l{T)hel4Uj3-4ZM_ z`8(dGS7+0A?@{zSqH#&Ee~qFif;`w?>(#L7t*IF`X~M{rT0O@aZ)d-q^*N=?jcm-V zP9t1Uo-D3dnj+~%-Jd5KZMyDxB5Qhj3{mrvapuKss(3{?W1G&0mF|gR<35}2L~C!b zKCA2rCh^ah@^j;Yn+UJ+3Wi^}uxwssbrFLORk`QY*?7m;^tCr$BuhNC8}rOj<Ud}` z;{X_5E~ulc<`9``caX{so1PmdDP7GwWi1bt!=m!a`8=)p&r!vs#@%gxD|CoV;q=-5 zYX^QPI<<8#qP)c>-*eC*=RclS^G2&|;R@|YOMTH3u!*e7T^;Ud^MgKG{b+G2r5nC@ z0ZpCbY?4$nw;UJ9nQm?QicR-aMbAc`%d0SdI1r5N<%W!Y_h0Ri*F2F$Npp%#vSOLu zz$rz4=5*$4^PsfP+CR!h4Az&*ow%9zsUI?0Zt{Jcjkgd~ee;GT;_vC~|J3T|j(%$y z8#mj#AZ6pJPUMCeIH;XSZ;B@f+dsI$s@G`af5gT+jC>cs2jv5hYrNOrK9iq#VuV+Y zrM&HO2gYEL`T%~W7A$7!V2R0(Tj$Wv`g5(Gf!6Q(OP(nT{iprOT?Y|)i+MekEI<7; zZ-!UsP4=eqH`XFw7tN0NBO7+GHgB#jGDDtAu&eVpA%DOUIf?HD1NMB~`dxW{-}+s( zXREi&@~iZ_SRQ6=yc2A`bYJV9J(N-IS56NDr=E#%xS)Ln6{h@7_OBkS=RD-9vn5`! zSLRi~b@iD1o-fzgNxD6z+UIoFAN-d7K>YjVPa`GuD7&m3?swra;_6-3Jnww|=6P{u zr{mrfIOB%c7MNFQ?9bXyOgm+!n~LiCE$^~_L8T6dVvx=o*ab{VRa7pmE??2TpNQJ@ zT|Z&&#cDcURss3%zAq`Om=K@l*3G!buo-iv`y=vIHooS(<=lNU@Ay$4J$cO7Q_ea+ zHm@jKAu%MWc6X5qXu*<_v5b8przFpsIU{n)va@Dha6#mhlE^6wBFD`)b72~)xseQG zNO}?50?<1qWy&ccBXG_Rtk>_j>8vyqVy4b^q%JFo<JeT2kqbSZUgbs-^Ah|X+VoUM zaxT5U*!;TR<Ev=s`YjI1S6X?tth3#Esmpp1r-Lmlt2IA5q_fBBb-(YYAyfMd$~Dh4 z8`3PN+K<CI+~bq*&8%RE$ky~V3|%z9rADh5H~KAr(Tm3Lm1WiR|Nc`D@42ru<>&Nv zjwg$mx?}N!?g338E~cZZh|F3(9TxRo+QvInKdt6BuWGqs^D2}njLzILPiEt*uax^5 zr2hKgIiIp>mZvC<WJv+ps!94cd@#QHUG<gO%H1Dux`LMtq5rVM&i_M~v5+ha8#qoO zH+Y_cyE*)V(QhKRM75shId&}>DXHXU(X*R<An<?U^+R1zSuw_+fHA+Q)eHfWv@*Xx zxW3x7qnS2iZrqiqGIG~We#CXng=dqikDSx>?7?K$e4Z>Ar<fBKn5e<l&d$wPqUU~E zbVbcrqc~Dy^tt)ipdP(fR67aEzky7@&q4V&dywCZTzy1_z2>))(!7W_*JTqq6{4{6 z5=I)6SjOYESi9WsKu}K$X=StP1>|b*!SKICb{G_&P&n0p5-VOp-%T&3Tb^fA|4CE* z9%1APmzr{MzhkanQ{U_2tIqvl65(&P;Z-J?F5CS@d<_^g?=VuGXM6T}^Ty88uZG4o zk<Pi^vl!yYMaG!<6W0&q88I(1jXY}S3$ZPER%A9)D2#oX5G*%81eLsM=ZST+-q6@y zeAb-Vg+&u5O!{QutO=8*oV$=EHmqUQ05!tv^ZAtwqdTn_+rOwttJsS85h!AjPgPA( z=QVp}zK$IUUSqwjoX#bQSQ8?QV<~P{_Kf{Bd3$V`Des{+Kfkg5KGxqB@ay+qO?c#T z6OZo9cMh%hp#DvDt}ZQR<GFb^PFVBb+Xv;pWc+e0i3R2FA=mFsQ2ux1`i&3D`}L6i zDHyEqV`*45NsdV2SHr#8vNO4UGp$Z)qD4<jX59a(wcq^?1@)DZu6`d}`N94}PCp=Y zbG*mv9B`}S<F!-;?;en{-YnU9))5_}Sq$m_g1Iny(pzkQ!|tdx^`tVBJ0APSfCt|n z{03IiOJ@A`DZFdv4x8UF^WqNMZ2BV=8222cdp4W=R0XikrRT8aNOK>>+D-bw_;(Ob zV+}K{;~qOzYxKLW<24JweGRKtn0&kbeVg^GFOuti_bp-OK3F1VTs@DnEH3XVl(%d; zozG#kJI!&}wqqVixrNO`PGq|ON^`cI81#=riIgn$A18IEFJ%6%^BI}UoE3>)W$d2n zTS<Pg%<CNN(0f$q7tYw`&x#s0QoVo?HgRsU@e6GH9&+Uew?~)yA$Qq$5gSi0FZ_P~ zSX(bv?&_=A_Ip{!{nd1THKj6?DL00B%ttXn$P#mZcJ|oJ5q`ndCco#~^!<9dXxfaT z*@bkCUC#=OnC8_RCFU*5_&K*F5ScdltjK&GM>Vs_>}A&qp&M+vAG7HOpO2pHW$x3S z=GWuznR)lRcMP`u-5}rQ#khsM7JI$j%A4Aa+^Xwbf1<E*sn42nO#iuiIxfBT*Nwd0 zUXQ+I{O0$h$6t+86Fe6zE92S8pFUkyTvg^z=`#JuIxZdLOIz&xsi42Fzs8jJ3}cqZ z8(01MmYf%CDCRXhb!giDn>L=i&p$-Q^gD1;=M<P`ZTPheJbwr00}4KC^t=1;OzY=1 z@?Cr2y>w6Y<I2BjyeZda+ke<(cs*jAYp)Gow(a;U1|t@u&ToI;BY}8LHeOqUi5Ir$ z>TP0W?KxWVJE3C5d2T@kcQ_5S(v*yfg*3-~&g5Hth1~n)yK@x;(cN53r)+tt9yRT7 zMz@)NWo+k)j<Nl{I;(G>QFoBOmG=7S_PXsx7O=#Q$7+qB&072Hsk5J@jj!Jlxq_4a z**EZ==k@VupLx=kN%yqRn{spGb+<k=oBUX!1m$jhs6V&Hv$j1it=k7m($r%0xOGgw zwSG6QjFgx8)9N+L^=!&YaNa7YJL(+Q`%f1KrxUI-_8(`m<Eb;$__eqzra3T;6Wg-c zG1ta-_Z<_e|I+ij)3#q~I>EGWcfUAn`M8nuBj}&^HR}VecXjt|#OB+jm$=5HXT=BI zr*-!FC3-zKQ3IKOwp|@+h0l`W6tlB6Rc5WYZ28I;Og$y6VQJgH{X4W*@GPia_24;# zWXYoBSl-$2JzM15b0gKs@`cVjCgKmD@~$;@UT*FD0Uf7)r-Sl#WEVjn^!+tow&>L+ zV@-Xlz{I|cB>pa|?_7AfpdOtIps~=wdC4^XJS~OsT`<1(<dXea;#Vyg%bP2G@N#aX zgclXQCm6r5uUYFEl(+Evemv<0<wLPo&-e%H%U1S&P1*V|6%b4>7d_eSuPiSq^1n<h z!f?I_F@t)JgPb%WK4H?d!b`K3M~DZ9L0Rondit`_PmaY!etHMxk27KY_(3~QMSjHl zr`wa62kD6&($21YJ41u^Oz6SRD|@ul|N4_RJUpHksjiC3N@_WHRL{55DVCX4vvk{> zb(TB@T%+Bb7f~3BJhSXZ9~{Mx8I04Jhtd5%dvH7YCG9ACy;mfcchys{x(_PH8*Dnk zb%t}fFLciY<vPdflq~W`k!jAR$*wn>4lmciKuhf@C?CZSo$dqIPv<Kz&h^s~JI?Lt zIvuw^4tC-8++g~pw3R2Krk{^nbn+&9{owo0?=P4{)z@T2?hw-gQ<@bgMG7kU<W0*` zBEFq&T<v@K*YG8y-~E0E-}40Dvrp!})pP2M%9;!GeO$bAam!?XZOf$2wJm%nt1(fg zfR!y%S=lnD>l?rsW?@T)q$~Uct+P#*pvlUTC1wrWoa&?)E=+K1S!_C!S;{iee^`q1 zerAICLU)S4f<>VxyQM1As=N43AtC&aGG;L)K9f}{7cf}lwIGXBy141AG?}f1CRU-Y zO!ZfpOf#QyOw%G0%TMMte4$pB%v@S?iC&}XV-EXFdjZo{D4cEAl6*?bNT#qDSgS~s zXklgLbgdy_+~BVvnaC27DV<A56he~8n5y6UNm@XXte)(zAgLzqMJyR%P2l2cYl<T1 zd1ZoKFfyGt*iyl2k!kwnGHXQUbgdETn)jSixe)tjF1*NJ7&5IQZr6p(<ZX7<oXiy= zu1yKu{ot@6+<pAw<PN=o=K37I)tsW{>aPbZTAC`Vsq~w#x^Eqhswu(={tIkBn5K() zcw;P|`8>Bcxq??i{<Mafe)3XZG3{KxZF0?a{_`Wo>s<n>pD(OhVb)QaFKN8s{SsXN z8}w&=uD1^fyiRB4xa+}>NtlBdYTsz=)cZ@h;NvB^^dBtaHk@phTa?S5kzjBf@4g!e z&bQSO{$#=j<)24>QWm++>r?+341ZG(^4o3tMDbnNqO0$wpPPO#_+EcD;q-hQl+Wrx zuCdy2P|z-&`IpUJogJ7>uCoeN9zFHj&DLI*5BGdCggA0e?r^9P`)44F)o+QMdwzHG zYwkJI^}kN;@Ng5YGoDM=wOh?M$+_p_BaPQ{>FT#ujwuY^Is05VXOBZ?r+a_k>dUP| zaN%4!uKhWCT|S)LrRU@hT|R#KY)8jS&|av|uRA)9fIb3!9Qq_Q7g`8i4Sfl^1=<MR z2R#8j2fYRjY3=B^5Ly6T1>FFB4f+<e32KI(f_6i%K>MNI&vkS}ps~<-P$5(X)j(f? zz6q^`wn45OT)tiXyZUkH+Jn0utOpn0m4mAnS585{i|^8N>AQMx<?O<_be+Af{+-;R z)92zjbm_bJPLI3p^6Bc&rRUJ4>-;W14A*?;uJ5tqj&_S2FzSzs23w3+JjLP!i?b|V zVR4bg<rc5CxYA;seLnq`^>46vx5bAo{>tJ@7T>Yh`^(0zu*H0f$66e1@naSzTZ~(r zXR*TKGK<$)Y_Rwpi+5YR-{LNdzp~h7@l}g&Sll^xe@82H^`-ke>cP)nzQ3dIy!{=& zzifX;82;r?@9!9X#r}?Kp*!GLE!f}jSaN^Il#=}&6TrV!?(fJ;?eBOHs)a6EYiP;3 z{T<IkLeZKG-pQO?al}7i!#veP*vxqU2*3WHOwZZ%kLXg^e<t1?hp=(EEuVkXj`s_H zdxp;UE2C!!dy5T!Sw_5`bUgF{!UW^)`X}OKrtv}Sdf=bXaj3e<)c?=$AH?SOOZSlv zkf+SLe!ukoPp-Q*a;P+Zj=ewp4t>b`(9ApDKTI%<uf9*3!SM254_yo03Z)>)>Y<x0 zFaM1h{Ab`lpW)Z_uLNvef~?Pl9UYfLTcFdS)vfzGs-D~5aR8hKt^=<J`@Ofn<7}wx zKSWOd<?rn8sDsx3hsfz4cJ_gev!Tl&@gIVcP|H~dI;Nk0pyN_#79@TlR0O?o-hqzE zu>&0!LRUcIlh6vN0eTR64En9*Uj+XKc@qwFR6sRQz2(0Ku7mD{j+=O(qX0Sw5<dyL z1gd~;n{uFIHPi%&{~`1Q^n2*r7aZtFL-#=9e+)ehy$D@@;en1Dq3=NAH$eA7PeI{N z9_Tm=x)T!rA*dXh23-Z+WcdX6Cn#_FfsPO){x8r-_{*W&W*q1^Hh!Sv3`qPrU>sTu z70o`-@l~h_`T|r2?SvkOesB3df?u3<pre?yE+PJ6NPHD|1GEMjGV?&k>Ck9Md_MFE zr~;Y}T?u{0@)v{GLoX#M^MwaGj)4-;fZrYHxZwAcG59F7`h^1>t<aG#cIRWSkO%10 zKOg82|HW4gKMnq)$QD6Gkocw05~u-M0e#-`H-R@m-`z`nK!1b8|Jw4$y-ge2Zsrr* zIM!jyG9&Ntx_xis!qtD<jQ<*Jyy*8#{IJEil}D`q0)Ns$Z!uX3&ztqmkA5`!l3Kx+ zT@}lh@ese<@c!~FBNf{{_H6%+Rdv@FR^2ZvnXYT874nZ=uy9cq=f5%YSA5x75ccT3 z-Q>qTH^hDA2M27j;oS2;QBnNTi>J)ExM=!>IWs0roieMaNczf)`Ea1~`yjUa`?HPb zo*z4`-_4_(Sjzm(i4i7fx^!xxPM6V_oi&ej8|7yEv85^B!`vooUgxpKjPKUQ)Q&&1 z=*)3rQVYh|)%s(WR4-e=R~hsDw@$+9ss%nTVHK|k=k@fqbmD4L9!u==iF;3)O@1t4 zf^zqs^n<=%y!*_fZa?dlrzYKT`I57K@`GP|G?<>7ud(JFG>;Z}z_h<0n#nG;J0@AX z&4qQYZAc_^Ig#+&Lhb*Ud}}USZngFE8o_sKt)FQT-~D@P#jV3q%jLx$uGQ)-F>1Xs z8jhY}|2_H-^{z8>Hv{(7yu*|iuQYt;-ZKQ{?tMbR8WTSDgsCqZ&Fcs_|Do4i{8!@O zURjfF$TfDQEgq!O-KYm$`1c4_+j0t~zRKFyXtBxS4vWne74O{BFDeuAipQVB+q)*$ z+xsQca@{q}U#L%ttj+b9TY&3|r#XozqH$asXRrAI{)vC}cbf8Tw^%#G)I(k`BR5IL zyv$;rJGhq@$7b40j~W%eG~;@02Cqm;!#OoE@i9p0I;VG4xpld9_aG~P-wjH3=~Fpg zCDieBj+gvJj`vMy%dc|0d!dof=6L1M{Sa;0f49~mWP<1RH*@?o(z9kyp8dt4NB?TW z&SyWl`L=H~pVEHRH)XH?JLi5OYR0BY=9_s^z4sQ$In)Gd?8iCYSnY&>T)G;lG~YjJ zNrl3;{2CiTCmR)wdg0a_??xs+jf@Wo$`&WfS9IBC<h8`g^%^LOv1R%=a?}|J@aKA( zKOGy5^0Uc1d;A(FjGZ*AaMavM6BMQ;Ad|4QyX-QBYwISP=EvO=kV)7ckWE6iue;1o z&+C<G%XB|I+E9jl(<e%AxVy}co8L_q_v1#o%lvQ!-DHJ+xF|BsQJOL+^uxuFHLpfi zWj`#n?Z@~>70M2^UDcbFRY}IZ?al3yEvqcAS)zVj_h*gj!cTA1Raafb?i<KU@3i@= zs#!2<+63R8R5#gWzCE>>vO*KCuA9t+<6bo7Ra5T&wlv^R$R7Oq-QynZyguqO6K5Z? zUY8Q4*5viQ1G(NkieuW%X|P^Xhq-^O>~zUmk!7^6agvp?$RURxFB|Sm$*3@IZU~tz zO5L}*7k&RI^BY))tU+PsSNnEzn3b1427k&%@)P>k{Bz^t|I^e$1A3h>Ah*xNoJ1%d zI=t@*hv)V`txvRfs#hYnEfnYd#L&Li56vAA8tnCo=B5;iwcgy%lQ-mgp<%tfK@<B$ z^HO~ieUKm5`|BgfK4%`%NURfAgTK!DbzS{jco=I$8KjE8eW<zk4k-VKqX%4nbnej5 zP;X##K&pSDUvuA<-fg+<PWLY99@)<uK5=j~oH{&lSo6S^{%v{fnKra%*q}M=efS#! zHt0OxICe<qb9P{@bBN{Fxx#XfSpSIO{pSqN4X+Jaq_UX15qm})+MYVXI467R@l#~J zb7i6h3XzD>|H^UZ+R?`w|3XmiCF?(wJ$pWYJ>kYKd&K4A586}50*e-GQh5x~Vhdah zlt0uz_k{kry-)Mo$e=!F49d-$?^=k;q=fWBgR{4fl>yzi;*Xl-{ohxmU&qbWVs(8= zHyW-K)U^>=^gVM3eeVVI_1bi>zUWE3Tej&R1}}F4=<6Hae+qSfZMM2EB(3+W`)df} zoVe@o$E?8BeRwqYgVk4Zvbk6VN<U>ihqAs!WgQyWd&<DvzE$pC3=QwU6x&`5+Qz+n z2KVx;%zK%1&LEvxemebgr@&YF{Na745VopY*u(qIL3T?wSr6ra4O#CEY)F#E-QCg{ zoOj0H+=27+qJ2|+66UTZ{u$(9{y}*N_qslun-}_!N8fbjFz?J#_Pk+3sgv2HXVj`( z@0iA3-Vr-`dxzgWAlg6GFOk>Wr=?e0PJ5_tZ?A8(PpWr9KFX|r?o#sl3qP;nT&4Mz z&-d=-^^W#R<tB34LRQxh=^E7M1lsdN*Pe-=B>vrsGa@4`we#s)a=j6U<#_a(apzY* zm_rrTa831l9oP%p3X%oixq6pJb!?>OzpjZ^5k6)_=Kt?={Bcjy?-=vp{sPMV&H6pA z`*CgmIXFxINm-vjSx-EutOxc!fp&FTXS?d1<8gN<$ZuzxQU1=^+C9JWyZn~GOMeZh z{H_MMrF<v3%cnzo$=Hmip71dz1j8xLNdTKFP26`tx!%Z?hOR5!ppC7v!`6xWP4c#N zzX$X12W<;qNuOiWeg~90VErQo^g4sz$@%&PR5`?mA38kO8!)l^Z(nIZPYQq3B<0&G zyVK~2LgEWSxy!6SG(7M6;kkoCM|fR#O^9O$a#EoL_x>5w(X7roI<nvON9G>R?_}oP zM41fjcgE1%!)Eo5_Dkg@`Zl}Y&X8>vM`mjmgZd<C^AB~l`GLKY=(@X8*YN&H+RZ}; zwHuq}(b>}Eyvbht#)q<uA)GB2Wr;^wFQu$sbY;C`d-t*qmb>nPHo`ckw%3l|R$*tk zxBbk7r~{jyH02+%va>+7r5LDs3EFVZUGHy031OU*4K?_KHpHGbA=;kuya*&+#h~1` zLD`VD@)3jcrqEYjn_0VRdpl*z@V?&QiG!j8Qv(vgvCnH6cF3Z=_+y})yAKmT_q;l6 zk^WKea#2wJ(1`xmkH{Sy%J;grphJy0N}A{wdKd%AW?gsv#7cPCu^QC8)lM)j18~kh z@v=qeoUVzQ2p@AoFw*LIwP5sF6X&X5n~T?iO5+LZA3`|!72Z#)V9@mir6WVf=8g!R zofB-?^eeV)56{v@m8UuuAN4d2{YT|08`V{#7BpF>H5kqK)7J0GCh>w1G=s`d_(jGJ zyAA)--<fNBKwbOX^Txj&l>dx2llSvL`5ypv|BSY2PPS=|IBwAO$CVECG!8$hhx|9* zP5zG^;2m4q%NxG-;Ia6><C@Az_f`wKol_fX$6pXK5`pp-*<<`|pk(n^j6a1xPMImp zxi6W$l1Kf)gxU78DUbbsGX5h8t8ldatm7=TgSp?M9C~Q`?)RsTFwTjq$A3`Y@MrFQ zY?QvMUp1k>0KT98)(@cHyQjPUJp4iZ4X;y<*dTrP{l(~i2(<cp90SiKt@q7;Kz}{{ zm{H-^jdaA@(T@${pMAqz+zrY<B4ga<_kq$m*o?azy8h+03|(p)@8MVfAm`eK_m)Yw z=}r0*^h!r7D0c=jg*zLR{F|WMM(YpS690bBw$yUXIoVQ=KWIzcUyYt9$aj#w&bgr6 z_d(h6V=F(BFmfl_I3u!*-Bmt&Wz*0;-r=X&v2k0Uc+l_4Yvd2QY}CCq8vl4-OILZN z_Zqv}z$UA^1YYj5plocgVR%04HkOb*HKf_o^Wf?X{dzWDk3Rv*x%!H|!+7v*W5cz7 zGuQqIs(v2ZXZ*hg<qwY4UX$*_4<4%x`(anU#f`)tHA!{qZQswB08+XSgL2PW{|Gx) z8{&11)s)v1>4c65lzqVO%6?Ub4J!La{Pn(;uDqvxYp?^19x%3q?Jwg-P-V0Y^z+`s zvzYA4-+^5{^iw^_ot-h_Iww2h_~TaK>ZmP8oaaSyIm;QVWKT)R$o>q9dlU5S9Mb3d zA-VlS!#p#>lzjt##28{|FK^&1Goy4UStuji2aWSV!zs^wv2`5wDA(oSAm!`cgXggj zcRJ-S+{fg%Q<%Xh-|{Miesh(!u#xmtrXsa}$y$)f$*+DxvVF+%zy{_bSAlVK%>`qS zb9Gkc!ZmO~XP^hewOE-8r#9D=CA~H)YoktOe}l&Kgxv<p>AZW{rgQ9dJ%@DVbZ)#K z9#kHrPbiy}GubJWPUTN}gl^1ra}Q&Vozy{3V=L90w7*6e=fv$*y~${tuFI};Kh7BL zYi#^np0W8>WV+^T_(l&lH2k<{8+x+sAC)UFGI<BOoKyL<;!g+iQpYAsvb!BrPGW}} zjA-+v{^r_Mpj_X9rrdS*x$5mDP#Ol?G)8=Q(DffK9pI1qM)Z*X@crc9ANS>W!{_%j z?)$&{n#y1I#(47ToZdy&;%~G9m8i-+?6*4Fa=t0!mLp8rHXLcnHjZ4WwBuKqM?tv+ z;hLx`rEyc(TzdpmJ(mtPalQcRzF9QH_~#5Waqj|^=1)O2hxl+)=N@UQ@9G08U4Dy~ z@?8I-d#+cX>ivZJ>tSxTfNRc)i{ejNfy;0F=#ZB<%B24=DA!{BBLZ_2(pN(oBj~dR zp4Qc`OQtebd8~$4DqBJ2<r%O5lyf@8D?dW#bWPMu_?Q!dO(Qu+9<0Mo#mU1jHw=_t z*Ch$g8|6QM9d_Qxp|df@HRt^D#vimXag5Ou1>3EzzVMPBXRn<I%7zg`dh7S^EjPcS za=KOe^}A-vNxBvHz6{;6VkiDuYmBZdy+nkw=Ra)HJ_cSc3i`V2{L3+(YX#;VPF^$m zdU%Hu<(hNi#^Z0Z0;jY6xRBR;tkE$WUalIH&XAp(niH6t8Zo%{^@DT!@jfYsnKWa^ zO4%{w;PKf#0UPeaU*~J-Y8R;!uwj(Zc^<sn4A9SamN{&{{XeL3?mj+qWnI8E=fp+v zt1Zd7e5XIceQR~RZFLSr=IgX`24C-?@4QF4dw6&CkIH4UY}}n;a~}S^_#4znah*1Q z)Ray1WYZoSz&301Wc%Q};UshId!X|2J5cxMSvKw$L8bCd@Pp(v{m{QYue(UoIpwtt ze=x62+UyD!iyC{=U_1Gh&4pvkwR^^zYY&2w{~1(XFS2oO0~K;JsO!PLV)Db?=fUN7 zeMLFE;$8!)8rFlsz5=nIuFyHf5j7A#=7eC&sUh<`2FjLMpxndOuj`VGuw&{YGRD+< zWJ{JlCA_n%9L441Z&ac<r6oNLr&DfLM-*P}Oi<U{7^$amS~=<U)SlhFc5A?f)%fcI z?Y3on$cvq6bT)m=*sv3su_4cYPn@6A{rRg+y7jzO)%EP_bml$MJ>OycJHmd}I`iFp z4$oO1H#(}|<yL{x>Cao29_DG@{lkI2jo(ziZyW06_<bAmw{w`kT^pRgy$+p+n!nu| zu<;)Jbd|nyb(5GF^5QXL)9awzTNA8}{ur*WKZeWt4rp-R3D`9;(=Jz^J@s4XJZkGR z%XrU~Z3$tV)0m|kfAHRn&t}|xk+JhrGmYI<pvv|~pECZ*gpr>)x2*3R)Q3MQdwO`5 zb@uGdut%1?hd*HrbmiFeLXOw|ym_DYlQ#7LtS17cKXgpL>yODD9vajuv!uAs>~sH7 zf#?3YkJ)y2%faogj%&_|tH-~~3Y;zB%R^q`5|j6rFEeG~A@j>(K(8sh6S-C`OZjh> zt}JsJdjfje@y7ysT1Ycy)2#;OKIioG@S8b#7y9&@IjbvgLBGmfC0I^a=X5(?gMX?O zxV%O`ZSs`>Bl8Tu4b=6K$mCA3VHi^`<z3V-GRI2IvL|Cq>FRt>hHhEWj=$7Tt*c*a zzJf7>O?v{o+$TW4oc($45Bc}b`91uGkA588{~v$;IaK~s*8M26LdwB8b)l>Ax0>Sg z>rv$y`-~AJ$_<7sHZ3;(*b?*n;^9}H(O|=S$Q8d1dAP{%4}xk>$0W=%(&s_N`RD?Z z*8L?W++7Qe|4~qBJX31g)hT7>nQ02B5Kn`8R(b(cyL}hT1N&JU3P8no_j_=jZ|W1Y zgDmrUN;}0h=fu_GueSnSSGw&LA+N#a^9*>ovq4|y(0)nY;ojZ-4V)P-@08vn^Sq&_ z9UeU_H7GH#*}MUC_rPlb-FxwC{zuN~Zho10%0HTZ?uVe<Tx3SKS@U&b;CW%kljzm+ z!u+l^U;k;pYm05z=G<X01wUgh*A!`2o|hXz+fsvd_?7c0sMsybO#7+>)vm(U9|sk# zfq3ysQ$KZA8ri*|+RRbaCXLH$Oxz!Ua?gN@`+HDr?JZEx_k&1F@{>U6ih*j|3qZxZ z5%lX~K(8eI{zEyo-`_#L{C<Ca_jLzt$ek0n2Y<o}l%CRxtYADxUd1o2HTGQz%5UEH zmH7SM6Fu~M@n4|Z@ArCp!yoEt-siv9f@>20TW0@l6aRl}6Y7^gLcdc>pW)mEFpa<D z-J&``oa>j`9yNVY3KZA!i0K=npklY|G<{>+k4#?~1(ihG4{d*KvEj!ie5yI*@0$|C zKY(zFdqe)dDs{xu9Bzd0>U+ZvguKI*4%dZEKQZ!{#ppxEU-z)FD@?lDFQx5%<4<#4 z`?0j6M|MYd7;LhbBAoVLX}ZtGC!J&P$E`o{ppiFQtizsTxgNIZ*OBkz@wZ$39^q2- z;p(q{3d)VYD&(JI{n2YoT9uzQ*YEjU$lqV*9Z+eWS!b^Q=-QAM!N2m0A^#hWbFVY< z+rXoce+N|hgReKXUkr{!emf}npKdUDNPa2gpNoD|eaPR#<~C5*-}tgAlNDbH`QNCl zy3wS6J_m59?5+m2)cPS%;Xi$ovG0kmn*5YCg#7P)zIaQ>KWFT^ubVoUcB`q|ao;fV z383P?b6d#YtLOD^h5T~@&iFRtMbdv2)cTq7+f5s&0Tuq(|1oW#?;Roko!(KP<Z)1K z<FlZ~4|iDk*zcHhPEVU~PpmQNAG5`@#Y;f7#RgEn?^{5n_ml6Mwt4&AA^$tLcfV)G z3E}UD{C()o24&|8a2R+CsJ5{MRGZof4h7Hsfw_JesO!r?#k(6+i9HM|{O>@8??u~I zTOJC^j-J*B^m>-x@*d_uM{~_NapUlhw*u)@y6ulKM*@le7AUvI`bXIHV@CznkLkI! zPI`}G>^dv^*tIc3x5h<H_;>lKcl8(1Cm4tAGP;X@YINTQ%9hs0jejpFzZ<&`3p}g$ z`Zad+Fm@l!HRt^N<6jl9A@Q@2*Mc3=QTn9Oc^NWar@yYOpTDkbc$Rf#Du=Do|4%+c zs&7^Q^-PAHDyu)>uUF&1$s*a(-ePQv{haw;Yu~4kNqQNmFh8|nUa<boZ$hH1g|_qA z+1>@R9(4}S<+r%!au+t+b92bfP3GIVN#}R(|C!6tn!~q(&pU2j>f=3(Nqap<IsS|1 z(?^r0b9&C5jKA3wV`sUB-!NsD0ClP1b><XaG_p_q$pro`DDE*(<$U$4#{ceXrcYb{ z7bCxdxC&PTDjajoo$LM8R(2?lf%j6*ueKm-+cIocTiAzxDk!J4m2ToKW0z+!26Fy@ zf9=z68XL+$h5aL_Huk3VySe2dw*F+-<lkUdmbv9}<j#q!!5@6~t@~@pt3$tZoeVE` zDkwke@2Bw2@>=({YQ*rqS`+<N*DSAj4&EpIfqVEL^{f2!+!gt4m(6;fi{THxV@v<9 zu`_0|?Qf=x8o=<|#`Y^gm0KEAiuYOnBlh|QgjFgvpxkFc<@r!^ozdOovxm9P|CC(i zrr*7}*yfzdtsZ~E3Y0_TE%A4g*D&b&-!)|v0TsIqxnE{r8)d67_4`aYr9rtTK!ut2 zj)`*vsCwHAs=W`eY14OguG4qlt_=;$V}yT4c?RBl2K}y&9Zy*2q;D$z1}kv+OCB&b zt_I(I&)EOkeq(>wUK;@_U)kQrH^`nI-pB9A&@W4x@uvd%iz%6(+VoHBFt$uX=IgWl zce026v*!2c{ulM1|7bcYW7XYiY;aEF^_}=5R^-Y!)z{>wp^rh&`Xkog#P=!<HqT@D zUPA6ReK!Gq2UJ-!=jQnB?Wdsp)xC23HuO3uJ^u?zeiLcQr9t^wPdJA;n6)`}4(6Qa zDYGnd?<L5c6IYHuZUvqB$Ybu_+I|VV++xtzljYkF^|MXV7wG3yc1p7~ptB8sqn~P5 zzyI9<^ws^0j>kZ`llvQgXmDPVcR>$jzTp@#wBHow9;<@C#j+*VhAoGfdo1O;bK+9? zgJZ{rq0BuFG5U+)<vs)2vhMbtqfz>@lyyr$Pb>bIpHf%8n}_9ioV)I)`we)xJHTvu zM!wKpPf({~PbQ3W>I(|-$E?8BZ<GzIm3|!LjCh~lKHTWu2P$01hT(Uv+jo<^=P=`` zs9Ku&jcb%$<~>JLGk&h#TQY2sRjv4^25hOzM=vCMrisUW%KF{7I6p8hzUD>r1=`yC z`9}o8tDPvFp5(#wlqda`te0-^8BpV{@JqQ~6Da4(H+;0oN7G1)_?16}Q8}~+{HiDQ zF$%qhboCph^^-fmC~3?EV~}$xE91N$Kiw)u+KSumkGR=G{3z3BECXdzD=7B@C_9v< z^eG)7&tC_91F7=4mAnO?zk+_1i&DRju+E8l62I)0bLA2_*4R-Op&$IPv1JInuIGcY zE!%gqqyK;%f#06@^UE&TQAk|pl-F|nQ7dqErH(Up#X!kCFoCVIXEUg)kAS-NxXr^a z?e*X~?t5fwmcC$jh8?o#4_WMp9#21tZt4A#wdpU`PaPfft*h#2{2xDf9VH0soVXPJ z;Qg2wWy+!nOks~~c?(qD_gO!A?%w~&hAeq*3FvFZAF~FyvX6g+JfC3n>;vTvSbuOm z%fOe>6ZmFw95Uy`orAx?3Y?BaK@MjE8y%;Ea_3mT>tlk?-fhwq=rgkQ6$;mrJQ)A| z{H~A5`xCmcaXhH`ni{Y{M&n$bA}8B=vZ!m4#6N1}@~iGrT$e;;qkcyGbkcKu%qsk8 z;spDcHY;ne<+KM>8s9p}^cfMZ%a-#&=}{U=U-5+0UFWykYNsvAQ{ef~^&R`T=A7c@ z?K$}OI5FDfD+;!eC+U0_lnqDNYe$2Ucm77Dx_+bPl4j<+_)Ol}QAZf(WJd%3;QbMf z8atX#!H%)k4p8n2WU{9OlpUd5*5*g`RV?*3>*(uev1a+A+JWBG`a#~rRfl=!G#>7q z$-<s7y9RrsT8H@WwNA`326poc*}g+n+3tOXy#0s9$Nz4<svG^zj(oMNZU(E2@h42N zcit26i;SRcx~Ydci(yc)W1lqgh6_!&c2M=yj$HRy8f^Qxk)3%~jz4Gm+Bv4ZcYq4} z{JF+|*?Go)!US`F-3;pbk0%-bQ=t5RoM!wv=u+Dl4a$}YpmfdxbuTUg)#h#h6_01_ ztose9zDNC;`tIRbTjeD^(+T68xI+8~{qB8=zLzqSzNzqXg`ltBK9glzn<X0>WkU~b z&e_lsu%Q)yov)><AB)XqEMRpW1uu67m}$c?S?&qhFz>aVZRp9ip5(Gw<#ZisI;Zj8 zYW!_hpmLR6Z5JCm(-vc3lePKjIi{Xp1r_#PP@(e)qxj=M$=y8U30d;yt{s1fYuUc{ zb^S!PxlO;k{?c=KxjMU!FwSYbv=TqlZ@zQ+Zz*Sdw7}%!#S&8%&nAt3#$w}tm@vwx zv#F<c)cyf%%74AfCUFt`b;hKwd%q&b3okJ`PluPA1o}2)%(ti<<`O^Kc%?3&ryhUs zo@u+1_XCj9{SGL%-un67>9+5QbhSuV#u~6pUS+Nn_GRc*ACULP!M}g8YNM;Iim?nN z{e__FWF08iXyrZ4DHW1#wmBrFaShj;6L%f{;Fu#(OMaFa-8U^aI&TAIi_;n0gK19| zok1Ides?d1+q-Piy;y+1&Kl|3V)SZbN6S?P(_k7qWz+l>=H7b+RQNYQ$y0<;=sHk- z<`)iH1Iu&B9Oie|y7^tz<*n!sK6kkL=DrMDW%W+{@qn$dI?C^JM&}TCxwAmoz<kCW z<}+@|HlGoG6TOF+<~j=fuVE2gfamFVJqSu8+XWjRt+)f%~IY`VK$%TZeQ&Pb2=| z{Sp2Wec=rz-9LeH?baWBR(dU<C#X}ghrQL6H{Bbf@CS9aqf_|^*XMXK%h!N%_kybX zhpn7)&bsf2>bI7(4^__T3|nMXBmM#@!MXMr|1$Z<4%u@Qyxj4i{O+CO5n0|j?v-x6 zbL?Tg+{nLnm4o`_QTQAE)Vp+tSMpo>6{Bm#jYfApDBXXv{sDyXZ3*mOQcilo@>ia2 z&Cn|gR^v|v^rjo=dm*J;0?O4|e^2j4+NCe>d=>P&{xN@VSKieI3h>wZTDtPycC*nR zy@fHH)jbiJ+=HO<(rUvT>ODpsX}{mtsxiYJS=NL<0m`{{(0nV;&)A|e83QkO1t`D! zP3&n6+qk!}L1U|Hv#ekH2j#9WokyC^X}np3Kdw#{=WK5Hma)(KwkfX!82*NleG`<M ze4Dve2r4gs_@?nsT4nmwOG!iNmD#jL*l(<Y-y!IJdr$eyKX{LrY%<ptM*XcTAG#05 z;cu5=IG2yQ)yCF1m?jUhbJOic_82JtNnBI-@u2*!PNrlTbII2HeYQ>}_E0DPmG}5O z(9@XvUnN)D&@*9-I&y9V%r5*9Q|+B?qkXHX@A!68|6x!;TDF<zvhWszb@<i3!h{#b zH=E}*57hlrx5@aML0ym9@GZobKTSBbt2FVoRwrTgrnuf@+uQ~JV{E7aWy4BPIcT=> zx2?bQJ0^`?V3_Ma2bK1B)8?7+Wl%aNt;zA%3rz=g{S{F1HS2TyHD*7$(-g+I4LSb$ zn@@lWR|@J`bTg>+NH2lP<86&O{v7u>@~Ez2HmLPS^FhT6&I2~R!#$E^+|Y{LIdN_H zV^-kq+0<Q(ji?Kyn-4GdA<)-zh-cyG-_iL#&%$#F<D9qz{@@tG`!4UitiFX!Mt>zJ z{h`D2y7nk(Hv9Jl$1+=GN0z)l6VSaoi|&@Yd7gr#>pD>GE7l(>KFTXD?eER49pKHZ zALvb4HOM==@i6by9fx}-?h1Ryv<~)$hWw|<c(?rpd-3jhKJ#$)IX>HdYU<Z^y!-F( z*K~jCce@Q;&S_4^d+*?PCv875`HzEY8)@qggV7(GGAak<mRkQ&_nG_V8Bka9?>BKT z1y!bRgK`I~Kcup`ma?(m90%XMUxQuQo=IfSdagMqZY%x<D{yT&{03v~*R5_)t~WB- z;_IG>?jH6{dr!I#v2WU_{oVDC#vk)Fbp8Itni&T^VD#m}%MAg2{e${;?T6cJ_nuVy zcHbeo_I-$ZZaq5R@1A=yVCOUVgZEt1BfNin(AZTEFLx{G+c~s9?b@F;2(;^7S}76N ztZFFS&+~VQ>=uG@=)<0E*V=j4KJ;Cu<@l=ovLC{?Gl%nySV=$c@NwjO)_~L~?gMH6 z-IF=qi$BZp_Z}A>@l=ji0BJ9B`9mhHct5qD;~0YzPj!CD(>dM?(2ocw4CSy6B#s?} zdU=D!_4fwNVqc@-tS=vTuQxoj!|S7K{d;-+$Mw;koT<>TJa1S@UvF4pA8**W-rlew z+BMSDr^9HD?jYmV=CaRo4&O0#bhU+XdAiQL0oR;cjV!?&bX%xPc0s>hULJAlGtwI} zpqJN#tTsb74p|ymBqO~-WK&76G$ULMvIfG%Gi0|Rt3~!fT_4|eenSTuJBL}^X>~tq z@kNX67T>cN?q%W~XK}p6xW$CUWfpI+c&o+r7Vov#YVj?Leb|Fl?g)!xEuLp_uEoz- zthIQX#d|D1V{wngy%rDaW9_#%-r{tN^DQp3Sa0!Gi<>RpYw>Z5yDh$DF{iK5Gt%Oj z7N=Xh!s0TED=n_K_ydbOEk0#&x5c+C&am|tvGwX%|EshMxfY8*u;>f<S3uj88)9*T zjo)PPQHw1WU$)q8@$VM>MELhP+djT(QG2Fmx>ekRa{U8#Tx;bn>{Zqt=ReF|f85GP zS^sGkV-_#8SZJ})hX1O?*DUU^*PpT2Zt;M{&)9HtE$*|}$6j}K9bx^A0eRe(OR2?W z7Qbq7qD7YuJzDYN<ir&^2s>F_J)0Ad%adHZq`0hRW`&jd{)|u)%F8{kI90}ZIXXIL zj1I*aQ@w%{e8!ZYI_9)7(Xk~8@lwtwk~JLjynuwKCX4CkKA$VRpel)qYkhter)zWa zw0BdV^6Hu@&St*E^J0oOX=znevcg68cDuAXP5NY?OUg=;lQ^xsu<|0#LoS=Rq9*B` z&_|YEP+4ViGIRcCk_&34)Ovr-onBd7GTofwGl6$92kV$t!X|u^s*=Su$%~e9d<3CZ zdy~p5tL@3$-XD9-N|q;!tIcJ86SC=I^*knCj8+PzL`QL_YRW1r%;DZu-sax3lQo5< zXepUpQ@mjDCzcjh6<5?yAd@pJG67g!uh7$rtL-7u-p4b-be=a(iLI18BisV{r&KH} ztE#M6LMdL%(bhWh=n}6Zq_VACvV_CNIoKP?A8np>wD%=mQX0iht5{fBwWL_KPh4TF z_q;2WZjV>14sn$#@YaSdD68O9@~Ub-jVZNQzS@`hrR-fQ-C3=^&QF=6&f~>3r877I zi<na+v#rFG7ZvEOk@TX<lBMoEb!su)C6nzo{`xMd$sTDw9irus>5FKw#fzlnYQL45 zHhNsI&URK<=^CGRMIZl6cFvZsu2c!;<YK|>ni5xLqkKEfRScu$7i35{5}kwICoxJm zEM(fAtNUVw$|#N5H9DTW8k>F*(jAoTQl;W3j}vMLJ)c&r@H0Y_m!`_g7SOP*QQ!4w z0-SfAG>OivDk-Zd=8g^fw)<z*XEd*Ql3qg9RW7g25PKnOvAxC7w23>U*gtn(_5FD7 z+2zS(3N~TNNS5JS=;y4qjCNR(w2^re(tB1-`K01h&C;qQh3TK)5|(UwW#!_fsS7wD zOpS3$MNQQT&pT3AE-tI8Sz25^r$Xm<dEO_d%$hM}`st^c-X}k2Mvb$E`EP}vLA?~$ zEahPP$(7aqvG3mY(2SaysbqyrBNRSU3u+`K@v>CXzp#<C{95*X6*Y8kzLImiCCMcV zQrL5tS5!2qw)TQ5;+aCOI-Rel{p(BU`9hv9Q@54NJNpFBJ5SfElQn!-BW_9MvLsLA zq*PR$N-ii{Shk?3lpD1?S>^HNR#DLc(<v4uUAMxwP+6o!3vBsaJZaXP8HLj>nsVwm z+b74oqUvO6(ZaIwnq(E}B)y4})D#t`Qbjc@Qb}e3iX>QCoz$JcnQZ9J_mt$qvPINQ zQAKeTxhN{GTC~jLtoWj`in5xr;_|YqIxi)=dLCN4SG0)qZ73HzGsu}H4Gdc1eXNTw zN_um=B+Z0bbq`Zv3XQD`m-zQlFDy+J70HqXT2<HS!>%K+*u)(UXJVi06;&k{l~pU} zHGUUd#7|Fgxr(8Pn*$9+c+2P0FVh}fo=s3cWK!v>v?`Lzi+m%fSJ|+D9@V#LT(|P5 ztnd@{kDK$nb29YXD+_I_=G(QR1y$67Z-uwWQ(<sV@{5-5E%bjcGk<TZYQ|J8t*9wm zlGG3GsrCmq5JI(=dAZv!HYspuT3KagfIxhc<IDiT6iWFq?{>Ye*Qb^yt5(F5RsN4h z#RBeR?4<dQ=;~PtF?&L_?j&*Ew{oX-HlW$lsU3z0CEi`R)2b&Y=PzBfC|MO}WWtY_ z_xIj&D$MQB^}9+kO5XdSemi=+{6kW+9ZhA`1^mL(j$Nu<BccCt|L-gT%@N45{0#HL z-^q>XdEB|GrPVb>sp5(<dIGu3Yew>q{SQ9TzMno9zUhpRZ&y&iKhJ>)LOESGvmly% z^_}rr&uMF)AM>q~t>>SBpM5&=i-pg0?N8|J3Fc$$`4cWyw@?4Pure0=Cp7(gqQt{| zy94PQt#+`3N&j9OC}rk!iXrhjuPKj@RrBF>e$z$JIC!0*^yr5f6~pUHrDGzzw}X#> zZyoP>k&UD?%JVLSV(@YB2}o(`e4*th5*~RS_&C%8ud{!a7tn_{A_sp66~Z@zA32G+ zL3o|nvk2M=ud{Ow8_ila&eqXcI+sD`z$d`mDEp{N4z7n<;M>5aQ&}qv-vZX2#=CU* zI`Bt~B;JE>2A>;e_%`ruPM-^JCNF2Q@8-u0-vY+Qo4CRz%L_+++{mL~+VV}{eP?rx zbeh2_=VB*(988`^9^q5q(~w_2;Nz1_m=^Gw$?OeEm^$$LQ}HVuFlRdRd+=d!!YuT` z$HC7tN>rFS@XMF7b{xI|{O#quV^=wVkIdscD&#HTm@AkoQrQ-BuFL|@8@Pr1mhk># zp((>S_<9-XAa4g-_~qWCFy)@N5c1mz`1cB9vsdYP7en4w>H=H|h2a~(4Ulx{e3yYK z+7)um&W(ab!$-l1kix{lry$i)3z&N)<*4xBD5yo@!P}t7HulH`e+7+$Zv|hkp?$*( z-(G6k`7w-JegdsVE=(;qZA171v>v(e@fD^Vgt4nkn6H78zu<ZI5Jp&k17!j)?E4kk z6})g(g9$Gje>3H<oq0s?$Xh4}c;S9Xa^Y9LZpu*j;ad$aeCHeFi!j2ARvBLSwQrgB zAUy8dCcLobb|V))4Jj|e5#KTODD=`MjPU6-rcDZuSZ8b#Ubo(a5x#M!;e}6aG-V<j zcQ<*%w!6VgzGuP+PrS$6UuS~bA(g-IPv19fQn>90w0YwG20V8M-}k`_)Avyy@WMAC z)w^)~{nQuyZ1Cn?rX30=KWWOO3DnsxYBR#CInQM!>FCUt-#}@2odfeWv=!caigJdO zzRqL$Efl_sFyL8FQzq~_vt<qxg-?Jh#FM_xPr3Bx+;@_LYoR9i7I4h7lur|F5<I$< zbl@Z4`Oswe7`SLR_Z_^>?zrLw*5iDabin(e@$k)H_(k52!V9NDrSNg^!+W@PH)(>! z(0KS1_#3DYUT0o>;uXpeUgutX8(Imk^CiyyGiCKXYyj_oV(>cm;mfa6KJYpR;&rH5 za`2*d!rwzW;C3hmuQL-G-z2<v@LlL0c%7?I0Nn?#vkVIU%Dw!3c<^c{2CwrD8lkE1 zI!oZ@y|hVqoiXrR=oxsOGm!T-?FU}_{-5&>>u!I5&EQo~349&++}}-GY6mC3XZoTz z_%PI>z5>+#{o;kMK~EA_`}!YyfcIwb+Ef2yUdS8wL&AXHfa36J@C7IVul?*>dH1jj zUi;kV=Z3sJ@DcEHkhcSSz~4Y4=?~k$>w2?a2XgJd{xDPzuYJ%%eL~)9_%L`O)C8}6 z(XWQ?gV)~Zo1hkW?T`Lj=nwGPBfVeWkhc$BdzXKsA8Fo;o#0=gt?=6Kd&ptzW%1)& zPy2sYLs9rT@GsC*c<mFeJ;D?4!fjA3y!N8jp455w(GJ1Kp(woev;N4DA#Xao_O8AG zDur(VUxu!OZwD{PXAfd{?RUKax({A^VspHX*9xzFvg6QRg#j-<D&+0vcSn0kUwAZO z?ne$@3AMv(|K`S#*v$UU+OzplC;_j%mB$=II`G<Sxd_U8fONnbC=6c*uKqCfqCNz? zG(ue{47duahfjm|9!q$_XdmO3kHa41+SmBV<0)tO2>63h*xgJRaPbM`9bWqmZ-DCH zwHNTWKN9kuf!E%^zlGZ2wZHDFk7ECW$iY!3hrDul?ZJBkbRE3*;e82O4X-_Fx12&d ze26rKQTFYJkAb&ADfl$_SEwG|JC!<uA`eqX;2)rI@Y=KWi>HUYo$%Vj^?7JFJY!{V z$eEN0zm*YC`>LuR6FxE?ekbXG$A3KJ6~b!|)bE}X^6r7xo~XUfrA<l>o_K!9D|iGR z{6s9|O@`Ngq@SKhTzKtIx)w^oH-VL(2zgJ!r@-^4Qa-vL!M{!qd9NYYo}g1^W80&o z1MY%G!MB0y3Qd?MAtd|TLG6_}>@jqKd6$R0(eT<s^Bia@y!N<!8cM@!&&wm{kr#OF zH>v$6^LAlBI1d^Hue~VmhQ`5bf5`i;pkBm-1B-|Yue}J*{S0w`O8Vdzp(woeE&MAq z9bS7GeikZ)*Zzg;pzGkZ=V8Bc?19%lh_j*l;I%j6ey9~*`yOgf#J%vsFF@hPNgw=k z1%1mC<Q<$+$)3RQ+K+DKQtW$@FyOOL0lfCO>${9F@Y=_2GBg!ld)Y0A=E7^AyGNl? z_!e-?a{2*y?T1$l)hi5mFSH828QcRk!fPKn?I*YDXOt)S8E7lK_9yz#52>T4kb`wl z0laWGG#S1P9JPb`gV&x(+n{UUwQtgxACacyU=4H+y!LI<-cK#?!cW~xJAl`|POVVh z)5yVdeoVUX+V|;8Pz+vsLcI*lgV#Py)9)jX@Y;iED|9P-6FBI8`eAtOy|e^+5?=c^ z?S}TiYtN@Mf5JV`Li*rlC<3oNqxw96Zg}k{wH%rYuf3f5HIoO)LG4M^055zC+6u4z zoGylT!6(4`p+CSkgU3Ha8UCDd0KW~5gx7vkxersP@L{kTDumbmRIfrQc<mLnVkd1v za_}*z5ng*}X+N!<@WLmcHp#)_M?>Cw@Y;WCH<bSi@(Yf6jC%oI`~18Hwf>6q!IRm0 zXD__=;5q3D%Jf;nfW=Tbd<uLLQs1sUa<2Fpagl4!oORG%_$II!^2cf5vzFH$H7~Z1 z=C9EOYTud~c;OGA2KZ)h)GtijDA@2z+J7r`4L<e^Jios!;OVX81$hjtdyc%o3*Y`7 z?GWDkJ$q3-PdkK<fTy-mh6)e<=tbhdYfqQ!U#CsNYmb)!Z*b4TYyX!|L(jl#519L) z*AyN+r``5%;9GA}7tf)Ky-rSr((u}M<qD_~J^@|@HNk5?l-j?ByvT3h!Kb0|@GaoG zkjl#28}fb*twgRpM{fKZeH6U*Ao)48177=*eC&T|SMb`a<W{H^Ui+8)0@|Z6;K09= z$KR3;I2%gCYp;%5p&jsPuoY^7*B%}D?-CbY`*svU-fqH!w?g^wY4D!+sC&v^`(S8a zjAxJwwKqmPyij{pc)!c_QF~XE!V7PN>fp7H!<ybXUMsxzbvTB#A@9L!uZNqUVeAi~ zeIWKjqv5p|#1d#Sy!MH>2bu@3eHO~|a=ekxBM0w>qVUb2_CwG!kx=_1T!&oP3Z>z_ zfjM4$P>#0`Ubyox*3)dFogbd#JqUSfW8l}qM&1CH4Pgx-ZC-mDtQ^XEzBcRxcS57# zwHLwn*%P21UV9U~0Ii1CJ^?2jK|1i-KVSyb>jm_JS3+U<I&kTcoTUNZ0=@}p?n2-9 z>pTDJ6b4)it%le4>!G7b6JFoU-w6$Tk@5szgGR#ZTlujgu?=3|t(QUP!0X%fjZmS& zgF}y@9N}YN>4(^Ro4lvMIUnXbfAXvE&998`?axct01iJEJK^<B`W4W1@cQQaUFZ++ z`Ud<A=skFS7hVDl`~zu%8=#T!`d0g-<M}QGUf*sfp+<!P3;BNfJ$QXjeJ7OvN5X?s zPGC(Dd>q^eX)a6OKY!vQl)2>Kx1rVWY48=uA4`DepJ>9Tz-G$}k0~%=^o{csP&;w; z&GXlwUN4h(@Dm@!CU|`xd^?na*EhS*L3Qx@ZuiKObG%jX`nLEpP!qhqA8vx0;f2pb zEea17jAoCpKaqE^2x^Acce<a0cEQ(ykuf>`+>3A?r2MA9$00p~w16j$HTEaK-Imw4 zwGW(vjy<#!a9}jYi^1#r+nLaG_!M};sg$|eJh*avj&~pOCh)0`V<&Qb&-tYb(TiN) zeZB&Xe1&!ep7+TdZydb73tbIOhu1ftFGD2?10FV=_5-hPNS8sk!t1-z-OyHeeQP@N zBJLk}eSf+Q+6}MoD(8QScKj-Kf;U0~|BMabnX_rD@cQ<#5o&<f_k(9#Ox;NiejOV2 z8uoy{hDO7;fyZA$zW^Tv2V71&hYy33iYRCJIC#YZ>isX&1-P@EI)K+VdwrJ>7hd1V zO|GC1f{%kggIW~^oSHKA6$jTqe)|OTue5Cp)c0>{lfqv^<6p-%@IzIUA-uj%`yG^) z9Q;@{>rCPG4O<<w6J9uNnYlOP;1?kMb~JzwT3+9p&8?*kyg^>T|A8jM>-(<T_;zcx zc<?D`2fV%kJN9bY9=yH_y98>5Pk<+1V{HbDKWo}d8hqk&=x8S{cy=B20<Z71u7a+E z*Edt2{sMIhuWzUxfZl^|2De^o>~8`$-C%jJ@0UpPP0ATO6)K0<cS(POn&I`m(qZ*E z-ZKgVo(i=o45)91)NX}SzD$1KqK?4ppc42daM)MKJ7Hqrjgaa>-~9|+N&1A*cR-gy zZSV;&r-AYzyuQ)-4mAC*lpFXglz`XwGSA&idk_yEdkZ$e>sy)%s107<vV8wm+Q(k< z4)*7JkWzSkA95YvdhCMN_apnE*WmRH$y}({+t>iEf=0sYdyG%4Mi;!k&-gs_47|SC zxC`0?ul4cEH_>1G4LNu(R0prM_6ML<@LGpI0cwKR+WecLUGQ3WKWGa!!)q=70%#w+ z*4;k>h5whj2K#TNUBPR8zt-l*;Dz6W=E7^;{tHkFUf&FSbQ}FMyw>?&38mrnjlhG@ z4tRY(aM*Ur4PM_A%z*a5>pO#+A#WdPf=@x3Th?0lMR#%Uzk_Yyz0i7ie+_yQHp6R8 z`bsGO@02ar3@NVgio5w<4Y}5hKMJjc*E;gv-@`We82Hk?><s|l4j%Vo`jB_A9~}4- z`aO89slE`J3$OLoUx!lgT66tts2*PHu|M<xX~IXrrO?#($UC?a+6k|<#^*KD7r|@o z@wLz%_$F}1gS6rOqz~?a=E7^u@7hPWFW|KX_z7sG<lvacXg}~#aMMp|KL-c{4tj#R zrVkH;F-YYgTnvpPOda?X6oYR8Z+Ozk>%mo!>=b_2^7nwzr^pN8g%cr#nGRlW`Fijn z%kKndK5f#BgL|NQ;tB`07`gBa%Z~@EEMEiO2r2!Q;MqSXFT||@zXnOZ1I+y;zhB4) zf>%J2r@&uAPa=N?3_oLd;dILvg0DPFUq_g>U$a&jYKLbs(CY*3gAaozLF!|LlPn(x zZ-UgHG=L{ON11P+Ou#gx-&Wy|AdTgk!4^pA2v7J8eIw(mD7YAsJOw@v?LpoK&it+6 z<KP39Zw8OqZQ@42^C88Jfe%6&y9zrX+2;L@b_8k6AlwWo{U-2#p#tRI?=|jbd<ZYR z_<4S>2_vk6lztuf6G-VagD*lFGwXbnyIvstqofJ$h3<j(UIZca$<5#$f1nOXGY!uF zqxpT*83p&h%)R^sVZc6rqJH6pC6Me-fsa7`^CmcKkC8{fPh0-;U<<TsH)RX{;uX{8 z+ra6s(#Cjpi-Y%Bz8M_-XCse-t1X`f2fk+HVenZ<^H*(P*<Xx21y;Ro($}{Necs?% zg7&6w7rqLW!#9BMwv%7Ar8mtw_yTyXhu2#7I(XsFp+<%Iw>$EWj)rxs)}_}qu4`J? zysl;4o^|c(^42%6FSxVu&bB-E-5K35bwgr9?S{Gyt2U%JG;V0xuwz5>hFu$4HneXj z*cjV5cVl8>>Bg43_T0tNHZMkc;WhbdB5M+BQ)}ziHmq%1+rHLY7hV@xH+9|Ib*1QP zTDNOm+q!-0qU-C{H>}^We%E^M&hVXyJG~g~p7`-~O`CUZ-nTijCAFn)OUstFE$v$( zTccZJTjN_Bwx+i>ZQZrC@vio}ym+p658<Qfsp*z<Te>~%tqHFwT~oJa)taU?yVlG_ z`<}J=>!N5*tg9t=t$yC-u1~E`qqFqRx;t0h*-9SsH<WJ3+nCyz-ne69%f>w$y~g~; zXyeqzL}P7ZLt|6puEw@TZ&P&B_)W1*Q#U0xP2C*dJa=<qbLr;P=DN-Gn;SN#H#cr> z+Pq_P^XAsgZJYOOZr|)}$=ed%(!O=-wpH8mwvXT5wmt8z#9e$q;N6PNQ`3obI=ySn zzBS(3scRE!YbmRGtlhUZysiL?$FGY~S_#T4wXS}hsUcY(Uthbv9_w4yx3AB~!nuv5 zjjfG)8Vj&3zNul;o=q{VN~1qPna!oVTDI=lTCi>WwyE28Y}>W1bz6A*-0h{?YqvLT zZ{J>USN&bP?($N+FIBl?(GE&7e@%2v1Lc>8Wv0Xul$f;cSif(5-kp&<$KP2?>j-Zs z*bv>YXTv_qE<!s<ZLGH?nBQ1HndNVaY$~9{Qk!Zw)op6t)Uqjmb1h}mzBzA8WJ~Fm zX6km&7H@0b*6`N+t&yz-)Nve}6I)BSrnc5@t=rnVHSAkHcU$SU+HLjQ(%TxhHEnAl z7j4`2Y-^`n!`t(>N4CeeC$^`yH*asDlq2LReb)}Pk2<@Db9y{2q&8ij-a{M7TN7I| zcTMe@_BE^4Hm%KDmrpsHHkrS^bbTW&KYVALwh`VicSCAJJ#BsmZGF!MZ=-Tqura!E zJgt7}#yB}{+Sp8c$=^JF^D64Ujhw~ESKXFXTbj1)qTTM>qC8C{N43;M<JM+!L_s&u z-vI^bD3-;rEuNlBdniq(u(A$28`7(&i^g;l_0gQ(MV++LzGeMB+ISwVTz6Oj^)#N` zOkEQvM+sVfYE8Z05}UbyS~^?ezRhW@@hD~8Ic-~`+Y*##in3ITh;47BCFI=|_RFpD kE(}ZO`uo2}Gc0pjJ@(!R6C{F-JJNI4!2Z|$Us?kH57^fldH?_b literal 0 HcmV?d00001 diff --git a/crates/venvlauncher/Cargo.toml b/crates/venvlauncher/Cargo.toml new file mode 100644 index 00000000000..d59a3a0269f --- /dev/null +++ b/crates/venvlauncher/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "rustpython-venvlauncher" +description = "Lightweight venv launcher for RustPython" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +license.workspace = true + +[[bin]] +name = "venvlauncher" +path = "src/main.rs" + +[[bin]] +name = "venvwlauncher" +path = "src/main.rs" + +# Free-threaded variants (RustPython uses Py_GIL_DISABLED=true) +[[bin]] +name = "venvlaunchert" +path = "src/main.rs" + +[[bin]] +name = "venvwlaunchert" +path = "src/main.rs" + +[target.'cfg(windows)'.dependencies] +windows-sys = { workspace = true, features = [ + "Win32_Foundation", + "Win32_System_Threading", + "Win32_System_Environment", + "Win32_Storage_FileSystem", + "Win32_System_Console", + "Win32_Security", +] } + +[lints] +workspace = true diff --git a/crates/venvlauncher/build.rs b/crates/venvlauncher/build.rs new file mode 100644 index 00000000000..404f46a484f --- /dev/null +++ b/crates/venvlauncher/build.rs @@ -0,0 +1,21 @@ +//! Build script for venvlauncher +//! +//! Sets the Windows subsystem to GUI for venvwlauncher variants. +//! Only MSVC toolchain is supported on Windows (same as CPython). + +fn main() { + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default(); + + // Only apply on Windows with MSVC toolchain + if target_os == "windows" && target_env == "msvc" { + let exe_name = std::env::var("CARGO_BIN_NAME").unwrap_or_default(); + + // venvwlauncher and venvwlaunchert should be Windows GUI applications + // (no console window) + if exe_name.contains("venvw") { + println!("cargo:rustc-link-arg=/SUBSYSTEM:WINDOWS"); + println!("cargo:rustc-link-arg=/ENTRY:mainCRTStartup"); + } + } +} diff --git a/crates/venvlauncher/src/main.rs b/crates/venvlauncher/src/main.rs new file mode 100644 index 00000000000..aaf584dfa87 --- /dev/null +++ b/crates/venvlauncher/src/main.rs @@ -0,0 +1,155 @@ +//! RustPython venv launcher +//! +//! A lightweight launcher that reads pyvenv.cfg and delegates execution +//! to the actual Python interpreter. This mimics CPython's venvlauncher.c. +//! Windows only. + +#[cfg(not(windows))] +compile_error!("venvlauncher is only supported on Windows"); + +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::ExitCode; + +fn main() -> ExitCode { + match run() { + Ok(code) => ExitCode::from(code as u8), + Err(e) => { + eprintln!("venvlauncher error: {}", e); + ExitCode::from(1) + } + } +} + +fn run() -> Result<u32, Box<dyn std::error::Error>> { + // 1. Get own executable path + let exe_path = env::current_exe()?; + let exe_name = exe_path + .file_name() + .ok_or("Failed to get executable name")? + .to_string_lossy(); + + // 2. Determine target executable name based on launcher name + // pythonw.exe / venvwlauncher -> pythonw.exe (GUI, no console) + // python.exe / venvlauncher -> python.exe (console) + let exe_name_lower = exe_name.to_lowercase(); + let target_exe = if exe_name_lower.contains("pythonw") || exe_name_lower.contains("venvw") { + "pythonw.exe" + } else { + "python.exe" + }; + + // 3. Find pyvenv.cfg + // The launcher is in Scripts/ directory, pyvenv.cfg is in parent (venv root) + let scripts_dir = exe_path.parent().ok_or("Failed to get Scripts directory")?; + let venv_dir = scripts_dir.parent().ok_or("Failed to get venv directory")?; + let cfg_path = venv_dir.join("pyvenv.cfg"); + + if !cfg_path.exists() { + return Err(format!("pyvenv.cfg not found: {}", cfg_path.display()).into()); + } + + // 4. Parse home= from pyvenv.cfg + let home = read_home(&cfg_path)?; + + // 5. Locate python executable in home directory + let python_path = PathBuf::from(&home).join(target_exe); + if !python_path.exists() { + return Err(format!("Python not found: {}", python_path.display()).into()); + } + + // 6. Set __PYVENV_LAUNCHER__ environment variable + // This tells Python it was launched from a venv + // SAFETY: We are in a single-threaded context (program entry point) + unsafe { + env::set_var("__PYVENV_LAUNCHER__", &exe_path); + } + + // 7. Launch Python with same arguments + let args: Vec<String> = env::args().skip(1).collect(); + launch_process(&python_path, &args) +} + +/// Parse the `home=` value from pyvenv.cfg +fn read_home(cfg_path: &Path) -> Result<String, Box<dyn std::error::Error>> { + let content = fs::read_to_string(cfg_path)?; + + for line in content.lines() { + let line = line.trim(); + // Skip comments and empty lines + if line.is_empty() || line.starts_with('#') { + continue; + } + + // Look for "home = <path>" or "home=<path>" + if let Some(rest) = line.strip_prefix("home") { + let rest = rest.trim_start(); + if let Some(value) = rest.strip_prefix('=') { + return Ok(value.trim().to_string()); + } + } + } + + Err("'home' key not found in pyvenv.cfg".into()) +} + +/// Launch the Python process and wait for it to complete +fn launch_process(exe: &Path, args: &[String]) -> Result<u32, Box<dyn std::error::Error>> { + use std::process::Command; + + let status = Command::new(exe).args(args).status()?; + + Ok(status.code().unwrap_or(1) as u32) +} + +#[cfg(all(test, windows))] +mod tests { + use super::*; + use std::io::Write; + + #[test] + fn test_read_home() { + let temp_dir = std::env::temp_dir(); + let cfg_path = temp_dir.join("test_pyvenv.cfg"); + + let mut file = fs::File::create(&cfg_path).unwrap(); + writeln!(file, "home = C:\\Python313").unwrap(); + writeln!(file, "include-system-site-packages = false").unwrap(); + writeln!(file, "version = 3.13.0").unwrap(); + + let home = read_home(&cfg_path).unwrap(); + assert_eq!(home, "C:\\Python313"); + + fs::remove_file(&cfg_path).unwrap(); + } + + #[test] + fn test_read_home_no_spaces() { + let temp_dir = std::env::temp_dir(); + let cfg_path = temp_dir.join("test_pyvenv2.cfg"); + + let mut file = fs::File::create(&cfg_path).unwrap(); + writeln!(file, "home=C:\\Python313").unwrap(); + + let home = read_home(&cfg_path).unwrap(); + assert_eq!(home, "C:\\Python313"); + + fs::remove_file(&cfg_path).unwrap(); + } + + #[test] + fn test_read_home_with_comments() { + let temp_dir = std::env::temp_dir(); + let cfg_path = temp_dir.join("test_pyvenv3.cfg"); + + let mut file = fs::File::create(&cfg_path).unwrap(); + writeln!(file, "# This is a comment").unwrap(); + writeln!(file, "home = D:\\RustPython").unwrap(); + + let home = read_home(&cfg_path).unwrap(); + assert_eq!(home, "D:\\RustPython"); + + fs::remove_file(&cfg_path).unwrap(); + } +} diff --git a/crates/vm/src/getpath.rs b/crates/vm/src/getpath.rs index 423b9b54136..011d5336873 100644 --- a/crates/vm/src/getpath.rs +++ b/crates/vm/src/getpath.rs @@ -110,19 +110,26 @@ pub fn init_path_config(settings: &Settings) -> Paths { // Step 0: Get executable path let executable = get_executable_path(); - paths.executable = executable + let real_executable = executable .as_ref() .map(|p| p.to_string_lossy().into_owned()) .unwrap_or_default(); - let exe_dir = executable - .as_ref() - .and_then(|p| p.parent().map(PathBuf::from)); - // Step 1: Check for __PYVENV_LAUNCHER__ environment variable - if let Ok(launcher) = env::var("__PYVENV_LAUNCHER__") { - paths.base_executable = launcher; - } + // When launched from a venv launcher, __PYVENV_LAUNCHER__ contains the venv's python.exe path + // In this case: + // - sys.executable should be the launcher path (where user invoked Python) + // - sys._base_executable should be the real Python executable + let exe_dir = if let Ok(launcher) = env::var("__PYVENV_LAUNCHER__") { + paths.executable = launcher.clone(); + paths.base_executable = real_executable; + PathBuf::from(&launcher).parent().map(PathBuf::from) + } else { + paths.executable = real_executable; + executable + .as_ref() + .and_then(|p| p.parent().map(PathBuf::from)) + }; // Step 2: Check for venv (pyvenv.cfg) and get 'home' let (venv_prefix, home_dir) = detect_venv(&exe_dir); From 21f24cdcc72c87f33f65fd32229187b1404dcd7d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 26 Dec 2025 01:37:21 +0900 Subject: [PATCH 585/819] impl more winapi (#6529) --- Lib/test/test_winapi.py | 14 -- crates/vm/src/stdlib/winapi.rs | 439 ++++++++++++++++++++++++++++++++- 2 files changed, 438 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_winapi.py b/Lib/test/test_winapi.py index 846398f89d4..99b5a0dfd16 100644 --- a/Lib/test/test_winapi.py +++ b/Lib/test/test_winapi.py @@ -77,34 +77,22 @@ def _events_waitany_test(self, n): evts[i] = old_evt - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_few_events_waitall(self): self._events_waitall_test(16) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_many_events_waitall(self): self._events_waitall_test(256) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_max_events_waitall(self): self._events_waitall_test(MAXIMUM_BATCHED_WAIT_OBJECTS) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_few_events_waitany(self): self._events_waitany_test(16) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_many_events_waitany(self): self._events_waitany_test(256) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_max_events_waitany(self): self._events_waitany_test(MAXIMUM_BATCHED_WAIT_OBJECTS) @@ -140,8 +128,6 @@ def test_getshortpathname(self): # Should contain "PROGRA~" but we can't predict the number self.assertIsNotNone(re.match(r".\:\\PROGRA~\d", actual.upper()), actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_namedpipe(self): pipe_name = rf"\\.\pipe\LOCAL\{os_helper.TESTFN}" diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index 44f56f0112f..5cfb62fad6f 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -572,7 +572,7 @@ mod _winapi { LCMapStringEx as WinLCMapStringEx, }; - // Reject unsupported flags (same as CPython) + // Reject unsupported flags if flags & (LCMAP_SORTHANDLE | LCMAP_HASH | LCMAP_BYTEREV | LCMAP_SORTKEY) != 0 { return Err(vm.new_value_error("unsupported flags")); } @@ -725,4 +725,441 @@ mod _winapi { use windows_sys::Win32::Storage::FileSystem::GetLongPathNameW; get_path_name_impl(&path, GetLongPathNameW, vm) } + + /// WaitNamedPipe - Wait for an instance of a named pipe to become available. + #[pyfunction] + fn WaitNamedPipe(name: PyStrRef, timeout: u32, vm: &VirtualMachine) -> PyResult<()> { + use windows_sys::Win32::System::Pipes::WaitNamedPipeW; + + let name_wide = name.as_wtf8().to_wide_with_nul(); + + let success = unsafe { WaitNamedPipeW(name_wide.as_ptr(), timeout) }; + + if success == 0 { + return Err(vm.new_last_os_error()); + } + + Ok(()) + } + + /// PeekNamedPipe - Peek at data in a named pipe without removing it. + #[pyfunction] + fn PeekNamedPipe( + handle: WinHandle, + size: OptionalArg<i32>, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + use windows_sys::Win32::System::Pipes::PeekNamedPipe as WinPeekNamedPipe; + + let size = size.unwrap_or(0); + + if size < 0 { + return Err(vm.new_value_error("negative size".to_string())); + } + + let mut navail: u32 = 0; + let mut nleft: u32 = 0; + + if size > 0 { + let mut buf = vec![0u8; size as usize]; + let mut nread: u32 = 0; + + let ret = unsafe { + WinPeekNamedPipe( + handle.0, + buf.as_mut_ptr() as *mut _, + size as u32, + &mut nread, + &mut navail, + &mut nleft, + ) + }; + + if ret == 0 { + return Err(vm.new_last_os_error()); + } + + buf.truncate(nread as usize); + let bytes: PyObjectRef = vm.ctx.new_bytes(buf).into(); + Ok(vm + .ctx + .new_tuple(vec![ + bytes, + vm.ctx.new_int(navail).into(), + vm.ctx.new_int(nleft).into(), + ]) + .into()) + } else { + let ret = unsafe { + WinPeekNamedPipe(handle.0, null_mut(), 0, null_mut(), &mut navail, &mut nleft) + }; + + if ret == 0 { + return Err(vm.new_last_os_error()); + } + + Ok(vm + .ctx + .new_tuple(vec![ + vm.ctx.new_int(navail).into(), + vm.ctx.new_int(nleft).into(), + ]) + .into()) + } + } + + /// CreateEventW - Create or open a named or unnamed event object. + #[pyfunction] + fn CreateEventW( + security_attributes: isize, // Always NULL (0) + manual_reset: bool, + initial_state: bool, + name: Option<PyStrRef>, + vm: &VirtualMachine, + ) -> PyResult<WinHandle> { + use windows_sys::Win32::System::Threading::CreateEventW as WinCreateEventW; + + let _ = security_attributes; // Ignored, always NULL + + let name_wide = name.map(|n| n.as_wtf8().to_wide_with_nul()); + let name_ptr = name_wide.as_ref().map_or(null(), |n| n.as_ptr()); + + let handle = unsafe { + WinCreateEventW( + null(), + i32::from(manual_reset), + i32::from(initial_state), + name_ptr, + ) + }; + + if handle.is_null() { + return Err(vm.new_last_os_error()); + } + + Ok(WinHandle(handle)) + } + + /// SetEvent - Set the specified event object to the signaled state. + #[pyfunction] + fn SetEvent(event: WinHandle, vm: &VirtualMachine) -> PyResult<()> { + use windows_sys::Win32::System::Threading::SetEvent as WinSetEvent; + + let ret = unsafe { WinSetEvent(event.0) }; + + if ret == 0 { + return Err(vm.new_last_os_error()); + } + + Ok(()) + } + + /// WriteFile - Write data to a file or I/O device. + #[pyfunction] + fn WriteFile( + handle: WinHandle, + buffer: crate::function::ArgBytesLike, + use_overlapped: OptionalArg<bool>, + vm: &VirtualMachine, + ) -> PyResult<(u32, u32)> { + use windows_sys::Win32::Storage::FileSystem::WriteFile as WinWriteFile; + + let use_overlapped = use_overlapped.unwrap_or(false); + + if use_overlapped { + return Err(vm.new_not_implemented_error( + "overlapped WriteFile is not yet implemented in _winapi".to_string(), + )); + } + + let buf = buffer.borrow_buf(); + let len = std::cmp::min(buf.len(), u32::MAX as usize) as u32; + let mut written: u32 = 0; + + let ret = unsafe { + WinWriteFile( + handle.0, + buf.as_ptr() as *const _, + len, + &mut written, + null_mut(), + ) + }; + + let err = if ret == 0 { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + } else { + 0 + }; + + if ret == 0 { + return Err(vm.new_last_os_error()); + } + + Ok((written, err)) + } + + const MAXIMUM_WAIT_OBJECTS: usize = 64; + + /// BatchedWaitForMultipleObjects - Wait for multiple handles, supporting more than 64. + #[pyfunction] + fn BatchedWaitForMultipleObjects( + handle_seq: PyObjectRef, + wait_all: bool, + milliseconds: OptionalArg<u32>, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + use std::sync::Arc; + use std::sync::atomic::{AtomicU32, Ordering}; + use windows_sys::Win32::Foundation::{CloseHandle, WAIT_FAILED, WAIT_OBJECT_0}; + use windows_sys::Win32::System::SystemInformation::GetTickCount64; + use windows_sys::Win32::System::Threading::{ + CreateEventW as WinCreateEventW, CreateThread, GetExitCodeThread, + INFINITE as WIN_INFINITE, ResumeThread, SetEvent as WinSetEvent, TerminateThread, + WaitForMultipleObjects, + }; + + let milliseconds = milliseconds.unwrap_or(WIN_INFINITE); + + // Get handles from sequence + let seq = ArgSequence::<isize>::try_from_object(vm, handle_seq)?; + let handles: Vec<isize> = seq.into_vec(); + let nhandles = handles.len(); + + if nhandles == 0 { + return if wait_all { + Ok(vm.ctx.none()) + } else { + Ok(vm.ctx.new_list(vec![]).into()) + }; + } + + let max_total_objects = (MAXIMUM_WAIT_OBJECTS - 1) * (MAXIMUM_WAIT_OBJECTS - 1); + if nhandles > max_total_objects { + return Err(vm.new_value_error(format!( + "need at most {} handles, got a sequence of length {}", + max_total_objects, nhandles + ))); + } + + // Create batches of handles + let batch_size = MAXIMUM_WAIT_OBJECTS - 1; // Leave room for cancel_event + let mut batches: Vec<Vec<isize>> = Vec::new(); + let mut i = 0; + while i < nhandles { + let end = std::cmp::min(i + batch_size, nhandles); + batches.push(handles[i..end].to_vec()); + i = end; + } + + if wait_all { + // For wait_all, we wait sequentially for each batch + let deadline = if milliseconds != WIN_INFINITE { + Some(unsafe { GetTickCount64() } + milliseconds as u64) + } else { + None + }; + + for batch in &batches { + let timeout = if let Some(deadline) = deadline { + let now = unsafe { GetTickCount64() }; + if now >= deadline { + return Err( + vm.new_exception_empty(vm.ctx.exceptions.timeout_error.to_owned()) + ); + } + (deadline - now) as u32 + } else { + WIN_INFINITE + }; + + let batch_handles: Vec<_> = batch.iter().map(|&h| h as _).collect(); + let result = unsafe { + WaitForMultipleObjects( + batch_handles.len() as u32, + batch_handles.as_ptr(), + 1, // wait_all = TRUE + timeout, + ) + }; + + if result == WAIT_FAILED { + return Err(vm.new_last_os_error()); + } + if result == windows_sys::Win32::Foundation::WAIT_TIMEOUT { + return Err(vm.new_exception_empty(vm.ctx.exceptions.timeout_error.to_owned())); + } + } + + Ok(vm.ctx.none()) + } else { + // For wait_any, we use threads to wait on each batch in parallel + let cancel_event = unsafe { WinCreateEventW(null(), 1, 0, null()) }; // Manual reset, not signaled + if cancel_event.is_null() { + return Err(vm.new_last_os_error()); + } + + struct BatchData { + handles: Vec<isize>, + cancel_event: isize, + handle_base: usize, + result: AtomicU32, + thread: std::cell::UnsafeCell<isize>, + } + + unsafe impl Send for BatchData {} + unsafe impl Sync for BatchData {} + + let batch_data: Vec<Arc<BatchData>> = batches + .iter() + .enumerate() + .map(|(idx, batch)| { + let base = idx * batch_size; + let mut handles_with_cancel = batch.clone(); + handles_with_cancel.push(cancel_event as isize); + Arc::new(BatchData { + handles: handles_with_cancel, + cancel_event: cancel_event as isize, + handle_base: base, + result: AtomicU32::new(WAIT_FAILED), + thread: std::cell::UnsafeCell::new(0), + }) + }) + .collect(); + + // Thread function + extern "system" fn batch_wait_thread(param: *mut std::ffi::c_void) -> u32 { + let data = unsafe { &*(param as *const BatchData) }; + let handles: Vec<_> = data.handles.iter().map(|&h| h as _).collect(); + let result = unsafe { + WaitForMultipleObjects( + handles.len() as u32, + handles.as_ptr(), + 0, // wait_any + WIN_INFINITE, + ) + }; + data.result.store(result, Ordering::SeqCst); + + if result == WAIT_FAILED { + let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + unsafe { WinSetEvent(data.cancel_event as _) }; + return err; + } else if result >= windows_sys::Win32::Foundation::WAIT_ABANDONED_0 + && result + < windows_sys::Win32::Foundation::WAIT_ABANDONED_0 + + MAXIMUM_WAIT_OBJECTS as u32 + { + data.result.store(WAIT_FAILED, Ordering::SeqCst); + unsafe { WinSetEvent(data.cancel_event as _) }; + return windows_sys::Win32::Foundation::ERROR_ABANDONED_WAIT_0; + } + 0 + } + + // Create threads + let mut thread_handles: Vec<isize> = Vec::new(); + for data in &batch_data { + let thread = unsafe { + CreateThread( + null(), + 1, // Smallest stack + Some(batch_wait_thread), + Arc::as_ptr(data) as *const _ as *mut _, + 4, // CREATE_SUSPENDED + null_mut(), + ) + }; + if thread.is_null() { + // Cleanup on error + for h in &thread_handles { + unsafe { TerminateThread(*h as _, 0) }; + unsafe { CloseHandle(*h as _) }; + } + unsafe { CloseHandle(cancel_event) }; + return Err(vm.new_last_os_error()); + } + unsafe { *data.thread.get() = thread as isize }; + thread_handles.push(thread as isize); + } + + // Resume all threads + for &thread in &thread_handles { + unsafe { ResumeThread(thread as _) }; + } + + // Wait for any thread to complete + let thread_handles_raw: Vec<_> = thread_handles.iter().map(|&h| h as _).collect(); + let result = unsafe { + WaitForMultipleObjects( + thread_handles_raw.len() as u32, + thread_handles_raw.as_ptr(), + 0, // wait_any + milliseconds, + ) + }; + + let err = if result == WAIT_FAILED { + Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }) + } else if result == windows_sys::Win32::Foundation::WAIT_TIMEOUT { + Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT) + } else { + None + }; + + // Signal cancel event to stop other threads + unsafe { WinSetEvent(cancel_event) }; + + // Wait for all threads to finish + unsafe { + WaitForMultipleObjects( + thread_handles_raw.len() as u32, + thread_handles_raw.as_ptr(), + 1, // wait_all + WIN_INFINITE, + ) + }; + + // Check for errors from threads + let mut thread_err = err; + for data in &batch_data { + if thread_err.is_none() && data.result.load(Ordering::SeqCst) == WAIT_FAILED { + let mut exit_code: u32 = 0; + let thread = unsafe { *data.thread.get() }; + if unsafe { GetExitCodeThread(thread as _, &mut exit_code) } == 0 { + thread_err = + Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); + } else if exit_code != 0 { + thread_err = Some(exit_code); + } + } + let thread = unsafe { *data.thread.get() }; + unsafe { CloseHandle(thread as _) }; + } + + unsafe { CloseHandle(cancel_event) }; + + // Return result + if let Some(e) = thread_err { + if e == windows_sys::Win32::Foundation::WAIT_TIMEOUT { + return Err(vm.new_exception_empty(vm.ctx.exceptions.timeout_error.to_owned())); + } + return Err(vm.new_os_error(e as i32)); + } + + // Collect triggered indices + let mut triggered_indices: Vec<PyObjectRef> = Vec::new(); + for data in &batch_data { + let result = data.result.load(Ordering::SeqCst); + let triggered = result as i32 - WAIT_OBJECT_0 as i32; + // Check if it's a valid handle index (not the cancel_event which is last) + if triggered >= 0 && (triggered as usize) < data.handles.len() - 1 { + let index = data.handle_base + triggered as usize; + triggered_indices.push(vm.ctx.new_int(index).into()); + } + } + + Ok(vm.ctx.new_list(triggered_indices).into()) + } + } } From ae39b132e410ed8ba1e54453f2573c60b2679f22 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 26 Dec 2025 09:43:01 +0900 Subject: [PATCH 586/819] impl more ntpath (#6531) --- Lib/test/test_ntpath.py | 4 -- crates/vm/src/builtins/str.rs | 15 +++++- crates/vm/src/stdlib/nt.rs | 91 +++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index ed9d8b5d281..e1982dfd0bd 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -1279,8 +1279,6 @@ def test_ismount(self): self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$")) self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$\\")) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; crash") def test_ismount_invalid_paths(self): ismount = ntpath.ismount self.assertFalse(ismount("c:\\\udfff")) @@ -1464,8 +1462,6 @@ def test_fast_paths_in_use(self): self.assertTrue(os.path.lexists is nt._path_lexists) self.assertFalse(inspect.isfunction(os.path.lexists)) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(os.name != 'nt', "Dev Drives only exist on Win32") def test_isdevdrive(self): # Result may be True or False, but shouldn't raise diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 95b41e6d55c..df65fd5a4ed 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -668,8 +668,19 @@ impl PyStr { // casefold is much more aggressive than lower #[pymethod] - fn casefold(&self) -> String { - caseless::default_case_fold_str(self.as_str()) + fn casefold(&self) -> Self { + match self.as_str_kind() { + PyKindStr::Ascii(s) => caseless::default_case_fold_str(s.as_str()).into(), + PyKindStr::Utf8(s) => caseless::default_case_fold_str(s).into(), + PyKindStr::Wtf8(w) => w + .chunks() + .map(|c| match c { + Wtf8Chunk::Utf8(s) => Wtf8Buf::from_string(caseless::default_case_fold_str(s)), + Wtf8Chunk::Surrogate(c) => Wtf8Buf::from(c), + }) + .collect::<Wtf8Buf>() + .into(), + } } #[pymethod] diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index fc68139dc88..05929344cc4 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -567,6 +567,97 @@ pub(crate) mod module { .is_some_and(|p| _test_file_exists(&p, false)) } + /// Check if a path is on a Windows Dev Drive. + #[pyfunction] + fn _path_isdevdrive(path: OsPath, vm: &VirtualMachine) -> PyResult<bool> { + use windows_sys::Win32::Foundation::CloseHandle; + use windows_sys::Win32::Storage::FileSystem::{ + CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, + FILE_SHARE_WRITE, GetDriveTypeW, GetVolumePathNameW, OPEN_EXISTING, + }; + use windows_sys::Win32::System::IO::DeviceIoControl; + use windows_sys::Win32::System::Ioctl::FSCTL_QUERY_PERSISTENT_VOLUME_STATE; + use windows_sys::Win32::System::WindowsProgramming::DRIVE_FIXED; + + // PERSISTENT_VOLUME_STATE_DEV_VOLUME flag - not yet in windows-sys + const PERSISTENT_VOLUME_STATE_DEV_VOLUME: u32 = 0x00002000; + + // FILE_FS_PERSISTENT_VOLUME_INFORMATION structure + #[repr(C)] + struct FileFsPersistentVolumeInformation { + volume_flags: u32, + flag_mask: u32, + version: u32, + reserved: u32, + } + + let wide_path = path.to_wide_cstring(vm)?; + let mut volume = [0u16; Foundation::MAX_PATH as usize]; + + // Get volume path + let ret = unsafe { + GetVolumePathNameW(wide_path.as_ptr(), volume.as_mut_ptr(), volume.len() as _) + }; + if ret == 0 { + return Err(vm.new_last_os_error()); + } + + // Check if it's a fixed drive + if unsafe { GetDriveTypeW(volume.as_ptr()) } != DRIVE_FIXED { + return Ok(false); + } + + // Open the volume + let handle = unsafe { + CreateFileW( + volume.as_ptr(), + FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE, + std::ptr::null(), + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + std::ptr::null_mut(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + return Err(vm.new_last_os_error()); + } + + // Query persistent volume state + let mut volume_state = FileFsPersistentVolumeInformation { + volume_flags: 0, + flag_mask: PERSISTENT_VOLUME_STATE_DEV_VOLUME, + version: 1, + reserved: 0, + }; + + let ret = unsafe { + DeviceIoControl( + handle, + FSCTL_QUERY_PERSISTENT_VOLUME_STATE, + &volume_state as *const _ as *const std::ffi::c_void, + std::mem::size_of::<FileFsPersistentVolumeInformation>() as u32, + &mut volume_state as *mut _ as *mut std::ffi::c_void, + std::mem::size_of::<FileFsPersistentVolumeInformation>() as u32, + std::ptr::null_mut(), + std::ptr::null_mut(), + ) + }; + + unsafe { CloseHandle(handle) }; + + if ret == 0 { + let err = io::Error::last_os_error(); + // ERROR_INVALID_PARAMETER means not supported on this platform + if err.raw_os_error() == Some(Foundation::ERROR_INVALID_PARAMETER as i32) { + return Ok(false); + } + return Err(err.to_pyexception(vm)); + } + + Ok((volume_state.volume_flags & PERSISTENT_VOLUME_STATE_DEV_VOLUME) != 0) + } + // cwait is available on MSVC only #[cfg(target_env = "msvc")] unsafe extern "C" { From c9bf8df19ca9b88e6848b9513308f62637792b9c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 26 Dec 2025 09:46:33 +0900 Subject: [PATCH 587/819] copyslot for init (#6515) --- Lib/test/test_codecs.py | 1 - Lib/test/test_memoryio.py | 8 ---- Lib/test/test_pickle.py | 15 ------- Lib/test/test_pickletools.py | 5 --- Lib/test/test_urllib2.py | 28 ------------- Lib/test/test_zipfile.py | 2 - crates/derive-impl/src/pyclass.rs | 23 ++++++++-- crates/vm/src/builtins/object.rs | 49 +++++++++++----------- crates/vm/src/builtins/type.rs | 70 +++++++++++++++++++++++++++---- crates/vm/src/exception_group.rs | 14 ++----- crates/vm/src/stdlib/io.rs | 57 +++++++++++++++++-------- crates/vm/src/types/slot.rs | 69 +++++++++++++++++++++++++++++- crates/vm/src/types/zoo.rs | 4 +- 13 files changed, 216 insertions(+), 129 deletions(-) diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index 0d3d8c9e2d6..736022599ed 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -3189,7 +3189,6 @@ def test_readline(self): sout = reader.readline() self.assertEqual(sout, b"\x80") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_buffer_api_usage(self): # We check all the transform codecs accept memoryview input # for encoding and decoding diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py index 61d9b180e26..07d9d38d6e4 100644 --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -724,8 +724,6 @@ class CBytesIOTest(PyBytesIOTest): ioclass = io.BytesIO UnsupportedOperation = io.UnsupportedOperation - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_bytes_array(self): super().test_bytes_array() @@ -739,8 +737,6 @@ def test_flags(self): def test_getbuffer(self): super().test_getbuffer() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_init(self): super().test_init() @@ -770,8 +766,6 @@ def test_relative_seek(self): def test_seek(self): super().test_seek() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_subclassing(self): super().test_subclassing() @@ -884,8 +878,6 @@ def test_detach(self): def test_flags(self): super().test_flags() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_init(self): super().test_init() diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py index 070e277c2a7..ea51b9d0916 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -180,11 +180,6 @@ def test_oob_buffers(self): # TODO(RUSTPYTHON): Remove this test when it passes def test_oob_buffers_writable_to_readonly(self): # TODO(RUSTPYTHON): Remove this test when it passes return super().test_oob_buffers_writable_to_readonly() - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes - return super().test_optional_frames() - # TODO: RUSTPYTHON @unittest.expectedFailure def test_buffers_error(self): # TODO(RUSTPYTHON): Remove this test when it passes @@ -220,11 +215,6 @@ def test_oob_buffers(self): # TODO(RUSTPYTHON): Remove this test when it passes def test_oob_buffers_writable_to_readonly(self): # TODO(RUSTPYTHON): Remove this test when it passes return super().test_oob_buffers_writable_to_readonly() - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes - return super().test_optional_frames() - class InMemoryPickleTests(AbstractPickleTests, AbstractUnpickleTests, BigmemPickleTests, unittest.TestCase): @@ -309,11 +299,6 @@ def test_in_band_buffers(self): # TODO(RUSTPYTHON): Remove this test when it pas def test_oob_buffers(self): # TODO(RUSTPYTHON): Remove this test when it passes return super().test_oob_buffers() - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes - return super().test_optional_frames() - class PersistentPicklerUnpicklerMixin(object): def dumps(self, arg, proto=None): diff --git a/Lib/test/test_pickletools.py b/Lib/test/test_pickletools.py index 6c38bef3d31..492f57cce22 100644 --- a/Lib/test/test_pickletools.py +++ b/Lib/test/test_pickletools.py @@ -97,11 +97,6 @@ def test_oob_buffers(self): # TODO(RUSTPYTHON): Remove this test when it passes def test_oob_buffers_writable_to_readonly(self): # TODO(RUSTPYTHON): Remove this test when it passes return super().test_oob_buffers_writable_to_readonly() - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes - return super().test_optional_frames() - # TODO: RUSTPYTHON @unittest.expectedFailure def test_py_methods(self): # TODO(RUSTPYTHON): Remove this test when it passes diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index 5a02b5db8e5..399c94213a6 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -641,8 +641,6 @@ def test_raise(self): self.assertRaises(urllib.error.URLError, o.open, req) self.assertEqual(o.calls, [(handlers[0], "http_open", (req,), {})]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_http_error(self): # XXX http_error_default # http errors are a special case @@ -666,8 +664,6 @@ def test_http_error(self): self.assertEqual((handler, method_name), got[:2]) self.assertEqual(args, got[2]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_processors(self): # *_request / *_response methods get called appropriately o = OpenerDirector() @@ -874,8 +870,6 @@ def test_file(self): self.assertEqual(req.type, "ftp") self.assertEqual(req.type == "ftp", ftp) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_http(self): h = urllib.request.AbstractHTTPHandler() @@ -1136,8 +1130,6 @@ def test_fixpath_in_weirdurls(self): self.assertEqual(newreq.host, 'www.python.org') self.assertEqual(newreq.selector, '') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_errors(self): h = urllib.request.HTTPErrorProcessor() o = h.parent = MockOpener() @@ -1163,8 +1155,6 @@ def test_errors(self): self.assertEqual(o.proto, "http") # o.error called self.assertEqual(o.args, (req, r, 502, "Bad gateway", {})) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_cookies(self): cj = MockCookieJar() h = urllib.request.HTTPCookieProcessor(cj) @@ -1291,8 +1281,6 @@ def test_relative_redirect(self): MockHeaders({"location": valid_url})) self.assertEqual(o.req.get_full_url(), valid_url) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_cookie_redirect(self): # cookies shouldn't leak into redirected requests from http.cookiejar import CookieJar @@ -1308,8 +1296,6 @@ def test_cookie_redirect(self): o.open("http://www.example.com/") self.assertFalse(hh.req.has_header("Cookie")) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_redirect_fragment(self): redirected_url = 'http://www.example.com/index.html#OK\r\n\r\n' hh = MockHTTPHandler(302, 'Location: ' + redirected_url) @@ -1374,8 +1360,6 @@ def http_open(self, req): request = handler.last_buf self.assertTrue(request.startswith(expected), repr(request)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_proxy(self): u = "proxy.example.com:3128" for d in dict(http=u), dict(HTTP=u): @@ -1420,8 +1404,6 @@ def test_proxy_no_proxy_all(self): self.assertEqual(req.host, "www.python.org") del os.environ['no_proxy'] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_proxy_https(self): o = OpenerDirector() ph = urllib.request.ProxyHandler(dict(https="proxy.example.com:3128")) @@ -1509,8 +1491,6 @@ def check_basic_auth(self, headers, realm): "http://acme.example.com/protected", "http://acme.example.com/protected") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basic_auth(self): realm = "realm2@example.com" realm2 = "realm2@example.com" @@ -1556,8 +1536,6 @@ def test_basic_auth(self): for challenge in challenges] self.check_basic_auth(headers, realm) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_proxy_basic_auth(self): opener = OpenerDirector() ph = urllib.request.ProxyHandler(dict(http="proxy.example.com:3128")) @@ -1575,8 +1553,6 @@ def test_proxy_basic_auth(self): "proxy.example.com:3128", ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basic_and_digest_auth_handlers(self): # HTTPDigestAuthHandler raised an exception if it couldn't handle a 40* # response (http://python.org/sf/1479302), where it should instead @@ -1684,8 +1660,6 @@ def _test_basic_auth(self, opener, auth_handler, auth_header, self.assertEqual(len(http_handler.requests), 1) self.assertFalse(http_handler.requests[0].has_header(auth_header)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basic_prior_auth_auto_send(self): # Assume already authenticated if is_authenticated=True # for APIs like Github that don't return 401 @@ -1713,8 +1687,6 @@ def test_basic_prior_auth_auto_send(self): # expect request to be sent with auth header self.assertTrue(http_handler.has_auth_header) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basic_prior_auth_send_after_first_success(self): # Auto send auth header after authentication is successful once diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py index 0a50f036046..e7705590cd2 100644 --- a/Lib/test/test_zipfile.py +++ b/Lib/test/test_zipfile.py @@ -1746,8 +1746,6 @@ def test_empty_file_raises_BadZipFile(self): fp.write("short file") self.assertRaises(zipfile.BadZipFile, zipfile.ZipFile, TESTFN) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_negative_central_directory_offset_raises_BadZipFile(self): # Zip file containing an empty EOCD record buffer = bytearray(b'PK\x05\x06' + b'\0'*18) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index d1fe5398634..1a01721391e 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -223,8 +223,8 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul const METHOD_DEFS: &'static [::rustpython_vm::function::PyMethodDef] = &#method_defs; fn extend_slots(slots: &mut ::rustpython_vm::types::PyTypeSlots) { - #impl_ty::__extend_slots(slots); #with_slots + #impl_ty::__extend_slots(slots); } } } @@ -1672,9 +1672,24 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac #extend_class(ctx, class); }); with_method_defs.push(method_defs); - with_slots.push(quote_spanned! { item_span => - #extend_slots(slots); - }); + // For Initializer and Constructor traits, directly set the slot + // instead of calling __extend_slots. This ensures that the trait + // impl's override (e.g., slot_init in impl Initializer) is used, + // not the trait's default implementation. + let slot_code = if path.is_ident("Initializer") { + quote_spanned! { item_span => + slots.init.store(Some(<Self as ::rustpython_vm::types::Initializer>::slot_init as _)); + } + } else if path.is_ident("Constructor") { + quote_spanned! { item_span => + slots.new.store(Some(<Self as ::rustpython_vm::types::Constructor>::slot_new as _)); + } + } else { + quote_spanned! { item_span => + #extend_slots(slots); + } + }; + with_slots.push(slot_code); } } else if path.is_ident("flags") { for meta in nested { diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index ca208790f4b..77a1eff6c7d 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -120,44 +120,36 @@ impl Initializer for PyBaseObject { // object_init: excess_args validation fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + if args.is_empty() { + return Ok(()); + } + let typ = zelf.class(); let object_type = &vm.ctx.types.object_type; let typ_init = typ.slots.init.load().map(|f| f as usize); let object_init = object_type.slots.init.load().map(|f| f as usize); - let typ_new = typ.slots.new.load().map(|f| f as usize); - let object_new = object_type.slots.new.load().map(|f| f as usize); - // For heap types (Python classes), check if __new__ is defined anywhere in MRO - // (before object) because heap types always have slots.new = new_wrapper via MRO - let is_heap_type = typ - .slots - .flags - .contains(crate::types::PyTypeFlags::HEAPTYPE); - let new_overridden = if is_heap_type { - // Check if __new__ is defined in any base class (excluding object) - let new_id = identifier!(vm, __new__); - typ.mro_collect() - .into_iter() - .take_while(|t| !std::ptr::eq(t.as_ref(), *object_type)) - .any(|t| t.attributes.read().contains_key(new_id)) - } else { - // For built-in types, use slot comparison - typ_new != object_new - }; - - // If both __init__ and __new__ are overridden, allow excess args - if typ_init != object_init && new_overridden { - return Ok(()); + // if (type->tp_init != object_init) → first error + if typ_init != object_init { + return Err(vm.new_type_error( + "object.__init__() takes exactly one argument (the instance to initialize)" + .to_owned(), + )); } - // Otherwise, reject excess args - if !args.is_empty() { + let typ_new = typ.slots.new.load().map(|f| f as usize); + let object_new = object_type.slots.new.load().map(|f| f as usize); + + // if (type->tp_new == object_new) → second error + if typ_new == object_new { return Err(vm.new_type_error(format!( "{}.__init__() takes exactly one argument (the instance to initialize)", typ.name() ))); } + + // Both conditions false → OK (e.g., tuple, dict with custom __new__) Ok(()) } @@ -591,6 +583,13 @@ pub fn object_set_dict(obj: PyObjectRef, dict: PyDictRef, vm: &VirtualMachine) - } pub fn init(ctx: &Context) { + // Manually set init slot - derive macro doesn't generate extend_slots + // for trait impl that overrides #[pyslot] method + ctx.types + .object_type + .slots + .init + .store(Some(<PyBaseObject as Initializer>::slot_init)); PyBaseObject::extend_class(ctx, ctx.types.object_type); } diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index ffaad324687..4f150d92589 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -25,8 +25,8 @@ use crate::{ object::{Traverse, TraverseFn}, protocol::{PyIterReturn, PyNumberMethods}, types::{ - AsNumber, Callable, Constructor, GetAttr, PyTypeFlags, PyTypeSlots, Representable, SetAttr, - TypeDataRef, TypeDataRefMut, TypeDataSlot, + AsNumber, Callable, Constructor, GetAttr, Initializer, PyTypeFlags, PyTypeSlots, + Representable, SetAttr, TypeDataRef, TypeDataRefMut, TypeDataSlot, }, }; use indexmap::{IndexMap, map::Entry}; @@ -412,8 +412,10 @@ impl PyType { } pub(crate) fn init_slots(&self, ctx: &Context) { - // Inherit slots from direct bases (not MRO) - for base in self.bases.read().iter() { + // Inherit slots from MRO + // Note: self.mro does NOT include self, so we iterate all elements + let mro: Vec<_> = self.mro.read().iter().cloned().collect(); + for base in mro.iter() { self.inherit_slots(base); } @@ -460,7 +462,7 @@ impl PyType { } } - /// Inherit slots from base type. typeobject.c: inherit_slots + /// Inherit slots from base type. inherit_slots pub(crate) fn inherit_slots(&self, base: &Self) { macro_rules! copyslot { ($slot:ident) => { @@ -472,6 +474,28 @@ impl PyType { }; } + // Copy init slot only if base actually defines it (not just inherited) + // This is needed for multiple inheritance where a later base might + // have a more specific init slot + macro_rules! copyslot_defined { + ($slot:ident) => { + if self.slots.$slot.load().is_none() { + if let Some(base_val) = base.slots.$slot.load() { + // SLOTDEFINED: base->SLOT && (basebase == NULL || base->SLOT != basebase->SLOT) + let basebase = base.base.as_ref(); + let slot_defined = match basebase { + None => true, + Some(bb) => bb.slots.$slot.load().map(|v| v as usize) + != Some(base_val as usize), + }; + if slot_defined { + self.slots.$slot.store(Some(base_val)); + } + } + } + }; + } + // Core slots copyslot!(hash); copyslot!(call); @@ -484,9 +508,8 @@ impl PyType { copyslot!(iternext); copyslot!(descr_get); copyslot!(descr_set); - // Note: init is NOT inherited here because object_init has special - // handling in CPython (checks if type->tp_init != object_init). - // TODO: implement proper init inheritance with object_init check + // init uses SLOTDEFINED check for multiple inheritance support + copyslot_defined!(init); copyslot!(del); // new is handled by set_new() // as_buffer is inherited at type creation time (not AtomicCell) @@ -832,7 +855,16 @@ impl Py<PyType> { } #[pyclass( - with(Py, Constructor, GetAttr, SetAttr, Callable, AsNumber, Representable), + with( + Py, + Constructor, + Initializer, + GetAttr, + SetAttr, + Callable, + AsNumber, + Representable + ), flags(BASETYPE) )] impl PyType { @@ -1532,6 +1564,26 @@ fn get_doc_from_internal_doc<'a>(name: &str, internal_doc: &'a str) -> &'a str { internal_doc } +impl Initializer for PyType { + type Args = FuncArgs; + + // type_init + fn slot_init(_zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + // type.__init__() takes 1 or 3 arguments + if args.args.len() == 1 && !args.kwargs.is_empty() { + return Err(vm.new_type_error("type.__init__() takes no keyword arguments".to_owned())); + } + if args.args.len() != 1 && args.args.len() != 3 { + return Err(vm.new_type_error("type.__init__() takes 1 or 3 arguments".to_owned())); + } + Ok(()) + } + + fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } +} + impl GetAttr for PyType { fn getattro(zelf: &Py<Self>, name_str: &Py<PyStr>, vm: &VirtualMachine) -> PyResult { #[cold] diff --git a/crates/vm/src/exception_group.rs b/crates/vm/src/exception_group.rs index 645a3e779ff..b2dee206200 100644 --- a/crates/vm/src/exception_group.rs +++ b/crates/vm/src/exception_group.rs @@ -353,21 +353,13 @@ pub(super) mod types { impl Initializer for PyBaseExceptionGroup { type Args = FuncArgs; - fn slot_init( - _zelf: PyObjectRef, - _args: ::rustpython_vm::function::FuncArgs, - _vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult<()> { - // CPython's BaseExceptionGroup.__init__ just calls BaseException.__init__ - // which stores args as-is. Since __new__ already set up the correct args - // (message, exceptions_tuple), we don't need to do anything here. - // This also allows subclasses to pass extra arguments to __new__ without - // __init__ complaining about argument count. + fn slot_init(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> { + // No-op: __new__ already set up the correct args (message, exceptions_tuple) Ok(()) } fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { - unreachable!("slot_init is defined") + unreachable!("slot_init is overridden") } } diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 9402660a86e..a02965797bc 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -3679,23 +3679,31 @@ mod _io { } impl Constructor for StringIO { + type Args = FuncArgs; + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + Ok(Self { + _base: Default::default(), + buffer: PyRwLock::new(BufferedIO::new(Cursor::new(Vec::new()))), + closed: AtomicCell::new(false), + }) + } + } + + impl Initializer for StringIO { type Args = StringIONewArgs; #[allow(unused_variables)] - fn py_new( - _cls: &Py<PyType>, + fn init( + zelf: PyRef<Self>, Self::Args { object, newline }: Self::Args, _vm: &VirtualMachine, - ) -> PyResult<Self> { + ) -> PyResult<()> { let raw_bytes = object .flatten() .map_or_else(Vec::new, |v| v.as_bytes().to_vec()); - - Ok(Self { - _base: Default::default(), - buffer: PyRwLock::new(BufferedIO::new(Cursor::new(raw_bytes))), - closed: AtomicCell::new(false), - }) + *zelf.buffer.write() = BufferedIO::new(Cursor::new(raw_bytes)); + Ok(()) } } @@ -3709,7 +3717,7 @@ mod _io { } } - #[pyclass(flags(BASETYPE, HAS_DICT), with(Constructor))] + #[pyclass(flags(BASETYPE, HAS_DICT), with(Constructor, Initializer))] impl StringIO { #[pymethod] const fn readable(&self) -> bool { @@ -3814,22 +3822,35 @@ mod _io { } impl Constructor for BytesIO { - type Args = OptionalArg<Option<PyBytesRef>>; - - fn py_new(_cls: &Py<PyType>, object: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - let raw_bytes = object - .flatten() - .map_or_else(Vec::new, |input| input.as_bytes().to_vec()); + type Args = FuncArgs; + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { Ok(Self { _base: Default::default(), - buffer: PyRwLock::new(BufferedIO::new(Cursor::new(raw_bytes))), + buffer: PyRwLock::new(BufferedIO::new(Cursor::new(Vec::new()))), closed: AtomicCell::new(false), exports: AtomicCell::new(0), }) } } + impl Initializer for BytesIO { + type Args = OptionalArg<Option<ArgBytesLike>>; + + fn init(zelf: PyRef<Self>, object: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + if zelf.exports.load() > 0 { + return Err(vm.new_buffer_error( + "Existing exports of data: object cannot be re-sized".to_owned(), + )); + } + let raw_bytes = object + .flatten() + .map_or_else(Vec::new, |input| input.borrow_buf().to_vec()); + *zelf.buffer.write() = BufferedIO::new(Cursor::new(raw_bytes)); + Ok(()) + } + } + impl BytesIO { fn buffer(&self, vm: &VirtualMachine) -> PyResult<PyRwLockWriteGuard<'_, BufferedIO>> { if !self.closed.load() { @@ -3840,7 +3861,7 @@ mod _io { } } - #[pyclass(flags(BASETYPE, HAS_DICT), with(PyRef, Constructor))] + #[pyclass(flags(BASETYPE, HAS_DICT), with(PyRef, Constructor, Initializer))] impl BytesIO { #[pymethod] const fn readable(&self) -> bool { diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index bfe6e047622..13a43ed9c95 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -632,13 +632,48 @@ impl PyType { update_slot!(descr_set, descr_set_wrapper); } _ if name == identifier!(ctx, __init__) => { - toggle_slot!(init, init_wrapper); + // Special handling: check if this type or any base has a real __init__ method + // If only slot wrappers exist, use the inherited slot from copyslot! + if ADD { + // First check if this type has __init__ in its own dict + let has_own_init = self.attributes.read().contains_key(name); + if has_own_init { + // This type defines __init__ - use wrapper + self.slots.init.store(Some(init_wrapper)); + } else if self.has_real_method_in_mro(name, ctx) { + // A base class defines a real __init__ method - use wrapper + self.slots.init.store(Some(init_wrapper)); + } + // else: keep inherited slot from copyslot! + } else { + let inherited = self + .mro + .read() + .iter() + .skip(1) + .find_map(|cls| cls.slots.init.load()); + self.slots.init.store(inherited); + } } _ if name == identifier!(ctx, __new__) => { toggle_slot!(new, new_wrapper); } _ if name == identifier!(ctx, __del__) => { - toggle_slot!(del, del_wrapper); + // Same special handling as __init__ + if ADD { + let has_own_del = self.attributes.read().contains_key(name); + if has_own_del || self.has_real_method_in_mro(name, ctx) { + self.slots.del.store(Some(del_wrapper)); + } + } else { + let inherited = self + .mro + .read() + .iter() + .skip(1) + .find_map(|cls| cls.slots.del.load()); + self.slots.del.store(inherited); + } } _ if name == identifier!(ctx, __bool__) => { toggle_sub_slot!(as_number, boolean, bool_wrapper); @@ -890,6 +925,36 @@ impl PyType { _ => {} } } + + /// Check if there's a real method (not a slot wrapper) for `name` anywhere in the MRO. + /// If a real method exists, we should use a wrapper function. Otherwise, use the inherited slot. + fn has_real_method_in_mro(&self, name: &'static PyStrInterned, ctx: &Context) -> bool { + use crate::builtins::descriptor::PySlotWrapper; + + // Check the entire MRO (including self) for the method + for cls in self.mro.read().iter() { + if let Some(attr) = cls.attributes.read().get(name).cloned() { + // Found the method - check if it's a slot wrapper + if attr.class().is(ctx.types.wrapper_descriptor_type) { + if let Some(wrapper) = attr.downcast_ref::<PySlotWrapper>() { + // It's a slot wrapper - check if it belongs to this class + let wrapper_typ: *const _ = wrapper.typ; + let cls_ptr: *const _ = cls.as_ref(); + if wrapper_typ == cls_ptr { + // Slot wrapper defined on this exact class - use inherited slot + return false; + } + } + // Inherited slot wrapper - continue checking MRO + } else { + // Real method found - use wrapper function + return true; + } + } + } + // No real method found in MRO - use inherited slot + false + } } /// Trait for types that can be constructed via Python's `__new__` method. diff --git a/crates/vm/src/types/zoo.rs b/crates/vm/src/types/zoo.rs index 0cd04a0ac17..9d164241676 100644 --- a/crates/vm/src/types/zoo.rs +++ b/crates/vm/src/types/zoo.rs @@ -199,8 +199,10 @@ impl TypeZoo { /// Fill attributes of builtin types. #[cold] pub(crate) fn extend(context: &Context) { - type_::init(context); + // object must be initialized before type to set object.slots.init, + // which type will inherit via inherit_slots() object::init(context); + type_::init(context); list::init(context); set::init(context); tuple::init(context); From 72a90da13be2258a6f8c888a07582c932ca7c7cd Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 26 Dec 2025 20:39:48 +0900 Subject: [PATCH 588/819] increase timeout-minutes to 45 (#6541) --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6dc9b75b138..dc5ce974e38 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -115,7 +115,7 @@ jobs: RUST_BACKTRACE: full name: Run rust tests runs-on: ${{ matrix.os }} - timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 35 }} + timeout-minutes: 45 strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] From 18564151967271b765189943c17c3ca3bddeebdf Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:38:05 +0900 Subject: [PATCH 589/819] Fix f-string conversion flag ValueError when compiling AST (#6533) (#6534) The ast_from_object for ConversionFlag was using bytecode::ConvertValueOparg::from_op_arg() which only accepts internal oparg values (0, 1, 2, 3, 255), but Python's AST uses ASCII codes ('s'=115, 'r'=114, 'a'=97, -1=None). This caused compile() on parsed AST with conversion flags to fail with "invalid conversion flag". --- crates/vm/src/stdlib/ast.rs | 1 - crates/vm/src/stdlib/ast/other.rs | 22 ++++++++++------------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/crates/vm/src/stdlib/ast.rs b/crates/vm/src/stdlib/ast.rs index 8e03cb225ea..b8df86fb091 100644 --- a/crates/vm/src/stdlib/ast.rs +++ b/crates/vm/src/stdlib/ast.rs @@ -15,7 +15,6 @@ use crate::{ builtins::PyIntRef, builtins::{PyDict, PyModule, PyStrRef, PyType}, class::{PyClassImpl, StaticType}, - compiler::core::bytecode::OpArgType, compiler::{CompileError, ParseError}, convert::ToPyObject, }; diff --git a/crates/vm/src/stdlib/ast/other.rs b/crates/vm/src/stdlib/ast/other.rs index cf8a8319749..ce7d5fe4807 100644 --- a/crates/vm/src/stdlib/ast/other.rs +++ b/crates/vm/src/stdlib/ast/other.rs @@ -1,6 +1,5 @@ use super::*; -use num_traits::ToPrimitive; -use rustpython_compiler_core::{SourceFile, bytecode}; +use rustpython_compiler_core::SourceFile; impl Node for ruff::ConversionFlag { fn ast_to_object(self, vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { @@ -12,16 +11,15 @@ impl Node for ruff::ConversionFlag { _source_file: &SourceFile, object: PyObjectRef, ) -> PyResult<Self> { - i32::try_from_object(vm, object)? - .to_u32() - .and_then(bytecode::ConvertValueOparg::from_op_arg) - .map(|flag| match flag { - bytecode::ConvertValueOparg::None => Self::None, - bytecode::ConvertValueOparg::Str => Self::Str, - bytecode::ConvertValueOparg::Repr => Self::Repr, - bytecode::ConvertValueOparg::Ascii => Self::Ascii, - }) - .ok_or_else(|| vm.new_value_error("invalid conversion flag")) + // Python's AST uses ASCII codes: 's', 'r', 'a', -1=None + // Note: 255 is -1i8 as u8 (ruff's ConversionFlag::None) + match i32::try_from_object(vm, object)? { + -1 | 255 => Ok(Self::None), + x if x == b's' as i32 => Ok(Self::Str), + x if x == b'r' as i32 => Ok(Self::Repr), + x if x == b'a' as i32 => Ok(Self::Ascii), + _ => Err(vm.new_value_error("invalid conversion flag")), + } } } From 57b4b4ae4520521971cadf705c2fa294e7189f72 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:43:00 +0900 Subject: [PATCH 590/819] fix sys path (#6537) --- Lib/test/test_cmd_line_script.py | 4 ---- src/lib.rs | 29 +++++++++++++++++++---------- src/settings.rs | 1 + 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index d773674feef..122c4ac16d4 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -411,8 +411,6 @@ def test_issue8202(self): script_name, script_name, script_dir, 'test_pkg', importlib.machinery.SourceFileLoader) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_issue8202_dash_c_file_ignored(self): # Make sure a "-c" file in the current directory # does not alter the value of sys.path[0] @@ -713,8 +711,6 @@ def test_syntaxerror_null_bytes_in_multiline_string(self): ] ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_consistent_sys_path_for_direct_execution(self): # This test case ensures that the following all give the same # sys.path configuration: diff --git a/src/lib.rs b/src/lib.rs index 8d278058933..8ee22d4eb5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,16 +169,9 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { let scope = setup_main_module(vm)?; - if !vm.state.config.settings.safe_path { - // TODO: The prepending path depends on running mode - // See https://docs.python.org/3/using/cmdline.html#cmdoption-P - vm.run_code_string( - vm.new_scope_with_builtins(), - "import sys; sys.path.insert(0, '')", - "<embedded>".to_owned(), - )?; - } - + // Import site first, before setting sys.path[0] + // This matches CPython's behavior where site.removeduppaths() runs + // before sys.path[0] is set, preventing '' from being converted to cwd let site_result = vm.import("site", 0); if site_result.is_err() { warn!( @@ -187,6 +180,22 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { ); } + // _PyPathConfig_ComputeSysPath0 - set sys.path[0] after site import + if !vm.state.config.settings.safe_path { + let path0: Option<String> = match &run_mode { + RunMode::Command(_) => Some(String::new()), + RunMode::Module(_) => env::current_dir() + .ok() + .and_then(|p| p.to_str().map(|s| s.to_owned())), + RunMode::Script(_) | RunMode::InstallPip(_) => None, // handled by run_script + RunMode::Repl => Some(String::new()), + }; + + if let Some(path) = path0 { + vm.insert_sys_path(vm.new_pyobj(path))?; + } + } + // Enable faulthandler if -X faulthandler, PYTHONFAULTHANDLER or -X dev is set // _PyFaulthandler_Init() if vm.state.config.settings.faulthandler { diff --git a/src/settings.rs b/src/settings.rs index f77db4d159d..a63f1a07ccc 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -339,6 +339,7 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> { /// Helper function to retrieve a sequence of paths from an environment variable. fn get_paths(env_variable_name: &str) -> impl Iterator<Item = String> + '_ { env::var_os(env_variable_name) + .filter(|v| !v.is_empty()) .into_iter() .flat_map(move |paths| { split_paths(&paths) From 4a6e8fb29edfe09827e529aca2d1ac4c1b1c51d7 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:54:23 +0900 Subject: [PATCH 591/819] Add except* support (#6530) --- .cspell.dict/python-more.txt | 1 + Lib/test/test_compile.py | 6 - crates/codegen/src/compile.rs | 157 ++++++++++++++++++++- crates/compiler-core/src/bytecode.rs | 7 +- crates/vm/src/exceptions.rs | 124 ++++++++++++++++ crates/vm/src/frame.rs | 15 ++ extra_tests/snippets/builtin_exceptions.py | 12 ++ 7 files changed, 308 insertions(+), 14 deletions(-) diff --git a/.cspell.dict/python-more.txt b/.cspell.dict/python-more.txt index e8534e9744a..d381bfe1e03 100644 --- a/.cspell.dict/python-more.txt +++ b/.cspell.dict/python-more.txt @@ -199,6 +199,7 @@ readbuffer reconstructor refcnt releaselevel +reraised reverseitemiterator reverseiterator reversekeyiterator diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index e4d335a193d..3ea09354c3c 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1536,8 +1536,6 @@ def test_try_except_as(self): """ self.check_stack_size(snippet) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_try_except_star_qualified(self): snippet = """ try: @@ -1549,8 +1547,6 @@ def test_try_except_star_qualified(self): """ self.check_stack_size(snippet) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_try_except_star_as(self): snippet = """ try: @@ -1562,8 +1558,6 @@ def test_try_except_star_as(self): """ self.check_stack_size(snippet) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_try_except_star_finally(self): snippet = """ try: diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 0131b3008e3..53cd81a4f42 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -1510,7 +1510,7 @@ impl Compiler { .. }) => { if *is_star { - self.compile_try_star_statement(body, handlers, orelse, finalbody)? + self.compile_try_star_except(body, handlers, orelse, finalbody)? } else { self.compile_try_statement(body, handlers, orelse, finalbody)? } @@ -2119,14 +2119,157 @@ impl Compiler { Ok(()) } - fn compile_try_star_statement( + fn compile_try_star_except( &mut self, - _body: &[Stmt], - _handlers: &[ExceptHandler], - _orelse: &[Stmt], - _finalbody: &[Stmt], + body: &[Stmt], + handlers: &[ExceptHandler], + orelse: &[Stmt], + finalbody: &[Stmt], ) -> CompileResult<()> { - Err(self.error(CodegenErrorType::NotImplementedYet)) + // Simplified except* implementation using CheckEgMatch + let handler_block = self.new_block(); + let finally_block = self.new_block(); + + if !finalbody.is_empty() { + emit!( + self, + Instruction::SetupFinally { + handler: finally_block, + } + ); + } + + let else_block = self.new_block(); + + emit!( + self, + Instruction::SetupExcept { + handler: handler_block, + } + ); + self.compile_statements(body)?; + emit!(self, Instruction::PopBlock); + emit!(self, Instruction::Jump { target: else_block }); + + self.switch_to_block(handler_block); + // Stack: [exc] + + for handler in handlers { + let ExceptHandler::ExceptHandler(ExceptHandlerExceptHandler { + type_, name, body, .. + }) = handler; + + let skip_block = self.new_block(); + let next_block = self.new_block(); + + // Compile exception type + if let Some(exc_type) = type_ { + // Check for unparenthesized tuple (e.g., `except* A, B:` instead of `except* (A, B):`) + if let Expr::Tuple(ExprTuple { elts, range, .. }) = exc_type.as_ref() + && let Some(first) = elts.first() + && range.start().to_u32() == first.range().start().to_u32() + { + return Err(self.error(CodegenErrorType::SyntaxError( + "multiple exception types must be parenthesized".to_owned(), + ))); + } + self.compile_expression(exc_type)?; + } else { + return Err(self.error(CodegenErrorType::SyntaxError( + "except* must specify an exception type".to_owned(), + ))); + } + // Stack: [exc, type] + + emit!(self, Instruction::CheckEgMatch); + // Stack: [rest, match] + + // Check if match is None (truthy check) + emit!(self, Instruction::CopyItem { index: 1 }); + emit!(self, Instruction::ToBool); + emit!(self, Instruction::PopJumpIfFalse { target: skip_block }); + + // Handler matched - store match to name if provided + // Stack: [rest, match] + if let Some(alias) = name { + self.store_name(alias.as_str())?; + } else { + emit!(self, Instruction::PopTop); + } + // Stack: [rest] + + self.compile_statements(body)?; + + if let Some(alias) = name { + self.emit_load_const(ConstantData::None); + self.store_name(alias.as_str())?; + self.compile_name(alias.as_str(), NameUsage::Delete)?; + } + + emit!(self, Instruction::Jump { target: next_block }); + + // No match - pop match (None) and continue with rest + self.switch_to_block(skip_block); + emit!(self, Instruction::PopTop); // drop match (None) + // Stack: [rest] + + self.switch_to_block(next_block); + // Stack: [rest] - continue with rest for next handler + } + + let handled_block = self.new_block(); + + // Check if remainder is truthy (has unhandled exceptions) + // Stack: [rest] + emit!(self, Instruction::CopyItem { index: 1 }); + emit!(self, Instruction::ToBool); + emit!( + self, + Instruction::PopJumpIfFalse { + target: handled_block + } + ); + // Reraise unhandled exceptions + emit!( + self, + Instruction::Raise { + kind: bytecode::RaiseKind::Raise + } + ); + + // All exceptions handled + self.switch_to_block(handled_block); + emit!(self, Instruction::PopTop); // drop remainder (None) + emit!(self, Instruction::PopException); + + if !finalbody.is_empty() { + emit!(self, Instruction::PopBlock); + emit!(self, Instruction::EnterFinally); + } + + emit!( + self, + Instruction::Jump { + target: finally_block, + } + ); + + // try-else path + self.switch_to_block(else_block); + self.compile_statements(orelse)?; + + if !finalbody.is_empty() { + emit!(self, Instruction::PopBlock); + emit!(self, Instruction::EnterFinally); + } + + self.switch_to_block(finally_block); + if !finalbody.is_empty() { + self.compile_statements(finalbody)?; + emit!(self, Instruction::EndFinally); + } + + Ok(()) } fn is_forbidden_arg_name(name: &str) -> bool { diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index dd49e679f27..add2c1c2f63 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -574,7 +574,7 @@ op_arg_enum!( #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] pub enum IntrinsicFunction2 { - // PrepReraiseS tar = 1, + PrepReraiseStar = 1, TypeVarWithBound = 2, TypeVarWithConstraint = 3, SetFunctionTypeParams = 4, @@ -652,6 +652,9 @@ pub enum Instruction { CallMethodPositional { nargs: Arg<u32>, }, + /// Check if exception matches except* handler type. + /// Pops exc_value and match_type, pushes (rest, match). + CheckEgMatch, CompareOperation { op: Arg<ComparisonOperator>, }, @@ -1721,6 +1724,7 @@ impl Instruction { CallMethodKeyword { nargs } => -1 - (nargs.get(arg) as i32) - 3 + 1, CallFunctionEx { has_kwargs } => -1 - (has_kwargs.get(arg) as i32) - 1 + 1, CallMethodEx { has_kwargs } => -1 - (has_kwargs.get(arg) as i32) - 3 + 1, + CheckEgMatch => 0, // pops 2 (exc, type), pushes 2 (rest, match) ConvertValue { .. } => 0, FormatSimple => 0, FormatWithSpec => -1, @@ -1887,6 +1891,7 @@ impl Instruction { CallMethodEx { has_kwargs } => w!(CALL_METHOD_EX, has_kwargs), CallMethodKeyword { nargs } => w!(CALL_METHOD_KEYWORD, nargs), CallMethodPositional { nargs } => w!(CALL_METHOD_POSITIONAL, nargs), + CheckEgMatch => w!(CHECK_EG_MATCH), CompareOperation { op } => w!(COMPARE_OPERATION, ?op), ContainsOp(inv) => w!(CONTAINS_OP, ?inv), Continue { target } => w!(CONTINUE, target), diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 8d6ce6142b4..7b8af83489e 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -2432,3 +2432,127 @@ pub(super) mod types { #[repr(transparent)] pub struct PyEncodingWarning(PyWarning); } + +/// Match exception against except* handler type. +/// Returns (rest, match) tuple. +pub fn exception_group_match( + exc_value: &PyObjectRef, + match_type: &PyObjectRef, + vm: &VirtualMachine, +) -> PyResult<(PyObjectRef, PyObjectRef)> { + // Implements _PyEval_ExceptionGroupMatch + + // If exc_value is None, return (None, None) + if vm.is_none(exc_value) { + return Ok((vm.ctx.none(), vm.ctx.none())); + } + + // Check if exc_value matches match_type + if exc_value.is_instance(match_type, vm)? { + // Full match of exc itself + let is_eg = exc_value.fast_isinstance(vm.ctx.exceptions.base_exception_group); + let matched = if is_eg { + exc_value.clone() + } else { + // Naked exception - wrap it in ExceptionGroup + let excs = vm.ctx.new_tuple(vec![exc_value.clone()]); + let eg_type: PyObjectRef = crate::exception_group::exception_group().to_owned().into(); + let wrapped = eg_type.call((vm.ctx.new_str(""), excs), vm)?; + // Copy traceback from original exception + if let Ok(exc) = exc_value.clone().downcast::<types::PyBaseException>() + && let Some(tb) = exc.__traceback__() + && let Ok(wrapped_exc) = wrapped.clone().downcast::<types::PyBaseException>() + { + let _ = wrapped_exc.set___traceback__(tb.into(), vm); + } + wrapped + }; + return Ok((vm.ctx.none(), matched)); + } + + // Check for partial match if it's an exception group + if exc_value.fast_isinstance(vm.ctx.exceptions.base_exception_group) { + let pair = vm.call_method(exc_value, "split", (match_type.clone(),))?; + let pair_tuple: PyTupleRef = pair.try_into_value(vm)?; + if pair_tuple.len() < 2 { + return Err(vm.new_type_error(format!( + "{}.split must return a 2-tuple, got tuple of size {}", + exc_value.class().name(), + pair_tuple.len() + ))); + } + let matched = pair_tuple[0].clone(); + let rest = pair_tuple[1].clone(); + return Ok((rest, matched)); + } + + // No match + Ok((exc_value.clone(), vm.ctx.none())) +} + +/// Prepare exception for reraise in except* block. +pub fn prep_reraise_star(orig: PyObjectRef, excs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // Implements _PyExc_PrepReraiseStar + use crate::builtins::PyList; + + let excs_list = excs + .downcast::<PyList>() + .map_err(|_| vm.new_type_error("expected list for prep_reraise_star"))?; + + let excs_vec: Vec<PyObjectRef> = excs_list.borrow_vec().to_vec(); + + // Filter out None values + let mut raised: Vec<PyObjectRef> = Vec::new(); + let mut reraised: Vec<PyObjectRef> = Vec::new(); + + for exc in excs_vec { + if vm.is_none(&exc) { + continue; + } + // Check if this exception was in the original exception group + if !vm.is_none(&orig) && is_same_exception_metadata(&exc, &orig, vm) { + reraised.push(exc); + } else { + raised.push(exc); + } + } + + // If no exceptions to reraise, return None + if raised.is_empty() && reraised.is_empty() { + return Ok(vm.ctx.none()); + } + + // Combine raised and reraised exceptions + let mut all_excs = raised; + all_excs.extend(reraised); + + if all_excs.len() == 1 { + // If only one exception, just return it + return Ok(all_excs.into_iter().next().unwrap()); + } + + // Create new ExceptionGroup + let excs_tuple = vm.ctx.new_tuple(all_excs); + let eg_type: PyObjectRef = crate::exception_group::exception_group().to_owned().into(); + eg_type.call((vm.ctx.new_str(""), excs_tuple), vm) +} + +/// Check if two exceptions have the same metadata (for reraise detection) +fn is_same_exception_metadata(exc1: &PyObjectRef, exc2: &PyObjectRef, vm: &VirtualMachine) -> bool { + // Check if exc1 is part of exc2's exception group + if exc2.fast_isinstance(vm.ctx.exceptions.base_exception_group) { + let exc_class: PyObjectRef = exc1.class().to_owned().into(); + if let Ok(result) = vm.call_method(exc2, "subgroup", (exc_class,)) + && !vm.is_none(&result) + && let Ok(subgroup_excs) = result.get_attr("exceptions", vm) + && let Ok(tuple) = subgroup_excs.downcast::<PyTuple>() + { + for e in tuple.iter() { + if e.is(exc1) { + return true; + } + } + } + } + exc1.is(exc2) +} diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index e9a938ba023..6cb41f41d93 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -691,6 +691,15 @@ impl ExecutingFrame<'_> { let args = self.collect_positional_args(nargs.get(arg)); self.execute_method_call(args, vm) } + bytecode::Instruction::CheckEgMatch => { + let match_type = self.pop_value(); + let exc_value = self.pop_value(); + let (rest, matched) = + crate::exceptions::exception_group_match(&exc_value, &match_type, vm)?; + self.push_value(rest); + self.push_value(matched); + Ok(None) + } bytecode::Instruction::CompareOperation { op } => self.execute_compare(vm, op.get(arg)), bytecode::Instruction::ContainsOp(invert) => { let b = self.pop_value(); @@ -2494,6 +2503,12 @@ impl ExecutingFrame<'_> { .into(); Ok(type_var) } + bytecode::IntrinsicFunction2::PrepReraiseStar => { + // arg1 = orig (original exception) + // arg2 = excs (list of exceptions raised/reraised in except* blocks) + // Returns: exception to reraise, or None if nothing to reraise + crate::exceptions::prep_reraise_star(arg1, arg2, vm) + } } } diff --git a/extra_tests/snippets/builtin_exceptions.py b/extra_tests/snippets/builtin_exceptions.py index 0af5cf05eaf..246be3b8fda 100644 --- a/extra_tests/snippets/builtin_exceptions.py +++ b/extra_tests/snippets/builtin_exceptions.py @@ -366,3 +366,15 @@ class SubError(MyError): vars(builtins).values(), ): assert isinstance(exc.__doc__, str) + + +# except* handling should normalize non-group exceptions +try: + raise ValueError("x") +except* ValueError as err: + assert isinstance(err, ExceptionGroup) + assert len(err.exceptions) == 1 + assert isinstance(err.exceptions[0], ValueError) + assert err.exceptions[0].args == ("x",) +else: + assert False, "except* handler did not run" From 7f4d308efc0ac13396997e98e7b4bdd868bba237 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 26 Dec 2025 22:13:00 +0900 Subject: [PATCH 592/819] Implement maybe_pyc_file and .pyc file execution (#6539) * Implement maybe_pyc_file and .pyc file execution * unmark failing tests --- Lib/test/test_cmd_line_script.py | 2 - Lib/test/test_compileall.py | 10 -- .../test_importlib/source/test_file_loader.py | 120 ------------------ Lib/test/test_importlib/test_util.py | 9 -- crates/vm/src/import.rs | 11 -- crates/vm/src/stdlib/imp.rs | 5 +- crates/vm/src/version.rs | 11 ++ crates/vm/src/vm/compile.rs | 60 ++++++++- 8 files changed, 69 insertions(+), 159 deletions(-) diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 122c4ac16d4..9fc240de818 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -247,8 +247,6 @@ def test_script_abspath(self): script_dir, None, importlib.machinery.SourceFileLoader) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_script_compiled(self): with os_helper.temp_dir() as script_dir: script_name = _make_test_script(script_dir, 'script') diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py index f84e67e9cf6..9fa3dbc47e5 100644 --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py @@ -325,8 +325,6 @@ def _test_ddir_only(self, *, ddir, parallel=True): self.assertEqual(mod_code_obj.co_filename, expected_in) self.assertIn(f'"{expected_in}"', os.fsdecode(err)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_ddir_only_one_worker(self): """Recursive compile_dir ddir= contains package paths; bpo39769.""" return self._test_ddir_only(ddir="<a prefix>", parallel=False) @@ -336,8 +334,6 @@ def test_ddir_multiple_workers(self): """Recursive compile_dir ddir= contains package paths; bpo39769.""" return self._test_ddir_only(ddir="<a prefix>", parallel=True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_ddir_empty_only_one_worker(self): """Recursive compile_dir ddir='' contains package paths; bpo39769.""" return self._test_ddir_only(ddir="", parallel=False) @@ -347,8 +343,6 @@ def test_ddir_empty_multiple_workers(self): """Recursive compile_dir ddir='' contains package paths; bpo39769.""" return self._test_ddir_only(ddir="", parallel=True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_strip_only(self): fullpath = ["test", "build", "real", "path"] path = os.path.join(self.directory, *fullpath) @@ -408,8 +402,6 @@ def test_prepend_only(self): str(err, encoding=sys.getdefaultencoding()) ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_strip_and_prepend(self): fullpath = ["test", "build", "real", "path"] path = os.path.join(self.directory, *fullpath) @@ -887,8 +879,6 @@ def test_workers_available_cores(self, compile_dir): self.assertTrue(compile_dir.called) self.assertEqual(compile_dir.call_args[-1]['workers'], 0) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_strip_and_prepend(self): fullpath = ["test", "build", "real", "path"] path = os.path.join(self.directory, *fullpath) diff --git a/Lib/test/test_importlib/source/test_file_loader.py b/Lib/test/test_importlib/source/test_file_loader.py index d487fc9b82d..fa0efee8da6 100644 --- a/Lib/test/test_importlib/source/test_file_loader.py +++ b/Lib/test/test_importlib/source/test_file_loader.py @@ -359,24 +359,6 @@ def test_overridden_unchecked_hash_based_pyc(self): ) = util.test_both(SimpleTest, importlib=importlib, machinery=machinery, abc=importlib_abc, util=importlib_util) -# TODO: RUSTPYTHON, get rid of this entire class when all of the following tests are fixed -class Source_SimpleTest(Source_SimpleTest): - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_checked_hash_based_pyc(self): - super().test_checked_hash_based_pyc() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_unchecked_hash_based_pyc(self): - super().test_unchecked_hash_based_pyc() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_overridden_unchecked_hash_based_pyc(self): - super().test_overridden_unchecked_hash_based_pyc() - - class SourceDateEpochTestMeta(SourceDateEpochTestMeta, type(Source_SimpleTest)): pass @@ -697,24 +679,6 @@ class SourceLoaderBadBytecodeTestPEP451( machinery=machinery, abc=importlib_abc, util=importlib_util) -# TODO: RUSTPYTHON, get rid of this entire class when all of the following tests are fixed -class Source_SourceBadBytecodePEP451(Source_SourceBadBytecodePEP451): - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_bad_marshal(self): - super().test_bad_marshal() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_no_marshal(self): - super().test_no_marshal() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_non_code_marshal(self): - super().test_non_code_marshal() - - class SourceLoaderBadBytecodeTestPEP302( SourceLoaderBadBytecodeTest, BadBytecodeTestPEP302): pass @@ -726,24 +690,6 @@ class SourceLoaderBadBytecodeTestPEP302( machinery=machinery, abc=importlib_abc, util=importlib_util) -# TODO: RUSTPYTHON, get rid of this entire class when all of the following tests are fixed -class Source_SourceBadBytecodePEP302(Source_SourceBadBytecodePEP302): - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_bad_marshal(self): - super().test_bad_marshal() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_no_marshal(self): - super().test_no_marshal() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_non_code_marshal(self): - super().test_non_code_marshal() - - class SourcelessLoaderBadBytecodeTest: @classmethod @@ -829,39 +775,6 @@ class SourcelessLoaderBadBytecodeTestPEP451(SourcelessLoaderBadBytecodeTest, machinery=machinery, abc=importlib_abc, util=importlib_util) -# TODO: RUSTPYTHON, get rid of this entire class when all of the following tests are fixed -class Source_SourcelessBadBytecodePEP451(Source_SourcelessBadBytecodePEP451): - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_magic_only(self): - super().test_magic_only() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_no_marshal(self): - super().test_no_marshal() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_partial_flags(self): - super().test_partial_flags() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_partial_hash(self): - super().test_partial_hash() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_partial_size(self): - super().test_partial_size() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_partial_timestamp(self): - super().test_partial_timestamp() - - class SourcelessLoaderBadBytecodeTestPEP302(SourcelessLoaderBadBytecodeTest, BadBytecodeTestPEP302): pass @@ -873,38 +786,5 @@ class SourcelessLoaderBadBytecodeTestPEP302(SourcelessLoaderBadBytecodeTest, machinery=machinery, abc=importlib_abc, util=importlib_util) -# TODO: RUSTPYTHON, get rid of this entire class when all of the following tests are fixed -class Source_SourcelessBadBytecodePEP302(Source_SourcelessBadBytecodePEP302): - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_magic_only(self): - super().test_magic_only() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_no_marshal(self): - super().test_no_marshal() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_partial_flags(self): - super().test_partial_flags() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_partial_hash(self): - super().test_partial_hash() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_partial_size(self): - super().test_partial_size() - - # TODO: RUSTPYTHON, get rid of all three of the following lines when this test is fixed - @unittest.expectedFailure - def test_partial_timestamp(self): - super().test_partial_timestamp() - - if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index 201f5069114..87b7de9889b 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -327,15 +327,6 @@ def test_incorporates_rn(self): ) = util.test_both(MagicNumberTests, util=importlib_util) -# TODO: RUSTPYTHON -@unittest.expectedFailure -def test_incorporates_rn_MONKEYPATCH(self): - self.assertTrue(self.util.MAGIC_NUMBER.endswith(b'\r\n')) - -# TODO: RUSTPYTHON -Frozen_MagicNumberTests.test_incorporates_rn = test_incorporates_rn_MONKEYPATCH - - class PEP3147Tests: """Tests of PEP 3147-related functions: cache_from_source and source_from_cache.""" diff --git a/crates/vm/src/import.rs b/crates/vm/src/import.rs index 39748655e0f..f970a335aa5 100644 --- a/crates/vm/src/import.rs +++ b/crates/vm/src/import.rs @@ -5,7 +5,6 @@ use crate::{ builtins::{PyCode, list, traceback::PyTraceback}, exceptions::types::PyBaseException, scope::Scope, - version::get_git_revision, vm::{VirtualMachine, thread}, }; @@ -44,16 +43,6 @@ pub(crate) fn init_importlib_package(vm: &VirtualMachine, importlib: PyObjectRef let install_external = importlib.get_attr("_install_external_importers", vm)?; install_external.call((), vm)?; - // Set pyc magic number to commit hash. Should be changed when bytecode will be more stable. - let importlib_external = vm.import("_frozen_importlib_external", 0)?; - let mut magic = get_git_revision().into_bytes(); - magic.truncate(4); - if magic.len() != 4 { - // os_random is expensive, but this is only ever called once - magic = rustpython_common::rand::os_random::<4>().to_vec(); - } - let magic: PyObjectRef = vm.ctx.new_bytes(magic).into(); - importlib_external.set_attr("MAGIC_NUMBER", magic, vm)?; let zipimport_res = (|| -> PyResult<()> { let zipimport = vm.import("zipimport", 0)?; let zipimporter = zipimport.get_attr("zipimporter", vm)?; diff --git a/crates/vm/src/stdlib/imp.rs b/crates/vm/src/stdlib/imp.rs index 76b3bfd124c..df32b0c0068 100644 --- a/crates/vm/src/stdlib/imp.rs +++ b/crates/vm/src/stdlib/imp.rs @@ -85,7 +85,7 @@ mod _imp { PyObjectRef, PyRef, PyResult, VirtualMachine, builtins::{PyBytesRef, PyCode, PyMemoryView, PyModule, PyStrRef}, function::OptionalArg, - import, + import, version, }; #[pyattr] @@ -94,6 +94,9 @@ mod _imp { .new_str(vm.state.config.settings.check_hash_pycs_mode.to_string()) } + #[pyattr(name = "pyc_magic_number_token")] + use version::PYC_MAGIC_NUMBER_TOKEN; + #[pyfunction] const fn extension_suffixes() -> PyResult<Vec<PyObjectRef>> { Ok(Vec::new()) diff --git a/crates/vm/src/version.rs b/crates/vm/src/version.rs index deb3dccd535..0a598842a56 100644 --- a/crates/vm/src/version.rs +++ b/crates/vm/src/version.rs @@ -126,3 +126,14 @@ pub fn get_git_datetime() -> String { format!("{date} {time}") } + +// Must be aligned to Lib/importlib/_bootstrap_external.py +pub const PYC_MAGIC_NUMBER: u16 = 3531; + +// CPython format: magic_number | ('\r' << 16) | ('\n' << 24) +// This protects against text-mode file reads +pub const PYC_MAGIC_NUMBER_TOKEN: u32 = + (PYC_MAGIC_NUMBER as u32) | ((b'\r' as u32) << 16) | ((b'\n' as u32) << 24); + +/// Magic number as little-endian bytes for .pyc files +pub const PYC_MAGIC_NUMBER_BYTES: [u8; 4] = PYC_MAGIC_NUMBER_TOKEN.to_le_bytes(); diff --git a/crates/vm/src/vm/compile.rs b/crates/vm/src/vm/compile.rs index a7e31cf0377..b5f10e47aa1 100644 --- a/crates/vm/src/vm/compile.rs +++ b/crates/vm/src/vm/compile.rs @@ -75,13 +75,20 @@ impl VirtualMachine { // Consider to use enum to distinguish `path` // https://github.com/RustPython/RustPython/pull/6276#discussion_r2529849479 - // TODO: check .pyc here - let pyc = false; + let pyc = maybe_pyc_file(path); if pyc { - todo!("running pyc is not implemented yet"); + // pyc file execution + set_main_loader(&module_dict, path, "SourcelessFileLoader", self)?; + let loader = module_dict.get_item("__loader__", self)?; + let get_code = loader.get_attr("get_code", self)?; + let code_obj = get_code.call((identifier!(self, __main__).to_owned(),), self)?; + let code = code_obj + .downcast::<PyCode>() + .map_err(|_| self.new_runtime_error("Bad code object in .pyc file".to_owned()))?; + self.run_code_obj(code, scope)?; } else { if path != "<stdin>" { - set_main_loader(&module_dict, path, self)?; + set_main_loader(&module_dict, path, "SourceFileLoader", self)?; } // TODO: replace to something equivalent to py_run_file match std::fs::read_to_string(path) { @@ -125,16 +132,57 @@ impl VirtualMachine { } } -fn set_main_loader(module_dict: &PyDictRef, filename: &str, vm: &VirtualMachine) -> PyResult<()> { +fn set_main_loader( + module_dict: &PyDictRef, + filename: &str, + loader_name: &str, + vm: &VirtualMachine, +) -> PyResult<()> { vm.import("importlib.machinery", 0)?; let sys_modules = vm.sys_module.get_attr(identifier!(vm, modules), vm)?; let machinery = sys_modules.get_item("importlib.machinery", vm)?; - let loader_class = machinery.get_attr("SourceFileLoader", vm)?; + let loader_name = vm.ctx.new_str(loader_name); + let loader_class = machinery.get_attr(&loader_name, vm)?; let loader = loader_class.call((identifier!(vm, __main__).to_owned(), filename), vm)?; module_dict.set_item("__loader__", loader, vm)?; Ok(()) } +/// Check whether a file is maybe a pyc file. +/// +/// Detection is performed by: +/// 1. Checking if the filename ends with ".pyc" +/// 2. If not, reading the first 2 bytes and comparing with the magic number +fn maybe_pyc_file(path: &str) -> bool { + // 1. Check if filename ends with ".pyc" + if path.ends_with(".pyc") { + return true; + } + maybe_pyc_file_with_magic(path, &crate::version::PYC_MAGIC_NUMBER_BYTES).unwrap_or(false) +} + +fn maybe_pyc_file_with_magic(path: &str, magic_number: &[u8]) -> std::io::Result<bool> { + // part of maybe_pyc_file + // For non-.pyc extension, check magic number + let path_obj = std::path::Path::new(path); + if !path_obj.is_file() { + return Ok(false); + } + + let mut file = std::fs::File::open(path)?; + let mut buf = [0u8; 2]; + + use std::io::Read; + if file.read(&mut buf)? != 2 || magic_number.len() < 2 { + return Ok(false); + } + + // Read only two bytes of the magic. If the file was opened in + // text mode, the bytes 3 and 4 of the magic (\r\n) might not + // be read as they are on disk. + Ok(buf == magic_number[..2]) +} + fn get_importer(path: &str, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> { let path_importer_cache = vm.sys_module.get_attr("path_importer_cache", vm)?; let path_importer_cache = PyDictRef::try_from_object(vm, path_importer_cache)?; From b704f42158e8be9a5de781497585207b3f8f576e Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 26 Dec 2025 16:16:16 +0100 Subject: [PATCH 593/819] Reduce usage `JumpIfTrueOrPop` & `JumpIfFalseOrPop` opcdes. Refactor `compile_compare` (#6524) * Remove `JumpIfTrueOrPop` & `JumpIfFalseOrPop` opcdes * Use correct instruction name * Extract `compile_cmpop` to its own method * Better alignment with CPython code * Restore `PopJumpIf` instructions * Revert PopJumpIfFalse for comparisons --- crates/codegen/src/compile.rs | 149 ++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 69 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 53cd81a4f42..4a0ef83a57e 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4173,7 +4173,9 @@ impl Compiler { if let Some(ref guard) = m.guard { // Compile guard and jump to end if false self.compile_expression(guard)?; - emit!(self, Instruction::JumpIfFalseOrPop { target: end }); + emit!(self, Instruction::CopyItem { index: 1_u32 }); + emit!(self, Instruction::PopJumpIfFalse { target: end }); + emit!(self, Instruction::PopTop); } self.compile_statements(&m.body)?; } @@ -4187,92 +4189,97 @@ impl Compiler { Ok(()) } - fn compile_chained_comparison( + /// [CPython `compiler_addcompare`](https://github.com/python/cpython/blob/627894459a84be3488a1789919679c997056a03c/Python/compile.c#L2880-L2924) + fn compile_addcompare(&mut self, op: &CmpOp) { + use bytecode::ComparisonOperator::*; + match op { + CmpOp::Eq => emit!(self, Instruction::CompareOperation { op: Equal }), + CmpOp::NotEq => emit!(self, Instruction::CompareOperation { op: NotEqual }), + CmpOp::Lt => emit!(self, Instruction::CompareOperation { op: Less }), + CmpOp::LtE => emit!(self, Instruction::CompareOperation { op: LessOrEqual }), + CmpOp::Gt => emit!(self, Instruction::CompareOperation { op: Greater }), + CmpOp::GtE => { + emit!(self, Instruction::CompareOperation { op: GreaterOrEqual }) + } + CmpOp::In => emit!(self, Instruction::ContainsOp(Invert::No)), + CmpOp::NotIn => emit!(self, Instruction::ContainsOp(Invert::Yes)), + CmpOp::Is => emit!(self, Instruction::IsOp(Invert::No)), + CmpOp::IsNot => emit!(self, Instruction::IsOp(Invert::Yes)), + } + } + + /// Compile a chained comparison. + /// + /// ```py + /// a == b == c == d + /// ``` + /// + /// Will compile into (pseudo code): + /// + /// ```py + /// result = a == b + /// if result: + /// result = b == c + /// if result: + /// result = c == d + /// ``` + /// + /// # See Also + /// - [CPython `compiler_compare`](https://github.com/python/cpython/blob/627894459a84be3488a1789919679c997056a03c/Python/compile.c#L4678-L4717) + fn compile_compare( &mut self, left: &Expr, ops: &[CmpOp], - exprs: &[Expr], + comparators: &[Expr], ) -> CompileResult<()> { - assert!(!ops.is_empty()); - assert_eq!(exprs.len(), ops.len()); let (last_op, mid_ops) = ops.split_last().unwrap(); - let (last_val, mid_exprs) = exprs.split_last().unwrap(); - - use bytecode::ComparisonOperator::*; - let compile_cmpop = |c: &mut Self, op: &CmpOp| match op { - CmpOp::Eq => emit!(c, Instruction::CompareOperation { op: Equal }), - CmpOp::NotEq => emit!(c, Instruction::CompareOperation { op: NotEqual }), - CmpOp::Lt => emit!(c, Instruction::CompareOperation { op: Less }), - CmpOp::LtE => emit!(c, Instruction::CompareOperation { op: LessOrEqual }), - CmpOp::Gt => emit!(c, Instruction::CompareOperation { op: Greater }), - CmpOp::GtE => { - emit!(c, Instruction::CompareOperation { op: GreaterOrEqual }) - } - CmpOp::In => emit!(c, Instruction::ContainsOp(Invert::No)), - CmpOp::NotIn => emit!(c, Instruction::ContainsOp(Invert::Yes)), - CmpOp::Is => emit!(c, Instruction::IsOp(Invert::No)), - CmpOp::IsNot => emit!(c, Instruction::IsOp(Invert::Yes)), - }; - - // a == b == c == d - // compile into (pseudo code): - // result = a == b - // if result: - // result = b == c - // if result: - // result = c == d + let (last_comparator, mid_comparators) = comparators.split_last().unwrap(); // initialize lhs outside of loop self.compile_expression(left)?; - let end_blocks = if mid_exprs.is_empty() { - None - } else { - let break_block = self.new_block(); - let after_block = self.new_block(); - Some((break_block, after_block)) - }; + if mid_comparators.is_empty() { + self.compile_expression(last_comparator)?; + self.compile_addcompare(last_op); + + return Ok(()); + } + + let cleanup = self.new_block(); // for all comparisons except the last (as the last one doesn't need a conditional jump) - for (op, val) in mid_ops.iter().zip(mid_exprs) { - self.compile_expression(val)?; + for (op, comparator) in mid_ops.iter().zip(mid_comparators) { + self.compile_expression(comparator)?; + // store rhs for the next comparison in chain emit!(self, Instruction::Swap { index: 2 }); - emit!(self, Instruction::CopyItem { index: 2_u32 }); + emit!(self, Instruction::CopyItem { index: 2 }); - compile_cmpop(self, op); + self.compile_addcompare(op); // if comparison result is false, we break with this value; if true, try the next one. - if let Some((break_block, _)) = end_blocks { - emit!( - self, - Instruction::JumpIfFalseOrPop { - target: break_block, - } - ); - } + /* + emit!(self, Instruction::CopyItem { index: 1 }); + // emit!(self, Instruction::ToBool); // TODO: Uncomment this + emit!(self, Instruction::PopJumpIfFalse { target: cleanup }); + emit!(self, Instruction::PopTop); + */ + + emit!(self, Instruction::JumpIfFalseOrPop { target: cleanup }); } - // handle the last comparison - self.compile_expression(last_val)?; - compile_cmpop(self, last_op); + self.compile_expression(last_comparator)?; + self.compile_addcompare(last_op); - if let Some((break_block, after_block)) = end_blocks { - emit!( - self, - Instruction::Jump { - target: after_block, - } - ); - - // early exit left us with stack: `rhs, comparison_result`. We need to clean up rhs. - self.switch_to_block(break_block); - emit!(self, Instruction::Swap { index: 2 }); - emit!(self, Instruction::PopTop); + let end = self.new_block(); + emit!(self, Instruction::Jump { target: end }); - self.switch_to_block(after_block); - } + // early exit left us with stack: `rhs, comparison_result`. We need to clean up rhs. + self.switch_to_block(cleanup); + emit!(self, Instruction::Swap { index: 2 }); + emit!(self, Instruction::PopTop); + self.switch_to_block(end); Ok(()) } @@ -4600,14 +4607,16 @@ impl Compiler { let after_block = self.new_block(); let (last_value, values) = values.split_last().unwrap(); + for value in values { self.compile_expression(value)?; + emit!(self, Instruction::CopyItem { index: 1_u32 }); match op { BoolOp::And => { emit!( self, - Instruction::JumpIfFalseOrPop { + Instruction::PopJumpIfFalse { target: after_block, } ); @@ -4615,12 +4624,14 @@ impl Compiler { BoolOp::Or => { emit!( self, - Instruction::JumpIfTrueOrPop { + Instruction::PopJumpIfTrue { target: after_block, } ); } } + + emit!(self, Instruction::PopTop); } // If all values did not qualify, take the value of the last value: @@ -4697,7 +4708,7 @@ impl Compiler { comparators, .. }) => { - self.compile_chained_comparison(left, ops, comparators)?; + self.compile_compare(left, ops, comparators)?; } // Expr::Constant(ExprConstant { value, .. }) => { // self.emit_load_const(compile_constant(value)); From a7d7f81ca7cd3d8ea591fd5369a3fadcc6446084 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 01:13:20 +0900 Subject: [PATCH 594/819] Handle unions with GenericAlias subclasses without TypeError (#6540) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Jeong YunWon <jeong@youknowone.org> --- Lib/test/test_types.py | 2 -- crates/vm/src/builtins/type.rs | 2 +- crates/vm/src/builtins/union.rs | 11 +++++++---- crates/vm/src/stdlib/typing.rs | 18 +++++++++++++++--- extra_tests/snippets/stdlib_typing.py | 4 ++++ 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 7e9b28236c7..4200cb8ee5e 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -838,8 +838,6 @@ def test_union_parameter_chaining(self): self.assertEqual((list[T] | list[S])[int, T], list[int] | list[T]) self.assertEqual((list[T] | list[S])[int, int], list[int]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_union_parameter_substitution(self): def eq(actual, expected, typed=True): self.assertEqual(actual, expected) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 4f150d92589..bc567cd0978 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1963,7 +1963,7 @@ pub(crate) fn call_slot_new( slot_new(subtype, args, vm) } -pub(super) fn or_(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn or_(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if !union_::is_unionable(zelf.clone(), vm) || !union_::is_unionable(other.clone(), vm) { return vm.ctx.not_implemented(); } diff --git a/crates/vm/src/builtins/union.rs b/crates/vm/src/builtins/union.rs index 8310201a129..83e1316d027 100644 --- a/crates/vm/src/builtins/union.rs +++ b/crates/vm/src/builtins/union.rs @@ -8,6 +8,7 @@ use crate::{ convert::{ToPyObject, ToPyResult}, function::PyComparisonValue, protocol::{PyMappingMethods, PyNumberMethods}, + stdlib::typing::TypeAliasType, types::{AsMapping, AsNumber, Comparable, GetAttr, Hashable, PyComparisonOp, Representable}, }; use std::fmt; @@ -152,10 +153,12 @@ impl PyUnion { } pub fn is_unionable(obj: PyObjectRef, vm: &VirtualMachine) -> bool { - obj.class().is(vm.ctx.types.none_type) + let cls = obj.class(); + cls.is(vm.ctx.types.none_type) || obj.downcastable::<PyType>() - || obj.class().is(vm.ctx.types.generic_alias_type) - || obj.class().is(vm.ctx.types.union_type) + || cls.fast_issubclass(vm.ctx.types.generic_alias_type) + || cls.is(vm.ctx.types.union_type) + || obj.downcast_ref::<TypeAliasType>().is_some() } fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupleRef { @@ -195,7 +198,7 @@ fn dedup_and_flatten_args(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupleRef if !new_args.iter().any(|param| { param .rich_compare_bool(arg, PyComparisonOp::Eq, vm) - .expect("types are always comparable") + .unwrap_or_default() }) { new_args.push(arg.clone()); } diff --git a/crates/vm/src/stdlib/typing.rs b/crates/vm/src/stdlib/typing.rs index 469a75b010f..f11acce3490 100644 --- a/crates/vm/src/stdlib/typing.rs +++ b/crates/vm/src/stdlib/typing.rs @@ -36,9 +36,11 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { pub(crate) mod decl { use crate::{ Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyStrRef, PyTupleRef, PyType, PyTypeRef, pystr::AsPyStr}, + builtins::{PyStrRef, PyTupleRef, PyType, PyTypeRef, pystr::AsPyStr, type_}, + convert::ToPyResult, function::{FuncArgs, IntoFuncArgs}, - types::{Constructor, Representable}, + protocol::PyNumberMethods, + types::{AsNumber, Constructor, Representable}, }; pub(crate) fn _call_typing_func_object<'a>( @@ -109,7 +111,7 @@ pub(crate) mod decl { // compute_value: PyObjectRef, // module: PyObjectRef, } - #[pyclass(with(Constructor, Representable), flags(BASETYPE))] + #[pyclass(with(Constructor, Representable, AsNumber), flags(BASETYPE))] impl TypeAliasType { pub const fn new(name: PyStrRef, type_params: PyTupleRef, value: PyObjectRef) -> Self { Self { @@ -183,6 +185,16 @@ pub(crate) mod decl { } } + impl AsNumber for TypeAliasType { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + or: Some(|a, b, vm| type_::or_(a.to_owned(), b.to_owned(), vm).to_pyresult(vm)), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } + } + // impl AsMapping for Generic { // fn as_mapping() -> &'static PyMappingMethods { // static AS_MAPPING: Lazy<PyMappingMethods> = Lazy::new(|| PyMappingMethods { diff --git a/extra_tests/snippets/stdlib_typing.py b/extra_tests/snippets/stdlib_typing.py index ddc30b68460..681790abd0b 100644 --- a/extra_tests/snippets/stdlib_typing.py +++ b/extra_tests/snippets/stdlib_typing.py @@ -8,3 +8,7 @@ def abort_signal_handler( fn: Callable[[], Awaitable[T]], on_abort: Callable[[], None] | None = None ) -> T: pass + + +# Ensure PEP 604 unions work with typing.Callable aliases. +TracebackFilter = bool | Callable[[int], int] From 27ab62de48340cd8ab557568540c1faacbb0ee3e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 01:16:09 +0900 Subject: [PATCH 595/819] Prevent __class__ reassignment across incompatible layouts (#6521) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Jeong YunWon <jeong@youknowone.org> --- crates/vm/src/builtins/object.rs | 35 +++++++++++++++++-- extra_tests/snippets/builtin_type.py | 50 ++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 77a1eff6c7d..a61fb1e2971 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -497,16 +497,45 @@ impl PyBaseObject { ) -> PyResult<()> { match value.downcast::<PyType>() { Ok(cls) => { - let both_module = instance.class().fast_issubclass(vm.ctx.types.module_type) + let current_cls = instance.class(); + let both_module = current_cls.fast_issubclass(vm.ctx.types.module_type) && cls.fast_issubclass(vm.ctx.types.module_type); - let both_mutable = !instance - .class() + let both_mutable = !current_cls .slots .flags .has_feature(PyTypeFlags::IMMUTABLETYPE) && !cls.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE); // FIXME(#1979) cls instances might have a payload if both_mutable || both_module { + let has_dict = + |typ: &Py<PyType>| typ.slots.flags.has_feature(PyTypeFlags::HAS_DICT); + // Compare slots tuples + let slots_equal = match ( + current_cls + .heaptype_ext + .as_ref() + .and_then(|e| e.slots.as_ref()), + cls.heaptype_ext.as_ref().and_then(|e| e.slots.as_ref()), + ) { + (Some(a), Some(b)) => { + a.len() == b.len() + && a.iter() + .zip(b.iter()) + .all(|(x, y)| x.as_str() == y.as_str()) + } + (None, None) => true, + _ => false, + }; + if current_cls.slots.basicsize != cls.slots.basicsize + || !slots_equal + || has_dict(current_cls) != has_dict(&cls) + { + return Err(vm.new_type_error(format!( + "__class__ assignment: '{}' object layout differs from '{}'", + cls.name(), + current_cls.name() + ))); + } instance.set_class(cls, vm); Ok(()) } else { diff --git a/extra_tests/snippets/builtin_type.py b/extra_tests/snippets/builtin_type.py index 7a8e4840e13..67269e694c0 100644 --- a/extra_tests/snippets/builtin_type.py +++ b/extra_tests/snippets/builtin_type.py @@ -240,6 +240,56 @@ class C(B, BB): assert C.mro() == [C, B, A, BB, AA, object] +class TypeA: + def __init__(self): + self.a = 1 + + +class TypeB: + __slots__ = "b" + + def __init__(self): + self.b = 2 + + +obj = TypeA() +with assert_raises(TypeError) as cm: + obj.__class__ = TypeB +assert "__class__ assignment: 'TypeB' object layout differs from 'TypeA'" in str( + cm.exception +) + + +# Test: same slot count but different slot names should fail +class SlotX: + __slots__ = ("x",) + + +class SlotY: + __slots__ = ("y",) + + +slot_obj = SlotX() +with assert_raises(TypeError) as cm: + slot_obj.__class__ = SlotY +assert "__class__ assignment: 'SlotY' object layout differs from 'SlotX'" in str( + cm.exception +) + + +# Test: same slots should succeed +class SlotA: + __slots__ = ("a",) + + +class SlotA2: + __slots__ = ("a",) + + +slot_a = SlotA() +slot_a.__class__ = SlotA2 # Should work + + assert type(Exception.args).__name__ == "getset_descriptor" assert type(None).__bool__(None) is False From 75ecd72428626298bda278885727adb8912570fc Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 27 Dec 2025 21:38:12 +0900 Subject: [PATCH 596/819] test_builtin.test_import (#6546) * fix warnings * fix test_import --- Lib/test/test_builtin.py | 2 -- Lib/test/test_import/__init__.py | 2 -- .../test_importlib/import_/test___package__.py | 4 ---- Lib/test/test_importlib/import_/test_helpers.py | 8 -------- .../test_importlib/import_/test_meta_path.py | 2 -- Lib/test/test_importlib/import_/test_path.py | 2 -- crates/vm/src/warn.rs | 17 ++++++++++++++--- 7 files changed, 14 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 183caa898ef..a5306b71d89 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -153,8 +153,6 @@ def check_iter_pickle(self, it, seq, proto): it = pickle.loads(d) self.assertEqual(list(it), seq[1:]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_import(self): __import__('sys') __import__('time') diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 85913cd7c58..51fafd89ceb 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1403,8 +1403,6 @@ def test_absolute_circular_submodule(self): str(cm.exception), ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_unwritable_module(self): self.addCleanup(unload, "test.test_import.data.unwritable") self.addCleanup(unload, "test.test_import.data.unwritable.x") diff --git a/Lib/test/test_importlib/import_/test___package__.py b/Lib/test/test_importlib/import_/test___package__.py index 431faea5b4e..7130c99a6fc 100644 --- a/Lib/test/test_importlib/import_/test___package__.py +++ b/Lib/test/test_importlib/import_/test___package__.py @@ -56,8 +56,6 @@ def test_using___name__(self): '__path__': []}) self.assertEqual(module.__name__, 'pkg') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_warn_when_using___name__(self): with self.assertWarns(ImportWarning): self.import_module({'__name__': 'pkg.fake', '__path__': []}) @@ -75,8 +73,6 @@ def test_spec_fallback(self): module = self.import_module({'__spec__': FakeSpec('pkg.fake')}) self.assertEqual(module.__name__, 'pkg') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_warn_when_package_and_spec_disagree(self): # Raise a DeprecationWarning if __package__ != __spec__.parent. with self.assertWarns(DeprecationWarning): diff --git a/Lib/test/test_importlib/import_/test_helpers.py b/Lib/test/test_importlib/import_/test_helpers.py index 28cdc0e526e..550f88d1d7a 100644 --- a/Lib/test/test_importlib/import_/test_helpers.py +++ b/Lib/test/test_importlib/import_/test_helpers.py @@ -126,8 +126,6 @@ def test_gh86298_loader_is_none_and_spec_loader_is_none(self): ValueError, _bootstrap_external._bless_my_loader, bar.__dict__) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_gh86298_no_spec(self): bar = ModuleType('bar') bar.__loader__ = object() @@ -137,8 +135,6 @@ def test_gh86298_no_spec(self): DeprecationWarning, _bootstrap_external._bless_my_loader, bar.__dict__) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_gh86298_spec_is_none(self): bar = ModuleType('bar') bar.__loader__ = object() @@ -148,8 +144,6 @@ def test_gh86298_spec_is_none(self): DeprecationWarning, _bootstrap_external._bless_my_loader, bar.__dict__) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_gh86298_no_spec_loader(self): bar = ModuleType('bar') bar.__loader__ = object() @@ -159,8 +153,6 @@ def test_gh86298_no_spec_loader(self): DeprecationWarning, _bootstrap_external._bless_my_loader, bar.__dict__) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_gh86298_loader_and_spec_loader_disagree(self): bar = ModuleType('bar') bar.__loader__ = object() diff --git a/Lib/test/test_importlib/import_/test_meta_path.py b/Lib/test/test_importlib/import_/test_meta_path.py index 26e7b070b95..8689017ba43 100644 --- a/Lib/test/test_importlib/import_/test_meta_path.py +++ b/Lib/test/test_importlib/import_/test_meta_path.py @@ -30,8 +30,6 @@ def test_continuing(self): with util.import_state(meta_path=[first, second]): self.assertIs(self.__import__(mod_name), second.modules[mod_name]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_empty(self): # Raise an ImportWarning if sys.meta_path is empty. module_name = 'nothing' diff --git a/Lib/test/test_importlib/import_/test_path.py b/Lib/test/test_importlib/import_/test_path.py index 9cf3a77cb84..89b52fbd1e1 100644 --- a/Lib/test/test_importlib/import_/test_path.py +++ b/Lib/test/test_importlib/import_/test_path.py @@ -68,8 +68,6 @@ def test_path_hooks(self): self.assertIn(path, sys.path_importer_cache) self.assertIs(sys.path_importer_cache[path], importer) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_empty_path_hooks(self): # Test that if sys.path_hooks is empty a warning is raised, # sys.path_importer_cache gets None set, and PathFinder returns None. diff --git a/crates/vm/src/warn.rs b/crates/vm/src/warn.rs index b632495eb4a..3dbd43ab537 100644 --- a/crates/vm/src/warn.rs +++ b/crates/vm/src/warn.rs @@ -67,8 +67,15 @@ fn get_warnings_attr( Err(_) => return Ok(None), } } else { - // TODO: finalizing support - return Ok(None); + // Check sys.modules for already-imported warnings module + // This is what CPython does with PyImport_GetModule + match vm.sys_module.get_attr(identifier!(vm, modules), vm) { + Ok(modules) => match modules.get_item(vm.ctx.intern_str("warnings"), vm) { + Ok(module) => module, + Err(_) => return Ok(None), + }, + Err(_) => return Ok(None), + } }; Ok(Some(module.get_attr(attr_name, vm)?)) } @@ -320,9 +327,13 @@ fn call_show_warning( return Err(vm.new_type_error("unable to get warnings.WarningMessage")); }; + // Create a Warning instance by calling category(message) + // This is what warnings module does + let warning_instance = category.as_object().call((message,), vm)?; + let msg = warnmsg_cls.call( vec![ - message.into(), + warning_instance, category.into(), filename.into(), vm.new_pyobj(lineno), From 8d9002800ea4ca75c8455383f05813351ef15007 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 27 Dec 2025 14:53:37 +0100 Subject: [PATCH 597/819] Update `test_dis.py` from 3.13.11 (#6528) --- Lib/dis.py | 29 +- Lib/test/test_dis.py | 2461 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 2434 insertions(+), 56 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 05de51ce49c..6583cab62b8 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -1052,21 +1052,30 @@ def dis(self): return output.getvalue() -from _dis import * +from _dis import * # TODO: RUSTPYTHON; Remove this import (and module) -# Disassembling a file by following cpython Lib/dis.py -def _test(): - """Simple test program to disassemble a file.""" +def main(args=None): import argparse parser = argparse.ArgumentParser() - parser.add_argument('infile', type=argparse.FileType('rb'), nargs='?', default='-') - args = parser.parse_args() - with args.infile as infile: - source = infile.read() - code = compile(source, args.infile.name, "exec") + parser.add_argument('-C', '--show-caches', action='store_true', + help='show inline caches') + parser.add_argument('-O', '--show-offsets', action='store_true', + help='show instruction offsets') + parser.add_argument('infile', nargs='?', default='-') + args = parser.parse_args(args=args) + if args.infile == '-': + name = '<stdin>' + source = sys.stdin.buffer.read() + else: + name = args.infile + with open(args.infile, 'rb') as infile: + source = infile.read() + code = compile(source, name, "exec") + # TODO: RUSTPYTHON; Add support for `show_caches` & `show_offsets` arguments + # dis(code, show_caches=args.show_caches, show_offsets=args.show_offsets) dis(code) if __name__ == "__main__": - _test() + main() diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 8bbba86a464..dab8d73e54c 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -1,59 +1,2428 @@ -import subprocess +# Minimal tests for dis module + +import contextlib +import dis +import functools +import io +import itertools +import opcode +import re import sys +import tempfile +import textwrap +import types import unittest +from test.support import (captured_stdout, requires_debug_ranges, + requires_specialization, cpython_only, + os_helper) +from test.support.bytecode_helper import BytecodeTestCase + + +CACHE = dis.opmap["CACHE"] + +def get_tb(): + def _error(): + try: + 1 / 0 + except Exception as e: + tb = e.__traceback__ + return tb + + tb = _error() + while tb.tb_next: + tb = tb.tb_next + return tb -# This only tests that it prints something in order -# to avoid changing this test if the bytecode changes +TRACEBACK_CODE = get_tb().tb_frame.f_code -# These tests start a new process instead of redirecting stdout because -# stdout is being written to by rust code, which currently can't be -# redirected by reassigning sys.stdout +class _C: + def __init__(self, x): + self.x = x == 1 + @staticmethod + def sm(x): + x = x == 1 -class TestDis(unittest.TestCase): @classmethod - def setUpClass(cls): - cls.setup = """ -import dis -def tested_func(): pass + def cm(cls, x): + cls.x = x == 1 + +dis_c_instance_method = """\ +%3d RESUME 0 + +%3d LOAD_FAST 1 (x) + LOAD_CONST 1 (1) + COMPARE_OP 72 (==) + LOAD_FAST 0 (self) + STORE_ATTR 0 (x) + RETURN_CONST 0 (None) +""" % (_C.__init__.__code__.co_firstlineno, _C.__init__.__code__.co_firstlineno + 1,) + +dis_c_instance_method_bytes = """\ + RESUME 0 + LOAD_FAST 1 + LOAD_CONST 1 + COMPARE_OP 72 (==) + LOAD_FAST 0 + STORE_ATTR 0 + RETURN_CONST 0 +""" + +dis_c_class_method = """\ +%3d RESUME 0 + +%3d LOAD_FAST 1 (x) + LOAD_CONST 1 (1) + COMPARE_OP 72 (==) + LOAD_FAST 0 (cls) + STORE_ATTR 0 (x) + RETURN_CONST 0 (None) +""" % (_C.cm.__code__.co_firstlineno, _C.cm.__code__.co_firstlineno + 2,) + +dis_c_static_method = """\ +%3d RESUME 0 + +%3d LOAD_FAST 0 (x) + LOAD_CONST 1 (1) + COMPARE_OP 72 (==) + STORE_FAST 0 (x) + RETURN_CONST 0 (None) +""" % (_C.sm.__code__.co_firstlineno, _C.sm.__code__.co_firstlineno + 2,) + +# Class disassembling info has an extra newline at end. +dis_c = """\ +Disassembly of %s: +%s +Disassembly of %s: +%s +Disassembly of %s: +%s +""" % (_C.__init__.__name__, dis_c_instance_method, + _C.cm.__name__, dis_c_class_method, + _C.sm.__name__, dis_c_static_method) + +def _f(a): + print(a) + return 1 + +dis_f = """\ +%3d RESUME 0 + +%3d LOAD_GLOBAL 1 (print + NULL) + LOAD_FAST 0 (a) + CALL 1 + POP_TOP + +%3d RETURN_CONST 1 (1) +""" % (_f.__code__.co_firstlineno, + _f.__code__.co_firstlineno + 1, + _f.__code__.co_firstlineno + 2) + +dis_f_with_offsets = """\ +%3d 0 RESUME 0 + +%3d 2 LOAD_GLOBAL 1 (print + NULL) + 12 LOAD_FAST 0 (a) + 14 CALL 1 + 22 POP_TOP + +%3d 24 RETURN_CONST 1 (1) +""" % (_f.__code__.co_firstlineno, + _f.__code__.co_firstlineno + 1, + _f.__code__.co_firstlineno + 2) + + +dis_f_co_code = """\ + RESUME 0 + LOAD_GLOBAL 1 + LOAD_FAST 0 + CALL 1 + POP_TOP + RETURN_CONST 1 +""" + +def bug708901(): + for res in range(1, + 10): + pass + +dis_bug708901 = """\ +%3d RESUME 0 + +%3d LOAD_GLOBAL 1 (range + NULL) + LOAD_CONST 1 (1) + +%3d LOAD_CONST 2 (10) + +%3d CALL 2 + GET_ITER + L1: FOR_ITER 3 (to L2) + STORE_FAST 0 (res) + +%3d JUMP_BACKWARD 5 (to L1) + +%3d L2: END_FOR + POP_TOP + RETURN_CONST 0 (None) +""" % (bug708901.__code__.co_firstlineno, + bug708901.__code__.co_firstlineno + 1, + bug708901.__code__.co_firstlineno + 2, + bug708901.__code__.co_firstlineno + 1, + bug708901.__code__.co_firstlineno + 3, + bug708901.__code__.co_firstlineno + 1) + + +def bug1333982(x=[]): + assert 0, ((s for s in x) + + 1) + pass + +dis_bug1333982 = """\ +%3d RESUME 0 + +%3d LOAD_ASSERTION_ERROR + LOAD_CONST 1 (<code object <genexpr> at 0x..., file "%s", line %d>) + MAKE_FUNCTION + LOAD_FAST 0 (x) + GET_ITER + CALL 0 + +%3d LOAD_CONST 2 (1) + +%3d BINARY_OP 0 (+) + CALL 0 + RAISE_VARARGS 1 +""" % (bug1333982.__code__.co_firstlineno, + bug1333982.__code__.co_firstlineno + 1, + __file__, + bug1333982.__code__.co_firstlineno + 1, + bug1333982.__code__.co_firstlineno + 2, + bug1333982.__code__.co_firstlineno + 1) + + +def bug42562(): + pass + + +# Set line number for 'pass' to None +bug42562.__code__ = bug42562.__code__.replace(co_linetable=b'\xf8') + + +dis_bug42562 = """\ + RESUME 0 + RETURN_CONST 0 (None) +""" + +# Extended arg followed by NOP +code_bug_45757 = bytes([ + opcode.opmap['EXTENDED_ARG'], 0x01, # EXTENDED_ARG 0x01 + opcode.opmap['NOP'], 0xFF, # NOP 0xFF + opcode.opmap['EXTENDED_ARG'], 0x01, # EXTENDED_ARG 0x01 + opcode.opmap['LOAD_CONST'], 0x29, # LOAD_CONST 0x29 + opcode.opmap['RETURN_VALUE'], 0x00, # RETURN_VALUE 0x00 + ]) + +dis_bug_45757 = """\ + EXTENDED_ARG 1 + NOP + EXTENDED_ARG 1 + LOAD_CONST 297 + RETURN_VALUE """ - cls.command = (sys.executable, "-c") +# [255, 255, 255, 252] is -4 in a 4 byte signed integer +bug46724 = bytes([ + opcode.EXTENDED_ARG, 255, + opcode.EXTENDED_ARG, 255, + opcode.EXTENDED_ARG, 255, + opcode.opmap['JUMP_FORWARD'], 252, +]) + + +dis_bug46724 = """\ + L1: EXTENDED_ARG 255 + EXTENDED_ARG 65535 + EXTENDED_ARG 16777215 + JUMP_FORWARD -4 (to L1) +""" + +def func_w_kwargs(a, b, **c): + pass + +def wrap_func_w_kwargs(): + func_w_kwargs(1, 2, c=5) + +dis_kw_names = """\ +%3d RESUME 0 + +%3d LOAD_GLOBAL 1 (func_w_kwargs + NULL) + LOAD_CONST 1 (1) + LOAD_CONST 2 (2) + LOAD_CONST 3 (5) + LOAD_CONST 4 (('c',)) + CALL_KW 3 + POP_TOP + RETURN_CONST 0 (None) +""" % (wrap_func_w_kwargs.__code__.co_firstlineno, + wrap_func_w_kwargs.__code__.co_firstlineno + 1) + +dis_intrinsic_1_2 = """\ + 0 RESUME 0 + + 1 LOAD_CONST 0 (0) + LOAD_CONST 1 (('*',)) + IMPORT_NAME 0 (math) + CALL_INTRINSIC_1 2 (INTRINSIC_IMPORT_STAR) + POP_TOP + RETURN_CONST 2 (None) +""" + +dis_intrinsic_1_5 = """\ + 0 RESUME 0 + + 1 LOAD_NAME 0 (a) + CALL_INTRINSIC_1 5 (INTRINSIC_UNARY_POSITIVE) + RETURN_VALUE +""" + +dis_intrinsic_1_6 = """\ + 0 RESUME 0 + + 1 BUILD_LIST 0 + LOAD_NAME 0 (a) + LIST_EXTEND 1 + CALL_INTRINSIC_1 6 (INTRINSIC_LIST_TO_TUPLE) + RETURN_VALUE +""" + +_BIG_LINENO_FORMAT = """\ + 1 RESUME 0 + +%3d LOAD_GLOBAL 0 (spam) + POP_TOP + RETURN_CONST 0 (None) +""" + +_BIG_LINENO_FORMAT2 = """\ + 1 RESUME 0 + +%4d LOAD_GLOBAL 0 (spam) + POP_TOP + RETURN_CONST 0 (None) +""" + +dis_module_expected_results = """\ +Disassembly of f: + 4 RESUME 0 + RETURN_CONST 0 (None) + +Disassembly of g: + 5 RESUME 0 + RETURN_CONST 0 (None) + +""" + +expr_str = "x + 1" + +dis_expr_str = """\ + 0 RESUME 0 + + 1 LOAD_NAME 0 (x) + LOAD_CONST 0 (1) + BINARY_OP 0 (+) + RETURN_VALUE +""" + +simple_stmt_str = "x = x + 1" + +dis_simple_stmt_str = """\ + 0 RESUME 0 + + 1 LOAD_NAME 0 (x) + LOAD_CONST 0 (1) + BINARY_OP 0 (+) + STORE_NAME 0 (x) + RETURN_CONST 1 (None) +""" + +annot_stmt_str = """\ + +x: int = 1 +y: fun(1) +lst[fun(0)]: int = 1 +""" +# leading newline is for a reason (tests lineno) + +dis_annot_stmt_str = """\ + 0 RESUME 0 + + 2 SETUP_ANNOTATIONS + LOAD_CONST 0 (1) + STORE_NAME 0 (x) + LOAD_NAME 1 (int) + LOAD_NAME 2 (__annotations__) + LOAD_CONST 1 ('x') + STORE_SUBSCR + + 3 LOAD_NAME 3 (fun) + PUSH_NULL + LOAD_CONST 0 (1) + CALL 1 + LOAD_NAME 2 (__annotations__) + LOAD_CONST 2 ('y') + STORE_SUBSCR + + 4 LOAD_CONST 0 (1) + LOAD_NAME 4 (lst) + LOAD_NAME 3 (fun) + PUSH_NULL + LOAD_CONST 3 (0) + CALL 1 + STORE_SUBSCR + LOAD_NAME 1 (int) + POP_TOP + RETURN_CONST 4 (None) +""" + +compound_stmt_str = """\ +x = 0 +while 1: + x += 1""" +# Trailing newline has been deliberately omitted + +dis_compound_stmt_str = """\ + 0 RESUME 0 + + 1 LOAD_CONST 0 (0) + STORE_NAME 0 (x) + + 2 NOP + + 3 L1: LOAD_NAME 0 (x) + LOAD_CONST 1 (1) + BINARY_OP 13 (+=) + STORE_NAME 0 (x) + + 2 JUMP_BACKWARD 7 (to L1) +""" + +dis_traceback = """\ +%4d RESUME 0 + +%4d NOP + +%4d L1: LOAD_CONST 1 (1) + LOAD_CONST 2 (0) + --> BINARY_OP 11 (/) + POP_TOP + +%4d L2: LOAD_FAST_CHECK 1 (tb) + RETURN_VALUE + + -- L3: PUSH_EXC_INFO + +%4d LOAD_GLOBAL 0 (Exception) + CHECK_EXC_MATCH + POP_JUMP_IF_FALSE 23 (to L7) + STORE_FAST 0 (e) + +%4d L4: LOAD_FAST 0 (e) + LOAD_ATTR 2 (__traceback__) + STORE_FAST 1 (tb) + L5: POP_EXCEPT + LOAD_CONST 0 (None) + STORE_FAST 0 (e) + DELETE_FAST 0 (e) + +%4d LOAD_FAST 1 (tb) + RETURN_VALUE + + -- L6: LOAD_CONST 0 (None) + STORE_FAST 0 (e) + DELETE_FAST 0 (e) + RERAISE 1 + +%4d L7: RERAISE 0 + + -- L8: COPY 3 + POP_EXCEPT + RERAISE 1 +ExceptionTable: + L1 to L2 -> L3 [0] + L3 to L4 -> L8 [1] lasti + L4 to L5 -> L6 [1] lasti + L6 to L8 -> L8 [1] lasti +""" % (TRACEBACK_CODE.co_firstlineno, + TRACEBACK_CODE.co_firstlineno + 1, + TRACEBACK_CODE.co_firstlineno + 2, + TRACEBACK_CODE.co_firstlineno + 5, + TRACEBACK_CODE.co_firstlineno + 3, + TRACEBACK_CODE.co_firstlineno + 4, + TRACEBACK_CODE.co_firstlineno + 5, + TRACEBACK_CODE.co_firstlineno + 3) + +def _fstring(a, b, c, d): + return f'{a} {b:4} {c!r} {d!r:4}' + +dis_fstring = """\ +%3d RESUME 0 + +%3d LOAD_FAST 0 (a) + FORMAT_SIMPLE + LOAD_CONST 1 (' ') + LOAD_FAST 1 (b) + LOAD_CONST 2 ('4') + FORMAT_WITH_SPEC + LOAD_CONST 1 (' ') + LOAD_FAST 2 (c) + CONVERT_VALUE 2 (repr) + FORMAT_SIMPLE + LOAD_CONST 1 (' ') + LOAD_FAST 3 (d) + CONVERT_VALUE 2 (repr) + LOAD_CONST 2 ('4') + FORMAT_WITH_SPEC + BUILD_STRING 7 + RETURN_VALUE +""" % (_fstring.__code__.co_firstlineno, _fstring.__code__.co_firstlineno + 1) + +def _with(c): + with c: + x = 1 + y = 2 + +dis_with = """\ +%4d RESUME 0 + +%4d LOAD_FAST 0 (c) + BEFORE_WITH + L1: POP_TOP + +%4d LOAD_CONST 1 (1) + STORE_FAST 1 (x) + +%4d L2: LOAD_CONST 0 (None) + LOAD_CONST 0 (None) + LOAD_CONST 0 (None) + CALL 2 + POP_TOP + +%4d LOAD_CONST 2 (2) + STORE_FAST 2 (y) + RETURN_CONST 0 (None) + +%4d L3: PUSH_EXC_INFO + WITH_EXCEPT_START + TO_BOOL + POP_JUMP_IF_TRUE 1 (to L4) + RERAISE 2 + L4: POP_TOP + L5: POP_EXCEPT + POP_TOP + POP_TOP + +%4d LOAD_CONST 2 (2) + STORE_FAST 2 (y) + RETURN_CONST 0 (None) + + -- L6: COPY 3 + POP_EXCEPT + RERAISE 1 +ExceptionTable: + L1 to L2 -> L3 [1] lasti + L3 to L5 -> L6 [3] lasti +""" % (_with.__code__.co_firstlineno, + _with.__code__.co_firstlineno + 1, + _with.__code__.co_firstlineno + 2, + _with.__code__.co_firstlineno + 1, + _with.__code__.co_firstlineno + 3, + _with.__code__.co_firstlineno + 1, + _with.__code__.co_firstlineno + 3, + ) + +async def _asyncwith(c): + async with c: + x = 1 + y = 2 + +dis_asyncwith = """\ +%4d RETURN_GENERATOR + POP_TOP + L1: RESUME 0 + +%4d LOAD_FAST 0 (c) + BEFORE_ASYNC_WITH + GET_AWAITABLE 1 + LOAD_CONST 0 (None) + L2: SEND 3 (to L5) + L3: YIELD_VALUE 1 + L4: RESUME 3 + JUMP_BACKWARD_NO_INTERRUPT 5 (to L2) + L5: END_SEND + L6: POP_TOP + +%4d LOAD_CONST 1 (1) + STORE_FAST 1 (x) + +%4d L7: LOAD_CONST 0 (None) + LOAD_CONST 0 (None) + LOAD_CONST 0 (None) + CALL 2 + GET_AWAITABLE 2 + LOAD_CONST 0 (None) + L8: SEND 3 (to L11) + L9: YIELD_VALUE 1 + L10: RESUME 3 + JUMP_BACKWARD_NO_INTERRUPT 5 (to L8) + L11: END_SEND + POP_TOP + +%4d LOAD_CONST 2 (2) + STORE_FAST 2 (y) + RETURN_CONST 0 (None) + +%4d L12: CLEANUP_THROW + L13: JUMP_BACKWARD_NO_INTERRUPT 25 (to L5) + L14: CLEANUP_THROW + L15: JUMP_BACKWARD_NO_INTERRUPT 9 (to L11) + L16: PUSH_EXC_INFO + WITH_EXCEPT_START + GET_AWAITABLE 2 + LOAD_CONST 0 (None) + L17: SEND 4 (to L21) + L18: YIELD_VALUE 1 + L19: RESUME 3 + JUMP_BACKWARD_NO_INTERRUPT 5 (to L17) + L20: CLEANUP_THROW + L21: END_SEND + TO_BOOL + POP_JUMP_IF_TRUE 1 (to L22) + RERAISE 2 + L22: POP_TOP + L23: POP_EXCEPT + POP_TOP + POP_TOP + +%4d LOAD_CONST 2 (2) + STORE_FAST 2 (y) + RETURN_CONST 0 (None) + + -- L24: COPY 3 + POP_EXCEPT + RERAISE 1 + L25: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + RERAISE 1 +ExceptionTable: + L1 to L3 -> L25 [0] lasti + L3 to L4 -> L12 [3] + L4 to L6 -> L25 [0] lasti + L6 to L7 -> L16 [1] lasti + L7 to L9 -> L25 [0] lasti + L9 to L10 -> L14 [2] + L10 to L13 -> L25 [0] lasti + L14 to L15 -> L25 [0] lasti + L16 to L18 -> L24 [3] lasti + L18 to L19 -> L20 [6] + L19 to L23 -> L24 [3] lasti + L23 to L25 -> L25 [0] lasti +""" % (_asyncwith.__code__.co_firstlineno, + _asyncwith.__code__.co_firstlineno + 1, + _asyncwith.__code__.co_firstlineno + 2, + _asyncwith.__code__.co_firstlineno + 1, + _asyncwith.__code__.co_firstlineno + 3, + _asyncwith.__code__.co_firstlineno + 1, + _asyncwith.__code__.co_firstlineno + 3, + ) + + +def _tryfinally(a, b): + try: + return a + finally: + b() + +def _tryfinallyconst(b): + try: + return 1 + finally: + b() + +dis_tryfinally = """\ +%4d RESUME 0 + +%4d NOP + +%4d L1: LOAD_FAST 0 (a) + +%4d L2: LOAD_FAST 1 (b) + PUSH_NULL + CALL 0 + POP_TOP + RETURN_VALUE + + -- L3: PUSH_EXC_INFO + +%4d LOAD_FAST 1 (b) + PUSH_NULL + CALL 0 + POP_TOP + RERAISE 0 + + -- L4: COPY 3 + POP_EXCEPT + RERAISE 1 +ExceptionTable: + L1 to L2 -> L3 [0] + L3 to L4 -> L4 [1] lasti +""" % (_tryfinally.__code__.co_firstlineno, + _tryfinally.__code__.co_firstlineno + 1, + _tryfinally.__code__.co_firstlineno + 2, + _tryfinally.__code__.co_firstlineno + 4, + _tryfinally.__code__.co_firstlineno + 4, + ) + +dis_tryfinallyconst = """\ +%4d RESUME 0 + +%4d NOP + +%4d NOP + +%4d LOAD_FAST 0 (b) + PUSH_NULL + CALL 0 + POP_TOP + RETURN_CONST 1 (1) + + -- L1: PUSH_EXC_INFO + +%4d LOAD_FAST 0 (b) + PUSH_NULL + CALL 0 + POP_TOP + RERAISE 0 + + -- L2: COPY 3 + POP_EXCEPT + RERAISE 1 +ExceptionTable: + L1 to L2 -> L2 [1] lasti +""" % (_tryfinallyconst.__code__.co_firstlineno, + _tryfinallyconst.__code__.co_firstlineno + 1, + _tryfinallyconst.__code__.co_firstlineno + 2, + _tryfinallyconst.__code__.co_firstlineno + 4, + _tryfinallyconst.__code__.co_firstlineno + 4, + ) + +def _g(x): + yield x + +async def _ag(x): + yield x + +async def _co(x): + async for item in _ag(x): + pass + +def _h(y): + def foo(x): + '''funcdoc''' + return list(x + z for z in y) + return foo + +dis_nested_0 = """\ + -- MAKE_CELL 0 (y) + +%4d RESUME 0 + +%4d LOAD_FAST 0 (y) + BUILD_TUPLE 1 + LOAD_CONST 1 (<code object foo at 0x..., file "%s", line %d>) + MAKE_FUNCTION + SET_FUNCTION_ATTRIBUTE 8 (closure) + STORE_FAST 1 (foo) + +%4d LOAD_FAST 1 (foo) + RETURN_VALUE +""" % (_h.__code__.co_firstlineno, + _h.__code__.co_firstlineno + 1, + __file__, + _h.__code__.co_firstlineno + 1, + _h.__code__.co_firstlineno + 4, +) + +dis_nested_1 = """%s +Disassembly of <code object foo at 0x..., file "%s", line %d>: + -- COPY_FREE_VARS 1 + MAKE_CELL 0 (x) + +%4d RESUME 0 + +%4d LOAD_GLOBAL 1 (list + NULL) + LOAD_FAST 0 (x) + BUILD_TUPLE 1 + LOAD_CONST 1 (<code object <genexpr> at 0x..., file "%s", line %d>) + MAKE_FUNCTION + SET_FUNCTION_ATTRIBUTE 8 (closure) + LOAD_DEREF 1 (y) + GET_ITER + CALL 0 + CALL 1 + RETURN_VALUE +""" % (dis_nested_0, + __file__, + _h.__code__.co_firstlineno + 1, + _h.__code__.co_firstlineno + 1, + _h.__code__.co_firstlineno + 3, + __file__, + _h.__code__.co_firstlineno + 3, +) + +dis_nested_2 = """%s +Disassembly of <code object <genexpr> at 0x..., file "%s", line %d>: + -- COPY_FREE_VARS 1 + +%4d RETURN_GENERATOR + POP_TOP + L1: RESUME 0 + LOAD_FAST 0 (.0) + GET_ITER + L2: FOR_ITER 10 (to L3) + STORE_FAST 1 (z) + LOAD_DEREF 2 (x) + LOAD_FAST 1 (z) + BINARY_OP 0 (+) + YIELD_VALUE 0 + RESUME 5 + POP_TOP + JUMP_BACKWARD 12 (to L2) + L3: END_FOR + POP_TOP + RETURN_CONST 0 (None) + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [0] lasti +""" % (dis_nested_1, + __file__, + _h.__code__.co_firstlineno + 3, + _h.__code__.co_firstlineno + 3, +) + +def load_test(x, y=0): + a, b = x, y + return a, b + +dis_load_test_quickened_code = """\ +%3d RESUME_CHECK 0 + +%3d LOAD_FAST_LOAD_FAST 1 (x, y) + STORE_FAST_STORE_FAST 50 (b, a) + +%3d LOAD_FAST_LOAD_FAST 35 (a, b) + BUILD_TUPLE 2 + RETURN_VALUE +""" % (load_test.__code__.co_firstlineno, + load_test.__code__.co_firstlineno + 1, + load_test.__code__.co_firstlineno + 2) + +def loop_test(): + for i in [1, 2, 3] * 3: + load_test(i) + +dis_loop_test_quickened_code = """\ +%3d RESUME_CHECK 0 + +%3d BUILD_LIST 0 + LOAD_CONST 1 ((1, 2, 3)) + LIST_EXTEND 1 + LOAD_CONST 2 (3) + BINARY_OP 5 (*) + GET_ITER + L1: FOR_ITER_LIST 14 (to L2) + STORE_FAST 0 (i) + +%3d LOAD_GLOBAL_MODULE 1 (load_test + NULL) + LOAD_FAST 0 (i) + CALL_PY_GENERAL 1 + POP_TOP + JUMP_BACKWARD 16 (to L1) + +%3d L2: END_FOR + POP_TOP + RETURN_CONST 0 (None) +""" % (loop_test.__code__.co_firstlineno, + loop_test.__code__.co_firstlineno + 1, + loop_test.__code__.co_firstlineno + 2, + loop_test.__code__.co_firstlineno + 1,) + +def extended_arg_quick(): + *_, _ = ... + +dis_extended_arg_quick_code = """\ +%3d RESUME 0 + +%3d LOAD_CONST 1 (Ellipsis) + EXTENDED_ARG 1 + UNPACK_EX 256 + POP_TOP + STORE_FAST 0 (_) + RETURN_CONST 0 (None) +"""% (extended_arg_quick.__code__.co_firstlineno, + extended_arg_quick.__code__.co_firstlineno + 1,) + +ADAPTIVE_WARMUP_DELAY = 2 + +class DisTestBase(unittest.TestCase): + "Common utilities for DisTests and TestDisTraceback" + + def strip_addresses(self, text): + return re.sub(r'\b0x[0-9A-Fa-f]+\b', '0x...', text) + + def assert_exception_table_increasing(self, lines): + prev_start, prev_end = -1, -1 + count = 0 + for line in lines: + m = re.match(r' L(\d+) to L(\d+) -> L\d+ \[\d+\]', line) + start, end = [int(g) for g in m.groups()] + self.assertGreaterEqual(end, start) + self.assertGreaterEqual(start, prev_end) + prev_start, prev_end = start, end + count += 1 + return count + + def do_disassembly_compare(self, got, expected): + if got != expected: + got = self.strip_addresses(got) + self.assertEqual(got, expected) + + +class DisTests(DisTestBase): + + maxDiff = None + + def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs): + # We want to test the default printing behaviour, not the file arg + output = io.StringIO() + with contextlib.redirect_stdout(output): + if wrapper: + dis.dis(func, **kwargs) + else: + dis.disassemble(func, lasti, **kwargs) + return output.getvalue() + + def get_disassemble_as_string(self, func, lasti=-1): + return self.get_disassembly(func, lasti, False) + + def do_disassembly_test(self, func, expected, **kwargs): + self.maxDiff = None + got = self.get_disassembly(func, depth=0, **kwargs) + self.do_disassembly_compare(got, expected) + # Add checks for dis.disco + if hasattr(func, '__code__'): + got_disco = io.StringIO() + with contextlib.redirect_stdout(got_disco): + dis.disco(func.__code__, **kwargs) + self.do_disassembly_compare(got_disco.getvalue(), expected) + + def test_opmap(self): + self.assertEqual(dis.opmap["CACHE"], 0) + self.assertIn(dis.opmap["LOAD_CONST"], dis.hasconst) + self.assertIn(dis.opmap["STORE_NAME"], dis.hasname) + + def test_opname(self): + self.assertEqual(dis.opname[dis.opmap["LOAD_FAST"]], "LOAD_FAST") + + def test_boundaries(self): + self.assertEqual(dis.opmap["EXTENDED_ARG"], dis.EXTENDED_ARG) + + def test_widths(self): + long_opcodes = set(['JUMP_BACKWARD_NO_INTERRUPT', + 'INSTRUMENTED_CALL_FUNCTION_EX']) + for opcode, opname in enumerate(dis.opname): + if opname in long_opcodes or opname.startswith("INSTRUMENTED"): + continue + with self.subTest(opname=opname): + width = dis._OPNAME_WIDTH + if opcode in dis.hasarg: + width += 1 + dis._OPARG_WIDTH + self.assertLessEqual(len(opname), width) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dis(self): - test_code = f""" -{self.setup} -dis.dis(tested_func) -dis.dis("x = 2; print(x)") -""" - - result = subprocess.run( - self.command + (test_code,), capture_output=True - ) - self.assertNotEqual("", result.stdout.decode()) - self.assertEqual("", result.stderr.decode()) - - def test_disassemble(self): - test_code = f""" -{self.setup} -dis.disassemble(tested_func) -""" - result = subprocess.run( - self.command + (test_code,), capture_output=True - ) - # In CPython this would raise an AttributeError, not a - # TypeError because dis is implemented in python in CPython and - # as such the type mismatch wouldn't be caught immeadiately - self.assertIn("TypeError", result.stderr.decode()) - - test_code = f""" -{self.setup} -dis.disassemble(tested_func.__code__) -""" - result = subprocess.run( - self.command + (test_code,), capture_output=True - ) - self.assertNotEqual("", result.stdout.decode()) - self.assertEqual("", result.stderr.decode()) + self.do_disassembly_test(_f, dis_f) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_dis_with_offsets(self): + self.do_disassembly_test(_f, dis_f_with_offsets, show_offsets=True) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_bug_708901(self): + self.do_disassembly_test(bug708901, dis_bug708901) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_bug_1333982(self): + # This one is checking bytecodes generated for an `assert` statement, + # so fails if the tests are run with -O. Skip this test then. + if not __debug__: + self.skipTest('need asserts, run without -O') + + self.do_disassembly_test(bug1333982, dis_bug1333982) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_bug_42562(self): + self.do_disassembly_test(bug42562, dis_bug42562) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_bug_45757(self): + # Extended arg followed by NOP + self.do_disassembly_test(code_bug_45757, dis_bug_45757) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_bug_46724(self): + # Test that negative operargs are handled properly + self.do_disassembly_test(bug46724, dis_bug46724) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_kw_names(self): + # Test that value is displayed for keyword argument names: + self.do_disassembly_test(wrap_func_w_kwargs, dis_kw_names) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_intrinsic_1(self): + # Test that argrepr is displayed for CALL_INTRINSIC_1 + self.do_disassembly_test("from math import *", dis_intrinsic_1_2) + self.do_disassembly_test("+a", dis_intrinsic_1_5) + self.do_disassembly_test("(*a,)", dis_intrinsic_1_6) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_intrinsic_2(self): + self.assertIn("CALL_INTRINSIC_2 1 (INTRINSIC_PREP_RERAISE_STAR)", + self.get_disassembly("try: pass\nexcept* Exception: x")) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_big_linenos(self): + def func(count): + namespace = {} + func = "def foo():\n " + "".join(["\n "] * count + ["spam\n"]) + exec(func, namespace) + return namespace['foo'] + + # Test all small ranges + for i in range(1, 300): + expected = _BIG_LINENO_FORMAT % (i + 2) + self.do_disassembly_test(func(i), expected) + + # Test some larger ranges too + for i in range(300, 1000, 10): + expected = _BIG_LINENO_FORMAT % (i + 2) + self.do_disassembly_test(func(i), expected) + + for i in range(1000, 5000, 10): + expected = _BIG_LINENO_FORMAT2 % (i + 2) + self.do_disassembly_test(func(i), expected) + + from test import dis_module + self.do_disassembly_test(dis_module, dis_module_expected_results) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_str(self): + self.do_disassembly_test(expr_str, dis_expr_str) + self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str) + self.do_disassembly_test(annot_stmt_str, dis_annot_stmt_str) + self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_bytes(self): + self.do_disassembly_test(_f.__code__.co_code, dis_f_co_code) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_class(self): + self.do_disassembly_test(_C, dis_c) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_instance_method(self): + self.do_disassembly_test(_C(1).__init__, dis_c_instance_method) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_instance_method_bytes(self): + method_bytecode = _C(1).__init__.__code__.co_code + self.do_disassembly_test(method_bytecode, dis_c_instance_method_bytes) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_static_method(self): + self.do_disassembly_test(_C.sm, dis_c_static_method) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_class_method(self): + self.do_disassembly_test(_C.cm, dis_c_class_method) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_generator(self): + gen_func_disas = self.get_disassembly(_g) # Generator function + gen_disas = self.get_disassembly(_g(1)) # Generator iterator + self.assertEqual(gen_disas, gen_func_disas) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_async_generator(self): + agen_func_disas = self.get_disassembly(_ag) # Async generator function + agen_disas = self.get_disassembly(_ag(1)) # Async generator iterator + self.assertEqual(agen_disas, agen_func_disas) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_coroutine(self): + coro_func_disas = self.get_disassembly(_co) # Coroutine function + coro = _co(1) # Coroutine object + coro.close() # Avoid a RuntimeWarning (never awaited) + coro_disas = self.get_disassembly(coro) + self.assertEqual(coro_disas, coro_func_disas) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_fstring(self): + self.do_disassembly_test(_fstring, dis_fstring) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_with(self): + self.do_disassembly_test(_with, dis_with) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_asyncwith(self): + self.do_disassembly_test(_asyncwith, dis_asyncwith) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_try_finally(self): + self.do_disassembly_test(_tryfinally, dis_tryfinally) + self.do_disassembly_test(_tryfinallyconst, dis_tryfinallyconst) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_dis_none(self): + try: + del sys.last_exc + except AttributeError: + pass + try: + del sys.last_traceback + except AttributeError: + pass + self.assertRaises(RuntimeError, dis.dis, None) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_dis_traceback(self): + self.maxDiff = None + try: + del sys.last_traceback + except AttributeError: + pass + + try: + 1/0 + except Exception as e: + tb = e.__traceback__ + sys.last_exc = e + + tb_dis = self.get_disassemble_as_string(tb.tb_frame.f_code, tb.tb_lasti) + self.do_disassembly_test(None, tb_dis) + + def test_dis_object(self): + self.assertRaises(TypeError, dis.dis, object()) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassemble_recursive(self): + def check(expected, **kwargs): + dis = self.get_disassembly(_h, **kwargs) + dis = self.strip_addresses(dis) + self.assertEqual(dis, expected) + + check(dis_nested_0, depth=0) + check(dis_nested_1, depth=1) + check(dis_nested_2, depth=2) + check(dis_nested_2, depth=3) + check(dis_nested_2, depth=None) + check(dis_nested_2) + + def test__try_compile_no_context_exc_on_error(self): + # see gh-102114 + try: + dis._try_compile(")", "") + except Exception as e: + self.assertIsNone(e.__context__) + + @staticmethod + def code_quicken(f, times=ADAPTIVE_WARMUP_DELAY): + for _ in range(times): + f() + + @cpython_only + @requires_specialization + def test_super_instructions(self): + self.code_quicken(lambda: load_test(0, 0)) + got = self.get_disassembly(load_test, adaptive=True) + self.do_disassembly_compare(got, dis_load_test_quickened_code) + + @cpython_only + @requires_specialization + def test_binary_specialize(self): + binary_op_quicken = """\ + 0 RESUME_CHECK 0 + + 1 LOAD_NAME 0 (a) + LOAD_NAME 1 (b) + %s + RETURN_VALUE +""" + co_int = compile('a + b', "<int>", "eval") + self.code_quicken(lambda: exec(co_int, {}, {'a': 1, 'b': 2})) + got = self.get_disassembly(co_int, adaptive=True) + self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADD_INT 0 (+)") + + co_unicode = compile('a + b', "<unicode>", "eval") + self.code_quicken(lambda: exec(co_unicode, {}, {'a': 'a', 'b': 'b'})) + got = self.get_disassembly(co_unicode, adaptive=True) + self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADD_UNICODE 0 (+)") + + binary_subscr_quicken = """\ + 0 RESUME_CHECK 0 + + 1 LOAD_NAME 0 (a) + LOAD_CONST 0 (0) + %s + RETURN_VALUE +""" + co_list = compile('a[0]', "<list>", "eval") + self.code_quicken(lambda: exec(co_list, {}, {'a': [0]})) + got = self.get_disassembly(co_list, adaptive=True) + self.do_disassembly_compare(got, binary_subscr_quicken % "BINARY_SUBSCR_LIST_INT") + + co_dict = compile('a[0]', "<dict>", "eval") + self.code_quicken(lambda: exec(co_dict, {}, {'a': {0: '1'}})) + got = self.get_disassembly(co_dict, adaptive=True) + self.do_disassembly_compare(got, binary_subscr_quicken % "BINARY_SUBSCR_DICT") + + @cpython_only + @requires_specialization + def test_load_attr_specialize(self): + load_attr_quicken = """\ + 0 RESUME_CHECK 0 + + 1 LOAD_CONST 0 ('a') + LOAD_ATTR_SLOT 0 (__class__) + RETURN_VALUE +""" + co = compile("'a'.__class__", "", "eval") + self.code_quicken(lambda: exec(co, {}, {})) + got = self.get_disassembly(co, adaptive=True) + self.do_disassembly_compare(got, load_attr_quicken) + + @cpython_only + @requires_specialization + def test_call_specialize(self): + call_quicken = """\ + 0 RESUME_CHECK 0 + + 1 LOAD_NAME 0 (str) + PUSH_NULL + LOAD_CONST 0 (1) + CALL_STR_1 1 + RETURN_VALUE +""" + co = compile("str(1)", "", "eval") + self.code_quicken(lambda: exec(co, {}, {})) + got = self.get_disassembly(co, adaptive=True) + self.do_disassembly_compare(got, call_quicken) + + @cpython_only + @requires_specialization + def test_loop_quicken(self): + # Loop can trigger a quicken where the loop is located + self.code_quicken(loop_test, 4) + got = self.get_disassembly(loop_test, adaptive=True) + expected = dis_loop_test_quickened_code + self.do_disassembly_compare(got, expected) + + @cpython_only + def test_extended_arg_quick(self): + got = self.get_disassembly(extended_arg_quick) + self.do_disassembly_compare(got, dis_extended_arg_quick_code) + + def get_cached_values(self, quickened, adaptive): + def f(): + l = [] + for i in range(42): + l.append(i) + if quickened: + self.code_quicken(f) + else: + # "copy" the code to un-quicken it: + f.__code__ = f.__code__.replace() + for instruction in _unroll_caches_as_Instructions(dis.get_instructions( + f, show_caches=True, adaptive=adaptive + ), show_caches=True): + if instruction.opname == "CACHE": + yield instruction.argrepr + + @cpython_only + def test_show_caches(self): + for quickened in (False, True): + for adaptive in (False, True): + with self.subTest(f"{quickened=}, {adaptive=}"): + if adaptive: + pattern = r"^(\w+: \d+)?$" + else: + pattern = r"^(\w+: 0)?$" + caches = list(self.get_cached_values(quickened, adaptive)) + for cache in caches: + self.assertRegex(cache, pattern) + total_caches = 21 + empty_caches = 7 + self.assertEqual(caches.count(""), empty_caches) + self.assertEqual(len(caches), total_caches) + + @cpython_only + def test_show_currinstr_with_cache(self): + """ + Make sure that with lasti pointing to CACHE, it still shows the current + line correctly + """ + def f(): + print(a) + # The code above should generate a LOAD_GLOBAL which has CACHE instr after + # However, this might change in the future. So we explicitly try to find + # a CACHE entry in the instructions. If we can't do that, fail the test + + for inst in _unroll_caches_as_Instructions( + dis.get_instructions(f, show_caches=True), show_caches=True): + if inst.opname == "CACHE": + op_offset = inst.offset - 2 + cache_offset = inst.offset + break + else: + opname = inst.opname + else: + self.fail("Can't find a CACHE entry in the function provided to do the test") + + assem_op = self.get_disassembly(f.__code__, lasti=op_offset, wrapper=False) + assem_cache = self.get_disassembly(f.__code__, lasti=cache_offset, wrapper=False) + + # Make sure --> exists and points to the correct op + self.assertRegex(assem_op, fr"--> {opname}") + # Make sure when lasti points to cache, it shows the same disassembly + self.assertEqual(assem_op, assem_cache) + + +class DisWithFileTests(DisTests): + + # Run the tests again, using the file arg instead of print + def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs): + output = io.StringIO() + if wrapper: + dis.dis(func, file=output, **kwargs) + else: + dis.disassemble(func, lasti, file=output, **kwargs) + return output.getvalue() + + +if dis.code_info.__doc__ is None: + code_info_consts = "0: None" +else: + code_info_consts = "0: 'Formatted details of methods, functions, or code.'" + +code_info_code_info = f"""\ +Name: code_info +Filename: (.*) +Argument count: 1 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 1 +Stack size: \\d+ +Flags: OPTIMIZED, NEWLOCALS +Constants: + {code_info_consts} +Names: + 0: _format_code_info + 1: _get_code_object +Variable names: + 0: x""" + + +@staticmethod +def tricky(a, b, /, x, y, z=True, *args, c, d, e=[], **kwds): + def f(c=c): + print(a, b, x, y, z, c, d, e, f) + yield a, b, x, y, z, c, d, e, f + +code_info_tricky = """\ +Name: tricky +Filename: (.*) +Argument count: 5 +Positional-only arguments: 2 +Kw-only arguments: 3 +Number of locals: 10 +Stack size: \\d+ +Flags: OPTIMIZED, NEWLOCALS, VARARGS, VARKEYWORDS, GENERATOR +Constants: + 0: None + 1: <code object f at (.*), file "(.*)", line (.*)> +Variable names: + 0: a + 1: b + 2: x + 3: y + 4: z + 5: c + 6: d + 7: e + 8: args + 9: kwds +Cell variables: + 0: [abedfxyz] + 1: [abedfxyz] + 2: [abedfxyz] + 3: [abedfxyz] + 4: [abedfxyz] + 5: [abedfxyz]""" +# NOTE: the order of the cell variables above depends on dictionary order! + +co_tricky_nested_f = tricky.__func__.__code__.co_consts[1] + +code_info_tricky_nested_f = """\ +Filename: (.*) +Argument count: 1 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 1 +Stack size: \\d+ +Flags: OPTIMIZED, NEWLOCALS, NESTED +Constants: + 0: None +Names: + 0: print +Variable names: + 0: c +Free variables: + 0: [abedfxyz] + 1: [abedfxyz] + 2: [abedfxyz] + 3: [abedfxyz] + 4: [abedfxyz] + 5: [abedfxyz]""" + +code_info_expr_str = """\ +Name: <module> +Filename: <disassembly> +Argument count: 0 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 0 +Stack size: \\d+ +Flags: 0x0 +Constants: + 0: 1 +Names: + 0: x""" + +code_info_simple_stmt_str = """\ +Name: <module> +Filename: <disassembly> +Argument count: 0 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 0 +Stack size: \\d+ +Flags: 0x0 +Constants: + 0: 1 + 1: None +Names: + 0: x""" + +code_info_compound_stmt_str = """\ +Name: <module> +Filename: <disassembly> +Argument count: 0 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 0 +Stack size: \\d+ +Flags: 0x0 +Constants: + 0: 0 + 1: 1 +Names: + 0: x""" + + +async def async_def(): + await 1 + async for a in b: pass + async with c as d: pass + +code_info_async_def = """\ +Name: async_def +Filename: (.*) +Argument count: 0 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 2 +Stack size: \\d+ +Flags: OPTIMIZED, NEWLOCALS, COROUTINE +Constants: + 0: None + 1: 1 +Names: + 0: b + 1: c +Variable names: + 0: a + 1: d""" + +class CodeInfoTests(unittest.TestCase): + test_pairs = [ + (dis.code_info, code_info_code_info), + (tricky, code_info_tricky), + (co_tricky_nested_f, code_info_tricky_nested_f), + (expr_str, code_info_expr_str), + (simple_stmt_str, code_info_simple_stmt_str), + (compound_stmt_str, code_info_compound_stmt_str), + (async_def, code_info_async_def) + ] + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_code_info(self): + self.maxDiff = 1000 + for x, expected in self.test_pairs: + self.assertRegex(dis.code_info(x), expected) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_show_code(self): + self.maxDiff = 1000 + for x, expected in self.test_pairs: + with captured_stdout() as output: + dis.show_code(x) + self.assertRegex(output.getvalue(), expected+"\n") + output = io.StringIO() + dis.show_code(x, file=output) + self.assertRegex(output.getvalue(), expected) + + def test_code_info_object(self): + self.assertRaises(TypeError, dis.code_info, object()) + + def test_pretty_flags_no_flags(self): + self.assertEqual(dis.pretty_flags(0), '0x0') + + +# Fodder for instruction introspection tests +# Editing any of these may require recalculating the expected output +def outer(a=1, b=2): + def f(c=3, d=4): + def inner(e=5, f=6): + print(a, b, c, d, e, f) + print(a, b, c, d) + return inner + print(a, b, '', 1, [], {}, "Hello world!") + return f + +def jumpy(): + # This won't actually run (but that's OK, we only disassemble it) + for i in range(10): + print(i) + if i < 4: + continue + if i > 6: + break + else: + print("I can haz else clause?") + while i: + print(i) + i -= 1 + if i > 6: + continue + if i < 4: + break + else: + print("Who let lolcatz into this test suite?") + try: + 1 / 0 + except ZeroDivisionError: + print("Here we go, here we go, here we go...") + else: + with i as dodgy: + print("Never reach this") + finally: + print("OK, now we're done") + +# End fodder for opinfo generation tests +expected_outer_line = 1 +_line_offset = outer.__code__.co_firstlineno - 1 +code_object_f = outer.__code__.co_consts[1] +# expected_f_line = code_object_f.co_firstlineno - _line_offset # TODO: RUSTPYTHON; AttributeError: 'int' object has no attribute 'co_firstlineno' +# code_object_inner = code_object_f.co_consts[1] # TODO: RUSTPYTHON; AttributeError: 'int' object has no attribute 'co_consts' +# expected_inner_line = code_object_inner.co_firstlineno - _line_offset # TODO: RUSTPYTHON; NameError: name 'code_object_inner' is not defined +expected_jumpy_line = 1 + +# The following lines are useful to regenerate the expected results after +# either the fodder is modified or the bytecode generation changes +# After regeneration, update the references to code_object_f and +# code_object_inner before rerunning the tests + +def _stringify_instruction(instr): + # Since line numbers and other offsets change a lot for these + # test cases, ignore them. + return f" {instr._replace(positions=None)!r}," + +def _prepare_test_cases(): + ignore = io.StringIO() + with contextlib.redirect_stdout(ignore): + f = outer() + inner = f() + _instructions_outer = dis.get_instructions(outer, first_line=expected_outer_line) + _instructions_f = dis.get_instructions(f, first_line=expected_f_line) + _instructions_inner = dis.get_instructions(inner, first_line=expected_inner_line) + _instructions_jumpy = dis.get_instructions(jumpy, first_line=expected_jumpy_line) + result = "\n".join( + [ + "expected_opinfo_outer = [", + *map(_stringify_instruction, _instructions_outer), + "]", + "", + "expected_opinfo_f = [", + *map(_stringify_instruction, _instructions_f), + "]", + "", + "expected_opinfo_inner = [", + *map(_stringify_instruction, _instructions_inner), + "]", + "", + "expected_opinfo_jumpy = [", + *map(_stringify_instruction, _instructions_jumpy), + "]", + ] + ) + result = result.replace(repr(repr(code_object_f)), "repr(code_object_f)") + result = result.replace(repr(code_object_f), "code_object_f") + result = result.replace(repr(repr(code_object_inner)), "repr(code_object_inner)") + result = result.replace(repr(code_object_inner), "code_object_inner") + print(result) + +# from test.test_dis import _prepare_test_cases; _prepare_test_cases() + +Instruction = dis.Instruction + +expected_opinfo_outer = [ + Instruction(opname='MAKE_CELL', opcode=94, arg=0, argval='a', argrepr='a', offset=0, start_offset=0, starts_line=True, line_number=None, label=None, positions=None), + Instruction(opname='MAKE_CELL', opcode=94, arg=1, argval='b', argrepr='b', offset=2, start_offset=2, starts_line=False, line_number=None, label=None, positions=None), + Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=4, start_offset=4, starts_line=True, line_number=1, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=(3, 4), argrepr='(3, 4)', offset=6, start_offset=6, starts_line=True, line_number=2, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='a', argrepr='a', offset=8, start_offset=8, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=1, argval='b', argrepr='b', offset=10, start_offset=10, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='BUILD_TUPLE', opcode=52, arg=2, argval=2, argrepr='', offset=12, start_offset=12, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=code_object_f, argrepr=repr(code_object_f), offset=14, start_offset=14, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='MAKE_FUNCTION', opcode=26, arg=None, argval=None, argrepr='', offset=16, start_offset=16, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=8, argval=8, argrepr='closure', offset=18, start_offset=18, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=1, argval=1, argrepr='defaults', offset=20, start_offset=20, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='STORE_FAST', opcode=110, arg=2, argval='f', argrepr='f', offset=22, start_offset=22, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='print', argrepr='print + NULL', offset=24, start_offset=24, starts_line=True, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=0, argval='a', argrepr='a', offset=34, start_offset=34, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=1, argval='b', argrepr='b', offset=36, start_offset=36, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval='', argrepr="''", offset=38, start_offset=38, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=1, argrepr='1', offset=40, start_offset=40, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='BUILD_LIST', opcode=47, arg=0, argval=0, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='BUILD_MAP', opcode=48, arg=0, argval=0, argrepr='', offset=44, start_offset=44, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=4, argval='Hello world!', argrepr="'Hello world!'", offset=46, start_offset=46, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='CALL', opcode=53, arg=7, argval=7, argrepr='', offset=48, start_offset=48, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=56, start_offset=56, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=2, argval='f', argrepr='f', offset=58, start_offset=58, starts_line=True, line_number=8, label=None, positions=None), + Instruction(opname='RETURN_VALUE', opcode=36, arg=None, argval=None, argrepr='', offset=60, start_offset=60, starts_line=False, line_number=8, label=None, positions=None), +] + +expected_opinfo_f = [ + Instruction(opname='COPY_FREE_VARS', opcode=62, arg=2, argval=2, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=None, label=None, positions=None), + Instruction(opname='MAKE_CELL', opcode=94, arg=0, argval='c', argrepr='c', offset=2, start_offset=2, starts_line=False, line_number=None, label=None, positions=None), + Instruction(opname='MAKE_CELL', opcode=94, arg=1, argval='d', argrepr='d', offset=4, start_offset=4, starts_line=False, line_number=None, label=None, positions=None), + Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=6, start_offset=6, starts_line=True, line_number=2, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=(5, 6), argrepr='(5, 6)', offset=8, start_offset=8, starts_line=True, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=3, argval='a', argrepr='a', offset=10, start_offset=10, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=4, argval='b', argrepr='b', offset=12, start_offset=12, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='c', argrepr='c', offset=14, start_offset=14, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=1, argval='d', argrepr='d', offset=16, start_offset=16, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='BUILD_TUPLE', opcode=52, arg=4, argval=4, argrepr='', offset=18, start_offset=18, starts_line=False, line_number=3, label=None, positions=None), + #Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=code_object_inner, argrepr=repr(code_object_inner), offset=20, start_offset=20, starts_line=False, line_number=3, label=None, positions=None), # TODO: RUSTPYTHON; NameError: name 'code_object_inner' is not defined + Instruction(opname='MAKE_FUNCTION', opcode=26, arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=8, argval=8, argrepr='closure', offset=24, start_offset=24, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=1, argval=1, argrepr='defaults', offset=26, start_offset=26, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='STORE_FAST', opcode=110, arg=2, argval='inner', argrepr='inner', offset=28, start_offset=28, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='print', argrepr='print + NULL', offset=30, start_offset=30, starts_line=True, line_number=5, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=3, argval='a', argrepr='a', offset=40, start_offset=40, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=4, argval='b', argrepr='b', offset=42, start_offset=42, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=0, argval='c', argrepr='c', offset=44, start_offset=44, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=1, argval='d', argrepr='d', offset=46, start_offset=46, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='CALL', opcode=53, arg=4, argval=4, argrepr='', offset=48, start_offset=48, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=56, start_offset=56, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=2, argval='inner', argrepr='inner', offset=58, start_offset=58, starts_line=True, line_number=6, label=None, positions=None), + Instruction(opname='RETURN_VALUE', opcode=36, arg=None, argval=None, argrepr='', offset=60, start_offset=60, starts_line=False, line_number=6, label=None, positions=None), +] + +expected_opinfo_inner = [ + Instruction(opname='COPY_FREE_VARS', opcode=62, arg=4, argval=4, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=None, label=None, positions=None), + Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=2, start_offset=2, starts_line=True, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='print', argrepr='print + NULL', offset=4, start_offset=4, starts_line=True, line_number=4, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=2, argval='a', argrepr='a', offset=14, start_offset=14, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=3, argval='b', argrepr='b', offset=16, start_offset=16, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=4, argval='c', argrepr='c', offset=18, start_offset=18, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=5, argval='d', argrepr='d', offset=20, start_offset=20, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='LOAD_FAST_LOAD_FAST', opcode=88, arg=1, argval=('e', 'f'), argrepr='e, f', offset=22, start_offset=22, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='CALL', opcode=53, arg=6, argval=6, argrepr='', offset=24, start_offset=24, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=32, start_offset=32, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='RETURN_CONST', opcode=103, arg=0, argval=None, argrepr='None', offset=34, start_offset=34, starts_line=False, line_number=4, label=None, positions=None), +] + +expected_opinfo_jumpy = [ + Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=1, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='range', argrepr='range + NULL', offset=2, start_offset=2, starts_line=True, line_number=3, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=10, argrepr='10', offset=12, start_offset=12, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=3, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='GET_ITER', opcode=19, arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='FOR_ITER', opcode=72, arg=30, argval=88, argrepr='to L4', offset=24, start_offset=24, starts_line=False, line_number=3, label=1, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='STORE_FAST', opcode=110, arg=0, argval='i', argrepr='i', offset=28, start_offset=28, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=30, start_offset=30, starts_line=True, line_number=4, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=40, start_offset=40, starts_line=False, line_number=4, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=4, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=50, start_offset=50, starts_line=False, line_number=4, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=52, start_offset=52, starts_line=True, line_number=5, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=4, argrepr='4', offset=54, start_offset=54, starts_line=False, line_number=5, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=18, argval='<', argrepr='bool(<)', offset=56, start_offset=56, starts_line=False, line_number=5, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=68, argrepr='to L2', offset=60, start_offset=60, starts_line=False, line_number=5, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=22, argval=24, argrepr='to L1', offset=64, start_offset=64, starts_line=True, line_number=6, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=68, start_offset=68, starts_line=True, line_number=7, label=2, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=6, argrepr='6', offset=70, start_offset=70, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=148, argval='>', argrepr='bool(>)', offset=72, start_offset=72, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_TRUE', opcode=100, arg=2, argval=84, argrepr='to L3', offset=76, start_offset=76, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=30, argval=24, argrepr='to L1', offset=80, start_offset=80, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=84, start_offset=84, starts_line=True, line_number=8, label=3, positions=None, cache_info=None), + Instruction(opname='JUMP_FORWARD', opcode=79, arg=13, argval=114, argrepr='to L5', offset=86, start_offset=86, starts_line=False, line_number=8, label=None, positions=None, cache_info=None), + Instruction(opname='END_FOR', opcode=11, arg=None, argval=None, argrepr='', offset=88, start_offset=88, starts_line=True, line_number=3, label=4, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=90, start_offset=90, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=92, start_offset=92, starts_line=True, line_number=10, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=102, start_offset=102, starts_line=False, line_number=10, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=104, start_offset=104, starts_line=False, line_number=10, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=112, start_offset=112, starts_line=False, line_number=10, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST_CHECK', opcode=87, arg=0, argval='i', argrepr='i', offset=114, start_offset=114, starts_line=True, line_number=11, label=5, positions=None, cache_info=None), + Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=116, start_offset=116, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=40, argval=208, argrepr='to L9', offset=124, start_offset=124, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=128, start_offset=128, starts_line=True, line_number=12, label=6, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=138, start_offset=138, starts_line=False, line_number=12, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=140, start_offset=140, starts_line=False, line_number=12, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=148, start_offset=148, starts_line=False, line_number=12, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=150, start_offset=150, starts_line=True, line_number=13, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=1, argrepr='1', offset=152, start_offset=152, starts_line=False, line_number=13, label=None, positions=None, cache_info=None), + Instruction(opname='BINARY_OP', opcode=45, arg=23, argval=23, argrepr='-=', offset=154, start_offset=154, starts_line=False, line_number=13, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='STORE_FAST', opcode=110, arg=0, argval='i', argrepr='i', offset=158, start_offset=158, starts_line=False, line_number=13, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=160, start_offset=160, starts_line=True, line_number=14, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=6, argrepr='6', offset=162, start_offset=162, starts_line=False, line_number=14, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=148, argval='>', argrepr='bool(>)', offset=164, start_offset=164, starts_line=False, line_number=14, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=176, argrepr='to L7', offset=168, start_offset=168, starts_line=False, line_number=14, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=31, argval=114, argrepr='to L5', offset=172, start_offset=172, starts_line=True, line_number=15, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=176, start_offset=176, starts_line=True, line_number=16, label=7, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=4, argrepr='4', offset=178, start_offset=178, starts_line=False, line_number=16, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=18, argval='<', argrepr='bool(<)', offset=180, start_offset=180, starts_line=False, line_number=16, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=1, argval=190, argrepr='to L8', offset=184, start_offset=184, starts_line=False, line_number=16, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_FORWARD', opcode=79, arg=20, argval=230, argrepr='to L10', offset=188, start_offset=188, starts_line=True, line_number=17, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=190, start_offset=190, starts_line=True, line_number=11, label=8, positions=None, cache_info=None), + Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=192, start_offset=192, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=208, argrepr='to L9', offset=200, start_offset=200, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=40, argval=128, argrepr='to L6', offset=204, start_offset=204, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=208, start_offset=208, starts_line=True, line_number=19, label=9, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=218, start_offset=218, starts_line=False, line_number=19, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=220, start_offset=220, starts_line=False, line_number=19, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=228, start_offset=228, starts_line=False, line_number=19, label=None, positions=None, cache_info=None), + Instruction(opname='NOP', opcode=30, arg=None, argval=None, argrepr='', offset=230, start_offset=230, starts_line=True, line_number=20, label=10, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=1, argrepr='1', offset=232, start_offset=232, starts_line=True, line_number=21, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=7, argval=0, argrepr='0', offset=234, start_offset=234, starts_line=False, line_number=21, label=None, positions=None, cache_info=None), + Instruction(opname='BINARY_OP', opcode=45, arg=11, argval=11, argrepr='/', offset=236, start_offset=236, starts_line=False, line_number=21, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=240, start_offset=240, starts_line=False, line_number=21, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=242, start_offset=242, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='BEFORE_WITH', opcode=2, arg=None, argval=None, argrepr='', offset=244, start_offset=244, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='STORE_FAST', opcode=110, arg=1, argval='dodgy', argrepr='dodgy', offset=246, start_offset=246, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=248, start_offset=248, starts_line=True, line_number=26, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=8, argval='Never reach this', argrepr="'Never reach this'", offset=258, start_offset=258, starts_line=False, line_number=26, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=260, start_offset=260, starts_line=False, line_number=26, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=268, start_offset=268, starts_line=False, line_number=26, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=270, start_offset=270, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=272, start_offset=272, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=274, start_offset=274, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=2, argval=2, argrepr='', offset=276, start_offset=276, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=284, start_offset=284, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=286, start_offset=286, starts_line=True, line_number=28, label=11, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=296, start_offset=296, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=298, start_offset=298, starts_line=False, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=306, start_offset=306, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='RETURN_CONST', opcode=103, arg=0, argval=None, argrepr='None', offset=308, start_offset=308, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=310, start_offset=310, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='WITH_EXCEPT_START', opcode=44, arg=None, argval=None, argrepr='', offset=312, start_offset=312, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=314, start_offset=314, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_JUMP_IF_TRUE', opcode=100, arg=1, argval=328, argrepr='to L12', offset=322, start_offset=322, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='RERAISE', opcode=102, arg=2, argval=2, argrepr='', offset=326, start_offset=326, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=328, start_offset=328, starts_line=False, line_number=25, label=12, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=330, start_offset=330, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=332, start_offset=332, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=334, start_offset=334, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='JUMP_BACKWARD_NO_INTERRUPT', opcode=78, arg=26, argval=286, argrepr='to L11', offset=336, start_offset=336, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=338, start_offset=338, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=340, start_offset=340, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=342, start_offset=342, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=344, start_offset=344, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=4, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=346, start_offset=346, starts_line=True, line_number=22, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='CHECK_EXC_MATCH', opcode=7, arg=None, argval=None, argrepr='', offset=356, start_offset=356, starts_line=False, line_number=22, label=None, positions=None, cache_info=None), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=14, argval=390, argrepr='to L13', offset=358, start_offset=358, starts_line=False, line_number=22, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=362, start_offset=362, starts_line=False, line_number=22, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=364, start_offset=364, starts_line=True, line_number=23, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=9, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=374, start_offset=374, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=376, start_offset=376, starts_line=False, line_number=23, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=384, start_offset=384, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=386, start_offset=386, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='JUMP_BACKWARD_NO_INTERRUPT', opcode=78, arg=52, argval=286, argrepr='to L11', offset=388, start_offset=388, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=0, argval=0, argrepr='', offset=390, start_offset=390, starts_line=True, line_number=22, label=13, positions=None, cache_info=None), + Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=392, start_offset=392, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=394, start_offset=394, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=396, start_offset=396, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=398, start_offset=398, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=400, start_offset=400, starts_line=True, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=410, start_offset=410, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=412, start_offset=412, starts_line=False, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=420, start_offset=420, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=0, argval=0, argrepr='', offset=422, start_offset=422, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=424, start_offset=424, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=426, start_offset=426, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=428, start_offset=428, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), +] + +# One last piece of inspect fodder to check the default line number handling +def simple(): pass +expected_opinfo_simple = [ + Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=simple.__code__.co_firstlineno, label=None, positions=None), + Instruction(opname='RETURN_CONST', opcode=103, arg=0, argval=None, argrepr='None', offset=2, start_offset=2, starts_line=False, line_number=simple.__code__.co_firstlineno, label=None), +] + + +class InstructionTestCase(BytecodeTestCase): + + def assertInstructionsEqual(self, instrs_1, instrs_2, /): + instrs_1 = [instr_1._replace(positions=None, cache_info=None) for instr_1 in instrs_1] + instrs_2 = [instr_2._replace(positions=None, cache_info=None) for instr_2 in instrs_2] + self.assertEqual(instrs_1, instrs_2) + +class InstructionTests(InstructionTestCase): + + def __init__(self, *args): + super().__init__(*args) + self.maxDiff = None + + def test_instruction_str(self): + # smoke test for __str__ + instrs = dis.get_instructions(simple) + for instr in instrs: + str(instr) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_default_first_line(self): + actual = dis.get_instructions(simple) + self.assertInstructionsEqual(list(actual), expected_opinfo_simple) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_first_line_set_to_None(self): + actual = dis.get_instructions(simple, first_line=None) + self.assertInstructionsEqual(list(actual), expected_opinfo_simple) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_outer(self): + actual = dis.get_instructions(outer, first_line=expected_outer_line) + self.assertInstructionsEqual(list(actual), expected_opinfo_outer) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_nested(self): + with captured_stdout(): + f = outer() + actual = dis.get_instructions(f, first_line=expected_f_line) + self.assertInstructionsEqual(list(actual), expected_opinfo_f) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_doubly_nested(self): + with captured_stdout(): + inner = outer()() + actual = dis.get_instructions(inner, first_line=expected_inner_line) + self.assertInstructionsEqual(list(actual), expected_opinfo_inner) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_jumpy(self): + actual = dis.get_instructions(jumpy, first_line=expected_jumpy_line) + self.assertInstructionsEqual(list(actual), expected_opinfo_jumpy) + + @requires_debug_ranges() + def test_co_positions(self): + code = compile('f(\n x, y, z\n)', '<test>', 'exec') + positions = [ + instr.positions + for instr in dis.get_instructions(code) + ] + expected = [ + (0, 1, 0, 0), + (1, 1, 0, 1), + (1, 1, 0, 1), + (2, 2, 2, 3), + (2, 2, 5, 6), + (2, 2, 8, 9), + (1, 3, 0, 1), + (1, 3, 0, 1), + (1, 3, 0, 1) + ] + self.assertEqual(positions, expected) + + named_positions = [ + (pos.lineno, pos.end_lineno, pos.col_offset, pos.end_col_offset) + for pos in positions + ] + self.assertEqual(named_positions, expected) + + @requires_debug_ranges() + def test_co_positions_missing_info(self): + code = compile('x, y, z', '<test>', 'exec') + code_without_location_table = code.replace(co_linetable=b'') + actual = dis.get_instructions(code_without_location_table) + for instruction in actual: + with self.subTest(instruction=instruction): + positions = instruction.positions + self.assertEqual(len(positions), 4) + if instruction.opname == "RESUME": + continue + self.assertIsNone(positions.lineno) + self.assertIsNone(positions.end_lineno) + self.assertIsNone(positions.col_offset) + self.assertIsNone(positions.end_col_offset) + + @requires_debug_ranges() + def test_co_positions_with_lots_of_caches(self): + def roots(a, b, c): + d = b**2 - 4 * a * c + yield (-b - cmath.sqrt(d)) / (2 * a) + if d: + yield (-b + cmath.sqrt(d)) / (2 * a) + code = roots.__code__ + ops = code.co_code[::2] + cache_opcode = opcode.opmap["CACHE"] + caches = sum(op == cache_opcode for op in ops) + non_caches = len(ops) - caches + # Make sure we have "lots of caches". If not, roots should be changed: + assert 1 / 3 <= caches / non_caches, "this test needs more caches!" + for show_caches in (False, True): + for adaptive in (False, True): + with self.subTest(f"{adaptive=}, {show_caches=}"): + co_positions = [ + positions + for op, positions in zip(ops, code.co_positions(), strict=True) + if show_caches or op != cache_opcode + ] + dis_positions = [ + None if instruction.positions is None else ( + instruction.positions.lineno, + instruction.positions.end_lineno, + instruction.positions.col_offset, + instruction.positions.end_col_offset, + ) + for instruction in _unroll_caches_as_Instructions(dis.get_instructions( + code, adaptive=adaptive, show_caches=show_caches + ), show_caches=show_caches) + ] + self.assertEqual(co_positions, dis_positions) + + def test_oparg_alias(self): + instruction = Instruction(opname="NOP", opcode=dis.opmap["NOP"], arg=None, argval=None, + argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, + positions=None) + self.assertEqual(instruction.arg, instruction.oparg) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_show_caches_with_label(self): + def f(x, y, z): + if x: + res = y + else: + res = z + return res + + output = io.StringIO() + dis.dis(f.__code__, file=output, show_caches=True) + self.assertIn("L1:", output.getvalue()) + + def test_baseopname_and_baseopcode(self): + # Standard instructions + for name, code in dis.opmap.items(): + instruction = Instruction(opname=name, opcode=code, arg=None, argval=None, argrepr='', offset=0, + start_offset=0, starts_line=True, line_number=1, label=None, positions=None) + baseopname = instruction.baseopname + baseopcode = instruction.baseopcode + self.assertIsNotNone(baseopname) + self.assertIsNotNone(baseopcode) + self.assertEqual(name, baseopname) + self.assertEqual(code, baseopcode) + + # Specialized instructions + for name in opcode._specialized_opmap: + instruction = Instruction(opname=name, opcode=dis._all_opmap[name], arg=None, argval=None, argrepr='', + offset=0, start_offset=0, starts_line=True, line_number=1, label=None, positions=None) + baseopname = instruction.baseopname + baseopcode = instruction.baseopcode + self.assertIn(name, opcode._specializations[baseopname]) + self.assertEqual(opcode.opmap[baseopname], baseopcode) + + def test_jump_target(self): + # Non-jump instructions should return None + instruction = Instruction(opname="NOP", opcode=dis.opmap["NOP"], arg=None, argval=None, + argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, + positions=None) + self.assertIsNone(instruction.jump_target) + + delta = 100 + instruction = Instruction(opname="JUMP_FORWARD", opcode=dis.opmap["JUMP_FORWARD"], arg=delta, argval=delta, + argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, + positions=None) + self.assertEqual(10 + 2 + 100*2, instruction.jump_target) + + # Test negative deltas + instruction = Instruction(opname="JUMP_BACKWARD", opcode=dis.opmap["JUMP_BACKWARD"], arg=delta, argval=delta, + argrepr='', offset=200, start_offset=200, starts_line=True, line_number=1, label=None, + positions=None) + self.assertEqual(200 + 2 - 100*2 + 2*1, instruction.jump_target) + + # Make sure cache entries are handled + instruction = Instruction(opname="SEND", opcode=dis.opmap["SEND"], arg=delta, argval=delta, + argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, + positions=None) + self.assertEqual(10 + 2 + 1*2 + 100*2, instruction.jump_target) + + def test_argval_argrepr(self): + def f(opcode, oparg, offset, *init_args): + arg_resolver = dis.ArgResolver(*init_args) + return arg_resolver.get_argval_argrepr(opcode, oparg, offset) + + offset = 42 + co_consts = (0, 1, 2, 3) + names = {1: 'a', 2: 'b'} + varname_from_oparg = lambda i : names[i] + labels_map = {24: 1} + args = (offset, co_consts, names, varname_from_oparg, labels_map) + self.assertEqual(f(opcode.opmap["POP_TOP"], None, *args), (None, '')) + self.assertEqual(f(opcode.opmap["LOAD_CONST"], 1, *args), (1, '1')) + self.assertEqual(f(opcode.opmap["LOAD_GLOBAL"], 2, *args), ('a', 'a')) + self.assertEqual(f(opcode.opmap["JUMP_BACKWARD"], 11, *args), (24, 'to L1')) + self.assertEqual(f(opcode.opmap["COMPARE_OP"], 3, *args), ('<', '<')) + self.assertEqual(f(opcode.opmap["SET_FUNCTION_ATTRIBUTE"], 2, *args), (2, 'kwdefaults')) + self.assertEqual(f(opcode.opmap["BINARY_OP"], 3, *args), (3, '<<')) + self.assertEqual(f(opcode.opmap["CALL_INTRINSIC_1"], 2, *args), (2, 'INTRINSIC_IMPORT_STAR')) + + def test_custom_arg_resolver(self): + class MyArgResolver(dis.ArgResolver): + def offset_from_jump_arg(self, op, arg, offset): + return arg + 1 + + def get_label_for_offset(self, offset): + return 2 * offset + + def f(opcode, oparg, offset, *init_args): + arg_resolver = MyArgResolver(*init_args) + return arg_resolver.get_argval_argrepr(opcode, oparg, offset) + offset = 42 + self.assertEqual(f(opcode.opmap["JUMP_BACKWARD"], 1, offset), (2, 'to L4')) + self.assertEqual(f(opcode.opmap["SETUP_FINALLY"], 2, offset), (3, 'to L6')) + + + def get_instructions(self, code): + return dis._get_instructions_bytes(code) + + def test_start_offset(self): + # When no extended args are present, + # start_offset should be equal to offset + + instructions = list(dis.Bytecode(_f)) + for instruction in instructions: + self.assertEqual(instruction.offset, instruction.start_offset) + + def last_item(iterable): + return functools.reduce(lambda a, b : b, iterable) + + code = bytes([ + opcode.opmap["LOAD_FAST"], 0x00, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, + ]) + labels_map = dis._make_labels_map(code) + jump = last_item(self.get_instructions(code)) + self.assertEqual(4, jump.offset) + self.assertEqual(2, jump.start_offset) + + code = bytes([ + opcode.opmap["LOAD_FAST"], 0x00, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, + opcode.opmap["CACHE"], 0x00, + ]) + jump = last_item(self.get_instructions(code)) + self.assertEqual(8, jump.offset) + self.assertEqual(2, jump.start_offset) + + code = bytes([ + opcode.opmap["LOAD_FAST"], 0x00, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, + opcode.opmap["CACHE"], 0x00, + ]) + instructions = list(self.get_instructions(code)) + # 1st jump + self.assertEqual(4, instructions[2].offset) + self.assertEqual(2, instructions[2].start_offset) + # 2nd jump + self.assertEqual(14, instructions[6].offset) + self.assertEqual(8, instructions[6].start_offset) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_cache_offset_and_end_offset(self): + code = bytes([ + opcode.opmap["LOAD_GLOBAL"], 0x01, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["LOAD_FAST"], 0x00, + opcode.opmap["CALL"], 0x01, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["CACHE"], 0x00 + ]) + instructions = list(self.get_instructions(code)) + self.assertEqual(2, instructions[0].cache_offset) + self.assertEqual(10, instructions[0].end_offset) + self.assertEqual(12, instructions[1].cache_offset) + self.assertEqual(12, instructions[1].end_offset) + self.assertEqual(14, instructions[2].cache_offset) + self.assertEqual(20, instructions[2].end_offset) + + # end_offset of the previous instruction should be equal to the + # start_offset of the following instruction + instructions = list(dis.Bytecode(self.test_cache_offset_and_end_offset)) + for prev, curr in zip(instructions, instructions[1:]): + self.assertEqual(prev.end_offset, curr.start_offset) + + +# get_instructions has its own tests above, so can rely on it to validate +# the object oriented API +class BytecodeTests(InstructionTestCase, DisTestBase): + + def test_instantiation(self): + # Test with function, method, code string and code object + for obj in [_f, _C(1).__init__, "a=1", _f.__code__]: + with self.subTest(obj=obj): + b = dis.Bytecode(obj) + self.assertIsInstance(b.codeobj, types.CodeType) + + self.assertRaises(TypeError, dis.Bytecode, object()) + + def test_iteration(self): + for obj in [_f, _C(1).__init__, "a=1", _f.__code__]: + with self.subTest(obj=obj): + via_object = list(dis.Bytecode(obj)) + via_generator = list(dis.get_instructions(obj)) + self.assertInstructionsEqual(via_object, via_generator) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_explicit_first_line(self): + actual = dis.Bytecode(outer, first_line=expected_outer_line) + self.assertInstructionsEqual(list(actual), expected_opinfo_outer) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_source_line_in_disassembly(self): + # Use the line in the source code + actual = dis.Bytecode(simple).dis() + actual = actual.strip().partition(" ")[0] # extract the line no + expected = str(simple.__code__.co_firstlineno) + self.assertEqual(actual, expected) + # Use an explicit first line number + actual = dis.Bytecode(simple, first_line=350).dis() + actual = actual.strip().partition(" ")[0] # extract the line no + self.assertEqual(actual, "350") + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_info(self): + self.maxDiff = 1000 + for x, expected in CodeInfoTests.test_pairs: + b = dis.Bytecode(x) + self.assertRegex(b.info(), expected) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_disassembled(self): + actual = dis.Bytecode(_f).dis() + self.do_disassembly_compare(actual, dis_f) + + def test_from_traceback(self): + tb = get_tb() + b = dis.Bytecode.from_traceback(tb) + while tb.tb_next: tb = tb.tb_next + + self.assertEqual(b.current_offset, tb.tb_lasti) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_from_traceback_dis(self): + self.maxDiff = None + tb = get_tb() + b = dis.Bytecode.from_traceback(tb) + self.assertEqual(b.dis(), dis_traceback) + + @requires_debug_ranges() + def test_bytecode_co_positions(self): + bytecode = dis.Bytecode("a=1") + for instr, positions in zip(bytecode, bytecode.codeobj.co_positions()): + assert instr.positions == positions + +class TestBytecodeTestCase(BytecodeTestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_assert_not_in_with_op_not_in_bytecode(self): + code = compile("a = 1", "<string>", "exec") + self.assertInBytecode(code, "LOAD_CONST", 1) + self.assertNotInBytecode(code, "LOAD_NAME") + self.assertNotInBytecode(code, "LOAD_NAME", "a") + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_assert_not_in_with_arg_not_in_bytecode(self): + code = compile("a = 1", "<string>", "exec") + self.assertInBytecode(code, "LOAD_CONST") + self.assertInBytecode(code, "LOAD_CONST", 1) + self.assertNotInBytecode(code, "LOAD_CONST", 2) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_assert_not_in_with_arg_in_bytecode(self): + code = compile("a = 1", "<string>", "exec") + with self.assertRaises(AssertionError): + self.assertNotInBytecode(code, "LOAD_CONST", 1) + +class TestFinderMethods(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON + def test__find_imports(self): + cases = [ + ("import a.b.c", ('a.b.c', 0, None)), + ("from a.b import c", ('a.b', 0, ('c',))), + ("from a.b import c as d", ('a.b', 0, ('c',))), + ("from a.b import *", ('a.b', 0, ('*',))), + ("from ...a.b import c as d", ('a.b', 3, ('c',))), + ("from ..a.b import c as d, e as f", ('a.b', 2, ('c', 'e'))), + ("from ..a.b import *", ('a.b', 2, ('*',))), + ] + for src, expected in cases: + with self.subTest(src=src): + code = compile(src, "<string>", "exec") + res = tuple(dis._find_imports(code)) + self.assertEqual(len(res), 1) + self.assertEqual(res[0], expected) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test__find_store_names(self): + cases = [ + ("x+y", ()), + ("x=y=1", ('x', 'y')), + ("x+=y", ('x',)), + ("global x\nx=y=1", ('x', 'y')), + ("global x\nz=x", ('z',)), + ] + for src, expected in cases: + with self.subTest(src=src): + code = compile(src, "<string>", "exec") + res = tuple(dis._find_store_names(code)) + self.assertEqual(res, expected) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_findlabels(self): + labels = dis.findlabels(jumpy.__code__.co_code) + jumps = [ + instr.offset + for instr in expected_opinfo_jumpy + if instr.is_jump_target + ] + + self.assertEqual(sorted(labels), sorted(jumps)) + + def test_findlinestarts(self): + def func(): + pass + + code = func.__code__ + offsets = [linestart[0] for linestart in dis.findlinestarts(code)] + self.assertEqual(offsets, [0, 2]) + + +class TestDisTraceback(DisTestBase): + def setUp(self) -> None: + try: # We need to clean up existing tracebacks + del sys.last_exc + except AttributeError: + pass + try: # We need to clean up existing tracebacks + del sys.last_traceback + except AttributeError: + pass + return super().setUp() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def get_disassembly(self, tb): + output = io.StringIO() + with contextlib.redirect_stdout(output): + dis.distb(tb) + return output.getvalue() + + def test_distb_empty(self): + with self.assertRaises(RuntimeError): + dis.distb() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_distb_last_traceback(self): + self.maxDiff = None + # We need to have an existing last traceback in `sys`: + tb = get_tb() + sys.last_traceback = tb + + self.do_disassembly_compare(self.get_disassembly(None), dis_traceback) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_distb_explicit_arg(self): + self.maxDiff = None + tb = get_tb() + + self.do_disassembly_compare(self.get_disassembly(tb), dis_traceback) + + +class TestDisTracebackWithFile(TestDisTraceback): + # Run the `distb` tests again, using the file arg instead of print + def get_disassembly(self, tb): + output = io.StringIO() + with contextlib.redirect_stdout(output): + dis.distb(tb, file=output) + return output.getvalue() + +def _unroll_caches_as_Instructions(instrs, show_caches=False): + # Cache entries are no longer reported by dis as fake instructions, + # but some tests assume that do. We should rewrite the tests to assume + # the new API, but it will be clearer to keep the tests working as + # before and do that in a separate PR. + + for instr in instrs: + yield instr + if not show_caches: + continue + + offset = instr.offset + for name, size, data in (instr.cache_info or ()): + for i in range(size): + offset += 2 + # Only show the fancy argrepr for a CACHE instruction when it's + # the first entry for a particular cache value: + if i == 0: + argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" + else: + argrepr = "" + + yield Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, + False, None, None, instr.positions) + + +class TestDisCLI(unittest.TestCase): + + def setUp(self): + self.filename = tempfile.mktemp() + self.addCleanup(os_helper.unlink, self.filename) + + @staticmethod + def text_normalize(string): + """Dedent *string* and strip it from its surrounding whitespaces. + + This method is used by the other utility functions so that any + string to write or to match against can be freely indented. + """ + return textwrap.dedent(string).strip() + + def set_source(self, content): + with open(self.filename, 'w') as fp: + fp.write(self.text_normalize(content)) + + def invoke_dis(self, *flags): + output = io.StringIO() + with contextlib.redirect_stdout(output): + dis.main(args=[*flags, self.filename]) + return self.text_normalize(output.getvalue()) + + def check_output(self, source, expect, *flags): + with self.subTest(source=source, flags=flags): + self.set_source(source) + res = self.invoke_dis(*flags) + expect = self.text_normalize(expect) + self.assertListEqual(res.splitlines(), expect.splitlines()) + + def test_invocation(self): + # test various combinations of parameters + base_flags = [ + ('-C', '--show-caches'), + ('-O', '--show-offsets'), + ] + + self.set_source(''' + def f(): + print(x) + return None + ''') + + for r in range(1, len(base_flags) + 1): + for choices in itertools.combinations(base_flags, r=r): + for args in itertools.product(*choices): + with self.subTest(args=args[1:]): + _ = self.invoke_dis(*args) + + with self.assertRaises(SystemExit): + # suppress argparse error message + with contextlib.redirect_stderr(io.StringIO()): + _ = self.invoke_dis('--unknown') + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_show_cache(self): + # test 'python -m dis -C/--show-caches' + source = 'print()' + expect = ''' + 0 RESUME 0 + + 1 LOAD_NAME 0 (print) + PUSH_NULL + CALL 0 + CACHE 0 (counter: 0) + CACHE 0 (func_version: 0) + CACHE 0 + POP_TOP + RETURN_CONST 0 (None) + ''' + for flag in ['-C', '--show-caches']: + self.check_output(source, expect, flag) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_show_offsets(self): + # test 'python -m dis -O/--show-offsets' + source = 'pass' + expect = ''' + 0 0 RESUME 0 + + 1 2 RETURN_CONST 0 (None) + ''' + for flag in ['-O', '--show-offsets']: + self.check_output(source, expect, flag) if __name__ == "__main__": From 5b20c458af41b8439f50976a0e30bef3281f88f1 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 27 Dec 2025 23:03:23 +0900 Subject: [PATCH 598/819] Implement more except* (#6545) * clean build documentation * SetExcInfo * fix EG subclass * test_except_star --- .github/copilot-instructions.md | 8 + Lib/test/test_except_star.py | 1220 ++++++++++++++++++++++++++ crates/codegen/src/compile.rs | 245 +++++- crates/codegen/src/error.rs | 8 + crates/compiler-core/src/bytecode.rs | 7 +- crates/vm/src/exceptions.rs | 210 ++++- crates/vm/src/frame.rs | 17 +- 7 files changed, 1634 insertions(+), 81 deletions(-) create mode 100644 Lib/test/test_except_star.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1db02dd17a9..a2ab43a695c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -73,6 +73,14 @@ The `Lib/` directory contains Python standard library files copied from the CPyt - `unittest.skip("TODO: RustPython <reason>")` - `unittest.expectedFailure` with `# TODO: RUSTPYTHON <reason>` comment +### Clean Build + +When you modify bytecode instructions, a full clean is required: + +```bash +rm -r target/debug/build/rustpython-* && find . | grep -E "\.pyc$" | xargs rm -r +``` + ### Testing ```bash diff --git a/Lib/test/test_except_star.py b/Lib/test/test_except_star.py new file mode 100644 index 00000000000..3e0f8caa9b2 --- /dev/null +++ b/Lib/test/test_except_star.py @@ -0,0 +1,1220 @@ +import sys +import unittest +import textwrap +from test.support.testcase import ExceptionIsLikeMixin + +class TestInvalidExceptStar(unittest.TestCase): + def test_mixed_except_and_except_star_is_syntax_error(self): + errors = [ + "try: pass\nexcept ValueError: pass\nexcept* TypeError: pass\n", + "try: pass\nexcept* ValueError: pass\nexcept TypeError: pass\n", + "try: pass\nexcept ValueError as e: pass\nexcept* TypeError: pass\n", + "try: pass\nexcept* ValueError as e: pass\nexcept TypeError: pass\n", + "try: pass\nexcept ValueError: pass\nexcept* TypeError as e: pass\n", + "try: pass\nexcept* ValueError: pass\nexcept TypeError as e: pass\n", + "try: pass\nexcept ValueError: pass\nexcept*: pass\n", + "try: pass\nexcept* ValueError: pass\nexcept: pass\n", + ] + + for err in errors: + with self.assertRaises(SyntaxError): + compile(err, "<string>", "exec") + + def test_except_star_ExceptionGroup_is_runtime_error_single(self): + with self.assertRaises(TypeError): + try: + raise OSError("blah") + except* ExceptionGroup as e: + pass + + def test_except_star_ExceptionGroup_is_runtime_error_tuple(self): + with self.assertRaises(TypeError): + try: + raise ExceptionGroup("eg", [ValueError(42)]) + except* (TypeError, ExceptionGroup): + pass + + def test_except_star_invalid_exception_type(self): + with self.assertRaises(TypeError): + try: + raise ValueError + except* 42: + pass + + with self.assertRaises(TypeError): + try: + raise ValueError + except* (ValueError, 42): + pass + + +class TestBreakContinueReturnInExceptStarBlock(unittest.TestCase): + MSG = (r"'break', 'continue' and 'return'" + r" cannot appear in an except\* block") + + def check_invalid(self, src): + with self.assertRaisesRegex(SyntaxError, self.MSG): + compile(textwrap.dedent(src), "<string>", "exec") + + def test_break_in_except_star(self): + self.check_invalid( + """ + try: + raise ValueError + except* Exception as e: + break + """) + + self.check_invalid( + """ + for i in range(5): + try: + pass + except* Exception as e: + if i == 2: + break + """) + + self.check_invalid( + """ + for i in range(5): + try: + pass + except* Exception as e: + if i == 2: + break + finally: + return 0 + """) + + + def test_continue_in_except_star_block_invalid(self): + self.check_invalid( + """ + for i in range(5): + try: + raise ValueError + except* Exception as e: + continue + """) + + self.check_invalid( + """ + for i in range(5): + try: + pass + except* Exception as e: + if i == 2: + continue + """) + + self.check_invalid( + """ + for i in range(5): + try: + pass + except* Exception as e: + if i == 2: + continue + finally: + return 0 + """) + + def test_return_in_except_star_block_invalid(self): + self.check_invalid( + """ + def f(): + try: + raise ValueError + except* Exception as e: + return 42 + """) + + self.check_invalid( + """ + def f(): + try: + pass + except* Exception as e: + return 42 + finally: + finished = True + """) + + def test_break_continue_in_except_star_block_valid(self): + try: + raise ValueError(42) + except* Exception as e: + count = 0 + for i in range(5): + if i == 0: + continue + if i == 4: + break + count += 1 + + self.assertEqual(count, 3) + self.assertEqual(i, 4) + exc = e + self.assertIsInstance(exc, ExceptionGroup) + + def test_return_in_except_star_block_valid(self): + try: + raise ValueError(42) + except* Exception as e: + def f(x): + return 2*x + r = f(3) + exc = e + self.assertEqual(r, 6) + self.assertIsInstance(exc, ExceptionGroup) + + +class ExceptStarTest(ExceptionIsLikeMixin, unittest.TestCase): + def assertMetadataEqual(self, e1, e2): + if e1 is None or e2 is None: + self.assertTrue(e1 is None and e2 is None) + else: + self.assertEqual(e1.__context__, e2.__context__) + self.assertEqual(e1.__cause__, e2.__cause__) + self.assertEqual(e1.__traceback__, e2.__traceback__) + + def assertMetadataNotEqual(self, e1, e2): + if e1 is None or e2 is None: + self.assertNotEqual(e1, e2) + else: + return not (e1.__context__ == e2.__context__ + and e1.__cause__ == e2.__cause__ + and e1.__traceback__ == e2.__traceback__) + + +class TestExceptStarSplitSemantics(ExceptStarTest): + def doSplitTestNamed(self, exc, T, match_template, rest_template): + initial_sys_exception = sys.exception() + sys_exception = match = rest = None + try: + try: + raise exc + except* T as e: + sys_exception = sys.exception() + match = e + except BaseException as e: + rest = e + + self.assertEqual(sys_exception, match) + self.assertExceptionIsLike(match, match_template) + self.assertExceptionIsLike(rest, rest_template) + self.assertEqual(sys.exception(), initial_sys_exception) + + def doSplitTestUnnamed(self, exc, T, match_template, rest_template): + initial_sys_exception = sys.exception() + sys_exception = match = rest = None + try: + try: + raise exc + except* T: + sys_exception = match = sys.exception() + else: + if rest_template: + self.fail("Exception not raised") + except BaseException as e: + rest = e + self.assertExceptionIsLike(match, match_template) + self.assertExceptionIsLike(rest, rest_template) + self.assertEqual(sys.exception(), initial_sys_exception) + + def doSplitTestInExceptHandler(self, exc, T, match_template, rest_template): + try: + raise ExceptionGroup('eg', [TypeError(1), ValueError(2)]) + except Exception: + self.doSplitTestNamed(exc, T, match_template, rest_template) + self.doSplitTestUnnamed(exc, T, match_template, rest_template) + + def doSplitTestInExceptStarHandler(self, exc, T, match_template, rest_template): + try: + raise ExceptionGroup('eg', [TypeError(1), ValueError(2)]) + except* Exception: + self.doSplitTestNamed(exc, T, match_template, rest_template) + self.doSplitTestUnnamed(exc, T, match_template, rest_template) + + def doSplitTest(self, exc, T, match_template, rest_template): + self.doSplitTestNamed(exc, T, match_template, rest_template) + self.doSplitTestUnnamed(exc, T, match_template, rest_template) + self.doSplitTestInExceptHandler(exc, T, match_template, rest_template) + self.doSplitTestInExceptStarHandler(exc, T, match_template, rest_template) + + def test_no_match_single_type(self): + self.doSplitTest( + ExceptionGroup("test1", [ValueError("V"), TypeError("T")]), + SyntaxError, + None, + ExceptionGroup("test1", [ValueError("V"), TypeError("T")])) + + def test_match_single_type(self): + self.doSplitTest( + ExceptionGroup("test2", [ValueError("V1"), ValueError("V2")]), + ValueError, + ExceptionGroup("test2", [ValueError("V1"), ValueError("V2")]), + None) + + def test_match_single_type_partial_match(self): + self.doSplitTest( + ExceptionGroup( + "test3", + [ValueError("V1"), OSError("OS"), ValueError("V2")]), + ValueError, + ExceptionGroup("test3", [ValueError("V1"), ValueError("V2")]), + ExceptionGroup("test3", [OSError("OS")])) + + def test_match_single_type_nested(self): + self.doSplitTest( + ExceptionGroup( + "g1", [ + ValueError("V1"), + OSError("OS1"), + ExceptionGroup( + "g2", [ + OSError("OS2"), + ValueError("V2"), + TypeError("T")])]), + ValueError, + ExceptionGroup( + "g1", [ + ValueError("V1"), + ExceptionGroup("g2", [ValueError("V2")])]), + ExceptionGroup("g1", [ + OSError("OS1"), + ExceptionGroup("g2", [ + OSError("OS2"), TypeError("T")])])) + + def test_match_type_tuple_nested(self): + self.doSplitTest( + ExceptionGroup( + "h1", [ + ValueError("V1"), + OSError("OS1"), + ExceptionGroup( + "h2", [OSError("OS2"), ValueError("V2"), TypeError("T")])]), + (ValueError, TypeError), + ExceptionGroup( + "h1", [ + ValueError("V1"), + ExceptionGroup("h2", [ValueError("V2"), TypeError("T")])]), + ExceptionGroup( + "h1", [ + OSError("OS1"), + ExceptionGroup("h2", [OSError("OS2")])])) + + def test_empty_groups_removed(self): + self.doSplitTest( + ExceptionGroup( + "eg", [ + ExceptionGroup("i1", [ValueError("V1")]), + ExceptionGroup("i2", [ValueError("V2"), TypeError("T1")]), + ExceptionGroup("i3", [TypeError("T2")])]), + TypeError, + ExceptionGroup("eg", [ + ExceptionGroup("i2", [TypeError("T1")]), + ExceptionGroup("i3", [TypeError("T2")])]), + ExceptionGroup("eg", [ + ExceptionGroup("i1", [ValueError("V1")]), + ExceptionGroup("i2", [ValueError("V2")])])) + + def test_singleton_groups_are_kept(self): + self.doSplitTest( + ExceptionGroup("j1", [ + ExceptionGroup("j2", [ + ExceptionGroup("j3", [ValueError("V1")]), + ExceptionGroup("j4", [TypeError("T")])])]), + TypeError, + ExceptionGroup( + "j1", + [ExceptionGroup("j2", [ExceptionGroup("j4", [TypeError("T")])])]), + ExceptionGroup( + "j1", + [ExceptionGroup("j2", [ExceptionGroup("j3", [ValueError("V1")])])])) + + def test_naked_exception_matched_wrapped1(self): + self.doSplitTest( + ValueError("V"), + ValueError, + ExceptionGroup("", [ValueError("V")]), + None) + + def test_naked_exception_matched_wrapped2(self): + self.doSplitTest( + ValueError("V"), + Exception, + ExceptionGroup("", [ValueError("V")]), + None) + + def test_exception_group_except_star_Exception_not_wrapped(self): + self.doSplitTest( + ExceptionGroup("eg", [ValueError("V")]), + Exception, + ExceptionGroup("eg", [ValueError("V")]), + None) + + def test_plain_exception_not_matched(self): + self.doSplitTest( + ValueError("V"), + TypeError, + None, + ValueError("V")) + + def test_match__supertype(self): + self.doSplitTest( + ExceptionGroup("st", [BlockingIOError("io"), TypeError("T")]), + OSError, + ExceptionGroup("st", [BlockingIOError("io")]), + ExceptionGroup("st", [TypeError("T")])) + + def test_multiple_matches_named(self): + try: + raise ExceptionGroup("mmn", [OSError("os"), BlockingIOError("io")]) + except* BlockingIOError as e: + self.assertExceptionIsLike(e, + ExceptionGroup("mmn", [BlockingIOError("io")])) + except* OSError as e: + self.assertExceptionIsLike(e, + ExceptionGroup("mmn", [OSError("os")])) + else: + self.fail("Exception not raised") + + def test_multiple_matches_unnamed(self): + try: + raise ExceptionGroup("mmu", [OSError("os"), BlockingIOError("io")]) + except* BlockingIOError: + e = sys.exception() + self.assertExceptionIsLike(e, + ExceptionGroup("mmu", [BlockingIOError("io")])) + except* OSError: + e = sys.exception() + self.assertExceptionIsLike(e, + ExceptionGroup("mmu", [OSError("os")])) + else: + self.fail("Exception not raised") + + def test_first_match_wins_named(self): + try: + raise ExceptionGroup("fst", [BlockingIOError("io")]) + except* OSError as e: + self.assertExceptionIsLike(e, + ExceptionGroup("fst", [BlockingIOError("io")])) + except* BlockingIOError: + self.fail("Should have been matched as OSError") + else: + self.fail("Exception not raised") + + def test_first_match_wins_unnamed(self): + try: + raise ExceptionGroup("fstu", [BlockingIOError("io")]) + except* OSError: + e = sys.exception() + self.assertExceptionIsLike(e, + ExceptionGroup("fstu", [BlockingIOError("io")])) + except* BlockingIOError: + pass + else: + self.fail("Exception not raised") + + def test_nested_except_stars(self): + try: + raise ExceptionGroup("n", [BlockingIOError("io")]) + except* BlockingIOError: + try: + raise ExceptionGroup("n", [ValueError("io")]) + except* ValueError: + pass + else: + self.fail("Exception not raised") + e = sys.exception() + self.assertExceptionIsLike(e, + ExceptionGroup("n", [BlockingIOError("io")])) + else: + self.fail("Exception not raised") + + def test_nested_in_loop(self): + for _ in range(2): + try: + raise ExceptionGroup("nl", [BlockingIOError("io")]) + except* BlockingIOError: + pass + else: + self.fail("Exception not raised") + + +class TestExceptStarReraise(ExceptStarTest): + def test_reraise_all_named(self): + try: + try: + raise ExceptionGroup( + "eg", [TypeError(1), ValueError(2), OSError(3)]) + except* TypeError as e: + raise + except* ValueError as e: + raise + # OSError not handled + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, + ExceptionGroup("eg", [TypeError(1), ValueError(2), OSError(3)])) + + def test_reraise_all_unnamed(self): + try: + try: + raise ExceptionGroup( + "eg", [TypeError(1), ValueError(2), OSError(3)]) + except* TypeError: + raise + except* ValueError: + raise + # OSError not handled + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, + ExceptionGroup("eg", [TypeError(1), ValueError(2), OSError(3)])) + + def test_reraise_some_handle_all_named(self): + try: + try: + raise ExceptionGroup( + "eg", [TypeError(1), ValueError(2), OSError(3)]) + except* TypeError as e: + raise + except* ValueError as e: + pass + # OSError not handled + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, ExceptionGroup("eg", [TypeError(1), OSError(3)])) + + def test_reraise_partial_handle_all_unnamed(self): + try: + try: + raise ExceptionGroup( + "eg", [TypeError(1), ValueError(2)]) + except* TypeError: + raise + except* ValueError: + pass + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, ExceptionGroup("eg", [TypeError(1)])) + + def test_reraise_partial_handle_some_named(self): + try: + try: + raise ExceptionGroup( + "eg", [TypeError(1), ValueError(2), OSError(3)]) + except* TypeError as e: + raise + except* ValueError as e: + pass + # OSError not handled + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, ExceptionGroup("eg", [TypeError(1), OSError(3)])) + + def test_reraise_partial_handle_some_unnamed(self): + try: + try: + raise ExceptionGroup( + "eg", [TypeError(1), ValueError(2), OSError(3)]) + except* TypeError: + raise + except* ValueError: + pass + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, ExceptionGroup("eg", [TypeError(1), OSError(3)])) + + def test_reraise_plain_exception_named(self): + try: + try: + raise ValueError(42) + except* ValueError as e: + raise + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, ExceptionGroup("", [ValueError(42)])) + + def test_reraise_plain_exception_unnamed(self): + try: + try: + raise ValueError(42) + except* ValueError: + raise + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, ExceptionGroup("", [ValueError(42)])) + + +class TestExceptStarRaise(ExceptStarTest): + def test_raise_named(self): + orig = ExceptionGroup("eg", [ValueError(1), OSError(2)]) + try: + try: + raise orig + except* OSError as e: + raise TypeError(3) + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, + ExceptionGroup( + "", [TypeError(3), ExceptionGroup("eg", [ValueError(1)])])) + + self.assertExceptionIsLike( + exc.exceptions[0].__context__, + ExceptionGroup("eg", [OSError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.exceptions[0].__context__) + + def test_raise_unnamed(self): + orig = ExceptionGroup("eg", [ValueError(1), OSError(2)]) + try: + try: + raise orig + except* OSError: + raise TypeError(3) + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, + ExceptionGroup( + "", [TypeError(3), ExceptionGroup("eg", [ValueError(1)])])) + + self.assertExceptionIsLike( + exc.exceptions[0].__context__, + ExceptionGroup("eg", [OSError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.exceptions[0].__context__) + + def test_raise_handle_all_raise_one_named(self): + orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)]) + try: + try: + raise orig + except* (TypeError, ValueError) as e: + raise SyntaxError(3) + except SyntaxError as e: + exc = e + + self.assertExceptionIsLike(exc, SyntaxError(3)) + + self.assertExceptionIsLike( + exc.__context__, + ExceptionGroup("eg", [TypeError(1), ValueError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.__context__) + + def test_raise_handle_all_raise_one_unnamed(self): + orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)]) + try: + try: + raise orig + except* (TypeError, ValueError) as e: + raise SyntaxError(3) + except SyntaxError as e: + exc = e + + self.assertExceptionIsLike(exc, SyntaxError(3)) + + self.assertExceptionIsLike( + exc.__context__, + ExceptionGroup("eg", [TypeError(1), ValueError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.__context__) + + def test_raise_handle_all_raise_two_named(self): + orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)]) + try: + try: + raise orig + except* TypeError as e: + raise SyntaxError(3) + except* ValueError as e: + raise SyntaxError(4) + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, ExceptionGroup("", [SyntaxError(3), SyntaxError(4)])) + + self.assertExceptionIsLike( + exc.exceptions[0].__context__, + ExceptionGroup("eg", [TypeError(1)])) + + self.assertExceptionIsLike( + exc.exceptions[1].__context__, + ExceptionGroup("eg", [ValueError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.exceptions[0].__context__) + self.assertMetadataEqual(orig, exc.exceptions[1].__context__) + + def test_raise_handle_all_raise_two_unnamed(self): + orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)]) + try: + try: + raise orig + except* TypeError: + raise SyntaxError(3) + except* ValueError: + raise SyntaxError(4) + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, ExceptionGroup("", [SyntaxError(3), SyntaxError(4)])) + + self.assertExceptionIsLike( + exc.exceptions[0].__context__, + ExceptionGroup("eg", [TypeError(1)])) + + self.assertExceptionIsLike( + exc.exceptions[1].__context__, + ExceptionGroup("eg", [ValueError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.exceptions[0].__context__) + self.assertMetadataEqual(orig, exc.exceptions[1].__context__) + + +class TestExceptStarRaiseFrom(ExceptStarTest): + def test_raise_named(self): + orig = ExceptionGroup("eg", [ValueError(1), OSError(2)]) + try: + try: + raise orig + except* OSError as e: + raise TypeError(3) from e + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, + ExceptionGroup( + "", [TypeError(3), ExceptionGroup("eg", [ValueError(1)])])) + + self.assertExceptionIsLike( + exc.exceptions[0].__context__, + ExceptionGroup("eg", [OSError(2)])) + + self.assertExceptionIsLike( + exc.exceptions[0].__cause__, + ExceptionGroup("eg", [OSError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.exceptions[0].__context__) + self.assertMetadataEqual(orig, exc.exceptions[0].__cause__) + self.assertMetadataNotEqual(orig, exc.exceptions[1].__context__) + self.assertMetadataNotEqual(orig, exc.exceptions[1].__cause__) + + def test_raise_unnamed(self): + orig = ExceptionGroup("eg", [ValueError(1), OSError(2)]) + try: + try: + raise orig + except* OSError: + e = sys.exception() + raise TypeError(3) from e + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, + ExceptionGroup( + "", [TypeError(3), ExceptionGroup("eg", [ValueError(1)])])) + + self.assertExceptionIsLike( + exc.exceptions[0].__context__, + ExceptionGroup("eg", [OSError(2)])) + + self.assertExceptionIsLike( + exc.exceptions[0].__cause__, + ExceptionGroup("eg", [OSError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.exceptions[0].__context__) + self.assertMetadataEqual(orig, exc.exceptions[0].__cause__) + self.assertMetadataNotEqual(orig, exc.exceptions[1].__context__) + self.assertMetadataNotEqual(orig, exc.exceptions[1].__cause__) + + def test_raise_handle_all_raise_one_named(self): + orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)]) + try: + try: + raise orig + except* (TypeError, ValueError) as e: + raise SyntaxError(3) from e + except SyntaxError as e: + exc = e + + self.assertExceptionIsLike(exc, SyntaxError(3)) + + self.assertExceptionIsLike( + exc.__context__, + ExceptionGroup("eg", [TypeError(1), ValueError(2)])) + + self.assertExceptionIsLike( + exc.__cause__, + ExceptionGroup("eg", [TypeError(1), ValueError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.__context__) + self.assertMetadataEqual(orig, exc.__cause__) + + def test_raise_handle_all_raise_one_unnamed(self): + orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)]) + try: + try: + raise orig + except* (TypeError, ValueError) as e: + e = sys.exception() + raise SyntaxError(3) from e + except SyntaxError as e: + exc = e + + self.assertExceptionIsLike(exc, SyntaxError(3)) + + self.assertExceptionIsLike( + exc.__context__, + ExceptionGroup("eg", [TypeError(1), ValueError(2)])) + + self.assertExceptionIsLike( + exc.__cause__, + ExceptionGroup("eg", [TypeError(1), ValueError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.__context__) + self.assertMetadataEqual(orig, exc.__cause__) + + def test_raise_handle_all_raise_two_named(self): + orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)]) + try: + try: + raise orig + except* TypeError as e: + raise SyntaxError(3) from e + except* ValueError as e: + raise SyntaxError(4) from e + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, ExceptionGroup("", [SyntaxError(3), SyntaxError(4)])) + + self.assertExceptionIsLike( + exc.exceptions[0].__context__, + ExceptionGroup("eg", [TypeError(1)])) + + self.assertExceptionIsLike( + exc.exceptions[0].__cause__, + ExceptionGroup("eg", [TypeError(1)])) + + self.assertExceptionIsLike( + exc.exceptions[1].__context__, + ExceptionGroup("eg", [ValueError(2)])) + + self.assertExceptionIsLike( + exc.exceptions[1].__cause__, + ExceptionGroup("eg", [ValueError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.exceptions[0].__context__) + self.assertMetadataEqual(orig, exc.exceptions[0].__cause__) + + def test_raise_handle_all_raise_two_unnamed(self): + orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)]) + try: + try: + raise orig + except* TypeError: + e = sys.exception() + raise SyntaxError(3) from e + except* ValueError: + e = sys.exception() + raise SyntaxError(4) from e + except ExceptionGroup as e: + exc = e + + self.assertExceptionIsLike( + exc, ExceptionGroup("", [SyntaxError(3), SyntaxError(4)])) + + self.assertExceptionIsLike( + exc.exceptions[0].__context__, + ExceptionGroup("eg", [TypeError(1)])) + + self.assertExceptionIsLike( + exc.exceptions[0].__cause__, + ExceptionGroup("eg", [TypeError(1)])) + + self.assertExceptionIsLike( + exc.exceptions[1].__context__, + ExceptionGroup("eg", [ValueError(2)])) + + self.assertExceptionIsLike( + exc.exceptions[1].__cause__, + ExceptionGroup("eg", [ValueError(2)])) + + self.assertMetadataNotEqual(orig, exc) + self.assertMetadataEqual(orig, exc.exceptions[0].__context__) + self.assertMetadataEqual(orig, exc.exceptions[0].__cause__) + self.assertMetadataEqual(orig, exc.exceptions[1].__context__) + self.assertMetadataEqual(orig, exc.exceptions[1].__cause__) + + +class TestExceptStarExceptionGroupSubclass(ExceptStarTest): + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_except_star_EG_subclass(self): + class EG(ExceptionGroup): + def __new__(cls, message, excs, code): + obj = super().__new__(cls, message, excs) + obj.code = code + return obj + + def derive(self, excs): + return EG(self.message, excs, self.code) + + try: + try: + try: + try: + raise TypeError(2) + except TypeError as te: + raise EG("nested", [te], 101) from None + except EG as nested: + try: + raise ValueError(1) + except ValueError as ve: + raise EG("eg", [ve, nested], 42) + except* ValueError as eg: + veg = eg + except EG as eg: + teg = eg + + self.assertIsInstance(veg, EG) + self.assertIsInstance(teg, EG) + self.assertIsInstance(teg.exceptions[0], EG) + self.assertMetadataEqual(veg, teg) + self.assertEqual(veg.code, 42) + self.assertEqual(teg.code, 42) + self.assertEqual(teg.exceptions[0].code, 101) + + def test_falsy_exception_group_subclass(self): + class FalsyEG(ExceptionGroup): + def __bool__(self): + return False + + def derive(self, excs): + return FalsyEG(self.message, excs) + + try: + try: + raise FalsyEG("eg", [TypeError(1), ValueError(2)]) + except *TypeError as e: + tes = e + raise + except *ValueError as e: + ves = e + pass + except Exception as e: + exc = e + + for e in [tes, ves, exc]: + self.assertFalse(e) + self.assertIsInstance(e, FalsyEG) + + self.assertExceptionIsLike(exc, FalsyEG("eg", [TypeError(1)])) + self.assertExceptionIsLike(tes, FalsyEG("eg", [TypeError(1)])) + self.assertExceptionIsLike(ves, FalsyEG("eg", [ValueError(2)])) + + def test_exception_group_subclass_with_bad_split_func(self): + # see gh-128049. + class BadEG1(ExceptionGroup): + def split(self, *args): + return "NOT A 2-TUPLE!" + + class BadEG2(ExceptionGroup): + def split(self, *args): + return ("NOT A 2-TUPLE!",) + + eg_list = [ + (BadEG1("eg", [OSError(123), ValueError(456)]), + r"split must return a tuple, not str"), + (BadEG2("eg", [OSError(123), ValueError(456)]), + r"split must return a 2-tuple, got tuple of size 1") + ] + + for eg_class, msg in eg_list: + with self.assertRaisesRegex(TypeError, msg) as m: + try: + raise eg_class + except* ValueError: + pass + except* OSError: + pass + + self.assertExceptionIsLike(m.exception.__context__, eg_class) + + # we allow tuples of length > 2 for backwards compatibility + class WeirdEG(ExceptionGroup): + def split(self, *args): + return super().split(*args) + ("anything", 123456, None) + + try: + raise WeirdEG("eg", [OSError(123), ValueError(456)]) + except* OSError as e: + oeg = e + except* ValueError as e: + veg = e + + self.assertExceptionIsLike(oeg, WeirdEG("eg", [OSError(123)])) + self.assertExceptionIsLike(veg, WeirdEG("eg", [ValueError(456)])) + + +class TestExceptStarCleanup(ExceptStarTest): + def test_sys_exception_restored(self): + try: + try: + raise ValueError(42) + except: + try: + raise TypeError(int) + except* Exception: + pass + 1/0 + except Exception as e: + exc = e + + self.assertExceptionIsLike(exc, ZeroDivisionError('division by zero')) + self.assertExceptionIsLike(exc.__context__, ValueError(42)) + self.assertEqual(sys.exception(), None) + + +class TestExceptStar_WeirdLeafExceptions(ExceptStarTest): + # Test that except* works when leaf exceptions are + # unhashable or have a bad custom __eq__ + + class UnhashableExc(ValueError): + __hash__ = None + + class AlwaysEqualExc(ValueError): + def __eq__(self, other): + return True + + class NeverEqualExc(ValueError): + def __eq__(self, other): + return False + + class BrokenEqualExc(ValueError): + def __eq__(self, other): + raise RuntimeError() + + def setUp(self): + self.bad_types = [self.UnhashableExc, + self.AlwaysEqualExc, + self.NeverEqualExc, + self.BrokenEqualExc] + + def except_type(self, eg, type): + match, rest = None, None + try: + try: + raise eg + except* type as e: + match = e + except Exception as e: + rest = e + return match, rest + + def test_catch_unhashable_leaf_exception(self): + for Bad in self.bad_types: + with self.subTest(Bad): + eg = ExceptionGroup("eg", [TypeError(1), Bad(2)]) + match, rest = self.except_type(eg, Bad) + self.assertExceptionIsLike( + match, ExceptionGroup("eg", [Bad(2)])) + self.assertExceptionIsLike( + rest, ExceptionGroup("eg", [TypeError(1)])) + + def test_propagate_unhashable_leaf(self): + for Bad in self.bad_types: + with self.subTest(Bad): + eg = ExceptionGroup("eg", [TypeError(1), Bad(2)]) + match, rest = self.except_type(eg, TypeError) + self.assertExceptionIsLike( + match, ExceptionGroup("eg", [TypeError(1)])) + self.assertExceptionIsLike( + rest, ExceptionGroup("eg", [Bad(2)])) + + def test_catch_nothing_unhashable_leaf(self): + for Bad in self.bad_types: + with self.subTest(Bad): + eg = ExceptionGroup("eg", [TypeError(1), Bad(2)]) + match, rest = self.except_type(eg, OSError) + self.assertIsNone(match) + self.assertExceptionIsLike(rest, eg) + + def test_catch_everything_unhashable_leaf(self): + for Bad in self.bad_types: + with self.subTest(Bad): + eg = ExceptionGroup("eg", [TypeError(1), Bad(2)]) + match, rest = self.except_type(eg, Exception) + self.assertExceptionIsLike(match, eg) + self.assertIsNone(rest) + + def test_reraise_unhashable_leaf(self): + for Bad in self.bad_types: + with self.subTest(Bad): + eg = ExceptionGroup( + "eg", [TypeError(1), Bad(2), ValueError(3)]) + + try: + try: + raise eg + except* TypeError: + pass + except* Bad: + raise + except Exception as e: + exc = e + + self.assertExceptionIsLike( + exc, ExceptionGroup("eg", [Bad(2), ValueError(3)])) + + +class TestExceptStar_WeirdExceptionGroupSubclass(ExceptStarTest): + # Test that except* works with exception groups that are + # unhashable or have a bad custom __eq__ + + class UnhashableEG(ExceptionGroup): + __hash__ = None + + def derive(self, excs): + return type(self)(self.message, excs) + + class AlwaysEqualEG(ExceptionGroup): + def __eq__(self, other): + return True + + def derive(self, excs): + return type(self)(self.message, excs) + + class NeverEqualEG(ExceptionGroup): + def __eq__(self, other): + return False + + def derive(self, excs): + return type(self)(self.message, excs) + + class BrokenEqualEG(ExceptionGroup): + def __eq__(self, other): + raise RuntimeError() + + def derive(self, excs): + return type(self)(self.message, excs) + + def setUp(self): + self.bad_types = [self.UnhashableEG, + self.AlwaysEqualEG, + self.NeverEqualEG, + self.BrokenEqualEG] + + def except_type(self, eg, type): + match, rest = None, None + try: + try: + raise eg + except* type as e: + match = e + except Exception as e: + rest = e + return match, rest + + def test_catch_some_unhashable_exception_group_subclass(self): + for BadEG in self.bad_types: + with self.subTest(BadEG): + eg = BadEG("eg", + [TypeError(1), + BadEG("nested", [ValueError(2)])]) + + match, rest = self.except_type(eg, TypeError) + self.assertExceptionIsLike(match, BadEG("eg", [TypeError(1)])) + self.assertExceptionIsLike(rest, + BadEG("eg", [BadEG("nested", [ValueError(2)])])) + + def test_catch_none_unhashable_exception_group_subclass(self): + for BadEG in self.bad_types: + with self.subTest(BadEG): + + eg = BadEG("eg", + [TypeError(1), + BadEG("nested", [ValueError(2)])]) + + match, rest = self.except_type(eg, OSError) + self.assertIsNone(match) + self.assertExceptionIsLike(rest, eg) + + def test_catch_all_unhashable_exception_group_subclass(self): + for BadEG in self.bad_types: + with self.subTest(BadEG): + + eg = BadEG("eg", + [TypeError(1), + BadEG("nested", [ValueError(2)])]) + + match, rest = self.except_type(eg, Exception) + self.assertExceptionIsLike(match, eg) + self.assertIsNone(rest) + + def test_reraise_unhashable_eg(self): + for BadEG in self.bad_types: + with self.subTest(BadEG): + + eg = BadEG("eg", + [TypeError(1), ValueError(2), + BadEG("nested", [ValueError(3), OSError(4)])]) + + try: + try: + raise eg + except* ValueError: + pass + except* OSError: + raise + except Exception as e: + exc = e + + self.assertExceptionIsLike( + exc, BadEG("eg", [TypeError(1), + BadEG("nested", [OSError(4)])])) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 4a0ef83a57e..7909e924251 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -1580,13 +1580,18 @@ impl Compiler { } Stmt::Break(_) => { // Find the innermost loop in fblock stack + // Error if we encounter ExceptionGroupHandler before finding a loop let found_loop = { let code = self.current_code_info(); - let mut result = None; + let mut result = Ok(None); for i in (0..code.fblock.len()).rev() { match code.fblock[i].fb_type { FBlockType::WhileLoop | FBlockType::ForLoop => { - result = Some(code.fblock[i].fb_exit); + result = Ok(Some(code.fblock[i].fb_exit)); + break; + } + FBlockType::ExceptionGroupHandler => { + result = Err(()); break; } _ => continue, @@ -1596,25 +1601,36 @@ impl Compiler { }; match found_loop { - Some(exit_block) => { + Ok(Some(exit_block)) => { emit!(self, Instruction::Break { target: exit_block }); } - None => { + Ok(None) => { return Err( self.error_ranged(CodegenErrorType::InvalidBreak, statement.range()) ); } + Err(()) => { + return Err(self.error_ranged( + CodegenErrorType::BreakContinueReturnInExceptStar, + statement.range(), + )); + } } } Stmt::Continue(_) => { // Find the innermost loop in fblock stack + // Error if we encounter ExceptionGroupHandler before finding a loop let found_loop = { let code = self.current_code_info(); - let mut result = None; + let mut result = Ok(None); for i in (0..code.fblock.len()).rev() { match code.fblock[i].fb_type { FBlockType::WhileLoop | FBlockType::ForLoop => { - result = Some(code.fblock[i].fb_block); + result = Ok(Some(code.fblock[i].fb_block)); + break; + } + FBlockType::ExceptionGroupHandler => { + result = Err(()); break; } _ => continue, @@ -1624,14 +1640,20 @@ impl Compiler { }; match found_loop { - Some(loop_block) => { + Ok(Some(loop_block)) => { emit!(self, Instruction::Continue { target: loop_block }); } - None => { + Ok(None) => { return Err( self.error_ranged(CodegenErrorType::InvalidContinue, statement.range()) ); } + Err(()) => { + return Err(self.error_ranged( + CodegenErrorType::BreakContinueReturnInExceptStar, + statement.range(), + )); + } } } Stmt::Return(StmtReturn { value, .. }) => { @@ -1640,6 +1662,18 @@ impl Compiler { self.error_ranged(CodegenErrorType::InvalidReturn, statement.range()) ); } + // Check if we're inside an except* block in the current function + { + let code = self.current_code_info(); + for block in code.fblock.iter().rev() { + if matches!(block.fb_type, FBlockType::ExceptionGroupHandler) { + return Err(self.error_ranged( + CodegenErrorType::BreakContinueReturnInExceptStar, + statement.range(), + )); + } + } + } match value { Some(v) => { if self.ctx.func == FunctionContext::AsyncFunction @@ -2126,9 +2160,14 @@ impl Compiler { orelse: &[Stmt], finalbody: &[Stmt], ) -> CompileResult<()> { - // Simplified except* implementation using CheckEgMatch + // Simplified except* implementation using PrepReraiseStar intrinsic + // Stack layout during handler processing: [orig, list, rest] let handler_block = self.new_block(); let finally_block = self.new_block(); + let else_block = self.new_block(); + let end_block = self.new_block(); + let reraise_star_block = self.new_block(); + let reraise_block = self.new_block(); if !finalbody.is_empty() { emit!( @@ -2139,8 +2178,6 @@ impl Compiler { ); } - let else_block = self.new_block(); - emit!( self, Instruction::SetupExcept { @@ -2151,20 +2188,32 @@ impl Compiler { emit!(self, Instruction::PopBlock); emit!(self, Instruction::Jump { target: else_block }); + // Exception handler entry self.switch_to_block(handler_block); // Stack: [exc] - for handler in handlers { + // Create list for tracking exception results and copy orig + emit!(self, Instruction::BuildList { size: 0 }); + // Stack: [exc, []] + // CopyItem is 1-indexed: CopyItem(1)=TOS, CopyItem(2)=second from top + // With stack [exc, []], CopyItem(2) copies exc + emit!(self, Instruction::CopyItem { index: 2 }); + // Stack: [exc, [], exc_copy] + + // Now stack is: [orig, list, rest] + + let n = handlers.len(); + for (i, handler) in handlers.iter().enumerate() { let ExceptHandler::ExceptHandler(ExceptHandlerExceptHandler { type_, name, body, .. }) = handler; - let skip_block = self.new_block(); + let no_match_block = self.new_block(); let next_block = self.new_block(); // Compile exception type if let Some(exc_type) = type_ { - // Check for unparenthesized tuple (e.g., `except* A, B:` instead of `except* (A, B):`) + // Check for unparenthesized tuple if let Expr::Tuple(ExprTuple { elts, range, .. }) = exc_type.as_ref() && let Some(first) = elts.first() && range.start().to_u32() == first.range().start().to_u32() @@ -2179,67 +2228,161 @@ impl Compiler { "except* must specify an exception type".to_owned(), ))); } - // Stack: [exc, type] + // Stack: [orig, list, rest, type] emit!(self, Instruction::CheckEgMatch); - // Stack: [rest, match] + // Stack: [orig, list, new_rest, match] - // Check if match is None (truthy check) + // Check if match is not None (use identity check, not truthiness) + // CopyItem is 1-indexed: CopyItem(1) = TOS, CopyItem(2) = second from top emit!(self, Instruction::CopyItem { index: 1 }); - emit!(self, Instruction::ToBool); - emit!(self, Instruction::PopJumpIfFalse { target: skip_block }); + self.emit_load_const(ConstantData::None); + emit!(self, Instruction::IsOp(bytecode::Invert::No)); // is None? + emit!( + self, + Instruction::PopJumpIfTrue { + target: no_match_block + } + ); - // Handler matched - store match to name if provided - // Stack: [rest, match] + // Handler matched + // Stack: [orig, list, new_rest, match] + let handler_except_block = self.new_block(); + let handler_done_block = self.new_block(); + + // Set matched exception as current exception for bare 'raise' + emit!(self, Instruction::SetExcInfo); + + // Store match to name if provided if let Some(alias) = name { + // CopyItem(1) copies TOS (match) + emit!(self, Instruction::CopyItem { index: 1 }); self.store_name(alias.as_str())?; - } else { - emit!(self, Instruction::PopTop); } - // Stack: [rest] + // Stack: [orig, list, new_rest, match] + + // Setup exception handler to catch 'raise' in handler body + emit!( + self, + Instruction::SetupExcept { + handler: handler_except_block, + } + ); + + // Push fblock to disallow break/continue/return in except* handler + self.push_fblock( + FBlockType::ExceptionGroupHandler, + handler_done_block, + end_block, + )?; + // Execute handler body self.compile_statements(body)?; + // Handler body completed normally (didn't raise) + self.pop_fblock(FBlockType::ExceptionGroupHandler); + emit!(self, Instruction::PopBlock); + + // Cleanup name binding + if let Some(alias) = name { + self.emit_load_const(ConstantData::None); + self.store_name(alias.as_str())?; + self.compile_name(alias.as_str(), NameUsage::Delete)?; + } + + // Stack: [orig, list, new_rest, match] + // Pop match (handler consumed it) + emit!(self, Instruction::PopTop); + // Stack: [orig, list, new_rest] + + // Append None to list (exception was consumed, not reraised) + self.emit_load_const(ConstantData::None); + // Stack: [orig, list, new_rest, None] + emit!(self, Instruction::ListAppend { i: 1 }); + // Stack: [orig, list, new_rest] + + emit!( + self, + Instruction::Jump { + target: handler_done_block + } + ); + + // Handler raised an exception (bare 'raise' or other) + self.switch_to_block(handler_except_block); + // Stack: [orig, list, new_rest, match, raised_exc] + + // Cleanup name binding if let Some(alias) = name { self.emit_load_const(ConstantData::None); self.store_name(alias.as_str())?; self.compile_name(alias.as_str(), NameUsage::Delete)?; } + // Append raised_exc to list (the actual exception that was raised) + // Stack: [orig, list, new_rest, match, raised_exc] + // ListAppend(2): pop raised_exc, then append to list at stack[4-2-1]=stack[1] + emit!(self, Instruction::ListAppend { i: 2 }); + // Stack: [orig, list, new_rest, match] + + // Pop match (no longer needed) + emit!(self, Instruction::PopTop); + // Stack: [orig, list, new_rest] + + self.switch_to_block(handler_done_block); + // Stack: [orig, list, new_rest] + emit!(self, Instruction::Jump { target: next_block }); - // No match - pop match (None) and continue with rest - self.switch_to_block(skip_block); - emit!(self, Instruction::PopTop); // drop match (None) - // Stack: [rest] + // No match - pop match (None), keep rest unchanged + self.switch_to_block(no_match_block); + emit!(self, Instruction::PopTop); // pop match (None) + // Stack: [orig, list, new_rest] self.switch_to_block(next_block); - // Stack: [rest] - continue with rest for next handler + // Stack: [orig, list, rest] (rest may have been updated) + + // After last handler, append remaining rest to list + if i == n - 1 { + // Stack: [orig, list, rest] + // ListAppend(i) pops TOS, then accesses stack[len - i - 1] + // After pop, stack is [orig, list], len=2 + // We want list at index 1, so 2 - i - 1 = 1, i = 0 + emit!(self, Instruction::ListAppend { i: 0 }); + // Stack: [orig, list] + emit!( + self, + Instruction::Jump { + target: reraise_star_block + } + ); + } } - let handled_block = self.new_block(); - - // Check if remainder is truthy (has unhandled exceptions) - // Stack: [rest] - emit!(self, Instruction::CopyItem { index: 1 }); - emit!(self, Instruction::ToBool); + // Reraise star block + self.switch_to_block(reraise_star_block); + // Stack: [orig, list] emit!( self, - Instruction::PopJumpIfFalse { - target: handled_block + Instruction::CallIntrinsic2 { + func: bytecode::IntrinsicFunction2::PrepReraiseStar } ); - // Reraise unhandled exceptions + // Stack: [result] (exception to reraise or None) + + // Check if result is not None (use identity check, not truthiness) + emit!(self, Instruction::CopyItem { index: 1 }); + self.emit_load_const(ConstantData::None); + emit!(self, Instruction::IsOp(bytecode::Invert::Yes)); // is not None? emit!( self, - Instruction::Raise { - kind: bytecode::RaiseKind::Raise + Instruction::PopJumpIfTrue { + target: reraise_block } ); - // All exceptions handled - self.switch_to_block(handled_block); - emit!(self, Instruction::PopTop); // drop remainder (None) + // Nothing to reraise + emit!(self, Instruction::PopTop); emit!(self, Instruction::PopException); if !finalbody.is_empty() { @@ -2247,10 +2390,17 @@ impl Compiler { emit!(self, Instruction::EnterFinally); } + emit!(self, Instruction::Jump { target: end_block }); + + // Reraise the result + self.switch_to_block(reraise_block); + // Don't call PopException before Raise - it truncates the stack and removes the result. + // When Raise is executed, the exception propagates through unwind_blocks which + // will properly handle the ExceptHandler block. emit!( self, - Instruction::Jump { - target: finally_block, + Instruction::Raise { + kind: bytecode::RaiseKind::Raise } ); @@ -2263,8 +2413,11 @@ impl Compiler { emit!(self, Instruction::EnterFinally); } - self.switch_to_block(finally_block); + emit!(self, Instruction::Jump { target: end_block }); + + self.switch_to_block(end_block); if !finalbody.is_empty() { + self.switch_to_block(finally_block); self.compile_statements(finalbody)?; emit!(self, Instruction::EndFinally); } diff --git a/crates/codegen/src/error.rs b/crates/codegen/src/error.rs index a0e36bf29ae..70e2f13f253 100644 --- a/crates/codegen/src/error.rs +++ b/crates/codegen/src/error.rs @@ -88,6 +88,8 @@ pub enum CodegenErrorType { UnreachablePattern(PatternUnreachableReason), RepeatedAttributePattern, ConflictingNameBindPattern, + /// break/continue/return inside except* block + BreakContinueReturnInExceptStar, NotImplementedYet, // RustPython marker for unimplemented features } @@ -148,6 +150,12 @@ impl fmt::Display for CodegenErrorType { ConflictingNameBindPattern => { write!(f, "alternative patterns bind different names") } + BreakContinueReturnInExceptStar => { + write!( + f, + "'break', 'continue' and 'return' cannot appear in an except* block" + ) + } NotImplementedYet => { write!(f, "RustPython does not implement this feature yet") } diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index add2c1c2f63..8df5d9caf6f 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -865,11 +865,14 @@ pub enum Instruction { WithCleanupStart, YieldFrom, YieldValue, + /// Set the current exception to TOS (for except* handlers). + /// Does not pop the value. + SetExcInfo, // If you add a new instruction here, be sure to keep LAST_INSTRUCTION updated } // This must be kept up to date to avoid marshaling errors -const LAST_INSTRUCTION: Instruction = Instruction::YieldValue; +const LAST_INSTRUCTION: Instruction = Instruction::SetExcInfo; const _: () = assert!(mem::size_of::<Instruction>() == 1); @@ -1743,6 +1746,7 @@ impl Instruction { Resume { .. } => 0, YieldValue => 0, YieldFrom => -1, + SetExcInfo => 0, SetupAnnotation | SetupLoop | SetupFinally { .. } | EnterFinally | EndFinally => 0, SetupExcept { .. } => jump as i32, SetupWith { .. } => (!jump) as i32, @@ -1961,6 +1965,7 @@ impl Instruction { SetupWith { end } => w!(SETUP_WITH, end), StoreAttr { idx } => w!(STORE_ATTR, name = idx), StoreDeref(idx) => w!(STORE_DEREF, cell_name = idx), + SetExcInfo => w!(SET_EXC_INFO), StoreFast(idx) => w!(STORE_FAST, varname = idx), StoreGlobal(idx) => w!(STORE_GLOBAL, name = idx), StoreLocal(idx) => w!(STORE_LOCAL, name = idx), diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 7b8af83489e..0e2051e5498 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -2433,6 +2433,40 @@ pub(super) mod types { pub struct PyEncodingWarning(PyWarning); } +/// Check if match_type is valid for except* (must be exception type, not ExceptionGroup). +fn check_except_star_type_valid(match_type: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let base_exc: PyObjectRef = vm.ctx.exceptions.base_exception_type.to_owned().into(); + let base_eg: PyObjectRef = vm.ctx.exceptions.base_exception_group.to_owned().into(); + + // Helper to check a single type + let check_one = |exc_type: &PyObjectRef| -> PyResult<()> { + // Must be a subclass of BaseException + if !exc_type.is_subclass(&base_exc, vm)? { + return Err(vm.new_type_error( + "catching classes that do not inherit from BaseException is not allowed".to_owned(), + )); + } + // Must not be a subclass of BaseExceptionGroup + if exc_type.is_subclass(&base_eg, vm)? { + return Err(vm.new_type_error( + "catching ExceptionGroup with except* is not allowed. Use except instead." + .to_owned(), + )); + } + Ok(()) + }; + + // If it's a tuple, check each element + if let Ok(tuple) = match_type.clone().downcast::<PyTuple>() { + for item in tuple.iter() { + check_one(item)?; + } + } else { + check_one(match_type)?; + } + Ok(()) +} + /// Match exception against except* handler type. /// Returns (rest, match) tuple. pub fn exception_group_match( @@ -2447,6 +2481,9 @@ pub fn exception_group_match( return Ok((vm.ctx.none(), vm.ctx.none())); } + // Validate match_type and reject ExceptionGroup/BaseExceptionGroup + check_except_star_type_valid(match_type, vm)?; + // Check if exc_value matches match_type if exc_value.is_instance(match_type, vm)? { // Full match of exc itself @@ -2473,6 +2510,13 @@ pub fn exception_group_match( // Check for partial match if it's an exception group if exc_value.fast_isinstance(vm.ctx.exceptions.base_exception_group) { let pair = vm.call_method(exc_value, "split", (match_type.clone(),))?; + if !pair.class().is(vm.ctx.types.tuple_type) { + return Err(vm.new_type_error(format!( + "{}.split must return a tuple, not {}", + exc_value.class().name(), + pair.class().name() + ))); + } let pair_tuple: PyTupleRef = pair.try_into_value(vm)?; if pair_tuple.len() < 2 { return Err(vm.new_type_error(format!( @@ -2491,8 +2535,8 @@ pub fn exception_group_match( } /// Prepare exception for reraise in except* block. +/// Implements _PyExc_PrepReraiseStar pub fn prep_reraise_star(orig: PyObjectRef, excs: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // Implements _PyExc_PrepReraiseStar use crate::builtins::PyList; let excs_list = excs @@ -2501,7 +2545,20 @@ pub fn prep_reraise_star(orig: PyObjectRef, excs: PyObjectRef, vm: &VirtualMachi let excs_vec: Vec<PyObjectRef> = excs_list.borrow_vec().to_vec(); - // Filter out None values + // If no exceptions to process, return None + if excs_vec.is_empty() { + return Ok(vm.ctx.none()); + } + + // Special case: naked exception (not an ExceptionGroup) + // Only one except* clause could have executed, so there's at most one exception to raise + if !orig.fast_isinstance(vm.ctx.exceptions.base_exception_group) { + // Find first non-None exception + let first = excs_vec.into_iter().find(|e| !vm.is_none(e)); + return Ok(first.unwrap_or_else(|| vm.ctx.none())); + } + + // Split excs into raised (new) and reraised (from original) by comparing metadata let mut raised: Vec<PyObjectRef> = Vec::new(); let mut reraised: Vec<PyObjectRef> = Vec::new(); @@ -2509,8 +2566,8 @@ pub fn prep_reraise_star(orig: PyObjectRef, excs: PyObjectRef, vm: &VirtualMachi if vm.is_none(&exc) { continue; } - // Check if this exception was in the original exception group - if !vm.is_none(&orig) && is_same_exception_metadata(&exc, &orig, vm) { + // Check if this exception came from the original group + if is_exception_from_orig(&exc, &orig, vm) { reraised.push(exc); } else { raised.push(exc); @@ -2522,37 +2579,134 @@ pub fn prep_reraise_star(orig: PyObjectRef, excs: PyObjectRef, vm: &VirtualMachi return Ok(vm.ctx.none()); } - // Combine raised and reraised exceptions - let mut all_excs = raised; - all_excs.extend(reraised); + // Project reraised exceptions onto original structure to preserve nesting + let reraised_eg = exception_group_projection(&orig, &reraised, vm)?; + + // If no new raised exceptions, just return the reraised projection + if raised.is_empty() { + return Ok(reraised_eg); + } + + // Combine raised with reraised_eg + if !vm.is_none(&reraised_eg) { + raised.push(reraised_eg); + } - if all_excs.len() == 1 { - // If only one exception, just return it - return Ok(all_excs.into_iter().next().unwrap()); + // If only one exception, return it directly + if raised.len() == 1 { + return Ok(raised.into_iter().next().unwrap()); } - // Create new ExceptionGroup - let excs_tuple = vm.ctx.new_tuple(all_excs); + // Create new ExceptionGroup for multiple exceptions + let excs_tuple = vm.ctx.new_tuple(raised); let eg_type: PyObjectRef = crate::exception_group::exception_group().to_owned().into(); eg_type.call((vm.ctx.new_str(""), excs_tuple), vm) } -/// Check if two exceptions have the same metadata (for reraise detection) -fn is_same_exception_metadata(exc1: &PyObjectRef, exc2: &PyObjectRef, vm: &VirtualMachine) -> bool { - // Check if exc1 is part of exc2's exception group - if exc2.fast_isinstance(vm.ctx.exceptions.base_exception_group) { - let exc_class: PyObjectRef = exc1.class().to_owned().into(); - if let Ok(result) = vm.call_method(exc2, "subgroup", (exc_class,)) - && !vm.is_none(&result) - && let Ok(subgroup_excs) = result.get_attr("exceptions", vm) - && let Ok(tuple) = subgroup_excs.downcast::<PyTuple>() - { - for e in tuple.iter() { - if e.is(exc1) { - return true; - } - } +/// Check if an exception came from the original group (for reraise detection). +/// Instead of comparing metadata (which can be modified when caught), we compare +/// leaf exception object IDs. split() preserves leaf exception identity. +fn is_exception_from_orig(exc: &PyObjectRef, orig: &PyObjectRef, vm: &VirtualMachine) -> bool { + // Collect leaf exception IDs from exc + let mut exc_leaf_ids = HashSet::new(); + collect_exception_group_leaf_ids(exc, &mut exc_leaf_ids, vm); + + if exc_leaf_ids.is_empty() { + return false; + } + + // Collect leaf exception IDs from orig + let mut orig_leaf_ids = HashSet::new(); + collect_exception_group_leaf_ids(orig, &mut orig_leaf_ids, vm); + + // If ALL of exc's leaves are in orig's leaves, it's a reraise + exc_leaf_ids.iter().all(|id| orig_leaf_ids.contains(id)) +} + +/// Collect all leaf exception IDs from an exception (group). +fn collect_exception_group_leaf_ids( + exc: &PyObjectRef, + leaf_ids: &mut HashSet<usize>, + vm: &VirtualMachine, +) { + if vm.is_none(exc) { + return; + } + + // If not an exception group, it's a leaf - add its ID + if !exc.fast_isinstance(vm.ctx.exceptions.base_exception_group) { + leaf_ids.insert(exc.get_id()); + return; + } + + // Recurse into exception group's exceptions + if let Ok(excs_attr) = exc.get_attr("exceptions", vm) + && let Ok(tuple) = excs_attr.downcast::<PyTuple>() + { + for e in tuple.iter() { + collect_exception_group_leaf_ids(e, leaf_ids, vm); + } + } +} + +/// Project orig onto keep list, preserving nested structure. +/// Returns an exception group containing only the exceptions from orig +/// that are also in the keep list. +fn exception_group_projection( + orig: &PyObjectRef, + keep: &[PyObjectRef], + vm: &VirtualMachine, +) -> PyResult { + if keep.is_empty() { + return Ok(vm.ctx.none()); + } + + // Collect all leaf IDs from keep list + let mut leaf_ids = HashSet::new(); + for e in keep { + collect_exception_group_leaf_ids(e, &mut leaf_ids, vm); + } + + // Split orig by matching leaf IDs, preserving structure + split_by_leaf_ids(orig, &leaf_ids, vm) +} + +/// Recursively split an exception (group) by leaf IDs. +/// Returns the projection containing only matching leaves with preserved structure. +fn split_by_leaf_ids( + exc: &PyObjectRef, + leaf_ids: &HashSet<usize>, + vm: &VirtualMachine, +) -> PyResult { + if vm.is_none(exc) { + return Ok(vm.ctx.none()); + } + + // If not an exception group, check if it's in our set + if !exc.fast_isinstance(vm.ctx.exceptions.base_exception_group) { + if leaf_ids.contains(&exc.get_id()) { + return Ok(exc.clone()); } + return Ok(vm.ctx.none()); } - exc1.is(exc2) + + // Exception group - recurse and reconstruct + let excs_attr = exc.get_attr("exceptions", vm)?; + let tuple: PyTupleRef = excs_attr.try_into_value(vm)?; + + let mut matched = Vec::new(); + for e in tuple.iter() { + let m = split_by_leaf_ids(e, leaf_ids, vm)?; + if !vm.is_none(&m) { + matched.push(m); + } + } + + if matched.is_empty() { + return Ok(vm.ctx.none()); + } + + // Reconstruct using derive() to preserve the structure (not necessarily the subclass type) + let matched_tuple = vm.ctx.new_tuple(matched); + vm.call_method(exc, "derive", (matched_tuple,)) } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 6cb41f41d93..3f470e5453f 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1,8 +1,8 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ - PyBaseExceptionRef, PyCode, PyCoroutine, PyDict, PyDictRef, PyGenerator, PyList, PySet, - PySlice, PyStr, PyStrInterned, PyStrRef, PyTraceback, PyType, + PyBaseException, PyBaseExceptionRef, PyCode, PyCoroutine, PyDict, PyDictRef, PyGenerator, + PyList, PySet, PySlice, PyStr, PyStrInterned, PyStrRef, PyTraceback, PyType, asyncgenerator::PyAsyncGenWrappedValue, function::{PyCell, PyCellRef, PyFunction}, tuple::{PyTuple, PyTupleRef}, @@ -361,10 +361,6 @@ impl ExecutingFrame<'_> { let mut arg_state = bytecode::OpArgState::default(); loop { let idx = self.lasti() as usize; - // eprintln!( - // "location: {:?} {}", - // self.code.locations[idx], self.code.source_path - // ); self.update_lasti(|i| *i += 1); let bytecode::CodeUnit { op, arg } = instructions[idx]; let arg = arg_state.extend(arg); @@ -1406,6 +1402,15 @@ impl ExecutingFrame<'_> { set.add(item, vm)?; Ok(None) } + bytecode::Instruction::SetExcInfo => { + // Set the current exception to TOS (for except* handlers) + // This updates sys.exc_info() so bare 'raise' will reraise the matched exception + let exc = self.top_value(); + if let Some(exc) = exc.downcast_ref::<PyBaseException>() { + vm.set_exception(Some(exc.to_owned())); + } + Ok(None) + } bytecode::Instruction::SetFunctionAttribute { attr } => { self.execute_set_function_attribute(vm, attr.get(arg)) } From 9b2ad34a084a95d55105e32708e039bff11f7ccc Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 00:06:29 +0900 Subject: [PATCH 599/819] check surrogates (#6547) --- Lib/test/test_builtin.py | 4 ---- crates/vm/src/builtins/str.rs | 4 ++-- crates/vm/src/builtins/type.rs | 29 +++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index a5306b71d89..b957f79b87c 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -2399,8 +2399,6 @@ def test_type_nokwargs(self): with self.assertRaises(TypeError): type('a', (), dict={}) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_type_name(self): for name in 'A', '\xc4', '\U0001f40d', 'B.A', '42', '': with self.subTest(name=name): @@ -2450,8 +2448,6 @@ def test_type_qualname(self): A.__qualname__ = b'B' self.assertEqual(A.__qualname__, 'D.E') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_type_doc(self): for doc in 'x', '\xc4', '\U0001f40d', 'x\x00y', b'x', 42, None: A = type('A', (), {'__doc__': doc}) diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index df65fd5a4ed..fa35c1725d4 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -441,7 +441,7 @@ impl PyStr { self.data.as_str() } - fn ensure_valid_utf8(&self, vm: &VirtualMachine) -> PyResult<()> { + pub(crate) fn ensure_valid_utf8(&self, vm: &VirtualMachine) -> PyResult<()> { if self.is_utf8() { Ok(()) } else { @@ -1336,7 +1336,7 @@ impl PyStr { } #[pymethod] - fn isidentifier(&self) -> bool { + pub fn isidentifier(&self) -> bool { let Some(s) = self.to_str() else { return false }; let mut chars = s.chars(); let is_identifier_start = chars.next().is_some_and(|c| c == '_' || is_xid_start(c)); diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index bc567cd0978..de34678ae45 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1163,6 +1163,7 @@ impl PyType { if name.as_bytes().contains(&0) { return Err(vm.new_value_error("type name must not contain null characters")); } + name.ensure_valid_utf8(vm)?; // Use std::mem::replace to swap the new value in and get the old value out, // then drop the old value after releasing the lock (similar to CPython's Py_SETREF) @@ -1254,6 +1255,7 @@ impl Constructor for PyType { if name.as_bytes().contains(&0) { return Err(vm.new_value_error("type name must not contain null characters")); } + name.ensure_valid_utf8(vm)?; let (metatype, base, bases, base_is_type) = if bases.is_empty() { let base = vm.ctx.types.object_type.to_owned(); @@ -1306,6 +1308,13 @@ impl Constructor for PyType { }); let mut attributes = dict.to_attributes(vm); + // Check __doc__ for surrogates - raises UnicodeEncodeError during type creation + if let Some(doc) = attributes.get(identifier!(vm, __doc__)) + && let Some(doc_str) = doc.downcast_ref::<PyStr>() + { + doc_str.ensure_valid_utf8(vm)?; + } + if let Some(f) = attributes.get_mut(identifier!(vm, __init_subclass__)) && f.class().is(vm.ctx.types.function_type) { @@ -1340,6 +1349,13 @@ impl Constructor for PyType { let (heaptype_slots, add_dict): (Option<PyRef<PyTuple<PyStrRef>>>, bool) = if let Some(x) = attributes.get(identifier!(vm, __slots__)) { + // Check if __slots__ is bytes - not allowed + if x.class().is(vm.ctx.types.bytes_type) { + return Err(vm.new_type_error( + "__slots__ items must be strings, not 'bytes'".to_owned(), + )); + } + let slots = if x.class().is(vm.ctx.types.str_type) { let x = unsafe { x.downcast_unchecked_ref::<PyStr>() }; PyTuple::new_ref_typed(vec![x.to_owned()], &vm.ctx) @@ -1348,6 +1364,12 @@ impl Constructor for PyType { let elements = { let mut elements = Vec::new(); while let PyIterReturn::Return(element) = iter.next(vm)? { + // Check if any slot item is bytes + if element.class().is(vm.ctx.types.bytes_type) { + return Err(vm.new_type_error( + "__slots__ items must be strings, not 'bytes'".to_owned(), + )); + } elements.push(element); } elements @@ -1356,6 +1378,13 @@ impl Constructor for PyType { tuple.try_into_typed(vm)? }; + // Validate that all slots are valid identifiers + for slot in slots.iter() { + if !slot.isidentifier() { + return Err(vm.new_type_error("__slots__ must be identifiers".to_owned())); + } + } + // Check if __dict__ is in slots let dict_name = "__dict__"; let has_dict = slots.iter().any(|s| s.as_str() == dict_name); From ce00640b22bdfbc1cb90af8ef1efa610b295c44c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 09:08:03 +0900 Subject: [PATCH 600/819] fix input to check pty child (#6553) * fix input to check pty child * Auto-format: cargo fmt --all --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- crates/vm/src/py_io.rs | 9 ++++++--- crates/vm/src/stdlib/builtins.rs | 28 ++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/crates/vm/src/py_io.rs b/crates/vm/src/py_io.rs index 6b82bbd4788..a8063673a70 100644 --- a/crates/vm/src/py_io.rs +++ b/crates/vm/src/py_io.rs @@ -70,11 +70,14 @@ pub fn file_readline(obj: &PyObject, size: Option<usize>, vm: &VirtualMachine) - }; let ret = match_class!(match ret { s @ PyStr => { - let s_val = s.as_str(); - if s_val.is_empty() { + // Use as_wtf8() to handle strings with surrogates (e.g., surrogateescape) + let s_wtf8 = s.as_wtf8(); + if s_wtf8.is_empty() { return Err(eof_err()); } - if let Some(no_nl) = s_val.strip_suffix('\n') { + // '\n' is ASCII, so we can check bytes directly + if s_wtf8.as_bytes().last() == Some(&b'\n') { + let no_nl = &s_wtf8[..s_wtf8.len() - 1]; vm.ctx.new_str(no_nl).into() } else { s.into() diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 19f85af55da..7cd91f8b4b7 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -7,8 +7,6 @@ pub use builtins::{ascii, print, reversed}; #[pymodule] mod builtins { - use std::io::IsTerminal; - use crate::{ AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ @@ -464,6 +462,8 @@ mod builtins { #[pyfunction] fn input(prompt: OptionalArg<PyStrRef>, vm: &VirtualMachine) -> PyResult { + use std::io::IsTerminal; + let stdin = sys::get_stdin(vm)?; let stdout = sys::get_stdout(vm)?; let stderr = sys::get_stderr(vm)?; @@ -476,8 +476,13 @@ mod builtins { .is_ok_and(|fd| fd == expected) }; - // everything is normal, we can just rely on rustyline to use stdin/stdout - if fd_matches(&stdin, 0) && fd_matches(&stdout, 1) && std::io::stdin().is_terminal() { + // Check if we should use rustyline (interactive terminal, not PTY child) + let use_rustyline = fd_matches(&stdin, 0) + && fd_matches(&stdout, 1) + && std::io::stdin().is_terminal() + && !is_pty_child(); + + if use_rustyline { let prompt = prompt.as_ref().map_or("", |s| s.as_str()); let mut readline = Readline::new(()); match readline.readline(prompt) { @@ -502,6 +507,21 @@ mod builtins { } } + /// Check if we're running in a PTY child process (e.g., after pty.fork()). + /// pty.fork() calls setsid(), making the child a session leader. + /// In this case, rustyline may hang because it uses raw mode. + #[cfg(unix)] + fn is_pty_child() -> bool { + use nix::unistd::{getpid, getsid}; + // If this process is a session leader, we're likely in a PTY child + getsid(None) == Ok(getpid()) + } + + #[cfg(not(unix))] + fn is_pty_child() -> bool { + false + } + #[pyfunction] fn isinstance(obj: PyObjectRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { obj.is_instance(&typ, vm) From fd6c54876624b738b2ed18985457c3e68fc2711e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 09:08:16 +0900 Subject: [PATCH 601/819] no redundant venvlauncher targets (#6554) --- crates/venvlauncher/Cargo.toml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/crates/venvlauncher/Cargo.toml b/crates/venvlauncher/Cargo.toml index d59a3a0269f..ac3ea106b7a 100644 --- a/crates/venvlauncher/Cargo.toml +++ b/crates/venvlauncher/Cargo.toml @@ -8,23 +8,11 @@ rust-version.workspace = true repository.workspace = true license.workspace = true -[[bin]] -name = "venvlauncher" -path = "src/main.rs" - -[[bin]] -name = "venvwlauncher" -path = "src/main.rs" - # Free-threaded variants (RustPython uses Py_GIL_DISABLED=true) [[bin]] name = "venvlaunchert" path = "src/main.rs" -[[bin]] -name = "venvwlaunchert" -path = "src/main.rs" - [target.'cfg(windows)'.dependencies] windows-sys = { workspace = true, features = [ "Win32_Foundation", From 012799f5600e29643133fc6e08a23fc2640d4d21 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 10:13:03 +0900 Subject: [PATCH 602/819] Fix enum and os.read related to signal (#6552) * fix enum * fix os.read to check signals --- Lib/test/test_descr.py | 1 - Lib/test/test_file_eintr.py | 46 ++++++++++++++++++++++++++++------ Lib/test/test_signal.py | 6 ----- Lib/test/test_socket.py | 8 ------ Lib/test/test_ssl.py | 6 ----- crates/vm/src/builtins/type.rs | 8 +++++- crates/vm/src/stdlib/os.rs | 19 ++++++++++---- 7 files changed, 60 insertions(+), 34 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 88d120a427f..4e04371f626 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4961,7 +4961,6 @@ class OverrideBoth(OverrideNew, OverrideInit): self.assertRaises(TypeError, case, 1, 2, 3) self.assertRaises(TypeError, case, 1, 2, foo=3) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_subclassing_does_not_duplicate_dict_descriptors(self): class Base: pass diff --git a/Lib/test/test_file_eintr.py b/Lib/test/test_file_eintr.py index 55cc31dc59f..13260b8e498 100644 --- a/Lib/test/test_file_eintr.py +++ b/Lib/test/test_file_eintr.py @@ -152,7 +152,6 @@ def _test_reading(self, data_to_write, read_and_verify_code): '"got data %r\\nexpected %r" % (got, expected))' ) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_readline(self): """readline() must handle signals and not lose data.""" self._test_reading( @@ -161,7 +160,6 @@ def test_readline(self): read_method_name='readline', expected=b'hello, world!\n')) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_readlines(self): """readlines() must handle signals and not lose data.""" self._test_reading( @@ -170,7 +168,6 @@ def test_readlines(self): read_method_name='readlines', expected=[b'hello\n', b'world!\n'])) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_readall(self): """readall() must handle signals and not lose data.""" self._test_reading( @@ -189,6 +186,19 @@ def test_readall(self): class CTestFileIOSignalInterrupt(TestFileIOSignalInterrupt, unittest.TestCase): modname = '_io' + # TODO: RUSTPYTHON - _io module uses _pyio internally, signal handling differs + @unittest.expectedFailure + def test_readline(self): + super().test_readline() + + @unittest.expectedFailure + def test_readlines(self): + super().test_readlines() + + @unittest.expectedFailure + def test_readall(self): + super().test_readall() + class PyTestFileIOSignalInterrupt(TestFileIOSignalInterrupt, unittest.TestCase): modname = '_pyio' @@ -200,7 +210,6 @@ def _generate_infile_setup_code(self): 'assert isinstance(infile, io.BufferedReader)' % self.modname) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_readall(self): """BufferedReader.read() must handle signals and not lose data.""" self._test_reading( @@ -212,6 +221,19 @@ def test_readall(self): class CTestBufferedIOSignalInterrupt(TestBufferedIOSignalInterrupt, unittest.TestCase): modname = '_io' + # TODO: RUSTPYTHON - _io module uses _pyio internally, signal handling differs + @unittest.expectedFailure + def test_readline(self): + super().test_readline() + + @unittest.expectedFailure + def test_readlines(self): + super().test_readlines() + + @unittest.expectedFailure + def test_readall(self): + super().test_readall() + class PyTestBufferedIOSignalInterrupt(TestBufferedIOSignalInterrupt, unittest.TestCase): modname = '_pyio' @@ -224,7 +246,6 @@ def _generate_infile_setup_code(self): 'assert isinstance(infile, io.TextIOWrapper)' % self.modname) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_readline(self): """readline() must handle signals and not lose data.""" self._test_reading( @@ -233,7 +254,6 @@ def test_readline(self): read_method_name='readline', expected='hello, world!\n')) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_readlines(self): """readlines() must handle signals and not lose data.""" self._test_reading( @@ -242,7 +262,6 @@ def test_readlines(self): read_method_name='readlines', expected=['hello\n', 'world!\n'])) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_readall(self): """read() must handle signals and not lose data.""" self._test_reading( @@ -254,6 +273,19 @@ def test_readall(self): class CTestTextIOSignalInterrupt(TestTextIOSignalInterrupt, unittest.TestCase): modname = '_io' + # TODO: RUSTPYTHON - _io module uses _pyio internally, signal handling differs + @unittest.expectedFailure + def test_readline(self): + super().test_readline() + + @unittest.expectedFailure + def test_readlines(self): + super().test_readlines() + + @unittest.expectedFailure + def test_readall(self): + super().test_readall() + class PyTestTextIOSignalInterrupt(TestTextIOSignalInterrupt, unittest.TestCase): modname = '_pyio' diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 34ef13adf33..58b3e55d321 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -24,8 +24,6 @@ class GenericTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_enums(self): for name in dir(signal): sig = getattr(signal, name) @@ -763,8 +761,6 @@ def handler(signum, frame): % (exitcode, stdout)) return (exitcode == 3) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_without_siginterrupt(self): # If a signal handler is installed and siginterrupt is not called # at all, when that signal arrives, it interrupts a syscall that's in @@ -772,8 +768,6 @@ def test_without_siginterrupt(self): interrupted = self.readpipe_interrupted(None) self.assertTrue(interrupted) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_siginterrupt_on(self): # If a signal handler is installed and siginterrupt is called with # a true value for the second argument, when that signal arrives, it diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 5ccbfa7ff83..d5a35a3253e 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -2052,8 +2052,6 @@ def test_socket_fileno_requires_socket_fd(self): fileno=afile.fileno()) self.assertEqual(cm.exception.errno, errno.ENOTSOCK) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_addressfamily_enum(self): import _socket, enum CheckedAddressFamily = enum._old_convert_( @@ -2063,8 +2061,6 @@ def test_addressfamily_enum(self): ) enum._test_simple_enum(CheckedAddressFamily, socket.AddressFamily) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_socketkind_enum(self): import _socket, enum CheckedSocketKind = enum._old_convert_( @@ -2074,8 +2070,6 @@ def test_socketkind_enum(self): ) enum._test_simple_enum(CheckedSocketKind, socket.SocketKind) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_msgflag_enum(self): import _socket, enum CheckedMsgFlag = enum._old_convert_( @@ -2085,8 +2079,6 @@ def test_msgflag_enum(self): ) enum._test_simple_enum(CheckedMsgFlag, socket.MsgFlag) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_addressinfo_enum(self): import _socket, enum CheckedAddressInfo = enum._old_convert_( diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index f073def5bc1..09d35a77a1c 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -5477,7 +5477,6 @@ class Checked_TLSMessageType(enum.IntEnum): CHANGE_CIPHER_SPEC = 0x0101 enum._test_simple_enum(Checked_TLSMessageType, _TLSMessageType) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_sslmethod(self): Checked_SSLMethod = enum._old_convert_( enum.IntEnum, '_SSLMethod', 'ssl', @@ -5488,7 +5487,6 @@ def test_sslmethod(self): Checked_SSLMethod.PROTOCOL_SSLv23 = Checked_SSLMethod.PROTOCOL_TLS enum._test_simple_enum(Checked_SSLMethod, ssl._SSLMethod) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_options(self): CheckedOptions = enum._old_convert_( enum.IntFlag, 'Options', 'ssl', @@ -5497,7 +5495,6 @@ def test_options(self): ) enum._test_simple_enum(CheckedOptions, ssl.Options) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_alertdescription(self): CheckedAlertDescription = enum._old_convert_( enum.IntEnum, 'AlertDescription', 'ssl', @@ -5506,7 +5503,6 @@ def test_alertdescription(self): ) enum._test_simple_enum(CheckedAlertDescription, ssl.AlertDescription) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_sslerrornumber(self): Checked_SSLErrorNumber = enum._old_convert_( enum.IntEnum, 'SSLErrorNumber', 'ssl', @@ -5515,7 +5511,6 @@ def test_sslerrornumber(self): ) enum._test_simple_enum(Checked_SSLErrorNumber, ssl.SSLErrorNumber) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_verifyflags(self): CheckedVerifyFlags = enum._old_convert_( enum.IntFlag, 'VerifyFlags', 'ssl', @@ -5524,7 +5519,6 @@ def test_verifyflags(self): ) enum._test_simple_enum(CheckedVerifyFlags, ssl.VerifyFlags) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_verifymode(self): CheckedVerifyMode = enum._old_convert_( enum.IntEnum, 'VerifyMode', 'ssl', diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index de34678ae45..86d871fe30f 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1499,9 +1499,15 @@ impl Constructor for PyType { // Only add if: // 1. base is not type (type subclasses inherit __dict__ from type) // 2. the class has HAS_DICT flag (i.e., __slots__ was not defined or __dict__ is in __slots__) + // 3. no base class in MRO already provides __dict__ descriptor if !base_is_type && typ.slots.flags.has_feature(PyTypeFlags::HAS_DICT) { let __dict__ = identifier!(vm, __dict__); - if !typ.attributes.read().contains_key(&__dict__) { + let has_inherited_dict = typ + .mro + .read() + .iter() + .any(|base| base.attributes.read().contains_key(&__dict__)); + if !typ.attributes.read().contains_key(&__dict__) && !has_inherited_dict { unsafe { let descriptor = vm.ctx diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 87080ff8e0f..28d4aeb3755 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -274,12 +274,21 @@ pub(super) mod _os { } #[pyfunction] - fn read(fd: crt_fd::Borrowed<'_>, n: usize, vm: &VirtualMachine) -> io::Result<PyBytesRef> { + fn read(fd: crt_fd::Borrowed<'_>, n: usize, vm: &VirtualMachine) -> PyResult<PyBytesRef> { let mut buffer = vec![0u8; n]; - let n = crt_fd::read(fd, &mut buffer)?; - buffer.truncate(n); - - Ok(vm.ctx.new_bytes(buffer)) + loop { + match crt_fd::read(fd, &mut buffer) { + Ok(n) => { + buffer.truncate(n); + return Ok(vm.ctx.new_bytes(buffer)); + } + Err(e) if e.raw_os_error() == Some(libc::EINTR) => { + vm.check_signals()?; + continue; + } + Err(e) => return Err(e.into_pyexception(vm)), + } + } } #[pyfunction] From 7edc3bba5d275e28184e77ad1bf0f2a4da7473a0 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 10:27:58 +0900 Subject: [PATCH 603/819] tp_itemsize (#6544) --- Lib/test/test_builtin.py | 2 - crates/derive-impl/src/pyclass.rs | 20 +++- crates/vm/src/builtins/bytes.rs | 1 + crates/vm/src/builtins/int.rs | 1 + crates/vm/src/builtins/tuple.rs | 11 +- crates/vm/src/builtins/type.rs | 192 ++++++++++++++++++++++-------- crates/vm/src/class.rs | 2 + crates/vm/src/types/slot.rs | 2 +- 8 files changed, 164 insertions(+), 67 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index b957f79b87c..7d8c7a5e016 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -2481,8 +2481,6 @@ def test_bad_args(self): with self.assertRaises(TypeError): type('A', (int, str), {}) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_bad_slots(self): with self.assertRaises(TypeError): type('A', (), {'__slots__': b'x'}) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 1a01721391e..82057d40f9e 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -165,6 +165,7 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul with_impl, with_method_defs, with_slots, + itemsize, } = extract_impl_attrs(attr, &impl_ty)?; let payload_ty = attr_payload.unwrap_or(payload_guess); let method_def = &context.method_items; @@ -189,9 +190,17 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul #(#class_extensions)* } }, - parse_quote! { - fn __extend_slots(slots: &mut ::rustpython_vm::types::PyTypeSlots) { - #slots_impl + { + let itemsize_impl = itemsize.as_ref().map(|size| { + quote! { + slots.itemsize = #size; + } + }); + parse_quote! { + fn __extend_slots(slots: &mut ::rustpython_vm::types::PyTypeSlots) { + #itemsize_impl + #slots_impl + } } }, ]; @@ -1618,6 +1627,7 @@ struct ExtractedImplAttrs { with_impl: TokenStream, with_method_defs: Vec<TokenStream>, with_slots: TokenStream, + itemsize: Option<syn::Expr>, } fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<ExtractedImplAttrs> { @@ -1636,6 +1646,7 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac } }]; let mut payload = None; + let mut itemsize = None; for attr in attr { match attr { @@ -1721,6 +1732,8 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac } else { bail_span!(value, "payload must be a string literal") } + } else if path.is_ident("itemsize") { + itemsize = Some(value); } else { bail_span!(path, "Unknown pyimpl attribute") } @@ -1741,6 +1754,7 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac with_slots: quote! { #(#with_slots)* }, + itemsize, }) } diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index f782c035f86..0c67cd7bf24 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -187,6 +187,7 @@ impl PyRef<PyBytes> { } #[pyclass( + itemsize = 1, flags(BASETYPE, _MATCH_SELF), with( Py, diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 8fe85267cd0..46a0ff4774d 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -320,6 +320,7 @@ impl PyInt { } #[pyclass( + itemsize = 4, flags(BASETYPE, _MATCH_SELF), with(PyRef, Comparable, Hashable, Constructor, AsNumber, Representable) )] diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index adc5b483de3..13335428b35 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -257,16 +257,9 @@ impl<T> PyTuple<PyRef<T>> { } #[pyclass( + itemsize = std::mem::size_of::<crate::PyObjectRef>(), flags(BASETYPE, SEQUENCE, _MATCH_SELF), - with( - AsMapping, - AsSequence, - Hashable, - Comparable, - Iterable, - Constructor, - Representable - ) + with(AsMapping, AsSequence, Hashable, Comparable, Iterable, Constructor, Representable) )] impl PyTuple { #[pymethod] diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 86d871fe30f..ee07c0db0b9 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -948,6 +948,11 @@ impl PyType { self.slots.basicsize } + #[pygetset] + fn __itemsize__(&self) -> usize { + self.slots.itemsize + } + #[pygetset] pub fn __name__(&self, vm: &VirtualMachine) -> PyStrRef { self.name_inner( @@ -1347,65 +1352,144 @@ impl Constructor for PyType { attributes.insert(identifier!(vm, __hash__), vm.ctx.none.clone().into()); } - let (heaptype_slots, add_dict): (Option<PyRef<PyTuple<PyStrRef>>>, bool) = - if let Some(x) = attributes.get(identifier!(vm, __slots__)) { - // Check if __slots__ is bytes - not allowed - if x.class().is(vm.ctx.types.bytes_type) { - return Err(vm.new_type_error( - "__slots__ items must be strings, not 'bytes'".to_owned(), - )); - } + let (heaptype_slots, add_dict): (Option<PyRef<PyTuple<PyStrRef>>>, bool) = if let Some(x) = + attributes.get(identifier!(vm, __slots__)) + { + // Check if __slots__ is bytes - not allowed + if x.class().is(vm.ctx.types.bytes_type) { + return Err( + vm.new_type_error("__slots__ items must be strings, not 'bytes'".to_owned()) + ); + } - let slots = if x.class().is(vm.ctx.types.str_type) { - let x = unsafe { x.downcast_unchecked_ref::<PyStr>() }; - PyTuple::new_ref_typed(vec![x.to_owned()], &vm.ctx) - } else { - let iter = x.get_iter(vm)?; - let elements = { - let mut elements = Vec::new(); - while let PyIterReturn::Return(element) = iter.next(vm)? { - // Check if any slot item is bytes - if element.class().is(vm.ctx.types.bytes_type) { - return Err(vm.new_type_error( - "__slots__ items must be strings, not 'bytes'".to_owned(), - )); - } - elements.push(element); + let slots = if x.class().is(vm.ctx.types.str_type) { + let x = unsafe { x.downcast_unchecked_ref::<PyStr>() }; + PyTuple::new_ref_typed(vec![x.to_owned()], &vm.ctx) + } else { + let iter = x.get_iter(vm)?; + let elements = { + let mut elements = Vec::new(); + while let PyIterReturn::Return(element) = iter.next(vm)? { + // Check if any slot item is bytes + if element.class().is(vm.ctx.types.bytes_type) { + return Err(vm.new_type_error( + "__slots__ items must be strings, not 'bytes'".to_owned(), + )); } - elements - }; - let tuple = elements.into_pytuple(vm); - tuple.try_into_typed(vm)? + elements.push(element); + } + elements }; + let tuple = elements.into_pytuple(vm); + tuple.try_into_typed(vm)? + }; + + // Check if base has itemsize > 0 - can't add arbitrary slots to variable-size types + // Types like int, bytes, tuple have itemsize > 0 and don't allow custom slots + // But types like weakref.ref have itemsize = 0 and DO allow slots + let has_custom_slots = slots + .iter() + .any(|s| s.as_str() != "__dict__" && s.as_str() != "__weakref__"); + if has_custom_slots && base.slots.itemsize > 0 { + return Err(vm.new_type_error(format!( + "nonempty __slots__ not supported for subtype of '{}'", + base.name() + ))); + } - // Validate that all slots are valid identifiers - for slot in slots.iter() { - if !slot.isidentifier() { - return Err(vm.new_type_error("__slots__ must be identifiers".to_owned())); + // Validate slot names and track duplicates + let mut seen_dict = false; + let mut seen_weakref = false; + for slot in slots.iter() { + // Use isidentifier for validation (handles Unicode properly) + if !slot.isidentifier() { + return Err(vm.new_type_error("__slots__ must be identifiers".to_owned())); + } + + let slot_name = slot.as_str(); + + // Check for duplicate __dict__ + if slot_name == "__dict__" { + if seen_dict { + return Err(vm.new_type_error( + "__dict__ slot disallowed: we already got one".to_owned(), + )); } + seen_dict = true; } - // Check if __dict__ is in slots - let dict_name = "__dict__"; - let has_dict = slots.iter().any(|s| s.as_str() == dict_name); - - // Filter out __dict__ from slots - let filtered_slots = if has_dict { - let filtered: Vec<PyStrRef> = slots - .iter() - .filter(|s| s.as_str() != dict_name) - .cloned() - .collect(); - PyTuple::new_ref_typed(filtered, &vm.ctx) + // Check for duplicate __weakref__ + if slot_name == "__weakref__" { + if seen_weakref { + return Err(vm.new_type_error( + "__weakref__ slot disallowed: we already got one".to_owned(), + )); + } + seen_weakref = true; + } + + // Check if slot name conflicts with class attributes + if attributes.contains_key(vm.ctx.intern_str(slot_name)) { + return Err(vm.new_value_error(format!( + "'{}' in __slots__ conflicts with a class variable", + slot_name + ))); + } + } + + // Check if base class already has __dict__ - can't redefine it + if seen_dict && base.slots.flags.has_feature(PyTypeFlags::HAS_DICT) { + return Err( + vm.new_type_error("__dict__ slot disallowed: we already got one".to_owned()) + ); + } + + // Check if base class already has __weakref__ - can't redefine it + // A base has weakref support if: + // 1. It's a heap type without explicit __slots__ (automatic weakref), OR + // 2. It's a heap type with __weakref__ in its __slots__ + if seen_weakref { + let base_has_weakref = if let Some(ref ext) = base.heaptype_ext { + match &ext.slots { + // Heap type without __slots__ - has automatic weakref + None => true, + // Heap type with __slots__ - check if __weakref__ is in slots + Some(base_slots) => base_slots.iter().any(|s| s.as_str() == "__weakref__"), + } } else { - slots + // Builtin type - check if it has __weakref__ descriptor + let weakref_name = vm.ctx.intern_str("__weakref__"); + base.attributes.read().contains_key(weakref_name) }; - (Some(filtered_slots), has_dict) + if base_has_weakref { + return Err(vm.new_type_error( + "__weakref__ slot disallowed: we already got one".to_owned(), + )); + } + } + + // Check if __dict__ is in slots + let dict_name = "__dict__"; + let has_dict = slots.iter().any(|s| s.as_str() == dict_name); + + // Filter out __dict__ from slots + let filtered_slots = if has_dict { + let filtered: Vec<PyStrRef> = slots + .iter() + .filter(|s| s.as_str() != dict_name) + .cloned() + .collect(); + PyTuple::new_ref_typed(filtered, &vm.ctx) } else { - (None, false) + slots }; + (Some(filtered_slots), has_dict) + } else { + (None, false) + }; + // FIXME: this is a temporary fix. multi bases with multiple slots will break object let base_member_count = bases .iter() @@ -2094,12 +2178,16 @@ fn solid_base<'a>(typ: &'a Py<PyType>, vm: &VirtualMachine) -> &'a Py<PyType> { vm.ctx.types.object_type }; - // TODO: requires itemsize comparison too - if typ.__basicsize__() != base.__basicsize__() { - typ - } else { - base - } + // Check for extra instance variables (CPython's extra_ivars) + let t_size = typ.__basicsize__(); + let b_size = base.__basicsize__(); + let t_itemsize = typ.slots.itemsize; + let b_itemsize = base.slots.itemsize; + + // Has extra ivars if: sizes differ AND (has items OR t_size > b_size) + let has_extra_ivars = t_size != b_size && (t_itemsize > 0 || b_itemsize > 0 || t_size > b_size); + + if has_extra_ivars { typ } else { base } } fn best_base<'a>(bases: &'a [PyTypeRef], vm: &VirtualMachine) -> PyResult<&'a Py<PyType>> { diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index da860a96289..577fd7d6844 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -70,6 +70,7 @@ pub trait PyClassDef { const TP_NAME: &'static str; const DOC: Option<&'static str> = None; const BASICSIZE: usize; + const ITEMSIZE: usize = 0; const UNHASHABLE: bool = false; // due to restriction of rust trait system, object.__base__ is None @@ -210,6 +211,7 @@ pub trait PyClassImpl: PyClassDef { flags: Self::TP_FLAGS, name: Self::TP_NAME, basicsize: Self::BASICSIZE, + itemsize: Self::ITEMSIZE, doc: Self::DOC, methods: Self::METHOD_DEFS, ..Default::default() diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 13a43ed9c95..0d6b17ae99d 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -128,7 +128,7 @@ pub struct PyTypeSlots { pub(crate) name: &'static str, // tp_name with <module>.<class> for print, not class name pub basicsize: usize, - // tp_itemsize + pub itemsize: usize, // tp_itemsize // Methods to implement standard operations From 527111bc983d7323e1d60ad5e60101ab746c9b08 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 11:44:36 +0900 Subject: [PATCH 604/819] PyWrapperDescrObject and rewrite toggle_slot (#6536) * SlotFunc * slotdef * unify slots * remove unsed slots --- .cspell.dict/cpython.txt | 2 + Lib/test/test_descr.py | 1 - Lib/test/test_inspect/test_inspect.py | 4 - Lib/test/test_sqlite3/test_factory.py | 2 - .../test_unittest/testmock/testhelpers.py | 2 - crates/vm/src/builtins/bool.rs | 14 +- crates/vm/src/builtins/complex.rs | 135 +- crates/vm/src/builtins/descriptor.rs | 220 ++- crates/vm/src/builtins/float.rs | 189 +-- crates/vm/src/builtins/int.rs | 153 +- crates/vm/src/builtins/type.rs | 172 +- crates/vm/src/class.rs | 91 +- crates/vm/src/protocol/mod.rs | 2 +- crates/vm/src/protocol/number.rs | 113 +- crates/vm/src/types/mod.rs | 2 + crates/vm/src/types/slot.rs | 1084 +++++++----- crates/vm/src/types/slot_defs.rs | 1465 +++++++++++++++++ crates/vm/src/types/zoo.rs | 2 +- 18 files changed, 2531 insertions(+), 1122 deletions(-) create mode 100644 crates/vm/src/types/slot_defs.rs diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 441e8af5b8f..cb6f774ed18 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -1,6 +1,7 @@ argtypes asdl asname +attro augassign badcert badsyntax @@ -50,6 +51,7 @@ prec preinitialized pythonw PYTHREAD_NAME +releasebuffer SA_ONSTACK SOABI stackdepth diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 4e04371f626..b3d973237df 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -1399,7 +1399,6 @@ class Q2: __qualname__ = object() __slots__ = ["__qualname__"] - @unittest.expectedFailure # TODO: RUSTPYTHON def test_slots_descriptor(self): # Issue2115: slot descriptors did not correctly check # the type of the given object diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 353d7c2e7b2..8f87fa8571a 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -2126,8 +2126,6 @@ class DataDescriptorSub(DataDescriptorWithNoGet, self.assertFalse(inspect.ismethoddescriptor(MethodDescriptorSub)) self.assertFalse(inspect.ismethoddescriptor(DataDescriptorSub)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_builtin_descriptors(self): builtin_slot_wrapper = int.__add__ # This one is mentioned in docs. class Owner: @@ -2217,8 +2215,6 @@ class DataDescriptor2: self.assertTrue(inspect.isdatadescriptor(DataDescriptor2()), 'class with __set__ = None is a data descriptor') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_slot(self): class Slotted: __slots__ = 'foo', diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py index e52c10fe944..c13a7481520 100644 --- a/Lib/test/test_sqlite3/test_factory.py +++ b/Lib/test/test_sqlite3/test_factory.py @@ -241,8 +241,6 @@ def test_sqlite_row_hash_cmp(self): self.assertEqual(hash(row_1), hash(row_2)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_sqlite_row_as_sequence(self): """ Checks if the row object can act like a sequence """ self.con.row_factory = sqlite.Row diff --git a/Lib/test/test_unittest/testmock/testhelpers.py b/Lib/test/test_unittest/testmock/testhelpers.py index 5facac685fd..c83068beb15 100644 --- a/Lib/test/test_unittest/testmock/testhelpers.py +++ b/Lib/test/test_unittest/testmock/testhelpers.py @@ -884,8 +884,6 @@ def f(a, self): pass a.f.assert_called_with(self=10) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_autospec_data_descriptor(self): class Descriptor(object): def __init__(self, value): diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 8fee4af3834..cfd1f136d14 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -126,10 +126,10 @@ impl PyBool { .and_then(|format_spec| format_spec.format_bool(new_bool)) .map_err(|err| err.into_pyexception(vm)) } +} - #[pymethod(name = "__ror__")] - #[pymethod] - fn __or__(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { +impl PyBool { + pub(crate) fn __or__(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if lhs.fast_isinstance(vm.ctx.types.bool_type) && rhs.fast_isinstance(vm.ctx.types.bool_type) { @@ -143,9 +143,7 @@ impl PyBool { } } - #[pymethod(name = "__rand__")] - #[pymethod] - fn __and__(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + pub(crate) fn __and__(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if lhs.fast_isinstance(vm.ctx.types.bool_type) && rhs.fast_isinstance(vm.ctx.types.bool_type) { @@ -159,9 +157,7 @@ impl PyBool { } } - #[pymethod(name = "__rxor__")] - #[pymethod] - fn __xor__(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + pub(crate) fn __xor__(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if lhs.fast_isinstance(vm.ctx.types.bool_type) && rhs.fast_isinstance(vm.ctx.types.bool_type) { diff --git a/crates/vm/src/builtins/complex.rs b/crates/vm/src/builtins/complex.rs index 8bf46cfdd5c..ba74d5e0367 100644 --- a/crates/vm/src/builtins/complex.rs +++ b/crates/vm/src/builtins/complex.rs @@ -5,11 +5,7 @@ use crate::{ class::PyClassImpl, common::format::FormatSpec, convert::{IntoPyException, ToPyObject, ToPyResult}, - function::{ - FuncArgs, OptionalArg, OptionalOption, - PyArithmeticValue::{self, *}, - PyComparisonValue, - }, + function::{FuncArgs, OptionalArg, PyComparisonValue}, protocol::PyNumberMethods, stdlib::warnings, types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp, Representable}, @@ -268,133 +264,11 @@ impl PyComplex { self.value.im } - #[pymethod] - fn __abs__(&self, vm: &VirtualMachine) -> PyResult<f64> { - let Complex64 { im, re } = self.value; - let is_finite = im.is_finite() && re.is_finite(); - let abs_result = re.hypot(im); - if is_finite && abs_result.is_infinite() { - Err(vm.new_overflow_error("absolute value too large")) - } else { - Ok(abs_result) - } - } - - #[inline] - fn op<F>( - &self, - other: PyObjectRef, - op: F, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<Complex64>> - where - F: Fn(Complex64, Complex64) -> PyResult<Complex64>, - { - to_op_complex(&other, vm)?.map_or_else( - || Ok(NotImplemented), - |other| Ok(Implemented(op(self.value, other)?)), - ) - } - - #[pymethod(name = "__radd__")] - #[pymethod] - fn __add__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<Complex64>> { - self.op(other, |a, b| Ok(a + b), vm) - } - - #[pymethod] - fn __sub__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<Complex64>> { - self.op(other, |a, b| Ok(a - b), vm) - } - - #[pymethod] - fn __rsub__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<Complex64>> { - self.op(other, |a, b| Ok(b - a), vm) - } - #[pymethod] fn conjugate(&self) -> Complex64 { self.value.conj() } - #[pymethod(name = "__rmul__")] - #[pymethod] - fn __mul__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<Complex64>> { - self.op(other, |a, b| Ok(a * b), vm) - } - - #[pymethod] - fn __truediv__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<Complex64>> { - self.op(other, |a, b| inner_div(a, b, vm), vm) - } - - #[pymethod] - fn __rtruediv__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<Complex64>> { - self.op(other, |a, b| inner_div(b, a, vm), vm) - } - - #[pymethod] - const fn __pos__(&self) -> Complex64 { - self.value - } - - #[pymethod] - fn __neg__(&self) -> Complex64 { - -self.value - } - - #[pymethod] - fn __pow__( - &self, - other: PyObjectRef, - mod_val: OptionalOption<PyObjectRef>, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<Complex64>> { - if mod_val.flatten().is_some() { - Err(vm.new_value_error("complex modulo not allowed")) - } else { - self.op(other, |a, b| inner_pow(a, b, vm), vm) - } - } - - #[pymethod] - fn __rpow__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<Complex64>> { - self.op(other, |a, b| inner_pow(b, a, vm), vm) - } - - #[pymethod] - fn __bool__(&self) -> bool { - !Complex64::is_zero(&self.value) - } - #[pymethod] const fn __getnewargs__(&self) -> (f64, f64) { let Complex64 { re, im } = self.value; @@ -489,7 +363,12 @@ impl AsNumber for PyComplex { }), absolute: Some(|number, vm| { let value = PyComplex::number_downcast(number).value; - value.norm().to_pyresult(vm) + let result = value.norm(); + // Check for overflow: hypot returns inf for finite inputs that overflow + if result.is_infinite() && value.re.is_finite() && value.im.is_finite() { + return Err(vm.new_overflow_error("absolute value too large".to_owned())); + } + result.to_pyresult(vm) }), boolean: Some(|number, _vm| Ok(!PyComplex::number_downcast(number).value.is_zero())), true_divide: Some(|a, b, vm| PyComplex::number_op(a, b, inner_div, vm)), diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index 297199eee18..802b81f6d79 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -4,11 +4,15 @@ use crate::{ builtins::{PyTypeRef, builtin_func::PyNativeMethod, type_}, class::PyClassImpl, common::hash::PyHash, - convert::ToPyResult, + convert::{ToPyObject, ToPyResult}, function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue}, + protocol::{PyNumberBinaryFunc, PyNumberTernaryFunc, PyNumberUnaryFunc}, types::{ - Callable, Comparable, GetDescriptor, HashFunc, Hashable, InitFunc, IterFunc, IterNextFunc, - PyComparisonOp, Representable, StringifyFunc, + Callable, Comparable, DelFunc, DescrGetFunc, DescrSetFunc, GenericMethod, GetDescriptor, + GetattroFunc, HashFunc, Hashable, InitFunc, IterFunc, IterNextFunc, MapAssSubscriptFunc, + MapLenFunc, MapSubscriptFunc, PyComparisonOp, Representable, RichCompareFunc, + SeqAssItemFunc, SeqConcatFunc, SeqContainsFunc, SeqItemFunc, SeqLenFunc, SeqRepeatFunc, + SetattroFunc, StringifyFunc, }, }; use rustpython_common::lock::PyRwLock; @@ -387,22 +391,58 @@ impl GetDescriptor for PyMemberDescriptor { pub fn init(ctx: &Context) { PyMemberDescriptor::extend_class(ctx, ctx.types.member_descriptor_type); PyMethodDescriptor::extend_class(ctx, ctx.types.method_descriptor_type); - PySlotWrapper::extend_class(ctx, ctx.types.wrapper_descriptor_type); + PyWrapper::extend_class(ctx, ctx.types.wrapper_descriptor_type); PyMethodWrapper::extend_class(ctx, ctx.types.method_wrapper_type); } -// PySlotWrapper - wrapper_descriptor +// PyWrapper - wrapper_descriptor -/// Type-erased slot function - mirrors CPython's void* d_wrapped /// Each variant knows how to call the wrapped function with proper types #[derive(Clone, Copy)] pub enum SlotFunc { + // Basic slots Init(InitFunc), Hash(HashFunc), Str(StringifyFunc), Repr(StringifyFunc), Iter(IterFunc), IterNext(IterNextFunc), + Call(GenericMethod), + Del(DelFunc), + + // Attribute access slots + GetAttro(GetattroFunc), + SetAttro(SetattroFunc), // __setattr__ + DelAttro(SetattroFunc), // __delattr__ (same func type, different PySetterValue) + + // Rich comparison slots (with comparison op) + RichCompare(RichCompareFunc, PyComparisonOp), + + // Descriptor slots + DescrGet(DescrGetFunc), + DescrSet(DescrSetFunc), // __set__ + DescrDel(DescrSetFunc), // __delete__ (same func type, different PySetterValue) + + // Sequence sub-slots (sq_*) + SeqLength(SeqLenFunc), + SeqConcat(SeqConcatFunc), + SeqRepeat(SeqRepeatFunc), + SeqItem(SeqItemFunc), + SeqAssItem(SeqAssItemFunc), + SeqContains(SeqContainsFunc), + + // Mapping sub-slots (mp_*) + MapLength(MapLenFunc), + MapSubscript(MapSubscriptFunc), + MapAssSubscript(MapAssSubscriptFunc), + + // Number sub-slots (nb_*) - grouped by signature + NumBoolean(PyNumberUnaryFunc<bool>), // __bool__ + NumUnary(PyNumberUnaryFunc), // __int__, __float__, __index__ + NumBinary(PyNumberBinaryFunc), // __add__, __sub__, __mul__, etc. + NumBinaryRight(PyNumberBinaryFunc), // __radd__, __rsub__, etc. (swapped args) + NumTernary(PyNumberTernaryFunc), // __pow__ + NumTernaryRight(PyNumberTernaryFunc), // __rpow__ (swapped first two args) } impl std::fmt::Debug for SlotFunc { @@ -414,6 +454,33 @@ impl std::fmt::Debug for SlotFunc { SlotFunc::Repr(_) => write!(f, "SlotFunc::Repr(...)"), SlotFunc::Iter(_) => write!(f, "SlotFunc::Iter(...)"), SlotFunc::IterNext(_) => write!(f, "SlotFunc::IterNext(...)"), + SlotFunc::Call(_) => write!(f, "SlotFunc::Call(...)"), + SlotFunc::Del(_) => write!(f, "SlotFunc::Del(...)"), + SlotFunc::GetAttro(_) => write!(f, "SlotFunc::GetAttro(...)"), + SlotFunc::SetAttro(_) => write!(f, "SlotFunc::SetAttro(...)"), + SlotFunc::DelAttro(_) => write!(f, "SlotFunc::DelAttro(...)"), + SlotFunc::RichCompare(_, op) => write!(f, "SlotFunc::RichCompare(..., {:?})", op), + SlotFunc::DescrGet(_) => write!(f, "SlotFunc::DescrGet(...)"), + SlotFunc::DescrSet(_) => write!(f, "SlotFunc::DescrSet(...)"), + SlotFunc::DescrDel(_) => write!(f, "SlotFunc::DescrDel(...)"), + // Sequence sub-slots + SlotFunc::SeqLength(_) => write!(f, "SlotFunc::SeqLength(...)"), + SlotFunc::SeqConcat(_) => write!(f, "SlotFunc::SeqConcat(...)"), + SlotFunc::SeqRepeat(_) => write!(f, "SlotFunc::SeqRepeat(...)"), + SlotFunc::SeqItem(_) => write!(f, "SlotFunc::SeqItem(...)"), + SlotFunc::SeqAssItem(_) => write!(f, "SlotFunc::SeqAssItem(...)"), + SlotFunc::SeqContains(_) => write!(f, "SlotFunc::SeqContains(...)"), + // Mapping sub-slots + SlotFunc::MapLength(_) => write!(f, "SlotFunc::MapLength(...)"), + SlotFunc::MapSubscript(_) => write!(f, "SlotFunc::MapSubscript(...)"), + SlotFunc::MapAssSubscript(_) => write!(f, "SlotFunc::MapAssSubscript(...)"), + // Number sub-slots + SlotFunc::NumBoolean(_) => write!(f, "SlotFunc::NumBoolean(...)"), + SlotFunc::NumUnary(_) => write!(f, "SlotFunc::NumUnary(...)"), + SlotFunc::NumBinary(_) => write!(f, "SlotFunc::NumBinary(...)"), + SlotFunc::NumBinaryRight(_) => write!(f, "SlotFunc::NumBinaryRight(...)"), + SlotFunc::NumTernary(_) => write!(f, "SlotFunc::NumTernary(...)"), + SlotFunc::NumTernaryRight(_) => write!(f, "SlotFunc::NumTernaryRight(...)"), } } } @@ -463,6 +530,133 @@ impl SlotFunc { } func(&obj, vm).to_pyresult(vm) } + SlotFunc::Call(func) => func(&obj, args, vm), + SlotFunc::Del(func) => { + if !args.args.is_empty() || !args.kwargs.is_empty() { + return Err( + vm.new_type_error("__del__() takes no arguments (1 given)".to_owned()) + ); + } + func(&obj, vm)?; + Ok(vm.ctx.none()) + } + SlotFunc::GetAttro(func) => { + let (name,): (PyRef<PyStr>,) = args.bind(vm)?; + func(&obj, &name, vm) + } + SlotFunc::SetAttro(func) => { + let (name, value): (PyRef<PyStr>, PyObjectRef) = args.bind(vm)?; + func(&obj, &name, PySetterValue::Assign(value), vm)?; + Ok(vm.ctx.none()) + } + SlotFunc::DelAttro(func) => { + let (name,): (PyRef<PyStr>,) = args.bind(vm)?; + func(&obj, &name, PySetterValue::Delete, vm)?; + Ok(vm.ctx.none()) + } + SlotFunc::RichCompare(func, op) => { + let (other,): (PyObjectRef,) = args.bind(vm)?; + func(&obj, &other, *op, vm).map(|r| match r { + crate::function::Either::A(obj) => obj, + crate::function::Either::B(cmp_val) => cmp_val.to_pyobject(vm), + }) + } + SlotFunc::DescrGet(func) => { + let (instance, owner): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) = + args.bind(vm)?; + let owner = owner.into_option(); + let instance_opt = if vm.is_none(&instance) { + None + } else { + Some(instance) + }; + func(obj, instance_opt, owner, vm) + } + SlotFunc::DescrSet(func) => { + let (instance, value): (PyObjectRef, PyObjectRef) = args.bind(vm)?; + func(&obj, instance, PySetterValue::Assign(value), vm)?; + Ok(vm.ctx.none()) + } + SlotFunc::DescrDel(func) => { + let (instance,): (PyObjectRef,) = args.bind(vm)?; + func(&obj, instance, PySetterValue::Delete, vm)?; + Ok(vm.ctx.none()) + } + // Sequence sub-slots + SlotFunc::SeqLength(func) => { + args.bind::<()>(vm)?; + let len = func(obj.sequence_unchecked(), vm)?; + Ok(vm.ctx.new_int(len).into()) + } + SlotFunc::SeqConcat(func) => { + let (other,): (PyObjectRef,) = args.bind(vm)?; + func(obj.sequence_unchecked(), &other, vm) + } + SlotFunc::SeqRepeat(func) => { + let (n,): (isize,) = args.bind(vm)?; + func(obj.sequence_unchecked(), n, vm) + } + SlotFunc::SeqItem(func) => { + let (index,): (isize,) = args.bind(vm)?; + func(obj.sequence_unchecked(), index, vm) + } + SlotFunc::SeqAssItem(func) => { + let (index, value): (isize, crate::function::OptionalArg<PyObjectRef>) = + args.bind(vm)?; + func(obj.sequence_unchecked(), index, value.into_option(), vm)?; + Ok(vm.ctx.none()) + } + SlotFunc::SeqContains(func) => { + let (item,): (PyObjectRef,) = args.bind(vm)?; + let result = func(obj.sequence_unchecked(), &item, vm)?; + Ok(vm.ctx.new_bool(result).into()) + } + // Mapping sub-slots + SlotFunc::MapLength(func) => { + args.bind::<()>(vm)?; + let len = func(obj.mapping_unchecked(), vm)?; + Ok(vm.ctx.new_int(len).into()) + } + SlotFunc::MapSubscript(func) => { + let (key,): (PyObjectRef,) = args.bind(vm)?; + func(obj.mapping_unchecked(), &key, vm) + } + SlotFunc::MapAssSubscript(func) => { + let (key, value): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) = + args.bind(vm)?; + func(obj.mapping_unchecked(), &key, value.into_option(), vm)?; + Ok(vm.ctx.none()) + } + // Number sub-slots + SlotFunc::NumBoolean(func) => { + args.bind::<()>(vm)?; + let result = func(obj.number(), vm)?; + Ok(vm.ctx.new_bool(result).into()) + } + SlotFunc::NumUnary(func) => { + args.bind::<()>(vm)?; + func(obj.number(), vm) + } + SlotFunc::NumBinary(func) => { + let (other,): (PyObjectRef,) = args.bind(vm)?; + func(&obj, &other, vm) + } + SlotFunc::NumBinaryRight(func) => { + let (other,): (PyObjectRef,) = args.bind(vm)?; + func(&other, &obj, vm) // Swapped: other op obj + } + SlotFunc::NumTernary(func) => { + let (y, z): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) = + args.bind(vm)?; + let z = z.unwrap_or_else(|| vm.ctx.none()); + func(&obj, &y, &z, vm) + } + SlotFunc::NumTernaryRight(func) => { + let (y, z): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) = + args.bind(vm)?; + let z = z.unwrap_or_else(|| vm.ctx.none()); + func(&y, &obj, &z, vm) // Swapped: y ** obj % z + } } } } @@ -471,20 +665,20 @@ impl SlotFunc { // = PyWrapperDescrObject #[pyclass(name = "wrapper_descriptor", module = false)] #[derive(Debug)] -pub struct PySlotWrapper { +pub struct PyWrapper { pub typ: &'static Py<PyType>, pub name: &'static PyStrInterned, pub wrapped: SlotFunc, pub doc: Option<&'static str>, } -impl PyPayload for PySlotWrapper { +impl PyPayload for PyWrapper { fn class(ctx: &Context) -> &'static Py<PyType> { ctx.types.wrapper_descriptor_type } } -impl GetDescriptor for PySlotWrapper { +impl GetDescriptor for PyWrapper { fn descr_get( zelf: PyObjectRef, obj: Option<PyObjectRef>, @@ -501,7 +695,7 @@ impl GetDescriptor for PySlotWrapper { } } -impl Callable for PySlotWrapper { +impl Callable for PyWrapper { type Args = FuncArgs; fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult { @@ -525,7 +719,7 @@ impl Callable for PySlotWrapper { with(GetDescriptor, Callable, Representable), flags(DISALLOW_INSTANTIATION) )] -impl PySlotWrapper { +impl PyWrapper { #[pygetset] fn __name__(&self) -> &'static PyStrInterned { self.name @@ -547,7 +741,7 @@ impl PySlotWrapper { } } -impl Representable for PySlotWrapper { +impl Representable for PyWrapper { #[inline] fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> { Ok(format!( @@ -565,7 +759,7 @@ impl Representable for PySlotWrapper { #[pyclass(name = "method-wrapper", module = false, traverse)] #[derive(Debug)] pub struct PyMethodWrapper { - pub wrapper: PyRef<PySlotWrapper>, + pub wrapper: PyRef<PyWrapper>, #[pytraverse(skip)] pub obj: PyObjectRef, } diff --git a/crates/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs index 26182d748a9..f101a3aa8e3 100644 --- a/crates/vm/src/builtins/float.rs +++ b/crates/vm/src/builtins/float.rs @@ -8,8 +8,7 @@ use crate::{ common::{float_ops, format::FormatSpec, hash}, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{ - ArgBytesLike, FuncArgs, OptionalArg, OptionalOption, - PyArithmeticValue::{self, *}, + ArgBytesLike, FuncArgs, OptionalArg, OptionalOption, PyArithmeticValue::*, PyComparisonValue, }, protocol::PyNumberMethods, @@ -239,182 +238,6 @@ impl PyFloat { .to_owned()) } - #[pymethod] - const fn __abs__(&self) -> f64 { - self.value.abs() - } - - #[inline] - fn simple_op<F>( - &self, - other: PyObjectRef, - op: F, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<f64>> - where - F: Fn(f64, f64) -> PyResult<f64>, - { - to_op_float(&other, vm)?.map_or_else( - || Ok(NotImplemented), - |other| Ok(Implemented(op(self.value, other)?)), - ) - } - - #[inline] - fn complex_op<F>(&self, other: PyObjectRef, op: F, vm: &VirtualMachine) -> PyResult - where - F: Fn(f64, f64) -> PyResult, - { - to_op_float(&other, vm)?.map_or_else( - || Ok(vm.ctx.not_implemented()), - |other| op(self.value, other), - ) - } - - #[inline] - fn tuple_op<F>( - &self, - other: PyObjectRef, - op: F, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<(f64, f64)>> - where - F: Fn(f64, f64) -> PyResult<(f64, f64)>, - { - to_op_float(&other, vm)?.map_or_else( - || Ok(NotImplemented), - |other| Ok(Implemented(op(self.value, other)?)), - ) - } - - #[pymethod(name = "__radd__")] - #[pymethod] - fn __add__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyArithmeticValue<f64>> { - self.simple_op(other, |a, b| Ok(a + b), vm) - } - - #[pymethod] - const fn __bool__(&self) -> bool { - self.value != 0.0 - } - - #[pymethod] - fn __divmod__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<(f64, f64)>> { - self.tuple_op(other, |a, b| inner_divmod(a, b, vm), vm) - } - - #[pymethod] - fn __rdivmod__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<(f64, f64)>> { - self.tuple_op(other, |a, b| inner_divmod(b, a, vm), vm) - } - - #[pymethod] - fn __floordiv__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<f64>> { - self.simple_op(other, |a, b| inner_floordiv(a, b, vm), vm) - } - - #[pymethod] - fn __rfloordiv__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<f64>> { - self.simple_op(other, |a, b| inner_floordiv(b, a, vm), vm) - } - - #[pymethod(name = "__mod__")] - fn mod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyArithmeticValue<f64>> { - self.simple_op(other, |a, b| inner_mod(a, b, vm), vm) - } - - #[pymethod] - fn __rmod__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<f64>> { - self.simple_op(other, |a, b| inner_mod(b, a, vm), vm) - } - - #[pymethod] - const fn __pos__(&self) -> f64 { - self.value - } - - #[pymethod] - const fn __neg__(&self) -> f64 { - -self.value - } - - #[pymethod] - fn __pow__( - &self, - other: PyObjectRef, - mod_val: OptionalOption<PyObjectRef>, - vm: &VirtualMachine, - ) -> PyResult { - if mod_val.flatten().is_some() { - Err(vm.new_type_error("floating point pow() does not accept a 3rd argument")) - } else { - self.complex_op(other, |a, b| float_pow(a, b, vm), vm) - } - } - - #[pymethod] - fn __rpow__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.complex_op(other, |a, b| float_pow(b, a, vm), vm) - } - - #[pymethod] - fn __sub__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyArithmeticValue<f64>> { - self.simple_op(other, |a, b| Ok(a - b), vm) - } - - #[pymethod] - fn __rsub__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<f64>> { - self.simple_op(other, |a, b| Ok(b - a), vm) - } - - #[pymethod] - fn __truediv__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<f64>> { - self.simple_op(other, |a, b| inner_div(a, b, vm), vm) - } - - #[pymethod] - fn __rtruediv__( - &self, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyArithmeticValue<f64>> { - self.simple_op(other, |a, b| inner_div(b, a, vm), vm) - } - - #[pymethod(name = "__rmul__")] - #[pymethod] - fn __mul__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyArithmeticValue<f64>> { - self.simple_op(other, |a, b| Ok(a * b), vm) - } - #[pymethod] fn __trunc__(&self, vm: &VirtualMachine) -> PyResult<BigInt> { try_to_bigint(self.value, vm) @@ -460,16 +283,6 @@ impl PyFloat { Ok(value) } - #[pymethod] - fn __int__(&self, vm: &VirtualMachine) -> PyResult<BigInt> { - self.__trunc__(vm) - } - - #[pymethod] - const fn __float__(zelf: PyRef<Self>) -> PyRef<Self> { - zelf - } - #[pygetset] const fn real(zelf: PyRef<Self>) -> PyRef<Self> { zelf diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 46a0ff4774d..37b41e085ad 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -12,8 +12,7 @@ use crate::{ }, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{ - ArgByteOrder, ArgIntoBool, FuncArgs, OptionalArg, OptionalOption, PyArithmeticValue, - PyComparisonValue, + ArgByteOrder, ArgIntoBool, FuncArgs, OptionalArg, PyArithmeticValue, PyComparisonValue, }, protocol::{PyNumberMethods, handle_bytes_to_int_err}, types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp, Representable}, @@ -325,83 +324,15 @@ impl PyInt { with(PyRef, Comparable, Hashable, Constructor, AsNumber, Representable) )] impl PyInt { - #[pymethod(name = "__radd__")] - #[pymethod] - fn __add__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> { - self.int_op(other, |a, b| a + b) - } - - #[pymethod] - fn __sub__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> { - self.int_op(other, |a, b| a - b) - } - - #[pymethod] - fn __rsub__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> { - self.int_op(other, |a, b| b - a) - } - - #[pymethod(name = "__rmul__")] - #[pymethod] - fn __mul__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> { - self.int_op(other, |a, b| a * b) - } - - #[pymethod] - fn __truediv__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_truediv(a, b, vm), vm) - } - - #[pymethod] - fn __rtruediv__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_truediv(b, a, vm), vm) - } - - #[pymethod] - fn __floordiv__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_floordiv(a, b, vm), vm) - } - - #[pymethod] - fn __rfloordiv__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_floordiv(b, a, vm), vm) - } - - #[pymethod] - fn __lshift__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_lshift(a, b, vm), vm) - } - - #[pymethod] - fn __rlshift__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_lshift(b, a, vm), vm) - } - - #[pymethod] - fn __rshift__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_rshift(a, b, vm), vm) - } - - #[pymethod] - fn __rrshift__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_rshift(b, a, vm), vm) - } - - #[pymethod(name = "__rxor__")] - #[pymethod] - pub fn __xor__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> { + pub(crate) fn __xor__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> { self.int_op(other, |a, b| a ^ b) } - #[pymethod(name = "__ror__")] - #[pymethod] - pub fn __or__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> { + pub(crate) fn __or__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> { self.int_op(other, |a, b| a | b) } - #[pymethod(name = "__rand__")] - #[pymethod] - pub fn __and__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> { + pub(crate) fn __and__(&self, other: PyObjectRef) -> PyArithmeticValue<BigInt> { self.int_op(other, |a, b| a & b) } @@ -447,54 +378,6 @@ impl PyInt { ) } - #[pymethod] - fn __pow__( - &self, - other: PyObjectRef, - r#mod: OptionalOption<PyObjectRef>, - vm: &VirtualMachine, - ) -> PyResult { - match r#mod.flatten() { - Some(modulus) => self.modpow(other, modulus, vm), - None => self.general_op(other, |a, b| inner_pow(a, b, vm), vm), - } - } - - #[pymethod] - fn __rpow__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_pow(b, a, vm), vm) - } - - #[pymethod(name = "__mod__")] - fn mod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_mod(a, b, vm), vm) - } - - #[pymethod] - fn __rmod__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_mod(b, a, vm), vm) - } - - #[pymethod] - fn __divmod__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_divmod(a, b, vm), vm) - } - - #[pymethod] - fn __rdivmod__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.general_op(other, |a, b| inner_divmod(b, a, vm), vm) - } - - #[pymethod] - fn __neg__(&self) -> BigInt { - -(&self.value) - } - - #[pymethod] - fn __abs__(&self) -> BigInt { - self.value.abs() - } - #[pymethod] fn __round__( zelf: PyRef<Self>, @@ -537,16 +420,6 @@ impl PyInt { Ok(zelf) } - #[pymethod] - fn __pos__(&self) -> BigInt { - self.value.clone() - } - - #[pymethod] - fn __float__(&self, vm: &VirtualMachine) -> PyResult<f64> { - try_to_float(&self.value, vm) - } - #[pymethod] fn __trunc__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyRefExact<Self> { zelf.__int__(vm) @@ -562,16 +435,6 @@ impl PyInt { zelf.__int__(vm) } - #[pymethod] - fn __index__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyRefExact<Self> { - zelf.__int__(vm) - } - - #[pymethod] - fn __invert__(&self) -> BigInt { - !(&self.value) - } - #[pymethod] fn __format__(&self, spec: PyStrRef, vm: &VirtualMachine) -> PyResult<String> { FormatSpec::parse(spec.as_str()) @@ -579,11 +442,6 @@ impl PyInt { .map_err(|err| err.into_pyexception(vm)) } - #[pymethod] - fn __bool__(&self) -> bool { - !self.value.is_zero() - } - #[pymethod] fn __sizeof__(&self) -> usize { std::mem::size_of::<Self>() + (((self.value.bits() + 7) & !7) / 8) as usize @@ -706,8 +564,7 @@ impl PyInt { #[pyclass] impl PyRef<PyInt> { - #[pymethod] - fn __int__(self, vm: &VirtualMachine) -> PyRefExact<PyInt> { + pub(crate) fn __int__(self, vm: &VirtualMachine) -> PyRefExact<PyInt> { self.into_exact_or(&vm.ctx, |zelf| unsafe { // TODO: this is actually safe. we need better interface PyRefExact::new_unchecked(vm.ctx.new_bigint(&zelf.value)) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index ee07c0db0b9..f74095a8c8a 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -26,7 +26,7 @@ use crate::{ protocol::{PyIterReturn, PyNumberMethods}, types::{ AsNumber, Callable, Constructor, GetAttr, Initializer, PyTypeFlags, PyTypeSlots, - Representable, SetAttr, TypeDataRef, TypeDataRefMut, TypeDataSlot, + Representable, SLOT_DEFS, SetAttr, TypeDataRef, TypeDataRefMut, TypeDataSlot, }, }; use indexmap::{IndexMap, map::Entry}; @@ -464,173 +464,11 @@ impl PyType { /// Inherit slots from base type. inherit_slots pub(crate) fn inherit_slots(&self, base: &Self) { - macro_rules! copyslot { - ($slot:ident) => { - if self.slots.$slot.load().is_none() { - if let Some(base_val) = base.slots.$slot.load() { - self.slots.$slot.store(Some(base_val)); - } - } - }; - } - - // Copy init slot only if base actually defines it (not just inherited) - // This is needed for multiple inheritance where a later base might - // have a more specific init slot - macro_rules! copyslot_defined { - ($slot:ident) => { - if self.slots.$slot.load().is_none() { - if let Some(base_val) = base.slots.$slot.load() { - // SLOTDEFINED: base->SLOT && (basebase == NULL || base->SLOT != basebase->SLOT) - let basebase = base.base.as_ref(); - let slot_defined = match basebase { - None => true, - Some(bb) => bb.slots.$slot.load().map(|v| v as usize) - != Some(base_val as usize), - }; - if slot_defined { - self.slots.$slot.store(Some(base_val)); - } - } - } - }; - } - - // Core slots - copyslot!(hash); - copyslot!(call); - copyslot!(str); - copyslot!(repr); - copyslot!(getattro); - copyslot!(setattro); - copyslot!(richcompare); - copyslot!(iter); - copyslot!(iternext); - copyslot!(descr_get); - copyslot!(descr_set); - // init uses SLOTDEFINED check for multiple inheritance support - copyslot_defined!(init); - copyslot!(del); - // new is handled by set_new() - // as_buffer is inherited at type creation time (not AtomicCell) - - // Sub-slots (number, sequence, mapping) - self.inherit_number_slots(base); - self.inherit_sequence_slots(base); - self.inherit_mapping_slots(base); - } - - /// Inherit number sub-slots from base type - fn inherit_number_slots(&self, base: &Self) { - macro_rules! copy_num_slot { - ($slot:ident) => { - if self.slots.as_number.$slot.load().is_none() { - if let Some(base_val) = base.slots.as_number.$slot.load() { - self.slots.as_number.$slot.store(Some(base_val)); - } - } - }; + // Use SLOT_DEFS to iterate all slots + // Note: as_buffer is handled in inherit_readonly_slots (not AtomicCell) + for def in SLOT_DEFS { + def.accessor.copyslot_if_none(self, base); } - - // Binary operations - copy_num_slot!(add); - copy_num_slot!(right_add); - copy_num_slot!(inplace_add); - copy_num_slot!(subtract); - copy_num_slot!(right_subtract); - copy_num_slot!(inplace_subtract); - copy_num_slot!(multiply); - copy_num_slot!(right_multiply); - copy_num_slot!(inplace_multiply); - copy_num_slot!(remainder); - copy_num_slot!(right_remainder); - copy_num_slot!(inplace_remainder); - copy_num_slot!(divmod); - copy_num_slot!(right_divmod); - copy_num_slot!(power); - copy_num_slot!(right_power); - copy_num_slot!(inplace_power); - - // Bitwise operations - copy_num_slot!(lshift); - copy_num_slot!(right_lshift); - copy_num_slot!(inplace_lshift); - copy_num_slot!(rshift); - copy_num_slot!(right_rshift); - copy_num_slot!(inplace_rshift); - copy_num_slot!(and); - copy_num_slot!(right_and); - copy_num_slot!(inplace_and); - copy_num_slot!(xor); - copy_num_slot!(right_xor); - copy_num_slot!(inplace_xor); - copy_num_slot!(or); - copy_num_slot!(right_or); - copy_num_slot!(inplace_or); - - // Division operations - copy_num_slot!(floor_divide); - copy_num_slot!(right_floor_divide); - copy_num_slot!(inplace_floor_divide); - copy_num_slot!(true_divide); - copy_num_slot!(right_true_divide); - copy_num_slot!(inplace_true_divide); - - // Matrix multiplication - copy_num_slot!(matrix_multiply); - copy_num_slot!(right_matrix_multiply); - copy_num_slot!(inplace_matrix_multiply); - - // Unary operations - copy_num_slot!(negative); - copy_num_slot!(positive); - copy_num_slot!(absolute); - copy_num_slot!(boolean); - copy_num_slot!(invert); - - // Conversion - copy_num_slot!(int); - copy_num_slot!(float); - copy_num_slot!(index); - } - - /// Inherit sequence sub-slots from base type - fn inherit_sequence_slots(&self, base: &Self) { - macro_rules! copy_seq_slot { - ($slot:ident) => { - if self.slots.as_sequence.$slot.load().is_none() { - if let Some(base_val) = base.slots.as_sequence.$slot.load() { - self.slots.as_sequence.$slot.store(Some(base_val)); - } - } - }; - } - - copy_seq_slot!(length); - copy_seq_slot!(concat); - copy_seq_slot!(repeat); - copy_seq_slot!(item); - copy_seq_slot!(ass_item); - copy_seq_slot!(contains); - copy_seq_slot!(inplace_concat); - copy_seq_slot!(inplace_repeat); - } - - /// Inherit mapping sub-slots from base type - fn inherit_mapping_slots(&self, base: &Self) { - macro_rules! copy_map_slot { - ($slot:ident) => { - if self.slots.as_mapping.$slot.load().is_none() { - if let Some(base_val) = base.slots.as_mapping.$slot.load() { - self.slots.as_mapping.$slot.store(Some(base_val)); - } - } - }; - } - - copy_map_slot!(length); - copy_map_slot!(subscript); - copy_map_slot!(ass_subscript); } // This is used for class initialization where the vm is not yet available. diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 577fd7d6844..ce41abcc60b 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -2,17 +2,60 @@ use crate::{ PyPayload, - builtins::{ - PyBaseObject, PyType, PyTypeRef, - descriptor::{PySlotWrapper, SlotFunc}, - }, + builtins::{PyBaseObject, PyType, PyTypeRef, descriptor::PyWrapper}, function::PyMethodDef, object::Py, - types::{PyTypeFlags, PyTypeSlots, hash_not_implemented}, + types::{PyTypeFlags, PyTypeSlots, SLOT_DEFS, hash_not_implemented}, vm::Context, }; use rustpython_common::static_cell; +/// Add slot wrapper descriptors to a type's dict +/// +/// Iterates SLOT_DEFS and creates a PyWrapper for each slot that: +/// 1. Has a function set in the type's slots +/// 2. Doesn't already have an attribute in the type's dict +pub fn add_operators(class: &'static Py<PyType>, ctx: &Context) { + for def in SLOT_DEFS.iter() { + // Skip __new__ - it has special handling + if def.name == "__new__" { + continue; + } + + // Special handling for __hash__ = None + if def.name == "__hash__" + && class + .slots + .hash + .load() + .is_some_and(|h| h as usize == hash_not_implemented as usize) + { + class.set_attr(ctx.names.__hash__, ctx.none.clone().into()); + continue; + } + + // Get the slot function wrapped in SlotFunc + let Some(slot_func) = def.accessor.get_slot_func_with_op(&class.slots, def.op) else { + continue; + }; + + // Check if attribute already exists in dict + let attr_name = ctx.intern_str(def.name); + if class.attributes.read().contains_key(attr_name) { + continue; + } + + // Create and add the wrapper + let wrapper = PyWrapper { + typ: class, + name: attr_name, + wrapped: slot_func, + doc: Some(def.doc), + }; + class.set_attr(attr_name, wrapper.into_ref(ctx).into()); + } +} + pub trait StaticType { // Ideally, saving PyType is better than PyTypeRef fn static_cell() -> &'static static_cell::StaticCell<PyTypeRef>; @@ -140,42 +183,8 @@ pub trait PyClassImpl: PyClassDef { } } - // Add slot wrappers for slots that exist and are not already in dict - // This mirrors CPython's add_operators() in typeobject.c - macro_rules! add_slot_wrapper { - ($slot:ident, $name:ident, $variant:ident, $doc:expr) => { - if let Some(func) = class.slots.$slot.load() { - let attr_name = identifier!(ctx, $name); - if !class.attributes.read().contains_key(attr_name) { - let wrapper = PySlotWrapper { - typ: class, - name: ctx.intern_str(stringify!($name)), - wrapped: SlotFunc::$variant(func), - doc: Some($doc), - }; - class.set_attr(attr_name, wrapper.into_ref(ctx).into()); - } - } - }; - } - - add_slot_wrapper!( - init, - __init__, - Init, - "Initialize self. See help(type(self)) for accurate signature." - ); - add_slot_wrapper!(repr, __repr__, Repr, "Return repr(self)."); - add_slot_wrapper!(str, __str__, Str, "Return str(self)."); - add_slot_wrapper!(iter, __iter__, Iter, "Implement iter(self)."); - add_slot_wrapper!(iternext, __next__, IterNext, "Implement next(self)."); - - // __hash__ needs special handling: hash_not_implemented sets __hash__ = None - if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize { - class.set_attr(ctx.names.__hash__, ctx.none.clone().into()); - } else { - add_slot_wrapper!(hash, __hash__, Hash, "Return hash(self)."); - } + // Add slot wrappers using SLOT_DEFS array + add_operators(class, ctx); // Inherit slots from base types after slots are fully initialized for base in class.bases.read().iter() { diff --git a/crates/vm/src/protocol/mod.rs b/crates/vm/src/protocol/mod.rs index e7be286c265..6eb2909f167 100644 --- a/crates/vm/src/protocol/mod.rs +++ b/crates/vm/src/protocol/mod.rs @@ -12,6 +12,6 @@ pub use iter::{PyIter, PyIterIter, PyIterReturn}; pub use mapping::{PyMapping, PyMappingMethods, PyMappingSlots}; pub use number::{ PyNumber, PyNumberBinaryFunc, PyNumberBinaryOp, PyNumberMethods, PyNumberSlots, - PyNumberTernaryOp, PyNumberUnaryFunc, handle_bytes_to_int_err, + PyNumberTernaryFunc, PyNumberTernaryOp, PyNumberUnaryFunc, handle_bytes_to_int_err, }; pub use sequence::{PySequence, PySequenceMethods, PySequenceSlots}; diff --git a/crates/vm/src/protocol/number.rs b/crates/vm/src/protocol/number.rs index 4f21a1e64f9..c208bf26de8 100644 --- a/crates/vm/src/protocol/number.rs +++ b/crates/vm/src/protocol/number.rs @@ -256,6 +256,7 @@ pub struct PyNumberSlots { pub int: AtomicCell<Option<PyNumberUnaryFunc>>, pub float: AtomicCell<Option<PyNumberUnaryFunc>>, + // Right variants (internal - not exposed in SlotAccessor) pub right_add: AtomicCell<Option<PyNumberBinaryFunc>>, pub right_subtract: AtomicCell<Option<PyNumberBinaryFunc>>, pub right_multiply: AtomicCell<Option<PyNumberBinaryFunc>>, @@ -295,8 +296,7 @@ pub struct PyNumberSlots { impl From<&PyNumberMethods> for PyNumberSlots { fn from(value: &PyNumberMethods) -> Self { - // right_* functions will use the same left function as PyNumberMethods - // allows both f(self, other) and f(other, self) + // right_* slots use the same function as left ops for native types Self { add: AtomicCell::new(value.add), subtract: AtomicCell::new(value.subtract), @@ -352,6 +352,115 @@ impl From<&PyNumberMethods> for PyNumberSlots { } impl PyNumberSlots { + /// Copy from static PyNumberMethods + pub fn copy_from(&self, methods: &PyNumberMethods) { + if let Some(f) = methods.add { + self.add.store(Some(f)); + } + if let Some(f) = methods.subtract { + self.subtract.store(Some(f)); + } + if let Some(f) = methods.multiply { + self.multiply.store(Some(f)); + } + if let Some(f) = methods.remainder { + self.remainder.store(Some(f)); + } + if let Some(f) = methods.divmod { + self.divmod.store(Some(f)); + } + if let Some(f) = methods.power { + self.power.store(Some(f)); + } + if let Some(f) = methods.negative { + self.negative.store(Some(f)); + } + if let Some(f) = methods.positive { + self.positive.store(Some(f)); + } + if let Some(f) = methods.absolute { + self.absolute.store(Some(f)); + } + if let Some(f) = methods.boolean { + self.boolean.store(Some(f)); + } + if let Some(f) = methods.invert { + self.invert.store(Some(f)); + } + if let Some(f) = methods.lshift { + self.lshift.store(Some(f)); + } + if let Some(f) = methods.rshift { + self.rshift.store(Some(f)); + } + if let Some(f) = methods.and { + self.and.store(Some(f)); + } + if let Some(f) = methods.xor { + self.xor.store(Some(f)); + } + if let Some(f) = methods.or { + self.or.store(Some(f)); + } + if let Some(f) = methods.int { + self.int.store(Some(f)); + } + if let Some(f) = methods.float { + self.float.store(Some(f)); + } + if let Some(f) = methods.inplace_add { + self.inplace_add.store(Some(f)); + } + if let Some(f) = methods.inplace_subtract { + self.inplace_subtract.store(Some(f)); + } + if let Some(f) = methods.inplace_multiply { + self.inplace_multiply.store(Some(f)); + } + if let Some(f) = methods.inplace_remainder { + self.inplace_remainder.store(Some(f)); + } + if let Some(f) = methods.inplace_power { + self.inplace_power.store(Some(f)); + } + if let Some(f) = methods.inplace_lshift { + self.inplace_lshift.store(Some(f)); + } + if let Some(f) = methods.inplace_rshift { + self.inplace_rshift.store(Some(f)); + } + if let Some(f) = methods.inplace_and { + self.inplace_and.store(Some(f)); + } + if let Some(f) = methods.inplace_xor { + self.inplace_xor.store(Some(f)); + } + if let Some(f) = methods.inplace_or { + self.inplace_or.store(Some(f)); + } + if let Some(f) = methods.floor_divide { + self.floor_divide.store(Some(f)); + } + if let Some(f) = methods.true_divide { + self.true_divide.store(Some(f)); + } + if let Some(f) = methods.inplace_floor_divide { + self.inplace_floor_divide.store(Some(f)); + } + if let Some(f) = methods.inplace_true_divide { + self.inplace_true_divide.store(Some(f)); + } + if let Some(f) = methods.index { + self.index.store(Some(f)); + } + if let Some(f) = methods.matrix_multiply { + self.matrix_multiply.store(Some(f)); + } + if let Some(f) = methods.inplace_matrix_multiply { + self.inplace_matrix_multiply.store(Some(f)); + } + } + pub fn left_binary_op(&self, op_slot: PyNumberBinaryOp) -> Option<PyNumberBinaryFunc> { use PyNumberBinaryOp::*; match op_slot { diff --git a/crates/vm/src/types/mod.rs b/crates/vm/src/types/mod.rs index f19328cdd2d..b17a737545f 100644 --- a/crates/vm/src/types/mod.rs +++ b/crates/vm/src/types/mod.rs @@ -1,7 +1,9 @@ mod slot; +pub mod slot_defs; mod structseq; mod zoo; pub use slot::*; +pub use slot_defs::{SLOT_DEFS, SlotAccessor, SlotDef}; pub use structseq::{PyStructSequence, PyStructSequenceData, struct_sequence_new}; pub(crate) use zoo::TypeZoo; diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 0d6b17ae99d..b7c20041d00 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -14,6 +14,7 @@ use crate::{ PyBuffer, PyIterReturn, PyMapping, PyMappingMethods, PyMappingSlots, PyNumber, PyNumberMethods, PyNumberSlots, PySequence, PySequenceMethods, PySequenceSlots, }, + types::slot_defs::{SlotAccessor, find_slot_defs_by_name}, vm::Context, }; use crossbeam_utils::atomic::AtomicCell; @@ -288,6 +289,21 @@ pub(crate) type NewFunc = fn(PyTypeRef, FuncArgs, &VirtualMachine) -> PyResult; pub(crate) type InitFunc = fn(PyObjectRef, FuncArgs, &VirtualMachine) -> PyResult<()>; pub(crate) type DelFunc = fn(&PyObject, &VirtualMachine) -> PyResult<()>; +// Sequence sub-slot function types +pub(crate) type SeqLenFunc = fn(PySequence<'_>, &VirtualMachine) -> PyResult<usize>; +pub(crate) type SeqConcatFunc = fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult; +pub(crate) type SeqRepeatFunc = fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult; +pub(crate) type SeqItemFunc = fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult; +pub(crate) type SeqAssItemFunc = + fn(PySequence<'_>, isize, Option<PyObjectRef>, &VirtualMachine) -> PyResult<()>; +pub(crate) type SeqContainsFunc = fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult<bool>; + +// Mapping sub-slot function types +pub(crate) type MapLenFunc = fn(PyMapping<'_>, &VirtualMachine) -> PyResult<usize>; +pub(crate) type MapSubscriptFunc = fn(PyMapping<'_>, &PyObject, &VirtualMachine) -> PyResult; +pub(crate) type MapAssSubscriptFunc = + fn(PyMapping<'_>, &PyObject, Option<PyObjectRef>, &VirtualMachine) -> PyResult<()>; + // slot_sq_length pub(crate) fn len_wrapper(obj: &PyObject, vm: &VirtualMachine) -> PyResult<usize> { let ret = vm.call_special_method(obj, identifier!(vm, __len__), ())?; @@ -331,6 +347,28 @@ macro_rules! number_binary_right_op_wrapper { |a, b, vm| vm.call_special_method(b, identifier!(vm, $name), (a.to_owned(),)) }; } +macro_rules! number_ternary_op_wrapper { + ($name:ident) => { + |a, b, c, vm: &VirtualMachine| { + if vm.is_none(c) { + vm.call_special_method(a, identifier!(vm, $name), (b.to_owned(),)) + } else { + vm.call_special_method(a, identifier!(vm, $name), (b.to_owned(), c.to_owned())) + } + } + }; +} +macro_rules! number_ternary_right_op_wrapper { + ($name:ident) => { + |a, b, c, vm: &VirtualMachine| { + if vm.is_none(c) { + vm.call_special_method(b, identifier!(vm, $name), (a.to_owned(),)) + } else { + vm.call_special_method(b, identifier!(vm, $name), (a.to_owned(), c.to_owned())) + } + } + }; +} fn getitem_wrapper<K: ToPyObject>(obj: &PyObject, needle: K, vm: &VirtualMachine) -> PyResult { vm.call_special_method(obj, identifier!(vm, __getitem__), (needle,)) } @@ -507,453 +545,717 @@ fn del_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> { } impl PyType { + /// Update slots based on dunder method changes + /// + /// Iterates SLOT_DEFS to find all slots matching the given name and updates them. pub(crate) fn update_slot<const ADD: bool>(&self, name: &'static PyStrInterned, ctx: &Context) { debug_assert!(name.as_str().starts_with("__")); debug_assert!(name.as_str().ends_with("__")); - macro_rules! toggle_slot { - ($name:ident, $func:expr) => {{ + // Find all slot_defs matching this name and update each + for def in find_slot_defs_by_name(name.as_str()) { + self.update_one_slot::<ADD>(&def.accessor, name, ctx); + } + } + + /// Update a single slot + fn update_one_slot<const ADD: bool>( + &self, + accessor: &SlotAccessor, + name: &'static PyStrInterned, + ctx: &Context, + ) { + use crate::builtins::descriptor::SlotFunc; + + // Helper macro for main slots + macro_rules! update_main_slot { + ($slot:ident, $wrapper:expr, $variant:ident) => {{ if ADD { - self.slots.$name.store(Some($func)); + if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| { + if let SlotFunc::$variant(f) = sf { + Some(*f) + } else { + None + } + }) { + self.slots.$slot.store(Some(func)); + } else { + self.slots.$slot.store(Some($wrapper)); + } } else { - // When deleting, re-inherit from MRO (skip self) - let inherited = self - .mro - .read() - .iter() - .skip(1) - .find_map(|cls| cls.slots.$name.load()); - self.slots.$name.store(inherited); + accessor.inherit_from_mro(self); } }}; } - macro_rules! toggle_sub_slot { - ($group:ident, $name:ident, $func:expr) => {{ + // Helper macro for number/sequence/mapping sub-slots + macro_rules! update_sub_slot { + ($group:ident, $slot:ident, $wrapper:expr, $variant:ident) => {{ if ADD { - self.slots.$group.$name.store(Some($func)); + if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| { + if let SlotFunc::$variant(f) = sf { + Some(*f) + } else { + None + } + }) { + self.slots.$group.$slot.store(Some(func)); + } else { + self.slots.$group.$slot.store(Some($wrapper)); + } } else { - // When deleting, re-inherit from MRO (skip self) - let inherited = self - .mro - .read() - .iter() - .skip(1) - .find_map(|cls| cls.slots.$group.$name.load()); - self.slots.$group.$name.store(inherited); + accessor.inherit_from_mro(self); } }}; } - macro_rules! update_slot { - ($name:ident, $func:expr) => {{ - self.slots.$name.store(Some($func)); - }}; - } - - match name { - _ if name == identifier!(ctx, __len__) => { - toggle_sub_slot!(as_sequence, length, |seq, vm| len_wrapper(seq.obj, vm)); - toggle_sub_slot!(as_mapping, length, |mapping, vm| len_wrapper( - mapping.obj, - vm - )); - } - _ if name == identifier!(ctx, __getitem__) => { - toggle_sub_slot!(as_sequence, item, |seq, i, vm| getitem_wrapper( - seq.obj, i, vm - )); - toggle_sub_slot!(as_mapping, subscript, |mapping, key, vm| { - getitem_wrapper(mapping.obj, key, vm) - }); - } - _ if name == identifier!(ctx, __setitem__) || name == identifier!(ctx, __delitem__) => { - toggle_sub_slot!(as_sequence, ass_item, |seq, i, value, vm| { - setitem_wrapper(seq.obj, i, value, vm) - }); - toggle_sub_slot!(as_mapping, ass_subscript, |mapping, key, value, vm| { - setitem_wrapper(mapping.obj, key, value, vm) - }); - } - _ if name == identifier!(ctx, __contains__) => { - toggle_sub_slot!(as_sequence, contains, |seq, needle, vm| { - contains_wrapper(seq.obj, needle, vm) - }); - } - _ if name == identifier!(ctx, __repr__) => { - update_slot!(repr, repr_wrapper); - } - _ if name == identifier!(ctx, __str__) => { - update_slot!(str, str_wrapper); - } - _ if name == identifier!(ctx, __hash__) => { - let is_unhashable = self - .attributes - .read() - .get(identifier!(ctx, __hash__)) - .is_some_and(|a| a.is(&ctx.none)); - let wrapper = if is_unhashable { - hash_not_implemented + match accessor { + // === Main slots === + SlotAccessor::TpRepr => update_main_slot!(repr, repr_wrapper, Repr), + SlotAccessor::TpStr => update_main_slot!(str, str_wrapper, Str), + SlotAccessor::TpHash => { + // Special handling for __hash__ = None + if ADD { + let method = self.attributes.read().get(name).cloned().or_else(|| { + self.mro + .read() + .iter() + .find_map(|cls| cls.attributes.read().get(name).cloned()) + }); + + if method.as_ref().is_some_and(|m| m.is(&ctx.none)) { + self.slots.hash.store(Some(hash_not_implemented)); + } else if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| { + if let SlotFunc::Hash(f) = sf { + Some(*f) + } else { + None + } + }) { + self.slots.hash.store(Some(func)); + } else { + self.slots.hash.store(Some(hash_wrapper)); + } } else { - hash_wrapper - }; - toggle_slot!(hash, wrapper); - } - _ if name == identifier!(ctx, __call__) => { - toggle_slot!(call, call_wrapper); - } - _ if name == identifier!(ctx, __getattr__) - || name == identifier!(ctx, __getattribute__) => - { - update_slot!(getattro, getattro_wrapper); - } - _ if name == identifier!(ctx, __setattr__) || name == identifier!(ctx, __delattr__) => { - update_slot!(setattro, setattro_wrapper); - } - _ if name == identifier!(ctx, __eq__) - || name == identifier!(ctx, __ne__) - || name == identifier!(ctx, __le__) - || name == identifier!(ctx, __lt__) - || name == identifier!(ctx, __ge__) - || name == identifier!(ctx, __gt__) => - { - update_slot!(richcompare, richcompare_wrapper); - } - _ if name == identifier!(ctx, __iter__) => { - toggle_slot!(iter, iter_wrapper); - } - _ if name == identifier!(ctx, __next__) => { - toggle_slot!(iternext, iternext_wrapper); - } - _ if name == identifier!(ctx, __get__) => { - toggle_slot!(descr_get, descr_get_wrapper); + accessor.inherit_from_mro(self); + } } - _ if name == identifier!(ctx, __set__) || name == identifier!(ctx, __delete__) => { - update_slot!(descr_set, descr_set_wrapper); + SlotAccessor::TpCall => update_main_slot!(call, call_wrapper, Call), + SlotAccessor::TpIter => update_main_slot!(iter, iter_wrapper, Iter), + SlotAccessor::TpIternext => update_main_slot!(iternext, iternext_wrapper, IterNext), + SlotAccessor::TpInit => update_main_slot!(init, init_wrapper, Init), + SlotAccessor::TpNew => { + // __new__ is not wrapped via PyWrapper + if ADD { + self.slots.new.store(Some(new_wrapper)); + } else { + accessor.inherit_from_mro(self); + } } - _ if name == identifier!(ctx, __init__) => { - // Special handling: check if this type or any base has a real __init__ method - // If only slot wrappers exist, use the inherited slot from copyslot! + SlotAccessor::TpDel => update_main_slot!(del, del_wrapper, Del), + SlotAccessor::TpGetattro => update_main_slot!(getattro, getattro_wrapper, GetAttro), + SlotAccessor::TpSetattro => { + // __setattr__ and __delattr__ share the same slot if ADD { - // First check if this type has __init__ in its own dict - let has_own_init = self.attributes.read().contains_key(name); - if has_own_init { - // This type defines __init__ - use wrapper - self.slots.init.store(Some(init_wrapper)); - } else if self.has_real_method_in_mro(name, ctx) { - // A base class defines a real __init__ method - use wrapper - self.slots.init.store(Some(init_wrapper)); + if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| match sf { + SlotFunc::SetAttro(f) | SlotFunc::DelAttro(f) => Some(*f), + _ => None, + }) { + self.slots.setattro.store(Some(func)); + } else { + self.slots.setattro.store(Some(setattro_wrapper)); } - // else: keep inherited slot from copyslot! } else { - let inherited = self - .mro - .read() - .iter() - .skip(1) - .find_map(|cls| cls.slots.init.load()); - self.slots.init.store(inherited); + accessor.inherit_from_mro(self); } } - _ if name == identifier!(ctx, __new__) => { - toggle_slot!(new, new_wrapper); - } - _ if name == identifier!(ctx, __del__) => { - // Same special handling as __init__ + SlotAccessor::TpDescrGet => update_main_slot!(descr_get, descr_get_wrapper, DescrGet), + SlotAccessor::TpDescrSet => { + // __set__ and __delete__ share the same slot if ADD { - let has_own_del = self.attributes.read().contains_key(name); - if has_own_del || self.has_real_method_in_mro(name, ctx) { - self.slots.del.store(Some(del_wrapper)); + if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| match sf { + SlotFunc::DescrSet(f) | SlotFunc::DescrDel(f) => Some(*f), + _ => None, + }) { + self.slots.descr_set.store(Some(func)); + } else { + self.slots.descr_set.store(Some(descr_set_wrapper)); } } else { - let inherited = self - .mro - .read() - .iter() - .skip(1) - .find_map(|cls| cls.slots.del.load()); - self.slots.del.store(inherited); + accessor.inherit_from_mro(self); } } - _ if name == identifier!(ctx, __bool__) => { - toggle_sub_slot!(as_number, boolean, bool_wrapper); - } - _ if name == identifier!(ctx, __int__) => { - toggle_sub_slot!(as_number, int, number_unary_op_wrapper!(__int__)); - } - _ if name == identifier!(ctx, __index__) => { - toggle_sub_slot!(as_number, index, number_unary_op_wrapper!(__index__)); - } - _ if name == identifier!(ctx, __float__) => { - toggle_sub_slot!(as_number, float, number_unary_op_wrapper!(__float__)); - } - _ if name == identifier!(ctx, __add__) => { - toggle_sub_slot!(as_number, add, number_binary_op_wrapper!(__add__)); - } - _ if name == identifier!(ctx, __radd__) => { - toggle_sub_slot!( - as_number, - right_add, - number_binary_right_op_wrapper!(__radd__) - ); - } - _ if name == identifier!(ctx, __iadd__) => { - toggle_sub_slot!(as_number, inplace_add, number_binary_op_wrapper!(__iadd__)); + + // === Rich compare (__lt__, __le__, __eq__, __ne__, __gt__, __ge__) === + SlotAccessor::TpRichcompare => { + if ADD { + // Check if self or any class in MRO has a Python-defined comparison method + // All comparison ops share the same slot, so if any is overridden anywhere + // in the hierarchy with a Python function, we need to use the wrapper + let cmp_names = [ + identifier!(ctx, __eq__), + identifier!(ctx, __ne__), + identifier!(ctx, __lt__), + identifier!(ctx, __le__), + identifier!(ctx, __gt__), + identifier!(ctx, __ge__), + ]; + + let has_python_cmp = { + // Check self first + let attrs = self.attributes.read(); + let in_self = cmp_names.iter().any(|n| attrs.contains_key(*n)); + drop(attrs); + + in_self + || self.mro.read().iter().any(|cls| { + let attrs = cls.attributes.read(); + cmp_names.iter().any(|n| { + if let Some(attr) = attrs.get(*n) { + // Check if it's a Python function (not wrapper_descriptor) + !attr.class().is(ctx.types.wrapper_descriptor_type) + } else { + false + } + }) + }) + }; + + if has_python_cmp { + // Use wrapper to call the Python method + self.slots.richcompare.store(Some(richcompare_wrapper)); + } else if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| { + if let SlotFunc::RichCompare(f, _) = sf { + Some(*f) + } else { + None + } + }) { + self.slots.richcompare.store(Some(func)); + } else { + self.slots.richcompare.store(Some(richcompare_wrapper)); + } + } else { + accessor.inherit_from_mro(self); + } } - _ if name == identifier!(ctx, __sub__) => { - toggle_sub_slot!(as_number, subtract, number_binary_op_wrapper!(__sub__)); + + // === Number binary operations === + SlotAccessor::NbAdd => { + if name.as_str() == "__radd__" { + update_sub_slot!( + as_number, + right_add, + number_binary_right_op_wrapper!(__radd__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + add, + number_binary_op_wrapper!(__add__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __rsub__) => { - toggle_sub_slot!( + SlotAccessor::NbInplaceAdd => { + update_sub_slot!( as_number, - right_subtract, - number_binary_right_op_wrapper!(__rsub__) - ); + inplace_add, + number_binary_op_wrapper!(__iadd__), + NumBinary + ) + } + SlotAccessor::NbSubtract => { + if name.as_str() == "__rsub__" { + update_sub_slot!( + as_number, + right_subtract, + number_binary_right_op_wrapper!(__rsub__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + subtract, + number_binary_op_wrapper!(__sub__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __isub__) => { - toggle_sub_slot!( + SlotAccessor::NbInplaceSubtract => { + update_sub_slot!( as_number, inplace_subtract, - number_binary_op_wrapper!(__isub__) - ); - } - _ if name == identifier!(ctx, __mul__) => { - toggle_sub_slot!(as_number, multiply, number_binary_op_wrapper!(__mul__)); - } - _ if name == identifier!(ctx, __rmul__) => { - toggle_sub_slot!( - as_number, - right_multiply, - number_binary_right_op_wrapper!(__rmul__) - ); + number_binary_op_wrapper!(__isub__), + NumBinary + ) + } + SlotAccessor::NbMultiply => { + if name.as_str() == "__rmul__" { + update_sub_slot!( + as_number, + right_multiply, + number_binary_right_op_wrapper!(__rmul__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + multiply, + number_binary_op_wrapper!(__mul__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __imul__) => { - toggle_sub_slot!( + SlotAccessor::NbInplaceMultiply => { + update_sub_slot!( as_number, inplace_multiply, - number_binary_op_wrapper!(__imul__) - ); - } - _ if name == identifier!(ctx, __mod__) => { - toggle_sub_slot!(as_number, remainder, number_binary_op_wrapper!(__mod__)); - } - _ if name == identifier!(ctx, __rmod__) => { - toggle_sub_slot!( - as_number, - right_remainder, - number_binary_right_op_wrapper!(__rmod__) - ); + number_binary_op_wrapper!(__imul__), + NumBinary + ) + } + SlotAccessor::NbRemainder => { + if name.as_str() == "__rmod__" { + update_sub_slot!( + as_number, + right_remainder, + number_binary_right_op_wrapper!(__rmod__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + remainder, + number_binary_op_wrapper!(__mod__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __imod__) => { - toggle_sub_slot!( + SlotAccessor::NbInplaceRemainder => { + update_sub_slot!( as_number, inplace_remainder, - number_binary_op_wrapper!(__imod__) - ); + number_binary_op_wrapper!(__imod__), + NumBinary + ) + } + SlotAccessor::NbDivmod => { + if name.as_str() == "__rdivmod__" { + update_sub_slot!( + as_number, + right_divmod, + number_binary_right_op_wrapper!(__rdivmod__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + divmod, + number_binary_op_wrapper!(__divmod__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __divmod__) => { - toggle_sub_slot!(as_number, divmod, number_binary_op_wrapper!(__divmod__)); + SlotAccessor::NbPower => { + if name.as_str() == "__rpow__" { + update_sub_slot!( + as_number, + right_power, + number_ternary_right_op_wrapper!(__rpow__), + NumTernary + ) + } else { + update_sub_slot!( + as_number, + power, + number_ternary_op_wrapper!(__pow__), + NumTernary + ) + } } - _ if name == identifier!(ctx, __rdivmod__) => { - toggle_sub_slot!( + SlotAccessor::NbInplacePower => { + update_sub_slot!( as_number, - right_divmod, - number_binary_right_op_wrapper!(__rdivmod__) - ); - } - _ if name == identifier!(ctx, __pow__) => { - toggle_sub_slot!(as_number, power, |a, b, c, vm| { - let args = if vm.is_none(c) { - vec![b.to_owned()] - } else { - vec![b.to_owned(), c.to_owned()] - }; - vm.call_special_method(a, identifier!(vm, __pow__), args) - }); - } - _ if name == identifier!(ctx, __rpow__) => { - toggle_sub_slot!(as_number, right_power, |a, b, c, vm| { - let args = if vm.is_none(c) { - vec![a.to_owned()] - } else { - vec![a.to_owned(), c.to_owned()] - }; - vm.call_special_method(b, identifier!(vm, __rpow__), args) - }); - } - _ if name == identifier!(ctx, __ipow__) => { - toggle_sub_slot!(as_number, inplace_power, |a, b, _, vm| { - vm.call_special_method(a, identifier!(vm, __ipow__), (b.to_owned(),)) - }); - } - _ if name == identifier!(ctx, __lshift__) => { - toggle_sub_slot!(as_number, lshift, number_binary_op_wrapper!(__lshift__)); + inplace_power, + number_ternary_op_wrapper!(__ipow__), + NumTernary + ) + } + SlotAccessor::NbFloorDivide => { + if name.as_str() == "__rfloordiv__" { + update_sub_slot!( + as_number, + right_floor_divide, + number_binary_right_op_wrapper!(__rfloordiv__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + floor_divide, + number_binary_op_wrapper!(__floordiv__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __rlshift__) => { - toggle_sub_slot!( + SlotAccessor::NbInplaceFloorDivide => { + update_sub_slot!( as_number, - right_lshift, - number_binary_right_op_wrapper!(__rlshift__) - ); + inplace_floor_divide, + number_binary_op_wrapper!(__ifloordiv__), + NumBinary + ) + } + SlotAccessor::NbTrueDivide => { + if name.as_str() == "__rtruediv__" { + update_sub_slot!( + as_number, + right_true_divide, + number_binary_right_op_wrapper!(__rtruediv__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + true_divide, + number_binary_op_wrapper!(__truediv__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __ilshift__) => { - toggle_sub_slot!( + SlotAccessor::NbInplaceTrueDivide => { + update_sub_slot!( as_number, - inplace_lshift, - number_binary_op_wrapper!(__ilshift__) - ); + inplace_true_divide, + number_binary_op_wrapper!(__itruediv__), + NumBinary + ) + } + SlotAccessor::NbMatrixMultiply => { + if name.as_str() == "__rmatmul__" { + update_sub_slot!( + as_number, + right_matrix_multiply, + number_binary_right_op_wrapper!(__rmatmul__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + matrix_multiply, + number_binary_op_wrapper!(__matmul__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __rshift__) => { - toggle_sub_slot!(as_number, rshift, number_binary_op_wrapper!(__rshift__)); + SlotAccessor::NbInplaceMatrixMultiply => { + update_sub_slot!( + as_number, + inplace_matrix_multiply, + number_binary_op_wrapper!(__imatmul__), + NumBinary + ) + } + + // === Number bitwise operations === + SlotAccessor::NbLshift => { + if name.as_str() == "__rlshift__" { + update_sub_slot!( + as_number, + right_lshift, + number_binary_right_op_wrapper!(__rlshift__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + lshift, + number_binary_op_wrapper!(__lshift__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __rrshift__) => { - toggle_sub_slot!( + SlotAccessor::NbInplaceLshift => { + update_sub_slot!( as_number, - right_rshift, - number_binary_right_op_wrapper!(__rrshift__) - ); + inplace_lshift, + number_binary_op_wrapper!(__ilshift__), + NumBinary + ) + } + SlotAccessor::NbRshift => { + if name.as_str() == "__rrshift__" { + update_sub_slot!( + as_number, + right_rshift, + number_binary_right_op_wrapper!(__rrshift__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + rshift, + number_binary_op_wrapper!(__rshift__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __irshift__) => { - toggle_sub_slot!( + SlotAccessor::NbInplaceRshift => { + update_sub_slot!( as_number, inplace_rshift, - number_binary_op_wrapper!(__irshift__) - ); - } - _ if name == identifier!(ctx, __and__) => { - toggle_sub_slot!(as_number, and, number_binary_op_wrapper!(__and__)); + number_binary_op_wrapper!(__irshift__), + NumBinary + ) + } + SlotAccessor::NbAnd => { + if name.as_str() == "__rand__" { + update_sub_slot!( + as_number, + right_and, + number_binary_right_op_wrapper!(__rand__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + and, + number_binary_op_wrapper!(__and__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __rand__) => { - toggle_sub_slot!( + SlotAccessor::NbInplaceAnd => { + update_sub_slot!( as_number, - right_and, - number_binary_right_op_wrapper!(__rand__) - ); - } - _ if name == identifier!(ctx, __iand__) => { - toggle_sub_slot!(as_number, inplace_and, number_binary_op_wrapper!(__iand__)); - } - _ if name == identifier!(ctx, __xor__) => { - toggle_sub_slot!(as_number, xor, number_binary_op_wrapper!(__xor__)); + inplace_and, + number_binary_op_wrapper!(__iand__), + NumBinary + ) + } + SlotAccessor::NbXor => { + if name.as_str() == "__rxor__" { + update_sub_slot!( + as_number, + right_xor, + number_binary_right_op_wrapper!(__rxor__), + NumBinary + ) + } else { + update_sub_slot!( + as_number, + xor, + number_binary_op_wrapper!(__xor__), + NumBinary + ) + } } - _ if name == identifier!(ctx, __rxor__) => { - toggle_sub_slot!( + SlotAccessor::NbInplaceXor => { + update_sub_slot!( as_number, - right_xor, - number_binary_right_op_wrapper!(__rxor__) - ); - } - _ if name == identifier!(ctx, __ixor__) => { - toggle_sub_slot!(as_number, inplace_xor, number_binary_op_wrapper!(__ixor__)); - } - _ if name == identifier!(ctx, __or__) => { - toggle_sub_slot!(as_number, or, number_binary_op_wrapper!(__or__)); + inplace_xor, + number_binary_op_wrapper!(__ixor__), + NumBinary + ) + } + SlotAccessor::NbOr => { + if name.as_str() == "__ror__" { + update_sub_slot!( + as_number, + right_or, + number_binary_right_op_wrapper!(__ror__), + NumBinary + ) + } else { + update_sub_slot!(as_number, or, number_binary_op_wrapper!(__or__), NumBinary) + } } - _ if name == identifier!(ctx, __ror__) => { - toggle_sub_slot!( + SlotAccessor::NbInplaceOr => { + update_sub_slot!( as_number, - right_or, - number_binary_right_op_wrapper!(__ror__) - ); - } - _ if name == identifier!(ctx, __ior__) => { - toggle_sub_slot!(as_number, inplace_or, number_binary_op_wrapper!(__ior__)); + inplace_or, + number_binary_op_wrapper!(__ior__), + NumBinary + ) } - _ if name == identifier!(ctx, __floordiv__) => { - toggle_sub_slot!( + + // === Number unary operations === + SlotAccessor::NbNegative => { + update_sub_slot!( as_number, - floor_divide, - number_binary_op_wrapper!(__floordiv__) - ); + negative, + number_unary_op_wrapper!(__neg__), + NumUnary + ) } - _ if name == identifier!(ctx, __rfloordiv__) => { - toggle_sub_slot!( + SlotAccessor::NbPositive => { + update_sub_slot!( as_number, - right_floor_divide, - number_binary_right_op_wrapper!(__rfloordiv__) - ); + positive, + number_unary_op_wrapper!(__pos__), + NumUnary + ) } - _ if name == identifier!(ctx, __ifloordiv__) => { - toggle_sub_slot!( + SlotAccessor::NbAbsolute => { + update_sub_slot!( as_number, - inplace_floor_divide, - number_binary_op_wrapper!(__ifloordiv__) - ); + absolute, + number_unary_op_wrapper!(__abs__), + NumUnary + ) } - _ if name == identifier!(ctx, __truediv__) => { - toggle_sub_slot!( + SlotAccessor::NbInvert => { + update_sub_slot!( as_number, - true_divide, - number_binary_op_wrapper!(__truediv__) - ); + invert, + number_unary_op_wrapper!(__invert__), + NumUnary + ) } - _ if name == identifier!(ctx, __rtruediv__) => { - toggle_sub_slot!( - as_number, - right_true_divide, - number_binary_right_op_wrapper!(__rtruediv__) - ); + SlotAccessor::NbBool => { + update_sub_slot!(as_number, boolean, bool_wrapper, NumBoolean) } - _ if name == identifier!(ctx, __itruediv__) => { - toggle_sub_slot!( - as_number, - inplace_true_divide, - number_binary_op_wrapper!(__itruediv__) - ); + SlotAccessor::NbInt => { + update_sub_slot!(as_number, int, number_unary_op_wrapper!(__int__), NumUnary) } - _ if name == identifier!(ctx, __matmul__) => { - toggle_sub_slot!( + SlotAccessor::NbFloat => { + update_sub_slot!( as_number, - matrix_multiply, - number_binary_op_wrapper!(__matmul__) - ); + float, + number_unary_op_wrapper!(__float__), + NumUnary + ) } - _ if name == identifier!(ctx, __rmatmul__) => { - toggle_sub_slot!( + SlotAccessor::NbIndex => { + update_sub_slot!( as_number, - right_matrix_multiply, - number_binary_right_op_wrapper!(__rmatmul__) - ); + index, + number_unary_op_wrapper!(__index__), + NumUnary + ) + } + + // === Sequence slots === + SlotAccessor::SqLength => { + update_sub_slot!( + as_sequence, + length, + |seq, vm| len_wrapper(seq.obj, vm), + SeqLength + ) + } + SlotAccessor::SqConcat | SlotAccessor::SqInplaceConcat => { + // Sequence concat uses sq_concat slot - no generic wrapper needed + // (handled by number protocol fallback) + if !ADD { + accessor.inherit_from_mro(self); + } } - _ if name == identifier!(ctx, __imatmul__) => { - toggle_sub_slot!( - as_number, - inplace_matrix_multiply, - number_binary_op_wrapper!(__imatmul__) - ); + SlotAccessor::SqRepeat | SlotAccessor::SqInplaceRepeat => { + // Sequence repeat uses sq_repeat slot - no generic wrapper needed + // (handled by number protocol fallback) + if !ADD { + accessor.inherit_from_mro(self); + } } + SlotAccessor::SqItem => { + update_sub_slot!( + as_sequence, + item, + |seq, i, vm| getitem_wrapper(seq.obj, i, vm), + SeqItem + ) + } + SlotAccessor::SqAssItem => { + update_sub_slot!( + as_sequence, + ass_item, + |seq, i, value, vm| setitem_wrapper(seq.obj, i, value, vm), + SeqAssItem + ) + } + SlotAccessor::SqContains => { + update_sub_slot!( + as_sequence, + contains, + |seq, needle, vm| contains_wrapper(seq.obj, needle, vm), + SeqContains + ) + } + + // === Mapping slots === + SlotAccessor::MpLength => { + update_sub_slot!( + as_mapping, + length, + |mapping, vm| len_wrapper(mapping.obj, vm), + MapLength + ) + } + SlotAccessor::MpSubscript => { + update_sub_slot!( + as_mapping, + subscript, + |mapping, key, vm| getitem_wrapper(mapping.obj, key, vm), + MapSubscript + ) + } + SlotAccessor::MpAssSubscript => { + update_sub_slot!( + as_mapping, + ass_subscript, + |mapping, key, value, vm| setitem_wrapper(mapping.obj, key, value, vm), + MapAssSubscript + ) + } + + // Reserved slots - no-op _ => {} } } - /// Check if there's a real method (not a slot wrapper) for `name` anywhere in the MRO. - /// If a real method exists, we should use a wrapper function. Otherwise, use the inherited slot. - fn has_real_method_in_mro(&self, name: &'static PyStrInterned, ctx: &Context) -> bool { - use crate::builtins::descriptor::PySlotWrapper; + /// Look up a method in MRO and extract the slot function if it's a slot wrapper. + /// Returns Some(slot_func) if a matching slot wrapper is found, None if a real method + /// is found or no method exists. + fn lookup_slot_in_mro<T: Copy>( + &self, + name: &'static PyStrInterned, + ctx: &Context, + extract: impl Fn(&crate::builtins::descriptor::SlotFunc) -> Option<T>, + ) -> Option<T> { + use crate::builtins::descriptor::PyWrapper; + + // Helper to extract slot from an attribute if it's a wrapper descriptor + let try_extract = |attr: &PyObjectRef| -> Option<T> { + if attr.class().is(ctx.types.wrapper_descriptor_type) { + attr.downcast_ref::<PyWrapper>() + .and_then(|wrapper| extract(&wrapper.wrapped)) + } else { + None + } + }; - // Check the entire MRO (including self) for the method + // Look up in self's dict first + if let Some(attr) = self.attributes.read().get(name).cloned() { + if let Some(func) = try_extract(&attr) { + return Some(func); + } + return None; + } + + // Look up in MRO for cls in self.mro.read().iter() { if let Some(attr) = cls.attributes.read().get(name).cloned() { - // Found the method - check if it's a slot wrapper - if attr.class().is(ctx.types.wrapper_descriptor_type) { - if let Some(wrapper) = attr.downcast_ref::<PySlotWrapper>() { - // It's a slot wrapper - check if it belongs to this class - let wrapper_typ: *const _ = wrapper.typ; - let cls_ptr: *const _ = cls.as_ref(); - if wrapper_typ == cls_ptr { - // Slot wrapper defined on this exact class - use inherited slot - return false; - } - } - // Inherited slot wrapper - continue checking MRO - } else { - // Real method found - use wrapper function - return true; + if let Some(func) = try_extract(&attr) { + return Some(func); } + return None; } } - // No real method found in MRO - use inherited slot - false + // No method found in MRO + None } } @@ -1233,60 +1535,8 @@ pub trait Comparable: PyPayload { vm: &VirtualMachine, ) -> PyResult<PyComparisonValue>; - #[inline] - #[pymethod] - fn __eq__( - zelf: &Py<Self>, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(zelf, &other, PyComparisonOp::Eq, vm) - } - #[inline] - #[pymethod] - fn __ne__( - zelf: &Py<Self>, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(zelf, &other, PyComparisonOp::Ne, vm) - } - #[inline] - #[pymethod] - fn __lt__( - zelf: &Py<Self>, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(zelf, &other, PyComparisonOp::Lt, vm) - } - #[inline] - #[pymethod] - fn __le__( - zelf: &Py<Self>, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(zelf, &other, PyComparisonOp::Le, vm) - } - #[inline] - #[pymethod] - fn __ge__( - zelf: &Py<Self>, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(zelf, &other, PyComparisonOp::Ge, vm) - } - #[inline] - #[pymethod] - fn __gt__( - zelf: &Py<Self>, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(zelf, &other, PyComparisonOp::Gt, vm) - } + // Comparison methods are exposed as wrapper_descriptor via slot_richcompare + // No #[pymethod] - add_operators creates wrapper from SLOT_DEFS } #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -1495,6 +1745,10 @@ pub trait AsNumber: PyPayload { #[pyslot] fn as_number() -> &'static PyNumberMethods; + fn extend_slots(slots: &mut PyTypeSlots) { + slots.as_number.copy_from(Self::as_number()); + } + fn clone_exact(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyRef<Self> { // not all AsNumber requires this implementation. unimplemented!() diff --git a/crates/vm/src/types/slot_defs.rs b/crates/vm/src/types/slot_defs.rs new file mode 100644 index 00000000000..0d8dbf4b0cc --- /dev/null +++ b/crates/vm/src/types/slot_defs.rs @@ -0,0 +1,1465 @@ +//! Slot definitions array +//! +//! This module provides a centralized array of all slot definitions, + +use super::{PyComparisonOp, PyTypeSlots}; +use crate::builtins::descriptor::SlotFunc; + +/// Slot operation type +/// +/// Used to distinguish between different operations that share the same slot: +/// - RichCompare: Lt, Le, Eq, Ne, Gt, Ge +/// - Binary ops: Left (__add__) vs Right (__radd__) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SlotOp { + // RichCompare operations + Lt, + Le, + Eq, + Ne, + Gt, + Ge, + // Binary operation direction + Left, + Right, + // Setter vs Deleter + Delete, +} + +impl SlotOp { + /// Convert to PyComparisonOp if this is a comparison operation + pub fn as_compare_op(&self) -> Option<PyComparisonOp> { + match self { + Self::Lt => Some(PyComparisonOp::Lt), + Self::Le => Some(PyComparisonOp::Le), + Self::Eq => Some(PyComparisonOp::Eq), + Self::Ne => Some(PyComparisonOp::Ne), + Self::Gt => Some(PyComparisonOp::Gt), + Self::Ge => Some(PyComparisonOp::Ge), + _ => None, + } + } + + /// Check if this is a right operation (__radd__, __rsub__, etc.) + pub fn is_right(&self) -> bool { + matches!(self, Self::Right) + } +} + +/// Slot definition entry +#[derive(Clone, Copy)] +pub struct SlotDef { + /// Method name ("__init__", "__add__", etc.) + pub name: &'static str, + + /// Slot accessor (which slot field to access) + pub accessor: SlotAccessor, + + /// Operation type (for shared slots like RichCompare, binary ops) + pub op: Option<SlotOp>, + + /// Documentation string + pub doc: &'static str, +} + +/// Slot accessor +/// +/// Values match CPython's Py_* slot IDs from typeslots.h. +/// Unused slots are included for value reservation. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum SlotAccessor { + // Buffer protocol (1-2) - Reserved, not used in RustPython + BfGetBuffer = 1, + BfReleaseBuffer = 2, + + // Mapping protocol (3-5) + MpAssSubscript = 3, + MpLength = 4, + MpSubscript = 5, + + // Number protocol (6-38) + NbAbsolute = 6, + NbAdd = 7, + NbAnd = 8, + NbBool = 9, + NbDivmod = 10, + NbFloat = 11, + NbFloorDivide = 12, + NbIndex = 13, + NbInplaceAdd = 14, + NbInplaceAnd = 15, + NbInplaceFloorDivide = 16, + NbInplaceLshift = 17, + NbInplaceMultiply = 18, + NbInplaceOr = 19, + NbInplacePower = 20, + NbInplaceRemainder = 21, + NbInplaceRshift = 22, + NbInplaceSubtract = 23, + NbInplaceTrueDivide = 24, + NbInplaceXor = 25, + NbInt = 26, + NbInvert = 27, + NbLshift = 28, + NbMultiply = 29, + NbNegative = 30, + NbOr = 31, + NbPositive = 32, + NbPower = 33, + NbRemainder = 34, + NbRshift = 35, + NbSubtract = 36, + NbTrueDivide = 37, + NbXor = 38, + + // Sequence protocol (39-46) + SqAssItem = 39, + SqConcat = 40, + SqContains = 41, + SqInplaceConcat = 42, + SqInplaceRepeat = 43, + SqItem = 44, + SqLength = 45, + SqRepeat = 46, + + // Type slots (47-74) + TpAlloc = 47, // Reserved + TpBase = 48, // Reserved + TpBases = 49, // Reserved + TpCall = 50, + TpClear = 51, // Reserved + TpDealloc = 52, // Reserved + TpDel = 53, + TpDescrGet = 54, + TpDescrSet = 55, + TpDoc = 56, // Reserved + TpGetattr = 57, // Reserved (use TpGetattro) + TpGetattro = 58, + TpHash = 59, + TpInit = 60, + TpIsGc = 61, // Reserved + TpIter = 62, + TpIternext = 63, + TpMethods = 64, // Reserved + TpNew = 65, + TpRepr = 66, + TpRichcompare = 67, + TpSetattr = 68, // Reserved (use TpSetattro) + TpSetattro = 69, + TpStr = 70, + TpTraverse = 71, // Reserved + TpMembers = 72, // Reserved + TpGetset = 73, // Reserved + TpFree = 74, // Reserved + + // Number protocol additions (75-76) + NbMatrixMultiply = 75, + NbInplaceMatrixMultiply = 76, + + // Async protocol (77-81) - Reserved for future + AmAwait = 77, + AmAiter = 78, + AmAnext = 79, + TpFinalize = 80, + AmSend = 81, +} + +impl SlotAccessor { + /// Check if this accessor is for a reserved/unused slot + pub fn is_reserved(&self) -> bool { + matches!( + self, + Self::BfGetBuffer + | Self::BfReleaseBuffer + | Self::TpAlloc + | Self::TpBase + | Self::TpBases + | Self::TpClear + | Self::TpDealloc + | Self::TpDoc + | Self::TpGetattr + | Self::TpIsGc + | Self::TpMethods + | Self::TpSetattr + | Self::TpTraverse + | Self::TpMembers + | Self::TpGetset + | Self::TpFree + | Self::TpFinalize + | Self::AmAwait + | Self::AmAiter + | Self::AmAnext + | Self::AmSend + ) + } + + /// Check if this is a number binary operation slot + pub fn is_number_binary(&self) -> bool { + matches!( + self, + Self::NbAdd + | Self::NbSubtract + | Self::NbMultiply + | Self::NbRemainder + | Self::NbDivmod + | Self::NbPower + | Self::NbLshift + | Self::NbRshift + | Self::NbAnd + | Self::NbXor + | Self::NbOr + | Self::NbFloorDivide + | Self::NbTrueDivide + | Self::NbMatrixMultiply + ) + } + + /// Check if this accessor refers to a shared slot + /// + /// Shared slots are used by multiple dunder methods: + /// - TpSetattro: __setattr__ and __delattr__ + /// - TpRichcompare: __lt__, __le__, __eq__, __ne__, __gt__, __ge__ + /// - TpDescrSet: __set__ and __delete__ + /// - SqAssItem/MpAssSubscript: __setitem__ and __delitem__ + /// - Number binaries: __add__ and __radd__, etc. + pub fn is_shared_slot(&self) -> bool { + matches!( + self, + Self::TpSetattro + | Self::TpRichcompare + | Self::TpDescrSet + | Self::SqAssItem + | Self::MpAssSubscript + ) || self.is_number_binary() + } + + /// Get underlying slot field name for debugging + pub fn slot_name(&self) -> &'static str { + match self { + Self::BfGetBuffer => "bf_getbuffer", + Self::BfReleaseBuffer => "bf_releasebuffer", + Self::MpAssSubscript => "mp_ass_subscript", + Self::MpLength => "mp_length", + Self::MpSubscript => "mp_subscript", + Self::NbAbsolute => "nb_absolute", + Self::NbAdd => "nb_add", + Self::NbAnd => "nb_and", + Self::NbBool => "nb_bool", + Self::NbDivmod => "nb_divmod", + Self::NbFloat => "nb_float", + Self::NbFloorDivide => "nb_floor_divide", + Self::NbIndex => "nb_index", + Self::NbInplaceAdd => "nb_inplace_add", + Self::NbInplaceAnd => "nb_inplace_and", + Self::NbInplaceFloorDivide => "nb_inplace_floor_divide", + Self::NbInplaceLshift => "nb_inplace_lshift", + Self::NbInplaceMultiply => "nb_inplace_multiply", + Self::NbInplaceOr => "nb_inplace_or", + Self::NbInplacePower => "nb_inplace_power", + Self::NbInplaceRemainder => "nb_inplace_remainder", + Self::NbInplaceRshift => "nb_inplace_rshift", + Self::NbInplaceSubtract => "nb_inplace_subtract", + Self::NbInplaceTrueDivide => "nb_inplace_true_divide", + Self::NbInplaceXor => "nb_inplace_xor", + Self::NbInt => "nb_int", + Self::NbInvert => "nb_invert", + Self::NbLshift => "nb_lshift", + Self::NbMultiply => "nb_multiply", + Self::NbNegative => "nb_negative", + Self::NbOr => "nb_or", + Self::NbPositive => "nb_positive", + Self::NbPower => "nb_power", + Self::NbRemainder => "nb_remainder", + Self::NbRshift => "nb_rshift", + Self::NbSubtract => "nb_subtract", + Self::NbTrueDivide => "nb_true_divide", + Self::NbXor => "nb_xor", + Self::SqAssItem => "sq_ass_item", + Self::SqConcat => "sq_concat", + Self::SqContains => "sq_contains", + Self::SqInplaceConcat => "sq_inplace_concat", + Self::SqInplaceRepeat => "sq_inplace_repeat", + Self::SqItem => "sq_item", + Self::SqLength => "sq_length", + Self::SqRepeat => "sq_repeat", + Self::TpAlloc => "tp_alloc", + Self::TpBase => "tp_base", + Self::TpBases => "tp_bases", + Self::TpCall => "tp_call", + Self::TpClear => "tp_clear", + Self::TpDealloc => "tp_dealloc", + Self::TpDel => "tp_del", + Self::TpDescrGet => "tp_descr_get", + Self::TpDescrSet => "tp_descr_set", + Self::TpDoc => "tp_doc", + Self::TpGetattr => "tp_getattr", + Self::TpGetattro => "tp_getattro", + Self::TpHash => "tp_hash", + Self::TpInit => "tp_init", + Self::TpIsGc => "tp_is_gc", + Self::TpIter => "tp_iter", + Self::TpIternext => "tp_iternext", + Self::TpMethods => "tp_methods", + Self::TpNew => "tp_new", + Self::TpRepr => "tp_repr", + Self::TpRichcompare => "tp_richcompare", + Self::TpSetattr => "tp_setattr", + Self::TpSetattro => "tp_setattro", + Self::TpStr => "tp_str", + Self::TpTraverse => "tp_traverse", + Self::TpMembers => "tp_members", + Self::TpGetset => "tp_getset", + Self::TpFree => "tp_free", + Self::NbMatrixMultiply => "nb_matrix_multiply", + Self::NbInplaceMatrixMultiply => "nb_inplace_matrix_multiply", + Self::AmAwait => "am_await", + Self::AmAiter => "am_aiter", + Self::AmAnext => "am_anext", + Self::TpFinalize => "tp_finalize", + Self::AmSend => "am_send", + } + } + + /// Extract the raw function pointer from a SlotFunc if it matches this accessor's type + pub fn extract_from_slot_func(&self, slot_func: &SlotFunc) -> bool { + match self { + // Type slots + Self::TpHash => matches!(slot_func, SlotFunc::Hash(_)), + Self::TpRepr => matches!(slot_func, SlotFunc::Repr(_)), + Self::TpStr => matches!(slot_func, SlotFunc::Str(_)), + Self::TpCall => matches!(slot_func, SlotFunc::Call(_)), + Self::TpIter => matches!(slot_func, SlotFunc::Iter(_)), + Self::TpIternext => matches!(slot_func, SlotFunc::IterNext(_)), + Self::TpInit => matches!(slot_func, SlotFunc::Init(_)), + Self::TpDel => matches!(slot_func, SlotFunc::Del(_)), + Self::TpGetattro => matches!(slot_func, SlotFunc::GetAttro(_)), + Self::TpSetattro => { + matches!(slot_func, SlotFunc::SetAttro(_) | SlotFunc::DelAttro(_)) + } + Self::TpDescrGet => matches!(slot_func, SlotFunc::DescrGet(_)), + Self::TpDescrSet => { + matches!(slot_func, SlotFunc::DescrSet(_) | SlotFunc::DescrDel(_)) + } + Self::TpRichcompare => matches!(slot_func, SlotFunc::RichCompare(_, _)), + + // Number - Power (ternary) + Self::NbPower | Self::NbInplacePower => { + matches!(slot_func, SlotFunc::NumTernary(_)) + } + // Number - Boolean + Self::NbBool => matches!(slot_func, SlotFunc::NumBoolean(_)), + // Number - Unary + Self::NbNegative + | Self::NbPositive + | Self::NbAbsolute + | Self::NbInvert + | Self::NbInt + | Self::NbFloat + | Self::NbIndex => matches!(slot_func, SlotFunc::NumUnary(_)), + // Number - Binary + Self::NbAdd + | Self::NbSubtract + | Self::NbMultiply + | Self::NbRemainder + | Self::NbDivmod + | Self::NbLshift + | Self::NbRshift + | Self::NbAnd + | Self::NbXor + | Self::NbOr + | Self::NbFloorDivide + | Self::NbTrueDivide + | Self::NbMatrixMultiply + | Self::NbInplaceAdd + | Self::NbInplaceSubtract + | Self::NbInplaceMultiply + | Self::NbInplaceRemainder + | Self::NbInplaceLshift + | Self::NbInplaceRshift + | Self::NbInplaceAnd + | Self::NbInplaceXor + | Self::NbInplaceOr + | Self::NbInplaceFloorDivide + | Self::NbInplaceTrueDivide + | Self::NbInplaceMatrixMultiply => matches!(slot_func, SlotFunc::NumBinary(_)), + + // Sequence + Self::SqLength => matches!(slot_func, SlotFunc::SeqLength(_)), + Self::SqConcat | Self::SqInplaceConcat => matches!(slot_func, SlotFunc::SeqConcat(_)), + Self::SqRepeat | Self::SqInplaceRepeat => matches!(slot_func, SlotFunc::SeqRepeat(_)), + Self::SqItem => matches!(slot_func, SlotFunc::SeqItem(_)), + Self::SqAssItem => matches!(slot_func, SlotFunc::SeqAssItem(_)), + Self::SqContains => matches!(slot_func, SlotFunc::SeqContains(_)), + + // Mapping + Self::MpLength => matches!(slot_func, SlotFunc::MapLength(_)), + Self::MpSubscript => matches!(slot_func, SlotFunc::MapSubscript(_)), + Self::MpAssSubscript => matches!(slot_func, SlotFunc::MapAssSubscript(_)), + + // New and reserved slots + Self::TpNew => false, + _ => false, // Reserved slots + } + } + + /// Inherit slot value from MRO + pub fn inherit_from_mro(&self, typ: &crate::builtins::PyType) { + // Note: typ.mro does NOT include typ itself + let mro = typ.mro.read(); + + macro_rules! inherit_main { + ($slot:ident) => {{ + let inherited = mro.iter().find_map(|cls| cls.slots.$slot.load()); + typ.slots.$slot.store(inherited); + }}; + } + + macro_rules! inherit_number { + ($slot:ident) => {{ + let inherited = mro.iter().find_map(|cls| cls.slots.as_number.$slot.load()); + typ.slots.as_number.$slot.store(inherited); + }}; + } + + macro_rules! inherit_sequence { + ($slot:ident) => {{ + let inherited = mro + .iter() + .find_map(|cls| cls.slots.as_sequence.$slot.load()); + typ.slots.as_sequence.$slot.store(inherited); + }}; + } + + macro_rules! inherit_mapping { + ($slot:ident) => {{ + let inherited = mro.iter().find_map(|cls| cls.slots.as_mapping.$slot.load()); + typ.slots.as_mapping.$slot.store(inherited); + }}; + } + + match self { + // Type slots + Self::TpHash => inherit_main!(hash), + Self::TpRepr => inherit_main!(repr), + Self::TpStr => inherit_main!(str), + Self::TpCall => inherit_main!(call), + Self::TpIter => inherit_main!(iter), + Self::TpIternext => inherit_main!(iternext), + Self::TpInit => inherit_main!(init), + Self::TpNew => inherit_main!(new), + Self::TpDel => inherit_main!(del), + Self::TpGetattro => inherit_main!(getattro), + Self::TpSetattro => inherit_main!(setattro), + Self::TpDescrGet => inherit_main!(descr_get), + Self::TpDescrSet => inherit_main!(descr_set), + Self::TpRichcompare => inherit_main!(richcompare), + + // Number slots + Self::NbAdd => inherit_number!(add), + Self::NbSubtract => inherit_number!(subtract), + Self::NbMultiply => inherit_number!(multiply), + Self::NbRemainder => inherit_number!(remainder), + Self::NbDivmod => inherit_number!(divmod), + Self::NbPower => inherit_number!(power), + Self::NbLshift => inherit_number!(lshift), + Self::NbRshift => inherit_number!(rshift), + Self::NbAnd => inherit_number!(and), + Self::NbXor => inherit_number!(xor), + Self::NbOr => inherit_number!(or), + Self::NbFloorDivide => inherit_number!(floor_divide), + Self::NbTrueDivide => inherit_number!(true_divide), + Self::NbMatrixMultiply => inherit_number!(matrix_multiply), + Self::NbInplaceAdd => inherit_number!(inplace_add), + Self::NbInplaceSubtract => inherit_number!(inplace_subtract), + Self::NbInplaceMultiply => inherit_number!(inplace_multiply), + Self::NbInplaceRemainder => inherit_number!(inplace_remainder), + Self::NbInplacePower => inherit_number!(inplace_power), + Self::NbInplaceLshift => inherit_number!(inplace_lshift), + Self::NbInplaceRshift => inherit_number!(inplace_rshift), + Self::NbInplaceAnd => inherit_number!(inplace_and), + Self::NbInplaceXor => inherit_number!(inplace_xor), + Self::NbInplaceOr => inherit_number!(inplace_or), + Self::NbInplaceFloorDivide => inherit_number!(inplace_floor_divide), + Self::NbInplaceTrueDivide => inherit_number!(inplace_true_divide), + Self::NbInplaceMatrixMultiply => inherit_number!(inplace_matrix_multiply), + // Number unary + Self::NbNegative => inherit_number!(negative), + Self::NbPositive => inherit_number!(positive), + Self::NbAbsolute => inherit_number!(absolute), + Self::NbInvert => inherit_number!(invert), + Self::NbBool => inherit_number!(boolean), + Self::NbInt => inherit_number!(int), + Self::NbFloat => inherit_number!(float), + Self::NbIndex => inherit_number!(index), + + // Sequence slots + Self::SqLength => inherit_sequence!(length), + Self::SqConcat => inherit_sequence!(concat), + Self::SqRepeat => inherit_sequence!(repeat), + Self::SqItem => inherit_sequence!(item), + Self::SqAssItem => inherit_sequence!(ass_item), + Self::SqContains => inherit_sequence!(contains), + Self::SqInplaceConcat => inherit_sequence!(inplace_concat), + Self::SqInplaceRepeat => inherit_sequence!(inplace_repeat), + + // Mapping slots + Self::MpLength => inherit_mapping!(length), + Self::MpSubscript => inherit_mapping!(subscript), + Self::MpAssSubscript => inherit_mapping!(ass_subscript), + + // Reserved slots - no-op + _ => {} + } + } + + /// Copy slot from base type if self's slot is None + pub fn copyslot_if_none(&self, typ: &crate::builtins::PyType, base: &crate::builtins::PyType) { + macro_rules! copy_main { + ($slot:ident) => {{ + if typ.slots.$slot.load().is_none() { + if let Some(base_val) = base.slots.$slot.load() { + typ.slots.$slot.store(Some(base_val)); + } + } + }}; + } + + macro_rules! copy_number { + ($slot:ident) => {{ + if typ.slots.as_number.$slot.load().is_none() { + if let Some(base_val) = base.slots.as_number.$slot.load() { + typ.slots.as_number.$slot.store(Some(base_val)); + } + } + }}; + } + + macro_rules! copy_sequence { + ($slot:ident) => {{ + if typ.slots.as_sequence.$slot.load().is_none() { + if let Some(base_val) = base.slots.as_sequence.$slot.load() { + typ.slots.as_sequence.$slot.store(Some(base_val)); + } + } + }}; + } + + macro_rules! copy_mapping { + ($slot:ident) => {{ + if typ.slots.as_mapping.$slot.load().is_none() { + if let Some(base_val) = base.slots.as_mapping.$slot.load() { + typ.slots.as_mapping.$slot.store(Some(base_val)); + } + } + }}; + } + + match self { + // Type slots + Self::TpHash => copy_main!(hash), + Self::TpRepr => copy_main!(repr), + Self::TpStr => copy_main!(str), + Self::TpCall => copy_main!(call), + Self::TpIter => copy_main!(iter), + Self::TpIternext => copy_main!(iternext), + Self::TpInit => { + // SLOTDEFINED check for multiple inheritance support + if typ.slots.init.load().is_none() + && let Some(base_val) = base.slots.init.load() + { + let slot_defined = base.base.as_ref().is_none_or(|bb| { + bb.slots.init.load().map(|v| v as usize) != Some(base_val as usize) + }); + if slot_defined { + typ.slots.init.store(Some(base_val)); + } + } + } + Self::TpNew => {} // handled by set_new() + Self::TpDel => copy_main!(del), + Self::TpGetattro => copy_main!(getattro), + Self::TpSetattro => copy_main!(setattro), + Self::TpDescrGet => copy_main!(descr_get), + Self::TpDescrSet => copy_main!(descr_set), + Self::TpRichcompare => copy_main!(richcompare), + + // Number slots + Self::NbAdd => copy_number!(add), + Self::NbSubtract => copy_number!(subtract), + Self::NbMultiply => copy_number!(multiply), + Self::NbRemainder => copy_number!(remainder), + Self::NbDivmod => copy_number!(divmod), + Self::NbPower => copy_number!(power), + Self::NbLshift => copy_number!(lshift), + Self::NbRshift => copy_number!(rshift), + Self::NbAnd => copy_number!(and), + Self::NbXor => copy_number!(xor), + Self::NbOr => copy_number!(or), + Self::NbFloorDivide => copy_number!(floor_divide), + Self::NbTrueDivide => copy_number!(true_divide), + Self::NbMatrixMultiply => copy_number!(matrix_multiply), + Self::NbInplaceAdd => copy_number!(inplace_add), + Self::NbInplaceSubtract => copy_number!(inplace_subtract), + Self::NbInplaceMultiply => copy_number!(inplace_multiply), + Self::NbInplaceRemainder => copy_number!(inplace_remainder), + Self::NbInplacePower => copy_number!(inplace_power), + Self::NbInplaceLshift => copy_number!(inplace_lshift), + Self::NbInplaceRshift => copy_number!(inplace_rshift), + Self::NbInplaceAnd => copy_number!(inplace_and), + Self::NbInplaceXor => copy_number!(inplace_xor), + Self::NbInplaceOr => copy_number!(inplace_or), + Self::NbInplaceFloorDivide => copy_number!(inplace_floor_divide), + Self::NbInplaceTrueDivide => copy_number!(inplace_true_divide), + Self::NbInplaceMatrixMultiply => copy_number!(inplace_matrix_multiply), + // Number unary + Self::NbNegative => copy_number!(negative), + Self::NbPositive => copy_number!(positive), + Self::NbAbsolute => copy_number!(absolute), + Self::NbInvert => copy_number!(invert), + Self::NbBool => copy_number!(boolean), + Self::NbInt => copy_number!(int), + Self::NbFloat => copy_number!(float), + Self::NbIndex => copy_number!(index), + + // Sequence slots + Self::SqLength => copy_sequence!(length), + Self::SqConcat => copy_sequence!(concat), + Self::SqRepeat => copy_sequence!(repeat), + Self::SqItem => copy_sequence!(item), + Self::SqAssItem => copy_sequence!(ass_item), + Self::SqContains => copy_sequence!(contains), + Self::SqInplaceConcat => copy_sequence!(inplace_concat), + Self::SqInplaceRepeat => copy_sequence!(inplace_repeat), + + // Mapping slots + Self::MpLength => copy_mapping!(length), + Self::MpSubscript => copy_mapping!(subscript), + Self::MpAssSubscript => copy_mapping!(ass_subscript), + + // Reserved slots - no-op + _ => {} + } + } + + /// Get the SlotFunc from type slots for this accessor + pub fn get_slot_func(&self, slots: &PyTypeSlots) -> Option<SlotFunc> { + match self { + // Type slots + Self::TpHash => slots.hash.load().map(SlotFunc::Hash), + Self::TpRepr => slots.repr.load().map(SlotFunc::Repr), + Self::TpStr => slots.str.load().map(SlotFunc::Str), + Self::TpCall => slots.call.load().map(SlotFunc::Call), + Self::TpIter => slots.iter.load().map(SlotFunc::Iter), + Self::TpIternext => slots.iternext.load().map(SlotFunc::IterNext), + Self::TpInit => slots.init.load().map(SlotFunc::Init), + Self::TpNew => None, // __new__ handled separately + Self::TpDel => slots.del.load().map(SlotFunc::Del), + Self::TpGetattro => slots.getattro.load().map(SlotFunc::GetAttro), + Self::TpSetattro => slots.setattro.load().map(SlotFunc::SetAttro), + Self::TpDescrGet => slots.descr_get.load().map(SlotFunc::DescrGet), + Self::TpDescrSet => slots.descr_set.load().map(SlotFunc::DescrSet), + Self::TpRichcompare => slots + .richcompare + .load() + .map(|f| SlotFunc::RichCompare(f, PyComparisonOp::Eq)), + + // Number binary slots + Self::NbAdd => slots.as_number.add.load().map(SlotFunc::NumBinary), + Self::NbSubtract => slots.as_number.subtract.load().map(SlotFunc::NumBinary), + Self::NbMultiply => slots.as_number.multiply.load().map(SlotFunc::NumBinary), + Self::NbRemainder => slots.as_number.remainder.load().map(SlotFunc::NumBinary), + Self::NbDivmod => slots.as_number.divmod.load().map(SlotFunc::NumBinary), + Self::NbPower => slots.as_number.power.load().map(SlotFunc::NumTernary), + Self::NbLshift => slots.as_number.lshift.load().map(SlotFunc::NumBinary), + Self::NbRshift => slots.as_number.rshift.load().map(SlotFunc::NumBinary), + Self::NbAnd => slots.as_number.and.load().map(SlotFunc::NumBinary), + Self::NbXor => slots.as_number.xor.load().map(SlotFunc::NumBinary), + Self::NbOr => slots.as_number.or.load().map(SlotFunc::NumBinary), + Self::NbFloorDivide => slots.as_number.floor_divide.load().map(SlotFunc::NumBinary), + Self::NbTrueDivide => slots.as_number.true_divide.load().map(SlotFunc::NumBinary), + Self::NbMatrixMultiply => slots + .as_number + .matrix_multiply + .load() + .map(SlotFunc::NumBinary), + + // Number inplace slots + Self::NbInplaceAdd => slots.as_number.inplace_add.load().map(SlotFunc::NumBinary), + Self::NbInplaceSubtract => slots + .as_number + .inplace_subtract + .load() + .map(SlotFunc::NumBinary), + Self::NbInplaceMultiply => slots + .as_number + .inplace_multiply + .load() + .map(SlotFunc::NumBinary), + Self::NbInplaceRemainder => slots + .as_number + .inplace_remainder + .load() + .map(SlotFunc::NumBinary), + Self::NbInplacePower => slots + .as_number + .inplace_power + .load() + .map(SlotFunc::NumTernary), + Self::NbInplaceLshift => slots + .as_number + .inplace_lshift + .load() + .map(SlotFunc::NumBinary), + Self::NbInplaceRshift => slots + .as_number + .inplace_rshift + .load() + .map(SlotFunc::NumBinary), + Self::NbInplaceAnd => slots.as_number.inplace_and.load().map(SlotFunc::NumBinary), + Self::NbInplaceXor => slots.as_number.inplace_xor.load().map(SlotFunc::NumBinary), + Self::NbInplaceOr => slots.as_number.inplace_or.load().map(SlotFunc::NumBinary), + Self::NbInplaceFloorDivide => slots + .as_number + .inplace_floor_divide + .load() + .map(SlotFunc::NumBinary), + Self::NbInplaceTrueDivide => slots + .as_number + .inplace_true_divide + .load() + .map(SlotFunc::NumBinary), + Self::NbInplaceMatrixMultiply => slots + .as_number + .inplace_matrix_multiply + .load() + .map(SlotFunc::NumBinary), + + // Number unary slots + Self::NbNegative => slots.as_number.negative.load().map(SlotFunc::NumUnary), + Self::NbPositive => slots.as_number.positive.load().map(SlotFunc::NumUnary), + Self::NbAbsolute => slots.as_number.absolute.load().map(SlotFunc::NumUnary), + Self::NbInvert => slots.as_number.invert.load().map(SlotFunc::NumUnary), + Self::NbBool => slots.as_number.boolean.load().map(SlotFunc::NumBoolean), + Self::NbInt => slots.as_number.int.load().map(SlotFunc::NumUnary), + Self::NbFloat => slots.as_number.float.load().map(SlotFunc::NumUnary), + Self::NbIndex => slots.as_number.index.load().map(SlotFunc::NumUnary), + + // Sequence slots + Self::SqLength => slots.as_sequence.length.load().map(SlotFunc::SeqLength), + Self::SqConcat => slots.as_sequence.concat.load().map(SlotFunc::SeqConcat), + Self::SqRepeat => slots.as_sequence.repeat.load().map(SlotFunc::SeqRepeat), + Self::SqItem => slots.as_sequence.item.load().map(SlotFunc::SeqItem), + Self::SqAssItem => slots.as_sequence.ass_item.load().map(SlotFunc::SeqAssItem), + Self::SqContains => slots.as_sequence.contains.load().map(SlotFunc::SeqContains), + Self::SqInplaceConcat => slots + .as_sequence + .inplace_concat + .load() + .map(SlotFunc::SeqConcat), + Self::SqInplaceRepeat => slots + .as_sequence + .inplace_repeat + .load() + .map(SlotFunc::SeqRepeat), + + // Mapping slots + Self::MpLength => slots.as_mapping.length.load().map(SlotFunc::MapLength), + Self::MpSubscript => slots + .as_mapping + .subscript + .load() + .map(SlotFunc::MapSubscript), + Self::MpAssSubscript => slots + .as_mapping + .ass_subscript + .load() + .map(SlotFunc::MapAssSubscript), + + // Reserved slots + _ => None, + } + } + + /// Get slot function considering SlotOp for right-hand and delete operations + pub fn get_slot_func_with_op( + &self, + slots: &PyTypeSlots, + op: Option<SlotOp>, + ) -> Option<SlotFunc> { + // For Delete operations, return the delete variant + if op == Some(SlotOp::Delete) { + match self { + Self::TpSetattro => return slots.setattro.load().map(SlotFunc::DelAttro), + Self::TpDescrSet => return slots.descr_set.load().map(SlotFunc::DescrDel), + _ => {} + } + } + // For Right operations on binary number slots, use right_* fields with swapped args + if op == Some(SlotOp::Right) { + match self { + Self::NbAdd => { + return slots + .as_number + .right_add + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbSubtract => { + return slots + .as_number + .right_subtract + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbMultiply => { + return slots + .as_number + .right_multiply + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbRemainder => { + return slots + .as_number + .right_remainder + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbDivmod => { + return slots + .as_number + .right_divmod + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbPower => { + return slots + .as_number + .right_power + .load() + .map(SlotFunc::NumTernaryRight); + } + Self::NbLshift => { + return slots + .as_number + .right_lshift + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbRshift => { + return slots + .as_number + .right_rshift + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbAnd => { + return slots + .as_number + .right_and + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbXor => { + return slots + .as_number + .right_xor + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbOr => { + return slots + .as_number + .right_or + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbFloorDivide => { + return slots + .as_number + .right_floor_divide + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbTrueDivide => { + return slots + .as_number + .right_true_divide + .load() + .map(SlotFunc::NumBinaryRight); + } + Self::NbMatrixMultiply => { + return slots + .as_number + .right_matrix_multiply + .load() + .map(SlotFunc::NumBinaryRight); + } + _ => {} + } + } + // For comparison operations, use the appropriate PyComparisonOp + if let Self::TpRichcompare = self + && let Some(cmp_op) = op.and_then(|o| o.as_compare_op()) + { + return slots + .richcompare + .load() + .map(|f| SlotFunc::RichCompare(f, cmp_op)); + } + // Fall back to existing get_slot_func for left/other operations + self.get_slot_func(slots) + } +} + +/// Find all slot definitions with a given name +pub fn find_slot_defs_by_name(name: &str) -> impl Iterator<Item = &'static SlotDef> { + SLOT_DEFS.iter().filter(move |def| def.name == name) +} + +/// Total number of slot definitions +pub const SLOT_DEFS_COUNT: usize = SLOT_DEFS.len(); + +/// All slot definitions +pub static SLOT_DEFS: &[SlotDef] = &[ + // Type slots (tp_*) + SlotDef { + name: "__init__", + accessor: SlotAccessor::TpInit, + op: None, + doc: "Initialize self. See help(type(self)) for accurate signature.", + }, + SlotDef { + name: "__new__", + accessor: SlotAccessor::TpNew, + op: None, + doc: "Create and return a new object. See help(type) for accurate signature.", + }, + SlotDef { + name: "__del__", + accessor: SlotAccessor::TpDel, + op: None, + doc: "Called when the instance is about to be destroyed.", + }, + SlotDef { + name: "__repr__", + accessor: SlotAccessor::TpRepr, + op: None, + doc: "Return repr(self).", + }, + SlotDef { + name: "__str__", + accessor: SlotAccessor::TpStr, + op: None, + doc: "Return str(self).", + }, + SlotDef { + name: "__hash__", + accessor: SlotAccessor::TpHash, + op: None, + doc: "Return hash(self).", + }, + SlotDef { + name: "__call__", + accessor: SlotAccessor::TpCall, + op: None, + doc: "Call self as a function.", + }, + SlotDef { + name: "__iter__", + accessor: SlotAccessor::TpIter, + op: None, + doc: "Implement iter(self).", + }, + SlotDef { + name: "__next__", + accessor: SlotAccessor::TpIternext, + op: None, + doc: "Implement next(self).", + }, + // Attribute access + SlotDef { + name: "__getattribute__", + accessor: SlotAccessor::TpGetattro, + op: None, + doc: "Return getattr(self, name).", + }, + SlotDef { + name: "__setattr__", + accessor: SlotAccessor::TpSetattro, + op: None, + doc: "Implement setattr(self, name, value).", + }, + SlotDef { + name: "__delattr__", + accessor: SlotAccessor::TpSetattro, + op: Some(SlotOp::Delete), + doc: "Implement delattr(self, name).", + }, + // Rich comparison - all map to TpRichcompare with different op + SlotDef { + name: "__eq__", + accessor: SlotAccessor::TpRichcompare, + op: Some(SlotOp::Eq), + doc: "Return self==value.", + }, + SlotDef { + name: "__ne__", + accessor: SlotAccessor::TpRichcompare, + op: Some(SlotOp::Ne), + doc: "Return self!=value.", + }, + SlotDef { + name: "__lt__", + accessor: SlotAccessor::TpRichcompare, + op: Some(SlotOp::Lt), + doc: "Return self<value.", + }, + SlotDef { + name: "__le__", + accessor: SlotAccessor::TpRichcompare, + op: Some(SlotOp::Le), + doc: "Return self<=value.", + }, + SlotDef { + name: "__gt__", + accessor: SlotAccessor::TpRichcompare, + op: Some(SlotOp::Gt), + doc: "Return self>value.", + }, + SlotDef { + name: "__ge__", + accessor: SlotAccessor::TpRichcompare, + op: Some(SlotOp::Ge), + doc: "Return self>=value.", + }, + // Descriptor protocol + SlotDef { + name: "__get__", + accessor: SlotAccessor::TpDescrGet, + op: None, + doc: "Return an attribute of instance, which is of type owner.", + }, + SlotDef { + name: "__set__", + accessor: SlotAccessor::TpDescrSet, + op: None, + doc: "Set an attribute of instance to value.", + }, + SlotDef { + name: "__delete__", + accessor: SlotAccessor::TpDescrSet, + op: Some(SlotOp::Delete), + doc: "Delete an attribute of instance.", + }, + // Sequence protocol (sq_*) + SlotDef { + name: "__len__", + accessor: SlotAccessor::SqLength, + op: None, + doc: "Return len(self).", + }, + SlotDef { + name: "__getitem__", + accessor: SlotAccessor::SqItem, + op: None, + doc: "Return self[key].", + }, + SlotDef { + name: "__setitem__", + accessor: SlotAccessor::SqAssItem, + op: None, + doc: "Set self[key] to value.", + }, + SlotDef { + name: "__delitem__", + accessor: SlotAccessor::SqAssItem, + op: None, + doc: "Delete self[key].", + }, + SlotDef { + name: "__contains__", + accessor: SlotAccessor::SqContains, + op: None, + doc: "Return key in self.", + }, + // Mapping protocol (mp_*) + SlotDef { + name: "__len__", + accessor: SlotAccessor::MpLength, + op: None, + doc: "Return len(self).", + }, + SlotDef { + name: "__getitem__", + accessor: SlotAccessor::MpSubscript, + op: None, + doc: "Return self[key].", + }, + SlotDef { + name: "__setitem__", + accessor: SlotAccessor::MpAssSubscript, + op: None, + doc: "Set self[key] to value.", + }, + SlotDef { + name: "__delitem__", + accessor: SlotAccessor::MpAssSubscript, + op: None, + doc: "Delete self[key].", + }, + // Number protocol - binary ops with left/right variants + SlotDef { + name: "__add__", + accessor: SlotAccessor::NbAdd, + op: Some(SlotOp::Left), + doc: "Return self+value.", + }, + SlotDef { + name: "__radd__", + accessor: SlotAccessor::NbAdd, + op: Some(SlotOp::Right), + doc: "Return value+self.", + }, + SlotDef { + name: "__iadd__", + accessor: SlotAccessor::NbInplaceAdd, + op: None, + doc: "Implement self+=value.", + }, + SlotDef { + name: "__sub__", + accessor: SlotAccessor::NbSubtract, + op: Some(SlotOp::Left), + doc: "Return self-value.", + }, + SlotDef { + name: "__rsub__", + accessor: SlotAccessor::NbSubtract, + op: Some(SlotOp::Right), + doc: "Return value-self.", + }, + SlotDef { + name: "__isub__", + accessor: SlotAccessor::NbInplaceSubtract, + op: None, + doc: "Implement self-=value.", + }, + SlotDef { + name: "__mul__", + accessor: SlotAccessor::NbMultiply, + op: Some(SlotOp::Left), + doc: "Return self*value.", + }, + SlotDef { + name: "__rmul__", + accessor: SlotAccessor::NbMultiply, + op: Some(SlotOp::Right), + doc: "Return value*self.", + }, + SlotDef { + name: "__imul__", + accessor: SlotAccessor::NbInplaceMultiply, + op: None, + doc: "Implement self*=value.", + }, + SlotDef { + name: "__mod__", + accessor: SlotAccessor::NbRemainder, + op: Some(SlotOp::Left), + doc: "Return self%value.", + }, + SlotDef { + name: "__rmod__", + accessor: SlotAccessor::NbRemainder, + op: Some(SlotOp::Right), + doc: "Return value%self.", + }, + SlotDef { + name: "__imod__", + accessor: SlotAccessor::NbInplaceRemainder, + op: None, + doc: "Implement self%=value.", + }, + SlotDef { + name: "__divmod__", + accessor: SlotAccessor::NbDivmod, + op: Some(SlotOp::Left), + doc: "Return divmod(self, value).", + }, + SlotDef { + name: "__rdivmod__", + accessor: SlotAccessor::NbDivmod, + op: Some(SlotOp::Right), + doc: "Return divmod(value, self).", + }, + SlotDef { + name: "__pow__", + accessor: SlotAccessor::NbPower, + op: Some(SlotOp::Left), + doc: "Return pow(self, value, mod).", + }, + SlotDef { + name: "__rpow__", + accessor: SlotAccessor::NbPower, + op: Some(SlotOp::Right), + doc: "Return pow(value, self, mod).", + }, + SlotDef { + name: "__ipow__", + accessor: SlotAccessor::NbInplacePower, + op: None, + doc: "Implement self**=value.", + }, + SlotDef { + name: "__lshift__", + accessor: SlotAccessor::NbLshift, + op: Some(SlotOp::Left), + doc: "Return self<<value.", + }, + SlotDef { + name: "__rlshift__", + accessor: SlotAccessor::NbLshift, + op: Some(SlotOp::Right), + doc: "Return value<<self.", + }, + SlotDef { + name: "__ilshift__", + accessor: SlotAccessor::NbInplaceLshift, + op: None, + doc: "Implement self<<=value.", + }, + SlotDef { + name: "__rshift__", + accessor: SlotAccessor::NbRshift, + op: Some(SlotOp::Left), + doc: "Return self>>value.", + }, + SlotDef { + name: "__rrshift__", + accessor: SlotAccessor::NbRshift, + op: Some(SlotOp::Right), + doc: "Return value>>self.", + }, + SlotDef { + name: "__irshift__", + accessor: SlotAccessor::NbInplaceRshift, + op: None, + doc: "Implement self>>=value.", + }, + SlotDef { + name: "__and__", + accessor: SlotAccessor::NbAnd, + op: Some(SlotOp::Left), + doc: "Return self&value.", + }, + SlotDef { + name: "__rand__", + accessor: SlotAccessor::NbAnd, + op: Some(SlotOp::Right), + doc: "Return value&self.", + }, + SlotDef { + name: "__iand__", + accessor: SlotAccessor::NbInplaceAnd, + op: None, + doc: "Implement self&=value.", + }, + SlotDef { + name: "__xor__", + accessor: SlotAccessor::NbXor, + op: Some(SlotOp::Left), + doc: "Return self^value.", + }, + SlotDef { + name: "__rxor__", + accessor: SlotAccessor::NbXor, + op: Some(SlotOp::Right), + doc: "Return value^self.", + }, + SlotDef { + name: "__ixor__", + accessor: SlotAccessor::NbInplaceXor, + op: None, + doc: "Implement self^=value.", + }, + SlotDef { + name: "__or__", + accessor: SlotAccessor::NbOr, + op: Some(SlotOp::Left), + doc: "Return self|value.", + }, + SlotDef { + name: "__ror__", + accessor: SlotAccessor::NbOr, + op: Some(SlotOp::Right), + doc: "Return value|self.", + }, + SlotDef { + name: "__ior__", + accessor: SlotAccessor::NbInplaceOr, + op: None, + doc: "Implement self|=value.", + }, + SlotDef { + name: "__floordiv__", + accessor: SlotAccessor::NbFloorDivide, + op: Some(SlotOp::Left), + doc: "Return self//value.", + }, + SlotDef { + name: "__rfloordiv__", + accessor: SlotAccessor::NbFloorDivide, + op: Some(SlotOp::Right), + doc: "Return value//self.", + }, + SlotDef { + name: "__ifloordiv__", + accessor: SlotAccessor::NbInplaceFloorDivide, + op: None, + doc: "Implement self//=value.", + }, + SlotDef { + name: "__truediv__", + accessor: SlotAccessor::NbTrueDivide, + op: Some(SlotOp::Left), + doc: "Return self/value.", + }, + SlotDef { + name: "__rtruediv__", + accessor: SlotAccessor::NbTrueDivide, + op: Some(SlotOp::Right), + doc: "Return value/self.", + }, + SlotDef { + name: "__itruediv__", + accessor: SlotAccessor::NbInplaceTrueDivide, + op: None, + doc: "Implement self/=value.", + }, + SlotDef { + name: "__matmul__", + accessor: SlotAccessor::NbMatrixMultiply, + op: Some(SlotOp::Left), + doc: "Return self@value.", + }, + SlotDef { + name: "__rmatmul__", + accessor: SlotAccessor::NbMatrixMultiply, + op: Some(SlotOp::Right), + doc: "Return value@self.", + }, + SlotDef { + name: "__imatmul__", + accessor: SlotAccessor::NbInplaceMatrixMultiply, + op: None, + doc: "Implement self@=value.", + }, + // Number unary operations + SlotDef { + name: "__neg__", + accessor: SlotAccessor::NbNegative, + op: None, + doc: "Return -self.", + }, + SlotDef { + name: "__pos__", + accessor: SlotAccessor::NbPositive, + op: None, + doc: "Return +self.", + }, + SlotDef { + name: "__abs__", + accessor: SlotAccessor::NbAbsolute, + op: None, + doc: "Return abs(self).", + }, + SlotDef { + name: "__invert__", + accessor: SlotAccessor::NbInvert, + op: None, + doc: "Return ~self.", + }, + SlotDef { + name: "__bool__", + accessor: SlotAccessor::NbBool, + op: None, + doc: "Return self != 0.", + }, + SlotDef { + name: "__int__", + accessor: SlotAccessor::NbInt, + op: None, + doc: "Return int(self).", + }, + SlotDef { + name: "__float__", + accessor: SlotAccessor::NbFloat, + op: None, + doc: "Return float(self).", + }, + SlotDef { + name: "__index__", + accessor: SlotAccessor::NbIndex, + op: None, + doc: "Return self converted to an integer, if self is suitable for use as an index into a list.", + }, + // Sequence inplace operations (also map to number slots for some types) + SlotDef { + name: "__add__", + accessor: SlotAccessor::SqConcat, + op: None, + doc: "Return self+value.", + }, + SlotDef { + name: "__mul__", + accessor: SlotAccessor::SqRepeat, + op: None, + doc: "Return self*value.", + }, + SlotDef { + name: "__iadd__", + accessor: SlotAccessor::SqInplaceConcat, + op: None, + doc: "Implement self+=value.", + }, + SlotDef { + name: "__imul__", + accessor: SlotAccessor::SqInplaceRepeat, + op: None, + doc: "Implement self*=value.", + }, +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_find_by_name() { + // __len__ appears in both sequence and mapping + let len_defs: Vec<_> = find_slot_defs_by_name("__len__").collect(); + assert_eq!(len_defs.len(), 2); + + // __init__ appears once + let init_defs: Vec<_> = find_slot_defs_by_name("__init__").collect(); + assert_eq!(init_defs.len(), 1); + + // __add__ appears in number (left/right) and sequence + let add_defs: Vec<_> = find_slot_defs_by_name("__add__").collect(); + assert_eq!(add_defs.len(), 2); // NbAdd(Left) and SqConcat + } + + #[test] + fn test_slot_op() { + // Test comparison ops + assert_eq!(SlotOp::Lt.as_compare_op(), Some(PyComparisonOp::Lt)); + assert_eq!(SlotOp::Eq.as_compare_op(), Some(PyComparisonOp::Eq)); + assert_eq!(SlotOp::Left.as_compare_op(), None); + + // Test right check + assert!(SlotOp::Right.is_right()); + assert!(!SlotOp::Left.is_right()); + } +} diff --git a/crates/vm/src/types/zoo.rs b/crates/vm/src/types/zoo.rs index 9d164241676..c4aa7258c07 100644 --- a/crates/vm/src/types/zoo.rs +++ b/crates/vm/src/types/zoo.rs @@ -189,7 +189,7 @@ impl TypeZoo { generic_alias_type: genericalias::PyGenericAlias::init_builtin_type(), union_type: union_::PyUnion::init_builtin_type(), member_descriptor_type: descriptor::PyMemberDescriptor::init_builtin_type(), - wrapper_descriptor_type: descriptor::PySlotWrapper::init_builtin_type(), + wrapper_descriptor_type: descriptor::PyWrapper::init_builtin_type(), method_wrapper_type: descriptor::PyMethodWrapper::init_builtin_type(), method_def: crate::function::HeapMethodDef::init_builtin_type(), From ee747134b5fd68c079ee788fdf06f2ffcebdae0d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 12:04:33 +0900 Subject: [PATCH 605/819] signal timer (#6535) * fix waitpid * signal timer --- Lib/test/test_signal.py | 12 ---- crates/vm/src/stdlib/posix.rs | 14 +++- crates/vm/src/stdlib/signal.rs | 116 +++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 58b3e55d321..cb744b41e8b 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -127,8 +127,6 @@ def test_strsignal(self): self.assertIn("Terminated", signal.strsignal(signal.SIGTERM)) self.assertIn("Hangup", signal.strsignal(signal.SIGHUP)) - # TODO: RUSTPYTHON - @unittest.expectedFailure # Issue 3864, unknown if this affects earlier versions of freebsd also def test_interprocess_signal(self): dirname = os.path.dirname(__file__) @@ -820,8 +818,6 @@ def sig_prof(self, *args): self.hndl_called = True signal.setitimer(signal.ITIMER_PROF, 0) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_itimer_exc(self): # XXX I'm assuming -1 is an invalid itimer, but maybe some platform # defines it ? @@ -831,16 +827,12 @@ def test_itimer_exc(self): self.assertRaises(signal.ItimerError, signal.setitimer, signal.ITIMER_REAL, -1) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_itimer_real(self): self.itimer = signal.ITIMER_REAL signal.setitimer(self.itimer, 1.0) signal.pause() self.assertEqual(self.hndl_called, True) - # TODO: RUSTPYTHON - @unittest.expectedFailure # Issue 3864, unknown if this affects earlier versions of freebsd also @unittest.skipIf(sys.platform in ('netbsd5',), 'itimer not reliable (does not mix well with threading) on some BSDs.') @@ -861,8 +853,6 @@ def test_itimer_virtual(self): # and the handler should have been called self.assertEqual(self.hndl_called, True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_itimer_prof(self): self.itimer = signal.ITIMER_PROF signal.signal(signal.SIGPROF, self.sig_prof) @@ -880,8 +870,6 @@ def test_itimer_prof(self): # and the handler should have been called self.assertEqual(self.hndl_called, True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_setitimer_tiny(self): # bpo-30807: C setitimer() takes a microsecond-resolution interval. # Check that float -> timeval conversion doesn't round diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 15c8745ded4..b903e41889d 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -1738,9 +1738,17 @@ pub mod module { #[pyfunction] fn waitpid(pid: libc::pid_t, opt: i32, vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> { let mut status = 0; - let pid = unsafe { libc::waitpid(pid, &mut status, opt) }; - let pid = nix::Error::result(pid).map_err(|err| err.into_pyexception(vm))?; - Ok((pid, status)) + loop { + let res = unsafe { libc::waitpid(pid, &mut status, opt) }; + if res == -1 { + if nix::Error::last_raw() == libc::EINTR { + vm.check_signals()?; + continue; + } + return Err(nix::Error::last().into_pyexception(vm)); + } + return Ok((res, status)); + } } #[pyfunction] diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index d34a681c2da..810ffabefe6 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -19,6 +19,11 @@ pub(crate) mod _signal { convert::{IntoPyException, TryFromBorrowedObject}, }; use crate::{PyObjectRef, PyResult, VirtualMachine, signal}; + #[cfg(unix)] + use crate::{ + builtins::PyTypeRef, + function::{ArgIntoFloat, OptionalArg}, + }; use std::sync::atomic::{self, Ordering}; #[cfg(any(unix, windows))] @@ -89,6 +94,18 @@ pub(crate) mod _signal { fn siginterrupt(sig: i32, flag: i32) -> i32; } + #[cfg(any(target_os = "linux", target_os = "android"))] + mod ffi { + unsafe extern "C" { + pub fn getitimer(which: libc::c_int, curr_value: *mut libc::itimerval) -> libc::c_int; + pub fn setitimer( + which: libc::c_int, + new_value: *const libc::itimerval, + old_value: *mut libc::itimerval, + ) -> libc::c_int; + } + } + #[pyattr] use crate::signal::NSIG; @@ -114,6 +131,31 @@ pub(crate) mod _signal { #[pyattr] use libc::{SIGPWR, SIGSTKFLT}; + // Interval timer constants + #[cfg(all(unix, not(target_os = "android")))] + #[pyattr] + use libc::{ITIMER_PROF, ITIMER_REAL, ITIMER_VIRTUAL}; + + #[cfg(target_os = "android")] + #[pyattr] + const ITIMER_REAL: libc::c_int = 0; + #[cfg(target_os = "android")] + #[pyattr] + const ITIMER_VIRTUAL: libc::c_int = 1; + #[cfg(target_os = "android")] + #[pyattr] + const ITIMER_PROF: libc::c_int = 2; + + #[cfg(unix)] + #[pyattr(name = "ItimerError", once)] + fn itimer_error(vm: &VirtualMachine) -> PyTypeRef { + vm.ctx.new_exception_type( + "signal", + "ItimerError", + Some(vec![vm.ctx.exceptions.os_error.to_owned()]), + ) + } + #[cfg(any(unix, windows))] pub(super) fn init_signal_handlers( module: &Py<crate::builtins::PyModule>, @@ -216,6 +258,80 @@ pub(crate) mod _signal { prev_time.unwrap_or(0) } + #[cfg(unix)] + #[pyfunction] + fn pause(vm: &VirtualMachine) -> PyResult<()> { + unsafe { libc::pause() }; + signal::check_signals(vm)?; + Ok(()) + } + + #[cfg(unix)] + fn timeval_to_double(tv: &libc::timeval) -> f64 { + tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0) + } + + #[cfg(unix)] + fn double_to_timeval(val: f64) -> libc::timeval { + libc::timeval { + tv_sec: val.trunc() as _, + tv_usec: ((val.fract()) * 1_000_000.0) as _, + } + } + + #[cfg(unix)] + fn itimerval_to_tuple(it: &libc::itimerval) -> (f64, f64) { + ( + timeval_to_double(&it.it_value), + timeval_to_double(&it.it_interval), + ) + } + + #[cfg(unix)] + #[pyfunction] + fn setitimer( + which: i32, + seconds: ArgIntoFloat, + interval: OptionalArg<ArgIntoFloat>, + vm: &VirtualMachine, + ) -> PyResult<(f64, f64)> { + let seconds: f64 = seconds.into(); + let interval: f64 = interval.map(|v| v.into()).unwrap_or(0.0); + let new = libc::itimerval { + it_value: double_to_timeval(seconds), + it_interval: double_to_timeval(interval), + }; + let mut old = std::mem::MaybeUninit::<libc::itimerval>::uninit(); + #[cfg(any(target_os = "linux", target_os = "android"))] + let ret = unsafe { ffi::setitimer(which, &new, old.as_mut_ptr()) }; + #[cfg(not(any(target_os = "linux", target_os = "android")))] + let ret = unsafe { libc::setitimer(which, &new, old.as_mut_ptr()) }; + if ret != 0 { + let err = std::io::Error::last_os_error(); + let itimer_error = itimer_error(vm); + return Err(vm.new_exception_msg(itimer_error, err.to_string())); + } + let old = unsafe { old.assume_init() }; + Ok(itimerval_to_tuple(&old)) + } + + #[cfg(unix)] + #[pyfunction] + fn getitimer(which: i32, vm: &VirtualMachine) -> PyResult<(f64, f64)> { + let mut old = std::mem::MaybeUninit::<libc::itimerval>::uninit(); + #[cfg(any(target_os = "linux", target_os = "android"))] + let ret = unsafe { ffi::getitimer(which, old.as_mut_ptr()) }; + #[cfg(not(any(target_os = "linux", target_os = "android")))] + let ret = unsafe { libc::getitimer(which, old.as_mut_ptr()) }; + if ret != 0 { + let err = std::io::Error::last_os_error(); + let itimer_error = itimer_error(vm); + return Err(vm.new_exception_msg(itimer_error, err.to_string())); + } + let old = unsafe { old.assume_init() }; + Ok(itimerval_to_tuple(&old)) + } + #[pyfunction] fn default_int_handler( _signum: PyObjectRef, From f8199d70991ad08e145223dddc97d94489002e5b Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 14:25:10 +0900 Subject: [PATCH 606/819] impl basicsize (#6557) * shape_differs * may_add_dict * basicsize --- Lib/test/test_types.py | 2 -- crates/derive-impl/src/pyclass.rs | 1 + crates/vm/src/builtins/type.rs | 31 +++++++++++++++++-------------- crates/vm/src/object/core.rs | 1 + crates/vm/src/object/mod.rs | 1 + 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 4200cb8ee5e..b57fdf35fb6 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -591,8 +591,6 @@ def test_format_spec_errors(self): for code in 'xXobns': self.assertRaises(ValueError, format, 0, ',' + code) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_internal_sizes(self): self.assertGreater(object.__basicsize__, 0) self.assertGreater(tuple.__itemsize__, 0) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 82057d40f9e..55f9c769940 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -455,6 +455,7 @@ fn generate_class_def( }); // If repr(transparent) with a base, the type has the same memory layout as base, // so basicsize should be 0 (no additional space beyond the base type) + // Otherwise, basicsize = sizeof(payload). The header size is added in __basicsize__ getter. let basicsize = if is_repr_transparent && base.is_some() { quote!(0) } else { diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index f74095a8c8a..e807d3f4f8e 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -782,8 +782,8 @@ impl PyType { } #[pygetset] - const fn __basicsize__(&self) -> usize { - self.slots.basicsize + fn __basicsize__(&self) -> usize { + crate::object::SIZEOF_PYOBJECT_HEAD + self.slots.basicsize } #[pygetset] @@ -1338,10 +1338,16 @@ impl Constructor for PyType { let member_count: usize = base_member_count + heaptype_member_count; let mut flags = PyTypeFlags::heap_type_flags(); + + // Check if we may add dict + // We can only add a dict if the primary base class doesn't already have one + // In CPython, this checks tp_dictoffset == 0 + let may_add_dict = !base.slots.flags.has_feature(PyTypeFlags::HAS_DICT); + // Add HAS_DICT and MANAGED_DICT if: - // 1. __slots__ is not defined, OR + // 1. __slots__ is not defined AND base doesn't have dict, OR // 2. __dict__ is in __slots__ - if heaptype_slots.is_none() || add_dict { + if (heaptype_slots.is_none() && may_add_dict) || add_dict { flags |= PyTypeFlags::HAS_DICT | PyTypeFlags::MANAGED_DICT; } @@ -1349,6 +1355,7 @@ impl Constructor for PyType { let slots = PyTypeSlots { flags, member_count, + itemsize: base.slots.itemsize, ..PyTypeSlots::heap_default() }; let heaptype_ext = HeapTypeExt { @@ -2009,6 +2016,11 @@ fn calculate_meta_class( Ok(winner) } +/// Returns true if the two types have different instance layouts. +fn shape_differs(t1: &Py<PyType>, t2: &Py<PyType>) -> bool { + t1.__basicsize__() != t2.__basicsize__() || t1.slots.itemsize != t2.slots.itemsize +} + fn solid_base<'a>(typ: &'a Py<PyType>, vm: &VirtualMachine) -> &'a Py<PyType> { let base = if let Some(base) = &typ.base { solid_base(base, vm) @@ -2016,16 +2028,7 @@ fn solid_base<'a>(typ: &'a Py<PyType>, vm: &VirtualMachine) -> &'a Py<PyType> { vm.ctx.types.object_type }; - // Check for extra instance variables (CPython's extra_ivars) - let t_size = typ.__basicsize__(); - let b_size = base.__basicsize__(); - let t_itemsize = typ.slots.itemsize; - let b_itemsize = base.slots.itemsize; - - // Has extra ivars if: sizes differ AND (has items OR t_size > b_size) - let has_extra_ivars = t_size != b_size && (t_itemsize > 0 || b_itemsize > 0 || t_size > b_size); - - if has_extra_ivars { typ } else { base } + if shape_differs(typ, base) { typ } else { base } } fn best_base<'a>(bases: &'a [PyTypeRef], vm: &VirtualMachine) -> PyResult<&'a Py<PyType>> { diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index a092d7097be..d52a33884ce 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -114,6 +114,7 @@ pub(super) struct PyInner<T> { pub(super) payload: T, } +pub(crate) const SIZEOF_PYOBJECT_HEAD: usize = std::mem::size_of::<PyInner<()>>(); impl<T: fmt::Debug> fmt::Debug for PyInner<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/vm/src/object/mod.rs b/crates/vm/src/object/mod.rs index 034523afe5a..a279201a0b0 100644 --- a/crates/vm/src/object/mod.rs +++ b/crates/vm/src/object/mod.rs @@ -7,4 +7,5 @@ mod traverse_object; pub use self::core::*; pub use self::ext::*; pub use self::payload::*; +pub(crate) use core::SIZEOF_PYOBJECT_HEAD; pub use traverse::{MaybeTraverse, Traverse, TraverseFn}; From 04a4d1e02facaad1675b2811e33541627f1cd7e1 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 14:48:27 +0900 Subject: [PATCH 607/819] Prevent array.tofile re-entrant deadlock and add regression snippet (#6559) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/stdlib/src/array.rs | 6 ++++-- extra_tests/snippets/stdlib_array.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/crates/stdlib/src/array.rs b/crates/stdlib/src/array.rs index 49ca4a89037..b51bc02d3fb 100644 --- a/crates/stdlib/src/array.rs +++ b/crates/stdlib/src/array.rs @@ -938,8 +938,10 @@ mod array { /* XXX Make the block size settable */ const BLOCKSIZE: usize = 64 * 1024; - let bytes = self.read(); - let bytes = bytes.get_bytes(); + let bytes = { + let bytes = self.read(); + bytes.get_bytes().to_vec() + }; for b in bytes.chunks(BLOCKSIZE) { let b = PyBytes::from(b.to_vec()).into_ref(&vm.ctx); diff --git a/extra_tests/snippets/stdlib_array.py b/extra_tests/snippets/stdlib_array.py index 34eac949168..ed2a8f22369 100644 --- a/extra_tests/snippets/stdlib_array.py +++ b/extra_tests/snippets/stdlib_array.py @@ -126,3 +126,20 @@ def test_array_frombytes(): a = array("B", [0]) assert a.__contains__(0) assert not a.__contains__(1) + + +class _ReenteringWriter: + def __init__(self, arr): + self.arr = arr + self.reentered = False + + def write(self, chunk): + if not self.reentered: + self.reentered = True + self.arr.append(0) + return len(chunk) + + +arr = array("b", range(128)) +arr.tofile(_ReenteringWriter(arr)) +assert len(arr) == 129 From a37f4ec38e9edb12f0a214d666798ea3a5eb4dfe Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 15:49:10 +0900 Subject: [PATCH 608/819] upgrade shutil (#6558) * Update shutil from v3.13.11 * fix os.remove * fix os.fchmod * fix is_file --- Lib/shutil.py | 288 ++++----- Lib/test/test_shutil.py | 935 +++++++++++++++++---------- crates/vm/src/stdlib/nt.rs | 128 +++- crates/vm/src/stdlib/os.rs | 56 +- crates/vm/src/stdlib/posix.rs | 7 + crates/vm/src/stdlib/posix_compat.rs | 10 +- 6 files changed, 881 insertions(+), 543 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 6803ee3ce6e..7df972012c7 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -10,7 +10,6 @@ import fnmatch import collections import errno -import warnings try: import zlib @@ -48,7 +47,8 @@ COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024 # This should never be removed, see rationale in: # https://bugs.python.org/issue43743#msg393429 -_USE_CP_SENDFILE = hasattr(os, "sendfile") and sys.platform.startswith("linux") +_USE_CP_SENDFILE = (hasattr(os, "sendfile") + and sys.platform.startswith(("linux", "android"))) _HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") # macOS # CMD defaults in Windows 10 @@ -153,16 +153,14 @@ def _fastcopy_sendfile(fsrc, fdst): err.filename = fsrc.name err.filename2 = fdst.name - # XXX RUSTPYTHON TODO: consistent OSError.errno - if hasattr(err, "errno") and err.errno == errno.ENOTSOCK: + if err.errno == errno.ENOTSOCK: # sendfile() on this platform (probably Linux < 2.6.33) # does not support copies between regular files (only # sockets). _USE_CP_SENDFILE = False raise _GiveupOnFastCopy(err) - # XXX RUSTPYTHON TODO: consistent OSError.errno - if hasattr(err, "errno") and err.errno == errno.ENOSPC: # filesystem is full + if err.errno == errno.ENOSPC: # filesystem is full raise err from None # Give up on first call and if no data was copied. @@ -304,16 +302,17 @@ def copymode(src, dst, *, follow_symlinks=True): sys.audit("shutil.copymode", src, dst) if not follow_symlinks and _islink(src) and os.path.islink(dst): - if os.name == 'nt': - stat_func, chmod_func = os.lstat, os.chmod - elif hasattr(os, 'lchmod'): + if hasattr(os, 'lchmod'): stat_func, chmod_func = os.lstat, os.lchmod else: return else: + stat_func = _stat if os.name == 'nt' and os.path.islink(dst): - dst = os.path.realpath(dst, strict=True) - stat_func, chmod_func = _stat, os.chmod + def chmod_func(*args): + os.chmod(*args, follow_symlinks=True) + else: + chmod_func = os.chmod st = stat_func(src) chmod_func(dst, stat.S_IMODE(st.st_mode)) @@ -388,16 +387,8 @@ def lookup(name): # We must copy extended attributes before the file is (potentially) # chmod()'ed read-only, otherwise setxattr() will error with -EACCES. _copyxattr(src, dst, follow_symlinks=follow) - _chmod = lookup("chmod") - if os.name == 'nt': - if follow: - if os.path.islink(dst): - dst = os.path.realpath(dst, strict=True) - else: - def _chmod(*args, **kwargs): - os.chmod(*args) try: - _chmod(dst, mode, follow_symlinks=follow) + lookup("chmod")(dst, mode, follow_symlinks=follow) except NotImplementedError: # if we got a NotImplementedError, it's because # * follow_symlinks=False, @@ -565,7 +556,7 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, If the optional symlinks flag is true, symbolic links in the source tree result in symbolic links in the destination tree; if it is false, the contents of the files pointed to by symbolic - links are copied. If the file pointed by the symlink doesn't + links are copied. If the file pointed to by the symlink doesn't exist, an exception will be added in the list of errors raised in an Error exception at the end of the copy process. @@ -605,118 +596,115 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, dirs_exist_ok=dirs_exist_ok) if hasattr(os.stat_result, 'st_file_attributes'): - def _rmtree_islink(path): - try: - st = os.lstat(path) - return (stat.S_ISLNK(st.st_mode) or - (st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT - and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT)) - except OSError: - return False + def _rmtree_islink(st): + return (stat.S_ISLNK(st.st_mode) or + (st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT + and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT)) else: - def _rmtree_islink(path): - return os.path.islink(path) + def _rmtree_islink(st): + return stat.S_ISLNK(st.st_mode) # version vulnerable to race conditions def _rmtree_unsafe(path, onexc): - try: - with os.scandir(path) as scandir_it: - entries = list(scandir_it) - except OSError as err: - onexc(os.scandir, path, err) - entries = [] - for entry in entries: - fullname = entry.path - try: - is_dir = entry.is_dir(follow_symlinks=False) - except OSError: - is_dir = False - - if is_dir and not entry.is_junction(): + def onerror(err): + if not isinstance(err, FileNotFoundError): + onexc(os.scandir, err.filename, err) + results = os.walk(path, topdown=False, onerror=onerror, followlinks=os._walk_symlinks_as_files) + for dirpath, dirnames, filenames in results: + for name in dirnames: + fullname = os.path.join(dirpath, name) try: - if entry.is_symlink(): - # This can only happen if someone replaces - # a directory with a symlink after the call to - # os.scandir or entry.is_dir above. - raise OSError("Cannot call rmtree on a symbolic link") - except OSError as err: - onexc(os.path.islink, fullname, err) + os.rmdir(fullname) + except FileNotFoundError: continue - _rmtree_unsafe(fullname, onexc) - else: + except OSError as err: + onexc(os.rmdir, fullname, err) + for name in filenames: + fullname = os.path.join(dirpath, name) try: os.unlink(fullname) + except FileNotFoundError: + continue except OSError as err: onexc(os.unlink, fullname, err) try: os.rmdir(path) + except FileNotFoundError: + pass except OSError as err: onexc(os.rmdir, path, err) # Version using fd-based APIs to protect against races -def _rmtree_safe_fd(topfd, path, onexc): +def _rmtree_safe_fd(stack, onexc): + # Each stack item has four elements: + # * func: The first operation to perform: os.lstat, os.close or os.rmdir. + # Walking a directory starts with an os.lstat() to detect symlinks; in + # this case, func is updated before subsequent operations and passed to + # onexc() if an error occurs. + # * dirfd: Open file descriptor, or None if we're processing the top-level + # directory given to rmtree() and the user didn't supply dir_fd. + # * path: Path of file to operate upon. This is passed to onexc() if an + # error occurs. + # * orig_entry: os.DirEntry, or None if we're processing the top-level + # directory given to rmtree(). We used the cached stat() of the entry to + # save a call to os.lstat() when walking subdirectories. + func, dirfd, path, orig_entry = stack.pop() + name = path if orig_entry is None else orig_entry.name try: + if func is os.close: + os.close(dirfd) + return + if func is os.rmdir: + os.rmdir(name, dir_fd=dirfd) + return + + # Note: To guard against symlink races, we use the standard + # lstat()/open()/fstat() trick. + assert func is os.lstat + if orig_entry is None: + orig_st = os.lstat(name, dir_fd=dirfd) + else: + orig_st = orig_entry.stat(follow_symlinks=False) + + func = os.open # For error reporting. + topfd = os.open(name, os.O_RDONLY | os.O_NONBLOCK, dir_fd=dirfd) + + func = os.path.islink # For error reporting. + try: + if not os.path.samestat(orig_st, os.fstat(topfd)): + # Symlinks to directories are forbidden, see GH-46010. + raise OSError("Cannot call rmtree on a symbolic link") + stack.append((os.rmdir, dirfd, path, orig_entry)) + finally: + stack.append((os.close, topfd, path, orig_entry)) + + func = os.scandir # For error reporting. with os.scandir(topfd) as scandir_it: entries = list(scandir_it) - except OSError as err: - err.filename = path - onexc(os.scandir, path, err) - return - for entry in entries: - fullname = os.path.join(path, entry.name) - try: - is_dir = entry.is_dir(follow_symlinks=False) - except OSError: - is_dir = False - else: - if is_dir: - try: - orig_st = entry.stat(follow_symlinks=False) - is_dir = stat.S_ISDIR(orig_st.st_mode) - except OSError as err: - onexc(os.lstat, fullname, err) - continue - if is_dir: + for entry in entries: + fullname = os.path.join(path, entry.name) try: - dirfd = os.open(entry.name, os.O_RDONLY | os.O_NONBLOCK, dir_fd=topfd) - dirfd_closed = False - except OSError as err: - onexc(os.open, fullname, err) - else: - try: - if os.path.samestat(orig_st, os.fstat(dirfd)): - _rmtree_safe_fd(dirfd, fullname, onexc) - try: - os.close(dirfd) - except OSError as err: - # close() should not be retried after an error. - dirfd_closed = True - onexc(os.close, fullname, err) - dirfd_closed = True - try: - os.rmdir(entry.name, dir_fd=topfd) - except OSError as err: - onexc(os.rmdir, fullname, err) - else: - try: - # This can only happen if someone replaces - # a directory with a symlink after the call to - # os.scandir or stat.S_ISDIR above. - raise OSError("Cannot call rmtree on a symbolic " - "link") - except OSError as err: - onexc(os.path.islink, fullname, err) - finally: - if not dirfd_closed: - try: - os.close(dirfd) - except OSError as err: - onexc(os.close, fullname, err) - else: + if entry.is_dir(follow_symlinks=False): + # Traverse into sub-directory. + stack.append((os.lstat, topfd, fullname, entry)) + continue + except FileNotFoundError: + continue + except OSError: + pass try: os.unlink(entry.name, dir_fd=topfd) + except FileNotFoundError: + continue except OSError as err: onexc(os.unlink, fullname, err) + except FileNotFoundError as err: + if orig_entry is None or func is os.close: + err.filename = path + onexc(func, path, err) + except OSError as err: + err.filename = path + onexc(func, path, err) _use_fd_functions = ({os.open, os.stat, os.unlink, os.rmdir} <= os.supports_dir_fd and @@ -769,41 +757,16 @@ def onexc(*args): # While the unsafe rmtree works fine on bytes, the fd based does not. if isinstance(path, bytes): path = os.fsdecode(path) - # Note: To guard against symlink races, we use the standard - # lstat()/open()/fstat() trick. - try: - orig_st = os.lstat(path, dir_fd=dir_fd) - except Exception as err: - onexc(os.lstat, path, err) - return + stack = [(os.lstat, dir_fd, path, None)] try: - fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK, dir_fd=dir_fd) - fd_closed = False - except Exception as err: - onexc(os.open, path, err) - return - try: - if os.path.samestat(orig_st, os.fstat(fd)): - _rmtree_safe_fd(fd, path, onexc) - try: - os.close(fd) - except OSError as err: - # close() should not be retried after an error. - fd_closed = True - onexc(os.close, path, err) - fd_closed = True - try: - os.rmdir(path, dir_fd=dir_fd) - except OSError as err: - onexc(os.rmdir, path, err) - else: - try: - # symlinks to directories are forbidden, see bug #1669 - raise OSError("Cannot call rmtree on a symbolic link") - except OSError as err: - onexc(os.path.islink, path, err) + while stack: + _rmtree_safe_fd(stack, onexc) finally: - if not fd_closed: + # Close any file descriptors still on the stack. + while stack: + func, fd, path, entry = stack.pop() + if func is not os.close: + continue try: os.close(fd) except OSError as err: @@ -812,7 +775,12 @@ def onexc(*args): if dir_fd is not None: raise NotImplementedError("dir_fd unavailable on this platform") try: - if _rmtree_islink(path): + st = os.lstat(path) + except OSError as err: + onexc(os.lstat, path, err) + return + try: + if _rmtree_islink(st): # symlinks to directories are forbidden, see bug #1669 raise OSError("Cannot call rmtree on a symbolic link") except OSError as err: @@ -1428,11 +1396,18 @@ def disk_usage(path): return _ntuple_diskusage(total, used, free) -def chown(path, user=None, group=None): +def chown(path, user=None, group=None, *, dir_fd=None, follow_symlinks=True): """Change owner user and group of the given path. user and group can be the uid/gid or the user/group names, and in that case, they are converted to their respective uid/gid. + + If dir_fd is set, it should be an open file descriptor to the directory to + be used as the root of *path* if it is relative. + + If follow_symlinks is set to False and the last element of the path is a + symbolic link, chown will modify the link itself and not the file being + referenced by the link. """ sys.audit('shutil.chown', path, user, group) @@ -1458,7 +1433,8 @@ def chown(path, user=None, group=None): if _group is None: raise LookupError("no such group: {!r}".format(group)) - os.chown(path, _user, _group) + os.chown(path, _user, _group, dir_fd=dir_fd, + follow_symlinks=follow_symlinks) def get_terminal_size(fallback=(80, 24)): """Get the size of the terminal window. @@ -1575,21 +1551,21 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None): if sys.platform == "win32": # PATHEXT is necessary to check on Windows. pathext_source = os.getenv("PATHEXT") or _WIN_DEFAULT_PATHEXT - pathext = [ext for ext in pathext_source.split(os.pathsep) if ext] + pathext = pathext_source.split(os.pathsep) + pathext = [ext.rstrip('.') for ext in pathext if ext] if use_bytes: pathext = [os.fsencode(ext) for ext in pathext] - files = ([cmd] + [cmd + ext for ext in pathext]) + files = [cmd + ext for ext in pathext] - # gh-109590. If we are looking for an executable, we need to look - # for a PATHEXT match. The first cmd is the direct match - # (e.g. python.exe instead of python) - # Check that direct match first if and only if the extension is in PATHEXT - # Otherwise check it last - suffix = os.path.splitext(files[0])[1].upper() - if mode & os.X_OK and not any(suffix == ext.upper() for ext in pathext): - files.append(files.pop(0)) + # If X_OK in mode, simulate the cmd.exe behavior: look at direct + # match if and only if the extension is in PATHEXT. + # If X_OK not in mode, simulate the first result of where.exe: + # always look at direct match before a PATHEXT match. + normcmd = cmd.upper() + if not (mode & os.X_OK) or any(normcmd.endswith(ext.upper()) for ext in pathext): + files.insert(0, cmd) else: # On other platforms you don't have things like PATHEXT to tell you # what file suffixes are executable, so just pass on cmd as-is. @@ -1598,7 +1574,7 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None): seen = set() for dir in path: normdir = os.path.normcase(dir) - if not normdir in seen: + if normdir not in seen: seen.add(normdir) for thefile in files: name = os.path.join(dir, thefile) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index ad67583d438..73ad81e871b 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -23,7 +23,6 @@ unregister_unpack_format, get_unpack_formats, SameFileError, _GiveupOnFastCopy) import tarfile -import warnings import zipfile try: import posix @@ -71,18 +70,17 @@ def wrap(*args, **kwargs): os.rename = builtin_rename return wrap -def write_file(path, content, binary=False): +def create_file(path, content=b''): """Write *content* to a file located at *path*. If *path* is a tuple instead of a string, os.path.join will be used to - make a path. If *binary* is true, the file will be opened in binary - mode. + make a path. """ if isinstance(path, tuple): path = os.path.join(*path) - mode = 'wb' if binary else 'w' - encoding = None if binary else "utf-8" - with open(path, mode, encoding=encoding) as fp: + if isinstance(content, str): + content = content.encode() + with open(path, 'xb') as fp: fp.write(content) def write_test_file(path, size): @@ -191,7 +189,7 @@ def test_rmtree_works_on_bytes(self): tmp = self.mkdtemp() victim = os.path.join(tmp, 'killme') os.mkdir(victim) - write_file(os.path.join(victim, 'somefile'), 'foo') + create_file(os.path.join(victim, 'somefile'), 'foo') victim = os.fsencode(victim) self.assertIsInstance(victim, bytes) shutil.rmtree(victim) @@ -244,7 +242,7 @@ def test_rmtree_works_on_symlinks(self): for d in dir1, dir2, dir3: os.mkdir(d) file1 = os.path.join(tmp, 'file1') - write_file(file1, 'foo') + create_file(file1, 'foo') link1 = os.path.join(dir1, 'link1') os.symlink(dir2, link1) link2 = os.path.join(dir1, 'link2') @@ -306,7 +304,7 @@ def test_rmtree_works_on_junctions(self): for d in dir1, dir2, dir3: os.mkdir(d) file1 = os.path.join(tmp, 'file1') - write_file(file1, 'foo') + create_file(file1, 'foo') link1 = os.path.join(dir1, 'link1') _winapi.CreateJunction(dir2, link1) link2 = os.path.join(dir1, 'link2') @@ -319,7 +317,7 @@ def test_rmtree_works_on_junctions(self): self.assertTrue(os.path.exists(dir3)) self.assertTrue(os.path.exists(file1)) - def test_rmtree_errors_onerror(self): + def test_rmtree_errors(self): # filename is guaranteed not to exist filename = tempfile.mktemp(dir=self.mkdtemp()) self.assertRaises(FileNotFoundError, shutil.rmtree, filename) @@ -328,8 +326,8 @@ def test_rmtree_errors_onerror(self): # existing file tmpdir = self.mkdtemp() - write_file((tmpdir, "tstfile"), "") filename = os.path.join(tmpdir, "tstfile") + create_file(filename) with self.assertRaises(NotADirectoryError) as cm: shutil.rmtree(filename) self.assertEqual(cm.exception.filename, filename) @@ -337,6 +335,19 @@ def test_rmtree_errors_onerror(self): # test that ignore_errors option is honored shutil.rmtree(filename, ignore_errors=True) self.assertTrue(os.path.exists(filename)) + + self.assertRaises(TypeError, shutil.rmtree, None) + self.assertRaises(TypeError, shutil.rmtree, None, ignore_errors=True) + exc = TypeError if shutil.rmtree.avoids_symlink_attacks else NotImplementedError + with self.assertRaises(exc): + shutil.rmtree(filename, dir_fd='invalid') + with self.assertRaises(exc): + shutil.rmtree(filename, dir_fd='invalid', ignore_errors=True) + + def test_rmtree_errors_onerror(self): + tmpdir = self.mkdtemp() + filename = os.path.join(tmpdir, "tstfile") + create_file(filename) errors = [] def onerror(*args): errors.append(args) @@ -352,23 +363,9 @@ def onerror(*args): self.assertEqual(errors[1][2][1].filename, filename) def test_rmtree_errors_onexc(self): - # filename is guaranteed not to exist - filename = tempfile.mktemp(dir=self.mkdtemp()) - self.assertRaises(FileNotFoundError, shutil.rmtree, filename) - # test that ignore_errors option is honored - shutil.rmtree(filename, ignore_errors=True) - - # existing file tmpdir = self.mkdtemp() - write_file((tmpdir, "tstfile"), "") filename = os.path.join(tmpdir, "tstfile") - with self.assertRaises(NotADirectoryError) as cm: - shutil.rmtree(filename) - self.assertEqual(cm.exception.filename, filename) - self.assertTrue(os.path.exists(filename)) - # test that ignore_errors option is honored - shutil.rmtree(filename, ignore_errors=True) - self.assertTrue(os.path.exists(filename)) + create_file(filename) errors = [] def onexc(*args): errors.append(args) @@ -550,7 +547,7 @@ def raiser(fn, *args, **kwargs): os.lstat = raiser os.mkdir(TESTFN) - write_file((TESTFN, 'foo'), 'foo') + create_file((TESTFN, 'foo'), 'foo') shutil.rmtree(TESTFN) finally: os.lstat = orig_lstat @@ -623,7 +620,7 @@ def test_rmtree_with_dir_fd(self): self.addCleanup(os.close, dir_fd) os.mkdir(fullname) os.mkdir(os.path.join(fullname, 'subdir')) - write_file(os.path.join(fullname, 'subdir', 'somefile'), 'foo') + create_file(os.path.join(fullname, 'subdir', 'somefile'), 'foo') self.assertTrue(os.path.exists(fullname)) shutil.rmtree(victim, dir_fd=dir_fd) self.assertFalse(os.path.exists(fullname)) @@ -663,7 +660,7 @@ def test_rmtree_on_junction(self): src = os.path.join(TESTFN, 'cheese') dst = os.path.join(TESTFN, 'shop') os.mkdir(src) - open(os.path.join(src, 'spam'), 'wb').close() + create_file(os.path.join(src, 'spam')) _winapi.CreateJunction(src, dst) self.assertRaises(OSError, shutil.rmtree, dst) shutil.rmtree(dst, ignore_errors=True) @@ -687,6 +684,73 @@ def test_rmtree_on_named_pipe(self): shutil.rmtree(TESTFN) self.assertFalse(os.path.exists(TESTFN)) + @unittest.skipIf(sys.platform[:6] == 'cygwin', + "This test can't be run on Cygwin (issue #1071513).") + @os_helper.skip_if_dac_override + @os_helper.skip_unless_working_chmod + def test_rmtree_deleted_race_condition(self): + # bpo-37260 + # + # Test that a file or a directory deleted after it is enumerated + # by scandir() but before unlink() or rmdr() is called doesn't + # generate any errors. + def _onexc(fn, path, exc): + assert fn in (os.rmdir, os.unlink) + if not isinstance(exc, PermissionError): + raise + # Make the parent and the children writeable. + for p, mode in zip(paths, old_modes): + os.chmod(p, mode) + # Remove other dirs except one. + keep = next(p for p in dirs if p != path) + for p in dirs: + if p != keep: + os.rmdir(p) + # Remove other files except one. + keep = next(p for p in files if p != path) + for p in files: + if p != keep: + os.unlink(p) + + os.mkdir(TESTFN) + paths = [TESTFN] + [os.path.join(TESTFN, f'child{i}') + for i in range(6)] + dirs = paths[1::2] + files = paths[2::2] + for path in dirs: + os.mkdir(path) + for path in files: + create_file(path) + + old_modes = [os.stat(path).st_mode for path in paths] + + # Make the parent and the children non-writeable. + new_mode = stat.S_IREAD|stat.S_IEXEC + for path in reversed(paths): + os.chmod(path, new_mode) + + try: + shutil.rmtree(TESTFN, onexc=_onexc) + except: + # Test failed, so cleanup artifacts. + for path, mode in zip(paths, old_modes): + try: + os.chmod(path, mode) + except OSError: + pass + shutil.rmtree(TESTFN) + raise + + def test_rmtree_above_recursion_limit(self): + recursion_limit = 40 + # directory_depth > recursion_limit + directory_depth = recursion_limit + 10 + base = os.path.join(TESTFN, *(['d'] * directory_depth)) + os.makedirs(base) + + with support.infinite_recursion(recursion_limit): + shutil.rmtree(TESTFN) + class TestCopyTree(BaseTest, unittest.TestCase): @@ -695,9 +759,9 @@ def test_copytree_simple(self): dst_dir = os.path.join(self.mkdtemp(), 'destination') self.addCleanup(shutil.rmtree, src_dir) self.addCleanup(shutil.rmtree, os.path.dirname(dst_dir)) - write_file((src_dir, 'test.txt'), '123') + create_file((src_dir, 'test.txt'), '123') os.mkdir(os.path.join(src_dir, 'test_dir')) - write_file((src_dir, 'test_dir', 'test.txt'), '456') + create_file((src_dir, 'test_dir', 'test.txt'), '456') shutil.copytree(src_dir, dst_dir) self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'test.txt'))) @@ -715,11 +779,11 @@ def test_copytree_dirs_exist_ok(self): self.addCleanup(shutil.rmtree, src_dir) self.addCleanup(shutil.rmtree, dst_dir) - write_file((src_dir, 'nonexisting.txt'), '123') + create_file((src_dir, 'nonexisting.txt'), '123') os.mkdir(os.path.join(src_dir, 'existing_dir')) os.mkdir(os.path.join(dst_dir, 'existing_dir')) - write_file((dst_dir, 'existing_dir', 'existing.txt'), 'will be replaced') - write_file((src_dir, 'existing_dir', 'existing.txt'), 'has been replaced') + create_file((dst_dir, 'existing_dir', 'existing.txt'), 'will be replaced') + create_file((src_dir, 'existing_dir', 'existing.txt'), 'has been replaced') shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True) self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'nonexisting.txt'))) @@ -742,7 +806,7 @@ def test_copytree_symlinks(self): sub_dir = os.path.join(src_dir, 'sub') os.mkdir(src_dir) os.mkdir(sub_dir) - write_file((src_dir, 'file.txt'), 'foo') + create_file((src_dir, 'file.txt'), 'foo') src_link = os.path.join(sub_dir, 'link') dst_link = os.path.join(dst_dir, 'sub/link') os.symlink(os.path.join(src_dir, 'file.txt'), @@ -773,16 +837,16 @@ def test_copytree_with_exclude(self): src_dir = self.mkdtemp() try: dst_dir = join(self.mkdtemp(), 'destination') - write_file((src_dir, 'test.txt'), '123') - write_file((src_dir, 'test.tmp'), '123') + create_file((src_dir, 'test.txt'), '123') + create_file((src_dir, 'test.tmp'), '123') os.mkdir(join(src_dir, 'test_dir')) - write_file((src_dir, 'test_dir', 'test.txt'), '456') + create_file((src_dir, 'test_dir', 'test.txt'), '456') os.mkdir(join(src_dir, 'test_dir2')) - write_file((src_dir, 'test_dir2', 'test.txt'), '456') + create_file((src_dir, 'test_dir2', 'test.txt'), '456') os.mkdir(join(src_dir, 'test_dir2', 'subdir')) os.mkdir(join(src_dir, 'test_dir2', 'subdir2')) - write_file((src_dir, 'test_dir2', 'subdir', 'test.txt'), '456') - write_file((src_dir, 'test_dir2', 'subdir2', 'test.py'), '456') + create_file((src_dir, 'test_dir2', 'subdir', 'test.txt'), '456') + create_file((src_dir, 'test_dir2', 'subdir2', 'test.py'), '456') # testing glob-like patterns try: @@ -841,7 +905,7 @@ def test_copytree_arg_types_of_ignore(self): os.mkdir(join(src_dir)) os.mkdir(join(src_dir, 'test_dir')) os.mkdir(os.path.join(src_dir, 'test_dir', 'subdir')) - write_file((src_dir, 'test_dir', 'subdir', 'test.txt'), '456') + create_file((src_dir, 'test_dir', 'subdir', 'test.txt'), '456') invokations = [] @@ -860,7 +924,7 @@ def _ignore(src, names): 'test.txt'))) dst_dir = join(self.mkdtemp(), 'destination') - shutil.copytree(pathlib.Path(src_dir), dst_dir, ignore=_ignore) + shutil.copytree(FakePath(src_dir), dst_dir, ignore=_ignore) self.assertTrue(exists(join(dst_dir, 'test_dir', 'subdir', 'test.txt'))) @@ -881,9 +945,9 @@ def test_copytree_retains_permissions(self): self.addCleanup(shutil.rmtree, tmp_dir) os.chmod(src_dir, 0o777) - write_file((src_dir, 'permissive.txt'), '123') + create_file((src_dir, 'permissive.txt'), '123') os.chmod(os.path.join(src_dir, 'permissive.txt'), 0o777) - write_file((src_dir, 'restrictive.txt'), '456') + create_file((src_dir, 'restrictive.txt'), '456') os.chmod(os.path.join(src_dir, 'restrictive.txt'), 0o600) restrictive_subdir = tempfile.mkdtemp(dir=src_dir) self.addCleanup(os_helper.rmtree, restrictive_subdir) @@ -926,8 +990,7 @@ def custom_cpfun(a, b): flag = [] src = self.mkdtemp() dst = tempfile.mktemp(dir=self.mkdtemp()) - with open(os.path.join(src, 'foo'), 'w', encoding='utf-8') as f: - f.close() + create_file(os.path.join(src, 'foo')) shutil.copytree(src, dst, copy_function=custom_cpfun) self.assertEqual(len(flag), 1) @@ -962,9 +1025,9 @@ def test_copytree_named_pipe(self): def test_copytree_special_func(self): src_dir = self.mkdtemp() dst_dir = os.path.join(self.mkdtemp(), 'destination') - write_file((src_dir, 'test.txt'), '123') + create_file((src_dir, 'test.txt'), '123') os.mkdir(os.path.join(src_dir, 'test_dir')) - write_file((src_dir, 'test_dir', 'test.txt'), '456') + create_file((src_dir, 'test_dir', 'test.txt'), '456') copied = [] def _copy(src, dst): @@ -977,7 +1040,7 @@ def _copy(src, dst): def test_copytree_dangling_symlinks(self): src_dir = self.mkdtemp() valid_file = os.path.join(src_dir, 'test.txt') - write_file(valid_file, 'abc') + create_file(valid_file, 'abc') dir_a = os.path.join(src_dir, 'dir_a') os.mkdir(dir_a) for d in src_dir, dir_a: @@ -1005,8 +1068,7 @@ def test_copytree_symlink_dir(self): src_dir = self.mkdtemp() dst_dir = os.path.join(self.mkdtemp(), 'destination') os.mkdir(os.path.join(src_dir, 'real_dir')) - with open(os.path.join(src_dir, 'real_dir', 'test.txt'), 'wb'): - pass + create_file(os.path.join(src_dir, 'real_dir', 'test.txt')) os.symlink(os.path.join(src_dir, 'real_dir'), os.path.join(src_dir, 'link_to_dir'), target_is_directory=True) @@ -1026,7 +1088,7 @@ def test_copytree_return_value(self): dst_dir = src_dir + "dest" self.addCleanup(shutil.rmtree, dst_dir, True) src = os.path.join(src_dir, 'foo') - write_file(src, 'foo') + create_file(src, 'foo') rv = shutil.copytree(src_dir, dst_dir) self.assertEqual(['foo'], os.listdir(rv)) @@ -1038,7 +1100,7 @@ def test_copytree_subdirectory(self): dst_dir = os.path.join(src_dir, "somevendor", "1.0") os.makedirs(src_dir) src = os.path.join(src_dir, 'pol') - write_file(src, 'pol') + create_file(src, 'pol') rv = shutil.copytree(src_dir, dst_dir) self.assertEqual(['pol'], os.listdir(rv)) @@ -1053,8 +1115,8 @@ def test_copymode_follow_symlinks(self): dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') dst_link = os.path.join(tmp_dir, 'quux') - write_file(src, 'foo') - write_file(dst, 'foo') + create_file(src, 'foo') + create_file(dst, 'foo') os.symlink(src, src_link) os.symlink(dst, dst_link) os.chmod(src, stat.S_IRWXU|stat.S_IRWXG) @@ -1077,35 +1139,34 @@ def test_copymode_follow_symlinks(self): shutil.copymode(src_link, dst_link) self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) - @unittest.skipUnless(hasattr(os, 'lchmod') or os.name == 'nt', 'requires os.lchmod') + @unittest.skipUnless(hasattr(os, 'lchmod'), 'requires os.lchmod') @os_helper.skip_unless_symlink def test_copymode_symlink_to_symlink(self): - _lchmod = os.chmod if os.name == 'nt' else os.lchmod tmp_dir = self.mkdtemp() src = os.path.join(tmp_dir, 'foo') dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') dst_link = os.path.join(tmp_dir, 'quux') - write_file(src, 'foo') - write_file(dst, 'foo') + create_file(src, 'foo') + create_file(dst, 'foo') os.symlink(src, src_link) os.symlink(dst, dst_link) os.chmod(src, stat.S_IRWXU|stat.S_IRWXG) os.chmod(dst, stat.S_IRWXU) - _lchmod(src_link, stat.S_IRWXO|stat.S_IRWXG) + os.lchmod(src_link, stat.S_IRWXO|stat.S_IRWXG) # link to link - _lchmod(dst_link, stat.S_IRWXO) + os.lchmod(dst_link, stat.S_IRWXO) old_mode = os.stat(dst).st_mode shutil.copymode(src_link, dst_link, follow_symlinks=False) self.assertEqual(os.lstat(src_link).st_mode, os.lstat(dst_link).st_mode) self.assertEqual(os.stat(dst).st_mode, old_mode) # src link - use chmod - _lchmod(dst_link, stat.S_IRWXO) + os.lchmod(dst_link, stat.S_IRWXO) shutil.copymode(src_link, dst, follow_symlinks=False) self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) # dst link - use chmod - _lchmod(dst_link, stat.S_IRWXO) + os.lchmod(dst_link, stat.S_IRWXO) shutil.copymode(src, dst_link, follow_symlinks=False) self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) @@ -1117,8 +1178,8 @@ def test_copymode_symlink_to_symlink_wo_lchmod(self): dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') dst_link = os.path.join(tmp_dir, 'quux') - write_file(src, 'foo') - write_file(dst, 'foo') + create_file(src, 'foo') + create_file(dst, 'foo') os.symlink(src, src_link) os.symlink(dst, dst_link) shutil.copymode(src_link, dst_link, follow_symlinks=False) # silent fail @@ -1132,23 +1193,21 @@ def test_copystat_symlinks(self): dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') dst_link = os.path.join(tmp_dir, 'qux') - write_file(src, 'foo') + create_file(src, 'foo') src_stat = os.stat(src) os.utime(src, (src_stat.st_atime, src_stat.st_mtime - 42.0)) # ensure different mtimes - write_file(dst, 'bar') + create_file(dst, 'bar') self.assertNotEqual(os.stat(src).st_mtime, os.stat(dst).st_mtime) os.symlink(src, src_link) os.symlink(dst, dst_link) if hasattr(os, 'lchmod'): os.lchmod(src_link, stat.S_IRWXO) - elif os.name == 'nt': - os.chmod(src_link, stat.S_IRWXO) if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'): os.lchflags(src_link, stat.UF_NODUMP) src_link_stat = os.lstat(src_link) # follow - if hasattr(os, 'lchmod') or os.name == 'nt': + if hasattr(os, 'lchmod'): shutil.copystat(src_link, dst_link, follow_symlinks=True) self.assertNotEqual(src_link_stat.st_mode, os.stat(dst).st_mode) # don't follow @@ -1159,7 +1218,7 @@ def test_copystat_symlinks(self): # The modification times may be truncated in the new file. self.assertLessEqual(getattr(src_link_stat, attr), getattr(dst_link_stat, attr) + 1) - if hasattr(os, 'lchmod') or os.name == 'nt': + if hasattr(os, 'lchmod'): self.assertEqual(src_link_stat.st_mode, dst_link_stat.st_mode) if hasattr(os, 'lchflags') and hasattr(src_link_stat, 'st_flags'): self.assertEqual(src_link_stat.st_flags, dst_link_stat.st_flags) @@ -1176,8 +1235,8 @@ def test_copystat_handles_harmless_chflags_errors(self): tmpdir = self.mkdtemp() file1 = os.path.join(tmpdir, 'file1') file2 = os.path.join(tmpdir, 'file2') - write_file(file1, 'xxx') - write_file(file2, 'xxx') + create_file(file1, 'xxx') + create_file(file2, 'xxx') def make_chflags_raiser(err): ex = OSError() @@ -1203,9 +1262,9 @@ def _chflags_raiser(path, flags, *, follow_symlinks=True): def test_copyxattr(self): tmp_dir = self.mkdtemp() src = os.path.join(tmp_dir, 'foo') - write_file(src, 'foo') + create_file(src, 'foo') dst = os.path.join(tmp_dir, 'bar') - write_file(dst, 'bar') + create_file(dst, 'bar') # no xattr == no problem shutil._copyxattr(src, dst) @@ -1219,7 +1278,7 @@ def test_copyxattr(self): os.getxattr(dst, 'user.foo')) # check errors don't affect other attrs os.remove(dst) - write_file(dst, 'bar') + create_file(dst, 'bar') os_error = OSError(errno.EPERM, 'EPERM') def _raise_on_user_foo(fname, attr, val, **kwargs): @@ -1249,15 +1308,15 @@ def _raise_on_src(fname, *, follow_symlinks=True): # test that shutil.copystat copies xattrs src = os.path.join(tmp_dir, 'the_original') srcro = os.path.join(tmp_dir, 'the_original_ro') - write_file(src, src) - write_file(srcro, srcro) + create_file(src, src) + create_file(srcro, srcro) os.setxattr(src, 'user.the_value', b'fiddly') os.setxattr(srcro, 'user.the_value', b'fiddly') os.chmod(srcro, 0o444) dst = os.path.join(tmp_dir, 'the_copy') dstro = os.path.join(tmp_dir, 'the_copy_ro') - write_file(dst, dst) - write_file(dstro, dstro) + create_file(dst, dst) + create_file(dstro, dstro) shutil.copystat(src, dst) shutil.copystat(srcro, dstro) self.assertEqual(os.getxattr(dst, 'user.the_value'), b'fiddly') @@ -1273,13 +1332,13 @@ def test_copyxattr_symlinks(self): tmp_dir = self.mkdtemp() src = os.path.join(tmp_dir, 'foo') src_link = os.path.join(tmp_dir, 'baz') - write_file(src, 'foo') + create_file(src, 'foo') os.symlink(src, src_link) os.setxattr(src, 'trusted.foo', b'42') os.setxattr(src_link, 'trusted.foo', b'43', follow_symlinks=False) dst = os.path.join(tmp_dir, 'bar') dst_link = os.path.join(tmp_dir, 'qux') - write_file(dst, 'bar') + create_file(dst, 'bar') os.symlink(dst, dst_link) shutil._copyxattr(src_link, dst_link, follow_symlinks=False) self.assertEqual(os.getxattr(dst_link, 'trusted.foo', follow_symlinks=False), b'43') @@ -1292,7 +1351,7 @@ def test_copyxattr_symlinks(self): def _copy_file(self, method): fname = 'test.txt' tmpdir = self.mkdtemp() - write_file((tmpdir, fname), 'xxx') + create_file((tmpdir, fname), 'xxx') file1 = os.path.join(tmpdir, fname) tmpdir2 = self.mkdtemp() method(file1, tmpdir2) @@ -1311,7 +1370,7 @@ def test_copy_symlinks(self): src = os.path.join(tmp_dir, 'foo') dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') - write_file(src, 'foo') + create_file(src, 'foo') os.symlink(src, src_link) if hasattr(os, 'lchmod'): os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO) @@ -1353,7 +1412,7 @@ def test_copy2_symlinks(self): src = os.path.join(tmp_dir, 'foo') dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') - write_file(src, 'foo') + create_file(src, 'foo') os.symlink(src, src_link) if hasattr(os, 'lchmod'): os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO) @@ -1387,7 +1446,7 @@ def test_copy2_xattr(self): tmp_dir = self.mkdtemp() src = os.path.join(tmp_dir, 'foo') dst = os.path.join(tmp_dir, 'bar') - write_file(src, 'foo') + create_file(src, 'foo') os.setxattr(src, 'user.foo', b'42') shutil.copy2(src, dst) self.assertEqual( @@ -1401,7 +1460,7 @@ def test_copy_return_value(self): src_dir = self.mkdtemp() dst_dir = self.mkdtemp() src = os.path.join(src_dir, 'foo') - write_file(src, 'foo') + create_file(src, 'foo') rv = fn(src, dst_dir) self.assertEqual(rv, os.path.join(dst_dir, 'foo')) rv = fn(src, os.path.join(dst_dir, 'bar')) @@ -1418,7 +1477,7 @@ def _test_copy_dir(self, copy_func): src_file = os.path.join(src_dir, 'foo') dir2 = self.mkdtemp() dst = os.path.join(src_dir, 'does_not_exist/') - write_file(src_file, 'foo') + create_file(src_file, 'foo') if sys.platform == "win32": err = PermissionError else: @@ -1438,7 +1497,7 @@ def test_copyfile_symlinks(self): dst = os.path.join(tmp_dir, 'dst') dst_link = os.path.join(tmp_dir, 'dst_link') link = os.path.join(tmp_dir, 'link') - write_file(src, 'foo') + create_file(src, 'foo') os.symlink(src, link) # don't follow shutil.copyfile(link, dst_link, follow_symlinks=False) @@ -1455,8 +1514,7 @@ def test_dont_copy_file_onto_link_to_itself(self): src = os.path.join(TESTFN, 'cheese') dst = os.path.join(TESTFN, 'shop') try: - with open(src, 'w', encoding='utf-8') as f: - f.write('cheddar') + create_file(src, 'cheddar') try: os.link(src, dst) except PermissionError as e: @@ -1475,8 +1533,7 @@ def test_dont_copy_file_onto_symlink_to_itself(self): src = os.path.join(TESTFN, 'cheese') dst = os.path.join(TESTFN, 'shop') try: - with open(src, 'w', encoding='utf-8') as f: - f.write('cheddar') + create_file(src, 'cheddar') # Using `src` here would mean we end up with a symlink pointing # to TESTFN/TESTFN/cheese, while it should point at # TESTFN/cheese. @@ -1511,7 +1568,7 @@ def test_copyfile_return_value(self): dst_dir = self.mkdtemp() dst_file = os.path.join(dst_dir, 'bar') src_file = os.path.join(src_dir, 'foo') - write_file(src_file, 'foo') + create_file(src_file, 'foo') rv = shutil.copyfile(src_file, dst_file) self.assertTrue(os.path.exists(rv)) self.assertEqual(read_file(src_file), read_file(dst_file)) @@ -1521,7 +1578,7 @@ def test_copyfile_same_file(self): # are the same. src_dir = self.mkdtemp() src_file = os.path.join(src_dir, 'foo') - write_file(src_file, 'foo') + create_file(src_file, 'foo') self.assertRaises(SameFileError, shutil.copyfile, src_file, src_file) # But Error should work too, to stay backward compatible. self.assertRaises(Error, shutil.copyfile, src_file, src_file) @@ -1538,7 +1595,7 @@ def test_copyfile_nonexistent_dir(self): src_dir = self.mkdtemp() src_file = os.path.join(src_dir, 'foo') dst = os.path.join(src_dir, 'does_not_exist/') - write_file(src_file, 'foo') + create_file(src_file, 'foo') self.assertRaises(FileNotFoundError, shutil.copyfile, src_file, dst) def test_copyfile_copy_dir(self): @@ -1549,7 +1606,7 @@ def test_copyfile_copy_dir(self): src_file = os.path.join(src_dir, 'foo') dir2 = self.mkdtemp() dst = os.path.join(src_dir, 'does_not_exist/') - write_file(src_file, 'foo') + create_file(src_file, 'foo') if sys.platform == "win32": err = PermissionError else: @@ -1564,42 +1621,6 @@ class TestArchives(BaseTest, unittest.TestCase): ### shutil.make_archive - @support.requires_zlib() - def test_make_tarball(self): - # creating something to tar - root_dir, base_dir = self._create_files('') - - tmpdir2 = self.mkdtemp() - # force shutil to create the directory - os.rmdir(tmpdir2) - # working with relative paths - work_dir = os.path.dirname(tmpdir2) - rel_base_name = os.path.join(os.path.basename(tmpdir2), 'archive') - - with os_helper.change_cwd(work_dir), no_chdir: - base_name = os.path.abspath(rel_base_name) - tarball = make_archive(rel_base_name, 'gztar', root_dir, '.') - - # check if the compressed tarball was created - self.assertEqual(tarball, base_name + '.tar.gz') - self.assertTrue(os.path.isfile(tarball)) - self.assertTrue(tarfile.is_tarfile(tarball)) - with tarfile.open(tarball, 'r:gz') as tf: - self.assertCountEqual(tf.getnames(), - ['.', './sub', './sub2', - './file1', './file2', './sub/file3']) - - # trying an uncompressed one - with os_helper.change_cwd(work_dir), no_chdir: - tarball = make_archive(rel_base_name, 'tar', root_dir, '.') - self.assertEqual(tarball, base_name + '.tar') - self.assertTrue(os.path.isfile(tarball)) - self.assertTrue(tarfile.is_tarfile(tarball)) - with tarfile.open(tarball, 'r') as tf: - self.assertCountEqual(tf.getnames(), - ['.', './sub', './sub2', - './file1', './file2', './sub/file3']) - def _tarinfo(self, path): with tarfile.open(path) as tar: names = tar.getnames() @@ -1611,15 +1632,101 @@ def _create_files(self, base_dir='dist'): root_dir = self.mkdtemp() dist = os.path.join(root_dir, base_dir) os.makedirs(dist, exist_ok=True) - write_file((dist, 'file1'), 'xxx') - write_file((dist, 'file2'), 'xxx') + create_file((dist, 'file1'), 'xxx') + create_file((dist, 'file2'), 'xxx') os.mkdir(os.path.join(dist, 'sub')) - write_file((dist, 'sub', 'file3'), 'xxx') + create_file((dist, 'sub', 'file3'), 'xxx') os.mkdir(os.path.join(dist, 'sub2')) if base_dir: - write_file((root_dir, 'outer'), 'xxx') + create_file((root_dir, 'outer'), 'xxx') return root_dir, base_dir + @support.requires_zlib() + def test_make_tarfile(self): + root_dir, base_dir = self._create_files() + # Test without base_dir. + with os_helper.temp_cwd(), no_chdir: + base_name = os.path.join('dst', 'archive') + archive = make_archive(base_name, 'tar', root_dir) + # check if the compressed tarball was created + self.assertEqual(archive, os.path.abspath(base_name) + '.tar') + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(tarfile.is_tarfile(archive)) + with tarfile.open(archive, 'r') as tf: + self.assertCountEqual(tf.getnames(), + ['.', './dist', './dist/sub', './dist/sub2', + './dist/file1', './dist/file2', './dist/sub/file3', + './outer']) + + # Test with base_dir. + with os_helper.temp_cwd(), no_chdir: + base_name = os.path.join('dst2', 'archive') + archive = make_archive(base_name, 'tar', root_dir, base_dir) + self.assertEqual(archive, os.path.abspath(base_name) + '.tar') + # check if the uncompressed tarball was created + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(tarfile.is_tarfile(archive)) + with tarfile.open(archive, 'r') as tf: + self.assertCountEqual(tf.getnames(), + ['dist', 'dist/sub', 'dist/sub2', + 'dist/file1', 'dist/file2', 'dist/sub/file3']) + + # Test with multi-component base_dir. + with os_helper.temp_cwd(), no_chdir: + base_name = os.path.join('dst3', 'archive') + archive = make_archive(base_name, 'tar', root_dir, + os.path.join(base_dir, 'sub')) + self.assertEqual(archive, os.path.abspath(base_name) + '.tar') + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(tarfile.is_tarfile(archive)) + with tarfile.open(archive, 'r') as tf: + self.assertCountEqual(tf.getnames(), + ['dist/sub', 'dist/sub/file3']) + + @support.requires_zlib() + def test_make_tarfile_without_rootdir(self): + root_dir, base_dir = self._create_files() + # Test without base_dir. + base_name = os.path.join(self.mkdtemp(), 'dst', 'archive') + base_name = os.path.relpath(base_name, root_dir) + with os_helper.change_cwd(root_dir), no_chdir: + archive = make_archive(base_name, 'gztar') + self.assertEqual(archive, base_name + '.tar.gz') + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(tarfile.is_tarfile(archive)) + with tarfile.open(archive, 'r:gz') as tf: + self.assertCountEqual(tf.getnames(), + ['.', './dist', './dist/sub', './dist/sub2', + './dist/file1', './dist/file2', './dist/sub/file3', + './outer']) + + # Test with base_dir. + with os_helper.change_cwd(root_dir), no_chdir: + base_name = os.path.join('dst', 'archive') + archive = make_archive(base_name, 'tar', base_dir=base_dir) + self.assertEqual(archive, base_name + '.tar') + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(tarfile.is_tarfile(archive)) + with tarfile.open(archive, 'r') as tf: + self.assertCountEqual(tf.getnames(), + ['dist', 'dist/sub', 'dist/sub2', + 'dist/file1', 'dist/file2', 'dist/sub/file3']) + + def test_make_tarfile_with_explicit_curdir(self): + # Test with base_dir=os.curdir. + root_dir, base_dir = self._create_files() + with os_helper.temp_cwd(), no_chdir: + base_name = os.path.join('dst', 'archive') + archive = make_archive(base_name, 'tar', root_dir, os.curdir) + self.assertEqual(archive, os.path.abspath(base_name) + '.tar') + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(tarfile.is_tarfile(archive)) + with tarfile.open(archive, 'r') as tf: + self.assertCountEqual(tf.getnames(), + ['.', './dist', './dist/sub', './dist/sub2', + './dist/file1', './dist/file2', './dist/sub/file3', + './outer']) + @support.requires_zlib() @unittest.skipUnless(shutil.which('tar'), 'Need the tar command to run') @@ -1669,40 +1776,89 @@ def test_tarfile_vs_tar(self): @support.requires_zlib() def test_make_zipfile(self): - # creating something to zip root_dir, base_dir = self._create_files() + # Test without base_dir. + with os_helper.temp_cwd(), no_chdir: + base_name = os.path.join('dst', 'archive') + archive = make_archive(base_name, 'zip', root_dir) + self.assertEqual(archive, os.path.abspath(base_name) + '.zip') + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(zipfile.is_zipfile(archive)) + with zipfile.ZipFile(archive) as zf: + self.assertCountEqual(zf.namelist(), + ['dist/', 'dist/sub/', 'dist/sub2/', + 'dist/file1', 'dist/file2', 'dist/sub/file3', + 'outer']) + + # Test with base_dir. + with os_helper.temp_cwd(), no_chdir: + base_name = os.path.join('dst2', 'archive') + archive = make_archive(base_name, 'zip', root_dir, base_dir) + self.assertEqual(archive, os.path.abspath(base_name) + '.zip') + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(zipfile.is_zipfile(archive)) + with zipfile.ZipFile(archive) as zf: + self.assertCountEqual(zf.namelist(), + ['dist/', 'dist/sub/', 'dist/sub2/', + 'dist/file1', 'dist/file2', 'dist/sub/file3']) + + # Test with multi-component base_dir. + with os_helper.temp_cwd(), no_chdir: + base_name = os.path.join('dst3', 'archive') + archive = make_archive(base_name, 'zip', root_dir, + os.path.join(base_dir, 'sub')) + self.assertEqual(archive, os.path.abspath(base_name) + '.zip') + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(zipfile.is_zipfile(archive)) + with zipfile.ZipFile(archive) as zf: + self.assertCountEqual(zf.namelist(), + ['dist/sub/', 'dist/sub/file3']) - tmpdir2 = self.mkdtemp() - # force shutil to create the directory - os.rmdir(tmpdir2) - # working with relative paths - work_dir = os.path.dirname(tmpdir2) - rel_base_name = os.path.join(os.path.basename(tmpdir2), 'archive') - - with os_helper.change_cwd(work_dir), no_chdir: - base_name = os.path.abspath(rel_base_name) - res = make_archive(rel_base_name, 'zip', root_dir) + @support.requires_zlib() + def test_make_zipfile_without_rootdir(self): + root_dir, base_dir = self._create_files() + # Test without base_dir. + base_name = os.path.join(self.mkdtemp(), 'dst', 'archive') + base_name = os.path.relpath(base_name, root_dir) + with os_helper.change_cwd(root_dir), no_chdir: + archive = make_archive(base_name, 'zip') + self.assertEqual(archive, base_name + '.zip') + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(zipfile.is_zipfile(archive)) + with zipfile.ZipFile(archive) as zf: + self.assertCountEqual(zf.namelist(), + ['dist/', 'dist/sub/', 'dist/sub2/', + 'dist/file1', 'dist/file2', 'dist/sub/file3', + 'outer']) + + # Test with base_dir. + root_dir, base_dir = self._create_files() + with os_helper.change_cwd(root_dir), no_chdir: + base_name = os.path.join('dst', 'archive') + archive = make_archive(base_name, 'zip', base_dir=base_dir) + self.assertEqual(archive, base_name + '.zip') + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(zipfile.is_zipfile(archive)) + with zipfile.ZipFile(archive) as zf: + self.assertCountEqual(zf.namelist(), + ['dist/', 'dist/sub/', 'dist/sub2/', + 'dist/file1', 'dist/file2', 'dist/sub/file3']) - self.assertEqual(res, base_name + '.zip') - self.assertTrue(os.path.isfile(res)) - self.assertTrue(zipfile.is_zipfile(res)) - with zipfile.ZipFile(res) as zf: - self.assertCountEqual(zf.namelist(), - ['dist/', 'dist/sub/', 'dist/sub2/', - 'dist/file1', 'dist/file2', 'dist/sub/file3', - 'outer']) - - with os_helper.change_cwd(work_dir), no_chdir: - base_name = os.path.abspath(rel_base_name) - res = make_archive(rel_base_name, 'zip', root_dir, base_dir) - - self.assertEqual(res, base_name + '.zip') - self.assertTrue(os.path.isfile(res)) - self.assertTrue(zipfile.is_zipfile(res)) - with zipfile.ZipFile(res) as zf: - self.assertCountEqual(zf.namelist(), - ['dist/', 'dist/sub/', 'dist/sub2/', - 'dist/file1', 'dist/file2', 'dist/sub/file3']) + @support.requires_zlib() + def test_make_zipfile_with_explicit_curdir(self): + # Test with base_dir=os.curdir. + root_dir, base_dir = self._create_files() + with os_helper.temp_cwd(), no_chdir: + base_name = os.path.join('dst', 'archive') + archive = make_archive(base_name, 'zip', root_dir, os.curdir) + self.assertEqual(archive, os.path.abspath(base_name) + '.zip') + self.assertTrue(os.path.isfile(archive)) + self.assertTrue(zipfile.is_zipfile(archive)) + with zipfile.ZipFile(archive) as zf: + self.assertCountEqual(zf.namelist(), + ['dist/', 'dist/sub/', 'dist/sub2/', + 'dist/file1', 'dist/file2', 'dist/sub/file3', + 'outer']) @support.requires_zlib() @unittest.skipUnless(shutil.which('zip'), @@ -1751,7 +1907,10 @@ def test_unzip_zipfile(self): subprocess.check_output(zip_cmd, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as exc: details = exc.output.decode(errors="replace") - if 'unrecognized option: t' in details: + if any(message in details for message in [ + 'unrecognized option: t', # BusyBox + 'invalid option -- t', # Android + ]): self.skipTest("unzip doesn't support -t") msg = "{}\n\n**Unzip Output**\n{}" self.fail(msg.format(exc, details)) @@ -1872,17 +2031,19 @@ def archiver(base_name, base_dir, **kw): unregister_archive_format('xxx') def test_make_tarfile_in_curdir(self): - # Issue #21280 + # Issue #21280: Test with the archive in the current directory. root_dir = self.mkdtemp() with os_helper.change_cwd(root_dir), no_chdir: + # root_dir must be None, so the archive path is relative. self.assertEqual(make_archive('test', 'tar'), 'test.tar') self.assertTrue(os.path.isfile('test.tar')) @support.requires_zlib() def test_make_zipfile_in_curdir(self): - # Issue #21280 + # Issue #21280: Test with the archive in the current directory. root_dir = self.mkdtemp() with os_helper.change_cwd(root_dir), no_chdir: + # root_dir must be None, so the archive path is relative. self.assertEqual(make_archive('test', 'zip'), 'test.zip') self.assertTrue(os.path.isfile('test.zip')) @@ -1903,10 +2064,11 @@ def test_register_archive_format(self): self.assertNotIn('xxx', formats) def test_make_tarfile_rootdir_nodir(self): - # GH-99203 + # GH-99203: Test with root_dir is not a real directory. self.addCleanup(os_helper.unlink, f'{TESTFN}.tar') for dry_run in (False, True): with self.subTest(dry_run=dry_run): + # root_dir does not exist. tmp_dir = self.mkdtemp() nonexisting_file = os.path.join(tmp_dir, 'nonexisting') with self.assertRaises(FileNotFoundError) as cm: @@ -1915,6 +2077,7 @@ def test_make_tarfile_rootdir_nodir(self): self.assertEqual(cm.exception.filename, nonexisting_file) self.assertFalse(os.path.exists(f'{TESTFN}.tar')) + # root_dir is a file. tmp_fd, tmp_file = tempfile.mkstemp(dir=tmp_dir) os.close(tmp_fd) with self.assertRaises(NotADirectoryError) as cm: @@ -1925,10 +2088,11 @@ def test_make_tarfile_rootdir_nodir(self): @support.requires_zlib() def test_make_zipfile_rootdir_nodir(self): - # GH-99203 + # GH-99203: Test with root_dir is not a real directory. self.addCleanup(os_helper.unlink, f'{TESTFN}.zip') for dry_run in (False, True): with self.subTest(dry_run=dry_run): + # root_dir does not exist. tmp_dir = self.mkdtemp() nonexisting_file = os.path.join(tmp_dir, 'nonexisting') with self.assertRaises(FileNotFoundError) as cm: @@ -1937,6 +2101,7 @@ def test_make_zipfile_rootdir_nodir(self): self.assertEqual(cm.exception.filename, nonexisting_file) self.assertFalse(os.path.exists(f'{TESTFN}.zip')) + # root_dir is a file. tmp_fd, tmp_file = tempfile.mkstemp(dir=tmp_dir) os.close(tmp_fd) with self.assertRaises(NotADirectoryError) as cm: @@ -1951,7 +2116,7 @@ def check_unpack_archive(self, format, **kwargs): self.check_unpack_archive_with_converter( format, lambda path: path, **kwargs) self.check_unpack_archive_with_converter( - format, pathlib.Path, **kwargs) + format, FakePath, **kwargs) self.check_unpack_archive_with_converter(format, FakePath, **kwargs) def check_unpack_archive_with_converter(self, format, converter, **kwargs): @@ -2056,7 +2221,9 @@ def test_disk_usage(self): def test_chown(self): dirname = self.mkdtemp() filename = tempfile.mktemp(dir=dirname) - write_file(filename, 'testing chown function') + linkname = os.path.join(dirname, "chown_link") + create_file(filename, 'testing chown function') + os.symlink(filename, linkname) with self.assertRaises(ValueError): shutil.chown(filename) @@ -2077,7 +2244,7 @@ def test_chown(self): gid = os.getgid() def check_chown(path, uid=None, gid=None): - s = os.stat(filename) + s = os.stat(path) if uid is not None: self.assertEqual(uid, s.st_uid) if gid is not None: @@ -2113,41 +2280,76 @@ def check_chown(path, uid=None, gid=None): shutil.chown(dirname, user, group) check_chown(dirname, uid, gid) + dirfd = os.open(dirname, os.O_RDONLY) + self.addCleanup(os.close, dirfd) + basename = os.path.basename(filename) + baselinkname = os.path.basename(linkname) + shutil.chown(basename, uid, gid, dir_fd=dirfd) + check_chown(filename, uid, gid) + shutil.chown(basename, uid, dir_fd=dirfd) + check_chown(filename, uid) + shutil.chown(basename, group=gid, dir_fd=dirfd) + check_chown(filename, gid=gid) + shutil.chown(basename, uid, gid, dir_fd=dirfd, follow_symlinks=True) + check_chown(filename, uid, gid) + shutil.chown(basename, uid, gid, dir_fd=dirfd, follow_symlinks=False) + check_chown(filename, uid, gid) + shutil.chown(linkname, uid, follow_symlinks=True) + check_chown(filename, uid) + shutil.chown(baselinkname, group=gid, dir_fd=dirfd, follow_symlinks=False) + check_chown(filename, gid=gid) + shutil.chown(baselinkname, uid, gid, dir_fd=dirfd, follow_symlinks=True) + check_chown(filename, uid, gid) + + with self.assertRaises(TypeError): + shutil.chown(filename, uid, dir_fd=dirname) + + with self.assertRaises(FileNotFoundError): + shutil.chown('missingfile', uid, gid, dir_fd=dirfd) + + with self.assertRaises(ValueError): + shutil.chown(filename, dir_fd=dirfd) + +@support.requires_subprocess() class TestWhich(BaseTest, unittest.TestCase): def setUp(self): - self.temp_dir = self.mkdtemp(prefix="Tmp") + temp_dir = self.mkdtemp(prefix="Tmp") + base_dir = os.path.join(temp_dir, TESTFN + '-basedir') + os.mkdir(base_dir) + self.dir = os.path.join(base_dir, TESTFN + '-dir') + os.mkdir(self.dir) + self.other_dir = os.path.join(base_dir, TESTFN + '-dir2') + os.mkdir(self.other_dir) # Give the temp_file an ".exe" suffix for all. # It's needed on Windows and not harmful on other platforms. - self.temp_file = tempfile.NamedTemporaryFile(dir=self.temp_dir, - prefix="Tmp", - suffix=".Exe") - os.chmod(self.temp_file.name, stat.S_IXUSR) - self.addCleanup(self.temp_file.close) - self.dir, self.file = os.path.split(self.temp_file.name) + self.file = TESTFN + '.Exe' + self.filepath = os.path.join(self.dir, self.file) + self.create_file(self.filepath) self.env_path = self.dir self.curdir = os.curdir self.ext = ".EXE" - def to_text_type(self, s): - ''' - In this class we're testing with str, so convert s to a str - ''' - if isinstance(s, bytes): - return s.decode() - return s + to_text_type = staticmethod(os.fsdecode) + + def create_file(self, path): + create_file(path) + os.chmod(path, 0o755) + + def assertNormEqual(self, actual, expected): + self.assertEqual(os.path.normcase(actual), os.path.normcase(expected)) def test_basic(self): # Given an EXE in a directory, it should be returned. rv = shutil.which(self.file, path=self.dir) - self.assertEqual(rv, self.temp_file.name) + self.assertEqual(rv, self.filepath) def test_absolute_cmd(self): # When given the fully qualified path to an executable that exists, # it should be returned. - rv = shutil.which(self.temp_file.name, path=self.temp_dir) - self.assertEqual(rv, self.temp_file.name) + rv = shutil.which(self.filepath, path=self.other_dir) + self.assertEqual(rv, self.filepath) def test_relative_cmd(self): # When given the relative path with a directory part to an executable @@ -2155,7 +2357,7 @@ def test_relative_cmd(self): base_dir, tail_dir = os.path.split(self.dir) relpath = os.path.join(tail_dir, self.file) with os_helper.change_cwd(path=base_dir): - rv = shutil.which(relpath, path=self.temp_dir) + rv = shutil.which(relpath, path=self.other_dir) self.assertEqual(rv, relpath) # But it shouldn't be searched in PATH directories (issue #16957). with os_helper.change_cwd(path=self.dir): @@ -2166,9 +2368,8 @@ def test_relative_cmd(self): "test is for non win32") def test_cwd_non_win32(self): # Issue #16957 - base_dir = os.path.dirname(self.dir) with os_helper.change_cwd(path=self.dir): - rv = shutil.which(self.file, path=base_dir) + rv = shutil.which(self.file, path=self.other_dir) # non-win32: shouldn't match in the current directory. self.assertIsNone(rv) @@ -2178,57 +2379,32 @@ def test_cwd_win32(self): base_dir = os.path.dirname(self.dir) with os_helper.change_cwd(path=self.dir): with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=True): - rv = shutil.which(self.file, path=base_dir) + rv = shutil.which(self.file, path=self.other_dir) # Current directory implicitly on PATH self.assertEqual(rv, os.path.join(self.curdir, self.file)) with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=False): - rv = shutil.which(self.file, path=base_dir) + rv = shutil.which(self.file, path=self.other_dir) # Current directory not on PATH self.assertIsNone(rv) @unittest.skipUnless(sys.platform == "win32", "test is for win32") def test_cwd_win32_added_before_all_other_path(self): - base_dir = pathlib.Path(os.fsdecode(self.dir)) - - elsewhere_in_path_dir = base_dir / 'dir1' - elsewhere_in_path_dir.mkdir() - match_elsewhere_in_path = elsewhere_in_path_dir / 'hello.exe' - match_elsewhere_in_path.touch() - - exe_in_cwd = base_dir / 'hello.exe' - exe_in_cwd.touch() - - with os_helper.change_cwd(path=base_dir): - with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=True): - rv = shutil.which('hello.exe', path=elsewhere_in_path_dir) - - self.assertEqual(os.path.abspath(rv), os.path.abspath(exe_in_cwd)) - - @unittest.skipUnless(sys.platform == "win32", - "test is for win32") - def test_pathext_match_before_path_full_match(self): - base_dir = pathlib.Path(os.fsdecode(self.dir)) - dir1 = base_dir / 'dir1' - dir2 = base_dir / 'dir2' - dir1.mkdir() - dir2.mkdir() - - pathext_match = dir1 / 'hello.com.exe' - path_match = dir2 / 'hello.com' - pathext_match.touch() - path_match.touch() - - test_path = os.pathsep.join([str(dir1), str(dir2)]) - assert os.path.basename(shutil.which( - 'hello.com', path=test_path, mode = os.F_OK - )).lower() == 'hello.com.exe' + other_file_path = os.path.join(self.other_dir, self.file) + self.create_file(other_file_path) + with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=True): + with os_helper.change_cwd(path=self.dir): + rv = shutil.which(self.file, path=self.other_dir) + self.assertEqual(rv, os.path.join(self.curdir, self.file)) + with os_helper.change_cwd(path=self.other_dir): + rv = shutil.which(self.file, path=self.dir) + self.assertEqual(rv, os.path.join(self.curdir, self.file)) @os_helper.skip_if_dac_override def test_non_matching_mode(self): # Set the file read-only and ask for writeable files. - os.chmod(self.temp_file.name, stat.S_IREAD) - if os.access(self.temp_file.name, os.W_OK): + os.chmod(self.filepath, stat.S_IREAD) + if os.access(self.filepath, os.W_OK): self.skipTest("can't set the file read-only") rv = shutil.which(self.file, path=self.dir, mode=os.W_OK) self.assertIsNone(rv) @@ -2250,13 +2426,13 @@ def test_pathext_checking(self): # Ask for the file without the ".exe" extension, then ensure that # it gets found properly with the extension. rv = shutil.which(self.file[:-4], path=self.dir) - self.assertEqual(rv, self.temp_file.name[:-4] + self.ext) + self.assertEqual(rv, self.filepath[:-4] + self.ext) def test_environ_path(self): with os_helper.EnvironmentVarGuard() as env: env['PATH'] = self.env_path rv = shutil.which(self.file) - self.assertEqual(rv, self.temp_file.name) + self.assertEqual(rv, self.filepath) def test_environ_path_empty(self): # PATH='': no match @@ -2270,12 +2446,9 @@ def test_environ_path_empty(self): self.assertIsNone(rv) def test_environ_path_cwd(self): - expected_cwd = os.path.basename(self.temp_file.name) + expected_cwd = self.file if sys.platform == "win32": - curdir = os.curdir - if isinstance(expected_cwd, bytes): - curdir = os.fsencode(curdir) - expected_cwd = os.path.join(curdir, expected_cwd) + expected_cwd = os.path.join(self.curdir, expected_cwd) # PATH=':': explicitly looks in the current directory with os_helper.EnvironmentVarGuard() as env: @@ -2293,21 +2466,21 @@ def test_environ_path_cwd(self): def test_environ_path_missing(self): with os_helper.EnvironmentVarGuard() as env: - env.pop('PATH', None) + del env['PATH'] # without confstr with unittest.mock.patch('os.confstr', side_effect=ValueError, \ create=True), \ support.swap_attr(os, 'defpath', self.dir): rv = shutil.which(self.file) - self.assertEqual(rv, self.temp_file.name) + self.assertEqual(rv, self.filepath) # with confstr with unittest.mock.patch('os.confstr', return_value=self.dir, \ create=True), \ support.swap_attr(os, 'defpath', ''): rv = shutil.which(self.file) - self.assertEqual(rv, self.temp_file.name) + self.assertEqual(rv, self.filepath) def test_empty_path(self): base_dir = os.path.dirname(self.dir) @@ -2319,56 +2492,94 @@ def test_empty_path(self): def test_empty_path_no_PATH(self): with os_helper.EnvironmentVarGuard() as env: - env.pop('PATH', None) + del env['PATH'] rv = shutil.which(self.file) self.assertIsNone(rv) @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') def test_pathext(self): - ext = self.to_text_type(".xyz") - temp_filexyz = tempfile.NamedTemporaryFile(dir=self.temp_dir, - prefix=self.to_text_type("Tmp2"), suffix=ext) - os.chmod(temp_filexyz.name, stat.S_IXUSR) - self.addCleanup(temp_filexyz.close) - - # strip path and extension - program = os.path.basename(temp_filexyz.name) - program = os.path.splitext(program)[0] - + ext = '.xyz' + cmd = self.to_text_type(TESTFN2) + cmdext = cmd + self.to_text_type(ext) + filepath = os.path.join(self.dir, cmdext) + self.create_file(filepath) with os_helper.EnvironmentVarGuard() as env: - env['PATHEXT'] = ext if isinstance(ext, str) else ext.decode() - rv = shutil.which(program, path=self.temp_dir) - self.assertEqual(rv, temp_filexyz.name) + env['PATHEXT'] = ext + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) + self.assertEqual(shutil.which(cmdext, path=self.dir), filepath) # Issue 40592: See https://bugs.python.org/issue40592 @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') def test_pathext_with_empty_str(self): - ext = self.to_text_type(".xyz") - temp_filexyz = tempfile.NamedTemporaryFile(dir=self.temp_dir, - prefix=self.to_text_type("Tmp2"), suffix=ext) - self.addCleanup(temp_filexyz.close) + ext = '.xyz' + cmd = self.to_text_type(TESTFN2) + cmdext = cmd + self.to_text_type(ext) + filepath = os.path.join(self.dir, cmdext) + self.create_file(filepath) + with os_helper.EnvironmentVarGuard() as env: + env['PATHEXT'] = ext + ';' # note the ; + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) + self.assertEqual(shutil.which(cmdext, path=self.dir), filepath) - # strip path and extension - program = os.path.basename(temp_filexyz.name) - program = os.path.splitext(program)[0] + @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') + def test_pathext_with_multidot_extension(self): + ext = '.foo.bar' + cmd = self.to_text_type(TESTFN2) + cmdext = cmd + self.to_text_type(ext) + filepath = os.path.join(self.dir, cmdext) + self.create_file(filepath) + with os_helper.EnvironmentVarGuard() as env: + env['PATHEXT'] = ext + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) + self.assertEqual(shutil.which(cmdext, path=self.dir), filepath) + + @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') + def test_pathext_with_null_extension(self): + cmd = self.to_text_type(TESTFN2) + cmddot = cmd + self.to_text_type('.') + filepath = os.path.join(self.dir, cmd) + self.create_file(filepath) + with os_helper.EnvironmentVarGuard() as env: + env['PATHEXT'] = '.xyz' + self.assertIsNone(shutil.which(cmd, path=self.dir)) + self.assertIsNone(shutil.which(cmddot, path=self.dir)) + env['PATHEXT'] = '.xyz;.' # note the . + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) + self.assertEqual(shutil.which(cmddot, path=self.dir), + filepath + self.to_text_type('.')) + env['PATHEXT'] = '.xyz;..' # multiple dots + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) + self.assertEqual(shutil.which(cmddot, path=self.dir), + filepath + self.to_text_type('.')) + @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') + def test_pathext_extension_ends_with_dot(self): + ext = '.xyz' + cmd = self.to_text_type(TESTFN2) + cmdext = cmd + self.to_text_type(ext) + dot = self.to_text_type('.') + filepath = os.path.join(self.dir, cmdext) + self.create_file(filepath) with os_helper.EnvironmentVarGuard() as env: - env['PATHEXT'] = f"{ext if isinstance(ext, str) else ext.decode()};" # note the ; - rv = shutil.which(program, path=self.temp_dir) - self.assertEqual(rv, temp_filexyz.name) + env['PATHEXT'] = ext + '.' + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) # cmd.exe hangs here + self.assertEqual(shutil.which(cmdext, path=self.dir), filepath) + self.assertIsNone(shutil.which(cmd + dot, path=self.dir)) + self.assertIsNone(shutil.which(cmdext + dot, path=self.dir)) # See GH-75586 @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') def test_pathext_applied_on_files_in_path(self): + ext = '.xyz' + cmd = self.to_text_type(TESTFN2) + cmdext = cmd + self.to_text_type(ext) + filepath = os.path.join(self.dir, cmdext) + self.create_file(filepath) with os_helper.EnvironmentVarGuard() as env: - env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode() - env["PATHEXT"] = ".test" - - test_path = os.path.join(self.temp_dir, self.to_text_type("test_program.test")) - open(test_path, 'w').close() - os.chmod(test_path, 0o755) - - self.assertEqual(shutil.which(self.to_text_type("test_program")), test_path) + env["PATH"] = os.fsdecode(self.dir) + env["PATHEXT"] = ext + self.assertEqual(shutil.which(cmd), filepath) + self.assertEqual(shutil.which(cmdext), filepath) # See GH-75586 @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') @@ -2384,49 +2595,107 @@ def test_win_path_needs_curdir(self): self.assertFalse(shutil._win_path_needs_curdir('dontcare', os.X_OK)) need_curdir_mock.assert_called_once_with('dontcare') - # See GH-109590 @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') - def test_pathext_preferred_for_execute(self): - with os_helper.EnvironmentVarGuard() as env: - env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode() - env["PATHEXT"] = ".test" - - exe = os.path.join(self.temp_dir, self.to_text_type("test.exe")) - open(exe, 'w').close() - os.chmod(exe, 0o755) - - # default behavior allows a direct match if nothing in PATHEXT matches - self.assertEqual(shutil.which(self.to_text_type("test.exe")), exe) - - dot_test = os.path.join(self.temp_dir, self.to_text_type("test.exe.test")) - open(dot_test, 'w').close() - os.chmod(dot_test, 0o755) + def test_same_dir_with_pathext_extension(self): + cmd = self.file # with .exe extension + # full match + self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK), + self.filepath) + + cmd2 = cmd + self.to_text_type('.com') # with .exe.com extension + other_file_path = os.path.join(self.dir, cmd2) + self.create_file(other_file_path) + + # full match + self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK), + self.filepath) + self.assertNormEqual(shutil.which(cmd2, path=self.dir), other_file_path) + self.assertNormEqual(shutil.which(cmd2, path=self.dir, mode=os.F_OK), + other_file_path) - # now we have a PATHEXT match, so it take precedence - self.assertEqual(shutil.which(self.to_text_type("test.exe")), dot_test) - - # but if we don't use os.X_OK we don't change the order based off PATHEXT - # and therefore get the direct match. - self.assertEqual(shutil.which(self.to_text_type("test.exe"), mode=os.F_OK), exe) - - # See GH-109590 @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') - def test_pathext_given_extension_preferred(self): - with os_helper.EnvironmentVarGuard() as env: - env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode() - env["PATHEXT"] = ".exe2;.exe" - - exe = os.path.join(self.temp_dir, self.to_text_type("test.exe")) - open(exe, 'w').close() - os.chmod(exe, 0o755) + def test_same_dir_without_pathext_extension(self): + cmd = self.file[:-4] # without .exe extension + # pathext match + self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK), + self.filepath) + + # without extension + other_file_path = os.path.join(self.dir, cmd) + self.create_file(other_file_path) + + # pathext match if mode contains X_OK + self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath) + # full match + self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK), + other_file_path) + self.assertNormEqual(shutil.which(self.file, path=self.dir), self.filepath) + self.assertNormEqual(shutil.which(self.file, path=self.dir, mode=os.F_OK), + self.filepath) - exe2 = os.path.join(self.temp_dir, self.to_text_type("test.exe2")) - open(exe2, 'w').close() - os.chmod(exe2, 0o755) + @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') + def test_dir_order_with_pathext_extension(self): + cmd = self.file # with .exe extension + search_path = os.pathsep.join([os.fsdecode(self.other_dir), + os.fsdecode(self.dir)]) + # full match in the second directory + self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + self.filepath) + + cmd2 = cmd + self.to_text_type('.com') # with .exe.com extension + other_file_path = os.path.join(self.other_dir, cmd2) + self.create_file(other_file_path) + + # pathext match in the first directory + self.assertNormEqual(shutil.which(cmd, path=search_path), other_file_path) + self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + other_file_path) + # full match in the first directory + self.assertNormEqual(shutil.which(cmd2, path=search_path), other_file_path) + self.assertNormEqual(shutil.which(cmd2, path=search_path, mode=os.F_OK), + other_file_path) + + # full match in the first directory + search_path = os.pathsep.join([os.fsdecode(self.dir), + os.fsdecode(self.other_dir)]) + self.assertEqual(shutil.which(cmd, path=search_path), self.filepath) + self.assertEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + self.filepath) - # even though .exe2 is preferred in PATHEXT, we matched directly to test.exe - self.assertEqual(shutil.which(self.to_text_type("test.exe")), exe) - self.assertEqual(shutil.which(self.to_text_type("test")), exe2) + @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') + def test_dir_order_without_pathext_extension(self): + cmd = self.file[:-4] # without .exe extension + search_path = os.pathsep.join([os.fsdecode(self.other_dir), + os.fsdecode(self.dir)]) + # pathext match in the second directory + self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + self.filepath) + + # without extension + other_file_path = os.path.join(self.other_dir, cmd) + self.create_file(other_file_path) + + # pathext match in the second directory + self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath) + # full match in the first directory + self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + other_file_path) + # full match in the second directory + self.assertNormEqual(shutil.which(self.file, path=search_path), self.filepath) + self.assertNormEqual(shutil.which(self.file, path=search_path, mode=os.F_OK), + self.filepath) + + # pathext match in the first directory + search_path = os.pathsep.join([os.fsdecode(self.dir), + os.fsdecode(self.other_dir)]) + self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + self.filepath) class TestWhichBytes(TestWhich): @@ -2434,18 +2703,12 @@ def setUp(self): TestWhich.setUp(self) self.dir = os.fsencode(self.dir) self.file = os.fsencode(self.file) - self.temp_file.name = os.fsencode(self.temp_file.name) - self.temp_dir = os.fsencode(self.temp_dir) + self.filepath = os.fsencode(self.filepath) + self.other_dir = os.fsencode(self.other_dir) self.curdir = os.fsencode(self.curdir) self.ext = os.fsencode(self.ext) - def to_text_type(self, s): - ''' - In this class we're testing with bytes, so convert s to a bytes - ''' - if isinstance(s, str): - return s.encode() - return s + to_text_type = staticmethod(os.fsencode) class TestMove(BaseTest, unittest.TestCase): @@ -2456,8 +2719,7 @@ def setUp(self): self.dst_dir = self.mkdtemp() self.src_file = os.path.join(self.src_dir, filename) self.dst_file = os.path.join(self.dst_dir, filename) - with open(self.src_file, "wb") as f: - f.write(b"spam") + create_file(self.src_file, b"spam") def _check_move_file(self, src, dst, real_dst): with open(src, "rb") as f: @@ -2483,12 +2745,12 @@ def test_move_file_to_dir(self): def test_move_file_to_dir_pathlike_src(self): # Move a pathlike file to another location on the same filesystem. - src = pathlib.Path(self.src_file) + src = FakePath(self.src_file) self._check_move_file(src, self.dst_dir, self.dst_file) def test_move_file_to_dir_pathlike_dst(self): # Move a file to another pathlike location on the same filesystem. - dst = pathlib.Path(self.dst_dir) + dst = FakePath(self.dst_dir) self._check_move_file(self.src_file, dst, self.dst_file) @mock_rename @@ -2535,8 +2797,7 @@ def test_move_dir_altsep_to_dir(self): def test_existing_file_inside_dest_dir(self): # A file with the same name inside the destination dir already exists. - with open(self.dst_file, "wb"): - pass + create_file(self.dst_file) self.assertRaises(shutil.Error, shutil.move, self.src_file, self.dst_dir) def test_dont_move_dir_in_itself(self): @@ -2951,8 +3212,7 @@ def test_empty_file(self): dstname = TESTFN + 'dst' self.addCleanup(lambda: os_helper.unlink(srcname)) self.addCleanup(lambda: os_helper.unlink(dstname)) - with open(srcname, "wb"): - pass + create_file(srcname) with open(srcname, "rb") as src: with open(dstname, "wb") as dst: @@ -3075,7 +3335,7 @@ def test_blocksize_arg(self): self.assertEqual(blocksize, os.path.getsize(TESTFN)) # ...unless we're dealing with a small file. os_helper.unlink(TESTFN2) - write_file(TESTFN2, b"hello", binary=True) + create_file(TESTFN2, b"hello") self.addCleanup(os_helper.unlink, TESTFN2 + '3') self.assertRaises(ZeroDivisionError, shutil.copyfile, TESTFN2, TESTFN2 + '3') @@ -3147,6 +3407,7 @@ def test_bad_environ(self): self.assertGreaterEqual(size.lines, 0) @unittest.skipUnless(os.isatty(sys.__stdout__.fileno()), "not on tty") + @support.requires_subprocess() @unittest.skipUnless(hasattr(os, 'get_terminal_size'), 'need os.get_terminal_size()') def test_stty_match(self): @@ -3164,8 +3425,7 @@ def test_stty_match(self): expected = (int(size[1]), int(size[0])) # reversed order with os_helper.EnvironmentVarGuard() as env: - del env['LINES'] - del env['COLUMNS'] + env.unset('LINES', 'COLUMNS') actual = shutil.get_terminal_size() self.assertEqual(expected, actual) @@ -3173,8 +3433,7 @@ def test_stty_match(self): @unittest.skipIf(support.is_wasi, "WASI has no /dev/null") def test_fallback(self): with os_helper.EnvironmentVarGuard() as env: - del env['LINES'] - del env['COLUMNS'] + env.unset('LINES', 'COLUMNS') # sys.__stdout__ has no fileno() with support.swap_attr(sys, '__stdout__', None): diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 05929344cc4..e056142658d 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -77,6 +77,58 @@ pub(crate) mod module { || attr & FileSystem::FILE_ATTRIBUTE_DIRECTORY != 0)) } + #[pyfunction] + #[pyfunction(name = "unlink")] + pub(super) fn remove( + path: OsPath, + dir_fd: DirFd<'static, 0>, + vm: &VirtualMachine, + ) -> PyResult<()> { + // On Windows, use DeleteFileW directly. + // Rust's std::fs::remove_file may have different behavior for read-only files. + // See Py_DeleteFileW. + use windows_sys::Win32::Storage::FileSystem::{ + DeleteFileW, FindClose, FindFirstFileW, RemoveDirectoryW, WIN32_FIND_DATAW, + }; + use windows_sys::Win32::System::SystemServices::{ + IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, + }; + + let [] = dir_fd.0; + let wide_path = path.to_wide_cstring(vm)?; + let attrs = unsafe { FileSystem::GetFileAttributesW(wide_path.as_ptr()) }; + + let mut is_directory = false; + let mut is_link = false; + + if attrs != FileSystem::INVALID_FILE_ATTRIBUTES { + is_directory = (attrs & FileSystem::FILE_ATTRIBUTE_DIRECTORY) != 0; + + // Check if it's a symlink or junction point + if is_directory && (attrs & FileSystem::FILE_ATTRIBUTE_REPARSE_POINT) != 0 { + let mut find_data: WIN32_FIND_DATAW = unsafe { std::mem::zeroed() }; + let handle = unsafe { FindFirstFileW(wide_path.as_ptr(), &mut find_data) }; + if handle != INVALID_HANDLE_VALUE { + is_link = find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK + || find_data.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT; + unsafe { FindClose(handle) }; + } + } + } + + let result = if is_directory && is_link { + unsafe { RemoveDirectoryW(wide_path.as_ptr()) } + } else { + unsafe { DeleteFileW(wide_path.as_ptr()) } + }; + + if result == 0 { + let err = io::Error::last_os_error(); + return Err(OSErrorBuilder::with_filename(&err, path, vm)); + } + Ok(()) + } + #[pyfunction] pub(super) fn _supports_virtual_terminal() -> PyResult<bool> { // TODO: implement this @@ -139,9 +191,9 @@ pub(crate) mod module { } #[derive(FromArgs)] - struct ChmodArgs { + struct ChmodArgs<'a> { #[pyarg(any)] - path: OsPath, + path: OsPathOrFd<'a>, #[pyarg(any)] mode: u32, #[pyarg(flatten)] @@ -150,17 +202,85 @@ pub(crate) mod module { follow_symlinks: OptionalArg<bool>, } + const S_IWRITE: u32 = 128; + + fn fchmod_impl(fd: i32, mode: u32, vm: &VirtualMachine) -> PyResult<()> { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_BASIC_INFO, FileBasicInfo, GetFileInformationByHandleEx, + SetFileInformationByHandle, + }; + + // Get Windows HANDLE from fd + let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd) }; + let handle = crt_fd::as_handle(borrowed).map_err(|e| e.to_pyexception(vm))?; + let hfile = handle.as_raw_handle() as Foundation::HANDLE; + + // Get current file info + let mut info: FILE_BASIC_INFO = unsafe { std::mem::zeroed() }; + let ret = unsafe { + GetFileInformationByHandleEx( + hfile, + FileBasicInfo, + &mut info as *mut _ as *mut _, + std::mem::size_of::<FILE_BASIC_INFO>() as u32, + ) + }; + if ret == 0 { + return Err(vm.new_last_os_error()); + } + + // Modify readonly attribute based on S_IWRITE bit + if mode & S_IWRITE != 0 { + info.FileAttributes &= !FileSystem::FILE_ATTRIBUTE_READONLY; + } else { + info.FileAttributes |= FileSystem::FILE_ATTRIBUTE_READONLY; + } + + // Set the new attributes + let ret = unsafe { + SetFileInformationByHandle( + hfile, + FileBasicInfo, + &info as *const _ as *const _, + std::mem::size_of::<FILE_BASIC_INFO>() as u32, + ) + }; + if ret == 0 { + return Err(vm.new_last_os_error()); + } + + Ok(()) + } + #[pyfunction] - fn chmod(args: ChmodArgs, vm: &VirtualMachine) -> PyResult<()> { + fn fchmod(fd: i32, mode: u32, vm: &VirtualMachine) -> PyResult<()> { + fchmod_impl(fd, mode, vm) + } + + #[pyfunction] + fn chmod(args: ChmodArgs<'_>, vm: &VirtualMachine) -> PyResult<()> { let ChmodArgs { path, mode, dir_fd, follow_symlinks, } = args; - const S_IWRITE: u32 = 128; let [] = dir_fd.0; + // If path is a file descriptor, use fchmod + if let OsPathOrFd::Fd(fd) = path { + if follow_symlinks.into_option().is_some() { + return Err(vm.new_value_error( + "chmod: follow_symlinks is not supported with fd argument".to_owned(), + )); + } + return fchmod_impl(fd.as_raw(), mode, vm); + } + + let OsPathOrFd::Path(path) = path else { + unreachable!() + }; + // On Windows, os.chmod behavior differs based on whether follow_symlinks is explicitly provided: // - Not provided (default): use SetFileAttributesW on the path directly (doesn't follow symlinks) // - Explicitly True: resolve symlink first, then apply permissions to target diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 28d4aeb3755..b7c0d6401bb 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -296,40 +296,6 @@ pub(super) mod _os { data.with_ref(|b| crt_fd::write(fd, b)) } - #[pyfunction] - #[pyfunction(name = "unlink")] - fn remove(path: OsPath, dir_fd: DirFd<'_, 0>, vm: &VirtualMachine) -> PyResult<()> { - let [] = dir_fd.0; - #[cfg(windows)] - let is_dir_link = { - // On Windows, we need to check if it's a directory symlink/junction - // using GetFileAttributesW, which doesn't follow symlinks. - // This is similar to CPython's Py_DeleteFileW. - use windows_sys::Win32::Storage::FileSystem::{ - FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_REPARSE_POINT, GetFileAttributesW, - INVALID_FILE_ATTRIBUTES, - }; - let wide_path: Vec<u16> = path.path.as_os_str().to_wide_with_nul(); - let attrs = unsafe { GetFileAttributesW(wide_path.as_ptr()) }; - if attrs != INVALID_FILE_ATTRIBUTES { - let is_dir = (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0; - let is_reparse = (attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0; - is_dir && is_reparse - } else { - false - } - }; - #[cfg(not(windows))] - let is_dir_link = false; - - let res = if is_dir_link { - fs::remove_dir(&path) - } else { - fs::remove_file(&path) - }; - res.map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) - } - #[cfg(not(windows))] #[pyfunction] fn mkdir( @@ -596,15 +562,16 @@ pub(super) mod _os { #[pymethod] fn is_dir(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> { + // Use cached file_type first to avoid stat() calls that may fail + if let Ok(file_type) = &self.file_type { + if !follow_symlinks.0 || !file_type.is_symlink() { + return Ok(file_type.is_dir()); + } + } match super::fs_metadata(&self.pathval, follow_symlinks.0) { Ok(meta) => Ok(meta.is_dir()), Err(e) => { if e.kind() == io::ErrorKind::NotFound { - // On Windows, use cached file_type when file is removed - #[cfg(windows)] - if let Ok(file_type) = &self.file_type { - return Ok(file_type.is_dir()); - } Ok(false) } else { Err(e.into_pyexception(vm)) @@ -615,15 +582,16 @@ pub(super) mod _os { #[pymethod] fn is_file(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> { + // Use cached file_type first to avoid stat() calls that may fail + if let Ok(file_type) = &self.file_type { + if !follow_symlinks.0 || !file_type.is_symlink() { + return Ok(file_type.is_file()); + } + } match super::fs_metadata(&self.pathval, follow_symlinks.0) { Ok(meta) => Ok(meta.is_file()), Err(e) => { if e.kind() == io::ErrorKind::NotFound { - // On Windows, use cached file_type when file is removed - #[cfg(windows)] - if let Ok(file_type) = &self.file_type { - return Ok(file_type.is_file()); - } Ok(false) } else { Err(e.into_pyexception(vm)) diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index b903e41889d..efbd0cf9049 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -475,6 +475,13 @@ pub mod module { } } + #[pyfunction] + #[pyfunction(name = "unlink")] + fn remove(path: OsPath, dir_fd: DirFd<'_, 0>, vm: &VirtualMachine) -> PyResult<()> { + let [] = dir_fd.0; + fs::remove_file(&path).map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) + } + #[cfg(not(target_os = "redox"))] #[pyfunction] fn fchdir(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult<()> { diff --git a/crates/vm/src/stdlib/posix_compat.rs b/crates/vm/src/stdlib/posix_compat.rs index b2149b43192..0fabd6a1f45 100644 --- a/crates/vm/src/stdlib/posix_compat.rs +++ b/crates/vm/src/stdlib/posix_compat.rs @@ -14,16 +14,24 @@ pub(crate) mod module { use crate::{ PyObjectRef, PyResult, VirtualMachine, builtins::PyStrRef, + convert::IntoPyException, ospath::OsPath, stdlib::os::{_os, DirFd, SupportFunc, TargetIsDirectory}, }; - use std::env; + use std::{env, fs}; #[pyfunction] pub(super) fn access(_path: PyStrRef, _mode: u8, vm: &VirtualMachine) -> PyResult<bool> { os_unimpl("os.access", vm) } + #[pyfunction] + #[pyfunction(name = "unlink")] + fn remove(path: OsPath, dir_fd: DirFd<'_, 0>, vm: &VirtualMachine) -> PyResult<()> { + let [] = dir_fd.0; + fs::remove_file(&path).map_err(|err| err.into_pyexception(vm)) + } + #[derive(FromArgs)] #[allow(unused)] pub(super) struct SymlinkArgs<'a> { From 75daf6f5ecb244e76782c1f73593013df2674910 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 16:03:11 +0900 Subject: [PATCH 609/819] fix is_file (#6563) --- crates/vm/src/stdlib/os.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index b7c0d6401bb..f20046a1a66 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -563,10 +563,10 @@ pub(super) mod _os { #[pymethod] fn is_dir(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> { // Use cached file_type first to avoid stat() calls that may fail - if let Ok(file_type) = &self.file_type { - if !follow_symlinks.0 || !file_type.is_symlink() { - return Ok(file_type.is_dir()); - } + if let Ok(file_type) = &self.file_type + && (!follow_symlinks.0 || !file_type.is_symlink()) + { + return Ok(file_type.is_dir()); } match super::fs_metadata(&self.pathval, follow_symlinks.0) { Ok(meta) => Ok(meta.is_dir()), @@ -583,10 +583,10 @@ pub(super) mod _os { #[pymethod] fn is_file(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> { // Use cached file_type first to avoid stat() calls that may fail - if let Ok(file_type) = &self.file_type { - if !follow_symlinks.0 || !file_type.is_symlink() { - return Ok(file_type.is_file()); - } + if let Ok(file_type) = &self.file_type + && (!follow_symlinks.0 || !file_type.is_symlink()) + { + return Ok(file_type.is_file()); } match super::fs_metadata(&self.pathval, follow_symlinks.0) { Ok(meta) => Ok(meta.is_file()), From 7b36c9e8e0e3f145b3352b1538337cd554fb5662 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 16:27:32 +0900 Subject: [PATCH 610/819] Handle oversized __hash__ results without panicking (#6561) * Initial plan * Fix hash wrapper overflow handling Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> * Auto-format: cargo fmt --all * Adjust __hash__ wrapper conversion Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> * Auto-format: cargo fmt --all --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- crates/vm/src/types/slot.rs | 8 ++++---- extra_tests/snippets/builtin_hash.py | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index b7c20041d00..9823f189a26 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -5,7 +5,7 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyInt, PyStr, PyStrInterned, PyStrRef, PyType, PyTypeRef}, bytecode::ComparisonOperator, - common::hash::PyHash, + common::hash::{PyHash, fix_sentinel, hash_bigint}, convert::ToPyObject, function::{ Either, FromArgs, FuncArgs, OptionalArg, PyComparisonValue, PyMethodDef, PySetterValue, @@ -18,7 +18,6 @@ use crate::{ vm::Context, }; use crossbeam_utils::atomic::AtomicCell; -use malachite_bigint::BigInt; use num_traits::{Signed, ToPrimitive}; use std::{any::Any, any::TypeId, borrow::Borrow, cmp::Ordering, ops::Deref}; @@ -412,9 +411,10 @@ fn hash_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> { .downcast_ref::<PyInt>() .ok_or_else(|| vm.new_type_error("__hash__ method should return an integer"))?; let big_int = py_int.as_bigint(); - let hash: PyHash = big_int + let hash = big_int .to_i64() - .unwrap_or_else(|| (big_int % BigInt::from(u64::MAX)).to_i64().unwrap()); + .map(fix_sentinel) + .unwrap_or_else(|| hash_bigint(big_int)); Ok(hash) } diff --git a/extra_tests/snippets/builtin_hash.py b/extra_tests/snippets/builtin_hash.py index 96ccc46ba80..9b2c8388790 100644 --- a/extra_tests/snippets/builtin_hash.py +++ b/extra_tests/snippets/builtin_hash.py @@ -12,6 +12,14 @@ class A: assert type(hash(1.1)) is int assert type(hash("")) is int + +class Evil: + def __hash__(self): + return 1 << 63 + + +assert hash(Evil()) == 4 + with assert_raises(TypeError): hash({}) From 44327a8ee4b82ec94db7d6180456dbea12f34bb3 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 16:30:44 +0900 Subject: [PATCH 611/819] Upgrade test/support from CPython 3.13.11 (#6556) * Upgrade test/support from CPython 3.13.11 * fix test_support --- Lib/test/support/interpreters.py | 198 ------------------ Lib/test/support/os_helper.py | 90 ++++++--- Lib/test/support/refleak_helper.py | 8 + Lib/test/support/script_helper.py | 32 ++- Lib/test/support/socket_helper.py | 4 + Lib/test/support/threading_helper.py | 53 ++--- Lib/test/test_support.py | 292 ++++++++++++++------------- 7 files changed, 275 insertions(+), 402 deletions(-) delete mode 100644 Lib/test/support/interpreters.py create mode 100644 Lib/test/support/refleak_helper.py diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py deleted file mode 100644 index 5c484d1170d..00000000000 --- a/Lib/test/support/interpreters.py +++ /dev/null @@ -1,198 +0,0 @@ -"""Subinterpreters High Level Module.""" - -import time -import _xxsubinterpreters as _interpreters -import _xxinterpchannels as _channels - -# aliases: -from _xxsubinterpreters import is_shareable, RunFailedError -from _xxinterpchannels import ( - ChannelError, ChannelNotFoundError, ChannelEmptyError, -) - - -__all__ = [ - 'Interpreter', 'get_current', 'get_main', 'create', 'list_all', - 'SendChannel', 'RecvChannel', - 'create_channel', 'list_all_channels', 'is_shareable', - 'ChannelError', 'ChannelNotFoundError', - 'ChannelEmptyError', - ] - - -def create(*, isolated=True): - """Return a new (idle) Python interpreter.""" - id = _interpreters.create(isolated=isolated) - return Interpreter(id, isolated=isolated) - - -def list_all(): - """Return all existing interpreters.""" - return [Interpreter(id) for id in _interpreters.list_all()] - - -def get_current(): - """Return the currently running interpreter.""" - id = _interpreters.get_current() - return Interpreter(id) - - -def get_main(): - """Return the main interpreter.""" - id = _interpreters.get_main() - return Interpreter(id) - - -class Interpreter: - """A single Python interpreter.""" - - def __init__(self, id, *, isolated=None): - if not isinstance(id, (int, _interpreters.InterpreterID)): - raise TypeError(f'id must be an int, got {id!r}') - self._id = id - self._isolated = isolated - - def __repr__(self): - data = dict(id=int(self._id), isolated=self._isolated) - kwargs = (f'{k}={v!r}' for k, v in data.items()) - return f'{type(self).__name__}({", ".join(kwargs)})' - - def __hash__(self): - return hash(self._id) - - def __eq__(self, other): - if not isinstance(other, Interpreter): - return NotImplemented - else: - return other._id == self._id - - @property - def id(self): - return self._id - - @property - def isolated(self): - if self._isolated is None: - # XXX The low-level function has not been added yet. - # See bpo-.... - self._isolated = _interpreters.is_isolated(self._id) - return self._isolated - - def is_running(self): - """Return whether or not the identified interpreter is running.""" - return _interpreters.is_running(self._id) - - def close(self): - """Finalize and destroy the interpreter. - - Attempting to destroy the current interpreter results - in a RuntimeError. - """ - return _interpreters.destroy(self._id) - - def run(self, src_str, /, *, channels=None): - """Run the given source code in the interpreter. - - This blocks the current Python thread until done. - """ - _interpreters.run_string(self._id, src_str, channels) - - -def create_channel(): - """Return (recv, send) for a new cross-interpreter channel. - - The channel may be used to pass data safely between interpreters. - """ - cid = _channels.create() - recv, send = RecvChannel(cid), SendChannel(cid) - return recv, send - - -def list_all_channels(): - """Return a list of (recv, send) for all open channels.""" - return [(RecvChannel(cid), SendChannel(cid)) - for cid in _channels.list_all()] - - -class _ChannelEnd: - """The base class for RecvChannel and SendChannel.""" - - def __init__(self, id): - if not isinstance(id, (int, _channels.ChannelID)): - raise TypeError(f'id must be an int, got {id!r}') - self._id = id - - def __repr__(self): - return f'{type(self).__name__}(id={int(self._id)})' - - def __hash__(self): - return hash(self._id) - - def __eq__(self, other): - if isinstance(self, RecvChannel): - if not isinstance(other, RecvChannel): - return NotImplemented - elif not isinstance(other, SendChannel): - return NotImplemented - return other._id == self._id - - @property - def id(self): - return self._id - - -_NOT_SET = object() - - -class RecvChannel(_ChannelEnd): - """The receiving end of a cross-interpreter channel.""" - - def recv(self, *, _sentinel=object(), _delay=10 / 1000): # 10 milliseconds - """Return the next object from the channel. - - This blocks until an object has been sent, if none have been - sent already. - """ - obj = _channels.recv(self._id, _sentinel) - while obj is _sentinel: - time.sleep(_delay) - obj = _channels.recv(self._id, _sentinel) - return obj - - def recv_nowait(self, default=_NOT_SET): - """Return the next object from the channel. - - If none have been sent then return the default if one - is provided or fail with ChannelEmptyError. Otherwise this - is the same as recv(). - """ - if default is _NOT_SET: - return _channels.recv(self._id) - else: - return _channels.recv(self._id, default) - - -class SendChannel(_ChannelEnd): - """The sending end of a cross-interpreter channel.""" - - def send(self, obj): - """Send the object (i.e. its data) to the channel's receiving end. - - This blocks until the object is received. - """ - _channels.send(self._id, obj) - # XXX We are missing a low-level channel_send_wait(). - # See bpo-32604 and gh-19829. - # Until that shows up we fake it: - time.sleep(2) - - def send_nowait(self, obj): - """Send the object to the channel's receiving end. - - If the object is immediately received then return True - (else False). Otherwise this is the same as send(). - """ - # XXX Note that at the moment channel_send() only ever returns - # None. This should be fixed when channel_send_wait() is added. - # See bpo-32604 and gh-19829. - return _channels.send(self._id, obj) diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py index 70161e90132..26c467a7ad2 100644 --- a/Lib/test/support/os_helper.py +++ b/Lib/test/support/os_helper.py @@ -1,6 +1,7 @@ import collections.abc import contextlib import errno +import logging import os import re import stat @@ -10,7 +11,6 @@ import unittest import warnings -# From CPython 3.13.5 from test import support @@ -23,8 +23,8 @@ # TESTFN_UNICODE is a non-ascii filename TESTFN_UNICODE = TESTFN_ASCII + "-\xe0\xf2\u0258\u0141\u011f" -if sys.platform == 'darwin': - # In Mac OS X's VFS API file names are, by definition, canonically +if support.is_apple: + # On Apple's VFS API file names are, by definition, canonically # decomposed Unicode, encoded using UTF-8. See QA1173: # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html import unicodedata @@ -49,8 +49,8 @@ 'encoding (%s). Unicode filename tests may not be effective' % (TESTFN_UNENCODABLE, sys.getfilesystemencoding())) TESTFN_UNENCODABLE = None -# macOS and Emscripten deny unencodable filenames (invalid utf-8) -elif sys.platform not in {'darwin', 'emscripten', 'wasi'}: +# Apple and Emscripten deny unencodable filenames (invalid utf-8) +elif not support.is_apple and sys.platform not in {"emscripten", "wasi"}: try: # ascii and utf-8 cannot encode the byte 0xff b'\xff'.decode(sys.getfilesystemencoding()) @@ -199,10 +199,8 @@ def skip_unless_symlink(test): return test if ok else unittest.skip(msg)(test) -# From CPython 3.13.5 _can_hardlink = None -# From CPython 3.13.5 def can_hardlink(): global _can_hardlink if _can_hardlink is None: @@ -212,7 +210,6 @@ def can_hardlink(): return _can_hardlink -# From CPython 3.13.5 def skip_unless_hardlink(test): ok = can_hardlink() msg = "requires hardlink support" @@ -268,15 +265,15 @@ def can_chmod(): global _can_chmod if _can_chmod is not None: return _can_chmod - if not hasattr(os, "chown"): + if not hasattr(os, "chmod"): _can_chmod = False return _can_chmod try: with open(TESTFN, "wb") as f: try: - os.chmod(TESTFN, 0o777) + os.chmod(TESTFN, 0o555) mode1 = os.stat(TESTFN).st_mode - os.chmod(TESTFN, 0o666) + os.chmod(TESTFN, 0o777) mode2 = os.stat(TESTFN).st_mode except OSError as e: can = False @@ -323,6 +320,10 @@ def can_dac_override(): else: _can_dac_override = True finally: + try: + os.chmod(TESTFN, 0o700) + except OSError: + pass unlink(TESTFN) return _can_dac_override @@ -378,8 +379,12 @@ def _waitfor(func, pathname, waitall=False): # Increase the timeout and try again time.sleep(timeout) timeout *= 2 - warnings.warn('tests may fail, delete still pending for ' + pathname, - RuntimeWarning, stacklevel=4) + logging.getLogger(__name__).warning( + 'tests may fail, delete still pending for %s', + pathname, + stack_info=True, + stacklevel=4, + ) def _unlink(filename): _waitfor(os.unlink, filename) @@ -494,9 +499,14 @@ def temp_dir(path=None, quiet=False): except OSError as exc: if not quiet: raise - warnings.warn(f'tests may fail, unable to create ' - f'temporary directory {path!r}: {exc}', - RuntimeWarning, stacklevel=3) + logging.getLogger(__name__).warning( + "tests may fail, unable to create temporary directory %r: %s", + path, + exc, + exc_info=exc, + stack_info=True, + stacklevel=3, + ) if dir_created: pid = os.getpid() try: @@ -527,9 +537,15 @@ def change_cwd(path, quiet=False): except OSError as exc: if not quiet: raise - warnings.warn(f'tests may fail, unable to change the current working ' - f'directory to {path!r}: {exc}', - RuntimeWarning, stacklevel=3) + logging.getLogger(__name__).warning( + 'tests may fail, unable to change the current working directory ' + 'to %r: %s', + path, + exc, + exc_info=exc, + stack_info=True, + stacklevel=3, + ) try: yield os.getcwd() finally: @@ -612,11 +628,18 @@ def __fspath__(self): def fd_count(): """Count the number of open file descriptors. """ - if sys.platform.startswith(('linux', 'freebsd', 'emscripten')): + if sys.platform.startswith(('linux', 'android', 'freebsd', 'emscripten')): + fd_path = "/proc/self/fd" + elif support.is_apple: + fd_path = "/dev/fd" + else: + fd_path = None + + if fd_path is not None: try: - names = os.listdir("/proc/self/fd") + names = os.listdir(fd_path) # Subtract one because listdir() internally opens a file - # descriptor to list the content of the /proc/self/fd/ directory. + # descriptor to list the content of the directory. return len(names) - 1 except FileNotFoundError: pass @@ -686,9 +709,10 @@ def temp_umask(umask): class EnvironmentVarGuard(collections.abc.MutableMapping): + """Class to help protect the environment variable properly. - """Class to help protect the environment variable properly. Can be used as - a context manager.""" + Can be used as a context manager. + """ def __init__(self): self._environ = os.environ @@ -722,7 +746,6 @@ def __len__(self): def set(self, envvar, value): self[envvar] = value - # From CPython 3.13.5 def unset(self, envvar, /, *envvars): """Unset one or more environment variables.""" for ev in (envvar, *envvars): @@ -746,13 +769,16 @@ def __exit__(self, *ignore_exc): try: - import ctypes - kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) - - ERROR_FILE_NOT_FOUND = 2 - DDD_REMOVE_DEFINITION = 2 - DDD_EXACT_MATCH_ON_REMOVE = 4 - DDD_NO_BROADCAST_SYSTEM = 8 + if support.MS_WINDOWS: + import ctypes + kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + + ERROR_FILE_NOT_FOUND = 2 + DDD_REMOVE_DEFINITION = 2 + DDD_EXACT_MATCH_ON_REMOVE = 4 + DDD_NO_BROADCAST_SYSTEM = 8 + else: + raise AttributeError except (ImportError, AttributeError): def subst_drive(path): raise unittest.SkipTest('ctypes or kernel32 is not available') diff --git a/Lib/test/support/refleak_helper.py b/Lib/test/support/refleak_helper.py new file mode 100644 index 00000000000..2f86c93a1e2 --- /dev/null +++ b/Lib/test/support/refleak_helper.py @@ -0,0 +1,8 @@ +""" +Utilities for changing test behaviour while hunting +for refleaks +""" + +_hunting_for_refleaks = False +def hunting_for_refleaks(): + return _hunting_for_refleaks diff --git a/Lib/test/support/script_helper.py b/Lib/test/support/script_helper.py index c2b43f4060e..04458077d51 100644 --- a/Lib/test/support/script_helper.py +++ b/Lib/test/support/script_helper.py @@ -8,7 +8,6 @@ import os.path import subprocess import py_compile -import zipfile from importlib.util import source_from_cache from test import support @@ -64,8 +63,8 @@ class _PythonRunResult(collections.namedtuple("_PythonRunResult", """Helper for reporting Python subprocess run results""" def fail(self, cmd_line): """Provide helpful details about failed subcommand runs""" - # Limit to 80 lines to ASCII characters - maxlen = 80 * 100 + # Limit to 300 lines of ASCII characters + maxlen = 300 * 100 out, err = self.out, self.err if len(out) > maxlen: out = b'(... truncated stdout ...)' + out[-maxlen:] @@ -93,13 +92,28 @@ def fail(self, cmd_line): # Executing the interpreter in a subprocess @support.requires_subprocess() def run_python_until_end(*args, **env_vars): + """Used to implement assert_python_*. + + *args are the command line flags to pass to the python interpreter. + **env_vars keyword arguments are environment variables to set on the process. + + If __run_using_command= is supplied, it must be a list of + command line arguments to prepend to the command line used. + Useful when you want to run another command that should launch the + python interpreter via its own arguments. ["/bin/echo", "--"] for + example could print the unquoted python command line instead of + run it. + """ env_required = interpreter_requires_environment() + run_using_command = env_vars.pop('__run_using_command', None) cwd = env_vars.pop('__cwd', None) if '__isolated' in env_vars: isolated = env_vars.pop('__isolated') else: isolated = not env_vars and not env_required cmd_line = [sys.executable, '-X', 'faulthandler'] + if run_using_command: + cmd_line = run_using_command + cmd_line if isolated: # isolated mode: ignore Python environment variables, ignore user # site-packages, and don't add the current directory to sys.path @@ -218,14 +232,19 @@ def make_script(script_dir, script_basename, source, omit_suffix=False): if not omit_suffix: script_filename += os.extsep + 'py' script_name = os.path.join(script_dir, script_filename) - # The script should be encoded to UTF-8, the default string encoding - with open(script_name, 'w', encoding='utf-8') as script_file: - script_file.write(source) + if isinstance(source, str): + # The script should be encoded to UTF-8, the default string encoding + with open(script_name, 'w', encoding='utf-8') as script_file: + script_file.write(source) + else: + with open(script_name, 'wb') as script_file: + script_file.write(source) importlib.invalidate_caches() return script_name def make_zip_script(zip_dir, zip_basename, script_name, name_in_zip=None): + import zipfile zip_filename = zip_basename+os.extsep+'zip' zip_name = os.path.join(zip_dir, zip_filename) with zipfile.ZipFile(zip_name, 'w') as zip_file: @@ -252,6 +271,7 @@ def make_pkg(pkg_dir, init_source=''): def make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename, source, depth=1, compiled=False): + import zipfile unlink = [] init_name = make_script(zip_dir, '__init__', '') unlink.append(init_name) diff --git a/Lib/test/support/socket_helper.py b/Lib/test/support/socket_helper.py index 87941ee1791..a41e487f3e4 100644 --- a/Lib/test/support/socket_helper.py +++ b/Lib/test/support/socket_helper.py @@ -259,6 +259,10 @@ def filter_error(err): # raise OSError('socket error', msg) from msg elif len(a) >= 2 and isinstance(a[1], OSError): err = a[1] + # The error can also be wrapped as __cause__: + # raise URLError(f"ftp error: {exp}") from exp + elif isinstance(err, urllib.error.URLError) and err.__cause__: + err = err.__cause__ else: break filter_error(err) diff --git a/Lib/test/support/threading_helper.py b/Lib/test/support/threading_helper.py index 7f16050f32b..afa25a76f63 100644 --- a/Lib/test/support/threading_helper.py +++ b/Lib/test/support/threading_helper.py @@ -22,34 +22,37 @@ def threading_setup(): - return _thread._count(), threading._dangling.copy() + return _thread._count(), len(threading._dangling) def threading_cleanup(*original_values): - _MAX_COUNT = 100 - - for count in range(_MAX_COUNT): - values = _thread._count(), threading._dangling - if values == original_values: - break - - if not count: - # Display a warning at the first iteration - support.environment_altered = True - dangling_threads = values[1] - support.print_warning(f"threading_cleanup() failed to cleanup " - f"{values[0] - original_values[0]} threads " - f"(count: {values[0]}, " - f"dangling: {len(dangling_threads)})") - for thread in dangling_threads: - support.print_warning(f"Dangling thread: {thread!r}") - - # Don't hold references to threads - dangling_threads = None - values = None - - time.sleep(0.01) - support.gc_collect() + orig_count, orig_ndangling = original_values + + timeout = 1.0 + for _ in support.sleeping_retry(timeout, error=False): + # Copy the thread list to get a consistent output. threading._dangling + # is a WeakSet, its value changes when it's read. + dangling_threads = list(threading._dangling) + count = _thread._count() + + if count <= orig_count: + return + + # Timeout! + support.environment_altered = True + support.print_warning( + f"threading_cleanup() failed to clean up threads " + f"in {timeout:.1f} seconds\n" + f" before: thread count={orig_count}, dangling={orig_ndangling}\n" + f" after: thread count={count}, dangling={len(dangling_threads)}") + for thread in dangling_threads: + support.print_warning(f"Dangling thread: {thread!r}") + + # The warning happens when a test spawns threads and some of these threads + # are still running after the test completes. To fix this warning, join + # threads explicitly to wait until they complete. + # + # To make the warning more likely, reduce the timeout. def reap_threads(func): diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 7c8380498e3..d66afdc833c 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -1,12 +1,15 @@ +import contextlib import errno import importlib import io +import logging import os import shutil import socket import stat import subprocess import sys +import sysconfig import tempfile import textwrap import unittest @@ -18,11 +21,37 @@ from test.support import script_helper from test.support import socket_helper from test.support import warnings_helper +from test.support.testcase import ExtraAssertions TESTFN = os_helper.TESTFN -class TestSupport(unittest.TestCase): +class LogCaptureHandler(logging.StreamHandler): + # Inspired by pytest's caplog + def __init__(self): + super().__init__(io.StringIO()) + self.records = [] + + def emit(self, record) -> None: + self.records.append(record) + super().emit(record) + + def handleError(self, record): + raise + + +@contextlib.contextmanager +def _caplog(): + handler = LogCaptureHandler() + root_logger = logging.getLogger() + root_logger.addHandler(handler) + try: + yield handler + finally: + root_logger.removeHandler(handler) + + +class TestSupport(unittest.TestCase, ExtraAssertions): @classmethod def setUpClass(cls): orig_filter_len = len(warnings.filters) @@ -185,7 +214,7 @@ def test_temp_dir__existing_dir__quiet_true(self): path = os.path.realpath(path) try: - with warnings_helper.check_warnings() as recorder: + with warnings_helper.check_warnings() as recorder, _caplog() as caplog: with os_helper.temp_dir(path, quiet=True) as temp_path: self.assertEqual(path, temp_path) warnings = [str(w.message) for w in recorder.warnings] @@ -194,11 +223,14 @@ def test_temp_dir__existing_dir__quiet_true(self): finally: shutil.rmtree(path) - self.assertEqual(len(warnings), 1, warnings) - warn = warnings[0] - self.assertTrue(warn.startswith(f'tests may fail, unable to create ' - f'temporary directory {path!r}: '), - warn) + self.assertListEqual(warnings, []) + self.assertEqual(len(caplog.records), 1) + record = caplog.records[0] + self.assertStartsWith( + record.getMessage(), + f'tests may fail, unable to create ' + f'temporary directory {path!r}: ' + ) @support.requires_fork() def test_temp_dir__forked_child(self): @@ -258,35 +290,41 @@ def test_change_cwd__non_existent_dir__quiet_true(self): with os_helper.temp_dir() as parent_dir: bad_dir = os.path.join(parent_dir, 'does_not_exist') - with warnings_helper.check_warnings() as recorder: + with warnings_helper.check_warnings() as recorder, _caplog() as caplog: with os_helper.change_cwd(bad_dir, quiet=True) as new_cwd: self.assertEqual(new_cwd, original_cwd) self.assertEqual(os.getcwd(), new_cwd) warnings = [str(w.message) for w in recorder.warnings] - self.assertEqual(len(warnings), 1, warnings) - warn = warnings[0] - self.assertTrue(warn.startswith(f'tests may fail, unable to change ' - f'the current working directory ' - f'to {bad_dir!r}: '), - warn) + self.assertListEqual(warnings, []) + self.assertEqual(len(caplog.records), 1) + record = caplog.records[0] + self.assertStartsWith( + record.getMessage(), + f'tests may fail, unable to change ' + f'the current working directory ' + f'to {bad_dir!r}: ' + ) # Tests for change_cwd() def test_change_cwd__chdir_warning(self): """Check the warning message when os.chdir() fails.""" path = TESTFN + '_does_not_exist' - with warnings_helper.check_warnings() as recorder: + with warnings_helper.check_warnings() as recorder, _caplog() as caplog: with os_helper.change_cwd(path=path, quiet=True): pass messages = [str(w.message) for w in recorder.warnings] - self.assertEqual(len(messages), 1, messages) - msg = messages[0] - self.assertTrue(msg.startswith(f'tests may fail, unable to change ' - f'the current working directory ' - f'to {path!r}: '), - msg) + self.assertListEqual(messages, []) + self.assertEqual(len(caplog.records), 1) + record = caplog.records[0] + self.assertStartsWith( + record.getMessage(), + f'tests may fail, unable to change ' + f'the current working directory ' + f'to {path!r}: ', + ) # Tests for temp_cwd() @@ -420,8 +458,6 @@ def test_detect_api_mismatch__ignore(self): self.OtherClass, self.RefClass, ignore=ignore) self.assertEqual(set(), missing_items) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_check__all__(self): extra = {'tempdir'} not_exported = {'template'} @@ -432,10 +468,8 @@ def test_check__all__(self): extra = { 'TextTestResult', - 'findTestCases', - 'getTestCaseNames', 'installHandler', - 'makeSuite', + 'IsolatedAsyncioTestCase', } not_exported = {'load_tests', "TestProgram", "BaseTestSuite"} support.check__all__(self, @@ -551,119 +585,14 @@ def test_optim_args_from_interpreter_flags(self): with self.subTest(opts=opts): self.check_options(opts, 'optim_args_from_interpreter_flags') - def test_match_test(self): - class Test: - def __init__(self, test_id): - self.test_id = test_id - - def id(self): - return self.test_id - - test_access = Test('test.test_os.FileTests.test_access') - test_chdir = Test('test.test_os.Win32ErrorTests.test_chdir') - - # Test acceptance - with support.swap_attr(support, '_match_test_func', None): - # match all - support.set_match_tests([]) - self.assertTrue(support.match_test(test_access)) - self.assertTrue(support.match_test(test_chdir)) - - # match all using None - support.set_match_tests(None, None) - self.assertTrue(support.match_test(test_access)) - self.assertTrue(support.match_test(test_chdir)) - - # match the full test identifier - support.set_match_tests([test_access.id()], None) - self.assertTrue(support.match_test(test_access)) - self.assertFalse(support.match_test(test_chdir)) - - # match the module name - support.set_match_tests(['test_os'], None) - self.assertTrue(support.match_test(test_access)) - self.assertTrue(support.match_test(test_chdir)) - - # Test '*' pattern - support.set_match_tests(['test_*'], None) - self.assertTrue(support.match_test(test_access)) - self.assertTrue(support.match_test(test_chdir)) - - # Test case sensitivity - support.set_match_tests(['filetests'], None) - self.assertFalse(support.match_test(test_access)) - support.set_match_tests(['FileTests'], None) - self.assertTrue(support.match_test(test_access)) - - # Test pattern containing '.' and a '*' metacharacter - support.set_match_tests(['*test_os.*.test_*'], None) - self.assertTrue(support.match_test(test_access)) - self.assertTrue(support.match_test(test_chdir)) - - # Multiple patterns - support.set_match_tests([test_access.id(), test_chdir.id()], None) - self.assertTrue(support.match_test(test_access)) - self.assertTrue(support.match_test(test_chdir)) - - support.set_match_tests(['test_access', 'DONTMATCH'], None) - self.assertTrue(support.match_test(test_access)) - self.assertFalse(support.match_test(test_chdir)) - - # Test rejection - with support.swap_attr(support, '_match_test_func', None): - # match all - support.set_match_tests(ignore_patterns=[]) - self.assertTrue(support.match_test(test_access)) - self.assertTrue(support.match_test(test_chdir)) - - # match all using None - support.set_match_tests(None, None) - self.assertTrue(support.match_test(test_access)) - self.assertTrue(support.match_test(test_chdir)) - - # match the full test identifier - support.set_match_tests(None, [test_access.id()]) - self.assertFalse(support.match_test(test_access)) - self.assertTrue(support.match_test(test_chdir)) - - # match the module name - support.set_match_tests(None, ['test_os']) - self.assertFalse(support.match_test(test_access)) - self.assertFalse(support.match_test(test_chdir)) - - # Test '*' pattern - support.set_match_tests(None, ['test_*']) - self.assertFalse(support.match_test(test_access)) - self.assertFalse(support.match_test(test_chdir)) - - # Test case sensitivity - support.set_match_tests(None, ['filetests']) - self.assertTrue(support.match_test(test_access)) - support.set_match_tests(None, ['FileTests']) - self.assertFalse(support.match_test(test_access)) - - # Test pattern containing '.' and a '*' metacharacter - support.set_match_tests(None, ['*test_os.*.test_*']) - self.assertFalse(support.match_test(test_access)) - self.assertFalse(support.match_test(test_chdir)) - - # Multiple patterns - support.set_match_tests(None, [test_access.id(), test_chdir.id()]) - self.assertFalse(support.match_test(test_access)) - self.assertFalse(support.match_test(test_chdir)) - - support.set_match_tests(None, ['test_access', 'DONTMATCH']) - self.assertFalse(support.match_test(test_access)) - self.assertTrue(support.match_test(test_chdir)) - - @unittest.skipIf(sys.platform.startswith("win"), "TODO: RUSTPYTHON; os.dup on windows") + @unittest.skipIf(support.is_apple_mobile, "Unstable on Apple Mobile") @unittest.skipIf(support.is_emscripten, "Unstable in Emscripten") @unittest.skipIf(support.is_wasi, "Unavailable on WASI") def test_fd_count(self): - # We cannot test the absolute value of fd_count(): on old Linux - # kernel or glibc versions, os.urandom() keeps a FD open on - # /dev/urandom device and Python has 4 FD opens instead of 3. - # Test is unstable on Emscripten. The platform starts and stops + # We cannot test the absolute value of fd_count(): on old Linux kernel + # or glibc versions, os.urandom() keeps a FD open on /dev/urandom + # device and Python has 4 FD opens instead of 3. Test is unstable on + # Emscripten and Apple Mobile platforms; these platforms start and stop # background threads that use pipes and epoll fds. start = os_helper.fd_count() fd = os.open(__file__, os.O_RDONLY) @@ -685,13 +614,16 @@ def test_print_warning(self): self.check_print_warning("a\nb", 'Warning -- a\nWarning -- b\n') - @unittest.expectedFailureIf(sys.platform != "win32", "TODO: RUSTPYTHON") + # TODO: RUSTPYTHON - strftime extension not fully supported on non-Windows + @unittest.skipUnless(sys.platform == "win32" or support.is_emscripten, + "strftime extension not fully supported on non-Windows") def test_has_strftime_extensions(self): if support.is_emscripten or sys.platform == "win32": self.assertFalse(support.has_strftime_extensions) else: self.assertTrue(support.has_strftime_extensions) + # TODO: RUSTPYTHON - _testinternalcapi module not available @unittest.expectedFailure def test_get_recursion_depth(self): # test support.get_recursion_depth() @@ -736,13 +668,15 @@ def test_recursive(depth, limit): """) script_helper.assert_python_ok("-c", code) + # TODO: RUSTPYTHON - stack overflow in debug mode with deep recursion + @unittest.skip("TODO: RUSTPYTHON - causes segfault in debug builds") def test_recursion(self): # Test infinite_recursion() and get_recursion_available() functions. def recursive_function(depth): if depth: recursive_function(depth - 1) - for max_depth in (5, 25, 250): + for max_depth in (5, 25, 250, 2500): with support.infinite_recursion(max_depth): available = support.get_recursion_available() @@ -768,7 +702,85 @@ def recursive_function(depth): else: self.fail("RecursionError was not raised") - #self.assertEqual(available, 2) + def test_parse_memlimit(self): + parse = support._parse_memlimit + KiB = 1024 + MiB = KiB * 1024 + GiB = MiB * 1024 + TiB = GiB * 1024 + self.assertEqual(parse('0k'), 0) + self.assertEqual(parse('3k'), 3 * KiB) + self.assertEqual(parse('2.4m'), int(2.4 * MiB)) + self.assertEqual(parse('4g'), int(4 * GiB)) + self.assertEqual(parse('1t'), TiB) + + for limit in ('', '3', '3.5.10k', '10x'): + with self.subTest(limit=limit): + with self.assertRaises(ValueError): + parse(limit) + + def test_set_memlimit(self): + _4GiB = 4 * 1024 ** 3 + TiB = 1024 ** 4 + old_max_memuse = support.max_memuse + old_real_max_memuse = support.real_max_memuse + try: + if sys.maxsize > 2**32: + support.set_memlimit('4g') + self.assertEqual(support.max_memuse, _4GiB) + self.assertEqual(support.real_max_memuse, _4GiB) + + big = 2**100 // TiB + support.set_memlimit(f'{big}t') + self.assertEqual(support.max_memuse, sys.maxsize) + self.assertEqual(support.real_max_memuse, big * TiB) + else: + support.set_memlimit('4g') + self.assertEqual(support.max_memuse, sys.maxsize) + self.assertEqual(support.real_max_memuse, _4GiB) + finally: + support.max_memuse = old_max_memuse + support.real_max_memuse = old_real_max_memuse + + def test_copy_python_src_ignore(self): + # Get source directory + src_dir = sysconfig.get_config_var('abs_srcdir') + if not src_dir: + src_dir = sysconfig.get_config_var('srcdir') + src_dir = os.path.abspath(src_dir) + + # Check that the source code is available + if not os.path.exists(src_dir): + self.skipTest(f"cannot access Python source code directory:" + f" {src_dir!r}") + # Check that the landmark copy_python_src_ignore() expects is available + # (Previously we looked for 'Lib\os.py', which is always present on Windows.) + landmark = os.path.join(src_dir, 'Modules') + if not os.path.exists(landmark): + self.skipTest(f"cannot access Python source code directory:" + f" {landmark!r} landmark is missing") + + # Test support.copy_python_src_ignore() + + # Source code directory + ignored = {'.git', '__pycache__'} + names = os.listdir(src_dir) + self.assertEqual(support.copy_python_src_ignore(src_dir, names), + ignored | {'build'}) + + # Doc/ directory + path = os.path.join(src_dir, 'Doc') + self.assertEqual(support.copy_python_src_ignore(path, os.listdir(path)), + ignored | {'build', 'venv'}) + + # Another directory + path = os.path.join(src_dir, 'Objects') + self.assertEqual(support.copy_python_src_ignore(path, os.listdir(path)), + ignored) + + def test_linked_to_musl(self): + linked = support.linked_to_musl() + self.assertIsInstance(linked, bool) # XXX -follows a list of untested API # make_legacy_pyc @@ -781,12 +793,10 @@ def recursive_function(depth): # EnvironmentVarGuard # transient_internet # run_with_locale - # set_memlimit # bigmemtest # precisionbigmemtest # bigaddrspacetest # requires_resource - # run_doctest # threading_cleanup # reap_threads # can_symlink From 3600b6652dcc50ce39f94a97d8107ccaffb67d82 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 18:06:47 +0900 Subject: [PATCH 612/819] update _pyio, test_fileio from 3.13.11 and impl more io features (#6560) * Update _pyio, test_fileio from 3.13.11 * impl more io * unmark sucessful tests * fix windows fileio --- Lib/_pyio.py | 55 +++-- Lib/test/test_file_eintr.py | 36 +-- Lib/test/test_fileio.py | 26 -- Lib/test/test_io.py | 66 +++-- Lib/test/test_subprocess.py | 2 - Lib/test/test_tarfile.py | 4 - crates/common/src/crt_fd.rs | 16 +- crates/derive-impl/src/pystructseq.rs | 121 +++++++-- crates/vm/src/exceptions.rs | 68 +++++- crates/vm/src/stdlib/io.rs | 340 +++++++++++++++++++++----- crates/vm/src/stdlib/os.rs | 20 ++ 11 files changed, 563 insertions(+), 191 deletions(-) diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 2629ed9e009..48c8f770f81 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -33,11 +33,8 @@ # Rebind for compatibility BlockingIOError = BlockingIOError -# Does io.IOBase finalizer log the exception if the close() method fails? -# The exception is ignored silently by default in release build. -_IOBASE_EMITS_UNRAISABLE = (hasattr(sys, "gettotalrefcount") or sys.flags.dev_mode) # Does open() check its 'errors' argument? -_CHECK_ERRORS = _IOBASE_EMITS_UNRAISABLE +_CHECK_ERRORS = (hasattr(sys, "gettotalrefcount") or sys.flags.dev_mode) def text_encoding(encoding, stacklevel=2): @@ -416,18 +413,12 @@ def __del__(self): if closed: return - if _IOBASE_EMITS_UNRAISABLE: - self.close() - else: - # The try/except block is in case this is called at program - # exit time, when it's possible that globals have already been - # deleted, and then the close() call might fail. Since - # there's nothing we can do about such failures and they annoy - # the end users, we suppress the traceback. - try: - self.close() - except: - pass + if dealloc_warn := getattr(self, "_dealloc_warn", None): + dealloc_warn(self) + + # If close() fails, the caller logs the exception with + # sys.unraisablehook. close() must be called at the end at __del__(). + self.close() ### Inquiries ### @@ -632,16 +623,15 @@ def read(self, size=-1): n = self.readinto(b) if n is None: return None + if n < 0 or n > len(b): + raise ValueError(f"readinto returned {n} outside buffer size {len(b)}") del b[n:] return bytes(b) def readall(self): """Read until EOF, using multiple read() call.""" res = bytearray() - while True: - data = self.read(DEFAULT_BUFFER_SIZE) - if not data: - break + while data := self.read(DEFAULT_BUFFER_SIZE): res += data if res: return bytes(res) @@ -666,8 +656,6 @@ def write(self, b): self._unsupported("write") io.RawIOBase.register(RawIOBase) -from _io import FileIO -RawIOBase.register(FileIO) class BufferedIOBase(IOBase): @@ -874,6 +862,10 @@ def __repr__(self): else: return "<{}.{} name={!r}>".format(modname, clsname, name) + def _dealloc_warn(self, source): + if dealloc_warn := getattr(self.raw, "_dealloc_warn", None): + dealloc_warn(source) + ### Lower-level APIs ### def fileno(self): @@ -1511,6 +1503,11 @@ def __init__(self, file, mode='r', closefd=True, opener=None): if isinstance(file, float): raise TypeError('integer argument expected, got float') if isinstance(file, int): + if isinstance(file, bool): + import warnings + warnings.warn("bool is used as a file descriptor", + RuntimeWarning, stacklevel=2) + file = int(file) fd = file if fd < 0: raise ValueError('negative file descriptor') @@ -1569,7 +1566,8 @@ def __init__(self, file, mode='r', closefd=True, opener=None): if not isinstance(fd, int): raise TypeError('expected integer from opener') if fd < 0: - raise OSError('Negative file descriptor') + # bpo-27066: Raise a ValueError for bad value. + raise ValueError(f'opener returned {fd}') owned_fd = fd if not noinherit_flag: os.set_inheritable(fd, False) @@ -1608,12 +1606,11 @@ def __init__(self, file, mode='r', closefd=True, opener=None): raise self._fd = fd - def __del__(self): + def _dealloc_warn(self, source): if self._fd >= 0 and self._closefd and not self.closed: import warnings - warnings.warn('unclosed file %r' % (self,), ResourceWarning, + warnings.warn(f'unclosed file {source!r}', ResourceWarning, stacklevel=2, source=self) - self.close() def __getstate__(self): raise TypeError(f"cannot pickle {self.__class__.__name__!r} object") @@ -1757,7 +1754,7 @@ def close(self): """ if not self.closed: try: - if self._closefd: + if self._closefd and self._fd >= 0: os.close(self._fd) finally: super().close() @@ -2649,6 +2646,10 @@ def readline(self, size=None): def newlines(self): return self._decoder.newlines if self._decoder else None + def _dealloc_warn(self, source): + if dealloc_warn := getattr(self.buffer, "_dealloc_warn", None): + dealloc_warn(source) + class StringIO(TextIOWrapper): """Text I/O implementation using an in-memory buffer. diff --git a/Lib/test/test_file_eintr.py b/Lib/test/test_file_eintr.py index 13260b8e498..fa9c6637fd4 100644 --- a/Lib/test/test_file_eintr.py +++ b/Lib/test/test_file_eintr.py @@ -186,15 +186,7 @@ def test_readall(self): class CTestFileIOSignalInterrupt(TestFileIOSignalInterrupt, unittest.TestCase): modname = '_io' - # TODO: RUSTPYTHON - _io module uses _pyio internally, signal handling differs - @unittest.expectedFailure - def test_readline(self): - super().test_readline() - - @unittest.expectedFailure - def test_readlines(self): - super().test_readlines() - + # TODO: RUSTPYTHON - _io.FileIO.readall uses read_to_end which differs from _pyio.FileIO.readall @unittest.expectedFailure def test_readall(self): super().test_readall() @@ -221,19 +213,6 @@ def test_readall(self): class CTestBufferedIOSignalInterrupt(TestBufferedIOSignalInterrupt, unittest.TestCase): modname = '_io' - # TODO: RUSTPYTHON - _io module uses _pyio internally, signal handling differs - @unittest.expectedFailure - def test_readline(self): - super().test_readline() - - @unittest.expectedFailure - def test_readlines(self): - super().test_readlines() - - @unittest.expectedFailure - def test_readall(self): - super().test_readall() - class PyTestBufferedIOSignalInterrupt(TestBufferedIOSignalInterrupt, unittest.TestCase): modname = '_pyio' @@ -273,19 +252,6 @@ def test_readall(self): class CTestTextIOSignalInterrupt(TestTextIOSignalInterrupt, unittest.TestCase): modname = '_io' - # TODO: RUSTPYTHON - _io module uses _pyio internally, signal handling differs - @unittest.expectedFailure - def test_readline(self): - super().test_readline() - - @unittest.expectedFailure - def test_readlines(self): - super().test_readlines() - - @unittest.expectedFailure - def test_readall(self): - super().test_readall() - class PyTestTextIOSignalInterrupt(TestTextIOSignalInterrupt, unittest.TestCase): modname = '_pyio' diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py index edc29b34d54..fdb36ed997d 100644 --- a/Lib/test/test_fileio.py +++ b/Lib/test/test_fileio.py @@ -363,27 +363,6 @@ class CAutoFileTests(AutoFileTests, unittest.TestCase): FileIO = _io.FileIO modulename = '_io' - @unittest.expectedFailure # TODO: RUSTPYTHON - def testBlksize(self): - return super().testBlksize() - - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") - def testErrnoOnClosedTruncate(self): - return super().testErrnoOnClosedTruncate() - - @unittest.expectedFailure # TODO: RUSTPYTHON - def testMethods(self): - return super().testMethods() - - @unittest.expectedFailure # TODO: RUSTPYTHON - def testOpenDirFD(self): - return super().testOpenDirFD() - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_subclass_repr(self): - return super().test_subclass_repr() - -@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON, test setUp errors on Windows") class PyAutoFileTests(AutoFileTests, unittest.TestCase): FileIO = _pyio.FileIO modulename = '_pyio' @@ -506,7 +485,6 @@ def testInvalidFd(self): import msvcrt self.assertRaises(OSError, msvcrt.get_osfhandle, make_bad_fd()) - @unittest.expectedFailure # TODO: RUSTPYTHON def testBooleanFd(self): for fd in False, True: with self.assertWarnsRegex(RuntimeWarning, @@ -634,10 +612,6 @@ def test_open_code(self): actual = f.read() self.assertEqual(expected, actual) - @unittest.expectedFailure # TODO: RUSTPYTHON - def testUnclosedFDOnException(self): - return super().testUnclosedFDOnException() - class PyOtherFileTests(OtherFileTests, unittest.TestCase): FileIO = _pyio.FileIO diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 0142208427b..2cf84d9aae9 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -871,6 +871,22 @@ def test_RawIOBase_read(self): self.assertEqual(rawio.read(2), None) self.assertEqual(rawio.read(2), b"") + def test_RawIOBase_read_bounds_checking(self): + # Make sure a `.readinto` call which returns a value outside + # (0, len(buffer)) raises. + class Misbehaved(self.RawIOBase): + def __init__(self, readinto_return) -> None: + self._readinto_return = readinto_return + def readinto(self, b): + return self._readinto_return + + with self.assertRaises(ValueError) as cm: + Misbehaved(2).read(1) + self.assertEqual(str(cm.exception), "readinto returned 2 outside buffer size 1") + for bad_size in (2147483647, sys.maxsize, -1, -1000): + with self.assertRaises(ValueError): + Misbehaved(bad_size).read() + def test_types_have_dict(self): test = ( self.IOBase(), @@ -1204,14 +1220,6 @@ def test_stringio_setstate(self): class PyIOTest(IOTest): pass - @unittest.expectedFailure # TODO: RUSTPYTHON; OSError: Negative file descriptor - def test_bad_opener_negative_1(): - return super().test_bad_opener_negative_1() - - @unittest.expectedFailure # TODO: RUSTPYTHON; OSError: Negative file descriptor - def test_bad_opener_other_negative(): - return super().test_bad_opener_other_negative() - @support.cpython_only class APIMismatchTest(unittest.TestCase): @@ -1288,7 +1296,6 @@ def _with(): # a ValueError. self.assertRaises(ValueError, _with) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_error_through_destructor(self): # Test that the exception state is not modified by a destructor, # even if close() fails. @@ -1811,6 +1818,10 @@ def test_garbage_collection(self): support.gc_collect() self.assertIsNone(wr(), wr) + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_error_through_destructor(self): + return super().test_error_through_destructor() + def test_args_error(self): # Issue #17275 with self.assertRaisesRegex(TypeError, "BufferedReader"): @@ -1824,7 +1835,6 @@ def test_bad_readinto_value(self): bufio.readline() self.assertIsNone(cm.exception.__cause__) - @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: 'bytes' object cannot be interpreted as an integer") def test_bad_readinto_type(self): rawio = self.tp(self.BytesIO(b"12")) rawio.readinto = lambda buf: b'' @@ -1841,7 +1851,6 @@ def test_flush_error_on_close(self): def test_seek_character_device_file(self): return super().test_seek_character_device_file() - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: UnsupportedOperation not raised by truncate def test_truncate_on_read_only(self): return super().test_truncate_on_read_only() @@ -1961,7 +1970,6 @@ def _seekrel(bufio): def test_writes_and_truncates(self): self.check_writes(lambda bufio: bufio.truncate(bufio.tell())) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_non_blocking(self): raw = self.MockNonBlockWriterIO() bufio = self.tp(raw, 8) @@ -2158,6 +2166,10 @@ def test_slow_close_from_thread(self): class CBufferedWriterTest(BufferedWriterTest, SizeofTest): tp = io.BufferedWriter + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_error_through_destructor(self): + return super().test_error_through_destructor() + def test_initialization(self): rawio = self.MockRawIO() bufio = self.tp(rawio) @@ -2680,6 +2692,10 @@ def test_interleaved_readline_write(self): class CBufferedRandomTest(BufferedRandomTest, SizeofTest): tp = io.BufferedRandom + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_error_through_destructor(self): + return super().test_error_through_destructor() + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; cyclic GC not supported, causes file locking') @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): @@ -3205,7 +3221,6 @@ def flush(self): support.gc_collect() self.assertEqual(record, [1, 2, 3]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_error_through_destructor(self): # Test that the exception state is not modified by a destructor, # even if close() fails. @@ -4115,6 +4130,10 @@ class CTextIOWrapperTest(TextIOWrapperTest): io = io shutdown_error = "LookupError: unknown encoding: ascii" + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_error_through_destructor(self): + return super().test_error_through_destructor() + @unittest.expectedFailure # TODO: RUSTPYTHON def test_initialization(self): r = self.BytesIO(b"\xc3\xa9\n\n") @@ -4276,7 +4295,6 @@ def test_reconfigure_write_through(self): def test_repr(self): return super().test_repr() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_uninitialized(self): return super().test_uninitialized() @@ -4596,7 +4614,6 @@ def _check_warn_on_dealloc(self, *args, **kwargs): support.gc_collect() self.assertIn(r, str(cm.warning.args[0])) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_warn_on_dealloc(self): self._check_warn_on_dealloc(os_helper.TESTFN, "wb", buffering=0) self._check_warn_on_dealloc(os_helper.TESTFN, "wb") @@ -4621,7 +4638,6 @@ def cleanup_fds(): with warnings_helper.check_no_resource_warning(self): self.open(r, *args, closefd=False, **kwargs) - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") def test_warn_on_dealloc_fd(self): self._check_warn_on_dealloc_fd("rb", buffering=0) @@ -4651,14 +4667,12 @@ def test_pickling(self): with self.assertRaisesRegex(TypeError, msg): pickle.dumps(f, protocol) - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf( support.is_emscripten, "fstat() of a pipe fd is not supported" ) def test_nonblock_pipe_write_bigbuf(self): self._test_nonblock_pipe_write(16*1024) - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf( support.is_emscripten, "fstat() of a pipe fd is not supported" ) @@ -4822,6 +4836,22 @@ class CMiscIOTest(MiscIOTest): name_of_module = "io", "_io" extra_exported = "BlockingIOError", + @unittest.expectedFailure # TODO: RUSTPYTHON; BufferedWriter seeks on non-seekable pipe + def test_nonblock_pipe_write_bigbuf(self): + return super().test_nonblock_pipe_write_bigbuf() + + @unittest.expectedFailure # TODO: RUSTPYTHON; BufferedWriter seeks on non-seekable pipe + def test_nonblock_pipe_write_smallbuf(self): + return super().test_nonblock_pipe_write_smallbuf() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_warn_on_dealloc(self): + return super().test_warn_on_dealloc() + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_warn_on_dealloc_fd(self): + return super().test_warn_on_dealloc_fd() + def test_readinto_buffer_overflow(self): # Issue #18025 class BadReader(self.io.BufferedIOBase): diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 3917c0a76d9..5c5d2a9600f 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1554,8 +1554,6 @@ def test_communicate_epipe_only_stdin(self): p.wait() p.communicate(b"x" * 2**20) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipUnless(hasattr(signal, 'SIGUSR1'), "Requires signal.SIGUSR1") @unittest.skipUnless(hasattr(os, 'kill'), diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 635a1c1c85a..4cf233d5f8b 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -1050,19 +1050,15 @@ def _test_sparse_file(self, name): s = os.stat(filename) self.assertLess(s.st_blocks * 512, s.st_size) - @unittest.expectedFailureIf(sys.platform == "linux", "TODO: RUSTPYTHON") def test_sparse_file_old(self): self._test_sparse_file("gnu/sparse") - @unittest.expectedFailureIf(sys.platform == "linux", "TODO: RUSTPYTHON") def test_sparse_file_00(self): self._test_sparse_file("gnu/sparse-0.0") - @unittest.expectedFailureIf(sys.platform == "linux", "TODO: RUSTPYTHON") def test_sparse_file_01(self): self._test_sparse_file("gnu/sparse-0.1") - @unittest.expectedFailureIf(sys.platform == "linux", "TODO: RUSTPYTHON") def test_sparse_file_10(self): self._test_sparse_file("gnu/sparse-1.0") diff --git a/crates/common/src/crt_fd.rs b/crates/common/src/crt_fd.rs index 7b8279adbe3..b873ef9c52c 100644 --- a/crates/common/src/crt_fd.rs +++ b/crates/common/src/crt_fd.rs @@ -334,7 +334,21 @@ pub fn close(fd: Owned) -> io::Result<()> { } pub fn ftruncate(fd: Borrowed<'_>, len: Offset) -> io::Result<()> { - cvt(unsafe { suppress_iph!(c::ftruncate(fd.as_raw(), len)) })?; + let ret = unsafe { suppress_iph!(c::ftruncate(fd.as_raw(), len)) }; + // On Windows, _chsize_s returns 0 on success, or a positive error code (errno value) on failure. + // On other platforms, ftruncate returns 0 on success, or -1 on failure with errno set. + #[cfg(windows)] + { + if ret != 0 { + // _chsize_s returns errno directly, convert to Windows error code + let winerror = crate::os::errno_to_winerror(ret); + return Err(io::Error::from_raw_os_error(winerror)); + } + } + #[cfg(not(windows))] + { + cvt(ret)?; + } Ok(()) } diff --git a/crates/derive-impl/src/pystructseq.rs b/crates/derive-impl/src/pystructseq.rs index 6c34844696d..2ebb05075e4 100644 --- a/crates/derive-impl/src/pystructseq.rs +++ b/crates/derive-impl/src/pystructseq.rs @@ -22,6 +22,8 @@ enum FieldKind { struct ParsedField { ident: Ident, kind: FieldKind, + /// Optional cfg attributes for conditional compilation + cfg_attrs: Vec<syn::Attribute>, } /// Parsed field info from struct @@ -31,27 +33,24 @@ struct FieldInfo { } impl FieldInfo { - fn named_fields(&self) -> Vec<Ident> { + fn named_fields(&self) -> Vec<&ParsedField> { self.fields .iter() .filter(|f| f.kind == FieldKind::Named) - .map(|f| f.ident.clone()) .collect() } - fn visible_fields(&self) -> Vec<Ident> { + fn visible_fields(&self) -> Vec<&ParsedField> { self.fields .iter() .filter(|f| f.kind != FieldKind::Skipped) - .map(|f| f.ident.clone()) .collect() } - fn skipped_fields(&self) -> Vec<Ident> { + fn skipped_fields(&self) -> Vec<&ParsedField> { self.fields .iter() .filter(|f| f.kind == FieldKind::Skipped) - .map(|f| f.ident.clone()) .collect() } @@ -82,8 +81,15 @@ fn parse_fields(input: &mut DeriveInput) -> Result<FieldInfo> { let mut skip = false; let mut unnamed = false; let mut attrs_to_remove = Vec::new(); + let mut cfg_attrs = Vec::new(); for (i, attr) in field.attrs.iter().enumerate() { + // Collect cfg attributes for conditional compilation + if attr.path().is_ident("cfg") { + cfg_attrs.push(attr.clone()); + continue; + } + if !attr.path().is_ident("pystruct_sequence") { continue; } @@ -135,7 +141,11 @@ fn parse_fields(input: &mut DeriveInput) -> Result<FieldInfo> { FieldKind::Named }; - parsed_fields.push(ParsedField { ident, kind }); + parsed_fields.push(ParsedField { + ident, + kind, + cfg_attrs, + }); } Ok(FieldInfo { @@ -194,18 +204,91 @@ pub(crate) fn impl_pystruct_sequence_data( let skipped_fields = field_info.skipped_fields(); let n_unnamed_fields = field_info.n_unnamed_fields(); - // Generate field index constants for visible fields + // Generate field index constants for visible fields (with cfg guards) let field_indices: Vec<_> = visible_fields .iter() .enumerate() .map(|(i, field)| { - let const_name = format_ident!("{}_INDEX", field.to_string().to_uppercase()); + let const_name = format_ident!("{}_INDEX", field.ident.to_string().to_uppercase()); + let cfg_attrs = &field.cfg_attrs; quote! { + #(#cfg_attrs)* pub const #const_name: usize = #i; } }) .collect(); + // Generate field name entries with cfg guards for named fields + let named_field_names: Vec<_> = named_fields + .iter() + .map(|f| { + let ident = &f.ident; + let cfg_attrs = &f.cfg_attrs; + if cfg_attrs.is_empty() { + quote! { stringify!(#ident), } + } else { + quote! { + #(#cfg_attrs)* + { stringify!(#ident) }, + } + } + }) + .collect(); + + // Generate field name entries with cfg guards for skipped fields + let skipped_field_names: Vec<_> = skipped_fields + .iter() + .map(|f| { + let ident = &f.ident; + let cfg_attrs = &f.cfg_attrs; + if cfg_attrs.is_empty() { + quote! { stringify!(#ident), } + } else { + quote! { + #(#cfg_attrs)* + { stringify!(#ident) }, + } + } + }) + .collect(); + + // Generate into_tuple items with cfg guards + let visible_tuple_items: Vec<_> = visible_fields + .iter() + .map(|f| { + let ident = &f.ident; + let cfg_attrs = &f.cfg_attrs; + if cfg_attrs.is_empty() { + quote! { + ::rustpython_vm::convert::ToPyObject::to_pyobject(self.#ident, vm), + } + } else { + quote! { + #(#cfg_attrs)* + { ::rustpython_vm::convert::ToPyObject::to_pyobject(self.#ident, vm) }, + } + } + }) + .collect(); + + let skipped_tuple_items: Vec<_> = skipped_fields + .iter() + .map(|f| { + let ident = &f.ident; + let cfg_attrs = &f.cfg_attrs; + if cfg_attrs.is_empty() { + quote! { + ::rustpython_vm::convert::ToPyObject::to_pyobject(self.#ident, vm), + } + } else { + quote! { + #(#cfg_attrs)* + { ::rustpython_vm::convert::ToPyObject::to_pyobject(self.#ident, vm) }, + } + } + }) + .collect(); + // Generate TryFromObject impl only when try_from_object=true let try_from_object_impl = if try_from_object { let n_required = visible_fields.len(); @@ -233,6 +316,8 @@ pub(crate) fn impl_pystruct_sequence_data( // Generate try_from_elements trait override only when try_from_object=true let try_from_elements_trait_override = if try_from_object { + let visible_field_idents: Vec<_> = visible_fields.iter().map(|f| &f.ident).collect(); + let skipped_field_idents: Vec<_> = skipped_fields.iter().map(|f| &f.ident).collect(); quote! { fn try_from_elements( elements: Vec<::rustpython_vm::PyObjectRef>, @@ -240,8 +325,8 @@ pub(crate) fn impl_pystruct_sequence_data( ) -> ::rustpython_vm::PyResult<Self> { let mut iter = elements.into_iter(); Ok(Self { - #(#visible_fields: iter.next().unwrap().clone().try_into_value(vm)?,)* - #(#skipped_fields: match iter.next() { + #(#visible_field_idents: iter.next().unwrap().clone().try_into_value(vm)?,)* + #(#skipped_field_idents: match iter.next() { Some(v) => v.clone().try_into_value(vm)?, None => vm.ctx.none(), },)* @@ -259,20 +344,14 @@ pub(crate) fn impl_pystruct_sequence_data( // PyStructSequenceData trait impl impl ::rustpython_vm::types::PyStructSequenceData for #data_ident { - const REQUIRED_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#named_fields),)*]; - const OPTIONAL_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#skipped_fields),)*]; + const REQUIRED_FIELD_NAMES: &'static [&'static str] = &[#(#named_field_names)*]; + const OPTIONAL_FIELD_NAMES: &'static [&'static str] = &[#(#skipped_field_names)*]; const UNNAMED_FIELDS_LEN: usize = #n_unnamed_fields; fn into_tuple(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::builtins::PyTuple { let items = vec![ - #(::rustpython_vm::convert::ToPyObject::to_pyobject( - self.#visible_fields, - vm, - ),)* - #(::rustpython_vm::convert::ToPyObject::to_pyobject( - self.#skipped_fields, - vm, - ),)* + #(#visible_tuple_items)* + #(#skipped_tuple_items)* ]; ::rustpython_vm::builtins::PyTuple::new_unchecked(items.into_boxed_slice()) } diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 0e2051e5498..4e8b572e457 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1592,6 +1592,9 @@ pub(super) mod types { filename2: PyAtomicRef<Option<PyObject>>, #[cfg(windows)] winerror: PyAtomicRef<Option<PyObject>>, + // For BlockingIOError: characters written before blocking occurred + // -1 means not set (AttributeError when accessed) + written: AtomicCell<isize>, } impl crate::class::PySubclass for PyOSError { @@ -1666,6 +1669,7 @@ pub(super) mod types { filename2: filename2.into(), #[cfg(windows)] winerror: None.into(), + written: AtomicCell::new(-1), }) } @@ -1707,8 +1711,14 @@ pub(super) mod types { #[allow(deprecated)] let exc: &Py<PyOSError> = zelf.downcast_ref::<PyOSError>().unwrap(); + // Check if this is BlockingIOError - need to handle characters_written + let is_blocking_io_error = + zelf.class() + .is(vm.ctx.exceptions.blocking_io_error.as_ref()); + // SAFETY: slot_init is called during object initialization, // so fields are None and swap result can be safely ignored + let mut set_filename = true; if len <= 5 { // Only set errno/strerror when args len is 2-5 if 2 <= len { @@ -1716,7 +1726,22 @@ pub(super) mod types { let _ = unsafe { exc.strerror.swap(Some(new_args.args[1].clone())) }; } if 3 <= len { - let _ = unsafe { exc.filename.swap(Some(new_args.args[2].clone())) }; + let third_arg = &new_args.args[2]; + // BlockingIOError's 3rd argument can be the number of characters written + if is_blocking_io_error + && !vm.is_none(third_arg) + && crate::protocol::PyNumber::check(third_arg) + && let Ok(written) = third_arg.try_index(vm) + && let Ok(n) = written.try_to_primitive::<isize>(vm) + { + exc.written.store(n); + set_filename = false; + // Clear filename that was set in py_new + let _ = unsafe { exc.filename.swap(None) }; + } + if set_filename { + let _ = unsafe { exc.filename.swap(Some(third_arg.clone())) }; + } } #[cfg(windows)] if 4 <= len { @@ -1938,6 +1963,47 @@ pub(super) mod types { fn set_winerror(&self, value: Option<PyObjectRef>, vm: &VirtualMachine) { self.winerror.swap_to_temporary_refs(value, vm); } + + #[pygetset] + fn characters_written(&self, vm: &VirtualMachine) -> PyResult<isize> { + let written = self.written.load(); + if written == -1 { + Err(vm.new_attribute_error("characters_written".to_owned())) + } else { + Ok(written) + } + } + + #[pygetset(setter)] + fn set_characters_written( + &self, + value: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<()> { + match value { + None => { + // Deleting the attribute + if self.written.load() == -1 { + Err(vm.new_attribute_error("characters_written".to_owned())) + } else { + self.written.store(-1); + Ok(()) + } + } + Some(v) => { + let n = v + .try_index(vm)? + .try_to_primitive::<isize>(vm) + .map_err(|_| { + vm.new_value_error( + "cannot convert characters_written value to isize".to_owned(), + ) + })?; + self.written.store(n); + Ok(()) + } + } + } } #[pyexception(name, base = PyOSError, ctx = "blocking_io_error", impl)] diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index a02965797bc..0167df2bed4 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -9,6 +9,15 @@ cfg_if::cfg_if! { } } +// EAGAIN constant for BlockingIOError +cfg_if::cfg_if! { + if #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] { + const EAGAIN: i32 = libc::EAGAIN; + } else { + const EAGAIN: i32 = 11; // Standard POSIX value + } +} + use crate::{ PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{PyBaseExceptionRef, PyModule}, @@ -159,6 +168,7 @@ mod _io { }, common::wtf8::{Wtf8, Wtf8Buf}, convert::ToPyObject, + exceptions::cstring_error, function::{ ArgBytesLike, ArgIterable, ArgMemoryBuffer, ArgSize, Either, FuncArgs, IntoFuncArgs, OptionalArg, OptionalOption, PySetterValue, @@ -169,6 +179,7 @@ mod _io { recursion::ReprGuard, types::{ Callable, Constructor, DefaultConstructor, Destructor, Initializer, IterNext, Iterable, + Representable, }, vm::VirtualMachine, }; @@ -202,6 +213,35 @@ mod _io { } } + /// Check if an error is an OSError with errno == EINTR. + /// If so, call check_signals() and return Ok(None) to indicate retry. + /// Otherwise, return Ok(Some(val)) for success or Err for other errors. + /// This mirrors CPythons _PyIO_trap_eintr() pattern. + #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + fn trap_eintr<T>(result: PyResult<T>, vm: &VirtualMachine) -> PyResult<Option<T>> { + match result { + Ok(val) => Ok(Some(val)), + Err(exc) => { + // Check if its an OSError with errno == EINTR + if exc.fast_isinstance(vm.ctx.exceptions.os_error) + && let Ok(errno_attr) = exc.as_object().get_attr("errno", vm) + && let Ok(errno_val) = i32::try_from_object(vm, errno_attr) + && errno_val == libc::EINTR + { + vm.check_signals()?; + return Ok(None); + } + Err(exc) + } + } + } + + /// WASM version: no EINTR handling needed + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] + fn trap_eintr<T>(result: PyResult<T>, _vm: &VirtualMachine) -> PyResult<Option<T>> { + result.map(Some) + } + pub fn new_unsupported_operation(vm: &VirtualMachine, msg: String) -> PyBaseExceptionRef { vm.new_os_subtype_error(unsupported_operation().to_owned(), None, msg) .upcast() @@ -650,17 +690,26 @@ mod _io { if let Some(size) = size.to_usize() { // FIXME: unnecessary zero-init let b = PyByteArray::from(vec![0; size]).into_ref(&vm.ctx); - let n = <Option<usize>>::try_from_object( + let n = <Option<isize>>::try_from_object( vm, vm.call_method(&instance, "readinto", (b.clone(),))?, )?; - Ok(n.map(|n| { - let mut bytes = b.borrow_buf_mut(); - bytes.truncate(n); - // FIXME: try to use Arc::unwrap on the bytearray to get at the inner buffer - bytes.clone() + Ok(match n { + None => vm.ctx.none(), + Some(n) => { + // Validate the return value is within bounds + if n < 0 || (n as usize) > size { + return Err(vm.new_value_error(format!( + "readinto returned {n} outside buffer size {size}" + ))); + } + let n = n as usize; + let mut bytes = b.borrow_buf_mut(); + bytes.truncate(n); + // FIXME: try to use Arc::unwrap on the bytearray to get at the inner buffer + bytes.clone().to_pyobject(vm) + } }) - .to_pyobject(vm)) } else { vm.call_method(&instance, "readall", ()) } @@ -671,7 +720,14 @@ mod _io { let mut chunks = Vec::new(); let mut total_len = 0; loop { - let data = vm.call_method(&instance, "read", (DEFAULT_BUFFER_SIZE,))?; + // Loop with EINTR handling (PEP 475) + let data = loop { + let res = vm.call_method(&instance, "read", (DEFAULT_BUFFER_SIZE,)); + match trap_eintr(res, vm)? { + Some(val) => break val, + None => continue, + } + }; let data = <Option<PyBytesRef>>::try_from_object(vm, data)?; match data { None => { @@ -885,12 +941,20 @@ mod _io { while self.write_pos < self.write_end { let n = self.raw_write(None, self.write_pos as usize..self.write_end as usize, vm)?; - let n = n.ok_or_else(|| { - vm.new_exception_msg( - vm.ctx.exceptions.blocking_io_error.to_owned(), - "write could not complete without blocking".to_owned(), - ) - })?; + let n = match n { + Some(n) => n, + None => { + // BlockingIOError(errno, msg, characters_written=0) + return Err(vm.invoke_exception( + vm.ctx.exceptions.blocking_io_error.to_owned(), + vec![ + vm.new_pyobj(EAGAIN), + vm.new_pyobj("write could not complete without blocking"), + vm.new_pyobj(0), + ], + )?); + } + }; self.write_pos += n as Offset; self.raw_pos = self.write_pos; vm.check_signals()?; @@ -1082,15 +1146,16 @@ mod _io { let buffer_size = self.buffer.len() as _; self.adjust_position(buffer_size); self.write_end = buffer_size; - // TODO: BlockingIOError(errno, msg, written) - // written += self.buffer.len(); - return Err(vm - .new_os_subtype_error( - vm.ctx.exceptions.blocking_io_error.to_owned(), - None, - "write could not complete without blocking".to_owned(), - ) - .upcast()); + // BlockingIOError(errno, msg, characters_written) + let chars_written = written + buffer_len; + return Err(vm.invoke_exception( + vm.ctx.exceptions.blocking_io_error.to_owned(), + vec![ + vm.new_pyobj(EAGAIN), + vm.new_pyobj("write could not complete without blocking"), + vm.new_pyobj(chars_written), + ], + )?); } else { break; } @@ -1156,7 +1221,7 @@ mod _io { } }; } - while remaining > 0 { + while remaining > 0 && !self.buffer.is_empty() { // MINUS_LAST_BLOCK() in CPython let r = self.buffer.len() * (remaining / self.buffer.len()); if r == 0 { @@ -1227,30 +1292,60 @@ mod _io { )? .into_ref(&vm.ctx); - // TODO: loop if readinto() raises an interrupt - let res = - vm.call_method(self.raw.as_ref().unwrap(), "readinto", (mem_obj.clone(),)); + // Loop if readinto() raises EINTR (PEP 475) + let res = loop { + let res = vm.call_method( + self.raw.as_ref().unwrap(), + "readinto", + (mem_obj.clone(),), + ); + match trap_eintr(res, vm) { + Ok(Some(val)) => break Ok(val), + Ok(None) => continue, // EINTR, retry + Err(e) => break Err(e), + } + }; mem_obj.release(); + // Always restore the buffer, even if an error occurred *v = read_buf.take(); res? } Either::B(buf) => { - let mem_obj = PyMemoryView::from_buffer_range(buf, buf_range, vm)?; - // TODO: loop if readinto() raises an interrupt - vm.call_method(self.raw.as_ref().unwrap(), "readinto", (mem_obj,))? + let mem_obj = + PyMemoryView::from_buffer_range(buf, buf_range, vm)?.into_ref(&vm.ctx); + // Loop if readinto() raises EINTR (PEP 475) + loop { + let res = vm.call_method( + self.raw.as_ref().unwrap(), + "readinto", + (mem_obj.clone(),), + ); + match trap_eintr(res, vm)? { + Some(val) => break val, + None => continue, + } + } } }; if vm.is_none(&res) { return Ok(None); } - let n = isize::try_from_object(vm, res)?; + // Try to convert to int; if it fails, treat as -1 and chain the TypeError + let (n, type_error) = match isize::try_from_object(vm, res.clone()) { + Ok(n) => (n, None), + Err(e) => (-1, Some(e)), + }; if n < 0 || n as usize > len { - return Err(vm.new_os_error(format!( + let os_error = vm.new_os_error(format!( "raw readinto() returned invalid length {n} (should have been between 0 and {len})" - ))); + )); + if let Some(cause) = type_error { + os_error.set___cause__(Some(cause)); + } + return Err(os_error); } if n > 0 && self.abs_pos != -1 { self.abs_pos += n as Offset @@ -1293,7 +1388,14 @@ mod _io { let mut read_size = 0; loop { - let read_data = vm.call_method(self.raw.as_ref().unwrap(), "read", ())?; + // Loop with EINTR handling (PEP 475) + let read_data = loop { + let res = vm.call_method(self.raw.as_ref().unwrap(), "read", ()); + match trap_eintr(res, vm)? { + Some(val) => break val, + None => continue, + } + }; let read_data = <Option<PyBytesRef>>::try_from_object(vm, read_data)?; match read_data { @@ -1564,9 +1666,10 @@ mod _io { let pos = pos.flatten().to_pyobject(vm); let mut data = zelf.lock(vm)?; data.check_init(vm)?; - if data.writable() { - data.flush_rewind(vm)?; + if !data.writable() { + return Err(new_unsupported_operation(vm, "truncate".to_owned())); } + data.flush_rewind(vm)?; let res = vm.call_method(data.raw.as_ref().unwrap(), "truncate", (pos,))?; let _ = data.raw_tell(vm); Ok(res) @@ -2400,7 +2503,13 @@ mod _io { None if vm.state.config.settings.utf8_mode > 0 => { identifier_utf8!(vm, utf_8).to_owned() } - Some(enc) if enc.as_str() != "locale" => enc, + Some(enc) if enc.as_str() != "locale" => { + // Check for embedded null character + if enc.as_str().contains('\0') { + return Err(cstring_error(vm)); + } + enc + } _ => { // None without utf8_mode or "locale" encoding vm.import("locale", 0)? @@ -2414,6 +2523,11 @@ mod _io { .errors .unwrap_or_else(|| identifier!(vm, strict).to_owned()); + // Check for embedded null character in errors (use as_wtf8 to handle surrogates) + if errors.as_wtf8().as_bytes().contains(&0) { + return Err(cstring_error(vm)); + } + let has_read1 = vm.get_attribute_opt(buffer.clone(), "read1")?.is_some(); let seekable = vm.call_method(&buffer, "seekable", ())?.try_to_bool(vm)?; @@ -2519,7 +2633,14 @@ mod _io { } #[pyclass( - with(Constructor, Initializer, Destructor, Iterable, IterNext), + with( + Constructor, + Initializer, + Destructor, + Iterable, + IterNext, + Representable + ), flags(BASETYPE) )] impl TextIOWrapper { @@ -3396,6 +3517,46 @@ mod _io { } } + impl Representable for TextIOWrapper { + #[inline] + fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> { + let type_name = zelf.class().slot_name(); + let Some(data) = zelf.data.lock() else { + // Reentrant call + return Ok(format!("<{type_name}>")); + }; + let Some(data) = data.as_ref() else { + return Err(vm.new_value_error("I/O operation on uninitialized object".to_owned())); + }; + + let mut result = format!("<{type_name}"); + + // Add name if present + if let Ok(Some(name)) = vm.get_attribute_opt(data.buffer.clone(), "name") + && let Ok(name_repr) = name.repr(vm) + { + result.push_str(" name="); + result.push_str(name_repr.as_str()); + } + + // Add mode if present + if let Ok(Some(mode)) = vm.get_attribute_opt(data.buffer.clone(), "mode") + && let Ok(mode_repr) = mode.repr(vm) + { + result.push_str(" mode="); + result.push_str(mode_repr.as_str()); + } + + // Add encoding + result.push_str(" encoding='"); + result.push_str(data.encoding.as_str()); + result.push('\''); + + result.push('>'); + Ok(result) + } + } + impl Iterable for TextIOWrapper { fn slot_iter(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { check_closed(&zelf, vm)?; @@ -4465,7 +4626,7 @@ mod fileio { } #[pyattr] - #[pyclass(module = "io", name, base = _RawIOBase)] + #[pyclass(module = "_io", name, base = _RawIOBase)] #[derive(Debug)] pub(super) struct FileIO { _base: _RawIOBase, @@ -4473,6 +4634,7 @@ mod fileio { closefd: AtomicCell<bool>, mode: AtomicCell<Mode>, seekable: AtomicCell<Option<bool>>, + blksize: AtomicCell<i64>, } #[derive(FromArgs)] @@ -4495,6 +4657,7 @@ mod fileio { closefd: AtomicCell::new(true), mode: AtomicCell::new(Mode::empty()), seekable: AtomicCell::new(None), + blksize: AtomicCell::new(8 * 1024), // DEFAULT_BUFFER_SIZE } } } @@ -4507,6 +4670,15 @@ mod fileio { fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { // TODO: let atomic_flag_works let name = args.name; + // Check if bool is used as file descriptor + if name.class().is(vm.ctx.types.bool_type) { + crate::stdlib::warnings::warn( + vm.ctx.exceptions.runtime_warning, + "bool is used as a file descriptor".to_owned(), + 1, + vm, + )?; + } let arg_fd = if let Some(i) = name.downcast_ref::<crate::builtins::PyInt>() { let fd = i.try_to_primitive(vm)?; if fd < 0 { @@ -4557,6 +4729,7 @@ mod fileio { } } }; + let fd_is_own = arg_fd.is_none(); zelf.fd.store(fd); let fd = unsafe { crt_fd::Borrowed::borrow_raw(fd) }; let filename = filename.unwrap_or(OsPathOrFd::Fd(fd)); @@ -4576,12 +4749,25 @@ mod fileio { match fd_fstat { Ok(status) => { if (status.st_mode & libc::S_IFMT) == libc::S_IFDIR { + // If fd was passed by user, don't close it on error + if !fd_is_own { + zelf.fd.store(-1); + } let err = std::io::Error::from_raw_os_error(libc::EISDIR); return Err(OSErrorBuilder::with_filename(&err, filename, vm)); } + // Store st_blksize for _blksize property + if status.st_blksize > 1 { + #[allow(clippy::useless_conversion)] // needed for 32-bit platforms + zelf.blksize.store(i64::from(status.st_blksize)); + } } Err(err) => { if err.raw_os_error() == Some(libc::EBADF) { + // If fd was passed by user, don't close it on error + if !fd_is_own { + zelf.fd.store(-1); + } return Err(OSErrorBuilder::with_filename(&err, filename, vm)); } } @@ -4590,7 +4776,13 @@ mod fileio { #[cfg(windows)] crate::stdlib::msvcrt::setmode_binary(fd); - zelf.as_object().set_attr("name", name, vm)?; + if let Err(e) = zelf.as_object().set_attr("name", name, vm) { + // If fd was passed by user, don't close it on error + if !fd_is_own { + zelf.fd.store(-1); + } + return Err(e); + } if mode.contains(Mode::APPENDING) { let _ = os::lseek(fd, 0, libc::SEEK_END, vm); @@ -4603,17 +4795,18 @@ mod fileio { impl Representable for FileIO { #[inline] fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> { + let type_name = zelf.class().slot_name(); let fd = zelf.fd.load(); if fd < 0 { - return Ok("<_io.FileIO [closed]>".to_owned()); + return Ok(format!("<{type_name} [closed]>")); } let name_repr = repr_file_obj_name(zelf.as_object(), vm)?; let mode = zelf.mode(); let closefd = if zelf.closefd.load() { "True" } else { "False" }; let repr = if let Some(name_repr) = name_repr { - format!("<_io.FileIO name={name_repr} mode='{mode}' closefd={closefd}>") + format!("<{type_name} name={name_repr} mode='{mode}' closefd={closefd}>") } else { - format!("<_io.FileIO fd={fd} mode='{mode}' closefd={closefd}>") + format!("<{type_name} fd={fd} mode='{mode}' closefd={closefd}>") }; Ok(repr) } @@ -4648,6 +4841,11 @@ mod fileio { self.closefd.load() } + #[pygetset(name = "_blksize")] + fn blksize(&self) -> i64 { + self.blksize.load() + } + #[pymethod] fn fileno(&self, vm: &VirtualMachine) -> PyResult<i32> { let fd = self.fd.load(); @@ -4664,13 +4862,19 @@ mod fileio { } #[pymethod] - fn readable(&self) -> bool { - self.mode.load().contains(Mode::READABLE) + fn readable(&self, vm: &VirtualMachine) -> PyResult<bool> { + if self.fd.load() < 0 { + return Err(io_closed_error(vm)); + } + Ok(self.mode.load().contains(Mode::READABLE)) } #[pymethod] - fn writable(&self) -> bool { - self.mode.load().contains(Mode::WRITABLE) + fn writable(&self, vm: &VirtualMachine) -> PyResult<bool> { + if self.fd.load() < 0 { + return Err(io_closed_error(vm)); + } + Ok(self.mode.load().contains(Mode::WRITABLE)) } #[pygetset] @@ -4714,16 +4918,32 @@ mod fileio { let mut handle = zelf.get_fd(vm)?; let bytes = if let Some(read_byte) = read_byte.to_usize() { let mut bytes = vec![0; read_byte]; - let n = handle - .read(&mut bytes) - .map_err(|err| Self::io_error(zelf, err, vm))?; + // Loop on EINTR (PEP 475) + let n = loop { + match handle.read(&mut bytes) { + Ok(n) => break n, + Err(e) if e.raw_os_error() == Some(libc::EINTR) => { + vm.check_signals()?; + continue; + } + Err(e) => return Err(Self::io_error(zelf, e, vm)), + } + }; bytes.truncate(n); bytes } else { let mut bytes = vec![]; - handle - .read_to_end(&mut bytes) - .map_err(|err| Self::io_error(zelf, err, vm))?; + // Loop on EINTR (PEP 475) + loop { + match handle.read_to_end(&mut bytes) { + Ok(_) => break, + Err(e) if e.raw_os_error() == Some(libc::EINTR) => { + vm.check_signals()?; + continue; + } + Err(e) => return Err(Self::io_error(zelf, e, vm)), + } + } bytes }; @@ -4743,9 +4963,17 @@ mod fileio { let mut buf = obj.borrow_buf_mut(); let mut f = handle.take(buf.len() as _); - let ret = f - .read(&mut buf) - .map_err(|err| Self::io_error(zelf, err, vm))?; + // Loop on EINTR (PEP 475) + let ret = loop { + match f.read(&mut buf) { + Ok(n) => break n, + Err(e) if e.raw_os_error() == Some(libc::EINTR) => { + vm.check_signals()?; + continue; + } + Err(e) => return Err(Self::io_error(zelf, e, vm)), + } + }; Ok(ret) } diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index f20046a1a66..6849ea365df 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -893,6 +893,15 @@ pub(super) mod _os { #[pyarg(any, default)] #[pystruct_sequence(skip)] pub st_ctime_ns: i128, + // Unix-specific attributes + #[cfg(not(windows))] + #[pyarg(any, default)] + #[pystruct_sequence(skip)] + pub st_blksize: i64, + #[cfg(not(windows))] + #[pyarg(any, default)] + #[pystruct_sequence(skip)] + pub st_blocks: i64, #[pyarg(any, default)] #[pystruct_sequence(skip)] pub st_reparse_tag: u32, @@ -945,6 +954,13 @@ pub(super) mod _os { #[cfg(not(windows))] let st_ino = stat.st_ino; + #[cfg(not(windows))] + #[allow(clippy::useless_conversion)] // needed for 32-bit platforms + let st_blksize = i64::from(stat.st_blksize); + #[cfg(not(windows))] + #[allow(clippy::useless_conversion)] // needed for 32-bit platforms + let st_blocks = i64::from(stat.st_blocks); + Self { st_mode: vm.ctx.new_pyref(stat.st_mode), st_ino: vm.ctx.new_pyref(st_ino), @@ -962,6 +978,10 @@ pub(super) mod _os { st_atime_ns: to_ns(atime), st_mtime_ns: to_ns(mtime), st_ctime_ns: to_ns(ctime), + #[cfg(not(windows))] + st_blksize, + #[cfg(not(windows))] + st_blocks, st_reparse_tag, st_file_attributes, } From 476b75de499c413a5b0668c96a1099acaf30484a Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 28 Dec 2025 20:36:15 +0900 Subject: [PATCH 613/819] Upgrade test_io from 3.13.11 and fix more io tests (#6565) * fix various test_io * Upgrade test_io from 3.13.11 * Fix more test_io --- Lib/test/test_io.py | 99 +++++++------- crates/derive-impl/src/pystructseq.rs | 47 ++++++- crates/vm/src/stdlib/io.rs | 189 ++++++++++++++++++++++---- 3 files changed, 249 insertions(+), 86 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 2cf84d9aae9..1e594c49640 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -1818,10 +1818,6 @@ def test_garbage_collection(self): support.gc_collect() self.assertIsNone(wr(), wr) - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_error_through_destructor(self): - return super().test_error_through_destructor() - def test_args_error(self): # Issue #17275 with self.assertRaisesRegex(TypeError, "BufferedReader"): @@ -1844,12 +1840,8 @@ def test_bad_readinto_type(self): self.assertIsInstance(cm.exception.__cause__, TypeError) @unittest.expectedFailure # TODO: RUSTPYTHON - def test_flush_error_on_close(self): - return super().test_flush_error_on_close() - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_seek_character_device_file(self): - return super().test_seek_character_device_file() + def test_error_through_destructor(self): + return super().test_error_through_destructor() def test_truncate_on_read_only(self): return super().test_truncate_on_read_only() @@ -2166,10 +2158,6 @@ def test_slow_close_from_thread(self): class CBufferedWriterTest(BufferedWriterTest, SizeofTest): tp = io.BufferedWriter - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_error_through_destructor(self): - return super().test_error_through_destructor() - def test_initialization(self): rawio = self.MockRawIO() bufio = self.tp(rawio) @@ -2205,8 +2193,8 @@ def test_args_error(self): self.tp(self.BytesIO(), 1024, 1024, 1024) @unittest.expectedFailure # TODO: RUSTPYTHON - def test_flush_error_on_close(self): - return super().test_flush_error_on_close() + def test_error_through_destructor(self): + return super().test_error_through_destructor() @unittest.skip('TODO: RUSTPYTHON; fallible allocation') def test_constructor(self): @@ -2692,10 +2680,6 @@ def test_interleaved_readline_write(self): class CBufferedRandomTest(BufferedRandomTest, SizeofTest): tp = io.BufferedRandom - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_error_through_destructor(self): - return super().test_error_through_destructor() - @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; cyclic GC not supported, causes file locking') @unittest.expectedFailure # TODO: RUSTPYTHON def test_garbage_collection(self): @@ -2708,16 +2692,8 @@ def test_args_error(self): self.tp(self.BytesIO(), 1024, 1024, 1024) @unittest.expectedFailure # TODO: RUSTPYTHON - def test_flush_error_on_close(self): - return super().test_flush_error_on_close() - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_seek_character_device_file(self): - return super().test_seek_character_device_file() - - @unittest.expectedFailure # TODO: RUSTPYTHON; f.read1(1) returns b'a' - def test_read1_after_write(self): - return super().test_read1_after_write() + def test_error_through_destructor(self): + return super().test_error_through_destructor() @unittest.skip('TODO: RUSTPYTHON; fallible allocation') def test_constructor(self): @@ -3395,7 +3371,24 @@ def test_multibyte_seek_and_tell(self): self.assertEqual(f.tell(), p1) f.close() - @unittest.expectedFailure # TODO: RUSTPYTHON + def test_tell_after_readline_with_cr(self): + # Test for gh-141314: TextIOWrapper.tell() assertion failure + # when dealing with standalone carriage returns + data = b'line1\r' + with self.open(os_helper.TESTFN, "wb") as f: + f.write(data) + + with self.open(os_helper.TESTFN, "r") as f: + # Read line that ends with \r + line = f.readline() + self.assertEqual(line, "line1\n") + # This should not cause an assertion failure + pos = f.tell() + # Verify we can seek back to this position + f.seek(pos) + remaining = f.read() + self.assertEqual(remaining, "") + def test_seek_with_encoder_state(self): f = self.open(os_helper.TESTFN, "w", encoding="euc_jis_2004") f.write("\u00e6\u0300") @@ -4130,10 +4123,6 @@ class CTextIOWrapperTest(TextIOWrapperTest): io = io shutdown_error = "LookupError: unknown encoding: ascii" - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_error_through_destructor(self): - return super().test_error_through_destructor() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_initialization(self): r = self.BytesIO(b"\xc3\xa9\n\n") @@ -4291,6 +4280,10 @@ def test_reconfigure_write_fromascii(self): def test_reconfigure_write_through(self): return super().test_reconfigure_write_through() + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_error_through_destructor(self): + return super().test_error_through_destructor() + @unittest.expectedFailure # TODO: RUSTPYTHON def test_repr(self): return super().test_repr() @@ -4306,6 +4299,11 @@ def test_recursive_repr(self): def test_pickling_subclass(self): return super().test_pickling_subclass() + # TODO: RUSTPYTHON; euc_jis_2004 encoding not supported + @unittest.expectedFailure + def test_seek_with_encoder_state(self): + return super().test_seek_with_encoder_state() + class PyTextIOWrapperTest(TextIOWrapperTest): io = pyio @@ -4319,6 +4317,11 @@ def test_constructor(self): def test_newlines(self): return super().test_newlines() + # TODO: RUSTPYTHON; euc_jis_2004 encoding not supported + @unittest.expectedFailure + def test_seek_with_encoder_state(self): + return super().test_seek_with_encoder_state() + class IncrementalNewlineDecoderTest(unittest.TestCase): @@ -4836,22 +4839,6 @@ class CMiscIOTest(MiscIOTest): name_of_module = "io", "_io" extra_exported = "BlockingIOError", - @unittest.expectedFailure # TODO: RUSTPYTHON; BufferedWriter seeks on non-seekable pipe - def test_nonblock_pipe_write_bigbuf(self): - return super().test_nonblock_pipe_write_bigbuf() - - @unittest.expectedFailure # TODO: RUSTPYTHON; BufferedWriter seeks on non-seekable pipe - def test_nonblock_pipe_write_smallbuf(self): - return super().test_nonblock_pipe_write_smallbuf() - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_warn_on_dealloc(self): - return super().test_warn_on_dealloc() - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_warn_on_dealloc_fd(self): - return super().test_warn_on_dealloc_fd() - def test_readinto_buffer_overflow(self): # Issue #18025 class BadReader(self.io.BufferedIOBase): @@ -4916,6 +4903,16 @@ def test_daemon_threads_shutdown_stderr_deadlock(self): def test_check_encoding_errors(self): return super().test_check_encoding_errors() + # TODO: RUSTPYTHON; ResourceWarning not triggered by _io.FileIO + @unittest.expectedFailure + def test_warn_on_dealloc(self): + return super().test_warn_on_dealloc() + + # TODO: RUSTPYTHON; ResourceWarning not triggered by _io.FileIO + @unittest.expectedFailure + def test_warn_on_dealloc_fd(self): + return super().test_warn_on_dealloc_fd() + class PyMiscIOTest(MiscIOTest): io = pyio diff --git a/crates/derive-impl/src/pystructseq.rs b/crates/derive-impl/src/pystructseq.rs index 2ebb05075e4..32b603fe478 100644 --- a/crates/derive-impl/src/pystructseq.rs +++ b/crates/derive-impl/src/pystructseq.rs @@ -316,8 +316,44 @@ pub(crate) fn impl_pystruct_sequence_data( // Generate try_from_elements trait override only when try_from_object=true let try_from_elements_trait_override = if try_from_object { - let visible_field_idents: Vec<_> = visible_fields.iter().map(|f| &f.ident).collect(); - let skipped_field_idents: Vec<_> = skipped_fields.iter().map(|f| &f.ident).collect(); + let visible_field_inits: Vec<_> = visible_fields + .iter() + .map(|f| { + let ident = &f.ident; + let cfg_attrs = &f.cfg_attrs; + if cfg_attrs.is_empty() { + quote! { #ident: iter.next().unwrap().clone().try_into_value(vm)?, } + } else { + quote! { + #(#cfg_attrs)* + #ident: iter.next().unwrap().clone().try_into_value(vm)?, + } + } + }) + .collect(); + let skipped_field_inits: Vec<_> = skipped_fields + .iter() + .map(|f| { + let ident = &f.ident; + let cfg_attrs = &f.cfg_attrs; + if cfg_attrs.is_empty() { + quote! { + #ident: match iter.next() { + Some(v) => v.clone().try_into_value(vm)?, + None => vm.ctx.none(), + }, + } + } else { + quote! { + #(#cfg_attrs)* + #ident: match iter.next() { + Some(v) => v.clone().try_into_value(vm)?, + None => vm.ctx.none(), + }, + } + } + }) + .collect(); quote! { fn try_from_elements( elements: Vec<::rustpython_vm::PyObjectRef>, @@ -325,11 +361,8 @@ pub(crate) fn impl_pystruct_sequence_data( ) -> ::rustpython_vm::PyResult<Self> { let mut iter = elements.into_iter(); Ok(Self { - #(#visible_field_idents: iter.next().unwrap().clone().try_into_value(vm)?,)* - #(#skipped_field_idents: match iter.next() { - Some(v) => v.clone().try_into_value(vm)?, - None => vm.ctx.none(), - },)* + #(#visible_field_inits)* + #(#skipped_field_inits)* }) } } diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 0167df2bed4..89dc8bca925 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -191,6 +191,7 @@ mod _io { borrow::Cow, io::{self, Cursor, SeekFrom, prelude::*}, ops::Range, + sync::atomic::{AtomicBool, Ordering}, }; #[allow(clippy::let_and_return)] @@ -935,7 +936,7 @@ mod _io { let rewind = self.raw_offset() + (self.pos - self.write_pos); if rewind != 0 { self.raw_seek(-rewind, 1, vm)?; - self.raw_pos = -rewind; + self.raw_pos -= rewind; } while self.write_pos < self.write_end { @@ -999,7 +1000,10 @@ mod _io { }; if offset >= -self.pos && offset <= available { self.pos += offset; - return Ok(current - available + offset); + // GH-95782: character devices may report raw position 0 + // even after reading, which would make this negative + let result = current - available + offset; + return Ok(if result < 0 { 0 } else { result }); } } } @@ -1111,9 +1115,51 @@ mod _io { } } - // TODO: something something check if error is BlockingIOError? - let _ = self.flush(vm); + // if BlockingIOError, shift buffer + // and try to buffer the new data; otherwise propagate the error + match self.flush(vm) { + Ok(()) => {} + Err(e) if e.fast_isinstance(vm.ctx.exceptions.blocking_io_error) => { + if self.readable() { + self.reset_read(); + } + // Shift buffer and adjust positions + let shift = self.write_pos; + if shift > 0 { + self.buffer + .copy_within(shift as usize..self.write_end as usize, 0); + self.write_end -= shift; + self.raw_pos -= shift; + self.pos -= shift; + self.write_pos = 0; + } + let avail = self.buffer.len() - self.write_end as usize; + if buf_len <= avail { + // Everything can be buffered + let buf = obj.borrow_buf(); + self.buffer[self.write_end as usize..][..buf_len].copy_from_slice(&buf); + self.write_end += buf_len as Offset; + self.pos += buf_len as Offset; + return Ok(buf_len); + } + // Buffer as much as possible and return BlockingIOError + let buf = obj.borrow_buf(); + self.buffer[self.write_end as usize..][..avail].copy_from_slice(&buf[..avail]); + self.write_end += avail as Offset; + self.pos += avail as Offset; + return Err(vm.invoke_exception( + vm.ctx.exceptions.blocking_io_error.to_owned(), + vec![ + vm.new_pyobj(EAGAIN), + vm.new_pyobj("write could not complete without blocking"), + vm.new_pyobj(avail), + ], + )?); + } + Err(e) => return Err(e), + } + // Only reach here if flush succeeded let offset = self.raw_offset(); if offset != 0 { self.raw_seek(-offset, 1, vm)?; @@ -1556,6 +1602,7 @@ mod _io { const SEEKABLE: bool = false; fn data(&self) -> &PyThreadMutex<BufferedData>; + fn closing(&self) -> &AtomicBool; fn lock(&self, vm: &VirtualMachine) -> PyResult<PyThreadMutexGuard<'_, BufferedData>> { self.data() @@ -1694,19 +1741,25 @@ mod _io { Ok(self.lock(vm)?.raw.clone()) } + /// Get raw stream without holding the lock (for calling Python code safely) + fn get_raw_unlocked(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + let data = self.lock(vm)?; + Ok(data.check_init(vm)?.to_owned()) + } + #[pygetset] fn closed(&self, vm: &VirtualMachine) -> PyResult { - self.lock(vm)?.check_init(vm)?.get_attr("closed", vm) + self.get_raw_unlocked(vm)?.get_attr("closed", vm) } #[pygetset] fn name(&self, vm: &VirtualMachine) -> PyResult { - self.lock(vm)?.check_init(vm)?.get_attr("name", vm) + self.get_raw_unlocked(vm)?.get_attr("name", vm) } #[pygetset] fn mode(&self, vm: &VirtualMachine) -> PyResult { - self.lock(vm)?.check_init(vm)?.get_attr("mode", vm) + self.get_raw_unlocked(vm)?.get_attr("mode", vm) } #[pymethod] @@ -1750,17 +1803,19 @@ mod _io { #[pymethod] fn close(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult { - { + // Don't hold the lock while calling Python code to avoid reentrant lock issues + let raw = { let data = zelf.lock(vm)?; let raw = data.check_init(vm)?; if file_closed(raw, vm)? { return Ok(vm.ctx.none()); } - } + raw.to_owned() + }; + // Set closing flag so that concurrent write() calls will fail + zelf.closing().store(true, Ordering::Release); let flush_res = vm.call_method(zelf.as_object(), "flush", ()).map(drop); - let data = zelf.lock(vm)?; - let raw = data.raw.as_ref().unwrap(); - let close_res = vm.call_method(raw, "close", ()); + let close_res = vm.call_method(&raw, "close", ()); exception_chain(flush_res, close_res) } @@ -1774,10 +1829,9 @@ mod _io { Self::WRITABLE } - // TODO: this should be the default for an equivalent of _PyObject_GetState #[pymethod] - fn __reduce__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error(format!("cannot pickle '{}' object", zelf.class().name()))) + fn __getstate__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error(format!("cannot pickle '{}' instances", zelf.class().name()))) } } @@ -1830,6 +1884,10 @@ mod _io { let n = std::cmp::min(have as usize, n); return Ok(data.read_fast(n).unwrap()); } + // Flush write buffer before reading + if data.writable() { + data.flush_rewind(vm)?; + } let mut v = vec![0; n]; data.reset_read(); let r = data @@ -1855,6 +1913,19 @@ mod _io { ensure_unclosed(raw, "readinto of closed file", vm)?; data.readinto_generic(buf.into(), true, vm) } + + #[pymethod] + fn flush(&self, vm: &VirtualMachine) -> PyResult<()> { + // For read-only buffers, flush just calls raw.flush() + // Don't hold the lock while calling Python code to avoid reentrant lock issues + let raw = { + let data = self.reader().lock(vm)?; + data.check_init(vm)?.to_owned() + }; + ensure_unclosed(&raw, "flush of closed file", vm)?; + vm.call_method(&raw, "flush", ())?; + Ok(()) + } } fn exception_chain<T>(e1: PyResult<()>, e2: PyResult<T>) -> PyResult<T> { @@ -1874,6 +1945,7 @@ mod _io { struct BufferedReader { _base: _BufferedIOBase, data: PyThreadMutex<BufferedData>, + closing: AtomicBool, } impl BufferedMixin for BufferedReader { @@ -1884,6 +1956,10 @@ mod _io { fn data(&self) -> &PyThreadMutex<BufferedData> { &self.data } + + fn closing(&self) -> &AtomicBool { + &self.closing + } } impl BufferedReadable for BufferedReader { @@ -1922,6 +1998,27 @@ mod _io { #[pymethod] fn write(&self, obj: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> { + // Check if close() is in progress (Issue #31976) + // If closing, wait for close() to complete by spinning until raw is closed. + // Note: This spin-wait has no timeout because close() is expected to always + // complete (flush + fd close). + if self.writer().closing().load(Ordering::Acquire) { + loop { + let raw = { + let data = self.writer().lock(vm)?; + match &data.raw { + Some(raw) => raw.to_owned(), + None => break, // detached + } + }; + if file_closed(&raw, vm)? { + break; + } + // Yield to other threads + std::thread::yield_now(); + } + return Err(vm.new_value_error("write to closed file".to_owned())); + } let mut data = self.writer().lock(vm)?; let raw = data.check_init(vm)?; ensure_unclosed(raw, "write to closed file", vm)?; @@ -1944,6 +2041,7 @@ mod _io { struct BufferedWriter { _base: _BufferedIOBase, data: PyThreadMutex<BufferedData>, + closing: AtomicBool, } impl BufferedMixin for BufferedWriter { @@ -1954,6 +2052,10 @@ mod _io { fn data(&self) -> &PyThreadMutex<BufferedData> { &self.data } + + fn closing(&self) -> &AtomicBool { + &self.closing + } } impl BufferedWritable for BufferedWriter { @@ -1990,6 +2092,7 @@ mod _io { struct BufferedRandom { _base: _BufferedIOBase, data: PyThreadMutex<BufferedData>, + closing: AtomicBool, } impl BufferedMixin for BufferedRandom { @@ -2001,6 +2104,10 @@ mod _io { fn data(&self) -> &PyThreadMutex<BufferedData> { &self.data } + + fn closing(&self) -> &AtomicBool { + &self.closing + } } impl BufferedReadable for BufferedRandom { @@ -3347,8 +3454,8 @@ mod _io { } #[pymethod] - fn __reduce__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error(format!("cannot pickle '{}' object", zelf.class().name()))) + fn __getstate__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error(format!("cannot pickle '{}' instances", zelf.class().name()))) } } @@ -4908,7 +5015,7 @@ mod fileio { zelf: &Py<Self>, read_byte: OptionalSize, vm: &VirtualMachine, - ) -> PyResult<Vec<u8>> { + ) -> PyResult<Option<Vec<u8>>> { if !zelf.mode.load().contains(Mode::READABLE) { return Err(new_unsupported_operation( vm, @@ -4926,6 +5033,10 @@ mod fileio { vm.check_signals()?; continue; } + // Non-blocking mode: return None if EAGAIN + Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => { + return Ok(None); + } Err(e) => return Err(Self::io_error(zelf, e, vm)), } }; @@ -4941,17 +5052,28 @@ mod fileio { vm.check_signals()?; continue; } + // Non-blocking mode: return None if EAGAIN (only if no data read yet) + Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => { + if bytes.is_empty() { + return Ok(None); + } + break; + } Err(e) => return Err(Self::io_error(zelf, e, vm)), } } bytes }; - Ok(bytes) + Ok(Some(bytes)) } #[pymethod] - fn readinto(zelf: &Py<Self>, obj: ArgMemoryBuffer, vm: &VirtualMachine) -> PyResult<usize> { + fn readinto( + zelf: &Py<Self>, + obj: ArgMemoryBuffer, + vm: &VirtualMachine, + ) -> PyResult<Option<usize>> { if !zelf.mode.load().contains(Mode::READABLE) { return Err(new_unsupported_operation( vm, @@ -4971,15 +5093,23 @@ mod fileio { vm.check_signals()?; continue; } + // Non-blocking mode: return None if EAGAIN + Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => { + return Ok(None); + } Err(e) => return Err(Self::io_error(zelf, e, vm)), } }; - Ok(ret) + Ok(Some(ret)) } #[pymethod] - fn write(zelf: &Py<Self>, obj: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> { + fn write( + zelf: &Py<Self>, + obj: ArgBytesLike, + vm: &VirtualMachine, + ) -> PyResult<Option<usize>> { if !zelf.mode.load().contains(Mode::WRITABLE) { return Err(new_unsupported_operation( vm, @@ -4989,12 +5119,15 @@ mod fileio { let mut handle = zelf.get_fd(vm)?; - let len = obj - .with_ref(|b| handle.write(b)) - .map_err(|err| Self::io_error(zelf, err, vm))?; + let len = match obj.with_ref(|b| handle.write(b)) { + Ok(n) => n, + // Non-blocking mode: return None if EAGAIN + Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => return Ok(None), + Err(e) => return Err(Self::io_error(zelf, e, vm)), + }; //return number of bytes written - Ok(len) + Ok(Some(len)) } #[pymethod] @@ -5060,8 +5193,8 @@ mod fileio { } #[pymethod] - fn __reduce__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error(format!("cannot pickle '{}' object", zelf.class().name()))) + fn __getstate__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error(format!("cannot pickle '{}' instances", zelf.class().name()))) } } From d1ff2cde776ad737ffaa120a0eaf5340899a3ffb Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 29 Dec 2025 11:21:11 +0900 Subject: [PATCH 614/819] RichCompare contains pymethod. No AtomicCell for slots (#6562) * slots: add comparison pymethods to Comparable trait * No AtomicCell --- crates/stdlib/src/contextvars.rs | 2 +- crates/stdlib/src/sqlite.rs | 15 ++- crates/vm/src/protocol/mapping.rs | 20 ++-- crates/vm/src/protocol/sequence.rs | 52 +++++----- crates/vm/src/types/slot.rs | 158 +++++++++++++++++++++-------- 5 files changed, 156 insertions(+), 91 deletions(-) diff --git a/crates/stdlib/src/contextvars.rs b/crates/stdlib/src/contextvars.rs index 56c2657f585..f88ce398c1c 100644 --- a/crates/stdlib/src/contextvars.rs +++ b/crates/stdlib/src/contextvars.rs @@ -264,7 +264,7 @@ mod _contextvars { Err(vm.new_key_error(needle.to_owned().into())) } }), - ass_subscript: AtomicCell::new(None), + ass_subscript: None, }; &AS_MAPPING } diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index c760c2830d7..103a827e99a 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -20,7 +20,6 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { #[pymodule] mod _sqlite { - use crossbeam_utils::atomic::AtomicCell; use libsqlite3_sys::{ SQLITE_BLOB, SQLITE_DETERMINISTIC, SQLITE_FLOAT, SQLITE_INTEGER, SQLITE_NULL, SQLITE_OPEN_CREATE, SQLITE_OPEN_READWRITE, SQLITE_OPEN_URI, SQLITE_TEXT, SQLITE_TRACE_STMT, @@ -2549,19 +2548,19 @@ mod _sqlite { impl AsSequence for Blob { fn as_sequence() -> &'static PySequenceMethods { static AS_SEQUENCE: PySequenceMethods = PySequenceMethods { - length: AtomicCell::new(None), - concat: AtomicCell::new(None), - repeat: AtomicCell::new(None), - item: AtomicCell::new(None), - ass_item: AtomicCell::new(None), + length: None, + concat: None, + repeat: None, + item: None, + ass_item: None, contains: atomic_func!(|seq, _needle, vm| { Err(vm.new_type_error(format!( "argument of type '{}' is not iterable", seq.obj.class().name(), ))) }), - inplace_concat: AtomicCell::new(None), - inplace_repeat: AtomicCell::new(None), + inplace_concat: None, + inplace_repeat: None, }; &AS_SEQUENCE } diff --git a/crates/vm/src/protocol/mapping.rs b/crates/vm/src/protocol/mapping.rs index 36813bf1df8..43dafeb9238 100644 --- a/crates/vm/src/protocol/mapping.rs +++ b/crates/vm/src/protocol/mapping.rs @@ -35,13 +35,13 @@ impl PyMappingSlots { /// Copy from static PyMappingMethods pub fn copy_from(&self, methods: &PyMappingMethods) { - if let Some(f) = methods.length.load() { + if let Some(f) = methods.length { self.length.store(Some(f)); } - if let Some(f) = methods.subscript.load() { + if let Some(f) = methods.subscript { self.subscript.store(Some(f)); } - if let Some(f) = methods.ass_subscript.load() { + if let Some(f) = methods.ass_subscript { self.ass_subscript.store(Some(f)); } } @@ -50,11 +50,10 @@ impl PyMappingSlots { #[allow(clippy::type_complexity)] #[derive(Default)] pub struct PyMappingMethods { - pub length: AtomicCell<Option<fn(PyMapping<'_>, &VirtualMachine) -> PyResult<usize>>>, - pub subscript: AtomicCell<Option<fn(PyMapping<'_>, &PyObject, &VirtualMachine) -> PyResult>>, - pub ass_subscript: AtomicCell< + pub length: Option<fn(PyMapping<'_>, &VirtualMachine) -> PyResult<usize>>, + pub subscript: Option<fn(PyMapping<'_>, &PyObject, &VirtualMachine) -> PyResult>, + pub ass_subscript: Option<fn(PyMapping<'_>, &PyObject, Option<PyObjectRef>, &VirtualMachine) -> PyResult<()>>, - >, } impl std::fmt::Debug for PyMappingMethods { @@ -64,11 +63,10 @@ impl std::fmt::Debug for PyMappingMethods { } impl PyMappingMethods { - #[allow(clippy::declare_interior_mutable_const)] pub const NOT_IMPLEMENTED: Self = Self { - length: AtomicCell::new(None), - subscript: AtomicCell::new(None), - ass_subscript: AtomicCell::new(None), + length: None, + subscript: None, + ass_subscript: None, }; } diff --git a/crates/vm/src/protocol/sequence.rs b/crates/vm/src/protocol/sequence.rs index a7576e63efb..888ef91565f 100644 --- a/crates/vm/src/protocol/sequence.rs +++ b/crates/vm/src/protocol/sequence.rs @@ -42,28 +42,28 @@ impl PySequenceSlots { /// Copy from static PySequenceMethods pub fn copy_from(&self, methods: &PySequenceMethods) { - if let Some(f) = methods.length.load() { + if let Some(f) = methods.length { self.length.store(Some(f)); } - if let Some(f) = methods.concat.load() { + if let Some(f) = methods.concat { self.concat.store(Some(f)); } - if let Some(f) = methods.repeat.load() { + if let Some(f) = methods.repeat { self.repeat.store(Some(f)); } - if let Some(f) = methods.item.load() { + if let Some(f) = methods.item { self.item.store(Some(f)); } - if let Some(f) = methods.ass_item.load() { + if let Some(f) = methods.ass_item { self.ass_item.store(Some(f)); } - if let Some(f) = methods.contains.load() { + if let Some(f) = methods.contains { self.contains.store(Some(f)); } - if let Some(f) = methods.inplace_concat.load() { + if let Some(f) = methods.inplace_concat { self.inplace_concat.store(Some(f)); } - if let Some(f) = methods.inplace_repeat.load() { + if let Some(f) = methods.inplace_repeat { self.inplace_repeat.store(Some(f)); } } @@ -72,18 +72,15 @@ impl PySequenceSlots { #[allow(clippy::type_complexity)] #[derive(Default)] pub struct PySequenceMethods { - pub length: AtomicCell<Option<fn(PySequence<'_>, &VirtualMachine) -> PyResult<usize>>>, - pub concat: AtomicCell<Option<fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult>>, - pub repeat: AtomicCell<Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>>, - pub item: AtomicCell<Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>>, - pub ass_item: AtomicCell< + pub length: Option<fn(PySequence<'_>, &VirtualMachine) -> PyResult<usize>>, + pub concat: Option<fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult>, + pub repeat: Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>, + pub item: Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>, + pub ass_item: Option<fn(PySequence<'_>, isize, Option<PyObjectRef>, &VirtualMachine) -> PyResult<()>>, - >, - pub contains: - AtomicCell<Option<fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult<bool>>>, - pub inplace_concat: - AtomicCell<Option<fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult>>, - pub inplace_repeat: AtomicCell<Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>>, + pub contains: Option<fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult<bool>>, + pub inplace_concat: Option<fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult>, + pub inplace_repeat: Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>, } impl std::fmt::Debug for PySequenceMethods { @@ -93,16 +90,15 @@ impl std::fmt::Debug for PySequenceMethods { } impl PySequenceMethods { - #[allow(clippy::declare_interior_mutable_const)] pub const NOT_IMPLEMENTED: Self = Self { - length: AtomicCell::new(None), - concat: AtomicCell::new(None), - repeat: AtomicCell::new(None), - item: AtomicCell::new(None), - ass_item: AtomicCell::new(None), - contains: AtomicCell::new(None), - inplace_concat: AtomicCell::new(None), - inplace_repeat: AtomicCell::new(None), + length: None, + concat: None, + repeat: None, + item: None, + ass_item: None, + contains: None, + inplace_concat: None, + inplace_repeat: None, }; } diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 9823f189a26..658e21cba8a 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -113,7 +113,7 @@ impl<T: Any + 'static> std::ops::DerefMut for TypeDataRefMut<'_, T> { #[macro_export] macro_rules! atomic_func { ($x:expr) => { - crossbeam_utils::atomic::AtomicCell::new(Some($x)) + Some($x) }; } @@ -385,6 +385,59 @@ fn setitem_wrapper<K: ToPyObject>( .map(drop) } +#[inline(never)] +fn mapping_setitem_wrapper( + mapping: PyMapping<'_>, + key: &PyObject, + value: Option<PyObjectRef>, + vm: &VirtualMachine, +) -> PyResult<()> { + setitem_wrapper(mapping.obj, key, value, vm) +} + +#[inline(never)] +fn mapping_getitem_wrapper( + mapping: PyMapping<'_>, + key: &PyObject, + vm: &VirtualMachine, +) -> PyResult { + getitem_wrapper(mapping.obj, key, vm) +} + +#[inline(never)] +fn mapping_len_wrapper(mapping: PyMapping<'_>, vm: &VirtualMachine) -> PyResult<usize> { + len_wrapper(mapping.obj, vm) +} + +#[inline(never)] +fn sequence_len_wrapper(seq: PySequence<'_>, vm: &VirtualMachine) -> PyResult<usize> { + len_wrapper(seq.obj, vm) +} + +#[inline(never)] +fn sequence_getitem_wrapper(seq: PySequence<'_>, i: isize, vm: &VirtualMachine) -> PyResult { + getitem_wrapper(seq.obj, i, vm) +} + +#[inline(never)] +fn sequence_setitem_wrapper( + seq: PySequence<'_>, + i: isize, + value: Option<PyObjectRef>, + vm: &VirtualMachine, +) -> PyResult<()> { + setitem_wrapper(seq.obj, i, value, vm) +} + +#[inline(never)] +fn sequence_contains_wrapper( + seq: PySequence<'_>, + needle: &PyObject, + vm: &VirtualMachine, +) -> PyResult<bool> { + contains_wrapper(seq.obj, needle, vm) +} + fn repr_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> { let ret = vm.call_special_method(zelf, identifier!(vm, __repr__), ())?; ret.downcast::<PyStr>().map_err(|obj| { @@ -1139,12 +1192,7 @@ impl PyType { // === Sequence slots === SlotAccessor::SqLength => { - update_sub_slot!( - as_sequence, - length, - |seq, vm| len_wrapper(seq.obj, vm), - SeqLength - ) + update_sub_slot!(as_sequence, length, sequence_len_wrapper, SeqLength) } SlotAccessor::SqConcat | SlotAccessor::SqInplaceConcat => { // Sequence concat uses sq_concat slot - no generic wrapper needed @@ -1161,52 +1209,32 @@ impl PyType { } } SlotAccessor::SqItem => { - update_sub_slot!( - as_sequence, - item, - |seq, i, vm| getitem_wrapper(seq.obj, i, vm), - SeqItem - ) + update_sub_slot!(as_sequence, item, sequence_getitem_wrapper, SeqItem) } SlotAccessor::SqAssItem => { - update_sub_slot!( - as_sequence, - ass_item, - |seq, i, value, vm| setitem_wrapper(seq.obj, i, value, vm), - SeqAssItem - ) + update_sub_slot!(as_sequence, ass_item, sequence_setitem_wrapper, SeqAssItem) } SlotAccessor::SqContains => { update_sub_slot!( as_sequence, contains, - |seq, needle, vm| contains_wrapper(seq.obj, needle, vm), + sequence_contains_wrapper, SeqContains ) } // === Mapping slots === SlotAccessor::MpLength => { - update_sub_slot!( - as_mapping, - length, - |mapping, vm| len_wrapper(mapping.obj, vm), - MapLength - ) + update_sub_slot!(as_mapping, length, mapping_len_wrapper, MapLength) } SlotAccessor::MpSubscript => { - update_sub_slot!( - as_mapping, - subscript, - |mapping, key, vm| getitem_wrapper(mapping.obj, key, vm), - MapSubscript - ) + update_sub_slot!(as_mapping, subscript, mapping_getitem_wrapper, MapSubscript) } SlotAccessor::MpAssSubscript => { update_sub_slot!( as_mapping, ass_subscript, - |mapping, key, value, vm| setitem_wrapper(mapping.obj, key, value, vm), + mapping_setitem_wrapper, MapAssSubscript ) } @@ -1484,8 +1512,6 @@ pub trait Hashable: PyPayload { Self::hash(zelf, vm) } - // __hash__ is now exposed via SlotFunc::Hash wrapper in extend_class() - fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash>; } @@ -1535,8 +1561,60 @@ pub trait Comparable: PyPayload { vm: &VirtualMachine, ) -> PyResult<PyComparisonValue>; - // Comparison methods are exposed as wrapper_descriptor via slot_richcompare - // No #[pymethod] - add_operators creates wrapper from SLOT_DEFS + #[inline] + #[pymethod] + fn __eq__( + zelf: &Py<Self>, + other: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<PyComparisonValue> { + Self::cmp(zelf, &other, PyComparisonOp::Eq, vm) + } + #[inline] + #[pymethod] + fn __ne__( + zelf: &Py<Self>, + other: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<PyComparisonValue> { + Self::cmp(zelf, &other, PyComparisonOp::Ne, vm) + } + #[inline] + #[pymethod] + fn __lt__( + zelf: &Py<Self>, + other: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<PyComparisonValue> { + Self::cmp(zelf, &other, PyComparisonOp::Lt, vm) + } + #[inline] + #[pymethod] + fn __le__( + zelf: &Py<Self>, + other: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<PyComparisonValue> { + Self::cmp(zelf, &other, PyComparisonOp::Le, vm) + } + #[inline] + #[pymethod] + fn __ge__( + zelf: &Py<Self>, + other: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<PyComparisonValue> { + Self::cmp(zelf, &other, PyComparisonOp::Ge, vm) + } + #[inline] + #[pymethod] + fn __gt__( + zelf: &Py<Self>, + other: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<PyComparisonValue> { + Self::cmp(zelf, &other, PyComparisonOp::Gt, vm) + } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -1779,8 +1857,6 @@ pub trait Iterable: PyPayload { Self::iter(zelf, vm) } - // __iter__ is exposed via SlotFunc::Iter wrapper in extend_class() - fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult; fn extend_slots(_slots: &mut PyTypeSlots) {} @@ -1798,8 +1874,6 @@ pub trait IterNext: PyPayload + Iterable { } fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn>; - - // __next__ is exposed via SlotFunc::IterNext wrapper in extend_class() } pub trait SelfIter: PyPayload {} @@ -1814,8 +1888,6 @@ where unreachable!("slot must be overridden for {}", repr.as_str()); } - // __iter__ is exposed via SlotFunc::Iter wrapper in extend_class() - #[cold] fn iter(_zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult { unreachable!("slot_iter is implemented"); From a7d417cf634459724180bfc299e49f79aca41db0 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Sun, 28 Dec 2025 11:09:55 +0900 Subject: [PATCH 615/819] Fix socket.getattrinfo --- crates/stdlib/src/socket.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index 08b05b56aa8..8af0bdd8679 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -1951,13 +1951,31 @@ mod _socket { flags: opts.flags, }; - let host = opts.host.as_ref().map(|s| s.as_str()); - let port = opts.port.as_ref().map(|p| -> std::borrow::Cow<'_, str> { - match p { - Either::A(s) => s.as_str().into(), - Either::B(i) => i.to_string().into(), + // Encode host using IDNA encoding + let host_encoded: Option<String> = match opts.host.as_ref() { + Some(s) => { + let encoded = + vm.state + .codec_registry + .encode_text(s.to_owned().into(), "idna", None, vm)?; + let host_str = std::str::from_utf8(encoded.as_bytes()) + .map_err(|_| vm.new_runtime_error("idna output is not utf8".to_owned()))?; + Some(host_str.to_owned()) } - }); + None => None, + }; + let host = host_encoded.as_deref(); + + // Encode port using UTF-8 + let port: Option<std::borrow::Cow<'_, str>> = match opts.port.as_ref() { + Some(Either::A(s)) => { + Some(std::borrow::Cow::Borrowed(s.to_str().ok_or_else(|| { + vm.new_unicode_encode_error("surrogates not allowed".to_owned()) + })?)) + } + Some(Either::B(i)) => Some(std::borrow::Cow::Owned(i.to_string())), + None => None, + }; let port = port.as_ref().map(|p| p.as_ref()); let addrs = dns_lookup::getaddrinfo(host, port, Some(hints)) From 81c13347a4af4010a44790c37176a884b6a8146e Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Sun, 28 Dec 2025 19:15:45 +0900 Subject: [PATCH 616/819] CAN_ attrs --- crates/stdlib/src/socket.rs | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index 8af0bdd8679..f29b8cedbb7 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -138,6 +138,82 @@ mod _socket { SOL_CAN_RAW, }; + // CAN BCM opcodes + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_TX_SETUP: i32 = 1; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_TX_DELETE: i32 = 2; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_TX_READ: i32 = 3; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_TX_SEND: i32 = 4; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_RX_SETUP: i32 = 5; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_RX_DELETE: i32 = 6; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_RX_READ: i32 = 7; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_TX_STATUS: i32 = 8; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_TX_EXPIRED: i32 = 9; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_RX_STATUS: i32 = 10; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_RX_TIMEOUT: i32 = 11; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_RX_CHANGED: i32 = 12; + + // CAN BCM flags (linux/can/bcm.h) + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_SETTIMER: i32 = 0x0001; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_STARTTIMER: i32 = 0x0002; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_TX_COUNTEVT: i32 = 0x0004; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_TX_ANNOUNCE: i32 = 0x0008; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_TX_CP_CAN_ID: i32 = 0x0010; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_RX_FILTER_ID: i32 = 0x0020; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_RX_CHECK_DLC: i32 = 0x0040; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_RX_NO_AUTOTIMER: i32 = 0x0080; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_RX_ANNOUNCE_RESUME: i32 = 0x0100; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_TX_RESET_MULTI_IDX: i32 = 0x0200; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_RX_RTR_FRAME: i32 = 0x0400; + #[cfg(target_os = "linux")] + #[pyattr] + const CAN_BCM_CAN_FD_FRAME: i32 = 0x0800; + #[cfg(all(target_os = "linux", target_env = "gnu"))] #[pyattr] use c::SOL_RDS; From 219b4e316b6aac26887cba455df1b56240b47e84 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Sun, 28 Dec 2025 19:15:50 +0900 Subject: [PATCH 617/819] DEFAULT_TIMEOUT --- crates/stdlib/src/socket.rs | 56 ++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index f29b8cedbb7..1350209a9c2 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -925,10 +925,64 @@ mod _socket { sock: Socket, ) -> io::Result<()> { self.family.store(family); - self.kind.store(socket_kind); + // Mask out SOCK_NONBLOCK and SOCK_CLOEXEC flags from stored type + // to ensure consistent cross-platform behavior + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox" + ))] + let masked_kind = socket_kind & !(c::SOCK_NONBLOCK | c::SOCK_CLOEXEC); + #[cfg(not(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox" + )))] + let masked_kind = socket_kind; + self.kind.store(masked_kind); self.proto.store(proto); let mut s = self.sock.write(); let sock = s.insert(sock); + // If SOCK_NONBLOCK is set, use timeout 0 (non-blocking) + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox" + ))] + let timeout = if socket_kind & c::SOCK_NONBLOCK != 0 { + 0.0 + } else { + DEFAULT_TIMEOUT.load() + }; + #[cfg(not(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox" + )))] let timeout = DEFAULT_TIMEOUT.load(); self.timeout.store(timeout); if timeout >= 0.0 { From 2271f7464240eb5380fe45776126de0526244d72 Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Sun, 28 Dec 2025 18:25:44 +0900 Subject: [PATCH 618/819] del for socket --- Lib/test/test_socket.py | 2 + crates/stdlib/src/socket.rs | 75 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index d5a35a3253e..756603bd514 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -824,6 +824,8 @@ class GeneralModuleTests(unittest.TestCase): # TODO: RUSTPYTHON @unittest.expectedFailure @unittest.skipUnless(_socket is not None, 'need _socket module') + # TODO: RUSTPYTHON gc.is_tracked not implemented + @unittest.expectedFailure def test_socket_type(self): self.assertTrue(gc.is_tracked(_socket.socket)) with self.assertRaisesRegex(TypeError, "immutable"): diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index 1350209a9c2..aaa28a19c78 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -226,6 +226,48 @@ mod _socket { #[pyattr] use c::{AF_SYSTEM, PF_SYSTEM, SYSPROTO_CONTROL, TCP_KEEPALIVE}; + // RFC3542 IPv6 socket options for macOS (netinet6/in6.h) + // Not available in libc, define manually + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_RECVHOPLIMIT: i32 = 37; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_RECVRTHDR: i32 = 38; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_RECVHOPOPTS: i32 = 39; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_RECVDSTOPTS: i32 = 40; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_USE_MIN_MTU: i32 = 42; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_RECVPATHMTU: i32 = 43; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_PATHMTU: i32 = 44; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_NEXTHOP: i32 = 48; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_HOPOPTS: i32 = 49; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_DSTOPTS: i32 = 50; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_RTHDR: i32 = 51; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_RTHDRDSTOPTS: i32 = 57; + #[cfg(target_vendor = "apple")] + #[pyattr] + const IPV6_RTHDR_TYPE_0: i32 = 0; + #[cfg(windows)] #[pyattr] use c::{ @@ -491,6 +533,7 @@ mod _socket { target_os = "dragonfly", target_os = "freebsd", target_os = "linux", + target_vendor = "apple", windows ))] #[pyattr] @@ -1597,6 +1640,38 @@ mod _socket { Ok(()) } + #[pymethod] + fn __del__(&self, vm: &VirtualMachine) { + // Emit ResourceWarning if socket is still open + if self.sock.read().is_some() { + let laddr = if let Ok(sock) = self.sock() + && let Ok(addr) = sock.local_addr() + && let Ok(repr) = get_addr_tuple(&addr, vm).repr(vm) + { + format!(", laddr={}", repr.as_str()) + } else { + String::new() + }; + + let msg = format!( + "unclosed <socket.socket fd={}, family={}, type={}, proto={}{}>", + self.fileno(), + self.family.load(), + self.kind.load(), + self.proto.load(), + laddr + ); + let _ = crate::vm::warn::warn( + vm.ctx.new_str(msg), + Some(vm.ctx.exceptions.resource_warning.to_owned()), + 1, + None, + vm, + ); + } + let _ = self.close(); + } + #[pymethod] #[inline] fn detach(&self) -> i64 { From 5079ee8fe3b60de8112ac193157ab179b6d4b88d Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Mon, 29 Dec 2025 11:26:50 +0900 Subject: [PATCH 619/819] fix socket --- crates/stdlib/src/socket.rs | 648 +++++++++++++++++++++++++++++++++++- 1 file changed, 645 insertions(+), 3 deletions(-) diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index aaa28a19c78..8ad699d60a8 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -63,6 +63,10 @@ mod _socket { SOL_SOCKET, SOMAXCONN, TCP_NODELAY, WSAEBADF, WSAECONNRESET, WSAENOTSOCK, WSAEWOULDBLOCK, }; + pub use windows_sys::Win32::Networking::WinSock::{ + INVALID_SOCKET, SOCKET_ERROR, WSA_FLAG_OVERLAPPED, WSADuplicateSocketW, + WSAGetLastError, WSAIoctl, WSAPROTOCOL_INFOW, WSASocketW, + }; pub use windows_sys::Win32::Networking::WinSock::{ SO_REUSEADDR as SO_EXCLUSIVEADDRUSE, getprotobyname, getservbyname, getservbyport, getsockopt, setsockopt, @@ -82,6 +86,7 @@ mod _socket { pub const AI_PASSIVE: i32 = windows_sys::Win32::Networking::WinSock::AI_PASSIVE as _; pub const AI_NUMERICHOST: i32 = windows_sys::Win32::Networking::WinSock::AI_NUMERICHOST as _; + pub const FROM_PROTOCOL_INFO: i32 = -1; } // constants #[pyattr(name = "has_ipv6")] @@ -902,6 +907,21 @@ mod _socket { .map(|sock| sock as RawSocket) } + #[cfg(target_os = "linux")] + #[derive(FromArgs)] + struct SendmsgAfalgArgs { + #[pyarg(any, default)] + msg: Vec<ArgBytesLike>, + #[pyarg(named)] + op: u32, + #[pyarg(named, default)] + iv: Option<ArgBytesLike>, + #[pyarg(named, default)] + assoclen: OptionalArg<isize>, + #[pyarg(named, default)] + flags: i32, + } + #[pyattr(name = "socket")] #[pyattr(name = "SocketType")] #[pyclass(name = "socket")] @@ -1169,6 +1189,129 @@ mod _socket { } Ok(addr6.into()) } + #[cfg(target_os = "linux")] + c::AF_CAN => { + let tuple: PyTupleRef = addr.downcast().map_err(|obj| { + vm.new_type_error(format!( + "{}(): AF_CAN address must be tuple, not {}", + caller, + obj.class().name() + )) + })?; + if tuple.is_empty() || tuple.len() > 2 { + return Err(vm + .new_type_error( + "AF_CAN address must be a tuple (interface,) or (interface, addr)", + ) + .into()); + } + let interface: PyStrRef = tuple[0].clone().downcast().map_err(|obj| { + vm.new_type_error(format!( + "{}(): AF_CAN interface must be str, not {}", + caller, + obj.class().name() + )) + })?; + let ifname = interface.as_str(); + + // Get interface index + let ifindex = if ifname.is_empty() { + 0 // Bind to all CAN interfaces + } else { + // Check interface name length (IFNAMSIZ is typically 16) + if ifname.len() >= 16 { + return Err(vm + .new_os_error("interface name too long".to_owned()) + .into()); + } + let cstr = std::ffi::CString::new(ifname) + .map_err(|_| vm.new_os_error("invalid interface name".to_owned()))?; + let idx = unsafe { libc::if_nametoindex(cstr.as_ptr()) }; + if idx == 0 { + return Err(io::Error::last_os_error().into()); + } + idx as i32 + }; + + // Create sockaddr_can + let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() }; + let can_addr = + &mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_can; + unsafe { + (*can_addr).can_family = libc::AF_CAN as libc::sa_family_t; + (*can_addr).can_ifindex = ifindex; + } + let storage: socket2::SockAddrStorage = unsafe { std::mem::transmute(storage) }; + Ok(unsafe { + socket2::SockAddr::new( + storage, + std::mem::size_of::<libc::sockaddr_can>() as libc::socklen_t, + ) + }) + } + #[cfg(target_os = "linux")] + c::AF_ALG => { + let tuple: PyTupleRef = addr.downcast().map_err(|obj| { + vm.new_type_error(format!( + "{}(): AF_ALG address must be tuple, not {}", + caller, + obj.class().name() + )) + })?; + if tuple.len() != 2 { + return Err(vm + .new_type_error("AF_ALG address must be a tuple (type, name)") + .into()); + } + let alg_type: PyStrRef = tuple[0].clone().downcast().map_err(|obj| { + vm.new_type_error(format!( + "{}(): AF_ALG type must be str, not {}", + caller, + obj.class().name() + )) + })?; + let alg_name: PyStrRef = tuple[1].clone().downcast().map_err(|obj| { + vm.new_type_error(format!( + "{}(): AF_ALG name must be str, not {}", + caller, + obj.class().name() + )) + })?; + + let type_str = alg_type.as_str(); + let name_str = alg_name.as_str(); + + // salg_type is 14 bytes, salg_name is 64 bytes + if type_str.len() >= 14 { + return Err(vm.new_value_error("type too long".to_owned()).into()); + } + if name_str.len() >= 64 { + return Err(vm.new_value_error("name too long".to_owned()).into()); + } + + // Create sockaddr_alg + let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() }; + let alg_addr = + &mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_alg; + unsafe { + (*alg_addr).salg_family = libc::AF_ALG as libc::sa_family_t; + // Copy type string + for (i, b) in type_str.bytes().enumerate() { + (*alg_addr).salg_type[i] = b; + } + // Copy name string + for (i, b) in name_str.bytes().enumerate() { + (*alg_addr).salg_name[i] = b; + } + } + let storage: socket2::SockAddrStorage = unsafe { std::mem::transmute(storage) }; + Ok(unsafe { + socket2::SockAddr::new( + storage, + std::mem::size_of::<libc::sockaddr_alg>() as libc::socklen_t, + ) + }) + } _ => Err(vm.new_os_error(format!("{caller}(): bad family")).into()), } } @@ -1256,11 +1399,90 @@ mod _socket { let mut family = family.unwrap_or(-1); let mut socket_kind = socket_kind.unwrap_or(-1); let mut proto = proto.unwrap_or(-1); + + let sock; + + // On Windows, fileno can be bytes from socket.share() for fromshare() + #[cfg(windows)] + if let Some(fileno_obj) = fileno.flatten() { + use crate::vm::builtins::PyBytes; + if let Ok(bytes) = fileno_obj.clone().downcast::<PyBytes>() { + let bytes_data = bytes.as_bytes(); + let expected_size = std::mem::size_of::<c::WSAPROTOCOL_INFOW>(); + + if bytes_data.len() != expected_size { + return Err(vm + .new_value_error(format!( + "socket descriptor string has wrong size, should be {} bytes", + expected_size + )) + .into()); + } + + let mut info: c::WSAPROTOCOL_INFOW = unsafe { std::mem::zeroed() }; + unsafe { + std::ptr::copy_nonoverlapping( + bytes_data.as_ptr(), + &mut info as *mut c::WSAPROTOCOL_INFOW as *mut u8, + expected_size, + ); + } + + let fd = unsafe { + c::WSASocketW( + c::FROM_PROTOCOL_INFO, + c::FROM_PROTOCOL_INFO, + c::FROM_PROTOCOL_INFO, + &info, + 0, + c::WSA_FLAG_OVERLAPPED, + ) + }; + + if fd == c::INVALID_SOCKET { + return Err(Self::wsa_error().into()); + } + + crate::vm::stdlib::nt::raw_set_handle_inheritable(fd as _, false)?; + + family = info.iAddressFamily; + socket_kind = info.iSocketType; + proto = info.iProtocol; + + sock = unsafe { sock_from_raw_unchecked(fd as RawSocket) }; + return Ok(zelf.init_inner(family, socket_kind, proto, sock)?); + } + + // Not bytes, treat as regular fileno + let fileno = get_raw_sock(fileno_obj, vm)?; + sock = sock_from_raw(fileno, vm)?; + match sock.local_addr() { + Ok(addr) if family == -1 => family = addr.family() as i32, + Err(e) + if family == -1 + || matches!( + e.raw_os_error(), + Some(errcode!(ENOTSOCK)) | Some(errcode!(EBADF)) + ) => + { + std::mem::forget(sock); + return Err(e.into()); + } + _ => {} + } + if socket_kind == -1 { + socket_kind = sock.r#type().map_err(|e| e.into_pyexception(vm))?.into(); + } + proto = 0; + return Ok(zelf.init_inner(family, socket_kind, proto, sock)?); + } + + #[cfg(not(windows))] let fileno = fileno .flatten() .map(|obj| get_raw_sock(obj, vm)) .transpose()?; - let sock; + #[cfg(not(windows))] if let Some(fileno) = fileno { sock = sock_from_raw(fileno, vm)?; match sock.local_addr() { @@ -1294,7 +1516,11 @@ mod _socket { proto = 0; } } - } else { + return Ok(zelf.init_inner(family, socket_kind, proto, sock)?); + } + + // No fileno provided, create new socket + { if family == -1 { family = c::AF_INET as _ } @@ -1576,6 +1802,248 @@ mod _socket { .map_err(|e| e.into_pyexception(vm)) } + /// sendmsg_afalg([msg], *, op[, iv[, assoclen[, flags]]]) -> int + /// + /// Set operation mode and target IV for an AF_ALG socket. + #[cfg(target_os = "linux")] + #[pymethod] + fn sendmsg_afalg(&self, args: SendmsgAfalgArgs, vm: &VirtualMachine) -> PyResult<usize> { + let msg = args.msg; + let op = args.op; + let iv = args.iv; + let flags = args.flags; + + // Validate assoclen - must be non-negative if provided + let assoclen: Option<u32> = match args.assoclen { + OptionalArg::Present(val) if val < 0 => { + return Err(vm.new_type_error("assoclen must be non-negative".to_owned())); + } + OptionalArg::Present(val) => Some(val as u32), + OptionalArg::Missing => None, + }; + + // Build control messages for AF_ALG + let mut control_buf = Vec::new(); + + // Add ALG_SET_OP control message + { + let op_bytes = op.to_ne_bytes(); + let space = unsafe { libc::CMSG_SPACE(std::mem::size_of::<u32>() as u32) } as usize; + let old_len = control_buf.len(); + control_buf.resize(old_len + space, 0u8); + + let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr; + unsafe { + (*cmsg).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<u32>() as u32) as _; + (*cmsg).cmsg_level = libc::SOL_ALG; + (*cmsg).cmsg_type = libc::ALG_SET_OP; + let data = libc::CMSG_DATA(cmsg); + std::ptr::copy_nonoverlapping(op_bytes.as_ptr(), data, op_bytes.len()); + } + } + + // Add ALG_SET_IV control message if iv is provided + if let Some(iv_data) = iv { + let iv_bytes = iv_data.borrow_buf(); + // struct af_alg_iv { __u32 ivlen; __u8 iv[]; } + let iv_struct_size = 4 + iv_bytes.len(); + let space = unsafe { libc::CMSG_SPACE(iv_struct_size as u32) } as usize; + let old_len = control_buf.len(); + control_buf.resize(old_len + space, 0u8); + + let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr; + unsafe { + (*cmsg).cmsg_len = libc::CMSG_LEN(iv_struct_size as u32) as _; + (*cmsg).cmsg_level = libc::SOL_ALG; + (*cmsg).cmsg_type = libc::ALG_SET_IV; + let data = libc::CMSG_DATA(cmsg); + // Write ivlen + let ivlen = (iv_bytes.len() as u32).to_ne_bytes(); + std::ptr::copy_nonoverlapping(ivlen.as_ptr(), data, 4); + // Write iv + std::ptr::copy_nonoverlapping(iv_bytes.as_ptr(), data.add(4), iv_bytes.len()); + } + } + + // Add ALG_SET_AEAD_ASSOCLEN control message if assoclen is provided + if let Some(assoclen_val) = assoclen { + let assoclen_bytes = assoclen_val.to_ne_bytes(); + let space = unsafe { libc::CMSG_SPACE(std::mem::size_of::<u32>() as u32) } as usize; + let old_len = control_buf.len(); + control_buf.resize(old_len + space, 0u8); + + let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr; + unsafe { + (*cmsg).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<u32>() as u32) as _; + (*cmsg).cmsg_level = libc::SOL_ALG; + (*cmsg).cmsg_type = libc::ALG_SET_AEAD_ASSOCLEN; + let data = libc::CMSG_DATA(cmsg); + std::ptr::copy_nonoverlapping( + assoclen_bytes.as_ptr(), + data, + assoclen_bytes.len(), + ); + } + } + + // Build buffers + let buffers = msg.iter().map(|buf| buf.borrow_buf()).collect::<Vec<_>>(); + let iovecs: Vec<libc::iovec> = buffers + .iter() + .map(|buf| libc::iovec { + iov_base: buf.as_ptr() as *mut _, + iov_len: buf.len(), + }) + .collect(); + + // Set up msghdr + let mut msghdr: libc::msghdr = unsafe { std::mem::zeroed() }; + msghdr.msg_iov = iovecs.as_ptr() as *mut _; + msghdr.msg_iovlen = iovecs.len() as _; + if !control_buf.is_empty() { + msghdr.msg_control = control_buf.as_mut_ptr() as *mut _; + msghdr.msg_controllen = control_buf.len() as _; + } + + self.sock_op(vm, SelectKind::Write, || { + let sock = self.sock()?; + let fd = sock_fileno(&sock); + let ret = unsafe { libc::sendmsg(fd as libc::c_int, &msghdr, flags) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret as usize) + } + }) + .map_err(|e| e.into_pyexception(vm)) + } + + /// recvmsg(bufsize[, ancbufsize[, flags]]) -> (data, ancdata, msg_flags, address) + /// + /// Receive normal data and ancillary data from the socket. + #[cfg(all(unix, not(target_os = "redox")))] + #[pymethod] + fn recvmsg( + &self, + bufsize: isize, + ancbufsize: OptionalArg<isize>, + flags: OptionalArg<i32>, + vm: &VirtualMachine, + ) -> PyResult<PyTupleRef> { + use std::mem::MaybeUninit; + + if bufsize < 0 { + return Err(vm.new_value_error("negative buffer size in recvmsg".to_owned())); + } + let bufsize = bufsize as usize; + + let ancbufsize = ancbufsize.unwrap_or(0); + if ancbufsize < 0 { + return Err( + vm.new_value_error("negative ancillary buffer size in recvmsg".to_owned()) + ); + } + let ancbufsize = ancbufsize as usize; + let flags = flags.unwrap_or(0); + + // Allocate buffers + let mut data_buf: Vec<MaybeUninit<u8>> = vec![MaybeUninit::uninit(); bufsize]; + let mut anc_buf: Vec<MaybeUninit<u8>> = vec![MaybeUninit::uninit(); ancbufsize]; + let mut addr_storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() }; + + // Set up iovec + let mut iov = [libc::iovec { + iov_base: data_buf.as_mut_ptr().cast(), + iov_len: bufsize, + }]; + + // Set up msghdr + let mut msg: libc::msghdr = unsafe { std::mem::zeroed() }; + msg.msg_name = (&mut addr_storage as *mut libc::sockaddr_storage).cast(); + msg.msg_namelen = std::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t; + msg.msg_iov = iov.as_mut_ptr(); + msg.msg_iovlen = 1; + if ancbufsize > 0 { + msg.msg_control = anc_buf.as_mut_ptr().cast(); + msg.msg_controllen = ancbufsize as _; + } + + let n = self + .sock_op(vm, SelectKind::Read, || { + let sock = self.sock()?; + let fd = sock_fileno(&sock); + let ret = unsafe { libc::recvmsg(fd as libc::c_int, &mut msg, flags) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret as usize) + } + }) + .map_err(|e| e.into_pyexception(vm))?; + + // Build data bytes + let data = unsafe { + data_buf.set_len(n); + std::mem::transmute::<Vec<MaybeUninit<u8>>, Vec<u8>>(data_buf) + }; + + // Build ancdata list + let ancdata = Self::parse_ancillary_data(&msg, vm)?; + + // Build address tuple + let address = if msg.msg_namelen > 0 { + let storage: socket2::SockAddrStorage = + unsafe { std::mem::transmute(addr_storage) }; + let addr = unsafe { socket2::SockAddr::new(storage, msg.msg_namelen) }; + get_addr_tuple(&addr, vm) + } else { + vm.ctx.none() + }; + + Ok(vm.ctx.new_tuple(vec![ + vm.ctx.new_bytes(data).into(), + ancdata, + vm.ctx.new_int(msg.msg_flags).into(), + address, + ])) + } + + /// Parse ancillary data from a received message header + #[cfg(all(unix, not(target_os = "redox")))] + fn parse_ancillary_data(msg: &libc::msghdr, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + let mut result = Vec::new(); + + // Calculate buffer end for truncation handling + let ctrl_buf = msg.msg_control as *const u8; + let ctrl_end = unsafe { ctrl_buf.add(msg.msg_controllen as _) }; + + let mut cmsg: *mut libc::cmsghdr = unsafe { libc::CMSG_FIRSTHDR(msg) }; + while !cmsg.is_null() { + let cmsg_ref = unsafe { &*cmsg }; + let data_ptr = unsafe { libc::CMSG_DATA(cmsg) }; + + // Calculate data length, respecting buffer truncation + let data_len_from_cmsg = + cmsg_ref.cmsg_len as usize - (data_ptr as usize - cmsg as usize); + let available = ctrl_end as usize - data_ptr as usize; + let data_len = data_len_from_cmsg.min(available); + + let data = unsafe { std::slice::from_raw_parts(data_ptr, data_len) }; + + let tuple = vm.ctx.new_tuple(vec![ + vm.ctx.new_int(cmsg_ref.cmsg_level).into(), + vm.ctx.new_int(cmsg_ref.cmsg_type).into(), + vm.ctx.new_bytes(data.to_vec()).into(), + ]); + + result.push(tuple.into()); + + cmsg = unsafe { libc::CMSG_NXTHDR(msg, cmsg) }; + } + + Ok(vm.ctx.new_list(result).into()) + } + // based on nix's implementation #[cfg(all(unix, not(target_os = "redox")))] fn pack_cmsgs_to_send( @@ -1620,7 +2088,7 @@ mod _socket { unsafe { (*pmhdr).cmsg_level = *lvl; (*pmhdr).cmsg_type = *typ; - (*pmhdr).cmsg_len = data.len() as _; + (*pmhdr).cmsg_len = libc::CMSG_LEN(data.len() as _) as _; ptr::copy_nonoverlapping(data.as_ptr(), libc::CMSG_DATA(pmhdr), data.len()); } @@ -1834,6 +2302,136 @@ mod _socket { Ok(self.sock()?.shutdown(how)?) } + #[cfg(windows)] + fn wsa_error() -> io::Error { + io::Error::from_raw_os_error(unsafe { c::WSAGetLastError() }) + } + + #[cfg(windows)] + #[pymethod] + fn ioctl( + &self, + cmd: PyObjectRef, + option: PyObjectRef, + vm: &VirtualMachine, + ) -> Result<u32, IoOrPyException> { + use crate::vm::builtins::PyInt; + use crate::vm::convert::TryFromObject; + + let sock = self.sock()?; + let fd = sock_fileno(&sock); + let mut recv: u32 = 0; + + // Convert cmd to u32, returning ValueError for invalid/negative values + let cmd_int = cmd + .downcast::<PyInt>() + .map_err(|_| vm.new_type_error("an integer is required"))?; + let cmd_val = cmd_int.as_bigint(); + let cmd: u32 = cmd_val + .to_u32() + .ok_or_else(|| vm.new_value_error(format!("invalid ioctl command {}", cmd_val)))?; + + match cmd { + c::SIO_RCVALL | c::SIO_LOOPBACK_FAST_PATH => { + // Option must be an integer, not None + if vm.is_none(&option) { + return Err(vm + .new_type_error("an integer is required (got type NoneType)") + .into()); + } + let option_val: u32 = TryFromObject::try_from_object(vm, option)?; + let ret = unsafe { + c::WSAIoctl( + fd as _, + cmd, + &option_val as *const u32 as *const _, + std::mem::size_of::<u32>() as u32, + std::ptr::null_mut(), + 0, + &mut recv, + std::ptr::null_mut(), + None, + ) + }; + if ret == c::SOCKET_ERROR { + return Err(Self::wsa_error().into()); + } + Ok(recv) + } + c::SIO_KEEPALIVE_VALS => { + let tuple: PyTupleRef = option + .downcast() + .map_err(|_| vm.new_type_error("SIO_KEEPALIVE_VALS requires a tuple"))?; + if tuple.len() != 3 { + return Err(vm + .new_type_error( + "SIO_KEEPALIVE_VALS requires (onoff, keepalivetime, keepaliveinterval)", + ) + .into()); + } + + #[repr(C)] + struct TcpKeepalive { + onoff: u32, + keepalivetime: u32, + keepaliveinterval: u32, + } + + let ka = TcpKeepalive { + onoff: TryFromObject::try_from_object(vm, tuple[0].clone())?, + keepalivetime: TryFromObject::try_from_object(vm, tuple[1].clone())?, + keepaliveinterval: TryFromObject::try_from_object(vm, tuple[2].clone())?, + }; + + let ret = unsafe { + c::WSAIoctl( + fd as _, + cmd, + &ka as *const TcpKeepalive as *const _, + std::mem::size_of::<TcpKeepalive>() as u32, + std::ptr::null_mut(), + 0, + &mut recv, + std::ptr::null_mut(), + None, + ) + }; + if ret == c::SOCKET_ERROR { + return Err(Self::wsa_error().into()); + } + Ok(recv) + } + _ => Err(vm + .new_value_error(format!("invalid ioctl command {}", cmd)) + .into()), + } + } + + #[cfg(windows)] + #[pymethod] + fn share(&self, process_id: u32, _vm: &VirtualMachine) -> Result<Vec<u8>, IoOrPyException> { + let sock = self.sock()?; + let fd = sock_fileno(&sock); + + let mut info: MaybeUninit<c::WSAPROTOCOL_INFOW> = MaybeUninit::uninit(); + + let ret = unsafe { c::WSADuplicateSocketW(fd as _, process_id, info.as_mut_ptr()) }; + + if ret == c::SOCKET_ERROR { + return Err(Self::wsa_error().into()); + } + + let info = unsafe { info.assume_init() }; + let bytes = unsafe { + std::slice::from_raw_parts( + &info as *const c::WSAPROTOCOL_INFOW as *const u8, + std::mem::size_of::<c::WSAPROTOCOL_INFOW>(), + ) + }; + + Ok(bytes.to_vec()) + } + #[pygetset(name = "type")] fn kind(&self) -> i32 { self.kind.load() @@ -1934,6 +2532,50 @@ mod _socket { let path = ffi::OsStr::from_bytes(&path[..nul_pos]); return vm.fsdecode(path).into(); } + #[cfg(target_os = "linux")] + { + let family = addr.family(); + if family == libc::AF_CAN as libc::sa_family_t { + // AF_CAN address: (interface_name,) or (interface_name, can_id) + let can_addr = unsafe { &*(addr.as_ptr() as *const libc::sockaddr_can) }; + let ifindex = can_addr.can_ifindex; + let ifname = if ifindex == 0 { + String::new() + } else { + let mut buf = [0u8; libc::IF_NAMESIZE]; + let ret = unsafe { + libc::if_indextoname( + ifindex as libc::c_uint, + buf.as_mut_ptr() as *mut libc::c_char, + ) + }; + if ret.is_null() { + String::new() + } else { + let nul_pos = memchr::memchr(b'\0', &buf).unwrap_or(buf.len()); + String::from_utf8_lossy(&buf[..nul_pos]).into_owned() + } + }; + return vm.ctx.new_tuple(vec![vm.ctx.new_str(ifname).into()]).into(); + } + if family == libc::AF_ALG as libc::sa_family_t { + // AF_ALG address: (type, name) + let alg_addr = unsafe { &*(addr.as_ptr() as *const libc::sockaddr_alg) }; + let type_bytes = &alg_addr.salg_type; + let name_bytes = &alg_addr.salg_name; + let type_nul = memchr::memchr(b'\0', type_bytes).unwrap_or(type_bytes.len()); + let name_nul = memchr::memchr(b'\0', name_bytes).unwrap_or(name_bytes.len()); + let type_str = String::from_utf8_lossy(&type_bytes[..type_nul]).into_owned(); + let name_str = String::from_utf8_lossy(&name_bytes[..name_nul]).into_owned(); + return vm + .ctx + .new_tuple(vec![ + vm.ctx.new_str(type_str).into(), + vm.ctx.new_str(name_str).into(), + ]) + .into(); + } + } // TODO: support more address families (String::new(), 0).to_pyobject(vm) } From 183cebe8a1e5de4d5f97be577e84d09242cfffdb Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <jeong@youknowone.org> Date: Sun, 28 Dec 2025 10:47:30 +0900 Subject: [PATCH 620/819] Upgrade socket from 3.13.11 --- Lib/socket.py | 130 +++++---- Lib/test/test_socket.py | 562 +++++++++++++++++++++++++++++------- crates/stdlib/src/socket.rs | 2 +- 3 files changed, 533 insertions(+), 161 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py index 42ee1307732..35d87eff34d 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -306,7 +306,8 @@ def makefile(self, mode="r", buffering=None, *, """makefile(...) -> an I/O stream connected to the socket The arguments are as for io.open() after the filename, except the only - supported mode values are 'r' (default), 'w' and 'b'. + supported mode values are 'r' (default), 'w', 'b', or a combination of + those. """ # XXX refactor to share code? if not set(mode) <= {"r", "w", "b"}: @@ -591,16 +592,65 @@ def fromshare(info): return socket(0, 0, 0, info) __all__.append("fromshare") -if hasattr(_socket, "socketpair"): +# Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain. +# This is used if _socket doesn't natively provide socketpair. It's +# always defined so that it can be patched in for testing purposes. +def _fallback_socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): + if family == AF_INET: + host = _LOCALHOST + elif family == AF_INET6: + host = _LOCALHOST_V6 + else: + raise ValueError("Only AF_INET and AF_INET6 socket address families " + "are supported") + if type != 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(False) that prevents us from having to create a thread. + lsock = socket(family, type, proto) + try: + lsock.bind((host, 0)) + lsock.listen() + # On IPv6, ignore flow_info and scope_id + addr, port = lsock.getsockname()[:2] + csock = 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() - def socketpair(family=None, type=SOCK_STREAM, proto=0): - """socketpair([family[, type[, proto]]]) -> (socket object, socket object) + # Authenticating avoids using a connection from something else + # able to connect to {host}:{port} instead of us. + # We expect only AF_INET and AF_INET6 families. + try: + if ( + ssock.getsockname() != csock.getpeername() + or csock.getsockname() != ssock.getpeername() + ): + raise ConnectionError("Unexpected peer connection") + except: + # getsockname() and getpeername() can fail + # if either socket isn't connected. + ssock.close() + csock.close() + raise - Create a pair of socket objects from the sockets returned by the platform - socketpair() function. - The arguments are the same as for socket() except the default family is - AF_UNIX if defined on the platform; otherwise, the default is AF_INET. - """ + return (ssock, csock) + +if hasattr(_socket, "socketpair"): + def socketpair(family=None, type=SOCK_STREAM, proto=0): if family is None: try: family = AF_UNIX @@ -612,44 +662,7 @@ def socketpair(family=None, type=SOCK_STREAM, proto=0): return a, b else: - - # Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain. - def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): - if family == AF_INET: - host = _LOCALHOST - elif family == AF_INET6: - host = _LOCALHOST_V6 - else: - raise ValueError("Only AF_INET and AF_INET6 socket address families " - "are supported") - if type != 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(False) that prevents us from having to create a thread. - lsock = socket(family, type, proto) - try: - lsock.bind((host, 0)) - lsock.listen() - # On IPv6, ignore flow_info and scope_id - addr, port = lsock.getsockname()[:2] - csock = 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) + socketpair = _fallback_socketpair __all__.append("socketpair") socketpair.__doc__ = """socketpair([family[, type[, proto]]]) -> (socket object, socket object) @@ -702,16 +715,15 @@ def readinto(self, b): self._checkReadable() if self._timeout_occurred: raise OSError("cannot read from timed out object") - while True: - try: - return self._sock.recv_into(b) - except timeout: - self._timeout_occurred = True - raise - except error as e: - if e.errno in _blocking_errnos: - return None - raise + try: + return self._sock.recv_into(b) + except timeout: + self._timeout_occurred = True + raise + except error as e: + if e.errno in _blocking_errnos: + return None + raise def write(self, b): """Write the given bytes or bytearray object *b* to the socket @@ -919,7 +931,9 @@ def create_server(address, *, family=AF_INET, backlog=None, reuse_port=False, # Fail later on bind(), for platforms which may not # support this option. pass - if reuse_port: + # Since Linux 6.12.9, SO_REUSEPORT is not allowed + # on other address families than AF_INET/AF_INET6. + if reuse_port and family in (AF_INET, AF_INET6): sock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1) if has_ipv6 and family == AF_INET6: if dualstack_ipv6: diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 756603bd514..08676cf3388 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1,9 +1,9 @@ import unittest +from unittest import mock from test import support -from test.support import os_helper -from test.support import socket_helper -from test.support import threading_helper - +from test.support import ( + is_apple, os_helper, refleak_helper, socket_helper, threading_helper +) import _thread as thread import array import contextlib @@ -28,6 +28,7 @@ import threading import time import traceback +import warnings from weakref import proxy try: import multiprocessing @@ -37,6 +38,10 @@ import fcntl except ImportError: fcntl = None +try: + import _testcapi +except ImportError: + _testcapi = None support.requires_working_socket(module=True) @@ -44,6 +49,7 @@ # test unicode string and carriage return MSG = 'Michael Gilfix was here\u1234\r\n'.encode('utf-8') +VMADDR_CID_LOCAL = 1 VSOCKPORT = 1234 AIX = platform.system() == "AIX" WSL = "microsoft-standard-WSL" in platform.release() @@ -53,6 +59,35 @@ except ImportError: _socket = None +def skipForRefleakHuntinIf(condition, issueref): + if not condition: + def decorator(f): + f.client_skip = lambda f: f + return f + + else: + def decorator(f): + @contextlib.wraps(f) + def wrapper(*args, **kwds): + if refleak_helper.hunting_for_refleaks(): + raise unittest.SkipTest(f"ignore while hunting for refleaks, see {issueref}") + + return f(*args, **kwds) + + def client_skip(f): + @contextlib.wraps(f) + def wrapper(*args, **kwds): + if refleak_helper.hunting_for_refleaks(): + return + + return f(*args, **kwds) + + return wrapper + wrapper.client_skip = client_skip + return wrapper + + return decorator + def get_cid(): if fcntl is None: return None @@ -128,8 +163,8 @@ def _have_socket_qipcrtr(): def _have_socket_vsock(): """Check whether AF_VSOCK sockets are supported on this host.""" - ret = get_cid() is not None - return ret + cid = get_cid() + return (cid is not None) def _have_socket_bluetooth(): @@ -145,6 +180,17 @@ def _have_socket_bluetooth(): return True +def _have_socket_bluetooth_l2cap(): + """Check whether BTPROTO_L2CAP sockets are supported on this host.""" + try: + s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) + except (AttributeError, OSError): + return False + else: + s.close() + return True + + def _have_socket_hyperv(): """Check whether AF_HYPERV sockets are supported on this host.""" try: @@ -166,6 +212,24 @@ def socket_setdefaulttimeout(timeout): socket.setdefaulttimeout(old_timeout) +@contextlib.contextmanager +def downgrade_malformed_data_warning(): + # This warning happens on macos and win, but does not always happen on linux. + if sys.platform not in {"win32", "darwin"}: + yield + return + + with warnings.catch_warnings(): + # TODO: gh-110012, we should investigate why this warning is happening + # and fix it properly. + warnings.filterwarnings( + action="always", + message="received malformed or improperly-truncated ancillary data", + category=RuntimeWarning, + ) + yield + + HAVE_SOCKET_CAN = _have_socket_can() HAVE_SOCKET_CAN_ISOTP = _have_socket_can_isotp() @@ -180,10 +244,15 @@ def socket_setdefaulttimeout(timeout): HAVE_SOCKET_VSOCK = _have_socket_vsock() -HAVE_SOCKET_UDPLITE = hasattr(socket, "IPPROTO_UDPLITE") +# Older Android versions block UDPLITE with SELinux. +HAVE_SOCKET_UDPLITE = ( + hasattr(socket, "IPPROTO_UDPLITE") + and not (support.is_android and platform.android_ver().api_level < 29)) HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth() +HAVE_SOCKET_BLUETOOTH_L2CAP = _have_socket_bluetooth_l2cap() + HAVE_SOCKET_HYPERV = _have_socket_hyperv() # Size in bytes of the int type @@ -485,8 +554,8 @@ def clientTearDown(self): @unittest.skipIf(WSL, 'VSOCK does not work on Microsoft WSL') @unittest.skipUnless(HAVE_SOCKET_VSOCK, 'VSOCK sockets required for this test.') -@unittest.skipUnless(get_cid() != 2, - "This test can only be run on a virtual guest.") +@unittest.skipUnless(get_cid() != 2, # VMADDR_CID_HOST + "This test can only be run on a virtual guest.") class ThreadedVSOCKSocketStreamTest(unittest.TestCase, ThreadableTest): def __init__(self, methodName='runTest'): @@ -508,10 +577,16 @@ def clientSetUp(self): self.cli = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) self.addCleanup(self.cli.close) cid = get_cid() + if cid in (socket.VMADDR_CID_HOST, socket.VMADDR_CID_ANY): + # gh-119461: Use the local communication address (loopback) + cid = VMADDR_CID_LOCAL self.cli.connect((cid, VSOCKPORT)) def testStream(self): - msg = self.conn.recv(1024) + try: + msg = self.conn.recv(1024) + except PermissionError as exc: + self.skipTest(repr(exc)) self.assertEqual(msg, MSG) def _testStream(self): @@ -556,19 +631,27 @@ class SocketPairTest(unittest.TestCase, ThreadableTest): def __init__(self, methodName='runTest'): unittest.TestCase.__init__(self, methodName=methodName) ThreadableTest.__init__(self) + self.cli = None + self.serv = None + + def socketpair(self): + # To be overridden by some child classes. + return socket.socketpair() def setUp(self): - self.serv, self.cli = socket.socketpair() + self.serv, self.cli = self.socketpair() def tearDown(self): - self.serv.close() + if self.serv: + self.serv.close() self.serv = None def clientSetUp(self): pass def clientTearDown(self): - self.cli.close() + if self.cli: + self.cli.close() self.cli = None ThreadableTest.clientTearDown(self) @@ -821,10 +904,8 @@ def requireSocket(*args): class GeneralModuleTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipUnless(_socket is not None, 'need _socket module') - # TODO: RUSTPYTHON gc.is_tracked not implemented + # TODO: RUSTPYTHON; gc.is_tracked not implemented @unittest.expectedFailure def test_socket_type(self): self.assertTrue(gc.is_tracked(_socket.socket)) @@ -888,7 +969,7 @@ def testSocketError(self): with self.assertRaises(OSError, msg=msg % 'socket.gaierror'): raise socket.gaierror - # TODO: RUSTPYTHON + # TODO: RUSTPYTHON; error message format differs @unittest.expectedFailure def testSendtoErrors(self): # Testing that sendto doesn't mask failures. See #10169. @@ -975,8 +1056,6 @@ def test_socket_methods(self): if not hasattr(socket.socket, name): self.fail(f"socket method {name} is missing") - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipUnless(sys.platform == 'darwin', 'macOS specific test') @unittest.skipUnless(socket_helper.IPV6_ENABLED, 'IPv6 required for this test') def test3542SocketOptions(self): @@ -1092,7 +1171,10 @@ def testInterfaceNameIndex(self): @unittest.skipUnless(hasattr(socket, 'if_indextoname'), 'socket.if_indextoname() not available.') def testInvalidInterfaceIndexToName(self): - self.assertRaises(OSError, socket.if_indextoname, 0) + with self.assertRaises(OSError) as cm: + socket.if_indextoname(0) + self.assertIsNotNone(cm.exception.errno) + self.assertRaises(OverflowError, socket.if_indextoname, -1) self.assertRaises(OverflowError, socket.if_indextoname, 2**1000) self.assertRaises(TypeError, socket.if_indextoname, '_DEADBEEF') @@ -1111,8 +1193,11 @@ def testInvalidInterfaceIndexToName(self): @unittest.skipUnless(hasattr(socket, 'if_nametoindex'), 'socket.if_nametoindex() not available.') def testInvalidInterfaceNameToIndex(self): + with self.assertRaises(OSError) as cm: + socket.if_nametoindex("_DEADBEEF") + self.assertIsNotNone(cm.exception.errno) + self.assertRaises(TypeError, socket.if_nametoindex, 0) - self.assertRaises(OSError, socket.if_nametoindex, '_DEADBEEF') @unittest.skipUnless(hasattr(sys, 'getrefcount'), 'test needs sys.getrefcount()') @@ -1149,6 +1234,7 @@ def testNtoH(self): self.assertRaises(OverflowError, func, 1<<34) @support.cpython_only + @unittest.skipIf(_testcapi is None, "requires _testcapi") def testNtoHErrors(self): import _testcapi s_good_values = [0, 1, 2, 0xffff] @@ -1177,8 +1263,11 @@ def testGetServBy(self): # Find one service that exists, then check all the related interfaces. # I've ordered this by protocols that have both a tcp and udp # protocol, at least for modern Linuxes. - if (sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) - or sys.platform in ('linux', 'darwin')): + if ( + sys.platform.startswith( + ('linux', 'android', 'freebsd', 'netbsd', 'gnukfreebsd')) + or is_apple + ): # avoid the 'echo' service on this platform, as there is an # assumption breaking non-standard port/protocol entry services = ('daytime', 'qotd', 'domain') @@ -1193,9 +1282,8 @@ def testGetServBy(self): else: raise OSError # Try same call with optional protocol omitted - # Issue #26936: Android getservbyname() was broken before API 23. - if (not hasattr(sys, 'getandroidapilevel') or - sys.getandroidapilevel() >= 23): + # Issue gh-71123: this fails on Android before API level 23. + if not (support.is_android and platform.android_ver().api_level < 23): port2 = socket.getservbyname(service) eq(port, port2) # Try udp, but don't barf if it doesn't exist @@ -1206,8 +1294,9 @@ def testGetServBy(self): else: eq(udpport, port) # Now make sure the lookup by port returns the same service name - # Issue #26936: Android getservbyport() is broken. - if not support.is_android: + # Issue #26936: when the protocol is omitted, this fails on Android + # before API level 28. + if not (support.is_android and platform.android_ver().api_level < 28): eq(socket.getservbyport(port2), service) eq(socket.getservbyport(port, 'tcp'), service) if udpport is not None: @@ -1505,8 +1594,6 @@ def test_getsockaddrarg(self): break @unittest.skipUnless(os.name == "nt", "Windows specific") - # TODO: RUSTPYTHON, windows ioctls - @unittest.expectedFailure def test_sock_ioctl(self): self.assertTrue(hasattr(socket.socket, 'ioctl')) self.assertTrue(hasattr(socket, 'SIO_RCVALL')) @@ -1521,8 +1608,6 @@ def test_sock_ioctl(self): @unittest.skipUnless(os.name == "nt", "Windows specific") @unittest.skipUnless(hasattr(socket, 'SIO_LOOPBACK_FAST_PATH'), 'Loopback fast path support required for this test') - # TODO: RUSTPYTHON, AttributeError: 'socket' object has no attribute 'ioctl' - @unittest.expectedFailure def test_sio_loopback_fast_path(self): s = socket.socket() self.addCleanup(s.close) @@ -1556,9 +1641,8 @@ def testGetaddrinfo(self): socket.getaddrinfo('::1', 80) # port can be a string service name such as "http", a numeric # port number or None - # Issue #26936: Android getaddrinfo() was broken before API level 23. - if (not hasattr(sys, 'getandroidapilevel') or - sys.getandroidapilevel() >= 23): + # Issue #26936: this fails on Android before API level 23. + if not (support.is_android and platform.android_ver().api_level < 23): socket.getaddrinfo(HOST, "http") socket.getaddrinfo(HOST, 80) socket.getaddrinfo(HOST, None) @@ -1604,8 +1688,7 @@ def testGetaddrinfo(self): flags=socket.AI_PASSIVE) self.assertEqual(a, b) # Issue #6697. - # XXX RUSTPYTHON TODO: surrogates in str - # self.assertRaises(UnicodeEncodeError, socket.getaddrinfo, 'localhost', '\uD800') + self.assertRaises(UnicodeEncodeError, socket.getaddrinfo, 'localhost', '\uD800') # Issue 17269: test workaround for OS X platform bug segfault if hasattr(socket, 'AI_NUMERICSERV'): @@ -1617,8 +1700,7 @@ def testGetaddrinfo(self): except socket.gaierror: pass - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_getaddrinfo_int_port_overflow(self): # gh-74895: Test that getaddrinfo does not raise OverflowError on port. # @@ -1636,7 +1718,7 @@ def test_getaddrinfo_int_port_overflow(self): try: socket.getaddrinfo(None, ULONG_MAX + 1, type=socket.SOCK_STREAM) except OverflowError: - # Platforms differ as to what values consitute a getaddrinfo() error + # Platforms differ as to what values constitute a getaddrinfo() error # return. Some fail for LONG_MAX+1, others ULONG_MAX+1, and Windows # silently accepts such huge "port" aka "service" numeric values. self.fail("Either no error or socket.gaierror expected.") @@ -1671,7 +1753,6 @@ def test_getnameinfo(self): # only IP addresses are allowed self.assertRaises(OSError, socket.getnameinfo, ('mail.python.org',0), 0) - @unittest.skip("TODO: RUSTPYTHON: flaky on CI?") @unittest.skipUnless(support.is_resource_enabled('network'), 'network is not enabled') def test_idna(self): @@ -1726,8 +1807,6 @@ def test_sendall_interrupted(self): def test_sendall_interrupted_with_timeout(self): self.check_sendall_interrupted(True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_dealloc_warn(self): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) r = repr(sock) @@ -1815,6 +1894,7 @@ def test_listen_backlog(self): srv.listen() @support.cpython_only + @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_listen_backlog_overflow(self): # Issue 15989 import _testcapi @@ -1907,7 +1987,6 @@ def test_str_for_enums(self): self.assertEqual(str(s.family), str(s.family.value)) self.assertEqual(str(s.type), str(s.type.value)) - @unittest.expectedFailureIf(sys.platform.startswith("linux"), "TODO: RUSTPYTHON, AssertionError: 526337 != <SocketKind.SOCK_STREAM: 1>") def test_socket_consistent_sock_type(self): SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', 0) SOCK_CLOEXEC = getattr(socket, 'SOCK_CLOEXEC', 0) @@ -2100,8 +2179,6 @@ def testCrucialConstants(self): @unittest.skipUnless(hasattr(socket, "CAN_BCM"), 'socket.CAN_BCM required for this test.') - # TODO: RUSTPYTHON, AttributeError: module 'socket' has no attribute 'CAN_BCM_TX_SETUP' - @unittest.expectedFailure def testBCMConstants(self): socket.CAN_BCM @@ -2142,16 +2219,12 @@ def testCreateBCMSocket(self): with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_BCM) as s: pass - # TODO: RUSTPYTHON, OSError: bind(): bad family - @unittest.expectedFailure def testBindAny(self): with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s: address = ('', ) s.bind(address) self.assertEqual(s.getsockname(), address) - # TODO: RUSTPYTHON, AssertionError: "interface name too long" does not match "bind(): bad family" - @unittest.expectedFailure def testTooLongInterfaceName(self): # most systems limit IFNAMSIZ to 16, take 1024 to be sure with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s: @@ -2294,16 +2367,12 @@ def testCreateISOTPSocket(self): with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: pass - # TODO: RUSTPYTHON, OSError: bind(): bad family - @unittest.expectedFailure def testTooLongInterfaceName(self): # most systems limit IFNAMSIZ to 16, take 1024 to be sure with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: with self.assertRaisesRegex(OSError, 'interface name too long'): s.bind(('x' * 1024, 1, 2)) - # TODO: RUSTPYTHON, OSError: bind(): bad family - @unittest.expectedFailure def testBind(self): try: with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: @@ -2325,8 +2394,6 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.interface = "vcan0" - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipUnless(hasattr(socket, "CAN_J1939"), 'socket.CAN_J1939 required for this test.') def testJ1939Constants(self): @@ -2368,8 +2435,6 @@ def testCreateJ1939Socket(self): with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939) as s: pass - # TODO: RUSTPYTHON - @unittest.expectedFailure def testBind(self): try: with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939) as s: @@ -2581,6 +2646,143 @@ def testCreateScoSocket(self): with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_SCO) as s: pass + @unittest.skipUnless(HAVE_SOCKET_BLUETOOTH_L2CAP, 'Bluetooth L2CAP sockets required for this test') + def testBindBrEdrL2capSocket(self): + with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) as f: + # First user PSM in BR/EDR L2CAP + psm = 0x1001 + f.bind((socket.BDADDR_ANY, psm)) + addr = f.getsockname() + self.assertEqual(addr, (socket.BDADDR_ANY, psm)) + + @unittest.skipUnless(HAVE_SOCKET_BLUETOOTH_L2CAP, 'Bluetooth L2CAP sockets required for this test') + def testBadL2capAddr(self): + with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) as f: + with self.assertRaises(OSError): + f.bind((socket.BDADDR_ANY, 0, 0)) + with self.assertRaises(OSError): + f.bind((socket.BDADDR_ANY,)) + with self.assertRaises(OSError): + f.bind(socket.BDADDR_ANY) + with self.assertRaises(OSError): + f.bind((socket.BDADDR_ANY.encode(), 0x1001)) + with self.assertRaises(OSError): + f.bind(('\ud812', 0x1001)) + + def testBindRfcommSocket(self): + with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM) as s: + channel = 0 + try: + s.bind((socket.BDADDR_ANY, channel)) + except OSError as err: + if sys.platform == 'win32' and err.winerror == 10050: + self.skipTest(str(err)) + raise + addr = s.getsockname() + self.assertEqual(addr, (mock.ANY, channel)) + self.assertRegex(addr[0], r'(?i)[0-9a-f]{2}(?::[0-9a-f]{2}){4}') + if sys.platform != 'win32': + self.assertEqual(addr, (socket.BDADDR_ANY, channel)) + with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM) as s: + s.bind(addr) + addr2 = s.getsockname() + self.assertEqual(addr2, addr) + + def testBadRfcommAddr(self): + with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM) as s: + channel = 0 + with self.assertRaises(OSError): + s.bind((socket.BDADDR_ANY.encode(), channel)) + with self.assertRaises(OSError): + s.bind((socket.BDADDR_ANY,)) + with self.assertRaises(OSError): + s.bind((socket.BDADDR_ANY, channel, 0)) + with self.assertRaises(OSError): + s.bind((socket.BDADDR_ANY + '\0', channel)) + with self.assertRaises(OSError): + s.bind('\ud812') + with self.assertRaises(OSError): + s.bind(('invalid', channel)) + + @unittest.skipUnless(hasattr(socket, 'BTPROTO_HCI'), 'Bluetooth HCI sockets required for this test') + def testBindHciSocket(self): + with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) as s: + if sys.platform.startswith(('netbsd', 'dragonfly', 'freebsd')): + s.bind(socket.BDADDR_ANY) + addr = s.getsockname() + self.assertEqual(addr, socket.BDADDR_ANY) + else: + dev = 0 + try: + s.bind((dev,)) + except OSError as err: + if err.errno in (errno.EINVAL, errno.ENODEV): + self.skipTest(str(err)) + raise + addr = s.getsockname() + self.assertEqual(addr, dev) + + @unittest.skipUnless(hasattr(socket, 'BTPROTO_HCI'), 'Bluetooth HCI sockets required for this test') + def testBadHciAddr(self): + with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) as s: + if sys.platform.startswith(('netbsd', 'dragonfly', 'freebsd')): + with self.assertRaises(OSError): + s.bind(socket.BDADDR_ANY.encode()) + with self.assertRaises(OSError): + s.bind((socket.BDADDR_ANY,)) + with self.assertRaises(OSError): + s.bind(socket.BDADDR_ANY + '\0') + with self.assertRaises((ValueError, OSError)): + s.bind(socket.BDADDR_ANY + ' '*100) + with self.assertRaises(OSError): + s.bind('\ud812') + with self.assertRaises(OSError): + s.bind('invalid') + with self.assertRaises(OSError): + s.bind(b'invalid') + else: + dev = 0 + with self.assertRaises(OSError): + s.bind(()) + with self.assertRaises(OSError): + s.bind((dev, 0)) + with self.assertRaises(OSError): + s.bind(dev) + with self.assertRaises(OSError): + s.bind(socket.BDADDR_ANY) + with self.assertRaises(OSError): + s.bind(socket.BDADDR_ANY.encode()) + + @unittest.skipUnless(hasattr(socket, 'BTPROTO_SCO'), 'Bluetooth SCO sockets required for this test') + def testBindScoSocket(self): + with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_SCO) as s: + s.bind(socket.BDADDR_ANY) + addr = s.getsockname() + self.assertEqual(addr, socket.BDADDR_ANY) + + with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_SCO) as s: + s.bind(socket.BDADDR_ANY.encode()) + addr = s.getsockname() + self.assertEqual(addr, socket.BDADDR_ANY) + + @unittest.skipUnless(hasattr(socket, 'BTPROTO_SCO'), 'Bluetooth SCO sockets required for this test') + def testBadScoAddr(self): + with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_SCO) as s: + with self.assertRaises(OSError): + s.bind((socket.BDADDR_ANY,)) + with self.assertRaises(OSError): + s.bind((socket.BDADDR_ANY.encode(),)) + with self.assertRaises(ValueError): + s.bind(socket.BDADDR_ANY + '\0') + with self.assertRaises(ValueError): + s.bind(socket.BDADDR_ANY.encode() + b'\0') + with self.assertRaises(UnicodeEncodeError): + s.bind('\ud812') + with self.assertRaises(OSError): + s.bind('invalid') + with self.assertRaises(OSError): + s.bind(b'invalid') + @unittest.skipUnless(HAVE_SOCKET_HYPERV, 'Hyper-V sockets required for this test.') @@ -2711,22 +2913,29 @@ def testDup(self): def _testDup(self): self.serv_conn.send(MSG) - def testShutdown(self): - # Testing shutdown() + def check_shutdown(self): + # Test shutdown() helper msg = self.cli_conn.recv(1024) self.assertEqual(msg, MSG) - # wait for _testShutdown to finish: on OS X, when the server + # wait for _testShutdown[_overflow] to finish: on OS X, when the server # closes the connection the client also becomes disconnected, # and the client's shutdown call will fail. (Issue #4397.) self.done.wait() + def testShutdown(self): + self.check_shutdown() + def _testShutdown(self): self.serv_conn.send(MSG) self.serv_conn.shutdown(2) - testShutdown_overflow = support.cpython_only(testShutdown) + @support.cpython_only + @unittest.skipIf(_testcapi is None, "requires _testcapi") + def testShutdown_overflow(self): + self.check_shutdown() @support.cpython_only + @unittest.skipIf(_testcapi is None, "requires _testcapi") def _testShutdown_overflow(self): import _testcapi self.serv_conn.send(MSG) @@ -3197,7 +3406,7 @@ def _testSendmsgTimeout(self): # Linux supports MSG_DONTWAIT when sending, but in general, it # only works when receiving. Could add other platforms if they # support it too. - @skipWithClientIf(sys.platform not in {"linux"}, + @skipWithClientIf(sys.platform not in {"linux", "android"}, "MSG_DONTWAIT not known to work on this platform when " "sending") def testSendmsgDontWait(self): @@ -3714,7 +3923,7 @@ def testFDPassCMSG_LEN(self): def _testFDPassCMSG_LEN(self): self.createAndSendFDs(1) - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") @requireAttrs(socket, "CMSG_SPACE") def testFDPassSeparate(self): @@ -3725,7 +3934,7 @@ def testFDPassSeparate(self): maxcmsgs=2) @testFDPassSeparate.client_skip - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") def _testFDPassSeparate(self): fd0, fd1 = self.newFDs(2) @@ -3738,7 +3947,7 @@ def _testFDPassSeparate(self): array.array("i", [fd1]))]), len(MSG)) - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") @requireAttrs(socket, "CMSG_SPACE") def testFDPassSeparateMinSpace(self): @@ -3752,7 +3961,7 @@ def testFDPassSeparateMinSpace(self): maxcmsgs=2, ignoreflags=socket.MSG_CTRUNC) @testFDPassSeparateMinSpace.client_skip - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") def _testFDPassSeparateMinSpace(self): fd0, fd1 = self.newFDs(2) @@ -3776,7 +3985,7 @@ def sendAncillaryIfPossible(self, msg, ancdata): nbytes = self.sendmsgToServer([msg]) self.assertEqual(nbytes, len(msg)) - @unittest.skipIf(sys.platform == "darwin", "see issue #24725") + @unittest.skipIf(is_apple, "skipping, see issue #12958") def testFDPassEmpty(self): # Try to pass an empty FD array. Can receive either no array # or an empty array. @@ -3850,6 +4059,7 @@ def checkTruncatedHeader(self, result, ignoreflags=0): self.checkFlags(flags, eor=True, checkset=socket.MSG_CTRUNC, ignore=ignoreflags) + @skipForRefleakHuntinIf(sys.platform == "darwin", "#80931") def testCmsgTruncNoBufSize(self): # Check that no ancillary data is received when no buffer size # is specified. @@ -3859,26 +4069,32 @@ def testCmsgTruncNoBufSize(self): # received. ignoreflags=socket.MSG_CTRUNC) + @testCmsgTruncNoBufSize.client_skip def _testCmsgTruncNoBufSize(self): self.createAndSendFDs(1) + @skipForRefleakHuntinIf(sys.platform == "darwin", "#80931") def testCmsgTrunc0(self): # Check that no ancillary data is received when buffer size is 0. self.checkTruncatedHeader(self.doRecvmsg(self.serv_sock, len(MSG), 0), ignoreflags=socket.MSG_CTRUNC) + @testCmsgTrunc0.client_skip def _testCmsgTrunc0(self): self.createAndSendFDs(1) # Check that no ancillary data is returned for various non-zero # (but still too small) buffer sizes. + @skipForRefleakHuntinIf(sys.platform == "darwin", "#80931") def testCmsgTrunc1(self): self.checkTruncatedHeader(self.doRecvmsg(self.serv_sock, len(MSG), 1)) + @testCmsgTrunc1.client_skip def _testCmsgTrunc1(self): self.createAndSendFDs(1) + @skipForRefleakHuntinIf(sys.platform == "darwin", "#80931") def testCmsgTrunc2Int(self): # The cmsghdr structure has at least three members, two of # which are ints, so we still shouldn't see any ancillary @@ -3886,13 +4102,16 @@ def testCmsgTrunc2Int(self): self.checkTruncatedHeader(self.doRecvmsg(self.serv_sock, len(MSG), SIZEOF_INT * 2)) + @testCmsgTrunc2Int.client_skip def _testCmsgTrunc2Int(self): self.createAndSendFDs(1) + @skipForRefleakHuntinIf(sys.platform == "darwin", "#80931") def testCmsgTruncLen0Minus1(self): self.checkTruncatedHeader(self.doRecvmsg(self.serv_sock, len(MSG), socket.CMSG_LEN(0) - 1)) + @testCmsgTruncLen0Minus1.client_skip def _testCmsgTruncLen0Minus1(self): self.createAndSendFDs(1) @@ -3904,8 +4123,9 @@ def checkTruncatedArray(self, ancbuf, maxdata, mindata=0): # mindata and maxdata bytes when received with buffer size # ancbuf, and that any complete file descriptor numbers are # valid. - msg, ancdata, flags, addr = self.doRecvmsg(self.serv_sock, - len(MSG), ancbuf) + with downgrade_malformed_data_warning(): # TODO: gh-110012 + msg, ancdata, flags, addr = self.doRecvmsg(self.serv_sock, + len(MSG), ancbuf) self.assertEqual(msg, MSG) self.checkRecvmsgAddress(addr, self.cli_addr) self.checkFlags(flags, eor=True, checkset=socket.MSG_CTRUNC) @@ -3923,29 +4143,38 @@ def checkTruncatedArray(self, ancbuf, maxdata, mindata=0): len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) self.checkFDs(fds) + @skipForRefleakHuntinIf(sys.platform == "darwin", "#80931") def testCmsgTruncLen0(self): self.checkTruncatedArray(ancbuf=socket.CMSG_LEN(0), maxdata=0) + @testCmsgTruncLen0.client_skip def _testCmsgTruncLen0(self): self.createAndSendFDs(1) + @skipForRefleakHuntinIf(sys.platform == "darwin", "#80931") def testCmsgTruncLen0Plus1(self): self.checkTruncatedArray(ancbuf=socket.CMSG_LEN(0) + 1, maxdata=1) + @testCmsgTruncLen0Plus1.client_skip def _testCmsgTruncLen0Plus1(self): self.createAndSendFDs(2) + @skipForRefleakHuntinIf(sys.platform == "darwin", "#80931") def testCmsgTruncLen1(self): self.checkTruncatedArray(ancbuf=socket.CMSG_LEN(SIZEOF_INT), maxdata=SIZEOF_INT) + @testCmsgTruncLen1.client_skip def _testCmsgTruncLen1(self): self.createAndSendFDs(2) + + @skipForRefleakHuntinIf(sys.platform == "darwin", "#80931") def testCmsgTruncLen2Minus1(self): self.checkTruncatedArray(ancbuf=socket.CMSG_LEN(2 * SIZEOF_INT) - 1, maxdata=(2 * SIZEOF_INT) - 1) + @testCmsgTruncLen2Minus1.client_skip def _testCmsgTruncLen2Minus1(self): self.createAndSendFDs(2) @@ -4247,8 +4476,9 @@ def testSingleCmsgTruncInData(self): self.serv_sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVHOPLIMIT, 1) self.misc_event.set() - msg, ancdata, flags, addr = self.doRecvmsg( - self.serv_sock, len(MSG), socket.CMSG_LEN(SIZEOF_INT) - 1) + with downgrade_malformed_data_warning(): # TODO: gh-110012 + msg, ancdata, flags, addr = self.doRecvmsg( + self.serv_sock, len(MSG), socket.CMSG_LEN(SIZEOF_INT) - 1) self.assertEqual(msg, MSG) self.checkRecvmsgAddress(addr, self.cli_addr) @@ -4351,9 +4581,10 @@ def testSecondCmsgTruncInData(self): self.serv_sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVTCLASS, 1) self.misc_event.set() - msg, ancdata, flags, addr = self.doRecvmsg( - self.serv_sock, len(MSG), - socket.CMSG_SPACE(SIZEOF_INT) + socket.CMSG_LEN(SIZEOF_INT) - 1) + with downgrade_malformed_data_warning(): # TODO: gh-110012 + msg, ancdata, flags, addr = self.doRecvmsg( + self.serv_sock, len(MSG), + socket.CMSG_SPACE(SIZEOF_INT) + socket.CMSG_LEN(SIZEOF_INT) - 1) self.assertEqual(msg, MSG) self.checkRecvmsgAddress(addr, self.cli_addr) @@ -4595,9 +4826,9 @@ class SendrecvmsgUnixStreamTestBase(SendrecvmsgConnectedBase, ConnectedStreamTestMixin, UnixStreamBase): pass -@unittest.skipIf(sys.platform == "darwin", "flaky on macOS") @requireAttrs(socket.socket, "sendmsg") @requireAttrs(socket, "AF_UNIX") +@unittest.skip("TODO: RUSTPYTHON; accept() on Unix sockets returns EINVAL") class SendmsgUnixStreamTest(SendmsgStreamTests, SendrecvmsgUnixStreamTestBase): pass @@ -4818,6 +5049,112 @@ def _testSend(self): self.assertEqual(msg, MSG) +class PurePythonSocketPairTest(SocketPairTest): + # Explicitly use socketpair AF_INET or AF_INET6 to ensure that is the + # code path we're using regardless platform is the pure python one where + # `_socket.socketpair` does not exist. (AF_INET does not work with + # _socket.socketpair on many platforms). + def socketpair(self): + # called by super().setUp(). + try: + return socket.socketpair(socket.AF_INET6) + except OSError: + return socket.socketpair(socket.AF_INET) + + # Local imports in this class make for easy security fix backporting. + + def setUp(self): + if hasattr(_socket, "socketpair"): + self._orig_sp = socket.socketpair + # This forces the version using the non-OS provided socketpair + # emulation via an AF_INET socket in Lib/socket.py. + socket.socketpair = socket._fallback_socketpair + else: + # This platform already uses the non-OS provided version. + self._orig_sp = None + super().setUp() + + def tearDown(self): + super().tearDown() + if self._orig_sp is not None: + # Restore the default socket.socketpair definition. + socket.socketpair = self._orig_sp + + def test_recv(self): + msg = self.serv.recv(1024) + self.assertEqual(msg, MSG) + + def _test_recv(self): + self.cli.send(MSG) + + def test_send(self): + self.serv.send(MSG) + + def _test_send(self): + msg = self.cli.recv(1024) + self.assertEqual(msg, MSG) + + def test_ipv4(self): + cli, srv = socket.socketpair(socket.AF_INET) + cli.close() + srv.close() + + def _test_ipv4(self): + pass + + @unittest.skipIf(not hasattr(_socket, 'IPPROTO_IPV6') or + not hasattr(_socket, 'IPV6_V6ONLY'), + "IPV6_V6ONLY option not supported") + @unittest.skipUnless(socket_helper.IPV6_ENABLED, 'IPv6 required for this test') + def test_ipv6(self): + cli, srv = socket.socketpair(socket.AF_INET6) + cli.close() + srv.close() + + def _test_ipv6(self): + pass + + def test_injected_authentication_failure(self): + orig_getsockname = socket.socket.getsockname + inject_sock = None + + def inject_getsocketname(self): + nonlocal inject_sock + sockname = orig_getsockname(self) + # Connect to the listening socket ahead of the + # client socket. + if inject_sock is None: + inject_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + inject_sock.setblocking(False) + try: + inject_sock.connect(sockname[:2]) + except (BlockingIOError, InterruptedError): + pass + inject_sock.setblocking(True) + return sockname + + sock1 = sock2 = None + try: + socket.socket.getsockname = inject_getsocketname + with self.assertRaises(OSError): + sock1, sock2 = socket.socketpair() + finally: + socket.socket.getsockname = orig_getsockname + if inject_sock: + inject_sock.close() + if sock1: # This cleanup isn't needed on a successful test. + sock1.close() + if sock2: + sock2.close() + + def _test_injected_authentication_failure(self): + # No-op. Exists for base class threading infrastructure to call. + # We could refactor this test into its own lesser class along with the + # setUp and tearDown code to construct an ideal; it is simpler to keep + # it here and live with extra overhead one this _one_ failure test. + pass + + class NonBlockingTCPTests(ThreadedTCPSocketTest): def __init__(self, methodName='runTest'): @@ -4865,6 +5202,7 @@ def _testSetBlocking(self): pass @support.cpython_only + @unittest.skipIf(_testcapi is None, "requires _testcapi") def testSetBlocking_overflow(self): # Issue 15989 import _testcapi @@ -4882,8 +5220,6 @@ def testSetBlocking_overflow(self): @unittest.skipUnless(hasattr(socket, 'SOCK_NONBLOCK'), 'test needs socket.SOCK_NONBLOCK') @support.requires_linux_version(2, 6, 28) - # TODO: RUSTPYTHON, AssertionError: None != 0 - @unittest.expectedFailure def testInitNonBlocking(self): # create a socket with SOCK_NONBLOCK self.serv.close() @@ -4979,6 +5315,39 @@ def _testRecv(self): # send data: recv() will no longer block self.cli.sendall(MSG) + def testLargeTimeout(self): + # gh-126876: Check that a timeout larger than INT_MAX is replaced with + # INT_MAX in the poll() code path. The following assertion must not + # fail: assert(INT_MIN <= ms && ms <= INT_MAX). + if _testcapi is not None: + large_timeout = _testcapi.INT_MAX + 1 + else: + large_timeout = 2147483648 + + # test recv() with large timeout + conn, addr = self.serv.accept() + self.addCleanup(conn.close) + try: + conn.settimeout(large_timeout) + except OverflowError: + # On Windows, settimeout() fails with OverflowError, whereas + # we want to test recv(). Just give up silently. + return + msg = conn.recv(len(MSG)) + + def _testLargeTimeout(self): + # test sendall() with large timeout + if _testcapi is not None: + large_timeout = _testcapi.INT_MAX + 1 + else: + large_timeout = 2147483648 + self.cli.connect((HOST, self.port)) + try: + self.cli.settimeout(large_timeout) + except OverflowError: + return + self.cli.sendall(MSG) + class FileObjectClassTestCase(SocketConnectedTest): """Unit tests for the object returned by socket.makefile() @@ -5181,6 +5550,8 @@ def _testMakefileClose(self): self.write_file.write(self.write_msg) self.write_file.flush() + @unittest.skipUnless(hasattr(sys, 'getrefcount'), + 'test needs sys.getrefcount()') def testMakefileCloseSocketDestroy(self): refcount_before = sys.getrefcount(self.cli_conn) self.read_file.close() @@ -5619,7 +5990,7 @@ def test_setblocking_invalidfd(self): sock.setblocking(False) -@unittest.skipUnless(sys.platform == 'linux', 'Linux specific test') +@unittest.skipUnless(sys.platform in ('linux', 'android'), 'Linux specific test') class TestLinuxAbstractNamespace(unittest.TestCase): UNIX_PATH_MAX = 108 @@ -5744,7 +6115,8 @@ def testUnencodableAddr(self): self.addCleanup(os_helper.unlink, path) self.assertEqual(self.sock.getsockname(), path) - @unittest.skipIf(sys.platform == 'linux', 'Linux specific test') + @unittest.skipIf(sys.platform in ('linux', 'android'), + 'Linux behavior is tested by TestLinuxAbstractNamespace') def testEmptyAddress(self): # Test that binding empty address fails. self.assertRaises(OSError, self.sock.bind, "") @@ -5970,8 +6342,6 @@ class InheritanceTest(unittest.TestCase): @unittest.skipUnless(hasattr(socket, "SOCK_CLOEXEC"), "SOCK_CLOEXEC not defined") @support.requires_linux_version(2, 6, 28) - # TODO: RUSTPYTHON, AssertionError: 524289 != <SocketKind.SOCK_STREAM: 1> - @unittest.expectedFailure def test_SOCK_CLOEXEC(self): with socket.socket(socket.AF_INET, socket.SOCK_STREAM | socket.SOCK_CLOEXEC) as s: @@ -6064,8 +6434,6 @@ def checkNonblock(self, s, nonblock=True, timeout=0.0): self.assertTrue(s.getblocking()) @support.requires_linux_version(2, 6, 28) - # TODO: RUSTPYTHON, AssertionError: 2049 != <SocketKind.SOCK_STREAM: 1> - @unittest.expectedFailure def test_SOCK_NONBLOCK(self): # a lot of it seems silly and redundant, but I wanted to test that # changing back and forth worked ok @@ -6101,7 +6469,6 @@ def test_SOCK_NONBLOCK(self): @unittest.skipUnless(os.name == "nt", "Windows specific") @unittest.skipUnless(multiprocessing, "need multiprocessing") -@unittest.skip("TODO: RUSTPYTHON, socket sharing") class TestSocketSharing(SocketTCPTest): # This must be classmethod and not staticmethod or multiprocessing # won't be able to bootstrap it. @@ -6342,7 +6709,6 @@ def _testCount(self): self.assertEqual(sent, count) self.assertEqual(file.tell(), count) - @unittest.skipIf(sys.platform == "darwin", "TODO: RUSTPYTHON, killed (for OOM?)") def testCount(self): count = 5000007 conn = self.accept_conn() @@ -6418,7 +6784,6 @@ def _testWithTimeout(self): sent = meth(file) self.assertEqual(sent, self.FILESIZE) - @unittest.skip("TODO: RUSTPYTHON") def testWithTimeout(self): conn = self.accept_conn() data = self.recv_data(conn) @@ -6472,6 +6837,7 @@ def test_errors(self): @unittest.skipUnless(hasattr(os, "sendfile"), 'os.sendfile() required for this test.') +@unittest.skip("TODO: RUSTPYTHON; os.sendfile count parameter not handled correctly") class SendfileUsingSendfileTest(SendfileUsingSendTest): """ Test the sendfile() implementation of socket.sendfile(). @@ -6497,8 +6863,6 @@ def create_alg(self, typ, name): # bpo-31705: On kernel older than 4.5, sendto() failed with ENOKEY, # at least on ppc64le architecture @support.requires_linux_version(4, 5) - # TODO: RUSTPYTHON, OSError: bind(): bad family - @unittest.expectedFailure def test_sha256(self): expected = bytes.fromhex("ba7816bf8f01cfea414140de5dae2223b00361a396" "177a9cb410ff61f20015ad") @@ -6516,8 +6880,6 @@ def test_sha256(self): op.send(b'') self.assertEqual(op.recv(512), expected) - # TODO: RUSTPYTHON, OSError: bind(): bad family - @unittest.expectedFailure def test_hmac_sha1(self): # gh-109396: In FIPS mode, Linux 6.5 requires a key # of at least 112 bits. Use a key of 152 bits. @@ -6534,8 +6896,6 @@ def test_hmac_sha1(self): # Although it should work with 3.19 and newer the test blocks on # Ubuntu 15.10 with Kernel 4.2.0-19. @support.requires_linux_version(4, 3) - # TODO: RUSTPYTHON, OSError: bind(): bad family - @unittest.expectedFailure def test_aes_cbc(self): key = bytes.fromhex('06a9214036b8a15b512e03d534120006') iv = bytes.fromhex('3dafba429d9eb430b422da802c9fac41') @@ -6576,10 +6936,14 @@ def test_aes_cbc(self): self.assertEqual(len(dec), msglen * multiplier) self.assertEqual(dec, msg * multiplier) - @support.requires_linux_version(4, 9) # see issue29324 - # TODO: RUSTPYTHON, OSError: bind(): bad family - @unittest.expectedFailure + @support.requires_linux_version(4, 9) # see gh-73510 def test_aead_aes_gcm(self): + kernel_version = support._get_kernel_version("Linux") + if kernel_version is not None: + if kernel_version >= (6, 16) and kernel_version < (6, 18): + # See https://github.com/python/cpython/issues/139310. + self.skipTest("upstream Linux kernel issue") + key = bytes.fromhex('c939cc13397c1d37de6ae0e1cb7c423c') iv = bytes.fromhex('b3d8cc017cbb89b39e0f67e2') plain = bytes.fromhex('c3b3c41f113a31b73d9a5cd432103069') @@ -6642,8 +7006,6 @@ def test_aead_aes_gcm(self): self.assertEqual(plain, res[assoclen:]) @support.requires_linux_version(4, 3) # see test_aes_cbc - # TODO: RUSTPYTHON, OSError: bind(): bad family - @unittest.expectedFailure def test_drbg_pr_sha256(self): # deterministic random bit generator, prediction resistance, sha256 with self.create_alg('rng', 'drbg_pr_sha256') as algo: @@ -6654,8 +7016,6 @@ def test_drbg_pr_sha256(self): rn = op.recv(32) self.assertEqual(len(rn), 32) - # TODO: RUSTPYTHON, AttributeError: 'socket' object has no attribute 'sendmsg_afalg' - @unittest.expectedFailure def test_sendmsg_afalg_args(self): sock = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0) with sock: @@ -6674,8 +7034,6 @@ def test_sendmsg_afalg_args(self): with self.assertRaises(TypeError): sock.sendmsg_afalg(op=socket.ALG_OP_ENCRYPT, assoclen=-1) - # TODO: RUSTPYTHON, OSError: bind(): bad family - @unittest.expectedFailure def test_length_restriction(self): # bpo-35050, off-by-one error in length check sock = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0) diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index 8ad699d60a8..d6250e472bb 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -2804,7 +2804,7 @@ mod _socket { let encoded = vm.state .codec_registry - .encode_text(s.to_owned().into(), "idna", None, vm)?; + .encode_text(s.to_owned(), "idna", None, vm)?; let host_str = std::str::from_utf8(encoded.as_bytes()) .map_err(|_| vm.new_runtime_error("idna output is not utf8".to_owned()))?; Some(host_str.to_owned()) From ed62c8ef58cbac2ab037f206203bfbbc907ae8cf Mon Sep 17 00:00:00 2001 From: Jeong YunWon <jeong@youknowone.org> Date: Mon, 29 Dec 2025 11:27:00 +0900 Subject: [PATCH 621/819] mark failures --- Lib/test/test_socket.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 08676cf3388..87479171b5c 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -2394,6 +2394,8 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.interface = "vcan0" + # TODO: RUSTPYTHON - J1939 constants not fully implemented + @unittest.expectedFailure @unittest.skipUnless(hasattr(socket, "CAN_J1939"), 'socket.CAN_J1939 required for this test.') def testJ1939Constants(self): @@ -2435,6 +2437,8 @@ def testCreateJ1939Socket(self): with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939) as s: pass + # TODO: RUSTPYTHON - AF_CAN J1939 address format not fully implemented + @unittest.expectedFailure def testBind(self): try: with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939) as s: @@ -4834,6 +4838,7 @@ class SendmsgUnixStreamTest(SendmsgStreamTests, SendrecvmsgUnixStreamTestBase): @requireAttrs(socket.socket, "recvmsg") @requireAttrs(socket, "AF_UNIX") +@unittest.skip("TODO: RUSTPYTHON; intermittent accept() EINVAL on Unix sockets") class RecvmsgUnixStreamTest(RecvmsgTests, RecvmsgGenericStreamTests, SendrecvmsgUnixStreamTestBase): pass @@ -4846,6 +4851,7 @@ class RecvmsgIntoUnixStreamTest(RecvmsgIntoTests, RecvmsgGenericStreamTests, @requireAttrs(socket.socket, "sendmsg", "recvmsg") @requireAttrs(socket, "AF_UNIX", "SOL_SOCKET", "SCM_RIGHTS") +@unittest.skip("TODO: RUSTPYTHON; intermittent accept() EINVAL on Unix sockets") class RecvmsgSCMRightsStreamTest(SCMRightsTest, SendrecvmsgUnixStreamTestBase): pass @@ -6486,6 +6492,8 @@ def remoteProcessServer(cls, q): s2.close() s.close() + # TODO: RUSTPYTHON; multiprocessing.SemLock not implemented + @unittest.expectedFailure def testShare(self): # Transfer the listening server socket to another process # and service it from there. @@ -6862,6 +6870,8 @@ def create_alg(self, typ, name): # bpo-31705: On kernel older than 4.5, sendto() failed with ENOKEY, # at least on ppc64le architecture + # TODO: RUSTPYTHON - AF_ALG not fully implemented + @unittest.expectedFailure @support.requires_linux_version(4, 5) def test_sha256(self): expected = bytes.fromhex("ba7816bf8f01cfea414140de5dae2223b00361a396" @@ -6880,6 +6890,8 @@ def test_sha256(self): op.send(b'') self.assertEqual(op.recv(512), expected) + # TODO: RUSTPYTHON - AF_ALG not fully implemented + @unittest.expectedFailure def test_hmac_sha1(self): # gh-109396: In FIPS mode, Linux 6.5 requires a key # of at least 112 bits. Use a key of 152 bits. @@ -6895,6 +6907,8 @@ def test_hmac_sha1(self): # Although it should work with 3.19 and newer the test blocks on # Ubuntu 15.10 with Kernel 4.2.0-19. + # TODO: RUSTPYTHON - AF_ALG not fully implemented + @unittest.expectedFailure @support.requires_linux_version(4, 3) def test_aes_cbc(self): key = bytes.fromhex('06a9214036b8a15b512e03d534120006') @@ -6936,6 +6950,8 @@ def test_aes_cbc(self): self.assertEqual(len(dec), msglen * multiplier) self.assertEqual(dec, msg * multiplier) + # TODO: RUSTPYTHON - AF_ALG not fully implemented + @unittest.expectedFailure @support.requires_linux_version(4, 9) # see gh-73510 def test_aead_aes_gcm(self): kernel_version = support._get_kernel_version("Linux") @@ -7005,6 +7021,8 @@ def test_aead_aes_gcm(self): res = op.recv(len(msg) - taglen) self.assertEqual(plain, res[assoclen:]) + # TODO: RUSTPYTHON - AF_ALG not fully implemented + @unittest.expectedFailure @support.requires_linux_version(4, 3) # see test_aes_cbc def test_drbg_pr_sha256(self): # deterministic random bit generator, prediction resistance, sha256 From feb5066be2d4854ef0d49b8a725093743598d54d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 29 Dec 2025 21:43:40 +0900 Subject: [PATCH 622/819] stdlib_module_names (#6568) --- .cspell.dict/cpython.txt | 4 + Lib/test/test_sys.py | 2 - crates/vm/src/builtins/frame.rs | 5 + crates/vm/src/stdlib/sys.rs | 307 +++++++++++++++++++++++++++++++- 4 files changed, 315 insertions(+), 3 deletions(-) diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index cb6f774ed18..5011975f496 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -36,8 +36,10 @@ kwonlyarg kwonlyargs lasti linearise +lsprof maxdepth mult +multibytecodec nkwargs noraise numer @@ -49,6 +51,8 @@ posonlyarg posonlyargs prec preinitialized +pydecimal +pyrepl pythonw PYTHREAD_NAME releasebuffer diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 1ce2e9fc0fe..6ae3eaee74b 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1124,8 +1124,6 @@ def test_orig_argv(self): self.assertEqual(proc.stdout.rstrip().splitlines(), expected, proc) - # TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute 'stdlib_module_names' - @unittest.expectedFailure def test_module_names(self): self.assertIsInstance(sys.stdlib_module_names, frozenset) for name in sys.stdlib_module_names: diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index 6ccc594d338..3712b04e875 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -41,6 +41,11 @@ impl Frame { self.globals.clone() } + #[pygetset] + fn f_builtins(&self) -> PyDictRef { + self.builtins.clone() + } + #[pygetset] fn f_locals(&self, vm: &VirtualMachine) -> PyResult { self.locals(vm).map(Into::into) diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 0e46ec18a01..92b15878b46 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -9,7 +9,8 @@ mod sys { use crate::{ AsObject, PyObject, PyObjectRef, PyRef, PyRefExact, PyResult, builtins::{ - PyBaseExceptionRef, PyDictRef, PyNamespace, PyStr, PyStrRef, PyTupleRef, PyTypeRef, + PyBaseExceptionRef, PyDictRef, PyFrozenSet, PyNamespace, PyStr, PyStrRef, PyTupleRef, + PyTypeRef, }, common::{ ascii, @@ -148,6 +149,310 @@ mod sys { ) } + // List from cpython/Python/stdlib_module_names.h + const STDLIB_MODULE_NAMES: &[&str] = &[ + "__future__", + "_abc", + "_aix_support", + "_android_support", + "_apple_support", + "_ast", + "_asyncio", + "_bisect", + "_blake2", + "_bz2", + "_codecs", + "_codecs_cn", + "_codecs_hk", + "_codecs_iso2022", + "_codecs_jp", + "_codecs_kr", + "_codecs_tw", + "_collections", + "_collections_abc", + "_colorize", + "_compat_pickle", + "_compression", + "_contextvars", + "_csv", + "_ctypes", + "_curses", + "_curses_panel", + "_datetime", + "_dbm", + "_decimal", + "_elementtree", + "_frozen_importlib", + "_frozen_importlib_external", + "_functools", + "_gdbm", + "_hashlib", + "_heapq", + "_imp", + "_interpchannels", + "_interpqueues", + "_interpreters", + "_io", + "_ios_support", + "_json", + "_locale", + "_lsprof", + "_lzma", + "_markupbase", + "_md5", + "_multibytecodec", + "_multiprocessing", + "_opcode", + "_opcode_metadata", + "_operator", + "_osx_support", + "_overlapped", + "_pickle", + "_posixshmem", + "_posixsubprocess", + "_py_abc", + "_pydatetime", + "_pydecimal", + "_pyio", + "_pylong", + "_pyrepl", + "_queue", + "_random", + "_scproxy", + "_sha1", + "_sha2", + "_sha3", + "_signal", + "_sitebuiltins", + "_socket", + "_sqlite3", + "_sre", + "_ssl", + "_stat", + "_statistics", + "_string", + "_strptime", + "_struct", + "_suggestions", + "_symtable", + "_sysconfig", + "_thread", + "_threading_local", + "_tkinter", + "_tokenize", + "_tracemalloc", + "_typing", + "_uuid", + "_warnings", + "_weakref", + "_weakrefset", + "_winapi", + "_wmi", + "_zoneinfo", + "abc", + "antigravity", + "argparse", + "array", + "ast", + "asyncio", + "atexit", + "base64", + "bdb", + "binascii", + "bisect", + "builtins", + "bz2", + "cProfile", + "calendar", + "cmath", + "cmd", + "code", + "codecs", + "codeop", + "collections", + "colorsys", + "compileall", + "concurrent", + "configparser", + "contextlib", + "contextvars", + "copy", + "copyreg", + "csv", + "ctypes", + "curses", + "dataclasses", + "datetime", + "dbm", + "decimal", + "difflib", + "dis", + "doctest", + "email", + "encodings", + "ensurepip", + "enum", + "errno", + "faulthandler", + "fcntl", + "filecmp", + "fileinput", + "fnmatch", + "fractions", + "ftplib", + "functools", + "gc", + "genericpath", + "getopt", + "getpass", + "gettext", + "glob", + "graphlib", + "grp", + "gzip", + "hashlib", + "heapq", + "hmac", + "html", + "http", + "idlelib", + "imaplib", + "importlib", + "inspect", + "io", + "ipaddress", + "itertools", + "json", + "keyword", + "linecache", + "locale", + "logging", + "lzma", + "mailbox", + "marshal", + "math", + "mimetypes", + "mmap", + "modulefinder", + "msvcrt", + "multiprocessing", + "netrc", + "nt", + "ntpath", + "nturl2path", + "numbers", + "opcode", + "operator", + "optparse", + "os", + "pathlib", + "pdb", + "pickle", + "pickletools", + "pkgutil", + "platform", + "plistlib", + "poplib", + "posix", + "posixpath", + "pprint", + "profile", + "pstats", + "pty", + "pwd", + "py_compile", + "pyclbr", + "pydoc", + "pydoc_data", + "pyexpat", + "queue", + "quopri", + "random", + "re", + "readline", + "reprlib", + "resource", + "rlcompleter", + "runpy", + "sched", + "secrets", + "select", + "selectors", + "shelve", + "shlex", + "shutil", + "signal", + "site", + "smtplib", + "socket", + "socketserver", + "sqlite3", + "sre_compile", + "sre_constants", + "sre_parse", + "ssl", + "stat", + "statistics", + "string", + "stringprep", + "struct", + "subprocess", + "symtable", + "sys", + "sysconfig", + "syslog", + "tabnanny", + "tarfile", + "tempfile", + "termios", + "textwrap", + "this", + "threading", + "time", + "timeit", + "tkinter", + "token", + "tokenize", + "tomllib", + "trace", + "traceback", + "tracemalloc", + "tty", + "turtle", + "turtledemo", + "types", + "typing", + "unicodedata", + "unittest", + "urllib", + "uuid", + "venv", + "warnings", + "wave", + "weakref", + "webbrowser", + "winreg", + "winsound", + "wsgiref", + "xml", + "xmlrpc", + "zipapp", + "zipfile", + "zipimport", + "zlib", + "zoneinfo", + ]; + + #[pyattr(once)] + fn stdlib_module_names(vm: &VirtualMachine) -> PyObjectRef { + let names = STDLIB_MODULE_NAMES + .iter() + .map(|&n| vm.ctx.new_str(n).into()); + PyFrozenSet::from_iter(vm, names) + .expect("Creating stdlib_module_names frozen set must succeed") + .to_pyobject(vm) + } + #[pyattr] fn byteorder(vm: &VirtualMachine) -> PyStrRef { // https://doc.rust-lang.org/reference/conditional-compilation.html#target_endian From 879813f7635f78ddf4b8ff00a59c0b1518726df1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:52:52 +0900 Subject: [PATCH 623/819] Bump rustix from 1.1.2 to 1.1.3 (#6572) Bumps [rustix](https://github.com/bytecodealliance/rustix) from 1.1.2 to 1.1.3. - [Release notes](https://github.com/bytecodealliance/rustix/releases) - [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md) - [Commits](https://github.com/bytecodealliance/rustix/compare/v1.1.2...v1.1.3) --- updated-dependencies: - dependency-name: rustix dependency-version: 1.1.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 18 +++++++++--------- Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffcc1e37c17..ad1e52c9587 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,7 +113,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -124,7 +124,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1146,7 +1146,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2853,15 +2853,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2926,7 +2926,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3739,7 +3739,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4389,7 +4389,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 44f9d3190f7..06734b47e18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -199,7 +199,7 @@ quote = "1.0.38" radium = "1.1.1" rand = "0.9" rand_core = { version = "0.9", features = ["os_rng"] } -rustix = { version = "1.0", features = ["event"] } +rustix = { version = "1.1", features = ["event"] } rustyline = "17.0.1" serde = { version = "1.0.225", default-features = false } schannel = "0.1.28" From 80599603e5775159a695d051fe43fd006bc868ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:53:04 +0900 Subject: [PATCH 624/819] Bump insta from 1.45.0 to 1.45.1 (#6575) Bumps [insta](https://github.com/mitsuhiko/insta) from 1.45.0 to 1.45.1. - [Release notes](https://github.com/mitsuhiko/insta/releases) - [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md) - [Commits](https://github.com/mitsuhiko/insta/compare/1.45.0...1.45.1) --- updated-dependencies: - dependency-name: insta dependency-version: 1.45.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad1e52c9587..46c4dafe13b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,7 +1181,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1487,9 +1487,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.45.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b76866be74d68b1595eb8060cb9191dca9c021db2316558e52ddc5d55d41b66c" +checksum = "983e3b24350c84ab8a65151f537d67afbbf7153bb9f1110e03e9fa9b07f67a5c" dependencies = [ "console", "once_cell", From 39aa6f952489c290c2174b27eec3aad21e365648 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 29 Dec 2025 23:17:16 +0900 Subject: [PATCH 625/819] use accept_raw (#6578) --- Lib/test/test_socket.py | 3 --- crates/stdlib/src/socket.rs | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 87479171b5c..08d3dcb5792 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -4832,13 +4832,11 @@ class SendrecvmsgUnixStreamTestBase(SendrecvmsgConnectedBase, @requireAttrs(socket.socket, "sendmsg") @requireAttrs(socket, "AF_UNIX") -@unittest.skip("TODO: RUSTPYTHON; accept() on Unix sockets returns EINVAL") class SendmsgUnixStreamTest(SendmsgStreamTests, SendrecvmsgUnixStreamTestBase): pass @requireAttrs(socket.socket, "recvmsg") @requireAttrs(socket, "AF_UNIX") -@unittest.skip("TODO: RUSTPYTHON; intermittent accept() EINVAL on Unix sockets") class RecvmsgUnixStreamTest(RecvmsgTests, RecvmsgGenericStreamTests, SendrecvmsgUnixStreamTestBase): pass @@ -4851,7 +4849,6 @@ class RecvmsgIntoUnixStreamTest(RecvmsgIntoTests, RecvmsgGenericStreamTests, @requireAttrs(socket.socket, "sendmsg", "recvmsg") @requireAttrs(socket, "AF_UNIX", "SOL_SOCKET", "SCM_RIGHTS") -@unittest.skip("TODO: RUSTPYTHON; intermittent accept() EINVAL on Unix sockets") class RecvmsgSCMRightsStreamTest(SCMRightsTest, SendrecvmsgUnixStreamTestBase): pass diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index d6250e472bb..91c7b1201f1 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -1570,7 +1570,9 @@ mod _socket { &self, vm: &VirtualMachine, ) -> Result<(RawSocket, PyObjectRef), IoOrPyException> { - let (sock, addr) = self.sock_op(vm, SelectKind::Read, || self.sock()?.accept())?; + // Use accept_raw() instead of accept() to avoid socket2's set_common_flags() + // which tries to set SO_NOSIGPIPE and fails with EINVAL on Unix domain sockets on macOS + let (sock, addr) = self.sock_op(vm, SelectKind::Read, || self.sock()?.accept_raw())?; let fd = into_sock_fileno(sock); Ok((fd, get_addr_tuple(&addr, vm))) } From 4ce68279166016888797f3a5f9eccdae781c13da Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 30 Dec 2025 00:24:07 +0900 Subject: [PATCH 626/819] various fix to support subprocess/multiprocessing (#6567) * hashlib.UnsupportedDigestmodError * mmap readonly * thread _refcount * fix os.statvfs * apply review --- Lib/test/test_hmac.py | 8 --- crates/stdlib/src/hashlib.rs | 29 ++++++--- crates/stdlib/src/mmap.rs | 3 +- crates/vm/src/stdlib/os.rs | 106 +++++++++++++++++++++++++++++++++ crates/vm/src/stdlib/thread.rs | 25 +++++++- 5 files changed, 152 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index 8e1a4a204c5..74ebcb2fe70 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -432,8 +432,6 @@ def test_dot_new_with_str_key(self): with self.assertRaises(TypeError): h = hmac.new("key", digestmod='sha256') - # TODO: RUSTPYTHON - @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_withtext(self): # Constructor call with text. @@ -443,8 +441,6 @@ def test_withtext(self): self.fail("Constructor call with text argument raised exception.") self.assertEqual(h.hexdigest(), self.expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_with_bytearray(self): try: @@ -454,8 +450,6 @@ def test_with_bytearray(self): self.fail("Constructor call with bytearray arguments raised exception.") self.assertEqual(h.hexdigest(), self.expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_with_memoryview_msg(self): try: @@ -464,8 +458,6 @@ def test_with_memoryview_msg(self): self.fail("Constructor call with memoryview msg raised exception.") self.assertEqual(h.hexdigest(), self.expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_withmodule(self): # Constructor call with text and digest module. diff --git a/crates/stdlib/src/hashlib.rs b/crates/stdlib/src/hashlib.rs index e7b03a2ff12..bfde58f43f7 100644 --- a/crates/stdlib/src/hashlib.rs +++ b/crates/stdlib/src/hashlib.rs @@ -7,11 +7,11 @@ pub mod _hashlib { use crate::common::lock::PyRwLock; use crate::vm::{ Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyBytes, PyStrRef, PyTypeRef}, + builtins::{PyBytes, PyStrRef, PyTypeRef, PyValueError}, + class::StaticType, convert::ToPyObject, function::{ArgBytesLike, ArgStrOrBytesLike, FuncArgs, OptionalArg}, - protocol::PyBuffer, - types::Representable, + types::{Constructor, Initializer, Representable}, }; use blake2::{Blake2b512, Blake2s256}; use digest::{DynDigest, core_api::BlockSizeUser}; @@ -22,6 +22,12 @@ pub mod _hashlib { use sha2::{Sha224, Sha256, Sha384, Sha512}; use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256}; + #[pyattr] + #[pyexception(name = "UnsupportedDigestmodError", base = PyValueError, impl)] + #[derive(Debug)] + #[repr(transparent)] + pub struct UnsupportedDigestmodError(PyValueError); + #[derive(FromArgs, Debug)] #[allow(unused)] struct NewHashArgs { @@ -338,16 +344,21 @@ pub mod _hashlib { #[allow(unused)] pub struct NewHMACHashArgs { #[pyarg(positional)] - name: PyBuffer, + key: ArgBytesLike, #[pyarg(any, optional)] - data: OptionalArg<ArgBytesLike>, - #[pyarg(named, default = true)] - digestmod: bool, // TODO: RUSTPYTHON support functions & name functions + msg: OptionalArg<ArgBytesLike>, + #[pyarg(named, optional)] + digestmod: OptionalArg<PyObjectRef>, } #[pyfunction] - fn hmac_new(_args: NewHMACHashArgs, vm: &VirtualMachine) -> PyResult<PyObjectRef> { - Err(vm.new_type_error("cannot create 'hmac' instances")) // TODO: RUSTPYTHON support hmac + fn hmac_new(args: NewHMACHashArgs, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + // Raise UnsupportedDigestmodError so Python's hmac.py falls back to pure-Python implementation + let _ = args; + Err(vm.new_exception_msg( + UnsupportedDigestmodError::static_type().to_owned(), + "unsupported hash type".to_owned(), + )) } pub trait ThreadSafeDynDigest: DynClone + DynDigest + Sync + Send {} diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index 5309917a999..f1e2c2a039d 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -651,9 +651,10 @@ mod mmap { impl AsBuffer for PyMmap { fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { + let readonly = matches!(zelf.access, AccessMode::Read); let buf = PyBuffer::new( zelf.to_owned().into(), - BufferDescriptor::simple(zelf.__len__(), true), + BufferDescriptor::simple(zelf.__len__(), readonly), &BUFFER_METHODS, ); diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 6849ea365df..6e2bb274fec 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -202,6 +202,15 @@ pub(super) mod _os { #[pyattr] pub(crate) const X_OK: u8 = 1 << 0; + // ST_RDONLY and ST_NOSUID flags for statvfs + #[cfg(all(unix, not(target_os = "redox")))] + #[pyattr] + const ST_RDONLY: libc::c_ulong = libc::ST_RDONLY; + + #[cfg(all(unix, not(target_os = "redox")))] + #[pyattr] + const ST_NOSUID: libc::c_ulong = libc::ST_NOSUID; + #[pyfunction] fn close(fileno: crt_fd::Owned) -> io::Result<()> { crt_fd::close(fileno) @@ -1701,6 +1710,103 @@ pub(super) mod _os { #[pyclass(with(PyStructSequence))] impl PyUnameResult {} + // statvfs_result: Result from statvfs or fstatvfs. + // = statvfs_result_fields + #[cfg(all(unix, not(target_os = "redox")))] + #[derive(Debug)] + #[pystruct_sequence_data] + pub(crate) struct StatvfsResultData { + pub f_bsize: libc::c_ulong, // filesystem block size + pub f_frsize: libc::c_ulong, // fragment size + pub f_blocks: libc::fsblkcnt_t, // size of fs in f_frsize units + pub f_bfree: libc::fsblkcnt_t, // free blocks + pub f_bavail: libc::fsblkcnt_t, // free blocks for unprivileged users + pub f_files: libc::fsfilcnt_t, // inodes + pub f_ffree: libc::fsfilcnt_t, // free inodes + pub f_favail: libc::fsfilcnt_t, // free inodes for unprivileged users + pub f_flag: libc::c_ulong, // mount flags + pub f_namemax: libc::c_ulong, // maximum filename length + #[pystruct_sequence(skip)] + pub f_fsid: libc::c_ulong, // filesystem ID (not in tuple but accessible as attribute) + } + + #[cfg(all(unix, not(target_os = "redox")))] + #[pyattr] + #[pystruct_sequence(name = "statvfs_result", module = "os", data = "StatvfsResultData")] + pub(crate) struct PyStatvfsResult; + + #[cfg(all(unix, not(target_os = "redox")))] + #[pyclass(with(PyStructSequence))] + impl PyStatvfsResult { + #[pyslot] + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let seq: PyObjectRef = args.bind(vm)?; + crate::types::struct_sequence_new(cls, seq, vm) + } + } + + #[cfg(all(unix, not(target_os = "redox")))] + impl StatvfsResultData { + fn from_statvfs(st: libc::statvfs) -> Self { + // f_fsid is a struct on some platforms (e.g., Linux fsid_t) and a scalar on others. + // We extract raw bytes and interpret as a native-endian integer. + // Note: The value may differ across architectures due to endianness. + let f_fsid = { + let ptr = std::ptr::addr_of!(st.f_fsid) as *const u8; + let size = std::mem::size_of_val(&st.f_fsid); + if size >= 8 { + let bytes = unsafe { std::slice::from_raw_parts(ptr, 8) }; + u64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], + bytes[7], + ]) as libc::c_ulong + } else if size >= 4 { + let bytes = unsafe { std::slice::from_raw_parts(ptr, 4) }; + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as libc::c_ulong + } else { + 0 + } + }; + + Self { + f_bsize: st.f_bsize, + f_frsize: st.f_frsize, + f_blocks: st.f_blocks, + f_bfree: st.f_bfree, + f_bavail: st.f_bavail, + f_files: st.f_files, + f_ffree: st.f_ffree, + f_favail: st.f_favail, + f_flag: st.f_flag, + f_namemax: st.f_namemax, + f_fsid, + } + } + } + + /// Perform a statvfs system call on the given path. + #[cfg(all(unix, not(target_os = "redox")))] + #[pyfunction] + #[pyfunction(name = "fstatvfs")] + fn statvfs(path: OsPathOrFd<'_>, vm: &VirtualMachine) -> PyResult { + let mut st: libc::statvfs = unsafe { std::mem::zeroed() }; + let ret = match &path { + OsPathOrFd::Path(p) => { + let cpath = p.clone().into_cstring(vm)?; + unsafe { libc::statvfs(cpath.as_ptr(), &mut st) } + } + OsPathOrFd::Fd(fd) => unsafe { libc::fstatvfs(fd.as_raw(), &mut st) }, + }; + if ret != 0 { + return Err(OSErrorBuilder::with_filename( + &io::Error::last_os_error(), + path, + vm, + )); + } + Ok(StatvfsResultData::from_statvfs(st).to_pyobject(vm)) + } + pub(super) fn support_funcs() -> Vec<SupportFunc> { let mut supports = super::platform::module::support_funcs(); supports.extend(vec![ diff --git a/crates/vm/src/stdlib/thread.rs b/crates/vm/src/stdlib/thread.rs index 36252279397..fd300ac2f74 100644 --- a/crates/vm/src/stdlib/thread.rs +++ b/crates/vm/src/stdlib/thread.rs @@ -190,6 +190,7 @@ pub(crate) mod _thread { #[derive(PyPayload)] struct RLock { mu: RawRMutex, + count: std::sync::atomic::AtomicUsize, } impl fmt::Debug for RLock { @@ -204,6 +205,7 @@ pub(crate) mod _thread { fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { Self { mu: RawRMutex::INIT, + count: std::sync::atomic::AtomicUsize::new(0), } .into_ref_with_type(vm, cls) .map(Into::into) @@ -213,7 +215,12 @@ pub(crate) mod _thread { #[pymethod(name = "acquire_lock")] #[pymethod(name = "__enter__")] fn acquire(&self, args: AcquireArgs, vm: &VirtualMachine) -> PyResult<bool> { - acquire_lock_impl!(&self.mu, args, vm) + let result = acquire_lock_impl!(&self.mu, args, vm)?; + if result { + self.count + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + Ok(result) } #[pymethod] #[pymethod(name = "release_lock")] @@ -221,6 +228,12 @@ pub(crate) mod _thread { if !self.mu.is_locked() { return Err(vm.new_runtime_error("release unlocked lock")); } + debug_assert!( + self.count.load(std::sync::atomic::Ordering::Relaxed) > 0, + "RLock count underflow" + ); + self.count + .fetch_sub(1, std::sync::atomic::Ordering::Relaxed); unsafe { self.mu.unlock() }; Ok(()) } @@ -232,6 +245,7 @@ pub(crate) mod _thread { self.mu.unlock(); }; } + self.count.store(0, std::sync::atomic::Ordering::Relaxed); let new_mut = RawRMutex::INIT; let old_mutex: AtomicCell<&RawRMutex> = AtomicCell::new(&self.mu); @@ -245,6 +259,15 @@ pub(crate) mod _thread { self.mu.is_owned_by_current_thread() } + #[pymethod] + fn _recursion_count(&self) -> usize { + if self.mu.is_owned_by_current_thread() { + self.count.load(std::sync::atomic::Ordering::Relaxed) + } else { + 0 + } + } + #[pymethod] fn __exit__(&self, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { self.release(vm) From f91ffe34d45ec744cd0d6e00703ed3eb19db0c22 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 30 Dec 2025 00:36:52 +0900 Subject: [PATCH 627/819] Fix pyexpat parse (#6582) --- Lib/test/test_pyexpat.py | 7 -- Lib/test/test_xml_dom_xmlbuilder.py | 4 - Lib/test/test_xmlrpc.py | 38 ---------- Lib/xmlrpc/client.py | 3 +- crates/stdlib/src/pyexpat.rs | 110 +++++++++++++++++++++------- 5 files changed, 85 insertions(+), 77 deletions(-) diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index 015e7497268..d360e8cd65d 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -227,7 +227,6 @@ def _verify_parse_output(self, operations): for operation, expected_operation in zip(operations, expected_operations): self.assertEqual(operation, expected_operation) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_bytes(self): out = self.Outputter() parser = expat.ParserCreate(namespace_separator='!') @@ -276,7 +275,6 @@ def test_parse_again(self): expat.errors.XML_ERROR_FINISHED) class NamespaceSeparatorTest(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_legal(self): # Tests that make sure we get errors when the namespace_separator value # is illegal, and that we don't for good values: @@ -409,14 +407,12 @@ def test2(self): self.assertEqual(self.stuff, ["1<2> \n 3"], "buffered text not properly collapsed") - @unittest.expectedFailure # TODO: RUSTPYTHON def test3(self): self.setHandlers(["StartElementHandler"]) self.parser.Parse(b"<a>1<b/>2<c/>3</a>", True) self.assertEqual(self.stuff, ["<a>", "1", "<b>", "2", "<c>", "3"], "buffered text not properly split") - @unittest.expectedFailure # TODO: RUSTPYTHON def test4(self): self.setHandlers(["StartElementHandler", "EndElementHandler"]) self.parser.CharacterDataHandler = None @@ -424,14 +420,12 @@ def test4(self): self.assertEqual(self.stuff, ["<a>", "<b>", "</b>", "<c>", "</c>", "</a>"]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test5(self): self.setHandlers(["StartElementHandler", "EndElementHandler"]) self.parser.Parse(b"<a>1<b></b>2<c/>3</a>", True) self.assertEqual(self.stuff, ["<a>", "1", "<b>", "</b>", "2", "<c>", "</c>", "3", "</a>"]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test6(self): self.setHandlers(["CommentHandler", "EndElementHandler", "StartElementHandler"]) @@ -529,7 +523,6 @@ def check_pos(self, event): 'Expected position %s, got position %s' %(pos, expected)) self.upto += 1 - @unittest.expectedFailure # TODO: RUSTPYTHON def test(self): self.parser = expat.ParserCreate() self.parser.StartElementHandler = self.StartElementHandler diff --git a/Lib/test/test_xml_dom_xmlbuilder.py b/Lib/test/test_xml_dom_xmlbuilder.py index 5282e806e40..5f5f2eb328d 100644 --- a/Lib/test/test_xml_dom_xmlbuilder.py +++ b/Lib/test/test_xml_dom_xmlbuilder.py @@ -50,8 +50,6 @@ def test_builder(self): builder = imp.createDOMBuilder(imp.MODE_SYNCHRONOUS, None) self.assertIsInstance(builder, xmlbuilder.DOMBuilder) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_parse_uri(self): body = ( b"HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n\r\n" @@ -74,8 +72,6 @@ def test_parse_uri(self): self.assertIsInstance(document, minidom.Document) self.assertEqual(len(document.childNodes), 1) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_parse_with_systemId(self): response = io.BytesIO(SMALL_SAMPLE) diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py index 8684e042bee..cf3f535b190 100644 --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@ -47,13 +47,11 @@ class XMLRPCTestCase(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_load(self): dump = xmlrpclib.dumps((alist,)) load = xmlrpclib.loads(dump) self.assertEqual(alist, load[0][0]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_bare_datetime(self): # This checks that an unwrapped datetime.date object can be handled # by the marshalling code. This can't be done via test_dump_load() @@ -88,7 +86,6 @@ def test_dump_bare_datetime(self): self.assertIsNone(m) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_datetime_before_1900(self): # same as before but with a date before 1900 dt = datetime.datetime(1, 2, 10, 11, 41, 23) @@ -107,7 +104,6 @@ def test_datetime_before_1900(self): self.assertIs(type(newdt), xmlrpclib.DateTime) self.assertIsNone(m) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_1164912 (self): d = xmlrpclib.DateTime() ((new_d,), dummy) = xmlrpclib.loads(xmlrpclib.dumps((d,), @@ -118,7 +114,6 @@ def test_bug_1164912 (self): s = xmlrpclib.dumps((new_d,), methodresponse=True) self.assertIsInstance(s, str) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_newstyle_class(self): class T(object): pass @@ -184,7 +179,6 @@ def dummy_write(s): m.dump_double(xmlrpclib.MAXINT + 42, dummy_write) m.dump_double(xmlrpclib.MININT - 42, dummy_write) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_none(self): value = alist + [None] arg1 = (alist + [None],) @@ -215,7 +209,6 @@ def test_dump_encoding(self): self.assertEqual(xmlrpclib.loads(strg)[0][0], value) self.assertEqual(xmlrpclib.loads(strg)[1], methodname) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_bytes(self): sample = b"my dog has fleas" self.assertEqual(sample, xmlrpclib.Binary(sample)) @@ -258,7 +251,6 @@ def check_loads(self, s, value, **kwargs): self.assertIs(type(newvalue), type(value)) self.assertIsNone(m) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_load_standard_types(self): check = self.check_loads check('string', 'string') @@ -300,7 +292,6 @@ def test_load_extension_types(self): check('<bigdecimal>9876543210.0123456789</bigdecimal>', decimal.Decimal('9876543210.0123456789')) - @unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'expat' is not defined def test_limit_int(self): check = self.check_loads maxdigits = 5000 @@ -332,7 +323,6 @@ def test_ssl_presence(self): except OSError: self.assertTrue(has_ssl) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_keepalive_disconnect(self): class RequestHandler(http.server.BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" @@ -472,7 +462,6 @@ def test_repr(self): self.assertEqual(repr(f), "<Fault 42: 'Test Fault'>") self.assertEqual(repr(f), str(f)) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_dump_fault(self): f = xmlrpclib.Fault(42, 'Test Fault') s = xmlrpclib.dumps((f,)) @@ -820,7 +809,6 @@ def tearDown(self): xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = False class SimpleServerTestCase(BaseServerTestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_simple1(self): try: p = xmlrpclib.ServerProxy(URL) @@ -831,7 +819,6 @@ def test_simple1(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_nonascii(self): start_string = 'P\N{LATIN SMALL LETTER Y WITH CIRCUMFLEX}t' end_string = 'h\N{LATIN SMALL LETTER O WITH HORN}n' @@ -860,7 +847,6 @@ def test_client_encoding(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_nonascii_methodname(self): try: p = xmlrpclib.ServerProxy(URL, encoding='ascii') @@ -881,7 +867,6 @@ def test_404(self): self.assertEqual(response.status, 404) self.assertEqual(response.reason, 'Not Found') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_introspection1(self): expected_methods = set(['pow', 'div', 'my_function', 'add', 'têšt', 'system.listMethods', 'system.methodHelp', @@ -898,7 +883,6 @@ def test_introspection1(self): self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_introspection2(self): try: # test _methodHelp() @@ -911,7 +895,6 @@ def test_introspection2(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - @unittest.expectedFailure # TODO: RUSTPYTHON @make_request_and_skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_introspection3(self): @@ -926,7 +909,6 @@ def test_introspection3(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_introspection4(self): # the SimpleXMLRPCServer doesn't support signatures, but # at least check that we can try making the call @@ -940,7 +922,6 @@ def test_introspection4(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_multicall(self): try: p = xmlrpclib.ServerProxy(URL) @@ -958,7 +939,6 @@ def test_multicall(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_non_existing_multicall(self): try: p = xmlrpclib.ServerProxy(URL) @@ -980,7 +960,6 @@ def test_non_existing_multicall(self): # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_dotted_attribute(self): # Raises an AttributeError because private methods are not allowed. self.assertRaises(AttributeError, @@ -991,14 +970,12 @@ def test_dotted_attribute(self): # This avoids waiting for the socket timeout. self.test_simple1() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_allow_dotted_names_true(self): # XXX also need allow_dotted_names_false test. server = xmlrpclib.ServerProxy("http://%s:%d/RPC2" % (ADDR, PORT)) data = server.Fixture.getData() self.assertEqual(data, '42') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_unicode_host(self): server = xmlrpclib.ServerProxy("http://%s:%d/RPC2" % (ADDR, PORT)) self.assertEqual(server.add("a", "\xe9"), "a\xe9") @@ -1013,7 +990,6 @@ def test_partial_post(self): 'Accept-Encoding: identity\r\n' 'Content-Length: 0\r\n\r\n'.encode('ascii')) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_context_manager(self): with xmlrpclib.ServerProxy(URL) as server: server.add(2, 3) @@ -1022,7 +998,6 @@ def test_context_manager(self): self.assertEqual(server('transport')._connection, (None, None)) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_context_manager_method_error(self): try: with xmlrpclib.ServerProxy(URL) as server: @@ -1057,13 +1032,11 @@ def test_server_encoding(self): class MultiPathServerTestCase(BaseServerTestCase): threadFunc = staticmethod(http_multi_server) request_count = 2 - @unittest.expectedFailure # TODO: RUSTPYTHON def test_path1(self): p = xmlrpclib.ServerProxy(URL+"/foo") self.assertEqual(p.pow(6,8), 6**8) self.assertRaises(xmlrpclib.Fault, p.add, 6, 8) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_path2(self): p = xmlrpclib.ServerProxy(URL+"/foo/bar") self.assertEqual(p.add(6,8), 6+8) @@ -1151,7 +1124,6 @@ def setUp(self): #A test case that verifies that a server using the HTTP/1.1 keep-alive mechanism #does indeed serve subsequent requests on the same connection class KeepaliveServerTestCase1(BaseKeepaliveServerTestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_two(self): p = xmlrpclib.ServerProxy(URL) #do three requests. @@ -1235,7 +1207,6 @@ def send_content(self, connection, body): def setUp(self): BaseServerTestCase.setUp(self) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_gzip_request(self): t = self.Transport() t.encode_threshold = None @@ -1259,7 +1230,6 @@ def test_bad_gzip_request(self): p.pow(6, 8) p("close")() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_gzip_response(self): t = self.Transport() p = xmlrpclib.ServerProxy(URL, transport=t) @@ -1318,7 +1288,6 @@ def assertContainsAdditionalHeaders(self, headers, additional): for key, value in additional.items(): self.assertEqual(headers.get(key), value) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_header(self): p = xmlrpclib.ServerProxy(URL, headers=[('X-Test', 'foo')]) self.assertEqual(p.pow(6, 8), 6**8) @@ -1326,7 +1295,6 @@ def test_header(self): headers = self.RequestHandler.test_headers self.assertContainsAdditionalHeaders(headers, {'X-Test': 'foo'}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_header_many(self): p = xmlrpclib.ServerProxy( URL, headers=[('X-Test', 'foo'), ('X-Test-Second', 'bar')]) @@ -1336,7 +1304,6 @@ def test_header_many(self): self.assertContainsAdditionalHeaders( headers, {'X-Test': 'foo', 'X-Test-Second': 'bar'}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_header_empty(self): p = xmlrpclib.ServerProxy(URL, headers=[]) self.assertEqual(p.pow(6, 8), 6**8) @@ -1344,7 +1311,6 @@ def test_header_empty(self): headers = self.RequestHandler.test_headers self.assertContainsAdditionalHeaders(headers, {}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_header_tuple(self): p = xmlrpclib.ServerProxy(URL, headers=(('X-Test', 'foo'),)) self.assertEqual(p.pow(6, 8), 6**8) @@ -1352,7 +1318,6 @@ def test_header_tuple(self): headers = self.RequestHandler.test_headers self.assertContainsAdditionalHeaders(headers, {'X-Test': 'foo'}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_header_items(self): p = xmlrpclib.ServerProxy(URL, headers={'X-Test': 'foo'}.items()) self.assertEqual(p.pow(6, 8), 6**8) @@ -1411,7 +1376,6 @@ def tearDown(self): default_class = http.client.HTTPMessage xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = default_class - @unittest.expectedFailure # TODO: RUSTPYTHON def test_basic(self): # check that flag is false by default flagval = xmlrpc.server.SimpleXMLRPCServer._send_traceback_header @@ -1506,7 +1470,6 @@ def test_cgi_get(self): self.assertEqual(message, 'Bad Request') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_cgi_xmlrpc_response(self): data = """<?xml version='1.0'?> <methodCall> @@ -1552,7 +1515,6 @@ def test_cgi_xmlrpc_response(self): class UseBuiltinTypesTestCase(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_use_builtin_types(self): # SimpleXMLRPCDispatcher.__init__ accepts use_builtin_types, which # makes all dispatch of binary data as bytes instances, and all diff --git a/Lib/xmlrpc/client.py b/Lib/xmlrpc/client.py index 121e44023c1..a614cef6ab2 100644 --- a/Lib/xmlrpc/client.py +++ b/Lib/xmlrpc/client.py @@ -135,8 +135,7 @@ from decimal import Decimal import http.client import urllib.parse -# XXX RUSTPYTHON TODO: pyexpat -# from xml.parsers import expat +from xml.parsers import expat import errno from io import BytesIO try: diff --git a/crates/stdlib/src/pyexpat.rs b/crates/stdlib/src/pyexpat.rs index e96d6287489..89cf690770d 100644 --- a/crates/stdlib/src/pyexpat.rs +++ b/crates/stdlib/src/pyexpat.rs @@ -1,4 +1,7 @@ -/// Pyexpat builtin module +//! Pyexpat builtin module + +// spell-checker: ignore libexpat + use crate::vm::{PyRef, VirtualMachine, builtins::PyModule, extend_module}; pub fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { @@ -49,9 +52,9 @@ macro_rules! create_bool_property { mod _pyexpat { use crate::vm::{ Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyStr, PyStrRef, PyType}, + builtins::{PyBytesRef, PyStr, PyStrRef, PyType}, function::ArgBytesLike, - function::{IntoFuncArgs, OptionalArg}, + function::{Either, IntoFuncArgs, OptionalArg}, }; use rustpython_common::lock::PyRwLock; use std::io::Cursor; @@ -66,6 +69,8 @@ mod _pyexpat { #[pyclass(name = "xmlparser", module = false, traverse)] #[derive(Debug, PyPayload)] pub struct PyExpatLikeXmlParser { + #[pytraverse(skip)] + namespace_separator: Option<String>, start_element: MutableObject, end_element: MutableObject, character_data: MutableObject, @@ -109,8 +114,14 @@ mod _pyexpat { #[pyclass] impl PyExpatLikeXmlParser { - fn new(vm: &VirtualMachine) -> PyResult<PyExpatLikeXmlParserRef> { + fn new( + namespace_separator: Option<String>, + intern: Option<PyObjectRef>, + vm: &VirtualMachine, + ) -> PyResult<PyExpatLikeXmlParserRef> { + let intern_dict = intern.unwrap_or_else(|| vm.ctx.new_dict().into()); Ok(Self { + namespace_separator, start_element: MutableObject::new(vm.ctx.none()), end_element: MutableObject::new(vm.ctx.none()), character_data: MutableObject::new(vm.ctx.none()), @@ -119,9 +130,7 @@ mod _pyexpat { namespace_prefixes: MutableObject::new(vm.ctx.new_bool(false).into()), ordered_attributes: MutableObject::new(vm.ctx.new_bool(false).into()), specified_attributes: MutableObject::new(vm.ctx.new_bool(false).into()), - // String interning dictionary - used by the parser to intern element/attribute names - // for memory efficiency and faster comparisons. See CPython's pyexpat documentation. - intern: MutableObject::new(vm.ctx.new_dict().into()), + intern: MutableObject::new(intern_dict), // Additional handlers (stubs for compatibility) processing_instruction: MutableObject::new(vm.ctx.none()), unparsed_entity_decl: MutableObject::new(vm.ctx.none()), @@ -282,7 +291,19 @@ mod _pyexpat { .whitespace_to_characters(true) } - fn do_parse<T>(&self, vm: &VirtualMachine, parser: xml::EventReader<T>) + /// Construct element name with namespace if separator is set + fn make_name(&self, name: &xml::name::OwnedName) -> String { + match (&self.namespace_separator, &name.namespace) { + (Some(sep), Some(ns)) => format!("{}{}{}", ns, sep, name.local_name), + _ => name.local_name.clone(), + } + } + + fn do_parse<T>( + &self, + vm: &VirtualMachine, + parser: xml::EventReader<T>, + ) -> Result<(), xml::reader::Error> where T: std::io::Read, { @@ -293,69 +314,106 @@ mod _pyexpat { }) => { let dict = vm.ctx.new_dict(); for attribute in attributes { + let attr_name = self.make_name(&attribute.name); dict.set_item( - attribute.name.local_name.as_str(), + attr_name.as_str(), vm.ctx.new_str(attribute.value).into(), vm, ) .unwrap(); } - let name_str = PyStr::from(name.local_name).into_ref(&vm.ctx); + let name_str = PyStr::from(self.make_name(&name)).into_ref(&vm.ctx); invoke_handler(vm, &self.start_element, (name_str, dict)); } Ok(XmlEvent::EndElement { name, .. }) => { - let name_str = PyStr::from(name.local_name).into_ref(&vm.ctx); + let name_str = PyStr::from(self.make_name(&name)).into_ref(&vm.ctx); invoke_handler(vm, &self.end_element, (name_str,)); } Ok(XmlEvent::Characters(chars)) => { let str = PyStr::from(chars).into_ref(&vm.ctx); invoke_handler(vm, &self.character_data, (str,)); } + Err(e) => return Err(e), _ => {} } } + Ok(()) } #[pymethod(name = "Parse")] - fn parse(&self, data: PyStrRef, _isfinal: OptionalArg<bool>, vm: &VirtualMachine) { - let reader = Cursor::<Vec<u8>>::new(data.as_bytes().to_vec()); + fn parse( + &self, + data: Either<PyStrRef, PyBytesRef>, + _isfinal: OptionalArg<bool>, + vm: &VirtualMachine, + ) -> PyResult<i32> { + let bytes = match data { + Either::A(s) => s.as_bytes().to_vec(), + Either::B(b) => b.as_bytes().to_vec(), + }; + // Empty data is valid - used to finalize parsing + if bytes.is_empty() { + return Ok(1); + } + let reader = Cursor::<Vec<u8>>::new(bytes); let parser = self.create_config().create_reader(reader); - self.do_parse(vm, parser); + // Note: xml-rs is stricter than libexpat; some errors are silently ignored + // to maintain compatibility with existing Python code + let _ = self.do_parse(vm, parser); + Ok(1) } #[pymethod(name = "ParseFile")] - fn parse_file(&self, file: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - // todo: read chunks at a time + fn parse_file(&self, file: PyObjectRef, vm: &VirtualMachine) -> PyResult<i32> { let read_res = vm.call_method(&file, "read", ())?; let bytes_like = ArgBytesLike::try_from_object(vm, read_res)?; let buf = bytes_like.borrow_buf().to_vec(); + if buf.is_empty() { + return Ok(1); + } let reader = Cursor::new(buf); let parser = self.create_config().create_reader(reader); - self.do_parse(vm, parser); - - // todo: return value - Ok(()) + // Note: xml-rs is stricter than libexpat; some errors are silently ignored + let _ = self.do_parse(vm, parser); + Ok(1) } } #[derive(FromArgs)] - #[allow(dead_code)] struct ParserCreateArgs { #[pyarg(any, optional)] - encoding: OptionalArg<PyStrRef>, + encoding: Option<PyStrRef>, #[pyarg(any, optional)] - namespace_separator: OptionalArg<PyStrRef>, + namespace_separator: Option<PyStrRef>, #[pyarg(any, optional)] - intern: OptionalArg<PyStrRef>, + intern: Option<PyObjectRef>, } #[pyfunction(name = "ParserCreate")] fn parser_create( - _args: ParserCreateArgs, + args: ParserCreateArgs, vm: &VirtualMachine, ) -> PyResult<PyExpatLikeXmlParserRef> { - PyExpatLikeXmlParser::new(vm) + // Validate namespace_separator: must be at most one character + let ns_sep = match args.namespace_separator { + Some(ref s) => { + let chars: Vec<char> = s.as_str().chars().collect(); + if chars.len() > 1 { + return Err(vm.new_value_error( + "namespace_separator must be at most one character, omitted, or None" + .to_owned(), + )); + } + Some(s.as_str().to_owned()) + } + None => None, + }; + + // encoding parameter is currently not used (xml-rs handles encoding from XML declaration) + let _ = args.encoding; + + PyExpatLikeXmlParser::new(ns_sep, args.intern, vm) } } From 2f4ca509c80191222766d0b5443390802668395d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 30 Dec 2025 00:58:26 +0900 Subject: [PATCH 628/819] Initializer for PyCSimple, PyCArray (#6581) --- crates/vm/src/stdlib/ctypes/array.rs | 57 +++++++++++++++++++++------ crates/vm/src/stdlib/ctypes/simple.rs | 36 ++++++++++++++++- 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index f31c8284d8b..25708b57f8e 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -13,6 +13,7 @@ use crate::{ types::{AsBuffer, AsNumber, AsSequence, Constructor, Initializer}, }; use num_traits::{Signed, ToPrimitive}; +use std::borrow::Cow; /// Get itemsize from a PEP 3118 format string /// Extracts the type code (last char after endianness prefix) and returns its size @@ -430,6 +431,18 @@ impl Constructor for PyCArray { } } +impl Initializer for PyCArray { + type Args = FuncArgs; + + fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + // Re-initialize array elements when __init__ is called + for (i, value) in args.args.iter().enumerate() { + PyCArray::setitem_by_index(&zelf, i as isize, value.clone(), vm)?; + } + Ok(()) + } +} + impl AsSequence for PyCArray { fn as_sequence() -> &'static PySequenceMethods { use std::sync::LazyLock; @@ -457,7 +470,7 @@ impl AsSequence for PyCArray { #[pyclass( flags(BASETYPE, IMMUTABLETYPE), - with(Constructor, AsSequence, AsBuffer) + with(Constructor, Initializer, AsSequence, AsBuffer) )] impl PyCArray { #[pyclassmethod] @@ -868,16 +881,38 @@ impl PyCArray { }; let mut buffer = buffer_lock.write(); - Self::write_element_to_buffer( - buffer.to_mut(), - final_offset, - element_size, - type_code.as_deref(), - &value, - zelf, - index, - vm, - ) + + // For shared memory (Cow::Borrowed), we need to write directly to the memory + // For owned memory (Cow::Owned), we can write to the owned buffer + match &mut *buffer { + Cow::Borrowed(slice) => { + // SAFETY: For from_buffer, the slice points to writable shared memory. + // Python's from_buffer requires writable buffer, so this is safe. + let ptr = slice.as_ptr() as *mut u8; + let len = slice.len(); + let owned_slice = unsafe { std::slice::from_raw_parts_mut(ptr, len) }; + Self::write_element_to_buffer( + owned_slice, + final_offset, + element_size, + type_code.as_deref(), + &value, + zelf, + index, + vm, + ) + } + Cow::Owned(vec) => Self::write_element_to_buffer( + vec, + final_offset, + element_size, + type_code.as_deref(), + &value, + zelf, + index, + vm, + ), + } } // Array_subscript diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs index 803b38d6e05..fbb17620fe4 100644 --- a/crates/vm/src/stdlib/ctypes/simple.rs +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -1085,6 +1085,18 @@ impl Constructor for PyCSimple { } } +impl Initializer for PyCSimple { + type Args = (OptionalArg,); + + fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + // If an argument is provided, update the value + if let Some(value) = args.0.into_option() { + PyCSimple::set_value(zelf.into(), value, vm)?; + } + Ok(()) + } +} + // Simple_repr impl Representable for PyCSimple { fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> { @@ -1111,7 +1123,10 @@ impl Representable for PyCSimple { } } -#[pyclass(flags(BASETYPE), with(Constructor, AsBuffer, AsNumber, Representable))] +#[pyclass( + flags(BASETYPE), + with(Constructor, Initializer, AsBuffer, AsNumber, Representable) +)] impl PyCSimple { #[pygetset] fn _b0_(&self) -> Option<PyObjectRef> { @@ -1278,7 +1293,24 @@ impl PyCSimple { // Update buffer when value changes let buffer_bytes = value_to_bytes_endian(&type_code, &content, swapped, vm); - *zelf.0.buffer.write() = std::borrow::Cow::Owned(buffer_bytes); + + // If the buffer is borrowed (from shared memory), write in-place + // Otherwise replace with new owned buffer + let mut buffer = zelf.0.buffer.write(); + match &mut *buffer { + Cow::Borrowed(slice) => { + // SAFETY: For from_buffer, the slice points to writable shared memory. + // Python's from_buffer requires writable buffer, so this is safe. + let ptr = slice.as_ptr() as *mut u8; + let len = slice.len().min(buffer_bytes.len()); + unsafe { + std::ptr::copy_nonoverlapping(buffer_bytes.as_ptr(), ptr, len); + } + } + Cow::Owned(vec) => { + vec.copy_from_slice(&buffer_bytes); + } + } // For c_char_p (type "z"), c_wchar_p (type "Z"), and py_object (type "O"), // keep the reference in _objects From f274164dcd4e53f1303df26f6f89dcd8987cd5d9 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:28:03 -0500 Subject: [PATCH 629/819] Add __future__.py tests (#6585) * Added tests for __future__.py from v3.13.10 * * Deleted futures not in v3.13.10 * "Fixed" issues in test_future.py --- ...syntax_future10.py => badsyntax_future.py} | 0 .../test_future_stmt/badsyntax_future3.py | 10 - .../test_future_stmt/badsyntax_future4.py | 10 - .../test_future_stmt/badsyntax_future5.py | 12 -- .../test_future_stmt/badsyntax_future6.py | 10 - .../test_future_stmt/badsyntax_future7.py | 11 - .../test_future_stmt/badsyntax_future8.py | 10 - .../test_future_stmt/badsyntax_future9.py | 10 - ..._test1.py => import_nested_scope_twice.py} | 0 .../{future_test2.py => nested_scope.py} | 0 Lib/test/test_future_stmt/test_future.py | 201 ++++++++++++------ 11 files changed, 139 insertions(+), 135 deletions(-) rename Lib/test/test_future_stmt/{badsyntax_future10.py => badsyntax_future.py} (100%) delete mode 100644 Lib/test/test_future_stmt/badsyntax_future3.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future4.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future5.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future6.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future7.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future8.py delete mode 100644 Lib/test/test_future_stmt/badsyntax_future9.py rename Lib/test/test_future_stmt/{future_test1.py => import_nested_scope_twice.py} (100%) rename Lib/test/test_future_stmt/{future_test2.py => nested_scope.py} (100%) diff --git a/Lib/test/test_future_stmt/badsyntax_future10.py b/Lib/test/test_future_stmt/badsyntax_future.py similarity index 100% rename from Lib/test/test_future_stmt/badsyntax_future10.py rename to Lib/test/test_future_stmt/badsyntax_future.py diff --git a/Lib/test/test_future_stmt/badsyntax_future3.py b/Lib/test/test_future_stmt/badsyntax_future3.py deleted file mode 100644 index f1c8417edaa..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future3.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This is a test""" -from __future__ import nested_scopes -from __future__ import rested_snopes - -def f(x): - def g(y): - return x + y - return g - -result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future4.py b/Lib/test/test_future_stmt/badsyntax_future4.py deleted file mode 100644 index b5f4c98e922..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future4.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This is a test""" -import __future__ -from __future__ import nested_scopes - -def f(x): - def g(y): - return x + y - return g - -result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future5.py b/Lib/test/test_future_stmt/badsyntax_future5.py deleted file mode 100644 index 8a7e5fcb70f..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future5.py +++ /dev/null @@ -1,12 +0,0 @@ -"""This is a test""" -from __future__ import nested_scopes -import foo -from __future__ import nested_scopes - - -def f(x): - def g(y): - return x + y - return g - -result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future6.py b/Lib/test/test_future_stmt/badsyntax_future6.py deleted file mode 100644 index 5a8b55a02c4..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future6.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This is a test""" -"this isn't a doc string" -from __future__ import nested_scopes - -def f(x): - def g(y): - return x + y - return g - -result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future7.py b/Lib/test/test_future_stmt/badsyntax_future7.py deleted file mode 100644 index 131db2c2164..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future7.py +++ /dev/null @@ -1,11 +0,0 @@ -"""This is a test""" - -from __future__ import nested_scopes; import string; from __future__ import \ - nested_scopes - -def f(x): - def g(y): - return x + y - return g - -result = f(2)(4) diff --git a/Lib/test/test_future_stmt/badsyntax_future8.py b/Lib/test/test_future_stmt/badsyntax_future8.py deleted file mode 100644 index ca45289e2e5..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future8.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This is a test""" - -from __future__ import * - -def f(x): - def g(y): - return x + y - return g - -print(f(2)(4)) diff --git a/Lib/test/test_future_stmt/badsyntax_future9.py b/Lib/test/test_future_stmt/badsyntax_future9.py deleted file mode 100644 index 916de06ab71..00000000000 --- a/Lib/test/test_future_stmt/badsyntax_future9.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This is a test""" - -from __future__ import nested_scopes, braces - -def f(x): - def g(y): - return x + y - return g - -print(f(2)(4)) diff --git a/Lib/test/test_future_stmt/future_test1.py b/Lib/test/test_future_stmt/import_nested_scope_twice.py similarity index 100% rename from Lib/test/test_future_stmt/future_test1.py rename to Lib/test/test_future_stmt/import_nested_scope_twice.py diff --git a/Lib/test/test_future_stmt/future_test2.py b/Lib/test/test_future_stmt/nested_scope.py similarity index 100% rename from Lib/test/test_future_stmt/future_test2.py rename to Lib/test/test_future_stmt/nested_scope.py diff --git a/Lib/test/test_future_stmt/test_future.py b/Lib/test/test_future_stmt/test_future.py index 9c30054963b..a7d649b32a6 100644 --- a/Lib/test/test_future_stmt/test_future.py +++ b/Lib/test/test_future_stmt/test_future.py @@ -10,6 +10,8 @@ import re import sys +TOP_LEVEL_MSG = 'from __future__ imports must occur at the beginning of the file' + rx = re.compile(r'\((\S+).py, line (\d+)') def get_error_location(msg): @@ -18,21 +20,48 @@ def get_error_location(msg): class FutureTest(unittest.TestCase): - def check_syntax_error(self, err, basename, lineno, offset=1): - self.assertIn('%s.py, line %d' % (basename, lineno), str(err)) - self.assertEqual(os.path.basename(err.filename), basename + '.py') + def check_syntax_error(self, err, basename, + *, + lineno, + message=TOP_LEVEL_MSG, offset=1): + if basename != '<string>': + basename += '.py' + + self.assertEqual(f'{message} ({basename}, line {lineno})', str(err)) + self.assertEqual(os.path.basename(err.filename), basename) self.assertEqual(err.lineno, lineno) self.assertEqual(err.offset, offset) - def test_future1(self): - with import_helper.CleanImport('test.test_future_stmt.future_test1'): - from test.test_future_stmt import future_test1 - self.assertEqual(future_test1.result, 6) + def assertSyntaxError(self, code, + *, + lineno=1, + message=TOP_LEVEL_MSG, offset=1, + parametrize_docstring=True): + code = dedent(code.lstrip('\n')) + for add_docstring in ([False, True] if parametrize_docstring else [False]): + with self.subTest(code=code, add_docstring=add_docstring): + if add_docstring: + code = '"""Docstring"""\n' + code + lineno += 1 + with self.assertRaises(SyntaxError) as cm: + exec(code) + self.check_syntax_error(cm.exception, "<string>", + lineno=lineno, + message=message, + offset=offset) + + def test_import_nested_scope_twice(self): + # Import the name nested_scopes twice to trigger SF bug #407394 + with import_helper.CleanImport( + 'test.test_future_stmt.import_nested_scope_twice', + ): + from test.test_future_stmt import import_nested_scope_twice + self.assertEqual(import_nested_scope_twice.result, 6) - def test_future2(self): - with import_helper.CleanImport('test.test_future_stmt.future_test2'): - from test.test_future_stmt import future_test2 - self.assertEqual(future_test2.result, 6) + def test_nested_scope(self): + with import_helper.CleanImport('test.test_future_stmt.nested_scope'): + from test.test_future_stmt import nested_scope + self.assertEqual(nested_scope.result, 6) def test_future_single_import(self): with import_helper.CleanImport( @@ -52,47 +81,92 @@ def test_future_multiple_features(self): ): from test.test_future_stmt import test_future_multiple_features - def test_badfuture3(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future3 - self.check_syntax_error(cm.exception, "badsyntax_future3", 3) + # TODO: RUSTPYTHON + # AssertionError: 1 != 24 + @unittest.expectedFailure + def test_unknown_future_flag(self): + code = """ + from __future__ import nested_scopes + from __future__ import rested_snopes # typo error here: nested => rested + """ + self.assertSyntaxError( + code, lineno=2, + message='future feature rested_snopes is not defined', offset=24, + ) - def test_badfuture4(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future4 - self.check_syntax_error(cm.exception, "badsyntax_future4", 3) + def test_future_import_not_on_top(self): + code = """ + import some_module + from __future__ import annotations + """ + self.assertSyntaxError(code, lineno=2) - def test_badfuture5(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future5 - self.check_syntax_error(cm.exception, "badsyntax_future5", 4) + code = """ + import __future__ + from __future__ import annotations + """ + self.assertSyntaxError(code, lineno=2) + + code = """ + from __future__ import absolute_import + "spam, bar, blah" + from __future__ import print_function + """ + self.assertSyntaxError(code, lineno=3) # TODO: RUSTPYTHON + # AssertionError: SyntaxError not raised @unittest.expectedFailure - def test_badfuture6(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future6 - self.check_syntax_error(cm.exception, "badsyntax_future6", 3) + def test_future_import_with_extra_string(self): + code = """ + '''Docstring''' + "this isn't a doc string" + from __future__ import nested_scopes + """ + self.assertSyntaxError(code, lineno=3, parametrize_docstring=False) - def test_badfuture7(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future7 - self.check_syntax_error(cm.exception, "badsyntax_future7", 3, 54) + def test_multiple_import_statements_on_same_line(self): + # With `\`: + code = """ + from __future__ import nested_scopes; import string; from __future__ import \ + nested_scopes + """ + self.assertSyntaxError(code, offset=54) - def test_badfuture8(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future8 - self.check_syntax_error(cm.exception, "badsyntax_future8", 3) + # Without `\`: + code = """ + from __future__ import nested_scopes; import string; from __future__ import nested_scopes + """ + self.assertSyntaxError(code, offset=54) - def test_badfuture9(self): - with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future9 - self.check_syntax_error(cm.exception, "badsyntax_future9", 3) + # TODO: RUSTPYTHON + # AssertionError: 1 != 24 + @unittest.expectedFailure + def test_future_import_star(self): + code = """ + from __future__ import * + """ + self.assertSyntaxError(code, message='future feature * is not defined', offset=24) + + # TODO: RUSTPYTHON + # AssertionError: 'not a chance (<string>, line 2)' != 'future feature braces is not defined (<string>, line 2)' + @unittest.expectedFailure + def test_future_import_braces(self): + code = """ + from __future__ import braces + """ + # Congrats, you found an easter egg! + self.assertSyntaxError(code, message='not a chance', offset=24) - def test_badfuture10(self): + code = """ + from __future__ import nested_scopes, braces + """ + self.assertSyntaxError(code, message='not a chance', offset=39) + + def test_module_with_future_import_not_on_top(self): with self.assertRaises(SyntaxError) as cm: - from test.test_future_stmt import badsyntax_future10 - self.check_syntax_error(cm.exception, "badsyntax_future10", 3) + from test.test_future_stmt import badsyntax_future + self.check_syntax_error(cm.exception, "badsyntax_future", lineno=3) def test_ensure_flags_dont_clash(self): # bpo-39562: test that future flags and compiler flags doesn't clash @@ -109,26 +183,6 @@ def test_ensure_flags_dont_clash(self): } self.assertCountEqual(set(flags.values()), flags.values()) - def test_parserhack(self): - # test that the parser.c::future_hack function works as expected - # Note: although this test must pass, it's not testing the original - # bug as of 2.6 since the with statement is not optional and - # the parser hack disabled. If a new keyword is introduced in - # 2.6, change this to refer to the new future import. - try: - exec("from __future__ import print_function; print 0") - except SyntaxError: - pass - else: - self.fail("syntax error didn't occur") - - try: - exec("from __future__ import (print_function); print 0") - except SyntaxError: - pass - else: - self.fail("syntax error didn't occur") - def test_unicode_literals_exec(self): scope = {} exec("from __future__ import unicode_literals; x = ''", {}, scope) @@ -141,6 +195,27 @@ def test_syntactical_future_repl(self): out = kill_python(p) self.assertNotIn(b'SyntaxError: invalid syntax', out) + @unittest.skip('TODO: RUSTPYTHON') + # SyntaxError: future feature spam is not defined + def test_future_dotted_import(self): + with self.assertRaises(ImportError): + exec("from .__future__ import spam") + + code = dedent( + """ + from __future__ import print_function + from ...__future__ import ham + """ + ) + with self.assertRaises(ImportError): + exec(code) + + code = """ + from .__future__ import nested_scopes + from __future__ import barry_as_FLUFL + """ + self.assertSyntaxError(code, lineno=2) + class AnnotationsFutureTestCase(unittest.TestCase): template = dedent( """ @@ -385,6 +460,7 @@ def test_infinity_numbers(self): self.assertAnnotationEqual("(1e1000, (1e1000j,))", expected=f"({inf}, ({infj},))") # TODO: RUSTPYTHON + # AssertionError: SyntaxError not raised @unittest.expectedFailure def test_annotation_with_complex_target(self): with self.assertRaises(SyntaxError): @@ -410,6 +486,7 @@ def bar(): self.assertEqual(foo().__code__.co_freevars, ()) # TODO: RUSTPYTHON + # AssertionError: SyntaxError not raised @unittest.expectedFailure def test_annotations_forbidden(self): with self.assertRaises(SyntaxError): From 489289f54aaaf5084e8d3360c237c1de1d166521 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 30 Dec 2025 12:28:47 +0900 Subject: [PATCH 630/819] Fix subprocess and Update subprocess from Python 3.13.11 (#6583) * fix test_subprocess * fix posixsubprocess * fix io warning * Update subprocess from Python 3.13.11 * fix * Auto-format: cargo fmt --all * fix macos --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- Lib/subprocess.py | 128 +++++++---- Lib/test/test_file.py | 3 - Lib/test/test_subprocess.py | 329 +++++++++++++++++++++++---- crates/stdlib/src/posixsubprocess.rs | 7 +- crates/vm/src/stdlib/io.rs | 10 + crates/vm/src/stdlib/posix.rs | 19 +- crates/vm/src/stdlib/winapi.rs | 3 +- 7 files changed, 398 insertions(+), 101 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 1d17ae3608a..885f0092b53 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -74,8 +74,8 @@ else: _mswindows = True -# wasm32-emscripten and wasm32-wasi do not support processes -_can_fork_exec = sys.platform not in {"emscripten", "wasi"} +# some platforms do not support subprocesses +_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"} if _mswindows: import _winapi @@ -83,6 +83,7 @@ STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE, SW_HIDE, STARTF_USESTDHANDLES, STARTF_USESHOWWINDOW, + STARTF_FORCEONFEEDBACK, STARTF_FORCEOFFFEEDBACK, ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, REALTIME_PRIORITY_CLASS, @@ -93,6 +94,7 @@ "STD_INPUT_HANDLE", "STD_OUTPUT_HANDLE", "STD_ERROR_HANDLE", "SW_HIDE", "STARTF_USESTDHANDLES", "STARTF_USESHOWWINDOW", + "STARTF_FORCEONFEEDBACK", "STARTF_FORCEOFFFEEDBACK", "STARTUPINFO", "ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS", "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", @@ -103,18 +105,22 @@ if _can_fork_exec: from _posixsubprocess import fork_exec as _fork_exec # used in methods that are called by __del__ - _waitpid = os.waitpid - _waitstatus_to_exitcode = os.waitstatus_to_exitcode - _WIFSTOPPED = os.WIFSTOPPED - _WSTOPSIG = os.WSTOPSIG - _WNOHANG = os.WNOHANG + class _del_safe: + waitpid = os.waitpid + waitstatus_to_exitcode = os.waitstatus_to_exitcode + WIFSTOPPED = os.WIFSTOPPED + WSTOPSIG = os.WSTOPSIG + WNOHANG = os.WNOHANG + ECHILD = errno.ECHILD else: - _fork_exec = None - _waitpid = None - _waitstatus_to_exitcode = None - _WIFSTOPPED = None - _WSTOPSIG = None - _WNOHANG = None + class _del_safe: + waitpid = None + waitstatus_to_exitcode = None + WIFSTOPPED = None + WSTOPSIG = None + WNOHANG = None + ECHILD = errno.ECHILD + import select import selectors @@ -346,7 +352,7 @@ def _args_from_interpreter_flags(): if dev_mode: args.extend(('-X', 'dev')) for opt in ('faulthandler', 'tracemalloc', 'importtime', - 'frozen_modules', 'showrefcount', 'utf8'): + 'frozen_modules', 'showrefcount', 'utf8', 'gil'): if opt in xoptions: value = xoptions[opt] if value is True: @@ -380,7 +386,7 @@ def _text_encoding(): def call(*popenargs, timeout=None, **kwargs): """Run command with arguments. Wait for command to complete or - timeout, then return the returncode attribute. + for timeout seconds, then return the returncode attribute. The arguments are the same as for the Popen constructor. Example: @@ -517,8 +523,8 @@ def run(*popenargs, in the returncode attribute, and output & stderr attributes if those streams were captured. - If timeout is given, and the process takes too long, a TimeoutExpired - exception will be raised. + If timeout (seconds) is given and the process takes too long, + a TimeoutExpired exception will be raised. There is an optional argument "input", allowing you to pass bytes or a string to the subprocess's stdin. If you use this argument @@ -709,6 +715,9 @@ def _use_posix_spawn(): # os.posix_spawn() is not available return False + if ((_env := os.environ.get('_PYTHON_SUBPROCESS_USE_POSIX_SPAWN')) in ('0', '1')): + return bool(int(_env)) + if sys.platform in ('darwin', 'sunos5'): # posix_spawn() is a syscall on both macOS and Solaris, # and properly reports errors @@ -744,6 +753,7 @@ def _use_posix_spawn(): # guarantee the given libc/syscall API will be used. _USE_POSIX_SPAWN = _use_posix_spawn() _USE_VFORK = True +_HAVE_POSIX_SPAWN_CLOSEFROM = hasattr(os, 'POSIX_SPAWN_CLOSEFROM') class Popen: @@ -834,6 +844,9 @@ def __init__(self, args, bufsize=-1, executable=None, if not isinstance(bufsize, int): raise TypeError("bufsize must be an integer") + if stdout is STDOUT: + raise ValueError("STDOUT can only be used for stderr") + if pipesize is None: pipesize = -1 # Restore default if not isinstance(pipesize, int): @@ -1224,8 +1237,11 @@ def communicate(self, input=None, timeout=None): finally: self._communication_started = True - - sts = self.wait(timeout=self._remaining_time(endtime)) + try: + sts = self.wait(timeout=self._remaining_time(endtime)) + except TimeoutExpired as exc: + exc.timeout = timeout + raise return (stdout, stderr) @@ -1600,6 +1616,10 @@ def _readerthread(self, fh, buffer): fh.close() + def _writerthread(self, input): + self._stdin_write(input) + + def _communicate(self, input, endtime, orig_timeout): # Start reader threads feeding into a list hanging off of this # object, unless they've already been started. @@ -1618,8 +1638,23 @@ def _communicate(self, input, endtime, orig_timeout): self.stderr_thread.daemon = True self.stderr_thread.start() - if self.stdin: - self._stdin_write(input) + # Start writer thread to send input to stdin, unless already + # started. The thread writes input and closes stdin when done, + # or continues in the background on timeout. + if self.stdin and not hasattr(self, "_stdin_thread"): + self._stdin_thread = \ + threading.Thread(target=self._writerthread, + args=(input,)) + self._stdin_thread.daemon = True + self._stdin_thread.start() + + # Wait for the writer thread, or time out. If we time out, the + # thread remains writing and the fd left open in case the user + # calls communicate again. + if hasattr(self, "_stdin_thread"): + self._stdin_thread.join(self._remaining_time(endtime)) + if self._stdin_thread.is_alive(): + raise TimeoutExpired(self.args, orig_timeout) # Wait for the reader threads, or time out. If we time out, the # threads remain reading and the fds left open in case the user @@ -1749,14 +1784,11 @@ def _get_handles(self, stdin, stdout, stderr): errread, errwrite) - def _posix_spawn(self, args, executable, env, restore_signals, + def _posix_spawn(self, args, executable, env, restore_signals, close_fds, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite): """Execute program using os.posix_spawn().""" - if env is None: - env = os.environ - kwargs = {} if restore_signals: # See _Py_RestoreSignals() in Python/pylifecycle.c @@ -1778,6 +1810,10 @@ def _posix_spawn(self, args, executable, env, restore_signals, ): if fd != -1: file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2)) + + if close_fds: + file_actions.append((os.POSIX_SPAWN_CLOSEFROM, 3)) + if file_actions: kwargs['file_actions'] = file_actions @@ -1825,7 +1861,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, if (_USE_POSIX_SPAWN and os.path.dirname(executable) and preexec_fn is None - and not close_fds + and (not close_fds or _HAVE_POSIX_SPAWN_CLOSEFROM) and not pass_fds and cwd is None and (p2cread == -1 or p2cread > 2) @@ -1837,7 +1873,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, and gids is None and uid is None and umask < 0): - self._posix_spawn(args, executable, env, restore_signals, + self._posix_spawn(args, executable, env, restore_signals, close_fds, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) @@ -1958,20 +1994,16 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, raise child_exception_type(err_msg) - def _handle_exitstatus(self, sts, - _waitstatus_to_exitcode=_waitstatus_to_exitcode, - _WIFSTOPPED=_WIFSTOPPED, - _WSTOPSIG=_WSTOPSIG): + def _handle_exitstatus(self, sts, _del_safe=_del_safe): """All callers to this function MUST hold self._waitpid_lock.""" # This method is called (indirectly) by __del__, so it cannot # refer to anything outside of its local scope. - if _WIFSTOPPED(sts): - self.returncode = -_WSTOPSIG(sts) + if _del_safe.WIFSTOPPED(sts): + self.returncode = -_del_safe.WSTOPSIG(sts) else: - self.returncode = _waitstatus_to_exitcode(sts) + self.returncode = _del_safe.waitstatus_to_exitcode(sts) - def _internal_poll(self, _deadstate=None, _waitpid=_waitpid, - _WNOHANG=_WNOHANG, _ECHILD=errno.ECHILD): + def _internal_poll(self, _deadstate=None, _del_safe=_del_safe): """Check if child process has terminated. Returns returncode attribute. @@ -1987,13 +2019,13 @@ def _internal_poll(self, _deadstate=None, _waitpid=_waitpid, try: if self.returncode is not None: return self.returncode # Another thread waited. - pid, sts = _waitpid(self.pid, _WNOHANG) + pid, sts = _del_safe.waitpid(self.pid, _del_safe.WNOHANG) if pid == self.pid: self._handle_exitstatus(sts) except OSError as e: if _deadstate is not None: self.returncode = _deadstate - elif e.errno == _ECHILD: + elif e.errno == _del_safe.ECHILD: # This happens if SIGCLD is set to be ignored or # waiting for child processes has otherwise been # disabled for our process. This child is dead, we @@ -2067,6 +2099,10 @@ def _communicate(self, input, endtime, orig_timeout): self.stdin.flush() except BrokenPipeError: pass # communicate() must ignore BrokenPipeError. + except ValueError: + # ignore ValueError: I/O operation on closed file. + if not self.stdin.closed: + raise if not input: try: self.stdin.close() @@ -2092,10 +2128,13 @@ def _communicate(self, input, endtime, orig_timeout): self._save_input(input) if self._input: - input_view = memoryview(self._input) + if not isinstance(self._input, memoryview): + input_view = memoryview(self._input) + else: + input_view = self._input.cast("b") # byte input required with _PopenSelector() as selector: - if self.stdin and input: + if self.stdin and not self.stdin.closed and self._input: selector.register(self.stdin, selectors.EVENT_WRITE) if self.stdout and not self.stdout.closed: selector.register(self.stdout, selectors.EVENT_READ) @@ -2128,7 +2167,7 @@ def _communicate(self, input, endtime, orig_timeout): selector.unregister(key.fileobj) key.fileobj.close() else: - if self._input_offset >= len(self._input): + if self._input_offset >= len(input_view): selector.unregister(key.fileobj) key.fileobj.close() elif key.fileobj in (self.stdout, self.stderr): @@ -2137,8 +2176,11 @@ def _communicate(self, input, endtime, orig_timeout): selector.unregister(key.fileobj) key.fileobj.close() self._fileobj2output[key.fileobj].append(data) - - self.wait(timeout=self._remaining_time(endtime)) + try: + self.wait(timeout=self._remaining_time(endtime)) + except TimeoutExpired as exc: + exc.timeout = orig_timeout + raise # All data exchanged. Translate lists into strings. if stdout is not None: diff --git a/Lib/test/test_file.py b/Lib/test/test_file.py index d64f3f797d8..36aea52a5a9 100644 --- a/Lib/test/test_file.py +++ b/Lib/test/test_file.py @@ -344,9 +344,6 @@ def testIteration(self): class COtherFileTests(OtherFileTests, unittest.TestCase): open = io.open - @unittest.expectedFailure # TODO: RUSTPYTHON - def testSetBufferSize(self): - return super().testSetBufferSize() class PyOtherFileTests(OtherFileTests, unittest.TestCase): open = staticmethod(pyio.open) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 5c5d2a9600f..997c98c2c62 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -25,7 +25,6 @@ import gc import textwrap import json -import pathlib from test.support.os_helper import FakePath try: @@ -41,6 +40,10 @@ import grp except ImportError: grp = None +try: + import resource +except ImportError: + resource = None try: import fcntl @@ -158,6 +161,20 @@ def test_call_timeout(self): [sys.executable, "-c", "while True: pass"], timeout=0.1) + def test_timeout_exception(self): + try: + subprocess.run([sys.executable, '-c', 'import time;time.sleep(9)'], timeout = -1) + except subprocess.TimeoutExpired as e: + self.assertIn("-1 seconds", str(e)) + else: + self.fail("Expected TimeoutExpired exception not raised") + try: + subprocess.run([sys.executable, '-c', 'import time;time.sleep(9)'], timeout = 0) + except subprocess.TimeoutExpired as e: + self.assertIn("0 seconds", str(e)) + else: + self.fail("Expected TimeoutExpired exception not raised") + def test_check_call_zero(self): # check_call() function with zero return code rc = subprocess.check_call(ZERO_RETURN_CMD) @@ -269,21 +286,13 @@ def test_check_output_stdin_with_input_arg(self): self.assertIn('stdin', c.exception.args[0]) self.assertIn('input', c.exception.args[0]) - @support.requires_resource('walltime') def test_check_output_timeout(self): # check_output() function with timeout arg with self.assertRaises(subprocess.TimeoutExpired) as c: output = subprocess.check_output( [sys.executable, "-c", - "import sys, time\n" - "sys.stdout.write('BDFL')\n" - "sys.stdout.flush()\n" - "time.sleep(3600)"], - # Some heavily loaded buildbots (sparc Debian 3.x) require - # this much time to start and print. - timeout=3) - self.fail("Expected TimeoutExpired.") - self.assertEqual(c.exception.output, b'BDFL') + "import time; time.sleep(3600)"], + timeout=0.1) def test_call_kwargs(self): # call() function with keyword args @@ -949,6 +958,48 @@ def test_communicate(self): self.assertEqual(stdout, b"banana") self.assertEqual(stderr, b"pineapple") + def test_communicate_memoryview_input(self): + # Test memoryview input with byte elements + test_data = b"Hello, memoryview!" + mv = memoryview(test_data) + p = subprocess.Popen([sys.executable, "-c", + 'import sys; sys.stdout.write(sys.stdin.read())'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + self.addCleanup(p.stdout.close) + self.addCleanup(p.stdin.close) + (stdout, stderr) = p.communicate(mv) + self.assertEqual(stdout, test_data) + self.assertIsNone(stderr) + + def test_communicate_memoryview_input_nonbyte(self): + # Test memoryview input with non-byte elements (e.g., int32) + # This tests the fix for gh-134453 where non-byte memoryviews + # had incorrect length tracking on POSIX + import array + # Create an array of 32-bit integers that's large enough to trigger + # the chunked writing behavior (> PIPE_BUF) + pipe_buf = getattr(select, 'PIPE_BUF', 512) + # Each 'i' element is 4 bytes, so we need more than pipe_buf/4 elements + # Add some extra to ensure we exceed the buffer size + num_elements = pipe_buf + 1 + test_array = array.array('i', [0x64306f66 for _ in range(num_elements)]) + expected_bytes = test_array.tobytes() + mv = memoryview(test_array) + + p = subprocess.Popen([sys.executable, "-c", + 'import sys; ' + 'data = sys.stdin.buffer.read(); ' + 'sys.stdout.buffer.write(data)'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + self.addCleanup(p.stdout.close) + self.addCleanup(p.stdin.close) + (stdout, stderr) = p.communicate(mv) + self.assertEqual(stdout, expected_bytes, + msg=f"{len(stdout)=} =? {len(expected_bytes)=}") + self.assertIsNone(stderr) + def test_communicate_timeout(self): p = subprocess.Popen([sys.executable, "-c", 'import sys,os,time;' @@ -984,6 +1035,62 @@ def test_communicate_timeout_large_output(self): (stdout, _) = p.communicate() self.assertEqual(len(stdout), 4 * 64 * 1024) + def test_communicate_timeout_large_input(self): + # Test that timeout is enforced when writing large input to a + # slow-to-read subprocess, and that partial input is preserved + # for continuation after timeout (gh-141473). + # + # This is a regression test for Windows matching POSIX behavior. + # On POSIX, select() is used to multiplex I/O with timeout checking. + # On Windows, stdin writing must also honor the timeout rather than + # blocking indefinitely when the pipe buffer fills. + + # Input larger than typical pipe buffer (4-64KB on Windows) + input_data = b"x" * (128 * 1024) + + p = subprocess.Popen( + [sys.executable, "-c", + "import sys, time; " + "time.sleep(30); " # Don't read stdin for a long time + "sys.stdout.buffer.write(sys.stdin.buffer.read())"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + try: + timeout = 0.2 + start = time.monotonic() + try: + p.communicate(input_data, timeout=timeout) + # If we get here without TimeoutExpired, the timeout was ignored + elapsed = time.monotonic() - start + self.fail( + f"TimeoutExpired not raised. communicate() completed in " + f"{elapsed:.2f}s, but subprocess sleeps for 30s. " + "Stdin writing blocked without enforcing timeout.") + except subprocess.TimeoutExpired: + elapsed = time.monotonic() - start + + # Timeout should occur close to the specified timeout value, + # not after waiting for the subprocess to finish sleeping. + # Allow generous margin for slow CI, but must be well under + # the subprocess sleep time. + self.assertLess(elapsed, 5.0, + f"TimeoutExpired raised after {elapsed:.2f}s; expected ~{timeout}s. " + "Stdin writing blocked without checking timeout.") + + # After timeout, continue communication. The remaining input + # should be sent and we should receive all data back. + stdout, stderr = p.communicate() + + # Verify all input was eventually received by the subprocess + self.assertEqual(len(stdout), len(input_data), + f"Expected {len(input_data)} bytes output but got {len(stdout)}") + self.assertEqual(stdout, input_data) + finally: + p.kill() + p.wait() + # Test for the fd leak reported in http://bugs.python.org/issue2791. def test_communicate_pipe_fd_leak(self): for stdin_pipe in (False, True): @@ -1054,6 +1161,19 @@ def test_writes_before_communicate(self): self.assertEqual(stdout, b"bananasplit") self.assertEqual(stderr, b"") + def test_communicate_stdin_closed_before_call(self): + # gh-70560, gh-74389: stdin.close() before communicate() + # should not raise ValueError from stdin.flush() + with subprocess.Popen([sys.executable, "-c", + 'import sys; sys.exit(0)'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as p: + p.stdin.close() # Close stdin before communicate + # This should not raise ValueError + (stdout, stderr) = p.communicate() + self.assertEqual(p.returncode, 0) + def test_universal_newlines_and_text(self): args = [ sys.executable, "-c", @@ -1223,6 +1343,16 @@ def test_no_leaking(self): max_handles = 1026 # too much for most UNIX systems else: max_handles = 2050 # too much for (at least some) Windows setups + if resource: + # And if it is not too much, try to make it too much. + try: + soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) + if soft > 1024: + resource.setrlimit(resource.RLIMIT_NOFILE, (1024, hard)) + self.addCleanup(resource.setrlimit, resource.RLIMIT_NOFILE, + (soft, hard)) + except (OSError, ValueError): + pass handles = [] tmpdir = tempfile.mkdtemp() try: @@ -1237,7 +1367,9 @@ def test_no_leaking(self): else: self.skipTest("failed to reach the file descriptor limit " "(tried %d)" % max_handles) - # Close a couple of them (should be enough for a subprocess) + # Close a couple of them (should be enough for a subprocess). + # Close lower file descriptors, so select() will work. + handles.reverse() for i in range(10): os.close(handles.pop()) # Loop creating some subprocesses. If one of them leaks some fds, @@ -1341,8 +1473,6 @@ def test_bufsize_equal_one_text_mode(self): line = "line\n" self._test_bufsize_equal_one(line, line, universal_newlines=True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_bufsize_equal_one_binary_mode(self): # line is not flushed in binary mode with bufsize=1. # we should get empty response @@ -1414,7 +1544,7 @@ def open_fds(): t = threading.Thread(target=open_fds) t.start() try: - with self.assertRaises(EnvironmentError): + with self.assertRaises(OSError): subprocess.Popen(NONEXISTING_CMD, stdin=subprocess.PIPE, stdout=subprocess.PIPE, @@ -1528,9 +1658,6 @@ def test_communicate_epipe(self): p.communicate(b"x" * 2**20) def test_repr(self): - path_cmd = pathlib.Path("my-tool.py") - pathlib_cls = path_cmd.__class__.__name__ - cases = [ ("ls", True, 123, "<Popen: returncode: 123 args: 'ls'>"), ('a' * 100, True, 0, @@ -1538,7 +1665,8 @@ def test_repr(self): (["ls"], False, None, "<Popen: returncode: None args: ['ls']>"), (["ls", '--my-opts', 'a' * 100], False, None, "<Popen: returncode: None args: ['ls', '--my-opts', 'aaaaaaaaaaaaaaaaaaaaaaaa...>"), - (path_cmd, False, 7, f"<Popen: returncode: 7 args: {pathlib_cls}('my-tool.py')>") + (os_helper.FakePath("my-tool.py"), False, 7, + "<Popen: returncode: 7 args: <FakePath 'my-tool.py'>>") ] with unittest.mock.patch.object(subprocess.Popen, '_execute_child'): for cmd, shell, code, sx in cases: @@ -1613,21 +1741,6 @@ def test_class_getitems(self): self.assertIsInstance(subprocess.Popen[bytes], types.GenericAlias) self.assertIsInstance(subprocess.CompletedProcess[str], types.GenericAlias) - @unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"), - "vfork() not enabled by configure.") - @mock.patch("subprocess._fork_exec") - def test__use_vfork(self, mock_fork_exec): - self.assertTrue(subprocess._USE_VFORK) # The default value regardless. - mock_fork_exec.side_effect = RuntimeError("just testing args") - with self.assertRaises(RuntimeError): - subprocess.run([sys.executable, "-c", "pass"]) - mock_fork_exec.assert_called_once() - self.assertTrue(mock_fork_exec.call_args.args[-1]) - with mock.patch.object(subprocess, '_USE_VFORK', False): - with self.assertRaises(RuntimeError): - subprocess.run([sys.executable, "-c", "pass"]) - self.assertFalse(mock_fork_exec.call_args_list[-1].args[-1]) - @unittest.skipUnless(hasattr(subprocess, '_winapi'), 'need subprocess._winapi') def test_wait_negative_timeout(self): @@ -1644,6 +1757,40 @@ def test_wait_negative_timeout(self): self.assertEqual(proc.wait(), 0) + def test_post_timeout_communicate_sends_input(self): + """GH-141473 regression test; the stdin pipe must close""" + with subprocess.Popen( + [sys.executable, "-uc", """\ +import sys +while c := sys.stdin.read(512): + sys.stdout.write(c) +print() +"""], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) as proc: + try: + data = f"spam{'#'*4096}beans" + proc.communicate( + input=data, + timeout=0, + ) + except subprocess.TimeoutExpired as exc: + pass + # Prior to the bugfix, this would hang as the stdin + # pipe to the child had not been closed. + try: + stdout, stderr = proc.communicate(timeout=15) + except subprocess.TimeoutExpired as exc: + self.fail("communicate() hung waiting on child process that should have seen its stdin pipe close and exit") + self.assertEqual( + proc.returncode, 0, + msg=f"STDERR:\n{stderr}\nSTDOUT:\n{stdout}") + self.assertTrue(stdout.startswith("spam"), msg=stdout) + self.assertIn("beans", stdout) + class RunFuncTestCase(BaseTestCase): def run_python(self, code, **kwargs): @@ -1717,20 +1864,11 @@ def test_check_output_stdin_with_input_arg(self): self.assertIn('stdin', c.exception.args[0]) self.assertIn('input', c.exception.args[0]) - @support.requires_resource('walltime') def test_check_output_timeout(self): with self.assertRaises(subprocess.TimeoutExpired) as c: - cp = self.run_python(( - "import sys, time\n" - "sys.stdout.write('BDFL')\n" - "sys.stdout.flush()\n" - "time.sleep(3600)"), - # Some heavily loaded buildbots (sparc Debian 3.x) require - # this much time to start and print. - timeout=3, stdout=subprocess.PIPE) - self.assertEqual(c.exception.output, b'BDFL') - # output is aliased to stdout - self.assertEqual(c.exception.stdout, b'BDFL') + cp = self.run_python( + "import time; time.sleep(3600)", + timeout=0.1, stdout=subprocess.PIPE) def test_run_kwargs(self): newenv = os.environ.copy() @@ -1785,6 +1923,13 @@ def test_capture_output(self): self.assertIn(b'BDFL', cp.stdout) self.assertIn(b'FLUFL', cp.stderr) + def test_stdout_stdout(self): + # run() refuses to accept stdout=STDOUT + with self.assertRaises(ValueError, + msg=("STDOUT can only be used for stderr")): + self.run_python("print('will not be run')", + stdout=subprocess.STDOUT) + def test_stdout_with_capture_output_arg(self): # run() refuses to accept 'stdout' with 'capture_output' tf = tempfile.TemporaryFile() @@ -1969,8 +2114,6 @@ def bad_error(*args): self.assertIn(repr(error_data), str(e.exception)) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(not os.path.exists('/proc/self/status'), "need /proc/self/status") def test_restore_signals(self): @@ -2189,8 +2332,6 @@ def test_extra_groups_invalid_gid_t_values(self): cwd=os.curdir, env=os.environ, extra_groups=[2**64]) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(mswindows or not hasattr(os, 'umask'), 'POSIX umask() is not available.') def test_umask(self): @@ -3446,6 +3587,94 @@ def __del__(self): self.assertEqual(out.strip(), b"OK") self.assertIn(b"preexec_fn not supported at interpreter shutdown", err) + @unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"), + "vfork() not enabled by configure.") + @mock.patch("subprocess._fork_exec") + @mock.patch("subprocess._USE_POSIX_SPAWN", new=False) + def test__use_vfork(self, mock_fork_exec): + self.assertTrue(subprocess._USE_VFORK) # The default value regardless. + mock_fork_exec.side_effect = RuntimeError("just testing args") + with self.assertRaises(RuntimeError): + subprocess.run([sys.executable, "-c", "pass"]) + mock_fork_exec.assert_called_once() + # NOTE: These assertions are *ugly* as they require the last arg + # to remain the have_vfork boolean. We really need to refactor away + # from the giant "wall of args" internal C extension API. + self.assertTrue(mock_fork_exec.call_args.args[-1]) + with mock.patch.object(subprocess, '_USE_VFORK', False): + with self.assertRaises(RuntimeError): + subprocess.run([sys.executable, "-c", "pass"]) + self.assertFalse(mock_fork_exec.call_args_list[-1].args[-1]) + + @unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"), + "vfork() not enabled by configure.") + @unittest.skipIf(sys.platform != "linux", "Linux only, requires strace.") + @mock.patch("subprocess._USE_POSIX_SPAWN", new=False) + def test_vfork_used_when_expected(self): + # This is a performance regression test to ensure we default to using + # vfork() when possible. + # Technically this test could pass when posix_spawn is used as well + # because libc tends to implement that internally using vfork. But + # that'd just be testing a libc+kernel implementation detail. + strace_binary = "/usr/bin/strace" + # The only system calls we are interested in. + strace_filter = "--trace=clone,clone2,clone3,fork,vfork,exit,exit_group" + true_binary = "/bin/true" + strace_command = [strace_binary, strace_filter] + + try: + does_strace_work_process = subprocess.run( + strace_command + [true_binary], + stderr=subprocess.PIPE, + stdout=subprocess.DEVNULL, + ) + rc = does_strace_work_process.returncode + stderr = does_strace_work_process.stderr + except OSError: + rc = -1 + stderr = "" + if rc or (b"+++ exited with 0 +++" not in stderr): + self.skipTest("strace not found or not working as expected.") + + with self.subTest(name="default_is_vfork"): + vfork_result = assert_python_ok( + "-c", + textwrap.dedent(f"""\ + import subprocess + subprocess.check_call([{true_binary!r}])"""), + __run_using_command=strace_command, + ) + # Match both vfork() and clone(..., flags=...|CLONE_VFORK|...) + self.assertRegex(vfork_result.err, br"(?i)vfork") + # Do NOT check that fork() or other clones did not happen. + # If the OS denys the vfork it'll fallback to plain fork(). + + # Test that each individual thing that would disable the use of vfork + # actually disables it. + for sub_name, preamble, sp_kwarg, expect_permission_error in ( + ("!use_vfork", "subprocess._USE_VFORK = False", "", False), + ("preexec", "", "preexec_fn=lambda: None", False), + ("setgid", "", f"group={os.getgid()}", True), + ("setuid", "", f"user={os.getuid()}", True), + ("setgroups", "", "extra_groups=[]", True), + ): + with self.subTest(name=sub_name): + non_vfork_result = assert_python_ok( + "-c", + textwrap.dedent(f"""\ + import subprocess + {preamble} + try: + subprocess.check_call( + [{true_binary!r}], **dict({sp_kwarg})) + except PermissionError: + if not {expect_permission_error}: + raise"""), + __run_using_command=strace_command, + ) + # Ensure neither vfork() or clone(..., flags=...|CLONE_VFORK|...). + self.assertNotRegex(non_vfork_result.err, br"(?i)vfork") + @unittest.skipUnless(mswindows, "Windows specific tests") class Win32ProcessTestCase(BaseTestCase): diff --git a/crates/stdlib/src/posixsubprocess.rs b/crates/stdlib/src/posixsubprocess.rs index d05b24fd6dd..4b895d2102b 100644 --- a/crates/stdlib/src/posixsubprocess.rs +++ b/crates/stdlib/src/posixsubprocess.rs @@ -321,11 +321,14 @@ fn exec_inner( } if args.child_umask >= 0 { - // TODO: umask(child_umask); + unsafe { libc::umask(args.child_umask as libc::mode_t) }; } if args.restore_signals { - // TODO: restore signals SIGPIPE, SIGXFZ, SIGXFSZ to SIG_DFL + unsafe { + libc::signal(libc::SIGPIPE, libc::SIG_DFL); + libc::signal(libc::SIGXFSZ, libc::SIG_DFL); + } } if args.call_setsid { diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 89dc8bca925..52900faec08 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -4489,6 +4489,16 @@ mod _io { bool::try_from_object(vm, atty)? }; + // Warn if line buffering is requested in binary mode + if opts.buffering == 1 && matches!(mode.encode, EncodeMode::Bytes) { + crate::stdlib::warnings::warn( + vm.ctx.exceptions.runtime_warning, + "line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used".to_owned(), + 1, + vm, + )?; + } + let line_buffering = opts.buffering == 1 || isatty; let buffering = if opts.buffering < 0 || opts.buffering == 1 { diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index efbd0cf9049..a4a311df06a 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -1515,7 +1515,7 @@ pub mod module { #[pyarg(positional)] args: crate::function::ArgIterable<OsPath>, #[pyarg(positional)] - env: crate::function::ArgMapping, + env: Option<crate::function::ArgMapping>, #[pyarg(named, default)] file_actions: Option<crate::function::ArgIterable<PyTupleRef>>, #[pyarg(named, default)] @@ -1678,7 +1678,22 @@ pub mod module { .map_err(|_| vm.new_value_error("path should not have nul bytes")) }) .collect::<Result<_, _>>()?; - let env = envp_from_dict(self.env, vm)?; + let env = if let Some(env_dict) = self.env { + envp_from_dict(env_dict, vm)? + } else { + // env=None means use the current environment + use rustpython_common::os::ffi::OsStringExt; + env::vars_os() + .map(|(k, v)| { + let mut entry = k.into_vec(); + entry.push(b'='); + entry.extend(v.into_vec()); + CString::new(entry).map_err(|_| { + vm.new_value_error("environment string contains null byte") + }) + }) + .collect::<PyResult<Vec<_>>>()? + }; let ret = if spawnp { nix::spawn::posix_spawnp(&path, &file_actions, &attrp, &args, &env) diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index 5cfb62fad6f..a3aba447489 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -63,7 +63,8 @@ mod _winapi { CREATE_BREAKAWAY_FROM_JOB, CREATE_DEFAULT_ERROR_MODE, CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW, DETACHED_PROCESS, HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS, INFINITE, NORMAL_PRIORITY_CLASS, PROCESS_DUP_HANDLE, - REALTIME_PRIORITY_CLASS, STARTF_USESHOWWINDOW, STARTF_USESTDHANDLES, + REALTIME_PRIORITY_CLASS, STARTF_FORCEOFFFEEDBACK, STARTF_FORCEONFEEDBACK, + STARTF_USESHOWWINDOW, STARTF_USESTDHANDLES, }, }, UI::WindowsAndMessaging::SW_HIDE, From 1464d5ca43273ccf7a7add66a211f67a982ae9f3 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Mon, 29 Dec 2025 23:10:14 -0500 Subject: [PATCH 631/819] Adding + Fixing Clippy rules to better align with #[no_std] (#6570) * * Added alloc_instead_of_core, std_instead_of_alloc, and std_instead_of_core clippy rules * Manually changed part of the code to use core/alloc * use clippy --fix to fix issues in stdlib * * Used clippy --fix to fix issues in vm * Imported Range in vm/src/anystr.rs * * Used clippy --fix to fix issues in common --- Cargo.toml | 3 + crates/codegen/src/compile.rs | 13 +-- crates/codegen/src/error.rs | 5 +- crates/codegen/src/ir.rs | 6 +- crates/codegen/src/lib.rs | 2 + crates/codegen/src/string_parser.rs | 4 +- crates/codegen/src/symboltable.rs | 12 +-- crates/codegen/src/unparse.rs | 9 +- crates/common/src/borrow.rs | 6 +- crates/common/src/boxvec.rs | 12 +-- crates/common/src/cformat.rs | 15 ++-- crates/common/src/crt_fd.rs | 4 +- crates/common/src/encodings.rs | 4 +- crates/common/src/fileutils.rs | 10 +-- crates/common/src/format.rs | 6 +- crates/common/src/hash.rs | 8 +- crates/common/src/int.rs | 12 +-- crates/common/src/lib.rs | 2 + crates/common/src/linked_list.rs | 2 +- crates/common/src/lock/cell_lock.rs | 2 +- crates/common/src/lock/immutable_mutex.rs | 7 +- crates/common/src/lock/thread_mutex.rs | 14 +-- crates/common/src/os.rs | 3 +- crates/common/src/rc.rs | 4 +- crates/common/src/str.rs | 25 +++--- crates/compiler-core/src/bytecode.rs | 13 +-- crates/compiler-core/src/lib.rs | 2 + crates/compiler-core/src/marshal.rs | 18 ++-- crates/compiler-core/src/mode.rs | 6 +- crates/compiler/src/lib.rs | 4 +- crates/derive-impl/src/compile_bytecode.rs | 4 +- crates/derive-impl/src/from_args.rs | 8 +- crates/derive-impl/src/pyclass.rs | 10 +-- crates/derive-impl/src/pymodule.rs | 9 +- crates/derive-impl/src/pytraverse.rs | 2 +- crates/derive-impl/src/util.rs | 10 +-- crates/derive/src/lib.rs | 2 +- crates/jit/src/lib.rs | 5 +- crates/literal/src/escape.rs | 30 +++---- crates/literal/src/float.rs | 2 +- crates/sre_engine/src/engine.rs | 8 +- crates/sre_engine/src/string.rs | 2 +- crates/stdlib/src/array.rs | 41 ++++----- crates/stdlib/src/binascii.rs | 2 +- crates/stdlib/src/bz2.rs | 3 +- crates/stdlib/src/cmath.rs | 4 +- crates/stdlib/src/compression.rs | 4 +- crates/stdlib/src/contextvars.rs | 26 +++--- crates/stdlib/src/csv.rs | 7 +- crates/stdlib/src/faulthandler.rs | 25 +++--- crates/stdlib/src/fcntl.rs | 2 +- crates/stdlib/src/grp.rs | 4 +- crates/stdlib/src/hashlib.rs | 8 +- crates/stdlib/src/json.rs | 4 +- crates/stdlib/src/lib.rs | 3 +- crates/stdlib/src/locale.rs | 6 +- crates/stdlib/src/lzma.rs | 2 +- crates/stdlib/src/math.rs | 16 ++-- crates/stdlib/src/mmap.rs | 4 +- crates/stdlib/src/opcode.rs | 2 +- crates/stdlib/src/openssl.rs | 8 +- crates/stdlib/src/overlapped.rs | 24 ++--- crates/stdlib/src/posixshmem.rs | 2 +- crates/stdlib/src/posixsubprocess.rs | 10 +-- crates/stdlib/src/resource.rs | 3 +- crates/stdlib/src/scproxy.rs | 2 +- crates/stdlib/src/select.rs | 15 ++-- crates/stdlib/src/socket.rs | 32 ++++--- crates/stdlib/src/sqlite.rs | 4 +- crates/stdlib/src/ssl.rs | 22 +++-- crates/stdlib/src/ssl/cert.rs | 31 +++---- crates/stdlib/src/ssl/compat.rs | 3 +- crates/stdlib/src/syslog.rs | 5 +- crates/stdlib/src/tkinter.rs | 8 +- crates/stdlib/src/zlib.rs | 4 +- crates/venvlauncher/src/main.rs | 6 +- crates/vm/src/anystr.rs | 30 +++---- crates/vm/src/buffer.rs | 12 +-- crates/vm/src/builtins/bool.rs | 4 +- crates/vm/src/builtins/builtin_func.rs | 4 +- crates/vm/src/builtins/bytearray.rs | 4 +- crates/vm/src/builtins/bytes.rs | 2 +- crates/vm/src/builtins/code.rs | 5 +- crates/vm/src/builtins/complex.rs | 2 +- crates/vm/src/builtins/descriptor.rs | 12 +-- crates/vm/src/builtins/dict.rs | 6 +- crates/vm/src/builtins/function.rs | 4 +- crates/vm/src/builtins/genericalias.rs | 2 +- crates/vm/src/builtins/getset.rs | 6 +- crates/vm/src/builtins/int.rs | 8 +- crates/vm/src/builtins/list.rs | 17 ++-- crates/vm/src/builtins/map.rs | 2 +- crates/vm/src/builtins/memory.rs | 2 +- crates/vm/src/builtins/module.rs | 4 +- crates/vm/src/builtins/object.rs | 2 +- crates/vm/src/builtins/property.rs | 4 +- crates/vm/src/builtins/range.rs | 2 +- crates/vm/src/builtins/set.rs | 31 +++---- crates/vm/src/builtins/str.rs | 43 ++++----- crates/vm/src/builtins/traceback.rs | 2 +- crates/vm/src/builtins/tuple.rs | 27 +++--- crates/vm/src/builtins/type.rs | 27 +++--- crates/vm/src/builtins/union.rs | 2 +- crates/vm/src/bytes_inner.rs | 14 +-- crates/vm/src/cformat.rs | 4 +- crates/vm/src/class.rs | 4 +- crates/vm/src/codecs.rs | 8 +- crates/vm/src/convert/try_from.rs | 2 +- crates/vm/src/dict_inner.rs | 9 +- crates/vm/src/exceptions.rs | 10 +-- crates/vm/src/frame.rs | 25 +++--- crates/vm/src/function/argument.rs | 8 +- crates/vm/src/function/builtin.rs | 8 +- crates/vm/src/function/either.rs | 2 +- crates/vm/src/function/fspath.rs | 7 +- crates/vm/src/function/method.rs | 6 +- crates/vm/src/function/number.rs | 4 +- crates/vm/src/function/protocol.rs | 12 +-- crates/vm/src/intern.rs | 30 +++---- crates/vm/src/lib.rs | 1 + crates/vm/src/macros.rs | 4 +- crates/vm/src/object/core.rs | 52 +++++------ crates/vm/src/object/ext.rs | 9 +- crates/vm/src/object/payload.rs | 18 ++-- crates/vm/src/object/traverse.rs | 2 +- crates/vm/src/object/traverse_object.rs | 2 +- crates/vm/src/ospath.rs | 6 +- crates/vm/src/protocol/buffer.rs | 11 +-- crates/vm/src/protocol/callable.rs | 4 +- crates/vm/src/protocol/iter.rs | 8 +- crates/vm/src/protocol/mapping.rs | 8 +- crates/vm/src/protocol/number.rs | 2 +- crates/vm/src/protocol/sequence.rs | 8 +- crates/vm/src/py_io.rs | 4 +- crates/vm/src/py_serde.rs | 2 +- crates/vm/src/readline.rs | 2 +- crates/vm/src/scope.rs | 2 +- crates/vm/src/sequence.rs | 4 +- crates/vm/src/signal.rs | 10 +-- crates/vm/src/sliceable.rs | 2 +- crates/vm/src/stdlib/ast/elif_else_clause.rs | 2 +- crates/vm/src/stdlib/ast/parameter.rs | 2 +- crates/vm/src/stdlib/ast/string.rs | 4 +- crates/vm/src/stdlib/atexit.rs | 2 +- crates/vm/src/stdlib/builtins.rs | 10 +-- crates/vm/src/stdlib/codecs.rs | 8 +- crates/vm/src/stdlib/collections.rs | 10 +-- crates/vm/src/stdlib/ctypes.rs | 42 +++++---- crates/vm/src/stdlib/ctypes/array.rs | 14 +-- crates/vm/src/stdlib/ctypes/base.rs | 64 ++++++------- crates/vm/src/stdlib/ctypes/function.rs | 50 +++++------ crates/vm/src/stdlib/ctypes/library.rs | 4 +- crates/vm/src/stdlib/ctypes/pointer.rs | 56 ++++++------ crates/vm/src/stdlib/ctypes/simple.rs | 94 ++++++++++---------- crates/vm/src/stdlib/ctypes/structure.rs | 12 +-- crates/vm/src/stdlib/ctypes/union.rs | 12 +-- crates/vm/src/stdlib/io.rs | 52 +++++------ crates/vm/src/stdlib/itertools.rs | 4 +- crates/vm/src/stdlib/mod.rs | 3 +- crates/vm/src/stdlib/nt.rs | 34 +++---- crates/vm/src/stdlib/os.rs | 29 +++--- crates/vm/src/stdlib/posix.rs | 14 +-- crates/vm/src/stdlib/pwd.rs | 4 +- crates/vm/src/stdlib/signal.rs | 14 +-- crates/vm/src/stdlib/string.rs | 2 +- crates/vm/src/stdlib/symtable.rs | 4 +- crates/vm/src/stdlib/sys.rs | 12 +-- crates/vm/src/stdlib/thread.rs | 8 +- crates/vm/src/stdlib/time.rs | 28 +++--- crates/vm/src/stdlib/winapi.rs | 12 +-- crates/vm/src/stdlib/winreg.rs | 18 ++-- crates/vm/src/suggestion.rs | 2 +- crates/vm/src/types/slot.rs | 18 ++-- crates/vm/src/utils.rs | 8 +- crates/vm/src/version.rs | 3 +- crates/vm/src/vm/context.rs | 6 +- crates/vm/src/vm/interpreter.rs | 2 +- crates/vm/src/vm/mod.rs | 16 ++-- crates/vm/src/vm/thread.rs | 6 +- crates/vm/src/vm/vm_new.rs | 4 +- crates/vm/src/vm/vm_ops.rs | 4 +- crates/vm/src/warn.rs | 2 +- crates/vm/src/windows.rs | 6 +- examples/dis.rs | 2 +- src/lib.rs | 2 +- 185 files changed, 1000 insertions(+), 952 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 06734b47e18..12607cfa2cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -232,6 +232,9 @@ unsafe_op_in_unsafe_fn = "deny" elided_lifetimes_in_paths = "warn" [workspace.lints.clippy] +# alloc_instead_of_core = "warn" +# std_instead_of_alloc = "warn" +# std_instead_of_core = "warn" perf = "warn" style = "warn" complexity = "warn" diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 7909e924251..c44b2b00684 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -16,6 +16,7 @@ use crate::{ symboltable::{self, CompilerScope, SymbolFlags, SymbolScope, SymbolTable}, unparse::UnparseExpr, }; +use alloc::borrow::Cow; use itertools::Itertools; use malachite_bigint::BigInt; use num_complex::Complex; @@ -42,7 +43,7 @@ use rustpython_compiler_core::{ }, }; use rustpython_wtf8::Wtf8Buf; -use std::{borrow::Cow, collections::HashSet}; +use std::collections::HashSet; const MAXBLOCKS: usize = 20; @@ -293,7 +294,7 @@ fn compiler_unwrap_option<T>(zelf: &Compiler, o: Option<T>) -> T { o.unwrap() } -// fn compiler_result_unwrap<T, E: std::fmt::Debug>(zelf: &Compiler, result: Result<T, E>) -> T { +// fn compiler_result_unwrap<T, E: core::fmt::Debug>(zelf: &Compiler, result: Result<T, E>) -> T { // if result.is_err() { // eprintln!("=== CODEGEN PANIC INFO ==="); // eprintln!("This IS an internal error, an result was unwrapped during codegen"); @@ -1831,7 +1832,7 @@ impl Compiler { name.to_owned(), ); - let args_iter = std::iter::empty() + let args_iter = core::iter::empty() .chain(¶meters.posonlyargs) .chain(¶meters.args) .map(|arg| &arg.parameter) @@ -2438,7 +2439,7 @@ impl Compiler { let mut funcflags = bytecode::MakeFunctionFlags::empty(); // Handle positional defaults - let defaults: Vec<_> = std::iter::empty() + let defaults: Vec<_> = core::iter::empty() .chain(¶meters.posonlyargs) .chain(¶meters.args) .filter_map(|x| x.default.as_deref()) @@ -2566,7 +2567,7 @@ impl Compiler { let mut num_annotations = 0; // Handle parameter annotations - let parameters_iter = std::iter::empty() + let parameters_iter = core::iter::empty() .chain(¶meters.posonlyargs) .chain(¶meters.args) .chain(¶meters.kwonlyargs) @@ -4965,7 +4966,7 @@ impl Compiler { let name = "<lambda>".to_owned(); // Prepare defaults before entering function - let defaults: Vec<_> = std::iter::empty() + let defaults: Vec<_> = core::iter::empty() .chain(¶ms.posonlyargs) .chain(¶ms.args) .filter_map(|x| x.default.as_deref()) diff --git a/crates/codegen/src/error.rs b/crates/codegen/src/error.rs index 70e2f13f253..459ba8e33b5 100644 --- a/crates/codegen/src/error.rs +++ b/crates/codegen/src/error.rs @@ -1,5 +1,6 @@ +use alloc::fmt; +use core::fmt::Display; use rustpython_compiler_core::SourceLocation; -use std::fmt::{self, Display}; use thiserror::Error; #[derive(Debug)] @@ -93,7 +94,7 @@ pub enum CodegenErrorType { NotImplementedYet, // RustPython marker for unimplemented features } -impl std::error::Error for CodegenErrorType {} +impl core::error::Error for CodegenErrorType {} impl fmt::Display for CodegenErrorType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index de0126f1122..670635fbd37 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -1,4 +1,4 @@ -use std::ops; +use core::ops; use crate::{IndexMap, IndexSet, error::InternalError}; use rustpython_compiler_core::{ @@ -198,7 +198,7 @@ impl CodeInfo { *arg = new_arg; } let (extras, lo_arg) = arg.split(); - locations.extend(std::iter::repeat_n(info.location, arg.instr_size())); + locations.extend(core::iter::repeat_n(info.location, arg.instr_size())); instructions.extend( extras .map(|byte| CodeUnit::new(Instruction::ExtendedArg, byte)) @@ -401,7 +401,7 @@ fn stackdepth_push( fn iter_blocks(blocks: &[Block]) -> impl Iterator<Item = (BlockIdx, &Block)> + '_ { let mut next = BlockIdx(0); - std::iter::from_fn(move || { + core::iter::from_fn(move || { if next == BlockIdx::NULL { return None; } diff --git a/crates/codegen/src/lib.rs b/crates/codegen/src/lib.rs index 291b57d7f67..34d3870ae91 100644 --- a/crates/codegen/src/lib.rs +++ b/crates/codegen/src/lib.rs @@ -5,6 +5,8 @@ #[macro_use] extern crate log; +extern crate alloc; + type IndexMap<K, V> = indexmap::IndexMap<K, V, ahash::RandomState>; type IndexSet<T> = indexmap::IndexSet<T, ahash::RandomState>; diff --git a/crates/codegen/src/string_parser.rs b/crates/codegen/src/string_parser.rs index ede2f118c37..175e75c1a26 100644 --- a/crates/codegen/src/string_parser.rs +++ b/crates/codegen/src/string_parser.rs @@ -5,7 +5,7 @@ //! after ruff has already successfully parsed the string literal, meaning //! we don't need to do any validation or error handling. -use std::convert::Infallible; +use core::convert::Infallible; use ruff_python_ast::{AnyStringFlags, StringFlags}; use rustpython_wtf8::{CodePoint, Wtf8, Wtf8Buf}; @@ -96,7 +96,7 @@ impl StringParser { } // OK because radix_bytes is always going to be in the ASCII range. - let radix_str = std::str::from_utf8(&radix_bytes[..len]).expect("ASCII bytes"); + let radix_str = core::str::from_utf8(&radix_bytes[..len]).expect("ASCII bytes"); let value = u32::from_str_radix(radix_str, 8).unwrap(); char::from_u32(value).unwrap() } diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index 3c8454b9e22..1629e5fff38 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -11,6 +11,7 @@ use crate::{ IndexMap, error::{CodegenError, CodegenErrorType}, }; +use alloc::{borrow::Cow, fmt}; use bitflags::bitflags; use ruff_python_ast::{ self as ast, Comprehension, Decorator, Expr, Identifier, ModExpression, ModModule, Parameter, @@ -20,7 +21,6 @@ use ruff_python_ast::{ }; use ruff_text_size::{Ranged, TextRange}; use rustpython_compiler_core::{PositionEncoding, SourceFile, SourceLocation}; -use std::{borrow::Cow, fmt}; /// Captures all symbols in the current scope, and has a list of sub-scopes in this scope. #[derive(Clone)] @@ -215,8 +215,8 @@ impl SymbolTableError { type SymbolTableResult<T = ()> = Result<T, SymbolTableError>; -impl std::fmt::Debug for SymbolTable { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for SymbolTable { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, "SymbolTable({:?} symbols, {:?} sub scopes)", @@ -261,8 +261,8 @@ fn drop_class_free(symbol_table: &mut SymbolTable) { type SymbolMap = IndexMap<String, Symbol>; mod stack { + use core::ptr::NonNull; use std::panic; - use std::ptr::NonNull; pub struct StackStack<T> { v: Vec<NonNull<T>>, } @@ -325,7 +325,7 @@ struct SymbolTableAnalyzer { impl SymbolTableAnalyzer { fn analyze_symbol_table(&mut self, symbol_table: &mut SymbolTable) -> SymbolTableResult { - let symbols = std::mem::take(&mut symbol_table.symbols); + let symbols = core::mem::take(&mut symbol_table.symbols); let sub_tables = &mut *symbol_table.sub_tables; let mut info = (symbols, symbol_table.typ); @@ -689,7 +689,7 @@ impl SymbolTableBuilder { fn leave_scope(&mut self) { let mut table = self.tables.pop().unwrap(); // Save the collected varnames to the symbol table - table.varnames = std::mem::take(&mut self.current_varnames); + table.varnames = core::mem::take(&mut self.current_varnames); self.tables.last_mut().unwrap().sub_tables.push(table); } diff --git a/crates/codegen/src/unparse.rs b/crates/codegen/src/unparse.rs index 74e35fd5e2a..7b26d229187 100644 --- a/crates/codegen/src/unparse.rs +++ b/crates/codegen/src/unparse.rs @@ -1,3 +1,5 @@ +use alloc::fmt; +use core::fmt::Display as _; use ruff_python_ast::{ self as ruff, Arguments, BoolOp, Comprehension, ConversionFlag, Expr, Identifier, Operator, Parameter, ParameterWithDefault, Parameters, @@ -5,7 +7,6 @@ use ruff_python_ast::{ use ruff_text_size::Ranged; use rustpython_compiler_core::SourceFile; use rustpython_literal::escape::{AsciiEscape, UnicodeEscape}; -use std::fmt::{self, Display as _}; mod precedence { macro_rules! precedence { @@ -51,7 +52,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { } fn p_delim(&mut self, first: &mut bool, s: &str) -> fmt::Result { - self.p_if(!std::mem::take(first), s) + self.p_if(!core::mem::take(first), s) } fn write_fmt(&mut self, f: fmt::Arguments<'_>) -> fmt::Result { @@ -575,7 +576,7 @@ impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { if conversion != ConversionFlag::None { self.p("!")?; let buf = &[conversion as u8]; - let c = std::str::from_utf8(buf).unwrap(); + let c = core::str::from_utf8(buf).unwrap(); self.p(c)?; } @@ -650,7 +651,7 @@ impl fmt::Display for UnparseExpr<'_> { } fn to_string_fmt(f: impl FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result) -> String { - use std::cell::Cell; + use core::cell::Cell; struct Fmt<F>(Cell<Option<F>>); impl<F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result> fmt::Display for Fmt<F> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/common/src/borrow.rs b/crates/common/src/borrow.rs index 610084006e1..d8389479b33 100644 --- a/crates/common/src/borrow.rs +++ b/crates/common/src/borrow.rs @@ -2,10 +2,8 @@ use crate::lock::{ MapImmutable, PyImmutableMappedMutexGuard, PyMappedMutexGuard, PyMappedRwLockReadGuard, PyMappedRwLockWriteGuard, PyMutexGuard, PyRwLockReadGuard, PyRwLockWriteGuard, }; -use std::{ - fmt, - ops::{Deref, DerefMut}, -}; +use alloc::fmt; +use core::ops::{Deref, DerefMut}; macro_rules! impl_from { ($lt:lifetime, $gen:ident, $t:ty, $($var:ident($from:ty),)*) => { diff --git a/crates/common/src/boxvec.rs b/crates/common/src/boxvec.rs index 8687ba7f7f5..3260e76ca87 100644 --- a/crates/common/src/boxvec.rs +++ b/crates/common/src/boxvec.rs @@ -2,13 +2,13 @@ //! An unresizable vector backed by a `Box<[T]>` #![allow(clippy::needless_lifetimes)] - -use std::{ +use alloc::{fmt, slice}; +use core::{ borrow::{Borrow, BorrowMut}, - cmp, fmt, + cmp, mem::{self, MaybeUninit}, ops::{Bound, Deref, DerefMut, RangeBounds}, - ptr, slice, + ptr, }; pub struct BoxVec<T> { @@ -555,7 +555,7 @@ impl<T> Extend<T> for BoxVec<T> { }; let mut iter = iter.into_iter(); loop { - if std::ptr::eq(ptr, end_ptr) { + if core::ptr::eq(ptr, end_ptr) { break; } if let Some(elt) = iter.next() { @@ -693,7 +693,7 @@ impl<T> CapacityError<T> { const CAPERROR: &str = "insufficient capacity"; -impl<T> std::error::Error for CapacityError<T> {} +impl<T> core::error::Error for CapacityError<T> {} impl<T> fmt::Display for CapacityError<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/common/src/cformat.rs b/crates/common/src/cformat.rs index b553f0b6b10..24332396fdb 100644 --- a/crates/common/src/cformat.rs +++ b/crates/common/src/cformat.rs @@ -1,15 +1,16 @@ //! Implementation of Printf-Style string formatting //! as per the [Python Docs](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting). +use alloc::fmt; use bitflags::bitflags; +use core::{ + cmp, + iter::{Enumerate, Peekable}, + str::FromStr, +}; use itertools::Itertools; use malachite_bigint::{BigInt, Sign}; use num_traits::Signed; use rustpython_literal::{float, format::Case}; -use std::{ - cmp, fmt, - iter::{Enumerate, Peekable}, - str::FromStr, -}; use crate::wtf8::{CodePoint, Wtf8, Wtf8Buf}; @@ -785,7 +786,7 @@ impl<S> CFormatStrOrBytes<S> { if !literal.is_empty() { parts.push(( part_index, - CFormatPart::Literal(std::mem::take(&mut literal)), + CFormatPart::Literal(core::mem::take(&mut literal)), )); } let spec = CFormatSpecKeyed::parse(iter).map_err(|err| CFormatError { @@ -816,7 +817,7 @@ impl<S> CFormatStrOrBytes<S> { impl<S> IntoIterator for CFormatStrOrBytes<S> { type Item = (usize, CFormatPart<S>); - type IntoIter = std::vec::IntoIter<Self::Item>; + type IntoIter = alloc::vec::IntoIter<Self::Item>; fn into_iter(self) -> Self::IntoIter { self.parts.into_iter() diff --git a/crates/common/src/crt_fd.rs b/crates/common/src/crt_fd.rs index b873ef9c52c..1902a362e32 100644 --- a/crates/common/src/crt_fd.rs +++ b/crates/common/src/crt_fd.rs @@ -1,7 +1,9 @@ //! A module implementing an io type backed by the C runtime's file descriptors, i.e. what's //! returned from libc::open, even on windows. -use std::{cmp, ffi, fmt, io}; +use alloc::fmt; +use core::cmp; +use std::{ffi, io}; #[cfg(not(windows))] use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; diff --git a/crates/common/src/encodings.rs b/crates/common/src/encodings.rs index 39ca2661262..d54581eb9ea 100644 --- a/crates/common/src/encodings.rs +++ b/crates/common/src/encodings.rs @@ -1,4 +1,4 @@ -use std::ops::{self, Range}; +use core::ops::{self, Range}; use num_traits::ToPrimitive; @@ -260,7 +260,7 @@ pub mod errors { use crate::str::UnicodeEscapeCodepoint; use super::*; - use std::fmt::Write; + use core::fmt::Write; pub struct Strict; diff --git a/crates/common/src/fileutils.rs b/crates/common/src/fileutils.rs index a12c1cd82e5..9ed5e77afbb 100644 --- a/crates/common/src/fileutils.rs +++ b/crates/common/src/fileutils.rs @@ -9,7 +9,7 @@ pub use windows::{StatStruct, fstat}; #[cfg(not(windows))] pub fn fstat(fd: crate::crt_fd::Borrowed<'_>) -> std::io::Result<StatStruct> { - let mut stat = std::mem::MaybeUninit::uninit(); + let mut stat = core::mem::MaybeUninit::uninit(); unsafe { let ret = libc::fstat(fd.as_raw(), stat.as_mut_ptr()); if ret == -1 { @@ -165,7 +165,7 @@ pub mod windows { } fn file_time_to_time_t_nsec(in_ptr: &FILETIME) -> (libc::time_t, libc::c_int) { - let in_val: i64 = unsafe { std::mem::transmute_copy(in_ptr) }; + let in_val: i64 = unsafe { core::mem::transmute_copy(in_ptr) }; let nsec_out = (in_val % 10_000_000) * 100; // FILETIME is in units of 100 nsec. let time_out = (in_val / 10_000_000) - SECS_BETWEEN_EPOCHS; (time_out, nsec_out as _) @@ -204,7 +204,7 @@ pub mod windows { let st_nlink = info.nNumberOfLinks as i32; let st_ino = if let Some(id_info) = id_info { - let file_id: [u64; 2] = unsafe { std::mem::transmute_copy(&id_info.FileId) }; + let file_id: [u64; 2] = unsafe { core::mem::transmute_copy(&id_info.FileId) }; file_id } else { let ino = ((info.nFileIndexHigh as u64) << 32) + info.nFileIndexLow as u64; @@ -313,7 +313,7 @@ pub mod windows { unsafe { GetProcAddress(module, name.as_bytes_with_nul().as_ptr()) } { Some(unsafe { - std::mem::transmute::< + core::mem::transmute::< unsafe extern "system" fn() -> isize, unsafe extern "system" fn( *const u16, @@ -441,7 +441,7 @@ pub mod windows { // Open a file using std::fs::File and convert to FILE* // Automatically handles path encoding and EINTR retries pub fn fopen(path: &std::path::Path, mode: &str) -> std::io::Result<*mut libc::FILE> { - use std::ffi::CString; + use alloc::ffi::CString; use std::fs::File; // Currently only supports read mode diff --git a/crates/common/src/format.rs b/crates/common/src/format.rs index 447ae575f48..1afee519aef 100644 --- a/crates/common/src/format.rs +++ b/crates/common/src/format.rs @@ -1,4 +1,6 @@ // spell-checker:ignore ddfe +use core::ops::Deref; +use core::{cmp, str::FromStr}; use itertools::{Itertools, PeekingNext}; use malachite_base::num::basic::floats::PrimitiveFloat; use malachite_bigint::{BigInt, Sign}; @@ -7,8 +9,6 @@ use num_traits::FromPrimitive; use num_traits::{Signed, cast::ToPrimitive}; use rustpython_literal::float; use rustpython_literal::format::Case; -use std::ops::Deref; -use std::{cmp, str::FromStr}; use crate::wtf8::{CodePoint, Wtf8, Wtf8Buf}; @@ -598,7 +598,7 @@ impl FormatSpec { (Some(_), _) => Err(FormatSpecError::NotAllowed("Sign")), (_, true) => Err(FormatSpecError::NotAllowed("Alternate form (#)")), (_, _) => match num.to_u32() { - Some(n) if n <= 0x10ffff => Ok(std::char::from_u32(n).unwrap().to_string()), + Some(n) if n <= 0x10ffff => Ok(core::char::from_u32(n).unwrap().to_string()), Some(_) | None => Err(FormatSpecError::CodeNotInRange), }, }, diff --git a/crates/common/src/hash.rs b/crates/common/src/hash.rs index dcf424f7ba9..40c428d89e3 100644 --- a/crates/common/src/hash.rs +++ b/crates/common/src/hash.rs @@ -1,7 +1,7 @@ +use core::hash::{BuildHasher, Hash, Hasher}; use malachite_bigint::BigInt; use num_traits::ToPrimitive; use siphasher::sip::SipHasher24; -use std::hash::{BuildHasher, Hash, Hasher}; pub type PyHash = i64; pub type PyUHash = u64; @@ -19,9 +19,9 @@ pub const INF: PyHash = 314_159; pub const NAN: PyHash = 0; pub const IMAG: PyHash = MULTIPLIER; pub const ALGO: &str = "siphash24"; -pub const HASH_BITS: usize = std::mem::size_of::<PyHash>() * 8; +pub const HASH_BITS: usize = core::mem::size_of::<PyHash>() * 8; // SipHasher24 takes 2 u64s as a seed -pub const SEED_BITS: usize = std::mem::size_of::<u64>() * 2 * 8; +pub const SEED_BITS: usize = core::mem::size_of::<u64>() * 2 * 8; // pub const CUTOFF: usize = 7; @@ -134,7 +134,7 @@ pub fn hash_bigint(value: &BigInt) -> PyHash { Some(i) => mod_int(i), None => (value % MODULUS).to_i64().unwrap_or_else(|| unsafe { // SAFETY: MODULUS < i64::MAX, so value % MODULUS is guaranteed to be in the range of i64 - std::hint::unreachable_unchecked() + core::hint::unreachable_unchecked() }), }; fix_sentinel(ret) diff --git a/crates/common/src/int.rs b/crates/common/src/int.rs index ed09cc01a0a..57696e21fe7 100644 --- a/crates/common/src/int.rs +++ b/crates/common/src/int.rs @@ -7,18 +7,18 @@ pub fn true_div(numerator: &BigInt, denominator: &BigInt) -> f64 { let rational = Rational::from_integers_ref(numerator.into(), denominator.into()); match rational.rounding_into(RoundingMode::Nearest) { // returned value is $t::MAX but still less than the original - (val, std::cmp::Ordering::Less) if val == f64::MAX => f64::INFINITY, + (val, core::cmp::Ordering::Less) if val == f64::MAX => f64::INFINITY, // returned value is $t::MIN but still greater than the original - (val, std::cmp::Ordering::Greater) if val == f64::MIN => f64::NEG_INFINITY, + (val, core::cmp::Ordering::Greater) if val == f64::MIN => f64::NEG_INFINITY, (val, _) => val, } } pub fn float_to_ratio(value: f64) -> Option<(BigInt, BigInt)> { - let sign = match std::cmp::PartialOrd::partial_cmp(&value, &0.0)? { - std::cmp::Ordering::Less => Sign::Minus, - std::cmp::Ordering::Equal => return Some((BigInt::zero(), BigInt::one())), - std::cmp::Ordering::Greater => Sign::Plus, + let sign = match core::cmp::PartialOrd::partial_cmp(&value, &0.0)? { + core::cmp::Ordering::Less => Sign::Minus, + core::cmp::Ordering::Equal => return Some((BigInt::zero(), BigInt::one())), + core::cmp::Ordering::Greater => Sign::Plus, }; Rational::try_from(value).ok().map(|x| { let (numer, denom) = x.into_numerator_and_denominator(); diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index c99ba0286a4..0181562d043 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -2,6 +2,8 @@ #![cfg_attr(all(target_os = "wasi", target_env = "p2"), feature(wasip2))] +extern crate alloc; + #[macro_use] mod macros; pub use macros::*; diff --git a/crates/common/src/linked_list.rs b/crates/common/src/linked_list.rs index 8afc1478e6b..fb2b1260346 100644 --- a/crates/common/src/linked_list.rs +++ b/crates/common/src/linked_list.rs @@ -253,7 +253,7 @@ impl<L: Link> LinkedList<L, L::Target> { // === rustpython additions === pub fn iter(&self) -> impl Iterator<Item = &L::Target> { - std::iter::successors(self.head, |node| unsafe { + core::iter::successors(self.head, |node| unsafe { L::pointers(*node).as_ref().get_next() }) .map(|ptr| unsafe { ptr.as_ref() }) diff --git a/crates/common/src/lock/cell_lock.rs b/crates/common/src/lock/cell_lock.rs index 25a5cfedba1..73d722a8fdb 100644 --- a/crates/common/src/lock/cell_lock.rs +++ b/crates/common/src/lock/cell_lock.rs @@ -1,9 +1,9 @@ // spell-checker:ignore upgradably sharedly +use core::{cell::Cell, num::NonZero}; use lock_api::{ GetThreadId, RawMutex, RawRwLock, RawRwLockDowngrade, RawRwLockRecursive, RawRwLockUpgrade, RawRwLockUpgradeDowngrade, }; -use std::{cell::Cell, num::NonZero}; pub struct RawCellMutex { locked: Cell<bool>, diff --git a/crates/common/src/lock/immutable_mutex.rs b/crates/common/src/lock/immutable_mutex.rs index 81c5c93be71..2013cf1c60d 100644 --- a/crates/common/src/lock/immutable_mutex.rs +++ b/crates/common/src/lock/immutable_mutex.rs @@ -1,7 +1,8 @@ #![allow(clippy::needless_lifetimes)] +use alloc::fmt; +use core::{marker::PhantomData, ops::Deref}; use lock_api::{MutexGuard, RawMutex}; -use std::{fmt, marker::PhantomData, ops::Deref}; /// A mutex guard that has an exclusive lock, but only an immutable reference; useful if you /// need to map a mutex guard with a function that returns an `&T`. Construct using the @@ -22,7 +23,7 @@ impl<'a, R: RawMutex, T: ?Sized> MapImmutable<'a, R, T> for MutexGuard<'a, R, T> { let raw = unsafe { MutexGuard::mutex(&s).raw() }; let data = f(&s) as *const U; - std::mem::forget(s); + core::mem::forget(s); ImmutableMappedMutexGuard { raw, data, @@ -38,7 +39,7 @@ impl<'a, R: RawMutex, T: ?Sized> ImmutableMappedMutexGuard<'a, R, T> { { let raw = s.raw; let data = f(&s) as *const U; - std::mem::forget(s); + core::mem::forget(s); ImmutableMappedMutexGuard { raw, data, diff --git a/crates/common/src/lock/thread_mutex.rs b/crates/common/src/lock/thread_mutex.rs index 2949a3c6c14..67ffc89245d 100644 --- a/crates/common/src/lock/thread_mutex.rs +++ b/crates/common/src/lock/thread_mutex.rs @@ -1,14 +1,14 @@ #![allow(clippy::needless_lifetimes)] -use lock_api::{GetThreadId, GuardNoSend, RawMutex}; -use std::{ +use alloc::fmt; +use core::{ cell::UnsafeCell, - fmt, marker::PhantomData, ops::{Deref, DerefMut}, ptr::NonNull, sync::atomic::{AtomicUsize, Ordering}, }; +use lock_api::{GetThreadId, GuardNoSend, RawMutex}; // based off ReentrantMutex from lock_api @@ -174,7 +174,7 @@ impl<'a, R: RawMutex, G: GetThreadId, T: ?Sized> ThreadMutexGuard<'a, R, G, T> { ) -> MappedThreadMutexGuard<'a, R, G, U> { let data = f(&mut s).into(); let mu = &s.mu.raw; - std::mem::forget(s); + core::mem::forget(s); MappedThreadMutexGuard { mu, data, @@ -188,7 +188,7 @@ impl<'a, R: RawMutex, G: GetThreadId, T: ?Sized> ThreadMutexGuard<'a, R, G, T> { if let Some(data) = f(&mut s) { let data = data.into(); let mu = &s.mu.raw; - std::mem::forget(s); + core::mem::forget(s); Ok(MappedThreadMutexGuard { mu, data, @@ -241,7 +241,7 @@ impl<'a, R: RawMutex, G: GetThreadId, T: ?Sized> MappedThreadMutexGuard<'a, R, G ) -> MappedThreadMutexGuard<'a, R, G, U> { let data = f(&mut s).into(); let mu = s.mu; - std::mem::forget(s); + core::mem::forget(s); MappedThreadMutexGuard { mu, data, @@ -255,7 +255,7 @@ impl<'a, R: RawMutex, G: GetThreadId, T: ?Sized> MappedThreadMutexGuard<'a, R, G if let Some(data) = f(&mut s) { let data = data.into(); let mu = s.mu; - std::mem::forget(s); + core::mem::forget(s); Ok(MappedThreadMutexGuard { mu, data, diff --git a/crates/common/src/os.rs b/crates/common/src/os.rs index e77a81fd94f..3e09a29210a 100644 --- a/crates/common/src/os.rs +++ b/crates/common/src/os.rs @@ -1,7 +1,8 @@ // spell-checker:disable // TODO: we can move more os-specific bindings/interfaces from stdlib::{os, posix, nt} to here -use std::{io, process::ExitCode, str::Utf8Error}; +use core::str::Utf8Error; +use std::{io, process::ExitCode}; /// Convert exit code to std::process::ExitCode /// diff --git a/crates/common/src/rc.rs b/crates/common/src/rc.rs index 40c7cf97a8d..9e4cca228fd 100644 --- a/crates/common/src/rc.rs +++ b/crates/common/src/rc.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "threading"))] -use std::rc::Rc; +use alloc::rc::Rc; #[cfg(feature = "threading")] -use std::sync::Arc; +use alloc::sync::Arc; // type aliases instead of new-types because you can't do `fn method(self: PyRc<Self>)` with a // newtype; requires the arbitrary_self_types unstable feature diff --git a/crates/common/src/str.rs b/crates/common/src/str.rs index 2d867130edd..155012ed21f 100644 --- a/crates/common/src/str.rs +++ b/crates/common/src/str.rs @@ -4,8 +4,8 @@ use crate::format::CharLen; use crate::wtf8::{CodePoint, Wtf8, Wtf8Buf}; use ascii::{AsciiChar, AsciiStr, AsciiString}; use core::fmt; +use core::ops::{Bound, RangeBounds}; use core::sync::atomic::Ordering::Relaxed; -use std::ops::{Bound, RangeBounds}; #[cfg(not(target_arch = "wasm32"))] #[allow(non_camel_case_types)] @@ -22,7 +22,7 @@ pub enum StrKind { Wtf8, } -impl std::ops::BitOr for StrKind { +impl core::ops::BitOr for StrKind { type Output = Self; fn bitor(self, other: Self) -> Self { @@ -128,7 +128,7 @@ impl From<usize> for StrLen { } impl fmt::Debug for StrLen { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let len = self.0.load(Relaxed); if len == usize::MAX { f.write_str("<uncomputed>") @@ -262,7 +262,7 @@ impl StrData { pub fn as_str(&self) -> Option<&str> { self.kind .is_utf8() - .then(|| unsafe { std::str::from_utf8_unchecked(self.data.as_bytes()) }) + .then(|| unsafe { core::str::from_utf8_unchecked(self.data.as_bytes()) }) } pub fn as_ascii(&self) -> Option<&AsciiStr> { @@ -282,7 +282,7 @@ impl StrData { PyKindStr::Ascii(unsafe { AsciiStr::from_ascii_unchecked(self.data.as_bytes()) }) } StrKind::Utf8 => { - PyKindStr::Utf8(unsafe { std::str::from_utf8_unchecked(self.data.as_bytes()) }) + PyKindStr::Utf8(unsafe { core::str::from_utf8_unchecked(self.data.as_bytes()) }) } StrKind::Wtf8 => PyKindStr::Wtf8(&self.data), } @@ -327,8 +327,8 @@ impl StrData { } } -impl std::fmt::Display for StrData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for StrData { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.data.fmt(f) } } @@ -421,7 +421,7 @@ pub fn zfill(bytes: &[u8], width: usize) -> Vec<u8> { }; let mut filled = Vec::new(); filled.extend_from_slice(sign); - filled.extend(std::iter::repeat_n(b'0', width - bytes.len())); + filled.extend(core::iter::repeat_n(b'0', width - bytes.len())); filled.extend_from_slice(s); filled } @@ -465,7 +465,8 @@ impl fmt::Display for UnicodeEscapeCodepoint { } pub mod levenshtein { - use std::{cell::RefCell, thread_local}; + use core::cell::RefCell; + use std::thread_local; pub const MOVE_COST: usize = 2; const CASE_COST: usize = 1; @@ -524,9 +525,9 @@ pub mod levenshtein { } if b_end < a_end { - std::mem::swap(&mut a_bytes, &mut b_bytes); - std::mem::swap(&mut a_begin, &mut b_begin); - std::mem::swap(&mut a_end, &mut b_end); + core::mem::swap(&mut a_bytes, &mut b_bytes); + core::mem::swap(&mut a_begin, &mut b_begin); + core::mem::swap(&mut a_end, &mut b_end); } if (b_end - a_end) * MOVE_COST > max_cost { diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 8df5d9caf6f..5569fa2012b 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -5,12 +5,13 @@ use crate::{ marshal::MarshalError, {OneIndexed, SourceLocation}, }; +use alloc::{collections::BTreeSet, fmt}; use bitflags::bitflags; +use core::{hash, marker::PhantomData, mem, num::NonZeroU8, ops::Deref}; use itertools::Itertools; use malachite_bigint::BigInt; use num_complex::Complex64; use rustpython_wtf8::{Wtf8, Wtf8Buf}; -use std::{collections::BTreeSet, fmt, hash, marker::PhantomData, mem, num::NonZeroU8, ops::Deref}; /// Oparg values for [`Instruction::ConvertValue`]. /// @@ -506,7 +507,7 @@ impl<T: OpArgType> Eq for Arg<T> {} impl<T: OpArgType> fmt::Debug for Arg<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Arg<{}>", std::any::type_name::<T>()) + write!(f, "Arg<{}>", core::any::type_name::<T>()) } } @@ -880,7 +881,7 @@ impl From<Instruction> for u8 { #[inline] fn from(ins: Instruction) -> Self { // SAFETY: there's no padding bits - unsafe { std::mem::transmute::<Instruction, Self>(ins) } + unsafe { core::mem::transmute::<Instruction, Self>(ins) } } } @@ -890,7 +891,7 @@ impl TryFrom<u8> for Instruction { #[inline] fn try_from(value: u8) -> Result<Self, MarshalError> { if value <= u8::from(LAST_INSTRUCTION) { - Ok(unsafe { std::mem::transmute::<u8, Self>(value) }) + Ok(unsafe { core::mem::transmute::<u8, Self>(value) }) } else { Err(MarshalError::InvalidBytecode) } @@ -1027,7 +1028,7 @@ impl PartialEq for ConstantData { (Boolean { value: a }, Boolean { value: b }) => a == b, (Str { value: a }, Str { value: b }) => a == b, (Bytes { value: a }, Bytes { value: b }) => a == b, - (Code { code: a }, Code { code: b }) => std::ptr::eq(a.as_ref(), b.as_ref()), + (Code { code: a }, Code { code: b }) => core::ptr::eq(a.as_ref(), b.as_ref()), (Tuple { elements: a }, Tuple { elements: b }) => a == b, (None, None) => true, (Ellipsis, Ellipsis) => true, @@ -1053,7 +1054,7 @@ impl hash::Hash for ConstantData { Boolean { value } => value.hash(state), Str { value } => value.hash(state), Bytes { value } => value.hash(state), - Code { code } => std::ptr::hash(code.as_ref(), state), + Code { code } => core::ptr::hash(code.as_ref(), state), Tuple { elements } => elements.hash(state), None => {} Ellipsis => {} diff --git a/crates/compiler-core/src/lib.rs b/crates/compiler-core/src/lib.rs index 08cdc0ec21f..11246f6f44c 100644 --- a/crates/compiler-core/src/lib.rs +++ b/crates/compiler-core/src/lib.rs @@ -1,6 +1,8 @@ #![doc(html_logo_url = "https://raw.githubusercontent.com/RustPython/RustPython/main/logo.png")] #![doc(html_root_url = "https://docs.rs/rustpython-compiler-core/")] +extern crate alloc; + pub mod bytecode; pub mod frozen; pub mod marshal; diff --git a/crates/compiler-core/src/marshal.rs b/crates/compiler-core/src/marshal.rs index 39e48071678..b30894ea065 100644 --- a/crates/compiler-core/src/marshal.rs +++ b/crates/compiler-core/src/marshal.rs @@ -1,8 +1,8 @@ use crate::{OneIndexed, SourceLocation, bytecode::*}; +use core::convert::Infallible; use malachite_bigint::{BigInt, Sign}; use num_complex::Complex64; use rustpython_wtf8::Wtf8; -use std::convert::Infallible; pub const FORMAT_VERSION: u32 = 4; @@ -20,8 +20,8 @@ pub enum MarshalError { BadType, } -impl std::fmt::Display for MarshalError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for MarshalError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Eof => f.write_str("unexpected end of data"), Self::InvalidBytecode => f.write_str("invalid bytecode"), @@ -32,15 +32,15 @@ impl std::fmt::Display for MarshalError { } } -impl From<std::str::Utf8Error> for MarshalError { - fn from(_: std::str::Utf8Error) -> Self { +impl From<core::str::Utf8Error> for MarshalError { + fn from(_: core::str::Utf8Error) -> Self { Self::InvalidUtf8 } } -impl std::error::Error for MarshalError {} +impl core::error::Error for MarshalError {} -type Result<T, E = MarshalError> = std::result::Result<T, E>; +type Result<T, E = MarshalError> = core::result::Result<T, E>; #[repr(u8)] enum Type { @@ -119,7 +119,7 @@ pub trait Read { } fn read_str(&mut self, len: u32) -> Result<&str> { - Ok(std::str::from_utf8(self.read_slice(len)?)?) + Ok(core::str::from_utf8(self.read_slice(len)?)?) } fn read_wtf8(&mut self, len: u32) -> Result<&Wtf8> { @@ -147,7 +147,7 @@ pub(crate) trait ReadBorrowed<'a>: Read { fn read_slice_borrow(&mut self, n: u32) -> Result<&'a [u8]>; fn read_str_borrow(&mut self, len: u32) -> Result<&'a str> { - Ok(std::str::from_utf8(self.read_slice_borrow(len)?)?) + Ok(core::str::from_utf8(self.read_slice_borrow(len)?)?) } } diff --git a/crates/compiler-core/src/mode.rs b/crates/compiler-core/src/mode.rs index 35e9e77f590..f2b19d677be 100644 --- a/crates/compiler-core/src/mode.rs +++ b/crates/compiler-core/src/mode.rs @@ -7,7 +7,7 @@ pub enum Mode { BlockExpr, } -impl std::str::FromStr for Mode { +impl core::str::FromStr for Mode { type Err = ModeParseError; // To support `builtins.compile()` `mode` argument @@ -25,8 +25,8 @@ impl std::str::FromStr for Mode { #[derive(Debug)] pub struct ModeParseError; -impl std::fmt::Display for ModeParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for ModeParseError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, r#"mode must be "exec", "eval", or "single""#) } } diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 84e64f3c27f..7fa695c0c71 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -28,8 +28,8 @@ pub struct ParseError { pub source_path: String, } -impl std::fmt::Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl ::core::fmt::Display for ParseError { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { self.error.fmt(f) } } diff --git a/crates/derive-impl/src/compile_bytecode.rs b/crates/derive-impl/src/compile_bytecode.rs index cdcc89b9984..23c90690dad 100644 --- a/crates/derive-impl/src/compile_bytecode.rs +++ b/crates/derive-impl/src/compile_bytecode.rs @@ -58,11 +58,11 @@ pub trait Compiler { source: &str, mode: Mode, module_name: String, - ) -> Result<CodeObject, Box<dyn std::error::Error>>; + ) -> Result<CodeObject, Box<dyn core::error::Error>>; } impl CompilationSource { - fn compile_string<D: std::fmt::Display, F: FnOnce() -> D>( + fn compile_string<D: core::fmt::Display, F: FnOnce() -> D>( &self, source: &str, mode: Mode, diff --git a/crates/derive-impl/src/from_args.rs b/crates/derive-impl/src/from_args.rs index 4633c9b3aac..667f887e81c 100644 --- a/crates/derive-impl/src/from_args.rs +++ b/crates/derive-impl/src/from_args.rs @@ -18,7 +18,7 @@ enum ParameterKind { impl TryFrom<&Ident> for ParameterKind { type Error = (); - fn try_from(ident: &Ident) -> std::result::Result<Self, Self::Error> { + fn try_from(ident: &Ident) -> core::result::Result<Self, Self::Error> { Ok(match ident.to_string().as_str() { "positional" => Self::PositionalOnly, "any" => Self::PositionalOrKeyword, @@ -105,12 +105,12 @@ impl ArgAttribute { impl TryFrom<&Field> for ArgAttribute { type Error = syn::Error; - fn try_from(field: &Field) -> std::result::Result<Self, Self::Error> { + fn try_from(field: &Field) -> core::result::Result<Self, Self::Error> { let mut pyarg_attrs = field .attrs .iter() .filter_map(Self::from_attribute) - .collect::<std::result::Result<Vec<_>, _>>()?; + .collect::<core::result::Result<Vec<_>, _>>()?; if pyarg_attrs.len() >= 2 { bail_span!(field, "Multiple pyarg attributes on field") @@ -234,7 +234,7 @@ pub fn impl_from_args(input: DeriveInput) -> Result<TokenStream> { fn from_args( vm: &::rustpython_vm::VirtualMachine, args: &mut ::rustpython_vm::function::FuncArgs - ) -> ::std::result::Result<Self, ::rustpython_vm::function::ArgumentError> { + ) -> ::core::result::Result<Self, ::rustpython_vm::function::ArgumentError> { Ok(Self { #fields }) } } diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 55f9c769940..06bbc06cfb2 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -4,11 +4,11 @@ use crate::util::{ ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta, format_doc, pyclass_ident_and_attrs, pyexception_ident_and_attrs, text_signature, }; +use core::str::FromStr; use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree}; use quote::{ToTokens, quote, quote_spanned}; use rustpython_doc::DB; use std::collections::{HashMap, HashSet}; -use std::str::FromStr; use syn::{Attribute, Ident, Item, Result, parse_quote, spanned::Spanned}; use syn_ext::ext::*; use syn_ext::types::*; @@ -25,8 +25,8 @@ enum AttrName { Member, } -impl std::fmt::Display for AttrName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for AttrName { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let s = match self { Self::Method => "pymethod", Self::ClassMethod => "pyclassmethod", @@ -44,7 +44,7 @@ impl std::fmt::Display for AttrName { impl FromStr for AttrName { type Err = String; - fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + fn from_str(s: &str) -> core::result::Result<Self, Self::Err> { Ok(match s { "pymethod" => Self::Method, "pyclassmethod" => Self::ClassMethod, @@ -1488,7 +1488,7 @@ impl ItemMeta for SlotItemMeta { fn from_nested<I>(item_ident: Ident, meta_ident: Ident, mut nested: I) -> Result<Self> where - I: std::iter::Iterator<Item = NestedMeta>, + I: core::iter::Iterator<Item = NestedMeta>, { let meta_map = if let Some(nested_meta) = nested.next() { match nested_meta { diff --git a/crates/derive-impl/src/pymodule.rs b/crates/derive-impl/src/pymodule.rs index 2d5ff7cb0c2..3689ac97fd8 100644 --- a/crates/derive-impl/src/pymodule.rs +++ b/crates/derive-impl/src/pymodule.rs @@ -5,10 +5,11 @@ use crate::util::{ ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, format_doc, iter_use_idents, pyclass_ident_and_attrs, text_signature, }; +use core::str::FromStr; use proc_macro2::{Delimiter, Group, TokenStream, TokenTree}; use quote::{ToTokens, quote, quote_spanned}; use rustpython_doc::DB; -use std::{collections::HashSet, str::FromStr}; +use std::collections::HashSet; use syn::{Attribute, Ident, Item, Result, parse_quote, spanned::Spanned}; use syn_ext::ext::*; use syn_ext::types::PunctuatedNestedMeta; @@ -22,8 +23,8 @@ enum AttrName { StructSequence, } -impl std::fmt::Display for AttrName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for AttrName { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let s = match self { Self::Function => "pyfunction", Self::Attr => "pyattr", @@ -38,7 +39,7 @@ impl std::fmt::Display for AttrName { impl FromStr for AttrName { type Err = String; - fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + fn from_str(s: &str) -> core::result::Result<Self, Self::Err> { Ok(match s { "pyfunction" => Self::Function, "pyattr" => Self::Attr, diff --git a/crates/derive-impl/src/pytraverse.rs b/crates/derive-impl/src/pytraverse.rs index c5c4bbd2704..c4ec3823298 100644 --- a/crates/derive-impl/src/pytraverse.rs +++ b/crates/derive-impl/src/pytraverse.rs @@ -37,7 +37,7 @@ fn field_to_traverse_code(field: &Field) -> Result<TokenStream> { .attrs .iter() .filter_map(pytraverse_arg) - .collect::<std::result::Result<Vec<_>, _>>()?; + .collect::<core::result::Result<Vec<_>, _>>()?; let do_trace = if pytraverse_attrs.len() > 1 { bail_span!( field, diff --git a/crates/derive-impl/src/util.rs b/crates/derive-impl/src/util.rs index 379adc65b57..6be1fcdf7ad 100644 --- a/crates/derive-impl/src/util.rs +++ b/crates/derive-impl/src/util.rs @@ -97,7 +97,7 @@ pub(crate) struct ContentItemInner<T> { } pub(crate) trait ContentItem { - type AttrName: std::str::FromStr + std::fmt::Display; + type AttrName: core::str::FromStr + core::fmt::Display; fn inner(&self) -> &ContentItemInner<Self::AttrName>; fn index(&self) -> usize { @@ -125,7 +125,7 @@ impl ItemMetaInner { allowed_names: &[&'static str], ) -> Result<Self> where - I: std::iter::Iterator<Item = NestedMeta>, + I: core::iter::Iterator<Item = NestedMeta>, { let (meta_map, lits) = nested.into_unique_map_and_lits(|path| { if let Some(ident) = path.get_ident() { @@ -243,7 +243,7 @@ impl ItemMetaInner { pub fn _optional_list( &self, key: &str, - ) -> Result<Option<impl std::iter::Iterator<Item = &'_ NestedMeta>>> { + ) -> Result<Option<impl core::iter::Iterator<Item = &'_ NestedMeta>>> { let value = if let Some((_, meta)) = self.meta_map.get(key) { let Meta::List(MetaList { path: _, nested, .. @@ -269,7 +269,7 @@ pub(crate) trait ItemMeta: Sized { fn from_nested<I>(item_ident: Ident, meta_ident: Ident, nested: I) -> Result<Self> where - I: std::iter::Iterator<Item = NestedMeta>, + I: core::iter::Iterator<Item = NestedMeta>, { Ok(Self::from_inner(ItemMetaInner::from_nested( item_ident, @@ -529,7 +529,7 @@ impl ExceptionItemMeta { } } -impl std::ops::Deref for ExceptionItemMeta { +impl core::ops::Deref for ExceptionItemMeta { type Target = ClassItemMeta; fn deref(&self) -> &Self::Target { &self.0 diff --git a/crates/derive/src/lib.rs b/crates/derive/src/lib.rs index 655ad3b4c9e..5a3ff84c63a 100644 --- a/crates/derive/src/lib.rs +++ b/crates/derive/src/lib.rs @@ -274,7 +274,7 @@ impl derive_impl::Compiler for Compiler { source: &str, mode: rustpython_compiler::Mode, module_name: String, - ) -> Result<rustpython_compiler::CodeObject, Box<dyn std::error::Error>> { + ) -> Result<rustpython_compiler::CodeObject, Box<dyn core::error::Error>> { use rustpython_compiler::{CompileOpts, compile}; Ok(compile(source, mode, &module_name, CompileOpts::default())?) } diff --git a/crates/jit/src/lib.rs b/crates/jit/src/lib.rs index 65ef87a62f6..1e278617661 100644 --- a/crates/jit/src/lib.rs +++ b/crates/jit/src/lib.rs @@ -1,11 +1,14 @@ mod instructions; +extern crate alloc; + +use alloc::fmt; +use core::mem::ManuallyDrop; use cranelift::prelude::*; use cranelift_jit::{JITBuilder, JITModule}; use cranelift_module::{FuncId, Linkage, Module, ModuleError}; use instructions::FunctionCompiler; use rustpython_compiler_core::bytecode; -use std::{fmt, mem::ManuallyDrop}; #[derive(Debug, thiserror::Error)] #[non_exhaustive] diff --git a/crates/literal/src/escape.rs b/crates/literal/src/escape.rs index 6bdd94e9860..72ceaf60d5b 100644 --- a/crates/literal/src/escape.rs +++ b/crates/literal/src/escape.rs @@ -55,9 +55,9 @@ pub unsafe trait Escape { /// # Safety /// /// This string must only contain printable characters. - unsafe fn write_source(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result; - fn write_body_slow(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result; - fn write_body(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { + unsafe fn write_source(&self, formatter: &mut impl std::fmt::Write) -> core::fmt::Result; + fn write_body_slow(&self, formatter: &mut impl std::fmt::Write) -> core::fmt::Result; + fn write_body(&self, formatter: &mut impl std::fmt::Write) -> core::fmt::Result { if self.changed() { self.write_body_slow(formatter) } else { @@ -117,7 +117,7 @@ impl<'a> UnicodeEscape<'a> { pub struct StrRepr<'r, 'a>(&'r UnicodeEscape<'a>); impl StrRepr<'_, '_> { - pub fn write(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { + pub fn write(&self, formatter: &mut impl std::fmt::Write) -> core::fmt::Result { let quote = self.0.layout().quote.to_char(); formatter.write_char(quote)?; self.0.write_body(formatter)?; @@ -131,8 +131,8 @@ impl StrRepr<'_, '_> { } } -impl std::fmt::Display for StrRepr<'_, '_> { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for StrRepr<'_, '_> { + fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.write(formatter) } } @@ -217,7 +217,7 @@ impl UnicodeEscape<'_> { ch: CodePoint, quote: Quote, formatter: &mut impl std::fmt::Write, - ) -> std::fmt::Result { + ) -> core::fmt::Result { let Some(ch) = ch.to_char() else { return write!(formatter, "\\u{:04x}", ch.to_u32()); }; @@ -260,7 +260,7 @@ unsafe impl Escape for UnicodeEscape<'_> { &self.layout } - unsafe fn write_source(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { + unsafe fn write_source(&self, formatter: &mut impl std::fmt::Write) -> core::fmt::Result { formatter.write_str(unsafe { // SAFETY: this function must be called only when source is printable characters (i.e. no surrogates) std::str::from_utf8_unchecked(self.source.as_bytes()) @@ -268,7 +268,7 @@ unsafe impl Escape for UnicodeEscape<'_> { } #[cold] - fn write_body_slow(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { + fn write_body_slow(&self, formatter: &mut impl std::fmt::Write) -> core::fmt::Result { for ch in self.source.code_points() { Self::write_char(ch, self.layout().quote, formatter)?; } @@ -378,7 +378,7 @@ impl AsciiEscape<'_> { } } - fn write_char(ch: u8, quote: Quote, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { + fn write_char(ch: u8, quote: Quote, formatter: &mut impl std::fmt::Write) -> core::fmt::Result { match ch { b'\t' => formatter.write_str("\\t"), b'\n' => formatter.write_str("\\n"), @@ -404,7 +404,7 @@ unsafe impl Escape for AsciiEscape<'_> { &self.layout } - unsafe fn write_source(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { + unsafe fn write_source(&self, formatter: &mut impl std::fmt::Write) -> core::fmt::Result { formatter.write_str(unsafe { // SAFETY: this function must be called only when source is printable ascii characters std::str::from_utf8_unchecked(self.source) @@ -412,7 +412,7 @@ unsafe impl Escape for AsciiEscape<'_> { } #[cold] - fn write_body_slow(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { + fn write_body_slow(&self, formatter: &mut impl std::fmt::Write) -> core::fmt::Result { for ch in self.source { Self::write_char(*ch, self.layout().quote, formatter)?; } @@ -423,7 +423,7 @@ unsafe impl Escape for AsciiEscape<'_> { pub struct BytesRepr<'r, 'a>(&'r AsciiEscape<'a>); impl BytesRepr<'_, '_> { - pub fn write(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { + pub fn write(&self, formatter: &mut impl std::fmt::Write) -> core::fmt::Result { let quote = self.0.layout().quote.to_char(); formatter.write_char('b')?; formatter.write_char(quote)?; @@ -438,8 +438,8 @@ impl BytesRepr<'_, '_> { } } -impl std::fmt::Display for BytesRepr<'_, '_> { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for BytesRepr<'_, '_> { + fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.write(formatter) } } diff --git a/crates/literal/src/float.rs b/crates/literal/src/float.rs index e2bc54a8f1b..4d0d65cbb34 100644 --- a/crates/literal/src/float.rs +++ b/crates/literal/src/float.rs @@ -55,7 +55,7 @@ pub fn format_fixed(precision: usize, magnitude: f64, case: Case, alternate_form match magnitude { magnitude if magnitude.is_finite() => { let point = decimal_point_or_empty(precision, alternate_form); - let precision = std::cmp::min(precision, u16::MAX as usize); + let precision = core::cmp::min(precision, u16::MAX as usize); format!("{magnitude:.precision$}{point}") } magnitude if magnitude.is_nan() => format_nan(case), diff --git a/crates/sre_engine/src/engine.rs b/crates/sre_engine/src/engine.rs index 9cc2e4788a5..f1f25a2d920 100644 --- a/crates/sre_engine/src/engine.rs +++ b/crates/sre_engine/src/engine.rs @@ -6,8 +6,8 @@ use crate::string::{ }; use super::{MAXREPEAT, SreAtCode, SreCatCode, SreInfo, SreOpcode, StrDrive, StringCursor}; +use core::{convert::TryFrom, ptr::null}; use optional::Optioned; -use std::{convert::TryFrom, ptr::null}; #[derive(Debug, Clone, Copy)] pub struct Request<'a, S> { @@ -27,8 +27,8 @@ impl<'a, S: StrDrive> Request<'a, S> { pattern_codes: &'a [u32], match_all: bool, ) -> Self { - let end = std::cmp::min(end, string.count()); - let start = std::cmp::min(start, end); + let end = core::cmp::min(end, string.count()); + let start = core::cmp::min(start, end); Self { string, @@ -1332,7 +1332,7 @@ fn _count<S: StrDrive>( ctx: &mut MatchContext, max_count: usize, ) -> usize { - let max_count = std::cmp::min(max_count, ctx.remaining_chars(req)); + let max_count = core::cmp::min(max_count, ctx.remaining_chars(req)); let end = ctx.cursor.position + max_count; let opcode = SreOpcode::try_from(ctx.peek_code(req, 0)).unwrap(); diff --git a/crates/sre_engine/src/string.rs b/crates/sre_engine/src/string.rs index 0d3325b6a1d..489819bfb3e 100644 --- a/crates/sre_engine/src/string.rs +++ b/crates/sre_engine/src/string.rs @@ -9,7 +9,7 @@ pub struct StringCursor { impl Default for StringCursor { fn default() -> Self { Self { - ptr: std::ptr::null(), + ptr: core::ptr::null(), position: 0, } } diff --git a/crates/stdlib/src/array.rs b/crates/stdlib/src/array.rs index b51bc02d3fb..b7a6fbd8b4f 100644 --- a/crates/stdlib/src/array.rs +++ b/crates/stdlib/src/array.rs @@ -68,11 +68,12 @@ mod array { }, }, }; + use alloc::fmt; + use core::cmp::Ordering; use itertools::Itertools; use num_traits::ToPrimitive; use rustpython_common::wtf8::{CodePoint, Wtf8, Wtf8Buf}; - use std::{cmp::Ordering, fmt, os::raw}; - + use std::os::raw; macro_rules! def_array_enum { ($(($n:ident, $t:ty, $c:literal, $scode:literal)),*$(,)?) => { #[derive(Debug, Clone)] @@ -104,14 +105,14 @@ mod array { const fn itemsize_of_typecode(c: char) -> Option<usize> { match c { - $($c => Some(std::mem::size_of::<$t>()),)* + $($c => Some(core::mem::size_of::<$t>()),)* _ => None, } } const fn itemsize(&self) -> usize { match self { - $(ArrayContentType::$n(_) => std::mem::size_of::<$t>(),)* + $(ArrayContentType::$n(_) => core::mem::size_of::<$t>(),)* } } @@ -201,10 +202,10 @@ mod array { if v.is_empty() { // safe because every configuration of bytes for the types we // support are valid - let b = std::mem::ManuallyDrop::new(b); + let b = core::mem::ManuallyDrop::new(b); let ptr = b.as_ptr() as *mut $t; - let len = b.len() / std::mem::size_of::<$t>(); - let capacity = b.capacity() / std::mem::size_of::<$t>(); + let len = b.len() / core::mem::size_of::<$t>(); + let capacity = b.capacity() / core::mem::size_of::<$t>(); *v = unsafe { Vec::from_raw_parts(ptr, len, capacity) }; } else { self.frombytes(&b); @@ -220,8 +221,8 @@ mod array { // support are valid if b.len() > 0 { let ptr = b.as_ptr() as *const $t; - let ptr_len = b.len() / std::mem::size_of::<$t>(); - let slice = unsafe { std::slice::from_raw_parts(ptr, ptr_len) }; + let ptr_len = b.len() / core::mem::size_of::<$t>(); + let slice = unsafe { core::slice::from_raw_parts(ptr, ptr_len) }; v.extend_from_slice(slice); } })* @@ -249,8 +250,8 @@ mod array { $(ArrayContentType::$n(v) => { // safe because we're just reading memory as bytes let ptr = v.as_ptr() as *const u8; - let ptr_len = v.len() * std::mem::size_of::<$t>(); - unsafe { std::slice::from_raw_parts(ptr, ptr_len) } + let ptr_len = v.len() * core::mem::size_of::<$t>(); + unsafe { core::slice::from_raw_parts(ptr, ptr_len) } })* } } @@ -260,8 +261,8 @@ mod array { $(ArrayContentType::$n(v) => { // safe because we're just reading memory as bytes let ptr = v.as_ptr() as *mut u8; - let ptr_len = v.len() * std::mem::size_of::<$t>(); - unsafe { std::slice::from_raw_parts_mut(ptr, ptr_len) } + let ptr_len = v.len() * core::mem::size_of::<$t>(); + unsafe { core::slice::from_raw_parts_mut(ptr, ptr_len) } })* } } @@ -785,18 +786,18 @@ mod array { if item_size == 2 { // safe because every configuration of bytes for the types we support are valid let utf16 = unsafe { - std::slice::from_raw_parts( + core::slice::from_raw_parts( bytes.as_ptr() as *const u16, - bytes.len() / std::mem::size_of::<u16>(), + bytes.len() / core::mem::size_of::<u16>(), ) }; Ok(Wtf8Buf::from_wide(utf16)) } else { // safe because every configuration of bytes for the types we support are valid let chars = unsafe { - std::slice::from_raw_parts( + core::slice::from_raw_parts( bytes.as_ptr() as *const u32, - bytes.len() / std::mem::size_of::<u32>(), + bytes.len() / core::mem::size_of::<u32>(), ) }; chars @@ -1516,7 +1517,7 @@ mod array { impl MachineFormatCode { fn from_typecode(code: char) -> Option<Self> { - use std::mem::size_of; + use core::mem::size_of; let signed = code.is_ascii_uppercase(); let big_endian = cfg!(target_endian = "big"); let int_size = match code { @@ -1590,7 +1591,7 @@ mod array { macro_rules! chunk_to_obj { ($BYTE:ident, $TY:ty, $BIG_ENDIAN:ident) => {{ - let b = <[u8; ::std::mem::size_of::<$TY>()]>::try_from($BYTE).unwrap(); + let b = <[u8; ::core::mem::size_of::<$TY>()]>::try_from($BYTE).unwrap(); if $BIG_ENDIAN { <$TY>::from_be_bytes(b) } else { @@ -1601,7 +1602,7 @@ mod array { chunk_to_obj!($BYTE, $TY, $BIG_ENDIAN).to_pyobject($VM) }; ($VM:ident, $BYTE:ident, $SIGNED_TY:ty, $UNSIGNED_TY:ty, $SIGNED:ident, $BIG_ENDIAN:ident) => {{ - let b = <[u8; ::std::mem::size_of::<$SIGNED_TY>()]>::try_from($BYTE).unwrap(); + let b = <[u8; ::core::mem::size_of::<$SIGNED_TY>()]>::try_from($BYTE).unwrap(); match ($SIGNED, $BIG_ENDIAN) { (false, false) => <$UNSIGNED_TY>::from_le_bytes(b).to_pyobject($VM), (false, true) => <$UNSIGNED_TY>::from_be_bytes(b).to_pyobject($VM), diff --git a/crates/stdlib/src/binascii.rs b/crates/stdlib/src/binascii.rs index a2316d3c204..671d1d9e253 100644 --- a/crates/stdlib/src/binascii.rs +++ b/crates/stdlib/src/binascii.rs @@ -359,7 +359,7 @@ mod decl { } _ => unsafe { // quad_pos is only assigned in this match statement to constants - std::hint::unreachable_unchecked() + core::hint::unreachable_unchecked() }, } } diff --git a/crates/stdlib/src/bz2.rs b/crates/stdlib/src/bz2.rs index a2a40953cff..93142e92a68 100644 --- a/crates/stdlib/src/bz2.rs +++ b/crates/stdlib/src/bz2.rs @@ -15,9 +15,10 @@ mod _bz2 { object::PyResult, types::Constructor, }; + use alloc::fmt; use bzip2::{Decompress, Status, write::BzEncoder}; use rustpython_vm::convert::ToPyException; - use std::{fmt, io::Write}; + use std::io::Write; const BUFSIZ: usize = 8192; diff --git a/crates/stdlib/src/cmath.rs b/crates/stdlib/src/cmath.rs index e5d1d55a578..7f975e41719 100644 --- a/crates/stdlib/src/cmath.rs +++ b/crates/stdlib/src/cmath.rs @@ -11,7 +11,7 @@ mod cmath { // Constants #[pyattr] - use std::f64::consts::{E as e, PI as pi, TAU as tau}; + use core::f64::consts::{E as e, PI as pi, TAU as tau}; #[pyattr(name = "inf")] const INF: f64 = f64::INFINITY; #[pyattr(name = "nan")] @@ -93,7 +93,7 @@ mod cmath { z.log( base.into_option() .map(|base| base.re) - .unwrap_or(std::f64::consts::E), + .unwrap_or(core::f64::consts::E), ) } diff --git a/crates/stdlib/src/compression.rs b/crates/stdlib/src/compression.rs index 7f4e3432eab..a857b4e53de 100644 --- a/crates/stdlib/src/compression.rs +++ b/crates/stdlib/src/compression.rs @@ -107,7 +107,7 @@ impl<'a> Chunker<'a> { pub fn advance(&mut self, consumed: usize) { self.data1 = &self.data1[consumed..]; if self.data1.is_empty() { - self.data1 = std::mem::take(&mut self.data2); + self.data1 = core::mem::take(&mut self.data2); } } } @@ -140,7 +140,7 @@ pub fn _decompress_chunks<D: Decompressor>( let chunk = data.chunk(); let flush = calc_flush(chunk.len() == data.len()); loop { - let additional = std::cmp::min(bufsize, max_length - buf.capacity()); + let additional = core::cmp::min(bufsize, max_length - buf.capacity()); if additional == 0 { return Ok((buf, false)); } diff --git a/crates/stdlib/src/contextvars.rs b/crates/stdlib/src/contextvars.rs index f88ce398c1c..731f5d11e0b 100644 --- a/crates/stdlib/src/contextvars.rs +++ b/crates/stdlib/src/contextvars.rs @@ -1,6 +1,6 @@ use crate::vm::{PyRef, VirtualMachine, builtins::PyModule, class::StaticType}; use _contextvars::PyContext; -use std::cell::RefCell; +use core::cell::RefCell; pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { let module = _contextvars::make_module(vm); @@ -31,13 +31,13 @@ mod _contextvars { protocol::{PyMappingMethods, PySequenceMethods}, types::{AsMapping, AsSequence, Constructor, Hashable, Representable}, }; - use crossbeam_utils::atomic::AtomicCell; - use indexmap::IndexMap; - use std::sync::LazyLock; - use std::{ + use core::{ cell::{Cell, RefCell, UnsafeCell}, sync::atomic::Ordering, }; + use crossbeam_utils::atomic::AtomicCell; + use indexmap::IndexMap; + use std::sync::LazyLock; // TODO: Real hamt implementation type Hamt = IndexMap<PyRef<ContextVar>, PyObjectRef, ahash::RandomState>; @@ -90,11 +90,11 @@ mod _contextvars { } } - fn borrow_vars(&self) -> impl std::ops::Deref<Target = Hamt> + '_ { + fn borrow_vars(&self) -> impl core::ops::Deref<Target = Hamt> + '_ { self.inner.vars.hamt.borrow() } - fn borrow_vars_mut(&self) -> impl std::ops::DerefMut<Target = Hamt> + '_ { + fn borrow_vars_mut(&self) -> impl core::ops::DerefMut<Target = Hamt> + '_ { self.inner.vars.hamt.borrow_mut() } @@ -293,13 +293,13 @@ mod _contextvars { #[pytraverse(skip)] cached: AtomicCell<Option<ContextVarCache>>, #[pytraverse(skip)] - cached_id: std::sync::atomic::AtomicUsize, // cached_tsid in CPython + cached_id: core::sync::atomic::AtomicUsize, // cached_tsid in CPython #[pytraverse(skip)] hash: UnsafeCell<PyHash>, } - impl std::fmt::Debug for ContextVar { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + impl core::fmt::Debug for ContextVar { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("ContextVar").finish() } } @@ -308,7 +308,7 @@ mod _contextvars { impl PartialEq for ContextVar { fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) + core::ptr::eq(self, other) } } impl Eq for ContextVar {} @@ -512,9 +512,9 @@ mod _contextvars { } } - impl std::hash::Hash for ContextVar { + impl core::hash::Hash for ContextVar { #[inline] - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + fn hash<H: core::hash::Hasher>(&self, state: &mut H) { unsafe { *self.hash.get() }.hash(state) } } diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index a62594a9f1b..4f6cbd76828 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -12,12 +12,13 @@ mod _csv { raise_if_stop, types::{Constructor, IterNext, Iterable, SelfIter}, }; + use alloc::fmt; use csv_core::Terminator; use itertools::{self, Itertools}; use parking_lot::Mutex; use rustpython_vm::match_class; + use std::collections::HashMap; use std::sync::LazyLock; - use std::{collections::HashMap, fmt}; #[pyattr] const QUOTE_MINIMAL: i32 = QuoteStyle::Minimal as i32; @@ -1006,7 +1007,7 @@ mod _csv { return Err(new_csv_error(vm, "filed too long to read".to_string())); } prev_end = end; - let s = std::str::from_utf8(&buffer[range.clone()]) + let s = core::str::from_utf8(&buffer[range.clone()]) // not sure if this is possible - the input was all strings .map_err(|_e| vm.new_unicode_decode_error("csv not utf8"))?; // Rustpython TODO! @@ -1116,7 +1117,7 @@ mod _csv { loop { handle_res!(writer.terminator(&mut buffer[buffer_offset..])); } - let s = std::str::from_utf8(&buffer[..buffer_offset]) + let s = core::str::from_utf8(&buffer[..buffer_offset]) .map_err(|_| vm.new_unicode_decode_error("csv not utf8"))?; self.write.call((s,), vm) diff --git a/crates/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs index f45c9909c6f..eba5643b866 100644 --- a/crates/stdlib/src/faulthandler.rs +++ b/crates/stdlib/src/faulthandler.rs @@ -7,13 +7,13 @@ mod decl { PyObjectRef, PyResult, VirtualMachine, builtins::PyFloat, frame::Frame, function::OptionalArg, py_io::Write, }; + use alloc::sync::Arc; + use core::sync::atomic::{AtomicBool, AtomicI32, Ordering}; + use core::time::Duration; use parking_lot::{Condvar, Mutex}; #[cfg(any(unix, windows))] use rustpython_common::os::{get_errno, set_errno}; - use std::sync::Arc; - use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; use std::thread; - use std::time::Duration; /// fault_handler_t #[cfg(unix)] @@ -40,7 +40,7 @@ mod decl { enabled: false, name, // SAFETY: sigaction is a C struct that can be zero-initialized - previous: unsafe { std::mem::zeroed() }, + previous: unsafe { core::mem::zeroed() }, } } } @@ -144,7 +144,8 @@ mod decl { static mut FRAME_SNAPSHOTS: [FrameSnapshot; MAX_SNAPSHOT_FRAMES] = [FrameSnapshot::EMPTY; MAX_SNAPSHOT_FRAMES]; #[cfg(any(unix, windows))] - static SNAPSHOT_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + static SNAPSHOT_COUNT: core::sync::atomic::AtomicUsize = + core::sync::atomic::AtomicUsize::new(0); // Signal-safe output functions @@ -240,7 +241,7 @@ mod decl { } let thread_id = current_thread_id(); // Use appropriate width based on platform pointer size - dump_hexadecimal(fd, thread_id, std::mem::size_of::<usize>() * 2); + dump_hexadecimal(fd, thread_id, core::mem::size_of::<usize>() * 2); puts(fd, " (most recent call first):\n"); } @@ -429,7 +430,7 @@ mod decl { } handler.enabled = false; unsafe { - libc::sigaction(handler.signum, &handler.previous, std::ptr::null_mut()); + libc::sigaction(handler.signum, &handler.previous, core::ptr::null_mut()); } } @@ -549,7 +550,7 @@ mod decl { continue; } - let mut action: libc::sigaction = std::mem::zeroed(); + let mut action: libc::sigaction = core::mem::zeroed(); action.sa_sigaction = faulthandler_fatal_error as libc::sighandler_t; // SA_NODEFER flag action.sa_flags = libc::SA_NODEFER; @@ -1051,8 +1052,8 @@ mod decl { #[cfg(not(target_arch = "wasm32"))] unsafe { suppress_crash_report(); - let ptr: *const i32 = std::ptr::null(); - std::ptr::read_volatile(ptr); + let ptr: *const i32 = core::ptr::null(); + core::ptr::read_volatile(ptr); } } @@ -1132,7 +1133,7 @@ mod decl { panic!("Fatal Python error: in new thread"); }); // Wait a bit for the thread to panic - std::thread::sleep(std::time::Duration::from_secs(1)); + std::thread::sleep(core::time::Duration::from_secs(1)); } } @@ -1203,7 +1204,7 @@ mod decl { suppress_crash_report(); unsafe { - RaiseException(args.code, args.flags, 0, std::ptr::null()); + RaiseException(args.code, args.flags, 0, core::ptr::null()); } } } diff --git a/crates/stdlib/src/fcntl.rs b/crates/stdlib/src/fcntl.rs index dc6a0b8171e..822faeeedaa 100644 --- a/crates/stdlib/src/fcntl.rs +++ b/crates/stdlib/src/fcntl.rs @@ -173,7 +173,7 @@ mod fcntl { }; } - let mut l: libc::flock = unsafe { std::mem::zeroed() }; + let mut l: libc::flock = unsafe { core::mem::zeroed() }; l.l_type = if cmd == libc::LOCK_UN { try_into_l_type!(libc::F_UNLCK) } else if (cmd & libc::LOCK_SH) != 0 { diff --git a/crates/stdlib/src/grp.rs b/crates/stdlib/src/grp.rs index 4664d5fc575..9f7e4195509 100644 --- a/crates/stdlib/src/grp.rs +++ b/crates/stdlib/src/grp.rs @@ -10,8 +10,8 @@ mod grp { exceptions, types::PyStructSequence, }; + use core::ptr::NonNull; use nix::unistd; - use std::ptr::NonNull; #[pystruct_sequence_data] struct GroupData { @@ -30,7 +30,7 @@ mod grp { impl GroupData { fn from_unistd_group(group: unistd::Group, vm: &VirtualMachine) -> Self { - let cstr_lossy = |s: std::ffi::CString| { + let cstr_lossy = |s: alloc::ffi::CString| { s.into_string() .unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned()) }; diff --git a/crates/stdlib/src/hashlib.rs b/crates/stdlib/src/hashlib.rs index bfde58f43f7..2da47ceb740 100644 --- a/crates/stdlib/src/hashlib.rs +++ b/crates/stdlib/src/hashlib.rs @@ -97,8 +97,8 @@ pub mod _hashlib { pub ctx: PyRwLock<HashWrapper>, } - impl std::fmt::Debug for PyHasher { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + impl core::fmt::Debug for PyHasher { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "HASH {}", self.name) } } @@ -170,8 +170,8 @@ pub mod _hashlib { ctx: PyRwLock<HashXofWrapper>, } - impl std::fmt::Debug for PyHasherXof { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + impl core::fmt::Debug for PyHasherXof { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "HASHXOF {}", self.name) } } diff --git a/crates/stdlib/src/json.rs b/crates/stdlib/src/json.rs index eb6ed3a5f64..a3fd7972126 100644 --- a/crates/stdlib/src/json.rs +++ b/crates/stdlib/src/json.rs @@ -12,9 +12,9 @@ mod _json { protocol::PyIterReturn, types::{Callable, Constructor}, }; + use core::str::FromStr; use malachite_bigint::BigInt; use rustpython_common::wtf8::Wtf8Buf; - use std::str::FromStr; #[pyattr(name = "make_scanner")] #[pyclass(name = "Scanner", traverse)] @@ -216,7 +216,7 @@ mod _json { let mut buf = Vec::<u8>::with_capacity(s.len() + 2); machinery::write_json_string(s, ascii_only, &mut buf) // SAFETY: writing to a vec can't fail - .unwrap_or_else(|_| unsafe { std::hint::unreachable_unchecked() }); + .unwrap_or_else(|_| unsafe { core::hint::unreachable_unchecked() }); // SAFETY: we only output valid utf8 from write_json_string unsafe { String::from_utf8_unchecked(buf) } } diff --git a/crates/stdlib/src/lib.rs b/crates/stdlib/src/lib.rs index 4b463e09c73..6b7796c8bad 100644 --- a/crates/stdlib/src/lib.rs +++ b/crates/stdlib/src/lib.rs @@ -6,6 +6,7 @@ #[macro_use] extern crate rustpython_derive; +extern crate alloc; pub mod array; mod binascii; @@ -103,7 +104,7 @@ use rustpython_common as common; use rustpython_vm as vm; use crate::vm::{builtins, stdlib::StdlibInitFunc}; -use std::borrow::Cow; +use alloc::borrow::Cow; pub fn get_module_inits() -> impl Iterator<Item = (Cow<'static, str>, StdlibInitFunc)> { macro_rules! modules { diff --git a/crates/stdlib/src/locale.rs b/crates/stdlib/src/locale.rs index 6cca8b9123b..c65f861d208 100644 --- a/crates/stdlib/src/locale.rs +++ b/crates/stdlib/src/locale.rs @@ -41,16 +41,14 @@ use libc::localeconv; #[pymodule] mod _locale { + use alloc::ffi::CString; + use core::{ffi::CStr, ptr}; use rustpython_vm::{ PyObjectRef, PyResult, VirtualMachine, builtins::{PyDictRef, PyIntRef, PyListRef, PyStrRef, PyTypeRef}, convert::ToPyException, function::OptionalArg, }; - use std::{ - ffi::{CStr, CString}, - ptr, - }; #[cfg(all( unix, diff --git a/crates/stdlib/src/lzma.rs b/crates/stdlib/src/lzma.rs index 855a5eae562..b18ac3ee69a 100644 --- a/crates/stdlib/src/lzma.rs +++ b/crates/stdlib/src/lzma.rs @@ -8,6 +8,7 @@ mod _lzma { CompressFlushKind, CompressState, CompressStatusKind, Compressor, DecompressArgs, DecompressError, DecompressState, DecompressStatus, Decompressor, }; + use alloc::fmt; #[pyattr] use lzma_sys::{ LZMA_CHECK_CRC32 as CHECK_CRC32, LZMA_CHECK_CRC64 as CHECK_CRC64, @@ -38,7 +39,6 @@ mod _lzma { use rustpython_vm::function::ArgBytesLike; use rustpython_vm::types::Constructor; use rustpython_vm::{Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; - use std::fmt; use xz2::stream::{Action, Check, Error, Filters, LzmaOptions, Status, Stream}; #[cfg(windows)] diff --git a/crates/stdlib/src/math.rs b/crates/stdlib/src/math.rs index 62b0ef73ad3..6e139530804 100644 --- a/crates/stdlib/src/math.rs +++ b/crates/stdlib/src/math.rs @@ -10,15 +10,15 @@ mod math { function::{ArgIndex, ArgIntoFloat, ArgIterable, Either, OptionalArg, PosArgs}, identifier, }; + use core::cmp::Ordering; use itertools::Itertools; use malachite_bigint::BigInt; use num_traits::{One, Signed, ToPrimitive, Zero}; use rustpython_common::{float_ops, int::true_div}; - use std::cmp::Ordering; // Constants #[pyattr] - use std::f64::consts::{E as e, PI as pi, TAU as tau}; + use core::f64::consts::{E as e, PI as pi, TAU as tau}; use super::pymath_error_to_exception; #[pyattr(name = "inf")] @@ -136,7 +136,7 @@ mod math { #[pyfunction] fn log(x: PyObjectRef, base: OptionalArg<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<f64> { - let base = base.map(|b| *b).unwrap_or(std::f64::consts::E); + let base = base.map(|b| *b).unwrap_or(core::f64::consts::E); if base.is_sign_negative() { return Err(vm.new_value_error("math domain error")); } @@ -359,9 +359,9 @@ mod math { .iter() .copied() .map(|x| (x / scale).powi(2)) - .chain(std::iter::once(-norm * norm)) + .chain(core::iter::once(-norm * norm)) // Pairwise summation of floats gives less rounding error than a naive sum. - .tree_reduce(std::ops::Add::add) + .tree_reduce(core::ops::Add::add) .expect("expected at least 1 element"); norm = norm + correction / (2.0 * norm); } @@ -424,12 +424,12 @@ mod math { #[pyfunction] fn degrees(x: ArgIntoFloat) -> f64 { - *x * (180.0 / std::f64::consts::PI) + *x * (180.0 / core::f64::consts::PI) } #[pyfunction] fn radians(x: ArgIntoFloat) -> f64 { - *x * (std::f64::consts::PI / 180.0) + *x * (core::f64::consts::PI / 180.0) } // Hyperbolic functions: @@ -684,7 +684,7 @@ mod math { for j in 0..partials.len() { let mut y: f64 = partials[j]; if x.abs() < y.abs() { - std::mem::swap(&mut x, &mut y); + core::mem::swap(&mut x, &mut y); } // Rounded `x+y` is stored in `hi` with round-off stored in // `lo`. Together `hi+lo` are exactly equal to `x+y`. diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index f1e2c2a039d..b520eb2a1a7 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -21,11 +21,11 @@ mod mmap { sliceable::{SaturatedSlice, SequenceIndex, SequenceIndexOp}, types::{AsBuffer, AsMapping, AsSequence, Constructor, Representable}, }; + use core::ops::{Deref, DerefMut}; use crossbeam_utils::atomic::AtomicCell; use memmap2::{Mmap, MmapMut, MmapOptions}; use num_traits::Signed; use std::io::{self, Write}; - use std::ops::{Deref, DerefMut}; #[cfg(unix)] use nix::{sys::stat::fstat, unistd}; @@ -1057,7 +1057,7 @@ mod mmap { // 3. Replace the old mmap let old_size = self.size.load(); - let copy_size = std::cmp::min(old_size, newsize); + let copy_size = core::cmp::min(old_size, newsize); // Create new anonymous mmap let mut new_mmap_opts = MmapOptions::new(); diff --git a/crates/stdlib/src/opcode.rs b/crates/stdlib/src/opcode.rs index c355b59df91..bd4b9aa750a 100644 --- a/crates/stdlib/src/opcode.rs +++ b/crates/stdlib/src/opcode.rs @@ -8,7 +8,7 @@ mod opcode { bytecode::Instruction, match_class, }; - use std::ops::Deref; + use core::ops::Deref; struct Opcode(Instruction); diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index d352d15a614..38103a9ab05 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -522,10 +522,10 @@ mod _ssl { // Thread-local storage for VirtualMachine pointer during handshake // SNI callback is only called during handshake which is synchronous thread_local! { - static HANDSHAKE_VM: std::cell::Cell<Option<*const VirtualMachine>> = const { std::cell::Cell::new(None) }; + static HANDSHAKE_VM: core::cell::Cell<Option<*const VirtualMachine>> = const { core::cell::Cell::new(None) }; // SSL pointer during handshake - needed because connection lock is held during handshake // and callbacks may need to access SSL without acquiring the lock - static HANDSHAKE_SSL_PTR: std::cell::Cell<Option<*mut sys::SSL>> = const { std::cell::Cell::new(None) }; + static HANDSHAKE_SSL_PTR: core::cell::Cell<Option<*mut sys::SSL>> = const { core::cell::Cell::new(None) }; } // RAII guard to set/clear thread-local handshake context @@ -1896,7 +1896,7 @@ mod _ssl { ))); return Err(openssl::error::ErrorStack::get()); } - let len = std::cmp::min(pw.len(), buf.len()); + let len = core::cmp::min(pw.len(), buf.len()); buf[..len].copy_from_slice(&pw[..len]); Ok(len) } @@ -2714,7 +2714,7 @@ mod _ssl { // Use thread-local SSL pointer during handshake to avoid deadlock let ssl_ptr = get_ssl_ptr_for_context_change(&self.connection); unsafe { - let mut out: *const libc::c_uchar = std::ptr::null(); + let mut out: *const libc::c_uchar = core::ptr::null(); let mut outlen: libc::c_uint = 0; sys::SSL_get0_alpn_selected(ssl_ptr, &mut out, &mut outlen); diff --git a/crates/stdlib/src/overlapped.rs b/crates/stdlib/src/overlapped.rs index d8f14baf35e..1c74ee271b9 100644 --- a/crates/stdlib/src/overlapped.rs +++ b/crates/stdlib/src/overlapped.rs @@ -35,7 +35,7 @@ mod _overlapped { #[pyattr] const INVALID_HANDLE_VALUE: isize = - unsafe { std::mem::transmute(windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE) }; + unsafe { core::mem::transmute(windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE) }; #[pyattr] const NULL: isize = 0; @@ -57,8 +57,8 @@ mod _overlapped { unsafe impl Sync for OverlappedInner {} unsafe impl Send for OverlappedInner {} - impl std::fmt::Debug for Overlapped { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + impl core::fmt::Debug for Overlapped { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let zelf = self.inner.lock(); f.debug_struct("Overlapped") // .field("overlapped", &(self.overlapped as *const _ as usize)) @@ -98,8 +98,8 @@ mod _overlapped { address_length: libc::c_int, } - impl std::fmt::Debug for OverlappedReadFrom { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + impl core::fmt::Debug for OverlappedReadFrom { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("OverlappedReadFrom") .field("result", &self.result) .field("allocated_buffer", &self.allocated_buffer) @@ -119,8 +119,8 @@ mod _overlapped { address_length: libc::c_int, } - impl std::fmt::Debug for OverlappedReadFromInto { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + impl core::fmt::Debug for OverlappedReadFromInto { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("OverlappedReadFromInto") .field("result", &self.result) .field("user_buffer", &self.user_buffer) @@ -226,7 +226,7 @@ mod _overlapped { } #[cfg(target_pointer_width = "32")] - let size = std::cmp::min(size, std::isize::MAX as _); + let size = core::cmp::min(size, std::isize::MAX as _); let buf = vec![0u8; std::cmp::max(size, 1) as usize]; let buf = vm.ctx.new_bytes(buf); @@ -272,10 +272,10 @@ mod _overlapped { if event == INVALID_HANDLE_VALUE { event = unsafe { windows_sys::Win32::System::Threading::CreateEventA( - std::ptr::null(), + core::ptr::null(), Foundation::TRUE, Foundation::FALSE, - std::ptr::null(), + core::ptr::null(), ) as isize }; if event == NULL { @@ -378,11 +378,11 @@ mod _overlapped { let name = widestring::WideCString::from_str(&name).unwrap(); name.as_ptr() } - None => std::ptr::null(), + None => core::ptr::null(), }; let event = unsafe { windows_sys::Win32::System::Threading::CreateEventW( - std::ptr::null(), + core::ptr::null(), manual_reset as _, initial_state as _, name, diff --git a/crates/stdlib/src/posixshmem.rs b/crates/stdlib/src/posixshmem.rs index 2957f16792c..53bf372532d 100644 --- a/crates/stdlib/src/posixshmem.rs +++ b/crates/stdlib/src/posixshmem.rs @@ -4,7 +4,7 @@ pub(crate) use _posixshmem::make_module; #[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))] #[pymodule] mod _posixshmem { - use std::ffi::CString; + use alloc::ffi::CString; use crate::{ common::os::errno_io_error, diff --git a/crates/stdlib/src/posixsubprocess.rs b/crates/stdlib/src/posixsubprocess.rs index 4b895d2102b..1ebf6619c24 100644 --- a/crates/stdlib/src/posixsubprocess.rs +++ b/crates/stdlib/src/posixsubprocess.rs @@ -13,15 +13,15 @@ use nix::{ unistd::{self, Pid}, }; use std::{ - convert::Infallible as Never, - ffi::{CStr, CString}, io::prelude::*, - marker::PhantomData, - ops::Deref, os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd}, }; use unistd::{Gid, Uid}; +use alloc::ffi::CString; + +use core::{convert::Infallible as Never, ffi::CStr, marker::PhantomData, ops::Deref}; + pub(crate) use _posixsubprocess::make_module; #[pymodule] @@ -87,7 +87,7 @@ impl<'a, T: AsRef<CStr>> FromIterator<&'a T> for CharPtrVec<'a> { let vec = iter .into_iter() .map(|x| x.as_ref().as_ptr()) - .chain(std::iter::once(std::ptr::null())) + .chain(core::iter::once(core::ptr::null())) .collect(); Self { vec, diff --git a/crates/stdlib/src/resource.rs b/crates/stdlib/src/resource.rs index 052f45e0cad..e6df75e4b01 100644 --- a/crates/stdlib/src/resource.rs +++ b/crates/stdlib/src/resource.rs @@ -9,7 +9,8 @@ mod resource { convert::{ToPyException, ToPyObject}, types::PyStructSequence, }; - use std::{io, mem}; + use core::mem; + use std::io; cfg_if::cfg_if! { if #[cfg(target_os = "android")] { diff --git a/crates/stdlib/src/scproxy.rs b/crates/stdlib/src/scproxy.rs index 1974e7814ae..40267579029 100644 --- a/crates/stdlib/src/scproxy.rs +++ b/crates/stdlib/src/scproxy.rs @@ -22,7 +22,7 @@ mod _scproxy { fn proxy_dict() -> Option<CFDictionary<CFString, CFType>> { // Py_BEGIN_ALLOW_THREADS - let proxy_dict = unsafe { SCDynamicStoreCopyProxies(std::ptr::null()) }; + let proxy_dict = unsafe { SCDynamicStoreCopyProxies(core::ptr::null()) }; // Py_END_ALLOW_THREADS if proxy_dict.is_null() { None diff --git a/crates/stdlib/src/select.rs b/crates/stdlib/src/select.rs index 5639a66d2cc..3c2f5e63c7c 100644 --- a/crates/stdlib/src/select.rs +++ b/crates/stdlib/src/select.rs @@ -4,7 +4,8 @@ use crate::vm::{ PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::PyListRef, builtins::PyModule, }; -use std::{io, mem}; +use core::mem; +use std::io; pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { #[cfg(windows)] @@ -158,7 +159,7 @@ impl FdSet { pub fn new() -> Self { // it's just ints, and all the code that's actually // interacting with it is in C, so it's safe to zero - let mut fdset = std::mem::MaybeUninit::zeroed(); + let mut fdset = core::mem::MaybeUninit::zeroed(); unsafe { platform::FD_ZERO(fdset.as_mut_ptr()) }; Self(fdset) } @@ -191,7 +192,7 @@ pub fn select( ) -> io::Result<i32> { let timeout = match timeout { Some(tv) => tv as *mut timeval, - None => std::ptr::null_mut(), + None => core::ptr::null_mut(), }; let ret = unsafe { platform::select( @@ -336,12 +337,10 @@ mod decl { function::OptionalArg, stdlib::io::Fildes, }; + use core::{convert::TryFrom, time::Duration}; use libc::pollfd; use num_traits::{Signed, ToPrimitive}; - use std::{ - convert::TryFrom, - time::{Duration, Instant}, - }; + use std::time::Instant; #[derive(Default)] pub(super) struct TimeoutArg<const MILLIS: bool>(pub Option<Duration>); @@ -554,8 +553,8 @@ mod decl { stdlib::io::Fildes, types::Constructor, }; + use core::ops::Deref; use rustix::event::epoll::{self, EventData, EventFlags}; - use std::ops::Deref; use std::os::fd::{AsRawFd, IntoRawFd, OwnedFd}; use std::time::Instant; diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index 91c7b1201f1..71bc8e9f170 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -22,15 +22,19 @@ mod _socket { types::{Constructor, DefaultConstructor, Initializer, Representable}, utils::ToCString, }; + use core::{ + mem::MaybeUninit, + net::{Ipv4Addr, Ipv6Addr, SocketAddr}, + time::Duration, + }; use crossbeam_utils::atomic::AtomicCell; use num_traits::ToPrimitive; use socket2::Socket; use std::{ ffi, io::{self, Read, Write}, - mem::MaybeUninit, - net::{self, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, ToSocketAddrs}, - time::{Duration, Instant}, + net::{self, Shutdown, ToSocketAddrs}, + time::Instant, }; #[cfg(unix)] @@ -934,7 +938,7 @@ mod _socket { sock: PyRwLock<Option<Socket>>, } - const _: () = assert!(std::mem::size_of::<Option<Socket>>() == std::mem::size_of::<Socket>()); + const _: () = assert!(core::mem::size_of::<Option<Socket>>() == core::mem::size_of::<Socket>()); impl Default for PySocket { fn default() -> Self { @@ -1494,7 +1498,7 @@ mod _socket { Some(errcode!(ENOTSOCK)) | Some(errcode!(EBADF)) ) => { - std::mem::forget(sock); + core::mem::forget(sock); return Err(e.into()); } _ => {} @@ -2052,7 +2056,7 @@ mod _socket { cmsgs: &[(i32, i32, ArgBytesLike)], vm: &VirtualMachine, ) -> PyResult<Vec<u8>> { - use std::{mem, ptr}; + use core::{mem, ptr}; if cmsgs.is_empty() { return Ok(vec![]); @@ -2210,7 +2214,7 @@ mod _socket { let buflen = buflen.unwrap_or(0); if buflen == 0 { let mut flag: libc::c_int = 0; - let mut flagsize = std::mem::size_of::<libc::c_int>() as _; + let mut flagsize = core::mem::size_of::<libc::c_int>() as _; let ret = unsafe { c::getsockopt( fd as _, @@ -2270,11 +2274,11 @@ mod _socket { level, name, val as *const i32 as *const _, - std::mem::size_of::<i32>() as _, + core::mem::size_of::<i32>() as _, ) }, (None, OptionalArg::Present(optlen)) => unsafe { - c::setsockopt(fd as _, level, name, std::ptr::null(), optlen as _) + c::setsockopt(fd as _, level, name, core::ptr::null(), optlen as _) }, _ => { return Err(vm @@ -2456,7 +2460,7 @@ mod _socket { } impl ToSocketAddrs for Address { - type Iter = std::vec::IntoIter<SocketAddr>; + type Iter = alloc::vec::IntoIter<SocketAddr>; fn to_socket_addrs(&self) -> io::Result<Self::Iter> { (self.host.as_str(), self.port).to_socket_addrs() } @@ -2616,7 +2620,7 @@ mod _socket { } fn cstr_opt_as_ptr(x: &OptionalArg<ffi::CString>) -> *const libc::c_char { - x.as_ref().map_or_else(std::ptr::null, |s| s.as_ptr()) + x.as_ref().map_or_else(core::ptr::null, |s| s.as_ptr()) } #[pyfunction] @@ -2807,7 +2811,7 @@ mod _socket { vm.state .codec_registry .encode_text(s.to_owned(), "idna", None, vm)?; - let host_str = std::str::from_utf8(encoded.as_bytes()) + let host_str = core::str::from_utf8(encoded.as_bytes()) .map_err(|_| vm.new_runtime_error("idna output is not utf8".to_owned()))?; Some(host_str.to_owned()) } @@ -3183,7 +3187,7 @@ mod _socket { .state .codec_registry .encode_text(pyname, "idna", None, vm)?; - let name = std::str::from_utf8(name.as_bytes()) + let name = core::str::from_utf8(name.as_bytes()) .map_err(|_| vm.new_runtime_error("idna output is not utf8"))?; let mut res = dns_lookup::getaddrinfo(Some(name), None, Some(hints)) .map_err(|e| convert_socket_error(vm, e, SocketError::GaiError))?; @@ -3339,7 +3343,7 @@ mod _socket { #[pyfunction] fn dup(x: PyObjectRef, vm: &VirtualMachine) -> Result<RawSocket, IoOrPyException> { let sock = get_raw_sock(x, vm)?; - let sock = std::mem::ManuallyDrop::new(sock_from_raw(sock, vm)?); + let sock = core::mem::ManuallyDrop::new(sock_from_raw(sock, vm)?); let newsock = sock.try_clone()?; let fd = into_sock_fileno(newsock); #[cfg(windows)] diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index 103a827e99a..3a82787cd8f 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -844,7 +844,7 @@ mod _sqlite { } impl Debug for Connection { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { write!(f, "Sqlite3 Connection") } } @@ -2583,7 +2583,7 @@ mod _sqlite { } impl Debug for Statement { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { write!( f, "{} Statement", diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 16449e2d019..b90176a62fa 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -52,14 +52,12 @@ mod _ssl { use super::error::{ PySSLEOFError, PySSLError, create_ssl_want_read_error, create_ssl_want_write_error, }; - use std::{ - collections::HashMap, - sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, - }, - time::{Duration, SystemTime}, + use alloc::sync::Arc; + use core::{ + sync::atomic::{AtomicUsize, Ordering}, + time::Duration, }; + use std::{collections::HashMap, time::SystemTime}; // Rustls imports use parking_lot::{Mutex as ParkingMutex, RwLock as ParkingRwLock}; @@ -3124,7 +3122,7 @@ mod _ssl { // When server_hostname=None, use an IP address to suppress SNI // no hostname = no SNI extension ServerName::IpAddress( - std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)).into(), + core::net::IpAddr::V4(core::net::Ipv4Addr::new(127, 0, 0, 1)).into(), ) }; @@ -3385,7 +3383,7 @@ mod _ssl { let mut written = 0; while written < data.len() { - let chunk_end = std::cmp::min(written + CHUNK_SIZE, data.len()); + let chunk_end = core::cmp::min(written + CHUNK_SIZE, data.len()); let chunk = &data[written..chunk_end]; // Write chunk to TLS layer @@ -4176,8 +4174,8 @@ mod _ssl { #[pygetset] fn id(&self, vm: &VirtualMachine) -> PyBytesRef { // Return session ID (hash of session data for uniqueness) + use core::hash::{Hash, Hasher}; use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new(); self.session_data.hash(&mut hasher); @@ -4487,7 +4485,7 @@ mod _ssl { let mut result = Vec::new(); - let mut crl_context: *const CRL_CONTEXT = std::ptr::null(); + let mut crl_context: *const CRL_CONTEXT = core::ptr::null(); loop { crl_context = unsafe { CertEnumCRLsInStore(store, crl_context) }; if crl_context.is_null() { @@ -4587,8 +4585,8 @@ mod _ssl { // Implement Hashable trait for PySSLCertificate impl Hashable for PySSLCertificate { fn hash(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyHash> { + use core::hash::{Hash, Hasher}; use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new(); zelf.der_bytes.hash(&mut hasher); diff --git a/crates/stdlib/src/ssl/cert.rs b/crates/stdlib/src/ssl/cert.rs index b3cb7d6c14e..cd39972cf41 100644 --- a/crates/stdlib/src/ssl/cert.rs +++ b/crates/stdlib/src/ssl/cert.rs @@ -9,6 +9,7 @@ //! - Building and verifying certificate chains //! - Loading certificates from files, directories, and bytes +use alloc::sync::Arc; use chrono::{DateTime, Utc}; use parking_lot::RwLock as ParkingRwLock; use rustls::{ @@ -19,7 +20,6 @@ use rustls::{ }; use rustpython_vm::{PyObjectRef, PyResult, VirtualMachine}; use std::collections::HashSet; -use std::sync::Arc; use x509_parser::prelude::*; use super::compat::{VERIFY_X509_PARTIAL_CHAIN, VERIFY_X509_STRICT}; @@ -51,8 +51,9 @@ const ALL_SIGNATURE_SCHEMES: &[SignatureScheme] = &[ /// operations, reducing code duplication and ensuring uniform error messages /// across the codebase. mod cert_error { + use alloc::sync::Arc; + use core::fmt::{Debug, Display}; use std::io; - use std::sync::Arc; /// Create InvalidData error with formatted message pub fn invalid_data(msg: impl Into<String>) -> io::Error { @@ -67,11 +68,11 @@ mod cert_error { invalid_data(format!("no start line: {context}")) } - pub fn parse_failed(e: impl std::fmt::Display) -> io::Error { + pub fn parse_failed(e: impl Display) -> io::Error { invalid_data(format!("Failed to parse PEM certificate: {e}")) } - pub fn parse_failed_debug(e: impl std::fmt::Debug) -> io::Error { + pub fn parse_failed_debug(e: impl Debug) -> io::Error { invalid_data(format!("Failed to parse PEM certificate: {e:?}")) } @@ -88,7 +89,7 @@ mod cert_error { invalid_data(format!("not enough data: {context}")) } - pub fn parse_failed(e: impl std::fmt::Display) -> io::Error { + pub fn parse_failed(e: impl Display) -> io::Error { invalid_data(format!("Failed to parse DER certificate: {e}")) } } @@ -101,15 +102,15 @@ mod cert_error { invalid_data(format!("No private key found in {context}")) } - pub fn parse_failed(e: impl std::fmt::Display) -> io::Error { + pub fn parse_failed(e: impl Display) -> io::Error { invalid_data(format!("Failed to parse private key: {e}")) } - pub fn parse_encrypted_failed(e: impl std::fmt::Display) -> io::Error { + pub fn parse_encrypted_failed(e: impl Display) -> io::Error { invalid_data(format!("Failed to parse encrypted private key: {e}")) } - pub fn decrypt_failed(e: impl std::fmt::Display) -> io::Error { + pub fn decrypt_failed(e: impl Display) -> io::Error { io::Error::other(format!( "Failed to decrypt private key (wrong password?): {e}", )) @@ -383,7 +384,7 @@ pub fn cert_der_to_dict_helper(vm: &VirtualMachine, cert_der: &[u8]) -> PyResult s.to_string() } else { let value_bytes = attr.attr_value().data; - match std::str::from_utf8(value_bytes) { + match core::str::from_utf8(value_bytes) { Ok(s) => s.to_string(), Err(_) => String::from_utf8_lossy(value_bytes).into_owned(), } @@ -1126,7 +1127,7 @@ pub(super) fn load_cert_chain_from_file( cert_path: &str, key_path: &str, password: Option<&str>, -) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), Box<dyn std::error::Error>> { +) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), Box<dyn core::error::Error>> { // Load certificate file - preserve io::Error for errno let cert_contents = std::fs::read(cert_path)?; @@ -1727,13 +1728,13 @@ fn verify_ip_address( cert: &X509Certificate<'_>, expected_ip: &rustls::pki_types::IpAddr, ) -> Result<(), rustls::Error> { - use std::net::IpAddr; + use core::net::IpAddr; use x509_parser::extensions::GeneralName; // Convert rustls IpAddr to std::net::IpAddr for comparison let expected_std_ip: IpAddr = match expected_ip { - rustls::pki_types::IpAddr::V4(octets) => IpAddr::V4(std::net::Ipv4Addr::from(*octets)), - rustls::pki_types::IpAddr::V6(octets) => IpAddr::V6(std::net::Ipv6Addr::from(*octets)), + rustls::pki_types::IpAddr::V4(octets) => IpAddr::V4(core::net::Ipv4Addr::from(*octets)), + rustls::pki_types::IpAddr::V6(octets) => IpAddr::V6(core::net::Ipv6Addr::from(*octets)), }; // Check Subject Alternative Names for IP addresses @@ -1745,7 +1746,7 @@ fn verify_ip_address( 4 => { // IPv4 if let Ok(octets) = <[u8; 4]>::try_from(*cert_ip_bytes) { - IpAddr::V4(std::net::Ipv4Addr::from(octets)) + IpAddr::V4(core::net::Ipv4Addr::from(octets)) } else { continue; } @@ -1753,7 +1754,7 @@ fn verify_ip_address( 16 => { // IPv6 if let Ok(octets) = <[u8; 16]>::try_from(*cert_ip_bytes) { - IpAddr::V6(std::net::Ipv6Addr::from(octets)) + IpAddr::V6(core::net::Ipv6Addr::from(octets)) } else { continue; } diff --git a/crates/stdlib/src/ssl/compat.rs b/crates/stdlib/src/ssl/compat.rs index fa12855e242..2168fcfc91f 100644 --- a/crates/stdlib/src/ssl/compat.rs +++ b/crates/stdlib/src/ssl/compat.rs @@ -13,6 +13,7 @@ mod ssl_data; use crate::socket::{SelectKind, timeout_error_msg}; use crate::vm::VirtualMachine; +use alloc::sync::Arc; use parking_lot::RwLock as ParkingRwLock; use rustls::RootCertStore; use rustls::client::ClientConfig; @@ -28,7 +29,7 @@ use rustpython_vm::convert::IntoPyException; use rustpython_vm::function::ArgBytesLike; use rustpython_vm::{AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject}; use std::io::Read; -use std::sync::{Arc, Once}; +use std::sync::Once; // Import PySSLSocket from parent module use super::_ssl::PySSLSocket; diff --git a/crates/stdlib/src/syslog.rs b/crates/stdlib/src/syslog.rs index d0ed3f60949..b52d1415692 100644 --- a/crates/stdlib/src/syslog.rs +++ b/crates/stdlib/src/syslog.rs @@ -11,7 +11,8 @@ mod syslog { function::{OptionalArg, OptionalOption}, utils::ToCString, }; - use std::{ffi::CStr, os::raw::c_char}; + use core::ffi::CStr; + use std::os::raw::c_char; #[pyattr] use libc::{ @@ -50,7 +51,7 @@ mod syslog { fn as_ptr(&self) -> *const c_char { match self { Self::Explicit(cstr) => cstr.as_ptr(), - Self::Implicit => std::ptr::null(), + Self::Implicit => core::ptr::null(), } } } diff --git a/crates/stdlib/src/tkinter.rs b/crates/stdlib/src/tkinter.rs index 687458b193b..49dcdc5f84f 100644 --- a/crates/stdlib/src/tkinter.rs +++ b/crates/stdlib/src/tkinter.rs @@ -59,8 +59,8 @@ mod _tkinter { value: *mut tk_sys::Tcl_Obj, } - impl std::fmt::Debug for TclObject { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + impl core::fmt::Debug for TclObject { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "TclObject") } } @@ -107,8 +107,8 @@ mod _tkinter { unsafe impl Send for TkApp {} unsafe impl Sync for TkApp {} - impl std::fmt::Debug for TkApp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + impl core::fmt::Debug for TkApp { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "TkApp") } } diff --git a/crates/stdlib/src/zlib.rs b/crates/stdlib/src/zlib.rs index 9ca94939f78..632543c5c64 100644 --- a/crates/stdlib/src/zlib.rs +++ b/crates/stdlib/src/zlib.rs @@ -39,7 +39,7 @@ mod zlib { #[pyattr(name = "ZLIB_RUNTIME_VERSION")] #[pyattr] const ZLIB_VERSION: &str = unsafe { - match std::ffi::CStr::from_ptr(libz_sys::zlibVersion()).to_str() { + match core::ffi::CStr::from_ptr(libz_sys::zlibVersion()).to_str() { Ok(s) => s, Err(_) => unreachable!(), } @@ -322,7 +322,7 @@ mod zlib { }; let inner = &mut *self.inner.lock(); - let data = std::mem::replace(&mut inner.unconsumed_tail, vm.ctx.empty_bytes.clone()); + let data = core::mem::replace(&mut inner.unconsumed_tail, vm.ctx.empty_bytes.clone()); let (ret, _) = Self::decompress_inner(inner, &data, length, None, true, vm)?; diff --git a/crates/venvlauncher/src/main.rs b/crates/venvlauncher/src/main.rs index aaf584dfa87..fe147ce7ff3 100644 --- a/crates/venvlauncher/src/main.rs +++ b/crates/venvlauncher/src/main.rs @@ -22,7 +22,7 @@ fn main() -> ExitCode { } } -fn run() -> Result<u32, Box<dyn std::error::Error>> { +fn run() -> Result<u32, Box<dyn core::error::Error>> { // 1. Get own executable path let exe_path = env::current_exe()?; let exe_name = exe_path @@ -72,7 +72,7 @@ fn run() -> Result<u32, Box<dyn std::error::Error>> { } /// Parse the `home=` value from pyvenv.cfg -fn read_home(cfg_path: &Path) -> Result<String, Box<dyn std::error::Error>> { +fn read_home(cfg_path: &Path) -> Result<String, Box<dyn core::error::Error>> { let content = fs::read_to_string(cfg_path)?; for line in content.lines() { @@ -95,7 +95,7 @@ fn read_home(cfg_path: &Path) -> Result<String, Box<dyn std::error::Error>> { } /// Launch the Python process and wait for it to complete -fn launch_process(exe: &Path, args: &[String]) -> Result<u32, Box<dyn std::error::Error>> { +fn launch_process(exe: &Path, args: &[String]) -> Result<u32, Box<dyn core::error::Error>> { use std::process::Command; let status = Command::new(exe).args(args).status()?; diff --git a/crates/vm/src/anystr.rs b/crates/vm/src/anystr.rs index ef6d24c100e..79b62a58abf 100644 --- a/crates/vm/src/anystr.rs +++ b/crates/vm/src/anystr.rs @@ -6,6 +6,8 @@ use crate::{ }; use num_traits::{cast::ToPrimitive, sign::Signed}; +use core::ops::Range; + #[derive(FromArgs)] pub struct SplitArgs<T: TryFromObject> { #[pyarg(any, default)] @@ -43,7 +45,7 @@ pub struct StartsEndsWithArgs { } impl StartsEndsWithArgs { - pub fn get_value(self, len: usize) -> (PyObjectRef, Option<std::ops::Range<usize>>) { + pub fn get_value(self, len: usize) -> (PyObjectRef, Option<Range<usize>>) { let range = if self.start.is_some() || self.end.is_some() { Some(adjust_indices(self.start, self.end, len)) } else { @@ -56,7 +58,7 @@ impl StartsEndsWithArgs { pub fn prepare<S, F>(self, s: &S, len: usize, substr: F) -> Option<(PyObjectRef, &S)> where S: ?Sized + AnyStr, - F: Fn(&S, std::ops::Range<usize>) -> &S, + F: Fn(&S, Range<usize>) -> &S, { let (affix, range) = self.get_value(len); let substr = if let Some(range) = range { @@ -83,11 +85,7 @@ fn saturate_to_isize(py_int: PyIntRef) -> isize { } // help get optional string indices -pub fn adjust_indices( - start: Option<PyIntRef>, - end: Option<PyIntRef>, - len: usize, -) -> std::ops::Range<usize> { +pub fn adjust_indices(start: Option<PyIntRef>, end: Option<PyIntRef>, len: usize) -> Range<usize> { let mut start = start.map_or(0, saturate_to_isize); let mut end = end.map_or(len as isize, saturate_to_isize); if end > len as isize { @@ -111,7 +109,7 @@ pub trait StringRange { fn is_normal(&self) -> bool; } -impl StringRange for std::ops::Range<usize> { +impl StringRange for Range<usize> { fn is_normal(&self) -> bool { self.start <= self.end } @@ -144,9 +142,9 @@ pub trait AnyStr { fn to_container(&self) -> Self::Container; fn as_bytes(&self) -> &[u8]; fn elements(&self) -> impl Iterator<Item = Self::Char>; - fn get_bytes(&self, range: std::ops::Range<usize>) -> &Self; + fn get_bytes(&self, range: Range<usize>) -> &Self; // FIXME: get_chars is expensive for str - fn get_chars(&self, range: std::ops::Range<usize>) -> &Self; + fn get_chars(&self, range: Range<usize>) -> &Self; fn bytes_len(&self) -> usize; // NOTE: str::chars().count() consumes the O(n) time. But pystr::char_len does cache. // So using chars_len directly is too expensive and the below method shouldn't be implemented. @@ -254,7 +252,7 @@ pub trait AnyStr { } #[inline] - fn py_find<F>(&self, needle: &Self, range: std::ops::Range<usize>, find: F) -> Option<usize> + fn py_find<F>(&self, needle: &Self, range: Range<usize>, find: F) -> Option<usize> where F: Fn(&Self, &Self) -> Option<usize>, { @@ -268,7 +266,7 @@ pub trait AnyStr { } #[inline] - fn py_count<F>(&self, needle: &Self, range: std::ops::Range<usize>, count: F) -> usize + fn py_count<F>(&self, needle: &Self, range: Range<usize>, count: F) -> usize where F: Fn(&Self, &Self) -> usize, { @@ -283,9 +281,9 @@ pub trait AnyStr { let mut u = Self::Container::with_capacity( (left + right) * fillchar.bytes_len() + self.bytes_len(), ); - u.extend(std::iter::repeat_n(fillchar, left)); + u.extend(core::iter::repeat_n(fillchar, left)); u.push_str(self); - u.extend(std::iter::repeat_n(fillchar, right)); + u.extend(core::iter::repeat_n(fillchar, right)); u } @@ -305,7 +303,7 @@ pub trait AnyStr { fn py_join( &self, - mut iter: impl std::iter::Iterator<Item = PyResult<impl AnyStrWrapper<Self> + TryFromObject>>, + mut iter: impl core::iter::Iterator<Item = PyResult<impl AnyStrWrapper<Self> + TryFromObject>>, ) -> PyResult<Self::Container> { let mut joined = if let Some(elem) = iter.next() { elem?.as_ref().unwrap().to_container() @@ -328,7 +326,7 @@ pub trait AnyStr { ) -> PyResult<(Self::Container, bool, Self::Container)> where F: Fn() -> S, - S: std::iter::Iterator<Item = &'a Self>, + S: core::iter::Iterator<Item = &'a Self>, { if sub.is_empty() { return Err(vm.new_value_error("empty separator")); diff --git a/crates/vm/src/buffer.rs b/crates/vm/src/buffer.rs index 5c67f87521d..3d5e48015ea 100644 --- a/crates/vm/src/buffer.rs +++ b/crates/vm/src/buffer.rs @@ -5,11 +5,13 @@ use crate::{ convert::ToPyObject, function::{ArgBytesLike, ArgIntoBool, ArgIntoFloat}, }; +use alloc::fmt; +use core::{iter::Peekable, mem}; use half::f16; use itertools::Itertools; use malachite_bigint::BigInt; use num_traits::{PrimInt, ToPrimitive}; -use std::{fmt, iter::Peekable, mem, os::raw}; +use std::os::raw; type PackFunc = fn(&VirtualMachine, PyObjectRef, &mut [u8]) -> PyResult<()>; type UnpackFunc = fn(&VirtualMachine, &[u8]) -> PyObjectRef; @@ -545,7 +547,7 @@ macro_rules! make_pack_prim_int { } #[inline] fn unpack_int<E: ByteOrder>(data: &[u8]) -> Self { - let mut x = [0; std::mem::size_of::<$T>()]; + let mut x = [0; core::mem::size_of::<$T>()]; x.copy_from_slice(data); E::convert(<$T>::from_ne_bytes(x)) } @@ -681,7 +683,7 @@ fn pack_pascal(vm: &VirtualMachine, arg: PyObjectRef, buf: &mut [u8]) -> PyResul } let b = ArgBytesLike::try_from_object(vm, arg)?; b.with_ref(|data| { - let string_length = std::cmp::min(std::cmp::min(data.len(), 255), buf.len() - 1); + let string_length = core::cmp::min(core::cmp::min(data.len(), 255), buf.len() - 1); buf[0] = string_length as u8; write_string(&mut buf[1..], data); }); @@ -689,7 +691,7 @@ fn pack_pascal(vm: &VirtualMachine, arg: PyObjectRef, buf: &mut [u8]) -> PyResul } fn write_string(buf: &mut [u8], data: &[u8]) { - let len_from_data = std::cmp::min(data.len(), buf.len()); + let len_from_data = core::cmp::min(data.len(), buf.len()); buf[..len_from_data].copy_from_slice(&data[..len_from_data]); for byte in &mut buf[len_from_data..] { *byte = 0 @@ -708,7 +710,7 @@ fn unpack_pascal(vm: &VirtualMachine, data: &[u8]) -> PyObjectRef { return vm.ctx.new_bytes(vec![]).into(); } }; - let len = std::cmp::min(len as usize, data.len()); + let len = core::cmp::min(len as usize, data.len()); vm.ctx.new_bytes(data[..len].to_vec()).into() } diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index cfd1f136d14..3dabdbae717 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -8,9 +8,9 @@ use crate::{ protocol::PyNumberMethods, types::{AsNumber, Constructor, Representable}, }; +use core::fmt::{Debug, Formatter}; use malachite_bigint::Sign; use num_traits::Zero; -use std::fmt::{Debug, Formatter}; impl ToPyObject for bool { fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef { @@ -90,7 +90,7 @@ impl PyObjectRef { pub struct PyBool(pub PyInt); impl Debug for PyBool { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { let value = !self.0.as_bigint().is_zero(); write!(f, "PyBool({})", value) } diff --git a/crates/vm/src/builtins/builtin_func.rs b/crates/vm/src/builtins/builtin_func.rs index 2b569375b28..422f922df94 100644 --- a/crates/vm/src/builtins/builtin_func.rs +++ b/crates/vm/src/builtins/builtin_func.rs @@ -7,7 +7,7 @@ use crate::{ function::{FuncArgs, PyComparisonValue, PyMethodDef, PyMethodFlags, PyNativeFn}, types::{Callable, Comparable, PyComparisonOp, Representable}, }; -use std::fmt; +use alloc::fmt; // PyCFunctionObject in CPython #[pyclass(name = "builtin_function_or_method", module = false)] @@ -212,7 +212,7 @@ impl Comparable for PyNativeMethod { (None, None) => true, _ => false, }; - let eq = eq && std::ptr::eq(zelf.func.value, other.func.value); + let eq = eq && core::ptr::eq(zelf.func.value, other.func.value); Ok(eq.into()) } else { Ok(PyComparisonValue::NotImplemented) diff --git a/crates/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs index c5861befb73..212e4604ec9 100644 --- a/crates/vm/src/builtins/bytearray.rs +++ b/crates/vm/src/builtins/bytearray.rs @@ -37,7 +37,7 @@ use crate::{ }, }; use bstr::ByteSlice; -use std::mem::size_of; +use core::mem::size_of; #[pyclass(module = false, name = "bytearray", unhashable = true)] #[derive(Debug, Default)] @@ -687,7 +687,7 @@ impl Initializer for PyByteArray { fn init(zelf: PyRef<Self>, options: Self::Args, vm: &VirtualMachine) -> PyResult<()> { // First unpack bytearray and *then* get a lock to set it. let mut inner = options.get_bytearray_inner(vm)?; - std::mem::swap(&mut *zelf.inner_mut(), &mut inner); + core::mem::swap(&mut *zelf.inner_mut(), &mut inner); Ok(()) } } diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index 0c67cd7bf24..b3feac8ac97 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -29,8 +29,8 @@ use crate::{ }, }; use bstr::ByteSlice; +use core::{mem::size_of, ops::Deref}; use std::sync::LazyLock; -use std::{mem::size_of, ops::Deref}; #[pyclass(module = false, name = "bytes")] #[derive(Clone, Debug)] diff --git a/crates/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index e46cc711bb3..b897ef9d311 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -11,10 +11,11 @@ use crate::{ function::OptionalArg, types::{Constructor, Representable}, }; +use alloc::fmt; +use core::{borrow::Borrow, ops::Deref}; use malachite_bigint::BigInt; use num_traits::Zero; use rustpython_compiler_core::{OneIndexed, bytecode::CodeUnits, bytecode::PyCodeLocationInfoKind}; -use std::{borrow::Borrow, fmt, ops::Deref}; /// State for iterating through code address ranges struct PyCodeAddressRange<'a> { @@ -601,7 +602,7 @@ impl PyCode { pub fn co_code(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef { // SAFETY: CodeUnit is #[repr(C)] with size 2, so we can safely transmute to bytes let bytes = unsafe { - std::slice::from_raw_parts( + core::slice::from_raw_parts( self.code.instructions.as_ptr() as *const u8, self.code.instructions.len() * 2, ) diff --git a/crates/vm/src/builtins/complex.rs b/crates/vm/src/builtins/complex.rs index ba74d5e0367..78729b2f5c0 100644 --- a/crates/vm/src/builtins/complex.rs +++ b/crates/vm/src/builtins/complex.rs @@ -10,10 +10,10 @@ use crate::{ stdlib::warnings, types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp, Representable}, }; +use core::num::Wrapping; use num_complex::Complex64; use num_traits::Zero; use rustpython_common::hash; -use std::num::Wrapping; /// Create a complex number from a real part and an optional imaginary part. /// diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index 802b81f6d79..aa9da6e2d44 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -59,8 +59,8 @@ impl PyPayload for PyMethodDescriptor { } } -impl std::fmt::Debug for PyMethodDescriptor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PyMethodDescriptor { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "method descriptor for '{}'", self.common.name) } } @@ -218,8 +218,8 @@ impl PyMemberDef { } } -impl std::fmt::Debug for PyMemberDef { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PyMemberDef { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("PyMemberDef") .field("name", &self.name) .field("kind", &self.kind) @@ -445,8 +445,8 @@ pub enum SlotFunc { NumTernaryRight(PyNumberTernaryFunc), // __rpow__ (swapped first two args) } -impl std::fmt::Debug for SlotFunc { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for SlotFunc { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { SlotFunc::Init(_) => write!(f, "SlotFunc::Init(...)"), SlotFunc::Hash(_) => write!(f, "SlotFunc::Hash(...)"), diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 567e18d6419..358685fcdc4 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -23,8 +23,8 @@ use crate::{ }, vm::VirtualMachine, }; +use alloc::fmt; use rustpython_common::lock::PyMutex; -use std::fmt; use std::sync::LazyLock; pub type DictContentType = dict_inner::Dict; @@ -219,7 +219,7 @@ impl PyDict { #[pymethod] fn __sizeof__(&self) -> usize { - std::mem::size_of::<Self>() + self.entries.sizeof() + core::mem::size_of::<Self>() + self.entries.sizeof() } #[pymethod] @@ -759,7 +759,7 @@ impl ExactSizeIterator for DictIter<'_> { #[pyclass] trait DictView: PyPayload + PyClassDef + Iterable + Representable { - type ReverseIter: PyPayload + std::fmt::Debug; + type ReverseIter: PyPayload + core::fmt::Debug; fn dict(&self) -> &Py<PyDict>; fn item(vm: &VirtualMachine, key: PyObjectRef, value: PyObjectRef) -> PyObjectRef; diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index c29e45ddcf6..95d70afcbc7 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -149,7 +149,7 @@ impl PyFunction { None }; - let arg_pos = |range: std::ops::Range<_>, name: &str| { + let arg_pos = |range: core::ops::Range<_>, name: &str| { code.varnames .iter() .enumerate() @@ -255,7 +255,7 @@ impl PyFunction { } if let Some(defaults) = defaults { - let n = std::cmp::min(nargs, n_expected_args); + let n = core::cmp::min(nargs, n_expected_args); let i = n.saturating_sub(n_required); // We have sufficient defaults, so iterate over the corresponding names and use diff --git a/crates/vm/src/builtins/genericalias.rs b/crates/vm/src/builtins/genericalias.rs index 8a7288980fa..5596aca9da2 100644 --- a/crates/vm/src/builtins/genericalias.rs +++ b/crates/vm/src/builtins/genericalias.rs @@ -16,7 +16,7 @@ use crate::{ PyComparisonOp, Representable, }, }; -use std::fmt; +use alloc::fmt; // attr_exceptions static ATTR_EXCEPTIONS: [&str; 12] = [ diff --git a/crates/vm/src/builtins/getset.rs b/crates/vm/src/builtins/getset.rs index 3fa4667a997..a3f0605a473 100644 --- a/crates/vm/src/builtins/getset.rs +++ b/crates/vm/src/builtins/getset.rs @@ -19,8 +19,8 @@ pub struct PyGetSet { // doc: Option<String>, } -impl std::fmt::Debug for PyGetSet { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PyGetSet { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, "PyGetSet {{ name: {}, getter: {}, setter: {} }}", @@ -158,7 +158,7 @@ impl Representable for PyGetSet { fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> { let class = unsafe { zelf.class.borrow_static() }; // Special case for object type - if std::ptr::eq(class, vm.ctx.types.object_type) { + if core::ptr::eq(class, vm.ctx.types.object_type) { Ok(format!("<attribute '{}'>", zelf.name)) } else { Ok(format!( diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 37b41e085ad..182333cea51 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -17,11 +17,11 @@ use crate::{ protocol::{PyNumberMethods, handle_bytes_to_int_err}, types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp, Representable}, }; +use alloc::fmt; +use core::ops::{Neg, Not}; use malachite_bigint::{BigInt, Sign}; use num_integer::Integer; use num_traits::{One, Pow, PrimInt, Signed, ToPrimitive, Zero}; -use std::fmt; -use std::ops::{Neg, Not}; #[pyclass(module = false, name = "int")] #[derive(Debug)] @@ -289,7 +289,7 @@ impl PyInt { I::try_from(self.as_bigint()).map_err(|_| { vm.new_overflow_error(format!( "Python int too large to convert to Rust {}", - std::any::type_name::<I>() + core::any::type_name::<I>() )) }) } @@ -444,7 +444,7 @@ impl PyInt { #[pymethod] fn __sizeof__(&self) -> usize { - std::mem::size_of::<Self>() + (((self.value.bits() + 7) & !7) / 8) as usize + core::mem::size_of::<Self>() + (((self.value.bits() + 7) & !7) / 8) as usize } #[pymethod] diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index 12cab27a750..46145b339cf 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -20,7 +20,8 @@ use crate::{ utils::collection_repr, vm::VirtualMachine, }; -use std::{fmt, ops::DerefMut}; +use alloc::fmt; +use core::ops::DerefMut; #[pyclass(module = false, name = "list", unhashable = true, traverse)] #[derive(Default)] @@ -172,7 +173,7 @@ impl PyList { #[pymethod] fn clear(&self) { - let _removed = std::mem::take(self.borrow_vec_mut().deref_mut()); + let _removed = core::mem::take(self.borrow_vec_mut().deref_mut()); } #[pymethod] @@ -188,8 +189,8 @@ impl PyList { #[pymethod] fn __sizeof__(&self) -> usize { - std::mem::size_of::<Self>() - + self.elements.read().capacity() * std::mem::size_of::<PyObjectRef>() + core::mem::size_of::<Self>() + + self.elements.read().capacity() * core::mem::size_of::<PyObjectRef>() } #[pymethod] @@ -324,9 +325,9 @@ impl PyList { // replace list contents with [] for duration of sort. // this prevents keyfunc from messing with the list and makes it easy to // check if it tries to append elements to it. - let mut elements = std::mem::take(self.borrow_vec_mut().deref_mut()); + let mut elements = core::mem::take(self.borrow_vec_mut().deref_mut()); let res = do_sort(vm, &mut elements, options.key, options.reverse); - std::mem::swap(self.borrow_vec_mut().deref_mut(), &mut elements); + core::mem::swap(self.borrow_vec_mut().deref_mut(), &mut elements); res?; if !elements.is_empty() { @@ -375,7 +376,7 @@ impl MutObjectSequenceOp for PyList { inner.get(index).map(|r| r.as_ref()) } - fn do_lock(&self) -> impl std::ops::Deref<Target = [PyObjectRef]> { + fn do_lock(&self) -> impl core::ops::Deref<Target = [PyObjectRef]> { self.borrow_vec() } } @@ -397,7 +398,7 @@ impl Initializer for PyList { } else { vec![] }; - std::mem::swap(zelf.borrow_vec_mut().deref_mut(), &mut elements); + core::mem::swap(zelf.borrow_vec_mut().deref_mut(), &mut elements); Ok(()) } } diff --git a/crates/vm/src/builtins/map.rs b/crates/vm/src/builtins/map.rs index f5cee945ece..f83030824f1 100644 --- a/crates/vm/src/builtins/map.rs +++ b/crates/vm/src/builtins/map.rs @@ -42,7 +42,7 @@ impl PyMap { fn __length_hint__(&self, vm: &VirtualMachine) -> PyResult<usize> { self.iterators.iter().try_fold(0, |prev, cur| { let cur = cur.as_ref().to_owned().length_hint(0, vm)?; - let max = std::cmp::max(prev, cur); + let max = core::cmp::max(prev, cur); Ok(max) }) } diff --git a/crates/vm/src/builtins/memory.rs b/crates/vm/src/builtins/memory.rs index 4e895f92b7e..ff5df031c42 100644 --- a/crates/vm/src/builtins/memory.rs +++ b/crates/vm/src/builtins/memory.rs @@ -26,11 +26,11 @@ use crate::{ PyComparisonOp, Representable, SelfIter, }, }; +use core::{cmp::Ordering, fmt::Debug, mem::ManuallyDrop, ops::Range}; use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; use rustpython_common::lock::PyMutex; use std::sync::LazyLock; -use std::{cmp::Ordering, fmt::Debug, mem::ManuallyDrop, ops::Range}; #[derive(FromArgs)] pub struct PyMemoryViewNewArgs { diff --git a/crates/vm/src/builtins/module.rs b/crates/vm/src/builtins/module.rs index f8e42b28e0b..faa6e4813fd 100644 --- a/crates/vm/src/builtins/module.rs +++ b/crates/vm/src/builtins/module.rs @@ -32,8 +32,8 @@ pub struct PyModuleSlots { pub exec: Option<ModuleExec>, } -impl std::fmt::Debug for PyModuleSlots { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PyModuleSlots { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("PyModuleSlots") .field("create", &self.create.is_some()) .field("exec", &self.exec.is_some()) diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index a61fb1e2971..e73f4b79ae0 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -218,7 +218,7 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine) // basicsize += std::mem::size_of::<PyObjectRef>(); // } if let Some(ref slot_names) = slot_names { - basicsize += std::mem::size_of::<PyObjectRef>() * slot_names.__len__(); + basicsize += core::mem::size_of::<PyObjectRef>() * slot_names.__len__(); } if obj.class().slots.basicsize > basicsize { return Err( diff --git a/crates/vm/src/builtins/property.rs b/crates/vm/src/builtins/property.rs index 7ea36d39768..3a86867176a 100644 --- a/crates/vm/src/builtins/property.rs +++ b/crates/vm/src/builtins/property.rs @@ -10,7 +10,7 @@ use crate::{ function::{FuncArgs, PySetterValue}, types::{Constructor, GetDescriptor, Initializer}, }; -use std::sync::atomic::{AtomicBool, Ordering}; +use core::sync::atomic::{AtomicBool, Ordering}; #[pyclass(module = false, name = "property", traverse)] #[derive(Debug)] @@ -21,7 +21,7 @@ pub struct PyProperty { doc: PyRwLock<Option<PyObjectRef>>, name: PyRwLock<Option<PyObjectRef>>, #[pytraverse(skip)] - getter_doc: std::sync::atomic::AtomicBool, + getter_doc: core::sync::atomic::AtomicBool, } impl PyPayload for PyProperty { diff --git a/crates/vm/src/builtins/range.rs b/crates/vm/src/builtins/range.rs index 9f79f8efb2d..ab84c977ccd 100644 --- a/crates/vm/src/builtins/range.rs +++ b/crates/vm/src/builtins/range.rs @@ -14,11 +14,11 @@ use crate::{ Representable, SelfIter, }, }; +use core::cmp::max; use crossbeam_utils::atomic::AtomicCell; use malachite_bigint::{BigInt, Sign}; use num_integer::Integer; use num_traits::{One, Signed, ToPrimitive, Zero}; -use std::cmp::max; use std::sync::LazyLock; // Search flag passed to iter_search diff --git a/crates/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs index 5582ff3323c..b1236e44e93 100644 --- a/crates/vm/src/builtins/set.rs +++ b/crates/vm/src/builtins/set.rs @@ -23,12 +23,13 @@ use crate::{ utils::collection_repr, vm::VirtualMachine, }; +use alloc::fmt; +use core::ops::Deref; use rustpython_common::{ atomic::{Ordering, PyAtomic, Radium}, hash, }; use std::sync::LazyLock; -use std::{fmt, ops::Deref}; pub type SetContentType = dict_inner::Dict<()>; @@ -50,7 +51,7 @@ impl PySet { fn fold_op( &self, - others: impl std::iter::Iterator<Item = ArgIterable>, + others: impl core::iter::Iterator<Item = ArgIterable>, op: fn(&PySetInner, ArgIterable, &VirtualMachine) -> PyResult<PySetInner>, vm: &VirtualMachine, ) -> PyResult<Self> { @@ -68,7 +69,7 @@ impl PySet { Ok(Self { inner: self .inner - .fold_op(std::iter::once(other.into_iterable(vm)?), op, vm)?, + .fold_op(core::iter::once(other.into_iterable(vm)?), op, vm)?, }) } } @@ -111,7 +112,7 @@ impl PyFrozenSet { fn fold_op( &self, - others: impl std::iter::Iterator<Item = ArgIterable>, + others: impl core::iter::Iterator<Item = ArgIterable>, op: fn(&PySetInner, ArgIterable, &VirtualMachine) -> PyResult<PySetInner>, vm: &VirtualMachine, ) -> PyResult<Self> { @@ -130,7 +131,7 @@ impl PyFrozenSet { Ok(Self { inner: self .inner - .fold_op(std::iter::once(other.into_iterable(vm)?), op, vm)?, + .fold_op(core::iter::once(other.into_iterable(vm)?), op, vm)?, ..Default::default() }) } @@ -191,7 +192,7 @@ impl PySetInner { fn fold_op<O>( &self, - others: impl std::iter::Iterator<Item = O>, + others: impl core::iter::Iterator<Item = O>, op: fn(&Self, O, &VirtualMachine) -> PyResult<Self>, vm: &VirtualMachine, ) -> PyResult<Self> { @@ -352,7 +353,7 @@ impl PySetInner { fn update( &self, - others: impl std::iter::Iterator<Item = ArgIterable>, + others: impl core::iter::Iterator<Item = ArgIterable>, vm: &VirtualMachine, ) -> PyResult<()> { for iterable in others { @@ -395,7 +396,7 @@ impl PySetInner { fn intersection_update( &self, - others: impl std::iter::Iterator<Item = ArgIterable>, + others: impl core::iter::Iterator<Item = ArgIterable>, vm: &VirtualMachine, ) -> PyResult<()> { let temp_inner = self.fold_op(others, Self::intersection, vm)?; @@ -408,7 +409,7 @@ impl PySetInner { fn difference_update( &self, - others: impl std::iter::Iterator<Item = ArgIterable>, + others: impl core::iter::Iterator<Item = ArgIterable>, vm: &VirtualMachine, ) -> PyResult<()> { for iterable in others { @@ -422,7 +423,7 @@ impl PySetInner { fn symmetric_difference_update( &self, - others: impl std::iter::Iterator<Item = ArgIterable>, + others: impl core::iter::Iterator<Item = ArgIterable>, vm: &VirtualMachine, ) -> PyResult<()> { for iterable in others { @@ -539,7 +540,7 @@ impl PySet { #[pymethod] fn __sizeof__(&self) -> usize { - std::mem::size_of::<Self>() + self.inner.sizeof() + core::mem::size_of::<Self>() + self.inner.sizeof() } #[pymethod] @@ -731,7 +732,7 @@ impl PySet { #[pymethod] fn __iand__(zelf: PyRef<Self>, set: AnySet, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { zelf.inner - .intersection_update(std::iter::once(set.into_iterable(vm)?), vm)?; + .intersection_update(core::iter::once(set.into_iterable(vm)?), vm)?; Ok(zelf) } @@ -978,7 +979,7 @@ impl PyFrozenSet { #[pymethod] fn __sizeof__(&self) -> usize { - std::mem::size_of::<Self>() + self.inner.sizeof() + core::mem::size_of::<Self>() + self.inner.sizeof() } #[pymethod] @@ -1258,8 +1259,8 @@ impl AnySet { fn into_iterable_iter( self, vm: &VirtualMachine, - ) -> PyResult<impl std::iter::Iterator<Item = ArgIterable>> { - Ok(std::iter::once(self.into_iterable(vm)?)) + ) -> PyResult<impl core::iter::Iterator<Item = ArgIterable>> { + Ok(core::iter::once(self.into_iterable(vm)?)) } fn as_inner(&self) -> &PySetInner { diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index fa35c1725d4..0357e81b365 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -24,8 +24,10 @@ use crate::{ PyComparisonOp, Representable, SelfIter, }, }; +use alloc::{borrow::Cow, fmt}; use ascii::{AsciiChar, AsciiStr, AsciiString}; use bstr::ByteSlice; +use core::{char, mem, ops::Range}; use itertools::Itertools; use num_traits::ToPrimitive; use rustpython_common::{ @@ -37,8 +39,7 @@ use rustpython_common::{ str::DeduceStrKind, wtf8::{CodePoint, Wtf8, Wtf8Buf, Wtf8Chunk}, }; -use std::{borrow::Cow, char, fmt, ops::Range}; -use std::{mem, sync::LazyLock}; +use std::sync::LazyLock; use unic_ucd_bidi::BidiClass; use unic_ucd_category::GeneralCategory; use unic_ucd_ident::{is_xid_continue, is_xid_start}; @@ -191,8 +192,8 @@ impl From<StrData> for PyStr { } } -impl<'a> From<std::borrow::Cow<'a, str>> for PyStr { - fn from(s: std::borrow::Cow<'a, str>) -> Self { +impl<'a> From<alloc::borrow::Cow<'a, str>> for PyStr { + fn from(s: alloc::borrow::Cow<'a, str>) -> Self { s.into_owned().into() } } @@ -632,7 +633,7 @@ impl PyStr { #[pymethod] fn __sizeof__(&self) -> usize { - std::mem::size_of::<Self>() + self.byte_len() * std::mem::size_of::<u8>() + core::mem::size_of::<Self>() + self.byte_len() * core::mem::size_of::<u8>() } #[pymethod(name = "__rmul__")] @@ -1045,7 +1046,7 @@ impl PyStr { #[pymethod] fn replace(&self, args: ReplaceArgs) -> Wtf8Buf { - use std::cmp::Ordering; + use core::cmp::Ordering; let s = self.as_wtf8(); let ReplaceArgs { old, new, count } = args; @@ -1361,7 +1362,7 @@ impl PyStr { let ch = bigint .as_bigint() .to_u32() - .and_then(std::char::from_u32) + .and_then(core::char::from_u32) .ok_or_else(|| { vm.new_value_error("character mapping must be in range(0x110000)") })?; @@ -1494,7 +1495,7 @@ impl PyRef<PyStr> { } struct CharLenStr<'a>(&'a str, usize); -impl std::ops::Deref for CharLenStr<'_> { +impl core::ops::Deref for CharLenStr<'_> { type Target = str; fn deref(&self) -> &Self::Target { @@ -1705,7 +1706,7 @@ pub struct FindArgs { } impl FindArgs { - fn get_value(self, len: usize) -> (PyStrRef, std::ops::Range<usize>) { + fn get_value(self, len: usize) -> (PyStrRef, core::ops::Range<usize>) { let range = adjust_indices(self.start, self.end, len); (self.sub, range) } @@ -1945,8 +1946,8 @@ impl PyPayload for PyUtf8Str { ctx.types.str_type } - fn payload_type_id() -> std::any::TypeId { - std::any::TypeId::of::<PyStr>() + fn payload_type_id() -> core::any::TypeId { + core::any::TypeId::of::<PyStr>() } fn validate_downcastable_from(obj: &PyObject) -> bool { @@ -2005,8 +2006,8 @@ impl From<char> for PyUtf8Str { } } -impl<'a> From<std::borrow::Cow<'a, str>> for PyUtf8Str { - fn from(s: std::borrow::Cow<'a, str>) -> Self { +impl<'a> From<alloc::borrow::Cow<'a, str>> for PyUtf8Str { + fn from(s: alloc::borrow::Cow<'a, str>) -> Self { s.into_owned().into() } } @@ -2128,11 +2129,11 @@ impl AnyStr for str { Self::chars(self) } - fn get_bytes(&self, range: std::ops::Range<usize>) -> &Self { + fn get_bytes(&self, range: core::ops::Range<usize>) -> &Self { &self[range] } - fn get_chars(&self, range: std::ops::Range<usize>) -> &Self { + fn get_chars(&self, range: core::ops::Range<usize>) -> &Self { rustpython_common::str::get_chars(self, range) } @@ -2239,11 +2240,11 @@ impl AnyStr for Wtf8 { self.code_points() } - fn get_bytes(&self, range: std::ops::Range<usize>) -> &Self { + fn get_bytes(&self, range: core::ops::Range<usize>) -> &Self { &self[range] } - fn get_chars(&self, range: std::ops::Range<usize>) -> &Self { + fn get_chars(&self, range: core::ops::Range<usize>) -> &Self { rustpython_common::str::get_codepoints(self, range) } @@ -2361,11 +2362,11 @@ impl AnyStr for AsciiStr { self.chars() } - fn get_bytes(&self, range: std::ops::Range<usize>) -> &Self { + fn get_bytes(&self, range: core::ops::Range<usize>) -> &Self { &self[range] } - fn get_chars(&self, range: std::ops::Range<usize>) -> &Self { + fn get_chars(&self, range: core::ops::Range<usize>) -> &Self { &self[range] } @@ -2436,8 +2437,8 @@ impl PyStrInterned { } } -impl std::fmt::Display for PyStrInterned { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for PyStrInterned { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.data.fmt(f) } } diff --git a/crates/vm/src/builtins/traceback.rs b/crates/vm/src/builtins/traceback.rs index 6bf4070c9b5..84493e7125f 100644 --- a/crates/vm/src/builtins/traceback.rs +++ b/crates/vm/src/builtins/traceback.rs @@ -81,7 +81,7 @@ impl Constructor for PyTraceback { impl PyTracebackRef { pub fn iter(&self) -> impl Iterator<Item = Self> { - std::iter::successors(Some(self.clone()), |tb| tb.next.lock().clone()) + core::iter::successors(Some(self.clone()), |tb| tb.next.lock().clone()) } } diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index 13335428b35..70e4e20e405 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -21,7 +21,8 @@ use crate::{ utils::collection_repr, vm::VirtualMachine, }; -use std::{fmt, sync::LazyLock}; +use alloc::fmt; +use std::sync::LazyLock; #[pyclass(module = false, name = "tuple", traverse)] pub struct PyTuple<R = PyObjectRef> { @@ -158,7 +159,7 @@ impl<R> AsRef<[R]> for PyTuple<R> { } } -impl<R> std::ops::Deref for PyTuple<R> { +impl<R> core::ops::Deref for PyTuple<R> { type Target = [R]; fn deref(&self) -> &[R] { @@ -166,18 +167,18 @@ impl<R> std::ops::Deref for PyTuple<R> { } } -impl<'a, R> std::iter::IntoIterator for &'a PyTuple<R> { +impl<'a, R> core::iter::IntoIterator for &'a PyTuple<R> { type Item = &'a R; - type IntoIter = std::slice::Iter<'a, R>; + type IntoIter = core::slice::Iter<'a, R>; fn into_iter(self) -> Self::IntoIter { self.iter() } } -impl<'a, R> std::iter::IntoIterator for &'a Py<PyTuple<R>> { +impl<'a, R> core::iter::IntoIterator for &'a Py<PyTuple<R>> { type Item = &'a R; - type IntoIter = std::slice::Iter<'a, R>; + type IntoIter = core::slice::Iter<'a, R>; fn into_iter(self) -> Self::IntoIter { self.iter() @@ -200,7 +201,7 @@ impl<R> PyTuple<R> { } #[inline] - pub fn iter(&self) -> std::slice::Iter<'_, R> { + pub fn iter(&self) -> core::slice::Iter<'_, R> { self.elements.iter() } } @@ -249,15 +250,15 @@ impl<T> PyTuple<PyRef<T>> { // SAFETY: PyRef<T> has the same layout as PyObjectRef unsafe { let elements: Vec<PyObjectRef> = - std::mem::transmute::<Vec<PyRef<T>>, Vec<PyObjectRef>>(elements); + core::mem::transmute::<Vec<PyRef<T>>, Vec<PyObjectRef>>(elements); let tuple = PyTuple::<PyObjectRef>::new_ref(elements, ctx); - std::mem::transmute::<PyRef<PyTuple>, PyRef<Self>>(tuple) + core::mem::transmute::<PyRef<PyTuple>, PyRef<Self>>(tuple) } } } #[pyclass( - itemsize = std::mem::size_of::<crate::PyObjectRef>(), + itemsize = core::mem::size_of::<crate::PyObjectRef>(), flags(BASETYPE, SEQUENCE, _MATCH_SELF), with(AsMapping, AsSequence, Hashable, Comparable, Iterable, Constructor, Representable) )] @@ -489,21 +490,21 @@ impl PyRef<PyTuple<PyObjectRef>> { <PyRef<T> as TransmuteFromObject>::check(vm, elem)?; } // SAFETY: We just verified all elements are of type T - Ok(unsafe { std::mem::transmute::<Self, PyRef<PyTuple<PyRef<T>>>>(self) }) + Ok(unsafe { core::mem::transmute::<Self, PyRef<PyTuple<PyRef<T>>>>(self) }) } } impl<T: PyPayload> PyRef<PyTuple<PyRef<T>>> { pub fn into_untyped(self) -> PyRef<PyTuple> { // SAFETY: PyTuple<PyRef<T>> has the same layout as PyTuple - unsafe { std::mem::transmute::<Self, PyRef<PyTuple>>(self) } + unsafe { core::mem::transmute::<Self, PyRef<PyTuple>>(self) } } } impl<T: PyPayload> Py<PyTuple<PyRef<T>>> { pub fn as_untyped(&self) -> &Py<PyTuple> { // SAFETY: PyTuple<PyRef<T>> has the same layout as PyTuple - unsafe { std::mem::transmute::<&Self, &Py<PyTuple>>(self) } + unsafe { core::mem::transmute::<&Self, &Py<PyTuple>>(self) } } } diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index e807d3f4f8e..3eca5edc478 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -29,10 +29,11 @@ use crate::{ Representable, SLOT_DEFS, SetAttr, TypeDataRef, TypeDataRefMut, TypeDataSlot, }, }; +use core::{any::Any, borrow::Borrow, ops::Deref, pin::Pin, ptr::NonNull}; use indexmap::{IndexMap, map::Entry}; use itertools::Itertools; use num_traits::ToPrimitive; -use std::{any::Any, borrow::Borrow, collections::HashSet, ops::Deref, pin::Pin, ptr::NonNull}; +use std::collections::HashSet; #[pyclass(module = false, name = "type", traverse = "manual")] pub struct PyType { @@ -118,14 +119,14 @@ unsafe impl Traverse for PyAttributes { } } -impl std::fmt::Display for PyType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.name(), f) +impl core::fmt::Display for PyType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(&self.name(), f) } } -impl std::fmt::Debug for PyType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PyType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "[PyType {}]", &self.name()) } } @@ -368,7 +369,7 @@ impl PyType { // Inherit SEQUENCE and MAPPING flags from base class // For static types, we only have a single base - Self::inherit_patma_flags(&mut slots, std::slice::from_ref(&base)); + Self::inherit_patma_flags(&mut slots, core::slice::from_ref(&base)); if slots.basicsize == 0 { slots.basicsize = base.slots.basicsize; @@ -549,7 +550,7 @@ impl PyType { // Gather all members here: let mut attributes = PyAttributes::default(); - for bc in std::iter::once(self) + for bc in core::iter::once(self) .chain(self.mro.read().iter().map(|cls| -> &Self { cls })) .rev() { @@ -667,21 +668,21 @@ impl Py<PyType> { where F: Fn(&Self) -> R, { - std::iter::once(self) + core::iter::once(self) .chain(self.mro.read().iter().map(|x| x.deref())) .map(f) .collect() } pub fn mro_collect(&self) -> Vec<PyRef<PyType>> { - std::iter::once(self) + core::iter::once(self) .chain(self.mro.read().iter().map(|x| x.deref())) .map(|x| x.to_owned()) .collect() } pub fn iter_base_chain(&self) -> impl Iterator<Item = &Self> { - std::iter::successors(Some(self), |cls| cls.base.as_deref()) + core::iter::successors(Some(self), |cls| cls.base.as_deref()) } pub fn extend_methods(&'static self, method_defs: &'static [PyMethodDef], ctx: &Context) { @@ -846,7 +847,7 @@ impl PyType { // then drop the old value after releasing the lock let _old_qualname = { let mut qualname_guard = heap_type.qualname.write(); - std::mem::replace(&mut *qualname_guard, str_value) + core::mem::replace(&mut *qualname_guard, str_value) }; // old_qualname is dropped here, outside the lock scope @@ -1012,7 +1013,7 @@ impl PyType { // then drop the old value after releasing the lock (similar to CPython's Py_SETREF) let _old_name = { let mut name_guard = self.heaptype_ext.as_ref().unwrap().name.write(); - std::mem::replace(&mut *name_guard, name) + core::mem::replace(&mut *name_guard, name) }; // old_name is dropped here, outside the lock scope diff --git a/crates/vm/src/builtins/union.rs b/crates/vm/src/builtins/union.rs index 83e1316d027..0342442b83d 100644 --- a/crates/vm/src/builtins/union.rs +++ b/crates/vm/src/builtins/union.rs @@ -11,7 +11,7 @@ use crate::{ stdlib::typing::TypeAliasType, types::{AsMapping, AsNumber, Comparable, GetAttr, Hashable, PyComparisonOp, Representable}, }; -use std::fmt; +use alloc::fmt; use std::sync::LazyLock; const CLS_ATTRS: &[&str] = &["__module__"]; diff --git a/crates/vm/src/bytes_inner.rs b/crates/vm/src/bytes_inner.rs index 8593f16fcd9..bb5db442c35 100644 --- a/crates/vm/src/bytes_inner.rs +++ b/crates/vm/src/bytes_inner.rs @@ -151,7 +151,7 @@ impl ByteInnerFindOptions { self, len: usize, vm: &VirtualMachine, - ) -> PyResult<(Vec<u8>, std::ops::Range<usize>)> { + ) -> PyResult<(Vec<u8>, core::ops::Range<usize>)> { let sub = match self.sub { Either::A(v) => v.elements.to_vec(), Either::B(int) => vec![int.as_bigint().byte_or(vm)?], @@ -719,7 +719,7 @@ impl PyBytesInner { // len(self)>=1, from="", len(to)>=1, max_count>=1 fn replace_interleave(&self, to: Self, max_count: Option<usize>) -> Vec<u8> { let place_count = self.elements.len() + 1; - let count = max_count.map_or(place_count, |v| std::cmp::min(v, place_count)) - 1; + let count = max_count.map_or(place_count, |v| core::cmp::min(v, place_count)) - 1; let capacity = self.elements.len() + count * to.len(); let mut result = Vec::with_capacity(capacity); let to_slice = to.elements.as_slice(); @@ -952,7 +952,7 @@ where fn count_substring(haystack: &[u8], needle: &[u8], max_count: Option<usize>) -> usize { let substrings = haystack.find_iter(needle); if let Some(max_count) = max_count { - std::cmp::min(substrings.take(max_count).count(), max_count) + core::cmp::min(substrings.take(max_count).count(), max_count) } else { substrings.count() } @@ -1025,11 +1025,11 @@ impl AnyStr for [u8] { self.iter().copied() } - fn get_bytes(&self, range: std::ops::Range<usize>) -> &Self { + fn get_bytes(&self, range: core::ops::Range<usize>) -> &Self { &self[range] } - fn get_chars(&self, range: std::ops::Range<usize>) -> &Self { + fn get_chars(&self, range: core::ops::Range<usize>) -> &Self { &self[range] } @@ -1120,7 +1120,7 @@ fn hex_impl(bytes: &[u8], sep: u8, bytes_per_sep: isize) -> String { let len = bytes.len(); let buf = if bytes_per_sep < 0 { - let bytes_per_sep = std::cmp::min(len, (-bytes_per_sep) as usize); + let bytes_per_sep = core::cmp::min(len, (-bytes_per_sep) as usize); let chunks = (len - 1) / bytes_per_sep; let chunked = chunks * bytes_per_sep; let unchunked = len - chunked; @@ -1139,7 +1139,7 @@ fn hex_impl(bytes: &[u8], sep: u8, bytes_per_sep: isize) -> String { hex::encode_to_slice(&bytes[chunked..], &mut buf[j..j + unchunked * 2]).unwrap(); buf } else { - let bytes_per_sep = std::cmp::min(len, bytes_per_sep as usize); + let bytes_per_sep = core::cmp::min(len, bytes_per_sep as usize); let chunks = (len - 1) / bytes_per_sep; let chunked = chunks * bytes_per_sep; let unchunked = len - chunked; diff --git a/crates/vm/src/cformat.rs b/crates/vm/src/cformat.rs index efb3cb2acc9..939b1c7760f 100644 --- a/crates/vm/src/cformat.rs +++ b/crates/vm/src/cformat.rs @@ -342,7 +342,7 @@ pub(crate) fn cformat_bytes( let values = if let Some(tup) = values_obj.downcast_ref::<tuple::PyTuple>() { tup.as_slice() } else { - std::slice::from_ref(&values_obj) + core::slice::from_ref(&values_obj) }; let mut value_iter = values.iter(); @@ -435,7 +435,7 @@ pub(crate) fn cformat_string( let values = if let Some(tup) = values_obj.downcast_ref::<tuple::PyTuple>() { tup.as_slice() } else { - std::slice::from_ref(&values_obj) + core::slice::from_ref(&values_obj) }; let mut value_iter = values.iter(); diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index ce41abcc60b..98dc6fd2ed2 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -169,7 +169,7 @@ pub trait PyClassImpl: PyClassDef { // Exception: object itself should have __new__ in its dict if let Some(slot_new) = class.slots.new.load() { let object_new = ctx.types.object_type.slots.new.load(); - let is_object_itself = std::ptr::eq(class, ctx.types.object_type); + let is_object_itself = core::ptr::eq(class, ctx.types.object_type); let is_inherited_from_object = !is_object_itself && object_new.is_some_and(|obj_new| slot_new as usize == obj_new as usize); @@ -203,7 +203,7 @@ pub trait PyClassImpl: PyClassDef { Self::extend_class(ctx, unsafe { // typ will be saved in static_cell let r: &Py<PyType> = &typ; - let r: &'static Py<PyType> = std::mem::transmute(r); + let r: &'static Py<PyType> = core::mem::transmute(r); r }); typ diff --git a/crates/vm/src/codecs.rs b/crates/vm/src/codecs.rs index 2edb67b497b..3241dee4981 100644 --- a/crates/vm/src/codecs.rs +++ b/crates/vm/src/codecs.rs @@ -16,12 +16,10 @@ use crate::{ convert::ToPyObject, function::{ArgBytesLike, PyMethodDef}, }; +use alloc::borrow::Cow; +use core::ops::{self, Range}; use once_cell::unsync::OnceCell; -use std::{ - borrow::Cow, - collections::HashMap, - ops::{self, Range}, -}; +use std::collections::HashMap; pub struct CodecsRegistry { inner: PyRwLock<RegistryInner>, diff --git a/crates/vm/src/convert/try_from.rs b/crates/vm/src/convert/try_from.rs index 4f921e9c5de..ceb7d003e9b 100644 --- a/crates/vm/src/convert/try_from.rs +++ b/crates/vm/src/convert/try_from.rs @@ -122,7 +122,7 @@ impl<'a, T: PyPayload> TryFromBorrowedObject<'a> for &'a Py<T> { } } -impl TryFromObject for std::time::Duration { +impl TryFromObject for core::time::Duration { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { if let Some(float) = obj.downcast_ref::<PyFloat>() { let f = float.to_f64(); diff --git a/crates/vm/src/dict_inner.rs b/crates/vm/src/dict_inner.rs index 02e237afb00..d57f8be0fe7 100644 --- a/crates/vm/src/dict_inner.rs +++ b/crates/vm/src/dict_inner.rs @@ -16,8 +16,9 @@ use crate::{ }, object::{Traverse, TraverseFn}, }; +use alloc::fmt; +use core::{mem::size_of, ops::ControlFlow}; use num_traits::ToPrimitive; -use std::{fmt, mem::size_of, ops::ControlFlow}; // HashIndex is intended to be same size with hash::PyHash // but it doesn't mean the values are compatible with actual PyHash value @@ -281,7 +282,7 @@ impl<T: Clone> Dict<T> { continue; }; if entry.index == index_index { - let removed = std::mem::replace(&mut entry.value, value); + let removed = core::mem::replace(&mut entry.value, value); // defer dec RC break Some(removed); } else { @@ -357,7 +358,7 @@ impl<T: Clone> Dict<T> { inner.used = 0; inner.filled = 0; // defer dec rc - std::mem::take(&mut inner.entries) + core::mem::take(&mut inner.entries) }; } @@ -633,7 +634,7 @@ impl<T: Clone> Dict<T> { // returns Err(()) if changed since lookup fn pop_inner(&self, lookup: LookupResult) -> PopInnerResult<T> { - self.pop_inner_if(lookup, |_| Ok::<_, std::convert::Infallible>(true)) + self.pop_inner_if(lookup, |_| Ok::<_, core::convert::Infallible>(true)) .unwrap_or_else(|x| match x {}) } diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 4e8b572e457..b1a654d58c4 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -33,8 +33,8 @@ unsafe impl Traverse for PyBaseException { } } -impl std::fmt::Debug for PyBaseException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PyBaseException { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { // TODO: implement more detailed, non-recursive Debug formatter f.write_str("PyBaseException") } @@ -1173,7 +1173,7 @@ pub fn cstring_error(vm: &VirtualMachine) -> PyBaseExceptionRef { vm.new_value_error("embedded null character") } -impl ToPyException for std::ffi::NulError { +impl ToPyException for alloc::ffi::NulError { fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef { cstring_error(vm) } @@ -1604,8 +1604,8 @@ pub(super) mod types { } } - impl std::fmt::Debug for PyOSError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + impl core::fmt::Debug for PyOSError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("PyOSError").finish_non_exhaustive() } } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 3f470e5453f..6acf0e84795 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -18,13 +18,14 @@ use crate::{ types::PyTypeFlags, vm::{Context, PyMethod}, }; +use alloc::fmt; +use core::iter::zip; +#[cfg(feature = "threading")] +use core::sync::atomic; use indexmap::IndexMap; use itertools::Itertools; use rustpython_common::{boxvec::BoxVec, lock::PyMutex, wtf8::Wtf8Buf}; use rustpython_compiler_core::SourceLocation; -#[cfg(feature = "threading")] -use std::sync::atomic; -use std::{fmt, iter::zip}; #[derive(Clone, Debug)] struct Block { @@ -91,7 +92,7 @@ struct FrameState { #[cfg(feature = "threading")] type Lasti = atomic::AtomicU32; #[cfg(not(feature = "threading"))] -type Lasti = std::cell::Cell<u32>; +type Lasti = core::cell::Cell<u32>; #[pyclass(module = false, name = "frame")] pub struct Frame { @@ -142,7 +143,7 @@ impl Frame { func_obj: Option<PyObjectRef>, vm: &VirtualMachine, ) -> Self { - let cells_frees = std::iter::repeat_with(|| PyCell::default().into_ref(&vm.ctx)) + let cells_frees = core::iter::repeat_with(|| PyCell::default().into_ref(&vm.ctx)) .take(code.cellvars.len()) .chain(closure.iter().cloned()) .collect(); @@ -189,7 +190,7 @@ impl Frame { let locals = &self.locals; let code = &**self.code; let map = &code.varnames; - let j = std::cmp::min(map.len(), code.varnames.len()); + let j = core::cmp::min(map.len(), code.varnames.len()); if !code.varnames.is_empty() { let fastlocals = self.fastlocals.lock(); for (&k, v) in zip(&map[..j], &**fastlocals) { @@ -2243,14 +2244,14 @@ impl ExecutingFrame<'_> { } })?; let msg = match elements.len().cmp(&(size as usize)) { - std::cmp::Ordering::Equal => { + core::cmp::Ordering::Equal => { self.state.stack.extend(elements.into_iter().rev()); return Ok(None); } - std::cmp::Ordering::Greater => { + core::cmp::Ordering::Greater => { format!("too many values to unpack (expected {size})") } - std::cmp::Ordering::Less => format!( + core::cmp::Ordering::Less => format!( "not enough values to unpack (expected {}, got {})", size, elements.len() @@ -2525,7 +2526,7 @@ impl ExecutingFrame<'_> { #[inline] fn replace_top(&mut self, mut top: PyObjectRef) -> PyObjectRef { let last = self.state.stack.last_mut().unwrap(); - std::mem::swap(&mut top, last); + core::mem::swap(&mut top, last); top } @@ -2561,12 +2562,12 @@ impl fmt::Debug for Frame { if elem.downcastable::<Self>() { s.push_str("\n > {frame}"); } else { - std::fmt::write(&mut s, format_args!("\n > {elem:?}")).unwrap(); + core::fmt::write(&mut s, format_args!("\n > {elem:?}")).unwrap(); } s }); let block_str = state.blocks.iter().fold(String::new(), |mut s, elem| { - std::fmt::write(&mut s, format_args!("\n > {elem:?}")).unwrap(); + core::fmt::write(&mut s, format_args!("\n > {elem:?}")).unwrap(); s }); // TODO: fix this up diff --git a/crates/vm/src/function/argument.rs b/crates/vm/src/function/argument.rs index d657ff6be8f..a4877cf4042 100644 --- a/crates/vm/src/function/argument.rs +++ b/crates/vm/src/function/argument.rs @@ -4,9 +4,9 @@ use crate::{ convert::ToPyObject, object::{Traverse, TraverseFn}, }; +use core::ops::RangeInclusive; use indexmap::IndexMap; use itertools::Itertools; -use std::ops::RangeInclusive; pub trait IntoFuncArgs: Sized { fn into_args(self, vm: &VirtualMachine) -> FuncArgs; @@ -100,7 +100,7 @@ impl From<KwArgs> for FuncArgs { impl FromArgs for FuncArgs { fn from_args(_vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> { - Ok(std::mem::take(args)) + Ok(core::mem::take(args)) } } @@ -424,7 +424,7 @@ impl<T> PosArgs<T> { self.0 } - pub fn iter(&self) -> std::slice::Iter<'_, T> { + pub fn iter(&self) -> core::slice::Iter<'_, T> { self.0.iter() } } @@ -469,7 +469,7 @@ where impl<T> IntoIterator for PosArgs<T> { type Item = T; - type IntoIter = std::vec::IntoIter<T>; + type IntoIter = alloc::vec::IntoIter<T>; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() diff --git a/crates/vm/src/function/builtin.rs b/crates/vm/src/function/builtin.rs index 1a91e4344ba..06fd6a44f54 100644 --- a/crates/vm/src/function/builtin.rs +++ b/crates/vm/src/function/builtin.rs @@ -3,7 +3,7 @@ use crate::{ Py, PyPayload, PyRef, PyResult, VirtualMachine, convert::ToPyResult, object::PyThreadingConstraint, }; -use std::marker::PhantomData; +use core::marker::PhantomData; /// A built-in Python function. // PyCFunction in CPython @@ -54,14 +54,14 @@ const fn zst_ref_out_of_thin_air<T: 'static>(x: T) -> &'static T { // if T is zero-sized, there's no issue forgetting it - even if it does have a Drop impl, it // would never get called anyway if we consider this semantically a Box::leak(Box::new(x))-type // operation. if T isn't zero-sized, we don't have to worry about it because we'll fail to compile. - std::mem::forget(x); + core::mem::forget(x); const { - if std::mem::size_of::<T>() != 0 { + if core::mem::size_of::<T>() != 0 { panic!("can't use a non-zero-sized type here") } // SAFETY: we just confirmed that T is zero-sized, so we can // pull a value of it out of thin air. - unsafe { std::ptr::NonNull::<T>::dangling().as_ref() } + unsafe { core::ptr::NonNull::<T>::dangling().as_ref() } } } diff --git a/crates/vm/src/function/either.rs b/crates/vm/src/function/either.rs index 8700c6150db..9ee7f028bd2 100644 --- a/crates/vm/src/function/either.rs +++ b/crates/vm/src/function/either.rs @@ -1,7 +1,7 @@ use crate::{ AsObject, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, convert::ToPyObject, }; -use std::borrow::Borrow; +use core::borrow::Borrow; pub enum Either<A, B> { A(A), diff --git a/crates/vm/src/function/fspath.rs b/crates/vm/src/function/fspath.rs index 44d41ab7632..7d3a0dcbbd5 100644 --- a/crates/vm/src/function/fspath.rs +++ b/crates/vm/src/function/fspath.rs @@ -5,7 +5,8 @@ use crate::{ function::PyStr, protocol::PyBuffer, }; -use std::{borrow::Cow, ffi::OsStr, path::PathBuf}; +use alloc::borrow::Cow; +use std::{ffi::OsStr, path::PathBuf}; /// Helper to implement os.fspath() #[derive(Clone)] @@ -111,8 +112,8 @@ impl FsPath { Ok(path) } - pub fn to_cstring(&self, vm: &VirtualMachine) -> PyResult<std::ffi::CString> { - std::ffi::CString::new(self.as_bytes()).map_err(|e| e.into_pyexception(vm)) + pub fn to_cstring(&self, vm: &VirtualMachine) -> PyResult<alloc::ffi::CString> { + alloc::ffi::CString::new(self.as_bytes()).map_err(|e| e.into_pyexception(vm)) } #[cfg(windows)] diff --git a/crates/vm/src/function/method.rs b/crates/vm/src/function/method.rs index 5e109176c5e..6440fd801fc 100644 --- a/crates/vm/src/function/method.rs +++ b/crates/vm/src/function/method.rs @@ -251,14 +251,14 @@ impl PyMethodDef { } } -impl std::fmt::Debug for PyMethodDef { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PyMethodDef { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("PyMethodDef") .field("name", &self.name) .field( "func", &(unsafe { - std::mem::transmute::<&dyn PyNativeFn, [usize; 2]>(self.func)[1] as *const u8 + core::mem::transmute::<&dyn PyNativeFn, [usize; 2]>(self.func)[1] as *const u8 }), ) .field("flags", &self.flags) diff --git a/crates/vm/src/function/number.rs b/crates/vm/src/function/number.rs index 7bb37b8f549..fb872cc48fd 100644 --- a/crates/vm/src/function/number.rs +++ b/crates/vm/src/function/number.rs @@ -1,9 +1,9 @@ use super::argument::OptionalArg; use crate::{AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, builtins::PyIntRef}; +use core::ops::Deref; use malachite_bigint::BigInt; use num_complex::Complex64; use num_traits::PrimInt; -use std::ops::Deref; /// A Python complex-like object. /// @@ -62,7 +62,7 @@ pub struct ArgIntoFloat { impl ArgIntoFloat { pub fn vec_into_f64(v: Vec<Self>) -> Vec<f64> { // TODO: Vec::into_raw_parts once stabilized - let mut v = std::mem::ManuallyDrop::new(v); + let mut v = core::mem::ManuallyDrop::new(v); let (p, l, c) = (v.as_mut_ptr(), v.len(), v.capacity()); // SAFETY: IntoPyFloat is repr(transparent) over f64 unsafe { Vec::from_raw_parts(p.cast(), l, c) } diff --git a/crates/vm/src/function/protocol.rs b/crates/vm/src/function/protocol.rs index a87ef339edd..94bdd3027eb 100644 --- a/crates/vm/src/function/protocol.rs +++ b/crates/vm/src/function/protocol.rs @@ -7,7 +7,7 @@ use crate::{ protocol::{PyIter, PyIterIter, PyMapping}, types::GenericMethod, }; -use std::{borrow::Borrow, marker::PhantomData, ops::Deref}; +use core::{borrow::Borrow, marker::PhantomData, ops::Deref}; #[derive(Clone, Traverse)] pub struct ArgCallable { @@ -24,8 +24,8 @@ impl ArgCallable { } } -impl std::fmt::Debug for ArgCallable { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for ArgCallable { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("ArgCallable") .field("obj", &self.obj) .field("call", &format!("{:08x}", self.call as usize)) @@ -203,7 +203,7 @@ impl<T> ArgSequence<T> { } } -impl<T> std::ops::Deref for ArgSequence<T> { +impl<T> core::ops::Deref for ArgSequence<T> { type Target = [T]; #[inline(always)] fn deref(&self) -> &[T] { @@ -213,14 +213,14 @@ impl<T> std::ops::Deref for ArgSequence<T> { impl<'a, T> IntoIterator for &'a ArgSequence<T> { type Item = &'a T; - type IntoIter = std::slice::Iter<'a, T>; + type IntoIter = core::slice::Iter<'a, T>; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl<T> IntoIterator for ArgSequence<T> { type Item = T; - type IntoIter = std::vec::IntoIter<T>; + type IntoIter = alloc::vec::IntoIter<T>; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } diff --git a/crates/vm/src/intern.rs b/crates/vm/src/intern.rs index a5b2a798d53..a50b8871cb9 100644 --- a/crates/vm/src/intern.rs +++ b/crates/vm/src/intern.rs @@ -6,10 +6,8 @@ use crate::{ common::lock::PyRwLock, convert::ToPyObject, }; -use std::{ - borrow::{Borrow, ToOwned}, - ops::Deref, -}; +use alloc::borrow::ToOwned; +use core::{borrow::Borrow, ops::Deref}; #[derive(Debug)] pub struct StringPool { @@ -86,8 +84,8 @@ pub struct CachedPyStrRef { inner: PyRefExact<PyStr>, } -impl std::hash::Hash for CachedPyStrRef { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { +impl core::hash::Hash for CachedPyStrRef { + fn hash<H: core::hash::Hasher>(&self, state: &mut H) { self.inner.as_wtf8().hash(state) } } @@ -100,7 +98,7 @@ impl PartialEq for CachedPyStrRef { impl Eq for CachedPyStrRef {} -impl std::borrow::Borrow<Wtf8> for CachedPyStrRef { +impl core::borrow::Borrow<Wtf8> for CachedPyStrRef { #[inline] fn borrow(&self) -> &Wtf8 { self.as_wtf8() @@ -119,7 +117,7 @@ impl CachedPyStrRef { /// the given cache must be alive while returned reference is alive #[inline] const unsafe fn as_interned_str(&self) -> &'static PyStrInterned { - unsafe { std::mem::transmute_copy(self) } + unsafe { core::mem::transmute_copy(self) } } #[inline] @@ -135,7 +133,7 @@ pub struct PyInterned<T> { impl<T: PyPayload> PyInterned<T> { #[inline] pub fn leak(cache: PyRef<T>) -> &'static Self { - unsafe { std::mem::transmute(cache) } + unsafe { core::mem::transmute(cache) } } #[inline] @@ -163,9 +161,9 @@ impl<T: PyPayload> Borrow<PyObject> for PyInterned<T> { // NOTE: std::hash::Hash of Self and Self::Borrowed *must* be the same // This is ok only because PyObject doesn't implement Hash -impl<T: PyPayload> std::hash::Hash for PyInterned<T> { +impl<T: PyPayload> core::hash::Hash for PyInterned<T> { #[inline(always)] - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + fn hash<H: core::hash::Hasher>(&self, state: &mut H) { self.get_id().hash(state) } } @@ -188,15 +186,15 @@ impl<T> Deref for PyInterned<T> { impl<T: PyPayload> PartialEq for PyInterned<T> { #[inline(always)] fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) + core::ptr::eq(self, other) } } impl<T: PyPayload> Eq for PyInterned<T> {} -impl<T: std::fmt::Debug + PyPayload> std::fmt::Debug for PyInterned<T> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(&**self, f)?; +impl<T: core::fmt::Debug + PyPayload> core::fmt::Debug for PyInterned<T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(&**self, f)?; write!(f, "@{:p}", self.as_ptr()) } } @@ -308,7 +306,7 @@ impl MaybeInternedString for Py<PyStr> { #[inline(always)] fn as_interned(&self) -> Option<&'static PyStrInterned> { if self.as_object().is_interned() { - Some(unsafe { std::mem::transmute::<&Self, &PyInterned<PyStr>>(self) }) + Some(unsafe { core::mem::transmute::<&Self, &PyInterned<PyStr>>(self) }) } else { None } diff --git a/crates/vm/src/lib.rs b/crates/vm/src/lib.rs index f461c612955..3f0eee278a2 100644 --- a/crates/vm/src/lib.rs +++ b/crates/vm/src/lib.rs @@ -24,6 +24,7 @@ extern crate bitflags; #[macro_use] extern crate log; // extern crate env_logger; +extern crate alloc; #[macro_use] extern crate rustpython_derive; diff --git a/crates/vm/src/macros.rs b/crates/vm/src/macros.rs index 1284c202782..f5c912d89cf 100644 --- a/crates/vm/src/macros.rs +++ b/crates/vm/src/macros.rs @@ -146,8 +146,8 @@ macro_rules! match_class { }; (match ($obj:expr) { ref $binding:ident @ $class:ty => $expr:expr, $($rest:tt)* }) => { match $obj.downcast_ref::<$class>() { - ::std::option::Option::Some($binding) => $expr, - ::std::option::Option::None => $crate::match_class!(match ($obj) { $($rest)* }), + core::option::Option::Some($binding) => $expr, + core::option::Option::None => $crate::match_class!(match ($obj) { $($rest)* }), } }; diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index d52a33884ce..cee3fac266e 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -31,11 +31,13 @@ use crate::{ object::traverse::{MaybeTraverse, Traverse, TraverseFn}, }; use itertools::Itertools; -use std::{ + +use alloc::fmt; + +use core::{ any::TypeId, borrow::Borrow, cell::UnsafeCell, - fmt, marker::PhantomData, mem::ManuallyDrop, ops::Deref, @@ -82,7 +84,7 @@ pub(super) struct Erased; pub(super) unsafe fn drop_dealloc_obj<T: PyPayload>(x: *mut PyObject) { drop(unsafe { Box::from_raw(x as *mut PyInner<T>) }); } -pub(super) unsafe fn debug_obj<T: PyPayload + std::fmt::Debug>( +pub(super) unsafe fn debug_obj<T: PyPayload + core::fmt::Debug>( x: &PyObject, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { @@ -114,7 +116,7 @@ pub(super) struct PyInner<T> { pub(super) payload: T, } -pub(crate) const SIZEOF_PYOBJECT_HEAD: usize = std::mem::size_of::<PyInner<()>>(); +pub(crate) const SIZEOF_PYOBJECT_HEAD: usize = core::mem::size_of::<PyInner<()>>(); impl<T: fmt::Debug> fmt::Debug for PyInner<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -376,7 +378,7 @@ impl PyWeak { let node_ptr = unsafe { NonNull::new_unchecked(py_inner as *mut Py<Self>) }; // the list doesn't have ownership over its PyRef<PyWeak>! we're being dropped // right now so that should be obvious!! - std::mem::forget(unsafe { guard.list.remove(node_ptr) }); + core::mem::forget(unsafe { guard.list.remove(node_ptr) }); guard.ref_count -= 1; if Some(node_ptr) == guard.generic_weakref { guard.generic_weakref = None; @@ -438,11 +440,11 @@ impl InstanceDict { #[inline] pub fn replace(&self, d: PyDictRef) -> PyDictRef { - std::mem::replace(&mut self.d.write(), d) + core::mem::replace(&mut self.d.write(), d) } } -impl<T: PyPayload + std::fmt::Debug> PyInner<T> { +impl<T: PyPayload + core::fmt::Debug> PyInner<T> { fn new(payload: T, typ: PyTypeRef, dict: Option<PyDictRef>) -> Box<Self> { let member_count = typ.slots.member_count; Box::new(Self { @@ -453,7 +455,7 @@ impl<T: PyPayload + std::fmt::Debug> PyInner<T> { dict: dict.map(InstanceDict::new), weak_list: WeakRefList::new(), payload, - slots: std::iter::repeat_with(|| PyRwLock::new(None)) + slots: core::iter::repeat_with(|| PyRwLock::new(None)) .take(member_count) .collect_vec() .into_boxed_slice(), @@ -513,7 +515,7 @@ impl PyObjectRef { #[inline(always)] pub const fn into_raw(self) -> NonNull<PyObject> { let ptr = self.ptr; - std::mem::forget(self); + core::mem::forget(self); ptr } @@ -946,12 +948,12 @@ impl<T: PyPayload> Borrow<PyObject> for Py<T> { } } -impl<T> std::hash::Hash for Py<T> +impl<T> core::hash::Hash for Py<T> where - T: std::hash::Hash + PyPayload, + T: core::hash::Hash + PyPayload, { #[inline] - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + fn hash<H: core::hash::Hasher>(&self, state: &mut H) { self.deref().hash(state) } } @@ -978,7 +980,7 @@ where } } -impl<T: PyPayload + std::fmt::Debug> fmt::Debug for Py<T> { +impl<T: PyPayload + core::fmt::Debug> fmt::Debug for Py<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { (**self).fmt(f) } @@ -1059,12 +1061,12 @@ impl<T: PyPayload> PyRef<T> { pub const fn leak(pyref: Self) -> &'static Py<T> { let ptr = pyref.ptr; - std::mem::forget(pyref); + core::mem::forget(pyref); unsafe { ptr.as_ref() } } } -impl<T: PyPayload + std::fmt::Debug> PyRef<T> { +impl<T: PyPayload + core::fmt::Debug> PyRef<T> { #[inline(always)] pub fn new_ref(payload: T, typ: crate::builtins::PyTypeRef, dict: Option<PyDictRef>) -> Self { let inner = Box::into_raw(PyInner::new(payload, typ, dict)); @@ -1074,9 +1076,9 @@ impl<T: PyPayload + std::fmt::Debug> PyRef<T> { } } -impl<T: crate::class::PySubclass + std::fmt::Debug> PyRef<T> +impl<T: crate::class::PySubclass + core::fmt::Debug> PyRef<T> where - T::Base: std::fmt::Debug, + T::Base: core::fmt::Debug, { /// Converts this reference to the base type (ownership transfer). /// # Safety @@ -1086,7 +1088,7 @@ where let obj: PyObjectRef = self.into(); match obj.downcast() { Ok(base_ref) => base_ref, - Err(_) => unsafe { std::hint::unreachable_unchecked() }, + Err(_) => unsafe { core::hint::unreachable_unchecked() }, } } #[inline] @@ -1098,7 +1100,7 @@ where let obj: PyObjectRef = self.into(); match obj.downcast::<U>() { Ok(upcast_ref) => upcast_ref, - Err(_) => unsafe { std::hint::unreachable_unchecked() }, + Err(_) => unsafe { core::hint::unreachable_unchecked() }, } } } @@ -1176,12 +1178,12 @@ impl<T> Deref for PyRef<T> { } } -impl<T> std::hash::Hash for PyRef<T> +impl<T> core::hash::Hash for PyRef<T> where - T: std::hash::Hash + PyPayload, + T: core::hash::Hash + PyPayload, { #[inline] - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + fn hash<H: core::hash::Hasher>(&self, state: &mut H) { self.deref().hash(state) } } @@ -1230,10 +1232,10 @@ macro_rules! partially_init { $($uninit_field: unreachable!(),)* }}; } - let mut m = ::std::mem::MaybeUninit::<$ty>::uninit(); + let mut m = ::core::mem::MaybeUninit::<$ty>::uninit(); #[allow(unused_unsafe)] unsafe { - $(::std::ptr::write(&mut (*m.as_mut_ptr()).$init_field, $init_value);)* + $(::core::ptr::write(&mut (*m.as_mut_ptr()).$init_field, $init_value);)* } m }}; @@ -1241,7 +1243,7 @@ macro_rules! partially_init { pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) { use crate::{builtins::object, class::PyClassImpl}; - use std::mem::MaybeUninit; + use core::mem::MaybeUninit; // `type` inherits from `object` // and both `type` and `object are instances of `type`. diff --git a/crates/vm/src/object/ext.rs b/crates/vm/src/object/ext.rs index 88f5fdc66d7..c1a5f63f85e 100644 --- a/crates/vm/src/object/ext.rs +++ b/crates/vm/src/object/ext.rs @@ -12,9 +12,10 @@ use crate::{ convert::{IntoPyException, ToPyObject, ToPyResult, TryFromObject}, vm::Context, }; -use std::{ +use alloc::fmt; + +use core::{ borrow::Borrow, - fmt, marker::PhantomData, ops::Deref, ptr::{NonNull, null_mut}, @@ -108,7 +109,7 @@ impl<T: PyPayload> AsRef<Py<T>> for PyExact<T> { } } -impl<T: PyPayload> std::borrow::ToOwned for PyExact<T> { +impl<T: PyPayload> alloc::borrow::ToOwned for PyExact<T> { type Owned = PyRefExact<T>; fn to_owned(&self) -> Self::Owned { @@ -581,7 +582,7 @@ impl ToPyObject for &PyObject { // explicitly implementing `ToPyObject`. impl<T> ToPyObject for T where - T: PyPayload + std::fmt::Debug + Sized, + T: PyPayload + core::fmt::Debug + Sized, { #[inline(always)] fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef { diff --git a/crates/vm/src/object/payload.rs b/crates/vm/src/object/payload.rs index 4b900b7caa1..3a2f42675f7 100644 --- a/crates/vm/src/object/payload.rs +++ b/crates/vm/src/object/payload.rs @@ -27,8 +27,8 @@ pub(crate) fn cold_downcast_type_error( pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { #[inline] - fn payload_type_id() -> std::any::TypeId { - std::any::TypeId::of::<Self>() + fn payload_type_id() -> core::any::TypeId { + core::any::TypeId::of::<Self>() } /// # Safety: this function should only be called if `payload_type_id` matches the type of `obj`. @@ -56,7 +56,7 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { #[inline] fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef where - Self: std::fmt::Debug, + Self: core::fmt::Debug, { self.into_ref(&vm.ctx).into() } @@ -64,7 +64,7 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { #[inline] fn _into_ref(self, cls: PyTypeRef, ctx: &Context) -> PyRef<Self> where - Self: std::fmt::Debug, + Self: core::fmt::Debug, { let dict = if cls.slots.flags.has_feature(PyTypeFlags::HAS_DICT) { Some(ctx.new_dict()) @@ -77,7 +77,7 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { #[inline] fn into_exact_ref(self, ctx: &Context) -> PyRefExact<Self> where - Self: std::fmt::Debug, + Self: core::fmt::Debug, { unsafe { // Self::into_ref() always returns exact typed PyRef @@ -88,7 +88,7 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { #[inline] fn into_ref(self, ctx: &Context) -> PyRef<Self> where - Self: std::fmt::Debug, + Self: core::fmt::Debug, { let cls = Self::class(ctx); self._into_ref(cls.to_owned(), ctx) @@ -97,7 +97,7 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { #[inline] fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyTypeRef) -> PyResult<PyRef<Self>> where - Self: std::fmt::Debug, + Self: core::fmt::Debug, { let exact_class = Self::class(&vm.ctx); if cls.fast_issubclass(exact_class) { @@ -138,11 +138,11 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { } pub trait PyObjectPayload: - PyPayload + std::any::Any + std::fmt::Debug + MaybeTraverse + PyThreadingConstraint + 'static + PyPayload + core::any::Any + core::fmt::Debug + MaybeTraverse + PyThreadingConstraint + 'static { } -impl<T: PyPayload + std::fmt::Debug + 'static> PyObjectPayload for T {} +impl<T: PyPayload + core::fmt::Debug + 'static> PyObjectPayload for T {} pub trait SlotOffset { fn offset() -> usize; diff --git a/crates/vm/src/object/traverse.rs b/crates/vm/src/object/traverse.rs index 31bee8becea..2ce0db41a5e 100644 --- a/crates/vm/src/object/traverse.rs +++ b/crates/vm/src/object/traverse.rs @@ -1,4 +1,4 @@ -use std::ptr::NonNull; +use core::ptr::NonNull; use rustpython_common::lock::{PyMutex, PyRwLock}; diff --git a/crates/vm/src/object/traverse_object.rs b/crates/vm/src/object/traverse_object.rs index 281b0e56eb5..075ce5b9513 100644 --- a/crates/vm/src/object/traverse_object.rs +++ b/crates/vm/src/object/traverse_object.rs @@ -1,4 +1,4 @@ -use std::fmt; +use alloc::fmt; use crate::{ PyObject, diff --git a/crates/vm/src/ospath.rs b/crates/vm/src/ospath.rs index 77abbee2cd5..25fcafb74c5 100644 --- a/crates/vm/src/ospath.rs +++ b/crates/vm/src/ospath.rs @@ -230,12 +230,12 @@ impl OsPath { self.path.into_encoded_bytes() } - pub fn to_string_lossy(&self) -> std::borrow::Cow<'_, str> { + pub fn to_string_lossy(&self) -> alloc::borrow::Cow<'_, str> { self.path.to_string_lossy() } - pub fn into_cstring(self, vm: &VirtualMachine) -> PyResult<std::ffi::CString> { - std::ffi::CString::new(self.into_bytes()).map_err(|err| err.to_pyexception(vm)) + pub fn into_cstring(self, vm: &VirtualMachine) -> PyResult<alloc::ffi::CString> { + alloc::ffi::CString::new(self.into_bytes()).map_err(|err| err.to_pyexception(vm)) } #[cfg(windows)] diff --git a/crates/vm/src/protocol/buffer.rs b/crates/vm/src/protocol/buffer.rs index 88524a9a9ee..0fe4d15458b 100644 --- a/crates/vm/src/protocol/buffer.rs +++ b/crates/vm/src/protocol/buffer.rs @@ -10,8 +10,9 @@ use crate::{ object::PyObjectPayload, sliceable::SequenceIndexOp, }; +use alloc::borrow::Cow; +use core::{fmt::Debug, ops::Range}; use itertools::Itertools; -use std::{borrow::Cow, fmt::Debug, ops::Range}; pub struct BufferMethods { pub obj_bytes: fn(&PyBuffer) -> BorrowedValue<'_, [u8]>, @@ -21,7 +22,7 @@ pub struct BufferMethods { } impl Debug for BufferMethods { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("BufferMethods") .field("obj_bytes", &(self.obj_bytes as usize)) .field("obj_bytes_mut", &(self.obj_bytes_mut as usize)) @@ -134,8 +135,8 @@ impl PyBuffer { pub(crate) unsafe fn drop_without_release(&mut self) { // SAFETY: requirements forwarded from caller unsafe { - std::ptr::drop_in_place(&mut self.obj); - std::ptr::drop_in_place(&mut self.desc); + core::ptr::drop_in_place(&mut self.obj); + core::ptr::drop_in_place(&mut self.desc); } } } @@ -414,7 +415,7 @@ pub struct VecBuffer { #[pyclass(flags(BASETYPE, DISALLOW_INSTANTIATION))] impl VecBuffer { pub fn take(&self) -> Vec<u8> { - std::mem::take(&mut self.data.lock()) + core::mem::take(&mut self.data.lock()) } } diff --git a/crates/vm/src/protocol/callable.rs b/crates/vm/src/protocol/callable.rs index 5280e04e928..fa5e48d58ba 100644 --- a/crates/vm/src/protocol/callable.rs +++ b/crates/vm/src/protocol/callable.rs @@ -61,8 +61,8 @@ enum TraceEvent { Return, } -impl std::fmt::Display for TraceEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for TraceEvent { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { use TraceEvent::*; match self { Call => write!(f, "call"), diff --git a/crates/vm/src/protocol/iter.rs b/crates/vm/src/protocol/iter.rs index f6146543de9..aa6ab6769cd 100644 --- a/crates/vm/src/protocol/iter.rs +++ b/crates/vm/src/protocol/iter.rs @@ -4,8 +4,8 @@ use crate::{ convert::{ToPyObject, ToPyResult}, object::{Traverse, TraverseFn}, }; -use std::borrow::Borrow; -use std::ops::Deref; +use core::borrow::Borrow; +use core::ops::Deref; /// Iterator Protocol // https://docs.python.org/3/c-api/iter.html @@ -223,7 +223,7 @@ where vm: &'a VirtualMachine, obj: O, // creating PyIter<O> is zero-cost length_hint: Option<usize>, - _phantom: std::marker::PhantomData<T>, + _phantom: core::marker::PhantomData<T>, } unsafe impl<T, O> Traverse for PyIterIter<'_, T, O> @@ -244,7 +244,7 @@ where vm, obj, length_hint, - _phantom: std::marker::PhantomData, + _phantom: core::marker::PhantomData, } } } diff --git a/crates/vm/src/protocol/mapping.rs b/crates/vm/src/protocol/mapping.rs index 43dafeb9238..6c200043e35 100644 --- a/crates/vm/src/protocol/mapping.rs +++ b/crates/vm/src/protocol/mapping.rs @@ -22,8 +22,8 @@ pub struct PyMappingSlots { >, } -impl std::fmt::Debug for PyMappingSlots { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PyMappingSlots { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str("PyMappingSlots") } } @@ -56,8 +56,8 @@ pub struct PyMappingMethods { Option<fn(PyMapping<'_>, &PyObject, Option<PyObjectRef>, &VirtualMachine) -> PyResult<()>>, } -impl std::fmt::Debug for PyMappingMethods { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PyMappingMethods { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str("PyMappingMethods") } } diff --git a/crates/vm/src/protocol/number.rs b/crates/vm/src/protocol/number.rs index c208bf26de8..58891d1d710 100644 --- a/crates/vm/src/protocol/number.rs +++ b/crates/vm/src/protocol/number.rs @@ -1,4 +1,4 @@ -use std::ops::Deref; +use core::ops::Deref; use crossbeam_utils::atomic::AtomicCell; diff --git a/crates/vm/src/protocol/sequence.rs b/crates/vm/src/protocol/sequence.rs index 888ef91565f..cee46a29089 100644 --- a/crates/vm/src/protocol/sequence.rs +++ b/crates/vm/src/protocol/sequence.rs @@ -29,8 +29,8 @@ pub struct PySequenceSlots { pub inplace_repeat: AtomicCell<Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>>, } -impl std::fmt::Debug for PySequenceSlots { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PySequenceSlots { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str("PySequenceSlots") } } @@ -83,8 +83,8 @@ pub struct PySequenceMethods { pub inplace_repeat: Option<fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult>, } -impl std::fmt::Debug for PySequenceMethods { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PySequenceMethods { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str("PySequenceMethods") } } diff --git a/crates/vm/src/py_io.rs b/crates/vm/src/py_io.rs index a8063673a70..5649463b30e 100644 --- a/crates/vm/src/py_io.rs +++ b/crates/vm/src/py_io.rs @@ -3,7 +3,9 @@ use crate::{ builtins::{PyBaseExceptionRef, PyBytes, PyStr}, common::ascii, }; -use std::{fmt, io, ops}; +use alloc::fmt; +use core::ops; +use std::io; pub trait Write { type Error; diff --git a/crates/vm/src/py_serde.rs b/crates/vm/src/py_serde.rs index f9a5f4bc060..945068113f1 100644 --- a/crates/vm/src/py_serde.rs +++ b/crates/vm/src/py_serde.rs @@ -130,7 +130,7 @@ impl<'de> DeserializeSeed<'de> for PyObjectDeserializer<'de> { impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { type Value = PyObjectRef; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { formatter.write_str("a type that can deserialize in Python") } diff --git a/crates/vm/src/readline.rs b/crates/vm/src/readline.rs index 77402dc6839..d62d520ecbd 100644 --- a/crates/vm/src/readline.rs +++ b/crates/vm/src/readline.rs @@ -5,7 +5,7 @@ use std::{io, path::Path}; -type OtherError = Box<dyn std::error::Error>; +type OtherError = Box<dyn core::error::Error>; type OtherResult<T> = Result<T, OtherError>; pub enum ReadlineResult { diff --git a/crates/vm/src/scope.rs b/crates/vm/src/scope.rs index 4f80e9999ec..74392dc9b73 100644 --- a/crates/vm/src/scope.rs +++ b/crates/vm/src/scope.rs @@ -1,5 +1,5 @@ use crate::{VirtualMachine, builtins::PyDictRef, function::ArgMapping}; -use std::fmt; +use alloc::fmt; #[derive(Clone)] pub struct Scope { diff --git a/crates/vm/src/sequence.rs b/crates/vm/src/sequence.rs index 6e03ad1697e..0bc12fd2631 100644 --- a/crates/vm/src/sequence.rs +++ b/crates/vm/src/sequence.rs @@ -6,8 +6,8 @@ use crate::{ types::PyComparisonOp, vm::{MAX_MEMORY_SIZE, VirtualMachine}, }; +use core::ops::{Deref, Range}; use optional::Optioned; -use std::ops::{Deref, Range}; pub trait MutObjectSequenceOp { type Inner: ?Sized; @@ -100,7 +100,7 @@ where fn mul(&self, vm: &VirtualMachine, n: isize) -> PyResult<Vec<T>> { let n = vm.check_repeat_or_overflow_error(self.as_ref().len(), n)?; - if n > 1 && std::mem::size_of_val(self.as_ref()) >= MAX_MEMORY_SIZE / n { + if n > 1 && core::mem::size_of_val(self.as_ref()) >= MAX_MEMORY_SIZE / n { return Err(vm.new_memory_error("")); } diff --git a/crates/vm/src/signal.rs b/crates/vm/src/signal.rs index 1074c8e8f11..4a1b84a1521 100644 --- a/crates/vm/src/signal.rs +++ b/crates/vm/src/signal.rs @@ -1,12 +1,8 @@ #![cfg_attr(target_os = "wasi", allow(dead_code))] use crate::{PyResult, VirtualMachine}; -use std::{ - fmt, - sync::{ - atomic::{AtomicBool, Ordering}, - mpsc, - }, -}; +use alloc::fmt; +use core::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc; pub(crate) const NSIG: usize = 64; static ANY_TRIGGERED: AtomicBool = AtomicBool::new(false); diff --git a/crates/vm/src/sliceable.rs b/crates/vm/src/sliceable.rs index 786b66fb36a..e416f5a1b49 100644 --- a/crates/vm/src/sliceable.rs +++ b/crates/vm/src/sliceable.rs @@ -3,9 +3,9 @@ use crate::{ PyObject, PyResult, VirtualMachine, builtins::{int::PyInt, slice::PySlice}, }; +use core::ops::Range; use malachite_bigint::BigInt; use num_traits::{Signed, ToPrimitive}; -use std::ops::Range; pub trait SliceableSequenceMutOp where diff --git a/crates/vm/src/stdlib/ast/elif_else_clause.rs b/crates/vm/src/stdlib/ast/elif_else_clause.rs index 581fc499b8a..e2a8789dd08 100644 --- a/crates/vm/src/stdlib/ast/elif_else_clause.rs +++ b/crates/vm/src/stdlib/ast/elif_else_clause.rs @@ -3,7 +3,7 @@ use rustpython_compiler_core::SourceFile; pub(super) fn ast_to_object( clause: ruff::ElifElseClause, - mut rest: std::vec::IntoIter<ruff::ElifElseClause>, + mut rest: alloc::vec::IntoIter<ruff::ElifElseClause>, vm: &VirtualMachine, source_file: &SourceFile, ) -> PyObjectRef { diff --git a/crates/vm/src/stdlib/ast/parameter.rs b/crates/vm/src/stdlib/ast/parameter.rs index 87fa736687b..44fcbb2b464 100644 --- a/crates/vm/src/stdlib/ast/parameter.rs +++ b/crates/vm/src/stdlib/ast/parameter.rs @@ -403,7 +403,7 @@ fn merge_keyword_parameter_defaults( kw_only_args: KeywordParameters, defaults: ParameterDefaults, ) -> Vec<ruff::ParameterWithDefault> { - std::iter::zip(kw_only_args.keywords, defaults.defaults) + core::iter::zip(kw_only_args.keywords, defaults.defaults) .map(|(parameter, default)| ruff::ParameterWithDefault { node_index: Default::default(), parameter, diff --git a/crates/vm/src/stdlib/ast/string.rs b/crates/vm/src/stdlib/ast/string.rs index f3df8d99262..ffa5a3a958a 100644 --- a/crates/vm/src/stdlib/ast/string.rs +++ b/crates/vm/src/stdlib/ast/string.rs @@ -12,7 +12,7 @@ fn ruff_fstring_value_into_iter( }); (0..fstring_value.as_slice().len()).map(move |i| { let tmp = fstring_value.iter_mut().nth(i).unwrap(); - std::mem::replace(tmp, default.clone()) + core::mem::replace(tmp, default.clone()) }) } @@ -28,7 +28,7 @@ fn ruff_fstring_element_into_iter( (0..fstring_element.into_iter().len()).map(move |i| { let fstring_element = &mut fstring_element; let tmp = fstring_element.into_iter().nth(i).unwrap(); - std::mem::replace(tmp, default.clone()) + core::mem::replace(tmp, default.clone()) }) } diff --git a/crates/vm/src/stdlib/atexit.rs b/crates/vm/src/stdlib/atexit.rs index b1832b5481d..2286c36f1db 100644 --- a/crates/vm/src/stdlib/atexit.rs +++ b/crates/vm/src/stdlib/atexit.rs @@ -34,7 +34,7 @@ mod atexit { #[pyfunction] pub fn _run_exitfuncs(vm: &VirtualMachine) { - let funcs: Vec<_> = std::mem::take(&mut *vm.state.atexit_funcs.lock()); + let funcs: Vec<_> = core::mem::take(&mut *vm.state.atexit_funcs.lock()); for (func, args) in funcs.into_iter().rev() { if let Err(e) = func.call(args, vm) { let exit = e.fast_isinstance(vm.ctx.exceptions.system_exit); diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 7cd91f8b4b7..c82161fc553 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -175,7 +175,7 @@ mod builtins { let source = source.borrow_bytes(); // TODO: compiler::compile should probably get bytes - let source = std::str::from_utf8(&source) + let source = core::str::from_utf8(&source) .map_err(|e| vm.new_unicode_decode_error(e.to_string()))?; let flags = args.flags.map_or(Ok(0), |v| v.try_to_primitive(vm))?; @@ -333,7 +333,7 @@ mod builtins { )); } - let source = std::str::from_utf8(source).map_err(|err| { + let source = core::str::from_utf8(source).map_err(|err| { let msg = format!( "(unicode error) 'utf-8' codec can't decode byte 0x{:x?} in position {}: invalid start byte", source[err.valid_up_to()], @@ -605,7 +605,7 @@ mod builtins { } let candidates = match args.args.len().cmp(&1) { - std::cmp::Ordering::Greater => { + core::cmp::Ordering::Greater => { if default.is_some() { return Err(vm.new_type_error(format!( "Cannot specify a default for {func_name}() with multiple positional arguments" @@ -613,8 +613,8 @@ mod builtins { } args.args } - std::cmp::Ordering::Equal => args.args[0].try_to_value(vm)?, - std::cmp::Ordering::Less => { + core::cmp::Ordering::Equal => args.args[0].try_to_value(vm)?, + core::cmp::Ordering::Less => { // zero arguments means type error: return Err( vm.new_type_error(format!("{func_name} expected at least 1 argument, got 0")) diff --git a/crates/vm/src/stdlib/codecs.rs b/crates/vm/src/stdlib/codecs.rs index 821b313090c..1661eef1750 100644 --- a/crates/vm/src/stdlib/codecs.rs +++ b/crates/vm/src/stdlib/codecs.rs @@ -270,7 +270,7 @@ mod _codecs { wide.len() as i32, std::ptr::null_mut(), 0, - std::ptr::null(), + core::ptr::null(), std::ptr::null_mut(), ) }; @@ -291,7 +291,7 @@ mod _codecs { wide.len() as i32, buffer.as_mut_ptr().cast(), size, - std::ptr::null(), + core::ptr::null(), if errors == "strict" { &mut used_default_char } else { @@ -472,7 +472,7 @@ mod _codecs { wide.len() as i32, std::ptr::null_mut(), 0, - std::ptr::null(), + core::ptr::null(), std::ptr::null_mut(), ) }; @@ -493,7 +493,7 @@ mod _codecs { wide.len() as i32, buffer.as_mut_ptr().cast(), size, - std::ptr::null(), + core::ptr::null(), if errors == "strict" { &mut used_default_char } else { diff --git a/crates/vm/src/stdlib/collections.rs b/crates/vm/src/stdlib/collections.rs index eae56968cba..1249fa9315d 100644 --- a/crates/vm/src/stdlib/collections.rs +++ b/crates/vm/src/stdlib/collections.rs @@ -22,9 +22,9 @@ mod _collections { }, utils::collection_repr, }; + use alloc::collections::VecDeque; + use core::cmp::max; use crossbeam_utils::atomic::AtomicCell; - use std::cmp::max; - use std::collections::VecDeque; #[pyattr] #[pyclass(module = "collections", name = "deque", unhashable = true)] @@ -157,7 +157,7 @@ mod _collections { let mut created = VecDeque::from(elements); let mut borrowed = self.borrow_deque_mut(); created.append(&mut borrowed); - std::mem::swap(&mut created, &mut borrowed); + core::mem::swap(&mut created, &mut borrowed); Ok(()) } @@ -426,7 +426,7 @@ mod _collections { inner.get(index).map(|r| r.as_ref()) } - fn do_lock(&self) -> impl std::ops::Deref<Target = Self::Inner> { + fn do_lock(&self) -> impl core::ops::Deref<Target = Self::Inner> { self.borrow_deque() } } @@ -484,7 +484,7 @@ mod _collections { // `maxlen` is better to be defined as UnsafeCell in common practice, // but then more type works without any safety benefits let unsafe_maxlen = - &zelf.maxlen as *const _ as *const std::cell::UnsafeCell<Option<usize>>; + &zelf.maxlen as *const _ as *const core::cell::UnsafeCell<Option<usize>>; *(*unsafe_maxlen).get() = maxlen; } if let Some(elements) = elements { diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index a9c0636bd12..f3b6dd25aca 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -15,11 +15,11 @@ use crate::{ class::PyClassImpl, types::TypeDataRef, }; -use std::ffi::{ +use core::ffi::{ c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, c_ulonglong, c_ushort, }; -use std::mem; +use core::mem; use widestring::WideChar; pub use array::PyCArray; @@ -387,7 +387,7 @@ pub(crate) mod _ctypes { const RTLD_GLOBAL: i32 = 0; #[pyattr] - const SIZEOF_TIME_T: usize = std::mem::size_of::<libc::time_t>(); + const SIZEOF_TIME_T: usize = core::mem::size_of::<libc::time_t>(); #[pyattr] const CTYPES_MAX_ARGCOUNT: usize = 1024; @@ -535,11 +535,11 @@ pub(crate) mod _ctypes { { return Ok(super::get_size(type_str.as_ref())); } - return Ok(std::mem::size_of::<usize>()); + return Ok(core::mem::size_of::<usize>()); } // Pointer types if type_obj.fast_issubclass(PyCPointer::static_type()) { - return Ok(std::mem::size_of::<usize>()); + return Ok(core::mem::size_of::<usize>()); } return Err(vm.new_type_error("this type has no size")); } @@ -550,7 +550,7 @@ pub(crate) mod _ctypes { return Ok(cdata.size()); } if obj.fast_isinstance(PyCPointer::static_type()) { - return Ok(std::mem::size_of::<usize>()); + return Ok(core::mem::size_of::<usize>()); } Err(vm.new_type_error("this type has no size")) @@ -596,13 +596,17 @@ pub(crate) mod _ctypes { } None => { // dlopen(NULL, mode) to get the current process handle (for pythonapi) - let handle = unsafe { libc::dlopen(std::ptr::null(), mode) }; + let handle = unsafe { libc::dlopen(core::ptr::null(), mode) }; if handle.is_null() { let err = unsafe { libc::dlerror() }; let msg = if err.is_null() { "dlopen() error".to_string() } else { - unsafe { std::ffi::CStr::from_ptr(err).to_string_lossy().into_owned() } + unsafe { + core::ffi::CStr::from_ptr(err) + .to_string_lossy() + .into_owned() + } }; return Err(vm.new_os_error(msg)); } @@ -641,7 +645,7 @@ pub(crate) mod _ctypes { name: crate::builtins::PyStrRef, vm: &VirtualMachine, ) -> PyResult<usize> { - let symbol_name = std::ffi::CString::new(name.as_str()) + let symbol_name = alloc::ffi::CString::new(name.as_str()) .map_err(|_| vm.new_value_error("symbol name contains null byte"))?; // Clear previous error @@ -652,7 +656,11 @@ pub(crate) mod _ctypes { // Check for error via dlerror first let err = unsafe { libc::dlerror() }; if !err.is_null() { - let msg = unsafe { std::ffi::CStr::from_ptr(err).to_string_lossy().into_owned() }; + let msg = unsafe { + core::ffi::CStr::from_ptr(err) + .to_string_lossy() + .into_owned() + }; return Err(vm.new_os_error(msg)); } @@ -851,7 +859,7 @@ pub(crate) mod _ctypes { } if obj.fast_isinstance(PyCPointer::static_type()) { // Pointer alignment is always pointer size - return Ok(std::mem::align_of::<usize>()); + return Ok(core::mem::align_of::<usize>()); } if obj.fast_isinstance(PyCUnion::static_type()) { // Calculate alignment from _fields_ @@ -914,7 +922,7 @@ pub(crate) mod _ctypes { #[pyfunction] fn resize(obj: PyObjectRef, size: isize, vm: &VirtualMachine) -> PyResult<()> { - use std::borrow::Cow; + use alloc::borrow::Cow; // 1. Get StgInfo from object's class (validates ctypes instance) let stg_info = obj @@ -1148,8 +1156,8 @@ pub(crate) mod _ctypes { } let raw_ptr = ptr as *mut crate::object::PyObject; unsafe { - let obj = crate::PyObjectRef::from_raw(std::ptr::NonNull::new_unchecked(raw_ptr)); - let obj = std::mem::ManuallyDrop::new(obj); + let obj = crate::PyObjectRef::from_raw(core::ptr::NonNull::new_unchecked(raw_ptr)); + let obj = core::mem::ManuallyDrop::new(obj); Ok((*obj).clone()) } } @@ -1208,12 +1216,12 @@ pub(crate) mod _ctypes { FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - std::ptr::null(), + core::ptr::null(), error_code, 0, &mut buffer as *mut *mut u16 as *mut u16, 0, - std::ptr::null(), + core::ptr::null(), ) }; @@ -1280,7 +1288,7 @@ pub(crate) mod _ctypes { let vtable = *iunknown; debug_assert!(!vtable.is_null(), "IUnknown vtable is null"); let addref_fn: extern "system" fn(*mut std::ffi::c_void) -> u32 = - std::mem::transmute(*vtable.add(1)); // AddRef is index 1 + core::mem::transmute(*vtable.add(1)); // AddRef is index 1 addref_fn(src_ptr as *mut std::ffi::c_void); } } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 25708b57f8e..168da2bcc01 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -631,7 +631,7 @@ impl PyCArray { let ptr_val = usize::from_ne_bytes( ptr_bytes .try_into() - .unwrap_or([0; std::mem::size_of::<usize>()]), + .unwrap_or([0; core::mem::size_of::<usize>()]), ); if ptr_val == 0 { return Ok(vm.ctx.none()); @@ -643,7 +643,7 @@ impl PyCArray { while *ptr.add(len) != 0 { len += 1; } - let bytes = std::slice::from_raw_parts(ptr, len); + let bytes = core::slice::from_raw_parts(ptr, len); Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) } } @@ -656,7 +656,7 @@ impl PyCArray { let ptr_val = usize::from_ne_bytes( ptr_bytes .try_into() - .unwrap_or([0; std::mem::size_of::<usize>()]), + .unwrap_or([0; core::mem::size_of::<usize>()]), ); if ptr_val == 0 { return Ok(vm.ctx.none()); @@ -668,10 +668,10 @@ impl PyCArray { let mut pos = 0usize; loop { let code = if WCHAR_SIZE == 2 { - let bytes = std::slice::from_raw_parts(ptr.add(pos), 2); + let bytes = core::slice::from_raw_parts(ptr.add(pos), 2); u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 } else { - let bytes = std::slice::from_raw_parts(ptr.add(pos), 4); + let bytes = core::slice::from_raw_parts(ptr.add(pos), 4); u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) }; if code == 0 { @@ -1136,7 +1136,7 @@ impl AsBuffer for PyCArray { len: buffer_len, readonly: false, itemsize, - format: std::borrow::Cow::Owned(fmt), + format: alloc::borrow::Cow::Owned(fmt), dim_desc, } } else { @@ -1291,7 +1291,7 @@ fn add_wchar_array_getsets(array_type: &Py<PyType>, vm: &VirtualMachine) { // Linux/macOS: sizeof(wchar_t) == 4 (UTF-32) /// Size of wchar_t on this platform -pub(super) const WCHAR_SIZE: usize = std::mem::size_of::<libc::wchar_t>(); +pub(super) const WCHAR_SIZE: usize = core::mem::size_of::<libc::wchar_t>(); /// Read a single wchar_t from bytes (platform-endian) #[inline] diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 0f859b3d10b..1f9eaeef56a 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -7,15 +7,15 @@ use crate::types::{GetDescriptor, Representable}; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, }; +use alloc::borrow::Cow; +use core::ffi::{ + c_double, c_float, c_int, c_long, c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort, +}; +use core::fmt::Debug; +use core::mem; use crossbeam_utils::atomic::AtomicCell; use num_traits::{Signed, ToPrimitive}; use rustpython_common::lock::PyRwLock; -use std::borrow::Cow; -use std::ffi::{ - c_double, c_float, c_int, c_long, c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort, -}; -use std::fmt::Debug; -use std::mem; use widestring::WideChar; // StgInfo - Storage information for ctypes types @@ -105,8 +105,8 @@ pub struct StgInfo { unsafe impl Send for StgInfo {} unsafe impl Sync for StgInfo {} -impl std::fmt::Debug for StgInfo { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for StgInfo { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("StgInfo") .field("initialized", &self.initialized) .field("size", &self.size) @@ -227,7 +227,7 @@ impl StgInfo { libffi::middle::Type::structure(self.ffi_field_types.iter().cloned()) } else if self.size <= MAX_FFI_STRUCT_SIZE { // Small struct without field types: use bytes array - libffi::middle::Type::structure(std::iter::repeat_n( + libffi::middle::Type::structure(core::iter::repeat_n( libffi::middle::Type::u8(), self.size, )) @@ -242,9 +242,9 @@ impl StgInfo { libffi::middle::Type::pointer() } else if let Some(ref fmt) = self.format { let elem_type = Self::format_to_ffi_type(fmt); - libffi::middle::Type::structure(std::iter::repeat_n(elem_type, self.length)) + libffi::middle::Type::structure(core::iter::repeat_n(elem_type, self.length)) } else { - libffi::middle::Type::structure(std::iter::repeat_n( + libffi::middle::Type::structure(core::iter::repeat_n( libffi::middle::Type::u8(), self.size, )) @@ -373,10 +373,10 @@ pub(super) static CDATA_BUFFER_METHODS: BufferMethods = BufferMethods { /// Convert Vec<T> to Vec<u8> by reinterpreting the memory (same allocation). fn vec_to_bytes<T>(vec: Vec<T>) -> Vec<u8> { - let len = vec.len() * std::mem::size_of::<T>(); - let cap = vec.capacity() * std::mem::size_of::<T>(); + let len = vec.len() * core::mem::size_of::<T>(); + let cap = vec.capacity() * core::mem::size_of::<T>(); let ptr = vec.as_ptr() as *mut u8; - std::mem::forget(vec); + core::mem::forget(vec); unsafe { Vec::from_raw_parts(ptr, len, cap) } } @@ -406,7 +406,7 @@ pub(super) fn str_to_wchar_bytes(s: &str, vm: &VirtualMachine) -> (PyObjectRef, let wchars: Vec<libc::wchar_t> = s .chars() .map(|c| c as libc::wchar_t) - .chain(std::iter::once(0)) + .chain(core::iter::once(0)) .collect(); let ptr = wchars.as_ptr() as usize; let bytes = vec_to_bytes(wchars); @@ -486,7 +486,7 @@ impl PyCData { pub unsafe fn at_address(ptr: *const u8, size: usize) -> Self { // = PyCData_AtAddress // SAFETY: Caller must ensure ptr is valid for the lifetime of returned PyCData - let slice: &'static [u8] = unsafe { std::slice::from_raw_parts(ptr, size) }; + let slice: &'static [u8] = unsafe { core::slice::from_raw_parts(ptr, size) }; PyCData { buffer: PyRwLock::new(Cow::Borrowed(slice)), base: PyRwLock::new(None), @@ -534,7 +534,7 @@ impl PyCData { ) -> Self { // = PyCData_FromBaseObj // SAFETY: ptr points into base_obj's buffer, kept alive via base reference - let slice: &'static [u8] = unsafe { std::slice::from_raw_parts(ptr, size) }; + let slice: &'static [u8] = unsafe { core::slice::from_raw_parts(ptr, size) }; PyCData { buffer: PyRwLock::new(Cow::Borrowed(slice)), base: PyRwLock::new(Some(base_obj)), @@ -561,7 +561,7 @@ impl PyCData { vm: &VirtualMachine, ) -> Self { // SAFETY: Caller must ensure ptr is valid for the lifetime of source - let slice: &'static [u8] = unsafe { std::slice::from_raw_parts(ptr, size) }; + let slice: &'static [u8] = unsafe { core::slice::from_raw_parts(ptr, size) }; // Python stores the reference in a dict with key "-1" (unique_key pattern) let objects_dict = vm.ctx.new_dict(); @@ -707,7 +707,7 @@ impl PyCData { // (e.g., from from_address pointing to a ctypes buffer) unsafe { let ptr = slice.as_ptr() as *mut u8; - std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(offset), bytes.len()); + core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(offset), bytes.len()); } } Cow::Owned(_) => { @@ -893,7 +893,7 @@ impl PyCData { if let Some(bytes_val) = value.downcast_ref::<PyBytes>() { let src = bytes_val.as_bytes(); let to_copy = PyCField::bytes_for_char_array(src); - let copy_len = std::cmp::min(to_copy.len(), size); + let copy_len = core::cmp::min(to_copy.len(), size); self.write_bytes_at_offset(offset, &to_copy[..copy_len]); self.keep_ref(index, value, vm)?; return Ok(()); @@ -936,7 +936,7 @@ impl PyCData { array_buffer.as_ptr() as usize }; let addr_bytes = buffer_addr.to_ne_bytes(); - let len = std::cmp::min(addr_bytes.len(), size); + let len = core::cmp::min(addr_bytes.len(), size); self.write_bytes_at_offset(offset, &addr_bytes[..len]); self.keep_ref(index, value, vm)?; return Ok(()); @@ -1364,7 +1364,7 @@ impl PyCField { if let Some(bytes) = value.downcast_ref::<PyBytes>() { let src = bytes.as_bytes(); let mut result = vec![0u8; size]; - let len = std::cmp::min(src.len(), size); + let len = core::cmp::min(src.len(), size); result[..len].copy_from_slice(&src[..len]); Ok(result) } @@ -1372,7 +1372,7 @@ impl PyCField { else if let Some(cdata) = value.downcast_ref::<super::PyCData>() { let buffer = cdata.buffer.read(); let mut result = vec![0u8; size]; - let len = std::cmp::min(buffer.len(), size); + let len = core::cmp::min(buffer.len(), size); result[..len].copy_from_slice(&buffer[..len]); Ok(result) } @@ -1473,7 +1473,7 @@ impl PyCField { let (converted, ptr) = ensure_z_null_terminated(bytes, vm); let mut result = vec![0u8; size]; let addr_bytes = ptr.to_ne_bytes(); - let len = std::cmp::min(addr_bytes.len(), size); + let len = core::cmp::min(addr_bytes.len(), size); result[..len].copy_from_slice(&addr_bytes[..len]); return Ok((result, Some(converted))); } @@ -1482,7 +1482,7 @@ impl PyCField { let v = int_val.as_bigint().to_usize().unwrap_or(0); let mut result = vec![0u8; size]; let bytes = v.to_ne_bytes(); - let len = std::cmp::min(bytes.len(), size); + let len = core::cmp::min(bytes.len(), size); result[..len].copy_from_slice(&bytes[..len]); return Ok((result, None)); } @@ -1498,7 +1498,7 @@ impl PyCField { let (holder, ptr) = str_to_wchar_bytes(s.as_str(), vm); let mut result = vec![0u8; size]; let addr_bytes = ptr.to_ne_bytes(); - let len = std::cmp::min(addr_bytes.len(), size); + let len = core::cmp::min(addr_bytes.len(), size); result[..len].copy_from_slice(&addr_bytes[..len]); return Ok((result, Some(holder))); } @@ -1507,7 +1507,7 @@ impl PyCField { let v = int_val.as_bigint().to_usize().unwrap_or(0); let mut result = vec![0u8; size]; let bytes = v.to_ne_bytes(); - let len = std::cmp::min(bytes.len(), size); + let len = core::cmp::min(bytes.len(), size); result[..len].copy_from_slice(&bytes[..len]); return Ok((result, None)); } @@ -1523,7 +1523,7 @@ impl PyCField { let v = int_val.as_bigint().to_usize().unwrap_or(0); let mut result = vec![0u8; size]; let bytes = v.to_ne_bytes(); - let len = std::cmp::min(bytes.len(), size); + let len = core::cmp::min(bytes.len(), size); result[..len].copy_from_slice(&bytes[..len]); return Ok((result, None)); } @@ -2078,7 +2078,7 @@ pub(super) fn bytes_to_pyobject( if ptr == 0 { return Ok(vm.ctx.none()); } - let c_str = unsafe { std::ffi::CStr::from_ptr(ptr as _) }; + let c_str = unsafe { core::ffi::CStr::from_ptr(ptr as _) }; Ok(vm.ctx.new_bytes(c_str.to_bytes().to_vec()).into()) } "Z" => { @@ -2089,7 +2089,7 @@ pub(super) fn bytes_to_pyobject( } let len = unsafe { libc::wcslen(ptr as *const libc::wchar_t) }; let wchars = - unsafe { std::slice::from_raw_parts(ptr as *const libc::wchar_t, len) }; + unsafe { core::slice::from_raw_parts(ptr as *const libc::wchar_t, len) }; let s: String = wchars .iter() .filter_map(|&c| char::from_u32(c as u32)) @@ -2149,7 +2149,7 @@ pub(super) fn get_usize_attr( /// Read a pointer value from buffer #[inline] pub(super) fn read_ptr_from_buffer(buffer: &[u8]) -> usize { - const PTR_SIZE: usize = std::mem::size_of::<usize>(); + const PTR_SIZE: usize = core::mem::size_of::<usize>(); if buffer.len() >= PTR_SIZE { usize::from_ne_bytes(buffer[..PTR_SIZE].try_into().unwrap()) } else { @@ -2242,7 +2242,7 @@ pub(super) fn get_field_size(field_type: &PyObject, vm: &VirtualMachine) -> PyRe return Ok(s); } - Ok(std::mem::size_of::<usize>()) + Ok(core::mem::size_of::<usize>()) } /// Get the alignment of a ctypes field type diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 55a42f0ba15..5906bc91cd4 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -16,6 +16,9 @@ use crate::{ types::{AsBuffer, Callable, Constructor, Initializer, Representable}, vm::thread::with_current_vm, }; +use alloc::borrow::Cow; +use core::ffi::c_void; +use core::fmt::Debug; use libffi::{ low, middle::{Arg, Cif, Closure, CodePtr, Type}, @@ -23,9 +26,6 @@ use libffi::{ use libloading::Symbol; use num_traits::{Signed, ToPrimitive}; use rustpython_common::lock::PyRwLock; -use std::borrow::Cow; -use std::ffi::c_void; -use std::fmt::Debug; // Internal function addresses for special ctypes functions pub(super) const INTERNAL_CAST_ADDR: usize = 1; @@ -37,7 +37,7 @@ std::thread_local! { /// Thread-local storage for ctypes errno /// This is separate from the system errno - ctypes swaps them during FFI calls /// when use_errno=True is specified. - static CTYPES_LOCAL_ERRNO: std::cell::Cell<i32> = const { std::cell::Cell::new(0) }; + static CTYPES_LOCAL_ERRNO: core::cell::Cell<i32> = const { core::cell::Cell::new(0) }; } /// Get ctypes thread-local errno value @@ -79,7 +79,7 @@ where #[cfg(windows)] std::thread_local! { /// Thread-local storage for ctypes last_error (Windows only) - static CTYPES_LOCAL_LAST_ERROR: std::cell::Cell<u32> = const { std::cell::Cell::new(0) }; + static CTYPES_LOCAL_LAST_ERROR: core::cell::Cell<u32> = const { core::cell::Cell::new(0) }; } #[cfg(windows)] @@ -135,14 +135,14 @@ fn ffi_type_from_tag(tag: u8) -> Type { b'i' => Type::i32(), b'I' => Type::u32(), b'l' => { - if std::mem::size_of::<libc::c_long>() == 8 { + if core::mem::size_of::<libc::c_long>() == 8 { Type::i64() } else { Type::i32() } } b'L' => { - if std::mem::size_of::<libc::c_ulong>() == 8 { + if core::mem::size_of::<libc::c_ulong>() == 8 { Type::u64() } else { Type::u32() @@ -154,7 +154,7 @@ fn ffi_type_from_tag(tag: u8) -> Type { b'd' | b'g' => Type::f64(), b'?' => Type::u8(), b'u' => { - if std::mem::size_of::<super::WideChar>() == 2 { + if core::mem::size_of::<super::WideChar>() == 2 { Type::u16() } else { Type::u32() @@ -207,7 +207,7 @@ fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult<FfiArgV // 5. PyCSimple (c_void_p, c_char_p, etc.) -> value from buffer if let Some(simple) = value.downcast_ref::<PyCSimple>() { let buffer = simple.0.buffer.read(); - if buffer.len() >= std::mem::size_of::<usize>() { + if buffer.len() >= core::mem::size_of::<usize>() { let addr = super::base::read_ptr_from_buffer(&buffer); return Ok(FfiArgValue::Pointer(addr)); } @@ -283,7 +283,7 @@ fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult<Argument> { let wide: Vec<u16> = s .as_str() .encode_utf16() - .chain(std::iter::once(0)) + .chain(core::iter::once(0)) .collect(); let wide_bytes: Vec<u8> = wide.iter().flat_map(|&x| x.to_ne_bytes()).collect(); let keep = vm.ctx.new_bytes(wide_bytes); @@ -499,7 +499,7 @@ impl Initializer for PyCFuncPtrType { new_type.check_not_initialized(vm)?; - let ptr_size = std::mem::size_of::<usize>(); + let ptr_size = core::mem::size_of::<usize>(); let mut stg_info = StgInfo::new(ptr_size, ptr_size); stg_info.format = Some("X{}".to_string()); stg_info.length = 1; @@ -552,7 +552,7 @@ pub(super) struct PyCFuncPtr { } impl Debug for PyCFuncPtr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("PyCFuncPtr") .field("func_ptr", &self.get_func_ptr()) .finish() @@ -567,9 +567,9 @@ fn extract_ptr_from_arg(arg: &PyObject, vm: &VirtualMachine) -> PyResult<usize> } if let Some(simple) = arg.downcast_ref::<PyCSimple>() { let buffer = simple.0.buffer.read(); - if buffer.len() >= std::mem::size_of::<usize>() { + if buffer.len() >= core::mem::size_of::<usize>() { return Ok(usize::from_ne_bytes( - buffer[..std::mem::size_of::<usize>()].try_into().unwrap(), + buffer[..core::mem::size_of::<usize>()].try_into().unwrap(), )); } } @@ -612,7 +612,7 @@ fn string_at_impl(ptr: usize, size: isize, vm: &VirtualMachine) -> PyResult { } size_usize }; - let bytes = unsafe { std::slice::from_raw_parts(ptr, len) }; + let bytes = unsafe { core::slice::from_raw_parts(ptr, len) }; Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) } @@ -627,12 +627,12 @@ fn wstring_at_impl(ptr: usize, size: isize, vm: &VirtualMachine) -> PyResult { } else { // Overflow check for huge size values let size_usize = size as usize; - if size_usize > isize::MAX as usize / std::mem::size_of::<libc::wchar_t>() { + if size_usize > isize::MAX as usize / core::mem::size_of::<libc::wchar_t>() { return Err(vm.new_overflow_error("string too long")); } size_usize }; - let wchars = unsafe { std::slice::from_raw_parts(w_ptr, len) }; + let wchars = unsafe { core::slice::from_raw_parts(w_ptr, len) }; // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide // macOS/Linux: wchar_t = i32 (UTF-32) -> convert via char::from_u32 @@ -815,7 +815,7 @@ impl Constructor for PyCFuncPtr { // 3. Tuple argument: (name, dll) form // 4. Callable: callback creation - let ptr_size = std::mem::size_of::<usize>(); + let ptr_size = core::mem::size_of::<usize>(); if args.args.is_empty() { return PyCFuncPtr { @@ -1513,11 +1513,11 @@ fn convert_raw_result( RawResult::Void => return None, RawResult::Pointer(ptr) => { let bytes = ptr.to_ne_bytes(); - (bytes.to_vec(), std::mem::size_of::<usize>()) + (bytes.to_vec(), core::mem::size_of::<usize>()) } RawResult::Value(val) => { let bytes = val.to_ne_bytes(); - (bytes.to_vec(), std::mem::size_of::<i64>()) + (bytes.to_vec(), core::mem::size_of::<i64>()) } }; @@ -1702,7 +1702,7 @@ impl Callable for PyCFuncPtr { None => { debug_assert!(false, "NULL function pointer"); // In release mode, this will crash - CodePtr(std::ptr::null_mut()) + CodePtr(core::ptr::null_mut()) } }; @@ -1758,7 +1758,7 @@ impl AsBuffer for PyCFuncPtr { stg_info.size, ) } else { - (Cow::Borrowed("X{}"), std::mem::size_of::<usize>()) + (Cow::Borrowed("X{}"), core::mem::size_of::<usize>()) }; let desc = BufferDescriptor { len: itemsize, @@ -1902,7 +1902,7 @@ fn ffi_to_python(ty: &Py<PyType>, ptr: *const c_void, vm: &VirtualMachine) -> Py if cstr_ptr.is_null() { vm.ctx.none() } else { - let cstr = std::ffi::CStr::from_ptr(cstr_ptr); + let cstr = core::ffi::CStr::from_ptr(cstr_ptr); vm.ctx.new_bytes(cstr.to_bytes().to_vec()).into() } } @@ -1916,7 +1916,7 @@ fn ffi_to_python(ty: &Py<PyType>, ptr: *const c_void, vm: &VirtualMachine) -> Py while *wstr_ptr.add(len) != 0 { len += 1; } - let slice = std::slice::from_raw_parts(wstr_ptr, len); + let slice = core::slice::from_raw_parts(wstr_ptr, len); // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide // Unix: wchar_t = i32 (UTF-32) -> convert via char::from_u32 #[cfg(windows)] @@ -2113,7 +2113,7 @@ pub(super) struct PyCThunk { } impl Debug for PyCThunk { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("PyCThunk") .field("callable", &self.callable) .finish() diff --git a/crates/vm/src/stdlib/ctypes/library.rs b/crates/vm/src/stdlib/ctypes/library.rs index 7512ce29d8a..35ccb433845 100644 --- a/crates/vm/src/stdlib/ctypes/library.rs +++ b/crates/vm/src/stdlib/ctypes/library.rs @@ -1,9 +1,9 @@ use crate::VirtualMachine; +use alloc::fmt; use libloading::Library; use rustpython_common::lock::{PyMutex, PyRwLock}; use std::collections::HashMap; use std::ffi::OsStr; -use std::fmt; #[cfg(unix)] use libloading::os::unix::Library as UnixLibrary; @@ -54,7 +54,7 @@ impl SharedLibrary { // On Windows: HMODULE (*mut c_void) // On Unix: *mut c_void from dlopen // We use transmute_copy to read the handle without consuming the Library - unsafe { std::mem::transmute_copy::<Library, usize>(l) } + unsafe { core::mem::transmute_copy::<Library, usize>(l) } } else { 0 } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index ae97b741b3c..f564fd1965c 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -8,8 +8,8 @@ use crate::{ class::StaticType, function::{FuncArgs, OptionalArg}, }; +use alloc::borrow::Cow; use num_traits::ToPrimitive; -use std::borrow::Cow; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] #[derive(Debug)] @@ -37,7 +37,7 @@ impl Initializer for PyCPointerType { .and_then(|obj| obj.downcast::<PyType>().ok()); // Initialize StgInfo for pointer type - let pointer_size = std::mem::size_of::<usize>(); + let pointer_size = core::mem::size_of::<usize>(); let mut stg_info = StgInfo::new(pointer_size, pointer_size); stg_info.proto = proto; stg_info.paramfunc = super::base::ParamFunc::Pointer; @@ -232,7 +232,7 @@ impl Constructor for PyCPointer { // Create a new PyCPointer instance with NULL pointer (all zeros) // Initial contents is set via __init__ if provided - let cdata = PyCData::from_bytes(vec![0u8; std::mem::size_of::<usize>()], None); + let cdata = PyCData::from_bytes(vec![0u8; core::mem::size_of::<usize>()], None); // pointer instance has b_length set to 2 (for index 0 and 1) cdata.length.store(2); PyCPointer(cdata) @@ -299,7 +299,7 @@ impl PyCPointer { let proto_type = stg_info.proto(); let element_size = proto_type .stg_info_opt() - .map_or(std::mem::size_of::<usize>(), |info| info.size); + .map_or(core::mem::size_of::<usize>(), |info| info.size); // Create instance that references the memory directly // PyCData.into_ref_with_type works for all ctypes (simple, structure, union, array, pointer) @@ -383,7 +383,7 @@ impl PyCPointer { let proto_type = stg_info.proto(); let element_size = proto_type .stg_info_opt() - .map_or(std::mem::size_of::<usize>(), |info| info.size); + .map_or(core::mem::size_of::<usize>(), |info| info.size); // offset = index * iteminfo->size let offset = index * element_size as isize; @@ -468,7 +468,7 @@ impl PyCPointer { let element_size = if let Some(ref proto_type) = stg_info.proto { proto_type.stg_info_opt().expect("proto has StgInfo").size } else { - std::mem::size_of::<usize>() + core::mem::size_of::<usize>() }; let type_code = stg_info .proto @@ -489,7 +489,7 @@ impl PyCPointer { // Optimized contiguous copy let start_addr = (ptr_value as isize + start * element_size as isize) as *const u8; unsafe { - result.extend_from_slice(std::slice::from_raw_parts(start_addr, len)); + result.extend_from_slice(core::slice::from_raw_parts(start_addr, len)); } } else { let mut cur = start; @@ -510,7 +510,7 @@ impl PyCPointer { return Ok(vm.ctx.new_str("").into()); } let mut result = String::with_capacity(len); - let wchar_size = std::mem::size_of::<libc::wchar_t>(); + let wchar_size = core::mem::size_of::<libc::wchar_t>(); let mut cur = start; for _ in 0..len { let addr = (ptr_value as isize + cur * wchar_size as isize) as *const libc::wchar_t; @@ -578,7 +578,7 @@ impl PyCPointer { let element_size = proto_type .stg_info_opt() - .map_or(std::mem::size_of::<usize>(), |info| info.size); + .map_or(core::mem::size_of::<usize>(), |info| info.size); // Calculate address let offset = index * element_size as isize; @@ -595,7 +595,7 @@ impl PyCPointer { let copy_len = src_buffer.len().min(element_size); unsafe { let dest_ptr = addr as *mut u8; - std::ptr::copy_nonoverlapping(src_buffer.as_ptr(), dest_ptr, copy_len); + core::ptr::copy_nonoverlapping(src_buffer.as_ptr(), dest_ptr, copy_len); } } else { // Handle z/Z specially to store converted value @@ -641,43 +641,43 @@ impl PyCPointer { // Multi-byte types need read_unaligned for safety on strict-alignment architectures Some("h") => Ok(vm .ctx - .new_int(std::ptr::read_unaligned(ptr as *const i16) as i32) + .new_int(core::ptr::read_unaligned(ptr as *const i16) as i32) .into()), Some("H") => Ok(vm .ctx - .new_int(std::ptr::read_unaligned(ptr as *const u16) as i32) + .new_int(core::ptr::read_unaligned(ptr as *const u16) as i32) .into()), Some("i") | Some("l") => Ok(vm .ctx - .new_int(std::ptr::read_unaligned(ptr as *const i32)) + .new_int(core::ptr::read_unaligned(ptr as *const i32)) .into()), Some("I") | Some("L") => Ok(vm .ctx - .new_int(std::ptr::read_unaligned(ptr as *const u32)) + .new_int(core::ptr::read_unaligned(ptr as *const u32)) .into()), Some("q") => Ok(vm .ctx - .new_int(std::ptr::read_unaligned(ptr as *const i64)) + .new_int(core::ptr::read_unaligned(ptr as *const i64)) .into()), Some("Q") => Ok(vm .ctx - .new_int(std::ptr::read_unaligned(ptr as *const u64)) + .new_int(core::ptr::read_unaligned(ptr as *const u64)) .into()), Some("f") => Ok(vm .ctx - .new_float(std::ptr::read_unaligned(ptr as *const f32) as f64) + .new_float(core::ptr::read_unaligned(ptr as *const f32) as f64) .into()), Some("d") | Some("g") => Ok(vm .ctx - .new_float(std::ptr::read_unaligned(ptr as *const f64)) + .new_float(core::ptr::read_unaligned(ptr as *const f64)) .into()), Some("P") | Some("z") | Some("Z") => Ok(vm .ctx - .new_int(std::ptr::read_unaligned(ptr as *const usize)) + .new_int(core::ptr::read_unaligned(ptr as *const usize)) .into()), _ => { // Default: read as bytes - let bytes = std::slice::from_raw_parts(ptr, size).to_vec(); + let bytes = core::slice::from_raw_parts(ptr, size).to_vec(); Ok(vm.ctx.new_bytes(bytes).into()) } } @@ -708,7 +708,7 @@ impl PyCPointer { "bytes/string or integer address expected".to_owned(), )); }; - std::ptr::write_unaligned(ptr as *mut usize, ptr_val); + core::ptr::write_unaligned(ptr as *mut usize, ptr_val); return Ok(()); } _ => {} @@ -723,19 +723,19 @@ impl PyCPointer { *ptr = i.to_u8().expect("int too large"); } 2 => { - std::ptr::write_unaligned( + core::ptr::write_unaligned( ptr as *mut i16, i.to_i16().expect("int too large"), ); } 4 => { - std::ptr::write_unaligned( + core::ptr::write_unaligned( ptr as *mut i32, i.to_i32().expect("int too large"), ); } 8 => { - std::ptr::write_unaligned( + core::ptr::write_unaligned( ptr as *mut i64, i.to_i64().expect("int too large"), ); @@ -743,7 +743,7 @@ impl PyCPointer { _ => { let bytes = i.to_signed_bytes_le(); let copy_len = bytes.len().min(size); - std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, copy_len); + core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, copy_len); } } return Ok(()); @@ -754,10 +754,10 @@ impl PyCPointer { let f = float_val.to_f64(); match size { 4 => { - std::ptr::write_unaligned(ptr as *mut f32, f as f32); + core::ptr::write_unaligned(ptr as *mut f32, f as f32); } 8 => { - std::ptr::write_unaligned(ptr as *mut f64, f); + core::ptr::write_unaligned(ptr as *mut f64, f); } _ => {} } @@ -767,7 +767,7 @@ impl PyCPointer { // Try bytes if let Ok(bytes) = value.try_bytes_like(vm, |b| b.to_vec()) { let copy_len = bytes.len().min(size); - std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, copy_len); + core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, copy_len); return Ok(()); } diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs index fbb17620fe4..9835953812f 100644 --- a/crates/vm/src/stdlib/ctypes/simple.rs +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -13,9 +13,9 @@ use crate::function::{Either, FuncArgs, OptionalArg}; use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}; use crate::types::{AsBuffer, AsNumber, Constructor, Initializer, Representable}; use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; +use alloc::borrow::Cow; +use core::fmt::Debug; use num_traits::ToPrimitive; -use std::borrow::Cow; -use std::fmt::Debug; /// Valid type codes for ctypes simple types // spell-checker: disable-next-line @@ -27,22 +27,22 @@ pub(super) const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQPXOv?g"; fn ctypes_code_to_pep3118(code: char) -> char { match code { // c_int: map based on sizeof(int) - 'i' if std::mem::size_of::<std::ffi::c_int>() == 2 => 'h', - 'i' if std::mem::size_of::<std::ffi::c_int>() == 4 => 'i', - 'i' if std::mem::size_of::<std::ffi::c_int>() == 8 => 'q', - 'I' if std::mem::size_of::<std::ffi::c_int>() == 2 => 'H', - 'I' if std::mem::size_of::<std::ffi::c_int>() == 4 => 'I', - 'I' if std::mem::size_of::<std::ffi::c_int>() == 8 => 'Q', + 'i' if core::mem::size_of::<core::ffi::c_int>() == 2 => 'h', + 'i' if core::mem::size_of::<core::ffi::c_int>() == 4 => 'i', + 'i' if core::mem::size_of::<core::ffi::c_int>() == 8 => 'q', + 'I' if core::mem::size_of::<core::ffi::c_int>() == 2 => 'H', + 'I' if core::mem::size_of::<core::ffi::c_int>() == 4 => 'I', + 'I' if core::mem::size_of::<core::ffi::c_int>() == 8 => 'Q', // c_long: map based on sizeof(long) - 'l' if std::mem::size_of::<std::ffi::c_long>() == 4 => 'l', - 'l' if std::mem::size_of::<std::ffi::c_long>() == 8 => 'q', - 'L' if std::mem::size_of::<std::ffi::c_long>() == 4 => 'L', - 'L' if std::mem::size_of::<std::ffi::c_long>() == 8 => 'Q', + 'l' if core::mem::size_of::<core::ffi::c_long>() == 4 => 'l', + 'l' if core::mem::size_of::<core::ffi::c_long>() == 8 => 'q', + 'L' if core::mem::size_of::<core::ffi::c_long>() == 4 => 'L', + 'L' if core::mem::size_of::<core::ffi::c_long>() == 8 => 'Q', // c_bool: map based on sizeof(bool) - typically 1 byte on all platforms - '?' if std::mem::size_of::<bool>() == 1 => '?', - '?' if std::mem::size_of::<bool>() == 2 => 'H', - '?' if std::mem::size_of::<bool>() == 4 => 'L', - '?' if std::mem::size_of::<bool>() == 8 => 'Q', + '?' if core::mem::size_of::<bool>() == 1 => '?', + '?' if core::mem::size_of::<bool>() == 2 => 'H', + '?' if core::mem::size_of::<bool>() == 4 => 'L', + '?' if core::mem::size_of::<bool>() == 8 => 'Q', // Default: use the same code _ => code, } @@ -268,7 +268,7 @@ impl PyCSimpleType { let create_simple_with_value = |type_str: &str, val: &PyObject| -> PyResult { let simple = new_simple_type(Either::B(&cls), vm)?; let buffer_bytes = value_to_bytes_endian(type_str, val, false, vm); - *simple.0.buffer.write() = std::borrow::Cow::Owned(buffer_bytes.clone()); + *simple.0.buffer.write() = alloc::borrow::Cow::Owned(buffer_bytes.clone()); let simple_obj: PyObjectRef = simple.into_ref_with_type(vm, cls.clone())?.into(); // from_param returns CArgObject, not the simple type itself let tag = type_str.as_bytes().first().copied().unwrap_or(b'?'); @@ -418,9 +418,9 @@ impl PyCSimpleType { if let Some(funcptr) = value.downcast_ref::<PyCFuncPtr>() { let ptr_val = { let buffer = funcptr._base.buffer.read(); - if buffer.len() >= std::mem::size_of::<usize>() { + if buffer.len() >= core::mem::size_of::<usize>() { usize::from_ne_bytes( - buffer[..std::mem::size_of::<usize>()].try_into().unwrap(), + buffer[..core::mem::size_of::<usize>()].try_into().unwrap(), ) } else { 0 @@ -441,9 +441,9 @@ impl PyCSimpleType { if matches!(value_type_code.as_deref(), Some("z") | Some("Z")) { let ptr_val = { let buffer = simple.0.buffer.read(); - if buffer.len() >= std::mem::size_of::<usize>() { + if buffer.len() >= core::mem::size_of::<usize>() { usize::from_ne_bytes( - buffer[..std::mem::size_of::<usize>()].try_into().unwrap(), + buffer[..core::mem::size_of::<usize>()].try_into().unwrap(), ) } else { 0 @@ -712,7 +712,7 @@ fn create_swapped_types( pub struct PyCSimple(pub PyCData); impl Debug for PyCSimple { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("PyCSimple") .field("size", &self.0.buffer.read().len()) .finish() @@ -833,7 +833,7 @@ fn value_to_bytes_endian( let v = int_val.as_bigint().to_i128().expect("int too large") as libc::c_long; return to_bytes!(v); } - const SIZE: usize = std::mem::size_of::<libc::c_long>(); + const SIZE: usize = core::mem::size_of::<libc::c_long>(); vec![0; SIZE] } "L" => { @@ -842,7 +842,7 @@ fn value_to_bytes_endian( let v = int_val.as_bigint().to_i128().expect("int too large") as libc::c_ulong; return to_bytes!(v); } - const SIZE: usize = std::mem::size_of::<libc::c_ulong>(); + const SIZE: usize = core::mem::size_of::<libc::c_ulong>(); vec![0; SIZE] } "q" => { @@ -938,7 +938,7 @@ fn value_to_bytes_endian( .expect("int too large for pointer"); return to_bytes!(v); } - vec![0; std::mem::size_of::<usize>()] + vec![0; core::mem::size_of::<usize>()] } "z" => { // c_char_p - pointer to char (stores pointer value from int) @@ -950,7 +950,7 @@ fn value_to_bytes_endian( .expect("int too large for pointer"); return to_bytes!(v); } - vec![0; std::mem::size_of::<usize>()] + vec![0; core::mem::size_of::<usize>()] } "Z" => { // c_wchar_p - pointer to wchar_t (stores pointer value from int) @@ -962,7 +962,7 @@ fn value_to_bytes_endian( .expect("int too large for pointer"); return to_bytes!(v); } - vec![0; std::mem::size_of::<usize>()] + vec![0; core::mem::size_of::<usize>()] } "O" => { // py_object - store object id as non-zero marker @@ -1166,7 +1166,7 @@ impl PyCSimple { } // Read null-terminated string at the address unsafe { - let cstr = std::ffi::CStr::from_ptr(ptr as _); + let cstr = core::ffi::CStr::from_ptr(ptr as _); return Ok(vm.ctx.new_bytes(cstr.to_bytes().to_vec()).into()); } } @@ -1183,7 +1183,7 @@ impl PyCSimple { unsafe { let w_ptr = ptr as *const libc::wchar_t; let len = libc::wcslen(w_ptr); - let wchars = std::slice::from_raw_parts(w_ptr, len); + let wchars = core::slice::from_raw_parts(w_ptr, len); #[cfg(windows)] { use rustpython_common::wtf8::Wtf8Buf; @@ -1221,13 +1221,13 @@ impl PyCSimple { // Read value from buffer, swap bytes if needed let buffer = zelf.0.buffer.read(); - let buffer_data: std::borrow::Cow<'_, [u8]> = if swapped { + let buffer_data: alloc::borrow::Cow<'_, [u8]> = if swapped { // Reverse bytes for swapped endian types let mut swapped_bytes = buffer.to_vec(); swapped_bytes.reverse(); - std::borrow::Cow::Owned(swapped_bytes) + alloc::borrow::Cow::Owned(swapped_bytes) } else { - std::borrow::Cow::Borrowed(&*buffer) + alloc::borrow::Cow::Borrowed(&*buffer) }; let cls_ref = cls.to_owned(); @@ -1269,7 +1269,7 @@ impl PyCSimple { if type_code == "z" { if let Some(bytes) = value.downcast_ref::<PyBytes>() { let (converted, ptr) = super::base::ensure_z_null_terminated(bytes, vm); - *zelf.0.buffer.write() = std::borrow::Cow::Owned(ptr.to_ne_bytes().to_vec()); + *zelf.0.buffer.write() = alloc::borrow::Cow::Owned(ptr.to_ne_bytes().to_vec()); *zelf.0.objects.write() = Some(converted); return Ok(()); } @@ -1277,7 +1277,7 @@ impl PyCSimple { && let Some(s) = value.downcast_ref::<PyStr>() { let (holder, ptr) = super::base::str_to_wchar_bytes(s.as_str(), vm); - *zelf.0.buffer.write() = std::borrow::Cow::Owned(ptr.to_ne_bytes().to_vec()); + *zelf.0.buffer.write() = alloc::borrow::Cow::Owned(ptr.to_ne_bytes().to_vec()); *zelf.0.objects.write() = Some(holder); return Ok(()); } @@ -1368,65 +1368,65 @@ impl PyCSimple { let buffer = self.0.buffer.read(); let bytes: &[u8] = &buffer; - if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u8().as_raw_ptr()) { + if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u8().as_raw_ptr()) { if !bytes.is_empty() { return Some(FfiArgValue::U8(bytes[0])); } - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i8().as_raw_ptr()) { + } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i8().as_raw_ptr()) { if !bytes.is_empty() { return Some(FfiArgValue::I8(bytes[0] as i8)); } - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u16().as_raw_ptr()) { + } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u16().as_raw_ptr()) { if bytes.len() >= 2 { return Some(FfiArgValue::U16(u16::from_ne_bytes([bytes[0], bytes[1]]))); } - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i16().as_raw_ptr()) { + } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i16().as_raw_ptr()) { if bytes.len() >= 2 { return Some(FfiArgValue::I16(i16::from_ne_bytes([bytes[0], bytes[1]]))); } - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u32().as_raw_ptr()) { + } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u32().as_raw_ptr()) { if bytes.len() >= 4 { return Some(FfiArgValue::U32(u32::from_ne_bytes([ bytes[0], bytes[1], bytes[2], bytes[3], ]))); } - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i32().as_raw_ptr()) { + } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i32().as_raw_ptr()) { if bytes.len() >= 4 { return Some(FfiArgValue::I32(i32::from_ne_bytes([ bytes[0], bytes[1], bytes[2], bytes[3], ]))); } - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u64().as_raw_ptr()) { + } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u64().as_raw_ptr()) { if bytes.len() >= 8 { return Some(FfiArgValue::U64(u64::from_ne_bytes([ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], ]))); } - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i64().as_raw_ptr()) { + } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i64().as_raw_ptr()) { if bytes.len() >= 8 { return Some(FfiArgValue::I64(i64::from_ne_bytes([ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], ]))); } - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::f32().as_raw_ptr()) { + } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::f32().as_raw_ptr()) { if bytes.len() >= 4 { return Some(FfiArgValue::F32(f32::from_ne_bytes([ bytes[0], bytes[1], bytes[2], bytes[3], ]))); } - } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::f64().as_raw_ptr()) { + } else if core::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::f64().as_raw_ptr()) { if bytes.len() >= 8 { return Some(FfiArgValue::F64(f64::from_ne_bytes([ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], ]))); } - } else if std::ptr::eq( + } else if core::ptr::eq( ty.as_raw_ptr(), libffi::middle::Type::pointer().as_raw_ptr(), - ) && bytes.len() >= std::mem::size_of::<usize>() + ) && bytes.len() >= core::mem::size_of::<usize>() { let val = - usize::from_ne_bytes(bytes[..std::mem::size_of::<usize>()].try_into().unwrap()); + usize::from_ne_bytes(bytes[..core::mem::size_of::<usize>()].try_into().unwrap()); return Some(FfiArgValue::Pointer(val)); } None diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index d5aca392c52..295ce0d87cf 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -6,9 +6,9 @@ use crate::function::PySetterValue; use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}; use crate::types::{AsBuffer, AsNumber, Constructor, Initializer, SetAttr}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use alloc::borrow::Cow; +use core::fmt::Debug; use num_traits::ToPrimitive; -use std::borrow::Cow; -use std::fmt::Debug; /// Calculate Structure type size from _fields_ (sum of field sizes) pub(super) fn calculate_struct_size(cls: &Py<PyType>, vm: &VirtualMachine) -> PyResult<usize> { @@ -206,7 +206,7 @@ impl PyCStructType { { ( baseinfo.size, - std::cmp::max(baseinfo.align, forced_alignment), + core::cmp::max(baseinfo.align, forced_alignment), baseinfo.flags.contains(StgInfoFlags::TYPEFLAG_HASPOINTER), baseinfo.flags.contains(StgInfoFlags::TYPEFLAG_HASUNION), baseinfo.flags.contains(StgInfoFlags::TYPEFLAG_HASBITFIELD), @@ -252,7 +252,7 @@ impl PyCStructType { // Calculate effective alignment (PyCField_FromDesc) let effective_align = if pack > 0 { - std::cmp::min(pack, field_align) + core::cmp::min(pack, field_align) } else { field_align }; @@ -347,7 +347,7 @@ impl PyCStructType { } // Calculate total_align = max(max_align, forced_alignment) - let total_align = std::cmp::max(max_align, forced_alignment); + let total_align = core::cmp::max(max_align, forced_alignment); // Calculate aligned_size (PyCStructUnionType_update_stginfo) let aligned_size = if total_align > 0 { @@ -501,7 +501,7 @@ impl SetAttr for PyCStructType { pub struct PyCStructure(pub PyCData); impl Debug for PyCStructure { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("PyCStructure") .field("size", &self.0.size()) .finish() diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 41bc7492a25..0da1ffee3fd 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -7,7 +7,7 @@ use crate::function::PySetterValue; use crate::protocol::{BufferDescriptor, PyBuffer}; use crate::types::{AsBuffer, Constructor, Initializer, SetAttr}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; -use std::borrow::Cow; +use alloc::borrow::Cow; /// Calculate Union type size from _fields_ (max field size) pub(super) fn calculate_union_size(cls: &Py<PyType>, vm: &VirtualMachine) -> PyResult<usize> { @@ -175,7 +175,7 @@ impl PyCUnionType { { ( baseinfo.size, - std::cmp::max(baseinfo.align, forced_alignment), + core::cmp::max(baseinfo.align, forced_alignment), baseinfo.flags.contains(StgInfoFlags::TYPEFLAG_HASPOINTER), baseinfo.flags.contains(StgInfoFlags::TYPEFLAG_HASBITFIELD), baseinfo.ffi_field_types.clone(), @@ -215,7 +215,7 @@ impl PyCUnionType { // Calculate effective alignment let effective_align = if pack > 0 { - std::cmp::min(pack, field_align) + core::cmp::min(pack, field_align) } else { field_align }; @@ -264,7 +264,7 @@ impl PyCUnionType { } // Calculate total_align and aligned_size - let total_align = std::cmp::max(max_align, forced_alignment); + let total_align = core::cmp::max(max_align, forced_alignment); let aligned_size = if total_align > 0 { max_size.div_ceil(total_align) * total_align } else { @@ -418,8 +418,8 @@ impl SetAttr for PyCUnionType { #[repr(transparent)] pub struct PyCUnion(pub PyCData); -impl std::fmt::Debug for PyCUnion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PyCUnion { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("PyCUnion") .field("size", &self.0.size()) .finish() diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 52900faec08..54a38ef20e6 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -53,7 +53,7 @@ impl ToOSErrorBuilder for std::io::Error { let msg = { let ptr = unsafe { libc::strerror(errno) }; if !ptr.is_null() { - unsafe { std::ffi::CStr::from_ptr(ptr) } + unsafe { core::ffi::CStr::from_ptr(ptr) } .to_string_lossy() .into_owned() } else { @@ -183,16 +183,16 @@ mod _io { }, vm::VirtualMachine, }; + use alloc::borrow::Cow; use bstr::ByteSlice; - use crossbeam_utils::atomic::AtomicCell; - use malachite_bigint::BigInt; - use num_traits::ToPrimitive; - use std::{ - borrow::Cow, - io::{self, Cursor, SeekFrom, prelude::*}, + use core::{ ops::Range, sync::atomic::{AtomicBool, Ordering}, }; + use crossbeam_utils::atomic::AtomicCell; + use malachite_bigint::BigInt; + use num_traits::ToPrimitive; + use std::io::{self, Cursor, SeekFrom, prelude::*}; #[allow(clippy::let_and_return)] fn validate_whence(whence: i32) -> bool { @@ -354,7 +354,7 @@ mod _io { // if we don't specify the number of bytes, or it's too big, give the whole rest of the slice let n = bytes.map_or_else( || avail_slice.len(), - |n| std::cmp::min(n, avail_slice.len()), + |n| core::cmp::min(n, avail_slice.len()), ); let b = avail_slice[..n].to_vec(); self.cursor.set_position((pos + n) as u64); @@ -1059,7 +1059,7 @@ mod _io { // TODO: loop if write() raises an interrupt vm.call_method(self.raw.as_ref().unwrap(), "write", (mem_obj,))? } else { - let v = std::mem::take(&mut self.buffer); + let v = core::mem::take(&mut self.buffer); let write_buf = VecBuffer::from(v).into_ref(&vm.ctx); let mem_obj = PyMemoryView::from_buffer_range( write_buf.clone().into_pybuffer(true), @@ -1330,7 +1330,7 @@ mod _io { let res = match v { Either::A(v) => { let v = v.unwrap_or(&mut self.buffer); - let read_buf = VecBuffer::from(std::mem::take(v)).into_ref(&vm.ctx); + let read_buf = VecBuffer::from(core::mem::take(v)).into_ref(&vm.ctx); let mem_obj = PyMemoryView::from_buffer_range( read_buf.clone().into_pybuffer(false), buf_range, @@ -1527,7 +1527,7 @@ mod _io { } else if !(readinto1 && written != 0) { let n = self.fill_buffer(vm)?; if let Some(n) = n.filter(|&n| n > 0) { - let n = std::cmp::min(n, remaining); + let n = core::cmp::min(n, remaining); buf.as_contiguous_mut().unwrap()[written..][..n] .copy_from_slice(&self.buffer[self.pos as usize..][..n]); self.pos += n as Offset; @@ -1881,7 +1881,7 @@ mod _io { } let have = data.readahead(); if have > 0 { - let n = std::cmp::min(have as usize, n); + let n = core::cmp::min(have as usize, n); return Ok(data.read_fast(n).unwrap()); } // Flush write buffer before reading @@ -2373,7 +2373,7 @@ mod _io { } } - impl std::ops::Add for Utf8size { + impl core::ops::Add for Utf8size { type Output = Self; #[inline] @@ -2383,7 +2383,7 @@ mod _io { } } - impl std::ops::AddAssign for Utf8size { + impl core::ops::AddAssign for Utf8size { #[inline] fn add_assign(&mut self, rhs: Self) { self.bytes += rhs.bytes; @@ -2391,7 +2391,7 @@ mod _io { } } - impl std::ops::Sub for Utf8size { + impl core::ops::Sub for Utf8size { type Output = Self; #[inline] @@ -2401,7 +2401,7 @@ mod _io { } } - impl std::ops::SubAssign for Utf8size { + impl core::ops::SubAssign for Utf8size { #[inline] fn sub_assign(&mut self, rhs: Self) { self.bytes -= rhs.bytes; @@ -2470,7 +2470,7 @@ mod _io { impl PendingWrites { fn push(&mut self, write: PendingWrite) { self.num_bytes += write.as_bytes().len(); - self.data = match std::mem::take(&mut self.data) { + self.data = match core::mem::take(&mut self.data) { PendingWritesData::None => PendingWritesData::One(write), PendingWritesData::One(write1) => PendingWritesData::Many(vec![write1, write]), PendingWritesData::Many(mut v) => { @@ -2480,13 +2480,13 @@ mod _io { } } fn take(&mut self, vm: &VirtualMachine) -> PyBytesRef { - let Self { num_bytes, data } = std::mem::take(self); + let Self { num_bytes, data } = core::mem::take(self); if let PendingWritesData::One(PendingWrite::Bytes(b)) = data { return b; } let writes_iter = match data { PendingWritesData::None => itertools::Either::Left(vec![].into_iter()), - PendingWritesData::One(write) => itertools::Either::Right(std::iter::once(write)), + PendingWritesData::One(write) => itertools::Either::Right(core::iter::once(write)), PendingWritesData::Many(writes) => itertools::Either::Left(writes.into_iter()), }; let mut buf = Vec::with_capacity(num_bytes); @@ -2508,7 +2508,7 @@ mod _io { impl TextIOCookie { const START_POS_OFF: usize = 0; - const DEC_FLAGS_OFF: usize = Self::START_POS_OFF + std::mem::size_of::<Offset>(); + const DEC_FLAGS_OFF: usize = Self::START_POS_OFF + core::mem::size_of::<Offset>(); const BYTES_TO_FEED_OFF: usize = Self::DEC_FLAGS_OFF + 4; const CHARS_TO_SKIP_OFF: usize = Self::BYTES_TO_FEED_OFF + 4; const NEED_EOF_OFF: usize = Self::CHARS_TO_SKIP_OFF + 4; @@ -2525,7 +2525,7 @@ mod _io { macro_rules! get_field { ($t:ty, $off:ident) => {{ <$t>::from_ne_bytes( - buf[Self::$off..][..std::mem::size_of::<$t>()] + buf[Self::$off..][..core::mem::size_of::<$t>()] .try_into() .unwrap(), ) @@ -2546,7 +2546,7 @@ mod _io { macro_rules! set_field { ($field:expr, $off:ident) => {{ let field = $field; - buf[Self::$off..][..std::mem::size_of_val(&field)] + buf[Self::$off..][..core::mem::size_of_val(&field)] .copy_from_slice(&field.to_ne_bytes()) }}; } @@ -3509,7 +3509,7 @@ mod _io { } else { size_hint }; - let chunk_size = std::cmp::max(self.chunk_size, size_hint); + let chunk_size = core::cmp::max(self.chunk_size, size_hint); let input_chunk = vm.call_method(&self.buffer, method, (chunk_size,))?; let buf = ArgBytesLike::try_from_borrowed_object(vm, &input_chunk).map_err(|_| { @@ -3591,8 +3591,8 @@ mod _io { vm: &VirtualMachine, ) -> PyStrRef { let empty_str = || vm.ctx.empty_str.to_owned(); - let chars_pos = std::mem::take(&mut self.decoded_chars_used).bytes; - let decoded_chars = match std::mem::take(&mut self.decoded_chars) { + let chars_pos = core::mem::take(&mut self.decoded_chars_used).bytes; + let decoded_chars = match core::mem::take(&mut self.decoded_chars) { None => return append.unwrap_or_else(empty_str), Some(s) if s.is_empty() => return append.unwrap_or_else(empty_str), Some(s) => s, @@ -4294,7 +4294,7 @@ mod _io { plus: bool, } - impl std::str::FromStr for Mode { + impl core::str::FromStr for Mode { type Err = ParseModeError; fn from_str(s: &str) -> Result<Self, Self::Err> { diff --git a/crates/vm/src/stdlib/itertools.rs b/crates/vm/src/stdlib/itertools.rs index 3fedd17f12b..3aad2f91931 100644 --- a/crates/vm/src/stdlib/itertools.rs +++ b/crates/vm/src/stdlib/itertools.rs @@ -25,8 +25,8 @@ mod decl { use malachite_bigint::BigInt; use num_traits::One; + use alloc::fmt; use num_traits::{Signed, ToPrimitive}; - use std::fmt; fn pickle_deprecation(vm: &VirtualMachine) -> PyResult<()> { warnings::warn( @@ -1320,7 +1320,7 @@ mod decl { for arg in iterables.iter() { pools.push(arg.try_to_value(vm)?); } - let pools = std::iter::repeat_n(pools, repeat) + let pools = core::iter::repeat_n(pools, repeat) .flatten() .collect::<Vec<Vec<PyObjectRef>>>(); diff --git a/crates/vm/src/stdlib/mod.rs b/crates/vm/src/stdlib/mod.rs index 9fae516fe04..e46f333a28b 100644 --- a/crates/vm/src/stdlib/mod.rs +++ b/crates/vm/src/stdlib/mod.rs @@ -62,7 +62,8 @@ mod winapi; mod winreg; use crate::{PyRef, VirtualMachine, builtins::PyModule}; -use std::{borrow::Cow, collections::HashMap}; +use alloc::borrow::Cow; +use std::collections::HashMap; pub type StdlibInitFunc = Box<py_dyn_fn!(dyn Fn(&VirtualMachine) -> PyRef<PyModule>)>; pub type StdlibMap = HashMap<Cow<'static, str>, StdlibInitFunc, ahash::RandomState>; diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index e056142658d..a32959808c0 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -497,7 +497,7 @@ pub(crate) mod module { wide_path.as_ptr(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - std::ptr::null(), + core::ptr::null(), OPEN_EXISTING, flags, std::ptr::null_mut(), @@ -517,7 +517,7 @@ pub(crate) mod module { wide_path.as_ptr(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - std::ptr::null(), + core::ptr::null(), OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, std::ptr::null_mut(), @@ -568,7 +568,7 @@ pub(crate) mod module { wide_path.as_ptr(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - std::ptr::null(), + core::ptr::null(), OPEN_EXISTING, flags, std::ptr::null_mut(), @@ -586,7 +586,7 @@ pub(crate) mod module { wide_path.as_ptr(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, - std::ptr::null(), + core::ptr::null(), OPEN_EXISTING, 0, std::ptr::null_mut(), @@ -733,7 +733,7 @@ pub(crate) mod module { volume.as_ptr(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, - std::ptr::null(), + core::ptr::null(), OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, std::ptr::null_mut(), @@ -862,7 +862,7 @@ pub(crate) mod module { conout.as_ptr(), Foundation::GENERIC_READ | Foundation::GENERIC_WRITE, FileSystem::FILE_SHARE_READ | FileSystem::FILE_SHARE_WRITE, - std::ptr::null(), + core::ptr::null(), FileSystem::OPEN_EXISTING, 0, std::ptr::null_mut(), @@ -933,7 +933,7 @@ pub(crate) mod module { let argv_spawn: Vec<*const u16> = argv .iter() .map(|v| v.as_ptr()) - .chain(once(std::ptr::null())) + .chain(once(core::ptr::null())) .collect(); let result = unsafe { suppress_iph!(_wspawnv(mode, path.as_ptr(), argv_spawn.as_ptr())) }; @@ -976,7 +976,7 @@ pub(crate) mod module { let argv_spawn: Vec<*const u16> = argv .iter() .map(|v| v.as_ptr()) - .chain(once(std::ptr::null())) + .chain(once(core::ptr::null())) .collect(); // Build environment strings as "KEY=VALUE\0" wide strings @@ -1004,7 +1004,7 @@ pub(crate) mod module { let envp: Vec<*const u16> = env_strings .iter() .map(|s| s.as_ptr()) - .chain(once(std::ptr::null())) + .chain(once(core::ptr::null())) .collect(); let result = unsafe { @@ -1052,7 +1052,7 @@ pub(crate) mod module { let argv_execv: Vec<*const u16> = argv .iter() .map(|v| v.as_ptr()) - .chain(once(std::ptr::null())) + .chain(once(core::ptr::null())) .collect(); if (unsafe { suppress_iph!(_wexecv(path.as_ptr(), argv_execv.as_ptr())) } == -1) { @@ -1093,7 +1093,7 @@ pub(crate) mod module { let argv_execve: Vec<*const u16> = argv .iter() .map(|v| v.as_ptr()) - .chain(once(std::ptr::null())) + .chain(once(core::ptr::null())) .collect(); // Build environment strings as "KEY=VALUE\0" wide strings @@ -1121,7 +1121,7 @@ pub(crate) mod module { let envp: Vec<*const u16> = env_strings .iter() .map(|s| s.as_ptr()) - .chain(once(std::ptr::null())) + .chain(once(core::ptr::null())) .collect(); if (unsafe { suppress_iph!(_wexecve(path.as_ptr(), argv_execve.as_ptr(), envp.as_ptr())) } @@ -1356,7 +1356,7 @@ pub(crate) mod module { .chain(std::iter::once(0)) // null-terminated .collect(); - let mut end: *const u16 = std::ptr::null(); + let mut end: *const u16 = core::ptr::null(); let hr = unsafe { windows_sys::Win32::UI::Shell::PathCchSkipRoot(backslashed.as_ptr(), &mut end) }; @@ -1667,7 +1667,7 @@ pub(crate) mod module { let res = CreatePipe( read.as_mut_ptr() as *mut _, write.as_mut_ptr() as *mut _, - std::ptr::null(), + core::ptr::null(), 0, ); if res == 0 { @@ -1723,7 +1723,7 @@ pub(crate) mod module { let Some(func) = func else { return 0; }; - let nt_query: NtQueryInformationProcessFn = unsafe { std::mem::transmute(func) }; + let nt_query: NtQueryInformationProcessFn = unsafe { core::mem::transmute(func) }; let mut info: PROCESS_BASIC_INFORMATION = unsafe { std::mem::zeroed() }; @@ -1808,7 +1808,7 @@ pub(crate) mod module { wide_path.as_ptr(), 0, // No access needed, just reading reparse data FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - std::ptr::null(), + core::ptr::null(), OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, std::ptr::null_mut(), @@ -1832,7 +1832,7 @@ pub(crate) mod module { DeviceIoControl( handle, FSCTL_GET_REPARSE_POINT, - std::ptr::null(), + core::ptr::null(), 0, buffer.as_mut_ptr() as *mut _, BUFFER_SIZE as u32, diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 6e2bb274fec..add8763f5e1 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -171,15 +171,10 @@ pub(super) mod _os { utils::ToCString, vm::VirtualMachine, }; + use core::time::Duration; use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; - use std::{ - env, fs, - fs::OpenOptions, - io, - path::PathBuf, - time::{Duration, SystemTime}, - }; + use std::{env, fs, fs::OpenOptions, io, path::PathBuf, time::SystemTime}; const OPEN_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); pub(crate) const MKDIR_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); @@ -518,7 +513,7 @@ pub(super) mod _os { 22, format!( "Invalid argument: {}", - std::str::from_utf8(key).unwrap_or("<bytes encoding failure>") + core::str::from_utf8(key).unwrap_or("<bytes encoding failure>") ), ); @@ -1051,12 +1046,12 @@ pub(super) mod _os { dir_fd: DirFd<'_, { STAT_DIR_FD as usize }>, follow_symlinks: FollowSymlinks, ) -> io::Result<Option<StatStruct>> { - let mut stat = std::mem::MaybeUninit::uninit(); + let mut stat = core::mem::MaybeUninit::uninit(); let ret = match file { OsPathOrFd::Path(path) => { use rustpython_common::os::ffi::OsStrExt; let path = path.as_ref().as_os_str().as_bytes(); - let path = match std::ffi::CString::new(path) { + let path = match alloc::ffi::CString::new(path) { Ok(x) => x, Err(_) => return Ok(None), }; @@ -1218,7 +1213,7 @@ pub(super) mod _os { use std::os::windows::io::AsRawHandle; use windows_sys::Win32::Storage::FileSystem; let handle = crt_fd::as_handle(fd).map_err(|e| e.into_pyexception(vm))?; - let mut distance_to_move: [i32; 2] = std::mem::transmute(position); + let mut distance_to_move: [i32; 2] = core::mem::transmute(position); let ret = FileSystem::SetFilePointer( handle.as_raw_handle(), distance_to_move[0], @@ -1229,7 +1224,7 @@ pub(super) mod _os { -1 } else { distance_to_move[0] = ret as _; - std::mem::transmute::<[i32; 2], i64>(distance_to_move) + core::mem::transmute::<[i32; 2], i64>(distance_to_move) } }; if res < 0 { @@ -1411,7 +1406,7 @@ pub(super) mod _os { .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; let ret = unsafe { - FileSystem::SetFileTime(f.as_raw_handle() as _, std::ptr::null(), &acc, &modif) + FileSystem::SetFileTime(f.as_raw_handle() as _, core::ptr::null(), &acc, &modif) }; if ret == 0 { @@ -1532,9 +1527,9 @@ pub(super) mod _os { #[pyfunction] fn copy_file_range(args: CopyFileRangeArgs<'_>, vm: &VirtualMachine) -> PyResult<usize> { #[allow(clippy::unnecessary_option_map_or_else)] - let p_offset_src = args.offset_src.as_ref().map_or_else(std::ptr::null, |x| x); + let p_offset_src = args.offset_src.as_ref().map_or_else(core::ptr::null, |x| x); #[allow(clippy::unnecessary_option_map_or_else)] - let p_offset_dst = args.offset_dst.as_ref().map_or_else(std::ptr::null, |x| x); + let p_offset_dst = args.offset_dst.as_ref().map_or_else(core::ptr::null, |x| x); let count: usize = args .count .try_into() @@ -1566,7 +1561,7 @@ pub(super) mod _os { #[pyfunction] fn strerror(e: i32) -> String { - unsafe { std::ffi::CStr::from_ptr(libc::strerror(e)) } + unsafe { core::ffi::CStr::from_ptr(libc::strerror(e)) } .to_string_lossy() .into_owned() } @@ -1670,7 +1665,7 @@ pub(super) mod _os { if encoding.is_null() || encoding.read() == '\0' as libc::c_char { "UTF-8".to_owned() } else { - std::ffi::CStr::from_ptr(encoding).to_string_lossy().into_owned() + core::ffi::CStr::from_ptr(encoding).to_string_lossy().into_owned() } }; diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index a4a311df06a..4ec71eecbf2 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -33,15 +33,15 @@ pub mod module { types::{Constructor, Representable}, utils::ToCString, }; + use alloc::ffi::CString; use bitflags::bitflags; + use core::ffi::CStr; use nix::{ fcntl, unistd::{self, Gid, Pid, Uid}, }; use std::{ - env, - ffi::{CStr, CString}, - fs, io, + env, fs, io, os::fd::{AsFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd}, }; use strum_macros::{EnumIter, EnumString}; @@ -917,7 +917,7 @@ pub mod module { #[pyfunction] fn sched_getparam(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<SchedParam> { let param = unsafe { - let mut param = std::mem::MaybeUninit::uninit(); + let mut param = core::mem::MaybeUninit::uninit(); if -1 == libc::sched_getparam(pid, param.as_mut_ptr()) { return Err(vm.new_last_errno_error()); } @@ -1280,7 +1280,7 @@ pub mod module { } fn try_from_id(vm: &VirtualMachine, obj: PyObjectRef, typ_name: &str) -> PyResult<u32> { - use std::cmp::Ordering; + use core::cmp::Ordering; let i = obj .try_to_ref::<PyInt>(vm) .map_err(|_| { @@ -1853,9 +1853,9 @@ pub mod module { #[pyfunction] fn dup2(args: Dup2Args<'_>, vm: &VirtualMachine) -> PyResult<OwnedFd> { - let mut fd2 = std::mem::ManuallyDrop::new(args.fd2); + let mut fd2 = core::mem::ManuallyDrop::new(args.fd2); nix::unistd::dup2(args.fd, &mut fd2).map_err(|e| e.into_pyexception(vm))?; - let fd2 = std::mem::ManuallyDrop::into_inner(fd2); + let fd2 = core::mem::ManuallyDrop::into_inner(fd2); if !args.inheritable { super::set_inheritable(fd2.as_fd(), false).map_err(|e| e.into_pyexception(vm))? } diff --git a/crates/vm/src/stdlib/pwd.rs b/crates/vm/src/stdlib/pwd.rs index e4d7075dbc8..6405ed7be91 100644 --- a/crates/vm/src/stdlib/pwd.rs +++ b/crates/vm/src/stdlib/pwd.rs @@ -37,7 +37,7 @@ mod pwd { impl From<User> for PasswdData { fn from(user: User) -> Self { // this is just a pain... - let cstr_lossy = |s: std::ffi::CString| { + let cstr_lossy = |s: alloc::ffi::CString| { s.into_string() .unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned()) }; @@ -105,7 +105,7 @@ mod pwd { let mut list = Vec::new(); unsafe { libc::setpwent() }; - while let Some(ptr) = std::ptr::NonNull::new(unsafe { libc::getpwent() }) { + while let Some(ptr) = core::ptr::NonNull::new(unsafe { libc::getpwent() }) { let user = User::from(unsafe { ptr.as_ref() }); let passwd = PasswdData::from(user).to_pyobject(vm); list.push(passwd); diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index 810ffabefe6..dd0d9a7a96f 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -24,7 +24,7 @@ pub(crate) mod _signal { builtins::PyTypeRef, function::{ArgIntoFloat, OptionalArg}, }; - use std::sync::atomic::{self, Ordering}; + use core::sync::atomic::{self, Ordering}; #[cfg(any(unix, windows))] use libc::sighandler_t; @@ -301,7 +301,7 @@ pub(crate) mod _signal { it_value: double_to_timeval(seconds), it_interval: double_to_timeval(interval), }; - let mut old = std::mem::MaybeUninit::<libc::itimerval>::uninit(); + let mut old = core::mem::MaybeUninit::<libc::itimerval>::uninit(); #[cfg(any(target_os = "linux", target_os = "android"))] let ret = unsafe { ffi::setitimer(which, &new, old.as_mut_ptr()) }; #[cfg(not(any(target_os = "linux", target_os = "android")))] @@ -318,7 +318,7 @@ pub(crate) mod _signal { #[cfg(unix)] #[pyfunction] fn getitimer(which: i32, vm: &VirtualMachine) -> PyResult<(f64, f64)> { - let mut old = std::mem::MaybeUninit::<libc::itimerval>::uninit(); + let mut old = core::mem::MaybeUninit::<libc::itimerval>::uninit(); #[cfg(any(target_os = "linux", target_os = "android"))] let ret = unsafe { ffi::getitimer(which, old.as_mut_ptr()) }; #[cfg(not(any(target_os = "linux", target_os = "android")))] @@ -489,7 +489,7 @@ pub(crate) mod _signal { if s.is_null() { Ok(None) } else { - let cstr = unsafe { std::ffi::CStr::from_ptr(s) }; + let cstr = unsafe { core::ffi::CStr::from_ptr(s) }; Ok(Some(cstr.to_string_lossy().into_owned())) } } @@ -522,7 +522,7 @@ pub(crate) mod _signal { #[cfg(unix)] { // Use sigfillset to get all valid signals - let mut mask: libc::sigset_t = unsafe { std::mem::zeroed() }; + let mut mask: libc::sigset_t = unsafe { core::mem::zeroed() }; // SAFETY: mask is a valid pointer if unsafe { libc::sigfillset(&mut mask) } != 0 { return Err(vm.new_os_error("sigfillset failed".to_owned())); @@ -580,7 +580,7 @@ pub(crate) mod _signal { use crate::convert::IntoPyException; // Initialize sigset - let mut sigset: libc::sigset_t = unsafe { std::mem::zeroed() }; + let mut sigset: libc::sigset_t = unsafe { core::mem::zeroed() }; // SAFETY: sigset is a valid pointer if unsafe { libc::sigemptyset(&mut sigset) } != 0 { return Err(std::io::Error::last_os_error().into_pyexception(vm)); @@ -611,7 +611,7 @@ pub(crate) mod _signal { } // Call pthread_sigmask - let mut old_mask: libc::sigset_t = unsafe { std::mem::zeroed() }; + let mut old_mask: libc::sigset_t = unsafe { core::mem::zeroed() }; // SAFETY: all pointers are valid let err = unsafe { libc::pthread_sigmask(how, &sigset, &mut old_mask) }; if err != 0 { diff --git a/crates/vm/src/stdlib/string.rs b/crates/vm/src/stdlib/string.rs index 576cae62775..a9911f3d45f 100644 --- a/crates/vm/src/stdlib/string.rs +++ b/crates/vm/src/stdlib/string.rs @@ -16,7 +16,7 @@ mod _string { convert::ToPyException, convert::ToPyObject, }; - use std::mem; + use core::mem; fn create_format_part( literal: Wtf8Buf, diff --git a/crates/vm/src/stdlib/symtable.rs b/crates/vm/src/stdlib/symtable.rs index 8a142857787..51c5c8e47ea 100644 --- a/crates/vm/src/stdlib/symtable.rs +++ b/crates/vm/src/stdlib/symtable.rs @@ -7,10 +7,10 @@ mod symtable { builtins::{PyDictRef, PyStrRef}, compiler, }; + use alloc::fmt; use rustpython_codegen::symboltable::{ CompilerScope, Symbol, SymbolFlags, SymbolScope, SymbolTable, }; - use std::fmt; // Consts as defined at // https://github.com/python/cpython/blob/6cb20a219a860eaf687b2d968b41c480c7461909/Include/internal/pycore_symtable.h#L156 @@ -180,7 +180,7 @@ mod symtable { #[pygetset] fn id(&self) -> usize { - self as *const Self as *const std::ffi::c_void as usize + self as *const Self as *const core::ffi::c_void as usize } #[pygetset] diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 92b15878b46..30f126b3742 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -24,11 +24,11 @@ mod sys { version, vm::{Settings, VirtualMachine}, }; + use core::sync::atomic::Ordering; use num_traits::ToPrimitive; use std::{ env::{self, VarError}, io::Read, - sync::atomic::Ordering, }; #[cfg(windows)] @@ -67,7 +67,7 @@ mod sys { #[pyattr(name = "maxsize")] pub(crate) const MAXSIZE: isize = isize::MAX; #[pyattr(name = "maxunicode")] - const MAXUNICODE: u32 = std::char::MAX as u32; + const MAXUNICODE: u32 = core::char::MAX as u32; #[pyattr(name = "platform")] pub(crate) const PLATFORM: &str = { cfg_if::cfg_if! { @@ -751,7 +751,7 @@ mod sys { let sizeof = || -> PyResult<usize> { let res = vm.call_special_method(&args.obj, identifier!(vm, __sizeof__), ())?; let res = res.try_index(vm)?.try_to_primitive::<usize>(vm)?; - Ok(res + std::mem::size_of::<PyObject>()) + Ok(res + core::mem::size_of::<PyObject>()) }; sizeof() .map(|x| vm.ctx.new_int(x).into()) @@ -1377,7 +1377,7 @@ mod sys { const INFO: Self = { use rustpython_common::hash::*; Self { - width: std::mem::size_of::<PyHash>() * 8, + width: core::mem::size_of::<PyHash>() * 8, modulus: MODULUS, inf: INF, nan: NAN, @@ -1407,7 +1407,7 @@ mod sys { impl IntInfoData { const INFO: Self = Self { bits_per_digit: 30, //? - sizeof_digit: std::mem::size_of::<u32>(), + sizeof_digit: core::mem::size_of::<u32>(), default_max_str_digits: 4300, str_digits_check_threshold: 640, }; @@ -1525,7 +1525,7 @@ pub(crate) fn init_module(vm: &VirtualMachine, module: &Py<PyModule>, builtins: pub struct PyStderr<'vm>(pub &'vm VirtualMachine); impl PyStderr<'_> { - pub fn write_fmt(&self, args: std::fmt::Arguments<'_>) { + pub fn write_fmt(&self, args: core::fmt::Arguments<'_>) { use crate::py_io::Write; let vm = self.0; diff --git a/crates/vm/src/stdlib/thread.rs b/crates/vm/src/stdlib/thread.rs index fd300ac2f74..6ab754be094 100644 --- a/crates/vm/src/stdlib/thread.rs +++ b/crates/vm/src/stdlib/thread.rs @@ -11,12 +11,14 @@ pub(crate) mod _thread { function::{ArgCallable, Either, FuncArgs, KwArgs, OptionalArg, PySetterValue}, types::{Constructor, GetAttr, Representable, SetAttr}, }; + use alloc::fmt; + use core::{cell::RefCell, time::Duration}; use crossbeam_utils::atomic::AtomicCell; use parking_lot::{ RawMutex, RawThreadId, lock_api::{RawMutex as RawMutexT, RawMutexTimed, RawReentrantMutex}, }; - use std::{cell::RefCell, fmt, thread, time::Duration}; + use std::thread; use thread_local::ThreadLocal; // PYTHREAD_NAME: show current thread name @@ -151,7 +153,7 @@ pub(crate) mod _thread { let new_mut = RawMutex::INIT; unsafe { - let old_mutex: &AtomicCell<RawMutex> = std::mem::transmute(&self.mu); + let old_mutex: &AtomicCell<RawMutex> = core::mem::transmute(&self.mu); old_mutex.swap(new_mut); } @@ -287,7 +289,7 @@ pub(crate) mod _thread { } fn thread_to_id(t: &thread::Thread) -> u64 { - use std::hash::{Hash, Hasher}; + use core::hash::{Hash, Hasher}; struct U64Hash { v: Option<u64>, } diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index b9b53cdc5c5..97d60ae98a1 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -21,12 +21,12 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> { unsafe extern "C" { #[cfg(not(target_os = "freebsd"))] #[link_name = "daylight"] - static c_daylight: std::ffi::c_int; + static c_daylight: core::ffi::c_int; // pub static dstbias: std::ffi::c_int; #[link_name = "timezone"] - static c_timezone: std::ffi::c_long; + static c_timezone: core::ffi::c_long; #[link_name = "tzname"] - static c_tzname: [*const std::ffi::c_char; 2]; + static c_tzname: [*const core::ffi::c_char; 2]; #[link_name = "tzset"] fn c_tzset(); } @@ -43,7 +43,7 @@ mod decl { DateTime, Datelike, TimeZone, Timelike, naive::{NaiveDate, NaiveDateTime, NaiveTime}, }; - use std::time::Duration; + use core::time::Duration; #[cfg(target_env = "msvc")] #[cfg(not(target_arch = "wasm32"))] use windows_sys::Win32::System::Time::{GetTimeZoneInformation, TIME_ZONE_INFORMATION}; @@ -104,7 +104,7 @@ mod decl { { // this is basically std::thread::sleep, but that catches interrupts and we don't want to; let ts = nix::sys::time::TimeSpec::from(dur); - let res = unsafe { libc::nanosleep(ts.as_ref(), std::ptr::null_mut()) }; + let res = unsafe { libc::nanosleep(ts.as_ref(), core::ptr::null_mut()) }; let interrupted = res == -1 && nix::Error::last_raw() == libc::EINTR; if interrupted { @@ -200,7 +200,7 @@ mod decl { #[cfg(not(target_env = "msvc"))] #[cfg(not(target_arch = "wasm32"))] #[pyattr] - fn timezone(_vm: &VirtualMachine) -> std::ffi::c_long { + fn timezone(_vm: &VirtualMachine) -> core::ffi::c_long { unsafe { super::c_timezone } } @@ -217,7 +217,7 @@ mod decl { #[cfg(not(target_env = "msvc"))] #[cfg(not(target_arch = "wasm32"))] #[pyattr] - fn daylight(_vm: &VirtualMachine) -> std::ffi::c_int { + fn daylight(_vm: &VirtualMachine) -> core::ffi::c_int { unsafe { super::c_daylight } } @@ -236,8 +236,8 @@ mod decl { fn tzname(vm: &VirtualMachine) -> crate::builtins::PyTupleRef { use crate::builtins::tuple::IntoPyTuple; - unsafe fn to_str(s: *const std::ffi::c_char) -> String { - unsafe { std::ffi::CStr::from_ptr(s) } + unsafe fn to_str(s: *const core::ffi::c_char) -> String { + unsafe { core::ffi::CStr::from_ptr(s) } .to_string_lossy() .into_owned() } @@ -357,7 +357,7 @@ mod decl { t: OptionalArg<StructTimeData>, vm: &VirtualMachine, ) -> PyResult { - use std::fmt::Write; + use core::fmt::Write; let instant = t.naive_or_local(vm)?; @@ -500,8 +500,8 @@ mod decl { pub tm_zone: PyObjectRef, } - impl std::fmt::Debug for StructTimeData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + impl core::fmt::Debug for StructTimeData { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "struct_time()") } } @@ -590,8 +590,8 @@ mod platform { builtins::{PyNamespace, PyStrRef}, convert::IntoPyException, }; + use core::time::Duration; use nix::{sys::time::TimeSpec, time::ClockId}; - use std::time::Duration; #[cfg(target_os = "solaris")] #[pyattr] @@ -818,7 +818,7 @@ mod platform { fn u64_from_filetime(time: FILETIME) -> u64 { let large: [u32; 2] = [time.dwLowDateTime, time.dwHighDateTime]; - unsafe { std::mem::transmute(large) } + unsafe { core::mem::transmute(large) } } fn win_perf_counter_frequency(vm: &VirtualMachine) -> PyResult<i64> { diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index a3aba447489..7279f9776e2 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -108,7 +108,7 @@ mod _winapi { WindowsSysResult(windows_sys::Win32::System::Pipes::CreatePipe( read.as_mut_ptr(), write.as_mut_ptr(), - std::ptr::null(), + core::ptr::null(), size, )) .to_pyresult(vm)?; @@ -279,8 +279,8 @@ mod _winapi { WindowsSysResult(windows_sys::Win32::System::Threading::CreateProcessW( app_name, command_line, - std::ptr::null(), - std::ptr::null(), + core::ptr::null(), + core::ptr::null(), args.inherit_handles, args.creation_flags | windows_sys::Win32::System::Threading::EXTENDED_STARTUPINFO_PRESENT @@ -455,7 +455,7 @@ mod _winapi { handlelist.as_mut_ptr() as _, (handlelist.len() * std::mem::size_of::<usize>()) as _, std::ptr::null_mut(), - std::ptr::null(), + core::ptr::null(), ) }) .into_pyresult(vm)?; @@ -874,7 +874,7 @@ mod _winapi { } let buf = buffer.borrow_buf(); - let len = std::cmp::min(buf.len(), u32::MAX as usize) as u32; + let len = core::cmp::min(buf.len(), u32::MAX as usize) as u32; let mut written: u32 = 0; let ret = unsafe { @@ -948,7 +948,7 @@ mod _winapi { let mut batches: Vec<Vec<isize>> = Vec::new(); let mut i = 0; while i < nhandles { - let end = std::cmp::min(i + batch_size, nhandles); + let end = core::cmp::min(i + batch_size, nhandles); batches.push(handles[i..end].to_vec()); i = end; } diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index b5e568fce6d..f3d8ca10768 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -367,10 +367,10 @@ mod winreg { key, wide_sub_key.as_ptr(), args.reserved, - std::ptr::null(), + core::ptr::null(), Registry::REG_OPTION_NON_VOLATILE, args.access, - std::ptr::null(), + core::ptr::null(), &mut res, std::ptr::null_mut(), ) @@ -404,7 +404,9 @@ mod winreg { #[pyfunction] fn DeleteValue(key: PyRef<PyHkey>, value: Option<String>, vm: &VirtualMachine) -> PyResult<()> { let wide_value = value.map(|v| v.to_wide_with_nul()); - let value_ptr = wide_value.as_ref().map_or(std::ptr::null(), |v| v.as_ptr()); + let value_ptr = wide_value + .as_ref() + .map_or(core::ptr::null(), |v| v.as_ptr()); let res = unsafe { Registry::RegDeleteValueW(key.hkey.load(), value_ptr) }; if res == 0 { Ok(()) @@ -713,7 +715,7 @@ mod winreg { let res = unsafe { Registry::RegQueryValueExW( target_key, - std::ptr::null(), // NULL value name for default value + core::ptr::null(), // NULL value name for default value std::ptr::null_mut(), &mut reg_type, buffer.as_mut_ptr(), @@ -871,10 +873,10 @@ mod winreg { hkey, wide_sub_key.as_ptr(), 0, - std::ptr::null(), + core::ptr::null(), 0, Registry::KEY_SET_VALUE, - std::ptr::null(), + core::ptr::null(), &mut out_key, std::ptr::null_mut(), ) @@ -893,7 +895,7 @@ mod winreg { let res = unsafe { Registry::RegSetValueExW( target_key, - std::ptr::null(), // value name is NULL + core::ptr::null(), // value name is NULL 0, typ, wide_value.as_ptr() as *const u8, @@ -1104,7 +1106,7 @@ mod winreg { } Ok(None) => { let len = 0; - let ptr = std::ptr::null(); + let ptr = core::ptr::null(); let wide_value_name = value_name.to_wide_with_nul(); let res = unsafe { Registry::RegSetValueExW( diff --git a/crates/vm/src/suggestion.rs b/crates/vm/src/suggestion.rs index 866deb668eb..55326d1d3f0 100644 --- a/crates/vm/src/suggestion.rs +++ b/crates/vm/src/suggestion.rs @@ -7,8 +7,8 @@ use crate::{ exceptions::types::PyBaseException, sliceable::SliceableSequenceOp, }; +use core::iter::ExactSizeIterator; use rustpython_common::str::levenshtein::{MOVE_COST, levenshtein_distance}; -use std::iter::ExactSizeIterator; const MAX_CANDIDATE_ITEMS: usize = 750; diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 658e21cba8a..9d7d09f2d18 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -17,9 +17,9 @@ use crate::{ types::slot_defs::{SlotAccessor, find_slot_defs_by_name}, vm::Context, }; +use core::{any::Any, any::TypeId, borrow::Borrow, cmp::Ordering, ops::Deref}; use crossbeam_utils::atomic::AtomicCell; use num_traits::{Signed, ToPrimitive}; -use std::{any::Any, any::TypeId, borrow::Borrow, cmp::Ordering, ops::Deref}; /// Type-erased storage for extension module data attached to heap types. pub struct TypeDataSlot { @@ -71,7 +71,7 @@ impl<'a, T: Any + 'static> TypeDataRef<'a, T> { } } -impl<T: Any + 'static> std::ops::Deref for TypeDataRef<'_, T> { +impl<T: Any + 'static> core::ops::Deref for TypeDataRef<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -96,7 +96,7 @@ impl<'a, T: Any + 'static> TypeDataRefMut<'a, T> { } } -impl<T: Any + 'static> std::ops::Deref for TypeDataRefMut<'_, T> { +impl<T: Any + 'static> core::ops::Deref for TypeDataRefMut<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -104,7 +104,7 @@ impl<T: Any + 'static> std::ops::Deref for TypeDataRefMut<'_, T> { } } -impl<T: Any + 'static> std::ops::DerefMut for TypeDataRefMut<'_, T> { +impl<T: Any + 'static> core::ops::DerefMut for TypeDataRefMut<'_, T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.guard } @@ -203,8 +203,8 @@ impl PyTypeSlots { } } -impl std::fmt::Debug for PyTypeSlots { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for PyTypeSlots { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str("PyTypeSlots") } } @@ -1310,7 +1310,7 @@ impl PyType { /// - Special class type handling (e.g., `PyType` and its metaclasses) /// - Post-creation mutations that require `PyRef` #[pyclass] -pub trait Constructor: PyPayload + std::fmt::Debug { +pub trait Constructor: PyPayload + core::fmt::Debug { type Args: FromArgs; /// The type slot for `__new__`. Override this only when you need special @@ -1328,7 +1328,7 @@ pub trait Constructor: PyPayload + std::fmt::Debug { fn py_new(cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self>; } -pub trait DefaultConstructor: PyPayload + Default + std::fmt::Debug { +pub trait DefaultConstructor: PyPayload + Default + core::fmt::Debug { fn construct_and_init(args: Self::Args, vm: &VirtualMachine) -> PyResult<PyRef<Self>> where Self: Initializer, @@ -1862,7 +1862,7 @@ pub trait Iterable: PyPayload { fn extend_slots(_slots: &mut PyTypeSlots) {} } -// `Iterator` fits better, but to avoid confusion with rust std::iter::Iterator +// `Iterator` fits better, but to avoid confusion with rust core::iter::Iterator #[pyclass(with(Iterable))] pub trait IterNext: PyPayload + Iterable { #[pyslot] diff --git a/crates/vm/src/utils.rs b/crates/vm/src/utils.rs index af34405c7be..db232e81949 100644 --- a/crates/vm/src/utils.rs +++ b/crates/vm/src/utils.rs @@ -14,15 +14,15 @@ pub fn hash_iter<'a, I: IntoIterator<Item = &'a PyObjectRef>>( vm.state.hash_secret.hash_iter(iter, |obj| obj.hash(vm)) } -impl ToPyObject for std::convert::Infallible { +impl ToPyObject for core::convert::Infallible { fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef { match self {} } } pub trait ToCString: AsRef<Wtf8> { - fn to_cstring(&self, vm: &VirtualMachine) -> PyResult<std::ffi::CString> { - std::ffi::CString::new(self.as_ref().as_bytes()).map_err(|err| err.to_pyexception(vm)) + fn to_cstring(&self, vm: &VirtualMachine) -> PyResult<alloc::ffi::CString> { + alloc::ffi::CString::new(self.as_ref().as_bytes()).map_err(|err| err.to_pyexception(vm)) } fn ensure_no_nul(&self, vm: &VirtualMachine) -> PyResult<()> { if self.as_ref().as_bytes().contains(&b'\0') { @@ -45,7 +45,7 @@ pub(crate) fn collection_repr<'a, I>( vm: &VirtualMachine, ) -> PyResult<String> where - I: std::iter::Iterator<Item = &'a PyObjectRef>, + I: core::iter::Iterator<Item = &'a PyObjectRef>, { let mut repr = String::new(); if let Some(name) = class_name { diff --git a/crates/vm/src/version.rs b/crates/vm/src/version.rs index 0a598842a56..cc118ee5b0e 100644 --- a/crates/vm/src/version.rs +++ b/crates/vm/src/version.rs @@ -1,7 +1,8 @@ //! Several function to retrieve version information. use chrono::{Local, prelude::DateTime}; -use std::time::{Duration, UNIX_EPOCH}; +use core::time::Duration; +use std::time::UNIX_EPOCH; // = 3.13.0alpha pub const MAJOR: usize = 3; diff --git a/crates/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs index 486c1861fb1..b12352f6eee 100644 --- a/crates/vm/src/vm/context.rs +++ b/crates/vm/src/vm/context.rs @@ -264,7 +264,7 @@ declare_const_name! { // Basic objects: impl Context { - pub const INT_CACHE_POOL_RANGE: std::ops::RangeInclusive<i32> = (-5)..=256; + pub const INT_CACHE_POOL_RANGE: core::ops::RangeInclusive<i32> = (-5)..=256; const INT_CACHE_POOL_MIN: i32 = *Self::INT_CACHE_POOL_RANGE.start(); pub fn genesis() -> &'static PyRc<Self> { @@ -374,14 +374,14 @@ impl Context { #[inline] pub fn empty_tuple_typed<T>(&self) -> &Py<PyTuple<T>> { let py: &Py<PyTuple> = &self.empty_tuple; - unsafe { std::mem::transmute(py) } + unsafe { core::mem::transmute(py) } } // universal pyref constructor pub fn new_pyref<T, P>(&self, value: T) -> PyRef<P> where T: Into<P>, - P: PyPayload + std::fmt::Debug, + P: PyPayload + core::fmt::Debug, { value.into().into_ref(self) } diff --git a/crates/vm/src/vm/interpreter.rs b/crates/vm/src/vm/interpreter.rs index 503feb3dc7f..8d37ad6c840 100644 --- a/crates/vm/src/vm/interpreter.rs +++ b/crates/vm/src/vm/interpreter.rs @@ -1,6 +1,6 @@ use super::{Context, PyConfig, VirtualMachine, setting::Settings, thread}; use crate::{PyResult, getpath, stdlib::atexit, vm::PyBaseExceptionRef}; -use std::sync::atomic::Ordering; +use core::sync::atomic::Ordering; /// The general interface for the VM /// diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 34092454059..7c527b3e0da 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -33,6 +33,11 @@ use crate::{ signal, stdlib, warn::WarningsState, }; +use alloc::borrow::Cow; +use core::{ + cell::{Cell, Ref, RefCell}, + sync::atomic::AtomicBool, +}; use crossbeam_utils::atomic::AtomicCell; #[cfg(unix)] use nix::{ @@ -40,11 +45,8 @@ use nix::{ unistd::getpid, }; use std::{ - borrow::Cow, - cell::{Cell, Ref, RefCell}, collections::{HashMap, HashSet}, ffi::{OsStr, OsString}, - sync::atomic::AtomicBool, }; pub use context::Context; @@ -789,14 +791,14 @@ impl VirtualMachine { pub(crate) fn push_exception(&self, exc: Option<PyBaseExceptionRef>) { let mut excs = self.exceptions.borrow_mut(); - let prev = std::mem::take(&mut *excs); + let prev = core::mem::take(&mut *excs); excs.prev = Some(Box::new(prev)); excs.exc = exc } pub(crate) fn pop_exception(&self) -> Option<PyBaseExceptionRef> { let mut excs = self.exceptions.borrow_mut(); - let cur = std::mem::take(&mut *excs); + let cur = core::mem::take(&mut *excs); *excs = *cur.prev.expect("pop_exception() without nested exc stack"); cur.exc } @@ -811,7 +813,7 @@ impl VirtualMachine { pub(crate) fn set_exception(&self, exc: Option<PyBaseExceptionRef>) { // don't be holding the RefCell guard while __del__ is called - let prev = std::mem::replace(&mut self.exceptions.borrow_mut().exc, exc); + let prev = core::mem::replace(&mut self.exceptions.borrow_mut().exc, exc); drop(prev); } @@ -984,7 +986,7 @@ impl AsRef<Context> for VirtualMachine { } fn core_frozen_inits() -> impl Iterator<Item = (&'static str, FrozenModule)> { - let iter = std::iter::empty(); + let iter = core::iter::empty(); macro_rules! ext_modules { ($iter:ident, $($t:tt)*) => { let $iter = $iter.chain(py_freeze!($($t)*)); diff --git a/crates/vm/src/vm/thread.rs b/crates/vm/src/vm/thread.rs index 2e687d99820..7e8f0f87e56 100644 --- a/crates/vm/src/vm/thread.rs +++ b/crates/vm/src/vm/thread.rs @@ -1,10 +1,10 @@ use crate::{AsObject, PyObject, PyObjectRef, VirtualMachine}; -use itertools::Itertools; -use std::{ +use core::{ cell::{Cell, RefCell}, ptr::NonNull, - thread_local, }; +use itertools::Itertools; +use std::thread_local; thread_local! { pub(super) static VM_STACK: RefCell<Vec<NonNull<VirtualMachine>>> = Vec::with_capacity(1).into(); diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 6d0e983c844..ba09c8ecf69 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -95,7 +95,7 @@ impl VirtualMachine { pub fn new_exception(&self, exc_type: PyTypeRef, args: Vec<PyObjectRef>) -> PyBaseExceptionRef { debug_assert_eq!( exc_type.slots.basicsize, - std::mem::size_of::<PyBaseException>(), + core::mem::size_of::<PyBaseException>(), "vm.new_exception() is only for exception types without additional payload. The given type '{}' is not allowed.", exc_type.class().name() ); @@ -118,7 +118,7 @@ impl VirtualMachine { errno: Option<i32>, msg: impl ToPyObject, ) -> PyRef<PyOSError> { - debug_assert_eq!(exc_type.slots.basicsize, std::mem::size_of::<PyOSError>()); + debug_assert_eq!(exc_type.slots.basicsize, core::mem::size_of::<PyOSError>()); OSErrorBuilder::with_subtype(exc_type, errno, msg, self).build(self) } diff --git a/crates/vm/src/vm/vm_ops.rs b/crates/vm/src/vm/vm_ops.rs index 635fa10e630..1d466984377 100644 --- a/crates/vm/src/vm/vm_ops.rs +++ b/crates/vm/src/vm/vm_ops.rs @@ -302,8 +302,8 @@ impl VirtualMachine { } if let Some(slot_c) = class_c.slots.as_number.left_ternary_op(op_slot) - && slot_a.is_some_and(|slot_a| !std::ptr::fn_addr_eq(slot_a, slot_c)) - && slot_b.is_some_and(|slot_b| !std::ptr::fn_addr_eq(slot_b, slot_c)) + && slot_a.is_some_and(|slot_a| !core::ptr::fn_addr_eq(slot_a, slot_c)) + && slot_b.is_some_and(|slot_b| !core::ptr::fn_addr_eq(slot_b, slot_c)) { let ret = slot_c(a, b, c, self)?; if !ret.is(&self.ctx.not_implemented) { diff --git a/crates/vm/src/warn.rs b/crates/vm/src/warn.rs index 3dbd43ab537..09d48078e56 100644 --- a/crates/vm/src/warn.rs +++ b/crates/vm/src/warn.rs @@ -60,7 +60,7 @@ fn get_warnings_attr( && !vm .state .finalizing - .load(std::sync::atomic::Ordering::SeqCst) + .load(core::sync::atomic::Ordering::SeqCst) { match vm.import("warnings", 0) { Ok(module) => module, diff --git a/crates/vm/src/windows.rs b/crates/vm/src/windows.rs index ccf940811b8..ff2b612c06d 100644 --- a/crates/vm/src/windows.rs +++ b/crates/vm/src/windows.rs @@ -301,7 +301,7 @@ fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatSt wide.as_ptr(), access, 0, - std::ptr::null(), + core::ptr::null(), OPEN_EXISTING, flags, std::ptr::null_mut(), @@ -337,7 +337,7 @@ fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatSt wide.as_ptr(), access | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, - std::ptr::null(), + core::ptr::null(), OPEN_EXISTING, flags, std::ptr::null_mut(), @@ -355,7 +355,7 @@ fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatSt wide.as_ptr(), access, 0, - std::ptr::null(), + core::ptr::null(), OPEN_EXISTING, flags | FILE_FLAG_OPEN_REPARSE_POINT, std::ptr::null_mut(), diff --git a/examples/dis.rs b/examples/dis.rs index 504b734ca59..942643cd54b 100644 --- a/examples/dis.rs +++ b/examples/dis.rs @@ -9,9 +9,9 @@ #[macro_use] extern crate log; +use core::error::Error; use lexopt::ValueExt; use rustpython_compiler as compiler; -use std::error::Error; use std::fs; use std::path::{Path, PathBuf}; diff --git a/src/lib.rs b/src/lib.rs index 8ee22d4eb5a..9dff074e20d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -258,7 +258,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { } #[cfg(feature = "flame-it")] -fn write_profile(settings: &Settings) -> Result<(), Box<dyn std::error::Error>> { +fn write_profile(settings: &Settings) -> Result<(), Box<dyn core::error::Error>> { use std::{fs, io}; enum ProfileFormat { From 5e1fc93f5043c1f054122e7220416b4979521dee Mon Sep 17 00:00:00 2001 From: Jack O'Connor <github@jackoconnor.dev> Date: Tue, 30 Dec 2025 04:42:47 +0000 Subject: [PATCH 632/819] Update zipfile to 3.13.5 (#6069) * Add Lib/test/archiver_tests.py @ 3.13.5 Needed for updated zipfile tests. * Update zipfile to 3.13.5 Notes: - I have to skip some brand new tests due to shift_jis encoding not being supported - `test_write_filtered_python_package` marked as `expectedFailure` with "AttributeError: module 'os' has no attribute 'supports_effective_ids'" - I didn't want to do a partial or full update to os module in this PR --------- Co-authored-by: Jack O'Connor <jack@jackoconnor.dev> --- Lib/test/archiver_tests.py | 177 +++ Lib/test/test_zipfile/__init__.py | 5 + Lib/test/test_zipfile/__main__.py | 7 + Lib/test/test_zipfile/_path/__init__.py | 0 Lib/test/test_zipfile/_path/_functools.py | 9 + Lib/test/test_zipfile/_path/_itertools.py | 79 + Lib/test/test_zipfile/_path/_support.py | 9 + Lib/test/test_zipfile/_path/_test_params.py | 39 + .../test_zipfile/_path/test_complexity.py | 105 ++ Lib/test/test_zipfile/_path/test_path.py | 687 +++++++++ Lib/test/test_zipfile/_path/write-alpharep.py | 3 + .../test_core.py} | 1291 ++++++++++++----- Lib/{zipfile.py => zipfile/__init__.py} | 737 ++++------ Lib/zipfile/__main__.py | 4 + Lib/zipfile/_path/__init__.py | 452 ++++++ Lib/zipfile/_path/glob.py | 113 ++ 16 files changed, 2890 insertions(+), 827 deletions(-) create mode 100644 Lib/test/archiver_tests.py create mode 100644 Lib/test/test_zipfile/__init__.py create mode 100644 Lib/test/test_zipfile/__main__.py create mode 100644 Lib/test/test_zipfile/_path/__init__.py create mode 100644 Lib/test/test_zipfile/_path/_functools.py create mode 100644 Lib/test/test_zipfile/_path/_itertools.py create mode 100644 Lib/test/test_zipfile/_path/_support.py create mode 100644 Lib/test/test_zipfile/_path/_test_params.py create mode 100644 Lib/test/test_zipfile/_path/test_complexity.py create mode 100644 Lib/test/test_zipfile/_path/test_path.py create mode 100644 Lib/test/test_zipfile/_path/write-alpharep.py rename Lib/test/{test_zipfile.py => test_zipfile/test_core.py} (72%) rename Lib/{zipfile.py => zipfile/__init__.py} (85%) create mode 100644 Lib/zipfile/__main__.py create mode 100644 Lib/zipfile/_path/__init__.py create mode 100644 Lib/zipfile/_path/glob.py diff --git a/Lib/test/archiver_tests.py b/Lib/test/archiver_tests.py new file mode 100644 index 00000000000..24745941b08 --- /dev/null +++ b/Lib/test/archiver_tests.py @@ -0,0 +1,177 @@ +"""Tests common to tarfile and zipfile.""" + +import os +import sys + +from test.support import swap_attr +from test.support import os_helper + +class OverwriteTests: + + def setUp(self): + os.makedirs(self.testdir) + self.addCleanup(os_helper.rmtree, self.testdir) + + def create_file(self, path, content=b''): + with open(path, 'wb') as f: + f.write(content) + + def open(self, path): + raise NotImplementedError + + def extractall(self, ar): + raise NotImplementedError + + + def test_overwrite_file_as_file(self): + target = os.path.join(self.testdir, 'test') + self.create_file(target, b'content') + with self.open(self.ar_with_file) as ar: + self.extractall(ar) + self.assertTrue(os.path.isfile(target)) + with open(target, 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + def test_overwrite_dir_as_dir(self): + target = os.path.join(self.testdir, 'test') + os.mkdir(target) + with self.open(self.ar_with_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + + def test_overwrite_dir_as_implicit_dir(self): + target = os.path.join(self.testdir, 'test') + os.mkdir(target) + with self.open(self.ar_with_implicit_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + self.assertTrue(os.path.isfile(os.path.join(target, 'file'))) + with open(os.path.join(target, 'file'), 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + def test_overwrite_dir_as_file(self): + target = os.path.join(self.testdir, 'test') + os.mkdir(target) + with self.open(self.ar_with_file) as ar: + with self.assertRaises(PermissionError if sys.platform == 'win32' + else IsADirectoryError): + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + + def test_overwrite_file_as_dir(self): + target = os.path.join(self.testdir, 'test') + self.create_file(target, b'content') + with self.open(self.ar_with_dir) as ar: + with self.assertRaises(FileExistsError): + self.extractall(ar) + self.assertTrue(os.path.isfile(target)) + with open(target, 'rb') as f: + self.assertEqual(f.read(), b'content') + + def test_overwrite_file_as_implicit_dir(self): + target = os.path.join(self.testdir, 'test') + self.create_file(target, b'content') + with self.open(self.ar_with_implicit_dir) as ar: + with self.assertRaises(FileNotFoundError if sys.platform == 'win32' + else NotADirectoryError): + self.extractall(ar) + self.assertTrue(os.path.isfile(target)) + with open(target, 'rb') as f: + self.assertEqual(f.read(), b'content') + + @os_helper.skip_unless_symlink + def test_overwrite_file_symlink_as_file(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + self.create_file(target2, b'content') + os.symlink('test2', target) + with self.open(self.ar_with_file) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isfile(target2)) + with open(target2, 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + @os_helper.skip_unless_symlink + def test_overwrite_broken_file_symlink_as_file(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.symlink('test2', target) + with self.open(self.ar_with_file) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isfile(target2)) + with open(target2, 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + @os_helper.skip_unless_symlink + def test_overwrite_dir_symlink_as_dir(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.mkdir(target2) + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isdir(target2)) + + @os_helper.skip_unless_symlink + def test_overwrite_dir_symlink_as_implicit_dir(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.mkdir(target2) + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_implicit_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isdir(target2)) + self.assertTrue(os.path.isfile(os.path.join(target2, 'file'))) + with open(os.path.join(target2, 'file'), 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + @os_helper.skip_unless_symlink + def test_overwrite_broken_dir_symlink_as_dir(self): + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_dir) as ar: + with self.assertRaises(FileExistsError): + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertFalse(os.path.exists(target2)) + + @os_helper.skip_unless_symlink + def test_overwrite_broken_dir_symlink_as_implicit_dir(self): + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_implicit_dir) as ar: + with self.assertRaises(FileExistsError): + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertFalse(os.path.exists(target2)) + + def test_concurrent_extract_dir(self): + target = os.path.join(self.testdir, 'test') + def concurrent_mkdir(*args, **kwargs): + orig_mkdir(*args, **kwargs) + orig_mkdir(*args, **kwargs) + with swap_attr(os, 'mkdir', concurrent_mkdir) as orig_mkdir: + with self.open(self.ar_with_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + + def test_concurrent_extract_implicit_dir(self): + target = os.path.join(self.testdir, 'test') + def concurrent_mkdir(*args, **kwargs): + orig_mkdir(*args, **kwargs) + orig_mkdir(*args, **kwargs) + with swap_attr(os, 'mkdir', concurrent_mkdir) as orig_mkdir: + with self.open(self.ar_with_implicit_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + self.assertTrue(os.path.isfile(os.path.join(target, 'file'))) diff --git a/Lib/test/test_zipfile/__init__.py b/Lib/test/test_zipfile/__init__.py new file mode 100644 index 00000000000..4b16ecc3115 --- /dev/null +++ b/Lib/test/test_zipfile/__init__.py @@ -0,0 +1,5 @@ +import os +from test.support import load_package_tests + +def load_tests(*args): + return load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_zipfile/__main__.py b/Lib/test/test_zipfile/__main__.py new file mode 100644 index 00000000000..e25ac946edf --- /dev/null +++ b/Lib/test/test_zipfile/__main__.py @@ -0,0 +1,7 @@ +import unittest + +from . import load_tests # noqa: F401 + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_zipfile/_path/__init__.py b/Lib/test/test_zipfile/_path/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Lib/test/test_zipfile/_path/_functools.py b/Lib/test/test_zipfile/_path/_functools.py new file mode 100644 index 00000000000..75f2b20e06d --- /dev/null +++ b/Lib/test/test_zipfile/_path/_functools.py @@ -0,0 +1,9 @@ +import functools + + +# from jaraco.functools 3.5.2 +def compose(*funcs): + def compose_two(f1, f2): + return lambda *args, **kwargs: f1(f2(*args, **kwargs)) + + return functools.reduce(compose_two, funcs) diff --git a/Lib/test/test_zipfile/_path/_itertools.py b/Lib/test/test_zipfile/_path/_itertools.py new file mode 100644 index 00000000000..f735dd21733 --- /dev/null +++ b/Lib/test/test_zipfile/_path/_itertools.py @@ -0,0 +1,79 @@ +import itertools +from collections import deque +from itertools import islice + + +# from jaraco.itertools 6.3.0 +class Counter: + """ + Wrap an iterable in an object that stores the count of items + that pass through it. + + >>> items = Counter(range(20)) + >>> items.count + 0 + >>> values = list(items) + >>> items.count + 20 + """ + + def __init__(self, i): + self.count = 0 + self.iter = zip(itertools.count(1), i) + + def __iter__(self): + return self + + def __next__(self): + self.count, result = next(self.iter) + return result + + +# from more_itertools v8.13.0 +def always_iterable(obj, base_type=(str, bytes)): + if obj is None: + return iter(()) + + if (base_type is not None) and isinstance(obj, base_type): + return iter((obj,)) + + try: + return iter(obj) + except TypeError: + return iter((obj,)) + + +# from more_itertools v9.0.0 +def consume(iterator, n=None): + """Advance *iterable* by *n* steps. If *n* is ``None``, consume it + entirely. + Efficiently exhausts an iterator without returning values. Defaults to + consuming the whole iterator, but an optional second argument may be + provided to limit consumption. + >>> i = (x for x in range(10)) + >>> next(i) + 0 + >>> consume(i, 3) + >>> next(i) + 4 + >>> consume(i) + >>> next(i) + Traceback (most recent call last): + File "<stdin>", line 1, in <module> + StopIteration + If the iterator has fewer items remaining than the provided limit, the + whole iterator will be consumed. + >>> i = (x for x in range(3)) + >>> consume(i, 5) + >>> next(i) + Traceback (most recent call last): + File "<stdin>", line 1, in <module> + StopIteration + """ + # Use functions that consume iterators at C speed. + if n is None: + # feed the entire iterator into a zero-length deque + deque(iterator, maxlen=0) + else: + # advance to the empty slice starting at position n + next(islice(iterator, n, n), None) diff --git a/Lib/test/test_zipfile/_path/_support.py b/Lib/test/test_zipfile/_path/_support.py new file mode 100644 index 00000000000..1afdf3b3a77 --- /dev/null +++ b/Lib/test/test_zipfile/_path/_support.py @@ -0,0 +1,9 @@ +import importlib +import unittest + + +def import_or_skip(name): + try: + return importlib.import_module(name) + except ImportError: # pragma: no cover + raise unittest.SkipTest(f'Unable to import {name}') diff --git a/Lib/test/test_zipfile/_path/_test_params.py b/Lib/test/test_zipfile/_path/_test_params.py new file mode 100644 index 00000000000..00a9eaf2f99 --- /dev/null +++ b/Lib/test/test_zipfile/_path/_test_params.py @@ -0,0 +1,39 @@ +import functools +import types + +from ._itertools import always_iterable + + +def parameterize(names, value_groups): + """ + Decorate a test method to run it as a set of subtests. + + Modeled after pytest.parametrize. + """ + + def decorator(func): + @functools.wraps(func) + def wrapped(self): + for values in value_groups: + resolved = map(Invoked.eval, always_iterable(values)) + params = dict(zip(always_iterable(names), resolved)) + with self.subTest(**params): + func(self, **params) + + return wrapped + + return decorator + + +class Invoked(types.SimpleNamespace): + """ + Wrap a function to be invoked for each usage. + """ + + @classmethod + def wrap(cls, func): + return cls(func=func) + + @classmethod + def eval(cls, cand): + return cand.func() if isinstance(cand, cls) else cand diff --git a/Lib/test/test_zipfile/_path/test_complexity.py b/Lib/test/test_zipfile/_path/test_complexity.py new file mode 100644 index 00000000000..7c108fc6ab8 --- /dev/null +++ b/Lib/test/test_zipfile/_path/test_complexity.py @@ -0,0 +1,105 @@ +import io +import itertools +import math +import re +import string +import unittest +import zipfile + +from ._functools import compose +from ._itertools import consume +from ._support import import_or_skip + +big_o = import_or_skip('big_o') +pytest = import_or_skip('pytest') + + +class TestComplexity(unittest.TestCase): + @pytest.mark.flaky + def test_implied_dirs_performance(self): + best, others = big_o.big_o( + compose(consume, zipfile._path.CompleteDirs._implied_dirs), + lambda size: [ + '/'.join(string.ascii_lowercase + str(n)) for n in range(size) + ], + max_n=1000, + min_n=1, + ) + assert best <= big_o.complexities.Linear + + def make_zip_path(self, depth=1, width=1) -> zipfile.Path: + """ + Construct a Path with width files at every level of depth. + """ + zf = zipfile.ZipFile(io.BytesIO(), mode='w') + pairs = itertools.product(self.make_deep_paths(depth), self.make_names(width)) + for path, name in pairs: + zf.writestr(f"{path}{name}.txt", b'') + zf.filename = "big un.zip" + return zipfile.Path(zf) + + @classmethod + def make_names(cls, width, letters=string.ascii_lowercase): + """ + >>> list(TestComplexity.make_names(1)) + ['a'] + >>> list(TestComplexity.make_names(2)) + ['a', 'b'] + >>> list(TestComplexity.make_names(30)) + ['aa', 'ab', ..., 'bd'] + >>> list(TestComplexity.make_names(17124)) + ['aaa', 'aab', ..., 'zip'] + """ + # determine how many products are needed to produce width + n_products = max(1, math.ceil(math.log(width, len(letters)))) + inputs = (letters,) * n_products + combinations = itertools.product(*inputs) + names = map(''.join, combinations) + return itertools.islice(names, width) + + @classmethod + def make_deep_paths(cls, depth): + return map(cls.make_deep_path, range(depth)) + + @classmethod + def make_deep_path(cls, depth): + return ''.join(('d/',) * depth) + + def test_baseline_regex_complexity(self): + best, others = big_o.big_o( + lambda path: re.fullmatch(r'[^/]*\\.txt', path), + self.make_deep_path, + max_n=100, + min_n=1, + ) + assert best <= big_o.complexities.Constant + + @pytest.mark.flaky + def test_glob_depth(self): + best, others = big_o.big_o( + lambda path: consume(path.glob('*.txt')), + self.make_zip_path, + max_n=100, + min_n=1, + ) + assert best <= big_o.complexities.Linear + + @pytest.mark.flaky + def test_glob_width(self): + best, others = big_o.big_o( + lambda path: consume(path.glob('*.txt')), + lambda size: self.make_zip_path(width=size), + max_n=100, + min_n=1, + ) + assert best <= big_o.complexities.Linear + + @pytest.mark.flaky + def test_glob_width_and_depth(self): + best, others = big_o.big_o( + lambda path: consume(path.glob('*.txt')), + lambda size: self.make_zip_path(depth=size, width=size), + max_n=10, + min_n=1, + ) + assert best <= big_o.complexities.Linear diff --git a/Lib/test/test_zipfile/_path/test_path.py b/Lib/test/test_zipfile/_path/test_path.py new file mode 100644 index 00000000000..2d1c06cd968 --- /dev/null +++ b/Lib/test/test_zipfile/_path/test_path.py @@ -0,0 +1,687 @@ +import contextlib +import io +import itertools +import pathlib +import pickle +import stat +import sys +import time +import unittest +import zipfile +import zipfile._path + +from test.support.os_helper import FakePath, temp_dir + +from ._functools import compose +from ._itertools import Counter +from ._test_params import Invoked, parameterize + + +class jaraco: + class itertools: + Counter = Counter + + +def _make_link(info: zipfile.ZipInfo): # type: ignore[name-defined] + info.external_attr |= stat.S_IFLNK << 16 + + +def build_alpharep_fixture(): + """ + Create a zip file with this structure: + + . + ├── a.txt + ├── n.txt (-> a.txt) + ├── b + │ ├── c.txt + │ ├── d + │ │ └── e.txt + │ └── f.txt + ├── g + │ └── h + │ └── i.txt + └── j + ├── k.bin + ├── l.baz + └── m.bar + + This fixture has the following key characteristics: + + - a file at the root (a) + - a file two levels deep (b/d/e) + - multiple files in a directory (b/c, b/f) + - a directory containing only a directory (g/h) + - a directory with files of different extensions (j/klm) + - a symlink (n) pointing to (a) + + "alpha" because it uses alphabet + "rep" because it's a representative example + """ + data = io.BytesIO() + zf = zipfile.ZipFile(data, "w") + zf.writestr("a.txt", b"content of a") + zf.writestr("b/c.txt", b"content of c") + zf.writestr("b/d/e.txt", b"content of e") + zf.writestr("b/f.txt", b"content of f") + zf.writestr("g/h/i.txt", b"content of i") + zf.writestr("j/k.bin", b"content of k") + zf.writestr("j/l.baz", b"content of l") + zf.writestr("j/m.bar", b"content of m") + zf.writestr("n.txt", b"a.txt") + _make_link(zf.infolist()[-1]) + + zf.filename = "alpharep.zip" + return zf + + +alpharep_generators = [ + Invoked.wrap(build_alpharep_fixture), + Invoked.wrap(compose(zipfile._path.CompleteDirs.inject, build_alpharep_fixture)), +] + +pass_alpharep = parameterize(['alpharep'], alpharep_generators) + + +class TestPath(unittest.TestCase): + def setUp(self): + self.fixtures = contextlib.ExitStack() + self.addCleanup(self.fixtures.close) + + def zipfile_ondisk(self, alpharep): + tmpdir = pathlib.Path(self.fixtures.enter_context(temp_dir())) + buffer = alpharep.fp + alpharep.close() + path = tmpdir / alpharep.filename + with path.open("wb") as strm: + strm.write(buffer.getvalue()) + return path + + @pass_alpharep + def test_iterdir_and_types(self, alpharep): + root = zipfile.Path(alpharep) + assert root.is_dir() + a, n, b, g, j = root.iterdir() + assert a.is_file() + assert b.is_dir() + assert g.is_dir() + c, f, d = b.iterdir() + assert c.is_file() and f.is_file() + (e,) = d.iterdir() + assert e.is_file() + (h,) = g.iterdir() + (i,) = h.iterdir() + assert i.is_file() + + @pass_alpharep + def test_is_file_missing(self, alpharep): + root = zipfile.Path(alpharep) + assert not root.joinpath('missing.txt').is_file() + + @pass_alpharep + def test_iterdir_on_file(self, alpharep): + root = zipfile.Path(alpharep) + a, n, b, g, j = root.iterdir() + with self.assertRaises(ValueError): + a.iterdir() + + @pass_alpharep + def test_subdir_is_dir(self, alpharep): + root = zipfile.Path(alpharep) + assert (root / 'b').is_dir() + assert (root / 'b/').is_dir() + assert (root / 'g').is_dir() + assert (root / 'g/').is_dir() + + @pass_alpharep + def test_open(self, alpharep): + root = zipfile.Path(alpharep) + a, n, b, g, j = root.iterdir() + with a.open(encoding="utf-8") as strm: + data = strm.read() + self.assertEqual(data, "content of a") + with a.open('r', "utf-8") as strm: # not a kw, no gh-101144 TypeError + data = strm.read() + self.assertEqual(data, "content of a") + + def test_open_encoding_utf16(self): + in_memory_file = io.BytesIO() + zf = zipfile.ZipFile(in_memory_file, "w") + zf.writestr("path/16.txt", "This was utf-16".encode("utf-16")) + zf.filename = "test_open_utf16.zip" + root = zipfile.Path(zf) + (path,) = root.iterdir() + u16 = path.joinpath("16.txt") + with u16.open('r', "utf-16") as strm: + data = strm.read() + assert data == "This was utf-16" + with u16.open(encoding="utf-16") as strm: + data = strm.read() + assert data == "This was utf-16" + + def test_open_encoding_errors(self): + in_memory_file = io.BytesIO() + zf = zipfile.ZipFile(in_memory_file, "w") + zf.writestr("path/bad-utf8.bin", b"invalid utf-8: \xff\xff.") + zf.filename = "test_read_text_encoding_errors.zip" + root = zipfile.Path(zf) + (path,) = root.iterdir() + u16 = path.joinpath("bad-utf8.bin") + + # encoding= as a positional argument for gh-101144. + data = u16.read_text("utf-8", errors="ignore") + assert data == "invalid utf-8: ." + with u16.open("r", "utf-8", errors="surrogateescape") as f: + assert f.read() == "invalid utf-8: \udcff\udcff." + + # encoding= both positional and keyword is an error; gh-101144. + with self.assertRaisesRegex(TypeError, "encoding"): + data = u16.read_text("utf-8", encoding="utf-8") + + # both keyword arguments work. + with u16.open("r", encoding="utf-8", errors="strict") as f: + # error during decoding with wrong codec. + with self.assertRaises(UnicodeDecodeError): + f.read() + + @unittest.skipIf( + not getattr(sys.flags, 'warn_default_encoding', 0), + "Requires warn_default_encoding", + ) + @pass_alpharep + def test_encoding_warnings(self, alpharep): + """EncodingWarning must blame the read_text and open calls.""" + assert sys.flags.warn_default_encoding + root = zipfile.Path(alpharep) + with self.assertWarns(EncodingWarning) as wc: # noqa: F821 (astral-sh/ruff#13296) + root.joinpath("a.txt").read_text() + assert __file__ == wc.filename + with self.assertWarns(EncodingWarning) as wc: # noqa: F821 (astral-sh/ruff#13296) + root.joinpath("a.txt").open("r").close() + assert __file__ == wc.filename + + def test_open_write(self): + """ + If the zipfile is open for write, it should be possible to + write bytes or text to it. + """ + zf = zipfile.Path(zipfile.ZipFile(io.BytesIO(), mode='w')) + with zf.joinpath('file.bin').open('wb') as strm: + strm.write(b'binary contents') + with zf.joinpath('file.txt').open('w', encoding="utf-8") as strm: + strm.write('text file') + + @pass_alpharep + def test_open_extant_directory(self, alpharep): + """ + Attempting to open a directory raises IsADirectoryError. + """ + zf = zipfile.Path(alpharep) + with self.assertRaises(IsADirectoryError): + zf.joinpath('b').open() + + @pass_alpharep + def test_open_binary_invalid_args(self, alpharep): + root = zipfile.Path(alpharep) + with self.assertRaises(ValueError): + root.joinpath('a.txt').open('rb', encoding='utf-8') + with self.assertRaises(ValueError): + root.joinpath('a.txt').open('rb', 'utf-8') + + @pass_alpharep + def test_open_missing_directory(self, alpharep): + """ + Attempting to open a missing directory raises FileNotFoundError. + """ + zf = zipfile.Path(alpharep) + with self.assertRaises(FileNotFoundError): + zf.joinpath('z').open() + + @pass_alpharep + def test_read(self, alpharep): + root = zipfile.Path(alpharep) + a, n, b, g, j = root.iterdir() + assert a.read_text(encoding="utf-8") == "content of a" + # Also check positional encoding arg (gh-101144). + assert a.read_text("utf-8") == "content of a" + assert a.read_bytes() == b"content of a" + + @pass_alpharep + def test_joinpath(self, alpharep): + root = zipfile.Path(alpharep) + a = root.joinpath("a.txt") + assert a.is_file() + e = root.joinpath("b").joinpath("d").joinpath("e.txt") + assert e.read_text(encoding="utf-8") == "content of e" + + @pass_alpharep + def test_joinpath_multiple(self, alpharep): + root = zipfile.Path(alpharep) + e = root.joinpath("b", "d", "e.txt") + assert e.read_text(encoding="utf-8") == "content of e" + + @pass_alpharep + def test_traverse_truediv(self, alpharep): + root = zipfile.Path(alpharep) + a = root / "a.txt" + assert a.is_file() + e = root / "b" / "d" / "e.txt" + assert e.read_text(encoding="utf-8") == "content of e" + + @pass_alpharep + def test_pathlike_construction(self, alpharep): + """ + zipfile.Path should be constructable from a path-like object + """ + zipfile_ondisk = self.zipfile_ondisk(alpharep) + pathlike = FakePath(str(zipfile_ondisk)) + zipfile.Path(pathlike) + + @pass_alpharep + def test_traverse_pathlike(self, alpharep): + root = zipfile.Path(alpharep) + root / FakePath("a") + + @pass_alpharep + def test_parent(self, alpharep): + root = zipfile.Path(alpharep) + assert (root / 'a').parent.at == '' + assert (root / 'a' / 'b').parent.at == 'a/' + + @pass_alpharep + def test_dir_parent(self, alpharep): + root = zipfile.Path(alpharep) + assert (root / 'b').parent.at == '' + assert (root / 'b/').parent.at == '' + + @pass_alpharep + def test_missing_dir_parent(self, alpharep): + root = zipfile.Path(alpharep) + assert (root / 'missing dir/').parent.at == '' + + @pass_alpharep + def test_mutability(self, alpharep): + """ + If the underlying zipfile is changed, the Path object should + reflect that change. + """ + root = zipfile.Path(alpharep) + a, n, b, g, j = root.iterdir() + alpharep.writestr('foo.txt', 'foo') + alpharep.writestr('bar/baz.txt', 'baz') + assert any(child.name == 'foo.txt' for child in root.iterdir()) + assert (root / 'foo.txt').read_text(encoding="utf-8") == 'foo' + (baz,) = (root / 'bar').iterdir() + assert baz.read_text(encoding="utf-8") == 'baz' + + HUGE_ZIPFILE_NUM_ENTRIES = 2**13 + + def huge_zipfile(self): + """Create a read-only zipfile with a huge number of entries entries.""" + strm = io.BytesIO() + zf = zipfile.ZipFile(strm, "w") + for entry in map(str, range(self.HUGE_ZIPFILE_NUM_ENTRIES)): + zf.writestr(entry, entry) + zf.mode = 'r' + return zf + + def test_joinpath_constant_time(self): + """ + Ensure joinpath on items in zipfile is linear time. + """ + root = zipfile.Path(self.huge_zipfile()) + entries = jaraco.itertools.Counter(root.iterdir()) + for entry in entries: + entry.joinpath('suffix') + # Check the file iterated all items + assert entries.count == self.HUGE_ZIPFILE_NUM_ENTRIES + + @pass_alpharep + def test_read_does_not_close(self, alpharep): + alpharep = self.zipfile_ondisk(alpharep) + with zipfile.ZipFile(alpharep) as file: + for rep in range(2): + zipfile.Path(file, 'a.txt').read_text(encoding="utf-8") + + @pass_alpharep + def test_subclass(self, alpharep): + class Subclass(zipfile.Path): + pass + + root = Subclass(alpharep) + assert isinstance(root / 'b', Subclass) + + @pass_alpharep + def test_filename(self, alpharep): + root = zipfile.Path(alpharep) + assert root.filename == pathlib.Path('alpharep.zip') + + @pass_alpharep + def test_root_name(self, alpharep): + """ + The name of the root should be the name of the zipfile + """ + root = zipfile.Path(alpharep) + assert root.name == 'alpharep.zip' == root.filename.name + + @pass_alpharep + def test_root_on_disk(self, alpharep): + """ + The name/stem of the root should match the zipfile on disk. + + This condition must hold across platforms. + """ + root = zipfile.Path(self.zipfile_ondisk(alpharep)) + assert root.name == 'alpharep.zip' == root.filename.name + assert root.stem == 'alpharep' == root.filename.stem + + @pass_alpharep + def test_suffix(self, alpharep): + """ + The suffix of the root should be the suffix of the zipfile. + The suffix of each nested file is the final component's last suffix, if any. + Includes the leading period, just like pathlib.Path. + """ + root = zipfile.Path(alpharep) + assert root.suffix == '.zip' == root.filename.suffix + + b = root / "b.txt" + assert b.suffix == ".txt" + + c = root / "c" / "filename.tar.gz" + assert c.suffix == ".gz" + + d = root / "d" + assert d.suffix == "" + + @pass_alpharep + def test_suffixes(self, alpharep): + """ + The suffix of the root should be the suffix of the zipfile. + The suffix of each nested file is the final component's last suffix, if any. + Includes the leading period, just like pathlib.Path. + """ + root = zipfile.Path(alpharep) + assert root.suffixes == ['.zip'] == root.filename.suffixes + + b = root / 'b.txt' + assert b.suffixes == ['.txt'] + + c = root / 'c' / 'filename.tar.gz' + assert c.suffixes == ['.tar', '.gz'] + + d = root / 'd' + assert d.suffixes == [] + + e = root / '.hgrc' + assert e.suffixes == [] + + @pass_alpharep + def test_suffix_no_filename(self, alpharep): + alpharep.filename = None + root = zipfile.Path(alpharep) + assert root.joinpath('example').suffix == "" + assert root.joinpath('example').suffixes == [] + + @pass_alpharep + def test_stem(self, alpharep): + """ + The final path component, without its suffix + """ + root = zipfile.Path(alpharep) + assert root.stem == 'alpharep' == root.filename.stem + + b = root / "b.txt" + assert b.stem == "b" + + c = root / "c" / "filename.tar.gz" + assert c.stem == "filename.tar" + + d = root / "d" + assert d.stem == "d" + + assert (root / ".gitignore").stem == ".gitignore" + + @pass_alpharep + def test_root_parent(self, alpharep): + root = zipfile.Path(alpharep) + assert root.parent == pathlib.Path('.') + root.root.filename = 'foo/bar.zip' + assert root.parent == pathlib.Path('foo') + + @pass_alpharep + def test_root_unnamed(self, alpharep): + """ + It is an error to attempt to get the name + or parent of an unnamed zipfile. + """ + alpharep.filename = None + root = zipfile.Path(alpharep) + with self.assertRaises(TypeError): + root.name + with self.assertRaises(TypeError): + root.parent + + # .name and .parent should still work on subs + sub = root / "b" + assert sub.name == "b" + assert sub.parent + + @pass_alpharep + def test_match_and_glob(self, alpharep): + root = zipfile.Path(alpharep) + assert not root.match("*.txt") + + assert list(root.glob("b/c.*")) == [zipfile.Path(alpharep, "b/c.txt")] + assert list(root.glob("b/*.txt")) == [ + zipfile.Path(alpharep, "b/c.txt"), + zipfile.Path(alpharep, "b/f.txt"), + ] + + @pass_alpharep + def test_glob_recursive(self, alpharep): + root = zipfile.Path(alpharep) + files = root.glob("**/*.txt") + assert all(each.match("*.txt") for each in files) + + assert list(root.glob("**/*.txt")) == list(root.rglob("*.txt")) + + @pass_alpharep + def test_glob_dirs(self, alpharep): + root = zipfile.Path(alpharep) + assert list(root.glob('b')) == [zipfile.Path(alpharep, "b/")] + assert list(root.glob('b*')) == [zipfile.Path(alpharep, "b/")] + + @pass_alpharep + def test_glob_subdir(self, alpharep): + root = zipfile.Path(alpharep) + assert list(root.glob('g/h')) == [zipfile.Path(alpharep, "g/h/")] + assert list(root.glob('g*/h*')) == [zipfile.Path(alpharep, "g/h/")] + + @pass_alpharep + def test_glob_subdirs(self, alpharep): + root = zipfile.Path(alpharep) + + assert list(root.glob("*/i.txt")) == [] + assert list(root.rglob("*/i.txt")) == [zipfile.Path(alpharep, "g/h/i.txt")] + + @pass_alpharep + def test_glob_does_not_overmatch_dot(self, alpharep): + root = zipfile.Path(alpharep) + + assert list(root.glob("*.xt")) == [] + + @pass_alpharep + def test_glob_single_char(self, alpharep): + root = zipfile.Path(alpharep) + + assert list(root.glob("a?txt")) == [zipfile.Path(alpharep, "a.txt")] + assert list(root.glob("a[.]txt")) == [zipfile.Path(alpharep, "a.txt")] + assert list(root.glob("a[?]txt")) == [] + + @pass_alpharep + def test_glob_chars(self, alpharep): + root = zipfile.Path(alpharep) + + assert list(root.glob("j/?.b[ai][nz]")) == [ + zipfile.Path(alpharep, "j/k.bin"), + zipfile.Path(alpharep, "j/l.baz"), + ] + + def test_glob_empty(self): + root = zipfile.Path(zipfile.ZipFile(io.BytesIO(), 'w')) + with self.assertRaises(ValueError): + root.glob('') + + @pass_alpharep + def test_eq_hash(self, alpharep): + root = zipfile.Path(alpharep) + assert root == zipfile.Path(alpharep) + + assert root != (root / "a.txt") + assert (root / "a.txt") == (root / "a.txt") + + root = zipfile.Path(alpharep) + assert root in {root} + + @pass_alpharep + def test_is_symlink(self, alpharep): + root = zipfile.Path(alpharep) + assert not root.joinpath('a.txt').is_symlink() + assert root.joinpath('n.txt').is_symlink() + + @pass_alpharep + def test_relative_to(self, alpharep): + root = zipfile.Path(alpharep) + relative = root.joinpath("b", "c.txt").relative_to(root / "b") + assert str(relative) == "c.txt" + + relative = root.joinpath("b", "d", "e.txt").relative_to(root / "b") + assert str(relative) == "d/e.txt" + + @pass_alpharep + def test_inheritance(self, alpharep): + cls = type('PathChild', (zipfile.Path,), {}) + file = cls(alpharep).joinpath('some dir').parent + assert isinstance(file, cls) + + @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON, fails on Windows") + @parameterize( + ['alpharep', 'path_type', 'subpath'], + itertools.product( + alpharep_generators, + [str, FakePath], + ['', 'b/'], + ), + ) + def test_pickle(self, alpharep, path_type, subpath): + zipfile_ondisk = path_type(str(self.zipfile_ondisk(alpharep))) + + saved_1 = pickle.dumps(zipfile.Path(zipfile_ondisk, at=subpath)) + restored_1 = pickle.loads(saved_1) + first, *rest = restored_1.iterdir() + assert first.read_text(encoding='utf-8').startswith('content of ') + + @pass_alpharep + def test_extract_orig_with_implied_dirs(self, alpharep): + """ + A zip file wrapped in a Path should extract even with implied dirs. + """ + source_path = self.zipfile_ondisk(alpharep) + zf = zipfile.ZipFile(source_path) + # wrap the zipfile for its side effect + zipfile.Path(zf) + zf.extractall(source_path.parent) + + @pass_alpharep + def test_getinfo_missing(self, alpharep): + """ + Validate behavior of getinfo on original zipfile after wrapping. + """ + zipfile.Path(alpharep) + with self.assertRaises(KeyError): + alpharep.getinfo('does-not-exist') + + def test_malformed_paths(self): + """ + Path should handle malformed paths gracefully. + + Paths with leading slashes are not visible. + + Paths with dots are treated like regular files. + """ + data = io.BytesIO() + zf = zipfile.ZipFile(data, "w") + zf.writestr("/one-slash.txt", b"content") + zf.writestr("//two-slash.txt", b"content") + zf.writestr("../parent.txt", b"content") + zf.filename = '' + root = zipfile.Path(zf) + assert list(map(str, root.iterdir())) == ['../'] + assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content' + + def test_unsupported_names(self): + """ + Path segments with special characters are readable. + + On some platforms or file systems, characters like + ``:`` and ``?`` are not allowed, but they are valid + in the zip file. + """ + data = io.BytesIO() + zf = zipfile.ZipFile(data, "w") + zf.writestr("path?", b"content") + zf.writestr("V: NMS.flac", b"fLaC...") + zf.filename = '' + root = zipfile.Path(zf) + contents = root.iterdir() + assert next(contents).name == 'path?' + assert next(contents).name == 'V: NMS.flac' + assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..." + + def test_backslash_not_separator(self): + """ + In a zip file, backslashes are not separators. + """ + data = io.BytesIO() + zf = zipfile.ZipFile(data, "w") + zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content") + zf.filename = '' + root = zipfile.Path(zf) + (first,) = root.iterdir() + assert not first.is_dir() + assert first.name == 'foo\\bar' + + @pass_alpharep + def test_interface(self, alpharep): + from importlib.resources.abc import Traversable + + zf = zipfile.Path(alpharep) + assert isinstance(zf, Traversable) + + +class DirtyZipInfo(zipfile.ZipInfo): + """ + Bypass name sanitization. + """ + + def __init__(self, filename, *args, **kwargs): + super().__init__(filename, *args, **kwargs) + self.filename = filename + + @classmethod + def for_name(cls, name, archive): + """ + Construct the same way that ZipFile.writestr does. + + TODO: extract this functionality and re-use + """ + self = cls(filename=name, date_time=time.localtime(time.time())[:6]) + self.compress_type = archive.compression + self.compress_level = archive.compresslevel + if self.filename.endswith('/'): # pragma: no cover + self.external_attr = 0o40775 << 16 # drwxrwxr-x + self.external_attr |= 0x10 # MS-DOS directory flag + else: + self.external_attr = 0o600 << 16 # ?rw------- + return self diff --git a/Lib/test/test_zipfile/_path/write-alpharep.py b/Lib/test/test_zipfile/_path/write-alpharep.py new file mode 100644 index 00000000000..7418391abad --- /dev/null +++ b/Lib/test/test_zipfile/_path/write-alpharep.py @@ -0,0 +1,3 @@ +from . import test_path + +__name__ == '__main__' and test_path.build_alpharep_fixture().extractall('alpharep') diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile/test_core.py similarity index 72% rename from Lib/test/test_zipfile.py rename to Lib/test/test_zipfile/test_core.py index e7705590cd2..fa1feef00cd 100644 --- a/Lib/test/test_zipfile.py +++ b/Lib/test/test_zipfile/test_core.py @@ -1,12 +1,11 @@ +import _pyio import array import contextlib import importlib.util import io import itertools import os -import pathlib import posixpath -import string import struct import subprocess import sys @@ -14,16 +13,20 @@ import unittest import unittest.mock as mock import zipfile -import functools from tempfile import TemporaryFile from random import randint, random, randbytes +from test import archiver_tests from test.support import script_helper -from test.support import (findfile, requires_zlib, requires_bz2, - requires_lzma, captured_stdout) -from test.support.os_helper import TESTFN, unlink, rmtree, temp_dir, temp_cwd +from test.support import ( + findfile, requires_zlib, requires_bz2, requires_lzma, + captured_stdout, captured_stderr, requires_subprocess +) +from test.support.os_helper import ( + TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count, FakePath +) TESTFN2 = TESTFN + "2" @@ -120,8 +123,9 @@ def zip_test(self, f, compression, compresslevel=None): self.assertEqual(info.filename, nm) self.assertEqual(info.file_size, len(self.data)) - # Check that testzip doesn't raise an exception - zipfp.testzip() + # Check that testzip thinks the archive is ok + # (it returns None if all contents could be read properly) + self.assertIsNone(zipfp.testzip()) def test_basic(self): for f in get_files(self): @@ -156,7 +160,7 @@ def test_open(self): self.zip_open_test(f, self.compression) def test_open_with_pathlike(self): - path = pathlib.Path(TESTFN2) + path = FakePath(TESTFN2) self.zip_open_test(path, self.compression) with zipfile.ZipFile(path, "r", self.compression) as zipfp: self.assertIsInstance(zipfp.filename, str) @@ -312,7 +316,7 @@ def test_writestr_compresslevel(self): # Compression level follows the constructor. a_info = zipfp.getinfo('a.txt') self.assertEqual(a_info.compress_type, self.compression) - self.assertEqual(a_info._compresslevel, 1) + self.assertEqual(a_info.compress_level, 1) # Compression level is overridden. b_info = zipfp.getinfo('b.txt') @@ -386,7 +390,6 @@ def test_repr(self): with zipfp.open(fname) as zipopen: r = repr(zipopen) self.assertIn('name=%r' % fname, r) - self.assertIn("mode='r'", r) if self.compression != zipfile.ZIP_STORED: self.assertIn('compress_type=', r) self.assertIn('[closed]', repr(zipopen)) @@ -405,7 +408,7 @@ def test_per_file_compresslevel(self): one_info = zipfp.getinfo('compress_1') nine_info = zipfp.getinfo('compress_9') self.assertEqual(one_info._compresslevel, 1) - self.assertEqual(nine_info._compresslevel, 9) + self.assertEqual(nine_info.compress_level, 9) def test_writing_errors(self): class BrokenFile(io.BytesIO): @@ -443,6 +446,27 @@ def write(self, data): self.assertEqual(zipfp.read('file1'), b'data1') self.assertEqual(zipfp.read('file2'), b'data2') + def test_zipextfile_attrs(self): + fname = "somefile.txt" + with zipfile.ZipFile(TESTFN2, mode="w") as zipfp: + zipfp.writestr(fname, "bogus") + + with zipfile.ZipFile(TESTFN2, mode="r") as zipfp: + with zipfp.open(fname) as fid: + self.assertEqual(fid.name, fname) + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertEqual(fid.mode, 'rb') + self.assertIs(fid.readable(), True) + self.assertIs(fid.writable(), False) + self.assertIs(fid.seekable(), True) + self.assertIs(fid.closed, False) + self.assertIs(fid.closed, True) + self.assertEqual(fid.name, fname) + self.assertEqual(fid.mode, 'rb') + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertRaises(ValueError, fid.readable) + self.assertIs(fid.writable(), False) + self.assertRaises(ValueError, fid.seekable) def tearDown(self): unlink(TESTFN) @@ -574,17 +598,16 @@ def test_write_default_name(self): def test_io_on_closed_zipextfile(self): fname = "somefile.txt" - with zipfile.ZipFile(TESTFN2, mode="w") as zipfp: + with zipfile.ZipFile(TESTFN2, mode="w", compression=self.compression) as zipfp: zipfp.writestr(fname, "bogus") with zipfile.ZipFile(TESTFN2, mode="r") as zipfp: with zipfp.open(fname) as fid: fid.close() + self.assertIs(fid.closed, True) self.assertRaises(ValueError, fid.read) self.assertRaises(ValueError, fid.seek, 0) self.assertRaises(ValueError, fid.tell) - self.assertRaises(ValueError, fid.readable) - self.assertRaises(ValueError, fid.seekable) def test_write_to_readonly(self): """Check that trying to call write() on a readonly ZipFile object @@ -744,8 +767,8 @@ def zip_test(self, f, compression): self.assertEqual(info.filename, nm) self.assertEqual(info.file_size, len(self.data)) - # Check that testzip doesn't raise an exception - zipfp.testzip() + # Check that testzip thinks the archive is valid + self.assertIsNone(zipfp.testzip()) def test_basic(self): for f in get_files(self): @@ -1077,6 +1100,159 @@ def test_generated_valid_zip64_extra(self): self.assertEqual(zinfo.header_offset, expected_header_offset) self.assertEqual(zf.read(zinfo), expected_content) + def test_force_zip64(self): + """Test that forcing zip64 extensions correctly notes this in the zip file""" + + # GH-103861 describes an issue where forcing a small file to use zip64 + # extensions would add a zip64 extra record, but not change the data + # sizes to 0xFFFFFFFF to indicate to the extractor that the zip64 + # record should be read. Additionally, it would not set the required + # version to indicate that zip64 extensions are required to extract it. + # This test replicates the situation and reads the raw data to specifically ensure: + # - The required extract version is always >= ZIP64_VERSION + # - The compressed and uncompressed size in the file headers are both + # 0xFFFFFFFF (ie. point to zip64 record) + # - The zip64 record is provided and has the correct sizes in it + # Other aspects of the zip are checked as well, but verifying the above is the main goal. + # Because this is hard to verify by parsing the data as a zip, the raw + # bytes are checked to ensure that they line up with the zip spec. + # The spec for this can be found at: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT + # The relevant sections for this test are: + # - 4.3.7 for local file header + # - 4.5.3 for zip64 extra field + + data = io.BytesIO() + with zipfile.ZipFile(data, mode="w", allowZip64=True) as zf: + with zf.open("text.txt", mode="w", force_zip64=True) as zi: + zi.write(b"_") + + zipdata = data.getvalue() + + # pull out and check zip information + ( + header, vers, os, flags, comp, csize, usize, fn_len, + ex_total_len, filename, ex_id, ex_len, ex_usize, ex_csize, cd_sig + ) = struct.unpack("<4sBBHH8xIIHH8shhQQx4s", zipdata[:63]) + + self.assertEqual(header, b"PK\x03\x04") # local file header + self.assertGreaterEqual(vers, zipfile.ZIP64_VERSION) # requires zip64 to extract + self.assertEqual(os, 0) # compatible with MS-DOS + self.assertEqual(flags, 0) # no flags + self.assertEqual(comp, 0) # compression method = stored + self.assertEqual(csize, 0xFFFFFFFF) # sizes are in zip64 extra + self.assertEqual(usize, 0xFFFFFFFF) + self.assertEqual(fn_len, 8) # filename len + self.assertEqual(ex_total_len, 20) # size of extra records + self.assertEqual(ex_id, 1) # Zip64 extra record + self.assertEqual(ex_len, 16) # 16 bytes of data + self.assertEqual(ex_usize, 1) # uncompressed size + self.assertEqual(ex_csize, 1) # compressed size + self.assertEqual(cd_sig, b"PK\x01\x02") # ensure the central directory header is next + + z = zipfile.ZipFile(io.BytesIO(zipdata)) + zinfos = z.infolist() + self.assertEqual(len(zinfos), 1) + self.assertGreaterEqual(zinfos[0].extract_version, zipfile.ZIP64_VERSION) # requires zip64 to extract + + def test_unseekable_zip_unknown_filesize(self): + """Test that creating a zip with/without seeking will raise a RuntimeError if zip64 was required but not used""" + + def make_zip(fp): + with zipfile.ZipFile(fp, mode="w", allowZip64=True) as zf: + with zf.open("text.txt", mode="w", force_zip64=False) as zi: + zi.write(b"_" * (zipfile.ZIP64_LIMIT + 1)) + + self.assertRaises(RuntimeError, make_zip, io.BytesIO()) + self.assertRaises(RuntimeError, make_zip, Unseekable(io.BytesIO())) + + def test_zip64_required_not_allowed_fail(self): + """Test that trying to add a large file to a zip that doesn't allow zip64 extensions fails on add""" + def make_zip(fp): + with zipfile.ZipFile(fp, mode="w", allowZip64=False) as zf: + # pretend zipfile.ZipInfo.from_file was used to get the name and filesize + info = zipfile.ZipInfo("text.txt") + info.file_size = zipfile.ZIP64_LIMIT + 1 + zf.open(info, mode="w") + + self.assertRaises(zipfile.LargeZipFile, make_zip, io.BytesIO()) + self.assertRaises(zipfile.LargeZipFile, make_zip, Unseekable(io.BytesIO())) + + def test_unseekable_zip_known_filesize(self): + """Test that creating a zip without seeking will use zip64 extensions if the file size is provided up-front""" + + # This test ensures that the zip will use a zip64 data descriptor (same + # as a regular data descriptor except the sizes are 8 bytes instead of + # 4) record to communicate the size of a file if the zip is being + # written to an unseekable stream. + # Because this sort of thing is hard to verify by parsing the data back + # in as a zip, this test looks at the raw bytes created to ensure that + # the correct data has been generated. + # The spec for this can be found at: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT + # The relevant sections for this test are: + # - 4.3.7 for local file header + # - 4.3.9 for the data descriptor + # - 4.5.3 for zip64 extra field + + file_size = zipfile.ZIP64_LIMIT + 1 + + def make_zip(fp): + with zipfile.ZipFile(fp, mode="w", allowZip64=True) as zf: + # pretend zipfile.ZipInfo.from_file was used to get the name and filesize + info = zipfile.ZipInfo("text.txt") + info.file_size = file_size + with zf.open(info, mode="w", force_zip64=False) as zi: + zi.write(b"_" * file_size) + return fp + + # check seekable file information + seekable_data = make_zip(io.BytesIO()).getvalue() + ( + header, vers, os, flags, comp, csize, usize, fn_len, + ex_total_len, filename, ex_id, ex_len, ex_usize, ex_csize, + cd_sig + ) = struct.unpack("<4sBBHH8xIIHH8shhQQ{}x4s".format(file_size), seekable_data[:62 + file_size]) + + self.assertEqual(header, b"PK\x03\x04") # local file header + self.assertGreaterEqual(vers, zipfile.ZIP64_VERSION) # requires zip64 to extract + self.assertEqual(os, 0) # compatible with MS-DOS + self.assertEqual(flags, 0) # no flags set + self.assertEqual(comp, 0) # compression method = stored + self.assertEqual(csize, 0xFFFFFFFF) # sizes are in zip64 extra + self.assertEqual(usize, 0xFFFFFFFF) + self.assertEqual(fn_len, 8) # filename len + self.assertEqual(ex_total_len, 20) # size of extra records + self.assertEqual(ex_id, 1) # Zip64 extra record + self.assertEqual(ex_len, 16) # 16 bytes of data + self.assertEqual(ex_usize, file_size) # uncompressed size + self.assertEqual(ex_csize, file_size) # compressed size + self.assertEqual(cd_sig, b"PK\x01\x02") # ensure the central directory header is next + + # check unseekable file information + unseekable_data = make_zip(Unseekable(io.BytesIO())).fp.getvalue() + ( + header, vers, os, flags, comp, csize, usize, fn_len, + ex_total_len, filename, ex_id, ex_len, ex_usize, ex_csize, + dd_header, dd_usize, dd_csize, cd_sig + ) = struct.unpack("<4sBBHH8xIIHH8shhQQ{}x4s4xQQ4s".format(file_size), unseekable_data[:86 + file_size]) + + self.assertEqual(header, b"PK\x03\x04") # local file header + self.assertGreaterEqual(vers, zipfile.ZIP64_VERSION) # requires zip64 to extract + self.assertEqual(os, 0) # compatible with MS-DOS + self.assertEqual("{:b}".format(flags), "1000") # streaming flag set + self.assertEqual(comp, 0) # compression method = stored + self.assertEqual(csize, 0xFFFFFFFF) # sizes are in zip64 extra + self.assertEqual(usize, 0xFFFFFFFF) + self.assertEqual(fn_len, 8) # filename len + self.assertEqual(ex_total_len, 20) # size of extra records + self.assertEqual(ex_id, 1) # Zip64 extra record + self.assertEqual(ex_len, 16) # 16 bytes of data + self.assertEqual(ex_usize, 0) # uncompressed size - 0 to defer to data descriptor + self.assertEqual(ex_csize, 0) # compressed size - 0 to defer to data descriptor + self.assertEqual(dd_header, b"PK\07\x08") # data descriptor + self.assertEqual(dd_usize, file_size) # file size (8 bytes because zip64) + self.assertEqual(dd_csize, file_size) # compressed size (8 bytes because zip64) + self.assertEqual(cd_sig, b"PK\x01\x02") # ensure the central directory header is next + @requires_zlib() class DeflateTestZip64InSmallFiles(AbstractTestZip64InSmallFiles, @@ -1128,6 +1304,25 @@ def test_issue44439(self): self.assertEqual(data.write(q), LENGTH) self.assertEqual(zip.getinfo('data').file_size, LENGTH) + def test_zipwritefile_attrs(self): + fname = "somefile.txt" + with zipfile.ZipFile(TESTFN2, mode="w", compression=self.compression) as zipfp: + with zipfp.open(fname, 'w') as fid: + self.assertEqual(fid.name, fname) + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertEqual(fid.mode, 'wb') + self.assertIs(fid.readable(), False) + self.assertIs(fid.writable(), True) + self.assertIs(fid.seekable(), False) + self.assertIs(fid.closed, False) + self.assertIs(fid.closed, True) + self.assertEqual(fid.name, fname) + self.assertEqual(fid.mode, 'wb') + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertIs(fid.readable(), False) + self.assertIs(fid.writable(), True) + self.assertIs(fid.seekable(), False) + class StoredWriterTests(AbstractWriterTests, unittest.TestCase): compression = zipfile.ZIP_STORED @@ -1210,7 +1405,7 @@ def test_write_python_package(self): self.assertCompiledIn('email/__init__.py', names) self.assertCompiledIn('email/mime/text.py', names) - # TODO: RUSTPYTHON + # TODO: RUSTPYTHON - AttributeError: module 'os' has no attribute 'supports_effective_ids' @unittest.expectedFailure def test_write_filtered_python_package(self): import test @@ -1338,7 +1533,7 @@ def test_write_pathlike(self): fp.write("print(42)\n") with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp: - zipfp.writepy(pathlib.Path(TESTFN2) / "mod1.py") + zipfp.writepy(FakePath(os.path.join(TESTFN2, "mod1.py"))) names = zipfp.namelist() self.assertCompiledIn('mod1.py', names) finally: @@ -1396,7 +1591,7 @@ def test_extract_with_target(self): def test_extract_with_target_pathlike(self): with temp_dir() as extdir: - self._test_extract_with_target(pathlib.Path(extdir)) + self._test_extract_with_target(FakePath(extdir)) def test_extract_all(self): with temp_cwd(): @@ -1431,7 +1626,7 @@ def test_extract_all_with_target(self): def test_extract_all_with_target_pathlike(self): with temp_dir() as extdir: - self._test_extract_all_with_target(pathlib.Path(extdir)) + self._test_extract_all_with_target(FakePath(extdir)) def check_file(self, filename, content): self.assertTrue(os.path.isfile(filename)) @@ -1444,6 +1639,8 @@ def test_sanitize_windows_name(self): self.assertEqual(san(r',,?,C:,foo,bar/z', ','), r'_,C_,foo,bar/z') self.assertEqual(san(r'a\b,c<d>e|f"g?h*i', ','), r'a\b,c_d_e_f_g_h_i') self.assertEqual(san('../../foo../../ba..r', '/'), r'foo/ba..r') + self.assertEqual(san(' / /foo / /ba r', '/'), r'foo/ba r') + self.assertEqual(san(' . /. /foo ./ . /. ./ba .r', '/'), r'foo/ba .r') def test_extract_hackers_arcnames_common_cases(self): common_hacknames = [ @@ -1458,8 +1655,6 @@ def test_extract_hackers_arcnames_common_cases(self): ] self._test_extract_hackers_arcnames(common_hacknames) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(os.path.sep != '\\', 'Requires \\ as path separator.') def test_extract_hackers_arcnames_windows_only(self): """Test combination of path fixing and windows name sanitization.""" @@ -1474,10 +1669,10 @@ def test_extract_hackers_arcnames_windows_only(self): (r'C:\foo\bar', 'foo/bar'), (r'//conky/mountpoint/foo/bar', 'foo/bar'), (r'\\conky\mountpoint\foo\bar', 'foo/bar'), - (r'///conky/mountpoint/foo/bar', 'conky/mountpoint/foo/bar'), - (r'\\\conky\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'), - (r'//conky//mountpoint/foo/bar', 'conky/mountpoint/foo/bar'), - (r'\\conky\\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'), + (r'///conky/mountpoint/foo/bar', 'mountpoint/foo/bar'), + (r'\\\conky\mountpoint\foo\bar', 'mountpoint/foo/bar'), + (r'//conky//mountpoint/foo/bar', 'mountpoint/foo/bar'), + (r'\\conky\\mountpoint\foo\bar', 'mountpoint/foo/bar'), (r'//?/C:/foo/bar', 'foo/bar'), (r'\\?\C:\foo\bar', 'foo/bar'), (r'C:/../C:/foo/bar', 'C_/foo/bar'), @@ -1539,6 +1734,33 @@ def _test_extract_hackers_arcnames(self, hacknames): unlink(TESTFN2) +class OverwriteTests(archiver_tests.OverwriteTests, unittest.TestCase): + testdir = TESTFN + + @classmethod + def setUpClass(cls): + p = cls.ar_with_file = TESTFN + '-with-file.zip' + cls.addClassCleanup(unlink, p) + with zipfile.ZipFile(p, 'w') as zipfp: + zipfp.writestr('test', b'newcontent') + + p = cls.ar_with_dir = TESTFN + '-with-dir.zip' + cls.addClassCleanup(unlink, p) + with zipfile.ZipFile(p, 'w') as zipfp: + zipfp.mkdir('test') + + p = cls.ar_with_implicit_dir = TESTFN + '-with-implicit-dir.zip' + cls.addClassCleanup(unlink, p) + with zipfile.ZipFile(p, 'w') as zipfp: + zipfp.writestr('test/file', b'newcontent') + + def open(self, path): + return zipfile.ZipFile(path, 'r') + + def extractall(self, ar): + ar.extractall(self.testdir) + + class OtherTests(unittest.TestCase): def test_open_via_zip_info(self): # Create the ZIP archive @@ -1564,7 +1786,7 @@ def test_writestr_extended_local_header_issue1202(self): with zipfile.ZipFile(TESTFN2, 'w') as orig_zip: for data in 'abcdefghijklmnop': zinfo = zipfile.ZipInfo(data) - zinfo.flag_bits |= 0x08 # Include an extended local header. + zinfo.flag_bits |= zipfile._MASK_USE_DATA_DESCRIPTOR # Include an extended local header. orig_zip.writestr(zinfo, data) def test_close(self): @@ -1606,7 +1828,7 @@ def test_unsupported_version(self): @requires_zlib() def test_read_unicode_filenames(self): # bug #10801 - fname = findfile('zip_cp437_header.zip') + fname = findfile('zip_cp437_header.zip', subdir='archivetestdata') with zipfile.ZipFile(fname) as zipfp: for name in zipfp.namelist(): zipfp.open(name).close() @@ -1621,6 +1843,44 @@ def test_write_unicode_filenames(self): self.assertEqual(zf.filelist[0].filename, "foo.txt") self.assertEqual(zf.filelist[1].filename, "\xf6.txt") + def create_zipfile_with_extra_data(self, filename, extra_data_name): + with zipfile.ZipFile(TESTFN, mode='w') as zf: + filename_encoded = filename.encode("utf-8") + # create a ZipInfo object with Unicode path extra field + zip_info = zipfile.ZipInfo(filename) + + tag_for_unicode_path = b'\x75\x70' + version_of_unicode_path = b'\x01' + + import zlib + filename_crc = struct.pack('<L', zlib.crc32(filename_encoded)) + + extra_data = version_of_unicode_path + filename_crc + extra_data_name + tsize = len(extra_data).to_bytes(2, 'little') + + zip_info.extra = tag_for_unicode_path + tsize + extra_data + + # add the file to the ZIP archive + zf.writestr(zip_info, b'Hello World!') + + @requires_zlib() + def test_read_zipfile_containing_unicode_path_extra_field(self): + self.create_zipfile_with_extra_data("이름.txt", "이름.txt".encode("utf-8")) + with zipfile.ZipFile(TESTFN, "r") as zf: + self.assertEqual(zf.filelist[0].filename, "이름.txt") + + @requires_zlib() + def test_read_zipfile_warning(self): + self.create_zipfile_with_extra_data("이름.txt", b"") + with self.assertWarns(UserWarning): + zipfile.ZipFile(TESTFN, "r").close() + + @requires_zlib() + def test_read_zipfile_error(self): + self.create_zipfile_with_extra_data("이름.txt", b"\xff") + with self.assertRaises(zipfile.BadZipfile): + zipfile.ZipFile(TESTFN, "r").close() + def test_read_after_write_unicode_filenames(self): with zipfile.ZipFile(TESTFN2, 'w') as zipfp: zipfp.writestr('приклад', b'sample') @@ -1679,7 +1939,7 @@ def test_is_zip_erroneous_file(self): fp.write("this is not a legal zip file\n") self.assertFalse(zipfile.is_zipfile(TESTFN)) # - passing a path-like object - self.assertFalse(zipfile.is_zipfile(pathlib.Path(TESTFN))) + self.assertFalse(zipfile.is_zipfile(FakePath(TESTFN))) # - passing a file object with open(TESTFN, "rb") as fp: self.assertFalse(zipfile.is_zipfile(fp)) @@ -2036,6 +2296,7 @@ def test_seek_tell(self): fp.seek(bloc, os.SEEK_CUR) self.assertEqual(fp.tell(), bloc) self.assertEqual(fp.read(5), txt[bloc:bloc+5]) + self.assertEqual(fp.tell(), bloc + 5) fp.seek(0, os.SEEK_END) self.assertEqual(fp.tell(), len(txt)) fp.seek(0, os.SEEK_SET) @@ -2053,11 +2314,40 @@ def test_seek_tell(self): fp.seek(bloc, os.SEEK_CUR) self.assertEqual(fp.tell(), bloc) self.assertEqual(fp.read(5), txt[bloc:bloc+5]) + self.assertEqual(fp.tell(), bloc + 5) fp.seek(0, os.SEEK_END) self.assertEqual(fp.tell(), len(txt)) fp.seek(0, os.SEEK_SET) self.assertEqual(fp.tell(), 0) + def test_read_after_seek(self): + # Issue 102956: Make sure seek(x, os.SEEK_CUR) doesn't break read() + txt = b"Charge men!" + bloc = txt.find(b"men") + with zipfile.ZipFile(TESTFN, "w") as zipf: + zipf.writestr("foo.txt", txt) + with zipfile.ZipFile(TESTFN, mode="r") as zipf: + with zipf.open("foo.txt", "r") as fp: + fp.seek(bloc, os.SEEK_CUR) + self.assertEqual(fp.read(-1), b'men!') + with zipfile.ZipFile(TESTFN, mode="r") as zipf: + with zipf.open("foo.txt", "r") as fp: + fp.read(6) + fp.seek(1, os.SEEK_CUR) + self.assertEqual(fp.read(-1), b'men!') + + def test_uncompressed_interleaved_seek_read(self): + # gh-127847: Make sure the position in the archive is correct + # in the special case of seeking in a ZIP_STORED entry. + with zipfile.ZipFile(TESTFN, "w") as zipf: + zipf.writestr("a.txt", "123") + zipf.writestr("b.txt", "456") + with zipfile.ZipFile(TESTFN, "r") as zipf: + with zipf.open("a.txt", "r") as a, zipf.open("b.txt", "r") as b: + self.assertEqual(a.read(1), b"1") + self.assertEqual(b.seek(1), 1) + self.assertEqual(b.read(1), b"5") + @requires_bz2() def test_decompress_without_3rd_party_library(self): data = b'PK\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' @@ -2068,6 +2358,170 @@ def test_decompress_without_3rd_party_library(self): with zipfile.ZipFile(zip_file) as zf: self.assertRaises(RuntimeError, zf.extract, 'a.txt') + @requires_zlib() + def test_full_overlap_different_names(self): + data = ( + b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e' + b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00b\xed' + b'\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\d\x0b`P' + b'K\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2' + b'\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00aPK' + b'\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e' + b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00bPK\x05' + b'\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00\x00/\x00\x00' + b'\x00\x00\x00' + ) + with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: + self.assertEqual(zipf.namelist(), ['a', 'b']) + zi = zipf.getinfo('a') + self.assertEqual(zi.header_offset, 0) + self.assertEqual(zi.compress_size, 16) + self.assertEqual(zi.file_size, 1033) + zi = zipf.getinfo('b') + self.assertEqual(zi.header_offset, 0) + self.assertEqual(zi.compress_size, 16) + self.assertEqual(zi.file_size, 1033) + self.assertEqual(len(zipf.read('b')), 1033) + with self.assertRaisesRegex(zipfile.BadZipFile, 'File name.*differ'): + zipf.read('a') + + @requires_zlib() + def test_full_overlap_different_names2(self): + data = ( + b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e' + b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00a\xed' + b'\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\d\x0b`P' + b'K\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2' + b'\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00aPK' + b'\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e' + b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00bPK\x05' + b'\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00\x00/\x00\x00' + b'\x00\x00\x00' + ) + with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: + self.assertEqual(zipf.namelist(), ['a', 'b']) + zi = zipf.getinfo('a') + self.assertEqual(zi.header_offset, 0) + self.assertEqual(zi.compress_size, 16) + self.assertEqual(zi.file_size, 1033) + zi = zipf.getinfo('b') + self.assertEqual(zi.header_offset, 0) + self.assertEqual(zi.compress_size, 16) + self.assertEqual(zi.file_size, 1033) + with self.assertRaisesRegex(zipfile.BadZipFile, 'File name.*differ'): + zipf.read('b') + with self.assertWarnsRegex(UserWarning, 'Overlapped entries') as cm: + self.assertEqual(len(zipf.read('a')), 1033) + self.assertEqual(cm.filename, __file__) + + @requires_zlib() + def test_full_overlap_same_name(self): + data = ( + b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e' + b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00a\xed' + b'\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\d\x0b`P' + b'K\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2' + b'\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00aPK' + b'\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e' + b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00aPK\x05' + b'\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00\x00/\x00\x00' + b'\x00\x00\x00' + ) + with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: + self.assertEqual(zipf.namelist(), ['a', 'a']) + self.assertEqual(len(zipf.infolist()), 2) + zi = zipf.getinfo('a') + self.assertEqual(zi.header_offset, 0) + self.assertEqual(zi.compress_size, 16) + self.assertEqual(zi.file_size, 1033) + self.assertEqual(len(zipf.read('a')), 1033) + self.assertEqual(len(zipf.read(zi)), 1033) + self.assertEqual(len(zipf.read(zipf.infolist()[1])), 1033) + with self.assertWarnsRegex(UserWarning, 'Overlapped entries') as cm: + self.assertEqual(len(zipf.read(zipf.infolist()[0])), 1033) + self.assertEqual(cm.filename, __file__) + with self.assertWarnsRegex(UserWarning, 'Overlapped entries') as cm: + zipf.open(zipf.infolist()[0]).close() + self.assertEqual(cm.filename, __file__) + + @requires_zlib() + def test_quoted_overlap(self): + data = ( + b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05Y\xfc' + b'8\x044\x00\x00\x00(\x04\x00\x00\x01\x00\x00\x00a\x00' + b'\x1f\x00\xe0\xffPK\x03\x04\x14\x00\x00\x00\x08\x00\xa0l' + b'H\x05\xe2\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00' + b'\x00\x00b\xed\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\' + b'd\x0b`PK\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0' + b'lH\x05Y\xfc8\x044\x00\x00\x00(\x04\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00aPK\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0l' + b'H\x05\xe2\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x00\x00' + b'bPK\x05\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00' + b'\x00S\x00\x00\x00\x00\x00' + ) + with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: + self.assertEqual(zipf.namelist(), ['a', 'b']) + zi = zipf.getinfo('a') + self.assertEqual(zi.header_offset, 0) + self.assertEqual(zi.compress_size, 52) + self.assertEqual(zi.file_size, 1064) + zi = zipf.getinfo('b') + self.assertEqual(zi.header_offset, 36) + self.assertEqual(zi.compress_size, 16) + self.assertEqual(zi.file_size, 1033) + with self.assertRaisesRegex(zipfile.BadZipFile, 'Overlapped entries'): + zipf.read('a') + self.assertEqual(len(zipf.read('b')), 1033) + + @requires_zlib() + def test_overlap_with_central_dir(self): + data = ( + b'PK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00G_|Z' + b'\xe2\x1e8\xbb\x0b\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81\x00\x00\x00\x00aP' + b'K\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00/\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00' + ) + with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: + self.assertEqual(zipf.namelist(), ['a']) + self.assertEqual(len(zipf.infolist()), 1) + zi = zipf.getinfo('a') + self.assertEqual(zi.header_offset, 0) + self.assertEqual(zi.compress_size, 11) + self.assertEqual(zi.file_size, 1033) + with self.assertRaisesRegex(zipfile.BadZipFile, 'Bad magic number'): + zipf.read('a') + + @requires_zlib() + def test_overlap_with_archive_comment(self): + data = ( + b'PK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00G_|Z' + b'\xe2\x1e8\xbb\x0b\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81E\x00\x00\x00aP' + b'K\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00/\x00\x00\x00\x00' + b'\x00\x00\x00*\x00' + b'PK\x03\x04\x14\x00\x00\x00\x08\x00G_|Z\xe2\x1e' + b'8\xbb\x0b\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00aK' + b'L\x1c\x05\xa3`\x14\x8cx\x00\x00' + ) + with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: + self.assertEqual(zipf.namelist(), ['a']) + self.assertEqual(len(zipf.infolist()), 1) + zi = zipf.getinfo('a') + self.assertEqual(zi.header_offset, 69) + self.assertEqual(zi.compress_size, 11) + self.assertEqual(zi.file_size, 1033) + with self.assertRaisesRegex(zipfile.BadZipFile, 'Overlapped entries'): + zipf.read('a') + def tearDown(self): unlink(TESTFN) unlink(TESTFN2) @@ -2220,10 +2674,23 @@ def test_good_password(self): self.assertEqual(self.zip2.read("zero"), self.plain2) def test_unicode_password(self): - self.assertRaises(TypeError, self.zip.setpassword, "unicode") - self.assertRaises(TypeError, self.zip.read, "test.txt", "python") - self.assertRaises(TypeError, self.zip.open, "test.txt", pwd="python") - self.assertRaises(TypeError, self.zip.extract, "test.txt", pwd="python") + expected_msg = "pwd: expected bytes, got str" + + with self.assertRaisesRegex(TypeError, expected_msg): + self.zip.setpassword("unicode") + + with self.assertRaisesRegex(TypeError, expected_msg): + self.zip.read("test.txt", "python") + + with self.assertRaisesRegex(TypeError, expected_msg): + self.zip.open("test.txt", pwd="python") + + with self.assertRaisesRegex(TypeError, expected_msg): + self.zip.extract("test.txt", pwd="python") + + with self.assertRaisesRegex(TypeError, expected_msg): + self.zip.pwd = "python" + self.zip.open("test.txt") def test_seek_tell(self): self.zip.setpassword(b"python") @@ -2561,14 +3028,14 @@ def test_write_after_read(self): def test_many_opens(self): # Verify that read() and open() promptly close the file descriptor, # and don't rely on the garbage collector to free resources. + startcount = fd_count() self.make_test_archive(TESTFN2) with zipfile.ZipFile(TESTFN2, mode="r") as zipf: - for x in range(200): + for x in range(100): zipf.read('ones') with zipf.open('ones') as zopen1: pass - with open(os.devnull, "rb") as f: - self.assertLess(f.fileno(), 200) + self.assertEqual(startcount, fd_count()) def test_write_while_reading(self): with zipfile.ZipFile(TESTFN2, 'w', zipfile.ZIP_DEFLATED) as zipf: @@ -2592,7 +3059,7 @@ def setUp(self): os.mkdir(TESTFN2) def test_extract_dir(self): - with zipfile.ZipFile(findfile("zipdir.zip")) as zipf: + with zipfile.ZipFile(findfile("zipdir.zip", subdir="archivetestdata")) as zipf: zipf.extractall(TESTFN2) self.assertTrue(os.path.isdir(os.path.join(TESTFN2, "a"))) self.assertTrue(os.path.isdir(os.path.join(TESTFN2, "a", "b"))) @@ -2603,6 +3070,22 @@ def test_bug_6050(self): os.mkdir(os.path.join(TESTFN2, "a")) self.test_extract_dir() + def test_extract_dir_backslash(self): + zfname = findfile("zipdir_backslash.zip", subdir="archivetestdata") + with zipfile.ZipFile(zfname) as zipf: + zipf.extractall(TESTFN2) + if os.name == 'nt': + self.assertTrue(os.path.isdir(os.path.join(TESTFN2, "a"))) + self.assertTrue(os.path.isdir(os.path.join(TESTFN2, "a", "b"))) + self.assertTrue(os.path.isfile(os.path.join(TESTFN2, "a", "b", "c"))) + self.assertTrue(os.path.isdir(os.path.join(TESTFN2, "d"))) + self.assertTrue(os.path.isdir(os.path.join(TESTFN2, "d", "e"))) + else: + self.assertTrue(os.path.isfile(os.path.join(TESTFN2, "a\\b\\c"))) + self.assertTrue(os.path.isfile(os.path.join(TESTFN2, "d\\e\\"))) + self.assertFalse(os.path.exists(os.path.join(TESTFN2, "a"))) + self.assertFalse(os.path.exists(os.path.join(TESTFN2, "d"))) + def test_write_dir(self): dirpath = os.path.join(TESTFN2, "x") os.mkdir(dirpath) @@ -2646,6 +3129,70 @@ def test_writestr_dir(self): self.assertTrue(os.path.isdir(os.path.join(target, "x"))) self.assertEqual(os.listdir(target), ["x"]) + def test_mkdir(self): + with zipfile.ZipFile(TESTFN, "w") as zf: + zf.mkdir("directory") + zinfo = zf.filelist[0] + self.assertEqual(zinfo.filename, "directory/") + self.assertEqual(zinfo.external_attr, (0o40777 << 16) | 0x10) + + zf.mkdir("directory2/") + zinfo = zf.filelist[1] + self.assertEqual(zinfo.filename, "directory2/") + self.assertEqual(zinfo.external_attr, (0o40777 << 16) | 0x10) + + zf.mkdir("directory3", mode=0o777) + zinfo = zf.filelist[2] + self.assertEqual(zinfo.filename, "directory3/") + self.assertEqual(zinfo.external_attr, (0o40777 << 16) | 0x10) + + old_zinfo = zipfile.ZipInfo("directory4/") + old_zinfo.external_attr = (0o40777 << 16) | 0x10 + old_zinfo.CRC = 0 + old_zinfo.file_size = 0 + old_zinfo.compress_size = 0 + zf.mkdir(old_zinfo) + new_zinfo = zf.filelist[3] + self.assertEqual(old_zinfo.filename, "directory4/") + self.assertEqual(old_zinfo.external_attr, new_zinfo.external_attr) + + target = os.path.join(TESTFN2, "target") + os.mkdir(target) + zf.extractall(target) + self.assertEqual(set(os.listdir(target)), {"directory", "directory2", "directory3", "directory4"}) + + def test_create_directory_with_write(self): + with zipfile.ZipFile(TESTFN, "w") as zf: + zf.writestr(zipfile.ZipInfo('directory/'), '') + + zinfo = zf.filelist[0] + self.assertEqual(zinfo.filename, "directory/") + + directory = os.path.join(TESTFN2, "directory2") + os.mkdir(directory) + mode = os.stat(directory).st_mode & 0xFFFF + zf.write(directory, arcname="directory2/") + zinfo = zf.filelist[1] + self.assertEqual(zinfo.filename, "directory2/") + self.assertEqual(zinfo.external_attr, (mode << 16) | 0x10) + + target = os.path.join(TESTFN2, "target") + os.mkdir(target) + zf.extractall(target) + + self.assertEqual(set(os.listdir(target)), {"directory", "directory2"}) + + def test_root_folder_in_zipfile(self): + """ + gh-112795: Some tools or self constructed codes will add '/' folder to + the zip file, this is a strange behavior, but we should support it. + """ + in_memory_file = io.BytesIO() + zf = zipfile.ZipFile(in_memory_file, "w") + zf.mkdir('/') + zf.writestr('./a.txt', 'aaa') + zf.extractall(TESTFN2) + def tearDown(self): rmtree(TESTFN2) if os.path.exists(TESTFN): @@ -2655,13 +3202,13 @@ def tearDown(self): class ZipInfoTests(unittest.TestCase): def test_from_file(self): zi = zipfile.ZipInfo.from_file(__file__) - self.assertEqual(posixpath.basename(zi.filename), 'test_zipfile.py') + self.assertEqual(posixpath.basename(zi.filename), 'test_core.py') self.assertFalse(zi.is_dir()) self.assertEqual(zi.file_size, os.path.getsize(__file__)) def test_from_file_pathlike(self): - zi = zipfile.ZipInfo.from_file(pathlib.Path(__file__)) - self.assertEqual(posixpath.basename(zi.filename), 'test_zipfile.py') + zi = zipfile.ZipInfo.from_file(FakePath(__file__)) + self.assertEqual(posixpath.basename(zi.filename), 'test_core.py') self.assertFalse(zi.is_dir()) self.assertEqual(zi.file_size, os.path.getsize(__file__)) @@ -2686,6 +3233,17 @@ def test_from_dir(self): self.assertEqual(zi.compress_type, zipfile.ZIP_STORED) self.assertEqual(zi.file_size, 0) + def test_compresslevel_property(self): + zinfo = zipfile.ZipInfo("xxx") + self.assertFalse(zinfo._compresslevel) + self.assertFalse(zinfo.compress_level) + zinfo._compresslevel = 99 # test the legacy @property.setter + self.assertEqual(zinfo.compress_level, 99) + self.assertEqual(zinfo._compresslevel, 99) + zinfo.compress_level = 8 + self.assertEqual(zinfo.compress_level, 8) + self.assertEqual(zinfo._compresslevel, 8) + class CommandLineTest(unittest.TestCase): @@ -2708,7 +3266,7 @@ def test_bad_use(self): self.assertNotEqual(err.strip(), b'') def test_test_command(self): - zip_name = findfile('zipdir.zip') + zip_name = findfile('zipdir.zip', subdir='archivetestdata') for opt in '-t', '--test': out = self.zipfilecmd(opt, zip_name) self.assertEqual(out.rstrip(), b'Done testing') @@ -2717,7 +3275,7 @@ def test_test_command(self): self.assertEqual(out, b'') def test_list_command(self): - zip_name = findfile('zipdir.zip') + zip_name = findfile('zipdir.zip', subdir='archivetestdata') t = io.StringIO() with zipfile.ZipFile(zip_name, 'r') as tf: tf.printdir(t) @@ -2750,7 +3308,7 @@ def test_create_command(self): unlink(TESTFN2) def test_extract_command(self): - zip_name = findfile('zipdir.zip') + zip_name = findfile('zipdir.zip', subdir='archivetestdata') for opt in '-e', '--extract': with temp_dir() as extdir: out = self.zipfilecmd(opt, zip_name, extdir) @@ -2771,8 +3329,8 @@ class TestExecutablePrependedZip(unittest.TestCase): """Test our ability to open zip files with an executable prepended.""" def setUp(self): - self.exe_zip = findfile('exe_with_zip', subdir='ziptestdata') - self.exe_zip64 = findfile('exe_with_z64', subdir='ziptestdata') + self.exe_zip = findfile('exe_with_zip', subdir='archivetestdata') + self.exe_zip64 = findfile('exe_with_z64', subdir='archivetestdata') def _test_zip_works(self, name): # bpo28494 sanity check: ensure is_zipfile works on these. @@ -2795,6 +3353,7 @@ def test_read_zip64_with_exe_prepended(self): @unittest.skipUnless(sys.executable, 'sys.executable required.') @unittest.skipUnless(os.access('/bin/bash', os.X_OK), 'Test relies on #!/bin/bash working.') + @requires_subprocess() def test_execute_zip2(self): output = subprocess.check_output([self.exe_zip, sys.executable]) self.assertIn(b'number in executable: 5', output) @@ -2804,365 +3363,301 @@ def test_execute_zip2(self): @unittest.skipUnless(sys.executable, 'sys.executable required.') @unittest.skipUnless(os.access('/bin/bash', os.X_OK), 'Test relies on #!/bin/bash working.') + @requires_subprocess() def test_execute_zip64(self): output = subprocess.check_output([self.exe_zip64, sys.executable]) self.assertIn(b'number in executable: 5', output) -# Poor man's technique to consume a (smallish) iterable. -consume = tuple - - -# from jaraco.itertools 5.0 -class jaraco: - class itertools: - class Counter: - def __init__(self, i): - self.count = 0 - self._orig_iter = iter(i) - - def __iter__(self): - return self - - def __next__(self): - result = next(self._orig_iter) - self.count += 1 - return result - - -def add_dirs(zf): - """ - Given a writable zip file zf, inject directory entries for - any directories implied by the presence of children. - """ - for name in zipfile.CompleteDirs._implied_dirs(zf.namelist()): - zf.writestr(name, b"") - return zf - - -def build_alpharep_fixture(): - """ - Create a zip file with this structure: - - . - ├── a.txt - ├── b - │ ├── c.txt - │ ├── d - │ │ └── e.txt - │ └── f.txt - └── g - └── h - └── i.txt - - This fixture has the following key characteristics: - - - a file at the root (a) - - a file two levels deep (b/d/e) - - multiple files in a directory (b/c, b/f) - - a directory containing only a directory (g/h) - - "alpha" because it uses alphabet - "rep" because it's a representative example - """ - data = io.BytesIO() - zf = zipfile.ZipFile(data, "w") - zf.writestr("a.txt", b"content of a") - zf.writestr("b/c.txt", b"content of c") - zf.writestr("b/d/e.txt", b"content of e") - zf.writestr("b/f.txt", b"content of f") - zf.writestr("g/h/i.txt", b"content of i") - zf.filename = "alpharep.zip" - return zf - - -def pass_alpharep(meth): - """ - Given a method, wrap it in a for loop that invokes method - with each subtest. - """ - - @functools.wraps(meth) - def wrapper(self): - for alpharep in self.zipfile_alpharep(): - meth(self, alpharep=alpharep) - - return wrapper - - -class TestPath(unittest.TestCase): - def setUp(self): - self.fixtures = contextlib.ExitStack() - self.addCleanup(self.fixtures.close) - - def zipfile_alpharep(self): - with self.subTest(): - yield build_alpharep_fixture() - with self.subTest(): - yield add_dirs(build_alpharep_fixture()) - - def zipfile_ondisk(self, alpharep): - tmpdir = pathlib.Path(self.fixtures.enter_context(temp_dir())) - buffer = alpharep.fp - alpharep.close() - path = tmpdir / alpharep.filename - with path.open("wb") as strm: - strm.write(buffer.getvalue()) - return path - - @pass_alpharep - def test_iterdir_and_types(self, alpharep): - root = zipfile.Path(alpharep) - assert root.is_dir() - a, b, g = root.iterdir() - assert a.is_file() - assert b.is_dir() - assert g.is_dir() - c, f, d = b.iterdir() - assert c.is_file() and f.is_file() - (e,) = d.iterdir() - assert e.is_file() - (h,) = g.iterdir() - (i,) = h.iterdir() - assert i.is_file() - - @pass_alpharep - def test_is_file_missing(self, alpharep): - root = zipfile.Path(alpharep) - assert not root.joinpath('missing.txt').is_file() - - @pass_alpharep - def test_iterdir_on_file(self, alpharep): - root = zipfile.Path(alpharep) - a, b, g = root.iterdir() - with self.assertRaises(ValueError): - a.iterdir() - - @pass_alpharep - def test_subdir_is_dir(self, alpharep): - root = zipfile.Path(alpharep) - assert (root / 'b').is_dir() - assert (root / 'b/').is_dir() - assert (root / 'g').is_dir() - assert (root / 'g/').is_dir() - - @pass_alpharep - def test_open(self, alpharep): - root = zipfile.Path(alpharep) - a, b, g = root.iterdir() - with a.open(encoding="utf-8") as strm: - data = strm.read() - assert data == "content of a" +# TODO: RUSTPYTHON +@unittest.skip("TODO: RUSTPYTHON shift_jis encoding unsupported") +class EncodedMetadataTests(unittest.TestCase): + file_names = ['\u4e00', '\u4e8c', '\u4e09'] # Han 'one', 'two', 'three' + file_content = [ + "This is pure ASCII.\n".encode('ascii'), + # This is modern Japanese. (UTF-8) + "\u3053\u308c\u306f\u73fe\u4ee3\u7684\u65e5\u672c\u8a9e\u3067\u3059\u3002\n".encode('utf-8'), + # This is obsolete Japanese. (Shift JIS) + # "\u3053\u308c\u306f\u53e4\u3044\u65e5\u672c\u8a9e\u3067\u3059\u3002\n".encode('shift_jis'), # TODO: RUSTPYTHON + ] - def test_open_write(self): - """ - If the zipfile is open for write, it should be possible to - write bytes or text to it. - """ - zf = zipfile.Path(zipfile.ZipFile(io.BytesIO(), mode='w')) - with zf.joinpath('file.bin').open('wb') as strm: - strm.write(b'binary contents') - with zf.joinpath('file.txt').open('w', encoding="utf-8") as strm: - strm.write('text file') - - def test_open_extant_directory(self): - """ - Attempting to open a directory raises IsADirectoryError. - """ - zf = zipfile.Path(add_dirs(build_alpharep_fixture())) - with self.assertRaises(IsADirectoryError): - zf.joinpath('b').open() - - @pass_alpharep - def test_open_binary_invalid_args(self, alpharep): - root = zipfile.Path(alpharep) - with self.assertRaises(ValueError): - root.joinpath('a.txt').open('rb', encoding='utf-8') - with self.assertRaises(ValueError): - root.joinpath('a.txt').open('rb', 'utf-8') - - def test_open_missing_directory(self): - """ - Attempting to open a missing directory raises FileNotFoundError. - """ - zf = zipfile.Path(add_dirs(build_alpharep_fixture())) - with self.assertRaises(FileNotFoundError): - zf.joinpath('z').open() - - @pass_alpharep - def test_read(self, alpharep): - root = zipfile.Path(alpharep) - a, b, g = root.iterdir() - assert a.read_text(encoding="utf-8") == "content of a" - assert a.read_bytes() == b"content of a" - - @pass_alpharep - def test_joinpath(self, alpharep): - root = zipfile.Path(alpharep) - a = root.joinpath("a.txt") - assert a.is_file() - e = root.joinpath("b").joinpath("d").joinpath("e.txt") - assert e.read_text(encoding="utf-8") == "content of e" - - @pass_alpharep - def test_joinpath_multiple(self, alpharep): - root = zipfile.Path(alpharep) - e = root.joinpath("b", "d", "e.txt") - assert e.read_text(encoding="utf-8") == "content of e" - - @pass_alpharep - def test_traverse_truediv(self, alpharep): - root = zipfile.Path(alpharep) - a = root / "a.txt" - assert a.is_file() - e = root / "b" / "d" / "e.txt" - assert e.read_text(encoding="utf-8") == "content of e" - - @pass_alpharep - def test_traverse_simplediv(self, alpharep): - """ - Disable the __future__.division when testing traversal. - """ - code = compile( - source="zipfile.Path(alpharep) / 'a'", - filename="(test)", - mode="eval", - dont_inherit=True, - ) - eval(code) - - @pass_alpharep - def test_pathlike_construction(self, alpharep): - """ - zipfile.Path should be constructable from a path-like object - """ - zipfile_ondisk = self.zipfile_ondisk(alpharep) - pathlike = pathlib.Path(str(zipfile_ondisk)) - zipfile.Path(pathlike) - - @pass_alpharep - def test_traverse_pathlike(self, alpharep): - root = zipfile.Path(alpharep) - root / pathlib.Path("a") - - @pass_alpharep - def test_parent(self, alpharep): - root = zipfile.Path(alpharep) - assert (root / 'a').parent.at == '' - assert (root / 'a' / 'b').parent.at == 'a/' - - @pass_alpharep - def test_dir_parent(self, alpharep): - root = zipfile.Path(alpharep) - assert (root / 'b').parent.at == '' - assert (root / 'b/').parent.at == '' - - @pass_alpharep - def test_missing_dir_parent(self, alpharep): - root = zipfile.Path(alpharep) - assert (root / 'missing dir/').parent.at == '' - - @pass_alpharep - def test_mutability(self, alpharep): - """ - If the underlying zipfile is changed, the Path object should - reflect that change. - """ - root = zipfile.Path(alpharep) - a, b, g = root.iterdir() - alpharep.writestr('foo.txt', 'foo') - alpharep.writestr('bar/baz.txt', 'baz') - assert any(child.name == 'foo.txt' for child in root.iterdir()) - assert (root / 'foo.txt').read_text(encoding="utf-8") == 'foo' - (baz,) = (root / 'bar').iterdir() - assert baz.read_text(encoding="utf-8") == 'baz' - - HUGE_ZIPFILE_NUM_ENTRIES = 2 ** 13 - - def huge_zipfile(self): - """Create a read-only zipfile with a huge number of entries entries.""" - strm = io.BytesIO() - zf = zipfile.ZipFile(strm, "w") - for entry in map(str, range(self.HUGE_ZIPFILE_NUM_ENTRIES)): - zf.writestr(entry, entry) - zf.mode = 'r' - return zf - - def test_joinpath_constant_time(self): - """ - Ensure joinpath on items in zipfile is linear time. - """ - root = zipfile.Path(self.huge_zipfile()) - entries = jaraco.itertools.Counter(root.iterdir()) - for entry in entries: - entry.joinpath('suffix') - # Check the file iterated all items - assert entries.count == self.HUGE_ZIPFILE_NUM_ENTRIES - - # @func_timeout.func_set_timeout(3) - def test_implied_dirs_performance(self): - data = ['/'.join(string.ascii_lowercase + str(n)) for n in range(10000)] - zipfile.CompleteDirs._implied_dirs(data) - - @pass_alpharep - def test_read_does_not_close(self, alpharep): - alpharep = self.zipfile_ondisk(alpharep) - with zipfile.ZipFile(alpharep) as file: - for rep in range(2): - zipfile.Path(file, 'a.txt').read_text(encoding="utf-8") - - @pass_alpharep - def test_subclass(self, alpharep): - class Subclass(zipfile.Path): - pass - - root = Subclass(alpharep) - assert isinstance(root / 'b', Subclass) - - @pass_alpharep - def test_filename(self, alpharep): - root = zipfile.Path(alpharep) - assert root.filename == pathlib.Path('alpharep.zip') + def setUp(self): + self.addCleanup(unlink, TESTFN) + # Create .zip of 3 members with Han names encoded in Shift JIS. + # Each name is 1 Han character encoding to 2 bytes in Shift JIS. + # The ASCII names are arbitrary as long as they are length 2 and + # not otherwise contained in the zip file. + # Data elements are encoded bytes (ascii, utf-8, shift_jis). + placeholders = ["n1", "n2"] + self.file_names[2:] + with zipfile.ZipFile(TESTFN, mode="w") as tf: + for temp, content in zip(placeholders, self.file_content): + tf.writestr(temp, content, zipfile.ZIP_STORED) + # Hack in the Shift JIS names with flag bit 11 (UTF-8) unset. + with open(TESTFN, "rb") as tf: + data = tf.read() + for name, temp in zip(self.file_names, placeholders[:2]): + data = data.replace(temp.encode('ascii'), + name.encode('shift_jis')) + with open(TESTFN, "wb") as tf: + tf.write(data) + + def _test_read(self, zipfp, expected_names, expected_content): + # Check the namelist + names = zipfp.namelist() + self.assertEqual(sorted(names), sorted(expected_names)) + + # Check infolist + infos = zipfp.infolist() + names = [zi.filename for zi in infos] + self.assertEqual(sorted(names), sorted(expected_names)) + + # check getinfo + for name, content in zip(expected_names, expected_content): + info = zipfp.getinfo(name) + self.assertEqual(info.filename, name) + self.assertEqual(info.file_size, len(content)) + self.assertEqual(zipfp.read(name), content) + + def test_read_with_metadata_encoding(self): + # Read the ZIP archive with correct metadata_encoding + with zipfile.ZipFile(TESTFN, "r", metadata_encoding='shift_jis') as zipfp: + self._test_read(zipfp, self.file_names, self.file_content) + + def test_read_without_metadata_encoding(self): + # Read the ZIP archive without metadata_encoding + expected_names = [name.encode('shift_jis').decode('cp437') + for name in self.file_names[:2]] + self.file_names[2:] + with zipfile.ZipFile(TESTFN, "r") as zipfp: + self._test_read(zipfp, expected_names, self.file_content) + + def test_read_with_incorrect_metadata_encoding(self): + # Read the ZIP archive with incorrect metadata_encoding + expected_names = [name.encode('shift_jis').decode('koi8-u') + for name in self.file_names[:2]] + self.file_names[2:] + with zipfile.ZipFile(TESTFN, "r", metadata_encoding='koi8-u') as zipfp: + self._test_read(zipfp, expected_names, self.file_content) + + def test_read_with_unsuitable_metadata_encoding(self): + # Read the ZIP archive with metadata_encoding unsuitable for + # decoding metadata + with self.assertRaises(UnicodeDecodeError): + zipfile.ZipFile(TESTFN, "r", metadata_encoding='ascii') + with self.assertRaises(UnicodeDecodeError): + zipfile.ZipFile(TESTFN, "r", metadata_encoding='utf-8') + + def test_read_after_append(self): + newname = '\u56db' # Han 'four' + expected_names = [name.encode('shift_jis').decode('cp437') + for name in self.file_names[:2]] + self.file_names[2:] + expected_names.append(newname) + expected_content = (*self.file_content, b"newcontent") + + with zipfile.ZipFile(TESTFN, "a") as zipfp: + zipfp.writestr(newname, "newcontent") + self.assertEqual(sorted(zipfp.namelist()), sorted(expected_names)) + + with zipfile.ZipFile(TESTFN, "r") as zipfp: + self._test_read(zipfp, expected_names, expected_content) + + with zipfile.ZipFile(TESTFN, "r", metadata_encoding='shift_jis') as zipfp: + self.assertEqual(sorted(zipfp.namelist()), sorted(expected_names)) + for i, (name, content) in enumerate(zip(expected_names, expected_content)): + info = zipfp.getinfo(name) + self.assertEqual(info.filename, name) + self.assertEqual(info.file_size, len(content)) + if i < 2: + with self.assertRaises(zipfile.BadZipFile): + zipfp.read(name) + else: + self.assertEqual(zipfp.read(name), content) + + def test_write_with_metadata_encoding(self): + ZF = zipfile.ZipFile + for mode in ("w", "x", "a"): + with self.assertRaisesRegex(ValueError, + "^metadata_encoding is only"): + ZF("nonesuch.zip", mode, metadata_encoding="shift_jis") + + def test_cli_with_metadata_encoding(self): + errmsg = "Non-conforming encodings not supported with -c." + args = ["--metadata-encoding=shift_jis", "-c", "nonesuch", "nonesuch"] + with captured_stdout() as stdout: + with captured_stderr() as stderr: + self.assertRaises(SystemExit, zipfile.main, args) + self.assertEqual(stdout.getvalue(), "") + self.assertIn(errmsg, stderr.getvalue()) + + with captured_stdout() as stdout: + zipfile.main(["--metadata-encoding=shift_jis", "-t", TESTFN]) + listing = stdout.getvalue() + + with captured_stdout() as stdout: + zipfile.main(["--metadata-encoding=shift_jis", "-l", TESTFN]) + listing = stdout.getvalue() + for name in self.file_names: + self.assertIn(name, listing) + + def test_cli_with_metadata_encoding_extract(self): + os.mkdir(TESTFN2) + self.addCleanup(rmtree, TESTFN2) + # Depending on locale, extracted file names can be not encodable + # with the filesystem encoding. + for fn in self.file_names: + try: + os.stat(os.path.join(TESTFN2, fn)) + except OSError: + pass + except UnicodeEncodeError: + self.skipTest(f'cannot encode file name {fn!a}') + + zipfile.main(["--metadata-encoding=shift_jis", "-e", TESTFN, TESTFN2]) + listing = os.listdir(TESTFN2) + for name in self.file_names: + self.assertIn(name, listing) + + +class StripExtraTests(unittest.TestCase): + # Note: all of the "z" characters are technically invalid, but up + # to 3 bytes at the end of the extra will be passed through as they + # are too short to encode a valid extra. + + ZIP64_EXTRA = 1 + + def test_no_data(self): + s = struct.Struct("<HH") + a = s.pack(self.ZIP64_EXTRA, 0) + b = s.pack(2, 0) + c = s.pack(3, 0) + + self.assertEqual(b'', zipfile._Extra.strip(a, (self.ZIP64_EXTRA,))) + self.assertEqual(b, zipfile._Extra.strip(b, (self.ZIP64_EXTRA,))) + self.assertEqual( + b+b"z", zipfile._Extra.strip(b+b"z", (self.ZIP64_EXTRA,))) + + self.assertEqual(b+c, zipfile._Extra.strip(a+b+c, (self.ZIP64_EXTRA,))) + self.assertEqual(b+c, zipfile._Extra.strip(b+a+c, (self.ZIP64_EXTRA,))) + self.assertEqual(b+c, zipfile._Extra.strip(b+c+a, (self.ZIP64_EXTRA,))) + + def test_with_data(self): + s = struct.Struct("<HH") + a = s.pack(self.ZIP64_EXTRA, 1) + b"a" + b = s.pack(2, 2) + b"bb" + c = s.pack(3, 3) + b"ccc" + + self.assertEqual(b"", zipfile._Extra.strip(a, (self.ZIP64_EXTRA,))) + self.assertEqual(b, zipfile._Extra.strip(b, (self.ZIP64_EXTRA,))) + self.assertEqual( + b+b"z", zipfile._Extra.strip(b+b"z", (self.ZIP64_EXTRA,))) + + self.assertEqual(b+c, zipfile._Extra.strip(a+b+c, (self.ZIP64_EXTRA,))) + self.assertEqual(b+c, zipfile._Extra.strip(b+a+c, (self.ZIP64_EXTRA,))) + self.assertEqual(b+c, zipfile._Extra.strip(b+c+a, (self.ZIP64_EXTRA,))) + + def test_multiples(self): + s = struct.Struct("<HH") + a = s.pack(self.ZIP64_EXTRA, 1) + b"a" + b = s.pack(2, 2) + b"bb" + + self.assertEqual(b"", zipfile._Extra.strip(a+a, (self.ZIP64_EXTRA,))) + self.assertEqual(b"", zipfile._Extra.strip(a+a+a, (self.ZIP64_EXTRA,))) + self.assertEqual( + b"z", zipfile._Extra.strip(a+a+b"z", (self.ZIP64_EXTRA,))) + self.assertEqual( + b+b"z", zipfile._Extra.strip(a+a+b+b"z", (self.ZIP64_EXTRA,))) + + self.assertEqual(b, zipfile._Extra.strip(a+a+b, (self.ZIP64_EXTRA,))) + self.assertEqual(b, zipfile._Extra.strip(a+b+a, (self.ZIP64_EXTRA,))) + self.assertEqual(b, zipfile._Extra.strip(b+a+a, (self.ZIP64_EXTRA,))) + + def test_too_short(self): + self.assertEqual(b"", zipfile._Extra.strip(b"", (self.ZIP64_EXTRA,))) + self.assertEqual(b"z", zipfile._Extra.strip(b"z", (self.ZIP64_EXTRA,))) + self.assertEqual( + b"zz", zipfile._Extra.strip(b"zz", (self.ZIP64_EXTRA,))) + self.assertEqual( + b"zzz", zipfile._Extra.strip(b"zzz", (self.ZIP64_EXTRA,))) + + +class StatIO(_pyio.BytesIO): + """Buffer which remembers the number of bytes that were read.""" + + def __init__(self): + super().__init__() + self.bytes_read = 0 + + def read(self, size=-1): + bs = super().read(size) + self.bytes_read += len(bs) + return bs + + +class StoredZipExtFileRandomReadTest(unittest.TestCase): + """Tests whether an uncompressed, unencrypted zip entry can be randomly + seek and read without reading redundant bytes.""" + def test_stored_seek_and_read(self): + + sio = StatIO() + # 20000 bytes + txt = b'0123456789' * 2000 + + # The seek length must be greater than ZipExtFile.MIN_READ_SIZE + # as `ZipExtFile._read2()` reads in blocks of this size and we + # need to seek out of the buffered data + read_buffer_size = zipfile.ZipExtFile.MIN_READ_SIZE + self.assertGreaterEqual(10002, read_buffer_size) # for forward seek test + self.assertGreaterEqual(5003, read_buffer_size) # for backward seek test + # The read length must be less than MIN_READ_SIZE, since we assume that + # only 1 block is read in the test. + read_length = 100 + self.assertGreaterEqual(read_buffer_size, read_length) # for read() calls + + with zipfile.ZipFile(sio, "w", compression=zipfile.ZIP_STORED) as zipf: + zipf.writestr("foo.txt", txt) - @pass_alpharep - def test_root_name(self, alpharep): - """ - The name of the root should be the name of the zipfile - """ - root = zipfile.Path(alpharep) - assert root.name == 'alpharep.zip' == root.filename.name - - @pass_alpharep - def test_root_parent(self, alpharep): - root = zipfile.Path(alpharep) - assert root.parent == pathlib.Path('.') - root.root.filename = 'foo/bar.zip' - assert root.parent == pathlib.Path('foo') - - @pass_alpharep - def test_root_unnamed(self, alpharep): - """ - It is an error to attempt to get the name - or parent of an unnamed zipfile. - """ - alpharep.filename = None - root = zipfile.Path(alpharep) - with self.assertRaises(TypeError): - root.name - with self.assertRaises(TypeError): - root.parent - - # .name and .parent should still work on subs - sub = root / "b" - assert sub.name == "b" - assert sub.parent - - @pass_alpharep - def test_inheritance(self, alpharep): - cls = type('PathChild', (zipfile.Path,), {}) - for alpharep in self.zipfile_alpharep(): - file = cls(alpharep).joinpath('some dir').parent - assert isinstance(file, cls) + # check random seek and read on a file + with zipfile.ZipFile(sio, "r") as zipf: + with zipf.open("foo.txt", "r") as fp: + # Test this optimized read hasn't rewound and read from the + # start of the file (as in the case of the unoptimized path) + + # forward seek + old_count = sio.bytes_read + forward_seek_len = 10002 + current_pos = 0 + fp.seek(forward_seek_len, os.SEEK_CUR) + current_pos += forward_seek_len + self.assertEqual(fp.tell(), current_pos) + self.assertEqual(fp._left, fp._compress_left) + arr = fp.read(read_length) + current_pos += read_length + self.assertEqual(fp.tell(), current_pos) + self.assertEqual(arr, txt[current_pos - read_length:current_pos]) + self.assertEqual(fp._left, fp._compress_left) + read_count = sio.bytes_read - old_count + self.assertLessEqual(read_count, read_buffer_size) + + # backward seek + old_count = sio.bytes_read + backward_seek_len = 5003 + fp.seek(-backward_seek_len, os.SEEK_CUR) + current_pos -= backward_seek_len + self.assertEqual(fp.tell(), current_pos) + self.assertEqual(fp._left, fp._compress_left) + arr = fp.read(read_length) + current_pos += read_length + self.assertEqual(fp.tell(), current_pos) + self.assertEqual(arr, txt[current_pos - read_length:current_pos]) + self.assertEqual(fp._left, fp._compress_left) + read_count = sio.bytes_read - old_count + self.assertLessEqual(read_count, read_buffer_size) + + # eof flags test + fp.seek(0, os.SEEK_END) + fp.seek(12345, os.SEEK_SET) + current_pos = 12345 + arr = fp.read(read_length) + current_pos += read_length + self.assertEqual(arr, txt[current_pos - read_length:current_pos]) if __name__ == "__main__": diff --git a/Lib/zipfile.py b/Lib/zipfile/__init__.py similarity index 85% rename from Lib/zipfile.py rename to Lib/zipfile/__init__.py index ef1eb47f9f1..05f387a950b 100644 --- a/Lib/zipfile.py +++ b/Lib/zipfile/__init__.py @@ -6,26 +6,13 @@ import binascii import importlib.util import io -import itertools -try: - import os -except ImportError: - import _dummy_os as os -import posixpath -try: - import shutil -except ImportError: - pass +import os +import shutil import stat import struct import sys -try: - import threading -except ImportError: - import dummy_threading as threading +import threading import time -import contextlib -import pathlib try: import zlib # We may need its compression method @@ -132,6 +119,32 @@ class LargeZipFile(Exception): _CD_EXTERNAL_FILE_ATTRIBUTES = 17 _CD_LOCAL_HEADER_OFFSET = 18 +# General purpose bit flags +# Zip Appnote: 4.4.4 general purpose bit flag: (2 bytes) +_MASK_ENCRYPTED = 1 << 0 +# Bits 1 and 2 have different meanings depending on the compression used. +_MASK_COMPRESS_OPTION_1 = 1 << 1 +# _MASK_COMPRESS_OPTION_2 = 1 << 2 +# _MASK_USE_DATA_DESCRIPTOR: If set, crc-32, compressed size and uncompressed +# size are zero in the local header and the real values are written in the data +# descriptor immediately following the compressed data. +_MASK_USE_DATA_DESCRIPTOR = 1 << 3 +# Bit 4: Reserved for use with compression method 8, for enhanced deflating. +# _MASK_RESERVED_BIT_4 = 1 << 4 +_MASK_COMPRESSED_PATCH = 1 << 5 +_MASK_STRONG_ENCRYPTION = 1 << 6 +# _MASK_UNUSED_BIT_7 = 1 << 7 +# _MASK_UNUSED_BIT_8 = 1 << 8 +# _MASK_UNUSED_BIT_9 = 1 << 9 +# _MASK_UNUSED_BIT_10 = 1 << 10 +_MASK_UTF_FILENAME = 1 << 11 +# Bit 12: Reserved by PKWARE for enhanced compression. +# _MASK_RESERVED_BIT_12 = 1 << 12 +# _MASK_ENCRYPTED_CENTRAL_DIR = 1 << 13 +# Bit 14, 15: Reserved by PKWARE +# _MASK_RESERVED_BIT_14 = 1 << 14 +# _MASK_RESERVED_BIT_15 = 1 << 15 + # The "local file header" structure, magic number, size, and indices # (section V.A in the format document) structFileHeader = "<4s2B4HL2L2H" @@ -175,26 +188,42 @@ class LargeZipFile(Exception): _DD_SIGNATURE = 0x08074b50 -_EXTRA_FIELD_STRUCT = struct.Struct('<HH') - -def _strip_extra(extra, xids): - # Remove Extra Fields with specified IDs. - unpack = _EXTRA_FIELD_STRUCT.unpack - modified = False - buffer = [] - start = i = 0 - while i + 4 <= len(extra): - xid, xlen = unpack(extra[i : i + 4]) - j = i + 4 + xlen - if xid in xids: - if i != start: - buffer.append(extra[start : i]) - start = j - modified = True - i = j - if not modified: - return extra - return b''.join(buffer) + +class _Extra(bytes): + FIELD_STRUCT = struct.Struct('<HH') + + def __new__(cls, val, id=None): + return super().__new__(cls, val) + + def __init__(self, val, id=None): + self.id = id + + @classmethod + def read_one(cls, raw): + try: + xid, xlen = cls.FIELD_STRUCT.unpack(raw[:4]) + except struct.error: + xid = None + xlen = 0 + return cls(raw[:4+xlen], xid), raw[4+xlen:] + + @classmethod + def split(cls, data): + # use memoryview for zero-copy slices + rest = memoryview(data) + while rest: + extra, rest = _Extra.read_one(rest) + yield extra + + @classmethod + def strip(cls, data, xids): + """Remove Extra fields with specified IDs.""" + return b''.join( + ex + for ex in cls.split(data) + if ex.id not in xids + ) + def _check_zipfile(fp): try: @@ -280,7 +309,7 @@ def _EndRecData(fpin): fpin.seek(-sizeEndCentDir, 2) except OSError: return None - data = fpin.read() + data = fpin.read(sizeEndCentDir) if (len(data) == sizeEndCentDir and data[0:4] == stringEndArchive and data[-2:] == b"\000\000"): @@ -300,9 +329,9 @@ def _EndRecData(fpin): # record signature. The comment is the last item in the ZIP file and may be # up to 64K long. It is assumed that the "end of central directory" magic # number does not appear in the comment. - maxCommentStart = max(filesize - (1 << 16) - sizeEndCentDir, 0) + maxCommentStart = max(filesize - ZIP_MAX_COMMENT - sizeEndCentDir, 0) fpin.seek(maxCommentStart, 0) - data = fpin.read() + data = fpin.read(ZIP_MAX_COMMENT + sizeEndCentDir) start = data.rfind(stringEndArchive) if start >= 0: # found the magic number; attempt to unpack and interpret @@ -323,8 +352,26 @@ def _EndRecData(fpin): # Unable to find a valid end of central directory structure return None - -class ZipInfo (object): +def _sanitize_filename(filename): + """Terminate the file name at the first null byte and + ensure paths always use forward slashes as the directory separator.""" + + # Terminate the file name at the first null byte. Null bytes in file + # names are used as tricks by viruses in archives. + null_byte = filename.find(chr(0)) + if null_byte >= 0: + filename = filename[0:null_byte] + # This is used to ensure paths in generated ZIP files always use + # forward slashes as the directory separator, as required by the + # ZIP format specification. + if os.sep != "/" and os.sep in filename: + filename = filename.replace(os.sep, "/") + if os.altsep and os.altsep != "/" and os.altsep in filename: + filename = filename.replace(os.altsep, "/") + return filename + + +class ZipInfo: """Class with attributes describing each file in the ZIP archive.""" __slots__ = ( @@ -332,7 +379,7 @@ class ZipInfo (object): 'filename', 'date_time', 'compress_type', - '_compresslevel', + 'compress_level', 'comment', 'extra', 'create_system', @@ -348,21 +395,15 @@ class ZipInfo (object): 'compress_size', 'file_size', '_raw_time', + '_end_offset', ) def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): self.orig_filename = filename # Original file name in archive - # Terminate the file name at the first null byte. Null bytes in file - # names are used as tricks by viruses in archives. - null_byte = filename.find(chr(0)) - if null_byte >= 0: - filename = filename[0:null_byte] - # This is used to ensure paths in generated ZIP files always use - # forward slashes as the directory separator, as required by the - # ZIP format specification. - if os.sep != "/" and os.sep in filename: - filename = filename.replace(os.sep, "/") + # Terminate the file name at the first null byte and + # ensure paths always use forward slashes as the directory separator. + filename = _sanitize_filename(filename) self.filename = filename # Normalized file name self.date_time = date_time # year, month, day, hour, min, sec @@ -372,7 +413,7 @@ def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): # Standard values: self.compress_type = ZIP_STORED # Type of compression for the file - self._compresslevel = None # Level for the compressor + self.compress_level = None # Level for the compressor self.comment = b"" # Comment for each file self.extra = b"" # ZIP extra data if sys.platform == 'win32': @@ -389,10 +430,20 @@ def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): self.external_attr = 0 # External file attributes self.compress_size = 0 # Size of the compressed file self.file_size = 0 # Size of the uncompressed file + self._end_offset = None # Start of the next local header or central directory # Other attributes are set by class ZipFile: # header_offset Byte offset to the file header # CRC CRC-32 of the uncompressed file + # Maintain backward compatibility with the old protected attribute name. + @property + def _compresslevel(self): + return self.compress_level + + @_compresslevel.setter + def _compresslevel(self, value): + self.compress_level = value + def __repr__(self): result = ['<%s filename=%r' % (self.__class__.__name__, self.filename)] if self.compress_type != ZIP_STORED: @@ -416,11 +467,16 @@ def __repr__(self): return ''.join(result) def FileHeader(self, zip64=None): - """Return the per-file header as a bytes object.""" + """Return the per-file header as a bytes object. + + When the optional zip64 arg is None rather than a bool, we will + decide based upon the file_size and compress_size, if known, + False otherwise. + """ dt = self.date_time dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2) - if self.flag_bits & 0x08: + if self.flag_bits & _MASK_USE_DATA_DESCRIPTOR: # Set these to zero because we write them after the file data CRC = compress_size = file_size = 0 else: @@ -432,16 +488,13 @@ def FileHeader(self, zip64=None): min_version = 0 if zip64 is None: + # We always explicitly pass zip64 within this module.... This + # remains for anyone using ZipInfo.FileHeader as a public API. zip64 = file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT if zip64: fmt = '<HHQQ' extra = extra + struct.pack(fmt, 1, struct.calcsize(fmt)-4, file_size, compress_size) - if file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT: - if not zip64: - raise LargeZipFile("Filesize would require ZIP64 extensions") - # File is larger than what fits into a 4 byte integer, - # fall back to the ZIP64 extension file_size = 0xffffffff compress_size = 0xffffffff min_version = ZIP64_VERSION @@ -465,9 +518,9 @@ def _encodeFilenameFlags(self): try: return self.filename.encode('ascii'), self.flag_bits except UnicodeEncodeError: - return self.filename.encode('utf-8'), self.flag_bits | 0x800 + return self.filename.encode('utf-8'), self.flag_bits | _MASK_UTF_FILENAME - def _decodeExtra(self): + def _decodeExtra(self, filename_crc): # Try to decode the extra field. extra = self.extra unpack = struct.unpack @@ -493,6 +546,22 @@ def _decodeExtra(self): except struct.error: raise BadZipFile(f"Corrupt zip64 extra field. " f"{field} not found.") from None + elif tp == 0x7075: + data = extra[4:ln+4] + # Unicode Path Extra Field + try: + up_version, up_name_crc = unpack('<BL', data[:5]) + if up_version == 1 and up_name_crc == filename_crc: + up_unicode_name = data[5:].decode('utf-8') + if up_unicode_name: + self.filename = _sanitize_filename(up_unicode_name) + else: + import warnings + warnings.warn("Empty unicode path extra field (0x7075)", stacklevel=2) + except struct.error as e: + raise BadZipFile("Corrupt unicode path extra field (0x7075)") from e + except UnicodeDecodeError as e: + raise BadZipFile('Corrupt unicode path extra field (0x7075): invalid utf-8 bytes') from e extra = extra[ln+4:] @@ -536,7 +605,15 @@ def from_file(cls, filename, arcname=None, *, strict_timestamps=True): def is_dir(self): """Return True if this archive member is a directory.""" - return self.filename[-1] == '/' + if self.filename.endswith('/'): + return True + # The ZIP format specification requires to use forward slashes + # as the directory separator, but in practice some ZIP files + # created on Windows can use backward slashes. For compatibility + # with the extraction code which already handles this: + if os.path.altsep: + return self.filename.endswith((os.path.sep, os.path.altsep)) + return False # ZIP encryption uses the CRC32 one-byte primitive for scrambling some @@ -740,7 +817,10 @@ def seek(self, offset, whence=0): raise ValueError("Can't reposition in the ZIP file while " "there is an open writing handle on it. " "Close the writing handle before trying to read.") - self._file.seek(offset, whence) + if whence == os.SEEK_CUR: + self._file.seek(self._pos + offset) + else: + self._file.seek(offset, whence) self._pos = self._file.tell() return self._pos @@ -830,13 +910,14 @@ def __init__(self, fileobj, mode, zipinfo, pwd=None, self._orig_compress_size = zipinfo.compress_size self._orig_file_size = zipinfo.file_size self._orig_start_crc = self._running_crc + self._orig_crc = self._expected_crc self._seekable = True except AttributeError: pass self._decrypter = None if pwd: - if zipinfo.flag_bits & 0x8: + if zipinfo.flag_bits & _MASK_USE_DATA_DESCRIPTOR: # compare against the file type from extended local headers check_byte = (zipinfo._raw_time >> 8) & 0xff else: @@ -862,7 +943,7 @@ def __repr__(self): result = ['<%s.%s' % (self.__class__.__module__, self.__class__.__qualname__)] if not self.closed: - result.append(' name=%r mode=%r' % (self.name, self.mode)) + result.append(' name=%r' % (self.name,)) if self._compress_type != ZIP_STORED: result.append(' compress_type=%s' % compressor_names.get(self._compress_type, @@ -1052,17 +1133,17 @@ def seekable(self): raise ValueError("I/O operation on closed file.") return self._seekable - def seek(self, offset, whence=0): + def seek(self, offset, whence=os.SEEK_SET): if self.closed: raise ValueError("seek on closed file.") if not self._seekable: raise io.UnsupportedOperation("underlying stream is not seekable") curr_pos = self.tell() - if whence == 0: # Seek from start of file + if whence == os.SEEK_SET: new_pos = offset - elif whence == 1: # Seek from current position + elif whence == os.SEEK_CUR: new_pos = curr_pos + offset - elif whence == 2: # Seek from EOF + elif whence == os.SEEK_END: new_pos = self._orig_file_size + offset else: raise ValueError("whence must be os.SEEK_SET (0), " @@ -1081,10 +1162,25 @@ def seek(self, offset, whence=0): # Just move the _offset index if the new position is in the _readbuffer self._offset = buff_offset read_offset = 0 + # Fast seek uncompressed unencrypted file + elif self._compress_type == ZIP_STORED and self._decrypter is None and read_offset != 0: + # disable CRC checking after first seeking - it would be invalid + self._expected_crc = None + # seek actual file taking already buffered data into account + read_offset -= len(self._readbuffer) - self._offset + self._fileobj.seek(read_offset, os.SEEK_CUR) + self._left -= read_offset + self._compress_left -= read_offset + self._eof = self._left <= 0 + read_offset = 0 + # flush read buffer + self._readbuffer = b'' + self._offset = 0 elif read_offset < 0: # Position is before the current position. Reset the ZipExtFile self._fileobj.seek(self._orig_compress_start) self._running_crc = self._orig_start_crc + self._expected_crc = self._orig_crc self._compress_left = self._orig_compress_size self._left = self._orig_file_size self._readbuffer = b'' @@ -1117,7 +1213,7 @@ def __init__(self, zf, zinfo, zip64): self._zip64 = zip64 self._zipfile = zf self._compressor = _get_compressor(zinfo.compress_type, - zinfo._compresslevel) + zinfo.compress_level) self._file_size = 0 self._compress_size = 0 self._crc = 0 @@ -1126,6 +1222,14 @@ def __init__(self, zf, zinfo, zip64): def _fileobj(self): return self._zipfile.fp + @property + def name(self): + return self._zinfo.filename + + @property + def mode(self): + return 'wb' + def writable(self): return True @@ -1164,21 +1268,20 @@ def close(self): self._zinfo.CRC = self._crc self._zinfo.file_size = self._file_size + if not self._zip64: + if self._file_size > ZIP64_LIMIT: + raise RuntimeError("File size too large, try using force_zip64") + if self._compress_size > ZIP64_LIMIT: + raise RuntimeError("Compressed size too large, try using force_zip64") + # Write updated header info - if self._zinfo.flag_bits & 0x08: + if self._zinfo.flag_bits & _MASK_USE_DATA_DESCRIPTOR: # Write CRC and file sizes after the file data fmt = '<LLQQ' if self._zip64 else '<LLLL' self._fileobj.write(struct.pack(fmt, _DD_SIGNATURE, self._zinfo.CRC, self._zinfo.compress_size, self._zinfo.file_size)) self._zipfile.start_dir = self._fileobj.tell() else: - if not self._zip64: - if self._file_size > ZIP64_LIMIT: - raise RuntimeError( - 'File size unexpectedly exceeded ZIP64 limit') - if self._compress_size > ZIP64_LIMIT: - raise RuntimeError( - 'Compressed size unexpectedly exceeded ZIP64 limit') # Seek backwards and write file header (which will now include # correct CRC and file sizes) @@ -1223,7 +1326,7 @@ class ZipFile: _windows_illegal_name_trans_table = None def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True, - compresslevel=None, *, strict_timestamps=True): + compresslevel=None, *, strict_timestamps=True, metadata_encoding=None): """Open the ZIP file with mode read 'r', write 'w', exclusive create 'x', or append 'a'.""" if mode not in ('r', 'w', 'x', 'a'): @@ -1242,6 +1345,12 @@ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True, self.pwd = None self._comment = b'' self._strict_timestamps = strict_timestamps + self.metadata_encoding = metadata_encoding + + # Check that we don't try to write with nonconforming codecs + if self.metadata_encoding and mode != 'r': + raise ValueError( + "metadata_encoding is only supported for reading files") # Check if we were passed a file-like object if isinstance(file, os.PathLike): @@ -1374,13 +1483,14 @@ def _RealGetContents(self): if self.debug > 2: print(centdir) filename = fp.read(centdir[_CD_FILENAME_LENGTH]) - flags = centdir[5] - if flags & 0x800: + orig_filename_crc = crc32(filename) + flags = centdir[_CD_FLAG_BITS] + if flags & _MASK_UTF_FILENAME: # UTF-8 file names extension filename = filename.decode('utf-8') else: # Historical ZIP filename encoding - filename = filename.decode('cp437') + filename = filename.decode(self.metadata_encoding or 'cp437') # Create ZipInfo instance to store file information x = ZipInfo(filename) x.extra = fp.read(centdir[_CD_EXTRA_FIELD_LENGTH]) @@ -1397,8 +1507,7 @@ def _RealGetContents(self): x._raw_time = t x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F, t>>11, (t>>5)&0x3F, (t&0x1F) * 2 ) - - x._decodeExtra() + x._decodeExtra(orig_filename_crc) x.header_offset = x.header_offset + concat self.filelist.append(x) self.NameToInfo[x.filename] = x @@ -1411,6 +1520,11 @@ def _RealGetContents(self): if self.debug > 2: print("total", total) + end_offset = self.start_dir + for zinfo in reversed(sorted(self.filelist, + key=lambda zinfo: zinfo.header_offset)): + zinfo._end_offset = end_offset + end_offset = zinfo.header_offset def namelist(self): """Return a list of file names in the archive.""" @@ -1431,7 +1545,10 @@ def printdir(self, file=None): file=file) def testzip(self): - """Read all the files and check the CRC.""" + """Read all the files and check the CRC. + + Return None if all files could be read successfully, or the name + of the offending file otherwise.""" chunk_size = 2 ** 20 for zinfo in self.filelist: try: @@ -1480,7 +1597,8 @@ def comment(self, comment): self._didModify = True def read(self, name, pwd=None): - """Return file bytes for name.""" + """Return file bytes for name. 'pwd' is the password to decrypt + encrypted files.""" with self.open(name, "r", pwd) as fp: return fp.read() @@ -1502,8 +1620,6 @@ def open(self, name, mode="r", pwd=None, *, force_zip64=False): """ if mode not in {"r", "w"}: raise ValueError('open() requires mode "r" or "w"') - if pwd and not isinstance(pwd, bytes): - raise TypeError("pwd: expected bytes, got %s" % type(pwd).__name__) if pwd and (mode == "w"): raise ValueError("pwd is only supported for reading files") if not self.fp: @@ -1517,7 +1633,7 @@ def open(self, name, mode="r", pwd=None, *, force_zip64=False): elif mode == 'w': zinfo = ZipInfo(name) zinfo.compress_type = self.compression - zinfo._compresslevel = self.compresslevel + zinfo.compress_level = self.compresslevel else: # Get info object for name zinfo = self.getinfo(name) @@ -1545,39 +1661,54 @@ def open(self, name, mode="r", pwd=None, *, force_zip64=False): fname = zef_file.read(fheader[_FH_FILENAME_LENGTH]) if fheader[_FH_EXTRA_FIELD_LENGTH]: - zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH]) + zef_file.seek(fheader[_FH_EXTRA_FIELD_LENGTH], whence=1) - if zinfo.flag_bits & 0x20: + if zinfo.flag_bits & _MASK_COMPRESSED_PATCH: # Zip 2.7: compressed patched data raise NotImplementedError("compressed patched data (flag bit 5)") - if zinfo.flag_bits & 0x40: + if zinfo.flag_bits & _MASK_STRONG_ENCRYPTION: # strong encryption raise NotImplementedError("strong encryption (flag bit 6)") - if fheader[_FH_GENERAL_PURPOSE_FLAG_BITS] & 0x800: + if fheader[_FH_GENERAL_PURPOSE_FLAG_BITS] & _MASK_UTF_FILENAME: # UTF-8 filename fname_str = fname.decode("utf-8") else: - fname_str = fname.decode("cp437") + fname_str = fname.decode(self.metadata_encoding or "cp437") if fname_str != zinfo.orig_filename: raise BadZipFile( 'File name in directory %r and header %r differ.' % (zinfo.orig_filename, fname)) + if (zinfo._end_offset is not None and + zef_file.tell() + zinfo.compress_size > zinfo._end_offset): + if zinfo._end_offset == zinfo.header_offset: + import warnings + warnings.warn( + f"Overlapped entries: {zinfo.orig_filename!r} " + f"(possible zip bomb)", + skip_file_prefixes=(os.path.dirname(__file__),)) + else: + raise BadZipFile( + f"Overlapped entries: {zinfo.orig_filename!r} " + f"(possible zip bomb)") + # check for encrypted flag & handle password - is_encrypted = zinfo.flag_bits & 0x1 + is_encrypted = zinfo.flag_bits & _MASK_ENCRYPTED if is_encrypted: if not pwd: pwd = self.pwd + if pwd and not isinstance(pwd, bytes): + raise TypeError("pwd: expected bytes, got %s" % type(pwd).__name__) if not pwd: raise RuntimeError("File %r is encrypted, password " "required for extraction" % name) else: pwd = None - return ZipExtFile(zef_file, mode, zinfo, pwd, True) + return ZipExtFile(zef_file, mode + 'b', zinfo, pwd, True) except: zef_file.close() raise @@ -1600,16 +1731,17 @@ def _open_to_write(self, zinfo, force_zip64=False): zinfo.flag_bits = 0x00 if zinfo.compress_type == ZIP_LZMA: # Compressed data includes an end-of-stream (EOS) marker - zinfo.flag_bits |= 0x02 + zinfo.flag_bits |= _MASK_COMPRESS_OPTION_1 if not self._seekable: - zinfo.flag_bits |= 0x08 + zinfo.flag_bits |= _MASK_USE_DATA_DESCRIPTOR if not zinfo.external_attr: zinfo.external_attr = 0o600 << 16 # permissions: ?rw------- # Compressed size can be larger than uncompressed size - zip64 = self._allowZip64 and \ - (force_zip64 or zinfo.file_size * 1.05 > ZIP64_LIMIT) + zip64 = force_zip64 or (zinfo.file_size * 1.05 > ZIP64_LIMIT) + if not self._allowZip64 and zip64: + raise LargeZipFile("Filesize would require ZIP64 extensions") if self._seekable: self.fp.seek(self.start_dir) @@ -1627,7 +1759,8 @@ def extract(self, member, path=None, pwd=None): """Extract a member from the archive to the current working directory, using its full name. Its file information is extracted as accurately as possible. `member' may be a filename or a ZipInfo object. You can - specify a different directory using `path'. + specify a different directory using `path'. You can specify the + password to decrypt the file using 'pwd'. """ if path is None: path = os.getcwd() @@ -1640,7 +1773,8 @@ def extractall(self, path=None, members=None, pwd=None): """Extract all members from the archive to the current working directory. `path' specifies a different directory to extract to. `members' is optional and must be a subset of the list returned - by namelist(). + by namelist(). You can specify the password to decrypt all files + using 'pwd'. """ if members is None: members = self.namelist() @@ -1662,8 +1796,8 @@ def _sanitize_windows_name(cls, arcname, pathsep): table = str.maketrans(illegal, '_' * len(illegal)) cls._windows_illegal_name_trans_table = table arcname = arcname.translate(table) - # remove trailing dots - arcname = (x.rstrip('.') for x in arcname.split(pathsep)) + # remove trailing dots and spaces + arcname = (x.rstrip(' .') for x in arcname.split(pathsep)) # rejoin, removing empty parts. arcname = pathsep.join(x for x in arcname if x) return arcname @@ -1691,17 +1825,24 @@ def _extract_member(self, member, targetpath, pwd): # filter illegal characters on Windows arcname = self._sanitize_windows_name(arcname, os.path.sep) + if not arcname and not member.is_dir(): + raise ValueError("Empty filename.") + targetpath = os.path.join(targetpath, arcname) targetpath = os.path.normpath(targetpath) # Create all upper directories if necessary. upperdirs = os.path.dirname(targetpath) if upperdirs and not os.path.exists(upperdirs): - os.makedirs(upperdirs) + os.makedirs(upperdirs, exist_ok=True) if member.is_dir(): if not os.path.isdir(targetpath): - os.mkdir(targetpath) + try: + os.mkdir(targetpath) + except FileExistsError: + if not os.path.isdir(targetpath): + raise return targetpath with self.open(member, pwd=pwd) as source, \ @@ -1751,6 +1892,7 @@ def write(self, filename, arcname=None, if zinfo.is_dir(): zinfo.compress_size = 0 zinfo.CRC = 0 + self.mkdir(zinfo) else: if compress_type is not None: zinfo.compress_type = compress_type @@ -1758,27 +1900,10 @@ def write(self, filename, arcname=None, zinfo.compress_type = self.compression if compresslevel is not None: - zinfo._compresslevel = compresslevel + zinfo.compress_level = compresslevel else: - zinfo._compresslevel = self.compresslevel + zinfo.compress_level = self.compresslevel - if zinfo.is_dir(): - with self._lock: - if self._seekable: - self.fp.seek(self.start_dir) - zinfo.header_offset = self.fp.tell() # Start of header bytes - if zinfo.compress_type == ZIP_LZMA: - # Compressed data includes an end-of-stream (EOS) marker - zinfo.flag_bits |= 0x02 - - self._writecheck(zinfo) - self._didModify = True - - self.filelist.append(zinfo) - self.NameToInfo[zinfo.filename] = zinfo - self.fp.write(zinfo.FileHeader(False)) - self.start_dir = self.fp.tell() - else: with open(filename, "rb") as src, self.open(zinfo, 'w') as dest: shutil.copyfileobj(src, dest, 1024*8) @@ -1795,8 +1920,8 @@ def writestr(self, zinfo_or_arcname, data, zinfo = ZipInfo(filename=zinfo_or_arcname, date_time=time.localtime(time.time())[:6]) zinfo.compress_type = self.compression - zinfo._compresslevel = self.compresslevel - if zinfo.filename[-1] == '/': + zinfo.compress_level = self.compresslevel + if zinfo.filename.endswith('/'): zinfo.external_attr = 0o40775 << 16 # drwxrwxr-x zinfo.external_attr |= 0x10 # MS-DOS directory flag else: @@ -1816,13 +1941,48 @@ def writestr(self, zinfo_or_arcname, data, zinfo.compress_type = compress_type if compresslevel is not None: - zinfo._compresslevel = compresslevel + zinfo.compress_level = compresslevel zinfo.file_size = len(data) # Uncompressed size with self._lock: with self.open(zinfo, mode='w') as dest: dest.write(data) + def mkdir(self, zinfo_or_directory_name, mode=511): + """Creates a directory inside the zip archive.""" + if isinstance(zinfo_or_directory_name, ZipInfo): + zinfo = zinfo_or_directory_name + if not zinfo.is_dir(): + raise ValueError("The given ZipInfo does not describe a directory") + elif isinstance(zinfo_or_directory_name, str): + directory_name = zinfo_or_directory_name + if not directory_name.endswith("/"): + directory_name += "/" + zinfo = ZipInfo(directory_name) + zinfo.compress_size = 0 + zinfo.CRC = 0 + zinfo.external_attr = ((0o40000 | mode) & 0xFFFF) << 16 + zinfo.file_size = 0 + zinfo.external_attr |= 0x10 + else: + raise TypeError("Expected type str or ZipInfo") + + with self._lock: + if self._seekable: + self.fp.seek(self.start_dir) + zinfo.header_offset = self.fp.tell() # Start of header bytes + if zinfo.compress_type == ZIP_LZMA: + # Compressed data includes an end-of-stream (EOS) marker + zinfo.flag_bits |= _MASK_COMPRESS_OPTION_1 + + self._writecheck(zinfo) + self._didModify = True + + self.filelist.append(zinfo) + self.NameToInfo[zinfo.filename] = zinfo + self.fp.write(zinfo.FileHeader(False)) + self.start_dir = self.fp.tell() + def __del__(self): """Call the "close()" method in case the user forgot.""" self.close() @@ -1875,7 +2035,7 @@ def _write_end_record(self): min_version = 0 if extra: # Append a ZIP64 field to the extra's - extra_data = _strip_extra(extra_data, (1,)) + extra_data = _Extra.strip(extra_data, (1,)) extra_data = struct.pack( '<HH' + 'Q'*len(extra), 1, 8*len(extra), *extra) + extra_data @@ -2124,300 +2284,6 @@ def _compile(file, optimize=-1): return (fname, archivename) -def _parents(path): - """ - Given a path with elements separated by - posixpath.sep, generate all parents of that path. - - >>> list(_parents('b/d')) - ['b'] - >>> list(_parents('/b/d/')) - ['/b'] - >>> list(_parents('b/d/f/')) - ['b/d', 'b'] - >>> list(_parents('b')) - [] - >>> list(_parents('')) - [] - """ - return itertools.islice(_ancestry(path), 1, None) - - -def _ancestry(path): - """ - Given a path with elements separated by - posixpath.sep, generate all elements of that path - - >>> list(_ancestry('b/d')) - ['b/d', 'b'] - >>> list(_ancestry('/b/d/')) - ['/b/d', '/b'] - >>> list(_ancestry('b/d/f/')) - ['b/d/f', 'b/d', 'b'] - >>> list(_ancestry('b')) - ['b'] - >>> list(_ancestry('')) - [] - """ - path = path.rstrip(posixpath.sep) - while path and path != posixpath.sep: - yield path - path, tail = posixpath.split(path) - - -_dedupe = dict.fromkeys -"""Deduplicate an iterable in original order""" - - -def _difference(minuend, subtrahend): - """ - Return items in minuend not in subtrahend, retaining order - with O(1) lookup. - """ - return itertools.filterfalse(set(subtrahend).__contains__, minuend) - - -class CompleteDirs(ZipFile): - """ - A ZipFile subclass that ensures that implied directories - are always included in the namelist. - """ - - @staticmethod - def _implied_dirs(names): - parents = itertools.chain.from_iterable(map(_parents, names)) - as_dirs = (p + posixpath.sep for p in parents) - return _dedupe(_difference(as_dirs, names)) - - def namelist(self): - names = super(CompleteDirs, self).namelist() - return names + list(self._implied_dirs(names)) - - def _name_set(self): - return set(self.namelist()) - - def resolve_dir(self, name): - """ - If the name represents a directory, return that name - as a directory (with the trailing slash). - """ - names = self._name_set() - dirname = name + '/' - dir_match = name not in names and dirname in names - return dirname if dir_match else name - - @classmethod - def make(cls, source): - """ - Given a source (filename or zipfile), return an - appropriate CompleteDirs subclass. - """ - if isinstance(source, CompleteDirs): - return source - - if not isinstance(source, ZipFile): - return cls(source) - - # Only allow for FastLookup when supplied zipfile is read-only - if 'r' not in source.mode: - cls = CompleteDirs - - source.__class__ = cls - return source - - -class FastLookup(CompleteDirs): - """ - ZipFile subclass to ensure implicit - dirs exist and are resolved rapidly. - """ - - def namelist(self): - with contextlib.suppress(AttributeError): - return self.__names - self.__names = super(FastLookup, self).namelist() - return self.__names - - def _name_set(self): - with contextlib.suppress(AttributeError): - return self.__lookup - self.__lookup = super(FastLookup, self)._name_set() - return self.__lookup - - -class Path: - """ - A pathlib-compatible interface for zip files. - - Consider a zip file with this structure:: - - . - ├── a.txt - └── b - ├── c.txt - └── d - └── e.txt - - >>> data = io.BytesIO() - >>> zf = ZipFile(data, 'w') - >>> zf.writestr('a.txt', 'content of a') - >>> zf.writestr('b/c.txt', 'content of c') - >>> zf.writestr('b/d/e.txt', 'content of e') - >>> zf.filename = 'mem/abcde.zip' - - Path accepts the zipfile object itself or a filename - - >>> root = Path(zf) - - From there, several path operations are available. - - Directory iteration (including the zip file itself): - - >>> a, b = root.iterdir() - >>> a - Path('mem/abcde.zip', 'a.txt') - >>> b - Path('mem/abcde.zip', 'b/') - - name property: - - >>> b.name - 'b' - - join with divide operator: - - >>> c = b / 'c.txt' - >>> c - Path('mem/abcde.zip', 'b/c.txt') - >>> c.name - 'c.txt' - - Read text: - - >>> c.read_text() - 'content of c' - - existence: - - >>> c.exists() - True - >>> (b / 'missing.txt').exists() - False - - Coercion to string: - - >>> import os - >>> str(c).replace(os.sep, posixpath.sep) - 'mem/abcde.zip/b/c.txt' - - At the root, ``name``, ``filename``, and ``parent`` - resolve to the zipfile. Note these attributes are not - valid and will raise a ``ValueError`` if the zipfile - has no filename. - - >>> root.name - 'abcde.zip' - >>> str(root.filename).replace(os.sep, posixpath.sep) - 'mem/abcde.zip' - >>> str(root.parent) - 'mem' - """ - - __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" - - def __init__(self, root, at=""): - """ - Construct a Path from a ZipFile or filename. - - Note: When the source is an existing ZipFile object, - its type (__class__) will be mutated to a - specialized type. If the caller wishes to retain the - original type, the caller should either create a - separate ZipFile object or pass a filename. - """ - self.root = FastLookup.make(root) - self.at = at - - def open(self, mode='r', *args, pwd=None, **kwargs): - """ - Open this entry as text or binary following the semantics - of ``pathlib.Path.open()`` by passing arguments through - to io.TextIOWrapper(). - """ - if self.is_dir(): - raise IsADirectoryError(self) - zip_mode = mode[0] - if not self.exists() and zip_mode == 'r': - raise FileNotFoundError(self) - stream = self.root.open(self.at, zip_mode, pwd=pwd) - if 'b' in mode: - if args or kwargs: - raise ValueError("encoding args invalid for binary operation") - return stream - else: - kwargs["encoding"] = io.text_encoding(kwargs.get("encoding")) - return io.TextIOWrapper(stream, *args, **kwargs) - - @property - def name(self): - return pathlib.Path(self.at).name or self.filename.name - - @property - def filename(self): - return pathlib.Path(self.root.filename).joinpath(self.at) - - def read_text(self, *args, **kwargs): - kwargs["encoding"] = io.text_encoding(kwargs.get("encoding")) - with self.open('r', *args, **kwargs) as strm: - return strm.read() - - def read_bytes(self): - with self.open('rb') as strm: - return strm.read() - - def _is_child(self, path): - return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/") - - def _next(self, at): - return self.__class__(self.root, at) - - def is_dir(self): - return not self.at or self.at.endswith("/") - - def is_file(self): - return self.exists() and not self.is_dir() - - def exists(self): - return self.at in self.root._name_set() - - def iterdir(self): - if not self.is_dir(): - raise ValueError("Can't listdir a file") - subs = map(self._next, self.root.namelist()) - return filter(self._is_child, subs) - - def __str__(self): - return posixpath.join(self.root.filename, self.at) - - def __repr__(self): - return self.__repr.format(self=self) - - def joinpath(self, *other): - next = posixpath.join(self.at, *other) - return self._next(self.root.resolve_dir(next)) - - __truediv__ = joinpath - - @property - def parent(self): - if not self.at: - return self.filename.parent - parent_at = posixpath.dirname(self.at.rstrip('/')) - if parent_at: - parent_at += '/' - return self._next(parent_at) - - def main(args=None): import argparse @@ -2434,11 +2300,15 @@ def main(args=None): help='Create zipfile from sources') group.add_argument('-t', '--test', metavar='<zipfile>', help='Test if a zipfile is valid') + parser.add_argument('--metadata-encoding', metavar='<encoding>', + help='Specify encoding of member names for -l, -e and -t') args = parser.parse_args(args) + encoding = args.metadata_encoding + if args.test is not None: src = args.test - with ZipFile(src, 'r') as zf: + with ZipFile(src, 'r', metadata_encoding=encoding) as zf: badfile = zf.testzip() if badfile: print("The following enclosed file is corrupted: {!r}".format(badfile)) @@ -2446,15 +2316,20 @@ def main(args=None): elif args.list is not None: src = args.list - with ZipFile(src, 'r') as zf: + with ZipFile(src, 'r', metadata_encoding=encoding) as zf: zf.printdir() elif args.extract is not None: src, curdir = args.extract - with ZipFile(src, 'r') as zf: + with ZipFile(src, 'r', metadata_encoding=encoding) as zf: zf.extractall(curdir) elif args.create is not None: + if encoding: + print("Non-conforming encodings not supported with -c.", + file=sys.stderr) + sys.exit(1) + zip_name = args.create.pop(0) files = args.create @@ -2479,5 +2354,9 @@ def addToZip(zf, path, zippath): addToZip(zf, path, zippath) -if __name__ == "__main__": - main() +from ._path import ( # noqa: E402 + Path, + + # used privately for tests + CompleteDirs, # noqa: F401 +) diff --git a/Lib/zipfile/__main__.py b/Lib/zipfile/__main__.py new file mode 100644 index 00000000000..868d99efc3c --- /dev/null +++ b/Lib/zipfile/__main__.py @@ -0,0 +1,4 @@ +from . import main + +if __name__ == "__main__": + main() diff --git a/Lib/zipfile/_path/__init__.py b/Lib/zipfile/_path/__init__.py new file mode 100644 index 00000000000..02f81171b4f --- /dev/null +++ b/Lib/zipfile/_path/__init__.py @@ -0,0 +1,452 @@ +""" +A Path-like interface for zipfiles. + +This codebase is shared between zipfile.Path in the stdlib +and zipp in PyPI. See +https://github.com/python/importlib_metadata/wiki/Development-Methodology +for more detail. +""" + +import contextlib +import io +import itertools +import pathlib +import posixpath +import re +import stat +import sys +import zipfile + +from .glob import Translator + +__all__ = ['Path'] + + +def _parents(path): + """ + Given a path with elements separated by + posixpath.sep, generate all parents of that path. + + >>> list(_parents('b/d')) + ['b'] + >>> list(_parents('/b/d/')) + ['/b'] + >>> list(_parents('b/d/f/')) + ['b/d', 'b'] + >>> list(_parents('b')) + [] + >>> list(_parents('')) + [] + """ + return itertools.islice(_ancestry(path), 1, None) + + +def _ancestry(path): + """ + Given a path with elements separated by + posixpath.sep, generate all elements of that path. + + >>> list(_ancestry('b/d')) + ['b/d', 'b'] + >>> list(_ancestry('/b/d/')) + ['/b/d', '/b'] + >>> list(_ancestry('b/d/f/')) + ['b/d/f', 'b/d', 'b'] + >>> list(_ancestry('b')) + ['b'] + >>> list(_ancestry('')) + [] + + Multiple separators are treated like a single. + + >>> list(_ancestry('//b//d///f//')) + ['//b//d///f', '//b//d', '//b'] + """ + path = path.rstrip(posixpath.sep) + while path.rstrip(posixpath.sep): + yield path + path, tail = posixpath.split(path) + + +_dedupe = dict.fromkeys +"""Deduplicate an iterable in original order""" + + +def _difference(minuend, subtrahend): + """ + Return items in minuend not in subtrahend, retaining order + with O(1) lookup. + """ + return itertools.filterfalse(set(subtrahend).__contains__, minuend) + + +class InitializedState: + """ + Mix-in to save the initialization state for pickling. + """ + + def __init__(self, *args, **kwargs): + self.__args = args + self.__kwargs = kwargs + super().__init__(*args, **kwargs) + + def __getstate__(self): + return self.__args, self.__kwargs + + def __setstate__(self, state): + args, kwargs = state + super().__init__(*args, **kwargs) + + +class CompleteDirs(InitializedState, zipfile.ZipFile): + """ + A ZipFile subclass that ensures that implied directories + are always included in the namelist. + + >>> list(CompleteDirs._implied_dirs(['foo/bar.txt', 'foo/bar/baz.txt'])) + ['foo/', 'foo/bar/'] + >>> list(CompleteDirs._implied_dirs(['foo/bar.txt', 'foo/bar/baz.txt', 'foo/bar/'])) + ['foo/'] + """ + + @staticmethod + def _implied_dirs(names): + parents = itertools.chain.from_iterable(map(_parents, names)) + as_dirs = (p + posixpath.sep for p in parents) + return _dedupe(_difference(as_dirs, names)) + + def namelist(self): + names = super().namelist() + return names + list(self._implied_dirs(names)) + + def _name_set(self): + return set(self.namelist()) + + def resolve_dir(self, name): + """ + If the name represents a directory, return that name + as a directory (with the trailing slash). + """ + names = self._name_set() + dirname = name + '/' + dir_match = name not in names and dirname in names + return dirname if dir_match else name + + def getinfo(self, name): + """ + Supplement getinfo for implied dirs. + """ + try: + return super().getinfo(name) + except KeyError: + if not name.endswith('/') or name not in self._name_set(): + raise + return zipfile.ZipInfo(filename=name) + + @classmethod + def make(cls, source): + """ + Given a source (filename or zipfile), return an + appropriate CompleteDirs subclass. + """ + if isinstance(source, CompleteDirs): + return source + + if not isinstance(source, zipfile.ZipFile): + return cls(source) + + # Only allow for FastLookup when supplied zipfile is read-only + if 'r' not in source.mode: + cls = CompleteDirs + + source.__class__ = cls + return source + + @classmethod + def inject(cls, zf: zipfile.ZipFile) -> zipfile.ZipFile: + """ + Given a writable zip file zf, inject directory entries for + any directories implied by the presence of children. + """ + for name in cls._implied_dirs(zf.namelist()): + zf.writestr(name, b"") + return zf + + +class FastLookup(CompleteDirs): + """ + ZipFile subclass to ensure implicit + dirs exist and are resolved rapidly. + """ + + def namelist(self): + with contextlib.suppress(AttributeError): + return self.__names + self.__names = super().namelist() + return self.__names + + def _name_set(self): + with contextlib.suppress(AttributeError): + return self.__lookup + self.__lookup = super()._name_set() + return self.__lookup + +def _extract_text_encoding(encoding=None, *args, **kwargs): + # compute stack level so that the caller of the caller sees any warning. + is_pypy = sys.implementation.name == 'pypy' + # PyPy no longer special cased after 7.3.19 (or maybe 7.3.18) + # See jaraco/zipp#143 + is_old_pypi = is_pypy and sys.pypy_version_info < (7, 3, 19) + stack_level = 3 + is_old_pypi + return io.text_encoding(encoding, stack_level), args, kwargs + + +class Path: + """ + A :class:`importlib.resources.abc.Traversable` interface for zip files. + + Implements many of the features users enjoy from + :class:`pathlib.Path`. + + Consider a zip file with this structure:: + + . + ├── a.txt + └── b + ├── c.txt + └── d + └── e.txt + + >>> data = io.BytesIO() + >>> zf = ZipFile(data, 'w') + >>> zf.writestr('a.txt', 'content of a') + >>> zf.writestr('b/c.txt', 'content of c') + >>> zf.writestr('b/d/e.txt', 'content of e') + >>> zf.filename = 'mem/abcde.zip' + + Path accepts the zipfile object itself or a filename + + >>> path = Path(zf) + + From there, several path operations are available. + + Directory iteration (including the zip file itself): + + >>> a, b = path.iterdir() + >>> a + Path('mem/abcde.zip', 'a.txt') + >>> b + Path('mem/abcde.zip', 'b/') + + name property: + + >>> b.name + 'b' + + join with divide operator: + + >>> c = b / 'c.txt' + >>> c + Path('mem/abcde.zip', 'b/c.txt') + >>> c.name + 'c.txt' + + Read text: + + >>> c.read_text(encoding='utf-8') + 'content of c' + + existence: + + >>> c.exists() + True + >>> (b / 'missing.txt').exists() + False + + Coercion to string: + + >>> import os + >>> str(c).replace(os.sep, posixpath.sep) + 'mem/abcde.zip/b/c.txt' + + At the root, ``name``, ``filename``, and ``parent`` + resolve to the zipfile. + + >>> str(path) + 'mem/abcde.zip/' + >>> path.name + 'abcde.zip' + >>> path.filename == pathlib.Path('mem/abcde.zip') + True + >>> str(path.parent) + 'mem' + + If the zipfile has no filename, such attributes are not + valid and accessing them will raise an Exception. + + >>> zf.filename = None + >>> path.name + Traceback (most recent call last): + ... + TypeError: ... + + >>> path.filename + Traceback (most recent call last): + ... + TypeError: ... + + >>> path.parent + Traceback (most recent call last): + ... + TypeError: ... + + # workaround python/cpython#106763 + >>> pass + """ + + __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" + + def __init__(self, root, at=""): + """ + Construct a Path from a ZipFile or filename. + + Note: When the source is an existing ZipFile object, + its type (__class__) will be mutated to a + specialized type. If the caller wishes to retain the + original type, the caller should either create a + separate ZipFile object or pass a filename. + """ + self.root = FastLookup.make(root) + self.at = at + + def __eq__(self, other): + """ + >>> Path(zipfile.ZipFile(io.BytesIO(), 'w')) == 'foo' + False + """ + if self.__class__ is not other.__class__: + return NotImplemented + return (self.root, self.at) == (other.root, other.at) + + def __hash__(self): + return hash((self.root, self.at)) + + def open(self, mode='r', *args, pwd=None, **kwargs): + """ + Open this entry as text or binary following the semantics + of ``pathlib.Path.open()`` by passing arguments through + to io.TextIOWrapper(). + """ + if self.is_dir(): + raise IsADirectoryError(self) + zip_mode = mode[0] + if zip_mode == 'r' and not self.exists(): + raise FileNotFoundError(self) + stream = self.root.open(self.at, zip_mode, pwd=pwd) + if 'b' in mode: + if args or kwargs: + raise ValueError("encoding args invalid for binary operation") + return stream + # Text mode: + encoding, args, kwargs = _extract_text_encoding(*args, **kwargs) + return io.TextIOWrapper(stream, encoding, *args, **kwargs) + + def _base(self): + return pathlib.PurePosixPath(self.at) if self.at else self.filename + + @property + def name(self): + return self._base().name + + @property + def suffix(self): + return self._base().suffix + + @property + def suffixes(self): + return self._base().suffixes + + @property + def stem(self): + return self._base().stem + + @property + def filename(self): + return pathlib.Path(self.root.filename).joinpath(self.at) + + def read_text(self, *args, **kwargs): + encoding, args, kwargs = _extract_text_encoding(*args, **kwargs) + with self.open('r', encoding, *args, **kwargs) as strm: + return strm.read() + + def read_bytes(self): + with self.open('rb') as strm: + return strm.read() + + def _is_child(self, path): + return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/") + + def _next(self, at): + return self.__class__(self.root, at) + + def is_dir(self): + return not self.at or self.at.endswith("/") + + def is_file(self): + return self.exists() and not self.is_dir() + + def exists(self): + return self.at in self.root._name_set() + + def iterdir(self): + if not self.is_dir(): + raise ValueError("Can't listdir a file") + subs = map(self._next, self.root.namelist()) + return filter(self._is_child, subs) + + def match(self, path_pattern): + return pathlib.PurePosixPath(self.at).match(path_pattern) + + def is_symlink(self): + """ + Return whether this path is a symlink. + """ + info = self.root.getinfo(self.at) + mode = info.external_attr >> 16 + return stat.S_ISLNK(mode) + + def glob(self, pattern): + if not pattern: + raise ValueError(f"Unacceptable pattern: {pattern!r}") + + prefix = re.escape(self.at) + tr = Translator(seps='/') + matches = re.compile(prefix + tr.translate(pattern)).fullmatch + return map(self._next, filter(matches, self.root.namelist())) + + def rglob(self, pattern): + return self.glob(f'**/{pattern}') + + def relative_to(self, other, *extra): + return posixpath.relpath(str(self), str(other.joinpath(*extra))) + + def __str__(self): + return posixpath.join(self.root.filename, self.at) + + def __repr__(self): + return self.__repr.format(self=self) + + def joinpath(self, *other): + next = posixpath.join(self.at, *other) + return self._next(self.root.resolve_dir(next)) + + __truediv__ = joinpath + + @property + def parent(self): + if not self.at: + return self.filename.parent + parent_at = posixpath.dirname(self.at.rstrip('/')) + if parent_at: + parent_at += '/' + return self._next(parent_at) diff --git a/Lib/zipfile/_path/glob.py b/Lib/zipfile/_path/glob.py new file mode 100644 index 00000000000..4ed74cc48d9 --- /dev/null +++ b/Lib/zipfile/_path/glob.py @@ -0,0 +1,113 @@ +import os +import re + +_default_seps = os.sep + str(os.altsep) * bool(os.altsep) + + +class Translator: + """ + >>> Translator('xyz') + Traceback (most recent call last): + ... + AssertionError: Invalid separators + + >>> Translator('') + Traceback (most recent call last): + ... + AssertionError: Invalid separators + """ + + seps: str + + def __init__(self, seps: str = _default_seps): + assert seps and set(seps) <= set(_default_seps), "Invalid separators" + self.seps = seps + + def translate(self, pattern): + """ + Given a glob pattern, produce a regex that matches it. + """ + return self.extend(self.match_dirs(self.translate_core(pattern))) + + def extend(self, pattern): + r""" + Extend regex for pattern-wide concerns. + + Apply '(?s:)' to create a non-matching group that + matches newlines (valid on Unix). + + Append '\Z' to imply fullmatch even when match is used. + """ + return rf'(?s:{pattern})\Z' + + def match_dirs(self, pattern): + """ + Ensure that zipfile.Path directory names are matched. + + zipfile.Path directory names always end in a slash. + """ + return rf'{pattern}[/]?' + + def translate_core(self, pattern): + r""" + Given a glob pattern, produce a regex that matches it. + + >>> t = Translator() + >>> t.translate_core('*.txt').replace('\\\\', '') + '[^/]*\\.txt' + >>> t.translate_core('a?txt') + 'a[^/]txt' + >>> t.translate_core('**/*').replace('\\\\', '') + '.*/[^/][^/]*' + """ + self.restrict_rglob(pattern) + return ''.join(map(self.replace, separate(self.star_not_empty(pattern)))) + + def replace(self, match): + """ + Perform the replacements for a match from :func:`separate`. + """ + return match.group('set') or ( + re.escape(match.group(0)) + .replace('\\*\\*', r'.*') + .replace('\\*', rf'[^{re.escape(self.seps)}]*') + .replace('\\?', r'[^/]') + ) + + def restrict_rglob(self, pattern): + """ + Raise ValueError if ** appears in anything but a full path segment. + + >>> Translator().translate('**foo') + Traceback (most recent call last): + ... + ValueError: ** must appear alone in a path segment + """ + seps_pattern = rf'[{re.escape(self.seps)}]+' + segments = re.split(seps_pattern, pattern) + if any('**' in segment and segment != '**' for segment in segments): + raise ValueError("** must appear alone in a path segment") + + def star_not_empty(self, pattern): + """ + Ensure that * will not match an empty segment. + """ + + def handle_segment(match): + segment = match.group(0) + return '?*' if segment == '*' else segment + + not_seps_pattern = rf'[^{re.escape(self.seps)}]+' + return re.sub(not_seps_pattern, handle_segment, pattern) + + +def separate(pattern): + """ + Separate out character sets to avoid translating their contents. + + >>> [m.group(0) for m in separate('*.txt')] + ['*.txt'] + >>> [m.group(0) for m in separate('a[?]txt')] + ['a', '[?]', 'txt'] + """ + return re.finditer(r'([^\[]+)|(?P<set>[\[].*?[\]])|([\[][^\]]*$)', pattern) From dac236dac077d737298d7570d10b5833a8c64f62 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:15:02 +0900 Subject: [PATCH 633/819] more no_std clippy (#6587) --- crates/common/src/os.rs | 2 +- crates/jit/tests/common.rs | 2 +- crates/sre_engine/benches/benches.rs | 2 +- crates/stdlib/src/scproxy.rs | 2 +- crates/stdlib/src/socket.rs | 32 +++++++++++++-------------- crates/stdlib/src/sqlite.rs | 6 +++-- crates/vm/src/function/builtin.rs | 2 +- crates/vm/src/stdlib/ctypes.rs | 2 +- crates/vm/src/stdlib/ctypes/array.rs | 4 ++-- crates/vm/src/stdlib/ctypes/simple.rs | 2 +- crates/vm/src/stdlib/os.rs | 10 ++++----- crates/vm/src/stdlib/posix.rs | 4 ++-- crates/vm/src/stdlib/thread.rs | 14 ++++++------ crates/wasm/src/browser_module.rs | 3 ++- crates/wasm/src/js_module.rs | 2 +- crates/wasm/src/lib.rs | 2 ++ crates/wasm/src/vm_class.rs | 8 +++---- 17 files changed, 50 insertions(+), 49 deletions(-) diff --git a/crates/common/src/os.rs b/crates/common/src/os.rs index 3e09a29210a..1ce25988d28 100644 --- a/crates/common/src/os.rs +++ b/crates/common/src/os.rs @@ -89,7 +89,7 @@ pub fn bytes_as_os_str(b: &[u8]) -> Result<&std::ffi::OsStr, Utf8Error> { #[cfg(not(unix))] pub fn bytes_as_os_str(b: &[u8]) -> Result<&std::ffi::OsStr, Utf8Error> { - Ok(std::str::from_utf8(b)?.as_ref()) + Ok(core::str::from_utf8(b)?.as_ref()) } #[cfg(unix)] diff --git a/crates/jit/tests/common.rs b/crates/jit/tests/common.rs index 5dafeaeb807..17c280ec3c9 100644 --- a/crates/jit/tests/common.rs +++ b/crates/jit/tests/common.rs @@ -1,9 +1,9 @@ +use core::ops::ControlFlow; use rustpython_compiler_core::bytecode::{ CodeObject, ConstantData, Instruction, OpArg, OpArgState, }; use rustpython_jit::{CompiledCode, JitType}; use std::collections::HashMap; -use std::ops::ControlFlow; #[derive(Debug, Clone)] pub struct Function { diff --git a/crates/sre_engine/benches/benches.rs b/crates/sre_engine/benches/benches.rs index 127f72e2747..9905a8db70f 100644 --- a/crates/sre_engine/benches/benches.rs +++ b/crates/sre_engine/benches/benches.rs @@ -15,7 +15,7 @@ impl Pattern { fn state_range<'a, S: StrDrive>( &self, string: S, - range: std::ops::Range<usize>, + range: core::ops::Range<usize>, ) -> (Request<'a, S>, State) { let req = Request::new(string, range.start, range.end, self.code, false); let state = State::default(); diff --git a/crates/stdlib/src/scproxy.rs b/crates/stdlib/src/scproxy.rs index 40267579029..b105dc4f2fb 100644 --- a/crates/stdlib/src/scproxy.rs +++ b/crates/stdlib/src/scproxy.rs @@ -91,7 +91,7 @@ mod _scproxy { .find(host_key) .and_then(|v| v.downcast::<CFString>()) { - let h = std::borrow::Cow::<str>::from(&host); + let h = alloc::borrow::Cow::<str>::from(&host); let v = if let Some(port) = proxy_dict .find(port_key) .and_then(|v| v.downcast::<CFNumber>()) diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index 71bc8e9f170..a37a4fd241d 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -1228,7 +1228,7 @@ mod _socket { .new_os_error("interface name too long".to_owned()) .into()); } - let cstr = std::ffi::CString::new(ifname) + let cstr = alloc::ffi::CString::new(ifname) .map_err(|_| vm.new_os_error("invalid interface name".to_owned()))?; let idx = unsafe { libc::if_nametoindex(cstr.as_ptr()) }; if idx == 0 { @@ -1238,7 +1238,7 @@ mod _socket { }; // Create sockaddr_can - let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() }; + let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() }; let can_addr = &mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_can; unsafe { @@ -1294,7 +1294,7 @@ mod _socket { } // Create sockaddr_alg - let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() }; + let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() }; let alg_addr = &mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_alg; unsafe { @@ -1936,7 +1936,7 @@ mod _socket { flags: OptionalArg<i32>, vm: &VirtualMachine, ) -> PyResult<PyTupleRef> { - use std::mem::MaybeUninit; + use core::mem::MaybeUninit; if bufsize < 0 { return Err(vm.new_value_error("negative buffer size in recvmsg".to_owned())); @@ -1955,7 +1955,7 @@ mod _socket { // Allocate buffers let mut data_buf: Vec<MaybeUninit<u8>> = vec![MaybeUninit::uninit(); bufsize]; let mut anc_buf: Vec<MaybeUninit<u8>> = vec![MaybeUninit::uninit(); ancbufsize]; - let mut addr_storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() }; + let mut addr_storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() }; // Set up iovec let mut iov = [libc::iovec { @@ -1964,9 +1964,9 @@ mod _socket { }]; // Set up msghdr - let mut msg: libc::msghdr = unsafe { std::mem::zeroed() }; + let mut msg: libc::msghdr = unsafe { core::mem::zeroed() }; msg.msg_name = (&mut addr_storage as *mut libc::sockaddr_storage).cast(); - msg.msg_namelen = std::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t; + msg.msg_namelen = core::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t; msg.msg_iov = iov.as_mut_ptr(); msg.msg_iovlen = 1; if ancbufsize > 0 { @@ -1990,7 +1990,7 @@ mod _socket { // Build data bytes let data = unsafe { data_buf.set_len(n); - std::mem::transmute::<Vec<MaybeUninit<u8>>, Vec<u8>>(data_buf) + core::mem::transmute::<Vec<MaybeUninit<u8>>, Vec<u8>>(data_buf) }; // Build ancdata list @@ -1999,7 +1999,7 @@ mod _socket { // Build address tuple let address = if msg.msg_namelen > 0 { let storage: socket2::SockAddrStorage = - unsafe { std::mem::transmute(addr_storage) }; + unsafe { core::mem::transmute(addr_storage) }; let addr = unsafe { socket2::SockAddr::new(storage, msg.msg_namelen) }; get_addr_tuple(&addr, vm) } else { @@ -2034,7 +2034,7 @@ mod _socket { let available = ctrl_end as usize - data_ptr as usize; let data_len = data_len_from_cmsg.min(available); - let data = unsafe { std::slice::from_raw_parts(data_ptr, data_len) }; + let data = unsafe { core::slice::from_raw_parts(data_ptr, data_len) }; let tuple = vm.ctx.new_tuple(vec![ vm.ctx.new_int(cmsg_ref.cmsg_level).into(), @@ -2820,13 +2820,11 @@ mod _socket { let host = host_encoded.as_deref(); // Encode port using UTF-8 - let port: Option<std::borrow::Cow<'_, str>> = match opts.port.as_ref() { - Some(Either::A(s)) => { - Some(std::borrow::Cow::Borrowed(s.to_str().ok_or_else(|| { - vm.new_unicode_encode_error("surrogates not allowed".to_owned()) - })?)) - } - Some(Either::B(i)) => Some(std::borrow::Cow::Owned(i.to_string())), + let port: Option<alloc::borrow::Cow<'_, str>> = match opts.port.as_ref() { + Some(Either::A(s)) => Some(alloc::borrow::Cow::Borrowed(s.to_str().ok_or_else( + || vm.new_unicode_encode_error("surrogates not allowed".to_owned()), + )?)), + Some(Either::B(i)) => Some(alloc::borrow::Cow::Owned(i.to_string())), None => None, }; let port = port.as_ref().map(|p| p.as_ref()); diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index 3a82787cd8f..54c889ecb6b 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -3191,7 +3191,9 @@ mod _sqlite { } fn aggregate_context<T>(self) -> *mut T { - unsafe { sqlite3_aggregate_context(self.ctx, std::mem::size_of::<T>() as c_int).cast() } + unsafe { + sqlite3_aggregate_context(self.ctx, core::mem::size_of::<T>() as c_int).cast() + } } fn result_exception(self, vm: &VirtualMachine, exc: PyBaseExceptionRef, msg: &str) { @@ -3297,7 +3299,7 @@ mod _sqlite { } else if nbytes < 0 { Err(vm.new_system_error("negative size with ptr")) } else { - Ok(unsafe { std::slice::from_raw_parts(p.cast(), nbytes as usize) }.to_vec()) + Ok(unsafe { core::slice::from_raw_parts(p.cast(), nbytes as usize) }.to_vec()) } } diff --git a/crates/vm/src/function/builtin.rs b/crates/vm/src/function/builtin.rs index 06fd6a44f54..444df64a8ef 100644 --- a/crates/vm/src/function/builtin.rs +++ b/crates/vm/src/function/builtin.rs @@ -218,7 +218,7 @@ into_py_native_fn_tuple!( #[cfg(test)] mod tests { use super::*; - use std::mem::size_of_val; + use core::mem::size_of_val; #[test] fn test_into_native_fn_noalloc() { diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index f3b6dd25aca..cbe3f5405b6 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -1180,7 +1180,7 @@ pub(crate) mod _ctypes { path: Option<PyObjectRef>, vm: &VirtualMachine, ) -> PyResult<bool> { - use std::ffi::CString; + use alloc::ffi::CString; let path = match path { Some(p) if !vm.is_none(&p) => p, diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 168da2bcc01..5c298d26a56 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -12,8 +12,8 @@ use crate::{ protocol::{BufferDescriptor, PyBuffer, PyNumberMethods, PySequenceMethods}, types::{AsBuffer, AsNumber, AsSequence, Constructor, Initializer}, }; +use alloc::borrow::Cow; use num_traits::{Signed, ToPrimitive}; -use std::borrow::Cow; /// Get itemsize from a PEP 3118 format string /// Extracts the type code (last char after endianness prefix) and returns its size @@ -890,7 +890,7 @@ impl PyCArray { // Python's from_buffer requires writable buffer, so this is safe. let ptr = slice.as_ptr() as *mut u8; let len = slice.len(); - let owned_slice = unsafe { std::slice::from_raw_parts_mut(ptr, len) }; + let owned_slice = unsafe { core::slice::from_raw_parts_mut(ptr, len) }; Self::write_element_to_buffer( owned_slice, final_offset, diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs index 9835953812f..7bcfa203b02 100644 --- a/crates/vm/src/stdlib/ctypes/simple.rs +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -1304,7 +1304,7 @@ impl PyCSimple { let ptr = slice.as_ptr() as *mut u8; let len = slice.len().min(buffer_bytes.len()); unsafe { - std::ptr::copy_nonoverlapping(buffer_bytes.as_ptr(), ptr, len); + core::ptr::copy_nonoverlapping(buffer_bytes.as_ptr(), ptr, len); } } Cow::Owned(vec) => { diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index add8763f5e1..fa1495fcdbf 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1747,16 +1747,16 @@ pub(super) mod _os { // We extract raw bytes and interpret as a native-endian integer. // Note: The value may differ across architectures due to endianness. let f_fsid = { - let ptr = std::ptr::addr_of!(st.f_fsid) as *const u8; - let size = std::mem::size_of_val(&st.f_fsid); + let ptr = core::ptr::addr_of!(st.f_fsid) as *const u8; + let size = core::mem::size_of_val(&st.f_fsid); if size >= 8 { - let bytes = unsafe { std::slice::from_raw_parts(ptr, 8) }; + let bytes = unsafe { core::slice::from_raw_parts(ptr, 8) }; u64::from_ne_bytes([ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], ]) as libc::c_ulong } else if size >= 4 { - let bytes = unsafe { std::slice::from_raw_parts(ptr, 4) }; + let bytes = unsafe { core::slice::from_raw_parts(ptr, 4) }; u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as libc::c_ulong } else { 0 @@ -1784,7 +1784,7 @@ pub(super) mod _os { #[pyfunction] #[pyfunction(name = "fstatvfs")] fn statvfs(path: OsPathOrFd<'_>, vm: &VirtualMachine) -> PyResult { - let mut st: libc::statvfs = unsafe { std::mem::zeroed() }; + let mut st: libc::statvfs = unsafe { core::mem::zeroed() }; let ret = match &path { OsPathOrFd::Path(p) => { let cpath = p.clone().into_cstring(vm)?; diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 4ec71eecbf2..65a659790b2 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -360,9 +360,9 @@ pub mod module { #[cfg(any(target_os = "macos", target_os = "ios"))] fn getgroups_impl() -> nix::Result<Vec<Gid>> { + use core::ptr; use libc::{c_int, gid_t}; use nix::errno::Errno; - use std::ptr; let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; let mut groups = Vec::<Gid>::with_capacity(Errno::result(ret)? as usize); let ret = unsafe { @@ -1825,7 +1825,7 @@ pub mod module { #[cfg(target_os = "macos")] #[pyfunction] fn _fcopyfile(in_fd: i32, out_fd: i32, flags: i32, vm: &VirtualMachine) -> PyResult<()> { - let ret = unsafe { fcopyfile(in_fd, out_fd, std::ptr::null_mut(), flags as u32) }; + let ret = unsafe { fcopyfile(in_fd, out_fd, core::ptr::null_mut(), flags as u32) }; if ret < 0 { Err(vm.new_last_errno_error()) } else { diff --git a/crates/vm/src/stdlib/thread.rs b/crates/vm/src/stdlib/thread.rs index 6ab754be094..eb881911659 100644 --- a/crates/vm/src/stdlib/thread.rs +++ b/crates/vm/src/stdlib/thread.rs @@ -192,7 +192,7 @@ pub(crate) mod _thread { #[derive(PyPayload)] struct RLock { mu: RawRMutex, - count: std::sync::atomic::AtomicUsize, + count: core::sync::atomic::AtomicUsize, } impl fmt::Debug for RLock { @@ -207,7 +207,7 @@ pub(crate) mod _thread { fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { Self { mu: RawRMutex::INIT, - count: std::sync::atomic::AtomicUsize::new(0), + count: core::sync::atomic::AtomicUsize::new(0), } .into_ref_with_type(vm, cls) .map(Into::into) @@ -220,7 +220,7 @@ pub(crate) mod _thread { let result = acquire_lock_impl!(&self.mu, args, vm)?; if result { self.count - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + .fetch_add(1, core::sync::atomic::Ordering::Relaxed); } Ok(result) } @@ -231,11 +231,11 @@ pub(crate) mod _thread { return Err(vm.new_runtime_error("release unlocked lock")); } debug_assert!( - self.count.load(std::sync::atomic::Ordering::Relaxed) > 0, + self.count.load(core::sync::atomic::Ordering::Relaxed) > 0, "RLock count underflow" ); self.count - .fetch_sub(1, std::sync::atomic::Ordering::Relaxed); + .fetch_sub(1, core::sync::atomic::Ordering::Relaxed); unsafe { self.mu.unlock() }; Ok(()) } @@ -247,7 +247,7 @@ pub(crate) mod _thread { self.mu.unlock(); }; } - self.count.store(0, std::sync::atomic::Ordering::Relaxed); + self.count.store(0, core::sync::atomic::Ordering::Relaxed); let new_mut = RawRMutex::INIT; let old_mutex: AtomicCell<&RawRMutex> = AtomicCell::new(&self.mu); @@ -264,7 +264,7 @@ pub(crate) mod _thread { #[pymethod] fn _recursion_count(&self) -> usize { if self.mu.is_owned_by_current_thread() { - self.count.load(std::sync::atomic::Ordering::Relaxed) + self.count.load(core::sync::atomic::Ordering::Relaxed) } else { 0 } diff --git a/crates/wasm/src/browser_module.rs b/crates/wasm/src/browser_module.rs index 0b978a4b563..9b6219cab6f 100644 --- a/crates/wasm/src/browser_module.rs +++ b/crates/wasm/src/browser_module.rs @@ -117,7 +117,8 @@ mod _browser { #[pyfunction] fn request_animation_frame(func: ArgCallable, vm: &VirtualMachine) -> PyResult { - use std::{cell::RefCell, rc::Rc}; + use alloc::rc::Rc; + use core::cell::RefCell; // this basic setup for request_animation_frame taken from: // https://rustwasm.github.io/wasm-bindgen/examples/request-animation-frame.html diff --git a/crates/wasm/src/js_module.rs b/crates/wasm/src/js_module.rs index d4f623da9f4..11d23bdf6d8 100644 --- a/crates/wasm/src/js_module.rs +++ b/crates/wasm/src/js_module.rs @@ -8,6 +8,7 @@ mod _js { vm_class::{WASMVirtualMachine, stored_vm_from_wasm}, weak_vm, }; + use core::{cell, fmt, future}; use js_sys::{Array, Object, Promise, Reflect}; use rustpython_vm::{ Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, @@ -17,7 +18,6 @@ mod _js { protocol::PyIterReturn, types::{IterNext, Representable, SelfIter}, }; - use std::{cell, fmt, future}; use wasm_bindgen::{JsCast, closure::Closure, prelude::*}; use wasm_bindgen_futures::{JsFuture, future_to_promise}; diff --git a/crates/wasm/src/lib.rs b/crates/wasm/src/lib.rs index 8d1d19ddae1..64595fd63ab 100644 --- a/crates/wasm/src/lib.rs +++ b/crates/wasm/src/lib.rs @@ -1,3 +1,5 @@ +extern crate alloc; + pub mod browser_module; pub mod convert; pub mod js_module; diff --git a/crates/wasm/src/vm_class.rs b/crates/wasm/src/vm_class.rs index ad5df8dab96..0339a47821e 100644 --- a/crates/wasm/src/vm_class.rs +++ b/crates/wasm/src/vm_class.rs @@ -3,6 +3,8 @@ use crate::{ convert::{self, PyResultExt}, js_module, wasm_builtins, }; +use alloc::rc::{Rc, Weak}; +use core::cell::RefCell; use js_sys::{Object, TypeError}; use rustpython_vm::{ Interpreter, PyObjectRef, PyPayload, PyRef, PyResult, Settings, VirtualMachine, @@ -10,11 +12,7 @@ use rustpython_vm::{ compiler::Mode, scope::Scope, }; -use std::{ - cell::RefCell, - collections::HashMap, - rc::{Rc, Weak}, -}; +use std::collections::HashMap; use wasm_bindgen::prelude::*; pub(crate) struct StoredVirtualMachine { From 6ed0b4f0acfc3ca2c7a46cdf54ada7281d1821e1 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:15:16 +0900 Subject: [PATCH 634/819] Add PythonFinallizationError and upgrade multiprocessing (#6592) * PythonFinallizationError * fix posixprocess * upgrade multiprocessing from 3.13.11 --- Lib/multiprocessing/connection.py | 2 +- Lib/multiprocessing/resource_tracker.py | 253 ++++++++++++++++-------- Lib/multiprocessing/util.py | 81 +++++++- crates/stdlib/src/posixsubprocess.rs | 12 ++ crates/vm/src/exceptions.rs | 13 ++ crates/vm/src/vm/vm_new.rs | 1 + 6 files changed, 283 insertions(+), 79 deletions(-) diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py index 8caddd204d7..abd88adf76e 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py @@ -74,7 +74,7 @@ def arbitrary_address(family): if family == 'AF_INET': return ('localhost', 0) elif family == 'AF_UNIX': - return tempfile.mktemp(prefix='listener-', dir=util.get_temp_dir()) + return tempfile.mktemp(prefix='sock-', dir=util.get_temp_dir()) elif family == 'AF_PIPE': return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' % (os.getpid(), next(_mmap_counter)), dir="") diff --git a/Lib/multiprocessing/resource_tracker.py b/Lib/multiprocessing/resource_tracker.py index 05633ac21a2..22e3bbcf21b 100644 --- a/Lib/multiprocessing/resource_tracker.py +++ b/Lib/multiprocessing/resource_tracker.py @@ -15,11 +15,15 @@ # this resource tracker process, "killall python" would probably leave unlinked # resources. +import base64 import os import signal import sys import threading import warnings +from collections import deque + +import json from . import spawn from . import util @@ -66,6 +70,14 @@ def __init__(self): self._fd = None self._pid = None self._exitcode = None + self._reentrant_messages = deque() + + # True to use colon-separated lines, rather than JSON lines, + # for internal communication. (Mainly for testing). + # Filenames not supported by the simple format will always be sent + # using JSON. + # The reader should understand all formats. + self._use_simple_format = True def _reentrant_call_error(self): # gh-109629: this happens if an explicit call to the ResourceTracker @@ -102,7 +114,7 @@ def _stop_locked( # This shouldn't happen (it might when called by a finalizer) # so we check for it anyway. if self._lock._recursion_count() > 1: - return self._reentrant_call_error() + raise self._reentrant_call_error() if self._fd is None: # not running return @@ -113,7 +125,12 @@ def _stop_locked( close(self._fd) self._fd = None - _, status = waitpid(self._pid, 0) + try: + _, status = waitpid(self._pid, 0) + except ChildProcessError: + self._pid = None + self._exitcode = None + return self._pid = None @@ -132,76 +149,119 @@ def ensure_running(self): This can be run from any process. Usually a child process will use the resource created by its parent.''' + return self._ensure_running_and_write() + + def _teardown_dead_process(self): + os.close(self._fd) + + # Clean-up to avoid dangling processes. + try: + # _pid can be None if this process is a child from another + # python process, which has started the resource_tracker. + if self._pid is not None: + os.waitpid(self._pid, 0) + except ChildProcessError: + # The resource_tracker has already been terminated. + pass + self._fd = None + self._pid = None + self._exitcode = None + + warnings.warn('resource_tracker: process died unexpectedly, ' + 'relaunching. Some resources might leak.') + + def _launch(self): + fds_to_pass = [] + try: + fds_to_pass.append(sys.stderr.fileno()) + except Exception: + pass + r, w = os.pipe() + try: + fds_to_pass.append(r) + # process will out live us, so no need to wait on pid + exe = spawn.get_executable() + args = [ + exe, + *util._args_from_interpreter_flags(), + '-c', + f'from multiprocessing.resource_tracker import main;main({r})', + ] + # bpo-33613: Register a signal mask that will block the signals. + # This signal mask will be inherited by the child that is going + # to be spawned and will protect the child from a race condition + # that can make the child die before it registers signal handlers + # for SIGINT and SIGTERM. The mask is unregistered after spawning + # the child. + prev_sigmask = None + try: + if _HAVE_SIGMASK: + prev_sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS) + pid = util.spawnv_passfds(exe, args, fds_to_pass) + finally: + if prev_sigmask is not None: + signal.pthread_sigmask(signal.SIG_SETMASK, prev_sigmask) + except: + os.close(w) + raise + else: + self._fd = w + self._pid = pid + finally: + os.close(r) + + def _make_probe_message(self): + """Return a probe message.""" + if self._use_simple_format: + return b'PROBE:0:noop\n' + return ( + json.dumps( + {"cmd": "PROBE", "rtype": "noop"}, + ensure_ascii=True, + separators=(",", ":"), + ) + + "\n" + ).encode("ascii") + + def _ensure_running_and_write(self, msg=None): with self._lock: if self._lock._recursion_count() > 1: # The code below is certainly not reentrant-safe, so bail out - return self._reentrant_call_error() + if msg is None: + raise self._reentrant_call_error() + return self._reentrant_messages.append(msg) + if self._fd is not None: # resource tracker was launched before, is it still running? - if self._check_alive(): - # => still alive - return - # => dead, launch it again - os.close(self._fd) - - # Clean-up to avoid dangling processes. + if msg is None: + to_send = self._make_probe_message() + else: + to_send = msg try: - # _pid can be None if this process is a child from another - # python process, which has started the resource_tracker. - if self._pid is not None: - os.waitpid(self._pid, 0) - except ChildProcessError: - # The resource_tracker has already been terminated. - pass - self._fd = None - self._pid = None - self._exitcode = None + self._write(to_send) + except OSError: + self._teardown_dead_process() + self._launch() - warnings.warn('resource_tracker: process died unexpectedly, ' - 'relaunching. Some resources might leak.') + msg = None # message was sent in probe + else: + self._launch() - fds_to_pass = [] - try: - fds_to_pass.append(sys.stderr.fileno()) - except Exception: - pass - cmd = 'from multiprocessing.resource_tracker import main;main(%d)' - r, w = os.pipe() + while True: try: - fds_to_pass.append(r) - # process will out live us, so no need to wait on pid - exe = spawn.get_executable() - args = [exe] + util._args_from_interpreter_flags() - args += ['-c', cmd % r] - # bpo-33613: Register a signal mask that will block the signals. - # This signal mask will be inherited by the child that is going - # to be spawned and will protect the child from a race condition - # that can make the child die before it registers signal handlers - # for SIGINT and SIGTERM. The mask is unregistered after spawning - # the child. - prev_sigmask = None - try: - if _HAVE_SIGMASK: - prev_sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS) - pid = util.spawnv_passfds(exe, args, fds_to_pass) - finally: - if prev_sigmask is not None: - signal.pthread_sigmask(signal.SIG_SETMASK, prev_sigmask) - except: - os.close(w) - raise - else: - self._fd = w - self._pid = pid - finally: - os.close(r) + reentrant_msg = self._reentrant_messages.popleft() + except IndexError: + break + self._write(reentrant_msg) + if msg is not None: + self._write(msg) def _check_alive(self): '''Check that the pipe has not been closed by sending a probe.''' try: # We cannot use send here as it calls ensure_running, creating # a cycle. - os.write(self._fd, b'PROBE:0:noop\n') + os.write(self._fd, self._make_probe_message()) except OSError: return False else: @@ -215,27 +275,42 @@ def unregister(self, name, rtype): '''Unregister name of resource with resource tracker.''' self._send('UNREGISTER', name, rtype) - def _send(self, cmd, name, rtype): - try: - self.ensure_running() - except ReentrantCallError: - # The code below might or might not work, depending on whether - # the resource tracker was already running and still alive. - # Better warn the user. - # (XXX is warnings.warn itself reentrant-safe? :-) - warnings.warn( - f"ResourceTracker called reentrantly for resource cleanup, " - f"which is unsupported. " - f"The {rtype} object {name!r} might leak.") - msg = '{0}:{1}:{2}\n'.format(cmd, name, rtype).encode('ascii') - if len(msg) > 512: - # posix guarantees that writes to a pipe of less than PIPE_BUF - # bytes are atomic, and that PIPE_BUF >= 512 - raise ValueError('msg too long') + def _write(self, msg): nbytes = os.write(self._fd, msg) - assert nbytes == len(msg), "nbytes {0:n} but len(msg) {1:n}".format( - nbytes, len(msg)) + assert nbytes == len(msg), f"{nbytes=} != {len(msg)=}" + def _send(self, cmd, name, rtype): + if self._use_simple_format and '\n' not in name: + msg = f"{cmd}:{name}:{rtype}\n".encode("ascii") + if len(msg) > 512: + # posix guarantees that writes to a pipe of less than PIPE_BUF + # bytes are atomic, and that PIPE_BUF >= 512 + raise ValueError('msg too long') + self._ensure_running_and_write(msg) + return + + # POSIX guarantees that writes to a pipe of less than PIPE_BUF (512 on Linux) + # bytes are atomic. Therefore, we want the message to be shorter than 512 bytes. + # POSIX shm_open() and sem_open() require the name, including its leading slash, + # to be at most NAME_MAX bytes (255 on Linux) + # With json.dump(..., ensure_ascii=True) every non-ASCII byte becomes a 6-char + # escape like \uDC80. + # As we want the overall message to be kept atomic and therefore smaller than 512, + # we encode encode the raw name bytes with URL-safe Base64 - so a 255 long name + # will not exceed 340 bytes. + b = name.encode('utf-8', 'surrogateescape') + if len(b) > 255: + raise ValueError('shared memory name too long (max 255 bytes)') + b64 = base64.urlsafe_b64encode(b).decode('ascii') + + payload = {"cmd": cmd, "rtype": rtype, "base64_name": b64} + msg = (json.dumps(payload, ensure_ascii=True, separators=(",", ":")) + "\n").encode("ascii") + + # The entire JSON message is guaranteed < PIPE_BUF (512 bytes) by construction. + assert len(msg) <= 512, f"internal error: message too long ({len(msg)} bytes)" + assert msg.startswith(b'{') + + self._ensure_running_and_write(msg) _resource_tracker = ResourceTracker() ensure_running = _resource_tracker.ensure_running @@ -244,6 +319,30 @@ def _send(self, cmd, name, rtype): getfd = _resource_tracker.getfd +def _decode_message(line): + if line.startswith(b'{'): + try: + obj = json.loads(line.decode('ascii')) + except Exception as e: + raise ValueError("malformed resource_tracker message: %r" % (line,)) from e + + cmd = obj["cmd"] + rtype = obj["rtype"] + b64 = obj.get("base64_name", "") + + if not isinstance(cmd, str) or not isinstance(rtype, str) or not isinstance(b64, str): + raise ValueError("malformed resource_tracker fields: %r" % (obj,)) + + try: + name = base64.urlsafe_b64decode(b64).decode('utf-8', 'surrogateescape') + except ValueError as e: + raise ValueError("malformed resource_tracker base64_name: %r" % (b64,)) from e + else: + cmd, rest = line.strip().decode('ascii').split(':', maxsplit=1) + name, rtype = rest.rsplit(':', maxsplit=1) + return cmd, rtype, name + + def main(fd): '''Run resource tracker.''' # protect the process from ^C and "killall python" etc @@ -266,7 +365,7 @@ def main(fd): with open(fd, 'rb') as f: for line in f: try: - cmd, name, rtype = line.strip().decode('ascii').split(':') + cmd, rtype, name = _decode_message(line) cleanup_func = _CLEANUP_FUNCS.get(rtype, None) if cleanup_func is None: raise ValueError( diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py index 75dde02d88c..b8bfea045df 100644 --- a/Lib/multiprocessing/util.py +++ b/Lib/multiprocessing/util.py @@ -34,6 +34,7 @@ DEBUG = 10 INFO = 20 SUBWARNING = 25 +WARNING = 30 LOGGER_NAME = 'multiprocessing' DEFAULT_LOGGING_FORMAT = '[%(levelname)s/%(processName)s] %(message)s' @@ -53,6 +54,10 @@ def info(msg, *args): if _logger: _logger.log(INFO, msg, *args, stacklevel=2) +def _warn(msg, *args): + if _logger: + _logger.log(WARNING, msg, *args, stacklevel=2) + def sub_warning(msg, *args): if _logger: _logger.log(SUBWARNING, msg, *args, stacklevel=2) @@ -121,6 +126,23 @@ def is_abstract_socket_namespace(address): # Function returning a temp directory which will be removed on exit # +# Maximum length of a NULL-terminated [1] socket file path is usually +# between 92 and 108 [2], but Linux is known to use a size of 108 [3]. +# BSD-based systems usually use a size of 104 or 108 and Windows does +# not create AF_UNIX sockets. +# +# [1]: https://github.com/python/cpython/issues/140734 +# [2]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_un.h.html +# [3]: https://man7.org/linux/man-pages/man7/unix.7.html + +if sys.platform == 'linux': + _SUN_PATH_MAX = 108 +elif sys.platform.startswith(('openbsd', 'freebsd')): + _SUN_PATH_MAX = 104 +else: + # On Windows platforms, we do not create AF_UNIX sockets. + _SUN_PATH_MAX = None if os.name == 'nt' else 92 + def _remove_temp_dir(rmtree, tempdir): rmtree(tempdir) @@ -130,12 +152,69 @@ def _remove_temp_dir(rmtree, tempdir): if current_process is not None: current_process._config['tempdir'] = None +def _get_base_temp_dir(tempfile): + """Get a temporary directory where socket files will be created. + + To prevent additional imports, pass a pre-imported 'tempfile' module. + """ + if os.name == 'nt': + return None + # Most of the time, the default temporary directory is /tmp. Thus, + # listener sockets files "$TMPDIR/pymp-XXXXXXXX/sock-XXXXXXXX" do + # not have a path length exceeding SUN_PATH_MAX. + # + # If users specify their own temporary directory, we may be unable + # to create those files. Therefore, we fall back to the system-wide + # temporary directory /tmp, assumed to exist on POSIX systems. + # + # See https://github.com/python/cpython/issues/132124. + base_tempdir = tempfile.gettempdir() + # Files created in a temporary directory are suffixed by a string + # generated by tempfile._RandomNameSequence, which, by design, + # is 8 characters long. + # + # Thus, the socket file path length (without NULL terminator) will be: + # + # len(base_tempdir + '/pymp-XXXXXXXX' + '/sock-XXXXXXXX') + sun_path_len = len(base_tempdir) + 14 + 14 + # Strict inequality to account for the NULL terminator. + # See https://github.com/python/cpython/issues/140734. + if sun_path_len < _SUN_PATH_MAX: + return base_tempdir + # Fallback to the default system-wide temporary directory. + # This ignores user-defined environment variables. + # + # On POSIX systems, /tmp MUST be writable by any application [1]. + # We however emit a warning if this is not the case to prevent + # obscure errors later in the execution. + # + # On some legacy systems, /var/tmp and /usr/tmp can be present + # and will be used instead. + # + # [1]: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03s18.html + dirlist = ['/tmp', '/var/tmp', '/usr/tmp'] + try: + base_system_tempdir = tempfile._get_default_tempdir(dirlist) + except FileNotFoundError: + _warn("Process-wide temporary directory %s will not be usable for " + "creating socket files and no usable system-wide temporary " + "directory was found in %s", base_tempdir, dirlist) + # At this point, the system-wide temporary directory is not usable + # but we may assume that the user-defined one is, even if we will + # not be able to write socket files out there. + return base_tempdir + _warn("Ignoring user-defined temporary directory: %s", base_tempdir) + # at most max(map(len, dirlist)) + 14 + 14 = 36 characters + assert len(base_system_tempdir) + 14 + 14 < _SUN_PATH_MAX + return base_system_tempdir + def get_temp_dir(): # get name of a temp directory which will be automatically cleaned up tempdir = process.current_process()._config.get('tempdir') if tempdir is None: import shutil, tempfile - tempdir = tempfile.mkdtemp(prefix='pymp-') + base_tempdir = _get_base_temp_dir(tempfile) + tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir) info('created temp directory %s', tempdir) # keep a strong reference to shutil.rmtree(), since the finalizer # can be called late during Python shutdown diff --git a/crates/stdlib/src/posixsubprocess.rs b/crates/stdlib/src/posixsubprocess.rs index 1ebf6619c24..a6badc081d2 100644 --- a/crates/stdlib/src/posixsubprocess.rs +++ b/crates/stdlib/src/posixsubprocess.rs @@ -33,6 +33,18 @@ mod _posixsubprocess { #[pyfunction] fn fork_exec(args: ForkExecArgs<'_>, vm: &VirtualMachine) -> PyResult<libc::pid_t> { + // Check for interpreter shutdown when preexec_fn is used + if args.preexec_fn.is_some() + && vm + .state + .finalizing + .load(std::sync::atomic::Ordering::Acquire) + { + return Err(vm.new_python_finalization_error( + "preexec_fn not supported at interpreter shutdown".to_owned(), + )); + } + let extra_groups = args .groups_list .as_ref() diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index b1a654d58c4..5d0c1c6f3ed 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -493,6 +493,7 @@ pub struct ExceptionZoo { pub runtime_error: &'static Py<PyType>, pub not_implemented_error: &'static Py<PyType>, pub recursion_error: &'static Py<PyType>, + pub python_finalization_error: &'static Py<PyType>, pub syntax_error: &'static Py<PyType>, pub incomplete_input_error: &'static Py<PyType>, pub indentation_error: &'static Py<PyType>, @@ -804,6 +805,7 @@ impl ExceptionZoo { let runtime_error = PyRuntimeError::init_builtin_type(); let not_implemented_error = PyNotImplementedError::init_builtin_type(); let recursion_error = PyRecursionError::init_builtin_type(); + let python_finalization_error = PyPythonFinalizationError::init_builtin_type(); let syntax_error = PySyntaxError::init_builtin_type(); let incomplete_input_error = PyIncompleteInputError::init_builtin_type(); @@ -879,6 +881,7 @@ impl ExceptionZoo { runtime_error, not_implemented_error, recursion_error, + python_finalization_error, syntax_error, incomplete_input_error, indentation_error, @@ -992,6 +995,11 @@ impl ExceptionZoo { extend_exception!(PyRuntimeError, ctx, excs.runtime_error); extend_exception!(PyNotImplementedError, ctx, excs.not_implemented_error); extend_exception!(PyRecursionError, ctx, excs.recursion_error); + extend_exception!( + PyPythonFinalizationError, + ctx, + excs.python_finalization_error + ); extend_exception!(PySyntaxError, ctx, excs.syntax_error, { "msg" => ctx.new_static_getset( @@ -2111,6 +2119,11 @@ pub(super) mod types { #[repr(transparent)] pub struct PyRecursionError(PyRuntimeError); + #[pyexception(name, base = PyRuntimeError, ctx = "python_finalization_error", impl)] + #[derive(Debug)] + #[repr(transparent)] + pub struct PyPythonFinalizationError(PyRuntimeError); + #[pyexception(name, base = PyException, ctx = "syntax_error")] #[derive(Debug)] #[repr(transparent)] diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index ba09c8ecf69..4f25d6e2036 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -600,5 +600,6 @@ impl VirtualMachine { define_exception_fn!(fn new_zero_division_error, zero_division_error, ZeroDivisionError); define_exception_fn!(fn new_overflow_error, overflow_error, OverflowError); define_exception_fn!(fn new_runtime_error, runtime_error, RuntimeError); + define_exception_fn!(fn new_python_finalization_error, python_finalization_error, PythonFinalizationError); define_exception_fn!(fn new_memory_error, memory_error, MemoryError); } From 6bd1d90b6a8f010ddac7a047b3023cd93e151a9b Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Tue, 30 Dec 2025 02:35:58 -0500 Subject: [PATCH 635/819] Updated bdb.py + test_bdb.py (#6593) * Updated bdb.py + test_bdb.py * Double checked test_bdb.py with the script --- Lib/bdb.py | 148 +++++++++++++++++++++++++++++++++---------- Lib/test/test_bdb.py | 95 +++++++++++++++++---------- 2 files changed, 177 insertions(+), 66 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 0f3eec653ba..f256b56daaa 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -3,6 +3,7 @@ import fnmatch import sys import os +from contextlib import contextmanager from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR __all__ = ["BdbQuit", "Bdb", "Breakpoint"] @@ -32,7 +33,12 @@ def __init__(self, skip=None): self.skip = set(skip) if skip else None self.breaks = {} self.fncache = {} + self.frame_trace_lines_opcodes = {} self.frame_returning = None + self.trace_opcodes = False + self.enterframe = None + self.cmdframe = None + self.cmdlineno = None self._load_breaks() @@ -60,6 +66,12 @@ def reset(self): self.botframe = None self._set_stopinfo(None, None) + @contextmanager + def set_enterframe(self, frame): + self.enterframe = frame + yield + self.enterframe = None + def trace_dispatch(self, frame, event, arg): """Dispatch a trace function for debugged frames based on the event. @@ -84,24 +96,28 @@ def trace_dispatch(self, frame, event, arg): The arg parameter depends on the previous event. """ - if self.quitting: - return # None - if event == 'line': - return self.dispatch_line(frame) - if event == 'call': - return self.dispatch_call(frame, arg) - if event == 'return': - return self.dispatch_return(frame, arg) - if event == 'exception': - return self.dispatch_exception(frame, arg) - if event == 'c_call': - return self.trace_dispatch - if event == 'c_exception': - return self.trace_dispatch - if event == 'c_return': + + with self.set_enterframe(frame): + if self.quitting: + return # None + if event == 'line': + return self.dispatch_line(frame) + if event == 'call': + return self.dispatch_call(frame, arg) + if event == 'return': + return self.dispatch_return(frame, arg) + if event == 'exception': + return self.dispatch_exception(frame, arg) + if event == 'c_call': + return self.trace_dispatch + if event == 'c_exception': + return self.trace_dispatch + if event == 'c_return': + return self.trace_dispatch + if event == 'opcode': + return self.dispatch_opcode(frame, arg) + print('bdb.Bdb.dispatch: unknown debugging event:', repr(event)) return self.trace_dispatch - print('bdb.Bdb.dispatch: unknown debugging event:', repr(event)) - return self.trace_dispatch def dispatch_line(self, frame): """Invoke user function and return trace function for line event. @@ -110,7 +126,12 @@ def dispatch_line(self, frame): self.user_line(). Raise BdbQuit if self.quitting is set. Return self.trace_dispatch to continue tracing in this scope. """ - if self.stop_here(frame) or self.break_here(frame): + # GH-136057 + # For line events, we don't want to stop at the same line where + # the latest next/step command was issued. + if (self.stop_here(frame) or self.break_here(frame)) and not ( + self.cmdframe == frame and self.cmdlineno == frame.f_lineno + ): self.user_line(frame) if self.quitting: raise BdbQuit return self.trace_dispatch @@ -157,6 +178,11 @@ def dispatch_return(self, frame, arg): # The user issued a 'next' or 'until' command. if self.stopframe is frame and self.stoplineno != -1: self._set_stopinfo(None, None) + # The previous frame might not have f_trace set, unless we are + # issuing a command that does not expect to stop, we should set + # f_trace + if self.stoplineno != -1: + self._set_caller_tracefunc(frame) return self.trace_dispatch def dispatch_exception(self, frame, arg): @@ -186,6 +212,17 @@ def dispatch_exception(self, frame, arg): return self.trace_dispatch + def dispatch_opcode(self, frame, arg): + """Invoke user function and return trace function for opcode event. + If the debugger stops on the current opcode, invoke + self.user_opcode(). Raise BdbQuit if self.quitting is set. + Return self.trace_dispatch to continue tracing in this scope. + """ + if self.stop_here(frame) or self.break_here(frame): + self.user_opcode(frame) + if self.quitting: raise BdbQuit + return self.trace_dispatch + # Normally derived classes don't override the following # methods, but they may if they want to redefine the # definition of stopping and breakpoints. @@ -272,7 +309,22 @@ def user_exception(self, frame, exc_info): """Called when we stop on an exception.""" pass - def _set_stopinfo(self, stopframe, returnframe, stoplineno=0): + def user_opcode(self, frame): + """Called when we are about to execute an opcode.""" + pass + + def _set_trace_opcodes(self, trace_opcodes): + if trace_opcodes != self.trace_opcodes: + self.trace_opcodes = trace_opcodes + frame = self.enterframe + while frame is not None: + frame.f_trace_opcodes = trace_opcodes + if frame is self.botframe: + break + frame = frame.f_back + + def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False, + cmdframe=None, cmdlineno=None): """Set the attributes for stopping. If stoplineno is greater than or equal to 0, then stop at line @@ -285,6 +337,21 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0): # stoplineno >= 0 means: stop at line >= the stoplineno # stoplineno -1 means: don't stop at all self.stoplineno = stoplineno + # cmdframe/cmdlineno is the frame/line number when the user issued + # step/next commands. + self.cmdframe = cmdframe + self.cmdlineno = cmdlineno + self._set_trace_opcodes(opcode) + + def _set_caller_tracefunc(self, current_frame): + # Issue #13183: pdb skips frames after hitting a breakpoint and running + # step commands. + # Restore the trace function in the caller (that may not have been set + # for performance reasons) when returning from the current frame, unless + # the caller is the botframe. + caller_frame = current_frame.f_back + if caller_frame and not caller_frame.f_trace and caller_frame is not self.botframe: + caller_frame.f_trace = self.trace_dispatch # Derived classes and clients can call the following methods # to affect the stepping state. @@ -299,19 +366,17 @@ def set_until(self, frame, lineno=None): def set_step(self): """Stop after one line of code.""" - # Issue #13183: pdb skips frames after hitting a breakpoint and running - # step commands. - # Restore the trace function in the caller (that may not have been set - # for performance reasons) when returning from the current frame. - if self.frame_returning: - caller_frame = self.frame_returning.f_back - if caller_frame and not caller_frame.f_trace: - caller_frame.f_trace = self.trace_dispatch - self._set_stopinfo(None, None) + # set_step() could be called from signal handler so enterframe might be None + self._set_stopinfo(None, None, cmdframe=self.enterframe, + cmdlineno=getattr(self.enterframe, 'f_lineno', None)) + + def set_stepinstr(self): + """Stop before the next instruction.""" + self._set_stopinfo(None, None, opcode=True) def set_next(self, frame): """Stop on the next line in or below the given frame.""" - self._set_stopinfo(frame, None) + self._set_stopinfo(frame, None, cmdframe=frame, cmdlineno=frame.f_lineno) def set_return(self, frame): """Stop when returning from the given frame.""" @@ -328,11 +393,15 @@ def set_trace(self, frame=None): if frame is None: frame = sys._getframe().f_back self.reset() - while frame: - frame.f_trace = self.trace_dispatch - self.botframe = frame - frame = frame.f_back - self.set_step() + with self.set_enterframe(frame): + while frame: + frame.f_trace = self.trace_dispatch + self.botframe = frame + self.frame_trace_lines_opcodes[frame] = (frame.f_trace_lines, frame.f_trace_opcodes) + # We need f_trace_lines == True for the debugger to work + frame.f_trace_lines = True + frame = frame.f_back + self.set_stepinstr() sys.settrace(self.trace_dispatch) def set_continue(self): @@ -349,6 +418,9 @@ def set_continue(self): while frame and frame is not self.botframe: del frame.f_trace frame = frame.f_back + for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items(): + frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes + self.frame_trace_lines_opcodes = {} def set_quit(self): """Set quitting attribute to True. @@ -387,6 +459,14 @@ def set_break(self, filename, lineno, temporary=False, cond=None, return 'Line %s:%d does not exist' % (filename, lineno) self._add_to_breaks(filename, lineno) bp = Breakpoint(filename, lineno, temporary, cond, funcname) + # After we set a new breakpoint, we need to search through all frames + # and set f_trace to trace_dispatch if there could be a breakpoint in + # that frame. + frame = self.enterframe + while frame: + if self.break_anywhere(frame): + frame.f_trace = self.trace_dispatch + frame = frame.f_back return None def _load_breaks(self): diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py index a3abbbb8db2..d1c1c786861 100644 --- a/Lib/test/test_bdb.py +++ b/Lib/test/test_bdb.py @@ -228,6 +228,10 @@ def user_exception(self, frame, exc_info): self.process_event('exception', frame) self.next_set_method() + def user_opcode(self, frame): + self.process_event('opcode', frame) + self.next_set_method() + def do_clear(self, arg): # The temporary breakpoints are deleted in user_line(). bp_list = [self.currentbp] @@ -366,7 +370,7 @@ def next_set_method(self): set_method = getattr(self, 'set_' + set_type) # The following set methods give back control to the tracer. - if set_type in ('step', 'continue', 'quit'): + if set_type in ('step', 'stepinstr', 'continue', 'quit'): set_method() return elif set_type in ('next', 'return'): @@ -586,7 +590,7 @@ def fail(self, msg=None): class StateTestCase(BaseTestCase): """Test the step, next, return, until and quit 'set_' methods.""" - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_step(self): self.expect_set = [ ('line', 2, 'tfunc_main'), ('step', ), @@ -597,7 +601,7 @@ def test_step(self): with TracerRun(self) as tracer: tracer.runcall(tfunc_main) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_step_next_on_last_statement(self): for set_type in ('step', 'next'): with self.subTest(set_type=set_type): @@ -612,7 +616,18 @@ def test_step_next_on_last_statement(self): with TracerRun(self) as tracer: tracer.runcall(tfunc_main) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON') + # AssertionError: All paired tuples have not been processed, the last one was number 1 [('next',), ('quit',)] + def test_stepinstr(self): + self.expect_set = [ + ('line', 2, 'tfunc_main'), ('stepinstr', ), + ('opcode', 2, 'tfunc_main'), ('next', ), + ('line', 3, 'tfunc_main'), ('quit', ), + ] + with TracerRun(self) as tracer: + tracer.runcall(tfunc_main) + + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_next(self): self.expect_set = [ ('line', 2, 'tfunc_main'), ('step', ), @@ -624,7 +639,7 @@ def test_next(self): with TracerRun(self) as tracer: tracer.runcall(tfunc_main) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_next_over_import(self): code = """ def main(): @@ -639,7 +654,7 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_next_on_plain_statement(self): # Check that set_next() is equivalent to set_step() on a plain # statement. @@ -652,7 +667,7 @@ def test_next_on_plain_statement(self): with TracerRun(self) as tracer: tracer.runcall(tfunc_main) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_next_in_caller_frame(self): # Check that set_next() in the caller frame causes the tracer # to stop next in the caller frame. @@ -666,7 +681,7 @@ def test_next_in_caller_frame(self): with TracerRun(self) as tracer: tracer.runcall(tfunc_main) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_return(self): self.expect_set = [ ('line', 2, 'tfunc_main'), ('step', ), @@ -679,7 +694,7 @@ def test_return(self): with TracerRun(self) as tracer: tracer.runcall(tfunc_main) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_return_in_caller_frame(self): self.expect_set = [ ('line', 2, 'tfunc_main'), ('step', ), @@ -691,7 +706,7 @@ def test_return_in_caller_frame(self): with TracerRun(self) as tracer: tracer.runcall(tfunc_main) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_until(self): self.expect_set = [ ('line', 2, 'tfunc_main'), ('step', ), @@ -703,7 +718,7 @@ def test_until(self): with TracerRun(self) as tracer: tracer.runcall(tfunc_main) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_until_with_too_large_count(self): self.expect_set = [ ('line', 2, 'tfunc_main'), break_in_func('tfunc_first'), @@ -714,7 +729,7 @@ def test_until_with_too_large_count(self): with TracerRun(self) as tracer: tracer.runcall(tfunc_main) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_until_in_caller_frame(self): self.expect_set = [ ('line', 2, 'tfunc_main'), ('step', ), @@ -726,7 +741,8 @@ def test_until_in_caller_frame(self): with TracerRun(self) as tracer: tracer.runcall(tfunc_main) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') + @patch_list(sys.meta_path) def test_skip(self): # Check that tracing is skipped over the import statement in # 'tfunc_import()'. @@ -759,7 +775,7 @@ def test_skip_with_no_name_module(self): bdb = Bdb(skip=['anything*']) self.assertIs(bdb.is_skipped_module(None), False) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_down(self): # Check that set_down() raises BdbError at the newest frame. self.expect_set = [ @@ -768,7 +784,7 @@ def test_down(self): with TracerRun(self) as tracer: self.assertRaises(BdbError, tracer.runcall, tfunc_main) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_up(self): self.expect_set = [ ('line', 2, 'tfunc_main'), ('step', ), @@ -782,7 +798,7 @@ def test_up(self): class BreakpointTestCase(BaseTestCase): """Test the breakpoint set method.""" - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_bp_on_non_existent_module(self): self.expect_set = [ ('line', 2, 'tfunc_import'), ('break', ('/non/existent/module.py', 1)) @@ -790,7 +806,7 @@ def test_bp_on_non_existent_module(self): with TracerRun(self) as tracer: self.assertRaises(BdbError, tracer.runcall, tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_bp_after_last_statement(self): code = """ def main(): @@ -804,7 +820,7 @@ def main(): with TracerRun(self) as tracer: self.assertRaises(BdbError, tracer.runcall, tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_temporary_bp(self): code = """ def func(): @@ -828,7 +844,7 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_disabled_temporary_bp(self): code = """ def func(): @@ -857,7 +873,7 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_bp_condition(self): code = """ def func(a): @@ -878,7 +894,7 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_bp_exception_on_condition_evaluation(self): code = """ def func(a): @@ -898,7 +914,7 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_bp_ignore_count(self): code = """ def func(): @@ -920,7 +936,7 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_ignore_count_on_disabled_bp(self): code = """ def func(): @@ -948,7 +964,7 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_clear_two_bp_on_same_line(self): code = """ def func(): @@ -974,7 +990,7 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_clear_at_no_bp(self): self.expect_set = [ ('line', 2, 'tfunc_import'), ('clear', (__file__, 1)) @@ -1028,7 +1044,7 @@ def test_load_bps_from_previous_Bdb_instance(self): class RunTestCase(BaseTestCase): """Test run, runeval and set_trace.""" - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_run_step(self): # Check that the bdb 'run' method stops at the first line event. code = """ @@ -1041,7 +1057,7 @@ def test_run_step(self): with TracerRun(self) as tracer: tracer.run(compile(textwrap.dedent(code), '<string>', 'exec')) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_runeval_step(self): # Test bdb 'runeval'. code = """ @@ -1064,7 +1080,7 @@ def main(): class IssuesTestCase(BaseTestCase): """Test fixed bdb issues.""" - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_step_at_return_with_no_trace_in_caller(self): # Issue #13183. # Check that the tracer does step into the caller frame when the @@ -1095,7 +1111,7 @@ def func(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_next_until_return_in_generator(self): # Issue #16596. # Check that set_next(), set_until() and set_return() do not treat the @@ -1137,7 +1153,7 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_next_command_in_generator_for_loop(self): # Issue #16596. code = """ @@ -1169,7 +1185,7 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_next_command_in_generator_with_subiterator(self): # Issue #16596. code = """ @@ -1201,7 +1217,7 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) - @unittest.skip("TODO: RUSTPYTHON, Error in atexit._run_exitfuncs") + @unittest.skip('TODO: RUSTPYTHON; Error in atexit._run_exitfuncs') def test_return_command_in_generator_with_subiterator(self): # Issue #16596. code = """ @@ -1233,6 +1249,21 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) + @unittest.skip('TODO: RUSTPYTHON') + # AssertionError: All paired tuples have not been processed, the last one was number 1 [('next',)] + def test_next_to_botframe(self): + # gh-125422 + # Check that next command won't go to the bottom frame. + code = """ + lno = 2 + """ + self.expect_set = [ + ('line', 2, '<module>'), ('step', ), + ('return', 2, '<module>'), ('next', ), + ] + with TracerRun(self) as tracer: + tracer.run(compile(textwrap.dedent(code), '<string>', 'exec')) + class TestRegressions(unittest.TestCase): def test_format_stack_entry_no_lineno(self): From e79a1a1a6665565530bc143a305496b5cefed92e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 30 Dec 2025 16:58:53 +0900 Subject: [PATCH 636/819] Fix traceback (#6569) * Fix traceback * Update traceback from CPython 3.13.11 * unmark test_traceback * fix code * fix debug range * fix tests --- .cspell.dict/python-more.txt | 1 + Lib/test/test_code.py | 4 - Lib/test/test_traceback.py | 110 --- Lib/test/test_zipimport.py | 2 + Lib/traceback.py | 1198 +++++++++++++++++++++++--- crates/codegen/src/compile.rs | 30 +- crates/codegen/src/ir.rs | 87 +- crates/compiler-core/src/bytecode.rs | 6 +- crates/compiler-core/src/marshal.rs | 19 +- crates/vm/src/builtins/code.rs | 21 +- crates/vm/src/builtins/frame.rs | 11 +- crates/vm/src/frame.rs | 6 +- crates/vm/src/vm/mod.rs | 1 + crates/vm/src/vm/setting.rs | 4 +- examples/dis.rs | 5 +- src/settings.rs | 4 + 16 files changed, 1202 insertions(+), 307 deletions(-) diff --git a/.cspell.dict/python-more.txt b/.cspell.dict/python-more.txt index d381bfe1e03..1f3fc4864cd 100644 --- a/.cspell.dict/python-more.txt +++ b/.cspell.dict/python-more.txt @@ -178,6 +178,7 @@ PYTHONHASHSEED PYTHONHOME PYTHONINSPECT PYTHONINTMAXSTRDIGITS +PYTHONNODEBUGRANGES PYTHONNOUSERSITE PYTHONOPTIMIZE PYTHONPATH diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 804cce1dba4..f2ef233a59a 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -425,8 +425,6 @@ def test_co_positions_artificial_instructions(self): ] ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_endline_and_columntable_none_when_no_debug_ranges(self): # Make sure that if `-X no_debug_ranges` is used, there is # minimal debug info @@ -442,8 +440,6 @@ def f(): """) assert_python_ok('-X', 'no_debug_ranges', '-c', code) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_endline_and_columntable_none_when_no_debug_ranges_env(self): # Same as above but using the environment variable opt out. code = textwrap.dedent(""" diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 9d95903d526..f2ec8344b2f 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -218,8 +218,6 @@ def test_base_exception(self): lst = traceback.format_exception_only(e.__class__, e) self.assertEqual(lst, ['KeyboardInterrupt\n']) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_only_bad__str__(self): class X(Exception): def __str__(self): @@ -238,8 +236,6 @@ def test_format_exception_group_without_show_group(self): err = traceback.format_exception_only(eg) self.assertEqual(err, ['ExceptionGroup: A (1 sub-exception)\n']) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_group(self): eg = ExceptionGroup('A', [ValueError('B')]) err = traceback.format_exception_only(eg, show_group=True) @@ -248,8 +244,6 @@ def test_format_exception_group(self): ' ValueError: B\n', ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_base_exception_group(self): eg = BaseExceptionGroup('A', [BaseException('B')]) err = traceback.format_exception_only(eg, show_group=True) @@ -258,8 +252,6 @@ def test_format_base_exception_group(self): ' BaseException: B\n', ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_group_with_note(self): exc = ValueError('B') exc.add_note('Note') @@ -271,8 +263,6 @@ def test_format_exception_group_with_note(self): ' Note\n', ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_group_explicit_class(self): eg = ExceptionGroup('A', [ValueError('B')]) err = traceback.format_exception_only(ExceptionGroup, eg, show_group=True) @@ -281,8 +271,6 @@ def test_format_exception_group_explicit_class(self): ' ValueError: B\n', ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_group_multiple_exceptions(self): eg = ExceptionGroup('A', [ValueError('B'), TypeError('C')]) err = traceback.format_exception_only(eg, show_group=True) @@ -292,8 +280,6 @@ def test_format_exception_group_multiple_exceptions(self): ' TypeError: C\n', ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_group_multiline_messages(self): eg = ExceptionGroup('A\n1', [ValueError('B\n2')]) err = traceback.format_exception_only(eg, show_group=True) @@ -303,8 +289,6 @@ def test_format_exception_group_multiline_messages(self): ' 2\n', ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_group_multiline2_messages(self): exc = ValueError('B\n\n2\n') exc.add_note('\nC\n\n3') @@ -323,8 +307,6 @@ def test_format_exception_group_multiline2_messages(self): ' IndexError: D\n', ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_group_syntax_error(self): exc = SyntaxError("error", ("x.py", 23, None, "bad syntax")) eg = ExceptionGroup('A\n1', [exc]) @@ -336,8 +318,6 @@ def test_format_exception_group_syntax_error(self): ' SyntaxError: error\n', ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_group_nested_with_notes(self): exc = IndexError('D') exc.add_note('Note\nmultiline') @@ -358,8 +338,6 @@ def test_format_exception_group_nested_with_notes(self): ' TypeError: F\n', ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_group_with_tracebacks(self): def f(): try: @@ -385,8 +363,6 @@ def g(): ' TypeError: g\n', ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_group_with_cause(self): def f(): try: @@ -404,8 +380,6 @@ def f(): ' ValueError: 0\n', ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_group_syntax_error_with_custom_values(self): # See https://github.com/python/cpython/issues/128894 for exc in [ @@ -550,16 +524,12 @@ def test_print_exception_exc(self): traceback.print_exception(Exception("projector"), file=output) self.assertEqual(output.getvalue(), "Exception: projector\n") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_print_last(self): with support.swap_attr(sys, 'last_exc', ValueError(42)): output = StringIO() traceback.print_last(file=output) self.assertEqual(output.getvalue(), "ValueError: 42\n") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_format_exception_exc(self): e = Exception("projector") output = traceback.format_exception(e) @@ -598,8 +568,6 @@ def test_exception_is_None(self): self.assertEqual( traceback.format_exception_only(None, None), [NONE_EXC_STRING]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_signatures(self): self.assertEqual( str(inspect.signature(traceback.print_exception)), @@ -2286,8 +2254,6 @@ def test_print_exception_bad_type_capi(self): 'Exception expected for value, int found\n') ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_print_exception_bad_type_python(self): msg = "Exception expected for value, int found" with self.assertRaisesRegex(TypeError, msg): @@ -2366,8 +2332,6 @@ def test_simple(self): self.assertTrue(lines[1].startswith(' File')) self.assertIn('1/0 # Marker', lines[2]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_cause(self): def inner_raise(): try: @@ -2501,8 +2465,6 @@ def test_message_none(self): err = self.get_report(Exception('')) self.assertIn('Exception\n', err) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_syntax_error_various_offsets(self): for offset in range(-5, 10): for add in [0, 2]: @@ -2525,8 +2487,6 @@ def test_syntax_error_various_offsets(self): exp = "\n".join(expected) self.assertEqual(exp, err) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_with_note(self): e = ValueError(123) vanilla = self.get_report(e) @@ -2545,8 +2505,6 @@ def test_exception_with_note(self): del e.__notes__ self.assertEqual(self.get_report(e), vanilla) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_with_invalid_notes(self): e = ValueError(123) vanilla = self.get_report(e) @@ -2604,8 +2562,6 @@ def __getattr__(self, name): self.get_report(e), vanilla + "Ignored error getting __notes__: ValueError('no __notes__')\n") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_with_multiple_notes(self): for e in [ValueError(42), SyntaxError('bad syntax')]: with self.subTest(e=e): @@ -2685,8 +2641,6 @@ def __str__(self): exp = f'<unknown>.{X.__qualname__}: I am X\n' self.assertEqual(exp, err) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_bad__str__(self): class X(Exception): def __str__(self): @@ -2861,8 +2815,6 @@ def exc(): report = self.get_report(exc) self.assertEqual(report, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_group_width_limit(self): excs = [] for i in range(1000): @@ -2907,8 +2859,6 @@ def test_exception_group_width_limit(self): report = self.get_report(eg) self.assertEqual(report, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_group_depth_limit(self): exc = TypeError('bad type') for i in range(1000): @@ -3371,8 +3321,6 @@ def test_basics(self): self.assertNotEqual(f, object()) self.assertEqual(f, ALWAYS_EQ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_lazy_lines(self): linecache.clearcache() f = traceback.FrameSummary("f", 1, "dummy", lookup_line=False) @@ -3509,8 +3457,6 @@ def some_inner(): s.format(), [f'{__file__}:{some_inner.__code__.co_firstlineno + 1}']) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_dropping_frames(self): def f(): 1/0 @@ -3611,13 +3557,9 @@ def do_test_smoke(self, exc, expected_type_str): self.assertEqual(expected_type_str, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_smoke_builtin(self): self.do_test_smoke(ValueError(42), 'ValueError') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_smoke_user_exception(self): class MyException(Exception): pass @@ -3630,8 +3572,6 @@ class MyException(Exception): 'test_smoke_user_exception.<locals>.MyException') self.do_test_smoke(MyException('bad things happened'), expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_from_exception(self): # Check all the parameters are accepted. def foo(): @@ -3657,8 +3597,6 @@ def foo(): self.assertEqual(type(exc_obj).__name__, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_cause(self): try: try: @@ -3683,8 +3621,6 @@ def test_cause(self): self.assertEqual(type(exc_obj).__name__, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_context(self): try: try: @@ -3734,8 +3670,6 @@ def f(): self.assertIn( "RecursionError: maximum recursion depth exceeded", res[-1]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_compact_with_cause(self): try: try: @@ -3758,8 +3692,6 @@ def test_compact_with_cause(self): self.assertEqual(type(exc_obj).__name__, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_compact_no_cause(self): try: try: @@ -3782,8 +3714,6 @@ def test_compact_no_cause(self): self.assertEqual(type(exc_obj).__name__, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_no_save_exc_type(self): try: 1/0 @@ -3912,8 +3842,6 @@ def test_lookup_lines(self): linecache.updatecache('/foo.py', globals()) self.assertEqual(exc.stack[0].line, "import sys") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_locals(self): linecache.updatecache('/foo.py', globals()) e = Exception("uh oh") @@ -3962,8 +3890,6 @@ def f(): 'ZeroDivisionError: division by zero', '']) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_dont_swallow_cause_or_context_of_falsey_exception(self): # see gh-132308: Ensure that __cause__ or __context__ attributes of exceptions # that evaluate as falsey are included in the output. For falsey term, @@ -4034,8 +3960,6 @@ def test_exception_group_format_exception_only(self): self.assertEqual(formatted, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_group_format_exception_onlyi_recursive(self): teg = traceback.TracebackException.from_exception(self.eg) formatted = ''.join(teg.format_exception_only(show_group=True)).split('\n') @@ -4100,8 +4024,6 @@ def test_exception_group_format(self): self.assertEqual(formatted, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_max_group_width(self): excs1 = [] excs2 = [] @@ -4140,8 +4062,6 @@ def test_max_group_width(self): self.assertEqual(formatted, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_max_group_depth(self): exc = TypeError('bad type') for i in range(3): @@ -4191,8 +4111,6 @@ def test_comparison(self): self.assertNotEqual(exc, object()) self.assertEqual(exc, ALWAYS_EQ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_dont_swallow_subexceptions_of_falsey_exceptiongroup(self): # see gh-132308: Ensure that subexceptions of exception groups # that evaluate as falsey are displayed in the output. For falsey term, @@ -4230,8 +4148,6 @@ def callable(): ) return result_lines[0] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_getattr_suggestions(self): class Substitution: noise = more_noise = a = bc = None @@ -4275,8 +4191,6 @@ class CaseChangeOverSubstitution: actual = self.get_suggestion(cls(), 'bluch') self.assertIn(suggestion, actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_getattr_suggestions_underscored(self): class A: bluch = None @@ -4330,8 +4244,6 @@ class A: actual = self.get_suggestion(A(), 'bluch') self.assertNotIn("blech", actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_getattr_suggestions_no_args(self): class A: blech = None @@ -4349,8 +4261,6 @@ def __getattr__(self, attr): actual = self.get_suggestion(A(), 'bluch') self.assertIn("blech", actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_getattr_suggestions_invalid_args(self): class NonStringifyClass: __str__ = None @@ -4392,8 +4302,6 @@ def __dir__(self): self.assertNotIn("blech", actual) self.assertNotIn("oh no!", actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_attribute_error_with_non_string_candidates(self): class T: bluch = 1 @@ -4567,8 +4475,6 @@ def raise_attribute_error_with_bad_name(): ) self.assertNotIn("?", result_lines[-1]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_name_error_suggestions(self): def Substitution(): noise = more_noise = a = bc = None @@ -4609,24 +4515,18 @@ def EliminationOverAddition(): actual = self.get_suggestion(func) self.assertIn(suggestion, actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_name_error_suggestions_from_globals(self): def func(): print(global_for_suggestio) actual = self.get_suggestion(func) self.assertIn("'global_for_suggestions'?", actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_name_error_suggestions_from_builtins(self): def func(): print(ZeroDivisionErrrrr) actual = self.get_suggestion(func) self.assertIn("'ZeroDivisionError'?", actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_name_error_suggestions_from_builtins_when_builtins_is_module(self): def func(): custom_globals = globals().copy() @@ -4635,8 +4535,6 @@ def func(): actual = self.get_suggestion(func) self.assertIn("'ZeroDivisionError'?", actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_name_error_suggestions_with_non_string_candidates(self): def func(): abc = 1 @@ -4785,8 +4683,6 @@ def func(): actual = self.get_suggestion(func) self.assertNotIn("blech", actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_name_error_with_instance(self): class A: def __init__(self): @@ -4843,8 +4739,6 @@ def func(): actual = self.get_suggestion(func) self.assertNotIn("something", actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_name_error_for_stdlib_modules(self): def func(): stream = io.StringIO() @@ -4852,8 +4746,6 @@ def func(): actual = self.get_suggestion(func) self.assertIn("forget to import 'io'", actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_name_error_for_private_stdlib_modules(self): def func(): stream = _io.StringIO() @@ -4898,8 +4790,6 @@ def test_all(self): expected.add(name) self.assertCountEqual(traceback.__all__, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_levenshtein_distance(self): # copied from _testinternalcapi.test_edit_cost # to also exercise the Python implementation diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py index b291d530169..b9c1fd6134e 100644 --- a/Lib/test/test_zipimport.py +++ b/Lib/test/test_zipimport.py @@ -724,6 +724,8 @@ def doTraceback(self, module): else: raise AssertionError("This ought to be impossible") + # TODO: RUSTPYTHON; empty caret lines from equal col/end_col + @unittest.expectedFailure def testTraceback(self): files = {TESTMOD + ".py": (NOW, raise_src)} self.doTest(None, files, TESTMOD, call=self.doTraceback) diff --git a/Lib/traceback.py b/Lib/traceback.py index d6a010f4157..572a3177cb0 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1,9 +1,14 @@ """Extract, format and print information about Python stack traces.""" -import collections +import collections.abc import itertools import linecache import sys +import textwrap +import warnings +from contextlib import suppress +import _colorize +from _colorize import ANSIColors __all__ = ['extract_stack', 'extract_tb', 'format_exception', 'format_exception_only', 'format_list', 'format_stack', @@ -16,6 +21,7 @@ # Formatting and printing lists of traceback lines. # + def print_list(extracted_list, file=None): """Print the list of tuples as returned by extract_tb() or extract_stack() as a formatted stack trace to the given file.""" @@ -69,7 +75,8 @@ def extract_tb(tb, limit=None): trace. The line is a string with leading and trailing whitespace stripped; if the source is not available it is None. """ - return StackSummary.extract(walk_tb(tb), limit=limit) + return StackSummary._extract_from_extended_frame_gen( + _walk_tb_with_full_positions(tb), limit=limit) # # Exception formatting and output. @@ -95,14 +102,18 @@ def _parse_value_tb(exc, value, tb): raise ValueError("Both or neither of value and tb must be given") if value is tb is _sentinel: if exc is not None: - return exc, exc.__traceback__ + if isinstance(exc, BaseException): + return exc, exc.__traceback__ + + raise TypeError(f'Exception expected for value, ' + f'{type(exc).__name__} found') else: return None, None return value, tb def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ - file=None, chain=True): + file=None, chain=True, **kwargs): """Print exception up to 'limit' stack trace entries from 'tb' to 'file'. This differs from print_tb() in the following ways: (1) if @@ -113,16 +124,23 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ occurred with a caret on the next line indicating the approximate position of the error. """ + colorize = kwargs.get("colorize", False) value, tb = _parse_value_tb(exc, value, tb) - if file is None: - file = sys.stderr te = TracebackException(type(value), value, tb, limit=limit, compact=True) - for line in te.format(chain=chain): - print(line, file=file, end="") + te.print(file=file, chain=chain, colorize=colorize) + + +BUILTIN_EXCEPTION_LIMIT = object() + + +def _print_exception_bltin(exc, /): + file = sys.stderr if sys.stderr is not None else sys.__stderr__ + colorize = _colorize.can_colorize(file=file) + return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ - chain=True): + chain=True, **kwargs): """Format a stack trace and the exception information. The arguments have the same meaning as the corresponding arguments @@ -131,64 +149,79 @@ def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ these lines are concatenated and printed, exactly the same text is printed as does print_exception(). """ + colorize = kwargs.get("colorize", False) value, tb = _parse_value_tb(exc, value, tb) te = TracebackException(type(value), value, tb, limit=limit, compact=True) - return list(te.format(chain=chain)) + return list(te.format(chain=chain, colorize=colorize)) -def format_exception_only(exc, /, value=_sentinel): +def format_exception_only(exc, /, value=_sentinel, *, show_group=False, **kwargs): """Format the exception part of a traceback. The return value is a list of strings, each ending in a newline. - Normally, the list contains a single string; however, for - SyntaxError exceptions, it contains several lines that (when - printed) display detailed information about where the syntax - error occurred. - - The message indicating which exception occurred is always the last - string in the list. + The list contains the exception's message, which is + normally a single string; however, for :exc:`SyntaxError` exceptions, it + contains several lines that (when printed) display detailed information + about where the syntax error occurred. Following the message, the list + contains the exception's ``__notes__``. + When *show_group* is ``True``, and the exception is an instance of + :exc:`BaseExceptionGroup`, the nested exceptions are included as + well, recursively, with indentation relative to their nesting depth. """ + colorize = kwargs.get("colorize", False) if value is _sentinel: value = exc te = TracebackException(type(value), value, None, compact=True) - return list(te.format_exception_only()) + return list(te.format_exception_only(show_group=show_group, colorize=colorize)) # -- not official API but folk probably use these two functions. -def _format_final_exc_line(etype, value): - valuestr = _some_str(value) - if value is None or not valuestr: - line = "%s\n" % etype +def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize=False): + valuestr = _safe_string(value, 'exception') + end_char = "\n" if insert_final_newline else "" + if colorize: + if value is None or not valuestr: + line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}{end_char}" + else: + line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}: {ANSIColors.MAGENTA}{valuestr}{ANSIColors.RESET}{end_char}" else: - line = "%s: %s\n" % (etype, valuestr) + if value is None or not valuestr: + line = f"{etype}{end_char}" + else: + line = f"{etype}: {valuestr}{end_char}" return line -def _some_str(value): + +def _safe_string(value, what, func=str): try: - return str(value) + return func(value) except: - return '<unprintable %s object>' % type(value).__name__ + return f'<{what} {func.__name__}() failed>' # -- def print_exc(limit=None, file=None, chain=True): - """Shorthand for 'print_exception(*sys.exc_info(), limit, file)'.""" - print_exception(*sys.exc_info(), limit=limit, file=file, chain=chain) + """Shorthand for 'print_exception(sys.exception(), limit=limit, file=file, chain=chain)'.""" + print_exception(sys.exception(), limit=limit, file=file, chain=chain) def format_exc(limit=None, chain=True): """Like print_exc() but return a string.""" - return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain)) + return "".join(format_exception(sys.exception(), limit=limit, chain=chain)) def print_last(limit=None, file=None, chain=True): - """This is a shorthand for 'print_exception(sys.last_type, - sys.last_value, sys.last_traceback, limit, file)'.""" - if not hasattr(sys, "last_type"): + """This is a shorthand for 'print_exception(sys.last_exc, limit=limit, file=file, chain=chain)'.""" + if not hasattr(sys, "last_exc") and not hasattr(sys, "last_type"): raise ValueError("no last exception") - print_exception(sys.last_type, sys.last_value, sys.last_traceback, - limit, file, chain) + + if hasattr(sys, "last_exc"): + print_exception(sys.last_exc, limit=limit, file=file, chain=chain) + else: + print_exception(sys.last_type, sys.last_value, sys.last_traceback, + limit=limit, file=file, chain=chain) + # # Printing and Extracting Stacks. @@ -241,7 +274,7 @@ def clear_frames(tb): class FrameSummary: - """A single frame from a traceback. + """Information about a single frame from a traceback. - :attr:`filename` The filename for the frame. - :attr:`lineno` The line within filename for the frame that was @@ -254,10 +287,12 @@ class FrameSummary: mapping the name to the repr() of the variable. """ - __slots__ = ('filename', 'lineno', 'name', '_line', 'locals') + __slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno', + 'name', '_lines', '_lines_dedented', 'locals', '_code') def __init__(self, filename, lineno, name, *, lookup_line=True, - locals=None, line=None): + locals=None, line=None, + end_lineno=None, colno=None, end_colno=None, **kwargs): """Construct a FrameSummary. :param lookup_line: If True, `linecache` is consulted for the source @@ -269,11 +304,17 @@ def __init__(self, filename, lineno, name, *, lookup_line=True, """ self.filename = filename self.lineno = lineno + self.end_lineno = lineno if end_lineno is None else end_lineno + self.colno = colno + self.end_colno = end_colno self.name = name - self._line = line + self._code = kwargs.get("_code") + self._lines = line + self._lines_dedented = None if lookup_line: self.line - self.locals = {k: repr(v) for k, v in locals.items()} if locals else None + self.locals = {k: _safe_string(v, 'local', func=repr) + for k, v in locals.items()} if locals else None def __eq__(self, other): if isinstance(other, FrameSummary): @@ -298,13 +339,43 @@ def __repr__(self): def __len__(self): return 4 + def _set_lines(self): + if ( + self._lines is None + and self.lineno is not None + and self.end_lineno is not None + ): + lines = [] + for lineno in range(self.lineno, self.end_lineno + 1): + # treat errors (empty string) and empty lines (newline) as the same + line = linecache.getline(self.filename, lineno).rstrip() + if not line and self._code is not None and self.filename.startswith("<"): + line = linecache._getline_from_code(self._code, lineno).rstrip() + lines.append(line) + self._lines = "\n".join(lines) + "\n" + + @property + def _original_lines(self): + # Returns the line as-is from the source, without modifying whitespace. + self._set_lines() + return self._lines + + @property + def _dedented_lines(self): + # Returns _original_lines, but dedented + self._set_lines() + if self._lines_dedented is None and self._lines is not None: + self._lines_dedented = textwrap.dedent(self._lines) + return self._lines_dedented + @property def line(self): - if self._line is None: - if self.lineno is None: - return None - self._line = linecache.getline(self.filename, self.lineno) - return self._line.strip() + self._set_lines() + if self._lines is None: + return None + # return only the first line, stripped + return self._lines.partition("\n")[0].strip() + def walk_stack(f): """Walk a stack yielding the frame and line number for each frame. @@ -313,7 +384,7 @@ def walk_stack(f): current stack is used. Usually used with StackSummary.extract. """ if f is None: - f = sys._getframe().f_back.f_back + f = sys._getframe().f_back.f_back.f_back.f_back while f is not None: yield f, f.f_lineno f = f.f_back @@ -330,18 +401,40 @@ def walk_tb(tb): tb = tb.tb_next +def _walk_tb_with_full_positions(tb): + # Internal version of walk_tb that yields full code positions including + # end line and column information. + while tb is not None: + positions = _get_code_position(tb.tb_frame.f_code, tb.tb_lasti) + # Yield tb_lineno when co_positions does not have a line number to + # maintain behavior with walk_tb. + if positions[0] is None: + yield tb.tb_frame, (tb.tb_lineno, ) + positions[1:] + else: + yield tb.tb_frame, positions + tb = tb.tb_next + + +def _get_code_position(code, instruction_index): + if instruction_index < 0: + return (None, None, None, None) + positions_gen = code.co_positions() + return next(itertools.islice(positions_gen, instruction_index // 2, None)) + + _RECURSIVE_CUTOFF = 3 # Also hardcoded in traceback.c. + class StackSummary(list): - """A stack of frames.""" + """A list of FrameSummary objects, representing a stack of frames.""" @classmethod def extract(klass, frame_gen, *, limit=None, lookup_lines=True, capture_locals=False): """Create a StackSummary from a traceback or stack object. - :param frame_gen: A generator that yields (frame, lineno) tuples to - include in the stack. + :param frame_gen: A generator that yields (frame, lineno) tuples + whose summaries are to be included in the stack. :param limit: None to include all frames or the number of frames to include. :param lookup_lines: If True, lookup lines for each frame immediately, @@ -349,23 +442,41 @@ def extract(klass, frame_gen, *, limit=None, lookup_lines=True, :param capture_locals: If True, the local variables from each frame will be captured as object representations into the FrameSummary. """ - if limit is None: + def extended_frame_gen(): + for f, lineno in frame_gen: + yield f, (lineno, None, None, None) + + return klass._extract_from_extended_frame_gen( + extended_frame_gen(), limit=limit, lookup_lines=lookup_lines, + capture_locals=capture_locals) + + @classmethod + def _extract_from_extended_frame_gen(klass, frame_gen, *, limit=None, + lookup_lines=True, capture_locals=False): + # Same as extract but operates on a frame generator that yields + # (frame, (lineno, end_lineno, colno, end_colno)) in the stack. + # Only lineno is required, the remaining fields can be None if the + # information is not available. + builtin_limit = limit is BUILTIN_EXCEPTION_LIMIT + if limit is None or builtin_limit: limit = getattr(sys, 'tracebacklimit', None) if limit is not None and limit < 0: limit = 0 if limit is not None: - if limit >= 0: + if builtin_limit: + frame_gen = tuple(frame_gen) + frame_gen = frame_gen[len(frame_gen) - limit:] + elif limit >= 0: frame_gen = itertools.islice(frame_gen, limit) else: frame_gen = collections.deque(frame_gen, maxlen=-limit) result = klass() fnames = set() - for f, lineno in frame_gen: + for f, (lineno, end_lineno, colno, end_colno) in frame_gen: co = f.f_code filename = co.co_filename name = co.co_name - fnames.add(filename) linecache.lazycache(filename, f.f_globals) # Must defer line lookups until we have called checkcache. @@ -373,10 +484,16 @@ def extract(klass, frame_gen, *, limit=None, lookup_lines=True, f_locals = f.f_locals else: f_locals = None - result.append(FrameSummary( - filename, lineno, name, lookup_line=False, locals=f_locals)) + result.append( + FrameSummary(filename, lineno, name, + lookup_line=False, locals=f_locals, + end_lineno=end_lineno, colno=colno, end_colno=end_colno, + _code=f.f_code, + ) + ) for filename in fnames: linecache.checkcache(filename) + # If immediate lookup was desired, trigger lookups now. if lookup_lines: for f in result: @@ -402,7 +519,223 @@ def from_list(klass, a_list): result.append(FrameSummary(filename, lineno, name, line=line)) return result - def format(self): + def format_frame_summary(self, frame_summary, **kwargs): + """Format the lines for a single FrameSummary. + + Returns a string representing one frame involved in the stack. This + gets called for every frame to be printed in the stack summary. + """ + colorize = kwargs.get("colorize", False) + row = [] + filename = frame_summary.filename + if frame_summary.filename.startswith("<stdin-") and frame_summary.filename.endswith('>'): + filename = "<stdin>" + if colorize: + row.append(' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format( + ANSIColors.MAGENTA, + filename, + ANSIColors.RESET, + ANSIColors.MAGENTA, + frame_summary.lineno, + ANSIColors.RESET, + ANSIColors.MAGENTA, + frame_summary.name, + ANSIColors.RESET, + ) + ) + else: + row.append(' File "{}", line {}, in {}\n'.format( + filename, frame_summary.lineno, frame_summary.name)) + if frame_summary._dedented_lines and frame_summary._dedented_lines.strip(): + if ( + frame_summary.colno is None or + frame_summary.end_colno is None + ): + # only output first line if column information is missing + row.append(textwrap.indent(frame_summary.line, ' ') + "\n") + else: + # get first and last line + all_lines_original = frame_summary._original_lines.splitlines() + first_line = all_lines_original[0] + # assume all_lines_original has enough lines (since we constructed it) + last_line = all_lines_original[frame_summary.end_lineno - frame_summary.lineno] + + # character index of the start/end of the instruction + start_offset = _byte_offset_to_character_offset(first_line, frame_summary.colno) + end_offset = _byte_offset_to_character_offset(last_line, frame_summary.end_colno) + + all_lines = frame_summary._dedented_lines.splitlines()[ + :frame_summary.end_lineno - frame_summary.lineno + 1 + ] + + # adjust start/end offset based on dedent + dedent_characters = len(first_line) - len(all_lines[0]) + start_offset = max(0, start_offset - dedent_characters) + end_offset = max(0, end_offset - dedent_characters) + + # When showing this on a terminal, some of the non-ASCII characters + # might be rendered as double-width characters, so we need to take + # that into account when calculating the length of the line. + dp_start_offset = _display_width(all_lines[0], offset=start_offset) + dp_end_offset = _display_width(all_lines[-1], offset=end_offset) + + # get exact code segment corresponding to the instruction + segment = "\n".join(all_lines) + segment = segment[start_offset:len(segment) - (len(all_lines[-1]) - end_offset)] + + # attempt to parse for anchors + anchors = None + show_carets = False + with suppress(Exception): + anchors = _extract_caret_anchors_from_line_segment(segment) + show_carets = self._should_show_carets(start_offset, end_offset, all_lines, anchors) + + result = [] + + # only display first line, last line, and lines around anchor start/end + significant_lines = {0, len(all_lines) - 1} + + anchors_left_end_offset = 0 + anchors_right_start_offset = 0 + primary_char = "^" + secondary_char = "^" + if anchors: + anchors_left_end_offset = anchors.left_end_offset + anchors_right_start_offset = anchors.right_start_offset + # computed anchor positions do not take start_offset into account, + # so account for it here + if anchors.left_end_lineno == 0: + anchors_left_end_offset += start_offset + if anchors.right_start_lineno == 0: + anchors_right_start_offset += start_offset + + # account for display width + anchors_left_end_offset = _display_width( + all_lines[anchors.left_end_lineno], offset=anchors_left_end_offset + ) + anchors_right_start_offset = _display_width( + all_lines[anchors.right_start_lineno], offset=anchors_right_start_offset + ) + + primary_char = anchors.primary_char + secondary_char = anchors.secondary_char + significant_lines.update( + range(anchors.left_end_lineno - 1, anchors.left_end_lineno + 2) + ) + significant_lines.update( + range(anchors.right_start_lineno - 1, anchors.right_start_lineno + 2) + ) + + # remove bad line numbers + significant_lines.discard(-1) + significant_lines.discard(len(all_lines)) + + def output_line(lineno): + """output all_lines[lineno] along with carets""" + result.append(all_lines[lineno] + "\n") + if not show_carets: + return + num_spaces = len(all_lines[lineno]) - len(all_lines[lineno].lstrip()) + carets = [] + num_carets = dp_end_offset if lineno == len(all_lines) - 1 else _display_width(all_lines[lineno]) + # compute caret character for each position + for col in range(num_carets): + if col < num_spaces or (lineno == 0 and col < dp_start_offset): + # before first non-ws char of the line, or before start of instruction + carets.append(' ') + elif anchors and ( + lineno > anchors.left_end_lineno or + (lineno == anchors.left_end_lineno and col >= anchors_left_end_offset) + ) and ( + lineno < anchors.right_start_lineno or + (lineno == anchors.right_start_lineno and col < anchors_right_start_offset) + ): + # within anchors + carets.append(secondary_char) + else: + carets.append(primary_char) + if colorize: + # Replace the previous line with a red version of it only in the parts covered + # by the carets. + line = result[-1] + colorized_line_parts = [] + colorized_carets_parts = [] + + for color, group in itertools.groupby(itertools.zip_longest(line, carets, fillvalue=""), key=lambda x: x[1]): + caret_group = list(group) + if color == "^": + colorized_line_parts.append(ANSIColors.BOLD_RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET) + colorized_carets_parts.append(ANSIColors.BOLD_RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET) + elif color == "~": + colorized_line_parts.append(ANSIColors.RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET) + colorized_carets_parts.append(ANSIColors.RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET) + else: + colorized_line_parts.append("".join(char for char, _ in caret_group)) + colorized_carets_parts.append("".join(caret for _, caret in caret_group)) + + colorized_line = "".join(colorized_line_parts) + colorized_carets = "".join(colorized_carets_parts) + result[-1] = colorized_line + result.append(colorized_carets + "\n") + else: + result.append("".join(carets) + "\n") + + # display significant lines + sig_lines_list = sorted(significant_lines) + for i, lineno in enumerate(sig_lines_list): + if i: + linediff = lineno - sig_lines_list[i - 1] + if linediff == 2: + # 1 line in between - just output it + output_line(lineno - 1) + elif linediff > 2: + # > 1 line in between - abbreviate + result.append(f"...<{linediff - 1} lines>...\n") + output_line(lineno) + + row.append( + textwrap.indent(textwrap.dedent("".join(result)), ' ', lambda line: True) + ) + if frame_summary.locals: + for name, value in sorted(frame_summary.locals.items()): + row.append(' {name} = {value}\n'.format(name=name, value=value)) + + return ''.join(row) + + def _should_show_carets(self, start_offset, end_offset, all_lines, anchors): + with suppress(SyntaxError, ImportError): + import ast + tree = ast.parse('\n'.join(all_lines)) + if not tree.body: + return False + statement = tree.body[0] + value = None + def _spawns_full_line(value): + return ( + value.lineno == 1 + and value.end_lineno == len(all_lines) + and value.col_offset == start_offset + and value.end_col_offset == end_offset + ) + match statement: + case ast.Return(value=ast.Call()): + if isinstance(statement.value.func, ast.Name): + value = statement.value + case ast.Assign(value=ast.Call()): + if ( + len(statement.targets) == 1 and + isinstance(statement.targets[0], ast.Name) + ): + value = statement.value + if value is not None and _spawns_full_line(value): + return False + if anchors: + return True + if all_lines[0][:start_offset].lstrip() or all_lines[-1][end_offset:].rstrip(): + return True + return False + + def format(self, **kwargs): """Format the stack ready for printing. Returns a list of strings ready for printing. Each string in the @@ -414,37 +747,34 @@ def format(self): repetitions are shown, followed by a summary line stating the exact number of further repetitions. """ + colorize = kwargs.get("colorize", False) result = [] last_file = None last_line = None last_name = None count = 0 - for frame in self: - if (last_file is None or last_file != frame.filename or - last_line is None or last_line != frame.lineno or - last_name is None or last_name != frame.name): + for frame_summary in self: + formatted_frame = self.format_frame_summary(frame_summary, colorize=colorize) + if formatted_frame is None: + continue + if (last_file is None or last_file != frame_summary.filename or + last_line is None or last_line != frame_summary.lineno or + last_name is None or last_name != frame_summary.name): if count > _RECURSIVE_CUTOFF: count -= _RECURSIVE_CUTOFF result.append( f' [Previous line repeated {count} more ' f'time{"s" if count > 1 else ""}]\n' ) - last_file = frame.filename - last_line = frame.lineno - last_name = frame.name + last_file = frame_summary.filename + last_line = frame_summary.lineno + last_name = frame_summary.name count = 0 count += 1 if count > _RECURSIVE_CUTOFF: continue - row = [] - row.append(' File "{}", line {}, in {}\n'.format( - frame.filename, frame.lineno, frame.name)) - if frame.line: - row.append(' {}\n'.format(frame.line.strip())) - if frame.locals: - for name, value in sorted(frame.locals.items()): - row.append(' {name} = {value}\n'.format(name=name, value=value)) - result.append(''.join(row)) + result.append(formatted_frame) + if count > _RECURSIVE_CUTOFF: count -= _RECURSIVE_CUTOFF result.append( @@ -454,6 +784,216 @@ def format(self): return result +def _byte_offset_to_character_offset(str, offset): + as_utf8 = str.encode('utf-8') + return len(as_utf8[:offset].decode("utf-8", errors="replace")) + + +_Anchors = collections.namedtuple( + "_Anchors", + [ + "left_end_lineno", + "left_end_offset", + "right_start_lineno", + "right_start_offset", + "primary_char", + "secondary_char", + ], + defaults=["~", "^"] +) + +def _extract_caret_anchors_from_line_segment(segment): + """ + Given source code `segment` corresponding to a FrameSummary, determine: + - for binary ops, the location of the binary op + - for indexing and function calls, the location of the brackets. + `segment` is expected to be a valid Python expression. + """ + import ast + + try: + # Without parentheses, `segment` is parsed as a statement. + # Binary ops, subscripts, and calls are expressions, so + # we can wrap them with parentheses to parse them as + # (possibly multi-line) expressions. + # e.g. if we try to highlight the addition in + # x = ( + # a + + # b + # ) + # then we would ast.parse + # a + + # b + # which is not a valid statement because of the newline. + # Adding brackets makes it a valid expression. + # ( + # a + + # b + # ) + # Line locations will be different than the original, + # which is taken into account later on. + tree = ast.parse(f"(\n{segment}\n)") + except SyntaxError: + return None + + if len(tree.body) != 1: + return None + + lines = segment.splitlines() + + def normalize(lineno, offset): + """Get character index given byte offset""" + return _byte_offset_to_character_offset(lines[lineno], offset) + + def next_valid_char(lineno, col): + """Gets the next valid character index in `lines`, if + the current location is not valid. Handles empty lines. + """ + while lineno < len(lines) and col >= len(lines[lineno]): + col = 0 + lineno += 1 + assert lineno < len(lines) and col < len(lines[lineno]) + return lineno, col + + def increment(lineno, col): + """Get the next valid character index in `lines`.""" + col += 1 + lineno, col = next_valid_char(lineno, col) + return lineno, col + + def nextline(lineno, col): + """Get the next valid character at least on the next line""" + col = 0 + lineno += 1 + lineno, col = next_valid_char(lineno, col) + return lineno, col + + def increment_until(lineno, col, stop): + """Get the next valid non-"\\#" character that satisfies the `stop` predicate""" + while True: + ch = lines[lineno][col] + if ch in "\\#": + lineno, col = nextline(lineno, col) + elif not stop(ch): + lineno, col = increment(lineno, col) + else: + break + return lineno, col + + def setup_positions(expr, force_valid=True): + """Get the lineno/col position of the end of `expr`. If `force_valid` is True, + forces the position to be a valid character (e.g. if the position is beyond the + end of the line, move to the next line) + """ + # -2 since end_lineno is 1-indexed and because we added an extra + # bracket + newline to `segment` when calling ast.parse + lineno = expr.end_lineno - 2 + col = normalize(lineno, expr.end_col_offset) + return next_valid_char(lineno, col) if force_valid else (lineno, col) + + statement = tree.body[0] + match statement: + case ast.Expr(expr): + match expr: + case ast.BinOp(): + # ast gives these locations for BinOp subexpressions + # ( left_expr ) + ( right_expr ) + # left^^^^^ right^^^^^ + lineno, col = setup_positions(expr.left) + + # First operator character is the first non-space/')' character + lineno, col = increment_until(lineno, col, lambda x: not x.isspace() and x != ')') + + # binary op is 1 or 2 characters long, on the same line, + # before the right subexpression + right_col = col + 1 + if ( + right_col < len(lines[lineno]) + and ( + # operator char should not be in the right subexpression + expr.right.lineno - 2 > lineno or + right_col < normalize(expr.right.lineno - 2, expr.right.col_offset) + ) + and not (ch := lines[lineno][right_col]).isspace() + and ch not in "\\#" + ): + right_col += 1 + + # right_col can be invalid since it is exclusive + return _Anchors(lineno, col, lineno, right_col) + case ast.Subscript(): + # ast gives these locations for value and slice subexpressions + # ( value_expr ) [ slice_expr ] + # value^^^^^ slice^^^^^ + # subscript^^^^^^^^^^^^^^^^^^^^ + + # find left bracket + left_lineno, left_col = setup_positions(expr.value) + left_lineno, left_col = increment_until(left_lineno, left_col, lambda x: x == '[') + # find right bracket (final character of expression) + right_lineno, right_col = setup_positions(expr, force_valid=False) + return _Anchors(left_lineno, left_col, right_lineno, right_col) + case ast.Call(): + # ast gives these locations for function call expressions + # ( func_expr ) (args, kwargs) + # func^^^^^ + # call^^^^^^^^^^^^^^^^^^^^^^^^ + + # find left bracket + left_lineno, left_col = setup_positions(expr.func) + left_lineno, left_col = increment_until(left_lineno, left_col, lambda x: x == '(') + # find right bracket (final character of expression) + right_lineno, right_col = setup_positions(expr, force_valid=False) + return _Anchors(left_lineno, left_col, right_lineno, right_col) + + return None + +_WIDE_CHAR_SPECIFIERS = "WF" + +def _display_width(line, offset=None): + """Calculate the extra amount of width space the given source + code segment might take if it were to be displayed on a fixed + width output device. Supports wide unicode characters and emojis.""" + + if offset is None: + offset = len(line) + + # Fast track for ASCII-only strings + if line.isascii(): + return offset + + import unicodedata + + return sum( + 2 if unicodedata.east_asian_width(char) in _WIDE_CHAR_SPECIFIERS else 1 + for char in line[:offset] + ) + + + +class _ExceptionPrintContext: + def __init__(self): + self.seen = set() + self.exception_group_depth = 0 + self.need_close = False + + def indent(self): + return ' ' * (2 * self.exception_group_depth) + + def emit(self, text_gen, margin_char=None): + if margin_char is None: + margin_char = '|' + indent_str = self.indent() + if self.exception_group_depth: + indent_str += margin_char + ' ' + + if isinstance(text_gen, str): + yield textwrap.indent(text_gen, indent_str, lambda line: True) + else: + for text in text_gen: + yield textwrap.indent(text, indent_str, lambda line: True) + + class TracebackException: """An exception ready for rendering. @@ -461,16 +1001,24 @@ class TracebackException: to this intermediary form to ensure that no references are held, while still being able to fully print or format it. + max_group_width and max_group_depth control the formatting of exception + groups. The depth refers to the nesting level of the group, and the width + refers to the size of a single exception group's exceptions array. The + formatted output is truncated when either limit is exceeded. + Use `from_exception` to create TracebackException instances from exception objects, or the constructor to create TracebackException instances from individual components. - :attr:`__cause__` A TracebackException of the original *__cause__*. - :attr:`__context__` A TracebackException of the original *__context__*. + - :attr:`exceptions` For exception groups - a list of TracebackException + instances for the nested *exceptions*. ``None`` for other exceptions. - :attr:`__suppress_context__` The *__suppress_context__* value from the original exception. - :attr:`stack` A `StackSummary` representing the traceback. - - :attr:`exc_type` The class of the original traceback. + - :attr:`exc_type` (deprecated) The class of the original traceback. + - :attr:`exc_type_str` String display of exc_type - :attr:`filename` For syntax errors - the filename where the error occurred. - :attr:`lineno` For syntax errors - the linenumber where the error @@ -481,14 +1029,14 @@ class TracebackException: occurred. - :attr:`offset` For syntax errors - the offset into the text where the error occurred. - - :attr:`end_offset` For syntax errors - the offset into the text where the - error occurred. Can be `None` if not present. + - :attr:`end_offset` For syntax errors - the end offset into the text where + the error occurred. Can be `None` if not present. - :attr:`msg` For syntax errors - the compiler error message. """ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=True, capture_locals=False, compact=False, - _seen=None): + max_group_width=15, max_group_depth=10, save_exc_type=True, _seen=None): # NB: we need to accept exc_traceback, exc_value, exc_traceback to # permit backwards compat with the existing API, otherwise we # need stub thunk objects just to glue it together. @@ -498,14 +1046,34 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, _seen = set() _seen.add(id(exc_value)) - # TODO: locals. - self.stack = StackSummary.extract( - walk_tb(exc_traceback), limit=limit, lookup_lines=lookup_lines, + self.max_group_width = max_group_width + self.max_group_depth = max_group_depth + + self.stack = StackSummary._extract_from_extended_frame_gen( + _walk_tb_with_full_positions(exc_traceback), + limit=limit, lookup_lines=lookup_lines, capture_locals=capture_locals) - self.exc_type = exc_type + + self._exc_type = exc_type if save_exc_type else None + # Capture now to permit freeing resources: only complication is in the # unofficial API _format_final_exc_line - self._str = _some_str(exc_value) + self._str = _safe_string(exc_value, 'exception') + try: + self.__notes__ = getattr(exc_value, '__notes__', None) + except Exception as e: + self.__notes__ = [ + f'Ignored error getting __notes__: {_safe_string(e, '__notes__', repr)}'] + + self._is_syntax_error = False + self._have_exc_type = exc_type is not None + if exc_type is not None: + self.exc_type_qualname = exc_type.__qualname__ + self.exc_type_module = exc_type.__module__ + else: + self.exc_type_qualname = None + self.exc_type_module = None + if exc_type and issubclass(exc_type, SyntaxError): # Handle SyntaxError's specially self.filename = exc_value.filename @@ -517,6 +1085,26 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, self.offset = exc_value.offset self.end_offset = exc_value.end_offset self.msg = exc_value.msg + self._is_syntax_error = True + elif exc_type and issubclass(exc_type, ImportError) and \ + getattr(exc_value, "name_from", None) is not None: + wrong_name = getattr(exc_value, "name_from", None) + suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name) + if suggestion: + self._str += f". Did you mean: '{suggestion}'?" + elif exc_type and issubclass(exc_type, (NameError, AttributeError)) and \ + getattr(exc_value, "name", None) is not None: + wrong_name = getattr(exc_value, "name", None) + suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name) + if suggestion: + self._str += f". Did you mean: '{suggestion}'?" + if issubclass(exc_type, NameError): + wrong_name = getattr(exc_value, "name", None) + if wrong_name is not None and wrong_name in sys.stdlib_module_names: + if suggestion: + self._str += f" Or did you forget to import '{wrong_name}'?" + else: + self._str += f". Did you forget to import '{wrong_name}'?" if lookup_lines: self._load_lines() self.__suppress_context__ = \ @@ -528,7 +1116,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, queue = [(self, exc_value)] while queue: te, e = queue.pop() - if (e and e.__cause__ is not None + if (e is not None and e.__cause__ is not None and id(e.__cause__) not in _seen): cause = TracebackException( type(e.__cause__), @@ -537,6 +1125,8 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, limit=limit, lookup_lines=lookup_lines, capture_locals=capture_locals, + max_group_width=max_group_width, + max_group_depth=max_group_depth, _seen=_seen) else: cause = None @@ -547,7 +1137,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, not e.__suppress_context__) else: need_context = True - if (e and e.__context__ is not None + if (e is not None and e.__context__ is not None and need_context and id(e.__context__) not in _seen): context = TracebackException( type(e.__context__), @@ -556,21 +1146,62 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, limit=limit, lookup_lines=lookup_lines, capture_locals=capture_locals, + max_group_width=max_group_width, + max_group_depth=max_group_depth, _seen=_seen) else: context = None + + if e is not None and isinstance(e, BaseExceptionGroup): + exceptions = [] + for exc in e.exceptions: + texc = TracebackException( + type(exc), + exc, + exc.__traceback__, + limit=limit, + lookup_lines=lookup_lines, + capture_locals=capture_locals, + max_group_width=max_group_width, + max_group_depth=max_group_depth, + _seen=_seen) + exceptions.append(texc) + else: + exceptions = None + te.__cause__ = cause te.__context__ = context + te.exceptions = exceptions if cause: queue.append((te.__cause__, e.__cause__)) if context: queue.append((te.__context__, e.__context__)) + if exceptions: + queue.extend(zip(te.exceptions, e.exceptions)) @classmethod def from_exception(cls, exc, *args, **kwargs): """Create a TracebackException from an exception.""" return cls(type(exc), exc, exc.__traceback__, *args, **kwargs) + @property + def exc_type(self): + warnings.warn('Deprecated in 3.13. Use exc_type_str instead.', + DeprecationWarning, stacklevel=2) + return self._exc_type + + @property + def exc_type_str(self): + if not self._have_exc_type: + return None + stype = self.exc_type_qualname + smod = self.exc_type_module + if smod not in ("__main__", "builtins"): + if not isinstance(smod, str): + smod = "<unknown>" + stype = smod + '.' + stype + return stype + def _load_lines(self): """Private API. force all lines in the stack to be loaded.""" for frame in self.stack: @@ -584,72 +1215,152 @@ def __eq__(self, other): def __str__(self): return self._str - def format_exception_only(self): + def format_exception_only(self, *, show_group=False, _depth=0, **kwargs): """Format the exception part of the traceback. The return value is a generator of strings, each ending in a newline. - Normally, the generator emits a single string; however, for - SyntaxError exceptions, it emits several lines that (when - printed) display detailed information about where the syntax - error occurred. - - The message indicating which exception occurred is always the last - string in the output. + Generator yields the exception message. + For :exc:`SyntaxError` exceptions, it + also yields (before the exception message) + several lines that (when printed) + display detailed information about where the syntax error occurred. + Following the message, generator also yields + all the exception's ``__notes__``. + + When *show_group* is ``True``, and the exception is an instance of + :exc:`BaseExceptionGroup`, the nested exceptions are included as + well, recursively, with indentation relative to their nesting depth. """ - if self.exc_type is None: - yield _format_final_exc_line(None, self._str) - return + colorize = kwargs.get("colorize", False) - stype = self.exc_type.__qualname__ - smod = self.exc_type.__module__ - if smod not in ("__main__", "builtins"): - if not isinstance(smod, str): - smod = "<unknown>" - stype = smod + '.' + stype + indent = 3 * _depth * ' ' + if not self._have_exc_type: + yield indent + _format_final_exc_line(None, self._str, colorize=colorize) + return - if not issubclass(self.exc_type, SyntaxError): - yield _format_final_exc_line(stype, self._str) + stype = self.exc_type_str + if not self._is_syntax_error: + if _depth > 0: + # Nested exceptions needs correct handling of multiline messages. + formatted = _format_final_exc_line( + stype, self._str, insert_final_newline=False, colorize=colorize + ).split('\n') + yield from [ + indent + l + '\n' + for l in formatted + ] + else: + yield _format_final_exc_line(stype, self._str, colorize=colorize) else: - yield from self._format_syntax_error(stype) - - def _format_syntax_error(self, stype): + yield from [indent + l for l in self._format_syntax_error(stype, colorize=colorize)] + + if ( + isinstance(self.__notes__, collections.abc.Sequence) + and not isinstance(self.__notes__, (str, bytes)) + ): + for note in self.__notes__: + note = _safe_string(note, 'note') + yield from [indent + l + '\n' for l in note.split('\n')] + elif self.__notes__ is not None: + yield indent + "{}\n".format(_safe_string(self.__notes__, '__notes__', func=repr)) + + if self.exceptions and show_group: + for ex in self.exceptions: + yield from ex.format_exception_only(show_group=show_group, _depth=_depth+1, colorize=colorize) + + def _format_syntax_error(self, stype, **kwargs): """Format SyntaxError exceptions (internal helper).""" # Show exactly where the problem was found. + colorize = kwargs.get("colorize", False) filename_suffix = '' if self.lineno is not None: - yield ' File "{}", line {}\n'.format( - self.filename or "<string>", self.lineno) + if colorize: + yield ' File {}"{}"{}, line {}{}{}\n'.format( + ANSIColors.MAGENTA, + self.filename or "<string>", + ANSIColors.RESET, + ANSIColors.MAGENTA, + self.lineno, + ANSIColors.RESET, + ) + else: + yield ' File "{}", line {}\n'.format( + self.filename or "<string>", self.lineno) elif self.filename is not None: filename_suffix = ' ({})'.format(self.filename) text = self.text - if text is not None: + if isinstance(text, str): # text = " foo\n" # rtext = " foo" # ltext = "foo" rtext = text.rstrip('\n') ltext = rtext.lstrip(' \n\f') spaces = len(rtext) - len(ltext) - yield ' {}\n'.format(ltext) - - if self.offset is not None: + if self.offset is None: + yield ' {}\n'.format(ltext) + elif isinstance(self.offset, int): offset = self.offset - end_offset = self.end_offset if self.end_offset not in {None, 0} else offset - if offset == end_offset or end_offset == -1: + if self.lineno == self.end_lineno: + end_offset = ( + self.end_offset + if ( + isinstance(self.end_offset, int) + and self.end_offset != 0 + ) + else offset + ) + else: + end_offset = len(rtext) + 1 + + if self.text and offset > len(self.text): + offset = len(rtext) + 1 + if self.text and end_offset > len(self.text): + end_offset = len(rtext) + 1 + if offset >= end_offset or end_offset < 0: end_offset = offset + 1 # Convert 1-based column offset to 0-based index into stripped text colno = offset - 1 - spaces end_colno = end_offset - 1 - spaces + caretspace = ' ' if colno >= 0: # non-space whitespace (likes tabs) must be kept for alignment caretspace = ((c if c.isspace() else ' ') for c in ltext[:colno]) - yield ' {}{}'.format("".join(caretspace), ('^' * (end_colno - colno) + "\n")) + start_color = end_color = "" + if colorize: + # colorize from colno to end_colno + ltext = ( + ltext[:colno] + + ANSIColors.BOLD_RED + ltext[colno:end_colno] + ANSIColors.RESET + + ltext[end_colno:] + ) + start_color = ANSIColors.BOLD_RED + end_color = ANSIColors.RESET + yield ' {}\n'.format(ltext) + yield ' {}{}{}{}\n'.format( + "".join(caretspace), + start_color, + ('^' * (end_colno - colno)), + end_color, + ) + else: + yield ' {}\n'.format(ltext) msg = self.msg or "<no detail available>" - yield "{}: {}{}\n".format(stype, msg, filename_suffix) + if colorize: + yield "{}{}{}: {}{}{}{}\n".format( + ANSIColors.BOLD_MAGENTA, + stype, + ANSIColors.RESET, + ANSIColors.MAGENTA, + msg, + ANSIColors.RESET, + filename_suffix) + else: + yield "{}: {}{}\n".format(stype, msg, filename_suffix) - def format(self, *, chain=True): + def format(self, *, chain=True, _ctx=None, **kwargs): """Format the exception. If chain is not *True*, *__cause__* and *__context__* will not be formatted. @@ -661,11 +1372,14 @@ def format(self, *, chain=True): The message indicating which exception occurred is always the last string in the output. """ + colorize = kwargs.get("colorize", False) + if _ctx is None: + _ctx = _ExceptionPrintContext() output = [] exc = self - while exc: - if chain: + if chain: + while exc: if exc.__cause__ is not None: chained_msg = _cause_message chained_exc = exc.__cause__ @@ -679,14 +1393,246 @@ def format(self, *, chain=True): output.append((chained_msg, exc)) exc = chained_exc - else: - output.append((None, exc)) - exc = None + else: + output.append((None, exc)) for msg, exc in reversed(output): if msg is not None: - yield msg - if exc.stack: - yield 'Traceback (most recent call last):\n' - yield from exc.stack.format() - yield from exc.format_exception_only() + yield from _ctx.emit(msg) + if exc.exceptions is None: + if exc.stack: + yield from _ctx.emit('Traceback (most recent call last):\n') + yield from _ctx.emit(exc.stack.format(colorize=colorize)) + yield from _ctx.emit(exc.format_exception_only(colorize=colorize)) + elif _ctx.exception_group_depth > self.max_group_depth: + # exception group, but depth exceeds limit + yield from _ctx.emit( + f"... (max_group_depth is {self.max_group_depth})\n") + else: + # format exception group + is_toplevel = (_ctx.exception_group_depth == 0) + if is_toplevel: + _ctx.exception_group_depth += 1 + + if exc.stack: + yield from _ctx.emit( + 'Exception Group Traceback (most recent call last):\n', + margin_char = '+' if is_toplevel else None) + yield from _ctx.emit(exc.stack.format(colorize=colorize)) + + yield from _ctx.emit(exc.format_exception_only(colorize=colorize)) + num_excs = len(exc.exceptions) + if num_excs <= self.max_group_width: + n = num_excs + else: + n = self.max_group_width + 1 + _ctx.need_close = False + for i in range(n): + last_exc = (i == n-1) + if last_exc: + # The closing frame may be added by a recursive call + _ctx.need_close = True + + if self.max_group_width is not None: + truncated = (i >= self.max_group_width) + else: + truncated = False + title = f'{i+1}' if not truncated else '...' + yield (_ctx.indent() + + ('+-' if i==0 else ' ') + + f'+---------------- {title} ----------------\n') + _ctx.exception_group_depth += 1 + if not truncated: + yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx, colorize=colorize) + else: + remaining = num_excs - self.max_group_width + plural = 's' if remaining > 1 else '' + yield from _ctx.emit( + f"and {remaining} more exception{plural}\n") + + if last_exc and _ctx.need_close: + yield (_ctx.indent() + + "+------------------------------------\n") + _ctx.need_close = False + _ctx.exception_group_depth -= 1 + + if is_toplevel: + assert _ctx.exception_group_depth == 1 + _ctx.exception_group_depth = 0 + + + def print(self, *, file=None, chain=True, **kwargs): + """Print the result of self.format(chain=chain) to 'file'.""" + colorize = kwargs.get("colorize", False) + if file is None: + file = sys.stderr + for line in self.format(chain=chain, colorize=colorize): + print(line, file=file, end="") + + +_MAX_CANDIDATE_ITEMS = 750 +_MAX_STRING_SIZE = 40 +_MOVE_COST = 2 +_CASE_COST = 1 + + +def _substitution_cost(ch_a, ch_b): + if ch_a == ch_b: + return 0 + if ch_a.lower() == ch_b.lower(): + return _CASE_COST + return _MOVE_COST + + +def _compute_suggestion_error(exc_value, tb, wrong_name): + if wrong_name is None or not isinstance(wrong_name, str): + return None + if isinstance(exc_value, AttributeError): + obj = exc_value.obj + try: + try: + d = dir(obj) + except TypeError: # Attributes are unsortable, e.g. int and str + d = list(obj.__class__.__dict__.keys()) + list(obj.__dict__.keys()) + d = sorted([x for x in d if isinstance(x, str)]) + hide_underscored = (wrong_name[:1] != '_') + if hide_underscored and tb is not None: + while tb.tb_next is not None: + tb = tb.tb_next + frame = tb.tb_frame + if 'self' in frame.f_locals and frame.f_locals['self'] is obj: + hide_underscored = False + if hide_underscored: + d = [x for x in d if x[:1] != '_'] + except Exception: + return None + elif isinstance(exc_value, ImportError): + try: + mod = __import__(exc_value.name) + try: + d = dir(mod) + except TypeError: # Attributes are unsortable, e.g. int and str + d = list(mod.__dict__.keys()) + d = sorted([x for x in d if isinstance(x, str)]) + if wrong_name[:1] != '_': + d = [x for x in d if x[:1] != '_'] + except Exception: + return None + else: + assert isinstance(exc_value, NameError) + # find most recent frame + if tb is None: + return None + while tb.tb_next is not None: + tb = tb.tb_next + frame = tb.tb_frame + d = ( + list(frame.f_locals) + + list(frame.f_globals) + + list(frame.f_builtins) + ) + d = [x for x in d if isinstance(x, str)] + + # Check first if we are in a method and the instance + # has the wrong name as attribute + if 'self' in frame.f_locals: + self = frame.f_locals['self'] + try: + has_wrong_name = hasattr(self, wrong_name) + except Exception: + has_wrong_name = False + if has_wrong_name: + return f"self.{wrong_name}" + + try: + import _suggestions + except ImportError: + pass + else: + return _suggestions._generate_suggestions(d, wrong_name) + + # Compute closest match + + if len(d) > _MAX_CANDIDATE_ITEMS: + return None + wrong_name_len = len(wrong_name) + if wrong_name_len > _MAX_STRING_SIZE: + return None + best_distance = wrong_name_len + suggestion = None + for possible_name in d: + if possible_name == wrong_name: + # A missing attribute is "found". Don't suggest it (see GH-88821). + continue + # No more than 1/3 of the involved characters should need changed. + max_distance = (len(possible_name) + wrong_name_len + 3) * _MOVE_COST // 6 + # Don't take matches we've already beaten. + max_distance = min(max_distance, best_distance - 1) + current_distance = _levenshtein_distance(wrong_name, possible_name, max_distance) + if current_distance > max_distance: + continue + if not suggestion or current_distance < best_distance: + suggestion = possible_name + best_distance = current_distance + return suggestion + + +def _levenshtein_distance(a, b, max_cost): + # A Python implementation of Python/suggestions.c:levenshtein_distance. + + # Both strings are the same + if a == b: + return 0 + + # Trim away common affixes + pre = 0 + while a[pre:] and b[pre:] and a[pre] == b[pre]: + pre += 1 + a = a[pre:] + b = b[pre:] + post = 0 + while a[:post or None] and b[:post or None] and a[post-1] == b[post-1]: + post -= 1 + a = a[:post or None] + b = b[:post or None] + if not a or not b: + return _MOVE_COST * (len(a) + len(b)) + if len(a) > _MAX_STRING_SIZE or len(b) > _MAX_STRING_SIZE: + return max_cost + 1 + + # Prefer shorter buffer + if len(b) < len(a): + a, b = b, a + + # Quick fail when a match is impossible + if (len(b) - len(a)) * _MOVE_COST > max_cost: + return max_cost + 1 + + # Instead of producing the whole traditional len(a)-by-len(b) + # matrix, we can update just one row in place. + # Initialize the buffer row + row = list(range(_MOVE_COST, _MOVE_COST * (len(a) + 1), _MOVE_COST)) + + result = 0 + for bindex in range(len(b)): + bchar = b[bindex] + distance = result = bindex * _MOVE_COST + minimum = sys.maxsize + for index in range(len(a)): + # 1) Previous distance in this row is cost(b[:b_index], a[:index]) + substitute = distance + _substitution_cost(bchar, a[index]) + # 2) cost(b[:b_index], a[:index+1]) from previous row + distance = row[index] + # 3) existing result is cost(b[:b_index+1], a[index]) + + insert_delete = min(result, distance) + _MOVE_COST + result = min(insert_delete, substitute) + + # cost(b[:b_index+1], a[:index+1]) + row[index] = result + if result < minimum: + minimum = result + if minimum > max_cost: + # Everything in this row is too big, so bail early. + return max_cost + 1 + return result diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index c44b2b00684..5cc7d0d2212 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -115,11 +115,22 @@ enum DoneWithFuture { Yes, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct CompileOpts { /// How optimized the bytecode output should be; any optimize > 0 does /// not emit assert statements pub optimize: u8, + /// Include column info in bytecode (-X no_debug_ranges disables) + pub debug_ranges: bool, +} + +impl Default for CompileOpts { + fn default() -> Self { + Self { + optimize: 0, + debug_ranges: true, + } + } } #[derive(Debug, Clone, Copy)] @@ -859,7 +870,7 @@ impl Compiler { let pop = self.code_stack.pop(); let stack_top = compiler_unwrap_option(self, pop); // No parent scope stack to maintain - unwrap_internal(self, stack_top.finalize_code(self.opts.optimize)) + unwrap_internal(self, stack_top.finalize_code(&self.opts)) } /// Push a new fblock @@ -1486,7 +1497,9 @@ impl Compiler { .. }) => self.compile_for(target, iter, body, orelse, *is_async)?, Stmt::Match(StmtMatch { subject, cases, .. }) => self.compile_match(subject, cases)?, - Stmt::Raise(StmtRaise { exc, cause, .. }) => { + Stmt::Raise(StmtRaise { + exc, cause, range, .. + }) => { let kind = match exc { Some(value) => { self.compile_expression(value)?; @@ -1500,6 +1513,7 @@ impl Compiler { } None => bytecode::RaiseKind::Reraise, }; + self.set_source_range(*range); emit!(self, Instruction::Raise { kind }); } Stmt::Try(StmtTry { @@ -5639,17 +5653,15 @@ impl Compiler { // Low level helper functions: fn _emit(&mut self, instr: Instruction, arg: OpArg, target: BlockIdx) { let range = self.current_source_range; - let location = self - .source_file - .to_source_code() - .source_location(range.start(), PositionEncoding::Utf8); - // TODO: insert source filename + let source = self.source_file.to_source_code(); + let location = source.source_location(range.start(), PositionEncoding::Utf8); + let end_location = source.source_location(range.end(), PositionEncoding::Utf8); self.current_block().instructions.push(ir::InstructionInfo { instr, arg, target, location, - // range, + end_location, }); } diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 670635fbd37..31ee8d8b230 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -86,9 +86,8 @@ pub struct InstructionInfo { pub instr: Instruction, pub arg: OpArg, pub target: BlockIdx, - // pub range: TextRange, pub location: SourceLocation, - // TODO: end_location for debug ranges + pub end_location: SourceLocation, } // spell-checker:ignore petgraph @@ -133,8 +132,11 @@ pub struct CodeInfo { } impl CodeInfo { - pub fn finalize_code(mut self, optimize: u8) -> crate::InternalResult<CodeObject> { - if optimize > 0 { + pub fn finalize_code( + mut self, + opts: &crate::compile::CompileOpts, + ) -> crate::InternalResult<CodeObject> { + if opts.optimize > 0 { self.dce(); } @@ -198,7 +200,10 @@ impl CodeInfo { *arg = new_arg; } let (extras, lo_arg) = arg.split(); - locations.extend(core::iter::repeat_n(info.location, arg.instr_size())); + locations.extend(core::iter::repeat_n( + (info.location, info.end_location), + arg.instr_size(), + )); instructions.extend( extras .map(|byte| CodeUnit::new(Instruction::ExtendedArg, byte)) @@ -217,7 +222,11 @@ impl CodeInfo { } // Generate linetable from locations - let linetable = generate_linetable(&locations, first_line_number.get() as i32); + let linetable = generate_linetable( + &locations, + first_line_number.get() as i32, + opts.debug_ranges, + ); Ok(CodeObject { flags, @@ -412,7 +421,11 @@ fn iter_blocks(blocks: &[Block]) -> impl Iterator<Item = (BlockIdx, &Block)> + ' } /// Generate CPython 3.11+ format linetable from source locations -fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8]> { +fn generate_linetable( + locations: &[(SourceLocation, SourceLocation)], + first_line: i32, + debug_ranges: bool, +) -> Box<[u8]> { if locations.is_empty() { return Box::new([]); } @@ -424,7 +437,7 @@ fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8] let mut i = 0; while i < locations.len() { - let loc = &locations[i]; + let (loc, end_loc) = &locations[i]; // Count consecutive instructions with the same location let mut length = 1; @@ -436,18 +449,33 @@ fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8] while length > 0 { let entry_length = length.min(8); - // Get line and column information - // SourceLocation always has row and column (both are OneIndexed) + // Get line information let line = loc.line.get() as i32; - let col = loc.character_offset.to_zero_indexed() as i32; - + let end_line = end_loc.line.get() as i32; let line_delta = line - prev_line; + let end_line_delta = end_line - line; - // Choose the appropriate encoding based on line delta and column info - // Note: SourceLocation always has valid column, so we never get NO_COLUMNS case - if line_delta == 0 { - let end_col = col; // Use same column for end (no range info available) + // When debug_ranges is disabled, only emit line info (NoColumns format) + if !debug_ranges { + // NoColumns format (code 13): line info only, no column data + linetable.push( + 0x80 | ((PyCodeLocationInfoKind::NoColumns as u8) << 3) + | ((entry_length - 1) as u8), + ); + write_signed_varint(&mut linetable, line_delta); + prev_line = line; + length -= entry_length; + i += entry_length; + continue; + } + + // Get column information (only when debug_ranges is enabled) + let col = loc.character_offset.to_zero_indexed() as i32; + let end_col = end_loc.character_offset.to_zero_indexed() as i32; + + // Choose the appropriate encoding based on line delta and column info + if line_delta == 0 && end_line_delta == 0 { if col < 80 && end_col - col < 16 && end_col >= col { // Short form (codes 0-9) for common cases let code = (col / 8).min(9) as u8; // Short0 to Short9 @@ -470,42 +498,37 @@ fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8] ); write_signed_varint(&mut linetable, 0); // line_delta = 0 write_varint(&mut linetable, 0); // end_line delta = 0 - write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding - write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1 + write_varint(&mut linetable, (col as u32) + 1); + write_varint(&mut linetable, (end_col as u32) + 1); } - } else if line_delta > 0 && line_delta < 3 - /* && column.is_some() */ - { + } else if line_delta > 0 && line_delta < 3 && end_line_delta == 0 { // One-line form (codes 11-12) for line deltas 1-2 - let end_col = col; // Use same column for end - if col < 128 && end_col < 128 { - let code = (PyCodeLocationInfoKind::OneLine0 as u8) + (line_delta as u8); // 11 for delta=1, 12 for delta=2 + let code = (PyCodeLocationInfoKind::OneLine0 as u8) + (line_delta as u8); linetable.push(0x80 | (code << 3) | ((entry_length - 1) as u8)); linetable.push(col as u8); linetable.push(end_col as u8); } else { - // Long form for columns >= 128 or negative line delta + // Long form for columns >= 128 linetable.push( 0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3) | ((entry_length - 1) as u8), ); write_signed_varint(&mut linetable, line_delta); write_varint(&mut linetable, 0); // end_line delta = 0 - write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding - write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1 + write_varint(&mut linetable, (col as u32) + 1); + write_varint(&mut linetable, (end_col as u32) + 1); } } else { // Long form (code 14) for all other cases - // This handles: line_delta < 0, line_delta >= 3, or columns >= 128 - let end_col = col; // Use same column for end + // Handles: line_delta < 0, line_delta >= 3, multi-line spans, or columns >= 128 linetable.push( 0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3) | ((entry_length - 1) as u8), ); write_signed_varint(&mut linetable, line_delta); - write_varint(&mut linetable, 0); // end_line delta = 0 - write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding - write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1 + write_varint(&mut linetable, end_line_delta as u32); + write_varint(&mut linetable, (col as u32) + 1); + write_varint(&mut linetable, (end_col as u32) + 1); } prev_line = line; diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 5569fa2012b..7675ef34863 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -258,7 +258,7 @@ impl ConstantBag for BasicBag { #[derive(Clone)] pub struct CodeObject<C: Constant = ConstantData> { pub instructions: CodeUnits, - pub locations: Box<[SourceLocation]>, + pub locations: Box<[(SourceLocation, SourceLocation)]>, pub flags: CodeFlags, /// Number of positional-only arguments pub posonlyarg_count: u32, @@ -1483,14 +1483,14 @@ impl<C: Constant> CodeObject<C> { level: usize, ) -> fmt::Result { let label_targets = self.label_targets(); - let line_digits = (3).max(self.locations.last().unwrap().line.digits().get()); + let line_digits = (3).max(self.locations.last().unwrap().0.line.digits().get()); let offset_digits = (4).max(1 + self.instructions.len().ilog10() as usize); let mut last_line = OneIndexed::MAX; let mut arg_state = OpArgState::default(); for (offset, &instruction) in self.instructions.iter().enumerate() { let (instruction, arg) = arg_state.get(instruction); // optional line number - let line = self.locations[offset].line; + let line = self.locations[offset].0.line; if line != last_line { if last_line != OneIndexed::MAX { writeln!(f)?; diff --git a/crates/compiler-core/src/marshal.rs b/crates/compiler-core/src/marshal.rs index b30894ea065..5b528fe7e50 100644 --- a/crates/compiler-core/src/marshal.rs +++ b/crates/compiler-core/src/marshal.rs @@ -190,12 +190,17 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>( let len = rdr.read_u32()?; let locations = (0..len) .map(|_| { - Ok(SourceLocation { + let start = SourceLocation { line: OneIndexed::new(rdr.read_u32()? as _).ok_or(MarshalError::InvalidLocation)?, character_offset: OneIndexed::from_zero_indexed(rdr.read_u32()? as _), - }) + }; + let end = SourceLocation { + line: OneIndexed::new(rdr.read_u32()? as _).ok_or(MarshalError::InvalidLocation)?, + character_offset: OneIndexed::from_zero_indexed(rdr.read_u32()? as _), + }; + Ok((start, end)) }) - .collect::<Result<Box<[SourceLocation]>>>()?; + .collect::<Result<Box<[(SourceLocation, SourceLocation)]>>>()?; let flags = CodeFlags::from_bits_truncate(rdr.read_u16()?); @@ -648,9 +653,11 @@ pub fn serialize_code<W: Write, C: Constant>(buf: &mut W, code: &CodeObject<C>) buf.write_slice(instructions_bytes); write_len(buf, code.locations.len()); - for loc in &*code.locations { - buf.write_u32(loc.line.get() as _); - buf.write_u32(loc.character_offset.to_zero_indexed() as _); + for (start, end) in &*code.locations { + buf.write_u32(start.line.get() as _); + buf.write_u32(start.character_offset.to_zero_indexed() as _); + buf.write_u32(end.line.get() as _); + buf.write_u32(end.character_offset.to_zero_indexed() as _); } buf.write_u16(code.flags.bits()); diff --git a/crates/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index b897ef9d311..32e53a5d376 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -467,20 +467,22 @@ impl Constructor for PyCode { .collect::<Vec<_>>() .into_boxed_slice(); - // Create locations + // Create locations (start and end pairs) let row = if args.firstlineno > 0 { OneIndexed::new(args.firstlineno as usize).unwrap_or(OneIndexed::MIN) } else { OneIndexed::MIN }; - let locations: Box<[rustpython_compiler_core::SourceLocation]> = vec![ - rustpython_compiler_core::SourceLocation { - line: row, - character_offset: OneIndexed::from_zero_indexed(0), - }; - instructions.len() - ] - .into_boxed_slice(); + let loc = rustpython_compiler_core::SourceLocation { + line: row, + character_offset: OneIndexed::from_zero_indexed(0), + }; + let locations: Box< + [( + rustpython_compiler_core::SourceLocation, + rustpython_compiler_core::SourceLocation, + )], + > = vec![(loc, loc); instructions.len()].into_boxed_slice(); // Build the CodeObject let code = CodeObject { @@ -809,7 +811,6 @@ impl PyCode { Some(line + end_line_delta) }; - // Convert Option to PyObject (None or int) let line_obj = final_line.to_pyobject(vm); let end_line_obj = final_endline.to_pyobject(vm); let column_obj = column.to_pyobject(vm); diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index 3712b04e875..28c4e751476 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -58,12 +58,19 @@ impl Frame { #[pygetset] fn f_lasti(&self) -> u32 { - self.lasti() + // Return byte offset (each instruction is 2 bytes) for compatibility + self.lasti() * 2 } #[pygetset] pub fn f_lineno(&self) -> usize { - self.current_location().line.get() + // If lasti is 0, execution hasn't started yet - use first line number + // Similar to PyCode_Addr2Line which returns co_firstlineno for addr_q < 0 + if self.lasti() == 0 { + self.code.first_line_number.map(|n| n.get()).unwrap_or(1) + } else { + self.current_location().line.get() + } } #[pygetset] diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 6acf0e84795..6500f578dca 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -172,7 +172,7 @@ impl Frame { } pub fn current_location(&self) -> SourceLocation { - self.code.locations[self.lasti() as usize - 1] + self.code.locations[self.lasti() as usize - 1].0 } pub fn lasti(&self) -> u32 { @@ -385,12 +385,12 @@ impl ExecutingFrame<'_> { // 2. Add new entry with current execution position (filename, lineno, code_object) to traceback. // 3. Unwind block stack till appropriate handler is found. - let loc = frame.code.locations[idx]; + let (loc, _end_loc) = frame.code.locations[idx]; let next = exception.__traceback__(); let new_traceback = PyTraceback::new( next, frame.object.to_owned(), - frame.lasti(), + frame.lasti() * 2, loc.line, ); vm_trace!("Adding to traceback: {:?} {:?}", new_traceback, loc.line); diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 7c527b3e0da..ddbf7660f7d 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -508,6 +508,7 @@ impl VirtualMachine { pub fn compile_opts(&self) -> crate::compiler::CompileOpts { crate::compiler::CompileOpts { optimize: self.state.config.settings.optimize, + debug_ranges: self.state.config.settings.code_debug_ranges, } } diff --git a/crates/vm/src/vm/setting.rs b/crates/vm/src/vm/setting.rs index 53e2cef1160..8a307d1852b 100644 --- a/crates/vm/src/vm/setting.rs +++ b/crates/vm/src/vm/setting.rs @@ -57,7 +57,8 @@ pub struct Settings { // int tracemalloc; // int perf_profiling; // int import_time; - // int code_debug_ranges; + /// -X no_debug_ranges: disable column info in bytecode + pub code_debug_ranges: bool, // int show_ref_count; // int dump_refs; // wchar_t *dump_refs_file; @@ -192,6 +193,7 @@ impl Default for Settings { argv: vec![], hash_seed: None, faulthandler: false, + code_debug_ranges: true, buffered_stdio: true, check_hash_pycs_mode: CheckHashPycsMode::Default, allow_external_library: cfg!(feature = "importlib"), diff --git a/examples/dis.rs b/examples/dis.rs index 942643cd54b..1ca350603f9 100644 --- a/examples/dis.rs +++ b/examples/dis.rs @@ -53,7 +53,10 @@ fn main() -> Result<(), lexopt::Error> { return Err("expected at least one argument".into()); } - let opts = compiler::CompileOpts { optimize }; + let opts = compiler::CompileOpts { + optimize, + debug_ranges: true, + }; for script in &scripts { if script.exists() && script.is_file() { diff --git a/src/settings.rs b/src/settings.rs index a63f1a07ccc..54e66086932 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -270,6 +270,7 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> { "faulthandler" => settings.faulthandler = true, "warn_default_encoding" => settings.warn_default_encoding = true, "no_sig_int" => settings.install_signal_handlers = false, + "no_debug_ranges" => settings.code_debug_ranges = false, "int_max_str_digits" => { settings.int_max_str_digits = match value.unwrap().parse() { Ok(digits) if digits == 0 || digits >= 640 => digits, @@ -293,6 +294,9 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> { settings.warn_default_encoding = settings.warn_default_encoding || env_bool("PYTHONWARNDEFAULTENCODING"); settings.faulthandler = settings.faulthandler || env_bool("PYTHONFAULTHANDLER"); + if env_bool("PYTHONNODEBUGRANGES") { + settings.code_debug_ranges = false; + } if settings.dev_mode { settings.warnoptions.push("default".to_owned()); From 7b9f0d9657e86db7627c76e2414c105588838671 Mon Sep 17 00:00:00 2001 From: Ashwin Naren <arihant2math@gmail.com> Date: Tue, 30 Dec 2025 00:07:03 -0800 Subject: [PATCH 637/819] More tkinter (#5784) * fix build * fix usage of Tcl_GetVar2Ex Signed-off-by: Ashwin Naren <arihant2math@gmail.com> * no panic on mainloop Signed-off-by: Ashwin Naren <arihant2math@gmail.com> * add static var Signed-off-by: Ashwin Naren <arihant2math@gmail.com> * fix getvar Signed-off-by: Ashwin Naren <arihant2math@gmail.com> * add globalgetvar support Signed-off-by: Ashwin Naren <arihant2math@gmail.com> * formatting Signed-off-by: Ashwin Naren <arihant2math@gmail.com> * address review * fix build * Auto-format: cargo fmt --all --------- Signed-off-by: Ashwin Naren <arihant2math@gmail.com> Co-authored-by: Jeong YunWon <jeong@youknowone.org> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 8 ++ crates/stdlib/Cargo.toml | 3 +- crates/stdlib/src/tkinter.rs | 144 ++++++++++++++++++++++++++--------- 3 files changed, 118 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index dc5ce974e38..a873aa2d29d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -157,6 +157,14 @@ jobs: cargo build --no-default-features --features ssl-openssl if: runner.os == 'Linux' + # - name: Install tk-dev for tkinter build + # run: sudo apt-get update && sudo apt-get install -y tk-dev + # if: runner.os == 'Linux' + + # - name: Test tkinter build + # run: cargo build --features tkinter + # if: runner.os == 'Linux' + - name: Test example projects run: cargo run --manifest-path example_projects/barebone/Cargo.toml diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 4885319ea17..e943470a3af 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -21,7 +21,7 @@ ssl-rustls = ["ssl", "rustls", "rustls-native-certs", "rustls-pemfile", "rustls- ssl-rustls-fips = ["ssl-rustls", "aws-lc-rs/fips"] ssl-openssl = ["ssl", "openssl", "openssl-sys", "foreign-types-shared", "openssl-probe"] ssl-vendor = ["ssl-openssl", "openssl/vendored"] -tkinter = ["dep:tk-sys", "dep:tcl-sys"] +tkinter = ["dep:tk-sys", "dep:tcl-sys", "dep:widestring"] [dependencies] # rustpython crates @@ -90,6 +90,7 @@ bzip2 = "0.6" # tkinter tk-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0", optional = true } tcl-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0", optional = true } +widestring = { workspace = true, optional = true } chrono.workspace = true # uuid diff --git a/crates/stdlib/src/tkinter.rs b/crates/stdlib/src/tkinter.rs index 49dcdc5f84f..6b7c4387d6c 100644 --- a/crates/stdlib/src/tkinter.rs +++ b/crates/stdlib/src/tkinter.rs @@ -4,8 +4,9 @@ pub(crate) use self::_tkinter::make_module; #[pymodule] mod _tkinter { + use rustpython_vm::convert::IntoPyException; use rustpython_vm::types::Constructor; - use rustpython_vm::{PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; + use rustpython_vm::{Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use rustpython_vm::builtins::{PyInt, PyStr, PyType}; use std::{ffi, ptr}; @@ -72,6 +73,7 @@ mod _tkinter { impl TclObject {} static QUIT_MAIN_LOOP: AtomicBool = AtomicBool::new(false); + static ERROR_IN_CMD: AtomicBool = AtomicBool::new(false); #[pyattr] #[pyclass(name = "tkapp")] @@ -125,7 +127,7 @@ mod _tkinter { interactive: i32, #[pyarg(any)] wantobjects: i32, - #[pyarg(any, default = "true")] + #[pyarg(any, default = true)] want_tk: bool, #[pyarg(any)] sync: i32, @@ -136,11 +138,7 @@ mod _tkinter { impl Constructor for TkApp { type Args = TkAppConstructorArgs; - fn py_new( - _zelf: PyRef<PyType>, - args: Self::Args, - vm: &VirtualMachine, - ) -> PyResult<PyObjectRef> { + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { create(args, vm) } } @@ -168,10 +166,18 @@ mod _tkinter { ))) } + #[derive(Debug, FromArgs)] + struct TkAppGetVarArgs { + #[pyarg(any)] + name: PyObjectRef, + #[pyarg(any, default)] + name2: Option<String>, + } + // TODO: DISALLOW_INSTANTIATION #[pyclass(with(Constructor))] impl TkApp { - fn from_bool(&self, obj: *mut tk_sys::Tcl_Obj) -> bool { + fn tcl_obj_to_bool(&self, obj: *mut tk_sys::Tcl_Obj) -> bool { let mut res = -1; unsafe { if tk_sys::Tcl_GetBooleanFromObj(self.interpreter, obj, &mut res) @@ -184,16 +190,16 @@ mod _tkinter { res != 0 } - fn from_object( + fn tcl_obj_to_pyobject( &self, obj: *mut tk_sys::Tcl_Obj, vm: &VirtualMachine, ) -> PyResult<PyObjectRef> { let type_ptr = unsafe { (*obj).typePtr }; - if type_ptr == ptr::null() { + if type_ptr.is_null() { return self.unicode_from_object(obj, vm); } else if type_ptr == self.old_boolean_type || type_ptr == self.boolean_type { - return Ok(vm.ctx.new_bool(self.from_bool(obj)).into()); + return Ok(vm.ctx.new_bool(self.tcl_obj_to_bool(obj)).into()); } else if type_ptr == self.string_type || type_ptr == self.utf32_string_type || type_ptr == self.pixel_type @@ -202,7 +208,7 @@ mod _tkinter { } // TODO: handle other types - return Ok(TclObject { value: obj }.into_pyobject(vm)); + Ok(TclObject { value: obj }.into_pyobject(vm)) } fn unicode_from_string( @@ -226,8 +232,8 @@ mod _tkinter { vm: &VirtualMachine, ) -> PyResult<PyObjectRef> { let type_ptr = unsafe { (*obj).typePtr }; - if type_ptr != ptr::null() - && self.interpreter != ptr::null_mut() + if !type_ptr.is_null() + && !self.interpreter.is_null() && (type_ptr == self.string_type || type_ptr == self.utf32_string_type) { let len = ptr::null_mut(); @@ -247,30 +253,75 @@ mod _tkinter { Self::unicode_from_string(s, len as _, vm) } - #[pymethod] - fn getvar(&self, arg: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + fn var_invoke(&self) { + if self.threaded && self.thread_id != Some(unsafe { tk_sys::Tcl_GetCurrentThread() }) { + // TODO: do stuff + } + } + + fn inner_getvar( + &self, + args: TkAppGetVarArgs, + flags: u32, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + let TkAppGetVarArgs { name, name2 } = args; // TODO: technically not thread safe - let name = varname_converter(arg, vm)?; + let name = varname_converter(name, vm)?; + let name = ffi::CString::new(name).map_err(|e| e.into_pyexception(vm))?; + let name2 = + ffi::CString::new(name2.unwrap_or_default()).map_err(|e| e.into_pyexception(vm))?; + let name2_ptr = if name2.is_empty() { + ptr::null() + } else { + name2.as_ptr() + }; let res = unsafe { tk_sys::Tcl_GetVar2Ex( self.interpreter, - ptr::null(), name.as_ptr() as _, - tk_sys::TCL_LEAVE_ERR_MSG as _, + name2_ptr as _, + flags as _, ) }; - if res == ptr::null_mut() { - todo!(); + if res.is_null() { + // TODO: Should be tk error + unsafe { + let err_obj = tk_sys::Tcl_GetObjResult(self.interpreter); + let err_str_obj = tk_sys::Tcl_GetString(err_obj); + let err_cstr = ffi::CStr::from_ptr(err_str_obj as _); + return Err(vm.new_type_error(format!("{err_cstr:?}"))); + } } let res = if self.want_objects { - self.from_object(res, vm) + self.tcl_obj_to_pyobject(res, vm) } else { self.unicode_from_object(res, vm) }?; Ok(res) } + #[pymethod] + fn getvar(&self, args: TkAppGetVarArgs, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + self.var_invoke(); + self.inner_getvar(args, tk_sys::TCL_LEAVE_ERR_MSG, vm) + } + + #[pymethod] + fn globalgetvar( + &self, + args: TkAppGetVarArgs, + vm: &VirtualMachine, + ) -> PyResult<PyObjectRef> { + self.var_invoke(); + self.inner_getvar( + args, + tk_sys::TCL_LEAVE_ERR_MSG | tk_sys::TCL_GLOBAL_ONLY, + vm, + ) + } + #[pymethod] fn getint(&self, arg: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> { if let Some(int) = arg.downcast_ref::<PyInt>() { @@ -289,7 +340,21 @@ mod _tkinter { #[pymethod] fn mainloop(&self, threshold: Option<i32>) -> PyResult<()> { let threshold = threshold.unwrap_or(0); - todo!(); + // self.dispatching = true; + QUIT_MAIN_LOOP.store(false, Ordering::Relaxed); + while unsafe { tk_sys::Tk_GetNumMainWindows() } > threshold + && !QUIT_MAIN_LOOP.load(Ordering::Relaxed) + && !ERROR_IN_CMD.load(Ordering::Relaxed) + { + if self.threaded { + unsafe { tk_sys::Tcl_DoOneEvent(0 as _) }; + } else { + unsafe { tk_sys::Tcl_DoOneEvent(tk_sys::TCL_DONT_WAIT as _) }; + // TODO: sleep for the proper time + std::thread::sleep(std::time::Duration::from_millis(1)); + } + } + Ok(()) } #[pymethod] @@ -299,13 +364,15 @@ mod _tkinter { } #[pyfunction] - fn create(args: TkAppConstructorArgs, vm: &VirtualMachine) -> PyResult<PyObjectRef> { + fn create(args: TkAppConstructorArgs, vm: &VirtualMachine) -> PyResult<TkApp> { unsafe { let interp = tk_sys::Tcl_CreateInterp(); let want_objects = args.wantobjects != 0; - let threaded = { + let threaded = !{ let part1 = String::from("tcl_platform"); let part2 = String::from("threaded"); + let part1 = ffi::CString::new(part1).map_err(|e| e.into_pyexception(vm))?; + let part2 = ffi::CString::new(part2).map_err(|e| e.into_pyexception(vm))?; let part1_ptr = part1.as_ptr(); let part2_ptr = part2.as_ptr(); tk_sys::Tcl_GetVar2Ex( @@ -314,7 +381,8 @@ mod _tkinter { part2_ptr as _, tk_sys::TCL_GLOBAL_ONLY as ffi::c_int, ) - } != ptr::null_mut(); + } + .is_null(); let thread_id = tk_sys::Tcl_GetCurrentThread(); let dispatching = false; let trace = None; @@ -339,8 +407,8 @@ mod _tkinter { let double_type = tk_sys::Tcl_GetObjType(double_str.as_ptr() as _); let int_str = String::from("int"); let int_type = tk_sys::Tcl_GetObjType(int_str.as_ptr() as _); - let int_type = if int_type == ptr::null() { - let mut value = *tk_sys::Tcl_NewIntObj(0); + let int_type = if int_type.is_null() { + let mut value = *tk_sys::Tcl_NewWideIntObj(0); let res = value.typePtr; tk_sys::Tcl_DecrRefCount(&mut value); res @@ -374,33 +442,37 @@ mod _tkinter { } if args.interactive != 0 { - tk_sys::Tcl_SetVar( + tk_sys::Tcl_SetVar2( interp, "tcl_interactive".as_ptr() as _, + ptr::null(), "1".as_ptr() as _, tk_sys::TCL_GLOBAL_ONLY as i32, ); } else { - tk_sys::Tcl_SetVar( + tk_sys::Tcl_SetVar2( interp, "tcl_interactive".as_ptr() as _, + ptr::null(), "0".as_ptr() as _, tk_sys::TCL_GLOBAL_ONLY as i32, ); } let argv0 = args.class_name.clone().to_lowercase(); - tk_sys::Tcl_SetVar( + tk_sys::Tcl_SetVar2( interp, "argv0".as_ptr() as _, + ptr::null(), argv0.as_ptr() as _, tk_sys::TCL_GLOBAL_ONLY as i32, ); if !args.want_tk { - tk_sys::Tcl_SetVar( + tk_sys::Tcl_SetVar2( interp, "_tkinter_skip_tk_init".as_ptr() as _, + ptr::null(), "1".as_ptr() as _, tk_sys::TCL_GLOBAL_ONLY as i32, ); @@ -418,11 +490,12 @@ mod _tkinter { argv.push_str("-use "); argv.push_str(&args.use_.unwrap()); } - argv.push_str("\0"); + argv.push('\0'); let argv_ptr = argv.as_ptr() as *mut *mut i8; - tk_sys::Tcl_SetVar( + tk_sys::Tcl_SetVar2( interp, "argv".as_ptr() as _, + ptr::null(), argv_ptr as *const i8, tk_sys::TCL_GLOBAL_ONLY as i32, ); @@ -460,8 +533,7 @@ mod _tkinter { string_type, utf32_string_type, pixel_type, - } - .into_pyobject(vm)) + }) } } From 9fcc471c94844cbfeaa4b6c1853217ea18330f53 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 30 Dec 2025 17:36:15 +0900 Subject: [PATCH 638/819] Prevent tb_next loop (#6596) --- Lib/test/test_raise.py | 1 - crates/vm/src/builtins/traceback.rs | 26 ++++++++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py index 53fce0a501d..605169f4b40 100644 --- a/Lib/test/test_raise.py +++ b/Lib/test/test_raise.py @@ -244,7 +244,6 @@ class TestTracebackType(unittest.TestCase): def raiser(self): raise ValueError - @unittest.expectedFailure # TODO: RUSTPYTHON def test_attrs(self): try: self.raiser() diff --git a/crates/vm/src/builtins/traceback.rs b/crates/vm/src/builtins/traceback.rs index 84493e7125f..675025cd6b6 100644 --- a/crates/vm/src/builtins/traceback.rs +++ b/crates/vm/src/builtins/traceback.rs @@ -1,7 +1,7 @@ use super::PyType; use crate::{ - Context, Py, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, frame::FrameRef, - types::Constructor, + AsObject, Context, Py, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, + frame::FrameRef, types::Constructor, }; use rustpython_common::lock::PyMutex; use rustpython_compiler_core::OneIndexed; @@ -63,8 +63,26 @@ impl PyTraceback { } #[pygetset(setter)] - fn set_tb_next(&self, value: Option<PyRef<Self>>) { - *self.next.lock() = value; + fn set_tb_next( + zelf: &Py<Self>, + value: Option<PyRef<Self>>, + vm: &VirtualMachine, + ) -> PyResult<()> { + if let Some(ref new_next) = value { + let mut cursor = new_next.clone(); + loop { + if cursor.is(zelf) { + return Err(vm.new_value_error("traceback loop detected".to_owned())); + } + let next = cursor.next.lock().clone(); + match next { + Some(n) => cursor = n, + None => break, + } + } + } + *zelf.next.lock() = value; + Ok(()) } } From b98f06214d0f6e8b2dc642e354e53c7ea566d4d4 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 31 Dec 2025 01:42:11 +0900 Subject: [PATCH 639/819] Update issubclass and make mro representation fit to CPython (#5866) --- crates/vm/src/builtins/type.rs | 56 ++++++++++++++------------------ crates/vm/src/object/core.rs | 8 ++++- crates/vm/src/types/slot.rs | 7 ++-- crates/vm/src/types/slot_defs.rs | 5 +-- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 3eca5edc478..178131c96d6 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -50,7 +50,8 @@ unsafe impl crate::object::Traverse for PyType { fn traverse(&self, tracer_fn: &mut crate::object::TraverseFn<'_>) { self.base.traverse(tracer_fn); self.bases.traverse(tracer_fn); - self.mro.traverse(tracer_fn); + // mro contains self as mro[0], so skip traversing to avoid circular reference + // self.mro.traverse(tracer_fn); self.subclasses.traverse(tracer_fn); self.attributes .read_recursive() @@ -341,6 +342,7 @@ impl PyType { metaclass, None, ); + new_type.mro.write().insert(0, new_type.clone()); new_type.init_slots(ctx); @@ -393,6 +395,7 @@ impl PyType { metaclass, None, ); + new_type.mro.write().insert(0, new_type.clone()); // Note: inherit_slots is called in PyClassImpl::init_class after // slots are fully initialized by make_slots() @@ -413,9 +416,8 @@ impl PyType { } pub(crate) fn init_slots(&self, ctx: &Context) { - // Inherit slots from MRO - // Note: self.mro does NOT include self, so we iterate all elements - let mro: Vec<_> = self.mro.read().iter().cloned().collect(); + // Inherit slots from MRO (mro[0] is self, so skip it) + let mro: Vec<_> = self.mro.read()[1..].to_vec(); for base in mro.iter() { self.inherit_slots(base); } @@ -424,7 +426,8 @@ impl PyType { #[allow(clippy::mutable_key_type)] let mut slot_name_set = std::collections::HashSet::new(); - for cls in self.mro.read().iter() { + // mro[0] is self, so skip it; self.attributes is checked separately below + for cls in self.mro.read()[1..].iter() { for &name in cls.attributes.read().keys() { if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") { slot_name_set.insert(name); @@ -503,18 +506,12 @@ impl PyType { /// Equivalent to CPython's find_name_in_mro /// Look in tp_dict of types in MRO - bypasses descriptors and other attribute access machinery fn find_name_in_mro(&self, name: &'static PyStrInterned) -> Option<PyObjectRef> { - // First check in our own dict - if let Some(value) = self.attributes.read().get(name) { - return Some(value.clone()); - } - - // Then check in MRO - for base in self.mro.read().iter() { - if let Some(value) = base.attributes.read().get(name) { + // mro[0] is self, so we just iterate through the entire MRO + for cls in self.mro.read().iter() { + if let Some(value) = cls.attributes.read().get(name) { return Some(value.clone()); } } - None } @@ -530,8 +527,7 @@ impl PyType { } pub fn get_super_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> { - self.mro - .read() + self.mro.read()[1..] .iter() .find_map(|class| class.attributes.read().get(attr_name).cloned()) } @@ -539,9 +535,7 @@ impl PyType { // This is the internal has_attr implementation for fast lookup on a class. pub fn has_attr(&self, attr_name: &'static PyStrInterned) -> bool { self.attributes.read().contains_key(attr_name) - || self - .mro - .read() + || self.mro.read()[1..] .iter() .any(|c| c.attributes.read().contains_key(attr_name)) } @@ -550,10 +544,8 @@ impl PyType { // Gather all members here: let mut attributes = PyAttributes::default(); - for bc in core::iter::once(self) - .chain(self.mro.read().iter().map(|cls| -> &Self { cls })) - .rev() - { + // mro[0] is self, so we iterate through the entire MRO in reverse + for bc in self.mro.read().iter().map(|cls| -> &Self { cls }).rev() { for (name, value) in bc.attributes.read().iter() { attributes.insert(name.to_owned(), value.clone()); } @@ -661,22 +653,21 @@ impl Py<PyType> { /// so only use this if `cls` is known to have not overridden the base __subclasscheck__ magic /// method. pub fn fast_issubclass(&self, cls: &impl Borrow<PyObject>) -> bool { - self.as_object().is(cls.borrow()) || self.mro.read().iter().any(|c| c.is(cls.borrow())) + self.as_object().is(cls.borrow()) || self.mro.read()[1..].iter().any(|c| c.is(cls.borrow())) } pub fn mro_map_collect<F, R>(&self, f: F) -> Vec<R> where F: Fn(&Self) -> R, { - core::iter::once(self) - .chain(self.mro.read().iter().map(|x| x.deref())) - .map(f) - .collect() + self.mro.read().iter().map(|x| x.deref()).map(f).collect() } pub fn mro_collect(&self) -> Vec<PyRef<PyType>> { - core::iter::once(self) - .chain(self.mro.read().iter().map(|x| x.deref())) + self.mro + .read() + .iter() + .map(|x| x.deref()) .map(|x| x.to_owned()) .collect() } @@ -745,8 +736,11 @@ impl PyType { *zelf.bases.write() = bases; // Recursively update the mros of this class and all subclasses fn update_mro_recursively(cls: &PyType, vm: &VirtualMachine) -> PyResult<()> { - *cls.mro.write() = + let mut mro = PyType::resolve_mro(&cls.bases.read()).map_err(|msg| vm.new_type_error(msg))?; + // Preserve self (mro[0]) when updating MRO + mro.insert(0, cls.mro.read()[0].to_owned()); + *cls.mro.write() = mro; for subclass in cls.subclasses.write().iter() { let subclass = subclass.upgrade().unwrap(); let subclass: &Py<PyType> = subclass.downcast_ref().unwrap(); diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index cee3fac266e..e904117b06a 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -1310,12 +1310,16 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) { ptr::write(&mut (*type_type_ptr).typ, PyAtomicRef::from(type_type)); let object_type = PyTypeRef::from_raw(object_type_ptr.cast()); + // object's mro is [object] + (*object_type_ptr).payload.mro = PyRwLock::new(vec![object_type.clone()]); - (*type_type_ptr).payload.mro = PyRwLock::new(vec![object_type.clone()]); (*type_type_ptr).payload.bases = PyRwLock::new(vec![object_type.clone()]); (*type_type_ptr).payload.base = Some(object_type.clone()); let type_type = PyTypeRef::from_raw(type_type_ptr.cast()); + // type's mro is [type, object] + (*type_type_ptr).payload.mro = + PyRwLock::new(vec![type_type.clone(), object_type.clone()]); (type_type, object_type) } @@ -1331,6 +1335,8 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) { heaptype_ext: None, }; let weakref_type = PyRef::new_ref(weakref_type, type_type.clone(), None); + // weakref's mro is [weakref, object] + weakref_type.mro.write().insert(0, weakref_type.clone()); object_type.subclasses.write().push( type_type diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 9d7d09f2d18..085cfea5cee 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -760,8 +760,9 @@ impl PyType { let in_self = cmp_names.iter().any(|n| attrs.contains_key(*n)); drop(attrs); + // mro[0] is self, so skip it since we already checked self above in_self - || self.mro.read().iter().any(|cls| { + || self.mro.read()[1..].iter().any(|cls| { let attrs = cls.attributes.read(); cmp_names.iter().any(|n| { if let Some(attr) = attrs.get(*n) { @@ -1273,8 +1274,8 @@ impl PyType { return None; } - // Look up in MRO - for cls in self.mro.read().iter() { + // Look up in MRO (mro[0] is self, so skip it) + for cls in self.mro.read()[1..].iter() { if let Some(attr) = cls.attributes.read().get(name).cloned() { if let Some(func) = try_extract(&attr) { return Some(func); diff --git a/crates/vm/src/types/slot_defs.rs b/crates/vm/src/types/slot_defs.rs index 0d8dbf4b0cc..958adb2f27e 100644 --- a/crates/vm/src/types/slot_defs.rs +++ b/crates/vm/src/types/slot_defs.rs @@ -405,8 +405,9 @@ impl SlotAccessor { /// Inherit slot value from MRO pub fn inherit_from_mro(&self, typ: &crate::builtins::PyType) { - // Note: typ.mro does NOT include typ itself - let mro = typ.mro.read(); + // mro[0] is self, so skip it + let mro_guard = typ.mro.read(); + let mro = &mro_guard[1..]; macro_rules! inherit_main { ($slot:ident) => {{ From ca1c4c1f715c32ab6a60f6d395c9cbc6df3c56e4 Mon Sep 17 00:00:00 2001 From: Vinh Nguyen <89290241+ntvinh2005@users.noreply.github.com> Date: Tue, 30 Dec 2025 11:54:50 -0500 Subject: [PATCH 640/819] feat: Sync code.py + test_code_module.py from CPython v3.13.1 (#6181) * feat: Sync code module from cpython 3.13 * also udpate test_code_module --------- Co-authored-by: Jeong YunWon <jeong@youknowone.org> --- Lib/code.py | 203 +++++++++++++++++++++++----------- Lib/test/test_code_module.py | 208 ++++++++++++++++++++++++++++++++--- 2 files changed, 326 insertions(+), 85 deletions(-) diff --git a/Lib/code.py b/Lib/code.py index 2bd5fa3e795..2777c311187 100644 --- a/Lib/code.py +++ b/Lib/code.py @@ -5,6 +5,7 @@ # Inspired by similar code by Jeff Epler and Fredrik Lundh. +import builtins import sys import traceback from codeop import CommandCompiler, compile_command @@ -12,6 +13,7 @@ __all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact", "compile_command"] + class InteractiveInterpreter: """Base class for InteractiveConsole. @@ -24,10 +26,10 @@ class InteractiveInterpreter: def __init__(self, locals=None): """Constructor. - The optional 'locals' argument specifies the dictionary in - which code will be executed; it defaults to a newly created - dictionary with key "__name__" set to "__console__" and key - "__doc__" set to None. + The optional 'locals' argument specifies a mapping to use as the + namespace in which code will be executed; it defaults to a newly + created dictionary with key "__name__" set to "__console__" and + key "__doc__" set to None. """ if locals is None: @@ -63,7 +65,7 @@ def runsource(self, source, filename="<input>", symbol="single"): code = self.compile(source, filename, symbol) except (OverflowError, SyntaxError, ValueError): # Case 1 - self.showsyntaxerror(filename) + self.showsyntaxerror(filename, source=source) return False if code is None: @@ -93,7 +95,7 @@ def runcode(self, code): except: self.showtraceback() - def showsyntaxerror(self, filename=None): + def showsyntaxerror(self, filename=None, **kwargs): """Display the syntax error that just occurred. This doesn't display a stack trace because there isn't one. @@ -105,29 +107,14 @@ def showsyntaxerror(self, filename=None): The output is written by self.write(), below. """ - type, value, tb = sys.exc_info() - sys.last_exc = value - sys.last_type = type - sys.last_value = value - sys.last_traceback = tb - if filename and type is SyntaxError: - # Work hard to stuff the correct filename in the exception - try: - msg, (dummy_filename, lineno, offset, line) = value.args - except ValueError: - # Not the format we expect; leave it alone - pass - else: - # Stuff in the right filename - value = SyntaxError(msg, (filename, lineno, offset, line)) - sys.last_exc = sys.last_value = value - if sys.excepthook is sys.__excepthook__: - lines = traceback.format_exception_only(type, value) - self.write(''.join(lines)) - else: - # If someone has set sys.excepthook, we let that take precedence - # over self.write - sys.excepthook(type, value, tb) + try: + typ, value, tb = sys.exc_info() + if filename and issubclass(typ, SyntaxError): + value.filename = filename + source = kwargs.pop('source', "") + self._showtraceback(typ, value, None, source) + finally: + typ = value = tb = None def showtraceback(self): """Display the exception that just occurred. @@ -137,19 +124,46 @@ def showtraceback(self): The output is written by self.write(), below. """ - sys.last_type, sys.last_value, last_tb = ei = sys.exc_info() - sys.last_traceback = last_tb - sys.last_exc = ei[1] try: - lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next) - if sys.excepthook is sys.__excepthook__: - self.write(''.join(lines)) - else: - # If someone has set sys.excepthook, we let that take precedence - # over self.write - sys.excepthook(ei[0], ei[1], last_tb) + typ, value, tb = sys.exc_info() + self._showtraceback(typ, value, tb.tb_next, '') finally: - last_tb = ei = None + typ = value = tb = None + + def _showtraceback(self, typ, value, tb, source): + sys.last_type = typ + sys.last_traceback = tb + value = value.with_traceback(tb) + # Set the line of text that the exception refers to + lines = source.splitlines() + if (source and typ is SyntaxError + and not value.text and value.lineno is not None + and len(lines) >= value.lineno): + value.text = lines[value.lineno - 1] + sys.last_exc = sys.last_value = value = value.with_traceback(tb) + if sys.excepthook is sys.__excepthook__: + self._excepthook(typ, value, tb) + else: + # If someone has set sys.excepthook, we let that take precedence + # over self.write + try: + sys.excepthook(typ, value, tb) + except SystemExit: + raise + except BaseException as e: + e.__context__ = None + e = e.with_traceback(e.__traceback__.tb_next) + print('Error in sys.excepthook:', file=sys.stderr) + sys.__excepthook__(type(e), e, e.__traceback__) + print(file=sys.stderr) + print('Original exception was:', file=sys.stderr) + sys.__excepthook__(typ, value, tb) + + def _excepthook(self, typ, value, tb): + # This method is being overwritten in + # _pyrepl.console.InteractiveColoredConsole + lines = traceback.format_exception(typ, value, tb) + self.write(''.join(lines)) def write(self, data): """Write a string. @@ -169,7 +183,7 @@ class InteractiveConsole(InteractiveInterpreter): """ - def __init__(self, locals=None, filename="<console>"): + def __init__(self, locals=None, filename="<console>", *, local_exit=False): """Constructor. The optional locals argument will be passed to the @@ -181,6 +195,7 @@ def __init__(self, locals=None, filename="<console>"): """ InteractiveInterpreter.__init__(self, locals) self.filename = filename + self.local_exit = local_exit self.resetbuffer() def resetbuffer(self): @@ -219,29 +234,66 @@ def interact(self, banner=None, exitmsg=None): elif banner: self.write("%s\n" % str(banner)) more = 0 - while 1: - try: - if more: - prompt = sys.ps2 - else: - prompt = sys.ps1 + + # When the user uses exit() or quit() in their interactive shell + # they probably just want to exit the created shell, not the whole + # process. exit and quit in builtins closes sys.stdin which makes + # it super difficult to restore + # + # When self.local_exit is True, we overwrite the builtins so + # exit() and quit() only raises SystemExit and we can catch that + # to only exit the interactive shell + + _exit = None + _quit = None + + if self.local_exit: + if hasattr(builtins, "exit"): + _exit = builtins.exit + builtins.exit = Quitter("exit") + + if hasattr(builtins, "quit"): + _quit = builtins.quit + builtins.quit = Quitter("quit") + + try: + while True: try: - line = self.raw_input(prompt) - except EOFError: - self.write("\n") - break - else: - more = self.push(line) - except KeyboardInterrupt: - self.write("\nKeyboardInterrupt\n") - self.resetbuffer() - more = 0 - if exitmsg is None: - self.write('now exiting %s...\n' % self.__class__.__name__) - elif exitmsg != '': - self.write('%s\n' % exitmsg) - - def push(self, line): + if more: + prompt = sys.ps2 + else: + prompt = sys.ps1 + try: + line = self.raw_input(prompt) + except EOFError: + self.write("\n") + break + else: + more = self.push(line) + except KeyboardInterrupt: + self.write("\nKeyboardInterrupt\n") + self.resetbuffer() + more = 0 + except SystemExit as e: + if self.local_exit: + self.write("\n") + break + else: + raise e + finally: + # restore exit and quit in builtins if they were modified + if _exit is not None: + builtins.exit = _exit + + if _quit is not None: + builtins.quit = _quit + + if exitmsg is None: + self.write('now exiting %s...\n' % self.__class__.__name__) + elif exitmsg != '': + self.write('%s\n' % exitmsg) + + def push(self, line, filename=None, _symbol="single"): """Push a line to the interpreter. The line should not have a trailing newline; it may have @@ -257,7 +309,9 @@ def push(self, line): """ self.buffer.append(line) source = "\n".join(self.buffer) - more = self.runsource(source, self.filename) + if filename is None: + filename = self.filename + more = self.runsource(source, filename, symbol=_symbol) if not more: self.resetbuffer() return more @@ -276,8 +330,22 @@ def raw_input(self, prompt=""): return input(prompt) +class Quitter: + def __init__(self, name): + self.name = name + if sys.platform == "win32": + self.eof = 'Ctrl-Z plus Return' + else: + self.eof = 'Ctrl-D (i.e. EOF)' + + def __repr__(self): + return f'Use {self.name} or {self.eof} to exit' + + def __call__(self, code=None): + raise SystemExit(code) + -def interact(banner=None, readfunc=None, local=None, exitmsg=None): +def interact(banner=None, readfunc=None, local=None, exitmsg=None, local_exit=False): """Closely emulate the interactive Python interpreter. This is a backwards compatible interface to the InteractiveConsole @@ -290,9 +358,10 @@ def interact(banner=None, readfunc=None, local=None, exitmsg=None): readfunc -- if not None, replaces InteractiveConsole.raw_input() local -- passed to InteractiveInterpreter.__init__() exitmsg -- passed to InteractiveConsole.interact() + local_exit -- passed to InteractiveConsole.__init__() """ - console = InteractiveConsole(local) + console = InteractiveConsole(local, local_exit=local_exit) if readfunc is not None: console.raw_input = readfunc else: @@ -308,7 +377,7 @@ def interact(banner=None, readfunc=None, local=None, exitmsg=None): parser = argparse.ArgumentParser() parser.add_argument('-q', action='store_true', - help="don't print version and copyright messages") + help="don't print version and copyright messages") args = parser.parse_args() if args.q or sys.flags.quiet: banner = '' diff --git a/Lib/test/test_code_module.py b/Lib/test/test_code_module.py index 5ac17ef16ea..346cf6746a7 100644 --- a/Lib/test/test_code_module.py +++ b/Lib/test/test_code_module.py @@ -1,20 +1,17 @@ "Test InteractiveConsole and InteractiveInterpreter from code module" import sys +import traceback import unittest from textwrap import dedent from contextlib import ExitStack from unittest import mock +from test.support import force_not_colorized_test_class from test.support import import_helper - code = import_helper.import_module('code') -class TestInteractiveConsole(unittest.TestCase): - - def setUp(self): - self.console = code.InteractiveConsole() - self.mock_sys() +class MockSys: def mock_sys(self): "Mock system environment for InteractiveConsole" @@ -32,6 +29,15 @@ def mock_sys(self): del self.sysmod.ps1 del self.sysmod.ps2 + +@force_not_colorized_test_class +class TestInteractiveConsole(unittest.TestCase, MockSys): + maxDiff = None + + def setUp(self): + self.console = code.InteractiveConsole() + self.mock_sys() + def test_ps1(self): self.infunc.side_effect = EOFError('Finished') self.console.interact() @@ -44,9 +50,9 @@ def test_ps2(self): self.infunc.side_effect = EOFError('Finished') self.console.interact() self.assertEqual(self.sysmod.ps2, '... ') - self.sysmod.ps1 = 'custom2> ' + self.sysmod.ps2 = 'custom2> ' self.console.interact() - self.assertEqual(self.sysmod.ps1, 'custom2> ') + self.assertEqual(self.sysmod.ps2, 'custom2> ') def test_console_stderr(self): self.infunc.side_effect = ["'antioch'", "", EOFError('Finished')] @@ -57,22 +63,162 @@ def test_console_stderr(self): else: raise AssertionError("no console stdout") + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_syntax_error(self): - self.infunc.side_effect = ["undefined", EOFError('Finished')] + self.infunc.side_effect = ["def f():", + " x = ?", + "", + EOFError('Finished')] self.console.interact() - for call in self.stderr.method_calls: - if 'NameError' in ''.join(call[1]): - break - else: - raise AssertionError("No syntax error from console") + output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) + output = output[output.index('(InteractiveConsole)'):] + output = output[:output.index('\nnow exiting')] + self.assertEqual(output.splitlines()[1:], [ + ' File "<console>", line 2', + ' x = ?', + ' ^', + 'SyntaxError: invalid syntax']) + self.assertIs(self.sysmod.last_type, SyntaxError) + self.assertIs(type(self.sysmod.last_value), SyntaxError) + self.assertIsNone(self.sysmod.last_traceback) + self.assertIsNone(self.sysmod.last_value.__traceback__) + self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_indentation_error(self): + self.infunc.side_effect = [" 1", EOFError('Finished')] + self.console.interact() + output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) + output = output[output.index('(InteractiveConsole)'):] + output = output[:output.index('\nnow exiting')] + self.assertEqual(output.splitlines()[1:], [ + ' File "<console>", line 1', + ' 1', + 'IndentationError: unexpected indent']) + self.assertIs(self.sysmod.last_type, IndentationError) + self.assertIs(type(self.sysmod.last_value), IndentationError) + self.assertIsNone(self.sysmod.last_traceback) + self.assertIsNone(self.sysmod.last_value.__traceback__) + self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_unicode_error(self): + self.infunc.side_effect = ["'\ud800'", EOFError('Finished')] + self.console.interact() + output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) + output = output[output.index('(InteractiveConsole)'):] + output = output[output.index('\n') + 1:] + self.assertTrue(output.startswith('UnicodeEncodeError: '), output) + self.assertIs(self.sysmod.last_type, UnicodeEncodeError) + self.assertIs(type(self.sysmod.last_value), UnicodeEncodeError) + self.assertIsNone(self.sysmod.last_traceback) + self.assertIsNone(self.sysmod.last_value.__traceback__) + self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) def test_sysexcepthook(self): - self.infunc.side_effect = ["raise ValueError('')", + self.infunc.side_effect = ["def f():", + " raise ValueError('BOOM!')", + "", + "f()", + EOFError('Finished')] + hook = mock.Mock() + self.sysmod.excepthook = hook + self.console.interact() + hook.assert_called() + hook.assert_called_with(self.sysmod.last_type, + self.sysmod.last_value, + self.sysmod.last_traceback) + self.assertIs(self.sysmod.last_type, ValueError) + self.assertIs(type(self.sysmod.last_value), ValueError) + self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__) + self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) + self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [ + 'Traceback (most recent call last):\n', + ' File "<console>", line 1, in <module>\n', + ' File "<console>", line 2, in f\n', + 'ValueError: BOOM!\n']) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_sysexcepthook_syntax_error(self): + self.infunc.side_effect = ["def f():", + " x = ?", + "", EOFError('Finished')] hook = mock.Mock() self.sysmod.excepthook = hook self.console.interact() - self.assertTrue(hook.called) + hook.assert_called() + hook.assert_called_with(self.sysmod.last_type, + self.sysmod.last_value, + self.sysmod.last_traceback) + self.assertIs(self.sysmod.last_type, SyntaxError) + self.assertIs(type(self.sysmod.last_value), SyntaxError) + self.assertIsNone(self.sysmod.last_traceback) + self.assertIsNone(self.sysmod.last_value.__traceback__) + self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) + self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [ + ' File "<console>", line 2\n', + ' x = ?\n', + ' ^\n', + 'SyntaxError: invalid syntax\n']) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_sysexcepthook_indentation_error(self): + self.infunc.side_effect = [" 1", EOFError('Finished')] + hook = mock.Mock() + self.sysmod.excepthook = hook + self.console.interact() + hook.assert_called() + hook.assert_called_with(self.sysmod.last_type, + self.sysmod.last_value, + self.sysmod.last_traceback) + self.assertIs(self.sysmod.last_type, IndentationError) + self.assertIs(type(self.sysmod.last_value), IndentationError) + self.assertIsNone(self.sysmod.last_traceback) + self.assertIsNone(self.sysmod.last_value.__traceback__) + self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) + self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [ + ' File "<console>", line 1\n', + ' 1\n', + 'IndentationError: unexpected indent\n']) + + def test_sysexcepthook_crashing_doesnt_close_repl(self): + self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')] + self.sysmod.excepthook = 1 + self.console.interact() + self.assertEqual(['write', ('123', ), {}], self.stdout.method_calls[0]) + error = "".join(call.args[0] for call in self.stderr.method_calls if call[0] == 'write') + self.assertIn("Error in sys.excepthook:", error) + self.assertEqual(error.count("'int' object is not callable"), 1) + self.assertIn("Original exception was:", error) + self.assertIn("division by zero", error) + + def test_sysexcepthook_raising_BaseException(self): + self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')] + s = "not so fast" + def raise_base(*args, **kwargs): + raise BaseException(s) + self.sysmod.excepthook = raise_base + self.console.interact() + self.assertEqual(['write', ('123', ), {}], self.stdout.method_calls[0]) + error = "".join(call.args[0] for call in self.stderr.method_calls if call[0] == 'write') + self.assertIn("Error in sys.excepthook:", error) + self.assertEqual(error.count("not so fast"), 1) + self.assertIn("Original exception was:", error) + self.assertIn("division by zero", error) + + def test_sysexcepthook_raising_SystemExit_gets_through(self): + self.infunc.side_effect = ["1/0"] + def raise_base(*args, **kwargs): + raise SystemExit + self.sysmod.excepthook = raise_base + with self.assertRaises(SystemExit): + self.console.interact() def test_banner(self): # with banner @@ -115,6 +261,7 @@ def test_exit_msg(self): expected = message + '\n' self.assertEqual(err_msg, ['write', (expected,), {}]) + # TODO: RUSTPYTHON @unittest.expectedFailure def test_cause_tb(self): @@ -132,9 +279,12 @@ def test_cause_tb(self): ValueError """) self.assertIn(expected, output) + self.assertIs(self.sysmod.last_type, ValueError) + self.assertIs(type(self.sysmod.last_value), ValueError) + self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__) + self.assertIsNotNone(self.sysmod.last_traceback) + self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_context_tb(self): self.infunc.side_effect = ["try: ham\nexcept: eggs\n", EOFError('Finished')] @@ -152,6 +302,28 @@ def test_context_tb(self): NameError: name 'eggs' is not defined """) self.assertIn(expected, output) + self.assertIs(self.sysmod.last_type, NameError) + self.assertIs(type(self.sysmod.last_value), NameError) + self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__) + self.assertIsNotNone(self.sysmod.last_traceback) + self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) + + +class TestInteractiveConsoleLocalExit(unittest.TestCase, MockSys): + + def setUp(self): + self.console = code.InteractiveConsole(local_exit=True) + self.mock_sys() + + @unittest.skipIf(sys.flags.no_site, "exit() isn't defined unless there's a site module") + def test_exit(self): + # default exit message + self.infunc.side_effect = ["exit()"] + self.console.interact(banner='') + self.assertEqual(len(self.stderr.method_calls), 2) + err_msg = self.stderr.method_calls[1] + expected = 'now exiting InteractiveConsole...\n' + self.assertEqual(err_msg, ['write', (expected,), {}]) if __name__ == "__main__": From 6bf1ab65c08b614d4d15c716ba84e8dcf2774111 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 31 Dec 2025 01:58:34 +0900 Subject: [PATCH 641/819] Initiailzer for PyCStructure (#6586) --- crates/vm/src/stdlib/ctypes/structure.rs | 140 ++++++++++++++--------- crates/vm/src/stdlib/ctypes/union.rs | 8 +- 2 files changed, 94 insertions(+), 54 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 295ce0d87cf..732f3c66801 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -368,6 +368,7 @@ impl PyCStructType { // Store StgInfo with aligned size and total alignment let mut stg_info = StgInfo::new(aligned_size, total_align); + stg_info.length = fields.len(); stg_info.format = Some(format); stg_info.flags |= StgInfoFlags::DICTFLAG_FINAL; // Mark as finalized if has_pointer { @@ -511,7 +512,7 @@ impl Debug for PyCStructure { impl Constructor for PyCStructure { type Args = FuncArgs; - fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { // Check for abstract class and extract values in a block to drop the borrow let (total_size, total_align, length) = { let stg_info = cls.stg_info(vm)?; @@ -523,79 +524,116 @@ impl Constructor for PyCStructure { stg_info_mut.flags |= StgInfoFlags::DICTFLAG_FINAL; } - // Get _fields_ from the class using get_attr to properly search MRO - let fields_attr = cls.as_object().get_attr("_fields_", vm).ok(); + // Initialize buffer with zeros using computed size + let mut new_stg_info = StgInfo::new(total_size, total_align); + new_stg_info.length = length; + PyCStructure(PyCData::from_stg_info(&new_stg_info)) + .into_ref_with_type(vm, cls) + .map(Into::into) + } + + fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { + unimplemented!("use slot_new") + } +} + +impl PyCStructure { + /// Recursively initialize positional arguments through inheritance chain + /// Returns the number of arguments consumed + fn init_pos_args( + self_obj: &Py<Self>, + type_obj: &Py<PyType>, + args: &[PyObjectRef], + kwargs: &indexmap::IndexMap<String, PyObjectRef>, + index: usize, + vm: &VirtualMachine, + ) -> PyResult<usize> { + let mut current_index = index; - // Collect field names for initialization - let mut field_names: Vec<String> = Vec::new(); - if let Some(fields_attr) = fields_attr { - let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>() + // 1. First process base class fields recursively + let base_clone = { + let bases = type_obj.bases.read(); + if let Some(base) = bases.first() + && base.stg_info_opt().is_some() { - list.borrow_vec().to_vec() - } else if let Some(tuple) = fields_attr.downcast_ref::<PyTuple>() { - tuple.to_vec() + Some(base.clone()) } else { - vec![] - }; + None + } + }; + + if let Some(ref base) = base_clone { + current_index = Self::init_pos_args(self_obj, base, args, kwargs, current_index, vm)?; + } + + // 2. Process this class's _fields_ + if let Some(fields_attr) = type_obj.get_direct_attr(vm.ctx.intern_str("_fields_")) { + let fields: Vec<PyObjectRef> = fields_attr.try_to_value(vm)?; for field in fields.iter() { - let Some(field_tuple) = field.downcast_ref::<PyTuple>() else { - continue; - }; - if field_tuple.len() < 2 { - continue; + if current_index >= args.len() { + break; } - if let Some(name) = field_tuple.first().unwrap().downcast_ref::<PyStr>() { - field_names.push(name.to_string()); + if let Some(tuple) = field.downcast_ref::<PyTuple>() + && let Some(name) = tuple.first() + && let Some(name_str) = name.downcast_ref::<PyStr>() + { + let field_name = name_str.as_str().to_owned(); + // Check for duplicate in kwargs + if kwargs.contains_key(&field_name) { + return Err(vm.new_type_error(format!( + "duplicate values for field {:?}", + field_name + ))); + } + self_obj.as_object().set_attr( + vm.ctx.intern_str(field_name), + args[current_index].clone(), + vm, + )?; + current_index += 1; } } } - // Initialize buffer with zeros using computed size - let mut stg_info = StgInfo::new(total_size, total_align); - stg_info.length = if length > 0 { - length - } else { - field_names.len() - }; - stg_info.paramfunc = super::base::ParamFunc::Structure; - let instance = PyCStructure(PyCData::from_stg_info(&stg_info)); + Ok(current_index) + } +} - // Handle keyword arguments for field initialization - let py_instance = instance.into_ref_with_type(vm, cls.clone())?; - let py_obj: PyObjectRef = py_instance.clone().into(); +impl Initializer for PyCStructure { + type Args = FuncArgs; - // Set field values from kwargs using standard attribute setting - for (key, value) in args.kwargs.iter() { - if field_names.iter().any(|n| n == key.as_str()) { - py_obj.set_attr(vm.ctx.intern_str(key.as_str()), value.clone(), vm)?; + fn init(zelf: crate::PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + // Struct_init: handle positional and keyword arguments + let cls = zelf.class().to_owned(); + + // 1. Process positional arguments recursively through inheritance chain + if !args.args.is_empty() { + let consumed = + PyCStructure::init_pos_args(&zelf, &cls, &args.args, &args.kwargs, 0, vm)?; + + if consumed < args.args.len() { + return Err(vm.new_type_error("too many initializers")); } } - // Set field values from positional args - if args.args.len() > field_names.len() { - return Err(vm.new_type_error("too many initializers".to_string())); - } - for (i, value) in args.args.iter().enumerate() { - py_obj.set_attr( - vm.ctx.intern_str(field_names[i].as_str()), - value.clone(), - vm, - )?; + // 2. Process keyword arguments + for (key, value) in args.kwargs.iter() { + zelf.as_object() + .set_attr(vm.ctx.intern_str(key.as_str()), value.clone(), vm)?; } - Ok(py_instance.into()) - } - - fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> { - unimplemented!("use slot_new") + Ok(()) } } // Note: GetAttr and SetAttr are not implemented here. // Field access is handled by CField descriptors registered on the class. -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsBuffer))] +#[pyclass( + flags(BASETYPE, IMMUTABLETYPE), + with(Constructor, Initializer, AsBuffer) +)] impl PyCStructure { #[pygetset] fn _b0_(&self) -> Option<PyObjectRef> { diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 0da1ffee3fd..aecef18ec7c 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -273,6 +273,7 @@ impl PyCUnionType { // Store StgInfo with aligned size let mut stg_info = StgInfo::new(aligned_size, total_align); + stg_info.length = fields.len(); stg_info.flags |= StgInfoFlags::DICTFLAG_FINAL | StgInfoFlags::TYPEFLAG_HASUNION; // PEP 3118 doesn't support union. Use 'B' for bytes. stg_info.format = Some("B".to_string()); @@ -431,9 +432,9 @@ impl Constructor for PyCUnion { fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { // Check for abstract class and extract values in a block to drop the borrow - let (total_size, total_align) = { + let (total_size, total_align, length) = { let stg_info = cls.stg_info(vm)?; - (stg_info.size, stg_info.align) + (stg_info.size, stg_info.align, stg_info.length) }; // Mark the class as finalized (instance creation finalizes the type) @@ -442,7 +443,8 @@ impl Constructor for PyCUnion { } // Initialize buffer with zeros using computed size - let new_stg_info = StgInfo::new(total_size, total_align); + let mut new_stg_info = StgInfo::new(total_size, total_align); + new_stg_info.length = length; PyCUnion(PyCData::from_stg_info(&new_stg_info)) .into_ref_with_type(vm, cls) .map(Into::into) From e22091ef606c4eade1850fb5d57fdf0fc3693965 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Tue, 30 Dec 2025 12:31:08 -0500 Subject: [PATCH 642/819] Update straightforward packages Pt 1 (#6595) * Deleted _pycodecs.py * Updated code.py library * Updated the _pydatetime.py lib * Removed distutils package * Updated doctest package * * Updated datetimetester.py * Added back in _pycodecs.py * Used tool to verify datetimetester + test_code_module --- Lib/_pydatetime.py | 62 +- Lib/distutils/README | 13 - Lib/distutils/__init__.py | 13 - Lib/distutils/_msvccompiler.py | 574 ---------- Lib/distutils/archive_util.py | 256 ----- Lib/distutils/bcppcompiler.py | 393 ------- Lib/distutils/ccompiler.py | 1115 ------------------- Lib/distutils/cmd.py | 434 -------- Lib/distutils/command/__init__.py | 31 - Lib/distutils/command/bdist.py | 143 --- Lib/distutils/command/bdist_dumb.py | 123 -- Lib/distutils/command/bdist_msi.py | 741 ------------ Lib/distutils/command/bdist_rpm.py | 582 ---------- Lib/distutils/command/bdist_wininst.py | 367 ------ Lib/distutils/command/build.py | 157 --- Lib/distutils/command/build_clib.py | 209 ---- Lib/distutils/command/build_ext.py | 755 ------------- Lib/distutils/command/build_py.py | 416 ------- Lib/distutils/command/build_scripts.py | 160 --- Lib/distutils/command/check.py | 145 --- Lib/distutils/command/clean.py | 76 -- Lib/distutils/command/command_template | 33 - Lib/distutils/command/config.py | 347 ------ Lib/distutils/command/install.py | 705 ------------ Lib/distutils/command/install_data.py | 79 -- Lib/distutils/command/install_egg_info.py | 97 -- Lib/distutils/command/install_headers.py | 47 - Lib/distutils/command/install_lib.py | 221 ---- Lib/distutils/command/install_scripts.py | 60 - Lib/distutils/command/register.py | 304 ----- Lib/distutils/command/sdist.py | 456 -------- Lib/distutils/command/upload.py | 200 ---- Lib/distutils/config.py | 131 --- Lib/distutils/core.py | 234 ---- Lib/distutils/cygwinccompiler.py | 405 ------- Lib/distutils/debug.py | 5 - Lib/distutils/dep_util.py | 92 -- Lib/distutils/dir_util.py | 223 ---- Lib/distutils/dist.py | 1236 --------------------- Lib/distutils/errors.py | 97 -- Lib/distutils/extension.py | 240 ---- Lib/distutils/fancy_getopt.py | 457 -------- Lib/distutils/file_util.py | 238 ---- Lib/distutils/filelist.py | 327 ------ Lib/distutils/log.py | 77 -- Lib/distutils/msvc9compiler.py | 791 ------------- Lib/distutils/msvccompiler.py | 643 ----------- Lib/distutils/spawn.py | 192 ---- Lib/distutils/sysconfig.py | 556 --------- Lib/distutils/text_file.py | 286 ----- Lib/distutils/unixccompiler.py | 333 ------ Lib/distutils/util.py | 557 ---------- Lib/distutils/version.py | 343 ------ Lib/distutils/versionpredicate.py | 166 --- Lib/doctest.py | 292 +++-- Lib/test/datetimetester.py | 511 +++++++-- Lib/test/test_code_module.py | 8 +- 57 files changed, 638 insertions(+), 17116 deletions(-) delete mode 100644 Lib/distutils/README delete mode 100644 Lib/distutils/__init__.py delete mode 100644 Lib/distutils/_msvccompiler.py delete mode 100644 Lib/distutils/archive_util.py delete mode 100644 Lib/distutils/bcppcompiler.py delete mode 100644 Lib/distutils/ccompiler.py delete mode 100644 Lib/distutils/cmd.py delete mode 100644 Lib/distutils/command/__init__.py delete mode 100644 Lib/distutils/command/bdist.py delete mode 100644 Lib/distutils/command/bdist_dumb.py delete mode 100644 Lib/distutils/command/bdist_msi.py delete mode 100644 Lib/distutils/command/bdist_rpm.py delete mode 100644 Lib/distutils/command/bdist_wininst.py delete mode 100644 Lib/distutils/command/build.py delete mode 100644 Lib/distutils/command/build_clib.py delete mode 100644 Lib/distutils/command/build_ext.py delete mode 100644 Lib/distutils/command/build_py.py delete mode 100644 Lib/distutils/command/build_scripts.py delete mode 100644 Lib/distutils/command/check.py delete mode 100644 Lib/distutils/command/clean.py delete mode 100644 Lib/distutils/command/command_template delete mode 100644 Lib/distutils/command/config.py delete mode 100644 Lib/distutils/command/install.py delete mode 100644 Lib/distutils/command/install_data.py delete mode 100644 Lib/distutils/command/install_egg_info.py delete mode 100644 Lib/distutils/command/install_headers.py delete mode 100644 Lib/distutils/command/install_lib.py delete mode 100644 Lib/distutils/command/install_scripts.py delete mode 100644 Lib/distutils/command/register.py delete mode 100644 Lib/distutils/command/sdist.py delete mode 100644 Lib/distutils/command/upload.py delete mode 100644 Lib/distutils/config.py delete mode 100644 Lib/distutils/core.py delete mode 100644 Lib/distutils/cygwinccompiler.py delete mode 100644 Lib/distutils/debug.py delete mode 100644 Lib/distutils/dep_util.py delete mode 100644 Lib/distutils/dir_util.py delete mode 100644 Lib/distutils/dist.py delete mode 100644 Lib/distutils/errors.py delete mode 100644 Lib/distutils/extension.py delete mode 100644 Lib/distutils/fancy_getopt.py delete mode 100644 Lib/distutils/file_util.py delete mode 100644 Lib/distutils/filelist.py delete mode 100644 Lib/distutils/log.py delete mode 100644 Lib/distutils/msvc9compiler.py delete mode 100644 Lib/distutils/msvccompiler.py delete mode 100644 Lib/distutils/spawn.py delete mode 100644 Lib/distutils/sysconfig.py delete mode 100644 Lib/distutils/text_file.py delete mode 100644 Lib/distutils/unixccompiler.py delete mode 100644 Lib/distutils/util.py delete mode 100644 Lib/distutils/version.py delete mode 100644 Lib/distutils/versionpredicate.py diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index cd0ea900bfb..38e1f764f00 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -402,6 +402,8 @@ def _parse_hh_mm_ss_ff(tstr): raise ValueError("Invalid microsecond component") else: pos += 1 + if not all(map(_is_ascii_digit, tstr[pos:])): + raise ValueError("Non-digit values in fraction") len_remainder = len_str - pos @@ -413,9 +415,6 @@ def _parse_hh_mm_ss_ff(tstr): time_comps[3] = int(tstr[pos:(pos+to_parse)]) if to_parse < 6: time_comps[3] *= _FRACTION_CORRECTION[to_parse-1] - if (len_remainder > to_parse - and not all(map(_is_ascii_digit, tstr[(pos+to_parse):]))): - raise ValueError("Non-digit values in unparsed fraction") return time_comps @@ -556,10 +555,6 @@ def _check_tzinfo_arg(tz): if tz is not None and not isinstance(tz, tzinfo): raise TypeError("tzinfo argument must be None or of a tzinfo subclass") -def _cmperror(x, y): - raise TypeError("can't compare '%s' to '%s'" % ( - type(x).__name__, type(y).__name__)) - def _divide_and_round(a, b): """divide a by b and round result to the nearest integer @@ -970,6 +965,8 @@ def __new__(cls, year, month=None, day=None): @classmethod def fromtimestamp(cls, t): "Construct a date from a POSIX timestamp (like time.time())." + if t is None: + raise TypeError("'NoneType' object cannot be interpreted as an integer") y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t) return cls(y, m, d) @@ -1059,8 +1056,8 @@ def isoformat(self): This is 'YYYY-MM-DD'. References: - - http://www.w3.org/TR/NOTE-datetime - - http://www.cl.cam.ac.uk/~mgk25/iso-time.html + - https://www.w3.org/TR/NOTE-datetime + - https://www.cl.cam.ac.uk/~mgk25/iso-time.html """ return "%04d-%02d-%02d" % (self._year, self._month, self._day) @@ -1108,35 +1105,38 @@ def replace(self, year=None, month=None, day=None): day = self._day return type(self)(year, month, day) + __replace__ = replace + # Comparisons of date objects with other. def __eq__(self, other): - if isinstance(other, date): + if isinstance(other, date) and not isinstance(other, datetime): return self._cmp(other) == 0 return NotImplemented def __le__(self, other): - if isinstance(other, date): + if isinstance(other, date) and not isinstance(other, datetime): return self._cmp(other) <= 0 return NotImplemented def __lt__(self, other): - if isinstance(other, date): + if isinstance(other, date) and not isinstance(other, datetime): return self._cmp(other) < 0 return NotImplemented def __ge__(self, other): - if isinstance(other, date): + if isinstance(other, date) and not isinstance(other, datetime): return self._cmp(other) >= 0 return NotImplemented def __gt__(self, other): - if isinstance(other, date): + if isinstance(other, date) and not isinstance(other, datetime): return self._cmp(other) > 0 return NotImplemented def _cmp(self, other): assert isinstance(other, date) + assert not isinstance(other, datetime) y, m, d = self._year, self._month, self._day y2, m2, d2 = other._year, other._month, other._day return _cmp((y, m, d), (y2, m2, d2)) @@ -1191,7 +1191,7 @@ def isocalendar(self): The first week is 1; Monday is 1 ... Sunday is 7. ISO calendar algorithm taken from - http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm + https://www.phys.uu.nl/~vgent/calendar/isocalendar.htm (used with permission) """ year = self._year @@ -1633,6 +1633,8 @@ def replace(self, hour=None, minute=None, second=None, microsecond=None, fold = self._fold return type(self)(hour, minute, second, microsecond, tzinfo, fold=fold) + __replace__ = replace + # Pickle support. def _getstate(self, protocol=3): @@ -1680,7 +1682,7 @@ class datetime(date): The year, month and day arguments are required. tzinfo may be None, or an instance of a tzinfo subclass. The remaining arguments may be ints. """ - __slots__ = date.__slots__ + time.__slots__ + __slots__ = time.__slots__ def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0): @@ -1979,6 +1981,8 @@ def replace(self, year=None, month=None, day=None, hour=None, return type(self)(year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold) + __replace__ = replace + def _local_timezone(self): if self.tzinfo is None: ts = self._mktime() @@ -2040,7 +2044,7 @@ def isoformat(self, sep='T', timespec='auto'): By default, the fractional part is omitted if self.microsecond == 0. If self.tzinfo is not None, the UTC offset is also attached, giving - giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'. + a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'. Optional argument sep specifies the separator between date and time, default 'T'. @@ -2131,42 +2135,32 @@ def dst(self): def __eq__(self, other): if isinstance(other, datetime): return self._cmp(other, allow_mixed=True) == 0 - elif not isinstance(other, date): - return NotImplemented else: - return False + return NotImplemented def __le__(self, other): if isinstance(other, datetime): return self._cmp(other) <= 0 - elif not isinstance(other, date): - return NotImplemented else: - _cmperror(self, other) + return NotImplemented def __lt__(self, other): if isinstance(other, datetime): return self._cmp(other) < 0 - elif not isinstance(other, date): - return NotImplemented else: - _cmperror(self, other) + return NotImplemented def __ge__(self, other): if isinstance(other, datetime): return self._cmp(other) >= 0 - elif not isinstance(other, date): - return NotImplemented else: - _cmperror(self, other) + return NotImplemented def __gt__(self, other): if isinstance(other, datetime): return self._cmp(other) > 0 - elif not isinstance(other, date): - return NotImplemented else: - _cmperror(self, other) + return NotImplemented def _cmp(self, other, allow_mixed=False): assert isinstance(other, datetime) @@ -2311,7 +2305,6 @@ def __reduce__(self): def _isoweek1monday(year): # Helper to calculate the day number of the Monday starting week 1 - # XXX This could be done more efficiently THURSDAY = 3 firstday = _ymd2ord(year, 1, 1) firstweekday = (firstday + 6) % 7 # See weekday() above @@ -2341,6 +2334,9 @@ def __new__(cls, offset, name=_Omitted): "timedelta(hours=24).") return cls._create(offset, name) + def __init_subclass__(cls): + raise TypeError("type 'datetime.timezone' is not an acceptable base type") + @classmethod def _create(cls, offset, name=None): self = tzinfo.__new__(cls) diff --git a/Lib/distutils/README b/Lib/distutils/README deleted file mode 100644 index 408a203b85d..00000000000 --- a/Lib/distutils/README +++ /dev/null @@ -1,13 +0,0 @@ -This directory contains the Distutils package. - -There's a full documentation available at: - - http://docs.python.org/distutils/ - -The Distutils-SIG web page is also a good starting point: - - http://www.python.org/sigs/distutils-sig/ - -WARNING : Distutils must remain compatible with 2.3 - -$Id$ diff --git a/Lib/distutils/__init__.py b/Lib/distutils/__init__.py deleted file mode 100644 index d823d040a1c..00000000000 --- a/Lib/distutils/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -"""distutils - -The main package for the Python Module Distribution Utilities. Normally -used from a setup script as - - from distutils.core import setup - - setup (...) -""" - -import sys - -__version__ = sys.version[:sys.version.index(' ')] diff --git a/Lib/distutils/_msvccompiler.py b/Lib/distutils/_msvccompiler.py deleted file mode 100644 index 30b3b473985..00000000000 --- a/Lib/distutils/_msvccompiler.py +++ /dev/null @@ -1,574 +0,0 @@ -"""distutils._msvccompiler - -Contains MSVCCompiler, an implementation of the abstract CCompiler class -for Microsoft Visual Studio 2015. - -The module is compatible with VS 2015 and later. You can find legacy support -for older versions in distutils.msvc9compiler and distutils.msvccompiler. -""" - -# Written by Perry Stoll -# hacked by Robin Becker and Thomas Heller to do a better job of -# finding DevStudio (through the registry) -# ported to VS 2005 and VS 2008 by Christian Heimes -# ported to VS 2015 by Steve Dower - -import os -import shutil -import stat -import subprocess -import winreg - -from distutils.errors import DistutilsExecError, DistutilsPlatformError, \ - CompileError, LibError, LinkError -from distutils.ccompiler import CCompiler, gen_lib_options -from distutils import log -from distutils.util import get_platform - -from itertools import count - -def _find_vc2015(): - try: - key = winreg.OpenKeyEx( - winreg.HKEY_LOCAL_MACHINE, - r"Software\Microsoft\VisualStudio\SxS\VC7", - access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY - ) - except OSError: - log.debug("Visual C++ is not registered") - return None, None - - best_version = 0 - best_dir = None - with key: - for i in count(): - try: - v, vc_dir, vt = winreg.EnumValue(key, i) - except OSError: - break - if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir): - try: - version = int(float(v)) - except (ValueError, TypeError): - continue - if version >= 14 and version > best_version: - best_version, best_dir = version, vc_dir - return best_version, best_dir - -def _find_vc2017(): - import _distutils_findvs - import threading - - best_version = 0, # tuple for full version comparisons - best_dir = None - - # We need to call findall() on its own thread because it will - # initialize COM. - all_packages = [] - def _getall(): - all_packages.extend(_distutils_findvs.findall()) - t = threading.Thread(target=_getall) - t.start() - t.join() - - for name, version_str, path, packages in all_packages: - if 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64' in packages: - vc_dir = os.path.join(path, 'VC', 'Auxiliary', 'Build') - if not os.path.isdir(vc_dir): - continue - try: - version = tuple(int(i) for i in version_str.split('.')) - except (ValueError, TypeError): - continue - if version > best_version: - best_version, best_dir = version, vc_dir - try: - best_version = best_version[0] - except IndexError: - best_version = None - return best_version, best_dir - -def _find_vcvarsall(plat_spec): - best_version, best_dir = _find_vc2017() - vcruntime = None - vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86' - if best_version: - vcredist = os.path.join(best_dir, "..", "..", "redist", "MSVC", "**", - "Microsoft.VC141.CRT", "vcruntime140.dll") - try: - import glob - vcruntime = glob.glob(vcredist, recursive=True)[-1] - except (ImportError, OSError, LookupError): - vcruntime = None - - if not best_version: - best_version, best_dir = _find_vc2015() - if best_version: - vcruntime = os.path.join(best_dir, 'redist', vcruntime_plat, - "Microsoft.VC140.CRT", "vcruntime140.dll") - - if not best_version: - log.debug("No suitable Visual C++ version found") - return None, None - - vcvarsall = os.path.join(best_dir, "vcvarsall.bat") - if not os.path.isfile(vcvarsall): - log.debug("%s cannot be found", vcvarsall) - return None, None - - if not vcruntime or not os.path.isfile(vcruntime): - log.debug("%s cannot be found", vcruntime) - vcruntime = None - - return vcvarsall, vcruntime - -def _get_vc_env(plat_spec): - if os.getenv("DISTUTILS_USE_SDK"): - return { - key.lower(): value - for key, value in os.environ.items() - } - - vcvarsall, vcruntime = _find_vcvarsall(plat_spec) - if not vcvarsall: - raise DistutilsPlatformError("Unable to find vcvarsall.bat") - - try: - out = subprocess.check_output( - 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec), - stderr=subprocess.STDOUT, - ).decode('utf-16le', errors='replace') - except subprocess.CalledProcessError as exc: - log.error(exc.output) - raise DistutilsPlatformError("Error executing {}" - .format(exc.cmd)) - - env = { - key.lower(): value - for key, _, value in - (line.partition('=') for line in out.splitlines()) - if key and value - } - - if vcruntime: - env['py_vcruntime_redist'] = vcruntime - return env - -def _find_exe(exe, paths=None): - """Return path to an MSVC executable program. - - Tries to find the program in several places: first, one of the - MSVC program search paths from the registry; next, the directories - in the PATH environment variable. If any of those work, return an - absolute path that is known to exist. If none of them work, just - return the original program name, 'exe'. - """ - if not paths: - paths = os.getenv('path').split(os.pathsep) - for p in paths: - fn = os.path.join(os.path.abspath(p), exe) - if os.path.isfile(fn): - return fn - return exe - -# A map keyed by get_platform() return values to values accepted by -# 'vcvarsall.bat'. Always cross-compile from x86 to work with the -# lighter-weight MSVC installs that do not include native 64-bit tools. -PLAT_TO_VCVARS = { - 'win32' : 'x86', - 'win-amd64' : 'x86_amd64', -} - -# A set containing the DLLs that are guaranteed to be available for -# all micro versions of this Python version. Known extension -# dependencies that are not in this set will be copied to the output -# path. -_BUNDLED_DLLS = frozenset(['vcruntime140.dll']) - -class MSVCCompiler(CCompiler) : - """Concrete class that implements an interface to Microsoft Visual C++, - as defined by the CCompiler abstract class.""" - - compiler_type = 'msvc' - - # Just set this so CCompiler's constructor doesn't barf. We currently - # don't use the 'set_executables()' bureaucracy provided by CCompiler, - # as it really isn't necessary for this sort of single-compiler class. - # Would be nice to have a consistent interface with UnixCCompiler, - # though, so it's worth thinking about. - executables = {} - - # Private class data (need to distinguish C from C++ source for compiler) - _c_extensions = ['.c'] - _cpp_extensions = ['.cc', '.cpp', '.cxx'] - _rc_extensions = ['.rc'] - _mc_extensions = ['.mc'] - - # Needed for the filename generation methods provided by the - # base class, CCompiler. - src_extensions = (_c_extensions + _cpp_extensions + - _rc_extensions + _mc_extensions) - res_extension = '.res' - obj_extension = '.obj' - static_lib_extension = '.lib' - shared_lib_extension = '.dll' - static_lib_format = shared_lib_format = '%s%s' - exe_extension = '.exe' - - - def __init__(self, verbose=0, dry_run=0, force=0): - CCompiler.__init__ (self, verbose, dry_run, force) - # target platform (.plat_name is consistent with 'bdist') - self.plat_name = None - self.initialized = False - - def initialize(self, plat_name=None): - # multi-init means we would need to check platform same each time... - assert not self.initialized, "don't init multiple times" - if plat_name is None: - plat_name = get_platform() - # sanity check for platforms to prevent obscure errors later. - if plat_name not in PLAT_TO_VCVARS: - raise DistutilsPlatformError("--plat-name must be one of {}" - .format(tuple(PLAT_TO_VCVARS))) - - # Get the vcvarsall.bat spec for the requested platform. - plat_spec = PLAT_TO_VCVARS[plat_name] - - vc_env = _get_vc_env(plat_spec) - if not vc_env: - raise DistutilsPlatformError("Unable to find a compatible " - "Visual Studio installation.") - - self._paths = vc_env.get('path', '') - paths = self._paths.split(os.pathsep) - self.cc = _find_exe("cl.exe", paths) - self.linker = _find_exe("link.exe", paths) - self.lib = _find_exe("lib.exe", paths) - self.rc = _find_exe("rc.exe", paths) # resource compiler - self.mc = _find_exe("mc.exe", paths) # message compiler - self.mt = _find_exe("mt.exe", paths) # message compiler - self._vcruntime_redist = vc_env.get('py_vcruntime_redist', '') - - for dir in vc_env.get('include', '').split(os.pathsep): - if dir: - self.add_include_dir(dir.rstrip(os.sep)) - - for dir in vc_env.get('lib', '').split(os.pathsep): - if dir: - self.add_library_dir(dir.rstrip(os.sep)) - - self.preprocess_options = None - # If vcruntime_redist is available, link against it dynamically. Otherwise, - # use /MT[d] to build statically, then switch from libucrt[d].lib to ucrt[d].lib - # later to dynamically link to ucrtbase but not vcruntime. - self.compile_options = [ - '/nologo', '/Ox', '/W3', '/GL', '/DNDEBUG' - ] - self.compile_options.append('/MD' if self._vcruntime_redist else '/MT') - - self.compile_options_debug = [ - '/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG' - ] - - ldflags = [ - '/nologo', '/INCREMENTAL:NO', '/LTCG' - ] - if not self._vcruntime_redist: - ldflags.extend(('/nodefaultlib:libucrt.lib', 'ucrt.lib')) - - ldflags_debug = [ - '/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL' - ] - - self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1'] - self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1'] - self.ldflags_shared = [*ldflags, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO'] - self.ldflags_shared_debug = [*ldflags_debug, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO'] - self.ldflags_static = [*ldflags] - self.ldflags_static_debug = [*ldflags_debug] - - self._ldflags = { - (CCompiler.EXECUTABLE, None): self.ldflags_exe, - (CCompiler.EXECUTABLE, False): self.ldflags_exe, - (CCompiler.EXECUTABLE, True): self.ldflags_exe_debug, - (CCompiler.SHARED_OBJECT, None): self.ldflags_shared, - (CCompiler.SHARED_OBJECT, False): self.ldflags_shared, - (CCompiler.SHARED_OBJECT, True): self.ldflags_shared_debug, - (CCompiler.SHARED_LIBRARY, None): self.ldflags_static, - (CCompiler.SHARED_LIBRARY, False): self.ldflags_static, - (CCompiler.SHARED_LIBRARY, True): self.ldflags_static_debug, - } - - self.initialized = True - - # -- Worker methods ------------------------------------------------ - - def object_filenames(self, - source_filenames, - strip_dir=0, - output_dir=''): - ext_map = { - **{ext: self.obj_extension for ext in self.src_extensions}, - **{ext: self.res_extension for ext in self._rc_extensions + self._mc_extensions}, - } - - output_dir = output_dir or '' - - def make_out_path(p): - base, ext = os.path.splitext(p) - if strip_dir: - base = os.path.basename(base) - else: - _, base = os.path.splitdrive(base) - if base.startswith((os.path.sep, os.path.altsep)): - base = base[1:] - try: - # XXX: This may produce absurdly long paths. We should check - # the length of the result and trim base until we fit within - # 260 characters. - return os.path.join(output_dir, base + ext_map[ext]) - except LookupError: - # Better to raise an exception instead of silently continuing - # and later complain about sources and targets having - # different lengths - raise CompileError("Don't know how to compile {}".format(p)) - - return list(map(make_out_path, source_filenames)) - - - def compile(self, sources, - output_dir=None, macros=None, include_dirs=None, debug=0, - extra_preargs=None, extra_postargs=None, depends=None): - - if not self.initialized: - self.initialize() - compile_info = self._setup_compile(output_dir, macros, include_dirs, - sources, depends, extra_postargs) - macros, objects, extra_postargs, pp_opts, build = compile_info - - compile_opts = extra_preargs or [] - compile_opts.append('/c') - if debug: - compile_opts.extend(self.compile_options_debug) - else: - compile_opts.extend(self.compile_options) - - - add_cpp_opts = False - - for obj in objects: - try: - src, ext = build[obj] - except KeyError: - continue - if debug: - # pass the full pathname to MSVC in debug mode, - # this allows the debugger to find the source file - # without asking the user to browse for it - src = os.path.abspath(src) - - if ext in self._c_extensions: - input_opt = "/Tc" + src - elif ext in self._cpp_extensions: - input_opt = "/Tp" + src - add_cpp_opts = True - elif ext in self._rc_extensions: - # compile .RC to .RES file - input_opt = src - output_opt = "/fo" + obj - try: - self.spawn([self.rc] + pp_opts + [output_opt, input_opt]) - except DistutilsExecError as msg: - raise CompileError(msg) - continue - elif ext in self._mc_extensions: - # Compile .MC to .RC file to .RES file. - # * '-h dir' specifies the directory for the - # generated include file - # * '-r dir' specifies the target directory of the - # generated RC file and the binary message resource - # it includes - # - # For now (since there are no options to change this), - # we use the source-directory for the include file and - # the build directory for the RC file and message - # resources. This works at least for win32all. - h_dir = os.path.dirname(src) - rc_dir = os.path.dirname(obj) - try: - # first compile .MC to .RC and .H file - self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src]) - base, _ = os.path.splitext(os.path.basename (src)) - rc_file = os.path.join(rc_dir, base + '.rc') - # then compile .RC to .RES file - self.spawn([self.rc, "/fo" + obj, rc_file]) - - except DistutilsExecError as msg: - raise CompileError(msg) - continue - else: - # how to handle this file? - raise CompileError("Don't know how to compile {} to {}" - .format(src, obj)) - - args = [self.cc] + compile_opts + pp_opts - if add_cpp_opts: - args.append('/EHsc') - args.append(input_opt) - args.append("/Fo" + obj) - args.extend(extra_postargs) - - try: - self.spawn(args) - except DistutilsExecError as msg: - raise CompileError(msg) - - return objects - - - def create_static_lib(self, - objects, - output_libname, - output_dir=None, - debug=0, - target_lang=None): - - if not self.initialized: - self.initialize() - objects, output_dir = self._fix_object_args(objects, output_dir) - output_filename = self.library_filename(output_libname, - output_dir=output_dir) - - if self._need_link(objects, output_filename): - lib_args = objects + ['/OUT:' + output_filename] - if debug: - pass # XXX what goes here? - try: - log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args)) - self.spawn([self.lib] + lib_args) - except DistutilsExecError as msg: - raise LibError(msg) - else: - log.debug("skipping %s (up-to-date)", output_filename) - - - def link(self, - target_desc, - objects, - output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None): - - if not self.initialized: - self.initialize() - objects, output_dir = self._fix_object_args(objects, output_dir) - fixed_args = self._fix_lib_args(libraries, library_dirs, - runtime_library_dirs) - libraries, library_dirs, runtime_library_dirs = fixed_args - - if runtime_library_dirs: - self.warn("I don't know what to do with 'runtime_library_dirs': " - + str(runtime_library_dirs)) - - lib_opts = gen_lib_options(self, - library_dirs, runtime_library_dirs, - libraries) - if output_dir is not None: - output_filename = os.path.join(output_dir, output_filename) - - if self._need_link(objects, output_filename): - ldflags = self._ldflags[target_desc, debug] - - export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])] - - ld_args = (ldflags + lib_opts + export_opts + - objects + ['/OUT:' + output_filename]) - - # The MSVC linker generates .lib and .exp files, which cannot be - # suppressed by any linker switches. The .lib files may even be - # needed! Make sure they are generated in the temporary build - # directory. Since they have different names for debug and release - # builds, they can go into the same directory. - build_temp = os.path.dirname(objects[0]) - if export_symbols is not None: - (dll_name, dll_ext) = os.path.splitext( - os.path.basename(output_filename)) - implib_file = os.path.join( - build_temp, - self.library_filename(dll_name)) - ld_args.append ('/IMPLIB:' + implib_file) - - if extra_preargs: - ld_args[:0] = extra_preargs - if extra_postargs: - ld_args.extend(extra_postargs) - - output_dir = os.path.dirname(os.path.abspath(output_filename)) - self.mkpath(output_dir) - try: - log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args)) - self.spawn([self.linker] + ld_args) - self._copy_vcruntime(output_dir) - except DistutilsExecError as msg: - raise LinkError(msg) - else: - log.debug("skipping %s (up-to-date)", output_filename) - - def _copy_vcruntime(self, output_dir): - vcruntime = self._vcruntime_redist - if not vcruntime or not os.path.isfile(vcruntime): - return - - if os.path.basename(vcruntime).lower() in _BUNDLED_DLLS: - return - - log.debug('Copying "%s"', vcruntime) - vcruntime = shutil.copy(vcruntime, output_dir) - os.chmod(vcruntime, stat.S_IWRITE) - - def spawn(self, cmd): - old_path = os.getenv('path') - try: - os.environ['path'] = self._paths - return super().spawn(cmd) - finally: - os.environ['path'] = old_path - - # -- Miscellaneous methods ----------------------------------------- - # These are all used by the 'gen_lib_options() function, in - # ccompiler.py. - - def library_dir_option(self, dir): - return "/LIBPATH:" + dir - - def runtime_library_dir_option(self, dir): - raise DistutilsPlatformError( - "don't know how to set runtime library search path for MSVC") - - def library_option(self, lib): - return self.library_filename(lib) - - def find_library_file(self, dirs, lib, debug=0): - # Prefer a debugging library if found (and requested), but deal - # with it if we don't have one. - if debug: - try_names = [lib + "_d", lib] - else: - try_names = [lib] - for dir in dirs: - for name in try_names: - libfile = os.path.join(dir, self.library_filename(name)) - if os.path.isfile(libfile): - return libfile - else: - # Oops, didn't find it in *any* of 'dirs' - return None diff --git a/Lib/distutils/archive_util.py b/Lib/distutils/archive_util.py deleted file mode 100644 index b002dc3b845..00000000000 --- a/Lib/distutils/archive_util.py +++ /dev/null @@ -1,256 +0,0 @@ -"""distutils.archive_util - -Utility functions for creating archive files (tarballs, zip files, -that sort of thing).""" - -import os -from warnings import warn -import sys - -try: - import zipfile -except ImportError: - zipfile = None - - -from distutils.errors import DistutilsExecError -from distutils.spawn import spawn -from distutils.dir_util import mkpath -from distutils import log - -try: - from pwd import getpwnam -except ImportError: - getpwnam = None - -try: - from grp import getgrnam -except ImportError: - getgrnam = None - -def _get_gid(name): - """Returns a gid, given a group name.""" - if getgrnam is None or name is None: - return None - try: - result = getgrnam(name) - except KeyError: - result = None - if result is not None: - return result[2] - return None - -def _get_uid(name): - """Returns an uid, given a user name.""" - if getpwnam is None or name is None: - return None - try: - result = getpwnam(name) - except KeyError: - result = None - if result is not None: - return result[2] - return None - -def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0, - owner=None, group=None): - """Create a (possibly compressed) tar file from all the files under - 'base_dir'. - - 'compress' must be "gzip" (the default), "bzip2", "xz", "compress", or - None. ("compress" will be deprecated in Python 3.2) - - 'owner' and 'group' can be used to define an owner and a group for the - archive that is being built. If not provided, the current owner and group - will be used. - - The output tar file will be named 'base_dir' + ".tar", possibly plus - the appropriate compression extension (".gz", ".bz2", ".xz" or ".Z"). - - Returns the output filename. - """ - tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', 'xz': 'xz', None: '', - 'compress': ''} - compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz', - 'compress': '.Z'} - - # flags for compression program, each element of list will be an argument - if compress is not None and compress not in compress_ext.keys(): - raise ValueError( - "bad value for 'compress': must be None, 'gzip', 'bzip2', " - "'xz' or 'compress'") - - archive_name = base_name + '.tar' - if compress != 'compress': - archive_name += compress_ext.get(compress, '') - - mkpath(os.path.dirname(archive_name), dry_run=dry_run) - - # creating the tarball - import tarfile # late import so Python build itself doesn't break - - log.info('Creating tar archive') - - uid = _get_uid(owner) - gid = _get_gid(group) - - def _set_uid_gid(tarinfo): - if gid is not None: - tarinfo.gid = gid - tarinfo.gname = group - if uid is not None: - tarinfo.uid = uid - tarinfo.uname = owner - return tarinfo - - if not dry_run: - tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress]) - try: - tar.add(base_dir, filter=_set_uid_gid) - finally: - tar.close() - - # compression using `compress` - if compress == 'compress': - warn("'compress' will be deprecated.", PendingDeprecationWarning) - # the option varies depending on the platform - compressed_name = archive_name + compress_ext[compress] - if sys.platform == 'win32': - cmd = [compress, archive_name, compressed_name] - else: - cmd = [compress, '-f', archive_name] - spawn(cmd, dry_run=dry_run) - return compressed_name - - return archive_name - -def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): - """Create a zip file from all the files under 'base_dir'. - - The output zip file will be named 'base_name' + ".zip". Uses either the - "zipfile" Python module (if available) or the InfoZIP "zip" utility - (if installed and found on the default search path). If neither tool is - available, raises DistutilsExecError. Returns the name of the output zip - file. - """ - zip_filename = base_name + ".zip" - mkpath(os.path.dirname(zip_filename), dry_run=dry_run) - - # If zipfile module is not available, try spawning an external - # 'zip' command. - if zipfile is None: - if verbose: - zipoptions = "-r" - else: - zipoptions = "-rq" - - try: - spawn(["zip", zipoptions, zip_filename, base_dir], - dry_run=dry_run) - except DistutilsExecError: - # XXX really should distinguish between "couldn't find - # external 'zip' command" and "zip failed". - raise DistutilsExecError(("unable to create zip file '%s': " - "could neither import the 'zipfile' module nor " - "find a standalone zip utility") % zip_filename) - - else: - log.info("creating '%s' and adding '%s' to it", - zip_filename, base_dir) - - if not dry_run: - try: - zip = zipfile.ZipFile(zip_filename, "w", - compression=zipfile.ZIP_DEFLATED) - except RuntimeError: - zip = zipfile.ZipFile(zip_filename, "w", - compression=zipfile.ZIP_STORED) - - if base_dir != os.curdir: - path = os.path.normpath(os.path.join(base_dir, '')) - zip.write(path, path) - log.info("adding '%s'", path) - for dirpath, dirnames, filenames in os.walk(base_dir): - for name in dirnames: - path = os.path.normpath(os.path.join(dirpath, name, '')) - zip.write(path, path) - log.info("adding '%s'", path) - for name in filenames: - path = os.path.normpath(os.path.join(dirpath, name)) - if os.path.isfile(path): - zip.write(path, path) - log.info("adding '%s'", path) - zip.close() - - return zip_filename - -ARCHIVE_FORMATS = { - 'gztar': (make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"), - 'bztar': (make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"), - 'xztar': (make_tarball, [('compress', 'xz')], "xz'ed tar-file"), - 'ztar': (make_tarball, [('compress', 'compress')], "compressed tar file"), - 'tar': (make_tarball, [('compress', None)], "uncompressed tar file"), - 'zip': (make_zipfile, [],"ZIP file") - } - -def check_archive_formats(formats): - """Returns the first format from the 'format' list that is unknown. - - If all formats are known, returns None - """ - for format in formats: - if format not in ARCHIVE_FORMATS: - return format - return None - -def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, - dry_run=0, owner=None, group=None): - """Create an archive file (eg. zip or tar). - - 'base_name' is the name of the file to create, minus any format-specific - extension; 'format' is the archive format: one of "zip", "tar", "gztar", - "bztar", "xztar", or "ztar". - - 'root_dir' is a directory that will be the root directory of the - archive; ie. we typically chdir into 'root_dir' before creating the - archive. 'base_dir' is the directory where we start archiving from; - ie. 'base_dir' will be the common prefix of all files and - directories in the archive. 'root_dir' and 'base_dir' both default - to the current directory. Returns the name of the archive file. - - 'owner' and 'group' are used when creating a tar archive. By default, - uses the current owner and group. - """ - save_cwd = os.getcwd() - if root_dir is not None: - log.debug("changing into '%s'", root_dir) - base_name = os.path.abspath(base_name) - if not dry_run: - os.chdir(root_dir) - - if base_dir is None: - base_dir = os.curdir - - kwargs = {'dry_run': dry_run} - - try: - format_info = ARCHIVE_FORMATS[format] - except KeyError: - raise ValueError("unknown archive format '%s'" % format) - - func = format_info[0] - for arg, val in format_info[1]: - kwargs[arg] = val - - if format != 'zip': - kwargs['owner'] = owner - kwargs['group'] = group - - try: - filename = func(base_name, base_dir, **kwargs) - finally: - if root_dir is not None: - log.debug("changing back to '%s'", save_cwd) - os.chdir(save_cwd) - - return filename diff --git a/Lib/distutils/bcppcompiler.py b/Lib/distutils/bcppcompiler.py deleted file mode 100644 index 9f4c432d90e..00000000000 --- a/Lib/distutils/bcppcompiler.py +++ /dev/null @@ -1,393 +0,0 @@ -"""distutils.bcppcompiler - -Contains BorlandCCompiler, an implementation of the abstract CCompiler class -for the Borland C++ compiler. -""" - -# This implementation by Lyle Johnson, based on the original msvccompiler.py -# module and using the directions originally published by Gordon Williams. - -# XXX looks like there's a LOT of overlap between these two classes: -# someone should sit down and factor out the common code as -# WindowsCCompiler! --GPW - - -import os -from distutils.errors import \ - DistutilsExecError, DistutilsPlatformError, \ - CompileError, LibError, LinkError, UnknownFileError -from distutils.ccompiler import \ - CCompiler, gen_preprocess_options, gen_lib_options -from distutils.file_util import write_file -from distutils.dep_util import newer -from distutils import log - -class BCPPCompiler(CCompiler) : - """Concrete class that implements an interface to the Borland C/C++ - compiler, as defined by the CCompiler abstract class. - """ - - compiler_type = 'bcpp' - - # Just set this so CCompiler's constructor doesn't barf. We currently - # don't use the 'set_executables()' bureaucracy provided by CCompiler, - # as it really isn't necessary for this sort of single-compiler class. - # Would be nice to have a consistent interface with UnixCCompiler, - # though, so it's worth thinking about. - executables = {} - - # Private class data (need to distinguish C from C++ source for compiler) - _c_extensions = ['.c'] - _cpp_extensions = ['.cc', '.cpp', '.cxx'] - - # Needed for the filename generation methods provided by the - # base class, CCompiler. - src_extensions = _c_extensions + _cpp_extensions - obj_extension = '.obj' - static_lib_extension = '.lib' - shared_lib_extension = '.dll' - static_lib_format = shared_lib_format = '%s%s' - exe_extension = '.exe' - - - def __init__ (self, - verbose=0, - dry_run=0, - force=0): - - CCompiler.__init__ (self, verbose, dry_run, force) - - # These executables are assumed to all be in the path. - # Borland doesn't seem to use any special registry settings to - # indicate their installation locations. - - self.cc = "bcc32.exe" - self.linker = "ilink32.exe" - self.lib = "tlib.exe" - - self.preprocess_options = None - self.compile_options = ['/tWM', '/O2', '/q', '/g0'] - self.compile_options_debug = ['/tWM', '/Od', '/q', '/g0'] - - self.ldflags_shared = ['/Tpd', '/Gn', '/q', '/x'] - self.ldflags_shared_debug = ['/Tpd', '/Gn', '/q', '/x'] - self.ldflags_static = [] - self.ldflags_exe = ['/Gn', '/q', '/x'] - self.ldflags_exe_debug = ['/Gn', '/q', '/x','/r'] - - - # -- Worker methods ------------------------------------------------ - - def compile(self, sources, - output_dir=None, macros=None, include_dirs=None, debug=0, - extra_preargs=None, extra_postargs=None, depends=None): - - macros, objects, extra_postargs, pp_opts, build = \ - self._setup_compile(output_dir, macros, include_dirs, sources, - depends, extra_postargs) - compile_opts = extra_preargs or [] - compile_opts.append ('-c') - if debug: - compile_opts.extend (self.compile_options_debug) - else: - compile_opts.extend (self.compile_options) - - for obj in objects: - try: - src, ext = build[obj] - except KeyError: - continue - # XXX why do the normpath here? - src = os.path.normpath(src) - obj = os.path.normpath(obj) - # XXX _setup_compile() did a mkpath() too but before the normpath. - # Is it possible to skip the normpath? - self.mkpath(os.path.dirname(obj)) - - if ext == '.res': - # This is already a binary file -- skip it. - continue # the 'for' loop - if ext == '.rc': - # This needs to be compiled to a .res file -- do it now. - try: - self.spawn (["brcc32", "-fo", obj, src]) - except DistutilsExecError as msg: - raise CompileError(msg) - continue # the 'for' loop - - # The next two are both for the real compiler. - if ext in self._c_extensions: - input_opt = "" - elif ext in self._cpp_extensions: - input_opt = "-P" - else: - # Unknown file type -- no extra options. The compiler - # will probably fail, but let it just in case this is a - # file the compiler recognizes even if we don't. - input_opt = "" - - output_opt = "-o" + obj - - # Compiler command line syntax is: "bcc32 [options] file(s)". - # Note that the source file names must appear at the end of - # the command line. - try: - self.spawn ([self.cc] + compile_opts + pp_opts + - [input_opt, output_opt] + - extra_postargs + [src]) - except DistutilsExecError as msg: - raise CompileError(msg) - - return objects - - # compile () - - - def create_static_lib (self, - objects, - output_libname, - output_dir=None, - debug=0, - target_lang=None): - - (objects, output_dir) = self._fix_object_args (objects, output_dir) - output_filename = \ - self.library_filename (output_libname, output_dir=output_dir) - - if self._need_link (objects, output_filename): - lib_args = [output_filename, '/u'] + objects - if debug: - pass # XXX what goes here? - try: - self.spawn ([self.lib] + lib_args) - except DistutilsExecError as msg: - raise LibError(msg) - else: - log.debug("skipping %s (up-to-date)", output_filename) - - # create_static_lib () - - - def link (self, - target_desc, - objects, - output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None): - - # XXX this ignores 'build_temp'! should follow the lead of - # msvccompiler.py - - (objects, output_dir) = self._fix_object_args (objects, output_dir) - (libraries, library_dirs, runtime_library_dirs) = \ - self._fix_lib_args (libraries, library_dirs, runtime_library_dirs) - - if runtime_library_dirs: - log.warn("I don't know what to do with 'runtime_library_dirs': %s", - str(runtime_library_dirs)) - - if output_dir is not None: - output_filename = os.path.join (output_dir, output_filename) - - if self._need_link (objects, output_filename): - - # Figure out linker args based on type of target. - if target_desc == CCompiler.EXECUTABLE: - startup_obj = 'c0w32' - if debug: - ld_args = self.ldflags_exe_debug[:] - else: - ld_args = self.ldflags_exe[:] - else: - startup_obj = 'c0d32' - if debug: - ld_args = self.ldflags_shared_debug[:] - else: - ld_args = self.ldflags_shared[:] - - - # Create a temporary exports file for use by the linker - if export_symbols is None: - def_file = '' - else: - head, tail = os.path.split (output_filename) - modname, ext = os.path.splitext (tail) - temp_dir = os.path.dirname(objects[0]) # preserve tree structure - def_file = os.path.join (temp_dir, '%s.def' % modname) - contents = ['EXPORTS'] - for sym in (export_symbols or []): - contents.append(' %s=_%s' % (sym, sym)) - self.execute(write_file, (def_file, contents), - "writing %s" % def_file) - - # Borland C++ has problems with '/' in paths - objects2 = map(os.path.normpath, objects) - # split objects in .obj and .res files - # Borland C++ needs them at different positions in the command line - objects = [startup_obj] - resources = [] - for file in objects2: - (base, ext) = os.path.splitext(os.path.normcase(file)) - if ext == '.res': - resources.append(file) - else: - objects.append(file) - - - for l in library_dirs: - ld_args.append("/L%s" % os.path.normpath(l)) - ld_args.append("/L.") # we sometimes use relative paths - - # list of object files - ld_args.extend(objects) - - # XXX the command-line syntax for Borland C++ is a bit wonky; - # certain filenames are jammed together in one big string, but - # comma-delimited. This doesn't mesh too well with the - # Unix-centric attitude (with a DOS/Windows quoting hack) of - # 'spawn()', so constructing the argument list is a bit - # awkward. Note that doing the obvious thing and jamming all - # the filenames and commas into one argument would be wrong, - # because 'spawn()' would quote any filenames with spaces in - # them. Arghghh!. Apparently it works fine as coded... - - # name of dll/exe file - ld_args.extend([',',output_filename]) - # no map file and start libraries - ld_args.append(',,') - - for lib in libraries: - # see if we find it and if there is a bcpp specific lib - # (xxx_bcpp.lib) - libfile = self.find_library_file(library_dirs, lib, debug) - if libfile is None: - ld_args.append(lib) - # probably a BCPP internal library -- don't warn - else: - # full name which prefers bcpp_xxx.lib over xxx.lib - ld_args.append(libfile) - - # some default libraries - ld_args.append ('import32') - ld_args.append ('cw32mt') - - # def file for export symbols - ld_args.extend([',',def_file]) - # add resource files - ld_args.append(',') - ld_args.extend(resources) - - - if extra_preargs: - ld_args[:0] = extra_preargs - if extra_postargs: - ld_args.extend(extra_postargs) - - self.mkpath (os.path.dirname (output_filename)) - try: - self.spawn ([self.linker] + ld_args) - except DistutilsExecError as msg: - raise LinkError(msg) - - else: - log.debug("skipping %s (up-to-date)", output_filename) - - # link () - - # -- Miscellaneous methods ----------------------------------------- - - - def find_library_file (self, dirs, lib, debug=0): - # List of effective library names to try, in order of preference: - # xxx_bcpp.lib is better than xxx.lib - # and xxx_d.lib is better than xxx.lib if debug is set - # - # The "_bcpp" suffix is to handle a Python installation for people - # with multiple compilers (primarily Distutils hackers, I suspect - # ;-). The idea is they'd have one static library for each - # compiler they care about, since (almost?) every Windows compiler - # seems to have a different format for static libraries. - if debug: - dlib = (lib + "_d") - try_names = (dlib + "_bcpp", lib + "_bcpp", dlib, lib) - else: - try_names = (lib + "_bcpp", lib) - - for dir in dirs: - for name in try_names: - libfile = os.path.join(dir, self.library_filename(name)) - if os.path.exists(libfile): - return libfile - else: - # Oops, didn't find it in *any* of 'dirs' - return None - - # overwrite the one from CCompiler to support rc and res-files - def object_filenames (self, - source_filenames, - strip_dir=0, - output_dir=''): - if output_dir is None: output_dir = '' - obj_names = [] - for src_name in source_filenames: - # use normcase to make sure '.rc' is really '.rc' and not '.RC' - (base, ext) = os.path.splitext (os.path.normcase(src_name)) - if ext not in (self.src_extensions + ['.rc','.res']): - raise UnknownFileError("unknown file type '%s' (from '%s')" % \ - (ext, src_name)) - if strip_dir: - base = os.path.basename (base) - if ext == '.res': - # these can go unchanged - obj_names.append (os.path.join (output_dir, base + ext)) - elif ext == '.rc': - # these need to be compiled to .res-files - obj_names.append (os.path.join (output_dir, base + '.res')) - else: - obj_names.append (os.path.join (output_dir, - base + self.obj_extension)) - return obj_names - - # object_filenames () - - def preprocess (self, - source, - output_file=None, - macros=None, - include_dirs=None, - extra_preargs=None, - extra_postargs=None): - - (_, macros, include_dirs) = \ - self._fix_compile_args(None, macros, include_dirs) - pp_opts = gen_preprocess_options(macros, include_dirs) - pp_args = ['cpp32.exe'] + pp_opts - if output_file is not None: - pp_args.append('-o' + output_file) - if extra_preargs: - pp_args[:0] = extra_preargs - if extra_postargs: - pp_args.extend(extra_postargs) - pp_args.append(source) - - # We need to preprocess: either we're being forced to, or the - # source file is newer than the target (or the target doesn't - # exist). - if self.force or output_file is None or newer(source, output_file): - if output_file: - self.mkpath(os.path.dirname(output_file)) - try: - self.spawn(pp_args) - except DistutilsExecError as msg: - print(msg) - raise CompileError(msg) - - # preprocess() diff --git a/Lib/distutils/ccompiler.py b/Lib/distutils/ccompiler.py deleted file mode 100644 index b71d1d39bcd..00000000000 --- a/Lib/distutils/ccompiler.py +++ /dev/null @@ -1,1115 +0,0 @@ -"""distutils.ccompiler - -Contains CCompiler, an abstract base class that defines the interface -for the Distutils compiler abstraction model.""" - -import sys, os, re -from distutils.errors import * -from distutils.spawn import spawn -from distutils.file_util import move_file -from distutils.dir_util import mkpath -from distutils.dep_util import newer_pairwise, newer_group -from distutils.util import split_quoted, execute -from distutils import log - -class CCompiler: - """Abstract base class to define the interface that must be implemented - by real compiler classes. Also has some utility methods used by - several compiler classes. - - The basic idea behind a compiler abstraction class is that each - instance can be used for all the compile/link steps in building a - single project. Thus, attributes common to all of those compile and - link steps -- include directories, macros to define, libraries to link - against, etc. -- are attributes of the compiler instance. To allow for - variability in how individual files are treated, most of those - attributes may be varied on a per-compilation or per-link basis. - """ - - # 'compiler_type' is a class attribute that identifies this class. It - # keeps code that wants to know what kind of compiler it's dealing with - # from having to import all possible compiler classes just to do an - # 'isinstance'. In concrete CCompiler subclasses, 'compiler_type' - # should really, really be one of the keys of the 'compiler_class' - # dictionary (see below -- used by the 'new_compiler()' factory - # function) -- authors of new compiler interface classes are - # responsible for updating 'compiler_class'! - compiler_type = None - - # XXX things not handled by this compiler abstraction model: - # * client can't provide additional options for a compiler, - # e.g. warning, optimization, debugging flags. Perhaps this - # should be the domain of concrete compiler abstraction classes - # (UnixCCompiler, MSVCCompiler, etc.) -- or perhaps the base - # class should have methods for the common ones. - # * can't completely override the include or library searchg - # path, ie. no "cc -I -Idir1 -Idir2" or "cc -L -Ldir1 -Ldir2". - # I'm not sure how widely supported this is even by Unix - # compilers, much less on other platforms. And I'm even less - # sure how useful it is; maybe for cross-compiling, but - # support for that is a ways off. (And anyways, cross - # compilers probably have a dedicated binary with the - # right paths compiled in. I hope.) - # * can't do really freaky things with the library list/library - # dirs, e.g. "-Ldir1 -lfoo -Ldir2 -lfoo" to link against - # different versions of libfoo.a in different locations. I - # think this is useless without the ability to null out the - # library search path anyways. - - - # Subclasses that rely on the standard filename generation methods - # implemented below should override these; see the comment near - # those methods ('object_filenames()' et. al.) for details: - src_extensions = None # list of strings - obj_extension = None # string - static_lib_extension = None - shared_lib_extension = None # string - static_lib_format = None # format string - shared_lib_format = None # prob. same as static_lib_format - exe_extension = None # string - - # Default language settings. language_map is used to detect a source - # file or Extension target language, checking source filenames. - # language_order is used to detect the language precedence, when deciding - # what language to use when mixing source types. For example, if some - # extension has two files with ".c" extension, and one with ".cpp", it - # is still linked as c++. - language_map = {".c" : "c", - ".cc" : "c++", - ".cpp" : "c++", - ".cxx" : "c++", - ".m" : "objc", - } - language_order = ["c++", "objc", "c"] - - def __init__(self, verbose=0, dry_run=0, force=0): - self.dry_run = dry_run - self.force = force - self.verbose = verbose - - # 'output_dir': a common output directory for object, library, - # shared object, and shared library files - self.output_dir = None - - # 'macros': a list of macro definitions (or undefinitions). A - # macro definition is a 2-tuple (name, value), where the value is - # either a string or None (no explicit value). A macro - # undefinition is a 1-tuple (name,). - self.macros = [] - - # 'include_dirs': a list of directories to search for include files - self.include_dirs = [] - - # 'libraries': a list of libraries to include in any link - # (library names, not filenames: eg. "foo" not "libfoo.a") - self.libraries = [] - - # 'library_dirs': a list of directories to search for libraries - self.library_dirs = [] - - # 'runtime_library_dirs': a list of directories to search for - # shared libraries/objects at runtime - self.runtime_library_dirs = [] - - # 'objects': a list of object files (or similar, such as explicitly - # named library files) to include on any link - self.objects = [] - - for key in self.executables.keys(): - self.set_executable(key, self.executables[key]) - - def set_executables(self, **kwargs): - """Define the executables (and options for them) that will be run - to perform the various stages of compilation. The exact set of - executables that may be specified here depends on the compiler - class (via the 'executables' class attribute), but most will have: - compiler the C/C++ compiler - linker_so linker used to create shared objects and libraries - linker_exe linker used to create binary executables - archiver static library creator - - On platforms with a command-line (Unix, DOS/Windows), each of these - is a string that will be split into executable name and (optional) - list of arguments. (Splitting the string is done similarly to how - Unix shells operate: words are delimited by spaces, but quotes and - backslashes can override this. See - 'distutils.util.split_quoted()'.) - """ - - # Note that some CCompiler implementation classes will define class - # attributes 'cpp', 'cc', etc. with hard-coded executable names; - # this is appropriate when a compiler class is for exactly one - # compiler/OS combination (eg. MSVCCompiler). Other compiler - # classes (UnixCCompiler, in particular) are driven by information - # discovered at run-time, since there are many different ways to do - # basically the same things with Unix C compilers. - - for key in kwargs: - if key not in self.executables: - raise ValueError("unknown executable '%s' for class %s" % - (key, self.__class__.__name__)) - self.set_executable(key, kwargs[key]) - - def set_executable(self, key, value): - if isinstance(value, str): - setattr(self, key, split_quoted(value)) - else: - setattr(self, key, value) - - def _find_macro(self, name): - i = 0 - for defn in self.macros: - if defn[0] == name: - return i - i += 1 - return None - - def _check_macro_definitions(self, definitions): - """Ensures that every element of 'definitions' is a valid macro - definition, ie. either (name,value) 2-tuple or a (name,) tuple. Do - nothing if all definitions are OK, raise TypeError otherwise. - """ - for defn in definitions: - if not (isinstance(defn, tuple) and - (len(defn) in (1, 2) and - (isinstance (defn[1], str) or defn[1] is None)) and - isinstance (defn[0], str)): - raise TypeError(("invalid macro definition '%s': " % defn) + \ - "must be tuple (string,), (string, string), or " + \ - "(string, None)") - - - # -- Bookkeeping methods ------------------------------------------- - - def define_macro(self, name, value=None): - """Define a preprocessor macro for all compilations driven by this - compiler object. The optional parameter 'value' should be a - string; if it is not supplied, then the macro will be defined - without an explicit value and the exact outcome depends on the - compiler used (XXX true? does ANSI say anything about this?) - """ - # Delete from the list of macro definitions/undefinitions if - # already there (so that this one will take precedence). - i = self._find_macro (name) - if i is not None: - del self.macros[i] - - self.macros.append((name, value)) - - def undefine_macro(self, name): - """Undefine a preprocessor macro for all compilations driven by - this compiler object. If the same macro is defined by - 'define_macro()' and undefined by 'undefine_macro()' the last call - takes precedence (including multiple redefinitions or - undefinitions). If the macro is redefined/undefined on a - per-compilation basis (ie. in the call to 'compile()'), then that - takes precedence. - """ - # Delete from the list of macro definitions/undefinitions if - # already there (so that this one will take precedence). - i = self._find_macro (name) - if i is not None: - del self.macros[i] - - undefn = (name,) - self.macros.append(undefn) - - def add_include_dir(self, dir): - """Add 'dir' to the list of directories that will be searched for - header files. The compiler is instructed to search directories in - the order in which they are supplied by successive calls to - 'add_include_dir()'. - """ - self.include_dirs.append(dir) - - def set_include_dirs(self, dirs): - """Set the list of directories that will be searched to 'dirs' (a - list of strings). Overrides any preceding calls to - 'add_include_dir()'; subsequence calls to 'add_include_dir()' add - to the list passed to 'set_include_dirs()'. This does not affect - any list of standard include directories that the compiler may - search by default. - """ - self.include_dirs = dirs[:] - - def add_library(self, libname): - """Add 'libname' to the list of libraries that will be included in - all links driven by this compiler object. Note that 'libname' - should *not* be the name of a file containing a library, but the - name of the library itself: the actual filename will be inferred by - the linker, the compiler, or the compiler class (depending on the - platform). - - The linker will be instructed to link against libraries in the - order they were supplied to 'add_library()' and/or - 'set_libraries()'. It is perfectly valid to duplicate library - names; the linker will be instructed to link against libraries as - many times as they are mentioned. - """ - self.libraries.append(libname) - - def set_libraries(self, libnames): - """Set the list of libraries to be included in all links driven by - this compiler object to 'libnames' (a list of strings). This does - not affect any standard system libraries that the linker may - include by default. - """ - self.libraries = libnames[:] - - def add_library_dir(self, dir): - """Add 'dir' to the list of directories that will be searched for - libraries specified to 'add_library()' and 'set_libraries()'. The - linker will be instructed to search for libraries in the order they - are supplied to 'add_library_dir()' and/or 'set_library_dirs()'. - """ - self.library_dirs.append(dir) - - def set_library_dirs(self, dirs): - """Set the list of library search directories to 'dirs' (a list of - strings). This does not affect any standard library search path - that the linker may search by default. - """ - self.library_dirs = dirs[:] - - def add_runtime_library_dir(self, dir): - """Add 'dir' to the list of directories that will be searched for - shared libraries at runtime. - """ - self.runtime_library_dirs.append(dir) - - def set_runtime_library_dirs(self, dirs): - """Set the list of directories to search for shared libraries at - runtime to 'dirs' (a list of strings). This does not affect any - standard search path that the runtime linker may search by - default. - """ - self.runtime_library_dirs = dirs[:] - - def add_link_object(self, object): - """Add 'object' to the list of object files (or analogues, such as - explicitly named library files or the output of "resource - compilers") to be included in every link driven by this compiler - object. - """ - self.objects.append(object) - - def set_link_objects(self, objects): - """Set the list of object files (or analogues) to be included in - every link to 'objects'. This does not affect any standard object - files that the linker may include by default (such as system - libraries). - """ - self.objects = objects[:] - - - # -- Private utility methods -------------------------------------- - # (here for the convenience of subclasses) - - # Helper method to prep compiler in subclass compile() methods - - def _setup_compile(self, outdir, macros, incdirs, sources, depends, - extra): - """Process arguments and decide which source files to compile.""" - if outdir is None: - outdir = self.output_dir - elif not isinstance(outdir, str): - raise TypeError("'output_dir' must be a string or None") - - if macros is None: - macros = self.macros - elif isinstance(macros, list): - macros = macros + (self.macros or []) - else: - raise TypeError("'macros' (if supplied) must be a list of tuples") - - if incdirs is None: - incdirs = self.include_dirs - elif isinstance(incdirs, (list, tuple)): - incdirs = list(incdirs) + (self.include_dirs or []) - else: - raise TypeError( - "'include_dirs' (if supplied) must be a list of strings") - - if extra is None: - extra = [] - - # Get the list of expected output (object) files - objects = self.object_filenames(sources, strip_dir=0, - output_dir=outdir) - assert len(objects) == len(sources) - - pp_opts = gen_preprocess_options(macros, incdirs) - - build = {} - for i in range(len(sources)): - src = sources[i] - obj = objects[i] - ext = os.path.splitext(src)[1] - self.mkpath(os.path.dirname(obj)) - build[obj] = (src, ext) - - return macros, objects, extra, pp_opts, build - - def _get_cc_args(self, pp_opts, debug, before): - # works for unixccompiler, cygwinccompiler - cc_args = pp_opts + ['-c'] - if debug: - cc_args[:0] = ['-g'] - if before: - cc_args[:0] = before - return cc_args - - def _fix_compile_args(self, output_dir, macros, include_dirs): - """Typecheck and fix-up some of the arguments to the 'compile()' - method, and return fixed-up values. Specifically: if 'output_dir' - is None, replaces it with 'self.output_dir'; ensures that 'macros' - is a list, and augments it with 'self.macros'; ensures that - 'include_dirs' is a list, and augments it with 'self.include_dirs'. - Guarantees that the returned values are of the correct type, - i.e. for 'output_dir' either string or None, and for 'macros' and - 'include_dirs' either list or None. - """ - if output_dir is None: - output_dir = self.output_dir - elif not isinstance(output_dir, str): - raise TypeError("'output_dir' must be a string or None") - - if macros is None: - macros = self.macros - elif isinstance(macros, list): - macros = macros + (self.macros or []) - else: - raise TypeError("'macros' (if supplied) must be a list of tuples") - - if include_dirs is None: - include_dirs = self.include_dirs - elif isinstance(include_dirs, (list, tuple)): - include_dirs = list(include_dirs) + (self.include_dirs or []) - else: - raise TypeError( - "'include_dirs' (if supplied) must be a list of strings") - - return output_dir, macros, include_dirs - - def _prep_compile(self, sources, output_dir, depends=None): - """Decide which souce files must be recompiled. - - Determine the list of object files corresponding to 'sources', - and figure out which ones really need to be recompiled. - Return a list of all object files and a dictionary telling - which source files can be skipped. - """ - # Get the list of expected output (object) files - objects = self.object_filenames(sources, output_dir=output_dir) - assert len(objects) == len(sources) - - # Return an empty dict for the "which source files can be skipped" - # return value to preserve API compatibility. - return objects, {} - - def _fix_object_args(self, objects, output_dir): - """Typecheck and fix up some arguments supplied to various methods. - Specifically: ensure that 'objects' is a list; if output_dir is - None, replace with self.output_dir. Return fixed versions of - 'objects' and 'output_dir'. - """ - if not isinstance(objects, (list, tuple)): - raise TypeError("'objects' must be a list or tuple of strings") - objects = list(objects) - - if output_dir is None: - output_dir = self.output_dir - elif not isinstance(output_dir, str): - raise TypeError("'output_dir' must be a string or None") - - return (objects, output_dir) - - def _fix_lib_args(self, libraries, library_dirs, runtime_library_dirs): - """Typecheck and fix up some of the arguments supplied to the - 'link_*' methods. Specifically: ensure that all arguments are - lists, and augment them with their permanent versions - (eg. 'self.libraries' augments 'libraries'). Return a tuple with - fixed versions of all arguments. - """ - if libraries is None: - libraries = self.libraries - elif isinstance(libraries, (list, tuple)): - libraries = list (libraries) + (self.libraries or []) - else: - raise TypeError( - "'libraries' (if supplied) must be a list of strings") - - if library_dirs is None: - library_dirs = self.library_dirs - elif isinstance(library_dirs, (list, tuple)): - library_dirs = list (library_dirs) + (self.library_dirs or []) - else: - raise TypeError( - "'library_dirs' (if supplied) must be a list of strings") - - if runtime_library_dirs is None: - runtime_library_dirs = self.runtime_library_dirs - elif isinstance(runtime_library_dirs, (list, tuple)): - runtime_library_dirs = (list(runtime_library_dirs) + - (self.runtime_library_dirs or [])) - else: - raise TypeError("'runtime_library_dirs' (if supplied) " - "must be a list of strings") - - return (libraries, library_dirs, runtime_library_dirs) - - def _need_link(self, objects, output_file): - """Return true if we need to relink the files listed in 'objects' - to recreate 'output_file'. - """ - if self.force: - return True - else: - if self.dry_run: - newer = newer_group (objects, output_file, missing='newer') - else: - newer = newer_group (objects, output_file) - return newer - - def detect_language(self, sources): - """Detect the language of a given file, or list of files. Uses - language_map, and language_order to do the job. - """ - if not isinstance(sources, list): - sources = [sources] - lang = None - index = len(self.language_order) - for source in sources: - base, ext = os.path.splitext(source) - extlang = self.language_map.get(ext) - try: - extindex = self.language_order.index(extlang) - if extindex < index: - lang = extlang - index = extindex - except ValueError: - pass - return lang - - - # -- Worker methods ------------------------------------------------ - # (must be implemented by subclasses) - - def preprocess(self, source, output_file=None, macros=None, - include_dirs=None, extra_preargs=None, extra_postargs=None): - """Preprocess a single C/C++ source file, named in 'source'. - Output will be written to file named 'output_file', or stdout if - 'output_file' not supplied. 'macros' is a list of macro - definitions as for 'compile()', which will augment the macros set - with 'define_macro()' and 'undefine_macro()'. 'include_dirs' is a - list of directory names that will be added to the default list. - - Raises PreprocessError on failure. - """ - pass - - def compile(self, sources, output_dir=None, macros=None, - include_dirs=None, debug=0, extra_preargs=None, - extra_postargs=None, depends=None): - """Compile one or more source files. - - 'sources' must be a list of filenames, most likely C/C++ - files, but in reality anything that can be handled by a - particular compiler and compiler class (eg. MSVCCompiler can - handle resource files in 'sources'). Return a list of object - filenames, one per source filename in 'sources'. Depending on - the implementation, not all source files will necessarily be - compiled, but all corresponding object filenames will be - returned. - - If 'output_dir' is given, object files will be put under it, while - retaining their original path component. That is, "foo/bar.c" - normally compiles to "foo/bar.o" (for a Unix implementation); if - 'output_dir' is "build", then it would compile to - "build/foo/bar.o". - - 'macros', if given, must be a list of macro definitions. A macro - definition is either a (name, value) 2-tuple or a (name,) 1-tuple. - The former defines a macro; if the value is None, the macro is - defined without an explicit value. The 1-tuple case undefines a - macro. Later definitions/redefinitions/ undefinitions take - precedence. - - 'include_dirs', if given, must be a list of strings, the - directories to add to the default include file search path for this - compilation only. - - 'debug' is a boolean; if true, the compiler will be instructed to - output debug symbols in (or alongside) the object file(s). - - 'extra_preargs' and 'extra_postargs' are implementation- dependent. - On platforms that have the notion of a command-line (e.g. Unix, - DOS/Windows), they are most likely lists of strings: extra - command-line arguments to prepand/append to the compiler command - line. On other platforms, consult the implementation class - documentation. In any event, they are intended as an escape hatch - for those occasions when the abstract compiler framework doesn't - cut the mustard. - - 'depends', if given, is a list of filenames that all targets - depend on. If a source file is older than any file in - depends, then the source file will be recompiled. This - supports dependency tracking, but only at a coarse - granularity. - - Raises CompileError on failure. - """ - # A concrete compiler class can either override this method - # entirely or implement _compile(). - macros, objects, extra_postargs, pp_opts, build = \ - self._setup_compile(output_dir, macros, include_dirs, sources, - depends, extra_postargs) - cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) - - for obj in objects: - try: - src, ext = build[obj] - except KeyError: - continue - self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) - - # Return *all* object filenames, not just the ones we just built. - return objects - - def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): - """Compile 'src' to product 'obj'.""" - # A concrete compiler class that does not override compile() - # should implement _compile(). - pass - - def create_static_lib(self, objects, output_libname, output_dir=None, - debug=0, target_lang=None): - """Link a bunch of stuff together to create a static library file. - The "bunch of stuff" consists of the list of object files supplied - as 'objects', the extra object files supplied to - 'add_link_object()' and/or 'set_link_objects()', the libraries - supplied to 'add_library()' and/or 'set_libraries()', and the - libraries supplied as 'libraries' (if any). - - 'output_libname' should be a library name, not a filename; the - filename will be inferred from the library name. 'output_dir' is - the directory where the library file will be put. - - 'debug' is a boolean; if true, debugging information will be - included in the library (note that on most platforms, it is the - compile step where this matters: the 'debug' flag is included here - just for consistency). - - 'target_lang' is the target language for which the given objects - are being compiled. This allows specific linkage time treatment of - certain languages. - - Raises LibError on failure. - """ - pass - - - # values for target_desc parameter in link() - SHARED_OBJECT = "shared_object" - SHARED_LIBRARY = "shared_library" - EXECUTABLE = "executable" - - def link(self, - target_desc, - objects, - output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None): - """Link a bunch of stuff together to create an executable or - shared library file. - - The "bunch of stuff" consists of the list of object files supplied - as 'objects'. 'output_filename' should be a filename. If - 'output_dir' is supplied, 'output_filename' is relative to it - (i.e. 'output_filename' can provide directory components if - needed). - - 'libraries' is a list of libraries to link against. These are - library names, not filenames, since they're translated into - filenames in a platform-specific way (eg. "foo" becomes "libfoo.a" - on Unix and "foo.lib" on DOS/Windows). However, they can include a - directory component, which means the linker will look in that - specific directory rather than searching all the normal locations. - - 'library_dirs', if supplied, should be a list of directories to - search for libraries that were specified as bare library names - (ie. no directory component). These are on top of the system - default and those supplied to 'add_library_dir()' and/or - 'set_library_dirs()'. 'runtime_library_dirs' is a list of - directories that will be embedded into the shared library and used - to search for other shared libraries that *it* depends on at - run-time. (This may only be relevant on Unix.) - - 'export_symbols' is a list of symbols that the shared library will - export. (This appears to be relevant only on Windows.) - - 'debug' is as for 'compile()' and 'create_static_lib()', with the - slight distinction that it actually matters on most platforms (as - opposed to 'create_static_lib()', which includes a 'debug' flag - mostly for form's sake). - - 'extra_preargs' and 'extra_postargs' are as for 'compile()' (except - of course that they supply command-line arguments for the - particular linker being used). - - 'target_lang' is the target language for which the given objects - are being compiled. This allows specific linkage time treatment of - certain languages. - - Raises LinkError on failure. - """ - raise NotImplementedError - - - # Old 'link_*()' methods, rewritten to use the new 'link()' method. - - def link_shared_lib(self, - objects, - output_libname, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None): - self.link(CCompiler.SHARED_LIBRARY, objects, - self.library_filename(output_libname, lib_type='shared'), - output_dir, - libraries, library_dirs, runtime_library_dirs, - export_symbols, debug, - extra_preargs, extra_postargs, build_temp, target_lang) - - - def link_shared_object(self, - objects, - output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None): - self.link(CCompiler.SHARED_OBJECT, objects, - output_filename, output_dir, - libraries, library_dirs, runtime_library_dirs, - export_symbols, debug, - extra_preargs, extra_postargs, build_temp, target_lang) - - - def link_executable(self, - objects, - output_progname, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - target_lang=None): - self.link(CCompiler.EXECUTABLE, objects, - self.executable_filename(output_progname), output_dir, - libraries, library_dirs, runtime_library_dirs, None, - debug, extra_preargs, extra_postargs, None, target_lang) - - - # -- Miscellaneous methods ----------------------------------------- - # These are all used by the 'gen_lib_options() function; there is - # no appropriate default implementation so subclasses should - # implement all of these. - - def library_dir_option(self, dir): - """Return the compiler option to add 'dir' to the list of - directories searched for libraries. - """ - raise NotImplementedError - - def runtime_library_dir_option(self, dir): - """Return the compiler option to add 'dir' to the list of - directories searched for runtime libraries. - """ - raise NotImplementedError - - def library_option(self, lib): - """Return the compiler option to add 'lib' to the list of libraries - linked into the shared library or executable. - """ - raise NotImplementedError - - def has_function(self, funcname, includes=None, include_dirs=None, - libraries=None, library_dirs=None): - """Return a boolean indicating whether funcname is supported on - the current platform. The optional arguments can be used to - augment the compilation environment. - """ - # this can't be included at module scope because it tries to - # import math which might not be available at that point - maybe - # the necessary logic should just be inlined? - import tempfile - if includes is None: - includes = [] - if include_dirs is None: - include_dirs = [] - if libraries is None: - libraries = [] - if library_dirs is None: - library_dirs = [] - fd, fname = tempfile.mkstemp(".c", funcname, text=True) - f = os.fdopen(fd, "w") - try: - for incl in includes: - f.write("""#include "%s"\n""" % incl) - f.write("""\ -main (int argc, char **argv) { - %s(); -} -""" % funcname) - finally: - f.close() - try: - objects = self.compile([fname], include_dirs=include_dirs) - except CompileError: - return False - - try: - self.link_executable(objects, "a.out", - libraries=libraries, - library_dirs=library_dirs) - except (LinkError, TypeError): - return False - return True - - def find_library_file (self, dirs, lib, debug=0): - """Search the specified list of directories for a static or shared - library file 'lib' and return the full path to that file. If - 'debug' true, look for a debugging version (if that makes sense on - the current platform). Return None if 'lib' wasn't found in any of - the specified directories. - """ - raise NotImplementedError - - # -- Filename generation methods ----------------------------------- - - # The default implementation of the filename generating methods are - # prejudiced towards the Unix/DOS/Windows view of the world: - # * object files are named by replacing the source file extension - # (eg. .c/.cpp -> .o/.obj) - # * library files (shared or static) are named by plugging the - # library name and extension into a format string, eg. - # "lib%s.%s" % (lib_name, ".a") for Unix static libraries - # * executables are named by appending an extension (possibly - # empty) to the program name: eg. progname + ".exe" for - # Windows - # - # To reduce redundant code, these methods expect to find - # several attributes in the current object (presumably defined - # as class attributes): - # * src_extensions - - # list of C/C++ source file extensions, eg. ['.c', '.cpp'] - # * obj_extension - - # object file extension, eg. '.o' or '.obj' - # * static_lib_extension - - # extension for static library files, eg. '.a' or '.lib' - # * shared_lib_extension - - # extension for shared library/object files, eg. '.so', '.dll' - # * static_lib_format - - # format string for generating static library filenames, - # eg. 'lib%s.%s' or '%s.%s' - # * shared_lib_format - # format string for generating shared library filenames - # (probably same as static_lib_format, since the extension - # is one of the intended parameters to the format string) - # * exe_extension - - # extension for executable files, eg. '' or '.exe' - - def object_filenames(self, source_filenames, strip_dir=0, output_dir=''): - if output_dir is None: - output_dir = '' - obj_names = [] - for src_name in source_filenames: - base, ext = os.path.splitext(src_name) - base = os.path.splitdrive(base)[1] # Chop off the drive - base = base[os.path.isabs(base):] # If abs, chop off leading / - if ext not in self.src_extensions: - raise UnknownFileError( - "unknown file type '%s' (from '%s')" % (ext, src_name)) - if strip_dir: - base = os.path.basename(base) - obj_names.append(os.path.join(output_dir, - base + self.obj_extension)) - return obj_names - - def shared_object_filename(self, basename, strip_dir=0, output_dir=''): - assert output_dir is not None - if strip_dir: - basename = os.path.basename(basename) - return os.path.join(output_dir, basename + self.shared_lib_extension) - - def executable_filename(self, basename, strip_dir=0, output_dir=''): - assert output_dir is not None - if strip_dir: - basename = os.path.basename(basename) - return os.path.join(output_dir, basename + (self.exe_extension or '')) - - def library_filename(self, libname, lib_type='static', # or 'shared' - strip_dir=0, output_dir=''): - assert output_dir is not None - if lib_type not in ("static", "shared", "dylib", "xcode_stub"): - raise ValueError( - "'lib_type' must be \"static\", \"shared\", \"dylib\", or \"xcode_stub\"") - fmt = getattr(self, lib_type + "_lib_format") - ext = getattr(self, lib_type + "_lib_extension") - - dir, base = os.path.split(libname) - filename = fmt % (base, ext) - if strip_dir: - dir = '' - - return os.path.join(output_dir, dir, filename) - - - # -- Utility methods ----------------------------------------------- - - def announce(self, msg, level=1): - log.debug(msg) - - def debug_print(self, msg): - from distutils.debug import DEBUG - if DEBUG: - print(msg) - - def warn(self, msg): - sys.stderr.write("warning: %s\n" % msg) - - def execute(self, func, args, msg=None, level=1): - execute(func, args, msg, self.dry_run) - - def spawn(self, cmd): - spawn(cmd, dry_run=self.dry_run) - - def move_file(self, src, dst): - return move_file(src, dst, dry_run=self.dry_run) - - def mkpath (self, name, mode=0o777): - mkpath(name, mode, dry_run=self.dry_run) - - -# Map a sys.platform/os.name ('posix', 'nt') to the default compiler -# type for that platform. Keys are interpreted as re match -# patterns. Order is important; platform mappings are preferred over -# OS names. -_default_compilers = ( - - # Platform string mappings - - # on a cygwin built python we can use gcc like an ordinary UNIXish - # compiler - ('cygwin.*', 'unix'), - - # OS name mappings - ('posix', 'unix'), - ('nt', 'msvc'), - - ) - -def get_default_compiler(osname=None, platform=None): - """Determine the default compiler to use for the given platform. - - osname should be one of the standard Python OS names (i.e. the - ones returned by os.name) and platform the common value - returned by sys.platform for the platform in question. - - The default values are os.name and sys.platform in case the - parameters are not given. - """ - if osname is None: - osname = os.name - if platform is None: - platform = sys.platform - for pattern, compiler in _default_compilers: - if re.match(pattern, platform) is not None or \ - re.match(pattern, osname) is not None: - return compiler - # Default to Unix compiler - return 'unix' - -# Map compiler types to (module_name, class_name) pairs -- ie. where to -# find the code that implements an interface to this compiler. (The module -# is assumed to be in the 'distutils' package.) -compiler_class = { 'unix': ('unixccompiler', 'UnixCCompiler', - "standard UNIX-style compiler"), - 'msvc': ('_msvccompiler', 'MSVCCompiler', - "Microsoft Visual C++"), - 'cygwin': ('cygwinccompiler', 'CygwinCCompiler', - "Cygwin port of GNU C Compiler for Win32"), - 'mingw32': ('cygwinccompiler', 'Mingw32CCompiler', - "Mingw32 port of GNU C Compiler for Win32"), - 'bcpp': ('bcppcompiler', 'BCPPCompiler', - "Borland C++ Compiler"), - } - -def show_compilers(): - """Print list of available compilers (used by the "--help-compiler" - options to "build", "build_ext", "build_clib"). - """ - # XXX this "knows" that the compiler option it's describing is - # "--compiler", which just happens to be the case for the three - # commands that use it. - from distutils.fancy_getopt import FancyGetopt - compilers = [] - for compiler in compiler_class.keys(): - compilers.append(("compiler="+compiler, None, - compiler_class[compiler][2])) - compilers.sort() - pretty_printer = FancyGetopt(compilers) - pretty_printer.print_help("List of available compilers:") - - -def new_compiler(plat=None, compiler=None, verbose=0, dry_run=0, force=0): - """Generate an instance of some CCompiler subclass for the supplied - platform/compiler combination. 'plat' defaults to 'os.name' - (eg. 'posix', 'nt'), and 'compiler' defaults to the default compiler - for that platform. Currently only 'posix' and 'nt' are supported, and - the default compilers are "traditional Unix interface" (UnixCCompiler - class) and Visual C++ (MSVCCompiler class). Note that it's perfectly - possible to ask for a Unix compiler object under Windows, and a - Microsoft compiler object under Unix -- if you supply a value for - 'compiler', 'plat' is ignored. - """ - if plat is None: - plat = os.name - - try: - if compiler is None: - compiler = get_default_compiler(plat) - - (module_name, class_name, long_description) = compiler_class[compiler] - except KeyError: - msg = "don't know how to compile C/C++ code on platform '%s'" % plat - if compiler is not None: - msg = msg + " with '%s' compiler" % compiler - raise DistutilsPlatformError(msg) - - try: - module_name = "distutils." + module_name - __import__ (module_name) - module = sys.modules[module_name] - klass = vars(module)[class_name] - except ImportError: - raise DistutilsModuleError( - "can't compile C/C++ code: unable to load module '%s'" % \ - module_name) - except KeyError: - raise DistutilsModuleError( - "can't compile C/C++ code: unable to find class '%s' " - "in module '%s'" % (class_name, module_name)) - - # XXX The None is necessary to preserve backwards compatibility - # with classes that expect verbose to be the first positional - # argument. - return klass(None, dry_run, force) - - -def gen_preprocess_options(macros, include_dirs): - """Generate C pre-processor options (-D, -U, -I) as used by at least - two types of compilers: the typical Unix compiler and Visual C++. - 'macros' is the usual thing, a list of 1- or 2-tuples, where (name,) - means undefine (-U) macro 'name', and (name,value) means define (-D) - macro 'name' to 'value'. 'include_dirs' is just a list of directory - names to be added to the header file search path (-I). Returns a list - of command-line options suitable for either Unix compilers or Visual - C++. - """ - # XXX it would be nice (mainly aesthetic, and so we don't generate - # stupid-looking command lines) to go over 'macros' and eliminate - # redundant definitions/undefinitions (ie. ensure that only the - # latest mention of a particular macro winds up on the command - # line). I don't think it's essential, though, since most (all?) - # Unix C compilers only pay attention to the latest -D or -U - # mention of a macro on their command line. Similar situation for - # 'include_dirs'. I'm punting on both for now. Anyways, weeding out - # redundancies like this should probably be the province of - # CCompiler, since the data structures used are inherited from it - # and therefore common to all CCompiler classes. - pp_opts = [] - for macro in macros: - if not (isinstance(macro, tuple) and 1 <= len(macro) <= 2): - raise TypeError( - "bad macro definition '%s': " - "each element of 'macros' list must be a 1- or 2-tuple" - % macro) - - if len(macro) == 1: # undefine this macro - pp_opts.append("-U%s" % macro[0]) - elif len(macro) == 2: - if macro[1] is None: # define with no explicit value - pp_opts.append("-D%s" % macro[0]) - else: - # XXX *don't* need to be clever about quoting the - # macro value here, because we're going to avoid the - # shell at all costs when we spawn the command! - pp_opts.append("-D%s=%s" % macro) - - for dir in include_dirs: - pp_opts.append("-I%s" % dir) - return pp_opts - - -def gen_lib_options (compiler, library_dirs, runtime_library_dirs, libraries): - """Generate linker options for searching library directories and - linking with specific libraries. 'libraries' and 'library_dirs' are, - respectively, lists of library names (not filenames!) and search - directories. Returns a list of command-line options suitable for use - with some compiler (depending on the two format strings passed in). - """ - lib_opts = [] - - for dir in library_dirs: - lib_opts.append(compiler.library_dir_option(dir)) - - for dir in runtime_library_dirs: - opt = compiler.runtime_library_dir_option(dir) - if isinstance(opt, list): - lib_opts = lib_opts + opt - else: - lib_opts.append(opt) - - # XXX it's important that we *not* remove redundant library mentions! - # sometimes you really do have to say "-lfoo -lbar -lfoo" in order to - # resolve all symbols. I just hope we never have to say "-lfoo obj.o - # -lbar" to get things to work -- that's certainly a possibility, but a - # pretty nasty way to arrange your C code. - - for lib in libraries: - (lib_dir, lib_name) = os.path.split(lib) - if lib_dir: - lib_file = compiler.find_library_file([lib_dir], lib_name) - if lib_file: - lib_opts.append(lib_file) - else: - compiler.warn("no library file corresponding to " - "'%s' found (skipping)" % lib) - else: - lib_opts.append(compiler.library_option (lib)) - return lib_opts diff --git a/Lib/distutils/cmd.py b/Lib/distutils/cmd.py deleted file mode 100644 index 939f7959457..00000000000 --- a/Lib/distutils/cmd.py +++ /dev/null @@ -1,434 +0,0 @@ -"""distutils.cmd - -Provides the Command class, the base class for the command classes -in the distutils.command package. -""" - -import sys, os, re -from distutils.errors import DistutilsOptionError -from distutils import util, dir_util, file_util, archive_util, dep_util -from distutils import log - -class Command: - """Abstract base class for defining command classes, the "worker bees" - of the Distutils. A useful analogy for command classes is to think of - them as subroutines with local variables called "options". The options - are "declared" in 'initialize_options()' and "defined" (given their - final values, aka "finalized") in 'finalize_options()', both of which - must be defined by every command class. The distinction between the - two is necessary because option values might come from the outside - world (command line, config file, ...), and any options dependent on - other options must be computed *after* these outside influences have - been processed -- hence 'finalize_options()'. The "body" of the - subroutine, where it does all its work based on the values of its - options, is the 'run()' method, which must also be implemented by every - command class. - """ - - # 'sub_commands' formalizes the notion of a "family" of commands, - # eg. "install" as the parent with sub-commands "install_lib", - # "install_headers", etc. The parent of a family of commands - # defines 'sub_commands' as a class attribute; it's a list of - # (command_name : string, predicate : unbound_method | string | None) - # tuples, where 'predicate' is a method of the parent command that - # determines whether the corresponding command is applicable in the - # current situation. (Eg. we "install_headers" is only applicable if - # we have any C header files to install.) If 'predicate' is None, - # that command is always applicable. - # - # 'sub_commands' is usually defined at the *end* of a class, because - # predicates can be unbound methods, so they must already have been - # defined. The canonical example is the "install" command. - sub_commands = [] - - - # -- Creation/initialization methods ------------------------------- - - def __init__(self, dist): - """Create and initialize a new Command object. Most importantly, - invokes the 'initialize_options()' method, which is the real - initializer and depends on the actual command being - instantiated. - """ - # late import because of mutual dependence between these classes - from distutils.dist import Distribution - - if not isinstance(dist, Distribution): - raise TypeError("dist must be a Distribution instance") - if self.__class__ is Command: - raise RuntimeError("Command is an abstract class") - - self.distribution = dist - self.initialize_options() - - # Per-command versions of the global flags, so that the user can - # customize Distutils' behaviour command-by-command and let some - # commands fall back on the Distribution's behaviour. None means - # "not defined, check self.distribution's copy", while 0 or 1 mean - # false and true (duh). Note that this means figuring out the real - # value of each flag is a touch complicated -- hence "self._dry_run" - # will be handled by __getattr__, below. - # XXX This needs to be fixed. - self._dry_run = None - - # verbose is largely ignored, but needs to be set for - # backwards compatibility (I think)? - self.verbose = dist.verbose - - # Some commands define a 'self.force' option to ignore file - # timestamps, but methods defined *here* assume that - # 'self.force' exists for all commands. So define it here - # just to be safe. - self.force = None - - # The 'help' flag is just used for command-line parsing, so - # none of that complicated bureaucracy is needed. - self.help = 0 - - # 'finalized' records whether or not 'finalize_options()' has been - # called. 'finalize_options()' itself should not pay attention to - # this flag: it is the business of 'ensure_finalized()', which - # always calls 'finalize_options()', to respect/update it. - self.finalized = 0 - - # XXX A more explicit way to customize dry_run would be better. - def __getattr__(self, attr): - if attr == 'dry_run': - myval = getattr(self, "_" + attr) - if myval is None: - return getattr(self.distribution, attr) - else: - return myval - else: - raise AttributeError(attr) - - def ensure_finalized(self): - if not self.finalized: - self.finalize_options() - self.finalized = 1 - - # Subclasses must define: - # initialize_options() - # provide default values for all options; may be customized by - # setup script, by options from config file(s), or by command-line - # options - # finalize_options() - # decide on the final values for all options; this is called - # after all possible intervention from the outside world - # (command-line, option file, etc.) has been processed - # run() - # run the command: do whatever it is we're here to do, - # controlled by the command's various option values - - def initialize_options(self): - """Set default values for all the options that this command - supports. Note that these defaults may be overridden by other - commands, by the setup script, by config files, or by the - command-line. Thus, this is not the place to code dependencies - between options; generally, 'initialize_options()' implementations - are just a bunch of "self.foo = None" assignments. - - This method must be implemented by all command classes. - """ - raise RuntimeError("abstract method -- subclass %s must override" - % self.__class__) - - def finalize_options(self): - """Set final values for all the options that this command supports. - This is always called as late as possible, ie. after any option - assignments from the command-line or from other commands have been - done. Thus, this is the place to code option dependencies: if - 'foo' depends on 'bar', then it is safe to set 'foo' from 'bar' as - long as 'foo' still has the same value it was assigned in - 'initialize_options()'. - - This method must be implemented by all command classes. - """ - raise RuntimeError("abstract method -- subclass %s must override" - % self.__class__) - - - def dump_options(self, header=None, indent=""): - from distutils.fancy_getopt import longopt_xlate - if header is None: - header = "command options for '%s':" % self.get_command_name() - self.announce(indent + header, level=log.INFO) - indent = indent + " " - for (option, _, _) in self.user_options: - option = option.translate(longopt_xlate) - if option[-1] == "=": - option = option[:-1] - value = getattr(self, option) - self.announce(indent + "%s = %s" % (option, value), - level=log.INFO) - - def run(self): - """A command's raison d'etre: carry out the action it exists to - perform, controlled by the options initialized in - 'initialize_options()', customized by other commands, the setup - script, the command-line, and config files, and finalized in - 'finalize_options()'. All terminal output and filesystem - interaction should be done by 'run()'. - - This method must be implemented by all command classes. - """ - raise RuntimeError("abstract method -- subclass %s must override" - % self.__class__) - - def announce(self, msg, level=1): - """If the current verbosity level is of greater than or equal to - 'level' print 'msg' to stdout. - """ - log.log(level, msg) - - def debug_print(self, msg): - """Print 'msg' to stdout if the global DEBUG (taken from the - DISTUTILS_DEBUG environment variable) flag is true. - """ - from distutils.debug import DEBUG - if DEBUG: - print(msg) - sys.stdout.flush() - - - # -- Option validation methods ------------------------------------- - # (these are very handy in writing the 'finalize_options()' method) - # - # NB. the general philosophy here is to ensure that a particular option - # value meets certain type and value constraints. If not, we try to - # force it into conformance (eg. if we expect a list but have a string, - # split the string on comma and/or whitespace). If we can't force the - # option into conformance, raise DistutilsOptionError. Thus, command - # classes need do nothing more than (eg.) - # self.ensure_string_list('foo') - # and they can be guaranteed that thereafter, self.foo will be - # a list of strings. - - def _ensure_stringlike(self, option, what, default=None): - val = getattr(self, option) - if val is None: - setattr(self, option, default) - return default - elif not isinstance(val, str): - raise DistutilsOptionError("'%s' must be a %s (got `%s`)" - % (option, what, val)) - return val - - def ensure_string(self, option, default=None): - """Ensure that 'option' is a string; if not defined, set it to - 'default'. - """ - self._ensure_stringlike(option, "string", default) - - def ensure_string_list(self, option): - r"""Ensure that 'option' is a list of strings. If 'option' is - currently a string, we split it either on /,\s*/ or /\s+/, so - "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become - ["foo", "bar", "baz"]. - """ - val = getattr(self, option) - if val is None: - return - elif isinstance(val, str): - setattr(self, option, re.split(r',\s*|\s+', val)) - else: - if isinstance(val, list): - ok = all(isinstance(v, str) for v in val) - else: - ok = False - if not ok: - raise DistutilsOptionError( - "'%s' must be a list of strings (got %r)" - % (option, val)) - - def _ensure_tested_string(self, option, tester, what, error_fmt, - default=None): - val = self._ensure_stringlike(option, what, default) - if val is not None and not tester(val): - raise DistutilsOptionError(("error in '%s' option: " + error_fmt) - % (option, val)) - - def ensure_filename(self, option): - """Ensure that 'option' is the name of an existing file.""" - self._ensure_tested_string(option, os.path.isfile, - "filename", - "'%s' does not exist or is not a file") - - def ensure_dirname(self, option): - self._ensure_tested_string(option, os.path.isdir, - "directory name", - "'%s' does not exist or is not a directory") - - - # -- Convenience methods for commands ------------------------------ - - def get_command_name(self): - if hasattr(self, 'command_name'): - return self.command_name - else: - return self.__class__.__name__ - - def set_undefined_options(self, src_cmd, *option_pairs): - """Set the values of any "undefined" options from corresponding - option values in some other command object. "Undefined" here means - "is None", which is the convention used to indicate that an option - has not been changed between 'initialize_options()' and - 'finalize_options()'. Usually called from 'finalize_options()' for - options that depend on some other command rather than another - option of the same command. 'src_cmd' is the other command from - which option values will be taken (a command object will be created - for it if necessary); the remaining arguments are - '(src_option,dst_option)' tuples which mean "take the value of - 'src_option' in the 'src_cmd' command object, and copy it to - 'dst_option' in the current command object". - """ - # Option_pairs: list of (src_option, dst_option) tuples - src_cmd_obj = self.distribution.get_command_obj(src_cmd) - src_cmd_obj.ensure_finalized() - for (src_option, dst_option) in option_pairs: - if getattr(self, dst_option) is None: - setattr(self, dst_option, getattr(src_cmd_obj, src_option)) - - def get_finalized_command(self, command, create=1): - """Wrapper around Distribution's 'get_command_obj()' method: find - (create if necessary and 'create' is true) the command object for - 'command', call its 'ensure_finalized()' method, and return the - finalized command object. - """ - cmd_obj = self.distribution.get_command_obj(command, create) - cmd_obj.ensure_finalized() - return cmd_obj - - # XXX rename to 'get_reinitialized_command()'? (should do the - # same in dist.py, if so) - def reinitialize_command(self, command, reinit_subcommands=0): - return self.distribution.reinitialize_command(command, - reinit_subcommands) - - def run_command(self, command): - """Run some other command: uses the 'run_command()' method of - Distribution, which creates and finalizes the command object if - necessary and then invokes its 'run()' method. - """ - self.distribution.run_command(command) - - def get_sub_commands(self): - """Determine the sub-commands that are relevant in the current - distribution (ie., that need to be run). This is based on the - 'sub_commands' class attribute: each tuple in that list may include - a method that we call to determine if the subcommand needs to be - run for the current distribution. Return a list of command names. - """ - commands = [] - for (cmd_name, method) in self.sub_commands: - if method is None or method(self): - commands.append(cmd_name) - return commands - - - # -- External world manipulation ----------------------------------- - - def warn(self, msg): - log.warn("warning: %s: %s\n", self.get_command_name(), msg) - - def execute(self, func, args, msg=None, level=1): - util.execute(func, args, msg, dry_run=self.dry_run) - - def mkpath(self, name, mode=0o777): - dir_util.mkpath(name, mode, dry_run=self.dry_run) - - def copy_file(self, infile, outfile, preserve_mode=1, preserve_times=1, - link=None, level=1): - """Copy a file respecting verbose, dry-run and force flags. (The - former two default to whatever is in the Distribution object, and - the latter defaults to false for commands that don't define it.)""" - return file_util.copy_file(infile, outfile, preserve_mode, - preserve_times, not self.force, link, - dry_run=self.dry_run) - - def copy_tree(self, infile, outfile, preserve_mode=1, preserve_times=1, - preserve_symlinks=0, level=1): - """Copy an entire directory tree respecting verbose, dry-run, - and force flags. - """ - return dir_util.copy_tree(infile, outfile, preserve_mode, - preserve_times, preserve_symlinks, - not self.force, dry_run=self.dry_run) - - def move_file (self, src, dst, level=1): - """Move a file respecting dry-run flag.""" - return file_util.move_file(src, dst, dry_run=self.dry_run) - - def spawn(self, cmd, search_path=1, level=1): - """Spawn an external command respecting dry-run flag.""" - from distutils.spawn import spawn - spawn(cmd, search_path, dry_run=self.dry_run) - - def make_archive(self, base_name, format, root_dir=None, base_dir=None, - owner=None, group=None): - return archive_util.make_archive(base_name, format, root_dir, base_dir, - dry_run=self.dry_run, - owner=owner, group=group) - - def make_file(self, infiles, outfile, func, args, - exec_msg=None, skip_msg=None, level=1): - """Special case of 'execute()' for operations that process one or - more input files and generate one output file. Works just like - 'execute()', except the operation is skipped and a different - message printed if 'outfile' already exists and is newer than all - files listed in 'infiles'. If the command defined 'self.force', - and it is true, then the command is unconditionally run -- does no - timestamp checks. - """ - if skip_msg is None: - skip_msg = "skipping %s (inputs unchanged)" % outfile - - # Allow 'infiles' to be a single string - if isinstance(infiles, str): - infiles = (infiles,) - elif not isinstance(infiles, (list, tuple)): - raise TypeError( - "'infiles' must be a string, or a list or tuple of strings") - - if exec_msg is None: - exec_msg = "generating %s from %s" % (outfile, ', '.join(infiles)) - - # If 'outfile' must be regenerated (either because it doesn't - # exist, is out-of-date, or the 'force' flag is true) then - # perform the action that presumably regenerates it - if self.force or dep_util.newer_group(infiles, outfile): - self.execute(func, args, exec_msg, level) - # Otherwise, print the "skip" message - else: - log.debug(skip_msg) - -# XXX 'install_misc' class not currently used -- it was the base class for -# both 'install_scripts' and 'install_data', but they outgrew it. It might -# still be useful for 'install_headers', though, so I'm keeping it around -# for the time being. - -class install_misc(Command): - """Common base class for installing some files in a subdirectory. - Currently used by install_data and install_scripts. - """ - - user_options = [('install-dir=', 'd', "directory to install the files to")] - - def initialize_options (self): - self.install_dir = None - self.outfiles = [] - - def _install_dir_from(self, dirname): - self.set_undefined_options('install', (dirname, 'install_dir')) - - def _copy_files(self, filelist): - self.outfiles = [] - if not filelist: - return - self.mkpath(self.install_dir) - for f in filelist: - self.copy_file(f, self.install_dir) - self.outfiles.append(os.path.join(self.install_dir, f)) - - def get_outputs(self): - return self.outfiles diff --git a/Lib/distutils/command/__init__.py b/Lib/distutils/command/__init__.py deleted file mode 100644 index 481eea9fd4b..00000000000 --- a/Lib/distutils/command/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -"""distutils.command - -Package containing implementation of all the standard Distutils -commands.""" - -__all__ = ['build', - 'build_py', - 'build_ext', - 'build_clib', - 'build_scripts', - 'clean', - 'install', - 'install_lib', - 'install_headers', - 'install_scripts', - 'install_data', - 'sdist', - 'register', - 'bdist', - 'bdist_dumb', - 'bdist_rpm', - 'bdist_wininst', - 'check', - 'upload', - # These two are reserved for future use: - #'bdist_sdux', - #'bdist_pkgtool', - # Note: - # bdist_packager is not included because it only provides - # an abstract base class - ] diff --git a/Lib/distutils/command/bdist.py b/Lib/distutils/command/bdist.py deleted file mode 100644 index 014871d280e..00000000000 --- a/Lib/distutils/command/bdist.py +++ /dev/null @@ -1,143 +0,0 @@ -"""distutils.command.bdist - -Implements the Distutils 'bdist' command (create a built [binary] -distribution).""" - -import os -from distutils.core import Command -from distutils.errors import * -from distutils.util import get_platform - - -def show_formats(): - """Print list of available formats (arguments to "--format" option). - """ - from distutils.fancy_getopt import FancyGetopt - formats = [] - for format in bdist.format_commands: - formats.append(("formats=" + format, None, - bdist.format_command[format][1])) - pretty_printer = FancyGetopt(formats) - pretty_printer.print_help("List of available distribution formats:") - - -class bdist(Command): - - description = "create a built (binary) distribution" - - user_options = [('bdist-base=', 'b', - "temporary directory for creating built distributions"), - ('plat-name=', 'p', - "platform name to embed in generated filenames " - "(default: %s)" % get_platform()), - ('formats=', None, - "formats for distribution (comma-separated list)"), - ('dist-dir=', 'd', - "directory to put final built distributions in " - "[default: dist]"), - ('skip-build', None, - "skip rebuilding everything (for testing/debugging)"), - ('owner=', 'u', - "Owner name used when creating a tar file" - " [default: current user]"), - ('group=', 'g', - "Group name used when creating a tar file" - " [default: current group]"), - ] - - boolean_options = ['skip-build'] - - help_options = [ - ('help-formats', None, - "lists available distribution formats", show_formats), - ] - - # The following commands do not take a format option from bdist - no_format_option = ('bdist_rpm',) - - # This won't do in reality: will need to distinguish RPM-ish Linux, - # Debian-ish Linux, Solaris, FreeBSD, ..., Windows, Mac OS. - default_format = {'posix': 'gztar', - 'nt': 'zip'} - - # Establish the preferred order (for the --help-formats option). - format_commands = ['rpm', 'gztar', 'bztar', 'xztar', 'ztar', 'tar', - 'wininst', 'zip', 'msi'] - - # And the real information. - format_command = {'rpm': ('bdist_rpm', "RPM distribution"), - 'gztar': ('bdist_dumb', "gzip'ed tar file"), - 'bztar': ('bdist_dumb', "bzip2'ed tar file"), - 'xztar': ('bdist_dumb', "xz'ed tar file"), - 'ztar': ('bdist_dumb', "compressed tar file"), - 'tar': ('bdist_dumb', "tar file"), - 'wininst': ('bdist_wininst', - "Windows executable installer"), - 'zip': ('bdist_dumb', "ZIP file"), - 'msi': ('bdist_msi', "Microsoft Installer") - } - - - def initialize_options(self): - self.bdist_base = None - self.plat_name = None - self.formats = None - self.dist_dir = None - self.skip_build = 0 - self.group = None - self.owner = None - - def finalize_options(self): - # have to finalize 'plat_name' before 'bdist_base' - if self.plat_name is None: - if self.skip_build: - self.plat_name = get_platform() - else: - self.plat_name = self.get_finalized_command('build').plat_name - - # 'bdist_base' -- parent of per-built-distribution-format - # temporary directories (eg. we'll probably have - # "build/bdist.<plat>/dumb", "build/bdist.<plat>/rpm", etc.) - if self.bdist_base is None: - build_base = self.get_finalized_command('build').build_base - self.bdist_base = os.path.join(build_base, - 'bdist.' + self.plat_name) - - self.ensure_string_list('formats') - if self.formats is None: - try: - self.formats = [self.default_format[os.name]] - except KeyError: - raise DistutilsPlatformError( - "don't know how to create built distributions " - "on platform %s" % os.name) - - if self.dist_dir is None: - self.dist_dir = "dist" - - def run(self): - # Figure out which sub-commands we need to run. - commands = [] - for format in self.formats: - try: - commands.append(self.format_command[format][0]) - except KeyError: - raise DistutilsOptionError("invalid format '%s'" % format) - - # Reinitialize and run each command. - for i in range(len(self.formats)): - cmd_name = commands[i] - sub_cmd = self.reinitialize_command(cmd_name) - if cmd_name not in self.no_format_option: - sub_cmd.format = self.formats[i] - - # passing the owner and group names for tar archiving - if cmd_name == 'bdist_dumb': - sub_cmd.owner = self.owner - sub_cmd.group = self.group - - # If we're going to need to run this command again, tell it to - # keep its temporary files around so subsequent runs go faster. - if cmd_name in commands[i+1:]: - sub_cmd.keep_temp = 1 - self.run_command(cmd_name) diff --git a/Lib/distutils/command/bdist_dumb.py b/Lib/distutils/command/bdist_dumb.py deleted file mode 100644 index f0d6b5b8cd8..00000000000 --- a/Lib/distutils/command/bdist_dumb.py +++ /dev/null @@ -1,123 +0,0 @@ -"""distutils.command.bdist_dumb - -Implements the Distutils 'bdist_dumb' command (create a "dumb" built -distribution -- i.e., just an archive to be unpacked under $prefix or -$exec_prefix).""" - -import os -from distutils.core import Command -from distutils.util import get_platform -from distutils.dir_util import remove_tree, ensure_relative -from distutils.errors import * -from distutils.sysconfig import get_python_version -from distutils import log - -class bdist_dumb(Command): - - description = "create a \"dumb\" built distribution" - - user_options = [('bdist-dir=', 'd', - "temporary directory for creating the distribution"), - ('plat-name=', 'p', - "platform name to embed in generated filenames " - "(default: %s)" % get_platform()), - ('format=', 'f', - "archive format to create (tar, gztar, bztar, xztar, " - "ztar, zip)"), - ('keep-temp', 'k', - "keep the pseudo-installation tree around after " + - "creating the distribution archive"), - ('dist-dir=', 'd', - "directory to put final built distributions in"), - ('skip-build', None, - "skip rebuilding everything (for testing/debugging)"), - ('relative', None, - "build the archive using relative paths " - "(default: false)"), - ('owner=', 'u', - "Owner name used when creating a tar file" - " [default: current user]"), - ('group=', 'g', - "Group name used when creating a tar file" - " [default: current group]"), - ] - - boolean_options = ['keep-temp', 'skip-build', 'relative'] - - default_format = { 'posix': 'gztar', - 'nt': 'zip' } - - def initialize_options(self): - self.bdist_dir = None - self.plat_name = None - self.format = None - self.keep_temp = 0 - self.dist_dir = None - self.skip_build = None - self.relative = 0 - self.owner = None - self.group = None - - def finalize_options(self): - if self.bdist_dir is None: - bdist_base = self.get_finalized_command('bdist').bdist_base - self.bdist_dir = os.path.join(bdist_base, 'dumb') - - if self.format is None: - try: - self.format = self.default_format[os.name] - except KeyError: - raise DistutilsPlatformError( - "don't know how to create dumb built distributions " - "on platform %s" % os.name) - - self.set_undefined_options('bdist', - ('dist_dir', 'dist_dir'), - ('plat_name', 'plat_name'), - ('skip_build', 'skip_build')) - - def run(self): - if not self.skip_build: - self.run_command('build') - - install = self.reinitialize_command('install', reinit_subcommands=1) - install.root = self.bdist_dir - install.skip_build = self.skip_build - install.warn_dir = 0 - - log.info("installing to %s", self.bdist_dir) - self.run_command('install') - - # And make an archive relative to the root of the - # pseudo-installation tree. - archive_basename = "%s.%s" % (self.distribution.get_fullname(), - self.plat_name) - - pseudoinstall_root = os.path.join(self.dist_dir, archive_basename) - if not self.relative: - archive_root = self.bdist_dir - else: - if (self.distribution.has_ext_modules() and - (install.install_base != install.install_platbase)): - raise DistutilsPlatformError( - "can't make a dumb built distribution where " - "base and platbase are different (%s, %s)" - % (repr(install.install_base), - repr(install.install_platbase))) - else: - archive_root = os.path.join(self.bdist_dir, - ensure_relative(install.install_base)) - - # Make the archive - filename = self.make_archive(pseudoinstall_root, - self.format, root_dir=archive_root, - owner=self.owner, group=self.group) - if self.distribution.has_ext_modules(): - pyversion = get_python_version() - else: - pyversion = 'any' - self.distribution.dist_files.append(('bdist_dumb', pyversion, - filename)) - - if not self.keep_temp: - remove_tree(self.bdist_dir, dry_run=self.dry_run) diff --git a/Lib/distutils/command/bdist_msi.py b/Lib/distutils/command/bdist_msi.py deleted file mode 100644 index 80104c372d9..00000000000 --- a/Lib/distutils/command/bdist_msi.py +++ /dev/null @@ -1,741 +0,0 @@ -# Copyright (C) 2005, 2006 Martin von Löwis -# Licensed to PSF under a Contributor Agreement. -# The bdist_wininst command proper -# based on bdist_wininst -""" -Implements the bdist_msi command. -""" - -import sys, os -from distutils.core import Command -from distutils.dir_util import remove_tree -from distutils.sysconfig import get_python_version -from distutils.version import StrictVersion -from distutils.errors import DistutilsOptionError -from distutils.util import get_platform -from distutils import log -import msilib -from msilib import schema, sequence, text -from msilib import Directory, Feature, Dialog, add_data - -class PyDialog(Dialog): - """Dialog class with a fixed layout: controls at the top, then a ruler, - then a list of buttons: back, next, cancel. Optionally a bitmap at the - left.""" - def __init__(self, *args, **kw): - """Dialog(database, name, x, y, w, h, attributes, title, first, - default, cancel, bitmap=true)""" - Dialog.__init__(self, *args) - ruler = self.h - 36 - bmwidth = 152*ruler/328 - #if kw.get("bitmap", True): - # self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin") - self.line("BottomLine", 0, ruler, self.w, 0) - - def title(self, title): - "Set the title text of the dialog at the top." - # name, x, y, w, h, flags=Visible|Enabled|Transparent|NoPrefix, - # text, in VerdanaBold10 - self.text("Title", 15, 10, 320, 60, 0x30003, - r"{\VerdanaBold10}%s" % title) - - def back(self, title, next, name = "Back", active = 1): - """Add a back button with a given title, the tab-next button, - its name in the Control table, possibly initially disabled. - - Return the button, so that events can be associated""" - if active: - flags = 3 # Visible|Enabled - else: - flags = 1 # Visible - return self.pushbutton(name, 180, self.h-27 , 56, 17, flags, title, next) - - def cancel(self, title, next, name = "Cancel", active = 1): - """Add a cancel button with a given title, the tab-next button, - its name in the Control table, possibly initially disabled. - - Return the button, so that events can be associated""" - if active: - flags = 3 # Visible|Enabled - else: - flags = 1 # Visible - return self.pushbutton(name, 304, self.h-27, 56, 17, flags, title, next) - - def next(self, title, next, name = "Next", active = 1): - """Add a Next button with a given title, the tab-next button, - its name in the Control table, possibly initially disabled. - - Return the button, so that events can be associated""" - if active: - flags = 3 # Visible|Enabled - else: - flags = 1 # Visible - return self.pushbutton(name, 236, self.h-27, 56, 17, flags, title, next) - - def xbutton(self, name, title, next, xpos): - """Add a button with a given title, the tab-next button, - its name in the Control table, giving its x position; the - y-position is aligned with the other buttons. - - Return the button, so that events can be associated""" - return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next) - -class bdist_msi(Command): - - description = "create a Microsoft Installer (.msi) binary distribution" - - user_options = [('bdist-dir=', None, - "temporary directory for creating the distribution"), - ('plat-name=', 'p', - "platform name to embed in generated filenames " - "(default: %s)" % get_platform()), - ('keep-temp', 'k', - "keep the pseudo-installation tree around after " + - "creating the distribution archive"), - ('target-version=', None, - "require a specific python version" + - " on the target system"), - ('no-target-compile', 'c', - "do not compile .py to .pyc on the target system"), - ('no-target-optimize', 'o', - "do not compile .py to .pyo (optimized) " - "on the target system"), - ('dist-dir=', 'd', - "directory to put final built distributions in"), - ('skip-build', None, - "skip rebuilding everything (for testing/debugging)"), - ('install-script=', None, - "basename of installation script to be run after " - "installation or before deinstallation"), - ('pre-install-script=', None, - "Fully qualified filename of a script to be run before " - "any files are installed. This script need not be in the " - "distribution"), - ] - - boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize', - 'skip-build'] - - all_versions = ['2.0', '2.1', '2.2', '2.3', '2.4', - '2.5', '2.6', '2.7', '2.8', '2.9', - '3.0', '3.1', '3.2', '3.3', '3.4', - '3.5', '3.6', '3.7', '3.8', '3.9'] - other_version = 'X' - - def initialize_options(self): - self.bdist_dir = None - self.plat_name = None - self.keep_temp = 0 - self.no_target_compile = 0 - self.no_target_optimize = 0 - self.target_version = None - self.dist_dir = None - self.skip_build = None - self.install_script = None - self.pre_install_script = None - self.versions = None - - def finalize_options(self): - self.set_undefined_options('bdist', ('skip_build', 'skip_build')) - - if self.bdist_dir is None: - bdist_base = self.get_finalized_command('bdist').bdist_base - self.bdist_dir = os.path.join(bdist_base, 'msi') - - short_version = get_python_version() - if (not self.target_version) and self.distribution.has_ext_modules(): - self.target_version = short_version - - if self.target_version: - self.versions = [self.target_version] - if not self.skip_build and self.distribution.has_ext_modules()\ - and self.target_version != short_version: - raise DistutilsOptionError( - "target version can only be %s, or the '--skip-build'" - " option must be specified" % (short_version,)) - else: - self.versions = list(self.all_versions) - - self.set_undefined_options('bdist', - ('dist_dir', 'dist_dir'), - ('plat_name', 'plat_name'), - ) - - if self.pre_install_script: - raise DistutilsOptionError( - "the pre-install-script feature is not yet implemented") - - if self.install_script: - for script in self.distribution.scripts: - if self.install_script == os.path.basename(script): - break - else: - raise DistutilsOptionError( - "install_script '%s' not found in scripts" - % self.install_script) - self.install_script_key = None - - def run(self): - if not self.skip_build: - self.run_command('build') - - install = self.reinitialize_command('install', reinit_subcommands=1) - install.prefix = self.bdist_dir - install.skip_build = self.skip_build - install.warn_dir = 0 - - install_lib = self.reinitialize_command('install_lib') - # we do not want to include pyc or pyo files - install_lib.compile = 0 - install_lib.optimize = 0 - - if self.distribution.has_ext_modules(): - # If we are building an installer for a Python version other - # than the one we are currently running, then we need to ensure - # our build_lib reflects the other Python version rather than ours. - # Note that for target_version!=sys.version, we must have skipped the - # build step, so there is no issue with enforcing the build of this - # version. - target_version = self.target_version - if not target_version: - assert self.skip_build, "Should have already checked this" - target_version = '%d.%d' % sys.version_info[:2] - plat_specifier = ".%s-%s" % (self.plat_name, target_version) - build = self.get_finalized_command('build') - build.build_lib = os.path.join(build.build_base, - 'lib' + plat_specifier) - - log.info("installing to %s", self.bdist_dir) - install.ensure_finalized() - - # avoid warning of 'install_lib' about installing - # into a directory not in sys.path - sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB')) - - install.run() - - del sys.path[0] - - self.mkpath(self.dist_dir) - fullname = self.distribution.get_fullname() - installer_name = self.get_installer_filename(fullname) - installer_name = os.path.abspath(installer_name) - if os.path.exists(installer_name): os.unlink(installer_name) - - metadata = self.distribution.metadata - author = metadata.author - if not author: - author = metadata.maintainer - if not author: - author = "UNKNOWN" - version = metadata.get_version() - # ProductVersion must be strictly numeric - # XXX need to deal with prerelease versions - sversion = "%d.%d.%d" % StrictVersion(version).version - # Prefix ProductName with Python x.y, so that - # it sorts together with the other Python packages - # in Add-Remove-Programs (APR) - fullname = self.distribution.get_fullname() - if self.target_version: - product_name = "Python %s %s" % (self.target_version, fullname) - else: - product_name = "Python %s" % (fullname) - self.db = msilib.init_database(installer_name, schema, - product_name, msilib.gen_uuid(), - sversion, author) - msilib.add_tables(self.db, sequence) - props = [('DistVersion', version)] - email = metadata.author_email or metadata.maintainer_email - if email: - props.append(("ARPCONTACT", email)) - if metadata.url: - props.append(("ARPURLINFOABOUT", metadata.url)) - if props: - add_data(self.db, 'Property', props) - - self.add_find_python() - self.add_files() - self.add_scripts() - self.add_ui() - self.db.Commit() - - if hasattr(self.distribution, 'dist_files'): - tup = 'bdist_msi', self.target_version or 'any', fullname - self.distribution.dist_files.append(tup) - - if not self.keep_temp: - remove_tree(self.bdist_dir, dry_run=self.dry_run) - - def add_files(self): - db = self.db - cab = msilib.CAB("distfiles") - rootdir = os.path.abspath(self.bdist_dir) - - root = Directory(db, cab, None, rootdir, "TARGETDIR", "SourceDir") - f = Feature(db, "Python", "Python", "Everything", - 0, 1, directory="TARGETDIR") - - items = [(f, root, '')] - for version in self.versions + [self.other_version]: - target = "TARGETDIR" + version - name = default = "Python" + version - desc = "Everything" - if version is self.other_version: - title = "Python from another location" - level = 2 - else: - title = "Python %s from registry" % version - level = 1 - f = Feature(db, name, title, desc, 1, level, directory=target) - dir = Directory(db, cab, root, rootdir, target, default) - items.append((f, dir, version)) - db.Commit() - - seen = {} - for feature, dir, version in items: - todo = [dir] - while todo: - dir = todo.pop() - for file in os.listdir(dir.absolute): - afile = os.path.join(dir.absolute, file) - if os.path.isdir(afile): - short = "%s|%s" % (dir.make_short(file), file) - default = file + version - newdir = Directory(db, cab, dir, file, default, short) - todo.append(newdir) - else: - if not dir.component: - dir.start_component(dir.logical, feature, 0) - if afile not in seen: - key = seen[afile] = dir.add_file(file) - if file==self.install_script: - if self.install_script_key: - raise DistutilsOptionError( - "Multiple files with name %s" % file) - self.install_script_key = '[#%s]' % key - else: - key = seen[afile] - add_data(self.db, "DuplicateFile", - [(key + version, dir.component, key, None, dir.logical)]) - db.Commit() - cab.commit(db) - - def add_find_python(self): - """Adds code to the installer to compute the location of Python. - - Properties PYTHON.MACHINE.X.Y and PYTHON.USER.X.Y will be set from the - registry for each version of Python. - - Properties TARGETDIRX.Y will be set from PYTHON.USER.X.Y if defined, - else from PYTHON.MACHINE.X.Y. - - Properties PYTHONX.Y will be set to TARGETDIRX.Y\\python.exe""" - - start = 402 - for ver in self.versions: - install_path = r"SOFTWARE\Python\PythonCore\%s\InstallPath" % ver - machine_reg = "python.machine." + ver - user_reg = "python.user." + ver - machine_prop = "PYTHON.MACHINE." + ver - user_prop = "PYTHON.USER." + ver - machine_action = "PythonFromMachine" + ver - user_action = "PythonFromUser" + ver - exe_action = "PythonExe" + ver - target_dir_prop = "TARGETDIR" + ver - exe_prop = "PYTHON" + ver - if msilib.Win64: - # type: msidbLocatorTypeRawValue + msidbLocatorType64bit - Type = 2+16 - else: - Type = 2 - add_data(self.db, "RegLocator", - [(machine_reg, 2, install_path, None, Type), - (user_reg, 1, install_path, None, Type)]) - add_data(self.db, "AppSearch", - [(machine_prop, machine_reg), - (user_prop, user_reg)]) - add_data(self.db, "CustomAction", - [(machine_action, 51+256, target_dir_prop, "[" + machine_prop + "]"), - (user_action, 51+256, target_dir_prop, "[" + user_prop + "]"), - (exe_action, 51+256, exe_prop, "[" + target_dir_prop + "]\\python.exe"), - ]) - add_data(self.db, "InstallExecuteSequence", - [(machine_action, machine_prop, start), - (user_action, user_prop, start + 1), - (exe_action, None, start + 2), - ]) - add_data(self.db, "InstallUISequence", - [(machine_action, machine_prop, start), - (user_action, user_prop, start + 1), - (exe_action, None, start + 2), - ]) - add_data(self.db, "Condition", - [("Python" + ver, 0, "NOT TARGETDIR" + ver)]) - start += 4 - assert start < 500 - - def add_scripts(self): - if self.install_script: - start = 6800 - for ver in self.versions + [self.other_version]: - install_action = "install_script." + ver - exe_prop = "PYTHON" + ver - add_data(self.db, "CustomAction", - [(install_action, 50, exe_prop, self.install_script_key)]) - add_data(self.db, "InstallExecuteSequence", - [(install_action, "&Python%s=3" % ver, start)]) - start += 1 - # XXX pre-install scripts are currently refused in finalize_options() - # but if this feature is completed, it will also need to add - # entries for each version as the above code does - if self.pre_install_script: - scriptfn = os.path.join(self.bdist_dir, "preinstall.bat") - f = open(scriptfn, "w") - # The batch file will be executed with [PYTHON], so that %1 - # is the path to the Python interpreter; %0 will be the path - # of the batch file. - # rem =""" - # %1 %0 - # exit - # """ - # <actual script> - f.write('rem ="""\n%1 %0\nexit\n"""\n') - f.write(open(self.pre_install_script).read()) - f.close() - add_data(self.db, "Binary", - [("PreInstall", msilib.Binary(scriptfn)) - ]) - add_data(self.db, "CustomAction", - [("PreInstall", 2, "PreInstall", None) - ]) - add_data(self.db, "InstallExecuteSequence", - [("PreInstall", "NOT Installed", 450)]) - - - def add_ui(self): - db = self.db - x = y = 50 - w = 370 - h = 300 - title = "[ProductName] Setup" - - # see "Dialog Style Bits" - modal = 3 # visible | modal - modeless = 1 # visible - track_disk_space = 32 - - # UI customization properties - add_data(db, "Property", - # See "DefaultUIFont Property" - [("DefaultUIFont", "DlgFont8"), - # See "ErrorDialog Style Bit" - ("ErrorDialog", "ErrorDlg"), - ("Progress1", "Install"), # modified in maintenance type dlg - ("Progress2", "installs"), - ("MaintenanceForm_Action", "Repair"), - # possible values: ALL, JUSTME - ("WhichUsers", "ALL") - ]) - - # Fonts, see "TextStyle Table" - add_data(db, "TextStyle", - [("DlgFont8", "Tahoma", 9, None, 0), - ("DlgFontBold8", "Tahoma", 8, None, 1), #bold - ("VerdanaBold10", "Verdana", 10, None, 1), - ("VerdanaRed9", "Verdana", 9, 255, 0), - ]) - - # UI Sequences, see "InstallUISequence Table", "Using a Sequence Table" - # Numbers indicate sequence; see sequence.py for how these action integrate - add_data(db, "InstallUISequence", - [("PrepareDlg", "Not Privileged or Windows9x or Installed", 140), - ("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141), - # In the user interface, assume all-users installation if privileged. - ("SelectFeaturesDlg", "Not Installed", 1230), - # XXX no support for resume installations yet - #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240), - ("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250), - ("ProgressDlg", None, 1280)]) - - add_data(db, 'ActionText', text.ActionText) - add_data(db, 'UIText', text.UIText) - ##################################################################### - # Standard dialogs: FatalError, UserExit, ExitDialog - fatal=PyDialog(db, "FatalError", x, y, w, h, modal, title, - "Finish", "Finish", "Finish") - fatal.title("[ProductName] Installer ended prematurely") - fatal.back("< Back", "Finish", active = 0) - fatal.cancel("Cancel", "Back", active = 0) - fatal.text("Description1", 15, 70, 320, 80, 0x30003, - "[ProductName] setup ended prematurely because of an error. Your system has not been modified. To install this program at a later time, please run the installation again.") - fatal.text("Description2", 15, 155, 320, 20, 0x30003, - "Click the Finish button to exit the Installer.") - c=fatal.next("Finish", "Cancel", name="Finish") - c.event("EndDialog", "Exit") - - user_exit=PyDialog(db, "UserExit", x, y, w, h, modal, title, - "Finish", "Finish", "Finish") - user_exit.title("[ProductName] Installer was interrupted") - user_exit.back("< Back", "Finish", active = 0) - user_exit.cancel("Cancel", "Back", active = 0) - user_exit.text("Description1", 15, 70, 320, 80, 0x30003, - "[ProductName] setup was interrupted. Your system has not been modified. " - "To install this program at a later time, please run the installation again.") - user_exit.text("Description2", 15, 155, 320, 20, 0x30003, - "Click the Finish button to exit the Installer.") - c = user_exit.next("Finish", "Cancel", name="Finish") - c.event("EndDialog", "Exit") - - exit_dialog = PyDialog(db, "ExitDialog", x, y, w, h, modal, title, - "Finish", "Finish", "Finish") - exit_dialog.title("Completing the [ProductName] Installer") - exit_dialog.back("< Back", "Finish", active = 0) - exit_dialog.cancel("Cancel", "Back", active = 0) - exit_dialog.text("Description", 15, 235, 320, 20, 0x30003, - "Click the Finish button to exit the Installer.") - c = exit_dialog.next("Finish", "Cancel", name="Finish") - c.event("EndDialog", "Return") - - ##################################################################### - # Required dialog: FilesInUse, ErrorDlg - inuse = PyDialog(db, "FilesInUse", - x, y, w, h, - 19, # KeepModeless|Modal|Visible - title, - "Retry", "Retry", "Retry", bitmap=False) - inuse.text("Title", 15, 6, 200, 15, 0x30003, - r"{\DlgFontBold8}Files in Use") - inuse.text("Description", 20, 23, 280, 20, 0x30003, - "Some files that need to be updated are currently in use.") - inuse.text("Text", 20, 55, 330, 50, 3, - "The following applications are using files that need to be updated by this setup. Close these applications and then click Retry to continue the installation or Cancel to exit it.") - inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess", - None, None, None) - c=inuse.back("Exit", "Ignore", name="Exit") - c.event("EndDialog", "Exit") - c=inuse.next("Ignore", "Retry", name="Ignore") - c.event("EndDialog", "Ignore") - c=inuse.cancel("Retry", "Exit", name="Retry") - c.event("EndDialog","Retry") - - # See "Error Dialog". See "ICE20" for the required names of the controls. - error = Dialog(db, "ErrorDlg", - 50, 10, 330, 101, - 65543, # Error|Minimize|Modal|Visible - title, - "ErrorText", None, None) - error.text("ErrorText", 50,9,280,48,3, "") - #error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None) - error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo") - error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes") - error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort") - error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel") - error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore") - error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk") - error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry") - - ##################################################################### - # Global "Query Cancel" dialog - cancel = Dialog(db, "CancelDlg", 50, 10, 260, 85, 3, title, - "No", "No", "No") - cancel.text("Text", 48, 15, 194, 30, 3, - "Are you sure you want to cancel [ProductName] installation?") - #cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None, - # "py.ico", None, None) - c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No") - c.event("EndDialog", "Exit") - - c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes") - c.event("EndDialog", "Return") - - ##################################################################### - # Global "Wait for costing" dialog - costing = Dialog(db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title, - "Return", "Return", "Return") - costing.text("Text", 48, 15, 194, 30, 3, - "Please wait while the installer finishes determining your disk space requirements.") - c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None) - c.event("EndDialog", "Exit") - - ##################################################################### - # Preparation dialog: no user input except cancellation - prep = PyDialog(db, "PrepareDlg", x, y, w, h, modeless, title, - "Cancel", "Cancel", "Cancel") - prep.text("Description", 15, 70, 320, 40, 0x30003, - "Please wait while the Installer prepares to guide you through the installation.") - prep.title("Welcome to the [ProductName] Installer") - c=prep.text("ActionText", 15, 110, 320, 20, 0x30003, "Pondering...") - c.mapping("ActionText", "Text") - c=prep.text("ActionData", 15, 135, 320, 30, 0x30003, None) - c.mapping("ActionData", "Text") - prep.back("Back", None, active=0) - prep.next("Next", None, active=0) - c=prep.cancel("Cancel", None) - c.event("SpawnDialog", "CancelDlg") - - ##################################################################### - # Feature (Python directory) selection - seldlg = PyDialog(db, "SelectFeaturesDlg", x, y, w, h, modal, title, - "Next", "Next", "Cancel") - seldlg.title("Select Python Installations") - - seldlg.text("Hint", 15, 30, 300, 20, 3, - "Select the Python locations where %s should be installed." - % self.distribution.get_fullname()) - - seldlg.back("< Back", None, active=0) - c = seldlg.next("Next >", "Cancel") - order = 1 - c.event("[TARGETDIR]", "[SourceDir]", ordering=order) - for version in self.versions + [self.other_version]: - order += 1 - c.event("[TARGETDIR]", "[TARGETDIR%s]" % version, - "FEATURE_SELECTED AND &Python%s=3" % version, - ordering=order) - c.event("SpawnWaitDialog", "WaitForCostingDlg", ordering=order + 1) - c.event("EndDialog", "Return", ordering=order + 2) - c = seldlg.cancel("Cancel", "Features") - c.event("SpawnDialog", "CancelDlg") - - c = seldlg.control("Features", "SelectionTree", 15, 60, 300, 120, 3, - "FEATURE", None, "PathEdit", None) - c.event("[FEATURE_SELECTED]", "1") - ver = self.other_version - install_other_cond = "FEATURE_SELECTED AND &Python%s=3" % ver - dont_install_other_cond = "FEATURE_SELECTED AND &Python%s<>3" % ver - - c = seldlg.text("Other", 15, 200, 300, 15, 3, - "Provide an alternate Python location") - c.condition("Enable", install_other_cond) - c.condition("Show", install_other_cond) - c.condition("Disable", dont_install_other_cond) - c.condition("Hide", dont_install_other_cond) - - c = seldlg.control("PathEdit", "PathEdit", 15, 215, 300, 16, 1, - "TARGETDIR" + ver, None, "Next", None) - c.condition("Enable", install_other_cond) - c.condition("Show", install_other_cond) - c.condition("Disable", dont_install_other_cond) - c.condition("Hide", dont_install_other_cond) - - ##################################################################### - # Disk cost - cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title, - "OK", "OK", "OK", bitmap=False) - cost.text("Title", 15, 6, 200, 15, 0x30003, - r"{\DlgFontBold8}Disk Space Requirements") - cost.text("Description", 20, 20, 280, 20, 0x30003, - "The disk space required for the installation of the selected features.") - cost.text("Text", 20, 53, 330, 60, 3, - "The highlighted volumes (if any) do not have enough disk space " - "available for the currently selected features. You can either " - "remove some files from the highlighted volumes, or choose to " - "install less features onto local drive(s), or select different " - "destination drive(s).") - cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223, - None, "{120}{70}{70}{70}{70}", None, None) - cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return") - - ##################################################################### - # WhichUsers Dialog. Only available on NT, and for privileged users. - # This must be run before FindRelatedProducts, because that will - # take into account whether the previous installation was per-user - # or per-machine. We currently don't support going back to this - # dialog after "Next" was selected; to support this, we would need to - # find how to reset the ALLUSERS property, and how to re-run - # FindRelatedProducts. - # On Windows9x, the ALLUSERS property is ignored on the command line - # and in the Property table, but installer fails according to the documentation - # if a dialog attempts to set ALLUSERS. - whichusers = PyDialog(db, "WhichUsersDlg", x, y, w, h, modal, title, - "AdminInstall", "Next", "Cancel") - whichusers.title("Select whether to install [ProductName] for all users of this computer.") - # A radio group with two options: allusers, justme - g = whichusers.radiogroup("AdminInstall", 15, 60, 260, 50, 3, - "WhichUsers", "", "Next") - g.add("ALL", 0, 5, 150, 20, "Install for all users") - g.add("JUSTME", 0, 25, 150, 20, "Install just for me") - - whichusers.back("Back", None, active=0) - - c = whichusers.next("Next >", "Cancel") - c.event("[ALLUSERS]", "1", 'WhichUsers="ALL"', 1) - c.event("EndDialog", "Return", ordering = 2) - - c = whichusers.cancel("Cancel", "AdminInstall") - c.event("SpawnDialog", "CancelDlg") - - ##################################################################### - # Installation Progress dialog (modeless) - progress = PyDialog(db, "ProgressDlg", x, y, w, h, modeless, title, - "Cancel", "Cancel", "Cancel", bitmap=False) - progress.text("Title", 20, 15, 200, 15, 0x30003, - r"{\DlgFontBold8}[Progress1] [ProductName]") - progress.text("Text", 35, 65, 300, 30, 3, - "Please wait while the Installer [Progress2] [ProductName]. " - "This may take several minutes.") - progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:") - - c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...") - c.mapping("ActionText", "Text") - - #c=progress.text("ActionData", 35, 140, 300, 20, 3, None) - #c.mapping("ActionData", "Text") - - c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537, - None, "Progress done", None, None) - c.mapping("SetProgress", "Progress") - - progress.back("< Back", "Next", active=False) - progress.next("Next >", "Cancel", active=False) - progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg") - - ################################################################### - # Maintenance type: repair/uninstall - maint = PyDialog(db, "MaintenanceTypeDlg", x, y, w, h, modal, title, - "Next", "Next", "Cancel") - maint.title("Welcome to the [ProductName] Setup Wizard") - maint.text("BodyText", 15, 63, 330, 42, 3, - "Select whether you want to repair or remove [ProductName].") - g=maint.radiogroup("RepairRadioGroup", 15, 108, 330, 60, 3, - "MaintenanceForm_Action", "", "Next") - #g.add("Change", 0, 0, 200, 17, "&Change [ProductName]") - g.add("Repair", 0, 18, 200, 17, "&Repair [ProductName]") - g.add("Remove", 0, 36, 200, 17, "Re&move [ProductName]") - - maint.back("< Back", None, active=False) - c=maint.next("Finish", "Cancel") - # Change installation: Change progress dialog to "Change", then ask - # for feature selection - #c.event("[Progress1]", "Change", 'MaintenanceForm_Action="Change"', 1) - #c.event("[Progress2]", "changes", 'MaintenanceForm_Action="Change"', 2) - - # Reinstall: Change progress dialog to "Repair", then invoke reinstall - # Also set list of reinstalled features to "ALL" - c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 5) - c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 6) - c.event("[Progress2]", "repairs", 'MaintenanceForm_Action="Repair"', 7) - c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 8) - - # Uninstall: Change progress to "Remove", then invoke uninstall - # Also set list of removed features to "ALL" - c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11) - c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12) - c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13) - c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14) - - # Close dialog when maintenance action scheduled - c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20) - #c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21) - - maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg") - - def get_installer_filename(self, fullname): - # Factored out to allow overriding in subclasses - if self.target_version: - base_name = "%s.%s-py%s.msi" % (fullname, self.plat_name, - self.target_version) - else: - base_name = "%s.%s.msi" % (fullname, self.plat_name) - installer_name = os.path.join(self.dist_dir, base_name) - return installer_name diff --git a/Lib/distutils/command/bdist_rpm.py b/Lib/distutils/command/bdist_rpm.py deleted file mode 100644 index 02f10dd89d9..00000000000 --- a/Lib/distutils/command/bdist_rpm.py +++ /dev/null @@ -1,582 +0,0 @@ -"""distutils.command.bdist_rpm - -Implements the Distutils 'bdist_rpm' command (create RPM source and binary -distributions).""" - -import subprocess, sys, os -from distutils.core import Command -from distutils.debug import DEBUG -from distutils.util import get_platform -from distutils.file_util import write_file -from distutils.errors import * -from distutils.sysconfig import get_python_version -from distutils import log - -class bdist_rpm(Command): - - description = "create an RPM distribution" - - user_options = [ - ('bdist-base=', None, - "base directory for creating built distributions"), - ('rpm-base=', None, - "base directory for creating RPMs (defaults to \"rpm\" under " - "--bdist-base; must be specified for RPM 2)"), - ('dist-dir=', 'd', - "directory to put final RPM files in " - "(and .spec files if --spec-only)"), - ('python=', None, - "path to Python interpreter to hard-code in the .spec file " - "(default: \"python\")"), - ('fix-python', None, - "hard-code the exact path to the current Python interpreter in " - "the .spec file"), - ('spec-only', None, - "only regenerate spec file"), - ('source-only', None, - "only generate source RPM"), - ('binary-only', None, - "only generate binary RPM"), - ('use-bzip2', None, - "use bzip2 instead of gzip to create source distribution"), - - # More meta-data: too RPM-specific to put in the setup script, - # but needs to go in the .spec file -- so we make these options - # to "bdist_rpm". The idea is that packagers would put this - # info in setup.cfg, although they are of course free to - # supply it on the command line. - ('distribution-name=', None, - "name of the (Linux) distribution to which this " - "RPM applies (*not* the name of the module distribution!)"), - ('group=', None, - "package classification [default: \"Development/Libraries\"]"), - ('release=', None, - "RPM release number"), - ('serial=', None, - "RPM serial number"), - ('vendor=', None, - "RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") " - "[default: maintainer or author from setup script]"), - ('packager=', None, - "RPM packager (eg. \"Jane Doe <jane@example.net>\") " - "[default: vendor]"), - ('doc-files=', None, - "list of documentation files (space or comma-separated)"), - ('changelog=', None, - "RPM changelog"), - ('icon=', None, - "name of icon file"), - ('provides=', None, - "capabilities provided by this package"), - ('requires=', None, - "capabilities required by this package"), - ('conflicts=', None, - "capabilities which conflict with this package"), - ('build-requires=', None, - "capabilities required to build this package"), - ('obsoletes=', None, - "capabilities made obsolete by this package"), - ('no-autoreq', None, - "do not automatically calculate dependencies"), - - # Actions to take when building RPM - ('keep-temp', 'k', - "don't clean up RPM build directory"), - ('no-keep-temp', None, - "clean up RPM build directory [default]"), - ('use-rpm-opt-flags', None, - "compile with RPM_OPT_FLAGS when building from source RPM"), - ('no-rpm-opt-flags', None, - "do not pass any RPM CFLAGS to compiler"), - ('rpm3-mode', None, - "RPM 3 compatibility mode (default)"), - ('rpm2-mode', None, - "RPM 2 compatibility mode"), - - # Add the hooks necessary for specifying custom scripts - ('prep-script=', None, - "Specify a script for the PREP phase of RPM building"), - ('build-script=', None, - "Specify a script for the BUILD phase of RPM building"), - - ('pre-install=', None, - "Specify a script for the pre-INSTALL phase of RPM building"), - ('install-script=', None, - "Specify a script for the INSTALL phase of RPM building"), - ('post-install=', None, - "Specify a script for the post-INSTALL phase of RPM building"), - - ('pre-uninstall=', None, - "Specify a script for the pre-UNINSTALL phase of RPM building"), - ('post-uninstall=', None, - "Specify a script for the post-UNINSTALL phase of RPM building"), - - ('clean-script=', None, - "Specify a script for the CLEAN phase of RPM building"), - - ('verify-script=', None, - "Specify a script for the VERIFY phase of the RPM build"), - - # Allow a packager to explicitly force an architecture - ('force-arch=', None, - "Force an architecture onto the RPM build process"), - - ('quiet', 'q', - "Run the INSTALL phase of RPM building in quiet mode"), - ] - - boolean_options = ['keep-temp', 'use-rpm-opt-flags', 'rpm3-mode', - 'no-autoreq', 'quiet'] - - negative_opt = {'no-keep-temp': 'keep-temp', - 'no-rpm-opt-flags': 'use-rpm-opt-flags', - 'rpm2-mode': 'rpm3-mode'} - - - def initialize_options(self): - self.bdist_base = None - self.rpm_base = None - self.dist_dir = None - self.python = None - self.fix_python = None - self.spec_only = None - self.binary_only = None - self.source_only = None - self.use_bzip2 = None - - self.distribution_name = None - self.group = None - self.release = None - self.serial = None - self.vendor = None - self.packager = None - self.doc_files = None - self.changelog = None - self.icon = None - - self.prep_script = None - self.build_script = None - self.install_script = None - self.clean_script = None - self.verify_script = None - self.pre_install = None - self.post_install = None - self.pre_uninstall = None - self.post_uninstall = None - self.prep = None - self.provides = None - self.requires = None - self.conflicts = None - self.build_requires = None - self.obsoletes = None - - self.keep_temp = 0 - self.use_rpm_opt_flags = 1 - self.rpm3_mode = 1 - self.no_autoreq = 0 - - self.force_arch = None - self.quiet = 0 - - def finalize_options(self): - self.set_undefined_options('bdist', ('bdist_base', 'bdist_base')) - if self.rpm_base is None: - if not self.rpm3_mode: - raise DistutilsOptionError( - "you must specify --rpm-base in RPM 2 mode") - self.rpm_base = os.path.join(self.bdist_base, "rpm") - - if self.python is None: - if self.fix_python: - self.python = sys.executable - else: - self.python = "python3" - elif self.fix_python: - raise DistutilsOptionError( - "--python and --fix-python are mutually exclusive options") - - if os.name != 'posix': - raise DistutilsPlatformError("don't know how to create RPM " - "distributions on platform %s" % os.name) - if self.binary_only and self.source_only: - raise DistutilsOptionError( - "cannot supply both '--source-only' and '--binary-only'") - - # don't pass CFLAGS to pure python distributions - if not self.distribution.has_ext_modules(): - self.use_rpm_opt_flags = 0 - - self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) - self.finalize_package_data() - - def finalize_package_data(self): - self.ensure_string('group', "Development/Libraries") - self.ensure_string('vendor', - "%s <%s>" % (self.distribution.get_contact(), - self.distribution.get_contact_email())) - self.ensure_string('packager') - self.ensure_string_list('doc_files') - if isinstance(self.doc_files, list): - for readme in ('README', 'README.txt'): - if os.path.exists(readme) and readme not in self.doc_files: - self.doc_files.append(readme) - - self.ensure_string('release', "1") - self.ensure_string('serial') # should it be an int? - - self.ensure_string('distribution_name') - - self.ensure_string('changelog') - # Format changelog correctly - self.changelog = self._format_changelog(self.changelog) - - self.ensure_filename('icon') - - self.ensure_filename('prep_script') - self.ensure_filename('build_script') - self.ensure_filename('install_script') - self.ensure_filename('clean_script') - self.ensure_filename('verify_script') - self.ensure_filename('pre_install') - self.ensure_filename('post_install') - self.ensure_filename('pre_uninstall') - self.ensure_filename('post_uninstall') - - # XXX don't forget we punted on summaries and descriptions -- they - # should be handled here eventually! - - # Now *this* is some meta-data that belongs in the setup script... - self.ensure_string_list('provides') - self.ensure_string_list('requires') - self.ensure_string_list('conflicts') - self.ensure_string_list('build_requires') - self.ensure_string_list('obsoletes') - - self.ensure_string('force_arch') - - def run(self): - if DEBUG: - print("before _get_package_data():") - print("vendor =", self.vendor) - print("packager =", self.packager) - print("doc_files =", self.doc_files) - print("changelog =", self.changelog) - - # make directories - if self.spec_only: - spec_dir = self.dist_dir - self.mkpath(spec_dir) - else: - rpm_dir = {} - for d in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'): - rpm_dir[d] = os.path.join(self.rpm_base, d) - self.mkpath(rpm_dir[d]) - spec_dir = rpm_dir['SPECS'] - - # Spec file goes into 'dist_dir' if '--spec-only specified', - # build/rpm.<plat> otherwise. - spec_path = os.path.join(spec_dir, - "%s.spec" % self.distribution.get_name()) - self.execute(write_file, - (spec_path, - self._make_spec_file()), - "writing '%s'" % spec_path) - - if self.spec_only: # stop if requested - return - - # Make a source distribution and copy to SOURCES directory with - # optional icon. - saved_dist_files = self.distribution.dist_files[:] - sdist = self.reinitialize_command('sdist') - if self.use_bzip2: - sdist.formats = ['bztar'] - else: - sdist.formats = ['gztar'] - self.run_command('sdist') - self.distribution.dist_files = saved_dist_files - - source = sdist.get_archive_files()[0] - source_dir = rpm_dir['SOURCES'] - self.copy_file(source, source_dir) - - if self.icon: - if os.path.exists(self.icon): - self.copy_file(self.icon, source_dir) - else: - raise DistutilsFileError( - "icon file '%s' does not exist" % self.icon) - - # build package - log.info("building RPMs") - rpm_cmd = ['rpm'] - if os.path.exists('/usr/bin/rpmbuild') or \ - os.path.exists('/bin/rpmbuild'): - rpm_cmd = ['rpmbuild'] - - if self.source_only: # what kind of RPMs? - rpm_cmd.append('-bs') - elif self.binary_only: - rpm_cmd.append('-bb') - else: - rpm_cmd.append('-ba') - rpm_cmd.extend(['--define', '__python %s' % self.python]) - if self.rpm3_mode: - rpm_cmd.extend(['--define', - '_topdir %s' % os.path.abspath(self.rpm_base)]) - if not self.keep_temp: - rpm_cmd.append('--clean') - - if self.quiet: - rpm_cmd.append('--quiet') - - rpm_cmd.append(spec_path) - # Determine the binary rpm names that should be built out of this spec - # file - # Note that some of these may not be really built (if the file - # list is empty) - nvr_string = "%{name}-%{version}-%{release}" - src_rpm = nvr_string + ".src.rpm" - non_src_rpm = "%{arch}/" + nvr_string + ".%{arch}.rpm" - q_cmd = r"rpm -q --qf '%s %s\n' --specfile '%s'" % ( - src_rpm, non_src_rpm, spec_path) - - out = os.popen(q_cmd) - try: - binary_rpms = [] - source_rpm = None - while True: - line = out.readline() - if not line: - break - l = line.strip().split() - assert(len(l) == 2) - binary_rpms.append(l[1]) - # The source rpm is named after the first entry in the spec file - if source_rpm is None: - source_rpm = l[0] - - status = out.close() - if status: - raise DistutilsExecError("Failed to execute: %s" % repr(q_cmd)) - - finally: - out.close() - - self.spawn(rpm_cmd) - - if not self.dry_run: - if self.distribution.has_ext_modules(): - pyversion = get_python_version() - else: - pyversion = 'any' - - if not self.binary_only: - srpm = os.path.join(rpm_dir['SRPMS'], source_rpm) - assert(os.path.exists(srpm)) - self.move_file(srpm, self.dist_dir) - filename = os.path.join(self.dist_dir, source_rpm) - self.distribution.dist_files.append( - ('bdist_rpm', pyversion, filename)) - - if not self.source_only: - for rpm in binary_rpms: - rpm = os.path.join(rpm_dir['RPMS'], rpm) - if os.path.exists(rpm): - self.move_file(rpm, self.dist_dir) - filename = os.path.join(self.dist_dir, - os.path.basename(rpm)) - self.distribution.dist_files.append( - ('bdist_rpm', pyversion, filename)) - - def _dist_path(self, path): - return os.path.join(self.dist_dir, os.path.basename(path)) - - def _make_spec_file(self): - """Generate the text of an RPM spec file and return it as a - list of strings (one per line). - """ - # definitions and headers - spec_file = [ - '%define name ' + self.distribution.get_name(), - '%define version ' + self.distribution.get_version().replace('-','_'), - '%define unmangled_version ' + self.distribution.get_version(), - '%define release ' + self.release.replace('-','_'), - '', - 'Summary: ' + self.distribution.get_description(), - ] - - # Workaround for #14443 which affects some RPM based systems such as - # RHEL6 (and probably derivatives) - vendor_hook = subprocess.getoutput('rpm --eval %{__os_install_post}') - # Generate a potential replacement value for __os_install_post (whilst - # normalizing the whitespace to simplify the test for whether the - # invocation of brp-python-bytecompile passes in __python): - vendor_hook = '\n'.join([' %s \\' % line.strip() - for line in vendor_hook.splitlines()]) - problem = "brp-python-bytecompile \\\n" - fixed = "brp-python-bytecompile %{__python} \\\n" - fixed_hook = vendor_hook.replace(problem, fixed) - if fixed_hook != vendor_hook: - spec_file.append('# Workaround for http://bugs.python.org/issue14443') - spec_file.append('%define __os_install_post ' + fixed_hook + '\n') - - # put locale summaries into spec file - # XXX not supported for now (hard to put a dictionary - # in a config file -- arg!) - #for locale in self.summaries.keys(): - # spec_file.append('Summary(%s): %s' % (locale, - # self.summaries[locale])) - - spec_file.extend([ - 'Name: %{name}', - 'Version: %{version}', - 'Release: %{release}',]) - - # XXX yuck! this filename is available from the "sdist" command, - # but only after it has run: and we create the spec file before - # running "sdist", in case of --spec-only. - if self.use_bzip2: - spec_file.append('Source0: %{name}-%{unmangled_version}.tar.bz2') - else: - spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz') - - spec_file.extend([ - 'License: ' + self.distribution.get_license(), - 'Group: ' + self.group, - 'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot', - 'Prefix: %{_prefix}', ]) - - if not self.force_arch: - # noarch if no extension modules - if not self.distribution.has_ext_modules(): - spec_file.append('BuildArch: noarch') - else: - spec_file.append( 'BuildArch: %s' % self.force_arch ) - - for field in ('Vendor', - 'Packager', - 'Provides', - 'Requires', - 'Conflicts', - 'Obsoletes', - ): - val = getattr(self, field.lower()) - if isinstance(val, list): - spec_file.append('%s: %s' % (field, ' '.join(val))) - elif val is not None: - spec_file.append('%s: %s' % (field, val)) - - - if self.distribution.get_url() != 'UNKNOWN': - spec_file.append('Url: ' + self.distribution.get_url()) - - if self.distribution_name: - spec_file.append('Distribution: ' + self.distribution_name) - - if self.build_requires: - spec_file.append('BuildRequires: ' + - ' '.join(self.build_requires)) - - if self.icon: - spec_file.append('Icon: ' + os.path.basename(self.icon)) - - if self.no_autoreq: - spec_file.append('AutoReq: 0') - - spec_file.extend([ - '', - '%description', - self.distribution.get_long_description() - ]) - - # put locale descriptions into spec file - # XXX again, suppressed because config file syntax doesn't - # easily support this ;-( - #for locale in self.descriptions.keys(): - # spec_file.extend([ - # '', - # '%description -l ' + locale, - # self.descriptions[locale], - # ]) - - # rpm scripts - # figure out default build script - def_setup_call = "%s %s" % (self.python,os.path.basename(sys.argv[0])) - def_build = "%s build" % def_setup_call - if self.use_rpm_opt_flags: - def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build - - # insert contents of files - - # XXX this is kind of misleading: user-supplied options are files - # that we open and interpolate into the spec file, but the defaults - # are just text that we drop in as-is. Hmmm. - - install_cmd = ('%s install -O1 --root=$RPM_BUILD_ROOT ' - '--record=INSTALLED_FILES') % def_setup_call - - script_options = [ - ('prep', 'prep_script', "%setup -n %{name}-%{unmangled_version}"), - ('build', 'build_script', def_build), - ('install', 'install_script', install_cmd), - ('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"), - ('verifyscript', 'verify_script', None), - ('pre', 'pre_install', None), - ('post', 'post_install', None), - ('preun', 'pre_uninstall', None), - ('postun', 'post_uninstall', None), - ] - - for (rpm_opt, attr, default) in script_options: - # Insert contents of file referred to, if no file is referred to - # use 'default' as contents of script - val = getattr(self, attr) - if val or default: - spec_file.extend([ - '', - '%' + rpm_opt,]) - if val: - spec_file.extend(open(val, 'r').read().split('\n')) - else: - spec_file.append(default) - - - # files section - spec_file.extend([ - '', - '%files -f INSTALLED_FILES', - '%defattr(-,root,root)', - ]) - - if self.doc_files: - spec_file.append('%doc ' + ' '.join(self.doc_files)) - - if self.changelog: - spec_file.extend([ - '', - '%changelog',]) - spec_file.extend(self.changelog) - - return spec_file - - def _format_changelog(self, changelog): - """Format the changelog correctly and convert it to a list of strings - """ - if not changelog: - return changelog - new_changelog = [] - for line in changelog.strip().split('\n'): - line = line.strip() - if line[0] == '*': - new_changelog.extend(['', line]) - elif line[0] == '-': - new_changelog.append(line) - else: - new_changelog.append(' ' + line) - - # strip trailing newline inserted by first changelog entry - if not new_changelog[0]: - del new_changelog[0] - - return new_changelog diff --git a/Lib/distutils/command/bdist_wininst.py b/Lib/distutils/command/bdist_wininst.py deleted file mode 100644 index 1db47f9b983..00000000000 --- a/Lib/distutils/command/bdist_wininst.py +++ /dev/null @@ -1,367 +0,0 @@ -"""distutils.command.bdist_wininst - -Implements the Distutils 'bdist_wininst' command: create a windows installer -exe-program.""" - -import sys, os -from distutils.core import Command -from distutils.util import get_platform -from distutils.dir_util import create_tree, remove_tree -from distutils.errors import * -from distutils.sysconfig import get_python_version -from distutils import log - -class bdist_wininst(Command): - - description = "create an executable installer for MS Windows" - - user_options = [('bdist-dir=', None, - "temporary directory for creating the distribution"), - ('plat-name=', 'p', - "platform name to embed in generated filenames " - "(default: %s)" % get_platform()), - ('keep-temp', 'k', - "keep the pseudo-installation tree around after " + - "creating the distribution archive"), - ('target-version=', None, - "require a specific python version" + - " on the target system"), - ('no-target-compile', 'c', - "do not compile .py to .pyc on the target system"), - ('no-target-optimize', 'o', - "do not compile .py to .pyo (optimized) " - "on the target system"), - ('dist-dir=', 'd', - "directory to put final built distributions in"), - ('bitmap=', 'b', - "bitmap to use for the installer instead of python-powered logo"), - ('title=', 't', - "title to display on the installer background instead of default"), - ('skip-build', None, - "skip rebuilding everything (for testing/debugging)"), - ('install-script=', None, - "basename of installation script to be run after " - "installation or before deinstallation"), - ('pre-install-script=', None, - "Fully qualified filename of a script to be run before " - "any files are installed. This script need not be in the " - "distribution"), - ('user-access-control=', None, - "specify Vista's UAC handling - 'none'/default=no " - "handling, 'auto'=use UAC if target Python installed for " - "all users, 'force'=always use UAC"), - ] - - boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize', - 'skip-build'] - - def initialize_options(self): - self.bdist_dir = None - self.plat_name = None - self.keep_temp = 0 - self.no_target_compile = 0 - self.no_target_optimize = 0 - self.target_version = None - self.dist_dir = None - self.bitmap = None - self.title = None - self.skip_build = None - self.install_script = None - self.pre_install_script = None - self.user_access_control = None - - - def finalize_options(self): - self.set_undefined_options('bdist', ('skip_build', 'skip_build')) - - if self.bdist_dir is None: - if self.skip_build and self.plat_name: - # If build is skipped and plat_name is overridden, bdist will - # not see the correct 'plat_name' - so set that up manually. - bdist = self.distribution.get_command_obj('bdist') - bdist.plat_name = self.plat_name - # next the command will be initialized using that name - bdist_base = self.get_finalized_command('bdist').bdist_base - self.bdist_dir = os.path.join(bdist_base, 'wininst') - - if not self.target_version: - self.target_version = "" - - if not self.skip_build and self.distribution.has_ext_modules(): - short_version = get_python_version() - if self.target_version and self.target_version != short_version: - raise DistutilsOptionError( - "target version can only be %s, or the '--skip-build'" \ - " option must be specified" % (short_version,)) - self.target_version = short_version - - self.set_undefined_options('bdist', - ('dist_dir', 'dist_dir'), - ('plat_name', 'plat_name'), - ) - - if self.install_script: - for script in self.distribution.scripts: - if self.install_script == os.path.basename(script): - break - else: - raise DistutilsOptionError( - "install_script '%s' not found in scripts" - % self.install_script) - - def run(self): - if (sys.platform != "win32" and - (self.distribution.has_ext_modules() or - self.distribution.has_c_libraries())): - raise DistutilsPlatformError \ - ("distribution contains extensions and/or C libraries; " - "must be compiled on a Windows 32 platform") - - if not self.skip_build: - self.run_command('build') - - install = self.reinitialize_command('install', reinit_subcommands=1) - install.root = self.bdist_dir - install.skip_build = self.skip_build - install.warn_dir = 0 - install.plat_name = self.plat_name - - install_lib = self.reinitialize_command('install_lib') - # we do not want to include pyc or pyo files - install_lib.compile = 0 - install_lib.optimize = 0 - - if self.distribution.has_ext_modules(): - # If we are building an installer for a Python version other - # than the one we are currently running, then we need to ensure - # our build_lib reflects the other Python version rather than ours. - # Note that for target_version!=sys.version, we must have skipped the - # build step, so there is no issue with enforcing the build of this - # version. - target_version = self.target_version - if not target_version: - assert self.skip_build, "Should have already checked this" - target_version = '%d.%d' % sys.version_info[:2] - plat_specifier = ".%s-%s" % (self.plat_name, target_version) - build = self.get_finalized_command('build') - build.build_lib = os.path.join(build.build_base, - 'lib' + plat_specifier) - - # Use a custom scheme for the zip-file, because we have to decide - # at installation time which scheme to use. - for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'): - value = key.upper() - if key == 'headers': - value = value + '/Include/$dist_name' - setattr(install, - 'install_' + key, - value) - - log.info("installing to %s", self.bdist_dir) - install.ensure_finalized() - - # avoid warning of 'install_lib' about installing - # into a directory not in sys.path - sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB')) - - install.run() - - del sys.path[0] - - # And make an archive relative to the root of the - # pseudo-installation tree. - from tempfile import mktemp - archive_basename = mktemp() - fullname = self.distribution.get_fullname() - arcname = self.make_archive(archive_basename, "zip", - root_dir=self.bdist_dir) - # create an exe containing the zip-file - self.create_exe(arcname, fullname, self.bitmap) - if self.distribution.has_ext_modules(): - pyversion = get_python_version() - else: - pyversion = 'any' - self.distribution.dist_files.append(('bdist_wininst', pyversion, - self.get_installer_filename(fullname))) - # remove the zip-file again - log.debug("removing temporary file '%s'", arcname) - os.remove(arcname) - - if not self.keep_temp: - remove_tree(self.bdist_dir, dry_run=self.dry_run) - - def get_inidata(self): - # Return data describing the installation. - lines = [] - metadata = self.distribution.metadata - - # Write the [metadata] section. - lines.append("[metadata]") - - # 'info' will be displayed in the installer's dialog box, - # describing the items to be installed. - info = (metadata.long_description or '') + '\n' - - # Escape newline characters - def escape(s): - return s.replace("\n", "\\n") - - for name in ["author", "author_email", "description", "maintainer", - "maintainer_email", "name", "url", "version"]: - data = getattr(metadata, name, "") - if data: - info = info + ("\n %s: %s" % \ - (name.capitalize(), escape(data))) - lines.append("%s=%s" % (name, escape(data))) - - # The [setup] section contains entries controlling - # the installer runtime. - lines.append("\n[Setup]") - if self.install_script: - lines.append("install_script=%s" % self.install_script) - lines.append("info=%s" % escape(info)) - lines.append("target_compile=%d" % (not self.no_target_compile)) - lines.append("target_optimize=%d" % (not self.no_target_optimize)) - if self.target_version: - lines.append("target_version=%s" % self.target_version) - if self.user_access_control: - lines.append("user_access_control=%s" % self.user_access_control) - - title = self.title or self.distribution.get_fullname() - lines.append("title=%s" % escape(title)) - import time - import distutils - build_info = "Built %s with distutils-%s" % \ - (time.ctime(time.time()), distutils.__version__) - lines.append("build_info=%s" % build_info) - return "\n".join(lines) - - def create_exe(self, arcname, fullname, bitmap=None): - import struct - - self.mkpath(self.dist_dir) - - cfgdata = self.get_inidata() - - installer_name = self.get_installer_filename(fullname) - self.announce("creating %s" % installer_name) - - if bitmap: - bitmapdata = open(bitmap, "rb").read() - bitmaplen = len(bitmapdata) - else: - bitmaplen = 0 - - file = open(installer_name, "wb") - file.write(self.get_exe_bytes()) - if bitmap: - file.write(bitmapdata) - - # Convert cfgdata from unicode to ascii, mbcs encoded - if isinstance(cfgdata, str): - cfgdata = cfgdata.encode("mbcs") - - # Append the pre-install script - cfgdata = cfgdata + b"\0" - if self.pre_install_script: - # We need to normalize newlines, so we open in text mode and - # convert back to bytes. "latin-1" simply avoids any possible - # failures. - with open(self.pre_install_script, "r", - encoding="latin-1") as script: - script_data = script.read().encode("latin-1") - cfgdata = cfgdata + script_data + b"\n\0" - else: - # empty pre-install script - cfgdata = cfgdata + b"\0" - file.write(cfgdata) - - # The 'magic number' 0x1234567B is used to make sure that the - # binary layout of 'cfgdata' is what the wininst.exe binary - # expects. If the layout changes, increment that number, make - # the corresponding changes to the wininst.exe sources, and - # recompile them. - header = struct.pack("<iii", - 0x1234567B, # tag - len(cfgdata), # length - bitmaplen, # number of bytes in bitmap - ) - file.write(header) - file.write(open(arcname, "rb").read()) - - def get_installer_filename(self, fullname): - # Factored out to allow overriding in subclasses - if self.target_version: - # if we create an installer for a specific python version, - # it's better to include this in the name - installer_name = os.path.join(self.dist_dir, - "%s.%s-py%s.exe" % - (fullname, self.plat_name, self.target_version)) - else: - installer_name = os.path.join(self.dist_dir, - "%s.%s.exe" % (fullname, self.plat_name)) - return installer_name - - def get_exe_bytes(self): - # If a target-version other than the current version has been - # specified, then using the MSVC version from *this* build is no good. - # Without actually finding and executing the target version and parsing - # its sys.version, we just hard-code our knowledge of old versions. - # NOTE: Possible alternative is to allow "--target-version" to - # specify a Python executable rather than a simple version string. - # We can then execute this program to obtain any info we need, such - # as the real sys.version string for the build. - cur_version = get_python_version() - - # If the target version is *later* than us, then we assume they - # use what we use - # string compares seem wrong, but are what sysconfig.py itself uses - if self.target_version and self.target_version < cur_version: - if self.target_version < "2.4": - bv = '6.0' - elif self.target_version == "2.4": - bv = '7.1' - elif self.target_version == "2.5": - bv = '8.0' - elif self.target_version <= "3.2": - bv = '9.0' - elif self.target_version <= "3.4": - bv = '10.0' - else: - bv = '14.0' - else: - # for current version - use authoritative check. - try: - from msvcrt import CRT_ASSEMBLY_VERSION - except ImportError: - # cross-building, so assume the latest version - bv = '14.0' - else: - # as far as we know, CRT is binary compatible based on - # the first field, so assume 'x.0' until proven otherwise - major = CRT_ASSEMBLY_VERSION.partition('.')[0] - bv = major + '.0' - - - # wininst-x.y.exe is in the same directory as this file - directory = os.path.dirname(__file__) - # we must use a wininst-x.y.exe built with the same C compiler - # used for python. XXX What about mingw, borland, and so on? - - # if plat_name starts with "win" but is not "win32" - # we want to strip "win" and leave the rest (e.g. -amd64) - # for all other cases, we don't want any suffix - if self.plat_name != 'win32' and self.plat_name[:3] == 'win': - sfix = self.plat_name[3:] - else: - sfix = '' - - filename = os.path.join(directory, "wininst-%s%s.exe" % (bv, sfix)) - try: - f = open(filename, "rb") - except IOError as e: - raise DistutilsFileError(str(e) + ', %s not included in the Debian packages.' % filename) - try: - return f.read() - finally: - f.close() diff --git a/Lib/distutils/command/build.py b/Lib/distutils/command/build.py deleted file mode 100644 index c6f52e61e1b..00000000000 --- a/Lib/distutils/command/build.py +++ /dev/null @@ -1,157 +0,0 @@ -"""distutils.command.build - -Implements the Distutils 'build' command.""" - -import sys, os -from distutils.core import Command -from distutils.errors import DistutilsOptionError -from distutils.util import get_platform - - -def show_compilers(): - from distutils.ccompiler import show_compilers - show_compilers() - - -class build(Command): - - description = "build everything needed to install" - - user_options = [ - ('build-base=', 'b', - "base directory for build library"), - ('build-purelib=', None, - "build directory for platform-neutral distributions"), - ('build-platlib=', None, - "build directory for platform-specific distributions"), - ('build-lib=', None, - "build directory for all distribution (defaults to either " + - "build-purelib or build-platlib"), - ('build-scripts=', None, - "build directory for scripts"), - ('build-temp=', 't', - "temporary build directory"), - ('plat-name=', 'p', - "platform name to build for, if supported " - "(default: %s)" % get_platform()), - ('compiler=', 'c', - "specify the compiler type"), - ('parallel=', 'j', - "number of parallel build jobs"), - ('debug', 'g', - "compile extensions and libraries with debugging information"), - ('force', 'f', - "forcibly build everything (ignore file timestamps)"), - ('executable=', 'e', - "specify final destination interpreter path (build.py)"), - ] - - boolean_options = ['debug', 'force'] - - help_options = [ - ('help-compiler', None, - "list available compilers", show_compilers), - ] - - def initialize_options(self): - self.build_base = 'build' - # these are decided only after 'build_base' has its final value - # (unless overridden by the user or client) - self.build_purelib = None - self.build_platlib = None - self.build_lib = None - self.build_temp = None - self.build_scripts = None - self.compiler = None - self.plat_name = None - self.debug = None - self.force = 0 - self.executable = None - self.parallel = None - - def finalize_options(self): - if self.plat_name is None: - self.plat_name = get_platform() - else: - # plat-name only supported for windows (other platforms are - # supported via ./configure flags, if at all). Avoid misleading - # other platforms. - if os.name != 'nt': - raise DistutilsOptionError( - "--plat-name only supported on Windows (try " - "using './configure --help' on your platform)") - - plat_specifier = ".%s-%d.%d" % (self.plat_name, *sys.version_info[:2]) - - # Make it so Python 2.x and Python 2.x with --with-pydebug don't - # share the same build directories. Doing so confuses the build - # process for C modules - if hasattr(sys, 'gettotalrefcount'): - plat_specifier += '-pydebug' - - # 'build_purelib' and 'build_platlib' just default to 'lib' and - # 'lib.<plat>' under the base build directory. We only use one of - # them for a given distribution, though -- - if self.build_purelib is None: - self.build_purelib = os.path.join(self.build_base, 'lib') - if self.build_platlib is None: - self.build_platlib = os.path.join(self.build_base, - 'lib' + plat_specifier) - - # 'build_lib' is the actual directory that we will use for this - # particular module distribution -- if user didn't supply it, pick - # one of 'build_purelib' or 'build_platlib'. - if self.build_lib is None: - if self.distribution.ext_modules: - self.build_lib = self.build_platlib - else: - self.build_lib = self.build_purelib - - # 'build_temp' -- temporary directory for compiler turds, - # "build/temp.<plat>" - if self.build_temp is None: - self.build_temp = os.path.join(self.build_base, - 'temp' + plat_specifier) - if self.build_scripts is None: - self.build_scripts = os.path.join(self.build_base, - 'scripts-%d.%d' % sys.version_info[:2]) - - if self.executable is None: - self.executable = os.path.normpath(sys.executable) - - if isinstance(self.parallel, str): - try: - self.parallel = int(self.parallel) - except ValueError: - raise DistutilsOptionError("parallel should be an integer") - - def run(self): - # Run all relevant sub-commands. This will be some subset of: - # - build_py - pure Python modules - # - build_clib - standalone C libraries - # - build_ext - Python extensions - # - build_scripts - (Python) scripts - for cmd_name in self.get_sub_commands(): - self.run_command(cmd_name) - - - # -- Predicates for the sub-command list --------------------------- - - def has_pure_modules(self): - return self.distribution.has_pure_modules() - - def has_c_libraries(self): - return self.distribution.has_c_libraries() - - def has_ext_modules(self): - return self.distribution.has_ext_modules() - - def has_scripts(self): - return self.distribution.has_scripts() - - - sub_commands = [('build_py', has_pure_modules), - ('build_clib', has_c_libraries), - ('build_ext', has_ext_modules), - ('build_scripts', has_scripts), - ] diff --git a/Lib/distutils/command/build_clib.py b/Lib/distutils/command/build_clib.py deleted file mode 100644 index 3e20ef23cd8..00000000000 --- a/Lib/distutils/command/build_clib.py +++ /dev/null @@ -1,209 +0,0 @@ -"""distutils.command.build_clib - -Implements the Distutils 'build_clib' command, to build a C/C++ library -that is included in the module distribution and needed by an extension -module.""" - - -# XXX this module has *lots* of code ripped-off quite transparently from -# build_ext.py -- not surprisingly really, as the work required to build -# a static library from a collection of C source files is not really all -# that different from what's required to build a shared object file from -# a collection of C source files. Nevertheless, I haven't done the -# necessary refactoring to account for the overlap in code between the -# two modules, mainly because a number of subtle details changed in the -# cut 'n paste. Sigh. - -import os -from distutils.core import Command -from distutils.errors import * -from distutils.sysconfig import customize_compiler -from distutils import log - -def show_compilers(): - from distutils.ccompiler import show_compilers - show_compilers() - - -class build_clib(Command): - - description = "build C/C++ libraries used by Python extensions" - - user_options = [ - ('build-clib=', 'b', - "directory to build C/C++ libraries to"), - ('build-temp=', 't', - "directory to put temporary build by-products"), - ('debug', 'g', - "compile with debugging information"), - ('force', 'f', - "forcibly build everything (ignore file timestamps)"), - ('compiler=', 'c', - "specify the compiler type"), - ] - - boolean_options = ['debug', 'force'] - - help_options = [ - ('help-compiler', None, - "list available compilers", show_compilers), - ] - - def initialize_options(self): - self.build_clib = None - self.build_temp = None - - # List of libraries to build - self.libraries = None - - # Compilation options for all libraries - self.include_dirs = None - self.define = None - self.undef = None - self.debug = None - self.force = 0 - self.compiler = None - - - def finalize_options(self): - # This might be confusing: both build-clib and build-temp default - # to build-temp as defined by the "build" command. This is because - # I think that C libraries are really just temporary build - # by-products, at least from the point of view of building Python - # extensions -- but I want to keep my options open. - self.set_undefined_options('build', - ('build_temp', 'build_clib'), - ('build_temp', 'build_temp'), - ('compiler', 'compiler'), - ('debug', 'debug'), - ('force', 'force')) - - self.libraries = self.distribution.libraries - if self.libraries: - self.check_library_list(self.libraries) - - if self.include_dirs is None: - self.include_dirs = self.distribution.include_dirs or [] - if isinstance(self.include_dirs, str): - self.include_dirs = self.include_dirs.split(os.pathsep) - - # XXX same as for build_ext -- what about 'self.define' and - # 'self.undef' ? - - - def run(self): - if not self.libraries: - return - - # Yech -- this is cut 'n pasted from build_ext.py! - from distutils.ccompiler import new_compiler - self.compiler = new_compiler(compiler=self.compiler, - dry_run=self.dry_run, - force=self.force) - customize_compiler(self.compiler) - - if self.include_dirs is not None: - self.compiler.set_include_dirs(self.include_dirs) - if self.define is not None: - # 'define' option is a list of (name,value) tuples - for (name,value) in self.define: - self.compiler.define_macro(name, value) - if self.undef is not None: - for macro in self.undef: - self.compiler.undefine_macro(macro) - - self.build_libraries(self.libraries) - - - def check_library_list(self, libraries): - """Ensure that the list of libraries is valid. - - `library` is presumably provided as a command option 'libraries'. - This method checks that it is a list of 2-tuples, where the tuples - are (library_name, build_info_dict). - - Raise DistutilsSetupError if the structure is invalid anywhere; - just returns otherwise. - """ - if not isinstance(libraries, list): - raise DistutilsSetupError( - "'libraries' option must be a list of tuples") - - for lib in libraries: - if not isinstance(lib, tuple) and len(lib) != 2: - raise DistutilsSetupError( - "each element of 'libraries' must a 2-tuple") - - name, build_info = lib - - if not isinstance(name, str): - raise DistutilsSetupError( - "first element of each tuple in 'libraries' " - "must be a string (the library name)") - - if '/' in name or (os.sep != '/' and os.sep in name): - raise DistutilsSetupError("bad library name '%s': " - "may not contain directory separators" % lib[0]) - - if not isinstance(build_info, dict): - raise DistutilsSetupError( - "second element of each tuple in 'libraries' " - "must be a dictionary (build info)") - - - def get_library_names(self): - # Assume the library list is valid -- 'check_library_list()' is - # called from 'finalize_options()', so it should be! - if not self.libraries: - return None - - lib_names = [] - for (lib_name, build_info) in self.libraries: - lib_names.append(lib_name) - return lib_names - - - def get_source_files(self): - self.check_library_list(self.libraries) - filenames = [] - for (lib_name, build_info) in self.libraries: - sources = build_info.get('sources') - if sources is None or not isinstance(sources, (list, tuple)): - raise DistutilsSetupError( - "in 'libraries' option (library '%s'), " - "'sources' must be present and must be " - "a list of source filenames" % lib_name) - - filenames.extend(sources) - return filenames - - - def build_libraries(self, libraries): - for (lib_name, build_info) in libraries: - sources = build_info.get('sources') - if sources is None or not isinstance(sources, (list, tuple)): - raise DistutilsSetupError( - "in 'libraries' option (library '%s'), " - "'sources' must be present and must be " - "a list of source filenames" % lib_name) - sources = list(sources) - - log.info("building '%s' library", lib_name) - - # First, compile the source code to object files in the library - # directory. (This should probably change to putting object - # files in a temporary build directory.) - macros = build_info.get('macros') - include_dirs = build_info.get('include_dirs') - objects = self.compiler.compile(sources, - output_dir=self.build_temp, - macros=macros, - include_dirs=include_dirs, - debug=self.debug) - - # Now "link" the object files together into a static library. - # (On Unix at least, this isn't really linking -- it just - # builds an archive. Whatever.) - self.compiler.create_static_lib(objects, lib_name, - output_dir=self.build_clib, - debug=self.debug) diff --git a/Lib/distutils/command/build_ext.py b/Lib/distutils/command/build_ext.py deleted file mode 100644 index acf2fc5484a..00000000000 --- a/Lib/distutils/command/build_ext.py +++ /dev/null @@ -1,755 +0,0 @@ -"""distutils.command.build_ext - -Implements the Distutils 'build_ext' command, for building extension -modules (currently limited to C extensions, should accommodate C++ -extensions ASAP).""" - -import contextlib -import os -import re -import sys -from distutils.core import Command -from distutils.errors import * -from distutils.sysconfig import customize_compiler, get_python_version -from distutils.sysconfig import get_config_h_filename -from distutils.dep_util import newer_group -from distutils.extension import Extension -from distutils.util import get_platform -from distutils import log - -from site import USER_BASE - -# An extension name is just a dot-separated list of Python NAMEs (ie. -# the same as a fully-qualified module name). -extension_name_re = re.compile \ - (r'^[a-zA-Z_][a-zA-Z_0-9]*(\.[a-zA-Z_][a-zA-Z_0-9]*)*$') - - -def show_compilers (): - from distutils.ccompiler import show_compilers - show_compilers() - - -class build_ext(Command): - - description = "build C/C++ extensions (compile/link to build directory)" - - # XXX thoughts on how to deal with complex command-line options like - # these, i.e. how to make it so fancy_getopt can suck them off the - # command line and make it look like setup.py defined the appropriate - # lists of tuples of what-have-you. - # - each command needs a callback to process its command-line options - # - Command.__init__() needs access to its share of the whole - # command line (must ultimately come from - # Distribution.parse_command_line()) - # - it then calls the current command class' option-parsing - # callback to deal with weird options like -D, which have to - # parse the option text and churn out some custom data - # structure - # - that data structure (in this case, a list of 2-tuples) - # will then be present in the command object by the time - # we get to finalize_options() (i.e. the constructor - # takes care of both command-line and client options - # in between initialize_options() and finalize_options()) - - sep_by = " (separated by '%s')" % os.pathsep - user_options = [ - ('build-lib=', 'b', - "directory for compiled extension modules"), - ('build-temp=', 't', - "directory for temporary files (build by-products)"), - ('plat-name=', 'p', - "platform name to cross-compile for, if supported " - "(default: %s)" % get_platform()), - ('inplace', 'i', - "ignore build-lib and put compiled extensions into the source " + - "directory alongside your pure Python modules"), - ('include-dirs=', 'I', - "list of directories to search for header files" + sep_by), - ('define=', 'D', - "C preprocessor macros to define"), - ('undef=', 'U', - "C preprocessor macros to undefine"), - ('libraries=', 'l', - "external C libraries to link with"), - ('library-dirs=', 'L', - "directories to search for external C libraries" + sep_by), - ('rpath=', 'R', - "directories to search for shared C libraries at runtime"), - ('link-objects=', 'O', - "extra explicit link objects to include in the link"), - ('debug', 'g', - "compile/link with debugging information"), - ('force', 'f', - "forcibly build everything (ignore file timestamps)"), - ('compiler=', 'c', - "specify the compiler type"), - ('parallel=', 'j', - "number of parallel build jobs"), - ('swig-cpp', None, - "make SWIG create C++ files (default is C)"), - ('swig-opts=', None, - "list of SWIG command line options"), - ('swig=', None, - "path to the SWIG executable"), - ('user', None, - "add user include, library and rpath") - ] - - boolean_options = ['inplace', 'debug', 'force', 'swig-cpp', 'user'] - - help_options = [ - ('help-compiler', None, - "list available compilers", show_compilers), - ] - - def initialize_options(self): - self.extensions = None - self.build_lib = None - self.plat_name = None - self.build_temp = None - self.inplace = 0 - self.package = None - - self.include_dirs = None - self.define = None - self.undef = None - self.libraries = None - self.library_dirs = None - self.rpath = None - self.link_objects = None - self.debug = None - self.force = None - self.compiler = None - self.swig = None - self.swig_cpp = None - self.swig_opts = None - self.user = None - self.parallel = None - - def finalize_options(self): - from distutils import sysconfig - - self.set_undefined_options('build', - ('build_lib', 'build_lib'), - ('build_temp', 'build_temp'), - ('compiler', 'compiler'), - ('debug', 'debug'), - ('force', 'force'), - ('parallel', 'parallel'), - ('plat_name', 'plat_name'), - ) - - if self.package is None: - self.package = self.distribution.ext_package - - self.extensions = self.distribution.ext_modules - - # Make sure Python's include directories (for Python.h, pyconfig.h, - # etc.) are in the include search path. - py_include = sysconfig.get_python_inc() - plat_py_include = sysconfig.get_python_inc(plat_specific=1) - if self.include_dirs is None: - self.include_dirs = self.distribution.include_dirs or [] - if isinstance(self.include_dirs, str): - self.include_dirs = self.include_dirs.split(os.pathsep) - - # If in a virtualenv, add its include directory - # Issue 16116 - if sys.exec_prefix != sys.base_exec_prefix: - self.include_dirs.append(os.path.join(sys.exec_prefix, 'include')) - - # Put the Python "system" include dir at the end, so that - # any local include dirs take precedence. - self.include_dirs.append(py_include) - if plat_py_include != py_include: - self.include_dirs.append(plat_py_include) - - self.ensure_string_list('libraries') - self.ensure_string_list('link_objects') - - # Life is easier if we're not forever checking for None, so - # simplify these options to empty lists if unset - if self.libraries is None: - self.libraries = [] - if self.library_dirs is None: - self.library_dirs = [] - elif isinstance(self.library_dirs, str): - self.library_dirs = self.library_dirs.split(os.pathsep) - - if self.rpath is None: - self.rpath = [] - elif isinstance(self.rpath, str): - self.rpath = self.rpath.split(os.pathsep) - - # for extensions under windows use different directories - # for Release and Debug builds. - # also Python's library directory must be appended to library_dirs - if os.name == 'nt': - # the 'libs' directory is for binary installs - we assume that - # must be the *native* platform. But we don't really support - # cross-compiling via a binary install anyway, so we let it go. - self.library_dirs.append(os.path.join(sys.exec_prefix, 'libs')) - if sys.base_exec_prefix != sys.prefix: # Issue 16116 - self.library_dirs.append(os.path.join(sys.base_exec_prefix, 'libs')) - if self.debug: - self.build_temp = os.path.join(self.build_temp, "Debug") - else: - self.build_temp = os.path.join(self.build_temp, "Release") - - # Append the source distribution include and library directories, - # this allows distutils on windows to work in the source tree - self.include_dirs.append(os.path.dirname(get_config_h_filename())) - _sys_home = getattr(sys, '_home', None) - if _sys_home: - self.library_dirs.append(_sys_home) - - # Use the .lib files for the correct architecture - if self.plat_name == 'win32': - suffix = 'win32' - else: - # win-amd64 or win-ia64 - suffix = self.plat_name[4:] - new_lib = os.path.join(sys.exec_prefix, 'PCbuild') - if suffix: - new_lib = os.path.join(new_lib, suffix) - self.library_dirs.append(new_lib) - - # for extensions under Cygwin and AtheOS Python's library directory must be - # appended to library_dirs - if sys.platform[:6] == 'cygwin' or sys.platform[:6] == 'atheos': - if sys.executable.startswith(os.path.join(sys.exec_prefix, "bin")): - # building third party extensions - self.library_dirs.append(os.path.join(sys.prefix, "lib", - "python" + get_python_version(), - "config")) - else: - # building python standard extensions - self.library_dirs.append('.') - - # For building extensions with a shared Python library, - # Python's library directory must be appended to library_dirs - # See Issues: #1600860, #4366 - if False and (sysconfig.get_config_var('Py_ENABLE_SHARED')): - if not sysconfig.python_build: - # building third party extensions - self.library_dirs.append(sysconfig.get_config_var('LIBDIR')) - else: - # building python standard extensions - self.library_dirs.append('.') - - # The argument parsing will result in self.define being a string, but - # it has to be a list of 2-tuples. All the preprocessor symbols - # specified by the 'define' option will be set to '1'. Multiple - # symbols can be separated with commas. - - if self.define: - defines = self.define.split(',') - self.define = [(symbol, '1') for symbol in defines] - - # The option for macros to undefine is also a string from the - # option parsing, but has to be a list. Multiple symbols can also - # be separated with commas here. - if self.undef: - self.undef = self.undef.split(',') - - if self.swig_opts is None: - self.swig_opts = [] - else: - self.swig_opts = self.swig_opts.split(' ') - - # Finally add the user include and library directories if requested - if self.user: - user_include = os.path.join(USER_BASE, "include") - user_lib = os.path.join(USER_BASE, "lib") - if os.path.isdir(user_include): - self.include_dirs.append(user_include) - if os.path.isdir(user_lib): - self.library_dirs.append(user_lib) - self.rpath.append(user_lib) - - if isinstance(self.parallel, str): - try: - self.parallel = int(self.parallel) - except ValueError: - raise DistutilsOptionError("parallel should be an integer") - - def run(self): - from distutils.ccompiler import new_compiler - - # 'self.extensions', as supplied by setup.py, is a list of - # Extension instances. See the documentation for Extension (in - # distutils.extension) for details. - # - # For backwards compatibility with Distutils 0.8.2 and earlier, we - # also allow the 'extensions' list to be a list of tuples: - # (ext_name, build_info) - # where build_info is a dictionary containing everything that - # Extension instances do except the name, with a few things being - # differently named. We convert these 2-tuples to Extension - # instances as needed. - - if not self.extensions: - return - - # If we were asked to build any C/C++ libraries, make sure that the - # directory where we put them is in the library search path for - # linking extensions. - if self.distribution.has_c_libraries(): - build_clib = self.get_finalized_command('build_clib') - self.libraries.extend(build_clib.get_library_names() or []) - self.library_dirs.append(build_clib.build_clib) - - # Setup the CCompiler object that we'll use to do all the - # compiling and linking - self.compiler = new_compiler(compiler=self.compiler, - verbose=self.verbose, - dry_run=self.dry_run, - force=self.force) - customize_compiler(self.compiler) - # If we are cross-compiling, init the compiler now (if we are not - # cross-compiling, init would not hurt, but people may rely on - # late initialization of compiler even if they shouldn't...) - if os.name == 'nt' and self.plat_name != get_platform(): - self.compiler.initialize(self.plat_name) - - # And make sure that any compile/link-related options (which might - # come from the command-line or from the setup script) are set in - # that CCompiler object -- that way, they automatically apply to - # all compiling and linking done here. - if self.include_dirs is not None: - self.compiler.set_include_dirs(self.include_dirs) - if self.define is not None: - # 'define' option is a list of (name,value) tuples - for (name, value) in self.define: - self.compiler.define_macro(name, value) - if self.undef is not None: - for macro in self.undef: - self.compiler.undefine_macro(macro) - if self.libraries is not None: - self.compiler.set_libraries(self.libraries) - if self.library_dirs is not None: - self.compiler.set_library_dirs(self.library_dirs) - if self.rpath is not None: - self.compiler.set_runtime_library_dirs(self.rpath) - if self.link_objects is not None: - self.compiler.set_link_objects(self.link_objects) - - # Now actually compile and link everything. - self.build_extensions() - - def check_extensions_list(self, extensions): - """Ensure that the list of extensions (presumably provided as a - command option 'extensions') is valid, i.e. it is a list of - Extension objects. We also support the old-style list of 2-tuples, - where the tuples are (ext_name, build_info), which are converted to - Extension instances here. - - Raise DistutilsSetupError if the structure is invalid anywhere; - just returns otherwise. - """ - if not isinstance(extensions, list): - raise DistutilsSetupError( - "'ext_modules' option must be a list of Extension instances") - - for i, ext in enumerate(extensions): - if isinstance(ext, Extension): - continue # OK! (assume type-checking done - # by Extension constructor) - - if not isinstance(ext, tuple) or len(ext) != 2: - raise DistutilsSetupError( - "each element of 'ext_modules' option must be an " - "Extension instance or 2-tuple") - - ext_name, build_info = ext - - log.warn("old-style (ext_name, build_info) tuple found in " - "ext_modules for extension '%s' " - "-- please convert to Extension instance", ext_name) - - if not (isinstance(ext_name, str) and - extension_name_re.match(ext_name)): - raise DistutilsSetupError( - "first element of each tuple in 'ext_modules' " - "must be the extension name (a string)") - - if not isinstance(build_info, dict): - raise DistutilsSetupError( - "second element of each tuple in 'ext_modules' " - "must be a dictionary (build info)") - - # OK, the (ext_name, build_info) dict is type-safe: convert it - # to an Extension instance. - ext = Extension(ext_name, build_info['sources']) - - # Easy stuff: one-to-one mapping from dict elements to - # instance attributes. - for key in ('include_dirs', 'library_dirs', 'libraries', - 'extra_objects', 'extra_compile_args', - 'extra_link_args'): - val = build_info.get(key) - if val is not None: - setattr(ext, key, val) - - # Medium-easy stuff: same syntax/semantics, different names. - ext.runtime_library_dirs = build_info.get('rpath') - if 'def_file' in build_info: - log.warn("'def_file' element of build info dict " - "no longer supported") - - # Non-trivial stuff: 'macros' split into 'define_macros' - # and 'undef_macros'. - macros = build_info.get('macros') - if macros: - ext.define_macros = [] - ext.undef_macros = [] - for macro in macros: - if not (isinstance(macro, tuple) and len(macro) in (1, 2)): - raise DistutilsSetupError( - "'macros' element of build info dict " - "must be 1- or 2-tuple") - if len(macro) == 1: - ext.undef_macros.append(macro[0]) - elif len(macro) == 2: - ext.define_macros.append(macro) - - extensions[i] = ext - - def get_source_files(self): - self.check_extensions_list(self.extensions) - filenames = [] - - # Wouldn't it be neat if we knew the names of header files too... - for ext in self.extensions: - filenames.extend(ext.sources) - return filenames - - def get_outputs(self): - # Sanity check the 'extensions' list -- can't assume this is being - # done in the same run as a 'build_extensions()' call (in fact, we - # can probably assume that it *isn't*!). - self.check_extensions_list(self.extensions) - - # And build the list of output (built) filenames. Note that this - # ignores the 'inplace' flag, and assumes everything goes in the - # "build" tree. - outputs = [] - for ext in self.extensions: - outputs.append(self.get_ext_fullpath(ext.name)) - return outputs - - def build_extensions(self): - # First, sanity-check the 'extensions' list - self.check_extensions_list(self.extensions) - if self.parallel: - self._build_extensions_parallel() - else: - self._build_extensions_serial() - - def _build_extensions_parallel(self): - workers = self.parallel - if self.parallel is True: - workers = os.cpu_count() # may return None - try: - from concurrent.futures import ThreadPoolExecutor - except ImportError: - workers = None - - if workers is None: - self._build_extensions_serial() - return - - with ThreadPoolExecutor(max_workers=workers) as executor: - futures = [executor.submit(self.build_extension, ext) - for ext in self.extensions] - for ext, fut in zip(self.extensions, futures): - with self._filter_build_errors(ext): - fut.result() - - def _build_extensions_serial(self): - for ext in self.extensions: - with self._filter_build_errors(ext): - self.build_extension(ext) - - @contextlib.contextmanager - def _filter_build_errors(self, ext): - try: - yield - except (CCompilerError, DistutilsError, CompileError) as e: - if not ext.optional: - raise - self.warn('building extension "%s" failed: %s' % - (ext.name, e)) - - def build_extension(self, ext): - sources = ext.sources - if sources is None or not isinstance(sources, (list, tuple)): - raise DistutilsSetupError( - "in 'ext_modules' option (extension '%s'), " - "'sources' must be present and must be " - "a list of source filenames" % ext.name) - sources = list(sources) - - ext_path = self.get_ext_fullpath(ext.name) - depends = sources + ext.depends - if not (self.force or newer_group(depends, ext_path, 'newer')): - log.debug("skipping '%s' extension (up-to-date)", ext.name) - return - else: - log.info("building '%s' extension", ext.name) - - # First, scan the sources for SWIG definition files (.i), run - # SWIG on 'em to create .c files, and modify the sources list - # accordingly. - sources = self.swig_sources(sources, ext) - - # Next, compile the source code to object files. - - # XXX not honouring 'define_macros' or 'undef_macros' -- the - # CCompiler API needs to change to accommodate this, and I - # want to do one thing at a time! - - # Two possible sources for extra compiler arguments: - # - 'extra_compile_args' in Extension object - # - CFLAGS environment variable (not particularly - # elegant, but people seem to expect it and I - # guess it's useful) - # The environment variable should take precedence, and - # any sensible compiler will give precedence to later - # command line args. Hence we combine them in order: - extra_args = ext.extra_compile_args or [] - - macros = ext.define_macros[:] - for undef in ext.undef_macros: - macros.append((undef,)) - - objects = self.compiler.compile(sources, - output_dir=self.build_temp, - macros=macros, - include_dirs=ext.include_dirs, - debug=self.debug, - extra_postargs=extra_args, - depends=ext.depends) - - # XXX outdated variable, kept here in case third-part code - # needs it. - self._built_objects = objects[:] - - # Now link the object files together into a "shared object" -- - # of course, first we have to figure out all the other things - # that go into the mix. - if ext.extra_objects: - objects.extend(ext.extra_objects) - extra_args = ext.extra_link_args or [] - - # Detect target language, if not provided - language = ext.language or self.compiler.detect_language(sources) - - self.compiler.link_shared_object( - objects, ext_path, - libraries=self.get_libraries(ext), - library_dirs=ext.library_dirs, - runtime_library_dirs=ext.runtime_library_dirs, - extra_postargs=extra_args, - export_symbols=self.get_export_symbols(ext), - debug=self.debug, - build_temp=self.build_temp, - target_lang=language) - - def swig_sources(self, sources, extension): - """Walk the list of source files in 'sources', looking for SWIG - interface (.i) files. Run SWIG on all that are found, and - return a modified 'sources' list with SWIG source files replaced - by the generated C (or C++) files. - """ - new_sources = [] - swig_sources = [] - swig_targets = {} - - # XXX this drops generated C/C++ files into the source tree, which - # is fine for developers who want to distribute the generated - # source -- but there should be an option to put SWIG output in - # the temp dir. - - if self.swig_cpp: - log.warn("--swig-cpp is deprecated - use --swig-opts=-c++") - - if self.swig_cpp or ('-c++' in self.swig_opts) or \ - ('-c++' in extension.swig_opts): - target_ext = '.cpp' - else: - target_ext = '.c' - - for source in sources: - (base, ext) = os.path.splitext(source) - if ext == ".i": # SWIG interface file - new_sources.append(base + '_wrap' + target_ext) - swig_sources.append(source) - swig_targets[source] = new_sources[-1] - else: - new_sources.append(source) - - if not swig_sources: - return new_sources - - swig = self.swig or self.find_swig() - swig_cmd = [swig, "-python"] - swig_cmd.extend(self.swig_opts) - if self.swig_cpp: - swig_cmd.append("-c++") - - # Do not override commandline arguments - if not self.swig_opts: - for o in extension.swig_opts: - swig_cmd.append(o) - - for source in swig_sources: - target = swig_targets[source] - log.info("swigging %s to %s", source, target) - self.spawn(swig_cmd + ["-o", target, source]) - - return new_sources - - def find_swig(self): - """Return the name of the SWIG executable. On Unix, this is - just "swig" -- it should be in the PATH. Tries a bit harder on - Windows. - """ - if os.name == "posix": - return "swig" - elif os.name == "nt": - # Look for SWIG in its standard installation directory on - # Windows (or so I presume!). If we find it there, great; - # if not, act like Unix and assume it's in the PATH. - for vers in ("1.3", "1.2", "1.1"): - fn = os.path.join("c:\\swig%s" % vers, "swig.exe") - if os.path.isfile(fn): - return fn - else: - return "swig.exe" - else: - raise DistutilsPlatformError( - "I don't know how to find (much less run) SWIG " - "on platform '%s'" % os.name) - - # -- Name generators ----------------------------------------------- - # (extension names, filenames, whatever) - def get_ext_fullpath(self, ext_name): - """Returns the path of the filename for a given extension. - - The file is located in `build_lib` or directly in the package - (inplace option). - """ - fullname = self.get_ext_fullname(ext_name) - modpath = fullname.split('.') - filename = self.get_ext_filename(modpath[-1]) - - if not self.inplace: - # no further work needed - # returning : - # build_dir/package/path/filename - filename = os.path.join(*modpath[:-1]+[filename]) - return os.path.join(self.build_lib, filename) - - # the inplace option requires to find the package directory - # using the build_py command for that - package = '.'.join(modpath[0:-1]) - build_py = self.get_finalized_command('build_py') - package_dir = os.path.abspath(build_py.get_package_dir(package)) - - # returning - # package_dir/filename - return os.path.join(package_dir, filename) - - def get_ext_fullname(self, ext_name): - """Returns the fullname of a given extension name. - - Adds the `package.` prefix""" - if self.package is None: - return ext_name - else: - return self.package + '.' + ext_name - - def get_ext_filename(self, ext_name): - r"""Convert the name of an extension (eg. "foo.bar") into the name - of the file from which it will be loaded (eg. "foo/bar.so", or - "foo\bar.pyd"). - """ - from distutils.sysconfig import get_config_var - ext_path = ext_name.split('.') - ext_suffix = get_config_var('EXT_SUFFIX') - return os.path.join(*ext_path) + ext_suffix - - def get_export_symbols(self, ext): - """Return the list of symbols that a shared extension has to - export. This either uses 'ext.export_symbols' or, if it's not - provided, "PyInit_" + module_name. Only relevant on Windows, where - the .pyd file (DLL) must export the module "PyInit_" function. - """ - initfunc_name = "PyInit_" + ext.name.split('.')[-1] - if initfunc_name not in ext.export_symbols: - ext.export_symbols.append(initfunc_name) - return ext.export_symbols - - def get_libraries(self, ext): - """Return the list of libraries to link against when building a - shared extension. On most platforms, this is just 'ext.libraries'; - on Windows, we add the Python library (eg. python20.dll). - """ - # The python library is always needed on Windows. For MSVC, this - # is redundant, since the library is mentioned in a pragma in - # pyconfig.h that MSVC groks. The other Windows compilers all seem - # to need it mentioned explicitly, though, so that's what we do. - # Append '_d' to the python import library on debug builds. - if sys.platform == "win32": - from distutils._msvccompiler import MSVCCompiler - if not isinstance(self.compiler, MSVCCompiler): - template = "python%d%d" - if self.debug: - template = template + '_d' - pythonlib = (template % - (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) - # don't extend ext.libraries, it may be shared with other - # extensions, it is a reference to the original list - return ext.libraries + [pythonlib] - else: - return ext.libraries - elif sys.platform[:6] == "cygwin": - template = "python%d.%d" - pythonlib = (template % - (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) - # don't extend ext.libraries, it may be shared with other - # extensions, it is a reference to the original list - return ext.libraries + [pythonlib] - elif sys.platform[:6] == "atheos": - from distutils import sysconfig - - template = "python%d.%d" - pythonlib = (template % - (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) - # Get SHLIBS from Makefile - extra = [] - for lib in sysconfig.get_config_var('SHLIBS').split(): - if lib.startswith('-l'): - extra.append(lib[2:]) - else: - extra.append(lib) - # don't extend ext.libraries, it may be shared with other - # extensions, it is a reference to the original list - return ext.libraries + [pythonlib, "m"] + extra - elif sys.platform == 'darwin': - # Don't use the default code below - return ext.libraries - elif sys.platform[:3] == 'aix': - # Don't use the default code below - return ext.libraries - else: - from distutils import sysconfig - if False and sysconfig.get_config_var('Py_ENABLE_SHARED'): - pythonlib = 'python{}.{}{}'.format( - sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff, - sysconfig.get_config_var('ABIFLAGS')) - return ext.libraries + [pythonlib] - else: - return ext.libraries diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py deleted file mode 100644 index cf0ca57c320..00000000000 --- a/Lib/distutils/command/build_py.py +++ /dev/null @@ -1,416 +0,0 @@ -"""distutils.command.build_py - -Implements the Distutils 'build_py' command.""" - -import os -import importlib.util -import sys -from glob import glob - -from distutils.core import Command -from distutils.errors import * -from distutils.util import convert_path, Mixin2to3 -from distutils import log - -class build_py (Command): - - description = "\"build\" pure Python modules (copy to build directory)" - - user_options = [ - ('build-lib=', 'd', "directory to \"build\" (copy) to"), - ('compile', 'c', "compile .py to .pyc"), - ('no-compile', None, "don't compile .py files [default]"), - ('optimize=', 'O', - "also compile with optimization: -O1 for \"python -O\", " - "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), - ('force', 'f', "forcibly build everything (ignore file timestamps)"), - ] - - boolean_options = ['compile', 'force'] - negative_opt = {'no-compile' : 'compile'} - - def initialize_options(self): - self.build_lib = None - self.py_modules = None - self.package = None - self.package_data = None - self.package_dir = None - self.compile = 0 - self.optimize = 0 - self.force = None - - def finalize_options(self): - self.set_undefined_options('build', - ('build_lib', 'build_lib'), - ('force', 'force')) - - # Get the distribution options that are aliases for build_py - # options -- list of packages and list of modules. - self.packages = self.distribution.packages - self.py_modules = self.distribution.py_modules - self.package_data = self.distribution.package_data - self.package_dir = {} - if self.distribution.package_dir: - for name, path in self.distribution.package_dir.items(): - self.package_dir[name] = convert_path(path) - self.data_files = self.get_data_files() - - # Ick, copied straight from install_lib.py (fancy_getopt needs a - # type system! Hell, *everything* needs a type system!!!) - if not isinstance(self.optimize, int): - try: - self.optimize = int(self.optimize) - assert 0 <= self.optimize <= 2 - except (ValueError, AssertionError): - raise DistutilsOptionError("optimize must be 0, 1, or 2") - - def run(self): - # XXX copy_file by default preserves atime and mtime. IMHO this is - # the right thing to do, but perhaps it should be an option -- in - # particular, a site administrator might want installed files to - # reflect the time of installation rather than the last - # modification time before the installed release. - - # XXX copy_file by default preserves mode, which appears to be the - # wrong thing to do: if a file is read-only in the working - # directory, we want it to be installed read/write so that the next - # installation of the same module distribution can overwrite it - # without problems. (This might be a Unix-specific issue.) Thus - # we turn off 'preserve_mode' when copying to the build directory, - # since the build directory is supposed to be exactly what the - # installation will look like (ie. we preserve mode when - # installing). - - # Two options control which modules will be installed: 'packages' - # and 'py_modules'. The former lets us work with whole packages, not - # specifying individual modules at all; the latter is for - # specifying modules one-at-a-time. - - if self.py_modules: - self.build_modules() - if self.packages: - self.build_packages() - self.build_package_data() - - self.byte_compile(self.get_outputs(include_bytecode=0)) - - def get_data_files(self): - """Generate list of '(package,src_dir,build_dir,filenames)' tuples""" - data = [] - if not self.packages: - return data - for package in self.packages: - # Locate package source directory - src_dir = self.get_package_dir(package) - - # Compute package build directory - build_dir = os.path.join(*([self.build_lib] + package.split('.'))) - - # Length of path to strip from found files - plen = 0 - if src_dir: - plen = len(src_dir)+1 - - # Strip directory from globbed filenames - filenames = [ - file[plen:] for file in self.find_data_files(package, src_dir) - ] - data.append((package, src_dir, build_dir, filenames)) - return data - - def find_data_files(self, package, src_dir): - """Return filenames for package's data files in 'src_dir'""" - globs = (self.package_data.get('', []) - + self.package_data.get(package, [])) - files = [] - for pattern in globs: - # Each pattern has to be converted to a platform-specific path - filelist = glob(os.path.join(src_dir, convert_path(pattern))) - # Files that match more than one pattern are only added once - files.extend([fn for fn in filelist if fn not in files - and os.path.isfile(fn)]) - return files - - def build_package_data(self): - """Copy data files into build directory""" - lastdir = None - for package, src_dir, build_dir, filenames in self.data_files: - for filename in filenames: - target = os.path.join(build_dir, filename) - self.mkpath(os.path.dirname(target)) - self.copy_file(os.path.join(src_dir, filename), target, - preserve_mode=False) - - def get_package_dir(self, package): - """Return the directory, relative to the top of the source - distribution, where package 'package' should be found - (at least according to the 'package_dir' option, if any).""" - path = package.split('.') - - if not self.package_dir: - if path: - return os.path.join(*path) - else: - return '' - else: - tail = [] - while path: - try: - pdir = self.package_dir['.'.join(path)] - except KeyError: - tail.insert(0, path[-1]) - del path[-1] - else: - tail.insert(0, pdir) - return os.path.join(*tail) - else: - # Oops, got all the way through 'path' without finding a - # match in package_dir. If package_dir defines a directory - # for the root (nameless) package, then fallback on it; - # otherwise, we might as well have not consulted - # package_dir at all, as we just use the directory implied - # by 'tail' (which should be the same as the original value - # of 'path' at this point). - pdir = self.package_dir.get('') - if pdir is not None: - tail.insert(0, pdir) - - if tail: - return os.path.join(*tail) - else: - return '' - - def check_package(self, package, package_dir): - # Empty dir name means current directory, which we can probably - # assume exists. Also, os.path.exists and isdir don't know about - # my "empty string means current dir" convention, so we have to - # circumvent them. - if package_dir != "": - if not os.path.exists(package_dir): - raise DistutilsFileError( - "package directory '%s' does not exist" % package_dir) - if not os.path.isdir(package_dir): - raise DistutilsFileError( - "supposed package directory '%s' exists, " - "but is not a directory" % package_dir) - - # Require __init__.py for all but the "root package" - if package: - init_py = os.path.join(package_dir, "__init__.py") - if os.path.isfile(init_py): - return init_py - else: - log.warn(("package init file '%s' not found " + - "(or not a regular file)"), init_py) - - # Either not in a package at all (__init__.py not expected), or - # __init__.py doesn't exist -- so don't return the filename. - return None - - def check_module(self, module, module_file): - if not os.path.isfile(module_file): - log.warn("file %s (for module %s) not found", module_file, module) - return False - else: - return True - - def find_package_modules(self, package, package_dir): - self.check_package(package, package_dir) - module_files = glob(os.path.join(package_dir, "*.py")) - modules = [] - setup_script = os.path.abspath(self.distribution.script_name) - - for f in module_files: - abs_f = os.path.abspath(f) - if abs_f != setup_script: - module = os.path.splitext(os.path.basename(f))[0] - modules.append((package, module, f)) - else: - self.debug_print("excluding %s" % setup_script) - return modules - - def find_modules(self): - """Finds individually-specified Python modules, ie. those listed by - module name in 'self.py_modules'. Returns a list of tuples (package, - module_base, filename): 'package' is a tuple of the path through - package-space to the module; 'module_base' is the bare (no - packages, no dots) module name, and 'filename' is the path to the - ".py" file (relative to the distribution root) that implements the - module. - """ - # Map package names to tuples of useful info about the package: - # (package_dir, checked) - # package_dir - the directory where we'll find source files for - # this package - # checked - true if we have checked that the package directory - # is valid (exists, contains __init__.py, ... ?) - packages = {} - - # List of (package, module, filename) tuples to return - modules = [] - - # We treat modules-in-packages almost the same as toplevel modules, - # just the "package" for a toplevel is empty (either an empty - # string or empty list, depending on context). Differences: - # - don't check for __init__.py in directory for empty package - for module in self.py_modules: - path = module.split('.') - package = '.'.join(path[0:-1]) - module_base = path[-1] - - try: - (package_dir, checked) = packages[package] - except KeyError: - package_dir = self.get_package_dir(package) - checked = 0 - - if not checked: - init_py = self.check_package(package, package_dir) - packages[package] = (package_dir, 1) - if init_py: - modules.append((package, "__init__", init_py)) - - # XXX perhaps we should also check for just .pyc files - # (so greedy closed-source bastards can distribute Python - # modules too) - module_file = os.path.join(package_dir, module_base + ".py") - if not self.check_module(module, module_file): - continue - - modules.append((package, module_base, module_file)) - - return modules - - def find_all_modules(self): - """Compute the list of all modules that will be built, whether - they are specified one-module-at-a-time ('self.py_modules') or - by whole packages ('self.packages'). Return a list of tuples - (package, module, module_file), just like 'find_modules()' and - 'find_package_modules()' do.""" - modules = [] - if self.py_modules: - modules.extend(self.find_modules()) - if self.packages: - for package in self.packages: - package_dir = self.get_package_dir(package) - m = self.find_package_modules(package, package_dir) - modules.extend(m) - return modules - - def get_source_files(self): - return [module[-1] for module in self.find_all_modules()] - - def get_module_outfile(self, build_dir, package, module): - outfile_path = [build_dir] + list(package) + [module + ".py"] - return os.path.join(*outfile_path) - - def get_outputs(self, include_bytecode=1): - modules = self.find_all_modules() - outputs = [] - for (package, module, module_file) in modules: - package = package.split('.') - filename = self.get_module_outfile(self.build_lib, package, module) - outputs.append(filename) - if include_bytecode: - if self.compile: - outputs.append(importlib.util.cache_from_source( - filename, optimization='')) - if self.optimize > 0: - outputs.append(importlib.util.cache_from_source( - filename, optimization=self.optimize)) - - outputs += [ - os.path.join(build_dir, filename) - for package, src_dir, build_dir, filenames in self.data_files - for filename in filenames - ] - - return outputs - - def build_module(self, module, module_file, package): - if isinstance(package, str): - package = package.split('.') - elif not isinstance(package, (list, tuple)): - raise TypeError( - "'package' must be a string (dot-separated), list, or tuple") - - # Now put the module source file into the "build" area -- this is - # easy, we just copy it somewhere under self.build_lib (the build - # directory for Python source). - outfile = self.get_module_outfile(self.build_lib, package, module) - dir = os.path.dirname(outfile) - self.mkpath(dir) - return self.copy_file(module_file, outfile, preserve_mode=0) - - def build_modules(self): - modules = self.find_modules() - for (package, module, module_file) in modules: - # Now "build" the module -- ie. copy the source file to - # self.build_lib (the build directory for Python source). - # (Actually, it gets copied to the directory for this package - # under self.build_lib.) - self.build_module(module, module_file, package) - - def build_packages(self): - for package in self.packages: - # Get list of (package, module, module_file) tuples based on - # scanning the package directory. 'package' is only included - # in the tuple so that 'find_modules()' and - # 'find_package_tuples()' have a consistent interface; it's - # ignored here (apart from a sanity check). Also, 'module' is - # the *unqualified* module name (ie. no dots, no package -- we - # already know its package!), and 'module_file' is the path to - # the .py file, relative to the current directory - # (ie. including 'package_dir'). - package_dir = self.get_package_dir(package) - modules = self.find_package_modules(package, package_dir) - - # Now loop over the modules we found, "building" each one (just - # copy it to self.build_lib). - for (package_, module, module_file) in modules: - assert package == package_ - self.build_module(module, module_file, package) - - def byte_compile(self, files): - if sys.dont_write_bytecode: - self.warn('byte-compiling is disabled, skipping.') - return - - from distutils.util import byte_compile - prefix = self.build_lib - if prefix[-1] != os.sep: - prefix = prefix + os.sep - - # XXX this code is essentially the same as the 'byte_compile() - # method of the "install_lib" command, except for the determination - # of the 'prefix' string. Hmmm. - if self.compile: - byte_compile(files, optimize=0, - force=self.force, prefix=prefix, dry_run=self.dry_run) - if self.optimize > 0: - byte_compile(files, optimize=self.optimize, - force=self.force, prefix=prefix, dry_run=self.dry_run) - -class build_py_2to3(build_py, Mixin2to3): - def run(self): - self.updated_files = [] - - # Base class code - if self.py_modules: - self.build_modules() - if self.packages: - self.build_packages() - self.build_package_data() - - # 2to3 - self.run_2to3(self.updated_files) - - # Remaining base class code - self.byte_compile(self.get_outputs(include_bytecode=0)) - - def build_module(self, module, module_file, package): - res = build_py.build_module(self, module, module_file, package) - if res[1]: - # file was copied - self.updated_files.append(res[0]) - return res diff --git a/Lib/distutils/command/build_scripts.py b/Lib/distutils/command/build_scripts.py deleted file mode 100644 index ccc70e64650..00000000000 --- a/Lib/distutils/command/build_scripts.py +++ /dev/null @@ -1,160 +0,0 @@ -"""distutils.command.build_scripts - -Implements the Distutils 'build_scripts' command.""" - -import os, re -from stat import ST_MODE -from distutils import sysconfig -from distutils.core import Command -from distutils.dep_util import newer -from distutils.util import convert_path, Mixin2to3 -from distutils import log -import tokenize - -# check if Python is called on the first line with this expression -first_line_re = re.compile(b'^#!.*python[0-9.]*([ \t].*)?$') - -class build_scripts(Command): - - description = "\"build\" scripts (copy and fixup #! line)" - - user_options = [ - ('build-dir=', 'd', "directory to \"build\" (copy) to"), - ('force', 'f', "forcibly build everything (ignore file timestamps"), - ('executable=', 'e', "specify final destination interpreter path"), - ] - - boolean_options = ['force'] - - - def initialize_options(self): - self.build_dir = None - self.scripts = None - self.force = None - self.executable = None - self.outfiles = None - - def finalize_options(self): - self.set_undefined_options('build', - ('build_scripts', 'build_dir'), - ('force', 'force'), - ('executable', 'executable')) - self.scripts = self.distribution.scripts - - def get_source_files(self): - return self.scripts - - def run(self): - if not self.scripts: - return - self.copy_scripts() - - - def copy_scripts(self): - r"""Copy each script listed in 'self.scripts'; if it's marked as a - Python script in the Unix way (first line matches 'first_line_re', - ie. starts with "\#!" and contains "python"), then adjust the first - line to refer to the current Python interpreter as we copy. - """ - self.mkpath(self.build_dir) - outfiles = [] - updated_files = [] - for script in self.scripts: - adjust = False - script = convert_path(script) - outfile = os.path.join(self.build_dir, os.path.basename(script)) - outfiles.append(outfile) - - if not self.force and not newer(script, outfile): - log.debug("not copying %s (up-to-date)", script) - continue - - # Always open the file, but ignore failures in dry-run mode -- - # that way, we'll get accurate feedback if we can read the - # script. - try: - f = open(script, "rb") - except OSError: - if not self.dry_run: - raise - f = None - else: - encoding, lines = tokenize.detect_encoding(f.readline) - f.seek(0) - first_line = f.readline() - if not first_line: - self.warn("%s is an empty file (skipping)" % script) - continue - - match = first_line_re.match(first_line) - if match: - adjust = True - post_interp = match.group(1) or b'' - - if adjust: - log.info("copying and adjusting %s -> %s", script, - self.build_dir) - updated_files.append(outfile) - if not self.dry_run: - if not sysconfig.python_build: - executable = self.executable - else: - executable = os.path.join( - sysconfig.get_config_var("BINDIR"), - "python%s%s" % (sysconfig.get_config_var("VERSION"), - sysconfig.get_config_var("EXE"))) - executable = os.fsencode(executable) - shebang = b"#!" + executable + post_interp + b"\n" - # Python parser starts to read a script using UTF-8 until - # it gets a #coding:xxx cookie. The shebang has to be the - # first line of a file, the #coding:xxx cookie cannot be - # written before. So the shebang has to be decodable from - # UTF-8. - try: - shebang.decode('utf-8') - except UnicodeDecodeError: - raise ValueError( - "The shebang ({!r}) is not decodable " - "from utf-8".format(shebang)) - # If the script is encoded to a custom encoding (use a - # #coding:xxx cookie), the shebang has to be decodable from - # the script encoding too. - try: - shebang.decode(encoding) - except UnicodeDecodeError: - raise ValueError( - "The shebang ({!r}) is not decodable " - "from the script encoding ({})" - .format(shebang, encoding)) - with open(outfile, "wb") as outf: - outf.write(shebang) - outf.writelines(f.readlines()) - if f: - f.close() - else: - if f: - f.close() - updated_files.append(outfile) - self.copy_file(script, outfile) - - if os.name == 'posix': - for file in outfiles: - if self.dry_run: - log.info("changing mode of %s", file) - else: - oldmode = os.stat(file)[ST_MODE] & 0o7777 - newmode = (oldmode | 0o555) & 0o7777 - if newmode != oldmode: - log.info("changing mode of %s from %o to %o", - file, oldmode, newmode) - os.chmod(file, newmode) - # XXX should we modify self.outfiles? - return outfiles, updated_files - -class build_scripts_2to3(build_scripts, Mixin2to3): - - def copy_scripts(self): - outfiles, updated_files = build_scripts.copy_scripts(self) - if not self.dry_run: - self.run_2to3(updated_files) - return outfiles, updated_files diff --git a/Lib/distutils/command/check.py b/Lib/distutils/command/check.py deleted file mode 100644 index 7ebe707cff4..00000000000 --- a/Lib/distutils/command/check.py +++ /dev/null @@ -1,145 +0,0 @@ -"""distutils.command.check - -Implements the Distutils 'check' command. -""" -from distutils.core import Command -from distutils.errors import DistutilsSetupError - -try: - # docutils is installed - from docutils.utils import Reporter - from docutils.parsers.rst import Parser - from docutils import frontend - from docutils import nodes - from io import StringIO - - class SilentReporter(Reporter): - - def __init__(self, source, report_level, halt_level, stream=None, - debug=0, encoding='ascii', error_handler='replace'): - self.messages = [] - Reporter.__init__(self, source, report_level, halt_level, stream, - debug, encoding, error_handler) - - def system_message(self, level, message, *children, **kwargs): - self.messages.append((level, message, children, kwargs)) - return nodes.system_message(message, level=level, - type=self.levels[level], - *children, **kwargs) - - HAS_DOCUTILS = True -except Exception: - # Catch all exceptions because exceptions besides ImportError probably - # indicate that docutils is not ported to Py3k. - HAS_DOCUTILS = False - -class check(Command): - """This command checks the meta-data of the package. - """ - description = ("perform some checks on the package") - user_options = [('metadata', 'm', 'Verify meta-data'), - ('restructuredtext', 'r', - ('Checks if long string meta-data syntax ' - 'are reStructuredText-compliant')), - ('strict', 's', - 'Will exit with an error if a check fails')] - - boolean_options = ['metadata', 'restructuredtext', 'strict'] - - def initialize_options(self): - """Sets default values for options.""" - self.restructuredtext = 0 - self.metadata = 1 - self.strict = 0 - self._warnings = 0 - - def finalize_options(self): - pass - - def warn(self, msg): - """Counts the number of warnings that occurs.""" - self._warnings += 1 - return Command.warn(self, msg) - - def run(self): - """Runs the command.""" - # perform the various tests - if self.metadata: - self.check_metadata() - if self.restructuredtext: - if HAS_DOCUTILS: - self.check_restructuredtext() - elif self.strict: - raise DistutilsSetupError('The docutils package is needed.') - - # let's raise an error in strict mode, if we have at least - # one warning - if self.strict and self._warnings > 0: - raise DistutilsSetupError('Please correct your package.') - - def check_metadata(self): - """Ensures that all required elements of meta-data are supplied. - - name, version, URL, (author and author_email) or - (maintainer and maintainer_email)). - - Warns if any are missing. - """ - metadata = self.distribution.metadata - - missing = [] - for attr in ('name', 'version', 'url'): - if not (hasattr(metadata, attr) and getattr(metadata, attr)): - missing.append(attr) - - if missing: - self.warn("missing required meta-data: %s" % ', '.join(missing)) - if metadata.author: - if not metadata.author_email: - self.warn("missing meta-data: if 'author' supplied, " + - "'author_email' must be supplied too") - elif metadata.maintainer: - if not metadata.maintainer_email: - self.warn("missing meta-data: if 'maintainer' supplied, " + - "'maintainer_email' must be supplied too") - else: - self.warn("missing meta-data: either (author and author_email) " + - "or (maintainer and maintainer_email) " + - "must be supplied") - - def check_restructuredtext(self): - """Checks if the long string fields are reST-compliant.""" - data = self.distribution.get_long_description() - for warning in self._check_rst_data(data): - line = warning[-1].get('line') - if line is None: - warning = warning[1] - else: - warning = '%s (line %s)' % (warning[1], line) - self.warn(warning) - - def _check_rst_data(self, data): - """Returns warnings when the provided data doesn't compile.""" - source_path = StringIO() - parser = Parser() - settings = frontend.OptionParser(components=(Parser,)).get_default_values() - settings.tab_width = 4 - settings.pep_references = None - settings.rfc_references = None - reporter = SilentReporter(source_path, - settings.report_level, - settings.halt_level, - stream=settings.warning_stream, - debug=settings.debug, - encoding=settings.error_encoding, - error_handler=settings.error_encoding_error_handler) - - document = nodes.document(settings, reporter, source=source_path) - document.note_source(source_path, -1) - try: - parser.parse(data, document) - except AttributeError as e: - reporter.messages.append( - (-1, 'Could not finish the parsing: %s.' % e, '', {})) - - return reporter.messages diff --git a/Lib/distutils/command/clean.py b/Lib/distutils/command/clean.py deleted file mode 100644 index 0cb27016621..00000000000 --- a/Lib/distutils/command/clean.py +++ /dev/null @@ -1,76 +0,0 @@ -"""distutils.command.clean - -Implements the Distutils 'clean' command.""" - -# contributed by Bastian Kleineidam <calvin@cs.uni-sb.de>, added 2000-03-18 - -import os -from distutils.core import Command -from distutils.dir_util import remove_tree -from distutils import log - -class clean(Command): - - description = "clean up temporary files from 'build' command" - user_options = [ - ('build-base=', 'b', - "base build directory (default: 'build.build-base')"), - ('build-lib=', None, - "build directory for all modules (default: 'build.build-lib')"), - ('build-temp=', 't', - "temporary build directory (default: 'build.build-temp')"), - ('build-scripts=', None, - "build directory for scripts (default: 'build.build-scripts')"), - ('bdist-base=', None, - "temporary directory for built distributions"), - ('all', 'a', - "remove all build output, not just temporary by-products") - ] - - boolean_options = ['all'] - - def initialize_options(self): - self.build_base = None - self.build_lib = None - self.build_temp = None - self.build_scripts = None - self.bdist_base = None - self.all = None - - def finalize_options(self): - self.set_undefined_options('build', - ('build_base', 'build_base'), - ('build_lib', 'build_lib'), - ('build_scripts', 'build_scripts'), - ('build_temp', 'build_temp')) - self.set_undefined_options('bdist', - ('bdist_base', 'bdist_base')) - - def run(self): - # remove the build/temp.<plat> directory (unless it's already - # gone) - if os.path.exists(self.build_temp): - remove_tree(self.build_temp, dry_run=self.dry_run) - else: - log.debug("'%s' does not exist -- can't clean it", - self.build_temp) - - if self.all: - # remove build directories - for directory in (self.build_lib, - self.bdist_base, - self.build_scripts): - if os.path.exists(directory): - remove_tree(directory, dry_run=self.dry_run) - else: - log.warn("'%s' does not exist -- can't clean it", - directory) - - # just for the heck of it, try to remove the base build directory: - # we might have emptied it right now, but if not we don't care - if not self.dry_run: - try: - os.rmdir(self.build_base) - log.info("removing '%s'", self.build_base) - except OSError: - pass diff --git a/Lib/distutils/command/command_template b/Lib/distutils/command/command_template deleted file mode 100644 index 6106819db84..00000000000 --- a/Lib/distutils/command/command_template +++ /dev/null @@ -1,33 +0,0 @@ -"""distutils.command.x - -Implements the Distutils 'x' command. -""" - -# created 2000/mm/dd, John Doe - -__revision__ = "$Id$" - -from distutils.core import Command - - -class x(Command): - - # Brief (40-50 characters) description of the command - description = "" - - # List of option tuples: long name, short name (None if no short - # name), and help string. - user_options = [('', '', - ""), - ] - - def initialize_options(self): - self. = None - self. = None - self. = None - - def finalize_options(self): - if self.x is None: - self.x = - - def run(self): diff --git a/Lib/distutils/command/config.py b/Lib/distutils/command/config.py deleted file mode 100644 index 4ae153d1943..00000000000 --- a/Lib/distutils/command/config.py +++ /dev/null @@ -1,347 +0,0 @@ -"""distutils.command.config - -Implements the Distutils 'config' command, a (mostly) empty command class -that exists mainly to be sub-classed by specific module distributions and -applications. The idea is that while every "config" command is different, -at least they're all named the same, and users always see "config" in the -list of standard commands. Also, this is a good place to put common -configure-like tasks: "try to compile this C code", or "figure out where -this header file lives". -""" - -import os, re - -from distutils.core import Command -from distutils.errors import DistutilsExecError -from distutils.sysconfig import customize_compiler -from distutils import log - -LANG_EXT = {"c": ".c", "c++": ".cxx"} - -class config(Command): - - description = "prepare to build" - - user_options = [ - ('compiler=', None, - "specify the compiler type"), - ('cc=', None, - "specify the compiler executable"), - ('include-dirs=', 'I', - "list of directories to search for header files"), - ('define=', 'D', - "C preprocessor macros to define"), - ('undef=', 'U', - "C preprocessor macros to undefine"), - ('libraries=', 'l', - "external C libraries to link with"), - ('library-dirs=', 'L', - "directories to search for external C libraries"), - - ('noisy', None, - "show every action (compile, link, run, ...) taken"), - ('dump-source', None, - "dump generated source files before attempting to compile them"), - ] - - - # The three standard command methods: since the "config" command - # does nothing by default, these are empty. - - def initialize_options(self): - self.compiler = None - self.cc = None - self.include_dirs = None - self.libraries = None - self.library_dirs = None - - # maximal output for now - self.noisy = 1 - self.dump_source = 1 - - # list of temporary files generated along-the-way that we have - # to clean at some point - self.temp_files = [] - - def finalize_options(self): - if self.include_dirs is None: - self.include_dirs = self.distribution.include_dirs or [] - elif isinstance(self.include_dirs, str): - self.include_dirs = self.include_dirs.split(os.pathsep) - - if self.libraries is None: - self.libraries = [] - elif isinstance(self.libraries, str): - self.libraries = [self.libraries] - - if self.library_dirs is None: - self.library_dirs = [] - elif isinstance(self.library_dirs, str): - self.library_dirs = self.library_dirs.split(os.pathsep) - - def run(self): - pass - - # Utility methods for actual "config" commands. The interfaces are - # loosely based on Autoconf macros of similar names. Sub-classes - # may use these freely. - - def _check_compiler(self): - """Check that 'self.compiler' really is a CCompiler object; - if not, make it one. - """ - # We do this late, and only on-demand, because this is an expensive - # import. - from distutils.ccompiler import CCompiler, new_compiler - if not isinstance(self.compiler, CCompiler): - self.compiler = new_compiler(compiler=self.compiler, - dry_run=self.dry_run, force=1) - customize_compiler(self.compiler) - if self.include_dirs: - self.compiler.set_include_dirs(self.include_dirs) - if self.libraries: - self.compiler.set_libraries(self.libraries) - if self.library_dirs: - self.compiler.set_library_dirs(self.library_dirs) - - def _gen_temp_sourcefile(self, body, headers, lang): - filename = "_configtest" + LANG_EXT[lang] - file = open(filename, "w") - if headers: - for header in headers: - file.write("#include <%s>\n" % header) - file.write("\n") - file.write(body) - if body[-1] != "\n": - file.write("\n") - file.close() - return filename - - def _preprocess(self, body, headers, include_dirs, lang): - src = self._gen_temp_sourcefile(body, headers, lang) - out = "_configtest.i" - self.temp_files.extend([src, out]) - self.compiler.preprocess(src, out, include_dirs=include_dirs) - return (src, out) - - def _compile(self, body, headers, include_dirs, lang): - src = self._gen_temp_sourcefile(body, headers, lang) - if self.dump_source: - dump_file(src, "compiling '%s':" % src) - (obj,) = self.compiler.object_filenames([src]) - self.temp_files.extend([src, obj]) - self.compiler.compile([src], include_dirs=include_dirs) - return (src, obj) - - def _link(self, body, headers, include_dirs, libraries, library_dirs, - lang): - (src, obj) = self._compile(body, headers, include_dirs, lang) - prog = os.path.splitext(os.path.basename(src))[0] - self.compiler.link_executable([obj], prog, - libraries=libraries, - library_dirs=library_dirs, - target_lang=lang) - - if self.compiler.exe_extension is not None: - prog = prog + self.compiler.exe_extension - self.temp_files.append(prog) - - return (src, obj, prog) - - def _clean(self, *filenames): - if not filenames: - filenames = self.temp_files - self.temp_files = [] - log.info("removing: %s", ' '.join(filenames)) - for filename in filenames: - try: - os.remove(filename) - except OSError: - pass - - - # XXX these ignore the dry-run flag: what to do, what to do? even if - # you want a dry-run build, you still need some sort of configuration - # info. My inclination is to make it up to the real config command to - # consult 'dry_run', and assume a default (minimal) configuration if - # true. The problem with trying to do it here is that you'd have to - # return either true or false from all the 'try' methods, neither of - # which is correct. - - # XXX need access to the header search path and maybe default macros. - - def try_cpp(self, body=None, headers=None, include_dirs=None, lang="c"): - """Construct a source file from 'body' (a string containing lines - of C/C++ code) and 'headers' (a list of header files to include) - and run it through the preprocessor. Return true if the - preprocessor succeeded, false if there were any errors. - ('body' probably isn't of much use, but what the heck.) - """ - from distutils.ccompiler import CompileError - self._check_compiler() - ok = True - try: - self._preprocess(body, headers, include_dirs, lang) - except CompileError: - ok = False - - self._clean() - return ok - - def search_cpp(self, pattern, body=None, headers=None, include_dirs=None, - lang="c"): - """Construct a source file (just like 'try_cpp()'), run it through - the preprocessor, and return true if any line of the output matches - 'pattern'. 'pattern' should either be a compiled regex object or a - string containing a regex. If both 'body' and 'headers' are None, - preprocesses an empty file -- which can be useful to determine the - symbols the preprocessor and compiler set by default. - """ - self._check_compiler() - src, out = self._preprocess(body, headers, include_dirs, lang) - - if isinstance(pattern, str): - pattern = re.compile(pattern) - - file = open(out) - match = False - while True: - line = file.readline() - if line == '': - break - if pattern.search(line): - match = True - break - - file.close() - self._clean() - return match - - def try_compile(self, body, headers=None, include_dirs=None, lang="c"): - """Try to compile a source file built from 'body' and 'headers'. - Return true on success, false otherwise. - """ - from distutils.ccompiler import CompileError - self._check_compiler() - try: - self._compile(body, headers, include_dirs, lang) - ok = True - except CompileError: - ok = False - - log.info(ok and "success!" or "failure.") - self._clean() - return ok - - def try_link(self, body, headers=None, include_dirs=None, libraries=None, - library_dirs=None, lang="c"): - """Try to compile and link a source file, built from 'body' and - 'headers', to executable form. Return true on success, false - otherwise. - """ - from distutils.ccompiler import CompileError, LinkError - self._check_compiler() - try: - self._link(body, headers, include_dirs, - libraries, library_dirs, lang) - ok = True - except (CompileError, LinkError): - ok = False - - log.info(ok and "success!" or "failure.") - self._clean() - return ok - - def try_run(self, body, headers=None, include_dirs=None, libraries=None, - library_dirs=None, lang="c"): - """Try to compile, link to an executable, and run a program - built from 'body' and 'headers'. Return true on success, false - otherwise. - """ - from distutils.ccompiler import CompileError, LinkError - self._check_compiler() - try: - src, obj, exe = self._link(body, headers, include_dirs, - libraries, library_dirs, lang) - self.spawn([exe]) - ok = True - except (CompileError, LinkError, DistutilsExecError): - ok = False - - log.info(ok and "success!" or "failure.") - self._clean() - return ok - - - # -- High-level methods -------------------------------------------- - # (these are the ones that are actually likely to be useful - # when implementing a real-world config command!) - - def check_func(self, func, headers=None, include_dirs=None, - libraries=None, library_dirs=None, decl=0, call=0): - """Determine if function 'func' is available by constructing a - source file that refers to 'func', and compiles and links it. - If everything succeeds, returns true; otherwise returns false. - - The constructed source file starts out by including the header - files listed in 'headers'. If 'decl' is true, it then declares - 'func' (as "int func()"); you probably shouldn't supply 'headers' - and set 'decl' true in the same call, or you might get errors about - a conflicting declarations for 'func'. Finally, the constructed - 'main()' function either references 'func' or (if 'call' is true) - calls it. 'libraries' and 'library_dirs' are used when - linking. - """ - self._check_compiler() - body = [] - if decl: - body.append("int %s ();" % func) - body.append("int main () {") - if call: - body.append(" %s();" % func) - else: - body.append(" %s;" % func) - body.append("}") - body = "\n".join(body) + "\n" - - return self.try_link(body, headers, include_dirs, - libraries, library_dirs) - - def check_lib(self, library, library_dirs=None, headers=None, - include_dirs=None, other_libraries=[]): - """Determine if 'library' is available to be linked against, - without actually checking that any particular symbols are provided - by it. 'headers' will be used in constructing the source file to - be compiled, but the only effect of this is to check if all the - header files listed are available. Any libraries listed in - 'other_libraries' will be included in the link, in case 'library' - has symbols that depend on other libraries. - """ - self._check_compiler() - return self.try_link("int main (void) { }", headers, include_dirs, - [library] + other_libraries, library_dirs) - - def check_header(self, header, include_dirs=None, library_dirs=None, - lang="c"): - """Determine if the system header file named by 'header_file' - exists and can be found by the preprocessor; return true if so, - false otherwise. - """ - return self.try_cpp(body="/* No body */", headers=[header], - include_dirs=include_dirs) - - -def dump_file(filename, head=None): - """Dumps a file content into log.info. - - If head is not None, will be dumped before the file content. - """ - if head is None: - log.info('%s', filename) - else: - log.info(head) - file = open(filename) - try: - log.info(file.read()) - finally: - file.close() diff --git a/Lib/distutils/command/install.py b/Lib/distutils/command/install.py deleted file mode 100644 index fd3357ea78e..00000000000 --- a/Lib/distutils/command/install.py +++ /dev/null @@ -1,705 +0,0 @@ -"""distutils.command.install - -Implements the Distutils 'install' command.""" - -import sys -import os - -from distutils import log -from distutils.core import Command -from distutils.debug import DEBUG -from distutils.sysconfig import get_config_vars -from distutils.errors import DistutilsPlatformError -from distutils.file_util import write_file -from distutils.util import convert_path, subst_vars, change_root -from distutils.util import get_platform -from distutils.errors import DistutilsOptionError - -from site import USER_BASE -from site import USER_SITE -HAS_USER_SITE = True - -WINDOWS_SCHEME = { - 'purelib': '$base/Lib/site-packages', - 'platlib': '$base/Lib/site-packages', - 'headers': '$base/Include/$dist_name', - 'scripts': '$base/Scripts', - 'data' : '$base', -} - -INSTALL_SCHEMES = { - 'unix_prefix': { - 'purelib': '$base/lib/python$py_version_short/site-packages', - 'platlib': '$platbase/lib/python$py_version_short/site-packages', - 'headers': '$base/include/python$py_version_short$abiflags/$dist_name', - 'scripts': '$base/bin', - 'data' : '$base', - }, - 'unix_local': { - 'purelib': '$base/local/lib/python$py_version_short/dist-packages', - 'platlib': '$platbase/local/lib/python$py_version_short/dist-packages', - 'headers': '$base/local/include/python$py_version_short/$dist_name', - 'scripts': '$base/local/bin', - 'data' : '$base/local', - }, - 'deb_system': { - 'purelib': '$base/lib/python3/dist-packages', - 'platlib': '$platbase/lib/python3/dist-packages', - 'headers': '$base/include/python$py_version_short/$dist_name', - 'scripts': '$base/bin', - 'data' : '$base', - }, - 'unix_home': { - 'purelib': '$base/lib/python', - 'platlib': '$base/lib/python', - 'headers': '$base/include/python/$dist_name', - 'scripts': '$base/bin', - 'data' : '$base', - }, - 'nt': WINDOWS_SCHEME, - } - -# user site schemes -if HAS_USER_SITE: - INSTALL_SCHEMES['nt_user'] = { - 'purelib': '$usersite', - 'platlib': '$usersite', - 'headers': '$userbase/Python$py_version_nodot/Include/$dist_name', - 'scripts': '$userbase/Python$py_version_nodot/Scripts', - 'data' : '$userbase', - } - - INSTALL_SCHEMES['unix_user'] = { - 'purelib': '$usersite', - 'platlib': '$usersite', - 'headers': - '$userbase/include/python$py_version_short$abiflags/$dist_name', - 'scripts': '$userbase/bin', - 'data' : '$userbase', - } - -# XXX RUSTPYTHON: replace python with rustpython in all these paths -for group in INSTALL_SCHEMES.values(): - for key in group.keys(): - group[key] = group[key].replace("Python", "RustPython").replace("python", "rustpython") - -# The keys to an installation scheme; if any new types of files are to be -# installed, be sure to add an entry to every installation scheme above, -# and to SCHEME_KEYS here. -SCHEME_KEYS = ('purelib', 'platlib', 'headers', 'scripts', 'data') - - -class install(Command): - - description = "install everything from build directory" - - user_options = [ - # Select installation scheme and set base director(y|ies) - ('prefix=', None, - "installation prefix"), - ('exec-prefix=', None, - "(Unix only) prefix for platform-specific files"), - ('home=', None, - "(Unix only) home directory to install under"), - - # Or, just set the base director(y|ies) - ('install-base=', None, - "base installation directory (instead of --prefix or --home)"), - ('install-platbase=', None, - "base installation directory for platform-specific files " + - "(instead of --exec-prefix or --home)"), - ('root=', None, - "install everything relative to this alternate root directory"), - - # Or, explicitly set the installation scheme - ('install-purelib=', None, - "installation directory for pure Python module distributions"), - ('install-platlib=', None, - "installation directory for non-pure module distributions"), - ('install-lib=', None, - "installation directory for all module distributions " + - "(overrides --install-purelib and --install-platlib)"), - - ('install-headers=', None, - "installation directory for C/C++ headers"), - ('install-scripts=', None, - "installation directory for Python scripts"), - ('install-data=', None, - "installation directory for data files"), - - # Byte-compilation options -- see install_lib.py for details, as - # these are duplicated from there (but only install_lib does - # anything with them). - ('compile', 'c', "compile .py to .pyc [default]"), - ('no-compile', None, "don't compile .py files"), - ('optimize=', 'O', - "also compile with optimization: -O1 for \"python -O\", " - "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), - - # Miscellaneous control options - ('force', 'f', - "force installation (overwrite any existing files)"), - ('skip-build', None, - "skip rebuilding everything (for testing/debugging)"), - - # Where to install documentation (eventually!) - #('doc-format=', None, "format of documentation to generate"), - #('install-man=', None, "directory for Unix man pages"), - #('install-html=', None, "directory for HTML documentation"), - #('install-info=', None, "directory for GNU info files"), - - ('record=', None, - "filename in which to record list of installed files"), - - ('install-layout=', None, - "installation layout to choose (known values: deb, unix)"), - ] - - boolean_options = ['compile', 'force', 'skip-build'] - - if HAS_USER_SITE: - user_options.append(('user', None, - "install in user site-package '%s'" % USER_SITE)) - boolean_options.append('user') - - negative_opt = {'no-compile' : 'compile'} - - - def initialize_options(self): - """Initializes options.""" - # High-level options: these select both an installation base - # and scheme. - self.prefix = None - self.exec_prefix = None - self.home = None - self.user = 0 - self.prefix_option = None - - # These select only the installation base; it's up to the user to - # specify the installation scheme (currently, that means supplying - # the --install-{platlib,purelib,scripts,data} options). - self.install_base = None - self.install_platbase = None - self.root = None - - # These options are the actual installation directories; if not - # supplied by the user, they are filled in using the installation - # scheme implied by prefix/exec-prefix/home and the contents of - # that installation scheme. - self.install_purelib = None # for pure module distributions - self.install_platlib = None # non-pure (dists w/ extensions) - self.install_headers = None # for C/C++ headers - self.install_lib = None # set to either purelib or platlib - self.install_scripts = None - self.install_data = None - self.install_userbase = USER_BASE - self.install_usersite = USER_SITE - - # enable custom installation, known values: deb - self.install_layout = None - self.multiarch = None - - self.compile = None - self.optimize = None - - # Deprecated - # These two are for putting non-packagized distributions into their - # own directory and creating a .pth file if it makes sense. - # 'extra_path' comes from the setup file; 'install_path_file' can - # be turned off if it makes no sense to install a .pth file. (But - # better to install it uselessly than to guess wrong and not - # install it when it's necessary and would be used!) Currently, - # 'install_path_file' is always true unless some outsider meddles - # with it. - self.extra_path = None - self.install_path_file = 1 - - # 'force' forces installation, even if target files are not - # out-of-date. 'skip_build' skips running the "build" command, - # handy if you know it's not necessary. 'warn_dir' (which is *not* - # a user option, it's just there so the bdist_* commands can turn - # it off) determines whether we warn about installing to a - # directory not in sys.path. - self.force = 0 - self.skip_build = 0 - self.warn_dir = 1 - - # These are only here as a conduit from the 'build' command to the - # 'install_*' commands that do the real work. ('build_base' isn't - # actually used anywhere, but it might be useful in future.) They - # are not user options, because if the user told the install - # command where the build directory is, that wouldn't affect the - # build command. - self.build_base = None - self.build_lib = None - - # Not defined yet because we don't know anything about - # documentation yet. - #self.install_man = None - #self.install_html = None - #self.install_info = None - - self.record = None - - - # -- Option finalizing methods ------------------------------------- - # (This is rather more involved than for most commands, - # because this is where the policy for installing third- - # party Python modules on various platforms given a wide - # array of user input is decided. Yes, it's quite complex!) - - def finalize_options(self): - """Finalizes options.""" - # This method (and its pliant slaves, like 'finalize_unix()', - # 'finalize_other()', and 'select_scheme()') is where the default - # installation directories for modules, extension modules, and - # anything else we care to install from a Python module - # distribution. Thus, this code makes a pretty important policy - # statement about how third-party stuff is added to a Python - # installation! Note that the actual work of installation is done - # by the relatively simple 'install_*' commands; they just take - # their orders from the installation directory options determined - # here. - - # Check for errors/inconsistencies in the options; first, stuff - # that's wrong on any platform. - - if ((self.prefix or self.exec_prefix or self.home) and - (self.install_base or self.install_platbase)): - raise DistutilsOptionError( - "must supply either prefix/exec-prefix/home or " + - "install-base/install-platbase -- not both") - - if self.home and (self.prefix or self.exec_prefix): - raise DistutilsOptionError( - "must supply either home or prefix/exec-prefix -- not both") - - if self.user and (self.prefix or self.exec_prefix or self.home or - self.install_base or self.install_platbase): - raise DistutilsOptionError("can't combine user with prefix, " - "exec_prefix/home, or install_(plat)base") - - # Next, stuff that's wrong (or dubious) only on certain platforms. - if os.name != "posix": - if self.exec_prefix: - self.warn("exec-prefix option ignored on this platform") - self.exec_prefix = None - - # Now the interesting logic -- so interesting that we farm it out - # to other methods. The goal of these methods is to set the final - # values for the install_{lib,scripts,data,...} options, using as - # input a heady brew of prefix, exec_prefix, home, install_base, - # install_platbase, user-supplied versions of - # install_{purelib,platlib,lib,scripts,data,...}, and the - # INSTALL_SCHEME dictionary above. Phew! - - self.dump_dirs("pre-finalize_{unix,other}") - - if os.name == 'posix': - self.finalize_unix() - else: - self.finalize_other() - - self.dump_dirs("post-finalize_{unix,other}()") - - # Expand configuration variables, tilde, etc. in self.install_base - # and self.install_platbase -- that way, we can use $base or - # $platbase in the other installation directories and not worry - # about needing recursive variable expansion (shudder). - - py_version = sys.version.split()[0] - (prefix, exec_prefix) = get_config_vars('prefix', 'exec_prefix') - try: - abiflags = sys.abiflags - except AttributeError: - # sys.abiflags may not be defined on all platforms. - abiflags = '' - self.config_vars = {'dist_name': self.distribution.get_name(), - 'dist_version': self.distribution.get_version(), - 'dist_fullname': self.distribution.get_fullname(), - 'py_version': py_version, - 'py_version_short': '%d.%d' % sys.version_info[:2], - 'py_version_nodot': '%d%d' % sys.version_info[:2], - 'sys_prefix': prefix, - 'prefix': prefix, - 'sys_exec_prefix': exec_prefix, - 'exec_prefix': exec_prefix, - 'abiflags': abiflags, - } - - if HAS_USER_SITE: - self.config_vars['userbase'] = self.install_userbase - self.config_vars['usersite'] = self.install_usersite - - self.expand_basedirs() - - self.dump_dirs("post-expand_basedirs()") - - # Now define config vars for the base directories so we can expand - # everything else. - self.config_vars['base'] = self.install_base - self.config_vars['platbase'] = self.install_platbase - - if DEBUG: - from pprint import pprint - print("config vars:") - pprint(self.config_vars) - - # Expand "~" and configuration variables in the installation - # directories. - self.expand_dirs() - - self.dump_dirs("post-expand_dirs()") - - # Create directories in the home dir: - if self.user: - self.create_home_path() - - # Pick the actual directory to install all modules to: either - # install_purelib or install_platlib, depending on whether this - # module distribution is pure or not. Of course, if the user - # already specified install_lib, use their selection. - if self.install_lib is None: - if self.distribution.ext_modules: # has extensions: non-pure - self.install_lib = self.install_platlib - else: - self.install_lib = self.install_purelib - - - # Convert directories from Unix /-separated syntax to the local - # convention. - self.convert_paths('lib', 'purelib', 'platlib', - 'scripts', 'data', 'headers', - 'userbase', 'usersite') - - # Deprecated - # Well, we're not actually fully completely finalized yet: we still - # have to deal with 'extra_path', which is the hack for allowing - # non-packagized module distributions (hello, Numerical Python!) to - # get their own directories. - self.handle_extra_path() - self.install_libbase = self.install_lib # needed for .pth file - self.install_lib = os.path.join(self.install_lib, self.extra_dirs) - - # If a new root directory was supplied, make all the installation - # dirs relative to it. - if self.root is not None: - self.change_roots('libbase', 'lib', 'purelib', 'platlib', - 'scripts', 'data', 'headers') - - self.dump_dirs("after prepending root") - - # Find out the build directories, ie. where to install from. - self.set_undefined_options('build', - ('build_base', 'build_base'), - ('build_lib', 'build_lib')) - - # Punt on doc directories for now -- after all, we're punting on - # documentation completely! - - def dump_dirs(self, msg): - """Dumps the list of user options.""" - if not DEBUG: - return - from distutils.fancy_getopt import longopt_xlate - log.debug(msg + ":") - for opt in self.user_options: - opt_name = opt[0] - if opt_name[-1] == "=": - opt_name = opt_name[0:-1] - if opt_name in self.negative_opt: - opt_name = self.negative_opt[opt_name] - opt_name = opt_name.translate(longopt_xlate) - val = not getattr(self, opt_name) - else: - opt_name = opt_name.translate(longopt_xlate) - val = getattr(self, opt_name) - log.debug(" %s: %s", opt_name, val) - - def finalize_unix(self): - """Finalizes options for posix platforms.""" - if self.install_base is not None or self.install_platbase is not None: - if ((self.install_lib is None and - self.install_purelib is None and - self.install_platlib is None) or - self.install_headers is None or - self.install_scripts is None or - self.install_data is None): - raise DistutilsOptionError( - "install-base or install-platbase supplied, but " - "installation scheme is incomplete") - return - - if self.user: - if self.install_userbase is None: - raise DistutilsPlatformError( - "User base directory is not specified") - self.install_base = self.install_platbase = self.install_userbase - self.select_scheme("unix_user") - elif self.home is not None: - self.install_base = self.install_platbase = self.home - self.select_scheme("unix_home") - else: - self.prefix_option = self.prefix - if self.prefix is None: - if self.exec_prefix is not None: - raise DistutilsOptionError( - "must not supply exec-prefix without prefix") - - self.prefix = os.path.normpath(sys.prefix) - self.exec_prefix = os.path.normpath(sys.exec_prefix) - - else: - if self.exec_prefix is None: - self.exec_prefix = self.prefix - - self.install_base = self.prefix - self.install_platbase = self.exec_prefix - if self.install_layout: - if self.install_layout.lower() in ['deb']: - import sysconfig - self.multiarch = sysconfig.get_config_var('MULTIARCH') - self.select_scheme("deb_system") - elif self.install_layout.lower() in ['unix']: - self.select_scheme("unix_prefix") - else: - raise DistutilsOptionError( - "unknown value for --install-layout") - elif ((self.prefix_option and - os.path.normpath(self.prefix) != '/usr/local') - or sys.base_prefix != sys.prefix - or 'PYTHONUSERBASE' in os.environ - or 'VIRTUAL_ENV' in os.environ - or 'real_prefix' in sys.__dict__): - self.select_scheme("unix_prefix") - else: - if os.path.normpath(self.prefix) == '/usr/local': - self.prefix = self.exec_prefix = '/usr' - self.install_base = self.install_platbase = '/usr' - self.select_scheme("unix_local") - - def finalize_other(self): - """Finalizes options for non-posix platforms""" - if self.user: - if self.install_userbase is None: - raise DistutilsPlatformError( - "User base directory is not specified") - self.install_base = self.install_platbase = self.install_userbase - self.select_scheme(os.name + "_user") - elif self.home is not None: - self.install_base = self.install_platbase = self.home - self.select_scheme("unix_home") - else: - if self.prefix is None: - self.prefix = os.path.normpath(sys.prefix) - - self.install_base = self.install_platbase = self.prefix - try: - self.select_scheme(os.name) - except KeyError: - raise DistutilsPlatformError( - "I don't know how to install stuff on '%s'" % os.name) - - def select_scheme(self, name): - """Sets the install directories by applying the install schemes.""" - # it's the caller's problem if they supply a bad name! - scheme = INSTALL_SCHEMES[name] - for key in SCHEME_KEYS: - attrname = 'install_' + key - if getattr(self, attrname) is None: - setattr(self, attrname, scheme[key]) - - def _expand_attrs(self, attrs): - for attr in attrs: - val = getattr(self, attr) - if val is not None: - if os.name == 'posix' or os.name == 'nt': - val = os.path.expanduser(val) - val = subst_vars(val, self.config_vars) - setattr(self, attr, val) - - def expand_basedirs(self): - """Calls `os.path.expanduser` on install_base, install_platbase and - root.""" - self._expand_attrs(['install_base', 'install_platbase', 'root']) - - def expand_dirs(self): - """Calls `os.path.expanduser` on install dirs.""" - self._expand_attrs(['install_purelib', 'install_platlib', - 'install_lib', 'install_headers', - 'install_scripts', 'install_data',]) - - def convert_paths(self, *names): - """Call `convert_path` over `names`.""" - for name in names: - attr = "install_" + name - setattr(self, attr, convert_path(getattr(self, attr))) - - def handle_extra_path(self): - """Set `path_file` and `extra_dirs` using `extra_path`.""" - if self.extra_path is None: - self.extra_path = self.distribution.extra_path - - if self.extra_path is not None: - log.warn( - "Distribution option extra_path is deprecated. " - "See issue27919 for details." - ) - if isinstance(self.extra_path, str): - self.extra_path = self.extra_path.split(',') - - if len(self.extra_path) == 1: - path_file = extra_dirs = self.extra_path[0] - elif len(self.extra_path) == 2: - path_file, extra_dirs = self.extra_path - else: - raise DistutilsOptionError( - "'extra_path' option must be a list, tuple, or " - "comma-separated string with 1 or 2 elements") - - # convert to local form in case Unix notation used (as it - # should be in setup scripts) - extra_dirs = convert_path(extra_dirs) - else: - path_file = None - extra_dirs = '' - - # XXX should we warn if path_file and not extra_dirs? (in which - # case the path file would be harmless but pointless) - self.path_file = path_file - self.extra_dirs = extra_dirs - - def change_roots(self, *names): - """Change the install directories pointed by name using root.""" - for name in names: - attr = "install_" + name - setattr(self, attr, change_root(self.root, getattr(self, attr))) - - def create_home_path(self): - """Create directories under ~.""" - if not self.user: - return - home = convert_path(os.path.expanduser("~")) - for name, path in self.config_vars.items(): - if path.startswith(home) and not os.path.isdir(path): - self.debug_print("os.makedirs('%s', 0o700)" % path) - os.makedirs(path, 0o700) - - # -- Command execution methods ------------------------------------- - - def run(self): - """Runs the command.""" - # Obviously have to build before we can install - if not self.skip_build: - self.run_command('build') - # If we built for any other platform, we can't install. - build_plat = self.distribution.get_command_obj('build').plat_name - # check warn_dir - it is a clue that the 'install' is happening - # internally, and not to sys.path, so we don't check the platform - # matches what we are running. - if self.warn_dir and build_plat != get_platform(): - raise DistutilsPlatformError("Can't install when " - "cross-compiling") - - # Run all sub-commands (at least those that need to be run) - for cmd_name in self.get_sub_commands(): - self.run_command(cmd_name) - - if self.path_file: - self.create_path_file() - - # write list of installed files, if requested. - if self.record: - outputs = self.get_outputs() - if self.root: # strip any package prefix - root_len = len(self.root) - for counter in range(len(outputs)): - outputs[counter] = outputs[counter][root_len:] - self.execute(write_file, - (self.record, outputs), - "writing list of installed files to '%s'" % - self.record) - - sys_path = map(os.path.normpath, sys.path) - sys_path = map(os.path.normcase, sys_path) - install_lib = os.path.normcase(os.path.normpath(self.install_lib)) - if (self.warn_dir and - not (self.path_file and self.install_path_file) and - install_lib not in sys_path): - log.debug(("modules installed to '%s', which is not in " - "Python's module search path (sys.path) -- " - "you'll have to change the search path yourself"), - self.install_lib) - - def create_path_file(self): - """Creates the .pth file""" - filename = os.path.join(self.install_libbase, - self.path_file + ".pth") - if self.install_path_file: - self.execute(write_file, - (filename, [self.extra_dirs]), - "creating %s" % filename) - else: - self.warn("path file '%s' not created" % filename) - - - # -- Reporting methods --------------------------------------------- - - def get_outputs(self): - """Assembles the outputs of all the sub-commands.""" - outputs = [] - for cmd_name in self.get_sub_commands(): - cmd = self.get_finalized_command(cmd_name) - # Add the contents of cmd.get_outputs(), ensuring - # that outputs doesn't contain duplicate entries - for filename in cmd.get_outputs(): - if filename not in outputs: - outputs.append(filename) - - if self.path_file and self.install_path_file: - outputs.append(os.path.join(self.install_libbase, - self.path_file + ".pth")) - - return outputs - - def get_inputs(self): - """Returns the inputs of all the sub-commands""" - # XXX gee, this looks familiar ;-( - inputs = [] - for cmd_name in self.get_sub_commands(): - cmd = self.get_finalized_command(cmd_name) - inputs.extend(cmd.get_inputs()) - - return inputs - - # -- Predicates for sub-command list ------------------------------- - - def has_lib(self): - """Returns true if the current distribution has any Python - modules to install.""" - return (self.distribution.has_pure_modules() or - self.distribution.has_ext_modules()) - - def has_headers(self): - """Returns true if the current distribution has any headers to - install.""" - return self.distribution.has_headers() - - def has_scripts(self): - """Returns true if the current distribution has any scripts to. - install.""" - return self.distribution.has_scripts() - - def has_data(self): - """Returns true if the current distribution has any data to. - install.""" - return self.distribution.has_data_files() - - # 'sub_commands': a list of commands this command might have to run to - # get its work done. See cmd.py for more info. - sub_commands = [('install_lib', has_lib), - ('install_headers', has_headers), - ('install_scripts', has_scripts), - ('install_data', has_data), - ('install_egg_info', lambda self:True), - ] diff --git a/Lib/distutils/command/install_data.py b/Lib/distutils/command/install_data.py deleted file mode 100644 index 947cd76a99e..00000000000 --- a/Lib/distutils/command/install_data.py +++ /dev/null @@ -1,79 +0,0 @@ -"""distutils.command.install_data - -Implements the Distutils 'install_data' command, for installing -platform-independent data files.""" - -# contributed by Bastian Kleineidam - -import os -from distutils.core import Command -from distutils.util import change_root, convert_path - -class install_data(Command): - - description = "install data files" - - user_options = [ - ('install-dir=', 'd', - "base directory for installing data files " - "(default: installation base dir)"), - ('root=', None, - "install everything relative to this alternate root directory"), - ('force', 'f', "force installation (overwrite existing files)"), - ] - - boolean_options = ['force'] - - def initialize_options(self): - self.install_dir = None - self.outfiles = [] - self.root = None - self.force = 0 - self.data_files = self.distribution.data_files - self.warn_dir = 1 - - def finalize_options(self): - self.set_undefined_options('install', - ('install_data', 'install_dir'), - ('root', 'root'), - ('force', 'force'), - ) - - def run(self): - self.mkpath(self.install_dir) - for f in self.data_files: - if isinstance(f, str): - # it's a simple file, so copy it - f = convert_path(f) - if self.warn_dir: - self.warn("setup script did not provide a directory for " - "'%s' -- installing right in '%s'" % - (f, self.install_dir)) - (out, _) = self.copy_file(f, self.install_dir) - self.outfiles.append(out) - else: - # it's a tuple with path to install to and a list of files - dir = convert_path(f[0]) - if not os.path.isabs(dir): - dir = os.path.join(self.install_dir, dir) - elif self.root: - dir = change_root(self.root, dir) - self.mkpath(dir) - - if f[1] == []: - # If there are no files listed, the user must be - # trying to create an empty directory, so add the - # directory to the list of output files. - self.outfiles.append(dir) - else: - # Copy files, adding them to the list of output files. - for data in f[1]: - data = convert_path(data) - (out, _) = self.copy_file(data, dir) - self.outfiles.append(out) - - def get_inputs(self): - return self.data_files or [] - - def get_outputs(self): - return self.outfiles diff --git a/Lib/distutils/command/install_egg_info.py b/Lib/distutils/command/install_egg_info.py deleted file mode 100644 index 0a71b610005..00000000000 --- a/Lib/distutils/command/install_egg_info.py +++ /dev/null @@ -1,97 +0,0 @@ -"""distutils.command.install_egg_info - -Implements the Distutils 'install_egg_info' command, for installing -a package's PKG-INFO metadata.""" - - -from distutils.cmd import Command -from distutils import log, dir_util -import os, sys, re - -class install_egg_info(Command): - """Install an .egg-info file for the package""" - - description = "Install package's PKG-INFO metadata as an .egg-info file" - user_options = [ - ('install-dir=', 'd', "directory to install to"), - ('install-layout', None, "custom installation layout"), - ] - - def initialize_options(self): - self.install_dir = None - self.install_layout = None - self.prefix_option = None - - def finalize_options(self): - self.set_undefined_options('install_lib',('install_dir','install_dir')) - self.set_undefined_options('install',('install_layout','install_layout')) - self.set_undefined_options('install',('prefix_option','prefix_option')) - if self.install_layout: - if not self.install_layout.lower() in ['deb', 'unix']: - raise DistutilsOptionError( - "unknown value for --install-layout") - no_pyver = (self.install_layout.lower() == 'deb') - elif self.prefix_option: - no_pyver = False - else: - no_pyver = True - if no_pyver: - basename = "%s-%s.egg-info" % ( - to_filename(safe_name(self.distribution.get_name())), - to_filename(safe_version(self.distribution.get_version())) - ) - else: - basename = "%s-%s-py%d.%d.egg-info" % ( - to_filename(safe_name(self.distribution.get_name())), - to_filename(safe_version(self.distribution.get_version())), - *sys.version_info[:2] - ) - self.target = os.path.join(self.install_dir, basename) - self.outputs = [self.target] - - def run(self): - target = self.target - if os.path.isdir(target) and not os.path.islink(target): - dir_util.remove_tree(target, dry_run=self.dry_run) - elif os.path.exists(target): - self.execute(os.unlink,(self.target,),"Removing "+target) - elif not os.path.isdir(self.install_dir): - self.execute(os.makedirs, (self.install_dir,), - "Creating "+self.install_dir) - log.info("Writing %s", target) - if not self.dry_run: - with open(target, 'w', encoding='UTF-8') as f: - self.distribution.metadata.write_pkg_file(f) - - def get_outputs(self): - return self.outputs - - -# The following routines are taken from setuptools' pkg_resources module and -# can be replaced by importing them from pkg_resources once it is included -# in the stdlib. - -def safe_name(name): - """Convert an arbitrary string to a standard distribution name - - Any runs of non-alphanumeric/. characters are replaced with a single '-'. - """ - return re.sub('[^A-Za-z0-9.]+', '-', name) - - -def safe_version(version): - """Convert an arbitrary string to a standard version string - - Spaces become dots, and all other non-alphanumeric characters become - dashes, with runs of multiple dashes condensed to a single dash. - """ - version = version.replace(' ','.') - return re.sub('[^A-Za-z0-9.]+', '-', version) - - -def to_filename(name): - """Convert a project or version name to its filename-escaped form - - Any '-' characters are currently replaced with '_'. - """ - return name.replace('-','_') diff --git a/Lib/distutils/command/install_headers.py b/Lib/distutils/command/install_headers.py deleted file mode 100644 index 9bb0b18dc0d..00000000000 --- a/Lib/distutils/command/install_headers.py +++ /dev/null @@ -1,47 +0,0 @@ -"""distutils.command.install_headers - -Implements the Distutils 'install_headers' command, to install C/C++ header -files to the Python include directory.""" - -from distutils.core import Command - - -# XXX force is never used -class install_headers(Command): - - description = "install C/C++ header files" - - user_options = [('install-dir=', 'd', - "directory to install header files to"), - ('force', 'f', - "force installation (overwrite existing files)"), - ] - - boolean_options = ['force'] - - def initialize_options(self): - self.install_dir = None - self.force = 0 - self.outfiles = [] - - def finalize_options(self): - self.set_undefined_options('install', - ('install_headers', 'install_dir'), - ('force', 'force')) - - - def run(self): - headers = self.distribution.headers - if not headers: - return - - self.mkpath(self.install_dir) - for header in headers: - (out, _) = self.copy_file(header, self.install_dir) - self.outfiles.append(out) - - def get_inputs(self): - return self.distribution.headers or [] - - def get_outputs(self): - return self.outfiles diff --git a/Lib/distutils/command/install_lib.py b/Lib/distutils/command/install_lib.py deleted file mode 100644 index eef63626ff7..00000000000 --- a/Lib/distutils/command/install_lib.py +++ /dev/null @@ -1,221 +0,0 @@ -"""distutils.command.install_lib - -Implements the Distutils 'install_lib' command -(install all Python modules).""" - -import os -import importlib.util -import sys - -from distutils.core import Command -from distutils.errors import DistutilsOptionError - - -# Extension for Python source files. -PYTHON_SOURCE_EXTENSION = ".py" - -class install_lib(Command): - - description = "install all Python modules (extensions and pure Python)" - - # The byte-compilation options are a tad confusing. Here are the - # possible scenarios: - # 1) no compilation at all (--no-compile --no-optimize) - # 2) compile .pyc only (--compile --no-optimize; default) - # 3) compile .pyc and "opt-1" .pyc (--compile --optimize) - # 4) compile "opt-1" .pyc only (--no-compile --optimize) - # 5) compile .pyc and "opt-2" .pyc (--compile --optimize-more) - # 6) compile "opt-2" .pyc only (--no-compile --optimize-more) - # - # The UI for this is two options, 'compile' and 'optimize'. - # 'compile' is strictly boolean, and only decides whether to - # generate .pyc files. 'optimize' is three-way (0, 1, or 2), and - # decides both whether to generate .pyc files and what level of - # optimization to use. - - user_options = [ - ('install-dir=', 'd', "directory to install to"), - ('build-dir=','b', "build directory (where to install from)"), - ('force', 'f', "force installation (overwrite existing files)"), - ('compile', 'c', "compile .py to .pyc [default]"), - ('no-compile', None, "don't compile .py files"), - ('optimize=', 'O', - "also compile with optimization: -O1 for \"python -O\", " - "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), - ('skip-build', None, "skip the build steps"), - ] - - boolean_options = ['force', 'compile', 'skip-build'] - negative_opt = {'no-compile' : 'compile'} - - def initialize_options(self): - # let the 'install' command dictate our installation directory - self.install_dir = None - self.build_dir = None - self.force = 0 - self.compile = None - self.optimize = None - self.skip_build = None - self.multiarch = None # if we should rename the extensions - - def finalize_options(self): - # Get all the information we need to install pure Python modules - # from the umbrella 'install' command -- build (source) directory, - # install (target) directory, and whether to compile .py files. - self.set_undefined_options('install', - ('build_lib', 'build_dir'), - ('install_lib', 'install_dir'), - ('force', 'force'), - ('compile', 'compile'), - ('optimize', 'optimize'), - ('skip_build', 'skip_build'), - ('multiarch', 'multiarch'), - ) - - if self.compile is None: - self.compile = True - if self.optimize is None: - self.optimize = False - - if not isinstance(self.optimize, int): - try: - self.optimize = int(self.optimize) - if self.optimize not in (0, 1, 2): - raise AssertionError - except (ValueError, AssertionError): - raise DistutilsOptionError("optimize must be 0, 1, or 2") - - def run(self): - # Make sure we have built everything we need first - self.build() - - # Install everything: simply dump the entire contents of the build - # directory to the installation directory (that's the beauty of - # having a build directory!) - outfiles = self.install() - - # (Optionally) compile .py to .pyc - if outfiles is not None and self.distribution.has_pure_modules(): - self.byte_compile(outfiles) - - # -- Top-level worker functions ------------------------------------ - # (called from 'run()') - - def build(self): - if not self.skip_build: - if self.distribution.has_pure_modules(): - self.run_command('build_py') - if self.distribution.has_ext_modules(): - self.run_command('build_ext') - - def install(self): - if os.path.isdir(self.build_dir): - import distutils.dir_util - distutils.dir_util._multiarch = self.multiarch - outfiles = self.copy_tree(self.build_dir, self.install_dir) - else: - self.warn("'%s' does not exist -- no Python modules to install" % - self.build_dir) - return - return outfiles - - def byte_compile(self, files): - if sys.dont_write_bytecode: - self.warn('byte-compiling is disabled, skipping.') - return - - from distutils.util import byte_compile - - # Get the "--root" directory supplied to the "install" command, - # and use it as a prefix to strip off the purported filename - # encoded in bytecode files. This is far from complete, but it - # should at least generate usable bytecode in RPM distributions. - install_root = self.get_finalized_command('install').root - - if self.compile: - byte_compile(files, optimize=0, - force=self.force, prefix=install_root, - dry_run=self.dry_run) - if self.optimize > 0: - byte_compile(files, optimize=self.optimize, - force=self.force, prefix=install_root, - verbose=self.verbose, dry_run=self.dry_run) - - - # -- Utility methods ----------------------------------------------- - - def _mutate_outputs(self, has_any, build_cmd, cmd_option, output_dir): - if not has_any: - return [] - - build_cmd = self.get_finalized_command(build_cmd) - build_files = build_cmd.get_outputs() - build_dir = getattr(build_cmd, cmd_option) - - prefix_len = len(build_dir) + len(os.sep) - outputs = [] - for file in build_files: - outputs.append(os.path.join(output_dir, file[prefix_len:])) - - return outputs - - def _bytecode_filenames(self, py_filenames): - bytecode_files = [] - for py_file in py_filenames: - # Since build_py handles package data installation, the - # list of outputs can contain more than just .py files. - # Make sure we only report bytecode for the .py files. - ext = os.path.splitext(os.path.normcase(py_file))[1] - if ext != PYTHON_SOURCE_EXTENSION: - continue - if self.compile: - bytecode_files.append(importlib.util.cache_from_source( - py_file, optimization='')) - if self.optimize > 0: - bytecode_files.append(importlib.util.cache_from_source( - py_file, optimization=self.optimize)) - - return bytecode_files - - - # -- External interface -------------------------------------------- - # (called by outsiders) - - def get_outputs(self): - """Return the list of files that would be installed if this command - were actually run. Not affected by the "dry-run" flag or whether - modules have actually been built yet. - """ - pure_outputs = \ - self._mutate_outputs(self.distribution.has_pure_modules(), - 'build_py', 'build_lib', - self.install_dir) - if self.compile: - bytecode_outputs = self._bytecode_filenames(pure_outputs) - else: - bytecode_outputs = [] - - ext_outputs = \ - self._mutate_outputs(self.distribution.has_ext_modules(), - 'build_ext', 'build_lib', - self.install_dir) - - return pure_outputs + bytecode_outputs + ext_outputs - - def get_inputs(self): - """Get the list of files that are input to this command, ie. the - files that get installed as they are named in the build tree. - The files in this list correspond one-to-one to the output - filenames returned by 'get_outputs()'. - """ - inputs = [] - - if self.distribution.has_pure_modules(): - build_py = self.get_finalized_command('build_py') - inputs.extend(build_py.get_outputs()) - - if self.distribution.has_ext_modules(): - build_ext = self.get_finalized_command('build_ext') - inputs.extend(build_ext.get_outputs()) - - return inputs diff --git a/Lib/distutils/command/install_scripts.py b/Lib/distutils/command/install_scripts.py deleted file mode 100644 index 31a1130ee54..00000000000 --- a/Lib/distutils/command/install_scripts.py +++ /dev/null @@ -1,60 +0,0 @@ -"""distutils.command.install_scripts - -Implements the Distutils 'install_scripts' command, for installing -Python scripts.""" - -# contributed by Bastian Kleineidam - -import os -from distutils.core import Command -from distutils import log -from stat import ST_MODE - - -class install_scripts(Command): - - description = "install scripts (Python or otherwise)" - - user_options = [ - ('install-dir=', 'd', "directory to install scripts to"), - ('build-dir=','b', "build directory (where to install from)"), - ('force', 'f', "force installation (overwrite existing files)"), - ('skip-build', None, "skip the build steps"), - ] - - boolean_options = ['force', 'skip-build'] - - def initialize_options(self): - self.install_dir = None - self.force = 0 - self.build_dir = None - self.skip_build = None - - def finalize_options(self): - self.set_undefined_options('build', ('build_scripts', 'build_dir')) - self.set_undefined_options('install', - ('install_scripts', 'install_dir'), - ('force', 'force'), - ('skip_build', 'skip_build'), - ) - - def run(self): - if not self.skip_build: - self.run_command('build_scripts') - self.outfiles = self.copy_tree(self.build_dir, self.install_dir) - if os.name == 'posix': - # Set the executable bits (owner, group, and world) on - # all the scripts we just installed. - for file in self.get_outputs(): - if self.dry_run: - log.info("changing mode of %s", file) - else: - mode = ((os.stat(file)[ST_MODE]) | 0o555) & 0o7777 - log.info("changing mode of %s to %o", file, mode) - os.chmod(file, mode) - - def get_inputs(self): - return self.distribution.scripts or [] - - def get_outputs(self): - return self.outfiles or [] diff --git a/Lib/distutils/command/register.py b/Lib/distutils/command/register.py deleted file mode 100644 index 0fac94e9e54..00000000000 --- a/Lib/distutils/command/register.py +++ /dev/null @@ -1,304 +0,0 @@ -"""distutils.command.register - -Implements the Distutils 'register' command (register with the repository). -""" - -# created 2002/10/21, Richard Jones - -import getpass -import io -import urllib.parse, urllib.request -from warnings import warn - -from distutils.core import PyPIRCCommand -from distutils.errors import * -from distutils import log - -class register(PyPIRCCommand): - - description = ("register the distribution with the Python package index") - user_options = PyPIRCCommand.user_options + [ - ('list-classifiers', None, - 'list the valid Trove classifiers'), - ('strict', None , - 'Will stop the registering if the meta-data are not fully compliant') - ] - boolean_options = PyPIRCCommand.boolean_options + [ - 'verify', 'list-classifiers', 'strict'] - - sub_commands = [('check', lambda self: True)] - - def initialize_options(self): - PyPIRCCommand.initialize_options(self) - self.list_classifiers = 0 - self.strict = 0 - - def finalize_options(self): - PyPIRCCommand.finalize_options(self) - # setting options for the `check` subcommand - check_options = {'strict': ('register', self.strict), - 'restructuredtext': ('register', 1)} - self.distribution.command_options['check'] = check_options - - def run(self): - self.finalize_options() - self._set_config() - - # Run sub commands - for cmd_name in self.get_sub_commands(): - self.run_command(cmd_name) - - if self.dry_run: - self.verify_metadata() - elif self.list_classifiers: - self.classifiers() - else: - self.send_metadata() - - def check_metadata(self): - """Deprecated API.""" - warn("distutils.command.register.check_metadata is deprecated, \ - use the check command instead", PendingDeprecationWarning) - check = self.distribution.get_command_obj('check') - check.ensure_finalized() - check.strict = self.strict - check.restructuredtext = 1 - check.run() - - def _set_config(self): - ''' Reads the configuration file and set attributes. - ''' - config = self._read_pypirc() - if config != {}: - self.username = config['username'] - self.password = config['password'] - self.repository = config['repository'] - self.realm = config['realm'] - self.has_config = True - else: - if self.repository not in ('pypi', self.DEFAULT_REPOSITORY): - raise ValueError('%s not found in .pypirc' % self.repository) - if self.repository == 'pypi': - self.repository = self.DEFAULT_REPOSITORY - self.has_config = False - - def classifiers(self): - ''' Fetch the list of classifiers from the server. - ''' - url = self.repository+'?:action=list_classifiers' - response = urllib.request.urlopen(url) - log.info(self._read_pypi_response(response)) - - def verify_metadata(self): - ''' Send the metadata to the package index server to be checked. - ''' - # send the info to the server and report the result - (code, result) = self.post_to_server(self.build_post_data('verify')) - log.info('Server response (%s): %s', code, result) - - def send_metadata(self): - ''' Send the metadata to the package index server. - - Well, do the following: - 1. figure who the user is, and then - 2. send the data as a Basic auth'ed POST. - - First we try to read the username/password from $HOME/.pypirc, - which is a ConfigParser-formatted file with a section - [distutils] containing username and password entries (both - in clear text). Eg: - - [distutils] - index-servers = - pypi - - [pypi] - username: fred - password: sekrit - - Otherwise, to figure who the user is, we offer the user three - choices: - - 1. use existing login, - 2. register as a new user, or - 3. set the password to a random string and email the user. - - ''' - # see if we can short-cut and get the username/password from the - # config - if self.has_config: - choice = '1' - username = self.username - password = self.password - else: - choice = 'x' - username = password = '' - - # get the user's login info - choices = '1 2 3 4'.split() - while choice not in choices: - self.announce('''\ -We need to know who you are, so please choose either: - 1. use your existing login, - 2. register as a new user, - 3. have the server generate a new password for you (and email it to you), or - 4. quit -Your selection [default 1]: ''', log.INFO) - choice = input() - if not choice: - choice = '1' - elif choice not in choices: - print('Please choose one of the four options!') - - if choice == '1': - # get the username and password - while not username: - username = input('Username: ') - while not password: - password = getpass.getpass('Password: ') - - # set up the authentication - auth = urllib.request.HTTPPasswordMgr() - host = urllib.parse.urlparse(self.repository)[1] - auth.add_password(self.realm, host, username, password) - # send the info to the server and report the result - code, result = self.post_to_server(self.build_post_data('submit'), - auth) - self.announce('Server response (%s): %s' % (code, result), - log.INFO) - - # possibly save the login - if code == 200: - if self.has_config: - # sharing the password in the distribution instance - # so the upload command can reuse it - self.distribution.password = password - else: - self.announce(('I can store your PyPI login so future ' - 'submissions will be faster.'), log.INFO) - self.announce('(the login will be stored in %s)' % \ - self._get_rc_file(), log.INFO) - choice = 'X' - while choice.lower() not in 'yn': - choice = input('Save your login (y/N)?') - if not choice: - choice = 'n' - if choice.lower() == 'y': - self._store_pypirc(username, password) - - elif choice == '2': - data = {':action': 'user'} - data['name'] = data['password'] = data['email'] = '' - data['confirm'] = None - while not data['name']: - data['name'] = input('Username: ') - while data['password'] != data['confirm']: - while not data['password']: - data['password'] = getpass.getpass('Password: ') - while not data['confirm']: - data['confirm'] = getpass.getpass(' Confirm: ') - if data['password'] != data['confirm']: - data['password'] = '' - data['confirm'] = None - print("Password and confirm don't match!") - while not data['email']: - data['email'] = input(' EMail: ') - code, result = self.post_to_server(data) - if code != 200: - log.info('Server response (%s): %s', code, result) - else: - log.info('You will receive an email shortly.') - log.info(('Follow the instructions in it to ' - 'complete registration.')) - elif choice == '3': - data = {':action': 'password_reset'} - data['email'] = '' - while not data['email']: - data['email'] = input('Your email address: ') - code, result = self.post_to_server(data) - log.info('Server response (%s): %s', code, result) - - def build_post_data(self, action): - # figure the data to send - the metadata plus some additional - # information used by the package server - meta = self.distribution.metadata - data = { - ':action': action, - 'metadata_version' : '1.0', - 'name': meta.get_name(), - 'version': meta.get_version(), - 'summary': meta.get_description(), - 'home_page': meta.get_url(), - 'author': meta.get_contact(), - 'author_email': meta.get_contact_email(), - 'license': meta.get_licence(), - 'description': meta.get_long_description(), - 'keywords': meta.get_keywords(), - 'platform': meta.get_platforms(), - 'classifiers': meta.get_classifiers(), - 'download_url': meta.get_download_url(), - # PEP 314 - 'provides': meta.get_provides(), - 'requires': meta.get_requires(), - 'obsoletes': meta.get_obsoletes(), - } - if data['provides'] or data['requires'] or data['obsoletes']: - data['metadata_version'] = '1.1' - return data - - def post_to_server(self, data, auth=None): - ''' Post a query to the server, and return a string response. - ''' - if 'name' in data: - self.announce('Registering %s to %s' % (data['name'], - self.repository), - log.INFO) - # Build up the MIME payload for the urllib2 POST data - boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' - sep_boundary = '\n--' + boundary - end_boundary = sep_boundary + '--' - body = io.StringIO() - for key, value in data.items(): - # handle multiple entries for the same name - if type(value) not in (type([]), type( () )): - value = [value] - for value in value: - value = str(value) - body.write(sep_boundary) - body.write('\nContent-Disposition: form-data; name="%s"'%key) - body.write("\n\n") - body.write(value) - if value and value[-1] == '\r': - body.write('\n') # write an extra newline (lurve Macs) - body.write(end_boundary) - body.write("\n") - body = body.getvalue().encode("utf-8") - - # build the Request - headers = { - 'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'%boundary, - 'Content-length': str(len(body)) - } - req = urllib.request.Request(self.repository, body, headers) - - # handle HTTP and include the Basic Auth handler - opener = urllib.request.build_opener( - urllib.request.HTTPBasicAuthHandler(password_mgr=auth) - ) - data = '' - try: - result = opener.open(req) - except urllib.error.HTTPError as e: - if self.show_response: - data = e.fp.read() - result = e.code, e.msg - except urllib.error.URLError as e: - result = 500, str(e) - else: - if self.show_response: - data = self._read_pypi_response(result) - result = 200, 'OK' - if self.show_response: - msg = '\n'.join(('-' * 75, data, '-' * 75)) - self.announce(msg, log.INFO) - return result diff --git a/Lib/distutils/command/sdist.py b/Lib/distutils/command/sdist.py deleted file mode 100644 index 4fd1d4715de..00000000000 --- a/Lib/distutils/command/sdist.py +++ /dev/null @@ -1,456 +0,0 @@ -"""distutils.command.sdist - -Implements the Distutils 'sdist' command (create a source distribution).""" - -import os -import sys -from types import * -from glob import glob -from warnings import warn - -from distutils.core import Command -from distutils import dir_util, dep_util, file_util, archive_util -from distutils.text_file import TextFile -from distutils.errors import * -from distutils.filelist import FileList -from distutils import log -from distutils.util import convert_path - -def show_formats(): - """Print all possible values for the 'formats' option (used by - the "--help-formats" command-line option). - """ - from distutils.fancy_getopt import FancyGetopt - from distutils.archive_util import ARCHIVE_FORMATS - formats = [] - for format in ARCHIVE_FORMATS.keys(): - formats.append(("formats=" + format, None, - ARCHIVE_FORMATS[format][2])) - formats.sort() - FancyGetopt(formats).print_help( - "List of available source distribution formats:") - -class sdist(Command): - - description = "create a source distribution (tarball, zip file, etc.)" - - def checking_metadata(self): - """Callable used for the check sub-command. - - Placed here so user_options can view it""" - return self.metadata_check - - user_options = [ - ('template=', 't', - "name of manifest template file [default: MANIFEST.in]"), - ('manifest=', 'm', - "name of manifest file [default: MANIFEST]"), - ('use-defaults', None, - "include the default file set in the manifest " - "[default; disable with --no-defaults]"), - ('no-defaults', None, - "don't include the default file set"), - ('prune', None, - "specifically exclude files/directories that should not be " - "distributed (build tree, RCS/CVS dirs, etc.) " - "[default; disable with --no-prune]"), - ('no-prune', None, - "don't automatically exclude anything"), - ('manifest-only', 'o', - "just regenerate the manifest and then stop " - "(implies --force-manifest)"), - ('force-manifest', 'f', - "forcibly regenerate the manifest and carry on as usual. " - "Deprecated: now the manifest is always regenerated."), - ('formats=', None, - "formats for source distribution (comma-separated list)"), - ('keep-temp', 'k', - "keep the distribution tree around after creating " + - "archive file(s)"), - ('dist-dir=', 'd', - "directory to put the source distribution archive(s) in " - "[default: dist]"), - ('metadata-check', None, - "Ensure that all required elements of meta-data " - "are supplied. Warn if any missing. [default]"), - ('owner=', 'u', - "Owner name used when creating a tar file [default: current user]"), - ('group=', 'g', - "Group name used when creating a tar file [default: current group]"), - ] - - boolean_options = ['use-defaults', 'prune', - 'manifest-only', 'force-manifest', - 'keep-temp', 'metadata-check'] - - help_options = [ - ('help-formats', None, - "list available distribution formats", show_formats), - ] - - negative_opt = {'no-defaults': 'use-defaults', - 'no-prune': 'prune' } - - sub_commands = [('check', checking_metadata)] - - def initialize_options(self): - # 'template' and 'manifest' are, respectively, the names of - # the manifest template and manifest file. - self.template = None - self.manifest = None - - # 'use_defaults': if true, we will include the default file set - # in the manifest - self.use_defaults = 1 - self.prune = 1 - - self.manifest_only = 0 - self.force_manifest = 0 - - self.formats = ['gztar'] - self.keep_temp = 0 - self.dist_dir = None - - self.archive_files = None - self.metadata_check = 1 - self.owner = None - self.group = None - - def finalize_options(self): - if self.manifest is None: - self.manifest = "MANIFEST" - if self.template is None: - self.template = "MANIFEST.in" - - self.ensure_string_list('formats') - - bad_format = archive_util.check_archive_formats(self.formats) - if bad_format: - raise DistutilsOptionError( - "unknown archive format '%s'" % bad_format) - - if self.dist_dir is None: - self.dist_dir = "dist" - - def run(self): - # 'filelist' contains the list of files that will make up the - # manifest - self.filelist = FileList() - - # Run sub commands - for cmd_name in self.get_sub_commands(): - self.run_command(cmd_name) - - # Do whatever it takes to get the list of files to process - # (process the manifest template, read an existing manifest, - # whatever). File list is accumulated in 'self.filelist'. - self.get_file_list() - - # If user just wanted us to regenerate the manifest, stop now. - if self.manifest_only: - return - - # Otherwise, go ahead and create the source distribution tarball, - # or zipfile, or whatever. - self.make_distribution() - - def check_metadata(self): - """Deprecated API.""" - warn("distutils.command.sdist.check_metadata is deprecated, \ - use the check command instead", PendingDeprecationWarning) - check = self.distribution.get_command_obj('check') - check.ensure_finalized() - check.run() - - def get_file_list(self): - """Figure out the list of files to include in the source - distribution, and put it in 'self.filelist'. This might involve - reading the manifest template (and writing the manifest), or just - reading the manifest, or just using the default file set -- it all - depends on the user's options. - """ - # new behavior when using a template: - # the file list is recalculated every time because - # even if MANIFEST.in or setup.py are not changed - # the user might have added some files in the tree that - # need to be included. - # - # This makes --force the default and only behavior with templates. - template_exists = os.path.isfile(self.template) - if not template_exists and self._manifest_is_not_generated(): - self.read_manifest() - self.filelist.sort() - self.filelist.remove_duplicates() - return - - if not template_exists: - self.warn(("manifest template '%s' does not exist " + - "(using default file list)") % - self.template) - self.filelist.findall() - - if self.use_defaults: - self.add_defaults() - - if template_exists: - self.read_template() - - if self.prune: - self.prune_file_list() - - self.filelist.sort() - self.filelist.remove_duplicates() - self.write_manifest() - - def add_defaults(self): - """Add all the default files to self.filelist: - - README or README.txt - - setup.py - - test/test*.py - - all pure Python modules mentioned in setup script - - all files pointed by package_data (build_py) - - all files defined in data_files. - - all files defined as scripts. - - all C sources listed as part of extensions or C libraries - in the setup script (doesn't catch C headers!) - Warns if (README or README.txt) or setup.py are missing; everything - else is optional. - """ - standards = [('README', 'README.txt'), self.distribution.script_name] - for fn in standards: - if isinstance(fn, tuple): - alts = fn - got_it = False - for fn in alts: - if os.path.exists(fn): - got_it = True - self.filelist.append(fn) - break - - if not got_it: - self.warn("standard file not found: should have one of " + - ', '.join(alts)) - else: - if os.path.exists(fn): - self.filelist.append(fn) - else: - self.warn("standard file '%s' not found" % fn) - - optional = ['test/test*.py', 'setup.cfg'] - for pattern in optional: - files = filter(os.path.isfile, glob(pattern)) - self.filelist.extend(files) - - # build_py is used to get: - # - python modules - # - files defined in package_data - build_py = self.get_finalized_command('build_py') - - # getting python files - if self.distribution.has_pure_modules(): - self.filelist.extend(build_py.get_source_files()) - - # getting package_data files - # (computed in build_py.data_files by build_py.finalize_options) - for pkg, src_dir, build_dir, filenames in build_py.data_files: - for filename in filenames: - self.filelist.append(os.path.join(src_dir, filename)) - - # getting distribution.data_files - if self.distribution.has_data_files(): - for item in self.distribution.data_files: - if isinstance(item, str): # plain file - item = convert_path(item) - if os.path.isfile(item): - self.filelist.append(item) - else: # a (dirname, filenames) tuple - dirname, filenames = item - for f in filenames: - f = convert_path(f) - if os.path.isfile(f): - self.filelist.append(f) - - if self.distribution.has_ext_modules(): - build_ext = self.get_finalized_command('build_ext') - self.filelist.extend(build_ext.get_source_files()) - - if self.distribution.has_c_libraries(): - build_clib = self.get_finalized_command('build_clib') - self.filelist.extend(build_clib.get_source_files()) - - if self.distribution.has_scripts(): - build_scripts = self.get_finalized_command('build_scripts') - self.filelist.extend(build_scripts.get_source_files()) - - def read_template(self): - """Read and parse manifest template file named by self.template. - - (usually "MANIFEST.in") The parsing and processing is done by - 'self.filelist', which updates itself accordingly. - """ - log.info("reading manifest template '%s'", self.template) - template = TextFile(self.template, strip_comments=1, skip_blanks=1, - join_lines=1, lstrip_ws=1, rstrip_ws=1, - collapse_join=1) - - try: - while True: - line = template.readline() - if line is None: # end of file - break - - try: - self.filelist.process_template_line(line) - # the call above can raise a DistutilsTemplateError for - # malformed lines, or a ValueError from the lower-level - # convert_path function - except (DistutilsTemplateError, ValueError) as msg: - self.warn("%s, line %d: %s" % (template.filename, - template.current_line, - msg)) - finally: - template.close() - - def prune_file_list(self): - """Prune off branches that might slip into the file list as created - by 'read_template()', but really don't belong there: - * the build tree (typically "build") - * the release tree itself (only an issue if we ran "sdist" - previously with --keep-temp, or it aborted) - * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories - """ - build = self.get_finalized_command('build') - base_dir = self.distribution.get_fullname() - - self.filelist.exclude_pattern(None, prefix=build.build_base) - self.filelist.exclude_pattern(None, prefix=base_dir) - - if sys.platform == 'win32': - seps = r'/|\\' - else: - seps = '/' - - vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr', - '_darcs'] - vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps) - self.filelist.exclude_pattern(vcs_ptrn, is_regex=1) - - def write_manifest(self): - """Write the file list in 'self.filelist' (presumably as filled in - by 'add_defaults()' and 'read_template()') to the manifest file - named by 'self.manifest'. - """ - if self._manifest_is_not_generated(): - log.info("not writing to manually maintained " - "manifest file '%s'" % self.manifest) - return - - content = self.filelist.files[:] - content.insert(0, '# file GENERATED by distutils, do NOT edit') - self.execute(file_util.write_file, (self.manifest, content), - "writing manifest file '%s'" % self.manifest) - - def _manifest_is_not_generated(self): - # check for special comment used in 3.1.3 and higher - if not os.path.isfile(self.manifest): - return False - - fp = open(self.manifest) - try: - first_line = fp.readline() - finally: - fp.close() - return first_line != '# file GENERATED by distutils, do NOT edit\n' - - def read_manifest(self): - """Read the manifest file (named by 'self.manifest') and use it to - fill in 'self.filelist', the list of files to include in the source - distribution. - """ - log.info("reading manifest file '%s'", self.manifest) - manifest = open(self.manifest) - for line in manifest: - # ignore comments and blank lines - line = line.strip() - if line.startswith('#') or not line: - continue - self.filelist.append(line) - manifest.close() - - def make_release_tree(self, base_dir, files): - """Create the directory tree that will become the source - distribution archive. All directories implied by the filenames in - 'files' are created under 'base_dir', and then we hard link or copy - (if hard linking is unavailable) those files into place. - Essentially, this duplicates the developer's source tree, but in a - directory named after the distribution, containing only the files - to be distributed. - """ - # Create all the directories under 'base_dir' necessary to - # put 'files' there; the 'mkpath()' is just so we don't die - # if the manifest happens to be empty. - self.mkpath(base_dir) - dir_util.create_tree(base_dir, files, dry_run=self.dry_run) - - # And walk over the list of files, either making a hard link (if - # os.link exists) to each one that doesn't already exist in its - # corresponding location under 'base_dir', or copying each file - # that's out-of-date in 'base_dir'. (Usually, all files will be - # out-of-date, because by default we blow away 'base_dir' when - # we're done making the distribution archives.) - - if hasattr(os, 'link'): # can make hard links on this system - link = 'hard' - msg = "making hard links in %s..." % base_dir - else: # nope, have to copy - link = None - msg = "copying files to %s..." % base_dir - - if not files: - log.warn("no files to distribute -- empty manifest?") - else: - log.info(msg) - for file in files: - if not os.path.isfile(file): - log.warn("'%s' not a regular file -- skipping", file) - else: - dest = os.path.join(base_dir, file) - self.copy_file(file, dest, link=link) - - self.distribution.metadata.write_pkg_info(base_dir) - - def make_distribution(self): - """Create the source distribution(s). First, we create the release - tree with 'make_release_tree()'; then, we create all required - archive files (according to 'self.formats') from the release tree. - Finally, we clean up by blowing away the release tree (unless - 'self.keep_temp' is true). The list of archive files created is - stored so it can be retrieved later by 'get_archive_files()'. - """ - # Don't warn about missing meta-data here -- should be (and is!) - # done elsewhere. - base_dir = self.distribution.get_fullname() - base_name = os.path.join(self.dist_dir, base_dir) - - self.make_release_tree(base_dir, self.filelist.files) - archive_files = [] # remember names of files we create - # tar archive must be created last to avoid overwrite and remove - if 'tar' in self.formats: - self.formats.append(self.formats.pop(self.formats.index('tar'))) - - for fmt in self.formats: - file = self.make_archive(base_name, fmt, base_dir=base_dir, - owner=self.owner, group=self.group) - archive_files.append(file) - self.distribution.dist_files.append(('sdist', '', file)) - - self.archive_files = archive_files - - if not self.keep_temp: - dir_util.remove_tree(base_dir, dry_run=self.dry_run) - - def get_archive_files(self): - """Return the list of archive files created when the command - was run, or None if the command hasn't run yet. - """ - return self.archive_files diff --git a/Lib/distutils/command/upload.py b/Lib/distutils/command/upload.py deleted file mode 100644 index 32dda359bad..00000000000 --- a/Lib/distutils/command/upload.py +++ /dev/null @@ -1,200 +0,0 @@ -""" -distutils.command.upload - -Implements the Distutils 'upload' subcommand (upload package to a package -index). -""" - -import os -import io -import platform -import hashlib -from base64 import standard_b64encode -from urllib.request import urlopen, Request, HTTPError -from urllib.parse import urlparse -from distutils.errors import DistutilsError, DistutilsOptionError -from distutils.core import PyPIRCCommand -from distutils.spawn import spawn -from distutils import log - -class upload(PyPIRCCommand): - - description = "upload binary package to PyPI" - - user_options = PyPIRCCommand.user_options + [ - ('sign', 's', - 'sign files to upload using gpg'), - ('identity=', 'i', 'GPG identity used to sign files'), - ] - - boolean_options = PyPIRCCommand.boolean_options + ['sign'] - - def initialize_options(self): - PyPIRCCommand.initialize_options(self) - self.username = '' - self.password = '' - self.show_response = 0 - self.sign = False - self.identity = None - - def finalize_options(self): - PyPIRCCommand.finalize_options(self) - if self.identity and not self.sign: - raise DistutilsOptionError( - "Must use --sign for --identity to have meaning" - ) - config = self._read_pypirc() - if config != {}: - self.username = config['username'] - self.password = config['password'] - self.repository = config['repository'] - self.realm = config['realm'] - - # getting the password from the distribution - # if previously set by the register command - if not self.password and self.distribution.password: - self.password = self.distribution.password - - def run(self): - if not self.distribution.dist_files: - msg = ("Must create and upload files in one command " - "(e.g. setup.py sdist upload)") - raise DistutilsOptionError(msg) - for command, pyversion, filename in self.distribution.dist_files: - self.upload_file(command, pyversion, filename) - - def upload_file(self, command, pyversion, filename): - # Makes sure the repository URL is compliant - schema, netloc, url, params, query, fragments = \ - urlparse(self.repository) - if params or query or fragments: - raise AssertionError("Incompatible url %s" % self.repository) - - if schema not in ('http', 'https'): - raise AssertionError("unsupported schema " + schema) - - # Sign if requested - if self.sign: - gpg_args = ["gpg", "--detach-sign", "-a", filename] - if self.identity: - gpg_args[2:2] = ["--local-user", self.identity] - spawn(gpg_args, - dry_run=self.dry_run) - - # Fill in the data - send all the meta-data in case we need to - # register a new release - f = open(filename,'rb') - try: - content = f.read() - finally: - f.close() - meta = self.distribution.metadata - data = { - # action - ':action': 'file_upload', - 'protocol_version': '1', - - # identify release - 'name': meta.get_name(), - 'version': meta.get_version(), - - # file content - 'content': (os.path.basename(filename),content), - 'filetype': command, - 'pyversion': pyversion, - 'md5_digest': hashlib.md5(content).hexdigest(), - - # additional meta-data - 'metadata_version': '1.0', - 'summary': meta.get_description(), - 'home_page': meta.get_url(), - 'author': meta.get_contact(), - 'author_email': meta.get_contact_email(), - 'license': meta.get_licence(), - 'description': meta.get_long_description(), - 'keywords': meta.get_keywords(), - 'platform': meta.get_platforms(), - 'classifiers': meta.get_classifiers(), - 'download_url': meta.get_download_url(), - # PEP 314 - 'provides': meta.get_provides(), - 'requires': meta.get_requires(), - 'obsoletes': meta.get_obsoletes(), - } - comment = '' - if command == 'bdist_rpm': - dist, version, id = platform.dist() - if dist: - comment = 'built for %s %s' % (dist, version) - elif command == 'bdist_dumb': - comment = 'built for %s' % platform.platform(terse=1) - data['comment'] = comment - - if self.sign: - data['gpg_signature'] = (os.path.basename(filename) + ".asc", - open(filename+".asc", "rb").read()) - - # set up the authentication - user_pass = (self.username + ":" + self.password).encode('ascii') - # The exact encoding of the authentication string is debated. - # Anyway PyPI only accepts ascii for both username or password. - auth = "Basic " + standard_b64encode(user_pass).decode('ascii') - - # Build up the MIME payload for the POST data - boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' - sep_boundary = b'\r\n--' + boundary.encode('ascii') - end_boundary = sep_boundary + b'--\r\n' - body = io.BytesIO() - for key, value in data.items(): - title = '\r\nContent-Disposition: form-data; name="%s"' % key - # handle multiple entries for the same name - if not isinstance(value, list): - value = [value] - for value in value: - if type(value) is tuple: - title += '; filename="%s"' % value[0] - value = value[1] - else: - value = str(value).encode('utf-8') - body.write(sep_boundary) - body.write(title.encode('utf-8')) - body.write(b"\r\n\r\n") - body.write(value) - body.write(end_boundary) - body = body.getvalue() - - msg = "Submitting %s to %s" % (filename, self.repository) - self.announce(msg, log.INFO) - - # build the Request - headers = { - 'Content-type': 'multipart/form-data; boundary=%s' % boundary, - 'Content-length': str(len(body)), - 'Authorization': auth, - } - - request = Request(self.repository, data=body, - headers=headers) - # send the data - try: - result = urlopen(request) - status = result.getcode() - reason = result.msg - except HTTPError as e: - status = e.code - reason = e.msg - except OSError as e: - self.announce(str(e), log.ERROR) - raise - - if status == 200: - self.announce('Server response (%s): %s' % (status, reason), - log.INFO) - if self.show_response: - text = self._read_pypi_response(result) - msg = '\n'.join(('-' * 75, text, '-' * 75)) - self.announce(msg, log.INFO) - else: - msg = 'Upload failed (%s): %s' % (status, reason) - self.announce(msg, log.ERROR) - raise DistutilsError(msg) diff --git a/Lib/distutils/config.py b/Lib/distutils/config.py deleted file mode 100644 index bf8d8dd2f5a..00000000000 --- a/Lib/distutils/config.py +++ /dev/null @@ -1,131 +0,0 @@ -"""distutils.pypirc - -Provides the PyPIRCCommand class, the base class for the command classes -that uses .pypirc in the distutils.command package. -""" -import os -from configparser import RawConfigParser - -from distutils.cmd import Command - -DEFAULT_PYPIRC = """\ -[distutils] -index-servers = - pypi - -[pypi] -username:%s -password:%s -""" - -class PyPIRCCommand(Command): - """Base command that knows how to handle the .pypirc file - """ - DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/' - DEFAULT_REALM = 'pypi' - repository = None - realm = None - - user_options = [ - ('repository=', 'r', - "url of repository [default: %s]" % \ - DEFAULT_REPOSITORY), - ('show-response', None, - 'display full response text from server')] - - boolean_options = ['show-response'] - - def _get_rc_file(self): - """Returns rc file path.""" - return os.path.join(os.path.expanduser('~'), '.pypirc') - - def _store_pypirc(self, username, password): - """Creates a default .pypirc file.""" - rc = self._get_rc_file() - with os.fdopen(os.open(rc, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as f: - f.write(DEFAULT_PYPIRC % (username, password)) - - def _read_pypirc(self): - """Reads the .pypirc file.""" - rc = self._get_rc_file() - if os.path.exists(rc): - self.announce('Using PyPI login from %s' % rc) - repository = self.repository or self.DEFAULT_REPOSITORY - realm = self.realm or self.DEFAULT_REALM - - config = RawConfigParser() - config.read(rc) - sections = config.sections() - if 'distutils' in sections: - # let's get the list of servers - index_servers = config.get('distutils', 'index-servers') - _servers = [server.strip() for server in - index_servers.split('\n') - if server.strip() != ''] - if _servers == []: - # nothing set, let's try to get the default pypi - if 'pypi' in sections: - _servers = ['pypi'] - else: - # the file is not properly defined, returning - # an empty dict - return {} - for server in _servers: - current = {'server': server} - current['username'] = config.get(server, 'username') - - # optional params - for key, default in (('repository', - self.DEFAULT_REPOSITORY), - ('realm', self.DEFAULT_REALM), - ('password', None)): - if config.has_option(server, key): - current[key] = config.get(server, key) - else: - current[key] = default - - # work around people having "repository" for the "pypi" - # section of their config set to the HTTP (rather than - # HTTPS) URL - if (server == 'pypi' and - repository in (self.DEFAULT_REPOSITORY, 'pypi')): - current['repository'] = self.DEFAULT_REPOSITORY - return current - - if (current['server'] == repository or - current['repository'] == repository): - return current - elif 'server-login' in sections: - # old format - server = 'server-login' - if config.has_option(server, 'repository'): - repository = config.get(server, 'repository') - else: - repository = self.DEFAULT_REPOSITORY - return {'username': config.get(server, 'username'), - 'password': config.get(server, 'password'), - 'repository': repository, - 'server': server, - 'realm': self.DEFAULT_REALM} - - return {} - - def _read_pypi_response(self, response): - """Read and decode a PyPI HTTP response.""" - import cgi - content_type = response.getheader('content-type', 'text/plain') - encoding = cgi.parse_header(content_type)[1].get('charset', 'ascii') - return response.read().decode(encoding) - - def initialize_options(self): - """Initialize options.""" - self.repository = None - self.realm = None - self.show_response = 0 - - def finalize_options(self): - """Finalizes options.""" - if self.repository is None: - self.repository = self.DEFAULT_REPOSITORY - if self.realm is None: - self.realm = self.DEFAULT_REALM diff --git a/Lib/distutils/core.py b/Lib/distutils/core.py deleted file mode 100644 index d603d4a45a7..00000000000 --- a/Lib/distutils/core.py +++ /dev/null @@ -1,234 +0,0 @@ -"""distutils.core - -The only module that needs to be imported to use the Distutils; provides -the 'setup' function (which is to be called from the setup script). Also -indirectly provides the Distribution and Command classes, although they are -really defined in distutils.dist and distutils.cmd. -""" - -import os -import sys - -from distutils.debug import DEBUG -from distutils.errors import * - -# Mainly import these so setup scripts can "from distutils.core import" them. -from distutils.dist import Distribution -from distutils.cmd import Command -from distutils.config import PyPIRCCommand -from distutils.extension import Extension - -# This is a barebones help message generated displayed when the user -# runs the setup script with no arguments at all. More useful help -# is generated with various --help options: global help, list commands, -# and per-command help. -USAGE = """\ -usage: %(script)s [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] - or: %(script)s --help [cmd1 cmd2 ...] - or: %(script)s --help-commands - or: %(script)s cmd --help -""" - -def gen_usage (script_name): - script = os.path.basename(script_name) - return USAGE % vars() - - -# Some mild magic to control the behaviour of 'setup()' from 'run_setup()'. -_setup_stop_after = None -_setup_distribution = None - -# Legal keyword arguments for the setup() function -setup_keywords = ('distclass', 'script_name', 'script_args', 'options', - 'name', 'version', 'author', 'author_email', - 'maintainer', 'maintainer_email', 'url', 'license', - 'description', 'long_description', 'keywords', - 'platforms', 'classifiers', 'download_url', - 'requires', 'provides', 'obsoletes', - ) - -# Legal keyword arguments for the Extension constructor -extension_keywords = ('name', 'sources', 'include_dirs', - 'define_macros', 'undef_macros', - 'library_dirs', 'libraries', 'runtime_library_dirs', - 'extra_objects', 'extra_compile_args', 'extra_link_args', - 'swig_opts', 'export_symbols', 'depends', 'language') - -def setup (**attrs): - """The gateway to the Distutils: do everything your setup script needs - to do, in a highly flexible and user-driven way. Briefly: create a - Distribution instance; find and parse config files; parse the command - line; run each Distutils command found there, customized by the options - supplied to 'setup()' (as keyword arguments), in config files, and on - the command line. - - The Distribution instance might be an instance of a class supplied via - the 'distclass' keyword argument to 'setup'; if no such class is - supplied, then the Distribution class (in dist.py) is instantiated. - All other arguments to 'setup' (except for 'cmdclass') are used to set - attributes of the Distribution instance. - - The 'cmdclass' argument, if supplied, is a dictionary mapping command - names to command classes. Each command encountered on the command line - will be turned into a command class, which is in turn instantiated; any - class found in 'cmdclass' is used in place of the default, which is - (for command 'foo_bar') class 'foo_bar' in module - 'distutils.command.foo_bar'. The command class must provide a - 'user_options' attribute which is a list of option specifiers for - 'distutils.fancy_getopt'. Any command-line options between the current - and the next command are used to set attributes of the current command - object. - - When the entire command-line has been successfully parsed, calls the - 'run()' method on each command object in turn. This method will be - driven entirely by the Distribution object (which each command object - has a reference to, thanks to its constructor), and the - command-specific options that became attributes of each command - object. - """ - - global _setup_stop_after, _setup_distribution - - # Determine the distribution class -- either caller-supplied or - # our Distribution (see below). - klass = attrs.get('distclass') - if klass: - del attrs['distclass'] - else: - klass = Distribution - - if 'script_name' not in attrs: - attrs['script_name'] = os.path.basename(sys.argv[0]) - if 'script_args' not in attrs: - attrs['script_args'] = sys.argv[1:] - - # Create the Distribution instance, using the remaining arguments - # (ie. everything except distclass) to initialize it - try: - _setup_distribution = dist = klass(attrs) - except DistutilsSetupError as msg: - if 'name' not in attrs: - raise SystemExit("error in setup command: %s" % msg) - else: - raise SystemExit("error in %s setup command: %s" % \ - (attrs['name'], msg)) - - if _setup_stop_after == "init": - return dist - - # Find and parse the config file(s): they will override options from - # the setup script, but be overridden by the command line. - dist.parse_config_files() - - if DEBUG: - print("options (after parsing config files):") - dist.dump_option_dicts() - - if _setup_stop_after == "config": - return dist - - # Parse the command line and override config files; any - # command-line errors are the end user's fault, so turn them into - # SystemExit to suppress tracebacks. - try: - ok = dist.parse_command_line() - except DistutilsArgError as msg: - raise SystemExit(gen_usage(dist.script_name) + "\nerror: %s" % msg) - - if DEBUG: - print("options (after parsing command line):") - dist.dump_option_dicts() - - if _setup_stop_after == "commandline": - return dist - - # And finally, run all the commands found on the command line. - if ok: - try: - dist.run_commands() - except KeyboardInterrupt: - raise SystemExit("interrupted") - except OSError as exc: - if DEBUG: - sys.stderr.write("error: %s\n" % (exc,)) - raise - else: - raise SystemExit("error: %s" % (exc,)) - - except (DistutilsError, - CCompilerError) as msg: - if DEBUG: - raise - else: - raise SystemExit("error: " + str(msg)) - - return dist - -# setup () - - -def run_setup (script_name, script_args=None, stop_after="run"): - """Run a setup script in a somewhat controlled environment, and - return the Distribution instance that drives things. This is useful - if you need to find out the distribution meta-data (passed as - keyword args from 'script' to 'setup()', or the contents of the - config files or command-line. - - 'script_name' is a file that will be read and run with 'exec()'; - 'sys.argv[0]' will be replaced with 'script' for the duration of the - call. 'script_args' is a list of strings; if supplied, - 'sys.argv[1:]' will be replaced by 'script_args' for the duration of - the call. - - 'stop_after' tells 'setup()' when to stop processing; possible - values: - init - stop after the Distribution instance has been created and - populated with the keyword arguments to 'setup()' - config - stop after config files have been parsed (and their data - stored in the Distribution instance) - commandline - stop after the command-line ('sys.argv[1:]' or 'script_args') - have been parsed (and the data stored in the Distribution) - run [default] - stop after all commands have been run (the same as if 'setup()' - had been called in the usual way - - Returns the Distribution instance, which provides all information - used to drive the Distutils. - """ - if stop_after not in ('init', 'config', 'commandline', 'run'): - raise ValueError("invalid value for 'stop_after': %r" % (stop_after,)) - - global _setup_stop_after, _setup_distribution - _setup_stop_after = stop_after - - save_argv = sys.argv.copy() - g = {'__file__': script_name} - try: - try: - sys.argv[0] = script_name - if script_args is not None: - sys.argv[1:] = script_args - with open(script_name, 'rb') as f: - exec(f.read(), g) - finally: - sys.argv = save_argv - _setup_stop_after = None - except SystemExit: - # Hmm, should we do something if exiting with a non-zero code - # (ie. error)? - pass - - if _setup_distribution is None: - raise RuntimeError(("'distutils.core.setup()' was never called -- " - "perhaps '%s' is not a Distutils setup script?") % \ - script_name) - - # I wonder if the setup script's namespace -- g and l -- would be of - # any interest to callers? - #print "_setup_distribution:", _setup_distribution - return _setup_distribution - -# run_setup () diff --git a/Lib/distutils/cygwinccompiler.py b/Lib/distutils/cygwinccompiler.py deleted file mode 100644 index 1c369903477..00000000000 --- a/Lib/distutils/cygwinccompiler.py +++ /dev/null @@ -1,405 +0,0 @@ -"""distutils.cygwinccompiler - -Provides the CygwinCCompiler class, a subclass of UnixCCompiler that -handles the Cygwin port of the GNU C compiler to Windows. It also contains -the Mingw32CCompiler class which handles the mingw32 port of GCC (same as -cygwin in no-cygwin mode). -""" - -# problems: -# -# * if you use a msvc compiled python version (1.5.2) -# 1. you have to insert a __GNUC__ section in its config.h -# 2. you have to generate an import library for its dll -# - create a def-file for python??.dll -# - create an import library using -# dlltool --dllname python15.dll --def python15.def \ -# --output-lib libpython15.a -# -# see also http://starship.python.net/crew/kernr/mingw32/Notes.html -# -# * We put export_symbols in a def-file, and don't use -# --export-all-symbols because it doesn't worked reliable in some -# tested configurations. And because other windows compilers also -# need their symbols specified this no serious problem. -# -# tested configurations: -# -# * cygwin gcc 2.91.57/ld 2.9.4/dllwrap 0.2.4 works -# (after patching python's config.h and for C++ some other include files) -# see also http://starship.python.net/crew/kernr/mingw32/Notes.html -# * mingw32 gcc 2.95.2/ld 2.9.4/dllwrap 0.2.4 works -# (ld doesn't support -shared, so we use dllwrap) -# * cygwin gcc 2.95.2/ld 2.10.90/dllwrap 2.10.90 works now -# - its dllwrap doesn't work, there is a bug in binutils 2.10.90 -# see also http://sources.redhat.com/ml/cygwin/2000-06/msg01274.html -# - using gcc -mdll instead dllwrap doesn't work without -static because -# it tries to link against dlls instead their import libraries. (If -# it finds the dll first.) -# By specifying -static we force ld to link against the import libraries, -# this is windows standard and there are normally not the necessary symbols -# in the dlls. -# *** only the version of June 2000 shows these problems -# * cygwin gcc 3.2/ld 2.13.90 works -# (ld supports -shared) -# * mingw gcc 3.2/ld 2.13 works -# (ld supports -shared) - -import os -import sys -import copy -from subprocess import Popen, PIPE, check_output -import re - -from distutils.ccompiler import gen_preprocess_options, gen_lib_options -from distutils.unixccompiler import UnixCCompiler -from distutils.file_util import write_file -from distutils.errors import (DistutilsExecError, CCompilerError, - CompileError, UnknownFileError) -from distutils import log -from distutils.version import LooseVersion -from distutils.spawn import find_executable - -def get_msvcr(): - """Include the appropriate MSVC runtime library if Python was built - with MSVC 7.0 or later. - """ - msc_pos = sys.version.find('MSC v.') - if msc_pos != -1: - msc_ver = sys.version[msc_pos+6:msc_pos+10] - if msc_ver == '1300': - # MSVC 7.0 - return ['msvcr70'] - elif msc_ver == '1310': - # MSVC 7.1 - return ['msvcr71'] - elif msc_ver == '1400': - # VS2005 / MSVC 8.0 - return ['msvcr80'] - elif msc_ver == '1500': - # VS2008 / MSVC 9.0 - return ['msvcr90'] - elif msc_ver == '1600': - # VS2010 / MSVC 10.0 - return ['msvcr100'] - else: - raise ValueError("Unknown MS Compiler version %s " % msc_ver) - - -class CygwinCCompiler(UnixCCompiler): - """ Handles the Cygwin port of the GNU C compiler to Windows. - """ - compiler_type = 'cygwin' - obj_extension = ".o" - static_lib_extension = ".a" - shared_lib_extension = ".dll" - static_lib_format = "lib%s%s" - shared_lib_format = "%s%s" - exe_extension = ".exe" - - def __init__(self, verbose=0, dry_run=0, force=0): - - UnixCCompiler.__init__(self, verbose, dry_run, force) - - status, details = check_config_h() - self.debug_print("Python's GCC status: %s (details: %s)" % - (status, details)) - if status is not CONFIG_H_OK: - self.warn( - "Python's pyconfig.h doesn't seem to support your compiler. " - "Reason: %s. " - "Compiling may fail because of undefined preprocessor macros." - % details) - - self.gcc_version, self.ld_version, self.dllwrap_version = \ - get_versions() - self.debug_print(self.compiler_type + ": gcc %s, ld %s, dllwrap %s\n" % - (self.gcc_version, - self.ld_version, - self.dllwrap_version) ) - - # ld_version >= "2.10.90" and < "2.13" should also be able to use - # gcc -mdll instead of dllwrap - # Older dllwraps had own version numbers, newer ones use the - # same as the rest of binutils ( also ld ) - # dllwrap 2.10.90 is buggy - if self.ld_version >= "2.10.90": - self.linker_dll = "gcc" - else: - self.linker_dll = "dllwrap" - - # ld_version >= "2.13" support -shared so use it instead of - # -mdll -static - if self.ld_version >= "2.13": - shared_option = "-shared" - else: - shared_option = "-mdll -static" - - # Hard-code GCC because that's what this is all about. - # XXX optimization, warnings etc. should be customizable. - self.set_executables(compiler='gcc -mcygwin -O -Wall', - compiler_so='gcc -mcygwin -mdll -O -Wall', - compiler_cxx='g++ -mcygwin -O -Wall', - linker_exe='gcc -mcygwin', - linker_so=('%s -mcygwin %s' % - (self.linker_dll, shared_option))) - - # cygwin and mingw32 need different sets of libraries - if self.gcc_version == "2.91.57": - # cygwin shouldn't need msvcrt, but without the dlls will crash - # (gcc version 2.91.57) -- perhaps something about initialization - self.dll_libraries=["msvcrt"] - self.warn( - "Consider upgrading to a newer version of gcc") - else: - # Include the appropriate MSVC runtime library if Python was built - # with MSVC 7.0 or later. - self.dll_libraries = get_msvcr() - - def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): - """Compiles the source by spawning GCC and windres if needed.""" - if ext == '.rc' or ext == '.res': - # gcc needs '.res' and '.rc' compiled to object files !!! - try: - self.spawn(["windres", "-i", src, "-o", obj]) - except DistutilsExecError as msg: - raise CompileError(msg) - else: # for other files use the C-compiler - try: - self.spawn(self.compiler_so + cc_args + [src, '-o', obj] + - extra_postargs) - except DistutilsExecError as msg: - raise CompileError(msg) - - def link(self, target_desc, objects, output_filename, output_dir=None, - libraries=None, library_dirs=None, runtime_library_dirs=None, - export_symbols=None, debug=0, extra_preargs=None, - extra_postargs=None, build_temp=None, target_lang=None): - """Link the objects.""" - # use separate copies, so we can modify the lists - extra_preargs = copy.copy(extra_preargs or []) - libraries = copy.copy(libraries or []) - objects = copy.copy(objects or []) - - # Additional libraries - libraries.extend(self.dll_libraries) - - # handle export symbols by creating a def-file - # with executables this only works with gcc/ld as linker - if ((export_symbols is not None) and - (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")): - # (The linker doesn't do anything if output is up-to-date. - # So it would probably better to check if we really need this, - # but for this we had to insert some unchanged parts of - # UnixCCompiler, and this is not what we want.) - - # we want to put some files in the same directory as the - # object files are, build_temp doesn't help much - # where are the object files - temp_dir = os.path.dirname(objects[0]) - # name of dll to give the helper files the same base name - (dll_name, dll_extension) = os.path.splitext( - os.path.basename(output_filename)) - - # generate the filenames for these files - def_file = os.path.join(temp_dir, dll_name + ".def") - lib_file = os.path.join(temp_dir, 'lib' + dll_name + ".a") - - # Generate .def file - contents = [ - "LIBRARY %s" % os.path.basename(output_filename), - "EXPORTS"] - for sym in export_symbols: - contents.append(sym) - self.execute(write_file, (def_file, contents), - "writing %s" % def_file) - - # next add options for def-file and to creating import libraries - - # dllwrap uses different options than gcc/ld - if self.linker_dll == "dllwrap": - extra_preargs.extend(["--output-lib", lib_file]) - # for dllwrap we have to use a special option - extra_preargs.extend(["--def", def_file]) - # we use gcc/ld here and can be sure ld is >= 2.9.10 - else: - # doesn't work: bfd_close build\...\libfoo.a: Invalid operation - #extra_preargs.extend(["-Wl,--out-implib,%s" % lib_file]) - # for gcc/ld the def-file is specified as any object files - objects.append(def_file) - - #end: if ((export_symbols is not None) and - # (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")): - - # who wants symbols and a many times larger output file - # should explicitly switch the debug mode on - # otherwise we let dllwrap/ld strip the output file - # (On my machine: 10KB < stripped_file < ??100KB - # unstripped_file = stripped_file + XXX KB - # ( XXX=254 for a typical python extension)) - if not debug: - extra_preargs.append("-s") - - UnixCCompiler.link(self, target_desc, objects, output_filename, - output_dir, libraries, library_dirs, - runtime_library_dirs, - None, # export_symbols, we do this in our def-file - debug, extra_preargs, extra_postargs, build_temp, - target_lang) - - # -- Miscellaneous methods ----------------------------------------- - - def object_filenames(self, source_filenames, strip_dir=0, output_dir=''): - """Adds supports for rc and res files.""" - if output_dir is None: - output_dir = '' - obj_names = [] - for src_name in source_filenames: - # use normcase to make sure '.rc' is really '.rc' and not '.RC' - base, ext = os.path.splitext(os.path.normcase(src_name)) - if ext not in (self.src_extensions + ['.rc','.res']): - raise UnknownFileError("unknown file type '%s' (from '%s')" % \ - (ext, src_name)) - if strip_dir: - base = os.path.basename (base) - if ext in ('.res', '.rc'): - # these need to be compiled to object files - obj_names.append (os.path.join(output_dir, - base + ext + self.obj_extension)) - else: - obj_names.append (os.path.join(output_dir, - base + self.obj_extension)) - return obj_names - -# the same as cygwin plus some additional parameters -class Mingw32CCompiler(CygwinCCompiler): - """ Handles the Mingw32 port of the GNU C compiler to Windows. - """ - compiler_type = 'mingw32' - - def __init__(self, verbose=0, dry_run=0, force=0): - - CygwinCCompiler.__init__ (self, verbose, dry_run, force) - - # ld_version >= "2.13" support -shared so use it instead of - # -mdll -static - if self.ld_version >= "2.13": - shared_option = "-shared" - else: - shared_option = "-mdll -static" - - # A real mingw32 doesn't need to specify a different entry point, - # but cygwin 2.91.57 in no-cygwin-mode needs it. - if self.gcc_version <= "2.91.57": - entry_point = '--entry _DllMain@12' - else: - entry_point = '' - - if is_cygwingcc(): - raise CCompilerError( - 'Cygwin gcc cannot be used with --compiler=mingw32') - - self.set_executables(compiler='gcc -O -Wall', - compiler_so='gcc -mdll -O -Wall', - compiler_cxx='g++ -O -Wall', - linker_exe='gcc', - linker_so='%s %s %s' - % (self.linker_dll, shared_option, - entry_point)) - # Maybe we should also append -mthreads, but then the finished - # dlls need another dll (mingwm10.dll see Mingw32 docs) - # (-mthreads: Support thread-safe exception handling on `Mingw32') - - # no additional libraries needed - self.dll_libraries=[] - - # Include the appropriate MSVC runtime library if Python was built - # with MSVC 7.0 or later. - self.dll_libraries = get_msvcr() - -# Because these compilers aren't configured in Python's pyconfig.h file by -# default, we should at least warn the user if he is using an unmodified -# version. - -CONFIG_H_OK = "ok" -CONFIG_H_NOTOK = "not ok" -CONFIG_H_UNCERTAIN = "uncertain" - -def check_config_h(): - """Check if the current Python installation appears amenable to building - extensions with GCC. - - Returns a tuple (status, details), where 'status' is one of the following - constants: - - - CONFIG_H_OK: all is well, go ahead and compile - - CONFIG_H_NOTOK: doesn't look good - - CONFIG_H_UNCERTAIN: not sure -- unable to read pyconfig.h - - 'details' is a human-readable string explaining the situation. - - Note there are two ways to conclude "OK": either 'sys.version' contains - the string "GCC" (implying that this Python was built with GCC), or the - installed "pyconfig.h" contains the string "__GNUC__". - """ - - # XXX since this function also checks sys.version, it's not strictly a - # "pyconfig.h" check -- should probably be renamed... - - from distutils import sysconfig - - # if sys.version contains GCC then python was compiled with GCC, and the - # pyconfig.h file should be OK - if "GCC" in sys.version: - return CONFIG_H_OK, "sys.version mentions 'GCC'" - - # let's see if __GNUC__ is mentioned in python.h - fn = sysconfig.get_config_h_filename() - try: - config_h = open(fn) - try: - if "__GNUC__" in config_h.read(): - return CONFIG_H_OK, "'%s' mentions '__GNUC__'" % fn - else: - return CONFIG_H_NOTOK, "'%s' does not mention '__GNUC__'" % fn - finally: - config_h.close() - except OSError as exc: - return (CONFIG_H_UNCERTAIN, - "couldn't read '%s': %s" % (fn, exc.strerror)) - -RE_VERSION = re.compile(br'(\d+\.\d+(\.\d+)*)') - -def _find_exe_version(cmd): - """Find the version of an executable by running `cmd` in the shell. - - If the command is not found, or the output does not match - `RE_VERSION`, returns None. - """ - executable = cmd.split()[0] - if find_executable(executable) is None: - return None - out = Popen(cmd, shell=True, stdout=PIPE).stdout - try: - out_string = out.read() - finally: - out.close() - result = RE_VERSION.search(out_string) - if result is None: - return None - # LooseVersion works with strings - # so we need to decode our bytes - return LooseVersion(result.group(1).decode()) - -def get_versions(): - """ Try to find out the versions of gcc, ld and dllwrap. - - If not possible it returns None for it. - """ - commands = ['gcc -dumpversion', 'ld -v', 'dllwrap --version'] - return tuple([_find_exe_version(cmd) for cmd in commands]) - -def is_cygwingcc(): - '''Try to determine if the gcc that would be used is from cygwin.''' - out_string = check_output(['gcc', '-dumpmachine']) - return out_string.strip().endswith(b'cygwin') diff --git a/Lib/distutils/debug.py b/Lib/distutils/debug.py deleted file mode 100644 index daf1660f0d8..00000000000 --- a/Lib/distutils/debug.py +++ /dev/null @@ -1,5 +0,0 @@ -import os - -# If DISTUTILS_DEBUG is anything other than the empty string, we run in -# debug mode. -DEBUG = os.environ.get('DISTUTILS_DEBUG') diff --git a/Lib/distutils/dep_util.py b/Lib/distutils/dep_util.py deleted file mode 100644 index d74f5e4e92f..00000000000 --- a/Lib/distutils/dep_util.py +++ /dev/null @@ -1,92 +0,0 @@ -"""distutils.dep_util - -Utility functions for simple, timestamp-based dependency of files -and groups of files; also, function based entirely on such -timestamp dependency analysis.""" - -import os -from distutils.errors import DistutilsFileError - - -def newer (source, target): - """Return true if 'source' exists and is more recently modified than - 'target', or if 'source' exists and 'target' doesn't. Return false if - both exist and 'target' is the same age or younger than 'source'. - Raise DistutilsFileError if 'source' does not exist. - """ - if not os.path.exists(source): - raise DistutilsFileError("file '%s' does not exist" % - os.path.abspath(source)) - if not os.path.exists(target): - return 1 - - from stat import ST_MTIME - mtime1 = os.stat(source)[ST_MTIME] - mtime2 = os.stat(target)[ST_MTIME] - - return mtime1 > mtime2 - -# newer () - - -def newer_pairwise (sources, targets): - """Walk two filename lists in parallel, testing if each source is newer - than its corresponding target. Return a pair of lists (sources, - targets) where source is newer than target, according to the semantics - of 'newer()'. - """ - if len(sources) != len(targets): - raise ValueError("'sources' and 'targets' must be same length") - - # build a pair of lists (sources, targets) where source is newer - n_sources = [] - n_targets = [] - for i in range(len(sources)): - if newer(sources[i], targets[i]): - n_sources.append(sources[i]) - n_targets.append(targets[i]) - - return (n_sources, n_targets) - -# newer_pairwise () - - -def newer_group (sources, target, missing='error'): - """Return true if 'target' is out-of-date with respect to any file - listed in 'sources'. In other words, if 'target' exists and is newer - than every file in 'sources', return false; otherwise return true. - 'missing' controls what we do when a source file is missing; the - default ("error") is to blow up with an OSError from inside 'stat()'; - if it is "ignore", we silently drop any missing source files; if it is - "newer", any missing source files make us assume that 'target' is - out-of-date (this is handy in "dry-run" mode: it'll make you pretend to - carry out commands that wouldn't work because inputs are missing, but - that doesn't matter because you're not actually going to run the - commands). - """ - # If the target doesn't even exist, then it's definitely out-of-date. - if not os.path.exists(target): - return 1 - - # Otherwise we have to find out the hard way: if *any* source file - # is more recent than 'target', then 'target' is out-of-date and - # we can immediately return true. If we fall through to the end - # of the loop, then 'target' is up-to-date and we return false. - from stat import ST_MTIME - target_mtime = os.stat(target)[ST_MTIME] - for source in sources: - if not os.path.exists(source): - if missing == 'error': # blow up when we stat() the file - pass - elif missing == 'ignore': # missing source dropped from - continue # target's dependency list - elif missing == 'newer': # missing source means target is - return 1 # out-of-date - - source_mtime = os.stat(source)[ST_MTIME] - if source_mtime > target_mtime: - return 1 - else: - return 0 - -# newer_group () diff --git a/Lib/distutils/dir_util.py b/Lib/distutils/dir_util.py deleted file mode 100644 index df4d751c942..00000000000 --- a/Lib/distutils/dir_util.py +++ /dev/null @@ -1,223 +0,0 @@ -"""distutils.dir_util - -Utility functions for manipulating directories and directory trees.""" - -import os -import errno -from distutils.errors import DistutilsFileError, DistutilsInternalError -from distutils import log - -# cache for by mkpath() -- in addition to cheapening redundant calls, -# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode -_path_created = {} - -# I don't use os.makedirs because a) it's new to Python 1.5.2, and -# b) it blows up if the directory already exists (I want to silently -# succeed in that case). -def mkpath(name, mode=0o777, verbose=1, dry_run=0): - """Create a directory and any missing ancestor directories. - - If the directory already exists (or if 'name' is the empty string, which - means the current directory, which of course exists), then do nothing. - Raise DistutilsFileError if unable to create some directory along the way - (eg. some sub-path exists, but is a file rather than a directory). - If 'verbose' is true, print a one-line summary of each mkdir to stdout. - Return the list of directories actually created. - """ - - global _path_created - - # Detect a common bug -- name is None - if not isinstance(name, str): - raise DistutilsInternalError( - "mkpath: 'name' must be a string (got %r)" % (name,)) - - # XXX what's the better way to handle verbosity? print as we create - # each directory in the path (the current behaviour), or only announce - # the creation of the whole path? (quite easy to do the latter since - # we're not using a recursive algorithm) - - name = os.path.normpath(name) - created_dirs = [] - if os.path.isdir(name) or name == '': - return created_dirs - if _path_created.get(os.path.abspath(name)): - return created_dirs - - (head, tail) = os.path.split(name) - tails = [tail] # stack of lone dirs to create - - while head and tail and not os.path.isdir(head): - (head, tail) = os.path.split(head) - tails.insert(0, tail) # push next higher dir onto stack - - # now 'head' contains the deepest directory that already exists - # (that is, the child of 'head' in 'name' is the highest directory - # that does *not* exist) - for d in tails: - #print "head = %s, d = %s: " % (head, d), - head = os.path.join(head, d) - abs_head = os.path.abspath(head) - - if _path_created.get(abs_head): - continue - - if verbose >= 1: - log.info("creating %s", head) - - if not dry_run: - try: - os.mkdir(head, mode) - except OSError as exc: - if not (exc.errno == errno.EEXIST and os.path.isdir(head)): - raise DistutilsFileError( - "could not create '%s': %s" % (head, exc.args[-1])) - created_dirs.append(head) - - _path_created[abs_head] = 1 - return created_dirs - -def create_tree(base_dir, files, mode=0o777, verbose=1, dry_run=0): - """Create all the empty directories under 'base_dir' needed to put 'files' - there. - - 'base_dir' is just the name of a directory which doesn't necessarily - exist yet; 'files' is a list of filenames to be interpreted relative to - 'base_dir'. 'base_dir' + the directory portion of every file in 'files' - will be created if it doesn't already exist. 'mode', 'verbose' and - 'dry_run' flags are as for 'mkpath()'. - """ - # First get the list of directories to create - need_dir = set() - for file in files: - need_dir.add(os.path.join(base_dir, os.path.dirname(file))) - - # Now create them - for dir in sorted(need_dir): - mkpath(dir, mode, verbose=verbose, dry_run=dry_run) - -import sysconfig -_multiarch = None - -def copy_tree(src, dst, preserve_mode=1, preserve_times=1, - preserve_symlinks=0, update=0, verbose=1, dry_run=0): - """Copy an entire directory tree 'src' to a new location 'dst'. - - Both 'src' and 'dst' must be directory names. If 'src' is not a - directory, raise DistutilsFileError. If 'dst' does not exist, it is - created with 'mkpath()'. The end result of the copy is that every - file in 'src' is copied to 'dst', and directories under 'src' are - recursively copied to 'dst'. Return the list of files that were - copied or might have been copied, using their output name. The - return value is unaffected by 'update' or 'dry_run': it is simply - the list of all files under 'src', with the names changed to be - under 'dst'. - - 'preserve_mode' and 'preserve_times' are the same as for - 'copy_file'; note that they only apply to regular files, not to - directories. If 'preserve_symlinks' is true, symlinks will be - copied as symlinks (on platforms that support them!); otherwise - (the default), the destination of the symlink will be copied. - 'update' and 'verbose' are the same as for 'copy_file'. - """ - from distutils.file_util import copy_file - - if not dry_run and not os.path.isdir(src): - raise DistutilsFileError( - "cannot copy tree '%s': not a directory" % src) - try: - names = os.listdir(src) - except OSError as e: - if dry_run: - names = [] - else: - raise DistutilsFileError( - "error listing files in '%s': %s" % (src, e.strerror)) - - ext_suffix = sysconfig.get_config_var ('EXT_SUFFIX') - _multiarch = sysconfig.get_config_var ('MULTIARCH') - if ext_suffix.endswith(_multiarch + ext_suffix[-3:]): - new_suffix = None - else: - new_suffix = "%s-%s%s" % (ext_suffix[:-3], _multiarch, ext_suffix[-3:]) - - if not dry_run: - mkpath(dst, verbose=verbose) - - outputs = [] - - for n in names: - src_name = os.path.join(src, n) - dst_name = os.path.join(dst, n) - if new_suffix and _multiarch and n.endswith(ext_suffix) and not n.endswith(new_suffix): - dst_name = os.path.join(dst, n.replace(ext_suffix, new_suffix)) - log.info("renaming extension %s -> %s", n, n.replace(ext_suffix, new_suffix)) - - if n.startswith('.nfs'): - # skip NFS rename files - continue - - if preserve_symlinks and os.path.islink(src_name): - link_dest = os.readlink(src_name) - if verbose >= 1: - log.info("linking %s -> %s", dst_name, link_dest) - if not dry_run: - os.symlink(link_dest, dst_name) - outputs.append(dst_name) - - elif os.path.isdir(src_name): - outputs.extend( - copy_tree(src_name, dst_name, preserve_mode, - preserve_times, preserve_symlinks, update, - verbose=verbose, dry_run=dry_run)) - else: - copy_file(src_name, dst_name, preserve_mode, - preserve_times, update, verbose=verbose, - dry_run=dry_run) - outputs.append(dst_name) - - return outputs - -def _build_cmdtuple(path, cmdtuples): - """Helper for remove_tree().""" - for f in os.listdir(path): - real_f = os.path.join(path,f) - if os.path.isdir(real_f) and not os.path.islink(real_f): - _build_cmdtuple(real_f, cmdtuples) - else: - cmdtuples.append((os.remove, real_f)) - cmdtuples.append((os.rmdir, path)) - -def remove_tree(directory, verbose=1, dry_run=0): - """Recursively remove an entire directory tree. - - Any errors are ignored (apart from being reported to stdout if 'verbose' - is true). - """ - global _path_created - - if verbose >= 1: - log.info("removing '%s' (and everything under it)", directory) - if dry_run: - return - cmdtuples = [] - _build_cmdtuple(directory, cmdtuples) - for cmd in cmdtuples: - try: - cmd[0](cmd[1]) - # remove dir from cache if it's already there - abspath = os.path.abspath(cmd[1]) - if abspath in _path_created: - del _path_created[abspath] - except OSError as exc: - log.warn("error removing %s: %s", directory, exc) - -def ensure_relative(path): - """Take the full path 'path', and make it a relative path. - - This is useful to make 'path' the second argument to os.path.join(). - """ - drive, path = os.path.splitdrive(path) - if path[0:1] == os.sep: - path = drive + path[1:] - return path diff --git a/Lib/distutils/dist.py b/Lib/distutils/dist.py deleted file mode 100644 index 62a24516cfa..00000000000 --- a/Lib/distutils/dist.py +++ /dev/null @@ -1,1236 +0,0 @@ -"""distutils.dist - -Provides the Distribution class, which represents the module distribution -being built/installed/distributed. -""" - -import sys -import os -import re -from email import message_from_file - -try: - import warnings -except ImportError: - warnings = None - -from distutils.errors import * -from distutils.fancy_getopt import FancyGetopt, translate_longopt -from distutils.util import check_environ, strtobool, rfc822_escape -from distutils import log -from distutils.debug import DEBUG - -# Regex to define acceptable Distutils command names. This is not *quite* -# the same as a Python NAME -- I don't allow leading underscores. The fact -# that they're very similar is no coincidence; the default naming scheme is -# to look for a Python module named after the command. -command_re = re.compile(r'^[a-zA-Z]([a-zA-Z0-9_]*)$') - - -class Distribution: - """The core of the Distutils. Most of the work hiding behind 'setup' - is really done within a Distribution instance, which farms the work out - to the Distutils commands specified on the command line. - - Setup scripts will almost never instantiate Distribution directly, - unless the 'setup()' function is totally inadequate to their needs. - However, it is conceivable that a setup script might wish to subclass - Distribution for some specialized purpose, and then pass the subclass - to 'setup()' as the 'distclass' keyword argument. If so, it is - necessary to respect the expectations that 'setup' has of Distribution. - See the code for 'setup()', in core.py, for details. - """ - - # 'global_options' describes the command-line options that may be - # supplied to the setup script prior to any actual commands. - # Eg. "./setup.py -n" or "./setup.py --quiet" both take advantage of - # these global options. This list should be kept to a bare minimum, - # since every global option is also valid as a command option -- and we - # don't want to pollute the commands with too many options that they - # have minimal control over. - # The fourth entry for verbose means that it can be repeated. - global_options = [ - ('verbose', 'v', "run verbosely (default)", 1), - ('quiet', 'q', "run quietly (turns verbosity off)"), - ('dry-run', 'n', "don't actually do anything"), - ('help', 'h', "show detailed help message"), - ('no-user-cfg', None, - 'ignore pydistutils.cfg in your home directory'), - ] - - # 'common_usage' is a short (2-3 line) string describing the common - # usage of the setup script. - common_usage = """\ -Common commands: (see '--help-commands' for more) - - setup.py build will build the package underneath 'build/' - setup.py install will install the package -""" - - # options that are not propagated to the commands - display_options = [ - ('help-commands', None, - "list all available commands"), - ('name', None, - "print package name"), - ('version', 'V', - "print package version"), - ('fullname', None, - "print <package name>-<version>"), - ('author', None, - "print the author's name"), - ('author-email', None, - "print the author's email address"), - ('maintainer', None, - "print the maintainer's name"), - ('maintainer-email', None, - "print the maintainer's email address"), - ('contact', None, - "print the maintainer's name if known, else the author's"), - ('contact-email', None, - "print the maintainer's email address if known, else the author's"), - ('url', None, - "print the URL for this package"), - ('license', None, - "print the license of the package"), - ('licence', None, - "alias for --license"), - ('description', None, - "print the package description"), - ('long-description', None, - "print the long package description"), - ('platforms', None, - "print the list of platforms"), - ('classifiers', None, - "print the list of classifiers"), - ('keywords', None, - "print the list of keywords"), - ('provides', None, - "print the list of packages/modules provided"), - ('requires', None, - "print the list of packages/modules required"), - ('obsoletes', None, - "print the list of packages/modules made obsolete") - ] - display_option_names = [translate_longopt(x[0]) for x in display_options] - - # negative options are options that exclude other options - negative_opt = {'quiet': 'verbose'} - - # -- Creation/initialization methods ------------------------------- - - def __init__(self, attrs=None): - """Construct a new Distribution instance: initialize all the - attributes of a Distribution, and then use 'attrs' (a dictionary - mapping attribute names to values) to assign some of those - attributes their "real" values. (Any attributes not mentioned in - 'attrs' will be assigned to some null value: 0, None, an empty list - or dictionary, etc.) Most importantly, initialize the - 'command_obj' attribute to the empty dictionary; this will be - filled in with real command objects by 'parse_command_line()'. - """ - - # Default values for our command-line options - self.verbose = 1 - self.dry_run = 0 - self.help = 0 - for attr in self.display_option_names: - setattr(self, attr, 0) - - # Store the distribution meta-data (name, version, author, and so - # forth) in a separate object -- we're getting to have enough - # information here (and enough command-line options) that it's - # worth it. Also delegate 'get_XXX()' methods to the 'metadata' - # object in a sneaky and underhanded (but efficient!) way. - self.metadata = DistributionMetadata() - for basename in self.metadata._METHOD_BASENAMES: - method_name = "get_" + basename - setattr(self, method_name, getattr(self.metadata, method_name)) - - # 'cmdclass' maps command names to class objects, so we - # can 1) quickly figure out which class to instantiate when - # we need to create a new command object, and 2) have a way - # for the setup script to override command classes - self.cmdclass = {} - - # 'command_packages' is a list of packages in which commands - # are searched for. The factory for command 'foo' is expected - # to be named 'foo' in the module 'foo' in one of the packages - # named here. This list is searched from the left; an error - # is raised if no named package provides the command being - # searched for. (Always access using get_command_packages().) - self.command_packages = None - - # 'script_name' and 'script_args' are usually set to sys.argv[0] - # and sys.argv[1:], but they can be overridden when the caller is - # not necessarily a setup script run from the command-line. - self.script_name = None - self.script_args = None - - # 'command_options' is where we store command options between - # parsing them (from config files, the command-line, etc.) and when - # they are actually needed -- ie. when the command in question is - # instantiated. It is a dictionary of dictionaries of 2-tuples: - # command_options = { command_name : { option : (source, value) } } - self.command_options = {} - - # 'dist_files' is the list of (command, pyversion, file) that - # have been created by any dist commands run so far. This is - # filled regardless of whether the run is dry or not. pyversion - # gives sysconfig.get_python_version() if the dist file is - # specific to a Python version, 'any' if it is good for all - # Python versions on the target platform, and '' for a source - # file. pyversion should not be used to specify minimum or - # maximum required Python versions; use the metainfo for that - # instead. - self.dist_files = [] - - # These options are really the business of various commands, rather - # than of the Distribution itself. We provide aliases for them in - # Distribution as a convenience to the developer. - self.packages = None - self.package_data = {} - self.package_dir = None - self.py_modules = None - self.libraries = None - self.headers = None - self.ext_modules = None - self.ext_package = None - self.include_dirs = None - self.extra_path = None - self.scripts = None - self.data_files = None - self.password = '' - - # And now initialize bookkeeping stuff that can't be supplied by - # the caller at all. 'command_obj' maps command names to - # Command instances -- that's how we enforce that every command - # class is a singleton. - self.command_obj = {} - - # 'have_run' maps command names to boolean values; it keeps track - # of whether we have actually run a particular command, to make it - # cheap to "run" a command whenever we think we might need to -- if - # it's already been done, no need for expensive filesystem - # operations, we just check the 'have_run' dictionary and carry on. - # It's only safe to query 'have_run' for a command class that has - # been instantiated -- a false value will be inserted when the - # command object is created, and replaced with a true value when - # the command is successfully run. Thus it's probably best to use - # '.get()' rather than a straight lookup. - self.have_run = {} - - # Now we'll use the attrs dictionary (ultimately, keyword args from - # the setup script) to possibly override any or all of these - # distribution options. - - if attrs: - # Pull out the set of command options and work on them - # specifically. Note that this order guarantees that aliased - # command options will override any supplied redundantly - # through the general options dictionary. - options = attrs.get('options') - if options is not None: - del attrs['options'] - for (command, cmd_options) in options.items(): - opt_dict = self.get_option_dict(command) - for (opt, val) in cmd_options.items(): - opt_dict[opt] = ("setup script", val) - - if 'licence' in attrs: - attrs['license'] = attrs['licence'] - del attrs['licence'] - msg = "'licence' distribution option is deprecated; use 'license'" - if warnings is not None: - warnings.warn(msg) - else: - sys.stderr.write(msg + "\n") - - # Now work on the rest of the attributes. Any attribute that's - # not already defined is invalid! - for (key, val) in attrs.items(): - if hasattr(self.metadata, "set_" + key): - getattr(self.metadata, "set_" + key)(val) - elif hasattr(self.metadata, key): - setattr(self.metadata, key, val) - elif hasattr(self, key): - setattr(self, key, val) - else: - msg = "Unknown distribution option: %s" % repr(key) - if warnings is not None: - warnings.warn(msg) - else: - sys.stderr.write(msg + "\n") - - # no-user-cfg is handled before other command line args - # because other args override the config files, and this - # one is needed before we can load the config files. - # If attrs['script_args'] wasn't passed, assume false. - # - # This also make sure we just look at the global options - self.want_user_cfg = True - - if self.script_args is not None: - for arg in self.script_args: - if not arg.startswith('-'): - break - if arg == '--no-user-cfg': - self.want_user_cfg = False - break - - self.finalize_options() - - def get_option_dict(self, command): - """Get the option dictionary for a given command. If that - command's option dictionary hasn't been created yet, then create it - and return the new dictionary; otherwise, return the existing - option dictionary. - """ - dict = self.command_options.get(command) - if dict is None: - dict = self.command_options[command] = {} - return dict - - def dump_option_dicts(self, header=None, commands=None, indent=""): - from pprint import pformat - - if commands is None: # dump all command option dicts - commands = sorted(self.command_options.keys()) - - if header is not None: - self.announce(indent + header) - indent = indent + " " - - if not commands: - self.announce(indent + "no commands known yet") - return - - for cmd_name in commands: - opt_dict = self.command_options.get(cmd_name) - if opt_dict is None: - self.announce(indent + - "no option dict for '%s' command" % cmd_name) - else: - self.announce(indent + - "option dict for '%s' command:" % cmd_name) - out = pformat(opt_dict) - for line in out.split('\n'): - self.announce(indent + " " + line) - - # -- Config file finding/parsing methods --------------------------- - - def find_config_files(self): - """Find as many configuration files as should be processed for this - platform, and return a list of filenames in the order in which they - should be parsed. The filenames returned are guaranteed to exist - (modulo nasty race conditions). - - There are three possible config files: distutils.cfg in the - Distutils installation directory (ie. where the top-level - Distutils __inst__.py file lives), a file in the user's home - directory named .pydistutils.cfg on Unix and pydistutils.cfg - on Windows/Mac; and setup.cfg in the current directory. - - The file in the user's home directory can be disabled with the - --no-user-cfg option. - """ - files = [] - check_environ() - - # Where to look for the system-wide Distutils config file - sys_dir = os.path.dirname(sys.modules['distutils'].__file__) - - # Look for the system config file - sys_file = os.path.join(sys_dir, "distutils.cfg") - if os.path.isfile(sys_file): - files.append(sys_file) - - # What to call the per-user config file - if os.name == 'posix': - user_filename = ".pydistutils.cfg" - else: - user_filename = "pydistutils.cfg" - - # And look for the user config file - if self.want_user_cfg: - user_file = os.path.join(os.path.expanduser('~'), user_filename) - if os.path.isfile(user_file): - files.append(user_file) - - # All platforms support local setup.cfg - local_file = "setup.cfg" - if os.path.isfile(local_file): - files.append(local_file) - - if DEBUG: - self.announce("using config files: %s" % ', '.join(files)) - - return files - - def parse_config_files(self, filenames=None): - from configparser import ConfigParser - - # Ignore install directory options if we have a venv - if sys.prefix != sys.base_prefix: - ignore_options = [ - 'install-base', 'install-platbase', 'install-lib', - 'install-platlib', 'install-purelib', 'install-headers', - 'install-scripts', 'install-data', 'prefix', 'exec-prefix', - 'home', 'user', 'root'] - else: - ignore_options = [] - - ignore_options = frozenset(ignore_options) - - if filenames is None: - filenames = self.find_config_files() - - if DEBUG: - self.announce("Distribution.parse_config_files():") - - parser = ConfigParser() - for filename in filenames: - if DEBUG: - self.announce(" reading %s" % filename) - parser.read(filename) - for section in parser.sections(): - options = parser.options(section) - opt_dict = self.get_option_dict(section) - - for opt in options: - if opt != '__name__' and opt not in ignore_options: - val = parser.get(section,opt) - opt = opt.replace('-', '_') - opt_dict[opt] = (filename, val) - - # Make the ConfigParser forget everything (so we retain - # the original filenames that options come from) - parser.__init__() - - # If there was a "global" section in the config file, use it - # to set Distribution options. - - if 'global' in self.command_options: - for (opt, (src, val)) in self.command_options['global'].items(): - alias = self.negative_opt.get(opt) - try: - if alias: - setattr(self, alias, not strtobool(val)) - elif opt in ('verbose', 'dry_run'): # ugh! - setattr(self, opt, strtobool(val)) - else: - setattr(self, opt, val) - except ValueError as msg: - raise DistutilsOptionError(msg) - - # -- Command-line parsing methods ---------------------------------- - - def parse_command_line(self): - """Parse the setup script's command line, taken from the - 'script_args' instance attribute (which defaults to 'sys.argv[1:]' - -- see 'setup()' in core.py). This list is first processed for - "global options" -- options that set attributes of the Distribution - instance. Then, it is alternately scanned for Distutils commands - and options for that command. Each new command terminates the - options for the previous command. The allowed options for a - command are determined by the 'user_options' attribute of the - command class -- thus, we have to be able to load command classes - in order to parse the command line. Any error in that 'options' - attribute raises DistutilsGetoptError; any error on the - command-line raises DistutilsArgError. If no Distutils commands - were found on the command line, raises DistutilsArgError. Return - true if command-line was successfully parsed and we should carry - on with executing commands; false if no errors but we shouldn't - execute commands (currently, this only happens if user asks for - help). - """ - # - # We now have enough information to show the Macintosh dialog - # that allows the user to interactively specify the "command line". - # - toplevel_options = self._get_toplevel_options() - - # We have to parse the command line a bit at a time -- global - # options, then the first command, then its options, and so on -- - # because each command will be handled by a different class, and - # the options that are valid for a particular class aren't known - # until we have loaded the command class, which doesn't happen - # until we know what the command is. - - self.commands = [] - parser = FancyGetopt(toplevel_options + self.display_options) - parser.set_negative_aliases(self.negative_opt) - parser.set_aliases({'licence': 'license'}) - args = parser.getopt(args=self.script_args, object=self) - option_order = parser.get_option_order() - log.set_verbosity(self.verbose) - - # for display options we return immediately - if self.handle_display_options(option_order): - return - while args: - args = self._parse_command_opts(parser, args) - if args is None: # user asked for help (and got it) - return - - # Handle the cases of --help as a "global" option, ie. - # "setup.py --help" and "setup.py --help command ...". For the - # former, we show global options (--verbose, --dry-run, etc.) - # and display-only options (--name, --version, etc.); for the - # latter, we omit the display-only options and show help for - # each command listed on the command line. - if self.help: - self._show_help(parser, - display_options=len(self.commands) == 0, - commands=self.commands) - return - - # Oops, no commands found -- an end-user error - if not self.commands: - raise DistutilsArgError("no commands supplied") - - # All is well: return true - return True - - def _get_toplevel_options(self): - """Return the non-display options recognized at the top level. - - This includes options that are recognized *only* at the top - level as well as options recognized for commands. - """ - return self.global_options + [ - ("command-packages=", None, - "list of packages that provide distutils commands"), - ] - - def _parse_command_opts(self, parser, args): - """Parse the command-line options for a single command. - 'parser' must be a FancyGetopt instance; 'args' must be the list - of arguments, starting with the current command (whose options - we are about to parse). Returns a new version of 'args' with - the next command at the front of the list; will be the empty - list if there are no more commands on the command line. Returns - None if the user asked for help on this command. - """ - # late import because of mutual dependence between these modules - from distutils.cmd import Command - - # Pull the current command from the head of the command line - command = args[0] - if not command_re.match(command): - raise SystemExit("invalid command name '%s'" % command) - self.commands.append(command) - - # Dig up the command class that implements this command, so we - # 1) know that it's a valid command, and 2) know which options - # it takes. - try: - cmd_class = self.get_command_class(command) - except DistutilsModuleError as msg: - raise DistutilsArgError(msg) - - # Require that the command class be derived from Command -- want - # to be sure that the basic "command" interface is implemented. - if not issubclass(cmd_class, Command): - raise DistutilsClassError( - "command class %s must subclass Command" % cmd_class) - - # Also make sure that the command object provides a list of its - # known options. - if not (hasattr(cmd_class, 'user_options') and - isinstance(cmd_class.user_options, list)): - msg = ("command class %s must provide " - "'user_options' attribute (a list of tuples)") - raise DistutilsClassError(msg % cmd_class) - - # If the command class has a list of negative alias options, - # merge it in with the global negative aliases. - negative_opt = self.negative_opt - if hasattr(cmd_class, 'negative_opt'): - negative_opt = negative_opt.copy() - negative_opt.update(cmd_class.negative_opt) - - # Check for help_options in command class. They have a different - # format (tuple of four) so we need to preprocess them here. - if (hasattr(cmd_class, 'help_options') and - isinstance(cmd_class.help_options, list)): - help_options = fix_help_options(cmd_class.help_options) - else: - help_options = [] - - # All commands support the global options too, just by adding - # in 'global_options'. - parser.set_option_table(self.global_options + - cmd_class.user_options + - help_options) - parser.set_negative_aliases(negative_opt) - (args, opts) = parser.getopt(args[1:]) - if hasattr(opts, 'help') and opts.help: - self._show_help(parser, display_options=0, commands=[cmd_class]) - return - - if (hasattr(cmd_class, 'help_options') and - isinstance(cmd_class.help_options, list)): - help_option_found=0 - for (help_option, short, desc, func) in cmd_class.help_options: - if hasattr(opts, parser.get_attr_name(help_option)): - help_option_found=1 - if callable(func): - func() - else: - raise DistutilsClassError( - "invalid help function %r for help option '%s': " - "must be a callable object (function, etc.)" - % (func, help_option)) - - if help_option_found: - return - - # Put the options from the command-line into their official - # holding pen, the 'command_options' dictionary. - opt_dict = self.get_option_dict(command) - for (name, value) in vars(opts).items(): - opt_dict[name] = ("command line", value) - - return args - - def finalize_options(self): - """Set final values for all the options on the Distribution - instance, analogous to the .finalize_options() method of Command - objects. - """ - for attr in ('keywords', 'platforms'): - value = getattr(self.metadata, attr) - if value is None: - continue - if isinstance(value, str): - value = [elm.strip() for elm in value.split(',')] - setattr(self.metadata, attr, value) - - def _show_help(self, parser, global_options=1, display_options=1, - commands=[]): - """Show help for the setup script command-line in the form of - several lists of command-line options. 'parser' should be a - FancyGetopt instance; do not expect it to be returned in the - same state, as its option table will be reset to make it - generate the correct help text. - - If 'global_options' is true, lists the global options: - --verbose, --dry-run, etc. If 'display_options' is true, lists - the "display-only" options: --name, --version, etc. Finally, - lists per-command help for every command name or command class - in 'commands'. - """ - # late import because of mutual dependence between these modules - from distutils.core import gen_usage - from distutils.cmd import Command - - if global_options: - if display_options: - options = self._get_toplevel_options() - else: - options = self.global_options - parser.set_option_table(options) - parser.print_help(self.common_usage + "\nGlobal options:") - print('') - - if display_options: - parser.set_option_table(self.display_options) - parser.print_help( - "Information display options (just display " + - "information, ignore any commands)") - print('') - - for command in self.commands: - if isinstance(command, type) and issubclass(command, Command): - klass = command - else: - klass = self.get_command_class(command) - if (hasattr(klass, 'help_options') and - isinstance(klass.help_options, list)): - parser.set_option_table(klass.user_options + - fix_help_options(klass.help_options)) - else: - parser.set_option_table(klass.user_options) - parser.print_help("Options for '%s' command:" % klass.__name__) - print('') - - print(gen_usage(self.script_name)) - - def handle_display_options(self, option_order): - """If there were any non-global "display-only" options - (--help-commands or the metadata display options) on the command - line, display the requested info and return true; else return - false. - """ - from distutils.core import gen_usage - - # User just wants a list of commands -- we'll print it out and stop - # processing now (ie. if they ran "setup --help-commands foo bar", - # we ignore "foo bar"). - if self.help_commands: - self.print_commands() - print('') - print(gen_usage(self.script_name)) - return 1 - - # If user supplied any of the "display metadata" options, then - # display that metadata in the order in which the user supplied the - # metadata options. - any_display_options = 0 - is_display_option = {} - for option in self.display_options: - is_display_option[option[0]] = 1 - - for (opt, val) in option_order: - if val and is_display_option.get(opt): - opt = translate_longopt(opt) - value = getattr(self.metadata, "get_"+opt)() - if opt in ['keywords', 'platforms']: - print(','.join(value)) - elif opt in ('classifiers', 'provides', 'requires', - 'obsoletes'): - print('\n'.join(value)) - else: - print(value) - any_display_options = 1 - - return any_display_options - - def print_command_list(self, commands, header, max_length): - """Print a subset of the list of all commands -- used by - 'print_commands()'. - """ - print(header + ":") - - for cmd in commands: - klass = self.cmdclass.get(cmd) - if not klass: - klass = self.get_command_class(cmd) - try: - description = klass.description - except AttributeError: - description = "(no description available)" - - print(" %-*s %s" % (max_length, cmd, description)) - - def print_commands(self): - """Print out a help message listing all available commands with a - description of each. The list is divided into "standard commands" - (listed in distutils.command.__all__) and "extra commands" - (mentioned in self.cmdclass, but not a standard command). The - descriptions come from the command class attribute - 'description'. - """ - import distutils.command - std_commands = distutils.command.__all__ - is_std = {} - for cmd in std_commands: - is_std[cmd] = 1 - - extra_commands = [] - for cmd in self.cmdclass.keys(): - if not is_std.get(cmd): - extra_commands.append(cmd) - - max_length = 0 - for cmd in (std_commands + extra_commands): - if len(cmd) > max_length: - max_length = len(cmd) - - self.print_command_list(std_commands, - "Standard commands", - max_length) - if extra_commands: - print() - self.print_command_list(extra_commands, - "Extra commands", - max_length) - - def get_command_list(self): - """Get a list of (command, description) tuples. - The list is divided into "standard commands" (listed in - distutils.command.__all__) and "extra commands" (mentioned in - self.cmdclass, but not a standard command). The descriptions come - from the command class attribute 'description'. - """ - # Currently this is only used on Mac OS, for the Mac-only GUI - # Distutils interface (by Jack Jansen) - import distutils.command - std_commands = distutils.command.__all__ - is_std = {} - for cmd in std_commands: - is_std[cmd] = 1 - - extra_commands = [] - for cmd in self.cmdclass.keys(): - if not is_std.get(cmd): - extra_commands.append(cmd) - - rv = [] - for cmd in (std_commands + extra_commands): - klass = self.cmdclass.get(cmd) - if not klass: - klass = self.get_command_class(cmd) - try: - description = klass.description - except AttributeError: - description = "(no description available)" - rv.append((cmd, description)) - return rv - - # -- Command class/object methods ---------------------------------- - - def get_command_packages(self): - """Return a list of packages from which commands are loaded.""" - pkgs = self.command_packages - if not isinstance(pkgs, list): - if pkgs is None: - pkgs = '' - pkgs = [pkg.strip() for pkg in pkgs.split(',') if pkg != ''] - if "distutils.command" not in pkgs: - pkgs.insert(0, "distutils.command") - self.command_packages = pkgs - return pkgs - - def get_command_class(self, command): - """Return the class that implements the Distutils command named by - 'command'. First we check the 'cmdclass' dictionary; if the - command is mentioned there, we fetch the class object from the - dictionary and return it. Otherwise we load the command module - ("distutils.command." + command) and fetch the command class from - the module. The loaded class is also stored in 'cmdclass' - to speed future calls to 'get_command_class()'. - - Raises DistutilsModuleError if the expected module could not be - found, or if that module does not define the expected class. - """ - klass = self.cmdclass.get(command) - if klass: - return klass - - for pkgname in self.get_command_packages(): - module_name = "%s.%s" % (pkgname, command) - klass_name = command - - try: - __import__(module_name) - module = sys.modules[module_name] - except ImportError: - continue - - try: - klass = getattr(module, klass_name) - except AttributeError: - raise DistutilsModuleError( - "invalid command '%s' (no class '%s' in module '%s')" - % (command, klass_name, module_name)) - - self.cmdclass[command] = klass - return klass - - raise DistutilsModuleError("invalid command '%s'" % command) - - def get_command_obj(self, command, create=1): - """Return the command object for 'command'. Normally this object - is cached on a previous call to 'get_command_obj()'; if no command - object for 'command' is in the cache, then we either create and - return it (if 'create' is true) or return None. - """ - cmd_obj = self.command_obj.get(command) - if not cmd_obj and create: - if DEBUG: - self.announce("Distribution.get_command_obj(): " - "creating '%s' command object" % command) - - klass = self.get_command_class(command) - cmd_obj = self.command_obj[command] = klass(self) - self.have_run[command] = 0 - - # Set any options that were supplied in config files - # or on the command line. (NB. support for error - # reporting is lame here: any errors aren't reported - # until 'finalize_options()' is called, which means - # we won't report the source of the error.) - options = self.command_options.get(command) - if options: - self._set_command_options(cmd_obj, options) - - return cmd_obj - - def _set_command_options(self, command_obj, option_dict=None): - """Set the options for 'command_obj' from 'option_dict'. Basically - this means copying elements of a dictionary ('option_dict') to - attributes of an instance ('command'). - - 'command_obj' must be a Command instance. If 'option_dict' is not - supplied, uses the standard option dictionary for this command - (from 'self.command_options'). - """ - command_name = command_obj.get_command_name() - if option_dict is None: - option_dict = self.get_option_dict(command_name) - - if DEBUG: - self.announce(" setting options for '%s' command:" % command_name) - for (option, (source, value)) in option_dict.items(): - if DEBUG: - self.announce(" %s = %s (from %s)" % (option, value, - source)) - try: - bool_opts = [translate_longopt(o) - for o in command_obj.boolean_options] - except AttributeError: - bool_opts = [] - try: - neg_opt = command_obj.negative_opt - except AttributeError: - neg_opt = {} - - try: - is_string = isinstance(value, str) - if option in neg_opt and is_string: - setattr(command_obj, neg_opt[option], not strtobool(value)) - elif option in bool_opts and is_string: - setattr(command_obj, option, strtobool(value)) - elif hasattr(command_obj, option): - setattr(command_obj, option, value) - else: - raise DistutilsOptionError( - "error in %s: command '%s' has no such option '%s'" - % (source, command_name, option)) - except ValueError as msg: - raise DistutilsOptionError(msg) - - def reinitialize_command(self, command, reinit_subcommands=0): - """Reinitializes a command to the state it was in when first - returned by 'get_command_obj()': ie., initialized but not yet - finalized. This provides the opportunity to sneak option - values in programmatically, overriding or supplementing - user-supplied values from the config files and command line. - You'll have to re-finalize the command object (by calling - 'finalize_options()' or 'ensure_finalized()') before using it for - real. - - 'command' should be a command name (string) or command object. If - 'reinit_subcommands' is true, also reinitializes the command's - sub-commands, as declared by the 'sub_commands' class attribute (if - it has one). See the "install" command for an example. Only - reinitializes the sub-commands that actually matter, ie. those - whose test predicates return true. - - Returns the reinitialized command object. - """ - from distutils.cmd import Command - if not isinstance(command, Command): - command_name = command - command = self.get_command_obj(command_name) - else: - command_name = command.get_command_name() - - if not command.finalized: - return command - command.initialize_options() - command.finalized = 0 - self.have_run[command_name] = 0 - self._set_command_options(command) - - if reinit_subcommands: - for sub in command.get_sub_commands(): - self.reinitialize_command(sub, reinit_subcommands) - - return command - - # -- Methods that operate on the Distribution ---------------------- - - def announce(self, msg, level=log.INFO): - log.log(level, msg) - - def run_commands(self): - """Run each command that was seen on the setup script command line. - Uses the list of commands found and cache of command objects - created by 'get_command_obj()'. - """ - for cmd in self.commands: - self.run_command(cmd) - - # -- Methods that operate on its Commands -------------------------- - - def run_command(self, command): - """Do whatever it takes to run a command (including nothing at all, - if the command has already been run). Specifically: if we have - already created and run the command named by 'command', return - silently without doing anything. If the command named by 'command' - doesn't even have a command object yet, create one. Then invoke - 'run()' on that command object (or an existing one). - """ - # Already been here, done that? then return silently. - if self.have_run.get(command): - return - - log.info("running %s", command) - cmd_obj = self.get_command_obj(command) - cmd_obj.ensure_finalized() - cmd_obj.run() - self.have_run[command] = 1 - - # -- Distribution query methods ------------------------------------ - - def has_pure_modules(self): - return len(self.packages or self.py_modules or []) > 0 - - def has_ext_modules(self): - return self.ext_modules and len(self.ext_modules) > 0 - - def has_c_libraries(self): - return self.libraries and len(self.libraries) > 0 - - def has_modules(self): - return self.has_pure_modules() or self.has_ext_modules() - - def has_headers(self): - return self.headers and len(self.headers) > 0 - - def has_scripts(self): - return self.scripts and len(self.scripts) > 0 - - def has_data_files(self): - return self.data_files and len(self.data_files) > 0 - - def is_pure(self): - return (self.has_pure_modules() and - not self.has_ext_modules() and - not self.has_c_libraries()) - - # -- Metadata query methods ---------------------------------------- - - # If you're looking for 'get_name()', 'get_version()', and so forth, - # they are defined in a sneaky way: the constructor binds self.get_XXX - # to self.metadata.get_XXX. The actual code is in the - # DistributionMetadata class, below. - -class DistributionMetadata: - """Dummy class to hold the distribution meta-data: name, version, - author, and so forth. - """ - - _METHOD_BASENAMES = ("name", "version", "author", "author_email", - "maintainer", "maintainer_email", "url", - "license", "description", "long_description", - "keywords", "platforms", "fullname", "contact", - "contact_email", "classifiers", "download_url", - # PEP 314 - "provides", "requires", "obsoletes", - ) - - def __init__(self, path=None): - if path is not None: - self.read_pkg_file(open(path)) - else: - self.name = None - self.version = None - self.author = None - self.author_email = None - self.maintainer = None - self.maintainer_email = None - self.url = None - self.license = None - self.description = None - self.long_description = None - self.keywords = None - self.platforms = None - self.classifiers = None - self.download_url = None - # PEP 314 - self.provides = None - self.requires = None - self.obsoletes = None - - def read_pkg_file(self, file): - """Reads the metadata values from a file object.""" - msg = message_from_file(file) - - def _read_field(name): - value = msg[name] - if value == 'UNKNOWN': - return None - return value - - def _read_list(name): - values = msg.get_all(name, None) - if values == []: - return None - return values - - metadata_version = msg['metadata-version'] - self.name = _read_field('name') - self.version = _read_field('version') - self.description = _read_field('summary') - # we are filling author only. - self.author = _read_field('author') - self.maintainer = None - self.author_email = _read_field('author-email') - self.maintainer_email = None - self.url = _read_field('home-page') - self.license = _read_field('license') - - if 'download-url' in msg: - self.download_url = _read_field('download-url') - else: - self.download_url = None - - self.long_description = _read_field('description') - self.description = _read_field('summary') - - if 'keywords' in msg: - self.keywords = _read_field('keywords').split(',') - - self.platforms = _read_list('platform') - self.classifiers = _read_list('classifier') - - # PEP 314 - these fields only exist in 1.1 - if metadata_version == '1.1': - self.requires = _read_list('requires') - self.provides = _read_list('provides') - self.obsoletes = _read_list('obsoletes') - else: - self.requires = None - self.provides = None - self.obsoletes = None - - def write_pkg_info(self, base_dir): - """Write the PKG-INFO file into the release tree. - """ - with open(os.path.join(base_dir, 'PKG-INFO'), 'w', - encoding='UTF-8') as pkg_info: - self.write_pkg_file(pkg_info) - - def write_pkg_file(self, file): - """Write the PKG-INFO format data to a file object. - """ - version = '1.0' - if (self.provides or self.requires or self.obsoletes or - self.classifiers or self.download_url): - version = '1.1' - - file.write('Metadata-Version: %s\n' % version) - file.write('Name: %s\n' % self.get_name()) - file.write('Version: %s\n' % self.get_version()) - file.write('Summary: %s\n' % self.get_description()) - file.write('Home-page: %s\n' % self.get_url()) - file.write('Author: %s\n' % self.get_contact()) - file.write('Author-email: %s\n' % self.get_contact_email()) - file.write('License: %s\n' % self.get_license()) - if self.download_url: - file.write('Download-URL: %s\n' % self.download_url) - - long_desc = rfc822_escape(self.get_long_description()) - file.write('Description: %s\n' % long_desc) - - keywords = ','.join(self.get_keywords()) - if keywords: - file.write('Keywords: %s\n' % keywords) - - self._write_list(file, 'Platform', self.get_platforms()) - self._write_list(file, 'Classifier', self.get_classifiers()) - - # PEP 314 - self._write_list(file, 'Requires', self.get_requires()) - self._write_list(file, 'Provides', self.get_provides()) - self._write_list(file, 'Obsoletes', self.get_obsoletes()) - - def _write_list(self, file, name, values): - for value in values: - file.write('%s: %s\n' % (name, value)) - - # -- Metadata query methods ---------------------------------------- - - def get_name(self): - return self.name or "UNKNOWN" - - def get_version(self): - return self.version or "0.0.0" - - def get_fullname(self): - return "%s-%s" % (self.get_name(), self.get_version()) - - def get_author(self): - return self.author or "UNKNOWN" - - def get_author_email(self): - return self.author_email or "UNKNOWN" - - def get_maintainer(self): - return self.maintainer or "UNKNOWN" - - def get_maintainer_email(self): - return self.maintainer_email or "UNKNOWN" - - def get_contact(self): - return self.maintainer or self.author or "UNKNOWN" - - def get_contact_email(self): - return self.maintainer_email or self.author_email or "UNKNOWN" - - def get_url(self): - return self.url or "UNKNOWN" - - def get_license(self): - return self.license or "UNKNOWN" - get_licence = get_license - - def get_description(self): - return self.description or "UNKNOWN" - - def get_long_description(self): - return self.long_description or "UNKNOWN" - - def get_keywords(self): - return self.keywords or [] - - def get_platforms(self): - return self.platforms or ["UNKNOWN"] - - def get_classifiers(self): - return self.classifiers or [] - - def get_download_url(self): - return self.download_url or "UNKNOWN" - - # PEP 314 - def get_requires(self): - return self.requires or [] - - def set_requires(self, value): - import distutils.versionpredicate - for v in value: - distutils.versionpredicate.VersionPredicate(v) - self.requires = value - - def get_provides(self): - return self.provides or [] - - def set_provides(self, value): - value = [v.strip() for v in value] - for v in value: - import distutils.versionpredicate - distutils.versionpredicate.split_provision(v) - self.provides = value - - def get_obsoletes(self): - return self.obsoletes or [] - - def set_obsoletes(self, value): - import distutils.versionpredicate - for v in value: - distutils.versionpredicate.VersionPredicate(v) - self.obsoletes = value - -def fix_help_options(options): - """Convert a 4-tuple 'help_options' list as found in various command - classes to the 3-tuple form required by FancyGetopt. - """ - new_options = [] - for help_tuple in options: - new_options.append(help_tuple[0:3]) - return new_options diff --git a/Lib/distutils/errors.py b/Lib/distutils/errors.py deleted file mode 100644 index 8b93059e19f..00000000000 --- a/Lib/distutils/errors.py +++ /dev/null @@ -1,97 +0,0 @@ -"""distutils.errors - -Provides exceptions used by the Distutils modules. Note that Distutils -modules may raise standard exceptions; in particular, SystemExit is -usually raised for errors that are obviously the end-user's fault -(eg. bad command-line arguments). - -This module is safe to use in "from ... import *" mode; it only exports -symbols whose names start with "Distutils" and end with "Error".""" - -class DistutilsError (Exception): - """The root of all Distutils evil.""" - pass - -class DistutilsModuleError (DistutilsError): - """Unable to load an expected module, or to find an expected class - within some module (in particular, command modules and classes).""" - pass - -class DistutilsClassError (DistutilsError): - """Some command class (or possibly distribution class, if anyone - feels a need to subclass Distribution) is found not to be holding - up its end of the bargain, ie. implementing some part of the - "command "interface.""" - pass - -class DistutilsGetoptError (DistutilsError): - """The option table provided to 'fancy_getopt()' is bogus.""" - pass - -class DistutilsArgError (DistutilsError): - """Raised by fancy_getopt in response to getopt.error -- ie. an - error in the command line usage.""" - pass - -class DistutilsFileError (DistutilsError): - """Any problems in the filesystem: expected file not found, etc. - Typically this is for problems that we detect before OSError - could be raised.""" - pass - -class DistutilsOptionError (DistutilsError): - """Syntactic/semantic errors in command options, such as use of - mutually conflicting options, or inconsistent options, - badly-spelled values, etc. No distinction is made between option - values originating in the setup script, the command line, config - files, or what-have-you -- but if we *know* something originated in - the setup script, we'll raise DistutilsSetupError instead.""" - pass - -class DistutilsSetupError (DistutilsError): - """For errors that can be definitely blamed on the setup script, - such as invalid keyword arguments to 'setup()'.""" - pass - -class DistutilsPlatformError (DistutilsError): - """We don't know how to do something on the current platform (but - we do know how to do it on some platform) -- eg. trying to compile - C files on a platform not supported by a CCompiler subclass.""" - pass - -class DistutilsExecError (DistutilsError): - """Any problems executing an external program (such as the C - compiler, when compiling C files).""" - pass - -class DistutilsInternalError (DistutilsError): - """Internal inconsistencies or impossibilities (obviously, this - should never be seen if the code is working!).""" - pass - -class DistutilsTemplateError (DistutilsError): - """Syntax error in a file list template.""" - -class DistutilsByteCompileError(DistutilsError): - """Byte compile error.""" - -# Exception classes used by the CCompiler implementation classes -class CCompilerError (Exception): - """Some compile/link operation failed.""" - -class PreprocessError (CCompilerError): - """Failure to preprocess one or more C/C++ files.""" - -class CompileError (CCompilerError): - """Failure to compile one or more C/C++ source files.""" - -class LibError (CCompilerError): - """Failure to create a static library from one or more C/C++ object - files.""" - -class LinkError (CCompilerError): - """Failure to link one or more C/C++ object files into an executable - or shared library file.""" - -class UnknownFileError (CCompilerError): - """Attempt to process an unknown file type.""" diff --git a/Lib/distutils/extension.py b/Lib/distutils/extension.py deleted file mode 100644 index c507da360aa..00000000000 --- a/Lib/distutils/extension.py +++ /dev/null @@ -1,240 +0,0 @@ -"""distutils.extension - -Provides the Extension class, used to describe C/C++ extension -modules in setup scripts.""" - -import os -import warnings - -# This class is really only used by the "build_ext" command, so it might -# make sense to put it in distutils.command.build_ext. However, that -# module is already big enough, and I want to make this class a bit more -# complex to simplify some common cases ("foo" module in "foo.c") and do -# better error-checking ("foo.c" actually exists). -# -# Also, putting this in build_ext.py means every setup script would have to -# import that large-ish module (indirectly, through distutils.core) in -# order to do anything. - -class Extension: - """Just a collection of attributes that describes an extension - module and everything needed to build it (hopefully in a portable - way, but there are hooks that let you be as unportable as you need). - - Instance attributes: - name : string - the full name of the extension, including any packages -- ie. - *not* a filename or pathname, but Python dotted name - sources : [string] - list of source filenames, relative to the distribution root - (where the setup script lives), in Unix form (slash-separated) - for portability. Source files may be C, C++, SWIG (.i), - platform-specific resource files, or whatever else is recognized - by the "build_ext" command as source for a Python extension. - include_dirs : [string] - list of directories to search for C/C++ header files (in Unix - form for portability) - define_macros : [(name : string, value : string|None)] - list of macros to define; each macro is defined using a 2-tuple, - where 'value' is either the string to define it to or None to - define it without a particular value (equivalent of "#define - FOO" in source or -DFOO on Unix C compiler command line) - undef_macros : [string] - list of macros to undefine explicitly - library_dirs : [string] - list of directories to search for C/C++ libraries at link time - libraries : [string] - list of library names (not filenames or paths) to link against - runtime_library_dirs : [string] - list of directories to search for C/C++ libraries at run time - (for shared extensions, this is when the extension is loaded) - extra_objects : [string] - list of extra files to link with (eg. object files not implied - by 'sources', static library that must be explicitly specified, - binary resource files, etc.) - extra_compile_args : [string] - any extra platform- and compiler-specific information to use - when compiling the source files in 'sources'. For platforms and - compilers where "command line" makes sense, this is typically a - list of command-line arguments, but for other platforms it could - be anything. - extra_link_args : [string] - any extra platform- and compiler-specific information to use - when linking object files together to create the extension (or - to create a new static Python interpreter). Similar - interpretation as for 'extra_compile_args'. - export_symbols : [string] - list of symbols to be exported from a shared extension. Not - used on all platforms, and not generally necessary for Python - extensions, which typically export exactly one symbol: "init" + - extension_name. - swig_opts : [string] - any extra options to pass to SWIG if a source file has the .i - extension. - depends : [string] - list of files that the extension depends on - language : string - extension language (i.e. "c", "c++", "objc"). Will be detected - from the source extensions if not provided. - optional : boolean - specifies that a build failure in the extension should not abort the - build process, but simply not install the failing extension. - """ - - # When adding arguments to this constructor, be sure to update - # setup_keywords in core.py. - def __init__(self, name, sources, - include_dirs=None, - define_macros=None, - undef_macros=None, - library_dirs=None, - libraries=None, - runtime_library_dirs=None, - extra_objects=None, - extra_compile_args=None, - extra_link_args=None, - export_symbols=None, - swig_opts = None, - depends=None, - language=None, - optional=None, - **kw # To catch unknown keywords - ): - if not isinstance(name, str): - raise AssertionError("'name' must be a string") - if not (isinstance(sources, list) and - all(isinstance(v, str) for v in sources)): - raise AssertionError("'sources' must be a list of strings") - - self.name = name - self.sources = sources - self.include_dirs = include_dirs or [] - self.define_macros = define_macros or [] - self.undef_macros = undef_macros or [] - self.library_dirs = library_dirs or [] - self.libraries = libraries or [] - self.runtime_library_dirs = runtime_library_dirs or [] - self.extra_objects = extra_objects or [] - self.extra_compile_args = extra_compile_args or [] - self.extra_link_args = extra_link_args or [] - self.export_symbols = export_symbols or [] - self.swig_opts = swig_opts or [] - self.depends = depends or [] - self.language = language - self.optional = optional - - # If there are unknown keyword options, warn about them - if len(kw) > 0: - options = [repr(option) for option in kw] - options = ', '.join(sorted(options)) - msg = "Unknown Extension options: %s" % options - warnings.warn(msg) - - def __repr__(self): - return '<%s.%s(%r) at %#x>' % ( - self.__class__.__module__, - self.__class__.__qualname__, - self.name, - id(self)) - - -def read_setup_file(filename): - """Reads a Setup file and returns Extension instances.""" - from distutils.sysconfig import (parse_makefile, expand_makefile_vars, - _variable_rx) - - from distutils.text_file import TextFile - from distutils.util import split_quoted - - # First pass over the file to gather "VAR = VALUE" assignments. - vars = parse_makefile(filename) - - # Second pass to gobble up the real content: lines of the form - # <module> ... [<sourcefile> ...] [<cpparg> ...] [<library> ...] - file = TextFile(filename, - strip_comments=1, skip_blanks=1, join_lines=1, - lstrip_ws=1, rstrip_ws=1) - try: - extensions = [] - - while True: - line = file.readline() - if line is None: # eof - break - if _variable_rx.match(line): # VAR=VALUE, handled in first pass - continue - - if line[0] == line[-1] == "*": - file.warn("'%s' lines not handled yet" % line) - continue - - line = expand_makefile_vars(line, vars) - words = split_quoted(line) - - # NB. this parses a slightly different syntax than the old - # makesetup script: here, there must be exactly one extension per - # line, and it must be the first word of the line. I have no idea - # why the old syntax supported multiple extensions per line, as - # they all wind up being the same. - - module = words[0] - ext = Extension(module, []) - append_next_word = None - - for word in words[1:]: - if append_next_word is not None: - append_next_word.append(word) - append_next_word = None - continue - - suffix = os.path.splitext(word)[1] - switch = word[0:2] ; value = word[2:] - - if suffix in (".c", ".cc", ".cpp", ".cxx", ".c++", ".m", ".mm"): - # hmm, should we do something about C vs. C++ sources? - # or leave it up to the CCompiler implementation to - # worry about? - ext.sources.append(word) - elif switch == "-I": - ext.include_dirs.append(value) - elif switch == "-D": - equals = value.find("=") - if equals == -1: # bare "-DFOO" -- no value - ext.define_macros.append((value, None)) - else: # "-DFOO=blah" - ext.define_macros.append((value[0:equals], - value[equals+2:])) - elif switch == "-U": - ext.undef_macros.append(value) - elif switch == "-C": # only here 'cause makesetup has it! - ext.extra_compile_args.append(word) - elif switch == "-l": - ext.libraries.append(value) - elif switch == "-L": - ext.library_dirs.append(value) - elif switch == "-R": - ext.runtime_library_dirs.append(value) - elif word == "-rpath": - append_next_word = ext.runtime_library_dirs - elif word == "-Xlinker": - append_next_word = ext.extra_link_args - elif word == "-Xcompiler": - append_next_word = ext.extra_compile_args - elif switch == "-u": - ext.extra_link_args.append(word) - if not value: - append_next_word = ext.extra_link_args - elif suffix in (".a", ".so", ".sl", ".o", ".dylib"): - # NB. a really faithful emulation of makesetup would - # append a .o file to extra_objects only if it - # had a slash in it; otherwise, it would s/.o/.c/ - # and append it to sources. Hmmmm. - ext.extra_objects.append(word) - else: - file.warn("unrecognized argument '%s'" % word) - - extensions.append(ext) - finally: - file.close() - - return extensions diff --git a/Lib/distutils/fancy_getopt.py b/Lib/distutils/fancy_getopt.py deleted file mode 100644 index 7d170dd2773..00000000000 --- a/Lib/distutils/fancy_getopt.py +++ /dev/null @@ -1,457 +0,0 @@ -"""distutils.fancy_getopt - -Wrapper around the standard getopt module that provides the following -additional features: - * short and long options are tied together - * options have help strings, so fancy_getopt could potentially - create a complete usage summary - * options set attributes of a passed-in object -""" - -import sys, string, re -import getopt -from distutils.errors import * - -# Much like command_re in distutils.core, this is close to but not quite -# the same as a Python NAME -- except, in the spirit of most GNU -# utilities, we use '-' in place of '_'. (The spirit of LISP lives on!) -# The similarities to NAME are again not a coincidence... -longopt_pat = r'[a-zA-Z](?:[a-zA-Z0-9-]*)' -longopt_re = re.compile(r'^%s$' % longopt_pat) - -# For recognizing "negative alias" options, eg. "quiet=!verbose" -neg_alias_re = re.compile("^(%s)=!(%s)$" % (longopt_pat, longopt_pat)) - -# This is used to translate long options to legitimate Python identifiers -# (for use as attributes of some object). -longopt_xlate = str.maketrans('-', '_') - -class FancyGetopt: - """Wrapper around the standard 'getopt()' module that provides some - handy extra functionality: - * short and long options are tied together - * options have help strings, and help text can be assembled - from them - * options set attributes of a passed-in object - * boolean options can have "negative aliases" -- eg. if - --quiet is the "negative alias" of --verbose, then "--quiet" - on the command line sets 'verbose' to false - """ - - def __init__(self, option_table=None): - # The option table is (currently) a list of tuples. The - # tuples may have 3 or four values: - # (long_option, short_option, help_string [, repeatable]) - # if an option takes an argument, its long_option should have '=' - # appended; short_option should just be a single character, no ':' - # in any case. If a long_option doesn't have a corresponding - # short_option, short_option should be None. All option tuples - # must have long options. - self.option_table = option_table - - # 'option_index' maps long option names to entries in the option - # table (ie. those 3-tuples). - self.option_index = {} - if self.option_table: - self._build_index() - - # 'alias' records (duh) alias options; {'foo': 'bar'} means - # --foo is an alias for --bar - self.alias = {} - - # 'negative_alias' keeps track of options that are the boolean - # opposite of some other option - self.negative_alias = {} - - # These keep track of the information in the option table. We - # don't actually populate these structures until we're ready to - # parse the command-line, since the 'option_table' passed in here - # isn't necessarily the final word. - self.short_opts = [] - self.long_opts = [] - self.short2long = {} - self.attr_name = {} - self.takes_arg = {} - - # And 'option_order' is filled up in 'getopt()'; it records the - # original order of options (and their values) on the command-line, - # but expands short options, converts aliases, etc. - self.option_order = [] - - def _build_index(self): - self.option_index.clear() - for option in self.option_table: - self.option_index[option[0]] = option - - def set_option_table(self, option_table): - self.option_table = option_table - self._build_index() - - def add_option(self, long_option, short_option=None, help_string=None): - if long_option in self.option_index: - raise DistutilsGetoptError( - "option conflict: already an option '%s'" % long_option) - else: - option = (long_option, short_option, help_string) - self.option_table.append(option) - self.option_index[long_option] = option - - def has_option(self, long_option): - """Return true if the option table for this parser has an - option with long name 'long_option'.""" - return long_option in self.option_index - - def get_attr_name(self, long_option): - """Translate long option name 'long_option' to the form it - has as an attribute of some object: ie., translate hyphens - to underscores.""" - return long_option.translate(longopt_xlate) - - def _check_alias_dict(self, aliases, what): - assert isinstance(aliases, dict) - for (alias, opt) in aliases.items(): - if alias not in self.option_index: - raise DistutilsGetoptError(("invalid %s '%s': " - "option '%s' not defined") % (what, alias, alias)) - if opt not in self.option_index: - raise DistutilsGetoptError(("invalid %s '%s': " - "aliased option '%s' not defined") % (what, alias, opt)) - - def set_aliases(self, alias): - """Set the aliases for this option parser.""" - self._check_alias_dict(alias, "alias") - self.alias = alias - - def set_negative_aliases(self, negative_alias): - """Set the negative aliases for this option parser. - 'negative_alias' should be a dictionary mapping option names to - option names, both the key and value must already be defined - in the option table.""" - self._check_alias_dict(negative_alias, "negative alias") - self.negative_alias = negative_alias - - def _grok_option_table(self): - """Populate the various data structures that keep tabs on the - option table. Called by 'getopt()' before it can do anything - worthwhile. - """ - self.long_opts = [] - self.short_opts = [] - self.short2long.clear() - self.repeat = {} - - for option in self.option_table: - if len(option) == 3: - long, short, help = option - repeat = 0 - elif len(option) == 4: - long, short, help, repeat = option - else: - # the option table is part of the code, so simply - # assert that it is correct - raise ValueError("invalid option tuple: %r" % (option,)) - - # Type- and value-check the option names - if not isinstance(long, str) or len(long) < 2: - raise DistutilsGetoptError(("invalid long option '%s': " - "must be a string of length >= 2") % long) - - if (not ((short is None) or - (isinstance(short, str) and len(short) == 1))): - raise DistutilsGetoptError("invalid short option '%s': " - "must a single character or None" % short) - - self.repeat[long] = repeat - self.long_opts.append(long) - - if long[-1] == '=': # option takes an argument? - if short: short = short + ':' - long = long[0:-1] - self.takes_arg[long] = 1 - else: - # Is option is a "negative alias" for some other option (eg. - # "quiet" == "!verbose")? - alias_to = self.negative_alias.get(long) - if alias_to is not None: - if self.takes_arg[alias_to]: - raise DistutilsGetoptError( - "invalid negative alias '%s': " - "aliased option '%s' takes a value" - % (long, alias_to)) - - self.long_opts[-1] = long # XXX redundant?! - self.takes_arg[long] = 0 - - # If this is an alias option, make sure its "takes arg" flag is - # the same as the option it's aliased to. - alias_to = self.alias.get(long) - if alias_to is not None: - if self.takes_arg[long] != self.takes_arg[alias_to]: - raise DistutilsGetoptError( - "invalid alias '%s': inconsistent with " - "aliased option '%s' (one of them takes a value, " - "the other doesn't" - % (long, alias_to)) - - # Now enforce some bondage on the long option name, so we can - # later translate it to an attribute name on some object. Have - # to do this a bit late to make sure we've removed any trailing - # '='. - if not longopt_re.match(long): - raise DistutilsGetoptError( - "invalid long option name '%s' " - "(must be letters, numbers, hyphens only" % long) - - self.attr_name[long] = self.get_attr_name(long) - if short: - self.short_opts.append(short) - self.short2long[short[0]] = long - - def getopt(self, args=None, object=None): - """Parse command-line options in args. Store as attributes on object. - - If 'args' is None or not supplied, uses 'sys.argv[1:]'. If - 'object' is None or not supplied, creates a new OptionDummy - object, stores option values there, and returns a tuple (args, - object). If 'object' is supplied, it is modified in place and - 'getopt()' just returns 'args'; in both cases, the returned - 'args' is a modified copy of the passed-in 'args' list, which - is left untouched. - """ - if args is None: - args = sys.argv[1:] - if object is None: - object = OptionDummy() - created_object = True - else: - created_object = False - - self._grok_option_table() - - short_opts = ' '.join(self.short_opts) - try: - opts, args = getopt.getopt(args, short_opts, self.long_opts) - except getopt.error as msg: - raise DistutilsArgError(msg) - - for opt, val in opts: - if len(opt) == 2 and opt[0] == '-': # it's a short option - opt = self.short2long[opt[1]] - else: - assert len(opt) > 2 and opt[:2] == '--' - opt = opt[2:] - - alias = self.alias.get(opt) - if alias: - opt = alias - - if not self.takes_arg[opt]: # boolean option? - assert val == '', "boolean option can't have value" - alias = self.negative_alias.get(opt) - if alias: - opt = alias - val = 0 - else: - val = 1 - - attr = self.attr_name[opt] - # The only repeating option at the moment is 'verbose'. - # It has a negative option -q quiet, which should set verbose = 0. - if val and self.repeat.get(attr) is not None: - val = getattr(object, attr, 0) + 1 - setattr(object, attr, val) - self.option_order.append((opt, val)) - - # for opts - if created_object: - return args, object - else: - return args - - def get_option_order(self): - """Returns the list of (option, value) tuples processed by the - previous run of 'getopt()'. Raises RuntimeError if - 'getopt()' hasn't been called yet. - """ - if self.option_order is None: - raise RuntimeError("'getopt()' hasn't been called yet") - else: - return self.option_order - - def generate_help(self, header=None): - """Generate help text (a list of strings, one per suggested line of - output) from the option table for this FancyGetopt object. - """ - # Blithely assume the option table is good: probably wouldn't call - # 'generate_help()' unless you've already called 'getopt()'. - - # First pass: determine maximum length of long option names - max_opt = 0 - for option in self.option_table: - long = option[0] - short = option[1] - l = len(long) - if long[-1] == '=': - l = l - 1 - if short is not None: - l = l + 5 # " (-x)" where short == 'x' - if l > max_opt: - max_opt = l - - opt_width = max_opt + 2 + 2 + 2 # room for indent + dashes + gutter - - # Typical help block looks like this: - # --foo controls foonabulation - # Help block for longest option looks like this: - # --flimflam set the flim-flam level - # and with wrapped text: - # --flimflam set the flim-flam level (must be between - # 0 and 100, except on Tuesdays) - # Options with short names will have the short name shown (but - # it doesn't contribute to max_opt): - # --foo (-f) controls foonabulation - # If adding the short option would make the left column too wide, - # we push the explanation off to the next line - # --flimflam (-l) - # set the flim-flam level - # Important parameters: - # - 2 spaces before option block start lines - # - 2 dashes for each long option name - # - min. 2 spaces between option and explanation (gutter) - # - 5 characters (incl. space) for short option name - - # Now generate lines of help text. (If 80 columns were good enough - # for Jesus, then 78 columns are good enough for me!) - line_width = 78 - text_width = line_width - opt_width - big_indent = ' ' * opt_width - if header: - lines = [header] - else: - lines = ['Option summary:'] - - for option in self.option_table: - long, short, help = option[:3] - text = wrap_text(help, text_width) - if long[-1] == '=': - long = long[0:-1] - - # Case 1: no short option at all (makes life easy) - if short is None: - if text: - lines.append(" --%-*s %s" % (max_opt, long, text[0])) - else: - lines.append(" --%-*s " % (max_opt, long)) - - # Case 2: we have a short option, so we have to include it - # just after the long option - else: - opt_names = "%s (-%s)" % (long, short) - if text: - lines.append(" --%-*s %s" % - (max_opt, opt_names, text[0])) - else: - lines.append(" --%-*s" % opt_names) - - for l in text[1:]: - lines.append(big_indent + l) - return lines - - def print_help(self, header=None, file=None): - if file is None: - file = sys.stdout - for line in self.generate_help(header): - file.write(line + "\n") - - -def fancy_getopt(options, negative_opt, object, args): - parser = FancyGetopt(options) - parser.set_negative_aliases(negative_opt) - return parser.getopt(args, object) - - -WS_TRANS = {ord(_wschar) : ' ' for _wschar in string.whitespace} - -def wrap_text(text, width): - """wrap_text(text : string, width : int) -> [string] - - Split 'text' into multiple lines of no more than 'width' characters - each, and return the list of strings that results. - """ - if text is None: - return [] - if len(text) <= width: - return [text] - - text = text.expandtabs() - text = text.translate(WS_TRANS) - chunks = re.split(r'( +|-+)', text) - chunks = [ch for ch in chunks if ch] # ' - ' results in empty strings - lines = [] - - while chunks: - cur_line = [] # list of chunks (to-be-joined) - cur_len = 0 # length of current line - - while chunks: - l = len(chunks[0]) - if cur_len + l <= width: # can squeeze (at least) this chunk in - cur_line.append(chunks[0]) - del chunks[0] - cur_len = cur_len + l - else: # this line is full - # drop last chunk if all space - if cur_line and cur_line[-1][0] == ' ': - del cur_line[-1] - break - - if chunks: # any chunks left to process? - # if the current line is still empty, then we had a single - # chunk that's too big too fit on a line -- so we break - # down and break it up at the line width - if cur_len == 0: - cur_line.append(chunks[0][0:width]) - chunks[0] = chunks[0][width:] - - # all-whitespace chunks at the end of a line can be discarded - # (and we know from the re.split above that if a chunk has - # *any* whitespace, it is *all* whitespace) - if chunks[0][0] == ' ': - del chunks[0] - - # and store this line in the list-of-all-lines -- as a single - # string, of course! - lines.append(''.join(cur_line)) - - return lines - - -def translate_longopt(opt): - """Convert a long option name to a valid Python identifier by - changing "-" to "_". - """ - return opt.translate(longopt_xlate) - - -class OptionDummy: - """Dummy class just used as a place to hold command-line option - values as instance attributes.""" - - def __init__(self, options=[]): - """Create a new OptionDummy instance. The attributes listed in - 'options' will be initialized to None.""" - for opt in options: - setattr(self, opt, None) - - -if __name__ == "__main__": - text = """\ -Tra-la-la, supercalifragilisticexpialidocious. -How *do* you spell that odd word, anyways? -(Someone ask Mary -- she'll know [or she'll -say, "How should I know?"].)""" - - for w in (10, 20, 30, 40): - print("width: %d" % w) - print("\n".join(wrap_text(text, w))) - print() diff --git a/Lib/distutils/file_util.py b/Lib/distutils/file_util.py deleted file mode 100644 index b3fee35a6cc..00000000000 --- a/Lib/distutils/file_util.py +++ /dev/null @@ -1,238 +0,0 @@ -"""distutils.file_util - -Utility functions for operating on single files. -""" - -import os -from distutils.errors import DistutilsFileError -from distutils import log - -# for generating verbose output in 'copy_file()' -_copy_action = { None: 'copying', - 'hard': 'hard linking', - 'sym': 'symbolically linking' } - - -def _copy_file_contents(src, dst, buffer_size=16*1024): - """Copy the file 'src' to 'dst'; both must be filenames. Any error - opening either file, reading from 'src', or writing to 'dst', raises - DistutilsFileError. Data is read/written in chunks of 'buffer_size' - bytes (default 16k). No attempt is made to handle anything apart from - regular files. - """ - # Stolen from shutil module in the standard library, but with - # custom error-handling added. - fsrc = None - fdst = None - try: - try: - fsrc = open(src, 'rb') - except OSError as e: - raise DistutilsFileError("could not open '%s': %s" % (src, e.strerror)) - - if os.path.exists(dst): - try: - os.unlink(dst) - except OSError as e: - raise DistutilsFileError( - "could not delete '%s': %s" % (dst, e.strerror)) - - try: - fdst = open(dst, 'wb') - except OSError as e: - raise DistutilsFileError( - "could not create '%s': %s" % (dst, e.strerror)) - - while True: - try: - buf = fsrc.read(buffer_size) - except OSError as e: - raise DistutilsFileError( - "could not read from '%s': %s" % (src, e.strerror)) - - if not buf: - break - - try: - fdst.write(buf) - except OSError as e: - raise DistutilsFileError( - "could not write to '%s': %s" % (dst, e.strerror)) - finally: - if fdst: - fdst.close() - if fsrc: - fsrc.close() - -def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0, - link=None, verbose=1, dry_run=0): - """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is - copied there with the same name; otherwise, it must be a filename. (If - the file exists, it will be ruthlessly clobbered.) If 'preserve_mode' - is true (the default), the file's mode (type and permission bits, or - whatever is analogous on the current platform) is copied. If - 'preserve_times' is true (the default), the last-modified and - last-access times are copied as well. If 'update' is true, 'src' will - only be copied if 'dst' does not exist, or if 'dst' does exist but is - older than 'src'. - - 'link' allows you to make hard links (os.link) or symbolic links - (os.symlink) instead of copying: set it to "hard" or "sym"; if it is - None (the default), files are copied. Don't set 'link' on systems that - don't support it: 'copy_file()' doesn't check if hard or symbolic - linking is available. If hardlink fails, falls back to - _copy_file_contents(). - - Under Mac OS, uses the native file copy function in macostools; on - other systems, uses '_copy_file_contents()' to copy file contents. - - Return a tuple (dest_name, copied): 'dest_name' is the actual name of - the output file, and 'copied' is true if the file was copied (or would - have been copied, if 'dry_run' true). - """ - # XXX if the destination file already exists, we clobber it if - # copying, but blow up if linking. Hmmm. And I don't know what - # macostools.copyfile() does. Should definitely be consistent, and - # should probably blow up if destination exists and we would be - # changing it (ie. it's not already a hard/soft link to src OR - # (not update) and (src newer than dst). - - from distutils.dep_util import newer - from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE - - if not os.path.isfile(src): - raise DistutilsFileError( - "can't copy '%s': doesn't exist or not a regular file" % src) - - if os.path.isdir(dst): - dir = dst - dst = os.path.join(dst, os.path.basename(src)) - else: - dir = os.path.dirname(dst) - - if update and not newer(src, dst): - if verbose >= 1: - log.debug("not copying %s (output up-to-date)", src) - return (dst, 0) - - try: - action = _copy_action[link] - except KeyError: - raise ValueError("invalid value '%s' for 'link' argument" % link) - - if verbose >= 1: - if os.path.basename(dst) == os.path.basename(src): - log.info("%s %s -> %s", action, src, dir) - else: - log.info("%s %s -> %s", action, src, dst) - - if dry_run: - return (dst, 1) - - # If linking (hard or symbolic), use the appropriate system call - # (Unix only, of course, but that's the caller's responsibility) - elif link == 'hard': - if not (os.path.exists(dst) and os.path.samefile(src, dst)): - try: - os.link(src, dst) - return (dst, 1) - except OSError: - # If hard linking fails, fall back on copying file - # (some special filesystems don't support hard linking - # even under Unix, see issue #8876). - pass - elif link == 'sym': - if not (os.path.exists(dst) and os.path.samefile(src, dst)): - os.symlink(src, dst) - return (dst, 1) - - # Otherwise (non-Mac, not linking), copy the file contents and - # (optionally) copy the times and mode. - _copy_file_contents(src, dst) - if preserve_mode or preserve_times: - st = os.stat(src) - - # According to David Ascher <da@ski.org>, utime() should be done - # before chmod() (at least under NT). - if preserve_times: - os.utime(dst, (st[ST_ATIME], st[ST_MTIME])) - if preserve_mode: - os.chmod(dst, S_IMODE(st[ST_MODE])) - - return (dst, 1) - - -# XXX I suspect this is Unix-specific -- need porting help! -def move_file (src, dst, - verbose=1, - dry_run=0): - - """Move a file 'src' to 'dst'. If 'dst' is a directory, the file will - be moved into it with the same name; otherwise, 'src' is just renamed - to 'dst'. Return the new full name of the file. - - Handles cross-device moves on Unix using 'copy_file()'. What about - other systems??? - """ - from os.path import exists, isfile, isdir, basename, dirname - import errno - - if verbose >= 1: - log.info("moving %s -> %s", src, dst) - - if dry_run: - return dst - - if not isfile(src): - raise DistutilsFileError("can't move '%s': not a regular file" % src) - - if isdir(dst): - dst = os.path.join(dst, basename(src)) - elif exists(dst): - raise DistutilsFileError( - "can't move '%s': destination '%s' already exists" % - (src, dst)) - - if not isdir(dirname(dst)): - raise DistutilsFileError( - "can't move '%s': destination '%s' not a valid path" % - (src, dst)) - - copy_it = False - try: - os.rename(src, dst) - except OSError as e: - (num, msg) = e.args - if num == errno.EXDEV: - copy_it = True - else: - raise DistutilsFileError( - "couldn't move '%s' to '%s': %s" % (src, dst, msg)) - - if copy_it: - copy_file(src, dst, verbose=verbose) - try: - os.unlink(src) - except OSError as e: - (num, msg) = e.args - try: - os.unlink(dst) - except OSError: - pass - raise DistutilsFileError( - "couldn't move '%s' to '%s' by copy/delete: " - "delete '%s' failed: %s" - % (src, dst, src, msg)) - return dst - - -def write_file (filename, contents): - """Create a file with the specified name and write 'contents' (a - sequence of strings without line terminators) to it. - """ - f = open(filename, "w") - try: - for line in contents: - f.write(line + "\n") - finally: - f.close() diff --git a/Lib/distutils/filelist.py b/Lib/distutils/filelist.py deleted file mode 100644 index c92d5fdba39..00000000000 --- a/Lib/distutils/filelist.py +++ /dev/null @@ -1,327 +0,0 @@ -"""distutils.filelist - -Provides the FileList class, used for poking about the filesystem -and building lists of files. -""" - -import os, re -import fnmatch -import functools -from distutils.util import convert_path -from distutils.errors import DistutilsTemplateError, DistutilsInternalError -from distutils import log - -class FileList: - """A list of files built by on exploring the filesystem and filtered by - applying various patterns to what we find there. - - Instance attributes: - dir - directory from which files will be taken -- only used if - 'allfiles' not supplied to constructor - files - list of filenames currently being built/filtered/manipulated - allfiles - complete list of files under consideration (ie. without any - filtering applied) - """ - - def __init__(self, warn=None, debug_print=None): - # ignore argument to FileList, but keep them for backwards - # compatibility - self.allfiles = None - self.files = [] - - def set_allfiles(self, allfiles): - self.allfiles = allfiles - - def findall(self, dir=os.curdir): - self.allfiles = findall(dir) - - def debug_print(self, msg): - """Print 'msg' to stdout if the global DEBUG (taken from the - DISTUTILS_DEBUG environment variable) flag is true. - """ - from distutils.debug import DEBUG - if DEBUG: - print(msg) - - # -- List-like methods --------------------------------------------- - - def append(self, item): - self.files.append(item) - - def extend(self, items): - self.files.extend(items) - - def sort(self): - # Not a strict lexical sort! - sortable_files = sorted(map(os.path.split, self.files)) - self.files = [] - for sort_tuple in sortable_files: - self.files.append(os.path.join(*sort_tuple)) - - - # -- Other miscellaneous utility methods --------------------------- - - def remove_duplicates(self): - # Assumes list has been sorted! - for i in range(len(self.files) - 1, 0, -1): - if self.files[i] == self.files[i - 1]: - del self.files[i] - - - # -- "File template" methods --------------------------------------- - - def _parse_template_line(self, line): - words = line.split() - action = words[0] - - patterns = dir = dir_pattern = None - - if action in ('include', 'exclude', - 'global-include', 'global-exclude'): - if len(words) < 2: - raise DistutilsTemplateError( - "'%s' expects <pattern1> <pattern2> ..." % action) - patterns = [convert_path(w) for w in words[1:]] - elif action in ('recursive-include', 'recursive-exclude'): - if len(words) < 3: - raise DistutilsTemplateError( - "'%s' expects <dir> <pattern1> <pattern2> ..." % action) - dir = convert_path(words[1]) - patterns = [convert_path(w) for w in words[2:]] - elif action in ('graft', 'prune'): - if len(words) != 2: - raise DistutilsTemplateError( - "'%s' expects a single <dir_pattern>" % action) - dir_pattern = convert_path(words[1]) - else: - raise DistutilsTemplateError("unknown action '%s'" % action) - - return (action, patterns, dir, dir_pattern) - - def process_template_line(self, line): - # Parse the line: split it up, make sure the right number of words - # is there, and return the relevant words. 'action' is always - # defined: it's the first word of the line. Which of the other - # three are defined depends on the action; it'll be either - # patterns, (dir and patterns), or (dir_pattern). - (action, patterns, dir, dir_pattern) = self._parse_template_line(line) - - # OK, now we know that the action is valid and we have the - # right number of words on the line for that action -- so we - # can proceed with minimal error-checking. - if action == 'include': - self.debug_print("include " + ' '.join(patterns)) - for pattern in patterns: - if not self.include_pattern(pattern, anchor=1): - log.warn("warning: no files found matching '%s'", - pattern) - - elif action == 'exclude': - self.debug_print("exclude " + ' '.join(patterns)) - for pattern in patterns: - if not self.exclude_pattern(pattern, anchor=1): - log.warn(("warning: no previously-included files " - "found matching '%s'"), pattern) - - elif action == 'global-include': - self.debug_print("global-include " + ' '.join(patterns)) - for pattern in patterns: - if not self.include_pattern(pattern, anchor=0): - log.warn(("warning: no files found matching '%s' " - "anywhere in distribution"), pattern) - - elif action == 'global-exclude': - self.debug_print("global-exclude " + ' '.join(patterns)) - for pattern in patterns: - if not self.exclude_pattern(pattern, anchor=0): - log.warn(("warning: no previously-included files matching " - "'%s' found anywhere in distribution"), - pattern) - - elif action == 'recursive-include': - self.debug_print("recursive-include %s %s" % - (dir, ' '.join(patterns))) - for pattern in patterns: - if not self.include_pattern(pattern, prefix=dir): - log.warn(("warning: no files found matching '%s' " - "under directory '%s'"), - pattern, dir) - - elif action == 'recursive-exclude': - self.debug_print("recursive-exclude %s %s" % - (dir, ' '.join(patterns))) - for pattern in patterns: - if not self.exclude_pattern(pattern, prefix=dir): - log.warn(("warning: no previously-included files matching " - "'%s' found under directory '%s'"), - pattern, dir) - - elif action == 'graft': - self.debug_print("graft " + dir_pattern) - if not self.include_pattern(None, prefix=dir_pattern): - log.warn("warning: no directories found matching '%s'", - dir_pattern) - - elif action == 'prune': - self.debug_print("prune " + dir_pattern) - if not self.exclude_pattern(None, prefix=dir_pattern): - log.warn(("no previously-included directories found " - "matching '%s'"), dir_pattern) - else: - raise DistutilsInternalError( - "this cannot happen: invalid action '%s'" % action) - - - # -- Filtering/selection methods ----------------------------------- - - def include_pattern(self, pattern, anchor=1, prefix=None, is_regex=0): - """Select strings (presumably filenames) from 'self.files' that - match 'pattern', a Unix-style wildcard (glob) pattern. Patterns - are not quite the same as implemented by the 'fnmatch' module: '*' - and '?' match non-special characters, where "special" is platform- - dependent: slash on Unix; colon, slash, and backslash on - DOS/Windows; and colon on Mac OS. - - If 'anchor' is true (the default), then the pattern match is more - stringent: "*.py" will match "foo.py" but not "foo/bar.py". If - 'anchor' is false, both of these will match. - - If 'prefix' is supplied, then only filenames starting with 'prefix' - (itself a pattern) and ending with 'pattern', with anything in between - them, will match. 'anchor' is ignored in this case. - - If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and - 'pattern' is assumed to be either a string containing a regex or a - regex object -- no translation is done, the regex is just compiled - and used as-is. - - Selected strings will be added to self.files. - - Return True if files are found, False otherwise. - """ - # XXX docstring lying about what the special chars are? - files_found = False - pattern_re = translate_pattern(pattern, anchor, prefix, is_regex) - self.debug_print("include_pattern: applying regex r'%s'" % - pattern_re.pattern) - - # delayed loading of allfiles list - if self.allfiles is None: - self.findall() - - for name in self.allfiles: - if pattern_re.search(name): - self.debug_print(" adding " + name) - self.files.append(name) - files_found = True - return files_found - - - def exclude_pattern (self, pattern, - anchor=1, prefix=None, is_regex=0): - """Remove strings (presumably filenames) from 'files' that match - 'pattern'. Other parameters are the same as for - 'include_pattern()', above. - The list 'self.files' is modified in place. - Return True if files are found, False otherwise. - """ - files_found = False - pattern_re = translate_pattern(pattern, anchor, prefix, is_regex) - self.debug_print("exclude_pattern: applying regex r'%s'" % - pattern_re.pattern) - for i in range(len(self.files)-1, -1, -1): - if pattern_re.search(self.files[i]): - self.debug_print(" removing " + self.files[i]) - del self.files[i] - files_found = True - return files_found - - -# ---------------------------------------------------------------------- -# Utility functions - -def _find_all_simple(path): - """ - Find all files under 'path' - """ - results = ( - os.path.join(base, file) - for base, dirs, files in os.walk(path, followlinks=True) - for file in files - ) - return filter(os.path.isfile, results) - - -def findall(dir=os.curdir): - """ - Find all files under 'dir' and return the list of full filenames. - Unless dir is '.', return full filenames with dir prepended. - """ - files = _find_all_simple(dir) - if dir == os.curdir: - make_rel = functools.partial(os.path.relpath, start=dir) - files = map(make_rel, files) - return list(files) - - -def glob_to_re(pattern): - """Translate a shell-like glob pattern to a regular expression; return - a string containing the regex. Differs from 'fnmatch.translate()' in - that '*' does not match "special characters" (which are - platform-specific). - """ - pattern_re = fnmatch.translate(pattern) - - # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which - # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix, - # and by extension they shouldn't match such "special characters" under - # any OS. So change all non-escaped dots in the RE to match any - # character except the special characters (currently: just os.sep). - sep = os.sep - if os.sep == '\\': - # we're using a regex to manipulate a regex, so we need - # to escape the backslash twice - sep = r'\\\\' - escaped = r'\1[^%s]' % sep - pattern_re = re.sub(r'((?<!\\)(\\\\)*)\.', escaped, pattern_re) - return pattern_re - - -def translate_pattern(pattern, anchor=1, prefix=None, is_regex=0): - """Translate a shell-like wildcard pattern to a compiled regular - expression. Return the compiled regex. If 'is_regex' true, - then 'pattern' is directly compiled to a regex (if it's a string) - or just returned as-is (assumes it's a regex object). - """ - if is_regex: - if isinstance(pattern, str): - return re.compile(pattern) - else: - return pattern - - # ditch start and end characters - start, _, end = glob_to_re('_').partition('_') - - if pattern: - pattern_re = glob_to_re(pattern) - assert pattern_re.startswith(start) and pattern_re.endswith(end) - else: - pattern_re = '' - - if prefix is not None: - prefix_re = glob_to_re(prefix) - assert prefix_re.startswith(start) and prefix_re.endswith(end) - prefix_re = prefix_re[len(start): len(prefix_re) - len(end)] - sep = os.sep - if os.sep == '\\': - sep = r'\\' - pattern_re = pattern_re[len(start): len(pattern_re) - len(end)] - pattern_re = r'%s\A%s%s.*%s%s' % (start, prefix_re, sep, pattern_re, end) - else: # no prefix -- respect anchor flag - if anchor: - pattern_re = r'%s\A%s' % (start, pattern_re[len(start):]) - - return re.compile(pattern_re) diff --git a/Lib/distutils/log.py b/Lib/distutils/log.py deleted file mode 100644 index 8ef6b28ea2e..00000000000 --- a/Lib/distutils/log.py +++ /dev/null @@ -1,77 +0,0 @@ -"""A simple log mechanism styled after PEP 282.""" - -# The class here is styled after PEP 282 so that it could later be -# replaced with a standard Python logging implementation. - -DEBUG = 1 -INFO = 2 -WARN = 3 -ERROR = 4 -FATAL = 5 - -import sys - -class Log: - - def __init__(self, threshold=WARN): - self.threshold = threshold - - def _log(self, level, msg, args): - if level not in (DEBUG, INFO, WARN, ERROR, FATAL): - raise ValueError('%s wrong log level' % str(level)) - - if level >= self.threshold: - if args: - msg = msg % args - if level in (WARN, ERROR, FATAL): - stream = sys.stderr - else: - stream = sys.stdout - try: - stream.write('%s\n' % msg) - except UnicodeEncodeError: - # emulate backslashreplace error handler - encoding = stream.encoding - msg = msg.encode(encoding, "backslashreplace").decode(encoding) - stream.write('%s\n' % msg) - stream.flush() - - def log(self, level, msg, *args): - self._log(level, msg, args) - - def debug(self, msg, *args): - self._log(DEBUG, msg, args) - - def info(self, msg, *args): - self._log(INFO, msg, args) - - def warn(self, msg, *args): - self._log(WARN, msg, args) - - def error(self, msg, *args): - self._log(ERROR, msg, args) - - def fatal(self, msg, *args): - self._log(FATAL, msg, args) - -_global_log = Log() -log = _global_log.log -debug = _global_log.debug -info = _global_log.info -warn = _global_log.warn -error = _global_log.error -fatal = _global_log.fatal - -def set_threshold(level): - # return the old threshold for use from tests - old = _global_log.threshold - _global_log.threshold = level - return old - -def set_verbosity(v): - if v <= 0: - set_threshold(WARN) - elif v == 1: - set_threshold(INFO) - elif v >= 2: - set_threshold(DEBUG) diff --git a/Lib/distutils/msvc9compiler.py b/Lib/distutils/msvc9compiler.py deleted file mode 100644 index 21191276227..00000000000 --- a/Lib/distutils/msvc9compiler.py +++ /dev/null @@ -1,791 +0,0 @@ -"""distutils.msvc9compiler - -Contains MSVCCompiler, an implementation of the abstract CCompiler class -for the Microsoft Visual Studio 2008. - -The module is compatible with VS 2005 and VS 2008. You can find legacy support -for older versions of VS in distutils.msvccompiler. -""" - -# Written by Perry Stoll -# hacked by Robin Becker and Thomas Heller to do a better job of -# finding DevStudio (through the registry) -# ported to VS2005 and VS 2008 by Christian Heimes - -import os -import subprocess -import sys -import re - -from distutils.errors import DistutilsExecError, DistutilsPlatformError, \ - CompileError, LibError, LinkError -from distutils.ccompiler import CCompiler, gen_preprocess_options, \ - gen_lib_options -from distutils import log -from distutils.util import get_platform - -import winreg - -RegOpenKeyEx = winreg.OpenKeyEx -RegEnumKey = winreg.EnumKey -RegEnumValue = winreg.EnumValue -RegError = winreg.error - -HKEYS = (winreg.HKEY_USERS, - winreg.HKEY_CURRENT_USER, - winreg.HKEY_LOCAL_MACHINE, - winreg.HKEY_CLASSES_ROOT) - -NATIVE_WIN64 = (sys.platform == 'win32' and sys.maxsize > 2**32) -if NATIVE_WIN64: - # Visual C++ is a 32-bit application, so we need to look in - # the corresponding registry branch, if we're running a - # 64-bit Python on Win64 - VS_BASE = r"Software\Wow6432Node\Microsoft\VisualStudio\%0.1f" - WINSDK_BASE = r"Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows" - NET_BASE = r"Software\Wow6432Node\Microsoft\.NETFramework" -else: - VS_BASE = r"Software\Microsoft\VisualStudio\%0.1f" - WINSDK_BASE = r"Software\Microsoft\Microsoft SDKs\Windows" - NET_BASE = r"Software\Microsoft\.NETFramework" - -# A map keyed by get_platform() return values to values accepted by -# 'vcvarsall.bat'. Note a cross-compile may combine these (eg, 'x86_amd64' is -# the param to cross-compile on x86 targeting amd64.) -PLAT_TO_VCVARS = { - 'win32' : 'x86', - 'win-amd64' : 'amd64', - 'win-ia64' : 'ia64', -} - -class Reg: - """Helper class to read values from the registry - """ - - def get_value(cls, path, key): - for base in HKEYS: - d = cls.read_values(base, path) - if d and key in d: - return d[key] - raise KeyError(key) - get_value = classmethod(get_value) - - def read_keys(cls, base, key): - """Return list of registry keys.""" - try: - handle = RegOpenKeyEx(base, key) - except RegError: - return None - L = [] - i = 0 - while True: - try: - k = RegEnumKey(handle, i) - except RegError: - break - L.append(k) - i += 1 - return L - read_keys = classmethod(read_keys) - - def read_values(cls, base, key): - """Return dict of registry keys and values. - - All names are converted to lowercase. - """ - try: - handle = RegOpenKeyEx(base, key) - except RegError: - return None - d = {} - i = 0 - while True: - try: - name, value, type = RegEnumValue(handle, i) - except RegError: - break - name = name.lower() - d[cls.convert_mbcs(name)] = cls.convert_mbcs(value) - i += 1 - return d - read_values = classmethod(read_values) - - def convert_mbcs(s): - dec = getattr(s, "decode", None) - if dec is not None: - try: - s = dec("mbcs") - except UnicodeError: - pass - return s - convert_mbcs = staticmethod(convert_mbcs) - -class MacroExpander: - - def __init__(self, version): - self.macros = {} - self.vsbase = VS_BASE % version - self.load_macros(version) - - def set_macro(self, macro, path, key): - self.macros["$(%s)" % macro] = Reg.get_value(path, key) - - def load_macros(self, version): - self.set_macro("VCInstallDir", self.vsbase + r"\Setup\VC", "productdir") - self.set_macro("VSInstallDir", self.vsbase + r"\Setup\VS", "productdir") - self.set_macro("FrameworkDir", NET_BASE, "installroot") - try: - if version >= 8.0: - self.set_macro("FrameworkSDKDir", NET_BASE, - "sdkinstallrootv2.0") - else: - raise KeyError("sdkinstallrootv2.0") - except KeyError: - raise DistutilsPlatformError( - """Python was built with Visual Studio 2008; -extensions must be built with a compiler than can generate compatible binaries. -Visual Studio 2008 was not found on this system. If you have Cygwin installed, -you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""") - - if version >= 9.0: - self.set_macro("FrameworkVersion", self.vsbase, "clr version") - self.set_macro("WindowsSdkDir", WINSDK_BASE, "currentinstallfolder") - else: - p = r"Software\Microsoft\NET Framework Setup\Product" - for base in HKEYS: - try: - h = RegOpenKeyEx(base, p) - except RegError: - continue - key = RegEnumKey(h, 0) - d = Reg.get_value(base, r"%s\%s" % (p, key)) - self.macros["$(FrameworkVersion)"] = d["version"] - - def sub(self, s): - for k, v in self.macros.items(): - s = s.replace(k, v) - return s - -def get_build_version(): - """Return the version of MSVC that was used to build Python. - - For Python 2.3 and up, the version number is included in - sys.version. For earlier versions, assume the compiler is MSVC 6. - """ - prefix = "MSC v." - i = sys.version.find(prefix) - if i == -1: - return 6 - i = i + len(prefix) - s, rest = sys.version[i:].split(" ", 1) - majorVersion = int(s[:-2]) - 6 - if majorVersion >= 13: - # v13 was skipped and should be v14 - majorVersion += 1 - minorVersion = int(s[2:3]) / 10.0 - # I don't think paths are affected by minor version in version 6 - if majorVersion == 6: - minorVersion = 0 - if majorVersion >= 6: - return majorVersion + minorVersion - # else we don't know what version of the compiler this is - return None - -def normalize_and_reduce_paths(paths): - """Return a list of normalized paths with duplicates removed. - - The current order of paths is maintained. - """ - # Paths are normalized so things like: /a and /a/ aren't both preserved. - reduced_paths = [] - for p in paths: - np = os.path.normpath(p) - # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set. - if np not in reduced_paths: - reduced_paths.append(np) - return reduced_paths - -def removeDuplicates(variable): - """Remove duplicate values of an environment variable. - """ - oldList = variable.split(os.pathsep) - newList = [] - for i in oldList: - if i not in newList: - newList.append(i) - newVariable = os.pathsep.join(newList) - return newVariable - -def find_vcvarsall(version): - """Find the vcvarsall.bat file - - At first it tries to find the productdir of VS 2008 in the registry. If - that fails it falls back to the VS90COMNTOOLS env var. - """ - vsbase = VS_BASE % version - try: - productdir = Reg.get_value(r"%s\Setup\VC" % vsbase, - "productdir") - except KeyError: - log.debug("Unable to find productdir in registry") - productdir = None - - if not productdir or not os.path.isdir(productdir): - toolskey = "VS%0.f0COMNTOOLS" % version - toolsdir = os.environ.get(toolskey, None) - - if toolsdir and os.path.isdir(toolsdir): - productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC") - productdir = os.path.abspath(productdir) - if not os.path.isdir(productdir): - log.debug("%s is not a valid directory" % productdir) - return None - else: - log.debug("Env var %s is not set or invalid" % toolskey) - if not productdir: - log.debug("No productdir found") - return None - vcvarsall = os.path.join(productdir, "vcvarsall.bat") - if os.path.isfile(vcvarsall): - return vcvarsall - log.debug("Unable to find vcvarsall.bat") - return None - -def query_vcvarsall(version, arch="x86"): - """Launch vcvarsall.bat and read the settings from its environment - """ - vcvarsall = find_vcvarsall(version) - interesting = set(("include", "lib", "libpath", "path")) - result = {} - - if vcvarsall is None: - raise DistutilsPlatformError("Unable to find vcvarsall.bat") - log.debug("Calling 'vcvarsall.bat %s' (version=%s)", arch, version) - popen = subprocess.Popen('"%s" %s & set' % (vcvarsall, arch), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - try: - stdout, stderr = popen.communicate() - if popen.wait() != 0: - raise DistutilsPlatformError(stderr.decode("mbcs")) - - stdout = stdout.decode("mbcs") - for line in stdout.split("\n"): - line = Reg.convert_mbcs(line) - if '=' not in line: - continue - line = line.strip() - key, value = line.split('=', 1) - key = key.lower() - if key in interesting: - if value.endswith(os.pathsep): - value = value[:-1] - result[key] = removeDuplicates(value) - - finally: - popen.stdout.close() - popen.stderr.close() - - if len(result) != len(interesting): - raise ValueError(str(list(result.keys()))) - - return result - -# More globals -VERSION = get_build_version() -if VERSION < 8.0: - raise DistutilsPlatformError("VC %0.1f is not supported by this module" % VERSION) -# MACROS = MacroExpander(VERSION) - -class MSVCCompiler(CCompiler) : - """Concrete class that implements an interface to Microsoft Visual C++, - as defined by the CCompiler abstract class.""" - - compiler_type = 'msvc' - - # Just set this so CCompiler's constructor doesn't barf. We currently - # don't use the 'set_executables()' bureaucracy provided by CCompiler, - # as it really isn't necessary for this sort of single-compiler class. - # Would be nice to have a consistent interface with UnixCCompiler, - # though, so it's worth thinking about. - executables = {} - - # Private class data (need to distinguish C from C++ source for compiler) - _c_extensions = ['.c'] - _cpp_extensions = ['.cc', '.cpp', '.cxx'] - _rc_extensions = ['.rc'] - _mc_extensions = ['.mc'] - - # Needed for the filename generation methods provided by the - # base class, CCompiler. - src_extensions = (_c_extensions + _cpp_extensions + - _rc_extensions + _mc_extensions) - res_extension = '.res' - obj_extension = '.obj' - static_lib_extension = '.lib' - shared_lib_extension = '.dll' - static_lib_format = shared_lib_format = '%s%s' - exe_extension = '.exe' - - def __init__(self, verbose=0, dry_run=0, force=0): - CCompiler.__init__ (self, verbose, dry_run, force) - self.__version = VERSION - self.__root = r"Software\Microsoft\VisualStudio" - # self.__macros = MACROS - self.__paths = [] - # target platform (.plat_name is consistent with 'bdist') - self.plat_name = None - self.__arch = None # deprecated name - self.initialized = False - - def initialize(self, plat_name=None): - # multi-init means we would need to check platform same each time... - assert not self.initialized, "don't init multiple times" - if plat_name is None: - plat_name = get_platform() - # sanity check for platforms to prevent obscure errors later. - ok_plats = 'win32', 'win-amd64', 'win-ia64' - if plat_name not in ok_plats: - raise DistutilsPlatformError("--plat-name must be one of %s" % - (ok_plats,)) - - if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and self.find_exe("cl.exe"): - # Assume that the SDK set up everything alright; don't try to be - # smarter - self.cc = "cl.exe" - self.linker = "link.exe" - self.lib = "lib.exe" - self.rc = "rc.exe" - self.mc = "mc.exe" - else: - # On x86, 'vcvars32.bat amd64' creates an env that doesn't work; - # to cross compile, you use 'x86_amd64'. - # On AMD64, 'vcvars32.bat amd64' is a native build env; to cross - # compile use 'x86' (ie, it runs the x86 compiler directly) - # No idea how itanium handles this, if at all. - if plat_name == get_platform() or plat_name == 'win32': - # native build or cross-compile to win32 - plat_spec = PLAT_TO_VCVARS[plat_name] - else: - # cross compile from win32 -> some 64bit - plat_spec = PLAT_TO_VCVARS[get_platform()] + '_' + \ - PLAT_TO_VCVARS[plat_name] - - vc_env = query_vcvarsall(VERSION, plat_spec) - - self.__paths = vc_env['path'].split(os.pathsep) - os.environ['lib'] = vc_env['lib'] - os.environ['include'] = vc_env['include'] - - if len(self.__paths) == 0: - raise DistutilsPlatformError("Python was built with %s, " - "and extensions need to be built with the same " - "version of the compiler, but it isn't installed." - % self.__product) - - self.cc = self.find_exe("cl.exe") - self.linker = self.find_exe("link.exe") - self.lib = self.find_exe("lib.exe") - self.rc = self.find_exe("rc.exe") # resource compiler - self.mc = self.find_exe("mc.exe") # message compiler - #self.set_path_env_var('lib') - #self.set_path_env_var('include') - - # extend the MSVC path with the current path - try: - for p in os.environ['path'].split(';'): - self.__paths.append(p) - except KeyError: - pass - self.__paths = normalize_and_reduce_paths(self.__paths) - os.environ['path'] = ";".join(self.__paths) - - self.preprocess_options = None - if self.__arch == "x86": - self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', - '/DNDEBUG'] - self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', - '/Z7', '/D_DEBUG'] - else: - # Win64 - self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GS-' , - '/DNDEBUG'] - self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-', - '/Z7', '/D_DEBUG'] - - self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO'] - if self.__version >= 7: - self.ldflags_shared_debug = [ - '/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG' - ] - self.ldflags_static = [ '/nologo'] - - self.initialized = True - - # -- Worker methods ------------------------------------------------ - - def object_filenames(self, - source_filenames, - strip_dir=0, - output_dir=''): - # Copied from ccompiler.py, extended to return .res as 'object'-file - # for .rc input file - if output_dir is None: output_dir = '' - obj_names = [] - for src_name in source_filenames: - (base, ext) = os.path.splitext (src_name) - base = os.path.splitdrive(base)[1] # Chop off the drive - base = base[os.path.isabs(base):] # If abs, chop off leading / - if ext not in self.src_extensions: - # Better to raise an exception instead of silently continuing - # and later complain about sources and targets having - # different lengths - raise CompileError ("Don't know how to compile %s" % src_name) - if strip_dir: - base = os.path.basename (base) - if ext in self._rc_extensions: - obj_names.append (os.path.join (output_dir, - base + self.res_extension)) - elif ext in self._mc_extensions: - obj_names.append (os.path.join (output_dir, - base + self.res_extension)) - else: - obj_names.append (os.path.join (output_dir, - base + self.obj_extension)) - return obj_names - - - def compile(self, sources, - output_dir=None, macros=None, include_dirs=None, debug=0, - extra_preargs=None, extra_postargs=None, depends=None): - - if not self.initialized: - self.initialize() - compile_info = self._setup_compile(output_dir, macros, include_dirs, - sources, depends, extra_postargs) - macros, objects, extra_postargs, pp_opts, build = compile_info - - compile_opts = extra_preargs or [] - compile_opts.append ('/c') - if debug: - compile_opts.extend(self.compile_options_debug) - else: - compile_opts.extend(self.compile_options) - - for obj in objects: - try: - src, ext = build[obj] - except KeyError: - continue - if debug: - # pass the full pathname to MSVC in debug mode, - # this allows the debugger to find the source file - # without asking the user to browse for it - src = os.path.abspath(src) - - if ext in self._c_extensions: - input_opt = "/Tc" + src - elif ext in self._cpp_extensions: - input_opt = "/Tp" + src - elif ext in self._rc_extensions: - # compile .RC to .RES file - input_opt = src - output_opt = "/fo" + obj - try: - self.spawn([self.rc] + pp_opts + - [output_opt] + [input_opt]) - except DistutilsExecError as msg: - raise CompileError(msg) - continue - elif ext in self._mc_extensions: - # Compile .MC to .RC file to .RES file. - # * '-h dir' specifies the directory for the - # generated include file - # * '-r dir' specifies the target directory of the - # generated RC file and the binary message resource - # it includes - # - # For now (since there are no options to change this), - # we use the source-directory for the include file and - # the build directory for the RC file and message - # resources. This works at least for win32all. - h_dir = os.path.dirname(src) - rc_dir = os.path.dirname(obj) - try: - # first compile .MC to .RC and .H file - self.spawn([self.mc] + - ['-h', h_dir, '-r', rc_dir] + [src]) - base, _ = os.path.splitext (os.path.basename (src)) - rc_file = os.path.join (rc_dir, base + '.rc') - # then compile .RC to .RES file - self.spawn([self.rc] + - ["/fo" + obj] + [rc_file]) - - except DistutilsExecError as msg: - raise CompileError(msg) - continue - else: - # how to handle this file? - raise CompileError("Don't know how to compile %s to %s" - % (src, obj)) - - output_opt = "/Fo" + obj - try: - self.spawn([self.cc] + compile_opts + pp_opts + - [input_opt, output_opt] + - extra_postargs) - except DistutilsExecError as msg: - raise CompileError(msg) - - return objects - - - def create_static_lib(self, - objects, - output_libname, - output_dir=None, - debug=0, - target_lang=None): - - if not self.initialized: - self.initialize() - (objects, output_dir) = self._fix_object_args(objects, output_dir) - output_filename = self.library_filename(output_libname, - output_dir=output_dir) - - if self._need_link(objects, output_filename): - lib_args = objects + ['/OUT:' + output_filename] - if debug: - pass # XXX what goes here? - try: - self.spawn([self.lib] + lib_args) - except DistutilsExecError as msg: - raise LibError(msg) - else: - log.debug("skipping %s (up-to-date)", output_filename) - - - def link(self, - target_desc, - objects, - output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None): - - if not self.initialized: - self.initialize() - (objects, output_dir) = self._fix_object_args(objects, output_dir) - fixed_args = self._fix_lib_args(libraries, library_dirs, - runtime_library_dirs) - (libraries, library_dirs, runtime_library_dirs) = fixed_args - - if runtime_library_dirs: - self.warn ("I don't know what to do with 'runtime_library_dirs': " - + str (runtime_library_dirs)) - - lib_opts = gen_lib_options(self, - library_dirs, runtime_library_dirs, - libraries) - if output_dir is not None: - output_filename = os.path.join(output_dir, output_filename) - - if self._need_link(objects, output_filename): - if target_desc == CCompiler.EXECUTABLE: - if debug: - ldflags = self.ldflags_shared_debug[1:] - else: - ldflags = self.ldflags_shared[1:] - else: - if debug: - ldflags = self.ldflags_shared_debug - else: - ldflags = self.ldflags_shared - - export_opts = [] - for sym in (export_symbols or []): - export_opts.append("/EXPORT:" + sym) - - ld_args = (ldflags + lib_opts + export_opts + - objects + ['/OUT:' + output_filename]) - - # The MSVC linker generates .lib and .exp files, which cannot be - # suppressed by any linker switches. The .lib files may even be - # needed! Make sure they are generated in the temporary build - # directory. Since they have different names for debug and release - # builds, they can go into the same directory. - build_temp = os.path.dirname(objects[0]) - if export_symbols is not None: - (dll_name, dll_ext) = os.path.splitext( - os.path.basename(output_filename)) - implib_file = os.path.join( - build_temp, - self.library_filename(dll_name)) - ld_args.append ('/IMPLIB:' + implib_file) - - self.manifest_setup_ldargs(output_filename, build_temp, ld_args) - - if extra_preargs: - ld_args[:0] = extra_preargs - if extra_postargs: - ld_args.extend(extra_postargs) - - self.mkpath(os.path.dirname(output_filename)) - try: - self.spawn([self.linker] + ld_args) - except DistutilsExecError as msg: - raise LinkError(msg) - - # embed the manifest - # XXX - this is somewhat fragile - if mt.exe fails, distutils - # will still consider the DLL up-to-date, but it will not have a - # manifest. Maybe we should link to a temp file? OTOH, that - # implies a build environment error that shouldn't go undetected. - mfinfo = self.manifest_get_embed_info(target_desc, ld_args) - if mfinfo is not None: - mffilename, mfid = mfinfo - out_arg = '-outputresource:%s;%s' % (output_filename, mfid) - try: - self.spawn(['mt.exe', '-nologo', '-manifest', - mffilename, out_arg]) - except DistutilsExecError as msg: - raise LinkError(msg) - else: - log.debug("skipping %s (up-to-date)", output_filename) - - def manifest_setup_ldargs(self, output_filename, build_temp, ld_args): - # If we need a manifest at all, an embedded manifest is recommended. - # See MSDN article titled - # "How to: Embed a Manifest Inside a C/C++ Application" - # (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx) - # Ask the linker to generate the manifest in the temp dir, so - # we can check it, and possibly embed it, later. - temp_manifest = os.path.join( - build_temp, - os.path.basename(output_filename) + ".manifest") - ld_args.append('/MANIFESTFILE:' + temp_manifest) - - def manifest_get_embed_info(self, target_desc, ld_args): - # If a manifest should be embedded, return a tuple of - # (manifest_filename, resource_id). Returns None if no manifest - # should be embedded. See http://bugs.python.org/issue7833 for why - # we want to avoid any manifest for extension modules if we can) - for arg in ld_args: - if arg.startswith("/MANIFESTFILE:"): - temp_manifest = arg.split(":", 1)[1] - break - else: - # no /MANIFESTFILE so nothing to do. - return None - if target_desc == CCompiler.EXECUTABLE: - # by default, executables always get the manifest with the - # CRT referenced. - mfid = 1 - else: - # Extension modules try and avoid any manifest if possible. - mfid = 2 - temp_manifest = self._remove_visual_c_ref(temp_manifest) - if temp_manifest is None: - return None - return temp_manifest, mfid - - def _remove_visual_c_ref(self, manifest_file): - try: - # Remove references to the Visual C runtime, so they will - # fall through to the Visual C dependency of Python.exe. - # This way, when installed for a restricted user (e.g. - # runtimes are not in WinSxS folder, but in Python's own - # folder), the runtimes do not need to be in every folder - # with .pyd's. - # Returns either the filename of the modified manifest or - # None if no manifest should be embedded. - manifest_f = open(manifest_file) - try: - manifest_buf = manifest_f.read() - finally: - manifest_f.close() - pattern = re.compile( - r"""<assemblyIdentity.*?name=("|')Microsoft\."""\ - r"""VC\d{2}\.CRT("|').*?(/>|</assemblyIdentity>)""", - re.DOTALL) - manifest_buf = re.sub(pattern, "", manifest_buf) - pattern = r"<dependentAssembly>\s*</dependentAssembly>" - manifest_buf = re.sub(pattern, "", manifest_buf) - # Now see if any other assemblies are referenced - if not, we - # don't want a manifest embedded. - pattern = re.compile( - r"""<assemblyIdentity.*?name=(?:"|')(.+?)(?:"|')""" - r""".*?(?:/>|</assemblyIdentity>)""", re.DOTALL) - if re.search(pattern, manifest_buf) is None: - return None - - manifest_f = open(manifest_file, 'w') - try: - manifest_f.write(manifest_buf) - return manifest_file - finally: - manifest_f.close() - except OSError: - pass - - # -- Miscellaneous methods ----------------------------------------- - # These are all used by the 'gen_lib_options() function, in - # ccompiler.py. - - def library_dir_option(self, dir): - return "/LIBPATH:" + dir - - def runtime_library_dir_option(self, dir): - raise DistutilsPlatformError( - "don't know how to set runtime library search path for MSVC++") - - def library_option(self, lib): - return self.library_filename(lib) - - - def find_library_file(self, dirs, lib, debug=0): - # Prefer a debugging library if found (and requested), but deal - # with it if we don't have one. - if debug: - try_names = [lib + "_d", lib] - else: - try_names = [lib] - for dir in dirs: - for name in try_names: - libfile = os.path.join(dir, self.library_filename (name)) - if os.path.exists(libfile): - return libfile - else: - # Oops, didn't find it in *any* of 'dirs' - return None - - # Helper methods for using the MSVC registry settings - - def find_exe(self, exe): - """Return path to an MSVC executable program. - - Tries to find the program in several places: first, one of the - MSVC program search paths from the registry; next, the directories - in the PATH environment variable. If any of those work, return an - absolute path that is known to exist. If none of them work, just - return the original program name, 'exe'. - """ - for p in self.__paths: - fn = os.path.join(os.path.abspath(p), exe) - if os.path.isfile(fn): - return fn - - # didn't find it; try existing path - for p in os.environ['Path'].split(';'): - fn = os.path.join(os.path.abspath(p),exe) - if os.path.isfile(fn): - return fn - - return exe diff --git a/Lib/distutils/msvccompiler.py b/Lib/distutils/msvccompiler.py deleted file mode 100644 index 1048cd41593..00000000000 --- a/Lib/distutils/msvccompiler.py +++ /dev/null @@ -1,643 +0,0 @@ -"""distutils.msvccompiler - -Contains MSVCCompiler, an implementation of the abstract CCompiler class -for the Microsoft Visual Studio. -""" - -# Written by Perry Stoll -# hacked by Robin Becker and Thomas Heller to do a better job of -# finding DevStudio (through the registry) - -import sys, os -from distutils.errors import \ - DistutilsExecError, DistutilsPlatformError, \ - CompileError, LibError, LinkError -from distutils.ccompiler import \ - CCompiler, gen_preprocess_options, gen_lib_options -from distutils import log - -_can_read_reg = False -try: - import winreg - - _can_read_reg = True - hkey_mod = winreg - - RegOpenKeyEx = winreg.OpenKeyEx - RegEnumKey = winreg.EnumKey - RegEnumValue = winreg.EnumValue - RegError = winreg.error - -except ImportError: - try: - import win32api - import win32con - _can_read_reg = True - hkey_mod = win32con - - RegOpenKeyEx = win32api.RegOpenKeyEx - RegEnumKey = win32api.RegEnumKey - RegEnumValue = win32api.RegEnumValue - RegError = win32api.error - except ImportError: - log.info("Warning: Can't read registry to find the " - "necessary compiler setting\n" - "Make sure that Python modules winreg, " - "win32api or win32con are installed.") - pass - -if _can_read_reg: - HKEYS = (hkey_mod.HKEY_USERS, - hkey_mod.HKEY_CURRENT_USER, - hkey_mod.HKEY_LOCAL_MACHINE, - hkey_mod.HKEY_CLASSES_ROOT) - -def read_keys(base, key): - """Return list of registry keys.""" - try: - handle = RegOpenKeyEx(base, key) - except RegError: - return None - L = [] - i = 0 - while True: - try: - k = RegEnumKey(handle, i) - except RegError: - break - L.append(k) - i += 1 - return L - -def read_values(base, key): - """Return dict of registry keys and values. - - All names are converted to lowercase. - """ - try: - handle = RegOpenKeyEx(base, key) - except RegError: - return None - d = {} - i = 0 - while True: - try: - name, value, type = RegEnumValue(handle, i) - except RegError: - break - name = name.lower() - d[convert_mbcs(name)] = convert_mbcs(value) - i += 1 - return d - -def convert_mbcs(s): - dec = getattr(s, "decode", None) - if dec is not None: - try: - s = dec("mbcs") - except UnicodeError: - pass - return s - -class MacroExpander: - def __init__(self, version): - self.macros = {} - self.load_macros(version) - - def set_macro(self, macro, path, key): - for base in HKEYS: - d = read_values(base, path) - if d: - self.macros["$(%s)" % macro] = d[key] - break - - def load_macros(self, version): - vsbase = r"Software\Microsoft\VisualStudio\%0.1f" % version - self.set_macro("VCInstallDir", vsbase + r"\Setup\VC", "productdir") - self.set_macro("VSInstallDir", vsbase + r"\Setup\VS", "productdir") - net = r"Software\Microsoft\.NETFramework" - self.set_macro("FrameworkDir", net, "installroot") - try: - if version > 7.0: - self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1") - else: - self.set_macro("FrameworkSDKDir", net, "sdkinstallroot") - except KeyError as exc: # - raise DistutilsPlatformError( - """Python was built with Visual Studio 2003; -extensions must be built with a compiler than can generate compatible binaries. -Visual Studio 2003 was not found on this system. If you have Cygwin installed, -you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""") - - p = r"Software\Microsoft\NET Framework Setup\Product" - for base in HKEYS: - try: - h = RegOpenKeyEx(base, p) - except RegError: - continue - key = RegEnumKey(h, 0) - d = read_values(base, r"%s\%s" % (p, key)) - self.macros["$(FrameworkVersion)"] = d["version"] - - def sub(self, s): - for k, v in self.macros.items(): - s = s.replace(k, v) - return s - -def get_build_version(): - """Return the version of MSVC that was used to build Python. - - For Python 2.3 and up, the version number is included in - sys.version. For earlier versions, assume the compiler is MSVC 6. - """ - prefix = "MSC v." - i = sys.version.find(prefix) - if i == -1: - return 6 - i = i + len(prefix) - s, rest = sys.version[i:].split(" ", 1) - majorVersion = int(s[:-2]) - 6 - if majorVersion >= 13: - # v13 was skipped and should be v14 - majorVersion += 1 - minorVersion = int(s[2:3]) / 10.0 - # I don't think paths are affected by minor version in version 6 - if majorVersion == 6: - minorVersion = 0 - if majorVersion >= 6: - return majorVersion + minorVersion - # else we don't know what version of the compiler this is - return None - -def get_build_architecture(): - """Return the processor architecture. - - Possible results are "Intel", "Itanium", or "AMD64". - """ - - prefix = " bit (" - i = sys.version.find(prefix) - if i == -1: - return "Intel" - j = sys.version.find(")", i) - return sys.version[i+len(prefix):j] - -def normalize_and_reduce_paths(paths): - """Return a list of normalized paths with duplicates removed. - - The current order of paths is maintained. - """ - # Paths are normalized so things like: /a and /a/ aren't both preserved. - reduced_paths = [] - for p in paths: - np = os.path.normpath(p) - # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set. - if np not in reduced_paths: - reduced_paths.append(np) - return reduced_paths - - -class MSVCCompiler(CCompiler) : - """Concrete class that implements an interface to Microsoft Visual C++, - as defined by the CCompiler abstract class.""" - - compiler_type = 'msvc' - - # Just set this so CCompiler's constructor doesn't barf. We currently - # don't use the 'set_executables()' bureaucracy provided by CCompiler, - # as it really isn't necessary for this sort of single-compiler class. - # Would be nice to have a consistent interface with UnixCCompiler, - # though, so it's worth thinking about. - executables = {} - - # Private class data (need to distinguish C from C++ source for compiler) - _c_extensions = ['.c'] - _cpp_extensions = ['.cc', '.cpp', '.cxx'] - _rc_extensions = ['.rc'] - _mc_extensions = ['.mc'] - - # Needed for the filename generation methods provided by the - # base class, CCompiler. - src_extensions = (_c_extensions + _cpp_extensions + - _rc_extensions + _mc_extensions) - res_extension = '.res' - obj_extension = '.obj' - static_lib_extension = '.lib' - shared_lib_extension = '.dll' - static_lib_format = shared_lib_format = '%s%s' - exe_extension = '.exe' - - def __init__(self, verbose=0, dry_run=0, force=0): - CCompiler.__init__ (self, verbose, dry_run, force) - self.__version = get_build_version() - self.__arch = get_build_architecture() - if self.__arch == "Intel": - # x86 - if self.__version >= 7: - self.__root = r"Software\Microsoft\VisualStudio" - self.__macros = MacroExpander(self.__version) - else: - self.__root = r"Software\Microsoft\Devstudio" - self.__product = "Visual Studio version %s" % self.__version - else: - # Win64. Assume this was built with the platform SDK - self.__product = "Microsoft SDK compiler %s" % (self.__version + 6) - - self.initialized = False - - def initialize(self): - self.__paths = [] - if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and self.find_exe("cl.exe"): - # Assume that the SDK set up everything alright; don't try to be - # smarter - self.cc = "cl.exe" - self.linker = "link.exe" - self.lib = "lib.exe" - self.rc = "rc.exe" - self.mc = "mc.exe" - else: - self.__paths = self.get_msvc_paths("path") - - if len(self.__paths) == 0: - raise DistutilsPlatformError("Python was built with %s, " - "and extensions need to be built with the same " - "version of the compiler, but it isn't installed." - % self.__product) - - self.cc = self.find_exe("cl.exe") - self.linker = self.find_exe("link.exe") - self.lib = self.find_exe("lib.exe") - self.rc = self.find_exe("rc.exe") # resource compiler - self.mc = self.find_exe("mc.exe") # message compiler - self.set_path_env_var('lib') - self.set_path_env_var('include') - - # extend the MSVC path with the current path - try: - for p in os.environ['path'].split(';'): - self.__paths.append(p) - except KeyError: - pass - self.__paths = normalize_and_reduce_paths(self.__paths) - os.environ['path'] = ";".join(self.__paths) - - self.preprocess_options = None - if self.__arch == "Intel": - self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GX' , - '/DNDEBUG'] - self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GX', - '/Z7', '/D_DEBUG'] - else: - # Win64 - self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GS-' , - '/DNDEBUG'] - self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-', - '/Z7', '/D_DEBUG'] - - self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO'] - if self.__version >= 7: - self.ldflags_shared_debug = [ - '/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG' - ] - else: - self.ldflags_shared_debug = [ - '/DLL', '/nologo', '/INCREMENTAL:no', '/pdb:None', '/DEBUG' - ] - self.ldflags_static = [ '/nologo'] - - self.initialized = True - - # -- Worker methods ------------------------------------------------ - - def object_filenames(self, - source_filenames, - strip_dir=0, - output_dir=''): - # Copied from ccompiler.py, extended to return .res as 'object'-file - # for .rc input file - if output_dir is None: output_dir = '' - obj_names = [] - for src_name in source_filenames: - (base, ext) = os.path.splitext (src_name) - base = os.path.splitdrive(base)[1] # Chop off the drive - base = base[os.path.isabs(base):] # If abs, chop off leading / - if ext not in self.src_extensions: - # Better to raise an exception instead of silently continuing - # and later complain about sources and targets having - # different lengths - raise CompileError ("Don't know how to compile %s" % src_name) - if strip_dir: - base = os.path.basename (base) - if ext in self._rc_extensions: - obj_names.append (os.path.join (output_dir, - base + self.res_extension)) - elif ext in self._mc_extensions: - obj_names.append (os.path.join (output_dir, - base + self.res_extension)) - else: - obj_names.append (os.path.join (output_dir, - base + self.obj_extension)) - return obj_names - - - def compile(self, sources, - output_dir=None, macros=None, include_dirs=None, debug=0, - extra_preargs=None, extra_postargs=None, depends=None): - - if not self.initialized: - self.initialize() - compile_info = self._setup_compile(output_dir, macros, include_dirs, - sources, depends, extra_postargs) - macros, objects, extra_postargs, pp_opts, build = compile_info - - compile_opts = extra_preargs or [] - compile_opts.append ('/c') - if debug: - compile_opts.extend(self.compile_options_debug) - else: - compile_opts.extend(self.compile_options) - - for obj in objects: - try: - src, ext = build[obj] - except KeyError: - continue - if debug: - # pass the full pathname to MSVC in debug mode, - # this allows the debugger to find the source file - # without asking the user to browse for it - src = os.path.abspath(src) - - if ext in self._c_extensions: - input_opt = "/Tc" + src - elif ext in self._cpp_extensions: - input_opt = "/Tp" + src - elif ext in self._rc_extensions: - # compile .RC to .RES file - input_opt = src - output_opt = "/fo" + obj - try: - self.spawn([self.rc] + pp_opts + - [output_opt] + [input_opt]) - except DistutilsExecError as msg: - raise CompileError(msg) - continue - elif ext in self._mc_extensions: - # Compile .MC to .RC file to .RES file. - # * '-h dir' specifies the directory for the - # generated include file - # * '-r dir' specifies the target directory of the - # generated RC file and the binary message resource - # it includes - # - # For now (since there are no options to change this), - # we use the source-directory for the include file and - # the build directory for the RC file and message - # resources. This works at least for win32all. - h_dir = os.path.dirname(src) - rc_dir = os.path.dirname(obj) - try: - # first compile .MC to .RC and .H file - self.spawn([self.mc] + - ['-h', h_dir, '-r', rc_dir] + [src]) - base, _ = os.path.splitext (os.path.basename (src)) - rc_file = os.path.join (rc_dir, base + '.rc') - # then compile .RC to .RES file - self.spawn([self.rc] + - ["/fo" + obj] + [rc_file]) - - except DistutilsExecError as msg: - raise CompileError(msg) - continue - else: - # how to handle this file? - raise CompileError("Don't know how to compile %s to %s" - % (src, obj)) - - output_opt = "/Fo" + obj - try: - self.spawn([self.cc] + compile_opts + pp_opts + - [input_opt, output_opt] + - extra_postargs) - except DistutilsExecError as msg: - raise CompileError(msg) - - return objects - - - def create_static_lib(self, - objects, - output_libname, - output_dir=None, - debug=0, - target_lang=None): - - if not self.initialized: - self.initialize() - (objects, output_dir) = self._fix_object_args(objects, output_dir) - output_filename = self.library_filename(output_libname, - output_dir=output_dir) - - if self._need_link(objects, output_filename): - lib_args = objects + ['/OUT:' + output_filename] - if debug: - pass # XXX what goes here? - try: - self.spawn([self.lib] + lib_args) - except DistutilsExecError as msg: - raise LibError(msg) - else: - log.debug("skipping %s (up-to-date)", output_filename) - - - def link(self, - target_desc, - objects, - output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None): - - if not self.initialized: - self.initialize() - (objects, output_dir) = self._fix_object_args(objects, output_dir) - fixed_args = self._fix_lib_args(libraries, library_dirs, - runtime_library_dirs) - (libraries, library_dirs, runtime_library_dirs) = fixed_args - - if runtime_library_dirs: - self.warn ("I don't know what to do with 'runtime_library_dirs': " - + str (runtime_library_dirs)) - - lib_opts = gen_lib_options(self, - library_dirs, runtime_library_dirs, - libraries) - if output_dir is not None: - output_filename = os.path.join(output_dir, output_filename) - - if self._need_link(objects, output_filename): - if target_desc == CCompiler.EXECUTABLE: - if debug: - ldflags = self.ldflags_shared_debug[1:] - else: - ldflags = self.ldflags_shared[1:] - else: - if debug: - ldflags = self.ldflags_shared_debug - else: - ldflags = self.ldflags_shared - - export_opts = [] - for sym in (export_symbols or []): - export_opts.append("/EXPORT:" + sym) - - ld_args = (ldflags + lib_opts + export_opts + - objects + ['/OUT:' + output_filename]) - - # The MSVC linker generates .lib and .exp files, which cannot be - # suppressed by any linker switches. The .lib files may even be - # needed! Make sure they are generated in the temporary build - # directory. Since they have different names for debug and release - # builds, they can go into the same directory. - if export_symbols is not None: - (dll_name, dll_ext) = os.path.splitext( - os.path.basename(output_filename)) - implib_file = os.path.join( - os.path.dirname(objects[0]), - self.library_filename(dll_name)) - ld_args.append ('/IMPLIB:' + implib_file) - - if extra_preargs: - ld_args[:0] = extra_preargs - if extra_postargs: - ld_args.extend(extra_postargs) - - self.mkpath(os.path.dirname(output_filename)) - try: - self.spawn([self.linker] + ld_args) - except DistutilsExecError as msg: - raise LinkError(msg) - - else: - log.debug("skipping %s (up-to-date)", output_filename) - - - # -- Miscellaneous methods ----------------------------------------- - # These are all used by the 'gen_lib_options() function, in - # ccompiler.py. - - def library_dir_option(self, dir): - return "/LIBPATH:" + dir - - def runtime_library_dir_option(self, dir): - raise DistutilsPlatformError( - "don't know how to set runtime library search path for MSVC++") - - def library_option(self, lib): - return self.library_filename(lib) - - - def find_library_file(self, dirs, lib, debug=0): - # Prefer a debugging library if found (and requested), but deal - # with it if we don't have one. - if debug: - try_names = [lib + "_d", lib] - else: - try_names = [lib] - for dir in dirs: - for name in try_names: - libfile = os.path.join(dir, self.library_filename (name)) - if os.path.exists(libfile): - return libfile - else: - # Oops, didn't find it in *any* of 'dirs' - return None - - # Helper methods for using the MSVC registry settings - - def find_exe(self, exe): - """Return path to an MSVC executable program. - - Tries to find the program in several places: first, one of the - MSVC program search paths from the registry; next, the directories - in the PATH environment variable. If any of those work, return an - absolute path that is known to exist. If none of them work, just - return the original program name, 'exe'. - """ - for p in self.__paths: - fn = os.path.join(os.path.abspath(p), exe) - if os.path.isfile(fn): - return fn - - # didn't find it; try existing path - for p in os.environ['Path'].split(';'): - fn = os.path.join(os.path.abspath(p),exe) - if os.path.isfile(fn): - return fn - - return exe - - def get_msvc_paths(self, path, platform='x86'): - """Get a list of devstudio directories (include, lib or path). - - Return a list of strings. The list will be empty if unable to - access the registry or appropriate registry keys not found. - """ - if not _can_read_reg: - return [] - - path = path + " dirs" - if self.__version >= 7: - key = (r"%s\%0.1f\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories" - % (self.__root, self.__version)) - else: - key = (r"%s\6.0\Build System\Components\Platforms" - r"\Win32 (%s)\Directories" % (self.__root, platform)) - - for base in HKEYS: - d = read_values(base, key) - if d: - if self.__version >= 7: - return self.__macros.sub(d[path]).split(";") - else: - return d[path].split(";") - # MSVC 6 seems to create the registry entries we need only when - # the GUI is run. - if self.__version == 6: - for base in HKEYS: - if read_values(base, r"%s\6.0" % self.__root) is not None: - self.warn("It seems you have Visual Studio 6 installed, " - "but the expected registry settings are not present.\n" - "You must at least run the Visual Studio GUI once " - "so that these entries are created.") - break - return [] - - def set_path_env_var(self, name): - """Set environment variable 'name' to an MSVC path type value. - - This is equivalent to a SET command prior to execution of spawned - commands. - """ - - if name == "lib": - p = self.get_msvc_paths("library") - else: - p = self.get_msvc_paths(name) - if p: - os.environ[name] = ';'.join(p) - - -if get_build_version() >= 8.0: - log.debug("Importing new compiler from distutils.msvc9compiler") - OldMSVCCompiler = MSVCCompiler - from distutils.msvc9compiler import MSVCCompiler - # get_build_architecture not really relevant now we support cross-compile - from distutils.msvc9compiler import MacroExpander diff --git a/Lib/distutils/spawn.py b/Lib/distutils/spawn.py deleted file mode 100644 index 53876880932..00000000000 --- a/Lib/distutils/spawn.py +++ /dev/null @@ -1,192 +0,0 @@ -"""distutils.spawn - -Provides the 'spawn()' function, a front-end to various platform- -specific functions for launching another program in a sub-process. -Also provides the 'find_executable()' to search the path for a given -executable name. -""" - -import sys -import os - -from distutils.errors import DistutilsPlatformError, DistutilsExecError -from distutils.debug import DEBUG -from distutils import log - -def spawn(cmd, search_path=1, verbose=0, dry_run=0): - """Run another program, specified as a command list 'cmd', in a new process. - - 'cmd' is just the argument list for the new process, ie. - cmd[0] is the program to run and cmd[1:] are the rest of its arguments. - There is no way to run a program with a name different from that of its - executable. - - If 'search_path' is true (the default), the system's executable - search path will be used to find the program; otherwise, cmd[0] - must be the exact path to the executable. If 'dry_run' is true, - the command will not actually be run. - - Raise DistutilsExecError if running the program fails in any way; just - return on success. - """ - # cmd is documented as a list, but just in case some code passes a tuple - # in, protect our %-formatting code against horrible death - cmd = list(cmd) - if os.name == 'posix': - _spawn_posix(cmd, search_path, dry_run=dry_run) - elif os.name == 'nt': - _spawn_nt(cmd, search_path, dry_run=dry_run) - else: - raise DistutilsPlatformError( - "don't know how to spawn programs on platform '%s'" % os.name) - -def _nt_quote_args(args): - """Quote command-line arguments for DOS/Windows conventions. - - Just wraps every argument which contains blanks in double quotes, and - returns a new argument list. - """ - # XXX this doesn't seem very robust to me -- but if the Windows guys - # say it'll work, I guess I'll have to accept it. (What if an arg - # contains quotes? What other magic characters, other than spaces, - # have to be escaped? Is there an escaping mechanism other than - # quoting?) - for i, arg in enumerate(args): - if ' ' in arg: - args[i] = '"%s"' % arg - return args - -def _spawn_nt(cmd, search_path=1, verbose=0, dry_run=0): - executable = cmd[0] - cmd = _nt_quote_args(cmd) - if search_path: - # either we find one or it stays the same - executable = find_executable(executable) or executable - log.info(' '.join([executable] + cmd[1:])) - if not dry_run: - # spawn for NT requires a full path to the .exe - try: - rc = os.spawnv(os.P_WAIT, executable, cmd) - except OSError as exc: - # this seems to happen when the command isn't found - if not DEBUG: - cmd = executable - raise DistutilsExecError( - "command %r failed: %s" % (cmd, exc.args[-1])) - if rc != 0: - # and this reflects the command running but failing - if not DEBUG: - cmd = executable - raise DistutilsExecError( - "command %r failed with exit status %d" % (cmd, rc)) - -if sys.platform == 'darwin': - from distutils import sysconfig - _cfg_target = None - _cfg_target_split = None - -def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0): - log.info(' '.join(cmd)) - if dry_run: - return - executable = cmd[0] - exec_fn = search_path and os.execvp or os.execv - env = None - if sys.platform == 'darwin': - global _cfg_target, _cfg_target_split - if _cfg_target is None: - _cfg_target = sysconfig.get_config_var( - 'MACOSX_DEPLOYMENT_TARGET') or '' - if _cfg_target: - _cfg_target_split = [int(x) for x in _cfg_target.split('.')] - if _cfg_target: - # ensure that the deployment target of build process is not less - # than that used when the interpreter was built. This ensures - # extension modules are built with correct compatibility values - cur_target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', _cfg_target) - if _cfg_target_split > [int(x) for x in cur_target.split('.')]: - my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: ' - 'now "%s" but "%s" during configure' - % (cur_target, _cfg_target)) - raise DistutilsPlatformError(my_msg) - env = dict(os.environ, - MACOSX_DEPLOYMENT_TARGET=cur_target) - exec_fn = search_path and os.execvpe or os.execve - pid = os.fork() - if pid == 0: # in the child - try: - if env is None: - exec_fn(executable, cmd) - else: - exec_fn(executable, cmd, env) - except OSError as e: - if not DEBUG: - cmd = executable - sys.stderr.write("unable to execute %r: %s\n" - % (cmd, e.strerror)) - os._exit(1) - - if not DEBUG: - cmd = executable - sys.stderr.write("unable to execute %r for unknown reasons" % cmd) - os._exit(1) - else: # in the parent - # Loop until the child either exits or is terminated by a signal - # (ie. keep waiting if it's merely stopped) - while True: - try: - pid, status = os.waitpid(pid, 0) - except OSError as exc: - if not DEBUG: - cmd = executable - raise DistutilsExecError( - "command %r failed: %s" % (cmd, exc.args[-1])) - if os.WIFSIGNALED(status): - if not DEBUG: - cmd = executable - raise DistutilsExecError( - "command %r terminated by signal %d" - % (cmd, os.WTERMSIG(status))) - elif os.WIFEXITED(status): - exit_status = os.WEXITSTATUS(status) - if exit_status == 0: - return # hey, it succeeded! - else: - if not DEBUG: - cmd = executable - raise DistutilsExecError( - "command %r failed with exit status %d" - % (cmd, exit_status)) - elif os.WIFSTOPPED(status): - continue - else: - if not DEBUG: - cmd = executable - raise DistutilsExecError( - "unknown error executing %r: termination status %d" - % (cmd, status)) - -def find_executable(executable, path=None): - """Tries to find 'executable' in the directories listed in 'path'. - - A string listing directories separated by 'os.pathsep'; defaults to - os.environ['PATH']. Returns the complete filename or None if not found. - """ - if path is None: - path = os.environ.get('PATH', os.defpath) - - paths = path.split(os.pathsep) - base, ext = os.path.splitext(executable) - - if (sys.platform == 'win32') and (ext != '.exe'): - executable = executable + '.exe' - - if not os.path.isfile(executable): - for p in paths: - f = os.path.join(p, executable) - if os.path.isfile(f): - # the file exists, we have a shot at spawn working - return f - return None - else: - return executable diff --git a/Lib/distutils/sysconfig.py b/Lib/distutils/sysconfig.py deleted file mode 100644 index 3a5984f5c01..00000000000 --- a/Lib/distutils/sysconfig.py +++ /dev/null @@ -1,556 +0,0 @@ -"""Provide access to Python's configuration information. The specific -configuration variables available depend heavily on the platform and -configuration. The values may be retrieved using -get_config_var(name), and the list of variables is available via -get_config_vars().keys(). Additional convenience functions are also -available. - -Written by: Fred L. Drake, Jr. -Email: <fdrake@acm.org> -""" - -import _imp -import os -import re -import sys - -from .errors import DistutilsPlatformError - -# These are needed in a couple of spots, so just compute them once. -PREFIX = os.path.normpath(sys.prefix) -EXEC_PREFIX = os.path.normpath(sys.exec_prefix) -BASE_PREFIX = os.path.normpath(sys.base_prefix) -BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix) - -# Path to the base directory of the project. On Windows the binary may -# live in project/PCbuild/win32 or project/PCbuild/amd64. -# set for cross builds -if "_PYTHON_PROJECT_BASE" in os.environ: - project_base = os.path.abspath(os.environ["_PYTHON_PROJECT_BASE"]) -else: - if sys.executable: - project_base = os.path.dirname(os.path.abspath(sys.executable)) - else: - # sys.executable can be empty if argv[0] has been changed and Python is - # unable to retrieve the real program name - project_base = os.getcwd() - - -# python_build: (Boolean) if true, we're either building Python or -# building an extension with an un-installed Python, so we use -# different (hard-wired) directories. -def _is_python_source_dir(d): - for fn in ("Setup", "Setup.local"): - if os.path.isfile(os.path.join(d, "Modules", fn)): - return True - return False - -_sys_home = getattr(sys, '_home', None) - -if os.name == 'nt': - def _fix_pcbuild(d): - if d and os.path.normcase(d).startswith( - os.path.normcase(os.path.join(PREFIX, "PCbuild"))): - return PREFIX - return d - project_base = _fix_pcbuild(project_base) - _sys_home = _fix_pcbuild(_sys_home) - -def _python_build(): - if _sys_home: - return _is_python_source_dir(_sys_home) - return _is_python_source_dir(project_base) - -python_build = _python_build() - - -# Calculate the build qualifier flags if they are defined. Adding the flags -# to the include and lib directories only makes sense for an installation, not -# an in-source build. -build_flags = '' -try: - if not python_build: - build_flags = sys.abiflags -except AttributeError: - # It's not a configure-based build, so the sys module doesn't have - # this attribute, which is fine. - pass - -def get_python_version(): - """Return a string containing the major and minor Python version, - leaving off the patchlevel. Sample return values could be '1.5' - or '2.2'. - """ - return '%d.%d' % sys.version_info[:2] - - -def get_python_inc(plat_specific=0, prefix=None): - """Return the directory containing installed Python header files. - - If 'plat_specific' is false (the default), this is the path to the - non-platform-specific header files, i.e. Python.h and so on; - otherwise, this is the path to platform-specific header files - (namely pyconfig.h). - - If 'prefix' is supplied, use it instead of sys.base_prefix or - sys.base_exec_prefix -- i.e., ignore 'plat_specific'. - """ - if prefix is None: - prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX - if os.name == "posix": - if python_build: - # Assume the executable is in the build directory. The - # pyconfig.h file should be in the same directory. Since - # the build directory may not be the source directory, we - # must use "srcdir" from the makefile to find the "Include" - # directory. - if plat_specific: - return _sys_home or project_base - else: - incdir = os.path.join(get_config_var('srcdir'), 'Include') - return os.path.normpath(incdir) - python_dir = 'python' + get_python_version() + build_flags - return os.path.join(prefix, "include", python_dir) - elif os.name == "nt": - if python_build: - # Include both the include and PC dir to ensure we can find - # pyconfig.h - return (os.path.join(prefix, "include") + os.path.pathsep + - os.path.join(prefix, "PC")) - return os.path.join(prefix, "include") - else: - raise DistutilsPlatformError( - "I don't know where Python installs its C header files " - "on platform '%s'" % os.name) - - -def get_python_lib(plat_specific=0, standard_lib=0, prefix=None): - """Return the directory containing the Python library (standard or - site additions). - - If 'plat_specific' is true, return the directory containing - platform-specific modules, i.e. any module from a non-pure-Python - module distribution; otherwise, return the platform-shared library - directory. If 'standard_lib' is true, return the directory - containing standard Python library modules; otherwise, return the - directory for site-specific modules. - - If 'prefix' is supplied, use it instead of sys.base_prefix or - sys.base_exec_prefix -- i.e., ignore 'plat_specific'. - """ - if prefix is None: - if standard_lib: - prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX - else: - prefix = plat_specific and EXEC_PREFIX or PREFIX - - if os.name == "posix": - if plat_specific or standard_lib: - # Platform-specific modules (any module from a non-pure-Python - # module distribution) or standard Python library modules. - libdir = sys.platlibdir - else: - # Pure Python - libdir = "lib" - libpython = os.path.join(prefix, libdir, - # XXX RUSTPYTHON: changed from python->rustpython - "rustpython" + get_python_version()) - if standard_lib: - return libpython - else: - return os.path.join(libpython, "site-packages") - elif os.name == "nt": - if standard_lib: - return os.path.join(prefix, "Lib") - else: - return os.path.join(prefix, "Lib", "site-packages") - else: - raise DistutilsPlatformError( - "I don't know where Python installs its library " - "on platform '%s'" % os.name) - - - -def customize_compiler(compiler): - """Do any platform-specific customization of a CCompiler instance. - - Mainly needed on Unix, so we can plug in the information that - varies across Unices and is stored in Python's Makefile. - """ - if compiler.compiler_type == "unix": - if sys.platform == "darwin": - # Perform first-time customization of compiler-related - # config vars on OS X now that we know we need a compiler. - # This is primarily to support Pythons from binary - # installers. The kind and paths to build tools on - # the user system may vary significantly from the system - # that Python itself was built on. Also the user OS - # version and build tools may not support the same set - # of CPU architectures for universal builds. - global _config_vars - # Use get_config_var() to ensure _config_vars is initialized. - if not get_config_var('CUSTOMIZED_OSX_COMPILER'): - import _osx_support - _osx_support.customize_compiler(_config_vars) - _config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True' - - (cc, cxx, cflags, ccshared, ldshared, shlib_suffix, ar, ar_flags) = \ - get_config_vars('CC', 'CXX', 'CFLAGS', - 'CCSHARED', 'LDSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS') - - if 'CC' in os.environ: - newcc = os.environ['CC'] - if (sys.platform == 'darwin' - and 'LDSHARED' not in os.environ - and ldshared.startswith(cc)): - # On OS X, if CC is overridden, use that as the default - # command for LDSHARED as well - ldshared = newcc + ldshared[len(cc):] - cc = newcc - if 'CXX' in os.environ: - cxx = os.environ['CXX'] - if 'LDSHARED' in os.environ: - ldshared = os.environ['LDSHARED'] - if 'CPP' in os.environ: - cpp = os.environ['CPP'] - else: - cpp = cc + " -E" # not always - if 'LDFLAGS' in os.environ: - ldshared = ldshared + ' ' + os.environ['LDFLAGS'] - if 'CFLAGS' in os.environ: - cflags = cflags + ' ' + os.environ['CFLAGS'] - ldshared = ldshared + ' ' + os.environ['CFLAGS'] - if 'CPPFLAGS' in os.environ: - cpp = cpp + ' ' + os.environ['CPPFLAGS'] - cflags = cflags + ' ' + os.environ['CPPFLAGS'] - ldshared = ldshared + ' ' + os.environ['CPPFLAGS'] - if 'AR' in os.environ: - ar = os.environ['AR'] - if 'ARFLAGS' in os.environ: - archiver = ar + ' ' + os.environ['ARFLAGS'] - else: - archiver = ar + ' ' + ar_flags - - cc_cmd = cc + ' ' + cflags - compiler.set_executables( - preprocessor=cpp, - compiler=cc_cmd, - compiler_so=cc_cmd + ' ' + ccshared, - compiler_cxx=cxx, - linker_so=ldshared, - linker_exe=cc, - archiver=archiver) - - compiler.shared_lib_extension = shlib_suffix - - -def get_config_h_filename(): - """Return full pathname of installed pyconfig.h file.""" - if python_build: - if os.name == "nt": - inc_dir = os.path.join(_sys_home or project_base, "PC") - else: - inc_dir = _sys_home or project_base - else: - inc_dir = get_python_inc(plat_specific=1) - - return os.path.join(inc_dir, 'pyconfig.h') - - -def get_makefile_filename(): - """Return full pathname of installed Makefile from the Python build.""" - if python_build: - return os.path.join(_sys_home or project_base, "Makefile") - lib_dir = get_python_lib(plat_specific=0, standard_lib=1) - config_file = 'config-{}{}'.format(get_python_version(), build_flags) - if hasattr(sys.implementation, '_multiarch'): - config_file += '-%s' % sys.implementation._multiarch - return os.path.join(lib_dir, config_file, 'Makefile') - - -def parse_config_h(fp, g=None): - """Parse a config.h-style file. - - A dictionary containing name/value pairs is returned. If an - optional dictionary is passed in as the second argument, it is - used instead of a new dictionary. - """ - if g is None: - g = {} - define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n") - undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n") - # - while True: - line = fp.readline() - if not line: - break - m = define_rx.match(line) - if m: - n, v = m.group(1, 2) - try: v = int(v) - except ValueError: pass - g[n] = v - else: - m = undef_rx.match(line) - if m: - g[m.group(1)] = 0 - return g - - -# Regexes needed for parsing Makefile (and similar syntaxes, -# like old-style Setup files). -_variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)") -_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)") -_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}") - -def parse_makefile(fn, g=None): - """Parse a Makefile-style file. - - A dictionary containing name/value pairs is returned. If an - optional dictionary is passed in as the second argument, it is - used instead of a new dictionary. - """ - from distutils.text_file import TextFile - fp = TextFile(fn, strip_comments=1, skip_blanks=1, join_lines=1, errors="surrogateescape") - - if g is None: - g = {} - done = {} - notdone = {} - - while True: - line = fp.readline() - if line is None: # eof - break - m = _variable_rx.match(line) - if m: - n, v = m.group(1, 2) - v = v.strip() - # `$$' is a literal `$' in make - tmpv = v.replace('$$', '') - - if "$" in tmpv: - notdone[n] = v - else: - try: - v = int(v) - except ValueError: - # insert literal `$' - done[n] = v.replace('$$', '$') - else: - done[n] = v - - # Variables with a 'PY_' prefix in the makefile. These need to - # be made available without that prefix through sysconfig. - # Special care is needed to ensure that variable expansion works, even - # if the expansion uses the name without a prefix. - renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS') - - # do variable interpolation here - while notdone: - for name in list(notdone): - value = notdone[name] - m = _findvar1_rx.search(value) or _findvar2_rx.search(value) - if m: - n = m.group(1) - found = True - if n in done: - item = str(done[n]) - elif n in notdone: - # get it on a subsequent round - found = False - elif n in os.environ: - # do it like make: fall back to environment - item = os.environ[n] - - elif n in renamed_variables: - if name.startswith('PY_') and name[3:] in renamed_variables: - item = "" - - elif 'PY_' + n in notdone: - found = False - - else: - item = str(done['PY_' + n]) - else: - done[n] = item = "" - if found: - after = value[m.end():] - value = value[:m.start()] + item + after - if "$" in after: - notdone[name] = value - else: - try: value = int(value) - except ValueError: - done[name] = value.strip() - else: - done[name] = value - del notdone[name] - - if name.startswith('PY_') \ - and name[3:] in renamed_variables: - - name = name[3:] - if name not in done: - done[name] = value - else: - # bogus variable reference; just drop it since we can't deal - del notdone[name] - - fp.close() - - # strip spurious spaces - for k, v in done.items(): - if isinstance(v, str): - done[k] = v.strip() - - # save the results in the global dictionary - g.update(done) - return g - - -def expand_makefile_vars(s, vars): - """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in - 'string' according to 'vars' (a dictionary mapping variable names to - values). Variables not present in 'vars' are silently expanded to the - empty string. The variable values in 'vars' should not contain further - variable expansions; if 'vars' is the output of 'parse_makefile()', - you're fine. Returns a variable-expanded version of 's'. - """ - - # This algorithm does multiple expansion, so if vars['foo'] contains - # "${bar}", it will expand ${foo} to ${bar}, and then expand - # ${bar}... and so forth. This is fine as long as 'vars' comes from - # 'parse_makefile()', which takes care of such expansions eagerly, - # according to make's variable expansion semantics. - - while True: - m = _findvar1_rx.search(s) or _findvar2_rx.search(s) - if m: - (beg, end) = m.span() - s = s[0:beg] + vars.get(m.group(1)) + s[end:] - else: - break - return s - - -_config_vars = None - -def _init_posix(): - """Initialize the module as appropriate for POSIX systems.""" - # _sysconfigdata is generated at build time, see the sysconfig module - name = os.environ.get('_PYTHON_SYSCONFIGDATA_NAME', - '_sysconfigdata_{abi}_{platform}_{multiarch}'.format( - abi=sys.abiflags, - platform=sys.platform, - multiarch=getattr(sys.implementation, '_multiarch', ''), - )) - _temp = __import__(name, globals(), locals(), ['build_time_vars'], 0) - build_time_vars = _temp.build_time_vars - global _config_vars - _config_vars = {} - _config_vars.update(build_time_vars) - - -def _init_nt(): - """Initialize the module as appropriate for NT""" - g = {} - # set basic install directories - g['LIBDEST'] = get_python_lib(plat_specific=0, standard_lib=1) - g['BINLIBDEST'] = get_python_lib(plat_specific=1, standard_lib=1) - - # XXX hmmm.. a normal install puts include files here - g['INCLUDEPY'] = get_python_inc(plat_specific=0) - - g['EXT_SUFFIX'] = _imp.extension_suffixes()[0] - g['EXE'] = ".exe" - g['VERSION'] = get_python_version().replace(".", "") - g['BINDIR'] = os.path.dirname(os.path.abspath(sys.executable)) - - global _config_vars - _config_vars = g - - -def get_config_vars(*args): - """With no arguments, return a dictionary of all configuration - variables relevant for the current platform. Generally this includes - everything needed to build extensions and install both pure modules and - extensions. On Unix, this means every variable defined in Python's - installed Makefile; on Windows it's a much smaller set. - - With arguments, return a list of values that result from looking up - each argument in the configuration variable dictionary. - """ - global _config_vars - if _config_vars is None: - func = globals().get("_init_" + os.name) - if func: - func() - else: - _config_vars = {} - - # Normalized versions of prefix and exec_prefix are handy to have; - # in fact, these are the standard versions used most places in the - # Distutils. - _config_vars['prefix'] = PREFIX - _config_vars['exec_prefix'] = EXEC_PREFIX - - # For backward compatibility, see issue19555 - SO = _config_vars.get('EXT_SUFFIX') - if SO is not None: - _config_vars['SO'] = SO - - # Always convert srcdir to an absolute path - srcdir = _config_vars.get('srcdir', project_base) - if os.name == 'posix': - if python_build: - # If srcdir is a relative path (typically '.' or '..') - # then it should be interpreted relative to the directory - # containing Makefile. - base = os.path.dirname(get_makefile_filename()) - srcdir = os.path.join(base, srcdir) - else: - # srcdir is not meaningful since the installation is - # spread about the filesystem. We choose the - # directory containing the Makefile since we know it - # exists. - srcdir = os.path.dirname(get_makefile_filename()) - _config_vars['srcdir'] = os.path.abspath(os.path.normpath(srcdir)) - - # Convert srcdir into an absolute path if it appears necessary. - # Normally it is relative to the build directory. However, during - # testing, for example, we might be running a non-installed python - # from a different directory. - if python_build and os.name == "posix": - base = project_base - if (not os.path.isabs(_config_vars['srcdir']) and - base != os.getcwd()): - # srcdir is relative and we are not in the same directory - # as the executable. Assume executable is in the build - # directory and make srcdir absolute. - srcdir = os.path.join(base, _config_vars['srcdir']) - _config_vars['srcdir'] = os.path.normpath(srcdir) - - # OS X platforms require special customization to handle - # multi-architecture, multi-os-version installers - if sys.platform == 'darwin': - import _osx_support - _osx_support.customize_config_vars(_config_vars) - - if args: - vals = [] - for name in args: - vals.append(_config_vars.get(name)) - return vals - else: - return _config_vars - -def get_config_var(name): - """Return the value of a single variable using the dictionary - returned by 'get_config_vars()'. Equivalent to - get_config_vars().get(name) - """ - if name == 'SO': - import warnings - warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2) - return get_config_vars().get(name) diff --git a/Lib/distutils/text_file.py b/Lib/distutils/text_file.py deleted file mode 100644 index 93abad38f43..00000000000 --- a/Lib/distutils/text_file.py +++ /dev/null @@ -1,286 +0,0 @@ -"""text_file - -provides the TextFile class, which gives an interface to text files -that (optionally) takes care of stripping comments, ignoring blank -lines, and joining lines with backslashes.""" - -import sys, io - - -class TextFile: - """Provides a file-like object that takes care of all the things you - commonly want to do when processing a text file that has some - line-by-line syntax: strip comments (as long as "#" is your - comment character), skip blank lines, join adjacent lines by - escaping the newline (ie. backslash at end of line), strip - leading and/or trailing whitespace. All of these are optional - and independently controllable. - - Provides a 'warn()' method so you can generate warning messages that - report physical line number, even if the logical line in question - spans multiple physical lines. Also provides 'unreadline()' for - implementing line-at-a-time lookahead. - - Constructor is called as: - - TextFile (filename=None, file=None, **options) - - It bombs (RuntimeError) if both 'filename' and 'file' are None; - 'filename' should be a string, and 'file' a file object (or - something that provides 'readline()' and 'close()' methods). It is - recommended that you supply at least 'filename', so that TextFile - can include it in warning messages. If 'file' is not supplied, - TextFile creates its own using 'io.open()'. - - The options are all boolean, and affect the value returned by - 'readline()': - strip_comments [default: true] - strip from "#" to end-of-line, as well as any whitespace - leading up to the "#" -- unless it is escaped by a backslash - lstrip_ws [default: false] - strip leading whitespace from each line before returning it - rstrip_ws [default: true] - strip trailing whitespace (including line terminator!) from - each line before returning it - skip_blanks [default: true} - skip lines that are empty *after* stripping comments and - whitespace. (If both lstrip_ws and rstrip_ws are false, - then some lines may consist of solely whitespace: these will - *not* be skipped, even if 'skip_blanks' is true.) - join_lines [default: false] - if a backslash is the last non-newline character on a line - after stripping comments and whitespace, join the following line - to it to form one "logical line"; if N consecutive lines end - with a backslash, then N+1 physical lines will be joined to - form one logical line. - collapse_join [default: false] - strip leading whitespace from lines that are joined to their - predecessor; only matters if (join_lines and not lstrip_ws) - errors [default: 'strict'] - error handler used to decode the file content - - Note that since 'rstrip_ws' can strip the trailing newline, the - semantics of 'readline()' must differ from those of the builtin file - object's 'readline()' method! In particular, 'readline()' returns - None for end-of-file: an empty string might just be a blank line (or - an all-whitespace line), if 'rstrip_ws' is true but 'skip_blanks' is - not.""" - - default_options = { 'strip_comments': 1, - 'skip_blanks': 1, - 'lstrip_ws': 0, - 'rstrip_ws': 1, - 'join_lines': 0, - 'collapse_join': 0, - 'errors': 'strict', - } - - def __init__(self, filename=None, file=None, **options): - """Construct a new TextFile object. At least one of 'filename' - (a string) and 'file' (a file-like object) must be supplied. - They keyword argument options are described above and affect - the values returned by 'readline()'.""" - if filename is None and file is None: - raise RuntimeError("you must supply either or both of 'filename' and 'file'") - - # set values for all options -- either from client option hash - # or fallback to default_options - for opt in self.default_options.keys(): - if opt in options: - setattr(self, opt, options[opt]) - else: - setattr(self, opt, self.default_options[opt]) - - # sanity check client option hash - for opt in options.keys(): - if opt not in self.default_options: - raise KeyError("invalid TextFile option '%s'" % opt) - - if file is None: - self.open(filename) - else: - self.filename = filename - self.file = file - self.current_line = 0 # assuming that file is at BOF! - - # 'linebuf' is a stack of lines that will be emptied before we - # actually read from the file; it's only populated by an - # 'unreadline()' operation - self.linebuf = [] - - def open(self, filename): - """Open a new file named 'filename'. This overrides both the - 'filename' and 'file' arguments to the constructor.""" - self.filename = filename - self.file = io.open(self.filename, 'r', errors=self.errors) - self.current_line = 0 - - def close(self): - """Close the current file and forget everything we know about it - (filename, current line number).""" - file = self.file - self.file = None - self.filename = None - self.current_line = None - file.close() - - def gen_error(self, msg, line=None): - outmsg = [] - if line is None: - line = self.current_line - outmsg.append(self.filename + ", ") - if isinstance(line, (list, tuple)): - outmsg.append("lines %d-%d: " % tuple(line)) - else: - outmsg.append("line %d: " % line) - outmsg.append(str(msg)) - return "".join(outmsg) - - def error(self, msg, line=None): - raise ValueError("error: " + self.gen_error(msg, line)) - - def warn(self, msg, line=None): - """Print (to stderr) a warning message tied to the current logical - line in the current file. If the current logical line in the - file spans multiple physical lines, the warning refers to the - whole range, eg. "lines 3-5". If 'line' supplied, it overrides - the current line number; it may be a list or tuple to indicate a - range of physical lines, or an integer for a single physical - line.""" - sys.stderr.write("warning: " + self.gen_error(msg, line) + "\n") - - def readline(self): - """Read and return a single logical line from the current file (or - from an internal buffer if lines have previously been "unread" - with 'unreadline()'). If the 'join_lines' option is true, this - may involve reading multiple physical lines concatenated into a - single string. Updates the current line number, so calling - 'warn()' after 'readline()' emits a warning about the physical - line(s) just read. Returns None on end-of-file, since the empty - string can occur if 'rstrip_ws' is true but 'strip_blanks' is - not.""" - # If any "unread" lines waiting in 'linebuf', return the top - # one. (We don't actually buffer read-ahead data -- lines only - # get put in 'linebuf' if the client explicitly does an - # 'unreadline()'. - if self.linebuf: - line = self.linebuf[-1] - del self.linebuf[-1] - return line - - buildup_line = '' - - while True: - # read the line, make it None if EOF - line = self.file.readline() - if line == '': - line = None - - if self.strip_comments and line: - - # Look for the first "#" in the line. If none, never - # mind. If we find one and it's the first character, or - # is not preceded by "\", then it starts a comment -- - # strip the comment, strip whitespace before it, and - # carry on. Otherwise, it's just an escaped "#", so - # unescape it (and any other escaped "#"'s that might be - # lurking in there) and otherwise leave the line alone. - - pos = line.find("#") - if pos == -1: # no "#" -- no comments - pass - - # It's definitely a comment -- either "#" is the first - # character, or it's elsewhere and unescaped. - elif pos == 0 or line[pos-1] != "\\": - # Have to preserve the trailing newline, because it's - # the job of a later step (rstrip_ws) to remove it -- - # and if rstrip_ws is false, we'd better preserve it! - # (NB. this means that if the final line is all comment - # and has no trailing newline, we will think that it's - # EOF; I think that's OK.) - eol = (line[-1] == '\n') and '\n' or '' - line = line[0:pos] + eol - - # If all that's left is whitespace, then skip line - # *now*, before we try to join it to 'buildup_line' -- - # that way constructs like - # hello \\ - # # comment that should be ignored - # there - # result in "hello there". - if line.strip() == "": - continue - else: # it's an escaped "#" - line = line.replace("\\#", "#") - - # did previous line end with a backslash? then accumulate - if self.join_lines and buildup_line: - # oops: end of file - if line is None: - self.warn("continuation line immediately precedes " - "end-of-file") - return buildup_line - - if self.collapse_join: - line = line.lstrip() - line = buildup_line + line - - # careful: pay attention to line number when incrementing it - if isinstance(self.current_line, list): - self.current_line[1] = self.current_line[1] + 1 - else: - self.current_line = [self.current_line, - self.current_line + 1] - # just an ordinary line, read it as usual - else: - if line is None: # eof - return None - - # still have to be careful about incrementing the line number! - if isinstance(self.current_line, list): - self.current_line = self.current_line[1] + 1 - else: - self.current_line = self.current_line + 1 - - # strip whitespace however the client wants (leading and - # trailing, or one or the other, or neither) - if self.lstrip_ws and self.rstrip_ws: - line = line.strip() - elif self.lstrip_ws: - line = line.lstrip() - elif self.rstrip_ws: - line = line.rstrip() - - # blank line (whether we rstrip'ed or not)? skip to next line - # if appropriate - if (line == '' or line == '\n') and self.skip_blanks: - continue - - if self.join_lines: - if line[-1] == '\\': - buildup_line = line[:-1] - continue - - if line[-2:] == '\\\n': - buildup_line = line[0:-2] + '\n' - continue - - # well, I guess there's some actual content there: return it - return line - - def readlines(self): - """Read and return the list of all logical lines remaining in the - current file.""" - lines = [] - while True: - line = self.readline() - if line is None: - return lines - lines.append(line) - - def unreadline(self, line): - """Push 'line' (a string) onto an internal buffer that will be - checked by future 'readline()' calls. Handy for implementing - a parser with line-at-a-time lookahead.""" - self.linebuf.append(line) diff --git a/Lib/distutils/unixccompiler.py b/Lib/distutils/unixccompiler.py deleted file mode 100644 index 4524417e668..00000000000 --- a/Lib/distutils/unixccompiler.py +++ /dev/null @@ -1,333 +0,0 @@ -"""distutils.unixccompiler - -Contains the UnixCCompiler class, a subclass of CCompiler that handles -the "typical" Unix-style command-line C compiler: - * macros defined with -Dname[=value] - * macros undefined with -Uname - * include search directories specified with -Idir - * libraries specified with -lllib - * library search directories specified with -Ldir - * compile handled by 'cc' (or similar) executable with -c option: - compiles .c to .o - * link static library handled by 'ar' command (possibly with 'ranlib') - * link shared library handled by 'cc -shared' -""" - -import os, sys, re - -from distutils import sysconfig -from distutils.dep_util import newer -from distutils.ccompiler import \ - CCompiler, gen_preprocess_options, gen_lib_options -from distutils.errors import \ - DistutilsExecError, CompileError, LibError, LinkError -from distutils import log - -if sys.platform == 'darwin': - import _osx_support - -# XXX Things not currently handled: -# * optimization/debug/warning flags; we just use whatever's in Python's -# Makefile and live with it. Is this adequate? If not, we might -# have to have a bunch of subclasses GNUCCompiler, SGICCompiler, -# SunCCompiler, and I suspect down that road lies madness. -# * even if we don't know a warning flag from an optimization flag, -# we need some way for outsiders to feed preprocessor/compiler/linker -# flags in to us -- eg. a sysadmin might want to mandate certain flags -# via a site config file, or a user might want to set something for -# compiling this module distribution only via the setup.py command -# line, whatever. As long as these options come from something on the -# current system, they can be as system-dependent as they like, and we -# should just happily stuff them into the preprocessor/compiler/linker -# options and carry on. - - -class UnixCCompiler(CCompiler): - - compiler_type = 'unix' - - # These are used by CCompiler in two places: the constructor sets - # instance attributes 'preprocessor', 'compiler', etc. from them, and - # 'set_executable()' allows any of these to be set. The defaults here - # are pretty generic; they will probably have to be set by an outsider - # (eg. using information discovered by the sysconfig about building - # Python extensions). - executables = {'preprocessor' : None, - 'compiler' : ["cc"], - 'compiler_so' : ["cc"], - 'compiler_cxx' : ["cc"], - 'linker_so' : ["cc", "-shared"], - 'linker_exe' : ["cc"], - 'archiver' : ["ar", "-cr"], - 'ranlib' : None, - } - - if sys.platform[:6] == "darwin": - executables['ranlib'] = ["ranlib"] - - # Needed for the filename generation methods provided by the base - # class, CCompiler. NB. whoever instantiates/uses a particular - # UnixCCompiler instance should set 'shared_lib_ext' -- we set a - # reasonable common default here, but it's not necessarily used on all - # Unices! - - src_extensions = [".c",".C",".cc",".cxx",".cpp",".m"] - obj_extension = ".o" - static_lib_extension = ".a" - shared_lib_extension = ".so" - dylib_lib_extension = ".dylib" - xcode_stub_lib_extension = ".tbd" - static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s" - xcode_stub_lib_format = dylib_lib_format - if sys.platform == "cygwin": - exe_extension = ".exe" - - def preprocess(self, source, output_file=None, macros=None, - include_dirs=None, extra_preargs=None, extra_postargs=None): - fixed_args = self._fix_compile_args(None, macros, include_dirs) - ignore, macros, include_dirs = fixed_args - pp_opts = gen_preprocess_options(macros, include_dirs) - pp_args = self.preprocessor + pp_opts - if output_file: - pp_args.extend(['-o', output_file]) - if extra_preargs: - pp_args[:0] = extra_preargs - if extra_postargs: - pp_args.extend(extra_postargs) - pp_args.append(source) - - # We need to preprocess: either we're being forced to, or we're - # generating output to stdout, or there's a target output file and - # the source file is newer than the target (or the target doesn't - # exist). - if self.force or output_file is None or newer(source, output_file): - if output_file: - self.mkpath(os.path.dirname(output_file)) - try: - self.spawn(pp_args) - except DistutilsExecError as msg: - raise CompileError(msg) - - def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): - compiler_so = self.compiler_so - if sys.platform == 'darwin': - compiler_so = _osx_support.compiler_fixup(compiler_so, - cc_args + extra_postargs) - try: - self.spawn(compiler_so + cc_args + [src, '-o', obj] + - extra_postargs) - except DistutilsExecError as msg: - raise CompileError(msg) - - def create_static_lib(self, objects, output_libname, - output_dir=None, debug=0, target_lang=None): - objects, output_dir = self._fix_object_args(objects, output_dir) - - output_filename = \ - self.library_filename(output_libname, output_dir=output_dir) - - if self._need_link(objects, output_filename): - self.mkpath(os.path.dirname(output_filename)) - self.spawn(self.archiver + - [output_filename] + - objects + self.objects) - - # Not many Unices required ranlib anymore -- SunOS 4.x is, I - # think the only major Unix that does. Maybe we need some - # platform intelligence here to skip ranlib if it's not - # needed -- or maybe Python's configure script took care of - # it for us, hence the check for leading colon. - if self.ranlib: - try: - self.spawn(self.ranlib + [output_filename]) - except DistutilsExecError as msg: - raise LibError(msg) - else: - log.debug("skipping %s (up-to-date)", output_filename) - - def link(self, target_desc, objects, - output_filename, output_dir=None, libraries=None, - library_dirs=None, runtime_library_dirs=None, - export_symbols=None, debug=0, extra_preargs=None, - extra_postargs=None, build_temp=None, target_lang=None): - objects, output_dir = self._fix_object_args(objects, output_dir) - fixed_args = self._fix_lib_args(libraries, library_dirs, - runtime_library_dirs) - libraries, library_dirs, runtime_library_dirs = fixed_args - - # filter out standard library paths, which are not explicitely needed - # for linking - system_libdirs = ['/lib', '/lib64', '/usr/lib', '/usr/lib64'] - multiarch = sysconfig.get_config_var("MULTIARCH") - if multiarch: - system_libdirs.extend(['/lib/%s' % multiarch, '/usr/lib/%s' % multiarch]) - library_dirs = [dir for dir in library_dirs - if not dir in system_libdirs] - runtime_library_dirs = [dir for dir in runtime_library_dirs - if not dir in system_libdirs] - - lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, - libraries) - if not isinstance(output_dir, (str, type(None))): - raise TypeError("'output_dir' must be a string or None") - if output_dir is not None: - output_filename = os.path.join(output_dir, output_filename) - - if self._need_link(objects, output_filename): - ld_args = (objects + self.objects + - lib_opts + ['-o', output_filename]) - if debug: - ld_args[:0] = ['-g'] - if extra_preargs: - ld_args[:0] = extra_preargs - if extra_postargs: - ld_args.extend(extra_postargs) - self.mkpath(os.path.dirname(output_filename)) - try: - if target_desc == CCompiler.EXECUTABLE: - linker = self.linker_exe[:] - else: - linker = self.linker_so[:] - if target_lang == "c++" and self.compiler_cxx: - # skip over environment variable settings if /usr/bin/env - # is used to set up the linker's environment. - # This is needed on OSX. Note: this assumes that the - # normal and C++ compiler have the same environment - # settings. - i = 0 - if os.path.basename(linker[0]) == "env": - i = 1 - while '=' in linker[i]: - i += 1 - linker[i] = self.compiler_cxx[i] - - if sys.platform == 'darwin': - linker = _osx_support.compiler_fixup(linker, ld_args) - - self.spawn(linker + ld_args) - except DistutilsExecError as msg: - raise LinkError(msg) - else: - log.debug("skipping %s (up-to-date)", output_filename) - - # -- Miscellaneous methods ----------------------------------------- - # These are all used by the 'gen_lib_options() function, in - # ccompiler.py. - - def library_dir_option(self, dir): - return "-L" + dir - - def _is_gcc(self, compiler_name): - return "gcc" in compiler_name or "g++" in compiler_name - - def runtime_library_dir_option(self, dir): - # XXX Hackish, at the very least. See Python bug #445902: - # http://sourceforge.net/tracker/index.php - # ?func=detail&aid=445902&group_id=5470&atid=105470 - # Linkers on different platforms need different options to - # specify that directories need to be added to the list of - # directories searched for dependencies when a dynamic library - # is sought. GCC on GNU systems (Linux, FreeBSD, ...) has to - # be told to pass the -R option through to the linker, whereas - # other compilers and gcc on other systems just know this. - # Other compilers may need something slightly different. At - # this time, there's no way to determine this information from - # the configuration data stored in the Python installation, so - # we use this hack. - compiler = os.path.basename(sysconfig.get_config_var("CC")) - if sys.platform[:6] == "darwin": - # MacOSX's linker doesn't understand the -R flag at all - return "-L" + dir - elif sys.platform[:7] == "freebsd": - return "-Wl,-rpath=" + dir - elif sys.platform[:5] == "hp-ux": - if self._is_gcc(compiler): - return ["-Wl,+s", "-L" + dir] - return ["+s", "-L" + dir] - elif sys.platform[:7] == "irix646" or sys.platform[:6] == "osf1V5": - return ["-rpath", dir] - else: - if self._is_gcc(compiler): - # gcc on non-GNU systems does not need -Wl, but can - # use it anyway. Since distutils has always passed in - # -Wl whenever gcc was used in the past it is probably - # safest to keep doing so. - if sysconfig.get_config_var("GNULD") == "yes": - # GNU ld needs an extra option to get a RUNPATH - # instead of just an RPATH. - return "-Wl,--enable-new-dtags,-R" + dir - else: - return "-Wl,-R" + dir - else: - # No idea how --enable-new-dtags would be passed on to - # ld if this system was using GNU ld. Don't know if a - # system like this even exists. - return "-R" + dir - - def library_option(self, lib): - return "-l" + lib - - def find_library_file(self, dirs, lib, debug=0): - shared_f = self.library_filename(lib, lib_type='shared') - dylib_f = self.library_filename(lib, lib_type='dylib') - xcode_stub_f = self.library_filename(lib, lib_type='xcode_stub') - static_f = self.library_filename(lib, lib_type='static') - - if sys.platform == 'darwin': - # On OSX users can specify an alternate SDK using - # '-isysroot', calculate the SDK root if it is specified - # (and use it further on) - # - # Note that, as of Xcode 7, Apple SDKs may contain textual stub - # libraries with .tbd extensions rather than the normal .dylib - # shared libraries installed in /. The Apple compiler tool - # chain handles this transparently but it can cause problems - # for programs that are being built with an SDK and searching - # for specific libraries. Callers of find_library_file need to - # keep in mind that the base filename of the returned SDK library - # file might have a different extension from that of the library - # file installed on the running system, for example: - # /Applications/Xcode.app/Contents/Developer/Platforms/ - # MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/ - # usr/lib/libedit.tbd - # vs - # /usr/lib/libedit.dylib - cflags = sysconfig.get_config_var('CFLAGS') - m = re.search(r'-isysroot\s+(\S+)', cflags) - if m is None: - sysroot = '/' - else: - sysroot = m.group(1) - - - - for dir in dirs: - shared = os.path.join(dir, shared_f) - dylib = os.path.join(dir, dylib_f) - static = os.path.join(dir, static_f) - xcode_stub = os.path.join(dir, xcode_stub_f) - - if sys.platform == 'darwin' and ( - dir.startswith('/System/') or ( - dir.startswith('/usr/') and not dir.startswith('/usr/local/'))): - - shared = os.path.join(sysroot, dir[1:], shared_f) - dylib = os.path.join(sysroot, dir[1:], dylib_f) - static = os.path.join(sysroot, dir[1:], static_f) - xcode_stub = os.path.join(sysroot, dir[1:], xcode_stub_f) - - # We're second-guessing the linker here, with not much hard - # data to go on: GCC seems to prefer the shared library, so I'm - # assuming that *all* Unix C compilers do. And of course I'm - # ignoring even GCC's "-static" option. So sue me. - if os.path.exists(dylib): - return dylib - elif os.path.exists(xcode_stub): - return xcode_stub - elif os.path.exists(shared): - return shared - elif os.path.exists(static): - return static - - # Oops, didn't find it in *any* of 'dirs' - return None diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py deleted file mode 100644 index fdcf6fabae2..00000000000 --- a/Lib/distutils/util.py +++ /dev/null @@ -1,557 +0,0 @@ -"""distutils.util - -Miscellaneous utility functions -- anything that doesn't fit into -one of the other *util.py modules. -""" - -import os -import re -import importlib.util -import string -import sys -from distutils.errors import DistutilsPlatformError -from distutils.dep_util import newer -from distutils.spawn import spawn -from distutils import log -from distutils.errors import DistutilsByteCompileError - -def get_platform (): - """Return a string that identifies the current platform. This is used - mainly to distinguish platform-specific build directories and - platform-specific built distributions. Typically includes the OS name - and version and the architecture (as supplied by 'os.uname()'), - although the exact information included depends on the OS; eg. for IRIX - the architecture isn't particularly important (IRIX only runs on SGI - hardware), but for Linux the kernel version isn't particularly - important. - - Examples of returned values: - linux-i586 - linux-alpha (?) - solaris-2.6-sun4u - irix-5.3 - irix64-6.2 - - Windows will return one of: - win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) - win-ia64 (64bit Windows on Itanium) - win32 (all others - specifically, sys.platform is returned) - - For other non-POSIX platforms, currently just returns 'sys.platform'. - """ - if os.name == 'nt': - # sniff sys.version for architecture. - prefix = " bit (" - i = sys.version.find(prefix) - if i == -1: - return sys.platform - j = sys.version.find(")", i) - look = sys.version[i+len(prefix):j].lower() - if look == 'amd64': - return 'win-amd64' - if look == 'itanium': - return 'win-ia64' - return sys.platform - - # Set for cross builds explicitly - if "_PYTHON_HOST_PLATFORM" in os.environ: - return os.environ["_PYTHON_HOST_PLATFORM"] - - if os.name != "posix" or not hasattr(os, 'uname'): - # XXX what about the architecture? NT is Intel or Alpha, - # Mac OS is M68k or PPC, etc. - return sys.platform - - # Try to distinguish various flavours of Unix - - (osname, host, release, version, machine) = os.uname() - - # Convert the OS name to lowercase, remove '/' characters - # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh") - osname = osname.lower().replace('/', '') - machine = machine.replace(' ', '_') - machine = machine.replace('/', '-') - - if osname[:5] == "linux": - # At least on Linux/Intel, 'machine' is the processor -- - # i386, etc. - # XXX what about Alpha, SPARC, etc? - return "%s-%s" % (osname, machine) - elif osname[:5] == "sunos": - if release[0] >= "5": # SunOS 5 == Solaris 2 - osname = "solaris" - release = "%d.%s" % (int(release[0]) - 3, release[2:]) - # We can't use "platform.architecture()[0]" because a - # bootstrap problem. We use a dict to get an error - # if some suspicious happens. - bitness = {2147483647:"32bit", 9223372036854775807:"64bit"} - machine += ".%s" % bitness[sys.maxsize] - # fall through to standard osname-release-machine representation - elif osname[:4] == "irix": # could be "irix64"! - return "%s-%s" % (osname, release) - elif osname[:3] == "aix": - return "%s-%s.%s" % (osname, version, release) - elif osname[:6] == "cygwin": - osname = "cygwin" - rel_re = re.compile (r'[\d.]+', re.ASCII) - m = rel_re.match(release) - if m: - release = m.group() - elif osname[:6] == "darwin": - import _osx_support, distutils.sysconfig - osname, release, machine = _osx_support.get_platform_osx( - distutils.sysconfig.get_config_vars(), - osname, release, machine) - - return "%s-%s-%s" % (osname, release, machine) - -# get_platform () - - -def convert_path (pathname): - """Return 'pathname' as a name that will work on the native filesystem, - i.e. split it on '/' and put it back together again using the current - directory separator. Needed because filenames in the setup script are - always supplied in Unix style, and have to be converted to the local - convention before we can actually use them in the filesystem. Raises - ValueError on non-Unix-ish systems if 'pathname' either starts or - ends with a slash. - """ - if os.sep == '/': - return pathname - if not pathname: - return pathname - if pathname[0] == '/': - raise ValueError("path '%s' cannot be absolute" % pathname) - if pathname[-1] == '/': - raise ValueError("path '%s' cannot end with '/'" % pathname) - - paths = pathname.split('/') - while '.' in paths: - paths.remove('.') - if not paths: - return os.curdir - return os.path.join(*paths) - -# convert_path () - - -def change_root (new_root, pathname): - """Return 'pathname' with 'new_root' prepended. If 'pathname' is - relative, this is equivalent to "os.path.join(new_root,pathname)". - Otherwise, it requires making 'pathname' relative and then joining the - two, which is tricky on DOS/Windows and Mac OS. - """ - if os.name == 'posix': - if not os.path.isabs(pathname): - return os.path.join(new_root, pathname) - else: - return os.path.join(new_root, pathname[1:]) - - elif os.name == 'nt': - (drive, path) = os.path.splitdrive(pathname) - if path[0] == '\\': - path = path[1:] - return os.path.join(new_root, path) - - else: - raise DistutilsPlatformError("nothing known about platform '%s'" % os.name) - - -_environ_checked = 0 -def check_environ (): - """Ensure that 'os.environ' has all the environment variables we - guarantee that users can use in config files, command-line options, - etc. Currently this includes: - HOME - user's home directory (Unix only) - PLAT - description of the current platform, including hardware - and OS (see 'get_platform()') - """ - global _environ_checked - if _environ_checked: - return - - if os.name == 'posix' and 'HOME' not in os.environ: - import pwd - os.environ['HOME'] = pwd.getpwuid(os.getuid())[5] - - if 'PLAT' not in os.environ: - os.environ['PLAT'] = get_platform() - - _environ_checked = 1 - - -def subst_vars (s, local_vars): - """Perform shell/Perl-style variable substitution on 'string'. Every - occurrence of '$' followed by a name is considered a variable, and - variable is substituted by the value found in the 'local_vars' - dictionary, or in 'os.environ' if it's not in 'local_vars'. - 'os.environ' is first checked/augmented to guarantee that it contains - certain values: see 'check_environ()'. Raise ValueError for any - variables not found in either 'local_vars' or 'os.environ'. - """ - check_environ() - def _subst (match, local_vars=local_vars): - var_name = match.group(1) - if var_name in local_vars: - return str(local_vars[var_name]) - else: - return os.environ[var_name] - - try: - return re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s) - except KeyError as var: - raise ValueError("invalid variable '$%s'" % var) - -# subst_vars () - - -def grok_environment_error (exc, prefix="error: "): - # Function kept for backward compatibility. - # Used to try clever things with EnvironmentErrors, - # but nowadays str(exception) produces good messages. - return prefix + str(exc) - - -# Needed by 'split_quoted()' -_wordchars_re = _squote_re = _dquote_re = None -def _init_regex(): - global _wordchars_re, _squote_re, _dquote_re - _wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace) - _squote_re = re.compile(r"'(?:[^'\\]|\\.)*'") - _dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"') - -def split_quoted (s): - """Split a string up according to Unix shell-like rules for quotes and - backslashes. In short: words are delimited by spaces, as long as those - spaces are not escaped by a backslash, or inside a quoted string. - Single and double quotes are equivalent, and the quote characters can - be backslash-escaped. The backslash is stripped from any two-character - escape sequence, leaving only the escaped character. The quote - characters are stripped from any quoted string. Returns a list of - words. - """ - - # This is a nice algorithm for splitting up a single string, since it - # doesn't require character-by-character examination. It was a little - # bit of a brain-bender to get it working right, though... - if _wordchars_re is None: _init_regex() - - s = s.strip() - words = [] - pos = 0 - - while s: - m = _wordchars_re.match(s, pos) - end = m.end() - if end == len(s): - words.append(s[:end]) - break - - if s[end] in string.whitespace: # unescaped, unquoted whitespace: now - words.append(s[:end]) # we definitely have a word delimiter - s = s[end:].lstrip() - pos = 0 - - elif s[end] == '\\': # preserve whatever is being escaped; - # will become part of the current word - s = s[:end] + s[end+1:] - pos = end+1 - - else: - if s[end] == "'": # slurp singly-quoted string - m = _squote_re.match(s, end) - elif s[end] == '"': # slurp doubly-quoted string - m = _dquote_re.match(s, end) - else: - raise RuntimeError("this can't happen (bad char '%c')" % s[end]) - - if m is None: - raise ValueError("bad string (mismatched %s quotes?)" % s[end]) - - (beg, end) = m.span() - s = s[:beg] + s[beg+1:end-1] + s[end:] - pos = m.end() - 2 - - if pos >= len(s): - words.append(s) - break - - return words - -# split_quoted () - - -def execute (func, args, msg=None, verbose=0, dry_run=0): - """Perform some action that affects the outside world (eg. by - writing to the filesystem). Such actions are special because they - are disabled by the 'dry_run' flag. This method takes care of all - that bureaucracy for you; all you have to do is supply the - function to call and an argument tuple for it (to embody the - "external action" being performed), and an optional message to - print. - """ - if msg is None: - msg = "%s%r" % (func.__name__, args) - if msg[-2:] == ',)': # correct for singleton tuple - msg = msg[0:-2] + ')' - - log.info(msg) - if not dry_run: - func(*args) - - -def strtobool (val): - """Convert a string representation of truth to true (1) or false (0). - - True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values - are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if - 'val' is anything else. - """ - val = val.lower() - if val in ('y', 'yes', 't', 'true', 'on', '1'): - return 1 - elif val in ('n', 'no', 'f', 'false', 'off', '0'): - return 0 - else: - raise ValueError("invalid truth value %r" % (val,)) - - -def byte_compile (py_files, - optimize=0, force=0, - prefix=None, base_dir=None, - verbose=1, dry_run=0, - direct=None): - """Byte-compile a collection of Python source files to .pyc - files in a __pycache__ subdirectory. 'py_files' is a list - of files to compile; any files that don't end in ".py" are silently - skipped. 'optimize' must be one of the following: - 0 - don't optimize - 1 - normal optimization (like "python -O") - 2 - extra optimization (like "python -OO") - If 'force' is true, all files are recompiled regardless of - timestamps. - - The source filename encoded in each bytecode file defaults to the - filenames listed in 'py_files'; you can modify these with 'prefix' and - 'basedir'. 'prefix' is a string that will be stripped off of each - source filename, and 'base_dir' is a directory name that will be - prepended (after 'prefix' is stripped). You can supply either or both - (or neither) of 'prefix' and 'base_dir', as you wish. - - If 'dry_run' is true, doesn't actually do anything that would - affect the filesystem. - - Byte-compilation is either done directly in this interpreter process - with the standard py_compile module, or indirectly by writing a - temporary script and executing it. Normally, you should let - 'byte_compile()' figure out to use direct compilation or not (see - the source for details). The 'direct' flag is used by the script - generated in indirect mode; unless you know what you're doing, leave - it set to None. - """ - - # Late import to fix a bootstrap issue: _posixsubprocess is built by - # setup.py, but setup.py uses distutils. - import subprocess - - # nothing is done if sys.dont_write_bytecode is True - if sys.dont_write_bytecode: - raise DistutilsByteCompileError('byte-compiling is disabled.') - - # First, if the caller didn't force us into direct or indirect mode, - # figure out which mode we should be in. We take a conservative - # approach: choose direct mode *only* if the current interpreter is - # in debug mode and optimize is 0. If we're not in debug mode (-O - # or -OO), we don't know which level of optimization this - # interpreter is running with, so we can't do direct - # byte-compilation and be certain that it's the right thing. Thus, - # always compile indirectly if the current interpreter is in either - # optimize mode, or if either optimization level was requested by - # the caller. - if direct is None: - direct = (__debug__ and optimize == 0) - - # "Indirect" byte-compilation: write a temporary script and then - # run it with the appropriate flags. - if not direct: - try: - from tempfile import mkstemp - (script_fd, script_name) = mkstemp(".py") - except ImportError: - from tempfile import mktemp - (script_fd, script_name) = None, mktemp(".py") - log.info("writing byte-compilation script '%s'", script_name) - if not dry_run: - if script_fd is not None: - script = os.fdopen(script_fd, "w") - else: - script = open(script_name, "w") - - script.write("""\ -from distutils.util import byte_compile -files = [ -""") - - # XXX would be nice to write absolute filenames, just for - # safety's sake (script should be more robust in the face of - # chdir'ing before running it). But this requires abspath'ing - # 'prefix' as well, and that breaks the hack in build_lib's - # 'byte_compile()' method that carefully tacks on a trailing - # slash (os.sep really) to make sure the prefix here is "just - # right". This whole prefix business is rather delicate -- the - # problem is that it's really a directory, but I'm treating it - # as a dumb string, so trailing slashes and so forth matter. - - #py_files = map(os.path.abspath, py_files) - #if prefix: - # prefix = os.path.abspath(prefix) - - script.write(",\n".join(map(repr, py_files)) + "]\n") - script.write(""" -byte_compile(files, optimize=%r, force=%r, - prefix=%r, base_dir=%r, - verbose=%r, dry_run=0, - direct=1) -""" % (optimize, force, prefix, base_dir, verbose)) - - script.close() - - cmd = [sys.executable] - cmd.extend(subprocess._optim_args_from_interpreter_flags()) - cmd.append(script_name) - spawn(cmd, dry_run=dry_run) - execute(os.remove, (script_name,), "removing %s" % script_name, - dry_run=dry_run) - - # "Direct" byte-compilation: use the py_compile module to compile - # right here, right now. Note that the script generated in indirect - # mode simply calls 'byte_compile()' in direct mode, a weird sort of - # cross-process recursion. Hey, it works! - else: - from py_compile import compile - - for file in py_files: - if file[-3:] != ".py": - # This lets us be lazy and not filter filenames in - # the "install_lib" command. - continue - - # Terminology from the py_compile module: - # cfile - byte-compiled file - # dfile - purported source filename (same as 'file' by default) - if optimize >= 0: - opt = '' if optimize == 0 else optimize - cfile = importlib.util.cache_from_source( - file, optimization=opt) - else: - cfile = importlib.util.cache_from_source(file) - dfile = file - if prefix: - if file[:len(prefix)] != prefix: - raise ValueError("invalid prefix: filename %r doesn't start with %r" - % (file, prefix)) - dfile = dfile[len(prefix):] - if base_dir: - dfile = os.path.join(base_dir, dfile) - - cfile_base = os.path.basename(cfile) - if direct: - if force or newer(file, cfile): - log.info("byte-compiling %s to %s", file, cfile_base) - if not dry_run: - compile(file, cfile, dfile) - else: - log.debug("skipping byte-compilation of %s to %s", - file, cfile_base) - -# byte_compile () - -def rfc822_escape (header): - """Return a version of the string escaped for inclusion in an - RFC-822 header, by ensuring there are 8 spaces space after each newline. - """ - lines = header.split('\n') - sep = '\n' + 8 * ' ' - return sep.join(lines) - -# 2to3 support - -def run_2to3(files, fixer_names=None, options=None, explicit=None): - """Invoke 2to3 on a list of Python files. - The files should all come from the build area, as the - modification is done in-place. To reduce the build time, - only files modified since the last invocation of this - function should be passed in the files argument.""" - - if not files: - return - - # Make this class local, to delay import of 2to3 - from lib2to3.refactor import RefactoringTool, get_fixers_from_package - class DistutilsRefactoringTool(RefactoringTool): - def log_error(self, msg, *args, **kw): - log.error(msg, *args) - - def log_message(self, msg, *args): - log.info(msg, *args) - - def log_debug(self, msg, *args): - log.debug(msg, *args) - - if fixer_names is None: - fixer_names = get_fixers_from_package('lib2to3.fixes') - r = DistutilsRefactoringTool(fixer_names, options=options) - r.refactor(files, write=True) - -def copydir_run_2to3(src, dest, template=None, fixer_names=None, - options=None, explicit=None): - """Recursively copy a directory, only copying new and changed files, - running run_2to3 over all newly copied Python modules afterward. - - If you give a template string, it's parsed like a MANIFEST.in. - """ - from distutils.dir_util import mkpath - from distutils.file_util import copy_file - from distutils.filelist import FileList - filelist = FileList() - curdir = os.getcwd() - os.chdir(src) - try: - filelist.findall() - finally: - os.chdir(curdir) - filelist.files[:] = filelist.allfiles - if template: - for line in template.splitlines(): - line = line.strip() - if not line: continue - filelist.process_template_line(line) - copied = [] - for filename in filelist.files: - outname = os.path.join(dest, filename) - mkpath(os.path.dirname(outname)) - res = copy_file(os.path.join(src, filename), outname, update=1) - if res[1]: copied.append(outname) - run_2to3([fn for fn in copied if fn.lower().endswith('.py')], - fixer_names=fixer_names, options=options, explicit=explicit) - return copied - -class Mixin2to3: - '''Mixin class for commands that run 2to3. - To configure 2to3, setup scripts may either change - the class variables, or inherit from individual commands - to override how 2to3 is invoked.''' - - # provide list of fixers to run; - # defaults to all from lib2to3.fixers - fixer_names = None - - # options dictionary - options = None - - # list of fixers to invoke even though they are marked as explicit - explicit = None - - def run_2to3(self, files): - return run_2to3(files, self.fixer_names, self.options, self.explicit) diff --git a/Lib/distutils/version.py b/Lib/distutils/version.py deleted file mode 100644 index af14cc13481..00000000000 --- a/Lib/distutils/version.py +++ /dev/null @@ -1,343 +0,0 @@ -# -# distutils/version.py -# -# Implements multiple version numbering conventions for the -# Python Module Distribution Utilities. -# -# $Id$ -# - -"""Provides classes to represent module version numbers (one class for -each style of version numbering). There are currently two such classes -implemented: StrictVersion and LooseVersion. - -Every version number class implements the following interface: - * the 'parse' method takes a string and parses it to some internal - representation; if the string is an invalid version number, - 'parse' raises a ValueError exception - * the class constructor takes an optional string argument which, - if supplied, is passed to 'parse' - * __str__ reconstructs the string that was passed to 'parse' (or - an equivalent string -- ie. one that will generate an equivalent - version number instance) - * __repr__ generates Python code to recreate the version number instance - * _cmp compares the current instance with either another instance - of the same class or a string (which will be parsed to an instance - of the same class, thus must follow the same rules) -""" - -import re - -class Version: - """Abstract base class for version numbering classes. Just provides - constructor (__init__) and reproducer (__repr__), because those - seem to be the same for all version numbering classes; and route - rich comparisons to _cmp. - """ - - def __init__ (self, vstring=None): - if vstring: - self.parse(vstring) - - def __repr__ (self): - return "%s ('%s')" % (self.__class__.__name__, str(self)) - - def __eq__(self, other): - c = self._cmp(other) - if c is NotImplemented: - return c - return c == 0 - - def __lt__(self, other): - c = self._cmp(other) - if c is NotImplemented: - return c - return c < 0 - - def __le__(self, other): - c = self._cmp(other) - if c is NotImplemented: - return c - return c <= 0 - - def __gt__(self, other): - c = self._cmp(other) - if c is NotImplemented: - return c - return c > 0 - - def __ge__(self, other): - c = self._cmp(other) - if c is NotImplemented: - return c - return c >= 0 - - -# Interface for version-number classes -- must be implemented -# by the following classes (the concrete ones -- Version should -# be treated as an abstract class). -# __init__ (string) - create and take same action as 'parse' -# (string parameter is optional) -# parse (string) - convert a string representation to whatever -# internal representation is appropriate for -# this style of version numbering -# __str__ (self) - convert back to a string; should be very similar -# (if not identical to) the string supplied to parse -# __repr__ (self) - generate Python code to recreate -# the instance -# _cmp (self, other) - compare two version numbers ('other' may -# be an unparsed version string, or another -# instance of your version class) - - -class StrictVersion (Version): - - """Version numbering for anal retentives and software idealists. - Implements the standard interface for version number classes as - described above. A version number consists of two or three - dot-separated numeric components, with an optional "pre-release" tag - on the end. The pre-release tag consists of the letter 'a' or 'b' - followed by a number. If the numeric components of two version - numbers are equal, then one with a pre-release tag will always - be deemed earlier (lesser) than one without. - - The following are valid version numbers (shown in the order that - would be obtained by sorting according to the supplied cmp function): - - 0.4 0.4.0 (these two are equivalent) - 0.4.1 - 0.5a1 - 0.5b3 - 0.5 - 0.9.6 - 1.0 - 1.0.4a3 - 1.0.4b1 - 1.0.4 - - The following are examples of invalid version numbers: - - 1 - 2.7.2.2 - 1.3.a4 - 1.3pl1 - 1.3c4 - - The rationale for this version numbering system will be explained - in the distutils documentation. - """ - - version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', - re.VERBOSE | re.ASCII) - - - def parse (self, vstring): - match = self.version_re.match(vstring) - if not match: - raise ValueError("invalid version number '%s'" % vstring) - - (major, minor, patch, prerelease, prerelease_num) = \ - match.group(1, 2, 4, 5, 6) - - if patch: - self.version = tuple(map(int, [major, minor, patch])) - else: - self.version = tuple(map(int, [major, minor])) + (0,) - - if prerelease: - self.prerelease = (prerelease[0], int(prerelease_num)) - else: - self.prerelease = None - - - def __str__ (self): - - if self.version[2] == 0: - vstring = '.'.join(map(str, self.version[0:2])) - else: - vstring = '.'.join(map(str, self.version)) - - if self.prerelease: - vstring = vstring + self.prerelease[0] + str(self.prerelease[1]) - - return vstring - - - def _cmp (self, other): - if isinstance(other, str): - other = StrictVersion(other) - - if self.version != other.version: - # numeric versions don't match - # prerelease stuff doesn't matter - if self.version < other.version: - return -1 - else: - return 1 - - # have to compare prerelease - # case 1: neither has prerelease; they're equal - # case 2: self has prerelease, other doesn't; other is greater - # case 3: self doesn't have prerelease, other does: self is greater - # case 4: both have prerelease: must compare them! - - if (not self.prerelease and not other.prerelease): - return 0 - elif (self.prerelease and not other.prerelease): - return -1 - elif (not self.prerelease and other.prerelease): - return 1 - elif (self.prerelease and other.prerelease): - if self.prerelease == other.prerelease: - return 0 - elif self.prerelease < other.prerelease: - return -1 - else: - return 1 - else: - assert False, "never get here" - -# end class StrictVersion - - -# The rules according to Greg Stein: -# 1) a version number has 1 or more numbers separated by a period or by -# sequences of letters. If only periods, then these are compared -# left-to-right to determine an ordering. -# 2) sequences of letters are part of the tuple for comparison and are -# compared lexicographically -# 3) recognize the numeric components may have leading zeroes -# -# The LooseVersion class below implements these rules: a version number -# string is split up into a tuple of integer and string components, and -# comparison is a simple tuple comparison. This means that version -# numbers behave in a predictable and obvious way, but a way that might -# not necessarily be how people *want* version numbers to behave. There -# wouldn't be a problem if people could stick to purely numeric version -# numbers: just split on period and compare the numbers as tuples. -# However, people insist on putting letters into their version numbers; -# the most common purpose seems to be: -# - indicating a "pre-release" version -# ('alpha', 'beta', 'a', 'b', 'pre', 'p') -# - indicating a post-release patch ('p', 'pl', 'patch') -# but of course this can't cover all version number schemes, and there's -# no way to know what a programmer means without asking him. -# -# The problem is what to do with letters (and other non-numeric -# characters) in a version number. The current implementation does the -# obvious and predictable thing: keep them as strings and compare -# lexically within a tuple comparison. This has the desired effect if -# an appended letter sequence implies something "post-release": -# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002". -# -# However, if letters in a version number imply a pre-release version, -# the "obvious" thing isn't correct. Eg. you would expect that -# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison -# implemented here, this just isn't so. -# -# Two possible solutions come to mind. The first is to tie the -# comparison algorithm to a particular set of semantic rules, as has -# been done in the StrictVersion class above. This works great as long -# as everyone can go along with bondage and discipline. Hopefully a -# (large) subset of Python module programmers will agree that the -# particular flavour of bondage and discipline provided by StrictVersion -# provides enough benefit to be worth using, and will submit their -# version numbering scheme to its domination. The free-thinking -# anarchists in the lot will never give in, though, and something needs -# to be done to accommodate them. -# -# Perhaps a "moderately strict" version class could be implemented that -# lets almost anything slide (syntactically), and makes some heuristic -# assumptions about non-digits in version number strings. This could -# sink into special-case-hell, though; if I was as talented and -# idiosyncratic as Larry Wall, I'd go ahead and implement a class that -# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is -# just as happy dealing with things like "2g6" and "1.13++". I don't -# think I'm smart enough to do it right though. -# -# In any case, I've coded the test suite for this module (see -# ../test/test_version.py) specifically to fail on things like comparing -# "1.2a2" and "1.2". That's not because the *code* is doing anything -# wrong, it's because the simple, obvious design doesn't match my -# complicated, hairy expectations for real-world version numbers. It -# would be a snap to fix the test suite to say, "Yep, LooseVersion does -# the Right Thing" (ie. the code matches the conception). But I'd rather -# have a conception that matches common notions about version numbers. - -class LooseVersion (Version): - - """Version numbering for anarchists and software realists. - Implements the standard interface for version number classes as - described above. A version number consists of a series of numbers, - separated by either periods or strings of letters. When comparing - version numbers, the numeric components will be compared - numerically, and the alphabetic components lexically. The following - are all valid version numbers, in no particular order: - - 1.5.1 - 1.5.2b2 - 161 - 3.10a - 8.02 - 3.4j - 1996.07.12 - 3.2.pl0 - 3.1.1.6 - 2g6 - 11g - 0.960923 - 2.2beta29 - 1.13++ - 5.5.kw - 2.0b1pl0 - - In fact, there is no such thing as an invalid version number under - this scheme; the rules for comparison are simple and predictable, - but may not always give the results you want (for some definition - of "want"). - """ - - component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) - - def __init__ (self, vstring=None): - if vstring: - self.parse(vstring) - - - def parse (self, vstring): - # I've given up on thinking I can reconstruct the version string - # from the parsed tuple -- so I just store the string here for - # use by __str__ - self.vstring = vstring - components = [x for x in self.component_re.split(vstring) - if x and x != '.'] - for i, obj in enumerate(components): - try: - components[i] = int(obj) - except ValueError: - pass - - self.version = components - - - def __str__ (self): - return self.vstring - - - def __repr__ (self): - return "LooseVersion ('%s')" % str(self) - - - def _cmp (self, other): - if isinstance(other, str): - other = LooseVersion(other) - - if self.version == other.version: - return 0 - if self.version < other.version: - return -1 - if self.version > other.version: - return 1 - - -# end class LooseVersion diff --git a/Lib/distutils/versionpredicate.py b/Lib/distutils/versionpredicate.py deleted file mode 100644 index 062c98f2489..00000000000 --- a/Lib/distutils/versionpredicate.py +++ /dev/null @@ -1,166 +0,0 @@ -"""Module for parsing and testing package version predicate strings. -""" -import re -import distutils.version -import operator - - -re_validPackage = re.compile(r"(?i)^\s*([a-z_]\w*(?:\.[a-z_]\w*)*)(.*)", - re.ASCII) -# (package) (rest) - -re_paren = re.compile(r"^\s*\((.*)\)\s*$") # (list) inside of parentheses -re_splitComparison = re.compile(r"^\s*(<=|>=|<|>|!=|==)\s*([^\s,]+)\s*$") -# (comp) (version) - - -def splitUp(pred): - """Parse a single version comparison. - - Return (comparison string, StrictVersion) - """ - res = re_splitComparison.match(pred) - if not res: - raise ValueError("bad package restriction syntax: %r" % pred) - comp, verStr = res.groups() - return (comp, distutils.version.StrictVersion(verStr)) - -compmap = {"<": operator.lt, "<=": operator.le, "==": operator.eq, - ">": operator.gt, ">=": operator.ge, "!=": operator.ne} - -class VersionPredicate: - """Parse and test package version predicates. - - >>> v = VersionPredicate('pyepat.abc (>1.0, <3333.3a1, !=1555.1b3)') - - The `name` attribute provides the full dotted name that is given:: - - >>> v.name - 'pyepat.abc' - - The str() of a `VersionPredicate` provides a normalized - human-readable version of the expression:: - - >>> print(v) - pyepat.abc (> 1.0, < 3333.3a1, != 1555.1b3) - - The `satisfied_by()` method can be used to determine with a given - version number is included in the set described by the version - restrictions:: - - >>> v.satisfied_by('1.1') - True - >>> v.satisfied_by('1.4') - True - >>> v.satisfied_by('1.0') - False - >>> v.satisfied_by('4444.4') - False - >>> v.satisfied_by('1555.1b3') - False - - `VersionPredicate` is flexible in accepting extra whitespace:: - - >>> v = VersionPredicate(' pat( == 0.1 ) ') - >>> v.name - 'pat' - >>> v.satisfied_by('0.1') - True - >>> v.satisfied_by('0.2') - False - - If any version numbers passed in do not conform to the - restrictions of `StrictVersion`, a `ValueError` is raised:: - - >>> v = VersionPredicate('p1.p2.p3.p4(>=1.0, <=1.3a1, !=1.2zb3)') - Traceback (most recent call last): - ... - ValueError: invalid version number '1.2zb3' - - It the module or package name given does not conform to what's - allowed as a legal module or package name, `ValueError` is - raised:: - - >>> v = VersionPredicate('foo-bar') - Traceback (most recent call last): - ... - ValueError: expected parenthesized list: '-bar' - - >>> v = VersionPredicate('foo bar (12.21)') - Traceback (most recent call last): - ... - ValueError: expected parenthesized list: 'bar (12.21)' - - """ - - def __init__(self, versionPredicateStr): - """Parse a version predicate string. - """ - # Fields: - # name: package name - # pred: list of (comparison string, StrictVersion) - - versionPredicateStr = versionPredicateStr.strip() - if not versionPredicateStr: - raise ValueError("empty package restriction") - match = re_validPackage.match(versionPredicateStr) - if not match: - raise ValueError("bad package name in %r" % versionPredicateStr) - self.name, paren = match.groups() - paren = paren.strip() - if paren: - match = re_paren.match(paren) - if not match: - raise ValueError("expected parenthesized list: %r" % paren) - str = match.groups()[0] - self.pred = [splitUp(aPred) for aPred in str.split(",")] - if not self.pred: - raise ValueError("empty parenthesized list in %r" - % versionPredicateStr) - else: - self.pred = [] - - def __str__(self): - if self.pred: - seq = [cond + " " + str(ver) for cond, ver in self.pred] - return self.name + " (" + ", ".join(seq) + ")" - else: - return self.name - - def satisfied_by(self, version): - """True if version is compatible with all the predicates in self. - The parameter version must be acceptable to the StrictVersion - constructor. It may be either a string or StrictVersion. - """ - for cond, ver in self.pred: - if not compmap[cond](version, ver): - return False - return True - - -_provision_rx = None - -def split_provision(value): - """Return the name and optional version number of a provision. - - The version number, if given, will be returned as a `StrictVersion` - instance, otherwise it will be `None`. - - >>> split_provision('mypkg') - ('mypkg', None) - >>> split_provision(' mypkg( 1.2 ) ') - ('mypkg', StrictVersion ('1.2')) - """ - global _provision_rx - if _provision_rx is None: - _provision_rx = re.compile( - r"([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)(?:\s*\(\s*([^)\s]+)\s*\))?$", - re.ASCII) - value = value.strip() - m = _provision_rx.match(value) - if not m: - raise ValueError("illegal provides specification: %r" % value) - ver = m.group(2) or None - if ver: - ver = distutils.version.StrictVersion(ver) - return m.group(1), ver diff --git a/Lib/doctest.py b/Lib/doctest.py index 387f71b184a..ecac54ad5a5 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -94,6 +94,7 @@ def _test(): import __future__ import difflib +import functools import inspect import linecache import os @@ -104,8 +105,26 @@ def _test(): import unittest from io import StringIO, IncrementalNewlineDecoder from collections import namedtuple +import _colorize # Used in doctests +from _colorize import ANSIColors, can_colorize + + +class TestResults(namedtuple('TestResults', 'failed attempted')): + def __new__(cls, failed, attempted, *, skipped=0): + results = super().__new__(cls, failed, attempted) + results.skipped = skipped + return results + + def __repr__(self): + if self.skipped: + return (f'TestResults(failed={self.failed}, ' + f'attempted={self.attempted}, ' + f'skipped={self.skipped})') + else: + # Leave the repr() unchanged for backward compatibility + # if skipped is zero + return super().__repr__() -TestResults = namedtuple('TestResults', 'failed attempted') # There are 4 basic classes: # - Example: a <source, want> pair, plus an intra-docstring line number. @@ -207,7 +226,13 @@ def _normalize_module(module, depth=2): elif isinstance(module, str): return __import__(module, globals(), locals(), ["*"]) elif module is None: - return sys.modules[sys._getframe(depth).f_globals['__name__']] + try: + try: + return sys.modules[sys._getframemodulename(depth)] + except AttributeError: + return sys.modules[sys._getframe(depth).f_globals['__name__']] + except KeyError: + pass else: raise TypeError("Expected a module, string, or None") @@ -229,7 +254,6 @@ def _load_testfile(filename, package, module_relative, encoding): file_contents = file_contents.decode(encoding) # get_data() opens files as 'rb', so one must do the equivalent # conversion as universal newlines would do. - return _newline_convert(file_contents), filename with open(filename, encoding=encoding) as f: return f.read(), filename @@ -570,9 +594,11 @@ def __hash__(self): def __lt__(self, other): if not isinstance(other, DocTest): return NotImplemented - return ((self.name, self.filename, self.lineno, id(self)) + self_lno = self.lineno if self.lineno is not None else -1 + other_lno = other.lineno if other.lineno is not None else -1 + return ((self.name, self.filename, self_lno, id(self)) < - (other.name, other.filename, other.lineno, id(other))) + (other.name, other.filename, other_lno, id(other))) ###################################################################### ## 3. DocTestParser @@ -957,7 +983,8 @@ def _from_module(self, module, object): return module is inspect.getmodule(object) elif inspect.isfunction(object): return module.__dict__ is object.__globals__ - elif inspect.ismethoddescriptor(object): + elif (inspect.ismethoddescriptor(object) or + inspect.ismethodwrapper(object)): if hasattr(object, '__objclass__'): obj_mod = object.__objclass__.__module__ elif hasattr(object, '__module__'): @@ -1104,7 +1131,7 @@ def _find_lineno(self, obj, source_lines): if source_lines is None: return None pat = re.compile(r'^\s*class\s*%s\b' % - getattr(obj, '__name__', '-')) + re.escape(getattr(obj, '__name__', '-'))) for i, line in enumerate(source_lines): if pat.match(line): lineno = i @@ -1112,13 +1139,24 @@ def _find_lineno(self, obj, source_lines): # Find the line number for functions & methods. if inspect.ismethod(obj): obj = obj.__func__ - if inspect.isfunction(obj) and getattr(obj, '__doc__', None): + if isinstance(obj, property): + obj = obj.fget + if isinstance(obj, functools.cached_property): + obj = obj.func + if inspect.isroutine(obj) and getattr(obj, '__doc__', None): # We don't use `docstring` var here, because `obj` can be changed. - obj = obj.__code__ + obj = inspect.unwrap(obj) + try: + obj = obj.__code__ + except AttributeError: + # Functions implemented in C don't necessarily + # have a __code__ attribute. + # If there's no code, there's no lineno + return None if inspect.istraceback(obj): obj = obj.tb_frame if inspect.isframe(obj): obj = obj.f_code if inspect.iscode(obj): - lineno = getattr(obj, 'co_firstlineno', None)-1 + lineno = obj.co_firstlineno - 1 # Find the line number where the docstring starts. Assume # that it's the first line that begins with a quote mark. @@ -1144,8 +1182,10 @@ class DocTestRunner: """ A class used to run DocTest test cases, and accumulate statistics. The `run` method is used to process a single DocTest case. It - returns a tuple `(f, t)`, where `t` is the number of test cases - tried, and `f` is the number of test cases that failed. + returns a TestResults instance. + + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False >>> tests = DocTestFinder().find(_TestClass) >>> runner = DocTestRunner(verbose=False) @@ -1158,27 +1198,29 @@ class DocTestRunner: _TestClass.square -> TestResults(failed=0, attempted=1) The `summarize` method prints a summary of all the test cases that - have been run by the runner, and returns an aggregated `(f, t)` - tuple: + have been run by the runner, and returns an aggregated TestResults + instance: >>> runner.summarize(verbose=1) 4 items passed all tests: 2 tests in _TestClass 2 tests in _TestClass.__init__ 2 tests in _TestClass.get - 1 tests in _TestClass.square + 1 test in _TestClass.square 7 tests in 4 items. - 7 passed and 0 failed. + 7 passed. Test passed. TestResults(failed=0, attempted=7) - The aggregated number of tried examples and failed examples is - also available via the `tries` and `failures` attributes: + The aggregated number of tried examples and failed examples is also + available via the `tries`, `failures` and `skips` attributes: >>> runner.tries 7 >>> runner.failures 0 + >>> runner.skips + 0 The comparison between expected outputs and actual outputs is done by an `OutputChecker`. This comparison may be customized with a @@ -1195,6 +1237,8 @@ class DocTestRunner: can be also customized by subclassing DocTestRunner, and overriding the methods `report_start`, `report_success`, `report_unexpected_exception`, and `report_failure`. + + >>> _colorize.COLORIZE = save_colorize """ # This divider string is used to separate failure messages, and to # separate sections of the summary. @@ -1227,7 +1271,8 @@ def __init__(self, checker=None, verbose=None, optionflags=0): # Keep track of the examples we've run. self.tries = 0 self.failures = 0 - self._name2ft = {} + self.skips = 0 + self._stats = {} # Create a fake output target for capturing doctest output. self._fakeout = _SpoofOut() @@ -1272,7 +1317,10 @@ def report_unexpected_exception(self, out, test, example, exc_info): 'Exception raised:\n' + _indent(_exception_traceback(exc_info))) def _failure_header(self, test, example): - out = [self.DIVIDER] + red, reset = ( + (ANSIColors.RED, ANSIColors.RESET) if can_colorize() else ("", "") + ) + out = [f"{red}{self.DIVIDER}{reset}"] if test.filename: if test.lineno is not None and example.lineno is not None: lineno = test.lineno + example.lineno + 1 @@ -1296,13 +1344,11 @@ def __run(self, test, compileflags, out): Run the examples in `test`. Write the outcome of each example with one of the `DocTestRunner.report_*` methods, using the writer function `out`. `compileflags` is the set of compiler - flags that should be used to execute examples. Return a tuple - `(f, t)`, where `t` is the number of examples tried, and `f` - is the number of examples that failed. The examples are run - in the namespace `test.globs`. + flags that should be used to execute examples. Return a TestResults + instance. The examples are run in the namespace `test.globs`. """ - # Keep track of the number of failures and tries. - failures = tries = 0 + # Keep track of the number of failed, attempted, skipped examples. + failures = attempted = skips = 0 # Save the option flags (since option directives can be used # to modify them). @@ -1314,6 +1360,7 @@ def __run(self, test, compileflags, out): # Process each example. for examplenum, example in enumerate(test.examples): + attempted += 1 # If REPORT_ONLY_FIRST_FAILURE is set, then suppress # reporting after the first failure. @@ -1331,10 +1378,10 @@ def __run(self, test, compileflags, out): # If 'SKIP' is set, then skip this example. if self.optionflags & SKIP: + skips += 1 continue # Record that we started this example. - tries += 1 if not quiet: self.report_start(out, test, example) @@ -1370,7 +1417,24 @@ def __run(self, test, compileflags, out): # The example raised an exception: check if it was expected. else: - exc_msg = traceback.format_exception_only(*exception[:2])[-1] + formatted_ex = traceback.format_exception_only(*exception[:2]) + if issubclass(exception[0], SyntaxError): + # SyntaxError / IndentationError is special: + # we don't care about the carets / suggestions / etc + # We only care about the error message and notes. + # They start with `SyntaxError:` (or any other class name) + exception_line_prefixes = ( + f"{exception[0].__qualname__}:", + f"{exception[0].__module__}.{exception[0].__qualname__}:", + ) + exc_msg_index = next( + index + for index, line in enumerate(formatted_ex) + if line.startswith(exception_line_prefixes) + ) + formatted_ex = formatted_ex[exc_msg_index:] + + exc_msg = "".join(formatted_ex) if not quiet: got += _exception_traceback(exception) @@ -1412,19 +1476,22 @@ def __run(self, test, compileflags, out): # Restore the option flags (in case they were modified) self.optionflags = original_optionflags - # Record and return the number of failures and tries. - self.__record_outcome(test, failures, tries) - return TestResults(failures, tries) + # Record and return the number of failures and attempted. + self.__record_outcome(test, failures, attempted, skips) + return TestResults(failures, attempted, skipped=skips) - def __record_outcome(self, test, f, t): + def __record_outcome(self, test, failures, tries, skips): """ - Record the fact that the given DocTest (`test`) generated `f` - failures out of `t` tried examples. + Record the fact that the given DocTest (`test`) generated `failures` + failures out of `tries` tried examples. """ - f2, t2 = self._name2ft.get(test.name, (0,0)) - self._name2ft[test.name] = (f+f2, t+t2) - self.failures += f - self.tries += t + failures2, tries2, skips2 = self._stats.get(test.name, (0, 0, 0)) + self._stats[test.name] = (failures + failures2, + tries + tries2, + skips + skips2) + self.failures += failures + self.tries += tries + self.skips += skips __LINECACHE_FILENAME_RE = re.compile(r'<doctest ' r'(?P<name>.+)' @@ -1493,7 +1560,11 @@ def out(s): # Make sure sys.displayhook just prints the value to stdout save_displayhook = sys.displayhook sys.displayhook = sys.__displayhook__ - + saved_can_colorize = _colorize.can_colorize + _colorize.can_colorize = lambda *args, **kwargs: False + color_variables = {"PYTHON_COLORS": None, "FORCE_COLOR": None} + for key in color_variables: + color_variables[key] = os.environ.pop(key, None) try: return self.__run(test, compileflags, out) finally: @@ -1502,6 +1573,10 @@ def out(s): sys.settrace(save_trace) linecache.getlines = self.save_linecache_getlines sys.displayhook = save_displayhook + _colorize.can_colorize = saved_can_colorize + for key, value in color_variables.items(): + if value is not None: + os.environ[key] = value if clear_globs: test.globs.clear() import builtins @@ -1513,9 +1588,7 @@ def out(s): def summarize(self, verbose=None): """ Print a summary of all the test cases that have been run by - this DocTestRunner, and return a tuple `(f, t)`, where `f` is - the total number of failed examples, and `t` is the total - number of tried examples. + this DocTestRunner, and return a TestResults instance. The optional `verbose` argument controls how detailed the summary is. If the verbosity is not specified, then the @@ -1523,66 +1596,98 @@ def summarize(self, verbose=None): """ if verbose is None: verbose = self._verbose - notests = [] - passed = [] - failed = [] - totalt = totalf = 0 - for x in self._name2ft.items(): - name, (f, t) = x - assert f <= t - totalt += t - totalf += f - if t == 0: + + notests, passed, failed = [], [], [] + total_tries = total_failures = total_skips = 0 + + for name, (failures, tries, skips) in self._stats.items(): + assert failures <= tries + total_tries += tries + total_failures += failures + total_skips += skips + + if tries == 0: notests.append(name) - elif f == 0: - passed.append( (name, t) ) + elif failures == 0: + passed.append((name, tries)) else: - failed.append(x) + failed.append((name, (failures, tries, skips))) + + ansi = _colorize.get_colors() + bold_green = ansi.BOLD_GREEN + bold_red = ansi.BOLD_RED + green = ansi.GREEN + red = ansi.RED + reset = ansi.RESET + yellow = ansi.YELLOW + if verbose: if notests: - print(len(notests), "items had no tests:") + print(f"{_n_items(notests)} had no tests:") notests.sort() - for thing in notests: - print(" ", thing) + for name in notests: + print(f" {name}") + if passed: - print(len(passed), "items passed all tests:") - passed.sort() - for thing, count in passed: - print(" %3d tests in %s" % (count, thing)) + print(f"{green}{_n_items(passed)} passed all tests:{reset}") + for name, count in sorted(passed): + s = "" if count == 1 else "s" + print(f" {green}{count:3d} test{s} in {name}{reset}") + if failed: - print(self.DIVIDER) - print(len(failed), "items had failures:") - failed.sort() - for thing, (f, t) in failed: - print(" %3d of %3d in %s" % (f, t, thing)) + print(f"{red}{self.DIVIDER}{reset}") + print(f"{_n_items(failed)} had failures:") + for name, (failures, tries, skips) in sorted(failed): + print(f" {failures:3d} of {tries:3d} in {name}") + if verbose: - print(totalt, "tests in", len(self._name2ft), "items.") - print(totalt - totalf, "passed and", totalf, "failed.") - if totalf: - print("***Test Failed***", totalf, "failures.") + s = "" if total_tries == 1 else "s" + print(f"{total_tries} test{s} in {_n_items(self._stats)}.") + + and_f = ( + f" and {red}{total_failures} failed{reset}" + if total_failures else "" + ) + print(f"{green}{total_tries - total_failures} passed{reset}{and_f}.") + + if total_failures: + s = "" if total_failures == 1 else "s" + msg = f"{bold_red}***Test Failed*** {total_failures} failure{s}{reset}" + if total_skips: + s = "" if total_skips == 1 else "s" + msg = f"{msg} and {yellow}{total_skips} skipped test{s}{reset}" + print(f"{msg}.") elif verbose: - print("Test passed.") - return TestResults(totalf, totalt) + print(f"{bold_green}Test passed.{reset}") + + return TestResults(total_failures, total_tries, skipped=total_skips) #///////////////////////////////////////////////////////////////// # Backward compatibility cruft to maintain doctest.master. #///////////////////////////////////////////////////////////////// def merge(self, other): - d = self._name2ft - for name, (f, t) in other._name2ft.items(): + d = self._stats + for name, (failures, tries, skips) in other._stats.items(): if name in d: - # Don't print here by default, since doing - # so breaks some of the buildbots - #print("*** DocTestRunner.merge: '" + name + "' in both" \ - # " testers; summing outcomes.") - f2, t2 = d[name] - f = f + f2 - t = t + t2 - d[name] = f, t + failures2, tries2, skips2 = d[name] + failures = failures + failures2 + tries = tries + tries2 + skips = skips + skips2 + d[name] = (failures, tries, skips) + + +def _n_items(items: list | dict) -> str: + """ + Helper to pluralise the number of items in a list. + """ + n = len(items) + s = "" if n == 1 else "s" + return f"{n} item{s}" + class OutputChecker: """ - A class used to check the whether the actual output from a doctest + A class used to check whether the actual output from a doctest example matches the expected output. `OutputChecker` defines two methods: `check_output`, which compares a given pair of outputs, and returns true if they match; and `output_difference`, which @@ -1887,8 +1992,8 @@ def testmod(m=None, name=None, globs=None, verbose=None, from module m (or the current module if m is not supplied), starting with m.__doc__. - Also test examples reachable from dict m.__test__ if it exists and is - not None. m.__test__ maps names to functions, classes and strings; + Also test examples reachable from dict m.__test__ if it exists. + m.__test__ maps names to functions, classes and strings; function and class docstrings are tested even if the name is private; strings are tested directly, as if they were docstrings. @@ -1978,7 +2083,8 @@ class doctest.Tester, then merges the results into (or creates) else: master.merge(runner) - return TestResults(runner.failures, runner.tries) + return TestResults(runner.failures, runner.tries, skipped=runner.skips) + def testfile(filename, module_relative=True, name=None, package=None, globs=None, verbose=None, report=True, optionflags=0, @@ -2101,7 +2207,8 @@ class doctest.Tester, then merges the results into (or creates) else: master.merge(runner) - return TestResults(runner.failures, runner.tries) + return TestResults(runner.failures, runner.tries, skipped=runner.skips) + def run_docstring_examples(f, globs, verbose=False, name="NoName", compileflags=None, optionflags=0): @@ -2176,13 +2283,13 @@ def __init__(self, test, optionflags=0, setUp=None, tearDown=None, unittest.TestCase.__init__(self) self._dt_optionflags = optionflags self._dt_checker = checker - self._dt_globs = test.globs.copy() self._dt_test = test self._dt_setUp = setUp self._dt_tearDown = tearDown def setUp(self): test = self._dt_test + self._dt_globs = test.globs.copy() if self._dt_setUp is not None: self._dt_setUp(test) @@ -2213,12 +2320,13 @@ def runTest(self): try: runner.DIVIDER = "-"*70 - failures, tries = runner.run( - test, out=new.write, clear_globs=False) + results = runner.run(test, out=new.write, clear_globs=False) + if results.skipped == results.attempted: + raise unittest.SkipTest("all examples were skipped") finally: sys.stdout = old - if failures: + if results.failed: raise self.failureException(self.format_failure(new.getvalue())) def format_failure(self, err): diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 2636c1fbb98..1bb74c6d969 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -1,3 +1,6 @@ +# TODO: RustPython +# Skipping some tests because time module has no attribute tzset +# implementation detail specific to cpython """Test date/time type. See https://www.zope.dev/Members/fdrake/DateTimeWiki/TestCases @@ -13,6 +16,7 @@ import re import struct import sys +import textwrap import unittest import warnings @@ -22,6 +26,7 @@ from test import support from test.support import is_resource_enabled, ALWAYS_EQ, LARGEST, SMALLEST +from test.support import script_helper, warnings_helper import datetime as datetime_module from datetime import MINYEAR, MAXYEAR @@ -37,6 +42,10 @@ import _testcapi except ImportError: _testcapi = None +try: + import _interpreters +except ModuleNotFoundError: + _interpreters = None # Needed by test_datetime import _strptime @@ -301,6 +310,10 @@ def test_inheritance(self): self.assertIsInstance(timezone.utc, tzinfo) self.assertIsInstance(self.EST, tzinfo) + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class MyTimezone(timezone): pass + def test_utcoffset(self): dummy = self.DT for h in [0, 1.5, 12]: @@ -752,6 +765,9 @@ def test_str(self): microseconds=999999)), "999999999 days, 23:59:59.999999") + # test the Doc/library/datetime.rst recipe + eq(f'-({-td(hours=-1)!s})', "-(1:00:00)") + def test_repr(self): name = 'datetime.' + self.theclass.__name__ self.assertEqual(repr(self.theclass(1)), @@ -1331,6 +1347,11 @@ def test_insane_fromtimestamp(self): self.assertRaises(OverflowError, self.theclass.fromtimestamp, insane) + def test_fromtimestamp_with_none_arg(self): + # See gh-120268 for more details + with self.assertRaises(TypeError): + self.theclass.fromtimestamp(None) + def test_today(self): import time @@ -1498,8 +1519,7 @@ def test_strftime(self): # bpo-41260: The parameter was named "fmt" in the pure python impl. t.strftime(format="%f") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_strftime_trailing_percent(self): # bpo-35066: Make sure trailing '%' doesn't cause datetime's strftime to # complain. Different libcs have different handling of trailing @@ -1600,8 +1620,7 @@ def test_pickling(self): self.assertEqual(orig, derived) self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_compat_unpickle(self): tests = [ b"cdatetime\ndate\n(S'\\x07\\xdf\\x0b\\x1b'\ntR.", @@ -1703,29 +1722,44 @@ def test_replace(self): cls = self.theclass args = [1, 2, 3] base = cls(*args) - self.assertEqual(base, base.replace()) + self.assertEqual(base.replace(), base) + self.assertEqual(copy.replace(base), base) - i = 0 - for name, newval in (("year", 2), - ("month", 3), - ("day", 4)): + changes = (("year", 2), + ("month", 3), + ("day", 4)) + for i, (name, newval) in enumerate(changes): newargs = args[:] newargs[i] = newval expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 + self.assertEqual(base.replace(**{name: newval}), expected) + self.assertEqual(copy.replace(base, **{name: newval}), expected) # Out of bounds. base = cls(2000, 2, 29) self.assertRaises(ValueError, base.replace, year=2001) + self.assertRaises(ValueError, copy.replace, base, year=2001) def test_subclass_replace(self): class DateSubclass(self.theclass): - pass + def __new__(cls, *args, **kwargs): + result = self.theclass.__new__(cls, *args, **kwargs) + result.extra = 7 + return result dt = DateSubclass(2012, 1, 1) - self.assertIs(type(dt.replace(year=2013)), DateSubclass) + + test_cases = [ + ('self.replace', dt.replace(year=2013)), + ('copy.replace', copy.replace(dt, year=2013)), + ] + + for name, res in test_cases: + with self.subTest(name): + self.assertIs(type(res), DateSubclass) + self.assertEqual(res.year, 2013) + self.assertEqual(res.month, 1) + self.assertEqual(res.extra, 7) def test_subclass_date(self): @@ -1912,6 +1946,10 @@ def test_fromisoformat_fails(self): '2009-02-29', # Invalid leap day '2019-W53-1', # No week 53 in 2019 '2020-W54-1', # No week 54 + '0000-W25-1', # Invalid year + '10000-W25-1', # Invalid year + '2020-W25-0', # Invalid day-of-week + '2020-W25-8', # Invalid day-of-week '2009\ud80002\ud80028', # Separators are surrogate codepoints ] @@ -2369,8 +2407,7 @@ def test_pickling_subclass_datetime(self): self.assertEqual(orig, derived) self.assertTrue(isinstance(derived, SubclassDatetime)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_compat_unpickle(self): tests = [ b'cdatetime\ndatetime\n(' @@ -2780,6 +2817,20 @@ def test_strptime_single_digit(self): newdate = strptime(string, format) self.assertEqual(newdate, target, msg=reason) + @warnings_helper.ignore_warnings(category=DeprecationWarning) + def test_strptime_leap_year(self): + # GH-70647: warns if parsing a format with a day and no year. + with self.assertRaises(ValueError): + # The existing behavior that GH-70647 seeks to change. + self.theclass.strptime('02-29', '%m-%d') + with self.assertWarnsRegex(DeprecationWarning, + r'.*day of month without a year.*'): + self.theclass.strptime('03-14.159265', '%m-%d.%f') + with self._assertNotWarns(DeprecationWarning): + self.theclass.strptime('20-03-14.159265', '%y-%m-%d.%f') + with self._assertNotWarns(DeprecationWarning): + self.theclass.strptime('02-29,2024', '%m-%d,%Y') + def test_more_timetuple(self): # This tests fields beyond those tested by the TestDate.test_timetuple. t = self.theclass(2004, 12, 31, 6, 22, 33) @@ -2813,11 +2864,34 @@ def test_more_strftime(self): self.assertEqual(t.strftime("%z"), "-0200" + z) self.assertEqual(t.strftime("%:z"), "-02:00:" + z) - # bpo-34482: Check that surrogates don't cause a crash. - try: - t.strftime('%y\ud800%m %H\ud800%M') - except UnicodeEncodeError: - pass + @unittest.skip('TODO: RUSTPYTHON') + # UnicodeEncodeError: 'utf-8' codec can't encode character '\ud83d' in position 0: surrogates not allowed + def test_strftime_special(self): + t = self.theclass(2004, 12, 31, 6, 22, 33, 47) + s1 = t.strftime('%c') + s2 = t.strftime('%B') + # gh-52551, gh-78662: Unicode strings should pass through strftime, + # independently from locale. + self.assertEqual(t.strftime('\U0001f40d'), '\U0001f40d') + self.assertEqual(t.strftime('\U0001f4bb%c\U0001f40d%B'), f'\U0001f4bb{s1}\U0001f40d{s2}') + self.assertEqual(t.strftime('%c\U0001f4bb%B\U0001f40d'), f'{s1}\U0001f4bb{s2}\U0001f40d') + # Lone surrogates should pass through. + self.assertEqual(t.strftime('\ud83d'), '\ud83d') + self.assertEqual(t.strftime('\udc0d'), '\udc0d') + self.assertEqual(t.strftime('\ud83d%c\udc0d%B'), f'\ud83d{s1}\udc0d{s2}') + self.assertEqual(t.strftime('%c\ud83d%B\udc0d'), f'{s1}\ud83d{s2}\udc0d') + self.assertEqual(t.strftime('%c\udc0d%B\ud83d'), f'{s1}\udc0d{s2}\ud83d') + # Surrogate pairs should not recombine. + self.assertEqual(t.strftime('\ud83d\udc0d'), '\ud83d\udc0d') + self.assertEqual(t.strftime('%c\ud83d\udc0d%B'), f'{s1}\ud83d\udc0d{s2}') + # Surrogate-escaped bytes should not recombine. + self.assertEqual(t.strftime('\udcf0\udc9f\udc90\udc8d'), '\udcf0\udc9f\udc90\udc8d') + self.assertEqual(t.strftime('%c\udcf0\udc9f\udc90\udc8d%B'), f'{s1}\udcf0\udc9f\udc90\udc8d{s2}') + # gh-124531: The null character should not terminate the format string. + self.assertEqual(t.strftime('\0'), '\0') + self.assertEqual(t.strftime('\0'*1000), '\0'*1000) + self.assertEqual(t.strftime('\0%c\0%B'), f'\0{s1}\0{s2}') + self.assertEqual(t.strftime('%c\0%B\0'), f'{s1}\0{s2}\0') def test_extract(self): dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234) @@ -2862,26 +2936,27 @@ def test_replace(self): cls = self.theclass args = [1, 2, 3, 4, 5, 6, 7] base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in (("year", 2), - ("month", 3), - ("day", 4), - ("hour", 5), - ("minute", 6), - ("second", 7), - ("microsecond", 8)): + self.assertEqual(base.replace(), base) + self.assertEqual(copy.replace(base), base) + + changes = (("year", 2), + ("month", 3), + ("day", 4), + ("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8)) + for i, (name, newval) in enumerate(changes): newargs = args[:] newargs[i] = newval expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 + self.assertEqual(base.replace(**{name: newval}), expected) + self.assertEqual(copy.replace(base, **{name: newval}), expected) # Out of bounds. base = cls(2000, 2, 29) self.assertRaises(ValueError, base.replace, year=2001) + self.assertRaises(ValueError, copy.replace, base, year=2001) @support.run_with_tz('EDT4') def test_astimezone(self): @@ -3024,6 +3099,26 @@ def __new__(cls, *args, **kwargs): self.assertIsInstance(dt, DateTimeSubclass) self.assertEqual(dt.extra, 7) + def test_subclass_replace_fold(self): + class DateTimeSubclass(self.theclass): + pass + + dt = DateTimeSubclass(2012, 1, 1) + dt2 = DateTimeSubclass(2012, 1, 1, fold=1) + + test_cases = [ + ('self.replace', dt.replace(year=2013), 0), + ('self.replace', dt2.replace(year=2013), 1), + ('copy.replace', copy.replace(dt, year=2013), 0), + ('copy.replace', copy.replace(dt2, year=2013), 1), + ] + + for name, res, fold in test_cases: + with self.subTest(name, fold=fold): + self.assertIs(type(res), DateTimeSubclass) + self.assertEqual(res.year, 2013) + self.assertEqual(res.fold, fold) + def test_fromisoformat_datetime(self): # Test that isoformat() is reversible base_dates = [ @@ -3301,6 +3396,9 @@ def test_fromisoformat_fails_datetime(self): '2009-04-19T12:30:45.123456-05:00a', # Extra text '2009-04-19T12:30:45.123-05:00a', # Extra text '2009-04-19T12:30:45-05:00a', # Extra text + '2009-04-19T12:30:45.400 +02:30', # Space between ms and timezone (gh-130959) + '2009-04-19T12:30:45.400 ', # Trailing space (gh-130959) + '2009-04-19T12:30:45. 400', # Space before fraction (gh-130959) ] for bad_str in bad_strs: @@ -3570,6 +3668,35 @@ def test_strftime(self): # gh-85432: The parameter was named "fmt" in the pure-Python impl. t.strftime(format="%f") + @unittest.skip('TODO: RUSTPYTHON') + # UnicodeEncodeError: 'utf-8' codec can't encode character '\ud83d' in position 0: surrogates not allowed + def test_strftime_special(self): + t = self.theclass(1, 2, 3, 4) + s1 = t.strftime('%I%p%Z') + s2 = t.strftime('%X') + # gh-52551, gh-78662: Unicode strings should pass through strftime, + # independently from locale. + self.assertEqual(t.strftime('\U0001f40d'), '\U0001f40d') + self.assertEqual(t.strftime('\U0001f4bb%I%p%Z\U0001f40d%X'), f'\U0001f4bb{s1}\U0001f40d{s2}') + self.assertEqual(t.strftime('%I%p%Z\U0001f4bb%X\U0001f40d'), f'{s1}\U0001f4bb{s2}\U0001f40d') + # Lone surrogates should pass through. + self.assertEqual(t.strftime('\ud83d'), '\ud83d') + self.assertEqual(t.strftime('\udc0d'), '\udc0d') + self.assertEqual(t.strftime('\ud83d%I%p%Z\udc0d%X'), f'\ud83d{s1}\udc0d{s2}') + self.assertEqual(t.strftime('%I%p%Z\ud83d%X\udc0d'), f'{s1}\ud83d{s2}\udc0d') + self.assertEqual(t.strftime('%I%p%Z\udc0d%X\ud83d'), f'{s1}\udc0d{s2}\ud83d') + # Surrogate pairs should not recombine. + self.assertEqual(t.strftime('\ud83d\udc0d'), '\ud83d\udc0d') + self.assertEqual(t.strftime('%I%p%Z\ud83d\udc0d%X'), f'{s1}\ud83d\udc0d{s2}') + # Surrogate-escaped bytes should not recombine. + self.assertEqual(t.strftime('\udcf0\udc9f\udc90\udc8d'), '\udcf0\udc9f\udc90\udc8d') + self.assertEqual(t.strftime('%I%p%Z\udcf0\udc9f\udc90\udc8d%X'), f'{s1}\udcf0\udc9f\udc90\udc8d{s2}') + # gh-124531: The null character should not terminate the format string. + self.assertEqual(t.strftime('\0'), '\0') + self.assertEqual(t.strftime('\0'*1000), '\0'*1000) + self.assertEqual(t.strftime('\0%I%p%Z\0%X'), f'\0{s1}\0{s2}') + self.assertEqual(t.strftime('%I%p%Z\0%X\0'), f'{s1}\0{s2}\0') + def test_format(self): t = self.theclass(1, 2, 3, 4) self.assertEqual(t.__format__(''), str(t)) @@ -3641,8 +3768,7 @@ def test_pickling_subclass_time(self): self.assertEqual(orig, derived) self.assertTrue(isinstance(derived, SubclassTime)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_compat_unpickle(self): tests = [ (b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.", @@ -3679,19 +3805,19 @@ def test_replace(self): cls = self.theclass args = [1, 2, 3, 4] base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in (("hour", 5), - ("minute", 6), - ("second", 7), - ("microsecond", 8)): + self.assertEqual(base.replace(), base) + self.assertEqual(copy.replace(base), base) + + changes = (("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8)) + for i, (name, newval) in enumerate(changes): newargs = args[:] newargs[i] = newval expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 + self.assertEqual(base.replace(**{name: newval}), expected) + self.assertEqual(copy.replace(base, **{name: newval}), expected) # Out of bounds. base = cls(1) @@ -3699,13 +3825,35 @@ def test_replace(self): self.assertRaises(ValueError, base.replace, minute=-1) self.assertRaises(ValueError, base.replace, second=100) self.assertRaises(ValueError, base.replace, microsecond=1000000) + self.assertRaises(ValueError, copy.replace, base, hour=24) + self.assertRaises(ValueError, copy.replace, base, minute=-1) + self.assertRaises(ValueError, copy.replace, base, second=100) + self.assertRaises(ValueError, copy.replace, base, microsecond=1000000) def test_subclass_replace(self): class TimeSubclass(self.theclass): - pass + def __new__(cls, *args, **kwargs): + result = self.theclass.__new__(cls, *args, **kwargs) + result.extra = 7 + return result ctime = TimeSubclass(12, 30) - self.assertIs(type(ctime.replace(hour=10)), TimeSubclass) + ctime2 = TimeSubclass(12, 30, fold=1) + + test_cases = [ + ('self.replace', ctime.replace(hour=10), 0), + ('self.replace', ctime2.replace(hour=10), 1), + ('copy.replace', copy.replace(ctime, hour=10), 0), + ('copy.replace', copy.replace(ctime2, hour=10), 1), + ] + + for name, res, fold in test_cases: + with self.subTest(name, fold=fold): + self.assertIs(type(res), TimeSubclass) + self.assertEqual(res.hour, 10) + self.assertEqual(res.minute, 30) + self.assertEqual(res.extra, 7) + self.assertEqual(res.fold, fold) def test_subclass_time(self): @@ -3918,6 +4066,8 @@ def test_empty(self): self.assertEqual(t.microsecond, 0) self.assertIsNone(t.tzinfo) + @unittest.skip('TODO: RUSTPYTHON') + # UnicodeEncodeError: 'utf-8' codec can't encode character '\ud800' in position 0: surrogates not allowed def test_zones(self): est = FixedOffset(-300, "EST", 1) utc = FixedOffset(0, "UTC", -2) @@ -4001,9 +4151,8 @@ def tzname(self, dt): return self.tz self.assertRaises(TypeError, t.strftime, "%Z") # Issue #6697: - if '_Fast' in self.__class__.__name__: - Badtzname.tz = '\ud800' - self.assertRaises(ValueError, t.strftime, "%Z") + Badtzname.tz = '\ud800' + self.assertEqual(t.strftime("%Z"), '\ud800') def test_hash_edge_cases(self): # Offsets that overflow a basic time. @@ -4037,8 +4186,7 @@ def test_pickling(self): self.assertEqual(derived.tzname(), 'cookie') self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_compat_unpickle(self): tests = [ b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n" @@ -4095,31 +4243,37 @@ def test_replace(self): zm200 = FixedOffset(timedelta(minutes=-200), "-200") args = [1, 2, 3, 4, z100] base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in (("hour", 5), - ("minute", 6), - ("second", 7), - ("microsecond", 8), - ("tzinfo", zm200)): + self.assertEqual(base.replace(), base) + self.assertEqual(copy.replace(base), base) + + changes = (("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8), + ("tzinfo", zm200)) + for i, (name, newval) in enumerate(changes): newargs = args[:] newargs[i] = newval expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 + self.assertEqual(base.replace(**{name: newval}), expected) + self.assertEqual(copy.replace(base, **{name: newval}), expected) # Ensure we can get rid of a tzinfo. self.assertEqual(base.tzname(), "+100") base2 = base.replace(tzinfo=None) self.assertIsNone(base2.tzinfo) self.assertIsNone(base2.tzname()) + base22 = copy.replace(base, tzinfo=None) + self.assertIsNone(base22.tzinfo) + self.assertIsNone(base22.tzname()) # Ensure we can add one. base3 = base2.replace(tzinfo=z100) self.assertEqual(base, base3) self.assertIs(base.tzinfo, base3.tzinfo) + base32 = copy.replace(base22, tzinfo=z100) + self.assertEqual(base, base32) + self.assertIs(base.tzinfo, base32.tzinfo) # Out of bounds. base = cls(1) @@ -4127,6 +4281,10 @@ def test_replace(self): self.assertRaises(ValueError, base.replace, minute=-1) self.assertRaises(ValueError, base.replace, second=100) self.assertRaises(ValueError, base.replace, microsecond=1000000) + self.assertRaises(ValueError, copy.replace, base, hour=24) + self.assertRaises(ValueError, copy.replace, base, minute=-1) + self.assertRaises(ValueError, copy.replace, base, second=100) + self.assertRaises(ValueError, copy.replace, base, microsecond=1000000) def test_mixed_compare(self): t1 = self.theclass(1, 2, 3) @@ -4331,6 +4489,9 @@ def test_fromisoformat_fails(self): '12:30:45.123456-', # Extra at end of microsecond time '12:30:45.123456+', # Extra at end of microsecond time '12:30:45.123456+12:00:30a', # Extra at end of full time + '12:30:45.400 +02:30', # Space between ms and timezone (gh-130959) + '12:30:45.400 ', # Trailing space (gh-130959) + '12:30:45. 400', # Space before fraction (gh-130959) ] for bad_str in bad_strs: @@ -4491,8 +4652,7 @@ def test_pickling(self): self.assertEqual(derived.tzname(), 'cookie') self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_compat_unpickle(self): tests = [ b'cdatetime\ndatetime\n' @@ -4897,38 +5057,45 @@ def test_replace(self): zm200 = FixedOffset(timedelta(minutes=-200), "-200") args = [1, 2, 3, 4, 5, 6, 7, z100] base = cls(*args) - self.assertEqual(base, base.replace()) - - i = 0 - for name, newval in (("year", 2), - ("month", 3), - ("day", 4), - ("hour", 5), - ("minute", 6), - ("second", 7), - ("microsecond", 8), - ("tzinfo", zm200)): + self.assertEqual(base.replace(), base) + self.assertEqual(copy.replace(base), base) + + changes = (("year", 2), + ("month", 3), + ("day", 4), + ("hour", 5), + ("minute", 6), + ("second", 7), + ("microsecond", 8), + ("tzinfo", zm200)) + for i, (name, newval) in enumerate(changes): newargs = args[:] newargs[i] = newval expected = cls(*newargs) - got = base.replace(**{name: newval}) - self.assertEqual(expected, got) - i += 1 + self.assertEqual(base.replace(**{name: newval}), expected) + self.assertEqual(copy.replace(base, **{name: newval}), expected) # Ensure we can get rid of a tzinfo. self.assertEqual(base.tzname(), "+100") base2 = base.replace(tzinfo=None) self.assertIsNone(base2.tzinfo) self.assertIsNone(base2.tzname()) + base22 = copy.replace(base, tzinfo=None) + self.assertIsNone(base22.tzinfo) + self.assertIsNone(base22.tzname()) # Ensure we can add one. base3 = base2.replace(tzinfo=z100) self.assertEqual(base, base3) self.assertIs(base.tzinfo, base3.tzinfo) + base32 = copy.replace(base22, tzinfo=z100) + self.assertEqual(base, base32) + self.assertIs(base.tzinfo, base32.tzinfo) # Out of bounds. base = cls(2000, 2, 29) self.assertRaises(ValueError, base.replace, year=2001) + self.assertRaises(ValueError, copy.replace, base, year=2001) def test_more_astimezone(self): # The inherited test_astimezone covered some trivial and error cases. @@ -5418,42 +5585,50 @@ def fromutc(self, dt): class Oddballs(unittest.TestCase): - def test_bug_1028306(self): + def test_date_datetime_comparison(self): + # bpo-1028306, bpo-5516 (gh-49766) # Trying to compare a date to a datetime should act like a mixed- # type comparison, despite that datetime is a subclass of date. as_date = date.today() as_datetime = datetime.combine(as_date, time()) - self.assertTrue(as_date != as_datetime) - self.assertTrue(as_datetime != as_date) - self.assertFalse(as_date == as_datetime) - self.assertFalse(as_datetime == as_date) - self.assertRaises(TypeError, lambda: as_date < as_datetime) - self.assertRaises(TypeError, lambda: as_datetime < as_date) - self.assertRaises(TypeError, lambda: as_date <= as_datetime) - self.assertRaises(TypeError, lambda: as_datetime <= as_date) - self.assertRaises(TypeError, lambda: as_date > as_datetime) - self.assertRaises(TypeError, lambda: as_datetime > as_date) - self.assertRaises(TypeError, lambda: as_date >= as_datetime) - self.assertRaises(TypeError, lambda: as_datetime >= as_date) - - # Nevertheless, comparison should work with the base-class (date) - # projection if use of a date method is forced. - self.assertEqual(as_date.__eq__(as_datetime), True) - different_day = (as_date.day + 1) % 20 + 1 - as_different = as_datetime.replace(day= different_day) - self.assertEqual(as_date.__eq__(as_different), False) + date_sc = SubclassDate(as_date.year, as_date.month, as_date.day) + datetime_sc = SubclassDatetime(as_date.year, as_date.month, + as_date.day, 0, 0, 0) + for d in (as_date, date_sc): + for dt in (as_datetime, datetime_sc): + for x, y in (d, dt), (dt, d): + self.assertTrue(x != y) + self.assertFalse(x == y) + self.assertRaises(TypeError, lambda: x < y) + self.assertRaises(TypeError, lambda: x <= y) + self.assertRaises(TypeError, lambda: x > y) + self.assertRaises(TypeError, lambda: x >= y) # And date should compare with other subclasses of date. If a # subclass wants to stop this, it's up to the subclass to do so. - date_sc = SubclassDate(as_date.year, as_date.month, as_date.day) - self.assertEqual(as_date, date_sc) - self.assertEqual(date_sc, as_date) - # Ditto for datetimes. - datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month, - as_date.day, 0, 0, 0) - self.assertEqual(as_datetime, datetime_sc) - self.assertEqual(datetime_sc, as_datetime) + for x, y in ((as_date, date_sc), + (date_sc, as_date), + (as_datetime, datetime_sc), + (datetime_sc, as_datetime)): + self.assertTrue(x == y) + self.assertFalse(x != y) + self.assertFalse(x < y) + self.assertFalse(x > y) + self.assertTrue(x <= y) + self.assertTrue(x >= y) + + # Nevertheless, comparison should work if other object is an instance + # of date or datetime class with overridden comparison operators. + # So special methods should return NotImplemented, as if + # date and datetime were independent classes. + for x, y in (as_date, as_datetime), (as_datetime, as_date): + self.assertEqual(x.__eq__(y), NotImplemented) + self.assertEqual(x.__ne__(y), NotImplemented) + self.assertEqual(x.__lt__(y), NotImplemented) + self.assertEqual(x.__gt__(y), NotImplemented) + self.assertEqual(x.__gt__(y), NotImplemented) + self.assertEqual(x.__ge__(y), NotImplemented) def test_extra_attributes(self): with self.assertWarns(DeprecationWarning): @@ -6679,6 +6854,126 @@ def test_datetime_from_timestamp(self): self.assertEqual(dt_orig, dt_rt) + def test_type_check_in_subinterp(self): + # iOS requires the use of the custom framework loader, + # not the ExtensionFileLoader. + if sys.platform == "ios": + extension_loader = "AppleFrameworkLoader" + else: + extension_loader = "ExtensionFileLoader" + + script = textwrap.dedent(f""" + if {_interpreters is None}: + import _testcapi as module + module.test_datetime_capi() + else: + import importlib.machinery + import importlib.util + fullname = '_testcapi_datetime' + origin = importlib.util.find_spec('_testcapi').origin + loader = importlib.machinery.{extension_loader}(fullname, origin) + spec = importlib.util.spec_from_loader(fullname, loader) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + def run(type_checker, obj): + if not type_checker(obj, True): + raise TypeError(f'{{type(obj)}} is not C API type') + + import _datetime + run(module.datetime_check_date, _datetime.date.today()) + run(module.datetime_check_datetime, _datetime.datetime.now()) + run(module.datetime_check_time, _datetime.time(12, 30)) + run(module.datetime_check_delta, _datetime.timedelta(1)) + run(module.datetime_check_tzinfo, _datetime.tzinfo()) + """) + if _interpreters is None: + ret = support.run_in_subinterp(script) + self.assertEqual(ret, 0) + else: + for name in ('isolated', 'legacy'): + with self.subTest(name): + config = _interpreters.new_config(name).__dict__ + ret = support.run_in_subinterp_with_config(script, **config) + self.assertEqual(ret, 0) + + +class ExtensionModuleTests(unittest.TestCase): + + def setUp(self): + if self.__class__.__name__.endswith('Pure'): + self.skipTest('Not relevant in pure Python') + + @support.cpython_only + def test_gh_120161(self): + with self.subTest('simple'): + script = textwrap.dedent(""" + import datetime + from _ast import Tuple + f = lambda: None + Tuple.dims = property(f, f) + + class tzutc(datetime.tzinfo): + pass + """) + script_helper.assert_python_ok('-c', script) + + with self.subTest('complex'): + script = textwrap.dedent(""" + import asyncio + import datetime + from typing import Type + + class tzutc(datetime.tzinfo): + pass + _EPOCHTZ = datetime.datetime(1970, 1, 1, tzinfo=tzutc()) + + class FakeDateMeta(type): + def __instancecheck__(self, obj): + return True + class FakeDate(datetime.date, metaclass=FakeDateMeta): + pass + def pickle_fake_date(datetime_) -> Type[FakeDate]: + # A pickle function for FakeDate + return FakeDate + """) + script_helper.assert_python_ok('-c', script) + + # TODO: RUSTPYTHON + # AssertionError: Process return code is 1 + @unittest.expectedFailure + def test_update_type_cache(self): + # gh-120782 + script = textwrap.dedent(""" + import sys + for i in range(5): + import _datetime + assert _datetime.date.max > _datetime.date.min + assert _datetime.time.max > _datetime.time.min + assert _datetime.datetime.max > _datetime.datetime.min + assert _datetime.timedelta.max > _datetime.timedelta.min + assert _datetime.date.__dict__["min"] is _datetime.date.min + assert _datetime.date.__dict__["max"] is _datetime.date.max + assert _datetime.date.__dict__["resolution"] is _datetime.date.resolution + assert _datetime.time.__dict__["min"] is _datetime.time.min + assert _datetime.time.__dict__["max"] is _datetime.time.max + assert _datetime.time.__dict__["resolution"] is _datetime.time.resolution + assert _datetime.datetime.__dict__["min"] is _datetime.datetime.min + assert _datetime.datetime.__dict__["max"] is _datetime.datetime.max + assert _datetime.datetime.__dict__["resolution"] is _datetime.datetime.resolution + assert _datetime.timedelta.__dict__["min"] is _datetime.timedelta.min + assert _datetime.timedelta.__dict__["max"] is _datetime.timedelta.max + assert _datetime.timedelta.__dict__["resolution"] is _datetime.timedelta.resolution + assert _datetime.timezone.__dict__["min"] is _datetime.timezone.min + assert _datetime.timezone.__dict__["max"] is _datetime.timezone.max + assert _datetime.timezone.__dict__["utc"] is _datetime.timezone.utc + assert isinstance(_datetime.timezone.min, _datetime.tzinfo) + assert isinstance(_datetime.timezone.max, _datetime.tzinfo) + assert isinstance(_datetime.timezone.utc, _datetime.tzinfo) + del sys.modules['_datetime'] + """) + script_helper.assert_python_ok('-c', script) + def load_tests(loader, standard_tests, pattern): standard_tests.addTest(ZoneInfoCompleteTest()) diff --git a/Lib/test/test_code_module.py b/Lib/test/test_code_module.py index 346cf6746a7..4d5463e6db3 100644 --- a/Lib/test/test_code_module.py +++ b/Lib/test/test_code_module.py @@ -64,6 +64,7 @@ def test_console_stderr(self): raise AssertionError("no console stdout") # TODO: RUSTPYTHON + # AssertionError: Lists differ: [' F[27 chars] x = ?', ' ^', 'SyntaxError: got unexpected token ?'] != [' F[27 chars] x = ?', ' ^', 'SyntaxError: invalid syntax'] @unittest.expectedFailure def test_syntax_error(self): self.infunc.side_effect = ["def f():", @@ -86,6 +87,7 @@ def test_syntax_error(self): self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) # TODO: RUSTPYTHON + # AssertionError: Lists differ: [' F[15 chars], line 1', ' 1', 'IndentationError: unexpected indentation'] != [' F[15 chars], line 1', ' 1', 'IndentationError: unexpected indent'] @unittest.expectedFailure def test_indentation_error(self): self.infunc.side_effect = [" 1", EOFError('Finished')] @@ -104,6 +106,7 @@ def test_indentation_error(self): self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) # TODO: RUSTPYTHON + # AssertionError: False is not true : UnicodeDecodeError: invalid utf-8 sequence of 1 bytes from index 1 @unittest.expectedFailure def test_unicode_error(self): self.infunc.side_effect = ["'\ud800'", EOFError('Finished')] @@ -142,6 +145,7 @@ def test_sysexcepthook(self): 'ValueError: BOOM!\n']) # TODO: RUSTPYTHON + # AssertionError: Lists differ: [' F[35 chars]= ?\n', ' ^\n', 'SyntaxError: got unexpected token ?\n'] != [' F[35 chars]= ?\n', ' ^\n', 'SyntaxError: invalid syntax\n'] @unittest.expectedFailure def test_sysexcepthook_syntax_error(self): self.infunc.side_effect = ["def f():", @@ -167,6 +171,7 @@ def test_sysexcepthook_syntax_error(self): 'SyntaxError: invalid syntax\n']) # TODO: RUSTPYTHON + # AssertionError: Lists differ: [' F[21 chars] 1\n', ' 1\n', 'IndentationError: unexpected indentation\n'] != [' F[21 chars] 1\n', ' 1\n', 'IndentationError: unexpected indent\n'] @unittest.expectedFailure def test_sysexcepthook_indentation_error(self): self.infunc.side_effect = [" 1", EOFError('Finished')] @@ -262,8 +267,7 @@ def test_exit_msg(self): self.assertEqual(err_msg, ['write', (expected,), {}]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cause_tb(self): self.infunc.side_effect = ["raise ValueError('') from AttributeError", EOFError('Finished')] From cd012f9b2e5bea893d7ba483fe34c5189ef2fd17 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:43:10 -0500 Subject: [PATCH 643/819] Added doctest tests (#6598) --- Lib/test/test_doctest/__init__.py | 5 + Lib/test/test_doctest/decorator_mod.py | 10 + Lib/test/test_doctest/doctest_aliases.py | 13 + Lib/test/test_doctest/doctest_lineno.py | 107 + Lib/test/test_doctest/sample_doctest.py | 76 + .../test_doctest/sample_doctest_errors.py | 46 + .../sample_doctest_no_docstrings.py | 12 + .../sample_doctest_no_doctests.py | 15 + Lib/test/test_doctest/sample_doctest_skip.py | 49 + Lib/test/test_doctest/test_doctest.py | 4021 +++++++++++++++++ Lib/test/test_doctest/test_doctest.txt | 17 + Lib/test/test_doctest/test_doctest2.py | 126 + Lib/test/test_doctest/test_doctest2.txt | 14 + Lib/test/test_doctest/test_doctest3.txt | 5 + Lib/test/test_doctest/test_doctest4.txt | 11 + Lib/test/test_doctest/test_doctest_errors.txt | 14 + Lib/test/test_doctest/test_doctest_skip.txt | 6 + Lib/test/test_doctest/test_doctest_skip2.txt | 6 + crates/vm/src/builtins/int.rs | 2 +- 19 files changed, 4554 insertions(+), 1 deletion(-) create mode 100644 Lib/test/test_doctest/__init__.py create mode 100644 Lib/test/test_doctest/decorator_mod.py create mode 100644 Lib/test/test_doctest/doctest_aliases.py create mode 100644 Lib/test/test_doctest/doctest_lineno.py create mode 100644 Lib/test/test_doctest/sample_doctest.py create mode 100644 Lib/test/test_doctest/sample_doctest_errors.py create mode 100644 Lib/test/test_doctest/sample_doctest_no_docstrings.py create mode 100644 Lib/test/test_doctest/sample_doctest_no_doctests.py create mode 100644 Lib/test/test_doctest/sample_doctest_skip.py create mode 100644 Lib/test/test_doctest/test_doctest.py create mode 100644 Lib/test/test_doctest/test_doctest.txt create mode 100644 Lib/test/test_doctest/test_doctest2.py create mode 100644 Lib/test/test_doctest/test_doctest2.txt create mode 100644 Lib/test/test_doctest/test_doctest3.txt create mode 100644 Lib/test/test_doctest/test_doctest4.txt create mode 100644 Lib/test/test_doctest/test_doctest_errors.txt create mode 100644 Lib/test/test_doctest/test_doctest_skip.txt create mode 100644 Lib/test/test_doctest/test_doctest_skip2.txt diff --git a/Lib/test/test_doctest/__init__.py b/Lib/test/test_doctest/__init__.py new file mode 100644 index 00000000000..4b16ecc3115 --- /dev/null +++ b/Lib/test/test_doctest/__init__.py @@ -0,0 +1,5 @@ +import os +from test.support import load_package_tests + +def load_tests(*args): + return load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_doctest/decorator_mod.py b/Lib/test/test_doctest/decorator_mod.py new file mode 100644 index 00000000000..9f106888411 --- /dev/null +++ b/Lib/test/test_doctest/decorator_mod.py @@ -0,0 +1,10 @@ +# This module is used in `doctest_lineno.py`. +import functools + + +def decorator(f): + @functools.wraps(f) + def inner(): + return f() + + return inner diff --git a/Lib/test/test_doctest/doctest_aliases.py b/Lib/test/test_doctest/doctest_aliases.py new file mode 100644 index 00000000000..30cefafa83e --- /dev/null +++ b/Lib/test/test_doctest/doctest_aliases.py @@ -0,0 +1,13 @@ +# Used by test_doctest.py. + +class TwoNames: + '''f() and g() are two names for the same method''' + + def f(self): + ''' + >>> print(TwoNames().f()) + f + ''' + return 'f' + + g = f # define an alias for f diff --git a/Lib/test/test_doctest/doctest_lineno.py b/Lib/test/test_doctest/doctest_lineno.py new file mode 100644 index 00000000000..0bd402e9828 --- /dev/null +++ b/Lib/test/test_doctest/doctest_lineno.py @@ -0,0 +1,107 @@ +# This module is used in `test_doctest`. +# It must not have a docstring. + +def func_with_docstring(): + """Some unrelated info.""" + + +def func_without_docstring(): + pass + + +def func_with_doctest(): + """ + This function really contains a test case. + + >>> func_with_doctest.__name__ + 'func_with_doctest' + """ + return 3 + + +class ClassWithDocstring: + """Some unrelated class information.""" + + +class ClassWithoutDocstring: + pass + + +class ClassWithDoctest: + """This class really has a test case in it. + + >>> ClassWithDoctest.__name__ + 'ClassWithDoctest' + """ + + +class MethodWrapper: + def method_with_docstring(self): + """Method with a docstring.""" + + def method_without_docstring(self): + pass + + def method_with_doctest(self): + """ + This has a doctest! + >>> MethodWrapper.method_with_doctest.__name__ + 'method_with_doctest' + """ + + @classmethod + def classmethod_with_doctest(cls): + """ + This has a doctest! + >>> MethodWrapper.classmethod_with_doctest.__name__ + 'classmethod_with_doctest' + """ + + @property + def property_with_doctest(self): + """ + This has a doctest! + >>> MethodWrapper.property_with_doctest.__name__ + 'property_with_doctest' + """ + +# https://github.com/python/cpython/issues/99433 +str_wrapper = object().__str__ + + +# https://github.com/python/cpython/issues/115392 +from test.test_doctest.decorator_mod import decorator + +@decorator +@decorator +def func_with_docstring_wrapped(): + """Some unrelated info.""" + + +# https://github.com/python/cpython/issues/136914 +import functools + + +@functools.cache +def cached_func_with_doctest(value): + """ + >>> cached_func_with_doctest(1) + -1 + """ + return -value + + +@functools.cache +def cached_func_without_docstring(value): + return value + 1 + + +class ClassWithACachedProperty: + + @functools.cached_property + def cached(self): + """ + >>> X().cached + -1 + """ + return 0 diff --git a/Lib/test/test_doctest/sample_doctest.py b/Lib/test/test_doctest/sample_doctest.py new file mode 100644 index 00000000000..049f737a0a4 --- /dev/null +++ b/Lib/test/test_doctest/sample_doctest.py @@ -0,0 +1,76 @@ +"""This is a sample module that doesn't really test anything all that + interesting. + +It simply has a few tests, some of which succeed and some of which fail. + +It's important that the numbers remain constant as another test is +testing the running of these tests. + + +>>> 2+2 +4 +""" + + +def foo(): + """ + + >>> 2+2 + 5 + + >>> 2+2 + 4 + """ + +def bar(): + """ + + >>> 2+2 + 4 + """ + +def test_silly_setup(): + """ + + >>> import test.test_doctest.test_doctest + >>> test.test_doctest.test_doctest.sillySetup + True + """ + +def w_blank(): + """ + >>> if 1: + ... print('a') + ... print() + ... print('b') + a + <BLANKLINE> + b + """ + +x = 1 +def x_is_one(): + """ + >>> x + 1 + """ + +def y_is_one(): + """ + >>> y + 1 + """ + +__test__ = {'good': """ + >>> 42 + 42 + """, + 'bad': """ + >>> 42 + 666 + """, + } + +def test_suite(): + import doctest + return doctest.DocTestSuite() diff --git a/Lib/test/test_doctest/sample_doctest_errors.py b/Lib/test/test_doctest/sample_doctest_errors.py new file mode 100644 index 00000000000..4a6f07af2d4 --- /dev/null +++ b/Lib/test/test_doctest/sample_doctest_errors.py @@ -0,0 +1,46 @@ +"""This is a sample module used for testing doctest. + +This module includes various scenarios involving errors. + +>>> 2 + 2 +5 +>>> 1/0 +1 +""" + +def g(): + [][0] # line 12 + +def errors(): + """ + >>> 2 + 2 + 5 + >>> 1/0 + 1 + >>> def f(): + ... 2 + '2' + ... + >>> f() + 1 + >>> g() + 1 + """ + +def syntax_error(): + """ + >>> 2+*3 + 5 + """ + +__test__ = { + 'bad': """ + >>> 2 + 2 + 5 + >>> 1/0 + 1 + """, +} + +def test_suite(): + import doctest + return doctest.DocTestSuite() diff --git a/Lib/test/test_doctest/sample_doctest_no_docstrings.py b/Lib/test/test_doctest/sample_doctest_no_docstrings.py new file mode 100644 index 00000000000..e4201edbce9 --- /dev/null +++ b/Lib/test/test_doctest/sample_doctest_no_docstrings.py @@ -0,0 +1,12 @@ +# This is a sample module used for testing doctest. +# +# This module is for testing how doctest handles a module with no +# docstrings. + + +class Foo(object): + + # A class with no docstring. + + def __init__(self): + pass diff --git a/Lib/test/test_doctest/sample_doctest_no_doctests.py b/Lib/test/test_doctest/sample_doctest_no_doctests.py new file mode 100644 index 00000000000..7daa57231c8 --- /dev/null +++ b/Lib/test/test_doctest/sample_doctest_no_doctests.py @@ -0,0 +1,15 @@ +"""This is a sample module used for testing doctest. + +This module is for testing how doctest handles a module with docstrings +but no doctest examples. + +""" + + +class Foo(object): + """A docstring with no doctest examples. + + """ + + def __init__(self): + pass diff --git a/Lib/test/test_doctest/sample_doctest_skip.py b/Lib/test/test_doctest/sample_doctest_skip.py new file mode 100644 index 00000000000..1b83dec1f8c --- /dev/null +++ b/Lib/test/test_doctest/sample_doctest_skip.py @@ -0,0 +1,49 @@ +"""This is a sample module used for testing doctest. + +This module includes various scenarios involving skips. +""" + +def no_skip_pass(): + """ + >>> 2 + 2 + 4 + """ + +def no_skip_fail(): + """ + >>> 2 + 2 + 5 + """ + +def single_skip(): + """ + >>> 2 + 2 # doctest: +SKIP + 4 + """ + +def double_skip(): + """ + >>> 2 + 2 # doctest: +SKIP + 4 + >>> 3 + 3 # doctest: +SKIP + 6 + """ + +def partial_skip_pass(): + """ + >>> 2 + 2 # doctest: +SKIP + 4 + >>> 3 + 3 + 6 + """ + +def partial_skip_fail(): + """ + >>> 2 + 2 # doctest: +SKIP + 4 + >>> 2 + 2 + 5 + """ + +def no_examples(): + """A docstring with no examples should not be counted as run or skipped.""" diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py new file mode 100644 index 00000000000..48d5ff6f73f --- /dev/null +++ b/Lib/test/test_doctest/test_doctest.py @@ -0,0 +1,4021 @@ +""" +Test script for doctest. +""" + +from test import support +from test.support import import_helper +from test.support.pty_helper import FakeInput # used in doctests +import doctest +import functools +import os +import sys +import importlib +import importlib.abc +import importlib.util +import unittest +import tempfile +import types +import contextlib +import _colorize + + +def doctest_skip_if(condition): + def decorator(func): + if condition and support.HAVE_DOCSTRINGS: + func.__doc__ = ">>> pass # doctest: +SKIP" + return func + return decorator + + +# NOTE: There are some additional tests relating to interaction with +# zipimport in the test_zipimport_support test module. +# There are also related tests in `test_doctest2` module. + +###################################################################### +## Sample Objects (used by test cases) +###################################################################### + +def sample_func(v): + """ + Blah blah + + >>> print(sample_func(22)) + 44 + + Yee ha! + """ + return v+v + +class SampleClass: + """ + >>> print(1) + 1 + + >>> # comments get ignored. so are empty PS1 and PS2 prompts: + >>> + ... + + Multiline example: + >>> sc = SampleClass(3) + >>> for i in range(10): + ... sc = sc.double() + ... print(' ', sc.get(), sep='', end='') + 6 12 24 48 96 192 384 768 1536 3072 + """ + def __init__(self, val): + """ + >>> print(SampleClass(12).get()) + 12 + """ + self.val = val + + def double(self): + """ + >>> print(SampleClass(12).double().get()) + 24 + """ + return SampleClass(self.val + self.val) + + def get(self): + """ + >>> print(SampleClass(-5).get()) + -5 + """ + return self.val + + def setter(self, val): + """ + >>> s = SampleClass(-5) + >>> s.setter(1) + >>> print(s.val) + 1 + """ + self.val = val + + def a_staticmethod(v): + """ + >>> print(SampleClass.a_staticmethod(10)) + 11 + """ + return v+1 + a_staticmethod = staticmethod(a_staticmethod) + + def a_classmethod(cls, v): + """ + >>> print(SampleClass.a_classmethod(10)) + 12 + >>> print(SampleClass(0).a_classmethod(10)) + 12 + """ + return v+2 + a_classmethod = classmethod(a_classmethod) + + a_property = property(get, setter, doc=""" + >>> print(SampleClass(22).a_property) + 22 + """) + + a_class_attribute = 42 + + @functools.cached_property + def a_cached_property(self): + """ + >>> print(SampleClass(29).get()) + 29 + """ + return "hello" + + class NestedClass: + """ + >>> x = SampleClass.NestedClass(5) + >>> y = x.square() + >>> print(y.get()) + 25 + """ + def __init__(self, val=0): + """ + >>> print(SampleClass.NestedClass().get()) + 0 + """ + self.val = val + def square(self): + return SampleClass.NestedClass(self.val*self.val) + def get(self): + return self.val + +class SampleNewStyleClass(object): + r""" + >>> print('1\n2\n3') + 1 + 2 + 3 + """ + def __init__(self, val): + """ + >>> print(SampleNewStyleClass(12).get()) + 12 + """ + self.val = val + + def double(self): + """ + >>> print(SampleNewStyleClass(12).double().get()) + 24 + """ + return SampleNewStyleClass(self.val + self.val) + + def get(self): + """ + >>> print(SampleNewStyleClass(-5).get()) + -5 + """ + return self.val + +###################################################################### +## Test Cases +###################################################################### + +def test_Example(): r""" +Unit tests for the `Example` class. + +Example is a simple container class that holds: + - `source`: A source string. + - `want`: An expected output string. + - `exc_msg`: An expected exception message string (or None if no + exception is expected). + - `lineno`: A line number (within the docstring). + - `indent`: The example's indentation in the input string. + - `options`: An option dictionary, mapping option flags to True or + False. + +These attributes are set by the constructor. `source` and `want` are +required; the other attributes all have default values: + + >>> example = doctest.Example('print(1)', '1\n') + >>> (example.source, example.want, example.exc_msg, + ... example.lineno, example.indent, example.options) + ('print(1)\n', '1\n', None, 0, 0, {}) + +The first three attributes (`source`, `want`, and `exc_msg`) may be +specified positionally; the remaining arguments should be specified as +keyword arguments: + + >>> exc_msg = 'IndexError: pop from an empty list' + >>> example = doctest.Example('[].pop()', '', exc_msg, + ... lineno=5, indent=4, + ... options={doctest.ELLIPSIS: True}) + >>> (example.source, example.want, example.exc_msg, + ... example.lineno, example.indent, example.options) + ('[].pop()\n', '', 'IndexError: pop from an empty list\n', 5, 4, {8: True}) + +The constructor normalizes the `source` string to end in a newline: + + Source spans a single line: no terminating newline. + >>> e = doctest.Example('print(1)', '1\n') + >>> e.source, e.want + ('print(1)\n', '1\n') + + >>> e = doctest.Example('print(1)\n', '1\n') + >>> e.source, e.want + ('print(1)\n', '1\n') + + Source spans multiple lines: require terminating newline. + >>> e = doctest.Example('print(1);\nprint(2)\n', '1\n2\n') + >>> e.source, e.want + ('print(1);\nprint(2)\n', '1\n2\n') + + >>> e = doctest.Example('print(1);\nprint(2)', '1\n2\n') + >>> e.source, e.want + ('print(1);\nprint(2)\n', '1\n2\n') + + Empty source string (which should never appear in real examples) + >>> e = doctest.Example('', '') + >>> e.source, e.want + ('\n', '') + +The constructor normalizes the `want` string to end in a newline, +unless it's the empty string: + + >>> e = doctest.Example('print(1)', '1\n') + >>> e.source, e.want + ('print(1)\n', '1\n') + + >>> e = doctest.Example('print(1)', '1') + >>> e.source, e.want + ('print(1)\n', '1\n') + + >>> e = doctest.Example('print', '') + >>> e.source, e.want + ('print\n', '') + +The constructor normalizes the `exc_msg` string to end in a newline, +unless it's `None`: + + Message spans one line + >>> exc_msg = 'IndexError: pop from an empty list' + >>> e = doctest.Example('[].pop()', '', exc_msg) + >>> e.exc_msg + 'IndexError: pop from an empty list\n' + + >>> exc_msg = 'IndexError: pop from an empty list\n' + >>> e = doctest.Example('[].pop()', '', exc_msg) + >>> e.exc_msg + 'IndexError: pop from an empty list\n' + + Message spans multiple lines + >>> exc_msg = 'ValueError: 1\n 2' + >>> e = doctest.Example('raise ValueError("1\n 2")', '', exc_msg) + >>> e.exc_msg + 'ValueError: 1\n 2\n' + + >>> exc_msg = 'ValueError: 1\n 2\n' + >>> e = doctest.Example('raise ValueError("1\n 2")', '', exc_msg) + >>> e.exc_msg + 'ValueError: 1\n 2\n' + + Empty (but non-None) exception message (which should never appear + in real examples) + >>> exc_msg = '' + >>> e = doctest.Example('raise X()', '', exc_msg) + >>> e.exc_msg + '\n' + +Compare `Example`: + >>> example = doctest.Example('print 1', '1\n') + >>> same_example = doctest.Example('print 1', '1\n') + >>> other_example = doctest.Example('print 42', '42\n') + >>> example == same_example + True + >>> example != same_example + False + >>> hash(example) == hash(same_example) + True + >>> example == other_example + False + >>> example != other_example + True +""" + +def test_DocTest(): r""" +Unit tests for the `DocTest` class. + +DocTest is a collection of examples, extracted from a docstring, along +with information about where the docstring comes from (a name, +filename, and line number). The docstring is parsed by the `DocTest` +constructor: + + >>> docstring = ''' + ... >>> print(12) + ... 12 + ... + ... Non-example text. + ... + ... >>> print('another\\example') + ... another + ... example + ... ''' + >>> globs = {} # globals to run the test in. + >>> parser = doctest.DocTestParser() + >>> test = parser.get_doctest(docstring, globs, 'some_test', + ... 'some_file', 20) + >>> print(test) + <DocTest some_test from some_file:20 (2 examples)> + >>> len(test.examples) + 2 + >>> e1, e2 = test.examples + >>> (e1.source, e1.want, e1.lineno) + ('print(12)\n', '12\n', 1) + >>> (e2.source, e2.want, e2.lineno) + ("print('another\\example')\n", 'another\nexample\n', 6) + +Source information (name, filename, and line number) is available as +attributes on the doctest object: + + >>> (test.name, test.filename, test.lineno) + ('some_test', 'some_file', 20) + +The line number of an example within its containing file is found by +adding the line number of the example and the line number of its +containing test: + + >>> test.lineno + e1.lineno + 21 + >>> test.lineno + e2.lineno + 26 + +If the docstring contains inconsistent leading whitespace in the +expected output of an example, then `DocTest` will raise a ValueError: + + >>> docstring = r''' + ... >>> print('bad\nindentation') + ... bad + ... indentation + ... ''' + >>> parser.get_doctest(docstring, globs, 'some_test', 'filename', 0) + Traceback (most recent call last): + ValueError: line 4 of the docstring for some_test has inconsistent leading whitespace: 'indentation' + +If the docstring contains inconsistent leading whitespace on +continuation lines, then `DocTest` will raise a ValueError: + + >>> docstring = r''' + ... >>> print(('bad indentation', + ... ... 2)) + ... ('bad', 'indentation') + ... ''' + >>> parser.get_doctest(docstring, globs, 'some_test', 'filename', 0) + Traceback (most recent call last): + ValueError: line 2 of the docstring for some_test has inconsistent leading whitespace: '... 2))' + +If there's no blank space after a PS1 prompt ('>>>'), then `DocTest` +will raise a ValueError: + + >>> docstring = '>>>print(1)\n1' + >>> parser.get_doctest(docstring, globs, 'some_test', 'filename', 0) + Traceback (most recent call last): + ValueError: line 1 of the docstring for some_test lacks blank after >>>: '>>>print(1)' + +If there's no blank space after a PS2 prompt ('...'), then `DocTest` +will raise a ValueError: + + >>> docstring = '>>> if 1:\n...print(1)\n1' + >>> parser.get_doctest(docstring, globs, 'some_test', 'filename', 0) + Traceback (most recent call last): + ValueError: line 2 of the docstring for some_test lacks blank after ...: '...print(1)' + +Compare `DocTest`: + + >>> docstring = ''' + ... >>> print 12 + ... 12 + ... ''' + >>> test = parser.get_doctest(docstring, globs, 'some_test', + ... 'some_test', 20) + >>> same_test = parser.get_doctest(docstring, globs, 'some_test', + ... 'some_test', 20) + >>> test == same_test + True + >>> test != same_test + False + >>> hash(test) == hash(same_test) + True + >>> docstring = ''' + ... >>> print 42 + ... 42 + ... ''' + >>> other_test = parser.get_doctest(docstring, globs, 'other_test', + ... 'other_file', 10) + >>> test == other_test + False + >>> test != other_test + True + >>> test < other_test + False + >>> other_test < test + True + +Test comparison with lineno None on one side + + >>> no_lineno = parser.get_doctest(docstring, globs, 'some_test', + ... 'some_test', None) + >>> test.lineno is None + False + >>> no_lineno.lineno is None + True + >>> test < no_lineno + False + >>> no_lineno < test + True + +Compare `DocTestCase`: + + >>> DocTestCase = doctest.DocTestCase + >>> test_case = DocTestCase(test) + >>> same_test_case = DocTestCase(same_test) + >>> other_test_case = DocTestCase(other_test) + >>> test_case == same_test_case + True + >>> test_case != same_test_case + False + >>> hash(test_case) == hash(same_test_case) + True + >>> test == other_test_case + False + >>> test != other_test_case + True + +""" + +class test_DocTestFinder: + def basics(): r""" +Unit tests for the `DocTestFinder` class. + +DocTestFinder is used to extract DocTests from an object's docstring +and the docstrings of its contained objects. It can be used with +modules, functions, classes, methods, staticmethods, classmethods, and +properties. + +Finding Tests in Functions +~~~~~~~~~~~~~~~~~~~~~~~~~~ +For a function whose docstring contains examples, DocTestFinder.find() +will return a single test (for that function's docstring): + + >>> finder = doctest.DocTestFinder() + +We'll simulate a __file__ attr that ends in pyc: + + >>> from test.test_doctest import test_doctest + >>> old = test_doctest.__file__ + >>> test_doctest.__file__ = 'test_doctest.pyc' + + >>> tests = finder.find(sample_func) + + >>> print(tests) # doctest: +ELLIPSIS + [<DocTest sample_func from test_doctest.py:38 (1 example)>] + +The exact name depends on how test_doctest was invoked, so allow for +leading path components. + + >>> tests[0].filename # doctest: +ELLIPSIS + '...test_doctest.py' + + >>> test_doctest.__file__ = old + + + >>> e = tests[0].examples[0] + >>> (e.source, e.want, e.lineno) + ('print(sample_func(22))\n', '44\n', 3) + +By default, tests are created for objects with no docstring: + + >>> def no_docstring(v): + ... pass + >>> finder.find(no_docstring) + [] + +However, the optional argument `exclude_empty` to the DocTestFinder +constructor can be used to exclude tests for objects with empty +docstrings: + + >>> def no_docstring(v): + ... pass + >>> excl_empty_finder = doctest.DocTestFinder(exclude_empty=True) + >>> excl_empty_finder.find(no_docstring) + [] + +If the function has a docstring with no examples, then a test with no +examples is returned. (This lets `DocTestRunner` collect statistics +about which functions have no tests -- but is that useful? And should +an empty test also be created when there's no docstring?) + + >>> def no_examples(v): + ... ''' no doctest examples ''' + >>> finder.find(no_examples) # doctest: +ELLIPSIS + [<DocTest no_examples from ...:1 (no examples)>] + +Finding Tests in Classes +~~~~~~~~~~~~~~~~~~~~~~~~ +For a class, DocTestFinder will create a test for the class's +docstring, and will recursively explore its contents, including +methods, classmethods, staticmethods, properties, and nested classes. + + >>> finder = doctest.DocTestFinder() + >>> tests = finder.find(SampleClass) + >>> for t in tests: + ... print('%2s %s' % (len(t.examples), t.name)) + 3 SampleClass + 3 SampleClass.NestedClass + 1 SampleClass.NestedClass.__init__ + 1 SampleClass.__init__ + 1 SampleClass.a_cached_property + 2 SampleClass.a_classmethod + 1 SampleClass.a_property + 1 SampleClass.a_staticmethod + 1 SampleClass.double + 1 SampleClass.get + 3 SampleClass.setter + +New-style classes are also supported: + + >>> tests = finder.find(SampleNewStyleClass) + >>> for t in tests: + ... print('%2s %s' % (len(t.examples), t.name)) + 1 SampleNewStyleClass + 1 SampleNewStyleClass.__init__ + 1 SampleNewStyleClass.double + 1 SampleNewStyleClass.get + +Finding Tests in Modules +~~~~~~~~~~~~~~~~~~~~~~~~ +For a module, DocTestFinder will create a test for the class's +docstring, and will recursively explore its contents, including +functions, classes, and the `__test__` dictionary, if it exists: + + >>> # A module + >>> import types + >>> m = types.ModuleType('some_module') + >>> def triple(val): + ... ''' + ... >>> print(triple(11)) + ... 33 + ... ''' + ... return val*3 + >>> m.__dict__.update({ + ... 'sample_func': sample_func, + ... 'SampleClass': SampleClass, + ... '__doc__': ''' + ... Module docstring. + ... >>> print('module') + ... module + ... ''', + ... '__test__': { + ... 'd': '>>> print(6)\n6\n>>> print(7)\n7\n', + ... 'c': triple}}) + + >>> finder = doctest.DocTestFinder() + >>> # Use module=test_doctest, to prevent doctest from + >>> # ignoring the objects since they weren't defined in m. + >>> from test.test_doctest import test_doctest + >>> tests = finder.find(m, module=test_doctest) + >>> for t in tests: + ... print('%2s %s' % (len(t.examples), t.name)) + 1 some_module + 3 some_module.SampleClass + 3 some_module.SampleClass.NestedClass + 1 some_module.SampleClass.NestedClass.__init__ + 1 some_module.SampleClass.__init__ + 1 some_module.SampleClass.a_cached_property + 2 some_module.SampleClass.a_classmethod + 1 some_module.SampleClass.a_property + 1 some_module.SampleClass.a_staticmethod + 1 some_module.SampleClass.double + 1 some_module.SampleClass.get + 3 some_module.SampleClass.setter + 1 some_module.__test__.c + 2 some_module.__test__.d + 1 some_module.sample_func + +However, doctest will ignore imported objects from other modules +(without proper `module=`): + + >>> import types + >>> m = types.ModuleType('poluted_namespace') + >>> m.__dict__.update({ + ... 'sample_func': sample_func, + ... 'SampleClass': SampleClass, + ... }) + + >>> finder = doctest.DocTestFinder() + >>> finder.find(m) + [] + +Duplicate Removal +~~~~~~~~~~~~~~~~~ +If a single object is listed twice (under different names), then tests +will only be generated for it once: + + >>> from test.test_doctest import doctest_aliases + >>> assert doctest_aliases.TwoNames.f + >>> assert doctest_aliases.TwoNames.g + >>> tests = excl_empty_finder.find(doctest_aliases) + >>> print(len(tests)) + 2 + >>> print(tests[0].name) + test.test_doctest.doctest_aliases.TwoNames + + TwoNames.f and TwoNames.g are bound to the same object. + We can't guess which will be found in doctest's traversal of + TwoNames.__dict__ first, so we have to allow for either. + + >>> tests[1].name.split('.')[-1] in ['f', 'g'] + True + +Empty Tests +~~~~~~~~~~~ +By default, an object with no doctests doesn't create any tests: + + >>> tests = doctest.DocTestFinder().find(SampleClass) + >>> for t in tests: + ... print('%2s %s' % (len(t.examples), t.name)) + 3 SampleClass + 3 SampleClass.NestedClass + 1 SampleClass.NestedClass.__init__ + 1 SampleClass.__init__ + 1 SampleClass.a_cached_property + 2 SampleClass.a_classmethod + 1 SampleClass.a_property + 1 SampleClass.a_staticmethod + 1 SampleClass.double + 1 SampleClass.get + 3 SampleClass.setter + +By default, that excluded objects with no doctests. exclude_empty=False +tells it to include (empty) tests for objects with no doctests. This feature +is really to support backward compatibility in what doctest.master.summarize() +displays. + + >>> tests = doctest.DocTestFinder(exclude_empty=False).find(SampleClass) + >>> for t in tests: + ... print('%2s %s' % (len(t.examples), t.name)) + 3 SampleClass + 3 SampleClass.NestedClass + 1 SampleClass.NestedClass.__init__ + 0 SampleClass.NestedClass.get + 0 SampleClass.NestedClass.square + 1 SampleClass.__init__ + 1 SampleClass.a_cached_property + 2 SampleClass.a_classmethod + 1 SampleClass.a_property + 1 SampleClass.a_staticmethod + 1 SampleClass.double + 1 SampleClass.get + 3 SampleClass.setter + +When used with `exclude_empty=False` we are also interested in line numbers +of doctests that are empty. +It used to be broken for quite some time until `bpo-28249`. + + >>> from test.test_doctest import doctest_lineno + >>> tests = doctest.DocTestFinder(exclude_empty=False).find(doctest_lineno) + >>> for t in tests: + ... print('%5s %s' % (t.lineno, t.name)) + None test.test_doctest.doctest_lineno + None test.test_doctest.doctest_lineno.ClassWithACachedProperty + 102 test.test_doctest.doctest_lineno.ClassWithACachedProperty.cached + 22 test.test_doctest.doctest_lineno.ClassWithDocstring + 30 test.test_doctest.doctest_lineno.ClassWithDoctest + None test.test_doctest.doctest_lineno.ClassWithoutDocstring + None test.test_doctest.doctest_lineno.MethodWrapper + 53 test.test_doctest.doctest_lineno.MethodWrapper.classmethod_with_doctest + 39 test.test_doctest.doctest_lineno.MethodWrapper.method_with_docstring + 45 test.test_doctest.doctest_lineno.MethodWrapper.method_with_doctest + None test.test_doctest.doctest_lineno.MethodWrapper.method_without_docstring + 61 test.test_doctest.doctest_lineno.MethodWrapper.property_with_doctest + 86 test.test_doctest.doctest_lineno.cached_func_with_doctest + None test.test_doctest.doctest_lineno.cached_func_without_docstring + 4 test.test_doctest.doctest_lineno.func_with_docstring + 77 test.test_doctest.doctest_lineno.func_with_docstring_wrapped + 12 test.test_doctest.doctest_lineno.func_with_doctest + None test.test_doctest.doctest_lineno.func_without_docstring + +Turning off Recursion +~~~~~~~~~~~~~~~~~~~~~ +DocTestFinder can be told not to look for tests in contained objects +using the `recurse` flag: + + >>> tests = doctest.DocTestFinder(recurse=False).find(SampleClass) + >>> for t in tests: + ... print('%2s %s' % (len(t.examples), t.name)) + 3 SampleClass + +Line numbers +~~~~~~~~~~~~ +DocTestFinder finds the line number of each example: + + >>> def f(x): + ... ''' + ... >>> x = 12 + ... + ... some text + ... + ... >>> # examples are not created for comments & bare prompts. + ... >>> + ... ... + ... + ... >>> for x in range(10): + ... ... print(x, end=' ') + ... 0 1 2 3 4 5 6 7 8 9 + ... >>> x//2 + ... 6 + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> [e.lineno for e in test.examples] + [1, 9, 12] +""" + +# TODO: RUSTPYTHON +# Currently, only 5 builtins types exist in RustPython +# So this test will fail +# if int.__doc__: # simple check for --without-doc-strings, skip if lacking +# def non_Python_modules(): r""" + +# Finding Doctests in Modules Not Written in Python +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# DocTestFinder can also find doctests in most modules not written in Python. +# We'll use builtins as an example, since it almost certainly isn't written in +# plain ol' Python and is guaranteed to be available. + +# >>> import builtins +# >>> tests = doctest.DocTestFinder().find(builtins) +# >>> 830 < len(tests) < 860 # approximate number of objects with docstrings +# True +# >>> real_tests = [t for t in tests if len(t.examples) > 0] +# >>> len(real_tests) # objects that actually have doctests +# 14 +# >>> for t in real_tests: +# ... print('{} {}'.format(len(t.examples), t.name)) +# ... +# 1 builtins.bin +# 5 builtins.bytearray.hex +# 5 builtins.bytes.hex +# 3 builtins.float.as_integer_ratio +# 2 builtins.float.fromhex +# 2 builtins.float.hex +# 1 builtins.hex +# 1 builtins.int +# 3 builtins.int.as_integer_ratio +# 2 builtins.int.bit_count +# 2 builtins.int.bit_length +# 5 builtins.memoryview.hex +# 1 builtins.oct +# 1 builtins.zip + +# Note here that 'bin', 'oct', and 'hex' are functions; 'float.as_integer_ratio', +# 'float.hex', and 'int.bit_length' are methods; 'float.fromhex' is a classmethod, +# and 'int' is a type. +# """ + + +class TestDocTest(unittest.TestCase): + + def test_run(self): + test = ''' + >>> 1 + 1 + 11 + >>> 2 + 3 # doctest: +SKIP + "23" + >>> 5 + 7 + 57 + ''' + + def myfunc(): + pass + myfunc.__doc__ = test + + # test DocTestFinder.run() + test = doctest.DocTestFinder().find(myfunc)[0] + with support.captured_stdout(): + with support.captured_stderr(): + results = doctest.DocTestRunner(verbose=False).run(test) + + # test TestResults + self.assertIsInstance(results, doctest.TestResults) + self.assertEqual(results.failed, 2) + self.assertEqual(results.attempted, 3) + self.assertEqual(results.skipped, 1) + self.assertEqual(tuple(results), (2, 3)) + x, y = results + self.assertEqual((x, y), (2, 3)) + + +class TestDocTestFinder(unittest.TestCase): + + def test_issue35753(self): + # This import of `call` should trigger issue35753 when + # DocTestFinder.find() is called due to inspect.unwrap() failing, + # however with a patched doctest this should succeed. + from unittest.mock import call + dummy_module = types.ModuleType("dummy") + dummy_module.__dict__['inject_call'] = call + finder = doctest.DocTestFinder() + self.assertEqual(finder.find(dummy_module), []) + + def test_empty_namespace_package(self): + pkg_name = 'doctest_empty_pkg' + with tempfile.TemporaryDirectory() as parent_dir: + pkg_dir = os.path.join(parent_dir, pkg_name) + os.mkdir(pkg_dir) + sys.path.append(parent_dir) + try: + mod = importlib.import_module(pkg_name) + finally: + import_helper.forget(pkg_name) + sys.path.pop() + + include_empty_finder = doctest.DocTestFinder(exclude_empty=False) + exclude_empty_finder = doctest.DocTestFinder(exclude_empty=True) + + self.assertEqual(len(include_empty_finder.find(mod)), 1) + self.assertEqual(len(exclude_empty_finder.find(mod)), 0) + +def test_DocTestParser(): r""" +Unit tests for the `DocTestParser` class. + +DocTestParser is used to parse docstrings containing doctest examples. + +The `parse` method divides a docstring into examples and intervening +text: + + >>> s = ''' + ... >>> x, y = 2, 3 # no output expected + ... >>> if 1: + ... ... print(x) + ... ... print(y) + ... 2 + ... 3 + ... + ... Some text. + ... >>> x+y + ... 5 + ... ''' + >>> parser = doctest.DocTestParser() + >>> for piece in parser.parse(s): + ... if isinstance(piece, doctest.Example): + ... print('Example:', (piece.source, piece.want, piece.lineno)) + ... else: + ... print(' Text:', repr(piece)) + Text: '\n' + Example: ('x, y = 2, 3 # no output expected\n', '', 1) + Text: '' + Example: ('if 1:\n print(x)\n print(y)\n', '2\n3\n', 2) + Text: '\nSome text.\n' + Example: ('x+y\n', '5\n', 9) + Text: '' + +The `get_examples` method returns just the examples: + + >>> for piece in parser.get_examples(s): + ... print((piece.source, piece.want, piece.lineno)) + ('x, y = 2, 3 # no output expected\n', '', 1) + ('if 1:\n print(x)\n print(y)\n', '2\n3\n', 2) + ('x+y\n', '5\n', 9) + +The `get_doctest` method creates a Test from the examples, along with the +given arguments: + + >>> test = parser.get_doctest(s, {}, 'name', 'filename', lineno=5) + >>> (test.name, test.filename, test.lineno) + ('name', 'filename', 5) + >>> for piece in test.examples: + ... print((piece.source, piece.want, piece.lineno)) + ('x, y = 2, 3 # no output expected\n', '', 1) + ('if 1:\n print(x)\n print(y)\n', '2\n3\n', 2) + ('x+y\n', '5\n', 9) +""" + +class test_DocTestRunner: + def basics(): r""" +Unit tests for the `DocTestRunner` class. + +DocTestRunner is used to run DocTest test cases, and to accumulate +statistics. Here's a simple DocTest case we can use: + + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + + >>> def f(x): + ... ''' + ... >>> x = 12 + ... >>> print(x) + ... 12 + ... >>> x//2 + ... 6 + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + +The main DocTestRunner interface is the `run` method, which runs a +given DocTest case in a given namespace (globs). It returns a tuple +`(f,t)`, where `f` is the number of failed tests and `t` is the number +of tried tests. + + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=3) + +If any example produces incorrect output, then the test runner reports +the failure and proceeds to the next example: + + >>> def f(x): + ... ''' + ... >>> x = 12 + ... >>> print(x) + ... 14 + ... >>> x//2 + ... 6 + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=True).run(test) + ... # doctest: +ELLIPSIS + Trying: + x = 12 + Expecting nothing + ok + Trying: + print(x) + Expecting: + 14 + ********************************************************************** + File ..., line 4, in f + Failed example: + print(x) + Expected: + 14 + Got: + 12 + Trying: + x//2 + Expecting: + 6 + ok + TestResults(failed=1, attempted=3) + + >>> _colorize.COLORIZE = save_colorize +""" + def verbose_flag(): r""" +The `verbose` flag makes the test runner generate more detailed +output: + + >>> def f(x): + ... ''' + ... >>> x = 12 + ... >>> print(x) + ... 12 + ... >>> x//2 + ... 6 + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + + >>> doctest.DocTestRunner(verbose=True).run(test) + Trying: + x = 12 + Expecting nothing + ok + Trying: + print(x) + Expecting: + 12 + ok + Trying: + x//2 + Expecting: + 6 + ok + TestResults(failed=0, attempted=3) + +If the `verbose` flag is unspecified, then the output will be verbose +iff `-v` appears in sys.argv: + + >>> # Save the real sys.argv list. + >>> old_argv = sys.argv + + >>> # If -v does not appear in sys.argv, then output isn't verbose. + >>> sys.argv = ['test'] + >>> doctest.DocTestRunner().run(test) + TestResults(failed=0, attempted=3) + + >>> # If -v does appear in sys.argv, then output is verbose. + >>> sys.argv = ['test', '-v'] + >>> doctest.DocTestRunner().run(test) + Trying: + x = 12 + Expecting nothing + ok + Trying: + print(x) + Expecting: + 12 + ok + Trying: + x//2 + Expecting: + 6 + ok + TestResults(failed=0, attempted=3) + + >>> # Restore sys.argv + >>> sys.argv = old_argv + +In the remaining examples, the test runner's verbosity will be +explicitly set, to ensure that the test behavior is consistent. + """ + def exceptions(): r""" +Tests of `DocTestRunner`'s exception handling. + +An expected exception is specified with a traceback message. The +lines between the first line and the type/value may be omitted or +replaced with any other string: + + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + + >>> def f(x): + ... ''' + ... >>> x = 12 + ... >>> print(x//0) + ... Traceback (most recent call last): + ... ZeroDivisionError: integer division or modulo by zero + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=2) + +An example may not generate output before it raises an exception; if +it does, then the traceback message will not be recognized as +signaling an expected exception, so the example will be reported as an +unexpected exception: + + >>> def f(x): + ... ''' + ... >>> x = 12 + ... >>> print('pre-exception output', x//0) + ... pre-exception output + ... Traceback (most recent call last): + ... ZeroDivisionError: integer division or modulo by zero + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 4, in f + Failed example: + print('pre-exception output', x//0) + Exception raised: + ... + ZeroDivisionError: integer division or modulo by zero + TestResults(failed=1, attempted=2) + +Exception messages may contain newlines: + + >>> def f(x): + ... r''' + ... >>> raise ValueError('multi\nline\nmessage') + ... Traceback (most recent call last): + ... ValueError: multi + ... line + ... message + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=1) + +If an exception is expected, but an exception with the wrong type or +message is raised, then it is reported as a failure: + + >>> def f(x): + ... r''' + ... >>> raise ValueError('message') + ... Traceback (most recent call last): + ... ValueError: wrong message + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 3, in f + Failed example: + raise ValueError('message') + Expected: + Traceback (most recent call last): + ValueError: wrong message + Got: + Traceback (most recent call last): + ... + ValueError: message + TestResults(failed=1, attempted=1) + +However, IGNORE_EXCEPTION_DETAIL can be used to allow a mismatch in the +detail: + + >>> def f(x): + ... r''' + ... >>> raise ValueError('message') #doctest: +IGNORE_EXCEPTION_DETAIL + ... Traceback (most recent call last): + ... ValueError: wrong message + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=1) + +IGNORE_EXCEPTION_DETAIL also ignores difference in exception formatting +between Python versions. For example, in Python 2.x, the module path of +the exception is not in the output, but this will fail under Python 3: + + >>> def f(x): + ... r''' + ... >>> from http.client import HTTPException + ... >>> raise HTTPException('message') + ... Traceback (most recent call last): + ... HTTPException: message + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 4, in f + Failed example: + raise HTTPException('message') + Expected: + Traceback (most recent call last): + HTTPException: message + Got: + Traceback (most recent call last): + ... + http.client.HTTPException: message + TestResults(failed=1, attempted=2) + +But in Python 3 the module path is included, and therefore a test must look +like the following test to succeed in Python 3. But that test will fail under +Python 2. + + >>> def f(x): + ... r''' + ... >>> from http.client import HTTPException + ... >>> raise HTTPException('message') + ... Traceback (most recent call last): + ... http.client.HTTPException: message + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=2) + +However, with IGNORE_EXCEPTION_DETAIL, the module name of the exception +(or its unexpected absence) will be ignored: + + >>> def f(x): + ... r''' + ... >>> from http.client import HTTPException + ... >>> raise HTTPException('message') #doctest: +IGNORE_EXCEPTION_DETAIL + ... Traceback (most recent call last): + ... HTTPException: message + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=2) + +The module path will be completely ignored, so two different module paths will +still pass if IGNORE_EXCEPTION_DETAIL is given. This is intentional, so it can +be used when exceptions have changed module. + + >>> def f(x): + ... r''' + ... >>> from http.client import HTTPException + ... >>> raise HTTPException('message') #doctest: +IGNORE_EXCEPTION_DETAIL + ... Traceback (most recent call last): + ... foo.bar.HTTPException: message + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=2) + +But IGNORE_EXCEPTION_DETAIL does not allow a mismatch in the exception type: + + >>> def f(x): + ... r''' + ... >>> raise ValueError('message') #doctest: +IGNORE_EXCEPTION_DETAIL + ... Traceback (most recent call last): + ... TypeError: wrong type + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 3, in f + Failed example: + raise ValueError('message') #doctest: +IGNORE_EXCEPTION_DETAIL + Expected: + Traceback (most recent call last): + TypeError: wrong type + Got: + Traceback (most recent call last): + ... + ValueError: message + TestResults(failed=1, attempted=1) + +If the exception does not have a message, you can still use +IGNORE_EXCEPTION_DETAIL to normalize the modules between Python 2 and 3: + + >>> def f(x): + ... r''' + ... >>> from http.client import HTTPException + ... >>> raise HTTPException() #doctest: +IGNORE_EXCEPTION_DETAIL + ... Traceback (most recent call last): + ... foo.bar.HTTPException + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=2) + +Note that a trailing colon doesn't matter either: + + >>> def f(x): + ... r''' + ... >>> from http.client import HTTPException + ... >>> raise HTTPException() #doctest: +IGNORE_EXCEPTION_DETAIL + ... Traceback (most recent call last): + ... foo.bar.HTTPException: + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=2) + +If an exception is raised but not expected, then it is reported as an +unexpected exception: + + >>> def f(x): + ... r''' + ... >>> 1//0 + ... 0 + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 3, in f + Failed example: + 1//0 + Exception raised: + Traceback (most recent call last): + ... + ZeroDivisionError: integer division or modulo by zero + TestResults(failed=1, attempted=1) + + >>> _colorize.COLORIZE = save_colorize +""" + def displayhook(): r""" +Test that changing sys.displayhook doesn't matter for doctest. + + >>> import sys + >>> orig_displayhook = sys.displayhook + >>> def my_displayhook(x): + ... print('hi!') + >>> sys.displayhook = my_displayhook + >>> def f(): + ... ''' + ... >>> 3 + ... 3 + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> r = doctest.DocTestRunner(verbose=False).run(test) + >>> post_displayhook = sys.displayhook + + We need to restore sys.displayhook now, so that we'll be able to test + results. + + >>> sys.displayhook = orig_displayhook + + Ok, now we can check that everything is ok. + + >>> r + TestResults(failed=0, attempted=1) + >>> post_displayhook is my_displayhook + True +""" + def optionflags(): r""" +Tests of `DocTestRunner`'s option flag handling. + +Several option flags can be used to customize the behavior of the test +runner. These are defined as module constants in doctest, and passed +to the DocTestRunner constructor (multiple constants should be ORed +together). + +The DONT_ACCEPT_TRUE_FOR_1 flag disables matches between True/False +and 1/0: + + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + + >>> def f(x): + ... '>>> True\n1\n' + + >>> # Without the flag: + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=1) + + >>> # With the flag: + >>> test = doctest.DocTestFinder().find(f)[0] + >>> flags = doctest.DONT_ACCEPT_TRUE_FOR_1 + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 2, in f + Failed example: + True + Expected: + 1 + Got: + True + TestResults(failed=1, attempted=1) + +The DONT_ACCEPT_BLANKLINE flag disables the match between blank lines +and the '<BLANKLINE>' marker: + + >>> def f(x): + ... '>>> print("a\\n\\nb")\na\n<BLANKLINE>\nb\n' + + >>> # Without the flag: + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=1) + + >>> # With the flag: + >>> test = doctest.DocTestFinder().find(f)[0] + >>> flags = doctest.DONT_ACCEPT_BLANKLINE + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 2, in f + Failed example: + print("a\n\nb") + Expected: + a + <BLANKLINE> + b + Got: + a + <BLANKLINE> + b + TestResults(failed=1, attempted=1) + +The NORMALIZE_WHITESPACE flag causes all sequences of whitespace to be +treated as equal: + + >>> def f(x): + ... '\n>>> print(1, 2, 3)\n 1 2\n 3' + + >>> # Without the flag: + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 3, in f + Failed example: + print(1, 2, 3) + Expected: + 1 2 + 3 + Got: + 1 2 3 + TestResults(failed=1, attempted=1) + + >>> # With the flag: + >>> test = doctest.DocTestFinder().find(f)[0] + >>> flags = doctest.NORMALIZE_WHITESPACE + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + TestResults(failed=0, attempted=1) + + An example from the docs: + >>> print(list(range(20))) #doctest: +NORMALIZE_WHITESPACE + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] + +The ELLIPSIS flag causes ellipsis marker ("...") in the expected +output to match any substring in the actual output: + + >>> def f(x): + ... '>>> print(list(range(15)))\n[0, 1, 2, ..., 14]\n' + + >>> # Without the flag: + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 2, in f + Failed example: + print(list(range(15))) + Expected: + [0, 1, 2, ..., 14] + Got: + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] + TestResults(failed=1, attempted=1) + + >>> # With the flag: + >>> test = doctest.DocTestFinder().find(f)[0] + >>> flags = doctest.ELLIPSIS + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + TestResults(failed=0, attempted=1) + + ... also matches nothing: + + >>> if 1: + ... for i in range(100): + ... print(i**2, end=' ') #doctest: +ELLIPSIS + ... print('!') + 0 1...4...9 16 ... 36 49 64 ... 9801 ! + + ... can be surprising; e.g., this test passes: + + >>> if 1: #doctest: +ELLIPSIS + ... for i in range(20): + ... print(i, end=' ') + ... print(20) + 0 1 2 ...1...2...0 + + Examples from the docs: + + >>> print(list(range(20))) # doctest:+ELLIPSIS + [0, 1, ..., 18, 19] + + >>> print(list(range(20))) # doctest: +ELLIPSIS + ... # doctest: +NORMALIZE_WHITESPACE + [0, 1, ..., 18, 19] + +The SKIP flag causes an example to be skipped entirely. I.e., the +example is not run. It can be useful in contexts where doctest +examples serve as both documentation and test cases, and an example +should be included for documentation purposes, but should not be +checked (e.g., because its output is random, or depends on resources +which would be unavailable.) The SKIP flag can also be used for +'commenting out' broken examples. + + >>> import unavailable_resource # doctest: +SKIP + >>> unavailable_resource.do_something() # doctest: +SKIP + >>> unavailable_resource.blow_up() # doctest: +SKIP + Traceback (most recent call last): + ... + UncheckedBlowUpError: Nobody checks me. + + >>> import random + >>> print(random.random()) # doctest: +SKIP + 0.721216923889 + +The REPORT_UDIFF flag causes failures that involve multi-line expected +and actual outputs to be displayed using a unified diff: + + >>> def f(x): + ... r''' + ... >>> print('\n'.join('abcdefg')) + ... a + ... B + ... c + ... d + ... f + ... g + ... h + ... ''' + + >>> # Without the flag: + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 3, in f + Failed example: + print('\n'.join('abcdefg')) + Expected: + a + B + c + d + f + g + h + Got: + a + b + c + d + e + f + g + TestResults(failed=1, attempted=1) + + >>> # With the flag: + >>> test = doctest.DocTestFinder().find(f)[0] + >>> flags = doctest.REPORT_UDIFF + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 3, in f + Failed example: + print('\n'.join('abcdefg')) + Differences (unified diff with -expected +actual): + @@ -1,7 +1,7 @@ + a + -B + +b + c + d + +e + f + g + -h + TestResults(failed=1, attempted=1) + +The REPORT_CDIFF flag causes failures that involve multi-line expected +and actual outputs to be displayed using a context diff: + + >>> # Reuse f() from the REPORT_UDIFF example, above. + >>> test = doctest.DocTestFinder().find(f)[0] + >>> flags = doctest.REPORT_CDIFF + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 3, in f + Failed example: + print('\n'.join('abcdefg')) + Differences (context diff with expected followed by actual): + *************** + *** 1,7 **** + a + ! B + c + d + f + g + - h + --- 1,7 ---- + a + ! b + c + d + + e + f + g + TestResults(failed=1, attempted=1) + + +The REPORT_NDIFF flag causes failures to use the difflib.Differ algorithm +used by the popular ndiff.py utility. This does intraline difference +marking, as well as interline differences. + + >>> def f(x): + ... r''' + ... >>> print("a b c d e f g h i j k l m") + ... a b c d e f g h i j k 1 m + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> flags = doctest.REPORT_NDIFF + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 3, in f + Failed example: + print("a b c d e f g h i j k l m") + Differences (ndiff with -expected +actual): + - a b c d e f g h i j k 1 m + ? ^ + + a b c d e f g h i j k l m + ? + ++ ^ + TestResults(failed=1, attempted=1) + +The REPORT_ONLY_FIRST_FAILURE suppresses result output after the first +failing example: + + >>> def f(x): + ... r''' + ... >>> print(1) # first success + ... 1 + ... >>> print(2) # first failure + ... 200 + ... >>> print(3) # second failure + ... 300 + ... >>> print(4) # second success + ... 4 + ... >>> print(5) # third failure + ... 500 + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> flags = doctest.REPORT_ONLY_FIRST_FAILURE + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 5, in f + Failed example: + print(2) # first failure + Expected: + 200 + Got: + 2 + TestResults(failed=3, attempted=5) + +However, output from `report_start` is not suppressed: + + >>> doctest.DocTestRunner(verbose=True, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + Trying: + print(1) # first success + Expecting: + 1 + ok + Trying: + print(2) # first failure + Expecting: + 200 + ********************************************************************** + File ..., line 5, in f + Failed example: + print(2) # first failure + Expected: + 200 + Got: + 2 + TestResults(failed=3, attempted=5) + +The FAIL_FAST flag causes the runner to exit after the first failing example, +so subsequent examples are not even attempted: + + >>> flags = doctest.FAIL_FAST + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 5, in f + Failed example: + print(2) # first failure + Expected: + 200 + Got: + 2 + TestResults(failed=1, attempted=2) + +Specifying both FAIL_FAST and REPORT_ONLY_FIRST_FAILURE is equivalent to +FAIL_FAST only: + + >>> flags = doctest.FAIL_FAST | doctest.REPORT_ONLY_FIRST_FAILURE + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 5, in f + Failed example: + print(2) # first failure + Expected: + 200 + Got: + 2 + TestResults(failed=1, attempted=2) + +For the purposes of both REPORT_ONLY_FIRST_FAILURE and FAIL_FAST, unexpected +exceptions count as failures: + + >>> def f(x): + ... r''' + ... >>> print(1) # first success + ... 1 + ... >>> raise ValueError(2) # first failure + ... 200 + ... >>> print(3) # second failure + ... 300 + ... >>> print(4) # second success + ... 4 + ... >>> print(5) # third failure + ... 500 + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> flags = doctest.REPORT_ONLY_FIRST_FAILURE + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 5, in f + Failed example: + raise ValueError(2) # first failure + Exception raised: + ... + ValueError: 2 + TestResults(failed=3, attempted=5) + >>> flags = doctest.FAIL_FAST + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 5, in f + Failed example: + raise ValueError(2) # first failure + Exception raised: + ... + ValueError: 2 + TestResults(failed=1, attempted=2) + +New option flags can also be registered, via register_optionflag(). Here +we reach into doctest's internals a bit. + + >>> unlikely = "UNLIKELY_OPTION_NAME" + >>> unlikely in doctest.OPTIONFLAGS_BY_NAME + False + >>> new_flag_value = doctest.register_optionflag(unlikely) + >>> unlikely in doctest.OPTIONFLAGS_BY_NAME + True + +Before 2.4.4/2.5, registering a name more than once erroneously created +more than one flag value. Here we verify that's fixed: + + >>> redundant_flag_value = doctest.register_optionflag(unlikely) + >>> redundant_flag_value == new_flag_value + True + +Clean up. + >>> del doctest.OPTIONFLAGS_BY_NAME[unlikely] + >>> _colorize.COLORIZE = save_colorize + + """ + + def option_directives(): r""" +Tests of `DocTestRunner`'s option directive mechanism. + +Option directives can be used to turn option flags on or off for a +single example. To turn an option on for an example, follow that +example with a comment of the form ``# doctest: +OPTION``: + + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + + >>> def f(x): r''' + ... >>> print(list(range(10))) # should fail: no ellipsis + ... [0, 1, ..., 9] + ... + ... >>> print(list(range(10))) # doctest: +ELLIPSIS + ... [0, 1, ..., 9] + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 2, in f + Failed example: + print(list(range(10))) # should fail: no ellipsis + Expected: + [0, 1, ..., 9] + Got: + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + TestResults(failed=1, attempted=2) + +To turn an option off for an example, follow that example with a +comment of the form ``# doctest: -OPTION``: + + >>> def f(x): r''' + ... >>> print(list(range(10))) + ... [0, 1, ..., 9] + ... + ... >>> # should fail: no ellipsis + ... >>> print(list(range(10))) # doctest: -ELLIPSIS + ... [0, 1, ..., 9] + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False, + ... optionflags=doctest.ELLIPSIS).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 6, in f + Failed example: + print(list(range(10))) # doctest: -ELLIPSIS + Expected: + [0, 1, ..., 9] + Got: + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + TestResults(failed=1, attempted=2) + +Option directives affect only the example that they appear with; they +do not change the options for surrounding examples: + + >>> def f(x): r''' + ... >>> print(list(range(10))) # Should fail: no ellipsis + ... [0, 1, ..., 9] + ... + ... >>> print(list(range(10))) # doctest: +ELLIPSIS + ... [0, 1, ..., 9] + ... + ... >>> print(list(range(10))) # Should fail: no ellipsis + ... [0, 1, ..., 9] + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 2, in f + Failed example: + print(list(range(10))) # Should fail: no ellipsis + Expected: + [0, 1, ..., 9] + Got: + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + ********************************************************************** + File ..., line 8, in f + Failed example: + print(list(range(10))) # Should fail: no ellipsis + Expected: + [0, 1, ..., 9] + Got: + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + TestResults(failed=2, attempted=3) + +Multiple options may be modified by a single option directive. They +may be separated by whitespace, commas, or both: + + >>> def f(x): r''' + ... >>> print(list(range(10))) # Should fail + ... [0, 1, ..., 9] + ... >>> print(list(range(10))) # Should succeed + ... ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + ... [0, 1, ..., 9] + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 2, in f + Failed example: + print(list(range(10))) # Should fail + Expected: + [0, 1, ..., 9] + Got: + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + TestResults(failed=1, attempted=2) + + >>> def f(x): r''' + ... >>> print(list(range(10))) # Should fail + ... [0, 1, ..., 9] + ... >>> print(list(range(10))) # Should succeed + ... ... # doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE + ... [0, 1, ..., 9] + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 2, in f + Failed example: + print(list(range(10))) # Should fail + Expected: + [0, 1, ..., 9] + Got: + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + TestResults(failed=1, attempted=2) + + >>> def f(x): r''' + ... >>> print(list(range(10))) # Should fail + ... [0, 1, ..., 9] + ... >>> print(list(range(10))) # Should succeed + ... ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + ... [0, 1, ..., 9] + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 2, in f + Failed example: + print(list(range(10))) # Should fail + Expected: + [0, 1, ..., 9] + Got: + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + TestResults(failed=1, attempted=2) + +The option directive may be put on the line following the source, as +long as a continuation prompt is used: + + >>> def f(x): r''' + ... >>> print(list(range(10))) + ... ... # doctest: +ELLIPSIS + ... [0, 1, ..., 9] + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=1) + +For examples with multi-line source, the option directive may appear +at the end of any line: + + >>> def f(x): r''' + ... >>> for x in range(10): # doctest: +ELLIPSIS + ... ... print(' ', x, end='', sep='') + ... 0 1 2 ... 9 + ... + ... >>> for x in range(10): + ... ... print(' ', x, end='', sep='') # doctest: +ELLIPSIS + ... 0 1 2 ... 9 + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=2) + +If more than one line of an example with multi-line source has an +option directive, then they are combined: + + >>> def f(x): r''' + ... Should fail (option directive not on the last line): + ... >>> for x in range(10): # doctest: +ELLIPSIS + ... ... print(x, end=' ') # doctest: +NORMALIZE_WHITESPACE + ... 0 1 2...9 + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=1) + +It is an error to have a comment of the form ``# doctest:`` that is +*not* followed by words of the form ``+OPTION`` or ``-OPTION``, where +``OPTION`` is an option that has been registered with +`register_option`: + + >>> # Error: Option not registered + >>> s = '>>> print(12) #doctest: +BADOPTION' + >>> test = doctest.DocTestParser().get_doctest(s, {}, 's', 's.py', 0) + Traceback (most recent call last): + ValueError: line 1 of the doctest for s has an invalid option: '+BADOPTION' + + >>> # Error: No + or - prefix + >>> s = '>>> print(12) #doctest: ELLIPSIS' + >>> test = doctest.DocTestParser().get_doctest(s, {}, 's', 's.py', 0) + Traceback (most recent call last): + ValueError: line 1 of the doctest for s has an invalid option: 'ELLIPSIS' + +It is an error to use an option directive on a line that contains no +source: + + >>> s = '>>> # doctest: +ELLIPSIS' + >>> test = doctest.DocTestParser().get_doctest(s, {}, 's', 's.py', 0) + Traceback (most recent call last): + ValueError: line 0 of the doctest for s has an option directive on a line with no example: '# doctest: +ELLIPSIS' + + >>> _colorize.COLORIZE = save_colorize +""" + +def test_testsource(): r""" +Unit tests for `testsource()`. + +The testsource() function takes a module and a name, finds the (first) +test with that name in that module, and converts it to a script. The +example code is converted to regular Python code. The surrounding +words and expected output are converted to comments: + + >>> from test.test_doctest import test_doctest + >>> name = 'test.test_doctest.test_doctest.sample_func' + >>> print(doctest.testsource(test_doctest, name)) + # Blah blah + # + print(sample_func(22)) + # Expected: + ## 44 + # + # Yee ha! + <BLANKLINE> + + >>> name = 'test.test_doctest.test_doctest.SampleNewStyleClass' + >>> print(doctest.testsource(test_doctest, name)) + print('1\n2\n3') + # Expected: + ## 1 + ## 2 + ## 3 + <BLANKLINE> + + >>> name = 'test.test_doctest.test_doctest.SampleClass.a_classmethod' + >>> print(doctest.testsource(test_doctest, name)) + print(SampleClass.a_classmethod(10)) + # Expected: + ## 12 + print(SampleClass(0).a_classmethod(10)) + # Expected: + ## 12 + <BLANKLINE> +""" + +# TODO: RUSTPYTHON +# Issue with pdb +# def test_debug(): r""" + +# Create a docstring that we want to debug: + +# >>> s = ''' +# ... >>> x = 12 +# ... >>> print(x) +# ... 12 +# ... ''' + +# Create some fake stdin input, to feed to the debugger: + +# >>> real_stdin = sys.stdin +# >>> sys.stdin = FakeInput(['next', 'print(x)', 'continue']) + +# Run the debugger on the docstring, and then restore sys.stdin. + +# >>> try: doctest.debug_src(s) +# ... finally: sys.stdin = real_stdin +# > <string>(1)<module>() +# (Pdb) next +# 12 +# --Return-- +# > <string>(1)<module>()->None +# (Pdb) print(x) +# 12 +# (Pdb) continue + +# """ + +# TODO: RUSTPYTHON +# Issue with pdb +# AttributeError: 'frame' object has no attribute 'f_trace_opcodes'. Did you mean: 'f_trace_lines'? +# if not hasattr(sys, 'gettrace') or not sys.gettrace(): +# def test_pdb_set_trace(): +# """Using pdb.set_trace from a doctest. + +# You can use pdb.set_trace from a doctest. To do so, you must +# retrieve the set_trace function from the pdb module at the time +# you use it. The doctest module changes sys.stdout so that it can +# capture program output. It also temporarily replaces pdb.set_trace +# with a version that restores stdout. This is necessary for you to +# see debugger output. + +# >>> save_colorize = _colorize.COLORIZE +# >>> _colorize.COLORIZE = False + +# >>> doc = ''' +# ... >>> x = 42 +# ... >>> raise Exception('clé') +# ... Traceback (most recent call last): +# ... Exception: clé +# ... >>> import pdb; pdb.set_trace() +# ... ''' +# >>> parser = doctest.DocTestParser() +# >>> test = parser.get_doctest(doc, {}, "foo-bar@baz", "foo-bar@baz.py", 0) +# >>> runner = doctest.DocTestRunner(verbose=False) + +# To demonstrate this, we'll create a fake standard input that +# captures our debugger input: + +# >>> real_stdin = sys.stdin +# >>> sys.stdin = FakeInput([ +# ... 'print(x)', # print data defined by the example +# ... 'continue', # stop debugging +# ... '']) + +# >>> try: runner.run(test) +# ... finally: sys.stdin = real_stdin +# > <doctest foo-bar@baz[2]>(1)<module>() +# -> import pdb; pdb.set_trace() +# (Pdb) print(x) +# 42 +# (Pdb) continue +# TestResults(failed=0, attempted=3) + +# You can also put pdb.set_trace in a function called from a test: + +# >>> def calls_set_trace(): +# ... y=2 +# ... import pdb; pdb.set_trace() + +# >>> doc = ''' +# ... >>> x=1 +# ... >>> calls_set_trace() +# ... ''' +# >>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0) +# >>> real_stdin = sys.stdin +# >>> sys.stdin = FakeInput([ +# ... 'print(y)', # print data defined in the function +# ... 'up', # out of function +# ... 'print(x)', # print data defined by the example +# ... 'continue', # stop debugging +# ... '']) + +# >>> try: +# ... runner.run(test) +# ... finally: +# ... sys.stdin = real_stdin +# > <doctest test.test_doctest.test_doctest.test_pdb_set_trace[9]>(3)calls_set_trace() +# -> import pdb; pdb.set_trace() +# (Pdb) print(y) +# 2 +# (Pdb) up +# > <doctest foo-bar@baz[1]>(1)<module>() +# -> calls_set_trace() +# (Pdb) print(x) +# 1 +# (Pdb) continue +# TestResults(failed=0, attempted=2) + +# During interactive debugging, source code is shown, even for +# doctest examples: + +# >>> doc = ''' +# ... >>> def f(x): +# ... ... g(x*2) +# ... >>> def g(x): +# ... ... print(x+3) +# ... ... import pdb; pdb.set_trace() +# ... >>> f(3) +# ... ''' +# >>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0) +# >>> real_stdin = sys.stdin +# >>> sys.stdin = FakeInput([ +# ... 'step', # return event of g +# ... 'list', # list source from example 2 +# ... 'next', # return from g() +# ... 'list', # list source from example 1 +# ... 'next', # return from f() +# ... 'list', # list source from example 3 +# ... 'continue', # stop debugging +# ... '']) +# >>> try: runner.run(test) +# ... finally: sys.stdin = real_stdin +# ... # doctest: +NORMALIZE_WHITESPACE +# > <doctest foo-bar@baz[1]>(3)g() +# -> import pdb; pdb.set_trace() +# (Pdb) step +# --Return-- +# > <doctest foo-bar@baz[1]>(3)g()->None +# -> import pdb; pdb.set_trace() +# (Pdb) list +# 1 def g(x): +# 2 print(x+3) +# 3 -> import pdb; pdb.set_trace() +# [EOF] +# (Pdb) next +# --Return-- +# > <doctest foo-bar@baz[0]>(2)f()->None +# -> g(x*2) +# (Pdb) list +# 1 def f(x): +# 2 -> g(x*2) +# [EOF] +# (Pdb) next +# --Return-- +# > <doctest foo-bar@baz[2]>(1)<module>()->None +# -> f(3) +# (Pdb) list +# 1 -> f(3) +# [EOF] +# (Pdb) continue +# ********************************************************************** +# File "foo-bar@baz.py", line 7, in foo-bar@baz +# Failed example: +# f(3) +# Expected nothing +# Got: +# 9 +# TestResults(failed=1, attempted=3) + +# >>> _colorize.COLORIZE = save_colorize +# """ + + # TODO: RUSTPYTHON + # Issue with pdb + # AttributeError: 'frame' object has no attribute 'f_trace_opcodes'. Did you mean: 'f_trace_lines'? + # def test_pdb_set_trace_nested(): + # """This illustrates more-demanding use of set_trace with nested functions. + + # >>> class C(object): + # ... def calls_set_trace(self): + # ... y = 1 + # ... import pdb; pdb.set_trace() + # ... self.f1() + # ... y = 2 + # ... def f1(self): + # ... x = 1 + # ... self.f2() + # ... x = 2 + # ... def f2(self): + # ... z = 1 + # ... z = 2 + + # >>> calls_set_trace = C().calls_set_trace + + # >>> doc = ''' + # ... >>> a = 1 + # ... >>> calls_set_trace() + # ... ''' + # >>> parser = doctest.DocTestParser() + # >>> runner = doctest.DocTestRunner(verbose=False) + # >>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0) + # >>> real_stdin = sys.stdin + # >>> sys.stdin = FakeInput([ + # ... 'step', + # ... 'print(y)', # print data defined in the function + # ... 'step', 'step', 'step', 'step', 'step', 'step', 'print(z)', + # ... 'up', 'print(x)', + # ... 'up', 'print(y)', + # ... 'up', 'print(foo)', + # ... 'continue', # stop debugging + # ... '']) + + # >>> try: + # ... runner.run(test) + # ... finally: + # ... sys.stdin = real_stdin + # ... # doctest: +REPORT_NDIFF + # > <doctest test.test_doctest.test_doctest.test_pdb_set_trace_nested[0]>(4)calls_set_trace() + # -> import pdb; pdb.set_trace() + # (Pdb) step + # > <doctest test.test_doctest.test_doctest.test_pdb_set_trace_nested[0]>(5)calls_set_trace() + # -> self.f1() + # (Pdb) print(y) + # 1 + # (Pdb) step + # --Call-- + # > <doctest test.test_doctest.test_doctest.test_pdb_set_trace_nested[0]>(7)f1() + # -> def f1(self): + # (Pdb) step + # > <doctest test.test_doctest.test_doctest.test_pdb_set_trace_nested[0]>(8)f1() + # -> x = 1 + # (Pdb) step + # > <doctest test.test_doctest.test_doctest.test_pdb_set_trace_nested[0]>(9)f1() + # -> self.f2() + # (Pdb) step + # --Call-- + # > <doctest test.test_doctest.test_doctest.test_pdb_set_trace_nested[0]>(11)f2() + # -> def f2(self): + # (Pdb) step + # > <doctest test.test_doctest.test_doctest.test_pdb_set_trace_nested[0]>(12)f2() + # -> z = 1 + # (Pdb) step + # > <doctest test.test_doctest.test_doctest.test_pdb_set_trace_nested[0]>(13)f2() + # -> z = 2 + # (Pdb) print(z) + # 1 + # (Pdb) up + # > <doctest test.test_doctest.test_doctest.test_pdb_set_trace_nested[0]>(9)f1() + # -> self.f2() + # (Pdb) print(x) + # 1 + # (Pdb) up + # > <doctest test.test_doctest.test_doctest.test_pdb_set_trace_nested[0]>(5)calls_set_trace() + # -> self.f1() + # (Pdb) print(y) + # 1 + # (Pdb) up + # > <doctest foo-bar@baz[1]>(1)<module>() + # -> calls_set_trace() + # (Pdb) print(foo) + # *** NameError: name 'foo' is not defined + # (Pdb) continue + # TestResults(failed=0, attempted=2) + # """ + +# TODO: RUSTPYTHON +# Issue with pdb +# AttributeError: 'frame' object has no attribute 'f_trace_opcodes'. Did you mean: 'f_trace_lines'? +# def test_DocTestSuite(): +# """DocTestSuite creates a unittest test suite from a doctest. + +# We create a Suite by providing a module. A module can be provided +# by passing a module object: + +# >>> import unittest +# >>> import test.test_doctest.sample_doctest +# >>> suite = doctest.DocTestSuite(test.test_doctest.sample_doctest) +# >>> result = suite.run(unittest.TestResult()) +# >>> result +# <unittest.result.TestResult run=9 errors=0 failures=4> +# >>> for tst, _ in result.failures: +# ... print(tst) +# bad (test.test_doctest.sample_doctest.__test__) +# foo (test.test_doctest.sample_doctest) +# test_silly_setup (test.test_doctest.sample_doctest) +# y_is_one (test.test_doctest.sample_doctest) + +# We can also supply the module by name: + +# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest') +# >>> result = suite.run(unittest.TestResult()) +# >>> result +# <unittest.result.TestResult run=9 errors=0 failures=4> + +# The module need not contain any doctest examples: + +# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_no_doctests') +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=0 errors=0 failures=0> + +# The module need not contain any docstrings either: + +# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_no_docstrings') +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=0 errors=0 failures=0> + +# If all examples in a docstring are skipped, unittest will report it as a +# skipped test: + +# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_skip') +# >>> result = suite.run(unittest.TestResult()) +# >>> result +# <unittest.result.TestResult run=6 errors=0 failures=2> +# >>> len(result.skipped) +# 2 +# >>> for tst, _ in result.skipped: +# ... print(tst) +# double_skip (test.test_doctest.sample_doctest_skip) +# single_skip (test.test_doctest.sample_doctest_skip) +# >>> for tst, _ in result.failures: +# ... print(tst) +# no_skip_fail (test.test_doctest.sample_doctest_skip) +# partial_skip_fail (test.test_doctest.sample_doctest_skip) + +# We can use the current module: + +# >>> suite = test.test_doctest.sample_doctest.test_suite() +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=9 errors=0 failures=4> + +# We can also provide a DocTestFinder: + +# >>> finder = doctest.DocTestFinder() +# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', +# ... test_finder=finder) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=9 errors=0 failures=4> + +# The DocTestFinder need not return any tests: + +# >>> finder = doctest.DocTestFinder() +# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_no_docstrings', +# ... test_finder=finder) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=0 errors=0 failures=0> + +# We can supply global variables. If we pass globs, they will be +# used instead of the module globals. Here we'll pass an empty +# globals, triggering an extra error: + +# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', globs={}) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=9 errors=0 failures=5> + +# Alternatively, we can provide extra globals. Here we'll make an +# error go away by providing an extra global variable: + +# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', +# ... extraglobs={'y': 1}) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=9 errors=0 failures=3> + +# You can pass option flags. Here we'll cause an extra error +# by disabling the blank-line feature: + +# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', +# ... optionflags=doctest.DONT_ACCEPT_BLANKLINE) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=9 errors=0 failures=5> + +# You can supply setUp and tearDown functions: + +# >>> def setUp(t): +# ... from test.test_doctest import test_doctest +# ... test_doctest.sillySetup = True + +# >>> def tearDown(t): +# ... from test.test_doctest import test_doctest +# ... del test_doctest.sillySetup + +# Here, we installed a silly variable that the test expects: + +# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', +# ... setUp=setUp, tearDown=tearDown) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=9 errors=0 failures=3> + +# But the tearDown restores sanity: + +# >>> from test.test_doctest import test_doctest +# >>> test_doctest.sillySetup +# Traceback (most recent call last): +# ... +# AttributeError: module 'test.test_doctest.test_doctest' has no attribute 'sillySetup' + +# The setUp and tearDown functions are passed test objects. Here +# we'll use the setUp function to supply the missing variable y: + +# >>> def setUp(test): +# ... test.globs['y'] = 1 + +# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', setUp=setUp) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=9 errors=0 failures=3> + +# Here, we didn't need to use a tearDown function because we +# modified the test globals, which are a copy of the +# sample_doctest module dictionary. The test globals are +# automatically cleared for us after a test. +# """ + +# TODO: RUSTPYTHON +# traceback + error message is different than in CPython +# def test_DocTestSuite_errors(): +# """Tests for error reporting in DocTestSuite. + +# >>> import unittest +# >>> import test.test_doctest.sample_doctest_errors as mod +# >>> suite = doctest.DocTestSuite(mod) +# >>> result = suite.run(unittest.TestResult()) +# >>> result +# <unittest.result.TestResult run=4 errors=0 failures=4> +# >>> print(result.failures[0][1]) # doctest: +ELLIPSIS +# Traceback (most recent call last): +# File ... +# raise self.failureException(self.format_failure(new.getvalue())) +# AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors +# File "...sample_doctest_errors.py", line 0, in sample_doctest_errors +# <BLANKLINE> +# ---------------------------------------------------------------------- +# File "...sample_doctest_errors.py", line 5, in test.test_doctest.sample_doctest_errors +# Failed example: +# 2 + 2 +# Expected: +# 5 +# Got: +# 4 +# ---------------------------------------------------------------------- +# File "...sample_doctest_errors.py", line 7, in test.test_doctest.sample_doctest_errors +# Failed example: +# 1/0 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors[1]>", line 1, in <module> +# 1/0 +# ~^~ +# ZeroDivisionError: division by zero +# <BLANKLINE> +# <BLANKLINE> +# >>> print(result.failures[1][1]) # doctest: +ELLIPSIS +# Traceback (most recent call last): +# File ... +# raise self.failureException(self.format_failure(new.getvalue())) +# AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.__test__.bad +# File "...sample_doctest_errors.py", line unknown line number, in bad +# <BLANKLINE> +# ---------------------------------------------------------------------- +# File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad +# Failed example: +# 2 + 2 +# Expected: +# 5 +# Got: +# 4 +# ---------------------------------------------------------------------- +# File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad +# Failed example: +# 1/0 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors.__test__.bad[1]>", line 1, in <module> +# 1/0 +# ~^~ +# ZeroDivisionError: division by zero +# <BLANKLINE> +# <BLANKLINE> +# >>> print(result.failures[2][1]) # doctest: +ELLIPSIS +# Traceback (most recent call last): +# File ... +# raise self.failureException(self.format_failure(new.getvalue())) +# AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.errors +# File "...sample_doctest_errors.py", line 14, in errors +# <BLANKLINE> +# ---------------------------------------------------------------------- +# File "...sample_doctest_errors.py", line 16, in test.test_doctest.sample_doctest_errors.errors +# Failed example: +# 2 + 2 +# Expected: +# 5 +# Got: +# 4 +# ---------------------------------------------------------------------- +# File "...sample_doctest_errors.py", line 18, in test.test_doctest.sample_doctest_errors.errors +# Failed example: +# 1/0 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors.errors[1]>", line 1, in <module> +# 1/0 +# ~^~ +# ZeroDivisionError: division by zero +# ---------------------------------------------------------------------- +# File "...sample_doctest_errors.py", line 23, in test.test_doctest.sample_doctest_errors.errors +# Failed example: +# f() +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors.errors[3]>", line 1, in <module> +# f() +# ~^^ +# File "<doctest test.test_doctest.sample_doctest_errors.errors[2]>", line 2, in f +# 2 + '2' +# ~~^~~~~ +# TypeError: ... +# ---------------------------------------------------------------------- +# File "...sample_doctest_errors.py", line 25, in test.test_doctest.sample_doctest_errors.errors +# Failed example: +# g() +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors.errors[4]>", line 1, in <module> +# g() +# ~^^ +# File "...sample_doctest_errors.py", line 12, in g +# [][0] # line 12 +# ~~^^^ +# IndexError: list index out of range +# <BLANKLINE> +# <BLANKLINE> +# >>> print(result.failures[3][1]) # doctest: +ELLIPSIS +# Traceback (most recent call last): +# File ... +# raise self.failureException(self.format_failure(new.getvalue())) +# AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.syntax_error +# File "...sample_doctest_errors.py", line 29, in syntax_error +# <BLANKLINE> +# ---------------------------------------------------------------------- +# File "...sample_doctest_errors.py", line 31, in test.test_doctest.sample_doctest_errors.syntax_error +# Failed example: +# 2+*3 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors.syntax_error[0]>", line 1 +# 2+*3 +# ^ +# SyntaxError: invalid syntax +# <BLANKLINE> +# <BLANKLINE> +# """ + +# TODO: RUSTPYTHON +# try finally does not work in the interactive shell +# def test_DocFileSuite(): +# """We can test tests found in text files using a DocFileSuite. + +# We create a suite by providing the names of one or more text +# files that include examples: + +# >>> import unittest +# >>> suite = doctest.DocFileSuite('test_doctest.txt', +# ... 'test_doctest2.txt', +# ... 'test_doctest4.txt') +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=3 errors=0 failures=2> + +# The test files are looked for in the directory containing the +# calling module. A package keyword argument can be provided to +# specify a different relative location. + +# >>> import unittest +# >>> suite = doctest.DocFileSuite('test_doctest.txt', +# ... 'test_doctest2.txt', +# ... 'test_doctest4.txt', +# ... package='test.test_doctest') +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=3 errors=0 failures=2> + +# Support for using a package's __loader__.get_data() is also +# provided. + +# >>> import unittest, pkgutil, test +# >>> added_loader = False +# >>> if not hasattr(test, '__loader__'): +# ... test.__loader__ = pkgutil.get_loader(test) +# ... added_loader = True +# >>> try: +# ... suite = doctest.DocFileSuite('test_doctest.txt', +# ... 'test_doctest2.txt', +# ... 'test_doctest4.txt', +# ... package='test.test_doctest') +# ... suite.run(unittest.TestResult()) +# ... finally: +# ... if added_loader: +# ... del test.__loader__ +# <unittest.result.TestResult run=3 errors=0 failures=2> + +# '/' should be used as a path separator. It will be converted +# to a native separator at run time: + +# >>> suite = doctest.DocFileSuite('../test_doctest/test_doctest.txt') +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=1 errors=0 failures=1> + +# If DocFileSuite is used from an interactive session, then files +# are resolved relative to the directory of sys.argv[0]: + +# >>> import types, os.path +# >>> from test.test_doctest import test_doctest +# >>> save_argv = sys.argv +# >>> sys.argv = [test_doctest.__file__] +# >>> suite = doctest.DocFileSuite('test_doctest.txt', +# ... package=types.ModuleType('__main__')) +# >>> sys.argv = save_argv + +# By setting `module_relative=False`, os-specific paths may be +# used (including absolute paths and paths relative to the +# working directory): + +# >>> # Get the absolute path of the test package. +# >>> test_doctest_path = os.path.abspath(test_doctest.__file__) +# >>> test_pkg_path = os.path.split(test_doctest_path)[0] + +# >>> # Use it to find the absolute path of test_doctest.txt. +# >>> test_file = os.path.join(test_pkg_path, 'test_doctest.txt') + +# >>> suite = doctest.DocFileSuite(test_file, module_relative=False) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=1 errors=0 failures=1> + +# It is an error to specify `package` when `module_relative=False`: + +# >>> suite = doctest.DocFileSuite(test_file, module_relative=False, +# ... package='test') +# Traceback (most recent call last): +# ValueError: Package may only be specified for module-relative paths. + +# If all examples in a file are skipped, unittest will report it as a +# skipped test: + +# >>> suite = doctest.DocFileSuite('test_doctest.txt', +# ... 'test_doctest4.txt', +# ... 'test_doctest_skip.txt', +# ... 'test_doctest_skip2.txt') +# >>> result = suite.run(unittest.TestResult()) +# >>> result +# <unittest.result.TestResult run=4 errors=0 failures=1> +# >>> len(result.skipped) +# 1 +# >>> for tst, _ in result.skipped: # doctest: +ELLIPSIS +# ... print('=', tst) +# = ...test_doctest_skip.txt + +# You can specify initial global variables: + +# >>> suite = doctest.DocFileSuite('test_doctest.txt', +# ... 'test_doctest2.txt', +# ... 'test_doctest4.txt', +# ... globs={'favorite_color': 'blue'}) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=3 errors=0 failures=1> + +# In this case, we supplied a missing favorite color. You can +# provide doctest options: + +# >>> suite = doctest.DocFileSuite('test_doctest.txt', +# ... 'test_doctest2.txt', +# ... 'test_doctest4.txt', +# ... optionflags=doctest.DONT_ACCEPT_BLANKLINE, +# ... globs={'favorite_color': 'blue'}) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=3 errors=0 failures=2> + +# And, you can provide setUp and tearDown functions: + +# >>> def setUp(t): +# ... from test.test_doctest import test_doctest +# ... test_doctest.sillySetup = True + +# >>> def tearDown(t): +# ... from test.test_doctest import test_doctest +# ... del test_doctest.sillySetup + +# Here, we installed a silly variable that the test expects: + +# >>> suite = doctest.DocFileSuite('test_doctest.txt', +# ... 'test_doctest2.txt', +# ... 'test_doctest4.txt', +# ... setUp=setUp, tearDown=tearDown) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=3 errors=0 failures=1> + +# But the tearDown restores sanity: + +# >>> from test.test_doctest import test_doctest +# >>> test_doctest.sillySetup +# Traceback (most recent call last): +# ... +# AttributeError: module 'test.test_doctest.test_doctest' has no attribute 'sillySetup' + +# The setUp and tearDown functions are passed test objects. +# Here, we'll use a setUp function to set the favorite color in +# test_doctest.txt: + +# >>> def setUp(test): +# ... test.globs['favorite_color'] = 'blue' + +# >>> suite = doctest.DocFileSuite('test_doctest.txt', setUp=setUp) +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=1 errors=0 failures=0> + +# Here, we didn't need to use a tearDown function because we +# modified the test globals. The test globals are +# automatically cleared for us after a test. + +# Tests in a file run using `DocFileSuite` can also access the +# `__file__` global, which is set to the name of the file +# containing the tests: + +# >>> suite = doctest.DocFileSuite('test_doctest3.txt') +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=1 errors=0 failures=0> + +# If the tests contain non-ASCII characters, we have to specify which +# encoding the file is encoded with. We do so by using the `encoding` +# parameter: + +# >>> suite = doctest.DocFileSuite('test_doctest.txt', +# ... 'test_doctest2.txt', +# ... 'test_doctest4.txt', +# ... encoding='utf-8') +# >>> suite.run(unittest.TestResult()) +# <unittest.result.TestResult run=3 errors=0 failures=2> +# """ + +# TODO: RUSTPYTHON +# traceback + error message is different than in CPython +# def test_DocFileSuite_errors(): +# """Tests for error reporting in DocTestSuite. + +# >>> import unittest +# >>> suite = doctest.DocFileSuite('test_doctest_errors.txt') +# >>> result = suite.run(unittest.TestResult()) +# >>> result +# <unittest.result.TestResult run=1 errors=0 failures=1> +# >>> print(result.failures[0][1]) # doctest: +ELLIPSIS +# Traceback (most recent call last): +# File ... +# raise self.failureException(self.format_failure(new.getvalue())) +# AssertionError: Failed doctest test for test_doctest_errors.txt +# File "...test_doctest_errors.txt", line 0 +# <BLANKLINE> +# ---------------------------------------------------------------------- +# File "...test_doctest_errors.txt", line 4, in test_doctest_errors.txt +# Failed example: +# 2 + 2 +# Expected: +# 5 +# Got: +# 4 +# ---------------------------------------------------------------------- +# File "...test_doctest_errors.txt", line 6, in test_doctest_errors.txt +# Failed example: +# 1/0 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test_doctest_errors.txt[1]>", line 1, in <module> +# 1/0 +# ~^~ +# ZeroDivisionError: division by zero +# ---------------------------------------------------------------------- +# File "...test_doctest_errors.txt", line 11, in test_doctest_errors.txt +# Failed example: +# f() +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test_doctest_errors.txt[3]>", line 1, in <module> +# f() +# ~^^ +# File "<doctest test_doctest_errors.txt[2]>", line 2, in f +# 2 + '2' +# ~~^~~~~ +# TypeError: ... +# ---------------------------------------------------------------------- +# File "...test_doctest_errors.txt", line 13, in test_doctest_errors.txt +# Failed example: +# 2+*3 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^ +# File "<doctest test_doctest_errors.txt[4]>", line 1 +# 2+*3 +# ^ +# SyntaxError: invalid syntax +# <BLANKLINE> +# <BLANKLINE> +# """ + +def test_trailing_space_in_test(): + """ + Trailing spaces in expected output are significant: + + >>> x, y = 'foo', '' + >>> print(x, y) + foo \n + """ + +class Wrapper: + def __init__(self, func): + self.func = func + functools.update_wrapper(self, func) + + def __call__(self, *args, **kwargs): + self.func(*args, **kwargs) + +@Wrapper +def wrapped(): + """ + Docstrings in wrapped functions must be detected as well. + + >>> 'one other test' + 'one other test' + """ + +def test_look_in_unwrapped(): + """ + Ensure that wrapped doctests work correctly. + + >>> import doctest + >>> doctest.run_docstring_examples( + ... wrapped, {}, name=wrapped.__name__, verbose=True) + Finding tests in wrapped + Trying: + 'one other test' + Expecting: + 'one other test' + ok + """ + +@doctest_skip_if(support.check_impl_detail(cpython=False)) +def test_wrapped_c_func(): + """ + # https://github.com/python/cpython/issues/117692 + >>> import binascii + >>> from test.test_doctest.decorator_mod import decorator + + >>> c_func_wrapped = decorator(binascii.b2a_hex) + >>> tests = doctest.DocTestFinder(exclude_empty=False).find(c_func_wrapped) + >>> for test in tests: + ... print(test.lineno, test.name) + None b2a_hex + """ + +def test_unittest_reportflags(): + """Default unittest reporting flags can be set to control reporting + + Here, we'll set the REPORT_ONLY_FIRST_FAILURE option so we see + only the first failure of each test. First, we'll look at the + output without the flag. The file test_doctest.txt file has two + tests. They both fail if blank lines are disabled: + + >>> suite = doctest.DocFileSuite('test_doctest.txt', + ... optionflags=doctest.DONT_ACCEPT_BLANKLINE) + >>> import unittest + >>> result = suite.run(unittest.TestResult()) + >>> result + <unittest.result.TestResult run=1 errors=0 failures=1> + >>> print(result.failures[0][1]) # doctest: +ELLIPSIS + Traceback ... + Failed example: + favorite_color + ... + Failed example: + if 1: + ... + + Note that we see both failures displayed. + + >>> old = doctest.set_unittest_reportflags( + ... doctest.REPORT_ONLY_FIRST_FAILURE) + + Now, when we run the test: + + >>> result = suite.run(unittest.TestResult()) + >>> result + <unittest.result.TestResult run=1 errors=0 failures=1> + >>> print(result.failures[0][1]) # doctest: +ELLIPSIS + Traceback ... + Failed example: + favorite_color + Exception raised: + ... + NameError: name 'favorite_color' is not defined + <BLANKLINE> + <BLANKLINE> + + We get only the first failure. + + If we give any reporting options when we set up the tests, + however: + + >>> suite = doctest.DocFileSuite('test_doctest.txt', + ... optionflags=doctest.DONT_ACCEPT_BLANKLINE | doctest.REPORT_NDIFF) + + Then the default eporting options are ignored: + + >>> result = suite.run(unittest.TestResult()) + >>> result + <unittest.result.TestResult run=1 errors=0 failures=1> + + *NOTE*: These doctest are intentionally not placed in raw string to depict + the trailing whitespace using `\x20` in the diff below. + + >>> print(result.failures[0][1]) # doctest: +ELLIPSIS + Traceback ... + Failed example: + favorite_color + ... + Failed example: + if 1: + print('a') + print() + print('b') + Differences (ndiff with -expected +actual): + a + - <BLANKLINE> + +\x20 + b + <BLANKLINE> + <BLANKLINE> + + + Test runners can restore the formatting flags after they run: + + >>> ignored = doctest.set_unittest_reportflags(old) + + """ + +def test_testfile(): r""" +Tests for the `testfile()` function. This function runs all the +doctest examples in a given file. In its simple invocation, it is +called with the name of a file, which is taken to be relative to the +calling module. The return value is (#failures, #tests). + +We don't want color or `-v` in sys.argv for these tests. + + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + + >>> save_argv = sys.argv + >>> if '-v' in sys.argv: + ... sys.argv = [arg for arg in save_argv if arg != '-v'] + + + >>> doctest.testfile('test_doctest.txt') # doctest: +ELLIPSIS + ********************************************************************** + File "...", line 6, in test_doctest.txt + Failed example: + favorite_color + Exception raised: + ... + NameError: name 'favorite_color' is not defined + ********************************************************************** + 1 item had failures: + 1 of 2 in test_doctest.txt + ***Test Failed*** 1 failure. + TestResults(failed=1, attempted=2) + >>> doctest.master = None # Reset master. + +(Note: we'll be clearing doctest.master after each call to +`doctest.testfile`, to suppress warnings about multiple tests with the +same name.) + +Globals may be specified with the `globs` and `extraglobs` parameters: + + >>> globs = {'favorite_color': 'blue'} + >>> doctest.testfile('test_doctest.txt', globs=globs) + TestResults(failed=0, attempted=2) + >>> doctest.master = None # Reset master. + + >>> extraglobs = {'favorite_color': 'red'} + >>> doctest.testfile('test_doctest.txt', globs=globs, + ... extraglobs=extraglobs) # doctest: +ELLIPSIS + ********************************************************************** + File "...", line 6, in test_doctest.txt + Failed example: + favorite_color + Expected: + 'blue' + Got: + 'red' + ********************************************************************** + 1 item had failures: + 1 of 2 in test_doctest.txt + ***Test Failed*** 1 failure. + TestResults(failed=1, attempted=2) + >>> doctest.master = None # Reset master. + +The file may be made relative to a given module or package, using the +optional `module_relative` parameter: + + >>> doctest.testfile('test_doctest.txt', globs=globs, + ... module_relative='test') + TestResults(failed=0, attempted=2) + >>> doctest.master = None # Reset master. + +Verbosity can be increased with the optional `verbose` parameter: + + >>> doctest.testfile('test_doctest.txt', globs=globs, verbose=True) + Trying: + favorite_color + Expecting: + 'blue' + ok + Trying: + if 1: + print('a') + print() + print('b') + Expecting: + a + <BLANKLINE> + b + ok + 1 item passed all tests: + 2 tests in test_doctest.txt + 2 tests in 1 item. + 2 passed. + Test passed. + TestResults(failed=0, attempted=2) + >>> doctest.master = None # Reset master. + +The name of the test may be specified with the optional `name` +parameter: + + >>> doctest.testfile('test_doctest.txt', name='newname') + ... # doctest: +ELLIPSIS + ********************************************************************** + File "...", line 6, in newname + ... + TestResults(failed=1, attempted=2) + >>> doctest.master = None # Reset master. + +The summary report may be suppressed with the optional `report` +parameter: + + >>> doctest.testfile('test_doctest.txt', report=False) + ... # doctest: +ELLIPSIS + ********************************************************************** + File "...", line 6, in test_doctest.txt + Failed example: + favorite_color + Exception raised: + ... + NameError: name 'favorite_color' is not defined + TestResults(failed=1, attempted=2) + >>> doctest.master = None # Reset master. + +The optional keyword argument `raise_on_error` can be used to raise an +exception on the first error (which may be useful for postmortem +debugging): + + >>> doctest.testfile('test_doctest.txt', raise_on_error=True) + ... # doctest: +ELLIPSIS + Traceback (most recent call last): + doctest.UnexpectedException: ... + >>> doctest.master = None # Reset master. + +If the tests contain non-ASCII characters, the tests might fail, since +it's unknown which encoding is used. The encoding can be specified +using the optional keyword argument `encoding`: + + >>> doctest.testfile('test_doctest4.txt', encoding='latin-1') # doctest: +ELLIPSIS + ********************************************************************** + File "...", line 7, in test_doctest4.txt + Failed example: + '...' + Expected: + 'f\xf6\xf6' + Got: + 'f\xc3\xb6\xc3\xb6' + ********************************************************************** + ... + ********************************************************************** + 1 item had failures: + 2 of 2 in test_doctest4.txt + ***Test Failed*** 2 failures. + TestResults(failed=2, attempted=2) + >>> doctest.master = None # Reset master. + + >>> doctest.testfile('test_doctest4.txt', encoding='utf-8') + TestResults(failed=0, attempted=2) + >>> doctest.master = None # Reset master. + +Test the verbose output: + + >>> doctest.testfile('test_doctest4.txt', encoding='utf-8', verbose=True) + Trying: + 'föö' + Expecting: + 'f\xf6\xf6' + ok + Trying: + 'bąr' + Expecting: + 'b\u0105r' + ok + 1 item passed all tests: + 2 tests in test_doctest4.txt + 2 tests in 1 item. + 2 passed. + Test passed. + TestResults(failed=0, attempted=2) + >>> doctest.master = None # Reset master. + >>> sys.argv = save_argv + >>> _colorize.COLORIZE = save_colorize +""" + +# TODO: RUSTPYTHON +# traceback + error message is different than in CPython +# def test_testfile_errors(): r""" +# Tests for error reporting in the testfile() function. + +# >>> doctest.testfile('test_doctest_errors.txt', verbose=False) # doctest: +ELLIPSIS +# ********************************************************************** +# File "...test_doctest_errors.txt", line 4, in test_doctest_errors.txt +# Failed example: +# 2 + 2 +# Expected: +# 5 +# Got: +# 4 +# ********************************************************************** +# File "...test_doctest_errors.txt", line 6, in test_doctest_errors.txt +# Failed example: +# 1/0 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test_doctest_errors.txt[1]>", line 1, in <module> +# 1/0 +# ~^~ +# ZeroDivisionError: division by zero +# ********************************************************************** +# File "...test_doctest_errors.txt", line 11, in test_doctest_errors.txt +# Failed example: +# f() +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test_doctest_errors.txt[3]>", line 1, in <module> +# f() +# ~^^ +# File "<doctest test_doctest_errors.txt[2]>", line 2, in f +# 2 + '2' +# ~~^~~~~ +# TypeError: ... +# ********************************************************************** +# File "...test_doctest_errors.txt", line 13, in test_doctest_errors.txt +# Failed example: +# 2+*3 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^ +# File "<doctest test_doctest_errors.txt[4]>", line 1 +# 2+*3 +# ^ +# SyntaxError: invalid syntax +# ********************************************************************** +# 1 item had failures: +# 4 of 5 in test_doctest_errors.txt +# ***Test Failed*** 4 failures. +# TestResults(failed=4, attempted=5) +# """ + +class TestImporter(importlib.abc.MetaPathFinder): + + def find_spec(self, fullname, path, target=None): + return importlib.util.spec_from_file_location(fullname, path, loader=self) + + def get_data(self, path): + with open(path, mode='rb') as f: + return f.read() + + def exec_module(self, module): + raise ImportError + + def create_module(self, spec): + return None + +class TestHook: + + def __init__(self, pathdir): + self.sys_path = sys.path[:] + self.meta_path = sys.meta_path[:] + self.path_hooks = sys.path_hooks[:] + sys.path.append(pathdir) + sys.path_importer_cache.clear() + self.modules_before = sys.modules.copy() + self.importer = TestImporter() + sys.meta_path.append(self.importer) + + def remove(self): + sys.path[:] = self.sys_path + sys.meta_path[:] = self.meta_path + sys.path_hooks[:] = self.path_hooks + sys.path_importer_cache.clear() + sys.modules.clear() + sys.modules.update(self.modules_before) + + +@contextlib.contextmanager +def test_hook(pathdir): + hook = TestHook(pathdir) + try: + yield hook + finally: + hook.remove() + +# TODO: RUSTPYTHON +# f.write(b'Test:\r\n\r\n >>> x = 1 + 1\r\n\r\nDone.\r\n') +# does not return in with... +# def test_lineendings(): r""" +# *nix systems use \n line endings, while Windows systems use \r\n, and +# old Mac systems used \r, which Python still recognizes as a line ending. Python +# handles this using universal newline mode for reading files. Let's make +# sure doctest does so (issue 8473) by creating temporary test files using each +# of the three line disciplines. At least one will not match either the universal +# newline \n or os.linesep for the platform the test is run on. + +# Windows line endings first: + +# >>> import tempfile, os +# >>> fn = tempfile.mktemp() +# >>> with open(fn, 'wb') as f: +# ... f.write(b'Test:\r\n\r\n >>> x = 1 + 1\r\n\r\nDone.\r\n') +# 35 +# >>> doctest.testfile(fn, module_relative=False, verbose=False) +# TestResults(failed=0, attempted=1) +# >>> os.remove(fn) + +# And now *nix line endings: + +# >>> fn = tempfile.mktemp() +# >>> with open(fn, 'wb') as f: +# ... f.write(b'Test:\n\n >>> x = 1 + 1\n\nDone.\n') +# 30 +# >>> doctest.testfile(fn, module_relative=False, verbose=False) +# TestResults(failed=0, attempted=1) +# >>> os.remove(fn) + +# And finally old Mac line endings: + +# >>> fn = tempfile.mktemp() +# >>> with open(fn, 'wb') as f: +# ... f.write(b'Test:\r\r >>> x = 1 + 1\r\rDone.\r') +# 30 +# >>> doctest.testfile(fn, module_relative=False, verbose=False) +# TestResults(failed=0, attempted=1) +# >>> os.remove(fn) + +# Now we test with a package loader that has a get_data method, since that +# bypasses the standard universal newline handling so doctest has to do the +# newline conversion itself; let's make sure it does so correctly (issue 1812). +# We'll write a file inside the package that has all three kinds of line endings +# in it, and use a package hook to install a custom loader; on any platform, +# at least one of the line endings will raise a ValueError for inconsistent +# whitespace if doctest does not correctly do the newline conversion. + +# >>> from test.support import os_helper +# >>> import shutil +# >>> dn = tempfile.mkdtemp() +# >>> pkg = os.path.join(dn, "doctest_testpkg") +# >>> os.mkdir(pkg) +# >>> os_helper.create_empty_file(os.path.join(pkg, "__init__.py")) +# >>> fn = os.path.join(pkg, "doctest_testfile.txt") +# >>> with open(fn, 'wb') as f: +# ... f.write( +# ... b'Test:\r\n\r\n' +# ... b' >>> x = 1 + 1\r\n\r\n' +# ... b'Done.\r\n' +# ... b'Test:\n\n' +# ... b' >>> x = 1 + 1\n\n' +# ... b'Done.\n' +# ... b'Test:\r\r' +# ... b' >>> x = 1 + 1\r\r' +# ... b'Done.\r' +# ... ) +# 95 +# >>> with test_hook(dn): +# ... doctest.testfile("doctest_testfile.txt", package="doctest_testpkg", verbose=False) +# TestResults(failed=0, attempted=3) +# >>> shutil.rmtree(dn) + +# """ + +def test_testmod(): r""" +Tests for the testmod function. More might be useful, but for now we're just +testing the case raised by Issue 6195, where trying to doctest a C module would +fail with a UnicodeDecodeError because doctest tried to read the "source" lines +out of the binary module. + + >>> import unicodedata + >>> doctest.testmod(unicodedata, verbose=False) + TestResults(failed=0, attempted=0) +""" + +# TODO: RUSTPYTHON +# traceback + error message is different than in CPython +# def test_testmod_errors(): r""" +# Tests for error reporting in the testmod() function. + +# >>> import test.test_doctest.sample_doctest_errors as mod +# >>> doctest.testmod(mod, verbose=False) # doctest: +ELLIPSIS +# ********************************************************************** +# File "...sample_doctest_errors.py", line 5, in test.test_doctest.sample_doctest_errors +# Failed example: +# 2 + 2 +# Expected: +# 5 +# Got: +# 4 +# ********************************************************************** +# File "...sample_doctest_errors.py", line 7, in test.test_doctest.sample_doctest_errors +# Failed example: +# 1/0 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors[1]>", line 1, in <module> +# 1/0 +# ~^~ +# ZeroDivisionError: division by zero +# ********************************************************************** +# File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad +# Failed example: +# 2 + 2 +# Expected: +# 5 +# Got: +# 4 +# ********************************************************************** +# File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad +# Failed example: +# 1/0 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors.__test__.bad[1]>", line 1, in <module> +# 1/0 +# ~^~ +# ZeroDivisionError: division by zero +# ********************************************************************** +# File "...sample_doctest_errors.py", line 16, in test.test_doctest.sample_doctest_errors.errors +# Failed example: +# 2 + 2 +# Expected: +# 5 +# Got: +# 4 +# ********************************************************************** +# File "...sample_doctest_errors.py", line 18, in test.test_doctest.sample_doctest_errors.errors +# Failed example: +# 1/0 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors.errors[1]>", line 1, in <module> +# 1/0 +# ~^~ +# ZeroDivisionError: division by zero +# ********************************************************************** +# File "...sample_doctest_errors.py", line 23, in test.test_doctest.sample_doctest_errors.errors +# Failed example: +# f() +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors.errors[3]>", line 1, in <module> +# f() +# ~^^ +# File "<doctest test.test_doctest.sample_doctest_errors.errors[2]>", line 2, in f +# 2 + '2' +# ~~^~~~~ +# TypeError: ... +# ********************************************************************** +# File "...sample_doctest_errors.py", line 25, in test.test_doctest.sample_doctest_errors.errors +# Failed example: +# g() +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors.errors[4]>", line 1, in <module> +# g() +# ~^^ +# File "...sample_doctest_errors.py", line 12, in g +# [][0] # line 12 +# ~~^^^ +# IndexError: list index out of range +# ********************************************************************** +# File "...sample_doctest_errors.py", line 31, in test.test_doctest.sample_doctest_errors.syntax_error +# Failed example: +# 2+*3 +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^ +# File "<doctest test.test_doctest.sample_doctest_errors.syntax_error[0]>", line 1 +# 2+*3 +# ^ +# SyntaxError: invalid syntax +# ********************************************************************** +# 4 items had failures: +# 2 of 2 in test.test_doctest.sample_doctest_errors +# 2 of 2 in test.test_doctest.sample_doctest_errors.__test__.bad +# 4 of 5 in test.test_doctest.sample_doctest_errors.errors +# 1 of 1 in test.test_doctest.sample_doctest_errors.syntax_error +# ***Test Failed*** 9 failures. +# TestResults(failed=9, attempted=10) +# """ + +try: + os.fsencode("foo-bär@baz.py") + supports_unicode = True +except UnicodeEncodeError: + # Skip the test: the filesystem encoding is unable to encode the filename + supports_unicode = False + +# TODO: RUSTPYTHON +# traceback message is different than in CPython +# if supports_unicode: +# def test_unicode(): """ +# Check doctest with a non-ascii filename: + +# >>> save_colorize = _colorize.COLORIZE +# >>> _colorize.COLORIZE = False + +# >>> doc = ''' +# ... >>> raise Exception('clé') +# ... ''' +# ... +# >>> parser = doctest.DocTestParser() +# >>> test = parser.get_doctest(doc, {}, "foo-bär@baz", "foo-bär@baz.py", 0) +# >>> test +# <DocTest foo-bär@baz from foo-bär@baz.py:0 (1 example)> +# >>> runner = doctest.DocTestRunner(verbose=False) +# >>> runner.run(test) # doctest: +ELLIPSIS +# ********************************************************************** +# File "foo-bär@baz.py", line 2, in foo-bär@baz +# Failed example: +# raise Exception('clé') +# Exception raised: +# Traceback (most recent call last): +# File ... +# exec(compile(example.source, filename, "single", +# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# compileflags, True), test.globs) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# File "<doctest foo-bär@baz[0]>", line 1, in <module> +# raise Exception('clé') +# Exception: clé +# TestResults(failed=1, attempted=1) + +# >>> _colorize.COLORIZE = save_colorize +# """ + + +# TODO: RUSTPYTHON +# Difference in Error: +# FileNotFoundError: [Errno ...] ...nosuchfile... vs FileNotFoundError: (2, 'No such file or directory') +# @doctest_skip_if(not support.has_subprocess_support) +# def test_CLI(): r""" +# The doctest module can be used to run doctests against an arbitrary file. +# These tests test this CLI functionality. + +# We'll use the support module's script_helpers for this, and write a test files +# to a temp dir to run the command against. Due to a current limitation in +# script_helpers, though, we need a little utility function to turn the returned +# output into something we can doctest against: + +# >>> def normalize(s): +# ... return '\n'.join(s.decode().splitlines()) + +# With those preliminaries out of the way, we'll start with a file with two +# simple tests and no errors. We'll run both the unadorned doctest command, and +# the verbose version, and then check the output: + +# >>> from test.support import script_helper +# >>> from test.support.os_helper import temp_dir +# >>> with temp_dir() as tmpdir: +# ... fn = os.path.join(tmpdir, 'myfile.doc') +# ... with open(fn, 'w', encoding='utf-8') as f: +# ... _ = f.write('This is a very simple test file.\n') +# ... _ = f.write(' >>> 1 + 1\n') +# ... _ = f.write(' 2\n') +# ... _ = f.write(' >>> "a"\n') +# ... _ = f.write(" 'a'\n") +# ... _ = f.write('\n') +# ... _ = f.write('And that is it.\n') +# ... rc1, out1, err1 = script_helper.assert_python_ok( +# ... '-m', 'doctest', fn) +# ... rc2, out2, err2 = script_helper.assert_python_ok( +# ... '-m', 'doctest', '-v', fn) + +# With no arguments and passing tests, we should get no output: + +# >>> rc1, out1, err1 +# (0, b'', b'') + +# With the verbose flag, we should see the test output, but no error output: + +# >>> rc2, err2 +# (0, b'') +# >>> print(normalize(out2)) +# Trying: +# 1 + 1 +# Expecting: +# 2 +# ok +# Trying: +# "a" +# Expecting: +# 'a' +# ok +# 1 item passed all tests: +# 2 tests in myfile.doc +# 2 tests in 1 item. +# 2 passed. +# Test passed. + +# Now we'll write a couple files, one with three tests, the other a python module +# with two tests, both of the files having "errors" in the tests that can be made +# non-errors by applying the appropriate doctest options to the run (ELLIPSIS in +# the first file, NORMALIZE_WHITESPACE in the second). This combination will +# allow thoroughly testing the -f and -o flags, as well as the doctest command's +# ability to process more than one file on the command line and, since the second +# file ends in '.py', its handling of python module files (as opposed to straight +# text files). + +# >>> from test.support import script_helper +# >>> from test.support.os_helper import temp_dir +# >>> with temp_dir() as tmpdir: +# ... fn = os.path.join(tmpdir, 'myfile.doc') +# ... with open(fn, 'w', encoding="utf-8") as f: +# ... _ = f.write('This is another simple test file.\n') +# ... _ = f.write(' >>> 1 + 1\n') +# ... _ = f.write(' 2\n') +# ... _ = f.write(' >>> "abcdef"\n') +# ... _ = f.write(" 'a...f'\n") +# ... _ = f.write(' >>> "ajkml"\n') +# ... _ = f.write(" 'a...l'\n") +# ... _ = f.write('\n') +# ... _ = f.write('And that is it.\n') +# ... fn2 = os.path.join(tmpdir, 'myfile2.py') +# ... with open(fn2, 'w', encoding='utf-8') as f: +# ... _ = f.write('def test_func():\n') +# ... _ = f.write(' \"\"\"\n') +# ... _ = f.write(' This is simple python test function.\n') +# ... _ = f.write(' >>> 1 + 1\n') +# ... _ = f.write(' 2\n') +# ... _ = f.write(' >>> "abc def"\n') +# ... _ = f.write(" 'abc def'\n") +# ... _ = f.write("\n") +# ... _ = f.write(' \"\"\"\n') +# ... rc1, out1, err1 = script_helper.assert_python_failure( +# ... '-m', 'doctest', fn, fn2) +# ... rc2, out2, err2 = script_helper.assert_python_ok( +# ... '-m', 'doctest', '-o', 'ELLIPSIS', fn) +# ... rc3, out3, err3 = script_helper.assert_python_ok( +# ... '-m', 'doctest', '-o', 'ELLIPSIS', +# ... '-o', 'NORMALIZE_WHITESPACE', fn, fn2) +# ... rc4, out4, err4 = script_helper.assert_python_failure( +# ... '-m', 'doctest', '-f', fn, fn2) +# ... rc5, out5, err5 = script_helper.assert_python_ok( +# ... '-m', 'doctest', '-v', '-o', 'ELLIPSIS', +# ... '-o', 'NORMALIZE_WHITESPACE', fn, fn2) + +# Our first test run will show the errors from the first file (doctest stops if a +# file has errors). Note that doctest test-run error output appears on stdout, +# not stderr: + +# >>> rc1, err1 +# (1, b'') +# >>> print(normalize(out1)) # doctest: +ELLIPSIS +# ********************************************************************** +# File "...myfile.doc", line 4, in myfile.doc +# Failed example: +# "abcdef" +# Expected: +# 'a...f' +# Got: +# 'abcdef' +# ********************************************************************** +# File "...myfile.doc", line 6, in myfile.doc +# Failed example: +# "ajkml" +# Expected: +# 'a...l' +# Got: +# 'ajkml' +# ********************************************************************** +# 1 item had failures: +# 2 of 3 in myfile.doc +# ***Test Failed*** 2 failures. + +# With -o ELLIPSIS specified, the second run, against just the first file, should +# produce no errors, and with -o NORMALIZE_WHITESPACE also specified, neither +# should the third, which ran against both files: + +# >>> rc2, out2, err2 +# (0, b'', b'') +# >>> rc3, out3, err3 +# (0, b'', b'') + +# The fourth run uses FAIL_FAST, so we should see only one error: + +# >>> rc4, err4 +# (1, b'') +# >>> print(normalize(out4)) # doctest: +ELLIPSIS +# ********************************************************************** +# File "...myfile.doc", line 4, in myfile.doc +# Failed example: +# "abcdef" +# Expected: +# 'a...f' +# Got: +# 'abcdef' +# ********************************************************************** +# 1 item had failures: +# 1 of 2 in myfile.doc +# ***Test Failed*** 1 failure. + +# The fifth test uses verbose with the two options, so we should get verbose +# success output for the tests in both files: + +# >>> rc5, err5 +# (0, b'') +# >>> print(normalize(out5)) +# Trying: +# 1 + 1 +# Expecting: +# 2 +# ok +# Trying: +# "abcdef" +# Expecting: +# 'a...f' +# ok +# Trying: +# "ajkml" +# Expecting: +# 'a...l' +# ok +# 1 item passed all tests: +# 3 tests in myfile.doc +# 3 tests in 1 item. +# 3 passed. +# Test passed. +# Trying: +# 1 + 1 +# Expecting: +# 2 +# ok +# Trying: +# "abc def" +# Expecting: +# 'abc def' +# ok +# 1 item had no tests: +# myfile2 +# 1 item passed all tests: +# 2 tests in myfile2.test_func +# 2 tests in 2 items. +# 2 passed. +# Test passed. + +# We should also check some typical error cases. + +# Invalid file name: + +# >>> rc, out, err = script_helper.assert_python_failure( +# ... '-m', 'doctest', 'nosuchfile') +# >>> rc, out +# (1, b'') +# >>> # The exact error message changes depending on the platform. +# >>> print(normalize(err)) # doctest: +ELLIPSIS +# Traceback (most recent call last): +# ... +# FileNotFoundError: [Errno ...] ...nosuchfile... + +# Invalid doctest option: + +# >>> rc, out, err = script_helper.assert_python_failure( +# ... '-m', 'doctest', '-o', 'nosuchoption') +# >>> rc, out +# (2, b'') +# >>> print(normalize(err)) # doctest: +ELLIPSIS +# usage...invalid...nosuchoption... + +# """ + +def test_no_trailing_whitespace_stripping(): + r""" + The fancy reports had a bug for a long time where any trailing whitespace on + the reported diff lines was stripped, making it impossible to see the + differences in line reported as different that differed only in the amount of + trailing whitespace. The whitespace still isn't particularly visible unless + you use NDIFF, but at least it is now there to be found. + + *NOTE*: This snippet was intentionally put inside a raw string to get rid of + leading whitespace error in executing the example below + + >>> def f(x): + ... r''' + ... >>> print('\n'.join(['a ', 'b'])) + ... a + ... b + ... ''' + """ + """ + *NOTE*: These doctest are not placed in raw string to depict the trailing whitespace + using `\x20` + + >>> test = doctest.DocTestFinder().find(f)[0] + >>> flags = doctest.REPORT_NDIFF + >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File ..., line 3, in f + Failed example: + print('\n'.join(['a ', 'b'])) + Differences (ndiff with -expected +actual): + - a + + a + b + TestResults(failed=1, attempted=1) + + *NOTE*: `\x20` is for checking the trailing whitespace on the +a line above. + We cannot use actual spaces there, as a commit hook prevents from committing + patches that contain trailing whitespace. More info on Issue 24746. + """ + + +def test_run_doctestsuite_multiple_times(): + """ + It was not possible to run the same DocTestSuite multiple times + http://bugs.python.org/issue2604 + http://bugs.python.org/issue9736 + + >>> import unittest + >>> import test.test_doctest.sample_doctest + >>> suite = doctest.DocTestSuite(test.test_doctest.sample_doctest) + >>> suite.run(unittest.TestResult()) + <unittest.result.TestResult run=9 errors=0 failures=4> + >>> suite.run(unittest.TestResult()) + <unittest.result.TestResult run=9 errors=0 failures=4> + """ + + +def test_exception_with_note(note): + """ + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + + >>> test_exception_with_note('Note') + Traceback (most recent call last): + ... + ValueError: Text + Note + + >>> test_exception_with_note('Note') # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Text + Note + + >>> test_exception_with_note('''Note + ... multiline + ... example''') + Traceback (most recent call last): + ValueError: Text + Note + multiline + example + + Different note will fail the test: + + >>> def f(x): + ... r''' + ... >>> exc = ValueError('message') + ... >>> exc.add_note('note') + ... >>> raise exc + ... Traceback (most recent call last): + ... ValueError: message + ... wrong note + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File "...", line 5, in f + Failed example: + raise exc + Expected: + Traceback (most recent call last): + ValueError: message + wrong note + Got: + Traceback (most recent call last): + ... + ValueError: message + note + TestResults(failed=1, attempted=...) + + >>> _colorize.COLORIZE = save_colorize + """ + exc = ValueError('Text') + exc.add_note(note) + raise exc + + +def test_exception_with_multiple_notes(): + """ + >>> test_exception_with_multiple_notes() + Traceback (most recent call last): + ... + ValueError: Text + One + Two + """ + exc = ValueError('Text') + exc.add_note('One') + exc.add_note('Two') + raise exc + + +def test_syntax_error_with_note(cls, multiline=False): + """ + >>> test_syntax_error_with_note(SyntaxError) + Traceback (most recent call last): + ... + SyntaxError: error + Note + + >>> test_syntax_error_with_note(SyntaxError) + Traceback (most recent call last): + SyntaxError: error + Note + + >>> test_syntax_error_with_note(SyntaxError) + Traceback (most recent call last): + ... + File "x.py", line 23 + bad syntax + SyntaxError: error + Note + + >>> test_syntax_error_with_note(IndentationError) + Traceback (most recent call last): + ... + IndentationError: error + Note + + >>> test_syntax_error_with_note(TabError, multiline=True) + Traceback (most recent call last): + ... + TabError: error + Note + Line + """ + exc = cls("error", ("x.py", 23, None, "bad syntax")) + exc.add_note('Note\nLine' if multiline else 'Note') + raise exc + + +def test_syntax_error_subclass_from_stdlib(): + """ + `ParseError` is a subclass of `SyntaxError`, but it is not a builtin: + + >>> test_syntax_error_subclass_from_stdlib() + Traceback (most recent call last): + ... + xml.etree.ElementTree.ParseError: error + error + Note + Line + """ + from xml.etree.ElementTree import ParseError + exc = ParseError("error\nerror") + exc.add_note('Note\nLine') + raise exc + + +def test_syntax_error_with_incorrect_expected_note(): + """ + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + + >>> def f(x): + ... r''' + ... >>> exc = SyntaxError("error", ("x.py", 23, None, "bad syntax")) + ... >>> exc.add_note('note1') + ... >>> exc.add_note('note2') + ... >>> raise exc + ... Traceback (most recent call last): + ... SyntaxError: error + ... wrong note + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + ... # doctest: +ELLIPSIS + ********************************************************************** + File "...", line 6, in f + Failed example: + raise exc + Expected: + Traceback (most recent call last): + SyntaxError: error + wrong note + Got: + Traceback (most recent call last): + ... + SyntaxError: error + note1 + note2 + TestResults(failed=1, attempted=...) + + >>> _colorize.COLORIZE = save_colorize + """ + + +def load_tests(loader, tests, pattern): + tests.addTest(doctest.DocTestSuite(doctest)) + tests.addTest(doctest.DocTestSuite()) + return tests + + +if __name__ == '__main__': + unittest.main(module='test.test_doctest.test_doctest') diff --git a/Lib/test/test_doctest/test_doctest.txt b/Lib/test/test_doctest/test_doctest.txt new file mode 100644 index 00000000000..23446d1d224 --- /dev/null +++ b/Lib/test/test_doctest/test_doctest.txt @@ -0,0 +1,17 @@ +This is a sample doctest in a text file. + +In this example, we'll rely on a global variable being set for us +already: + + >>> favorite_color + 'blue' + +We can make this fail by disabling the blank-line feature. + + >>> if 1: + ... print('a') + ... print() + ... print('b') + a + <BLANKLINE> + b diff --git a/Lib/test/test_doctest/test_doctest2.py b/Lib/test/test_doctest/test_doctest2.py new file mode 100644 index 00000000000..ab8a0696736 --- /dev/null +++ b/Lib/test/test_doctest/test_doctest2.py @@ -0,0 +1,126 @@ +"""A module to test whether doctest recognizes some 2.2 features, +like static and class methods. + +>>> print('yup') # 1 +yup + +We include some (random) encoded (utf-8) text in the text surrounding +the example. It should be ignored: + +ЉЊЈЁЂ + +""" + +import sys +import unittest +if sys.flags.optimize >= 2: + raise unittest.SkipTest("Cannot test docstrings with -O2") + +class C(object): + """Class C. + + >>> print(C()) # 2 + 42 + + + We include some (random) encoded (utf-8) text in the text surrounding + the example. It should be ignored: + + ЉЊЈЁЂ + + """ + + def __init__(self): + """C.__init__. + + >>> print(C()) # 3 + 42 + """ + + def __str__(self): + """ + >>> print(C()) # 4 + 42 + """ + return "42" + + class D(object): + """A nested D class. + + >>> print("In D!") # 5 + In D! + """ + + def nested(self): + """ + >>> print(3) # 6 + 3 + """ + + def getx(self): + """ + >>> c = C() # 7 + >>> c.x = 12 # 8 + >>> print(c.x) # 9 + -12 + """ + return -self._x + + def setx(self, value): + """ + >>> c = C() # 10 + >>> c.x = 12 # 11 + >>> print(c.x) # 12 + -12 + """ + self._x = value + + x = property(getx, setx, doc="""\ + >>> c = C() # 13 + >>> c.x = 12 # 14 + >>> print(c.x) # 15 + -12 + """) + + @staticmethod + def statm(): + """ + A static method. + + >>> print(C.statm()) # 16 + 666 + >>> print(C().statm()) # 17 + 666 + """ + return 666 + + @classmethod + def clsm(cls, val): + """ + A class method. + + >>> print(C.clsm(22)) # 18 + 22 + >>> print(C().clsm(23)) # 19 + 23 + """ + return val + + +class Test(unittest.TestCase): + def test_testmod(self): + import doctest, sys + EXPECTED = 19 + f, t = doctest.testmod(sys.modules[__name__]) + if f: + self.fail("%d of %d doctests failed" % (f, t)) + if t != EXPECTED: + self.fail("expected %d tests to run, not %d" % (EXPECTED, t)) + + +# Pollute the namespace with a bunch of imported functions and classes, +# to make sure they don't get tested. +from doctest import * + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_doctest/test_doctest2.txt b/Lib/test/test_doctest/test_doctest2.txt new file mode 100644 index 00000000000..76dab94a9c0 --- /dev/null +++ b/Lib/test/test_doctest/test_doctest2.txt @@ -0,0 +1,14 @@ +This is a sample doctest in a text file. + +In this example, we'll rely on some silly setup: + + >>> import test.test_doctest.test_doctest + >>> test.test_doctest.test_doctest.sillySetup + True + +This test also has some (random) encoded (utf-8) unicode text: + + ЉЊЈЁЂ + +This doesn't cause a problem in the tect surrounding the examples, but +we include it here (in this test text file) to make sure. :) diff --git a/Lib/test/test_doctest/test_doctest3.txt b/Lib/test/test_doctest/test_doctest3.txt new file mode 100644 index 00000000000..dd8557e57a5 --- /dev/null +++ b/Lib/test/test_doctest/test_doctest3.txt @@ -0,0 +1,5 @@ + +Here we check that `__file__` is provided: + + >>> type(__file__) + <class 'str'> diff --git a/Lib/test/test_doctest/test_doctest4.txt b/Lib/test/test_doctest/test_doctest4.txt new file mode 100644 index 00000000000..0428e6f9632 --- /dev/null +++ b/Lib/test/test_doctest/test_doctest4.txt @@ -0,0 +1,11 @@ +This is a sample doctest in a text file that contains non-ASCII characters. +This file is encoded using UTF-8. + +In order to get this test to pass, we have to manually specify the +encoding. + + >>> 'föö' + 'f\xf6\xf6' + + >>> 'bąr' + 'b\u0105r' diff --git a/Lib/test/test_doctest/test_doctest_errors.txt b/Lib/test/test_doctest/test_doctest_errors.txt new file mode 100644 index 00000000000..93c3c106e60 --- /dev/null +++ b/Lib/test/test_doctest/test_doctest_errors.txt @@ -0,0 +1,14 @@ +This is a sample doctest in a text file, in which all examples fail +or raise an exception. + + >>> 2 + 2 + 5 + >>> 1/0 + 1 + >>> def f(): + ... 2 + '2' + ... + >>> f() + 1 + >>> 2+*3 + 5 diff --git a/Lib/test/test_doctest/test_doctest_skip.txt b/Lib/test/test_doctest/test_doctest_skip.txt new file mode 100644 index 00000000000..06c23d06e60 --- /dev/null +++ b/Lib/test/test_doctest/test_doctest_skip.txt @@ -0,0 +1,6 @@ +This is a sample doctest in a text file, in which all examples are skipped. + + >>> 2 + 2 # doctest: +SKIP + 5 + >>> 2 + 2 # doctest: +SKIP + 4 diff --git a/Lib/test/test_doctest/test_doctest_skip2.txt b/Lib/test/test_doctest/test_doctest_skip2.txt new file mode 100644 index 00000000000..85e4938c346 --- /dev/null +++ b/Lib/test/test_doctest/test_doctest_skip2.txt @@ -0,0 +1,6 @@ +This is a sample doctest in a text file, in which some examples are skipped. + + >>> 2 + 2 # doctest: +SKIP + 5 + >>> 2 + 2 + 4 diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 182333cea51..9b74b66e3a7 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -129,7 +129,7 @@ fn inner_mod(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult { fn inner_floordiv(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult { if int2.is_zero() { - Err(vm.new_zero_division_error("integer division by zero")) + Err(vm.new_zero_division_error("integer division or modulo by zero")) } else { Ok(vm.ctx.new_int(int1.div_floor(int2)).into()) } From 580f6e3f34659a89e4b26d076ba3a90199be0e2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Dec 2025 09:47:59 +0900 Subject: [PATCH 644/819] Bump cranelift-* from 0.126.1 to 0.127.0 (#6571) Bumps [cranelift](https://github.com/bytecodealliance/wasmtime) from 0.126.1 to 0.127.0. - [Release notes](https://github.com/bytecodealliance/wasmtime/releases) - [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md) - [Commits](https://github.com/bytecodealliance/wasmtime/commits) -------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- Cargo.lock | 76 +++++++++++++++++++++---------------------- crates/jit/Cargo.toml | 6 ++-- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46c4dafe13b..89cace13b26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -691,9 +691,9 @@ dependencies = [ [[package]] name = "cranelift" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68971376deb1edf5e9c0ac77ef00479d740ce7a60e6181adb0648afe1dc7b8f4" +checksum = "513887fe45ce3979a4766ddc9992c3cdbf509add2a31906350649423a1d0d287" dependencies = [ "cranelift-codegen", "cranelift-frontend", @@ -702,42 +702,42 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30054f4aef4d614d37f27d5b77e36e165f0b27a71563be348e7c9fcfac41eed8" +checksum = "8bd963a645179fa33834ba61fa63353998543b07f877e208da9eb47d4a70d1e7" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beab56413879d4f515e08bcf118b1cb85f294129bb117057f573d37bfbb925a" +checksum = "3f6d5739c9dc6b5553ca758d78d87d127dd19f397f776efecf817b8ba8d0bb01" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d054747549a69b264d5299c8ca1b0dd45dc6bd0ee43f1edfcc42a8b12952c7a" +checksum = "ff402c11bb1c9652b67a3e885e84b1b8d00c13472c8fd85211e06a41a63c3e03" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b92d481b77a7dc9d07c96e24a16f29e0c9c27d042828fdf7e49e54ee9819bf" +checksum = "769a0d88c2f5539e9c5536a93a7bf164b0dc68d91e3d00723e5b4ffc1440afdc" [[package]] name = "cranelift-codegen" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eeccfc043d599b0ef1806942707fc51cdd1c3965c343956dc975a55d82a920f" +checksum = "d4351f721fb3b26add1c180f0a75c7474bab2f903c8b777c6ca65238ded59a78" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -761,9 +761,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1174cdb9d9d43b2bdaa612a07ed82af13db9b95526bc2c286c2aec4689bcc038" +checksum = "61f86c0ba5b96713643f4dd0de0df12844de9c7bb137d6829b174b706939aa74" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -773,33 +773,33 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d572be73fae802eb115f45e7e67a9ed16acb4ee683b67c4086768786545419a" +checksum = "f08605eee8d51fd976a970bd5b16c9529b51b624f8af68f80649ffb172eb85a4" [[package]] name = "cranelift-control" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1587465cc84c5cc793b44add928771945f3132bbf6b3621ee9473c631a87156" +checksum = "623aab0a09e40f0cf0b5d35eb7832bae4c4f13e3768228e051a6c1a60e88ef5f" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063b83448b1343e79282c3c7cbda7ed5f0816f0b763a4c15f7cecb0a17d87ea6" +checksum = "ea0f066e07e3bcbe38884cc5c94c32c7a90267d69df80f187d9dfe421adaa7c4" dependencies = [ "cranelift-bitset", ] [[package]] name = "cranelift-frontend" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4461c2d2ca48bc72883f5f5c3129d9aefac832df1db824af9db8db3efee109" +checksum = "40865b02a0e52ca8e580ad64feef530cb1d05f6bb4972b4eef05e3eaeae81701" dependencies = [ "cranelift-codegen", "log", @@ -809,15 +809,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd811b25e18f14810d09c504e06098acc1d9dbfa24879bf0d6b6fb44415fc66" +checksum = "104b3c117ae513e9af1d90679842101193a5ccb96ac9f997966d85ea25be2852" [[package]] name = "cranelift-jit" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01527663ba63c10509d7c87fd1f8495d21170ba35bf714f57271495689d8fde5" +checksum = "3aa5f855cfb8e4253ed2d0dfc1a0b6ebe4912e67aa8b7ee14026ff55ca17f1fe" dependencies = [ "anyhow", "cranelift-codegen", @@ -830,14 +830,14 @@ dependencies = [ "region", "target-lexicon", "wasmtime-internal-jit-icache-coherence", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "cranelift-module" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72328edb49aeafb1655818c91c476623970cb7b8a89ffbdadd82ce7d13dedc1d" +checksum = "b1d01806b191b59f4fc4680293dd5f554caf2de5b62f95eff5beef7acb46c29c" dependencies = [ "anyhow", "cranelift-codegen", @@ -846,9 +846,9 @@ dependencies = [ [[package]] name = "cranelift-native" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2417046989d8d6367a55bbab2e406a9195d176f4779be4aa484d645887217d37" +checksum = "e5c54e0a358bc05b48f2032e1c320e7f468da068604f2869b77052eab68eb0fe" dependencies = [ "cranelift-codegen", "libc", @@ -857,9 +857,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.126.1" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d039de901c8d928222b8128e1b9a9ab27b82a7445cb749a871c75d9cb25c57d" +checksum = "cc6f4b039f453b66c75e9f7886e5a2af96276e151f44dc19b24b58f9a0c98009" [[package]] name = "crc32fast" @@ -4293,21 +4293,21 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "39.0.1" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ccd36e25390258ce6720add639ffe5a7d81a5c904350aa08f5bbc60433d22" +checksum = "0858b470463f3e7c73acd6049046049e64be17b98901c2db5047450cf83df1fe" dependencies = [ "anyhow", "cfg-if", "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "wasmtime-internal-math" -version = "39.0.1" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1b856e1bbf0230ab560ba4204e944b141971adc4e6cdf3feb6979c1a7b7953" +checksum = "222e1a590ece4e898f20af1e541b61d2cb803f2557e7eaff23e6c1db5434454a" dependencies = [ "libm", ] diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index a79e48bac2d..fde10b7c006 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -17,9 +17,9 @@ num-traits = { workspace = true } thiserror = { workspace = true } libffi = { workspace = true } -cranelift = "0.126" -cranelift-jit = "0.126" -cranelift-module = "0.126" +cranelift = "0.127" +cranelift-jit = "0.127" +cranelift-module = "0.127" [dev-dependencies] rustpython-derive = { workspace = true } From 00ae4a175136541851f88b1c0108e565917bfbae Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 31 Dec 2025 11:28:04 +0900 Subject: [PATCH 645/819] enhance error codec and exit code (#6602) --- crates/vm/src/vm/mod.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index ddbf7660f7d..b156e30b738 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -327,11 +327,17 @@ impl VirtualMachine { let line_buffering = buffered_stdio && (isatty || fd == 2); let newline = if cfg!(windows) { None } else { Some("\n") }; + // stderr uses backslashreplace error handler + let errors: Option<&str> = if fd == 2 { + Some("backslashreplace") + } else { + None + }; let stdio = self.call_method( &io, "TextIOWrapper", - (buf, (), (), newline, line_buffering, write_through), + (buf, (), errors, newline, line_buffering, write_through), )?; let mode = if write { "w" } else { "r" }; stdio.set_attr("mode", self.ctx.new_str(mode), self)?; @@ -853,7 +859,13 @@ impl VirtualMachine { [arg] => match_class!(match arg { ref i @ PyInt => { use num_traits::cast::ToPrimitive; - return i.as_bigint().to_u32().unwrap_or(0); + // Try u32 first, then i32 (for negative values), else -1 for overflow + let code = i + .as_bigint() + .to_u32() + .or_else(|| i.as_bigint().to_i32().map(|v| v as u32)) + .unwrap_or(-1i32 as u32); + return code; } arg => { if self.is_none(arg) { @@ -866,8 +878,11 @@ impl VirtualMachine { _ => args.as_object().repr(self).ok(), }; if let Some(msg) = msg { - let stderr = stdlib::sys::PyStderr(self); - writeln!(stderr, "{msg}"); + // Write using Python's write() to use stderr's error handler (backslashreplace) + if let Ok(stderr) = stdlib::sys::get_stderr(self) { + let _ = self.call_method(&stderr, "write", (msg,)); + let _ = self.call_method(&stderr, "write", ("\n",)); + } } 1 } else if exc.fast_isinstance(self.ctx.exceptions.keyboard_interrupt) { From 4cf5697e063361b0cb055199b10749576de92896 Mon Sep 17 00:00:00 2001 From: Ashwin Naren <arihant2math@gmail.com> Date: Tue, 30 Dec 2025 18:57:42 -0800 Subject: [PATCH 646/819] Symboltable updates (#5861) * symboltable updates * Fix symboltable --------- Co-authored-by: Jeong YunWon <jeong@youknowone.org> --- crates/codegen/src/symboltable.rs | 92 +++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index 1629e5fff38..07766373d5a 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -137,12 +137,12 @@ pub enum SymbolScope { bitflags! { #[derive(Copy, Clone, Debug, PartialEq)] pub struct SymbolFlags: u16 { - const REFERENCED = 0x001; - const ASSIGNED = 0x002; - const PARAMETER = 0x004; - const ANNOTATED = 0x008; - const IMPORTED = 0x010; - const NONLOCAL = 0x020; + const REFERENCED = 0x001; // USE + const ASSIGNED = 0x002; // DEF_LOCAL + const PARAMETER = 0x004; // DEF_PARAM + const ANNOTATED = 0x008; // DEF_ANNOT + const IMPORTED = 0x010; // DEF_IMPORT + const NONLOCAL = 0x020; // DEF_NONLOCAL // indicates if the symbol gets a value assigned by a named expression in a comprehension // this is required to correct the scope in the analysis. const ASSIGNED_IN_COMPREHENSION = 0x040; @@ -157,8 +157,12 @@ bitflags! { /// def method(self): /// return x // is_free_class /// ``` - const FREE_CLASS = 0x100; - const BOUND = Self::ASSIGNED.bits() | Self::PARAMETER.bits() | Self::IMPORTED.bits() | Self::ITER.bits(); + const FREE_CLASS = 0x100; // DEF_FREE_CLASS + const GLOBAL = 0x200; // DEF_GLOBAL + const COMP_ITER = 0x400; // DEF_COMP_ITER + const COMP_CELL = 0x800; // DEF_COMP_CELL + const TYPE_PARAM = 0x1000; // DEF_TYPE_PARAM + const BOUND = Self::ASSIGNED.bits() | Self::PARAMETER.bits() | Self::IMPORTED.bits() | Self::ITER.bits() | Self::TYPE_PARAM.bits(); } } @@ -611,6 +615,8 @@ struct SymbolTableBuilder { source_file: SourceFile, // Current scope's varnames being collected (temporary storage) current_varnames: Vec<String>, + // Track if we're inside an iterable definition expression (for nested comprehensions) + in_iter_def_exp: bool, } /// Enum to indicate in what mode an expression @@ -634,6 +640,7 @@ impl SymbolTableBuilder { future_annotations: false, source_file, current_varnames: Vec::new(), + in_iter_def_exp: false, }; this.enter_scope("top", CompilerScope::Module, 0); this @@ -720,6 +727,23 @@ impl SymbolTableBuilder { } else { SymbolUsage::Parameter }; + + // Check for duplicate parameter names + let table = self.tables.last().unwrap(); + if table.symbols.contains_key(parameter.name.as_str()) { + return Err(SymbolTableError { + error: format!( + "duplicate parameter '{}' in function definition", + parameter.name + ), + location: Some( + self.source_file + .to_source_code() + .source_location(parameter.name.range.start(), PositionEncoding::Utf8), + ), + }); + } + self.register_ident(¶meter.name, usage) } @@ -871,6 +895,18 @@ impl SymbolTableBuilder { if let Some(alias) = &name.asname { // `import my_module as my_alias` self.register_ident(alias, SymbolUsage::Imported)?; + } else if name.name.as_str() == "*" { + // Star imports are only allowed at module level + if self.tables.last().unwrap().typ != CompilerScope::Module { + return Err(SymbolTableError { + error: "'import *' only allowed at module level".to_string(), + location: Some(self.source_file.to_source_code().source_location( + name.name.range.start(), + PositionEncoding::Utf8, + )), + }); + } + // Don't register star imports as symbols } else { // `import module` self.register_name( @@ -1162,7 +1198,12 @@ impl SymbolTableBuilder { range, .. }) => { + let was_in_iter_def_exp = self.in_iter_def_exp; + if context == ExpressionContext::IterDefinitionExp { + self.in_iter_def_exp = true; + } self.scan_comprehension("genexpr", elt, None, generators, *range)?; + self.in_iter_def_exp = was_in_iter_def_exp; } Expr::ListComp(ExprListComp { elt, @@ -1170,7 +1211,12 @@ impl SymbolTableBuilder { range, node_index: _, }) => { + let was_in_iter_def_exp = self.in_iter_def_exp; + if context == ExpressionContext::IterDefinitionExp { + self.in_iter_def_exp = true; + } self.scan_comprehension("genexpr", elt, None, generators, *range)?; + self.in_iter_def_exp = was_in_iter_def_exp; } Expr::SetComp(ExprSetComp { elt, @@ -1178,7 +1224,12 @@ impl SymbolTableBuilder { range, node_index: _, }) => { + let was_in_iter_def_exp = self.in_iter_def_exp; + if context == ExpressionContext::IterDefinitionExp { + self.in_iter_def_exp = true; + } self.scan_comprehension("genexpr", elt, None, generators, *range)?; + self.in_iter_def_exp = was_in_iter_def_exp; } Expr::DictComp(ExprDictComp { key, @@ -1187,7 +1238,12 @@ impl SymbolTableBuilder { range, node_index: _, }) => { + let was_in_iter_def_exp = self.in_iter_def_exp; + if context == ExpressionContext::IterDefinitionExp { + self.in_iter_def_exp = true; + } self.scan_comprehension("genexpr", key, Some(value), generators, *range)?; + self.in_iter_def_exp = was_in_iter_def_exp; } Expr::Call(ExprCall { func, @@ -1312,8 +1368,8 @@ impl SymbolTableBuilder { node_index: _, }) => { // named expressions are not allowed in the definition of - // comprehension iterator definitions - if let ExpressionContext::IterDefinitionExp = context { + // comprehension iterator definitions (including nested comprehensions) + if context == ExpressionContext::IterDefinitionExp || self.in_iter_def_exp { return Err(SymbolTableError { error: "assignment expression cannot be used in a comprehension iterable expression".to_string(), location: Some(self.source_file.to_source_code().source_location(target.range().start(), PositionEncoding::Utf8)), @@ -1745,6 +1801,7 @@ impl SymbolTableBuilder { } SymbolUsage::Global => { symbol.scope = SymbolScope::GlobalExplicit; + flags.insert(SymbolFlags::GLOBAL); } SymbolUsage::Used => { flags.insert(SymbolFlags::REFERENCED); @@ -1753,21 +1810,20 @@ impl SymbolTableBuilder { flags.insert(SymbolFlags::ITER); } SymbolUsage::TypeParam => { - // Type parameters are always cell variables in their scope - symbol.scope = SymbolScope::Cell; - flags.insert(SymbolFlags::ASSIGNED); + flags.insert(SymbolFlags::ASSIGNED | SymbolFlags::TYPE_PARAM); } } // and even more checking // it is not allowed to assign to iterator variables (by named expressions) - if flags.contains(SymbolFlags::ITER | SymbolFlags::ASSIGNED) - /*&& symbol.is_assign_named_expr_in_comprehension*/ + if flags.contains(SymbolFlags::ITER) + && flags.contains(SymbolFlags::ASSIGNED_IN_COMPREHENSION) { return Err(SymbolTableError { - error: - "assignment expression cannot be used in a comprehension iterable expression" - .to_string(), + error: format!( + "assignment expression cannot rebind comprehension iteration variable '{}'", + symbol.name + ), location, }); } From 37c47fca6ba9a411192059e5ef671a470b286fbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:02:47 +0900 Subject: [PATCH 647/819] Bump qs and express in /wasm/demo (#6603) Bumps [qs](https://github.com/ljharb/qs) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `qs` from 6.13.0 to 6.14.1 - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.13.0...v6.14.1) Updates `express` from 4.21.2 to 4.22.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.2...v4.22.1) --- updated-dependencies: - dependency-name: qs dependency-version: 6.14.1 dependency-type: indirect - dependency-name: express dependency-version: 4.22.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- wasm/demo/package-lock.json | 48 ++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/wasm/demo/package-lock.json b/wasm/demo/package-lock.json index 3acc105e3c3..eb6957b3c54 100644 --- a/wasm/demo/package-lock.json +++ b/wasm/demo/package-lock.json @@ -2205,40 +2205,40 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -2271,6 +2271,22 @@ "dev": true, "license": "MIT" }, + "node_modules/express/node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/express/node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", From bd0aaf6f4f6aaf92283b3f83058ce20208759e67 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Wed, 31 Dec 2025 00:58:34 -0500 Subject: [PATCH 648/819] Updated the hmac + mailbox libraries + associated tests (#6608) * Updated hmac + test library * Updated mailbox library * Added mailbox library test (v3.13.10) --- Lib/hmac.py | 2 +- Lib/mailbox.py | 96 +- Lib/test/test_hmac.py | 8 + Lib/test/test_mailbox.py | 2495 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 2588 insertions(+), 13 deletions(-) create mode 100644 Lib/test/test_mailbox.py diff --git a/Lib/hmac.py b/Lib/hmac.py index 8b4f920db95..8b4eb2fe741 100644 --- a/Lib/hmac.py +++ b/Lib/hmac.py @@ -53,7 +53,7 @@ def __init__(self, key, msg=None, digestmod=''): raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) if not digestmod: - raise TypeError("Missing required parameter 'digestmod'.") + raise TypeError("Missing required argument 'digestmod'.") if _hashopenssl and isinstance(digestmod, (str, _functype)): try: diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 70da07ed2e9..b00d9e8634c 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -395,6 +395,56 @@ def get_file(self, key): f = open(os.path.join(self._path, self._lookup(key)), 'rb') return _ProxyFile(f) + def get_info(self, key): + """Get the keyed message's "info" as a string.""" + subpath = self._lookup(key) + if self.colon in subpath: + return subpath.split(self.colon)[-1] + return '' + + def set_info(self, key, info: str): + """Set the keyed message's "info" string.""" + if not isinstance(info, str): + raise TypeError(f'info must be a string: {type(info)}') + old_subpath = self._lookup(key) + new_subpath = old_subpath.split(self.colon)[0] + if info: + new_subpath += self.colon + info + if new_subpath == old_subpath: + return + old_path = os.path.join(self._path, old_subpath) + new_path = os.path.join(self._path, new_subpath) + os.rename(old_path, new_path) + self._toc[key] = new_subpath + + def get_flags(self, key): + """Return as a string the standard flags that are set on the keyed message.""" + info = self.get_info(key) + if info.startswith('2,'): + return info[2:] + return '' + + def set_flags(self, key, flags: str): + """Set the given flags and unset all others on the keyed message.""" + if not isinstance(flags, str): + raise TypeError(f'flags must be a string: {type(flags)}') + # TODO: check if flags are valid standard flag characters? + self.set_info(key, '2,' + ''.join(sorted(set(flags)))) + + def add_flag(self, key, flag: str): + """Set the given flag(s) without changing others on the keyed message.""" + if not isinstance(flag, str): + raise TypeError(f'flag must be a string: {type(flag)}') + # TODO: check that flag is a valid standard flag character? + self.set_flags(key, ''.join(set(self.get_flags(key)) | set(flag))) + + def remove_flag(self, key, flag: str): + """Unset the given string flag(s) without changing others on the keyed message.""" + if not isinstance(flag, str): + raise TypeError(f'flag must be a string: {type(flag)}') + if self.get_flags(key): + self.set_flags(key, ''.join(set(self.get_flags(key)) - set(flag))) + def iterkeys(self): """Return an iterator over keys.""" self._refresh() @@ -540,6 +590,8 @@ def _refresh(self): for subdir in self._toc_mtimes: path = self._paths[subdir] for entry in os.listdir(path): + if entry.startswith('.'): + continue p = os.path.join(path, entry) if os.path.isdir(p): continue @@ -698,9 +750,13 @@ def flush(self): _sync_close(new_file) # self._file is about to get replaced, so no need to sync. self._file.close() - # Make sure the new file's mode is the same as the old file's - mode = os.stat(self._path).st_mode - os.chmod(new_file.name, mode) + # Make sure the new file's mode and owner are the same as the old file's + info = os.stat(self._path) + os.chmod(new_file.name, info.st_mode) + try: + os.chown(new_file.name, info.st_uid, info.st_gid) + except (AttributeError, OSError): + pass try: os.rename(new_file.name, self._path) except FileExistsError: @@ -778,10 +834,11 @@ def get_message(self, key): """Return a Message representation or raise a KeyError.""" start, stop = self._lookup(key) self._file.seek(start) - from_line = self._file.readline().replace(linesep, b'') + from_line = self._file.readline().replace(linesep, b'').decode('ascii') string = self._file.read(stop - self._file.tell()) msg = self._message_factory(string.replace(linesep, b'\n')) - msg.set_from(from_line[5:].decode('ascii')) + msg.set_unixfrom(from_line) + msg.set_from(from_line[5:]) return msg def get_string(self, key, from_=False): @@ -1089,10 +1146,24 @@ def __len__(self): """Return a count of messages in the mailbox.""" return len(list(self.iterkeys())) + def _open_mh_sequences_file(self, text): + mode = '' if text else 'b' + kwargs = {'encoding': 'ASCII'} if text else {} + path = os.path.join(self._path, '.mh_sequences') + while True: + try: + return open(path, 'r+' + mode, **kwargs) + except FileNotFoundError: + pass + try: + return open(path, 'x+' + mode, **kwargs) + except FileExistsError: + pass + def lock(self): """Lock the mailbox.""" if not self._locked: - self._file = open(os.path.join(self._path, '.mh_sequences'), 'rb+') + self._file = self._open_mh_sequences_file(text=False) _lock_file(self._file) self._locked = True @@ -1146,7 +1217,11 @@ def remove_folder(self, folder): def get_sequences(self): """Return a name-to-key-list dictionary to define each sequence.""" results = {} - with open(os.path.join(self._path, '.mh_sequences'), 'r', encoding='ASCII') as f: + try: + f = open(os.path.join(self._path, '.mh_sequences'), 'r', encoding='ASCII') + except FileNotFoundError: + return results + with f: all_keys = set(self.keys()) for line in f: try: @@ -1169,7 +1244,7 @@ def get_sequences(self): def set_sequences(self, sequences): """Set sequences using the given name-to-key-list dictionary.""" - f = open(os.path.join(self._path, '.mh_sequences'), 'r+', encoding='ASCII') + f = self._open_mh_sequences_file(text=True) try: os.close(os.open(f.name, os.O_WRONLY | os.O_TRUNC)) for name, keys in sequences.items(): @@ -1956,10 +2031,7 @@ def readlines(self, sizehint=None): def __iter__(self): """Iterate over lines.""" - while True: - line = self.readline() - if not line: - return + while line := self.readline(): yield line def tell(self): diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index 74ebcb2fe70..1726975e864 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -505,6 +505,14 @@ def test_exercise_all_methods(self): self.fail("Exception raised during normal usage of HMAC class.") +class UpdateTestCase(unittest.TestCase): + @hashlib_helper.requires_hashdigest('sha256') + def test_with_str_update(self): + with self.assertRaises(TypeError): + h = hmac.new(b"key", digestmod='sha256') + h.update("invalid update") + + class CopyTestCase(unittest.TestCase): @hashlib_helper.requires_hashdigest('sha256') diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py new file mode 100644 index 00000000000..940baf39415 --- /dev/null +++ b/Lib/test/test_mailbox.py @@ -0,0 +1,2495 @@ +import os +import sys +import time +import stat +import socket +import email +import email.message +import re +import io +import tempfile +from test import support +from test.support import import_helper +from test.support import os_helper +from test.support import refleak_helper +from test.support import socket_helper +from test.support.testcase import ExtraAssertions +import unittest +import textwrap +import mailbox +import glob + + +if not socket_helper.has_gethostname: + raise unittest.SkipTest("test requires gethostname()") + + +class TestBase: + + all_mailbox_types = (mailbox.Message, mailbox.MaildirMessage, + mailbox.mboxMessage, mailbox.MHMessage, + mailbox.BabylMessage, mailbox.MMDFMessage) + + def _check_sample(self, msg): + # Inspect a mailbox.Message representation of the sample message + self.assertIsInstance(msg, email.message.Message) + self.assertIsInstance(msg, mailbox.Message) + for key, value in _sample_headers: + self.assertIn(value, msg.get_all(key)) + self.assertTrue(msg.is_multipart()) + self.assertEqual(len(msg.get_payload()), len(_sample_payloads)) + for i, payload in enumerate(_sample_payloads): + part = msg.get_payload(i) + self.assertIsInstance(part, email.message.Message) + self.assertNotIsInstance(part, mailbox.Message) + self.assertEqual(part.get_payload(), payload) + + def _delete_recursively(self, target): + # Delete a file or delete a directory recursively + if os.path.isdir(target): + os_helper.rmtree(target) + elif os.path.exists(target): + os_helper.unlink(target) + + +class TestMailbox(TestBase): + + maxDiff = None + + _factory = None # Overridden by subclasses to reuse tests + _template = 'From: foo\n\n%s\n' + + def setUp(self): + self._path = os_helper.TESTFN + self._delete_recursively(self._path) + self._box = self._factory(self._path) + + def tearDown(self): + self._box.close() + self._delete_recursively(self._path) + + def test_add(self): + # Add copies of a sample message + keys = [] + keys.append(self._box.add(self._template % 0)) + self.assertEqual(len(self._box), 1) + keys.append(self._box.add(mailbox.Message(_sample_message))) + self.assertEqual(len(self._box), 2) + keys.append(self._box.add(email.message_from_string(_sample_message))) + self.assertEqual(len(self._box), 3) + keys.append(self._box.add(io.BytesIO(_bytes_sample_message))) + self.assertEqual(len(self._box), 4) + keys.append(self._box.add(_sample_message)) + self.assertEqual(len(self._box), 5) + keys.append(self._box.add(_bytes_sample_message)) + self.assertEqual(len(self._box), 6) + with self.assertWarns(DeprecationWarning): + keys.append(self._box.add( + io.TextIOWrapper(io.BytesIO(_bytes_sample_message), encoding="utf-8"))) + self.assertEqual(len(self._box), 7) + self.assertEqual(self._box.get_string(keys[0]), self._template % 0) + for i in (1, 2, 3, 4, 5, 6): + self._check_sample(self._box[keys[i]]) + + _nonascii_msg = textwrap.dedent("""\ + From: foo + Subject: Falinaptár házhozszállítással. Már rendeltél? + + 0 + """) + + def test_add_invalid_8bit_bytes_header(self): + key = self._box.add(self._nonascii_msg.encode('latin-1')) + self.assertEqual(len(self._box), 1) + self.assertEqual(self._box.get_bytes(key), + self._nonascii_msg.encode('latin-1')) + + def test_invalid_nonascii_header_as_string(self): + subj = self._nonascii_msg.splitlines()[1] + key = self._box.add(subj.encode('latin-1')) + self.assertEqual(self._box.get_string(key), + 'Subject: =?unknown-8bit?b?RmFsaW5hcHThciBo4Xpob3pzeuFsbO104XNz' + 'YWwuIE3hciByZW5kZWx06Ww/?=\n\n') + + def test_add_nonascii_string_header_raises(self): + with self.assertRaisesRegex(ValueError, "ASCII-only"): + self._box.add(self._nonascii_msg) + self._box.flush() + self.assertEqual(len(self._box), 0) + self.assertMailboxEmpty() + + def test_add_that_raises_leaves_mailbox_empty(self): + class CustomError(Exception): ... + exc_msg = "a fake error" + + def raiser(*args, **kw): + raise CustomError(exc_msg) + support.patch(self, email.generator.BytesGenerator, 'flatten', raiser) + with self.assertRaisesRegex(CustomError, exc_msg): + self._box.add(email.message_from_string("From: Alphöso")) + self.assertEqual(len(self._box), 0) + self._box.close() + self.assertMailboxEmpty() + + _non_latin_bin_msg = textwrap.dedent("""\ + From: foo@bar.com + To: báz + Subject: Maintenant je vous présente mon collègue, le pouf célèbre + \tJean de Baddie + Mime-Version: 1.0 + Content-Type: text/plain; charset="utf-8" + Content-Transfer-Encoding: 8bit + + Да, они летят. + """).encode('utf-8') + + def test_add_8bit_body(self): + key = self._box.add(self._non_latin_bin_msg) + self.assertEqual(self._box.get_bytes(key), + self._non_latin_bin_msg) + with self._box.get_file(key) as f: + self.assertEqual(f.read(), + self._non_latin_bin_msg.replace(b'\n', + os.linesep.encode())) + self.assertEqual(self._box[key].get_payload(), + "Да, они летят.\n") + + def test_add_binary_file(self): + with tempfile.TemporaryFile('wb+') as f: + f.write(_bytes_sample_message) + f.seek(0) + key = self._box.add(f) + self.assertEqual(self._box.get_bytes(key).split(b'\n'), + _bytes_sample_message.split(b'\n')) + + def test_add_binary_nonascii_file(self): + with tempfile.TemporaryFile('wb+') as f: + f.write(self._non_latin_bin_msg) + f.seek(0) + key = self._box.add(f) + self.assertEqual(self._box.get_bytes(key).split(b'\n'), + self._non_latin_bin_msg.split(b'\n')) + + def test_add_text_file_warns(self): + with tempfile.TemporaryFile('w+', encoding='utf-8') as f: + f.write(_sample_message) + f.seek(0) + with self.assertWarns(DeprecationWarning): + key = self._box.add(f) + self.assertEqual(self._box.get_bytes(key).split(b'\n'), + _bytes_sample_message.split(b'\n')) + + def test_add_StringIO_warns(self): + with self.assertWarns(DeprecationWarning): + key = self._box.add(io.StringIO(self._template % "0")) + self.assertEqual(self._box.get_string(key), self._template % "0") + + def test_add_nonascii_StringIO_raises(self): + with self.assertWarns(DeprecationWarning): + with self.assertRaisesRegex(ValueError, "ASCII-only"): + self._box.add(io.StringIO(self._nonascii_msg)) + self.assertEqual(len(self._box), 0) + self._box.close() + self.assertMailboxEmpty() + + def test_remove(self): + # Remove messages using remove() + self._test_remove_or_delitem(self._box.remove) + + def test_delitem(self): + # Remove messages using __delitem__() + self._test_remove_or_delitem(self._box.__delitem__) + + def _test_remove_or_delitem(self, method): + # (Used by test_remove() and test_delitem().) + key0 = self._box.add(self._template % 0) + key1 = self._box.add(self._template % 1) + self.assertEqual(len(self._box), 2) + method(key0) + self.assertEqual(len(self._box), 1) + self.assertRaises(KeyError, lambda: self._box[key0]) + self.assertRaises(KeyError, lambda: method(key0)) + self.assertEqual(self._box.get_string(key1), self._template % 1) + key2 = self._box.add(self._template % 2) + self.assertEqual(len(self._box), 2) + method(key2) + self.assertEqual(len(self._box), 1) + self.assertRaises(KeyError, lambda: self._box[key2]) + self.assertRaises(KeyError, lambda: method(key2)) + self.assertEqual(self._box.get_string(key1), self._template % 1) + method(key1) + self.assertEqual(len(self._box), 0) + self.assertRaises(KeyError, lambda: self._box[key1]) + self.assertRaises(KeyError, lambda: method(key1)) + + def test_discard(self, repetitions=10): + # Discard messages + key0 = self._box.add(self._template % 0) + key1 = self._box.add(self._template % 1) + self.assertEqual(len(self._box), 2) + self._box.discard(key0) + self.assertEqual(len(self._box), 1) + self.assertRaises(KeyError, lambda: self._box[key0]) + self._box.discard(key0) + self.assertEqual(len(self._box), 1) + self.assertRaises(KeyError, lambda: self._box[key0]) + + def test_get(self): + # Retrieve messages using get() + key0 = self._box.add(self._template % 0) + msg = self._box.get(key0) + self.assertEqual(msg['from'], 'foo') + self.assertEqual(msg.get_payload(), '0\n') + self.assertIsNone(self._box.get('foo')) + self.assertIs(self._box.get('foo', False), False) + self._box.close() + self._box = self._factory(self._path) + key1 = self._box.add(self._template % 1) + msg = self._box.get(key1) + self.assertEqual(msg['from'], 'foo') + self.assertEqual(msg.get_payload(), '1\n') + + def test_getitem(self): + # Retrieve message using __getitem__() + key0 = self._box.add(self._template % 0) + msg = self._box[key0] + self.assertEqual(msg['from'], 'foo') + self.assertEqual(msg.get_payload(), '0\n') + self.assertRaises(KeyError, lambda: self._box['foo']) + self._box.discard(key0) + self.assertRaises(KeyError, lambda: self._box[key0]) + + def test_get_message(self): + # Get Message representations of messages + key0 = self._box.add(self._template % 0) + key1 = self._box.add(_sample_message) + msg0 = self._box.get_message(key0) + self.assertIsInstance(msg0, mailbox.Message) + self.assertEqual(msg0['from'], 'foo') + self.assertEqual(msg0.get_payload(), '0\n') + self._check_sample(self._box.get_message(key1)) + + def test_get_bytes(self): + # Get bytes representations of messages + key0 = self._box.add(self._template % 0) + key1 = self._box.add(_sample_message) + self.assertEqual(self._box.get_bytes(key0), + (self._template % 0).encode('ascii')) + self.assertEqual(self._box.get_bytes(key1), _bytes_sample_message) + + def test_get_string(self): + # Get string representations of messages + key0 = self._box.add(self._template % 0) + key1 = self._box.add(_sample_message) + self.assertEqual(self._box.get_string(key0), self._template % 0) + self.assertEqual(self._box.get_string(key1).split('\n'), + _sample_message.split('\n')) + + def test_get_file(self): + # Get file representations of messages + key0 = self._box.add(self._template % 0) + key1 = self._box.add(_sample_message) + with self._box.get_file(key0) as file: + data0 = file.read() + with self._box.get_file(key1) as file: + data1 = file.read() + self.assertEqual(data0.decode('ascii').replace(os.linesep, '\n'), + self._template % 0) + self.assertEqual(data1.decode('ascii').replace(os.linesep, '\n'), + _sample_message) + + def test_get_file_can_be_closed_twice(self): + # Issue 11700 + key = self._box.add(_sample_message) + f = self._box.get_file(key) + f.close() + f.close() + + def test_iterkeys(self): + # Get keys using iterkeys() + self._check_iteration(self._box.iterkeys, do_keys=True, do_values=False) + + def test_keys(self): + # Get keys using keys() + self._check_iteration(self._box.keys, do_keys=True, do_values=False) + + def test_itervalues(self): + # Get values using itervalues() + self._check_iteration(self._box.itervalues, do_keys=False, + do_values=True) + + def test_iter(self): + # Get values using __iter__() + self._check_iteration(self._box.__iter__, do_keys=False, + do_values=True) + + def test_values(self): + # Get values using values() + self._check_iteration(self._box.values, do_keys=False, do_values=True) + + def test_iteritems(self): + # Get keys and values using iteritems() + self._check_iteration(self._box.iteritems, do_keys=True, + do_values=True) + + def test_items(self): + # Get keys and values using items() + self._check_iteration(self._box.items, do_keys=True, do_values=True) + + def _check_iteration(self, method, do_keys, do_values, repetitions=10): + for value in method(): + self.fail("Not empty") + keys, values = [], [] + for i in range(repetitions): + keys.append(self._box.add(self._template % i)) + values.append(self._template % i) + if do_keys and not do_values: + returned_keys = list(method()) + elif do_values and not do_keys: + returned_values = list(method()) + else: + returned_keys, returned_values = [], [] + for key, value in method(): + returned_keys.append(key) + returned_values.append(value) + if do_keys: + self.assertEqual(len(keys), len(returned_keys)) + self.assertEqual(set(keys), set(returned_keys)) + if do_values: + count = 0 + for value in returned_values: + self.assertEqual(value['from'], 'foo') + self.assertLess(int(value.get_payload()), repetitions) + count += 1 + self.assertEqual(len(values), count) + + def test_contains(self): + # Check existence of keys using __contains__() + self.assertNotIn('foo', self._box) + key0 = self._box.add(self._template % 0) + self.assertIn(key0, self._box) + self.assertNotIn('foo', self._box) + key1 = self._box.add(self._template % 1) + self.assertIn(key1, self._box) + self.assertIn(key0, self._box) + self.assertNotIn('foo', self._box) + self._box.remove(key0) + self.assertNotIn(key0, self._box) + self.assertIn(key1, self._box) + self.assertNotIn('foo', self._box) + self._box.remove(key1) + self.assertNotIn(key1, self._box) + self.assertNotIn(key0, self._box) + self.assertNotIn('foo', self._box) + + def test_len(self, repetitions=10): + # Get message count + keys = [] + for i in range(repetitions): + self.assertEqual(len(self._box), i) + keys.append(self._box.add(self._template % i)) + self.assertEqual(len(self._box), i + 1) + for i in range(repetitions): + self.assertEqual(len(self._box), repetitions - i) + self._box.remove(keys[i]) + self.assertEqual(len(self._box), repetitions - i - 1) + + def test_set_item(self): + # Modify messages using __setitem__() + key0 = self._box.add(self._template % 'original 0') + self.assertEqual(self._box.get_string(key0), + self._template % 'original 0') + key1 = self._box.add(self._template % 'original 1') + self.assertEqual(self._box.get_string(key1), + self._template % 'original 1') + self._box[key0] = self._template % 'changed 0' + self.assertEqual(self._box.get_string(key0), + self._template % 'changed 0') + self._box[key1] = self._template % 'changed 1' + self.assertEqual(self._box.get_string(key1), + self._template % 'changed 1') + self._box[key0] = _sample_message + self._check_sample(self._box[key0]) + self._box[key1] = self._box[key0] + self._check_sample(self._box[key1]) + self._box[key0] = self._template % 'original 0' + self.assertEqual(self._box.get_string(key0), + self._template % 'original 0') + self._check_sample(self._box[key1]) + self.assertRaises(KeyError, + lambda: self._box.__setitem__('foo', 'bar')) + self.assertRaises(KeyError, lambda: self._box['foo']) + self.assertEqual(len(self._box), 2) + + def test_clear(self, iterations=10): + # Remove all messages using clear() + keys = [] + for i in range(iterations): + self._box.add(self._template % i) + for i, key in enumerate(keys): + self.assertEqual(self._box.get_string(key), self._template % i) + self._box.clear() + self.assertEqual(len(self._box), 0) + for i, key in enumerate(keys): + self.assertRaises(KeyError, lambda: self._box.get_string(key)) + + def test_pop(self): + # Get and remove a message using pop() + key0 = self._box.add(self._template % 0) + self.assertIn(key0, self._box) + key1 = self._box.add(self._template % 1) + self.assertIn(key1, self._box) + self.assertEqual(self._box.pop(key0).get_payload(), '0\n') + self.assertNotIn(key0, self._box) + self.assertIn(key1, self._box) + key2 = self._box.add(self._template % 2) + self.assertIn(key2, self._box) + self.assertEqual(self._box.pop(key2).get_payload(), '2\n') + self.assertNotIn(key2, self._box) + self.assertIn(key1, self._box) + self.assertEqual(self._box.pop(key1).get_payload(), '1\n') + self.assertNotIn(key1, self._box) + self.assertEqual(len(self._box), 0) + + def test_popitem(self, iterations=10): + # Get and remove an arbitrary (key, message) using popitem() + keys = [] + for i in range(10): + keys.append(self._box.add(self._template % i)) + seen = [] + for i in range(10): + key, msg = self._box.popitem() + self.assertIn(key, keys) + self.assertNotIn(key, seen) + seen.append(key) + self.assertEqual(int(msg.get_payload()), keys.index(key)) + self.assertEqual(len(self._box), 0) + for key in keys: + self.assertRaises(KeyError, lambda: self._box[key]) + + def test_update(self): + # Modify multiple messages using update() + key0 = self._box.add(self._template % 'original 0') + key1 = self._box.add(self._template % 'original 1') + key2 = self._box.add(self._template % 'original 2') + self._box.update({key0: self._template % 'changed 0', + key2: _sample_message}) + self.assertEqual(len(self._box), 3) + self.assertEqual(self._box.get_string(key0), + self._template % 'changed 0') + self.assertEqual(self._box.get_string(key1), + self._template % 'original 1') + self._check_sample(self._box[key2]) + self._box.update([(key2, self._template % 'changed 2'), + (key1, self._template % 'changed 1'), + (key0, self._template % 'original 0')]) + self.assertEqual(len(self._box), 3) + self.assertEqual(self._box.get_string(key0), + self._template % 'original 0') + self.assertEqual(self._box.get_string(key1), + self._template % 'changed 1') + self.assertEqual(self._box.get_string(key2), + self._template % 'changed 2') + self.assertRaises(KeyError, + lambda: self._box.update({'foo': 'bar', + key0: self._template % "changed 0"})) + self.assertEqual(len(self._box), 3) + self.assertEqual(self._box.get_string(key0), + self._template % "changed 0") + self.assertEqual(self._box.get_string(key1), + self._template % "changed 1") + self.assertEqual(self._box.get_string(key2), + self._template % "changed 2") + + def test_flush(self): + # Write changes to disk + self._test_flush_or_close(self._box.flush, True) + + def test_popitem_and_flush_twice(self): + # See #15036. + self._box.add(self._template % 0) + self._box.add(self._template % 1) + self._box.flush() + + self._box.popitem() + self._box.flush() + self._box.popitem() + self._box.flush() + + def test_lock_unlock(self): + # Lock and unlock the mailbox + self.assertFalse(os.path.exists(self._get_lock_path())) + self._box.lock() + self.assertTrue(os.path.exists(self._get_lock_path())) + self._box.unlock() + self.assertFalse(os.path.exists(self._get_lock_path())) + + def test_close(self): + # Close mailbox and flush changes to disk + self._test_flush_or_close(self._box.close, False) + + def _test_flush_or_close(self, method, should_call_close): + contents = [self._template % i for i in range(3)] + self._box.add(contents[0]) + self._box.add(contents[1]) + self._box.add(contents[2]) + oldbox = self._box + method() + if should_call_close: + self._box.close() + self._box = self._factory(self._path) + keys = self._box.keys() + self.assertEqual(len(keys), 3) + for key in keys: + self.assertIn(self._box.get_string(key), contents) + oldbox.close() + + def test_dump_message(self): + # Write message representations to disk + for input in (email.message_from_string(_sample_message), + _sample_message, io.BytesIO(_bytes_sample_message)): + output = io.BytesIO() + self._box._dump_message(input, output) + self.assertEqual(output.getvalue(), + _bytes_sample_message.replace(b'\n', os.linesep.encode())) + output = io.BytesIO() + self.assertRaises(TypeError, + lambda: self._box._dump_message(None, output)) + + def _get_lock_path(self): + # Return the path of the dot lock file. May be overridden. + return self._path + '.lock' + + +class TestMailboxSuperclass(TestBase, unittest.TestCase): + + def test_notimplemented(self): + # Test that all Mailbox methods raise NotImplementedException. + box = mailbox.Mailbox('path') + self.assertRaises(NotImplementedError, lambda: box.add('')) + self.assertRaises(NotImplementedError, lambda: box.remove('')) + self.assertRaises(NotImplementedError, lambda: box.__delitem__('')) + self.assertRaises(NotImplementedError, lambda: box.discard('')) + self.assertRaises(NotImplementedError, lambda: box.__setitem__('', '')) + self.assertRaises(NotImplementedError, lambda: box.iterkeys()) + self.assertRaises(NotImplementedError, lambda: box.keys()) + self.assertRaises(NotImplementedError, lambda: box.itervalues().__next__()) + self.assertRaises(NotImplementedError, lambda: box.__iter__().__next__()) + self.assertRaises(NotImplementedError, lambda: box.values()) + self.assertRaises(NotImplementedError, lambda: box.iteritems().__next__()) + self.assertRaises(NotImplementedError, lambda: box.items()) + self.assertRaises(NotImplementedError, lambda: box.get('')) + self.assertRaises(NotImplementedError, lambda: box.__getitem__('')) + self.assertRaises(NotImplementedError, lambda: box.get_message('')) + self.assertRaises(NotImplementedError, lambda: box.get_string('')) + self.assertRaises(NotImplementedError, lambda: box.get_bytes('')) + self.assertRaises(NotImplementedError, lambda: box.get_file('')) + self.assertRaises(NotImplementedError, lambda: '' in box) + self.assertRaises(NotImplementedError, lambda: box.__contains__('')) + self.assertRaises(NotImplementedError, lambda: box.__len__()) + self.assertRaises(NotImplementedError, lambda: box.clear()) + self.assertRaises(NotImplementedError, lambda: box.pop('')) + self.assertRaises(NotImplementedError, lambda: box.popitem()) + self.assertRaises(NotImplementedError, lambda: box.update((('', ''),))) + self.assertRaises(NotImplementedError, lambda: box.flush()) + self.assertRaises(NotImplementedError, lambda: box.lock()) + self.assertRaises(NotImplementedError, lambda: box.unlock()) + self.assertRaises(NotImplementedError, lambda: box.close()) + + +class TestMaildir(TestMailbox, unittest.TestCase): + + _factory = lambda self, path, factory=None: mailbox.Maildir(path, factory) + + def setUp(self): + TestMailbox.setUp(self) + if (os.name == 'nt') or (sys.platform == 'cygwin'): + self._box.colon = '!' + + def assertMailboxEmpty(self): + self.assertEqual(os.listdir(os.path.join(self._path, 'tmp')), []) + + def test_add_MM(self): + # Add a MaildirMessage instance + msg = mailbox.MaildirMessage(self._template % 0) + msg.set_subdir('cur') + msg.set_info('foo') + key = self._box.add(msg) + self.assertTrue(os.path.exists(os.path.join(self._path, 'cur', '%s%sfoo' % + (key, self._box.colon)))) + + def test_get_MM(self): + # Get a MaildirMessage instance + msg = mailbox.MaildirMessage(self._template % 0) + msg.set_subdir('cur') + msg.set_flags('RF') + key = self._box.add(msg) + msg_returned = self._box.get_message(key) + self.assertIsInstance(msg_returned, mailbox.MaildirMessage) + self.assertEqual(msg_returned.get_subdir(), 'cur') + self.assertEqual(msg_returned.get_flags(), 'FR') + + def test_set_MM(self): + # Set with a MaildirMessage instance + msg0 = mailbox.MaildirMessage(self._template % 0) + msg0.set_flags('TP') + key = self._box.add(msg0) + msg_returned = self._box.get_message(key) + self.assertEqual(msg_returned.get_subdir(), 'new') + self.assertEqual(msg_returned.get_flags(), 'PT') + msg1 = mailbox.MaildirMessage(self._template % 1) + self._box[key] = msg1 + msg_returned = self._box.get_message(key) + self.assertEqual(msg_returned.get_subdir(), 'new') + self.assertEqual(msg_returned.get_flags(), '') + self.assertEqual(msg_returned.get_payload(), '1\n') + msg2 = mailbox.MaildirMessage(self._template % 2) + msg2.set_info('2,S') + self._box[key] = msg2 + self._box[key] = self._template % 3 + msg_returned = self._box.get_message(key) + self.assertEqual(msg_returned.get_subdir(), 'new') + self.assertEqual(msg_returned.get_flags(), 'S') + self.assertEqual(msg_returned.get_payload(), '3\n') + + def test_consistent_factory(self): + # Add a message. + msg = mailbox.MaildirMessage(self._template % 0) + msg.set_subdir('cur') + msg.set_flags('RF') + key = self._box.add(msg) + + # Create new mailbox with + class FakeMessage(mailbox.MaildirMessage): + pass + box = mailbox.Maildir(self._path, factory=FakeMessage) + box.colon = self._box.colon + msg2 = box.get_message(key) + self.assertIsInstance(msg2, FakeMessage) + + def test_initialize_new(self): + # Initialize a non-existent mailbox + self.tearDown() + self._box = mailbox.Maildir(self._path) + self._check_basics() + self._delete_recursively(self._path) + self._box = self._factory(self._path, factory=None) + self._check_basics() + + def test_initialize_existing(self): + # Initialize an existing mailbox + self.tearDown() + for subdir in '', 'tmp', 'new', 'cur': + os.mkdir(os.path.normpath(os.path.join(self._path, subdir))) + self._box = mailbox.Maildir(self._path) + self._check_basics() + + def test_filename_leading_dot(self): + self.tearDown() + for subdir in '', 'tmp', 'new', 'cur': + os.mkdir(os.path.normpath(os.path.join(self._path, subdir))) + for subdir in 'tmp', 'new', 'cur': + fname = os.path.join(self._path, subdir, '.foo' + subdir) + with open(fname, 'wb') as f: + f.write(b"@") + self._box = mailbox.Maildir(self._path) + self.assertNotIn('.footmp', self._box) + self.assertNotIn('.foonew', self._box) + self.assertNotIn('.foocur', self._box) + self.assertEqual(list(self._box.iterkeys()), []) + + def _check_basics(self, factory=None): + # (Used by test_open_new() and test_open_existing().) + self.assertEqual(self._box._path, os.path.abspath(self._path)) + self.assertEqual(self._box._factory, factory) + for subdir in '', 'tmp', 'new', 'cur': + path = os.path.join(self._path, subdir) + self.assertTrue(os.path.isdir(path), f"Not a directory: {path!r}") + + def test_list_folders(self): + # List folders + self._box.add_folder('one') + self._box.add_folder('two') + self._box.add_folder('three') + self.assertEqual(len(self._box.list_folders()), 3) + self.assertEqual(set(self._box.list_folders()), + set(('one', 'two', 'three'))) + + def test_get_folder(self): + # Open folders + self._box.add_folder('foo.bar') + folder0 = self._box.get_folder('foo.bar') + folder0.add(self._template % 'bar') + self.assertTrue(os.path.isdir(os.path.join(self._path, '.foo.bar'))) + folder1 = self._box.get_folder('foo.bar') + self.assertEqual(folder1.get_string(folder1.keys()[0]), + self._template % 'bar') + + def test_add_and_remove_folders(self): + # Delete folders + self._box.add_folder('one') + self._box.add_folder('two') + self.assertEqual(len(self._box.list_folders()), 2) + self.assertEqual(set(self._box.list_folders()), set(('one', 'two'))) + self._box.remove_folder('one') + self.assertEqual(len(self._box.list_folders()), 1) + self.assertEqual(set(self._box.list_folders()), set(('two',))) + self._box.add_folder('three') + self.assertEqual(len(self._box.list_folders()), 2) + self.assertEqual(set(self._box.list_folders()), set(('two', 'three'))) + self._box.remove_folder('three') + self.assertEqual(len(self._box.list_folders()), 1) + self.assertEqual(set(self._box.list_folders()), set(('two',))) + self._box.remove_folder('two') + self.assertEqual(len(self._box.list_folders()), 0) + self.assertEqual(self._box.list_folders(), []) + + def test_clean(self): + # Remove old files from 'tmp' + foo_path = os.path.join(self._path, 'tmp', 'foo') + bar_path = os.path.join(self._path, 'tmp', 'bar') + with open(foo_path, 'w', encoding='utf-8') as f: + f.write("@") + with open(bar_path, 'w', encoding='utf-8') as f: + f.write("@") + self._box.clean() + self.assertTrue(os.path.exists(foo_path)) + self.assertTrue(os.path.exists(bar_path)) + foo_stat = os.stat(foo_path) + os.utime(foo_path, (time.time() - 129600 - 2, + foo_stat.st_mtime)) + self._box.clean() + self.assertFalse(os.path.exists(foo_path)) + self.assertTrue(os.path.exists(bar_path)) + + def test_create_tmp(self, repetitions=10): + # Create files in tmp directory + hostname = socket.gethostname() + if '/' in hostname: + hostname = hostname.replace('/', r'\057') + if ':' in hostname: + hostname = hostname.replace(':', r'\072') + pid = os.getpid() + pattern = re.compile(r"(?P<time>\d+)\.M(?P<M>\d{1,6})P(?P<P>\d+)" + r"Q(?P<Q>\d+)\.(?P<host>[^:/]*)") + previous_groups = None + for x in range(repetitions): + tmp_file = self._box._create_tmp() + head, tail = os.path.split(tmp_file.name) + self.assertEqual(head, os.path.abspath(os.path.join(self._path, + "tmp")), + "File in wrong location: '%s'" % head) + match = pattern.match(tail) + self.assertIsNotNone(match, "Invalid file name: '%s'" % tail) + groups = match.groups() + if previous_groups is not None: + self.assertGreaterEqual(int(groups[0]), int(previous_groups[0]), + "Non-monotonic seconds: '%s' before '%s'" % + (previous_groups[0], groups[0])) + if int(groups[0]) == int(previous_groups[0]): + self.assertGreaterEqual(int(groups[1]), int(previous_groups[1]), + "Non-monotonic milliseconds: '%s' before '%s'" % + (previous_groups[1], groups[1])) + self.assertEqual(int(groups[2]), pid, + "Process ID mismatch: '%s' should be '%s'" % + (groups[2], pid)) + self.assertEqual(int(groups[3]), int(previous_groups[3]) + 1, + "Non-sequential counter: '%s' before '%s'" % + (previous_groups[3], groups[3])) + self.assertEqual(groups[4], hostname, + "Host name mismatch: '%s' should be '%s'" % + (groups[4], hostname)) + previous_groups = groups + tmp_file.write(_bytes_sample_message) + tmp_file.seek(0) + self.assertEqual(tmp_file.read(), _bytes_sample_message) + tmp_file.close() + file_count = len(os.listdir(os.path.join(self._path, "tmp"))) + self.assertEqual(file_count, repetitions, + "Wrong file count: '%s' should be '%s'" % + (file_count, repetitions)) + + def test_refresh(self): + # Update the table of contents + self.assertEqual(self._box._toc, {}) + key0 = self._box.add(self._template % 0) + key1 = self._box.add(self._template % 1) + self.assertEqual(self._box._toc, {}) + self._box._refresh() + self.assertEqual(self._box._toc, {key0: os.path.join('new', key0), + key1: os.path.join('new', key1)}) + key2 = self._box.add(self._template % 2) + self.assertEqual(self._box._toc, {key0: os.path.join('new', key0), + key1: os.path.join('new', key1)}) + self._box._refresh() + self.assertEqual(self._box._toc, {key0: os.path.join('new', key0), + key1: os.path.join('new', key1), + key2: os.path.join('new', key2)}) + + def test_refresh_after_safety_period(self): + # Issue #13254: Call _refresh after the "file system safety + # period" of 2 seconds has passed; _toc should still be + # updated because this is the first call to _refresh. + key0 = self._box.add(self._template % 0) + key1 = self._box.add(self._template % 1) + + self._box = self._factory(self._path) + self.assertEqual(self._box._toc, {}) + + # Emulate sleeping. Instead of sleeping for 2 seconds, use the + # skew factor to make _refresh think that the filesystem + # safety period has passed and re-reading the _toc is only + # required if mtimes differ. + self._box._skewfactor = -3 + + self._box._refresh() + self.assertEqual(sorted(self._box._toc.keys()), sorted([key0, key1])) + + def test_lookup(self): + # Look up message subpaths in the TOC + self.assertRaises(KeyError, lambda: self._box._lookup('foo')) + key0 = self._box.add(self._template % 0) + self.assertEqual(self._box._lookup(key0), os.path.join('new', key0)) + os.remove(os.path.join(self._path, 'new', key0)) + self.assertEqual(self._box._toc, {key0: os.path.join('new', key0)}) + # Be sure that the TOC is read back from disk (see issue #6896 + # about bad mtime behaviour on some systems). + self._box.flush() + self.assertRaises(KeyError, lambda: self._box._lookup(key0)) + self.assertEqual(self._box._toc, {}) + + def test_lock_unlock(self): + # Lock and unlock the mailbox. For Maildir, this does nothing. + self._box.lock() + self._box.unlock() + + def test_get_info(self): + # Test getting message info from Maildir, not the message. + msg = mailbox.MaildirMessage(self._template % 0) + key = self._box.add(msg) + self.assertEqual(self._box.get_info(key), '') + msg.set_info('OurTestInfo') + self._box[key] = msg + self.assertEqual(self._box.get_info(key), 'OurTestInfo') + + def test_set_info(self): + # Test setting message info from Maildir, not the message. + # This should immediately rename the message file. + msg = mailbox.MaildirMessage(self._template % 0) + key = self._box.add(msg) + def check_info(oldinfo, newinfo): + oldfilename = os.path.join(self._box._path, self._box._lookup(key)) + newsubpath = self._box._lookup(key).split(self._box.colon)[0] + if newinfo: + newsubpath += self._box.colon + newinfo + newfilename = os.path.join(self._box._path, newsubpath) + # assert initial conditions + self.assertEqual(self._box.get_info(key), oldinfo) + if not oldinfo: + self.assertNotIn(self._box._lookup(key), self._box.colon) + self.assertTrue(os.path.exists(oldfilename)) + if oldinfo != newinfo: + self.assertFalse(os.path.exists(newfilename)) + # do the rename + self._box.set_info(key, newinfo) + # assert post conditions + if not newinfo: + self.assertNotIn(self._box._lookup(key), self._box.colon) + if oldinfo != newinfo: + self.assertFalse(os.path.exists(oldfilename)) + self.assertTrue(os.path.exists(newfilename)) + self.assertEqual(self._box.get_info(key), newinfo) + # none -> has info + check_info('', 'info1') + # has info -> same info + check_info('info1', 'info1') + # has info -> different info + check_info('info1', 'info2') + # has info -> none + check_info('info2', '') + # none -> none + check_info('', '') + + def test_get_flags(self): + # Test getting message flags from Maildir, not the message. + msg = mailbox.MaildirMessage(self._template % 0) + key = self._box.add(msg) + self.assertEqual(self._box.get_flags(key), '') + msg.set_flags('T') + self._box[key] = msg + self.assertEqual(self._box.get_flags(key), 'T') + + def test_set_flags(self): + msg = mailbox.MaildirMessage(self._template % 0) + key = self._box.add(msg) + self.assertEqual(self._box.get_flags(key), '') + self._box.set_flags(key, 'S') + self.assertEqual(self._box.get_flags(key), 'S') + + def test_add_flag(self): + msg = mailbox.MaildirMessage(self._template % 0) + key = self._box.add(msg) + self.assertEqual(self._box.get_flags(key), '') + self._box.add_flag(key, 'B') + self.assertEqual(self._box.get_flags(key), 'B') + self._box.add_flag(key, 'B') + self.assertEqual(self._box.get_flags(key), 'B') + self._box.add_flag(key, 'AC') + self.assertEqual(self._box.get_flags(key), 'ABC') + + def test_remove_flag(self): + msg = mailbox.MaildirMessage(self._template % 0) + key = self._box.add(msg) + self._box.set_flags(key, 'abc') + self.assertEqual(self._box.get_flags(key), 'abc') + self._box.remove_flag(key, 'b') + self.assertEqual(self._box.get_flags(key), 'ac') + self._box.remove_flag(key, 'b') + self.assertEqual(self._box.get_flags(key), 'ac') + self._box.remove_flag(key, 'ac') + self.assertEqual(self._box.get_flags(key), '') + + def test_folder (self): + # Test for bug #1569790: verify that folders returned by .get_folder() + # use the same factory function. + def dummy_factory (s): + return None + box = self._factory(self._path, factory=dummy_factory) + folder = box.add_folder('folder1') + self.assertIs(folder._factory, dummy_factory) + + folder1_alias = box.get_folder('folder1') + self.assertIs(folder1_alias._factory, dummy_factory) + + def test_directory_in_folder (self): + # Test that mailboxes still work if there's a stray extra directory + # in a folder. + for i in range(10): + self._box.add(mailbox.Message(_sample_message)) + + # Create a stray directory + os.mkdir(os.path.join(self._path, 'cur', 'stray-dir')) + + # Check that looping still works with the directory present. + for msg in self._box: + pass + + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + def test_file_permissions(self): + # Verify that message files are created without execute permissions + msg = mailbox.MaildirMessage(self._template % 0) + orig_umask = os.umask(0) + try: + key = self._box.add(msg) + finally: + os.umask(orig_umask) + path = os.path.join(self._path, self._box._lookup(key)) + mode = os.stat(path).st_mode + self.assertFalse(mode & 0o111) + + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + def test_folder_file_perms(self): + # From bug #3228, we want to verify that the file created inside a Maildir + # subfolder isn't marked as executable. + orig_umask = os.umask(0) + try: + subfolder = self._box.add_folder('subfolder') + finally: + os.umask(orig_umask) + + path = os.path.join(subfolder._path, 'maildirfolder') + st = os.stat(path) + perms = st.st_mode + self.assertFalse((perms & 0o111)) # Execute bits should all be off. + + def test_reread(self): + # Do an initial unconditional refresh + self._box._refresh() + + # Put the last modified times more than two seconds into the past + # (because mtime may have a two second granularity) + for subdir in ('cur', 'new'): + os.utime(os.path.join(self._box._path, subdir), + (time.time()-5,)*2) + + # Because mtime has a two second granularity in worst case (FAT), a + # refresh is done unconditionally if called for within + # two-second-plus-a-bit of the last one, just in case the mbox has + # changed; so now we have to wait for that interval to expire. + # + # Because this is a test, emulate sleeping. Instead of + # sleeping for 2 seconds, use the skew factor to make _refresh + # think that 2 seconds have passed and re-reading the _toc is + # only required if mtimes differ. + self._box._skewfactor = -3 + + # Re-reading causes the ._toc attribute to be assigned a new dictionary + # object, so we'll check that the ._toc attribute isn't a different + # object. + orig_toc = self._box._toc + def refreshed(): + return self._box._toc is not orig_toc + + self._box._refresh() + self.assertFalse(refreshed()) + + # Now, write something into cur and remove it. This changes + # the mtime and should cause a re-read. Note that "sleep + # emulation" is still in effect, as skewfactor is -3. + filename = os.path.join(self._path, 'cur', 'stray-file') + os_helper.create_empty_file(filename) + os.unlink(filename) + self._box._refresh() + self.assertTrue(refreshed()) + + +class _TestSingleFile(TestMailbox): + '''Common tests for single-file mailboxes''' + + def test_add_doesnt_rewrite(self): + # When only adding messages, flush() should not rewrite the + # mailbox file. See issue #9559. + + # Inode number changes if the contents are written to another + # file which is then renamed over the original file. So we + # must check that the inode number doesn't change. + inode_before = os.stat(self._path).st_ino + + self._box.add(self._template % 0) + self._box.flush() + + inode_after = os.stat(self._path).st_ino + self.assertEqual(inode_before, inode_after) + + # Make sure the message was really added + self._box.close() + self._box = self._factory(self._path) + self.assertEqual(len(self._box), 1) + + def test_permissions_after_flush(self): + # See issue #5346 + + # Make the mailbox world writable. It's unlikely that the new + # mailbox file would have these permissions after flush(), + # because umask usually prevents it. + mode = os.stat(self._path).st_mode | 0o666 + os.chmod(self._path, mode) + + self._box.add(self._template % 0) + i = self._box.add(self._template % 1) + # Need to remove one message to make flush() create a new file + self._box.remove(i) + self._box.flush() + + self.assertEqual(os.stat(self._path).st_mode, mode) + + @unittest.skipUnless(hasattr(os, 'chown'), 'requires os.chown') + def test_ownership_after_flush(self): + # See issue gh-117467 + + pwd = import_helper.import_module('pwd') + grp = import_helper.import_module('grp') + st = os.stat(self._path) + + for e in pwd.getpwall(): + if e.pw_uid != st.st_uid: + other_uid = e.pw_uid + break + else: + self.skipTest("test needs more than one user") + + for e in grp.getgrall(): + if e.gr_gid != st.st_gid: + other_gid = e.gr_gid + break + else: + self.skipTest("test needs more than one group") + + try: + os.chown(self._path, other_uid, other_gid) + except OSError: + self.skipTest('test needs root privilege') + # Change permissions as in test_permissions_after_flush. + mode = st.st_mode | 0o666 + os.chmod(self._path, mode) + + self._box.add(self._template % 0) + i = self._box.add(self._template % 1) + # Need to remove one message to make flush() create a new file + self._box.remove(i) + self._box.flush() + + st = os.stat(self._path) + self.assertEqual(st.st_uid, other_uid) + self.assertEqual(st.st_gid, other_gid) + self.assertEqual(st.st_mode, mode) + + +class _TestMboxMMDF(_TestSingleFile): + + def tearDown(self): + super().tearDown() + self._box.close() + self._delete_recursively(self._path) + for lock_remnant in glob.glob(glob.escape(self._path) + '.*'): + os_helper.unlink(lock_remnant) + + def assertMailboxEmpty(self): + with open(self._path, 'rb') as f: + self.assertEqual(f.readlines(), []) + + def test_get_bytes_from(self): + # Get bytes representations of messages with _unixfrom. + unixfrom = 'From foo@bar blah\n' + key0 = self._box.add(unixfrom + self._template % 0) + key1 = self._box.add(unixfrom + _sample_message) + self.assertEqual(self._box.get_bytes(key0, from_=False), + (self._template % 0).encode('ascii')) + self.assertEqual(self._box.get_bytes(key1, from_=False), + _bytes_sample_message) + self.assertEqual(self._box.get_bytes(key0, from_=True), + (unixfrom + self._template % 0).encode('ascii')) + self.assertEqual(self._box.get_bytes(key1, from_=True), + unixfrom.encode('ascii') + _bytes_sample_message) + + def test_get_string_from(self): + # Get string representations of messages with _unixfrom. + unixfrom = 'From foo@bar blah\n' + key0 = self._box.add(unixfrom + self._template % 0) + key1 = self._box.add(unixfrom + _sample_message) + self.assertEqual(self._box.get_string(key0, from_=False), + self._template % 0) + self.assertEqual(self._box.get_string(key1, from_=False).split('\n'), + _sample_message.split('\n')) + self.assertEqual(self._box.get_string(key0, from_=True), + unixfrom + self._template % 0) + self.assertEqual(self._box.get_string(key1, from_=True).split('\n'), + (unixfrom + _sample_message).split('\n')) + + def test_add_from_string(self): + # Add a string starting with 'From ' to the mailbox + key = self._box.add('From foo@bar blah\nFrom: foo\n\n0\n') + self.assertEqual(self._box[key].get_from(), 'foo@bar blah') + self.assertEqual(self._box[key].get_unixfrom(), 'From foo@bar blah') + self.assertEqual(self._box[key].get_payload(), '0\n') + + def test_add_from_bytes(self): + # Add a byte string starting with 'From ' to the mailbox + key = self._box.add(b'From foo@bar blah\nFrom: foo\n\n0\n') + self.assertEqual(self._box[key].get_from(), 'foo@bar blah') + self.assertEqual(self._box[key].get_unixfrom(), 'From foo@bar blah') + self.assertEqual(self._box[key].get_payload(), '0\n') + + def test_add_mbox_or_mmdf_message(self): + # Add an mboxMessage or MMDFMessage + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg = class_('From foo@bar blah\nFrom: foo\n\n0\n') + key = self._box.add(msg) + + def test_open_close_open(self): + # Open and inspect previously-created mailbox + values = [self._template % i for i in range(3)] + for value in values: + self._box.add(value) + self._box.close() + mtime = os.path.getmtime(self._path) + self._box = self._factory(self._path) + self.assertEqual(len(self._box), 3) + for key in self._box.iterkeys(): + self.assertIn(self._box.get_string(key), values) + self._box.close() + self.assertEqual(mtime, os.path.getmtime(self._path)) + + def test_add_and_close(self): + # Verifying that closing a mailbox doesn't change added items + self._box.add(_sample_message) + for i in range(3): + self._box.add(self._template % i) + self._box.add(_sample_message) + self._box._file.flush() + self._box._file.seek(0) + contents = self._box._file.read() + self._box.close() + with open(self._path, 'rb') as f: + self.assertEqual(contents, f.read()) + self._box = self._factory(self._path) + + @support.requires_fork() + @unittest.skipUnless(hasattr(socket, 'socketpair'), "Test needs socketpair().") + def test_lock_conflict(self): + # Fork off a child process that will lock the mailbox temporarily, + # unlock it and exit. + c, p = socket.socketpair() + self.addCleanup(c.close) + self.addCleanup(p.close) + + pid = os.fork() + if pid == 0: + # child + try: + # lock the mailbox, and signal the parent it can proceed + self._box.lock() + c.send(b'c') + + # wait until the parent is done, and unlock the mailbox + c.recv(1) + self._box.unlock() + finally: + os._exit(0) + + # In the parent, wait until the child signals it locked the mailbox. + p.recv(1) + try: + self.assertRaises(mailbox.ExternalClashError, + self._box.lock) + finally: + # Signal the child it can now release the lock and exit. + p.send(b'p') + # Wait for child to exit. Locking should now succeed. + support.wait_process(pid, exitcode=0) + + self._box.lock() + self._box.unlock() + + def test_relock(self): + # Test case for bug #1575506: the mailbox class was locking the + # wrong file object in its flush() method. + msg = "Subject: sub\n\nbody\n" + key1 = self._box.add(msg) + self._box.flush() + self._box.close() + + self._box = self._factory(self._path) + self._box.lock() + key2 = self._box.add(msg) + self._box.flush() + self.assertTrue(self._box._locked) + self._box.close() + + +class TestMbox(_TestMboxMMDF, unittest.TestCase, ExtraAssertions): + + _factory = lambda self, path, factory=None: mailbox.mbox(path, factory) + + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + def test_file_perms(self): + # From bug #3228, we want to verify that the mailbox file isn't executable, + # even if the umask is set to something that would leave executable bits set. + # We only run this test on platforms that support umask. + try: + old_umask = os.umask(0o077) + self._box.close() + os.unlink(self._path) + self._box = mailbox.mbox(self._path, create=True) + self._box.add('') + self._box.close() + finally: + os.umask(old_umask) + + st = os.stat(self._path) + perms = st.st_mode + self.assertFalse((perms & 0o111)) # Execute bits should all be off. + + def test_terminating_newline(self): + message = email.message.Message() + message['From'] = 'john@example.com' + message.set_payload('No newline at the end') + i = self._box.add(message) + + # A newline should have been appended to the payload + message = self._box.get(i) + self.assertEqual(message.get_payload(), 'No newline at the end\n') + + def test_message_separator(self): + # Check there's always a single blank line after each message + self._box.add('From: foo\n\n0') # No newline at the end + with open(self._path, encoding='utf-8') as f: + data = f.read() + self.assertEndsWith(data, '0\n\n') + + self._box.add('From: foo\n\n0\n') # Newline at the end + with open(self._path, encoding='utf-8') as f: + data = f.read() + self.assertEndsWith(data, '0\n\n') + + +class TestMMDF(_TestMboxMMDF, unittest.TestCase): + + _factory = lambda self, path, factory=None: mailbox.MMDF(path, factory) + + +class TestMH(TestMailbox, unittest.TestCase): + + _factory = lambda self, path, factory=None: mailbox.MH(path, factory) + + def assertMailboxEmpty(self): + self.assertEqual(os.listdir(self._path), ['.mh_sequences']) + + def test_list_folders(self): + # List folders + self._box.add_folder('one') + self._box.add_folder('two') + self._box.add_folder('three') + self.assertEqual(len(self._box.list_folders()), 3) + self.assertEqual(set(self._box.list_folders()), + set(('one', 'two', 'three'))) + + def test_get_folder(self): + # Open folders + def dummy_factory (s): + return None + self._box = self._factory(self._path, dummy_factory) + + new_folder = self._box.add_folder('foo.bar') + folder0 = self._box.get_folder('foo.bar') + folder0.add(self._template % 'bar') + self.assertTrue(os.path.isdir(os.path.join(self._path, 'foo.bar'))) + folder1 = self._box.get_folder('foo.bar') + self.assertEqual(folder1.get_string(folder1.keys()[0]), + self._template % 'bar') + + # Test for bug #1569790: verify that folders returned by .get_folder() + # use the same factory function. + self.assertIs(new_folder._factory, self._box._factory) + self.assertIs(folder0._factory, self._box._factory) + + def test_add_and_remove_folders(self): + # Delete folders + self._box.add_folder('one') + self._box.add_folder('two') + self.assertEqual(len(self._box.list_folders()), 2) + self.assertEqual(set(self._box.list_folders()), set(('one', 'two'))) + self._box.remove_folder('one') + self.assertEqual(len(self._box.list_folders()), 1) + self.assertEqual(set(self._box.list_folders()), set(('two',))) + self._box.add_folder('three') + self.assertEqual(len(self._box.list_folders()), 2) + self.assertEqual(set(self._box.list_folders()), set(('two', 'three'))) + self._box.remove_folder('three') + self.assertEqual(len(self._box.list_folders()), 1) + self.assertEqual(set(self._box.list_folders()), set(('two',))) + self._box.remove_folder('two') + self.assertEqual(len(self._box.list_folders()), 0) + self.assertEqual(self._box.list_folders(), []) + + def test_sequences(self): + # Get and set sequences + self.assertEqual(self._box.get_sequences(), {}) + msg0 = mailbox.MHMessage(self._template % 0) + msg0.add_sequence('foo') + key0 = self._box.add(msg0) + self.assertEqual(self._box.get_sequences(), {'foo':[key0]}) + msg1 = mailbox.MHMessage(self._template % 1) + msg1.set_sequences(['bar', 'replied', 'foo']) + key1 = self._box.add(msg1) + self.assertEqual(self._box.get_sequences(), + {'foo':[key0, key1], 'bar':[key1], 'replied':[key1]}) + msg0.set_sequences(['flagged']) + self._box[key0] = msg0 + self.assertEqual(self._box.get_sequences(), + {'foo':[key1], 'bar':[key1], 'replied':[key1], + 'flagged':[key0]}) + self._box.remove(key1) + self.assertEqual(self._box.get_sequences(), {'flagged':[key0]}) + + self._box.set_sequences({'foo':[key0]}) + self.assertEqual(self._box.get_sequences(), {'foo':[key0]}) + + def test_no_dot_mh_sequences_file(self): + path = os.path.join(self._path, 'foo.bar') + os.mkdir(path) + box = self._factory(path) + self.assertEqual(os.listdir(path), []) + self.assertEqual(box.get_sequences(), {}) + self.assertEqual(os.listdir(path), []) + box.set_sequences({}) + self.assertEqual(os.listdir(path), ['.mh_sequences']) + + def test_lock_unlock_no_dot_mh_sequences_file(self): + path = os.path.join(self._path, 'foo.bar') + os.mkdir(path) + box = self._factory(path) + self.assertEqual(os.listdir(path), []) + box.lock() + box.unlock() + self.assertEqual(os.listdir(path), ['.mh_sequences']) + + def test_issue2625(self): + msg0 = mailbox.MHMessage(self._template % 0) + msg0.add_sequence('foo') + key0 = self._box.add(msg0) + refmsg0 = self._box.get_message(key0) + + def test_issue7627(self): + msg0 = mailbox.MHMessage(self._template % 0) + key0 = self._box.add(msg0) + self._box.lock() + self._box.remove(key0) + self._box.unlock() + + def test_pack(self): + # Pack the contents of the mailbox + msg0 = mailbox.MHMessage(self._template % 0) + msg1 = mailbox.MHMessage(self._template % 1) + msg2 = mailbox.MHMessage(self._template % 2) + msg3 = mailbox.MHMessage(self._template % 3) + msg0.set_sequences(['foo', 'unseen']) + msg1.set_sequences(['foo']) + msg2.set_sequences(['foo', 'flagged']) + msg3.set_sequences(['foo', 'bar', 'replied']) + key0 = self._box.add(msg0) + key1 = self._box.add(msg1) + key2 = self._box.add(msg2) + key3 = self._box.add(msg3) + self.assertEqual(self._box.get_sequences(), + {'foo':[key0,key1,key2,key3], 'unseen':[key0], + 'flagged':[key2], 'bar':[key3], 'replied':[key3]}) + self._box.remove(key2) + self.assertEqual(self._box.get_sequences(), + {'foo':[key0,key1,key3], 'unseen':[key0], 'bar':[key3], + 'replied':[key3]}) + self._box.pack() + self.assertEqual(self._box.keys(), [1, 2, 3]) + key0 = key0 + key1 = key0 + 1 + key2 = key1 + 1 + self.assertEqual(self._box.get_sequences(), + {'foo':[1, 2, 3], 'unseen':[1], 'bar':[3], 'replied':[3]}) + + # Test case for packing while holding the mailbox locked. + key0 = self._box.add(msg1) + key1 = self._box.add(msg1) + key2 = self._box.add(msg1) + key3 = self._box.add(msg1) + + self._box.remove(key0) + self._box.remove(key2) + self._box.lock() + self._box.pack() + self._box.unlock() + self.assertEqual(self._box.get_sequences(), + {'foo':[1, 2, 3, 4, 5], + 'unseen':[1], 'bar':[3], 'replied':[3]}) + + def _get_lock_path(self): + return os.path.join(self._path, '.mh_sequences.lock') + + +class TestBabyl(_TestSingleFile, unittest.TestCase): + + _factory = lambda self, path, factory=None: mailbox.Babyl(path, factory) + + def assertMailboxEmpty(self): + with open(self._path, 'rb') as f: + self.assertEqual(f.readlines(), []) + + def tearDown(self): + super().tearDown() + self._box.close() + self._delete_recursively(self._path) + for lock_remnant in glob.glob(glob.escape(self._path) + '.*'): + os_helper.unlink(lock_remnant) + + def test_labels(self): + # Get labels from the mailbox + self.assertEqual(self._box.get_labels(), []) + msg0 = mailbox.BabylMessage(self._template % 0) + msg0.add_label('foo') + key0 = self._box.add(msg0) + self.assertEqual(self._box.get_labels(), ['foo']) + msg1 = mailbox.BabylMessage(self._template % 1) + msg1.set_labels(['bar', 'answered', 'foo']) + key1 = self._box.add(msg1) + self.assertEqual(set(self._box.get_labels()), set(['foo', 'bar'])) + msg0.set_labels(['blah', 'filed']) + self._box[key0] = msg0 + self.assertEqual(set(self._box.get_labels()), + set(['foo', 'bar', 'blah'])) + self._box.remove(key1) + self.assertEqual(set(self._box.get_labels()), set(['blah'])) + + +class FakeFileLikeObject: + + def __init__(self): + self.closed = False + + def close(self): + self.closed = True + + +class FakeMailBox(mailbox.Mailbox): + + def __init__(self): + mailbox.Mailbox.__init__(self, '', lambda file: None) + self.files = [FakeFileLikeObject() for i in range(10)] + + def get_file(self, key): + return self.files[key] + + +class TestFakeMailBox(unittest.TestCase): + + def test_closing_fd(self): + box = FakeMailBox() + for i in range(10): + self.assertFalse(box.files[i].closed) + for i in range(10): + box[i] + for i in range(10): + self.assertTrue(box.files[i].closed) + + +class TestMessage(TestBase, unittest.TestCase): + + _factory = mailbox.Message # Overridden by subclasses to reuse tests + + def setUp(self): + self._path = os_helper.TESTFN + + def tearDown(self): + self._delete_recursively(self._path) + + def test_initialize_with_eMM(self): + # Initialize based on email.message.Message instance + eMM = email.message_from_string(_sample_message) + msg = self._factory(eMM) + self._post_initialize_hook(msg) + self._check_sample(msg) + + def test_initialize_with_string(self): + # Initialize based on string + msg = self._factory(_sample_message) + self._post_initialize_hook(msg) + self._check_sample(msg) + + def test_initialize_with_file(self): + # Initialize based on contents of file + with open(self._path, 'w+', encoding='utf-8') as f: + f.write(_sample_message) + f.seek(0) + msg = self._factory(f) + self._post_initialize_hook(msg) + self._check_sample(msg) + + def test_initialize_with_binary_file(self): + # Initialize based on contents of binary file + with open(self._path, 'wb+') as f: + f.write(_bytes_sample_message) + f.seek(0) + msg = self._factory(f) + self._post_initialize_hook(msg) + self._check_sample(msg) + + def test_initialize_with_nothing(self): + # Initialize without arguments + msg = self._factory() + self._post_initialize_hook(msg) + self.assertIsInstance(msg, email.message.Message) + self.assertIsInstance(msg, mailbox.Message) + self.assertIsInstance(msg, self._factory) + self.assertEqual(msg.keys(), []) + self.assertFalse(msg.is_multipart()) + self.assertIsNone(msg.get_payload()) + + def test_initialize_incorrectly(self): + # Initialize with invalid argument + self.assertRaises(TypeError, lambda: self._factory(object())) + + def test_all_eMM_attributes_exist(self): + # Issue 12537 + eMM = email.message_from_string(_sample_message) + msg = self._factory(_sample_message) + for attr in eMM.__dict__: + self.assertIn(attr, msg.__dict__, + '{} attribute does not exist'.format(attr)) + + def test_become_message(self): + # Take on the state of another message + eMM = email.message_from_string(_sample_message) + msg = self._factory() + msg._become_message(eMM) + self._check_sample(msg) + + def test_explain_to(self): + # Copy self's format-specific data to other message formats. + # This test is superficial; better ones are in TestMessageConversion. + msg = self._factory() + for class_ in self.all_mailbox_types: + other_msg = class_() + msg._explain_to(other_msg) + other_msg = email.message.Message() + self.assertRaises(TypeError, lambda: msg._explain_to(other_msg)) + + def _post_initialize_hook(self, msg): + # Overridden by subclasses to check extra things after initialization + pass + + +class TestMaildirMessage(TestMessage, unittest.TestCase): + + _factory = mailbox.MaildirMessage + + def _post_initialize_hook(self, msg): + self.assertEqual(msg._subdir, 'new') + self.assertEqual(msg._info, '') + + def test_subdir(self): + # Use get_subdir() and set_subdir() + msg = mailbox.MaildirMessage(_sample_message) + self.assertEqual(msg.get_subdir(), 'new') + msg.set_subdir('cur') + self.assertEqual(msg.get_subdir(), 'cur') + msg.set_subdir('new') + self.assertEqual(msg.get_subdir(), 'new') + self.assertRaises(ValueError, lambda: msg.set_subdir('tmp')) + self.assertEqual(msg.get_subdir(), 'new') + msg.set_subdir('new') + self.assertEqual(msg.get_subdir(), 'new') + self._check_sample(msg) + + def test_flags(self): + # Use get_flags(), set_flags(), add_flag(), remove_flag() + msg = mailbox.MaildirMessage(_sample_message) + self.assertEqual(msg.get_flags(), '') + self.assertEqual(msg.get_subdir(), 'new') + msg.set_flags('F') + self.assertEqual(msg.get_subdir(), 'new') + self.assertEqual(msg.get_flags(), 'F') + msg.set_flags('SDTP') + self.assertEqual(msg.get_flags(), 'DPST') + msg.add_flag('FT') + self.assertEqual(msg.get_flags(), 'DFPST') + msg.remove_flag('TDRP') + self.assertEqual(msg.get_flags(), 'FS') + self.assertEqual(msg.get_subdir(), 'new') + self._check_sample(msg) + + def test_date(self): + # Use get_date() and set_date() + msg = mailbox.MaildirMessage(_sample_message) + self.assertLess(abs(msg.get_date() - time.time()), 60) + msg.set_date(0.0) + self.assertEqual(msg.get_date(), 0.0) + + def test_info(self): + # Use get_info() and set_info() + msg = mailbox.MaildirMessage(_sample_message) + self.assertEqual(msg.get_info(), '') + msg.set_info('1,foo=bar') + self.assertEqual(msg.get_info(), '1,foo=bar') + self.assertRaises(TypeError, lambda: msg.set_info(None)) + self._check_sample(msg) + + def test_info_and_flags(self): + # Test interaction of info and flag methods + msg = mailbox.MaildirMessage(_sample_message) + self.assertEqual(msg.get_info(), '') + msg.set_flags('SF') + self.assertEqual(msg.get_flags(), 'FS') + self.assertEqual(msg.get_info(), '2,FS') + msg.set_info('1,') + self.assertEqual(msg.get_flags(), '') + self.assertEqual(msg.get_info(), '1,') + msg.remove_flag('RPT') + self.assertEqual(msg.get_flags(), '') + self.assertEqual(msg.get_info(), '1,') + msg.add_flag('D') + self.assertEqual(msg.get_flags(), 'D') + self.assertEqual(msg.get_info(), '2,D') + self._check_sample(msg) + + +class _TestMboxMMDFMessage: + + _factory = mailbox._mboxMMDFMessage + + def _post_initialize_hook(self, msg): + self._check_from(msg) + + def test_initialize_with_unixfrom(self): + # Initialize with a message that already has a _unixfrom attribute + msg = mailbox.Message(_sample_message) + msg.set_unixfrom('From foo@bar blah') + msg = mailbox.mboxMessage(msg) + self.assertEqual(msg.get_from(), 'foo@bar blah') + self.assertEqual(msg.get_unixfrom(), 'From foo@bar blah') + + def test_from(self): + # Get and set "From " line + msg = mailbox.mboxMessage(_sample_message) + self._check_from(msg) + self.assertIsNone(msg.get_unixfrom()) + msg.set_from('foo bar') + self.assertEqual(msg.get_from(), 'foo bar') + self.assertIsNone(msg.get_unixfrom()) + msg.set_from('foo@bar', True) + self._check_from(msg, 'foo@bar') + self.assertIsNone(msg.get_unixfrom()) + msg.set_from('blah@temp', time.localtime()) + self._check_from(msg, 'blah@temp') + self.assertIsNone(msg.get_unixfrom()) + + def test_flags(self): + # Use get_flags(), set_flags(), add_flag(), remove_flag() + msg = mailbox.mboxMessage(_sample_message) + self.assertEqual(msg.get_flags(), '') + msg.set_flags('F') + self.assertEqual(msg.get_flags(), 'F') + msg.set_flags('XODR') + self.assertEqual(msg.get_flags(), 'RODX') + msg.add_flag('FA') + self.assertEqual(msg.get_flags(), 'RODFAX') + msg.remove_flag('FDXA') + self.assertEqual(msg.get_flags(), 'RO') + self._check_sample(msg) + + def _check_from(self, msg, sender=None): + # Check contents of "From " line + if sender is None: + sender = "MAILER-DAEMON" + self.assertIsNotNone(re.match( + sender + r" \w{3} \w{3} [\d ]\d [\d ]\d:\d{2}:\d{2} \d{4}", + msg.get_from())) + + +class TestMboxMessage(_TestMboxMMDFMessage, TestMessage): + + _factory = mailbox.mboxMessage + + +class TestMHMessage(TestMessage, unittest.TestCase): + + _factory = mailbox.MHMessage + + def _post_initialize_hook(self, msg): + self.assertEqual(msg._sequences, []) + + def test_sequences(self): + # Get, set, join, and leave sequences + msg = mailbox.MHMessage(_sample_message) + self.assertEqual(msg.get_sequences(), []) + msg.set_sequences(['foobar']) + self.assertEqual(msg.get_sequences(), ['foobar']) + msg.set_sequences([]) + self.assertEqual(msg.get_sequences(), []) + msg.add_sequence('unseen') + self.assertEqual(msg.get_sequences(), ['unseen']) + msg.add_sequence('flagged') + self.assertEqual(msg.get_sequences(), ['unseen', 'flagged']) + msg.add_sequence('flagged') + self.assertEqual(msg.get_sequences(), ['unseen', 'flagged']) + msg.remove_sequence('unseen') + self.assertEqual(msg.get_sequences(), ['flagged']) + msg.add_sequence('foobar') + self.assertEqual(msg.get_sequences(), ['flagged', 'foobar']) + msg.remove_sequence('replied') + self.assertEqual(msg.get_sequences(), ['flagged', 'foobar']) + msg.set_sequences(['foobar', 'replied']) + self.assertEqual(msg.get_sequences(), ['foobar', 'replied']) + + +class TestBabylMessage(TestMessage, unittest.TestCase): + + _factory = mailbox.BabylMessage + + def _post_initialize_hook(self, msg): + self.assertEqual(msg._labels, []) + + def test_labels(self): + # Get, set, join, and leave labels + msg = mailbox.BabylMessage(_sample_message) + self.assertEqual(msg.get_labels(), []) + msg.set_labels(['foobar']) + self.assertEqual(msg.get_labels(), ['foobar']) + msg.set_labels([]) + self.assertEqual(msg.get_labels(), []) + msg.add_label('filed') + self.assertEqual(msg.get_labels(), ['filed']) + msg.add_label('resent') + self.assertEqual(msg.get_labels(), ['filed', 'resent']) + msg.add_label('resent') + self.assertEqual(msg.get_labels(), ['filed', 'resent']) + msg.remove_label('filed') + self.assertEqual(msg.get_labels(), ['resent']) + msg.add_label('foobar') + self.assertEqual(msg.get_labels(), ['resent', 'foobar']) + msg.remove_label('unseen') + self.assertEqual(msg.get_labels(), ['resent', 'foobar']) + msg.set_labels(['foobar', 'answered']) + self.assertEqual(msg.get_labels(), ['foobar', 'answered']) + + def test_visible(self): + # Get, set, and update visible headers + msg = mailbox.BabylMessage(_sample_message) + visible = msg.get_visible() + self.assertEqual(visible.keys(), []) + self.assertIsNone(visible.get_payload()) + visible['User-Agent'] = 'FooBar 1.0' + visible['X-Whatever'] = 'Blah' + self.assertEqual(msg.get_visible().keys(), []) + msg.set_visible(visible) + visible = msg.get_visible() + self.assertEqual(visible.keys(), ['User-Agent', 'X-Whatever']) + self.assertEqual(visible['User-Agent'], 'FooBar 1.0') + self.assertEqual(visible['X-Whatever'], 'Blah') + self.assertIsNone(visible.get_payload()) + msg.update_visible() + self.assertEqual(visible.keys(), ['User-Agent', 'X-Whatever']) + self.assertIsNone(visible.get_payload()) + visible = msg.get_visible() + self.assertEqual(visible.keys(), ['User-Agent', 'Date', 'From', 'To', + 'Subject']) + for header in ('User-Agent', 'Date', 'From', 'To', 'Subject'): + self.assertEqual(visible[header], msg[header]) + + +class TestMMDFMessage(_TestMboxMMDFMessage, TestMessage): + + _factory = mailbox.MMDFMessage + + +class TestMessageConversion(TestBase, unittest.TestCase): + + def test_plain_to_x(self): + # Convert Message to all formats + for class_ in self.all_mailbox_types: + msg_plain = mailbox.Message(_sample_message) + msg = class_(msg_plain) + self._check_sample(msg) + + def test_x_to_plain(self): + # Convert all formats to Message + for class_ in self.all_mailbox_types: + msg = class_(_sample_message) + msg_plain = mailbox.Message(msg) + self._check_sample(msg_plain) + + def test_x_from_bytes(self): + # Convert all formats to Message + for class_ in self.all_mailbox_types: + msg = class_(_bytes_sample_message) + self._check_sample(msg) + + def test_x_to_invalid(self): + # Convert all formats to an invalid format + for class_ in self.all_mailbox_types: + self.assertRaises(TypeError, lambda: class_(False)) + + def test_type_specific_attributes_removed_on_conversion(self): + reference = {class_: class_(_sample_message).__dict__ + for class_ in self.all_mailbox_types} + for class1 in self.all_mailbox_types: + for class2 in self.all_mailbox_types: + if class1 is class2: + continue + source = class1(_sample_message) + target = class2(source) + type_specific = [a for a in reference[class1] + if a not in reference[class2]] + for attr in type_specific: + self.assertNotIn(attr, target.__dict__, + "while converting {} to {}".format(class1, class2)) + + def test_maildir_to_maildir(self): + # Convert MaildirMessage to MaildirMessage + msg_maildir = mailbox.MaildirMessage(_sample_message) + msg_maildir.set_flags('DFPRST') + msg_maildir.set_subdir('cur') + date = msg_maildir.get_date() + msg = mailbox.MaildirMessage(msg_maildir) + self._check_sample(msg) + self.assertEqual(msg.get_flags(), 'DFPRST') + self.assertEqual(msg.get_subdir(), 'cur') + self.assertEqual(msg.get_date(), date) + + def test_maildir_to_mboxmmdf(self): + # Convert MaildirMessage to mboxmessage and MMDFMessage + pairs = (('D', ''), ('F', 'F'), ('P', ''), ('R', 'A'), ('S', 'R'), + ('T', 'D'), ('DFPRST', 'RDFA')) + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg_maildir = mailbox.MaildirMessage(_sample_message) + msg_maildir.set_date(0.0) + for setting, result in pairs: + msg_maildir.set_flags(setting) + msg = class_(msg_maildir) + self.assertEqual(msg.get_flags(), result) + self.assertEqual(msg.get_from(), 'MAILER-DAEMON %s' % + time.asctime(time.gmtime(0.0))) + self.assertIsNone(msg.get_unixfrom()) + msg_maildir.set_subdir('cur') + self.assertEqual(class_(msg_maildir).get_flags(), 'RODFA') + + def test_maildir_to_mh(self): + # Convert MaildirMessage to MHMessage + msg_maildir = mailbox.MaildirMessage(_sample_message) + pairs = (('D', ['unseen']), ('F', ['unseen', 'flagged']), + ('P', ['unseen']), ('R', ['unseen', 'replied']), ('S', []), + ('T', ['unseen']), ('DFPRST', ['replied', 'flagged'])) + for setting, result in pairs: + msg_maildir.set_flags(setting) + self.assertEqual(mailbox.MHMessage(msg_maildir).get_sequences(), + result) + + def test_maildir_to_babyl(self): + # Convert MaildirMessage to Babyl + msg_maildir = mailbox.MaildirMessage(_sample_message) + pairs = (('D', ['unseen']), ('F', ['unseen']), + ('P', ['unseen', 'forwarded']), ('R', ['unseen', 'answered']), + ('S', []), ('T', ['unseen', 'deleted']), + ('DFPRST', ['deleted', 'answered', 'forwarded'])) + for setting, result in pairs: + msg_maildir.set_flags(setting) + self.assertEqual(mailbox.BabylMessage(msg_maildir).get_labels(), + result) + + def test_mboxmmdf_to_maildir(self): + # Convert mboxMessage and MMDFMessage to MaildirMessage + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg_mboxMMDF = class_(_sample_message) + msg_mboxMMDF.set_from('foo@bar', time.gmtime(0.0)) + pairs = (('R', 'S'), ('O', ''), ('D', 'T'), ('F', 'F'), ('A', 'R'), + ('RODFA', 'FRST')) + for setting, result in pairs: + msg_mboxMMDF.set_flags(setting) + msg = mailbox.MaildirMessage(msg_mboxMMDF) + self.assertEqual(msg.get_flags(), result) + self.assertEqual(msg.get_date(), 0.0) + msg_mboxMMDF.set_flags('O') + self.assertEqual(mailbox.MaildirMessage(msg_mboxMMDF).get_subdir(), + 'cur') + + def test_mboxmmdf_to_mboxmmdf(self): + # Convert mboxMessage and MMDFMessage to mboxMessage and MMDFMessage + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg_mboxMMDF = class_(_sample_message) + msg_mboxMMDF.set_flags('RODFA') + msg_mboxMMDF.set_from('foo@bar') + self.assertIsNone(msg_mboxMMDF.get_unixfrom()) + for class2_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg2 = class2_(msg_mboxMMDF) + self.assertEqual(msg2.get_flags(), 'RODFA') + self.assertEqual(msg2.get_from(), 'foo@bar') + self.assertIsNone(msg2.get_unixfrom()) + + def test_mboxmmdf_to_mh(self): + # Convert mboxMessage and MMDFMessage to MHMessage + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg_mboxMMDF = class_(_sample_message) + pairs = (('R', []), ('O', ['unseen']), ('D', ['unseen']), + ('F', ['unseen', 'flagged']), + ('A', ['unseen', 'replied']), + ('RODFA', ['replied', 'flagged'])) + for setting, result in pairs: + msg_mboxMMDF.set_flags(setting) + self.assertEqual(mailbox.MHMessage(msg_mboxMMDF).get_sequences(), + result) + + def test_mboxmmdf_to_babyl(self): + # Convert mboxMessage and MMDFMessage to BabylMessage + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg = class_(_sample_message) + pairs = (('R', []), ('O', ['unseen']), + ('D', ['unseen', 'deleted']), ('F', ['unseen']), + ('A', ['unseen', 'answered']), + ('RODFA', ['deleted', 'answered'])) + for setting, result in pairs: + msg.set_flags(setting) + self.assertEqual(mailbox.BabylMessage(msg).get_labels(), result) + + def test_mh_to_maildir(self): + # Convert MHMessage to MaildirMessage + pairs = (('unseen', ''), ('replied', 'RS'), ('flagged', 'FS')) + for setting, result in pairs: + msg = mailbox.MHMessage(_sample_message) + msg.add_sequence(setting) + self.assertEqual(mailbox.MaildirMessage(msg).get_flags(), result) + self.assertEqual(mailbox.MaildirMessage(msg).get_subdir(), 'cur') + msg = mailbox.MHMessage(_sample_message) + msg.add_sequence('unseen') + msg.add_sequence('replied') + msg.add_sequence('flagged') + self.assertEqual(mailbox.MaildirMessage(msg).get_flags(), 'FR') + self.assertEqual(mailbox.MaildirMessage(msg).get_subdir(), 'cur') + + def test_mh_to_mboxmmdf(self): + # Convert MHMessage to mboxMessage and MMDFMessage + pairs = (('unseen', 'O'), ('replied', 'ROA'), ('flagged', 'ROF')) + for setting, result in pairs: + msg = mailbox.MHMessage(_sample_message) + msg.add_sequence(setting) + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + self.assertEqual(class_(msg).get_flags(), result) + msg = mailbox.MHMessage(_sample_message) + msg.add_sequence('unseen') + msg.add_sequence('replied') + msg.add_sequence('flagged') + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + self.assertEqual(class_(msg).get_flags(), 'OFA') + + def test_mh_to_mh(self): + # Convert MHMessage to MHMessage + msg = mailbox.MHMessage(_sample_message) + msg.add_sequence('unseen') + msg.add_sequence('replied') + msg.add_sequence('flagged') + self.assertEqual(mailbox.MHMessage(msg).get_sequences(), + ['unseen', 'replied', 'flagged']) + + def test_mh_to_babyl(self): + # Convert MHMessage to BabylMessage + pairs = (('unseen', ['unseen']), ('replied', ['answered']), + ('flagged', [])) + for setting, result in pairs: + msg = mailbox.MHMessage(_sample_message) + msg.add_sequence(setting) + self.assertEqual(mailbox.BabylMessage(msg).get_labels(), result) + msg = mailbox.MHMessage(_sample_message) + msg.add_sequence('unseen') + msg.add_sequence('replied') + msg.add_sequence('flagged') + self.assertEqual(mailbox.BabylMessage(msg).get_labels(), + ['unseen', 'answered']) + + def test_babyl_to_maildir(self): + # Convert BabylMessage to MaildirMessage + pairs = (('unseen', ''), ('deleted', 'ST'), ('filed', 'S'), + ('answered', 'RS'), ('forwarded', 'PS'), ('edited', 'S'), + ('resent', 'PS')) + for setting, result in pairs: + msg = mailbox.BabylMessage(_sample_message) + msg.add_label(setting) + self.assertEqual(mailbox.MaildirMessage(msg).get_flags(), result) + self.assertEqual(mailbox.MaildirMessage(msg).get_subdir(), 'cur') + msg = mailbox.BabylMessage(_sample_message) + for label in ('unseen', 'deleted', 'filed', 'answered', 'forwarded', + 'edited', 'resent'): + msg.add_label(label) + self.assertEqual(mailbox.MaildirMessage(msg).get_flags(), 'PRT') + self.assertEqual(mailbox.MaildirMessage(msg).get_subdir(), 'cur') + + def test_babyl_to_mboxmmdf(self): + # Convert BabylMessage to mboxMessage and MMDFMessage + pairs = (('unseen', 'O'), ('deleted', 'ROD'), ('filed', 'RO'), + ('answered', 'ROA'), ('forwarded', 'RO'), ('edited', 'RO'), + ('resent', 'RO')) + for setting, result in pairs: + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg = mailbox.BabylMessage(_sample_message) + msg.add_label(setting) + self.assertEqual(class_(msg).get_flags(), result) + msg = mailbox.BabylMessage(_sample_message) + for label in ('unseen', 'deleted', 'filed', 'answered', 'forwarded', + 'edited', 'resent'): + msg.add_label(label) + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + self.assertEqual(class_(msg).get_flags(), 'ODA') + + def test_babyl_to_mh(self): + # Convert BabylMessage to MHMessage + pairs = (('unseen', ['unseen']), ('deleted', []), ('filed', []), + ('answered', ['replied']), ('forwarded', []), ('edited', []), + ('resent', [])) + for setting, result in pairs: + msg = mailbox.BabylMessage(_sample_message) + msg.add_label(setting) + self.assertEqual(mailbox.MHMessage(msg).get_sequences(), result) + msg = mailbox.BabylMessage(_sample_message) + for label in ('unseen', 'deleted', 'filed', 'answered', 'forwarded', + 'edited', 'resent'): + msg.add_label(label) + self.assertEqual(mailbox.MHMessage(msg).get_sequences(), + ['unseen', 'replied']) + + def test_babyl_to_babyl(self): + # Convert BabylMessage to BabylMessage + msg = mailbox.BabylMessage(_sample_message) + msg.update_visible() + for label in ('unseen', 'deleted', 'filed', 'answered', 'forwarded', + 'edited', 'resent'): + msg.add_label(label) + msg2 = mailbox.BabylMessage(msg) + self.assertEqual(msg2.get_labels(), ['unseen', 'deleted', 'filed', + 'answered', 'forwarded', 'edited', + 'resent']) + self.assertEqual(msg.get_visible().keys(), msg2.get_visible().keys()) + for key in msg.get_visible().keys(): + self.assertEqual(msg.get_visible()[key], msg2.get_visible()[key]) + + +class TestProxyFileBase(TestBase): + + def _test_read(self, proxy): + # Read by byte + proxy.seek(0) + self.assertEqual(proxy.read(), b'bar') + proxy.seek(1) + self.assertEqual(proxy.read(), b'ar') + proxy.seek(0) + self.assertEqual(proxy.read(2), b'ba') + proxy.seek(1) + self.assertEqual(proxy.read(-1), b'ar') + proxy.seek(2) + self.assertEqual(proxy.read(1000), b'r') + + def _test_readline(self, proxy): + # Read by line + linesep = os.linesep.encode() + proxy.seek(0) + self.assertEqual(proxy.readline(), b'foo' + linesep) + self.assertEqual(proxy.readline(), b'bar' + linesep) + self.assertEqual(proxy.readline(), b'fred' + linesep) + self.assertEqual(proxy.readline(), b'bob') + proxy.seek(2) + self.assertEqual(proxy.readline(), b'o' + linesep) + proxy.seek(6 + 2 * len(os.linesep)) + self.assertEqual(proxy.readline(), b'fred' + linesep) + proxy.seek(6 + 2 * len(os.linesep)) + self.assertEqual(proxy.readline(2), b'fr') + self.assertEqual(proxy.readline(-10), b'ed' + linesep) + + def _test_readlines(self, proxy): + # Read multiple lines + linesep = os.linesep.encode() + proxy.seek(0) + self.assertEqual(proxy.readlines(), [b'foo' + linesep, + b'bar' + linesep, + b'fred' + linesep, b'bob']) + proxy.seek(0) + self.assertEqual(proxy.readlines(2), [b'foo' + linesep]) + proxy.seek(3 + len(linesep)) + self.assertEqual(proxy.readlines(4 + len(linesep)), + [b'bar' + linesep, b'fred' + linesep]) + proxy.seek(3) + self.assertEqual(proxy.readlines(1000), [linesep, b'bar' + linesep, + b'fred' + linesep, b'bob']) + + def _test_iteration(self, proxy): + # Iterate by line + linesep = os.linesep.encode() + proxy.seek(0) + iterator = iter(proxy) + self.assertEqual(next(iterator), b'foo' + linesep) + self.assertEqual(next(iterator), b'bar' + linesep) + self.assertEqual(next(iterator), b'fred' + linesep) + self.assertEqual(next(iterator), b'bob') + self.assertRaises(StopIteration, next, iterator) + + def _test_seek_and_tell(self, proxy): + # Seek and use tell to check position + linesep = os.linesep.encode() + proxy.seek(3) + self.assertEqual(proxy.tell(), 3) + self.assertEqual(proxy.read(len(linesep)), linesep) + proxy.seek(2, 1) + self.assertEqual(proxy.read(1 + len(linesep)), b'r' + linesep) + proxy.seek(-3 - len(linesep), 2) + self.assertEqual(proxy.read(3), b'bar') + proxy.seek(2, 0) + self.assertEqual(proxy.read(), b'o' + linesep + b'bar' + linesep) + proxy.seek(100) + self.assertFalse(proxy.read()) + + def _test_close(self, proxy): + # Close a file + self.assertFalse(proxy.closed) + proxy.close() + self.assertTrue(proxy.closed) + # Issue 11700 subsequent closes should be a no-op. + proxy.close() + self.assertTrue(proxy.closed) + + +class TestProxyFile(TestProxyFileBase, unittest.TestCase): + + def setUp(self): + self._path = os_helper.TESTFN + self._file = open(self._path, 'wb+') + + def tearDown(self): + self._file.close() + self._delete_recursively(self._path) + + def test_initialize(self): + # Initialize and check position + self._file.write(b'foo') + pos = self._file.tell() + proxy0 = mailbox._ProxyFile(self._file) + self.assertEqual(proxy0.tell(), pos) + self.assertEqual(self._file.tell(), pos) + proxy1 = mailbox._ProxyFile(self._file, 0) + self.assertEqual(proxy1.tell(), 0) + self.assertEqual(self._file.tell(), pos) + + def test_read(self): + self._file.write(b'bar') + self._test_read(mailbox._ProxyFile(self._file)) + + def test_readline(self): + self._file.write(bytes('foo%sbar%sfred%sbob' % (os.linesep, os.linesep, + os.linesep), 'ascii')) + self._test_readline(mailbox._ProxyFile(self._file)) + + def test_readlines(self): + self._file.write(bytes('foo%sbar%sfred%sbob' % (os.linesep, os.linesep, + os.linesep), 'ascii')) + self._test_readlines(mailbox._ProxyFile(self._file)) + + def test_iteration(self): + self._file.write(bytes('foo%sbar%sfred%sbob' % (os.linesep, os.linesep, + os.linesep), 'ascii')) + self._test_iteration(mailbox._ProxyFile(self._file)) + + def test_seek_and_tell(self): + self._file.write(bytes('foo%sbar%s' % (os.linesep, os.linesep), 'ascii')) + self._test_seek_and_tell(mailbox._ProxyFile(self._file)) + + def test_close(self): + self._file.write(bytes('foo%sbar%s' % (os.linesep, os.linesep), 'ascii')) + self._test_close(mailbox._ProxyFile(self._file)) + + +class TestPartialFile(TestProxyFileBase, unittest.TestCase): + + def setUp(self): + self._path = os_helper.TESTFN + self._file = open(self._path, 'wb+') + + def tearDown(self): + self._file.close() + self._delete_recursively(self._path) + + def test_initialize(self): + # Initialize and check position + self._file.write(bytes('foo' + os.linesep + 'bar', 'ascii')) + pos = self._file.tell() + proxy = mailbox._PartialFile(self._file, 2, 5) + self.assertEqual(proxy.tell(), 0) + self.assertEqual(self._file.tell(), pos) + + def test_read(self): + self._file.write(bytes('***bar***', 'ascii')) + self._test_read(mailbox._PartialFile(self._file, 3, 6)) + + def test_readline(self): + self._file.write(bytes('!!!!!foo%sbar%sfred%sbob!!!!!' % + (os.linesep, os.linesep, os.linesep), 'ascii')) + self._test_readline(mailbox._PartialFile(self._file, 5, + 18 + 3 * len(os.linesep))) + + def test_readlines(self): + self._file.write(bytes('foo%sbar%sfred%sbob?????' % + (os.linesep, os.linesep, os.linesep), 'ascii')) + self._test_readlines(mailbox._PartialFile(self._file, 0, + 13 + 3 * len(os.linesep))) + + def test_iteration(self): + self._file.write(bytes('____foo%sbar%sfred%sbob####' % + (os.linesep, os.linesep, os.linesep), 'ascii')) + self._test_iteration(mailbox._PartialFile(self._file, 4, + 17 + 3 * len(os.linesep))) + + def test_seek_and_tell(self): + self._file.write(bytes('(((foo%sbar%s$$$' % (os.linesep, os.linesep), 'ascii')) + self._test_seek_and_tell(mailbox._PartialFile(self._file, 3, + 9 + 2 * len(os.linesep))) + + def test_close(self): + self._file.write(bytes('&foo%sbar%s^' % (os.linesep, os.linesep), 'ascii')) + self._test_close(mailbox._PartialFile(self._file, 1, + 6 + 3 * len(os.linesep))) + + +## Start: tests from the original module (for backward compatibility). + +FROM_ = "From some.body@dummy.domain Sat Jul 24 13:43:35 2004\n" +DUMMY_MESSAGE = """\ +From: some.body@dummy.domain +To: me@my.domain +Subject: Simple Test + +This is a dummy message. +""" + +class MaildirTestCase(unittest.TestCase): + + def setUp(self): + # create a new maildir mailbox to work with: + self._dir = os_helper.TESTFN + if os.path.isdir(self._dir): + os_helper.rmtree(self._dir) + elif os.path.isfile(self._dir): + os_helper.unlink(self._dir) + os.mkdir(self._dir) + os.mkdir(os.path.join(self._dir, "cur")) + os.mkdir(os.path.join(self._dir, "tmp")) + os.mkdir(os.path.join(self._dir, "new")) + self._counter = 1 + self._msgfiles = [] + + def tearDown(self): + list(map(os.unlink, self._msgfiles)) + os_helper.rmdir(os.path.join(self._dir, "cur")) + os_helper.rmdir(os.path.join(self._dir, "tmp")) + os_helper.rmdir(os.path.join(self._dir, "new")) + os_helper.rmdir(self._dir) + + def createMessage(self, dir, mbox=False): + t = int(time.time() % 1000000) + pid = self._counter + self._counter += 1 + filename = ".".join((str(t), str(pid), "myhostname", "mydomain")) + tmpname = os.path.join(self._dir, "tmp", filename) + newname = os.path.join(self._dir, dir, filename) + with open(tmpname, "w", encoding="utf-8") as fp: + self._msgfiles.append(tmpname) + if mbox: + fp.write(FROM_) + fp.write(DUMMY_MESSAGE) + try: + os.link(tmpname, newname) + except (AttributeError, PermissionError): + with open(newname, "w") as fp: + fp.write(DUMMY_MESSAGE) + self._msgfiles.append(newname) + return tmpname + + def test_empty_maildir(self): + """Test an empty maildir mailbox""" + # Test for regression on bug #117490: + # Make sure the boxes attribute actually gets set. + self.mbox = mailbox.Maildir(os_helper.TESTFN) + #self.assertHasAttr(self.mbox, "boxes") + #self.assertEqual(len(self.mbox.boxes), 0) + self.assertIsNone(self.mbox.next()) + self.assertIsNone(self.mbox.next()) + + def test_nonempty_maildir_cur(self): + self.createMessage("cur") + self.mbox = mailbox.Maildir(os_helper.TESTFN) + #self.assertEqual(len(self.mbox.boxes), 1) + self.assertIsNotNone(self.mbox.next()) + self.assertIsNone(self.mbox.next()) + self.assertIsNone(self.mbox.next()) + + def test_nonempty_maildir_new(self): + self.createMessage("new") + self.mbox = mailbox.Maildir(os_helper.TESTFN) + #self.assertEqual(len(self.mbox.boxes), 1) + self.assertIsNotNone(self.mbox.next()) + self.assertIsNone(self.mbox.next()) + self.assertIsNone(self.mbox.next()) + + def test_nonempty_maildir_both(self): + self.createMessage("cur") + self.createMessage("new") + self.mbox = mailbox.Maildir(os_helper.TESTFN) + #self.assertEqual(len(self.mbox.boxes), 2) + self.assertIsNotNone(self.mbox.next()) + self.assertIsNotNone(self.mbox.next()) + self.assertIsNone(self.mbox.next()) + self.assertIsNone(self.mbox.next()) + +## End: tests from the original module (for backward compatibility). + + +_sample_message = """\ +Return-Path: <gkj@gregorykjohnson.com> +X-Original-To: gkj+person@localhost +Delivered-To: gkj+person@localhost +Received: from localhost (localhost [127.0.0.1]) + by andy.gregorykjohnson.com (Postfix) with ESMTP id 356ED9DD17 + for <gkj+person@localhost>; Wed, 13 Jul 2005 17:23:16 -0400 (EDT) +Delivered-To: gkj@sundance.gregorykjohnson.com +Received: from localhost [127.0.0.1] + by localhost with POP3 (fetchmail-6.2.5) + for gkj+person@localhost (single-drop); Wed, 13 Jul 2005 17:23:16 -0400 (EDT) +Received: from andy.gregorykjohnson.com (andy.gregorykjohnson.com [64.32.235.228]) + by sundance.gregorykjohnson.com (Postfix) with ESMTP id 5B056316746 + for <gkj@gregorykjohnson.com>; Wed, 13 Jul 2005 17:23:11 -0400 (EDT) +Received: by andy.gregorykjohnson.com (Postfix, from userid 1000) + id 490CD9DD17; Wed, 13 Jul 2005 17:23:11 -0400 (EDT) +Date: Wed, 13 Jul 2005 17:23:11 -0400 +From: "Gregory K. Johnson" <gkj@gregorykjohnson.com> +To: gkj@gregorykjohnson.com +Subject: Sample message +Message-ID: <20050713212311.GC4701@andy.gregorykjohnson.com> +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary="NMuMz9nt05w80d4+" +Content-Disposition: inline +User-Agent: Mutt/1.5.9i + + +--NMuMz9nt05w80d4+ +Content-Type: text/plain; charset=us-ascii +Content-Disposition: inline + +This is a sample message. + +-- +Gregory K. Johnson + +--NMuMz9nt05w80d4+ +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="text.gz" +Content-Transfer-Encoding: base64 + +H4sICM2D1UIAA3RleHQAC8nILFYAokSFktSKEoW0zJxUPa7wzJIMhZLyfIWczLzUYj0uAHTs +3FYlAAAA + +--NMuMz9nt05w80d4+-- +""" + +_bytes_sample_message = _sample_message.encode('ascii') + +_sample_headers = [ + ("Return-Path", "<gkj@gregorykjohnson.com>"), + ("X-Original-To", "gkj+person@localhost"), + ("Delivered-To", "gkj+person@localhost"), + ("Received", """from localhost (localhost [127.0.0.1]) + by andy.gregorykjohnson.com (Postfix) with ESMTP id 356ED9DD17 + for <gkj+person@localhost>; Wed, 13 Jul 2005 17:23:16 -0400 (EDT)"""), + ("Delivered-To", "gkj@sundance.gregorykjohnson.com"), + ("Received", """from localhost [127.0.0.1] + by localhost with POP3 (fetchmail-6.2.5) + for gkj+person@localhost (single-drop); Wed, 13 Jul 2005 17:23:16 -0400 (EDT)"""), + ("Received", """from andy.gregorykjohnson.com (andy.gregorykjohnson.com [64.32.235.228]) + by sundance.gregorykjohnson.com (Postfix) with ESMTP id 5B056316746 + for <gkj@gregorykjohnson.com>; Wed, 13 Jul 2005 17:23:11 -0400 (EDT)"""), + ("Received", """by andy.gregorykjohnson.com (Postfix, from userid 1000) + id 490CD9DD17; Wed, 13 Jul 2005 17:23:11 -0400 (EDT)"""), + ("Date", "Wed, 13 Jul 2005 17:23:11 -0400"), + ("From", """"Gregory K. Johnson" <gkj@gregorykjohnson.com>"""), + ("To", "gkj@gregorykjohnson.com"), + ("Subject", "Sample message"), + ("Mime-Version", "1.0"), + ("Content-Type", """multipart/mixed; boundary="NMuMz9nt05w80d4+\""""), + ("Content-Disposition", "inline"), + ("User-Agent", "Mutt/1.5.9i"), +] + +_sample_payloads = ("""This is a sample message. + +-- +Gregory K. Johnson +""", +"""H4sICM2D1UIAA3RleHQAC8nILFYAokSFktSKEoW0zJxUPa7wzJIMhZLyfIWczLzUYj0uAHTs +3FYlAAAA +""") + + +class MiscTestCase(unittest.TestCase): + def test__all__(self): + support.check__all__(self, mailbox, + not_exported={"linesep", "fcntl"}) + + +def tearDownModule(): + support.reap_children() + # reap_children may have re-populated caches: + if refleak_helper.hunting_for_refleaks(): + sys._clear_internal_caches() + + +if __name__ == '__main__': + unittest.main() From 01a5f94e7b581da938a2c4dd7f631f9880dd1873 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 31 Dec 2025 15:01:55 +0900 Subject: [PATCH 649/819] Fix error / warning messages (#6594) * fix functools.partial repr * warn_deprecated_throw_signature * fix function error messages --- Lib/test/test_dataclasses/__init__.py | 2 -- Lib/test/test_functools.py | 2 -- Lib/test/test_generators.py | 1 - Lib/test/test_keywordonlyarg.py | 1 - Lib/test/test_positional_only_arg.py | 4 --- Lib/test/test_typing.py | 1 - crates/vm/src/builtins/asyncgenerator.rs | 5 +++- crates/vm/src/builtins/coroutine.rs | 4 ++- crates/vm/src/builtins/function.rs | 20 ++++++++++++--- crates/vm/src/builtins/generator.rs | 3 ++- crates/vm/src/coroutine.rs | 22 ++++++++++++++++ crates/vm/src/stdlib/functools.rs | 32 ++++++++++-------------- 12 files changed, 61 insertions(+), 36 deletions(-) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 1b16da42648..3f1e2331bc2 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -4728,8 +4728,6 @@ class C: b: int = field(kw_only=True) self.assertEqual(C(42, b=10).__match_args__, ('a',)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_KW_ONLY(self): @dataclass class A: diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 6de5d14bf73..047916caf07 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -465,11 +465,9 @@ def __str__(self): self.assertIn('astr', r) self.assertIn("['sth']", r) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_repr(self): return super().test_repr() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_recursive_repr(self): return super().test_recursive_repr() diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index c6f66085c74..8833a102b36 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -479,7 +479,6 @@ def generator(): with self.assertRaises(StopIteration): gen.throw(E) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: DeprecationWarning not triggered def test_gen_3_arg_deprecation_warning(self): def g(): yield 42 diff --git a/Lib/test/test_keywordonlyarg.py b/Lib/test/test_keywordonlyarg.py index e41e7c051f6..8fd2e89122f 100644 --- a/Lib/test/test_keywordonlyarg.py +++ b/Lib/test/test_keywordonlyarg.py @@ -58,7 +58,6 @@ def testSyntaxForManyArguments(self): fundef = "def f(*, %s):\n pass\n" % ', '.join('i%d' % i for i in range(300)) compile(fundef, "<test>", "single") - @unittest.expectedFailure # TODO: RUSTPYTHON def testTooManyPositionalErrorMessage(self): def f(a, b=None, *, c=None): pass diff --git a/Lib/test/test_positional_only_arg.py b/Lib/test/test_positional_only_arg.py index e0d784325ba..4b27f4f4d3f 100644 --- a/Lib/test/test_positional_only_arg.py +++ b/Lib/test/test_positional_only_arg.py @@ -152,8 +152,6 @@ def f(a, b, /, c): with self.assertRaisesRegex(TypeError, r"f\(\) takes 3 positional arguments but 4 were given"): f(1, 2, 3, 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_positional_only_and_optional_arg_invalid_calls(self): def f(a, b, /, c=3): pass @@ -198,8 +196,6 @@ def f(a, b, /): with self.assertRaisesRegex(TypeError, r"f\(\) takes 2 positional arguments but 3 were given"): f(1, 2, 3) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_positional_only_with_optional_invalid_calls(self): def f(a, b=2, /): pass diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 74ab94eb5c3..3e6c530cecc 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8144,7 +8144,6 @@ class CNT(NamedTuple): self.assertEqual(struct.__annotations__, {}) self.assertIsInstance(struct(), struct) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_namedtuple_errors(self): with self.assertRaises(TypeError): NamedTuple.__new__() diff --git a/crates/vm/src/builtins/asyncgenerator.rs b/crates/vm/src/builtins/asyncgenerator.rs index 483ba6a7f96..9218ccc8a43 100644 --- a/crates/vm/src/builtins/asyncgenerator.rs +++ b/crates/vm/src/builtins/asyncgenerator.rs @@ -4,7 +4,7 @@ use crate::{ builtins::PyBaseExceptionRef, class::PyClassImpl, common::lock::PyMutex, - coroutine::Coro, + coroutine::{Coro, warn_deprecated_throw_signature}, frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, @@ -312,6 +312,7 @@ impl PyAsyncGenASend { return Err(vm.new_runtime_error("cannot reuse already awaited __anext__()/asend()")); } + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; let res = self.ag.inner.throw( self.ag.as_object(), exc_type, @@ -431,6 +432,7 @@ impl PyAsyncGenAThrow { exc_tb: OptionalArg, vm: &VirtualMachine, ) -> PyResult { + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; let ret = self.ag.inner.throw( self.ag.as_object(), exc_type, @@ -601,6 +603,7 @@ impl PyAnextAwaitable { vm: &VirtualMachine, ) -> PyResult { self.check_closed(vm)?; + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; self.state.store(AwaitableState::Iter); let awaitable = self.get_awaitable_iter(vm)?; let result = vm.call_method( diff --git a/crates/vm/src/builtins/coroutine.rs b/crates/vm/src/builtins/coroutine.rs index 21405448693..9e8d5d534f1 100644 --- a/crates/vm/src/builtins/coroutine.rs +++ b/crates/vm/src/builtins/coroutine.rs @@ -2,7 +2,7 @@ use super::{PyCode, PyGenericAlias, PyStrRef, PyType, PyTypeRef}; use crate::{ AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, - coroutine::Coro, + coroutine::{Coro, warn_deprecated_throw_signature}, frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, @@ -108,6 +108,7 @@ impl Py<PyCoroutine> { exc_tb: OptionalArg, vm: &VirtualMachine, ) -> PyResult<PyIterReturn> { + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; self.inner.throw( self.as_object(), exc_type, @@ -181,6 +182,7 @@ impl PyCoroutineWrapper { vm: &VirtualMachine, ) -> PyResult<PyIterReturn> { self.check_closed(vm)?; + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; let result = self.coro.throw(exc_type, exc_val, exc_tb, vm); // Mark as closed if exhausted if let Ok(PyIterReturn::StopIteration(_)) = &result { diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index 95d70afcbc7..3e5048e133f 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -131,11 +131,25 @@ impl PyFunction { } else { // Check the number of positional arguments if nargs > n_expected_args { + let n_defaults = self + .defaults_and_kwdefaults + .lock() + .0 + .as_ref() + .map_or(0, |d| d.len()); + let n_required = n_expected_args - n_defaults; + let takes_msg = if n_defaults > 0 { + format!("from {} to {}", n_required, n_expected_args) + } else { + n_expected_args.to_string() + }; return Err(vm.new_type_error(format!( - "{}() takes {} positional arguments but {} were given", + "{}() takes {} positional argument{} but {} {} given", self.__qualname__(), - n_expected_args, - nargs + takes_msg, + if n_expected_args == 1 { "" } else { "s" }, + nargs, + if nargs == 1 { "was" } else { "were" } ))); } } diff --git a/crates/vm/src/builtins/generator.rs b/crates/vm/src/builtins/generator.rs index 04cd7dd3456..9a1e737500b 100644 --- a/crates/vm/src/builtins/generator.rs +++ b/crates/vm/src/builtins/generator.rs @@ -6,7 +6,7 @@ use super::{PyCode, PyGenericAlias, PyStrRef, PyType, PyTypeRef}; use crate::{ AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, - coroutine::Coro, + coroutine::{Coro, warn_deprecated_throw_signature}, frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, @@ -99,6 +99,7 @@ impl Py<PyGenerator> { exc_tb: OptionalArg, vm: &VirtualMachine, ) -> PyResult<PyIterReturn> { + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; self.inner.throw( self.as_object(), exc_type, diff --git a/crates/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs index ebe3107cb36..13dde152391 100644 --- a/crates/vm/src/coroutine.rs +++ b/crates/vm/src/coroutine.rs @@ -4,6 +4,7 @@ use crate::{ common::lock::PyMutex, exceptions::types::PyBaseException, frame::{ExecutionResult, FrameRef}, + function::OptionalArg, protocol::PyIterReturn, }; use crossbeam_utils::atomic::AtomicCell; @@ -211,3 +212,24 @@ impl Coro { pub fn is_gen_exit(exc: &Py<PyBaseException>, vm: &VirtualMachine) -> bool { exc.fast_isinstance(vm.ctx.exceptions.generator_exit) } + +/// Emit DeprecationWarning for the deprecated 3-argument throw() signature. +pub fn warn_deprecated_throw_signature( + exc_val: &OptionalArg, + exc_tb: &OptionalArg, + vm: &VirtualMachine, +) -> PyResult<()> { + if exc_val.is_present() || exc_tb.is_present() { + crate::warn::warn( + vm.ctx.new_str( + "the (type, val, tb) signature of throw() is deprecated, \ + use throw(val) instead", + ), + Some(vm.ctx.exceptions.deprecation_warning.to_owned()), + 1, + None, + vm, + )?; + } + Ok(()) +} diff --git a/crates/vm/src/stdlib/functools.rs b/crates/vm/src/stdlib/functools.rs index 77f352c78fc..a59fd48f6b2 100644 --- a/crates/vm/src/stdlib/functools.rs +++ b/crates/vm/src/stdlib/functools.rs @@ -43,7 +43,7 @@ mod _functools { } #[pyattr] - #[pyclass(name = "partial", module = "_functools")] + #[pyclass(name = "partial", module = "functools")] #[derive(Debug, PyPayload)] pub struct PyPartial { inner: PyRwLock<PyPartialInner>, @@ -319,28 +319,22 @@ mod _functools { )); } - let class_name = zelf.class().name(); + let qualname = zelf.class().__qualname__(vm); + let qualname_str = qualname + .downcast::<crate::builtins::PyStr>() + .map(|s| s.as_str().to_owned()) + .unwrap_or_else(|_| zelf.class().name().to_owned()); let module = zelf.class().__module__(vm); - let qualified_name = if zelf.class().is(Self::class(&vm.ctx)) { - // For the base partial class, always use functools.partial - "functools.partial".to_owned() - } else { - // For subclasses, check if they're defined in __main__ or test modules - match module.downcast::<crate::builtins::PyStr>() { - Ok(module_str) => { - let module_name = module_str.as_str(); - match module_name { - "builtins" | "" | "__main__" => class_name.to_owned(), - name if name.starts_with("test.") || name == "test" => { - // For test modules, just use the class name without module prefix - class_name.to_owned() - } - _ => format!("{module_name}.{class_name}"), - } + let qualified_name = match module.downcast::<crate::builtins::PyStr>() { + Ok(module_str) => { + let module_name = module_str.as_str(); + match module_name { + "builtins" | "" => qualname_str, + _ => format!("{module_name}.{qualname_str}"), } - Err(_) => class_name.to_owned(), } + Err(_) => qualname_str, }; Ok(format!( From b3b97cac7c2df62a6b1b1005e8a41f0397c6ebcd Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 31 Dec 2025 15:02:08 +0900 Subject: [PATCH 650/819] fix symtable for named_expressions (#6604) * check duplicated type parameter * typealias * fix self.in_comp_inner_loop_target --- Lib/test/test_named_expressions.py | 4 - crates/codegen/src/symboltable.rs | 165 ++++++++++++++++++++++++----- 2 files changed, 136 insertions(+), 33 deletions(-) diff --git a/Lib/test/test_named_expressions.py b/Lib/test/test_named_expressions.py index 03f5384b632..68e4a7704f4 100644 --- a/Lib/test/test_named_expressions.py +++ b/Lib/test/test_named_expressions.py @@ -165,8 +165,6 @@ def test_named_expression_invalid_rebinding_list_comprehension_iteration_variabl with self.assertRaisesRegex(SyntaxError, msg): exec(code, {}, {}) - # TODO: RUSTPYTHON - @unittest.expectedFailure # wrong error message def test_named_expression_invalid_rebinding_list_comprehension_inner_loop(self): cases = [ ("Inner reuse", 'j', "[i for i in range(5) if (j := 0) for j in range(5)]"), @@ -223,8 +221,6 @@ def test_named_expression_invalid_rebinding_set_comprehension_iteration_variable with self.assertRaisesRegex(SyntaxError, msg): exec(code, {}, {}) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_named_expression_invalid_rebinding_set_comprehension_inner_loop(self): cases = [ ("Inner reuse", 'j', "{i for i in range(5) if (j := 0) for j in range(5)}"), diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index 07766373d5a..95f9c0cd400 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -585,7 +585,11 @@ impl SymbolTableAnalyzer { self.analyze_symbol_comprehension(symbol, parent_offset + 1)?; } CompilerScope::TypeParams => { - todo!("analyze symbol comprehension for type params"); + // Named expression in comprehension cannot be used in type params + return Err(SymbolTableError { + error: "assignment expression within a comprehension cannot be used within the definition of a generic".to_string(), + location: None, + }); } } Ok(()) @@ -617,6 +621,14 @@ struct SymbolTableBuilder { current_varnames: Vec<String>, // Track if we're inside an iterable definition expression (for nested comprehensions) in_iter_def_exp: bool, + // Track if we're inside an annotation (yield/await/named expr not allowed) + in_annotation: bool, + // Track if we're inside a type alias (yield/await/named expr not allowed) + in_type_alias: bool, + // Track if we're scanning an inner loop iteration target (not the first generator) + in_comp_inner_loop_target: bool, + // Scope info for error messages (e.g., "a TypeVar bound") + scope_info: Option<&'static str>, } /// Enum to indicate in what mode an expression @@ -641,6 +653,10 @@ impl SymbolTableBuilder { source_file, current_varnames: Vec::new(), in_iter_def_exp: false, + in_annotation: false, + in_type_alias: false, + in_comp_inner_loop_target: false, + scope_info: None, }; this.enter_scope("top", CompilerScope::Module, 0); this @@ -751,7 +767,11 @@ impl SymbolTableBuilder { if self.future_annotations { Ok(()) } else { - self.scan_expression(annotation, ExpressionContext::Load) + let was_in_annotation = self.in_annotation; + self.in_annotation = true; + let result = self.scan_expression(annotation, ExpressionContext::Load); + self.in_annotation = was_in_annotation; + result } } @@ -1020,6 +1040,8 @@ impl SymbolTableBuilder { type_params, .. }) => { + let was_in_type_alias = self.in_type_alias; + self.in_type_alias = true; if let Some(type_params) = type_params { self.enter_type_param_block( "TypeAlias", @@ -1031,6 +1053,7 @@ impl SymbolTableBuilder { } else { self.scan_expression(value, ExpressionContext::Load)?; } + self.in_type_alias = was_in_type_alias; self.scan_expression(name, ExpressionContext::Store)?; } Stmt::IpyEscapeCommand(_) => todo!(), @@ -1067,24 +1090,40 @@ impl SymbolTableBuilder { ) -> SymbolTableResult { use ruff_python_ast::*; - // Check for expressions not allowed in type parameters scope - if let Some(table) = self.tables.last() - && table.typ == CompilerScope::TypeParams - && let Some(keyword) = match expression { - Expr::Yield(_) | Expr::YieldFrom(_) => Some("yield"), - Expr::Await(_) => Some("await"), - Expr::Named(_) => Some("named"), - _ => None, + // Check for expressions not allowed in certain contexts + // (type parameters, annotations, type aliases, TypeVar bounds/defaults) + if let Some(keyword) = match expression { + Expr::Yield(_) | Expr::YieldFrom(_) => Some("yield"), + Expr::Await(_) => Some("await"), + Expr::Named(_) => Some("named"), + _ => None, + } { + // Determine the context name for the error message + // scope_info takes precedence (e.g., "a TypeVar bound") + let context_name = if let Some(scope_info) = self.scope_info { + Some(scope_info) + } else if let Some(table) = self.tables.last() + && table.typ == CompilerScope::TypeParams + { + Some("a type parameter") + } else if self.in_annotation { + Some("an annotation") + } else if self.in_type_alias { + Some("a type alias") + } else { + None + }; + + if let Some(context_name) = context_name { + return Err(SymbolTableError { + error: format!("{keyword} expression cannot be used within {context_name}"), + location: Some( + self.source_file + .to_source_code() + .source_location(expression.range().start(), PositionEncoding::Utf8), + ), + }); } - { - return Err(SymbolTableError { - error: format!("{keyword} expression cannot be used within a type parameter"), - location: Some( - self.source_file - .to_source_code() - .source_location(expression.range().start(), PositionEncoding::Utf8), - ), - }); } match expression { @@ -1430,7 +1469,13 @@ impl SymbolTableBuilder { let mut is_first_generator = true; for generator in generators { + // Set flag for INNER_LOOP_CONFLICT check (only for inner loops, not the first) + if !is_first_generator { + self.in_comp_inner_loop_target = true; + } self.scan_expression(&generator.target, ExpressionContext::Iter)?; + self.in_comp_inner_loop_target = false; + if is_first_generator { is_first_generator = false; } else { @@ -1453,25 +1498,55 @@ impl SymbolTableBuilder { /// Scan type parameter bound or default in a separate scope // = symtable_visit_type_param_bound_or_default - fn scan_type_param_bound_or_default(&mut self, expr: &Expr, name: &str) -> SymbolTableResult { + fn scan_type_param_bound_or_default( + &mut self, + expr: &Expr, + scope_name: &str, + scope_info: &'static str, + ) -> SymbolTableResult { // Enter a new TypeParams scope for the bound/default expression // This allows the expression to access outer scope symbols let line_number = self.line_index_start(expr.range()); - self.enter_scope(name, CompilerScope::TypeParams, line_number); + self.enter_scope(scope_name, CompilerScope::TypeParams, line_number); // Note: In CPython, can_see_class_scope is preserved in the new scope // In RustPython, this is handled through the scope hierarchy + // Set scope_info for better error messages + let old_scope_info = self.scope_info; + self.scope_info = Some(scope_info); + // Scan the expression in this new scope let result = self.scan_expression(expr, ExpressionContext::Load); - // Exit the scope + // Restore scope_info and exit the scope + self.scope_info = old_scope_info; self.leave_scope(); result } fn scan_type_params(&mut self, type_params: &TypeParams) -> SymbolTableResult { + // Check for duplicate type parameter names + let mut seen_names: std::collections::HashSet<&str> = std::collections::HashSet::new(); + for type_param in &type_params.type_params { + let (name, range) = match type_param { + TypeParam::TypeVar(tv) => (tv.name.as_str(), tv.range), + TypeParam::ParamSpec(ps) => (ps.name.as_str(), ps.range), + TypeParam::TypeVarTuple(tvt) => (tvt.name.as_str(), tvt.range), + }; + if !seen_names.insert(name) { + return Err(SymbolTableError { + error: format!("duplicate type parameter '{}'", name), + location: Some( + self.source_file + .to_source_code() + .source_location(range.start(), PositionEncoding::Utf8), + ), + }); + } + } + // Register .type_params as a type parameter (automatically becomes cell variable) self.register_name(".type_params", SymbolUsage::TypeParam, type_params.range)?; @@ -1489,18 +1564,25 @@ impl SymbolTableBuilder { // Process bound in a separate scope if let Some(binding) = bound { - let scope_name = if binding.is_tuple_expr() { - format!("<TypeVar constraint of {name}>") + let (scope_name, scope_info) = if binding.is_tuple_expr() { + ( + format!("<TypeVar constraint of {name}>"), + "a TypeVar constraint", + ) } else { - format!("<TypeVar bound of {name}>") + (format!("<TypeVar bound of {name}>"), "a TypeVar bound") }; - self.scan_type_param_bound_or_default(binding, &scope_name)?; + self.scan_type_param_bound_or_default(binding, &scope_name, scope_info)?; } // Process default in a separate scope if let Some(default_value) = default { let scope_name = format!("<TypeVar default of {name}>"); - self.scan_type_param_bound_or_default(default_value, &scope_name)?; + self.scan_type_param_bound_or_default( + default_value, + &scope_name, + "a TypeVar default", + )?; } } TypeParam::ParamSpec(TypeParamParamSpec { @@ -1514,7 +1596,11 @@ impl SymbolTableBuilder { // Process default in a separate scope if let Some(default_value) = default { let scope_name = format!("<ParamSpec default of {name}>"); - self.scan_type_param_bound_or_default(default_value, &scope_name)?; + self.scan_type_param_bound_or_default( + default_value, + &scope_name, + "a ParamSpec default", + )?; } } TypeParam::TypeVarTuple(TypeParamTypeVarTuple { @@ -1528,7 +1614,11 @@ impl SymbolTableBuilder { // Process default in a separate scope if let Some(default_value) = default { let scope_name = format!("<TypeVarTuple default of {name}>"); - self.scan_type_param_bound_or_default(default_value, &scope_name)?; + self.scan_type_param_bound_or_default( + default_value, + &scope_name, + "a TypeVarTuple default", + )?; } } } @@ -1667,6 +1757,23 @@ impl SymbolTableBuilder { // Some checks for the symbol that present on this scope level: let symbol = if let Some(symbol) = table.symbols.get_mut(name.as_ref()) { let flags = &symbol.flags; + + // INNER_LOOP_CONFLICT: comprehension inner loop cannot rebind + // a variable that was used as a named expression target + // Example: [i for i in range(5) if (j := 0) for j in range(5)] + // Here 'j' is used in named expr first, then as inner loop iter target + if self.in_comp_inner_loop_target + && flags.contains(SymbolFlags::ASSIGNED_IN_COMPREHENSION) + { + return Err(SymbolTableError { + error: format!( + "comprehension inner loop cannot rebind assignment expression target '{}'", + name + ), + location, + }); + } + // Role already set.. match role { SymbolUsage::Global if !symbol.is_global() => { From d3afd465f3d08e56a2b00617921ab7159bde7d97 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 31 Dec 2025 17:52:24 +0900 Subject: [PATCH 651/819] fix concurrent socket close (#6612) --- crates/stdlib/src/socket.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index a37a4fd241d..ea5f39cb40d 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -2107,9 +2107,9 @@ mod _socket { #[pymethod] fn close(&self) -> io::Result<()> { - let sock = self.detach(); - if sock != INVALID_SOCKET as i64 { - close_inner(sock as RawSocket)?; + let sock = self.sock.write().take(); + if let Some(sock) = sock { + close_inner(into_sock_fileno(sock))?; } Ok(()) } From a8cfa36c8a6b595e0f1299882daabc51a852a469 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 31 Dec 2025 20:26:38 +0900 Subject: [PATCH 652/819] Remove duplicated richcompare pymethods and add missing rops' wrapper, __getattr__, proper __bool__ impl (#6579) * slots: remove duplicate pymethods from object * Add missing __rmul__ * migrate __bool__ * subclass_update, __getattr__ --- crates/vm/src/builtins/object.rs | 72 ------------------------- crates/vm/src/builtins/range.rs | 23 +++++--- crates/vm/src/builtins/singletons.rs | 30 +++++------ crates/vm/src/builtins/tuple.rs | 24 ++++++--- crates/vm/src/builtins/weakproxy.rs | 25 ++++++--- crates/vm/src/class.rs | 7 +++ crates/vm/src/stdlib/collections.rs | 25 ++++++--- crates/vm/src/stdlib/ctypes/function.rs | 24 ++++++--- crates/vm/src/stdlib/ctypes/pointer.rs | 21 +++++--- crates/vm/src/stdlib/ctypes/simple.rs | 8 --- crates/vm/src/stdlib/winreg.rs | 15 ++---- crates/vm/src/types/slot.rs | 70 +++++++++++++++++++++++- crates/vm/src/types/slot_defs.rs | 12 +++++ 13 files changed, 203 insertions(+), 153 deletions(-) diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index e73f4b79ae0..6f072542547 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -326,66 +326,6 @@ impl PyBaseObject { Ok(res) } - /// Return self==value. - #[pymethod] - fn __eq__( - zelf: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(&zelf, &value, PyComparisonOp::Eq, vm) - } - - /// Return self!=value. - #[pymethod] - fn __ne__( - zelf: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(&zelf, &value, PyComparisonOp::Ne, vm) - } - - /// Return self<value. - #[pymethod] - fn __lt__( - zelf: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(&zelf, &value, PyComparisonOp::Lt, vm) - } - - /// Return self<=value. - #[pymethod] - fn __le__( - zelf: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(&zelf, &value, PyComparisonOp::Le, vm) - } - - /// Return self>=value. - #[pymethod] - fn __ge__( - zelf: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(&zelf, &value, PyComparisonOp::Ge, vm) - } - - /// Return self>value. - #[pymethod] - fn __gt__( - zelf: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<PyComparisonValue> { - Self::cmp(&zelf, &value, PyComparisonOp::Gt, vm) - } - /// Implement setattr(self, name, value). #[pymethod] fn __setattr__( @@ -450,12 +390,6 @@ impl PyBaseObject { } } - /// Return repr(self). - #[pymethod] - fn __repr__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { - Self::slot_repr(&zelf, vm) - } - #[pyclassmethod] fn __subclasshook__(_args: FuncArgs, vm: &VirtualMachine) -> PyObjectRef { vm.ctx.not_implemented() @@ -590,12 +524,6 @@ impl PyBaseObject { Ok(zelf.get_id() as _) } - /// Return hash(self). - #[pymethod] - fn __hash__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyHash> { - Self::slot_hash(&zelf, vm) - } - #[pymethod] fn __sizeof__(zelf: PyObjectRef) -> usize { zelf.class().slots.basicsize diff --git a/crates/vm/src/builtins/range.rs b/crates/vm/src/builtins/range.rs index ab84c977ccd..fac5206b82d 100644 --- a/crates/vm/src/builtins/range.rs +++ b/crates/vm/src/builtins/range.rs @@ -8,9 +8,9 @@ use crate::{ class::PyClassImpl, common::hash::PyHash, function::{ArgIndex, FuncArgs, OptionalArg, PyComparisonValue}, - protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods}, + protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, types::{ - AsMapping, AsSequence, Comparable, Hashable, IterNext, Iterable, PyComparisonOp, + AsMapping, AsNumber, AsSequence, Comparable, Hashable, IterNext, Iterable, PyComparisonOp, Representable, SelfIter, }, }; @@ -176,6 +176,7 @@ pub fn init(context: &Context) { with( Py, AsMapping, + AsNumber, AsSequence, Hashable, Comparable, @@ -269,11 +270,6 @@ impl PyRange { self.compute_length() } - #[pymethod] - fn __bool__(&self) -> bool { - !self.is_empty() - } - #[pymethod] fn __reduce__(&self, vm: &VirtualMachine) -> (PyTypeRef, PyTupleRef) { let range_parameters: Vec<PyObjectRef> = [&self.start, &self.stop, &self.step] @@ -426,6 +422,19 @@ impl AsSequence for PyRange { } } +impl AsNumber for PyRange { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|number, _vm| { + let zelf = number.obj.downcast_ref::<PyRange>().unwrap(); + Ok(!zelf.is_empty()) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + impl Hashable for PyRange { fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> { let length = zelf.compute_length(); diff --git a/crates/vm/src/builtins/singletons.rs b/crates/vm/src/builtins/singletons.rs index bdc032cc865..61ab1968a45 100644 --- a/crates/vm/src/builtins/singletons.rs +++ b/crates/vm/src/builtins/singletons.rs @@ -50,12 +50,7 @@ impl Constructor for PyNone { } #[pyclass(with(Constructor, AsNumber, Representable))] -impl PyNone { - #[pymethod] - const fn __bool__(&self) -> bool { - false - } -} +impl PyNone {} impl Representable for PyNone { #[inline] @@ -103,22 +98,27 @@ impl Constructor for PyNotImplemented { } } -#[pyclass(with(Constructor))] +#[pyclass(with(Constructor, AsNumber, Representable))] impl PyNotImplemented { - // TODO: As per https://bugs.python.org/issue35712, using NotImplemented - // in boolean contexts will need to raise a DeprecationWarning in 3.9 - // and, eventually, a TypeError. - #[pymethod] - const fn __bool__(&self) -> bool { - true - } - #[pymethod] fn __reduce__(&self, vm: &VirtualMachine) -> PyStrRef { vm.ctx.names.NotImplemented.to_owned() } } +impl AsNumber for PyNotImplemented { + fn as_number() -> &'static PyNumberMethods { + // TODO: As per https://bugs.python.org/issue35712, using NotImplemented + // in boolean contexts will need to raise a DeprecationWarning in 3.9 + // and, eventually, a TypeError. + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|_number, _vm| Ok(true)), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + impl Representable for PyNotImplemented { #[inline] fn repr(_zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> { diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index 70e4e20e405..e0b8009e892 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -10,12 +10,12 @@ use crate::{ convert::{ToPyObject, TransmuteFromObject}, function::{ArgSize, FuncArgs, OptionalArg, PyArithmeticValue, PyComparisonValue}, iter::PyExactSizeIterator, - protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods}, + protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, recursion::ReprGuard, sequence::{OptionalRangeArgs, SequenceExt}, sliceable::{SequenceIndex, SliceableSequenceOp}, types::{ - AsMapping, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable, + AsMapping, AsNumber, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable, PyComparisonOp, Representable, SelfIter, }, utils::collection_repr, @@ -260,7 +260,7 @@ impl<T> PyTuple<PyRef<T>> { #[pyclass( itemsize = core::mem::size_of::<crate::PyObjectRef>(), flags(BASETYPE, SEQUENCE, _MATCH_SELF), - with(AsMapping, AsSequence, Hashable, Comparable, Iterable, Constructor, Representable) + with(AsMapping, AsNumber, AsSequence, Hashable, Comparable, Iterable, Constructor, Representable) )] impl PyTuple { #[pymethod] @@ -286,11 +286,6 @@ impl PyTuple { PyArithmeticValue::from_option(added.ok()) } - #[pymethod] - const fn __bool__(&self) -> bool { - !self.elements.is_empty() - } - #[pymethod] fn count(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { let mut count: usize = 0; @@ -423,6 +418,19 @@ impl AsSequence for PyTuple { } } +impl AsNumber for PyTuple { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|number, _vm| { + let zelf = number.obj.downcast_ref::<PyTuple>().unwrap(); + Ok(!zelf.elements.is_empty()) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + impl Hashable for PyTuple { #[inline] fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> { diff --git a/crates/vm/src/builtins/weakproxy.rs b/crates/vm/src/builtins/weakproxy.rs index 94c54b5459e..51120c02c01 100644 --- a/crates/vm/src/builtins/weakproxy.rs +++ b/crates/vm/src/builtins/weakproxy.rs @@ -4,11 +4,11 @@ use crate::{ class::PyClassImpl, common::hash::PyHash, function::{OptionalArg, PyComparisonValue, PySetterValue}, - protocol::{PyIter, PyIterReturn, PyMappingMethods, PySequenceMethods}, + protocol::{PyIter, PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, stdlib::builtins::reversed, types::{ - AsMapping, AsSequence, Comparable, Constructor, GetAttr, Hashable, IterNext, Iterable, - PyComparisonOp, Representable, SetAttr, + AsMapping, AsNumber, AsSequence, Comparable, Constructor, GetAttr, Hashable, IterNext, + Iterable, PyComparisonOp, Representable, SetAttr, }, }; use std::sync::LazyLock; @@ -68,6 +68,7 @@ crate::common::static_cell! { SetAttr, Constructor, Comparable, + AsNumber, AsSequence, AsMapping, Representable, @@ -87,11 +88,6 @@ impl PyWeakProxy { self.try_upgrade(vm)?.length(vm) } - #[pymethod] - fn __bool__(&self, vm: &VirtualMachine) -> PyResult<bool> { - self.try_upgrade(vm)?.is_true(vm) - } - #[pymethod] fn __bytes__(&self, vm: &VirtualMachine) -> PyResult { self.try_upgrade(vm)?.bytes(vm) @@ -171,6 +167,19 @@ impl SetAttr for PyWeakProxy { } } +impl AsNumber for PyWeakProxy { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: LazyLock<PyNumberMethods> = LazyLock::new(|| PyNumberMethods { + boolean: Some(|number, vm| { + let zelf = number.obj.downcast_ref::<PyWeakProxy>().unwrap(); + zelf.try_upgrade(vm)?.is_true(vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }); + &AS_NUMBER + } +} + impl Comparable for PyWeakProxy { fn cmp( zelf: &Py<Self>, diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 98dc6fd2ed2..4590b62d503 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -34,6 +34,13 @@ pub fn add_operators(class: &'static Py<PyType>, ctx: &Context) { continue; } + // __getattr__ should only have a wrapper if the type explicitly defines it. + // Unlike __getattribute__, __getattr__ is not present on object by default. + // Both map to TpGetattro, but only __getattribute__ gets a wrapper from the slot. + if def.name == "__getattr__" { + continue; + } + // Get the slot function wrapped in SlotFunc let Some(slot_func) = def.accessor.get_slot_func_with_op(&class.slots, def.op) else { continue; diff --git a/crates/vm/src/stdlib/collections.rs b/crates/vm/src/stdlib/collections.rs index 1249fa9315d..67e8e1f2734 100644 --- a/crates/vm/src/stdlib/collections.rs +++ b/crates/vm/src/stdlib/collections.rs @@ -12,13 +12,13 @@ mod _collections { common::lock::{PyMutex, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}, function::{KwArgs, OptionalArg, PyComparisonValue}, iter::PyExactSizeIterator, - protocol::{PyIterReturn, PySequenceMethods}, + protocol::{PyIterReturn, PyNumberMethods, PySequenceMethods}, recursion::ReprGuard, sequence::{MutObjectSequenceOp, OptionalRangeArgs}, sliceable::SequenceIndexOp, types::{ - AsSequence, Comparable, Constructor, DefaultConstructor, Initializer, IterNext, - Iterable, PyComparisonOp, Representable, SelfIter, + AsNumber, AsSequence, Comparable, Constructor, DefaultConstructor, Initializer, + IterNext, Iterable, PyComparisonOp, Representable, SelfIter, }, utils::collection_repr, }; @@ -60,6 +60,7 @@ mod _collections { with( Constructor, Initializer, + AsNumber, AsSequence, Comparable, Iterable, @@ -354,11 +355,6 @@ mod _collections { self.borrow_deque().len() } - #[pymethod] - fn __bool__(&self) -> bool { - !self.borrow_deque().is_empty() - } - #[pymethod] fn __add__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> { self.concat(&other, vm) @@ -496,6 +492,19 @@ mod _collections { } } + impl AsNumber for PyDeque { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|number, _vm| { + let zelf = number.obj.downcast_ref::<PyDeque>().unwrap(); + Ok(!zelf.borrow_deque().is_empty()) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } + } + impl AsSequence for PyDeque { fn as_sequence() -> &'static PySequenceMethods { static AS_SEQUENCE: PySequenceMethods = PySequenceMethods { diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 5906bc91cd4..295e6fd137d 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -12,8 +12,8 @@ use crate::{ builtins::{PyBytes, PyDict, PyNone, PyStr, PyTuple, PyType, PyTypeRef}, class::StaticType, function::FuncArgs, - protocol::{BufferDescriptor, PyBuffer}, - types::{AsBuffer, Callable, Constructor, Initializer, Representable}, + protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}, + types::{AsBuffer, AsNumber, Callable, Constructor, Initializer, Representable}, vm::thread::with_current_vm, }; use alloc::borrow::Cow; @@ -1772,7 +1772,10 @@ impl AsBuffer for PyCFuncPtr { } } -#[pyclass(flags(BASETYPE), with(Callable, Constructor, Representable, AsBuffer))] +#[pyclass( + flags(BASETYPE), + with(Callable, Constructor, AsNumber, Representable, AsBuffer) +)] impl PyCFuncPtr { // restype getter/setter #[pygetset] @@ -1848,11 +1851,18 @@ impl PyCFuncPtr { .map(|stg| stg.flags.bits()) .unwrap_or(StgInfoFlags::empty().bits()) } +} - // bool conversion - check if function pointer is set - #[pymethod] - fn __bool__(&self) -> bool { - self.get_func_ptr() != 0 +impl AsNumber for PyCFuncPtr { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|number, _vm| { + let zelf = number.obj.downcast_ref::<PyCFuncPtr>().unwrap(); + Ok(zelf.get_func_ptr() != 0) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER } } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index f564fd1965c..aad21f8fe45 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -261,7 +261,7 @@ impl Initializer for PyCPointer { #[pyclass( flags(BASETYPE, IMMUTABLETYPE), - with(Constructor, Initializer, AsBuffer) + with(Constructor, Initializer, AsNumber, AsBuffer) )] impl PyCPointer { /// Get the pointer value stored in buffer as usize @@ -279,12 +279,6 @@ impl PyCPointer { } } - /// Pointer_bool: returns True if pointer is not NULL - #[pymethod] - fn __bool__(&self) -> bool { - self.get_ptr_value() != 0 - } - /// contents getter - reads address from b_ptr and creates an instance of the pointed-to type #[pygetset] fn contents(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyObjectRef> { @@ -779,6 +773,19 @@ impl PyCPointer { } } +impl AsNumber for PyCPointer { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + boolean: Some(|number, _vm| { + let zelf = number.obj.downcast_ref::<PyCPointer>().unwrap(); + Ok(zelf.get_ptr_value() != 0) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + impl AsBuffer for PyCPointer { fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { let stg_info = zelf diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs index 7bcfa203b02..17d3aa17ad1 100644 --- a/crates/vm/src/stdlib/ctypes/simple.rs +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -1133,14 +1133,6 @@ impl PyCSimple { self.0.base.read().clone() } - /// return True if any byte in buffer is non-zero - #[pymethod] - fn __bool__(&self) -> bool { - let buffer = self.0.buffer.read(); - // Simple_bool: memcmp(self->b_ptr, zeros, self->b_size) - buffer.iter().any(|&b| b != 0) - } - #[pygetset] pub fn value(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> { let zelf: &Py<Self> = instance diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index f3d8ca10768..400cab44210 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -212,11 +212,6 @@ mod winreg { Ok(old_hkey as usize) } - #[pymethod] - fn __bool__(&self) -> bool { - !self.hkey.load().is_null() - } - #[pymethod] fn __enter__(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult<PyRef<Self>> { Ok(zelf) @@ -268,13 +263,9 @@ mod winreg { negative: Some(|_a, vm| PyHkey::unary_fail(vm)), positive: Some(|_a, vm| PyHkey::unary_fail(vm)), absolute: Some(|_a, vm| PyHkey::unary_fail(vm)), - boolean: Some(|a, vm| { - if let Some(a) = a.downcast_ref::<PyHkey>() { - Ok(a.__bool__()) - } else { - PyHkey::unary_fail(vm)?; - unreachable!() - } + boolean: Some(|a, _vm| { + let zelf = a.obj.downcast_ref::<PyHkey>().unwrap(); + Ok(!zelf.hkey.load().is_null()) }), invert: Some(|_a, vm| PyHkey::unary_fail(vm)), lshift: Some(|_a, _b, vm| PyHkey::binary_fail(vm)), diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 085cfea5cee..d7ead562063 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -601,6 +601,7 @@ impl PyType { /// Update slots based on dunder method changes /// /// Iterates SLOT_DEFS to find all slots matching the given name and updates them. + /// Also recursively updates subclasses that don't have their own definition. pub(crate) fn update_slot<const ADD: bool>(&self, name: &'static PyStrInterned, ctx: &Context) { debug_assert!(name.as_str().starts_with("__")); debug_assert!(name.as_str().ends_with("__")); @@ -609,6 +610,36 @@ impl PyType { for def in find_slot_defs_by_name(name.as_str()) { self.update_one_slot::<ADD>(&def.accessor, name, ctx); } + + // Recursively update subclasses that don't have their own definition + self.update_subclasses::<ADD>(name, ctx); + } + + /// Recursively update subclasses' slots + /// recurse_down_subclasses + fn update_subclasses<const ADD: bool>(&self, name: &'static PyStrInterned, ctx: &Context) { + let subclasses = self.subclasses.read(); + for weak_ref in subclasses.iter() { + let Some(subclass) = weak_ref.upgrade() else { + continue; + }; + let Some(subclass) = subclass.downcast_ref::<PyType>() else { + continue; + }; + + // Skip if subclass has its own definition for this attribute + if subclass.attributes.read().contains_key(name) { + continue; + } + + // Update subclass's slots + for def in find_slot_defs_by_name(name.as_str()) { + subclass.update_one_slot::<ADD>(&def.accessor, name, ctx); + } + + // Recurse into subclass's subclasses + subclass.update_subclasses::<ADD>(name, ctx); + } } /// Update a single slot @@ -706,7 +737,44 @@ impl PyType { } } SlotAccessor::TpDel => update_main_slot!(del, del_wrapper, Del), - SlotAccessor::TpGetattro => update_main_slot!(getattro, getattro_wrapper, GetAttro), + SlotAccessor::TpGetattro => { + // __getattribute__ and __getattr__ both map to TpGetattro. + // If __getattr__ is defined anywhere in MRO, we must use the wrapper + // because the native slot won't call __getattr__. + let __getattr__ = identifier!(ctx, __getattr__); + let has_getattr = { + let attrs = self.attributes.read(); + let in_self = attrs.contains_key(__getattr__); + drop(attrs); + // mro[0] is self, so skip it + in_self + || self + .mro + .read() + .iter() + .skip(1) + .any(|cls| cls.attributes.read().contains_key(__getattr__)) + }; + + if has_getattr { + // Must use wrapper to handle __getattr__ + self.slots.getattro.store(Some(getattro_wrapper)); + } else if ADD { + if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| { + if let SlotFunc::GetAttro(f) = sf { + Some(*f) + } else { + None + } + }) { + self.slots.getattro.store(Some(func)); + } else { + self.slots.getattro.store(Some(getattro_wrapper)); + } + } else { + accessor.inherit_from_mro(self); + } + } SlotAccessor::TpSetattro => { // __setattr__ and __delattr__ share the same slot if ADD { diff --git a/crates/vm/src/types/slot_defs.rs b/crates/vm/src/types/slot_defs.rs index 958adb2f27e..1fd493f685e 100644 --- a/crates/vm/src/types/slot_defs.rs +++ b/crates/vm/src/types/slot_defs.rs @@ -986,6 +986,12 @@ pub static SLOT_DEFS: &[SlotDef] = &[ op: None, doc: "Return getattr(self, name).", }, + SlotDef { + name: "__getattr__", + accessor: SlotAccessor::TpGetattro, + op: None, + doc: "Implement getattr(self, name).", + }, SlotDef { name: "__setattr__", accessor: SlotAccessor::TpSetattro, @@ -1419,6 +1425,12 @@ pub static SLOT_DEFS: &[SlotDef] = &[ op: None, doc: "Return self*value.", }, + SlotDef { + name: "__rmul__", + accessor: SlotAccessor::SqRepeat, + op: None, + doc: "Return value*self.", + }, SlotDef { name: "__iadd__", accessor: SlotAccessor::SqInplaceConcat, From 7c8df94f4ef81ae256390425c3f1a1d4f071b625 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:12:21 +0900 Subject: [PATCH 653/819] Fix __origname__ of frozen modules (#6613) --- crates/vm/src/import.rs | 15 ++++++++++----- crates/vm/src/stdlib/imp.rs | 4 +++- crates/vm/src/vm/mod.rs | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/crates/vm/src/import.rs b/crates/vm/src/import.rs index f970a335aa5..d67bf59b35f 100644 --- a/crates/vm/src/import.rs +++ b/crates/vm/src/import.rs @@ -5,7 +5,7 @@ use crate::{ builtins::{PyCode, list, traceback::PyTraceback}, exceptions::types::PyBaseException, scope::Scope, - vm::{VirtualMachine, thread}, + vm::{VirtualMachine, resolve_frozen_alias, thread}, }; pub(crate) fn init_importlib_base(vm: &mut VirtualMachine) -> PyResult<PyObjectRef> { @@ -69,11 +69,16 @@ pub fn make_frozen(vm: &VirtualMachine, name: &str) -> PyResult<PyRef<PyCode>> { } pub fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { - let frozen = make_frozen(vm, module_name)?; - let module = import_code_obj(vm, module_name, frozen, false)?; + let frozen = vm.state.frozen.get(module_name).ok_or_else(|| { + vm.new_import_error( + format!("No such frozen object named {module_name}"), + vm.ctx.new_str(module_name), + ) + })?; + let module = import_code_obj(vm, module_name, vm.ctx.new_code(frozen.code), false)?; debug_assert!(module.get_attr(identifier!(vm, __name__), vm).is_ok()); - // TODO: give a correct origname here - module.set_attr("__origname__", vm.ctx.new_str(module_name.to_owned()), vm)?; + let origname = resolve_frozen_alias(module_name); + module.set_attr("__origname__", vm.ctx.new_str(origname), vm)?; Ok(module) } diff --git a/crates/vm/src/stdlib/imp.rs b/crates/vm/src/stdlib/imp.rs index df32b0c0068..ad2cede975c 100644 --- a/crates/vm/src/stdlib/imp.rs +++ b/crates/vm/src/stdlib/imp.rs @@ -2,6 +2,8 @@ use crate::frozen::FrozenModule; use crate::{VirtualMachine, builtins::PyBaseExceptionRef}; pub(crate) use _imp::make_module; +pub use crate::vm::resolve_frozen_alias; + #[cfg(feature = "threading")] #[pymodule(sub)] mod lock { @@ -191,7 +193,7 @@ mod _imp { Err(e) => return Err(e.to_pyexception(name.as_str(), vm)), }; - let origname = name; // FIXME: origname != name + let origname = vm.ctx.new_str(super::resolve_frozen_alias(name.as_str())); Ok(Some((None, info.package, origname))) } diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index b156e30b738..2eef479d1b4 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -1001,6 +1001,16 @@ impl AsRef<Context> for VirtualMachine { } } +/// Resolve frozen module alias to its original name. +/// Returns the original module name if an alias exists, otherwise returns the input name. +pub fn resolve_frozen_alias(name: &str) -> &str { + match name { + "_frozen_importlib" => "importlib._bootstrap", + "_frozen_importlib_external" => "importlib._bootstrap_external", + _ => name, + } +} + fn core_frozen_inits() -> impl Iterator<Item = (&'static str, FrozenModule)> { let iter = core::iter::empty(); macro_rules! ext_modules { @@ -1064,3 +1074,26 @@ fn test_nested_frozen() { } }) } + +#[test] +fn frozen_origname_matches() { + use rustpython_vm as vm; + + vm::Interpreter::with_init(Default::default(), |_vm| {}).enter(|vm| { + let check = |name, expected| { + let module = import::import_frozen(vm, name).unwrap(); + let origname: PyStrRef = module + .get_attr("__origname__", vm) + .unwrap() + .try_into_value(vm) + .unwrap(); + assert_eq!(origname.as_str(), expected); + }; + + check("_frozen_importlib", "importlib._bootstrap"); + check( + "_frozen_importlib_external", + "importlib._bootstrap_external", + ); + }); +} From 6ccf3b5104ea9ded9797b99ec6eeb4132efe4f48 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 31 Dec 2025 14:13:29 +0200 Subject: [PATCH 654/819] Update ruff to `0.14.10` (#6271) * Update `ruff` to `0.14.10` * Mark failing test --- Cargo.lock | 168 +++++++++++++++++++++------------------- Cargo.toml | 10 +-- Lib/test/test_syntax.py | 1 + 3 files changed, 96 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89cace13b26..0b7436570a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,7 +113,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -124,7 +124,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -284,9 +284,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" [[package]] name = "bindgen" @@ -380,9 +380,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" dependencies = [ "allocator-api2", ] @@ -443,9 +443,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.47" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "jobserver", @@ -577,9 +577,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.54" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] @@ -1146,7 +1146,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1181,14 +1181,14 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "flagset" @@ -1282,9 +1282,9 @@ dependencies = [ [[package]] name = "get-size-derive2" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff47daa61505c85af126e9dd64af6a342a33dc0cccfe1be74ceadc7d352e6efd" +checksum = "ab21d7bd2c625f2064f04ce54bcb88bc57c45724cde45cba326d784e22d3f71a" dependencies = [ "attribute-derive", "quote", @@ -1293,9 +1293,9 @@ dependencies = [ [[package]] name = "get-size2" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac7bb8710e1f09672102be7ddf39f764d8440ae74a9f4e30aaa4820dcdffa4af" +checksum = "879272b0de109e2b67b39fcfe3d25fdbba96ac07e44a254f5a0b4d7ff55340cb" dependencies = [ "compact_str", "get-size-derive2", @@ -1541,15 +1541,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8" dependencies = [ "jiff-static", "log", @@ -1560,9 +1560,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58" dependencies = [ "proc-macro2", "quote", @@ -1732,9 +1732,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", @@ -1753,9 +1753,9 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" dependencies = [ "zlib-rs", ] @@ -2140,6 +2140,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "openssl-src" version = "300.5.4+3.5.4" @@ -2390,9 +2396,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -2441,9 +2447,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -2772,7 +2778,7 @@ dependencies = [ [[package]] name = "ruff_python_ast" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?rev=2bffef59665ce7d2630dfd72ee99846663660db8#2bffef59665ce7d2630dfd72ee99846663660db8" +source = "git+https://github.com/astral-sh/ruff.git?rev=45bbb4cbffe73cf925d4579c2e3eb413e0539390#45bbb4cbffe73cf925d4579c2e3eb413e0539390" dependencies = [ "aho-corasick", "bitflags 2.10.0", @@ -2791,7 +2797,7 @@ dependencies = [ [[package]] name = "ruff_python_parser" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?rev=2bffef59665ce7d2630dfd72ee99846663660db8#2bffef59665ce7d2630dfd72ee99846663660db8" +source = "git+https://github.com/astral-sh/ruff.git?rev=45bbb4cbffe73cf925d4579c2e3eb413e0539390#45bbb4cbffe73cf925d4579c2e3eb413e0539390" dependencies = [ "bitflags 2.10.0", "bstr", @@ -2811,7 +2817,7 @@ dependencies = [ [[package]] name = "ruff_python_trivia" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?rev=2bffef59665ce7d2630dfd72ee99846663660db8#2bffef59665ce7d2630dfd72ee99846663660db8" +source = "git+https://github.com/astral-sh/ruff.git?rev=45bbb4cbffe73cf925d4579c2e3eb413e0539390#45bbb4cbffe73cf925d4579c2e3eb413e0539390" dependencies = [ "itertools 0.14.0", "ruff_source_file", @@ -2822,7 +2828,7 @@ dependencies = [ [[package]] name = "ruff_source_file" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?rev=2bffef59665ce7d2630dfd72ee99846663660db8#2bffef59665ce7d2630dfd72ee99846663660db8" +source = "git+https://github.com/astral-sh/ruff.git?rev=45bbb4cbffe73cf925d4579c2e3eb413e0539390#45bbb4cbffe73cf925d4579c2e3eb413e0539390" dependencies = [ "memchr", "ruff_text_size", @@ -2831,7 +2837,7 @@ dependencies = [ [[package]] name = "ruff_text_size" version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?rev=2bffef59665ce7d2630dfd72ee99846663660db8#2bffef59665ce7d2630dfd72ee99846663660db8" +source = "git+https://github.com/astral-sh/ruff.git?rev=45bbb4cbffe73cf925d4579c2e3eb413e0539390#45bbb4cbffe73cf925d4579c2e3eb413e0539390" dependencies = [ "get-size2", ] @@ -2861,7 +2867,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2880,11 +2886,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe", + "openssl-probe 0.2.0", "rustls-pki-types", "schannel", "security-framework", @@ -2901,9 +2907,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "zeroize", ] @@ -2926,7 +2932,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3178,7 +3184,7 @@ dependencies = [ "num_enum", "oid-registry", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "page_size", "parking_lot", @@ -3365,9 +3371,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "safe_arch" @@ -3494,22 +3500,22 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -3582,9 +3588,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "similar" @@ -3662,9 +3668,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" dependencies = [ "proc-macro2", "quote", @@ -3716,9 +3722,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" +checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" [[package]] name = "tcl-sys" @@ -3731,15 +3737,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3911,9 +3917,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap", "serde_core", @@ -3926,27 +3932,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "twox-hash" @@ -4389,7 +4395,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4764,18 +4770,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", @@ -4793,9 +4799,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", @@ -4804,6 +4810,12 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.3" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zmij" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36134c44663532e6519d7a6dfdbbe06f6f8192bde8ae9ed076e9b213f0e31df7" +checksum = "e3280a1b827474fcd5dbef4b35a674deb52ba5c312363aef9135317df179d81b" diff --git a/Cargo.toml b/Cargo.toml index 12607cfa2cc..988e99efacc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -151,12 +151,12 @@ rustpython-sre_engine = { path = "crates/sre_engine", version = "0.4.0" } rustpython-wtf8 = { path = "crates/wtf8", version = "0.4.0" } rustpython-doc = { path = "crates/doc", version = "0.4.0" } -# Ruff tag 0.14.1 is based on commit 2bffef59665ce7d2630dfd72ee99846663660db8 +# Ruff tag 0.14.10 is based on commit 45bbb4cbffe73cf925d4579c2e3eb413e0539390 # at the time of this capture. We use the commit hash to ensure reproducible builds. -ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", rev = "2bffef59665ce7d2630dfd72ee99846663660db8" } -ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", rev = "2bffef59665ce7d2630dfd72ee99846663660db8" } -ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", rev = "2bffef59665ce7d2630dfd72ee99846663660db8" } -ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", rev = "2bffef59665ce7d2630dfd72ee99846663660db8" } +ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", rev = "45bbb4cbffe73cf925d4579c2e3eb413e0539390" } +ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", rev = "45bbb4cbffe73cf925d4579c2e3eb413e0539390" } +ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", rev = "45bbb4cbffe73cf925d4579c2e3eb413e0539390" } +ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", rev = "45bbb4cbffe73cf925d4579c2e3eb413e0539390" } phf = { version = "0.13.1", default-features = false, features = ["macros"]} ahash = "0.8.12" diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 29dd04995ec..5085798b81e 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -2577,6 +2577,7 @@ async def bug(): with self.subTest(f"out of range: {n=}"): self._check_error(get_code(n), "too many statically nested blocks") + @unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message def test_barry_as_flufl_with_syntax_errors(self): # The "barry_as_flufl" rule can produce some "bugs-at-a-distance" if # is reading the wrong token in the presence of syntax errors later From 1e706d911e41e37856ed1e6a8adff2f1aa13ad6c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 31 Dec 2025 22:14:28 +0900 Subject: [PATCH 655/819] Interpreter (#6614) * Refine documentation in interpreter.rs Improve InterpreterConfig with convenience methods (with_debug, add_path, add_paths), better documentation with working examples, refactored stdlib setup into focused functions, and comprehensive unit tests while maintaining 100% backward compatibility. * Improve error message for non-Unicode paths * Use consistent make_module naming in all doc examples * cut useful changes --------- Co-authored-by: Mohamed Gharbi <galaxysea777@gmail.com> --- src/interpreter.rs | 120 +++++++++++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 43 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index b4fd319cdae..51667e724f1 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -26,15 +26,18 @@ pub type InitHook = Box<dyn FnOnce(&mut VirtualMachine)>; /// ``` /// /// To add native modules: -/// ```compile_fail +/// ``` +/// use rustpython_vm::pymodule; +/// +/// #[pymodule] +/// mod your_module {} +/// /// let interpreter = rustpython::InterpreterConfig::new() /// .init_stdlib() -/// .init_hook(Box::new(|vm| { -/// vm.add_native_module( -/// "your_module_name".to_owned(), -/// Box::new(your_module::make_module), -/// ); -/// })) +/// .add_native_module( +/// "your_module_name".to_owned(), +/// your_module::make_module, +/// ) /// .interpreter(); /// ``` #[derive(Default)] @@ -44,9 +47,12 @@ pub struct InterpreterConfig { } impl InterpreterConfig { + /// Creates a new interpreter configuration with default settings. pub fn new() -> Self { Self::default() } + + /// Builds the interpreter with the current configuration. pub fn interpreter(self) -> Interpreter { let settings = self.settings.unwrap_or_default(); Interpreter::with_init(settings, |vm| { @@ -56,14 +62,23 @@ impl InterpreterConfig { }) } + /// Sets custom settings for the interpreter. + /// + /// If called multiple times, only the last settings will be used. pub fn settings(mut self, settings: Settings) -> Self { self.settings = Some(settings); self } + + /// Adds a custom initialization hook. + /// + /// Hooks are executed in the order they are added during interpreter creation. pub fn init_hook(mut self, hook: InitHook) -> Self { self.init_hooks.push(hook); self } + + /// Adds a native module to the interpreter. pub fn add_native_module( self, name: String, @@ -73,56 +88,75 @@ impl InterpreterConfig { vm.add_native_module(name, Box::new(make_module)) })) } + + /// Initializes the Python standard library. + /// + /// Requires the `stdlib` feature to be enabled. #[cfg(feature = "stdlib")] pub fn init_stdlib(self) -> Self { self.init_hook(Box::new(init_stdlib)) } } +/// Initializes all standard library modules for the given VM. #[cfg(feature = "stdlib")] pub fn init_stdlib(vm: &mut VirtualMachine) { vm.add_native_modules(rustpython_stdlib::get_module_inits()); - // if we're on freeze-stdlib, the core stdlib modules will be included anyway #[cfg(feature = "freeze-stdlib")] - { - vm.add_frozen(rustpython_pylib::FROZEN_STDLIB); - - // FIXME: Remove this hack once sys._stdlib_dir is properly implemented or _frozen_importlib doesn't depend on it anymore. - assert!(vm.sys_module.get_attr("_stdlib_dir", vm).is_err()); - vm.sys_module - .set_attr( - "_stdlib_dir", - vm.new_pyobj(rustpython_pylib::LIB_PATH.to_owned()), - vm, - ) - .unwrap(); - } + setup_frozen_stdlib(vm); #[cfg(not(feature = "freeze-stdlib"))] - { - use rustpython_vm::common::rc::PyRc; - - let state = PyRc::get_mut(&mut vm.state).unwrap(); - - // Collect additional paths to add - let mut additional_paths = Vec::new(); - - // BUILDTIME_RUSTPYTHONPATH should be set when distributing - if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { - additional_paths.extend( - crate::settings::split_paths(paths) - .map(|path| path.into_os_string().into_string().unwrap()), - ) - } else { - #[cfg(feature = "rustpython-pylib")] - additional_paths.push(rustpython_pylib::LIB_PATH.to_owned()) - } + setup_dynamic_stdlib(vm); +} - // Add to both path_list (for compatibility) and module_search_paths (for sys.path) - // Insert at the beginning so stdlib comes before user paths - for path in additional_paths.into_iter().rev() { - state.config.paths.module_search_paths.insert(0, path); +/// Setup frozen standard library (compiled into the binary) +#[cfg(all(feature = "stdlib", feature = "freeze-stdlib"))] +fn setup_frozen_stdlib(vm: &mut VirtualMachine) { + vm.add_frozen(rustpython_pylib::FROZEN_STDLIB); + + // FIXME: Remove this hack once sys._stdlib_dir is properly implemented + // or _frozen_importlib doesn't depend on it anymore. + assert!(vm.sys_module.get_attr("_stdlib_dir", vm).is_err()); + vm.sys_module + .set_attr( + "_stdlib_dir", + vm.new_pyobj(rustpython_pylib::LIB_PATH.to_owned()), + vm, + ) + .unwrap(); +} + +/// Setup dynamic standard library loading from filesystem +#[cfg(all(feature = "stdlib", not(feature = "freeze-stdlib")))] +fn setup_dynamic_stdlib(vm: &mut VirtualMachine) { + use rustpython_vm::common::rc::PyRc; + + let state = PyRc::get_mut(&mut vm.state).unwrap(); + let paths = collect_stdlib_paths(); + + // Insert at the beginning so stdlib comes before user paths + for path in paths.into_iter().rev() { + state.config.paths.module_search_paths.insert(0, path); + } +} + +/// Collect standard library paths from build-time configuration +#[cfg(all(feature = "stdlib", not(feature = "freeze-stdlib")))] +fn collect_stdlib_paths() -> Vec<String> { + // BUILDTIME_RUSTPYTHONPATH should be set when distributing + if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { + crate::settings::split_paths(paths) + .map(|path| path.into_os_string().into_string().unwrap()) + .collect() + } else { + #[cfg(feature = "rustpython-pylib")] + { + vec![rustpython_pylib::LIB_PATH.to_owned()] + } + #[cfg(not(feature = "rustpython-pylib"))] + { + vec![] } } } From a4df238b3db52527b49cfe1e6ff995d17ced8673 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Wed, 31 Dec 2025 18:58:59 -0500 Subject: [PATCH 656/819] Updated the importlib + nturl2path + pathlib libraries + associated tests - v3.13.10 (#6609) * Updated the nturl2path library * Updated pathlib library + test * Updated urllib test * Added expectedFailure to failing tests * Updated importlib library --- Lib/importlib/__init__.py | 4 +- Lib/importlib/_bootstrap.py | 16 +- Lib/importlib/_bootstrap_external.py | 103 +- Lib/importlib/abc.py | 6 +- Lib/importlib/machinery.py | 1 + Lib/importlib/metadata/__init__.py | 330 +- Lib/importlib/metadata/_adapters.py | 2 +- Lib/importlib/metadata/_meta.py | 52 +- Lib/importlib/metadata/diagnose.py | 21 + Lib/importlib/resources/__init__.py | 23 +- Lib/importlib/resources/_common.py | 16 +- Lib/importlib/resources/_functional.py | 81 + Lib/importlib/resources/readers.py | 67 +- Lib/importlib/resources/simple.py | 4 +- Lib/importlib/util.py | 90 +- Lib/nturl2path.py | 54 +- Lib/pathlib.py | 1435 --------- Lib/pathlib/__init__.py | 12 + Lib/pathlib/_abc.py | 930 ++++++ Lib/pathlib/_local.py | 861 ++++++ Lib/test/test_pathlib.py | 3328 --------------------- Lib/test/test_pathlib/__init__.py | 5 + Lib/test/test_pathlib/test_pathlib.py | 1660 ++++++++++ Lib/test/test_pathlib/test_pathlib_abc.py | 2499 ++++++++++++++++ Lib/test/test_urllib.py | 267 +- 25 files changed, 6751 insertions(+), 5116 deletions(-) create mode 100644 Lib/importlib/metadata/diagnose.py create mode 100644 Lib/importlib/resources/_functional.py delete mode 100644 Lib/pathlib.py create mode 100644 Lib/pathlib/__init__.py create mode 100644 Lib/pathlib/_abc.py create mode 100644 Lib/pathlib/_local.py delete mode 100644 Lib/test/test_pathlib.py create mode 100644 Lib/test/test_pathlib/__init__.py create mode 100644 Lib/test/test_pathlib/test_pathlib.py create mode 100644 Lib/test/test_pathlib/test_pathlib_abc.py diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py index 707c081cb2c..a7d57561ead 100644 --- a/Lib/importlib/__init__.py +++ b/Lib/importlib/__init__.py @@ -54,8 +54,6 @@ # Fully bootstrapped at this point, import whatever you like, circular # dependencies and startup overhead minimisation permitting :) -import warnings - # Public API ######################################################### @@ -105,7 +103,7 @@ def reload(module): try: name = module.__name__ except AttributeError: - raise TypeError("reload() argument must be a module") + raise TypeError("reload() argument must be a module") from None if sys.modules.get(name) is not module: raise ImportError(f"module {name} not in sys.modules", name=name) diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index 093a0b82456..68d993cacae 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -53,7 +53,7 @@ def _new_module(name): # For a list that can have a weakref to it. class _List(list): - pass + __slots__ = ("__weakref__",) # Copied from weakref.py with some simplifications and modifications unique to @@ -527,7 +527,7 @@ def _load_module_shim(self, fullname): """ msg = ("the load_module() method is deprecated and slated for removal in " - "Python 3.12; use exec_module() instead") + "Python 3.15; use exec_module() instead") _warnings.warn(msg, DeprecationWarning) spec = spec_from_loader(fullname, self) if fullname in sys.modules: @@ -825,10 +825,16 @@ def _module_repr_from_spec(spec): """Return the repr to use for the module.""" name = '?' if spec.name is None else spec.name if spec.origin is None: - if spec.loader is None: + loader = spec.loader + if loader is None: return f'<module {name!r}>' + elif ( + _bootstrap_external is not None + and isinstance(loader, _bootstrap_external.NamespaceLoader) + ): + return f'<module {name!r} (namespace) from {list(loader._path)}>' else: - return f'<module {name!r} (namespace) from {list(spec.loader._path)}>' + return f'<module {name!r} ({loader!r})>' else: if spec.has_location: return f'<module {name!r} from {spec.origin!r}>' @@ -1129,7 +1135,7 @@ def find_spec(cls, fullname, path=None, target=None): # part of the importer), instead of here (the finder part). # The loader is the usual place to get the data that will # be loaded into the module. (For example, see _LoaderBasics - # in _bootstra_external.py.) Most importantly, this importer + # in _bootstrap_external.py.) Most importantly, this importer # is simpler if we wait to get the data. # However, getting as much data in the finder as possible # to later load the module is okay, and sometimes important. diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index 73ac4405cb5..41f538acb03 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -52,7 +52,7 @@ # Bootstrap-related code ###################################################### _CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win', -_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin' +_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos' _CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY + _CASE_INSENSITIVE_PLATFORMS_STR_KEY) @@ -81,6 +81,11 @@ def _pack_uint32(x): return (int(x) & 0xFFFFFFFF).to_bytes(4, 'little') +def _unpack_uint64(data): + """Convert 8 bytes in little-endian to an integer.""" + assert len(data) == 8 + return int.from_bytes(data, 'little') + def _unpack_uint32(data): """Convert 4 bytes in little-endian to an integer.""" assert len(data) == 4 @@ -203,7 +208,7 @@ def _write_atomic(path, data, mode=0o666): try: # We first write data to a temporary file, and then use os.replace() to # perform an atomic rename. - with _io.FileIO(fd, 'wb') as file: + with _io.open(fd, 'wb') as file: file.write(data) _os.replace(path_tmp, path) except OSError: @@ -413,6 +418,7 @@ def _write_atomic(path, data, mode=0o666): # Python 3.11a7 3492 (make POP_JUMP_IF_NONE/NOT_NONE/TRUE/FALSE relative) # Python 3.11a7 3493 (Make JUMP_IF_TRUE_OR_POP/JUMP_IF_FALSE_OR_POP relative) # Python 3.11a7 3494 (New location info table) +# Python 3.11b4 3495 (Set line number of module's RESUME instr to 0 per PEP 626) # Python 3.12a1 3500 (Remove PRECALL opcode) # Python 3.12a1 3501 (YIELD_VALUE oparg == stack_depth) # Python 3.12a1 3502 (LOAD_FAST_CHECK, no NULL-check in LOAD_FAST) @@ -445,8 +451,30 @@ def _write_atomic(path, data, mode=0o666): # Python 3.12b1 3529 (Inline list/dict/set comprehensions) # Python 3.12b1 3530 (Shrink the LOAD_SUPER_ATTR caches) # Python 3.12b1 3531 (Add PEP 695 changes) - -# Python 3.13 will start with 3550 +# Python 3.13a1 3550 (Plugin optimizer support) +# Python 3.13a1 3551 (Compact superinstructions) +# Python 3.13a1 3552 (Remove LOAD_FAST__LOAD_CONST and LOAD_CONST__LOAD_FAST) +# Python 3.13a1 3553 (Add SET_FUNCTION_ATTRIBUTE) +# Python 3.13a1 3554 (more efficient bytecodes for f-strings) +# Python 3.13a1 3555 (generate specialized opcodes metadata from bytecodes.c) +# Python 3.13a1 3556 (Convert LOAD_CLOSURE to a pseudo-op) +# Python 3.13a1 3557 (Make the conversion to boolean in jumps explicit) +# Python 3.13a1 3558 (Reorder the stack items for CALL) +# Python 3.13a1 3559 (Generate opcode IDs from bytecodes.c) +# Python 3.13a1 3560 (Add RESUME_CHECK instruction) +# Python 3.13a1 3561 (Add cache entry to branch instructions) +# Python 3.13a1 3562 (Assign opcode IDs for internal ops in separate range) +# Python 3.13a1 3563 (Add CALL_KW and remove KW_NAMES) +# Python 3.13a1 3564 (Removed oparg from YIELD_VALUE, changed oparg values of RESUME) +# Python 3.13a1 3565 (Oparg of YIELD_VALUE indicates whether it is in a yield-from) +# Python 3.13a1 3566 (Emit JUMP_NO_INTERRUPT instead of JUMP for non-loop no-lineno cases) +# Python 3.13a1 3567 (Reimplement line number propagation by the compiler) +# Python 3.13a1 3568 (Change semantics of END_FOR) +# Python 3.13a5 3569 (Specialize CONTAINS_OP) +# Python 3.13a6 3570 (Add __firstlineno__ class attribute) +# Python 3.13b1 3571 (Fix miscompilation of private names in generic classes) + +# Python 3.14 will start with 3600 # Please don't copy-paste the same pre-release tag for new entries above!!! # You should always use the *upcoming* tag. For example, if 3.12a6 came out @@ -461,7 +489,7 @@ def _write_atomic(path, data, mode=0o666): # Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array # in PC/launcher.c must also be updated. -MAGIC_NUMBER = (3531).to_bytes(2, 'little') + b'\r\n' +MAGIC_NUMBER = (3571).to_bytes(2, 'little') + b'\r\n' _RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c @@ -535,7 +563,8 @@ def cache_from_source(path, debug_override=None, *, optimization=None): # Strip initial drive from a Windows path. We know we have an absolute # path here, so the second part of the check rules out a POSIX path that # happens to contain a colon at the second character. - if head[1] == ':' and head[0] not in path_separators: + # Slicing avoids issues with an empty (or short) `head`. + if head[1:2] == ':' and head[0:1] not in path_separators: head = head[2:] # Strip initial path separator from `head` to complete the conversion @@ -1437,7 +1466,7 @@ class PathFinder: @staticmethod def invalidate_caches(): """Call the invalidate_caches() method on all path entry finders - stored in sys.path_importer_caches (where implemented).""" + stored in sys.path_importer_cache (where implemented).""" for name, finder in list(sys.path_importer_cache.items()): # Drop entry if finder name is a relative path. The current # working directory may have changed. @@ -1449,6 +1478,9 @@ def invalidate_caches(): # https://bugs.python.org/issue45703 _NamespacePath._epoch += 1 + from importlib.metadata import MetadataPathFinder + MetadataPathFinder.invalidate_caches() + @staticmethod def _path_hooks(path): """Search sys.path_hooks for a finder for 'path'.""" @@ -1690,6 +1722,52 @@ def __repr__(self): return f'FileFinder({self.path!r})' +class AppleFrameworkLoader(ExtensionFileLoader): + """A loader for modules that have been packaged as frameworks for + compatibility with Apple's iOS App Store policies. + """ + def create_module(self, spec): + # If the ModuleSpec has been created by the FileFinder, it will have + # been created with an origin pointing to the .fwork file. We need to + # redirect this to the location in the Frameworks folder, using the + # content of the .fwork file. + if spec.origin.endswith(".fwork"): + with _io.FileIO(spec.origin, 'r') as file: + framework_binary = file.read().decode().strip() + bundle_path = _path_split(sys.executable)[0] + spec.origin = _path_join(bundle_path, framework_binary) + + # If the loader is created based on the spec for a loaded module, the + # path will be pointing at the Framework location. If this occurs, + # get the original .fwork location to use as the module's __file__. + if self.path.endswith(".fwork"): + path = self.path + else: + with _io.FileIO(self.path + ".origin", 'r') as file: + origin = file.read().decode().strip() + bundle_path = _path_split(sys.executable)[0] + path = _path_join(bundle_path, origin) + + module = _bootstrap._call_with_frames_removed(_imp.create_dynamic, spec) + + _bootstrap._verbose_message( + "Apple framework extension module {!r} loaded from {!r} (path {!r})", + spec.name, + spec.origin, + path, + ) + + # Ensure that the __file__ points at the .fwork location + try: + module.__file__ = path + except AttributeError: + # Not important enough to report. + # (The error is also ignored in _bootstrap._init_module_attrs or + # import_run_extension in import.c) + pass + + return module + # Import setup ############################################################### def _fix_up_module(ns, name, pathname, cpathname=None): @@ -1722,10 +1800,17 @@ def _get_supported_file_loaders(): Each item is a tuple (loader, suffixes). """ - extensions = ExtensionFileLoader, _imp.extension_suffixes() + extension_loaders = [] + if hasattr(_imp, 'create_dynamic'): + if sys.platform in {"ios", "tvos", "watchos"}: + extension_loaders = [(AppleFrameworkLoader, [ + suffix.replace(".so", ".fwork") + for suffix in _imp.extension_suffixes() + ])] + extension_loaders.append((ExtensionFileLoader, _imp.extension_suffixes())) source = SourceFileLoader, SOURCE_SUFFIXES bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES - return [extensions, source, bytecode] + return extension_loaders + [source, bytecode] def _set_bootstrap_module(_bootstrap_module): diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py index b56fa94eb9c..37fef357fe2 100644 --- a/Lib/importlib/abc.py +++ b/Lib/importlib/abc.py @@ -180,7 +180,11 @@ def get_code(self, fullname): else: return self.source_to_code(source, path) -_register(ExecutionLoader, machinery.ExtensionFileLoader) +_register( + ExecutionLoader, + machinery.ExtensionFileLoader, + machinery.AppleFrameworkLoader, +) class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader): diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py index d9a19a13f7b..fbd30b159fb 100644 --- a/Lib/importlib/machinery.py +++ b/Lib/importlib/machinery.py @@ -12,6 +12,7 @@ from ._bootstrap_external import SourceFileLoader from ._bootstrap_external import SourcelessFileLoader from ._bootstrap_external import ExtensionFileLoader +from ._bootstrap_external import AppleFrameworkLoader from ._bootstrap_external import NamespaceLoader diff --git a/Lib/importlib/metadata/__init__.py b/Lib/importlib/metadata/__init__.py index 82e0ce1b281..8ce62dd864f 100644 --- a/Lib/importlib/metadata/__init__.py +++ b/Lib/importlib/metadata/__init__.py @@ -1,9 +1,13 @@ +from __future__ import annotations + import os import re import abc -import csv import sys +import json import email +import types +import inspect import pathlib import zipfile import operator @@ -12,11 +16,9 @@ import functools import itertools import posixpath -import contextlib import collections -import inspect -from . import _adapters, _meta +from . import _meta from ._collections import FreezableDefaultDict, Pair from ._functools import method_cache, pass_none from ._itertools import always_iterable, unique_everseen @@ -26,8 +28,7 @@ from importlib import import_module from importlib.abc import MetaPathFinder from itertools import starmap -from typing import List, Mapping, Optional, cast - +from typing import Any, Iterable, List, Mapping, Match, Optional, Set, cast __all__ = [ 'Distribution', @@ -48,11 +49,11 @@ class PackageNotFoundError(ModuleNotFoundError): """The package was not found.""" - def __str__(self): + def __str__(self) -> str: return f"No package metadata was found for {self.name}" @property - def name(self): + def name(self) -> str: # type: ignore[override] (name,) = self.args return name @@ -118,38 +119,11 @@ def read(text, filter_=None): yield Pair(name, value) @staticmethod - def valid(line): + def valid(line: str): return line and not line.startswith('#') -class DeprecatedTuple: - """ - Provide subscript item access for backward compatibility. - - >>> recwarn = getfixture('recwarn') - >>> ep = EntryPoint(name='name', value='value', group='group') - >>> ep[:] - ('name', 'value', 'group') - >>> ep[0] - 'name' - >>> len(recwarn) - 1 - """ - - # Do not remove prior to 2023-05-01 or Python 3.13 - _warn = functools.partial( - warnings.warn, - "EntryPoint tuple interface is deprecated. Access members by name.", - DeprecationWarning, - stacklevel=2, - ) - - def __getitem__(self, item): - self._warn() - return self._key()[item] - - -class EntryPoint(DeprecatedTuple): +class EntryPoint: """An entry point as defined by Python packaging conventions. See `the packaging docs on entry points @@ -191,34 +165,37 @@ class EntryPoint(DeprecatedTuple): value: str group: str - dist: Optional['Distribution'] = None + dist: Optional[Distribution] = None - def __init__(self, name, value, group): + def __init__(self, name: str, value: str, group: str) -> None: vars(self).update(name=name, value=value, group=group) - def load(self): + def load(self) -> Any: """Load the entry point from its definition. If only a module is indicated by the value, return that module. Otherwise, return the named object. """ - match = self.pattern.match(self.value) + match = cast(Match, self.pattern.match(self.value)) module = import_module(match.group('module')) attrs = filter(None, (match.group('attr') or '').split('.')) return functools.reduce(getattr, attrs, module) @property - def module(self): + def module(self) -> str: match = self.pattern.match(self.value) + assert match is not None return match.group('module') @property - def attr(self): + def attr(self) -> str: match = self.pattern.match(self.value) + assert match is not None return match.group('attr') @property - def extras(self): + def extras(self) -> List[str]: match = self.pattern.match(self.value) + assert match is not None return re.findall(r'\w+', match.group('extras') or '') def _for(self, dist): @@ -266,7 +243,7 @@ def __repr__(self): f'group={self.group!r})' ) - def __hash__(self): + def __hash__(self) -> int: return hash(self._key()) @@ -277,7 +254,7 @@ class EntryPoints(tuple): __slots__ = () - def __getitem__(self, name): # -> EntryPoint: + def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override] """ Get the EntryPoint in self matching name. """ @@ -286,7 +263,14 @@ def __getitem__(self, name): # -> EntryPoint: except StopIteration: raise KeyError(name) - def select(self, **params): + def __repr__(self): + """ + Repr with classname and tuple constructor to + signal that we deviate from regular tuple behavior. + """ + return '%s(%r)' % (self.__class__.__name__, tuple(self)) + + def select(self, **params) -> EntryPoints: """ Select entry points from self that match the given parameters (typically group and/or name). @@ -294,14 +278,14 @@ def select(self, **params): return EntryPoints(ep for ep in self if ep.matches(**params)) @property - def names(self): + def names(self) -> Set[str]: """ Return the set of all names of all entry points. """ return {ep.name for ep in self} @property - def groups(self): + def groups(self) -> Set[str]: """ Return the set of all groups of all entry points. """ @@ -322,28 +306,31 @@ def _from_text(text): class PackagePath(pathlib.PurePosixPath): """A reference to a path in a package""" - def read_text(self, encoding='utf-8'): - with self.locate().open(encoding=encoding) as stream: - return stream.read() + hash: Optional[FileHash] + size: int + dist: Distribution - def read_binary(self): - with self.locate().open('rb') as stream: - return stream.read() + def read_text(self, encoding: str = 'utf-8') -> str: # type: ignore[override] + return self.locate().read_text(encoding=encoding) - def locate(self): + def read_binary(self) -> bytes: + return self.locate().read_bytes() + + def locate(self) -> SimplePath: """Return a path-like object for this path""" return self.dist.locate_file(self) class FileHash: - def __init__(self, spec): + def __init__(self, spec: str) -> None: self.mode, _, self.value = spec.partition('=') - def __repr__(self): + def __repr__(self) -> str: return f'<FileHash mode: {self.mode} value: {self.value}>' class DeprecatedNonAbstract: + # Required until Python 3.14 def __new__(cls, *args, **kwargs): all_names = { name for subclass in inspect.getmro(cls) for name in vars(subclass) @@ -363,25 +350,48 @@ def __new__(cls, *args, **kwargs): class Distribution(DeprecatedNonAbstract): - """A Python distribution package.""" + """ + An abstract Python distribution package. + + Custom providers may derive from this class and define + the abstract methods to provide a concrete implementation + for their environment. Some providers may opt to override + the default implementation of some properties to bypass + the file-reading mechanism. + """ @abc.abstractmethod def read_text(self, filename) -> Optional[str]: """Attempt to load metadata file given by the name. + Python distribution metadata is organized by blobs of text + typically represented as "files" in the metadata directory + (e.g. package-1.0.dist-info). These files include things + like: + + - METADATA: The distribution metadata including fields + like Name and Version and Description. + - entry_points.txt: A series of entry points as defined in + `the entry points spec <https://packaging.python.org/en/latest/specifications/entry-points/#file-format>`_. + - RECORD: A record of files according to + `this recording spec <https://packaging.python.org/en/latest/specifications/recording-installed-packages/#the-record-file>`_. + + A package may provide any set of files, including those + not listed here or none at all. + :param filename: The name of the file in the distribution info. :return: The text if found, otherwise None. """ @abc.abstractmethod - def locate_file(self, path): + def locate_file(self, path: str | os.PathLike[str]) -> SimplePath: """ - Given a path to a file in this distribution, return a path + Given a path to a file in this distribution, return a SimplePath to it. """ @classmethod - def from_name(cls, name: str): + def from_name(cls, name: str) -> Distribution: """Return the Distribution for the given package name. :param name: The name of the distribution package to search for. @@ -394,21 +404,23 @@ def from_name(cls, name: str): if not name: raise ValueError("A distribution name is required.") try: - return next(cls.discover(name=name)) + return next(iter(cls.discover(name=name))) except StopIteration: raise PackageNotFoundError(name) @classmethod - def discover(cls, **kwargs): + def discover( + cls, *, context: Optional[DistributionFinder.Context] = None, **kwargs + ) -> Iterable[Distribution]: """Return an iterable of Distribution objects for all packages. Pass a ``context`` or pass keyword arguments for constructing a context. :context: A ``DistributionFinder.Context`` object. - :return: Iterable of Distribution objects for all packages. + :return: Iterable of Distribution objects for packages matching + the context. """ - context = kwargs.pop('context', None) if context and kwargs: raise ValueError("cannot accept context and kwargs") context = context or DistributionFinder.Context(**kwargs) @@ -417,8 +429,8 @@ def discover(cls, **kwargs): ) @staticmethod - def at(path): - """Return a Distribution for the indicated metadata path + def at(path: str | os.PathLike[str]) -> Distribution: + """Return a Distribution for the indicated metadata path. :param path: a string or path-like object :return: a concrete Distribution instance for the path @@ -427,7 +439,7 @@ def at(path): @staticmethod def _discover_resolvers(): - """Search the meta_path for resolvers.""" + """Search the meta_path for resolvers (MetadataPathFinders).""" declared = ( getattr(finder, 'find_distributions', None) for finder in sys.meta_path ) @@ -438,8 +450,15 @@ def metadata(self) -> _meta.PackageMetadata: """Return the parsed metadata for this Distribution. The returned object will have keys that name the various bits of - metadata. See PEP 566 for details. + metadata per the + `Core metadata specifications <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_. + + Custom providers may provide the METADATA file or override this + property. """ + # deferred for performance (python/cpython#109829) + from . import _adapters + opt_text = ( self.read_text('METADATA') or self.read_text('PKG-INFO') @@ -452,7 +471,7 @@ def metadata(self) -> _meta.PackageMetadata: return _adapters.Message(email.message_from_string(text)) @property - def name(self): + def name(self) -> str: """Return the 'Name' metadata for the distribution package.""" return self.metadata['Name'] @@ -462,16 +481,22 @@ def _normalized_name(self): return Prepared.normalize(self.name) @property - def version(self): + def version(self) -> str: """Return the 'Version' metadata for the distribution package.""" return self.metadata['Version'] @property - def entry_points(self): + def entry_points(self) -> EntryPoints: + """ + Return EntryPoints for this distribution. + + Custom providers may provide the ``entry_points.txt`` file + or override this property. + """ return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self) @property - def files(self): + def files(self) -> Optional[List[PackagePath]]: """Files in this distribution. :return: List of PackagePath for this distribution or None @@ -480,6 +505,10 @@ def files(self): (i.e. RECORD for dist-info, or installed-files.txt or SOURCES.txt for egg-info) is missing. Result may be empty if the metadata exists but is empty. + + Custom providers are recommended to provide a "RECORD" file (in + ``read_text``) or override this property to allow for callers to be + able to resolve filenames provided by the package. """ def make_file(name, hash=None, size_str=None): @@ -491,6 +520,10 @@ def make_file(name, hash=None, size_str=None): @pass_none def make_files(lines): + # Delay csv import, since Distribution.files is not as widely used + # as other parts of importlib.metadata + import csv + return starmap(make_file, csv.reader(lines)) @pass_none @@ -507,7 +540,7 @@ def skip_missing_files(package_paths): def _read_files_distinfo(self): """ - Read the lines of RECORD + Read the lines of RECORD. """ text = self.read_text('RECORD') return text and text.splitlines() @@ -534,7 +567,7 @@ def _read_files_egginfo_installed(self): paths = ( (subdir / name) .resolve() - .relative_to(self.locate_file('').resolve()) + .relative_to(self.locate_file('').resolve(), walk_up=True) .as_posix() for name in text.splitlines() ) @@ -556,7 +589,7 @@ def _read_files_egginfo_sources(self): return text and map('"{}"'.format, text.splitlines()) @property - def requires(self): + def requires(self) -> Optional[List[str]]: """Generated requirements specified for this Distribution""" reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs() return reqs and list(reqs) @@ -607,10 +640,23 @@ def url_req_space(req): space = url_req_space(section.value) yield section.value + space + quoted_marker(section.name) + @property + def origin(self): + return self._load_json('direct_url.json') + + def _load_json(self, filename): + return pass_none(json.loads)( + self.read_text(filename), + object_hook=lambda data: types.SimpleNamespace(**data), + ) + class DistributionFinder(MetaPathFinder): """ A MetaPathFinder capable of discovering installed distributions. + + Custom providers should implement this interface in order to + supply metadata. """ class Context: @@ -623,6 +669,17 @@ class Context: Each DistributionFinder may expect any parameters and should attempt to honor the canonical parameters defined below when appropriate. + + This mechanism gives a custom provider a means to + solicit additional details from the caller beyond + "name" and "path" when searching distributions. + For example, imagine a provider that exposes suites + of packages in either a "public" or "private" ``realm``. + A caller may wish to query only for distributions in + a particular realm and could call + ``distributions(realm="private")`` to signal to the + custom provider to only include distributions from that + realm. """ name = None @@ -635,7 +692,7 @@ def __init__(self, **kwargs): vars(self).update(kwargs) @property - def path(self): + def path(self) -> List[str]: """ The sequence of directory path that a distribution finder should search. @@ -646,7 +703,7 @@ def path(self): return vars(self).get('path', sys.path) @abc.abstractmethod - def find_distributions(self, context=Context()): + def find_distributions(self, context=Context()) -> Iterable[Distribution]: """ Find distributions. @@ -658,11 +715,18 @@ def find_distributions(self, context=Context()): class FastPath: """ - Micro-optimized class for searching a path for - children. + Micro-optimized class for searching a root for children. + + Root is a path on the file system that may contain metadata + directories either as natural directories or within a zip file. >>> FastPath('').children() ['...'] + + FastPath objects are cached and recycled for any given root. + + >>> FastPath('foobar') is FastPath('foobar') + True """ @functools.lru_cache() # type: ignore @@ -704,7 +768,19 @@ def lookup(self, mtime): class Lookup: + """ + A micro-optimized class for searching a (fast) path for metadata. + """ + def __init__(self, path: FastPath): + """ + Calculate all of the children representing metadata. + + From the children in the path, calculate early all of the + children that appear to represent metadata (infos) or legacy + metadata (eggs). + """ + base = os.path.basename(path.root).lower() base_is_egg = base.endswith(".egg") self.infos = FreezableDefaultDict(list) @@ -725,7 +801,10 @@ def __init__(self, path: FastPath): self.infos.freeze() self.eggs.freeze() - def search(self, prepared): + def search(self, prepared: Prepared): + """ + Yield all infos and eggs matching the Prepared query. + """ infos = ( self.infos[prepared.normalized] if prepared @@ -741,13 +820,28 @@ def search(self, prepared): class Prepared: """ - A prepared search for metadata on a possibly-named package. + A prepared search query for metadata on a possibly-named package. + + Pre-calculates the normalization to prevent repeated operations. + + >>> none = Prepared(None) + >>> none.normalized + >>> none.legacy_normalized + >>> bool(none) + False + >>> sample = Prepared('Sample__Pkg-name.foo') + >>> sample.normalized + 'sample_pkg_name_foo' + >>> sample.legacy_normalized + 'sample__pkg_name.foo' + >>> bool(sample) + True """ normalized = None legacy_normalized = None - def __init__(self, name): + def __init__(self, name: Optional[str]): self.name = name if name is None: return @@ -775,7 +869,9 @@ def __bool__(self): class MetadataPathFinder(DistributionFinder): @classmethod - def find_distributions(cls, context=DistributionFinder.Context()): + def find_distributions( + cls, context=DistributionFinder.Context() + ) -> Iterable[PathDistribution]: """ Find distributions. @@ -795,19 +891,20 @@ def _search_paths(cls, name, paths): path.search(prepared) for path in map(FastPath, paths) ) - def invalidate_caches(cls): + @classmethod + def invalidate_caches(cls) -> None: FastPath.__new__.cache_clear() class PathDistribution(Distribution): - def __init__(self, path: SimplePath): + def __init__(self, path: SimplePath) -> None: """Construct a distribution. :param path: SimplePath indicating the metadata directory. """ self._path = path - def read_text(self, filename): + def read_text(self, filename: str | os.PathLike[str]) -> Optional[str]: with suppress( FileNotFoundError, IsADirectoryError, @@ -817,9 +914,11 @@ def read_text(self, filename): ): return self._path.joinpath(filename).read_text(encoding='utf-8') + return None + read_text.__doc__ = Distribution.read_text.__doc__ - def locate_file(self, path): + def locate_file(self, path: str | os.PathLike[str]) -> SimplePath: return self._path.parent / path @property @@ -852,7 +951,7 @@ def _name_from_stem(stem): return name -def distribution(distribution_name): +def distribution(distribution_name: str) -> Distribution: """Get the ``Distribution`` instance for the named package. :param distribution_name: The name of the distribution package as a string. @@ -861,7 +960,7 @@ def distribution(distribution_name): return Distribution.from_name(distribution_name) -def distributions(**kwargs): +def distributions(**kwargs) -> Iterable[Distribution]: """Get all ``Distribution`` instances in the current environment. :return: An iterable of ``Distribution`` instances. @@ -869,7 +968,7 @@ def distributions(**kwargs): return Distribution.discover(**kwargs) -def metadata(distribution_name) -> _meta.PackageMetadata: +def metadata(distribution_name: str) -> _meta.PackageMetadata: """Get the metadata for the named package. :param distribution_name: The name of the distribution package to query. @@ -878,7 +977,7 @@ def metadata(distribution_name) -> _meta.PackageMetadata: return Distribution.from_name(distribution_name).metadata -def version(distribution_name): +def version(distribution_name: str) -> str: """Get the version string for the named package. :param distribution_name: The name of the distribution package to query. @@ -912,7 +1011,7 @@ def entry_points(**params) -> EntryPoints: return EntryPoints(eps).select(**params) -def files(distribution_name): +def files(distribution_name: str) -> Optional[List[PackagePath]]: """Return a list of files for the named package. :param distribution_name: The name of the distribution package to query. @@ -921,11 +1020,11 @@ def files(distribution_name): return distribution(distribution_name).files -def requires(distribution_name): +def requires(distribution_name: str) -> Optional[List[str]]: """ Return a list of requirements for the named package. - :return: An iterator of requirements, suitable for + :return: An iterable of requirements, suitable for packaging.requirement.Requirement. """ return distribution(distribution_name).requires @@ -952,13 +1051,42 @@ def _top_level_declared(dist): return (dist.read_text('top_level.txt') or '').split() +def _topmost(name: PackagePath) -> Optional[str]: + """ + Return the top-most parent as long as there is a parent. + """ + top, *rest = name.parts + return top if rest else None + + +def _get_toplevel_name(name: PackagePath) -> str: + """ + Infer a possibly importable module name from a name presumed on + sys.path. + + >>> _get_toplevel_name(PackagePath('foo.py')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo.pyc')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo/__init__.py')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo.pth')) + 'foo.pth' + >>> _get_toplevel_name(PackagePath('foo.dist-info')) + 'foo.dist-info' + """ + return _topmost(name) or ( + # python/typeshed#10328 + inspect.getmodulename(name) # type: ignore + or str(name) + ) + + def _top_level_inferred(dist): - opt_names = { - f.parts[0] if len(f.parts) > 1 else inspect.getmodulename(f) - for f in always_iterable(dist.files) - } + opt_names = set(map(_get_toplevel_name, always_iterable(dist.files))) - @pass_none def importable_name(name): return '.' not in name diff --git a/Lib/importlib/metadata/_adapters.py b/Lib/importlib/metadata/_adapters.py index 6aed69a3085..59116880895 100644 --- a/Lib/importlib/metadata/_adapters.py +++ b/Lib/importlib/metadata/_adapters.py @@ -53,7 +53,7 @@ def __iter__(self): def __getitem__(self, item): """ Warn users that a ``KeyError`` can be expected when a - mising key is supplied. Ref python/importlib_metadata#371. + missing key is supplied. Ref python/importlib_metadata#371. """ res = super().__getitem__(item) if res is None: diff --git a/Lib/importlib/metadata/_meta.py b/Lib/importlib/metadata/_meta.py index c9a7ef906a8..1927d0f624d 100644 --- a/Lib/importlib/metadata/_meta.py +++ b/Lib/importlib/metadata/_meta.py @@ -1,3 +1,6 @@ +from __future__ import annotations + +import os from typing import Protocol from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload @@ -6,30 +9,27 @@ class PackageMetadata(Protocol): - def __len__(self) -> int: - ... # pragma: no cover + def __len__(self) -> int: ... # pragma: no cover - def __contains__(self, item: str) -> bool: - ... # pragma: no cover + def __contains__(self, item: str) -> bool: ... # pragma: no cover - def __getitem__(self, key: str) -> str: - ... # pragma: no cover + def __getitem__(self, key: str) -> str: ... # pragma: no cover - def __iter__(self) -> Iterator[str]: - ... # pragma: no cover + def __iter__(self) -> Iterator[str]: ... # pragma: no cover @overload - def get(self, name: str, failobj: None = None) -> Optional[str]: - ... # pragma: no cover + def get( + self, name: str, failobj: None = None + ) -> Optional[str]: ... # pragma: no cover @overload - def get(self, name: str, failobj: _T) -> Union[str, _T]: - ... # pragma: no cover + def get(self, name: str, failobj: _T) -> Union[str, _T]: ... # pragma: no cover # overload per python/importlib_metadata#435 @overload - def get_all(self, name: str, failobj: None = None) -> Optional[List[Any]]: - ... # pragma: no cover + def get_all( + self, name: str, failobj: None = None + ) -> Optional[List[Any]]: ... # pragma: no cover @overload def get_all(self, name: str, failobj: _T) -> Union[List[Any], _T]: @@ -44,20 +44,24 @@ def json(self) -> Dict[str, Union[str, List[str]]]: """ -class SimplePath(Protocol[_T]): +class SimplePath(Protocol): """ - A minimal subset of pathlib.Path required by PathDistribution. + A minimal subset of pathlib.Path required by Distribution. """ - def joinpath(self) -> _T: - ... # pragma: no cover + def joinpath( + self, other: Union[str, os.PathLike[str]] + ) -> SimplePath: ... # pragma: no cover - def __truediv__(self, other: Union[str, _T]) -> _T: - ... # pragma: no cover + def __truediv__( + self, other: Union[str, os.PathLike[str]] + ) -> SimplePath: ... # pragma: no cover @property - def parent(self) -> _T: - ... # pragma: no cover + def parent(self) -> SimplePath: ... # pragma: no cover + + def read_text(self, encoding=None) -> str: ... # pragma: no cover + + def read_bytes(self) -> bytes: ... # pragma: no cover - def read_text(self) -> str: - ... # pragma: no cover + def exists(self) -> bool: ... # pragma: no cover diff --git a/Lib/importlib/metadata/diagnose.py b/Lib/importlib/metadata/diagnose.py new file mode 100644 index 00000000000..e405471ac4d --- /dev/null +++ b/Lib/importlib/metadata/diagnose.py @@ -0,0 +1,21 @@ +import sys + +from . import Distribution + + +def inspect(path): + print("Inspecting", path) + dists = list(Distribution.discover(path=[path])) + if not dists: + return + print("Found", len(dists), "packages:", end=' ') + print(', '.join(dist.name for dist in dists)) + + +def run(): + for path in sys.path: + inspect(path) + + +if __name__ == '__main__': + run() diff --git a/Lib/importlib/resources/__init__.py b/Lib/importlib/resources/__init__.py index 34e3a9950cc..723c9f9eb33 100644 --- a/Lib/importlib/resources/__init__.py +++ b/Lib/importlib/resources/__init__.py @@ -1,20 +1,27 @@ -"""Read resources contained within a package.""" +""" +Read resources contained within a package. + +This codebase is shared between importlib.resources in the stdlib +and importlib_resources in PyPI. See +https://github.com/python/importlib_metadata/wiki/Development-Methodology +for more detail. +""" from ._common import ( as_file, files, Package, + Anchor, ) -from ._legacy import ( +from ._functional import ( contents, + is_resource, open_binary, - read_binary, open_text, - read_text, - is_resource, path, - Resource, + read_binary, + read_text, ) from .abc import ResourceReader @@ -22,11 +29,11 @@ __all__ = [ 'Package', - 'Resource', + 'Anchor', 'ResourceReader', 'as_file', - 'contents', 'files', + 'contents', 'is_resource', 'open_binary', 'open_text', diff --git a/Lib/importlib/resources/_common.py b/Lib/importlib/resources/_common.py index b402e05116e..d9128266a2e 100644 --- a/Lib/importlib/resources/_common.py +++ b/Lib/importlib/resources/_common.py @@ -12,8 +12,6 @@ from typing import Union, Optional, cast from .abc import ResourceReader, Traversable -from ._adapters import wrap_spec - Package = Union[types.ModuleType, str] Anchor = Package @@ -27,6 +25,8 @@ def package_to_anchor(func): >>> files('a', 'b') Traceback (most recent call last): TypeError: files() takes from 0 to 1 positional arguments but 2 were given + + Remove this compatibility in Python 3.14. """ undefined = object() @@ -66,10 +66,10 @@ def get_resource_reader(package: types.ModuleType) -> Optional[ResourceReader]: # zipimport.zipimporter does not support weak references, resulting in a # TypeError. That seems terrible. spec = package.__spec__ - reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore + reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore[union-attr] if reader is None: return None - return reader(spec.name) # type: ignore + return reader(spec.name) # type: ignore[union-attr] @functools.singledispatch @@ -93,12 +93,13 @@ def _infer_caller(): """ def is_this_file(frame_info): - return frame_info.filename == __file__ + return frame_info.filename == stack[0].filename def is_wrapper(frame_info): return frame_info.function == 'wrapper' - not_this_file = itertools.filterfalse(is_this_file, inspect.stack()) + stack = inspect.stack() + not_this_file = itertools.filterfalse(is_this_file, stack) # also exclude 'wrapper' due to singledispatch in the call stack callers = itertools.filterfalse(is_wrapper, not_this_file) return next(callers).frame @@ -109,6 +110,9 @@ def from_package(package: types.ModuleType): Return a Traversable object for the given package. """ + # deferred for performance (python/cpython#109829) + from ._adapters import wrap_spec + spec = wrap_spec(package) reader = spec.loader.get_resource_reader(spec.name) return reader.files() diff --git a/Lib/importlib/resources/_functional.py b/Lib/importlib/resources/_functional.py new file mode 100644 index 00000000000..f59416f2dd6 --- /dev/null +++ b/Lib/importlib/resources/_functional.py @@ -0,0 +1,81 @@ +"""Simplified function-based API for importlib.resources""" + +import warnings + +from ._common import files, as_file + + +_MISSING = object() + + +def open_binary(anchor, *path_names): + """Open for binary reading the *resource* within *package*.""" + return _get_resource(anchor, path_names).open('rb') + + +def open_text(anchor, *path_names, encoding=_MISSING, errors='strict'): + """Open for text reading the *resource* within *package*.""" + encoding = _get_encoding_arg(path_names, encoding) + resource = _get_resource(anchor, path_names) + return resource.open('r', encoding=encoding, errors=errors) + + +def read_binary(anchor, *path_names): + """Read and return contents of *resource* within *package* as bytes.""" + return _get_resource(anchor, path_names).read_bytes() + + +def read_text(anchor, *path_names, encoding=_MISSING, errors='strict'): + """Read and return contents of *resource* within *package* as str.""" + encoding = _get_encoding_arg(path_names, encoding) + resource = _get_resource(anchor, path_names) + return resource.read_text(encoding=encoding, errors=errors) + + +def path(anchor, *path_names): + """Return the path to the *resource* as an actual file system path.""" + return as_file(_get_resource(anchor, path_names)) + + +def is_resource(anchor, *path_names): + """Return ``True`` if there is a resource named *name* in the package, + + Otherwise returns ``False``. + """ + return _get_resource(anchor, path_names).is_file() + + +def contents(anchor, *path_names): + """Return an iterable over the named resources within the package. + + The iterable returns :class:`str` resources (e.g. files). + The iterable does not recurse into subdirectories. + """ + warnings.warn( + "importlib.resources.contents is deprecated. " + "Use files(anchor).iterdir() instead.", + DeprecationWarning, + stacklevel=1, + ) + return (resource.name for resource in _get_resource(anchor, path_names).iterdir()) + + +def _get_encoding_arg(path_names, encoding): + # For compatibility with versions where *encoding* was a positional + # argument, it needs to be given explicitly when there are multiple + # *path_names*. + # This limitation can be removed in Python 3.15. + if encoding is _MISSING: + if len(path_names) > 1: + raise TypeError( + "'encoding' argument required with multiple path names", + ) + else: + return 'utf-8' + return encoding + + +def _get_resource(anchor, path_names): + if anchor is None: + raise TypeError("anchor must be module or string, got None") + return files(anchor).joinpath(*path_names) diff --git a/Lib/importlib/resources/readers.py b/Lib/importlib/resources/readers.py index c3cdf769cbe..70fc7e2b9c0 100644 --- a/Lib/importlib/resources/readers.py +++ b/Lib/importlib/resources/readers.py @@ -1,8 +1,14 @@ +from __future__ import annotations + import collections +import contextlib import itertools import pathlib import operator +import re +import warnings import zipfile +from collections.abc import Iterator from . import abc @@ -31,8 +37,10 @@ def files(self): class ZipReader(abc.TraversableResources): def __init__(self, loader, module): - _, _, name = module.rpartition('.') - self.prefix = loader.prefix.replace('\\', '/') + name + '/' + self.prefix = loader.prefix.replace('\\', '/') + if loader.is_package(module): + _, _, name = module.rpartition('.') + self.prefix += name + '/' self.archive = loader.archive def open_resource(self, resource): @@ -62,7 +70,7 @@ class MultiplexedPath(abc.Traversable): """ def __init__(self, *paths): - self._paths = list(map(pathlib.Path, remove_duplicates(paths))) + self._paths = list(map(_ensure_traversable, remove_duplicates(paths))) if not self._paths: message = 'MultiplexedPath must contain at least one path' raise FileNotFoundError(message) @@ -130,7 +138,40 @@ class NamespaceReader(abc.TraversableResources): def __init__(self, namespace_path): if 'NamespacePath' not in str(namespace_path): raise ValueError('Invalid path') - self.path = MultiplexedPath(*list(namespace_path)) + self.path = MultiplexedPath(*filter(bool, map(self._resolve, namespace_path))) + + @classmethod + def _resolve(cls, path_str) -> abc.Traversable | None: + r""" + Given an item from a namespace path, resolve it to a Traversable. + + path_str might be a directory on the filesystem or a path to a + zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or + ``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``. + + path_str might also be a sentinel used by editable packages to + trigger other behaviors (see python/importlib_resources#311). + In that case, return None. + """ + dirs = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir()) + return next(dirs, None) + + @classmethod + def _candidate_paths(cls, path_str: str) -> Iterator[abc.Traversable]: + yield pathlib.Path(path_str) + yield from cls._resolve_zip_path(path_str) + + @staticmethod + def _resolve_zip_path(path_str: str): + for match in reversed(list(re.finditer(r'[\\/]', path_str))): + with contextlib.suppress( + FileNotFoundError, + IsADirectoryError, + NotADirectoryError, + PermissionError, + ): + inner = path_str[match.end() :].replace('\\', '/') + '/' + yield zipfile.Path(path_str[: match.start()], inner.lstrip('/')) def resource_path(self, resource): """ @@ -142,3 +183,21 @@ def resource_path(self, resource): def files(self): return self.path + + +def _ensure_traversable(path): + """ + Convert deprecated string arguments to traversables (pathlib.Path). + + Remove with Python 3.15. + """ + if not isinstance(path, str): + return path + + warnings.warn( + "String arguments are deprecated. Pass a Traversable instead.", + DeprecationWarning, + stacklevel=3, + ) + + return pathlib.Path(path) diff --git a/Lib/importlib/resources/simple.py b/Lib/importlib/resources/simple.py index 7770c922c84..2e75299b13a 100644 --- a/Lib/importlib/resources/simple.py +++ b/Lib/importlib/resources/simple.py @@ -77,7 +77,7 @@ class ResourceHandle(Traversable): def __init__(self, parent: ResourceContainer, name: str): self.parent = parent - self.name = name # type: ignore + self.name = name # type: ignore[misc] def is_file(self): return True @@ -88,7 +88,7 @@ def is_dir(self): def open(self, mode='r', *args, **kwargs): stream = self.parent.reader.open_binary(self.name) if 'b' not in mode: - stream = io.TextIOWrapper(*args, **kwargs) + stream = io.TextIOWrapper(stream, *args, **kwargs) return stream def joinpath(self, name): diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index f4d6e823315..284206b62f9 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -135,7 +135,7 @@ class _incompatible_extension_module_restrictions: may not be imported in a subinterpreter. That implies modules that do not implement multi-phase init or that explicitly of out. - Likewise for modules import in a subinterpeter with its own GIL + Likewise for modules import in a subinterpreter with its own GIL when the extension does not support a per-interpreter GIL. This implies the module does not have a Py_mod_multiple_interpreters slot set to Py_MOD_PER_INTERPRETER_GIL_SUPPORTED. @@ -145,7 +145,7 @@ class _incompatible_extension_module_restrictions: You can get the same effect as this function by implementing the basic interface of multi-phase init (PEP 489) and lying about - support for mulitple interpreters (or per-interpreter GIL). + support for multiple interpreters (or per-interpreter GIL). """ def __init__(self, *, disable_check): @@ -171,36 +171,57 @@ class _LazyModule(types.ModuleType): def __getattribute__(self, attr): """Trigger the load of the module and return the attribute.""" - # All module metadata must be garnered from __spec__ in order to avoid - # using mutated values. - # Stop triggering this method. - self.__class__ = types.ModuleType - # Get the original name to make sure no object substitution occurred - # in sys.modules. - original_name = self.__spec__.name - # Figure out exactly what attributes were mutated between the creation - # of the module and now. - attrs_then = self.__spec__.loader_state['__dict__'] - attrs_now = self.__dict__ - attrs_updated = {} - for key, value in attrs_now.items(): - # Code that set the attribute may have kept a reference to the - # assigned object, making identity more important than equality. - if key not in attrs_then: - attrs_updated[key] = value - elif id(attrs_now[key]) != id(attrs_then[key]): - attrs_updated[key] = value - self.__spec__.loader.exec_module(self) - # If exec_module() was used directly there is no guarantee the module - # object was put into sys.modules. - if original_name in sys.modules: - if id(self) != id(sys.modules[original_name]): - raise ValueError(f"module object for {original_name!r} " - "substituted in sys.modules during a lazy " - "load") - # Update after loading since that's what would happen in an eager - # loading situation. - self.__dict__.update(attrs_updated) + __spec__ = object.__getattribute__(self, '__spec__') + loader_state = __spec__.loader_state + with loader_state['lock']: + # Only the first thread to get the lock should trigger the load + # and reset the module's class. The rest can now getattr(). + if object.__getattribute__(self, '__class__') is _LazyModule: + __class__ = loader_state['__class__'] + + # Reentrant calls from the same thread must be allowed to proceed without + # triggering the load again. + # exec_module() and self-referential imports are the primary ways this can + # happen, but in any case we must return something to avoid deadlock. + if loader_state['is_loading']: + return __class__.__getattribute__(self, attr) + loader_state['is_loading'] = True + + __dict__ = __class__.__getattribute__(self, '__dict__') + + # All module metadata must be gathered from __spec__ in order to avoid + # using mutated values. + # Get the original name to make sure no object substitution occurred + # in sys.modules. + original_name = __spec__.name + # Figure out exactly what attributes were mutated between the creation + # of the module and now. + attrs_then = loader_state['__dict__'] + attrs_now = __dict__ + attrs_updated = {} + for key, value in attrs_now.items(): + # Code that set an attribute may have kept a reference to the + # assigned object, making identity more important than equality. + if key not in attrs_then: + attrs_updated[key] = value + elif id(attrs_now[key]) != id(attrs_then[key]): + attrs_updated[key] = value + __spec__.loader.exec_module(self) + # If exec_module() was used directly there is no guarantee the module + # object was put into sys.modules. + if original_name in sys.modules: + if id(self) != id(sys.modules[original_name]): + raise ValueError(f"module object for {original_name!r} " + "substituted in sys.modules during a lazy " + "load") + # Update after loading since that's what would happen in an eager + # loading situation. + __dict__.update(attrs_updated) + # Finally, stop triggering this method, if the module did not + # already update its own __class__. + if isinstance(self, _LazyModule): + object.__setattr__(self, '__class__', __class__) + return getattr(self, attr) def __delattr__(self, attr): @@ -235,6 +256,9 @@ def create_module(self, spec): def exec_module(self, module): """Make the module load lazily.""" + # Threading is only needed for lazy loading, and importlib.util can + # be pulled in at interpreter startup, so defer until needed. + import threading module.__spec__.loader = self.loader module.__loader__ = self.loader # Don't need to worry about deep-copying as trying to set an attribute @@ -244,5 +268,7 @@ def exec_module(self, module): loader_state = {} loader_state['__dict__'] = module.__dict__.copy() loader_state['__class__'] = module.__class__ + loader_state['lock'] = threading.RLock() + loader_state['is_loading'] = False module.__spec__.loader_state = loader_state module.__class__ = _LazyModule diff --git a/Lib/nturl2path.py b/Lib/nturl2path.py index 61852aff589..757fd01bec8 100644 --- a/Lib/nturl2path.py +++ b/Lib/nturl2path.py @@ -15,32 +15,29 @@ def url2pathname(url): # become # C:\foo\bar\spam.foo import string, urllib.parse + if url[:3] == '///': + # URL has an empty authority section, so the path begins on the third + # character. + url = url[2:] + elif url[:12] == '//localhost/': + # Skip past 'localhost' authority. + url = url[11:] + if url[:3] == '///': + # Skip past extra slash before UNC drive in URL path. + url = url[1:] # Windows itself uses ":" even in URLs. url = url.replace(':', '|') if not '|' in url: # No drive specifier, just convert slashes - if url[:4] == '////': - # path is something like ////host/path/on/remote/host - # convert this to \\host\path\on\remote\host - # (notice halving of slashes at the start of the path) - url = url[2:] - components = url.split('/') # make sure not to convert quoted slashes :-) - return urllib.parse.unquote('\\'.join(components)) + return urllib.parse.unquote(url.replace('/', '\\')) comp = url.split('|') if len(comp) != 2 or comp[0][-1] not in string.ascii_letters: error = 'Bad URL: ' + url raise OSError(error) drive = comp[0][-1].upper() - components = comp[1].split('/') - path = drive + ':' - for comp in components: - if comp: - path = path + '\\' + urllib.parse.unquote(comp) - # Issue #11474 - handing url such as |c/| - if path.endswith(':') and url.endswith('/'): - path += '\\' - return path + tail = urllib.parse.unquote(comp[1].replace('/', '\\')) + return drive + ':' + tail def pathname2url(p): """OS-specific conversion from a file system path to a relative URL @@ -52,30 +49,21 @@ def pathname2url(p): import urllib.parse # First, clean up some special forms. We are going to sacrifice # the additional information anyway - if p[:4] == '\\\\?\\': + p = p.replace('\\', '/') + if p[:4] == '//?/': p = p[4:] - if p[:4].upper() == 'UNC\\': - p = '\\' + p[4:] + if p[:4].upper() == 'UNC/': + p = '//' + p[4:] elif p[1:2] != ':': raise OSError('Bad path: ' + p) if not ':' in p: - # No drive specifier, just convert slashes and quote the name - if p[:2] == '\\\\': - # path is something like \\host\path\on\remote\host - # convert this to ////host/path/on/remote/host - # (notice doubling of slashes at the start of the path) - p = '\\\\' + p - components = p.split('\\') - return urllib.parse.quote('/'.join(components)) + # No DOS drive specified, just quote the pathname + return urllib.parse.quote(p) comp = p.split(':', maxsplit=2) if len(comp) != 2 or len(comp[0]) > 1: error = 'Bad path: ' + p raise OSError(error) drive = urllib.parse.quote(comp[0].upper()) - components = comp[1].split('\\') - path = '///' + drive + ':' - for comp in components: - if comp: - path = path + '/' + urllib.parse.quote(comp) - return path + tail = urllib.parse.quote(comp[1]) + return '///' + drive + ':' + tail diff --git a/Lib/pathlib.py b/Lib/pathlib.py deleted file mode 100644 index bd5a096f9e3..00000000000 --- a/Lib/pathlib.py +++ /dev/null @@ -1,1435 +0,0 @@ -"""Object-oriented filesystem paths. - -This module provides classes to represent abstract paths and concrete -paths with operations that have semantics appropriate for different -operating systems. -""" - -import fnmatch -import functools -import io -import ntpath -import os -import posixpath -import re -import sys -import warnings -from _collections_abc import Sequence -from errno import ENOENT, ENOTDIR, EBADF, ELOOP -from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO -from urllib.parse import quote_from_bytes as urlquote_from_bytes - - -__all__ = [ - "PurePath", "PurePosixPath", "PureWindowsPath", - "Path", "PosixPath", "WindowsPath", - ] - -# -# Internals -# - -# Reference for Windows paths can be found at -# https://learn.microsoft.com/en-gb/windows/win32/fileio/naming-a-file . -_WIN_RESERVED_NAMES = frozenset( - {'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} | - {f'COM{c}' for c in '123456789\xb9\xb2\xb3'} | - {f'LPT{c}' for c in '123456789\xb9\xb2\xb3'} -) - -_WINERROR_NOT_READY = 21 # drive exists but is not accessible -_WINERROR_INVALID_NAME = 123 # fix for bpo-35306 -_WINERROR_CANT_RESOLVE_FILENAME = 1921 # broken symlink pointing to itself - -# EBADF - guard against macOS `stat` throwing EBADF -_IGNORED_ERRNOS = (ENOENT, ENOTDIR, EBADF, ELOOP) - -_IGNORED_WINERRORS = ( - _WINERROR_NOT_READY, - _WINERROR_INVALID_NAME, - _WINERROR_CANT_RESOLVE_FILENAME) - -def _ignore_error(exception): - return (getattr(exception, 'errno', None) in _IGNORED_ERRNOS or - getattr(exception, 'winerror', None) in _IGNORED_WINERRORS) - - -@functools.cache -def _is_case_sensitive(flavour): - return flavour.normcase('Aa') == 'Aa' - -# -# Globbing helpers -# - - -# fnmatch.translate() returns a regular expression that includes a prefix and -# a suffix, which enable matching newlines and ensure the end of the string is -# matched, respectively. These features are undesirable for our implementation -# of PurePatch.match(), which represents path separators as newlines and joins -# pattern segments together. As a workaround, we define a slice object that -# can remove the prefix and suffix from any translate() result. See the -# _compile_pattern_lines() function for more details. -_FNMATCH_PREFIX, _FNMATCH_SUFFIX = fnmatch.translate('_').split('_') -_FNMATCH_SLICE = slice(len(_FNMATCH_PREFIX), -len(_FNMATCH_SUFFIX)) -_SWAP_SEP_AND_NEWLINE = { - '/': str.maketrans({'/': '\n', '\n': '/'}), - '\\': str.maketrans({'\\': '\n', '\n': '\\'}), -} - - -@functools.lru_cache() -def _make_selector(pattern_parts, flavour, case_sensitive): - pat = pattern_parts[0] - if not pat: - return _TerminatingSelector() - if pat == '**': - child_parts_idx = 1 - while child_parts_idx < len(pattern_parts) and pattern_parts[child_parts_idx] == '**': - child_parts_idx += 1 - child_parts = pattern_parts[child_parts_idx:] - if '**' in child_parts: - cls = _DoubleRecursiveWildcardSelector - else: - cls = _RecursiveWildcardSelector - else: - child_parts = pattern_parts[1:] - if pat == '..': - cls = _ParentSelector - elif '**' in pat: - raise ValueError("Invalid pattern: '**' can only be an entire path component") - else: - cls = _WildcardSelector - return cls(pat, child_parts, flavour, case_sensitive) - - -@functools.lru_cache(maxsize=256) -def _compile_pattern(pat, case_sensitive): - flags = re.NOFLAG if case_sensitive else re.IGNORECASE - return re.compile(fnmatch.translate(pat), flags).match - - -@functools.lru_cache() -def _compile_pattern_lines(pattern_lines, case_sensitive): - """Compile the given pattern lines to an `re.Pattern` object. - - The *pattern_lines* argument is a glob-style pattern (e.g. '*/*.py') with - its path separators and newlines swapped (e.g. '*\n*.py`). By using - newlines to separate path components, and not setting `re.DOTALL`, we - ensure that the `*` wildcard cannot match path separators. - - The returned `re.Pattern` object may have its `match()` method called to - match a complete pattern, or `search()` to match from the right. The - argument supplied to these methods must also have its path separators and - newlines swapped. - """ - - # Match the start of the path, or just after a path separator - parts = ['^'] - for part in pattern_lines.splitlines(keepends=True): - if part == '*\n': - part = r'.+\n' - elif part == '*': - part = r'.+' - else: - # Any other component: pass to fnmatch.translate(). We slice off - # the common prefix and suffix added by translate() to ensure that - # re.DOTALL is not set, and the end of the string not matched, - # respectively. With DOTALL not set, '*' wildcards will not match - # path separators, because the '.' characters in the pattern will - # not match newlines. - part = fnmatch.translate(part)[_FNMATCH_SLICE] - parts.append(part) - # Match the end of the path, always. - parts.append(r'\Z') - flags = re.MULTILINE - if not case_sensitive: - flags |= re.IGNORECASE - return re.compile(''.join(parts), flags=flags) - - -class _Selector: - """A selector matches a specific glob pattern part against the children - of a given path.""" - - def __init__(self, child_parts, flavour, case_sensitive): - self.child_parts = child_parts - if child_parts: - self.successor = _make_selector(child_parts, flavour, case_sensitive) - self.dironly = True - else: - self.successor = _TerminatingSelector() - self.dironly = False - - def select_from(self, parent_path): - """Iterate over all child paths of `parent_path` matched by this - selector. This can contain parent_path itself.""" - path_cls = type(parent_path) - scandir = path_cls._scandir - if not parent_path.is_dir(): - return iter([]) - return self._select_from(parent_path, scandir) - - -class _TerminatingSelector: - - def _select_from(self, parent_path, scandir): - yield parent_path - - -class _ParentSelector(_Selector): - - def __init__(self, name, child_parts, flavour, case_sensitive): - _Selector.__init__(self, child_parts, flavour, case_sensitive) - - def _select_from(self, parent_path, scandir): - path = parent_path._make_child_relpath('..') - for p in self.successor._select_from(path, scandir): - yield p - - -class _WildcardSelector(_Selector): - - def __init__(self, pat, child_parts, flavour, case_sensitive): - _Selector.__init__(self, child_parts, flavour, case_sensitive) - if case_sensitive is None: - # TODO: evaluate case-sensitivity of each directory in _select_from() - case_sensitive = _is_case_sensitive(flavour) - self.match = _compile_pattern(pat, case_sensitive) - - def _select_from(self, parent_path, scandir): - try: - # We must close the scandir() object before proceeding to - # avoid exhausting file descriptors when globbing deep trees. - with scandir(parent_path) as scandir_it: - entries = list(scandir_it) - except OSError: - pass - else: - for entry in entries: - if self.dironly: - try: - if not entry.is_dir(): - continue - except OSError: - continue - name = entry.name - if self.match(name): - path = parent_path._make_child_relpath(name) - for p in self.successor._select_from(path, scandir): - yield p - - -class _RecursiveWildcardSelector(_Selector): - - def __init__(self, pat, child_parts, flavour, case_sensitive): - _Selector.__init__(self, child_parts, flavour, case_sensitive) - - def _iterate_directories(self, parent_path): - yield parent_path - for dirpath, dirnames, _ in parent_path.walk(): - for dirname in dirnames: - yield dirpath._make_child_relpath(dirname) - - def _select_from(self, parent_path, scandir): - successor_select = self.successor._select_from - for starting_point in self._iterate_directories(parent_path): - for p in successor_select(starting_point, scandir): - yield p - - -class _DoubleRecursiveWildcardSelector(_RecursiveWildcardSelector): - """ - Like _RecursiveWildcardSelector, but also de-duplicates results from - successive selectors. This is necessary if the pattern contains - multiple non-adjacent '**' segments. - """ - - def _select_from(self, parent_path, scandir): - yielded = set() - try: - for p in super()._select_from(parent_path, scandir): - if p not in yielded: - yield p - yielded.add(p) - finally: - yielded.clear() - - -# -# Public API -# - -class _PathParents(Sequence): - """This object provides sequence-like access to the logical ancestors - of a path. Don't try to construct it yourself.""" - __slots__ = ('_path', '_drv', '_root', '_tail') - - def __init__(self, path): - self._path = path - self._drv = path.drive - self._root = path.root - self._tail = path._tail - - def __len__(self): - return len(self._tail) - - def __getitem__(self, idx): - if isinstance(idx, slice): - return tuple(self[i] for i in range(*idx.indices(len(self)))) - - if idx >= len(self) or idx < -len(self): - raise IndexError(idx) - if idx < 0: - idx += len(self) - return self._path._from_parsed_parts(self._drv, self._root, - self._tail[:-idx - 1]) - - def __repr__(self): - return "<{}.parents>".format(type(self._path).__name__) - - -class PurePath(object): - """Base class for manipulating paths without I/O. - - PurePath represents a filesystem path and offers operations which - don't imply any actual filesystem I/O. Depending on your system, - instantiating a PurePath will return either a PurePosixPath or a - PureWindowsPath object. You can also instantiate either of these classes - directly, regardless of your system. - """ - - __slots__ = ( - # The `_raw_paths` slot stores unnormalized string paths. This is set - # in the `__init__()` method. - '_raw_paths', - - # The `_drv`, `_root` and `_tail_cached` slots store parsed and - # normalized parts of the path. They are set when any of the `drive`, - # `root` or `_tail` properties are accessed for the first time. The - # three-part division corresponds to the result of - # `os.path.splitroot()`, except that the tail is further split on path - # separators (i.e. it is a list of strings), and that the root and - # tail are normalized. - '_drv', '_root', '_tail_cached', - - # The `_str` slot stores the string representation of the path, - # computed from the drive, root and tail when `__str__()` is called - # for the first time. It's used to implement `_str_normcase` - '_str', - - # The `_str_normcase_cached` slot stores the string path with - # normalized case. It is set when the `_str_normcase` property is - # accessed for the first time. It's used to implement `__eq__()` - # `__hash__()`, and `_parts_normcase` - '_str_normcase_cached', - - # The `_parts_normcase_cached` slot stores the case-normalized - # string path after splitting on path separators. It's set when the - # `_parts_normcase` property is accessed for the first time. It's used - # to implement comparison methods like `__lt__()`. - '_parts_normcase_cached', - - # The `_lines_cached` slot stores the string path with path separators - # and newlines swapped. This is used to implement `match()`. - '_lines_cached', - - # The `_hash` slot stores the hash of the case-normalized string - # path. It's set when `__hash__()` is called for the first time. - '_hash', - ) - _flavour = os.path - - def __new__(cls, *args, **kwargs): - """Construct a PurePath from one or several strings and or existing - PurePath objects. The strings and path objects are combined so as - to yield a canonicalized path, which is incorporated into the - new PurePath object. - """ - if cls is PurePath: - cls = PureWindowsPath if os.name == 'nt' else PurePosixPath - return object.__new__(cls) - - def __reduce__(self): - # Using the parts tuple helps share interned path parts - # when pickling related paths. - return (self.__class__, self.parts) - - def __init__(self, *args): - paths = [] - for arg in args: - if isinstance(arg, PurePath): - if arg._flavour is ntpath and self._flavour is posixpath: - # GH-103631: Convert separators for backwards compatibility. - paths.extend(path.replace('\\', '/') for path in arg._raw_paths) - else: - paths.extend(arg._raw_paths) - else: - try: - path = os.fspath(arg) - except TypeError: - path = arg - if not isinstance(path, str): - raise TypeError( - "argument should be a str or an os.PathLike " - "object where __fspath__ returns a str, " - f"not {type(path).__name__!r}") - paths.append(path) - self._raw_paths = paths - - def with_segments(self, *pathsegments): - """Construct a new path object from any number of path-like objects. - Subclasses may override this method to customize how new path objects - are created from methods like `iterdir()`. - """ - return type(self)(*pathsegments) - - @classmethod - def _parse_path(cls, path): - if not path: - return '', '', [] - sep = cls._flavour.sep - altsep = cls._flavour.altsep - if altsep: - path = path.replace(altsep, sep) - drv, root, rel = cls._flavour.splitroot(path) - if not root and drv.startswith(sep) and not drv.endswith(sep): - drv_parts = drv.split(sep) - if len(drv_parts) == 4 and drv_parts[2] not in '?.': - # e.g. //server/share - root = sep - elif len(drv_parts) == 6: - # e.g. //?/unc/server/share - root = sep - parsed = [sys.intern(str(x)) for x in rel.split(sep) if x and x != '.'] - return drv, root, parsed - - def _load_parts(self): - paths = self._raw_paths - if len(paths) == 0: - path = '' - elif len(paths) == 1: - path = paths[0] - else: - path = self._flavour.join(*paths) - drv, root, tail = self._parse_path(path) - self._drv = drv - self._root = root - self._tail_cached = tail - - def _from_parsed_parts(self, drv, root, tail): - path_str = self._format_parsed_parts(drv, root, tail) - path = self.with_segments(path_str) - path._str = path_str or '.' - path._drv = drv - path._root = root - path._tail_cached = tail - return path - - @classmethod - def _format_parsed_parts(cls, drv, root, tail): - if drv or root: - return drv + root + cls._flavour.sep.join(tail) - elif tail and cls._flavour.splitdrive(tail[0])[0]: - tail = ['.'] + tail - return cls._flavour.sep.join(tail) - - def __str__(self): - """Return the string representation of the path, suitable for - passing to system calls.""" - try: - return self._str - except AttributeError: - self._str = self._format_parsed_parts(self.drive, self.root, - self._tail) or '.' - return self._str - - def __fspath__(self): - return str(self) - - def as_posix(self): - """Return the string representation of the path with forward (/) - slashes.""" - f = self._flavour - return str(self).replace(f.sep, '/') - - def __bytes__(self): - """Return the bytes representation of the path. This is only - recommended to use under Unix.""" - return os.fsencode(self) - - def __repr__(self): - return "{}({!r})".format(self.__class__.__name__, self.as_posix()) - - def as_uri(self): - """Return the path as a 'file' URI.""" - if not self.is_absolute(): - raise ValueError("relative path can't be expressed as a file URI") - - drive = self.drive - if len(drive) == 2 and drive[1] == ':': - # It's a path on a local drive => 'file:///c:/a/b' - prefix = 'file:///' + drive - path = self.as_posix()[2:] - elif drive: - # It's a path on a network drive => 'file://host/share/a/b' - prefix = 'file:' - path = self.as_posix() - else: - # It's a posix path => 'file:///etc/hosts' - prefix = 'file://' - path = str(self) - return prefix + urlquote_from_bytes(os.fsencode(path)) - - @property - def _str_normcase(self): - # String with normalized case, for hashing and equality checks - try: - return self._str_normcase_cached - except AttributeError: - if _is_case_sensitive(self._flavour): - self._str_normcase_cached = str(self) - else: - self._str_normcase_cached = str(self).lower() - return self._str_normcase_cached - - @property - def _parts_normcase(self): - # Cached parts with normalized case, for comparisons. - try: - return self._parts_normcase_cached - except AttributeError: - self._parts_normcase_cached = self._str_normcase.split(self._flavour.sep) - return self._parts_normcase_cached - - @property - def _lines(self): - # Path with separators and newlines swapped, for pattern matching. - try: - return self._lines_cached - except AttributeError: - path_str = str(self) - if path_str == '.': - self._lines_cached = '' - else: - trans = _SWAP_SEP_AND_NEWLINE[self._flavour.sep] - self._lines_cached = path_str.translate(trans) - return self._lines_cached - - def __eq__(self, other): - if not isinstance(other, PurePath): - return NotImplemented - return self._str_normcase == other._str_normcase and self._flavour is other._flavour - - def __hash__(self): - try: - return self._hash - except AttributeError: - self._hash = hash(self._str_normcase) - return self._hash - - def __lt__(self, other): - if not isinstance(other, PurePath) or self._flavour is not other._flavour: - return NotImplemented - return self._parts_normcase < other._parts_normcase - - def __le__(self, other): - if not isinstance(other, PurePath) or self._flavour is not other._flavour: - return NotImplemented - return self._parts_normcase <= other._parts_normcase - - def __gt__(self, other): - if not isinstance(other, PurePath) or self._flavour is not other._flavour: - return NotImplemented - return self._parts_normcase > other._parts_normcase - - def __ge__(self, other): - if not isinstance(other, PurePath) or self._flavour is not other._flavour: - return NotImplemented - return self._parts_normcase >= other._parts_normcase - - @property - def drive(self): - """The drive prefix (letter or UNC path), if any.""" - try: - return self._drv - except AttributeError: - self._load_parts() - return self._drv - - @property - def root(self): - """The root of the path, if any.""" - try: - return self._root - except AttributeError: - self._load_parts() - return self._root - - @property - def _tail(self): - try: - return self._tail_cached - except AttributeError: - self._load_parts() - return self._tail_cached - - @property - def anchor(self): - """The concatenation of the drive and root, or ''.""" - anchor = self.drive + self.root - return anchor - - @property - def name(self): - """The final path component, if any.""" - tail = self._tail - if not tail: - return '' - return tail[-1] - - @property - def suffix(self): - """ - The final component's last suffix, if any. - - This includes the leading period. For example: '.txt' - """ - name = self.name - i = name.rfind('.') - if 0 < i < len(name) - 1: - return name[i:] - else: - return '' - - @property - def suffixes(self): - """ - A list of the final component's suffixes, if any. - - These include the leading periods. For example: ['.tar', '.gz'] - """ - name = self.name - if name.endswith('.'): - return [] - name = name.lstrip('.') - return ['.' + suffix for suffix in name.split('.')[1:]] - - @property - def stem(self): - """The final path component, minus its last suffix.""" - name = self.name - i = name.rfind('.') - if 0 < i < len(name) - 1: - return name[:i] - else: - return name - - def with_name(self, name): - """Return a new path with the file name changed.""" - if not self.name: - raise ValueError("%r has an empty name" % (self,)) - f = self._flavour - if not name or f.sep in name or (f.altsep and f.altsep in name) or name == '.': - raise ValueError("Invalid name %r" % (name)) - return self._from_parsed_parts(self.drive, self.root, - self._tail[:-1] + [name]) - - def with_stem(self, stem): - """Return a new path with the stem changed.""" - return self.with_name(stem + self.suffix) - - def with_suffix(self, suffix): - """Return a new path with the file suffix changed. If the path - has no suffix, add given suffix. If the given suffix is an empty - string, remove the suffix from the path. - """ - f = self._flavour - if f.sep in suffix or f.altsep and f.altsep in suffix: - raise ValueError("Invalid suffix %r" % (suffix,)) - if suffix and not suffix.startswith('.') or suffix == '.': - raise ValueError("Invalid suffix %r" % (suffix)) - name = self.name - if not name: - raise ValueError("%r has an empty name" % (self,)) - old_suffix = self.suffix - if not old_suffix: - name = name + suffix - else: - name = name[:-len(old_suffix)] + suffix - return self._from_parsed_parts(self.drive, self.root, - self._tail[:-1] + [name]) - - def relative_to(self, other, /, *_deprecated, walk_up=False): - """Return the relative path to another path identified by the passed - arguments. If the operation is not possible (because this is not - related to the other path), raise ValueError. - - The *walk_up* parameter controls whether `..` may be used to resolve - the path. - """ - if _deprecated: - msg = ("support for supplying more than one positional argument " - "to pathlib.PurePath.relative_to() is deprecated and " - "scheduled for removal in Python {remove}") - warnings._deprecated("pathlib.PurePath.relative_to(*args)", msg, - remove=(3, 14)) - other = self.with_segments(other, *_deprecated) - for step, path in enumerate([other] + list(other.parents)): - if self.is_relative_to(path): - break - elif not walk_up: - raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}") - elif path.name == '..': - raise ValueError(f"'..' segment in {str(other)!r} cannot be walked") - else: - raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors") - parts = ['..'] * step + self._tail[len(path._tail):] - return self.with_segments(*parts) - - def is_relative_to(self, other, /, *_deprecated): - """Return True if the path is relative to another path or False. - """ - if _deprecated: - msg = ("support for supplying more than one argument to " - "pathlib.PurePath.is_relative_to() is deprecated and " - "scheduled for removal in Python {remove}") - warnings._deprecated("pathlib.PurePath.is_relative_to(*args)", - msg, remove=(3, 14)) - other = self.with_segments(other, *_deprecated) - return other == self or other in self.parents - - @property - def parts(self): - """An object providing sequence-like access to the - components in the filesystem path.""" - if self.drive or self.root: - return (self.drive + self.root,) + tuple(self._tail) - else: - return tuple(self._tail) - - def joinpath(self, *pathsegments): - """Combine this path with one or several arguments, and return a - new path representing either a subpath (if all arguments are relative - paths) or a totally different path (if one of the arguments is - anchored). - """ - return self.with_segments(self, *pathsegments) - - def __truediv__(self, key): - try: - return self.joinpath(key) - except TypeError: - return NotImplemented - - def __rtruediv__(self, key): - try: - return self.with_segments(key, self) - except TypeError: - return NotImplemented - - @property - def parent(self): - """The logical parent of the path.""" - drv = self.drive - root = self.root - tail = self._tail - if not tail: - return self - return self._from_parsed_parts(drv, root, tail[:-1]) - - @property - def parents(self): - """A sequence of this path's logical parents.""" - # The value of this property should not be cached on the path object, - # as doing so would introduce a reference cycle. - return _PathParents(self) - - def is_absolute(self): - """True if the path is absolute (has both a root and, if applicable, - a drive).""" - if self._flavour is ntpath: - # ntpath.isabs() is defective - see GH-44626. - return bool(self.drive and self.root) - elif self._flavour is posixpath: - # Optimization: work with raw paths on POSIX. - for path in self._raw_paths: - if path.startswith('/'): - return True - return False - else: - return self._flavour.isabs(str(self)) - - def is_reserved(self): - """Return True if the path contains one of the special names reserved - by the system, if any.""" - if self._flavour is posixpath or not self._tail: - return False - - # NOTE: the rules for reserved names seem somewhat complicated - # (e.g. r"..\NUL" is reserved but not r"foo\NUL" if "foo" does not - # exist). We err on the side of caution and return True for paths - # which are not considered reserved by Windows. - if self.drive.startswith('\\\\'): - # UNC paths are never reserved. - return False - name = self._tail[-1].partition('.')[0].partition(':')[0].rstrip(' ') - return name.upper() in _WIN_RESERVED_NAMES - - def match(self, path_pattern, *, case_sensitive=None): - """ - Return True if this path matches the given pattern. - """ - if not isinstance(path_pattern, PurePath): - path_pattern = self.with_segments(path_pattern) - if case_sensitive is None: - case_sensitive = _is_case_sensitive(self._flavour) - pattern = _compile_pattern_lines(path_pattern._lines, case_sensitive) - if path_pattern.drive or path_pattern.root: - return pattern.match(self._lines) is not None - elif path_pattern._tail: - return pattern.search(self._lines) is not None - else: - raise ValueError("empty pattern") - - -# Can't subclass os.PathLike from PurePath and keep the constructor -# optimizations in PurePath.__slots__. -os.PathLike.register(PurePath) - - -class PurePosixPath(PurePath): - """PurePath subclass for non-Windows systems. - - On a POSIX system, instantiating a PurePath should return this object. - However, you can also instantiate it directly on any system. - """ - _flavour = posixpath - __slots__ = () - - -class PureWindowsPath(PurePath): - """PurePath subclass for Windows systems. - - On a Windows system, instantiating a PurePath should return this object. - However, you can also instantiate it directly on any system. - """ - _flavour = ntpath - __slots__ = () - - -# Filesystem-accessing classes - - -class Path(PurePath): - """PurePath subclass that can make system calls. - - Path represents a filesystem path but unlike PurePath, also offers - methods to do system calls on path objects. Depending on your system, - instantiating a Path will return either a PosixPath or a WindowsPath - object. You can also instantiate a PosixPath or WindowsPath directly, - but cannot instantiate a WindowsPath on a POSIX system or vice versa. - """ - __slots__ = () - - def stat(self, *, follow_symlinks=True): - """ - Return the result of the stat() system call on this path, like - os.stat() does. - """ - return os.stat(self, follow_symlinks=follow_symlinks) - - def lstat(self): - """ - Like stat(), except if the path points to a symlink, the symlink's - status information is returned, rather than its target's. - """ - return self.stat(follow_symlinks=False) - - - # Convenience functions for querying the stat results - - def exists(self, *, follow_symlinks=True): - """ - Whether this path exists. - - This method normally follows symlinks; to check whether a symlink exists, - add the argument follow_symlinks=False. - """ - try: - self.stat(follow_symlinks=follow_symlinks) - except OSError as e: - if not _ignore_error(e): - raise - return False - except ValueError: - # Non-encodable path - return False - return True - - def is_dir(self): - """ - Whether this path is a directory. - """ - try: - return S_ISDIR(self.stat().st_mode) - except OSError as e: - if not _ignore_error(e): - raise - # Path doesn't exist or is a broken symlink - # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) - return False - except ValueError: - # Non-encodable path - return False - - def is_file(self): - """ - Whether this path is a regular file (also True for symlinks pointing - to regular files). - """ - try: - return S_ISREG(self.stat().st_mode) - except OSError as e: - if not _ignore_error(e): - raise - # Path doesn't exist or is a broken symlink - # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) - return False - except ValueError: - # Non-encodable path - return False - - def is_mount(self): - """ - Check if this path is a mount point - """ - return self._flavour.ismount(self) - - def is_symlink(self): - """ - Whether this path is a symbolic link. - """ - try: - return S_ISLNK(self.lstat().st_mode) - except OSError as e: - if not _ignore_error(e): - raise - # Path doesn't exist - return False - except ValueError: - # Non-encodable path - return False - - def is_junction(self): - """ - Whether this path is a junction. - """ - return self._flavour.isjunction(self) - - def is_block_device(self): - """ - Whether this path is a block device. - """ - try: - return S_ISBLK(self.stat().st_mode) - except OSError as e: - if not _ignore_error(e): - raise - # Path doesn't exist or is a broken symlink - # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) - return False - except ValueError: - # Non-encodable path - return False - - def is_char_device(self): - """ - Whether this path is a character device. - """ - try: - return S_ISCHR(self.stat().st_mode) - except OSError as e: - if not _ignore_error(e): - raise - # Path doesn't exist or is a broken symlink - # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) - return False - except ValueError: - # Non-encodable path - return False - - def is_fifo(self): - """ - Whether this path is a FIFO. - """ - try: - return S_ISFIFO(self.stat().st_mode) - except OSError as e: - if not _ignore_error(e): - raise - # Path doesn't exist or is a broken symlink - # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) - return False - except ValueError: - # Non-encodable path - return False - - def is_socket(self): - """ - Whether this path is a socket. - """ - try: - return S_ISSOCK(self.stat().st_mode) - except OSError as e: - if not _ignore_error(e): - raise - # Path doesn't exist or is a broken symlink - # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) - return False - except ValueError: - # Non-encodable path - return False - - def samefile(self, other_path): - """Return whether other_path is the same or not as this file - (as returned by os.path.samefile()). - """ - st = self.stat() - try: - other_st = other_path.stat() - except AttributeError: - other_st = self.with_segments(other_path).stat() - return self._flavour.samestat(st, other_st) - - def open(self, mode='r', buffering=-1, encoding=None, - errors=None, newline=None): - """ - Open the file pointed by this path and return a file object, as - the built-in open() function does. - """ - if "b" not in mode: - encoding = io.text_encoding(encoding) - return io.open(self, mode, buffering, encoding, errors, newline) - - def read_bytes(self): - """ - Open the file in bytes mode, read it, and close the file. - """ - with self.open(mode='rb') as f: - return f.read() - - def read_text(self, encoding=None, errors=None): - """ - Open the file in text mode, read it, and close the file. - """ - encoding = io.text_encoding(encoding) - with self.open(mode='r', encoding=encoding, errors=errors) as f: - return f.read() - - def write_bytes(self, data): - """ - Open the file in bytes mode, write to it, and close the file. - """ - # type-check for the buffer interface before truncating the file - view = memoryview(data) - with self.open(mode='wb') as f: - return f.write(view) - - def write_text(self, data, encoding=None, errors=None, newline=None): - """ - Open the file in text mode, write to it, and close the file. - """ - if not isinstance(data, str): - raise TypeError('data must be str, not %s' % - data.__class__.__name__) - encoding = io.text_encoding(encoding) - with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f: - return f.write(data) - - def iterdir(self): - """Yield path objects of the directory contents. - - The children are yielded in arbitrary order, and the - special entries '.' and '..' are not included. - """ - for name in os.listdir(self): - yield self._make_child_relpath(name) - - def _scandir(self): - # bpo-24132: a future version of pathlib will support subclassing of - # pathlib.Path to customize how the filesystem is accessed. This - # includes scandir(), which is used to implement glob(). - return os.scandir(self) - - def _make_child_relpath(self, name): - path_str = str(self) - tail = self._tail - if tail: - path_str = f'{path_str}{self._flavour.sep}{name}' - elif path_str != '.': - path_str = f'{path_str}{name}' - else: - path_str = name - path = self.with_segments(path_str) - path._str = path_str - path._drv = self.drive - path._root = self.root - path._tail_cached = tail + [name] - return path - - def glob(self, pattern, *, case_sensitive=None): - """Iterate over this subtree and yield all existing files (of any - kind, including directories) matching the given relative pattern. - """ - sys.audit("pathlib.Path.glob", self, pattern) - if not pattern: - raise ValueError("Unacceptable pattern: {!r}".format(pattern)) - drv, root, pattern_parts = self._parse_path(pattern) - if drv or root: - raise NotImplementedError("Non-relative patterns are unsupported") - if pattern[-1] in (self._flavour.sep, self._flavour.altsep): - pattern_parts.append('') - selector = _make_selector(tuple(pattern_parts), self._flavour, case_sensitive) - for p in selector.select_from(self): - yield p - - def rglob(self, pattern, *, case_sensitive=None): - """Recursively yield all existing files (of any kind, including - directories) matching the given relative pattern, anywhere in - this subtree. - """ - sys.audit("pathlib.Path.rglob", self, pattern) - drv, root, pattern_parts = self._parse_path(pattern) - if drv or root: - raise NotImplementedError("Non-relative patterns are unsupported") - if pattern and pattern[-1] in (self._flavour.sep, self._flavour.altsep): - pattern_parts.append('') - selector = _make_selector(("**",) + tuple(pattern_parts), self._flavour, case_sensitive) - for p in selector.select_from(self): - yield p - - def walk(self, top_down=True, on_error=None, follow_symlinks=False): - """Walk the directory tree from this directory, similar to os.walk().""" - sys.audit("pathlib.Path.walk", self, on_error, follow_symlinks) - paths = [self] - - while paths: - path = paths.pop() - if isinstance(path, tuple): - yield path - continue - - # We may not have read permission for self, in which case we can't - # get a list of the files the directory contains. os.walk() - # always suppressed the exception in that instance, rather than - # blow up for a minor reason when (say) a thousand readable - # directories are still left to visit. That logic is copied here. - try: - scandir_it = path._scandir() - except OSError as error: - if on_error is not None: - on_error(error) - continue - - with scandir_it: - dirnames = [] - filenames = [] - for entry in scandir_it: - try: - is_dir = entry.is_dir(follow_symlinks=follow_symlinks) - except OSError: - # Carried over from os.path.isdir(). - is_dir = False - - if is_dir: - dirnames.append(entry.name) - else: - filenames.append(entry.name) - - if top_down: - yield path, dirnames, filenames - else: - paths.append((path, dirnames, filenames)) - - paths += [path._make_child_relpath(d) for d in reversed(dirnames)] - - def __init__(self, *args, **kwargs): - if kwargs: - msg = ("support for supplying keyword arguments to pathlib.PurePath " - "is deprecated and scheduled for removal in Python {remove}") - warnings._deprecated("pathlib.PurePath(**kwargs)", msg, remove=(3, 14)) - super().__init__(*args) - - def __new__(cls, *args, **kwargs): - if cls is Path: - cls = WindowsPath if os.name == 'nt' else PosixPath - return object.__new__(cls) - - def __enter__(self): - # In previous versions of pathlib, __exit__() marked this path as - # closed; subsequent attempts to perform I/O would raise an IOError. - # This functionality was never documented, and had the effect of - # making Path objects mutable, contrary to PEP 428. - # In Python 3.9 __exit__() was made a no-op. - # In Python 3.11 __enter__() began emitting DeprecationWarning. - # In Python 3.13 __enter__() and __exit__() should be removed. - warnings.warn("pathlib.Path.__enter__() is deprecated and scheduled " - "for removal in Python 3.13; Path objects as a context " - "manager is a no-op", - DeprecationWarning, stacklevel=2) - return self - - def __exit__(self, t, v, tb): - pass - - # Public API - - @classmethod - def cwd(cls): - """Return a new path pointing to the current working directory.""" - # We call 'absolute()' rather than using 'os.getcwd()' directly to - # enable users to replace the implementation of 'absolute()' in a - # subclass and benefit from the new behaviour here. This works because - # os.path.abspath('.') == os.getcwd(). - return cls().absolute() - - @classmethod - def home(cls): - """Return a new path pointing to the user's home directory (as - returned by os.path.expanduser('~')). - """ - return cls("~").expanduser() - - def absolute(self): - """Return an absolute version of this path by prepending the current - working directory. No normalization or symlink resolution is performed. - - Use resolve() to get the canonical path to a file. - """ - if self.is_absolute(): - return self - elif self.drive: - # There is a CWD on each drive-letter drive. - cwd = self._flavour.abspath(self.drive) - else: - cwd = os.getcwd() - # Fast path for "empty" paths, e.g. Path("."), Path("") or Path(). - # We pass only one argument to with_segments() to avoid the cost - # of joining, and we exploit the fact that getcwd() returns a - # fully-normalized string by storing it in _str. This is used to - # implement Path.cwd(). - if not self.root and not self._tail: - result = self.with_segments(cwd) - result._str = cwd - return result - return self.with_segments(cwd, self) - - def resolve(self, strict=False): - """ - Make the path absolute, resolving all symlinks on the way and also - normalizing it. - """ - - def check_eloop(e): - winerror = getattr(e, 'winerror', 0) - if e.errno == ELOOP or winerror == _WINERROR_CANT_RESOLVE_FILENAME: - raise RuntimeError("Symlink loop from %r" % e.filename) - - try: - s = self._flavour.realpath(self, strict=strict) - except OSError as e: - check_eloop(e) - raise - p = self.with_segments(s) - - # In non-strict mode, realpath() doesn't raise on symlink loops. - # Ensure we get an exception by calling stat() - if not strict: - try: - p.stat() - except OSError as e: - check_eloop(e) - return p - - def owner(self): - """ - Return the login name of the file owner. - """ - try: - import pwd - return pwd.getpwuid(self.stat().st_uid).pw_name - except ImportError: - raise NotImplementedError("Path.owner() is unsupported on this system") - - def group(self): - """ - Return the group name of the file gid. - """ - - try: - import grp - return grp.getgrgid(self.stat().st_gid).gr_name - except ImportError: - raise NotImplementedError("Path.group() is unsupported on this system") - - def readlink(self): - """ - Return the path to which the symbolic link points. - """ - if not hasattr(os, "readlink"): - raise NotImplementedError("os.readlink() not available on this system") - return self.with_segments(os.readlink(self)) - - def touch(self, mode=0o666, exist_ok=True): - """ - Create this file with the given access mode, if it doesn't exist. - """ - - if exist_ok: - # First try to bump modification time - # Implementation note: GNU touch uses the UTIME_NOW option of - # the utimensat() / futimens() functions. - try: - os.utime(self, None) - except OSError: - # Avoid exception chaining - pass - else: - return - flags = os.O_CREAT | os.O_WRONLY - if not exist_ok: - flags |= os.O_EXCL - fd = os.open(self, flags, mode) - os.close(fd) - - def mkdir(self, mode=0o777, parents=False, exist_ok=False): - """ - Create a new directory at this given path. - """ - try: - os.mkdir(self, mode) - except FileNotFoundError: - if not parents or self.parent == self: - raise - self.parent.mkdir(parents=True, exist_ok=True) - self.mkdir(mode, parents=False, exist_ok=exist_ok) - except OSError: - # Cannot rely on checking for EEXIST, since the operating system - # could give priority to other errors like EACCES or EROFS - if not exist_ok or not self.is_dir(): - raise - - def chmod(self, mode, *, follow_symlinks=True): - """ - Change the permissions of the path, like os.chmod(). - """ - os.chmod(self, mode, follow_symlinks=follow_symlinks) - - def lchmod(self, mode): - """ - Like chmod(), except if the path points to a symlink, the symlink's - permissions are changed, rather than its target's. - """ - self.chmod(mode, follow_symlinks=False) - - def unlink(self, missing_ok=False): - """ - Remove this file or link. - If the path is a directory, use rmdir() instead. - """ - try: - os.unlink(self) - except FileNotFoundError: - if not missing_ok: - raise - - def rmdir(self): - """ - Remove this directory. The directory must be empty. - """ - os.rmdir(self) - - def rename(self, target): - """ - Rename this path to the target path. - - The target path may be absolute or relative. Relative paths are - interpreted relative to the current working directory, *not* the - directory of the Path object. - - Returns the new Path instance pointing to the target path. - """ - os.rename(self, target) - return self.with_segments(target) - - def replace(self, target): - """ - Rename this path to the target path, overwriting if that path exists. - - The target path may be absolute or relative. Relative paths are - interpreted relative to the current working directory, *not* the - directory of the Path object. - - Returns the new Path instance pointing to the target path. - """ - os.replace(self, target) - return self.with_segments(target) - - def symlink_to(self, target, target_is_directory=False): - """ - Make this path a symlink pointing to the target path. - Note the order of arguments (link, target) is the reverse of os.symlink. - """ - if not hasattr(os, "symlink"): - raise NotImplementedError("os.symlink() not available on this system") - os.symlink(target, self, target_is_directory) - - def hardlink_to(self, target): - """ - Make this path a hard link pointing to the same file as *target*. - - Note the order of arguments (self, target) is the reverse of os.link's. - """ - if not hasattr(os, "link"): - raise NotImplementedError("os.link() not available on this system") - os.link(target, self) - - def expanduser(self): - """ Return a new path with expanded ~ and ~user constructs - (as returned by os.path.expanduser) - """ - if (not (self.drive or self.root) and - self._tail and self._tail[0][:1] == '~'): - homedir = self._flavour.expanduser(self._tail[0]) - if homedir[:1] == "~": - raise RuntimeError("Could not determine home directory.") - drv, root, tail = self._parse_path(homedir) - return self._from_parsed_parts(drv, root, tail + self._tail[1:]) - - return self - - -class PosixPath(Path, PurePosixPath): - """Path subclass for non-Windows systems. - - On a POSIX system, instantiating a Path should return this object. - """ - __slots__ = () - - if os.name == 'nt': - def __new__(cls, *args, **kwargs): - raise NotImplementedError( - f"cannot instantiate {cls.__name__!r} on your system") - -class WindowsPath(Path, PureWindowsPath): - """Path subclass for Windows systems. - - On a Windows system, instantiating a Path should return this object. - """ - __slots__ = () - - if os.name != 'nt': - def __new__(cls, *args, **kwargs): - raise NotImplementedError( - f"cannot instantiate {cls.__name__!r} on your system") diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py new file mode 100644 index 00000000000..4b3edf535a6 --- /dev/null +++ b/Lib/pathlib/__init__.py @@ -0,0 +1,12 @@ +"""Object-oriented filesystem paths. + +This module provides classes to represent abstract paths and concrete +paths with operations that have semantics appropriate for different +operating systems. +""" + +from ._abc import * +from ._local import * + +__all__ = (_abc.__all__ + + _local.__all__) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py new file mode 100644 index 00000000000..4d24146aa53 --- /dev/null +++ b/Lib/pathlib/_abc.py @@ -0,0 +1,930 @@ +""" +Abstract base classes for rich path objects. + +This module is published as a PyPI package called "pathlib-abc". + +This module is also a *PRIVATE* part of the Python standard library, where +it's developed alongside pathlib. If it finds success and maturity as a PyPI +package, it could become a public part of the standard library. + +Two base classes are defined here -- PurePathBase and PathBase -- that +resemble pathlib's PurePath and Path respectively. +""" + +import functools +from glob import _Globber, _no_recurse_symlinks +from errno import ENOENT, ENOTDIR, EBADF, ELOOP, EINVAL +from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO + + +__all__ = ["UnsupportedOperation"] + +# +# Internals +# + +_WINERROR_NOT_READY = 21 # drive exists but is not accessible +_WINERROR_INVALID_NAME = 123 # fix for bpo-35306 +_WINERROR_CANT_RESOLVE_FILENAME = 1921 # broken symlink pointing to itself + +# EBADF - guard against macOS `stat` throwing EBADF +_IGNORED_ERRNOS = (ENOENT, ENOTDIR, EBADF, ELOOP) + +_IGNORED_WINERRORS = ( + _WINERROR_NOT_READY, + _WINERROR_INVALID_NAME, + _WINERROR_CANT_RESOLVE_FILENAME) + +def _ignore_error(exception): + return (getattr(exception, 'errno', None) in _IGNORED_ERRNOS or + getattr(exception, 'winerror', None) in _IGNORED_WINERRORS) + + +@functools.cache +def _is_case_sensitive(parser): + return parser.normcase('Aa') == 'Aa' + + +class UnsupportedOperation(NotImplementedError): + """An exception that is raised when an unsupported operation is called on + a path object. + """ + pass + + +class ParserBase: + """Base class for path parsers, which do low-level path manipulation. + + Path parsers provide a subset of the os.path API, specifically those + functions needed to provide PurePathBase functionality. Each PurePathBase + subclass references its path parser via a 'parser' class attribute. + + Every method in this base class raises an UnsupportedOperation exception. + """ + + @classmethod + def _unsupported_msg(cls, attribute): + return f"{cls.__name__}.{attribute} is unsupported" + + @property + def sep(self): + """The character used to separate path components.""" + raise UnsupportedOperation(self._unsupported_msg('sep')) + + def join(self, path, *paths): + """Join path segments.""" + raise UnsupportedOperation(self._unsupported_msg('join()')) + + def split(self, path): + """Split the path into a pair (head, tail), where *head* is everything + before the final path separator, and *tail* is everything after. + Either part may be empty. + """ + raise UnsupportedOperation(self._unsupported_msg('split()')) + + def splitdrive(self, path): + """Split the path into a 2-item tuple (drive, tail), where *drive* is + a device name or mount point, and *tail* is everything after the + drive. Either part may be empty.""" + raise UnsupportedOperation(self._unsupported_msg('splitdrive()')) + + def normcase(self, path): + """Normalize the case of the path.""" + raise UnsupportedOperation(self._unsupported_msg('normcase()')) + + def isabs(self, path): + """Returns whether the path is absolute, i.e. unaffected by the + current directory or drive.""" + raise UnsupportedOperation(self._unsupported_msg('isabs()')) + + +class PurePathBase: + """Base class for pure path objects. + + This class *does not* provide several magic methods that are defined in + its subclass PurePath. They are: __fspath__, __bytes__, __reduce__, + __hash__, __eq__, __lt__, __le__, __gt__, __ge__. Its initializer and path + joining methods accept only strings, not os.PathLike objects more broadly. + """ + + __slots__ = ( + # The `_raw_path` slot store a joined string path. This is set in the + # `__init__()` method. + '_raw_path', + + # The '_resolving' slot stores a boolean indicating whether the path + # is being processed by `PathBase.resolve()`. This prevents duplicate + # work from occurring when `resolve()` calls `stat()` or `readlink()`. + '_resolving', + ) + parser = ParserBase() + _globber = _Globber + + def __init__(self, path, *paths): + self._raw_path = self.parser.join(path, *paths) if paths else path + if not isinstance(self._raw_path, str): + raise TypeError( + f"path should be a str, not {type(self._raw_path).__name__!r}") + self._resolving = False + + def with_segments(self, *pathsegments): + """Construct a new path object from any number of path-like objects. + Subclasses may override this method to customize how new path objects + are created from methods like `iterdir()`. + """ + return type(self)(*pathsegments) + + def __str__(self): + """Return the string representation of the path, suitable for + passing to system calls.""" + return self._raw_path + + def as_posix(self): + """Return the string representation of the path with forward (/) + slashes.""" + return str(self).replace(self.parser.sep, '/') + + @property + def drive(self): + """The drive prefix (letter or UNC path), if any.""" + return self.parser.splitdrive(self.anchor)[0] + + @property + def root(self): + """The root of the path, if any.""" + return self.parser.splitdrive(self.anchor)[1] + + @property + def anchor(self): + """The concatenation of the drive and root, or ''.""" + return self._stack[0] + + @property + def name(self): + """The final path component, if any.""" + return self.parser.split(self._raw_path)[1] + + @property + def suffix(self): + """ + The final component's last suffix, if any. + + This includes the leading period. For example: '.txt' + """ + name = self.name + i = name.rfind('.') + if 0 < i < len(name) - 1: + return name[i:] + else: + return '' + + @property + def suffixes(self): + """ + A list of the final component's suffixes, if any. + + These include the leading periods. For example: ['.tar', '.gz'] + """ + name = self.name + if name.endswith('.'): + return [] + name = name.lstrip('.') + return ['.' + suffix for suffix in name.split('.')[1:]] + + @property + def stem(self): + """The final path component, minus its last suffix.""" + name = self.name + i = name.rfind('.') + if 0 < i < len(name) - 1: + return name[:i] + else: + return name + + def with_name(self, name): + """Return a new path with the file name changed.""" + split = self.parser.split + if split(name)[0]: + raise ValueError(f"Invalid name {name!r}") + return self.with_segments(split(self._raw_path)[0], name) + + def with_stem(self, stem): + """Return a new path with the stem changed.""" + suffix = self.suffix + if not suffix: + return self.with_name(stem) + elif not stem: + # If the suffix is non-empty, we can't make the stem empty. + raise ValueError(f"{self!r} has a non-empty suffix") + else: + return self.with_name(stem + suffix) + + def with_suffix(self, suffix): + """Return a new path with the file suffix changed. If the path + has no suffix, add given suffix. If the given suffix is an empty + string, remove the suffix from the path. + """ + stem = self.stem + if not stem: + # If the stem is empty, we can't make the suffix non-empty. + raise ValueError(f"{self!r} has an empty name") + elif suffix and not (suffix.startswith('.') and len(suffix) > 1): + raise ValueError(f"Invalid suffix {suffix!r}") + else: + return self.with_name(stem + suffix) + + def relative_to(self, other, *, walk_up=False): + """Return the relative path to another path identified by the passed + arguments. If the operation is not possible (because this is not + related to the other path), raise ValueError. + + The *walk_up* parameter controls whether `..` may be used to resolve + the path. + """ + if not isinstance(other, PurePathBase): + other = self.with_segments(other) + anchor0, parts0 = self._stack + anchor1, parts1 = other._stack + if anchor0 != anchor1: + raise ValueError(f"{self._raw_path!r} and {other._raw_path!r} have different anchors") + while parts0 and parts1 and parts0[-1] == parts1[-1]: + parts0.pop() + parts1.pop() + for part in parts1: + if not part or part == '.': + pass + elif not walk_up: + raise ValueError(f"{self._raw_path!r} is not in the subpath of {other._raw_path!r}") + elif part == '..': + raise ValueError(f"'..' segment in {other._raw_path!r} cannot be walked") + else: + parts0.append('..') + return self.with_segments('', *reversed(parts0)) + + def is_relative_to(self, other): + """Return True if the path is relative to another path or False. + """ + if not isinstance(other, PurePathBase): + other = self.with_segments(other) + anchor0, parts0 = self._stack + anchor1, parts1 = other._stack + if anchor0 != anchor1: + return False + while parts0 and parts1 and parts0[-1] == parts1[-1]: + parts0.pop() + parts1.pop() + for part in parts1: + if part and part != '.': + return False + return True + + @property + def parts(self): + """An object providing sequence-like access to the + components in the filesystem path.""" + anchor, parts = self._stack + if anchor: + parts.append(anchor) + return tuple(reversed(parts)) + + def joinpath(self, *pathsegments): + """Combine this path with one or several arguments, and return a + new path representing either a subpath (if all arguments are relative + paths) or a totally different path (if one of the arguments is + anchored). + """ + return self.with_segments(self._raw_path, *pathsegments) + + def __truediv__(self, key): + try: + return self.with_segments(self._raw_path, key) + except TypeError: + return NotImplemented + + def __rtruediv__(self, key): + try: + return self.with_segments(key, self._raw_path) + except TypeError: + return NotImplemented + + @property + def _stack(self): + """ + Split the path into a 2-tuple (anchor, parts), where *anchor* is the + uppermost parent of the path (equivalent to path.parents[-1]), and + *parts* is a reversed list of parts following the anchor. + """ + split = self.parser.split + path = self._raw_path + parent, name = split(path) + names = [] + while path != parent: + names.append(name) + path = parent + parent, name = split(path) + return path, names + + @property + def parent(self): + """The logical parent of the path.""" + path = self._raw_path + parent = self.parser.split(path)[0] + if path != parent: + parent = self.with_segments(parent) + parent._resolving = self._resolving + return parent + return self + + @property + def parents(self): + """A sequence of this path's logical parents.""" + split = self.parser.split + path = self._raw_path + parent = split(path)[0] + parents = [] + while path != parent: + parents.append(self.with_segments(parent)) + path = parent + parent = split(path)[0] + return tuple(parents) + + def is_absolute(self): + """True if the path is absolute (has both a root and, if applicable, + a drive).""" + return self.parser.isabs(self._raw_path) + + @property + def _pattern_str(self): + """The path expressed as a string, for use in pattern-matching.""" + return str(self) + + def match(self, path_pattern, *, case_sensitive=None): + """ + Return True if this path matches the given pattern. If the pattern is + relative, matching is done from the right; otherwise, the entire path + is matched. The recursive wildcard '**' is *not* supported by this + method. + """ + if not isinstance(path_pattern, PurePathBase): + path_pattern = self.with_segments(path_pattern) + if case_sensitive is None: + case_sensitive = _is_case_sensitive(self.parser) + sep = path_pattern.parser.sep + path_parts = self.parts[::-1] + pattern_parts = path_pattern.parts[::-1] + if not pattern_parts: + raise ValueError("empty pattern") + if len(path_parts) < len(pattern_parts): + return False + if len(path_parts) > len(pattern_parts) and path_pattern.anchor: + return False + globber = self._globber(sep, case_sensitive) + for path_part, pattern_part in zip(path_parts, pattern_parts): + match = globber.compile(pattern_part) + if match(path_part) is None: + return False + return True + + def full_match(self, pattern, *, case_sensitive=None): + """ + Return True if this path matches the given glob-style pattern. The + pattern is matched against the entire path. + """ + if not isinstance(pattern, PurePathBase): + pattern = self.with_segments(pattern) + if case_sensitive is None: + case_sensitive = _is_case_sensitive(self.parser) + globber = self._globber(pattern.parser.sep, case_sensitive, recursive=True) + match = globber.compile(pattern._pattern_str) + return match(self._pattern_str) is not None + + + +class PathBase(PurePathBase): + """Base class for concrete path objects. + + This class provides dummy implementations for many methods that derived + classes can override selectively; the default implementations raise + UnsupportedOperation. The most basic methods, such as stat() and open(), + directly raise UnsupportedOperation; these basic methods are called by + other methods such as is_dir() and read_text(). + + The Path class derives this class to implement local filesystem paths. + Users may derive their own classes to implement virtual filesystem paths, + such as paths in archive files or on remote storage systems. + """ + __slots__ = () + + # Maximum number of symlinks to follow in resolve() + _max_symlinks = 40 + + @classmethod + def _unsupported_msg(cls, attribute): + return f"{cls.__name__}.{attribute} is unsupported" + + def stat(self, *, follow_symlinks=True): + """ + Return the result of the stat() system call on this path, like + os.stat() does. + """ + raise UnsupportedOperation(self._unsupported_msg('stat()')) + + def lstat(self): + """ + Like stat(), except if the path points to a symlink, the symlink's + status information is returned, rather than its target's. + """ + return self.stat(follow_symlinks=False) + + + # Convenience functions for querying the stat results + + def exists(self, *, follow_symlinks=True): + """ + Whether this path exists. + + This method normally follows symlinks; to check whether a symlink exists, + add the argument follow_symlinks=False. + """ + try: + self.stat(follow_symlinks=follow_symlinks) + except OSError as e: + if not _ignore_error(e): + raise + return False + except ValueError: + # Non-encodable path + return False + return True + + def is_dir(self, *, follow_symlinks=True): + """ + Whether this path is a directory. + """ + try: + return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode) + except OSError as e: + if not _ignore_error(e): + raise + # Path doesn't exist or is a broken symlink + # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) + return False + except ValueError: + # Non-encodable path + return False + + def is_file(self, *, follow_symlinks=True): + """ + Whether this path is a regular file (also True for symlinks pointing + to regular files). + """ + try: + return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode) + except OSError as e: + if not _ignore_error(e): + raise + # Path doesn't exist or is a broken symlink + # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) + return False + except ValueError: + # Non-encodable path + return False + + def is_mount(self): + """ + Check if this path is a mount point + """ + # Need to exist and be a dir + if not self.exists() or not self.is_dir(): + return False + + try: + parent_dev = self.parent.stat().st_dev + except OSError: + return False + + dev = self.stat().st_dev + if dev != parent_dev: + return True + ino = self.stat().st_ino + parent_ino = self.parent.stat().st_ino + return ino == parent_ino + + def is_symlink(self): + """ + Whether this path is a symbolic link. + """ + try: + return S_ISLNK(self.lstat().st_mode) + except OSError as e: + if not _ignore_error(e): + raise + # Path doesn't exist + return False + except ValueError: + # Non-encodable path + return False + + def is_junction(self): + """ + Whether this path is a junction. + """ + # Junctions are a Windows-only feature, not present in POSIX nor the + # majority of virtual filesystems. There is no cross-platform idiom + # to check for junctions (using stat().st_mode). + return False + + def is_block_device(self): + """ + Whether this path is a block device. + """ + try: + return S_ISBLK(self.stat().st_mode) + except OSError as e: + if not _ignore_error(e): + raise + # Path doesn't exist or is a broken symlink + # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) + return False + except ValueError: + # Non-encodable path + return False + + def is_char_device(self): + """ + Whether this path is a character device. + """ + try: + return S_ISCHR(self.stat().st_mode) + except OSError as e: + if not _ignore_error(e): + raise + # Path doesn't exist or is a broken symlink + # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) + return False + except ValueError: + # Non-encodable path + return False + + def is_fifo(self): + """ + Whether this path is a FIFO. + """ + try: + return S_ISFIFO(self.stat().st_mode) + except OSError as e: + if not _ignore_error(e): + raise + # Path doesn't exist or is a broken symlink + # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) + return False + except ValueError: + # Non-encodable path + return False + + def is_socket(self): + """ + Whether this path is a socket. + """ + try: + return S_ISSOCK(self.stat().st_mode) + except OSError as e: + if not _ignore_error(e): + raise + # Path doesn't exist or is a broken symlink + # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) + return False + except ValueError: + # Non-encodable path + return False + + def samefile(self, other_path): + """Return whether other_path is the same or not as this file + (as returned by os.path.samefile()). + """ + st = self.stat() + try: + other_st = other_path.stat() + except AttributeError: + other_st = self.with_segments(other_path).stat() + return (st.st_ino == other_st.st_ino and + st.st_dev == other_st.st_dev) + + def open(self, mode='r', buffering=-1, encoding=None, + errors=None, newline=None): + """ + Open the file pointed to by this path and return a file object, as + the built-in open() function does. + """ + raise UnsupportedOperation(self._unsupported_msg('open()')) + + def read_bytes(self): + """ + Open the file in bytes mode, read it, and close the file. + """ + with self.open(mode='rb') as f: + return f.read() + + def read_text(self, encoding=None, errors=None, newline=None): + """ + Open the file in text mode, read it, and close the file. + """ + with self.open(mode='r', encoding=encoding, errors=errors, newline=newline) as f: + return f.read() + + def write_bytes(self, data): + """ + Open the file in bytes mode, write to it, and close the file. + """ + # type-check for the buffer interface before truncating the file + view = memoryview(data) + with self.open(mode='wb') as f: + return f.write(view) + + def write_text(self, data, encoding=None, errors=None, newline=None): + """ + Open the file in text mode, write to it, and close the file. + """ + if not isinstance(data, str): + raise TypeError('data must be str, not %s' % + data.__class__.__name__) + with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f: + return f.write(data) + + def iterdir(self): + """Yield path objects of the directory contents. + + The children are yielded in arbitrary order, and the + special entries '.' and '..' are not included. + """ + raise UnsupportedOperation(self._unsupported_msg('iterdir()')) + + def _glob_selector(self, parts, case_sensitive, recurse_symlinks): + if case_sensitive is None: + case_sensitive = _is_case_sensitive(self.parser) + case_pedantic = False + else: + # The user has expressed a case sensitivity choice, but we don't + # know the case sensitivity of the underlying filesystem, so we + # must use scandir() for everything, including non-wildcard parts. + case_pedantic = True + recursive = True if recurse_symlinks else _no_recurse_symlinks + globber = self._globber(self.parser.sep, case_sensitive, case_pedantic, recursive) + return globber.selector(parts) + + def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=True): + """Iterate over this subtree and yield all existing files (of any + kind, including directories) matching the given relative pattern. + """ + if not isinstance(pattern, PurePathBase): + pattern = self.with_segments(pattern) + anchor, parts = pattern._stack + if anchor: + raise NotImplementedError("Non-relative patterns are unsupported") + select = self._glob_selector(parts, case_sensitive, recurse_symlinks) + return select(self) + + def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=True): + """Recursively yield all existing files (of any kind, including + directories) matching the given relative pattern, anywhere in + this subtree. + """ + if not isinstance(pattern, PurePathBase): + pattern = self.with_segments(pattern) + pattern = '**' / pattern + return self.glob(pattern, case_sensitive=case_sensitive, recurse_symlinks=recurse_symlinks) + + def walk(self, top_down=True, on_error=None, follow_symlinks=False): + """Walk the directory tree from this directory, similar to os.walk().""" + paths = [self] + while paths: + path = paths.pop() + if isinstance(path, tuple): + yield path + continue + dirnames = [] + filenames = [] + if not top_down: + paths.append((path, dirnames, filenames)) + try: + for child in path.iterdir(): + try: + if child.is_dir(follow_symlinks=follow_symlinks): + if not top_down: + paths.append(child) + dirnames.append(child.name) + else: + filenames.append(child.name) + except OSError: + filenames.append(child.name) + except OSError as error: + if on_error is not None: + on_error(error) + if not top_down: + while not isinstance(paths.pop(), tuple): + pass + continue + if top_down: + yield path, dirnames, filenames + paths += [path.joinpath(d) for d in reversed(dirnames)] + + def absolute(self): + """Return an absolute version of this path + No normalization or symlink resolution is performed. + + Use resolve() to resolve symlinks and remove '..' segments. + """ + raise UnsupportedOperation(self._unsupported_msg('absolute()')) + + @classmethod + def cwd(cls): + """Return a new path pointing to the current working directory.""" + # We call 'absolute()' rather than using 'os.getcwd()' directly to + # enable users to replace the implementation of 'absolute()' in a + # subclass and benefit from the new behaviour here. This works because + # os.path.abspath('.') == os.getcwd(). + return cls('').absolute() + + def expanduser(self): + """ Return a new path with expanded ~ and ~user constructs + (as returned by os.path.expanduser) + """ + raise UnsupportedOperation(self._unsupported_msg('expanduser()')) + + @classmethod + def home(cls): + """Return a new path pointing to expanduser('~'). + """ + return cls("~").expanduser() + + def readlink(self): + """ + Return the path to which the symbolic link points. + """ + raise UnsupportedOperation(self._unsupported_msg('readlink()')) + readlink._supported = False + + def resolve(self, strict=False): + """ + Make the path absolute, resolving all symlinks on the way and also + normalizing it. + """ + if self._resolving: + return self + path_root, parts = self._stack + path = self.with_segments(path_root) + try: + path = path.absolute() + except UnsupportedOperation: + path_tail = [] + else: + path_root, path_tail = path._stack + path_tail.reverse() + + # If the user has *not* overridden the `readlink()` method, then symlinks are unsupported + # and (in non-strict mode) we can improve performance by not calling `stat()`. + querying = strict or getattr(self.readlink, '_supported', True) + link_count = 0 + while parts: + part = parts.pop() + if not part or part == '.': + continue + if part == '..': + if not path_tail: + if path_root: + # Delete '..' segment immediately following root + continue + elif path_tail[-1] != '..': + # Delete '..' segment and its predecessor + path_tail.pop() + continue + path_tail.append(part) + if querying and part != '..': + path = self.with_segments(path_root + self.parser.sep.join(path_tail)) + path._resolving = True + try: + st = path.stat(follow_symlinks=False) + if S_ISLNK(st.st_mode): + # Like Linux and macOS, raise OSError(errno.ELOOP) if too many symlinks are + # encountered during resolution. + link_count += 1 + if link_count >= self._max_symlinks: + raise OSError(ELOOP, "Too many symbolic links in path", self._raw_path) + target_root, target_parts = path.readlink()._stack + # If the symlink target is absolute (like '/etc/hosts'), set the current + # path to its uppermost parent (like '/'). + if target_root: + path_root = target_root + path_tail.clear() + else: + path_tail.pop() + # Add the symlink target's reversed tail parts (like ['hosts', 'etc']) to + # the stack of unresolved path parts. + parts.extend(target_parts) + continue + elif parts and not S_ISDIR(st.st_mode): + raise NotADirectoryError(ENOTDIR, "Not a directory", self._raw_path) + except OSError: + if strict: + raise + else: + querying = False + return self.with_segments(path_root + self.parser.sep.join(path_tail)) + + def symlink_to(self, target, target_is_directory=False): + """ + Make this path a symlink pointing to the target path. + Note the order of arguments (link, target) is the reverse of os.symlink. + """ + raise UnsupportedOperation(self._unsupported_msg('symlink_to()')) + + def hardlink_to(self, target): + """ + Make this path a hard link pointing to the same file as *target*. + + Note the order of arguments (self, target) is the reverse of os.link's. + """ + raise UnsupportedOperation(self._unsupported_msg('hardlink_to()')) + + def touch(self, mode=0o666, exist_ok=True): + """ + Create this file with the given access mode, if it doesn't exist. + """ + raise UnsupportedOperation(self._unsupported_msg('touch()')) + + def mkdir(self, mode=0o777, parents=False, exist_ok=False): + """ + Create a new directory at this given path. + """ + raise UnsupportedOperation(self._unsupported_msg('mkdir()')) + + def rename(self, target): + """ + Rename this path to the target path. + + The target path may be absolute or relative. Relative paths are + interpreted relative to the current working directory, *not* the + directory of the Path object. + + Returns the new Path instance pointing to the target path. + """ + raise UnsupportedOperation(self._unsupported_msg('rename()')) + + def replace(self, target): + """ + Rename this path to the target path, overwriting if that path exists. + + The target path may be absolute or relative. Relative paths are + interpreted relative to the current working directory, *not* the + directory of the Path object. + + Returns the new Path instance pointing to the target path. + """ + raise UnsupportedOperation(self._unsupported_msg('replace()')) + + def chmod(self, mode, *, follow_symlinks=True): + """ + Change the permissions of the path, like os.chmod(). + """ + raise UnsupportedOperation(self._unsupported_msg('chmod()')) + + def lchmod(self, mode): + """ + Like chmod(), except if the path points to a symlink, the symlink's + permissions are changed, rather than its target's. + """ + self.chmod(mode, follow_symlinks=False) + + def unlink(self, missing_ok=False): + """ + Remove this file or link. + If the path is a directory, use rmdir() instead. + """ + raise UnsupportedOperation(self._unsupported_msg('unlink()')) + + def rmdir(self): + """ + Remove this directory. The directory must be empty. + """ + raise UnsupportedOperation(self._unsupported_msg('rmdir()')) + + def owner(self, *, follow_symlinks=True): + """ + Return the login name of the file owner. + """ + raise UnsupportedOperation(self._unsupported_msg('owner()')) + + def group(self, *, follow_symlinks=True): + """ + Return the group name of the file gid. + """ + raise UnsupportedOperation(self._unsupported_msg('group()')) + + @classmethod + def from_uri(cls, uri): + """Return a new path from the given 'file' URI.""" + raise UnsupportedOperation(cls._unsupported_msg('from_uri()')) + + def as_uri(self): + """Return the path as a URI.""" + raise UnsupportedOperation(self._unsupported_msg('as_uri()')) diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py new file mode 100644 index 00000000000..0188e7c7722 --- /dev/null +++ b/Lib/pathlib/_local.py @@ -0,0 +1,861 @@ +import io +import ntpath +import operator +import os +import posixpath +import sys +import warnings +from glob import _StringGlobber +from itertools import chain +from _collections_abc import Sequence + +try: + import pwd +except ImportError: + pwd = None +try: + import grp +except ImportError: + grp = None + +from ._abc import UnsupportedOperation, PurePathBase, PathBase + + +__all__ = [ + "PurePath", "PurePosixPath", "PureWindowsPath", + "Path", "PosixPath", "WindowsPath", + ] + + +class _PathParents(Sequence): + """This object provides sequence-like access to the logical ancestors + of a path. Don't try to construct it yourself.""" + __slots__ = ('_path', '_drv', '_root', '_tail') + + def __init__(self, path): + self._path = path + self._drv = path.drive + self._root = path.root + self._tail = path._tail + + def __len__(self): + return len(self._tail) + + def __getitem__(self, idx): + if isinstance(idx, slice): + return tuple(self[i] for i in range(*idx.indices(len(self)))) + + if idx >= len(self) or idx < -len(self): + raise IndexError(idx) + if idx < 0: + idx += len(self) + return self._path._from_parsed_parts(self._drv, self._root, + self._tail[:-idx - 1]) + + def __repr__(self): + return "<{}.parents>".format(type(self._path).__name__) + + +class PurePath(PurePathBase): + """Base class for manipulating paths without I/O. + + PurePath represents a filesystem path and offers operations which + don't imply any actual filesystem I/O. Depending on your system, + instantiating a PurePath will return either a PurePosixPath or a + PureWindowsPath object. You can also instantiate either of these classes + directly, regardless of your system. + """ + + __slots__ = ( + # The `_raw_paths` slot stores unnormalized string paths. This is set + # in the `__init__()` method. + '_raw_paths', + + # The `_drv`, `_root` and `_tail_cached` slots store parsed and + # normalized parts of the path. They are set when any of the `drive`, + # `root` or `_tail` properties are accessed for the first time. The + # three-part division corresponds to the result of + # `os.path.splitroot()`, except that the tail is further split on path + # separators (i.e. it is a list of strings), and that the root and + # tail are normalized. + '_drv', '_root', '_tail_cached', + + # The `_str` slot stores the string representation of the path, + # computed from the drive, root and tail when `__str__()` is called + # for the first time. It's used to implement `_str_normcase` + '_str', + + # The `_str_normcase_cached` slot stores the string path with + # normalized case. It is set when the `_str_normcase` property is + # accessed for the first time. It's used to implement `__eq__()` + # `__hash__()`, and `_parts_normcase` + '_str_normcase_cached', + + # The `_parts_normcase_cached` slot stores the case-normalized + # string path after splitting on path separators. It's set when the + # `_parts_normcase` property is accessed for the first time. It's used + # to implement comparison methods like `__lt__()`. + '_parts_normcase_cached', + + # The `_hash` slot stores the hash of the case-normalized string + # path. It's set when `__hash__()` is called for the first time. + '_hash', + ) + parser = os.path + _globber = _StringGlobber + + def __new__(cls, *args, **kwargs): + """Construct a PurePath from one or several strings and or existing + PurePath objects. The strings and path objects are combined so as + to yield a canonicalized path, which is incorporated into the + new PurePath object. + """ + if cls is PurePath: + cls = PureWindowsPath if os.name == 'nt' else PurePosixPath + return object.__new__(cls) + + def __init__(self, *args): + paths = [] + for arg in args: + if isinstance(arg, PurePath): + if arg.parser is not self.parser: + # GH-103631: Convert separators for backwards compatibility. + paths.append(arg.as_posix()) + else: + paths.extend(arg._raw_paths) + else: + try: + path = os.fspath(arg) + except TypeError: + path = arg + if not isinstance(path, str): + raise TypeError( + "argument should be a str or an os.PathLike " + "object where __fspath__ returns a str, " + f"not {type(path).__name__!r}") + paths.append(path) + # Avoid calling super().__init__, as an optimisation + self._raw_paths = paths + + def joinpath(self, *pathsegments): + """Combine this path with one or several arguments, and return a + new path representing either a subpath (if all arguments are relative + paths) or a totally different path (if one of the arguments is + anchored). + """ + return self.with_segments(self, *pathsegments) + + def __truediv__(self, key): + try: + return self.with_segments(self, key) + except TypeError: + return NotImplemented + + def __rtruediv__(self, key): + try: + return self.with_segments(key, self) + except TypeError: + return NotImplemented + + def __reduce__(self): + return self.__class__, tuple(self._raw_paths) + + def __repr__(self): + return "{}({!r})".format(self.__class__.__name__, self.as_posix()) + + def __fspath__(self): + return str(self) + + def __bytes__(self): + """Return the bytes representation of the path. This is only + recommended to use under Unix.""" + return os.fsencode(self) + + @property + def _str_normcase(self): + # String with normalized case, for hashing and equality checks + try: + return self._str_normcase_cached + except AttributeError: + if self.parser is posixpath: + self._str_normcase_cached = str(self) + else: + self._str_normcase_cached = str(self).lower() + return self._str_normcase_cached + + def __hash__(self): + try: + return self._hash + except AttributeError: + self._hash = hash(self._str_normcase) + return self._hash + + def __eq__(self, other): + if not isinstance(other, PurePath): + return NotImplemented + return self._str_normcase == other._str_normcase and self.parser is other.parser + + @property + def _parts_normcase(self): + # Cached parts with normalized case, for comparisons. + try: + return self._parts_normcase_cached + except AttributeError: + self._parts_normcase_cached = self._str_normcase.split(self.parser.sep) + return self._parts_normcase_cached + + def __lt__(self, other): + if not isinstance(other, PurePath) or self.parser is not other.parser: + return NotImplemented + return self._parts_normcase < other._parts_normcase + + def __le__(self, other): + if not isinstance(other, PurePath) or self.parser is not other.parser: + return NotImplemented + return self._parts_normcase <= other._parts_normcase + + def __gt__(self, other): + if not isinstance(other, PurePath) or self.parser is not other.parser: + return NotImplemented + return self._parts_normcase > other._parts_normcase + + def __ge__(self, other): + if not isinstance(other, PurePath) or self.parser is not other.parser: + return NotImplemented + return self._parts_normcase >= other._parts_normcase + + def __str__(self): + """Return the string representation of the path, suitable for + passing to system calls.""" + try: + return self._str + except AttributeError: + self._str = self._format_parsed_parts(self.drive, self.root, + self._tail) or '.' + return self._str + + @classmethod + def _format_parsed_parts(cls, drv, root, tail): + if drv or root: + return drv + root + cls.parser.sep.join(tail) + elif tail and cls.parser.splitdrive(tail[0])[0]: + tail = ['.'] + tail + return cls.parser.sep.join(tail) + + def _from_parsed_parts(self, drv, root, tail): + path = self._from_parsed_string(self._format_parsed_parts(drv, root, tail)) + path._drv = drv + path._root = root + path._tail_cached = tail + return path + + def _from_parsed_string(self, path_str): + path = self.with_segments(path_str) + path._str = path_str or '.' + return path + + @classmethod + def _parse_path(cls, path): + if not path: + return '', '', [] + sep = cls.parser.sep + altsep = cls.parser.altsep + if altsep: + path = path.replace(altsep, sep) + drv, root, rel = cls.parser.splitroot(path) + if not root and drv.startswith(sep) and not drv.endswith(sep): + drv_parts = drv.split(sep) + if len(drv_parts) == 4 and drv_parts[2] not in '?.': + # e.g. //server/share + root = sep + elif len(drv_parts) == 6: + # e.g. //?/unc/server/share + root = sep + parsed = [sys.intern(str(x)) for x in rel.split(sep) if x and x != '.'] + return drv, root, parsed + + @property + def _raw_path(self): + """The joined but unnormalized path.""" + paths = self._raw_paths + if len(paths) == 0: + path = '' + elif len(paths) == 1: + path = paths[0] + else: + path = self.parser.join(*paths) + return path + + @property + def drive(self): + """The drive prefix (letter or UNC path), if any.""" + try: + return self._drv + except AttributeError: + self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path) + return self._drv + + @property + def root(self): + """The root of the path, if any.""" + try: + return self._root + except AttributeError: + self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path) + return self._root + + @property + def _tail(self): + try: + return self._tail_cached + except AttributeError: + self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path) + return self._tail_cached + + @property + def anchor(self): + """The concatenation of the drive and root, or ''.""" + return self.drive + self.root + + @property + def parts(self): + """An object providing sequence-like access to the + components in the filesystem path.""" + if self.drive or self.root: + return (self.drive + self.root,) + tuple(self._tail) + else: + return tuple(self._tail) + + @property + def parent(self): + """The logical parent of the path.""" + drv = self.drive + root = self.root + tail = self._tail + if not tail: + return self + return self._from_parsed_parts(drv, root, tail[:-1]) + + @property + def parents(self): + """A sequence of this path's logical parents.""" + # The value of this property should not be cached on the path object, + # as doing so would introduce a reference cycle. + return _PathParents(self) + + @property + def name(self): + """The final path component, if any.""" + tail = self._tail + if not tail: + return '' + return tail[-1] + + def with_name(self, name): + """Return a new path with the file name changed.""" + p = self.parser + if not name or p.sep in name or (p.altsep and p.altsep in name) or name == '.': + raise ValueError(f"Invalid name {name!r}") + tail = self._tail.copy() + if not tail: + raise ValueError(f"{self!r} has an empty name") + tail[-1] = name + return self._from_parsed_parts(self.drive, self.root, tail) + + def relative_to(self, other, /, *_deprecated, walk_up=False): + """Return the relative path to another path identified by the passed + arguments. If the operation is not possible (because this is not + related to the other path), raise ValueError. + + The *walk_up* parameter controls whether `..` may be used to resolve + the path. + """ + if _deprecated: + msg = ("support for supplying more than one positional argument " + "to pathlib.PurePath.relative_to() is deprecated and " + "scheduled for removal in Python 3.14") + warnings.warn(msg, DeprecationWarning, stacklevel=2) + other = self.with_segments(other, *_deprecated) + elif not isinstance(other, PurePath): + other = self.with_segments(other) + for step, path in enumerate(chain([other], other.parents)): + if path == self or path in self.parents: + break + elif not walk_up: + raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}") + elif path.name == '..': + raise ValueError(f"'..' segment in {str(other)!r} cannot be walked") + else: + raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors") + parts = ['..'] * step + self._tail[len(path._tail):] + return self._from_parsed_parts('', '', parts) + + def is_relative_to(self, other, /, *_deprecated): + """Return True if the path is relative to another path or False. + """ + if _deprecated: + msg = ("support for supplying more than one argument to " + "pathlib.PurePath.is_relative_to() is deprecated and " + "scheduled for removal in Python 3.14") + warnings.warn(msg, DeprecationWarning, stacklevel=2) + other = self.with_segments(other, *_deprecated) + elif not isinstance(other, PurePath): + other = self.with_segments(other) + return other == self or other in self.parents + + def is_absolute(self): + """True if the path is absolute (has both a root and, if applicable, + a drive).""" + if self.parser is posixpath: + # Optimization: work with raw paths on POSIX. + for path in self._raw_paths: + if path.startswith('/'): + return True + return False + return self.parser.isabs(self) + + def is_reserved(self): + """Return True if the path contains one of the special names reserved + by the system, if any.""" + msg = ("pathlib.PurePath.is_reserved() is deprecated and scheduled " + "for removal in Python 3.15. Use os.path.isreserved() to " + "detect reserved paths on Windows.") + warnings.warn(msg, DeprecationWarning, stacklevel=2) + if self.parser is ntpath: + return self.parser.isreserved(self) + return False + + def as_uri(self): + """Return the path as a URI.""" + if not self.is_absolute(): + raise ValueError("relative path can't be expressed as a file URI") + + drive = self.drive + if len(drive) == 2 and drive[1] == ':': + # It's a path on a local drive => 'file:///c:/a/b' + prefix = 'file:///' + drive + path = self.as_posix()[2:] + elif drive: + # It's a path on a network drive => 'file://host/share/a/b' + prefix = 'file:' + path = self.as_posix() + else: + # It's a posix path => 'file:///etc/hosts' + prefix = 'file://' + path = str(self) + from urllib.parse import quote_from_bytes + return prefix + quote_from_bytes(os.fsencode(path)) + + @property + def _pattern_str(self): + """The path expressed as a string, for use in pattern-matching.""" + # The string representation of an empty path is a single dot ('.'). Empty + # paths shouldn't match wildcards, so we change it to the empty string. + path_str = str(self) + return '' if path_str == '.' else path_str + +# Subclassing os.PathLike makes isinstance() checks slower, +# which in turn makes Path construction slower. Register instead! +os.PathLike.register(PurePath) + + +class PurePosixPath(PurePath): + """PurePath subclass for non-Windows systems. + + On a POSIX system, instantiating a PurePath should return this object. + However, you can also instantiate it directly on any system. + """ + parser = posixpath + __slots__ = () + + +class PureWindowsPath(PurePath): + """PurePath subclass for Windows systems. + + On a Windows system, instantiating a PurePath should return this object. + However, you can also instantiate it directly on any system. + """ + parser = ntpath + __slots__ = () + + +class Path(PathBase, PurePath): + """PurePath subclass that can make system calls. + + Path represents a filesystem path but unlike PurePath, also offers + methods to do system calls on path objects. Depending on your system, + instantiating a Path will return either a PosixPath or a WindowsPath + object. You can also instantiate a PosixPath or WindowsPath directly, + but cannot instantiate a WindowsPath on a POSIX system or vice versa. + """ + __slots__ = () + as_uri = PurePath.as_uri + + @classmethod + def _unsupported_msg(cls, attribute): + return f"{cls.__name__}.{attribute} is unsupported on this system" + + def __init__(self, *args, **kwargs): + if kwargs: + msg = ("support for supplying keyword arguments to pathlib.PurePath " + "is deprecated and scheduled for removal in Python {remove}") + warnings._deprecated("pathlib.PurePath(**kwargs)", msg, remove=(3, 14)) + super().__init__(*args) + + def __new__(cls, *args, **kwargs): + if cls is Path: + cls = WindowsPath if os.name == 'nt' else PosixPath + return object.__new__(cls) + + def stat(self, *, follow_symlinks=True): + """ + Return the result of the stat() system call on this path, like + os.stat() does. + """ + return os.stat(self, follow_symlinks=follow_symlinks) + + def is_mount(self): + """ + Check if this path is a mount point + """ + return os.path.ismount(self) + + def is_junction(self): + """ + Whether this path is a junction. + """ + return os.path.isjunction(self) + + def open(self, mode='r', buffering=-1, encoding=None, + errors=None, newline=None): + """ + Open the file pointed to by this path and return a file object, as + the built-in open() function does. + """ + if "b" not in mode: + encoding = io.text_encoding(encoding) + return io.open(self, mode, buffering, encoding, errors, newline) + + def read_text(self, encoding=None, errors=None, newline=None): + """ + Open the file in text mode, read it, and close the file. + """ + # Call io.text_encoding() here to ensure any warning is raised at an + # appropriate stack level. + encoding = io.text_encoding(encoding) + return PathBase.read_text(self, encoding, errors, newline) + + def write_text(self, data, encoding=None, errors=None, newline=None): + """ + Open the file in text mode, write to it, and close the file. + """ + # Call io.text_encoding() here to ensure any warning is raised at an + # appropriate stack level. + encoding = io.text_encoding(encoding) + return PathBase.write_text(self, data, encoding, errors, newline) + + _remove_leading_dot = operator.itemgetter(slice(2, None)) + _remove_trailing_slash = operator.itemgetter(slice(-1)) + + def _filter_trailing_slash(self, paths): + sep = self.parser.sep + anchor_len = len(self.anchor) + for path_str in paths: + if len(path_str) > anchor_len and path_str[-1] == sep: + path_str = path_str[:-1] + yield path_str + + def iterdir(self): + """Yield path objects of the directory contents. + + The children are yielded in arbitrary order, and the + special entries '.' and '..' are not included. + """ + root_dir = str(self) + with os.scandir(root_dir) as scandir_it: + paths = [entry.path for entry in scandir_it] + if root_dir == '.': + paths = map(self._remove_leading_dot, paths) + return map(self._from_parsed_string, paths) + + def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=False): + """Iterate over this subtree and yield all existing files (of any + kind, including directories) matching the given relative pattern. + """ + sys.audit("pathlib.Path.glob", self, pattern) + if not isinstance(pattern, PurePath): + pattern = self.with_segments(pattern) + if pattern.anchor: + raise NotImplementedError("Non-relative patterns are unsupported") + parts = pattern._tail.copy() + if not parts: + raise ValueError("Unacceptable pattern: {!r}".format(pattern)) + raw = pattern._raw_path + if raw[-1] in (self.parser.sep, self.parser.altsep): + # GH-65238: pathlib doesn't preserve trailing slash. Add it back. + parts.append('') + select = self._glob_selector(parts[::-1], case_sensitive, recurse_symlinks) + root = str(self) + paths = select(root) + + # Normalize results + if root == '.': + paths = map(self._remove_leading_dot, paths) + if parts[-1] == '': + paths = map(self._remove_trailing_slash, paths) + elif parts[-1] == '**': + paths = self._filter_trailing_slash(paths) + paths = map(self._from_parsed_string, paths) + return paths + + def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=False): + """Recursively yield all existing files (of any kind, including + directories) matching the given relative pattern, anywhere in + this subtree. + """ + sys.audit("pathlib.Path.rglob", self, pattern) + if not isinstance(pattern, PurePath): + pattern = self.with_segments(pattern) + pattern = '**' / pattern + return self.glob(pattern, case_sensitive=case_sensitive, recurse_symlinks=recurse_symlinks) + + def walk(self, top_down=True, on_error=None, follow_symlinks=False): + """Walk the directory tree from this directory, similar to os.walk().""" + sys.audit("pathlib.Path.walk", self, on_error, follow_symlinks) + root_dir = str(self) + if not follow_symlinks: + follow_symlinks = os._walk_symlinks_as_files + results = os.walk(root_dir, top_down, on_error, follow_symlinks) + for path_str, dirnames, filenames in results: + if root_dir == '.': + path_str = path_str[2:] + yield self._from_parsed_string(path_str), dirnames, filenames + + def absolute(self): + """Return an absolute version of this path + No normalization or symlink resolution is performed. + + Use resolve() to resolve symlinks and remove '..' segments. + """ + if self.is_absolute(): + return self + if self.root: + drive = os.path.splitroot(os.getcwd())[0] + return self._from_parsed_parts(drive, self.root, self._tail) + if self.drive: + # There is a CWD on each drive-letter drive. + cwd = os.path.abspath(self.drive) + else: + cwd = os.getcwd() + if not self._tail: + # Fast path for "empty" paths, e.g. Path("."), Path("") or Path(). + # We pass only one argument to with_segments() to avoid the cost + # of joining, and we exploit the fact that getcwd() returns a + # fully-normalized string by storing it in _str. This is used to + # implement Path.cwd(). + return self._from_parsed_string(cwd) + drive, root, rel = os.path.splitroot(cwd) + if not rel: + return self._from_parsed_parts(drive, root, self._tail) + tail = rel.split(self.parser.sep) + tail.extend(self._tail) + return self._from_parsed_parts(drive, root, tail) + + def resolve(self, strict=False): + """ + Make the path absolute, resolving all symlinks on the way and also + normalizing it. + """ + + return self.with_segments(os.path.realpath(self, strict=strict)) + + if pwd: + def owner(self, *, follow_symlinks=True): + """ + Return the login name of the file owner. + """ + uid = self.stat(follow_symlinks=follow_symlinks).st_uid + return pwd.getpwuid(uid).pw_name + + if grp: + def group(self, *, follow_symlinks=True): + """ + Return the group name of the file gid. + """ + gid = self.stat(follow_symlinks=follow_symlinks).st_gid + return grp.getgrgid(gid).gr_name + + if hasattr(os, "readlink"): + def readlink(self): + """ + Return the path to which the symbolic link points. + """ + return self.with_segments(os.readlink(self)) + + def touch(self, mode=0o666, exist_ok=True): + """ + Create this file with the given access mode, if it doesn't exist. + """ + + if exist_ok: + # First try to bump modification time + # Implementation note: GNU touch uses the UTIME_NOW option of + # the utimensat() / futimens() functions. + try: + os.utime(self, None) + except OSError: + # Avoid exception chaining + pass + else: + return + flags = os.O_CREAT | os.O_WRONLY + if not exist_ok: + flags |= os.O_EXCL + fd = os.open(self, flags, mode) + os.close(fd) + + def mkdir(self, mode=0o777, parents=False, exist_ok=False): + """ + Create a new directory at this given path. + """ + try: + os.mkdir(self, mode) + except FileNotFoundError: + if not parents or self.parent == self: + raise + self.parent.mkdir(parents=True, exist_ok=True) + self.mkdir(mode, parents=False, exist_ok=exist_ok) + except OSError: + # Cannot rely on checking for EEXIST, since the operating system + # could give priority to other errors like EACCES or EROFS + if not exist_ok or not self.is_dir(): + raise + + def chmod(self, mode, *, follow_symlinks=True): + """ + Change the permissions of the path, like os.chmod(). + """ + os.chmod(self, mode, follow_symlinks=follow_symlinks) + + def unlink(self, missing_ok=False): + """ + Remove this file or link. + If the path is a directory, use rmdir() instead. + """ + try: + os.unlink(self) + except FileNotFoundError: + if not missing_ok: + raise + + def rmdir(self): + """ + Remove this directory. The directory must be empty. + """ + os.rmdir(self) + + def rename(self, target): + """ + Rename this path to the target path. + + The target path may be absolute or relative. Relative paths are + interpreted relative to the current working directory, *not* the + directory of the Path object. + + Returns the new Path instance pointing to the target path. + """ + os.rename(self, target) + return self.with_segments(target) + + def replace(self, target): + """ + Rename this path to the target path, overwriting if that path exists. + + The target path may be absolute or relative. Relative paths are + interpreted relative to the current working directory, *not* the + directory of the Path object. + + Returns the new Path instance pointing to the target path. + """ + os.replace(self, target) + return self.with_segments(target) + + if hasattr(os, "symlink"): + def symlink_to(self, target, target_is_directory=False): + """ + Make this path a symlink pointing to the target path. + Note the order of arguments (link, target) is the reverse of os.symlink. + """ + os.symlink(target, self, target_is_directory) + + if hasattr(os, "link"): + def hardlink_to(self, target): + """ + Make this path a hard link pointing to the same file as *target*. + + Note the order of arguments (self, target) is the reverse of os.link's. + """ + os.link(target, self) + + def expanduser(self): + """ Return a new path with expanded ~ and ~user constructs + (as returned by os.path.expanduser) + """ + if (not (self.drive or self.root) and + self._tail and self._tail[0][:1] == '~'): + homedir = os.path.expanduser(self._tail[0]) + if homedir[:1] == "~": + raise RuntimeError("Could not determine home directory.") + drv, root, tail = self._parse_path(homedir) + return self._from_parsed_parts(drv, root, tail + self._tail[1:]) + + return self + + @classmethod + def from_uri(cls, uri): + """Return a new path from the given 'file' URI.""" + if not uri.startswith('file:'): + raise ValueError(f"URI does not start with 'file:': {uri!r}") + path = uri[5:] + if path[:3] == '///': + # Remove empty authority + path = path[2:] + elif path[:12] == '//localhost/': + # Remove 'localhost' authority + path = path[11:] + if path[:3] == '///' or (path[:1] == '/' and path[2:3] in ':|'): + # Remove slash before DOS device/UNC path + path = path[1:] + if path[1:2] == '|': + # Replace bar with colon in DOS drive + path = path[:1] + ':' + path[2:] + from urllib.parse import unquote_to_bytes + path = cls(os.fsdecode(unquote_to_bytes(path))) + if not path.is_absolute(): + raise ValueError(f"URI is not absolute: {uri!r}") + return path + + +class PosixPath(Path, PurePosixPath): + """Path subclass for non-Windows systems. + + On a POSIX system, instantiating a Path should return this object. + """ + __slots__ = () + + if os.name == 'nt': + def __new__(cls, *args, **kwargs): + raise UnsupportedOperation( + f"cannot instantiate {cls.__name__!r} on your system") + +class WindowsPath(Path, PureWindowsPath): + """Path subclass for Windows systems. + + On a Windows system, instantiating a Path should return this object. + """ + __slots__ = () + + if os.name != 'nt': + def __new__(cls, *args, **kwargs): + raise UnsupportedOperation( + f"cannot instantiate {cls.__name__!r} on your system") diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py deleted file mode 100644 index c1696bb3737..00000000000 --- a/Lib/test/test_pathlib.py +++ /dev/null @@ -1,3328 +0,0 @@ -import contextlib -import collections.abc -import io -import os -import sys -import errno -import pathlib -import pickle -import socket -import stat -import tempfile -import unittest -from unittest import mock - -from test.support import import_helper -from test.support import set_recursion_limit -from test.support import is_emscripten, is_wasi -from test.support import os_helper -from test.support.os_helper import TESTFN, FakePath - -try: - import grp, pwd -except ImportError: - grp = pwd = None - - -# -# Tests for the pure classes. -# - -class _BasePurePathSubclass(object): - def __init__(self, *pathsegments, session_id): - super().__init__(*pathsegments) - self.session_id = session_id - - def with_segments(self, *pathsegments): - return type(self)(*pathsegments, session_id=self.session_id) - - -class _BasePurePathTest(object): - - # Keys are canonical paths, values are list of tuples of arguments - # supposed to produce equal paths. - equivalences = { - 'a/b': [ - ('a', 'b'), ('a/', 'b'), ('a', 'b/'), ('a/', 'b/'), - ('a/b/',), ('a//b',), ('a//b//',), - # Empty components get removed. - ('', 'a', 'b'), ('a', '', 'b'), ('a', 'b', ''), - ], - '/b/c/d': [ - ('a', '/b/c', 'd'), ('/a', '/b/c', 'd'), - # Empty components get removed. - ('/', 'b', '', 'c/d'), ('/', '', 'b/c/d'), ('', '/b/c/d'), - ], - } - - def setUp(self): - p = self.cls('a') - self.flavour = p._flavour - self.sep = self.flavour.sep - self.altsep = self.flavour.altsep - - def test_constructor_common(self): - P = self.cls - p = P('a') - self.assertIsInstance(p, P) - P('a', 'b', 'c') - P('/a', 'b', 'c') - P('a/b/c') - P('/a/b/c') - P(FakePath("a/b/c")) - self.assertEqual(P(P('a')), P('a')) - self.assertEqual(P(P('a'), 'b'), P('a/b')) - self.assertEqual(P(P('a'), P('b')), P('a/b')) - self.assertEqual(P(P('a'), P('b'), P('c')), P(FakePath("a/b/c"))) - self.assertEqual(P(P('./a:b')), P('./a:b')) - - def test_bytes(self): - P = self.cls - message = (r"argument should be a str or an os\.PathLike object " - r"where __fspath__ returns a str, not 'bytes'") - with self.assertRaisesRegex(TypeError, message): - P(b'a') - with self.assertRaisesRegex(TypeError, message): - P(b'a', 'b') - with self.assertRaisesRegex(TypeError, message): - P('a', b'b') - with self.assertRaises(TypeError): - P('a').joinpath(b'b') - with self.assertRaises(TypeError): - P('a') / b'b' - with self.assertRaises(TypeError): - b'a' / P('b') - with self.assertRaises(TypeError): - P('a').match(b'b') - with self.assertRaises(TypeError): - P('a').relative_to(b'b') - with self.assertRaises(TypeError): - P('a').with_name(b'b') - with self.assertRaises(TypeError): - P('a').with_stem(b'b') - with self.assertRaises(TypeError): - P('a').with_suffix(b'b') - - def _check_str_subclass(self, *args): - # Issue #21127: it should be possible to construct a PurePath object - # from a str subclass instance, and it then gets converted to - # a pure str object. - class StrSubclass(str): - pass - P = self.cls - p = P(*(StrSubclass(x) for x in args)) - self.assertEqual(p, P(*args)) - for part in p.parts: - self.assertIs(type(part), str) - - def test_str_subclass_common(self): - self._check_str_subclass('') - self._check_str_subclass('.') - self._check_str_subclass('a') - self._check_str_subclass('a/b.txt') - self._check_str_subclass('/a/b.txt') - - @unittest.skip("TODO: RUSTPYTHON; PyObject::set_slot index out of bounds") - def test_with_segments_common(self): - class P(_BasePurePathSubclass, self.cls): - pass - p = P('foo', 'bar', session_id=42) - self.assertEqual(42, (p / 'foo').session_id) - self.assertEqual(42, ('foo' / p).session_id) - self.assertEqual(42, p.joinpath('foo').session_id) - self.assertEqual(42, p.with_name('foo').session_id) - self.assertEqual(42, p.with_stem('foo').session_id) - self.assertEqual(42, p.with_suffix('.foo').session_id) - self.assertEqual(42, p.with_segments('foo').session_id) - self.assertEqual(42, p.relative_to('foo').session_id) - self.assertEqual(42, p.parent.session_id) - for parent in p.parents: - self.assertEqual(42, parent.session_id) - - def _get_drive_root_parts(self, parts): - path = self.cls(*parts) - return path.drive, path.root, path.parts - - def _check_drive_root_parts(self, arg, *expected): - sep = self.flavour.sep - actual = self._get_drive_root_parts([x.replace('/', sep) for x in arg]) - self.assertEqual(actual, expected) - if altsep := self.flavour.altsep: - actual = self._get_drive_root_parts([x.replace('/', altsep) for x in arg]) - self.assertEqual(actual, expected) - - def test_drive_root_parts_common(self): - check = self._check_drive_root_parts - sep = self.flavour.sep - # Unanchored parts. - check((), '', '', ()) - check(('a',), '', '', ('a',)) - check(('a/',), '', '', ('a',)) - check(('a', 'b'), '', '', ('a', 'b')) - # Expansion. - check(('a/b',), '', '', ('a', 'b')) - check(('a/b/',), '', '', ('a', 'b')) - check(('a', 'b/c', 'd'), '', '', ('a', 'b', 'c', 'd')) - # Collapsing and stripping excess slashes. - check(('a', 'b//c', 'd'), '', '', ('a', 'b', 'c', 'd')) - check(('a', 'b/c/', 'd'), '', '', ('a', 'b', 'c', 'd')) - # Eliminating standalone dots. - check(('.',), '', '', ()) - check(('.', '.', 'b'), '', '', ('b',)) - check(('a', '.', 'b'), '', '', ('a', 'b')) - check(('a', '.', '.'), '', '', ('a',)) - # The first part is anchored. - check(('/a/b',), '', sep, (sep, 'a', 'b')) - check(('/a', 'b'), '', sep, (sep, 'a', 'b')) - check(('/a/', 'b'), '', sep, (sep, 'a', 'b')) - # Ignoring parts before an anchored part. - check(('a', '/b', 'c'), '', sep, (sep, 'b', 'c')) - check(('a', '/b', '/c'), '', sep, (sep, 'c')) - - def test_join_common(self): - P = self.cls - p = P('a/b') - pp = p.joinpath('c') - self.assertEqual(pp, P('a/b/c')) - self.assertIs(type(pp), type(p)) - pp = p.joinpath('c', 'd') - self.assertEqual(pp, P('a/b/c/d')) - pp = p.joinpath(P('c')) - self.assertEqual(pp, P('a/b/c')) - pp = p.joinpath('/c') - self.assertEqual(pp, P('/c')) - - def test_div_common(self): - # Basically the same as joinpath(). - P = self.cls - p = P('a/b') - pp = p / 'c' - self.assertEqual(pp, P('a/b/c')) - self.assertIs(type(pp), type(p)) - pp = p / 'c/d' - self.assertEqual(pp, P('a/b/c/d')) - pp = p / 'c' / 'd' - self.assertEqual(pp, P('a/b/c/d')) - pp = 'c' / p / 'd' - self.assertEqual(pp, P('c/a/b/d')) - pp = p / P('c') - self.assertEqual(pp, P('a/b/c')) - pp = p/ '/c' - self.assertEqual(pp, P('/c')) - - def _check_str(self, expected, args): - p = self.cls(*args) - self.assertEqual(str(p), expected.replace('/', self.sep)) - - def test_str_common(self): - # Canonicalized paths roundtrip. - for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): - self._check_str(pathstr, (pathstr,)) - # Special case for the empty path. - self._check_str('.', ('',)) - # Other tests for str() are in test_equivalences(). - - def test_as_posix_common(self): - P = self.cls - for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): - self.assertEqual(P(pathstr).as_posix(), pathstr) - # Other tests for as_posix() are in test_equivalences(). - - def test_as_bytes_common(self): - sep = os.fsencode(self.sep) - P = self.cls - self.assertEqual(bytes(P('a/b')), b'a' + sep + b'b') - - def test_as_uri_common(self): - P = self.cls - with self.assertRaises(ValueError): - P('a').as_uri() - with self.assertRaises(ValueError): - P().as_uri() - - def test_repr_common(self): - for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): - with self.subTest(pathstr=pathstr): - p = self.cls(pathstr) - clsname = p.__class__.__name__ - r = repr(p) - # The repr() is in the form ClassName("forward-slashes path"). - self.assertTrue(r.startswith(clsname + '('), r) - self.assertTrue(r.endswith(')'), r) - inner = r[len(clsname) + 1 : -1] - self.assertEqual(eval(inner), p.as_posix()) - - def test_repr_roundtrips(self): - for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): - with self.subTest(pathstr=pathstr): - p = self.cls(pathstr) - r = repr(p) - # The repr() roundtrips. - q = eval(r, pathlib.__dict__) - self.assertIs(q.__class__, p.__class__) - self.assertEqual(q, p) - self.assertEqual(repr(q), r) - - def test_eq_common(self): - P = self.cls - self.assertEqual(P('a/b'), P('a/b')) - self.assertEqual(P('a/b'), P('a', 'b')) - self.assertNotEqual(P('a/b'), P('a')) - self.assertNotEqual(P('a/b'), P('/a/b')) - self.assertNotEqual(P('a/b'), P()) - self.assertNotEqual(P('/a/b'), P('/')) - self.assertNotEqual(P(), P('/')) - self.assertNotEqual(P(), "") - self.assertNotEqual(P(), {}) - self.assertNotEqual(P(), int) - - def test_match_common(self): - P = self.cls - self.assertRaises(ValueError, P('a').match, '') - self.assertRaises(ValueError, P('a').match, '.') - # Simple relative pattern. - self.assertTrue(P('b.py').match('b.py')) - self.assertTrue(P('a/b.py').match('b.py')) - self.assertTrue(P('/a/b.py').match('b.py')) - self.assertFalse(P('a.py').match('b.py')) - self.assertFalse(P('b/py').match('b.py')) - self.assertFalse(P('/a.py').match('b.py')) - self.assertFalse(P('b.py/c').match('b.py')) - # Wildcard relative pattern. - self.assertTrue(P('b.py').match('*.py')) - self.assertTrue(P('a/b.py').match('*.py')) - self.assertTrue(P('/a/b.py').match('*.py')) - self.assertFalse(P('b.pyc').match('*.py')) - self.assertFalse(P('b./py').match('*.py')) - self.assertFalse(P('b.py/c').match('*.py')) - # Multi-part relative pattern. - self.assertTrue(P('ab/c.py').match('a*/*.py')) - self.assertTrue(P('/d/ab/c.py').match('a*/*.py')) - self.assertFalse(P('a.py').match('a*/*.py')) - self.assertFalse(P('/dab/c.py').match('a*/*.py')) - self.assertFalse(P('ab/c.py/d').match('a*/*.py')) - # Absolute pattern. - self.assertTrue(P('/b.py').match('/*.py')) - self.assertFalse(P('b.py').match('/*.py')) - self.assertFalse(P('a/b.py').match('/*.py')) - self.assertFalse(P('/a/b.py').match('/*.py')) - # Multi-part absolute pattern. - self.assertTrue(P('/a/b.py').match('/a/*.py')) - self.assertFalse(P('/ab.py').match('/a/*.py')) - self.assertFalse(P('/a/b/c.py').match('/a/*.py')) - # Multi-part glob-style pattern. - self.assertFalse(P('/a/b/c.py').match('/**/*.py')) - self.assertTrue(P('/a/b/c.py').match('/a/**/*.py')) - # Case-sensitive flag - self.assertFalse(P('A.py').match('a.PY', case_sensitive=True)) - self.assertTrue(P('A.py').match('a.PY', case_sensitive=False)) - self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True)) - self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False)) - # Matching against empty path - self.assertFalse(P().match('*')) - self.assertTrue(P().match('**')) - self.assertFalse(P().match('**/*')) - - def test_ordering_common(self): - # Ordering is tuple-alike. - def assertLess(a, b): - self.assertLess(a, b) - self.assertGreater(b, a) - P = self.cls - a = P('a') - b = P('a/b') - c = P('abc') - d = P('b') - assertLess(a, b) - assertLess(a, c) - assertLess(a, d) - assertLess(b, c) - assertLess(c, d) - P = self.cls - a = P('/a') - b = P('/a/b') - c = P('/abc') - d = P('/b') - assertLess(a, b) - assertLess(a, c) - assertLess(a, d) - assertLess(b, c) - assertLess(c, d) - with self.assertRaises(TypeError): - P() < {} - - def test_parts_common(self): - # `parts` returns a tuple. - sep = self.sep - P = self.cls - p = P('a/b') - parts = p.parts - self.assertEqual(parts, ('a', 'b')) - # When the path is absolute, the anchor is a separate part. - p = P('/a/b') - parts = p.parts - self.assertEqual(parts, (sep, 'a', 'b')) - - def test_fspath_common(self): - P = self.cls - p = P('a/b') - self._check_str(p.__fspath__(), ('a/b',)) - self._check_str(os.fspath(p), ('a/b',)) - - def test_equivalences(self): - for k, tuples in self.equivalences.items(): - canon = k.replace('/', self.sep) - posix = k.replace(self.sep, '/') - if canon != posix: - tuples = tuples + [ - tuple(part.replace('/', self.sep) for part in t) - for t in tuples - ] - tuples.append((posix, )) - pcanon = self.cls(canon) - for t in tuples: - p = self.cls(*t) - self.assertEqual(p, pcanon, "failed with args {}".format(t)) - self.assertEqual(hash(p), hash(pcanon)) - self.assertEqual(str(p), canon) - self.assertEqual(p.as_posix(), posix) - - def test_parent_common(self): - # Relative - P = self.cls - p = P('a/b/c') - self.assertEqual(p.parent, P('a/b')) - self.assertEqual(p.parent.parent, P('a')) - self.assertEqual(p.parent.parent.parent, P()) - self.assertEqual(p.parent.parent.parent.parent, P()) - # Anchored - p = P('/a/b/c') - self.assertEqual(p.parent, P('/a/b')) - self.assertEqual(p.parent.parent, P('/a')) - self.assertEqual(p.parent.parent.parent, P('/')) - self.assertEqual(p.parent.parent.parent.parent, P('/')) - - def test_parents_common(self): - # Relative - P = self.cls - p = P('a/b/c') - par = p.parents - self.assertEqual(len(par), 3) - self.assertEqual(par[0], P('a/b')) - self.assertEqual(par[1], P('a')) - self.assertEqual(par[2], P('.')) - self.assertEqual(par[-1], P('.')) - self.assertEqual(par[-2], P('a')) - self.assertEqual(par[-3], P('a/b')) - self.assertEqual(par[0:1], (P('a/b'),)) - self.assertEqual(par[:2], (P('a/b'), P('a'))) - self.assertEqual(par[:-1], (P('a/b'), P('a'))) - self.assertEqual(par[1:], (P('a'), P('.'))) - self.assertEqual(par[::2], (P('a/b'), P('.'))) - self.assertEqual(par[::-1], (P('.'), P('a'), P('a/b'))) - self.assertEqual(list(par), [P('a/b'), P('a'), P('.')]) - with self.assertRaises(IndexError): - par[-4] - with self.assertRaises(IndexError): - par[3] - with self.assertRaises(TypeError): - par[0] = p - # Anchored - p = P('/a/b/c') - par = p.parents - self.assertEqual(len(par), 3) - self.assertEqual(par[0], P('/a/b')) - self.assertEqual(par[1], P('/a')) - self.assertEqual(par[2], P('/')) - self.assertEqual(par[-1], P('/')) - self.assertEqual(par[-2], P('/a')) - self.assertEqual(par[-3], P('/a/b')) - self.assertEqual(par[0:1], (P('/a/b'),)) - self.assertEqual(par[:2], (P('/a/b'), P('/a'))) - self.assertEqual(par[:-1], (P('/a/b'), P('/a'))) - self.assertEqual(par[1:], (P('/a'), P('/'))) - self.assertEqual(par[::2], (P('/a/b'), P('/'))) - self.assertEqual(par[::-1], (P('/'), P('/a'), P('/a/b'))) - self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')]) - with self.assertRaises(IndexError): - par[-4] - with self.assertRaises(IndexError): - par[3] - - def test_drive_common(self): - P = self.cls - self.assertEqual(P('a/b').drive, '') - self.assertEqual(P('/a/b').drive, '') - self.assertEqual(P('').drive, '') - - def test_root_common(self): - P = self.cls - sep = self.sep - self.assertEqual(P('').root, '') - self.assertEqual(P('a/b').root, '') - self.assertEqual(P('/').root, sep) - self.assertEqual(P('/a/b').root, sep) - - def test_anchor_common(self): - P = self.cls - sep = self.sep - self.assertEqual(P('').anchor, '') - self.assertEqual(P('a/b').anchor, '') - self.assertEqual(P('/').anchor, sep) - self.assertEqual(P('/a/b').anchor, sep) - - def test_name_common(self): - P = self.cls - self.assertEqual(P('').name, '') - self.assertEqual(P('.').name, '') - self.assertEqual(P('/').name, '') - self.assertEqual(P('a/b').name, 'b') - self.assertEqual(P('/a/b').name, 'b') - self.assertEqual(P('/a/b/.').name, 'b') - self.assertEqual(P('a/b.py').name, 'b.py') - self.assertEqual(P('/a/b.py').name, 'b.py') - - def test_suffix_common(self): - P = self.cls - self.assertEqual(P('').suffix, '') - self.assertEqual(P('.').suffix, '') - self.assertEqual(P('..').suffix, '') - self.assertEqual(P('/').suffix, '') - self.assertEqual(P('a/b').suffix, '') - self.assertEqual(P('/a/b').suffix, '') - self.assertEqual(P('/a/b/.').suffix, '') - self.assertEqual(P('a/b.py').suffix, '.py') - self.assertEqual(P('/a/b.py').suffix, '.py') - self.assertEqual(P('a/.hgrc').suffix, '') - self.assertEqual(P('/a/.hgrc').suffix, '') - self.assertEqual(P('a/.hg.rc').suffix, '.rc') - self.assertEqual(P('/a/.hg.rc').suffix, '.rc') - self.assertEqual(P('a/b.tar.gz').suffix, '.gz') - self.assertEqual(P('/a/b.tar.gz').suffix, '.gz') - self.assertEqual(P('a/Some name. Ending with a dot.').suffix, '') - self.assertEqual(P('/a/Some name. Ending with a dot.').suffix, '') - - def test_suffixes_common(self): - P = self.cls - self.assertEqual(P('').suffixes, []) - self.assertEqual(P('.').suffixes, []) - self.assertEqual(P('/').suffixes, []) - self.assertEqual(P('a/b').suffixes, []) - self.assertEqual(P('/a/b').suffixes, []) - self.assertEqual(P('/a/b/.').suffixes, []) - self.assertEqual(P('a/b.py').suffixes, ['.py']) - self.assertEqual(P('/a/b.py').suffixes, ['.py']) - self.assertEqual(P('a/.hgrc').suffixes, []) - self.assertEqual(P('/a/.hgrc').suffixes, []) - self.assertEqual(P('a/.hg.rc').suffixes, ['.rc']) - self.assertEqual(P('/a/.hg.rc').suffixes, ['.rc']) - self.assertEqual(P('a/b.tar.gz').suffixes, ['.tar', '.gz']) - self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz']) - self.assertEqual(P('a/Some name. Ending with a dot.').suffixes, []) - self.assertEqual(P('/a/Some name. Ending with a dot.').suffixes, []) - - def test_stem_common(self): - P = self.cls - self.assertEqual(P('').stem, '') - self.assertEqual(P('.').stem, '') - self.assertEqual(P('..').stem, '..') - self.assertEqual(P('/').stem, '') - self.assertEqual(P('a/b').stem, 'b') - self.assertEqual(P('a/b.py').stem, 'b') - self.assertEqual(P('a/.hgrc').stem, '.hgrc') - self.assertEqual(P('a/.hg.rc').stem, '.hg') - self.assertEqual(P('a/b.tar.gz').stem, 'b.tar') - self.assertEqual(P('a/Some name. Ending with a dot.').stem, - 'Some name. Ending with a dot.') - - def test_with_name_common(self): - P = self.cls - self.assertEqual(P('a/b').with_name('d.xml'), P('a/d.xml')) - self.assertEqual(P('/a/b').with_name('d.xml'), P('/a/d.xml')) - self.assertEqual(P('a/b.py').with_name('d.xml'), P('a/d.xml')) - self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml')) - self.assertEqual(P('a/Dot ending.').with_name('d.xml'), P('a/d.xml')) - self.assertEqual(P('/a/Dot ending.').with_name('d.xml'), P('/a/d.xml')) - self.assertRaises(ValueError, P('').with_name, 'd.xml') - self.assertRaises(ValueError, P('.').with_name, 'd.xml') - self.assertRaises(ValueError, P('/').with_name, 'd.xml') - self.assertRaises(ValueError, P('a/b').with_name, '') - self.assertRaises(ValueError, P('a/b').with_name, '.') - self.assertRaises(ValueError, P('a/b').with_name, '/c') - self.assertRaises(ValueError, P('a/b').with_name, 'c/') - self.assertRaises(ValueError, P('a/b').with_name, 'c/d') - - def test_with_stem_common(self): - P = self.cls - self.assertEqual(P('a/b').with_stem('d'), P('a/d')) - self.assertEqual(P('/a/b').with_stem('d'), P('/a/d')) - self.assertEqual(P('a/b.py').with_stem('d'), P('a/d.py')) - self.assertEqual(P('/a/b.py').with_stem('d'), P('/a/d.py')) - self.assertEqual(P('/a/b.tar.gz').with_stem('d'), P('/a/d.gz')) - self.assertEqual(P('a/Dot ending.').with_stem('d'), P('a/d')) - self.assertEqual(P('/a/Dot ending.').with_stem('d'), P('/a/d')) - self.assertRaises(ValueError, P('').with_stem, 'd') - self.assertRaises(ValueError, P('.').with_stem, 'd') - self.assertRaises(ValueError, P('/').with_stem, 'd') - self.assertRaises(ValueError, P('a/b').with_stem, '') - self.assertRaises(ValueError, P('a/b').with_stem, '.') - self.assertRaises(ValueError, P('a/b').with_stem, '/c') - self.assertRaises(ValueError, P('a/b').with_stem, 'c/') - self.assertRaises(ValueError, P('a/b').with_stem, 'c/d') - - def test_with_suffix_common(self): - P = self.cls - self.assertEqual(P('a/b').with_suffix('.gz'), P('a/b.gz')) - self.assertEqual(P('/a/b').with_suffix('.gz'), P('/a/b.gz')) - self.assertEqual(P('a/b.py').with_suffix('.gz'), P('a/b.gz')) - self.assertEqual(P('/a/b.py').with_suffix('.gz'), P('/a/b.gz')) - # Stripping suffix. - self.assertEqual(P('a/b.py').with_suffix(''), P('a/b')) - self.assertEqual(P('/a/b').with_suffix(''), P('/a/b')) - # Path doesn't have a "filename" component. - self.assertRaises(ValueError, P('').with_suffix, '.gz') - self.assertRaises(ValueError, P('.').with_suffix, '.gz') - self.assertRaises(ValueError, P('/').with_suffix, '.gz') - # Invalid suffix. - self.assertRaises(ValueError, P('a/b').with_suffix, 'gz') - self.assertRaises(ValueError, P('a/b').with_suffix, '/') - self.assertRaises(ValueError, P('a/b').with_suffix, '.') - self.assertRaises(ValueError, P('a/b').with_suffix, '/.gz') - self.assertRaises(ValueError, P('a/b').with_suffix, 'c/d') - self.assertRaises(ValueError, P('a/b').with_suffix, '.c/.d') - self.assertRaises(ValueError, P('a/b').with_suffix, './.d') - self.assertRaises(ValueError, P('a/b').with_suffix, '.d/.') - self.assertRaises(ValueError, P('a/b').with_suffix, - (self.flavour.sep, 'd')) - - def test_relative_to_common(self): - P = self.cls - p = P('a/b') - self.assertRaises(TypeError, p.relative_to) - self.assertRaises(TypeError, p.relative_to, b'a') - self.assertEqual(p.relative_to(P()), P('a/b')) - self.assertEqual(p.relative_to(''), P('a/b')) - self.assertEqual(p.relative_to(P('a')), P('b')) - self.assertEqual(p.relative_to('a'), P('b')) - self.assertEqual(p.relative_to('a/'), P('b')) - self.assertEqual(p.relative_to(P('a/b')), P()) - self.assertEqual(p.relative_to('a/b'), P()) - self.assertEqual(p.relative_to(P(), walk_up=True), P('a/b')) - self.assertEqual(p.relative_to('', walk_up=True), P('a/b')) - self.assertEqual(p.relative_to(P('a'), walk_up=True), P('b')) - self.assertEqual(p.relative_to('a', walk_up=True), P('b')) - self.assertEqual(p.relative_to('a/', walk_up=True), P('b')) - self.assertEqual(p.relative_to(P('a/b'), walk_up=True), P()) - self.assertEqual(p.relative_to('a/b', walk_up=True), P()) - self.assertEqual(p.relative_to(P('a/c'), walk_up=True), P('../b')) - self.assertEqual(p.relative_to('a/c', walk_up=True), P('../b')) - self.assertEqual(p.relative_to(P('a/b/c'), walk_up=True), P('..')) - self.assertEqual(p.relative_to('a/b/c', walk_up=True), P('..')) - self.assertEqual(p.relative_to(P('c'), walk_up=True), P('../a/b')) - self.assertEqual(p.relative_to('c', walk_up=True), P('../a/b')) - # With several args. - with self.assertWarns(DeprecationWarning): - p.relative_to('a', 'b') - p.relative_to('a', 'b', walk_up=True) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, P('c')) - self.assertRaises(ValueError, p.relative_to, P('a/b/c')) - self.assertRaises(ValueError, p.relative_to, P('a/c')) - self.assertRaises(ValueError, p.relative_to, P('/a')) - self.assertRaises(ValueError, p.relative_to, P("../a")) - self.assertRaises(ValueError, p.relative_to, P("a/..")) - self.assertRaises(ValueError, p.relative_to, P("/a/..")) - self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('/a'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) - p = P('/a/b') - self.assertEqual(p.relative_to(P('/')), P('a/b')) - self.assertEqual(p.relative_to('/'), P('a/b')) - self.assertEqual(p.relative_to(P('/a')), P('b')) - self.assertEqual(p.relative_to('/a'), P('b')) - self.assertEqual(p.relative_to('/a/'), P('b')) - self.assertEqual(p.relative_to(P('/a/b')), P()) - self.assertEqual(p.relative_to('/a/b'), P()) - self.assertEqual(p.relative_to(P('/'), walk_up=True), P('a/b')) - self.assertEqual(p.relative_to('/', walk_up=True), P('a/b')) - self.assertEqual(p.relative_to(P('/a'), walk_up=True), P('b')) - self.assertEqual(p.relative_to('/a', walk_up=True), P('b')) - self.assertEqual(p.relative_to('/a/', walk_up=True), P('b')) - self.assertEqual(p.relative_to(P('/a/b'), walk_up=True), P()) - self.assertEqual(p.relative_to('/a/b', walk_up=True), P()) - self.assertEqual(p.relative_to(P('/a/c'), walk_up=True), P('../b')) - self.assertEqual(p.relative_to('/a/c', walk_up=True), P('../b')) - self.assertEqual(p.relative_to(P('/a/b/c'), walk_up=True), P('..')) - self.assertEqual(p.relative_to('/a/b/c', walk_up=True), P('..')) - self.assertEqual(p.relative_to(P('/c'), walk_up=True), P('../a/b')) - self.assertEqual(p.relative_to('/c', walk_up=True), P('../a/b')) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, P('/c')) - self.assertRaises(ValueError, p.relative_to, P('/a/b/c')) - self.assertRaises(ValueError, p.relative_to, P('/a/c')) - self.assertRaises(ValueError, p.relative_to, P()) - self.assertRaises(ValueError, p.relative_to, '') - self.assertRaises(ValueError, p.relative_to, P('a')) - self.assertRaises(ValueError, p.relative_to, P("../a")) - self.assertRaises(ValueError, p.relative_to, P("a/..")) - self.assertRaises(ValueError, p.relative_to, P("/a/..")) - self.assertRaises(ValueError, p.relative_to, P(''), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('a'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) - - def test_is_relative_to_common(self): - P = self.cls - p = P('a/b') - self.assertRaises(TypeError, p.is_relative_to) - self.assertRaises(TypeError, p.is_relative_to, b'a') - self.assertTrue(p.is_relative_to(P())) - self.assertTrue(p.is_relative_to('')) - self.assertTrue(p.is_relative_to(P('a'))) - self.assertTrue(p.is_relative_to('a/')) - self.assertTrue(p.is_relative_to(P('a/b'))) - self.assertTrue(p.is_relative_to('a/b')) - # With several args. - with self.assertWarns(DeprecationWarning): - p.is_relative_to('a', 'b') - # Unrelated paths. - self.assertFalse(p.is_relative_to(P('c'))) - self.assertFalse(p.is_relative_to(P('a/b/c'))) - self.assertFalse(p.is_relative_to(P('a/c'))) - self.assertFalse(p.is_relative_to(P('/a'))) - p = P('/a/b') - self.assertTrue(p.is_relative_to(P('/'))) - self.assertTrue(p.is_relative_to('/')) - self.assertTrue(p.is_relative_to(P('/a'))) - self.assertTrue(p.is_relative_to('/a')) - self.assertTrue(p.is_relative_to('/a/')) - self.assertTrue(p.is_relative_to(P('/a/b'))) - self.assertTrue(p.is_relative_to('/a/b')) - # Unrelated paths. - self.assertFalse(p.is_relative_to(P('/c'))) - self.assertFalse(p.is_relative_to(P('/a/b/c'))) - self.assertFalse(p.is_relative_to(P('/a/c'))) - self.assertFalse(p.is_relative_to(P())) - self.assertFalse(p.is_relative_to('')) - self.assertFalse(p.is_relative_to(P('a'))) - - def test_pickling_common(self): - P = self.cls - p = P('/a/b') - for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): - dumped = pickle.dumps(p, proto) - pp = pickle.loads(dumped) - self.assertIs(pp.__class__, p.__class__) - self.assertEqual(pp, p) - self.assertEqual(hash(pp), hash(p)) - self.assertEqual(str(pp), str(p)) - - -class PurePosixPathTest(_BasePurePathTest, unittest.TestCase): - cls = pathlib.PurePosixPath - - def test_drive_root_parts(self): - check = self._check_drive_root_parts - # Collapsing of excess leading slashes, except for the double-slash - # special case. - check(('//a', 'b'), '', '//', ('//', 'a', 'b')) - check(('///a', 'b'), '', '/', ('/', 'a', 'b')) - check(('////a', 'b'), '', '/', ('/', 'a', 'b')) - # Paths which look like NT paths aren't treated specially. - check(('c:a',), '', '', ('c:a',)) - check(('c:\\a',), '', '', ('c:\\a',)) - check(('\\a',), '', '', ('\\a',)) - - def test_root(self): - P = self.cls - self.assertEqual(P('/a/b').root, '/') - self.assertEqual(P('///a/b').root, '/') - # POSIX special case for two leading slashes. - self.assertEqual(P('//a/b').root, '//') - - def test_eq(self): - P = self.cls - self.assertNotEqual(P('a/b'), P('A/b')) - self.assertEqual(P('/a'), P('///a')) - self.assertNotEqual(P('/a'), P('//a')) - - def test_as_uri(self): - P = self.cls - self.assertEqual(P('/').as_uri(), 'file:///') - self.assertEqual(P('/a/b.c').as_uri(), 'file:///a/b.c') - self.assertEqual(P('/a/b%#c').as_uri(), 'file:///a/b%25%23c') - - def test_as_uri_non_ascii(self): - from urllib.parse import quote_from_bytes - P = self.cls - try: - os.fsencode('\xe9') - except UnicodeEncodeError: - self.skipTest("\\xe9 cannot be encoded to the filesystem encoding") - self.assertEqual(P('/a/b\xe9').as_uri(), - 'file:///a/b' + quote_from_bytes(os.fsencode('\xe9'))) - - def test_match(self): - P = self.cls - self.assertFalse(P('A.py').match('a.PY')) - - def test_is_absolute(self): - P = self.cls - self.assertFalse(P().is_absolute()) - self.assertFalse(P('a').is_absolute()) - self.assertFalse(P('a/b/').is_absolute()) - self.assertTrue(P('/').is_absolute()) - self.assertTrue(P('/a').is_absolute()) - self.assertTrue(P('/a/b/').is_absolute()) - self.assertTrue(P('//a').is_absolute()) - self.assertTrue(P('//a/b').is_absolute()) - - def test_is_reserved(self): - P = self.cls - self.assertIs(False, P('').is_reserved()) - self.assertIs(False, P('/').is_reserved()) - self.assertIs(False, P('/foo/bar').is_reserved()) - self.assertIs(False, P('/dev/con/PRN/NUL').is_reserved()) - - def test_join(self): - P = self.cls - p = P('//a') - pp = p.joinpath('b') - self.assertEqual(pp, P('//a/b')) - pp = P('/a').joinpath('//c') - self.assertEqual(pp, P('//c')) - pp = P('//a').joinpath('/c') - self.assertEqual(pp, P('/c')) - - def test_div(self): - # Basically the same as joinpath(). - P = self.cls - p = P('//a') - pp = p / 'b' - self.assertEqual(pp, P('//a/b')) - pp = P('/a') / '//c' - self.assertEqual(pp, P('//c')) - pp = P('//a') / '/c' - self.assertEqual(pp, P('/c')) - - def test_parse_windows_path(self): - P = self.cls - p = P('c:', 'a', 'b') - pp = P(pathlib.PureWindowsPath('c:\\a\\b')) - self.assertEqual(p, pp) - - -class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase): - cls = pathlib.PureWindowsPath - - equivalences = _BasePurePathTest.equivalences.copy() - equivalences.update({ - './a:b': [ ('./a:b',) ], - 'c:a': [ ('c:', 'a'), ('c:', 'a/'), ('.', 'c:', 'a') ], - 'c:/a': [ - ('c:/', 'a'), ('c:', '/', 'a'), ('c:', '/a'), - ('/z', 'c:/', 'a'), ('//x/y', 'c:/', 'a'), - ], - '//a/b/': [ ('//a/b',) ], - '//a/b/c': [ - ('//a/b', 'c'), ('//a/b/', 'c'), - ], - }) - - def test_drive_root_parts(self): - check = self._check_drive_root_parts - # First part is anchored. - check(('c:',), 'c:', '', ('c:',)) - check(('c:/',), 'c:', '\\', ('c:\\',)) - check(('/',), '', '\\', ('\\',)) - check(('c:a',), 'c:', '', ('c:', 'a')) - check(('c:/a',), 'c:', '\\', ('c:\\', 'a')) - check(('/a',), '', '\\', ('\\', 'a')) - # UNC paths. - check(('//',), '\\\\', '', ('\\\\',)) - check(('//a',), '\\\\a', '', ('\\\\a',)) - check(('//a/',), '\\\\a\\', '', ('\\\\a\\',)) - check(('//a/b',), '\\\\a\\b', '\\', ('\\\\a\\b\\',)) - check(('//a/b/',), '\\\\a\\b', '\\', ('\\\\a\\b\\',)) - check(('//a/b/c',), '\\\\a\\b', '\\', ('\\\\a\\b\\', 'c')) - # Second part is anchored, so that the first part is ignored. - check(('a', 'Z:b', 'c'), 'Z:', '', ('Z:', 'b', 'c')) - check(('a', 'Z:/b', 'c'), 'Z:', '\\', ('Z:\\', 'b', 'c')) - # UNC paths. - check(('a', '//b/c', 'd'), '\\\\b\\c', '\\', ('\\\\b\\c\\', 'd')) - # Collapsing and stripping excess slashes. - check(('a', 'Z://b//c/', 'd/'), 'Z:', '\\', ('Z:\\', 'b', 'c', 'd')) - # UNC paths. - check(('a', '//b/c//', 'd'), '\\\\b\\c', '\\', ('\\\\b\\c\\', 'd')) - # Extended paths. - check(('//./c:',), '\\\\.\\c:', '', ('\\\\.\\c:',)) - check(('//?/c:/',), '\\\\?\\c:', '\\', ('\\\\?\\c:\\',)) - check(('//?/c:/a',), '\\\\?\\c:', '\\', ('\\\\?\\c:\\', 'a')) - check(('//?/c:/a', '/b'), '\\\\?\\c:', '\\', ('\\\\?\\c:\\', 'b')) - # Extended UNC paths (format is "\\?\UNC\server\share"). - check(('//?',), '\\\\?', '', ('\\\\?',)) - check(('//?/',), '\\\\?\\', '', ('\\\\?\\',)) - check(('//?/UNC',), '\\\\?\\UNC', '', ('\\\\?\\UNC',)) - check(('//?/UNC/',), '\\\\?\\UNC\\', '', ('\\\\?\\UNC\\',)) - check(('//?/UNC/b',), '\\\\?\\UNC\\b', '', ('\\\\?\\UNC\\b',)) - check(('//?/UNC/b/',), '\\\\?\\UNC\\b\\', '', ('\\\\?\\UNC\\b\\',)) - check(('//?/UNC/b/c',), '\\\\?\\UNC\\b\\c', '\\', ('\\\\?\\UNC\\b\\c\\',)) - check(('//?/UNC/b/c/',), '\\\\?\\UNC\\b\\c', '\\', ('\\\\?\\UNC\\b\\c\\',)) - check(('//?/UNC/b/c/d',), '\\\\?\\UNC\\b\\c', '\\', ('\\\\?\\UNC\\b\\c\\', 'd')) - # UNC device paths - check(('//./BootPartition/',), '\\\\.\\BootPartition', '\\', ('\\\\.\\BootPartition\\',)) - check(('//?/BootPartition/',), '\\\\?\\BootPartition', '\\', ('\\\\?\\BootPartition\\',)) - check(('//./PhysicalDrive0',), '\\\\.\\PhysicalDrive0', '', ('\\\\.\\PhysicalDrive0',)) - check(('//?/Volume{}/',), '\\\\?\\Volume{}', '\\', ('\\\\?\\Volume{}\\',)) - check(('//./nul',), '\\\\.\\nul', '', ('\\\\.\\nul',)) - # Second part has a root but not drive. - check(('a', '/b', 'c'), '', '\\', ('\\', 'b', 'c')) - check(('Z:/a', '/b', 'c'), 'Z:', '\\', ('Z:\\', 'b', 'c')) - check(('//?/Z:/a', '/b', 'c'), '\\\\?\\Z:', '\\', ('\\\\?\\Z:\\', 'b', 'c')) - # Joining with the same drive => the first path is appended to if - # the second path is relative. - check(('c:/a/b', 'c:x/y'), 'c:', '\\', ('c:\\', 'a', 'b', 'x', 'y')) - check(('c:/a/b', 'c:/x/y'), 'c:', '\\', ('c:\\', 'x', 'y')) - # Paths to files with NTFS alternate data streams - check(('./c:s',), '', '', ('c:s',)) - check(('cc:s',), '', '', ('cc:s',)) - check(('C:c:s',), 'C:', '', ('C:', 'c:s')) - check(('C:/c:s',), 'C:', '\\', ('C:\\', 'c:s')) - check(('D:a', './c:b'), 'D:', '', ('D:', 'a', 'c:b')) - check(('D:/a', './c:b'), 'D:', '\\', ('D:\\', 'a', 'c:b')) - - def test_str(self): - p = self.cls('a/b/c') - self.assertEqual(str(p), 'a\\b\\c') - p = self.cls('c:/a/b/c') - self.assertEqual(str(p), 'c:\\a\\b\\c') - p = self.cls('//a/b') - self.assertEqual(str(p), '\\\\a\\b\\') - p = self.cls('//a/b/c') - self.assertEqual(str(p), '\\\\a\\b\\c') - p = self.cls('//a/b/c/d') - self.assertEqual(str(p), '\\\\a\\b\\c\\d') - - def test_str_subclass(self): - self._check_str_subclass('.\\a:b') - self._check_str_subclass('c:') - self._check_str_subclass('c:a') - self._check_str_subclass('c:a\\b.txt') - self._check_str_subclass('c:\\') - self._check_str_subclass('c:\\a') - self._check_str_subclass('c:\\a\\b.txt') - self._check_str_subclass('\\\\some\\share') - self._check_str_subclass('\\\\some\\share\\a') - self._check_str_subclass('\\\\some\\share\\a\\b.txt') - - def test_eq(self): - P = self.cls - self.assertEqual(P('c:a/b'), P('c:a/b')) - self.assertEqual(P('c:a/b'), P('c:', 'a', 'b')) - self.assertNotEqual(P('c:a/b'), P('d:a/b')) - self.assertNotEqual(P('c:a/b'), P('c:/a/b')) - self.assertNotEqual(P('/a/b'), P('c:/a/b')) - # Case-insensitivity. - self.assertEqual(P('a/B'), P('A/b')) - self.assertEqual(P('C:a/B'), P('c:A/b')) - self.assertEqual(P('//Some/SHARE/a/B'), P('//somE/share/A/b')) - self.assertEqual(P('\u0130'), P('i\u0307')) - - def test_as_uri(self): - P = self.cls - with self.assertRaises(ValueError): - P('/a/b').as_uri() - with self.assertRaises(ValueError): - P('c:a/b').as_uri() - self.assertEqual(P('c:/').as_uri(), 'file:///c:/') - self.assertEqual(P('c:/a/b.c').as_uri(), 'file:///c:/a/b.c') - self.assertEqual(P('c:/a/b%#c').as_uri(), 'file:///c:/a/b%25%23c') - self.assertEqual(P('c:/a/b\xe9').as_uri(), 'file:///c:/a/b%C3%A9') - self.assertEqual(P('//some/share/').as_uri(), 'file://some/share/') - self.assertEqual(P('//some/share/a/b.c').as_uri(), - 'file://some/share/a/b.c') - self.assertEqual(P('//some/share/a/b%#c\xe9').as_uri(), - 'file://some/share/a/b%25%23c%C3%A9') - - def test_match(self): - P = self.cls - # Absolute patterns. - self.assertTrue(P('c:/b.py').match('*:/*.py')) - self.assertTrue(P('c:/b.py').match('c:/*.py')) - self.assertFalse(P('d:/b.py').match('c:/*.py')) # wrong drive - self.assertFalse(P('b.py').match('/*.py')) - self.assertFalse(P('b.py').match('c:*.py')) - self.assertFalse(P('b.py').match('c:/*.py')) - self.assertFalse(P('c:b.py').match('/*.py')) - self.assertFalse(P('c:b.py').match('c:/*.py')) - self.assertFalse(P('/b.py').match('c:*.py')) - self.assertFalse(P('/b.py').match('c:/*.py')) - # UNC patterns. - self.assertTrue(P('//some/share/a.py').match('//*/*/*.py')) - self.assertTrue(P('//some/share/a.py').match('//some/share/*.py')) - self.assertFalse(P('//other/share/a.py').match('//some/share/*.py')) - self.assertFalse(P('//some/share/a/b.py').match('//some/share/*.py')) - # Case-insensitivity. - self.assertTrue(P('B.py').match('b.PY')) - self.assertTrue(P('c:/a/B.Py').match('C:/A/*.pY')) - self.assertTrue(P('//Some/Share/B.Py').match('//somE/sharE/*.pY')) - # Path anchor doesn't match pattern anchor - self.assertFalse(P('c:/b.py').match('/*.py')) # 'c:/' vs '/' - self.assertFalse(P('c:/b.py').match('c:*.py')) # 'c:/' vs 'c:' - self.assertFalse(P('//some/share/a.py').match('/*.py')) # '//some/share/' vs '/' - - def test_ordering_common(self): - # Case-insensitivity. - def assertOrderedEqual(a, b): - self.assertLessEqual(a, b) - self.assertGreaterEqual(b, a) - P = self.cls - p = P('c:A/b') - q = P('C:a/B') - assertOrderedEqual(p, q) - self.assertFalse(p < q) - self.assertFalse(p > q) - p = P('//some/Share/A/b') - q = P('//Some/SHARE/a/B') - assertOrderedEqual(p, q) - self.assertFalse(p < q) - self.assertFalse(p > q) - - def test_parts(self): - P = self.cls - p = P('c:a/b') - parts = p.parts - self.assertEqual(parts, ('c:', 'a', 'b')) - p = P('c:/a/b') - parts = p.parts - self.assertEqual(parts, ('c:\\', 'a', 'b')) - p = P('//a/b/c/d') - parts = p.parts - self.assertEqual(parts, ('\\\\a\\b\\', 'c', 'd')) - - def test_parent(self): - # Anchored - P = self.cls - p = P('z:a/b/c') - self.assertEqual(p.parent, P('z:a/b')) - self.assertEqual(p.parent.parent, P('z:a')) - self.assertEqual(p.parent.parent.parent, P('z:')) - self.assertEqual(p.parent.parent.parent.parent, P('z:')) - p = P('z:/a/b/c') - self.assertEqual(p.parent, P('z:/a/b')) - self.assertEqual(p.parent.parent, P('z:/a')) - self.assertEqual(p.parent.parent.parent, P('z:/')) - self.assertEqual(p.parent.parent.parent.parent, P('z:/')) - p = P('//a/b/c/d') - self.assertEqual(p.parent, P('//a/b/c')) - self.assertEqual(p.parent.parent, P('//a/b')) - self.assertEqual(p.parent.parent.parent, P('//a/b')) - - def test_parents(self): - # Anchored - P = self.cls - p = P('z:a/b/') - par = p.parents - self.assertEqual(len(par), 2) - self.assertEqual(par[0], P('z:a')) - self.assertEqual(par[1], P('z:')) - self.assertEqual(par[0:1], (P('z:a'),)) - self.assertEqual(par[:-1], (P('z:a'),)) - self.assertEqual(par[:2], (P('z:a'), P('z:'))) - self.assertEqual(par[1:], (P('z:'),)) - self.assertEqual(par[::2], (P('z:a'),)) - self.assertEqual(par[::-1], (P('z:'), P('z:a'))) - self.assertEqual(list(par), [P('z:a'), P('z:')]) - with self.assertRaises(IndexError): - par[2] - p = P('z:/a/b/') - par = p.parents - self.assertEqual(len(par), 2) - self.assertEqual(par[0], P('z:/a')) - self.assertEqual(par[1], P('z:/')) - self.assertEqual(par[0:1], (P('z:/a'),)) - self.assertEqual(par[0:-1], (P('z:/a'),)) - self.assertEqual(par[:2], (P('z:/a'), P('z:/'))) - self.assertEqual(par[1:], (P('z:/'),)) - self.assertEqual(par[::2], (P('z:/a'),)) - self.assertEqual(par[::-1], (P('z:/'), P('z:/a'),)) - self.assertEqual(list(par), [P('z:/a'), P('z:/')]) - with self.assertRaises(IndexError): - par[2] - p = P('//a/b/c/d') - par = p.parents - self.assertEqual(len(par), 2) - self.assertEqual(par[0], P('//a/b/c')) - self.assertEqual(par[1], P('//a/b')) - self.assertEqual(par[0:1], (P('//a/b/c'),)) - self.assertEqual(par[0:-1], (P('//a/b/c'),)) - self.assertEqual(par[:2], (P('//a/b/c'), P('//a/b'))) - self.assertEqual(par[1:], (P('//a/b'),)) - self.assertEqual(par[::2], (P('//a/b/c'),)) - self.assertEqual(par[::-1], (P('//a/b'), P('//a/b/c'))) - self.assertEqual(list(par), [P('//a/b/c'), P('//a/b')]) - with self.assertRaises(IndexError): - par[2] - - def test_drive(self): - P = self.cls - self.assertEqual(P('c:').drive, 'c:') - self.assertEqual(P('c:a/b').drive, 'c:') - self.assertEqual(P('c:/').drive, 'c:') - self.assertEqual(P('c:/a/b/').drive, 'c:') - self.assertEqual(P('//a/b').drive, '\\\\a\\b') - self.assertEqual(P('//a/b/').drive, '\\\\a\\b') - self.assertEqual(P('//a/b/c/d').drive, '\\\\a\\b') - self.assertEqual(P('./c:a').drive, '') - - def test_root(self): - P = self.cls - self.assertEqual(P('c:').root, '') - self.assertEqual(P('c:a/b').root, '') - self.assertEqual(P('c:/').root, '\\') - self.assertEqual(P('c:/a/b/').root, '\\') - self.assertEqual(P('//a/b').root, '\\') - self.assertEqual(P('//a/b/').root, '\\') - self.assertEqual(P('//a/b/c/d').root, '\\') - - def test_anchor(self): - P = self.cls - self.assertEqual(P('c:').anchor, 'c:') - self.assertEqual(P('c:a/b').anchor, 'c:') - self.assertEqual(P('c:/').anchor, 'c:\\') - self.assertEqual(P('c:/a/b/').anchor, 'c:\\') - self.assertEqual(P('//a/b').anchor, '\\\\a\\b\\') - self.assertEqual(P('//a/b/').anchor, '\\\\a\\b\\') - self.assertEqual(P('//a/b/c/d').anchor, '\\\\a\\b\\') - - def test_name(self): - P = self.cls - self.assertEqual(P('c:').name, '') - self.assertEqual(P('c:/').name, '') - self.assertEqual(P('c:a/b').name, 'b') - self.assertEqual(P('c:/a/b').name, 'b') - self.assertEqual(P('c:a/b.py').name, 'b.py') - self.assertEqual(P('c:/a/b.py').name, 'b.py') - self.assertEqual(P('//My.py/Share.php').name, '') - self.assertEqual(P('//My.py/Share.php/a/b').name, 'b') - - def test_suffix(self): - P = self.cls - self.assertEqual(P('c:').suffix, '') - self.assertEqual(P('c:/').suffix, '') - self.assertEqual(P('c:a/b').suffix, '') - self.assertEqual(P('c:/a/b').suffix, '') - self.assertEqual(P('c:a/b.py').suffix, '.py') - self.assertEqual(P('c:/a/b.py').suffix, '.py') - self.assertEqual(P('c:a/.hgrc').suffix, '') - self.assertEqual(P('c:/a/.hgrc').suffix, '') - self.assertEqual(P('c:a/.hg.rc').suffix, '.rc') - self.assertEqual(P('c:/a/.hg.rc').suffix, '.rc') - self.assertEqual(P('c:a/b.tar.gz').suffix, '.gz') - self.assertEqual(P('c:/a/b.tar.gz').suffix, '.gz') - self.assertEqual(P('c:a/Some name. Ending with a dot.').suffix, '') - self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffix, '') - self.assertEqual(P('//My.py/Share.php').suffix, '') - self.assertEqual(P('//My.py/Share.php/a/b').suffix, '') - - def test_suffixes(self): - P = self.cls - self.assertEqual(P('c:').suffixes, []) - self.assertEqual(P('c:/').suffixes, []) - self.assertEqual(P('c:a/b').suffixes, []) - self.assertEqual(P('c:/a/b').suffixes, []) - self.assertEqual(P('c:a/b.py').suffixes, ['.py']) - self.assertEqual(P('c:/a/b.py').suffixes, ['.py']) - self.assertEqual(P('c:a/.hgrc').suffixes, []) - self.assertEqual(P('c:/a/.hgrc').suffixes, []) - self.assertEqual(P('c:a/.hg.rc').suffixes, ['.rc']) - self.assertEqual(P('c:/a/.hg.rc').suffixes, ['.rc']) - self.assertEqual(P('c:a/b.tar.gz').suffixes, ['.tar', '.gz']) - self.assertEqual(P('c:/a/b.tar.gz').suffixes, ['.tar', '.gz']) - self.assertEqual(P('//My.py/Share.php').suffixes, []) - self.assertEqual(P('//My.py/Share.php/a/b').suffixes, []) - self.assertEqual(P('c:a/Some name. Ending with a dot.').suffixes, []) - self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffixes, []) - - def test_stem(self): - P = self.cls - self.assertEqual(P('c:').stem, '') - self.assertEqual(P('c:.').stem, '') - self.assertEqual(P('c:..').stem, '..') - self.assertEqual(P('c:/').stem, '') - self.assertEqual(P('c:a/b').stem, 'b') - self.assertEqual(P('c:a/b.py').stem, 'b') - self.assertEqual(P('c:a/.hgrc').stem, '.hgrc') - self.assertEqual(P('c:a/.hg.rc').stem, '.hg') - self.assertEqual(P('c:a/b.tar.gz').stem, 'b.tar') - self.assertEqual(P('c:a/Some name. Ending with a dot.').stem, - 'Some name. Ending with a dot.') - - def test_with_name(self): - P = self.cls - self.assertEqual(P('c:a/b').with_name('d.xml'), P('c:a/d.xml')) - self.assertEqual(P('c:/a/b').with_name('d.xml'), P('c:/a/d.xml')) - self.assertEqual(P('c:a/Dot ending.').with_name('d.xml'), P('c:a/d.xml')) - self.assertEqual(P('c:/a/Dot ending.').with_name('d.xml'), P('c:/a/d.xml')) - self.assertRaises(ValueError, P('c:').with_name, 'd.xml') - self.assertRaises(ValueError, P('c:/').with_name, 'd.xml') - self.assertRaises(ValueError, P('//My/Share').with_name, 'd.xml') - self.assertEqual(str(P('a').with_name('d:')), '.\\d:') - self.assertEqual(str(P('a').with_name('d:e')), '.\\d:e') - self.assertEqual(P('c:a/b').with_name('d:'), P('c:a/d:')) - self.assertEqual(P('c:a/b').with_name('d:e'), P('c:a/d:e')) - self.assertRaises(ValueError, P('c:a/b').with_name, 'd:/e') - self.assertRaises(ValueError, P('c:a/b').with_name, '//My/Share') - - def test_with_stem(self): - P = self.cls - self.assertEqual(P('c:a/b').with_stem('d'), P('c:a/d')) - self.assertEqual(P('c:/a/b').with_stem('d'), P('c:/a/d')) - self.assertEqual(P('c:a/Dot ending.').with_stem('d'), P('c:a/d')) - self.assertEqual(P('c:/a/Dot ending.').with_stem('d'), P('c:/a/d')) - self.assertRaises(ValueError, P('c:').with_stem, 'd') - self.assertRaises(ValueError, P('c:/').with_stem, 'd') - self.assertRaises(ValueError, P('//My/Share').with_stem, 'd') - self.assertEqual(str(P('a').with_stem('d:')), '.\\d:') - self.assertEqual(str(P('a').with_stem('d:e')), '.\\d:e') - self.assertEqual(P('c:a/b').with_stem('d:'), P('c:a/d:')) - self.assertEqual(P('c:a/b').with_stem('d:e'), P('c:a/d:e')) - self.assertRaises(ValueError, P('c:a/b').with_stem, 'd:/e') - self.assertRaises(ValueError, P('c:a/b').with_stem, '//My/Share') - - def test_with_suffix(self): - P = self.cls - self.assertEqual(P('c:a/b').with_suffix('.gz'), P('c:a/b.gz')) - self.assertEqual(P('c:/a/b').with_suffix('.gz'), P('c:/a/b.gz')) - self.assertEqual(P('c:a/b.py').with_suffix('.gz'), P('c:a/b.gz')) - self.assertEqual(P('c:/a/b.py').with_suffix('.gz'), P('c:/a/b.gz')) - # Path doesn't have a "filename" component. - self.assertRaises(ValueError, P('').with_suffix, '.gz') - self.assertRaises(ValueError, P('.').with_suffix, '.gz') - self.assertRaises(ValueError, P('/').with_suffix, '.gz') - self.assertRaises(ValueError, P('//My/Share').with_suffix, '.gz') - # Invalid suffix. - self.assertRaises(ValueError, P('c:a/b').with_suffix, 'gz') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '/') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '\\') - self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c:') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '/.gz') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '\\.gz') - self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c:.gz') - self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c/d') - self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c\\d') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c/d') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c\\d') - - def test_relative_to(self): - P = self.cls - p = P('C:Foo/Bar') - self.assertEqual(p.relative_to(P('c:')), P('Foo/Bar')) - self.assertEqual(p.relative_to('c:'), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('c:foO')), P('Bar')) - self.assertEqual(p.relative_to('c:foO'), P('Bar')) - self.assertEqual(p.relative_to('c:foO/'), P('Bar')) - self.assertEqual(p.relative_to(P('c:foO/baR')), P()) - self.assertEqual(p.relative_to('c:foO/baR'), P()) - self.assertEqual(p.relative_to(P('c:'), walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to('c:', walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('c:foO'), walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('c:foO', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('c:foO/', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to(P('c:foO/baR'), walk_up=True), P()) - self.assertEqual(p.relative_to('c:foO/baR', walk_up=True), P()) - self.assertEqual(p.relative_to(P('C:Foo/Bar/Baz'), walk_up=True), P('..')) - self.assertEqual(p.relative_to(P('C:Foo/Baz'), walk_up=True), P('../Bar')) - self.assertEqual(p.relative_to(P('C:Baz/Bar'), walk_up=True), P('../../Foo/Bar')) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, P()) - self.assertRaises(ValueError, p.relative_to, '') - self.assertRaises(ValueError, p.relative_to, P('d:')) - self.assertRaises(ValueError, p.relative_to, P('/')) - self.assertRaises(ValueError, p.relative_to, P('Foo')) - self.assertRaises(ValueError, p.relative_to, P('/Foo')) - self.assertRaises(ValueError, p.relative_to, P('C:/Foo')) - self.assertRaises(ValueError, p.relative_to, P('C:Foo/Bar/Baz')) - self.assertRaises(ValueError, p.relative_to, P('C:Foo/Baz')) - self.assertRaises(ValueError, p.relative_to, P(), walk_up=True) - self.assertRaises(ValueError, p.relative_to, '', walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('d:'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('/Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('C:/Foo'), walk_up=True) - p = P('C:/Foo/Bar') - self.assertEqual(p.relative_to(P('c:/')), P('Foo/Bar')) - self.assertEqual(p.relative_to('c:/'), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('c:/foO')), P('Bar')) - self.assertEqual(p.relative_to('c:/foO'), P('Bar')) - self.assertEqual(p.relative_to('c:/foO/'), P('Bar')) - self.assertEqual(p.relative_to(P('c:/foO/baR')), P()) - self.assertEqual(p.relative_to('c:/foO/baR'), P()) - self.assertEqual(p.relative_to(P('c:/'), walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to('c:/', walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('c:/foO'), walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('c:/foO', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('c:/foO/', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to(P('c:/foO/baR'), walk_up=True), P()) - self.assertEqual(p.relative_to('c:/foO/baR', walk_up=True), P()) - self.assertEqual(p.relative_to('C:/Baz', walk_up=True), P('../Foo/Bar')) - self.assertEqual(p.relative_to('C:/Foo/Bar/Baz', walk_up=True), P('..')) - self.assertEqual(p.relative_to('C:/Foo/Baz', walk_up=True), P('../Bar')) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, 'c:') - self.assertRaises(ValueError, p.relative_to, P('c:')) - self.assertRaises(ValueError, p.relative_to, P('C:/Baz')) - self.assertRaises(ValueError, p.relative_to, P('C:/Foo/Bar/Baz')) - self.assertRaises(ValueError, p.relative_to, P('C:/Foo/Baz')) - self.assertRaises(ValueError, p.relative_to, P('C:Foo')) - self.assertRaises(ValueError, p.relative_to, P('d:')) - self.assertRaises(ValueError, p.relative_to, P('d:/')) - self.assertRaises(ValueError, p.relative_to, P('/')) - self.assertRaises(ValueError, p.relative_to, P('/Foo')) - self.assertRaises(ValueError, p.relative_to, P('//C/Foo')) - self.assertRaises(ValueError, p.relative_to, 'c:', walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('c:'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('C:Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('d:'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('d:/'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('/Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('//C/Foo'), walk_up=True) - # UNC paths. - p = P('//Server/Share/Foo/Bar') - self.assertEqual(p.relative_to(P('//sErver/sHare')), P('Foo/Bar')) - self.assertEqual(p.relative_to('//sErver/sHare'), P('Foo/Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/'), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('//sErver/sHare/Foo')), P('Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/Foo'), P('Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/Foo/'), P('Bar')) - self.assertEqual(p.relative_to(P('//sErver/sHare/Foo/Bar')), P()) - self.assertEqual(p.relative_to('//sErver/sHare/Foo/Bar'), P()) - self.assertEqual(p.relative_to(P('//sErver/sHare'), walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to('//sErver/sHare', walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/', walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('//sErver/sHare/Foo'), walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/Foo', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/Foo/', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to(P('//sErver/sHare/Foo/Bar'), walk_up=True), P()) - self.assertEqual(p.relative_to('//sErver/sHare/Foo/Bar', walk_up=True), P()) - self.assertEqual(p.relative_to(P('//sErver/sHare/bar'), walk_up=True), P('../Foo/Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/bar', walk_up=True), P('../Foo/Bar')) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, P('/Server/Share/Foo')) - self.assertRaises(ValueError, p.relative_to, P('c:/Server/Share/Foo')) - self.assertRaises(ValueError, p.relative_to, P('//z/Share/Foo')) - self.assertRaises(ValueError, p.relative_to, P('//Server/z/Foo')) - self.assertRaises(ValueError, p.relative_to, P('/Server/Share/Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('c:/Server/Share/Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('//z/Share/Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('//Server/z/Foo'), walk_up=True) - - def test_is_relative_to(self): - P = self.cls - p = P('C:Foo/Bar') - self.assertTrue(p.is_relative_to(P('c:'))) - self.assertTrue(p.is_relative_to('c:')) - self.assertTrue(p.is_relative_to(P('c:foO'))) - self.assertTrue(p.is_relative_to('c:foO')) - self.assertTrue(p.is_relative_to('c:foO/')) - self.assertTrue(p.is_relative_to(P('c:foO/baR'))) - self.assertTrue(p.is_relative_to('c:foO/baR')) - # Unrelated paths. - self.assertFalse(p.is_relative_to(P())) - self.assertFalse(p.is_relative_to('')) - self.assertFalse(p.is_relative_to(P('d:'))) - self.assertFalse(p.is_relative_to(P('/'))) - self.assertFalse(p.is_relative_to(P('Foo'))) - self.assertFalse(p.is_relative_to(P('/Foo'))) - self.assertFalse(p.is_relative_to(P('C:/Foo'))) - self.assertFalse(p.is_relative_to(P('C:Foo/Bar/Baz'))) - self.assertFalse(p.is_relative_to(P('C:Foo/Baz'))) - p = P('C:/Foo/Bar') - self.assertTrue(p.is_relative_to(P('c:/'))) - self.assertTrue(p.is_relative_to(P('c:/foO'))) - self.assertTrue(p.is_relative_to('c:/foO/')) - self.assertTrue(p.is_relative_to(P('c:/foO/baR'))) - self.assertTrue(p.is_relative_to('c:/foO/baR')) - # Unrelated paths. - self.assertFalse(p.is_relative_to('c:')) - self.assertFalse(p.is_relative_to(P('C:/Baz'))) - self.assertFalse(p.is_relative_to(P('C:/Foo/Bar/Baz'))) - self.assertFalse(p.is_relative_to(P('C:/Foo/Baz'))) - self.assertFalse(p.is_relative_to(P('C:Foo'))) - self.assertFalse(p.is_relative_to(P('d:'))) - self.assertFalse(p.is_relative_to(P('d:/'))) - self.assertFalse(p.is_relative_to(P('/'))) - self.assertFalse(p.is_relative_to(P('/Foo'))) - self.assertFalse(p.is_relative_to(P('//C/Foo'))) - # UNC paths. - p = P('//Server/Share/Foo/Bar') - self.assertTrue(p.is_relative_to(P('//sErver/sHare'))) - self.assertTrue(p.is_relative_to('//sErver/sHare')) - self.assertTrue(p.is_relative_to('//sErver/sHare/')) - self.assertTrue(p.is_relative_to(P('//sErver/sHare/Foo'))) - self.assertTrue(p.is_relative_to('//sErver/sHare/Foo')) - self.assertTrue(p.is_relative_to('//sErver/sHare/Foo/')) - self.assertTrue(p.is_relative_to(P('//sErver/sHare/Foo/Bar'))) - self.assertTrue(p.is_relative_to('//sErver/sHare/Foo/Bar')) - # Unrelated paths. - self.assertFalse(p.is_relative_to(P('/Server/Share/Foo'))) - self.assertFalse(p.is_relative_to(P('c:/Server/Share/Foo'))) - self.assertFalse(p.is_relative_to(P('//z/Share/Foo'))) - self.assertFalse(p.is_relative_to(P('//Server/z/Foo'))) - - def test_is_absolute(self): - P = self.cls - # Under NT, only paths with both a drive and a root are absolute. - self.assertFalse(P().is_absolute()) - self.assertFalse(P('a').is_absolute()) - self.assertFalse(P('a/b/').is_absolute()) - self.assertFalse(P('/').is_absolute()) - self.assertFalse(P('/a').is_absolute()) - self.assertFalse(P('/a/b/').is_absolute()) - self.assertFalse(P('c:').is_absolute()) - self.assertFalse(P('c:a').is_absolute()) - self.assertFalse(P('c:a/b/').is_absolute()) - self.assertTrue(P('c:/').is_absolute()) - self.assertTrue(P('c:/a').is_absolute()) - self.assertTrue(P('c:/a/b/').is_absolute()) - # UNC paths are absolute by definition. - self.assertTrue(P('//a/b').is_absolute()) - self.assertTrue(P('//a/b/').is_absolute()) - self.assertTrue(P('//a/b/c').is_absolute()) - self.assertTrue(P('//a/b/c/d').is_absolute()) - - def test_join(self): - P = self.cls - p = P('C:/a/b') - pp = p.joinpath('x/y') - self.assertEqual(pp, P('C:/a/b/x/y')) - pp = p.joinpath('/x/y') - self.assertEqual(pp, P('C:/x/y')) - # Joining with a different drive => the first path is ignored, even - # if the second path is relative. - pp = p.joinpath('D:x/y') - self.assertEqual(pp, P('D:x/y')) - pp = p.joinpath('D:/x/y') - self.assertEqual(pp, P('D:/x/y')) - pp = p.joinpath('//host/share/x/y') - self.assertEqual(pp, P('//host/share/x/y')) - # Joining with the same drive => the first path is appended to if - # the second path is relative. - pp = p.joinpath('c:x/y') - self.assertEqual(pp, P('C:/a/b/x/y')) - pp = p.joinpath('c:/x/y') - self.assertEqual(pp, P('C:/x/y')) - # Joining with files with NTFS data streams => the filename should - # not be parsed as a drive letter - pp = p.joinpath(P('./d:s')) - self.assertEqual(pp, P('C:/a/b/d:s')) - pp = p.joinpath(P('./dd:s')) - self.assertEqual(pp, P('C:/a/b/dd:s')) - pp = p.joinpath(P('E:d:s')) - self.assertEqual(pp, P('E:d:s')) - # Joining onto a UNC path with no root - pp = P('//').joinpath('server') - self.assertEqual(pp, P('//server')) - pp = P('//server').joinpath('share') - self.assertEqual(pp, P('//server/share')) - pp = P('//./BootPartition').joinpath('Windows') - self.assertEqual(pp, P('//./BootPartition/Windows')) - - def test_div(self): - # Basically the same as joinpath(). - P = self.cls - p = P('C:/a/b') - self.assertEqual(p / 'x/y', P('C:/a/b/x/y')) - self.assertEqual(p / 'x' / 'y', P('C:/a/b/x/y')) - self.assertEqual(p / '/x/y', P('C:/x/y')) - self.assertEqual(p / '/x' / 'y', P('C:/x/y')) - # Joining with a different drive => the first path is ignored, even - # if the second path is relative. - self.assertEqual(p / 'D:x/y', P('D:x/y')) - self.assertEqual(p / 'D:' / 'x/y', P('D:x/y')) - self.assertEqual(p / 'D:/x/y', P('D:/x/y')) - self.assertEqual(p / 'D:' / '/x/y', P('D:/x/y')) - self.assertEqual(p / '//host/share/x/y', P('//host/share/x/y')) - # Joining with the same drive => the first path is appended to if - # the second path is relative. - self.assertEqual(p / 'c:x/y', P('C:/a/b/x/y')) - self.assertEqual(p / 'c:/x/y', P('C:/x/y')) - # Joining with files with NTFS data streams => the filename should - # not be parsed as a drive letter - self.assertEqual(p / P('./d:s'), P('C:/a/b/d:s')) - self.assertEqual(p / P('./dd:s'), P('C:/a/b/dd:s')) - self.assertEqual(p / P('E:d:s'), P('E:d:s')) - - def test_is_reserved(self): - P = self.cls - self.assertIs(False, P('').is_reserved()) - self.assertIs(False, P('/').is_reserved()) - self.assertIs(False, P('/foo/bar').is_reserved()) - # UNC paths are never reserved. - self.assertIs(False, P('//my/share/nul/con/aux').is_reserved()) - # Case-insensitive DOS-device names are reserved. - self.assertIs(True, P('nul').is_reserved()) - self.assertIs(True, P('aux').is_reserved()) - self.assertIs(True, P('prn').is_reserved()) - self.assertIs(True, P('con').is_reserved()) - self.assertIs(True, P('conin$').is_reserved()) - self.assertIs(True, P('conout$').is_reserved()) - # COM/LPT + 1-9 or + superscript 1-3 are reserved. - self.assertIs(True, P('COM1').is_reserved()) - self.assertIs(True, P('LPT9').is_reserved()) - self.assertIs(True, P('com\xb9').is_reserved()) - self.assertIs(True, P('com\xb2').is_reserved()) - self.assertIs(True, P('lpt\xb3').is_reserved()) - # DOS-device name mataching ignores characters after a dot or - # a colon and also ignores trailing spaces. - self.assertIs(True, P('NUL.txt').is_reserved()) - self.assertIs(True, P('PRN ').is_reserved()) - self.assertIs(True, P('AUX .txt').is_reserved()) - self.assertIs(True, P('COM1:bar').is_reserved()) - self.assertIs(True, P('LPT9 :bar').is_reserved()) - # DOS-device names are only matched at the beginning - # of a path component. - self.assertIs(False, P('bar.com9').is_reserved()) - self.assertIs(False, P('bar.lpt9').is_reserved()) - # Only the last path component matters. - self.assertIs(True, P('c:/baz/con/NUL').is_reserved()) - self.assertIs(False, P('c:/NUL/con/baz').is_reserved()) - -class PurePathTest(_BasePurePathTest, unittest.TestCase): - cls = pathlib.PurePath - - def test_concrete_class(self): - p = self.cls('a') - self.assertIs(type(p), - pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath) - - def test_different_flavours_unequal(self): - p = pathlib.PurePosixPath('a') - q = pathlib.PureWindowsPath('a') - self.assertNotEqual(p, q) - - def test_different_flavours_unordered(self): - p = pathlib.PurePosixPath('a') - q = pathlib.PureWindowsPath('a') - with self.assertRaises(TypeError): - p < q - with self.assertRaises(TypeError): - p <= q - with self.assertRaises(TypeError): - p > q - with self.assertRaises(TypeError): - p >= q - - -# -# Tests for the concrete classes. -# - -# Make sure any symbolic links in the base test path are resolved. -BASE = os.path.realpath(TESTFN) -join = lambda *x: os.path.join(BASE, *x) -rel_join = lambda *x: os.path.join(TESTFN, *x) - -only_nt = unittest.skipIf(os.name != 'nt', - 'test requires a Windows-compatible system') -only_posix = unittest.skipIf(os.name == 'nt', - 'test requires a POSIX-compatible system') - -@only_posix -class PosixPathAsPureTest(PurePosixPathTest): - cls = pathlib.PosixPath - -@only_nt -class WindowsPathAsPureTest(PureWindowsPathTest): - cls = pathlib.WindowsPath - - def test_owner(self): - P = self.cls - with self.assertRaises(NotImplementedError): - P('c:/').owner() - - def test_group(self): - P = self.cls - with self.assertRaises(NotImplementedError): - P('c:/').group() - - -class _BasePathTest(object): - """Tests for the FS-accessing functionalities of the Path classes.""" - - # (BASE) - # | - # |-- brokenLink -> non-existing - # |-- dirA - # | `-- linkC -> ../dirB - # |-- dirB - # | |-- fileB - # | `-- linkD -> ../dirB - # |-- dirC - # | |-- dirD - # | | `-- fileD - # | `-- fileC - # | `-- novel.txt - # |-- dirE # No permissions - # |-- fileA - # |-- linkA -> fileA - # |-- linkB -> dirB - # `-- brokenLinkLoop -> brokenLinkLoop - # - - def setUp(self): - def cleanup(): - os.chmod(join('dirE'), 0o777) - os_helper.rmtree(BASE) - self.addCleanup(cleanup) - os.mkdir(BASE) - os.mkdir(join('dirA')) - os.mkdir(join('dirB')) - os.mkdir(join('dirC')) - os.mkdir(join('dirC', 'dirD')) - os.mkdir(join('dirE')) - with open(join('fileA'), 'wb') as f: - f.write(b"this is file A\n") - with open(join('dirB', 'fileB'), 'wb') as f: - f.write(b"this is file B\n") - with open(join('dirC', 'fileC'), 'wb') as f: - f.write(b"this is file C\n") - with open(join('dirC', 'novel.txt'), 'wb') as f: - f.write(b"this is a novel\n") - with open(join('dirC', 'dirD', 'fileD'), 'wb') as f: - f.write(b"this is file D\n") - os.chmod(join('dirE'), 0) - if os_helper.can_symlink(): - # Relative symlinks. - os.symlink('fileA', join('linkA')) - os.symlink('non-existing', join('brokenLink')) - self.dirlink('dirB', join('linkB')) - self.dirlink(os.path.join('..', 'dirB'), join('dirA', 'linkC')) - # This one goes upwards, creating a loop. - self.dirlink(os.path.join('..', 'dirB'), join('dirB', 'linkD')) - # Broken symlink (pointing to itself). - os.symlink('brokenLinkLoop', join('brokenLinkLoop')) - - if os.name == 'nt': - # Workaround for http://bugs.python.org/issue13772. - def dirlink(self, src, dest): - os.symlink(src, dest, target_is_directory=True) - else: - def dirlink(self, src, dest): - os.symlink(src, dest) - - def assertSame(self, path_a, path_b): - self.assertTrue(os.path.samefile(str(path_a), str(path_b)), - "%r and %r don't point to the same file" % - (path_a, path_b)) - - def assertFileNotFound(self, func, *args, **kwargs): - with self.assertRaises(FileNotFoundError) as cm: - func(*args, **kwargs) - self.assertEqual(cm.exception.errno, errno.ENOENT) - - def assertEqualNormCase(self, path_a, path_b): - self.assertEqual(os.path.normcase(path_a), os.path.normcase(path_b)) - - def _test_cwd(self, p): - q = self.cls(os.getcwd()) - self.assertEqual(p, q) - self.assertEqualNormCase(str(p), str(q)) - self.assertIs(type(p), type(q)) - self.assertTrue(p.is_absolute()) - - def test_cwd(self): - p = self.cls.cwd() - self._test_cwd(p) - - def test_absolute_common(self): - P = self.cls - - with mock.patch("os.getcwd") as getcwd: - getcwd.return_value = BASE - - # Simple relative paths. - self.assertEqual(str(P().absolute()), BASE) - self.assertEqual(str(P('.').absolute()), BASE) - self.assertEqual(str(P('a').absolute()), os.path.join(BASE, 'a')) - self.assertEqual(str(P('a', 'b', 'c').absolute()), os.path.join(BASE, 'a', 'b', 'c')) - - # Symlinks should not be resolved. - self.assertEqual(str(P('linkB', 'fileB').absolute()), os.path.join(BASE, 'linkB', 'fileB')) - self.assertEqual(str(P('brokenLink').absolute()), os.path.join(BASE, 'brokenLink')) - self.assertEqual(str(P('brokenLinkLoop').absolute()), os.path.join(BASE, 'brokenLinkLoop')) - - # '..' entries should be preserved and not normalised. - self.assertEqual(str(P('..').absolute()), os.path.join(BASE, '..')) - self.assertEqual(str(P('a', '..').absolute()), os.path.join(BASE, 'a', '..')) - self.assertEqual(str(P('..', 'b').absolute()), os.path.join(BASE, '..', 'b')) - - def _test_home(self, p): - q = self.cls(os.path.expanduser('~')) - self.assertEqual(p, q) - self.assertEqualNormCase(str(p), str(q)) - self.assertIs(type(p), type(q)) - self.assertTrue(p.is_absolute()) - - @unittest.skipIf( - pwd is None, reason="Test requires pwd module to get homedir." - ) - def test_home(self): - with os_helper.EnvironmentVarGuard() as env: - self._test_home(self.cls.home()) - - env.clear() - env['USERPROFILE'] = os.path.join(BASE, 'userprofile') - self._test_home(self.cls.home()) - - # bpo-38883: ignore `HOME` when set on windows - env['HOME'] = os.path.join(BASE, 'home') - self._test_home(self.cls.home()) - - @unittest.skip("TODO: RUSTPYTHON; PyObject::set_slot index out of bounds") - def test_with_segments(self): - class P(_BasePurePathSubclass, self.cls): - pass - p = P(BASE, session_id=42) - self.assertEqual(42, p.absolute().session_id) - self.assertEqual(42, p.resolve().session_id) - if not is_wasi: # WASI has no user accounts. - self.assertEqual(42, p.with_segments('~').expanduser().session_id) - self.assertEqual(42, (p / 'fileA').rename(p / 'fileB').session_id) - self.assertEqual(42, (p / 'fileB').replace(p / 'fileA').session_id) - if os_helper.can_symlink(): - self.assertEqual(42, (p / 'linkA').readlink().session_id) - for path in p.iterdir(): - self.assertEqual(42, path.session_id) - for path in p.glob('*'): - self.assertEqual(42, path.session_id) - for path in p.rglob('*'): - self.assertEqual(42, path.session_id) - for dirpath, dirnames, filenames in p.walk(): - self.assertEqual(42, dirpath.session_id) - - def test_samefile(self): - fileA_path = os.path.join(BASE, 'fileA') - fileB_path = os.path.join(BASE, 'dirB', 'fileB') - p = self.cls(fileA_path) - pp = self.cls(fileA_path) - q = self.cls(fileB_path) - self.assertTrue(p.samefile(fileA_path)) - self.assertTrue(p.samefile(pp)) - self.assertFalse(p.samefile(fileB_path)) - self.assertFalse(p.samefile(q)) - # Test the non-existent file case - non_existent = os.path.join(BASE, 'foo') - r = self.cls(non_existent) - self.assertRaises(FileNotFoundError, p.samefile, r) - self.assertRaises(FileNotFoundError, p.samefile, non_existent) - self.assertRaises(FileNotFoundError, r.samefile, p) - self.assertRaises(FileNotFoundError, r.samefile, non_existent) - self.assertRaises(FileNotFoundError, r.samefile, r) - self.assertRaises(FileNotFoundError, r.samefile, non_existent) - - def test_empty_path(self): - # The empty path points to '.' - p = self.cls('') - self.assertEqual(p.stat(), os.stat('.')) - - @unittest.skipIf(is_wasi, "WASI has no user accounts.") - def test_expanduser_common(self): - P = self.cls - p = P('~') - self.assertEqual(p.expanduser(), P(os.path.expanduser('~'))) - p = P('foo') - self.assertEqual(p.expanduser(), p) - p = P('/~') - self.assertEqual(p.expanduser(), p) - p = P('../~') - self.assertEqual(p.expanduser(), p) - p = P(P('').absolute().anchor) / '~' - self.assertEqual(p.expanduser(), p) - p = P('~/a:b') - self.assertEqual(p.expanduser(), P(os.path.expanduser('~'), './a:b')) - - def test_exists(self): - P = self.cls - p = P(BASE) - self.assertIs(True, p.exists()) - self.assertIs(True, (p / 'dirA').exists()) - self.assertIs(True, (p / 'fileA').exists()) - self.assertIs(False, (p / 'fileA' / 'bah').exists()) - if os_helper.can_symlink(): - self.assertIs(True, (p / 'linkA').exists()) - self.assertIs(True, (p / 'linkB').exists()) - self.assertIs(True, (p / 'linkB' / 'fileB').exists()) - self.assertIs(False, (p / 'linkA' / 'bah').exists()) - self.assertIs(False, (p / 'brokenLink').exists()) - self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False)) - self.assertIs(False, (p / 'foo').exists()) - self.assertIs(False, P('/xyzzy').exists()) - self.assertIs(False, P(BASE + '\udfff').exists()) - self.assertIs(False, P(BASE + '\x00').exists()) - - def test_open_common(self): - p = self.cls(BASE) - with (p / 'fileA').open('r') as f: - self.assertIsInstance(f, io.TextIOBase) - self.assertEqual(f.read(), "this is file A\n") - with (p / 'fileA').open('rb') as f: - self.assertIsInstance(f, io.BufferedIOBase) - self.assertEqual(f.read().strip(), b"this is file A") - with (p / 'fileA').open('rb', buffering=0) as f: - self.assertIsInstance(f, io.RawIOBase) - self.assertEqual(f.read().strip(), b"this is file A") - - def test_read_write_bytes(self): - p = self.cls(BASE) - (p / 'fileA').write_bytes(b'abcdefg') - self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') - # Check that trying to write str does not truncate the file. - self.assertRaises(TypeError, (p / 'fileA').write_bytes, 'somestr') - self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') - - def test_read_write_text(self): - p = self.cls(BASE) - (p / 'fileA').write_text('äbcdefg', encoding='latin-1') - self.assertEqual((p / 'fileA').read_text( - encoding='utf-8', errors='ignore'), 'bcdefg') - # Check that trying to write bytes does not truncate the file. - self.assertRaises(TypeError, (p / 'fileA').write_text, b'somebytes') - self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg') - - def test_write_text_with_newlines(self): - p = self.cls(BASE) - # Check that `\n` character change nothing - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\n') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\nfghlk\n\rmnopq') - # Check that `\r` character replaces `\n` - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\rfghlk\r\rmnopq') - # Check that `\r\n` character replaces `\n` - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r\n') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\r\nfghlk\r\n\rmnopq') - # Check that no argument passed will change `\n` to `os.linesep` - os_linesep_byte = bytes(os.linesep, encoding='ascii') - (p / 'fileA').write_text('abcde\nfghlk\n\rmnopq') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde' + os_linesep_byte + b'fghlk' + os_linesep_byte + b'\rmnopq') - - def test_iterdir(self): - P = self.cls - p = P(BASE) - it = p.iterdir() - paths = set(it) - expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA'] - if os_helper.can_symlink(): - expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop'] - self.assertEqual(paths, { P(BASE, q) for q in expected }) - - @os_helper.skip_unless_symlink - def test_iterdir_symlink(self): - # __iter__ on a symlink to a directory. - P = self.cls - p = P(BASE, 'linkB') - paths = set(p.iterdir()) - expected = { P(BASE, 'linkB', q) for q in ['fileB', 'linkD'] } - self.assertEqual(paths, expected) - - def test_iterdir_nodir(self): - # __iter__ on something that is not a directory. - p = self.cls(BASE, 'fileA') - with self.assertRaises(OSError) as cm: - next(p.iterdir()) - # ENOENT or EINVAL under Windows, ENOTDIR otherwise - # (see issue #12802). - self.assertIn(cm.exception.errno, (errno.ENOTDIR, - errno.ENOENT, errno.EINVAL)) - - def test_glob_common(self): - def _check(glob, expected): - self.assertEqual(set(glob), { P(BASE, q) for q in expected }) - P = self.cls - p = P(BASE) - it = p.glob("fileA") - self.assertIsInstance(it, collections.abc.Iterator) - _check(it, ["fileA"]) - _check(p.glob("fileB"), []) - _check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"]) - if not os_helper.can_symlink(): - _check(p.glob("*A"), ['dirA', 'fileA']) - else: - _check(p.glob("*A"), ['dirA', 'fileA', 'linkA']) - if not os_helper.can_symlink(): - _check(p.glob("*B/*"), ['dirB/fileB']) - else: - _check(p.glob("*B/*"), ['dirB/fileB', 'dirB/linkD', - 'linkB/fileB', 'linkB/linkD']) - if not os_helper.can_symlink(): - _check(p.glob("*/fileB"), ['dirB/fileB']) - else: - _check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB']) - if os_helper.can_symlink(): - _check(p.glob("brokenLink"), ['brokenLink']) - - if not os_helper.can_symlink(): - _check(p.glob("*/"), ["dirA", "dirB", "dirC", "dirE"]) - else: - _check(p.glob("*/"), ["dirA", "dirB", "dirC", "dirE", "linkB"]) - - def test_glob_case_sensitive(self): - P = self.cls - def _check(path, pattern, case_sensitive, expected): - actual = {str(q) for q in path.glob(pattern, case_sensitive=case_sensitive)} - expected = {str(P(BASE, q)) for q in expected} - self.assertEqual(actual, expected) - path = P(BASE) - _check(path, "DIRB/FILE*", True, []) - _check(path, "DIRB/FILE*", False, ["dirB/fileB"]) - _check(path, "dirb/file*", True, []) - _check(path, "dirb/file*", False, ["dirB/fileB"]) - - def test_rglob_common(self): - def _check(glob, expected): - self.assertEqual(sorted(glob), sorted(P(BASE, q) for q in expected)) - P = self.cls - p = P(BASE) - it = p.rglob("fileA") - self.assertIsInstance(it, collections.abc.Iterator) - _check(it, ["fileA"]) - _check(p.rglob("fileB"), ["dirB/fileB"]) - _check(p.rglob("**/fileB"), ["dirB/fileB"]) - _check(p.rglob("*/fileA"), []) - if not os_helper.can_symlink(): - _check(p.rglob("*/fileB"), ["dirB/fileB"]) - else: - _check(p.rglob("*/fileB"), ["dirB/fileB", "dirB/linkD/fileB", - "linkB/fileB", "dirA/linkC/fileB"]) - _check(p.rglob("file*"), ["fileA", "dirB/fileB", - "dirC/fileC", "dirC/dirD/fileD"]) - if not os_helper.can_symlink(): - _check(p.rglob("*/"), [ - "dirA", "dirB", "dirC", "dirC/dirD", "dirE", - ]) - else: - _check(p.rglob("*/"), [ - "dirA", "dirA/linkC", "dirB", "dirB/linkD", "dirC", - "dirC/dirD", "dirE", "linkB", - ]) - _check(p.rglob(""), ["", "dirA", "dirB", "dirC", "dirE", "dirC/dirD"]) - - p = P(BASE, "dirC") - _check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt", - "dirC/dirD", "dirC/dirD/fileD"]) - _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p.rglob("dir*/**"), ["dirC/dirD"]) - _check(p.rglob("*/*"), ["dirC/dirD/fileD"]) - _check(p.rglob("*/"), ["dirC/dirD"]) - _check(p.rglob(""), ["dirC", "dirC/dirD"]) - _check(p.rglob("**"), ["dirC", "dirC/dirD"]) - # gh-91616, a re module regression - _check(p.rglob("*.txt"), ["dirC/novel.txt"]) - _check(p.rglob("*.*"), ["dirC/novel.txt"]) - - @os_helper.skip_unless_symlink - def test_rglob_symlink_loop(self): - # Don't get fooled by symlink loops (Issue #26012). - P = self.cls - p = P(BASE) - given = set(p.rglob('*')) - expect = {'brokenLink', - 'dirA', 'dirA/linkC', - 'dirB', 'dirB/fileB', 'dirB/linkD', - 'dirC', 'dirC/dirD', 'dirC/dirD/fileD', - 'dirC/fileC', 'dirC/novel.txt', - 'dirE', - 'fileA', - 'linkA', - 'linkB', - 'brokenLinkLoop', - } - self.assertEqual(given, {p / x for x in expect}) - - def test_glob_many_open_files(self): - depth = 30 - P = self.cls - base = P(BASE) / 'deep' - p = P(base, *(['d']*depth)) - p.mkdir(parents=True) - pattern = '/'.join(['*'] * depth) - iters = [base.glob(pattern) for j in range(100)] - for it in iters: - self.assertEqual(next(it), p) - iters = [base.rglob('d') for j in range(100)] - p = base - for i in range(depth): - p = p / 'd' - for it in iters: - self.assertEqual(next(it), p) - - def test_glob_dotdot(self): - # ".." is not special in globs. - P = self.cls - p = P(BASE) - self.assertEqual(set(p.glob("..")), { P(BASE, "..") }) - self.assertEqual(set(p.glob("../..")), { P(BASE, "..", "..") }) - self.assertEqual(set(p.glob("dirA/..")), { P(BASE, "dirA", "..") }) - self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") }) - self.assertEqual(set(p.glob("dirA/../file*/..")), set()) - self.assertEqual(set(p.glob("../xyzzy")), set()) - self.assertEqual(set(p.glob("xyzzy/..")), set()) - self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(BASE, *[".."] * 50)}) - - @os_helper.skip_unless_symlink - def test_glob_permissions(self): - # See bpo-38894 - P = self.cls - base = P(BASE) / 'permissions' - base.mkdir() - self.addCleanup(os_helper.rmtree, base) - - for i in range(100): - link = base / f"link{i}" - if i % 2: - link.symlink_to(P(BASE, "dirE", "nonexistent")) - else: - link.symlink_to(P(BASE, "dirC")) - - self.assertEqual(len(set(base.glob("*"))), 100) - self.assertEqual(len(set(base.glob("*/"))), 50) - self.assertEqual(len(set(base.glob("*/fileC"))), 50) - self.assertEqual(len(set(base.glob("*/file*"))), 50) - - @os_helper.skip_unless_symlink - def test_glob_long_symlink(self): - # See gh-87695 - base = self.cls(BASE) / 'long_symlink' - base.mkdir() - bad_link = base / 'bad_link' - bad_link.symlink_to("bad" * 200) - self.assertEqual(sorted(base.glob('**/*')), [bad_link]) - - def test_glob_above_recursion_limit(self): - recursion_limit = 50 - # directory_depth > recursion_limit - directory_depth = recursion_limit + 10 - base = pathlib.Path(os_helper.TESTFN, 'deep') - path = pathlib.Path(base, *(['d'] * directory_depth)) - path.mkdir(parents=True) - - with set_recursion_limit(recursion_limit): - list(base.glob('**')) - - def _check_resolve(self, p, expected, strict=True): - q = p.resolve(strict) - self.assertEqual(q, expected) - - # This can be used to check both relative and absolute resolutions. - _check_resolve_relative = _check_resolve_absolute = _check_resolve - - @os_helper.skip_unless_symlink - def test_resolve_common(self): - P = self.cls - p = P(BASE, 'foo') - with self.assertRaises(OSError) as cm: - p.resolve(strict=True) - self.assertEqual(cm.exception.errno, errno.ENOENT) - # Non-strict - self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(BASE, 'foo')) - p = P(BASE, 'foo', 'in', 'spam') - self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(BASE, 'foo', 'in', 'spam')) - p = P(BASE, '..', 'foo', 'in', 'spam') - self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.abspath(os.path.join('foo', 'in', 'spam'))) - # These are all relative symlinks. - p = P(BASE, 'dirB', 'fileB') - self._check_resolve_relative(p, p) - p = P(BASE, 'linkA') - self._check_resolve_relative(p, P(BASE, 'fileA')) - p = P(BASE, 'dirA', 'linkC', 'fileB') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) - p = P(BASE, 'dirB', 'linkD', 'fileB') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) - # Non-strict - p = P(BASE, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo', 'in', - 'spam'), False) - p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam') - if os.name == 'nt': - # In Windows, if linkY points to dirB, 'dirA\linkY\..' - # resolves to 'dirA' without resolving linkY first. - self._check_resolve_relative(p, P(BASE, 'dirA', 'foo', 'in', - 'spam'), False) - else: - # In Posix, if linkY points to dirB, 'dirA/linkY/..' - # resolves to 'dirB/..' first before resolving to parent of dirB. - self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) - # Now create absolute symlinks. - d = os_helper._longpath(tempfile.mkdtemp(suffix='-dirD', - dir=os.getcwd())) - self.addCleanup(os_helper.rmtree, d) - os.symlink(os.path.join(d), join('dirA', 'linkX')) - os.symlink(join('dirB'), os.path.join(d, 'linkY')) - p = P(BASE, 'dirA', 'linkX', 'linkY', 'fileB') - self._check_resolve_absolute(p, P(BASE, 'dirB', 'fileB')) - # Non-strict - p = P(BASE, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam') - self._check_resolve_relative(p, P(BASE, 'dirB', 'foo', 'in', 'spam'), - False) - p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam') - if os.name == 'nt': - # In Windows, if linkY points to dirB, 'dirA\linkY\..' - # resolves to 'dirA' without resolving linkY first. - self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False) - else: - # In Posix, if linkY points to dirB, 'dirA/linkY/..' - # resolves to 'dirB/..' first before resolving to parent of dirB. - self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) - - @os_helper.skip_unless_symlink - def test_resolve_dot(self): - # See http://web.archive.org/web/20200623062557/https://bitbucket.org/pitrou/pathlib/issues/9/ - p = self.cls(BASE) - self.dirlink('.', join('0')) - self.dirlink(os.path.join('0', '0'), join('1')) - self.dirlink(os.path.join('1', '1'), join('2')) - q = p / '2' - self.assertEqual(q.resolve(strict=True), p) - r = q / '3' / '4' - self.assertRaises(FileNotFoundError, r.resolve, strict=True) - # Non-strict - self.assertEqual(r.resolve(strict=False), p / '3' / '4') - - def test_resolve_nonexist_relative_issue38671(self): - p = self.cls('non', 'exist') - - old_cwd = os.getcwd() - os.chdir(BASE) - try: - self.assertEqual(p.resolve(), self.cls(BASE, p)) - finally: - os.chdir(old_cwd) - - def test_with(self): - p = self.cls(BASE) - it = p.iterdir() - it2 = p.iterdir() - next(it2) - # bpo-46556: path context managers are deprecated in Python 3.11. - with self.assertWarns(DeprecationWarning): - with p: - pass - # Using a path as a context manager is a no-op, thus the following - # operations should still succeed after the context manage exits. - next(it) - next(it2) - p.exists() - p.resolve() - p.absolute() - with self.assertWarns(DeprecationWarning): - with p: - pass - - @os_helper.skip_unless_working_chmod - def test_chmod(self): - p = self.cls(BASE) / 'fileA' - mode = p.stat().st_mode - # Clear writable bit. - new_mode = mode & ~0o222 - p.chmod(new_mode) - self.assertEqual(p.stat().st_mode, new_mode) - # Set writable bit. - new_mode = mode | 0o222 - p.chmod(new_mode) - self.assertEqual(p.stat().st_mode, new_mode) - - # On Windows, os.chmod does not follow symlinks (issue #15411) - @only_posix - @os_helper.skip_unless_working_chmod - def test_chmod_follow_symlinks_true(self): - p = self.cls(BASE) / 'linkA' - q = p.resolve() - mode = q.stat().st_mode - # Clear writable bit. - new_mode = mode & ~0o222 - p.chmod(new_mode, follow_symlinks=True) - self.assertEqual(q.stat().st_mode, new_mode) - # Set writable bit - new_mode = mode | 0o222 - p.chmod(new_mode, follow_symlinks=True) - self.assertEqual(q.stat().st_mode, new_mode) - - # XXX also need a test for lchmod. - - @os_helper.skip_unless_working_chmod - def test_stat(self): - p = self.cls(BASE) / 'fileA' - st = p.stat() - self.assertEqual(p.stat(), st) - # Change file mode by flipping write bit. - p.chmod(st.st_mode ^ 0o222) - self.addCleanup(p.chmod, st.st_mode) - self.assertNotEqual(p.stat(), st) - - @os_helper.skip_unless_symlink - def test_stat_no_follow_symlinks(self): - p = self.cls(BASE) / 'linkA' - st = p.stat() - self.assertNotEqual(st, p.stat(follow_symlinks=False)) - - def test_stat_no_follow_symlinks_nosymlink(self): - p = self.cls(BASE) / 'fileA' - st = p.stat() - self.assertEqual(st, p.stat(follow_symlinks=False)) - - @os_helper.skip_unless_symlink - def test_lstat(self): - p = self.cls(BASE)/ 'linkA' - st = p.stat() - self.assertNotEqual(st, p.lstat()) - - def test_lstat_nosymlink(self): - p = self.cls(BASE) / 'fileA' - st = p.stat() - self.assertEqual(st, p.lstat()) - - @unittest.skipUnless(pwd, "the pwd module is needed for this test") - def test_owner(self): - p = self.cls(BASE) / 'fileA' - uid = p.stat().st_uid - try: - name = pwd.getpwuid(uid).pw_name - except KeyError: - self.skipTest( - "user %d doesn't have an entry in the system database" % uid) - self.assertEqual(name, p.owner()) - - @unittest.skipUnless(grp, "the grp module is needed for this test") - def test_group(self): - p = self.cls(BASE) / 'fileA' - gid = p.stat().st_gid - try: - name = grp.getgrgid(gid).gr_name - except KeyError: - self.skipTest( - "group %d doesn't have an entry in the system database" % gid) - self.assertEqual(name, p.group()) - - def test_unlink(self): - p = self.cls(BASE) / 'fileA' - p.unlink() - self.assertFileNotFound(p.stat) - self.assertFileNotFound(p.unlink) - - def test_unlink_missing_ok(self): - p = self.cls(BASE) / 'fileAAA' - self.assertFileNotFound(p.unlink) - p.unlink(missing_ok=True) - - def test_rmdir(self): - p = self.cls(BASE) / 'dirA' - for q in p.iterdir(): - q.unlink() - p.rmdir() - self.assertFileNotFound(p.stat) - self.assertFileNotFound(p.unlink) - - @unittest.skipUnless(hasattr(os, "link"), "os.link() is not present") - def test_hardlink_to(self): - P = self.cls(BASE) - target = P / 'fileA' - size = target.stat().st_size - # linking to another path. - link = P / 'dirA' / 'fileAA' - link.hardlink_to(target) - self.assertEqual(link.stat().st_size, size) - self.assertTrue(os.path.samefile(target, link)) - self.assertTrue(target.exists()) - # Linking to a str of a relative path. - link2 = P / 'dirA' / 'fileAAA' - target2 = rel_join('fileA') - link2.hardlink_to(target2) - self.assertEqual(os.stat(target2).st_size, size) - self.assertTrue(link2.exists()) - - @unittest.skipIf(hasattr(os, "link"), "os.link() is present") - def test_link_to_not_implemented(self): - P = self.cls(BASE) - p = P / 'fileA' - # linking to another path. - q = P / 'dirA' / 'fileAA' - with self.assertRaises(NotImplementedError): - q.hardlink_to(p) - - def test_rename(self): - P = self.cls(BASE) - p = P / 'fileA' - size = p.stat().st_size - # Renaming to another path. - q = P / 'dirA' / 'fileAA' - renamed_p = p.rename(q) - self.assertEqual(renamed_p, q) - self.assertEqual(q.stat().st_size, size) - self.assertFileNotFound(p.stat) - # Renaming to a str of a relative path. - r = rel_join('fileAAA') - renamed_q = q.rename(r) - self.assertEqual(renamed_q, self.cls(r)) - self.assertEqual(os.stat(r).st_size, size) - self.assertFileNotFound(q.stat) - - def test_replace(self): - P = self.cls(BASE) - p = P / 'fileA' - size = p.stat().st_size - # Replacing a non-existing path. - q = P / 'dirA' / 'fileAA' - replaced_p = p.replace(q) - self.assertEqual(replaced_p, q) - self.assertEqual(q.stat().st_size, size) - self.assertFileNotFound(p.stat) - # Replacing another (existing) path. - r = rel_join('dirB', 'fileB') - replaced_q = q.replace(r) - self.assertEqual(replaced_q, self.cls(r)) - self.assertEqual(os.stat(r).st_size, size) - self.assertFileNotFound(q.stat) - - @os_helper.skip_unless_symlink - def test_readlink(self): - P = self.cls(BASE) - self.assertEqual((P / 'linkA').readlink(), self.cls('fileA')) - self.assertEqual((P / 'brokenLink').readlink(), - self.cls('non-existing')) - self.assertEqual((P / 'linkB').readlink(), self.cls('dirB')) - with self.assertRaises(OSError): - (P / 'fileA').readlink() - - def test_touch_common(self): - P = self.cls(BASE) - p = P / 'newfileA' - self.assertFalse(p.exists()) - p.touch() - self.assertTrue(p.exists()) - st = p.stat() - old_mtime = st.st_mtime - old_mtime_ns = st.st_mtime_ns - # Rewind the mtime sufficiently far in the past to work around - # filesystem-specific timestamp granularity. - os.utime(str(p), (old_mtime - 10, old_mtime - 10)) - # The file mtime should be refreshed by calling touch() again. - p.touch() - st = p.stat() - self.assertGreaterEqual(st.st_mtime_ns, old_mtime_ns) - self.assertGreaterEqual(st.st_mtime, old_mtime) - # Now with exist_ok=False. - p = P / 'newfileB' - self.assertFalse(p.exists()) - p.touch(mode=0o700, exist_ok=False) - self.assertTrue(p.exists()) - self.assertRaises(OSError, p.touch, exist_ok=False) - - def test_touch_nochange(self): - P = self.cls(BASE) - p = P / 'fileA' - p.touch() - with p.open('rb') as f: - self.assertEqual(f.read().strip(), b"this is file A") - - def test_mkdir(self): - P = self.cls(BASE) - p = P / 'newdirA' - self.assertFalse(p.exists()) - p.mkdir() - self.assertTrue(p.exists()) - self.assertTrue(p.is_dir()) - with self.assertRaises(OSError) as cm: - p.mkdir() - self.assertEqual(cm.exception.errno, errno.EEXIST) - - def test_mkdir_parents(self): - # Creating a chain of directories. - p = self.cls(BASE, 'newdirB', 'newdirC') - self.assertFalse(p.exists()) - with self.assertRaises(OSError) as cm: - p.mkdir() - self.assertEqual(cm.exception.errno, errno.ENOENT) - p.mkdir(parents=True) - self.assertTrue(p.exists()) - self.assertTrue(p.is_dir()) - with self.assertRaises(OSError) as cm: - p.mkdir(parents=True) - self.assertEqual(cm.exception.errno, errno.EEXIST) - # Test `mode` arg. - mode = stat.S_IMODE(p.stat().st_mode) # Default mode. - p = self.cls(BASE, 'newdirD', 'newdirE') - p.mkdir(0o555, parents=True) - self.assertTrue(p.exists()) - self.assertTrue(p.is_dir()) - if os.name != 'nt': - # The directory's permissions follow the mode argument. - self.assertEqual(stat.S_IMODE(p.stat().st_mode), 0o7555 & mode) - # The parent's permissions follow the default process settings. - self.assertEqual(stat.S_IMODE(p.parent.stat().st_mode), mode) - - def test_mkdir_exist_ok(self): - p = self.cls(BASE, 'dirB') - st_ctime_first = p.stat().st_ctime - self.assertTrue(p.exists()) - self.assertTrue(p.is_dir()) - with self.assertRaises(FileExistsError) as cm: - p.mkdir() - self.assertEqual(cm.exception.errno, errno.EEXIST) - p.mkdir(exist_ok=True) - self.assertTrue(p.exists()) - self.assertEqual(p.stat().st_ctime, st_ctime_first) - - def test_mkdir_exist_ok_with_parent(self): - p = self.cls(BASE, 'dirC') - self.assertTrue(p.exists()) - with self.assertRaises(FileExistsError) as cm: - p.mkdir() - self.assertEqual(cm.exception.errno, errno.EEXIST) - p = p / 'newdirC' - p.mkdir(parents=True) - st_ctime_first = p.stat().st_ctime - self.assertTrue(p.exists()) - with self.assertRaises(FileExistsError) as cm: - p.mkdir(parents=True) - self.assertEqual(cm.exception.errno, errno.EEXIST) - p.mkdir(parents=True, exist_ok=True) - self.assertTrue(p.exists()) - self.assertEqual(p.stat().st_ctime, st_ctime_first) - - @unittest.skipIf(is_emscripten, "FS root cannot be modified on Emscripten.") - def test_mkdir_exist_ok_root(self): - # Issue #25803: A drive root could raise PermissionError on Windows. - self.cls('/').resolve().mkdir(exist_ok=True) - self.cls('/').resolve().mkdir(parents=True, exist_ok=True) - - @only_nt # XXX: not sure how to test this on POSIX. - def test_mkdir_with_unknown_drive(self): - for d in 'ZYXWVUTSRQPONMLKJIHGFEDCBA': - p = self.cls(d + ':\\') - if not p.is_dir(): - break - else: - self.skipTest("cannot find a drive that doesn't exist") - with self.assertRaises(OSError): - (p / 'child' / 'path').mkdir(parents=True) - - def test_mkdir_with_child_file(self): - p = self.cls(BASE, 'dirB', 'fileB') - self.assertTrue(p.exists()) - # An exception is raised when the last path component is an existing - # regular file, regardless of whether exist_ok is true or not. - with self.assertRaises(FileExistsError) as cm: - p.mkdir(parents=True) - self.assertEqual(cm.exception.errno, errno.EEXIST) - with self.assertRaises(FileExistsError) as cm: - p.mkdir(parents=True, exist_ok=True) - self.assertEqual(cm.exception.errno, errno.EEXIST) - - def test_mkdir_no_parents_file(self): - p = self.cls(BASE, 'fileA') - self.assertTrue(p.exists()) - # An exception is raised when the last path component is an existing - # regular file, regardless of whether exist_ok is true or not. - with self.assertRaises(FileExistsError) as cm: - p.mkdir() - self.assertEqual(cm.exception.errno, errno.EEXIST) - with self.assertRaises(FileExistsError) as cm: - p.mkdir(exist_ok=True) - self.assertEqual(cm.exception.errno, errno.EEXIST) - - def test_mkdir_concurrent_parent_creation(self): - for pattern_num in range(32): - p = self.cls(BASE, 'dirCPC%d' % pattern_num) - self.assertFalse(p.exists()) - - real_mkdir = os.mkdir - def my_mkdir(path, mode=0o777): - path = str(path) - # Emulate another process that would create the directory - # just before we try to create it ourselves. We do it - # in all possible pattern combinations, assuming that this - # function is called at most 5 times (dirCPC/dir1/dir2, - # dirCPC/dir1, dirCPC, dirCPC/dir1, dirCPC/dir1/dir2). - if pattern.pop(): - real_mkdir(path, mode) # From another process. - concurrently_created.add(path) - real_mkdir(path, mode) # Our real call. - - pattern = [bool(pattern_num & (1 << n)) for n in range(5)] - concurrently_created = set() - p12 = p / 'dir1' / 'dir2' - try: - with mock.patch("os.mkdir", my_mkdir): - p12.mkdir(parents=True, exist_ok=False) - except FileExistsError: - self.assertIn(str(p12), concurrently_created) - else: - self.assertNotIn(str(p12), concurrently_created) - self.assertTrue(p.exists()) - - @os_helper.skip_unless_symlink - def test_symlink_to(self): - P = self.cls(BASE) - target = P / 'fileA' - # Symlinking a path target. - link = P / 'dirA' / 'linkAA' - link.symlink_to(target) - self.assertEqual(link.stat(), target.stat()) - self.assertNotEqual(link.lstat(), target.stat()) - # Symlinking a str target. - link = P / 'dirA' / 'linkAAA' - link.symlink_to(str(target)) - self.assertEqual(link.stat(), target.stat()) - self.assertNotEqual(link.lstat(), target.stat()) - self.assertFalse(link.is_dir()) - # Symlinking to a directory. - target = P / 'dirB' - link = P / 'dirA' / 'linkAAAA' - link.symlink_to(target, target_is_directory=True) - self.assertEqual(link.stat(), target.stat()) - self.assertNotEqual(link.lstat(), target.stat()) - self.assertTrue(link.is_dir()) - self.assertTrue(list(link.iterdir())) - - def test_is_dir(self): - P = self.cls(BASE) - self.assertTrue((P / 'dirA').is_dir()) - self.assertFalse((P / 'fileA').is_dir()) - self.assertFalse((P / 'non-existing').is_dir()) - self.assertFalse((P / 'fileA' / 'bah').is_dir()) - if os_helper.can_symlink(): - self.assertFalse((P / 'linkA').is_dir()) - self.assertTrue((P / 'linkB').is_dir()) - self.assertFalse((P/ 'brokenLink').is_dir(), False) - self.assertIs((P / 'dirA\udfff').is_dir(), False) - self.assertIs((P / 'dirA\x00').is_dir(), False) - - def test_is_file(self): - P = self.cls(BASE) - self.assertTrue((P / 'fileA').is_file()) - self.assertFalse((P / 'dirA').is_file()) - self.assertFalse((P / 'non-existing').is_file()) - self.assertFalse((P / 'fileA' / 'bah').is_file()) - if os_helper.can_symlink(): - self.assertTrue((P / 'linkA').is_file()) - self.assertFalse((P / 'linkB').is_file()) - self.assertFalse((P/ 'brokenLink').is_file()) - self.assertIs((P / 'fileA\udfff').is_file(), False) - self.assertIs((P / 'fileA\x00').is_file(), False) - - def test_is_mount(self): - P = self.cls(BASE) - if os.name == 'nt': - R = self.cls('c:\\') - else: - R = self.cls('/') - self.assertFalse((P / 'fileA').is_mount()) - self.assertFalse((P / 'dirA').is_mount()) - self.assertFalse((P / 'non-existing').is_mount()) - self.assertFalse((P / 'fileA' / 'bah').is_mount()) - self.assertTrue(R.is_mount()) - if os_helper.can_symlink(): - self.assertFalse((P / 'linkA').is_mount()) - self.assertIs((R / '\udfff').is_mount(), False) - - def test_is_symlink(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_symlink()) - self.assertFalse((P / 'dirA').is_symlink()) - self.assertFalse((P / 'non-existing').is_symlink()) - self.assertFalse((P / 'fileA' / 'bah').is_symlink()) - if os_helper.can_symlink(): - self.assertTrue((P / 'linkA').is_symlink()) - self.assertTrue((P / 'linkB').is_symlink()) - self.assertTrue((P/ 'brokenLink').is_symlink()) - self.assertIs((P / 'fileA\udfff').is_file(), False) - self.assertIs((P / 'fileA\x00').is_file(), False) - if os_helper.can_symlink(): - self.assertIs((P / 'linkA\udfff').is_file(), False) - self.assertIs((P / 'linkA\x00').is_file(), False) - - def test_is_junction(self): - P = self.cls(BASE) - - with mock.patch.object(P._flavour, 'isjunction'): - self.assertEqual(P.is_junction(), P._flavour.isjunction.return_value) - P._flavour.isjunction.assert_called_once_with(P) - - def test_is_fifo_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_fifo()) - self.assertFalse((P / 'dirA').is_fifo()) - self.assertFalse((P / 'non-existing').is_fifo()) - self.assertFalse((P / 'fileA' / 'bah').is_fifo()) - self.assertIs((P / 'fileA\udfff').is_fifo(), False) - self.assertIs((P / 'fileA\x00').is_fifo(), False) - - @unittest.skipUnless(hasattr(os, "mkfifo"), "os.mkfifo() required") - @unittest.skipIf(sys.platform == "vxworks", - "fifo requires special path on VxWorks") - def test_is_fifo_true(self): - P = self.cls(BASE, 'myfifo') - try: - os.mkfifo(str(P)) - except PermissionError as e: - self.skipTest('os.mkfifo(): %s' % e) - self.assertTrue(P.is_fifo()) - self.assertFalse(P.is_socket()) - self.assertFalse(P.is_file()) - self.assertIs(self.cls(BASE, 'myfifo\udfff').is_fifo(), False) - self.assertIs(self.cls(BASE, 'myfifo\x00').is_fifo(), False) - - def test_is_socket_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_socket()) - self.assertFalse((P / 'dirA').is_socket()) - self.assertFalse((P / 'non-existing').is_socket()) - self.assertFalse((P / 'fileA' / 'bah').is_socket()) - self.assertIs((P / 'fileA\udfff').is_socket(), False) - self.assertIs((P / 'fileA\x00').is_socket(), False) - - @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") - @unittest.skipIf( - is_emscripten, "Unix sockets are not implemented on Emscripten." - ) - @unittest.skipIf( - is_wasi, "Cannot create socket on WASI." - ) - def test_is_socket_true(self): - P = self.cls(BASE, 'mysock') - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.addCleanup(sock.close) - try: - sock.bind(str(P)) - except OSError as e: - if (isinstance(e, PermissionError) or - "AF_UNIX path too long" in str(e)): - self.skipTest("cannot bind Unix socket: " + str(e)) - self.assertTrue(P.is_socket()) - self.assertFalse(P.is_fifo()) - self.assertFalse(P.is_file()) - self.assertIs(self.cls(BASE, 'mysock\udfff').is_socket(), False) - self.assertIs(self.cls(BASE, 'mysock\x00').is_socket(), False) - - def test_is_block_device_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_block_device()) - self.assertFalse((P / 'dirA').is_block_device()) - self.assertFalse((P / 'non-existing').is_block_device()) - self.assertFalse((P / 'fileA' / 'bah').is_block_device()) - self.assertIs((P / 'fileA\udfff').is_block_device(), False) - self.assertIs((P / 'fileA\x00').is_block_device(), False) - - def test_is_char_device_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_char_device()) - self.assertFalse((P / 'dirA').is_char_device()) - self.assertFalse((P / 'non-existing').is_char_device()) - self.assertFalse((P / 'fileA' / 'bah').is_char_device()) - self.assertIs((P / 'fileA\udfff').is_char_device(), False) - self.assertIs((P / 'fileA\x00').is_char_device(), False) - - def test_is_char_device_true(self): - # os.devnull should generally be a char device. - P = self.cls(os.devnull) - if not P.exists(): - self.skipTest("null device required") - self.assertTrue(P.is_char_device()) - self.assertFalse(P.is_block_device()) - self.assertFalse(P.is_file()) - self.assertIs(self.cls(f'{os.devnull}\udfff').is_char_device(), False) - self.assertIs(self.cls(f'{os.devnull}\x00').is_char_device(), False) - - def test_pickling_common(self): - p = self.cls(BASE, 'fileA') - for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): - dumped = pickle.dumps(p, proto) - pp = pickle.loads(dumped) - self.assertEqual(pp.stat(), p.stat()) - - def test_parts_interning(self): - P = self.cls - p = P('/usr/bin/foo') - q = P('/usr/local/bin') - # 'usr' - self.assertIs(p.parts[1], q.parts[1]) - # 'bin' - self.assertIs(p.parts[2], q.parts[3]) - - def _check_complex_symlinks(self, link0_target): - # Test solving a non-looping chain of symlinks (issue #19887). - P = self.cls(BASE) - self.dirlink(os.path.join('link0', 'link0'), join('link1')) - self.dirlink(os.path.join('link1', 'link1'), join('link2')) - self.dirlink(os.path.join('link2', 'link2'), join('link3')) - self.dirlink(link0_target, join('link0')) - - # Resolve absolute paths. - p = (P / 'link0').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = (P / 'link1').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = (P / 'link2').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = (P / 'link3').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - - # Resolve relative paths. - old_path = os.getcwd() - os.chdir(BASE) - try: - p = self.cls('link0').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = self.cls('link1').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = self.cls('link2').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = self.cls('link3').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - finally: - os.chdir(old_path) - - @os_helper.skip_unless_symlink - def test_complex_symlinks_absolute(self): - self._check_complex_symlinks(BASE) - - @os_helper.skip_unless_symlink - def test_complex_symlinks_relative(self): - self._check_complex_symlinks('.') - - @os_helper.skip_unless_symlink - def test_complex_symlinks_relative_dot_dot(self): - self._check_complex_symlinks(os.path.join('dirA', '..')) - - def test_passing_kwargs_deprecated(self): - with self.assertWarns(DeprecationWarning): - self.cls(foo="bar") - - -class WalkTests(unittest.TestCase): - - def setUp(self): - self.addCleanup(os_helper.rmtree, os_helper.TESTFN) - - # Build: - # TESTFN/ - # TEST1/ a file kid and two directory kids - # tmp1 - # SUB1/ a file kid and a directory kid - # tmp2 - # SUB11/ no kids - # SUB2/ a file kid and a dirsymlink kid - # tmp3 - # SUB21/ not readable - # tmp5 - # link/ a symlink to TEST2 - # broken_link - # broken_link2 - # broken_link3 - # TEST2/ - # tmp4 a lone file - self.walk_path = pathlib.Path(os_helper.TESTFN, "TEST1") - self.sub1_path = self.walk_path / "SUB1" - self.sub11_path = self.sub1_path / "SUB11" - self.sub2_path = self.walk_path / "SUB2" - sub21_path= self.sub2_path / "SUB21" - tmp1_path = self.walk_path / "tmp1" - tmp2_path = self.sub1_path / "tmp2" - tmp3_path = self.sub2_path / "tmp3" - tmp5_path = sub21_path / "tmp3" - self.link_path = self.sub2_path / "link" - t2_path = pathlib.Path(os_helper.TESTFN, "TEST2") - tmp4_path = pathlib.Path(os_helper.TESTFN, "TEST2", "tmp4") - broken_link_path = self.sub2_path / "broken_link" - broken_link2_path = self.sub2_path / "broken_link2" - broken_link3_path = self.sub2_path / "broken_link3" - - os.makedirs(self.sub11_path) - os.makedirs(self.sub2_path) - os.makedirs(sub21_path) - os.makedirs(t2_path) - - for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path, tmp5_path: - with open(path, "x", encoding='utf-8') as f: - f.write(f"I'm {path} and proud of it. Blame test_pathlib.\n") - - if os_helper.can_symlink(): - os.symlink(os.path.abspath(t2_path), self.link_path) - os.symlink('broken', broken_link_path, True) - os.symlink(pathlib.Path('tmp3', 'broken'), broken_link2_path, True) - os.symlink(pathlib.Path('SUB21', 'tmp5'), broken_link3_path, True) - self.sub2_tree = (self.sub2_path, ["SUB21"], - ["broken_link", "broken_link2", "broken_link3", - "link", "tmp3"]) - else: - self.sub2_tree = (self.sub2_path, ["SUB21"], ["tmp3"]) - - if not is_emscripten: - # Emscripten fails with inaccessible directories. - os.chmod(sub21_path, 0) - try: - os.listdir(sub21_path) - except PermissionError: - self.addCleanup(os.chmod, sub21_path, stat.S_IRWXU) - else: - os.chmod(sub21_path, stat.S_IRWXU) - os.unlink(tmp5_path) - os.rmdir(sub21_path) - del self.sub2_tree[1][:1] - - def test_walk_topdown(self): - walker = self.walk_path.walk() - entry = next(walker) - entry[1].sort() # Ensure we visit SUB1 before SUB2 - self.assertEqual(entry, (self.walk_path, ["SUB1", "SUB2"], ["tmp1"])) - entry = next(walker) - self.assertEqual(entry, (self.sub1_path, ["SUB11"], ["tmp2"])) - entry = next(walker) - self.assertEqual(entry, (self.sub11_path, [], [])) - entry = next(walker) - entry[1].sort() - entry[2].sort() - self.assertEqual(entry, self.sub2_tree) - with self.assertRaises(StopIteration): - next(walker) - - def test_walk_prune(self, walk_path=None): - if walk_path is None: - walk_path = self.walk_path - # Prune the search. - all = [] - for root, dirs, files in walk_path.walk(): - all.append((root, dirs, files)) - if 'SUB1' in dirs: - # Note that this also mutates the dirs we appended to all! - dirs.remove('SUB1') - - self.assertEqual(len(all), 2) - self.assertEqual(all[0], (self.walk_path, ["SUB2"], ["tmp1"])) - - all[1][-1].sort() - all[1][1].sort() - self.assertEqual(all[1], self.sub2_tree) - - def test_file_like_path(self): - self.test_walk_prune(FakePath(self.walk_path).__fspath__()) - - def test_walk_bottom_up(self): - seen_testfn = seen_sub1 = seen_sub11 = seen_sub2 = False - for path, dirnames, filenames in self.walk_path.walk(top_down=False): - if path == self.walk_path: - self.assertFalse(seen_testfn) - self.assertTrue(seen_sub1) - self.assertTrue(seen_sub2) - self.assertEqual(sorted(dirnames), ["SUB1", "SUB2"]) - self.assertEqual(filenames, ["tmp1"]) - seen_testfn = True - elif path == self.sub1_path: - self.assertFalse(seen_testfn) - self.assertFalse(seen_sub1) - self.assertTrue(seen_sub11) - self.assertEqual(dirnames, ["SUB11"]) - self.assertEqual(filenames, ["tmp2"]) - seen_sub1 = True - elif path == self.sub11_path: - self.assertFalse(seen_sub1) - self.assertFalse(seen_sub11) - self.assertEqual(dirnames, []) - self.assertEqual(filenames, []) - seen_sub11 = True - elif path == self.sub2_path: - self.assertFalse(seen_testfn) - self.assertFalse(seen_sub2) - self.assertEqual(sorted(dirnames), sorted(self.sub2_tree[1])) - self.assertEqual(sorted(filenames), sorted(self.sub2_tree[2])) - seen_sub2 = True - else: - raise AssertionError(f"Unexpected path: {path}") - self.assertTrue(seen_testfn) - - @os_helper.skip_unless_symlink - def test_walk_follow_symlinks(self): - walk_it = self.walk_path.walk(follow_symlinks=True) - for root, dirs, files in walk_it: - if root == self.link_path: - self.assertEqual(dirs, []) - self.assertEqual(files, ["tmp4"]) - break - else: - self.fail("Didn't follow symlink with follow_symlinks=True") - - @os_helper.skip_unless_symlink - def test_walk_symlink_location(self): - # Tests whether symlinks end up in filenames or dirnames depending - # on the `follow_symlinks` argument. - walk_it = self.walk_path.walk(follow_symlinks=False) - for root, dirs, files in walk_it: - if root == self.sub2_path: - self.assertIn("link", files) - break - else: - self.fail("symlink not found") - - walk_it = self.walk_path.walk(follow_symlinks=True) - for root, dirs, files in walk_it: - if root == self.sub2_path: - self.assertIn("link", dirs) - break - - def test_walk_bad_dir(self): - errors = [] - walk_it = self.walk_path.walk(on_error=errors.append) - root, dirs, files = next(walk_it) - self.assertEqual(errors, []) - dir1 = 'SUB1' - path1 = root / dir1 - path1new = (root / dir1).with_suffix(".new") - path1.rename(path1new) - try: - roots = [r for r, _, _ in walk_it] - self.assertTrue(errors) - self.assertNotIn(path1, roots) - self.assertNotIn(path1new, roots) - for dir2 in dirs: - if dir2 != dir1: - self.assertIn(root / dir2, roots) - finally: - path1new.rename(path1) - - def test_walk_many_open_files(self): - depth = 30 - base = pathlib.Path(os_helper.TESTFN, 'deep') - path = pathlib.Path(base, *(['d']*depth)) - path.mkdir(parents=True) - - iters = [base.walk(top_down=False) for _ in range(100)] - for i in range(depth + 1): - expected = (path, ['d'] if i else [], []) - for it in iters: - self.assertEqual(next(it), expected) - path = path.parent - - iters = [base.walk(top_down=True) for _ in range(100)] - path = base - for i in range(depth + 1): - expected = (path, ['d'] if i < depth else [], []) - for it in iters: - self.assertEqual(next(it), expected) - path = path / 'd' - - def test_walk_above_recursion_limit(self): - recursion_limit = 40 - # directory_depth > recursion_limit - directory_depth = recursion_limit + 10 - base = pathlib.Path(os_helper.TESTFN, 'deep') - path = pathlib.Path(base, *(['d'] * directory_depth)) - path.mkdir(parents=True) - - with set_recursion_limit(recursion_limit): - list(base.walk()) - list(base.walk(top_down=False)) - - -class PathTest(_BasePathTest, unittest.TestCase): - cls = pathlib.Path - - def test_concrete_class(self): - p = self.cls('a') - self.assertIs(type(p), - pathlib.WindowsPath if os.name == 'nt' else pathlib.PosixPath) - - def test_unsupported_flavour(self): - if os.name == 'nt': - self.assertRaises(NotImplementedError, pathlib.PosixPath) - else: - self.assertRaises(NotImplementedError, pathlib.WindowsPath) - - def test_glob_empty_pattern(self): - p = self.cls() - with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'): - list(p.glob('')) - - -@only_posix -class PosixPathTest(_BasePathTest, unittest.TestCase): - cls = pathlib.PosixPath - - def test_absolute(self): - P = self.cls - self.assertEqual(str(P('/').absolute()), '/') - self.assertEqual(str(P('/a').absolute()), '/a') - self.assertEqual(str(P('/a/b').absolute()), '/a/b') - - # '//'-prefixed absolute path (supported by POSIX). - self.assertEqual(str(P('//').absolute()), '//') - self.assertEqual(str(P('//a').absolute()), '//a') - self.assertEqual(str(P('//a/b').absolute()), '//a/b') - - def _check_symlink_loop(self, *args, strict=True): - path = self.cls(*args) - with self.assertRaises(RuntimeError): - print(path.resolve(strict)) - - @unittest.skipIf( - is_emscripten or is_wasi, - "umask is not implemented on Emscripten/WASI." - ) - def test_open_mode(self): - old_mask = os.umask(0) - self.addCleanup(os.umask, old_mask) - p = self.cls(BASE) - with (p / 'new_file').open('wb'): - pass - st = os.stat(join('new_file')) - self.assertEqual(stat.S_IMODE(st.st_mode), 0o666) - os.umask(0o022) - with (p / 'other_new_file').open('wb'): - pass - st = os.stat(join('other_new_file')) - self.assertEqual(stat.S_IMODE(st.st_mode), 0o644) - - def test_resolve_root(self): - current_directory = os.getcwd() - try: - os.chdir('/') - p = self.cls('spam') - self.assertEqual(str(p.resolve()), '/spam') - finally: - os.chdir(current_directory) - - @unittest.skipIf( - is_emscripten or is_wasi, - "umask is not implemented on Emscripten/WASI." - ) - def test_touch_mode(self): - old_mask = os.umask(0) - self.addCleanup(os.umask, old_mask) - p = self.cls(BASE) - (p / 'new_file').touch() - st = os.stat(join('new_file')) - self.assertEqual(stat.S_IMODE(st.st_mode), 0o666) - os.umask(0o022) - (p / 'other_new_file').touch() - st = os.stat(join('other_new_file')) - self.assertEqual(stat.S_IMODE(st.st_mode), 0o644) - (p / 'masked_new_file').touch(mode=0o750) - st = os.stat(join('masked_new_file')) - self.assertEqual(stat.S_IMODE(st.st_mode), 0o750) - - @os_helper.skip_unless_symlink - def test_resolve_loop(self): - # Loops with relative symlinks. - os.symlink('linkX/inside', join('linkX')) - self._check_symlink_loop(BASE, 'linkX') - os.symlink('linkY', join('linkY')) - self._check_symlink_loop(BASE, 'linkY') - os.symlink('linkZ/../linkZ', join('linkZ')) - self._check_symlink_loop(BASE, 'linkZ') - # Non-strict - self._check_symlink_loop(BASE, 'linkZ', 'foo', strict=False) - # Loops with absolute symlinks. - os.symlink(join('linkU/inside'), join('linkU')) - self._check_symlink_loop(BASE, 'linkU') - os.symlink(join('linkV'), join('linkV')) - self._check_symlink_loop(BASE, 'linkV') - os.symlink(join('linkW/../linkW'), join('linkW')) - self._check_symlink_loop(BASE, 'linkW') - # Non-strict - self._check_symlink_loop(BASE, 'linkW', 'foo', strict=False) - - def test_glob(self): - P = self.cls - p = P(BASE) - given = set(p.glob("FILEa")) - expect = set() if not os_helper.fs_is_case_insensitive(BASE) else given - self.assertEqual(given, expect) - self.assertEqual(set(p.glob("FILEa*")), set()) - - def test_rglob(self): - P = self.cls - p = P(BASE, "dirC") - given = set(p.rglob("FILEd")) - expect = set() if not os_helper.fs_is_case_insensitive(BASE) else given - self.assertEqual(given, expect) - self.assertEqual(set(p.rglob("FILEd*")), set()) - - @unittest.skipUnless(hasattr(pwd, 'getpwall'), - 'pwd module does not expose getpwall()') - @unittest.skipIf(sys.platform == "vxworks", - "no home directory on VxWorks") - def test_expanduser(self): - P = self.cls - import_helper.import_module('pwd') - import pwd - pwdent = pwd.getpwuid(os.getuid()) - username = pwdent.pw_name - userhome = pwdent.pw_dir.rstrip('/') or '/' - # Find arbitrary different user (if exists). - for pwdent in pwd.getpwall(): - othername = pwdent.pw_name - otherhome = pwdent.pw_dir.rstrip('/') - if othername != username and otherhome: - break - else: - othername = username - otherhome = userhome - - fakename = 'fakeuser' - # This user can theoretically exist on a test runner. Create unique name: - try: - while pwd.getpwnam(fakename): - fakename += '1' - except KeyError: - pass # Non-existent name found - - p1 = P('~/Documents') - p2 = P(f'~{username}/Documents') - p3 = P(f'~{othername}/Documents') - p4 = P(f'../~{username}/Documents') - p5 = P(f'/~{username}/Documents') - p6 = P('') - p7 = P(f'~{fakename}/Documents') - - with os_helper.EnvironmentVarGuard() as env: - env.pop('HOME', None) - - self.assertEqual(p1.expanduser(), P(userhome) / 'Documents') - self.assertEqual(p2.expanduser(), P(userhome) / 'Documents') - self.assertEqual(p3.expanduser(), P(otherhome) / 'Documents') - self.assertEqual(p4.expanduser(), p4) - self.assertEqual(p5.expanduser(), p5) - self.assertEqual(p6.expanduser(), p6) - self.assertRaises(RuntimeError, p7.expanduser) - - env['HOME'] = '/tmp' - self.assertEqual(p1.expanduser(), P('/tmp/Documents')) - self.assertEqual(p2.expanduser(), P(userhome) / 'Documents') - self.assertEqual(p3.expanduser(), P(otherhome) / 'Documents') - self.assertEqual(p4.expanduser(), p4) - self.assertEqual(p5.expanduser(), p5) - self.assertEqual(p6.expanduser(), p6) - self.assertRaises(RuntimeError, p7.expanduser) - - @unittest.skipIf(sys.platform != "darwin", - "Bad file descriptor in /dev/fd affects only macOS") - def test_handling_bad_descriptor(self): - try: - file_descriptors = list(pathlib.Path('/dev/fd').rglob("*"))[3:] - if not file_descriptors: - self.skipTest("no file descriptors - issue was not reproduced") - # Checking all file descriptors because there is no guarantee - # which one will fail. - for f in file_descriptors: - f.exists() - f.is_dir() - f.is_file() - f.is_symlink() - f.is_block_device() - f.is_char_device() - f.is_fifo() - f.is_socket() - except OSError as e: - if e.errno == errno.EBADF: - self.fail("Bad file descriptor not handled.") - raise - - -@only_nt -class WindowsPathTest(_BasePathTest, unittest.TestCase): - cls = pathlib.WindowsPath - - def test_absolute(self): - P = self.cls - - # Simple absolute paths. - self.assertEqual(str(P('c:\\').absolute()), 'c:\\') - self.assertEqual(str(P('c:\\a').absolute()), 'c:\\a') - self.assertEqual(str(P('c:\\a\\b').absolute()), 'c:\\a\\b') - - # UNC absolute paths. - share = '\\\\server\\share\\' - self.assertEqual(str(P(share).absolute()), share) - self.assertEqual(str(P(share + 'a').absolute()), share + 'a') - self.assertEqual(str(P(share + 'a\\b').absolute()), share + 'a\\b') - - # UNC relative paths. - with mock.patch("os.getcwd") as getcwd: - getcwd.return_value = share - - self.assertEqual(str(P().absolute()), share) - self.assertEqual(str(P('.').absolute()), share) - self.assertEqual(str(P('a').absolute()), os.path.join(share, 'a')) - self.assertEqual(str(P('a', 'b', 'c').absolute()), - os.path.join(share, 'a', 'b', 'c')) - - drive = os.path.splitdrive(BASE)[0] - with os_helper.change_cwd(BASE): - # Relative path with root - self.assertEqual(str(P('\\').absolute()), drive + '\\') - self.assertEqual(str(P('\\foo').absolute()), drive + '\\foo') - - # Relative path on current drive - self.assertEqual(str(P(drive).absolute()), BASE) - self.assertEqual(str(P(drive + 'foo').absolute()), os.path.join(BASE, 'foo')) - - with os_helper.subst_drive(BASE) as other_drive: - # Set the working directory on the substitute drive - saved_cwd = os.getcwd() - other_cwd = f'{other_drive}\\dirA' - os.chdir(other_cwd) - os.chdir(saved_cwd) - - # Relative path on another drive - self.assertEqual(str(P(other_drive).absolute()), other_cwd) - self.assertEqual(str(P(other_drive + 'foo').absolute()), other_cwd + '\\foo') - - def test_glob(self): - P = self.cls - p = P(BASE) - self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") }) - self.assertEqual(set(p.glob("*a\\")), { P(BASE, "dirA") }) - self.assertEqual(set(p.glob("F*a")), { P(BASE, "fileA") }) - self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\fileA"}) - self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"}) - - def test_rglob(self): - P = self.cls - p = P(BASE, "dirC") - self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") }) - self.assertEqual(set(p.rglob("*\\")), { P(BASE, "dirC/dirD") }) - self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\fileD"}) - - def test_expanduser(self): - P = self.cls - with os_helper.EnvironmentVarGuard() as env: - env.pop('HOME', None) - env.pop('USERPROFILE', None) - env.pop('HOMEPATH', None) - env.pop('HOMEDRIVE', None) - env['USERNAME'] = 'alice' - - # test that the path returns unchanged - p1 = P('~/My Documents') - p2 = P('~alice/My Documents') - p3 = P('~bob/My Documents') - p4 = P('/~/My Documents') - p5 = P('d:~/My Documents') - p6 = P('') - self.assertRaises(RuntimeError, p1.expanduser) - self.assertRaises(RuntimeError, p2.expanduser) - self.assertRaises(RuntimeError, p3.expanduser) - self.assertEqual(p4.expanduser(), p4) - self.assertEqual(p5.expanduser(), p5) - self.assertEqual(p6.expanduser(), p6) - - def check(): - env.pop('USERNAME', None) - self.assertEqual(p1.expanduser(), - P('C:/Users/alice/My Documents')) - self.assertRaises(RuntimeError, p2.expanduser) - env['USERNAME'] = 'alice' - self.assertEqual(p2.expanduser(), - P('C:/Users/alice/My Documents')) - self.assertEqual(p3.expanduser(), - P('C:/Users/bob/My Documents')) - self.assertEqual(p4.expanduser(), p4) - self.assertEqual(p5.expanduser(), p5) - self.assertEqual(p6.expanduser(), p6) - - env['HOMEPATH'] = 'C:\\Users\\alice' - check() - - env['HOMEDRIVE'] = 'C:\\' - env['HOMEPATH'] = 'Users\\alice' - check() - - env.pop('HOMEDRIVE', None) - env.pop('HOMEPATH', None) - env['USERPROFILE'] = 'C:\\Users\\alice' - check() - - # bpo-38883: ignore `HOME` when set on windows - env['HOME'] = 'C:\\Users\\eve' - check() - - -class PurePathSubclassTest(_BasePurePathTest, unittest.TestCase): - class cls(pathlib.PurePath): - pass - - # repr() roundtripping is not supported in custom subclass. - test_repr_roundtrips = None - - -class PathSubclassTest(_BasePathTest, unittest.TestCase): - class cls(pathlib.Path): - pass - - # repr() roundtripping is not supported in custom subclass. - test_repr_roundtrips = None - - -class CompatiblePathTest(unittest.TestCase): - """ - Test that a type can be made compatible with PurePath - derivatives by implementing division operator overloads. - """ - - class CompatPath: - """ - Minimum viable class to test PurePath compatibility. - Simply uses the division operator to join a given - string and the string value of another object with - a forward slash. - """ - def __init__(self, string): - self.string = string - - def __truediv__(self, other): - return type(self)(f"{self.string}/{other}") - - def __rtruediv__(self, other): - return type(self)(f"{other}/{self.string}") - - def test_truediv(self): - result = pathlib.PurePath("test") / self.CompatPath("right") - self.assertIsInstance(result, self.CompatPath) - self.assertEqual(result.string, "test/right") - - with self.assertRaises(TypeError): - # Verify improper operations still raise a TypeError - pathlib.PurePath("test") / 10 - - def test_rtruediv(self): - result = self.CompatPath("left") / pathlib.PurePath("test") - self.assertIsInstance(result, self.CompatPath) - self.assertEqual(result.string, "left/test") - - with self.assertRaises(TypeError): - # Verify improper operations still raise a TypeError - 10 / pathlib.PurePath("test") - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/test/test_pathlib/__init__.py b/Lib/test/test_pathlib/__init__.py new file mode 100644 index 00000000000..4b16ecc3115 --- /dev/null +++ b/Lib/test/test_pathlib/__init__.py @@ -0,0 +1,5 @@ +import os +from test.support import load_package_tests + +def load_tests(*args): + return load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py new file mode 100644 index 00000000000..6cbc15d7675 --- /dev/null +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -0,0 +1,1660 @@ +# TODO: RUSTPYTHON +# Has not been tested with Windows style paths +import io +import os +import sys +import errno +import ntpath +import pathlib +import pickle +import posixpath +import socket +import stat +import tempfile +import unittest +from unittest import mock +from urllib.request import pathname2url + +from test.support import import_helper +from test.support import is_emscripten, is_wasi +from test.support import infinite_recursion +from test.support import os_helper +from test.support.os_helper import TESTFN, FS_NONASCII, FakePath +from test.test_pathlib import test_pathlib_abc +from test.test_pathlib.test_pathlib_abc import needs_posix, needs_windows, needs_symlinks + +try: + import grp, pwd +except ImportError: + grp = pwd = None + + +root_in_posix = False +if hasattr(os, 'geteuid'): + root_in_posix = (os.geteuid() == 0) + +# +# Tests for the pure classes. +# + +class PurePathTest(test_pathlib_abc.DummyPurePathTest): + cls = pathlib.PurePath + + # Make sure any symbolic links in the base test path are resolved. + base = os.path.realpath(TESTFN) + + # Keys are canonical paths, values are list of tuples of arguments + # supposed to produce equal paths. + equivalences = { + 'a/b': [ + ('a', 'b'), ('a/', 'b'), ('a', 'b/'), ('a/', 'b/'), + ('a/b/',), ('a//b',), ('a//b//',), + # Empty components get removed. + ('', 'a', 'b'), ('a', '', 'b'), ('a', 'b', ''), + ], + '/b/c/d': [ + ('a', '/b/c', 'd'), ('/a', '/b/c', 'd'), + # Empty components get removed. + ('/', 'b', '', 'c/d'), ('/', '', 'b/c/d'), ('', '/b/c/d'), + ], + } + + def test_concrete_class(self): + if self.cls is pathlib.PurePath: + expected = pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath + else: + expected = self.cls + p = self.cls('a') + self.assertIs(type(p), expected) + + def test_concrete_parser(self): + if self.cls is pathlib.PurePosixPath: + expected = posixpath + elif self.cls is pathlib.PureWindowsPath: + expected = ntpath + else: + expected = os.path + p = self.cls('a') + self.assertIs(p.parser, expected) + + def test_different_parsers_unequal(self): + p = self.cls('a') + if p.parser is posixpath: + q = pathlib.PureWindowsPath('a') + else: + q = pathlib.PurePosixPath('a') + self.assertNotEqual(p, q) + + def test_different_parsers_unordered(self): + p = self.cls('a') + if p.parser is posixpath: + q = pathlib.PureWindowsPath('a') + else: + q = pathlib.PurePosixPath('a') + with self.assertRaises(TypeError): + p < q + with self.assertRaises(TypeError): + p <= q + with self.assertRaises(TypeError): + p > q + with self.assertRaises(TypeError): + p >= q + + def test_constructor_nested(self): + P = self.cls + P(FakePath("a/b/c")) + self.assertEqual(P(P('a')), P('a')) + self.assertEqual(P(P('a'), 'b'), P('a/b')) + self.assertEqual(P(P('a'), P('b')), P('a/b')) + self.assertEqual(P(P('a'), P('b'), P('c')), P(FakePath("a/b/c"))) + self.assertEqual(P(P('./a:b')), P('./a:b')) + + @needs_windows + def test_constructor_nested_foreign_flavour(self): + # See GH-125069. + p1 = pathlib.PurePosixPath('b/c:\\d') + p2 = pathlib.PurePosixPath('b/', 'c:\\d') + self.assertEqual(p1, p2) + self.assertEqual(self.cls(p1), self.cls('b/c:/d')) + self.assertEqual(self.cls(p2), self.cls('b/c:/d')) + + def _check_parse_path(self, raw_path, *expected): + sep = self.parser.sep + actual = self.cls._parse_path(raw_path.replace('/', sep)) + self.assertEqual(actual, expected) + if altsep := self.parser.altsep: + actual = self.cls._parse_path(raw_path.replace('/', altsep)) + self.assertEqual(actual, expected) + + def test_parse_path_common(self): + check = self._check_parse_path + sep = self.parser.sep + check('', '', '', []) + check('a', '', '', ['a']) + check('a/', '', '', ['a']) + check('a/b', '', '', ['a', 'b']) + check('a/b/', '', '', ['a', 'b']) + check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) + check('a/b//c/d', '', '', ['a', 'b', 'c', 'd']) + check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) + check('.', '', '', []) + check('././b', '', '', ['b']) + check('a/./b', '', '', ['a', 'b']) + check('a/./.', '', '', ['a']) + check('/a/b', '', sep, ['a', 'b']) + + def test_empty_path(self): + # The empty path points to '.' + p = self.cls('') + self.assertEqual(str(p), '.') + # Special case for the empty path. + self._check_str('.', ('',)) + + def test_parts_interning(self): + P = self.cls + p = P('/usr/bin/foo') + q = P('/usr/local/bin') + # 'usr' + self.assertIs(p.parts[1], q.parts[1]) + # 'bin' + self.assertIs(p.parts[2], q.parts[3]) + + def test_join_nested(self): + P = self.cls + p = P('a/b').joinpath(P('c')) + self.assertEqual(p, P('a/b/c')) + + def test_div_nested(self): + P = self.cls + p = P('a/b') / P('c') + self.assertEqual(p, P('a/b/c')) + + def test_pickling_common(self): + P = self.cls + for pathstr in ('a', 'a/', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c', 'a/b/c/'): + with self.subTest(pathstr=pathstr): + p = P(pathstr) + for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): + dumped = pickle.dumps(p, proto) + pp = pickle.loads(dumped) + self.assertIs(pp.__class__, p.__class__) + self.assertEqual(pp, p) + self.assertEqual(hash(pp), hash(p)) + self.assertEqual(str(pp), str(p)) + + def test_repr_common(self): + for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): + with self.subTest(pathstr=pathstr): + p = self.cls(pathstr) + clsname = p.__class__.__name__ + r = repr(p) + # The repr() is in the form ClassName("forward-slashes path"). + self.assertTrue(r.startswith(clsname + '('), r) + self.assertTrue(r.endswith(')'), r) + inner = r[len(clsname) + 1 : -1] + self.assertEqual(eval(inner), p.as_posix()) + + def test_fspath_common(self): + P = self.cls + p = P('a/b') + self._check_str(p.__fspath__(), ('a/b',)) + self._check_str(os.fspath(p), ('a/b',)) + + def test_bytes_exc_message(self): + P = self.cls + message = (r"argument should be a str or an os\.PathLike object " + r"where __fspath__ returns a str, not 'bytes'") + with self.assertRaisesRegex(TypeError, message): + P(b'a') + with self.assertRaisesRegex(TypeError, message): + P(b'a', 'b') + with self.assertRaisesRegex(TypeError, message): + P('a', b'b') + + def test_as_bytes_common(self): + sep = os.fsencode(self.sep) + P = self.cls + self.assertEqual(bytes(P('a/b')), b'a' + sep + b'b') + + def test_eq_common(self): + P = self.cls + self.assertEqual(P('a/b'), P('a/b')) + self.assertEqual(P('a/b'), P('a', 'b')) + self.assertNotEqual(P('a/b'), P('a')) + self.assertNotEqual(P('a/b'), P('/a/b')) + self.assertNotEqual(P('a/b'), P()) + self.assertNotEqual(P('/a/b'), P('/')) + self.assertNotEqual(P(), P('/')) + self.assertNotEqual(P(), "") + self.assertNotEqual(P(), {}) + self.assertNotEqual(P(), int) + + def test_equivalences(self, equivalences=None): + if equivalences is None: + equivalences = self.equivalences + for k, tuples in equivalences.items(): + canon = k.replace('/', self.sep) + posix = k.replace(self.sep, '/') + if canon != posix: + tuples = tuples + [ + tuple(part.replace('/', self.sep) for part in t) + for t in tuples + ] + tuples.append((posix, )) + pcanon = self.cls(canon) + for t in tuples: + p = self.cls(*t) + self.assertEqual(p, pcanon, "failed with args {}".format(t)) + self.assertEqual(hash(p), hash(pcanon)) + self.assertEqual(str(p), canon) + self.assertEqual(p.as_posix(), posix) + + def test_ordering_common(self): + # Ordering is tuple-alike. + def assertLess(a, b): + self.assertLess(a, b) + self.assertGreater(b, a) + P = self.cls + a = P('a') + b = P('a/b') + c = P('abc') + d = P('b') + assertLess(a, b) + assertLess(a, c) + assertLess(a, d) + assertLess(b, c) + assertLess(c, d) + P = self.cls + a = P('/a') + b = P('/a/b') + c = P('/abc') + d = P('/b') + assertLess(a, b) + assertLess(a, c) + assertLess(a, d) + assertLess(b, c) + assertLess(c, d) + with self.assertRaises(TypeError): + P() < {} + + def test_as_uri_common(self): + P = self.cls + with self.assertRaises(ValueError): + P('a').as_uri() + with self.assertRaises(ValueError): + P().as_uri() + + def test_repr_roundtrips(self): + for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): + with self.subTest(pathstr=pathstr): + p = self.cls(pathstr) + r = repr(p) + # The repr() roundtrips. + q = eval(r, pathlib.__dict__) + self.assertIs(q.__class__, p.__class__) + self.assertEqual(q, p) + self.assertEqual(repr(q), r) + + def test_name_empty(self): + P = self.cls + self.assertEqual(P('').name, '') + self.assertEqual(P('.').name, '') + self.assertEqual(P('/a/b/.').name, 'b') + + def test_stem_empty(self): + P = self.cls + self.assertEqual(P('').stem, '') + self.assertEqual(P('.').stem, '') + + def test_with_name_empty(self): + P = self.cls + self.assertRaises(ValueError, P('').with_name, 'd.xml') + self.assertRaises(ValueError, P('.').with_name, 'd.xml') + self.assertRaises(ValueError, P('/').with_name, 'd.xml') + self.assertRaises(ValueError, P('a/b').with_name, '') + self.assertRaises(ValueError, P('a/b').with_name, '.') + + def test_with_stem_empty(self): + P = self.cls + self.assertRaises(ValueError, P('').with_stem, 'd') + self.assertRaises(ValueError, P('.').with_stem, 'd') + self.assertRaises(ValueError, P('/').with_stem, 'd') + self.assertRaises(ValueError, P('a/b').with_stem, '') + self.assertRaises(ValueError, P('a/b').with_stem, '.') + + def test_relative_to_several_args(self): + P = self.cls + p = P('a/b') + with self.assertWarns(DeprecationWarning): + p.relative_to('a', 'b') + p.relative_to('a', 'b', walk_up=True) + + def test_is_relative_to_several_args(self): + P = self.cls + p = P('a/b') + with self.assertWarns(DeprecationWarning): + p.is_relative_to('a', 'b') + + def test_is_reserved_deprecated(self): + P = self.cls + p = P('a/b') + with self.assertWarns(DeprecationWarning): + p.is_reserved() + + def test_match_empty(self): + P = self.cls + self.assertRaises(ValueError, P('a').match, '') + self.assertRaises(ValueError, P('a').match, '.') + + @needs_posix + def test_parse_path_posix(self): + check = self._check_parse_path + # Collapsing of excess leading slashes, except for the double-slash + # special case. + check('//a/b', '', '//', ['a', 'b']) + check('///a/b', '', '/', ['a', 'b']) + check('////a/b', '', '/', ['a', 'b']) + # Paths which look like NT paths aren't treated specially. + check('c:a', '', '', ['c:a',]) + check('c:\\a', '', '', ['c:\\a',]) + check('\\a', '', '', ['\\a',]) + + @needs_posix + def test_eq_posix(self): + P = self.cls + self.assertNotEqual(P('a/b'), P('A/b')) + self.assertEqual(P('/a'), P('///a')) + self.assertNotEqual(P('/a'), P('//a')) + + @needs_posix + def test_as_uri_posix(self): + P = self.cls + self.assertEqual(P('/').as_uri(), 'file:///') + self.assertEqual(P('/a/b.c').as_uri(), 'file:///a/b.c') + self.assertEqual(P('/a/b%#c').as_uri(), 'file:///a/b%25%23c') + + @needs_posix + def test_as_uri_non_ascii(self): + from urllib.parse import quote_from_bytes + P = self.cls + try: + os.fsencode('\xe9') + except UnicodeEncodeError: + self.skipTest("\\xe9 cannot be encoded to the filesystem encoding") + self.assertEqual(P('/a/b\xe9').as_uri(), + 'file:///a/b' + quote_from_bytes(os.fsencode('\xe9'))) + + @needs_posix + def test_parse_windows_path(self): + P = self.cls + p = P('c:', 'a', 'b') + pp = P(pathlib.PureWindowsPath('c:\\a\\b')) + self.assertEqual(p, pp) + + windows_equivalences = { + './a:b': [ ('./a:b',) ], + 'c:a': [ ('c:', 'a'), ('c:', 'a/'), ('.', 'c:', 'a') ], + 'c:/a': [ + ('c:/', 'a'), ('c:', '/', 'a'), ('c:', '/a'), + ('/z', 'c:/', 'a'), ('//x/y', 'c:/', 'a'), + ], + '//a/b/': [ ('//a/b',) ], + '//a/b/c': [ + ('//a/b', 'c'), ('//a/b/', 'c'), + ], + } + + @needs_windows + def test_equivalences_windows(self): + self.test_equivalences(self.windows_equivalences) + + @needs_windows + def test_parse_path_windows(self): + check = self._check_parse_path + # First part is anchored. + check('c:', 'c:', '', []) + check('c:/', 'c:', '\\', []) + check('/', '', '\\', []) + check('c:a', 'c:', '', ['a']) + check('c:/a', 'c:', '\\', ['a']) + check('/a', '', '\\', ['a']) + # UNC paths. + check('//', '\\\\', '', []) + check('//a', '\\\\a', '', []) + check('//a/', '\\\\a\\', '', []) + check('//a/b', '\\\\a\\b', '\\', []) + check('//a/b/', '\\\\a\\b', '\\', []) + check('//a/b/c', '\\\\a\\b', '\\', ['c']) + # Collapsing and stripping excess slashes. + check('Z://b//c/d/', 'Z:', '\\', ['b', 'c', 'd']) + # UNC paths. + check('//b/c//d', '\\\\b\\c', '\\', ['d']) + # Extended paths. + check('//./c:', '\\\\.\\c:', '', []) + check('//?/c:/', '\\\\?\\c:', '\\', []) + check('//?/c:/a', '\\\\?\\c:', '\\', ['a']) + # Extended UNC paths (format is "\\?\UNC\server\share"). + check('//?', '\\\\?', '', []) + check('//?/', '\\\\?\\', '', []) + check('//?/UNC', '\\\\?\\UNC', '', []) + check('//?/UNC/', '\\\\?\\UNC\\', '', []) + check('//?/UNC/b', '\\\\?\\UNC\\b', '', []) + check('//?/UNC/b/', '\\\\?\\UNC\\b\\', '', []) + check('//?/UNC/b/c', '\\\\?\\UNC\\b\\c', '\\', []) + check('//?/UNC/b/c/', '\\\\?\\UNC\\b\\c', '\\', []) + check('//?/UNC/b/c/d', '\\\\?\\UNC\\b\\c', '\\', ['d']) + # UNC device paths + check('//./BootPartition/', '\\\\.\\BootPartition', '\\', []) + check('//?/BootPartition/', '\\\\?\\BootPartition', '\\', []) + check('//./PhysicalDrive0', '\\\\.\\PhysicalDrive0', '', []) + check('//?/Volume{}/', '\\\\?\\Volume{}', '\\', []) + check('//./nul', '\\\\.\\nul', '', []) + # Paths to files with NTFS alternate data streams + check('./c:s', '', '', ['c:s']) + check('cc:s', '', '', ['cc:s']) + check('C:c:s', 'C:', '', ['c:s']) + check('C:/c:s', 'C:', '\\', ['c:s']) + check('D:a/c:b', 'D:', '', ['a', 'c:b']) + check('D:/a/c:b', 'D:', '\\', ['a', 'c:b']) + + @needs_windows + def test_eq_windows(self): + P = self.cls + self.assertEqual(P('c:a/b'), P('c:a/b')) + self.assertEqual(P('c:a/b'), P('c:', 'a', 'b')) + self.assertNotEqual(P('c:a/b'), P('d:a/b')) + self.assertNotEqual(P('c:a/b'), P('c:/a/b')) + self.assertNotEqual(P('/a/b'), P('c:/a/b')) + # Case-insensitivity. + self.assertEqual(P('a/B'), P('A/b')) + self.assertEqual(P('C:a/B'), P('c:A/b')) + self.assertEqual(P('//Some/SHARE/a/B'), P('//somE/share/A/b')) + self.assertEqual(P('\u0130'), P('i\u0307')) + + @needs_windows + def test_as_uri_windows(self): + P = self.cls + with self.assertRaises(ValueError): + P('/a/b').as_uri() + with self.assertRaises(ValueError): + P('c:a/b').as_uri() + self.assertEqual(P('c:/').as_uri(), 'file:///c:/') + self.assertEqual(P('c:/a/b.c').as_uri(), 'file:///c:/a/b.c') + self.assertEqual(P('c:/a/b%#c').as_uri(), 'file:///c:/a/b%25%23c') + self.assertEqual(P('//some/share/').as_uri(), 'file://some/share/') + self.assertEqual(P('//some/share/a/b.c').as_uri(), + 'file://some/share/a/b.c') + + from urllib.parse import quote_from_bytes + QUOTED_FS_NONASCII = quote_from_bytes(os.fsencode(FS_NONASCII)) + self.assertEqual(P('c:/a/b' + FS_NONASCII).as_uri(), + 'file:///c:/a/b' + QUOTED_FS_NONASCII) + self.assertEqual(P('//some/share/a/b%#c' + FS_NONASCII).as_uri(), + 'file://some/share/a/b%25%23c' + QUOTED_FS_NONASCII) + + @needs_windows + def test_ordering_windows(self): + # Case-insensitivity. + def assertOrderedEqual(a, b): + self.assertLessEqual(a, b) + self.assertGreaterEqual(b, a) + P = self.cls + p = P('c:A/b') + q = P('C:a/B') + assertOrderedEqual(p, q) + self.assertFalse(p < q) + self.assertFalse(p > q) + p = P('//some/Share/A/b') + q = P('//Some/SHARE/a/B') + assertOrderedEqual(p, q) + self.assertFalse(p < q) + self.assertFalse(p > q) + + +class PurePosixPathTest(PurePathTest): + cls = pathlib.PurePosixPath + + +class PureWindowsPathTest(PurePathTest): + cls = pathlib.PureWindowsPath + + +class PurePathSubclassTest(PurePathTest): + class cls(pathlib.PurePath): + pass + + # repr() roundtripping is not supported in custom subclass. + test_repr_roundtrips = None + + +# +# Tests for the concrete classes. +# + +class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest): + """Tests for the FS-accessing functionalities of the Path classes.""" + cls = pathlib.Path + can_symlink = os_helper.can_symlink() + + def setUp(self): + super().setUp() + os.chmod(self.parser.join(self.base, 'dirE'), 0) + + def tearDown(self): + os.chmod(self.parser.join(self.base, 'dirE'), 0o777) + os_helper.rmtree(self.base) + + def tempdir(self): + d = os_helper._longpath(tempfile.mkdtemp(suffix='-dirD', + dir=os.getcwd())) + self.addCleanup(os_helper.rmtree, d) + return d + + def test_matches_pathbase_api(self): + our_names = {name for name in dir(self.cls) if name[0] != '_'} + our_names.remove('is_reserved') # only present in PurePath + path_names = {name for name in dir(pathlib._abc.PathBase) if name[0] != '_'} + self.assertEqual(our_names, path_names) + for attr_name in our_names: + if attr_name == 'parser': + # On Windows, Path.parser is ntpath, but PathBase.parser is + # posixpath, and so their docstrings differ. + continue + our_attr = getattr(self.cls, attr_name) + path_attr = getattr(pathlib._abc.PathBase, attr_name) + self.assertEqual(our_attr.__doc__, path_attr.__doc__) + + def test_concrete_class(self): + if self.cls is pathlib.Path: + expected = pathlib.WindowsPath if os.name == 'nt' else pathlib.PosixPath + else: + expected = self.cls + p = self.cls('a') + self.assertIs(type(p), expected) + + def test_unsupported_parser(self): + if self.cls.parser is os.path: + self.skipTest("path parser is supported") + else: + self.assertRaises(pathlib.UnsupportedOperation, self.cls) + + def _test_cwd(self, p): + q = self.cls(os.getcwd()) + self.assertEqual(p, q) + self.assertEqualNormCase(str(p), str(q)) + self.assertIs(type(p), type(q)) + self.assertTrue(p.is_absolute()) + + def test_cwd(self): + p = self.cls.cwd() + self._test_cwd(p) + + def test_absolute_common(self): + P = self.cls + + with mock.patch("os.getcwd") as getcwd: + getcwd.return_value = self.base + + # Simple relative paths. + self.assertEqual(str(P().absolute()), self.base) + self.assertEqual(str(P('.').absolute()), self.base) + self.assertEqual(str(P('a').absolute()), os.path.join(self.base, 'a')) + self.assertEqual(str(P('a', 'b', 'c').absolute()), os.path.join(self.base, 'a', 'b', 'c')) + + # Symlinks should not be resolved. + self.assertEqual(str(P('linkB', 'fileB').absolute()), os.path.join(self.base, 'linkB', 'fileB')) + self.assertEqual(str(P('brokenLink').absolute()), os.path.join(self.base, 'brokenLink')) + self.assertEqual(str(P('brokenLinkLoop').absolute()), os.path.join(self.base, 'brokenLinkLoop')) + + # '..' entries should be preserved and not normalised. + self.assertEqual(str(P('..').absolute()), os.path.join(self.base, '..')) + self.assertEqual(str(P('a', '..').absolute()), os.path.join(self.base, 'a', '..')) + self.assertEqual(str(P('..', 'b').absolute()), os.path.join(self.base, '..', 'b')) + + def _test_home(self, p): + q = self.cls(os.path.expanduser('~')) + self.assertEqual(p, q) + self.assertEqualNormCase(str(p), str(q)) + self.assertIs(type(p), type(q)) + self.assertTrue(p.is_absolute()) + + @unittest.skipIf( + pwd is None, reason="Test requires pwd module to get homedir." + ) + def test_home(self): + with os_helper.EnvironmentVarGuard() as env: + self._test_home(self.cls.home()) + + env.clear() + env['USERPROFILE'] = os.path.join(self.base, 'userprofile') + self._test_home(self.cls.home()) + + # bpo-38883: ignore `HOME` when set on windows + env['HOME'] = os.path.join(self.base, 'home') + self._test_home(self.cls.home()) + + @unittest.skipIf(is_wasi, "WASI has no user accounts.") + def test_expanduser_common(self): + P = self.cls + p = P('~') + self.assertEqual(p.expanduser(), P(os.path.expanduser('~'))) + p = P('foo') + self.assertEqual(p.expanduser(), p) + p = P('/~') + self.assertEqual(p.expanduser(), p) + p = P('../~') + self.assertEqual(p.expanduser(), p) + p = P(P('').absolute().anchor) / '~' + self.assertEqual(p.expanduser(), p) + p = P('~/a:b') + self.assertEqual(p.expanduser(), P(os.path.expanduser('~'), './a:b')) + + def test_with_segments(self): + class P(self.cls): + def __init__(self, *pathsegments, session_id): + super().__init__(*pathsegments) + self.session_id = session_id + + def with_segments(self, *pathsegments): + return type(self)(*pathsegments, session_id=self.session_id) + p = P(self.base, session_id=42) + self.assertEqual(42, p.absolute().session_id) + self.assertEqual(42, p.resolve().session_id) + if not is_wasi: # WASI has no user accounts. + self.assertEqual(42, p.with_segments('~').expanduser().session_id) + self.assertEqual(42, (p / 'fileA').rename(p / 'fileB').session_id) + self.assertEqual(42, (p / 'fileB').replace(p / 'fileA').session_id) + if self.can_symlink: + self.assertEqual(42, (p / 'linkA').readlink().session_id) + for path in p.iterdir(): + self.assertEqual(42, path.session_id) + for path in p.glob('*'): + self.assertEqual(42, path.session_id) + for path in p.rglob('*'): + self.assertEqual(42, path.session_id) + for dirpath, dirnames, filenames in p.walk(): + self.assertEqual(42, dirpath.session_id) + + def test_open_unbuffered(self): + p = self.cls(self.base) + with (p / 'fileA').open('rb', buffering=0) as f: + self.assertIsInstance(f, io.RawIOBase) + self.assertEqual(f.read().strip(), b"this is file A") + + def test_resolve_nonexist_relative_issue38671(self): + p = self.cls('non', 'exist') + + old_cwd = os.getcwd() + os.chdir(self.base) + try: + self.assertEqual(p.resolve(), self.cls(self.base, p)) + finally: + os.chdir(old_cwd) + + @os_helper.skip_unless_working_chmod + def test_chmod(self): + p = self.cls(self.base) / 'fileA' + mode = p.stat().st_mode + # Clear writable bit. + new_mode = mode & ~0o222 + p.chmod(new_mode) + self.assertEqual(p.stat().st_mode, new_mode) + # Set writable bit. + new_mode = mode | 0o222 + p.chmod(new_mode) + self.assertEqual(p.stat().st_mode, new_mode) + + # On Windows, os.chmod does not follow symlinks (issue #15411) + @needs_posix + @os_helper.skip_unless_working_chmod + def test_chmod_follow_symlinks_true(self): + p = self.cls(self.base) / 'linkA' + q = p.resolve() + mode = q.stat().st_mode + # Clear writable bit. + new_mode = mode & ~0o222 + p.chmod(new_mode, follow_symlinks=True) + self.assertEqual(q.stat().st_mode, new_mode) + # Set writable bit + new_mode = mode | 0o222 + p.chmod(new_mode, follow_symlinks=True) + self.assertEqual(q.stat().st_mode, new_mode) + + # XXX also need a test for lchmod. + + def _get_pw_name_or_skip_test(self, uid): + try: + return pwd.getpwuid(uid).pw_name + except KeyError: + self.skipTest( + "user %d doesn't have an entry in the system database" % uid) + + @unittest.skipUnless(pwd, "the pwd module is needed for this test") + def test_owner(self): + p = self.cls(self.base) / 'fileA' + expected_uid = p.stat().st_uid + expected_name = self._get_pw_name_or_skip_test(expected_uid) + + self.assertEqual(expected_name, p.owner()) + + @unittest.skipUnless(pwd, "the pwd module is needed for this test") + @unittest.skipUnless(root_in_posix, "test needs root privilege") + def test_owner_no_follow_symlinks(self): + all_users = [u.pw_uid for u in pwd.getpwall()] + if len(all_users) < 2: + self.skipTest("test needs more than one user") + + target = self.cls(self.base) / 'fileA' + link = self.cls(self.base) / 'linkA' + + uid_1, uid_2 = all_users[:2] + os.chown(target, uid_1, -1) + os.chown(link, uid_2, -1, follow_symlinks=False) + + expected_uid = link.stat(follow_symlinks=False).st_uid + expected_name = self._get_pw_name_or_skip_test(expected_uid) + + self.assertEqual(expected_uid, uid_2) + self.assertEqual(expected_name, link.owner(follow_symlinks=False)) + + def _get_gr_name_or_skip_test(self, gid): + try: + return grp.getgrgid(gid).gr_name + except KeyError: + self.skipTest( + "group %d doesn't have an entry in the system database" % gid) + + @unittest.skipUnless(grp, "the grp module is needed for this test") + def test_group(self): + p = self.cls(self.base) / 'fileA' + expected_gid = p.stat().st_gid + expected_name = self._get_gr_name_or_skip_test(expected_gid) + + self.assertEqual(expected_name, p.group()) + + @unittest.skipUnless(grp, "the grp module is needed for this test") + @unittest.skipUnless(root_in_posix, "test needs root privilege") + def test_group_no_follow_symlinks(self): + all_groups = [g.gr_gid for g in grp.getgrall()] + if len(all_groups) < 2: + self.skipTest("test needs more than one group") + + target = self.cls(self.base) / 'fileA' + link = self.cls(self.base) / 'linkA' + + gid_1, gid_2 = all_groups[:2] + os.chown(target, -1, gid_1) + os.chown(link, -1, gid_2, follow_symlinks=False) + + expected_gid = link.stat(follow_symlinks=False).st_gid + expected_name = self._get_gr_name_or_skip_test(expected_gid) + + self.assertEqual(expected_gid, gid_2) + self.assertEqual(expected_name, link.group(follow_symlinks=False)) + + def test_unlink(self): + p = self.cls(self.base) / 'fileA' + p.unlink() + self.assertFileNotFound(p.stat) + self.assertFileNotFound(p.unlink) + + def test_unlink_missing_ok(self): + p = self.cls(self.base) / 'fileAAA' + self.assertFileNotFound(p.unlink) + p.unlink(missing_ok=True) + + def test_rmdir(self): + p = self.cls(self.base) / 'dirA' + for q in p.iterdir(): + q.unlink() + p.rmdir() + self.assertFileNotFound(p.stat) + self.assertFileNotFound(p.unlink) + + @os_helper.skip_unless_hardlink + def test_hardlink_to(self): + P = self.cls(self.base) + target = P / 'fileA' + size = target.stat().st_size + # linking to another path. + link = P / 'dirA' / 'fileAA' + link.hardlink_to(target) + self.assertEqual(link.stat().st_size, size) + self.assertTrue(os.path.samefile(target, link)) + self.assertTrue(target.exists()) + # Linking to a str of a relative path. + link2 = P / 'dirA' / 'fileAAA' + target2 = self.parser.join(TESTFN, 'fileA') + link2.hardlink_to(target2) + self.assertEqual(os.stat(target2).st_size, size) + self.assertTrue(link2.exists()) + + @unittest.skipIf(hasattr(os, "link"), "os.link() is present") + def test_hardlink_to_unsupported(self): + P = self.cls(self.base) + p = P / 'fileA' + # linking to another path. + q = P / 'dirA' / 'fileAA' + with self.assertRaises(pathlib.UnsupportedOperation): + q.hardlink_to(p) + + def test_rename(self): + P = self.cls(self.base) + p = P / 'fileA' + size = p.stat().st_size + # Renaming to another path. + q = P / 'dirA' / 'fileAA' + renamed_p = p.rename(q) + self.assertEqual(renamed_p, q) + self.assertEqual(q.stat().st_size, size) + self.assertFileNotFound(p.stat) + # Renaming to a str of a relative path. + r = self.parser.join(TESTFN, 'fileAAA') + renamed_q = q.rename(r) + self.assertEqual(renamed_q, self.cls(r)) + self.assertEqual(os.stat(r).st_size, size) + self.assertFileNotFound(q.stat) + + def test_replace(self): + P = self.cls(self.base) + p = P / 'fileA' + size = p.stat().st_size + # Replacing a non-existing path. + q = P / 'dirA' / 'fileAA' + replaced_p = p.replace(q) + self.assertEqual(replaced_p, q) + self.assertEqual(q.stat().st_size, size) + self.assertFileNotFound(p.stat) + # Replacing another (existing) path. + r = self.parser.join(TESTFN, 'dirB', 'fileB') + replaced_q = q.replace(r) + self.assertEqual(replaced_q, self.cls(r)) + self.assertEqual(os.stat(r).st_size, size) + self.assertFileNotFound(q.stat) + + def test_touch_common(self): + P = self.cls(self.base) + p = P / 'newfileA' + self.assertFalse(p.exists()) + p.touch() + self.assertTrue(p.exists()) + st = p.stat() + old_mtime = st.st_mtime + old_mtime_ns = st.st_mtime_ns + # Rewind the mtime sufficiently far in the past to work around + # filesystem-specific timestamp granularity. + os.utime(str(p), (old_mtime - 10, old_mtime - 10)) + # The file mtime should be refreshed by calling touch() again. + p.touch() + st = p.stat() + self.assertGreaterEqual(st.st_mtime_ns, old_mtime_ns) + self.assertGreaterEqual(st.st_mtime, old_mtime) + # Now with exist_ok=False. + p = P / 'newfileB' + self.assertFalse(p.exists()) + p.touch(mode=0o700, exist_ok=False) + self.assertTrue(p.exists()) + self.assertRaises(OSError, p.touch, exist_ok=False) + + def test_touch_nochange(self): + P = self.cls(self.base) + p = P / 'fileA' + p.touch() + with p.open('rb') as f: + self.assertEqual(f.read().strip(), b"this is file A") + + def test_mkdir(self): + P = self.cls(self.base) + p = P / 'newdirA' + self.assertFalse(p.exists()) + p.mkdir() + self.assertTrue(p.exists()) + self.assertTrue(p.is_dir()) + with self.assertRaises(OSError) as cm: + p.mkdir() + self.assertEqual(cm.exception.errno, errno.EEXIST) + + def test_mkdir_parents(self): + # Creating a chain of directories. + p = self.cls(self.base, 'newdirB', 'newdirC') + self.assertFalse(p.exists()) + with self.assertRaises(OSError) as cm: + p.mkdir() + self.assertEqual(cm.exception.errno, errno.ENOENT) + p.mkdir(parents=True) + self.assertTrue(p.exists()) + self.assertTrue(p.is_dir()) + with self.assertRaises(OSError) as cm: + p.mkdir(parents=True) + self.assertEqual(cm.exception.errno, errno.EEXIST) + # Test `mode` arg. + mode = stat.S_IMODE(p.stat().st_mode) # Default mode. + p = self.cls(self.base, 'newdirD', 'newdirE') + p.mkdir(0o555, parents=True) + self.assertTrue(p.exists()) + self.assertTrue(p.is_dir()) + if os.name != 'nt': + # The directory's permissions follow the mode argument. + self.assertEqual(stat.S_IMODE(p.stat().st_mode), 0o7555 & mode) + # The parent's permissions follow the default process settings. + self.assertEqual(stat.S_IMODE(p.parent.stat().st_mode), mode) + + def test_mkdir_exist_ok(self): + p = self.cls(self.base, 'dirB') + st_ctime_first = p.stat().st_ctime + self.assertTrue(p.exists()) + self.assertTrue(p.is_dir()) + with self.assertRaises(FileExistsError) as cm: + p.mkdir() + self.assertEqual(cm.exception.errno, errno.EEXIST) + p.mkdir(exist_ok=True) + self.assertTrue(p.exists()) + self.assertEqual(p.stat().st_ctime, st_ctime_first) + + def test_mkdir_exist_ok_with_parent(self): + p = self.cls(self.base, 'dirC') + self.assertTrue(p.exists()) + with self.assertRaises(FileExistsError) as cm: + p.mkdir() + self.assertEqual(cm.exception.errno, errno.EEXIST) + p = p / 'newdirC' + p.mkdir(parents=True) + st_ctime_first = p.stat().st_ctime + self.assertTrue(p.exists()) + with self.assertRaises(FileExistsError) as cm: + p.mkdir(parents=True) + self.assertEqual(cm.exception.errno, errno.EEXIST) + p.mkdir(parents=True, exist_ok=True) + self.assertTrue(p.exists()) + self.assertEqual(p.stat().st_ctime, st_ctime_first) + + @unittest.skipIf(is_emscripten, "FS root cannot be modified on Emscripten.") + def test_mkdir_exist_ok_root(self): + # Issue #25803: A drive root could raise PermissionError on Windows. + self.cls('/').resolve().mkdir(exist_ok=True) + self.cls('/').resolve().mkdir(parents=True, exist_ok=True) + + @needs_windows # XXX: not sure how to test this on POSIX. + def test_mkdir_with_unknown_drive(self): + for d in 'ZYXWVUTSRQPONMLKJIHGFEDCBA': + p = self.cls(d + ':\\') + if not p.is_dir(): + break + else: + self.skipTest("cannot find a drive that doesn't exist") + with self.assertRaises(OSError): + (p / 'child' / 'path').mkdir(parents=True) + + def test_mkdir_with_child_file(self): + p = self.cls(self.base, 'dirB', 'fileB') + self.assertTrue(p.exists()) + # An exception is raised when the last path component is an existing + # regular file, regardless of whether exist_ok is true or not. + with self.assertRaises(FileExistsError) as cm: + p.mkdir(parents=True) + self.assertEqual(cm.exception.errno, errno.EEXIST) + with self.assertRaises(FileExistsError) as cm: + p.mkdir(parents=True, exist_ok=True) + self.assertEqual(cm.exception.errno, errno.EEXIST) + + def test_mkdir_no_parents_file(self): + p = self.cls(self.base, 'fileA') + self.assertTrue(p.exists()) + # An exception is raised when the last path component is an existing + # regular file, regardless of whether exist_ok is true or not. + with self.assertRaises(FileExistsError) as cm: + p.mkdir() + self.assertEqual(cm.exception.errno, errno.EEXIST) + with self.assertRaises(FileExistsError) as cm: + p.mkdir(exist_ok=True) + self.assertEqual(cm.exception.errno, errno.EEXIST) + + def test_mkdir_concurrent_parent_creation(self): + for pattern_num in range(32): + p = self.cls(self.base, 'dirCPC%d' % pattern_num) + self.assertFalse(p.exists()) + + real_mkdir = os.mkdir + def my_mkdir(path, mode=0o777): + path = str(path) + # Emulate another process that would create the directory + # just before we try to create it ourselves. We do it + # in all possible pattern combinations, assuming that this + # function is called at most 5 times (dirCPC/dir1/dir2, + # dirCPC/dir1, dirCPC, dirCPC/dir1, dirCPC/dir1/dir2). + if pattern.pop(): + real_mkdir(path, mode) # From another process. + concurrently_created.add(path) + real_mkdir(path, mode) # Our real call. + + pattern = [bool(pattern_num & (1 << n)) for n in range(5)] + concurrently_created = set() + p12 = p / 'dir1' / 'dir2' + try: + with mock.patch("os.mkdir", my_mkdir): + p12.mkdir(parents=True, exist_ok=False) + except FileExistsError: + self.assertIn(str(p12), concurrently_created) + else: + self.assertNotIn(str(p12), concurrently_created) + self.assertTrue(p.exists()) + + @needs_symlinks + def test_symlink_to(self): + P = self.cls(self.base) + target = P / 'fileA' + # Symlinking a path target. + link = P / 'dirA' / 'linkAA' + link.symlink_to(target) + self.assertEqual(link.stat(), target.stat()) + self.assertNotEqual(link.lstat(), target.stat()) + # Symlinking a str target. + link = P / 'dirA' / 'linkAAA' + link.symlink_to(str(target)) + self.assertEqual(link.stat(), target.stat()) + self.assertNotEqual(link.lstat(), target.stat()) + self.assertFalse(link.is_dir()) + # Symlinking to a directory. + target = P / 'dirB' + link = P / 'dirA' / 'linkAAAA' + link.symlink_to(target, target_is_directory=True) + self.assertEqual(link.stat(), target.stat()) + self.assertNotEqual(link.lstat(), target.stat()) + self.assertTrue(link.is_dir()) + self.assertTrue(list(link.iterdir())) + + @unittest.skipIf(hasattr(os, "symlink"), "os.symlink() is present") + def test_symlink_to_unsupported(self): + P = self.cls(self.base) + p = P / 'fileA' + # linking to another path. + q = P / 'dirA' / 'fileAA' + with self.assertRaises(pathlib.UnsupportedOperation): + q.symlink_to(p) + + def test_is_junction(self): + P = self.cls(self.base) + + with mock.patch.object(P.parser, 'isjunction'): + self.assertEqual(P.is_junction(), P.parser.isjunction.return_value) + P.parser.isjunction.assert_called_once_with(P) + + @unittest.skipUnless(hasattr(os, "mkfifo"), "os.mkfifo() required") + @unittest.skipIf(sys.platform == "vxworks", + "fifo requires special path on VxWorks") + def test_is_fifo_true(self): + P = self.cls(self.base, 'myfifo') + try: + os.mkfifo(str(P)) + except PermissionError as e: + self.skipTest('os.mkfifo(): %s' % e) + self.assertTrue(P.is_fifo()) + self.assertFalse(P.is_socket()) + self.assertFalse(P.is_file()) + self.assertIs(self.cls(self.base, 'myfifo\udfff').is_fifo(), False) + self.assertIs(self.cls(self.base, 'myfifo\x00').is_fifo(), False) + + @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") + @unittest.skipIf( + is_emscripten, "Unix sockets are not implemented on Emscripten." + ) + @unittest.skipIf( + is_wasi, "Cannot create socket on WASI." + ) + def test_is_socket_true(self): + P = self.cls(self.base, 'mysock') + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.addCleanup(sock.close) + try: + sock.bind(str(P)) + except OSError as e: + if (isinstance(e, PermissionError) or + "AF_UNIX path too long" in str(e)): + self.skipTest("cannot bind Unix socket: " + str(e)) + self.assertTrue(P.is_socket()) + self.assertFalse(P.is_fifo()) + self.assertFalse(P.is_file()) + self.assertIs(self.cls(self.base, 'mysock\udfff').is_socket(), False) + self.assertIs(self.cls(self.base, 'mysock\x00').is_socket(), False) + + def test_is_char_device_true(self): + # os.devnull should generally be a char device. + P = self.cls(os.devnull) + if not P.exists(): + self.skipTest("null device required") + self.assertTrue(P.is_char_device()) + self.assertFalse(P.is_block_device()) + self.assertFalse(P.is_file()) + self.assertIs(self.cls(f'{os.devnull}\udfff').is_char_device(), False) + self.assertIs(self.cls(f'{os.devnull}\x00').is_char_device(), False) + + def test_is_mount_root(self): + if os.name == 'nt': + R = self.cls('c:\\') + else: + R = self.cls('/') + self.assertTrue(R.is_mount()) + self.assertFalse((R / '\udfff').is_mount()) + + def test_passing_kwargs_deprecated(self): + with self.assertWarns(DeprecationWarning): + self.cls(foo="bar") + + def setUpWalk(self): + super().setUpWalk() + sub21_path= self.sub2_path / "SUB21" + tmp5_path = sub21_path / "tmp3" + broken_link3_path = self.sub2_path / "broken_link3" + + os.makedirs(sub21_path) + tmp5_path.write_text("I am tmp5, blame test_pathlib.") + if self.can_symlink: + os.symlink(tmp5_path, broken_link3_path) + self.sub2_tree[2].append('broken_link3') + self.sub2_tree[2].sort() + if not is_emscripten: + # Emscripten fails with inaccessible directories. + os.chmod(sub21_path, 0) + try: + os.listdir(sub21_path) + except PermissionError: + self.sub2_tree[1].append('SUB21') + else: + os.chmod(sub21_path, stat.S_IRWXU) + os.unlink(tmp5_path) + os.rmdir(sub21_path) + + def test_walk_bad_dir(self): + self.setUpWalk() + errors = [] + walk_it = self.walk_path.walk(on_error=errors.append) + root, dirs, files = next(walk_it) + self.assertEqual(errors, []) + dir1 = 'SUB1' + path1 = root / dir1 + path1new = (root / dir1).with_suffix(".new") + path1.rename(path1new) + try: + roots = [r for r, _, _ in walk_it] + self.assertTrue(errors) + self.assertNotIn(path1, roots) + self.assertNotIn(path1new, roots) + for dir2 in dirs: + if dir2 != dir1: + self.assertIn(root / dir2, roots) + finally: + path1new.rename(path1) + + def test_walk_many_open_files(self): + depth = 30 + base = self.cls(self.base, 'deep') + path = self.cls(base, *(['d']*depth)) + path.mkdir(parents=True) + + iters = [base.walk(top_down=False) for _ in range(100)] + for i in range(depth + 1): + expected = (path, ['d'] if i else [], []) + for it in iters: + self.assertEqual(next(it), expected) + path = path.parent + + iters = [base.walk(top_down=True) for _ in range(100)] + path = base + for i in range(depth + 1): + expected = (path, ['d'] if i < depth else [], []) + for it in iters: + self.assertEqual(next(it), expected) + path = path / 'd' + + def test_walk_above_recursion_limit(self): + recursion_limit = 40 + # directory_depth > recursion_limit + directory_depth = recursion_limit + 10 + base = self.cls(self.base, 'deep') + path = base.joinpath(*(['d'] * directory_depth)) + path.mkdir(parents=True) + + with infinite_recursion(recursion_limit): + list(base.walk()) + list(base.walk(top_down=False)) + + def test_glob_empty_pattern(self): + p = self.cls('') + with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'): + list(p.glob('')) + with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'): + list(p.glob('.')) + with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'): + list(p.glob('./')) + + def test_glob_many_open_files(self): + depth = 30 + P = self.cls + p = base = P(self.base) / 'deep' + p.mkdir() + for _ in range(depth): + p /= 'd' + p.mkdir() + pattern = '/'.join(['*'] * depth) + iters = [base.glob(pattern) for j in range(100)] + for it in iters: + self.assertEqual(next(it), p) + iters = [base.rglob('d') for j in range(100)] + p = base + for i in range(depth): + p = p / 'd' + for it in iters: + self.assertEqual(next(it), p) + + def test_glob_above_recursion_limit(self): + recursion_limit = 50 + # directory_depth > recursion_limit + directory_depth = recursion_limit + 10 + base = self.cls(self.base, 'deep') + path = base.joinpath(*(['d'] * directory_depth)) + path.mkdir(parents=True) + + with infinite_recursion(recursion_limit): + list(base.glob('**/')) + + def test_glob_pathlike(self): + P = self.cls + p = P(self.base) + pattern = "dir*/file*" + expect = {p / "dirB/fileB", p / "dirC/fileC"} + self.assertEqual(expect, set(p.glob(P(pattern)))) + self.assertEqual(expect, set(p.glob(FakePath(pattern)))) + + @needs_symlinks + def test_glob_dot(self): + P = self.cls + with os_helper.change_cwd(P(self.base, "dirC")): + self.assertEqual( + set(P('.').glob('*')), {P("fileC"), P("novel.txt"), P("dirD")}) + self.assertEqual( + set(P('.').glob('**')), {P("fileC"), P("novel.txt"), P("dirD"), P("dirD/fileD"), P(".")}) + self.assertEqual( + set(P('.').glob('**/*')), {P("fileC"), P("novel.txt"), P("dirD"), P("dirD/fileD")}) + self.assertEqual( + set(P('.').glob('**/*/*')), {P("dirD/fileD")}) + + def test_glob_inaccessible(self): + P = self.cls + p = P(self.base, "mydir1", "mydir2") + p.mkdir(parents=True) + p.parent.chmod(0) + self.assertEqual(set(p.glob('*')), set()) + + def test_rglob_pathlike(self): + P = self.cls + p = P(self.base, "dirC") + pattern = "**/file*" + expect = {p / "fileC", p / "dirD/fileD"} + self.assertEqual(expect, set(p.rglob(P(pattern)))) + self.assertEqual(expect, set(p.rglob(FakePath(pattern)))) + + @needs_posix + def test_absolute_posix(self): + P = self.cls + self.assertEqual(str(P('/').absolute()), '/') + self.assertEqual(str(P('/a').absolute()), '/a') + self.assertEqual(str(P('/a/b').absolute()), '/a/b') + + # '//'-prefixed absolute path (supported by POSIX). + self.assertEqual(str(P('//').absolute()), '//') + self.assertEqual(str(P('//a').absolute()), '//a') + self.assertEqual(str(P('//a/b').absolute()), '//a/b') + + @unittest.skipIf( + is_emscripten or is_wasi, + "umask is not implemented on Emscripten/WASI." + ) + @needs_posix + def test_open_mode(self): + # Unmask all permissions except world-write, which may + # not be supported on some filesystems (see GH-85633.) + old_mask = os.umask(0o002) + self.addCleanup(os.umask, old_mask) + p = self.cls(self.base) + with (p / 'new_file').open('wb'): + pass + st = os.stat(self.parser.join(self.base, 'new_file')) + self.assertEqual(stat.S_IMODE(st.st_mode), 0o664) + os.umask(0o026) + with (p / 'other_new_file').open('wb'): + pass + st = os.stat(self.parser.join(self.base, 'other_new_file')) + self.assertEqual(stat.S_IMODE(st.st_mode), 0o640) + + @needs_posix + def test_resolve_root(self): + current_directory = os.getcwd() + try: + os.chdir('/') + p = self.cls('spam') + self.assertEqual(str(p.resolve()), '/spam') + finally: + os.chdir(current_directory) + + @unittest.skipIf( + is_emscripten or is_wasi, + "umask is not implemented on Emscripten/WASI." + ) + @needs_posix + def test_touch_mode(self): + # Unmask all permissions except world-write, which may + # not be supported on some filesystems (see GH-85633.) + old_mask = os.umask(0o002) + self.addCleanup(os.umask, old_mask) + p = self.cls(self.base) + (p / 'new_file').touch() + st = os.stat(self.parser.join(self.base, 'new_file')) + self.assertEqual(stat.S_IMODE(st.st_mode), 0o664) + os.umask(0o026) + (p / 'other_new_file').touch() + st = os.stat(self.parser.join(self.base, 'other_new_file')) + self.assertEqual(stat.S_IMODE(st.st_mode), 0o640) + (p / 'masked_new_file').touch(mode=0o750) + st = os.stat(self.parser.join(self.base, 'masked_new_file')) + self.assertEqual(stat.S_IMODE(st.st_mode), 0o750) + + @unittest.skipUnless(hasattr(pwd, 'getpwall'), + 'pwd module does not expose getpwall()') + @unittest.skipIf(sys.platform == "vxworks", + "no home directory on VxWorks") + @needs_posix + def test_expanduser_posix(self): + P = self.cls + import_helper.import_module('pwd') + import pwd + pwdent = pwd.getpwuid(os.getuid()) + username = pwdent.pw_name + userhome = pwdent.pw_dir.rstrip('/') or '/' + # Find arbitrary different user (if exists). + for pwdent in pwd.getpwall(): + othername = pwdent.pw_name + otherhome = pwdent.pw_dir.rstrip('/') + if othername != username and otherhome: + break + else: + othername = username + otherhome = userhome + + fakename = 'fakeuser' + # This user can theoretically exist on a test runner. Create unique name: + try: + while pwd.getpwnam(fakename): + fakename += '1' + except KeyError: + pass # Non-existent name found + + p1 = P('~/Documents') + p2 = P(f'~{username}/Documents') + p3 = P(f'~{othername}/Documents') + p4 = P(f'../~{username}/Documents') + p5 = P(f'/~{username}/Documents') + p6 = P('') + p7 = P(f'~{fakename}/Documents') + + with os_helper.EnvironmentVarGuard() as env: + env.unset('HOME') + + self.assertEqual(p1.expanduser(), P(userhome) / 'Documents') + self.assertEqual(p2.expanduser(), P(userhome) / 'Documents') + self.assertEqual(p3.expanduser(), P(otherhome) / 'Documents') + self.assertEqual(p4.expanduser(), p4) + self.assertEqual(p5.expanduser(), p5) + self.assertEqual(p6.expanduser(), p6) + self.assertRaises(RuntimeError, p7.expanduser) + + env['HOME'] = '/tmp' + self.assertEqual(p1.expanduser(), P('/tmp/Documents')) + self.assertEqual(p2.expanduser(), P(userhome) / 'Documents') + self.assertEqual(p3.expanduser(), P(otherhome) / 'Documents') + self.assertEqual(p4.expanduser(), p4) + self.assertEqual(p5.expanduser(), p5) + self.assertEqual(p6.expanduser(), p6) + self.assertRaises(RuntimeError, p7.expanduser) + + @unittest.skipIf(sys.platform != "darwin", + "Bad file descriptor in /dev/fd affects only macOS") + @needs_posix + def test_handling_bad_descriptor(self): + try: + file_descriptors = list(pathlib.Path('/dev/fd').rglob("*"))[3:] + if not file_descriptors: + self.skipTest("no file descriptors - issue was not reproduced") + # Checking all file descriptors because there is no guarantee + # which one will fail. + for f in file_descriptors: + f.exists() + f.is_dir() + f.is_file() + f.is_symlink() + f.is_block_device() + f.is_char_device() + f.is_fifo() + f.is_socket() + except OSError as e: + if e.errno == errno.EBADF: + self.fail("Bad file descriptor not handled.") + raise + + @needs_posix + def test_from_uri_posix(self): + P = self.cls + self.assertEqual(P.from_uri('file:/foo/bar'), P('/foo/bar')) + self.assertEqual(P.from_uri('file://foo/bar'), P('//foo/bar')) + self.assertEqual(P.from_uri('file:///foo/bar'), P('/foo/bar')) + self.assertEqual(P.from_uri('file:////foo/bar'), P('//foo/bar')) + self.assertEqual(P.from_uri('file://localhost/foo/bar'), P('/foo/bar')) + self.assertRaises(ValueError, P.from_uri, 'foo/bar') + self.assertRaises(ValueError, P.from_uri, '/foo/bar') + self.assertRaises(ValueError, P.from_uri, '//foo/bar') + self.assertRaises(ValueError, P.from_uri, 'file:foo/bar') + self.assertRaises(ValueError, P.from_uri, 'http://foo/bar') + + @needs_posix + def test_from_uri_pathname2url_posix(self): + P = self.cls + self.assertEqual(P.from_uri('file:' + pathname2url('/foo/bar')), P('/foo/bar')) + self.assertEqual(P.from_uri('file:' + pathname2url('//foo/bar')), P('//foo/bar')) + + @needs_windows + def test_absolute_windows(self): + P = self.cls + + # Simple absolute paths. + self.assertEqual(str(P('c:\\').absolute()), 'c:\\') + self.assertEqual(str(P('c:\\a').absolute()), 'c:\\a') + self.assertEqual(str(P('c:\\a\\b').absolute()), 'c:\\a\\b') + + # UNC absolute paths. + share = '\\\\server\\share\\' + self.assertEqual(str(P(share).absolute()), share) + self.assertEqual(str(P(share + 'a').absolute()), share + 'a') + self.assertEqual(str(P(share + 'a\\b').absolute()), share + 'a\\b') + + # UNC relative paths. + with mock.patch("os.getcwd") as getcwd: + getcwd.return_value = share + + self.assertEqual(str(P().absolute()), share) + self.assertEqual(str(P('.').absolute()), share) + self.assertEqual(str(P('a').absolute()), os.path.join(share, 'a')) + self.assertEqual(str(P('a', 'b', 'c').absolute()), + os.path.join(share, 'a', 'b', 'c')) + + drive = os.path.splitdrive(self.base)[0] + with os_helper.change_cwd(self.base): + # Relative path with root + self.assertEqual(str(P('\\').absolute()), drive + '\\') + self.assertEqual(str(P('\\foo').absolute()), drive + '\\foo') + + # Relative path on current drive + self.assertEqual(str(P(drive).absolute()), self.base) + self.assertEqual(str(P(drive + 'foo').absolute()), os.path.join(self.base, 'foo')) + + with os_helper.subst_drive(self.base) as other_drive: + # Set the working directory on the substitute drive + saved_cwd = os.getcwd() + other_cwd = f'{other_drive}\\dirA' + os.chdir(other_cwd) + os.chdir(saved_cwd) + + # Relative path on another drive + self.assertEqual(str(P(other_drive).absolute()), other_cwd) + self.assertEqual(str(P(other_drive + 'foo').absolute()), other_cwd + '\\foo') + + @needs_windows + def test_expanduser_windows(self): + P = self.cls + with os_helper.EnvironmentVarGuard() as env: + env.unset('HOME', 'USERPROFILE', 'HOMEPATH', 'HOMEDRIVE') + env['USERNAME'] = 'alice' + + # test that the path returns unchanged + p1 = P('~/My Documents') + p2 = P('~alice/My Documents') + p3 = P('~bob/My Documents') + p4 = P('/~/My Documents') + p5 = P('d:~/My Documents') + p6 = P('') + self.assertRaises(RuntimeError, p1.expanduser) + self.assertRaises(RuntimeError, p2.expanduser) + self.assertRaises(RuntimeError, p3.expanduser) + self.assertEqual(p4.expanduser(), p4) + self.assertEqual(p5.expanduser(), p5) + self.assertEqual(p6.expanduser(), p6) + + def check(): + env.pop('USERNAME', None) + self.assertEqual(p1.expanduser(), + P('C:/Users/alice/My Documents')) + self.assertRaises(RuntimeError, p2.expanduser) + env['USERNAME'] = 'alice' + self.assertEqual(p2.expanduser(), + P('C:/Users/alice/My Documents')) + self.assertEqual(p3.expanduser(), + P('C:/Users/bob/My Documents')) + self.assertEqual(p4.expanduser(), p4) + self.assertEqual(p5.expanduser(), p5) + self.assertEqual(p6.expanduser(), p6) + + env['HOMEPATH'] = 'C:\\Users\\alice' + check() + + env['HOMEDRIVE'] = 'C:\\' + env['HOMEPATH'] = 'Users\\alice' + check() + + env.unset('HOMEDRIVE', 'HOMEPATH') + env['USERPROFILE'] = 'C:\\Users\\alice' + check() + + # bpo-38883: ignore `HOME` when set on windows + env['HOME'] = 'C:\\Users\\eve' + check() + + @needs_windows + def test_from_uri_windows(self): + P = self.cls + # DOS drive paths + self.assertEqual(P.from_uri('file:c:/path/to/file'), P('c:/path/to/file')) + self.assertEqual(P.from_uri('file:c|/path/to/file'), P('c:/path/to/file')) + self.assertEqual(P.from_uri('file:/c|/path/to/file'), P('c:/path/to/file')) + self.assertEqual(P.from_uri('file:///c|/path/to/file'), P('c:/path/to/file')) + # UNC paths + self.assertEqual(P.from_uri('file://server/path/to/file'), P('//server/path/to/file')) + self.assertEqual(P.from_uri('file:////server/path/to/file'), P('//server/path/to/file')) + self.assertEqual(P.from_uri('file://///server/path/to/file'), P('//server/path/to/file')) + # Localhost paths + self.assertEqual(P.from_uri('file://localhost/c:/path/to/file'), P('c:/path/to/file')) + self.assertEqual(P.from_uri('file://localhost/c|/path/to/file'), P('c:/path/to/file')) + # Invalid paths + self.assertRaises(ValueError, P.from_uri, 'foo/bar') + self.assertRaises(ValueError, P.from_uri, 'c:/foo/bar') + self.assertRaises(ValueError, P.from_uri, '//foo/bar') + self.assertRaises(ValueError, P.from_uri, 'file:foo/bar') + self.assertRaises(ValueError, P.from_uri, 'http://foo/bar') + + @needs_windows + def test_from_uri_pathname2url_windows(self): + P = self.cls + self.assertEqual(P.from_uri('file:' + pathname2url(r'c:\path\to\file')), P('c:/path/to/file')) + self.assertEqual(P.from_uri('file:' + pathname2url(r'\\server\path\to\file')), P('//server/path/to/file')) + + @needs_windows + def test_owner_windows(self): + P = self.cls + with self.assertRaises(pathlib.UnsupportedOperation): + P('c:/').owner() + + @needs_windows + def test_group_windows(self): + P = self.cls + with self.assertRaises(pathlib.UnsupportedOperation): + P('c:/').group() + + +@unittest.skipIf(os.name == 'nt', 'test requires a POSIX-compatible system') +class PosixPathTest(PathTest, PurePosixPathTest): + cls = pathlib.PosixPath + + +@unittest.skipIf(os.name != 'nt', 'test requires a Windows-compatible system') +class WindowsPathTest(PathTest, PureWindowsPathTest): + cls = pathlib.WindowsPath + + +class PathSubclassTest(PathTest): + class cls(pathlib.Path): + pass + + # repr() roundtripping is not supported in custom subclass. + test_repr_roundtrips = None + + +class CompatiblePathTest(unittest.TestCase): + """ + Test that a type can be made compatible with PurePath + derivatives by implementing division operator overloads. + """ + + class CompatPath: + """ + Minimum viable class to test PurePath compatibility. + Simply uses the division operator to join a given + string and the string value of another object with + a forward slash. + """ + def __init__(self, string): + self.string = string + + def __truediv__(self, other): + return type(self)(f"{self.string}/{other}") + + def __rtruediv__(self, other): + return type(self)(f"{other}/{self.string}") + + def test_truediv(self): + result = pathlib.PurePath("test") / self.CompatPath("right") + self.assertIsInstance(result, self.CompatPath) + self.assertEqual(result.string, "test/right") + + with self.assertRaises(TypeError): + # Verify improper operations still raise a TypeError + pathlib.PurePath("test") / 10 + + def test_rtruediv(self): + result = self.CompatPath("left") / pathlib.PurePath("test") + self.assertIsInstance(result, self.CompatPath) + self.assertEqual(result.string, "left/test") + + with self.assertRaises(TypeError): + # Verify improper operations still raise a TypeError + 10 / pathlib.PurePath("test") + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py new file mode 100644 index 00000000000..2e050362158 --- /dev/null +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -0,0 +1,2499 @@ +# TODO: RUSTPYTHON +# Has not been tested with Windows style paths +import collections +import io +import os +import errno +import stat +import unittest + +from pathlib._abc import UnsupportedOperation, ParserBase, PurePathBase, PathBase +import posixpath + +from test.support import is_wasi +from test.support.os_helper import TESTFN + + +_tests_needing_posix = set() +_tests_needing_windows = set() +_tests_needing_symlinks = set() + + +def needs_posix(fn): + """Decorator that marks a test as requiring a POSIX-flavoured path class.""" + _tests_needing_posix.add(fn.__name__) + return fn + +def needs_windows(fn): + """Decorator that marks a test as requiring a Windows-flavoured path class.""" + _tests_needing_windows.add(fn.__name__) + return fn + +def needs_symlinks(fn): + """Decorator that marks a test as requiring a path class that supports symlinks.""" + _tests_needing_symlinks.add(fn.__name__) + return fn + + +class UnsupportedOperationTest(unittest.TestCase): + def test_is_notimplemented(self): + self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError)) + self.assertTrue(isinstance(UnsupportedOperation(), NotImplementedError)) + + +class ParserBaseTest(unittest.TestCase): + cls = ParserBase + + def test_unsupported_operation(self): + m = self.cls() + e = UnsupportedOperation + with self.assertRaises(e): + m.sep + self.assertRaises(e, m.join, 'foo') + self.assertRaises(e, m.split, 'foo') + self.assertRaises(e, m.splitdrive, 'foo') + self.assertRaises(e, m.normcase, 'foo') + self.assertRaises(e, m.isabs, 'foo') + +# +# Tests for the pure classes. +# + + +class PurePathBaseTest(unittest.TestCase): + cls = PurePathBase + + def test_unsupported_operation_pure(self): + p = self.cls('foo') + e = UnsupportedOperation + with self.assertRaises(e): + p.drive + with self.assertRaises(e): + p.root + with self.assertRaises(e): + p.anchor + with self.assertRaises(e): + p.parts + with self.assertRaises(e): + p.parent + with self.assertRaises(e): + p.parents + with self.assertRaises(e): + p.name + with self.assertRaises(e): + p.stem + with self.assertRaises(e): + p.suffix + with self.assertRaises(e): + p.suffixes + with self.assertRaises(e): + p / 'bar' + with self.assertRaises(e): + 'bar' / p + self.assertRaises(e, p.joinpath, 'bar') + self.assertRaises(e, p.with_name, 'bar') + self.assertRaises(e, p.with_stem, 'bar') + self.assertRaises(e, p.with_suffix, '.txt') + self.assertRaises(e, p.relative_to, '') + self.assertRaises(e, p.is_relative_to, '') + self.assertRaises(e, p.is_absolute) + self.assertRaises(e, p.match, '*') + + def test_magic_methods(self): + P = self.cls + self.assertFalse(hasattr(P, '__fspath__')) + self.assertFalse(hasattr(P, '__bytes__')) + self.assertIs(P.__reduce__, object.__reduce__) + self.assertIs(P.__repr__, object.__repr__) + self.assertIs(P.__hash__, object.__hash__) + self.assertIs(P.__eq__, object.__eq__) + self.assertIs(P.__lt__, object.__lt__) + self.assertIs(P.__le__, object.__le__) + self.assertIs(P.__gt__, object.__gt__) + self.assertIs(P.__ge__, object.__ge__) + + def test_parser(self): + self.assertIsInstance(self.cls.parser, ParserBase) + + +class DummyPurePath(PurePathBase): + __slots__ = () + parser = posixpath + + def __eq__(self, other): + if not isinstance(other, DummyPurePath): + return NotImplemented + return str(self) == str(other) + + def __hash__(self): + return hash(str(self)) + + def __repr__(self): + return "{}({!r})".format(self.__class__.__name__, self.as_posix()) + + +class DummyPurePathTest(unittest.TestCase): + cls = DummyPurePath + + # Use a base path that's unrelated to any real filesystem path. + base = f'/this/path/kills/fascists/{TESTFN}' + + def setUp(self): + name = self.id().split('.')[-1] + if name in _tests_needing_posix and self.cls.parser is not posixpath: + self.skipTest('requires POSIX-flavoured path class') + if name in _tests_needing_windows and self.cls.parser is posixpath: + self.skipTest('requires Windows-flavoured path class') + p = self.cls('a') + self.parser = p.parser + self.sep = self.parser.sep + self.altsep = self.parser.altsep + + def test_constructor_common(self): + P = self.cls + p = P('a') + self.assertIsInstance(p, P) + P('a', 'b', 'c') + P('/a', 'b', 'c') + P('a/b/c') + P('/a/b/c') + + def test_bytes(self): + P = self.cls + with self.assertRaises(TypeError): + P(b'a') + with self.assertRaises(TypeError): + P(b'a', 'b') + with self.assertRaises(TypeError): + P('a', b'b') + with self.assertRaises(TypeError): + P('a').joinpath(b'b') + with self.assertRaises(TypeError): + P('a') / b'b' + with self.assertRaises(TypeError): + b'a' / P('b') + with self.assertRaises(TypeError): + P('a').match(b'b') + with self.assertRaises(TypeError): + P('a').relative_to(b'b') + with self.assertRaises(TypeError): + P('a').with_name(b'b') + with self.assertRaises(TypeError): + P('a').with_stem(b'b') + with self.assertRaises(TypeError): + P('a').with_suffix(b'b') + + def _check_str_subclass(self, *args): + # Issue #21127: it should be possible to construct a PurePath object + # from a str subclass instance, and it then gets converted to + # a pure str object. + class StrSubclass(str): + pass + P = self.cls + p = P(*(StrSubclass(x) for x in args)) + self.assertEqual(p, P(*args)) + for part in p.parts: + self.assertIs(type(part), str) + + def test_str_subclass_common(self): + self._check_str_subclass('') + self._check_str_subclass('.') + self._check_str_subclass('a') + self._check_str_subclass('a/b.txt') + self._check_str_subclass('/a/b.txt') + + @needs_windows + def test_str_subclass_windows(self): + self._check_str_subclass('.\\a:b') + self._check_str_subclass('c:') + self._check_str_subclass('c:a') + self._check_str_subclass('c:a\\b.txt') + self._check_str_subclass('c:\\') + self._check_str_subclass('c:\\a') + self._check_str_subclass('c:\\a\\b.txt') + self._check_str_subclass('\\\\some\\share') + self._check_str_subclass('\\\\some\\share\\a') + self._check_str_subclass('\\\\some\\share\\a\\b.txt') + + def test_with_segments_common(self): + class P(self.cls): + def __init__(self, *pathsegments, session_id): + super().__init__(*pathsegments) + self.session_id = session_id + + def with_segments(self, *pathsegments): + return type(self)(*pathsegments, session_id=self.session_id) + p = P('foo', 'bar', session_id=42) + self.assertEqual(42, (p / 'foo').session_id) + self.assertEqual(42, ('foo' / p).session_id) + self.assertEqual(42, p.joinpath('foo').session_id) + self.assertEqual(42, p.with_name('foo').session_id) + self.assertEqual(42, p.with_stem('foo').session_id) + self.assertEqual(42, p.with_suffix('.foo').session_id) + self.assertEqual(42, p.with_segments('foo').session_id) + self.assertEqual(42, p.relative_to('foo').session_id) + self.assertEqual(42, p.parent.session_id) + for parent in p.parents: + self.assertEqual(42, parent.session_id) + + def test_join_common(self): + P = self.cls + p = P('a/b') + pp = p.joinpath('c') + self.assertEqual(pp, P('a/b/c')) + self.assertIs(type(pp), type(p)) + pp = p.joinpath('c', 'd') + self.assertEqual(pp, P('a/b/c/d')) + pp = p.joinpath('/c') + self.assertEqual(pp, P('/c')) + + @needs_posix + def test_join_posix(self): + P = self.cls + p = P('//a') + pp = p.joinpath('b') + self.assertEqual(pp, P('//a/b')) + pp = P('/a').joinpath('//c') + self.assertEqual(pp, P('//c')) + pp = P('//a').joinpath('/c') + self.assertEqual(pp, P('/c')) + + @needs_windows + def test_join_windows(self): + P = self.cls + p = P('C:/a/b') + pp = p.joinpath('x/y') + self.assertEqual(pp, P('C:/a/b/x/y')) + pp = p.joinpath('/x/y') + self.assertEqual(pp, P('C:/x/y')) + # Joining with a different drive => the first path is ignored, even + # if the second path is relative. + pp = p.joinpath('D:x/y') + self.assertEqual(pp, P('D:x/y')) + pp = p.joinpath('D:/x/y') + self.assertEqual(pp, P('D:/x/y')) + pp = p.joinpath('//host/share/x/y') + self.assertEqual(pp, P('//host/share/x/y')) + # Joining with the same drive => the first path is appended to if + # the second path is relative. + pp = p.joinpath('c:x/y') + self.assertEqual(pp, P('C:/a/b/x/y')) + pp = p.joinpath('c:/x/y') + self.assertEqual(pp, P('C:/x/y')) + # Joining with files with NTFS data streams => the filename should + # not be parsed as a drive letter + pp = p.joinpath(P('./d:s')) + self.assertEqual(pp, P('C:/a/b/d:s')) + pp = p.joinpath(P('./dd:s')) + self.assertEqual(pp, P('C:/a/b/dd:s')) + pp = p.joinpath(P('E:d:s')) + self.assertEqual(pp, P('E:d:s')) + # Joining onto a UNC path with no root + pp = P('//').joinpath('server') + self.assertEqual(pp, P('//server')) + pp = P('//server').joinpath('share') + self.assertEqual(pp, P('//server/share')) + pp = P('//./BootPartition').joinpath('Windows') + self.assertEqual(pp, P('//./BootPartition/Windows')) + + def test_div_common(self): + # Basically the same as joinpath(). + P = self.cls + p = P('a/b') + pp = p / 'c' + self.assertEqual(pp, P('a/b/c')) + self.assertIs(type(pp), type(p)) + pp = p / 'c/d' + self.assertEqual(pp, P('a/b/c/d')) + pp = p / 'c' / 'd' + self.assertEqual(pp, P('a/b/c/d')) + pp = 'c' / p / 'd' + self.assertEqual(pp, P('c/a/b/d')) + pp = p/ '/c' + self.assertEqual(pp, P('/c')) + + @needs_posix + def test_div_posix(self): + # Basically the same as joinpath(). + P = self.cls + p = P('//a') + pp = p / 'b' + self.assertEqual(pp, P('//a/b')) + pp = P('/a') / '//c' + self.assertEqual(pp, P('//c')) + pp = P('//a') / '/c' + self.assertEqual(pp, P('/c')) + + @needs_windows + def test_div_windows(self): + # Basically the same as joinpath(). + P = self.cls + p = P('C:/a/b') + self.assertEqual(p / 'x/y', P('C:/a/b/x/y')) + self.assertEqual(p / 'x' / 'y', P('C:/a/b/x/y')) + self.assertEqual(p / '/x/y', P('C:/x/y')) + self.assertEqual(p / '/x' / 'y', P('C:/x/y')) + # Joining with a different drive => the first path is ignored, even + # if the second path is relative. + self.assertEqual(p / 'D:x/y', P('D:x/y')) + self.assertEqual(p / 'D:' / 'x/y', P('D:x/y')) + self.assertEqual(p / 'D:/x/y', P('D:/x/y')) + self.assertEqual(p / 'D:' / '/x/y', P('D:/x/y')) + self.assertEqual(p / '//host/share/x/y', P('//host/share/x/y')) + # Joining with the same drive => the first path is appended to if + # the second path is relative. + self.assertEqual(p / 'c:x/y', P('C:/a/b/x/y')) + self.assertEqual(p / 'c:/x/y', P('C:/x/y')) + # Joining with files with NTFS data streams => the filename should + # not be parsed as a drive letter + self.assertEqual(p / P('./d:s'), P('C:/a/b/d:s')) + self.assertEqual(p / P('./dd:s'), P('C:/a/b/dd:s')) + self.assertEqual(p / P('E:d:s'), P('E:d:s')) + + def _check_str(self, expected, args): + p = self.cls(*args) + self.assertEqual(str(p), expected.replace('/', self.sep)) + + def test_str_common(self): + # Canonicalized paths roundtrip. + for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): + self._check_str(pathstr, (pathstr,)) + # Other tests for str() are in test_equivalences(). + + @needs_windows + def test_str_windows(self): + p = self.cls('a/b/c') + self.assertEqual(str(p), 'a\\b\\c') + p = self.cls('c:/a/b/c') + self.assertEqual(str(p), 'c:\\a\\b\\c') + p = self.cls('//a/b') + self.assertEqual(str(p), '\\\\a\\b\\') + p = self.cls('//a/b/c') + self.assertEqual(str(p), '\\\\a\\b\\c') + p = self.cls('//a/b/c/d') + self.assertEqual(str(p), '\\\\a\\b\\c\\d') + + def test_as_posix_common(self): + P = self.cls + for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): + self.assertEqual(P(pathstr).as_posix(), pathstr) + # Other tests for as_posix() are in test_equivalences(). + + def test_match_empty(self): + P = self.cls + self.assertRaises(ValueError, P('a').match, '') + + def test_match_common(self): + P = self.cls + # Simple relative pattern. + self.assertTrue(P('b.py').match('b.py')) + self.assertTrue(P('a/b.py').match('b.py')) + self.assertTrue(P('/a/b.py').match('b.py')) + self.assertFalse(P('a.py').match('b.py')) + self.assertFalse(P('b/py').match('b.py')) + self.assertFalse(P('/a.py').match('b.py')) + self.assertFalse(P('b.py/c').match('b.py')) + # Wildcard relative pattern. + self.assertTrue(P('b.py').match('*.py')) + self.assertTrue(P('a/b.py').match('*.py')) + self.assertTrue(P('/a/b.py').match('*.py')) + self.assertFalse(P('b.pyc').match('*.py')) + self.assertFalse(P('b./py').match('*.py')) + self.assertFalse(P('b.py/c').match('*.py')) + # Multi-part relative pattern. + self.assertTrue(P('ab/c.py').match('a*/*.py')) + self.assertTrue(P('/d/ab/c.py').match('a*/*.py')) + self.assertFalse(P('a.py').match('a*/*.py')) + self.assertFalse(P('/dab/c.py').match('a*/*.py')) + self.assertFalse(P('ab/c.py/d').match('a*/*.py')) + # Absolute pattern. + self.assertTrue(P('/b.py').match('/*.py')) + self.assertFalse(P('b.py').match('/*.py')) + self.assertFalse(P('a/b.py').match('/*.py')) + self.assertFalse(P('/a/b.py').match('/*.py')) + # Multi-part absolute pattern. + self.assertTrue(P('/a/b.py').match('/a/*.py')) + self.assertFalse(P('/ab.py').match('/a/*.py')) + self.assertFalse(P('/a/b/c.py').match('/a/*.py')) + # Multi-part glob-style pattern. + self.assertFalse(P('/a/b/c.py').match('/**/*.py')) + self.assertTrue(P('/a/b/c.py').match('/a/**/*.py')) + # Case-sensitive flag + self.assertFalse(P('A.py').match('a.PY', case_sensitive=True)) + self.assertTrue(P('A.py').match('a.PY', case_sensitive=False)) + self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True)) + self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False)) + # Matching against empty path + self.assertFalse(P('').match('*')) + self.assertFalse(P('').match('**')) + self.assertFalse(P('').match('**/*')) + + @needs_posix + def test_match_posix(self): + P = self.cls + self.assertFalse(P('A.py').match('a.PY')) + + @needs_windows + def test_match_windows(self): + P = self.cls + # Absolute patterns. + self.assertTrue(P('c:/b.py').match('*:/*.py')) + self.assertTrue(P('c:/b.py').match('c:/*.py')) + self.assertFalse(P('d:/b.py').match('c:/*.py')) # wrong drive + self.assertFalse(P('b.py').match('/*.py')) + self.assertFalse(P('b.py').match('c:*.py')) + self.assertFalse(P('b.py').match('c:/*.py')) + self.assertFalse(P('c:b.py').match('/*.py')) + self.assertFalse(P('c:b.py').match('c:/*.py')) + self.assertFalse(P('/b.py').match('c:*.py')) + self.assertFalse(P('/b.py').match('c:/*.py')) + # UNC patterns. + self.assertTrue(P('//some/share/a.py').match('//*/*/*.py')) + self.assertTrue(P('//some/share/a.py').match('//some/share/*.py')) + self.assertFalse(P('//other/share/a.py').match('//some/share/*.py')) + self.assertFalse(P('//some/share/a/b.py').match('//some/share/*.py')) + # Case-insensitivity. + self.assertTrue(P('B.py').match('b.PY')) + self.assertTrue(P('c:/a/B.Py').match('C:/A/*.pY')) + self.assertTrue(P('//Some/Share/B.Py').match('//somE/sharE/*.pY')) + # Path anchor doesn't match pattern anchor + self.assertFalse(P('c:/b.py').match('/*.py')) # 'c:/' vs '/' + self.assertFalse(P('c:/b.py').match('c:*.py')) # 'c:/' vs 'c:' + self.assertFalse(P('//some/share/a.py').match('/*.py')) # '//some/share/' vs '/' + + def test_full_match_common(self): + P = self.cls + # Simple relative pattern. + self.assertTrue(P('b.py').full_match('b.py')) + self.assertFalse(P('a/b.py').full_match('b.py')) + self.assertFalse(P('/a/b.py').full_match('b.py')) + self.assertFalse(P('a.py').full_match('b.py')) + self.assertFalse(P('b/py').full_match('b.py')) + self.assertFalse(P('/a.py').full_match('b.py')) + self.assertFalse(P('b.py/c').full_match('b.py')) + # Wildcard relative pattern. + self.assertTrue(P('b.py').full_match('*.py')) + self.assertFalse(P('a/b.py').full_match('*.py')) + self.assertFalse(P('/a/b.py').full_match('*.py')) + self.assertFalse(P('b.pyc').full_match('*.py')) + self.assertFalse(P('b./py').full_match('*.py')) + self.assertFalse(P('b.py/c').full_match('*.py')) + # Multi-part relative pattern. + self.assertTrue(P('ab/c.py').full_match('a*/*.py')) + self.assertFalse(P('/d/ab/c.py').full_match('a*/*.py')) + self.assertFalse(P('a.py').full_match('a*/*.py')) + self.assertFalse(P('/dab/c.py').full_match('a*/*.py')) + self.assertFalse(P('ab/c.py/d').full_match('a*/*.py')) + # Absolute pattern. + self.assertTrue(P('/b.py').full_match('/*.py')) + self.assertFalse(P('b.py').full_match('/*.py')) + self.assertFalse(P('a/b.py').full_match('/*.py')) + self.assertFalse(P('/a/b.py').full_match('/*.py')) + # Multi-part absolute pattern. + self.assertTrue(P('/a/b.py').full_match('/a/*.py')) + self.assertFalse(P('/ab.py').full_match('/a/*.py')) + self.assertFalse(P('/a/b/c.py').full_match('/a/*.py')) + # Multi-part glob-style pattern. + self.assertTrue(P('a').full_match('**')) + self.assertTrue(P('c.py').full_match('**')) + self.assertTrue(P('a/b/c.py').full_match('**')) + self.assertTrue(P('/a/b/c.py').full_match('**')) + self.assertTrue(P('/a/b/c.py').full_match('/**')) + self.assertTrue(P('/a/b/c.py').full_match('/a/**')) + self.assertTrue(P('/a/b/c.py').full_match('**/*.py')) + self.assertTrue(P('/a/b/c.py').full_match('/**/*.py')) + self.assertTrue(P('/a/b/c.py').full_match('/a/**/*.py')) + self.assertTrue(P('/a/b/c.py').full_match('/a/b/**/*.py')) + self.assertTrue(P('/a/b/c.py').full_match('/**/**/**/**/*.py')) + self.assertFalse(P('c.py').full_match('**/a.py')) + self.assertFalse(P('c.py').full_match('c/**')) + self.assertFalse(P('a/b/c.py').full_match('**/a')) + self.assertFalse(P('a/b/c.py').full_match('**/a/b')) + self.assertFalse(P('a/b/c.py').full_match('**/a/b/c')) + self.assertFalse(P('a/b/c.py').full_match('**/a/b/c.')) + self.assertFalse(P('a/b/c.py').full_match('**/a/b/c./**')) + self.assertFalse(P('a/b/c.py').full_match('**/a/b/c./**')) + self.assertFalse(P('a/b/c.py').full_match('/a/b/c.py/**')) + self.assertFalse(P('a/b/c.py').full_match('/**/a/b/c.py')) + # Case-sensitive flag + self.assertFalse(P('A.py').full_match('a.PY', case_sensitive=True)) + self.assertTrue(P('A.py').full_match('a.PY', case_sensitive=False)) + self.assertFalse(P('c:/a/B.Py').full_match('C:/A/*.pY', case_sensitive=True)) + self.assertTrue(P('/a/b/c.py').full_match('/A/*/*.Py', case_sensitive=False)) + # Matching against empty path + self.assertFalse(P('').full_match('*')) + self.assertTrue(P('').full_match('**')) + self.assertFalse(P('').full_match('**/*')) + # Matching with empty pattern + self.assertTrue(P('').full_match('')) + self.assertTrue(P('.').full_match('.')) + self.assertFalse(P('/').full_match('')) + self.assertFalse(P('/').full_match('.')) + self.assertFalse(P('foo').full_match('')) + self.assertFalse(P('foo').full_match('.')) + + def test_parts_common(self): + # `parts` returns a tuple. + sep = self.sep + P = self.cls + p = P('a/b') + parts = p.parts + self.assertEqual(parts, ('a', 'b')) + # When the path is absolute, the anchor is a separate part. + p = P('/a/b') + parts = p.parts + self.assertEqual(parts, (sep, 'a', 'b')) + + @needs_windows + def test_parts_windows(self): + P = self.cls + p = P('c:a/b') + parts = p.parts + self.assertEqual(parts, ('c:', 'a', 'b')) + p = P('c:/a/b') + parts = p.parts + self.assertEqual(parts, ('c:\\', 'a', 'b')) + p = P('//a/b/c/d') + parts = p.parts + self.assertEqual(parts, ('\\\\a\\b\\', 'c', 'd')) + + def test_parent_common(self): + # Relative + P = self.cls + p = P('a/b/c') + self.assertEqual(p.parent, P('a/b')) + self.assertEqual(p.parent.parent, P('a')) + self.assertEqual(p.parent.parent.parent, P('')) + self.assertEqual(p.parent.parent.parent.parent, P('')) + # Anchored + p = P('/a/b/c') + self.assertEqual(p.parent, P('/a/b')) + self.assertEqual(p.parent.parent, P('/a')) + self.assertEqual(p.parent.parent.parent, P('/')) + self.assertEqual(p.parent.parent.parent.parent, P('/')) + + @needs_windows + def test_parent_windows(self): + # Anchored + P = self.cls + p = P('z:a/b/c') + self.assertEqual(p.parent, P('z:a/b')) + self.assertEqual(p.parent.parent, P('z:a')) + self.assertEqual(p.parent.parent.parent, P('z:')) + self.assertEqual(p.parent.parent.parent.parent, P('z:')) + p = P('z:/a/b/c') + self.assertEqual(p.parent, P('z:/a/b')) + self.assertEqual(p.parent.parent, P('z:/a')) + self.assertEqual(p.parent.parent.parent, P('z:/')) + self.assertEqual(p.parent.parent.parent.parent, P('z:/')) + p = P('//a/b/c/d') + self.assertEqual(p.parent, P('//a/b/c')) + self.assertEqual(p.parent.parent, P('//a/b')) + self.assertEqual(p.parent.parent.parent, P('//a/b')) + + def test_parents_common(self): + # Relative + P = self.cls + p = P('a/b/c') + par = p.parents + self.assertEqual(len(par), 3) + self.assertEqual(par[0], P('a/b')) + self.assertEqual(par[1], P('a')) + self.assertEqual(par[2], P('')) + self.assertEqual(par[-1], P('')) + self.assertEqual(par[-2], P('a')) + self.assertEqual(par[-3], P('a/b')) + self.assertEqual(par[0:1], (P('a/b'),)) + self.assertEqual(par[:2], (P('a/b'), P('a'))) + self.assertEqual(par[:-1], (P('a/b'), P('a'))) + self.assertEqual(par[1:], (P('a'), P(''))) + self.assertEqual(par[::2], (P('a/b'), P(''))) + self.assertEqual(par[::-1], (P(''), P('a'), P('a/b'))) + self.assertEqual(list(par), [P('a/b'), P('a'), P('')]) + with self.assertRaises(IndexError): + par[-4] + with self.assertRaises(IndexError): + par[3] + with self.assertRaises(TypeError): + par[0] = p + # Anchored + p = P('/a/b/c') + par = p.parents + self.assertEqual(len(par), 3) + self.assertEqual(par[0], P('/a/b')) + self.assertEqual(par[1], P('/a')) + self.assertEqual(par[2], P('/')) + self.assertEqual(par[-1], P('/')) + self.assertEqual(par[-2], P('/a')) + self.assertEqual(par[-3], P('/a/b')) + self.assertEqual(par[0:1], (P('/a/b'),)) + self.assertEqual(par[:2], (P('/a/b'), P('/a'))) + self.assertEqual(par[:-1], (P('/a/b'), P('/a'))) + self.assertEqual(par[1:], (P('/a'), P('/'))) + self.assertEqual(par[::2], (P('/a/b'), P('/'))) + self.assertEqual(par[::-1], (P('/'), P('/a'), P('/a/b'))) + self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')]) + with self.assertRaises(IndexError): + par[-4] + with self.assertRaises(IndexError): + par[3] + + @needs_windows + def test_parents_windows(self): + # Anchored + P = self.cls + p = P('z:a/b/') + par = p.parents + self.assertEqual(len(par), 2) + self.assertEqual(par[0], P('z:a')) + self.assertEqual(par[1], P('z:')) + self.assertEqual(par[0:1], (P('z:a'),)) + self.assertEqual(par[:-1], (P('z:a'),)) + self.assertEqual(par[:2], (P('z:a'), P('z:'))) + self.assertEqual(par[1:], (P('z:'),)) + self.assertEqual(par[::2], (P('z:a'),)) + self.assertEqual(par[::-1], (P('z:'), P('z:a'))) + self.assertEqual(list(par), [P('z:a'), P('z:')]) + with self.assertRaises(IndexError): + par[2] + p = P('z:/a/b/') + par = p.parents + self.assertEqual(len(par), 2) + self.assertEqual(par[0], P('z:/a')) + self.assertEqual(par[1], P('z:/')) + self.assertEqual(par[0:1], (P('z:/a'),)) + self.assertEqual(par[0:-1], (P('z:/a'),)) + self.assertEqual(par[:2], (P('z:/a'), P('z:/'))) + self.assertEqual(par[1:], (P('z:/'),)) + self.assertEqual(par[::2], (P('z:/a'),)) + self.assertEqual(par[::-1], (P('z:/'), P('z:/a'),)) + self.assertEqual(list(par), [P('z:/a'), P('z:/')]) + with self.assertRaises(IndexError): + par[2] + p = P('//a/b/c/d') + par = p.parents + self.assertEqual(len(par), 2) + self.assertEqual(par[0], P('//a/b/c')) + self.assertEqual(par[1], P('//a/b')) + self.assertEqual(par[0:1], (P('//a/b/c'),)) + self.assertEqual(par[0:-1], (P('//a/b/c'),)) + self.assertEqual(par[:2], (P('//a/b/c'), P('//a/b'))) + self.assertEqual(par[1:], (P('//a/b'),)) + self.assertEqual(par[::2], (P('//a/b/c'),)) + self.assertEqual(par[::-1], (P('//a/b'), P('//a/b/c'))) + self.assertEqual(list(par), [P('//a/b/c'), P('//a/b')]) + with self.assertRaises(IndexError): + par[2] + + def test_drive_common(self): + P = self.cls + self.assertEqual(P('a/b').drive, '') + self.assertEqual(P('/a/b').drive, '') + self.assertEqual(P('').drive, '') + + @needs_windows + def test_drive_windows(self): + P = self.cls + self.assertEqual(P('c:').drive, 'c:') + self.assertEqual(P('c:a/b').drive, 'c:') + self.assertEqual(P('c:/').drive, 'c:') + self.assertEqual(P('c:/a/b/').drive, 'c:') + self.assertEqual(P('//a/b').drive, '\\\\a\\b') + self.assertEqual(P('//a/b/').drive, '\\\\a\\b') + self.assertEqual(P('//a/b/c/d').drive, '\\\\a\\b') + self.assertEqual(P('./c:a').drive, '') + + def test_root_common(self): + P = self.cls + sep = self.sep + self.assertEqual(P('').root, '') + self.assertEqual(P('a/b').root, '') + self.assertEqual(P('/').root, sep) + self.assertEqual(P('/a/b').root, sep) + + @needs_posix + def test_root_posix(self): + P = self.cls + self.assertEqual(P('/a/b').root, '/') + # POSIX special case for two leading slashes. + self.assertEqual(P('//a/b').root, '//') + + @needs_windows + def test_root_windows(self): + P = self.cls + self.assertEqual(P('c:').root, '') + self.assertEqual(P('c:a/b').root, '') + self.assertEqual(P('c:/').root, '\\') + self.assertEqual(P('c:/a/b/').root, '\\') + self.assertEqual(P('//a/b').root, '\\') + self.assertEqual(P('//a/b/').root, '\\') + self.assertEqual(P('//a/b/c/d').root, '\\') + + def test_anchor_common(self): + P = self.cls + sep = self.sep + self.assertEqual(P('').anchor, '') + self.assertEqual(P('a/b').anchor, '') + self.assertEqual(P('/').anchor, sep) + self.assertEqual(P('/a/b').anchor, sep) + + @needs_windows + def test_anchor_windows(self): + P = self.cls + self.assertEqual(P('c:').anchor, 'c:') + self.assertEqual(P('c:a/b').anchor, 'c:') + self.assertEqual(P('c:/').anchor, 'c:\\') + self.assertEqual(P('c:/a/b/').anchor, 'c:\\') + self.assertEqual(P('//a/b').anchor, '\\\\a\\b\\') + self.assertEqual(P('//a/b/').anchor, '\\\\a\\b\\') + self.assertEqual(P('//a/b/c/d').anchor, '\\\\a\\b\\') + + def test_name_empty(self): + P = self.cls + self.assertEqual(P('').name, '') + self.assertEqual(P('.').name, '.') + self.assertEqual(P('/a/b/.').name, '.') + + def test_name_common(self): + P = self.cls + self.assertEqual(P('/').name, '') + self.assertEqual(P('a/b').name, 'b') + self.assertEqual(P('/a/b').name, 'b') + self.assertEqual(P('a/b.py').name, 'b.py') + self.assertEqual(P('/a/b.py').name, 'b.py') + + @needs_windows + def test_name_windows(self): + P = self.cls + self.assertEqual(P('c:').name, '') + self.assertEqual(P('c:/').name, '') + self.assertEqual(P('c:a/b').name, 'b') + self.assertEqual(P('c:/a/b').name, 'b') + self.assertEqual(P('c:a/b.py').name, 'b.py') + self.assertEqual(P('c:/a/b.py').name, 'b.py') + self.assertEqual(P('//My.py/Share.php').name, '') + self.assertEqual(P('//My.py/Share.php/a/b').name, 'b') + + def test_suffix_common(self): + P = self.cls + self.assertEqual(P('').suffix, '') + self.assertEqual(P('.').suffix, '') + self.assertEqual(P('..').suffix, '') + self.assertEqual(P('/').suffix, '') + self.assertEqual(P('a/b').suffix, '') + self.assertEqual(P('/a/b').suffix, '') + self.assertEqual(P('/a/b/.').suffix, '') + self.assertEqual(P('a/b.py').suffix, '.py') + self.assertEqual(P('/a/b.py').suffix, '.py') + self.assertEqual(P('a/.hgrc').suffix, '') + self.assertEqual(P('/a/.hgrc').suffix, '') + self.assertEqual(P('a/.hg.rc').suffix, '.rc') + self.assertEqual(P('/a/.hg.rc').suffix, '.rc') + self.assertEqual(P('a/b.tar.gz').suffix, '.gz') + self.assertEqual(P('/a/b.tar.gz').suffix, '.gz') + self.assertEqual(P('a/Some name. Ending with a dot.').suffix, '') + self.assertEqual(P('/a/Some name. Ending with a dot.').suffix, '') + + @needs_windows + def test_suffix_windows(self): + P = self.cls + self.assertEqual(P('c:').suffix, '') + self.assertEqual(P('c:/').suffix, '') + self.assertEqual(P('c:a/b').suffix, '') + self.assertEqual(P('c:/a/b').suffix, '') + self.assertEqual(P('c:a/b.py').suffix, '.py') + self.assertEqual(P('c:/a/b.py').suffix, '.py') + self.assertEqual(P('c:a/.hgrc').suffix, '') + self.assertEqual(P('c:/a/.hgrc').suffix, '') + self.assertEqual(P('c:a/.hg.rc').suffix, '.rc') + self.assertEqual(P('c:/a/.hg.rc').suffix, '.rc') + self.assertEqual(P('c:a/b.tar.gz').suffix, '.gz') + self.assertEqual(P('c:/a/b.tar.gz').suffix, '.gz') + self.assertEqual(P('c:a/Some name. Ending with a dot.').suffix, '') + self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffix, '') + self.assertEqual(P('//My.py/Share.php').suffix, '') + self.assertEqual(P('//My.py/Share.php/a/b').suffix, '') + + def test_suffixes_common(self): + P = self.cls + self.assertEqual(P('').suffixes, []) + self.assertEqual(P('.').suffixes, []) + self.assertEqual(P('/').suffixes, []) + self.assertEqual(P('a/b').suffixes, []) + self.assertEqual(P('/a/b').suffixes, []) + self.assertEqual(P('/a/b/.').suffixes, []) + self.assertEqual(P('a/b.py').suffixes, ['.py']) + self.assertEqual(P('/a/b.py').suffixes, ['.py']) + self.assertEqual(P('a/.hgrc').suffixes, []) + self.assertEqual(P('/a/.hgrc').suffixes, []) + self.assertEqual(P('a/.hg.rc').suffixes, ['.rc']) + self.assertEqual(P('/a/.hg.rc').suffixes, ['.rc']) + self.assertEqual(P('a/b.tar.gz').suffixes, ['.tar', '.gz']) + self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz']) + self.assertEqual(P('a/Some name. Ending with a dot.').suffixes, []) + self.assertEqual(P('/a/Some name. Ending with a dot.').suffixes, []) + + @needs_windows + def test_suffixes_windows(self): + P = self.cls + self.assertEqual(P('c:').suffixes, []) + self.assertEqual(P('c:/').suffixes, []) + self.assertEqual(P('c:a/b').suffixes, []) + self.assertEqual(P('c:/a/b').suffixes, []) + self.assertEqual(P('c:a/b.py').suffixes, ['.py']) + self.assertEqual(P('c:/a/b.py').suffixes, ['.py']) + self.assertEqual(P('c:a/.hgrc').suffixes, []) + self.assertEqual(P('c:/a/.hgrc').suffixes, []) + self.assertEqual(P('c:a/.hg.rc').suffixes, ['.rc']) + self.assertEqual(P('c:/a/.hg.rc').suffixes, ['.rc']) + self.assertEqual(P('c:a/b.tar.gz').suffixes, ['.tar', '.gz']) + self.assertEqual(P('c:/a/b.tar.gz').suffixes, ['.tar', '.gz']) + self.assertEqual(P('//My.py/Share.php').suffixes, []) + self.assertEqual(P('//My.py/Share.php/a/b').suffixes, []) + self.assertEqual(P('c:a/Some name. Ending with a dot.').suffixes, []) + self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffixes, []) + + def test_stem_empty(self): + P = self.cls + self.assertEqual(P('').stem, '') + self.assertEqual(P('.').stem, '.') + + def test_stem_common(self): + P = self.cls + self.assertEqual(P('..').stem, '..') + self.assertEqual(P('/').stem, '') + self.assertEqual(P('a/b').stem, 'b') + self.assertEqual(P('a/b.py').stem, 'b') + self.assertEqual(P('a/.hgrc').stem, '.hgrc') + self.assertEqual(P('a/.hg.rc').stem, '.hg') + self.assertEqual(P('a/b.tar.gz').stem, 'b.tar') + self.assertEqual(P('a/Some name. Ending with a dot.').stem, + 'Some name. Ending with a dot.') + + @needs_windows + def test_stem_windows(self): + P = self.cls + self.assertEqual(P('c:').stem, '') + self.assertEqual(P('c:.').stem, '') + self.assertEqual(P('c:..').stem, '..') + self.assertEqual(P('c:/').stem, '') + self.assertEqual(P('c:a/b').stem, 'b') + self.assertEqual(P('c:a/b.py').stem, 'b') + self.assertEqual(P('c:a/.hgrc').stem, '.hgrc') + self.assertEqual(P('c:a/.hg.rc').stem, '.hg') + self.assertEqual(P('c:a/b.tar.gz').stem, 'b.tar') + self.assertEqual(P('c:a/Some name. Ending with a dot.').stem, + 'Some name. Ending with a dot.') + def test_with_name_common(self): + P = self.cls + self.assertEqual(P('a/b').with_name('d.xml'), P('a/d.xml')) + self.assertEqual(P('/a/b').with_name('d.xml'), P('/a/d.xml')) + self.assertEqual(P('a/b.py').with_name('d.xml'), P('a/d.xml')) + self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml')) + self.assertEqual(P('a/Dot ending.').with_name('d.xml'), P('a/d.xml')) + self.assertEqual(P('/a/Dot ending.').with_name('d.xml'), P('/a/d.xml')) + + @needs_windows + def test_with_name_windows(self): + P = self.cls + self.assertEqual(P('c:a/b').with_name('d.xml'), P('c:a/d.xml')) + self.assertEqual(P('c:/a/b').with_name('d.xml'), P('c:/a/d.xml')) + self.assertEqual(P('c:a/Dot ending.').with_name('d.xml'), P('c:a/d.xml')) + self.assertEqual(P('c:/a/Dot ending.').with_name('d.xml'), P('c:/a/d.xml')) + self.assertRaises(ValueError, P('c:').with_name, 'd.xml') + self.assertRaises(ValueError, P('c:/').with_name, 'd.xml') + self.assertRaises(ValueError, P('//My/Share').with_name, 'd.xml') + self.assertEqual(str(P('a').with_name('d:')), '.\\d:') + self.assertEqual(str(P('a').with_name('d:e')), '.\\d:e') + self.assertEqual(P('c:a/b').with_name('d:'), P('c:a/d:')) + self.assertEqual(P('c:a/b').with_name('d:e'), P('c:a/d:e')) + self.assertRaises(ValueError, P('c:a/b').with_name, 'd:/e') + self.assertRaises(ValueError, P('c:a/b').with_name, '//My/Share') + + def test_with_name_empty(self): + P = self.cls + self.assertEqual(P('').with_name('d.xml'), P('d.xml')) + self.assertEqual(P('.').with_name('d.xml'), P('d.xml')) + self.assertEqual(P('/').with_name('d.xml'), P('/d.xml')) + self.assertEqual(P('a/b').with_name(''), P('a/')) + self.assertEqual(P('a/b').with_name('.'), P('a/.')) + + def test_with_name_seps(self): + P = self.cls + self.assertRaises(ValueError, P('a/b').with_name, '/c') + self.assertRaises(ValueError, P('a/b').with_name, 'c/') + self.assertRaises(ValueError, P('a/b').with_name, 'c/d') + + def test_with_stem_common(self): + P = self.cls + self.assertEqual(P('a/b').with_stem('d'), P('a/d')) + self.assertEqual(P('/a/b').with_stem('d'), P('/a/d')) + self.assertEqual(P('a/b.py').with_stem('d'), P('a/d.py')) + self.assertEqual(P('/a/b.py').with_stem('d'), P('/a/d.py')) + self.assertEqual(P('/a/b.tar.gz').with_stem('d'), P('/a/d.gz')) + self.assertEqual(P('a/Dot ending.').with_stem('d'), P('a/d')) + self.assertEqual(P('/a/Dot ending.').with_stem('d'), P('/a/d')) + + @needs_windows + def test_with_stem_windows(self): + P = self.cls + self.assertEqual(P('c:a/b').with_stem('d'), P('c:a/d')) + self.assertEqual(P('c:/a/b').with_stem('d'), P('c:/a/d')) + self.assertEqual(P('c:a/Dot ending.').with_stem('d'), P('c:a/d')) + self.assertEqual(P('c:/a/Dot ending.').with_stem('d'), P('c:/a/d')) + self.assertRaises(ValueError, P('c:').with_stem, 'd') + self.assertRaises(ValueError, P('c:/').with_stem, 'd') + self.assertRaises(ValueError, P('//My/Share').with_stem, 'd') + self.assertEqual(str(P('a').with_stem('d:')), '.\\d:') + self.assertEqual(str(P('a').with_stem('d:e')), '.\\d:e') + self.assertEqual(P('c:a/b').with_stem('d:'), P('c:a/d:')) + self.assertEqual(P('c:a/b').with_stem('d:e'), P('c:a/d:e')) + self.assertRaises(ValueError, P('c:a/b').with_stem, 'd:/e') + self.assertRaises(ValueError, P('c:a/b').with_stem, '//My/Share') + + def test_with_stem_empty(self): + P = self.cls + self.assertEqual(P('').with_stem('d'), P('d')) + self.assertEqual(P('.').with_stem('d'), P('d')) + self.assertEqual(P('/').with_stem('d'), P('/d')) + self.assertEqual(P('a/b').with_stem(''), P('a/')) + self.assertEqual(P('a/b').with_stem('.'), P('a/.')) + self.assertRaises(ValueError, P('foo.gz').with_stem, '') + self.assertRaises(ValueError, P('/a/b/foo.gz').with_stem, '') + + def test_with_stem_seps(self): + P = self.cls + self.assertRaises(ValueError, P('a/b').with_stem, '/c') + self.assertRaises(ValueError, P('a/b').with_stem, 'c/') + self.assertRaises(ValueError, P('a/b').with_stem, 'c/d') + + def test_with_suffix_common(self): + P = self.cls + self.assertEqual(P('a/b').with_suffix('.gz'), P('a/b.gz')) + self.assertEqual(P('/a/b').with_suffix('.gz'), P('/a/b.gz')) + self.assertEqual(P('a/b.py').with_suffix('.gz'), P('a/b.gz')) + self.assertEqual(P('/a/b.py').with_suffix('.gz'), P('/a/b.gz')) + # Stripping suffix. + self.assertEqual(P('a/b.py').with_suffix(''), P('a/b')) + self.assertEqual(P('/a/b').with_suffix(''), P('/a/b')) + + @needs_windows + def test_with_suffix_windows(self): + P = self.cls + self.assertEqual(P('c:a/b').with_suffix('.gz'), P('c:a/b.gz')) + self.assertEqual(P('c:/a/b').with_suffix('.gz'), P('c:/a/b.gz')) + self.assertEqual(P('c:a/b.py').with_suffix('.gz'), P('c:a/b.gz')) + self.assertEqual(P('c:/a/b.py').with_suffix('.gz'), P('c:/a/b.gz')) + # Path doesn't have a "filename" component. + self.assertRaises(ValueError, P('').with_suffix, '.gz') + self.assertRaises(ValueError, P('.').with_suffix, '.gz') + self.assertRaises(ValueError, P('/').with_suffix, '.gz') + self.assertRaises(ValueError, P('//My/Share').with_suffix, '.gz') + # Invalid suffix. + self.assertRaises(ValueError, P('c:a/b').with_suffix, 'gz') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '/') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '\\') + self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c:') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '/.gz') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '\\.gz') + self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c:.gz') + self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c/d') + self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c\\d') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c/d') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c\\d') + self.assertRaises(TypeError, P('c:a/b').with_suffix, None) + + def test_with_suffix_empty(self): + P = self.cls + # Path doesn't have a "filename" component. + self.assertRaises(ValueError, P('').with_suffix, '.gz') + self.assertRaises(ValueError, P('/').with_suffix, '.gz') + + def test_with_suffix_invalid(self): + P = self.cls + # Invalid suffix. + self.assertRaises(ValueError, P('a/b').with_suffix, 'gz') + self.assertRaises(ValueError, P('a/b').with_suffix, '/') + self.assertRaises(ValueError, P('a/b').with_suffix, '.') + self.assertRaises(ValueError, P('a/b').with_suffix, '/.gz') + self.assertRaises(ValueError, P('a/b').with_suffix, 'c/d') + self.assertRaises(ValueError, P('a/b').with_suffix, '.c/.d') + self.assertRaises(ValueError, P('a/b').with_suffix, './.d') + self.assertRaises(ValueError, P('a/b').with_suffix, '.d/.') + self.assertRaises(TypeError, P('a/b').with_suffix, None) + + def test_relative_to_common(self): + P = self.cls + p = P('a/b') + self.assertRaises(TypeError, p.relative_to) + self.assertRaises(TypeError, p.relative_to, b'a') + self.assertEqual(p.relative_to(P('')), P('a/b')) + self.assertEqual(p.relative_to(''), P('a/b')) + self.assertEqual(p.relative_to(P('a')), P('b')) + self.assertEqual(p.relative_to('a'), P('b')) + self.assertEqual(p.relative_to('a/'), P('b')) + self.assertEqual(p.relative_to(P('a/b')), P('')) + self.assertEqual(p.relative_to('a/b'), P('')) + self.assertEqual(p.relative_to(P(''), walk_up=True), P('a/b')) + self.assertEqual(p.relative_to('', walk_up=True), P('a/b')) + self.assertEqual(p.relative_to(P('a'), walk_up=True), P('b')) + self.assertEqual(p.relative_to('a', walk_up=True), P('b')) + self.assertEqual(p.relative_to('a/', walk_up=True), P('b')) + self.assertEqual(p.relative_to(P('a/b'), walk_up=True), P('')) + self.assertEqual(p.relative_to('a/b', walk_up=True), P('')) + self.assertEqual(p.relative_to(P('a/c'), walk_up=True), P('../b')) + self.assertEqual(p.relative_to('a/c', walk_up=True), P('../b')) + self.assertEqual(p.relative_to(P('a/b/c'), walk_up=True), P('..')) + self.assertEqual(p.relative_to('a/b/c', walk_up=True), P('..')) + self.assertEqual(p.relative_to(P('c'), walk_up=True), P('../a/b')) + self.assertEqual(p.relative_to('c', walk_up=True), P('../a/b')) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, P('c')) + self.assertRaises(ValueError, p.relative_to, P('a/b/c')) + self.assertRaises(ValueError, p.relative_to, P('a/c')) + self.assertRaises(ValueError, p.relative_to, P('/a')) + self.assertRaises(ValueError, p.relative_to, P("../a")) + self.assertRaises(ValueError, p.relative_to, P("a/..")) + self.assertRaises(ValueError, p.relative_to, P("/a/..")) + self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('/a'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) + p = P('/a/b') + self.assertEqual(p.relative_to(P('/')), P('a/b')) + self.assertEqual(p.relative_to('/'), P('a/b')) + self.assertEqual(p.relative_to(P('/a')), P('b')) + self.assertEqual(p.relative_to('/a'), P('b')) + self.assertEqual(p.relative_to('/a/'), P('b')) + self.assertEqual(p.relative_to(P('/a/b')), P('')) + self.assertEqual(p.relative_to('/a/b'), P('')) + self.assertEqual(p.relative_to(P('/'), walk_up=True), P('a/b')) + self.assertEqual(p.relative_to('/', walk_up=True), P('a/b')) + self.assertEqual(p.relative_to(P('/a'), walk_up=True), P('b')) + self.assertEqual(p.relative_to('/a', walk_up=True), P('b')) + self.assertEqual(p.relative_to('/a/', walk_up=True), P('b')) + self.assertEqual(p.relative_to(P('/a/b'), walk_up=True), P('')) + self.assertEqual(p.relative_to('/a/b', walk_up=True), P('')) + self.assertEqual(p.relative_to(P('/a/c'), walk_up=True), P('../b')) + self.assertEqual(p.relative_to('/a/c', walk_up=True), P('../b')) + self.assertEqual(p.relative_to(P('/a/b/c'), walk_up=True), P('..')) + self.assertEqual(p.relative_to('/a/b/c', walk_up=True), P('..')) + self.assertEqual(p.relative_to(P('/c'), walk_up=True), P('../a/b')) + self.assertEqual(p.relative_to('/c', walk_up=True), P('../a/b')) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, P('/c')) + self.assertRaises(ValueError, p.relative_to, P('/a/b/c')) + self.assertRaises(ValueError, p.relative_to, P('/a/c')) + self.assertRaises(ValueError, p.relative_to, P('')) + self.assertRaises(ValueError, p.relative_to, '') + self.assertRaises(ValueError, p.relative_to, P('a')) + self.assertRaises(ValueError, p.relative_to, P("../a")) + self.assertRaises(ValueError, p.relative_to, P("a/..")) + self.assertRaises(ValueError, p.relative_to, P("/a/..")) + self.assertRaises(ValueError, p.relative_to, P(''), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('a'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) + + @needs_windows + def test_relative_to_windows(self): + P = self.cls + p = P('C:Foo/Bar') + self.assertEqual(p.relative_to(P('c:')), P('Foo/Bar')) + self.assertEqual(p.relative_to('c:'), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('c:foO')), P('Bar')) + self.assertEqual(p.relative_to('c:foO'), P('Bar')) + self.assertEqual(p.relative_to('c:foO/'), P('Bar')) + self.assertEqual(p.relative_to(P('c:foO/baR')), P()) + self.assertEqual(p.relative_to('c:foO/baR'), P()) + self.assertEqual(p.relative_to(P('c:'), walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to('c:', walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('c:foO'), walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('c:foO', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('c:foO/', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to(P('c:foO/baR'), walk_up=True), P()) + self.assertEqual(p.relative_to('c:foO/baR', walk_up=True), P()) + self.assertEqual(p.relative_to(P('C:Foo/Bar/Baz'), walk_up=True), P('..')) + self.assertEqual(p.relative_to(P('C:Foo/Baz'), walk_up=True), P('../Bar')) + self.assertEqual(p.relative_to(P('C:Baz/Bar'), walk_up=True), P('../../Foo/Bar')) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, P()) + self.assertRaises(ValueError, p.relative_to, '') + self.assertRaises(ValueError, p.relative_to, P('d:')) + self.assertRaises(ValueError, p.relative_to, P('/')) + self.assertRaises(ValueError, p.relative_to, P('Foo')) + self.assertRaises(ValueError, p.relative_to, P('/Foo')) + self.assertRaises(ValueError, p.relative_to, P('C:/Foo')) + self.assertRaises(ValueError, p.relative_to, P('C:Foo/Bar/Baz')) + self.assertRaises(ValueError, p.relative_to, P('C:Foo/Baz')) + self.assertRaises(ValueError, p.relative_to, P(), walk_up=True) + self.assertRaises(ValueError, p.relative_to, '', walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('d:'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('/Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('C:/Foo'), walk_up=True) + p = P('C:/Foo/Bar') + self.assertEqual(p.relative_to(P('c:/')), P('Foo/Bar')) + self.assertEqual(p.relative_to('c:/'), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('c:/foO')), P('Bar')) + self.assertEqual(p.relative_to('c:/foO'), P('Bar')) + self.assertEqual(p.relative_to('c:/foO/'), P('Bar')) + self.assertEqual(p.relative_to(P('c:/foO/baR')), P()) + self.assertEqual(p.relative_to('c:/foO/baR'), P()) + self.assertEqual(p.relative_to(P('c:/'), walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to('c:/', walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('c:/foO'), walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('c:/foO', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('c:/foO/', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to(P('c:/foO/baR'), walk_up=True), P()) + self.assertEqual(p.relative_to('c:/foO/baR', walk_up=True), P()) + self.assertEqual(p.relative_to('C:/Baz', walk_up=True), P('../Foo/Bar')) + self.assertEqual(p.relative_to('C:/Foo/Bar/Baz', walk_up=True), P('..')) + self.assertEqual(p.relative_to('C:/Foo/Baz', walk_up=True), P('../Bar')) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, 'c:') + self.assertRaises(ValueError, p.relative_to, P('c:')) + self.assertRaises(ValueError, p.relative_to, P('C:/Baz')) + self.assertRaises(ValueError, p.relative_to, P('C:/Foo/Bar/Baz')) + self.assertRaises(ValueError, p.relative_to, P('C:/Foo/Baz')) + self.assertRaises(ValueError, p.relative_to, P('C:Foo')) + self.assertRaises(ValueError, p.relative_to, P('d:')) + self.assertRaises(ValueError, p.relative_to, P('d:/')) + self.assertRaises(ValueError, p.relative_to, P('/')) + self.assertRaises(ValueError, p.relative_to, P('/Foo')) + self.assertRaises(ValueError, p.relative_to, P('//C/Foo')) + self.assertRaises(ValueError, p.relative_to, 'c:', walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('c:'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('C:Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('d:'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('d:/'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('/Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('//C/Foo'), walk_up=True) + # UNC paths. + p = P('//Server/Share/Foo/Bar') + self.assertEqual(p.relative_to(P('//sErver/sHare')), P('Foo/Bar')) + self.assertEqual(p.relative_to('//sErver/sHare'), P('Foo/Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/'), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('//sErver/sHare/Foo')), P('Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/Foo'), P('Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/Foo/'), P('Bar')) + self.assertEqual(p.relative_to(P('//sErver/sHare/Foo/Bar')), P()) + self.assertEqual(p.relative_to('//sErver/sHare/Foo/Bar'), P()) + self.assertEqual(p.relative_to(P('//sErver/sHare'), walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to('//sErver/sHare', walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/', walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('//sErver/sHare/Foo'), walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/Foo', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/Foo/', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to(P('//sErver/sHare/Foo/Bar'), walk_up=True), P()) + self.assertEqual(p.relative_to('//sErver/sHare/Foo/Bar', walk_up=True), P()) + self.assertEqual(p.relative_to(P('//sErver/sHare/bar'), walk_up=True), P('../Foo/Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/bar', walk_up=True), P('../Foo/Bar')) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, P('/Server/Share/Foo')) + self.assertRaises(ValueError, p.relative_to, P('c:/Server/Share/Foo')) + self.assertRaises(ValueError, p.relative_to, P('//z/Share/Foo')) + self.assertRaises(ValueError, p.relative_to, P('//Server/z/Foo')) + self.assertRaises(ValueError, p.relative_to, P('/Server/Share/Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('c:/Server/Share/Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('//z/Share/Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('//Server/z/Foo'), walk_up=True) + + def test_is_relative_to_common(self): + P = self.cls + p = P('a/b') + self.assertRaises(TypeError, p.is_relative_to) + self.assertRaises(TypeError, p.is_relative_to, b'a') + self.assertTrue(p.is_relative_to(P(''))) + self.assertTrue(p.is_relative_to('')) + self.assertTrue(p.is_relative_to(P('a'))) + self.assertTrue(p.is_relative_to('a/')) + self.assertTrue(p.is_relative_to(P('a/b'))) + self.assertTrue(p.is_relative_to('a/b')) + # Unrelated paths. + self.assertFalse(p.is_relative_to(P('c'))) + self.assertFalse(p.is_relative_to(P('a/b/c'))) + self.assertFalse(p.is_relative_to(P('a/c'))) + self.assertFalse(p.is_relative_to(P('/a'))) + p = P('/a/b') + self.assertTrue(p.is_relative_to(P('/'))) + self.assertTrue(p.is_relative_to('/')) + self.assertTrue(p.is_relative_to(P('/a'))) + self.assertTrue(p.is_relative_to('/a')) + self.assertTrue(p.is_relative_to('/a/')) + self.assertTrue(p.is_relative_to(P('/a/b'))) + self.assertTrue(p.is_relative_to('/a/b')) + # Unrelated paths. + self.assertFalse(p.is_relative_to(P('/c'))) + self.assertFalse(p.is_relative_to(P('/a/b/c'))) + self.assertFalse(p.is_relative_to(P('/a/c'))) + self.assertFalse(p.is_relative_to(P(''))) + self.assertFalse(p.is_relative_to('')) + self.assertFalse(p.is_relative_to(P('a'))) + + @needs_windows + def test_is_relative_to_windows(self): + P = self.cls + p = P('C:Foo/Bar') + self.assertTrue(p.is_relative_to(P('c:'))) + self.assertTrue(p.is_relative_to('c:')) + self.assertTrue(p.is_relative_to(P('c:foO'))) + self.assertTrue(p.is_relative_to('c:foO')) + self.assertTrue(p.is_relative_to('c:foO/')) + self.assertTrue(p.is_relative_to(P('c:foO/baR'))) + self.assertTrue(p.is_relative_to('c:foO/baR')) + # Unrelated paths. + self.assertFalse(p.is_relative_to(P())) + self.assertFalse(p.is_relative_to('')) + self.assertFalse(p.is_relative_to(P('d:'))) + self.assertFalse(p.is_relative_to(P('/'))) + self.assertFalse(p.is_relative_to(P('Foo'))) + self.assertFalse(p.is_relative_to(P('/Foo'))) + self.assertFalse(p.is_relative_to(P('C:/Foo'))) + self.assertFalse(p.is_relative_to(P('C:Foo/Bar/Baz'))) + self.assertFalse(p.is_relative_to(P('C:Foo/Baz'))) + p = P('C:/Foo/Bar') + self.assertTrue(p.is_relative_to(P('c:/'))) + self.assertTrue(p.is_relative_to(P('c:/foO'))) + self.assertTrue(p.is_relative_to('c:/foO/')) + self.assertTrue(p.is_relative_to(P('c:/foO/baR'))) + self.assertTrue(p.is_relative_to('c:/foO/baR')) + # Unrelated paths. + self.assertFalse(p.is_relative_to('c:')) + self.assertFalse(p.is_relative_to(P('C:/Baz'))) + self.assertFalse(p.is_relative_to(P('C:/Foo/Bar/Baz'))) + self.assertFalse(p.is_relative_to(P('C:/Foo/Baz'))) + self.assertFalse(p.is_relative_to(P('C:Foo'))) + self.assertFalse(p.is_relative_to(P('d:'))) + self.assertFalse(p.is_relative_to(P('d:/'))) + self.assertFalse(p.is_relative_to(P('/'))) + self.assertFalse(p.is_relative_to(P('/Foo'))) + self.assertFalse(p.is_relative_to(P('//C/Foo'))) + # UNC paths. + p = P('//Server/Share/Foo/Bar') + self.assertTrue(p.is_relative_to(P('//sErver/sHare'))) + self.assertTrue(p.is_relative_to('//sErver/sHare')) + self.assertTrue(p.is_relative_to('//sErver/sHare/')) + self.assertTrue(p.is_relative_to(P('//sErver/sHare/Foo'))) + self.assertTrue(p.is_relative_to('//sErver/sHare/Foo')) + self.assertTrue(p.is_relative_to('//sErver/sHare/Foo/')) + self.assertTrue(p.is_relative_to(P('//sErver/sHare/Foo/Bar'))) + self.assertTrue(p.is_relative_to('//sErver/sHare/Foo/Bar')) + # Unrelated paths. + self.assertFalse(p.is_relative_to(P('/Server/Share/Foo'))) + self.assertFalse(p.is_relative_to(P('c:/Server/Share/Foo'))) + self.assertFalse(p.is_relative_to(P('//z/Share/Foo'))) + self.assertFalse(p.is_relative_to(P('//Server/z/Foo'))) + + @needs_posix + def test_is_absolute_posix(self): + P = self.cls + self.assertFalse(P('').is_absolute()) + self.assertFalse(P('a').is_absolute()) + self.assertFalse(P('a/b/').is_absolute()) + self.assertTrue(P('/').is_absolute()) + self.assertTrue(P('/a').is_absolute()) + self.assertTrue(P('/a/b/').is_absolute()) + self.assertTrue(P('//a').is_absolute()) + self.assertTrue(P('//a/b').is_absolute()) + + @needs_windows + def test_is_absolute_windows(self): + P = self.cls + # Under NT, only paths with both a drive and a root are absolute. + self.assertFalse(P().is_absolute()) + self.assertFalse(P('a').is_absolute()) + self.assertFalse(P('a/b/').is_absolute()) + self.assertFalse(P('/').is_absolute()) + self.assertFalse(P('/a').is_absolute()) + self.assertFalse(P('/a/b/').is_absolute()) + self.assertFalse(P('c:').is_absolute()) + self.assertFalse(P('c:a').is_absolute()) + self.assertFalse(P('c:a/b/').is_absolute()) + self.assertTrue(P('c:/').is_absolute()) + self.assertTrue(P('c:/a').is_absolute()) + self.assertTrue(P('c:/a/b/').is_absolute()) + # UNC paths are absolute by definition. + self.assertTrue(P('//').is_absolute()) + self.assertTrue(P('//a').is_absolute()) + self.assertTrue(P('//a/b').is_absolute()) + self.assertTrue(P('//a/b/').is_absolute()) + self.assertTrue(P('//a/b/c').is_absolute()) + self.assertTrue(P('//a/b/c/d').is_absolute()) + self.assertTrue(P('//?/UNC/').is_absolute()) + self.assertTrue(P('//?/UNC/spam').is_absolute()) + + +# +# Tests for the virtual classes. +# + +class PathBaseTest(PurePathBaseTest): + cls = PathBase + + def test_unsupported_operation(self): + P = self.cls + p = self.cls('') + e = UnsupportedOperation + self.assertRaises(e, p.stat) + self.assertRaises(e, p.lstat) + self.assertRaises(e, p.exists) + self.assertRaises(e, p.samefile, 'foo') + self.assertRaises(e, p.is_dir) + self.assertRaises(e, p.is_file) + self.assertRaises(e, p.is_mount) + self.assertRaises(e, p.is_symlink) + self.assertRaises(e, p.is_block_device) + self.assertRaises(e, p.is_char_device) + self.assertRaises(e, p.is_fifo) + self.assertRaises(e, p.is_socket) + self.assertRaises(e, p.open) + self.assertRaises(e, p.read_bytes) + self.assertRaises(e, p.read_text) + self.assertRaises(e, p.write_bytes, b'foo') + self.assertRaises(e, p.write_text, 'foo') + self.assertRaises(e, p.iterdir) + self.assertRaises(e, p.glob, '*') + self.assertRaises(e, p.rglob, '*') + self.assertRaises(e, lambda: list(p.walk())) + self.assertRaises(e, p.absolute) + self.assertRaises(e, P.cwd) + self.assertRaises(e, p.expanduser) + self.assertRaises(e, p.home) + self.assertRaises(e, p.readlink) + self.assertRaises(e, p.symlink_to, 'foo') + self.assertRaises(e, p.hardlink_to, 'foo') + self.assertRaises(e, p.mkdir) + self.assertRaises(e, p.touch) + self.assertRaises(e, p.rename, 'foo') + self.assertRaises(e, p.replace, 'foo') + self.assertRaises(e, p.chmod, 0o755) + self.assertRaises(e, p.lchmod, 0o755) + self.assertRaises(e, p.unlink) + self.assertRaises(e, p.rmdir) + self.assertRaises(e, p.owner) + self.assertRaises(e, p.group) + self.assertRaises(e, p.as_uri) + + def test_as_uri_common(self): + e = UnsupportedOperation + self.assertRaises(e, self.cls('').as_uri) + + def test_fspath_common(self): + self.assertRaises(TypeError, os.fspath, self.cls('')) + + def test_as_bytes_common(self): + self.assertRaises(TypeError, bytes, self.cls('')) + + +class DummyPathIO(io.BytesIO): + """ + Used by DummyPath to implement `open('w')` + """ + + def __init__(self, files, path): + super().__init__() + self.files = files + self.path = path + + def close(self): + self.files[self.path] = self.getvalue() + super().close() + + +DummyPathStatResult = collections.namedtuple( + 'DummyPathStatResult', + 'st_mode st_ino st_dev st_nlink st_uid st_gid st_size st_atime st_mtime st_ctime') + + +class DummyPath(PathBase): + """ + Simple implementation of PathBase that keeps files and directories in + memory. + """ + __slots__ = () + parser = posixpath + + _files = {} + _directories = {} + _symlinks = {} + + def __eq__(self, other): + if not isinstance(other, DummyPath): + return NotImplemented + return str(self) == str(other) + + def __hash__(self): + return hash(str(self)) + + def __repr__(self): + return "{}({!r})".format(self.__class__.__name__, self.as_posix()) + + def stat(self, *, follow_symlinks=True): + if follow_symlinks or self.name in ('', '.', '..'): + path = str(self.resolve(strict=True)) + else: + path = str(self.parent.resolve(strict=True) / self.name) + if path in self._files: + st_mode = stat.S_IFREG + elif path in self._directories: + st_mode = stat.S_IFDIR + elif path in self._symlinks: + st_mode = stat.S_IFLNK + else: + raise FileNotFoundError(errno.ENOENT, "Not found", str(self)) + return DummyPathStatResult(st_mode, hash(str(self)), 0, 0, 0, 0, 0, 0, 0, 0) + + def open(self, mode='r', buffering=-1, encoding=None, + errors=None, newline=None): + if buffering != -1: + raise NotImplementedError + path_obj = self.resolve() + path = str(path_obj) + name = path_obj.name + parent = str(path_obj.parent) + if path in self._directories: + raise IsADirectoryError(errno.EISDIR, "Is a directory", path) + + text = 'b' not in mode + mode = ''.join(c for c in mode if c not in 'btU') + if mode == 'r': + if path not in self._files: + raise FileNotFoundError(errno.ENOENT, "File not found", path) + stream = io.BytesIO(self._files[path]) + elif mode == 'w': + if parent not in self._directories: + raise FileNotFoundError(errno.ENOENT, "File not found", parent) + stream = DummyPathIO(self._files, path) + self._files[path] = b'' + self._directories[parent].add(name) + else: + raise NotImplementedError + if text: + stream = io.TextIOWrapper(stream, encoding=encoding, errors=errors, newline=newline) + return stream + + def iterdir(self): + path = str(self.resolve()) + if path in self._files: + raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path) + elif path in self._directories: + return (self / name for name in self._directories[path]) + else: + raise FileNotFoundError(errno.ENOENT, "File not found", path) + + def mkdir(self, mode=0o777, parents=False, exist_ok=False): + path = str(self.resolve()) + if path in self._directories: + if exist_ok: + return + else: + raise FileExistsError(errno.EEXIST, "File exists", path) + try: + if self.name: + self._directories[str(self.parent)].add(self.name) + self._directories[path] = set() + except KeyError: + if not parents: + raise FileNotFoundError(errno.ENOENT, "File not found", str(self.parent)) from None + self.parent.mkdir(parents=True, exist_ok=True) + self.mkdir(mode, parents=False, exist_ok=exist_ok) + + +class DummyPathTest(DummyPurePathTest): + """Tests for PathBase methods that use stat(), open() and iterdir().""" + + cls = DummyPath + can_symlink = False + + # (self.base) + # | + # |-- brokenLink -> non-existing + # |-- dirA + # | `-- linkC -> ../dirB + # |-- dirB + # | |-- fileB + # | `-- linkD -> ../dirB + # |-- dirC + # | |-- dirD + # | | `-- fileD + # | `-- fileC + # | `-- novel.txt + # |-- dirE # No permissions + # |-- fileA + # |-- linkA -> fileA + # |-- linkB -> dirB + # `-- brokenLinkLoop -> brokenLinkLoop + # + + def setUp(self): + super().setUp() + name = self.id().split('.')[-1] + if name in _tests_needing_symlinks and not self.can_symlink: + self.skipTest('requires symlinks') + parser = self.cls.parser + p = self.cls(self.base) + p.mkdir(parents=True) + p.joinpath('dirA').mkdir() + p.joinpath('dirB').mkdir() + p.joinpath('dirC').mkdir() + p.joinpath('dirC', 'dirD').mkdir() + p.joinpath('dirE').mkdir() + with p.joinpath('fileA').open('wb') as f: + f.write(b"this is file A\n") + with p.joinpath('dirB', 'fileB').open('wb') as f: + f.write(b"this is file B\n") + with p.joinpath('dirC', 'fileC').open('wb') as f: + f.write(b"this is file C\n") + with p.joinpath('dirC', 'novel.txt').open('wb') as f: + f.write(b"this is a novel\n") + with p.joinpath('dirC', 'dirD', 'fileD').open('wb') as f: + f.write(b"this is file D\n") + if self.can_symlink: + p.joinpath('linkA').symlink_to('fileA') + p.joinpath('brokenLink').symlink_to('non-existing') + p.joinpath('linkB').symlink_to('dirB') + p.joinpath('dirA', 'linkC').symlink_to(parser.join('..', 'dirB')) + p.joinpath('dirB', 'linkD').symlink_to(parser.join('..', 'dirB')) + p.joinpath('brokenLinkLoop').symlink_to('brokenLinkLoop') + + def tearDown(self): + cls = self.cls + cls._files.clear() + cls._directories.clear() + cls._symlinks.clear() + + def tempdir(self): + path = self.cls(self.base).with_name('tmp-dirD') + path.mkdir() + return path + + def assertFileNotFound(self, func, *args, **kwargs): + with self.assertRaises(FileNotFoundError) as cm: + func(*args, **kwargs) + self.assertEqual(cm.exception.errno, errno.ENOENT) + + def assertEqualNormCase(self, path_a, path_b): + normcase = self.parser.normcase + self.assertEqual(normcase(path_a), normcase(path_b)) + + def test_samefile(self): + parser = self.parser + fileA_path = parser.join(self.base, 'fileA') + fileB_path = parser.join(self.base, 'dirB', 'fileB') + p = self.cls(fileA_path) + pp = self.cls(fileA_path) + q = self.cls(fileB_path) + self.assertTrue(p.samefile(fileA_path)) + self.assertTrue(p.samefile(pp)) + self.assertFalse(p.samefile(fileB_path)) + self.assertFalse(p.samefile(q)) + # Test the non-existent file case + non_existent = parser.join(self.base, 'foo') + r = self.cls(non_existent) + self.assertRaises(FileNotFoundError, p.samefile, r) + self.assertRaises(FileNotFoundError, p.samefile, non_existent) + self.assertRaises(FileNotFoundError, r.samefile, p) + self.assertRaises(FileNotFoundError, r.samefile, non_existent) + self.assertRaises(FileNotFoundError, r.samefile, r) + self.assertRaises(FileNotFoundError, r.samefile, non_existent) + + def test_exists(self): + P = self.cls + p = P(self.base) + self.assertIs(True, p.exists()) + self.assertIs(True, (p / 'dirA').exists()) + self.assertIs(True, (p / 'fileA').exists()) + self.assertIs(False, (p / 'fileA' / 'bah').exists()) + if self.can_symlink: + self.assertIs(True, (p / 'linkA').exists()) + self.assertIs(True, (p / 'linkB').exists()) + self.assertIs(True, (p / 'linkB' / 'fileB').exists()) + self.assertIs(False, (p / 'linkA' / 'bah').exists()) + self.assertIs(False, (p / 'brokenLink').exists()) + self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False)) + self.assertIs(False, (p / 'foo').exists()) + self.assertIs(False, P('/xyzzy').exists()) + self.assertIs(False, P(self.base + '\udfff').exists()) + self.assertIs(False, P(self.base + '\x00').exists()) + + def test_open_common(self): + p = self.cls(self.base) + with (p / 'fileA').open('r') as f: + self.assertIsInstance(f, io.TextIOBase) + self.assertEqual(f.read(), "this is file A\n") + with (p / 'fileA').open('rb') as f: + self.assertIsInstance(f, io.BufferedIOBase) + self.assertEqual(f.read().strip(), b"this is file A") + + def test_read_write_bytes(self): + p = self.cls(self.base) + (p / 'fileA').write_bytes(b'abcdefg') + self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') + # Check that trying to write str does not truncate the file. + self.assertRaises(TypeError, (p / 'fileA').write_bytes, 'somestr') + self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') + + def test_read_write_text(self): + p = self.cls(self.base) + (p / 'fileA').write_text('äbcdefg', encoding='latin-1') + self.assertEqual((p / 'fileA').read_text( + encoding='utf-8', errors='ignore'), 'bcdefg') + # Check that trying to write bytes does not truncate the file. + self.assertRaises(TypeError, (p / 'fileA').write_text, b'somebytes') + self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg') + + def test_read_text_with_newlines(self): + p = self.cls(self.base) + # Check that `\n` character change nothing + (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_text(newline='\n'), + 'abcde\r\nfghlk\n\rmnopq') + # Check that `\r` character replaces `\n` + (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_text(newline='\r'), + 'abcde\r\nfghlk\n\rmnopq') + # Check that `\r\n` character replaces `\n` + (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_text(newline='\r\n'), + 'abcde\r\nfghlk\n\rmnopq') + + def test_write_text_with_newlines(self): + p = self.cls(self.base) + # Check that `\n` character change nothing + (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\n') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde\r\nfghlk\n\rmnopq') + # Check that `\r` character replaces `\n` + (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde\r\rfghlk\r\rmnopq') + # Check that `\r\n` character replaces `\n` + (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r\n') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde\r\r\nfghlk\r\n\rmnopq') + # Check that no argument passed will change `\n` to `os.linesep` + os_linesep_byte = bytes(os.linesep, encoding='ascii') + (p / 'fileA').write_text('abcde\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde' + os_linesep_byte + b'fghlk' + os_linesep_byte + b'\rmnopq') + + def test_iterdir(self): + P = self.cls + p = P(self.base) + it = p.iterdir() + paths = set(it) + expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA'] + if self.can_symlink: + expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop'] + self.assertEqual(paths, { P(self.base, q) for q in expected }) + + @needs_symlinks + def test_iterdir_symlink(self): + # __iter__ on a symlink to a directory. + P = self.cls + p = P(self.base, 'linkB') + paths = set(p.iterdir()) + expected = { P(self.base, 'linkB', q) for q in ['fileB', 'linkD'] } + self.assertEqual(paths, expected) + + def test_iterdir_nodir(self): + # __iter__ on something that is not a directory. + p = self.cls(self.base, 'fileA') + with self.assertRaises(OSError) as cm: + p.iterdir() + # ENOENT or EINVAL under Windows, ENOTDIR otherwise + # (see issue #12802). + self.assertIn(cm.exception.errno, (errno.ENOTDIR, + errno.ENOENT, errno.EINVAL)) + + def test_glob_common(self): + def _check(glob, expected): + self.assertEqual(set(glob), { P(self.base, q) for q in expected }) + P = self.cls + p = P(self.base) + it = p.glob("fileA") + self.assertIsInstance(it, collections.abc.Iterator) + _check(it, ["fileA"]) + _check(p.glob("fileB"), []) + _check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"]) + if not self.can_symlink: + _check(p.glob("*A"), ['dirA', 'fileA']) + else: + _check(p.glob("*A"), ['dirA', 'fileA', 'linkA']) + if not self.can_symlink: + _check(p.glob("*B/*"), ['dirB/fileB']) + else: + _check(p.glob("*B/*"), ['dirB/fileB', 'dirB/linkD', + 'linkB/fileB', 'linkB/linkD']) + if not self.can_symlink: + _check(p.glob("*/fileB"), ['dirB/fileB']) + else: + _check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB']) + if self.can_symlink: + _check(p.glob("brokenLink"), ['brokenLink']) + + if not self.can_symlink: + _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/"]) + else: + _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) + + @needs_posix + def test_glob_posix(self): + P = self.cls + p = P(self.base) + q = p / "FILEa" + given = set(p.glob("FILEa")) + expect = {q} if q.exists() else set() + self.assertEqual(given, expect) + self.assertEqual(set(p.glob("FILEa*")), set()) + + @needs_windows + def test_glob_windows(self): + P = self.cls + p = P(self.base) + self.assertEqual(set(p.glob("FILEa")), { P(self.base, "fileA") }) + self.assertEqual(set(p.glob("*a\\")), { P(self.base, "dirA/") }) + self.assertEqual(set(p.glob("F*a")), { P(self.base, "fileA") }) + + def test_glob_empty_pattern(self): + P = self.cls + p = P(self.base) + self.assertEqual(list(p.glob("")), [p]) + self.assertEqual(list(p.glob(".")), [p / "."]) + self.assertEqual(list(p.glob("./")), [p / "./"]) + + def test_glob_case_sensitive(self): + P = self.cls + def _check(path, pattern, case_sensitive, expected): + actual = {str(q) for q in path.glob(pattern, case_sensitive=case_sensitive)} + expected = {str(P(self.base, q)) for q in expected} + self.assertEqual(actual, expected) + path = P(self.base) + _check(path, "DIRB/FILE*", True, []) + _check(path, "DIRB/FILE*", False, ["dirB/fileB"]) + _check(path, "dirb/file*", True, []) + _check(path, "dirb/file*", False, ["dirB/fileB"]) + + @needs_symlinks + def test_glob_recurse_symlinks_common(self): + def _check(path, glob, expected): + actual = {path for path in path.glob(glob, recurse_symlinks=True) + if path.parts.count("linkD") <= 1} # exclude symlink loop. + self.assertEqual(actual, { P(self.base, q) for q in expected }) + P = self.cls + p = P(self.base) + _check(p, "fileB", []) + _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) + _check(p, "*A", ["dirA", "fileA", "linkA"]) + _check(p, "*B/*", ["dirB/fileB", "dirB/linkD", "linkB/fileB", "linkB/linkD"]) + _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) + _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/..", "dirB/linkD/.."]) + _check(p, "dir*/**", [ + "dirA/", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB", + "dirB/", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB", + "dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", + "dirE/"]) + _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirC/dirD/", "dirE/"]) + _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..", + "dirB/linkD/..", "dirA/linkC/linkD/..", + "dirC/..", "dirC/dirD/..", "dirE/.."]) + _check(p, "dir*/*/**", [ + "dirA/linkC/", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB", + "dirB/linkD/", "dirB/linkD/fileB", + "dirC/dirD/", "dirC/dirD/fileD"]) + _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"]) + _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirA/linkC/linkD/..", + "dirB/linkD/..", "dirC/dirD/.."]) + _check(p, "dir*/**/fileC", ["dirC/fileC"]) + _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) + _check(p, "*/dirD/**", ["dirC/dirD/", "dirC/dirD/fileD"]) + _check(p, "*/dirD/**/", ["dirC/dirD/"]) + + def test_rglob_recurse_symlinks_false(self): + def _check(path, glob, expected): + actual = set(path.rglob(glob, recurse_symlinks=False)) + self.assertEqual(actual, { P(self.base, q) for q in expected }) + P = self.cls + p = P(self.base) + it = p.rglob("fileA") + self.assertIsInstance(it, collections.abc.Iterator) + _check(p, "fileA", ["fileA"]) + _check(p, "fileB", ["dirB/fileB"]) + _check(p, "**/fileB", ["dirB/fileB"]) + _check(p, "*/fileA", []) + + if self.can_symlink: + _check(p, "*/fileB", ["dirB/fileB", "dirB/linkD/fileB", + "linkB/fileB", "dirA/linkC/fileB"]) + _check(p, "*/", [ + "dirA/", "dirA/linkC/", "dirB/", "dirB/linkD/", "dirC/", + "dirC/dirD/", "dirE/", "linkB/"]) + else: + _check(p, "*/fileB", ["dirB/fileB"]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) + + _check(p, "file*", ["fileA", "dirB/fileB", "dirC/fileC", "dirC/dirD/fileD"]) + _check(p, "", ["", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) + p = P(self.base, "dirC") + _check(p, "*", ["dirC/fileC", "dirC/novel.txt", + "dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p, "**/file*", ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p, "dir*/**", ["dirC/dirD/", "dirC/dirD/fileD"]) + _check(p, "dir*/**/", ["dirC/dirD/"]) + _check(p, "*/*", ["dirC/dirD/fileD"]) + _check(p, "*/", ["dirC/dirD/"]) + _check(p, "", ["dirC/", "dirC/dirD/"]) + _check(p, "**", ["dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"]) + _check(p, "**/", ["dirC/", "dirC/dirD/"]) + # gh-91616, a re module regression + _check(p, "*.txt", ["dirC/novel.txt"]) + _check(p, "*.*", ["dirC/novel.txt"]) + + @needs_posix + def test_rglob_posix(self): + P = self.cls + p = P(self.base, "dirC") + q = p / "dirD" / "FILEd" + given = set(p.rglob("FILEd")) + expect = {q} if q.exists() else set() + self.assertEqual(given, expect) + self.assertEqual(set(p.rglob("FILEd*")), set()) + + @needs_windows + def test_rglob_windows(self): + P = self.cls + p = P(self.base, "dirC") + self.assertEqual(set(p.rglob("FILEd")), { P(self.base, "dirC/dirD/fileD") }) + self.assertEqual(set(p.rglob("*\\")), { P(self.base, "dirC/dirD/") }) + + @needs_symlinks + def test_rglob_recurse_symlinks_common(self): + def _check(path, glob, expected): + actual = {path for path in path.rglob(glob, recurse_symlinks=True) + if path.parts.count("linkD") <= 1} # exclude symlink loop. + self.assertEqual(actual, { P(self.base, q) for q in expected }) + P = self.cls + p = P(self.base) + _check(p, "fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB", + "dirA/linkC/linkD/fileB", "dirB/linkD/fileB", "linkB/linkD/fileB"]) + _check(p, "*/fileA", []) + _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB", + "dirA/linkC/linkD/fileB", "dirB/linkD/fileB", "linkB/linkD/fileB"]) + _check(p, "file*", ["fileA", "dirA/linkC/fileB", "dirB/fileB", + "dirA/linkC/linkD/fileB", "dirB/linkD/fileB", "linkB/linkD/fileB", + "dirC/fileC", "dirC/dirD/fileD", "linkB/fileB"]) + _check(p, "*/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirC/dirD/", "dirE/", "linkB/", "linkB/linkD/"]) + _check(p, "", ["", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"]) + + p = P(self.base, "dirC") + _check(p, "*", ["dirC/fileC", "dirC/novel.txt", + "dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p, "*/*", ["dirC/dirD/fileD"]) + _check(p, "*/", ["dirC/dirD/"]) + _check(p, "", ["dirC/", "dirC/dirD/"]) + # gh-91616, a re module regression + _check(p, "*.txt", ["dirC/novel.txt"]) + _check(p, "*.*", ["dirC/novel.txt"]) + + @needs_symlinks + def test_rglob_symlink_loop(self): + # Don't get fooled by symlink loops (Issue #26012). + P = self.cls + p = P(self.base) + given = set(p.rglob('*', recurse_symlinks=False)) + expect = {'brokenLink', + 'dirA', 'dirA/linkC', + 'dirB', 'dirB/fileB', 'dirB/linkD', + 'dirC', 'dirC/dirD', 'dirC/dirD/fileD', + 'dirC/fileC', 'dirC/novel.txt', + 'dirE', + 'fileA', + 'linkA', + 'linkB', + 'brokenLinkLoop', + } + self.assertEqual(given, {p / x for x in expect}) + + # See https://github.com/WebAssembly/wasi-filesystem/issues/26 + @unittest.skipIf(is_wasi, "WASI resolution of '..' parts doesn't match POSIX") + def test_glob_dotdot(self): + # ".." is not special in globs. + P = self.cls + p = P(self.base) + self.assertEqual(set(p.glob("..")), { P(self.base, "..") }) + self.assertEqual(set(p.glob("../..")), { P(self.base, "..", "..") }) + self.assertEqual(set(p.glob("dirA/..")), { P(self.base, "dirA", "..") }) + self.assertEqual(set(p.glob("dirA/../file*")), { P(self.base, "dirA/../fileA") }) + self.assertEqual(set(p.glob("dirA/../file*/..")), set()) + self.assertEqual(set(p.glob("../xyzzy")), set()) + if self.cls.parser is posixpath: + self.assertEqual(set(p.glob("xyzzy/..")), set()) + else: + # ".." segments are normalized first on Windows, so this path is stat()able. + self.assertEqual(set(p.glob("xyzzy/..")), { P(self.base, "xyzzy", "..") }) + self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(self.base, *[".."] * 50)}) + + @needs_symlinks + def test_glob_permissions(self): + # See bpo-38894 + P = self.cls + base = P(self.base) / 'permissions' + base.mkdir() + + for i in range(100): + link = base / f"link{i}" + if i % 2: + link.symlink_to(P(self.base, "dirE", "nonexistent")) + else: + link.symlink_to(P(self.base, "dirC")) + + self.assertEqual(len(set(base.glob("*"))), 100) + self.assertEqual(len(set(base.glob("*/"))), 50) + self.assertEqual(len(set(base.glob("*/fileC"))), 50) + self.assertEqual(len(set(base.glob("*/file*"))), 50) + + @needs_symlinks + def test_glob_long_symlink(self): + # See gh-87695 + base = self.cls(self.base) / 'long_symlink' + base.mkdir() + bad_link = base / 'bad_link' + bad_link.symlink_to("bad" * 200) + self.assertEqual(sorted(base.glob('**/*')), [bad_link]) + + @needs_symlinks + def test_readlink(self): + P = self.cls(self.base) + self.assertEqual((P / 'linkA').readlink(), self.cls('fileA')) + self.assertEqual((P / 'brokenLink').readlink(), + self.cls('non-existing')) + self.assertEqual((P / 'linkB').readlink(), self.cls('dirB')) + self.assertEqual((P / 'linkB' / 'linkD').readlink(), self.cls('../dirB')) + with self.assertRaises(OSError): + (P / 'fileA').readlink() + + @unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present") + def test_readlink_unsupported(self): + P = self.cls(self.base) + p = P / 'fileA' + with self.assertRaises(UnsupportedOperation): + q.readlink(p) + + def _check_resolve(self, p, expected, strict=True): + q = p.resolve(strict) + self.assertEqual(q, expected) + + # This can be used to check both relative and absolute resolutions. + _check_resolve_relative = _check_resolve_absolute = _check_resolve + + @needs_symlinks + def test_resolve_common(self): + P = self.cls + p = P(self.base, 'foo') + with self.assertRaises(OSError) as cm: + p.resolve(strict=True) + self.assertEqual(cm.exception.errno, errno.ENOENT) + # Non-strict + parser = self.parser + self.assertEqualNormCase(str(p.resolve(strict=False)), + parser.join(self.base, 'foo')) + p = P(self.base, 'foo', 'in', 'spam') + self.assertEqualNormCase(str(p.resolve(strict=False)), + parser.join(self.base, 'foo', 'in', 'spam')) + p = P(self.base, '..', 'foo', 'in', 'spam') + self.assertEqualNormCase(str(p.resolve(strict=False)), + parser.join(parser.dirname(self.base), 'foo', 'in', 'spam')) + # These are all relative symlinks. + p = P(self.base, 'dirB', 'fileB') + self._check_resolve_relative(p, p) + p = P(self.base, 'linkA') + self._check_resolve_relative(p, P(self.base, 'fileA')) + p = P(self.base, 'dirA', 'linkC', 'fileB') + self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB')) + p = P(self.base, 'dirB', 'linkD', 'fileB') + self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB')) + # Non-strict + p = P(self.base, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam') + self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB', 'foo', 'in', + 'spam'), False) + p = P(self.base, 'dirA', 'linkC', '..', 'foo', 'in', 'spam') + if self.cls.parser is not posixpath: + # In Windows, if linkY points to dirB, 'dirA\linkY\..' + # resolves to 'dirA' without resolving linkY first. + self._check_resolve_relative(p, P(self.base, 'dirA', 'foo', 'in', + 'spam'), False) + else: + # In Posix, if linkY points to dirB, 'dirA/linkY/..' + # resolves to 'dirB/..' first before resolving to parent of dirB. + self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False) + # Now create absolute symlinks. + d = self.tempdir() + P(self.base, 'dirA', 'linkX').symlink_to(d) + P(self.base, str(d), 'linkY').symlink_to(self.parser.join(self.base, 'dirB')) + p = P(self.base, 'dirA', 'linkX', 'linkY', 'fileB') + self._check_resolve_absolute(p, P(self.base, 'dirB', 'fileB')) + # Non-strict + p = P(self.base, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam') + self._check_resolve_relative(p, P(self.base, 'dirB', 'foo', 'in', 'spam'), + False) + p = P(self.base, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam') + if self.cls.parser is not posixpath: + # In Windows, if linkY points to dirB, 'dirA\linkY\..' + # resolves to 'dirA' without resolving linkY first. + self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False) + else: + # In Posix, if linkY points to dirB, 'dirA/linkY/..' + # resolves to 'dirB/..' first before resolving to parent of dirB. + self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False) + + @needs_symlinks + def test_resolve_dot(self): + # See http://web.archive.org/web/20200623062557/https://bitbucket.org/pitrou/pathlib/issues/9/ + parser = self.parser + p = self.cls(self.base) + p.joinpath('0').symlink_to('.', target_is_directory=True) + p.joinpath('1').symlink_to(parser.join('0', '0'), target_is_directory=True) + p.joinpath('2').symlink_to(parser.join('1', '1'), target_is_directory=True) + q = p / '2' + self.assertEqual(q.resolve(strict=True), p) + r = q / '3' / '4' + self.assertRaises(FileNotFoundError, r.resolve, strict=True) + # Non-strict + self.assertEqual(r.resolve(strict=False), p / '3' / '4') + + def _check_symlink_loop(self, *args): + path = self.cls(*args) + with self.assertRaises(OSError) as cm: + path.resolve(strict=True) + self.assertEqual(cm.exception.errno, errno.ELOOP) + + @needs_posix + @needs_symlinks + def test_resolve_loop(self): + # Loops with relative symlinks. + self.cls(self.base, 'linkX').symlink_to('linkX/inside') + self._check_symlink_loop(self.base, 'linkX') + self.cls(self.base, 'linkY').symlink_to('linkY') + self._check_symlink_loop(self.base, 'linkY') + self.cls(self.base, 'linkZ').symlink_to('linkZ/../linkZ') + self._check_symlink_loop(self.base, 'linkZ') + # Non-strict + p = self.cls(self.base, 'linkZ', 'foo') + self.assertEqual(p.resolve(strict=False), p) + # Loops with absolute symlinks. + self.cls(self.base, 'linkU').symlink_to(self.parser.join(self.base, 'linkU/inside')) + self._check_symlink_loop(self.base, 'linkU') + self.cls(self.base, 'linkV').symlink_to(self.parser.join(self.base, 'linkV')) + self._check_symlink_loop(self.base, 'linkV') + self.cls(self.base, 'linkW').symlink_to(self.parser.join(self.base, 'linkW/../linkW')) + self._check_symlink_loop(self.base, 'linkW') + # Non-strict + q = self.cls(self.base, 'linkW', 'foo') + self.assertEqual(q.resolve(strict=False), q) + + def test_stat(self): + statA = self.cls(self.base).joinpath('fileA').stat() + statB = self.cls(self.base).joinpath('dirB', 'fileB').stat() + statC = self.cls(self.base).joinpath('dirC').stat() + # st_mode: files are the same, directory differs. + self.assertIsInstance(statA.st_mode, int) + self.assertEqual(statA.st_mode, statB.st_mode) + self.assertNotEqual(statA.st_mode, statC.st_mode) + self.assertNotEqual(statB.st_mode, statC.st_mode) + # st_ino: all different, + self.assertIsInstance(statA.st_ino, int) + self.assertNotEqual(statA.st_ino, statB.st_ino) + self.assertNotEqual(statA.st_ino, statC.st_ino) + self.assertNotEqual(statB.st_ino, statC.st_ino) + # st_dev: all the same. + self.assertIsInstance(statA.st_dev, int) + self.assertEqual(statA.st_dev, statB.st_dev) + self.assertEqual(statA.st_dev, statC.st_dev) + # other attributes not used by pathlib. + + @needs_symlinks + def test_stat_no_follow_symlinks(self): + p = self.cls(self.base) / 'linkA' + st = p.stat() + self.assertNotEqual(st, p.stat(follow_symlinks=False)) + + def test_stat_no_follow_symlinks_nosymlink(self): + p = self.cls(self.base) / 'fileA' + st = p.stat() + self.assertEqual(st, p.stat(follow_symlinks=False)) + + @needs_symlinks + def test_lstat(self): + p = self.cls(self.base)/ 'linkA' + st = p.stat() + self.assertNotEqual(st, p.lstat()) + + def test_lstat_nosymlink(self): + p = self.cls(self.base) / 'fileA' + st = p.stat() + self.assertEqual(st, p.lstat()) + + def test_is_dir(self): + P = self.cls(self.base) + self.assertTrue((P / 'dirA').is_dir()) + self.assertFalse((P / 'fileA').is_dir()) + self.assertFalse((P / 'non-existing').is_dir()) + self.assertFalse((P / 'fileA' / 'bah').is_dir()) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_dir()) + self.assertTrue((P / 'linkB').is_dir()) + self.assertFalse((P/ 'brokenLink').is_dir()) + self.assertFalse((P / 'dirA\udfff').is_dir()) + self.assertFalse((P / 'dirA\x00').is_dir()) + + def test_is_dir_no_follow_symlinks(self): + P = self.cls(self.base) + self.assertTrue((P / 'dirA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'fileA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'non-existing').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'fileA' / 'bah').is_dir(follow_symlinks=False)) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'linkB').is_dir(follow_symlinks=False)) + self.assertFalse((P/ 'brokenLink').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'dirA\udfff').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'dirA\x00').is_dir(follow_symlinks=False)) + + def test_is_file(self): + P = self.cls(self.base) + self.assertTrue((P / 'fileA').is_file()) + self.assertFalse((P / 'dirA').is_file()) + self.assertFalse((P / 'non-existing').is_file()) + self.assertFalse((P / 'fileA' / 'bah').is_file()) + if self.can_symlink: + self.assertTrue((P / 'linkA').is_file()) + self.assertFalse((P / 'linkB').is_file()) + self.assertFalse((P/ 'brokenLink').is_file()) + self.assertFalse((P / 'fileA\udfff').is_file()) + self.assertFalse((P / 'fileA\x00').is_file()) + + def test_is_file_no_follow_symlinks(self): + P = self.cls(self.base) + self.assertTrue((P / 'fileA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'dirA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'non-existing').is_file(follow_symlinks=False)) + self.assertFalse((P / 'fileA' / 'bah').is_file(follow_symlinks=False)) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'linkB').is_file(follow_symlinks=False)) + self.assertFalse((P/ 'brokenLink').is_file(follow_symlinks=False)) + self.assertFalse((P / 'fileA\udfff').is_file(follow_symlinks=False)) + self.assertFalse((P / 'fileA\x00').is_file(follow_symlinks=False)) + + def test_is_mount(self): + P = self.cls(self.base) + self.assertFalse((P / 'fileA').is_mount()) + self.assertFalse((P / 'dirA').is_mount()) + self.assertFalse((P / 'non-existing').is_mount()) + self.assertFalse((P / 'fileA' / 'bah').is_mount()) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_mount()) + + def test_is_symlink(self): + P = self.cls(self.base) + self.assertFalse((P / 'fileA').is_symlink()) + self.assertFalse((P / 'dirA').is_symlink()) + self.assertFalse((P / 'non-existing').is_symlink()) + self.assertFalse((P / 'fileA' / 'bah').is_symlink()) + if self.can_symlink: + self.assertTrue((P / 'linkA').is_symlink()) + self.assertTrue((P / 'linkB').is_symlink()) + self.assertTrue((P/ 'brokenLink').is_symlink()) + self.assertIs((P / 'fileA\udfff').is_file(), False) + self.assertIs((P / 'fileA\x00').is_file(), False) + if self.can_symlink: + self.assertIs((P / 'linkA\udfff').is_file(), False) + self.assertIs((P / 'linkA\x00').is_file(), False) + + def test_is_junction_false(self): + P = self.cls(self.base) + self.assertFalse((P / 'fileA').is_junction()) + self.assertFalse((P / 'dirA').is_junction()) + self.assertFalse((P / 'non-existing').is_junction()) + self.assertFalse((P / 'fileA' / 'bah').is_junction()) + self.assertFalse((P / 'fileA\udfff').is_junction()) + self.assertFalse((P / 'fileA\x00').is_junction()) + + def test_is_fifo_false(self): + P = self.cls(self.base) + self.assertFalse((P / 'fileA').is_fifo()) + self.assertFalse((P / 'dirA').is_fifo()) + self.assertFalse((P / 'non-existing').is_fifo()) + self.assertFalse((P / 'fileA' / 'bah').is_fifo()) + self.assertIs((P / 'fileA\udfff').is_fifo(), False) + self.assertIs((P / 'fileA\x00').is_fifo(), False) + + def test_is_socket_false(self): + P = self.cls(self.base) + self.assertFalse((P / 'fileA').is_socket()) + self.assertFalse((P / 'dirA').is_socket()) + self.assertFalse((P / 'non-existing').is_socket()) + self.assertFalse((P / 'fileA' / 'bah').is_socket()) + self.assertIs((P / 'fileA\udfff').is_socket(), False) + self.assertIs((P / 'fileA\x00').is_socket(), False) + + def test_is_block_device_false(self): + P = self.cls(self.base) + self.assertFalse((P / 'fileA').is_block_device()) + self.assertFalse((P / 'dirA').is_block_device()) + self.assertFalse((P / 'non-existing').is_block_device()) + self.assertFalse((P / 'fileA' / 'bah').is_block_device()) + self.assertIs((P / 'fileA\udfff').is_block_device(), False) + self.assertIs((P / 'fileA\x00').is_block_device(), False) + + def test_is_char_device_false(self): + P = self.cls(self.base) + self.assertFalse((P / 'fileA').is_char_device()) + self.assertFalse((P / 'dirA').is_char_device()) + self.assertFalse((P / 'non-existing').is_char_device()) + self.assertFalse((P / 'fileA' / 'bah').is_char_device()) + self.assertIs((P / 'fileA\udfff').is_char_device(), False) + self.assertIs((P / 'fileA\x00').is_char_device(), False) + + def _check_complex_symlinks(self, link0_target): + # Test solving a non-looping chain of symlinks (issue #19887). + parser = self.parser + P = self.cls(self.base) + P.joinpath('link1').symlink_to(parser.join('link0', 'link0'), target_is_directory=True) + P.joinpath('link2').symlink_to(parser.join('link1', 'link1'), target_is_directory=True) + P.joinpath('link3').symlink_to(parser.join('link2', 'link2'), target_is_directory=True) + P.joinpath('link0').symlink_to(link0_target, target_is_directory=True) + + # Resolve absolute paths. + p = (P / 'link0').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), self.base) + p = (P / 'link1').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), self.base) + p = (P / 'link2').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), self.base) + p = (P / 'link3').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), self.base) + + # Resolve relative paths. + try: + self.cls('').absolute() + except UnsupportedOperation: + return + old_path = os.getcwd() + os.chdir(self.base) + try: + p = self.cls('link0').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), self.base) + p = self.cls('link1').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), self.base) + p = self.cls('link2').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), self.base) + p = self.cls('link3').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), self.base) + finally: + os.chdir(old_path) + + @needs_symlinks + def test_complex_symlinks_absolute(self): + self._check_complex_symlinks(self.base) + + @needs_symlinks + def test_complex_symlinks_relative(self): + self._check_complex_symlinks('.') + + @needs_symlinks + def test_complex_symlinks_relative_dot_dot(self): + self._check_complex_symlinks(self.parser.join('dirA', '..')) + + def setUpWalk(self): + # Build: + # TESTFN/ + # TEST1/ a file kid and two directory kids + # tmp1 + # SUB1/ a file kid and a directory kid + # tmp2 + # SUB11/ no kids + # SUB2/ a file kid and a dirsymlink kid + # tmp3 + # link/ a symlink to TEST2 + # broken_link + # broken_link2 + # TEST2/ + # tmp4 a lone file + self.walk_path = self.cls(self.base, "TEST1") + self.sub1_path = self.walk_path / "SUB1" + self.sub11_path = self.sub1_path / "SUB11" + self.sub2_path = self.walk_path / "SUB2" + tmp1_path = self.walk_path / "tmp1" + tmp2_path = self.sub1_path / "tmp2" + tmp3_path = self.sub2_path / "tmp3" + self.link_path = self.sub2_path / "link" + t2_path = self.cls(self.base, "TEST2") + tmp4_path = self.cls(self.base, "TEST2", "tmp4") + broken_link_path = self.sub2_path / "broken_link" + broken_link2_path = self.sub2_path / "broken_link2" + + self.sub11_path.mkdir(parents=True) + self.sub2_path.mkdir(parents=True) + t2_path.mkdir(parents=True) + + for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path: + with path.open("w", encoding='utf-8') as f: + f.write(f"I'm {path} and proud of it. Blame test_pathlib.\n") + + if self.can_symlink: + self.link_path.symlink_to(t2_path) + broken_link_path.symlink_to('broken') + broken_link2_path.symlink_to(self.cls('tmp3', 'broken')) + self.sub2_tree = (self.sub2_path, [], ["broken_link", "broken_link2", "link", "tmp3"]) + else: + self.sub2_tree = (self.sub2_path, [], ["tmp3"]) + + def test_walk_topdown(self): + self.setUpWalk() + walker = self.walk_path.walk() + entry = next(walker) + entry[1].sort() # Ensure we visit SUB1 before SUB2 + self.assertEqual(entry, (self.walk_path, ["SUB1", "SUB2"], ["tmp1"])) + entry = next(walker) + self.assertEqual(entry, (self.sub1_path, ["SUB11"], ["tmp2"])) + entry = next(walker) + self.assertEqual(entry, (self.sub11_path, [], [])) + entry = next(walker) + entry[1].sort() + entry[2].sort() + self.assertEqual(entry, self.sub2_tree) + with self.assertRaises(StopIteration): + next(walker) + + def test_walk_prune(self): + self.setUpWalk() + # Prune the search. + all = [] + for root, dirs, files in self.walk_path.walk(): + all.append((root, dirs, files)) + if 'SUB1' in dirs: + # Note that this also mutates the dirs we appended to all! + dirs.remove('SUB1') + + self.assertEqual(len(all), 2) + self.assertEqual(all[0], (self.walk_path, ["SUB2"], ["tmp1"])) + + all[1][-1].sort() + all[1][1].sort() + self.assertEqual(all[1], self.sub2_tree) + + def test_walk_bottom_up(self): + self.setUpWalk() + seen_testfn = seen_sub1 = seen_sub11 = seen_sub2 = False + for path, dirnames, filenames in self.walk_path.walk(top_down=False): + if path == self.walk_path: + self.assertFalse(seen_testfn) + self.assertTrue(seen_sub1) + self.assertTrue(seen_sub2) + self.assertEqual(sorted(dirnames), ["SUB1", "SUB2"]) + self.assertEqual(filenames, ["tmp1"]) + seen_testfn = True + elif path == self.sub1_path: + self.assertFalse(seen_testfn) + self.assertFalse(seen_sub1) + self.assertTrue(seen_sub11) + self.assertEqual(dirnames, ["SUB11"]) + self.assertEqual(filenames, ["tmp2"]) + seen_sub1 = True + elif path == self.sub11_path: + self.assertFalse(seen_sub1) + self.assertFalse(seen_sub11) + self.assertEqual(dirnames, []) + self.assertEqual(filenames, []) + seen_sub11 = True + elif path == self.sub2_path: + self.assertFalse(seen_testfn) + self.assertFalse(seen_sub2) + self.assertEqual(sorted(dirnames), sorted(self.sub2_tree[1])) + self.assertEqual(sorted(filenames), sorted(self.sub2_tree[2])) + seen_sub2 = True + else: + raise AssertionError(f"Unexpected path: {path}") + self.assertTrue(seen_testfn) + + @needs_symlinks + def test_walk_follow_symlinks(self): + self.setUpWalk() + walk_it = self.walk_path.walk(follow_symlinks=True) + for root, dirs, files in walk_it: + if root == self.link_path: + self.assertEqual(dirs, []) + self.assertEqual(files, ["tmp4"]) + break + else: + self.fail("Didn't follow symlink with follow_symlinks=True") + + @needs_symlinks + def test_walk_symlink_location(self): + self.setUpWalk() + # Tests whether symlinks end up in filenames or dirnames depending + # on the `follow_symlinks` argument. + walk_it = self.walk_path.walk(follow_symlinks=False) + for root, dirs, files in walk_it: + if root == self.sub2_path: + self.assertIn("link", files) + break + else: + self.fail("symlink not found") + + walk_it = self.walk_path.walk(follow_symlinks=True) + for root, dirs, files in walk_it: + if root == self.sub2_path: + self.assertIn("link", dirs) + break + else: + self.fail("symlink not found") + + +class DummyPathWithSymlinks(DummyPath): + __slots__ = () + + # Reduce symlink traversal limit to make tests run faster. + _max_symlinks = 20 + + def readlink(self): + path = str(self.parent.resolve() / self.name) + if path in self._symlinks: + return self.with_segments(self._symlinks[path]) + elif path in self._files or path in self._directories: + raise OSError(errno.EINVAL, "Not a symlink", path) + else: + raise FileNotFoundError(errno.ENOENT, "File not found", path) + + def symlink_to(self, target, target_is_directory=False): + self._directories[str(self.parent)].add(self.name) + self._symlinks[str(self)] = str(target) + + +class DummyPathWithSymlinksTest(DummyPathTest): + cls = DummyPathWithSymlinks + can_symlink = True + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py index 82f1d9dc2e7..aee9fb78017 100644 --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -10,7 +10,9 @@ from unittest.mock import patch from test import support from test.support import os_helper +from test.support import socket_helper from test.support import warnings_helper +from test.support.testcase import ExtraAssertions import os try: import ssl @@ -18,12 +20,15 @@ ssl = None import sys import tempfile -from nturl2path import url2pathname, pathname2url from base64 import b64encode import collections +if not socket_helper.has_gethostname: + raise unittest.SkipTest("test requires gethostname()") + + def hexescape(char): """Escape char as RFC 2396 specifies""" hex_repr = hex(ord(char))[2:].upper() @@ -135,7 +140,7 @@ def unfakeftp(self): urllib.request.ftpwrapper = self._ftpwrapper_class -class urlopen_FileTests(unittest.TestCase): +class urlopen_FileTests(unittest.TestCase, ExtraAssertions): """Test urlopen() opening a temporary file. Try to test as much functionality as possible so as to cut down on reliance @@ -153,7 +158,7 @@ def setUp(self): finally: f.close() self.pathname = os_helper.TESTFN - self.quoted_pathname = urllib.parse.quote(self.pathname) + self.quoted_pathname = urllib.parse.quote(os.fsencode(self.pathname)) self.returned_obj = urlopen("file:%s" % self.quoted_pathname) def tearDown(self): @@ -165,9 +170,7 @@ def test_interface(self): # Make sure object returned by urlopen() has the specified methods for attr in ("read", "readline", "readlines", "fileno", "close", "info", "geturl", "getcode", "__iter__"): - self.assertTrue(hasattr(self.returned_obj, attr), - "object returned by urlopen() lacks %s attribute" % - attr) + self.assertHasAttr(self.returned_obj, attr) def test_read(self): self.assertEqual(self.text, self.returned_obj.read()) @@ -232,17 +235,12 @@ class ProxyTests(unittest.TestCase): def setUp(self): # Records changes to env vars - self.env = os_helper.EnvironmentVarGuard() + self.env = self.enterContext(os_helper.EnvironmentVarGuard()) # Delete all proxy related env vars for k in list(os.environ): if 'proxy' in k.lower(): self.env.unset(k) - def tearDown(self): - # Restore all proxy related env vars - self.env.__exit__() - del self.env - def test_getproxies_environment_keep_no_proxies(self): self.env.set('NO_PROXY', 'localhost') proxies = urllib.request.getproxies_environment() @@ -477,7 +475,9 @@ def test_read_bogus(self): Content-Type: text/html; charset=iso-8859-1 ''', mock_close=True) try: - self.assertRaises(OSError, urlopen, "http://python.org/") + with self.assertRaises(urllib.error.HTTPError) as cm: + urllib.request.urlopen("http://python.org/") + cm.exception.close() finally: self.unfakehttp() @@ -492,8 +492,9 @@ def test_invalid_redirect(self): ''', mock_close=True) try: msg = "Redirection to url 'file:" - with self.assertRaisesRegex(urllib.error.HTTPError, msg): - urlopen("http://python.org/") + with self.assertRaisesRegex(urllib.error.HTTPError, msg) as cm: + urllib.request.urlopen("http://python.org/") + cm.exception.close() finally: self.unfakehttp() @@ -506,8 +507,9 @@ def test_redirect_limit_independent(self): Connection: close ''', mock_close=True) try: - self.assertRaises(urllib.error.HTTPError, urlopen, - "http://something") + with self.assertRaises(urllib.error.HTTPError) as cm: + urllib.request.urlopen("http://something") + cm.exception.close() finally: self.unfakehttp() @@ -597,17 +599,8 @@ def test_URLopener_deprecation(self): with warnings_helper.check_warnings(('',DeprecationWarning)): urllib.request.URLopener() - @unittest.skipUnless(ssl, "ssl module required") - def test_cafile_and_context(self): - context = ssl.create_default_context() - with warnings_helper.check_warnings(('', DeprecationWarning)): - with self.assertRaises(ValueError): - urllib.request.urlopen( - "https://localhost", cafile="/nonexistent/path", context=context - ) - -class urlopen_DataTests(unittest.TestCase): +class urlopen_DataTests(unittest.TestCase, ExtraAssertions): """Test urlopen() opening a data URL.""" def setUp(self): @@ -636,18 +629,17 @@ def setUp(self): "QOjdAAAAAXNSR0IArs4c6QAAAA9JREFUCNdj%0AYGBg%2BP//PwAGAQL%2BCm8 " "vHgAAAABJRU5ErkJggg%3D%3D%0A%20") - self.text_url_resp = urllib.request.urlopen(self.text_url) - self.text_url_base64_resp = urllib.request.urlopen( - self.text_url_base64) - self.image_url_resp = urllib.request.urlopen(self.image_url) + self.text_url_resp = self.enterContext( + urllib.request.urlopen(self.text_url)) + self.text_url_base64_resp = self.enterContext( + urllib.request.urlopen(self.text_url_base64)) + self.image_url_resp = self.enterContext(urllib.request.urlopen(self.image_url)) def test_interface(self): # Make sure object returned by urlopen() has the specified methods for attr in ("read", "readline", "readlines", "close", "info", "geturl", "getcode", "__iter__"): - self.assertTrue(hasattr(self.text_url_resp, attr), - "object returned by urlopen() lacks %s attribute" % - attr) + self.assertHasAttr(self.text_url_resp, attr) def test_info(self): self.assertIsInstance(self.text_url_resp.info(), email.message.Message) @@ -655,8 +647,10 @@ def test_info(self): [('text/plain', ''), ('charset', 'ISO-8859-1')]) self.assertEqual(self.image_url_resp.info()['content-length'], str(len(self.image))) - self.assertEqual(urllib.request.urlopen("data:,").info().get_params(), + r = urllib.request.urlopen("data:,") + self.assertEqual(r.info().get_params(), [('text/plain', ''), ('charset', 'US-ASCII')]) + r.close() def test_geturl(self): self.assertEqual(self.text_url_resp.geturl(), self.text_url) @@ -719,10 +713,6 @@ def tearDown(self): def constructLocalFileUrl(self, filePath): filePath = os.path.abspath(filePath) - try: - filePath.encode("utf-8") - except UnicodeEncodeError: - raise unittest.SkipTest("filePath is not encodable to utf8") return "file://%s" % urllib.request.pathname2url(filePath) def createNewTempFile(self, data=b""): @@ -1104,6 +1094,8 @@ def test_unquoting(self): self.assertEqual(result.count('%'), 1, "using unquote(): not all characters escaped: " "%s" % result) + + def test_unquote_rejects_none_and_tuple(self): self.assertRaises((TypeError, AttributeError), urllib.parse.unquote, None) self.assertRaises((TypeError, AttributeError), urllib.parse.unquote, ()) @@ -1527,39 +1519,121 @@ def test_quoting(self): (expect, result)) @unittest.skipUnless(sys.platform == 'win32', - 'test specific to the nturl2path functions.') - def test_prefixes(self): + 'test specific to Windows pathnames.') + def test_pathname2url_win(self): # Test special prefixes are correctly handled in pathname2url() - given = '\\\\?\\C:\\dir' - expect = '///C:/dir' - result = urllib.request.pathname2url(given) - self.assertEqual(expect, result, - "pathname2url() failed; %s != %s" % - (expect, result)) - given = '\\\\?\\unc\\server\\share\\dir' - expect = '/server/share/dir' - result = urllib.request.pathname2url(given) - self.assertEqual(expect, result, - "pathname2url() failed; %s != %s" % - (expect, result)) - + fn = urllib.request.pathname2url + self.assertEqual(fn('\\\\?\\C:\\dir'), '///C:/dir') + self.assertEqual(fn('\\\\?\\unc\\server\\share\\dir'), '//server/share/dir') + self.assertEqual(fn("C:"), '///C:') + self.assertEqual(fn("C:\\"), '///C:/') + self.assertEqual(fn('C:\\a\\b.c'), '///C:/a/b.c') + self.assertEqual(fn('C:\\a\\b.c\\'), '///C:/a/b.c/') + self.assertEqual(fn('C:\\a\\\\b.c'), '///C:/a//b.c') + self.assertEqual(fn('C:\\a\\b%#c'), '///C:/a/b%25%23c') + self.assertEqual(fn('C:\\a\\b\xe9'), '///C:/a/b%C3%A9') + self.assertEqual(fn('C:\\foo\\bar\\spam.foo'), "///C:/foo/bar/spam.foo") + # Long drive letter + self.assertRaises(IOError, fn, "XX:\\") + # No drive letter + self.assertEqual(fn("\\folder\\test\\"), '/folder/test/') + self.assertEqual(fn("\\\\folder\\test\\"), '//folder/test/') + self.assertEqual(fn("\\\\\\folder\\test\\"), '///folder/test/') + self.assertEqual(fn('\\\\some\\share\\'), '//some/share/') + self.assertEqual(fn('\\\\some\\share\\a\\b.c'), '//some/share/a/b.c') + self.assertEqual(fn('\\\\some\\share\\a\\b%#c\xe9'), '//some/share/a/b%25%23c%C3%A9') + # Alternate path separator + self.assertEqual(fn('C:/a/b.c'), '///C:/a/b.c') + self.assertEqual(fn('//some/share/a/b.c'), '//some/share/a/b.c') + self.assertEqual(fn('//?/C:/dir'), '///C:/dir') + self.assertEqual(fn('//?/unc/server/share/dir'), '//server/share/dir') + # Round-tripping + urls = ['///C:', + '/folder/test/', + '///C:/foo/bar/spam.foo'] + for url in urls: + self.assertEqual(fn(urllib.request.url2pathname(url)), url) + + @unittest.skipIf(sys.platform == 'win32', + 'test specific to POSIX pathnames') + @unittest.expectedFailure # AssertionError: '//a/b.c' != '////a/b.c' + def test_pathname2url_posix(self): + fn = urllib.request.pathname2url + self.assertEqual(fn('/'), '/') + self.assertEqual(fn('/a/b.c'), '/a/b.c') + self.assertEqual(fn('//a/b.c'), '////a/b.c') + self.assertEqual(fn('///a/b.c'), '/////a/b.c') + self.assertEqual(fn('////a/b.c'), '//////a/b.c') + self.assertEqual(fn('/a/b%#c'), '/a/b%25%23c') + + @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') + def test_pathname2url_nonascii(self): + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() + url = urllib.parse.quote(os_helper.FS_NONASCII, encoding=encoding, errors=errors) + self.assertEqual(urllib.request.pathname2url(os_helper.FS_NONASCII), url) @unittest.skipUnless(sys.platform == 'win32', - 'test specific to the urllib.url2path function.') - def test_ntpath(self): - given = ('/C:/', '///C:/', '/C|//') - expect = 'C:\\' - for url in given: - result = urllib.request.url2pathname(url) - self.assertEqual(expect, result, - 'urllib.request..url2pathname() failed; %s != %s' % - (expect, result)) - given = '///C|/path' - expect = 'C:\\path' - result = urllib.request.url2pathname(given) - self.assertEqual(expect, result, - 'urllib.request.url2pathname() failed; %s != %s' % - (expect, result)) + 'test specific to Windows pathnames.') + def test_url2pathname_win(self): + fn = urllib.request.url2pathname + self.assertEqual(fn('/C:/'), 'C:\\') + self.assertEqual(fn("///C|"), 'C:') + self.assertEqual(fn("///C:"), 'C:') + self.assertEqual(fn('///C:/'), 'C:\\') + self.assertEqual(fn('/C|//'), 'C:\\\\') + self.assertEqual(fn('///C|/path'), 'C:\\path') + # No DOS drive + self.assertEqual(fn("///C/test/"), '\\C\\test\\') + self.assertEqual(fn("////C/test/"), '\\\\C\\test\\') + # DOS drive paths + self.assertEqual(fn('C:/path/to/file'), 'C:\\path\\to\\file') + self.assertEqual(fn('C:/path/to/file/'), 'C:\\path\\to\\file\\') + self.assertEqual(fn('C:/path/to//file'), 'C:\\path\\to\\\\file') + self.assertEqual(fn('C|/path/to/file'), 'C:\\path\\to\\file') + self.assertEqual(fn('/C|/path/to/file'), 'C:\\path\\to\\file') + self.assertEqual(fn('///C|/path/to/file'), 'C:\\path\\to\\file') + self.assertEqual(fn("///C|/foo/bar/spam.foo"), 'C:\\foo\\bar\\spam.foo') + # Non-ASCII drive letter + self.assertRaises(IOError, fn, "///\u00e8|/") + # UNC paths + self.assertEqual(fn('//server/path/to/file'), '\\\\server\\path\\to\\file') + self.assertEqual(fn('////server/path/to/file'), '\\\\server\\path\\to\\file') + self.assertEqual(fn('/////server/path/to/file'), '\\\\server\\path\\to\\file') + # Localhost paths + self.assertEqual(fn('//localhost/C:/path/to/file'), 'C:\\path\\to\\file') + self.assertEqual(fn('//localhost/C|/path/to/file'), 'C:\\path\\to\\file') + self.assertEqual(fn('//localhost/path/to/file'), '\\path\\to\\file') + self.assertEqual(fn('//localhost//server/path/to/file'), '\\\\server\\path\\to\\file') + # Percent-encoded forward slashes are preserved for backwards compatibility + self.assertEqual(fn('C:/foo%2fbar'), 'C:\\foo/bar') + self.assertEqual(fn('//server/share/foo%2fbar'), '\\\\server\\share\\foo/bar') + # Round-tripping + paths = ['C:', + r'\C\test\\', + r'C:\foo\bar\spam.foo'] + for path in paths: + self.assertEqual(fn(urllib.request.pathname2url(path)), path) + + @unittest.skipIf(sys.platform == 'win32', + 'test specific to POSIX pathnames') + @unittest.expectedFailure # AssertionError: '///foo/bar' != '/foo/bar' + def test_url2pathname_posix(self): + fn = urllib.request.url2pathname + self.assertEqual(fn('/foo/bar'), '/foo/bar') + self.assertEqual(fn('//foo/bar'), '//foo/bar') + self.assertEqual(fn('///foo/bar'), '/foo/bar') + self.assertEqual(fn('////foo/bar'), '//foo/bar') + self.assertEqual(fn('//localhost/foo/bar'), '/foo/bar') + + @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') + def test_url2pathname_nonascii(self): + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() + url = os_helper.FS_NONASCII + self.assertEqual(urllib.request.url2pathname(url), os_helper.FS_NONASCII) + url = urllib.parse.quote(url, encoding=encoding, errors=errors) + self.assertEqual(urllib.request.url2pathname(url), os_helper.FS_NONASCII) class Utility_Tests(unittest.TestCase): """Testcase to test the various utility functions in the urllib.""" @@ -1643,60 +1717,5 @@ def test_with_method_arg(self): self.assertEqual(request.get_method(), 'HEAD') -class URL2PathNameTests(unittest.TestCase): - - def test_converting_drive_letter(self): - self.assertEqual(url2pathname("///C|"), 'C:') - self.assertEqual(url2pathname("///C:"), 'C:') - self.assertEqual(url2pathname("///C|/"), 'C:\\') - - def test_converting_when_no_drive_letter(self): - # cannot end a raw string in \ - self.assertEqual(url2pathname("///C/test/"), r'\\\C\test' '\\') - self.assertEqual(url2pathname("////C/test/"), r'\\C\test' '\\') - - def test_simple_compare(self): - self.assertEqual(url2pathname("///C|/foo/bar/spam.foo"), - r'C:\foo\bar\spam.foo') - - def test_non_ascii_drive_letter(self): - self.assertRaises(IOError, url2pathname, "///\u00e8|/") - - def test_roundtrip_url2pathname(self): - list_of_paths = ['C:', - r'\\\C\test\\', - r'C:\foo\bar\spam.foo' - ] - for path in list_of_paths: - self.assertEqual(url2pathname(pathname2url(path)), path) - -class PathName2URLTests(unittest.TestCase): - - def test_converting_drive_letter(self): - self.assertEqual(pathname2url("C:"), '///C:') - self.assertEqual(pathname2url("C:\\"), '///C:') - - def test_converting_when_no_drive_letter(self): - self.assertEqual(pathname2url(r"\\\folder\test" "\\"), - '/////folder/test/') - self.assertEqual(pathname2url(r"\\folder\test" "\\"), - '////folder/test/') - self.assertEqual(pathname2url(r"\folder\test" "\\"), - '/folder/test/') - - def test_simple_compare(self): - self.assertEqual(pathname2url(r'C:\foo\bar\spam.foo'), - "///C:/foo/bar/spam.foo" ) - - def test_long_drive_letter(self): - self.assertRaises(IOError, pathname2url, "XX:\\") - - def test_roundtrip_pathname2url(self): - list_of_paths = ['///C:', - '/////folder/test/', - '///C:/foo/bar/spam.foo'] - for path in list_of_paths: - self.assertEqual(pathname2url(url2pathname(path)), path) - if __name__ == '__main__': unittest.main() From e58cf84c711977e04489699413e5e4d45fe333af Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:44:53 +0900 Subject: [PATCH 657/819] Update dbm from 3.13.11, implement sqlite3 autocommit (#6616) --- Lib/dbm/__init__.py | 21 +- Lib/dbm/dumb.py | 11 +- Lib/dbm/gnu.py | 3 + Lib/dbm/ndbm.py | 3 + Lib/dbm/sqlite3.py | 144 ++++++++++++++ Lib/test/test_dbm.py | 180 ++++++++++------- Lib/test/test_dbm_dumb.py | 86 ++++++++- Lib/test/test_dbm_sqlite3.py | 362 +++++++++++++++++++++++++++++++++++ crates/stdlib/src/sqlite.rs | 136 +++++++++++-- 9 files changed, 851 insertions(+), 95 deletions(-) create mode 100644 Lib/dbm/gnu.py create mode 100644 Lib/dbm/ndbm.py create mode 100644 Lib/dbm/sqlite3.py create mode 100644 Lib/test/test_dbm_sqlite3.py diff --git a/Lib/dbm/__init__.py b/Lib/dbm/__init__.py index f65da521af4..4fdbc54e74c 100644 --- a/Lib/dbm/__init__.py +++ b/Lib/dbm/__init__.py @@ -5,7 +5,7 @@ import dbm d = dbm.open(file, 'w', 0o666) -The returned object is a dbm.gnu, dbm.ndbm or dbm.dumb object, dependent on the +The returned object is a dbm.sqlite3, dbm.gnu, dbm.ndbm or dbm.dumb database object, dependent on the type of database being opened (determined by the whichdb function) in the case of an existing dbm. If the dbm does not exist and the create or new flag ('c' or 'n') was specified, the dbm type will be determined by the availability of @@ -38,7 +38,7 @@ class error(Exception): pass -_names = ['dbm.gnu', 'dbm.ndbm', 'dbm.dumb'] +_names = ['dbm.sqlite3', 'dbm.gnu', 'dbm.ndbm', 'dbm.dumb'] _defaultmod = None _modules = {} @@ -109,17 +109,18 @@ def whichdb(filename): """ # Check for ndbm first -- this has a .pag and a .dir file + filename = os.fsencode(filename) try: - f = io.open(filename + ".pag", "rb") + f = io.open(filename + b".pag", "rb") f.close() - f = io.open(filename + ".dir", "rb") + f = io.open(filename + b".dir", "rb") f.close() return "dbm.ndbm" except OSError: # some dbm emulations based on Berkeley DB generate a .db file # some do not, but they should be caught by the bsd checks try: - f = io.open(filename + ".db", "rb") + f = io.open(filename + b".db", "rb") f.close() # guarantee we can actually open the file using dbm # kind of overkill, but since we are dealing with emulations @@ -134,12 +135,12 @@ def whichdb(filename): # Check for dumbdbm next -- this has a .dir and a .dat file try: # First check for presence of files - os.stat(filename + ".dat") - size = os.stat(filename + ".dir").st_size + os.stat(filename + b".dat") + size = os.stat(filename + b".dir").st_size # dumbdbm files with no keys are empty if size == 0: return "dbm.dumb" - f = io.open(filename + ".dir", "rb") + f = io.open(filename + b".dir", "rb") try: if f.read(1) in (b"'", b'"'): return "dbm.dumb" @@ -163,6 +164,10 @@ def whichdb(filename): if len(s) != 4: return "" + # Check for SQLite3 header string. + if s16 == b"SQLite format 3\0": + return "dbm.sqlite3" + # Convert to 4-byte int in native byte order -- return "" if impossible try: (magic,) = struct.unpack("=l", s) diff --git a/Lib/dbm/dumb.py b/Lib/dbm/dumb.py index 864ad371ec9..def120ffc37 100644 --- a/Lib/dbm/dumb.py +++ b/Lib/dbm/dumb.py @@ -46,6 +46,7 @@ class _Database(collections.abc.MutableMapping): _io = _io # for _commit() def __init__(self, filebasename, mode, flag='c'): + filebasename = self._os.fsencode(filebasename) self._mode = mode self._readonly = (flag == 'r') @@ -54,14 +55,14 @@ def __init__(self, filebasename, mode, flag='c'): # where key is the string key, pos is the offset into the dat # file of the associated value's first byte, and siz is the number # of bytes in the associated value. - self._dirfile = filebasename + '.dir' + self._dirfile = filebasename + b'.dir' # The data file is a binary file pointed into by the directory # file, and holds the values associated with keys. Each value # begins at a _BLOCKSIZE-aligned byte offset, and is a raw # binary 8-bit string value. - self._datfile = filebasename + '.dat' - self._bakfile = filebasename + '.bak' + self._datfile = filebasename + b'.dat' + self._bakfile = filebasename + b'.bak' # The index is an in-memory dict, mirroring the directory file. self._index = None # maps keys to (pos, siz) pairs @@ -97,7 +98,8 @@ def _update(self, flag): except OSError: if flag not in ('c', 'n'): raise - self._modified = True + with self._io.open(self._dirfile, 'w', encoding="Latin-1") as f: + self._chmod(self._dirfile) else: with f: for line in f: @@ -133,6 +135,7 @@ def _commit(self): # position; UTF-8, though, does care sometimes. entry = "%r, %r\n" % (key.decode('Latin-1'), pos_and_siz_pair) f.write(entry) + self._modified = False sync = _commit diff --git a/Lib/dbm/gnu.py b/Lib/dbm/gnu.py new file mode 100644 index 00000000000..b07a1defffd --- /dev/null +++ b/Lib/dbm/gnu.py @@ -0,0 +1,3 @@ +"""Provide the _gdbm module as a dbm submodule.""" + +from _gdbm import * diff --git a/Lib/dbm/ndbm.py b/Lib/dbm/ndbm.py new file mode 100644 index 00000000000..23056a29ef2 --- /dev/null +++ b/Lib/dbm/ndbm.py @@ -0,0 +1,3 @@ +"""Provide the _dbm module as a dbm submodule.""" + +from _dbm import * diff --git a/Lib/dbm/sqlite3.py b/Lib/dbm/sqlite3.py new file mode 100644 index 00000000000..d0eed54e0f8 --- /dev/null +++ b/Lib/dbm/sqlite3.py @@ -0,0 +1,144 @@ +import os +import sqlite3 +from pathlib import Path +from contextlib import suppress, closing +from collections.abc import MutableMapping + +BUILD_TABLE = """ + CREATE TABLE IF NOT EXISTS Dict ( + key BLOB UNIQUE NOT NULL, + value BLOB NOT NULL + ) +""" +GET_SIZE = "SELECT COUNT (key) FROM Dict" +LOOKUP_KEY = "SELECT value FROM Dict WHERE key = CAST(? AS BLOB)" +STORE_KV = "REPLACE INTO Dict (key, value) VALUES (CAST(? AS BLOB), CAST(? AS BLOB))" +DELETE_KEY = "DELETE FROM Dict WHERE key = CAST(? AS BLOB)" +ITER_KEYS = "SELECT key FROM Dict" + + +class error(OSError): + pass + + +_ERR_CLOSED = "DBM object has already been closed" +_ERR_REINIT = "DBM object does not support reinitialization" + + +def _normalize_uri(path): + path = Path(path) + uri = path.absolute().as_uri() + while "//" in uri: + uri = uri.replace("//", "/") + return uri + + +class _Database(MutableMapping): + + def __init__(self, path, /, *, flag, mode): + if hasattr(self, "_cx"): + raise error(_ERR_REINIT) + + path = os.fsdecode(path) + match flag: + case "r": + flag = "ro" + case "w": + flag = "rw" + case "c": + flag = "rwc" + Path(path).touch(mode=mode, exist_ok=True) + case "n": + flag = "rwc" + Path(path).unlink(missing_ok=True) + Path(path).touch(mode=mode) + case _: + raise ValueError("Flag must be one of 'r', 'w', 'c', or 'n', " + f"not {flag!r}") + + # We use the URI format when opening the database. + uri = _normalize_uri(path) + uri = f"{uri}?mode={flag}" + if flag == "ro": + # Add immutable=1 to allow read-only SQLite access even if wal/shm missing + uri += "&immutable=1" + + try: + self._cx = sqlite3.connect(uri, autocommit=True, uri=True) + except sqlite3.Error as exc: + raise error(str(exc)) + + if flag != "ro": + # This is an optimization only; it's ok if it fails. + with suppress(sqlite3.OperationalError): + self._cx.execute("PRAGMA journal_mode = wal") + + if flag == "rwc": + self._execute(BUILD_TABLE) + + def _execute(self, *args, **kwargs): + if not self._cx: + raise error(_ERR_CLOSED) + try: + return closing(self._cx.execute(*args, **kwargs)) + except sqlite3.Error as exc: + raise error(str(exc)) + + def __len__(self): + with self._execute(GET_SIZE) as cu: + row = cu.fetchone() + return row[0] + + def __getitem__(self, key): + with self._execute(LOOKUP_KEY, (key,)) as cu: + row = cu.fetchone() + if not row: + raise KeyError(key) + return row[0] + + def __setitem__(self, key, value): + self._execute(STORE_KV, (key, value)) + + def __delitem__(self, key): + with self._execute(DELETE_KEY, (key,)) as cu: + if not cu.rowcount: + raise KeyError(key) + + def __iter__(self): + try: + with self._execute(ITER_KEYS) as cu: + for row in cu: + yield row[0] + except sqlite3.Error as exc: + raise error(str(exc)) + + def close(self): + if self._cx: + self._cx.close() + self._cx = None + + def keys(self): + return list(super().keys()) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + +def open(filename, /, flag="r", mode=0o666): + """Open a dbm.sqlite3 database and return the dbm object. + + The 'filename' parameter is the name of the database file. + + The optional 'flag' parameter can be one of ...: + 'r' (default): open an existing database for read only access + 'w': open an existing database for read/write access + 'c': create a database if it does not exist; open for read/write access + 'n': always create a new, empty database; open for read/write access + + The optional 'mode' parameter is the Unix file access mode of the database; + only used when creating a new database. Default: 0o666. + """ + return _Database(filename, flag=flag, mode=mode) diff --git a/Lib/test/test_dbm.py b/Lib/test/test_dbm.py index e615d284cd3..6785aa273ac 100644 --- a/Lib/test/test_dbm.py +++ b/Lib/test/test_dbm.py @@ -1,23 +1,28 @@ """Test script for the dbm.open function based on testdumbdbm.py""" import unittest -import glob -import test.support -from test.support import os_helper, import_helper +import dbm +import os +from test.support import import_helper +from test.support import os_helper + + +try: + from dbm import sqlite3 as dbm_sqlite3 +except ImportError: + dbm_sqlite3 = None -# Skip tests if dbm module doesn't exist. -dbm = import_helper.import_module('dbm') try: from dbm import ndbm except ImportError: ndbm = None -_fname = os_helper.TESTFN +dirname = os_helper.TESTFN +_fname = os.path.join(dirname, os_helper.TESTFN) # -# Iterates over every database module supported by dbm currently available, -# setting dbm to use each in turn, and yielding that module +# Iterates over every database module supported by dbm currently available. # def dbm_iterator(): for name in dbm._names: @@ -31,11 +36,12 @@ def dbm_iterator(): # # Clean up all scratch databases we might have created during testing # -def delete_files(): - # we don't know the precise name the underlying database uses - # so we use glob to locate all names - for f in glob.glob(glob.escape(_fname) + "*"): - os_helper.unlink(f) +def cleaunup_test_dir(): + os_helper.rmtree(dirname) + +def setup_test_dir(): + cleaunup_test_dir() + os.mkdir(dirname) class AnyDBMTestCase: @@ -129,85 +135,127 @@ def test_anydbm_access(self): assert(f[key] == b"Python:") f.close() + def test_open_with_bytes(self): + dbm.open(os.fsencode(_fname), "c").close() + + def test_open_with_pathlib_path(self): + dbm.open(os_helper.FakePath(_fname), "c").close() + + def test_open_with_pathlib_path_bytes(self): + dbm.open(os_helper.FakePath(os.fsencode(_fname)), "c").close() + def read_helper(self, f): keys = self.keys_helper(f) for key in self._dict: self.assertEqual(self._dict[key], f[key.encode("ascii")]) - def tearDown(self): - delete_files() + def test_keys(self): + with dbm.open(_fname, 'c') as d: + self.assertEqual(d.keys(), []) + a = [(b'a', b'b'), (b'12345678910', b'019237410982340912840198242')] + for k, v in a: + d[k] = v + self.assertEqual(sorted(d.keys()), sorted(k for (k, v) in a)) + for k, v in a: + self.assertIn(k, d) + self.assertEqual(d[k], v) + self.assertNotIn(b'xxx', d) + self.assertRaises(KeyError, lambda: d[b'xxx']) + + def test_clear(self): + with dbm.open(_fname, 'c') as d: + self.assertEqual(d.keys(), []) + a = [(b'a', b'b'), (b'12345678910', b'019237410982340912840198242')] + for k, v in a: + d[k] = v + for k, _ in a: + self.assertIn(k, d) + self.assertEqual(len(d), len(a)) + + d.clear() + self.assertEqual(len(d), 0) + for k, _ in a: + self.assertNotIn(k, d) def setUp(self): + self.addCleanup(setattr, dbm, '_defaultmod', dbm._defaultmod) dbm._defaultmod = self.module - delete_files() + self.addCleanup(cleaunup_test_dir) + setup_test_dir() class WhichDBTestCase(unittest.TestCase): def test_whichdb(self): + self.addCleanup(setattr, dbm, '_defaultmod', dbm._defaultmod) + _bytes_fname = os.fsencode(_fname) + fnames = [_fname, os_helper.FakePath(_fname), + _bytes_fname, os_helper.FakePath(_bytes_fname)] for module in dbm_iterator(): # Check whether whichdb correctly guesses module name # for databases opened with "module" module. - # Try with empty files first name = module.__name__ - if name == 'dbm.dumb': - continue # whichdb can't support dbm.dumb - delete_files() - f = module.open(_fname, 'c') - f.close() - self.assertEqual(name, self.dbm.whichdb(_fname)) + setup_test_dir() + dbm._defaultmod = module + # Try with empty files first + with module.open(_fname, 'c'): pass + for path in fnames: + self.assertEqual(name, self.dbm.whichdb(path)) # Now add a key - f = module.open(_fname, 'w') - f[b"1"] = b"1" - # and test that we can find it - self.assertIn(b"1", f) - # and read it - self.assertEqual(f[b"1"], b"1") - f.close() - self.assertEqual(name, self.dbm.whichdb(_fname)) + with module.open(_fname, 'w') as f: + f[b"1"] = b"1" + # and test that we can find it + self.assertIn(b"1", f) + # and read it + self.assertEqual(f[b"1"], b"1") + for path in fnames: + self.assertEqual(name, self.dbm.whichdb(path)) @unittest.skipUnless(ndbm, reason='Test requires ndbm') def test_whichdb_ndbm(self): # Issue 17198: check that ndbm which is referenced in whichdb is defined - db_file = '{}_ndbm.db'.format(_fname) - with open(db_file, 'w'): - self.addCleanup(os_helper.unlink, db_file) - self.assertIsNone(self.dbm.whichdb(db_file[:-3])) + with open(_fname + '.db', 'wb') as f: + f.write(b'spam') + _bytes_fname = os.fsencode(_fname) + fnames = [_fname, os_helper.FakePath(_fname), + _bytes_fname, os_helper.FakePath(_bytes_fname)] + for path in fnames: + self.assertIsNone(self.dbm.whichdb(path)) + + @unittest.skipUnless(dbm_sqlite3, reason='Test requires dbm.sqlite3') + def test_whichdb_sqlite3(self): + # Databases created by dbm.sqlite3 are detected correctly. + with dbm_sqlite3.open(_fname, "c") as db: + db["key"] = "value" + self.assertEqual(self.dbm.whichdb(_fname), "dbm.sqlite3") + + @unittest.skipUnless(dbm_sqlite3, reason='Test requires dbm.sqlite3') + def test_whichdb_sqlite3_existing_db(self): + # Existing sqlite3 databases are detected correctly. + sqlite3 = import_helper.import_module("sqlite3") + try: + # Create an empty database. + with sqlite3.connect(_fname) as cx: + cx.execute("CREATE TABLE dummy(database)") + cx.commit() + finally: + cx.close() + self.assertEqual(self.dbm.whichdb(_fname), "dbm.sqlite3") - def tearDown(self): - delete_files() def setUp(self): - delete_files() - self.filename = os_helper.TESTFN - self.d = dbm.open(self.filename, 'c') - self.d.close() + self.addCleanup(cleaunup_test_dir) + setup_test_dir() self.dbm = import_helper.import_fresh_module('dbm') - def test_keys(self): - self.d = dbm.open(self.filename, 'c') - self.assertEqual(self.d.keys(), []) - a = [(b'a', b'b'), (b'12345678910', b'019237410982340912840198242')] - for k, v in a: - self.d[k] = v - self.assertEqual(sorted(self.d.keys()), sorted(k for (k, v) in a)) - for k, v in a: - self.assertIn(k, self.d) - self.assertEqual(self.d[k], v) - self.assertNotIn(b'xxx', self.d) - self.assertRaises(KeyError, lambda: self.d[b'xxx']) - self.d.close() - - -def load_tests(loader, tests, pattern): - classes = [] - for mod in dbm_iterator(): - classes.append(type("TestCase-" + mod.__name__, - (AnyDBMTestCase, unittest.TestCase), - {'module': mod})) - suites = [unittest.makeSuite(c) for c in classes] - - tests.addTests(suites) - return tests + +for mod in dbm_iterator(): + assert mod.__name__.startswith('dbm.') + suffix = mod.__name__[4:] + testname = f'TestCase_{suffix}' + globals()[testname] = type(testname, + (AnyDBMTestCase, unittest.TestCase), + {'module': mod}) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_dbm_dumb.py b/Lib/test/test_dbm_dumb.py index 0dc489362b2..672f9092207 100644 --- a/Lib/test/test_dbm_dumb.py +++ b/Lib/test/test_dbm_dumb.py @@ -15,6 +15,7 @@ _fname = os_helper.TESTFN + def _delete_files(): for ext in [".dir", ".dat", ".bak"]: try: @@ -41,6 +42,7 @@ def test_dumbdbm_creation(self): self.read_helper(f) @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @os_helper.skip_unless_working_chmod def test_dumbdbm_creation_mode(self): try: old_umask = os.umask(0o002) @@ -231,7 +233,7 @@ def test_create_new(self): self.assertEqual(f.keys(), []) def test_eval(self): - with open(_fname + '.dir', 'w') as stream: + with open(_fname + '.dir', 'w', encoding="utf-8") as stream: stream.write("str(print('Hacked!')), 0\n") with support.captured_stdout() as stdout: with self.assertRaises(ValueError): @@ -244,9 +246,27 @@ def test_missing_data(self): _delete_files() with self.assertRaises(FileNotFoundError): dumbdbm.open(_fname, value) + self.assertFalse(os.path.exists(_fname + '.dat')) self.assertFalse(os.path.exists(_fname + '.dir')) self.assertFalse(os.path.exists(_fname + '.bak')) + for value in ('c', 'n'): + _delete_files() + with dumbdbm.open(_fname, value) as f: + self.assertTrue(os.path.exists(_fname + '.dat')) + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.bak')) + + for value in ('c', 'n'): + _delete_files() + with dumbdbm.open(_fname, value) as f: + f['key'] = 'value' + self.assertTrue(os.path.exists(_fname + '.dat')) + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertTrue(os.path.exists(_fname + '.bak')) + def test_missing_index(self): with dumbdbm.open(_fname, 'n') as f: pass @@ -257,6 +277,60 @@ def test_missing_index(self): self.assertFalse(os.path.exists(_fname + '.dir')) self.assertFalse(os.path.exists(_fname + '.bak')) + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + f['key'] = 'value' + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertTrue(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + os.unlink(_fname + '.bak') + + def test_sync_empty_unmodified(self): + with dumbdbm.open(_fname, 'n') as f: + pass + os.unlink(_fname + '.dir') + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + f.sync() + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + f.sync() + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + + def test_sync_nonempty_unmodified(self): + with dumbdbm.open(_fname, 'n') as f: + pass + os.unlink(_fname + '.dir') + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + f['key'] = 'value' + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + f.sync() + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertTrue(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + os.unlink(_fname + '.bak') + f.sync() + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + def test_invalid_flag(self): for flag in ('x', 'rf', None): with self.assertRaisesRegex(ValueError, @@ -264,6 +338,7 @@ def test_invalid_flag(self): "'r', 'w', 'c', or 'n'"): dumbdbm.open(_fname, flag) + @os_helper.skip_unless_working_chmod def test_readonly_files(self): with os_helper.temp_dir() as dir: fname = os.path.join(dir, 'db') @@ -293,6 +368,15 @@ def test_nonascii_filename(self): self.assertTrue(b'key' in db) self.assertEqual(db[b'key'], b'value') + def test_open_with_pathlib_path(self): + dumbdbm.open(os_helper.FakePath(_fname), "c").close() + + def test_open_with_bytes_path(self): + dumbdbm.open(os.fsencode(_fname), "c").close() + + def test_open_with_pathlib_bytes_path(self): + dumbdbm.open(os_helper.FakePath(os.fsencode(_fname)), "c").close() + def tearDown(self): _delete_files() diff --git a/Lib/test/test_dbm_sqlite3.py b/Lib/test/test_dbm_sqlite3.py new file mode 100644 index 00000000000..39eac7a35ec --- /dev/null +++ b/Lib/test/test_dbm_sqlite3.py @@ -0,0 +1,362 @@ +import os +import stat +import sys +import test.support +import unittest +from contextlib import closing +from functools import partial +from pathlib import Path +from test.support import cpython_only, import_helper, os_helper + +dbm_sqlite3 = import_helper.import_module("dbm.sqlite3") +# N.B. The test will fail on some platforms without sqlite3 +# if the sqlite3 import is above the import of dbm.sqlite3. +# This is deliberate: if the import helper managed to import dbm.sqlite3, +# we must inevitably be able to import sqlite3. Else, we have a problem. +import sqlite3 +from dbm.sqlite3 import _normalize_uri + + +root_in_posix = False +if hasattr(os, 'geteuid'): + root_in_posix = (os.geteuid() == 0) + + +class _SQLiteDbmTests(unittest.TestCase): + + def setUp(self): + self.filename = os_helper.TESTFN + db = dbm_sqlite3.open(self.filename, "c") + db.close() + + def tearDown(self): + for suffix in "", "-wal", "-shm": + os_helper.unlink(self.filename + suffix) + + +class URI(unittest.TestCase): + + def test_uri_substitutions(self): + dataset = ( + ("/absolute/////b/c", "/absolute/b/c"), + ("PRE#MID##END", "PRE%23MID%23%23END"), + ("%#?%%#", "%25%23%3F%25%25%23"), + ) + for path, normalized in dataset: + with self.subTest(path=path, normalized=normalized): + self.assertTrue(_normalize_uri(path).endswith(normalized)) + + @unittest.skipUnless(sys.platform == "win32", "requires Windows") + def test_uri_windows(self): + dataset = ( + # Relative subdir. + (r"2018\January.xlsx", + "2018/January.xlsx"), + # Absolute with drive letter. + (r"C:\Projects\apilibrary\apilibrary.sln", + "/C:/Projects/apilibrary/apilibrary.sln"), + # Relative with drive letter. + (r"C:Projects\apilibrary\apilibrary.sln", + "/C:Projects/apilibrary/apilibrary.sln"), + ) + for path, normalized in dataset: + with self.subTest(path=path, normalized=normalized): + if not Path(path).is_absolute(): + self.skipTest(f"skipping relative path: {path!r}") + self.assertTrue(_normalize_uri(path).endswith(normalized)) + + +class ReadOnly(_SQLiteDbmTests): + + def setUp(self): + super().setUp() + with dbm_sqlite3.open(self.filename, "w") as db: + db[b"key1"] = "value1" + db[b"key2"] = "value2" + self.db = dbm_sqlite3.open(self.filename, "r") + + def tearDown(self): + self.db.close() + super().tearDown() + + def test_readonly_read(self): + self.assertEqual(self.db[b"key1"], b"value1") + self.assertEqual(self.db[b"key2"], b"value2") + + def test_readonly_write(self): + with self.assertRaises(dbm_sqlite3.error): + self.db[b"new"] = "value" + + def test_readonly_delete(self): + with self.assertRaises(dbm_sqlite3.error): + del self.db[b"key1"] + + def test_readonly_keys(self): + self.assertEqual(self.db.keys(), [b"key1", b"key2"]) + + def test_readonly_iter(self): + self.assertEqual([k for k in self.db], [b"key1", b"key2"]) + + +@unittest.skipIf(root_in_posix, "test is meanless with root privilege") +class ReadOnlyFilesystem(unittest.TestCase): + + def setUp(self): + self.test_dir = os_helper.TESTFN + self.addCleanup(os_helper.rmtree, self.test_dir) + os.mkdir(self.test_dir) + self.db_path = os.path.join(self.test_dir, "test.db") + + db = dbm_sqlite3.open(self.db_path, "c") + db[b"key"] = b"value" + db.close() + + def test_readonly_file_read(self): + os.chmod(self.db_path, stat.S_IREAD) + with dbm_sqlite3.open(self.db_path, "r") as db: + self.assertEqual(db[b"key"], b"value") + + def test_readonly_file_write(self): + os.chmod(self.db_path, stat.S_IREAD) + with dbm_sqlite3.open(self.db_path, "w") as db: + with self.assertRaises(dbm_sqlite3.error): + db[b"newkey"] = b"newvalue" + + def test_readonly_dir_read(self): + os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC) + with dbm_sqlite3.open(self.db_path, "r") as db: + self.assertEqual(db[b"key"], b"value") + + def test_readonly_dir_write(self): + os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC) + with dbm_sqlite3.open(self.db_path, "w") as db: + try: + db[b"newkey"] = b"newvalue" + modified = True # on Windows and macOS + except dbm_sqlite3.error: + modified = False + with dbm_sqlite3.open(self.db_path, "r") as db: + if modified: + self.assertEqual(db[b"newkey"], b"newvalue") + else: + self.assertNotIn(b"newkey", db) + + +class ReadWrite(_SQLiteDbmTests): + + def setUp(self): + super().setUp() + self.db = dbm_sqlite3.open(self.filename, "w") + + def tearDown(self): + self.db.close() + super().tearDown() + + def db_content(self): + with closing(sqlite3.connect(self.filename)) as cx: + keys = [r[0] for r in cx.execute("SELECT key FROM Dict")] + vals = [r[0] for r in cx.execute("SELECT value FROM Dict")] + return keys, vals + + def test_readwrite_unique_key(self): + self.db["key"] = "value" + self.db["key"] = "other" + keys, vals = self.db_content() + self.assertEqual(keys, [b"key"]) + self.assertEqual(vals, [b"other"]) + + def test_readwrite_delete(self): + self.db["key"] = "value" + self.db["new"] = "other" + + del self.db[b"new"] + keys, vals = self.db_content() + self.assertEqual(keys, [b"key"]) + self.assertEqual(vals, [b"value"]) + + del self.db[b"key"] + keys, vals = self.db_content() + self.assertEqual(keys, []) + self.assertEqual(vals, []) + + def test_readwrite_null_key(self): + with self.assertRaises(dbm_sqlite3.error): + self.db[None] = "value" + + def test_readwrite_null_value(self): + with self.assertRaises(dbm_sqlite3.error): + self.db[b"key"] = None + + +class Misuse(_SQLiteDbmTests): + + def setUp(self): + super().setUp() + self.db = dbm_sqlite3.open(self.filename, "w") + + def tearDown(self): + self.db.close() + super().tearDown() + + def test_misuse_double_create(self): + self.db["key"] = "value" + with dbm_sqlite3.open(self.filename, "c") as db: + self.assertEqual(db[b"key"], b"value") + + def test_misuse_double_close(self): + self.db.close() + + def test_misuse_invalid_flag(self): + regex = "must be.*'r'.*'w'.*'c'.*'n', not 'invalid'" + with self.assertRaisesRegex(ValueError, regex): + dbm_sqlite3.open(self.filename, flag="invalid") + + def test_misuse_double_delete(self): + self.db["key"] = "value" + del self.db[b"key"] + with self.assertRaises(KeyError): + del self.db[b"key"] + + def test_misuse_invalid_key(self): + with self.assertRaises(KeyError): + self.db[b"key"] + + def test_misuse_iter_close1(self): + self.db["1"] = 1 + it = iter(self.db) + self.db.close() + with self.assertRaises(dbm_sqlite3.error): + next(it) + + def test_misuse_iter_close2(self): + self.db["1"] = 1 + self.db["2"] = 2 + it = iter(self.db) + next(it) + self.db.close() + with self.assertRaises(dbm_sqlite3.error): + next(it) + + def test_misuse_use_after_close(self): + self.db.close() + with self.assertRaises(dbm_sqlite3.error): + self.db[b"read"] + with self.assertRaises(dbm_sqlite3.error): + self.db[b"write"] = "value" + with self.assertRaises(dbm_sqlite3.error): + del self.db[b"del"] + with self.assertRaises(dbm_sqlite3.error): + len(self.db) + with self.assertRaises(dbm_sqlite3.error): + self.db.keys() + + def test_misuse_reinit(self): + with self.assertRaises(dbm_sqlite3.error): + self.db.__init__("new.db", flag="n", mode=0o666) + + def test_misuse_empty_filename(self): + for flag in "r", "w", "c", "n": + with self.assertRaises(dbm_sqlite3.error): + db = dbm_sqlite3.open("", flag="c") + + +class DataTypes(_SQLiteDbmTests): + + dataset = ( + # (raw, coerced) + (42, b"42"), + (3.14, b"3.14"), + ("string", b"string"), + (b"bytes", b"bytes"), + ) + + def setUp(self): + super().setUp() + self.db = dbm_sqlite3.open(self.filename, "w") + + def tearDown(self): + self.db.close() + super().tearDown() + + def test_datatypes_values(self): + for raw, coerced in self.dataset: + with self.subTest(raw=raw, coerced=coerced): + self.db["key"] = raw + self.assertEqual(self.db[b"key"], coerced) + + def test_datatypes_keys(self): + for raw, coerced in self.dataset: + with self.subTest(raw=raw, coerced=coerced): + self.db[raw] = "value" + self.assertEqual(self.db[coerced], b"value") + # Raw keys are silently coerced to bytes. + self.assertEqual(self.db[raw], b"value") + del self.db[raw] + + def test_datatypes_replace_coerced(self): + self.db["10"] = "value" + self.db[b"10"] = "value" + self.db[10] = "value" + self.assertEqual(self.db.keys(), [b"10"]) + + +class CorruptDatabase(_SQLiteDbmTests): + """Verify that database exceptions are raised as dbm.sqlite3.error.""" + + def setUp(self): + super().setUp() + with closing(sqlite3.connect(self.filename)) as cx: + with cx: + cx.execute("DROP TABLE IF EXISTS Dict") + cx.execute("CREATE TABLE Dict (invalid_schema)") + + def check(self, flag, fn, should_succeed=False): + with closing(dbm_sqlite3.open(self.filename, flag)) as db: + with self.assertRaises(dbm_sqlite3.error): + fn(db) + + @staticmethod + def read(db): + return db["key"] + + @staticmethod + def write(db): + db["key"] = "value" + + @staticmethod + def iter(db): + next(iter(db)) + + @staticmethod + def keys(db): + db.keys() + + @staticmethod + def del_(db): + del db["key"] + + @staticmethod + def len_(db): + len(db) + + def test_corrupt_readwrite(self): + for flag in "r", "w", "c": + with self.subTest(flag=flag): + check = partial(self.check, flag=flag) + check(fn=self.read) + check(fn=self.write) + check(fn=self.iter) + check(fn=self.keys) + check(fn=self.del_) + check(fn=self.len_) + + def test_corrupt_force_new(self): + with closing(dbm_sqlite3.open(self.filename, "n")) as db: + db["foo"] = "write" + _ = db[b"foo"] + next(iter(db)) + del db[b"foo"] + + +if __name__ == "__main__": + unittest.main() diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index 54c889ecb6b..7e0392b1f30 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -29,21 +29,21 @@ mod _sqlite { sqlite3_bind_null, sqlite3_bind_parameter_count, sqlite3_bind_parameter_name, sqlite3_bind_text, sqlite3_blob, sqlite3_blob_bytes, sqlite3_blob_close, sqlite3_blob_open, sqlite3_blob_read, sqlite3_blob_write, sqlite3_busy_timeout, sqlite3_changes, - sqlite3_close, sqlite3_column_blob, sqlite3_column_bytes, sqlite3_column_count, - sqlite3_column_decltype, sqlite3_column_double, sqlite3_column_int64, sqlite3_column_name, - sqlite3_column_text, sqlite3_column_type, sqlite3_complete, sqlite3_context, - sqlite3_context_db_handle, sqlite3_create_collation_v2, sqlite3_create_function_v2, - sqlite3_create_window_function, sqlite3_data_count, sqlite3_db_handle, sqlite3_errcode, - sqlite3_errmsg, sqlite3_exec, sqlite3_expanded_sql, sqlite3_extended_errcode, - sqlite3_finalize, sqlite3_get_autocommit, sqlite3_interrupt, sqlite3_last_insert_rowid, - sqlite3_libversion, sqlite3_limit, sqlite3_open_v2, sqlite3_prepare_v2, - sqlite3_progress_handler, sqlite3_reset, sqlite3_result_blob, sqlite3_result_double, - sqlite3_result_error, sqlite3_result_error_nomem, sqlite3_result_error_toobig, - sqlite3_result_int64, sqlite3_result_null, sqlite3_result_text, sqlite3_set_authorizer, - sqlite3_sleep, sqlite3_step, sqlite3_stmt, sqlite3_stmt_busy, sqlite3_stmt_readonly, - sqlite3_threadsafe, sqlite3_total_changes, sqlite3_trace_v2, sqlite3_user_data, - sqlite3_value, sqlite3_value_blob, sqlite3_value_bytes, sqlite3_value_double, - sqlite3_value_int64, sqlite3_value_text, sqlite3_value_type, + sqlite3_column_blob, sqlite3_column_bytes, sqlite3_column_count, sqlite3_column_decltype, + sqlite3_column_double, sqlite3_column_int64, sqlite3_column_name, sqlite3_column_text, + sqlite3_column_type, sqlite3_complete, sqlite3_context, sqlite3_context_db_handle, + sqlite3_create_collation_v2, sqlite3_create_function_v2, sqlite3_create_window_function, + sqlite3_data_count, sqlite3_db_handle, sqlite3_errcode, sqlite3_errmsg, sqlite3_exec, + sqlite3_expanded_sql, sqlite3_extended_errcode, sqlite3_finalize, sqlite3_get_autocommit, + sqlite3_interrupt, sqlite3_last_insert_rowid, sqlite3_libversion, sqlite3_limit, + sqlite3_open_v2, sqlite3_prepare_v2, sqlite3_progress_handler, sqlite3_reset, + sqlite3_result_blob, sqlite3_result_double, sqlite3_result_error, + sqlite3_result_error_nomem, sqlite3_result_error_toobig, sqlite3_result_int64, + sqlite3_result_null, sqlite3_result_text, sqlite3_set_authorizer, sqlite3_sleep, + sqlite3_step, sqlite3_stmt, sqlite3_stmt_busy, sqlite3_stmt_readonly, sqlite3_threadsafe, + sqlite3_total_changes, sqlite3_trace_v2, sqlite3_user_data, sqlite3_value, + sqlite3_value_blob, sqlite3_value_bytes, sqlite3_value_double, sqlite3_value_int64, + sqlite3_value_text, sqlite3_value_type, }; use malachite_bigint::Sign; use rustpython_common::{ @@ -160,6 +160,8 @@ mod _sqlite { const PARSE_DECLTYPES: c_int = 1; #[pyattr] const PARSE_COLNAMES: c_int = 2; + #[pyattr] + const LEGACY_TRANSACTION_CONTROL: c_int = -1; #[pyattr] use libsqlite3_sys::{ @@ -300,6 +302,41 @@ mod _sqlite { SQLITE_IOERR_CORRUPTFS ); + /// Autocommit mode setting for sqlite3 connections. + /// - Legacy (default): use isolation_level to control transactions + /// - Enabled: autocommit mode (no automatic transactions) + /// - Disabled: manual commit mode + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] + enum AutocommitMode { + #[default] + Legacy, + Enabled, + Disabled, + } + + impl TryFromBorrowedObject<'_> for AutocommitMode { + fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> { + if obj.is(&vm.ctx.true_value) { + Ok(Self::Enabled) + } else if obj.is(&vm.ctx.false_value) { + Ok(Self::Disabled) + } else if let Ok(val) = obj.try_to_value::<c_int>(vm) { + if val == LEGACY_TRANSACTION_CONTROL { + Ok(Self::Legacy) + } else { + Err(vm.new_value_error(format!( + "autocommit must be True, False, or sqlite3.LEGACY_TRANSACTION_CONTROL, not {val}" + ))) + } + } else { + Err(vm.new_type_error(format!( + "autocommit must be True, False, or sqlite3.LEGACY_TRANSACTION_CONTROL, not {}", + obj.class().name() + ))) + } + } + } + #[derive(FromArgs)] struct ConnectArgs { #[pyarg(any)] @@ -320,6 +357,8 @@ mod _sqlite { cached_statements: c_int, #[pyarg(any, default = false)] uri: bool, + #[pyarg(any, default)] + autocommit: AutocommitMode, } unsafe impl Traverse for ConnectArgs { @@ -841,6 +880,7 @@ mod _sqlite { thread_ident: PyMutex<ThreadId>, // TODO: Use atomic row_factory: PyAtomicRef<Option<PyObject>>, text_factory: PyAtomicRef<PyObject>, + autocommit: PyMutex<AutocommitMode>, } impl Debug for Connection { @@ -878,6 +918,7 @@ mod _sqlite { thread_ident: PyMutex::new(std::thread::current().id()), row_factory: PyAtomicRef::from(None), text_factory: PyAtomicRef::from(text_factory), + autocommit: PyMutex::new(args.autocommit), }) } } @@ -919,12 +960,14 @@ mod _sqlite { detect_types, isolation_level, check_same_thread, + autocommit, .. } = args; zelf.detect_types.store(detect_types, Ordering::Relaxed); zelf.check_same_thread .store(check_same_thread, Ordering::Relaxed); + *zelf.autocommit.lock() = autocommit; *zelf.thread_ident.lock() = std::thread::current().id(); let _ = unsafe { zelf.isolation_level.swap(isolation_level) }; @@ -1465,6 +1508,43 @@ mod _sqlite { } } + #[pygetset] + fn autocommit(&self, vm: &VirtualMachine) -> PyObjectRef { + match *self.autocommit.lock() { + AutocommitMode::Enabled => vm.ctx.true_value.clone().into(), + AutocommitMode::Disabled => vm.ctx.false_value.clone().into(), + AutocommitMode::Legacy => vm.ctx.new_int(LEGACY_TRANSACTION_CONTROL).into(), + } + } + #[pygetset(setter)] + fn set_autocommit(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let mode = AutocommitMode::try_from_borrowed_object(vm, &val)?; + let db = self.db_lock(vm)?; + + // Handle transaction state based on mode change + match mode { + AutocommitMode::Enabled => { + // If there's a pending transaction, commit it + if !db.is_autocommit() { + db._exec(b"COMMIT�", vm)?; + } + } + AutocommitMode::Disabled => { + // If not in a transaction, begin one + if db.is_autocommit() { + db._exec(b"BEGIN�", vm)?; + } + } + AutocommitMode::Legacy => { + // Legacy mode doesn't change transaction state + } + } + + drop(db); + *self.autocommit.lock() = mode; + Ok(()) + } + #[pygetset] fn text_factory(&self) -> PyObjectRef { self.text_factory.to_owned() @@ -1622,9 +1702,11 @@ mod _sqlite { let db = zelf.connection.db_lock(vm)?; + // Start implicit transaction for DML statements unless in autocommit mode if stmt.is_dml && db.is_autocommit() && zelf.connection.isolation_level.deref().is_some() + && *zelf.connection.autocommit.lock() != AutocommitMode::Enabled { db.begin_transaction( zelf.connection @@ -1715,9 +1797,11 @@ mod _sqlite { let db = zelf.connection.db_lock(vm)?; + // Start implicit transaction for DML statements unless in autocommit mode if stmt.is_dml && db.is_autocommit() && zelf.connection.isolation_level.deref().is_some() + && *zelf.connection.autocommit.lock() != AutocommitMode::Enabled { db.begin_transaction( zelf.connection @@ -1954,6 +2038,18 @@ mod _sqlite { impl SelfIter for Cursor {} impl IterNext for Cursor { fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> { + // Check if connection is closed first, and if so, clear statement to release file lock + if zelf.connection.is_closed() { + let mut guard = zelf.inner.lock(); + if let Some(stmt) = guard.as_mut().and_then(|inner| inner.statement.take()) { + stmt.lock().reset(); + } + return Err(new_programming_error( + vm, + "Cannot operate on a closed database.".to_owned(), + )); + } + let mut inner = zelf.inner(vm)?; let Some(stmt) = &inner.statement else { return Ok(PyIterReturn::StopIteration(None)); @@ -2658,9 +2754,17 @@ mod _sqlite { } } + // sqlite3_close_v2 is not exported by libsqlite3-sys, so we declare it manually. + // It handles "zombie close" - if there are still unfinalized statements, + // the database will be closed when the last statement is finalized. + unsafe extern "C" { + fn sqlite3_close_v2(db: *mut sqlite3) -> c_int; + } + impl Drop for Sqlite { fn drop(&mut self) { - unsafe { sqlite3_close(self.raw.db) }; + // Use sqlite3_close_v2 for safe closing even with active statements + unsafe { sqlite3_close_v2(self.raw.db) }; } } From e10dc949928e66f108a4a4a232cd1304dccb8565 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:48:27 +0900 Subject: [PATCH 658/819] Fix stdio_encoding, stdio_errors (#6617) * stdio_errors, stdio_encodings * add ascii * unmark tests --- .cspell.dict/python-more.txt | 1 + Lib/test/test_tarfile.py | 4 -- crates/vm/Lib/core_modules/encodings_ascii.py | 1 + crates/vm/src/vm/mod.rs | 51 +++++++++++++------ crates/vm/src/vm/setting.rs | 8 ++- src/settings.rs | 17 +++++++ 6 files changed, 61 insertions(+), 21 deletions(-) create mode 120000 crates/vm/Lib/core_modules/encodings_ascii.py diff --git a/.cspell.dict/python-more.txt b/.cspell.dict/python-more.txt index 1f3fc4864cd..a13f345eece 100644 --- a/.cspell.dict/python-more.txt +++ b/.cspell.dict/python-more.txt @@ -178,6 +178,7 @@ PYTHONHASHSEED PYTHONHOME PYTHONINSPECT PYTHONINTMAXSTRDIGITS +PYTHONIOENCODING PYTHONNODEBUGRANGES PYTHONNOUSERSITE PYTHONOPTIMIZE diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 4cf233d5f8b..d29c6918234 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -2494,8 +2494,6 @@ def test_test_command_invalid_file(self): finally: os_helper.unlink(tmpname) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_list_command(self): for tar_name in testtarnames: with support.captured_stdout() as t: @@ -2507,8 +2505,6 @@ def test_list_command(self): PYTHONIOENCODING='ascii') self.assertEqual(out, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_list_command_verbose(self): for tar_name in testtarnames: with support.captured_stdout() as t: diff --git a/crates/vm/Lib/core_modules/encodings_ascii.py b/crates/vm/Lib/core_modules/encodings_ascii.py new file mode 120000 index 00000000000..c0507e75b03 --- /dev/null +++ b/crates/vm/Lib/core_modules/encodings_ascii.py @@ -0,0 +1 @@ +../../../../Lib/encodings/ascii.py \ No newline at end of file diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 2eef479d1b4..7f7c5ea7674 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -261,17 +261,28 @@ impl VirtualMachine { Ok(()) } - fn import_utf8_encodings(&mut self) -> PyResult<()> { + fn import_ascii_utf8_encodings(&mut self) -> PyResult<()> { import::import_frozen(self, "codecs")?; - // FIXME: See corresponding part of `core_frozen_inits` - // let encoding_module_name = if cfg!(feature = "freeze-stdlib") { - // "encodings.utf_8" - // } else { - // "encodings_utf_8" - // }; - let encoding_module_name = "encodings_utf_8"; - let encoding_module = import::import_frozen(self, encoding_module_name)?; - let getregentry = encoding_module.get_attr("getregentry", self)?; + + // Use dotted names when freeze-stdlib is enabled (modules come from Lib/encodings/), + // otherwise use underscored names (modules come from core_modules/). + let (ascii_module_name, utf8_module_name) = if cfg!(feature = "freeze-stdlib") { + ("encodings.ascii", "encodings.utf_8") + } else { + ("encodings_ascii", "encodings_utf_8") + }; + + // Register ascii encoding + let ascii_module = import::import_frozen(self, ascii_module_name)?; + let getregentry = ascii_module.get_attr("getregentry", self)?; + let codec_info = getregentry.call((), self)?; + self.state + .codec_registry + .register_manual("ascii", codec_info.try_into_value(self)?)?; + + // Register utf-8 encoding + let utf8_module = import::import_frozen(self, utf8_module_name)?; + let getregentry = utf8_module.get_attr("getregentry", self)?; let codec_info = getregentry.call((), self)?; self.state .codec_registry @@ -298,7 +309,7 @@ impl VirtualMachine { #[cfg(not(feature = "threading"))] import::import_frozen(self, "_thread")?; let importlib = import::init_importlib_base(self)?; - self.import_utf8_encodings()?; + self.import_ascii_utf8_encodings()?; #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] { @@ -327,17 +338,25 @@ impl VirtualMachine { let line_buffering = buffered_stdio && (isatty || fd == 2); let newline = if cfg!(windows) { None } else { Some("\n") }; - // stderr uses backslashreplace error handler - let errors: Option<&str> = if fd == 2 { + let encoding = self.state.config.settings.stdio_encoding.as_deref(); + // stderr always uses backslashreplace (ignores stdio_errors) + let errors = if fd == 2 { Some("backslashreplace") } else { - None + self.state.config.settings.stdio_errors.as_deref() }; let stdio = self.call_method( &io, "TextIOWrapper", - (buf, (), errors, newline, line_buffering, write_through), + ( + buf, + encoding, + errors, + newline, + line_buffering, + write_through, + ), )?; let mode = if write { "w" } else { "r" }; stdio.set_attr("mode", self.ctx.new_str(mode), self)?; @@ -1007,6 +1026,8 @@ pub fn resolve_frozen_alias(name: &str) -> &str { match name { "_frozen_importlib" => "importlib._bootstrap", "_frozen_importlib_external" => "importlib._bootstrap_external", + "encodings_ascii" => "encodings.ascii", + "encodings_utf_8" => "encodings.utf_8", _ => name, } } diff --git a/crates/vm/src/vm/setting.rs b/crates/vm/src/vm/setting.rs index 8a307d1852b..9be21b4484e 100644 --- a/crates/vm/src/vm/setting.rs +++ b/crates/vm/src/vm/setting.rs @@ -112,9 +112,11 @@ pub struct Settings { /// -u, PYTHONUNBUFFERED=x pub buffered_stdio: bool, - // wchar_t *stdio_encoding; + /// PYTHONIOENCODING - stdio encoding + pub stdio_encoding: Option<String>, + /// PYTHONIOENCODING - stdio error handler + pub stdio_errors: Option<String>, pub utf8_mode: u8, - // wchar_t *stdio_errors; /// --check-hash-based-pycs pub check_hash_pycs_mode: CheckHashPycsMode, @@ -197,6 +199,8 @@ impl Default for Settings { buffered_stdio: true, check_hash_pycs_mode: CheckHashPycsMode::Default, allow_external_library: cfg!(feature = "importlib"), + stdio_encoding: None, + stdio_errors: None, utf8_mode: 1, int_max_str_digits: 4300, #[cfg(feature = "flame-it")] diff --git a/src/settings.rs b/src/settings.rs index 54e66086932..7dd3c1a7714 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -298,6 +298,23 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> { settings.code_debug_ranges = false; } + // Parse PYTHONIOENCODING=encoding[:errors] + if let Some(val) = get_env("PYTHONIOENCODING") + && let Some(val_str) = val.to_str() + && !val_str.is_empty() + { + if let Some((enc, err)) = val_str.split_once(':') { + if !enc.is_empty() { + settings.stdio_encoding = Some(enc.to_owned()); + } + if !err.is_empty() { + settings.stdio_errors = Some(err.to_owned()); + } + } else { + settings.stdio_encoding = Some(val_str.to_owned()); + } + } + if settings.dev_mode { settings.warnoptions.push("default".to_owned()); settings.faulthandler = true; From 020ff3058c46d60e2fc158658173e1789fdb265f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 1 Jan 2026 15:00:30 +0900 Subject: [PATCH 659/819] update_sub_slot, __delitem__, no __bool__, missing rops (#6618) * fix update_sub_slot * Fix __setitem__ __delitem__ handling * contains * no __bool__ anymore * Add rops to wrapper * Remove pymethod from set * remove len --- crates/vm/src/builtins/bool.rs | 63 +++++------- crates/vm/src/builtins/bytearray.rs | 1 - crates/vm/src/builtins/bytes.rs | 1 - crates/vm/src/builtins/dict.rs | 2 - crates/vm/src/builtins/list.rs | 1 - crates/vm/src/builtins/mappingproxy.rs | 2 +- crates/vm/src/builtins/memory.rs | 1 - crates/vm/src/builtins/set.rs | 129 +++++++++++++++++-------- crates/vm/src/builtins/tuple.rs | 1 - crates/vm/src/protocol/number.rs | 14 +++ crates/vm/src/stdlib/collections.rs | 6 -- crates/vm/src/stdlib/ctypes/array.rs | 1 - crates/vm/src/stdlib/winreg.rs | 1 - crates/vm/src/types/slot.rs | 28 +++++- 14 files changed, 154 insertions(+), 97 deletions(-) diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 3dabdbae717..24ded08ab10 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -9,7 +9,6 @@ use crate::{ types::{AsNumber, Constructor, Representable}, }; use core::fmt::{Debug, Formatter}; -use malachite_bigint::Sign; use num_traits::Zero; impl ToPyObject for bool { @@ -42,46 +41,28 @@ impl PyObjectRef { if self.is(&vm.ctx.false_value) { return Ok(false); } - let rs_bool = if let Some(nb_bool) = self.class().slots.as_number.boolean.load() { - nb_bool(self.as_object().number(), vm)? - } else { - // TODO: Fully implement AsNumber and remove this block - match vm.get_method(self.clone(), identifier!(vm, __bool__)) { - Some(method_or_err) => { - // If descriptor returns Error, propagate it further - let method = method_or_err?; - let bool_obj = method.call((), vm)?; - if !bool_obj.fast_isinstance(vm.ctx.types.bool_type) { - return Err(vm.new_type_error(format!( - "__bool__ should return bool, returned type {}", - bool_obj.class().name() - ))); - } - - get_value(&bool_obj) - } - None => match vm.get_method(self, identifier!(vm, __len__)) { - Some(method_or_err) => { - let method = method_or_err?; - let bool_obj = method.call((), vm)?; - let int_obj = bool_obj.downcast_ref::<PyInt>().ok_or_else(|| { - vm.new_type_error(format!( - "'{}' object cannot be interpreted as an integer", - bool_obj.class().name() - )) - })?; - - let len_val = int_obj.as_bigint(); - if len_val.sign() == Sign::Minus { - return Err(vm.new_value_error("__len__() should return >= 0")); - } - !len_val.is_zero() - } - None => true, - }, - } - }; - Ok(rs_bool) + + let slots = &self.class().slots; + + // 1. Try nb_bool slot first + if let Some(nb_bool) = slots.as_number.boolean.load() { + return nb_bool(self.as_object().number(), vm); + } + + // 2. Try mp_length slot (mapping protocol) + if let Some(mp_length) = slots.as_mapping.length.load() { + let len = mp_length(self.as_object().mapping_unchecked(), vm)?; + return Ok(len != 0); + } + + // 3. Try sq_length slot (sequence protocol) + if let Some(sq_length) = slots.as_sequence.length.load() { + let len = sq_length(self.as_object().sequence_unchecked(), vm)?; + return Ok(len != 0); + } + + // 4. Default: objects without __bool__ or __len__ are truthy + Ok(true) } } diff --git a/crates/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs index 212e4604ec9..dc5ee100acf 100644 --- a/crates/vm/src/builtins/bytearray.rs +++ b/crates/vm/src/builtins/bytearray.rs @@ -206,7 +206,6 @@ impl PyByteArray { self.inner().capacity() } - #[pymethod] fn __len__(&self) -> usize { self.borrow_buf().len() } diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index b3feac8ac97..7e5b98b1ece 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -205,7 +205,6 @@ impl PyRef<PyBytes> { )] impl PyBytes { #[inline] - #[pymethod] pub const fn __len__(&self) -> usize { self.inner.len() } diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 358685fcdc4..693505b1f82 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -212,7 +212,6 @@ impl PyDict { } } - #[pymethod] pub fn __len__(&self) -> usize { self.entries.len() } @@ -764,7 +763,6 @@ trait DictView: PyPayload + PyClassDef + Iterable + Representable { fn dict(&self) -> &Py<PyDict>; fn item(vm: &VirtualMachine, key: PyObjectRef, value: PyObjectRef) -> PyObjectRef; - #[pymethod] fn __len__(&self) -> usize { self.dict().__len__() } diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index 46145b339cf..52df0756498 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -182,7 +182,6 @@ impl PyList { } #[allow(clippy::len_without_is_empty)] - #[pymethod] pub fn __len__(&self) -> usize { self.borrow_vec().len() } diff --git a/crates/vm/src/builtins/mappingproxy.rs b/crates/vm/src/builtins/mappingproxy.rs index 475f36cb5a9..34a598b03e0 100644 --- a/crates/vm/src/builtins/mappingproxy.rs +++ b/crates/vm/src/builtins/mappingproxy.rs @@ -173,7 +173,6 @@ impl PyMappingProxy { PyGenericAlias::from_args(cls, args, vm) } - #[pymethod] fn __len__(&self, vm: &VirtualMachine) -> PyResult<usize> { let obj = self.to_object(vm)?; obj.length(vm) @@ -235,6 +234,7 @@ impl AsMapping for PyMappingProxy { impl AsSequence for PyMappingProxy { fn as_sequence() -> &'static PySequenceMethods { static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods { + length: atomic_func!(|seq, vm| PyMappingProxy::sequence_downcast(seq).__len__(vm)), contains: atomic_func!( |seq, target, vm| PyMappingProxy::sequence_downcast(seq)._contains(target, vm) ), diff --git a/crates/vm/src/builtins/memory.rs b/crates/vm/src/builtins/memory.rs index ff5df031c42..5ca4257cded 100644 --- a/crates/vm/src/builtins/memory.rs +++ b/crates/vm/src/builtins/memory.rs @@ -690,7 +690,6 @@ impl PyMemoryView { Err(vm.new_type_error("cannot delete memory")) } - #[pymethod] fn __len__(&self, vm: &VirtualMachine) -> PyResult<usize> { self.try_not_released(vm)?; if self.desc.ndim() == 0 { diff --git a/crates/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs index b1236e44e93..1c0268e2ed7 100644 --- a/crates/vm/src/builtins/set.rs +++ b/crates/vm/src/builtins/set.rs @@ -533,11 +533,14 @@ fn reduce_set( flags(BASETYPE, _MATCH_SELF) )] impl PySet { - #[pymethod] fn __len__(&self) -> usize { self.inner.len() } + fn __contains__(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult<bool> { + self.inner.contains(needle, vm) + } + #[pymethod] fn __sizeof__(&self) -> usize { core::mem::size_of::<Self>() + self.inner.sizeof() @@ -550,11 +553,6 @@ impl PySet { } } - #[pymethod] - fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { - self.inner.contains(&needle, vm) - } - #[pymethod] fn union(&self, others: PosArgs<ArgIterable>, vm: &VirtualMachine) -> PyResult<Self> { self.fold_op(others.into_iter(), PySetInner::union, vm) @@ -594,8 +592,6 @@ impl PySet { self.inner.isdisjoint(other, vm) } - #[pymethod(name = "__ror__")] - #[pymethod] fn __or__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyArithmeticValue<Self>> { if let Ok(other) = AnySet::try_from_object(vm, other) { Ok(PyArithmeticValue::Implemented(self.op( @@ -608,8 +604,6 @@ impl PySet { } } - #[pymethod(name = "__rand__")] - #[pymethod] fn __and__( &self, other: PyObjectRef, @@ -626,7 +620,6 @@ impl PySet { } } - #[pymethod] fn __sub__( &self, other: PyObjectRef, @@ -643,7 +636,6 @@ impl PySet { } } - #[pymethod] fn __rsub__( zelf: PyRef<Self>, other: PyObjectRef, @@ -660,8 +652,6 @@ impl PySet { } } - #[pymethod(name = "__rxor__")] - #[pymethod] fn __xor__( &self, other: PyObjectRef, @@ -705,7 +695,6 @@ impl PySet { self.inner.pop(vm) } - #[pymethod] fn __ior__(zelf: PyRef<Self>, set: AnySet, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { zelf.inner.update(set.into_iterable_iter(vm)?, vm)?; Ok(zelf) @@ -729,7 +718,6 @@ impl PySet { Ok(()) } - #[pymethod] fn __iand__(zelf: PyRef<Self>, set: AnySet, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { zelf.inner .intersection_update(core::iter::once(set.into_iterable(vm)?), vm)?; @@ -742,7 +730,6 @@ impl PySet { Ok(()) } - #[pymethod] fn __isub__(zelf: PyRef<Self>, set: AnySet, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { zelf.inner .difference_update(set.into_iterable_iter(vm)?, vm)?; @@ -760,7 +747,6 @@ impl PySet { Ok(()) } - #[pymethod] fn __ixor__(zelf: PyRef<Self>, set: AnySet, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { zelf.inner .symmetric_difference_update(set.into_iterable_iter(vm)?, vm)?; @@ -799,9 +785,9 @@ impl AsSequence for PySet { fn as_sequence() -> &'static PySequenceMethods { static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods { length: atomic_func!(|seq, _vm| Ok(PySet::sequence_downcast(seq).__len__())), - contains: atomic_func!(|seq, needle, vm| PySet::sequence_downcast(seq) - .inner - .contains(needle, vm)), + contains: atomic_func!( + |seq, needle, vm| PySet::sequence_downcast(seq).__contains__(needle, vm) + ), ..PySequenceMethods::NOT_IMPLEMENTED }); &AS_SEQUENCE @@ -830,30 +816,77 @@ impl Iterable for PySet { impl AsNumber for PySet { fn as_number() -> &'static PyNumberMethods { static AS_NUMBER: PyNumberMethods = PyNumberMethods { + // Binary ops check both operands are sets (like CPython's set_sub, etc.) + // This is needed because __rsub__ swaps operands: a.__rsub__(b) calls subtract(b, a) subtract: Some(|a, b, vm| { + if !AnySet::check(a, vm) || !AnySet::check(b, vm) { + return Ok(vm.ctx.not_implemented()); + } if let Some(a) = a.downcast_ref::<PySet>() { a.__sub__(b.to_owned(), vm).to_pyresult(vm) + } else if let Some(a) = a.downcast_ref::<PyFrozenSet>() { + // When called via __rsub__, a might be PyFrozenSet + a.__sub__(b.to_owned(), vm) + .map(|r| { + r.map(|s| PySet { + inner: s.inner.clone(), + }) + }) + .to_pyresult(vm) } else { Ok(vm.ctx.not_implemented()) } }), and: Some(|a, b, vm| { + if !AnySet::check(a, vm) || !AnySet::check(b, vm) { + return Ok(vm.ctx.not_implemented()); + } if let Some(a) = a.downcast_ref::<PySet>() { a.__and__(b.to_owned(), vm).to_pyresult(vm) + } else if let Some(a) = a.downcast_ref::<PyFrozenSet>() { + a.__and__(b.to_owned(), vm) + .map(|r| { + r.map(|s| PySet { + inner: s.inner.clone(), + }) + }) + .to_pyresult(vm) } else { Ok(vm.ctx.not_implemented()) } }), xor: Some(|a, b, vm| { + if !AnySet::check(a, vm) || !AnySet::check(b, vm) { + return Ok(vm.ctx.not_implemented()); + } if let Some(a) = a.downcast_ref::<PySet>() { a.__xor__(b.to_owned(), vm).to_pyresult(vm) + } else if let Some(a) = a.downcast_ref::<PyFrozenSet>() { + a.__xor__(b.to_owned(), vm) + .map(|r| { + r.map(|s| PySet { + inner: s.inner.clone(), + }) + }) + .to_pyresult(vm) } else { Ok(vm.ctx.not_implemented()) } }), or: Some(|a, b, vm| { + if !AnySet::check(a, vm) || !AnySet::check(b, vm) { + return Ok(vm.ctx.not_implemented()); + } if let Some(a) = a.downcast_ref::<PySet>() { a.__or__(b.to_owned(), vm).to_pyresult(vm) + } else if let Some(a) = a.downcast_ref::<PyFrozenSet>() { + a.__or__(b.to_owned(), vm) + .map(|r| { + r.map(|s| PySet { + inner: s.inner.clone(), + }) + }) + .to_pyresult(vm) } else { Ok(vm.ctx.not_implemented()) } @@ -972,11 +1005,14 @@ impl Constructor for PyFrozenSet { ) )] impl PyFrozenSet { - #[pymethod] fn __len__(&self) -> usize { self.inner.len() } + fn __contains__(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult<bool> { + self.inner.contains(needle, vm) + } + #[pymethod] fn __sizeof__(&self) -> usize { core::mem::size_of::<Self>() + self.inner.sizeof() @@ -995,11 +1031,6 @@ impl PyFrozenSet { } } - #[pymethod] - fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { - self.inner.contains(&needle, vm) - } - #[pymethod] fn union(&self, others: PosArgs<ArgIterable>, vm: &VirtualMachine) -> PyResult<Self> { self.fold_op(others.into_iter(), PySetInner::union, vm) @@ -1039,8 +1070,6 @@ impl PyFrozenSet { self.inner.isdisjoint(other, vm) } - #[pymethod(name = "__ror__")] - #[pymethod] fn __or__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyArithmeticValue<Self>> { if let Ok(set) = AnySet::try_from_object(vm, other) { Ok(PyArithmeticValue::Implemented(self.op( @@ -1053,8 +1082,6 @@ impl PyFrozenSet { } } - #[pymethod(name = "__rand__")] - #[pymethod] fn __and__( &self, other: PyObjectRef, @@ -1071,7 +1098,6 @@ impl PyFrozenSet { } } - #[pymethod] fn __sub__( &self, other: PyObjectRef, @@ -1088,7 +1114,6 @@ impl PyFrozenSet { } } - #[pymethod] fn __rsub__( zelf: PyRef<Self>, other: PyObjectRef, @@ -1106,8 +1131,6 @@ impl PyFrozenSet { } } - #[pymethod(name = "__rxor__")] - #[pymethod] fn __xor__( &self, other: PyObjectRef, @@ -1142,9 +1165,9 @@ impl AsSequence for PyFrozenSet { fn as_sequence() -> &'static PySequenceMethods { static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods { length: atomic_func!(|seq, _vm| Ok(PyFrozenSet::sequence_downcast(seq).__len__())), - contains: atomic_func!(|seq, needle, vm| PyFrozenSet::sequence_downcast(seq) - .inner - .contains(needle, vm)), + contains: atomic_func!( + |seq, needle, vm| PyFrozenSet::sequence_downcast(seq).__contains__(needle, vm) + ), ..PySequenceMethods::NOT_IMPLEMENTED }); &AS_SEQUENCE @@ -1196,30 +1219,53 @@ impl Iterable for PyFrozenSet { impl AsNumber for PyFrozenSet { fn as_number() -> &'static PyNumberMethods { static AS_NUMBER: PyNumberMethods = PyNumberMethods { + // Binary ops check both operands are sets (like CPython's set_sub, etc.) + // __rsub__ swaps operands. Result type follows first operand's type. subtract: Some(|a, b, vm| { + if !AnySet::check(a, vm) || !AnySet::check(b, vm) { + return Ok(vm.ctx.not_implemented()); + } if let Some(a) = a.downcast_ref::<PyFrozenSet>() { a.__sub__(b.to_owned(), vm).to_pyresult(vm) + } else if let Some(a) = a.downcast_ref::<PySet>() { + // When called via __rsub__, a might be PySet - return set (not frozenset) + a.__sub__(b.to_owned(), vm).to_pyresult(vm) } else { Ok(vm.ctx.not_implemented()) } }), and: Some(|a, b, vm| { + if !AnySet::check(a, vm) || !AnySet::check(b, vm) { + return Ok(vm.ctx.not_implemented()); + } if let Some(a) = a.downcast_ref::<PyFrozenSet>() { a.__and__(b.to_owned(), vm).to_pyresult(vm) + } else if let Some(a) = a.downcast_ref::<PySet>() { + a.__and__(b.to_owned(), vm).to_pyresult(vm) } else { Ok(vm.ctx.not_implemented()) } }), xor: Some(|a, b, vm| { + if !AnySet::check(a, vm) || !AnySet::check(b, vm) { + return Ok(vm.ctx.not_implemented()); + } if let Some(a) = a.downcast_ref::<PyFrozenSet>() { a.__xor__(b.to_owned(), vm).to_pyresult(vm) + } else if let Some(a) = a.downcast_ref::<PySet>() { + a.__xor__(b.to_owned(), vm).to_pyresult(vm) } else { Ok(vm.ctx.not_implemented()) } }), or: Some(|a, b, vm| { + if !AnySet::check(a, vm) || !AnySet::check(b, vm) { + return Ok(vm.ctx.not_implemented()); + } if let Some(a) = a.downcast_ref::<PyFrozenSet>() { a.__or__(b.to_owned(), vm).to_pyresult(vm) + } else if let Some(a) = a.downcast_ref::<PySet>() { + a.__or__(b.to_owned(), vm).to_pyresult(vm) } else { Ok(vm.ctx.not_implemented()) } @@ -1252,6 +1298,13 @@ struct AnySet { } impl AnySet { + /// Check if object is a set or frozenset (including subclasses) + /// Equivalent to CPython's PyAnySet_Check + fn check(obj: &PyObject, vm: &VirtualMachine) -> bool { + let ctx = &vm.ctx; + obj.fast_isinstance(ctx.types.set_type) || obj.fast_isinstance(ctx.types.frozenset_type) + } + fn into_iterable(self, vm: &VirtualMachine) -> PyResult<ArgIterable> { self.object.try_into_value(vm) } diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index e0b8009e892..910b0c8a204 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -298,7 +298,6 @@ impl PyTuple { } #[inline] - #[pymethod] pub const fn __len__(&self) -> usize { self.elements.len() } diff --git a/crates/vm/src/protocol/number.rs b/crates/vm/src/protocol/number.rs index 58891d1d710..542afce2c6c 100644 --- a/crates/vm/src/protocol/number.rs +++ b/crates/vm/src/protocol/number.rs @@ -356,21 +356,27 @@ impl PyNumberSlots { pub fn copy_from(&self, methods: &PyNumberMethods) { if let Some(f) = methods.add { self.add.store(Some(f)); + self.right_add.store(Some(f)); } if let Some(f) = methods.subtract { self.subtract.store(Some(f)); + self.right_subtract.store(Some(f)); } if let Some(f) = methods.multiply { self.multiply.store(Some(f)); + self.right_multiply.store(Some(f)); } if let Some(f) = methods.remainder { self.remainder.store(Some(f)); + self.right_remainder.store(Some(f)); } if let Some(f) = methods.divmod { self.divmod.store(Some(f)); + self.right_divmod.store(Some(f)); } if let Some(f) = methods.power { self.power.store(Some(f)); + self.right_power.store(Some(f)); } if let Some(f) = methods.negative { self.negative.store(Some(f)); @@ -389,18 +395,23 @@ impl PyNumberSlots { } if let Some(f) = methods.lshift { self.lshift.store(Some(f)); + self.right_lshift.store(Some(f)); } if let Some(f) = methods.rshift { self.rshift.store(Some(f)); + self.right_rshift.store(Some(f)); } if let Some(f) = methods.and { self.and.store(Some(f)); + self.right_and.store(Some(f)); } if let Some(f) = methods.xor { self.xor.store(Some(f)); + self.right_xor.store(Some(f)); } if let Some(f) = methods.or { self.or.store(Some(f)); + self.right_or.store(Some(f)); } if let Some(f) = methods.int { self.int.store(Some(f)); @@ -440,9 +451,11 @@ impl PyNumberSlots { } if let Some(f) = methods.floor_divide { self.floor_divide.store(Some(f)); + self.right_floor_divide.store(Some(f)); } if let Some(f) = methods.true_divide { self.true_divide.store(Some(f)); + self.right_true_divide.store(Some(f)); } if let Some(f) = methods.inplace_floor_divide { self.inplace_floor_divide.store(Some(f)); @@ -455,6 +468,7 @@ impl PyNumberSlots { } if let Some(f) = methods.matrix_multiply { self.matrix_multiply.store(Some(f)); + self.right_matrix_multiply.store(Some(f)); } if let Some(f) = methods.inplace_matrix_multiply { self.inplace_matrix_multiply.store(Some(f)); diff --git a/crates/vm/src/stdlib/collections.rs b/crates/vm/src/stdlib/collections.rs index 67e8e1f2734..9b7a78f7237 100644 --- a/crates/vm/src/stdlib/collections.rs +++ b/crates/vm/src/stdlib/collections.rs @@ -350,16 +350,10 @@ mod _collections { Ok(zelf) } - #[pymethod] fn __len__(&self) -> usize { self.borrow_deque().len() } - #[pymethod] - fn __add__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> { - self.concat(&other, vm) - } - fn concat(&self, other: &PyObject, vm: &VirtualMachine) -> PyResult<Self> { if let Some(o) = other.downcast_ref::<Self>() { let mut deque = self.borrow_deque().clone(); diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 5c298d26a56..63594879878 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -1087,7 +1087,6 @@ impl PyCArray { Ok(()) } - #[pymethod] fn __len__(zelf: &Py<Self>, _vm: &VirtualMachine) -> usize { zelf.class().stg_info_opt().map_or(0, |i| i.length) } diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index 400cab44210..28fa2e9b74c 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -222,7 +222,6 @@ mod winreg { zelf.Close(vm) } - #[pymethod] fn __int__(&self) -> usize { self.hkey.load() as usize } diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index d7ead562063..17b2b4e2f8a 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -607,7 +607,9 @@ impl PyType { debug_assert!(name.as_str().ends_with("__")); // Find all slot_defs matching this name and update each - for def in find_slot_defs_by_name(name.as_str()) { + // NOTE: Collect into Vec first to avoid issues during iteration + let defs: Vec<_> = find_slot_defs_by_name(name.as_str()).collect(); + for def in defs { self.update_one_slot::<ADD>(&def.accessor, name, ctx); } @@ -676,7 +678,29 @@ impl PyType { macro_rules! update_sub_slot { ($group:ident, $slot:ident, $wrapper:expr, $variant:ident) => {{ if ADD { - if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| { + // Check if this type defines any method that maps to this slot. + // Some slots like SqAssItem/MpAssSubscript are shared by multiple + // methods (__setitem__ and __delitem__). If any of those methods + // is defined, we must use the wrapper to ensure Python method calls. + let has_own = { + let guard = self.attributes.read(); + // Check the current method name + let mut result = guard.contains_key(name); + // For ass_item/ass_subscript slots, also check the paired method + // (__setitem__ and __delitem__ share the same slot) + if !result + && (stringify!($slot) == "ass_item" + || stringify!($slot) == "ass_subscript") + { + let setitem = ctx.intern_str("__setitem__"); + let delitem = ctx.intern_str("__delitem__"); + result = guard.contains_key(setitem) || guard.contains_key(delitem); + } + result + }; + if has_own { + self.slots.$group.$slot.store(Some($wrapper)); + } else if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| { if let SlotFunc::$variant(f) = sf { Some(*f) } else { From a445a228683412f6be0cc808c44bf462e71611e3 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:55:39 +0900 Subject: [PATCH 660/819] fix compiler (#6615) --- Lib/test/test_inspect/test_inspect.py | 2 -- Lib/test/test_unittest/test_case.py | 2 -- crates/vm/src/frame.rs | 37 ++++++++++++++++++++++++++- crates/vm/src/vm/compile.rs | 13 ++++++---- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 8f87fa8571a..b9cfca6df21 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -957,8 +957,6 @@ def test_getsource_stdlib_decimal(self): class TestGetsourceInteractive(unittest.TestCase): @support.force_not_colorized - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_getclasses_interactive(self): # bpo-44648: simulate a REPL session; # there is no `__file__` in the __main__ module diff --git a/Lib/test/test_unittest/test_case.py b/Lib/test/test_unittest/test_case.py index ae6a2b94dba..82a442a04e6 100644 --- a/Lib/test/test_unittest/test_case.py +++ b/Lib/test/test_unittest/test_case.py @@ -1970,8 +1970,6 @@ def testNoCycles(self): del case self.assertFalse(wr()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_no_exception_leak(self): # Issue #19880: TestCase.run() should not keep a reference # to the exception diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 6500f578dca..1f74d07f87c 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1739,7 +1739,42 @@ impl ExecutingFrame<'_> { // We do not have any more blocks to unwind. Inspect the reason we are here: match reason { UnwindReason::Raising { exception } => Err(exception), - UnwindReason::Returning { value } => Ok(Some(ExecutionResult::Return(value))), + UnwindReason::Returning { value } => { + // Clear tracebacks of exceptions in fastlocals to break reference cycles. + // This is needed because when returning from inside an except block, + // the exception cleanup code (e = None; del e) is skipped, leaving the + // exception with a traceback that references this frame, which references + // the exception in fastlocals, creating a cycle that can't be collected + // since RustPython doesn't have a tracing GC. + // + // We only clear tracebacks of exceptions that: + // 1. Are not the return value itself (will be needed by caller) + // 2. Are not the current active exception (still being handled) + // 3. Have a traceback whose top frame is THIS frame (we created it) + let current_exc = vm.current_exception(); + let fastlocals = self.fastlocals.lock(); + for obj in fastlocals.iter().flatten() { + // Skip if this object is the return value + if obj.is(&value) { + continue; + } + if let Ok(exc) = obj.clone().downcast::<PyBaseException>() { + // Skip if this is the current active exception + if current_exc.as_ref().is_some_and(|cur| exc.is(cur)) { + continue; + } + // Only clear if traceback's top frame is this frame + if exc + .__traceback__() + .is_some_and(|tb| core::ptr::eq::<Py<Frame>>(&*tb.frame, self.object)) + { + exc.set_traceback_typed(None); + } + } + } + drop(fastlocals); + Ok(Some(ExecutionResult::Return(value))) + } UnwindReason::Break { .. } | UnwindReason::Continue { .. } => { self.fatal("break or continue must occur within a loop block.") } // UnwindReason::NoWorries => Ok(None), diff --git a/crates/vm/src/vm/compile.rs b/crates/vm/src/vm/compile.rs index b5f10e47aa1..07ab4b833d2 100644 --- a/crates/vm/src/vm/compile.rs +++ b/crates/vm/src/vm/compile.rs @@ -115,11 +115,14 @@ impl VirtualMachine { .compile(source, compiler::Mode::Exec, source_path.clone()) .map_err(|err| self.new_syntax_error(&err, Some(source)))?; // trace!("Code object: {:?}", code_obj.borrow()); - scope.globals.set_item( - identifier!(self, __file__), - self.new_pyobj(source_path), - self, - )?; + // Only set __file__ for real file paths, not pseudo-paths like <string> + if !(source_path.starts_with('<') && source_path.ends_with('>')) { + scope.globals.set_item( + identifier!(self, __file__), + self.new_pyobj(source_path), + self, + )?; + } self.run_code_obj(code_obj, scope) } From b446dac359e9e708eee692c9b5d70c00ff76e470 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 1 Jan 2026 10:15:00 +0200 Subject: [PATCH 661/819] Update `shelve.py` from 3.13.11 (#6483) * Update `test_shelve.py` from 3.13.11 * Update `shelve.py` from 3.13.11 --- Lib/shelve.py | 11 +++- Lib/test/test_shelve.py | 142 +++++++++++++++++++--------------------- 2 files changed, 76 insertions(+), 77 deletions(-) diff --git a/Lib/shelve.py b/Lib/shelve.py index 5d443a0fa8d..50584716e9e 100644 --- a/Lib/shelve.py +++ b/Lib/shelve.py @@ -56,7 +56,7 @@ the persistent dictionary on disk, if feasible). """ -from pickle import Pickler, Unpickler +from pickle import DEFAULT_PROTOCOL, Pickler, Unpickler from io import BytesIO import collections.abc @@ -85,7 +85,7 @@ def __init__(self, dict, protocol=None, writeback=False, keyencoding="utf-8"): self.dict = dict if protocol is None: - protocol = 3 + protocol = DEFAULT_PROTOCOL self._protocol = protocol self.writeback = writeback self.cache = {} @@ -226,6 +226,13 @@ def __init__(self, filename, flag='c', protocol=None, writeback=False): import dbm Shelf.__init__(self, dbm.open(filename, flag), protocol, writeback) + def clear(self): + """Remove all items from the shelf.""" + # Call through to the clear method on dbm-backed shelves. + # see https://github.com/python/cpython/issues/107089 + self.cache.clear() + self.dict.clear() + def open(filename, flag='c', protocol=None, writeback=False): """Open a persistent dictionary for reading and writing. diff --git a/Lib/test/test_shelve.py b/Lib/test/test_shelve.py index ac25eee2e52..08c6562f2a2 100644 --- a/Lib/test/test_shelve.py +++ b/Lib/test/test_shelve.py @@ -1,7 +1,9 @@ import unittest +import dbm import shelve -import glob -from test import support +import pickle +import os + from test.support import os_helper from collections.abc import MutableMapping from test.test_dbm import dbm_iterator @@ -41,12 +43,8 @@ def copy(self): class TestCase(unittest.TestCase): - - fn = "shelftemp.db" - - def tearDown(self): - for f in glob.glob(self.fn+"*"): - os_helper.unlink(f) + dirname = os_helper.TESTFN + fn = os.path.join(os_helper.TESTFN, "shelftemp.db") def test_close(self): d1 = {} @@ -63,29 +61,34 @@ def test_close(self): else: self.fail('Closed shelf should not find a key') - def test_ascii_file_shelf(self): - s = shelve.open(self.fn, protocol=0) + def test_open_template(self, filename=None, protocol=None): + os.mkdir(self.dirname) + self.addCleanup(os_helper.rmtree, self.dirname) + s = shelve.open(filename=filename if filename is not None else self.fn, + protocol=protocol) try: s['key1'] = (1,2,3,4) self.assertEqual(s['key1'], (1,2,3,4)) finally: s.close() + def test_ascii_file_shelf(self): + self.test_open_template(protocol=0) + def test_binary_file_shelf(self): - s = shelve.open(self.fn, protocol=1) - try: - s['key1'] = (1,2,3,4) - self.assertEqual(s['key1'], (1,2,3,4)) - finally: - s.close() + self.test_open_template(protocol=1) def test_proto2_file_shelf(self): - s = shelve.open(self.fn, protocol=2) - try: - s['key1'] = (1,2,3,4) - self.assertEqual(s['key1'], (1,2,3,4)) - finally: - s.close() + self.test_open_template(protocol=2) + + def test_pathlib_path_file_shelf(self): + self.test_open_template(filename=os_helper.FakePath(self.fn)) + + def test_bytes_path_file_shelf(self): + self.test_open_template(filename=os.fsencode(self.fn)) + + def test_pathlib_bytes_path_file_shelf(self): + self.test_open_template(filename=os_helper.FakePath(os.fsencode(self.fn))) def test_in_memory_shelf(self): d1 = byteskeydict() @@ -160,65 +163,54 @@ def test_with(self): def test_default_protocol(self): with shelve.Shelf({}) as s: - self.assertEqual(s._protocol, 3) + self.assertEqual(s._protocol, pickle.DEFAULT_PROTOCOL) -from test import mapping_tests -class TestShelveBase(mapping_tests.BasicTestMappingProtocol): - fn = "shelftemp.db" - counter = 0 - def __init__(self, *args, **kw): - self._db = [] - mapping_tests.BasicTestMappingProtocol.__init__(self, *args, **kw) +class TestShelveBase: type2test = shelve.Shelf + def _reference(self): return {"key1":"value1", "key2":2, "key3":(1,2,3)} + + +class TestShelveInMemBase(TestShelveBase): def _empty_mapping(self): - if self._in_mem: - x= shelve.Shelf(byteskeydict(), **self._args) - else: - self.counter+=1 - x= shelve.open(self.fn+str(self.counter), **self._args) - self._db.append(x) + return shelve.Shelf(byteskeydict(), **self._args) + + +class TestShelveFileBase(TestShelveBase): + counter = 0 + + def _empty_mapping(self): + self.counter += 1 + x = shelve.open(self.base_path + str(self.counter), **self._args) + self.addCleanup(x.close) return x - def tearDown(self): - for db in self._db: - db.close() - self._db = [] - if not self._in_mem: - for f in glob.glob(self.fn+"*"): - os_helper.unlink(f) - -class TestAsciiFileShelve(TestShelveBase): - _args={'protocol':0} - _in_mem = False -class TestBinaryFileShelve(TestShelveBase): - _args={'protocol':1} - _in_mem = False -class TestProto2FileShelve(TestShelveBase): - _args={'protocol':2} - _in_mem = False -class TestAsciiMemShelve(TestShelveBase): - _args={'protocol':0} - _in_mem = True -class TestBinaryMemShelve(TestShelveBase): - _args={'protocol':1} - _in_mem = True -class TestProto2MemShelve(TestShelveBase): - _args={'protocol':2} - _in_mem = True - -def test_main(): - for module in dbm_iterator(): - support.run_unittest( - TestAsciiFileShelve, - TestBinaryFileShelve, - TestProto2FileShelve, - TestAsciiMemShelve, - TestBinaryMemShelve, - TestProto2MemShelve, - TestCase - ) + + def setUp(self): + dirname = os_helper.TESTFN + os.mkdir(dirname) + self.addCleanup(os_helper.rmtree, dirname) + self.base_path = os.path.join(dirname, "shelftemp.db") + self.addCleanup(setattr, dbm, '_defaultmod', dbm._defaultmod) + dbm._defaultmod = self.dbm_mod + + +from test import mapping_tests + +for proto in range(pickle.HIGHEST_PROTOCOL + 1): + bases = (TestShelveInMemBase, mapping_tests.BasicTestMappingProtocol) + name = f'TestProto{proto}MemShelve' + globals()[name] = type(name, bases, + {'_args': {'protocol': proto}}) + bases = (TestShelveFileBase, mapping_tests.BasicTestMappingProtocol) + for dbm_mod in dbm_iterator(): + assert dbm_mod.__name__.startswith('dbm.') + suffix = dbm_mod.__name__[4:] + name = f'TestProto{proto}File_{suffix}Shelve' + globals()[name] = type(name, bases, + {'dbm_mod': dbm_mod, '_args': {'protocol': proto}}) + if __name__ == "__main__": - test_main() + unittest.main() From 3e2ada058651d9a1648bdb1453999758a32c4d13 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 1 Jan 2026 11:35:55 +0200 Subject: [PATCH 662/819] Update `test_sys.py` from 3.13.11 (#6428) * mark RustPython as free-threaded * Py_GIL_DISABLED * Update `test_sys.py` from 3.13.11 * mark tests --------- Co-authored-by: Jeong YunWon <jeong@youknowone.org> --- Lib/test/test_sys.py | 716 ++++++++++++++++++-------- crates/vm/src/stdlib/sys.rs | 7 +- crates/vm/src/stdlib/sysconfigdata.rs | 2 + 3 files changed, 498 insertions(+), 227 deletions(-) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 6ae3eaee74b..969c2866aac 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1,9 +1,12 @@ import builtins import codecs +# import _datetime # TODO: RUSTPYTHON import gc +import io import locale import operator import os +import random import struct import subprocess import sys @@ -14,14 +17,21 @@ from test.support.script_helper import assert_python_ok, assert_python_failure from test.support import threading_helper from test.support import import_helper +from test.support import force_not_colorized +try: + from test.support import interpreters +except ImportError: + interpreters = None import textwrap import unittest import warnings -# count the number of test runs, used to create unique -# strings to intern in test_intern() -INTERN_NUMRUNS = 0 +def requires_subinterpreters(meth): + """Decorator to skip a test if subinterpreters are not supported.""" + return unittest.skipIf(interpreters is None, + 'subinterpreters required')(meth) + DICT_KEY_STRUCT_FORMAT = 'n2BI2n' @@ -71,6 +81,18 @@ def baddisplayhook(obj): code = compile("42", "<string>", "single") self.assertRaises(ValueError, eval, code) + def test_gh130163(self): + class X: + def __repr__(self): + sys.stdout = io.StringIO() + support.gc_collect() + return 'foo' + + with support.swap_attr(sys, 'stdout', None): + sys.stdout = io.StringIO() # the only reference + sys.displayhook(X()) # should not crash + + class ActiveExceptionTests(unittest.TestCase): def test_exc_info_no_exception(self): self.assertEqual(sys.exc_info(), (None, None, None)) @@ -137,6 +159,7 @@ def f(): class ExceptHookTest(unittest.TestCase): + @force_not_colorized def test_original_excepthook(self): try: raise ValueError(42) @@ -148,8 +171,8 @@ def test_original_excepthook(self): self.assertRaises(TypeError, sys.__excepthook__) - # TODO: RUSTPYTHON, SyntaxError formatting in arbitrary tracebacks - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError formatting in arbitrary tracebacks + @force_not_colorized def test_excepthook_bytes_filename(self): # bpo-37467: sys.excepthook() must not crash if a filename # is a bytes string @@ -169,7 +192,8 @@ def test_excepthook_bytes_filename(self): def test_excepthook(self): with test.support.captured_output("stderr") as stderr: - sys.excepthook(1, '1', 1) + with test.support.catch_unraisable_exception(): + sys.excepthook(1, '1', 1) self.assertTrue("TypeError: print_exception(): Exception expected for " \ "value, str found" in stderr.getvalue()) @@ -182,6 +206,7 @@ class SysModuleTest(unittest.TestCase): def tearDown(self): test.support.reap_children() + @unittest.expectedFailure # TODO: RUSTPYTHON def test_exit(self): # call with two arguments self.assertRaises(TypeError, sys.exit, 42, 42) @@ -196,6 +221,20 @@ def test_exit(self): self.assertEqual(out, b'') self.assertEqual(err, b'') + # gh-125842: Windows uses 32-bit unsigned integers for exit codes + # so a -1 exit code is sometimes interpreted as 0xffff_ffff. + rc, out, err = assert_python_failure('-c', 'import sys; sys.exit(0xffff_ffff)') + self.assertIn(rc, (-1, 0xff, 0xffff_ffff)) + self.assertEqual(out, b'') + self.assertEqual(err, b'') + + # Overflow results in a -1 exit code, which may be converted to 0xff + # or 0xffff_ffff. + rc, out, err = assert_python_failure('-c', 'import sys; sys.exit(2**128)') + self.assertIn(rc, (-1, 0xff, 0xffff_ffff)) + self.assertEqual(out, b'') + self.assertEqual(err, b'') + # call with integer argument with self.assertRaises(SystemExit) as cm: sys.exit(42) @@ -238,17 +277,36 @@ def check_exit_message(code, expected, **env_vars): # test that the exit message is written with backslashreplace error # handler to stderr - # TODO: RUSTPYTHON; allow surrogates in strings - # check_exit_message( - # r'import sys; sys.exit("surrogates:\uDCFF")', - # b"surrogates:\\udcff") + check_exit_message( + r'import sys; sys.exit("surrogates:\uDCFF")', + b"surrogates:\\udcff") # test that the unicode message is encoded to the stderr encoding # instead of the default encoding (utf8) - # TODO: RUSTPYTHON; handle PYTHONIOENCODING - # check_exit_message( - # r'import sys; sys.exit("h\xe9")', - # b"h\xe9", PYTHONIOENCODING='latin-1') + check_exit_message( + r'import sys; sys.exit("h\xe9")', + b"h\xe9", PYTHONIOENCODING='latin-1') + + @support.requires_subprocess() + def test_exit_codes_under_repl(self): + # GH-129900: SystemExit, or things that raised it, didn't + # get their return code propagated by the REPL + import tempfile + + exit_ways = [ + "exit", + "__import__('sys').exit", + "raise SystemExit" + ] + + for exitfunc in exit_ways: + for return_code in (0, 123): + with self.subTest(exitfunc=exitfunc, return_code=return_code): + with tempfile.TemporaryFile("w+") as stdin: + stdin.write(f"{exitfunc}({return_code})\n") + stdin.seek(0) + proc = subprocess.run([sys.executable], stdin=stdin) + self.assertEqual(proc.returncode, return_code) def test_getdefaultencoding(self): self.assertRaises(TypeError, sys.getdefaultencoding, 42) @@ -273,21 +331,30 @@ def test_switchinterval(self): finally: sys.setswitchinterval(orig) - def test_recursionlimit(self): + def test_getrecursionlimit(self): + limit = sys.getrecursionlimit() + self.assertIsInstance(limit, int) + self.assertGreater(limit, 1) + self.assertRaises(TypeError, sys.getrecursionlimit, 42) - oldlimit = sys.getrecursionlimit() - self.assertRaises(TypeError, sys.setrecursionlimit) - self.assertRaises(ValueError, sys.setrecursionlimit, -42) - sys.setrecursionlimit(10000) - self.assertEqual(sys.getrecursionlimit(), 10000) - sys.setrecursionlimit(oldlimit) - - @unittest.skipIf(getattr(sys, "_rustpython_debugbuild", False), "TODO: RUSTPYTHON, stack overflow on debug build") + + def test_setrecursionlimit(self): + old_limit = sys.getrecursionlimit() + try: + sys.setrecursionlimit(10_005) + self.assertEqual(sys.getrecursionlimit(), 10_005) + + self.assertRaises(TypeError, sys.setrecursionlimit) + self.assertRaises(ValueError, sys.setrecursionlimit, -42) + finally: + sys.setrecursionlimit(old_limit) + + @unittest.skipIf(getattr(sys, '_rustpython_debugbuild', False), 'TODO: RUSTPYTHON; stack overflow on debug build') def test_recursionlimit_recovery(self): if hasattr(sys, 'gettrace') and sys.gettrace(): self.skipTest('fatal error if run with a trace function') - oldlimit = sys.getrecursionlimit() + old_limit = sys.getrecursionlimit() def f(): f() try: @@ -306,38 +373,63 @@ def f(): with self.assertRaises(RecursionError): f() finally: - sys.setrecursionlimit(oldlimit) + sys.setrecursionlimit(old_limit) @test.support.cpython_only - def test_setrecursionlimit_recursion_depth(self): + def test_setrecursionlimit_to_depth(self): # Issue #25274: Setting a low recursion limit must be blocked if the # current recursion depth is already higher than limit. - from _testinternalcapi import get_recursion_depth - - def set_recursion_limit_at_depth(depth, limit): - recursion_depth = get_recursion_depth() - if recursion_depth >= depth: + old_limit = sys.getrecursionlimit() + try: + depth = support.get_recursion_depth() + with self.subTest(limit=sys.getrecursionlimit(), depth=depth): + # depth + 1 is OK + sys.setrecursionlimit(depth + 1) + + # reset the limit to be able to call self.assertRaises() + # context manager + sys.setrecursionlimit(old_limit) with self.assertRaises(RecursionError) as cm: - sys.setrecursionlimit(limit) - self.assertRegex(str(cm.exception), - "cannot set the recursion limit to [0-9]+ " - "at the recursion depth [0-9]+: " - "the limit is too low") - else: - set_recursion_limit_at_depth(depth, limit) + sys.setrecursionlimit(depth) + self.assertRegex(str(cm.exception), + "cannot set the recursion limit to [0-9]+ " + "at the recursion depth [0-9]+: " + "the limit is too low") + finally: + sys.setrecursionlimit(old_limit) - oldlimit = sys.getrecursionlimit() - try: - sys.setrecursionlimit(1000) + @unittest.skipUnless(support.Py_GIL_DISABLED, "only meaningful if the GIL is disabled") + @threading_helper.requires_working_threading() + def test_racing_recursion_limit(self): + from threading import Thread + def something_recursive(): + def count(n): + if n > 0: + return count(n - 1) + 1 + return 0 - for limit in (10, 25, 50, 75, 100, 150, 200): - set_recursion_limit_at_depth(limit, limit) - finally: - sys.setrecursionlimit(oldlimit) + count(50) + + def set_recursion_limit(): + for limit in range(100, 200): + sys.setrecursionlimit(limit) + + threads = [] + for _ in range(5): + threads.append(Thread(target=set_recursion_limit)) + + for _ in range(5): + threads.append(Thread(target=something_recursive)) + + with threading_helper.catch_threading_exception() as cm: + with threading_helper.start_threads(threads): + pass + + if cm.exc_value: + raise cm.exc_value - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_getwindowsversion(self): # Raise SkipTest if sys doesn't have getwindowsversion attribute test.support.get_attribute(sys, "getwindowsversion") @@ -368,8 +460,7 @@ def test_getwindowsversion(self): # still has 5 elements maj, min, buildno, plat, csd = sys.getwindowsversion() - # TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute 'call_tracing' - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute 'call_tracing' def test_call_tracing(self): self.assertRaises(TypeError, sys.call_tracing, type, 2) @@ -386,15 +477,21 @@ def test_dlopenflags(self): @test.support.refcount_test def test_refcount(self): - # n here must be a global in order for this test to pass while - # tracing with a python function. Tracing calls PyFrame_FastToLocals - # which will add a copy of any locals to the frame object, causing - # the reference count to increase by 2 instead of 1. + # n here originally had to be a global in order for this test to pass + # while tracing with a python function. Tracing used to call + # PyFrame_FastToLocals, which would add a copy of any locals to the + # frame object, causing the ref count to increase by 2 instead of 1. + # While that no longer happens (due to PEP 667), this test case retains + # its original global-based implementation + # PEP 683's immortal objects also made this point moot, since the + # refcount for None doesn't change anyway. Maybe this test should be + # using a different constant value? (e.g. an integer) global n self.assertRaises(TypeError, sys.getrefcount) c = sys.getrefcount(None) n = None - self.assertEqual(sys.getrefcount(None), c+1) + # Singleton refcnts don't change + self.assertEqual(sys.getrefcount(None), c) del n self.assertEqual(sys.getrefcount(None), c) if hasattr(sys, "gettotalrefcount"): @@ -408,6 +505,26 @@ def test_getframe(self): is sys._getframe().f_code ) + def test_getframemodulename(self): + # Default depth gets ourselves + self.assertEqual(__name__, sys._getframemodulename()) + self.assertEqual("unittest.case", sys._getframemodulename(1)) + i = 0 + f = sys._getframe(i) + while f: + self.assertEqual( + f.f_globals['__name__'], + sys._getframemodulename(i) or '__main__' + ) + i += 1 + f2 = f.f_back + try: + f = sys._getframe(i) + except ValueError: + break + self.assertIs(f, f2) + self.assertIsNone(sys._getframemodulename(i)) + # sys._current_frames() is a CPython-only gimmick. # XXX RUSTPYTHON: above comment is from original cpython test; not sure why the cpython_only decorator wasn't added @test.support.cpython_only @@ -436,49 +553,49 @@ def g456(): t.start() entered_g.wait() - # At this point, t has finished its entered_g.set(), although it's - # impossible to guess whether it's still on that line or has moved on - # to its leave_g.wait(). - self.assertEqual(len(thread_info), 1) - thread_id = thread_info[0] - - d = sys._current_frames() - for tid in d: - self.assertIsInstance(tid, int) - self.assertGreater(tid, 0) - - main_id = threading.get_ident() - self.assertIn(main_id, d) - self.assertIn(thread_id, d) - - # Verify that the captured main-thread frame is _this_ frame. - frame = d.pop(main_id) - self.assertTrue(frame is sys._getframe()) - - # Verify that the captured thread frame is blocked in g456, called - # from f123. This is a little tricky, since various bits of - # threading.py are also in the thread's call stack. - frame = d.pop(thread_id) - stack = traceback.extract_stack(frame) - for i, (filename, lineno, funcname, sourceline) in enumerate(stack): - if funcname == "f123": - break - else: - self.fail("didn't find f123() on thread's call stack") - - self.assertEqual(sourceline, "g456()") + try: + # At this point, t has finished its entered_g.set(), although it's + # impossible to guess whether it's still on that line or has moved on + # to its leave_g.wait(). + self.assertEqual(len(thread_info), 1) + thread_id = thread_info[0] + + d = sys._current_frames() + for tid in d: + self.assertIsInstance(tid, int) + self.assertGreater(tid, 0) + + main_id = threading.get_ident() + self.assertIn(main_id, d) + self.assertIn(thread_id, d) + + # Verify that the captured main-thread frame is _this_ frame. + frame = d.pop(main_id) + self.assertTrue(frame is sys._getframe()) + + # Verify that the captured thread frame is blocked in g456, called + # from f123. This is a little tricky, since various bits of + # threading.py are also in the thread's call stack. + frame = d.pop(thread_id) + stack = traceback.extract_stack(frame) + for i, (filename, lineno, funcname, sourceline) in enumerate(stack): + if funcname == "f123": + break + else: + self.fail("didn't find f123() on thread's call stack") - # And the next record must be for g456(). - filename, lineno, funcname, sourceline = stack[i+1] - self.assertEqual(funcname, "g456") - self.assertIn(sourceline, ["leave_g.wait()", "entered_g.set()"]) + self.assertEqual(sourceline, "g456()") - # Reap the spawned thread. - leave_g.set() - t.join() + # And the next record must be for g456(). + filename, lineno, funcname, sourceline = stack[i+1] + self.assertEqual(funcname, "g456") + self.assertIn(sourceline, ["leave_g.wait()", "entered_g.set()"]) + finally: + # Reap the spawned thread. + leave_g.set() + t.join() - # TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute '_current_exceptions' - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute '_current_exceptions' @threading_helper.reap_threads @threading_helper.requires_working_threading() def test_current_exceptions(self): @@ -488,7 +605,7 @@ def test_current_exceptions(self): # Spawn a thread that blocks at a known place. Then the main # thread does sys._current_frames(), and verifies that the frames # returned make sense. - entered_g = threading.Event() + g_raised = threading.Event() leave_g = threading.Event() thread_info = [] # the thread's id @@ -497,55 +614,54 @@ def f123(): def g456(): thread_info.append(threading.get_ident()) - entered_g.set() while True: try: raise ValueError("oops") except ValueError: + g_raised.set() if leave_g.wait(timeout=support.LONG_TIMEOUT): break t = threading.Thread(target=f123) t.start() - entered_g.wait() + g_raised.wait(timeout=support.LONG_TIMEOUT) - # At this point, t has finished its entered_g.set(), although it's - # impossible to guess whether it's still on that line or has moved on - # to its leave_g.wait(). - self.assertEqual(len(thread_info), 1) - thread_id = thread_info[0] - - d = sys._current_exceptions() - for tid in d: - self.assertIsInstance(tid, int) - self.assertGreater(tid, 0) - - main_id = threading.get_ident() - self.assertIn(main_id, d) - self.assertIn(thread_id, d) - self.assertEqual((None, None, None), d.pop(main_id)) - - # Verify that the captured thread frame is blocked in g456, called - # from f123. This is a little tricky, since various bits of - # threading.py are also in the thread's call stack. - exc_type, exc_value, exc_tb = d.pop(thread_id) - stack = traceback.extract_stack(exc_tb.tb_frame) - for i, (filename, lineno, funcname, sourceline) in enumerate(stack): - if funcname == "f123": - break - else: - self.fail("didn't find f123() on thread's call stack") - - self.assertEqual(sourceline, "g456()") + try: + self.assertEqual(len(thread_info), 1) + thread_id = thread_info[0] + + d = sys._current_exceptions() + for tid in d: + self.assertIsInstance(tid, int) + self.assertGreater(tid, 0) + + main_id = threading.get_ident() + self.assertIn(main_id, d) + self.assertIn(thread_id, d) + self.assertEqual(None, d.pop(main_id)) + + # Verify that the captured thread frame is blocked in g456, called + # from f123. This is a little tricky, since various bits of + # threading.py are also in the thread's call stack. + exc_value = d.pop(thread_id) + stack = traceback.extract_stack(exc_value.__traceback__.tb_frame) + for i, (filename, lineno, funcname, sourceline) in enumerate(stack): + if funcname == "f123": + break + else: + self.fail("didn't find f123() on thread's call stack") - # And the next record must be for g456(). - filename, lineno, funcname, sourceline = stack[i+1] - self.assertEqual(funcname, "g456") - self.assertTrue(sourceline.startswith("if leave_g.wait(")) + self.assertEqual(sourceline, "g456()") - # Reap the spawned thread. - leave_g.set() - t.join() + # And the next record must be for g456(). + filename, lineno, funcname, sourceline = stack[i+1] + self.assertEqual(funcname, "g456") + self.assertTrue((sourceline.startswith("if leave_g.wait(") or + sourceline.startswith("g_raised.set()"))) + finally: + # Reap the spawned thread. + leave_g.set() + t.join() def test_attributes(self): self.assertIsInstance(sys.api_version, int) @@ -647,7 +763,7 @@ def test_thread_info(self): self.assertEqual(len(info), 3) self.assertIn(info.name, ('nt', 'pthread', 'pthread-stubs', 'solaris', None)) self.assertIn(info.lock, ('semaphore', 'mutex+cond', None)) - if sys.platform.startswith(("linux", "freebsd")): + if sys.platform.startswith(("linux", "android", "freebsd")): self.assertEqual(info.name, "pthread") elif sys.platform == "win32": self.assertEqual(info.name, "nt") @@ -670,13 +786,23 @@ def test_43581(self): self.assertEqual(sys.__stdout__.encoding, sys.__stderr__.encoding) def test_intern(self): - global INTERN_NUMRUNS - INTERN_NUMRUNS += 1 + has_is_interned = (test.support.check_impl_detail(cpython=True) + or hasattr(sys, '_is_interned')) self.assertRaises(TypeError, sys.intern) - s = "never interned before" + str(INTERN_NUMRUNS) + self.assertRaises(TypeError, sys.intern, b'abc') + if has_is_interned: + self.assertRaises(TypeError, sys._is_interned) + self.assertRaises(TypeError, sys._is_interned, b'abc') + s = "never interned before" + str(random.randrange(0, 10**9)) self.assertTrue(sys.intern(s) is s) + if has_is_interned: + self.assertIs(sys._is_interned(s), True) s2 = s.swapcase().swapcase() + if has_is_interned: + self.assertIs(sys._is_interned(s2), False) self.assertTrue(sys.intern(s2) is s) + if has_is_interned: + self.assertIs(sys._is_interned(s2), False) # Subclasses of string can't be interned, because they # provide too much opportunity for insane things to happen. @@ -688,6 +814,73 @@ def __hash__(self): return 123 self.assertRaises(TypeError, sys.intern, S("abc")) + if has_is_interned: + self.assertIs(sys._is_interned(S("abc")), False) + + @support.cpython_only + @requires_subinterpreters + def test_subinterp_intern_dynamically_allocated(self): + # Implementation detail: Dynamically allocated strings + # are distinct between interpreters + s = "never interned before" + str(random.randrange(0, 10**9)) + t = sys.intern(s) + self.assertIs(t, s) + + interp = interpreters.create() + interp.exec(textwrap.dedent(f''' + import sys + + # set `s`, avoid parser interning & constant folding + s = str({s.encode()!r}, 'utf-8') + + t = sys.intern(s) + + assert id(t) != {id(s)}, (id(t), {id(s)}) + assert id(t) != {id(t)}, (id(t), {id(t)}) + ''')) + + @support.cpython_only + @requires_subinterpreters + def test_subinterp_intern_statically_allocated(self): + # Implementation detail: Statically allocated strings are shared + # between interpreters. + # See Tools/build/generate_global_objects.py for the list + # of strings that are always statically allocated. + for s in ('__init__', 'CANCELLED', '<module>', 'utf-8', + '{{', '', '\n', '_', 'x', '\0', '\N{CEDILLA}', '\xff', + ): + with self.subTest(s=s): + t = sys.intern(s) + + interp = interpreters.create() + interp.exec(textwrap.dedent(f''' + import sys + + # set `s`, avoid parser interning & constant folding + s = str({s.encode()!r}, 'utf-8') + + t = sys.intern(s) + assert id(t) == {id(t)}, (id(t), {id(t)}) + ''')) + + @support.cpython_only + @requires_subinterpreters + def test_subinterp_intern_singleton(self): + # Implementation detail: singletons are used for 0- and 1-character + # latin1 strings. + for s in '', '\n', '_', 'x', '\0', '\N{CEDILLA}', '\xff': + with self.subTest(s=s): + interp = interpreters.create() + interp.exec(textwrap.dedent(f''' + import sys + + # set `s`, avoid parser interning & constant folding + s = str({s.encode()!r}, 'utf-8') + + assert id(s) == {id(s)} + t = sys.intern(s) + ''')) + self.assertTrue(sys._is_interned(s)) def test_sys_flags(self): self.assertTrue(sys.flags) @@ -709,12 +902,7 @@ def test_sys_flags(self): def assert_raise_on_new_sys_type(self, sys_attr): # Users are intentionally prevented from creating new instances of # sys.flags, sys.version_info, and sys.getwindowsversion. - arg = sys_attr - attr_type = type(sys_attr) - with self.assertRaises(TypeError): - attr_type(arg) - with self.assertRaises(TypeError): - attr_type.__new__(attr_type, arg) + support.check_disallow_instantiation(self, type(sys_attr), sys_attr) def test_sys_flags_no_instantiation(self): self.assert_raise_on_new_sys_type(sys.flags) @@ -722,8 +910,7 @@ def test_sys_flags_no_instantiation(self): def test_sys_version_info_no_instantiation(self): self.assert_raise_on_new_sys_type(sys.version_info) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_sys_getwindowsversion_no_instantiation(self): # Skip if not being run on Windows. test.support.get_attribute(sys, "getwindowsversion") @@ -733,8 +920,8 @@ def test_sys_getwindowsversion_no_instantiation(self): def test_clear_type_cache(self): sys._clear_type_cache() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @force_not_colorized @support.requires_subprocess() def test_ioencoding(self): env = dict(os.environ) @@ -898,14 +1085,12 @@ def check_locale_surrogateescape(self, locale): 'stdout: surrogateescape\n' 'stderr: backslashreplace\n') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_c_locale_surrogateescape(self): self.check_locale_surrogateescape('C') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_posix_locale_surrogateescape(self): self.check_locale_surrogateescape('POSIX') @@ -958,12 +1143,12 @@ def test_debugmallocstats(self): "sys.getallocatedblocks unavailable on this build") def test_getallocatedblocks(self): try: - import _testcapi + import _testinternalcapi except ImportError: with_pymalloc = support.with_pymalloc() else: try: - alloc_name = _testcapi.pymem_getallocatorsname() + alloc_name = _testinternalcapi.pymem_getallocatorsname() except RuntimeError as exc: # "cannot get allocators name" (ex: tracemalloc is used) with_pymalloc = True @@ -995,8 +1180,13 @@ def test_getallocatedblocks(self): c = sys.getallocatedblocks() self.assertIn(c, range(b - 50, b + 50)) - # TODO: RUSTPYTHON, AtExit.__del__ is not invoked because module destruction is missing. - @unittest.expectedFailure + def test_is_gil_enabled(self): + if support.Py_GIL_DISABLED: + self.assertIs(type(sys._is_gil_enabled()), bool) + else: + self.assertTrue(sys._is_gil_enabled()) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AtExit.__del__ is not invoked because module destruction is missing. def test_is_finalizing(self): self.assertIs(sys.is_finalizing(), False) # Don't use the atexit module because _Py_Finalizing is only set @@ -1018,8 +1208,7 @@ def __del__(self): rc, stdout, stderr = assert_python_ok('-c', code) self.assertEqual(stdout.rstrip(), b'True') - # TODO: RUSTPYTHON, IndexError: list index out of range - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; IndexError: list index out of range def test_issue20602(self): # sys.flags and sys.float_info were wiped during shutdown. code = """if 1: @@ -1052,15 +1241,15 @@ def __del__(self): self.assertEqual(stdout.rstrip(), b"") self.assertEqual(stderr.rstrip(), b"") - @unittest.skipUnless(hasattr(sys, 'getandroidapilevel'), - 'need sys.getandroidapilevel()') + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute 'getandroidapilevel' + @unittest.skipUnless(sys.platform == "android", "Android only") def test_getandroidapilevel(self): level = sys.getandroidapilevel() self.assertIsInstance(level, int) self.assertGreater(level, 0) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @force_not_colorized @support.requires_subprocess() def test_sys_tracebacklimit(self): code = """if 1: @@ -1081,14 +1270,20 @@ def check(tracebacklimit, expected): traceback = [ b'Traceback (most recent call last):', b' File "<string>", line 8, in <module>', + b' f2()', + b' ~~^^', b' File "<string>", line 6, in f2', + b' f1()', + b' ~~^^', b' File "<string>", line 4, in f1', + b' 1 / 0', + b' ~~^~~', b'ZeroDivisionError: division by zero' ] check(10, traceback) check(3, traceback) - check(2, traceback[:1] + traceback[2:]) - check(1, traceback[:1] + traceback[3:]) + check(2, traceback[:1] + traceback[4:]) + check(1, traceback[:1] + traceback[7:]) check(0, [traceback[-1]]) check(-1, [traceback[-1]]) check(1<<1000, traceback) @@ -1129,8 +1324,7 @@ def test_module_names(self): for name in sys.stdlib_module_names: self.assertIsInstance(name, str) - # TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute '_stdlib_dir' - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute '_stdlib_dir' def test_stdlib_dir(self): os = import_helper.import_fresh_module('os') marker = getattr(os, '__file__', None) @@ -1140,41 +1334,76 @@ def test_stdlib_dir(self): self.assertEqual(os.path.normpath(sys._stdlib_dir), os.path.normpath(expected)) + @unittest.skipUnless(hasattr(sys, 'getobjects'), 'need sys.getobjects()') + def test_getobjects(self): + # sys.getobjects(0) + all_objects = sys.getobjects(0) + self.assertIsInstance(all_objects, list) + self.assertGreater(len(all_objects), 0) + + # sys.getobjects(0, MyType) + class MyType: + pass + size = 100 + my_objects = [MyType() for _ in range(size)] + get_objects = sys.getobjects(0, MyType) + self.assertEqual(len(get_objects), size) + for obj in get_objects: + self.assertIsInstance(obj, MyType) + + # sys.getobjects(3, MyType) + get_objects = sys.getobjects(3, MyType) + self.assertEqual(len(get_objects), 3) + + @unittest.skipUnless(hasattr(sys, '_stats_on'), 'need Py_STATS build') + def test_pystats(self): + # Call the functions, just check that they don't crash + # Cannot save/restore state. + sys._stats_on() + sys._stats_off() + sys._stats_clear() + sys._stats_dump() + + @test.support.cpython_only + @unittest.skipUnless(hasattr(sys, 'abiflags'), 'need sys.abiflags') + def test_disable_gil_abi(self): + self.assertEqual('t' in sys.abiflags, support.Py_GIL_DISABLED) + @test.support.cpython_only class UnraisableHookTest(unittest.TestCase): - def write_unraisable_exc(self, exc, err_msg, obj): - import _testcapi - import types - err_msg2 = f"Exception ignored {err_msg}" - try: - _testcapi.write_unraisable_exc(exc, err_msg, obj) - return types.SimpleNamespace(exc_type=type(exc), - exc_value=exc, - exc_traceback=exc.__traceback__, - err_msg=err_msg2, - object=obj) - finally: - # Explicitly break any reference cycle - exc = None - def test_original_unraisablehook(self): - for err_msg in (None, "original hook"): - with self.subTest(err_msg=err_msg): - obj = "an object" - - with test.support.captured_output("stderr") as stderr: - with test.support.swap_attr(sys, 'unraisablehook', - sys.__unraisablehook__): - self.write_unraisable_exc(ValueError(42), err_msg, obj) - - err = stderr.getvalue() - if err_msg is not None: - self.assertIn(f'Exception ignored {err_msg}: {obj!r}\n', err) - else: - self.assertIn(f'Exception ignored in: {obj!r}\n', err) - self.assertIn('Traceback (most recent call last):\n', err) - self.assertIn('ValueError: 42\n', err) + _testcapi = import_helper.import_module('_testcapi') + from _testcapi import err_writeunraisable, err_formatunraisable + obj = hex + + with support.swap_attr(sys, 'unraisablehook', + sys.__unraisablehook__): + with support.captured_stderr() as stderr: + err_writeunraisable(ValueError(42), obj) + lines = stderr.getvalue().splitlines() + self.assertEqual(lines[0], f'Exception ignored in: {obj!r}') + self.assertEqual(lines[1], 'Traceback (most recent call last):') + self.assertEqual(lines[-1], 'ValueError: 42') + + with support.captured_stderr() as stderr: + err_writeunraisable(ValueError(42), None) + lines = stderr.getvalue().splitlines() + self.assertEqual(lines[0], 'Traceback (most recent call last):') + self.assertEqual(lines[-1], 'ValueError: 42') + + with support.captured_stderr() as stderr: + err_formatunraisable(ValueError(42), 'Error in %R', obj) + lines = stderr.getvalue().splitlines() + self.assertEqual(lines[0], f'Error in {obj!r}:') + self.assertEqual(lines[1], 'Traceback (most recent call last):') + self.assertEqual(lines[-1], 'ValueError: 42') + + with support.captured_stderr() as stderr: + err_formatunraisable(ValueError(42), None) + lines = stderr.getvalue().splitlines() + self.assertEqual(lines[0], 'Traceback (most recent call last):') + self.assertEqual(lines[-1], 'ValueError: 42') def test_original_unraisablehook_err(self): # bpo-22836: PyErr_WriteUnraisable() should give sensible reports @@ -1221,6 +1450,8 @@ def test_original_unraisablehook_exception_qualname(self): # Check that the exception is printed with its qualified name # rather than just classname, and the module names appears # unless it is one of the hard-coded exclusions. + _testcapi = import_helper.import_module('_testcapi') + from _testcapi import err_writeunraisable class A: class B: class X(Exception): @@ -1232,9 +1463,7 @@ class X(Exception): with test.support.captured_stderr() as stderr, test.support.swap_attr( sys, 'unraisablehook', sys.__unraisablehook__ ): - expected = self.write_unraisable_exc( - A.B.X(), "msg", "obj" - ) + err_writeunraisable(A.B.X(), "obj") report = stderr.getvalue() self.assertIn(A.B.X.__qualname__, report) if moduleName in ['builtins', '__main__']: @@ -1250,34 +1479,45 @@ def test_original_unraisablehook_wrong_type(self): sys.unraisablehook(exc) def test_custom_unraisablehook(self): + _testcapi = import_helper.import_module('_testcapi') + from _testcapi import err_writeunraisable, err_formatunraisable hook_args = None def hook_func(args): nonlocal hook_args hook_args = args - obj = object() + obj = hex try: with test.support.swap_attr(sys, 'unraisablehook', hook_func): - expected = self.write_unraisable_exc(ValueError(42), - "custom hook", obj) - for attr in "exc_type exc_value exc_traceback err_msg object".split(): - self.assertEqual(getattr(hook_args, attr), - getattr(expected, attr), - (hook_args, expected)) + exc = ValueError(42) + err_writeunraisable(exc, obj) + self.assertIs(hook_args.exc_type, type(exc)) + self.assertIs(hook_args.exc_value, exc) + self.assertIs(hook_args.exc_traceback, exc.__traceback__) + self.assertIsNone(hook_args.err_msg) + self.assertEqual(hook_args.object, obj) + + err_formatunraisable(exc, "custom hook %R", obj) + self.assertIs(hook_args.exc_type, type(exc)) + self.assertIs(hook_args.exc_value, exc) + self.assertIs(hook_args.exc_traceback, exc.__traceback__) + self.assertEqual(hook_args.err_msg, f'custom hook {obj!r}') + self.assertIsNone(hook_args.object) finally: # expected and hook_args contain an exception: break reference cycle expected = None hook_args = None def test_custom_unraisablehook_fail(self): + _testcapi = import_helper.import_module('_testcapi') + from _testcapi import err_writeunraisable def hook_func(*args): raise Exception("hook_func failed") with test.support.captured_output("stderr") as stderr: with test.support.swap_attr(sys, 'unraisablehook', hook_func): - self.write_unraisable_exc(ValueError(42), - "custom hook fail", None) + err_writeunraisable(ValueError(42), "custom hook fail") err = stderr.getvalue() self.assertIn(f'Exception ignored in sys.unraisablehook: ' @@ -1293,8 +1533,9 @@ class SizeofTest(unittest.TestCase): def setUp(self): self.P = struct.calcsize('P') self.longdigit = sys.int_info.sizeof_digit - import _testinternalcapi + _testinternalcapi = import_helper.import_module("_testinternalcapi") self.gc_headsize = _testinternalcapi.SIZEOF_PYGC_HEAD + self.managed_pre_header_size = _testinternalcapi.SIZEOF_MANAGED_PRE_HEADER check_sizeof = test.support.check_sizeof @@ -1330,7 +1571,7 @@ class OverflowSizeof(int): def __sizeof__(self): return int(self) self.assertEqual(sys.getsizeof(OverflowSizeof(sys.maxsize)), - sys.maxsize + self.gc_headsize) + sys.maxsize + self.gc_headsize + self.managed_pre_header_size) with self.assertRaises(OverflowError): sys.getsizeof(OverflowSizeof(sys.maxsize + 1)) with self.assertRaises(ValueError): @@ -1452,10 +1693,10 @@ class C(object): pass def func(): return sys._getframe() x = func() - check(x, size('3Pi3c7P2ic??2P')) + check(x, size('3Pi2c2P7P2ic??2P')) # function def func(): pass - check(func, size('14Pi')) + check(func, size('15Pi')) class c(): @staticmethod def foo(): @@ -1469,7 +1710,7 @@ def bar(cls): check(bar, size('PP')) # generator def get_gen(): yield 1 - check(get_gen(), size('P2P4P4c7P2ic??P')) + check(get_gen(), size('PP4P4c7P2ic??2P')) # iterator check(iter('abc'), size('lP')) # callable-iterator @@ -1497,7 +1738,10 @@ def get_gen(): yield 1 check(int(PyLong_BASE**2-1), vsize('') + 2*self.longdigit) check(int(PyLong_BASE**2), vsize('') + 3*self.longdigit) # module - check(unittest, size('PnPPP')) + if support.Py_GIL_DISABLED: + check(unittest, size('PPPPPP')) + else: + check(unittest, size('PPPPP')) # None check(None, size('')) # NotImplementedType @@ -1512,9 +1756,10 @@ def delx(self): del self.__x x = property(getx, setx, delx, "") check(x, size('5Pi')) # PyCapsule - # XXX + check(_datetime.datetime_CAPI, size('6P')) # rangeiterator - check(iter(range(1)), size('4l')) + check(iter(range(1)), size('3l')) + check(iter(range(2**65)), size('3P')) # reverse check(reversed(''), size('nP')) # range @@ -1551,8 +1796,8 @@ def delx(self): del self.__x check((1,2,3), vsize('') + 3*self.P) # type # static type: PyTypeObject - fmt = 'P2nPI13Pl4Pn9Pn12PIP' - s = vsize('2P' + fmt) + fmt = 'P2nPI13Pl4Pn9Pn12PIPc' + s = vsize(fmt) check(int, s) # class s = vsize(fmt + # PyTypeObject @@ -1562,7 +1807,7 @@ def delx(self): del self.__x '10P' # PySequenceMethods '2P' # PyBufferProcs '6P' - '1P' # Specializer cache + '1PIP' # Specializer cache ) class newstyleclass(object): pass # Separate block for PyDictKeysObject with 8 keys and 5 entries @@ -1584,8 +1829,8 @@ class newstyleclass(object): pass '\u0100'*40, '\uffff'*100, '\U00010000'*30, '\U0010ffff'*100] # also update field definitions in test_unicode.test_raiseMemError - asciifields = "nnbP" - compactfields = asciifields + "nPn" + asciifields = "nnb" + compactfields = asciifields + "nP" unicodefields = compactfields + "P" for s in samples: maxchar = ord(max(s)) @@ -1609,11 +1854,15 @@ class newstyleclass(object): pass # TODO: add check that forces layout of unicodefields # weakref import weakref - check(weakref.ref(int), size('2Pn3P')) + if support.Py_GIL_DISABLED: + expected = size('2Pn4P') + else: + expected = size('2Pn3P') + check(weakref.ref(int), expected) # weakproxy # XXX # weakcallableproxy - check(weakref.proxy(int), size('2Pn3P')) + check(weakref.proxy(int), expected) def check_slots(self, obj, base, extra): expected = sys.getsizeof(base) + struct.calcsize(extra) @@ -1655,15 +1904,16 @@ def test_pythontypes(self): check(_ast.AST(), size('P')) try: raise TypeError - except TypeError: - tb = sys.exc_info()[2] + except TypeError as e: + tb = e.__traceback__ # traceback if tb is not None: check(tb, size('2P2i')) # symtable entry # XXX # sys.flags - check(sys.flags, vsize('') + self.P * len(sys.flags)) + # FIXME: The +1 will not be necessary once gh-122575 is fixed + check(sys.flags, vsize('') + self.P * (1 + len(sys.flags))) def test_asyncgen_hooks(self): old = sys.get_asyncgen_hooks() @@ -1671,6 +1921,21 @@ def test_asyncgen_hooks(self): self.assertIsNone(old.finalizer) firstiter = lambda *a: None + finalizer = lambda *a: None + + with self.assertRaises(TypeError): + sys.set_asyncgen_hooks(firstiter=firstiter, finalizer="invalid") + cur = sys.get_asyncgen_hooks() + self.assertIsNone(cur.firstiter) + self.assertIsNone(cur.finalizer) + + # gh-118473 + with self.assertRaises(TypeError): + sys.set_asyncgen_hooks(firstiter="invalid", finalizer=finalizer) + cur = sys.get_asyncgen_hooks() + self.assertIsNone(cur.firstiter) + self.assertIsNone(cur.finalizer) + sys.set_asyncgen_hooks(firstiter=firstiter) hooks = sys.get_asyncgen_hooks() self.assertIs(hooks.firstiter, firstiter) @@ -1678,7 +1943,6 @@ def test_asyncgen_hooks(self): self.assertIs(hooks.finalizer, None) self.assertIs(hooks[1], None) - finalizer = lambda *a: None sys.set_asyncgen_hooks(finalizer=finalizer) hooks = sys.get_asyncgen_hooks() self.assertIs(hooks.firstiter, firstiter) diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 30f126b3742..8295da9486d 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -53,7 +53,7 @@ mod sys { const RUSTPYTHON_DEBUGBUILD: bool = cfg!(debug_assertions); #[pyattr(name = "abiflags")] - pub(crate) const ABIFLAGS: &str = ""; + pub(crate) const ABIFLAGS: &str = "t"; // 't' for free-threaded (no GIL) #[pyattr(name = "api_version")] const API_VERSION: u32 = 0x0; // what C api? #[pyattr(name = "copyright")] @@ -599,6 +599,11 @@ mod sys { // TODO: sys.audit implementation } + #[pyfunction] + const fn _is_gil_enabled() -> bool { + false // RustPython has no GIL (like free-threaded Python) + } + #[pyfunction] fn exit(code: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult { let code = code.unwrap_or_none(vm); diff --git a/crates/vm/src/stdlib/sysconfigdata.rs b/crates/vm/src/stdlib/sysconfigdata.rs index 5e954f7fe7a..99228024d2d 100644 --- a/crates/vm/src/stdlib/sysconfigdata.rs +++ b/crates/vm/src/stdlib/sysconfigdata.rs @@ -23,6 +23,8 @@ pub(crate) mod _sysconfigdata { "RUST_MULTIARCH" => RUST_MULTIARCH, // enough for tests to stop expecting urandom() to fail after restricting file resources "HAVE_GETRANDOM" => 1, + // RustPython has no GIL (like free-threaded Python) + "Py_GIL_DISABLED" => 1, // Compiler configuration for native extension builds "CC" => "cc", "CXX" => "c++", From d3e2fa47ccca06cdf6372e343aceadcfbf79fcd2 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 1 Jan 2026 18:50:34 +0900 Subject: [PATCH 663/819] Remove wrong Deref (#6620) --- crates/stdlib/src/array.rs | 4 +- crates/stdlib/src/bisect.rs | 2 +- crates/stdlib/src/cmath.rs | 54 ++++++------- crates/stdlib/src/math.rs | 106 +++++++++++++------------ crates/stdlib/src/statistics.rs | 2 +- crates/vm/src/buffer.rs | 6 +- crates/vm/src/builtins/bytes.rs | 2 +- crates/vm/src/builtins/mappingproxy.rs | 8 +- crates/vm/src/builtins/slice.rs | 1 + crates/vm/src/function/number.rs | 58 +++++++------- crates/vm/src/function/protocol.rs | 15 ++-- crates/vm/src/stdlib/builtins.rs | 8 +- crates/vm/src/stdlib/itertools.rs | 4 +- 13 files changed, 141 insertions(+), 129 deletions(-) diff --git a/crates/stdlib/src/array.rs b/crates/stdlib/src/array.rs index b7a6fbd8b4f..4746882e462 100644 --- a/crates/stdlib/src/array.rs +++ b/crates/stdlib/src/array.rs @@ -565,11 +565,11 @@ mod array { } fn f32_try_into_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<f32> { - ArgIntoFloat::try_from_object(vm, obj).map(|x| *x as f32) + ArgIntoFloat::try_from_object(vm, obj).map(|x| x.into_float() as f32) } fn f64_try_into_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<f64> { - ArgIntoFloat::try_from_object(vm, obj).map(Into::into) + ArgIntoFloat::try_from_object(vm, obj).map(|x| x.into_float()) } fn pyfloat_from_f32(value: f32) -> PyFloat { diff --git a/crates/stdlib/src/bisect.rs b/crates/stdlib/src/bisect.rs index 46e689ac068..e712d0275d2 100644 --- a/crates/stdlib/src/bisect.rs +++ b/crates/stdlib/src/bisect.rs @@ -24,7 +24,7 @@ mod _bisect { #[inline] fn handle_default(arg: OptionalArg<ArgIndex>, vm: &VirtualMachine) -> PyResult<Option<isize>> { arg.into_option() - .map(|v| v.try_to_primitive(vm)) + .map(|v| v.into_int_ref().try_to_primitive(vm)) .transpose() } diff --git a/crates/stdlib/src/cmath.rs b/crates/stdlib/src/cmath.rs index 7f975e41719..6ce471195cc 100644 --- a/crates/stdlib/src/cmath.rs +++ b/crates/stdlib/src/cmath.rs @@ -23,64 +23,64 @@ mod cmath { #[pyfunction] fn phase(z: ArgIntoComplex) -> f64 { - z.arg() + z.into_complex().arg() } #[pyfunction] fn polar(x: ArgIntoComplex) -> (f64, f64) { - x.to_polar() + x.into_complex().to_polar() } #[pyfunction] fn rect(r: ArgIntoFloat, phi: ArgIntoFloat) -> Complex64 { - Complex64::from_polar(*r, *phi) + Complex64::from_polar(r.into_float(), phi.into_float()) } #[pyfunction] fn isinf(z: ArgIntoComplex) -> bool { - let Complex64 { re, im } = *z; + let Complex64 { re, im } = z.into_complex(); re.is_infinite() || im.is_infinite() } #[pyfunction] fn isfinite(z: ArgIntoComplex) -> bool { - z.is_finite() + z.into_complex().is_finite() } #[pyfunction] fn isnan(z: ArgIntoComplex) -> bool { - z.is_nan() + z.into_complex().is_nan() } #[pyfunction] fn exp(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult<Complex64> { - let z = *z; + let z = z.into_complex(); result_or_overflow(z, z.exp(), vm) } #[pyfunction] fn sqrt(z: ArgIntoComplex) -> Complex64 { - z.sqrt() + z.into_complex().sqrt() } #[pyfunction] fn sin(z: ArgIntoComplex) -> Complex64 { - z.sin() + z.into_complex().sin() } #[pyfunction] fn asin(z: ArgIntoComplex) -> Complex64 { - z.asin() + z.into_complex().asin() } #[pyfunction] fn cos(z: ArgIntoComplex) -> Complex64 { - z.cos() + z.into_complex().cos() } #[pyfunction] fn acos(z: ArgIntoComplex) -> Complex64 { - z.acos() + z.into_complex().acos() } #[pyfunction] @@ -90,56 +90,56 @@ mod cmath { // which returns NaN when base is negative. // log10(z) / log10(base) yields correct results but division // doesn't handle pos/neg zero nicely. (i.e log(1, 0.5)) - z.log( + z.into_complex().log( base.into_option() - .map(|base| base.re) + .map(|base| base.into_complex().re) .unwrap_or(core::f64::consts::E), ) } #[pyfunction] fn log10(z: ArgIntoComplex) -> Complex64 { - z.log(10.0) + z.into_complex().log(10.0) } #[pyfunction] fn acosh(z: ArgIntoComplex) -> Complex64 { - z.acosh() + z.into_complex().acosh() } #[pyfunction] fn atan(z: ArgIntoComplex) -> Complex64 { - z.atan() + z.into_complex().atan() } #[pyfunction] fn atanh(z: ArgIntoComplex) -> Complex64 { - z.atanh() + z.into_complex().atanh() } #[pyfunction] fn tan(z: ArgIntoComplex) -> Complex64 { - z.tan() + z.into_complex().tan() } #[pyfunction] fn tanh(z: ArgIntoComplex) -> Complex64 { - z.tanh() + z.into_complex().tanh() } #[pyfunction] fn sinh(z: ArgIntoComplex) -> Complex64 { - z.sinh() + z.into_complex().sinh() } #[pyfunction] fn cosh(z: ArgIntoComplex) -> Complex64 { - z.cosh() + z.into_complex().cosh() } #[pyfunction] fn asinh(z: ArgIntoComplex) -> Complex64 { - z.asinh() + z.into_complex().asinh() } #[derive(FromArgs)] @@ -156,10 +156,10 @@ mod cmath { #[pyfunction] fn isclose(args: IsCloseArgs, vm: &VirtualMachine) -> PyResult<bool> { - let a = *args.a; - let b = *args.b; - let rel_tol = args.rel_tol.map_or(1e-09, Into::into); - let abs_tol = args.abs_tol.map_or(0.0, Into::into); + let a = args.a.into_complex(); + let b = args.b.into_complex(); + let rel_tol = args.rel_tol.map_or(1e-09, |v| v.into_float()); + let abs_tol = args.abs_tol.map_or(0.0, |v| v.into_float()); if rel_tol < 0.0 || abs_tol < 0.0 { return Err(vm.new_value_error("tolerances must be non-negative")); diff --git a/crates/stdlib/src/math.rs b/crates/stdlib/src/math.rs index 6e139530804..fb8945c74f7 100644 --- a/crates/stdlib/src/math.rs +++ b/crates/stdlib/src/math.rs @@ -29,7 +29,7 @@ mod math { // Helper macro: macro_rules! call_math_func { ( $fun:ident, $name:ident, $vm:ident ) => {{ - let value = *$name; + let value = $name.into_float(); let result = value.$fun(); result_or_overflow(value, result, $vm) }}; @@ -54,17 +54,17 @@ mod math { #[pyfunction] fn isfinite(x: ArgIntoFloat) -> bool { - x.is_finite() + x.into_float().is_finite() } #[pyfunction] fn isinf(x: ArgIntoFloat) -> bool { - x.is_infinite() + x.into_float().is_infinite() } #[pyfunction] fn isnan(x: ArgIntoFloat) -> bool { - x.is_nan() + x.into_float().is_nan() } #[derive(FromArgs)] @@ -81,10 +81,10 @@ mod math { #[pyfunction] fn isclose(args: IsCloseArgs, vm: &VirtualMachine) -> PyResult<bool> { - let a = *args.a; - let b = *args.b; - let rel_tol = args.rel_tol.map_or(1e-09, |value| value.into()); - let abs_tol = args.abs_tol.map_or(0.0, |value| value.into()); + let a = args.a.into_float(); + let b = args.b.into_float(); + let rel_tol = args.rel_tol.map_or(1e-09, |v| v.into_float()); + let abs_tol = args.abs_tol.map_or(0.0, |v| v.into_float()); if rel_tol < 0.0 || abs_tol < 0.0 { return Err(vm.new_value_error("tolerances must be non-negative")); @@ -115,7 +115,7 @@ mod math { #[pyfunction] fn copysign(x: ArgIntoFloat, y: ArgIntoFloat) -> f64 { - x.copysign(*y) + x.into_float().copysign(y.into_float()) } // Power and logarithmic functions: @@ -136,7 +136,7 @@ mod math { #[pyfunction] fn log(x: PyObjectRef, base: OptionalArg<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<f64> { - let base = base.map(|b| *b).unwrap_or(core::f64::consts::E); + let base: f64 = base.map(Into::into).unwrap_or(core::f64::consts::E); if base.is_sign_negative() { return Err(vm.new_value_error("math domain error")); } @@ -145,7 +145,7 @@ mod math { #[pyfunction] fn log1p(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { - let x = *x; + let x = x.into_float(); if x.is_nan() || x > -1.0_f64 { Ok(x.ln_1p()) } else { @@ -197,8 +197,8 @@ mod math { #[pyfunction] fn pow(x: ArgIntoFloat, y: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { - let x = *x; - let y = *y; + let x = x.into_float(); + let y = y.into_float(); if x < 0.0 && x.is_finite() && y.fract() != 0.0 && y.is_finite() || x == 0.0 && y < 0.0 && y != f64::NEG_INFINITY @@ -217,7 +217,7 @@ mod math { #[pyfunction] fn sqrt(value: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { - let value = *value; + let value = value.into_float(); if value.is_nan() { return Ok(value); } @@ -232,6 +232,7 @@ mod math { #[pyfunction] fn isqrt(x: ArgIndex, vm: &VirtualMachine) -> PyResult<BigInt> { + let x = x.into_int_ref(); let value = x.as_bigint(); if value.is_negative() { @@ -243,7 +244,7 @@ mod math { // Trigonometric functions: #[pyfunction] fn acos(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { - let x = *x; + let x = x.into_float(); if x.is_nan() || (-1.0_f64..=1.0_f64).contains(&x) { Ok(x.acos()) } else { @@ -253,7 +254,7 @@ mod math { #[pyfunction] fn asin(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { - let x = *x; + let x = x.into_float(); if x.is_nan() || (-1.0_f64..=1.0_f64).contains(&x) { Ok(x.asin()) } else { @@ -268,15 +269,16 @@ mod math { #[pyfunction] fn atan2(y: ArgIntoFloat, x: ArgIntoFloat) -> f64 { - y.atan2(*x) + y.into_float().atan2(x.into()) } #[pyfunction] fn cos(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { + let x = x.into_float(); if x.is_infinite() { return Err(vm.new_value_error("math domain error")); } - call_math_func!(cos, x, vm) + result_or_overflow(x, x.cos(), vm) } #[pyfunction] @@ -408,35 +410,37 @@ mod math { #[pyfunction] fn sin(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { + let x = x.into_float(); if x.is_infinite() { return Err(vm.new_value_error("math domain error")); } - call_math_func!(sin, x, vm) + result_or_overflow(x, x.sin(), vm) } #[pyfunction] fn tan(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { + let x = x.into_float(); if x.is_infinite() { return Err(vm.new_value_error("math domain error")); } - call_math_func!(tan, x, vm) + result_or_overflow(x, x.tan(), vm) } #[pyfunction] fn degrees(x: ArgIntoFloat) -> f64 { - *x * (180.0 / core::f64::consts::PI) + x.into_float() * (180.0 / core::f64::consts::PI) } #[pyfunction] fn radians(x: ArgIntoFloat) -> f64 { - *x * (core::f64::consts::PI / 180.0) + x.into_float() * (core::f64::consts::PI / 180.0) } // Hyperbolic functions: #[pyfunction] fn acosh(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { - let x = *x; + let x = x.into_float(); if x.is_sign_negative() || x.is_zero() { Err(vm.new_value_error("math domain error")) } else { @@ -451,7 +455,7 @@ mod math { #[pyfunction] fn atanh(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { - let x = *x; + let x = x.into_float(); if x >= 1.0_f64 || x <= -1.0_f64 { Err(vm.new_value_error("math domain error")) } else { @@ -477,22 +481,22 @@ mod math { // Special functions: #[pyfunction] fn erf(x: ArgIntoFloat) -> f64 { - pymath::erf(*x) + pymath::erf(x.into()) } #[pyfunction] fn erfc(x: ArgIntoFloat) -> f64 { - pymath::erfc(*x) + pymath::erfc(x.into()) } #[pyfunction] fn gamma(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { - pymath::gamma(*x).map_err(|err| pymath_error_to_exception(err, vm)) + pymath::gamma(x.into()).map_err(|err| pymath_error_to_exception(err, vm)) } #[pyfunction] fn lgamma(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { - pymath::lgamma(*x).map_err(|err| pymath_error_to_exception(err, vm)) + pymath::lgamma(x.into()).map_err(|err| pymath_error_to_exception(err, vm)) } fn try_magic_method( @@ -541,7 +545,7 @@ mod math { #[pyfunction] fn frexp(x: ArgIntoFloat) -> (f64, i32) { - let value = *x; + let value: f64 = x.into(); if value.is_finite() { let (m, exp) = float_ops::decompose_float(value); (m * value.signum(), exp) @@ -641,12 +645,12 @@ mod math { if arg_vec.is_empty() { return default; } else if arg_vec.len() == 1 { - return op(arg_vec[0].as_bigint(), &arg_vec[0]); + return op(arg_vec[0].as_ref().as_bigint(), arg_vec[0].as_ref()); } - let mut res = arg_vec[0].as_bigint().clone(); + let mut res = arg_vec[0].as_ref().as_bigint().clone(); for num in &arg_vec[1..] { - res = op(&res, num) + res = op(&res, num.as_ref()) } res } @@ -665,7 +669,7 @@ mod math { #[pyfunction] fn cbrt(x: ArgIntoFloat) -> f64 { - x.cbrt() + x.into_float().cbrt() } #[pyfunction] @@ -675,7 +679,7 @@ mod math { let mut inf_sum = 0.0; for obj in seq.iter(vm)? { - let mut x = *obj?; + let mut x = obj?.into_float(); let xsave = x; let mut i = 0; @@ -790,11 +794,12 @@ mod math { k: OptionalArg<Option<ArgIndex>>, vm: &VirtualMachine, ) -> PyResult<BigInt> { + let n = n.into_int_ref(); let n = n.as_bigint(); let k_ref; let v = match k.flatten() { Some(k) => { - k_ref = k; + k_ref = k.into_int_ref(); k_ref.as_bigint() } None => n, @@ -818,7 +823,9 @@ mod math { #[pyfunction] fn comb(n: ArgIndex, k: ArgIndex, vm: &VirtualMachine) -> PyResult<BigInt> { + let k = k.into_int_ref(); let mut k = k.as_bigint(); + let n = n.into_int_ref(); let n = n.as_bigint(); let one = BigInt::one(); let zero = BigInt::zero(); @@ -856,7 +863,7 @@ mod math { #[pyfunction] fn modf(x: ArgIntoFloat) -> (f64, f64) { - let x = *x; + let x = x.into_float(); if !x.is_finite() { if x.is_infinite() { return (0.0_f64.copysign(x), x); @@ -882,27 +889,25 @@ mod math { fn nextafter(arg: NextAfterArgs, vm: &VirtualMachine) -> PyResult<f64> { let steps: Option<i64> = arg .steps - .map(|v| v.try_to_primitive(vm)) + .map(|v| v.into_int_ref().try_to_primitive(vm)) .transpose()? .into_option(); + let x: f64 = arg.x.into(); + let y: f64 = arg.y.into(); match steps { Some(steps) => { if steps < 0 { return Err(vm.new_value_error("steps must be a non-negative integer")); } - Ok(float_ops::nextafter_with_steps( - *arg.x, - *arg.y, - steps as u64, - )) + Ok(float_ops::nextafter_with_steps(x, y, steps as u64)) } - None => Ok(float_ops::nextafter(*arg.x, *arg.y)), + None => Ok(float_ops::nextafter(x, y)), } } #[pyfunction] fn ulp(x: ArgIntoFloat) -> f64 { - float_ops::ulp(*x) + float_ops::ulp(x.into()) } fn fmod(x: f64, y: f64) -> f64 { @@ -915,8 +920,8 @@ mod math { #[pyfunction(name = "fmod")] fn py_fmod(x: ArgIntoFloat, y: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { - let x = *x; - let y = *y; + let x = x.into_float(); + let y = y.into_float(); let r = fmod(x, y); @@ -929,8 +934,8 @@ mod math { #[pyfunction] fn remainder(x: ArgIntoFloat, y: ArgIntoFloat, vm: &VirtualMachine) -> PyResult<f64> { - let x = *x; - let y = *y; + let x = x.into_float(); + let y = y.into_float(); if x.is_finite() && y.is_finite() { if y == 0.0 { @@ -1025,7 +1030,10 @@ mod math { z: ArgIntoFloat, vm: &VirtualMachine, ) -> PyResult<f64> { - let result = (*x).mul_add(*y, *z); + let x = x.into_float(); + let y = y.into_float(); + let z = z.into_float(); + let result = x.mul_add(y, z); if result.is_finite() { return Ok(result); diff --git a/crates/stdlib/src/statistics.rs b/crates/stdlib/src/statistics.rs index 9f5b294c009..8be2447ffd6 100644 --- a/crates/stdlib/src/statistics.rs +++ b/crates/stdlib/src/statistics.rs @@ -126,7 +126,7 @@ mod _statistics { sigma: ArgIntoFloat, vm: &VirtualMachine, ) -> PyResult<f64> { - normal_dist_inv_cdf(*p, *mu, *sigma) + normal_dist_inv_cdf(p.into_float(), mu.into_float(), sigma.into_float()) .ok_or_else(|| vm.new_value_error("inv_cdf undefined for these parameters")) } } diff --git a/crates/vm/src/buffer.rs b/crates/vm/src/buffer.rs index 3d5e48015ea..6cbddb7333b 100644 --- a/crates/vm/src/buffer.rs +++ b/crates/vm/src/buffer.rs @@ -603,7 +603,7 @@ macro_rules! make_pack_float { arg: PyObjectRef, data: &mut [u8], ) -> PyResult<()> { - let f = *ArgIntoFloat::try_from_object(vm, arg)? as $T; + let f = ArgIntoFloat::try_from_object(vm, arg)?.into_float() as $T; f.to_bits().pack_int::<E>(data); Ok(()) } @@ -621,7 +621,7 @@ make_pack_float!(f64); impl Packable for f16 { fn pack<E: ByteOrder>(vm: &VirtualMachine, arg: PyObjectRef, data: &mut [u8]) -> PyResult<()> { - let f_64 = *ArgIntoFloat::try_from_object(vm, arg)?; + let f_64 = ArgIntoFloat::try_from_object(vm, arg)?.into_float(); // "from_f64 should be preferred in any non-`const` context" except it gives the wrong result :/ let f_16 = Self::from_f64_const(f_64); if f_16.is_infinite() != f_64.is_infinite() { @@ -649,7 +649,7 @@ impl Packable for *mut raw::c_void { impl Packable for bool { fn pack<E: ByteOrder>(vm: &VirtualMachine, arg: PyObjectRef, data: &mut [u8]) -> PyResult<()> { - let v = *ArgIntoBool::try_from_object(vm, arg)? as u8; + let v = ArgIntoBool::try_from_object(vm, arg)?.into_bool() as u8; v.pack_int::<E>(data); Ok(()) } diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index 7e5b98b1ece..df1acedfd63 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -515,7 +515,7 @@ impl PyBytes { #[pymethod(name = "__rmul__")] #[pymethod] fn __mul__(zelf: PyRef<Self>, value: ArgIndex, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { - zelf.repeat(value.try_to_primitive(vm)?, vm) + zelf.repeat(value.into_int_ref().try_to_primitive(vm)?, vm) } #[pymethod(name = "__mod__")] diff --git a/crates/vm/src/builtins/mappingproxy.rs b/crates/vm/src/builtins/mappingproxy.rs index 34a598b03e0..ed4e6367b66 100644 --- a/crates/vm/src/builtins/mappingproxy.rs +++ b/crates/vm/src/builtins/mappingproxy.rs @@ -122,7 +122,9 @@ impl PyMappingProxy { MappingProxyInner::Class(class) => Ok(key .as_interned_str(vm) .is_some_and(|key| class.attributes.read().contains_key(key))), - MappingProxyInner::Mapping(mapping) => mapping.sequence_unchecked().contains(key, vm), + MappingProxyInner::Mapping(mapping) => { + mapping.obj().sequence_unchecked().contains(key, vm) + } } } @@ -161,7 +163,9 @@ impl PyMappingProxy { #[pymethod] pub fn copy(&self, vm: &VirtualMachine) -> PyResult { match &self.mapping { - MappingProxyInner::Mapping(d) => vm.call_method(d, identifier!(vm, copy).as_str(), ()), + MappingProxyInner::Mapping(d) => { + vm.call_method(d.obj(), identifier!(vm, copy).as_str(), ()) + } MappingProxyInner::Class(c) => { Ok(PyDict::from_attributes(c.attributes.read().clone(), vm)?.to_pyobject(vm)) } diff --git a/crates/vm/src/builtins/slice.rs b/crates/vm/src/builtins/slice.rs index 09a6c462ed5..3aa23b0746c 100644 --- a/crates/vm/src/builtins/slice.rs +++ b/crates/vm/src/builtins/slice.rs @@ -174,6 +174,7 @@ impl PySlice { #[pymethod] fn indices(&self, length: ArgIndex, vm: &VirtualMachine) -> PyResult<PyTupleRef> { + let length = length.into_int_ref(); let length = length.as_bigint(); if length.is_negative() { return Err(vm.new_value_error("length should not be negative.")); diff --git a/crates/vm/src/function/number.rs b/crates/vm/src/function/number.rs index fb872cc48fd..b53208bcd93 100644 --- a/crates/vm/src/function/number.rs +++ b/crates/vm/src/function/number.rs @@ -20,17 +20,16 @@ pub struct ArgIntoComplex { value: Complex64, } -impl From<ArgIntoComplex> for Complex64 { - fn from(arg: ArgIntoComplex) -> Self { - arg.value +impl ArgIntoComplex { + #[inline] + pub fn into_complex(self) -> Complex64 { + self.value } } -impl Deref for ArgIntoComplex { - type Target = Complex64; - - fn deref(&self) -> &Self::Target { - &self.value +impl From<ArgIntoComplex> for Complex64 { + fn from(arg: ArgIntoComplex) -> Self { + arg.value } } @@ -60,6 +59,11 @@ pub struct ArgIntoFloat { } impl ArgIntoFloat { + #[inline] + pub fn into_float(self) -> f64 { + self.value + } + pub fn vec_into_f64(v: Vec<Self>) -> Vec<f64> { // TODO: Vec::into_raw_parts once stabilized let mut v = core::mem::ManuallyDrop::new(v); @@ -75,13 +79,6 @@ impl From<ArgIntoFloat> for f64 { } } -impl Deref for ArgIntoFloat { - type Target = f64; - fn deref(&self) -> &Self::Target { - &self.value - } -} - impl TryFromObject for ArgIntoFloat { // Equivalent to PyFloat_AsDouble. fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { @@ -106,6 +103,11 @@ pub struct ArgIntoBool { impl ArgIntoBool { pub const TRUE: Self = Self { value: true }; pub const FALSE: Self = Self { value: false }; + + #[inline] + pub fn into_bool(self) -> bool { + self.value + } } impl From<ArgIntoBool> for bool { @@ -114,13 +116,6 @@ impl From<ArgIntoBool> for bool { } } -impl Deref for ArgIntoBool { - type Target = bool; - fn deref(&self) -> &Self::Target { - &self.value - } -} - impl TryFromObject for ArgIntoBool { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { Ok(Self { @@ -136,20 +131,25 @@ pub struct ArgIndex { value: PyIntRef, } -impl From<ArgIndex> for PyIntRef { - fn from(arg: ArgIndex) -> Self { - arg.value +impl ArgIndex { + #[inline] + pub fn into_int_ref(self) -> PyIntRef { + self.value } } -impl Deref for ArgIndex { - type Target = PyIntRef; - - fn deref(&self) -> &Self::Target { +impl AsRef<PyIntRef> for ArgIndex { + fn as_ref(&self) -> &PyIntRef { &self.value } } +impl From<ArgIndex> for PyIntRef { + fn from(arg: ArgIndex) -> Self { + arg.value + } +} + impl TryFromObject for ArgIndex { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> { Ok(Self { diff --git a/crates/vm/src/function/protocol.rs b/crates/vm/src/function/protocol.rs index 94bdd3027eb..402f6d0365b 100644 --- a/crates/vm/src/function/protocol.rs +++ b/crates/vm/src/function/protocol.rs @@ -7,7 +7,7 @@ use crate::{ protocol::{PyIter, PyIterIter, PyMapping}, types::GenericMethod, }; -use core::{borrow::Borrow, marker::PhantomData, ops::Deref}; +use core::{borrow::Borrow, marker::PhantomData}; #[derive(Clone, Traverse)] pub struct ArgCallable { @@ -133,6 +133,11 @@ impl ArgMapping { Self { obj: dict.into() } } + #[inline(always)] + pub fn obj(&self) -> &PyObject { + &self.obj + } + #[inline(always)] pub fn mapping(&self) -> PyMapping<'_> { self.obj.mapping_unchecked() @@ -153,14 +158,6 @@ impl AsRef<PyObject> for ArgMapping { } } -impl Deref for ArgMapping { - type Target = PyObject; - #[inline(always)] - fn deref(&self) -> &PyObject { - &self.obj - } -} - impl From<ArgMapping> for PyObjectRef { #[inline(always)] fn from(value: ArgMapping) -> Self { diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index c82161fc553..b4554bc30aa 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -45,7 +45,7 @@ mod builtins { #[pyfunction] fn all(iterable: ArgIterable<ArgIntoBool>, vm: &VirtualMachine) -> PyResult<bool> { for item in iterable.iter(vm)? { - if !*item? { + if !item?.into_bool() { return Ok(false); } } @@ -55,7 +55,7 @@ mod builtins { #[pyfunction] fn any(iterable: ArgIterable<ArgIntoBool>, vm: &VirtualMachine) -> PyResult<bool> { for item in iterable.iter(vm)? { - if *item? { + if item?.into_bool() { return Ok(true); } } @@ -451,6 +451,7 @@ mod builtins { #[pyfunction] fn hex(number: ArgIndex) -> String { + let number = number.into_int_ref(); let n = number.as_bigint(); format!("{n:#x}") } @@ -687,6 +688,7 @@ mod builtins { #[pyfunction] fn oct(number: ArgIndex, vm: &VirtualMachine) -> PyResult { + let number = number.into_int_ref(); let n = number.as_bigint(); let s = if n.is_negative() { format!("-0o{:o}", n.abs()) @@ -786,7 +788,7 @@ mod builtins { .unwrap_or_else(|| PyStr::from("\n").into_ref(&vm.ctx)); write(end)?; - if *options.flush { + if options.flush.into() { vm.call_method(&file, "flush", ())?; } diff --git a/crates/vm/src/stdlib/itertools.rs b/crates/vm/src/stdlib/itertools.rs index 3aad2f91931..1eedbde7a22 100644 --- a/crates/vm/src/stdlib/itertools.rs +++ b/crates/vm/src/stdlib/itertools.rs @@ -561,7 +561,7 @@ mod decl { vm: &VirtualMachine, ) -> PyResult<()> { if let Ok(obj) = ArgIntoBool::try_from_object(vm, state) { - zelf.stop_flag.store(*obj); + zelf.stop_flag.store(obj.into_bool()); } Ok(()) } @@ -648,7 +648,7 @@ mod decl { vm: &VirtualMachine, ) -> PyResult<()> { if let Ok(obj) = ArgIntoBool::try_from_object(vm, state) { - zelf.start_flag.store(*obj); + zelf.start_flag.store(obj.into_bool()); } Ok(()) } From b19312b403ab3a6fbf73e32d5d2e8e54b79cd89e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 1 Jan 2026 22:39:16 +0900 Subject: [PATCH 664/819] slot for numeric ops (#6619) * fix sequence_repeat wrappers * slot for numeric ops * fix bpo-37619 --- crates/stdlib/src/array.rs | 5 --- crates/vm/src/builtins/bytearray.rs | 15 +------ crates/vm/src/builtins/bytes.rs | 13 +----- crates/vm/src/builtins/descriptor.rs | 15 +++++-- crates/vm/src/builtins/dict.rs | 11 ----- crates/vm/src/builtins/genericalias.rs | 2 - crates/vm/src/builtins/list.rs | 5 --- crates/vm/src/builtins/mappingproxy.rs | 3 -- crates/vm/src/builtins/str.rs | 13 +----- crates/vm/src/builtins/tuple.rs | 3 -- crates/vm/src/builtins/type.rs | 2 - crates/vm/src/builtins/union.rs | 2 - crates/vm/src/stdlib/collections.rs | 4 -- crates/vm/src/stdlib/ctypes/pointer.rs | 1 - crates/vm/src/stdlib/ctypes/simple.rs | 1 - crates/vm/src/stdlib/ctypes/structure.rs | 1 - crates/vm/src/types/slot.rs | 54 ++++++++++++++++++------ 17 files changed, 60 insertions(+), 90 deletions(-) diff --git a/crates/stdlib/src/array.rs b/crates/stdlib/src/array.rs index 4746882e462..878a6ce8fb7 100644 --- a/crates/stdlib/src/array.rs +++ b/crates/stdlib/src/array.rs @@ -1060,7 +1060,6 @@ mod array { self.delitem_inner(&needle, vm) } - #[pymethod] fn __add__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { if let Some(other) = other.downcast_ref::<Self>() { self.read() @@ -1074,7 +1073,6 @@ mod array { } } - #[pymethod] fn __iadd__( zelf: PyRef<Self>, other: PyObjectRef, @@ -1093,15 +1091,12 @@ mod array { Ok(zelf) } - #[pymethod(name = "__rmul__")] - #[pymethod] fn __mul__(&self, value: isize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { self.read() .mul(value, vm) .map(|x| Self::from(x).into_ref(&vm.ctx)) } - #[pymethod] fn __imul__(zelf: PyRef<Self>, value: isize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { zelf.try_resizable(vm)?.imul(value, vm)?; Ok(zelf) diff --git a/crates/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs index dc5ee100acf..d3f1b0bd4e8 100644 --- a/crates/vm/src/builtins/bytearray.rs +++ b/crates/vm/src/builtins/bytearray.rs @@ -215,7 +215,6 @@ impl PyByteArray { size_of::<Self>() + self.borrow_buf().len() * size_of::<u8>() } - #[pymethod] fn __add__(&self, other: ArgBytesLike) -> Self { self.inner().add(&other.borrow_buf()).into() } @@ -229,7 +228,6 @@ impl PyByteArray { self.inner().contains(needle, vm) } - #[pymethod] fn __iadd__( zelf: PyRef<Self>, other: ArgBytesLike, @@ -524,29 +522,20 @@ impl PyByteArray { self.inner().title().into() } - #[pymethod(name = "__rmul__")] - #[pymethod] fn __mul__(&self, value: ArgSize, vm: &VirtualMachine) -> PyResult<Self> { self.repeat(value.into(), vm) } - #[pymethod] fn __imul__(zelf: PyRef<Self>, value: ArgSize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { Self::irepeat(&zelf, value.into(), vm)?; Ok(zelf) } - #[pymethod(name = "__mod__")] - fn mod_(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> { + fn __mod__(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> { let formatted = self.inner().cformat(values, vm)?; Ok(formatted.into()) } - #[pymethod] - fn __rmod__(&self, _values: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - vm.ctx.not_implemented() - } - #[pymethod] fn reverse(&self) { self.borrow_buf_mut().reverse(); @@ -822,7 +811,7 @@ impl AsNumber for PyByteArray { static AS_NUMBER: PyNumberMethods = PyNumberMethods { remainder: Some(|a, b, vm| { if let Some(a) = a.downcast_ref::<PyByteArray>() { - a.mod_(b.to_owned(), vm).to_pyresult(vm) + a.__mod__(b.to_owned(), vm).to_pyresult(vm) } else { Ok(vm.ctx.not_implemented()) } diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index df1acedfd63..537a618be3b 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -224,7 +224,6 @@ impl PyBytes { size_of::<Self>() + self.len() * size_of::<u8>() } - #[pymethod] fn __add__(&self, other: ArgBytesLike) -> Vec<u8> { self.inner.add(&other.borrow_buf()) } @@ -512,23 +511,15 @@ impl PyBytes { self.inner.title().into() } - #[pymethod(name = "__rmul__")] - #[pymethod] fn __mul__(zelf: PyRef<Self>, value: ArgIndex, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { zelf.repeat(value.into_int_ref().try_to_primitive(vm)?, vm) } - #[pymethod(name = "__mod__")] - fn mod_(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> { + fn __mod__(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> { let formatted = self.inner.cformat(values, vm)?; Ok(formatted.into()) } - #[pymethod] - fn __rmod__(&self, _values: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - vm.ctx.not_implemented() - } - #[pymethod] fn __getnewargs__(&self, vm: &VirtualMachine) -> PyTupleRef { let param: Vec<PyObjectRef> = self.elements().map(|x| x.to_pyobject(vm)).collect(); @@ -677,7 +668,7 @@ impl AsNumber for PyBytes { static AS_NUMBER: PyNumberMethods = PyNumberMethods { remainder: Some(|a, b, vm| { if let Some(a) = a.downcast_ref::<PyBytes>() { - a.mod_(b.to_owned(), vm).to_pyresult(vm) + a.__mod__(b.to_owned(), vm).to_pyresult(vm) } else { Ok(vm.ctx.not_implemented()) } diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index aa9da6e2d44..a9218c8d689 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -5,7 +5,7 @@ use crate::{ class::PyClassImpl, common::hash::PyHash, convert::{ToPyObject, ToPyResult}, - function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue}, + function::{ArgSize, FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue}, protocol::{PyNumberBinaryFunc, PyNumberTernaryFunc, PyNumberUnaryFunc}, types::{ Callable, Comparable, DelFunc, DescrGetFunc, DescrSetFunc, GenericMethod, GetDescriptor, @@ -593,8 +593,8 @@ impl SlotFunc { func(obj.sequence_unchecked(), &other, vm) } SlotFunc::SeqRepeat(func) => { - let (n,): (isize,) = args.bind(vm)?; - func(obj.sequence_unchecked(), n, vm) + let (n,): (ArgSize,) = args.bind(vm)?; + func(obj.sequence_unchecked(), n.into(), vm) } SlotFunc::SeqItem(func) => { let (index,): (isize,) = args.bind(vm)?; @@ -774,6 +774,15 @@ impl Callable for PyMethodWrapper { type Args = FuncArgs; fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // bpo-37619: Check type compatibility before calling wrapped slot + if !zelf.obj.fast_isinstance(zelf.wrapper.typ) { + return Err(vm.new_type_error(format!( + "descriptor '{}' requires a '{}' object but received a '{}'", + zelf.wrapper.name.as_str(), + zelf.wrapper.typ.name(), + zelf.obj.class().name() + ))); + } zelf.wrapper.wrapped.call(zelf.obj.clone(), args, vm) } } diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 693505b1f82..41f6779c212 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -293,7 +293,6 @@ impl PyDict { Ok(()) } - #[pymethod] fn __or__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let other_dict: Result<PyDictRef, _> = other.downcast(); if let Ok(other) = other_dict { @@ -403,13 +402,11 @@ impl PyRef<PyDict> { PyDictReverseKeyIterator::new(self) } - #[pymethod] fn __ior__(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> { self.merge_object(other, vm)?; Ok(self) } - #[pymethod] fn __ror__(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let other_dict: Result<Self, _> = other.downcast(); if let Ok(other) = other_dict { @@ -1046,38 +1043,30 @@ trait ViewSetOps: DictView { PySetInner::from_iter(iter, vm) } - #[pymethod(name = "__rxor__")] - #[pymethod] fn __xor__(zelf: PyRef<Self>, other: ArgIterable, vm: &VirtualMachine) -> PyResult<PySet> { let zelf = Self::to_set(zelf, vm)?; let inner = zelf.symmetric_difference(other, vm)?; Ok(PySet { inner }) } - #[pymethod(name = "__rand__")] - #[pymethod] fn __and__(zelf: PyRef<Self>, other: ArgIterable, vm: &VirtualMachine) -> PyResult<PySet> { let zelf = Self::to_set(zelf, vm)?; let inner = zelf.intersection(other, vm)?; Ok(PySet { inner }) } - #[pymethod(name = "__ror__")] - #[pymethod] fn __or__(zelf: PyRef<Self>, other: ArgIterable, vm: &VirtualMachine) -> PyResult<PySet> { let zelf = Self::to_set(zelf, vm)?; let inner = zelf.union(other, vm)?; Ok(PySet { inner }) } - #[pymethod] fn __sub__(zelf: PyRef<Self>, other: ArgIterable, vm: &VirtualMachine) -> PyResult<PySet> { let zelf = Self::to_set(zelf, vm)?; let inner = zelf.difference(other, vm)?; Ok(PySet { inner }) } - #[pymethod] fn __rsub__(zelf: PyRef<Self>, other: ArgIterable, vm: &VirtualMachine) -> PyResult<PySet> { let left = PySetInner::from_iter(other.iter(vm)?, vm)?; let right = ArgIterable::try_from_object(vm, Self::iter(zelf, vm)?)?; diff --git a/crates/vm/src/builtins/genericalias.rs b/crates/vm/src/builtins/genericalias.rs index 5596aca9da2..99c1eacc3ec 100644 --- a/crates/vm/src/builtins/genericalias.rs +++ b/crates/vm/src/builtins/genericalias.rs @@ -236,12 +236,10 @@ impl PyGenericAlias { Err(vm.new_type_error("issubclass() argument 2 cannot be a parameterized generic")) } - #[pymethod] fn __ror__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { type_::or_(other, zelf, vm) } - #[pymethod] fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { type_::or_(zelf, other, vm) } diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index 52df0756498..514b38b6c28 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -145,7 +145,6 @@ impl PyList { Ok(Self::from(elements).into_ref(&vm.ctx)) } - #[pymethod] fn __add__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { self.concat(&other, vm) } @@ -160,7 +159,6 @@ impl PyList { Ok(zelf.to_owned().into()) } - #[pymethod] fn __iadd__( zelf: PyRef<Self>, other: PyObjectRef, @@ -240,13 +238,10 @@ impl PyList { self._setitem(&needle, value, vm) } - #[pymethod] - #[pymethod(name = "__rmul__")] fn __mul__(&self, n: ArgSize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { self.repeat(n.into(), vm) } - #[pymethod] fn __imul__(zelf: PyRef<Self>, n: ArgSize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { Self::irepeat(zelf, n.into(), vm) } diff --git a/crates/vm/src/builtins/mappingproxy.rs b/crates/vm/src/builtins/mappingproxy.rs index ed4e6367b66..13b0fa32490 100644 --- a/crates/vm/src/builtins/mappingproxy.rs +++ b/crates/vm/src/builtins/mappingproxy.rs @@ -191,7 +191,6 @@ impl PyMappingProxy { ) } - #[pymethod] fn __ior__(&self, _args: PyObjectRef, vm: &VirtualMachine) -> PyResult { Err(vm.new_type_error(format!( r#""'|=' is not supported by {}; use '|' instead""#, @@ -199,8 +198,6 @@ impl PyMappingProxy { ))) } - #[pymethod(name = "__ror__")] - #[pymethod] fn __or__(&self, args: PyObjectRef, vm: &VirtualMachine) -> PyResult { vm._or(self.copy(vm)?.as_ref(), args.as_ref()) } diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 0357e81b365..e101ef2a52b 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -541,7 +541,6 @@ impl Py<PyStr> { ) )] impl PyStr { - #[pymethod] fn __add__(zelf: PyRef<Self>, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if let Some(other) = other.downcast_ref::<Self>() { let bytes = zelf.as_wtf8().py_add(other.as_wtf8()); @@ -636,8 +635,6 @@ impl PyStr { core::mem::size_of::<Self>() + self.byte_len() * core::mem::size_of::<u8>() } - #[pymethod(name = "__rmul__")] - #[pymethod] fn __mul__(zelf: PyRef<Self>, value: ArgSize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { Self::repeat(zelf, value.into(), vm) } @@ -947,16 +944,10 @@ impl PyStr { && self.char_all(|c| GeneralCategory::of(c) == GeneralCategory::DecimalNumber) } - #[pymethod(name = "__mod__")] - fn modulo(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult<Wtf8Buf> { + fn __mod__(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult<Wtf8Buf> { cformat_string(vm, self.as_wtf8(), values) } - #[pymethod] - fn __rmod__(&self, _values: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - vm.ctx.not_implemented() - } - #[pymethod] fn format(&self, args: FuncArgs, vm: &VirtualMachine) -> PyResult<Wtf8Buf> { let format_str = @@ -1564,7 +1555,7 @@ impl AsNumber for PyStr { static AS_NUMBER: PyNumberMethods = PyNumberMethods { remainder: Some(|a, b, vm| { if let Some(a) = a.downcast_ref::<PyStr>() { - a.modulo(b.to_owned(), vm).to_pyresult(vm) + a.__mod__(b.to_owned(), vm).to_pyresult(vm) } else { Ok(vm.ctx.not_implemented()) } diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index 910b0c8a204..f3da8b26163 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -263,7 +263,6 @@ impl<T> PyTuple<PyRef<T>> { with(AsMapping, AsNumber, AsSequence, Hashable, Comparable, Iterable, Constructor, Representable) )] impl PyTuple { - #[pymethod] fn __add__( zelf: PyRef<Self>, other: PyObjectRef, @@ -302,8 +301,6 @@ impl PyTuple { self.elements.len() } - #[pymethod(name = "__rmul__")] - #[pymethod] fn __mul__(zelf: PyRef<Self>, value: ArgSize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { Self::repeat(zelf, value.into(), vm) } diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 178131c96d6..e2e69c7bffc 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -951,12 +951,10 @@ impl PyType { ) } - #[pymethod] pub fn __ror__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { or_(other, zelf, vm) } - #[pymethod] pub fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { or_(zelf, other, vm) } diff --git a/crates/vm/src/builtins/union.rs b/crates/vm/src/builtins/union.rs index 0342442b83d..b5e12dcb3c8 100644 --- a/crates/vm/src/builtins/union.rs +++ b/crates/vm/src/builtins/union.rs @@ -136,8 +136,6 @@ impl PyUnion { } } - #[pymethod(name = "__ror__")] - #[pymethod] fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { type_::or_(zelf, other, vm) } diff --git a/crates/vm/src/stdlib/collections.rs b/crates/vm/src/stdlib/collections.rs index 9b7a78f7237..4688121c9b5 100644 --- a/crates/vm/src/stdlib/collections.rs +++ b/crates/vm/src/stdlib/collections.rs @@ -332,8 +332,6 @@ mod _collections { Ok(deque) } - #[pymethod] - #[pymethod(name = "__rmul__")] fn __mul__(&self, n: isize, vm: &VirtualMachine) -> PyResult<Self> { let deque = self._mul(n, vm)?; Ok(Self { @@ -343,7 +341,6 @@ mod _collections { }) } - #[pymethod] fn __imul__(zelf: PyRef<Self>, n: isize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> { let mul_deque = zelf._mul(n, vm)?; *zelf.borrow_deque_mut() = mul_deque; @@ -379,7 +376,6 @@ mod _collections { } } - #[pymethod] fn __iadd__( zelf: PyRef<Self>, other: PyObjectRef, diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index aad21f8fe45..4f935945e88 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -137,7 +137,6 @@ impl PyCPointerType { ))) } - #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { use super::array::array_type_from_ctype; diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs index 17d3aa17ad1..de13dab2202 100644 --- a/crates/vm/src/stdlib/ctypes/simple.rs +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -488,7 +488,6 @@ impl PyCSimpleType { } } - #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { PyCSimple::repeat(cls, n, vm) } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 732f3c66801..cd36ce85560 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -393,7 +393,6 @@ impl PyCStructType { Ok(()) } - #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { use super::array::array_type_from_ctype; diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 17b2b4e2f8a..2df440b5b9f 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -438,6 +438,16 @@ fn sequence_contains_wrapper( contains_wrapper(seq.obj, needle, vm) } +#[inline(never)] +fn sequence_repeat_wrapper(seq: PySequence<'_>, n: isize, vm: &VirtualMachine) -> PyResult { + vm.call_special_method(seq.obj, identifier!(vm, __mul__), (n,)) +} + +#[inline(never)] +fn sequence_inplace_repeat_wrapper(seq: PySequence<'_>, n: isize, vm: &VirtualMachine) -> PyResult { + vm.call_special_method(seq.obj, identifier!(vm, __imul__), (n,)) +} + fn repr_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> { let ret = vm.call_special_method(zelf, identifier!(vm, __repr__), ())?; ret.downcast::<PyStr>().map_err(|obj| { @@ -1294,12 +1304,16 @@ impl PyType { accessor.inherit_from_mro(self); } } - SlotAccessor::SqRepeat | SlotAccessor::SqInplaceRepeat => { - // Sequence repeat uses sq_repeat slot - no generic wrapper needed - // (handled by number protocol fallback) - if !ADD { - accessor.inherit_from_mro(self); - } + SlotAccessor::SqRepeat => { + update_sub_slot!(as_sequence, repeat, sequence_repeat_wrapper, SeqRepeat) + } + SlotAccessor::SqInplaceRepeat => { + update_sub_slot!( + as_sequence, + inplace_repeat, + sequence_inplace_repeat_wrapper, + SeqRepeat + ) } SlotAccessor::SqItem => { update_sub_slot!(as_sequence, item, sequence_getitem_wrapper, SeqItem) @@ -1348,28 +1362,44 @@ impl PyType { ) -> Option<T> { use crate::builtins::descriptor::PyWrapper; + // Helper to check if a class is a subclass of another by checking MRO + let is_subclass_of = |subclass_mro: &[PyRef<PyType>], superclass: &Py<PyType>| -> bool { + subclass_mro.iter().any(|c| c.is(superclass)) + }; + // Helper to extract slot from an attribute if it's a wrapper descriptor - let try_extract = |attr: &PyObjectRef| -> Option<T> { + // and the wrapper's type is compatible with the given class. + // bpo-37619: wrapper descriptor from wrong class should not be used directly. + let try_extract = |attr: &PyObjectRef, for_class_mro: &[PyRef<PyType>]| -> Option<T> { if attr.class().is(ctx.types.wrapper_descriptor_type) { - attr.downcast_ref::<PyWrapper>() - .and_then(|wrapper| extract(&wrapper.wrapped)) + attr.downcast_ref::<PyWrapper>().and_then(|wrapper| { + // Only extract slot if for_class is a subclass of wrapper.typ + if is_subclass_of(for_class_mro, wrapper.typ) { + extract(&wrapper.wrapped) + } else { + None + } + }) } else { None } }; + let mro = self.mro.read(); + // Look up in self's dict first if let Some(attr) = self.attributes.read().get(name).cloned() { - if let Some(func) = try_extract(&attr) { + if let Some(func) = try_extract(&attr, &mro) { return Some(func); } return None; } // Look up in MRO (mro[0] is self, so skip it) - for cls in self.mro.read()[1..].iter() { + for (i, cls) in mro[1..].iter().enumerate() { if let Some(attr) = cls.attributes.read().get(name).cloned() { - if let Some(func) = try_extract(&attr) { + // Use the slice starting from this class in MRO + if let Some(func) = try_extract(&attr, &mro[i + 1..]) { return Some(func); } return None; From 1f8ef0aa3646babad28525646c442a7ce96b82a0 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 2 Jan 2026 00:35:59 +0900 Subject: [PATCH 665/819] sequence, mapping slots and fix separate __delitem__ slots (#6621) * fix call * Remove repr pymethod * Remove __get__ and __set__ pymethod * Remove __contains__ pymethod * Remove __len__ pymethod * mapping priors sequence * fix ctypes --- crates/stdlib/src/array.rs | 2 - crates/stdlib/src/contextvars.rs | 1 - crates/stdlib/src/mmap.rs | 1 - crates/vm/src/builtins/bytearray.rs | 1 - crates/vm/src/builtins/bytes.rs | 1 - crates/vm/src/builtins/descriptor.rs | 36 ++++++++++----- crates/vm/src/builtins/dict.rs | 3 -- crates/vm/src/builtins/getset.rs | 13 ------ crates/vm/src/builtins/list.rs | 1 - crates/vm/src/builtins/mappingproxy.rs | 1 - crates/vm/src/builtins/property.rs | 13 ------ crates/vm/src/builtins/range.rs | 2 - crates/vm/src/builtins/str.rs | 1 - crates/vm/src/builtins/tuple.rs | 1 - crates/vm/src/builtins/weakproxy.rs | 1 - crates/vm/src/exception_group.rs | 5 -- crates/vm/src/stdlib/collections.rs | 1 - crates/vm/src/stdlib/ctypes/array.rs | 30 ++++++++++-- crates/vm/src/stdlib/ctypes/base.rs | 15 ------ crates/vm/src/stdlib/ctypes/pointer.rs | 28 ++++++++++-- crates/vm/src/types/slot.rs | 61 ++++++++++++++++++++++--- crates/vm/src/types/slot_defs.rs | 63 +++++++++++++++++--------- 22 files changed, 171 insertions(+), 110 deletions(-) diff --git a/crates/stdlib/src/array.rs b/crates/stdlib/src/array.rs index 878a6ce8fb7..2e28714611c 100644 --- a/crates/stdlib/src/array.rs +++ b/crates/stdlib/src/array.rs @@ -1102,7 +1102,6 @@ mod array { Ok(zelf) } - #[pymethod] pub(crate) fn __len__(&self) -> usize { self.read().len() } @@ -1177,7 +1176,6 @@ mod array { )) } - #[pymethod] fn __contains__(&self, value: PyObjectRef, vm: &VirtualMachine) -> bool { let array = self.array.read(); for element in array diff --git a/crates/stdlib/src/contextvars.rs b/crates/stdlib/src/contextvars.rs index 731f5d11e0b..658d0906b24 100644 --- a/crates/stdlib/src/contextvars.rs +++ b/crates/stdlib/src/contextvars.rs @@ -202,7 +202,6 @@ mod _contextvars { Ok(item.to_owned()) } - #[pymethod] fn __len__(&self) -> usize { self.borrow_vars().len() } diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index b520eb2a1a7..191a330d536 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -731,7 +731,6 @@ mod mmap { .into() } - #[pymethod] fn __len__(&self) -> usize { self.size.load() } diff --git a/crates/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs index d3f1b0bd4e8..e1e437310eb 100644 --- a/crates/vm/src/builtins/bytearray.rs +++ b/crates/vm/src/builtins/bytearray.rs @@ -219,7 +219,6 @@ impl PyByteArray { self.inner().add(&other.borrow_buf()).into() } - #[pymethod] fn __contains__( &self, needle: Either<PyBytesInner, PyIntRef>, diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index 537a618be3b..57aed481dd4 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -228,7 +228,6 @@ impl PyBytes { self.inner.add(&other.borrow_buf()) } - #[pymethod] fn __contains__( &self, needle: Either<PyBytesInner, PyIntRef>, diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index a9218c8d689..89dafdd14b7 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -428,13 +428,15 @@ pub enum SlotFunc { SeqConcat(SeqConcatFunc), SeqRepeat(SeqRepeatFunc), SeqItem(SeqItemFunc), - SeqAssItem(SeqAssItemFunc), + SeqSetItem(SeqAssItemFunc), // __setitem__ (same func type, value = Some) + SeqDelItem(SeqAssItemFunc), // __delitem__ (same func type, value = None) SeqContains(SeqContainsFunc), // Mapping sub-slots (mp_*) MapLength(MapLenFunc), MapSubscript(MapSubscriptFunc), - MapAssSubscript(MapAssSubscriptFunc), + MapSetSubscript(MapAssSubscriptFunc), // __setitem__ (same func type, value = Some) + MapDelSubscript(MapAssSubscriptFunc), // __delitem__ (same func type, value = None) // Number sub-slots (nb_*) - grouped by signature NumBoolean(PyNumberUnaryFunc<bool>), // __bool__ @@ -468,12 +470,14 @@ impl core::fmt::Debug for SlotFunc { SlotFunc::SeqConcat(_) => write!(f, "SlotFunc::SeqConcat(...)"), SlotFunc::SeqRepeat(_) => write!(f, "SlotFunc::SeqRepeat(...)"), SlotFunc::SeqItem(_) => write!(f, "SlotFunc::SeqItem(...)"), - SlotFunc::SeqAssItem(_) => write!(f, "SlotFunc::SeqAssItem(...)"), + SlotFunc::SeqSetItem(_) => write!(f, "SlotFunc::SeqSetItem(...)"), + SlotFunc::SeqDelItem(_) => write!(f, "SlotFunc::SeqDelItem(...)"), SlotFunc::SeqContains(_) => write!(f, "SlotFunc::SeqContains(...)"), // Mapping sub-slots SlotFunc::MapLength(_) => write!(f, "SlotFunc::MapLength(...)"), SlotFunc::MapSubscript(_) => write!(f, "SlotFunc::MapSubscript(...)"), - SlotFunc::MapAssSubscript(_) => write!(f, "SlotFunc::MapAssSubscript(...)"), + SlotFunc::MapSetSubscript(_) => write!(f, "SlotFunc::MapSetSubscript(...)"), + SlotFunc::MapDelSubscript(_) => write!(f, "SlotFunc::MapDelSubscript(...)"), // Number sub-slots SlotFunc::NumBoolean(_) => write!(f, "SlotFunc::NumBoolean(...)"), SlotFunc::NumUnary(_) => write!(f, "SlotFunc::NumUnary(...)"), @@ -600,10 +604,14 @@ impl SlotFunc { let (index,): (isize,) = args.bind(vm)?; func(obj.sequence_unchecked(), index, vm) } - SlotFunc::SeqAssItem(func) => { - let (index, value): (isize, crate::function::OptionalArg<PyObjectRef>) = - args.bind(vm)?; - func(obj.sequence_unchecked(), index, value.into_option(), vm)?; + SlotFunc::SeqSetItem(func) => { + let (index, value): (isize, PyObjectRef) = args.bind(vm)?; + func(obj.sequence_unchecked(), index, Some(value), vm)?; + Ok(vm.ctx.none()) + } + SlotFunc::SeqDelItem(func) => { + let (index,): (isize,) = args.bind(vm)?; + func(obj.sequence_unchecked(), index, None, vm)?; Ok(vm.ctx.none()) } SlotFunc::SeqContains(func) => { @@ -621,10 +629,14 @@ impl SlotFunc { let (key,): (PyObjectRef,) = args.bind(vm)?; func(obj.mapping_unchecked(), &key, vm) } - SlotFunc::MapAssSubscript(func) => { - let (key, value): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) = - args.bind(vm)?; - func(obj.mapping_unchecked(), &key, value.into_option(), vm)?; + SlotFunc::MapSetSubscript(func) => { + let (key, value): (PyObjectRef, PyObjectRef) = args.bind(vm)?; + func(obj.mapping_unchecked(), &key, Some(value), vm)?; + Ok(vm.ctx.none()) + } + SlotFunc::MapDelSubscript(func) => { + let (key,): (PyObjectRef,) = args.bind(vm)?; + func(obj.mapping_unchecked(), &key, None, vm)?; Ok(vm.ctx.none()) } // Number sub-slots diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 41f6779c212..19b14688994 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -221,7 +221,6 @@ impl PyDict { core::mem::size_of::<Self>() + self.entries.sizeof() } - #[pymethod] fn __contains__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { self.entries.contains(vm, &*key) } @@ -1130,7 +1129,6 @@ impl ViewSetOps for PyDictKeys {} ) )] impl PyDictKeys { - #[pymethod] fn __contains__(zelf: PyObjectRef, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { zelf.sequence_unchecked().contains(&key, vm) } @@ -1195,7 +1193,6 @@ impl ViewSetOps for PyDictItems {} ) )] impl PyDictItems { - #[pymethod] fn __contains__(zelf: PyObjectRef, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { zelf.sequence_unchecked().contains(&needle, vm) } diff --git a/crates/vm/src/builtins/getset.rs b/crates/vm/src/builtins/getset.rs index a3f0605a473..86f0524b12c 100644 --- a/crates/vm/src/builtins/getset.rs +++ b/crates/vm/src/builtins/getset.rs @@ -118,19 +118,6 @@ impl PyGetSet { ))) } } - #[pymethod] - fn __set__( - zelf: PyObjectRef, - obj: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<()> { - Self::descr_set(&zelf, obj, PySetterValue::Assign(value), vm) - } - #[pymethod] - fn __delete__(zelf: PyObjectRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - Self::descr_set(&zelf, obj, PySetterValue::Delete, vm) - } #[pygetset] fn __name__(&self) -> String { diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index 514b38b6c28..eaff4a54688 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -251,7 +251,6 @@ impl PyList { self.mut_count(vm, &needle) } - #[pymethod] pub(crate) fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { self.mut_contains(vm, &needle) } diff --git a/crates/vm/src/builtins/mappingproxy.rs b/crates/vm/src/builtins/mappingproxy.rs index 13b0fa32490..c8f77c5c5fc 100644 --- a/crates/vm/src/builtins/mappingproxy.rs +++ b/crates/vm/src/builtins/mappingproxy.rs @@ -128,7 +128,6 @@ impl PyMappingProxy { } } - #[pymethod] pub fn __contains__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { self._contains(&key, vm) } diff --git a/crates/vm/src/builtins/property.rs b/crates/vm/src/builtins/property.rs index 3a86867176a..8cc9ba92e92 100644 --- a/crates/vm/src/builtins/property.rs +++ b/crates/vm/src/builtins/property.rs @@ -125,19 +125,6 @@ impl PyProperty { } } } - #[pymethod] - fn __set__( - zelf: PyObjectRef, - obj: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<()> { - Self::descr_set(&zelf, obj, PySetterValue::Assign(value), vm) - } - #[pymethod] - fn __delete__(zelf: PyObjectRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - Self::descr_set(&zelf, obj, PySetterValue::Delete, vm) - } // Access functions diff --git a/crates/vm/src/builtins/range.rs b/crates/vm/src/builtins/range.rs index fac5206b82d..957a66020c1 100644 --- a/crates/vm/src/builtins/range.rs +++ b/crates/vm/src/builtins/range.rs @@ -265,7 +265,6 @@ impl PyRange { ) } - #[pymethod] fn __len__(&self) -> BigInt { self.compute_length() } @@ -342,7 +341,6 @@ impl Py<PyRange> { } } - #[pymethod] fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> bool { self.contains_inner(&needle, vm) } diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index e101ef2a52b..d14bcd92bbe 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -572,7 +572,6 @@ impl PyStr { } } - #[pymethod] fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { self._contains(&needle, vm) } diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index f3da8b26163..e7271f9b6fd 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -345,7 +345,6 @@ impl PyTuple { Ok(false) } - #[pymethod] fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { self._contains(&needle, vm) } diff --git a/crates/vm/src/builtins/weakproxy.rs b/crates/vm/src/builtins/weakproxy.rs index 51120c02c01..0f68e9e9815 100644 --- a/crates/vm/src/builtins/weakproxy.rs +++ b/crates/vm/src/builtins/weakproxy.rs @@ -98,7 +98,6 @@ impl PyWeakProxy { let obj = self.try_upgrade(vm)?; reversed(obj, vm) } - #[pymethod] fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { self.try_upgrade(vm)? .sequence_unchecked() diff --git a/crates/vm/src/exception_group.rs b/crates/vm/src/exception_group.rs index b2dee206200..a55273480f6 100644 --- a/crates/vm/src/exception_group.rs +++ b/crates/vm/src/exception_group.rs @@ -202,11 +202,6 @@ pub(super) mod types { ))) } - #[pymethod] - fn __repr__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> { - Self::slot_repr(&zelf, vm) - } - #[pyslot] fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> { let zelf = zelf diff --git a/crates/vm/src/stdlib/collections.rs b/crates/vm/src/stdlib/collections.rs index 4688121c9b5..86caef7a25f 100644 --- a/crates/vm/src/stdlib/collections.rs +++ b/crates/vm/src/stdlib/collections.rs @@ -303,7 +303,6 @@ mod _collections { .ok_or_else(|| vm.new_index_error("deque index out of range")) } - #[pymethod] fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> { self._contains(&needle, vm) } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 63594879878..d808f967a0f 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -9,8 +9,8 @@ use crate::{ }, class::StaticType, function::{ArgBytesLike, FuncArgs, PySetterValue}, - protocol::{BufferDescriptor, PyBuffer, PyNumberMethods, PySequenceMethods}, - types::{AsBuffer, AsNumber, AsSequence, Constructor, Initializer}, + protocol::{BufferDescriptor, PyBuffer, PyMappingMethods, PyNumberMethods, PySequenceMethods}, + types::{AsBuffer, AsMapping, AsNumber, AsSequence, Constructor, Initializer}, }; use alloc::borrow::Cow; use num_traits::{Signed, ToPrimitive}; @@ -468,9 +468,33 @@ impl AsSequence for PyCArray { } } +impl AsMapping for PyCArray { + fn as_mapping() -> &'static PyMappingMethods { + use std::sync::LazyLock; + static AS_MAPPING: LazyLock<PyMappingMethods> = LazyLock::new(|| PyMappingMethods { + length: atomic_func!(|mapping, _vm| { + let zelf = PyCArray::mapping_downcast(mapping); + Ok(zelf.class().stg_info_opt().map_or(0, |i| i.length)) + }), + subscript: atomic_func!(|mapping, needle, vm| { + let zelf = PyCArray::mapping_downcast(mapping); + PyCArray::__getitem__(zelf, needle.to_owned(), vm) + }), + ass_subscript: atomic_func!(|mapping, needle, value, vm| { + let zelf = PyCArray::mapping_downcast(mapping); + match value { + Some(value) => PyCArray::__setitem__(zelf, needle.to_owned(), value, vm), + None => PyCArray::__delitem__(zelf, needle.to_owned(), vm), + } + }), + }); + &AS_MAPPING + } +} + #[pyclass( flags(BASETYPE, IMMUTABLETYPE), - with(Constructor, Initializer, AsSequence, AsBuffer) + with(Constructor, Initializer, AsSequence, AsMapping, AsBuffer) )] impl PyCArray { #[pyclassmethod] diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 1f9eaeef56a..58d9466adb2 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -1650,21 +1650,6 @@ impl PyCField { } } - #[pymethod] - fn __set__( - zelf: PyObjectRef, - obj: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<()> { - Self::descr_set(&zelf, obj, PySetterValue::Assign(value), vm) - } - - #[pymethod] - fn __delete__(zelf: PyObjectRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - Self::descr_set(&zelf, obj, PySetterValue::Delete, vm) - } - #[pygetset] fn offset(&self) -> isize { self.offset diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 4f935945e88..8a0f7cb0f6a 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -1,7 +1,8 @@ use super::base::CDATA_BUFFER_METHODS; use super::{PyCArray, PyCData, PyCSimple, PyCStructure, StgInfo, StgInfoFlags}; -use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}; -use crate::types::{AsBuffer, AsNumber, Constructor, Initializer}; +use crate::atomic_func; +use crate::protocol::{BufferDescriptor, PyBuffer, PyMappingMethods, PyNumberMethods}; +use crate::types::{AsBuffer, AsMapping, AsNumber, Constructor, Initializer}; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef}, @@ -260,7 +261,7 @@ impl Initializer for PyCPointer { #[pyclass( flags(BASETYPE, IMMUTABLETYPE), - with(Constructor, Initializer, AsNumber, AsBuffer) + with(Constructor, Initializer, AsNumber, AsBuffer, AsMapping) )] impl PyCPointer { /// Get the pointer value stored in buffer as usize @@ -785,6 +786,27 @@ impl AsNumber for PyCPointer { } } +impl AsMapping for PyCPointer { + fn as_mapping() -> &'static PyMappingMethods { + use std::sync::LazyLock; + static AS_MAPPING: LazyLock<PyMappingMethods> = LazyLock::new(|| PyMappingMethods { + subscript: atomic_func!(|mapping, needle, vm| { + let zelf = PyCPointer::mapping_downcast(mapping); + PyCPointer::__getitem__(zelf, needle.to_owned(), vm) + }), + ass_subscript: atomic_func!(|mapping, needle, value, vm| { + let zelf = PyCPointer::mapping_downcast(mapping); + match value { + Some(value) => PyCPointer::__setitem__(zelf, needle.to_owned(), value, vm), + None => Err(vm.new_type_error("Pointer does not support item deletion")), + } + }), + ..PyMappingMethods::NOT_IMPLEMENTED + }); + &AS_MAPPING + } +} + impl AsBuffer for PyCPointer { fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> { let stg_info = zelf diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 2df440b5b9f..b637bfc40b6 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -1319,7 +1319,33 @@ impl PyType { update_sub_slot!(as_sequence, item, sequence_getitem_wrapper, SeqItem) } SlotAccessor::SqAssItem => { - update_sub_slot!(as_sequence, ass_item, sequence_setitem_wrapper, SeqAssItem) + // SqAssItem is shared by __setitem__ (SeqSetItem) and __delitem__ (SeqDelItem) + if ADD { + let has_own = { + let guard = self.attributes.read(); + let setitem = ctx.intern_str("__setitem__"); + let delitem = ctx.intern_str("__delitem__"); + guard.contains_key(setitem) || guard.contains_key(delitem) + }; + if has_own { + self.slots + .as_sequence + .ass_item + .store(Some(sequence_setitem_wrapper)); + } else if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| match sf { + SlotFunc::SeqSetItem(f) | SlotFunc::SeqDelItem(f) => Some(*f), + _ => None, + }) { + self.slots.as_sequence.ass_item.store(Some(func)); + } else { + self.slots + .as_sequence + .ass_item + .store(Some(sequence_setitem_wrapper)); + } + } else { + accessor.inherit_from_mro(self); + } } SlotAccessor::SqContains => { update_sub_slot!( @@ -1338,12 +1364,33 @@ impl PyType { update_sub_slot!(as_mapping, subscript, mapping_getitem_wrapper, MapSubscript) } SlotAccessor::MpAssSubscript => { - update_sub_slot!( - as_mapping, - ass_subscript, - mapping_setitem_wrapper, - MapAssSubscript - ) + // MpAssSubscript is shared by __setitem__ (MapSetSubscript) and __delitem__ (MapDelSubscript) + if ADD { + let has_own = { + let guard = self.attributes.read(); + let setitem = ctx.intern_str("__setitem__"); + let delitem = ctx.intern_str("__delitem__"); + guard.contains_key(setitem) || guard.contains_key(delitem) + }; + if has_own { + self.slots + .as_mapping + .ass_subscript + .store(Some(mapping_setitem_wrapper)); + } else if let Some(func) = self.lookup_slot_in_mro(name, ctx, |sf| match sf { + SlotFunc::MapSetSubscript(f) | SlotFunc::MapDelSubscript(f) => Some(*f), + _ => None, + }) { + self.slots.as_mapping.ass_subscript.store(Some(func)); + } else { + self.slots + .as_mapping + .ass_subscript + .store(Some(mapping_setitem_wrapper)); + } + } else { + accessor.inherit_from_mro(self); + } } // Reserved slots - no-op diff --git a/crates/vm/src/types/slot_defs.rs b/crates/vm/src/types/slot_defs.rs index 1fd493f685e..024776f7893 100644 --- a/crates/vm/src/types/slot_defs.rs +++ b/crates/vm/src/types/slot_defs.rs @@ -389,13 +389,20 @@ impl SlotAccessor { Self::SqConcat | Self::SqInplaceConcat => matches!(slot_func, SlotFunc::SeqConcat(_)), Self::SqRepeat | Self::SqInplaceRepeat => matches!(slot_func, SlotFunc::SeqRepeat(_)), Self::SqItem => matches!(slot_func, SlotFunc::SeqItem(_)), - Self::SqAssItem => matches!(slot_func, SlotFunc::SeqAssItem(_)), + Self::SqAssItem => { + matches!(slot_func, SlotFunc::SeqSetItem(_) | SlotFunc::SeqDelItem(_)) + } Self::SqContains => matches!(slot_func, SlotFunc::SeqContains(_)), // Mapping Self::MpLength => matches!(slot_func, SlotFunc::MapLength(_)), Self::MpSubscript => matches!(slot_func, SlotFunc::MapSubscript(_)), - Self::MpAssSubscript => matches!(slot_func, SlotFunc::MapAssSubscript(_)), + Self::MpAssSubscript => { + matches!( + slot_func, + SlotFunc::MapSetSubscript(_) | SlotFunc::MapDelSubscript(_) + ) + } // New and reserved slots Self::TpNew => false, @@ -751,7 +758,7 @@ impl SlotAccessor { Self::SqConcat => slots.as_sequence.concat.load().map(SlotFunc::SeqConcat), Self::SqRepeat => slots.as_sequence.repeat.load().map(SlotFunc::SeqRepeat), Self::SqItem => slots.as_sequence.item.load().map(SlotFunc::SeqItem), - Self::SqAssItem => slots.as_sequence.ass_item.load().map(SlotFunc::SeqAssItem), + Self::SqAssItem => slots.as_sequence.ass_item.load().map(SlotFunc::SeqSetItem), Self::SqContains => slots.as_sequence.contains.load().map(SlotFunc::SeqContains), Self::SqInplaceConcat => slots .as_sequence @@ -775,7 +782,7 @@ impl SlotAccessor { .as_mapping .ass_subscript .load() - .map(SlotFunc::MapAssSubscript), + .map(SlotFunc::MapSetSubscript), // Reserved slots _ => None, @@ -793,6 +800,16 @@ impl SlotAccessor { match self { Self::TpSetattro => return slots.setattro.load().map(SlotFunc::DelAttro), Self::TpDescrSet => return slots.descr_set.load().map(SlotFunc::DescrDel), + Self::SqAssItem => { + return slots.as_sequence.ass_item.load().map(SlotFunc::SeqDelItem); + } + Self::MpAssSubscript => { + return slots + .as_mapping + .ass_subscript + .load() + .map(SlotFunc::MapDelSubscript); + } _ => {} } } @@ -1060,62 +1077,64 @@ pub static SLOT_DEFS: &[SlotDef] = &[ op: Some(SlotOp::Delete), doc: "Delete an attribute of instance.", }, - // Sequence protocol (sq_*) + // Mapping protocol (mp_*) - must come before Sequence protocol + // so that mp_subscript wins over sq_item for __getitem__ + // (see CPython typeobject.c:10995-11006) SlotDef { name: "__len__", - accessor: SlotAccessor::SqLength, + accessor: SlotAccessor::MpLength, op: None, doc: "Return len(self).", }, SlotDef { name: "__getitem__", - accessor: SlotAccessor::SqItem, + accessor: SlotAccessor::MpSubscript, op: None, doc: "Return self[key].", }, SlotDef { name: "__setitem__", - accessor: SlotAccessor::SqAssItem, + accessor: SlotAccessor::MpAssSubscript, op: None, doc: "Set self[key] to value.", }, SlotDef { name: "__delitem__", - accessor: SlotAccessor::SqAssItem, - op: None, + accessor: SlotAccessor::MpAssSubscript, + op: Some(SlotOp::Delete), doc: "Delete self[key].", }, - SlotDef { - name: "__contains__", - accessor: SlotAccessor::SqContains, - op: None, - doc: "Return key in self.", - }, - // Mapping protocol (mp_*) + // Sequence protocol (sq_*) SlotDef { name: "__len__", - accessor: SlotAccessor::MpLength, + accessor: SlotAccessor::SqLength, op: None, doc: "Return len(self).", }, SlotDef { name: "__getitem__", - accessor: SlotAccessor::MpSubscript, + accessor: SlotAccessor::SqItem, op: None, doc: "Return self[key].", }, SlotDef { name: "__setitem__", - accessor: SlotAccessor::MpAssSubscript, + accessor: SlotAccessor::SqAssItem, op: None, doc: "Set self[key] to value.", }, SlotDef { name: "__delitem__", - accessor: SlotAccessor::MpAssSubscript, - op: None, + accessor: SlotAccessor::SqAssItem, + op: Some(SlotOp::Delete), doc: "Delete self[key].", }, + SlotDef { + name: "__contains__", + accessor: SlotAccessor::SqContains, + op: None, + doc: "Return key in self.", + }, // Number protocol - binary ops with left/right variants SlotDef { name: "__add__", From 2e2924801c967913e8c71100d9c9300115c7a380 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 2 Jan 2026 08:29:42 +0900 Subject: [PATCH 666/819] __{get,set,del}item__ without pymethods (#6622) * __getitem__ without pymethod * __setitem__ __delitem__ without pymethod * sort slot names --- crates/stdlib/src/array.rs | 3 --- crates/stdlib/src/contextvars.rs | 1 - crates/stdlib/src/mmap.rs | 2 -- crates/vm/src/builtins/bytearray.rs | 3 --- crates/vm/src/builtins/bytes.rs | 1 - crates/vm/src/builtins/dict.rs | 3 --- crates/vm/src/builtins/genericalias.rs | 1 - crates/vm/src/builtins/list.rs | 3 --- crates/vm/src/builtins/mappingproxy.rs | 1 - crates/vm/src/builtins/memory.rs | 3 --- crates/vm/src/builtins/range.rs | 1 - crates/vm/src/builtins/str.rs | 1 - crates/vm/src/builtins/tuple.rs | 1 - crates/vm/src/builtins/type.rs | 5 ++++- crates/vm/src/stdlib/collections.rs | 3 --- crates/vm/src/stdlib/ctypes/array.rs | 3 --- crates/vm/src/stdlib/ctypes/pointer.rs | 2 -- crates/vm/src/stdlib/sre.rs | 1 - 18 files changed, 4 insertions(+), 34 deletions(-) diff --git a/crates/stdlib/src/array.rs b/crates/stdlib/src/array.rs index 2e28714611c..463027d9baa 100644 --- a/crates/stdlib/src/array.rs +++ b/crates/stdlib/src/array.rs @@ -996,7 +996,6 @@ mod array { } } - #[pymethod] fn __getitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.getitem_inner(&needle, vm) } @@ -1038,7 +1037,6 @@ mod array { } } - #[pymethod] fn __setitem__( zelf: &Py<Self>, needle: PyObjectRef, @@ -1055,7 +1053,6 @@ mod array { } } - #[pymethod] fn __delitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { self.delitem_inner(&needle, vm) } diff --git a/crates/stdlib/src/contextvars.rs b/crates/stdlib/src/contextvars.rs index 658d0906b24..329342fe6dc 100644 --- a/crates/stdlib/src/contextvars.rs +++ b/crates/stdlib/src/contextvars.rs @@ -189,7 +189,6 @@ mod _contextvars { } } - #[pymethod] fn __getitem__( &self, var: PyRef<ContextVar>, diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index 191a330d536..d3895521096 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -1240,12 +1240,10 @@ mod mmap { Ok(()) } - #[pymethod] fn __getitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> { self.getitem_inner(&needle, vm) } - #[pymethod] fn __setitem__( zelf: &Py<Self>, needle: PyObjectRef, diff --git a/crates/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs index e1e437310eb..0f9dce7230b 100644 --- a/crates/vm/src/builtins/bytearray.rs +++ b/crates/vm/src/builtins/bytearray.rs @@ -238,12 +238,10 @@ impl PyByteArray { Ok(zelf) } - #[pymethod] fn __getitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self._getitem(&needle, vm) } - #[pymethod] pub fn __delitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { self._delitem(&needle, vm) } @@ -549,7 +547,6 @@ impl PyByteArray { #[pyclass] impl Py<PyByteArray> { - #[pymethod] fn __setitem__( &self, needle: PyObjectRef, diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index 57aed481dd4..01e67358aae 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -241,7 +241,6 @@ impl PyBytes { PyBytesInner::maketrans(from, to, vm) } - #[pymethod] fn __getitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self._getitem(&needle, vm) } diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 19b14688994..43f7100e963 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -225,7 +225,6 @@ impl PyDict { self.entries.contains(vm, &*key) } - #[pymethod] fn __delitem__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { self.inner_delitem(&*key, vm) } @@ -235,7 +234,6 @@ impl PyDict { self.entries.clear() } - #[pymethod] fn __setitem__( &self, key: PyObjectRef, @@ -372,7 +370,6 @@ impl Py<PyDict> { Ok(Implemented(true)) } - #[pymethod] #[cfg_attr(feature = "flame-it", flame("PyDictRef"))] fn __getitem__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.inner_getitem(&*key, vm) diff --git a/crates/vm/src/builtins/genericalias.rs b/crates/vm/src/builtins/genericalias.rs index 99c1eacc3ec..e9150e4c088 100644 --- a/crates/vm/src/builtins/genericalias.rs +++ b/crates/vm/src/builtins/genericalias.rs @@ -189,7 +189,6 @@ impl PyGenericAlias { } } - #[pymethod] fn __getitem__(zelf: PyRef<Self>, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { let new_args = subs_parameters( zelf.to_owned().into(), diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index eaff4a54688..02475ee12b6 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -213,7 +213,6 @@ impl PyList { } } - #[pymethod] fn __getitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self._getitem(&needle, vm) } @@ -228,7 +227,6 @@ impl PyList { } } - #[pymethod] fn __setitem__( &self, needle: PyObjectRef, @@ -308,7 +306,6 @@ impl PyList { } } - #[pymethod] fn __delitem__(&self, subscript: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { self._delitem(&subscript, vm) } diff --git a/crates/vm/src/builtins/mappingproxy.rs b/crates/vm/src/builtins/mappingproxy.rs index c8f77c5c5fc..f7fb64fa6ab 100644 --- a/crates/vm/src/builtins/mappingproxy.rs +++ b/crates/vm/src/builtins/mappingproxy.rs @@ -111,7 +111,6 @@ impl PyMappingProxy { )?)) } - #[pymethod] pub fn __getitem__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.get_inner(key.clone(), vm)? .ok_or_else(|| vm.new_key_error(key)) diff --git a/crates/vm/src/builtins/memory.rs b/crates/vm/src/builtins/memory.rs index 5ca4257cded..6db260c80e0 100644 --- a/crates/vm/src/builtins/memory.rs +++ b/crates/vm/src/builtins/memory.rs @@ -659,7 +659,6 @@ impl PyMemoryView { self.release(); } - #[pymethod] fn __getitem__(zelf: PyRef<Self>, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { zelf.try_not_released(vm)?; if zelf.desc.ndim() == 0 { @@ -682,7 +681,6 @@ impl PyMemoryView { } } - #[pymethod] fn __delitem__(&self, _needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { if self.desc.readonly { return Err(vm.new_type_error("cannot modify read-only memory")); @@ -847,7 +845,6 @@ impl PyMemoryView { #[pyclass] impl Py<PyMemoryView> { - #[pymethod] fn __setitem__( &self, needle: PyObjectRef, diff --git a/crates/vm/src/builtins/range.rs b/crates/vm/src/builtins/range.rs index 957a66020c1..49f50ffbaa2 100644 --- a/crates/vm/src/builtins/range.rs +++ b/crates/vm/src/builtins/range.rs @@ -279,7 +279,6 @@ impl PyRange { (vm.ctx.types.range_type.to_owned(), range_parameters_tuple) } - #[pymethod] fn __getitem__(&self, subscript: PyObjectRef, vm: &VirtualMachine) -> PyResult { match RangeIndex::try_from_object(vm, subscript)? { RangeIndex::Slice(slice) => { diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index d14bcd92bbe..4931a748198 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -584,7 +584,6 @@ impl PyStr { Ok(item) } - #[pymethod] fn __getitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self._getitem(&needle, vm) } diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index e7271f9b6fd..f6eff5b91e5 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -315,7 +315,6 @@ impl PyTuple { } } - #[pymethod] fn __getitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self._getitem(&needle, vm) } diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index e2e69c7bffc..b6995d6f595 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -439,7 +439,10 @@ impl PyType { slot_name_set.insert(name); } } - for attr_name in slot_name_set { + // Sort for deterministic iteration order (important for slot processing) + let mut slot_names: Vec<_> = slot_name_set.into_iter().collect(); + slot_names.sort_by_key(|name| name.as_str()); + for attr_name in slot_names { self.update_slot::<true>(attr_name, ctx); } diff --git a/crates/vm/src/stdlib/collections.rs b/crates/vm/src/stdlib/collections.rs index 86caef7a25f..fd48cea0598 100644 --- a/crates/vm/src/stdlib/collections.rs +++ b/crates/vm/src/stdlib/collections.rs @@ -278,7 +278,6 @@ mod _collections { self.maxlen } - #[pymethod] fn __getitem__(&self, idx: isize, vm: &VirtualMachine) -> PyResult { let deque = self.borrow_deque(); idx.wrapped_at(deque.len()) @@ -286,7 +285,6 @@ mod _collections { .ok_or_else(|| vm.new_index_error("deque index out of range")) } - #[pymethod] fn __setitem__(&self, idx: isize, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { let mut deque = self.borrow_deque_mut(); idx.wrapped_at(deque.len()) @@ -295,7 +293,6 @@ mod _collections { .ok_or_else(|| vm.new_index_error("deque index out of range")) } - #[pymethod] fn __delitem__(&self, idx: isize, vm: &VirtualMachine) -> PyResult<()> { let mut deque = self.borrow_deque_mut(); idx.wrapped_at(deque.len()) diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index d808f967a0f..eea1fd765d5 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -940,7 +940,6 @@ impl PyCArray { } // Array_subscript - #[pymethod] fn __getitem__(zelf: &Py<Self>, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { // PyIndex_Check if let Some(i) = item.downcast_ref::<PyInt>() { @@ -1047,7 +1046,6 @@ impl PyCArray { } // Array_ass_subscript - #[pymethod] fn __setitem__( zelf: &Py<Self>, item: PyObjectRef, @@ -1074,7 +1072,6 @@ impl PyCArray { } // Array does not support item deletion - #[pymethod] fn __delitem__(&self, _item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { Err(vm.new_type_error("Array does not support item deletion")) } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 8a0f7cb0f6a..875d78a0010 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -346,7 +346,6 @@ impl PyCPointer { } // Pointer_subscript - #[pymethod] fn __getitem__(zelf: &Py<Self>, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { // PyIndex_Check if let Some(i) = item.downcast_ref::<PyInt>() { @@ -529,7 +528,6 @@ impl PyCPointer { } // Pointer_ass_item - #[pymethod] fn __setitem__( zelf: &Py<Self>, item: PyObjectRef, diff --git a/crates/vm/src/stdlib/sre.rs b/crates/vm/src/stdlib/sre.rs index b950db9e1ff..33f884e1adc 100644 --- a/crates/vm/src/stdlib/sre.rs +++ b/crates/vm/src/stdlib/sre.rs @@ -737,7 +737,6 @@ mod _sre { }) } - #[pymethod] fn __getitem__( &self, group: PyObjectRef, From 32184103c348c26b5ceb4a9d27a9ccd89fe583d9 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:14:39 +0900 Subject: [PATCH 667/819] ruff_source_file (#6624) --- crates/compiler-source/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/compiler-source/src/lib.rs b/crates/compiler-source/src/lib.rs index 2d967e218d2..e3fb55ef603 100644 --- a/crates/compiler-source/src/lib.rs +++ b/crates/compiler-source/src/lib.rs @@ -1,4 +1,4 @@ -pub use ruff_source_file::{LineIndex, OneIndexed as LineNumber, SourceLocation}; +pub use ruff_source_file::{LineIndex, OneIndexed as LineNumber, PositionEncoding, SourceLocation}; use ruff_text_size::TextRange; pub use ruff_text_size::TextSize; @@ -20,7 +20,8 @@ impl<'src> SourceCode<'src> { } pub fn source_location(&self, offset: TextSize) -> SourceLocation { - self.index.source_location(offset, self.text) + self.index + .source_location(offset, self.text, PositionEncoding::Utf8) } pub fn get_range(&'src self, range: TextRange) -> &'src str { From 346519bb6b0d39fa173f9cad5eae88232c8cae4b Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 2 Jan 2026 17:49:55 +0900 Subject: [PATCH 668/819] Warn user not to override special magic methods (#6625) --- crates/derive-impl/src/pyclass.rs | 106 +++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 9 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 06bbc06cfb2..028d3d7c292 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -909,18 +909,106 @@ where let py_name = item_meta.method_name()?; - // Disallow __new__ and __init__ as pymethod in impl blocks (not in traits) + // Disallow slot methods - they should be defined via trait implementations + // These are exposed as wrapper_descriptor via add_operators from SLOT_DEFS if !args.context.is_trait { - if py_name == "__new__" { - return Err(syn::Error::new( - ident.span(), - "#[pymethod] cannot define '__new__'. Use #[pyclass(with(Constructor))] instead.", - )); - } - if py_name == "__init__" { + const FORBIDDEN_SLOT_METHODS: &[(&str, &str)] = &[ + // Constructor/Initializer traits + ("__new__", "Constructor"), + ("__init__", "Initializer"), + // Representable trait + // ("__repr__", "Representable"), + // ("__str__", "???"), // allow __str__ + // Hashable trait + ("__hash__", "Hashable"), + // Callable trait + ("__call__", "Callable"), + // GetAttr/SetAttr traits + // NOTE: __getattribute__, __setattr__, __delattr__ are intentionally NOT forbidden + // because they need pymethod for subclass override mechanism to work properly. + // GetDescriptor/SetDescriptor traits + // ("__get__", "GetDescriptor"), + // ("__set__", "SetDescriptor"), + // ("__delete__", "SetDescriptor"), + // AsNumber trait + ("__add__", "AsNumber"), + ("__radd__", "AsNumber"), + ("__iadd__", "AsNumber"), + ("__sub__", "AsNumber"), + ("__rsub__", "AsNumber"), + ("__isub__", "AsNumber"), + ("__mul__", "AsNumber"), + ("__rmul__", "AsNumber"), + ("__imul__", "AsNumber"), + ("__truediv__", "AsNumber"), + ("__rtruediv__", "AsNumber"), + ("__itruediv__", "AsNumber"), + ("__floordiv__", "AsNumber"), + ("__rfloordiv__", "AsNumber"), + ("__ifloordiv__", "AsNumber"), + ("__mod__", "AsNumber"), + ("__rmod__", "AsNumber"), + ("__imod__", "AsNumber"), + ("__pow__", "AsNumber"), + ("__rpow__", "AsNumber"), + ("__ipow__", "AsNumber"), + ("__divmod__", "AsNumber"), + ("__rdivmod__", "AsNumber"), + ("__matmul__", "AsNumber"), + ("__rmatmul__", "AsNumber"), + ("__imatmul__", "AsNumber"), + ("__lshift__", "AsNumber"), + ("__rlshift__", "AsNumber"), + ("__ilshift__", "AsNumber"), + ("__rshift__", "AsNumber"), + ("__rrshift__", "AsNumber"), + ("__irshift__", "AsNumber"), + ("__and__", "AsNumber"), + ("__rand__", "AsNumber"), + ("__iand__", "AsNumber"), + ("__or__", "AsNumber"), + ("__ror__", "AsNumber"), + ("__ior__", "AsNumber"), + ("__xor__", "AsNumber"), + ("__rxor__", "AsNumber"), + ("__ixor__", "AsNumber"), + ("__neg__", "AsNumber"), + ("__pos__", "AsNumber"), + ("__abs__", "AsNumber"), + ("__invert__", "AsNumber"), + ("__int__", "AsNumber"), + ("__float__", "AsNumber"), + ("__index__", "AsNumber"), + ("__bool__", "AsNumber"), + // AsSequence trait + // ("__len__", "AsSequence (or AsMapping)"), + // ("__contains__", "AsSequence"), + // AsMapping trait + // ("__getitem__", "AsMapping (or AsSequence)"), + // ("__setitem__", "AsMapping (or AsSequence)"), + // ("__delitem__", "AsMapping (or AsSequence)"), + // IterNext trait + // ("__iter__", "IterNext"), + // ("__next__", "IterNext"), + // Comparable trait + ("__eq__", "Comparable"), + ("__ne__", "Comparable"), + ("__lt__", "Comparable"), + ("__le__", "Comparable"), + ("__gt__", "Comparable"), + ("__ge__", "Comparable"), + ]; + + if let Some((_, trait_name)) = FORBIDDEN_SLOT_METHODS + .iter() + .find(|(method, _)| *method == py_name.as_str()) + { return Err(syn::Error::new( ident.span(), - "#[pymethod] cannot define '__init__'. Use #[pyclass(with(Initializer))] instead.", + format!( + "#[pymethod] cannot define '{py_name}'. Use `impl {trait_name} for ...` instead. \ + Slot methods are exposed as wrapper_descriptor automatically.", + ), )); } } From 546d35b8c19a876241acc50762ca311e0bbc45f4 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:14:43 +0900 Subject: [PATCH 669/819] impl multiprocessing SemLock (#6542) --- .cspell.dict/cpython.txt | 2 + Lib/test/_test_multiprocessing.py | 113 ++- Lib/test/mp_fork_bomb.py | 18 + Lib/test/mp_preload.py | 18 + Lib/test/mp_preload_flush.py | 11 + .../test_importlib/test_threaded_import.py | 3 +- Lib/test/test_logging.py | 6 +- crates/stdlib/src/multiprocessing.rs | 692 +++++++++++++++++- crates/stdlib/src/posixshmem.rs | 26 +- crates/stdlib/src/pystruct.rs | 2 +- 10 files changed, 849 insertions(+), 42 deletions(-) create mode 100644 Lib/test/mp_fork_bomb.py create mode 100644 Lib/test/mp_preload.py create mode 100644 Lib/test/mp_preload_flush.py diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 5011975f496..0e4e17e2872 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -30,6 +30,7 @@ fromlist heaptype HIGHRES IMMUTABLETYPE +ismine Itertool keeped kwonlyarg @@ -40,6 +41,7 @@ lsprof maxdepth mult multibytecodec +newsemlockobject nkwargs noraise numer diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index c22ce769c48..351887819e6 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -38,7 +38,7 @@ from test.support import socket_helper from test.support import threading_helper from test.support import warnings_helper - +from test.support import subTests # Skip tests if _multiprocessing wasn't built. _multiprocessing = import_helper.import_module('_multiprocessing') @@ -1109,7 +1109,7 @@ def test_put(self): @classmethod def _test_get(cls, queue, child_can_start, parent_can_continue): child_can_start.wait() - #queue.put(1) + queue.put(1) queue.put(2) queue.put(3) queue.put(4) @@ -1133,15 +1133,16 @@ def test_get(self): child_can_start.set() parent_can_continue.wait() - time.sleep(DELTA) + for _ in support.sleeping_retry(support.SHORT_TIMEOUT): + if not queue_empty(queue): + break self.assertEqual(queue_empty(queue), False) - # Hangs unexpectedly, remove for now - #self.assertEqual(queue.get(), 1) + self.assertEqual(queue.get_nowait(), 1) self.assertEqual(queue.get(True, None), 2) self.assertEqual(queue.get(True), 3) self.assertEqual(queue.get(timeout=1), 4) - self.assertEqual(queue.get_nowait(), 5) + self.assertEqual(queue.get(), 5) self.assertEqual(queue_empty(queue), True) @@ -2970,6 +2971,8 @@ def test_map_no_failfast(self): # check that we indeed waited for all jobs self.assertGreater(time.monotonic() - t_start, 0.9) + # TODO: RUSTPYTHON - reference counting differences + @unittest.skip("TODO: RUSTPYTHON") def test_release_task_refs(self): # Issue #29861: task arguments and results should not be kept # alive after we are done with them. @@ -3882,6 +3885,8 @@ def _remote(cls, conn): conn.close() + # TODO: RUSTPYTHON - hangs + @unittest.skip("TODO: RUSTPYTHON") def test_pickling(self): families = self.connection.families @@ -4051,6 +4056,8 @@ def test_heap(self): self.assertEqual(len(heap._allocated_blocks), 0, heap._allocated_blocks) self.assertEqual(len(heap._len_to_seq), 0) + # TODO: RUSTPYTHON - gc.enable() not implemented + @unittest.expectedFailure def test_free_from_gc(self): # Check that freeing of blocks by the garbage collector doesn't deadlock # (issue #12352). @@ -4103,6 +4110,8 @@ def _double(cls, x, y, z, foo, arr, string): for i in range(len(arr)): arr[i] *= 2 + # TODO: RUSTPYTHON - ctypes Structure shared memory not working + @unittest.expectedFailure def test_sharedctypes(self, lock=False): x = Value('i', 7, lock=lock) y = Value(c_double, 1.0/3.0, lock=lock) @@ -4126,6 +4135,8 @@ def test_sharedctypes(self, lock=False): self.assertAlmostEqual(arr[i], i*2) self.assertEqual(string.value, latin('hellohello')) + # TODO: RUSTPYTHON - calls test_sharedctypes which fails + @unittest.expectedFailure def test_synchronize(self): self.test_sharedctypes(lock=True) @@ -4140,6 +4151,19 @@ def test_copy(self): self.assertEqual(bar.z, 2 ** 33) +def resource_tracker_format_subtests(func): + """Run given test using both resource tracker communication formats""" + def _inner(self, *args, **kwargs): + tracker = resource_tracker._resource_tracker + for use_simple_format in False, True: + with ( + self.subTest(use_simple_format=use_simple_format), + unittest.mock.patch.object( + tracker, '_use_simple_format', use_simple_format) + ): + func(self, *args, **kwargs) + return _inner + @unittest.skipUnless(HAS_SHMEM, "requires multiprocessing.shared_memory") @hashlib_helper.requires_hashdigest('sha256') class _TestSharedMemory(BaseTestCase): @@ -4417,6 +4441,7 @@ def test_shared_memory_SharedMemoryServer_ignores_sigint(self): smm.shutdown() @unittest.skipIf(os.name != "posix", "resource_tracker is posix only") + @resource_tracker_format_subtests def test_shared_memory_SharedMemoryManager_reuses_resource_tracker(self): # bpo-36867: test that a SharedMemoryManager uses the # same resource_tracker process as its parent. @@ -4667,6 +4692,7 @@ def test_shared_memory_cleaned_after_process_termination(self): "shared_memory objects to clean up at shutdown", err) @unittest.skipIf(os.name != "posix", "resource_tracker is posix only") + @resource_tracker_format_subtests def test_shared_memory_untracking(self): # gh-82300: When a separate Python process accesses shared memory # with track=False, it must not cause the memory to be deleted @@ -4694,6 +4720,7 @@ def test_shared_memory_untracking(self): mem.close() @unittest.skipIf(os.name != "posix", "resource_tracker is posix only") + @resource_tracker_format_subtests def test_shared_memory_tracking(self): # gh-82300: When a separate Python process accesses shared memory # with track=True, it must cause the memory to be deleted when @@ -4787,6 +4814,8 @@ def test_finalize(self): result = [obj for obj in iter(conn.recv, 'STOP')] self.assertEqual(result, ['a', 'b', 'd10', 'd03', 'd02', 'd01', 'e']) + # TODO: RUSTPYTHON - gc.get_threshold() and gc.set_threshold() not implemented + @unittest.expectedFailure @support.requires_resource('cpu') def test_thread_safety(self): # bpo-24484: _run_finalizers() should be thread-safe @@ -5414,6 +5443,8 @@ def run_in_child(cls, start_method): flags = (tuple(sys.flags), grandchild_flags) print(json.dumps(flags)) + # TODO: RUSTPYTHON - SyntaxError in subprocess after fork + @unittest.expectedFailure def test_flags(self): import json # start child process using unusual flags @@ -6457,28 +6488,13 @@ def test_std_streams_flushed_after_preload(self): if multiprocessing.get_start_method() != "forkserver": self.skipTest("forkserver specific test") - # Create a test module in the temporary directory on the child's path - # TODO: This can all be simplified once gh-126631 is fixed and we can - # use __main__ instead of a module. - dirname = os.path.join(self._temp_dir, 'preloaded_module') - init_name = os.path.join(dirname, '__init__.py') - os.mkdir(dirname) - with open(init_name, "w") as f: - cmd = '''if 1: - import sys - print('stderr', end='', file=sys.stderr) - print('stdout', end='', file=sys.stdout) - ''' - f.write(cmd) - name = os.path.join(os.path.dirname(__file__), 'mp_preload_flush.py') - env = {'PYTHONPATH': self._temp_dir} - _, out, err = test.support.script_helper.assert_python_ok(name, **env) + _, out, err = test.support.script_helper.assert_python_ok(name) # Check stderr first, as it is more likely to be useful to see in the # event of a failure. - self.assertEqual(err.decode().rstrip(), 'stderr') - self.assertEqual(out.decode().rstrip(), 'stdout') + self.assertEqual(err.decode().rstrip(), '__main____mp_main__') + self.assertEqual(out.decode().rstrip(), '__main____mp_main__') class MiscTestCase(unittest.TestCase): @@ -6804,3 +6820,52 @@ class SemLock(_multiprocessing.SemLock): name = f'test_semlock_subclass-{os.getpid()}' s = SemLock(1, 0, 10, name, False) _multiprocessing.sem_unlink(name) + + +@unittest.skipUnless(HAS_SHMEM, "requires multiprocessing.shared_memory") +class TestSharedMemoryNames(unittest.TestCase): + @subTests('use_simple_format', (True, False)) + def test_that_shared_memory_name_with_colons_has_no_resource_tracker_errors( + self, use_simple_format): + # Test script that creates and cleans up shared memory with colon in name + test_script = textwrap.dedent(""" + import sys + from multiprocessing import shared_memory + from multiprocessing import resource_tracker + import time + + resource_tracker._resource_tracker._use_simple_format = %s + + # Test various patterns of colons in names + test_names = [ + "a:b", + "a:b:c", + "test:name:with:many:colons", + ":starts:with:colon", + "ends:with:colon:", + "::double::colons::", + "name\\nwithnewline", + "name-with-trailing-newline\\n", + "\\nname-starts-with-newline", + "colons:and\\nnewlines:mix", + "multi\\nline\\nname", + ] + + for name in test_names: + try: + shm = shared_memory.SharedMemory(create=True, size=100, name=name) + shm.buf[:5] = b'hello' # Write something to the shared memory + shm.close() + shm.unlink() + + except Exception as e: + print(f"Error with name '{name}': {e}", file=sys.stderr) + sys.exit(1) + + print("SUCCESS") + """ % use_simple_format) + + rc, out, err = script_helper.assert_python_ok("-c", test_script) + self.assertIn(b"SUCCESS", out) + self.assertNotIn(b"traceback", err.lower(), err) + self.assertNotIn(b"resource_tracker.py", err, err) diff --git a/Lib/test/mp_fork_bomb.py b/Lib/test/mp_fork_bomb.py new file mode 100644 index 00000000000..017e010ba0e --- /dev/null +++ b/Lib/test/mp_fork_bomb.py @@ -0,0 +1,18 @@ +import multiprocessing, sys + +def foo(): + print("123") + +# Because "if __name__ == '__main__'" is missing this will not work +# correctly on Windows. However, we should get a RuntimeError rather +# than the Windows equivalent of a fork bomb. + +if len(sys.argv) > 1: + multiprocessing.set_start_method(sys.argv[1]) +else: + multiprocessing.set_start_method('spawn') + +p = multiprocessing.Process(target=foo) +p.start() +p.join() +sys.exit(p.exitcode) diff --git a/Lib/test/mp_preload.py b/Lib/test/mp_preload.py new file mode 100644 index 00000000000..5314e8f0b21 --- /dev/null +++ b/Lib/test/mp_preload.py @@ -0,0 +1,18 @@ +import multiprocessing + +multiprocessing.Lock() + + +def f(): + print("ok") + + +if __name__ == "__main__": + ctx = multiprocessing.get_context("forkserver") + modname = "test.mp_preload" + # Make sure it's importable + __import__(modname) + ctx.set_forkserver_preload([modname]) + proc = ctx.Process(target=f) + proc.start() + proc.join() diff --git a/Lib/test/mp_preload_flush.py b/Lib/test/mp_preload_flush.py new file mode 100644 index 00000000000..c195a9ef6b2 --- /dev/null +++ b/Lib/test/mp_preload_flush.py @@ -0,0 +1,11 @@ +import multiprocessing +import sys + +print(__name__, end='', file=sys.stderr) +print(__name__, end='', file=sys.stdout) +if __name__ == '__main__': + multiprocessing.set_start_method('forkserver') + for _ in range(2): + p = multiprocessing.Process() + p.start() + p.join() diff --git a/Lib/test/test_importlib/test_threaded_import.py b/Lib/test/test_importlib/test_threaded_import.py index 148b2e4370b..3ceb86cbea3 100644 --- a/Lib/test/test_importlib/test_threaded_import.py +++ b/Lib/test/test_importlib/test_threaded_import.py @@ -256,8 +256,7 @@ def test_concurrent_futures_circular_import(self): 'partial', 'cfimport.py') script_helper.assert_python_ok(fn) - @unittest.skipUnless(hasattr(_multiprocessing, "SemLock"), "TODO: RUSTPYTHON, pool_in_threads.py needs _multiprocessing.SemLock") - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + @unittest.skip("TODO: RUSTPYTHON - fails on Linux due to multiprocessing issues") def test_multiprocessing_pool_circular_import(self): # Regression test for bpo-41567 fn = os.path.join(os.path.dirname(__file__), diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 9004e9ed744..529cb2dc2f1 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -4058,7 +4058,8 @@ def _mpinit_issue121723(qspec, message_to_log): # log a message (this creates a record put in the queue) logging.getLogger().info(message_to_log) - @unittest.expectedFailure # TODO: RUSTPYTHON; ImportError: cannot import name 'SemLock' + # TODO: RUSTPYTHON - SemLock not implemented on Windows + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @skip_if_tsan_fork @support.requires_subprocess() def test_multiprocessing_queues(self): @@ -4118,7 +4119,8 @@ def test_90195(self): # Logger should be enabled, since explicitly mentioned self.assertFalse(logger.disabled) - @unittest.expectedFailure # TODO: RUSTPYTHON; ImportError: cannot import name 'SemLock' + # TODO: RUSTPYTHON - SemLock not implemented on Windows + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_111615(self): # See gh-111615 import_helper.import_module('_multiprocessing') # see gh-113692 diff --git a/crates/stdlib/src/multiprocessing.rs b/crates/stdlib/src/multiprocessing.rs index 9ff2d3dc318..21b7bfa85c7 100644 --- a/crates/stdlib/src/multiprocessing.rs +++ b/crates/stdlib/src/multiprocessing.rs @@ -41,6 +41,696 @@ mod _multiprocessing { } } -#[cfg(not(windows))] +// Unix platforms (Linux, macOS, etc.) +// macOS has broken sem_timedwait/sem_getvalue - we use polled fallback +#[cfg(unix)] +#[pymodule] +mod _multiprocessing { + use crate::vm::{ + Context, FromArgs, Py, PyPayload, PyRef, PyResult, VirtualMachine, + builtins::{PyBaseExceptionRef, PyDict, PyType, PyTypeRef}, + function::{FuncArgs, KwArgs}, + types::Constructor, + }; + use libc::sem_t; + use nix::errno::Errno; + use std::{ + ffi::CString, + sync::atomic::{AtomicI32, AtomicU64, Ordering}, + }; + + /// Error type for sem_timedwait operations + #[cfg(target_vendor = "apple")] + enum SemWaitError { + Timeout, + SignalException(PyBaseExceptionRef), + OsError(Errno), + } + + /// macOS fallback for sem_timedwait using select + sem_trywait polling + /// Matches sem_timedwait_save in semaphore.c + #[cfg(target_vendor = "apple")] + fn sem_timedwait_polled( + sem: *mut sem_t, + deadline: &libc::timespec, + vm: &VirtualMachine, + ) -> Result<(), SemWaitError> { + let mut delay: u64 = 0; + + loop { + // poll: try to acquire + if unsafe { libc::sem_trywait(sem) } == 0 { + return Ok(()); + } + let err = Errno::last(); + if err != Errno::EAGAIN { + return Err(SemWaitError::OsError(err)); + } + + // get current time + let mut now = libc::timeval { + tv_sec: 0, + tv_usec: 0, + }; + if unsafe { libc::gettimeofday(&mut now, std::ptr::null_mut()) } < 0 { + return Err(SemWaitError::OsError(Errno::last())); + } + + // check for timeout + let deadline_usec = deadline.tv_sec * 1_000_000 + deadline.tv_nsec / 1000; + #[allow(clippy::unnecessary_cast)] + let now_usec = now.tv_sec as i64 * 1_000_000 + now.tv_usec as i64; + + if now_usec >= deadline_usec { + return Err(SemWaitError::Timeout); + } + + // calculate how much time is left + let difference = (deadline_usec - now_usec) as u64; + + // check delay not too long -- maximum is 20 msecs + delay += 1000; + if delay > 20000 { + delay = 20000; + } + if delay > difference { + delay = difference; + } + + // sleep using select + let mut tv_delay = libc::timeval { + tv_sec: (delay / 1_000_000) as _, + tv_usec: (delay % 1_000_000) as _, + }; + unsafe { + libc::select( + 0, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut tv_delay, + ) + }; + + // check for signals - preserve the exception (e.g., KeyboardInterrupt) + if let Err(exc) = vm.check_signals() { + return Err(SemWaitError::SignalException(exc)); + } + } + } + + // These match the values in Lib/multiprocessing/synchronize.py + const RECURSIVE_MUTEX: i32 = 0; + const SEMAPHORE: i32 = 1; + + // #define ISMINE(o) (o->count > 0 && PyThread_get_thread_ident() == o->last_tid) + macro_rules! ismine { + ($self:expr) => { + $self.count.load(Ordering::Acquire) > 0 + && $self.last_tid.load(Ordering::Acquire) == current_thread_id() + }; + } + + #[derive(FromArgs)] + struct SemLockNewArgs { + #[pyarg(positional)] + kind: i32, + #[pyarg(positional)] + value: i32, + #[pyarg(positional)] + maxvalue: i32, + #[pyarg(positional)] + name: String, + #[pyarg(positional)] + unlink: bool, + } + + #[pyattr] + #[pyclass(name = "SemLock", module = "_multiprocessing")] + #[derive(Debug, PyPayload)] + struct SemLock { + handle: SemHandle, + kind: i32, + maxvalue: i32, + name: Option<String>, + last_tid: AtomicU64, // unsigned long + count: AtomicI32, // int + } + + #[derive(Debug)] + struct SemHandle { + raw: *mut sem_t, + } + + unsafe impl Send for SemHandle {} + unsafe impl Sync for SemHandle {} + + impl SemHandle { + fn create( + name: &str, + value: u32, + unlink: bool, + vm: &VirtualMachine, + ) -> PyResult<(Self, Option<String>)> { + let cname = semaphore_name(vm, name)?; + // SEM_CREATE(name, val, max) sem_open(name, O_CREAT | O_EXCL, 0600, val) + let raw = unsafe { + libc::sem_open(cname.as_ptr(), libc::O_CREAT | libc::O_EXCL, 0o600, value) + }; + if raw == libc::SEM_FAILED { + let err = Errno::last(); + return Err(os_error(vm, err)); + } + if unlink { + // SEM_UNLINK(name) sem_unlink(name) + unsafe { + libc::sem_unlink(cname.as_ptr()); + } + Ok((SemHandle { raw }, None)) + } else { + Ok((SemHandle { raw }, Some(name.to_owned()))) + } + } + + fn open_existing(name: &str, vm: &VirtualMachine) -> PyResult<Self> { + let cname = semaphore_name(vm, name)?; + let raw = unsafe { libc::sem_open(cname.as_ptr(), 0) }; + if raw == libc::SEM_FAILED { + let err = Errno::last(); + return Err(os_error(vm, err)); + } + Ok(SemHandle { raw }) + } + + #[inline] + fn as_ptr(&self) -> *mut sem_t { + self.raw + } + } + + impl Drop for SemHandle { + fn drop(&mut self) { + // Guard against default/uninitialized state. + // Note: SEM_FAILED is (sem_t*)-1, not null, but valid handles are never null + // and SEM_FAILED is never stored (error is returned immediately on sem_open failure). + if !self.raw.is_null() { + // SEM_CLOSE(sem) sem_close(sem) + unsafe { + libc::sem_close(self.raw); + } + } + } + } + + #[pyclass(with(Constructor), flags(BASETYPE))] + impl SemLock { + #[pygetset] + fn handle(&self) -> isize { + self.handle.as_ptr() as isize + } + + #[pygetset] + fn kind(&self) -> i32 { + self.kind + } + + #[pygetset] + fn maxvalue(&self) -> i32 { + self.maxvalue + } + + #[pygetset] + fn name(&self) -> Option<String> { + self.name.clone() + } + + /// Acquire the semaphore/lock. + // _multiprocessing_SemLock_acquire_impl + #[pymethod] + fn acquire(&self, args: FuncArgs, vm: &VirtualMachine) -> PyResult<bool> { + // block=True, timeout=None + + let blocking: bool = args + .kwargs + .get("block") + .or_else(|| args.args.first()) + .map(|o| o.clone().try_to_bool(vm)) + .transpose()? + .unwrap_or(true); + + let timeout_obj = args + .kwargs + .get("timeout") + .or_else(|| args.args.get(1)) + .cloned(); + + if self.kind == RECURSIVE_MUTEX && ismine!(self) { + self.count.fetch_add(1, Ordering::Release); + return Ok(true); + } + + // timeout_obj != Py_None + let use_deadline = timeout_obj.as_ref().is_some_and(|o| !vm.is_none(o)); + + let deadline = if use_deadline { + let timeout_obj = timeout_obj.unwrap(); + // This accepts both int and float, converting to f64 + let timeout: f64 = timeout_obj.try_float(vm)?.to_f64(); + let timeout = if timeout < 0.0 { 0.0 } else { timeout }; + + let mut tv = libc::timeval { + tv_sec: 0, + tv_usec: 0, + }; + let res = unsafe { libc::gettimeofday(&mut tv, std::ptr::null_mut()) }; + if res < 0 { + return Err(vm.new_os_error("gettimeofday failed".to_string())); + } + + // deadline calculation: + // long sec = (long) timeout; + // long nsec = (long) (1e9 * (timeout - sec) + 0.5); + // deadline.tv_sec = now.tv_sec + sec; + // deadline.tv_nsec = now.tv_usec * 1000 + nsec; + // deadline.tv_sec += (deadline.tv_nsec / 1000000000); + // deadline.tv_nsec %= 1000000000; + let sec = timeout as libc::c_long; + let nsec = (1e9 * (timeout - sec as f64) + 0.5) as libc::c_long; + let mut deadline = libc::timespec { + tv_sec: tv.tv_sec + sec as libc::time_t, + tv_nsec: (tv.tv_usec as libc::c_long * 1000 + nsec) as _, + }; + deadline.tv_sec += (deadline.tv_nsec / 1_000_000_000) as libc::time_t; + deadline.tv_nsec %= 1_000_000_000; + Some(deadline) + } else { + None + }; + + // Check whether we can acquire without releasing the GIL and blocking + let mut res; + loop { + res = unsafe { libc::sem_trywait(self.handle.as_ptr()) }; + if res >= 0 { + break; + } + let err = Errno::last(); + if err == Errno::EINTR { + vm.check_signals()?; + continue; + } + break; + } + + // if (res < 0 && errno == EAGAIN && blocking) + if res < 0 && Errno::last() == Errno::EAGAIN && blocking { + // Couldn't acquire immediately, need to block + #[cfg(not(target_vendor = "apple"))] + { + loop { + // Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS + // RustPython doesn't have GIL, so we just do the wait + if let Some(ref dl) = deadline { + res = unsafe { libc::sem_timedwait(self.handle.as_ptr(), dl) }; + } else { + res = unsafe { libc::sem_wait(self.handle.as_ptr()) }; + } + + if res >= 0 { + break; + } + let err = Errno::last(); + if err == Errno::EINTR { + vm.check_signals()?; + continue; + } + break; + } + } + #[cfg(target_vendor = "apple")] + { + // macOS: use polled fallback since sem_timedwait is not available + if let Some(ref dl) = deadline { + match sem_timedwait_polled(self.handle.as_ptr(), dl, vm) { + Ok(()) => res = 0, + Err(SemWaitError::Timeout) => { + // Timeout occurred - return false directly + return Ok(false); + } + Err(SemWaitError::SignalException(exc)) => { + // Propagate the original exception (e.g., KeyboardInterrupt) + return Err(exc); + } + Err(SemWaitError::OsError(e)) => { + return Err(os_error(vm, e)); + } + } + } else { + // No timeout: use sem_wait (available on macOS) + loop { + res = unsafe { libc::sem_wait(self.handle.as_ptr()) }; + if res >= 0 { + break; + } + let err = Errno::last(); + if err == Errno::EINTR { + vm.check_signals()?; + continue; + } + break; + } + } + } + } + + // result handling: + if res < 0 { + let err = Errno::last(); + match err { + Errno::EAGAIN | Errno::ETIMEDOUT => return Ok(false), + Errno::EINTR => { + // EINTR should be handled by the check_signals() loop above + // If we reach here, check signals again and propagate any exception + return vm.check_signals().map(|_| false); + } + _ => return Err(os_error(vm, err)), + } + } + + self.count.fetch_add(1, Ordering::Release); + self.last_tid.store(current_thread_id(), Ordering::Release); + + Ok(true) + } + + /// Release the semaphore/lock. + // _multiprocessing_SemLock_release_impl + #[pymethod] + fn release(&self, vm: &VirtualMachine) -> PyResult<()> { + if self.kind == RECURSIVE_MUTEX { + // if (!ISMINE(self)) + if !ismine!(self) { + return Err(vm.new_exception_msg( + vm.ctx.exceptions.assertion_error.to_owned(), + "attempt to release recursive lock not owned by thread".to_owned(), + )); + } + // if (self->count > 1) { --self->count; Py_RETURN_NONE; } + if self.count.load(Ordering::Acquire) > 1 { + self.count.fetch_sub(1, Ordering::Release); + return Ok(()); + } + // assert(self->count == 1); + } else { + // SEMAPHORE case: check value before releasing + #[cfg(not(target_vendor = "apple"))] + { + // Linux: use sem_getvalue + let mut sval: libc::c_int = 0; + let res = unsafe { libc::sem_getvalue(self.handle.as_ptr(), &mut sval) }; + if res < 0 { + return Err(os_error(vm, Errno::last())); + } + if sval >= self.maxvalue { + return Err(vm.new_value_error( + "semaphore or lock released too many times".to_owned(), + )); + } + } + #[cfg(target_vendor = "apple")] + { + // macOS: HAVE_BROKEN_SEM_GETVALUE + // We will only check properly the maxvalue == 1 case + if self.maxvalue == 1 { + // make sure that already locked + if unsafe { libc::sem_trywait(self.handle.as_ptr()) } < 0 { + if Errno::last() != Errno::EAGAIN { + return Err(os_error(vm, Errno::last())); + } + // it is already locked as expected + } else { + // it was not locked so undo wait and raise + if unsafe { libc::sem_post(self.handle.as_ptr()) } < 0 { + return Err(os_error(vm, Errno::last())); + } + return Err(vm.new_value_error( + "semaphore or lock released too many times".to_owned(), + )); + } + } + } + } + + let res = unsafe { libc::sem_post(self.handle.as_ptr()) }; + if res < 0 { + return Err(os_error(vm, Errno::last())); + } + + self.count.fetch_sub(1, Ordering::Release); + Ok(()) + } + + /// Enter the semaphore/lock (context manager). + // _multiprocessing_SemLock___enter___impl + #[pymethod(name = "__enter__")] + fn enter(&self, vm: &VirtualMachine) -> PyResult<bool> { + // return _multiprocessing_SemLock_acquire_impl(self, 1, Py_None); + self.acquire( + FuncArgs::new::<Vec<_>, KwArgs>( + vec![vm.ctx.new_bool(true).into()], + KwArgs::default(), + ), + vm, + ) + } + + /// Exit the semaphore/lock (context manager). + // _multiprocessing_SemLock___exit___impl + #[pymethod] + fn __exit__(&self, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + self.release(vm) + } + + /// Rebuild a SemLock from pickled state. + // _multiprocessing_SemLock__rebuild_impl + #[pyclassmethod(name = "_rebuild")] + fn rebuild( + cls: PyTypeRef, + _handle: isize, + kind: i32, + maxvalue: i32, + name: Option<String>, + vm: &VirtualMachine, + ) -> PyResult { + let Some(ref name_str) = name else { + return Err(vm.new_value_error("cannot rebuild SemLock without name".to_owned())); + }; + let handle = SemHandle::open_existing(name_str, vm)?; + // return newsemlockobject(type, handle, kind, maxvalue, name_copy); + let zelf = SemLock { + handle, + kind, + maxvalue, + name, + last_tid: AtomicU64::new(0), + count: AtomicI32::new(0), + }; + zelf.into_ref_with_type(vm, cls).map(Into::into) + } + + /// Rezero the net acquisition count after fork(). + // _multiprocessing_SemLock__after_fork_impl + #[pymethod] + fn _after_fork(&self) { + self.count.store(0, Ordering::Release); + // Also reset last_tid for safety + self.last_tid.store(0, Ordering::Release); + } + + /// Num of `acquire()`s minus num of `release()`s for this process. + // _multiprocessing_SemLock__count_impl + #[pymethod] + fn _count(&self) -> i32 { + self.count.load(Ordering::Acquire) + } + + /// Whether the lock is owned by this thread. + // _multiprocessing_SemLock__is_mine_impl + #[pymethod] + fn _is_mine(&self) -> bool { + ismine!(self) + } + + /// Get the value of the semaphore. + // _multiprocessing_SemLock__get_value_impl + #[pymethod] + fn _get_value(&self, vm: &VirtualMachine) -> PyResult<i32> { + #[cfg(not(target_vendor = "apple"))] + { + // Linux: use sem_getvalue + let mut sval: libc::c_int = 0; + let res = unsafe { libc::sem_getvalue(self.handle.as_ptr(), &mut sval) }; + if res < 0 { + return Err(os_error(vm, Errno::last())); + } + // some posix implementations use negative numbers to indicate + // the number of waiting threads + Ok(if sval < 0 { 0 } else { sval }) + } + #[cfg(target_vendor = "apple")] + { + // macOS: HAVE_BROKEN_SEM_GETVALUE - raise NotImplementedError + Err(vm.new_not_implemented_error(String::new())) + } + } + + /// Return whether semaphore has value zero. + // _multiprocessing_SemLock__is_zero_impl + #[pymethod] + fn _is_zero(&self, vm: &VirtualMachine) -> PyResult<bool> { + #[cfg(not(target_vendor = "apple"))] + { + Ok(self._get_value(vm)? == 0) + } + #[cfg(target_vendor = "apple")] + { + // macOS: HAVE_BROKEN_SEM_GETVALUE + // Try to acquire - if EAGAIN, value is 0 + if unsafe { libc::sem_trywait(self.handle.as_ptr()) } < 0 { + if Errno::last() == Errno::EAGAIN { + return Ok(true); + } + return Err(os_error(vm, Errno::last())); + } + // Successfully acquired - undo and return false + if unsafe { libc::sem_post(self.handle.as_ptr()) } < 0 { + return Err(os_error(vm, Errno::last())); + } + Ok(false) + } + } + + #[extend_class] + fn extend_class(ctx: &Context, class: &Py<PyType>) { + class.set_attr( + ctx.intern_str("RECURSIVE_MUTEX"), + ctx.new_int(RECURSIVE_MUTEX).into(), + ); + class.set_attr(ctx.intern_str("SEMAPHORE"), ctx.new_int(SEMAPHORE).into()); + // SEM_VALUE_MAX from system, or INT_MAX if negative + // We use a reasonable default + let sem_value_max: i32 = unsafe { + let val = libc::sysconf(libc::_SC_SEM_VALUE_MAX); + if val < 0 || val > i32::MAX as libc::c_long { + i32::MAX + } else { + val as i32 + } + }; + class.set_attr( + ctx.intern_str("SEM_VALUE_MAX"), + ctx.new_int(sem_value_max).into(), + ); + } + } + + impl Constructor for SemLock { + type Args = SemLockNewArgs; + + // Create a new SemLock. + // _multiprocessing_SemLock_impl + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { + if args.kind != RECURSIVE_MUTEX && args.kind != SEMAPHORE { + return Err(vm.new_value_error("unrecognized kind".to_owned())); + } + // Value validation + if args.value < 0 || args.value > args.maxvalue { + return Err(vm.new_value_error("invalid value".to_owned())); + } + + let value = args.value as u32; + let (handle, name) = SemHandle::create(&args.name, value, args.unlink, vm)?; + + // return newsemlockobject(type, handle, kind, maxvalue, name_copy); + Ok(SemLock { + handle, + kind: args.kind, + maxvalue: args.maxvalue, + name, + last_tid: AtomicU64::new(0), + count: AtomicI32::new(0), + }) + } + } + + /// Function to unlink semaphore names. + // _PyMp_sem_unlink. + #[pyfunction] + fn sem_unlink(name: String, vm: &VirtualMachine) -> PyResult<()> { + let cname = semaphore_name(vm, &name)?; + let res = unsafe { libc::sem_unlink(cname.as_ptr()) }; + if res < 0 { + return Err(os_error(vm, Errno::last())); + } + Ok(()) + } + + /// Module-level flags dict. + #[pyattr] + fn flags(vm: &VirtualMachine) -> PyRef<PyDict> { + let flags = vm.ctx.new_dict(); + // HAVE_SEM_OPEN is always 1 on Unix (we wouldn't be here otherwise) + flags + .set_item("HAVE_SEM_OPEN", vm.ctx.new_int(1).into(), vm) + .unwrap(); + + #[cfg(not(target_vendor = "apple"))] + { + // Linux: HAVE_SEM_TIMEDWAIT is available + flags + .set_item("HAVE_SEM_TIMEDWAIT", vm.ctx.new_int(1).into(), vm) + .unwrap(); + } + + #[cfg(target_vendor = "apple")] + { + // macOS: sem_getvalue is broken + flags + .set_item("HAVE_BROKEN_SEM_GETVALUE", vm.ctx.new_int(1).into(), vm) + .unwrap(); + } + + flags + } + + fn semaphore_name(vm: &VirtualMachine, name: &str) -> PyResult<CString> { + // POSIX semaphore names must start with / + let mut full = String::with_capacity(name.len() + 1); + if !name.starts_with('/') { + full.push('/'); + } + full.push_str(name); + CString::new(full).map_err(|_| vm.new_value_error("embedded null character".to_owned())) + } + + fn os_error(vm: &VirtualMachine, err: Errno) -> PyBaseExceptionRef { + // _PyMp_SetError maps to PyErr_SetFromErrno + let exc_type = match err { + Errno::EEXIST => vm.ctx.exceptions.file_exists_error.to_owned(), + Errno::ENOENT => vm.ctx.exceptions.file_not_found_error.to_owned(), + _ => vm.ctx.exceptions.os_error.to_owned(), + }; + vm.new_os_subtype_error(exc_type, Some(err as i32), err.desc().to_owned()) + .upcast() + } + + /// Get current thread identifier. + /// PyThread_get_thread_ident on Unix (pthread_self). + fn current_thread_id() -> u64 { + unsafe { libc::pthread_self() as u64 } + } +} + +#[cfg(all(not(unix), not(windows)))] #[pymodule] mod _multiprocessing {} diff --git a/crates/stdlib/src/posixshmem.rs b/crates/stdlib/src/posixshmem.rs index 53bf372532d..a52866b7985 100644 --- a/crates/stdlib/src/posixshmem.rs +++ b/crates/stdlib/src/posixshmem.rs @@ -8,25 +8,27 @@ mod _posixshmem { use crate::{ common::os::errno_io_error, - vm::{ - PyResult, VirtualMachine, builtins::PyStrRef, convert::IntoPyException, - function::OptionalArg, - }, + vm::{FromArgs, PyResult, VirtualMachine, builtins::PyStrRef, convert::IntoPyException}, }; - #[pyfunction] - fn shm_open( + #[derive(FromArgs)] + struct ShmOpenArgs { + #[pyarg(any)] name: PyStrRef, + #[pyarg(any)] flags: libc::c_int, - mode: OptionalArg<libc::mode_t>, - vm: &VirtualMachine, - ) -> PyResult<libc::c_int> { - let name = CString::new(name.as_str()).map_err(|e| e.into_pyexception(vm))?; - let mode: libc::c_uint = mode.unwrap_or(0o600) as _; + #[pyarg(any, default = 0o600)] + mode: libc::mode_t, + } + + #[pyfunction] + fn shm_open(args: ShmOpenArgs, vm: &VirtualMachine) -> PyResult<libc::c_int> { + let name = CString::new(args.name.as_str()).map_err(|e| e.into_pyexception(vm))?; + let mode: libc::c_uint = args.mode as _; #[cfg(target_os = "freebsd")] let mode = mode.try_into().unwrap(); // SAFETY: `name` is a NUL-terminated string and `shm_open` does not write through it. - let fd = unsafe { libc::shm_open(name.as_ptr(), flags, mode) }; + let fd = unsafe { libc::shm_open(name.as_ptr(), args.flags, mode) }; if fd == -1 { Err(errno_io_error().into_pyexception(vm)) } else { diff --git a/crates/stdlib/src/pystruct.rs b/crates/stdlib/src/pystruct.rs index 34a4905ed9f..8801f0d705e 100644 --- a/crates/stdlib/src/pystruct.rs +++ b/crates/stdlib/src/pystruct.rs @@ -71,7 +71,7 @@ pub(crate) mod _struct { } else { ("unpack_from", "unpacking") }; - if offset >= buffer_len { + if offset + needed > buffer_len { let msg = format!( "{op} requires a buffer of at least {required} bytes for {op_action} {needed} \ bytes at offset {offset} (actual buffer size is {buffer_len})", From 0eddee500ace0102b090abd13df94b01158e2d75 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:15:31 +0900 Subject: [PATCH 670/819] fix fcntl, sslsocket traverse, super , excepthook (#6623) * fix fcntl * fix traverse * fix super * excepthook * fix warn --- Lib/test/test_super.py | 2 - crates/stdlib/src/fcntl.rs | 6 +- crates/stdlib/src/ssl.rs | 12 ++- crates/vm/src/builtins/super.rs | 7 +- crates/vm/src/stdlib/thread.rs | 132 ++++++++++++++++++++++++++++++++ crates/vm/src/warn.rs | 14 +++- 6 files changed, 166 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index 76eda799da4..5548f4c71a2 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -344,7 +344,6 @@ def test_super_argcount(self): with self.assertRaisesRegex(TypeError, "expected at most"): super(int, int, int) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "argument 1 must be a type" does not match "Expected type 'type' but 'int' found." def test_super_argtype(self): with self.assertRaisesRegex(TypeError, "argument 1 must be a type"): super(1, int) @@ -409,7 +408,6 @@ def method(self): with self.assertRaisesRegex(AttributeError, "'super' object has no attribute 'msg'"): C().method() - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "argument 1 must be a type" does not match "Expected type 'type' but 'int' found." def test_bad_first_arg(self): class C: def method(self): diff --git a/crates/stdlib/src/fcntl.rs b/crates/stdlib/src/fcntl.rs index 822faeeedaa..477c1f54210 100644 --- a/crates/stdlib/src/fcntl.rs +++ b/crates/stdlib/src/fcntl.rs @@ -92,11 +92,15 @@ mod fcntl { #[pyfunction] fn ioctl( io::Fildes(fd): io::Fildes, - request: u32, + request: i64, arg: OptionalArg<Either<Either<ArgMemoryBuffer, ArgStrOrBytesLike>, i32>>, mutate_flag: OptionalArg<bool>, vm: &VirtualMachine, ) -> PyResult { + // Convert to unsigned - handles both positive u32 values and negative i32 values + // that represent the same bit pattern (e.g., TIOCSWINSZ on some platforms). + // First truncate to u32 (takes lower 32 bits), then zero-extend to c_ulong. + let request = (request as u32) as libc::c_ulong; let arg = arg.unwrap_or_else(|| Either::B(0)); match arg { Either::A(buf_kind) => { diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index b90176a62fa..c22dd303c59 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -2293,7 +2293,7 @@ mod _ssl { // SSLSocket - represents a TLS-wrapped socket #[pyattr] - #[pyclass(name = "_SSLSocket", module = "ssl")] + #[pyclass(name = "_SSLSocket", module = "ssl", traverse)] #[derive(Debug, PyPayload)] pub(crate) struct PySSLSocket { // Underlying socket @@ -2301,14 +2301,19 @@ mod _ssl { // SSL context context: PyRwLock<PyRef<PySSLContext>>, // Server-side or client-side + #[pytraverse(skip)] server_side: bool, // Server hostname for SNI + #[pytraverse(skip)] server_hostname: PyRwLock<Option<String>>, // TLS connection state + #[pytraverse(skip)] connection: PyMutex<Option<TlsConnection>>, // Handshake completed flag + #[pytraverse(skip)] handshake_done: PyMutex<bool>, // Session was reused (for session resumption tracking) + #[pytraverse(skip)] session_was_reused: PyMutex<bool>, // Owner (SSLSocket instance that owns this _SSLSocket) owner: PyRwLock<Option<PyObjectRef>>, @@ -2316,22 +2321,27 @@ mod _ssl { session: PyRwLock<Option<PyObjectRef>>, // Verified certificate chain (built during verification) #[allow(dead_code)] + #[pytraverse(skip)] verified_chain: PyRwLock<Option<Vec<CertificateDer<'static>>>>, // MemoryBIO mode (optional) incoming_bio: Option<PyRef<PyMemoryBIO>>, outgoing_bio: Option<PyRef<PyMemoryBIO>>, // SNI certificate resolver state (for server-side only) + #[pytraverse(skip)] sni_state: PyRwLock<Option<Arc<ParkingMutex<SniCertName>>>>, // Pending context change (for SNI callback deferred handling) pending_context: PyRwLock<Option<PyRef<PySSLContext>>>, // Buffer to store ClientHello for connection recreation + #[pytraverse(skip)] client_hello_buffer: PyMutex<Option<Vec<u8>>>, // Shutdown state for tracking close-notify exchange + #[pytraverse(skip)] shutdown_state: PyMutex<ShutdownState>, // Deferred client certificate verification error (for TLS 1.3) // Stores error message if client cert verification failed during handshake // Error is raised on first I/O operation after handshake // Using Arc to share with the certificate verifier + #[pytraverse(skip)] deferred_cert_error: Arc<ParkingRwLock<Option<String>>>, } diff --git a/crates/vm/src/builtins/super.rs b/crates/vm/src/builtins/super.rs index f0d873abfbb..893509bc6d3 100644 --- a/crates/vm/src/builtins/super.rs +++ b/crates/vm/src/builtins/super.rs @@ -61,7 +61,7 @@ impl Constructor for PySuper { #[derive(FromArgs)] pub struct InitArgs { #[pyarg(positional, optional)] - py_type: OptionalArg<PyTypeRef>, + py_type: OptionalArg<PyObjectRef>, #[pyarg(positional, optional)] py_obj: OptionalArg<PyObjectRef>, } @@ -75,7 +75,10 @@ impl Initializer for PySuper { vm: &VirtualMachine, ) -> PyResult<()> { // Get the type: - let (typ, obj) = if let OptionalArg::Present(ty) = py_type { + let (typ, obj) = if let OptionalArg::Present(ty_obj) = py_type { + let ty = ty_obj + .downcast::<PyType>() + .map_err(|_| vm.new_type_error("super() argument 1 must be a type"))?; (ty, py_obj.unwrap_or_none(vm)) } else { let frame = vm diff --git a/crates/vm/src/stdlib/thread.rs b/crates/vm/src/stdlib/thread.rs index eb881911659..b7f00537c26 100644 --- a/crates/vm/src/stdlib/thread.rs +++ b/crates/vm/src/stdlib/thread.rs @@ -400,6 +400,138 @@ pub(crate) mod _thread { vm.state.thread_count.load() } + /// ExceptHookArgs - simple class to hold exception hook arguments + /// This allows threading.py to import _excepthook and _ExceptHookArgs from _thread + #[pyattr] + #[pyclass(module = "_thread", name = "_ExceptHookArgs")] + #[derive(Debug, PyPayload)] + struct ExceptHookArgs { + exc_type: crate::PyObjectRef, + exc_value: crate::PyObjectRef, + exc_traceback: crate::PyObjectRef, + thread: crate::PyObjectRef, + } + + #[pyclass(with(Constructor))] + impl ExceptHookArgs { + #[pygetset] + fn exc_type(&self) -> crate::PyObjectRef { + self.exc_type.clone() + } + + #[pygetset] + fn exc_value(&self) -> crate::PyObjectRef { + self.exc_value.clone() + } + + #[pygetset] + fn exc_traceback(&self) -> crate::PyObjectRef { + self.exc_traceback.clone() + } + + #[pygetset] + fn thread(&self) -> crate::PyObjectRef { + self.thread.clone() + } + } + + impl Constructor for ExceptHookArgs { + // Takes a single iterable argument like namedtuple + type Args = (crate::PyObjectRef,); + + fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> { + // Convert the argument to a list/tuple and extract elements + let seq: Vec<crate::PyObjectRef> = args.0.try_to_value(vm)?; + if seq.len() != 4 { + return Err(vm.new_type_error(format!( + "_ExceptHookArgs expected 4 arguments, got {}", + seq.len() + ))); + } + Ok(Self { + exc_type: seq[0].clone(), + exc_value: seq[1].clone(), + exc_traceback: seq[2].clone(), + thread: seq[3].clone(), + }) + } + } + + /// Handle uncaught exception in Thread.run() + #[pyfunction] + fn _excepthook(args: crate::PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // Type check: args must be _ExceptHookArgs + let args = args.downcast::<ExceptHookArgs>().map_err(|_| { + vm.new_type_error( + "_thread._excepthook argument type must be _ExceptHookArgs".to_owned(), + ) + })?; + + let exc_type = args.exc_type.clone(); + let exc_value = args.exc_value.clone(); + let exc_traceback = args.exc_traceback.clone(); + let thread = args.thread.clone(); + + // Silently ignore SystemExit (identity check) + if exc_type.is(vm.ctx.exceptions.system_exit.as_ref()) { + return Ok(()); + } + + // Get stderr - fall back to thread._stderr if sys.stderr is None + let file = match vm.sys_module.get_attr("stderr", vm) { + Ok(stderr) if !vm.is_none(&stderr) => stderr, + _ => { + if vm.is_none(&thread) { + // do nothing if sys.stderr is None and thread is None + return Ok(()); + } + let thread_stderr = thread.get_attr("_stderr", vm)?; + if vm.is_none(&thread_stderr) { + // do nothing if sys.stderr is None and sys.stderr was None + // when the thread was created + return Ok(()); + } + thread_stderr + } + }; + + // Print "Exception in thread {thread.name}:" + let thread_name = if !vm.is_none(&thread) { + thread + .get_attr("name", vm) + .ok() + .and_then(|n| n.str(vm).ok()) + .map(|s| s.as_str().to_owned()) + } else { + None + }; + let name = thread_name.unwrap_or_else(|| format!("{}", get_ident())); + + let _ = vm.call_method( + &file, + "write", + (format!("Exception in thread {}:\n", name),), + ); + + // Display the traceback + if let Ok(traceback_mod) = vm.import("traceback", 0) + && let Ok(print_exc) = traceback_mod.get_attr("print_exception", vm) + { + use crate::function::KwArgs; + let kwargs: KwArgs = vec![("file".to_owned(), file.clone())] + .into_iter() + .collect(); + let _ = print_exc.call_with_args( + crate::function::FuncArgs::new(vec![exc_type, exc_value, exc_traceback], kwargs), + vm, + ); + } + + // Flush file + let _ = vm.call_method(&file, "flush", ()); + Ok(()) + } + #[pyattr] #[pyclass(module = "thread", name = "_local")] #[derive(Debug, PyPayload)] diff --git a/crates/vm/src/warn.rs b/crates/vm/src/warn.rs index 09d48078e56..b7249da19fe 100644 --- a/crates/vm/src/warn.rs +++ b/crates/vm/src/warn.rs @@ -404,8 +404,20 @@ fn setup_context( let (globals, filename, lineno) = if let Some(f) = f { (f.globals.clone(), f.code.source_path, f.f_lineno()) + } else if let Some(frame) = vm.current_frame() { + // We have a frame but it wasn't found during stack walking + (frame.globals.clone(), vm.ctx.intern_str("<sys>"), 1) } else { - (vm.current_globals().clone(), vm.ctx.intern_str("sys"), 1) + // No frames on the stack - use sys.__dict__ (interp->sysdict) + let globals = vm + .sys_module + .as_object() + .get_attr(identifier!(vm, __dict__), vm) + .and_then(|d| { + d.downcast::<crate::builtins::PyDict>() + .map_err(|_| vm.new_type_error("sys.__dict__ is not a dictionary")) + })?; + (globals, vm.ctx.intern_str("<sys>"), 0) }; let registry = if let Ok(registry) = globals.get_item(__warningregistry__, vm) { From 52dd8292c4ba23e3032731cddc808ae1f0b2c1b4 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Fri, 2 Jan 2026 18:27:18 -0500 Subject: [PATCH 671/819] Updated enum + file_cmp library + tests - v3.13.10 (#6606) * Updated enum library * Updated filecmp + file_cmp test * Removed unneeded @unittest.expectedFailures * Updated unexpected success in enum test * Fixed unexpected success in test_filecmp --- Lib/enum.py | 473 +++++++++++++++---------- Lib/filecmp.py | 23 +- Lib/test/test_ast/test_ast.py | 1 - Lib/test/test_enum.py | 645 +++++++++++++++++++++++++++------- Lib/test/test_filecmp.py | 171 +++++++-- Lib/test/test_ssl.py | 4 - Lib/test/test_uuid.py | 1 - 7 files changed, 974 insertions(+), 344 deletions(-) diff --git a/Lib/enum.py b/Lib/enum.py index 7cffb71863c..3adb208c7e6 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -1,12 +1,11 @@ import sys import builtins as bltns +from functools import partial from types import MappingProxyType, DynamicClassAttribute -from operator import or_ as _or_ -from functools import reduce __all__ = [ - 'EnumType', 'EnumMeta', + 'EnumType', 'EnumMeta', 'EnumDict', 'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum', 'auto', 'unique', 'property', 'verify', 'member', 'nonmember', 'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP', @@ -39,7 +38,7 @@ def _is_descriptor(obj): """ Returns True if obj is a descriptor, False otherwise. """ - return ( + return not isinstance(obj, partial) and ( hasattr(obj, '__get__') or hasattr(obj, '__set__') or hasattr(obj, '__delete__') @@ -63,8 +62,8 @@ def _is_sunder(name): return ( len(name) > 2 and name[0] == name[-1] == '_' and - name[1:2] != '_' and - name[-2:-1] != '_' + name[1] != '_' and + name[-2] != '_' ) def _is_internal_class(cls_name, obj): @@ -83,7 +82,6 @@ def _is_private(cls_name, name): if ( len(name) > pat_len and name.startswith(pattern) - and name[pat_len:pat_len+1] != ['_'] and (name[-1] != '_' or name[-2] != '_') ): return True @@ -158,7 +156,6 @@ def _dedent(text): Like textwrap.dedent. Rewritten because we cannot import textwrap. """ lines = text.split('\n') - blanks = 0 for i, ch in enumerate(lines[0]): if ch != ' ': break @@ -166,6 +163,11 @@ def _dedent(text): lines[j] = l[i:] return '\n'.join(lines) +class _not_given: + def __repr__(self): + return('<not given>') +_not_given = _not_given() + class _auto_null: def __repr__(self): return '_auto_null' @@ -283,9 +285,10 @@ def __set_name__(self, enum_class, member_name): enum_member._sort_order_ = len(enum_class._member_names_) if Flag is not None and issubclass(enum_class, Flag): - enum_class._flag_mask_ |= value - if _is_single_bit(value): - enum_class._singles_mask_ |= value + if isinstance(value, int): + enum_class._flag_mask_ |= value + if _is_single_bit(value): + enum_class._singles_mask_ |= value enum_class._all_bits_ = 2 ** ((enum_class._flag_mask_).bit_length()) - 1 # If another member with the same value was already defined, the @@ -313,72 +316,40 @@ def __set_name__(self, enum_class, member_name): elif ( Flag is not None and issubclass(enum_class, Flag) + and isinstance(value, int) and _is_single_bit(value) ): # no other instances found, record this member in _member_names_ enum_class._member_names_.append(member_name) - # if necessary, get redirect in place and then add it to _member_map_ - found_descriptor = None - descriptor_type = None - class_type = None - for base in enum_class.__mro__[1:]: - attr = base.__dict__.get(member_name) - if attr is not None: - if isinstance(attr, (property, DynamicClassAttribute)): - found_descriptor = attr - class_type = base - descriptor_type = 'enum' - break - elif _is_descriptor(attr): - found_descriptor = attr - descriptor_type = descriptor_type or 'desc' - class_type = class_type or base - continue - else: - descriptor_type = 'attr' - class_type = base - if found_descriptor: - redirect = property() - redirect.member = enum_member - redirect.__set_name__(enum_class, member_name) - if descriptor_type in ('enum','desc'): - # earlier descriptor found; copy fget, fset, fdel to this one. - redirect.fget = getattr(found_descriptor, 'fget', None) - redirect._get = getattr(found_descriptor, '__get__', None) - redirect.fset = getattr(found_descriptor, 'fset', None) - redirect._set = getattr(found_descriptor, '__set__', None) - redirect.fdel = getattr(found_descriptor, 'fdel', None) - redirect._del = getattr(found_descriptor, '__delete__', None) - redirect._attr_type = descriptor_type - redirect._cls_type = class_type - setattr(enum_class, member_name, redirect) - else: - setattr(enum_class, member_name, enum_member) - # now add to _member_map_ (even aliases) - enum_class._member_map_[member_name] = enum_member + + enum_class._add_member_(member_name, enum_member) try: # This may fail if value is not hashable. We can't add the value # to the map, and by-value lookups for this value will be # linear. enum_class._value2member_map_.setdefault(value, enum_member) + if value not in enum_class._hashable_values_: + enum_class._hashable_values_.append(value) except TypeError: # keep track of the value in a list so containment checks are quick enum_class._unhashable_values_.append(value) + enum_class._unhashable_values_map_.setdefault(member_name, []).append(value) -class _EnumDict(dict): +class EnumDict(dict): """ Track enum member order and ensure member names are not reused. EnumType will use the names found in self._member_names as the enumeration member names. """ - def __init__(self): + def __init__(self, cls_name=None): super().__init__() - self._member_names = {} # use a dict to keep insertion order + self._member_names = {} # use a dict -- faster look-up than a list, and keeps insertion order since 3.7 self._last_values = [] self._ignore = [] self._auto_called = False + self._cls_name = cls_name def __setitem__(self, key, value): """ @@ -389,23 +360,19 @@ def __setitem__(self, key, value): Single underscore (sunder) names are reserved. """ - if _is_internal_class(self._cls_name, value): - import warnings - warnings.warn( - "In 3.13 classes created inside an enum will not become a member. " - "Use the `member` decorator to keep the current behavior.", - DeprecationWarning, - stacklevel=2, - ) - if _is_private(self._cls_name, key): - # also do nothing, name will be a normal attribute + if self._cls_name is not None and _is_private(self._cls_name, key): + # do nothing, name will be a normal attribute pass elif _is_sunder(key): if key not in ( '_order_', '_generate_next_value_', '_numeric_repr_', '_missing_', '_ignore_', '_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_', - ): + '_add_alias_', '_add_value_alias_', + # While not in use internally, those are common for pretty + # printing and thus excluded from Enum's reservation of + # _sunder_ names + ) and not key.startswith('_repr_'): raise ValueError( '_sunder_ names, such as %r, are reserved for future Enum use' % (key, ) @@ -439,12 +406,17 @@ def __setitem__(self, key, value): elif isinstance(value, nonmember): # unwrap value here; it won't be processed by the below `else` value = value.value + elif isinstance(value, partial): + import warnings + warnings.warn('functools.partial will be a method descriptor ' + 'in future Python versions; wrap it in ' + 'enum.member() if you want to preserve the ' + 'old behavior', FutureWarning, stacklevel=2) elif _is_descriptor(value): pass - # TODO: uncomment next three lines in 3.13 - # elif _is_internal_class(self._cls_name, value): - # # do nothing, name will be a normal attribute - # pass + elif self._cls_name is not None and _is_internal_class(self._cls_name, value): + # do nothing, name will be a normal attribute + pass else: if key in self: # enum overwriting a descriptor? @@ -457,10 +429,11 @@ def __setitem__(self, key, value): if isinstance(value, auto): single = True value = (value, ) - if type(value) is tuple and any(isinstance(v, auto) for v in value): + if isinstance(value, tuple) and any(isinstance(v, auto) for v in value): # insist on an actual tuple, no subclasses, in keeping with only supporting # top-level auto() usage (not contained in any other data structure) auto_valued = [] + t = type(value) for v in value: if isinstance(v, auto): non_auto_store = False @@ -475,12 +448,21 @@ def __setitem__(self, key, value): if single: value = auto_valued[0] else: - value = tuple(auto_valued) + try: + # accepts iterable as multiple arguments? + value = t(auto_valued) + except TypeError: + # then pass them in singly + value = t(*auto_valued) self._member_names[key] = None if non_auto_store: self._last_values.append(value) super().__setitem__(key, value) + @property + def member_names(self): + return list(self._member_names) + def update(self, members, **more_members): try: for name in members.keys(): @@ -491,6 +473,8 @@ def update(self, members, **more_members): for name, value in more_members.items(): self[name] = value +_EnumDict = EnumDict # keep private name for backwards compatibility + class EnumType(type): """ @@ -502,8 +486,7 @@ def __prepare__(metacls, cls, bases, **kwds): # check that previous enum members do not exist metacls._check_for_existing_members_(cls, bases) # create the namespace dict - enum_dict = _EnumDict() - enum_dict._cls_name = cls + enum_dict = EnumDict(cls) # inherit previous flags and _generate_next_value_ function member_type, first_enum = metacls._get_mixins_(cls, bases) if first_enum is not None: @@ -564,7 +547,9 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k classdict['_member_names_'] = [] classdict['_member_map_'] = {} classdict['_value2member_map_'] = {} - classdict['_unhashable_values_'] = [] + classdict['_hashable_values_'] = [] # for comparing with non-hashable types + classdict['_unhashable_values_'] = [] # e.g. frozenset() with set() + classdict['_unhashable_values_map_'] = {} classdict['_member_type_'] = member_type # now set the __repr__ for the value classdict['_value_repr_'] = metacls._find_data_repr_(cls, bases) @@ -579,15 +564,16 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k classdict['_all_bits_'] = 0 classdict['_inverted_'] = None try: - exc = None + classdict['_%s__in_progress' % cls] = True enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) - except RuntimeError as e: - # any exceptions raised by member.__new__ will get converted to a - # RuntimeError, so get that original exception back and raise it instead - exc = e.__cause__ or e - if exc is not None: - raise exc - # + classdict['_%s__in_progress' % cls] = False + delattr(enum_class, '_%s__in_progress' % cls) + except Exception as e: + # since 3.12 the note "Error calling __set_name__ on '_proto_member' instance ..." + # is tacked on to the error instead of raising a RuntimeError, so discard it + if hasattr(e, '__notes__'): + del e.__notes__ + raise # update classdict with any changes made by __init_subclass__ classdict.update(enum_class.__dict__) # @@ -706,7 +692,7 @@ def __bool__(cls): """ return True - def __call__(cls, value, names=None, *values, module=None, qualname=None, type=None, start=1, boundary=None): + def __call__(cls, value, names=_not_given, *values, module=None, qualname=None, type=None, start=1, boundary=None): """ Either returns an existing member, or creates a new enum class. @@ -735,18 +721,18 @@ def __call__(cls, value, names=None, *values, module=None, qualname=None, type=N """ if cls._member_map_: # simple value lookup if members exist - if names: + if names is not _not_given: value = (value, names) + values return cls.__new__(cls, value) # otherwise, functional API: we're creating a new Enum type - if names is None and type is None: + if names is _not_given and type is None: # no body? no data-type? possibly wrong usage raise TypeError( f"{cls} has no members; specify `names=()` if you meant to create a new, empty, enum" ) return cls._create_( class_name=value, - names=names, + names=None if names is _not_given else names, module=module, qualname=qualname, type=type, @@ -760,10 +746,20 @@ def __contains__(cls, value): `value` is in `cls` if: 1) `value` is a member of `cls`, or 2) `value` is the value of one of the `cls`'s members. + 3) `value` is a pseudo-member (flags) """ if isinstance(value, cls): return True - return value in cls._value2member_map_ or value in cls._unhashable_values_ + if issubclass(cls, Flag): + try: + result = cls._missing_(value) + return isinstance(result, cls) + except ValueError: + pass + return ( + value in cls._unhashable_values_ # both structures are lists + or value in cls._hashable_values_ + ) def __delattr__(cls, attr): # nicer error message when someone tries to delete an attribute @@ -1059,7 +1055,55 @@ def _find_new_(mcls, classdict, member_type, first_enum): else: use_args = True return __new__, save_new, use_args -EnumMeta = EnumType + + def _add_member_(cls, name, member): + # _value_ structures are not updated + if name in cls._member_map_: + if cls._member_map_[name] is not member: + raise NameError('%r is already bound: %r' % (name, cls._member_map_[name])) + return + # + # if necessary, get redirect in place and then add it to _member_map_ + found_descriptor = None + descriptor_type = None + class_type = None + for base in cls.__mro__[1:]: + attr = base.__dict__.get(name) + if attr is not None: + if isinstance(attr, (property, DynamicClassAttribute)): + found_descriptor = attr + class_type = base + descriptor_type = 'enum' + break + elif _is_descriptor(attr): + found_descriptor = attr + descriptor_type = descriptor_type or 'desc' + class_type = class_type or base + continue + else: + descriptor_type = 'attr' + class_type = base + if found_descriptor: + redirect = property() + redirect.member = member + redirect.__set_name__(cls, name) + if descriptor_type in ('enum', 'desc'): + # earlier descriptor found; copy fget, fset, fdel to this one. + redirect.fget = getattr(found_descriptor, 'fget', None) + redirect._get = getattr(found_descriptor, '__get__', None) + redirect.fset = getattr(found_descriptor, 'fset', None) + redirect._set = getattr(found_descriptor, '__set__', None) + redirect.fdel = getattr(found_descriptor, 'fdel', None) + redirect._del = getattr(found_descriptor, '__delete__', None) + redirect._attr_type = descriptor_type + redirect._cls_type = class_type + setattr(cls, name, redirect) + else: + setattr(cls, name, member) + # now add to _member_map_ (even aliases) + cls._member_map_[name] = member + +EnumMeta = EnumType # keep EnumMeta name for backwards compatibility class Enum(metaclass=EnumType): @@ -1125,12 +1169,17 @@ def __new__(cls, value): pass except TypeError: # not there, now do long search -- O(n) behavior - for member in cls._member_map_.values(): - if member._value_ == value: - return member + for name, unhashable_values in cls._unhashable_values_map_.items(): + if value in unhashable_values: + return cls[name] + for name, member in cls._member_map_.items(): + if value == member._value_: + return cls[name] # still not found -- verify that members exist, in-case somebody got here mistakenly # (such as via super when trying to override __new__) if not cls._member_map_: + if getattr(cls, '_%s__in_progress' % cls.__name__, False): + raise TypeError('do not use `super().__new__; call the appropriate __new__ directly') from None raise TypeError("%r has no members defined" % cls) # # still not found -- try _missing_ hook @@ -1168,6 +1217,34 @@ def __new__(cls, value): def __init__(self, *args, **kwds): pass + def _add_alias_(self, name): + self.__class__._add_member_(name, self) + + def _add_value_alias_(self, value): + cls = self.__class__ + try: + if value in cls._value2member_map_: + if cls._value2member_map_[value] is not self: + raise ValueError('%r is already bound: %r' % (value, cls._value2member_map_[value])) + return + except TypeError: + # unhashable value, do long search + for m in cls._member_map_.values(): + if m._value_ == value: + if m is not self: + raise ValueError('%r is already bound: %r' % (value, cls._value2member_map_[value])) + return + try: + # This may fail if value is not hashable. We can't add the value + # to the map, and by-value lookups for this value will be + # linear. + cls._value2member_map_.setdefault(value, self) + cls._hashable_values_.append(value) + except TypeError: + # keep track of the value in a list so containment checks are quick + cls._unhashable_values_.append(value) + cls._unhashable_values_map_.setdefault(self.name, []).append(value) + @staticmethod def _generate_next_value_(name, start, count, last_values): """ @@ -1181,28 +1258,13 @@ def _generate_next_value_(name, start, count, last_values): if not last_values: return start try: - last = last_values[-1] - last_values.sort() - if last == last_values[-1]: - # no difference between old and new methods - return last + 1 - else: - # trigger old method (with warning) - raise TypeError + last_value = sorted(last_values).pop() except TypeError: - import warnings - warnings.warn( - "In 3.13 the default `auto()`/`_generate_next_value_` will require all values to be sortable and support adding +1\n" - "and the value returned will be the largest value in the enum incremented by 1", - DeprecationWarning, - stacklevel=3, - ) - for v in reversed(last_values): - try: - return v + 1 - except TypeError: - pass - return start + raise TypeError('unable to sort non-numeric values') from None + try: + return last_value + 1 + except TypeError: + raise TypeError('unable to increment %r' % (last_value, )) from None @classmethod def _missing_(cls, value): @@ -1217,14 +1279,13 @@ def __str__(self): def __dir__(self): """ - Returns all members and all public methods + Returns public methods and other interesting attributes. """ - if self.__class__._member_type_ is object: - interesting = set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value']) - else: + interesting = set() + if self.__class__._member_type_ is not object: interesting = set(object.__dir__(self)) for name in getattr(self, '__dict__', []): - if name[0] != '_': + if name[0] != '_' and name not in self._member_map_: interesting.add(name) for cls in self.__class__.mro(): for name, obj in cls.__dict__.items(): @@ -1237,7 +1298,7 @@ def __dir__(self): else: # in case it was added by `dir(self)` interesting.discard(name) - else: + elif name not in self._member_map_: interesting.add(name) names = sorted( set(['__class__', '__doc__', '__eq__', '__hash__', '__module__']) @@ -1525,37 +1586,50 @@ def __str__(self): def __bool__(self): return bool(self._value_) + def _get_value(self, flag): + if isinstance(flag, self.__class__): + return flag._value_ + elif self._member_type_ is not object and isinstance(flag, self._member_type_): + return flag + return NotImplemented + def __or__(self, other): - if isinstance(other, self.__class__): - other = other._value_ - elif self._member_type_ is not object and isinstance(other, self._member_type_): - other = other - else: + other_value = self._get_value(other) + if other_value is NotImplemented: return NotImplemented + + for flag in self, other: + if self._get_value(flag) is None: + raise TypeError(f"'{flag}' cannot be combined with other flags with |") value = self._value_ - return self.__class__(value | other) + return self.__class__(value | other_value) def __and__(self, other): - if isinstance(other, self.__class__): - other = other._value_ - elif self._member_type_ is not object and isinstance(other, self._member_type_): - other = other - else: + other_value = self._get_value(other) + if other_value is NotImplemented: return NotImplemented + + for flag in self, other: + if self._get_value(flag) is None: + raise TypeError(f"'{flag}' cannot be combined with other flags with &") value = self._value_ - return self.__class__(value & other) + return self.__class__(value & other_value) def __xor__(self, other): - if isinstance(other, self.__class__): - other = other._value_ - elif self._member_type_ is not object and isinstance(other, self._member_type_): - other = other - else: + other_value = self._get_value(other) + if other_value is NotImplemented: return NotImplemented + + for flag in self, other: + if self._get_value(flag) is None: + raise TypeError(f"'{flag}' cannot be combined with other flags with ^") value = self._value_ - return self.__class__(value ^ other) + return self.__class__(value ^ other_value) def __invert__(self): + if self._get_value(self) is None: + raise TypeError(f"'{self}' cannot be inverted") + if self._inverted_ is None: if self._boundary_ in (EJECT, KEEP): self._inverted_ = self.__class__(~self._value_) @@ -1622,7 +1696,7 @@ def global_flag_repr(self): cls_name = self.__class__.__name__ if self._name_ is None: return "%s.%s(%r)" % (module, cls_name, self._value_) - if _is_single_bit(self): + if _is_single_bit(self._value_): return '%s.%s' % (module, self._name_) if self._boundary_ is not FlagBoundary.KEEP: return '|'.join(['%s.%s' % (module, name) for name in self.name.split('|')]) @@ -1665,7 +1739,7 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None): Class decorator that converts a normal class into an :class:`Enum`. No safety checks are done, and some advanced behavior (such as :func:`__init_subclass__`) is not available. Enum creation can be faster - using :func:`simple_enum`. + using :func:`_simple_enum`. >>> from enum import Enum, _simple_enum >>> @_simple_enum(Enum) @@ -1696,7 +1770,9 @@ def convert_class(cls): body['_member_names_'] = member_names = [] body['_member_map_'] = member_map = {} body['_value2member_map_'] = value2member_map = {} - body['_unhashable_values_'] = [] + body['_hashable_values_'] = hashable_values = [] + body['_unhashable_values_'] = unhashable_values = [] + body['_unhashable_values_map_'] = {} body['_member_type_'] = member_type = etype._member_type_ body['_value_repr_'] = etype._value_repr_ if issubclass(etype, Flag): @@ -1743,35 +1819,42 @@ def convert_class(cls): for name, value in attrs.items(): if isinstance(value, auto) and auto.value is _auto_null: value = gnv(name, 1, len(member_names), gnv_last_values) - if value in value2member_map: + # create basic member (possibly isolate value for alias check) + if use_args: + if not isinstance(value, tuple): + value = (value, ) + member = new_member(enum_class, *value) + value = value[0] + else: + member = new_member(enum_class) + if __new__ is None: + member._value_ = value + # now check if alias + try: + contained = value2member_map.get(member._value_) + except TypeError: + contained = None + if member._value_ in unhashable_values or member.value in hashable_values: + for m in enum_class: + if m._value_ == member._value_: + contained = m + break + if contained is not None: # an alias to an existing member - member = value2member_map[value] - redirect = property() - redirect.member = member - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = member + contained._add_alias_(name) else: - # create the member - if use_args: - if not isinstance(value, tuple): - value = (value, ) - member = new_member(enum_class, *value) - value = value[0] - else: - member = new_member(enum_class) - if __new__ is None: - member._value_ = value + # finish creating member member._name_ = name member.__objclass__ = enum_class member.__init__(value) - redirect = property() - redirect.member = member - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = member member._sort_order_ = len(member_names) + if name not in ('name', 'value'): + setattr(enum_class, name, member) + member_map[name] = member + else: + enum_class._add_member_(name, member) value2member_map[value] = member + hashable_values.append(value) if _is_single_bit(value): # not a multi-bit alias, record in _member_names_ and _flag_mask_ member_names.append(name) @@ -1793,37 +1876,53 @@ def convert_class(cls): if value.value is _auto_null: value.value = gnv(name, 1, len(member_names), gnv_last_values) value = value.value - if value in value2member_map: + # create basic member (possibly isolate value for alias check) + if use_args: + if not isinstance(value, tuple): + value = (value, ) + member = new_member(enum_class, *value) + value = value[0] + else: + member = new_member(enum_class) + if __new__ is None: + member._value_ = value + # now check if alias + try: + contained = value2member_map.get(member._value_) + except TypeError: + contained = None + if member._value_ in unhashable_values or member._value_ in hashable_values: + for m in enum_class: + if m._value_ == member._value_: + contained = m + break + if contained is not None: # an alias to an existing member - member = value2member_map[value] - redirect = property() - redirect.member = member - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = member + contained._add_alias_(name) else: - # create the member - if use_args: - if not isinstance(value, tuple): - value = (value, ) - member = new_member(enum_class, *value) - value = value[0] - else: - member = new_member(enum_class) - if __new__ is None: - member._value_ = value + # finish creating member member._name_ = name member.__objclass__ = enum_class member.__init__(value) member._sort_order_ = len(member_names) - redirect = property() - redirect.member = member - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = member - value2member_map[value] = member + if name not in ('name', 'value'): + setattr(enum_class, name, member) + member_map[name] = member + else: + enum_class._add_member_(name, member) member_names.append(name) gnv_last_values.append(value) + try: + # This may fail if value is not hashable. We can't add the value + # to the map, and by-value lookups for this value will be + # linear. + enum_class._value2member_map_.setdefault(value, member) + if value not in hashable_values: + hashable_values.append(value) + except TypeError: + # keep track of the value in a list so containment checks are quick + enum_class._unhashable_values_.append(value) + enum_class._unhashable_values_map_.setdefault(name, []).append(value) if '__new__' in body: enum_class.__new_member__ = enum_class.__new__ enum_class.__new__ = Enum.__new__ @@ -1880,7 +1979,7 @@ def __call__(self, enumeration): if 2**i not in values: missing.append(2**i) elif enum_type == 'enum': - # check for powers of one + # check for missing consecutive integers for i in range(low+1, high): if i not in values: missing.append(i) @@ -1908,7 +2007,8 @@ def __call__(self, enumeration): missed = [v for v in values if v not in member_values] if missed: missing_names.append(name) - missing_value |= reduce(_or_, missed) + for val in missed: + missing_value |= val if missing_names: if len(missing_names) == 1: alias = 'alias %s is missing' % missing_names[0] @@ -1957,7 +2057,8 @@ def _test_simple_enum(checked_enum, simple_enum): + list(simple_enum._member_map_.keys()) ) for key in set(checked_keys + simple_keys): - if key in ('__module__', '_member_map_', '_value2member_map_', '__doc__'): + if key in ('__module__', '_member_map_', '_value2member_map_', '__doc__', + '__static_attributes__', '__firstlineno__'): # keys known to be different, or very long continue elif key in member_names: diff --git a/Lib/filecmp.py b/Lib/filecmp.py index 30bd900fa80..c5b8d854d77 100644 --- a/Lib/filecmp.py +++ b/Lib/filecmp.py @@ -88,12 +88,15 @@ def _do_cmp(f1, f2): class dircmp: """A class that manages the comparison of 2 directories. - dircmp(a, b, ignore=None, hide=None) + dircmp(a, b, ignore=None, hide=None, *, shallow=True) A and B are directories. IGNORE is a list of names to ignore, defaults to DEFAULT_IGNORES. HIDE is a list of names to hide, defaults to [os.curdir, os.pardir]. + SHALLOW specifies whether to just check the stat signature (do not read + the files). + defaults to True. High level usage: x = dircmp(dir1, dir2) @@ -121,7 +124,7 @@ class dircmp: in common_dirs. """ - def __init__(self, a, b, ignore=None, hide=None): # Initialize + def __init__(self, a, b, ignore=None, hide=None, *, shallow=True): # Initialize self.left = a self.right = b if hide is None: @@ -132,6 +135,7 @@ def __init__(self, a, b, ignore=None, hide=None): # Initialize self.ignore = DEFAULT_IGNORES else: self.ignore = ignore + self.shallow = shallow def phase0(self): # Compare everything except common subdirectories self.left_list = _filter(os.listdir(self.left), @@ -160,12 +164,14 @@ def phase2(self): # Distinguish files, directories, funnies ok = True try: a_stat = os.stat(a_path) - except OSError: + except (OSError, ValueError): + # See https://github.com/python/cpython/issues/122400 + # for the rationale for protecting against ValueError. # print('Can\'t stat', a_path, ':', why.args[1]) ok = False try: b_stat = os.stat(b_path) - except OSError: + except (OSError, ValueError): # print('Can\'t stat', b_path, ':', why.args[1]) ok = False @@ -184,7 +190,7 @@ def phase2(self): # Distinguish files, directories, funnies self.common_funny.append(x) def phase3(self): # Find out differences between common files - xx = cmpfiles(self.left, self.right, self.common_files) + xx = cmpfiles(self.left, self.right, self.common_files, self.shallow) self.same_files, self.diff_files, self.funny_files = xx def phase4(self): # Find out differences between common subdirectories @@ -196,7 +202,8 @@ def phase4(self): # Find out differences between common subdirectories for x in self.common_dirs: a_x = os.path.join(self.left, x) b_x = os.path.join(self.right, x) - self.subdirs[x] = self.__class__(a_x, b_x, self.ignore, self.hide) + self.subdirs[x] = self.__class__(a_x, b_x, self.ignore, self.hide, + shallow=self.shallow) def phase4_closure(self): # Recursively call phase4() on subdirectories self.phase4() @@ -280,12 +287,12 @@ def cmpfiles(a, b, common, shallow=True): # Return: # 0 for equal # 1 for different -# 2 for funny cases (can't stat, etc.) +# 2 for funny cases (can't stat, NUL bytes, etc.) # def _cmp(a, b, sh, abs=abs, cmp=cmp): try: return not abs(cmp(a, b, sh)) - except OSError: + except (OSError, ValueError): return 2 diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index 628c243f2ff..3ff1cea2bf8 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -995,7 +995,6 @@ def test_constant_as_unicode_name(self): f"identifier field can't represent '{constant[0]}' constant"): ast.parse(constant[1], mode="eval") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_precedence_enum(self): class _Precedence(enum.IntEnum): """Precedence table that originated from python grammar.""" diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 32a3c1dee07..8f4b39b4dbb 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -11,14 +11,15 @@ import builtins as bltns from collections import OrderedDict from datetime import date +from functools import partial from enum import Enum, EnumMeta, IntEnum, StrEnum, EnumType, Flag, IntFlag, unique, auto from enum import STRICT, CONFORM, EJECT, KEEP, _simple_enum, _test_simple_enum from enum import verify, UNIQUE, CONTINUOUS, NAMED_FLAGS, ReprEnum -from enum import member, nonmember, _iter_bits_lsb +from enum import member, nonmember, _iter_bits_lsb, EnumDict from io import StringIO from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL from test import support -from test.support import ALWAYS_EQ +from test.support import ALWAYS_EQ, REPO_ROOT from test.support import threading_helper from datetime import timedelta @@ -26,18 +27,42 @@ def load_tests(loader, tests, ignore): tests.addTests(doctest.DocTestSuite(enum)) - if os.path.exists('Doc/library/enum.rst'): + + lib_tests = os.path.join(REPO_ROOT, 'Doc/library/enum.rst') + if os.path.exists(lib_tests): tests.addTests(doctest.DocFileSuite( - '../../Doc/library/enum.rst', + lib_tests, + module_relative=False, optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE, )) - if os.path.exists('Doc/howto/enum.rst'): + howto_tests = os.path.join(REPO_ROOT, 'Doc/howto/enum.rst') + if os.path.exists(howto_tests) and sys.float_repr_style == 'short': tests.addTests(doctest.DocFileSuite( - '../../Doc/howto/enum.rst', + howto_tests, + module_relative=False, optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE, )) return tests +def reraise_if_not_enum(*enum_types_or_exceptions): + from functools import wraps + + def decorator(func): + @wraps(func) + def inner(*args, **kwargs): + excs = [ + e + for e in enum_types_or_exceptions + if isinstance(e, Exception) + ] + if len(excs) == 1: + raise excs[0] + elif excs: + raise ExceptionGroup('Enum Exceptions', excs) + return func(*args, **kwargs) + return inner + return decorator + MODULE = __name__ SHORT_MODULE = MODULE.split('.')[-1] @@ -75,30 +100,42 @@ class FlagStooges(Flag): except Exception as exc: FlagStooges = exc -class FlagStoogesWithZero(Flag): - NOFLAG = 0 - LARRY = 1 - CURLY = 2 - MOE = 4 - BIG = 389 - -class IntFlagStooges(IntFlag): - LARRY = 1 - CURLY = 2 - MOE = 4 - BIG = 389 - -class IntFlagStoogesWithZero(IntFlag): - NOFLAG = 0 - LARRY = 1 - CURLY = 2 - MOE = 4 - BIG = 389 +try: + class FlagStoogesWithZero(Flag): + NOFLAG = 0 + LARRY = 1 + CURLY = 2 + MOE = 4 + BIG = 389 +except Exception as exc: + FlagStoogesWithZero = exc + +try: + class IntFlagStooges(IntFlag): + LARRY = 1 + CURLY = 2 + MOE = 4 + BIG = 389 +except Exception as exc: + IntFlagStooges = exc + +try: + class IntFlagStoogesWithZero(IntFlag): + NOFLAG = 0 + LARRY = 1 + CURLY = 2 + MOE = 4 + BIG = 389 +except Exception as exc: + IntFlagStoogesWithZero = exc # for pickle test and subclass tests -class Name(StrEnum): - BDFL = 'Guido van Rossum' - FLUFL = 'Barry Warsaw' +try: + class Name(StrEnum): + BDFL = 'Guido van Rossum' + FLUFL = 'Barry Warsaw' +except Exception as exc: + Name = exc try: Question = Enum('Question', 'who what when where why', module=__name__) @@ -140,7 +177,7 @@ class TestHelpers(unittest.TestCase): sunder_names = '_bad_', '_good_', '_what_ho_' dunder_names = '__mal__', '__bien__', '__que_que__' - private_names = '_MyEnum__private', '_MyEnum__still_private' + private_names = '_MyEnum__private', '_MyEnum__still_private', '_MyEnum___triple_private' private_and_sunder_names = '_MyEnum__private_', '_MyEnum__also_private_' random_names = 'okay', '_semi_private', '_weird__', '_MyEnum__' @@ -204,26 +241,35 @@ def __get__(self, instance, ownerclass): # for global repr tests -@enum.global_enum -class HeadlightsK(IntFlag, boundary=enum.KEEP): - OFF_K = 0 - LOW_BEAM_K = auto() - HIGH_BEAM_K = auto() - FOG_K = auto() +try: + @enum.global_enum + class HeadlightsK(IntFlag, boundary=enum.KEEP): + OFF_K = 0 + LOW_BEAM_K = auto() + HIGH_BEAM_K = auto() + FOG_K = auto() +except Exception as exc: + HeadlightsK = exc -@enum.global_enum -class HeadlightsC(IntFlag, boundary=enum.CONFORM): - OFF_C = 0 - LOW_BEAM_C = auto() - HIGH_BEAM_C = auto() - FOG_C = auto() +try: + @enum.global_enum + class HeadlightsC(IntFlag, boundary=enum.CONFORM): + OFF_C = 0 + LOW_BEAM_C = auto() + HIGH_BEAM_C = auto() + FOG_C = auto() +except Exception as exc: + HeadlightsC = exc -@enum.global_enum -class NoName(Flag): - ONE = 1 - TWO = 2 +try: + @enum.global_enum + class NoName(Flag): + ONE = 1 + TWO = 2 +except Exception as exc: + NoName = exc # tests @@ -399,10 +445,12 @@ def spam(cls): with self.assertRaises(AttributeError): del Season.SPRING.name + @unittest.skip('TODO: RUSTPYTHON') + # RuntimeError: Error calling __set_name__ on '_proto_member' instance failed in 'BadSuper' def test_bad_new_super(self): with self.assertRaisesRegex( TypeError, - 'has no members defined', + 'do not use .super...__new__;', ): class BadSuper(self.enum_type): def __new__(cls, value): @@ -417,6 +465,7 @@ def test_basics(self): self.assertEqual(str(TE), "<flag 'MainEnum'>") self.assertEqual(format(TE), "<flag 'MainEnum'>") self.assertTrue(TE(5) is self.dupe2) + self.assertTrue(7 in TE) else: self.assertEqual(repr(TE), "<enum 'MainEnum'>") self.assertEqual(str(TE), "<enum 'MainEnum'>") @@ -469,6 +518,7 @@ def test_contains_tf(self): self.assertFalse('first' in MainEnum) val = MainEnum.dupe self.assertIn(val, MainEnum) + self.assertNotIn(float('nan'), MainEnum) # class OtherEnum(Enum): one = auto() @@ -1005,6 +1055,22 @@ class TestPlainEnumFunction(_EnumTests, _PlainOutputTests, unittest.TestCase): class TestPlainFlagClass(_EnumTests, _PlainOutputTests, _FlagTests, unittest.TestCase): enum_type = Flag + def test_none_member(self): + class FlagWithNoneMember(Flag): + A = 1 + E = None + + self.assertEqual(FlagWithNoneMember.A.value, 1) + self.assertIs(FlagWithNoneMember.E.value, None) + with self.assertRaisesRegex(TypeError, r"'FlagWithNoneMember.E' cannot be combined with other flags with |"): + FlagWithNoneMember.A | FlagWithNoneMember.E + with self.assertRaisesRegex(TypeError, r"'FlagWithNoneMember.E' cannot be combined with other flags with &"): + FlagWithNoneMember.E & FlagWithNoneMember.A + with self.assertRaisesRegex(TypeError, r"'FlagWithNoneMember.E' cannot be combined with other flags with \^"): + FlagWithNoneMember.A ^ FlagWithNoneMember.E + with self.assertRaisesRegex(TypeError, r"'FlagWithNoneMember.E' cannot be inverted"): + ~FlagWithNoneMember.E + class TestPlainFlagFunction(_EnumTests, _PlainOutputTests, _FlagTests, unittest.TestCase): enum_type = Flag @@ -1292,7 +1358,7 @@ class Color(Enum): red = 1 green = 2 blue = 3 - def red(self): + def red(self): # noqa: F811 return 'red' # with self.assertRaises(TypeError): @@ -1300,13 +1366,12 @@ class Color(Enum): @enum.property def red(self): return 'redder' - red = 1 + red = 1 # noqa: F811 green = 2 blue = 3 + @reraise_if_not_enum(Theory) def test_enum_function_with_qualname(self): - if isinstance(Theory, Exception): - raise Theory self.assertEqual(Theory.__qualname__, 'spanish_inquisition') def test_enum_of_types(self): @@ -1369,12 +1434,10 @@ class Inner(Enum): [Outer.a, Outer.b, Outer.Inner], ) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf( - python_version < (3, 13), - 'inner classes are still members', - ) + python_version < (3, 13), + 'inner classes are still members', + ) def test_nested_classes_in_enum_are_not_members(self): """Support locally-defined nested classes.""" class Outer(Enum): @@ -1439,6 +1502,27 @@ class SpamEnum(Enum): spam = nonmember(SpamEnumIsInner) self.assertTrue(SpamEnum.spam is SpamEnumIsInner) + def test_using_members_as_nonmember(self): + class Example(Flag): + A = 1 + B = 2 + ALL = nonmember(A | B) + + self.assertEqual(Example.A.value, 1) + self.assertEqual(Example.B.value, 2) + self.assertEqual(Example.ALL, 3) + self.assertIs(type(Example.ALL), int) + + class Example(Flag): + A = auto() + B = auto() + ALL = nonmember(A | B) + + self.assertEqual(Example.A.value, 1) + self.assertEqual(Example.B.value, 2) + self.assertEqual(Example.ALL, 3) + self.assertIs(type(Example.ALL), int) + def test_nested_classes_in_enum_with_member(self): """Support locally-defined nested classes.""" class Outer(Enum): @@ -1460,6 +1544,22 @@ class Inner(Enum): [Outer.a, Outer.b, Outer.Inner], ) + # TODO: RUSTPYTHON + # AssertionError: FutureWarning not triggered + @unittest.expectedFailure + def test_partial(self): + def func(a, b=5): + return a, b + with self.assertWarnsRegex(FutureWarning, r'partial.*enum\.member') as cm: + class E(Enum): + a = 1 + b = partial(func) + self.assertEqual(cm.filename, __file__) + self.assertIsInstance(E.b, partial) + self.assertEqual(E.b(2), (2, 5)) + with self.assertWarnsRegex(FutureWarning, 'partial'): + self.assertEqual(E.a.b(2), (2, 5)) + def test_enum_with_value_name(self): class Huh(Enum): name = 1 @@ -1491,6 +1591,17 @@ class IntFlag1(IntFlag): self.assertIn(IntEnum1.X, IntFlag1) self.assertIn(IntFlag1.X, IntEnum1) + def test_contains_does_not_call_missing(self): + class AnEnum(Enum): + UNKNOWN = None + LUCKY = 3 + @classmethod + def _missing_(cls, *values): + return cls.UNKNOWN + self.assertTrue(None in AnEnum) + self.assertTrue(3 in AnEnum) + self.assertFalse(7 in AnEnum) + def test_inherited_data_type(self): class HexInt(int): __qualname__ = 'HexInt' @@ -1537,6 +1648,7 @@ class MyUnBrokenEnum(UnBrokenInt, Enum): test_pickle_dump_load(self.assertIs, MyUnBrokenEnum.I) test_pickle_dump_load(self.assertIs, MyUnBrokenEnum) + @reraise_if_not_enum(FloatStooges) def test_floatenum_fromhex(self): h = float.hex(FloatStooges.MOE.value) self.assertIs(FloatStooges.fromhex(h), FloatStooges.MOE) @@ -1657,8 +1769,8 @@ class ThreePart(Enum): self.assertIs(ThreePart((3, 3.0, 'three')), ThreePart.THREE) self.assertIs(ThreePart(3, 3.0, 'three'), ThreePart.THREE) - # TODO: RUSTPYTHON, AssertionError: <test.test_enum.IntStooges object at 0x55c70c38a240> is not <IntStooges.MOE: 3> - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <test.test_enum.IntStooges object at 0x55c70c38a240> is not <IntStooges.MOE: 3> + @reraise_if_not_enum(IntStooges) def test_intenum_from_bytes(self): self.assertIs(IntStooges.from_bytes(b'\x00\x03', 'big'), IntStooges.MOE) with self.assertRaises(ValueError): @@ -1687,33 +1799,28 @@ def repr(self): class Huh(MyStr, MyInt, Enum): One = 1 + @reraise_if_not_enum(Stooges) def test_pickle_enum(self): - if isinstance(Stooges, Exception): - raise Stooges test_pickle_dump_load(self.assertIs, Stooges.CURLY) test_pickle_dump_load(self.assertIs, Stooges) + @reraise_if_not_enum(IntStooges) def test_pickle_int(self): - if isinstance(IntStooges, Exception): - raise IntStooges test_pickle_dump_load(self.assertIs, IntStooges.CURLY) test_pickle_dump_load(self.assertIs, IntStooges) + @reraise_if_not_enum(FloatStooges) def test_pickle_float(self): - if isinstance(FloatStooges, Exception): - raise FloatStooges test_pickle_dump_load(self.assertIs, FloatStooges.CURLY) test_pickle_dump_load(self.assertIs, FloatStooges) + @reraise_if_not_enum(Answer) def test_pickle_enum_function(self): - if isinstance(Answer, Exception): - raise Answer test_pickle_dump_load(self.assertIs, Answer.him) test_pickle_dump_load(self.assertIs, Answer) + @reraise_if_not_enum(Question) def test_pickle_enum_function_with_module(self): - if isinstance(Question, Exception): - raise Question test_pickle_dump_load(self.assertIs, Question.who) test_pickle_dump_load(self.assertIs, Question) @@ -1776,9 +1883,8 @@ class Season(Enum): [Season.SUMMER, Season.WINTER, Season.AUTUMN, Season.SPRING], ) + @reraise_if_not_enum(Name) def test_subclassing(self): - if isinstance(Name, Exception): - raise Name self.assertEqual(Name.BDFL, 'Guido van Rossum') self.assertTrue(Name.BDFL, Name('Guido van Rossum')) self.assertIs(Name.BDFL, getattr(Name, 'BDFL')) @@ -1817,6 +1923,27 @@ def test_wrong_inheritance_order(self): class Wrong(Enum, str): NotHere = 'error before this point' + @unittest.skip('TODO: RUSTPYTHON') + # RuntimeError: Error calling __set_name__ on '_proto_member' instance INVALID in 'RgbColor' + def test_raise_custom_error_on_creation(self): + class InvalidRgbColorError(ValueError): + def __init__(self, r, g, b): + self.r = r + self.g = g + self.b = b + super().__init__(f'({r}, {g}, {b}) is not a valid RGB color') + + with self.assertRaises(InvalidRgbColorError): + class RgbColor(Enum): + RED = (255, 0, 0) + GREEN = (0, 255, 0) + BLUE = (0, 0, 255) + INVALID = (256, 0, 0) + + def __init__(self, r, g, b): + if not all(0 <= val <= 255 for val in (r, g, b)): + raise InvalidRgbColorError(r, g, b) + def test_intenum_transitivity(self): class number(IntEnum): one = 1 @@ -2003,8 +2130,7 @@ class NEI(NamedInt, Enum): test_pickle_dump_load(self.assertIs, NEI.y) test_pickle_dump_load(self.assertIs, NEI) - # TODO: RUSTPYTHON, fails on pickle - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; fails on pickle def test_subclasses_with_getnewargs_ex(self): class NamedInt(int): __qualname__ = 'NamedInt' # needed for pickle protocol 4 @@ -2312,6 +2438,40 @@ class SomeTuple(tuple, Enum): globals()['SomeTuple'] = SomeTuple test_pickle_dump_load(self.assertIs, SomeTuple.first) + def test_tuple_subclass_with_auto_1(self): + from collections import namedtuple + T = namedtuple('T', 'index desc') + class SomeEnum(T, Enum): + __qualname__ = 'SomeEnum' # needed for pickle protocol 4 + first = auto(), 'for the money' + second = auto(), 'for the show' + third = auto(), 'for the music' + self.assertIs(type(SomeEnum.first), SomeEnum) + self.assertEqual(SomeEnum.third.value, (3, 'for the music')) + self.assertIsInstance(SomeEnum.third.value, T) + self.assertEqual(SomeEnum.first.index, 1) + self.assertEqual(SomeEnum.second.desc, 'for the show') + globals()['SomeEnum'] = SomeEnum + globals()['T'] = T + test_pickle_dump_load(self.assertIs, SomeEnum.first) + + def test_tuple_subclass_with_auto_2(self): + from collections import namedtuple + T = namedtuple('T', 'index desc') + class SomeEnum(Enum): + __qualname__ = 'SomeEnum' # needed for pickle protocol 4 + first = T(auto(), 'for the money') + second = T(auto(), 'for the show') + third = T(auto(), 'for the music') + self.assertIs(type(SomeEnum.first), SomeEnum) + self.assertEqual(SomeEnum.third.value, (3, 'for the music')) + self.assertIsInstance(SomeEnum.third.value, T) + self.assertEqual(SomeEnum.first.value.index, 1) + self.assertEqual(SomeEnum.second.value.desc, 'for the show') + globals()['SomeEnum'] = SomeEnum + globals()['T'] = T + test_pickle_dump_load(self.assertIs, SomeEnum.first) + def test_duplicate_values_give_unique_enum_items(self): class AutoNumber(Enum): first = () @@ -2453,6 +2613,8 @@ class Test(Base2): self.assertEqual(Test.flash.flash, 'flashy dynamic') self.assertEqual(Test.flash.value, 1) + @unittest.skip('TODO: RUSTPYTHON') + # RuntimeError: Error calling __set_name__ on '_proto_member' instance grene in 'Color' def test_no_duplicates(self): class UniqueEnum(Enum): def __init__(self, *args): @@ -2838,6 +3000,8 @@ def test_empty_globals(self): local_ls = {} exec(code, global_ns, local_ls) + @unittest.skip('TODO: RUSTPYTHON') + # RuntimeError: Error calling __set_name__ on '_proto_member' instance one in 'FirstFailedStrEnum' def test_strenum(self): class GoodStrEnum(StrEnum): one = '1' @@ -2900,8 +3064,7 @@ class ThirdFailedStrEnum(StrEnum): one = '1' two = b'2', 'ascii', 9 - # TODO: RUSTPYTHON, fails on encoding testing : TypeError: Expected type 'str' but 'builtin_function_or_method' found - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; fails on encoding testing : TypeError: Expected type 'str' but 'builtin_function_or_method' found def test_custom_strenum(self): class CustomStrEnum(str, Enum): pass @@ -2952,15 +3115,19 @@ class SecondFailedStrEnum(CustomStrEnum): class ThirdFailedStrEnum(CustomStrEnum): one = '1' two = 2 # this will become '2' - with self.assertRaisesRegex(TypeError, '.encoding. must be str, not '): + with self.assertRaisesRegex(TypeError, + r"argument (2|'encoding') must be str, not "): class ThirdFailedStrEnum(CustomStrEnum): one = '1' two = b'2', sys.getdefaultencoding - with self.assertRaisesRegex(TypeError, '.errors. must be str, not '): + with self.assertRaisesRegex(TypeError, + r"argument (3|'errors') must be str, not "): class ThirdFailedStrEnum(CustomStrEnum): one = '1' two = b'2', 'ascii', 9 + @unittest.skip('TODO: RUSTPYTHON') + # RuntimeError: Error calling __set_name__ on '_proto_member' instance key_type in 'Combined' def test_missing_value_error(self): with self.assertRaisesRegex(TypeError, "_value_ not set in __new__"): class Combined(str, Enum): @@ -3170,6 +3337,37 @@ class NTEnum(Enum): [TTuple(id=0, a=0, blist=[]), TTuple(id=1, a=2, blist=[4]), TTuple(id=2, a=4, blist=[0, 1, 2])], ) + self.assertRaises(AttributeError, getattr, NTEnum.NONE, 'id') + # + class NTCEnum(TTuple, Enum): + NONE = 0, 0, [] + A = 1, 2, [4] + B = 2, 4, [0, 1, 2] + self.assertEqual(repr(NTCEnum.NONE), "<NTCEnum.NONE: TTuple(id=0, a=0, blist=[])>") + self.assertEqual(NTCEnum.NONE.value, TTuple(id=0, a=0, blist=[])) + self.assertEqual(NTCEnum.NONE.id, 0) + self.assertEqual(NTCEnum.A.a, 2) + self.assertEqual(NTCEnum.B.blist, [0, 1 ,2]) + self.assertEqual( + [x.value for x in NTCEnum], + [TTuple(id=0, a=0, blist=[]), TTuple(id=1, a=2, blist=[4]), TTuple(id=2, a=4, blist=[0, 1, 2])], + ) + # + class NTDEnum(Enum): + def __new__(cls, id, a, blist): + member = object.__new__(cls) + member.id = id + member.a = a + member.blist = blist + return member + NONE = TTuple(0, 0, []) + A = TTuple(1, 2, [4]) + B = TTuple(2, 4, [0, 1, 2]) + self.assertEqual(repr(NTDEnum.NONE), "<NTDEnum.NONE: TTuple(id=0, a=0, blist=[])>") + self.assertEqual(NTDEnum.NONE.id, 0) + self.assertEqual(NTDEnum.A.a, 2) + self.assertEqual(NTDEnum.B.blist, [0, 1 ,2]) + def test_flag_with_custom_new(self): class FlagFromChar(IntFlag): def __new__(cls, c): @@ -3216,6 +3414,8 @@ def __new__(cls, c): self.assertEqual(FlagFromChar.a, 158456325028528675187087900672) self.assertEqual(FlagFromChar.a|1, 158456325028528675187087900673) + @unittest.skip('TODO: RUSTPYTHON') + # RuntimeError: Error calling __set_name__ on '_proto_member' instance A in 'MyEnum' def test_init_exception(self): class Base: def __new__(cls, *args): @@ -3237,6 +3437,102 @@ def __new__(cls, value): member._value_ = Base(value) return member + def test_extra_member_creation(self): + class IDEnumMeta(EnumMeta): + def __new__(metacls, cls, bases, classdict, **kwds): + # add new entries to classdict + for name in classdict.member_names: + classdict[f'{name}_DESC'] = f'-{classdict[name]}' + return super().__new__(metacls, cls, bases, classdict, **kwds) + class IDEnum(StrEnum, metaclass=IDEnumMeta): + pass + class MyEnum(IDEnum): + ID = 'id' + NAME = 'name' + self.assertEqual(list(MyEnum), [MyEnum.ID, MyEnum.NAME, MyEnum.ID_DESC, MyEnum.NAME_DESC]) + + def test_add_alias(self): + class mixin: + @property + def ORG(self): + return 'huh' + class Color(mixin, Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + Color.RED._add_alias_('ROJO') + self.assertIs(Color.RED, Color['ROJO']) + self.assertIs(Color.RED, Color.ROJO) + Color.BLUE._add_alias_('ORG') + self.assertIs(Color.BLUE, Color['ORG']) + self.assertIs(Color.BLUE, Color.ORG) + self.assertEqual(Color.RED.ORG, 'huh') + self.assertEqual(Color.GREEN.ORG, 'huh') + self.assertEqual(Color.BLUE.ORG, 'huh') + self.assertEqual(Color.ORG.ORG, 'huh') + + def test_add_value_alias_after_creation(self): + class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + Color.RED._add_value_alias_(5) + self.assertIs(Color.RED, Color(5)) + + def test_add_value_alias_during_creation(self): + class Types(Enum): + Unknown = 0, + Source = 1, 'src' + NetList = 2, 'nl' + def __new__(cls, int_value, *value_aliases): + member = object.__new__(cls) + member._value_ = int_value + for alias in value_aliases: + member._add_value_alias_(alias) + return member + self.assertIs(Types(0), Types.Unknown) + self.assertIs(Types(1), Types.Source) + self.assertIs(Types('src'), Types.Source) + self.assertIs(Types(2), Types.NetList) + self.assertIs(Types('nl'), Types.NetList) + + def test_second_tuple_item_is_falsey(self): + class Cardinal(Enum): + RIGHT = (1, 0) + UP = (0, 1) + LEFT = (-1, 0) + DOWN = (0, -1) + self.assertIs(Cardinal(1, 0), Cardinal.RIGHT) + self.assertIs(Cardinal(-1, 0), Cardinal.LEFT) + + def test_no_members(self): + with self.assertRaisesRegex( + TypeError, + 'has no members', + ): + Enum(7) + with self.assertRaisesRegex( + TypeError, + 'has no members', + ): + Flag(7) + + def test_empty_names(self): + for nothing in '', [], {}: + for e_type in None, int: + empty_enum = Enum('empty_enum', nothing, type=e_type) + self.assertEqual(len(empty_enum), 0) + self.assertRaisesRegex(TypeError, 'has no members', empty_enum, 0) + self.assertRaisesRegex(TypeError, '.int. object is not iterable', Enum, 'bad_enum', names=0) + self.assertRaisesRegex(TypeError, '.int. object is not iterable', Enum, 'bad_enum', 0, type=int) + + def test_nonhashable_matches_hashable(self): # issue 125710 + class Directions(Enum): + DOWN_ONLY = frozenset({"sc"}) + UP_ONLY = frozenset({"cs"}) + UNRESTRICTED = frozenset({"sc", "cs"}) + self.assertIs(Directions({"sc"}), Directions.DOWN_ONLY) + class TestOrder(unittest.TestCase): "test usage of the `_order_` attribute" @@ -3518,9 +3814,13 @@ def test_programatic_function_from_dict(self): self.assertIn(e, Perm) self.assertIs(type(e), Perm) + @reraise_if_not_enum( + FlagStooges, + FlagStoogesWithZero, + IntFlagStooges, + IntFlagStoogesWithZero, + ) def test_pickle(self): - if isinstance(FlagStooges, Exception): - raise FlagStooges test_pickle_dump_load(self.assertIs, FlagStooges.CURLY) test_pickle_dump_load(self.assertEqual, FlagStooges.CURLY|FlagStooges.MOE) @@ -3704,7 +4004,7 @@ class Color(StrMixin, AllMixin, Flag): self.assertEqual(Color.ALL.value, 7) self.assertEqual(str(Color.BLUE), 'blue') - @unittest.skip("TODO: RUSTPYTHON; flaky test") + @unittest.skip('TODO: RUSTPYTHON; flaky test') @threading_helper.reap_threads @threading_helper.requires_working_threading() def test_unique_composite(self): @@ -3826,6 +4126,7 @@ def test_type(self): self.assertTrue(isinstance(Open.WO | Open.RW, Open)) self.assertEqual(Open.WO | Open.RW, 3) + @reraise_if_not_enum(HeadlightsK) def test_global_repr_keep(self): self.assertEqual( repr(HeadlightsK(0)), @@ -3840,6 +4141,7 @@ def test_global_repr_keep(self): '%(m)s.HeadlightsK(8)' % {'m': SHORT_MODULE}, ) + @reraise_if_not_enum(HeadlightsC) def test_global_repr_conform1(self): self.assertEqual( repr(HeadlightsC(0)), @@ -3854,13 +4156,14 @@ def test_global_repr_conform1(self): '%(m)s.OFF_C' % {'m': SHORT_MODULE}, ) + @reraise_if_not_enum(NoName) def test_global_enum_str(self): + self.assertEqual(repr(NoName.ONE), 'test_enum.ONE') + self.assertEqual(repr(NoName(0)), 'test_enum.NoName(0)') self.assertEqual(str(NoName.ONE & NoName.TWO), 'NoName(0)') self.assertEqual(str(NoName(0)), 'NoName(0)') - - # TODO: RUSTPYTHON, format(NewPerm.R) does not use __str__ - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; format(NewPerm.R) does not use __str__ def test_format(self): Perm = self.Perm self.assertEqual(format(Perm.R, ''), '4') @@ -4223,7 +4526,7 @@ class Color(StrMixin, AllMixin, IntFlag): self.assertEqual(Color.ALL.value, 7) self.assertEqual(str(Color.BLUE), 'blue') - @unittest.skip("TODO: RUSTPYTHON; flaky test") + @unittest.skip('TODO: RUSTPYTHON; flaky test') @threading_helper.reap_threads @threading_helper.requires_working_threading() def test_unique_composite(self): @@ -4548,35 +4851,28 @@ class Color(Enum): red = 'red' blue = 2 green = auto() - yellow = auto() - self.assertEqual(list(Color), - [Color.red, Color.blue, Color.green, Color.yellow]) + self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) self.assertEqual(Color.red.value, 'red') self.assertEqual(Color.blue.value, 2) self.assertEqual(Color.green.value, 3) - self.assertEqual(Color.yellow.value, 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf( - python_version < (3, 13), - 'inner classes are still members', - ) + python_version < (3, 13), + 'mixed types with auto() will raise in 3.13', + ) def test_auto_garbage_fail(self): - with self.assertRaisesRegex(TypeError, 'will require all values to be sortable'): + with self.assertRaisesRegex(TypeError, "unable to increment 'red'"): class Color(Enum): red = 'red' blue = auto() - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf( - python_version < (3, 13), - 'inner classes are still members', - ) + python_version < (3, 13), + 'mixed types with auto() will raise in 3.13', + ) def test_auto_garbage_corrected_fail(self): - with self.assertRaisesRegex(TypeError, 'will require all values to be sortable'): + with self.assertRaisesRegex(TypeError, 'unable to sort non-numeric values'): class Color(Enum): red = 'red' blue = 2 @@ -4604,9 +4900,9 @@ def _generate_next_value_(name, start, count, last): self.assertEqual(Color.blue.value, 'blue') @unittest.skipIf( - python_version < (3, 13), - 'inner classes are still members', - ) + python_version < (3, 13), + 'auto() will return highest value + 1 in 3.13', + ) def test_auto_with_aliases(self): class Color(Enum): red = auto() @@ -4670,8 +4966,6 @@ def _generate_next_value_(name, start, count, last): self.assertEqual(Huh.TWO.value, (2, 2)) self.assertEqual(Huh.THREE.value, (3, 3, 3)) -class TestEnumTypeSubclassing(unittest.TestCase): - pass expected_help_output_with_docs = """\ Help on class Color in module %s: @@ -4702,22 +4996,23 @@ class Color(enum.Enum) | The value of the Enum member. | | ---------------------------------------------------------------------- - | Methods inherited from enum.EnumType: + | Static methods inherited from enum.EnumType: | - | __contains__(value) from enum.EnumType + | __contains__(value) | Return True if `value` is in `cls`. | | `value` is in `cls` if: | 1) `value` is a member of `cls`, or | 2) `value` is the value of one of the `cls`'s members. + | 3) `value` is a pseudo-member (flags) | - | __getitem__(name) from enum.EnumType + | __getitem__(name) | Return the member matching `name`. | - | __iter__() from enum.EnumType + | __iter__() | Return members in definition order. | - | __len__() from enum.EnumType + | __len__() | Return the number of members (no aliases) | | ---------------------------------------------------------------------- @@ -4742,11 +5037,11 @@ class Color(enum.Enum) | | Data and other attributes defined here: | - | YELLOW = <Color.YELLOW: 3> + | CYAN = <Color.CYAN: 1> | | MAGENTA = <Color.MAGENTA: 2> | - | CYAN = <Color.CYAN: 1> + | YELLOW = <Color.YELLOW: 3> | | ---------------------------------------------------------------------- | Data descriptors inherited from enum.Enum: @@ -4756,7 +5051,18 @@ class Color(enum.Enum) | value | | ---------------------------------------------------------------------- - | Data descriptors inherited from enum.EnumType: + | Static methods inherited from enum.EnumType: + | + | __contains__(value) + | + | __getitem__(name) + | + | __iter__() + | + | __len__() + | + | ---------------------------------------------------------------------- + | Readonly properties inherited from enum.EnumType: | | __members__""" @@ -4769,8 +5075,7 @@ class Color(Enum): MAGENTA = 2 YELLOW = 3 - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pydoc(self): # indirectly test __objclass__ if StrEnum.__doc__ is None: @@ -4903,8 +5208,7 @@ def test_inspect_signatures(self): ]), ) - # TODO: RUSTPYTHON, len is often/always > 256 - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; len is often/always > 256 def test_test_simple_enum(self): @_simple_enum(Enum) class SimpleColor: @@ -4921,12 +5225,14 @@ class CheckedColor(Enum): @bltns.property def zeroth(self): return 'zeroed %s' % self.name - self.assertTrue(_test_simple_enum(CheckedColor, SimpleColor) is None) + _test_simple_enum(CheckedColor, SimpleColor) SimpleColor.MAGENTA._value_ = 9 self.assertRaisesRegex( TypeError, "enum mismatch", _test_simple_enum, CheckedColor, SimpleColor, ) + # + # class CheckedMissing(IntFlag, boundary=KEEP): SIXTY_FOUR = 64 ONE_TWENTY_EIGHT = 128 @@ -4943,8 +5249,78 @@ class Missing: ALL = 2048 + 128 + 64 + 12 M = Missing self.assertEqual(list(CheckedMissing), [M.SIXTY_FOUR, M.ONE_TWENTY_EIGHT, M.TWENTY_FORTY_EIGHT]) - # _test_simple_enum(CheckedMissing, Missing) + # + # + class CheckedUnhashable(Enum): + ONE = dict() + TWO = set() + name = 'python' + self.assertIn(dict(), CheckedUnhashable) + self.assertIn('python', CheckedUnhashable) + self.assertEqual(CheckedUnhashable.name.value, 'python') + self.assertEqual(CheckedUnhashable.name.name, 'name') + # + @_simple_enum() + class Unhashable: + ONE = dict() + TWO = set() + name = 'python' + self.assertIn(dict(), Unhashable) + self.assertIn('python', Unhashable) + self.assertEqual(Unhashable.name.value, 'python') + self.assertEqual(Unhashable.name.name, 'name') + _test_simple_enum(CheckedUnhashable, Unhashable) + ## + class CheckedComplexStatus(IntEnum): + def __new__(cls, value, phrase, description=''): + obj = int.__new__(cls, value) + obj._value_ = value + obj.phrase = phrase + obj.description = description + return obj + CONTINUE = 100, 'Continue', 'Request received, please continue' + PROCESSING = 102, 'Processing' + EARLY_HINTS = 103, 'Early Hints' + SOME_HINTS = 103, 'Some Early Hints' + # + @_simple_enum(IntEnum) + class ComplexStatus: + def __new__(cls, value, phrase, description=''): + obj = int.__new__(cls, value) + obj._value_ = value + obj.phrase = phrase + obj.description = description + return obj + CONTINUE = 100, 'Continue', 'Request received, please continue' + PROCESSING = 102, 'Processing' + EARLY_HINTS = 103, 'Early Hints' + SOME_HINTS = 103, 'Some Early Hints' + _test_simple_enum(CheckedComplexStatus, ComplexStatus) + # + # + class CheckedComplexFlag(IntFlag): + def __new__(cls, value, label): + obj = int.__new__(cls, value) + obj._value_ = value + obj.label = label + return obj + SHIRT = 1, 'upper half' + VEST = 1, 'outer upper half' + PANTS = 2, 'lower half' + self.assertIs(CheckedComplexFlag.SHIRT, CheckedComplexFlag.VEST) + # + @_simple_enum(IntFlag) + class ComplexFlag: + def __new__(cls, value, label): + obj = int.__new__(cls, value) + obj._value_ = value + obj.label = label + return obj + SHIRT = 1, 'upper half' + VEST = 1, 'uppert half' + PANTS = 2, 'lower half' + _test_simple_enum(CheckedComplexFlag, ComplexFlag) class MiscTestCase(unittest.TestCase): @@ -5031,7 +5407,7 @@ def test_convert_value_lookup_priority(self): filter=lambda x: x.startswith('CONVERT_TEST_')) # We don't want the reverse lookup value to vary when there are # multiple possible names for a given value. It should always - # report the first lexigraphical name in that case. + # report the first lexicographical name in that case. self.assertEqual(test_type(5).name, 'CONVERT_TEST_NAME_A') def test_convert_int(self): @@ -5117,6 +5493,37 @@ def test_convert_repr_and_str(self): self.assertEqual(format(test_type.CONVERT_STRING_TEST_NAME_A), '5') +class TestEnumDict(unittest.TestCase): + def test_enum_dict_in_metaclass(self): + """Test that EnumDict is usable as a class namespace""" + class Meta(type): + @classmethod + def __prepare__(metacls, cls, bases, **kwds): + return EnumDict(cls) + + class MyClass(metaclass=Meta): + a = 1 + + with self.assertRaises(TypeError): + a = 2 # duplicate + + with self.assertRaises(ValueError): + _a_sunder_ = 3 + + def test_enum_dict_standalone(self): + """Test that EnumDict is usable on its own""" + enumdict = EnumDict() + enumdict['a'] = 1 + + with self.assertRaises(TypeError): + enumdict['a'] = 'other value' + + # Only MutableMapping interface is overridden for now. + # If this stops passing, update the documentation. + enumdict |= {'a': 'other value'} + self.assertEqual(enumdict['a'], 'other value') + + # helpers def enum_dir(cls): @@ -5151,7 +5558,7 @@ def member_dir(member): allowed.add(name) else: allowed.discard(name) - else: + elif name not in member._member_map_: allowed.add(name) return sorted(allowed) diff --git a/Lib/test/test_filecmp.py b/Lib/test/test_filecmp.py index 9b5ac12bccc..2c83667b22f 100644 --- a/Lib/test/test_filecmp.py +++ b/Lib/test/test_filecmp.py @@ -1,5 +1,6 @@ import filecmp import os +import re import shutil import tempfile import unittest @@ -8,11 +9,24 @@ from test.support import os_helper +def _create_file_shallow_equal(template_path, new_path): + """create a file with the same size and mtime but different content.""" + shutil.copy2(template_path, new_path) + with open(new_path, 'r+b') as f: + next_char = bytearray(f.read(1)) + next_char[0] = (next_char[0] + 1) % 256 + f.seek(0) + f.write(next_char) + shutil.copystat(template_path, new_path) + assert os.stat(new_path).st_size == os.stat(template_path).st_size + assert os.stat(new_path).st_mtime == os.stat(template_path).st_mtime + class FileCompareTestCase(unittest.TestCase): def setUp(self): self.name = os_helper.TESTFN self.name_same = os_helper.TESTFN + '-same' self.name_diff = os_helper.TESTFN + '-diff' + self.name_same_shallow = os_helper.TESTFN + '-same-shallow' data = 'Contents of file go here.\n' for name in [self.name, self.name_same, self.name_diff]: with open(name, 'w', encoding="utf-8") as output: @@ -20,12 +34,19 @@ def setUp(self): with open(self.name_diff, 'a+', encoding="utf-8") as output: output.write('An extra line.\n') + + for name in [self.name_same, self.name_diff]: + shutil.copystat(self.name, name) + + _create_file_shallow_equal(self.name, self.name_same_shallow) + self.dir = tempfile.gettempdir() def tearDown(self): os.unlink(self.name) os.unlink(self.name_same) os.unlink(self.name_diff) + os.unlink(self.name_same_shallow) def test_matching(self): self.assertTrue(filecmp.cmp(self.name, self.name), @@ -36,12 +57,17 @@ def test_matching(self): "Comparing file to identical file fails") self.assertTrue(filecmp.cmp(self.name, self.name_same, shallow=False), "Comparing file to identical file fails") + self.assertTrue(filecmp.cmp(self.name, self.name_same_shallow), + "Shallow identical files should be considered equal") def test_different(self): self.assertFalse(filecmp.cmp(self.name, self.name_diff), "Mismatched files compare as equal") self.assertFalse(filecmp.cmp(self.name, self.dir), "File and directory compare as equal") + self.assertFalse(filecmp.cmp(self.name, self.name_same_shallow, + shallow=False), + "Mismatched file to shallow identical file compares as equal") def test_cache_clear(self): first_compare = filecmp.cmp(self.name, self.name_same, shallow=False) @@ -56,6 +82,8 @@ def setUp(self): self.dir = os.path.join(tmpdir, 'dir') self.dir_same = os.path.join(tmpdir, 'dir-same') self.dir_diff = os.path.join(tmpdir, 'dir-diff') + self.dir_diff_file = os.path.join(tmpdir, 'dir-diff-file') + self.dir_same_shallow = os.path.join(tmpdir, 'dir-same-shallow') # Another dir is created under dir_same, but it has a name from the # ignored list so it should not affect testing results. @@ -63,7 +91,17 @@ def setUp(self): self.caseinsensitive = os.path.normcase('A') == os.path.normcase('a') data = 'Contents of file go here.\n' - for dir in (self.dir, self.dir_same, self.dir_diff, self.dir_ignored): + + shutil.rmtree(self.dir, True) + os.mkdir(self.dir) + subdir_path = os.path.join(self.dir, 'subdir') + os.mkdir(subdir_path) + dir_file_path = os.path.join(self.dir, "file") + with open(dir_file_path, 'w', encoding="utf-8") as output: + output.write(data) + + for dir in (self.dir_same, self.dir_same_shallow, + self.dir_diff, self.dir_diff_file): shutil.rmtree(dir, True) os.mkdir(dir) subdir_path = os.path.join(dir, 'subdir') @@ -72,14 +110,25 @@ def setUp(self): fn = 'FiLe' # Verify case-insensitive comparison else: fn = 'file' - with open(os.path.join(dir, fn), 'w', encoding="utf-8") as output: - output.write(data) + + file_path = os.path.join(dir, fn) + + if dir is self.dir_same_shallow: + _create_file_shallow_equal(dir_file_path, file_path) + else: + shutil.copy2(dir_file_path, file_path) with open(os.path.join(self.dir_diff, 'file2'), 'w', encoding="utf-8") as output: output.write('An extra file.\n') + # Add different file2 with respect to dir_diff + with open(os.path.join(self.dir_diff_file, 'file2'), 'w', encoding="utf-8") as output: + output.write('Different contents.\n') + + def tearDown(self): - for dir in (self.dir, self.dir_same, self.dir_diff): + for dir in (self.dir, self.dir_same, self.dir_diff, + self.dir_same_shallow, self.dir_diff_file): shutil.rmtree(dir) def test_default_ignores(self): @@ -102,25 +151,65 @@ def test_cmpfiles(self): shallow=False), "Comparing directory to same fails") - # Add different file2 - with open(os.path.join(self.dir, 'file2'), 'w', encoding="utf-8") as output: - output.write('Different contents.\n') - - self.assertFalse(filecmp.cmpfiles(self.dir, self.dir_same, + self.assertFalse(filecmp.cmpfiles(self.dir, self.dir_diff_file, ['file', 'file2']) == (['file'], ['file2'], []), "Comparing mismatched directories fails") + def test_cmpfiles_invalid_names(self): + # See https://github.com/python/cpython/issues/122400. + for file, desc in [ + ('\x00', 'NUL bytes filename'), + (__file__ + '\x00', 'filename with embedded NUL bytes'), + ("\uD834\uDD1E.py", 'surrogate codes (MUSICAL SYMBOL G CLEF)'), + ('a' * 1_000_000, 'very long filename'), + ]: + for other_dir in [self.dir, self.dir_same, self.dir_diff]: + with self.subTest(f'cmpfiles: {desc}', other_dir=other_dir): + res = filecmp.cmpfiles(self.dir, other_dir, [file]) + self.assertTupleEqual(res, ([], [], [file])) + + def test_dircmp_invalid_names(self): + for bad_dir, desc in [ + ('\x00', 'NUL bytes dirname'), + (f'Top{os.sep}Mid\x00', 'dirname with embedded NUL bytes'), + ("\uD834\uDD1E", 'surrogate codes (MUSICAL SYMBOL G CLEF)'), + ('a' * 1_000_000, 'very long dirname'), + ]: + d1 = filecmp.dircmp(self.dir, bad_dir) + d2 = filecmp.dircmp(bad_dir, self.dir) + for target in [ + # attributes where os.listdir() raises OSError or ValueError + 'left_list', 'right_list', + 'left_only', 'right_only', 'common', + ]: + with self.subTest(f'dircmp(ok, bad): {desc}', target=target): + with self.assertRaises((OSError, ValueError)): + getattr(d1, target) + with self.subTest(f'dircmp(bad, ok): {desc}', target=target): + with self.assertRaises((OSError, ValueError)): + getattr(d2, target) def _assert_lists(self, actual, expected): """Assert that two lists are equal, up to ordering.""" self.assertEqual(sorted(actual), sorted(expected)) + def test_dircmp_identical_directories(self): + self._assert_dircmp_identical_directories() + self._assert_dircmp_identical_directories(shallow=False) + + def test_dircmp_different_file(self): + self._assert_dircmp_different_file() + self._assert_dircmp_different_file(shallow=False) - def test_dircmp(self): + def test_dircmp_different_directories(self): + self._assert_dircmp_different_directories() + self._assert_dircmp_different_directories(shallow=False) + + def _assert_dircmp_identical_directories(self, **options): # Check attributes for comparison of two identical directories left_dir, right_dir = self.dir, self.dir_same - d = filecmp.dircmp(left_dir, right_dir) + d = filecmp.dircmp(left_dir, right_dir, **options) self.assertEqual(d.left, left_dir) self.assertEqual(d.right, right_dir) if self.caseinsensitive: @@ -142,9 +231,10 @@ def test_dircmp(self): ] self._assert_report(d.report, expected_report) + def _assert_dircmp_different_directories(self, **options): # Check attributes for comparison of two different directories (right) left_dir, right_dir = self.dir, self.dir_diff - d = filecmp.dircmp(left_dir, right_dir) + d = filecmp.dircmp(left_dir, right_dir, **options) self.assertEqual(d.left, left_dir) self.assertEqual(d.right, right_dir) self._assert_lists(d.left_list, ['file', 'subdir']) @@ -164,12 +254,8 @@ def test_dircmp(self): self._assert_report(d.report, expected_report) # Check attributes for comparison of two different directories (left) - left_dir, right_dir = self.dir, self.dir_diff - shutil.move( - os.path.join(self.dir_diff, 'file2'), - os.path.join(self.dir, 'file2') - ) - d = filecmp.dircmp(left_dir, right_dir) + left_dir, right_dir = self.dir_diff, self.dir + d = filecmp.dircmp(left_dir, right_dir, **options) self.assertEqual(d.left, left_dir) self.assertEqual(d.right, right_dir) self._assert_lists(d.left_list, ['file', 'file2', 'subdir']) @@ -180,27 +266,62 @@ def test_dircmp(self): self.assertEqual(d.same_files, ['file']) self.assertEqual(d.diff_files, []) expected_report = [ - "diff {} {}".format(self.dir, self.dir_diff), - "Only in {} : ['file2']".format(self.dir), + "diff {} {}".format(self.dir_diff, self.dir), + "Only in {} : ['file2']".format(self.dir_diff), "Identical files : ['file']", "Common subdirectories : ['subdir']", ] self._assert_report(d.report, expected_report) - # Add different file2 - with open(os.path.join(self.dir_diff, 'file2'), 'w', encoding="utf-8") as output: - output.write('Different contents.\n') - d = filecmp.dircmp(self.dir, self.dir_diff) + + def _assert_dircmp_different_file(self, **options): + # A different file2 + d = filecmp.dircmp(self.dir_diff, self.dir_diff_file, **options) self.assertEqual(d.same_files, ['file']) self.assertEqual(d.diff_files, ['file2']) expected_report = [ - "diff {} {}".format(self.dir, self.dir_diff), + "diff {} {}".format(self.dir_diff, self.dir_diff_file), "Identical files : ['file']", "Differing files : ['file2']", "Common subdirectories : ['subdir']", ] self._assert_report(d.report, expected_report) + def test_dircmp_no_shallow_different_file(self): + # A non shallow different file2 + d = filecmp.dircmp(self.dir, self.dir_same_shallow, shallow=False) + self.assertEqual(d.same_files, []) + self.assertEqual(d.diff_files, ['file']) + expected_report = [ + "diff {} {}".format(self.dir, self.dir_same_shallow), + "Differing files : ['file']", + "Common subdirectories : ['subdir']", + ] + self._assert_report(d.report, expected_report) + + def test_dircmp_shallow_same_file(self): + # A non shallow different file2 + d = filecmp.dircmp(self.dir, self.dir_same_shallow) + self.assertEqual(d.same_files, ['file']) + self.assertEqual(d.diff_files, []) + expected_report = [ + "diff {} {}".format(self.dir, self.dir_same_shallow), + "Identical files : ['file']", + "Common subdirectories : ['subdir']", + ] + self._assert_report(d.report, expected_report) + + def test_dircmp_shallow_is_keyword_only(self): + with self.assertRaisesRegex( + TypeError, + re.escape("dircmp.__init__() takes from 3 to 5 positional arguments but 6 were given"), + ): + filecmp.dircmp(self.dir, self.dir_same, None, None, True) + self.assertIsInstance( + filecmp.dircmp(self.dir, self.dir_same, None, None, shallow=True), + filecmp.dircmp, + ) + def test_dircmp_subdirs_type(self): """Check that dircmp.subdirs respects subclassing.""" class MyDirCmp(filecmp.dircmp): diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 09d35a77a1c..7450cd34143 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -5375,7 +5375,6 @@ def call_after_accept(conn_to_client): class TestEnumerations(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_tlsversion(self): class CheckedTLSVersion(enum.IntEnum): MINIMUM_SUPPORTED = _ssl.PROTO_MINIMUM_SUPPORTED @@ -5387,7 +5386,6 @@ class CheckedTLSVersion(enum.IntEnum): MAXIMUM_SUPPORTED = _ssl.PROTO_MAXIMUM_SUPPORTED enum._test_simple_enum(CheckedTLSVersion, TLSVersion) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_tlscontenttype(self): class Checked_TLSContentType(enum.IntEnum): """Content types (record layer) @@ -5403,7 +5401,6 @@ class Checked_TLSContentType(enum.IntEnum): INNER_CONTENT_TYPE = 0x101 enum._test_simple_enum(Checked_TLSContentType, _TLSContentType) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_tlsalerttype(self): class Checked_TLSAlertType(enum.IntEnum): """Alert types for TLSContentType.ALERT messages @@ -5446,7 +5443,6 @@ class Checked_TLSAlertType(enum.IntEnum): NO_APPLICATION_PROTOCOL = 120 enum._test_simple_enum(Checked_TLSAlertType, _TLSAlertType) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_tlsmessagetype(self): class Checked_TLSMessageType(enum.IntEnum): """Message types (handshake protocol) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 4aa15f69932..ce396aa942b 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -33,7 +33,6 @@ def get_command_stdout(command, args): class BaseTestUUID: uuid = None - @unittest.expectedFailure # TODO: RUSTPYTHON def test_safe_uuid_enum(self): class CheckedSafeUUID(enum.Enum): safe = 0 From 5ae75a679bbf37da78ed56367a14c0bed7943773 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Fri, 2 Jan 2026 18:28:25 -0500 Subject: [PATCH 672/819] Update the fractions and ftplib libraries + associated tests - v.3.13.10 (#6607) * Updated ftplib + test library * Updated fractions + test library --- Lib/fractions.py | 107 ++++++++++--- Lib/ftplib.py | 27 ++-- Lib/test/test_fractions.py | 315 ++++++++++++++++++++++--------------- Lib/test/test_ftplib.py | 86 ++++++---- 4 files changed, 338 insertions(+), 197 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 88b418fe383..9d42e809875 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -139,6 +139,23 @@ def _round_to_figures(n, d, figures): return sign, significand, exponent +# Pattern for matching non-float-style format specifications. +_GENERAL_FORMAT_SPECIFICATION_MATCHER = re.compile(r""" + (?: + (?P<fill>.)? + (?P<align>[<>=^]) + )? + (?P<sign>[-+ ]?) + # Alt flag forces a slash and denominator in the output, even for + # integer-valued Fraction objects. + (?P<alt>\#)? + # We don't implement the zeropad flag since there's no single obvious way + # to interpret it. + (?P<minimumwidth>0|[1-9][0-9]*)? + (?P<thousands_sep>[,_])? +""", re.DOTALL | re.VERBOSE).fullmatch + + # Pattern for matching float-style format specifications; # supports 'e', 'E', 'f', 'F', 'g', 'G' and '%' presentation types. _FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r""" @@ -414,27 +431,42 @@ def __str__(self): else: 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) + def _format_general(self, match): + """Helper method for __format__. + Handles fill, alignment, signs, and thousands separators in the + case of no presentation type. + """ # 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"] + alternate_form = bool(match["alt"]) + minimumwidth = int(match["minimumwidth"] or "0") + thousands_sep = match["thousands_sep"] or '' + + # Determine the body and sign representation. + n, d = self._numerator, self._denominator + if d > 1 or alternate_form: + body = f"{abs(n):{thousands_sep}}/{d:{thousands_sep}}" + else: + body = f"{abs(n):{thousands_sep}}" + sign = '-' if n < 0 else pos_sign + + # Pad with fill character if necessary and return. + 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 _format_float_style(self, match): + """Helper method for __format__; handles float presentation types.""" fill = match["fill"] or " " align = match["align"] or ">" pos_sign = "" if match["sign"] == "-" else match["sign"] @@ -449,6 +481,9 @@ def __format__(self, format_spec, /): trim_point = not alternate_form exponent_indicator = "E" if presentation_type in "EFG" else "e" + if align == '=' and fill == '0': + zeropad = True + # 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 @@ -530,7 +565,25 @@ def __format__(self, format_spec, /): else: # align == "=" return sign + padding + body - def _operator_fallbacks(monomorphic_operator, fallback_operator): + def __format__(self, format_spec, /): + """Format this fraction according to the given format specification.""" + + if match := _GENERAL_FORMAT_SPECIFICATION_MATCHER(format_spec): + return self._format_general(match) + + if match := _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec): + # Refuse the temptation to guess if both alignment _and_ + # zero padding are specified. + if match["align"] is None or match["zeropad"] is None: + return self._format_float_style(match) + + raise ValueError( + f"Invalid format specifier {format_spec!r} " + f"for object of type {type(self).__name__!r}" + ) + + def _operator_fallbacks(monomorphic_operator, fallback_operator, + handle_complex=True): """Generates forward and reverse operators given a purely-rational operator and a function from the operator module. @@ -617,7 +670,7 @@ def forward(a, b): return monomorphic_operator(a, Fraction(b)) elif isinstance(b, float): return fallback_operator(float(a), b) - elif isinstance(b, complex): + elif handle_complex and isinstance(b, complex): return fallback_operator(complex(a), b) else: return NotImplemented @@ -630,7 +683,7 @@ def reverse(b, a): return monomorphic_operator(Fraction(a), b) elif isinstance(a, numbers.Real): return fallback_operator(float(a), float(b)) - elif isinstance(a, numbers.Complex): + elif handle_complex and isinstance(a, numbers.Complex): return fallback_operator(complex(a), complex(b)) else: return NotImplemented @@ -781,7 +834,7 @@ def _floordiv(a, b): """a // b""" return (a.numerator * b.denominator) // (a.denominator * b.numerator) - __floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv) + __floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv, False) def _divmod(a, b): """(a // b, a % b)""" @@ -789,14 +842,14 @@ def _divmod(a, b): div, n_mod = divmod(a.numerator * db, da * b.numerator) return div, Fraction(n_mod, da * db) - __divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod) + __divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod, False) def _mod(a, b): """a % b""" da, db = a.denominator, b.denominator return Fraction((a.numerator * db) % (b.numerator * da), da * db) - __mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod) + __mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod, False) def __pow__(a, b): """a ** b @@ -825,8 +878,10 @@ def __pow__(a, b): # A fractional power will generally produce an # irrational number. return float(a) ** float(b) - else: + elif isinstance(b, (float, complex)): return float(a) ** b + else: + return NotImplemented def __rpow__(b, a): """a ** b""" diff --git a/Lib/ftplib.py b/Lib/ftplib.py index a56e0c30857..10c5d1ea08a 100644 --- a/Lib/ftplib.py +++ b/Lib/ftplib.py @@ -900,11 +900,17 @@ def ftpcp(source, sourcename, target, targetname = '', type = 'I'): def test(): '''Test program. - Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ... + Usage: ftplib [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ... - -d dir - -l list - -p password + Options: + -d increase debugging level + -r[file] set alternate ~/.netrc file + + Commands: + -l[dir] list directory + -d[dir] change the current directory + -p toggle passive and active mode + file retrieve the file and write it to stdout ''' if len(sys.argv) < 2: @@ -930,15 +936,14 @@ def test(): netrcobj = netrc.netrc(rcfile) except OSError: if rcfile is not None: - sys.stderr.write("Could not open account file" - " -- using anonymous login.") + print("Could not open account file -- using anonymous login.", + file=sys.stderr) else: try: userid, acct, passwd = netrcobj.authenticators(host) - except KeyError: + except (KeyError, TypeError): # no account for host - sys.stderr.write( - "No account -- using anonymous login.") + print("No account -- using anonymous login.", file=sys.stderr) ftp.login(userid, passwd, acct) for file in sys.argv[2:]: if file[:2] == '-l': @@ -951,7 +956,9 @@ def test(): ftp.set_pasv(not ftp.passiveserver) else: ftp.retrbinary('RETR ' + file, \ - sys.stdout.write, 1024) + sys.stdout.buffer.write, 1024) + sys.stdout.buffer.flush() + sys.stdout.flush() ftp.quit() diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 5c74e36a182..67c9b98d67f 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -2,7 +2,7 @@ import cmath from decimal import Decimal -# from test.support import requires_IEEE_754 +from test.support import requires_IEEE_754, adjust_int_max_str_digits import math import numbers import operator @@ -19,7 +19,7 @@ #locate file with float format test values test_dir = os.path.dirname(__file__) or os.curdir -format_testfile = os.path.join(test_dir, 'formatfloat_testcases.txt') +format_testfile = os.path.join(test_dir, 'mathdata', 'formatfloat_testcases.txt') class DummyFloat(object): """Dummy float class for testing comparisons with Fractions""" @@ -97,7 +97,7 @@ def typed_approx_eq(a, b): class Symbolic: """Simple non-numeric class for testing mixed arithmetic. - It is not Integral, Rational, Real or Complex, and cannot be conveted + It is not Integral, Rational, Real or Complex, and cannot be converted to int, float or complex. but it supports some arithmetic operations. """ def __init__(self, value): @@ -331,7 +331,7 @@ def testInit(self): self.assertRaises(TypeError, F, 3, 1j) self.assertRaises(TypeError, F, 1, 2, 3) - # @requires_IEEE_754 + @requires_IEEE_754 def testInitFromFloat(self): self.assertEqual((5, 2), _components(F(2.5))) self.assertEqual((0, 1), _components(F(-0.0))) @@ -357,12 +357,14 @@ def testInitFromDecimal(self): def testFromString(self): self.assertEqual((5, 1), _components(F("5"))) + self.assertEqual((5, 1), _components(F("005"))) self.assertEqual((3, 2), _components(F("3/2"))) self.assertEqual((3, 2), _components(F("3 / 2"))) self.assertEqual((3, 2), _components(F(" \n +3/2"))) self.assertEqual((-3, 2), _components(F("-3/2 "))) - self.assertEqual((13, 2), _components(F(" 013/02 \n "))) + self.assertEqual((13, 2), _components(F(" 0013/002 \n "))) self.assertEqual((16, 5), _components(F(" 3.2 "))) + self.assertEqual((16, 5), _components(F("003.2"))) self.assertEqual((-16, 5), _components(F(" -3.2 "))) self.assertEqual((-3, 1), _components(F(" -3. "))) self.assertEqual((3, 5), _components(F(" .6 "))) @@ -381,116 +383,102 @@ def testFromString(self): self.assertRaisesMessage( ZeroDivisionError, "Fraction(3, 0)", F, "3/0") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '3/'", - F, "3/") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '/2'", - F, "/2") - self.assertRaisesMessage( - # Denominators don't need a sign. - ValueError, "Invalid literal for Fraction: '3/+2'", - F, "3/+2") - self.assertRaisesMessage( - # Imitate float's parsing. - ValueError, "Invalid literal for Fraction: '+ 3/2'", - F, "+ 3/2") - self.assertRaisesMessage( - # Avoid treating '.' as a regex special character. - ValueError, "Invalid literal for Fraction: '3a2'", - F, "3a2") - self.assertRaisesMessage( - # Don't accept combinations of decimals and rationals. - ValueError, "Invalid literal for Fraction: '3/7.2'", - F, "3/7.2") - self.assertRaisesMessage( - # Don't accept combinations of decimals and rationals. - ValueError, "Invalid literal for Fraction: '3.2/7'", - F, "3.2/7") - self.assertRaisesMessage( - # Allow 3. and .3, but not . - ValueError, "Invalid literal for Fraction: '.'", - F, ".") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '_'", - F, "_") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '_1'", - F, "_1") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1__2'", - F, "1__2") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '/_'", - F, "/_") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1_/'", - F, "1_/") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '_1/'", - F, "_1/") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1__2/'", - F, "1__2/") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1/_'", - F, "1/_") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1/_1'", - F, "1/_1") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1/1__2'", - F, "1/1__2") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1._111'", - F, "1._111") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1.1__1'", - F, "1.1__1") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1.1e+_1'", - F, "1.1e+_1") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1.1e+1__1'", - F, "1.1e+1__1") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '123.dd'", - F, "123.dd") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '123.5_dd'", - F, "123.5_dd") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: 'dd.5'", - F, "dd.5") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '7_dd'", - F, "7_dd") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1/dd'", - F, "1/dd") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1/123_dd'", - F, "1/123_dd") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '789edd'", - F, "789edd") - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '789e2_dd'", - F, "789e2_dd") + + def check_invalid(s): + msg = "Invalid literal for Fraction: " + repr(s) + self.assertRaisesMessage(ValueError, msg, F, s) + + check_invalid("3/") + check_invalid("/2") + # Denominators don't need a sign. + check_invalid("3/+2") + check_invalid("3/-2") + # Imitate float's parsing. + check_invalid("+ 3/2") + check_invalid("- 3/2") + # Avoid treating '.' as a regex special character. + check_invalid("3a2") + # Don't accept combinations of decimals and rationals. + check_invalid("3/7.2") + check_invalid("3.2/7") + # No space around dot. + check_invalid("3 .2") + check_invalid("3. 2") + # No space around e. + check_invalid("3.2 e1") + check_invalid("3.2e 1") + # Fractional part don't need a sign. + check_invalid("3.+2") + check_invalid("3.-2") + # Only accept base 10. + check_invalid("0x10") + check_invalid("0x10/1") + check_invalid("1/0x10") + check_invalid("0x10.") + check_invalid("0x10.1") + check_invalid("1.0x10") + check_invalid("1.0e0x10") + # Only accept decimal digits. + check_invalid("³") + check_invalid("³/2") + check_invalid("3/²") + check_invalid("³.2") + check_invalid("3.²") + check_invalid("3.2e²") + check_invalid("¼") + # Allow 3. and .3, but not . + check_invalid(".") + check_invalid("_") + check_invalid("_1") + check_invalid("1__2") + check_invalid("/_") + check_invalid("1_/") + check_invalid("_1/") + check_invalid("1__2/") + check_invalid("1/_") + check_invalid("1/_1") + check_invalid("1/1__2") + check_invalid("1._111") + check_invalid("1.1__1") + check_invalid("1.1e+_1") + check_invalid("1.1e+1__1") + check_invalid("123.dd") + check_invalid("123.5_dd") + check_invalid("dd.5") + check_invalid("7_dd") + check_invalid("1/dd") + check_invalid("1/123_dd") + check_invalid("789edd") + check_invalid("789e2_dd") # Test catastrophic backtracking. val = "9"*50 + "_" - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '" + val + "'", - F, val) - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1/" + val + "'", - F, "1/" + val) - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1." + val + "'", - F, "1." + val) - self.assertRaisesMessage( - ValueError, "Invalid literal for Fraction: '1.1+e" + val + "'", - F, "1.1+e" + val) + check_invalid(val) + check_invalid("1/" + val) + check_invalid("1." + val) + check_invalid("." + val) + check_invalid("1.1+e" + val) + check_invalid("1.1e" + val) + + def test_limit_int(self): + maxdigits = 5000 + with adjust_int_max_str_digits(maxdigits): + msg = 'Exceeds the limit' + val = '1' * maxdigits + num = (10**maxdigits - 1)//9 + self.assertEqual((num, 1), _components(F(val))) + self.assertRaisesRegex(ValueError, msg, F, val + '1') + self.assertEqual((num, 2), _components(F(val + '/2'))) + self.assertRaisesRegex(ValueError, msg, F, val + '1/2') + self.assertEqual((1, num), _components(F('1/' + val))) + self.assertRaisesRegex(ValueError, msg, F, '1/1' + val) + self.assertEqual(((10**(maxdigits+1) - 1)//9, 10**maxdigits), + _components(F('1.' + val))) + self.assertRaisesRegex(ValueError, msg, F, '1.1' + val) + self.assertEqual((num, 10**maxdigits), _components(F('.' + val))) + self.assertRaisesRegex(ValueError, msg, F, '.1' + val) + self.assertRaisesRegex(ValueError, msg, F, '1.1e1' + val) + self.assertEqual((11, 10), _components(F('1.1e' + '0' * maxdigits))) + self.assertRaisesRegex(ValueError, msg, F, '1.1e' + '0' * (maxdigits+1)) def testImmutable(self): r = F(7, 3) @@ -922,21 +910,21 @@ def testMixedPower(self): self.assertTypedEquals(Root(4) ** F(2, 1), Root(4, F(1))) self.assertTypedEquals(Root(4) ** F(-2, 1), Root(4, -F(1))) self.assertTypedEquals(Root(4) ** F(-2, 3), Root(4, -3.0)) - self.assertEqual(F(3, 2) ** SymbolicReal('X'), SymbolicReal('1.5 ** X')) + self.assertEqual(F(3, 2) ** SymbolicReal('X'), SymbolicReal('3/2 ** X')) self.assertEqual(SymbolicReal('X') ** F(3, 2), SymbolicReal('X ** 1.5')) - self.assertTypedEquals(F(3, 2) ** Rect(2, 0), Polar(2.25, 0.0)) - self.assertTypedEquals(F(1, 1) ** Rect(2, 3), Polar(1.0, 0.0)) + self.assertTypedEquals(F(3, 2) ** Rect(2, 0), Polar(F(9,4), 0.0)) + self.assertTypedEquals(F(1, 1) ** Rect(2, 3), Polar(F(1), 0.0)) self.assertTypedEquals(F(3, 2) ** RectComplex(2, 0), Polar(2.25, 0.0)) self.assertTypedEquals(F(1, 1) ** RectComplex(2, 3), Polar(1.0, 0.0)) self.assertTypedEquals(Polar(4, 2) ** F(3, 2), Polar(8.0, 3.0)) self.assertTypedEquals(Polar(4, 2) ** F(3, 1), Polar(64, 6)) self.assertTypedEquals(Polar(4, 2) ** F(-3, 1), Polar(0.015625, -6)) self.assertTypedEquals(Polar(4, 2) ** F(-3, 2), Polar(0.125, -3.0)) - self.assertEqual(F(3, 2) ** SymbolicComplex('X'), SymbolicComplex('1.5 ** X')) + self.assertEqual(F(3, 2) ** SymbolicComplex('X'), SymbolicComplex('3/2 ** X')) self.assertEqual(SymbolicComplex('X') ** F(3, 2), SymbolicComplex('X ** 1.5')) - self.assertEqual(F(3, 2) ** Symbolic('X'), Symbolic('1.5 ** X')) + self.assertEqual(F(3, 2) ** Symbolic('X'), Symbolic('3/2 ** X')) self.assertEqual(Symbolic('X') ** F(3, 2), Symbolic('X ** 1.5')) def testMixingWithDecimal(self): @@ -1165,12 +1153,50 @@ def denominator(self): self.assertEqual(type(f.denominator), myint) def test_format_no_presentation_type(self): - # Triples (fraction, specification, expected_result) + # Triples (fraction, specification, expected_result). testcases = [ - (F(1, 3), '', '1/3'), - (F(-1, 3), '', '-1/3'), - (F(3), '', '3'), - (F(-3), '', '-3'), + # Explicit sign handling + (F(2, 3), '+', '+2/3'), + (F(-2, 3), '+', '-2/3'), + (F(3), '+', '+3'), + (F(-3), '+', '-3'), + (F(2, 3), ' ', ' 2/3'), + (F(-2, 3), ' ', '-2/3'), + (F(3), ' ', ' 3'), + (F(-3), ' ', '-3'), + (F(2, 3), '-', '2/3'), + (F(-2, 3), '-', '-2/3'), + (F(3), '-', '3'), + (F(-3), '-', '-3'), + # Padding + (F(0), '5', ' 0'), + (F(2, 3), '5', ' 2/3'), + (F(-2, 3), '5', ' -2/3'), + (F(2, 3), '0', '2/3'), + (F(2, 3), '1', '2/3'), + (F(2, 3), '2', '2/3'), + # Alignment + (F(2, 3), '<5', '2/3 '), + (F(2, 3), '>5', ' 2/3'), + (F(2, 3), '^5', ' 2/3 '), + (F(2, 3), '=5', ' 2/3'), + (F(-2, 3), '<5', '-2/3 '), + (F(-2, 3), '>5', ' -2/3'), + (F(-2, 3), '^5', '-2/3 '), + (F(-2, 3), '=5', '- 2/3'), + # Fill + (F(2, 3), 'X>5', 'XX2/3'), + (F(-2, 3), '.<5', '-2/3.'), + (F(-2, 3), '\n^6', '\n-2/3\n'), + # Thousands separators + (F(1234, 5679), ',', '1,234/5,679'), + (F(-1234, 5679), '_', '-1_234/5_679'), + (F(1234567), '_', '1_234_567'), + (F(-1234567), ',', '-1,234,567'), + # Alternate form forces a slash in the output + (F(123), '#', '123/1'), + (F(-123), '#', '-123/1'), + (F(0), '#', '0/1'), ] for fraction, spec, expected in testcases: with self.subTest(fraction=fraction, spec=spec): @@ -1385,11 +1411,8 @@ def test_format_f_presentation_type(self): (F('-1234.5678'), '08,.0f', '-001,235'), (F('-1234.5678'), '09,.0f', '-0,001,235'), # Corner-case - zero-padding specified through fill and align - # instead of the zero-pad character - in this case, treat '0' as a - # regular fill character and don't attempt to insert commas into - # the filled portion. This differs from the int and float - # behaviour. - (F('1234.5678'), '0=12,.2f', '00001,234.57'), + # instead of the zero-pad character. + (F('1234.5678'), '0=12,.2f', '0,001,234.57'), # Corner case where it's not clear whether the '0' indicates zero # padding or gives the minimum width, but there's still an obvious # answer to give. We want this to work in case the minimum width @@ -1534,13 +1557,17 @@ def test_invalid_formats(self): '.%', # Z instead of z for negative zero suppression 'Z.2f' + # z flag not supported for general formatting + 'z', + # zero padding not supported for general formatting + '05', ] for spec in invalid_specs: with self.subTest(spec=spec): with self.assertRaises(ValueError): format(fraction, spec) - # @requires_IEEE_754 + @requires_IEEE_754 def test_float_format_testfile(self): with open(format_testfile, encoding="utf-8") as testfile: for line in testfile: @@ -1564,6 +1591,36 @@ def test_float_format_testfile(self): self.assertEqual(float(format(f, fmt2)), float(rhs)) self.assertEqual(float(format(-f, fmt2)), float('-' + rhs)) + # TODO: RUSTPYTHON + # TypeError: '%' not supported between instances of 'Fraction' and 'complex' + @unittest.expectedFailure + def test_complex_handling(self): + # See issue gh-102840 for more details. + + a = F(1, 2) + b = 1j + message = "unsupported operand type(s) for %s: '%s' and '%s'" + # test forward + self.assertRaisesMessage(TypeError, + message % ("%", "Fraction", "complex"), + operator.mod, a, b) + self.assertRaisesMessage(TypeError, + message % ("//", "Fraction", "complex"), + operator.floordiv, a, b) + self.assertRaisesMessage(TypeError, + message % ("divmod()", "Fraction", "complex"), + divmod, a, b) + # test reverse + self.assertRaisesMessage(TypeError, + message % ("%", "complex", "Fraction"), + operator.mod, b, a) + self.assertRaisesMessage(TypeError, + message % ("//", "complex", "Fraction"), + operator.floordiv, b, a) + self.assertRaisesMessage(TypeError, + message % ("divmod()", "complex", "Fraction"), + divmod, b, a) + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py index b3e4e776a47..ae4ec5dbfb7 100644 --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -18,6 +18,7 @@ from unittest import TestCase, skipUnless from test import support +from test.support import requires_subprocess from test.support import threading_helper from test.support import socket_helper from test.support import warnings_helper @@ -32,7 +33,7 @@ DEFAULT_ENCODING = 'utf-8' # the dummy data returned by server over the data channel when # RETR, LIST, NLST, MLSD commands are issued -RETR_DATA = 'abcde12345\r\n' * 1000 + 'non-ascii char \xAE\r\n' +RETR_DATA = 'abcde\xB9\xB2\xB3\xA4\xA6\r\n' * 1000 LIST_DATA = 'foo\r\nbar\r\n non-ascii char \xAE\r\n' NLST_DATA = 'foo\r\nbar\r\n non-ascii char \xAE\r\n' MLSD_DATA = ("type=cdir;perm=el;unique==keVO1+ZF4; test\r\n" @@ -67,11 +68,11 @@ class DummyDTPHandler(asynchat.async_chat): def __init__(self, conn, baseclass): asynchat.async_chat.__init__(self, conn) self.baseclass = baseclass - self.baseclass.last_received_data = '' + self.baseclass.last_received_data = bytearray() self.encoding = baseclass.encoding def handle_read(self): - new_data = self.recv(1024).decode(self.encoding, 'replace') + new_data = self.recv(1024) self.baseclass.last_received_data += new_data def handle_close(self): @@ -80,7 +81,7 @@ def handle_close(self): # (behaviour witnessed with test_data_connection) if not self.dtp_conn_closed: self.baseclass.push('226 transfer complete') - self.close() + self.shutdown() self.dtp_conn_closed = True def push(self, what): @@ -94,6 +95,9 @@ def push(self, what): def handle_error(self): default_error_handler() + def shutdown(self): + self.close() + class DummyFTPHandler(asynchat.async_chat): @@ -107,7 +111,7 @@ def __init__(self, conn, encoding=DEFAULT_ENCODING): self.in_buffer = [] self.dtp = None self.last_received_cmd = None - self.last_received_data = '' + self.last_received_data = bytearray() self.next_response = '' self.next_data = None self.rest = None @@ -226,7 +230,7 @@ def cmd_type(self, arg): def cmd_quit(self, arg): self.push('221 quit ok') - self.close() + self.shutdown() def cmd_abor(self, arg): self.push('226 abor ok') @@ -313,7 +317,7 @@ def handle_accepted(self, conn, addr): self.handler_instance = self.handler(conn, encoding=self.encoding) def handle_connect(self): - self.close() + self.shutdown() handle_read = handle_connect def writable(self): @@ -325,8 +329,8 @@ def handle_error(self): if ssl is not None: - CERTFILE = os.path.join(os.path.dirname(__file__), "keycert3.pem") - CAFILE = os.path.join(os.path.dirname(__file__), "pycacert.pem") + CERTFILE = os.path.join(os.path.dirname(__file__), "certdata", "keycert3.pem") + CAFILE = os.path.join(os.path.dirname(__file__), "certdata", "pycacert.pem") class SSLConnection(asyncore.dispatcher): """An asyncore.dispatcher subclass supporting TLS/SSL.""" @@ -425,12 +429,12 @@ def recv(self, buffer_size): def handle_error(self): default_error_handler() - def close(self): + def shutdown(self): if (isinstance(self.socket, ssl.SSLSocket) and self.socket._sslobj is not None): self._do_ssl_shutdown() else: - super(SSLConnection, self).close() + self.close() class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler): @@ -542,8 +546,8 @@ def test_set_pasv(self): self.assertFalse(self.client.passiveserver) def test_voidcmd(self): - self.client.voidcmd('echo 200') - self.client.voidcmd('echo 299') + self.assertEqual(self.client.voidcmd('echo 200'), '200') + self.assertEqual(self.client.voidcmd('echo 299'), '299') self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 199') self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 300') @@ -589,37 +593,44 @@ def test_quit(self): def test_abort(self): self.client.abort() + @unittest.skip('TODO: RUSTPYTHON') + # TimeoutError: The read operation timed out def test_retrbinary(self): - def callback(data): - received.append(data.decode(self.client.encoding)) received = [] - self.client.retrbinary('retr', callback) - self.check_data(''.join(received), RETR_DATA) + self.client.retrbinary('retr', received.append) + self.check_data(b''.join(received), + RETR_DATA.encode(self.client.encoding)) + @unittest.skip('TODO: RUSTPYTHON') + # TimeoutError: The read operation timed out def test_retrbinary_rest(self): - def callback(data): - received.append(data.decode(self.client.encoding)) for rest in (0, 10, 20): received = [] - self.client.retrbinary('retr', callback, rest=rest) - self.check_data(''.join(received), RETR_DATA[rest:]) + self.client.retrbinary('retr', received.append, rest=rest) + self.check_data(b''.join(received), + RETR_DATA[rest:].encode(self.client.encoding)) + @unittest.skip('TODO: RUSTPYTHON') + # TimeoutError: The read operation timed out def test_retrlines(self): received = [] self.client.retrlines('retr', received.append) self.check_data(''.join(received), RETR_DATA.replace('\r\n', '')) - @unittest.skip("TODO: RUSTPYTHON; weird limiting to 8192, something w/ buffering?") + @unittest.skip('TODO: RUSTPYTHON; weird limiting to 8192, something w/ buffering?') def test_storbinary(self): f = io.BytesIO(RETR_DATA.encode(self.client.encoding)) self.client.storbinary('stor', f) - self.check_data(self.server.handler_instance.last_received_data, RETR_DATA) + self.check_data(self.server.handler_instance.last_received_data, + RETR_DATA.encode(self.server.encoding)) # test new callback arg flag = [] f.seek(0) self.client.storbinary('stor', f, callback=lambda x: flag.append(None)) self.assertTrue(flag) + @unittest.skip('TODO: RUSTPYTHON') + # ssl_error.SSLWantReadError: The operation did not complete (read) def test_storbinary_rest(self): data = RETR_DATA.replace('\r\n', '\n').encode(self.client.encoding) f = io.BytesIO(data) @@ -628,11 +639,14 @@ def test_storbinary_rest(self): self.client.storbinary('stor', f, rest=r) self.assertEqual(self.server.handler_instance.rest, str(r)) + @unittest.skip('TODO: RUSTPYTHON') + # ssl_error.SSLWantReadError: The operation did not complete (read) def test_storlines(self): data = RETR_DATA.replace('\r\n', '\n').encode(self.client.encoding) f = io.BytesIO(data) self.client.storlines('stor', f) - self.check_data(self.server.handler_instance.last_received_data, RETR_DATA) + self.check_data(self.server.handler_instance.last_received_data, + RETR_DATA.encode(self.server.encoding)) # test new callback arg flag = [] f.seek(0) @@ -644,15 +658,21 @@ def test_storlines(self): with warnings_helper.check_warnings(('', BytesWarning), quiet=True): self.assertRaises(TypeError, self.client.storlines, 'stor foo', f) + @unittest.skip('TODO: RUSTPYTHON') + # TimeoutError: The read operation timed out def test_nlst(self): self.client.nlst() self.assertEqual(self.client.nlst(), NLST_DATA.split('\r\n')[:-1]) + @unittest.skip('TODO: RUSTPYTHON') + # TimeoutError: The read operation timed out def test_dir(self): l = [] - self.client.dir(lambda x: l.append(x)) + self.client.dir(l.append) self.assertEqual(''.join(l), LIST_DATA.replace('\r\n', '')) + @unittest.skip('TODO: RUSTPYTHON') + # TimeoutError: The read operation timed out def test_mlsd(self): list(self.client.mlsd()) list(self.client.mlsd(path='/')) @@ -839,6 +859,8 @@ def test_storlines_too_long(self): f = io.BytesIO(b'x' * self.client.maxline * 2) self.assertRaises(ftplib.Error, self.client.storlines, 'stor', f) + @unittest.skip('TODO: RUSTPYTHON') + # TimeoutError: The read operation timed out def test_encoding_param(self): encodings = ['latin-1', 'utf-8'] for encoding in encodings: @@ -890,12 +912,10 @@ def test_makepasv(self): def test_transfer(self): def retr(): - def callback(data): - received.append(data.decode(self.client.encoding)) received = [] - self.client.retrbinary('retr', callback) - self.assertEqual(len(''.join(received)), len(RETR_DATA)) - self.assertEqual(''.join(received), RETR_DATA) + self.client.retrbinary('retr', received.append) + self.assertEqual(b''.join(received), + RETR_DATA.encode(self.client.encoding)) self.client.set_pasv(True) retr() self.client.set_pasv(False) @@ -903,7 +923,7 @@ def callback(data): @skipUnless(ssl, "SSL not available") -@unittest.skip("TODO: RUSTPYTHON; figure out why do_handshake() is throwing 'ssl session has been shut down'. SslSession object?") +@requires_subprocess() class TestTLS_FTPClassMixin(TestFTPClass): """Repeat TestFTPClass tests starting the TLS layer for both control and data connections first. @@ -920,7 +940,7 @@ def setUp(self, encoding=DEFAULT_ENCODING): @skipUnless(ssl, "SSL not available") -@unittest.skip("TODO: RUSTPYTHON; fix ssl") +@requires_subprocess() class TestTLS_FTPClass(TestCase): """Specific TLS_FTP class tests.""" @@ -1004,6 +1024,8 @@ def test_context(self): self.assertIs(sock.context, ctx) self.assertIsInstance(sock, ssl.SSLSocket) + @unittest.skip('TODO: RUSTPYTHON') + # ssl_error.SSLWantReadError: The operation did not complete (read) def test_ccc(self): self.assertRaises(ValueError, self.client.ccc) self.client.login(secure=True) From cff37af5f67c1c9f64fd655dd48d638151ddb505 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sat, 3 Jan 2026 11:43:29 +0900 Subject: [PATCH 673/819] Fix locale.getencoding, overlapped.getresult, chdir for windows (#6629) * locale getencoding * overlapped.getresult * Fix windows chdir * mark flaky --- Lib/test/_test_multiprocessing.py | 1 + crates/stdlib/src/locale.rs | 37 ++++++ crates/stdlib/src/overlapped.rs | 183 +++++++++++++++++++++++++++++- crates/vm/src/stdlib/os.rs | 35 +++++- 4 files changed, 253 insertions(+), 3 deletions(-) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 351887819e6..093139e45e5 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -1459,6 +1459,7 @@ def _acquire_release(lock, timeout, l=None, n=1): for _ in range(n): lock.release() + @unittest.skip("TODO: RUSTPYTHON; flaky test") def test_repr_rlock(self): if self.TYPE != 'processes': self.skipTest('test not appropriate for {}'.format(self.TYPE)) diff --git a/crates/stdlib/src/locale.rs b/crates/stdlib/src/locale.rs index c65f861d208..51cf352590b 100644 --- a/crates/stdlib/src/locale.rs +++ b/crates/stdlib/src/locale.rs @@ -49,6 +49,8 @@ mod _locale { convert::ToPyException, function::OptionalArg, }; + #[cfg(windows)] + use windows_sys::Win32::Globalization::GetACP; #[cfg(all( unix, @@ -260,4 +262,39 @@ mod _locale { pystr_from_raw_cstr(vm, result) } } + + /// Get the current locale encoding. + #[pyfunction] + fn getencoding() -> String { + #[cfg(windows)] + { + // On Windows, use GetACP() to get the ANSI code page + let acp = unsafe { GetACP() }; + format!("cp{}", acp) + } + #[cfg(not(windows))] + { + // On Unix, use nl_langinfo(CODESET) or fallback to UTF-8 + #[cfg(all( + unix, + not(any(target_os = "ios", target_os = "android", target_os = "redox")) + ))] + { + unsafe { + let codeset = libc::nl_langinfo(libc::CODESET); + if !codeset.is_null() + && let Ok(s) = CStr::from_ptr(codeset).to_str() + && !s.is_empty() + { + return s.to_string(); + } + } + "UTF-8".to_string() + } + #[cfg(any(target_os = "ios", target_os = "android", target_os = "redox"))] + { + "UTF-8".to_string() + } + } + } } diff --git a/crates/stdlib/src/overlapped.rs b/crates/stdlib/src/overlapped.rs index 1c74ee271b9..664aa3f392c 100644 --- a/crates/stdlib/src/overlapped.rs +++ b/crates/stdlib/src/overlapped.rs @@ -8,16 +8,17 @@ mod _overlapped { // straight-forward port of Modules/overlapped.c use crate::vm::{ - Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, + AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::{PyBaseExceptionRef, PyBytesRef, PyType}, common::lock::PyMutex, convert::{ToPyException, ToPyObject}, + function::OptionalArg, protocol::PyBuffer, types::Constructor, }; use windows_sys::Win32::{ Foundation::{self, GetLastError, HANDLE}, - Networking::WinSock::SOCKADDR_IN6, + Networking::WinSock::{AF_INET, AF_INET6, SOCKADDR_IN, SOCKADDR_IN6}, System::IO::OVERLAPPED, }; @@ -153,6 +154,51 @@ mod _overlapped { overlapped.Internal != (Foundation::STATUS_PENDING as usize) } + /// Parse a SOCKADDR_IN6 (which can also hold IPv4 addresses) to a Python address tuple + fn unparse_address( + addr: &SOCKADDR_IN6, + _addr_len: libc::c_int, + vm: &VirtualMachine, + ) -> PyObjectRef { + use crate::vm::convert::ToPyObject; + + unsafe { + let family = addr.sin6_family; + if family == AF_INET { + // IPv4 address stored in SOCKADDR_IN6 structure + let addr_in = &*(addr as *const SOCKADDR_IN6 as *const SOCKADDR_IN); + let ip_bytes = addr_in.sin_addr.S_un.S_un_b; + let ip_str = format!( + "{}.{}.{}.{}", + ip_bytes.s_b1, ip_bytes.s_b2, ip_bytes.s_b3, ip_bytes.s_b4 + ); + let port = u16::from_be(addr_in.sin_port); + (ip_str, port).to_pyobject(vm) + } else if family == AF_INET6 { + // IPv6 address + let ip_bytes = addr.sin6_addr.u.Byte; + let ip_str = format!( + "{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", + u16::from_be_bytes([ip_bytes[0], ip_bytes[1]]), + u16::from_be_bytes([ip_bytes[2], ip_bytes[3]]), + u16::from_be_bytes([ip_bytes[4], ip_bytes[5]]), + u16::from_be_bytes([ip_bytes[6], ip_bytes[7]]), + u16::from_be_bytes([ip_bytes[8], ip_bytes[9]]), + u16::from_be_bytes([ip_bytes[10], ip_bytes[11]]), + u16::from_be_bytes([ip_bytes[12], ip_bytes[13]]), + u16::from_be_bytes([ip_bytes[14], ip_bytes[15]]), + ); + let port = u16::from_be(addr.sin6_port); + let flowinfo = addr.sin6_flowinfo; + let scope_id = addr.Anonymous.sin6_scope_id; + (ip_str, port, flowinfo, scope_id).to_pyobject(vm) + } else { + // Unknown address family, return None + vm.ctx.none() + } + } + } + #[pyclass(with(Constructor))] impl Overlapped { #[pygetset] @@ -259,6 +305,139 @@ mod _overlapped { } Ok(()) } + + #[pymethod] + fn getresult(zelf: &Py<Self>, wait: OptionalArg<bool>, vm: &VirtualMachine) -> PyResult { + use windows_sys::Win32::Foundation::{ + ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, + }; + use windows_sys::Win32::System::IO::GetOverlappedResult; + + let mut inner = zelf.inner.lock(); + let wait = wait.unwrap_or(false); + + // Check operation state + if matches!(inner.data, OverlappedData::None) { + return Err(vm.new_value_error("operation not yet attempted".to_owned())); + } + if matches!(inner.data, OverlappedData::NotStarted) { + return Err(vm.new_value_error("operation failed to start".to_owned())); + } + + // Get the result + let mut transferred: u32 = 0; + let ret = unsafe { + GetOverlappedResult( + inner.handle, + &inner.overlapped, + &mut transferred, + if wait { 1 } else { 0 }, + ) + }; + + let err = if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { GetLastError() } + }; + inner.error = err; + + // Handle errors + match err { + ERROR_SUCCESS | ERROR_MORE_DATA => {} + ERROR_BROKEN_PIPE => { + // For read operations, broken pipe is acceptable + match &inner.data { + OverlappedData::Read(_) | OverlappedData::ReadInto(_) => {} + OverlappedData::ReadFrom(rf) + if rf.result.is(&vm.ctx.none()) + || rf.allocated_buffer.is(&vm.ctx.none()) => + { + return Err(from_windows_err(err, vm)); + } + OverlappedData::ReadFrom(_) => {} + OverlappedData::ReadFromInto(rfi) if rfi.result.is(&vm.ctx.none()) => { + return Err(from_windows_err(err, vm)); + } + OverlappedData::ReadFromInto(_) => {} + _ => return Err(from_windows_err(err, vm)), + } + } + ERROR_IO_PENDING => { + return Err(from_windows_err(err, vm)); + } + _ => return Err(from_windows_err(err, vm)), + } + + // Return result based on operation type + match &inner.data { + OverlappedData::Read(buf) => { + // Resize buffer to actual bytes read + let bytes = buf.as_bytes(); + let result = if transferred as usize != bytes.len() { + vm.ctx.new_bytes(bytes[..transferred as usize].to_vec()) + } else { + buf.clone() + }; + Ok(result.into()) + } + OverlappedData::ReadInto(_) => { + // Return number of bytes read + Ok(vm.ctx.new_int(transferred).into()) + } + OverlappedData::Write(_) => { + // Return number of bytes written + Ok(vm.ctx.new_int(transferred).into()) + } + OverlappedData::Accept(_) => { + // Return None for accept + Ok(vm.ctx.none()) + } + OverlappedData::Connect => { + // Return None for connect + Ok(vm.ctx.none()) + } + OverlappedData::Disconnect => { + // Return None for disconnect + Ok(vm.ctx.none()) + } + OverlappedData::ConnectNamedPipe => { + // Return None for connect named pipe + Ok(vm.ctx.none()) + } + OverlappedData::WaitNamedPipeAndConnect => { + // Return None + Ok(vm.ctx.none()) + } + OverlappedData::ReadFrom(rf) => { + // Return (resized_buffer, (host, port)) tuple + let buf = rf + .allocated_buffer + .downcast_ref::<crate::vm::builtins::PyBytes>() + .unwrap(); + let bytes = buf.as_bytes(); + let resized_buf = if transferred as usize != bytes.len() { + vm.ctx.new_bytes(bytes[..transferred as usize].to_vec()) + } else { + buf.to_owned() + }; + let addr_tuple = unparse_address(&rf.address, rf.address_length, vm); + Ok(vm + .ctx + .new_tuple(vec![resized_buf.into(), addr_tuple]) + .into()) + } + OverlappedData::ReadFromInto(rfi) => { + // Return (transferred, (host, port)) tuple + let addr_tuple = unparse_address(&rfi.address, rfi.address_length, vm); + Ok(vm + .ctx + .new_tuple(vec![vm.ctx.new_int(transferred).into(), addr_tuple]) + .into()) + } + _ => Ok(vm.ctx.none()), + } + } } impl Constructor for Overlapped { diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index fa1495fcdbf..416f26018cb 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1124,7 +1124,40 @@ pub(super) mod _os { #[pyfunction] fn chdir(path: OsPath, vm: &VirtualMachine) -> PyResult<()> { env::set_current_dir(&path.path) - .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))?; + + #[cfg(windows)] + { + // win32_wchdir() + + // On Windows, set the per-drive CWD environment variable (=X:) + // This is required for GetFullPathNameW to work correctly with drive-relative paths + + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::System::Environment::SetEnvironmentVariableW; + + if let Ok(cwd) = env::current_dir() { + let cwd_str = cwd.as_os_str(); + let mut cwd_wide: Vec<u16> = cwd_str.encode_wide().collect(); + + // Check for UNC-like paths (\\server\share or //server/share) + // wcsncmp(new_path, L"\\\\", 2) == 0 || wcsncmp(new_path, L"//", 2) == 0 + let is_unc_like_path = cwd_wide.len() >= 2 + && ((cwd_wide[0] == b'\\' as u16 && cwd_wide[1] == b'\\' as u16) + || (cwd_wide[0] == b'/' as u16 && cwd_wide[1] == b'/' as u16)); + + if !is_unc_like_path { + // Create env var name "=X:" where X is the drive letter + let env_name: [u16; 4] = [b'=' as u16, cwd_wide[0], b':' as u16, 0]; + cwd_wide.push(0); // null-terminate the path + unsafe { + SetEnvironmentVariableW(env_name.as_ptr(), cwd_wide.as_ptr()); + } + } + } + } + + Ok(()) } #[pyfunction] From eea6cc49cb0bcb03b28b7b4a01aa6cc118cfdefb Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Sat, 3 Jan 2026 02:27:25 -0500 Subject: [PATCH 674/819] Update the signal + socket libraries + associated tests - v3.13.11 (#6631) * Updated signal test library * Update socketserve + test library * Fixed test issues with mac * Added sys import into ftplib * Reverted ftplib test changes --- Lib/socketserver.py | 30 ++++++++++++++++++------ Lib/test/test_signal.py | 43 +++++++++++++++++++---------------- Lib/test/test_socketserver.py | 27 +++++----------------- 3 files changed, 52 insertions(+), 48 deletions(-) diff --git a/Lib/socketserver.py b/Lib/socketserver.py index 2905e3eac36..35b2723de3b 100644 --- a/Lib/socketserver.py +++ b/Lib/socketserver.py @@ -127,10 +127,7 @@ class will essentially render the service "deaf" while one request is import selectors import os import sys -try: - import threading -except ImportError: - import dummy_threading as threading +import threading from io import BufferedIOBase from time import monotonic as time @@ -144,6 +141,8 @@ class will essentially render the service "deaf" while one request is __all__.extend(["UnixStreamServer","UnixDatagramServer", "ThreadingUnixStreamServer", "ThreadingUnixDatagramServer"]) + if hasattr(os, "fork"): + __all__.extend(["ForkingUnixStreamServer", "ForkingUnixDatagramServer"]) # poll/select have the advantage of not requiring any extra file descriptor, # contrarily to epoll/kqueue (also, they require a single syscall). @@ -190,6 +189,7 @@ class BaseServer: - address_family - socket_type - allow_reuse_address + - allow_reuse_port Instance variables: @@ -294,8 +294,7 @@ def handle_request(self): selector.register(self, selectors.EVENT_READ) while True: - ready = selector.select(timeout) - if ready: + if selector.select(timeout): return self._handle_request_noblock() else: if timeout is not None: @@ -428,6 +427,7 @@ class TCPServer(BaseServer): - socket_type - request_queue_size (only for stream sockets) - allow_reuse_address + - allow_reuse_port Instance variables: @@ -445,6 +445,8 @@ class TCPServer(BaseServer): allow_reuse_address = False + allow_reuse_port = False + def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): """Constructor. May be extended, do not override.""" BaseServer.__init__(self, server_address, RequestHandlerClass) @@ -464,8 +466,15 @@ def server_bind(self): May be overridden. """ - if self.allow_reuse_address: + if self.allow_reuse_address and hasattr(socket, "SO_REUSEADDR"): self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # Since Linux 6.12.9, SO_REUSEPORT is not allowed + # on other address families than AF_INET/AF_INET6. + if ( + self.allow_reuse_port and hasattr(socket, "SO_REUSEPORT") + and self.address_family in (socket.AF_INET, socket.AF_INET6) + ): + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname() @@ -522,6 +531,8 @@ class UDPServer(TCPServer): allow_reuse_address = False + allow_reuse_port = False + socket_type = socket.SOCK_DGRAM max_packet_size = 8192 @@ -723,6 +734,11 @@ class ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer): pass class ThreadingUnixDatagramServer(ThreadingMixIn, UnixDatagramServer): pass + if hasattr(os, "fork"): + class ForkingUnixStreamServer(ForkingMixIn, UnixStreamServer): pass + + class ForkingUnixDatagramServer(ForkingMixIn, UnixDatagramServer): pass + class BaseRequestHandler: """Base class for request handler classes. diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index cb744b41e8b..578bee3172a 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -13,9 +13,10 @@ import time import unittest from test import support -from test.support import os_helper +from test.support import ( + is_apple, is_apple_mobile, os_helper, threading_helper +) from test.support.script_helper import assert_python_ok, spawn_python -from test.support import threading_helper try: import _testcapi except ImportError: @@ -122,6 +123,8 @@ def __repr__(self): self.assertEqual(signal.getsignal(signal.SIGHUP), hup) self.assertEqual(0, argument.repr_count) + @unittest.skipIf(sys.platform.startswith("netbsd"), + "gh-124083: strsignal is not supported on NetBSD") def test_strsignal(self): self.assertIn("Interrupt", signal.strsignal(signal.SIGINT)) self.assertIn("Terminated", signal.strsignal(signal.SIGTERM)) @@ -189,8 +192,7 @@ def test_valid_signals(self): self.assertNotIn(signal.NSIG, s) self.assertLess(len(s), signal.NSIG) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_issue9324(self): # Updated for issue #10003, adding SIGBREAK handler = lambda x, y: None @@ -699,7 +701,7 @@ def handler(signum, frame): @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") class SiginterruptTest(unittest.TestCase): - def readpipe_interrupted(self, interrupt): + def readpipe_interrupted(self, interrupt, timeout=support.SHORT_TIMEOUT): """Perform a read during which a signal will arrive. Return True if the read is interrupted by the signal and raises an exception. Return False if it returns normally. @@ -747,7 +749,7 @@ def handler(signum, frame): # wait until the child process is loaded and has started first_line = process.stdout.readline() - stdout, stderr = process.communicate(timeout=support.SHORT_TIMEOUT) + stdout, stderr = process.communicate(timeout=timeout) except subprocess.TimeoutExpired: process.kill() return False @@ -778,7 +780,7 @@ def test_siginterrupt_off(self): # If a signal handler is installed and siginterrupt is called with # a false value for the second argument, when that signal arrives, it # does not interrupt a syscall that's in progress. - interrupted = self.readpipe_interrupted(False) + interrupted = self.readpipe_interrupted(False, timeout=2) self.assertFalse(interrupted) @@ -834,16 +836,16 @@ def test_itimer_real(self): self.assertEqual(self.hndl_called, True) # Issue 3864, unknown if this affects earlier versions of freebsd also - @unittest.skipIf(sys.platform in ('netbsd5',), + @unittest.skipIf(sys.platform in ('netbsd5',) or is_apple_mobile, 'itimer not reliable (does not mix well with threading) on some BSDs.') def test_itimer_virtual(self): self.itimer = signal.ITIMER_VIRTUAL signal.signal(signal.SIGVTALRM, self.sig_vtalrm) - signal.setitimer(self.itimer, 0.3, 0.2) + signal.setitimer(self.itimer, 0.001, 0.001) for _ in support.busy_retry(support.LONG_TIMEOUT): # use up some virtual time by doing real work - _ = pow(12345, 67890, 10000019) + _ = sum(i * i for i in range(10**5)) if signal.getitimer(self.itimer) == (0.0, 0.0): # sig_vtalrm handler stopped this itimer break @@ -860,7 +862,7 @@ def test_itimer_prof(self): for _ in support.busy_retry(support.LONG_TIMEOUT): # do some work - _ = pow(12345, 67890, 10000019) + _ = sum(i * i for i in range(10**5)) if signal.getitimer(self.itimer) == (0.0, 0.0): # sig_prof handler stopped this itimer break @@ -1326,15 +1328,18 @@ def test_stress_delivery_simultaneous(self): def handler(signum, frame): sigs.append(signum) - self.setsig(signal.SIGUSR1, handler) + # On Android, SIGUSR1 is unreliable when used in close proximity to + # another signal – see Android/testbed/app/src/main/python/main.py. + # So we use a different signal. + self.setsig(signal.SIGUSR2, handler) self.setsig(signal.SIGALRM, handler) # for ITIMER_REAL expected_sigs = 0 while expected_sigs < N: # Hopefully the SIGALRM will be received somewhere during - # initial processing of SIGUSR1. + # initial processing of SIGUSR2. signal.setitimer(signal.ITIMER_REAL, 1e-6 + random.random() * 1e-5) - os.kill(os.getpid(), signal.SIGUSR1) + os.kill(os.getpid(), signal.SIGUSR2) expected_sigs += 2 # Wait for handlers to run to avoid signal coalescing @@ -1346,8 +1351,8 @@ def handler(signum, frame): # Python handler self.assertEqual(len(sigs), N, "Some signals were lost") - @unittest.skip("TODO: RUSTPYTHON; hang") - @unittest.skipIf(sys.platform == "darwin", "crashes due to system bug (FB13453490)") + @unittest.skip('TODO: RUSTPYTHON; hang') + @unittest.skipIf(is_apple, "crashes due to system bug (FB13453490)") @unittest.skipUnless(hasattr(signal, "SIGUSR1"), "test needs SIGUSR1") @threading_helper.requires_working_threading() @@ -1414,8 +1419,7 @@ def test_sigint(self): with self.assertRaises(KeyboardInterrupt): signal.raise_signal(signal.SIGINT) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.platform != "win32", "Windows specific test") def test_invalid_argument(self): try: @@ -1439,8 +1443,7 @@ def handler(a, b): signal.raise_signal(signal.SIGINT) self.assertTrue(is_ok) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test__thread_interrupt_main(self): # See https://github.com/python/cpython/issues/102397 code = """if 1: diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py index cdbf341b9cb..6235c8e74cf 100644 --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -8,7 +8,6 @@ import select import signal import socket -import tempfile import threading import unittest import socketserver @@ -21,6 +20,8 @@ test.support.requires("network") +test.support.requires_working_socket(module=True) + TEST_STR = b"hello world\n" HOST = socket_helper.HOST @@ -28,14 +29,9 @@ HAVE_UNIX_SOCKETS = hasattr(socket, "AF_UNIX") requires_unix_sockets = unittest.skipUnless(HAVE_UNIX_SOCKETS, 'requires Unix sockets') -HAVE_FORKING = hasattr(os, "fork") +HAVE_FORKING = test.support.has_fork_support requires_forking = unittest.skipUnless(HAVE_FORKING, 'requires forking') -def signal_alarm(n): - """Call signal.alarm when it exists (i.e. not on Windows).""" - if hasattr(signal, 'alarm'): - signal.alarm(n) - # Remember real select() to avoid interferences with mocking _real_select = select.select @@ -46,14 +42,6 @@ def receive(sock, n, timeout=test.support.SHORT_TIMEOUT): else: raise RuntimeError("timed out on %r" % (sock,)) -if HAVE_UNIX_SOCKETS and HAVE_FORKING: - class ForkingUnixStreamServer(socketserver.ForkingMixIn, - socketserver.UnixStreamServer): - pass - - class ForkingUnixDatagramServer(socketserver.ForkingMixIn, - socketserver.UnixDatagramServer): - pass @test.support.requires_fork() # TODO: RUSTPYTHON, os.fork is currently only supported on Unix-based systems @contextlib.contextmanager @@ -75,12 +63,10 @@ class SocketServerTest(unittest.TestCase): """Test all socket servers.""" def setUp(self): - signal_alarm(60) # Kill deadlocks after 60 seconds. self.port_seed = 0 self.test_files = [] def tearDown(self): - signal_alarm(0) # Didn't deadlock. reap_children() for fn in self.test_files: @@ -96,8 +82,7 @@ def pickaddr(self, proto): else: # XXX: We need a way to tell AF_UNIX to pick its own name # like AF_INET provides port==0. - dir = None - fn = tempfile.mktemp(prefix='unix_socket.', dir=dir) + fn = socket_helper.create_unix_domain_name() self.test_files.append(fn) return fn @@ -211,7 +196,7 @@ def test_ThreadingUnixStreamServer(self): @requires_forking def test_ForkingUnixStreamServer(self): with simple_subprocess(self): - self.run_server(ForkingUnixStreamServer, + self.run_server(socketserver.ForkingUnixStreamServer, socketserver.StreamRequestHandler, self.stream_examine) @@ -247,7 +232,7 @@ def test_ThreadingUnixDatagramServer(self): @requires_unix_sockets @requires_forking def test_ForkingUnixDatagramServer(self): - self.run_server(ForkingUnixDatagramServer, + self.run_server(socketserver.ForkingUnixDatagramServer, socketserver.DatagramRequestHandler, self.dgram_examine) From f0526b9e9a01bb81a76bbaeb3de5b3d1be659722 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Sat, 3 Jan 2026 02:29:10 -0500 Subject: [PATCH 675/819] Updated the wsgiref + xmlrpc libraries + associated tests - v3.13.11 (#6634) * Updated the wsgiref library + test * Uncommented fixed wsgi tests * Updated the xmlrpc library + test * Added expectedFailure to test_request_length in test_wsgiref * Annotated doc_xmlrpc test failures/errors --- Lib/test/test_docxmlrpc.py | 7 ++- Lib/test/test_wsgiref.py | 66 +++++++++----------------- Lib/test/test_xmlrpc.py | 10 ---- Lib/wsgiref/__init__.py | 2 + Lib/wsgiref/handlers.py | 38 ++++++++++----- Lib/wsgiref/simple_server.py | 7 +-- Lib/wsgiref/types.py | 54 +++++++++++++++++++++ Lib/wsgiref/util.py | 14 ++---- Lib/wsgiref/validate.py | 15 ++---- Lib/xmlrpc/client.py | 47 ++++--------------- Lib/xmlrpc/server.py | 91 ++++++++++++++++++++---------------- 11 files changed, 179 insertions(+), 172 deletions(-) create mode 100644 Lib/wsgiref/types.py diff --git a/Lib/test/test_docxmlrpc.py b/Lib/test/test_docxmlrpc.py index 99469a58496..3c05412ef57 100644 --- a/Lib/test/test_docxmlrpc.py +++ b/Lib/test/test_docxmlrpc.py @@ -100,8 +100,6 @@ def test_valid_get_response(self): # Server raises an exception if we don't start to read the data response.read() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_get_css(self): self.client.request("GET", "/pydoc.css") response = self.client.getresponse() @@ -121,6 +119,7 @@ def test_invalid_get_response(self): response.read() + @unittest.skip('TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response') def test_lambda(self): """Test that lambda functionality stays the same. The output produced currently is, I suspect invalid because of the unencoded brackets in the @@ -163,6 +162,7 @@ def test_autolinking(self): @make_request_and_skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") + @unittest.skip('TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response') def test_system_methods(self): """Test the presence of three consecutive system.* methods. @@ -190,6 +190,7 @@ def test_system_methods(self): b'<br>\nThis server does NOT support system' b'.methodSignature.</tt></dd></dl>'), response) + @unittest.skip('TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response') def test_autolink_dotted_methods(self): """Test that selfdot values are made strong automatically in the documentation.""" @@ -199,6 +200,7 @@ def test_autolink_dotted_methods(self): self.assertIn(b"""Try self.<strong>add</strong>, too.""", response.read()) + @unittest.skip('TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response') def test_annotations(self): """ Test that annotations works as expected """ self.client.request("GET", "/") @@ -212,6 +214,7 @@ def test_annotations(self): b'method_annotation</strong></a>(x: bytes)</dt></dl>'), response.read()) + @unittest.skip('TODO: RUSTPYTHON; TypeError: HTMLDoc.heading() missing 2 required positional arguments: "fgcol" and "bgcol"') def test_server_title_escape(self): # bpo-38243: Ensure that the server title and documentation # are escaped for HTML. diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py index b89e181f9f8..1a3b4d4b721 100644 --- a/Lib/test/test_wsgiref.py +++ b/Lib/test/test_wsgiref.py @@ -1,6 +1,6 @@ from unittest import mock from test import support -from test.support import warnings_helper +from test.support import socket_helper from test.test_httpservers import NoLogRequestHandler from unittest import TestCase from wsgiref.util import setup_testing_defaults @@ -80,41 +80,26 @@ def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"): return out.getvalue(), err.getvalue() -def compare_generic_iter(make_it,match): - """Utility to compare a generic 2.1/2.2+ iterator with an iterable - If running under Python 2.2+, this tests the iterator using iter()/next(), - as well as __getitem__. 'make_it' must be a function returning a fresh +def compare_generic_iter(make_it, match): + """Utility to compare a generic iterator with an iterable + + This tests the iterator using iter()/next(). + 'make_it' must be a function returning a fresh iterator to be tested (since this may test the iterator twice).""" it = make_it() - n = 0 + if not iter(it) is it: + raise AssertionError for item in match: - if not it[n]==item: raise AssertionError - n+=1 - try: - it[n] - except IndexError: - pass - else: - raise AssertionError("Too many items from __getitem__",it) - + if not next(it) == item: + raise AssertionError try: - iter, StopIteration - except NameError: + next(it) + except StopIteration: pass else: - # Only test iter mode under 2.2+ - it = make_it() - if not iter(it) is it: raise AssertionError - for item in match: - if not next(it) == item: raise AssertionError - try: - next(it) - except StopIteration: - pass - else: - raise AssertionError("Too many items from .__next__()", it) + raise AssertionError("Too many items from .__next__()", it) class IntegrationTests(TestCase): @@ -149,10 +134,11 @@ def test_environ(self): b"Python test,Python test 2;query=test;/path/" ) + @unittest.expectedFailure # TODO: RUSTPYTHON; http library needs to be updated def test_request_length(self): out, err = run_amock(data=b"GET " + (b"x" * 65537) + b" HTTP/1.0\n\n") self.assertEqual(out.splitlines()[0], - b"HTTP/1.0 414 Request-URI Too Long") + b"HTTP/1.0 414 URI Too Long") def test_validated_hello(self): out, err = run_amock(validator(hello_app)) @@ -264,7 +250,7 @@ def app(environ, start_response): class WsgiHandler(NoLogRequestHandler, WSGIRequestHandler): pass - server = make_server(support.HOST, 0, app, handler_class=WsgiHandler) + server = make_server(socket_helper.HOST, 0, app, handler_class=WsgiHandler) self.addCleanup(server.server_close) interrupted = threading.Event() @@ -339,7 +325,6 @@ def checkReqURI(self,uri,query=1,**kw): util.setup_testing_defaults(kw) self.assertEqual(util.request_uri(kw,query),uri) - @warnings_helper.ignore_warnings(category=DeprecationWarning) def checkFW(self,text,size,match): def make_it(text=text,size=size): @@ -358,15 +343,6 @@ def make_it(text=text,size=size): it.close() self.assertTrue(it.filelike.closed) - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_filewrapper_getitem_deprecation(self): - wrapper = util.FileWrapper(StringIO('foobar'), 3) - with self.assertWarnsRegex(DeprecationWarning, - r'Use iterator protocol instead'): - # This should have returned 'bar'. - self.assertEqual(wrapper[1], 'foo') - def testSimpleShifts(self): self.checkShift('','/', '', '/', '') self.checkShift('','/x', 'x', '/x', '') @@ -473,6 +449,10 @@ def testHopByHop(self): for alt in hop, hop.title(), hop.upper(), hop.lower(): self.assertFalse(util.is_hop_by_hop(alt)) + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_filewrapper_getitem_deprecation(self): + return super().test_filewrapper_getitem_deprecation() + class HeaderTests(TestCase): def testMappingInterface(self): @@ -581,7 +561,7 @@ def testEnviron(self): # Test handler.environ as a dict expected = {} setup_testing_defaults(expected) - # Handler inherits os_environ variables which are not overriden + # Handler inherits os_environ variables which are not overridden # by SimpleHandler.add_cgi_vars() (SimpleHandler.base_env) for key, value in os_environ.items(): if key not in expected: @@ -821,8 +801,6 @@ def flush(self): b"Hello, world!", written) - # TODO: RUSTPYTHON - @unittest.expectedFailure def testClientConnectionTerminations(self): environ = {"SERVER_PROTOCOL": "HTTP/1.0"} for exception in ( @@ -841,8 +819,6 @@ def write(self, b): self.assertFalse(stderr.getvalue()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def testDontResetInternalStateOnException(self): class CustomException(ValueError): pass diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py index cf3f535b190..6b2e6c44ca4 100644 --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@ -1042,55 +1042,46 @@ def test_path2(self): self.assertEqual(p.add(6,8), 6+8) self.assertRaises(xmlrpclib.Fault, p.pow, 6, 8) - @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_resource('walltime') def test_path3(self): p = xmlrpclib.ServerProxy(URL+"/is/broken") self.assertRaises(xmlrpclib.Fault, p.add, 6, 8) - @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_resource('walltime') def test_invalid_path(self): p = xmlrpclib.ServerProxy(URL+"/invalid") self.assertRaises(xmlrpclib.Fault, p.add, 6, 8) - @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_resource('walltime') def test_path_query_fragment(self): p = xmlrpclib.ServerProxy(URL+"/foo?k=v#frag") self.assertEqual(p.test(), "/foo?k=v#frag") - @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_resource('walltime') def test_path_fragment(self): p = xmlrpclib.ServerProxy(URL+"/foo#frag") self.assertEqual(p.test(), "/foo#frag") - @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_resource('walltime') def test_path_query(self): p = xmlrpclib.ServerProxy(URL+"/foo?k=v") self.assertEqual(p.test(), "/foo?k=v") - @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_resource('walltime') def test_empty_path(self): p = xmlrpclib.ServerProxy(URL) self.assertEqual(p.test(), "/RPC2") - @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_resource('walltime') def test_root_path(self): p = xmlrpclib.ServerProxy(URL + "/") self.assertEqual(p.test(), "/") - @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_resource('walltime') def test_empty_path_query(self): p = xmlrpclib.ServerProxy(URL + "?k=v") self.assertEqual(p.test(), "?k=v") - @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_resource('walltime') def test_empty_path_fragment(self): p = xmlrpclib.ServerProxy(URL + "#frag") @@ -1142,7 +1133,6 @@ def test_two(self): #test special attribute access on the serverproxy, through the __call__ #function. -@unittest.skip("TODO: RUSTPYTHON, appears to hang") class KeepaliveServerTestCase2(BaseKeepaliveServerTestCase): #ask for two keepalive requests to be handled. request_count=2 diff --git a/Lib/wsgiref/__init__.py b/Lib/wsgiref/__init__.py index 1efbba01a30..59ee48fddec 100644 --- a/Lib/wsgiref/__init__.py +++ b/Lib/wsgiref/__init__.py @@ -13,6 +13,8 @@ * validate -- validation wrapper that sits between an app and a server to detect errors in either +* types -- collection of WSGI-related types for static type checking + To-Do: * cgi_gateway -- Run WSGI apps under CGI (pending a deployment standard) diff --git a/Lib/wsgiref/handlers.py b/Lib/wsgiref/handlers.py index f4300b831a4..cafe872c7aa 100644 --- a/Lib/wsgiref/handlers.py +++ b/Lib/wsgiref/handlers.py @@ -136,6 +136,10 @@ def run(self, application): self.setup_environ() self.result = application(self.environ, self.start_response) self.finish_response() + except (ConnectionAbortedError, BrokenPipeError, ConnectionResetError): + # We expect the client to close the connection abruptly from time + # to time. + return except: try: self.handle_error() @@ -179,7 +183,16 @@ def finish_response(self): for data in self.result: self.write(data) self.finish_content() - finally: + except: + # Call close() on the iterable returned by the WSGI application + # in case of an exception. + if hasattr(self.result, 'close'): + self.result.close() + raise + else: + # We only call close() when no exception is raised, because it + # will set status, result, headers, and environ fields to None. + # See bpo-29183 for more details. self.close() @@ -215,8 +228,7 @@ def start_response(self, status, headers,exc_info=None): if exc_info: try: if self.headers_sent: - # Re-raise original exception if headers sent - raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) + raise finally: exc_info = None # avoid dangling circular ref elif self.headers is not None: @@ -225,18 +237,25 @@ def start_response(self, status, headers,exc_info=None): self.status = status self.headers = self.headers_class(headers) status = self._convert_string_type(status, "Status") - assert len(status)>=4,"Status must be at least 4 characters" - assert status[:3].isdigit(), "Status message must begin w/3-digit code" - assert status[3]==" ", "Status message must have a space after code" + self._validate_status(status) if __debug__: for name, val in headers: name = self._convert_string_type(name, "Header name") val = self._convert_string_type(val, "Header value") - assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed" + assert not is_hop_by_hop(name),\ + f"Hop-by-hop header, '{name}: {val}', not allowed" return self.write + def _validate_status(self, status): + if len(status) < 4: + raise AssertionError("Status must be at least 4 characters") + if not status[:3].isdigit(): + raise AssertionError("Status message must begin w/3-digit code") + if status[3] != " ": + raise AssertionError("Status message must have a space after code") + def _convert_string_type(self, value, title): """Convert/check value type.""" if type(value) is str: @@ -456,10 +475,7 @@ def _write(self,data): from warnings import warn warn("SimpleHandler.stdout.write() should not do partial writes", DeprecationWarning) - while True: - data = data[result:] - if not data: - break + while data := data[result:]: result = self.stdout.write(data) def _flush(self): diff --git a/Lib/wsgiref/simple_server.py b/Lib/wsgiref/simple_server.py index f71563a5ae0..a0f2397fcf0 100644 --- a/Lib/wsgiref/simple_server.py +++ b/Lib/wsgiref/simple_server.py @@ -84,10 +84,6 @@ def get_environ(self): env['PATH_INFO'] = urllib.parse.unquote(path, 'iso-8859-1') env['QUERY_STRING'] = query - - host = self.address_string() - if host != self.client_address[0]: - env['REMOTE_HOST'] = host env['REMOTE_ADDR'] = self.client_address[0] if self.headers.get('content-type') is None: @@ -127,7 +123,8 @@ def handle(self): return handler = ServerHandler( - self.rfile, self.wfile, self.get_stderr(), self.get_environ() + self.rfile, self.wfile, self.get_stderr(), self.get_environ(), + multithread=False, ) handler.request_handler = self # backpointer for logging handler.run(self.server.get_app()) diff --git a/Lib/wsgiref/types.py b/Lib/wsgiref/types.py new file mode 100644 index 00000000000..ef0aead5b28 --- /dev/null +++ b/Lib/wsgiref/types.py @@ -0,0 +1,54 @@ +"""WSGI-related types for static type checking""" + +from collections.abc import Callable, Iterable, Iterator +from types import TracebackType +from typing import Any, Protocol, TypeAlias + +__all__ = [ + "StartResponse", + "WSGIEnvironment", + "WSGIApplication", + "InputStream", + "ErrorStream", + "FileWrapper", +] + +_ExcInfo: TypeAlias = tuple[type[BaseException], BaseException, TracebackType] +_OptExcInfo: TypeAlias = _ExcInfo | tuple[None, None, None] + +class StartResponse(Protocol): + """start_response() callable as defined in PEP 3333""" + def __call__( + self, + status: str, + headers: list[tuple[str, str]], + exc_info: _OptExcInfo | None = ..., + /, + ) -> Callable[[bytes], object]: ... + +WSGIEnvironment: TypeAlias = dict[str, Any] +WSGIApplication: TypeAlias = Callable[[WSGIEnvironment, StartResponse], + Iterable[bytes]] + +class InputStream(Protocol): + """WSGI input stream as defined in PEP 3333""" + def read(self, size: int = ..., /) -> bytes: ... + def readline(self, size: int = ..., /) -> bytes: ... + def readlines(self, hint: int = ..., /) -> list[bytes]: ... + def __iter__(self) -> Iterator[bytes]: ... + +class ErrorStream(Protocol): + """WSGI error stream as defined in PEP 3333""" + def flush(self) -> object: ... + def write(self, s: str, /) -> object: ... + def writelines(self, seq: list[str], /) -> object: ... + +class _Readable(Protocol): + def read(self, size: int = ..., /) -> bytes: ... + # Optional: def close(self) -> object: ... + +class FileWrapper(Protocol): + """WSGI file wrapper as defined in PEP 3333""" + def __call__( + self, file: _Readable, block_size: int = ..., /, + ) -> Iterable[bytes]: ... diff --git a/Lib/wsgiref/util.py b/Lib/wsgiref/util.py index 516fe898d01..63b92331737 100644 --- a/Lib/wsgiref/util.py +++ b/Lib/wsgiref/util.py @@ -4,7 +4,7 @@ __all__ = [ 'FileWrapper', 'guess_scheme', 'application_uri', 'request_uri', - 'shift_path_info', 'setup_testing_defaults', + 'shift_path_info', 'setup_testing_defaults', 'is_hop_by_hop', ] @@ -17,12 +17,6 @@ def __init__(self, filelike, blksize=8192): if hasattr(filelike,'close'): self.close = filelike.close - def __getitem__(self,key): - data = self.filelike.read(self.blksize) - if data: - return data - raise IndexError - def __iter__(self): return self @@ -155,9 +149,9 @@ def setup_testing_defaults(environ): _hoppish = { - 'connection':1, 'keep-alive':1, 'proxy-authenticate':1, - 'proxy-authorization':1, 'te':1, 'trailers':1, 'transfer-encoding':1, - 'upgrade':1 + 'connection', 'keep-alive', 'proxy-authenticate', + 'proxy-authorization', 'te', 'trailers', 'transfer-encoding', + 'upgrade' }.__contains__ def is_hop_by_hop(header_name): diff --git a/Lib/wsgiref/validate.py b/Lib/wsgiref/validate.py index 6107dcd7a4d..1a1853cd63a 100644 --- a/Lib/wsgiref/validate.py +++ b/Lib/wsgiref/validate.py @@ -1,6 +1,6 @@ # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) -# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php -# Also licenced under the Apache License, 2.0: http://opensource.org/licenses/apache2.0.php +# Licensed under the MIT license: https://opensource.org/licenses/mit-license.php +# Also licenced under the Apache License, 2.0: https://opensource.org/licenses/apache2.0.php # Licensed to PSF under a Contributor Agreement """ Middleware to check for obedience to the WSGI specification. @@ -77,7 +77,7 @@ * That wsgi.input is used properly: - - .read() is called with zero or one argument + - .read() is called with exactly one argument - That it returns a string @@ -137,7 +137,7 @@ def validator(application): """ When applied between a WSGI server and a WSGI application, this - middleware will check for WSGI compliancy on a number of levels. + middleware will check for WSGI compliance on a number of levels. This middleware does not modify the request or response in any way, but will raise an AssertionError if anything seems off (except for a failure to close the application iterator, which @@ -214,10 +214,7 @@ def readlines(self, *args): return lines def __iter__(self): - while 1: - line = self.readline() - if not line: - return + while line := self.readline(): yield line def close(self): @@ -390,7 +387,6 @@ def check_headers(headers): assert_(type(headers) is list, "Headers (%r) must be of type list: %r" % (headers, type(headers))) - header_names = {} for item in headers: assert_(type(item) is tuple, "Individual headers (%r) must be of type tuple: %r" @@ -403,7 +399,6 @@ def check_headers(headers): "The Status header cannot be used; it conflicts with CGI " "script, and HTTP status is not given through headers " "(value: %r)." % value) - header_names[name.lower()] = None assert_('\n' not in name and ':' not in name, "Header names may not contain ':' or '\\n': %r" % name) assert_(header_re.search(name), "Bad header name: %r" % name) diff --git a/Lib/xmlrpc/client.py b/Lib/xmlrpc/client.py index a614cef6ab2..f441376d09c 100644 --- a/Lib/xmlrpc/client.py +++ b/Lib/xmlrpc/client.py @@ -245,41 +245,15 @@ def __repr__(self): ## # Backwards compatibility - boolean = Boolean = bool -## -# Wrapper for XML-RPC DateTime values. This converts a time value to -# the format used by XML-RPC. -# <p> -# The value can be given as a datetime object, as a string in the -# format "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by -# time.localtime()), or an integer value (as returned by time.time()). -# The wrapper uses time.localtime() to convert an integer to a time -# tuple. -# -# @param value The time, given as a datetime object, an ISO 8601 string, -# a time tuple, or an integer time value. - -# Issue #13305: different format codes across platforms -_day0 = datetime(1, 1, 1) -def _try(fmt): - try: - return _day0.strftime(fmt) == '0001' - except ValueError: - return False -if _try('%Y'): # Mac OS X - def _iso8601_format(value): - return value.strftime("%Y%m%dT%H:%M:%S") -elif _try('%4Y'): # Linux - def _iso8601_format(value): - return value.strftime("%4Y%m%dT%H:%M:%S") -else: - def _iso8601_format(value): - return value.strftime("%Y%m%dT%H:%M:%S").zfill(17) -del _day0 -del _try +def _iso8601_format(value): + if value.tzinfo is not None: + # XML-RPC only uses the naive portion of the datetime + value = value.replace(tzinfo=None) + # XML-RPC doesn't use '-' separator in the date part + return value.isoformat(timespec='seconds').replace('-', '') def _strftime(value): @@ -850,9 +824,9 @@ def __init__(self, results): def __getitem__(self, i): item = self.results[i] - if type(item) == type({}): + if isinstance(item, dict): raise Fault(item['faultCode'], item['faultString']) - elif type(item) == type([]): + elif isinstance(item, list): return item[0] else: raise ValueError("unexpected type in multicall result") @@ -1339,10 +1313,7 @@ def parse_response(self, response): p, u = self.getparser() - while 1: - data = stream.read(1024) - if not data: - break + while data := stream.read(1024): if self.verbose: print("body:", repr(data)) p.feed(data) diff --git a/Lib/xmlrpc/server.py b/Lib/xmlrpc/server.py index 69a260f5b12..4dddb1d10e0 100644 --- a/Lib/xmlrpc/server.py +++ b/Lib/xmlrpc/server.py @@ -268,17 +268,11 @@ def _marshaled_dispatch(self, data, dispatch_method = None, path = None): except Fault as fault: response = dumps(fault, allow_none=self.allow_none, encoding=self.encoding) - except: - # report exception back to server - exc_type, exc_value, exc_tb = sys.exc_info() - try: - response = dumps( - Fault(1, "%s:%s" % (exc_type, exc_value)), - encoding=self.encoding, allow_none=self.allow_none, - ) - finally: - # Break reference cycle - exc_type = exc_value = exc_tb = None + except BaseException as exc: + response = dumps( + Fault(1, "%s:%s" % (type(exc), exc)), + encoding=self.encoding, allow_none=self.allow_none, + ) return response.encode(self.encoding, 'xmlcharrefreplace') @@ -368,16 +362,11 @@ def system_multicall(self, call_list): {'faultCode' : fault.faultCode, 'faultString' : fault.faultString} ) - except: - exc_type, exc_value, exc_tb = sys.exc_info() - try: - results.append( - {'faultCode' : 1, - 'faultString' : "%s:%s" % (exc_type, exc_value)} - ) - finally: - # Break reference cycle - exc_type = exc_value = exc_tb = None + except BaseException as exc: + results.append( + {'faultCode' : 1, + 'faultString' : "%s:%s" % (type(exc), exc)} + ) return results def _dispatch(self, method, params): @@ -440,7 +429,7 @@ class SimpleXMLRPCRequestHandler(BaseHTTPRequestHandler): # Class attribute listing the accessible path components; # paths not on this list will result in a 404 error. - rpc_paths = ('/', '/RPC2') + rpc_paths = ('/', '/RPC2', '/pydoc.css') #if not None, encode responses larger than this, if possible encode_threshold = 1400 #a common MTU @@ -634,19 +623,14 @@ def _marshaled_dispatch(self, data, dispatch_method = None, path = None): try: response = self.dispatchers[path]._marshaled_dispatch( data, dispatch_method, path) - except: + except BaseException as exc: # report low level exception back to server # (each dispatcher should have handled their own # exceptions) - exc_type, exc_value = sys.exc_info()[:2] - try: - response = dumps( - Fault(1, "%s:%s" % (exc_type, exc_value)), - encoding=self.encoding, allow_none=self.allow_none) - response = response.encode(self.encoding, 'xmlcharrefreplace') - finally: - # Break reference cycle - exc_type = exc_value = None + response = dumps( + Fault(1, "%s:%s" % (type(exc), exc)), + encoding=self.encoding, allow_none=self.allow_none) + response = response.encode(self.encoding, 'xmlcharrefreplace') return response class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher): @@ -736,9 +720,7 @@ def markup(self, text, escape=None, funcs={}, classes={}, methods={}): r'RFC[- ]?(\d+)|' r'PEP[- ]?(\d+)|' r'(self\.)?((?:\w|\.)+))\b') - while 1: - match = pattern.search(text, here) - if not match: break + while match := pattern.search(text, here): start, end = match.span() results.append(escape(text[here:start])) @@ -747,10 +729,10 @@ def markup(self, text, escape=None, funcs={}, classes={}, methods={}): url = escape(all).replace('"', '"') results.append('<a href="%s">%s</a>' % (url, url)) elif rfc: - url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) + url = 'https://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) results.append('<a href="%s">%s</a>' % (url, escape(all))) elif pep: - url = 'https://www.python.org/dev/peps/pep-%04d/' % int(pep) + url = 'https://peps.python.org/pep-%04d/' % int(pep) results.append('<a href="%s">%s</a>' % (url, escape(all))) elif text[end:end+1] == '(': results.append(self.namelink(name, methods, funcs, classes)) @@ -801,7 +783,7 @@ def docserver(self, server_name, package_documentation, methods): server_name = self.escape(server_name) head = '<big><big><strong>%s</strong></big></big>' % server_name - result = self.heading(head, '#ffffff', '#7799ee') + result = self.heading(head) doc = self.markup(package_documentation, self.preformat, fdict) doc = doc and '<tt>%s</tt>' % doc @@ -812,10 +794,25 @@ def docserver(self, server_name, package_documentation, methods): for key, value in method_items: contents.append(self.docroutine(value, key, funcs=fdict)) result = result + self.bigsection( - 'Methods', '#ffffff', '#eeaa77', ''.join(contents)) + 'Methods', 'functions', ''.join(contents)) return result + + def page(self, title, contents): + """Format an HTML page.""" + css_path = "/pydoc.css" + css_link = ( + '<link rel="stylesheet" type="text/css" href="%s">' % + css_path) + return '''\ +<!DOCTYPE> +<html lang="en"> +<head> +<meta charset="utf-8"> +<title>Python: %s +%s%s''' % (title, css_link, contents) + class XMLRPCDocGenerator: """Generates documentation for an XML-RPC server. @@ -907,6 +904,12 @@ class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): for documentation. """ + def _get_css(self, url): + path_here = os.path.dirname(os.path.realpath(__file__)) + css_path = os.path.join(path_here, "..", "pydoc_data", "_pydoc.css") + with open(css_path, mode="rb") as fp: + return fp.read() + def do_GET(self): """Handles the HTTP GET request. @@ -918,9 +921,15 @@ def do_GET(self): self.report_404() return - response = self.server.generate_html_documentation().encode('utf-8') + if self.path.endswith('.css'): + content_type = 'text/css' + response = self._get_css(self.path) + else: + content_type = 'text/html' + response = self.server.generate_html_documentation().encode('utf-8') + self.send_response(200) - self.send_header("Content-type", "text/html") + self.send_header('Content-Type', '%s; charset=UTF-8' % content_type) self.send_header("Content-length", str(len(response))) self.end_headers() self.wfile.write(response) From eee360822c67ca75795894676f3281165a5ec489 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 4 Jan 2026 09:15:38 +0900 Subject: [PATCH 676/819] Rework compiler about exception handling (#6638) * asyncgen * fix coroutine * rewrite compiler * tests * set pyc magic number --- .cspell.dict/cpython.txt | 1 + Lib/_opcode_metadata.py | 257 +-- Lib/importlib/_bootstrap_external.py | 2 +- Lib/test/test__opcode.py | 3 +- Lib/test/test_asyncgen.py | 4 - Lib/test/test_compile.py | 2 - Lib/test/test_contextlib.py | 2 - Lib/test/test_contextlib_async.py | 5 + Lib/test/test_dis.py | 6 +- Lib/test/test_exceptions.py | 3 - Lib/test/test_generators.py | 2 - Lib/test/test_importlib/test_util.py | 2 +- Lib/test/test_patma.py | 1 - Lib/test/test_traceback.py | 2 - crates/codegen/src/compile.rs | 1996 +++++++++++++---- crates/codegen/src/error.rs | 11 + crates/codegen/src/ir.rs | 192 +- ...pile__tests__nested_double_async_with.snap | 203 +- ...pile__tests__nested_double_async_with.snap | 87 - crates/codegen/src/symboltable.rs | 64 +- crates/compiler-core/src/bytecode.rs | 817 +++++-- crates/compiler-core/src/lib.rs | 1 + crates/compiler-core/src/varint.rs | 138 ++ crates/jit/src/instructions.rs | 41 +- crates/stdlib/src/opcode.rs | 147 +- crates/vm/src/builtins/asyncgenerator.rs | 23 +- crates/vm/src/builtins/code.rs | 21 +- crates/vm/src/coroutine.rs | 29 +- crates/vm/src/frame.rs | 811 ++++--- crates/vm/src/version.rs | 2 +- crates/vm/src/vm/mod.rs | 22 +- scripts/generate_opcode_metadata.py | 246 ++ 32 files changed, 3732 insertions(+), 1411 deletions(-) delete mode 100644 crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__nested_double_async_with.snap create mode 100644 crates/compiler-core/src/varint.rs create mode 100644 scripts/generate_opcode_metadata.py diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 0e4e17e2872..2ac19a4fca0 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -14,6 +14,7 @@ cafile cellarg cellvar cellvars +CLASSDEREF cmpop denom DICTFLAG diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index b3d7b8103e8..0d6b7f1109e 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -1,198 +1,16 @@ -# This file is generated by Tools/cases_generator/py_metadata_generator.py -# from: -# Python/bytecodes.c +# This file is generated by scripts/generate_opcode_metadata.py +# for RustPython bytecode format (CPython 3.13 compatible opcode numbers). # Do not edit! -_specializations = { - "RESUME": [ - "RESUME_CHECK", - ], - "TO_BOOL": [ - "TO_BOOL_ALWAYS_TRUE", - "TO_BOOL_BOOL", - "TO_BOOL_INT", - "TO_BOOL_LIST", - "TO_BOOL_NONE", - "TO_BOOL_STR", - ], - "BINARY_OP": [ - "BINARY_OP_MULTIPLY_INT", - "BINARY_OP_ADD_INT", - "BINARY_OP_SUBTRACT_INT", - "BINARY_OP_MULTIPLY_FLOAT", - "BINARY_OP_ADD_FLOAT", - "BINARY_OP_SUBTRACT_FLOAT", - "BINARY_OP_ADD_UNICODE", - "BINARY_OP_INPLACE_ADD_UNICODE", - ], - "BINARY_SUBSCR": [ - "BINARY_SUBSCR_DICT", - "BINARY_SUBSCR_GETITEM", - "BINARY_SUBSCR_LIST_INT", - "BINARY_SUBSCR_STR_INT", - "BINARY_SUBSCR_TUPLE_INT", - ], - "STORE_SUBSCR": [ - "STORE_SUBSCR_DICT", - "STORE_SUBSCR_LIST_INT", - ], - "SEND": [ - "SEND_GEN", - ], - "UNPACK_SEQUENCE": [ - "UNPACK_SEQUENCE_TWO_TUPLE", - "UNPACK_SEQUENCE_TUPLE", - "UNPACK_SEQUENCE_LIST", - ], - "STORE_ATTR": [ - "STORE_ATTR_INSTANCE_VALUE", - "STORE_ATTR_SLOT", - "STORE_ATTR_WITH_HINT", - ], - "LOAD_GLOBAL": [ - "LOAD_GLOBAL_MODULE", - "LOAD_GLOBAL_BUILTIN", - ], - "LOAD_SUPER_ATTR": [ - "LOAD_SUPER_ATTR_ATTR", - "LOAD_SUPER_ATTR_METHOD", - ], - "LOAD_ATTR": [ - "LOAD_ATTR_INSTANCE_VALUE", - "LOAD_ATTR_MODULE", - "LOAD_ATTR_WITH_HINT", - "LOAD_ATTR_SLOT", - "LOAD_ATTR_CLASS", - "LOAD_ATTR_PROPERTY", - "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", - "LOAD_ATTR_METHOD_WITH_VALUES", - "LOAD_ATTR_METHOD_NO_DICT", - "LOAD_ATTR_METHOD_LAZY_DICT", - "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", - "LOAD_ATTR_NONDESCRIPTOR_NO_DICT", - ], - "COMPARE_OP": [ - "COMPARE_OP_FLOAT", - "COMPARE_OP_INT", - "COMPARE_OP_STR", - ], - "CONTAINS_OP": [ - "CONTAINS_OP_SET", - "CONTAINS_OP_DICT", - ], - "FOR_ITER": [ - "FOR_ITER_LIST", - "FOR_ITER_TUPLE", - "FOR_ITER_RANGE", - "FOR_ITER_GEN", - ], - "CALL": [ - "CALL_BOUND_METHOD_EXACT_ARGS", - "CALL_PY_EXACT_ARGS", - "CALL_TYPE_1", - "CALL_STR_1", - "CALL_TUPLE_1", - "CALL_BUILTIN_CLASS", - "CALL_BUILTIN_O", - "CALL_BUILTIN_FAST", - "CALL_BUILTIN_FAST_WITH_KEYWORDS", - "CALL_LEN", - "CALL_ISINSTANCE", - "CALL_LIST_APPEND", - "CALL_METHOD_DESCRIPTOR_O", - "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", - "CALL_METHOD_DESCRIPTOR_NOARGS", - "CALL_METHOD_DESCRIPTOR_FAST", - "CALL_ALLOC_AND_ENTER_INIT", - "CALL_PY_GENERAL", - "CALL_BOUND_METHOD_GENERAL", - "CALL_NON_PY_GENERAL", - ], -} -_specialized_opmap = { - 'BINARY_OP_ADD_FLOAT': 150, - 'BINARY_OP_ADD_INT': 151, - 'BINARY_OP_ADD_UNICODE': 152, - 'BINARY_OP_INPLACE_ADD_UNICODE': 3, - 'BINARY_OP_MULTIPLY_FLOAT': 153, - 'BINARY_OP_MULTIPLY_INT': 154, - 'BINARY_OP_SUBTRACT_FLOAT': 155, - 'BINARY_OP_SUBTRACT_INT': 156, - 'BINARY_SUBSCR_DICT': 157, - 'BINARY_SUBSCR_GETITEM': 158, - 'BINARY_SUBSCR_LIST_INT': 159, - 'BINARY_SUBSCR_STR_INT': 160, - 'BINARY_SUBSCR_TUPLE_INT': 161, - 'CALL_ALLOC_AND_ENTER_INIT': 162, - 'CALL_BOUND_METHOD_EXACT_ARGS': 163, - 'CALL_BOUND_METHOD_GENERAL': 164, - 'CALL_BUILTIN_CLASS': 165, - 'CALL_BUILTIN_FAST': 166, - 'CALL_BUILTIN_FAST_WITH_KEYWORDS': 167, - 'CALL_BUILTIN_O': 168, - 'CALL_ISINSTANCE': 169, - 'CALL_LEN': 170, - 'CALL_LIST_APPEND': 171, - 'CALL_METHOD_DESCRIPTOR_FAST': 172, - 'CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS': 173, - 'CALL_METHOD_DESCRIPTOR_NOARGS': 174, - 'CALL_METHOD_DESCRIPTOR_O': 175, - 'CALL_NON_PY_GENERAL': 176, - 'CALL_PY_EXACT_ARGS': 177, - 'CALL_PY_GENERAL': 178, - 'CALL_STR_1': 179, - 'CALL_TUPLE_1': 180, - 'CALL_TYPE_1': 181, - 'COMPARE_OP_FLOAT': 182, - 'COMPARE_OP_INT': 183, - 'COMPARE_OP_STR': 184, - 'CONTAINS_OP_DICT': 185, - 'CONTAINS_OP_SET': 186, - 'FOR_ITER_GEN': 187, - 'FOR_ITER_LIST': 188, - 'FOR_ITER_RANGE': 189, - 'FOR_ITER_TUPLE': 190, - 'LOAD_ATTR_CLASS': 191, - 'LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN': 192, - 'LOAD_ATTR_INSTANCE_VALUE': 193, - 'LOAD_ATTR_METHOD_LAZY_DICT': 194, - 'LOAD_ATTR_METHOD_NO_DICT': 195, - 'LOAD_ATTR_METHOD_WITH_VALUES': 196, - 'LOAD_ATTR_MODULE': 197, - 'LOAD_ATTR_NONDESCRIPTOR_NO_DICT': 198, - 'LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES': 199, - 'LOAD_ATTR_PROPERTY': 200, - 'LOAD_ATTR_SLOT': 201, - 'LOAD_ATTR_WITH_HINT': 202, - 'LOAD_GLOBAL_BUILTIN': 203, - 'LOAD_GLOBAL_MODULE': 204, - 'LOAD_SUPER_ATTR_ATTR': 205, - 'LOAD_SUPER_ATTR_METHOD': 206, - 'RESUME_CHECK': 207, - 'SEND_GEN': 208, - 'STORE_ATTR_INSTANCE_VALUE': 209, - 'STORE_ATTR_SLOT': 210, - 'STORE_ATTR_WITH_HINT': 211, - 'STORE_SUBSCR_DICT': 212, - 'STORE_SUBSCR_LIST_INT': 213, - 'TO_BOOL_ALWAYS_TRUE': 214, - 'TO_BOOL_BOOL': 215, - 'TO_BOOL_INT': 216, - 'TO_BOOL_LIST': 217, - 'TO_BOOL_NONE': 218, - 'TO_BOOL_STR': 219, - 'UNPACK_SEQUENCE_LIST': 220, - 'UNPACK_SEQUENCE_TUPLE': 221, - 'UNPACK_SEQUENCE_TWO_TUPLE': 222, -} +_specializations = {} + +_specialized_opmap = {} opmap = { 'CACHE': 0, - 'RESERVED': 17, - 'RESUME': 149, - 'INSTRUMENTED_LINE': 254, 'BEFORE_ASYNC_WITH': 1, 'BEFORE_WITH': 2, + 'RESERVED_3': 3, 'BINARY_SLICE': 4, 'BINARY_SUBSCR': 5, 'CHECK_EG_MATCH': 6, @@ -206,6 +24,7 @@ 'FORMAT_SIMPLE': 14, 'FORMAT_WITH_SPEC': 15, 'GET_AITER': 16, + 'RESERVED_17': 17, 'GET_ANEXT': 18, 'GET_ITER': 19, 'GET_LEN': 20, @@ -307,37 +126,39 @@ 'UNPACK_EX': 116, 'UNPACK_SEQUENCE': 117, 'YIELD_VALUE': 118, - 'INSTRUMENTED_RESUME': 236, - 'INSTRUMENTED_END_FOR': 237, - 'INSTRUMENTED_END_SEND': 238, - 'INSTRUMENTED_RETURN_VALUE': 239, - 'INSTRUMENTED_RETURN_CONST': 240, - 'INSTRUMENTED_YIELD_VALUE': 241, - 'INSTRUMENTED_LOAD_SUPER_ATTR': 242, - 'INSTRUMENTED_FOR_ITER': 243, - 'INSTRUMENTED_CALL': 244, - 'INSTRUMENTED_CALL_KW': 245, - 'INSTRUMENTED_CALL_FUNCTION_EX': 246, - 'INSTRUMENTED_INSTRUCTION': 247, - 'INSTRUMENTED_JUMP_FORWARD': 248, - 'INSTRUMENTED_JUMP_BACKWARD': 249, - 'INSTRUMENTED_POP_JUMP_IF_TRUE': 250, - 'INSTRUMENTED_POP_JUMP_IF_FALSE': 251, - 'INSTRUMENTED_POP_JUMP_IF_NONE': 252, - 'INSTRUMENTED_POP_JUMP_IF_NOT_NONE': 253, - 'JUMP': 256, - 'JUMP_NO_INTERRUPT': 257, - 'LOAD_CLOSURE': 258, - 'LOAD_METHOD': 259, - 'LOAD_SUPER_METHOD': 260, - 'LOAD_ZERO_SUPER_ATTR': 261, - 'LOAD_ZERO_SUPER_METHOD': 262, - 'POP_BLOCK': 263, - 'SETUP_CLEANUP': 264, - 'SETUP_FINALLY': 265, - 'SETUP_WITH': 266, - 'STORE_FAST_MAYBE_NULL': 267, + 'BREAK': 119, + 'BUILD_LIST_UNPACK': 120, + 'BUILD_MAP_FOR_CALL': 121, + 'BUILD_SET_UNPACK': 122, + 'BUILD_TUPLE_ITER': 123, + 'BUILD_TUPLE_UNPACK': 124, + 'CALL_METHOD': 125, + 'CALL_METHOD_KW': 126, + 'CALL_METHOD_EX': 127, + 'CONTINUE': 128, + 'JUMP': 129, + 'JUMP_IF_FALSE_OR_POP': 130, + 'JUMP_IF_TRUE_OR_POP': 131, + 'JUMP_IF_NOT_EXC_MATCH': 132, + 'LOAD_CLASSDEREF': 133, + 'LOAD_CLOSURE': 134, + 'LOAD_METHOD': 135, + 'POP_BLOCK': 136, + 'REVERSE': 137, + 'SET_EXC_INFO': 138, + 'SUBSCRIPT': 139, + 'UNARY_OP': 140, + 'RESERVED_141': 141, + 'RESERVED_142': 142, + 'RESERVED_143': 143, + 'RESERVED_144': 144, + 'RESERVED_145': 145, + 'RESERVED_146': 146, + 'RESERVED_147': 147, + 'RESERVED_148': 148, + 'RESUME': 149, } +# CPython 3.13 compatible: opcodes < 44 have no argument HAVE_ARGUMENT = 44 MIN_INSTRUMENTED_OPCODE = 236 diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index 41f538acb03..ca73eb2fcf7 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -489,7 +489,7 @@ def _write_atomic(path, data, mode=0o666): # Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array # in PC/launcher.c must also be updated. -MAGIC_NUMBER = (3571).to_bytes(2, 'little') + b'\r\n' +MAGIC_NUMBER = (2997).to_bytes(2, 'little') + b'\r\n' _RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py index dd4f30ab17d..60dcdc6cd70 100644 --- a/Lib/test/test__opcode.py +++ b/Lib/test/test__opcode.py @@ -27,6 +27,7 @@ def test_invalid_opcodes(self): self.check_bool_function_result(_opcode.has_local, invalid, False) self.check_bool_function_result(_opcode.has_exc, invalid, False) + @unittest.expectedFailure # TODO: RUSTPYTHON - no instrumented opcodes def test_is_valid(self): names = [ 'CACHE', @@ -56,7 +57,6 @@ def check_function(self, func, expected): class StackEffectTests(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_stack_effect(self): self.assertEqual(stack_effect(dis.opmap['POP_TOP']), -1) self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 0), -1) @@ -111,6 +111,7 @@ def test_stack_effect_jump(self): class SpecializationStatsTests(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON - no specialization stats def test_specialization_stats(self): stat_names = ["success", "failure", "hit", "deferred", "miss", "deopt"] specialized_opcodes = [ diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index 7039bd7054c..45d220e3e02 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -1026,8 +1026,6 @@ async def run(): fut.cancel() self.loop.run_until_complete(asyncio.sleep(0.01)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_async_gen_asyncio_gc_aclose_09(self): DONE = 0 @@ -1514,8 +1512,6 @@ async def main(): self.assertIn('an error occurred during closing of asynchronous generator', message['message']) - # TODO: RUSTPYTHON, ValueError: not enough values to unpack (expected 1, got 0) - @unittest.expectedFailure def test_async_gen_asyncio_shutdown_exception_02(self): messages = [] diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 3ea09354c3c..9f6ad6879c0 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1039,8 +1039,6 @@ def test_big_dict_literal(self): the_dict = "{" + ",".join(f"{x}:{x}" for x in range(dict_size)) + "}" self.assertEqual(len(eval(the_dict)), dict_size) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_redundant_jump_in_if_else_break(self): # Check if bytecode containing jumps that simply point to the next line # is generated around if-else-break style structures. See bpo-42615. diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index 9bb3fd0179b..420cc2510a8 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -844,8 +844,6 @@ def test_exit_suppress(self): stack.push(lambda *exc: True) 1/0 - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exit_exception_traceback(self): # This test captures the current behavior of ExitStack so that we know # if we ever unintendedly change it. It is not a statement of what the diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index d7331c4d433..0cf32abb76f 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -535,6 +535,11 @@ def __exit__(self, *exc_details): ('__aexit__', 'cb_suppress = cb(*exc_details)'), ] + # TODO: RUSTPYTHON - no _asyncio module, pure Python Task adds extra frame + @unittest.expectedFailure + def test_exit_exception_traceback(self): + super().test_exit_exception_traceback() + async def test_async_callback(self): expected = [ ((), {}), diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index dab8d73e54c..eddfc155619 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -1974,6 +1974,7 @@ def test_baseopname_and_baseopcode(self): self.assertIn(name, opcode._specializations[baseopname]) self.assertEqual(opcode.opmap[baseopname], baseopcode) + @unittest.expectedFailure # TODO: RUSTPYTHON - JUMP_BACKWARD/JUMP_FORWARD are placeholders def test_jump_target(self): # Non-jump instructions should return None instruction = Instruction(opname="NOP", opcode=dis.opmap["NOP"], arg=None, argval=None, @@ -1999,6 +2000,7 @@ def test_jump_target(self): positions=None) self.assertEqual(10 + 2 + 1*2 + 100*2, instruction.jump_target) + @unittest.expectedFailure # TODO: RUSTPYTHON - JUMP_BACKWARD is a placeholder def test_argval_argrepr(self): def f(opcode, oparg, offset, *init_args): arg_resolver = dis.ArgResolver(*init_args) @@ -2019,6 +2021,7 @@ def f(opcode, oparg, offset, *init_args): self.assertEqual(f(opcode.opmap["BINARY_OP"], 3, *args), (3, '<<')) self.assertEqual(f(opcode.opmap["CALL_INTRINSIC_1"], 2, *args), (2, 'INTRINSIC_IMPORT_STAR')) + @unittest.expectedFailure # TODO: RUSTPYTHON - JUMP_BACKWARD is a placeholder def test_custom_arg_resolver(self): class MyArgResolver(dis.ArgResolver): def offset_from_jump_arg(self, op, arg, offset): @@ -2090,7 +2093,6 @@ def last_item(iterable): self.assertEqual(14, instructions[6].offset) self.assertEqual(8, instructions[6].start_offset) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_cache_offset_and_end_offset(self): code = bytes([ opcode.opmap["LOAD_GLOBAL"], 0x01, @@ -2189,7 +2191,6 @@ def test_bytecode_co_positions(self): assert instr.positions == positions class TestBytecodeTestCase(BytecodeTestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_assert_not_in_with_op_not_in_bytecode(self): code = compile("a = 1", "", "exec") self.assertInBytecode(code, "LOAD_CONST", 1) @@ -2210,7 +2211,6 @@ def test_assert_not_in_with_arg_in_bytecode(self): self.assertNotInBytecode(code, "LOAD_CONST", 1) class TestFinderMethods(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test__find_imports(self): cases = [ ("import a.b.c", ('a.b.c', 0, None)), diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 3db9203602e..ceb94df324e 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1152,7 +1152,6 @@ class C(Exception): self.assertIs(c.__context__, b) self.assertIsNone(b.__context__) - @unittest.skip("TODO: RUSTPYTHON; Infinite loop") def test_no_hang_on_context_chain_cycle1(self): # See issue 25782. Cycle in context chain. @@ -1208,7 +1207,6 @@ class C(Exception): self.assertIs(b.__context__, a) self.assertIs(a.__context__, c) - @unittest.skip("TODO: RUSTPYTHON; Infinite loop") def test_no_hang_on_context_chain_cycle3(self): # See issue 25782. Longer context chain with cycle. @@ -2560,7 +2558,6 @@ def in_finally_except(): pass self.lineno_after_raise(in_finally_except, 4) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_lineno_after_with(self): class Noop: def __enter__(self): diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index 8833a102b36..853767135aa 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -725,7 +725,6 @@ def g(): class GeneratorThrowTest(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_exception_context_with_yield(self): def f(): try: @@ -762,7 +761,6 @@ def f(): # This ensures that the assertions inside were executed. self.assertEqual(actual, 'b') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_exception_context_with_yield_from(self): def f(): yield diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index 87b7de9889b..bc47159b235 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -636,7 +636,7 @@ def test_magic_number(self): # stakeholders such as OS package maintainers must be notified # in advance. Such exceptional releases will then require an # adjustment to this test case. - EXPECTED_MAGIC_NUMBER = 3531 + EXPECTED_MAGIC_NUMBER = 2997 actual = int.from_bytes(importlib.util.MAGIC_NUMBER[:2], 'little') msg = ( diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py index 847ca001e43..6ca1fa0ba40 100644 --- a/Lib/test/test_patma.py +++ b/Lib/test/test_patma.py @@ -3394,7 +3394,6 @@ class Keys: self.assertIs(z, None) class TestSourceLocations(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_jump_threading(self): # See gh-123048 def f(): diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index f2ec8344b2f..2ba7fbda5c3 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -3749,8 +3749,6 @@ def test_comparison_basic(self): self.assertNotEqual(exc, object()) self.assertEqual(exc, ALWAYS_EQ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_comparison_params_variations(self): def raise_exc(): try: diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 5cc7d0d2212..2fb22f46a85 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -64,12 +64,28 @@ pub enum FBlockType { StopIteration, } +/// Stores additional data for fblock unwinding +// fb_datum +#[derive(Debug, Clone)] +pub enum FBlockDatum { + None, + /// For FinallyTry: stores the finally body statements to compile during unwind + FinallyBody(Vec), + /// For HandlerCleanup: stores the exception variable name (e.g., "e" in "except X as e") + ExceptionName(String), +} + #[derive(Debug, Clone)] pub struct FBlockInfo { pub fb_type: FBlockType, pub fb_block: BlockIdx, pub fb_exit: BlockIdx, - // fb_datum is not needed in RustPython + // For Python 3.11+ exception table generation + pub fb_handler: Option, // Exception handler block + pub fb_stack_depth: u32, // Stack depth at block entry + pub fb_preserve_lasti: bool, // Whether to preserve lasti (for SETUP_CLEANUP) + // additional data for fblock unwinding + pub fb_datum: FBlockDatum, } pub(crate) type InternalResult = Result; @@ -138,6 +154,8 @@ struct CompileContext { loop_data: Option<(BlockIdx, BlockIdx)>, in_class: bool, func: FunctionContext, + /// True if we're anywhere inside an async function (even inside nested comprehensions) + in_async_scope: bool, } #[derive(Debug, Clone, Copy, PartialEq)] @@ -401,6 +419,7 @@ impl Compiler { loop_data: None, in_class: false, func: FunctionContext::NoFunction, + in_async_scope: false, }, opts, in_annotation: false, @@ -453,7 +472,7 @@ impl Compiler { // 3. Handle two-element slice specially // 4. Otherwise VISIT slice and emit appropriate instruction - // For Load context, CPython does some checks (we skip for now) + // For Load context, some checks are skipped for now // if ctx == ExprContext::Load { // check_subscripter(value); // check_index(value, slice); @@ -470,12 +489,10 @@ impl Compiler { }; match ctx { ExprContext::Load => { - // CPython uses BINARY_SLICE emit!(self, Instruction::BuildSlice { argc }); emit!(self, Instruction::Subscript); } ExprContext::Store => { - // CPython uses STORE_SLICE emit!(self, Instruction::BuildSlice { argc }); emit!(self, Instruction::StoreSubscript); } @@ -646,6 +663,14 @@ impl Compiler { self.symbol_table_stack.pop().expect("compiler bug") } + /// Check if this is an inlined comprehension context (PEP 709) + /// Currently disabled - always returns false to avoid stack issues + fn is_inlined_comprehension_context(&self, _comprehension_type: ComprehensionType) -> bool { + // TODO: Implement PEP 709 inlined comprehensions properly + // For now, disabled to avoid stack underflow issues + false + } + /// Enter a new scope // = compiler_enter_scope fn enter_scope( @@ -880,6 +905,50 @@ impl Compiler { fb_type: FBlockType, fb_block: BlockIdx, fb_exit: BlockIdx, + ) -> CompileResult<()> { + self.push_fblock_full( + fb_type, + fb_block, + fb_exit, + None, + 0, + false, + FBlockDatum::None, + ) + } + + /// Push an fblock with exception handler info + fn push_fblock_with_handler( + &mut self, + fb_type: FBlockType, + fb_block: BlockIdx, + fb_exit: BlockIdx, + fb_handler: Option, + fb_stack_depth: u32, + fb_preserve_lasti: bool, + ) -> CompileResult<()> { + self.push_fblock_full( + fb_type, + fb_block, + fb_exit, + fb_handler, + fb_stack_depth, + fb_preserve_lasti, + FBlockDatum::None, + ) + } + + /// Push an fblock with all parameters including fb_datum + #[allow(clippy::too_many_arguments)] + fn push_fblock_full( + &mut self, + fb_type: FBlockType, + fb_block: BlockIdx, + fb_exit: BlockIdx, + fb_handler: Option, + fb_stack_depth: u32, + fb_preserve_lasti: bool, + fb_datum: FBlockDatum, ) -> CompileResult<()> { let code = self.current_code_info(); if code.fblock.len() >= MAXBLOCKS { @@ -891,6 +960,10 @@ impl Compiler { fb_type, fb_block, fb_exit, + fb_handler, + fb_stack_depth, + fb_preserve_lasti, + fb_datum, }); Ok(()) } @@ -904,6 +977,233 @@ impl Compiler { code.fblock.pop().expect("fblock stack underflow") } + /// Unwind a single fblock, emitting cleanup code + /// preserve_tos: if true, preserve the top of stack (e.g., return value) + fn unwind_fblock(&mut self, info: &FBlockInfo, preserve_tos: bool) -> CompileResult<()> { + match info.fb_type { + FBlockType::WhileLoop + | FBlockType::ExceptionHandler + | FBlockType::ExceptionGroupHandler + | FBlockType::AsyncComprehensionGenerator + | FBlockType::StopIteration => { + // No cleanup needed + } + + FBlockType::ForLoop => { + // Pop the iterator + if preserve_tos { + emit!(self, Instruction::Swap { index: 2 }); + } + emit!(self, Instruction::PopTop); + } + + FBlockType::TryExcept => { + // No POP_BLOCK with exception table, just pop fblock + } + + FBlockType::FinallyTry => { + // FinallyTry is now handled specially in unwind_fblock_stack + // to avoid infinite recursion when the finally body contains return/break/continue. + // This branch should not be reached. + unreachable!("FinallyTry should be handled by unwind_fblock_stack"); + } + + FBlockType::FinallyEnd => { + // Stack when in FinallyEnd: [..., prev_exc, exc] or + // [..., prev_exc, exc, return_value] if preserve_tos + // Note: No lasti here - it's only pushed for cleanup handler exceptions + // We need to pop: exc, prev_exc (via PopException) + if preserve_tos { + emit!(self, Instruction::Swap { index: 2 }); + } + emit!(self, Instruction::PopTop); // exc + if preserve_tos { + emit!(self, Instruction::Swap { index: 2 }); + } + emit!(self, Instruction::PopException); // prev_exc is restored + } + + FBlockType::With | FBlockType::AsyncWith => { + // Stack when entering: [..., __exit__, return_value (if preserve_tos)] + // Need to call __exit__(None, None, None) + + emit!(self, Instruction::PopBlock); + + // If preserving return value, swap it below __exit__ + if preserve_tos { + emit!(self, Instruction::Swap { index: 2 }); + } + + // Call __exit__(None, None, None) - compiler_call_exit_with_nones + // Stack: [..., __exit__] or [..., return_value, __exit__] + self.emit_load_const(ConstantData::None); + self.emit_load_const(ConstantData::None); + self.emit_load_const(ConstantData::None); + emit!(self, Instruction::CallFunctionPositional { nargs: 3 }); + + // For async with, await the result + if matches!(info.fb_type, FBlockType::AsyncWith) { + emit!(self, Instruction::GetAwaitable); + self.emit_load_const(ConstantData::None); + self.compile_yield_from_sequence(true)?; + } + + // Pop the __exit__ result + emit!(self, Instruction::PopTop); + } + + FBlockType::HandlerCleanup => { + if preserve_tos { + emit!(self, Instruction::Swap { index: 2 }); + } + emit!(self, Instruction::PopException); + + // If there's an exception name, clean it up + if let FBlockDatum::ExceptionName(ref name) = info.fb_datum { + self.emit_load_const(ConstantData::None); + self.store_name(name)?; + self.compile_name(name, NameUsage::Delete)?; + } + } + + FBlockType::PopValue => { + if preserve_tos { + emit!(self, Instruction::Swap { index: 2 }); + } + emit!(self, Instruction::PopTop); + } + } + Ok(()) + } + + /// Unwind the fblock stack, emitting cleanup code for each block + /// preserve_tos: if true, preserve the top of stack (e.g., return value) + /// stop_at_loop: if true, stop when encountering a loop (for break/continue) + fn unwind_fblock_stack(&mut self, preserve_tos: bool, stop_at_loop: bool) -> CompileResult<()> { + // Collect the info we need, with indices for FinallyTry blocks + #[derive(Clone)] + enum UnwindInfo { + Normal(FBlockInfo), + FinallyTry { + body: Vec, + fblock_idx: usize, + }, + } + let mut unwind_infos = Vec::new(); + + { + let code = self.current_code_info(); + for i in (0..code.fblock.len()).rev() { + // Check for exception group handler (forbidden) + if matches!(code.fblock[i].fb_type, FBlockType::ExceptionGroupHandler) { + return Err(self.error(CodegenErrorType::BreakContinueReturnInExceptStar)); + } + + // Stop at loop if requested + if stop_at_loop + && matches!( + code.fblock[i].fb_type, + FBlockType::WhileLoop | FBlockType::ForLoop + ) + { + break; + } + + if matches!(code.fblock[i].fb_type, FBlockType::FinallyTry) { + if let FBlockDatum::FinallyBody(ref body) = code.fblock[i].fb_datum { + unwind_infos.push(UnwindInfo::FinallyTry { + body: body.clone(), + fblock_idx: i, + }); + } + } else { + unwind_infos.push(UnwindInfo::Normal(code.fblock[i].clone())); + } + } + } + + // Process each fblock + for info in unwind_infos { + match info { + UnwindInfo::Normal(fblock_info) => { + self.unwind_fblock(&fblock_info, preserve_tos)?; + } + UnwindInfo::FinallyTry { body, fblock_idx } => { + // Temporarily remove the FinallyTry fblock so nested return/break/continue + // in the finally body won't see it again + let code = self.current_code_info(); + let saved_fblock = code.fblock.remove(fblock_idx); + + // Push PopValue fblock if preserving tos + // IMPORTANT: When preserving TOS (return value), we need to update the + // exception handler's stack_depth to account for the return value on stack. + // Otherwise, if an exception occurs during the finally body, the stack + // will be unwound to the wrong depth and the return value will be lost. + if preserve_tos { + // Get the handler info from the saved fblock (or current handler) + // and create a new handler with stack_depth + 1 + let (handler, stack_depth, preserve_lasti) = + if let Some(handler) = saved_fblock.fb_handler { + ( + Some(handler), + saved_fblock.fb_stack_depth + 1, // +1 for return value + saved_fblock.fb_preserve_lasti, + ) + } else { + // No handler in saved_fblock, check current handler + if let Some(current_handler) = self.current_except_handler() { + ( + Some(current_handler.handler_block), + current_handler.stack_depth + 1, // +1 for return value + current_handler.preserve_lasti, + ) + } else { + (None, 1, false) // No handler, but still track the return value + } + }; + + self.push_fblock_with_handler( + FBlockType::PopValue, + saved_fblock.fb_block, + saved_fblock.fb_block, + handler, + stack_depth, + preserve_lasti, + )?; + } + + self.compile_statements(&body)?; + + if preserve_tos { + self.pop_fblock(FBlockType::PopValue); + } + + // Restore the fblock + let code = self.current_code_info(); + code.fblock.insert(fblock_idx, saved_fblock); + } + } + } + + Ok(()) + } + + /// Get the current exception handler from fblock stack + fn current_except_handler(&self) -> Option { + let code = self.code_stack.last()?; + // Walk fblock stack from top to find the nearest exception handler + for fblock in code.fblock.iter().rev() { + if let Some(handler) = fblock.fb_handler { + return Some(ir::ExceptHandlerInfo { + handler_block: handler, + stack_depth: fblock.fb_stack_depth, + preserve_lasti: fblock.fb_preserve_lasti, + }); + } + } + None + } + // could take impl Into>, but everything is borrowed from ast structs; we never // actually have a `String` to pass fn name(&mut self, name: &str) -> bytecode::NameIdx { @@ -1511,7 +1811,7 @@ impl Compiler { None => bytecode::RaiseKind::Raise, } } - None => bytecode::RaiseKind::Reraise, + None => bytecode::RaiseKind::BareRaise, }; self.set_source_range(*range); emit!(self, Instruction::Raise { kind }); @@ -1594,82 +1894,12 @@ impl Compiler { } } Stmt::Break(_) => { - // Find the innermost loop in fblock stack - // Error if we encounter ExceptionGroupHandler before finding a loop - let found_loop = { - let code = self.current_code_info(); - let mut result = Ok(None); - for i in (0..code.fblock.len()).rev() { - match code.fblock[i].fb_type { - FBlockType::WhileLoop | FBlockType::ForLoop => { - result = Ok(Some(code.fblock[i].fb_exit)); - break; - } - FBlockType::ExceptionGroupHandler => { - result = Err(()); - break; - } - _ => continue, - } - } - result - }; - - match found_loop { - Ok(Some(exit_block)) => { - emit!(self, Instruction::Break { target: exit_block }); - } - Ok(None) => { - return Err( - self.error_ranged(CodegenErrorType::InvalidBreak, statement.range()) - ); - } - Err(()) => { - return Err(self.error_ranged( - CodegenErrorType::BreakContinueReturnInExceptStar, - statement.range(), - )); - } - } + // Unwind fblock stack until we find a loop, emitting cleanup for each fblock + self.compile_break_continue(statement.range(), true)?; } Stmt::Continue(_) => { - // Find the innermost loop in fblock stack - // Error if we encounter ExceptionGroupHandler before finding a loop - let found_loop = { - let code = self.current_code_info(); - let mut result = Ok(None); - for i in (0..code.fblock.len()).rev() { - match code.fblock[i].fb_type { - FBlockType::WhileLoop | FBlockType::ForLoop => { - result = Ok(Some(code.fblock[i].fb_block)); - break; - } - FBlockType::ExceptionGroupHandler => { - result = Err(()); - break; - } - _ => continue, - } - } - result - }; - - match found_loop { - Ok(Some(loop_block)) => { - emit!(self, Instruction::Continue { target: loop_block }); - } - Ok(None) => { - return Err( - self.error_ranged(CodegenErrorType::InvalidContinue, statement.range()) - ); - } - Err(()) => { - return Err(self.error_ranged( - CodegenErrorType::BreakContinueReturnInExceptStar, - statement.range(), - )); - } - } + // Unwind fblock stack until we find a loop, emitting cleanup for each fblock + self.compile_break_continue(statement.range(), false)?; } Stmt::Return(StmtReturn { value, .. }) => { if !self.ctx.in_func() { @@ -1677,18 +1907,7 @@ impl Compiler { self.error_ranged(CodegenErrorType::InvalidReturn, statement.range()) ); } - // Check if we're inside an except* block in the current function - { - let code = self.current_code_info(); - for block in code.fblock.iter().rev() { - if matches!(block.fb_type, FBlockType::ExceptionGroupHandler) { - return Err(self.error_ranged( - CodegenErrorType::BreakContinueReturnInExceptStar, - statement.range(), - )); - } - } - } + match value { Some(v) => { if self.ctx.func == FunctionContext::AsyncFunction @@ -1703,9 +1922,13 @@ impl Compiler { )); } self.compile_expression(v)?; + // Unwind fblock stack with preserve_tos=true (preserve return value) + self.unwind_fblock_stack(true, false)?; self.emit_return_value(); } None => { + // Unwind fblock stack with preserve_tos=false (no value to preserve) + self.unwind_fblock_stack(false, false)?; self.emit_return_const(ConstantData::None); } } @@ -1926,7 +2149,7 @@ impl Compiler { } /// Store each type parameter so it is accessible to the current scope, and leave a tuple of - /// all the type parameters on the stack. + /// all the type parameters on the stack. Handles default values per PEP 695. fn compile_type_params(&mut self, type_params: &TypeParams) -> CompileResult<()> { // First, compile each type parameter and store it for type_param in &type_params.type_params { @@ -1964,7 +2187,6 @@ impl Compiler { ); } - // Handle default value if present (PEP 695) if let Some(default_expr) = default { let scope_name = format!(""); self.compile_type_param_bound_or_default(default_expr, &scope_name, false)?; @@ -1990,7 +2212,6 @@ impl Compiler { } ); - // Handle default value if present (PEP 695) if let Some(default_expr) = default { let scope_name = format!(""); self.compile_type_param_bound_or_default(default_expr, &scope_name, false)?; @@ -2016,7 +2237,6 @@ impl Compiler { } ); - // Handle default value if present (PEP 695) if let Some(default_expr) = default { // TypeVarTuple allows starred expressions let scope_name = format!(""); @@ -2053,32 +2273,178 @@ impl Compiler { let handler_block = self.new_block(); let finally_block = self.new_block(); + // finally needs TWO blocks: + // - finally_block: normal path (no exception active) + // - finally_except_block: exception path (PUSH_EXC_INFO -> body -> RERAISE) + let finally_except_block = if !finalbody.is_empty() { + Some(self.new_block()) + } else { + None + }; + let finally_cleanup_block = if finally_except_block.is_some() { + Some(self.new_block()) + } else { + None + }; + // End block - continuation point after try-finally + // Normal path jumps here to skip exception path blocks + let end_block = self.new_block(); + + // Calculate the stack depth at this point (for exception table) + // SETUP_FINALLY captures current stack depth + let current_depth = self.handler_stack_depth(); + // Setup a finally block if we have a finally statement. + // Push fblock with handler info for exception table generation + // IMPORTANT: handler goes to finally_except_block (exception path), not finally_block if !finalbody.is_empty() { - emit!( - self, - Instruction::SetupFinally { - handler: finally_block, - } - ); + // No SetupFinally emit - exception table handles this + // Store finally body in fb_datum for unwind_fblock to compile inline + // SETUP_FINALLY doesn't push lasti for try body handler + // Exception table: L1 to L2 -> L4 [1] (no lasti) + self.push_fblock_full( + FBlockType::FinallyTry, + finally_block, + finally_block, + finally_except_block, // Exception path goes to finally_except_block + current_depth, + false, // No lasti for first finally handler + FBlockDatum::FinallyBody(finalbody.to_vec()), // Clone finally body for unwind + )?; } let else_block = self.new_block(); - // try: - emit!( - self, - Instruction::SetupExcept { - handler: handler_block, + // if handlers is empty, compile body directly + // without wrapping in TryExcept (only FinallyTry is needed) + if handlers.is_empty() { + // Just compile body with FinallyTry fblock active (if finalbody exists) + self.compile_statements(body)?; + + // Pop FinallyTry fblock BEFORE compiling orelse/finally (normal path) + // This prevents exception table from covering the normal path + if !finalbody.is_empty() { + self.pop_fblock(FBlockType::FinallyTry); } - ); + + // Compile orelse (usually empty for try-finally without except) + self.compile_statements(orelse)?; + + // Snapshot sub_tables before first finally compilation + // This allows us to restore them for the second compilation (exception path) + let sub_tables_snapshot = if !finalbody.is_empty() && finally_except_block.is_some() { + Some( + self.symbol_table_stack + .last() + .map(|t| t.sub_tables.clone()) + .unwrap_or_default(), + ) + } else { + None + }; + + // Compile finally body inline for normal path + if !finalbody.is_empty() { + self.compile_statements(finalbody)?; + } + + // Jump to end (skip exception path blocks) + emit!(self, Instruction::Jump { target: end_block }); + + if let Some(finally_except) = finally_except_block { + // Restore sub_tables for exception path compilation + if let Some(snapshot) = sub_tables_snapshot + && let Some(current_table) = self.symbol_table_stack.last_mut() + { + current_table.sub_tables = snapshot; + } + + self.switch_to_block(finally_except); + // PUSH_EXC_INFO first, THEN push FinallyEnd fblock + // Stack after unwind (no lasti): [exc] (depth = current_depth + 1) + // Stack after PUSH_EXC_INFO: [prev_exc, exc] (depth = current_depth + 2) + emit!(self, Instruction::PushExcInfo); + if let Some(cleanup) = finally_cleanup_block { + // FinallyEnd fblock must be pushed AFTER PUSH_EXC_INFO + // Depth = current_depth + 1 (only prev_exc remains after RERAISE pops exc) + // Exception table: L4 to L5 -> L6 [2] lasti (cleanup handler DOES push lasti) + self.push_fblock_with_handler( + FBlockType::FinallyEnd, + cleanup, + cleanup, + Some(cleanup), + current_depth + 1, + true, // Cleanup handler pushes lasti + )?; + } + self.compile_statements(finalbody)?; + // RERAISE 0 is emitted BEFORE pop_fblock + // This ensures RERAISE goes to cleanup block (FinallyEnd handler) + // which then properly restores prev_exc before going to outer handler + emit!( + self, + Instruction::Raise { + kind: bytecode::RaiseKind::ReraiseFromStack + } + ); + if finally_cleanup_block.is_some() { + self.pop_fblock(FBlockType::FinallyEnd); + } + } + + if let Some(cleanup) = finally_cleanup_block { + self.switch_to_block(cleanup); + emit!(self, Instruction::CopyItem { index: 3_u32 }); + emit!(self, Instruction::PopException); + emit!( + self, + Instruction::Raise { + kind: bytecode::RaiseKind::ReraiseFromStack + } + ); + } + + self.switch_to_block(end_block); + return Ok(()); + } + + // try: + // Push fblock with handler info for exception table generation + // No SetupExcept emit - exception table handles this + self.push_fblock_with_handler( + FBlockType::TryExcept, + handler_block, + handler_block, + Some(handler_block), + current_depth, // stack depth for exception handler + false, // no lasti for except + )?; self.compile_statements(body)?; - emit!(self, Instruction::PopBlock); + self.pop_fblock(FBlockType::TryExcept); + // No PopBlock emit - exception table handles this emit!(self, Instruction::Jump { target: else_block }); // except handlers: self.switch_to_block(handler_block); - // Exception is on top of stack now + + // SETUP_CLEANUP(cleanup) for except block + // This handles exceptions during exception matching + // Exception table: L2 to L3 -> L5 [1] lasti + // After PUSH_EXC_INFO, stack is [prev_exc, exc] + // depth=1 means keep prev_exc on stack when routing to cleanup + let cleanup_block = self.new_block(); + self.push_fblock_with_handler( + FBlockType::ExceptionHandler, + cleanup_block, + cleanup_block, + Some(cleanup_block), + current_depth + 1, // After PUSH_EXC_INFO: [prev_exc] stays on stack + true, // preserve_lasti for cleanup + )?; + + // Exception is on top of stack now, pushed by unwind_blocks + // PUSH_EXC_INFO transforms [exc] -> [prev_exc, exc] for PopException + emit!(self, Instruction::PushExcInfo); for handler in handlers { let ExceptHandler::ExceptHandler(ExceptHandlerExceptHandler { type_, name, body, .. @@ -2108,11 +2474,76 @@ impl Compiler { emit!(self, Instruction::PopTop); } + // If name is bound, we need a cleanup handler for RERAISE + let handler_cleanup_block = if name.is_some() { + // SETUP_CLEANUP(cleanup_end) for named handler + let cleanup_end = self.new_block(); + // Stack at handler entry: [prev_exc, exc] + // depth = 1 (prev_exc on stack after exception is popped) + let handler_depth = current_depth + 1; + self.push_fblock_with_handler( + FBlockType::HandlerCleanup, + cleanup_end, + cleanup_end, + Some(cleanup_end), + handler_depth, + true, // preserve_lasti for RERAISE + )?; + Some(cleanup_end) + } else { + // no SETUP_CLEANUP for unnamed handler + self.push_fblock(FBlockType::HandlerCleanup, finally_block, finally_block)?; + None + }; + // Handler code: self.compile_statements(body)?; + + self.pop_fblock(FBlockType::HandlerCleanup); + + // Create a block for normal path continuation (after handler body succeeds) + let handler_normal_exit = self.new_block(); + emit!( + self, + Instruction::Jump { + target: handler_normal_exit, + } + ); + + // cleanup_end block for named handler + // IMPORTANT: In CPython, cleanup_end is within outer SETUP_CLEANUP scope. + // so when RERAISE is executed, it goes to the cleanup block which does POP_EXCEPT. + // We MUST compile cleanup_end BEFORE popping ExceptionHandler so RERAISE routes to cleanup_block. + if let Some(cleanup_end) = handler_cleanup_block { + self.switch_to_block(cleanup_end); + if let Some(alias) = name { + // name = None; del name; before RERAISE + self.emit_load_const(ConstantData::None); + self.store_name(alias.as_str())?; + self.compile_name(alias.as_str(), NameUsage::Delete)?; + } + // RERAISE 1 (with lasti) - exception is on stack from exception table routing + // Stack at entry: [prev_exc (at handler_depth), lasti, exc] + // This RERAISE is within ExceptionHandler scope, so it routes to cleanup_block + // which does COPY 3; POP_EXCEPT; RERAISE + emit!( + self, + Instruction::Raise { + kind: bytecode::RaiseKind::ReraiseFromStack, + } + ); + } + + // Switch to normal exit block - this is where handler body success continues + self.switch_to_block(handler_normal_exit); + + // Now pop ExceptionHandler - the normal path continues from here + // POP_BLOCK (HandlerCleanup) then POP_BLOCK (SETUP_CLEANUP) + // followed by POP_EXCEPT + self.pop_fblock(FBlockType::ExceptionHandler); emit!(self, Instruction::PopException); - // Delete the exception variable if it was bound + // Delete the exception variable if it was bound (normal path) if let Some(alias) = name { // Set the variable to None before deleting self.emit_load_const(ConstantData::None); @@ -2120,12 +2551,7 @@ impl Compiler { self.compile_name(alias.as_str(), NameUsage::Delete)?; } - if !finalbody.is_empty() { - emit!(self, Instruction::PopBlock); // pop excepthandler block - // We enter the finally block, without exception. - emit!(self, Instruction::EnterFinally); - } - + // Jump to finally block emit!( self, Instruction::Jump { @@ -2133,16 +2559,49 @@ impl Compiler { } ); + // Re-push ExceptionHandler for next handler in the loop + // This will be popped at the end of handlers loop or when matched + self.push_fblock_with_handler( + FBlockType::ExceptionHandler, + cleanup_block, + cleanup_block, + Some(cleanup_block), + current_depth + 1, // After PUSH_EXC_INFO: [prev_exc] stays on stack + true, // preserve_lasti for cleanup + )?; + // Emit a new label for the next handler self.switch_to_block(next_handler); } // If code flows here, we have an unhandled exception, // raise the exception again! + // RERAISE 0 + // Stack: [prev_exc, exc] - exception is on stack from PUSH_EXC_INFO + // NOTE: We emit RERAISE 0 BEFORE popping fblock so it is within cleanup handler scope + emit!( + self, + Instruction::Raise { + kind: bytecode::RaiseKind::ReraiseFromStack, + } + ); + + // Pop EXCEPTION_HANDLER fblock + // Pop after RERAISE so the instruction has the correct exception handler + self.pop_fblock(FBlockType::ExceptionHandler); + + // cleanup block (POP_EXCEPT_AND_RERAISE) + // Stack at entry: [prev_exc, lasti, exc] (depth=1 + lasti + exc pushed) + // COPY 3: copy prev_exc to top -> [prev_exc, lasti, exc, prev_exc] + // POP_EXCEPT: pop prev_exc from stack and restore -> [prev_exc, lasti, exc] + // RERAISE 1: reraise with lasti + self.switch_to_block(cleanup_block); + emit!(self, Instruction::CopyItem { index: 3_u32 }); + emit!(self, Instruction::PopException); emit!( self, Instruction::Raise { - kind: bytecode::RaiseKind::Reraise, + kind: bytecode::RaiseKind::ReraiseFromStack, } ); @@ -2151,20 +2610,108 @@ impl Compiler { self.switch_to_block(else_block); self.compile_statements(orelse)?; + // Pop the FinallyTry fblock before jumping to finally if !finalbody.is_empty() { - emit!(self, Instruction::PopBlock); // pop finally block - - // We enter the finallyhandler block, without return / exception. - emit!(self, Instruction::EnterFinally); + // No PopBlock/EnterFinally emit - exception table handles this + self.pop_fblock(FBlockType::FinallyTry); } - // finally: + // Snapshot sub_tables before first finally compilation (for double compilation issue) + let sub_tables_snapshot = if !finalbody.is_empty() && finally_except_block.is_some() { + Some( + self.symbol_table_stack + .last() + .map(|t| t.sub_tables.clone()) + .unwrap_or_default(), + ) + } else { + None + }; + + // finally (normal path): self.switch_to_block(finally_block); if !finalbody.is_empty() { self.compile_statements(finalbody)?; - emit!(self, Instruction::EndFinally); + // Jump to end_block to skip exception path blocks + // This prevents fall-through to finally_except_block + emit!(self, Instruction::Jump { target: end_block }); + } + + // finally (exception path) + // This is where exceptions go to run finally before reraise + // Stack at entry: [lasti, exc] (from exception table with preserve_lasti=true) + if let Some(finally_except) = finally_except_block { + // Restore sub_tables for exception path compilation + if let Some(snapshot) = sub_tables_snapshot + && let Some(current_table) = self.symbol_table_stack.last_mut() + { + current_table.sub_tables = snapshot; + } + + self.switch_to_block(finally_except); + + // SETUP_CLEANUP for finally body + // Exceptions during finally body need to go to cleanup block + // Stack at entry: [lasti, exc] (lasti from exception table, exc pushed) + // After PUSH_EXC_INFO: [lasti, prev_exc, exc] + // So depth should account for lasti being on stack + if let Some(cleanup) = finally_cleanup_block { + self.push_fblock_with_handler( + FBlockType::FinallyEnd, + cleanup, + cleanup, + Some(cleanup), + current_depth + 1, // [lasti] on stack before PUSH_EXC_INFO + true, + )?; + } + + // PUSH_EXC_INFO: [lasti, exc] -> [lasti, prev_exc, exc] + // Sets exc as current VM exception, saves prev_exc for restoration + emit!(self, Instruction::PushExcInfo); + + // Run finally body + self.compile_statements(finalbody)?; + + // RERAISE 0 is emitted BEFORE pop_fblock + // This ensures RERAISE goes to cleanup block (FinallyEnd handler) + // which then properly restores prev_exc before going to outer handler + // RERAISE 0: reraise the exception on TOS + // Stack: [lasti, prev_exc, exc] - exception is on top + emit!( + self, + Instruction::Raise { + kind: bytecode::RaiseKind::ReraiseFromStack, + } + ); + + if finally_cleanup_block.is_some() { + self.pop_fblock(FBlockType::FinallyEnd); + } } + // finally cleanup block + // This handles exceptions that occur during the finally body itself + // Stack at entry: [lasti, prev_exc, lasti2, exc2] after exception table routing + if let Some(cleanup) = finally_cleanup_block { + self.switch_to_block(cleanup); + // COPY 3: copy the exception from position 3 + emit!(self, Instruction::CopyItem { index: 3_u32 }); + // POP_EXCEPT: restore prev_exc as current exception + emit!(self, Instruction::PopException); + // RERAISE 1: reraise with lasti from stack + emit!( + self, + Instruction::Raise { + kind: bytecode::RaiseKind::ReraiseFromStack, + } + ); + } + + // End block - continuation point after try-finally + // Normal execution continues here after the finally block + self.switch_to_block(end_block); + Ok(()) } @@ -2175,47 +2722,58 @@ impl Compiler { orelse: &[Stmt], finalbody: &[Stmt], ) -> CompileResult<()> { - // Simplified except* implementation using PrepReraiseStar intrinsic - // Stack layout during handler processing: [orig, list, rest] + // compiler_try_star_except + // Stack layout during handler processing: [prev_exc, orig, list, rest] let handler_block = self.new_block(); let finally_block = self.new_block(); let else_block = self.new_block(); let end_block = self.new_block(); let reraise_star_block = self.new_block(); let reraise_block = self.new_block(); + let _cleanup_block = self.new_block(); + + // Calculate the stack depth at this point (for exception table) + let current_depth = self.handler_stack_depth(); + // Push fblock with handler info for exception table generation if !finalbody.is_empty() { - emit!( - self, - Instruction::SetupFinally { - handler: finally_block, - } - ); + // No SetupFinally emit - exception table handles this + self.push_fblock_with_handler( + FBlockType::FinallyTry, + finally_block, + finally_block, + Some(finally_block), + current_depth, // stack depth for exception handler + true, // preserve lasti for finally + )?; } - emit!( - self, - Instruction::SetupExcept { - handler: handler_block, - } - ); + // SETUP_FINALLY for try body + // Push fblock with handler info for exception table generation + self.push_fblock_with_handler( + FBlockType::TryExcept, + handler_block, + handler_block, + Some(handler_block), + current_depth, // stack depth for exception handler + false, // no lasti for except + )?; self.compile_statements(body)?; - emit!(self, Instruction::PopBlock); + self.pop_fblock(FBlockType::TryExcept); emit!(self, Instruction::Jump { target: else_block }); // Exception handler entry self.switch_to_block(handler_block); - // Stack: [exc] + // Stack: [exc] (from exception table) - // Create list for tracking exception results and copy orig - emit!(self, Instruction::BuildList { size: 0 }); - // Stack: [exc, []] - // CopyItem is 1-indexed: CopyItem(1)=TOS, CopyItem(2)=second from top - // With stack [exc, []], CopyItem(2) copies exc - emit!(self, Instruction::CopyItem { index: 2 }); - // Stack: [exc, [], exc_copy] + // PUSH_EXC_INFO + emit!(self, Instruction::PushExcInfo); + // Stack: [prev_exc, exc] - // Now stack is: [orig, list, rest] + // Push EXCEPTION_GROUP_HANDLER fblock + let eg_dummy1 = self.new_block(); + let eg_dummy2 = self.new_block(); + self.push_fblock(FBlockType::ExceptionGroupHandler, eg_dummy1, eg_dummy2)?; let n = handlers.len(); for (i, handler) in handlers.iter().enumerate() { @@ -2226,6 +2784,17 @@ impl Compiler { let no_match_block = self.new_block(); let next_block = self.new_block(); + // first handler creates list and copies exc + if i == 0 { + // ADDOP_I(c, loc, BUILD_LIST, 0); + emit!(self, Instruction::BuildList { size: 0 }); + // Stack: [prev_exc, exc, []] + // ADDOP_I(c, loc, COPY, 2); + emit!(self, Instruction::CopyItem { index: 2 }); + // Stack: [prev_exc, exc, [], exc_copy] + // Now stack is: [prev_exc, orig, list, rest] + } + // Compile exception type if let Some(exc_type) = type_ { // Check for unparenthesized tuple @@ -2243,13 +2812,14 @@ impl Compiler { "except* must specify an exception type".to_owned(), ))); } - // Stack: [orig, list, rest, type] + // Stack: [prev_exc, orig, list, rest, type] + // ADDOP(c, loc, CHECK_EG_MATCH); emit!(self, Instruction::CheckEgMatch); - // Stack: [orig, list, new_rest, match] + // Stack: [prev_exc, orig, list, new_rest, match] - // Check if match is not None (use identity check, not truthiness) - // CopyItem is 1-indexed: CopyItem(1) = TOS, CopyItem(2) = second from top + // ADDOP_I(c, loc, COPY, 1); + // ADDOP_JUMP(c, loc, POP_JUMP_IF_NONE, no_match); emit!(self, Instruction::CopyItem { index: 1 }); self.emit_load_const(ConstantData::None); emit!(self, Instruction::IsOp(bytecode::Invert::No)); // is None? @@ -2261,42 +2831,39 @@ impl Compiler { ); // Handler matched - // Stack: [orig, list, new_rest, match] + // Stack: [prev_exc, orig, list, new_rest, match] let handler_except_block = self.new_block(); - let handler_done_block = self.new_block(); - // Set matched exception as current exception for bare 'raise' + // Set matched exception as current exception (for __context__ in handler body) + // This ensures that exceptions raised in the handler get the matched part + // as their __context__, not the original full exception group emit!(self, Instruction::SetExcInfo); - // Store match to name if provided + // Store match to name or pop if let Some(alias) = name { - // CopyItem(1) copies TOS (match) - emit!(self, Instruction::CopyItem { index: 1 }); self.store_name(alias.as_str())?; + } else { + emit!(self, Instruction::PopTop); // pop match } - // Stack: [orig, list, new_rest, match] + // Stack: [prev_exc, orig, list, new_rest] - // Setup exception handler to catch 'raise' in handler body - emit!( - self, - Instruction::SetupExcept { - handler: handler_except_block, - } - ); - - // Push fblock to disallow break/continue/return in except* handler - self.push_fblock( - FBlockType::ExceptionGroupHandler, - handler_done_block, + // HANDLER_CLEANUP fblock for handler body + // Stack depth: prev_exc(1) + orig(1) + list(1) + new_rest(1) = 4 + let eg_handler_depth = self.handler_stack_depth() + 4; + self.push_fblock_with_handler( + FBlockType::HandlerCleanup, + next_block, end_block, + Some(handler_except_block), + eg_handler_depth, + true, // preserve lasti )?; // Execute handler body self.compile_statements(body)?; - // Handler body completed normally (didn't raise) - self.pop_fblock(FBlockType::ExceptionGroupHandler); - emit!(self, Instruction::PopBlock); + // Handler body completed normally + self.pop_fblock(FBlockType::HandlerCleanup); // Cleanup name binding if let Some(alias) = name { @@ -2305,27 +2872,13 @@ impl Compiler { self.compile_name(alias.as_str(), NameUsage::Delete)?; } - // Stack: [orig, list, new_rest, match] - // Pop match (handler consumed it) - emit!(self, Instruction::PopTop); - // Stack: [orig, list, new_rest] - - // Append None to list (exception was consumed, not reraised) - self.emit_load_const(ConstantData::None); - // Stack: [orig, list, new_rest, None] - emit!(self, Instruction::ListAppend { i: 1 }); - // Stack: [orig, list, new_rest] - - emit!( - self, - Instruction::Jump { - target: handler_done_block - } - ); + // Jump to next handler + emit!(self, Instruction::Jump { target: next_block }); - // Handler raised an exception (bare 'raise' or other) + // Handler raised an exception (cleanup_end label) self.switch_to_block(handler_except_block); - // Stack: [orig, list, new_rest, match, raised_exc] + // Stack: [prev_exc, orig, list, new_rest, lasti, raised_exc] + // (lasti is pushed because push_lasti=true in HANDLER_CLEANUP fblock) // Cleanup name binding if let Some(alias) = name { @@ -2334,37 +2887,42 @@ impl Compiler { self.compile_name(alias.as_str(), NameUsage::Delete)?; } - // Append raised_exc to list (the actual exception that was raised) - // Stack: [orig, list, new_rest, match, raised_exc] - // ListAppend(2): pop raised_exc, then append to list at stack[4-2-1]=stack[1] + // LIST_APPEND(3) - append raised_exc to list + // Stack: [prev_exc, orig, list, new_rest, lasti, raised_exc] + // After pop: [prev_exc, orig, list, new_rest, lasti] (len=5) + // nth_value(i) = stack[len - i - 1], we need stack[2] = list + // stack[5 - i - 1] = 2 -> i = 2 emit!(self, Instruction::ListAppend { i: 2 }); - // Stack: [orig, list, new_rest, match] + // Stack: [prev_exc, orig, list, new_rest, lasti] - // Pop match (no longer needed) + // POP_TOP - pop lasti emit!(self, Instruction::PopTop); - // Stack: [orig, list, new_rest] - - self.switch_to_block(handler_done_block); - // Stack: [orig, list, new_rest] + // Stack: [prev_exc, orig, list, new_rest] + // JUMP except_with_error + // We directly JUMP to next_block since no_match_block falls through to it emit!(self, Instruction::Jump { target: next_block }); - // No match - pop match (None), keep rest unchanged + // No match - pop match (None) self.switch_to_block(no_match_block); emit!(self, Instruction::PopTop); // pop match (None) - // Stack: [orig, list, new_rest] + // Stack: [prev_exc, orig, list, new_rest] + // Falls through to next_block + // except_with_error label + // All paths merge here at next_block self.switch_to_block(next_block); - // Stack: [orig, list, rest] (rest may have been updated) + // Stack: [prev_exc, orig, list, rest] - // After last handler, append remaining rest to list + // After last handler, append rest to list if i == n - 1 { - // Stack: [orig, list, rest] - // ListAppend(i) pops TOS, then accesses stack[len - i - 1] - // After pop, stack is [orig, list], len=2 - // We want list at index 1, so 2 - i - 1 = 1, i = 0 + // Stack: [prev_exc, orig, list, rest] + // ADDOP_I(c, NO_LOCATION, LIST_APPEND, 1); + // PEEK(1) = stack[len-1] after pop + // RustPython nth_value(i) = stack[len-i-1] after pop + // For LIST_APPEND 1: stack[len-1] = stack[len-i-1] -> i = 0 emit!(self, Instruction::ListAppend { i: 0 }); - // Stack: [orig, list] + // Stack: [prev_exc, orig, list] emit!( self, Instruction::Jump { @@ -2374,19 +2932,28 @@ impl Compiler { } } + // Pop EXCEPTION_GROUP_HANDLER fblock + self.pop_fblock(FBlockType::ExceptionGroupHandler); + // Reraise star block self.switch_to_block(reraise_star_block); - // Stack: [orig, list] + // Stack: [prev_exc, orig, list] + + // CALL_INTRINSIC_2 PREP_RERAISE_STAR + // Takes 2 args (orig, list) and produces result emit!( self, Instruction::CallIntrinsic2 { func: bytecode::IntrinsicFunction2::PrepReraiseStar } ); - // Stack: [result] (exception to reraise or None) + // Stack: [prev_exc, result] - // Check if result is not None (use identity check, not truthiness) + // COPY 1 emit!(self, Instruction::CopyItem { index: 1 }); + // Stack: [prev_exc, result, result] + + // POP_JUMP_IF_NOT_NONE reraise self.emit_load_const(ConstantData::None); emit!(self, Instruction::IsOp(bytecode::Invert::Yes)); // is not None? emit!( @@ -2395,37 +2962,61 @@ impl Compiler { target: reraise_block } ); + // Stack: [prev_exc, result] // Nothing to reraise + // POP_TOP - pop result (None) emit!(self, Instruction::PopTop); + // Stack: [prev_exc] + + // POP_BLOCK - no-op for us with exception tables (fblocks handle this) + // POP_EXCEPT - restore previous exception context emit!(self, Instruction::PopException); + // Stack: [] if !finalbody.is_empty() { - emit!(self, Instruction::PopBlock); - emit!(self, Instruction::EnterFinally); + self.pop_fblock(FBlockType::FinallyTry); } emit!(self, Instruction::Jump { target: end_block }); // Reraise the result self.switch_to_block(reraise_block); - // Don't call PopException before Raise - it truncates the stack and removes the result. - // When Raise is executed, the exception propagates through unwind_blocks which - // will properly handle the ExceptHandler block. - emit!( - self, - Instruction::Raise { - kind: bytecode::RaiseKind::Raise - } - ); + // Stack: [prev_exc, result] + + // POP_BLOCK - no-op for us + // SWAP 2 + emit!(self, Instruction::Swap { index: 2 }); + // Stack: [result, prev_exc] + + // POP_EXCEPT + emit!(self, Instruction::PopException); + // Stack: [result] + + // RERAISE 0 + emit!(self, Instruction::Reraise { depth: 0 }); // try-else path + // NOTE: When we reach here in compilation, the nothing-to-reraise path above + // has already popped FinallyTry. But else_block is a different execution path + // that branches from try body success (where FinallyTry is still active). + // We need to re-push FinallyTry to reflect the correct fblock state for else path. + if !finalbody.is_empty() { + self.push_fblock_with_handler( + FBlockType::FinallyTry, + finally_block, + finally_block, + Some(finally_block), + current_depth, + true, + )?; + } self.switch_to_block(else_block); self.compile_statements(orelse)?; if !finalbody.is_empty() { - emit!(self, Instruction::PopBlock); - emit!(self, Instruction::EnterFinally); + // Pop the FinallyTry fblock we just pushed for the else path + self.pop_fblock(FBlockType::FinallyTry); } emit!(self, Instruction::Jump { target: end_block }); @@ -2434,7 +3025,7 @@ impl Compiler { if !finalbody.is_empty() { self.switch_to_block(finally_block); self.compile_statements(finalbody)?; - emit!(self, Instruction::EndFinally); + // No EndFinally emit - exception table handles this } Ok(()) @@ -2527,6 +3118,8 @@ impl Compiler { } else { FunctionContext::Function }, + // A function starts a new async scope only if it's async + in_async_scope: is_async, }; // Set qualname @@ -2863,7 +3456,6 @@ impl Compiler { // Set closure if needed if has_freevars { - // Closure tuple is already on stack emit!( self, Instruction::SetFunctionAttribute { @@ -2874,7 +3466,6 @@ impl Compiler { // Set annotations if present if flags.contains(bytecode::MakeFunctionFlags::ANNOTATIONS) { - // Annotations dict is already on stack emit!( self, Instruction::SetFunctionAttribute { @@ -2885,7 +3476,6 @@ impl Compiler { // Set kwdefaults if present if flags.contains(bytecode::MakeFunctionFlags::KW_ONLY_DEFAULTS) { - // kwdefaults dict is already on stack emit!( self, Instruction::SetFunctionAttribute { @@ -2896,7 +3486,6 @@ impl Compiler { // Set defaults if present if flags.contains(bytecode::MakeFunctionFlags::DEFAULTS) { - // defaults tuple is already on stack emit!( self, Instruction::SetFunctionAttribute { @@ -2907,7 +3496,6 @@ impl Compiler { // Set type_params if present if flags.contains(bytecode::MakeFunctionFlags::TYPE_PARAMS) { - // type_params tuple is already on stack emit!( self, Instruction::SetFunctionAttribute { @@ -3089,6 +3677,7 @@ impl Compiler { func: FunctionContext::NoFunction, in_class: true, loop_data: None, + in_async_scope: false, }; let class_code = self.compile_class_body(name, body, type_params, firstlineno)?; self.ctx = prev_ctx; @@ -3195,7 +3784,7 @@ impl Compiler { let else_block = self.new_block(); let after_block = self.new_block(); - emit!(self, Instruction::SetupLoop); + // Note: SetupLoop is no longer emitted (break/continue use direct jumps) self.switch_to_block(while_block); // Push fblock for while loop @@ -3216,7 +3805,7 @@ impl Compiler { // Pop fblock self.pop_fblock(FBlockType::WhileLoop); - emit!(self, Instruction::PopBlock); + // Note: PopBlock is no longer emitted for loops self.compile_statements(orelse)?; self.switch_to_block(after_block); Ok(()) @@ -3228,45 +3817,89 @@ impl Compiler { body: &[Stmt], is_async: bool, ) -> CompileResult<()> { + // Python 3.12+ style with statement: + // + // BEFORE_WITH # TOS: ctx_mgr -> [__exit__, __enter__ result] + // L1: STORE_NAME f # exception table: L1 to L2 -> L3 [1] lasti + // L2: ... body ... + // LOAD_CONST None # normal exit + // LOAD_CONST None + // LOAD_CONST None + // CALL 2 # __exit__(None, None, None) + // POP_TOP + // JUMP after + // L3: PUSH_EXC_INFO # exception handler + // WITH_EXCEPT_START # call __exit__(type, value, tb), push result + // TO_BOOL + // POP_JUMP_IF_TRUE suppress + // RERAISE 2 + // suppress: + // POP_TOP # pop exit result + // L5: POP_EXCEPT + // POP_TOP # pop __exit__ + // POP_TOP # pop prev_exc (or lasti depending on layout) + // JUMP after + // L6: COPY 3 # cleanup handler for reraise + // POP_EXCEPT + // RERAISE 1 + // after: ... + let with_range = self.current_source_range; let Some((item, items)) = items.split_first() else { return Err(self.error(CodegenErrorType::EmptyWithItems)); }; - let final_block = { - let final_block = self.new_block(); - self.compile_expression(&item.context_expr)?; + let exc_handler_block = self.new_block(); + let after_block = self.new_block(); - self.set_source_range(with_range); + // Compile context expression and BEFORE_WITH + self.compile_expression(&item.context_expr)?; + self.set_source_range(with_range); + + if is_async { + if self.ctx.func != FunctionContext::AsyncFunction { + return Err(self.error(CodegenErrorType::InvalidAsyncWith)); + } + emit!(self, Instruction::BeforeAsyncWith); + emit!(self, Instruction::GetAwaitable); + self.emit_load_const(ConstantData::None); + self.compile_yield_from_sequence(true)?; + } else { + emit!(self, Instruction::BeforeWith); + } + + // Stack: [..., __exit__, enter_result] + // Push fblock for exception table - handler goes to exc_handler_block + // preserve_lasti=true for with statements + // Use handler_stack_depth() to include all items on stack (for loops, etc.) + let with_depth = self.handler_stack_depth() + 1; // +1 for current __exit__ + self.push_fblock_with_handler( if is_async { - emit!(self, Instruction::BeforeAsyncWith); - emit!(self, Instruction::GetAwaitable); - self.emit_load_const(ConstantData::None); - emit!(self, Instruction::YieldFrom); - emit!( - self, - Instruction::Resume { - arg: bytecode::ResumeType::AfterAwait as u32 - } - ); - emit!(self, Instruction::SetupAsyncWith { end: final_block }); + FBlockType::AsyncWith } else { - emit!(self, Instruction::SetupWith { end: final_block }); + FBlockType::With + }, + exc_handler_block, // block start (will become exit target after store) + after_block, + Some(exc_handler_block), + with_depth, + true, // preserve_lasti=true + )?; + + // Store or pop the enter result + match &item.optional_vars { + Some(var) => { + self.set_source_range(var.range()); + self.compile_store(var)?; } - - match &item.optional_vars { - Some(var) => { - self.set_source_range(var.range()); - self.compile_store(var)?; - } - None => { - emit!(self, Instruction::PopTop); - } + None => { + emit!(self, Instruction::PopTop); } - final_block - }; + } + // Stack: [..., __exit__] + // Compile body or nested with if items.is_empty() { if body.is_empty() { return Err(self.error(CodegenErrorType::EmptyWithBody)); @@ -3277,29 +3910,123 @@ impl Compiler { self.compile_with(items, body, is_async)?; } - // sort of "stack up" the layers of with blocks: - // with a, b: body -> start_with(a) start_with(b) body() end_with(b) end_with(a) - self.set_source_range(with_range); - emit!(self, Instruction::PopBlock); + // Pop fblock before normal exit + self.pop_fblock(if is_async { + FBlockType::AsyncWith + } else { + FBlockType::With + }); - emit!(self, Instruction::EnterFinally); + // ===== Normal exit path ===== + // Stack: [..., __exit__] + // Call __exit__(None, None, None) + self.set_source_range(with_range); + self.emit_load_const(ConstantData::None); + self.emit_load_const(ConstantData::None); + self.emit_load_const(ConstantData::None); + emit!(self, Instruction::CallFunctionPositional { nargs: 3 }); + if is_async { + emit!(self, Instruction::GetAwaitable); + self.emit_load_const(ConstantData::None); + self.compile_yield_from_sequence(true)?; + } + emit!(self, Instruction::PopTop); // Pop __exit__ result + emit!( + self, + Instruction::Jump { + target: after_block + } + ); - self.switch_to_block(final_block); - emit!(self, Instruction::WithCleanupStart); + // ===== Exception handler path ===== + // Stack at entry (after unwind): [..., __exit__, lasti, exc] + // PUSH_EXC_INFO -> [..., __exit__, lasti, prev_exc, exc] + self.switch_to_block(exc_handler_block); + + // Create blocks for exception handling + let cleanup_block = self.new_block(); + let suppress_block = self.new_block(); + + // Push nested fblock for cleanup handler + // Stack at exc_handler_block entry: [..., __exit__, lasti, exc] + // After PUSH_EXC_INFO: [..., __exit__, lasti, prev_exc, exc] + // If exception in __exit__, cleanup handler entry: [..., __exit__, lasti, prev_exc, lasti2, exc2] + // cleanup_depth should be: with_depth + 2 (lasti + prev_exc) + let cleanup_depth = with_depth + 2; + self.push_fblock_with_handler( + FBlockType::ExceptionHandler, + exc_handler_block, + after_block, + Some(cleanup_block), + cleanup_depth, + true, // preserve_lasti=true + )?; + + // PUSH_EXC_INFO: [exc] -> [prev_exc, exc] + emit!(self, Instruction::PushExcInfo); + + // WITH_EXCEPT_START: call __exit__(type, value, tb) + // Stack: [..., __exit__, lasti, prev_exc, exc] + // __exit__ is at TOS-3, call with exception info + emit!(self, Instruction::WithExceptStart); if is_async { emit!(self, Instruction::GetAwaitable); self.emit_load_const(ConstantData::None); - emit!(self, Instruction::YieldFrom); - emit!( - self, - Instruction::Resume { - arg: bytecode::ResumeType::AfterAwait as u32 - } - ); + self.compile_yield_from_sequence(true)?; } - emit!(self, Instruction::WithCleanupFinish); + // TO_BOOL + POP_JUMP_IF_TRUE: check if exception is suppressed + emit!(self, Instruction::ToBool); + emit!( + self, + Instruction::PopJumpIfTrue { + target: suppress_block + } + ); + + // Pop the nested fblock BEFORE RERAISE so that RERAISE's exception + // handler points to the outer handler (try-except), not cleanup_block. + // This is critical: when RERAISE propagates the exception, the exception + // table should route it to the outer try-except, not back to cleanup. + self.pop_fblock(FBlockType::ExceptionHandler); + + // Not suppressed: RERAISE 2 + emit!(self, Instruction::Reraise { depth: 2 }); + + // ===== Suppress block ===== + // Exception was suppressed, clean up stack + // Stack: [..., __exit__, lasti, prev_exc, exc, True] + // Need to pop: True, exc, prev_exc, __exit__ + self.switch_to_block(suppress_block); + emit!(self, Instruction::PopTop); // pop True (TO_BOOL result) + emit!(self, Instruction::PopException); // pop exc and restore prev_exc + emit!(self, Instruction::PopTop); // pop __exit__ + emit!(self, Instruction::PopTop); // pop lasti + emit!( + self, + Instruction::Jump { + target: after_block + } + ); + + // ===== Cleanup block (for nested exception during __exit__) ===== + // Stack: [..., __exit__, lasti, prev_exc, lasti2, exc2] + // COPY 3: copy prev_exc to TOS + // POP_EXCEPT: restore exception state + // RERAISE 1: re-raise with lasti + // + // NOTE: We DON'T clear the fblock stack here because we want + // outer exception handlers (e.g., try-except wrapping this with statement) + // to be in the exception table for these instructions. + // If we cleared fblock, exceptions here would propagate uncaught. + self.switch_to_block(cleanup_block); + emit!(self, Instruction::CopyItem { index: 3 }); + emit!(self, Instruction::PopException); + emit!(self, Instruction::Reraise { depth: 1 }); + + // ===== After block ===== + self.switch_to_block(after_block); Ok(()) } @@ -3317,36 +4044,36 @@ impl Compiler { let else_block = self.new_block(); let after_block = self.new_block(); - emit!(self, Instruction::SetupLoop); - // The thing iterated: self.compile_expression(iter)?; if is_async { + if self.ctx.func != FunctionContext::AsyncFunction { + return Err(self.error(CodegenErrorType::InvalidAsyncFor)); + } emit!(self, Instruction::GetAIter); self.switch_to_block(for_block); - // Push fblock for async for loop - self.push_fblock(FBlockType::ForLoop, for_block, after_block)?; + // Push fblock for async for loop with exception handler info + // Note: SetupExcept is no longer emitted (exception table handles StopAsyncIteration) + // Stack at this point: [..., async_iterator] + // We need handler_stack_depth() + 1 to keep parent items + async_iterator on stack when exception occurs + let async_for_depth = self.handler_stack_depth() + 1; + self.push_fblock_with_handler( + FBlockType::ForLoop, + for_block, + after_block, + Some(else_block), // Handler for StopAsyncIteration + async_for_depth, // stack depth: keep async_iterator and parent items + false, // no lasti needed + )?; - emit!( - self, - Instruction::SetupExcept { - handler: else_block, - } - ); emit!(self, Instruction::GetANext); self.emit_load_const(ConstantData::None); - emit!(self, Instruction::YieldFrom); - emit!( - self, - Instruction::Resume { - arg: bytecode::ResumeType::AfterAwait as u32 - } - ); + self.compile_yield_from_sequence(true)?; self.compile_store(target)?; - emit!(self, Instruction::PopBlock); + // Note: PopBlock is no longer emitted (exception table handles this) } else { // Retrieve Iterator emit!(self, Instruction::GetIter); @@ -3375,7 +4102,6 @@ impl Compiler { if is_async { emit!(self, Instruction::EndAsyncFor); } - emit!(self, Instruction::PopBlock); self.compile_statements(orelse)?; self.switch_to_block(after_block); @@ -3961,7 +4687,7 @@ impl Compiler { } // After processing subpatterns, adjust on_top - // CPython: "Whatever happens next should consume the tuple of keys and the subject" + // "Whatever happens next should consume the tuple of keys and the subject" // Stack currently: [subject, keys_tuple, ...any captured values...] pc.on_top -= 2; @@ -4465,7 +5191,6 @@ impl Compiler { // Special handling for starred annotations (*Ts -> Unpack[Ts]) let result = match annotation { Expr::Starred(ExprStarred { value, .. }) => { - // Following CPython's approach: // *args: *Ts (where Ts is a TypeVarTuple). // Do [annotation_value] = [*Ts]. self.compile_expression(value)?; @@ -4828,6 +5553,80 @@ impl Compiler { Ok(()) } + /// Compile the yield-from/await sequence using SEND/END_SEND/CLEANUP_THROW. + /// compiler_add_yield_from + /// This generates: + /// send: + /// SEND exit + /// SETUP_FINALLY fail (via exception table) + /// YIELD_VALUE 1 + /// POP_BLOCK (implicit) + /// RESUME + /// JUMP send + /// fail: + /// CLEANUP_THROW + /// exit: + /// END_SEND + fn compile_yield_from_sequence(&mut self, is_await: bool) -> CompileResult<()> { + let send_block = self.new_block(); + let fail_block = self.new_block(); + let exit_block = self.new_block(); + + // send: + self.switch_to_block(send_block); + emit!(self, Instruction::Send { target: exit_block }); + + // SETUP_FINALLY fail - set up exception handler for YIELD_VALUE + // Stack at this point: [receiver, yielded_value] + // handler_depth = base + 2 (receiver + yielded_value) + let handler_depth = self.handler_stack_depth() + 2; + self.push_fblock_with_handler( + FBlockType::TryExcept, // Use TryExcept for exception handler + send_block, + exit_block, + Some(fail_block), + handler_depth, + false, // no lasti needed + )?; + + // YIELD_VALUE with arg=1 (yield-from/await mode - not wrapped for async gen) + emit!(self, Instruction::YieldValue { arg: 1 }); + + // POP_BLOCK (implicit - pop fblock before RESUME) + self.pop_fblock(FBlockType::TryExcept); + + // RESUME + emit!( + self, + Instruction::Resume { + arg: if is_await { + bytecode::ResumeType::AfterAwait as u32 + } else { + bytecode::ResumeType::AfterYieldFrom as u32 + } + } + ); + + // JUMP_NO_INTERRUPT send (regular JUMP in RustPython) + emit!(self, Instruction::Jump { target: send_block }); + + // fail: CLEANUP_THROW + // Stack when exception: [receiver, yielded_value, exc] + // CLEANUP_THROW: [sub_iter, last_sent_val, exc] -> [None, value] + // After: stack is [None, value], fall through to exit + self.switch_to_block(fail_block); + emit!(self, Instruction::CleanupThrow); + // Fall through to exit block + + // exit: END_SEND + // Stack: [receiver, value] (from SEND) or [None, value] (from CLEANUP_THROW) + // END_SEND: [receiver/None, value] -> [value] + self.switch_to_block(exit_block); + emit!(self, Instruction::EndSend); + + Ok(()) + } + fn compile_expression(&mut self, expression: &Expr) -> CompileResult<()> { use ruff_python_ast::*; trace!("Compiling {expression:?}"); @@ -4923,7 +5722,8 @@ impl Compiler { Some(expression) => self.compile_expression(expression)?, Option::None => self.emit_load_const(ConstantData::None), }; - emit!(self, Instruction::YieldValue); + // arg=0: direct yield (wrapped for async generators) + emit!(self, Instruction::YieldValue { arg: 0 }); emit!( self, Instruction::Resume { @@ -4938,13 +5738,7 @@ impl Compiler { self.compile_expression(value)?; emit!(self, Instruction::GetAwaitable); self.emit_load_const(ConstantData::None); - emit!(self, Instruction::YieldFrom); - emit!( - self, - Instruction::Resume { - arg: bytecode::ResumeType::AfterAwait as u32 - } - ); + self.compile_yield_from_sequence(true)?; } Expr::YieldFrom(ExprYieldFrom { value, .. }) => { match self.ctx.func { @@ -4958,15 +5752,9 @@ impl Compiler { } self.mark_generator(); self.compile_expression(value)?; - emit!(self, Instruction::GetIter); + emit!(self, Instruction::GetYieldFromIter); self.emit_load_const(ConstantData::None); - emit!(self, Instruction::YieldFrom); - emit!( - self, - Instruction::Resume { - arg: bytecode::ResumeType::AfterYieldFrom as u32 - } - ); + self.compile_yield_from_sequence(false)?; } Expr::Name(ExprName { id, .. }) => self.load_name(id.as_str())?, Expr::Lambda(ExprLambda { @@ -5036,6 +5824,8 @@ impl Compiler { loop_data: Option::None, in_class: prev_ctx.in_class, func: FunctionContext::Function, + // Lambda is never async, so new scope is not async + in_async_scope: false, }; self.current_code_info() @@ -5072,7 +5862,7 @@ impl Compiler { Ok(()) }, ComprehensionType::List, - Self::contains_await(elt), + Self::contains_await(elt) || Self::generators_contain_await(generators), )?; } Expr::SetComp(ExprSetComp { @@ -5095,7 +5885,7 @@ impl Compiler { Ok(()) }, ComprehensionType::Set, - Self::contains_await(elt), + Self::contains_await(elt) || Self::generators_contain_await(generators), )?; } Expr::DictComp(ExprDictComp { @@ -5125,20 +5915,31 @@ impl Compiler { Ok(()) }, ComprehensionType::Dict, - Self::contains_await(key) || Self::contains_await(value), + Self::contains_await(key) + || Self::contains_await(value) + || Self::generators_contain_await(generators), )?; } Expr::Generator(ExprGenerator { elt, generators, .. }) => { + // Check if element or generators contain async content + // This makes the generator expression into an async generator + let element_contains_await = + Self::contains_await(elt) || Self::generators_contain_await(generators); self.compile_comprehension( "", None, generators, &|compiler| { + // Compile the element expression + // Note: if element is an async comprehension, compile_expression + // already handles awaiting it, so we don't need to await again here compiler.compile_comprehension_element(elt)?; + compiler.mark_generator(); - emit!(compiler, Instruction::YieldValue); + // arg=0: direct yield (wrapped for async generators) + emit!(compiler, Instruction::YieldValue { arg: 0 }); emit!( compiler, Instruction::Resume { @@ -5150,7 +5951,7 @@ impl Compiler { Ok(()) }, ComprehensionType::Generator, - Self::contains_await(elt), + element_contains_await, )?; } Expr::Starred(ExprStarred { value, .. }) => { @@ -5452,30 +6253,47 @@ impl Compiler { let prev_ctx = self.ctx; let has_an_async_gen = generators.iter().any(|g| g.is_async); - // async comprehensions are allowed in various contexts: - // - list/set/dict comprehensions in async functions - // - always for generator expressions - // Note: generators have to be treated specially since their async version is a fundamentally - // different type (aiter vs iter) instead of just an awaitable. - - // for if it actually is async, we check if any generator is async or if the element contains await + // Check for async comprehension outside async function (list/set/dict only, not generator expressions) + // Use in_async_scope to allow nested async comprehensions inside an async function + if comprehension_type != ComprehensionType::Generator + && (has_an_async_gen || element_contains_await) + && !prev_ctx.in_async_scope + { + return Err(self.error(CodegenErrorType::InvalidAsyncComprehension)); + } - // if the element expression contains await, but the context doesn't allow for async, - // then we continue on here with is_async=false and will produce a syntax once the await is hit + // Check if this comprehension should be inlined (PEP 709) + let is_inlined = self.is_inlined_comprehension_context(comprehension_type); + // async comprehensions are allowed in various contexts: + // - list/set/dict comprehensions in async functions (or nested within) + // - always for generator expressions let is_async_list_set_dict_comprehension = comprehension_type != ComprehensionType::Generator - && (has_an_async_gen || element_contains_await) // does it have to be async? (uses await or async for) - && prev_ctx.func == FunctionContext::AsyncFunction; // is it allowed to be async? (in an async function) + && (has_an_async_gen || element_contains_await) + && prev_ctx.in_async_scope; let is_async_generator_comprehension = comprehension_type == ComprehensionType::Generator && (has_an_async_gen || element_contains_await); - // since one is for generators, and one for not generators, they should never both be true debug_assert!(!(is_async_list_set_dict_comprehension && is_async_generator_comprehension)); let is_async = is_async_list_set_dict_comprehension || is_async_generator_comprehension; + // We must have at least one generator: + assert!(!generators.is_empty()); + + if is_inlined { + // PEP 709: Inlined comprehension - compile inline without new scope + return self.compile_inlined_comprehension( + init_collection, + generators, + compile_element, + has_an_async_gen, + ); + } + + // Non-inlined path: create a new code object (generator expressions, etc.) self.ctx = CompileContext { loop_data: None, in_class: prev_ctx.in_class, @@ -5484,11 +6302,11 @@ impl Compiler { } else { FunctionContext::Function }, + // Inherit in_async_scope from parent - nested async comprehensions are allowed + // if we're anywhere inside an async function + in_async_scope: prev_ctx.in_async_scope || is_async, }; - // We must have at least one generator: - assert!(!generators.is_empty()); - let flags = bytecode::CodeFlags::NEW_LOCALS | bytecode::CodeFlags::IS_OPTIMIZED; let flags = if is_async { flags | bytecode::CodeFlags::IS_COROUTINE @@ -5518,8 +6336,6 @@ impl Compiler { let loop_block = self.new_block(); let after_block = self.new_block(); - // emit!(self, Instruction::SetupLoop); - if loop_labels.is_empty() { // Load iterator onto stack (passed as first argument): emit!(self, Instruction::LoadFast(arg0)); @@ -5535,26 +6351,26 @@ impl Compiler { } } - loop_labels.push((loop_block, after_block)); + loop_labels.push((loop_block, after_block, generator.is_async)); self.switch_to_block(loop_block); if generator.is_async { - emit!( - self, - Instruction::SetupExcept { - handler: after_block, - } - ); emit!(self, Instruction::GetANext); + + let current_depth = (init_collection.is_some() as u32) + + u32::try_from(loop_labels.len()).unwrap() + + 1; + self.push_fblock_with_handler( + FBlockType::AsyncComprehensionGenerator, + loop_block, + after_block, + Some(after_block), + current_depth, + false, + )?; self.emit_load_const(ConstantData::None); - emit!(self, Instruction::YieldFrom); - emit!( - self, - Instruction::Resume { - arg: bytecode::ResumeType::AfterAwait as u32 - } - ); + self.compile_yield_from_sequence(true)?; self.compile_store(&generator.target)?; - emit!(self, Instruction::PopBlock); + self.pop_fblock(FBlockType::AsyncComprehensionGenerator); } else { emit!( self, @@ -5573,14 +6389,13 @@ impl Compiler { compile_element(self)?; - for (loop_block, after_block) in loop_labels.iter().rev().copied() { - // Repeat: + for (loop_block, after_block, is_async) in loop_labels.iter().rev().copied() { emit!(self, Instruction::Jump { target: loop_block }); - // End of for loop: self.switch_to_block(after_block); - if has_an_async_gen { + if is_async { emit!(self, Instruction::EndAsyncFor); + emit!(self, Instruction::PopTop); } } @@ -5588,10 +6403,8 @@ impl Compiler { self.emit_load_const(ConstantData::None) } - // Return freshly filled list: self.emit_return_value(); - // Fetch code for listcomp function: let code = self.exit_scope(); self.ctx = prev_ctx; @@ -5603,7 +6416,8 @@ impl Compiler { self.compile_expression(&generators[0].iter)?; // Get iterator / turn item into an iterator - if has_an_async_gen { + // Use is_async from the first generator, not has_an_async_gen which covers ALL generators + if generators[0].is_async { emit!(self, Instruction::GetAIter); } else { emit!(self, Instruction::GetIter); @@ -5612,20 +6426,229 @@ impl Compiler { // Call just created function: emit!(self, Instruction::CallFunctionPositional { nargs: 1 }); if is_async_list_set_dict_comprehension { - // async, but not a generator and not an async for - // in this case, we end up with an awaitable - // that evaluates to the list/set/dict, so here we add an await emit!(self, Instruction::GetAwaitable); self.emit_load_const(ConstantData::None); - emit!(self, Instruction::YieldFrom); + self.compile_yield_from_sequence(true)?; + } + + Ok(()) + } + + /// Collect variable names from an assignment target expression + fn collect_target_names(&self, target: &Expr, names: &mut Vec) { + match target { + Expr::Name(name) => { + let name_str = name.id.to_string(); + if !names.contains(&name_str) { + names.push(name_str); + } + } + Expr::Tuple(tuple) => { + for elt in &tuple.elts { + self.collect_target_names(elt, names); + } + } + Expr::List(list) => { + for elt in &list.elts { + self.collect_target_names(elt, names); + } + } + Expr::Starred(starred) => { + self.collect_target_names(&starred.value, names); + } + _ => { + // Other targets (attribute, subscript) don't bind local names + } + } + } + + /// Compile an inlined comprehension (PEP 709) + /// This generates bytecode inline without creating a new code object + fn compile_inlined_comprehension( + &mut self, + init_collection: Option, + generators: &[Comprehension], + compile_element: &dyn Fn(&mut Self) -> CompileResult<()>, + _has_an_async_gen: bool, + ) -> CompileResult<()> { + // PEP 709: Consume the comprehension's sub_table (but we won't use it as a separate scope) + // We need to consume it to keep sub_tables in sync with AST traversal order. + // The symbols are already merged into parent scope by analyze_symbol_table. + let _comp_table = self + .symbol_table_stack + .last_mut() + .expect("no current symbol table") + .sub_tables + .remove(0); + + // Collect local variables that need to be saved/restored + // These are variables bound in the comprehension (iteration vars from targets) + let mut pushed_locals: Vec = Vec::new(); + for generator in generators { + self.collect_target_names(&generator.target, &mut pushed_locals); + } + + // Step 1: Compile the outermost iterator + self.compile_expression(&generators[0].iter)?; + // Use is_async from the first generator, not has_an_async_gen which covers ALL generators + if generators[0].is_async { + emit!(self, Instruction::GetAIter); + } else { + emit!(self, Instruction::GetIter); + } + + // Step 2: Save local variables that will be shadowed by the comprehension + for name in &pushed_locals { + let idx = self.varname(name)?; + emit!(self, Instruction::LoadFastAndClear(idx)); + } + + // Step 3: SWAP iterator to TOS (above saved locals) + if !pushed_locals.is_empty() { + emit!( + self, + Instruction::Swap { + index: u32::try_from(pushed_locals.len() + 1).unwrap() + } + ); + } + + // Step 4: Create the collection (list/set/dict) + // For generator expressions, init_collection is None + if let Some(init_collection) = init_collection { + self._emit(init_collection, OpArg(0), BlockIdx::NULL); + // SWAP to get iterator on top + emit!(self, Instruction::Swap { index: 2 }); + } + + // Set up exception handler for cleanup on exception + let cleanup_block = self.new_block(); + let end_block = self.new_block(); + + if !pushed_locals.is_empty() { + // Calculate stack depth for exception handler + // Stack: [saved_locals..., collection?, iterator] + let depth = self.handler_stack_depth() + + u32::try_from(pushed_locals.len()).unwrap() + + init_collection.is_some() as u32 + + 1; + self.push_fblock_with_handler( + FBlockType::TryExcept, + cleanup_block, + end_block, + Some(cleanup_block), + depth, + false, + )?; + } + + // Step 5: Compile the comprehension loop(s) + let mut loop_labels = vec![]; + for (i, generator) in generators.iter().enumerate() { + let loop_block = self.new_block(); + let after_block = self.new_block(); + + if i > 0 { + // For nested loops, compile the iterator expression + self.compile_expression(&generator.iter)?; + if generator.is_async { + emit!(self, Instruction::GetAIter); + } else { + emit!(self, Instruction::GetIter); + } + } + + loop_labels.push((loop_block, after_block, generator.is_async)); + self.switch_to_block(loop_block); + + if generator.is_async { + emit!(self, Instruction::GetANext); + self.emit_load_const(ConstantData::None); + self.compile_yield_from_sequence(true)?; + self.compile_store(&generator.target)?; + } else { + emit!( + self, + Instruction::ForIter { + target: after_block, + } + ); + self.compile_store(&generator.target)?; + } + + // Evaluate the if conditions + for if_condition in &generator.ifs { + self.compile_jump_if(if_condition, false, loop_block)?; + } + } + + // Step 6: Compile the element expression and append to collection + compile_element(self)?; + + // Step 7: Close all loops + for (loop_block, after_block, is_async) in loop_labels.iter().rev().copied() { + emit!(self, Instruction::Jump { target: loop_block }); + self.switch_to_block(after_block); + if is_async { + emit!(self, Instruction::EndAsyncFor); + } + // Pop the iterator + emit!(self, Instruction::PopTop); + } + + // Step 8: Clean up - restore saved locals + if !pushed_locals.is_empty() { + self.pop_fblock(FBlockType::TryExcept); + + // Normal path: jump past cleanup + emit!(self, Instruction::Jump { target: end_block }); + + // Exception cleanup path + self.switch_to_block(cleanup_block); + // Stack: [saved_locals..., collection, exception] + // Swap to get collection out from under exception + emit!(self, Instruction::Swap { index: 2 }); + emit!(self, Instruction::PopTop); // Pop incomplete collection + + // Restore locals + emit!( + self, + Instruction::Swap { + index: u32::try_from(pushed_locals.len() + 1).unwrap() + } + ); + for name in pushed_locals.iter().rev() { + let idx = self.varname(name)?; + emit!(self, Instruction::StoreFast(idx)); + } + // Re-raise the exception + emit!( + self, + Instruction::Raise { + kind: bytecode::RaiseKind::ReraiseFromStack + } + ); + + // Normal end path + self.switch_to_block(end_block); + } + + // SWAP result to TOS (above saved locals) + if !pushed_locals.is_empty() { emit!( self, - Instruction::Resume { - arg: bytecode::ResumeType::AfterAwait as u32 + Instruction::Swap { + index: u32::try_from(pushed_locals.len() + 1).unwrap() } ); } + // Restore saved locals + for name in pushed_locals.iter().rev() { + let idx = self.varname(name)?; + emit!(self, Instruction::StoreFast(idx)); + } + Ok(()) } @@ -5656,12 +6679,14 @@ impl Compiler { let source = self.source_file.to_source_code(); let location = source.source_location(range.start(), PositionEncoding::Utf8); let end_location = source.source_location(range.end(), PositionEncoding::Utf8); + let except_handler = self.current_except_handler(); self.current_block().instructions.push(ir::InstructionInfo { instr, arg, target, location, end_location, + except_handler, }); } @@ -5709,6 +6734,204 @@ impl Compiler { self.code_stack.last_mut().expect("no code on stack") } + /// Compile break or continue statement with proper fblock cleanup. + /// compiler_break, compiler_continue + /// This handles unwinding through With blocks and exception handlers. + fn compile_break_continue( + &mut self, + range: ruff_text_size::TextRange, + is_break: bool, + ) -> CompileResult<()> { + // unwind_fblock_stack + // We need to unwind fblocks and compile cleanup code. For FinallyTry blocks, + // we need to compile the finally body inline, but we must temporarily pop + // the fblock so that nested break/continue in the finally body don't see it. + + // First, find the loop + let code = self.current_code_info(); + let mut loop_idx = None; + let mut is_for_loop = false; + + for i in (0..code.fblock.len()).rev() { + match code.fblock[i].fb_type { + FBlockType::WhileLoop => { + loop_idx = Some(i); + is_for_loop = false; + break; + } + FBlockType::ForLoop => { + loop_idx = Some(i); + is_for_loop = true; + break; + } + FBlockType::ExceptionGroupHandler => { + return Err( + self.error_ranged(CodegenErrorType::BreakContinueReturnInExceptStar, range) + ); + } + _ => {} + } + } + + let Some(loop_idx) = loop_idx else { + if is_break { + return Err(self.error_ranged(CodegenErrorType::InvalidBreak, range)); + } else { + return Err(self.error_ranged(CodegenErrorType::InvalidContinue, range)); + } + }; + + let loop_block = code.fblock[loop_idx].fb_block; + let exit_block = code.fblock[loop_idx].fb_exit; + + // Collect the fblocks we need to unwind through, from top down to (but not including) the loop + #[derive(Clone)] + enum UnwindAction { + With { + is_async: bool, + }, + HandlerCleanup, + FinallyTry { + body: Vec, + fblock_idx: usize, + }, + FinallyEnd, + PopValue, // Pop return value when continue/break cancels a return + } + let mut unwind_actions = Vec::new(); + + { + let code = self.current_code_info(); + for i in (loop_idx + 1..code.fblock.len()).rev() { + match code.fblock[i].fb_type { + FBlockType::With => { + unwind_actions.push(UnwindAction::With { is_async: false }); + } + FBlockType::AsyncWith => { + unwind_actions.push(UnwindAction::With { is_async: true }); + } + FBlockType::HandlerCleanup => { + unwind_actions.push(UnwindAction::HandlerCleanup); + } + FBlockType::FinallyTry => { + // Need to execute finally body before break/continue + if let FBlockDatum::FinallyBody(ref body) = code.fblock[i].fb_datum { + unwind_actions.push(UnwindAction::FinallyTry { + body: body.clone(), + fblock_idx: i, + }); + } + } + FBlockType::FinallyEnd => { + // Inside finally block reached via exception - need to pop exception + unwind_actions.push(UnwindAction::FinallyEnd); + } + FBlockType::PopValue => { + // Pop the return value that was saved on stack + unwind_actions.push(UnwindAction::PopValue); + } + _ => {} + } + } + } + + // Emit cleanup for each fblock + for action in unwind_actions { + match action { + UnwindAction::With { is_async } => { + // compiler_call_exit_with_nones + self.emit_load_const(ConstantData::None); + self.emit_load_const(ConstantData::None); + self.emit_load_const(ConstantData::None); + emit!(self, Instruction::CallFunctionPositional { nargs: 3 }); + + if is_async { + emit!(self, Instruction::GetAwaitable); + self.emit_load_const(ConstantData::None); + self.compile_yield_from_sequence(true)?; + } + + emit!(self, Instruction::PopTop); + } + UnwindAction::HandlerCleanup => { + emit!(self, Instruction::PopException); + } + UnwindAction::FinallyTry { body, fblock_idx } => { + // compile finally body inline + // Temporarily pop the FinallyTry fblock so nested break/continue + // in the finally body won't see it again. + let code = self.current_code_info(); + let saved_fblock = code.fblock.remove(fblock_idx); + + self.compile_statements(&body)?; + + // Restore the fblock (though this break/continue will jump away, + // this keeps the fblock stack consistent for error checking) + let code = self.current_code_info(); + code.fblock.insert(fblock_idx, saved_fblock); + } + UnwindAction::FinallyEnd => { + // Stack when in FinallyEnd: [..., prev_exc, exc] + // Note: No lasti here - it's only pushed for cleanup handler exceptions + // We need to pop: exc, prev_exc (via PopException) + emit!(self, Instruction::PopTop); // exc + emit!(self, Instruction::PopException); // prev_exc is restored + } + UnwindAction::PopValue => { + // Pop the return value - continue/break cancels the pending return + emit!(self, Instruction::PopTop); + } + } + } + + // For break in a for loop, pop the iterator + if is_break && is_for_loop { + emit!(self, Instruction::PopTop); + } + + // Jump to target + if is_break { + emit!(self, Instruction::Break { target: exit_block }); + } else { + emit!(self, Instruction::Continue { target: loop_block }); + } + + Ok(()) + } + + /// Calculate the current exception handler stack depth. + /// CPython calculates this based on the SETUP_FINALLY/SETUP_CLEANUP stack depth. + fn handler_stack_depth(&self) -> u32 { + let code = match self.code_stack.last() { + Some(c) => c, + None => return 0, + }; + let mut depth = 0u32; + for fblock in &code.fblock { + match fblock.fb_type { + FBlockType::ForLoop => depth += 1, + FBlockType::With | FBlockType::AsyncWith => depth += 1, + // HandlerCleanup does NOT add to stack depth - it only tracks + // cleanup code for named exception handlers. The stack item + // (prev_exc) is already counted by ExceptionHandler. + // FBlockType::HandlerCleanup => depth += 1, + // inside exception handler, prev_exc is on stack + FBlockType::ExceptionHandler => depth += 1, + // ExceptionGroupHandler: inside except* handler path + // Stack has [prev_exc, orig, list, rest] - add 4 for these + FBlockType::ExceptionGroupHandler => depth += 4, + // FinallyEnd: inside finally exception path + // Stack has [prev_exc, exc] - add 2 for these (no lasti at this level) + FBlockType::FinallyEnd => depth += 2, + // PopValue: preserving a return value on stack during inline finally + // The return value adds 1 to the stack depth + FBlockType::PopValue => depth += 1, + _ => {} + } + } + depth + } + fn current_block(&mut self) -> &mut ir::Block { let info = self.current_code_info(); &mut info.blocks[info.current_block] @@ -5777,6 +7000,11 @@ impl Compiler { match expr { Expr::Await(_) => self.found = true, + // Note: We do NOT check for async comprehensions here. + // Async list/set/dict comprehensions are handled by compile_comprehension + // which already awaits the result. A generator expression containing + // an async comprehension as its element does NOT become an async generator, + // because the async comprehension is awaited when evaluating the element. _ => walk_expr(self, expr), } } @@ -5787,6 +7015,24 @@ impl Compiler { visitor.found } + /// Check if any of the generators (except the first one's iter) contains an await expression. + /// The first generator's iter is evaluated outside the comprehension scope. + fn generators_contain_await(generators: &[Comprehension]) -> bool { + for (i, generator) in generators.iter().enumerate() { + // First generator's iter is evaluated outside the comprehension + if i > 0 && Self::contains_await(&generator.iter) { + return true; + } + // Check ifs in all generators + for if_expr in &generator.ifs { + if Self::contains_await(if_expr) { + return true; + } + } + } + false + } + fn compile_expr_fstring(&mut self, fstring: &ExprFString) -> CompileResult<()> { let fstring = &fstring.value; for part in fstring { @@ -5872,9 +7118,8 @@ impl Compiler { self.emit_load_const(ConstantData::Str { value: text.into() }); element_count += 1; - // Match CPython behavior: If debug text is present, apply repr conversion. - // if no `format_spec` specified. - // See: https://github.com/python/cpython/blob/f61afca262d3a0aa6a8a501db0b1936c60858e35/Parser/action_helpers.c#L1456 + // If debug text is present, apply repr conversion when no `format_spec` specified. + // See action_helpers.c: fstring_find_expr_replacement if matches!( (conversion, &fstring_expr.format_spec), (ConvertValueOparg::None, None) @@ -6280,15 +7525,16 @@ if (True and False) or (False and True): fn test_nested_double_async_with() { assert_dis_snapshot!(compile_exec( "\ -for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')): - with self.subTest(type=type(stop_exc)): - try: - async with egg(): - raise stop_exc - except Exception as ex: - self.assertIs(ex, stop_exc) - else: - self.fail(f'{stop_exc} was suppressed') +async def test(): + for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')): + with self.subTest(type=type(stop_exc)): + try: + async with egg(): + raise stop_exc + except Exception as ex: + self.assertIs(ex, stop_exc) + else: + self.fail(f'{stop_exc} was suppressed') " )); } diff --git a/crates/codegen/src/error.rs b/crates/codegen/src/error.rs index 459ba8e33b5..5bd7c7ecaf0 100644 --- a/crates/codegen/src/error.rs +++ b/crates/codegen/src/error.rs @@ -76,6 +76,9 @@ pub enum CodegenErrorType { InvalidYield, InvalidYieldFrom, InvalidAwait, + InvalidAsyncFor, + InvalidAsyncWith, + InvalidAsyncComprehension, AsyncYieldFrom, AsyncReturnValue, InvalidFuturePlacement, @@ -113,6 +116,14 @@ impl fmt::Display for CodegenErrorType { InvalidYield => write!(f, "'yield' outside function"), InvalidYieldFrom => write!(f, "'yield from' outside function"), InvalidAwait => write!(f, "'await' outside async function"), + InvalidAsyncFor => write!(f, "'async for' outside async function"), + InvalidAsyncWith => write!(f, "'async with' outside async function"), + InvalidAsyncComprehension => { + write!( + f, + "asynchronous comprehension outside of an asynchronous function" + ) + } AsyncYieldFrom => write!(f, "'yield from' inside async function"), AsyncReturnValue => { write!(f, "'return' with value inside async generator") diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 31ee8d8b230..6cc22b438f5 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -4,9 +4,11 @@ use crate::{IndexMap, IndexSet, error::InternalError}; use rustpython_compiler_core::{ OneIndexed, SourceLocation, bytecode::{ - CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, InstrDisplayContext, Instruction, - Label, OpArg, PyCodeLocationInfoKind, + CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, ExceptionTableEntry, + InstrDisplayContext, Instruction, Label, OpArg, PyCodeLocationInfoKind, + encode_exception_table, }, + varint::{write_signed_varint, write_varint}, }; /// Metadata for a code unit @@ -88,6 +90,18 @@ pub struct InstructionInfo { pub target: BlockIdx, pub location: SourceLocation, pub end_location: SourceLocation, + pub except_handler: Option, +} + +/// Exception handler information for an instruction +#[derive(Debug, Clone)] +pub struct ExceptHandlerInfo { + /// Block to jump to when exception occurs + pub handler_block: BlockIdx, + /// Stack depth at handler entry + pub stack_depth: u32, + /// Whether to push lasti before exception + pub preserve_lasti: bool, } // spell-checker:ignore petgraph @@ -176,12 +190,19 @@ impl CodeInfo { let mut locations = Vec::new(); let mut block_to_offset = vec![Label(0); blocks.len()]; + // block_to_index: maps block idx to instruction index (for exception table) + // This is the index into the final instructions array, including EXTENDED_ARG + let mut block_to_index = vec![0u32; blocks.len()]; loop { let mut num_instructions = 0; for (idx, block) in iter_blocks(&blocks) { block_to_offset[idx.idx()] = Label(num_instructions as u32); + // block_to_index uses the same value as block_to_offset but as u32 + // because lasti in frame.rs is the index into instructions array + // and instructions array index == byte offset (each instruction is 1 CodeUnit) + block_to_index[idx.idx()] = num_instructions as u32; for instr in &block.instructions { - num_instructions += instr.arg.instr_size() + num_instructions += instr.arg.instr_size(); } } @@ -228,6 +249,9 @@ impl CodeInfo { opts.debug_ranges, ); + // Generate exception table before moving source_path + let exceptiontable = generate_exception_table(&blocks, &block_to_index); + Ok(CodeObject { flags, posonlyarg_count, @@ -248,7 +272,7 @@ impl CodeInfo { freevars: freevar_cache.into_iter().collect(), cell2arg, linetable, - exceptiontable: Box::new([]), // TODO: Generate actual exception table + exceptiontable, }) } @@ -305,12 +329,24 @@ impl CodeInfo { start_depths[0] = 0; stack.push(BlockIdx(0)); const DEBUG: bool = false; - 'process_blocks: while let Some(block) = stack.pop() { - let mut depth = start_depths[block.idx()]; + // Global iteration limit as safety guard + // The algorithm is monotonic (depths only increase), so it should converge quickly. + // Max iterations = blocks * max_possible_depth_increases per block + let max_iterations = self.blocks.len() * 100; + let mut iterations = 0usize; + 'process_blocks: while let Some(block_idx) = stack.pop() { + iterations += 1; + if iterations > max_iterations { + // Safety guard: should never happen in valid code + // Return error instead of silently breaking to avoid underestimated stack depth + return Err(InternalError::StackOverflow); + } + let idx = block_idx.idx(); + let mut depth = start_depths[idx]; if DEBUG { - eprintln!("===BLOCK {}===", block.0); + eprintln!("===BLOCK {}===", block_idx.0); } - let block = &self.blocks[block]; + let block = &self.blocks[block_idx]; for ins in &block.instructions { let instr = &ins.instr; let effect = instr.stack_effect(ins.arg, false); @@ -336,15 +372,8 @@ impl CodeInfo { if new_depth > maxdepth { maxdepth = new_depth } - // we don't want to worry about Break/Continue, they use unwinding to jump to - // their targets and as such the stack size is taken care of in frame.rs by setting - // it back to the level it was at when SetupLoop was run - if ins.target != BlockIdx::NULL - && !matches!( - instr, - Instruction::Continue { .. } | Instruction::Break { .. } - ) - { + // Process target blocks for branching instructions + if ins.target != BlockIdx::NULL { let effect = instr.stack_effect(ins.arg, true); let target_depth = depth.checked_add_signed(effect).ok_or({ if effect < 0 { @@ -358,6 +387,35 @@ impl CodeInfo { } stackdepth_push(&mut stack, &mut start_depths, ins.target, target_depth); } + // Process exception handler blocks + // When exception occurs, stack is unwound to handler.stack_depth, then: + // - If preserve_lasti: push lasti (+1) + // - Push exception (+1) + // - Handler block starts with PUSH_EXC_INFO as its first instruction + // So the starting depth for the handler block (BEFORE PUSH_EXC_INFO) is: + // handler.stack_depth + preserve_lasti + 1 (exc) + // PUSH_EXC_INFO will then add +1 when the block is processed + if let Some(ref handler) = ins.except_handler { + let handler_depth = handler.stack_depth + 1 + (handler.preserve_lasti as u32); // +1 for exception, +1 for lasti if preserve_lasti + if DEBUG { + eprintln!( + " HANDLER: block={} depth={} (base={} lasti={})", + handler.handler_block.0, + handler_depth, + handler.stack_depth, + handler.preserve_lasti + ); + } + if handler_depth > maxdepth { + maxdepth = handler_depth; + } + stackdepth_push( + &mut stack, + &mut start_depths, + handler.handler_block, + handler_depth, + ); + } depth = new_depth; if instr.unconditional_branch() { continue 'process_blocks; @@ -401,8 +459,10 @@ fn stackdepth_push( target: BlockIdx, depth: u32, ) { - let block_depth = &mut start_depths[target.idx()]; - if *block_depth == u32::MAX || depth > *block_depth { + let idx = target.idx(); + let block_depth = &mut start_depths[idx]; + if depth > *block_depth || *block_depth == u32::MAX { + // Found a path with higher depth (or first visit): update max and queue *block_depth = depth; stack.push(target); } @@ -420,7 +480,7 @@ fn iter_blocks(blocks: &[Block]) -> impl Iterator + ' }) } -/// Generate CPython 3.11+ format linetable from source locations +/// Generate Python 3.11+ format linetable from source locations fn generate_linetable( locations: &[(SourceLocation, SourceLocation)], first_line: i32, @@ -540,27 +600,77 @@ fn generate_linetable( linetable.into_boxed_slice() } -/// Write a variable-length unsigned integer (6-bit chunks) -/// Returns the number of bytes written -fn write_varint(buf: &mut Vec, mut val: u32) -> usize { - let start_len = buf.len(); - while val >= 64 { - buf.push(0x40 | (val & 0x3f) as u8); - val >>= 6; +/// Generate Python 3.11+ exception table from instruction handler info +fn generate_exception_table(blocks: &[Block], block_to_index: &[u32]) -> Box<[u8]> { + let mut entries: Vec = Vec::new(); + let mut current_entry: Option<(ExceptHandlerInfo, u32)> = None; // (handler_info, start_index) + let mut instr_index = 0u32; + + // Iterate through all instructions in block order + // instr_index is the index into the final instructions array (including EXTENDED_ARG) + // This matches how frame.rs uses lasti + for (_, block) in iter_blocks(blocks) { + for instr in &block.instructions { + // instr_size includes EXTENDED_ARG instructions + let instr_size = instr.arg.instr_size() as u32; + + match (¤t_entry, &instr.except_handler) { + // No current entry, no handler - nothing to do + (None, None) => {} + + // No current entry, handler starts - begin new entry + (None, Some(handler)) => { + current_entry = Some((handler.clone(), instr_index)); + } + + // Current entry exists, same handler - continue + (Some((curr_handler, _)), Some(handler)) + if curr_handler.handler_block == handler.handler_block + && curr_handler.stack_depth == handler.stack_depth + && curr_handler.preserve_lasti == handler.preserve_lasti => {} + + // Current entry exists, different handler - finish current, start new + (Some((curr_handler, start)), Some(handler)) => { + let target_index = block_to_index[curr_handler.handler_block.idx()]; + entries.push(ExceptionTableEntry::new( + *start, + instr_index, + target_index, + curr_handler.stack_depth as u16, + curr_handler.preserve_lasti, + )); + current_entry = Some((handler.clone(), instr_index)); + } + + // Current entry exists, no handler - finish current entry + (Some((curr_handler, start)), None) => { + let target_index = block_to_index[curr_handler.handler_block.idx()]; + entries.push(ExceptionTableEntry::new( + *start, + instr_index, + target_index, + curr_handler.stack_depth as u16, + curr_handler.preserve_lasti, + )); + current_entry = None; + } + } + + instr_index += instr_size; // Account for EXTENDED_ARG instructions + } + } + + // Finish any remaining entry + if let Some((curr_handler, start)) = current_entry { + let target_index = block_to_index[curr_handler.handler_block.idx()]; + entries.push(ExceptionTableEntry::new( + start, + instr_index, + target_index, + curr_handler.stack_depth as u16, + curr_handler.preserve_lasti, + )); } - buf.push(val as u8); - buf.len() - start_len -} -/// Write a variable-length signed integer -/// Returns the number of bytes written -fn write_signed_varint(buf: &mut Vec, val: i32) -> usize { - let uval = if val < 0 { - // (unsigned int)(-val) has an undefined behavior for INT_MIN - // So we use (0 - val as u32) to handle it correctly - ((0u32.wrapping_sub(val as u32)) << 1) | 1 - } else { - (val as u32) << 1 - }; - write_varint(buf, uval) + encode_exception_table(&entries) } diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index 6cd7d4d523f..679c5ebade4 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap @@ -1,86 +1,141 @@ --- source: crates/codegen/src/compile.rs -expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\")" +expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\")" --- - 1 0 SETUP_LOOP - 1 LOAD_NAME_ANY (0, StopIteration) - 2 LOAD_CONST ("spam") - 3 CALL_FUNCTION_POSITIONAL(1) - 4 LOAD_NAME_ANY (1, StopAsyncIteration) - 5 LOAD_CONST ("ham") - 6 CALL_FUNCTION_POSITIONAL(1) - 7 BUILD_TUPLE (2) - 8 GET_ITER - >> 9 FOR_ITER (71) - 10 STORE_LOCAL (2, stop_exc) + 3 0 LOAD_CONST (): 1 0 RESUME (0) - 2 11 LOAD_NAME_ANY (3, self) - 12 LOAD_METHOD (4, subTest) - 13 LOAD_NAME_ANY (5, type) - 14 LOAD_NAME_ANY (2, stop_exc) - 15 CALL_FUNCTION_POSITIONAL(1) - 16 LOAD_CONST (("type")) - 17 CALL_METHOD_KEYWORD (1) - 18 SETUP_WITH (68) - 19 POP_TOP + 2 1 LOAD_GLOBAL (0, StopIteration) + 2 LOAD_CONST ("spam") + 3 CALL (1) + 4 LOAD_GLOBAL (1, StopAsyncIteration) + 5 LOAD_CONST ("ham") + 6 CALL (1) + 7 BUILD_TUPLE (2) + 8 GET_ITER + >> 9 FOR_ITER (123) + 10 STORE_FAST (0, stop_exc) - 3 20 SETUP_EXCEPT (42) + 3 11 LOAD_GLOBAL (2, self) + 12 LOAD_METHOD (3, subTest) + 13 LOAD_GLOBAL (4, type) + 14 LOAD_FAST (0, stop_exc) + 15 CALL (1) + 16 LOAD_CONST (("type")) + 17 CALL_METHOD_KW (1) + 18 BEFORE_WITH + 19 POP_TOP - 4 21 LOAD_NAME_ANY (6, egg) - 22 CALL_FUNCTION_POSITIONAL(0) - 23 BEFORE_ASYNC_WITH - 24 GET_AWAITABLE - 25 LOAD_CONST (None) - 26 YIELD_FROM - 27 RESUME (3) - 28 SETUP_ASYNC_WITH (34) - 29 POP_TOP + 5 20 LOAD_GLOBAL (5, egg) + 21 CALL (0) + 22 BEFORE_ASYNC_WITH + 23 GET_AWAITABLE + 24 LOAD_CONST (None) + >> 25 SEND (30) + 26 YIELD_VALUE (1) + 27 RESUME (3) + 28 JUMP (25) + 29 CLEANUP_THROW + >> 30 END_SEND + 31 POP_TOP - 5 30 LOAD_NAME_ANY (2, stop_exc) - 31 RAISE (Raise) + 6 32 LOAD_FAST (0, stop_exc) + 33 RAISE_VARARGS (Raise) - 4 32 POP_BLOCK - 33 ENTER_FINALLY - >> 34 WITH_CLEANUP_START - 35 GET_AWAITABLE - 36 LOAD_CONST (None) - 37 YIELD_FROM - 38 RESUME (3) - 39 WITH_CLEANUP_FINISH - 40 POP_BLOCK - 41 JUMP (58) - >> 42 COPY (1) + 5 34 LOAD_CONST (None) + 35 LOAD_CONST (None) + 36 LOAD_CONST (None) + 37 CALL (3) + 38 GET_AWAITABLE + 39 LOAD_CONST (None) + >> 40 SEND (45) + 41 YIELD_VALUE (1) + 42 RESUME (3) + 43 JUMP (40) + 44 CLEANUP_THROW + >> 45 END_SEND + 46 POP_TOP + 47 JUMP (69) + 48 PUSH_EXC_INFO + 49 WITH_EXCEPT_START + 50 GET_AWAITABLE + 51 LOAD_CONST (None) + >> 52 SEND (57) + 53 YIELD_VALUE (1) + 54 RESUME (3) + 55 JUMP (52) + 56 CLEANUP_THROW + >> 57 END_SEND + 58 TO_BOOL + 59 POP_JUMP_IF_TRUE (61) + 60 RERAISE (2) + >> 61 POP_TOP + 62 POP_EXCEPT + 63 POP_TOP + 64 POP_TOP + 65 JUMP (69) + 66 COPY (3) + 67 POP_EXCEPT + 68 RERAISE (1) + >> 69 JUMP (95) + 70 PUSH_EXC_INFO + 71 COPY (1) - 6 43 LOAD_NAME_ANY (7, Exception) - 44 JUMP_IF_NOT_EXC_MATCH(57) - 45 STORE_LOCAL (8, ex) + 7 72 LOAD_GLOBAL (6, Exception) + 73 JUMP_IF_NOT_EXC_MATCH(91) + 74 STORE_FAST (1, ex) - 7 46 LOAD_NAME_ANY (3, self) - 47 LOAD_METHOD (9, assertIs) - 48 LOAD_NAME_ANY (8, ex) - 49 LOAD_NAME_ANY (2, stop_exc) - 50 CALL_METHOD_POSITIONAL(2) - 51 POP_TOP - 52 POP_EXCEPTION - 53 LOAD_CONST (None) - 54 STORE_LOCAL (8, ex) - 55 DELETE_LOCAL (8, ex) - 56 JUMP (66) - >> 57 RAISE (Reraise) + 8 75 LOAD_GLOBAL (2, self) + 76 LOAD_METHOD (7, assertIs) + 77 LOAD_FAST (1, ex) + 78 LOAD_FAST (0, stop_exc) + 79 CALL_METHOD (2) + 80 POP_TOP + 81 JUMP (86) + 82 LOAD_CONST (None) + 83 STORE_FAST (1, ex) + 84 DELETE_FAST (1, ex) + 85 RAISE_VARARGS (ReraiseFromStack) + >> 86 POP_EXCEPT + 87 LOAD_CONST (None) + 88 STORE_FAST (1, ex) + 89 DELETE_FAST (1, ex) + 90 JUMP (103) + >> 91 RAISE_VARARGS (ReraiseFromStack) + 92 COPY (3) + 93 POP_EXCEPT + 94 RAISE_VARARGS (ReraiseFromStack) - 9 >> 58 LOAD_NAME_ANY (3, self) - 59 LOAD_METHOD (10, fail) - 60 LOAD_NAME_ANY (2, stop_exc) - 61 FORMAT_SIMPLE - 62 LOAD_CONST (" was suppressed") - 63 BUILD_STRING (2) - 64 CALL_METHOD_POSITIONAL(1) - 65 POP_TOP + 10 >> 95 LOAD_GLOBAL (2, self) + 96 LOAD_METHOD (8, fail) + 97 LOAD_FAST (0, stop_exc) + 98 FORMAT_SIMPLE + 99 LOAD_CONST (" was suppressed") + 100 BUILD_STRING (2) + 101 CALL_METHOD (1) + 102 POP_TOP - 2 >> 66 POP_BLOCK - 67 ENTER_FINALLY - >> 68 WITH_CLEANUP_START - 69 WITH_CLEANUP_FINISH - 70 JUMP (9) - >> 71 POP_BLOCK - 72 RETURN_CONST (None) + 3 >> 103 LOAD_CONST (None) + 104 LOAD_CONST (None) + 105 LOAD_CONST (None) + 106 CALL (3) + 107 POP_TOP + 108 JUMP (122) + 109 PUSH_EXC_INFO + 110 WITH_EXCEPT_START + 111 TO_BOOL + 112 POP_JUMP_IF_TRUE (114) + 113 RERAISE (2) + >> 114 POP_TOP + 115 POP_EXCEPT + 116 POP_TOP + 117 POP_TOP + 118 JUMP (122) + 119 COPY (3) + 120 POP_EXCEPT + 121 RERAISE (1) + >> 122 JUMP (9) + >> 123 RETURN_CONST (None) + + 1 MAKE_FUNCTION + 2 STORE_NAME (0, test) + 3 RETURN_CONST (None) diff --git a/crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__nested_double_async_with.snap deleted file mode 100644 index 589f3210cfa..00000000000 --- a/crates/codegen/src/snapshots/rustpython_compiler_core__compile__tests__nested_double_async_with.snap +++ /dev/null @@ -1,87 +0,0 @@ ---- -source: compiler/src/compile.rs -expression: "compile_exec(\"\\\nfor stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with woohoo():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\")" ---- - 1 0 SetupLoop (69) - 1 LoadNameAny (0, StopIteration) - 2 LoadConst ("spam") - 3 CallFunctionPositional (1) - 4 LoadNameAny (1, StopAsyncIteration) - 5 LoadConst ("ham") - 6 CallFunctionPositional (1) - 7 BuildTuple (2, false) - 8 GetIter - >> 9 ForIter (68) - 10 StoreLocal (2, stop_exc) - - 2 11 LoadNameAny (3, self) - 12 LoadMethod (subTest) - 13 LoadNameAny (5, type) - 14 LoadNameAny (2, stop_exc) - 15 CallFunctionPositional (1) - 16 LoadConst (("type")) - 17 CallMethodKeyword (1) - 18 SetupWith (65) - 19 Pop - - 3 20 SetupExcept (40) - - 4 21 LoadNameAny (6, woohoo) - 22 CallFunctionPositional (0) - 23 BeforeAsyncWith - 24 GetAwaitable - 25 LoadConst (None) - 26 YieldFrom - 27 SetupAsyncWith (33) - 28 Pop - - 5 29 LoadNameAny (2, stop_exc) - 30 Raise (Raise) - - 4 31 PopBlock - 32 EnterFinally - >> 33 WithCleanupStart - 34 GetAwaitable - 35 LoadConst (None) - 36 YieldFrom - 37 WithCleanupFinish - 38 PopBlock - 39 Jump (54) - >> 40 Duplicate - - 6 41 LoadNameAny (7, Exception) - 42 TestOperation (ExceptionMatch) - 43 PopJumpIfFalse (53) - 44 StoreLocal (8, ex) - - 7 45 LoadNameAny (3, self) - 46 LoadMethod (assertIs) - 47 LoadNameAny (8, ex) - 48 LoadNameAny (2, stop_exc) - 49 CallMethodPositional (2) - 50 Pop - 51 PopException - 52 Jump (63) - >> 53 Raise (Reraise) - - 9 >> 54 LoadNameAny (3, self) - 55 LoadMethod (fail) - 56 LoadConst ("") - - 1 57 LoadNameAny (2, stop_exc) - 58 FormatValue (None) - - 9 59 LoadConst (" was suppressed") - 60 BuildString (2) - 61 CallMethodPositional (1) - 62 Pop - - 2 >> 63 PopBlock - 64 EnterFinally - >> 65 WithCleanupStart - 66 WithCleanupFinish - 67 Jump (9) - >> 68 PopBlock - >> 69 LoadConst (None) - 70 ReturnValue - diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index 95f9c0cd400..fe4976bb086 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -55,6 +55,10 @@ pub struct SymbolTable { /// Whether this type param scope can see the parent class scope pub can_see_class_scope: bool, + + /// Whether this comprehension scope should be inlined (PEP 709) + /// True for list/set/dict comprehensions in non-generator expressions + pub comp_inlined: bool, } impl SymbolTable { @@ -70,6 +74,7 @@ impl SymbolTable { needs_class_closure: false, needs_classdict: false, can_see_class_scope: false, + comp_inlined: false, } } @@ -344,6 +349,37 @@ impl SymbolTableAnalyzer { symbol_table.symbols = info.0; + // PEP 709: Merge symbols from inlined comprehensions into parent scope + // Only merge symbols that are actually bound in the comprehension, + // not references to outer scope variables (Free symbols). + const BOUND_FLAGS: SymbolFlags = SymbolFlags::ASSIGNED + .union(SymbolFlags::PARAMETER) + .union(SymbolFlags::ITER) + .union(SymbolFlags::ASSIGNED_IN_COMPREHENSION); + + for sub_table in sub_tables.iter() { + if sub_table.comp_inlined { + for (name, sub_symbol) in &sub_table.symbols { + // Skip the .0 parameter - it's internal to the comprehension + if name == ".0" { + continue; + } + // Only merge symbols that are bound in the comprehension + // Skip Free references to outer scope variables + if !sub_symbol.flags.intersects(BOUND_FLAGS) { + continue; + } + // If the symbol doesn't exist in parent, add it + if !symbol_table.symbols.contains_key(name) { + let mut symbol = sub_symbol.clone(); + // Mark as local in parent scope + symbol.scope = SymbolScope::Local; + symbol_table.symbols.insert(name.clone(), symbol); + } + } + } + } + // Analyze symbols: for symbol in symbol_table.symbols.values_mut() { self.analyze_symbol(symbol, symbol_table.typ, sub_tables)?; @@ -1241,7 +1277,8 @@ impl SymbolTableBuilder { if context == ExpressionContext::IterDefinitionExp { self.in_iter_def_exp = true; } - self.scan_comprehension("genexpr", elt, None, generators, *range)?; + // Generator expression - is_generator = true + self.scan_comprehension("", elt, None, generators, *range, true)?; self.in_iter_def_exp = was_in_iter_def_exp; } Expr::ListComp(ExprListComp { @@ -1254,7 +1291,8 @@ impl SymbolTableBuilder { if context == ExpressionContext::IterDefinitionExp { self.in_iter_def_exp = true; } - self.scan_comprehension("genexpr", elt, None, generators, *range)?; + // List comprehension - is_generator = false (can be inlined) + self.scan_comprehension("", elt, None, generators, *range, false)?; self.in_iter_def_exp = was_in_iter_def_exp; } Expr::SetComp(ExprSetComp { @@ -1267,7 +1305,8 @@ impl SymbolTableBuilder { if context == ExpressionContext::IterDefinitionExp { self.in_iter_def_exp = true; } - self.scan_comprehension("genexpr", elt, None, generators, *range)?; + // Set comprehension - is_generator = false (can be inlined) + self.scan_comprehension("", elt, None, generators, *range, false)?; self.in_iter_def_exp = was_in_iter_def_exp; } Expr::DictComp(ExprDictComp { @@ -1281,7 +1320,8 @@ impl SymbolTableBuilder { if context == ExpressionContext::IterDefinitionExp { self.in_iter_def_exp = true; } - self.scan_comprehension("genexpr", key, Some(value), generators, *range)?; + // Dict comprehension - is_generator = false (can be inlined) + self.scan_comprehension("", key, Some(value), generators, *range, false)?; self.in_iter_def_exp = was_in_iter_def_exp; } Expr::Call(ExprCall { @@ -1451,6 +1491,7 @@ impl SymbolTableBuilder { elt2: Option<&Expr>, generators: &[Comprehension], range: TextRange, + is_generator: bool, ) -> SymbolTableResult { // Comprehensions are compiled as functions, so create a scope for them: self.enter_scope( @@ -1459,6 +1500,21 @@ impl SymbolTableBuilder { self.line_index_start(range), ); + // Mark non-generator comprehensions as inlined (PEP 709) + // inline_comp = entry->ste_comprehension && !entry->ste_generator && !ste->ste_can_see_class_scope + // We check is_generator and can_see_class_scope of parent + let parent_can_see_class = self + .tables + .get(self.tables.len().saturating_sub(2)) + .map(|t| t.can_see_class_scope) + .unwrap_or(false); + if !is_generator + && !parent_can_see_class + && let Some(table) = self.tables.last_mut() + { + table.comp_inlined = true; + } + // Register the passed argument to the generator function as the name ".0" self.register_name(".0", SymbolUsage::Parameter, range)?; diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 7675ef34863..08426287307 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -3,9 +3,10 @@ use crate::{ marshal::MarshalError, + varint::{read_varint, read_varint_with_start, write_varint, write_varint_with_start}, {OneIndexed, SourceLocation}, }; -use alloc::{collections::BTreeSet, fmt}; +use alloc::{collections::BTreeSet, fmt, vec::Vec}; use bitflags::bitflags; use core::{hash, marker::PhantomData, mem, num::NonZeroU8, ops::Deref}; use itertools::Itertools; @@ -13,6 +14,76 @@ use malachite_bigint::BigInt; use num_complex::Complex64; use rustpython_wtf8::{Wtf8, Wtf8Buf}; +/// Exception table entry for zero-cost exception handling +/// Format: (start, size, target, depth<<1|lasti) +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExceptionTableEntry { + /// Start instruction offset (inclusive) + pub start: u32, + /// End instruction offset (exclusive) + pub end: u32, + /// Handler target offset + pub target: u32, + /// Stack depth at handler entry + pub depth: u16, + /// Whether to push lasti before exception + pub push_lasti: bool, +} + +impl ExceptionTableEntry { + pub fn new(start: u32, end: u32, target: u32, depth: u16, push_lasti: bool) -> Self { + Self { + start, + end, + target, + depth, + push_lasti, + } + } +} + +/// Encode exception table entries. +/// Uses 6-bit varint encoding with start marker (MSB) and continuation bit. +pub fn encode_exception_table(entries: &[ExceptionTableEntry]) -> alloc::boxed::Box<[u8]> { + let mut data = Vec::new(); + for entry in entries { + let size = entry.end.saturating_sub(entry.start); + let depth_lasti = ((entry.depth as u32) << 1) | (entry.push_lasti as u32); + + write_varint_with_start(&mut data, entry.start); + write_varint(&mut data, size); + write_varint(&mut data, entry.target); + write_varint(&mut data, depth_lasti); + } + data.into_boxed_slice() +} + +/// Find exception handler for given instruction offset. +pub fn find_exception_handler(table: &[u8], offset: u32) -> Option { + let mut pos = 0; + while pos < table.len() { + let start = read_varint_with_start(table, &mut pos)?; + let size = read_varint(table, &mut pos)?; + let target = read_varint(table, &mut pos)?; + let depth_lasti = read_varint(table, &mut pos)?; + + let end = start + size; + let depth = (depth_lasti >> 1) as u16; + let push_lasti = (depth_lasti & 1) != 0; + + if offset >= start && offset < end { + return Some(ExceptionTableEntry { + start, + end, + target, + depth, + push_lasti, + }); + } + } + None +} + /// Oparg values for [`Instruction::ConvertValue`]. /// /// ## See also @@ -540,9 +611,21 @@ op_arg_enum!( #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] pub enum RaiseKind { - Reraise = 0, + /// Bare `raise` statement with no arguments. + /// Gets the current exception from VM state (topmost_exception). + /// Maps to RAISE_VARARGS with oparg=0. + BareRaise = 0, + /// `raise exc` - exception is on the stack. + /// Maps to RAISE_VARARGS with oparg=1. Raise = 1, + /// `raise exc from cause` - exception and cause are on the stack. + /// Maps to RAISE_VARARGS with oparg=2. RaiseCause = 2, + /// Reraise exception from the stack top. + /// Used in exception handler cleanup blocks (finally, except). + /// Gets exception from stack, not from VM state. + /// Maps to the RERAISE opcode. + ReraiseFromStack = 3, } ); @@ -587,293 +670,449 @@ op_arg_enum!( pub type NameIdx = u32; /// A Single bytecode instruction. +/// Instructions are ordered to match CPython 3.13 opcode numbers exactly. +/// HAVE_ARGUMENT = 44: opcodes 0-43 have no argument, 44+ have arguments. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(u8)] pub enum Instruction { + // ==================== No-argument instructions (opcode < 44) ==================== + // 0: CACHE - placeholder for inline cache (not executed) + Cache, + // 1: BEFORE_ASYNC_WITH BeforeAsyncWith, + // 2: BEFORE_WITH + BeforeWith, + // 3: Reserved (BINARY_OP_INPLACE_ADD_UNICODE in CPython) + Reserved3, + // 4: BINARY_SLICE - not implemented, placeholder + BinarySlice, + // 5: BINARY_SUBSCR + BinarySubscript, + // 6: CHECK_EG_MATCH + CheckEgMatch, + // 7: CHECK_EXC_MATCH + CheckExcMatch, + // 8: CLEANUP_THROW + CleanupThrow, + // 9: DELETE_SUBSCR + DeleteSubscript, + // 10: END_ASYNC_FOR + EndAsyncFor, + // 11: END_FOR - not implemented, placeholder + EndFor, + // 12: END_SEND + EndSend, + // 13: EXIT_INIT_CHECK - not implemented, placeholder + ExitInitCheck, + // 14: FORMAT_SIMPLE + FormatSimple, + // 15: FORMAT_WITH_SPEC + FormatWithSpec, + // 16: GET_AITER + GetAIter, + // 17: RESERVED + Reserved17, + // 18: GET_ANEXT + GetANext, + // 19: GET_ITER + GetIter, + // 20: GET_LEN + GetLen, + // 21: GET_YIELD_FROM_ITER - not implemented, placeholder + GetYieldFromIter, + // 22: INTERPRETER_EXIT - not implemented, placeholder + InterpreterExit, + // 23: LOAD_ASSERTION_ERROR - not implemented, placeholder + LoadAssertionError, + // 24: LOAD_BUILD_CLASS + LoadBuildClass, + // 25: LOAD_LOCALS - not implemented, placeholder + LoadLocals, + // 26: MAKE_FUNCTION + MakeFunction, + // 27: MATCH_KEYS + MatchKeys, + // 28: MATCH_MAPPING + MatchMapping, + // 29: MATCH_SEQUENCE + MatchSequence, + // 30: NOP + Nop, + // 31: POP_EXCEPT + PopException, + // 32: POP_TOP + PopTop, + // 33: PUSH_EXC_INFO + PushExcInfo, + // 34: PUSH_NULL - not implemented, placeholder + PushNull, + // 35: RETURN_GENERATOR - not implemented, placeholder + ReturnGenerator, + // 36: RETURN_VALUE + ReturnValue, + // 37: SETUP_ANNOTATIONS + SetupAnnotation, + // 38: STORE_SLICE - not implemented, placeholder + StoreSlice, + // 39: STORE_SUBSCR + StoreSubscript, + // 40: TO_BOOL + ToBool, + // 41: UNARY_INVERT - placeholder (RustPython uses UnaryOperation) + UnaryInvert, + // 42: UNARY_NEGATIVE - placeholder + UnaryNegative, + // 43: UNARY_NOT - placeholder + UnaryNot, + // ==================== With-argument instructions (opcode >= 44) ==================== + // 44: WITH_EXCEPT_START + WithExceptStart, + // 45: BINARY_OP BinaryOp { op: Arg, }, - BinarySubscript, - Break { - target: Arg.)(?P=a))(c)') - self.assertRaises(re.error, re.compile, r'(a)b(?<=(a)(?(2)b|x))(c)') - self.assertRaises(re.error, re.compile, r'(a)b(?<=(.)(?<=\2))(c)') + self.assertRaises(re.PatternError, re.compile, r'(a)b(?<=(.)\2)(c)') + self.assertRaises(re.PatternError, re.compile, r'(a)b(?<=(?P.)(?P=a))(c)') + self.assertRaises(re.PatternError, re.compile, r'(a)b(?<=(a)(?(2)b|x))(c)') + self.assertRaises(re.PatternError, re.compile, r'(a)b(?<=(.)(?<=\2))(c)') def test_ignore_case(self): self.assertEqual(re.match("abc", "ABC", re.I).group(0), "ABC") @@ -975,6 +1141,39 @@ def test_ignore_case_set(self): self.assertTrue(re.match(br'[19a]', b'a', re.I)) self.assertTrue(re.match(br'[19a]', b'A', re.I)) self.assertTrue(re.match(br'[19A]', b'a', re.I)) + self.assertTrue(re.match(r'[19\xc7]', '\xc7', re.I)) + self.assertTrue(re.match(r'[19\xc7]', '\xe7', re.I)) + self.assertTrue(re.match(r'[19\xe7]', '\xc7', re.I)) + self.assertTrue(re.match(r'[19\xe7]', '\xe7', re.I)) + self.assertTrue(re.match(r'[19\u0400]', '\u0400', re.I)) + self.assertTrue(re.match(r'[19\u0400]', '\u0450', re.I)) + self.assertTrue(re.match(r'[19\u0450]', '\u0400', re.I)) + self.assertTrue(re.match(r'[19\u0450]', '\u0450', re.I)) + self.assertTrue(re.match(r'[19\U00010400]', '\U00010400', re.I)) + self.assertTrue(re.match(r'[19\U00010400]', '\U00010428', re.I)) + self.assertTrue(re.match(r'[19\U00010428]', '\U00010400', re.I)) + self.assertTrue(re.match(r'[19\U00010428]', '\U00010428', re.I)) + + self.assertTrue(re.match(br'[19A]', b'A', re.I)) + self.assertTrue(re.match(br'[19a]', b'a', re.I)) + self.assertTrue(re.match(br'[19a]', b'A', re.I)) + self.assertTrue(re.match(br'[19A]', b'a', re.I)) + self.assertTrue(re.match(r'[19A]', 'A', re.I|re.A)) + self.assertTrue(re.match(r'[19a]', 'a', re.I|re.A)) + self.assertTrue(re.match(r'[19a]', 'A', re.I|re.A)) + self.assertTrue(re.match(r'[19A]', 'a', re.I|re.A)) + self.assertTrue(re.match(r'[19\xc7]', '\xc7', re.I|re.A)) + self.assertIsNone(re.match(r'[19\xc7]', '\xe7', re.I|re.A)) + self.assertIsNone(re.match(r'[19\xe7]', '\xc7', re.I|re.A)) + self.assertTrue(re.match(r'[19\xe7]', '\xe7', re.I|re.A)) + self.assertTrue(re.match(r'[19\u0400]', '\u0400', re.I|re.A)) + self.assertIsNone(re.match(r'[19\u0400]', '\u0450', re.I|re.A)) + self.assertIsNone(re.match(r'[19\u0450]', '\u0400', re.I|re.A)) + self.assertTrue(re.match(r'[19\u0450]', '\u0450', re.I|re.A)) + self.assertTrue(re.match(r'[19\U00010400]', '\U00010400', re.I|re.A)) + self.assertIsNone(re.match(r'[19\U00010400]', '\U00010428', re.I|re.A)) + self.assertIsNone(re.match(r'[19\U00010428]', '\U00010400', re.I|re.A)) + self.assertTrue(re.match(r'[19\U00010428]', '\U00010428', re.I|re.A)) # Two different characters have the same lowercase. assert 'K'.lower() == '\u212a'.lower() == 'k' # 'K' @@ -1011,8 +1210,10 @@ def test_ignore_case_range(self): self.assertTrue(re.match(br'[9-a]', b'_', re.I)) self.assertIsNone(re.match(br'[9-A]', b'_', re.I)) self.assertTrue(re.match(r'[\xc0-\xde]', '\xd7', re.I)) + self.assertTrue(re.match(r'[\xc0-\xde]', '\xe7', re.I)) self.assertIsNone(re.match(r'[\xc0-\xde]', '\xf7', re.I)) self.assertTrue(re.match(r'[\xe0-\xfe]', '\xf7', re.I)) + self.assertTrue(re.match(r'[\xe0-\xfe]', '\xc7', re.I)) self.assertIsNone(re.match(r'[\xe0-\xfe]', '\xd7', re.I)) self.assertTrue(re.match(r'[\u0430-\u045f]', '\u0450', re.I)) self.assertTrue(re.match(r'[\u0430-\u045f]', '\u0400', re.I)) @@ -1023,6 +1224,26 @@ def test_ignore_case_range(self): self.assertTrue(re.match(r'[\U00010400-\U00010427]', '\U00010428', re.I)) self.assertTrue(re.match(r'[\U00010400-\U00010427]', '\U00010400', re.I)) + self.assertTrue(re.match(r'[\xc0-\xde]', '\xd7', re.I|re.A)) + self.assertIsNone(re.match(r'[\xc0-\xde]', '\xe7', re.I|re.A)) + self.assertTrue(re.match(r'[\xe0-\xfe]', '\xf7', re.I|re.A)) + self.assertIsNone(re.match(r'[\xe0-\xfe]', '\xc7', re.I|re.A)) + self.assertTrue(re.match(r'[\u0430-\u045f]', '\u0450', re.I|re.A)) + self.assertIsNone(re.match(r'[\u0430-\u045f]', '\u0400', re.I|re.A)) + self.assertIsNone(re.match(r'[\u0400-\u042f]', '\u0450', re.I|re.A)) + self.assertTrue(re.match(r'[\u0400-\u042f]', '\u0400', re.I|re.A)) + self.assertTrue(re.match(r'[\U00010428-\U0001044f]', '\U00010428', re.I|re.A)) + self.assertIsNone(re.match(r'[\U00010428-\U0001044f]', '\U00010400', re.I|re.A)) + self.assertIsNone(re.match(r'[\U00010400-\U00010427]', '\U00010428', re.I|re.A)) + self.assertTrue(re.match(r'[\U00010400-\U00010427]', '\U00010400', re.I|re.A)) + + self.assertTrue(re.match(r'[N-\x7f]', 'A', re.I|re.A)) + self.assertTrue(re.match(r'[n-\x7f]', 'Z', re.I|re.A)) + self.assertTrue(re.match(r'[N-\uffff]', 'A', re.I|re.A)) + self.assertTrue(re.match(r'[n-\uffff]', 'Z', re.I|re.A)) + self.assertTrue(re.match(r'[N-\U00010000]', 'A', re.I|re.A)) + self.assertTrue(re.match(r'[n-\U00010000]', 'Z', re.I|re.A)) + # Two different characters have the same lowercase. assert 'K'.lower() == '\u212a'.lower() == 'k' # 'K' self.assertTrue(re.match(r'[J-M]', '\u212a', re.I)) @@ -1060,47 +1281,76 @@ def test_not_literal(self): def test_possible_set_operations(self): s = bytes(range(128)).decode() - with self.assertWarns(FutureWarning): + with self.assertWarnsRegex(FutureWarning, 'Possible set difference') as w: p = re.compile(r'[0-9--1]') + self.assertEqual(w.filename, __file__) self.assertEqual(p.findall(s), list('-./0123456789')) + with self.assertWarnsRegex(FutureWarning, 'Possible set difference') as w: + self.assertEqual(re.findall(r'[0-9--2]', s), list('-./0123456789')) + self.assertEqual(w.filename, __file__) + self.assertEqual(re.findall(r'[--1]', s), list('-./01')) - with self.assertWarns(FutureWarning): + + with self.assertWarnsRegex(FutureWarning, 'Possible set difference') as w: p = re.compile(r'[%--1]') + self.assertEqual(w.filename, __file__) self.assertEqual(p.findall(s), list("%&'()*+,-1")) - with self.assertWarns(FutureWarning): + + with self.assertWarnsRegex(FutureWarning, 'Possible set difference ') as w: p = re.compile(r'[%--]') + self.assertEqual(w.filename, __file__) self.assertEqual(p.findall(s), list("%&'()*+,-")) - with self.assertWarns(FutureWarning): + with self.assertWarnsRegex(FutureWarning, 'Possible set intersection ') as w: p = re.compile(r'[0-9&&1]') + self.assertEqual(w.filename, __file__) self.assertEqual(p.findall(s), list('&0123456789')) - with self.assertWarns(FutureWarning): + with self.assertWarnsRegex(FutureWarning, 'Possible set intersection ') as w: + self.assertEqual(re.findall(r'[0-8&&1]', s), list('&012345678')) + self.assertEqual(w.filename, __file__) + + with self.assertWarnsRegex(FutureWarning, 'Possible set intersection ') as w: p = re.compile(r'[\d&&1]') + self.assertEqual(w.filename, __file__) self.assertEqual(p.findall(s), list('&0123456789')) + self.assertEqual(re.findall(r'[&&1]', s), list('&1')) - with self.assertWarns(FutureWarning): + with self.assertWarnsRegex(FutureWarning, 'Possible set union ') as w: p = re.compile(r'[0-9||a]') + self.assertEqual(w.filename, __file__) self.assertEqual(p.findall(s), list('0123456789a|')) - with self.assertWarns(FutureWarning): + + with self.assertWarnsRegex(FutureWarning, 'Possible set union ') as w: p = re.compile(r'[\d||a]') + self.assertEqual(w.filename, __file__) self.assertEqual(p.findall(s), list('0123456789a|')) + self.assertEqual(re.findall(r'[||1]', s), list('1|')) - with self.assertWarns(FutureWarning): + with self.assertWarnsRegex(FutureWarning, 'Possible set symmetric difference ') as w: p = re.compile(r'[0-9~~1]') + self.assertEqual(w.filename, __file__) self.assertEqual(p.findall(s), list('0123456789~')) - with self.assertWarns(FutureWarning): + + with self.assertWarnsRegex(FutureWarning, 'Possible set symmetric difference ') as w: p = re.compile(r'[\d~~1]') + self.assertEqual(w.filename, __file__) self.assertEqual(p.findall(s), list('0123456789~')) + self.assertEqual(re.findall(r'[~~1]', s), list('1~')) - with self.assertWarns(FutureWarning): + with self.assertWarnsRegex(FutureWarning, 'Possible nested set ') as w: p = re.compile(r'[[0-9]|]') + self.assertEqual(w.filename, __file__) self.assertEqual(p.findall(s), list('0123456789[]')) + with self.assertWarnsRegex(FutureWarning, 'Possible nested set ') as w: + self.assertEqual(re.findall(r'[[0-8]|]', s), list('012345678[]')) + self.assertEqual(w.filename, __file__) - with self.assertWarns(FutureWarning): + with self.assertWarnsRegex(FutureWarning, 'Possible nested set ') as w: p = re.compile(r'[[:digit:]|]') + self.assertEqual(w.filename, __file__) self.assertEqual(p.findall(s), list(':[]dgit')) def test_search_coverage(self): @@ -1175,8 +1425,7 @@ def test_pickling(self): # current pickle expects the _compile() reconstructor in re module from re import _compile - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_copying(self): import copy p = re.compile(r'(?P\d+)(?:\.(?P\d*))?') @@ -1267,8 +1516,8 @@ def test_sre_byte_literals(self): self.assertTrue(re.match((r"\x%02x" % i).encode(), bytes([i]))) self.assertTrue(re.match((r"\x%02x0" % i).encode(), bytes([i])+b"0")) self.assertTrue(re.match((r"\x%02xz" % i).encode(), bytes([i])+b"z")) - self.assertRaises(re.error, re.compile, br"\u1234") - self.assertRaises(re.error, re.compile, br"\U00012345") + self.assertRaises(re.PatternError, re.compile, br"\u1234") + self.assertRaises(re.PatternError, re.compile, br"\U00012345") self.assertTrue(re.match(br"\0", b"\000")) self.assertTrue(re.match(br"\08", b"\0008")) self.assertTrue(re.match(br"\01", b"\001")) @@ -1290,8 +1539,8 @@ def test_sre_byte_class_literals(self): self.assertTrue(re.match((r"[\x%02x]" % i).encode(), bytes([i]))) self.assertTrue(re.match((r"[\x%02x0]" % i).encode(), bytes([i]))) self.assertTrue(re.match((r"[\x%02xz]" % i).encode(), bytes([i]))) - self.assertRaises(re.error, re.compile, br"[\u1234]") - self.assertRaises(re.error, re.compile, br"[\U00012345]") + self.assertRaises(re.PatternError, re.compile, br"[\u1234]") + self.assertRaises(re.PatternError, re.compile, br"[\U00012345]") self.checkPatternError(br"[\567]", r'octal escape value \567 outside of ' r'range 0-0o377', 1) @@ -1484,8 +1733,7 @@ def test_bug_817234(self): self.assertEqual(next(iter).span(), (4, 4)) self.assertRaises(StopIteration, next, iter) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_6561(self): # '\d' should match characters in Unicode category 'Nd' # (Number, Decimal Digit), but not those in 'Nl' (Number, @@ -1507,10 +1755,12 @@ def test_bug_6561(self): for x in not_decimal_digits: self.assertIsNone(re.match(r'^\d$', x)) + @unittest.expectedFailure # TODO: RUSTPYTHON a = array.array(typecode)\n ValueError: bad typecode (must be b, B, u, h, H, i, I, l, L, q, Q, f or d) + @warnings_helper.ignore_warnings(category=DeprecationWarning) # gh-80480 array('u') def test_empty_array(self): # SF buf 1647541 import array - for typecode in 'bBuhHiIlLfd': + for typecode in 'bBhuwHiIlLfd': a = array.array(typecode) self.assertIsNone(re.compile(b"bla").match(a)) self.assertEqual(re.compile(b"").match(a).groups(), ()) @@ -1625,11 +1875,11 @@ def test_ascii_and_unicode_flag(self): self.assertIsNone(pat.match(b'\xe0')) # Incompatibilities self.assertRaises(ValueError, re.compile, br'\w', re.UNICODE) - self.assertRaises(re.error, re.compile, br'(?u)\w') + self.assertRaises(re.PatternError, re.compile, br'(?u)\w') self.assertRaises(ValueError, re.compile, r'\w', re.UNICODE | re.ASCII) self.assertRaises(ValueError, re.compile, r'(?u)\w', re.ASCII) self.assertRaises(ValueError, re.compile, r'(?a)\w', re.UNICODE) - self.assertRaises(re.error, re.compile, r'(?au)\w') + self.assertRaises(re.PatternError, re.compile, r'(?au)\w') def test_locale_flag(self): enc = locale.getpreferredencoding() @@ -1670,11 +1920,11 @@ def test_locale_flag(self): self.assertIsNone(pat.match(bletter)) # Incompatibilities self.assertRaises(ValueError, re.compile, '', re.LOCALE) - self.assertRaises(re.error, re.compile, '(?L)') + self.assertRaises(re.PatternError, re.compile, '(?L)') self.assertRaises(ValueError, re.compile, b'', re.LOCALE | re.ASCII) self.assertRaises(ValueError, re.compile, b'(?L)', re.ASCII) self.assertRaises(ValueError, re.compile, b'(?a)', re.LOCALE) - self.assertRaises(re.error, re.compile, b'(?aL)') + self.assertRaises(re.PatternError, re.compile, b'(?aL)') def test_scoped_flags(self): self.assertTrue(re.match(r'(?i:a)b', 'Ab')) @@ -1854,8 +2104,7 @@ def test_issue17998(self): self.assertEqual(re.compile(pattern, re.S).findall(b'xyz'), [b'xyz'], msg=pattern) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_match_repr(self): for string in '[abracadabra]', S('[abracadabra]'): m = re.search(r'(.+)(.*?)\1', string) @@ -1935,6 +2184,7 @@ def test_bug_20998(self): # with ignore case. self.assertEqual(re.fullmatch('[a-c]+', 'ABC', re.I).span(), (0, 3)) + @unittest.expectedFailure # TODO: RUSTPYTHON; self.assertTrue(re.match(b'\xc5', b'\xe5', re.L|re.I))\n AssertionError: None is not true @unittest.skipIf( is_emscripten or is_wasi, "musl libc issue on Emscripten/WASI, bpo-46390" @@ -2012,7 +2262,7 @@ def test_locale_compiled(self): self.assertIsNone(p4.match(b'\xc5\xc5')) def test_error(self): - with self.assertRaises(re.error) as cm: + with self.assertRaises(re.PatternError) as cm: re.compile('(\u20ac))') err = cm.exception self.assertIsInstance(err.pattern, str) @@ -2024,14 +2274,14 @@ def test_error(self): self.assertIn(' at position 3', str(err)) self.assertNotIn(' at position 3', err.msg) # Bytes pattern - with self.assertRaises(re.error) as cm: + with self.assertRaises(re.PatternError) as cm: re.compile(b'(\xa4))') err = cm.exception self.assertIsInstance(err.pattern, bytes) self.assertEqual(err.pattern, b'(\xa4))') self.assertEqual(err.pos, 3) # Multiline pattern - with self.assertRaises(re.error) as cm: + with self.assertRaises(re.PatternError) as cm: re.compile(""" ( abc @@ -2231,24 +2481,24 @@ def test_bug_40736(self): with self.assertRaisesRegex(TypeError, "got 'type'"): re.search("x*", type) - @unittest.skip("TODO: RUSTPYTHON: flaky, improve perf") + # gh-117594: The test is not slow by itself, but it relies on + # the absolute computation time and can fail on very slow computers. + @unittest.skip('TODO: RUSTPYTHON; flaky, improve perf') @requires_resource('cpu') def test_search_anchor_at_beginning(self): s = 'x'*10**7 - start = time.perf_counter() - for p in r'\Ay', r'^y': - self.assertIsNone(re.search(p, s)) - self.assertEqual(re.split(p, s), [s]) - self.assertEqual(re.findall(p, s), []) - self.assertEqual(list(re.finditer(p, s)), []) - self.assertEqual(re.sub(p, '', s), s) - t = time.perf_counter() - start + with CPUStopwatch() as stopwatch: + for p in r'\Ay', r'^y': + self.assertIsNone(re.search(p, s)) + self.assertEqual(re.split(p, s), [s]) + self.assertEqual(re.findall(p, s), []) + self.assertEqual(list(re.finditer(p, s)), []) + self.assertEqual(re.sub(p, '', s), s) # Without optimization it takes 1 second on my computer. # With optimization -- 0.0003 seconds. - self.assertLess(t, 0.2) + self.assertLess(stopwatch.seconds, 0.1) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_possessive_quantifiers(self): """Test Possessive Quantifiers Test quantifiers of the form @+ for some repetition operator @, @@ -2288,8 +2538,7 @@ def test_possessive_quantifiers(self): self.assertIsNone(re.match("^x{}+$", "xxx")) self.assertTrue(re.match("^x{}+$", "x{}")) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_fullmatch_possessive_quantifiers(self): self.assertTrue(re.fullmatch(r'a++', 'a')) self.assertTrue(re.fullmatch(r'a*+', 'a')) @@ -2342,8 +2591,7 @@ def test_atomic_grouping(self): self.assertIsNone(re.match(r'(?>x)++x', 'xxx')) self.assertIsNone(re.match(r'(?>x++)x', 'xxx')) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_fullmatch_atomic_grouping(self): self.assertTrue(re.fullmatch(r'(?>a+)', 'a')) self.assertTrue(re.fullmatch(r'(?>a*)', 'a')) @@ -2382,39 +2630,12 @@ def test_findall_atomic_grouping(self): self.assertEqual(re.findall(r'(?>(?:ab)?)', 'ababc'), ['ab', 'ab', '', '']) self.assertEqual(re.findall(r'(?>(?:ab){1,3})', 'ababc'), ['abab']) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bug_gh91616(self): self.assertTrue(re.fullmatch(r'(?s:(?>.*?\.).*)\Z', "a.txt")) # reproducer self.assertTrue(re.fullmatch(r'(?s:(?=(?P.*?\.))(?P=g0).*)\Z', "a.txt")) - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_template_function_and_flag_is_deprecated(self): - with self.assertWarns(DeprecationWarning) as cm: - template_re1 = re.template(r'a') - self.assertIn('re.template()', str(cm.warning)) - self.assertIn('is deprecated', str(cm.warning)) - self.assertIn('function', str(cm.warning)) - self.assertNotIn('flag', str(cm.warning)) - - with self.assertWarns(DeprecationWarning) as cm: - # we deliberately use more flags here to test that that still - # triggers the warning - # if paranoid, we could test multiple different combinations, - # but it's probably not worth it - template_re2 = re.compile(r'a', flags=re.TEMPLATE|re.UNICODE) - self.assertIn('re.TEMPLATE', str(cm.warning)) - self.assertIn('is deprecated', str(cm.warning)) - self.assertIn('flag', str(cm.warning)) - self.assertNotIn('function', str(cm.warning)) - - # while deprecated, is should still function - self.assertEqual(template_re1, template_re2) - self.assertTrue(template_re1.match('ahoy')) - self.assertFalse(template_re1.match('nope')) - - def test_bug_gh106052(self): + def test_bug_gh100061(self): # gh-100061 self.assertEqual(re.match('(?>(?:.(?!D))+)', 'ABCDE').span(), (0, 2)) self.assertEqual(re.match('(?:.(?!D))++', 'ABCDE').span(), (0, 2)) @@ -2434,8 +2655,13 @@ def test_bug_gh106052(self): self.assertEqual(re.match("(?>(?:ab?c){1,3})", "aca").span(), (0, 2)) self.assertEqual(re.match("(?:ab?c){1,3}+", "aca").span(), (0, 2)) - # TODO: RUSTPYTHON - @unittest.skipUnless(sys.platform == 'linux', 'multiprocessing related issue') + @unittest.expectedFailure # TODO: RUSTPYTHON; self.assertEqual(re.match('((x)|y|z){3}+', 'xyz').groups(), ('z', 'x'))\n AssertionError: Tuples differ: ('x', 'x') != ('z', 'x') + def test_bug_gh101955(self): + # Possessive quantifier with nested alternative with capture groups + self.assertEqual(re.match('((x)|y|z)*+', 'xyz').groups(), ('z', 'x')) + self.assertEqual(re.match('((x)|y|z){3}+', 'xyz').groups(), ('z', 'x')) + self.assertEqual(re.match('((x)|y|z){3,}+', 'xyz').groups(), ('z', 'x')) + @unittest.skipIf(multiprocessing is None, 'test requires multiprocessing') def test_regression_gh94675(self): pattern = re.compile(r'(?<=[({}])(((//[^\n]*)?[\n])([\000-\040])*)*' @@ -2456,6 +2682,54 @@ def test_regression_gh94675(self): p.terminate() p.join() + def test_fail(self): + self.assertEqual(re.search(r'12(?!)|3', '123')[0], '3') + + def test_character_set_any(self): + # The union of complementary character sets matches any character + # and is equivalent to "(?s:.)". + s = '1x\n' + for p in r'[\s\S]', r'[\d\D]', r'[\w\W]', r'[\S\s]', r'\s|\S': + with self.subTest(pattern=p): + self.assertEqual(re.findall(p, s), list(s)) + self.assertEqual(re.fullmatch('(?:' + p + ')+', s).group(), s) + + def test_character_set_none(self): + # Negation of the union of complementary character sets does not match + # any character. + s = '1x\n' + for p in r'[^\s\S]', r'[^\d\D]', r'[^\w\W]', r'[^\S\s]': + with self.subTest(pattern=p): + self.assertIsNone(re.search(p, s)) + self.assertIsNone(re.search('(?s:.)' + p, s)) + + def check_interrupt(self, pattern, string, maxcount): + class Interrupt(Exception): + pass + p = re.compile(pattern) + for n in range(maxcount): + try: + p._fail_after(n, Interrupt) + p.match(string) + return n + except Interrupt: + pass + finally: + p._fail_after(-1, None) + + @unittest.skipUnless(hasattr(re.Pattern, '_fail_after'), 'requires debug build') + def test_memory_leaks(self): + self.check_interrupt(r'(.)*:', 'abc:', 100) + self.check_interrupt(r'([^:])*?:', 'abc:', 100) + self.check_interrupt(r'([^:])*+:', 'abc:', 100) + self.check_interrupt(r'(.){2,4}:', 'abc:', 100) + self.check_interrupt(r'([^:]){2,4}?:', 'abc:', 100) + self.check_interrupt(r'([^:]){2,4}+:', 'abc:', 100) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_template_function_and_flag_is_deprecated(self): + return super().test_template_function_and_flag_is_deprecated() + def get_debug_out(pat): with captured_stdout() as out: @@ -2590,8 +2864,7 @@ def test_inline_flags(self): self.check('(?i)pattern', "re.compile('(?i)pattern', re.IGNORECASE)") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unknown_flags(self): self.check_flags('random pattern', 0x123000, "re.compile('random pattern', 0x123000)") @@ -2626,8 +2899,6 @@ def test_long_pattern(self): self.assertEqual(r[:30], "re.compile('Very long long lon") self.assertEqual(r[-16:], ", re.IGNORECASE)") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_flags_repr(self): self.assertEqual(repr(re.I), "re.IGNORECASE") self.assertEqual(repr(re.I|re.S|re.X), @@ -2636,11 +2907,11 @@ def test_flags_repr(self): "re.IGNORECASE|re.DOTALL|re.VERBOSE|0x100000") self.assertEqual( repr(~re.I), - "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.DOTALL|re.VERBOSE|re.TEMPLATE|re.DEBUG") + "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.DOTALL|re.VERBOSE|re.DEBUG|0x1") self.assertEqual(repr(~(re.I|re.S|re.X)), - "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.TEMPLATE|re.DEBUG") + "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.DEBUG|0x1") self.assertEqual(repr(~(re.I|re.S|re.X|(1<<20))), - "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.TEMPLATE|re.DEBUG|0xffe00") + "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.DEBUG|0xffe01") class ImplementationTest(unittest.TestCase): @@ -2681,8 +2952,7 @@ def test_disallow_instantiation(self): pat = re.compile("") check_disallow_instantiation(self, type(pat.scanner(""))) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_deprecated_modules(self): deprecated = { 'sre_compile': ['compile', 'error', @@ -2813,7 +3083,7 @@ def test_re_tests(self): with self.subTest(pattern=pattern, string=s): if outcome == SYNTAX_ERROR: # Expected a syntax error - with self.assertRaises(re.error): + with self.assertRaises(re.PatternError): re.compile(pattern) continue diff --git a/crates/sre_engine/src/constants.rs b/crates/sre_engine/src/constants.rs index 9fe792ce17d..d90c08cb374 100644 --- a/crates/sre_engine/src/constants.rs +++ b/crates/sre_engine/src/constants.rs @@ -3,17 +3,17 @@ * * regular expression matching engine * - * NOTE: This file is generated by sre_constants.py. If you need - * to change anything in here, edit sre_constants.py and run it. + * Auto-generated by scripts/generate_sre_constants.py from + * Lib/re/_constants.py. * * Copyright (c) 1997-2001 by Secret Labs AB. All rights reserved. * - * See the _sre.c file for information on usage and redistribution. + * See the sre.c file for information on usage and redistribution. */ use bitflags::bitflags; -pub const SRE_MAGIC: usize = 20221023; +pub const SRE_MAGIC: usize = 20230612; #[derive(num_enum::TryFromPrimitive, Debug, PartialEq, Eq)] #[repr(u32)] #[allow(non_camel_case_types, clippy::upper_case_acronyms)] @@ -62,6 +62,7 @@ pub enum SreOpcode { NOT_LITERAL_UNI_IGNORE = 41, RANGE_UNI_IGNORE = 42, } + #[derive(num_enum::TryFromPrimitive, Debug, PartialEq, Eq)] #[repr(u32)] #[allow(non_camel_case_types, clippy::upper_case_acronyms)] @@ -79,6 +80,7 @@ pub enum SreAtCode { UNI_BOUNDARY = 10, UNI_NON_BOUNDARY = 11, } + #[derive(num_enum::TryFromPrimitive, Debug)] #[repr(u32)] #[allow(non_camel_case_types, clippy::upper_case_acronyms)] @@ -102,10 +104,10 @@ pub enum SreCatCode { UNI_LINEBREAK = 16, UNI_NOT_LINEBREAK = 17, } + bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct SreFlag: u16 { - const TEMPLATE = 1; const IGNORECASE = 2; const LOCALE = 4; const MULTILINE = 8; @@ -116,6 +118,7 @@ bitflags! { const ASCII = 256; } } + bitflags! { pub struct SreInfo: u32 { const PREFIX = 1; diff --git a/crates/vm/src/stdlib/sre.rs b/crates/vm/src/stdlib/sre.rs index 33f884e1adc..04ff0695b6b 100644 --- a/crates/vm/src/stdlib/sre.rs +++ b/crates/vm/src/stdlib/sre.rs @@ -554,7 +554,6 @@ mod _sre { #[inline] fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { let flag_names = [ - ("re.TEMPLATE", SreFlag::TEMPLATE), ("re.IGNORECASE", SreFlag::IGNORECASE), ("re.LOCALE", SreFlag::LOCALE), ("re.MULTILINE", SreFlag::MULTILINE), diff --git a/scripts/generate_sre_constants.py b/scripts/generate_sre_constants.py new file mode 100644 index 00000000000..8e4091d2eb9 --- /dev/null +++ b/scripts/generate_sre_constants.py @@ -0,0 +1,139 @@ +#! /usr/bin/env python3 +# This script generates crates/sre_engine/src/constants.rs from Lib/re/_constants.py. + +SCRIPT_NAME = "scripts/generate_sre_constants.py" + + +def update_file(file, content): + try: + with open(file, "r") as fobj: + if fobj.read() == content: + return False + except (OSError, ValueError): + pass + with open(file, "w") as fobj: + fobj.write(content) + return True + + +sre_constants_header = f"""\ +/* + * Secret Labs' Regular Expression Engine + * + * regular expression matching engine + * + * Auto-generated by {SCRIPT_NAME} from + * Lib/re/_constants.py. + * + * Copyright (c) 1997-2001 by Secret Labs AB. All rights reserved. + * + * See the sre.c file for information on usage and redistribution. + */ + +""" + + +def dump_enum(d, enum_name, derives, strip_prefix=""): + """Generate Rust enum definitions from a Python dictionary. + + Args: + d (list): The list containing the enum variants. + enum_name (str): The name of the enum to generate. + derives (str): The derive attributes to include. + strip_prefix (str, optional): A prefix to strip from the variant names. Defaults to "". + + Returns: + list: A list of strings representing the enum definition. + """ + items = sorted(d) + print(f"items is {items}") + content = [f"{derives}\n"] + content.append("#[repr(u32)]\n") + content.append("#[allow(non_camel_case_types, clippy::upper_case_acronyms)]\n") + content.append(f"pub enum {enum_name} {{\n") + for i, item in enumerate(items): + name = str(item).removeprefix(strip_prefix) + content.append(f" {name} = {i},\n") + content.append("}\n\n") + return content + + +def dump_bitflags(d, prefix, derives, struct_name, int_t): + """Generate Rust bitflags definitions from a Python dictionary. + + Args: + d (dict): The dictionary containing the bitflag variants. + prefix (str): The prefix to strip from the variant names. + derives (str): The derive attributes to include. + struct_name (str): The name of the struct to generate. + int_t (str): The integer type to use for the bitflags. + + Returns: + list: A list of strings representing the bitflags definition. + """ + items = [(value, name) for name, value in d.items() if name.startswith(prefix)] + content = ["bitflags! {\n"] + content.append(f"{derives}\n") if derives else None + content.append(f" pub struct {struct_name}: {int_t} {{\n") + for value, name in sorted(items): + name = str(name).removeprefix(prefix) + content.append(f" const {name} = {value};\n") + content.append(" }\n") + content.append("}\n\n") + return content + + +def main( + infile="Lib/re/_constants.py", + outfile_constants="crates/sre_engine/src/constants.rs", +): + ns = {} + with open(infile) as fp: + code = fp.read() + exec(code, ns) + + content = [sre_constants_header] + content.append("use bitflags::bitflags;\n\n") + content.append(f"pub const SRE_MAGIC: usize = {ns['MAGIC']};\n") + content.extend( + dump_enum( + ns["OPCODES"], + "SreOpcode", + "#[derive(num_enum::TryFromPrimitive, Debug, PartialEq, Eq)]", + ) + ) + content.extend( + dump_enum( + ns["ATCODES"], + "SreAtCode", + "#[derive(num_enum::TryFromPrimitive, Debug, PartialEq, Eq)]", + "AT_", + ) + ) + content.extend( + dump_enum( + ns["CHCODES"], + "SreCatCode", + "#[derive(num_enum::TryFromPrimitive, Debug)]", + "CATEGORY_", + ) + ) + + content.extend( + dump_bitflags( + ns, + "SRE_FLAG_", + "#[derive(Debug, PartialEq, Eq, Clone, Copy)]", + "SreFlag", + "u16", + ) + ) + content.extend(dump_bitflags(ns, "SRE_INFO_", "", "SreInfo", "u32")) + + update_file(outfile_constants, "".join(content)) + + +if __name__ == "__main__": + import sys + + main(*sys.argv[1:]) From 75838e567ea74eeb2aa4e45e633047f6326b9939 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Mon, 5 Jan 2026 00:38:04 -0500 Subject: [PATCH 690/819] Update pty + tty + associated libraries - v3.13.10 (#6630) * Update tty + added the test from v3.13.10 * Updated pty.py + test --- Lib/pty.py | 80 +++++++++++------- Lib/test/test_builtin.py | 4 + Lib/test/test_pty.py | 177 ++++++++++++++++++--------------------- Lib/test/test_tty.py | 96 +++++++++++++++++++++ Lib/tty.py | 69 +++++++++++---- 5 files changed, 286 insertions(+), 140 deletions(-) create mode 100644 Lib/test/test_tty.py diff --git a/Lib/pty.py b/Lib/pty.py index 8d8ce40df54..1d97994abef 100644 --- a/Lib/pty.py +++ b/Lib/pty.py @@ -40,6 +40,9 @@ def master_open(): Open a pty master and return the fd, and the filename of the slave end. Deprecated, use openpty() instead.""" + import warnings + warnings.warn("Use pty.openpty() instead.", DeprecationWarning, stacklevel=2) # Remove API in 3.14 + try: master_fd, slave_fd = os.openpty() except (AttributeError, OSError): @@ -69,6 +72,9 @@ def slave_open(tty_name): opened filedescriptor. Deprecated, use openpty() instead.""" + import warnings + warnings.warn("Use pty.openpty() instead.", DeprecationWarning, stacklevel=2) # Remove API in 3.14 + result = os.open(tty_name, os.O_RDWR) try: from fcntl import ioctl, I_PUSH @@ -101,32 +107,14 @@ def fork(): master_fd, slave_fd = openpty() pid = os.fork() if pid == CHILD: - # Establish a new session. - os.setsid() os.close(master_fd) - - # Slave becomes stdin/stdout/stderr of child. - os.dup2(slave_fd, STDIN_FILENO) - os.dup2(slave_fd, STDOUT_FILENO) - os.dup2(slave_fd, STDERR_FILENO) - if slave_fd > STDERR_FILENO: - os.close(slave_fd) - - # Explicitly open the tty to make it become a controlling tty. - tmp_fd = os.open(os.ttyname(STDOUT_FILENO), os.O_RDWR) - os.close(tmp_fd) + os.login_tty(slave_fd) else: os.close(slave_fd) # Parent and child process. return pid, master_fd -def _writen(fd, data): - """Write all the data to a descriptor.""" - while data: - n = os.write(fd, data) - data = data[n:] - def _read(fd): """Default read function.""" return os.read(fd, 1024) @@ -136,9 +124,42 @@ def _copy(master_fd, master_read=_read, stdin_read=_read): Copies pty master -> standard output (master_read) standard input -> pty master (stdin_read)""" - fds = [master_fd, STDIN_FILENO] - while fds: - rfds, _wfds, _xfds = select(fds, [], []) + if os.get_blocking(master_fd): + # If we write more than tty/ndisc is willing to buffer, we may block + # indefinitely. So we set master_fd to non-blocking temporarily during + # the copy operation. + os.set_blocking(master_fd, False) + try: + _copy(master_fd, master_read=master_read, stdin_read=stdin_read) + finally: + # restore blocking mode for backwards compatibility + os.set_blocking(master_fd, True) + return + high_waterlevel = 4096 + stdin_avail = master_fd != STDIN_FILENO + stdout_avail = master_fd != STDOUT_FILENO + i_buf = b'' + o_buf = b'' + while 1: + rfds = [] + wfds = [] + if stdin_avail and len(i_buf) < high_waterlevel: + rfds.append(STDIN_FILENO) + if stdout_avail and len(o_buf) < high_waterlevel: + rfds.append(master_fd) + if stdout_avail and len(o_buf) > 0: + wfds.append(STDOUT_FILENO) + if len(i_buf) > 0: + wfds.append(master_fd) + + rfds, wfds, _xfds = select(rfds, wfds, []) + + if STDOUT_FILENO in wfds: + try: + n = os.write(STDOUT_FILENO, o_buf) + o_buf = o_buf[n:] + except OSError: + stdout_avail = False if master_fd in rfds: # Some OSes signal EOF by returning an empty byte string, @@ -150,19 +171,22 @@ def _copy(master_fd, master_read=_read, stdin_read=_read): if not data: # Reached EOF. return # Assume the child process has exited and is # unreachable, so we clean up. - else: - os.write(STDOUT_FILENO, data) + o_buf += data + + if master_fd in wfds: + n = os.write(master_fd, i_buf) + i_buf = i_buf[n:] - if STDIN_FILENO in rfds: + if stdin_avail and STDIN_FILENO in rfds: data = stdin_read(STDIN_FILENO) if not data: - fds.remove(STDIN_FILENO) + stdin_avail = False else: - _writen(master_fd, data) + i_buf += data def spawn(argv, master_read=_read, stdin_read=_read): """Create a spawned process.""" - if type(argv) == type(''): + if isinstance(argv, str): argv = (argv,) sys.audit('pty.spawn', argv) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 7d8c7a5e016..55585d9ef84 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -2241,6 +2241,7 @@ def child(wpipe): expected = terminal_input.decode(sys.stdin.encoding) # what else? self.assertEqual(input_result, expected) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_input_tty(self): # Test input() functionality when wired to a tty (the code path # is different and invokes GNU readline if available). @@ -2257,17 +2258,20 @@ def skip_if_readline(self): self.skipTest("the readline module is loaded") @unittest.skipUnless(hasattr(sys.stdin, 'detach'), 'TODO: RustPython: requires detach function in TextIOWrapper') + @unittest.expectedFailure # TODO: RUSTPYTHON AssertionError: got 0 lines in pipe but expected 2, child output was: quux def test_input_tty_non_ascii(self): self.skip_if_readline() # Check stdin/stdout encoding is used when invoking PyOS_Readline() self.check_input_tty("prompté", b"quux\xe9", "utf-8") @unittest.skipUnless(hasattr(sys.stdin, 'detach'), 'TODO: RustPython: requires detach function in TextIOWrapper') + @unittest.expectedFailure # TODO: RUSTPYTHON AssertionError: got 0 lines in pipe but expected 2, child output was: quux def test_input_tty_non_ascii_unicode_errors(self): self.skip_if_readline() # Check stdin/stdout error handler is used when invoking PyOS_Readline() self.check_input_tty("prompté", b"quux\xe9", "ascii") + @unittest.expectedFailure # TODO: RUSTPYTHON AssertionError: got 0 lines in pipe but expected 2, child output was: quux def test_input_no_stdout_fileno(self): # Issue #24402: If stdin is the original terminal but stdout.fileno() # fails, do not use the original stdout file descriptor diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index b1c1f6abffb..6d3c47195c6 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -1,10 +1,16 @@ -from test import support -from test.support import verbose, reap_children +import unittest +from test.support import ( + is_android, is_apple_mobile, is_emscripten, is_wasi, reap_children, verbose +) from test.support.import_helper import import_module +from test.support.os_helper import TESTFN, unlink # Skip these tests if termios is not available import_module('termios') +if is_android or is_apple_mobile or is_emscripten or is_wasi: + raise unittest.SkipTest("pty is not available on this platform") + import errno import os import pty @@ -14,21 +20,12 @@ import signal import socket import io # readline -import unittest - -import struct -import fcntl import warnings TEST_STRING_1 = b"I wish to buy a fish license.\n" TEST_STRING_2 = b"For my pet fish, Eric.\n" -try: - _TIOCGWINSZ = tty.TIOCGWINSZ - _TIOCSWINSZ = tty.TIOCSWINSZ - _HAVE_WINSZ = True -except AttributeError: - _HAVE_WINSZ = False +_HAVE_WINSZ = hasattr(tty, "TIOCGWINSZ") and hasattr(tty, "TIOCSWINSZ") if verbose: def debug(msg): @@ -82,90 +79,78 @@ def expectedFailureIfStdinIsTTY(fun): pass return fun -def _get_term_winsz(fd): - s = struct.pack("HHHH", 0, 0, 0, 0) - return fcntl.ioctl(fd, _TIOCGWINSZ, s) -def _set_term_winsz(fd, winsz): - fcntl.ioctl(fd, _TIOCSWINSZ, winsz) +def write_all(fd, data): + written = os.write(fd, data) + if written != len(data): + # gh-73256, gh-110673: It should never happen, but check just in case + raise Exception(f"short write: os.write({fd}, {len(data)} bytes) " + f"wrote {written} bytes") # Marginal testing of pty suite. Cannot do extensive 'do or fail' testing # because pty code is not too portable. class PtyTest(unittest.TestCase): def setUp(self): - old_alarm = signal.signal(signal.SIGALRM, self.handle_sig) - self.addCleanup(signal.signal, signal.SIGALRM, old_alarm) - old_sighup = signal.signal(signal.SIGHUP, self.handle_sighup) self.addCleanup(signal.signal, signal.SIGHUP, old_sighup) - # isatty() and close() can hang on some platforms. Set an alarm - # before running the test to make sure we don't hang forever. - self.addCleanup(signal.alarm, 0) - signal.alarm(10) - - # Save original stdin window size - self.stdin_rows = None - self.stdin_cols = None + # Save original stdin window size. + self.stdin_dim = None if _HAVE_WINSZ: try: - stdin_dim = os.get_terminal_size(pty.STDIN_FILENO) - self.stdin_rows = stdin_dim.lines - self.stdin_cols = stdin_dim.columns - old_stdin_winsz = struct.pack("HHHH", self.stdin_rows, - self.stdin_cols, 0, 0) - self.addCleanup(_set_term_winsz, pty.STDIN_FILENO, old_stdin_winsz) - except OSError: + self.stdin_dim = tty.tcgetwinsize(pty.STDIN_FILENO) + self.addCleanup(tty.tcsetwinsize, pty.STDIN_FILENO, + self.stdin_dim) + except tty.error: pass - def handle_sig(self, sig, frame): - self.fail("isatty hung") - @staticmethod def handle_sighup(signum, frame): pass @expectedFailureIfStdinIsTTY + @unittest.skip('TODO: RUSTPYTHON; "Not runnable. tty.tcgetwinsize" is required to setUp') def test_openpty(self): try: mode = tty.tcgetattr(pty.STDIN_FILENO) except tty.error: - # not a tty or bad/closed fd + # Not a tty or bad/closed fd. debug("tty.tcgetattr(pty.STDIN_FILENO) failed") mode = None - new_stdin_winsz = None - if self.stdin_rows is not None and self.stdin_cols is not None: + new_dim = None + if self.stdin_dim: try: # Modify pty.STDIN_FILENO window size; we need to # check if pty.openpty() is able to set pty slave # window size accordingly. - debug("Setting pty.STDIN_FILENO window size") - debug(f"original size: (rows={self.stdin_rows}, cols={self.stdin_cols})") - target_stdin_rows = self.stdin_rows + 1 - target_stdin_cols = self.stdin_cols + 1 - debug(f"target size: (rows={target_stdin_rows}, cols={target_stdin_cols})") - target_stdin_winsz = struct.pack("HHHH", target_stdin_rows, - target_stdin_cols, 0, 0) - _set_term_winsz(pty.STDIN_FILENO, target_stdin_winsz) + debug("Setting pty.STDIN_FILENO window size.") + debug(f"original size: (row, col) = {self.stdin_dim}") + target_dim = (self.stdin_dim[0] + 1, self.stdin_dim[1] + 1) + debug(f"target size: (row, col) = {target_dim}") + tty.tcsetwinsize(pty.STDIN_FILENO, target_dim) # Were we able to set the window size # of pty.STDIN_FILENO successfully? - new_stdin_winsz = _get_term_winsz(pty.STDIN_FILENO) - self.assertEqual(new_stdin_winsz, target_stdin_winsz, + new_dim = tty.tcgetwinsize(pty.STDIN_FILENO) + self.assertEqual(new_dim, target_dim, "pty.STDIN_FILENO window size unchanged") - except OSError: - warnings.warn("Failed to set pty.STDIN_FILENO window size") + except OSError as e: + logging.getLogger(__name__).warning( + "Failed to set pty.STDIN_FILENO window size.", exc_info=e, + ) pass try: debug("Calling pty.openpty()") try: - master_fd, slave_fd = pty.openpty(mode, new_stdin_winsz) + master_fd, slave_fd, slave_name = pty.openpty(mode, new_dim, + True) except TypeError: master_fd, slave_fd = pty.openpty() - debug(f"Got master_fd '{master_fd}', slave_fd '{slave_fd}'") + slave_name = None + debug(f"Got {master_fd=}, {slave_fd=}, {slave_name=}") except OSError: # " An optional feature could not be imported " ... ? raise unittest.SkipTest("Pseudo-terminals (seemingly) not functional.") @@ -181,8 +166,8 @@ def test_openpty(self): if mode: self.assertEqual(tty.tcgetattr(slave_fd), mode, "openpty() failed to set slave termios") - if new_stdin_winsz: - self.assertEqual(_get_term_winsz(slave_fd), new_stdin_winsz, + if new_dim: + self.assertEqual(tty.tcgetwinsize(slave_fd), new_dim, "openpty() failed to set slave window size") # Ensure the fd is non-blocking in case there's nothing to read. @@ -200,18 +185,18 @@ def test_openpty(self): os.set_blocking(master_fd, blocking) debug("Writing to slave_fd") - os.write(slave_fd, TEST_STRING_1) + write_all(slave_fd, TEST_STRING_1) s1 = _readline(master_fd) self.assertEqual(b'I wish to buy a fish license.\n', normalize_output(s1)) debug("Writing chunked output") - os.write(slave_fd, TEST_STRING_2[:5]) - os.write(slave_fd, TEST_STRING_2[5:]) + write_all(slave_fd, TEST_STRING_2[:5]) + write_all(slave_fd, TEST_STRING_2[5:]) s2 = _readline(master_fd) self.assertEqual(b'For my pet fish, Eric.\n', normalize_output(s2)) - @support.requires_fork() + @unittest.skip('TODO: RUSTPYTHON; "Not runnable. tty.tcgetwinsize" is required to setUp') def test_fork(self): debug("calling pty.fork()") pid, master_fd = pty.fork() @@ -294,6 +279,7 @@ def test_fork(self): ##else: ## raise TestFailed("Read from master_fd did not raise exception") + @unittest.skip('TODO: RUSTPYTHON; AttributeError: module "tty" has no attribute "tcgetwinsize"') def test_master_read(self): # XXX(nnorwitz): this test leaks fds when there is an error. debug("Calling pty.openpty()") @@ -313,8 +299,28 @@ def test_master_read(self): self.assertEqual(data, b"") + @unittest.skip('TODO: RUSTPYTHON; AttributeError: module "tty" has no attribute "tcgetwinsize"') def test_spawn_doesnt_hang(self): - pty.spawn([sys.executable, '-c', 'print("hi there")']) + self.addCleanup(unlink, TESTFN) + with open(TESTFN, 'wb') as f: + STDOUT_FILENO = 1 + dup_stdout = os.dup(STDOUT_FILENO) + os.dup2(f.fileno(), STDOUT_FILENO) + buf = b'' + def master_read(fd): + nonlocal buf + data = os.read(fd, 1024) + buf += data + return data + try: + pty.spawn([sys.executable, '-c', 'print("hi there")'], + master_read) + finally: + os.dup2(dup_stdout, STDOUT_FILENO) + os.close(dup_stdout) + self.assertEqual(buf, b'hi there\r\n') + with open(TESTFN, 'rb') as f: + self.assertEqual(f.read(), b'hi there\r\n') class SmallPtyTests(unittest.TestCase): """These tests don't spawn children or hang.""" @@ -332,8 +338,8 @@ def setUp(self): self.orig_pty_waitpid = pty.waitpid self.fds = [] # A list of file descriptors to close. self.files = [] - self.select_rfds_lengths = [] - self.select_rfds_results = [] + self.select_input = [] + self.select_output = [] self.tcsetattr_mode_setting = None def tearDown(self): @@ -368,11 +374,10 @@ def _socketpair(self): self.files.extend(socketpair) return socketpair - def _mock_select(self, rfds, wfds, xfds, timeout=0): + def _mock_select(self, rfds, wfds, xfds): # This will raise IndexError when no more expected calls exist. - # This ignores the timeout - self.assertEqual(self.select_rfds_lengths.pop(0), len(rfds)) - return self.select_rfds_results.pop(0), [], [] + self.assertEqual((rfds, wfds, xfds), self.select_input.pop(0)) + return self.select_output.pop(0) def _make_mock_fork(self, pid): def mock_fork(): @@ -392,14 +397,16 @@ def test__copy_to_each(self): masters = [s.fileno() for s in socketpair] # Feed data. Smaller than PIPEBUF. These writes will not block. - os.write(masters[1], b'from master') - os.write(write_to_stdin_fd, b'from stdin') + write_all(masters[1], b'from master') + write_all(write_to_stdin_fd, b'from stdin') - # Expect two select calls, the last one will cause IndexError + # Expect three select calls, the last one will cause IndexError pty.select = self._mock_select - self.select_rfds_lengths.append(2) - self.select_rfds_results.append([mock_stdin_fd, masters[0]]) - self.select_rfds_lengths.append(2) + self.select_input.append(([mock_stdin_fd, masters[0]], [], [])) + self.select_output.append(([mock_stdin_fd, masters[0]], [], [])) + self.select_input.append(([mock_stdin_fd, masters[0]], [mock_stdout_fd, masters[0]], [])) + self.select_output.append(([], [mock_stdout_fd, masters[0]], [])) + self.select_input.append(([mock_stdin_fd, masters[0]], [], [])) with self.assertRaises(IndexError): pty._copy(masters[0]) @@ -410,28 +417,6 @@ def test__copy_to_each(self): self.assertEqual(os.read(read_from_stdout_fd, 20), b'from master') self.assertEqual(os.read(masters[1], 20), b'from stdin') - def test__copy_eof_on_all(self): - """Test the empty read EOF case on both master_fd and stdin.""" - read_from_stdout_fd, mock_stdout_fd = self._pipe() - pty.STDOUT_FILENO = mock_stdout_fd - mock_stdin_fd, write_to_stdin_fd = self._pipe() - pty.STDIN_FILENO = mock_stdin_fd - socketpair = self._socketpair() - masters = [s.fileno() for s in socketpair] - - socketpair[1].close() - os.close(write_to_stdin_fd) - - pty.select = self._mock_select - self.select_rfds_lengths.append(2) - self.select_rfds_results.append([mock_stdin_fd, masters[0]]) - # We expect that both fds were removed from the fds list as they - # both encountered an EOF before the second select call. - self.select_rfds_lengths.append(0) - - # We expect the function to return without error. - self.assertEqual(pty._copy(masters[0]), None) - def test__restore_tty_mode_normal_return(self): """Test that spawn resets the tty mode no when _copy returns normally.""" diff --git a/Lib/test/test_tty.py b/Lib/test/test_tty.py new file mode 100644 index 00000000000..681772fb519 --- /dev/null +++ b/Lib/test/test_tty.py @@ -0,0 +1,96 @@ +import os +import unittest +from test.support.import_helper import import_module + +termios = import_module('termios') +tty = import_module('tty') + + +@unittest.skipUnless(hasattr(os, 'openpty'), "need os.openpty()") +class TestTty(unittest.TestCase): + + def setUp(self): + master_fd, self.fd = os.openpty() + self.addCleanup(os.close, master_fd) + self.stream = self.enterContext(open(self.fd, 'wb', buffering=0)) + self.fd = self.stream.fileno() + self.mode = termios.tcgetattr(self.fd) + self.addCleanup(termios.tcsetattr, self.fd, termios.TCSANOW, self.mode) + self.addCleanup(termios.tcsetattr, self.fd, termios.TCSAFLUSH, self.mode) + + def check_cbreak(self, mode): + self.assertEqual(mode[3] & termios.ECHO, 0) + self.assertEqual(mode[3] & termios.ICANON, 0) + self.assertEqual(mode[6][termios.VMIN], 1) + self.assertEqual(mode[6][termios.VTIME], 0) + + def check_raw(self, mode): + self.check_cbreak(mode) + self.assertEqual(mode[0] & termios.ISTRIP, 0) + self.assertEqual(mode[0] & termios.ICRNL, 0) + self.assertEqual(mode[1] & termios.OPOST, 0) + self.assertEqual(mode[2] & termios.PARENB, termios.CS8 & termios.PARENB) + self.assertEqual(mode[2] & termios.CSIZE, termios.CS8 & termios.CSIZE) + self.assertEqual(mode[2] & termios.CS8, termios.CS8) + self.assertEqual(mode[3] & termios.ECHO, 0) + self.assertEqual(mode[3] & termios.ICANON, 0) + self.assertEqual(mode[3] & termios.ISIG, 0) + self.assertEqual(mode[6][termios.VMIN], 1) + self.assertEqual(mode[6][termios.VTIME], 0) + + def test_cfmakeraw(self): + mode = termios.tcgetattr(self.fd) + self.assertEqual(mode, self.mode) + tty.cfmakeraw(mode) + self.check_raw(mode) + self.assertEqual(mode[4], self.mode[4]) + self.assertEqual(mode[5], self.mode[5]) + + def test_cfmakecbreak(self): + mode = termios.tcgetattr(self.fd) + self.assertEqual(mode, self.mode) + tty.cfmakecbreak(mode) + self.check_cbreak(mode) + self.assertEqual(mode[1], self.mode[1]) + self.assertEqual(mode[2], self.mode[2]) + self.assertEqual(mode[4], self.mode[4]) + self.assertEqual(mode[5], self.mode[5]) + mode[tty.IFLAG] |= termios.ICRNL + tty.cfmakecbreak(mode) + self.assertEqual(mode[tty.IFLAG] & termios.ICRNL, termios.ICRNL, + msg="ICRNL should not be cleared by cbreak") + mode[tty.IFLAG] &= ~termios.ICRNL + tty.cfmakecbreak(mode) + self.assertEqual(mode[tty.IFLAG] & termios.ICRNL, 0, + msg="ICRNL should not be set by cbreak") + + @unittest.expectedFailure # TODO: RUSTPYTHON TypeError: Expected type "int" but "FileIO" found. + def test_setraw(self): + mode0 = termios.tcgetattr(self.fd) + mode1 = tty.setraw(self.fd) + self.assertEqual(mode1, mode0) + mode2 = termios.tcgetattr(self.fd) + self.check_raw(mode2) + mode3 = tty.setraw(self.fd, termios.TCSANOW) + self.assertEqual(mode3, mode2) + tty.setraw(self.stream) + tty.setraw(fd=self.fd, when=termios.TCSANOW) + + @unittest.expectedFailure # TODO: RUSTPYTHON TypeError: Expected type "int" but "FileIO" found. + def test_setcbreak(self): + mode0 = termios.tcgetattr(self.fd) + mode1 = tty.setcbreak(self.fd) + self.assertEqual(mode1, mode0) + mode2 = termios.tcgetattr(self.fd) + self.check_cbreak(mode2) + ICRNL = termios.ICRNL + self.assertEqual(mode2[tty.IFLAG] & ICRNL, mode0[tty.IFLAG] & ICRNL, + msg="ICRNL should not be altered by cbreak") + mode3 = tty.setcbreak(self.fd, termios.TCSANOW) + self.assertEqual(mode3, mode2) + tty.setcbreak(self.stream) + tty.setcbreak(fd=self.fd, when=termios.TCSANOW) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/tty.py b/Lib/tty.py index a72eb675545..5a49e040042 100644 --- a/Lib/tty.py +++ b/Lib/tty.py @@ -4,9 +4,9 @@ from termios import * -__all__ = ["setraw", "setcbreak"] +__all__ = ["cfmakeraw", "cfmakecbreak", "setraw", "setcbreak"] -# Indexes for termios list. +# Indices for termios list. IFLAG = 0 OFLAG = 1 CFLAG = 2 @@ -15,22 +15,59 @@ OSPEED = 5 CC = 6 -def setraw(fd, when=TCSAFLUSH): - """Put terminal into a raw mode.""" - mode = tcgetattr(fd) - mode[IFLAG] = mode[IFLAG] & ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON) - mode[OFLAG] = mode[OFLAG] & ~(OPOST) - mode[CFLAG] = mode[CFLAG] & ~(CSIZE | PARENB) - mode[CFLAG] = mode[CFLAG] | CS8 - mode[LFLAG] = mode[LFLAG] & ~(ECHO | ICANON | IEXTEN | ISIG) +def cfmakeraw(mode): + """Make termios mode raw.""" + # Clear all POSIX.1-2017 input mode flags. + # See chapter 11 "General Terminal Interface" + # of POSIX.1-2017 Base Definitions. + mode[IFLAG] &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | + INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF) + + # Do not post-process output. + mode[OFLAG] &= ~OPOST + + # Disable parity generation and detection; clear character size mask; + # let character size be 8 bits. + mode[CFLAG] &= ~(PARENB | CSIZE) + mode[CFLAG] |= CS8 + + # Clear all POSIX.1-2017 local mode flags. + mode[LFLAG] &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON | + IEXTEN | ISIG | NOFLSH | TOSTOP) + + # POSIX.1-2017, 11.1.7 Non-Canonical Mode Input Processing, + # Case B: MIN>0, TIME=0 + # A pending read shall block until MIN (here 1) bytes are received, + # or a signal is received. + mode[CC] = list(mode[CC]) mode[CC][VMIN] = 1 mode[CC][VTIME] = 0 - tcsetattr(fd, when, mode) -def setcbreak(fd, when=TCSAFLUSH): - """Put terminal into a cbreak mode.""" - mode = tcgetattr(fd) - mode[LFLAG] = mode[LFLAG] & ~(ECHO | ICANON) +def cfmakecbreak(mode): + """Make termios mode cbreak.""" + # Do not echo characters; disable canonical input. + mode[LFLAG] &= ~(ECHO | ICANON) + + # POSIX.1-2017, 11.1.7 Non-Canonical Mode Input Processing, + # Case B: MIN>0, TIME=0 + # A pending read shall block until MIN (here 1) bytes are received, + # or a signal is received. + mode[CC] = list(mode[CC]) mode[CC][VMIN] = 1 mode[CC][VTIME] = 0 - tcsetattr(fd, when, mode) + +def setraw(fd, when=TCSAFLUSH): + """Put terminal into raw mode.""" + mode = tcgetattr(fd) + new = list(mode) + cfmakeraw(new) + tcsetattr(fd, when, new) + return mode + +def setcbreak(fd, when=TCSAFLUSH): + """Put terminal into cbreak mode.""" + mode = tcgetattr(fd) + new = list(mode) + cfmakecbreak(new) + tcsetattr(fd, when, new) + return mode From 4af869b121497c41da0d10151fe9362dde261b4b Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:24:56 +0200 Subject: [PATCH 691/819] Fix opcode args (#6649) --- crates/stdlib/src/opcode.rs | 96 +++++++++++++------------------------ 1 file changed, 33 insertions(+), 63 deletions(-) diff --git a/crates/stdlib/src/opcode.rs b/crates/stdlib/src/opcode.rs index 0b5c5c8120d..cf6502181a7 100644 --- a/crates/stdlib/src/opcode.rs +++ b/crates/stdlib/src/opcode.rs @@ -4,9 +4,8 @@ pub(crate) use opcode::make_module; mod opcode { use crate::vm::{ AsObject, PyObjectRef, PyResult, VirtualMachine, - builtins::{PyBool, PyInt, PyIntRef, PyNone}, + builtins::{PyInt, PyIntRef}, bytecode::Instruction, - match_class, }; use core::ops::Deref; @@ -55,29 +54,20 @@ mod opcode { /// Check if instruction uses co_consts #[must_use] pub fn has_const(opcode: i32) -> bool { - if !Self::is_valid(opcode) { - return false; - } - if let Ok(instr) = Instruction::try_from(opcode as u8) { - matches!( - instr, - Instruction::LoadConst { .. } | Instruction::ReturnConst { .. } + Self::is_valid(opcode) + && matches!( + Instruction::try_from(opcode as u8), + Ok(Instruction::LoadConst { .. } | Instruction::ReturnConst { .. }) ) - } else { - false - } } /// Check if instruction uses co_names #[must_use] pub fn has_name(opcode: i32) -> bool { - if !Self::is_valid(opcode) { - return false; - } - if let Ok(instr) = Instruction::try_from(opcode as u8) { - matches!( - instr, - Instruction::DeleteAttr { .. } + Self::is_valid(opcode) + && matches!( + Instruction::try_from(opcode as u8), + Ok(Instruction::DeleteAttr { .. } | Instruction::DeleteGlobal(_) | Instruction::DeleteLocal(_) | Instruction::ImportFrom { .. } @@ -88,23 +78,17 @@ mod opcode { | Instruction::LoadNameAny(_) | Instruction::StoreAttr { .. } | Instruction::StoreGlobal(_) - | Instruction::StoreLocal(_) + | Instruction::StoreLocal(_)) ) - } else { - false - } } /// Check if instruction is a jump #[must_use] pub fn has_jump(opcode: i32) -> bool { - if !Self::is_valid(opcode) { - return false; - } - if let Ok(instr) = Instruction::try_from(opcode as u8) { - matches!( - instr, - Instruction::Break { .. } + Self::is_valid(opcode) + && matches!( + Instruction::try_from(opcode as u8), + Ok(Instruction::Break { .. } | Instruction::Continue { .. } | Instruction::ForIter { .. } | Instruction::JumpIfFalseOrPop { .. } @@ -115,51 +99,36 @@ mod opcode { | Instruction::PopJumpIfTrue { .. } | Instruction::PopJumpIfNone { .. } | Instruction::PopJumpIfNotNone { .. } - | Instruction::Send { .. } + | Instruction::Send { .. }) ) - } else { - false - } } /// Check if instruction uses co_freevars/co_cellvars #[must_use] pub fn has_free(opcode: i32) -> bool { - if !Self::is_valid(opcode) { - return false; - } - if let Ok(instr) = Instruction::try_from(opcode as u8) { - matches!( - instr, - Instruction::DeleteDeref(_) + Self::is_valid(opcode) + && matches!( + Instruction::try_from(opcode as u8), + Ok(Instruction::DeleteDeref(_) | Instruction::LoadClassDeref(_) | Instruction::LoadClosure(_) | Instruction::LoadDeref(_) - | Instruction::StoreDeref(_) + | Instruction::StoreDeref(_)) ) - } else { - false - } } /// Check if instruction uses co_varnames (local variables) #[must_use] pub fn has_local(opcode: i32) -> bool { - if !Self::is_valid(opcode) { - return false; - } - if let Ok(instr) = Instruction::try_from(opcode as u8) { - matches!( - instr, - Instruction::DeleteFast(_) + Self::is_valid(opcode) + && matches!( + Instruction::try_from(opcode as u8), + Ok(Instruction::DeleteFast(_) | Instruction::LoadFast(_) | Instruction::LoadFastAndClear(_) | Instruction::StoreFast(_) - | Instruction::StoreFastLoadFast { .. } + | Instruction::StoreFastLoadFast { .. }) ) - } else { - false - } } /// Check if instruction has exception info @@ -196,7 +165,12 @@ mod opcode { ))); } v.downcast_ref::() - .ok_or_else(|| vm.new_type_error(""))? + .ok_or_else(|| { + vm.new_type_error(format!( + "'{}' object cannot be interpreted as an integer", + v.class().name() + )) + })? .try_to_primitive::(vm) }) .unwrap_or(Ok(0))?; @@ -204,12 +178,8 @@ mod opcode { let jump = args .jump .map(|v| { - match_class!(match v { - b @ PyBool => Ok(b.is(&vm.ctx.true_value)), - _n @ PyNone => Ok(false), - _ => { - Err(vm.new_value_error("stack_effect: jump must be False, True or None")) - } + v.try_to_bool(vm).map_err(|_| { + vm.new_value_error("stack_effect: jump must be False, True or None") }) }) .unwrap_or(Ok(false))?; From dd65baf61738502d357a28f8e3beca343ada0c14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 22:53:03 +0900 Subject: [PATCH 692/819] Bump insta from 1.45.1 to 1.46.0 (#6650) Bumps [insta](https://github.com/mitsuhiko/insta) from 1.45.1 to 1.46.0. - [Release notes](https://github.com/mitsuhiko/insta/releases) - [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md) - [Commits](https://github.com/mitsuhiko/insta/compare/1.45.1...1.46.0) --- updated-dependencies: - dependency-name: insta dependency-version: 1.46.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b7436570a8..ccc39cecd00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1487,9 +1487,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.45.1" +version = "1.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983e3b24350c84ab8a65151f537d67afbbf7153bb9f1110e03e9fa9b07f67a5c" +checksum = "1b66886d14d18d420ab5052cbff544fc5d34d0b2cdd35eb5976aaa10a4a472e5" dependencies = [ "console", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 988e99efacc..5bd392e9d26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,7 +173,7 @@ getrandom = { version = "0.3", features = ["std"] } glob = "0.3" hex = "0.4.3" indexmap = { version = "2.11.3", features = ["std"] } -insta = "1.45" +insta = "1.46" itertools = "0.14.0" is-macro = "0.3.7" junction = "1.3.0" From ab885d37dec6bc435c9ada0e60098f036c3ac12d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 22:53:15 +0900 Subject: [PATCH 693/819] Bump libffi from 5.0.0 to 5.1.0 (#6651) Bumps [libffi](https://github.com/libffi-rs/libffi-rs) from 5.0.0 to 5.1.0. - [Commits](https://github.com/libffi-rs/libffi-rs/compare/libffi-v5.0.0...libffi-v5.1.0) --- updated-dependencies: - dependency-name: libffi dependency-version: 5.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ccc39cecd00..7ef945fccb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1687,9 +1687,9 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libffi" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0444124f3ffd67e1b0b0c661a7f81a278a135eb54aaad4078e79fbc8be50c8a5" +checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b" dependencies = [ "libc", "libffi-sys", @@ -1697,9 +1697,9 @@ dependencies = [ [[package]] name = "libffi-sys" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d722da8817ea580d0669da6babe2262d7b86a1af1103da24102b8bb9c101ce7" +checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb" dependencies = [ "cc", ] From 85bafc057a26761ce42d795b3977039b9497f894 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 22:53:46 +0900 Subject: [PATCH 694/819] Bump syn from 2.0.112 to 2.0.113 (#6652) Bumps [syn](https://github.com/dtolnay/syn) from 2.0.112 to 2.0.113. - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/2.0.112...2.0.113) --- updated-dependencies: - dependency-name: syn dependency-version: 2.0.113 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ef945fccb8..55ade0703e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3668,9 +3668,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.112" +version = "2.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" +checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" dependencies = [ "proc-macro2", "quote", From ead7e0c39ccf328da2e8f6043680c9ede9f60e64 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:40:41 +0200 Subject: [PATCH 695/819] Make use of `Unary` opcodes (#6647) * Make use of `Unary` opcodes * Add `Reserved140` opcode * re-add support for UnaryPositive in JIT * JIT support for `ToBool` instruction --- crates/codegen/src/compile.rs | 19 ++++++---- crates/compiler-core/src/bytecode.rs | 50 +++++++++----------------- crates/jit/src/instructions.rs | 53 ++++++++++++++++++---------- crates/vm/src/frame.rs | 45 +++++++++++------------ 4 files changed, 85 insertions(+), 82 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 2fb22f46a85..66eb785e962 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -5656,13 +5656,20 @@ impl Compiler { self.compile_expression(operand)?; // Perform operation: - let op = match op { - UnaryOp::UAdd => bytecode::UnaryOperator::Plus, - UnaryOp::USub => bytecode::UnaryOperator::Minus, - UnaryOp::Not => bytecode::UnaryOperator::Not, - UnaryOp::Invert => bytecode::UnaryOperator::Invert, + match op { + UnaryOp::UAdd => emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::UnaryPositive + } + ), + UnaryOp::USub => emit!(self, Instruction::UnaryNegative), + UnaryOp::Not => { + emit!(self, Instruction::ToBool); + emit!(self, Instruction::UnaryNot); + } + UnaryOp::Invert => emit!(self, Instruction::UnaryInvert), }; - emit!(self, Instruction::UnaryOperation { op }); } Expr::Attribute(ExprAttribute { value, attr, .. }) => { self.compile_expression(value)?; diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 08426287307..dc61e3b9b5a 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -640,7 +640,7 @@ op_arg_enum!( ImportStar = 2, // StopIterationError = 3, // AsyncGenWrap = 4, - // UnaryPositive = 5, + UnaryPositive = 5, /// Convert list to tuple ListToTuple = 6, /// Type parameter related @@ -758,11 +758,11 @@ pub enum Instruction { StoreSubscript, // 40: TO_BOOL ToBool, - // 41: UNARY_INVERT - placeholder (RustPython uses UnaryOperation) + // 41: UNARY_INVERT UnaryInvert, - // 42: UNARY_NEGATIVE - placeholder + // 42: UNARY_NEGATIVE UnaryNegative, - // 43: UNARY_NOT - placeholder + // 43: UNARY_NOT UnaryNot, // ==================== With-argument instructions (opcode >= 44) ==================== // 44: WITH_EXCEPT_START @@ -1091,11 +1091,8 @@ pub enum Instruction { SetExcInfo, // 139: SUBSCRIPT Subscript, - // 140: UNARY_OP (combines UNARY_*) - UnaryOperation { - op: Arg, - }, - // 141-148: Reserved (padding to keep RESUME at 149) + // 140-148: Reserved (padding to keep RESUME at 149) + Reserved140, Reserved141, Reserved142, Reserved143, @@ -1538,18 +1535,6 @@ impl fmt::Display for BinaryOperator { } } -op_arg_enum!( - /// The possible unary operators - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - #[repr(u8)] - pub enum UnaryOperator { - Not = 0, - Invert = 1, - Minus = 2, - Plus = 3, - } -); - op_arg_enum!( /// Whether or not to invert the operation. #[repr(u8)] @@ -1910,23 +1895,21 @@ impl Instruction { /// # Examples /// /// ``` - /// use rustpython_compiler_core::bytecode::{Arg, Instruction, Label, UnaryOperator}; + /// use rustpython_compiler_core::bytecode::{Arg, Instruction, Label}; /// let (target, jump_arg) = Arg::new(Label(0xF)); /// let jump_instruction = Instruction::Jump { target }; - /// let (op, invert_arg) = Arg::new(UnaryOperator::Invert); - /// let invert_instruction = Instruction::UnaryOperation { op }; /// assert_eq!(jump_instruction.stack_effect(jump_arg, true), 0); - /// assert_eq!(invert_instruction.stack_effect(invert_arg, false), 0); /// ``` /// pub fn stack_effect(&self, arg: OpArg, jump: bool) -> i32 { match self { // Dummy/placeholder instructions (never executed) - Cache | Reserved3 | Reserved17 | Reserved141 | Reserved142 | Reserved143 - | Reserved144 | Reserved145 | Reserved146 | Reserved147 | Reserved148 => 0, + Cache | Reserved3 | Reserved17 | Reserved140 | Reserved141 | Reserved142 + | Reserved143 | Reserved144 | Reserved145 | Reserved146 | Reserved147 | Reserved148 => { + 0 + } BinarySlice | EndFor | ExitInitCheck | GetYieldFromIter | InterpreterExit - | LoadAssertionError | LoadLocals | PushNull | ReturnGenerator | StoreSlice - | UnaryInvert | UnaryNegative | UnaryNot => 0, + | LoadAssertionError | LoadLocals | PushNull | ReturnGenerator | StoreSlice => 0, BuildConstKeyMap { .. } | CopyFreeVars { .. } | DictMerge { .. } @@ -1959,7 +1942,6 @@ impl Instruction { StoreAttr { .. } => -2, DeleteAttr { .. } => -1, LoadConst { .. } => 1, - UnaryOperation { .. } => 0, BinaryOp { .. } | CompareOperation { .. } => -1, BinarySubscript => -1, CopyItem { .. } => 1, @@ -2086,6 +2068,9 @@ impl Instruction { MatchKeys => 1, // Pop 2 (subject, keys), push 3 (subject, keys_or_none, values_or_none) MatchClass(_) => -2, ExtendedArg => 0, + UnaryInvert => 0, + UnaryNegative => 0, + UnaryNot => 0, } } @@ -2158,8 +2143,8 @@ impl Instruction { match self { // Dummy/placeholder instructions Cache => w!(CACHE), - Reserved3 | Reserved17 | Reserved141 | Reserved142 | Reserved143 | Reserved144 - | Reserved145 | Reserved146 | Reserved147 | Reserved148 => w!(RESERVED), + Reserved3 | Reserved17 | Reserved140 | Reserved141 | Reserved142 | Reserved143 + | Reserved144 | Reserved145 | Reserved146 | Reserved147 | Reserved148 => w!(RESERVED), BinarySlice => w!(BINARY_SLICE), EndFor => w!(END_FOR), ExitInitCheck => w!(EXIT_INIT_CHECK), @@ -2302,7 +2287,6 @@ impl Instruction { Subscript => w!(SUBSCRIPT), Swap { index } => w!(SWAP, index), ToBool => w!(TO_BOOL), - UnaryOperation { op } => w!(UNARY_OP, ?op), UnpackEx { args } => w!(UNPACK_EX, args), UnpackSequence { size } => w!(UNPACK_SEQUENCE, size), WithExceptStart => w!(WITH_EXCEPT_START), diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index e94bea86f59..d6e3a07e111 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -4,8 +4,8 @@ use cranelift::codegen::ir::FuncRef; use cranelift::prelude::*; use num_traits::cast::ToPrimitive; use rustpython_compiler_core::bytecode::{ - self, BinaryOperator, BorrowedConstant, CodeObject, ComparisonOperator, Instruction, Label, - OpArg, OpArgState, UnaryOperator, + self, BinaryOperator, BorrowedConstant, CodeObject, ComparisonOperator, Instruction, + IntrinsicFunction1, Label, OpArg, OpArgState, }; use std::collections::HashMap; @@ -474,6 +474,21 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { _ => Err(JitCompileError::BadBytecode), } } + Instruction::CallIntrinsic1 { func } => { + match func.get(arg) { + IntrinsicFunction1::UnaryPositive => { + match self.stack.pop().ok_or(JitCompileError::BadBytecode)? { + JitValue::Int(val) => { + // Nothing to do + self.stack.push(JitValue::Int(val)); + Ok(()) + } + _ => Err(JitCompileError::NotSupported), + } + } + _ => Err(JitCompileError::NotSupported), + } + } Instruction::CompareOperation { op, .. } => { let op = op.get(arg); // the rhs is popped off first @@ -620,28 +635,30 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { self.stack.swap(i, j); Ok(()) } - Instruction::UnaryOperation { op, .. } => { - let op = op.get(arg); + Instruction::ToBool => { let a = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; - match (op, a) { - (UnaryOperator::Minus, JitValue::Int(val)) => { - // Compile minus as 0 - a. + let value = self.boolean_val(a)?; + self.stack.push(JitValue::Bool(value)); + Ok(()) + } + Instruction::UnaryNot => { + let boolean = match self.stack.pop().ok_or(JitCompileError::BadBytecode)? { + JitValue::Bool(val) => val, + _ => return Err(JitCompileError::BadBytecode), + }; + let not_boolean = self.builder.ins().bxor_imm(boolean, 1); + self.stack.push(JitValue::Bool(not_boolean)); + Ok(()) + } + Instruction::UnaryNegative => { + match self.stack.pop().ok_or(JitCompileError::BadBytecode)? { + JitValue::Int(val) => { + // Compile minus as 0 - val. let zero = self.builder.ins().iconst(types::I64, 0); let out = self.compile_sub(zero, val); self.stack.push(JitValue::Int(out)); Ok(()) } - (UnaryOperator::Plus, JitValue::Int(val)) => { - // Nothing to do - self.stack.push(JitValue::Int(val)); - Ok(()) - } - (UnaryOperator::Not, a) => { - let boolean = self.boolean_val(a)?; - let not_boolean = self.builder.ins().bxor_imm(boolean, 1); - self.stack.push(JitValue::Bool(not_boolean)); - Ok(()) - } _ => Err(JitCompileError::NotSupported), } } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index f066e2fabcb..fc56f50fb3d 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1652,7 +1652,6 @@ impl ExecutingFrame<'_> { self.push_value(vm.ctx.new_bool(bool_val).into()); Ok(None) } - bytecode::Instruction::UnaryOperation { op } => self.execute_unary_op(vm, op.get(arg)), bytecode::Instruction::UnpackEx { args } => { let args = args.get(arg); self.execute_unpack_ex(vm, args.before, args.after) @@ -1762,7 +1761,24 @@ impl ExecutingFrame<'_> { .map_err(|_| vm.new_type_error("exception expected".to_owned()))?; Err(exc) } - + bytecode::Instruction::UnaryInvert => { + let a = self.pop_value(); + let value = vm._invert(&a)?; + self.push_value(value); + Ok(None) + } + bytecode::Instruction::UnaryNegative => { + let a = self.pop_value(); + let value = vm._neg(&a)?; + self.push_value(value); + Ok(None) + } + bytecode::Instruction::UnaryNot => { + let obj = self.pop_value(); + let value = obj.try_to_bool(vm)?; + self.push_value(vm.ctx.new_bool(!value).into()); + Ok(None) + } // Placeholder/dummy instructions - these should never be executed bytecode::Instruction::Cache | bytecode::Instruction::Reserved3 @@ -1776,9 +1792,6 @@ impl ExecutingFrame<'_> { | bytecode::Instruction::PushNull | bytecode::Instruction::ReturnGenerator | bytecode::Instruction::StoreSlice - | bytecode::Instruction::UnaryInvert - | bytecode::Instruction::UnaryNegative - | bytecode::Instruction::UnaryNot | bytecode::Instruction::BuildConstKeyMap { .. } | bytecode::Instruction::CopyFreeVars { .. } | bytecode::Instruction::DictMerge { .. } @@ -1797,6 +1810,7 @@ impl ExecutingFrame<'_> { | bytecode::Instruction::PopJumpIfNotNone { .. } | bytecode::Instruction::SetUpdate { .. } | bytecode::Instruction::StoreFastStoreFast { .. } + | bytecode::Instruction::Reserved140 | bytecode::Instruction::Reserved141 | bytecode::Instruction::Reserved142 | bytecode::Instruction::Reserved143 @@ -2440,26 +2454,6 @@ impl ExecutingFrame<'_> { Ok(None) } - #[cfg_attr(feature = "flame-it", flame("Frame"))] - fn execute_unary_op( - &mut self, - vm: &VirtualMachine, - op: bytecode::UnaryOperator, - ) -> FrameResult { - let a = self.pop_value(); - let value = match op { - bytecode::UnaryOperator::Minus => vm._neg(&a)?, - bytecode::UnaryOperator::Plus => vm._pos(&a)?, - bytecode::UnaryOperator::Invert => vm._invert(&a)?, - bytecode::UnaryOperator::Not => { - let value = a.try_to_bool(vm)?; - vm.ctx.new_bool(!value).into() - } - }; - self.push_value(value); - Ok(None) - } - #[cold] fn setup_annotations(&mut self, vm: &VirtualMachine) -> FrameResult { let __annotations__ = identifier!(vm, __annotations__); @@ -2634,6 +2628,7 @@ impl ExecutingFrame<'_> { self.import_star(vm)?; Ok(vm.ctx.none()) } + bytecode::IntrinsicFunction1::UnaryPositive => vm._pos(&arg), bytecode::IntrinsicFunction1::SubscriptGeneric => { // Used for PEP 695: Generic[*type_params] crate::builtins::genericalias::subscript_generic(arg, vm) From cf963b74c804674799a06145b364f815bdbff002 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 10:07:51 +0900 Subject: [PATCH 696/819] Bump libc from 0.2.178 to 0.2.179 (#6654) * Bump libc from 0.2.178 to 0.2.179 Bumps [libc](https://github.com/rust-lang/libc) from 0.2.178 to 0.2.179. - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/libc/compare/0.2.178...0.2.179) --- updated-dependencies: - dependency-name: libc dependency-version: 0.2.179 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Fix musl and wasi builds after libc 0.2.179 upgrade (#6655) * Initial plan * Fix musl and wasi build failures after libc 0.2.179 upgrade - Remove wasi-specific implementation of get_process_time that used CLOCK_PROCESS_CPUTIME_ID - Exclude wasi from CLOCK_PROCESS_CPUTIME_ID constant export - Add musl and wasi to use st_atim/st_mtim/st_ctim timespec fields for stat structure Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crates/vm/src/stdlib/os.rs | 4 ++-- crates/vm/src/stdlib/time.rs | 16 ++-------------- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55ade0703e7..f9741bc50ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1681,9 +1681,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libffi" diff --git a/Cargo.toml b/Cargo.toml index 5bd392e9d26..a0c4a149305 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,7 +177,7 @@ insta = "1.46" itertools = "0.14.0" is-macro = "0.3.7" junction = "1.3.0" -libc = "0.2.178" +libc = "0.2.179" libffi = "5" log = "0.4.29" nix = { version = "0.30", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] } diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 416f26018cb..415097b90d8 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -918,7 +918,7 @@ pub(super) mod _os { fn from_stat(stat: &StatStruct, vm: &VirtualMachine) -> Self { let (atime, mtime, ctime); #[cfg(any(unix, windows))] - #[cfg(not(target_os = "netbsd"))] + #[cfg(not(any(target_os = "netbsd", target_os = "wasi", target_env = "musl")))] { atime = (stat.st_atime, stat.st_atime_nsec); mtime = (stat.st_mtime, stat.st_mtime_nsec); @@ -930,7 +930,7 @@ pub(super) mod _os { mtime = (stat.st_mtime, stat.st_mtimensec); ctime = (stat.st_ctime, stat.st_ctimensec); } - #[cfg(target_os = "wasi")] + #[cfg(any(target_os = "wasi", target_env = "musl"))] { atime = (stat.st_atim.tv_sec, stat.st_atim.tv_nsec); mtime = (stat.st_mtim.tv_sec, stat.st_mtim.tv_nsec); diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index 97d60ae98a1..8c368b9cc59 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -441,19 +441,6 @@ mod decl { )) } - // same as the get_process_time impl for most unixes - #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] - pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult { - let time: libc::timespec = unsafe { - let mut time = std::mem::MaybeUninit::uninit(); - if libc::clock_gettime(libc::CLOCK_PROCESS_CPUTIME_ID, time.as_mut_ptr()) == -1 { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); - } - time.assume_init() - }; - Ok(Duration::new(time.tv_sec as u64, time.tv_nsec as u32)) - } - #[cfg(not(any( windows, target_os = "macos", @@ -466,7 +453,7 @@ mod decl { target_os = "solaris", target_os = "openbsd", target_os = "redox", - all(target_arch = "wasm32", not(target_os = "unknown")) + all(target_arch = "wasm32", target_os = "emscripten") )))] fn get_process_time(vm: &VirtualMachine) -> PyResult { Err(vm.new_not_implemented_error("process time unsupported in this system")) @@ -601,6 +588,7 @@ mod platform { target_os = "netbsd", target_os = "solaris", target_os = "openbsd", + target_os = "wasi", )))] #[pyattr] use libc::CLOCK_PROCESS_CPUTIME_ID; From 906aebf5a1efaf394a1cc791739878846d48973c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:15:35 +0900 Subject: [PATCH 697/819] Bump malachite-q from 0.8.0 to 0.9.0 (#6653) * Bump malachite-q from 0.8.0 to 0.9.0 Bumps [malachite-q](https://github.com/mhogrefe/malachite) from 0.8.0 to 0.9.0. - [Release notes](https://github.com/mhogrefe/malachite/releases) - [Commits](https://github.com/mhogrefe/malachite/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: malachite-q dependency-version: 0.9.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump all malachite-* crates from 0.8.0 to 0.9.0 (#6656) * Initial plan * Update all malachite-* crates to 0.9.0 * Fix ctypes overflow check for malachite 0.9.0 compatibility --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 6 +++--- crates/vm/src/stdlib/ctypes/simple.rs | 7 ++++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9741bc50ce..e03c1221781 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1822,9 +1822,9 @@ dependencies = [ [[package]] name = "malachite-base" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c91cb6071ed9ac48669d3c79bd2792db596c7e542dbadd217b385bb359f42d" +checksum = "5eb2a098b227df48779e28ed4125dd3161b792a9254961377cea6f5c19e5b417" dependencies = [ "hashbrown 0.16.1", "itertools 0.14.0", @@ -1834,9 +1834,9 @@ dependencies = [ [[package]] name = "malachite-bigint" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff3af5010102f29f2ef4ee6f7b1c5b3f08a6c261b5164e01c41cf43772b6f90" +checksum = "6eaf19f1b8ba023528050372eafd72ca11f80f70c9dc2af9bb22f888bf079013" dependencies = [ "malachite-base", "malachite-nz", @@ -1847,9 +1847,9 @@ dependencies = [ [[package]] name = "malachite-nz" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9ecf4dd76246fd622de4811097966106aa43f9cd7cc36cb85e774fe84c8adc" +checksum = "ab7c0ddc4e2681459d70591baf30ca5abd31c25969e76d3605838bec794c8077" dependencies = [ "itertools 0.14.0", "libm", @@ -1859,9 +1859,9 @@ dependencies = [ [[package]] name = "malachite-q" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7bc9d9adf5b0a7999d84f761c809bec3dc46fe983e4de547725d2b7730462a0" +checksum = "e13d19f04fc672f251d477c8d58c13c6c5550553dfba6a665c9ad3604466ac9a" dependencies = [ "itertools 0.14.0", "malachite-base", diff --git a/Cargo.toml b/Cargo.toml index a0c4a149305..1ef31f825f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -181,9 +181,9 @@ libc = "0.2.179" libffi = "5" log = "0.4.29" nix = { version = "0.30", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] } -malachite-bigint = "0.8" -malachite-q = "0.8" -malachite-base = "0.8" +malachite-bigint = "0.9" +malachite-q = "0.9" +malachite-base = "0.9" memchr = "2.7.4" num-complex = "0.4.6" num-integer = "0.1.46" diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs index de13dab2202..b2ae0f7cc5b 100644 --- a/crates/vm/src/stdlib/ctypes/simple.rs +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -141,11 +141,12 @@ fn set_primitive(_type_: &str, value: &PyObject, vm: &VirtualMachine) -> PyResul // Handle int specially to check overflow if let Some(int_obj) = value.downcast_ref_if_exact::(vm) { // Check if int can fit in f64 - if int_obj.as_bigint().to_f64().is_some() { + if let Some(f) = int_obj.as_bigint().to_f64() + && f.is_finite() + { return Ok(value.to_owned()); - } else { - return Err(vm.new_overflow_error("int too large to convert to float")); } + return Err(vm.new_overflow_error("int too large to convert to float")); } // __float__ protocol if value.try_float(vm).is_ok() { From 03380dc4ecf6d21f53be9de36a689b5c6534da0d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:16:51 +0900 Subject: [PATCH 698/819] Ensure __new__ entries in class __dict__ are staticmethods (#6659) * Initial plan * Wrap __new__ methods as staticmethods in type dicts Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> * Unmark passing enum test Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- Lib/test/test_enum.py | 1 - crates/vm/src/builtins/type.rs | 10 ++++++++-- extra_tests/snippets/builtin_type.py | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 8f4b39b4dbb..b9d9cf33e35 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -5208,7 +5208,6 @@ def test_inspect_signatures(self): ]), ) - @unittest.expectedFailure # TODO: RUSTPYTHON; len is often/always > 256 def test_test_simple_enum(self): @_simple_enum(Enum) class SimpleColor: diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 8660d1f2e27..fed9af976f6 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1,6 +1,6 @@ use super::{ - PyClassMethod, PyDictRef, PyList, PyStr, PyStrInterned, PyStrRef, PyTupleRef, PyWeak, - mappingproxy::PyMappingProxy, object, union_, + PyClassMethod, PyDictRef, PyList, PyStaticMethod, PyStr, PyStrInterned, PyStrRef, PyTupleRef, + PyWeak, mappingproxy::PyMappingProxy, object, union_, }; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, @@ -1166,6 +1166,12 @@ impl Constructor for PyType { *f = PyClassMethod::from(f.clone()).into_pyobject(vm); } + if let Some(f) = attributes.get_mut(identifier!(vm, __new__)) + && f.class().is(vm.ctx.types.function_type) + { + *f = PyStaticMethod::from(f.clone()).into_pyobject(vm); + } + if let Some(current_frame) = vm.current_frame() { let entry = attributes.entry(identifier!(vm, __module__)); if matches!(entry, Entry::Vacant(_)) { diff --git a/extra_tests/snippets/builtin_type.py b/extra_tests/snippets/builtin_type.py index 67269e694c0..8cb0a09a215 100644 --- a/extra_tests/snippets/builtin_type.py +++ b/extra_tests/snippets/builtin_type.py @@ -584,6 +584,7 @@ def __new__(cls, *args, **kwargs): assert ClassWithNew().__new__.__qualname__ == "ClassWithNew.__new__" assert ClassWithNew.__new__.__name__ == "__new__" assert ClassWithNew().__new__.__name__ == "__new__" +assert isinstance(ClassWithNew.__dict__.get("__new__"), staticmethod) assert ClassWithNew.N.__new__.__qualname__ == "ClassWithNew.N.__new__" assert ClassWithNew().N.__new__.__qualname__ == "ClassWithNew.N.__new__" @@ -593,6 +594,7 @@ def __new__(cls, *args, **kwargs): assert ClassWithNew().N().__new__.__qualname__ == "ClassWithNew.N.__new__" assert ClassWithNew.N().__new__.__name__ == "__new__" assert ClassWithNew().N().__new__.__name__ == "__new__" +assert isinstance(ClassWithNew.N.__dict__.get("__new__"), staticmethod) # Regression to: From e367145a4ac9d90332eb5373cc10dd49a43c3cfd Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:10:23 +0900 Subject: [PATCH 699/819] Add Linux pidfd syscalls to expose asyncio pidfd watcher (#6660) * Add pidfd support syscalls * Validate pidfd signal range * Clarify pidfd syscall return handling * Skip flaky is_alive_after_fork test --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> Co-authored-by: github-actions[bot] --- Lib/test/test_threading.py | 1 + crates/vm/src/stdlib/posix.rs | 17 +++++++++++++++++ crates/vm/src/stdlib/signal.rs | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 937f5903b5f..031db4a49af 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -589,6 +589,7 @@ def background_thread(evt): self.assertEqual(out, b'') self.assertEqual(err, b'') + @unittest.skip("TODO: RUSTPYTHON; flaky") @support.requires_fork() def test_is_alive_after_fork(self): # Try hard to trigger #18418: is_alive() could sometimes be True on diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 65a659790b2..05f435649ad 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -1757,6 +1757,23 @@ pub mod module { libc::WTERMSIG(status) } + #[cfg(target_os = "linux")] + #[pyfunction] + fn pidfd_open( + pid: libc::pid_t, + flags: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let flags = flags.unwrap_or(0); + let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, flags) as libc::c_long }; + if fd == -1 { + Err(vm.new_last_errno_error()) + } else { + // Safety: syscall returns a new owned file descriptor. + Ok(unsafe { OwnedFd::from_raw_fd(fd as libc::c_int) }) + } + } + #[pyfunction] fn waitpid(pid: libc::pid_t, opt: i32, vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> { let mut status = 0; diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index dd0d9a7a96f..b316ea306d0 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -431,6 +431,40 @@ pub(crate) mod _signal { } } + #[cfg(target_os = "linux")] + #[pyfunction] + fn pidfd_send_signal( + pidfd: i32, + sig: i32, + siginfo: OptionalArg, + flags: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult<()> { + signal::assert_in_range(sig, vm)?; + if let OptionalArg::Present(obj) = siginfo + && !vm.is_none(&obj) + { + return Err(vm.new_type_error("siginfo must be None".to_owned())); + } + + let flags = flags.unwrap_or(0); + let ret = unsafe { + libc::syscall( + libc::SYS_pidfd_send_signal, + pidfd, + sig, + std::ptr::null::(), + flags, + ) as libc::c_long + }; + + if ret == -1 { + Err(vm.new_last_errno_error()) + } else { + Ok(()) + } + } + #[cfg(all(unix, not(target_os = "redox")))] #[pyfunction(name = "siginterrupt")] fn py_siginterrupt(signum: i32, flag: i32, vm: &VirtualMachine) -> PyResult<()> { From cd613edc71d277e9c2061b4ea455f0bda40a3cfd Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Tue, 6 Jan 2026 07:35:21 -0500 Subject: [PATCH 700/819] Update the zipfile + zipimport libraries + tests - v3.13.11 (#6639) * Updated zipimport library + test * Updated zipfile library + test * Annotated failing/erroring tests in test_zipfile and test_zipimport * Changed all skips in `test_core.py` to expectedFailures * skip EncodedMetadataTests --------- Co-authored-by: Jeong YunWon --- Lib/test/test_zipfile/_path/test_path.py | 13 +- Lib/test/test_zipfile/test_core.py | 152 ++++-- Lib/test/test_zipimport.py | 473 ++++++++++++------ .../sparse-zip64-c0-0x000000000.part | Bin 0 -> 4096 bytes .../sparse-zip64-c0-0x100000000.part | Bin 0 -> 4096 bytes .../sparse-zip64-c0-0x200000000.part | Bin 0 -> 546 bytes Lib/zipfile/__init__.py | 51 +- Lib/zipimport.py | 395 ++++++++------- 8 files changed, 675 insertions(+), 409 deletions(-) create mode 100644 Lib/test/zipimport_data/sparse-zip64-c0-0x000000000.part create mode 100644 Lib/test/zipimport_data/sparse-zip64-c0-0x100000000.part create mode 100644 Lib/test/zipimport_data/sparse-zip64-c0-0x200000000.part diff --git a/Lib/test/test_zipfile/_path/test_path.py b/Lib/test/test_zipfile/_path/test_path.py index 2d1c06cd968..5c69c77f7d8 100644 --- a/Lib/test/test_zipfile/_path/test_path.py +++ b/Lib/test/test_zipfile/_path/test_path.py @@ -275,7 +275,8 @@ def test_pathlike_construction(self, alpharep): """ zipfile_ondisk = self.zipfile_ondisk(alpharep) pathlike = FakePath(str(zipfile_ondisk)) - zipfile.Path(pathlike) + root = zipfile.Path(pathlike) + root.root.close() @pass_alpharep def test_traverse_pathlike(self, alpharep): @@ -374,6 +375,7 @@ def test_root_on_disk(self, alpharep): root = zipfile.Path(self.zipfile_ondisk(alpharep)) assert root.name == 'alpharep.zip' == root.filename.name assert root.stem == 'alpharep' == root.filename.stem + root.root.close() @pass_alpharep def test_suffix(self, alpharep): @@ -565,7 +567,7 @@ def test_inheritance(self, alpharep): file = cls(alpharep).joinpath('some dir').parent assert isinstance(file, cls) - @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON, fails on Windows") + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; fails on Windows') @parameterize( ['alpharep', 'path_type', 'subpath'], itertools.product( @@ -576,11 +578,13 @@ def test_inheritance(self, alpharep): ) def test_pickle(self, alpharep, path_type, subpath): zipfile_ondisk = path_type(str(self.zipfile_ondisk(alpharep))) - - saved_1 = pickle.dumps(zipfile.Path(zipfile_ondisk, at=subpath)) + root = zipfile.Path(zipfile_ondisk, at=subpath) + saved_1 = pickle.dumps(root) + root.root.close() restored_1 = pickle.loads(saved_1) first, *rest = restored_1.iterdir() assert first.read_text(encoding='utf-8').startswith('content of ') + restored_1.root.close() @pass_alpharep def test_extract_orig_with_implied_dirs(self, alpharep): @@ -592,6 +596,7 @@ def test_extract_orig_with_implied_dirs(self, alpharep): # wrap the zipfile for its side effect zipfile.Path(zf) zf.extractall(source_path.parent) + zf.close() @pass_alpharep def test_getinfo_missing(self, alpharep): diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index fa1feef00cd..63413d7b944 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -302,26 +302,26 @@ def test_low_compression(self): self.assertEqual(openobj.read(1), b'2') def test_writestr_compression(self): - zipfp = zipfile.ZipFile(TESTFN2, "w") - zipfp.writestr("b.txt", "hello world", compress_type=self.compression) - info = zipfp.getinfo('b.txt') - self.assertEqual(info.compress_type, self.compression) + with zipfile.ZipFile(TESTFN2, "w") as zipfp: + zipfp.writestr("b.txt", "hello world", compress_type=self.compression) + info = zipfp.getinfo('b.txt') + self.assertEqual(info.compress_type, self.compression) def test_writestr_compresslevel(self): - zipfp = zipfile.ZipFile(TESTFN2, "w", compresslevel=1) - zipfp.writestr("a.txt", "hello world", compress_type=self.compression) - zipfp.writestr("b.txt", "hello world", compress_type=self.compression, - compresslevel=2) + with zipfile.ZipFile(TESTFN2, "w", compresslevel=1) as zipfp: + zipfp.writestr("a.txt", "hello world", compress_type=self.compression) + zipfp.writestr("b.txt", "hello world", compress_type=self.compression, + compresslevel=2) - # Compression level follows the constructor. - a_info = zipfp.getinfo('a.txt') - self.assertEqual(a_info.compress_type, self.compression) - self.assertEqual(a_info.compress_level, 1) + # Compression level follows the constructor. + a_info = zipfp.getinfo('a.txt') + self.assertEqual(a_info.compress_type, self.compression) + self.assertEqual(a_info.compress_level, 1) - # Compression level is overridden. - b_info = zipfp.getinfo('b.txt') - self.assertEqual(b_info.compress_type, self.compression) - self.assertEqual(b_info._compresslevel, 2) + # Compression level is overridden. + b_info = zipfp.getinfo('b.txt') + self.assertEqual(b_info.compress_type, self.compression) + self.assertEqual(b_info._compresslevel, 2) def test_read_return_size(self): # Issue #9837: ZipExtFile.read() shouldn't return more bytes @@ -884,6 +884,8 @@ def make_zip64_file( self, file_size_64_set=False, file_size_extra=False, compress_size_64_set=False, compress_size_extra=False, header_offset_64_set=False, header_offset_extra=False, + extensible_data=b'', + end_of_central_dir_size=None, offset_to_end_of_central_dir=None, ): """Generate bytes sequence for a zip with (incomplete) zip64 data. @@ -937,6 +939,12 @@ def make_zip64_file( central_dir_size = struct.pack('", "exec"), NOW, len(src)) - files = {TESTMOD + pyc_ext: (NOW, pyc), - "some.data": (NOW, "some data")} - self.doTest(pyc_ext, files, TESTMOD) + files = {TESTMOD + pyc_ext: pyc, + "some.data": "some data"} + self.doTest(pyc_ext, files, TESTMOD, prefix='') def testDefaultOptimizationLevel(self): # zipimport should use the default optimization level (#28131) @@ -648,17 +667,20 @@ def testDefaultOptimizationLevel(self): def test(val): assert(val) return val\n""" - files = {TESTMOD + '.py': (NOW, src)} + files = {TESTMOD + '.py': src} self.makeZip(files) sys.path.insert(0, TEMP_ZIP) mod = importlib.import_module(TESTMOD) self.assertEqual(mod.test(1), 1) - self.assertRaises(AssertionError, mod.test, False) + if __debug__: + self.assertRaises(AssertionError, mod.test, False) + else: + self.assertEqual(mod.test(0), 0) def testImport_WithStuff(self): # try importing from a zipfile which contains additional # stuff at the beginning of the file - files = {TESTMOD + ".py": (NOW, test_src)} + files = {TESTMOD + ".py": test_src} self.doTest(".py", files, TESTMOD, stuff=b"Some Stuff"*31) @@ -666,18 +688,18 @@ def assertModuleSource(self, module): self.assertEqual(inspect.getsource(module), test_src) def testGetSource(self): - files = {TESTMOD + ".py": (NOW, test_src)} + files = {TESTMOD + ".py": test_src} self.doTest(".py", files, TESTMOD, call=self.assertModuleSource) def testGetCompiledSource(self): pyc = make_pyc(compile(test_src, "", "exec"), NOW, len(test_src)) - files = {TESTMOD + ".py": (NOW, test_src), - TESTMOD + pyc_ext: (NOW, pyc)} + files = {TESTMOD + ".py": test_src, + TESTMOD + pyc_ext: pyc} self.doTest(pyc_ext, files, TESTMOD, call=self.assertModuleSource) def runDoctest(self, callback): - files = {TESTMOD + ".py": (NOW, test_src), - "xyz.txt": (NOW, ">>> log.append(True)\n")} + files = {TESTMOD + ".py": test_src, + "xyz.txt": ">>> log.append(True)\n"} self.doTest(".py", files, TESTMOD, call=callback) def doDoctestFile(self, module): @@ -720,56 +742,177 @@ def doTraceback(self, module): s = io.StringIO() print_tb(tb, 1, s) - self.assertTrue(s.getvalue().endswith(raise_src)) + self.assertTrue(s.getvalue().endswith( + ' def do_raise(): raise TypeError\n' + '' if support.has_no_debug_ranges() else + ' ^^^^^^^^^^^^^^^\n' + )) else: raise AssertionError("This ought to be impossible") - # TODO: RUSTPYTHON; empty caret lines from equal col/end_col - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; empty caret lines from equal col/end_col def testTraceback(self): - files = {TESTMOD + ".py": (NOW, raise_src)} + files = {TESTMOD + ".py": raise_src} self.doTest(None, files, TESTMOD, call=self.doTraceback) @unittest.skipIf(os_helper.TESTFN_UNENCODABLE is None, "need an unencodable filename") def testUnencodable(self): filename = os_helper.TESTFN_UNENCODABLE + ".zip" - self.addCleanup(os_helper.unlink, filename) - with ZipFile(filename, "w") as z: - zinfo = ZipInfo(TESTMOD + ".py", time.localtime(NOW)) - zinfo.compress_type = self.compression - z.writestr(zinfo, test_src) + self.makeZip({TESTMOD + ".py": test_src}, filename) spec = zipimport.zipimporter(filename).find_spec(TESTMOD) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) def testBytesPath(self): filename = os_helper.TESTFN + ".zip" - self.addCleanup(os_helper.unlink, filename) - with ZipFile(filename, "w") as z: - zinfo = ZipInfo(TESTMOD + ".py", time.localtime(NOW)) - zinfo.compress_type = self.compression - z.writestr(zinfo, test_src) + self.makeZip({TESTMOD + ".py": test_src}, filename) zipimport.zipimporter(filename) - zipimport.zipimporter(os.fsencode(filename)) + with self.assertRaises(TypeError): + zipimport.zipimporter(os.fsencode(filename)) with self.assertRaises(TypeError): zipimport.zipimporter(bytearray(os.fsencode(filename))) with self.assertRaises(TypeError): zipimport.zipimporter(memoryview(os.fsencode(filename))) def testComment(self): - files = {TESTMOD + ".py": (NOW, test_src)} + files = {TESTMOD + ".py": test_src} self.doTest(".py", files, TESTMOD, comment=b"comment") def testBeginningCruftAndComment(self): - files = {TESTMOD + ".py": (NOW, test_src)} + files = {TESTMOD + ".py": test_src} self.doTest(".py", files, TESTMOD, stuff=b"cruft" * 64, comment=b"hi") def testLargestPossibleComment(self): - files = {TESTMOD + ".py": (NOW, test_src)} + files = {TESTMOD + ".py": test_src} self.doTest(".py", files, TESTMOD, comment=b"c" * ((1 << 16) - 1)) + def testZip64(self): + files = self.getZip64Files() + self.doTest(".py", files, "f6") + + def testZip64CruftAndComment(self): + files = self.getZip64Files() + self.doTest(".py", files, "f65536", comment=b"c" * ((1 << 16) - 1)) + + @unittest.skip('TODO: RUSTPYTHON; (intermittent success/failures); ValueError: name="RustPython/crates/pylib/Lib/test/zipimport_data/sparse-zip64-c0-0x000000000.part" does not fit expected pattern.') + def testZip64LargeFile(self): + support.requires( + "largefile", + f"test generates files >{0xFFFFFFFF} bytes and takes a long time " + "to run" + ) + + # N.B.: We do a lot of gymnastics below in the ZIP_STORED case to save + # and reconstruct a sparse zip on systems that support sparse files. + # Instead of creating a ~8GB zip file mainly consisting of null bytes + # for every run of the test, we create the zip once and save off the + # non-null portions of the resulting file as data blobs with offsets + # that allow re-creating the zip file sparsely. This drops disk space + # usage to ~9KB for the ZIP_STORED case and drops that test time by ~2 + # orders of magnitude. For the ZIP_DEFLATED case, however, we bite the + # bullet. The resulting zip file is ~8MB of non-null data; so the sparse + # trick doesn't work and would result in that full ~8MB zip data file + # being checked in to source control. + parts_glob = f"sparse-zip64-c{self.compression:d}-0x*.part" + full_parts_glob = os.path.join(TEST_DATA_DIR, parts_glob) + pre_built_zip_parts = glob.glob(full_parts_glob) + + self.addCleanup(os_helper.unlink, TEMP_ZIP) + if not pre_built_zip_parts: + if self.compression != ZIP_STORED: + support.requires( + "cpu", + "test requires a lot of CPU for compression." + ) + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + with open(os_helper.TESTFN, "wb") as f: + f.write(b"data") + f.write(os.linesep.encode()) + f.seek(0xffff_ffff, os.SEEK_CUR) + f.write(os.linesep.encode()) + os.utime(os_helper.TESTFN, (0.0, 0.0)) + with ZipFile( + TEMP_ZIP, + "w", + compression=self.compression, + strict_timestamps=False + ) as z: + z.write(os_helper.TESTFN, "data1") + z.writestr( + ZipInfo("module.py", (1980, 1, 1, 0, 0, 0)), test_src + ) + z.write(os_helper.TESTFN, "data2") + + # This "works" but relies on the zip format having a non-empty + # final page due to the trailing central directory to wind up with + # the correct length file. + def make_sparse_zip_parts(name): + empty_page = b"\0" * 4096 + with open(name, "rb") as f: + part = None + try: + while True: + offset = f.tell() + data = f.read(len(empty_page)) + if not data: + break + if data != empty_page: + if not part: + part_fullname = os.path.join( + TEST_DATA_DIR, + f"sparse-zip64-c{self.compression:d}-" + f"{offset:#011x}.part", + ) + os.makedirs( + os.path.dirname(part_fullname), + exist_ok=True + ) + part = open(part_fullname, "wb") + print("Created", part_fullname) + part.write(data) + else: + if part: + part.close() + part = None + finally: + if part: + part.close() + + if self.compression == ZIP_STORED: + print(f"Creating sparse parts to check in into {TEST_DATA_DIR}:") + make_sparse_zip_parts(TEMP_ZIP) + + else: + def extract_offset(name): + if m := re.search(r"-(0x[0-9a-f]{9})\.part$", name): + return int(m.group(1), base=16) + raise ValueError(f"{name=} does not fit expected pattern.") + offset_parts = [(extract_offset(n), n) for n in pre_built_zip_parts] + with open(TEMP_ZIP, "wb") as f: + for offset, part_fn in sorted(offset_parts): + with open(part_fn, "rb") as part: + f.seek(offset, os.SEEK_SET) + f.write(part.read()) + # Confirm that the reconstructed zip file works and looks right. + with ZipFile(TEMP_ZIP, "r") as z: + self.assertEqual( + z.getinfo("module.py").date_time, (1980, 1, 1, 0, 0, 0) + ) + self.assertEqual( + z.read("module.py"), test_src.encode(), + msg=f"Recreate {full_parts_glob}, unexpected contents." + ) + def assertDataEntry(name): + zinfo = z.getinfo(name) + self.assertEqual(zinfo.date_time, (1980, 1, 1, 0, 0, 0)) + self.assertGreater(zinfo.file_size, 0xffff_ffff) + assertDataEntry("data1") + assertDataEntry("data2") + + self.doTestWithPreBuiltZip(".py", "module") + @support.requires_zlib() class CompressedZipImportTestCase(UncompressedZipImportTestCase): @@ -801,6 +944,7 @@ def testEmptyFile(self): os_helper.create_empty_file(TESTMOD) self.assertZipFailure(TESTMOD) + @unittest.skipIf(support.is_wasi, "mode 000 not supported.") def testFileUnreadable(self): os_helper.unlink(TESTMOD) fd = os.open(TESTMOD, os.O_CREAT, 000) @@ -844,7 +988,6 @@ def _testBogusZipFile(self): self.assertRaises(TypeError, z.get_source, None) error = zipimport.ZipImportError - self.assertIsNone(z.find_module('abc')) self.assertIsNone(z.find_spec('abc')) with warnings.catch_warnings(): diff --git a/Lib/test/zipimport_data/sparse-zip64-c0-0x000000000.part b/Lib/test/zipimport_data/sparse-zip64-c0-0x000000000.part new file mode 100644 index 0000000000000000000000000000000000000000..c6beae8e2552d6a7be5c70df6feeb42230270912 GIT binary patch literal 4096 zcmWIWW@gc4fB;2?DAjhu|4_inAi|K6SdwVS$RNPL3RJ-e#4s8p!3EVeN{xoVXb6mk kz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD4Dt{F0RIvV#Q*>R literal 0 HcmV?d00001 diff --git a/Lib/test/zipimport_data/sparse-zip64-c0-0x100000000.part b/Lib/test/zipimport_data/sparse-zip64-c0-0x100000000.part new file mode 100644 index 0000000000000000000000000000000000000000..74ab03b4648948b5a040716f2d0d1dcd6d5f2d8e GIT binary patch literal 4096 zcmZQzARBN6cr&wzK(#9}80R11^8oTdm=lO|^HWN5QuPWdQ&Q6u(o;*~^AdAYH8icb z6o8;8wWPEtPaz&G7$46CQ)po=GP{7I{0uO^FjUZ-9Vo9PQBZB|~E0Dto#4sA& z7~}jSd>%k~5awjy0g@2Vzz8xsH$SB`CsnVYl97Rf!3HP;G78-YRF}v=4Panc(g-pF z2dYVz0p>vl24<)jjJ^rwKL^rGKpf!B#tsrX0CW?G4{;hR Q8%T^92tn#W4q#va017uX9RL6T literal 0 HcmV?d00001 diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 05f387a950b..c01f13729e1 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -245,7 +245,7 @@ def is_zipfile(filename): else: with open(filename, "rb") as fp: result = _check_zipfile(fp) - except OSError: + except (OSError, BadZipFile): pass return result @@ -253,16 +253,15 @@ def _EndRecData64(fpin, offset, endrec): """ Read the ZIP64 end-of-archive records and use that to update endrec """ - try: - fpin.seek(offset - sizeEndCentDir64Locator, 2) - except OSError: - # If the seek fails, the file is not large enough to contain a ZIP64 + offset -= sizeEndCentDir64Locator + if offset < 0: + # The file is not large enough to contain a ZIP64 # end-of-archive record, so just return the end record we were given. return endrec - + fpin.seek(offset) data = fpin.read(sizeEndCentDir64Locator) if len(data) != sizeEndCentDir64Locator: - return endrec + raise OSError("Unknown I/O error") sig, diskno, reloff, disks = struct.unpack(structEndArchive64Locator, data) if sig != stringEndArchive64Locator: return endrec @@ -270,16 +269,33 @@ def _EndRecData64(fpin, offset, endrec): if diskno != 0 or disks > 1: raise BadZipFile("zipfiles that span multiple disks are not supported") - # Assume no 'zip64 extensible data' - fpin.seek(offset - sizeEndCentDir64Locator - sizeEndCentDir64, 2) + offset -= sizeEndCentDir64 + if reloff > offset: + raise BadZipFile("Corrupt zip64 end of central directory locator") + # First, check the assumption that there is no prepended data. + fpin.seek(reloff) + extrasz = offset - reloff data = fpin.read(sizeEndCentDir64) if len(data) != sizeEndCentDir64: - return endrec + raise OSError("Unknown I/O error") + if not data.startswith(stringEndArchive64) and reloff != offset: + # Since we already have seen the Zip64 EOCD Locator, it's + # possible we got here because there is prepended data. + # Assume no 'zip64 extensible data' + fpin.seek(offset) + extrasz = 0 + data = fpin.read(sizeEndCentDir64) + if len(data) != sizeEndCentDir64: + raise OSError("Unknown I/O error") + if not data.startswith(stringEndArchive64): + raise BadZipFile("Zip64 end of central directory record not found") + sig, sz, create_version, read_version, disk_num, disk_dir, \ dircount, dircount2, dirsize, diroffset = \ struct.unpack(structEndArchive64, data) - if sig != stringEndArchive64: - return endrec + if (diroffset + dirsize != reloff or + sz + 12 != sizeEndCentDir64 + extrasz): + raise BadZipFile("Corrupt zip64 end of central directory record") # Update the original endrec using data from the ZIP64 record endrec[_ECD_SIGNATURE] = sig @@ -289,6 +305,7 @@ def _EndRecData64(fpin, offset, endrec): endrec[_ECD_ENTRIES_TOTAL] = dircount2 endrec[_ECD_SIZE] = dirsize endrec[_ECD_OFFSET] = diroffset + endrec[_ECD_LOCATION] = offset - extrasz return endrec @@ -322,7 +339,7 @@ def _EndRecData(fpin): endrec.append(filesize - sizeEndCentDir) # Try to read the "Zip64 end of central directory" structure - return _EndRecData64(fpin, -sizeEndCentDir, endrec) + return _EndRecData64(fpin, filesize - sizeEndCentDir, endrec) # Either this is not a ZIP file, or it is a ZIP file with an archive # comment. Search the end of the file for the "end of central directory" @@ -346,8 +363,7 @@ def _EndRecData(fpin): endrec.append(maxCommentStart + start) # Try to read the "Zip64 end of central directory" structure - return _EndRecData64(fpin, maxCommentStart + start - filesize, - endrec) + return _EndRecData64(fpin, maxCommentStart + start, endrec) # Unable to find a valid end of central directory structure return None @@ -1458,9 +1474,6 @@ def _RealGetContents(self): # "concat" is zero, unless zip was concatenated to another file concat = endrec[_ECD_LOCATION] - size_cd - offset_cd - if endrec[_ECD_SIGNATURE] == stringEndArchive64: - # If Zip64 extension structures are present, account for them - concat -= (sizeEndCentDir64 + sizeEndCentDir64Locator) if self.debug > 2: inferred = concat + offset_cd @@ -2082,7 +2095,7 @@ def _write_end_record(self): " would require ZIP64 extensions") zip64endrec = struct.pack( structEndArchive64, stringEndArchive64, - 44, 45, 45, 0, 0, centDirCount, centDirCount, + sizeEndCentDir64 - 12, 45, 45, 0, 0, centDirCount, centDirCount, centDirSize, centDirOffset) self.fp.write(zip64endrec) diff --git a/Lib/zipimport.py b/Lib/zipimport.py index 25eaee9c0f2..fb312be115e 100644 --- a/Lib/zipimport.py +++ b/Lib/zipimport.py @@ -1,11 +1,9 @@ """zipimport provides support for importing Python modules from Zip archives. -This module exports three objects: +This module exports two objects: - zipimporter: a class; its constructor takes a path to a Zip archive. - ZipImportError: exception raised by zipimporter objects. It's a subclass of ImportError, so it can be caught as ImportError, too. -- _zip_directory_cache: a dict, mapping archive paths to zip directory - info dicts, as used in zipimporter._files. It is usually not needed to use the zipimport module explicitly; it is used by the builtin import mechanism for sys.path items that are paths @@ -15,7 +13,7 @@ #from importlib import _bootstrap_external #from importlib import _bootstrap # for _verbose_message import _frozen_importlib_external as _bootstrap_external -from _frozen_importlib_external import _unpack_uint16, _unpack_uint32 +from _frozen_importlib_external import _unpack_uint16, _unpack_uint32, _unpack_uint64 import _frozen_importlib as _bootstrap # for _verbose_message import _imp # for check_hash_based_pycs import _io # for open @@ -40,8 +38,14 @@ class ZipImportError(ImportError): _module_type = type(sys) END_CENTRAL_DIR_SIZE = 22 -STRING_END_ARCHIVE = b'PK\x05\x06' +END_CENTRAL_DIR_SIZE_64 = 56 +END_CENTRAL_DIR_LOCATOR_SIZE_64 = 20 +STRING_END_ARCHIVE = b'PK\x05\x06' # standard EOCD signature +STRING_END_LOCATOR_64 = b'PK\x06\x07' # Zip64 EOCD Locator signature +STRING_END_ZIP_64 = b'PK\x06\x06' # Zip64 EOCD signature MAX_COMMENT_LEN = (1 << 16) - 1 +MAX_UINT32 = 0xffffffff +ZIP64_EXTRA_TAG = 0x1 class zipimporter(_bootstrap_external._LoaderBasics): """zipimporter(archivepath) -> zipimporter object @@ -63,8 +67,7 @@ class zipimporter(_bootstrap_external._LoaderBasics): # if found, or else read it from the archive. def __init__(self, path): if not isinstance(path, str): - import os - path = os.fsdecode(path) + raise TypeError(f"expected str, not {type(path)!r}") if not path: raise ZipImportError('archive path is empty', path=path) if alt_path_sep: @@ -89,12 +92,8 @@ def __init__(self, path): raise ZipImportError('not a Zip file', path=path) break - try: - files = _zip_directory_cache[path] - except KeyError: - files = _read_directory(path) - _zip_directory_cache[path] = files - self._files = files + if path not in _zip_directory_cache: + _zip_directory_cache[path] = _read_directory(path) self.archive = path # a prefix directory following the ZIP file path. self.prefix = _bootstrap_external._path_join(*prefix[::-1]) @@ -102,64 +101,6 @@ def __init__(self, path): self.prefix += path_sep - # Check whether we can satisfy the import of the module named by - # 'fullname', or whether it could be a portion of a namespace - # package. Return self if we can load it, a string containing the - # full path if it's a possible namespace portion, None if we - # can't load it. - def find_loader(self, fullname, path=None): - """find_loader(fullname, path=None) -> self, str or None. - - Search for a module specified by 'fullname'. 'fullname' must be the - fully qualified (dotted) module name. It returns the zipimporter - instance itself if the module was found, a string containing the - full path name if it's possibly a portion of a namespace package, - or None otherwise. The optional 'path' argument is ignored -- it's - there for compatibility with the importer protocol. - - Deprecated since Python 3.10. Use find_spec() instead. - """ - _warnings.warn("zipimporter.find_loader() is deprecated and slated for " - "removal in Python 3.12; use find_spec() instead", - DeprecationWarning) - mi = _get_module_info(self, fullname) - if mi is not None: - # This is a module or package. - return self, [] - - # Not a module or regular package. See if this is a directory, and - # therefore possibly a portion of a namespace package. - - # We're only interested in the last path component of fullname - # earlier components are recorded in self.prefix. - modpath = _get_module_path(self, fullname) - if _is_dir(self, modpath): - # This is possibly a portion of a namespace - # package. Return the string representing its path, - # without a trailing separator. - return None, [f'{self.archive}{path_sep}{modpath}'] - - return None, [] - - - # Check whether we can satisfy the import of the module named by - # 'fullname'. Return self if we can, None if we can't. - def find_module(self, fullname, path=None): - """find_module(fullname, path=None) -> self or None. - - Search for a module specified by 'fullname'. 'fullname' must be the - fully qualified (dotted) module name. It returns the zipimporter - instance itself if the module was found, or None if it wasn't. - The optional 'path' argument is ignored -- it's there for compatibility - with the importer protocol. - - Deprecated since Python 3.10. Use find_spec() instead. - """ - _warnings.warn("zipimporter.find_module() is deprecated and slated for " - "removal in Python 3.12; use find_spec() instead", - DeprecationWarning) - return self.find_loader(fullname, path)[0] - def find_spec(self, fullname, target=None): """Create a ModuleSpec for the specified module. @@ -211,7 +152,7 @@ def get_data(self, pathname): key = pathname[len(self.archive + path_sep):] try: - toc_entry = self._files[key] + toc_entry = self._get_files()[key] except KeyError: raise OSError(0, '', key) return _get_data(self.archive, toc_entry) @@ -248,7 +189,7 @@ def get_source(self, fullname): fullpath = f'{path}.py' try: - toc_entry = self._files[fullpath] + toc_entry = self._get_files()[fullpath] except KeyError: # we have the module, but no source return None @@ -313,28 +254,28 @@ def load_module(self, fullname): def get_resource_reader(self, fullname): - """Return the ResourceReader for a package in a zip file. - - If 'fullname' is a package within the zip file, return the - 'ResourceReader' object for the package. Otherwise return None. - """ - try: - if not self.is_package(fullname): - return None - except ZipImportError: - return None + """Return the ResourceReader for a module in a zip file.""" from importlib.readers import ZipReader + return ZipReader(self, fullname) - def invalidate_caches(self): - """Reload the file data of the archive path.""" + def _get_files(self): + """Return the files within the archive path.""" try: - self._files = _read_directory(self.archive) - _zip_directory_cache[self.archive] = self._files - except ZipImportError: - _zip_directory_cache.pop(self.archive, None) - self._files = {} + files = _zip_directory_cache[self.archive] + except KeyError: + try: + files = _zip_directory_cache[self.archive] = _read_directory(self.archive) + except ZipImportError: + files = {} + + return files + + + def invalidate_caches(self): + """Invalidates the cache of file data of the archive path.""" + _zip_directory_cache.pop(self.archive, None) def __repr__(self): @@ -364,15 +305,15 @@ def _is_dir(self, path): # of a namespace package. We test by seeing if the name, with an # appended path separator, exists. dirpath = path + path_sep - # If dirpath is present in self._files, we have a directory. - return dirpath in self._files + # If dirpath is present in self._get_files(), we have a directory. + return dirpath in self._get_files() # Return some information about a module. def _get_module_info(self, fullname): path = _get_module_path(self, fullname) for suffix, isbytecode, ispackage in _zip_searchorder: fullpath = path + suffix - if fullpath in self._files: + if fullpath in self._get_files(): return ispackage return None @@ -406,16 +347,11 @@ def _read_directory(archive): raise ZipImportError(f"can't open Zip file: {archive!r}", path=archive) with fp: + # GH-87235: On macOS all file descriptors for /dev/fd/N share the same + # file offset, reset the file offset after scanning the zipfile directory + # to not cause problems when some runs 'python3 /dev/fd/9 9= 0 and pos64+END_CENTRAL_DIR_SIZE_64+END_CENTRAL_DIR_LOCATOR_SIZE_64==pos): + # Zip64 at "correct" offset from standard EOCD + buffer = data[pos64:pos64 + END_CENTRAL_DIR_SIZE_64] + if len(buffer) != END_CENTRAL_DIR_SIZE_64: + raise ZipImportError( + f"corrupt Zip64 file: Expected {END_CENTRAL_DIR_SIZE_64} byte " + f"zip64 central directory, but read {len(buffer)} bytes.", + path=archive) + header_position = file_size - len(data) + pos64 + + central_directory_size = _unpack_uint64(buffer[40:48]) + central_directory_position = _unpack_uint64(buffer[48:56]) + num_entries = _unpack_uint64(buffer[24:32]) + elif pos >= 0: + buffer = data[pos:pos+END_CENTRAL_DIR_SIZE] + if len(buffer) != END_CENTRAL_DIR_SIZE: + raise ZipImportError(f"corrupt Zip file: {archive!r}", + path=archive) + + header_position = file_size - len(data) + pos + + # Buffer now contains a valid EOCD, and header_position gives the + # starting position of it. + central_directory_size = _unpack_uint32(buffer[12:16]) + central_directory_position = _unpack_uint32(buffer[16:20]) + num_entries = _unpack_uint16(buffer[8:10]) + + # N.b. if someday you want to prefer the standard (non-zip64) EOCD, + # you need to adjust position by 76 for arc to be 0. + else: raise ZipImportError(f'not a Zip file: {archive!r}', path=archive) - buffer = data[pos:pos+END_CENTRAL_DIR_SIZE] - if len(buffer) != END_CENTRAL_DIR_SIZE: - raise ZipImportError(f"corrupt Zip file: {archive!r}", - path=archive) - header_position = file_size - len(data) + pos - - header_size = _unpack_uint32(buffer[12:16]) - header_offset = _unpack_uint32(buffer[16:20]) - if header_position < header_size: - raise ZipImportError(f'bad central directory size: {archive!r}', path=archive) - if header_position < header_offset: - raise ZipImportError(f'bad central directory offset: {archive!r}', path=archive) - header_position -= header_size - arc_offset = header_position - header_offset - if arc_offset < 0: - raise ZipImportError(f'bad central directory size or offset: {archive!r}', path=archive) - - files = {} - # Start of Central Directory - count = 0 - try: - fp.seek(header_position) - except OSError: - raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive) - while True: - buffer = fp.read(46) - if len(buffer) < 4: - raise EOFError('EOF read where not expected') - # Start of file header - if buffer[:4] != b'PK\x01\x02': - break # Bad: Central Dir File Header - if len(buffer) != 46: - raise EOFError('EOF read where not expected') - flags = _unpack_uint16(buffer[8:10]) - compress = _unpack_uint16(buffer[10:12]) - time = _unpack_uint16(buffer[12:14]) - date = _unpack_uint16(buffer[14:16]) - crc = _unpack_uint32(buffer[16:20]) - data_size = _unpack_uint32(buffer[20:24]) - file_size = _unpack_uint32(buffer[24:28]) - name_size = _unpack_uint16(buffer[28:30]) - extra_size = _unpack_uint16(buffer[30:32]) - comment_size = _unpack_uint16(buffer[32:34]) - file_offset = _unpack_uint32(buffer[42:46]) - header_size = name_size + extra_size + comment_size - if file_offset > header_offset: - raise ZipImportError(f'bad local header offset: {archive!r}', path=archive) - file_offset += arc_offset + # Buffer now contains a valid EOCD, and header_position gives the + # starting position of it. + # XXX: These are cursory checks but are not as exact or strict as they + # could be. Checking the arc-adjusted value is probably good too. + if header_position < central_directory_size: + raise ZipImportError(f'bad central directory size: {archive!r}', path=archive) + if header_position < central_directory_position: + raise ZipImportError(f'bad central directory offset: {archive!r}', path=archive) + header_position -= central_directory_size + # On just-a-zipfile these values are the same and arc_offset is zero; if + # the file has some bytes prepended, `arc_offset` is the number of such + # bytes. This is used for pex as well as self-extracting .exe. + arc_offset = header_position - central_directory_position + if arc_offset < 0: + raise ZipImportError(f'bad central directory size or offset: {archive!r}', path=archive) + + files = {} + # Start of Central Directory + count = 0 try: - name = fp.read(name_size) - except OSError: - raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive) - if len(name) != name_size: - raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive) - # On Windows, calling fseek to skip over the fields we don't use is - # slower than reading the data because fseek flushes stdio's - # internal buffers. See issue #8745. - try: - if len(fp.read(header_size - name_size)) != header_size - name_size: - raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive) + fp.seek(header_position) except OSError: raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive) + while True: + buffer = fp.read(46) + if len(buffer) < 4: + raise EOFError('EOF read where not expected') + # Start of file header + if buffer[:4] != b'PK\x01\x02': + if count != num_entries: + raise ZipImportError( + f"mismatched num_entries: {count} should be {num_entries} in {archive!r}", + path=archive, + ) + break # Bad: Central Dir File Header + if len(buffer) != 46: + raise EOFError('EOF read where not expected') + flags = _unpack_uint16(buffer[8:10]) + compress = _unpack_uint16(buffer[10:12]) + time = _unpack_uint16(buffer[12:14]) + date = _unpack_uint16(buffer[14:16]) + crc = _unpack_uint32(buffer[16:20]) + data_size = _unpack_uint32(buffer[20:24]) + file_size = _unpack_uint32(buffer[24:28]) + name_size = _unpack_uint16(buffer[28:30]) + extra_size = _unpack_uint16(buffer[30:32]) + comment_size = _unpack_uint16(buffer[32:34]) + file_offset = _unpack_uint32(buffer[42:46]) + header_size = name_size + extra_size + comment_size - if flags & 0x800: - # UTF-8 file names extension - name = name.decode() - else: - # Historical ZIP filename encoding try: - name = name.decode('ascii') - except UnicodeDecodeError: - name = name.decode('latin1').translate(cp437_table) - - name = name.replace('/', path_sep) - path = _bootstrap_external._path_join(archive, name) - t = (path, compress, data_size, file_size, file_offset, time, date, crc) - files[name] = t - count += 1 + name = fp.read(name_size) + except OSError: + raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive) + if len(name) != name_size: + raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive) + # On Windows, calling fseek to skip over the fields we don't use is + # slower than reading the data because fseek flushes stdio's + # internal buffers. See issue #8745. + try: + extra_data_len = header_size - name_size + extra_data = memoryview(fp.read(extra_data_len)) + + if len(extra_data) != extra_data_len: + raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive) + except OSError: + raise ZipImportError(f"can't read Zip file: {archive!r}", path=archive) + + if flags & 0x800: + # UTF-8 file names extension + name = name.decode() + else: + # Historical ZIP filename encoding + try: + name = name.decode('ascii') + except UnicodeDecodeError: + name = name.decode('latin1').translate(cp437_table) + + name = name.replace('/', path_sep) + path = _bootstrap_external._path_join(archive, name) + + # Ordering matches unpacking below. + if ( + file_size == MAX_UINT32 or + data_size == MAX_UINT32 or + file_offset == MAX_UINT32 + ): + # need to decode extra_data looking for a zip64 extra (which might not + # be present) + while extra_data: + if len(extra_data) < 4: + raise ZipImportError(f"can't read header extra: {archive!r}", path=archive) + tag = _unpack_uint16(extra_data[:2]) + size = _unpack_uint16(extra_data[2:4]) + if len(extra_data) < 4 + size: + raise ZipImportError(f"can't read header extra: {archive!r}", path=archive) + if tag == ZIP64_EXTRA_TAG: + if (len(extra_data) - 4) % 8 != 0: + raise ZipImportError(f"can't read header extra: {archive!r}", path=archive) + num_extra_values = (len(extra_data) - 4) // 8 + if num_extra_values > 3: + raise ZipImportError(f"can't read header extra: {archive!r}", path=archive) + import struct + values = list(struct.unpack_from(f"<{min(num_extra_values, 3)}Q", + extra_data, offset=4)) + + # N.b. Here be dragons: the ordering of these is different than + # the header fields, and it's really easy to get it wrong since + # naturally-occuring zips that use all 3 are >4GB + if file_size == MAX_UINT32: + file_size = values.pop(0) + if data_size == MAX_UINT32: + data_size = values.pop(0) + if file_offset == MAX_UINT32: + file_offset = values.pop(0) + + break + + # For a typical zip, this bytes-slicing only happens 2-3 times, on + # small data like timestamps and filesizes. + extra_data = extra_data[4+size:] + else: + _bootstrap._verbose_message( + "zipimport: suspected zip64 but no zip64 extra for {!r}", + path, + ) + # XXX These two statements seem swapped because `central_directory_position` + # is a position within the actual file, but `file_offset` (when compared) is + # as encoded in the entry, not adjusted for this file. + # N.b. this must be after we've potentially read the zip64 extra which can + # change `file_offset`. + if file_offset > central_directory_position: + raise ZipImportError(f'bad local header offset: {archive!r}', path=archive) + file_offset += arc_offset + + t = (path, compress, data_size, file_size, file_offset, time, date, crc) + files[name] = t + count += 1 + finally: + fp.seek(start_offset) _bootstrap._verbose_message('zipimport: found {} names in {!r}', count, archive) return files @@ -708,7 +739,7 @@ def _get_mtime_and_size_of_source(self, path): # strip 'c' or 'o' from *.py[co] assert path[-1:] in ('c', 'o') path = path[:-1] - toc_entry = self._files[path] + toc_entry = self._get_files()[path] # fetch the time stamp of the .py file for comparison # with an embedded pyc time stamp time = toc_entry[5] @@ -728,7 +759,7 @@ def _get_pyc_source(self, path): path = path[:-1] try: - toc_entry = self._files[path] + toc_entry = self._get_files()[path] except KeyError: return None else: @@ -744,7 +775,7 @@ def _get_module_code(self, fullname): fullpath = path + suffix _bootstrap._verbose_message('trying {}{}{}', self.archive, path_sep, fullpath, verbosity=2) try: - toc_entry = self._files[fullpath] + toc_entry = self._get_files()[fullpath] except KeyError: pass else: From 7becac9a101ee0ad11458eb12b950ccb3e0e348e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 08:52:40 +0900 Subject: [PATCH 701/819] Bump proc-macro2 from 1.0.104 to 1.0.105 (#6664) Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.104 to 1.0.105. - [Release notes](https://github.com/dtolnay/proc-macro2/releases) - [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.104...1.0.105) --- updated-dependencies: - dependency-name: proc-macro2 dependency-version: 1.0.105 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e03c1221781..b0b01ff79ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2447,9 +2447,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index 1ef31f825f7..728f657f052 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -193,7 +193,7 @@ optional = "0.5" once_cell = "1.20.3" parking_lot = "0.12.3" paste = "1.0.15" -proc-macro2 = "1.0.93" +proc-macro2 = "1.0.105" pymath = "0.0.2" quote = "1.0.38" radium = "1.1.1" From 8df2df3ed72807559618a2c21e37b62d2e450b4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 08:52:50 +0900 Subject: [PATCH 702/819] Bump webpki-roots from 1.0.4 to 1.0.5 (#6665) Bumps [webpki-roots](https://github.com/rustls/webpki-roots) from 1.0.4 to 1.0.5. - [Release notes](https://github.com/rustls/webpki-roots/releases) - [Commits](https://github.com/rustls/webpki-roots/compare/v/1.0.4...v/1.0.5) --- updated-dependencies: - dependency-name: webpki-roots dependency-version: 1.0.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0b01ff79ff..30a59b5e41c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4339,9 +4339,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] From 985961b0130021db24fc9d9468cce94d2ae91669 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 08:54:10 +0900 Subject: [PATCH 703/819] Bump rustls from 0.23.35 to 0.23.36 (#6666) Bumps [rustls](https://github.com/rustls/rustls) from 0.23.35 to 0.23.36. - [Release notes](https://github.com/rustls/rustls/releases) - [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md) - [Commits](https://github.com/rustls/rustls/compare/v/0.23.35...v/0.23.36) --- updated-dependencies: - dependency-name: rustls dependency-version: 0.23.36 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30a59b5e41c..03016e55eec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2872,9 +2872,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "once_cell", diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index e943470a3af..d39f0ab43b4 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -119,7 +119,7 @@ openssl-probe = { version = "0.1.5", optional = true } foreign-types-shared = { version = "0.1.1", optional = true } # Rustls dependencies (optional, for ssl-rustls feature) -rustls = { version = "0.23.35", default-features = false, features = ["std", "tls12", "aws_lc_rs"], optional = true } +rustls = { version = "0.23.36", default-features = false, features = ["std", "tls12", "aws_lc_rs"], optional = true } rustls-native-certs = { version = "0.8", optional = true } rustls-pemfile = { version = "2.2", optional = true } rustls-platform-verifier = { version = "0.6", optional = true } From 4fbf617c06e6fb445e8815de798e3731cb2d99ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 08:54:21 +0900 Subject: [PATCH 704/819] Bump quote from 1.0.42 to 1.0.43 (#6667) Bumps [quote](https://github.com/dtolnay/quote) from 1.0.42 to 1.0.43. - [Release notes](https://github.com/dtolnay/quote/releases) - [Commits](https://github.com/dtolnay/quote/compare/1.0.42...1.0.43) --- updated-dependencies: - dependency-name: quote dependency-version: 1.0.43 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03016e55eec..d8b09bf94b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2526,9 +2526,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] diff --git a/Cargo.toml b/Cargo.toml index 728f657f052..fe28491b520 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -195,7 +195,7 @@ parking_lot = "0.12.3" paste = "1.0.15" proc-macro2 = "1.0.105" pymath = "0.0.2" -quote = "1.0.38" +quote = "1.0.43" radium = "1.1.1" rand = "0.9" rand_core = { version = "0.9", features = ["os_rng"] } From 6408228b3e7c843786416358c099739db50ce44f Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Wed, 7 Jan 2026 10:09:51 +0900 Subject: [PATCH 705/819] Update DEVELOPMENT.md (#6671) * Update DEVELOPMENT.md to reflect crates/ directory structure * Update DEVELOPMENT.md to reflect Ruff parser-related changes --- DEVELOPMENT.md | 54 ++++++++++++++++++++++---------------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index d5c675faca6..82364e9b812 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -19,7 +19,7 @@ The contents of the Development Guide include: RustPython requires the following: -- Rust latest stable version (e.g 1.69.0 as of Apr 20 2023) +- Rust latest stable version (e.g 1.92.0 as of Jan 7 2026) - To check Rust version: `rustc --version` - If you have `rustup` on your system, enter to update to the latest stable version: `rustup update stable` @@ -118,18 +118,17 @@ exists a raw html viewer which is currently broken, and we welcome a PR to fix i Understanding a new codebase takes time. Here's a brief view of the repository's structure: -- `compiler/src`: python compilation to bytecode - - `core/src`: python bytecode representation in rust structures - - `parser/src`: python lexing, parsing and ast -- `derive/src`: Rust language extensions and macros specific to rustpython +- `crates/compiler/src`: python compilation to bytecode + - `crates/compiler-core/src`: python bytecode representation in rust structures +- `crates/derive/src` and `crates/derive-impl/src`: Rust language extensions and macros specific to rustpython - `Lib`: Carefully selected / copied files from CPython sourcecode. This is the python side of the standard library. - `test`: CPython test suite -- `vm/src`: python virtual machine +- `crates/vm/src`: python virtual machine - `builtins`: Builtin functions and types - `stdlib`: Standard library parts implemented in rust. - `src`: using the other subcrates to bring rustpython to life. -- `wasm`: Binary crate and resources for WebAssembly build +- `crates/wasm`: Binary crate and resources for WebAssembly build - `extra_tests`: extra integration test snippets as a supplement to `Lib/test`. Add new RustPython-only regression tests here; do not place new tests under `Lib/test`. @@ -141,9 +140,9 @@ implementation is found in the `src` directory (specifically, `src/lib.rs`). The top-level `rustpython` binary depends on several lower-level crates including: -- `rustpython-parser` (implementation in `compiler/parser/src`) -- `rustpython-compiler` (implementation in `compiler/src`) -- `rustpython-vm` (implementation in `vm/src`) +- `ruff_python_parser` and `ruff_python_ast` (external dependencies from the Ruff project) +- `rustpython-compiler` (implementation in `crates/compiler/src`) +- `rustpython-vm` (implementation in `crates/vm/src`) Together, these crates provide the functions of a programming language and enable a line of code to go through a series of steps: @@ -154,31 +153,26 @@ enable a line of code to go through a series of steps: - compile the AST into bytecode - execute the bytecode in the virtual machine (VM). -### rustpython-parser +### Parser and AST -This crate contains the lexer and parser to convert a line of code to -an Abstract Syntax Tree (AST): +RustPython uses the Ruff project's parser and AST implementation: -- Lexer: `compiler/parser/src/lexer.rs` converts Python source code into tokens -- Parser: `compiler/parser/src/parser.rs` takes the tokens generated by the lexer and parses - the tokens into an AST (Abstract Syntax Tree) where the nodes of the syntax - tree are Rust structs and enums. - - The Parser relies on `LALRPOP`, a Rust parser generator framework. The - LALRPOP definition of Python's grammar is in `compiler/parser/src/python.lalrpop`. - - More information on parsers and a tutorial can be found in the - [LALRPOP book](https://lalrpop.github.io/lalrpop/). -- AST: `compiler/ast/` implements in Rust the Python types and expressions - represented by the AST nodes. +- Parser: `ruff_python_parser` is used to convert Python source code into tokens + and parse them into an Abstract Syntax Tree (AST) +- AST: `ruff_python_ast` provides the Rust types and expressions represented by + the AST nodes +- These are external dependencies maintained by the Ruff project +- For more information, visit the [Ruff GitHub repository](https://github.com/astral-sh/ruff) ### rustpython-compiler The `rustpython-compiler` crate's purpose is to transform the AST (Abstract Syntax Tree) to bytecode. The implementation of the compiler is found in the -`compiler/src` directory. The compiler implements Python's symbol table, +`crates/compiler/src` directory. The compiler implements Python's symbol table, ast->bytecode compiler, and bytecode optimizer in Rust. -Implementation of bytecode structure in Rust is found in the `compiler/core/src` -directory. `compiler/core/src/bytecode.rs` contains the representation of +Implementation of bytecode structure in Rust is found in the `crates/compiler-core/src` +directory. `crates/compiler-core/src/bytecode.rs` contains the representation of instructions and operations in Rust. Further information about Python's bytecode instructions can be found in the [Python documentation](https://docs.python.org/3/library/dis.html#bytecodes). @@ -186,14 +180,14 @@ bytecode instructions can be found in the ### rustpython-vm The `rustpython-vm` crate has the important job of running the virtual machine that -executes Python's instructions. The `vm/src` directory contains code to +executes Python's instructions. The `crates/vm/src` directory contains code to implement the read and evaluation loop that fetches and dispatches instructions. This directory also contains the implementation of the -Python Standard Library modules in Rust (`vm/src/stdlib`). In Python -everything can be represented as an object. The `vm/src/builtins` directory holds +Python Standard Library modules in Rust (`crates/vm/src/stdlib`). In Python +everything can be represented as an object. The `crates/vm/src/builtins` directory holds the Rust code used to represent different Python objects and their methods. The core implementation of what a Python object is can be found in -`vm/src/object/core.rs`. +`crates/vm/src/object/core.rs`. ### Code generation From 42cc59a90c35ffd12d4c793cf155ea4e885c5de1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 10:13:13 +0900 Subject: [PATCH 706/819] Bump openssl-probe from 0.1.6 to 0.2.0 (#6663) * Bump openssl-probe from 0.1.6 to 0.2.0 Bumps [openssl-probe](https://github.com/alexcrichton/openssl-probe) from 0.1.6 to 0.2.0. - [Release notes](https://github.com/alexcrichton/openssl-probe/releases) - [Commits](https://github.com/alexcrichton/openssl-probe/compare/0.1.6...0.2.0) --- updated-dependencies: - dependency-name: openssl-probe dependency-version: 0.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Fix openssl-probe 0.2.0 API compatibility (#6669) * Bump openssl-probe from 0.1.6 to 0.2.0 Bumps [openssl-probe](https://github.com/alexcrichton/openssl-probe) from 0.1.6 to 0.2.0. - [Release notes](https://github.com/alexcrichton/openssl-probe/releases) - [Commits](https://github.com/alexcrichton/openssl-probe/compare/0.1.6...0.2.0) --- updated-dependencies: - dependency-name: openssl-probe dependency-version: 0.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Initial plan * Fix openssl-probe 0.2.0 API compatibility --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- Cargo.lock | 10 ++-------- crates/stdlib/Cargo.toml | 2 +- crates/stdlib/src/openssl.rs | 7 ++++--- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8b09bf94b2..3b676e2a785 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2134,12 +2134,6 @@ dependencies = [ "syn", ] -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.0" @@ -2890,7 +2884,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.0", + "openssl-probe", "rustls-pki-types", "schannel", "security-framework", @@ -3184,7 +3178,7 @@ dependencies = [ "num_enum", "oid-registry", "openssl", - "openssl-probe 0.1.6", + "openssl-probe", "openssl-sys", "page_size", "parking_lot", diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index d39f0ab43b4..4af890b8e69 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -115,7 +115,7 @@ dns-lookup = "3.0" # OpenSSL dependencies (optional, for ssl-openssl feature) openssl = { version = "0.10.72", optional = true } openssl-sys = { version = "0.9.110", optional = true } -openssl-probe = { version = "0.1.5", optional = true } +openssl-probe = { version = "0.2.0", optional = true } foreign-types-shared = { version = "0.1.1", optional = true } # Rustls dependencies (optional, for ssl-rustls feature) diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index 38103a9ab05..df153459f62 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -25,6 +25,7 @@ cfg_if::cfg_if! { use crate::vm::{PyRef, VirtualMachine, builtins::PyModule}; use openssl_probe::ProbeResult; +use std::sync::LazyLock; pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { // if openssl is vendored, it doesn't know the locations @@ -38,12 +39,12 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { // easily, without having to have a bunch of cfgs cfg_if::cfg_if! { if #[cfg(openssl_vendored)] { - use std::sync::LazyLock; static PROBE: LazyLock = LazyLock::new(openssl_probe::probe); fn probe() -> &'static ProbeResult { &PROBE } } else { + static EMPTY_PROBE: LazyLock = LazyLock::new(|| ProbeResult { cert_file: None, cert_dir: vec![] }); fn probe() -> &'static ProbeResult { - &ProbeResult { cert_file: None, cert_dir: None } + &EMPTY_PROBE } } } @@ -446,7 +447,7 @@ mod _ssl { }); let cert_dir = probe .cert_dir - .as_ref() + .first() .map(PathBuf::from) .unwrap_or_else(|| { path_from_cstr(unsafe { CStr::from_ptr(sys::X509_get_default_cert_dir()) }) From 09bde28281dfdaaa65c3a44435b3426aa9213547 Mon Sep 17 00:00:00 2001 From: Noa Date: Tue, 6 Jan 2026 21:33:29 -0600 Subject: [PATCH 707/819] Move typeid into vtable (#6231) * Move typeid into vtable * Bump rust-version --- Cargo.toml | 2 +- crates/derive-impl/src/pyclass.rs | 5 +---- crates/derive-impl/src/pystructseq.rs | 5 +---- crates/vm/src/builtins/str.rs | 4 +--- crates/vm/src/object/core.rs | 9 ++------- crates/vm/src/object/payload.rs | 7 ++----- crates/vm/src/object/traverse_object.rs | 3 +++ 7 files changed, 11 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fe28491b520..d127630f1ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,7 +131,7 @@ members = [ version = "0.4.0" authors = ["RustPython Team"] edition = "2024" -rust-version = "1.89.0" +rust-version = "1.91.0" repository = "https://github.com/RustPython/RustPython" license = "MIT" diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 028d3d7c292..a81a7bacbad 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -635,10 +635,7 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result() <= std::mem::size_of::<#ident>()); impl ::rustpython_vm::PyPayload for #ident { - #[inline] - fn payload_type_id() -> ::std::any::TypeId { - <#base_type as ::rustpython_vm::PyPayload>::payload_type_id() - } + const PAYLOAD_TYPE_ID: ::core::any::TypeId = <#base_type as ::rustpython_vm::PyPayload>::PAYLOAD_TYPE_ID; #[inline] fn validate_downcastable_from(obj: &::rustpython_vm::PyObject) -> bool { diff --git a/crates/derive-impl/src/pystructseq.rs b/crates/derive-impl/src/pystructseq.rs index 32b603fe478..0ee0c2c2e3c 100644 --- a/crates/derive-impl/src/pystructseq.rs +++ b/crates/derive-impl/src/pystructseq.rs @@ -592,10 +592,7 @@ pub(crate) fn impl_pystruct_sequence( // Subtype uses base type's payload_type_id impl ::rustpython_vm::PyPayload for #pytype_ident { - #[inline] - fn payload_type_id() -> ::std::any::TypeId { - <::rustpython_vm::builtins::PyTuple as ::rustpython_vm::PyPayload>::payload_type_id() - } + const PAYLOAD_TYPE_ID: ::core::any::TypeId = <::rustpython_vm::builtins::PyTuple as ::rustpython_vm::PyPayload>::PAYLOAD_TYPE_ID; #[inline] fn validate_downcastable_from(obj: &::rustpython_vm::PyObject) -> bool { diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 4931a748198..640778c8cb9 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -1935,9 +1935,7 @@ impl PyPayload for PyUtf8Str { ctx.types.str_type } - fn payload_type_id() -> core::any::TypeId { - core::any::TypeId::of::() - } + const PAYLOAD_TYPE_ID: core::any::TypeId = core::any::TypeId::of::(); fn validate_downcastable_from(obj: &PyObject) -> bool { // SAFETY: we know the object is a PyStr in this context diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index e904117b06a..271a9cc34d2 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -105,8 +105,6 @@ pub(super) unsafe fn try_trace_obj(x: &PyObject, tracer_fn: &mut T #[repr(C)] pub(super) struct PyInner { pub(super) ref_count: RefCount, - // TODO: move typeid into vtable once TypeId::of is const - pub(super) typeid: TypeId, pub(super) vtable: &'static PyObjVTable, pub(super) typ: PyAtomicRef, // __class__ member @@ -449,7 +447,6 @@ impl PyInner { let member_count = typ.slots.member_count; Box::new(Self { ref_count: RefCount::new(), - typeid: T::payload_type_id(), vtable: PyObjVTable::of::(), typ: PyAtomicRef::from(typ), dict: dict.map(InstanceDict::new), @@ -639,7 +636,7 @@ impl PyObject { #[deprecated(note = "use downcastable instead")] #[inline(always)] pub fn payload_is(&self) -> bool { - self.0.typeid == T::payload_type_id() + self.0.vtable.typeid == T::PAYLOAD_TYPE_ID } /// Force to return payload as T. @@ -722,7 +719,7 @@ impl PyObject { #[inline] pub(crate) fn typeid(&self) -> TypeId { - self.0.typeid + self.0.vtable.typeid } /// Check if this object can be downcast to T. @@ -1276,7 +1273,6 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) { let type_type_ptr = Box::into_raw(Box::new(partially_init!( PyInner:: { ref_count: RefCount::new(), - typeid: TypeId::of::(), vtable: PyObjVTable::of::(), dict: None, weak_list: WeakRefList::new(), @@ -1288,7 +1284,6 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) { let object_type_ptr = Box::into_raw(Box::new(partially_init!( PyInner:: { ref_count: RefCount::new(), - typeid: TypeId::of::(), vtable: PyObjVTable::of::(), dict: None, weak_list: WeakRefList::new(), diff --git a/crates/vm/src/object/payload.rs b/crates/vm/src/object/payload.rs index 3a2f42675f7..143033ee642 100644 --- a/crates/vm/src/object/payload.rs +++ b/crates/vm/src/object/payload.rs @@ -26,15 +26,12 @@ pub(crate) fn cold_downcast_type_error( } pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { - #[inline] - fn payload_type_id() -> core::any::TypeId { - core::any::TypeId::of::() - } + const PAYLOAD_TYPE_ID: core::any::TypeId = core::any::TypeId::of::(); /// # Safety: this function should only be called if `payload_type_id` matches the type of `obj`. #[inline] fn downcastable_from(obj: &PyObject) -> bool { - obj.typeid() == Self::payload_type_id() && Self::validate_downcastable_from(obj) + obj.typeid() == Self::PAYLOAD_TYPE_ID && Self::validate_downcastable_from(obj) } #[inline] diff --git a/crates/vm/src/object/traverse_object.rs b/crates/vm/src/object/traverse_object.rs index 075ce5b9513..7a66f0b35f0 100644 --- a/crates/vm/src/object/traverse_object.rs +++ b/crates/vm/src/object/traverse_object.rs @@ -1,4 +1,5 @@ use alloc::fmt; +use core::any::TypeId; use crate::{ PyObject, @@ -11,6 +12,7 @@ use crate::{ use super::{Traverse, TraverseFn}; pub(in crate::object) struct PyObjVTable { + pub(in crate::object) typeid: TypeId, pub(in crate::object) drop_dealloc: unsafe fn(*mut PyObject), pub(in crate::object) debug: unsafe fn(&PyObject, &mut fmt::Formatter<'_>) -> fmt::Result, pub(in crate::object) trace: Option)>, @@ -19,6 +21,7 @@ pub(in crate::object) struct PyObjVTable { impl PyObjVTable { pub const fn of() -> &'static Self { &Self { + typeid: T::PAYLOAD_TYPE_ID, drop_dealloc: drop_dealloc_obj::, debug: debug_obj::, trace: const { From bf80d2715d353d063c9595d4c99639e404d52cec Mon Sep 17 00:00:00 2001 From: Noa Date: Tue, 6 Jan 2026 21:53:44 -0600 Subject: [PATCH 708/819] Update to windows-2025 runner on ci (#5571) * Update to windows-2025 on ci * Unmark unexpected successes * Try adding .dll --- .github/workflows/ci.yaml | 22 ++++------------------ .github/workflows/release.yml | 15 +++++---------- Cargo.toml | 10 ---------- Lib/test/test_shutil.py | 1 - crates/common/src/fileutils.rs | 3 ++- 5 files changed, 11 insertions(+), 40 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a873aa2d29d..dc7b4bc502d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -107,6 +107,8 @@ env: test_yield_from # Python version targeted by the CI. PYTHON_VERSION: "3.13.1" + X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD + X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include jobs: rust_tests: @@ -118,7 +120,7 @@ jobs: timeout-minutes: 45 strategy: matrix: - os: [macos-latest, ubuntu-latest, windows-latest] + os: [macos-latest, ubuntu-latest, windows-2025] fail-fast: false steps: - uses: actions/checkout@v6.0.1 @@ -127,14 +129,6 @@ jobs: components: clippy - uses: Swatinem/rust-cache@v2 - # Only for OpenSSL builds - # - name: Set up the Windows environment - # shell: bash - # run: | - # git config --system core.longpaths true - # cargo install --target-dir=target -v cargo-vcpkg - # cargo vcpkg -v build - # if: runner.os == 'Windows' - name: Set up the Mac environment run: brew install autoconf automake libtool if: runner.os == 'macOS' @@ -257,7 +251,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macos-latest, ubuntu-latest, windows-latest] + os: [macos-latest, ubuntu-latest, windows-2025] fail-fast: false steps: - uses: actions/checkout@v6.0.1 @@ -266,14 +260,6 @@ jobs: - uses: actions/setup-python@v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - # Only for OpenSSL builds - # - name: Set up the Windows environment - # shell: bash - # run: | - # git config --system core.longpaths true - # cargo install cargo-vcpkg - # cargo vcpkg build - # if: runner.os == 'Windows' - name: Set up the Mac environment run: brew install autoconf automake libtool openssl@3 if: runner.os == 'macOS' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5ad2cf3f97a..c11d11d9805 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,8 @@ permissions: env: CARGO_ARGS: --no-default-features --features stdlib,importlib,encodings,sqlite,ssl + X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD + X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include jobs: build: @@ -42,11 +44,11 @@ jobs: target: aarch64-apple-darwin # - runner: macos-latest # target: x86_64-apple-darwin - - runner: windows-latest + - runner: windows-2025 target: x86_64-pc-windows-msvc -# - runner: windows-latest +# - runner: windows-2025 # target: i686-pc-windows-msvc -# - runner: windows-latest +# - runner: windows-2025 # target: aarch64-pc-windows-msvc fail-fast: false steps: @@ -57,13 +59,6 @@ jobs: - name: Set up Environment shell: bash run: rustup target add ${{ matrix.platform.target }} - - name: Set up Windows Environment - shell: bash - run: | - git config --global core.longpaths true - 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' diff --git a/Cargo.toml b/Cargo.toml index d127630f1ca..a7ac9ee4999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,16 +91,6 @@ lto = "thin" # REDOX START, Uncomment when you want to compile/check with redoxer # 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 = "2025.09.17" - -[package.metadata.vcpkg.target] -x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md", dev-dependencies = ["openssl" ] } - [package.metadata.packager] product-name = "RustPython" identifier = "com.rustpython.rustpython" diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 73ad81e871b..0e973255621 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -194,7 +194,6 @@ def test_rmtree_works_on_bytes(self): self.assertIsInstance(victim, bytes) shutil.rmtree(victim) - @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; flaky') @os_helper.skip_unless_symlink def test_rmtree_fails_on_symlink_onerror(self): tmp = self.mkdtemp() diff --git a/crates/common/src/fileutils.rs b/crates/common/src/fileutils.rs index 9ed5e77afbb..7170b270d95 100644 --- a/crates/common/src/fileutils.rs +++ b/crates/common/src/fileutils.rs @@ -303,7 +303,8 @@ pub mod windows { let GetFileInformationByName = GET_FILE_INFORMATION_BY_NAME .get_or_init(|| { - let library_name = OsString::from("api-ms-win-core-file-l2-1-4").to_wide_with_nul(); + let library_name = + OsString::from("api-ms-win-core-file-l2-1-4.dll").to_wide_with_nul(); let module = unsafe { LoadLibraryW(library_name.as_ptr()) }; if module.is_null() { return None; From a3425b435e0e17c7f94b33ea7b665a1600ceff02 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 06:32:12 +0900 Subject: [PATCH 709/819] Track symbol table cursors to avoid exhaustion (#6670) * Handle missing symbol table without panic * Update symbol table error message * Track symbol table cursors to avoid exhaustion --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> Co-authored-by: github-actions[bot] --- crates/codegen/src/compile.rs | 71 ++++++++++++++----------------- crates/codegen/src/symboltable.rs | 4 ++ 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 66eb785e962..7a6fdc87b26 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -637,25 +637,29 @@ impl Compiler { } /// Push the next symbol table on to the stack - fn push_symbol_table(&mut self) -> &SymbolTable { + fn push_symbol_table(&mut self) -> CompileResult<&SymbolTable> { // Look up the next table contained in the scope of the current table let current_table = self .symbol_table_stack .last_mut() .expect("no current symbol table"); - if current_table.sub_tables.is_empty() { - panic!( - "push_symbol_table: no sub_tables available in {} (type: {:?})", - current_table.name, current_table.typ - ); + if current_table.next_sub_table >= current_table.sub_tables.len() { + let name = current_table.name.clone(); + let typ = current_table.typ; + return Err(self.error(CodegenErrorType::SyntaxError(format!( + "no symbol table available in {} (type: {:?})", + name, typ + )))); } - let table = current_table.sub_tables.remove(0); + let idx = current_table.next_sub_table; + current_table.next_sub_table += 1; + let table = current_table.sub_tables[idx].clone(); // Push the next table onto the stack self.symbol_table_stack.push(table); - self.current_symbol_table() + Ok(self.current_symbol_table()) } /// Pop the current symbol table off the stack @@ -853,9 +857,9 @@ impl Compiler { arg_count: u32, kwonlyarg_count: u32, obj_name: String, - ) { + ) -> CompileResult<()> { // First push the symbol table - let table = self.push_symbol_table(); + let table = self.push_symbol_table()?; let scope_type = table.typ; // The key is the current position in the symbol table stack @@ -865,11 +869,7 @@ impl Compiler { let lineno = self.get_source_line_number().get(); // Call enter_scope which does most of the work - if let Err(e) = self.enter_scope(&obj_name, scope_type, key, lineno.to_u32()) { - // In the current implementation, push_output doesn't return an error, - // so we panic here. This maintains the same behavior. - panic!("enter_scope failed: {e:?}"); - } + self.enter_scope(&obj_name, scope_type, key, lineno.to_u32())?; // Override the values that push_output sets explicitly // enter_scope sets default values based on scope_type, but push_output @@ -880,6 +880,7 @@ impl Compiler { info.metadata.posonlyargcount = posonlyarg_count; info.metadata.kwonlyargcount = kwonlyarg_count; } + Ok(()) } // compiler_exit_scope @@ -1984,7 +1985,7 @@ impl Compiler { if let Some(type_params) = type_params { // For TypeAlias, we need to use push_symbol_table to properly handle the TypeAlias scope - self.push_symbol_table(); + self.push_symbol_table()?; // Compile type params and push to stack self.compile_type_params(type_params)?; @@ -2067,7 +2068,7 @@ impl Compiler { (parameters.posonlyargs.len() + parameters.args.len()).to_u32(), parameters.kwonlyargs.len().to_u32(), name.to_owned(), - ); + )?; let args_iter = core::iter::empty() .chain(¶meters.posonlyargs) @@ -2113,7 +2114,7 @@ impl Compiler { allow_starred: bool, ) -> CompileResult<()> { // Push the next symbol table onto the stack - self.push_symbol_table(); + self.push_symbol_table()?; // Get the current symbol table let key = self.symbol_table_stack.len() - 1; @@ -2332,13 +2333,8 @@ impl Compiler { // Snapshot sub_tables before first finally compilation // This allows us to restore them for the second compilation (exception path) - let sub_tables_snapshot = if !finalbody.is_empty() && finally_except_block.is_some() { - Some( - self.symbol_table_stack - .last() - .map(|t| t.sub_tables.clone()) - .unwrap_or_default(), - ) + let sub_table_cursor = if !finalbody.is_empty() && finally_except_block.is_some() { + self.symbol_table_stack.last().map(|t| t.next_sub_table) } else { None }; @@ -2353,10 +2349,10 @@ impl Compiler { if let Some(finally_except) = finally_except_block { // Restore sub_tables for exception path compilation - if let Some(snapshot) = sub_tables_snapshot + if let Some(cursor) = sub_table_cursor && let Some(current_table) = self.symbol_table_stack.last_mut() { - current_table.sub_tables = snapshot; + current_table.next_sub_table = cursor; } self.switch_to_block(finally_except); @@ -2617,13 +2613,8 @@ impl Compiler { } // Snapshot sub_tables before first finally compilation (for double compilation issue) - let sub_tables_snapshot = if !finalbody.is_empty() && finally_except_block.is_some() { - Some( - self.symbol_table_stack - .last() - .map(|t| t.sub_tables.clone()) - .unwrap_or_default(), - ) + let sub_table_cursor = if !finalbody.is_empty() && finally_except_block.is_some() { + self.symbol_table_stack.last().map(|t| t.next_sub_table) } else { None }; @@ -2642,10 +2633,10 @@ impl Compiler { // Stack at entry: [lasti, exc] (from exception table with preserve_lasti=true) if let Some(finally_except) = finally_except_block { // Restore sub_tables for exception path compilation - if let Some(snapshot) = sub_tables_snapshot + if let Some(cursor) = sub_table_cursor && let Some(current_table) = self.symbol_table_stack.last_mut() { - current_table.sub_tables = snapshot; + current_table.next_sub_table = cursor; } self.switch_to_block(finally_except); @@ -3246,7 +3237,7 @@ impl Compiler { num_typeparam_args as u32, 0, type_params_name, - ); + )?; // Add parameter names to varnames for the type params scope // These will be passed as arguments when the closure is called @@ -3554,7 +3545,7 @@ impl Compiler { ) -> CompileResult { // 1. Enter class scope let key = self.symbol_table_stack.len(); - self.push_symbol_table(); + self.push_symbol_table()?; self.enter_scope(name, CompilerScope::Class, key, firstlineno)?; // Set qualname using the new method @@ -3660,7 +3651,7 @@ impl Compiler { 0, 0, type_params_name, - ); + )?; // Set private name for name mangling self.code_stack.last_mut().unwrap().private = Some(name.to_owned()); @@ -6322,7 +6313,7 @@ impl Compiler { }; // Create magnificent function : - self.push_output(flags, 1, 1, 0, name.to_owned()); + self.push_output(flags, 1, 1, 0, name.to_owned())?; // Mark that we're in an inlined comprehension self.current_code_info().in_inlined_comp = true; diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index fe4976bb086..47a6ccbbf30 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -44,6 +44,9 @@ pub struct SymbolTable { /// AST nodes. pub sub_tables: Vec, + /// Cursor pointing to the next sub-table to consume during compilation. + pub next_sub_table: usize, + /// Variable names in definition order (parameters first, then locals) pub varnames: Vec, @@ -70,6 +73,7 @@ impl SymbolTable { is_nested, symbols: IndexMap::default(), sub_tables: vec![], + next_sub_table: 0, varnames: Vec::new(), needs_class_closure: false, needs_classdict: false, From b38cdaa30ebf3379c85092df7d627fccfff143b1 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Fri, 9 Jan 2026 01:28:12 -0500 Subject: [PATCH 710/819] Update the concurrent library + Added tests (#6673) * Updated concurrent library * Added test_concurrent_futures from v3.13.11 * Annotated failing tests in test_concurrent_futures --- Lib/concurrent/futures/__init__.py | 3 +- Lib/concurrent/futures/_base.py | 24 +- Lib/concurrent/futures/process.py | 238 ++++++---- Lib/concurrent/futures/thread.py | 27 +- Lib/test/test_concurrent_futures/__init__.py | 18 + Lib/test/test_concurrent_futures/executor.py | 162 +++++++ .../test_as_completed.py | 118 +++++ .../test_concurrent_futures/test_deadlock.py | 332 ++++++++++++++ .../test_concurrent_futures/test_future.py | 291 +++++++++++++ Lib/test/test_concurrent_futures/test_init.py | 152 +++++++ .../test_process_pool.py | 234 ++++++++++ .../test_concurrent_futures/test_shutdown.py | 406 ++++++++++++++++++ .../test_thread_pool.py | 122 ++++++ Lib/test/test_concurrent_futures/test_wait.py | 205 +++++++++ Lib/test/test_concurrent_futures/util.py | 169 ++++++++ 15 files changed, 2400 insertions(+), 101 deletions(-) create mode 100644 Lib/test/test_concurrent_futures/__init__.py create mode 100644 Lib/test/test_concurrent_futures/executor.py create mode 100644 Lib/test/test_concurrent_futures/test_as_completed.py create mode 100644 Lib/test/test_concurrent_futures/test_deadlock.py create mode 100644 Lib/test/test_concurrent_futures/test_future.py create mode 100644 Lib/test/test_concurrent_futures/test_init.py create mode 100644 Lib/test/test_concurrent_futures/test_process_pool.py create mode 100644 Lib/test/test_concurrent_futures/test_shutdown.py create mode 100644 Lib/test/test_concurrent_futures/test_thread_pool.py create mode 100644 Lib/test/test_concurrent_futures/test_wait.py create mode 100644 Lib/test/test_concurrent_futures/util.py diff --git a/Lib/concurrent/futures/__init__.py b/Lib/concurrent/futures/__init__.py index d746aeac50a..72de617a5b6 100644 --- a/Lib/concurrent/futures/__init__.py +++ b/Lib/concurrent/futures/__init__.py @@ -23,6 +23,7 @@ 'ALL_COMPLETED', 'CancelledError', 'TimeoutError', + 'InvalidStateError', 'BrokenExecutor', 'Future', 'Executor', @@ -50,4 +51,4 @@ def __getattr__(name): ThreadPoolExecutor = te return te - raise AttributeError(f"module {__name__} has no attribute {name}") + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py index cf119ac6437..7d69a5baead 100644 --- a/Lib/concurrent/futures/_base.py +++ b/Lib/concurrent/futures/_base.py @@ -50,9 +50,7 @@ class CancelledError(Error): """The Future was cancelled.""" pass -class TimeoutError(Error): - """The operation exceeded the given deadline.""" - pass +TimeoutError = TimeoutError # make local alias for the standard exception class InvalidStateError(Error): """The operation is not allowed in this state.""" @@ -284,7 +282,7 @@ def wait(fs, timeout=None, return_when=ALL_COMPLETED): A named 2-tuple of sets. The first set, named 'done', contains the futures that completed (is finished or cancelled) before the wait completed. The second set, named 'not_done', contains uncompleted - futures. Duplicate futures given to *fs* are removed and will be + futures. Duplicate futures given to *fs* are removed and will be returned only once. """ fs = set(fs) @@ -312,6 +310,18 @@ def wait(fs, timeout=None, return_when=ALL_COMPLETED): done.update(waiter.finished_futures) return DoneAndNotDoneFutures(done, fs - done) + +def _result_or_cancel(fut, timeout=None): + try: + try: + return fut.result(timeout) + finally: + fut.cancel() + finally: + # Break a reference cycle with the exception in self._exception + del fut + + class Future(object): """Represents the result of an asynchronous computation.""" @@ -386,7 +396,7 @@ def done(self): return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED] def __get_result(self): - if self._exception: + if self._exception is not None: try: raise self._exception finally: @@ -606,9 +616,9 @@ def result_iterator(): while fs: # Careful not to keep a reference to the popped future if timeout is None: - yield fs.pop().result() + yield _result_or_cancel(fs.pop()) else: - yield fs.pop().result(end_time - time.monotonic()) + yield _result_or_cancel(fs.pop(), end_time - time.monotonic()) finally: for future in fs: future.cancel() diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py index 57941e485d8..0dee8303ba2 100644 --- a/Lib/concurrent/futures/process.py +++ b/Lib/concurrent/futures/process.py @@ -49,6 +49,8 @@ from concurrent.futures import _base import queue import multiprocessing as mp +# This import is required to load the multiprocessing.connection submodule +# so that it can be accessed later as `mp.connection` import multiprocessing.connection from multiprocessing.queues import Queue import threading @@ -56,7 +58,7 @@ from functools import partial import itertools import sys -import traceback +from traceback import format_exception _threads_wakeups = weakref.WeakKeyDictionary() @@ -66,22 +68,31 @@ class _ThreadWakeup: def __init__(self): self._closed = False + self._lock = threading.Lock() self._reader, self._writer = mp.Pipe(duplex=False) def close(self): - if not self._closed: - self._closed = True - self._writer.close() - self._reader.close() + # Please note that we do not take the self._lock when + # calling clear() (to avoid deadlocking) so this method can + # only be called safely from the same thread as all calls to + # clear() even if you hold the lock. Otherwise we + # might try to read from the closed pipe. + with self._lock: + if not self._closed: + self._closed = True + self._writer.close() + self._reader.close() def wakeup(self): - if not self._closed: - self._writer.send_bytes(b"") + with self._lock: + if not self._closed: + self._writer.send_bytes(b"") def clear(self): - if not self._closed: - while self._reader.poll(): - self._reader.recv_bytes() + if self._closed: + raise RuntimeError('operation on closed _ThreadWakeup') + while self._reader.poll(): + self._reader.recv_bytes() def _python_exit(): @@ -123,8 +134,7 @@ def __str__(self): class _ExceptionWithTraceback: def __init__(self, exc, tb): - tb = traceback.format_exception(type(exc), exc, tb) - tb = ''.join(tb) + tb = ''.join(format_exception(type(exc), exc, tb)) self.exc = exc # Traceback object needs to be garbage-collected as its frames # contain references to all the objects in the exception scope @@ -145,10 +155,11 @@ def __init__(self, future, fn, args, kwargs): self.kwargs = kwargs class _ResultItem(object): - def __init__(self, work_id, exception=None, result=None): + def __init__(self, work_id, exception=None, result=None, exit_pid=None): self.work_id = work_id self.exception = exception self.result = result + self.exit_pid = exit_pid class _CallItem(object): def __init__(self, work_id, fn, args, kwargs): @@ -160,20 +171,17 @@ def __init__(self, work_id, fn, args, kwargs): class _SafeQueue(Queue): """Safe Queue set exception to the future object linked to a job""" - def __init__(self, max_size=0, *, ctx, pending_work_items, shutdown_lock, - thread_wakeup): + def __init__(self, max_size=0, *, ctx, pending_work_items, thread_wakeup): self.pending_work_items = pending_work_items - self.shutdown_lock = shutdown_lock self.thread_wakeup = thread_wakeup super().__init__(max_size, ctx=ctx) def _on_queue_feeder_error(self, e, obj): if isinstance(obj, _CallItem): - tb = traceback.format_exception(type(e), e, e.__traceback__) + tb = format_exception(type(e), e, e.__traceback__) e.__cause__ = _RemoteTraceback('\n"""\n{}"""'.format(''.join(tb))) work_item = self.pending_work_items.pop(obj.work_id, None) - with self.shutdown_lock: - self.thread_wakeup.wakeup() + self.thread_wakeup.wakeup() # work_item can be None if another process terminated. In this # case, the executor_manager_thread fails all work_items # with BrokenProcessPool @@ -183,16 +191,6 @@ def _on_queue_feeder_error(self, e, obj): super()._on_queue_feeder_error(e, obj) -def _get_chunks(*iterables, chunksize): - """ Iterates over zip()ed iterables in chunks. """ - it = zip(*iterables) - while True: - chunk = tuple(itertools.islice(it, chunksize)) - if not chunk: - return - yield chunk - - def _process_chunk(fn, chunk): """ Processes a chunk of an iterable passed to map. @@ -205,17 +203,19 @@ def _process_chunk(fn, chunk): return [fn(*args) for args in chunk] -def _sendback_result(result_queue, work_id, result=None, exception=None): +def _sendback_result(result_queue, work_id, result=None, exception=None, + exit_pid=None): """Safely send back the given result or exception""" try: result_queue.put(_ResultItem(work_id, result=result, - exception=exception)) + exception=exception, exit_pid=exit_pid)) except BaseException as e: exc = _ExceptionWithTraceback(e, e.__traceback__) - result_queue.put(_ResultItem(work_id, exception=exc)) + result_queue.put(_ResultItem(work_id, exception=exc, + exit_pid=exit_pid)) -def _process_worker(call_queue, result_queue, initializer, initargs): +def _process_worker(call_queue, result_queue, initializer, initargs, max_tasks=None): """Evaluates calls from call_queue and places the results in result_queue. This worker is run in a separate process. @@ -236,25 +236,38 @@ def _process_worker(call_queue, result_queue, initializer, initargs): # The parent will notice that the process stopped and # mark the pool broken return + num_tasks = 0 + exit_pid = None while True: call_item = call_queue.get(block=True) if call_item is None: # Wake up queue management thread result_queue.put(os.getpid()) return + + if max_tasks is not None: + num_tasks += 1 + if num_tasks >= max_tasks: + exit_pid = os.getpid() + try: r = call_item.fn(*call_item.args, **call_item.kwargs) except BaseException as e: exc = _ExceptionWithTraceback(e, e.__traceback__) - _sendback_result(result_queue, call_item.work_id, exception=exc) + _sendback_result(result_queue, call_item.work_id, exception=exc, + exit_pid=exit_pid) else: - _sendback_result(result_queue, call_item.work_id, result=r) + _sendback_result(result_queue, call_item.work_id, result=r, + exit_pid=exit_pid) del r # Liberate the resource as soon as possible, to avoid holding onto # open files or shared memory that is not needed anymore del call_item + if exit_pid is not None: + return + class _ExecutorManagerThread(threading.Thread): """Manages the communication between this process and the worker processes. @@ -284,11 +297,10 @@ def __init__(self, executor): # if there is no pending work item. def weakref_cb(_, thread_wakeup=self.thread_wakeup, - shutdown_lock=self.shutdown_lock): - mp.util.debug('Executor collected: triggering callback for' + mp_util_debug=mp.util.debug): + mp_util_debug('Executor collected: triggering callback for' ' QueueManager wakeup') - with shutdown_lock: - thread_wakeup.wakeup() + thread_wakeup.wakeup() self.executor_reference = weakref.ref(executor, weakref_cb) @@ -305,6 +317,10 @@ def weakref_cb(_, # A queue.Queue of work ids e.g. Queue([5, 6, ...]). self.work_ids_queue = executor._work_ids + # Maximum number of tasks a worker process can execute before + # exiting safely + self.max_tasks_per_child = executor._max_tasks_per_child + # A dict mapping work ids to _WorkItems e.g. # {5: <_WorkItem...>, 6: <_WorkItem...>, ...} self.pending_work_items = executor._pending_work_items @@ -315,7 +331,14 @@ def run(self): # Main loop for the executor manager thread. while True: - self.add_call_item_to_queue() + # gh-109047: During Python finalization, self.call_queue.put() + # creation of a thread can fail with RuntimeError. + try: + self.add_call_item_to_queue() + except BaseException as exc: + cause = format_exception(exc) + self.terminate_broken(cause) + return result_item, is_broken, cause = self.wait_result_broken_or_wakeup() @@ -324,19 +347,32 @@ def run(self): return if result_item is not None: self.process_result_item(result_item) + + process_exited = result_item.exit_pid is not None + if process_exited: + p = self.processes.pop(result_item.exit_pid) + p.join() + # Delete reference to result_item to avoid keeping references # while waiting on new results. del result_item - # attempt to increment idle process count - executor = self.executor_reference() - if executor is not None: - executor._idle_worker_semaphore.release() - del executor + if executor := self.executor_reference(): + if process_exited: + with self.shutdown_lock: + executor._adjust_process_count() + else: + executor._idle_worker_semaphore.release() + del executor if self.is_shutting_down(): self.flag_executor_shutting_down() + # When only canceled futures remain in pending_work_items, our + # next call to wait_result_broken_or_wakeup would hang forever. + # This makes sure we have some running futures or none at all. + self.add_call_item_to_queue() + # Since no new work items can be added, it is safe to shutdown # this thread if there are no pending work items. if not self.pending_work_items: @@ -386,14 +422,13 @@ def wait_result_broken_or_wakeup(self): try: result_item = result_reader.recv() is_broken = False - except BaseException as e: - cause = traceback.format_exception(type(e), e, e.__traceback__) + except BaseException as exc: + cause = format_exception(exc) elif wakeup_reader in ready: is_broken = False - with self.shutdown_lock: - self.thread_wakeup.clear() + self.thread_wakeup.clear() return result_item, is_broken, cause @@ -401,24 +436,14 @@ def process_result_item(self, result_item): # Process the received a result_item. This can be either the PID of a # worker that exited gracefully or a _ResultItem - if isinstance(result_item, int): - # Clean shutdown of a worker using its PID - # (avoids marking the executor broken) - assert self.is_shutting_down() - p = self.processes.pop(result_item) - p.join() - if not self.processes: - self.join_executor_internals() - return - else: - # Received a _ResultItem so mark the future as completed. - work_item = self.pending_work_items.pop(result_item.work_id, None) - # work_item can be None if another process terminated (see above) - if work_item is not None: - if result_item.exception: - work_item.future.set_exception(result_item.exception) - else: - work_item.future.set_result(result_item.result) + # Received a _ResultItem so mark the future as completed. + work_item = self.pending_work_items.pop(result_item.work_id, None) + # work_item can be None if another process terminated (see above) + if work_item is not None: + if result_item.exception is not None: + work_item.future.set_exception(result_item.exception) + else: + work_item.future.set_result(result_item.result) def is_shutting_down(self): # Check whether we should start shutting down the executor. @@ -430,7 +455,7 @@ def is_shutting_down(self): return (_global_shutdown or executor is None or executor._shutdown_thread) - def terminate_broken(self, cause): + def _terminate_broken(self, cause): # Terminate the executor because it is in a broken state. The cause # argument can be used to display more information on the error that # lead the executor into becoming broken. @@ -455,7 +480,14 @@ def terminate_broken(self, cause): # Mark pending tasks as failed. for work_id, work_item in self.pending_work_items.items(): - work_item.future.set_exception(bpe) + try: + work_item.future.set_exception(bpe) + except _base.InvalidStateError: + # set_exception() fails if the future is cancelled: ignore it. + # Trying to check if the future is cancelled before calling + # set_exception() would leave a race condition if the future is + # cancelled between the check and set_exception(). + pass # Delete references to object. See issue16284 del work_item self.pending_work_items.clear() @@ -465,8 +497,14 @@ def terminate_broken(self, cause): for p in self.processes.values(): p.terminate() + self.call_queue._terminate_broken() + # clean up resources - self.join_executor_internals() + self._join_executor_internals(broken=True) + + def terminate_broken(self, cause): + with self.shutdown_lock: + self._terminate_broken(cause) def flag_executor_shutting_down(self): # Flag the executor as shutting down and cancel remaining tasks if @@ -509,15 +547,24 @@ def shutdown_workers(self): break def join_executor_internals(self): - self.shutdown_workers() + with self.shutdown_lock: + self._join_executor_internals() + + def _join_executor_internals(self, broken=False): + # If broken, call_queue was closed and so can no longer be used. + if not broken: + self.shutdown_workers() + # Release the queue's resources as soon as possible. self.call_queue.close() self.call_queue.join_thread() - with self.shutdown_lock: - self.thread_wakeup.close() + self.thread_wakeup.close() + # If .join() is not called on the created processes then # some ctx.Queue methods may deadlock on Mac OS X. for p in self.processes.values(): + if broken: + p.terminate() p.join() def get_n_children_alive(self): @@ -582,22 +629,29 @@ class BrokenProcessPool(_base.BrokenExecutor): class ProcessPoolExecutor(_base.Executor): def __init__(self, max_workers=None, mp_context=None, - initializer=None, initargs=()): + initializer=None, initargs=(), *, max_tasks_per_child=None): """Initializes a new ProcessPoolExecutor instance. Args: max_workers: The maximum number of processes that can be used to execute the given calls. If None or not given then as many worker processes will be created as the machine has processors. - mp_context: A multiprocessing context to launch the workers. This + mp_context: A multiprocessing context to launch the workers created + using the multiprocessing.get_context('start method') API. This object should provide SimpleQueue, Queue and Process. initializer: A callable used to initialize worker processes. initargs: A tuple of arguments to pass to the initializer. + max_tasks_per_child: The maximum number of tasks a worker process + can complete before it will exit and be replaced with a fresh + worker process. The default of None means worker process will + live as long as the executor. Requires a non-'fork' mp_context + start method. When given, we default to using 'spawn' if no + mp_context is supplied. """ _check_system_limits() if max_workers is None: - self._max_workers = os.cpu_count() or 1 + self._max_workers = os.process_cpu_count() or 1 if sys.platform == 'win32': self._max_workers = min(_MAX_WINDOWS_WORKERS, self._max_workers) @@ -612,7 +666,10 @@ def __init__(self, max_workers=None, mp_context=None, self._max_workers = max_workers if mp_context is None: - mp_context = mp.get_context() + if max_tasks_per_child is not None: + mp_context = mp.get_context("spawn") + else: + mp_context = mp.get_context() self._mp_context = mp_context # https://github.com/python/cpython/issues/90622 @@ -624,6 +681,18 @@ def __init__(self, max_workers=None, mp_context=None, self._initializer = initializer self._initargs = initargs + if max_tasks_per_child is not None: + if not isinstance(max_tasks_per_child, int): + raise TypeError("max_tasks_per_child must be an integer") + elif max_tasks_per_child <= 0: + raise ValueError("max_tasks_per_child must be >= 1") + if self._mp_context.get_start_method(allow_none=False) == "fork": + # https://github.com/python/cpython/issues/90622 + raise ValueError("max_tasks_per_child is incompatible with" + " the 'fork' multiprocessing start method;" + " supply a different mp_context.") + self._max_tasks_per_child = max_tasks_per_child + # Management thread self._executor_manager_thread = None @@ -646,7 +715,9 @@ def __init__(self, max_workers=None, mp_context=None, # as it could result in a deadlock if a worker process dies with the # _result_queue write lock still acquired. # - # _shutdown_lock must be locked to access _ThreadWakeup. + # Care must be taken to only call clear and close from the + # executor_manager_thread, since _ThreadWakeup.clear() is not protected + # by a lock. self._executor_manager_thread_wakeup = _ThreadWakeup() # Create communication channels for the executor @@ -657,7 +728,6 @@ def __init__(self, max_workers=None, mp_context=None, self._call_queue = _SafeQueue( max_size=queue_size, ctx=self._mp_context, pending_work_items=self._pending_work_items, - shutdown_lock=self._shutdown_lock, thread_wakeup=self._executor_manager_thread_wakeup) # Killed worker processes can produce spurious "broken pipe" # tracebacks in the queue's own worker thread. But we detect killed @@ -677,6 +747,11 @@ def _start_executor_manager_thread(self): self._executor_manager_thread_wakeup def _adjust_process_count(self): + # gh-132969: avoid error when state is reset and executor is still running, + # which will happen when shutdown(wait=False) is called. + if self._processes is None: + return + # if there's an idle process, we don't need to spawn a new one. if self._idle_worker_semaphore.acquire(blocking=False): return @@ -705,7 +780,8 @@ def _spawn_process(self): args=(self._call_queue, self._result_queue, self._initializer, - self._initargs)) + self._initargs, + self._max_tasks_per_child)) p.start() self._processes[p.pid] = p @@ -759,7 +835,7 @@ def map(self, fn, *iterables, timeout=None, chunksize=1): raise ValueError("chunksize must be >= 1.") results = super().map(partial(_process_chunk, fn), - _get_chunks(*iterables, chunksize=chunksize), + itertools.batched(zip(*iterables), chunksize), timeout=timeout) return _chain_from_iterable_of_lists(results) diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py index 493861d314d..9021dde48ef 100644 --- a/Lib/concurrent/futures/thread.py +++ b/Lib/concurrent/futures/thread.py @@ -37,14 +37,14 @@ def _python_exit(): threading._register_atexit(_python_exit) # At fork, reinitialize the `_global_shutdown_lock` lock in the child process -# TODO RUSTPYTHON - _at_fork_reinit is not implemented yet -if hasattr(os, 'register_at_fork') and hasattr(_global_shutdown_lock, '_at_fork_reinit'): +if hasattr(os, 'register_at_fork'): os.register_at_fork(before=_global_shutdown_lock.acquire, after_in_child=_global_shutdown_lock._at_fork_reinit, after_in_parent=_global_shutdown_lock.release) + os.register_at_fork(after_in_child=_threads_queues.clear) -class _WorkItem(object): +class _WorkItem: def __init__(self, future, fn, args, kwargs): self.future = future self.fn = fn @@ -79,17 +79,20 @@ def _worker(executor_reference, work_queue, initializer, initargs): return try: while True: - work_item = work_queue.get(block=True) - if work_item is not None: - work_item.run() - # Delete references to object. See issue16284 - del work_item - - # attempt to increment idle count + try: + work_item = work_queue.get_nowait() + except queue.Empty: + # attempt to increment idle count if queue is empty executor = executor_reference() if executor is not None: executor._idle_semaphore.release() del executor + work_item = work_queue.get(block=True) + + if work_item is not None: + work_item.run() + # Delete references to object. See GH-60488 + del work_item continue executor = executor_reference() @@ -137,10 +140,10 @@ def __init__(self, max_workers=None, thread_name_prefix='', # * CPU bound task which releases GIL # * I/O bound task (which releases GIL, of course) # - # We use cpu_count + 4 for both types of tasks. + # We use process_cpu_count + 4 for both types of tasks. # But we limit it to 32 to avoid consuming surprisingly large resource # on many core machine. - max_workers = min(32, (os.cpu_count() or 1) + 4) + max_workers = min(32, (os.process_cpu_count() or 1) + 4) if max_workers <= 0: raise ValueError("max_workers must be greater than 0") diff --git a/Lib/test/test_concurrent_futures/__init__.py b/Lib/test/test_concurrent_futures/__init__.py new file mode 100644 index 00000000000..b38bd38d338 --- /dev/null +++ b/Lib/test/test_concurrent_futures/__init__.py @@ -0,0 +1,18 @@ +import os.path +import unittest +from test import support +from test.support import threading_helper + + +# Adjust if we ever have a platform with processes but not threads. +threading_helper.requires_working_threading(module=True) + + +if support.check_sanitizer(address=True, memory=True): + # gh-90791: Skip the test because it is too slow when Python is built + # with ASAN/MSAN: between 5 and 20 minutes on GitHub Actions. + raise unittest.SkipTest("test too slow on ASAN/MSAN build") + + +def load_tests(*args): + return support.load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_concurrent_futures/executor.py b/Lib/test/test_concurrent_futures/executor.py new file mode 100644 index 00000000000..7442d3bee52 --- /dev/null +++ b/Lib/test/test_concurrent_futures/executor.py @@ -0,0 +1,162 @@ +import threading +import time +import unittest +import weakref +from concurrent import futures +from test import support +from test.support import Py_GIL_DISABLED + + +def mul(x, y): + return x * y + +def capture(*args, **kwargs): + return args, kwargs + + +class MyObject(object): + def my_method(self): + pass + + +def make_dummy_object(_): + return MyObject() + + +# Used in test_swallows_falsey_exceptions +def raiser(exception, msg='std'): + raise exception(msg) + + +class FalseyBoolException(Exception): + def __bool__(self): + return False + + +class FalseyLenException(Exception): + def __len__(self): + return 0 + + +class ExecutorTest: + # Executor.shutdown() and context manager usage is tested by + # ExecutorShutdownTest. + def test_submit(self): + future = self.executor.submit(pow, 2, 8) + self.assertEqual(256, future.result()) + + def test_submit_keyword(self): + future = self.executor.submit(mul, 2, y=8) + self.assertEqual(16, future.result()) + future = self.executor.submit(capture, 1, self=2, fn=3) + self.assertEqual(future.result(), ((1,), {'self': 2, 'fn': 3})) + with self.assertRaises(TypeError): + self.executor.submit(fn=capture, arg=1) + with self.assertRaises(TypeError): + self.executor.submit(arg=1) + + def test_map(self): + self.assertEqual( + list(self.executor.map(pow, range(10), range(10))), + list(map(pow, range(10), range(10)))) + + self.assertEqual( + list(self.executor.map(pow, range(10), range(10), chunksize=3)), + list(map(pow, range(10), range(10)))) + + def test_map_exception(self): + i = self.executor.map(divmod, [1, 1, 1, 1], [2, 3, 0, 5]) + self.assertEqual(i.__next__(), (0, 1)) + self.assertEqual(i.__next__(), (0, 1)) + self.assertRaises(ZeroDivisionError, i.__next__) + + @support.requires_resource('walltime') + def test_map_timeout(self): + results = [] + try: + for i in self.executor.map(time.sleep, + [0, 0, 6], + timeout=5): + results.append(i) + except futures.TimeoutError: + pass + else: + self.fail('expected TimeoutError') + + self.assertEqual([None, None], results) + + def test_shutdown_race_issue12456(self): + # Issue #12456: race condition at shutdown where trying to post a + # sentinel in the call queue blocks (the queue is full while processes + # have exited). + self.executor.map(str, [2] * (self.worker_count + 1)) + self.executor.shutdown() + + @support.cpython_only + def test_no_stale_references(self): + # Issue #16284: check that the executors don't unnecessarily hang onto + # references. + my_object = MyObject() + my_object_collected = threading.Event() + def set_event(): + if Py_GIL_DISABLED: + # gh-117688 Avoid deadlock by setting the event in a + # background thread. The current thread may be in the middle + # of the my_object_collected.wait() call, which holds locks + # needed by my_object_collected.set(). + threading.Thread(target=my_object_collected.set).start() + else: + my_object_collected.set() + my_object_callback = weakref.ref(my_object, lambda obj: set_event()) + # Deliberately discarding the future. + self.executor.submit(my_object.my_method) + del my_object + + if Py_GIL_DISABLED: + # Due to biased reference counting, my_object might only be + # deallocated while the thread that created it runs -- if the + # thread is paused waiting on an event, it may not merge the + # refcount of the queued object. For that reason, we alternate + # between running the GC and waiting for the event. + wait_time = 0 + collected = False + while not collected and wait_time <= support.SHORT_TIMEOUT: + support.gc_collect() + collected = my_object_collected.wait(timeout=1.0) + wait_time += 1.0 + else: + collected = my_object_collected.wait(timeout=support.SHORT_TIMEOUT) + self.assertTrue(collected, + "Stale reference not collected within timeout.") + + def test_max_workers_negative(self): + for number in (0, -1): + with self.assertRaisesRegex(ValueError, + "max_workers must be greater " + "than 0"): + self.executor_type(max_workers=number) + + def test_free_reference(self): + # Issue #14406: Result iterator should not keep an internal + # reference to result objects. + for obj in self.executor.map(make_dummy_object, range(10)): + wr = weakref.ref(obj) + del obj + support.gc_collect() # For PyPy or other GCs. + + for _ in support.sleeping_retry(support.SHORT_TIMEOUT): + if wr() is None: + break + + def test_swallows_falsey_exceptions(self): + # see gh-132063: Prevent exceptions that evaluate as falsey + # from being ignored. + # Recall: `x` is falsey if `len(x)` returns 0 or `bool(x)` returns False. + + msg = 'boolbool' + with self.assertRaisesRegex(FalseyBoolException, msg): + self.executor.submit(raiser, FalseyBoolException, msg).result() + + msg = 'lenlen' + with self.assertRaisesRegex(FalseyLenException, msg): + self.executor.submit(raiser, FalseyLenException, msg).result() diff --git a/Lib/test/test_concurrent_futures/test_as_completed.py b/Lib/test/test_concurrent_futures/test_as_completed.py new file mode 100644 index 00000000000..c90b0021d85 --- /dev/null +++ b/Lib/test/test_concurrent_futures/test_as_completed.py @@ -0,0 +1,118 @@ +import itertools +import time +import unittest +import weakref +from concurrent import futures +from concurrent.futures._base import ( + CANCELLED_AND_NOTIFIED, FINISHED, Future) + +from test import support + +from .util import ( + PENDING_FUTURE, RUNNING_FUTURE, + CANCELLED_AND_NOTIFIED_FUTURE, EXCEPTION_FUTURE, SUCCESSFUL_FUTURE, + create_future, create_executor_tests, setup_module) + + +def mul(x, y): + return x * y + + +class AsCompletedTests: + def test_no_timeout(self): + future1 = self.executor.submit(mul, 2, 21) + future2 = self.executor.submit(mul, 7, 6) + + completed = set(futures.as_completed( + [CANCELLED_AND_NOTIFIED_FUTURE, + EXCEPTION_FUTURE, + SUCCESSFUL_FUTURE, + future1, future2])) + self.assertEqual(set( + [CANCELLED_AND_NOTIFIED_FUTURE, + EXCEPTION_FUTURE, + SUCCESSFUL_FUTURE, + future1, future2]), + completed) + + def test_future_times_out(self): + """Test ``futures.as_completed`` timing out before + completing it's final future.""" + already_completed = {CANCELLED_AND_NOTIFIED_FUTURE, + EXCEPTION_FUTURE, + SUCCESSFUL_FUTURE} + + # Windows clock resolution is around 15.6 ms + short_timeout = 0.100 + for timeout in (0, short_timeout): + with self.subTest(timeout): + + completed_futures = set() + future = self.executor.submit(time.sleep, short_timeout * 10) + + try: + for f in futures.as_completed( + already_completed | {future}, + timeout + ): + completed_futures.add(f) + except futures.TimeoutError: + pass + + # Check that ``future`` wasn't completed. + self.assertEqual(completed_futures, already_completed) + + def test_duplicate_futures(self): + # Issue 20367. Duplicate futures should not raise exceptions or give + # duplicate responses. + # Issue #31641: accept arbitrary iterables. + future1 = self.executor.submit(time.sleep, 2) + completed = [ + f for f in futures.as_completed(itertools.repeat(future1, 3)) + ] + self.assertEqual(len(completed), 1) + + def test_free_reference_yielded_future(self): + # Issue #14406: Generator should not keep references + # to finished futures. + futures_list = [Future() for _ in range(8)] + futures_list.append(create_future(state=CANCELLED_AND_NOTIFIED)) + futures_list.append(create_future(state=FINISHED, result=42)) + + with self.assertRaises(futures.TimeoutError): + for future in futures.as_completed(futures_list, timeout=0): + futures_list.remove(future) + wr = weakref.ref(future) + del future + support.gc_collect() # For PyPy or other GCs. + self.assertIsNone(wr()) + + futures_list[0].set_result("test") + for future in futures.as_completed(futures_list): + futures_list.remove(future) + wr = weakref.ref(future) + del future + support.gc_collect() # For PyPy or other GCs. + self.assertIsNone(wr()) + if futures_list: + futures_list[0].set_result("test") + + def test_correct_timeout_exception_msg(self): + futures_list = [CANCELLED_AND_NOTIFIED_FUTURE, PENDING_FUTURE, + RUNNING_FUTURE, SUCCESSFUL_FUTURE] + + with self.assertRaises(futures.TimeoutError) as cm: + list(futures.as_completed(futures_list, timeout=0)) + + self.assertEqual(str(cm.exception), '2 (of 4) futures unfinished') + + +create_executor_tests(globals(), AsCompletedTests) + + +def setUpModule(): + setup_module() + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_concurrent_futures/test_deadlock.py b/Lib/test/test_concurrent_futures/test_deadlock.py new file mode 100644 index 00000000000..dcc1d68563a --- /dev/null +++ b/Lib/test/test_concurrent_futures/test_deadlock.py @@ -0,0 +1,332 @@ +import contextlib +import queue +import signal +import sys +import time +import unittest +import unittest.mock +from pickle import PicklingError +from concurrent import futures +from concurrent.futures.process import BrokenProcessPool, _ThreadWakeup + +from test import support + +from .util import ( + create_executor_tests, setup_module, + ProcessPoolForkMixin, ProcessPoolForkserverMixin, ProcessPoolSpawnMixin) + + +def _crash(delay=None): + """Induces a segfault.""" + if delay: + time.sleep(delay) + import faulthandler + faulthandler.disable() + faulthandler._sigsegv() + + +def _crash_with_data(data): + """Induces a segfault with dummy data in input.""" + _crash() + + +def _exit(): + """Induces a sys exit with exitcode 1.""" + sys.exit(1) + + +def _raise_error(Err): + """Function that raises an Exception in process.""" + raise Err() + + +def _raise_error_ignore_stderr(Err): + """Function that raises an Exception in process and ignores stderr.""" + import io + sys.stderr = io.StringIO() + raise Err() + + +def _return_instance(cls): + """Function that returns a instance of cls.""" + return cls() + + +class CrashAtPickle(object): + """Bad object that triggers a segfault at pickling time.""" + def __reduce__(self): + _crash() + + +class CrashAtUnpickle(object): + """Bad object that triggers a segfault at unpickling time.""" + def __reduce__(self): + return _crash, () + + +class ExitAtPickle(object): + """Bad object that triggers a process exit at pickling time.""" + def __reduce__(self): + _exit() + + +class ExitAtUnpickle(object): + """Bad object that triggers a process exit at unpickling time.""" + def __reduce__(self): + return _exit, () + + +class ErrorAtPickle(object): + """Bad object that triggers an error at pickling time.""" + def __reduce__(self): + from pickle import PicklingError + raise PicklingError("Error in pickle") + + +class ErrorAtUnpickle(object): + """Bad object that triggers an error at unpickling time.""" + def __reduce__(self): + from pickle import UnpicklingError + return _raise_error_ignore_stderr, (UnpicklingError, ) + + +class ExecutorDeadlockTest: + TIMEOUT = support.LONG_TIMEOUT + + def _fail_on_deadlock(self, executor): + # If we did not recover before TIMEOUT seconds, consider that the + # executor is in a deadlock state and forcefully clean all its + # composants. + import faulthandler + from tempfile import TemporaryFile + with TemporaryFile(mode="w+") as f: + faulthandler.dump_traceback(file=f) + f.seek(0) + tb = f.read() + for p in executor._processes.values(): + p.terminate() + # This should be safe to call executor.shutdown here as all possible + # deadlocks should have been broken. + executor.shutdown(wait=True) + print(f"\nTraceback:\n {tb}", file=sys.__stderr__) + self.fail(f"Executor deadlock:\n\n{tb}") + + + def _check_crash(self, error, func, *args, ignore_stderr=False): + # test for deadlock caused by crashes in a pool + self.executor.shutdown(wait=True) + + executor = self.executor_type( + max_workers=2, mp_context=self.get_context()) + res = executor.submit(func, *args) + + if ignore_stderr: + cm = support.captured_stderr() + else: + cm = contextlib.nullcontext() + + try: + with self.assertRaises(error): + with cm: + res.result(timeout=self.TIMEOUT) + except futures.TimeoutError: + # If we did not recover before TIMEOUT seconds, + # consider that the executor is in a deadlock state + self._fail_on_deadlock(executor) + executor.shutdown(wait=True) + + def test_error_at_task_pickle(self): + # Check problem occurring while pickling a task in + # the task_handler thread + self._check_crash(PicklingError, id, ErrorAtPickle()) + + def test_exit_at_task_unpickle(self): + # Check problem occurring while unpickling a task on workers + self._check_crash(BrokenProcessPool, id, ExitAtUnpickle()) + + def test_error_at_task_unpickle(self): + # gh-109832: Restore stderr overridden by _raise_error_ignore_stderr() + self.addCleanup(setattr, sys, 'stderr', sys.stderr) + + # Check problem occurring while unpickling a task on workers + self._check_crash(BrokenProcessPool, id, ErrorAtUnpickle()) + + def test_crash_at_task_unpickle(self): + # Check problem occurring while unpickling a task on workers + self._check_crash(BrokenProcessPool, id, CrashAtUnpickle()) + + def test_crash_during_func_exec_on_worker(self): + # Check problem occurring during func execution on workers + self._check_crash(BrokenProcessPool, _crash) + + def test_exit_during_func_exec_on_worker(self): + # Check problem occurring during func execution on workers + self._check_crash(SystemExit, _exit) + + def test_error_during_func_exec_on_worker(self): + # Check problem occurring during func execution on workers + self._check_crash(RuntimeError, _raise_error, RuntimeError) + + def test_crash_during_result_pickle_on_worker(self): + # Check problem occurring while pickling a task result + # on workers + self._check_crash(BrokenProcessPool, _return_instance, CrashAtPickle) + + def test_exit_during_result_pickle_on_worker(self): + # Check problem occurring while pickling a task result + # on workers + self._check_crash(SystemExit, _return_instance, ExitAtPickle) + + def test_error_during_result_pickle_on_worker(self): + # Check problem occurring while pickling a task result + # on workers + self._check_crash(PicklingError, _return_instance, ErrorAtPickle) + + def test_error_during_result_unpickle_in_result_handler(self): + # gh-109832: Restore stderr overridden by _raise_error_ignore_stderr() + self.addCleanup(setattr, sys, 'stderr', sys.stderr) + + # Check problem occurring while unpickling a task in + # the result_handler thread + self._check_crash(BrokenProcessPool, + _return_instance, ErrorAtUnpickle, + ignore_stderr=True) + + def test_exit_during_result_unpickle_in_result_handler(self): + # Check problem occurring while unpickling a task in + # the result_handler thread + self._check_crash(BrokenProcessPool, _return_instance, ExitAtUnpickle) + + def test_shutdown_deadlock(self): + # Test that the pool calling shutdown do not cause deadlock + # if a worker fails after the shutdown call. + self.executor.shutdown(wait=True) + with self.executor_type(max_workers=2, + mp_context=self.get_context()) as executor: + self.executor = executor # Allow clean up in fail_on_deadlock + f = executor.submit(_crash, delay=.1) + executor.shutdown(wait=True) + with self.assertRaises(BrokenProcessPool): + f.result() + + def test_shutdown_deadlock_pickle(self): + # Test that the pool calling shutdown with wait=False does not cause + # a deadlock if a task fails at pickle after the shutdown call. + # Reported in bpo-39104. + self.executor.shutdown(wait=True) + with self.executor_type(max_workers=2, + mp_context=self.get_context()) as executor: + self.executor = executor # Allow clean up in fail_on_deadlock + + # Start the executor and get the executor_manager_thread to collect + # the threads and avoid dangling thread that should be cleaned up + # asynchronously. + executor.submit(id, 42).result() + executor_manager = executor._executor_manager_thread + + # Submit a task that fails at pickle and shutdown the executor + # without waiting + f = executor.submit(id, ErrorAtPickle()) + executor.shutdown(wait=False) + with self.assertRaises(PicklingError): + f.result() + + # Make sure the executor is eventually shutdown and do not leave + # dangling threads + executor_manager.join() + + def test_crash_big_data(self): + # Test that there is a clean exception instad of a deadlock when a + # child process crashes while some data is being written into the + # queue. + # https://github.com/python/cpython/issues/94777 + self.executor.shutdown(wait=True) + data = "a" * support.PIPE_MAX_SIZE + with self.executor_type(max_workers=2, + mp_context=self.get_context()) as executor: + self.executor = executor # Allow clean up in fail_on_deadlock + with self.assertRaises(BrokenProcessPool): + list(executor.map(_crash_with_data, [data] * 10)) + + executor.shutdown(wait=True) + + def test_gh105829_should_not_deadlock_if_wakeup_pipe_full(self): + # Issue #105829: The _ExecutorManagerThread wakeup pipe could + # fill up and block. See: https://github.com/python/cpython/issues/105829 + + # Lots of cargo culting while writing this test, apologies if + # something is really stupid... + + self.executor.shutdown(wait=True) + + if not hasattr(signal, 'alarm'): + raise unittest.SkipTest( + "Tested platform does not support the alarm signal") + + def timeout(_signum, _frame): + import faulthandler + faulthandler.dump_traceback() + + raise RuntimeError("timed out while submitting jobs?") + + thread_run = futures.process._ExecutorManagerThread.run + def mock_run(self): + # Delay thread startup so the wakeup pipe can fill up and block + time.sleep(3) + thread_run(self) + + class MockWakeup(_ThreadWakeup): + """Mock wakeup object to force the wakeup to block""" + def __init__(self): + super().__init__() + self._dummy_queue = queue.Queue(maxsize=1) + + def wakeup(self): + self._dummy_queue.put(None, block=True) + super().wakeup() + + def clear(self): + super().clear() + try: + while True: + self._dummy_queue.get_nowait() + except queue.Empty: + pass + + with (unittest.mock.patch.object(futures.process._ExecutorManagerThread, + 'run', mock_run), + unittest.mock.patch('concurrent.futures.process._ThreadWakeup', + MockWakeup)): + with self.executor_type(max_workers=2, + mp_context=self.get_context()) as executor: + self.executor = executor # Allow clean up in fail_on_deadlock + + job_num = 100 + job_data = range(job_num) + + # Need to use sigalarm for timeout detection because + # Executor.submit is not guarded by any timeout (both + # self._work_ids.put(self._queue_count) and + # self._executor_manager_thread_wakeup.wakeup() might + # timeout, maybe more?). In this specific case it was + # the wakeup call that deadlocked on a blocking pipe. + old_handler = signal.signal(signal.SIGALRM, timeout) + try: + signal.alarm(int(self.TIMEOUT)) + self.assertEqual(job_num, len(list(executor.map(int, job_data)))) + finally: + signal.alarm(0) + signal.signal(signal.SIGALRM, old_handler) + + +create_executor_tests(globals(), ExecutorDeadlockTest, + executor_mixins=(ProcessPoolForkMixin, + ProcessPoolForkserverMixin, + ProcessPoolSpawnMixin)) + +def setUpModule(): + setup_module() + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_concurrent_futures/test_future.py b/Lib/test/test_concurrent_futures/test_future.py new file mode 100644 index 00000000000..4066ea1ee4b --- /dev/null +++ b/Lib/test/test_concurrent_futures/test_future.py @@ -0,0 +1,291 @@ +import threading +import time +import unittest +from concurrent import futures +from concurrent.futures._base import ( + PENDING, RUNNING, CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED, Future) + +from test import support + +from .util import ( + PENDING_FUTURE, RUNNING_FUTURE, CANCELLED_FUTURE, + CANCELLED_AND_NOTIFIED_FUTURE, EXCEPTION_FUTURE, SUCCESSFUL_FUTURE, + BaseTestCase, create_future, setup_module) + + +class FutureTests(BaseTestCase): + def test_done_callback_with_result(self): + callback_result = None + def fn(callback_future): + nonlocal callback_result + callback_result = callback_future.result() + + f = Future() + f.add_done_callback(fn) + f.set_result(5) + self.assertEqual(5, callback_result) + + def test_done_callback_with_exception(self): + callback_exception = None + def fn(callback_future): + nonlocal callback_exception + callback_exception = callback_future.exception() + + f = Future() + f.add_done_callback(fn) + f.set_exception(Exception('test')) + self.assertEqual(('test',), callback_exception.args) + + def test_done_callback_with_cancel(self): + was_cancelled = None + def fn(callback_future): + nonlocal was_cancelled + was_cancelled = callback_future.cancelled() + + f = Future() + f.add_done_callback(fn) + self.assertTrue(f.cancel()) + self.assertTrue(was_cancelled) + + def test_done_callback_raises(self): + with support.captured_stderr() as stderr: + raising_was_called = False + fn_was_called = False + + def raising_fn(callback_future): + nonlocal raising_was_called + raising_was_called = True + raise Exception('doh!') + + def fn(callback_future): + nonlocal fn_was_called + fn_was_called = True + + f = Future() + f.add_done_callback(raising_fn) + f.add_done_callback(fn) + f.set_result(5) + self.assertTrue(raising_was_called) + self.assertTrue(fn_was_called) + self.assertIn('Exception: doh!', stderr.getvalue()) + + def test_done_callback_already_successful(self): + callback_result = None + def fn(callback_future): + nonlocal callback_result + callback_result = callback_future.result() + + f = Future() + f.set_result(5) + f.add_done_callback(fn) + self.assertEqual(5, callback_result) + + def test_done_callback_already_failed(self): + callback_exception = None + def fn(callback_future): + nonlocal callback_exception + callback_exception = callback_future.exception() + + f = Future() + f.set_exception(Exception('test')) + f.add_done_callback(fn) + self.assertEqual(('test',), callback_exception.args) + + def test_done_callback_already_cancelled(self): + was_cancelled = None + def fn(callback_future): + nonlocal was_cancelled + was_cancelled = callback_future.cancelled() + + f = Future() + self.assertTrue(f.cancel()) + f.add_done_callback(fn) + self.assertTrue(was_cancelled) + + def test_done_callback_raises_already_succeeded(self): + with support.captured_stderr() as stderr: + def raising_fn(callback_future): + raise Exception('doh!') + + f = Future() + + # Set the result first to simulate a future that runs instantly, + # effectively allowing the callback to be run immediately. + f.set_result(5) + f.add_done_callback(raising_fn) + + self.assertIn('exception calling callback for', stderr.getvalue()) + self.assertIn('doh!', stderr.getvalue()) + + + def test_repr(self): + self.assertRegex(repr(PENDING_FUTURE), + '') + self.assertRegex(repr(RUNNING_FUTURE), + '') + self.assertRegex(repr(CANCELLED_FUTURE), + '') + self.assertRegex(repr(CANCELLED_AND_NOTIFIED_FUTURE), + '') + self.assertRegex( + repr(EXCEPTION_FUTURE), + '') + self.assertRegex( + repr(SUCCESSFUL_FUTURE), + '') + + def test_cancel(self): + f1 = create_future(state=PENDING) + f2 = create_future(state=RUNNING) + f3 = create_future(state=CANCELLED) + f4 = create_future(state=CANCELLED_AND_NOTIFIED) + f5 = create_future(state=FINISHED, exception=OSError()) + f6 = create_future(state=FINISHED, result=5) + + self.assertTrue(f1.cancel()) + self.assertEqual(f1._state, CANCELLED) + + self.assertFalse(f2.cancel()) + self.assertEqual(f2._state, RUNNING) + + self.assertTrue(f3.cancel()) + self.assertEqual(f3._state, CANCELLED) + + self.assertTrue(f4.cancel()) + self.assertEqual(f4._state, CANCELLED_AND_NOTIFIED) + + self.assertFalse(f5.cancel()) + self.assertEqual(f5._state, FINISHED) + + self.assertFalse(f6.cancel()) + self.assertEqual(f6._state, FINISHED) + + def test_cancelled(self): + self.assertFalse(PENDING_FUTURE.cancelled()) + self.assertFalse(RUNNING_FUTURE.cancelled()) + self.assertTrue(CANCELLED_FUTURE.cancelled()) + self.assertTrue(CANCELLED_AND_NOTIFIED_FUTURE.cancelled()) + self.assertFalse(EXCEPTION_FUTURE.cancelled()) + self.assertFalse(SUCCESSFUL_FUTURE.cancelled()) + + def test_done(self): + self.assertFalse(PENDING_FUTURE.done()) + self.assertFalse(RUNNING_FUTURE.done()) + self.assertTrue(CANCELLED_FUTURE.done()) + self.assertTrue(CANCELLED_AND_NOTIFIED_FUTURE.done()) + self.assertTrue(EXCEPTION_FUTURE.done()) + self.assertTrue(SUCCESSFUL_FUTURE.done()) + + def test_running(self): + self.assertFalse(PENDING_FUTURE.running()) + self.assertTrue(RUNNING_FUTURE.running()) + self.assertFalse(CANCELLED_FUTURE.running()) + self.assertFalse(CANCELLED_AND_NOTIFIED_FUTURE.running()) + self.assertFalse(EXCEPTION_FUTURE.running()) + self.assertFalse(SUCCESSFUL_FUTURE.running()) + + def test_result_with_timeout(self): + self.assertRaises(futures.TimeoutError, + PENDING_FUTURE.result, timeout=0) + self.assertRaises(futures.TimeoutError, + RUNNING_FUTURE.result, timeout=0) + self.assertRaises(futures.CancelledError, + CANCELLED_FUTURE.result, timeout=0) + self.assertRaises(futures.CancelledError, + CANCELLED_AND_NOTIFIED_FUTURE.result, timeout=0) + self.assertRaises(OSError, EXCEPTION_FUTURE.result, timeout=0) + self.assertEqual(SUCCESSFUL_FUTURE.result(timeout=0), 42) + + def test_result_with_success(self): + # TODO(brian@sweetapp.com): This test is timing dependent. + def notification(): + # Wait until the main thread is waiting for the result. + time.sleep(1) + f1.set_result(42) + + f1 = create_future(state=PENDING) + t = threading.Thread(target=notification) + t.start() + + self.assertEqual(f1.result(timeout=5), 42) + t.join() + + def test_result_with_cancel(self): + # TODO(brian@sweetapp.com): This test is timing dependent. + def notification(): + # Wait until the main thread is waiting for the result. + time.sleep(1) + f1.cancel() + + f1 = create_future(state=PENDING) + t = threading.Thread(target=notification) + t.start() + + self.assertRaises(futures.CancelledError, + f1.result, timeout=support.SHORT_TIMEOUT) + t.join() + + def test_exception_with_timeout(self): + self.assertRaises(futures.TimeoutError, + PENDING_FUTURE.exception, timeout=0) + self.assertRaises(futures.TimeoutError, + RUNNING_FUTURE.exception, timeout=0) + self.assertRaises(futures.CancelledError, + CANCELLED_FUTURE.exception, timeout=0) + self.assertRaises(futures.CancelledError, + CANCELLED_AND_NOTIFIED_FUTURE.exception, timeout=0) + self.assertTrue(isinstance(EXCEPTION_FUTURE.exception(timeout=0), + OSError)) + self.assertEqual(SUCCESSFUL_FUTURE.exception(timeout=0), None) + + def test_exception_with_success(self): + def notification(): + # Wait until the main thread is waiting for the exception. + time.sleep(1) + with f1._condition: + f1._state = FINISHED + f1._exception = OSError() + f1._condition.notify_all() + + f1 = create_future(state=PENDING) + t = threading.Thread(target=notification) + t.start() + + self.assertTrue(isinstance(f1.exception(timeout=support.SHORT_TIMEOUT), OSError)) + t.join() + + def test_multiple_set_result(self): + f = create_future(state=PENDING) + f.set_result(1) + + with self.assertRaisesRegex( + futures.InvalidStateError, + 'FINISHED: ' + ): + f.set_result(2) + + self.assertTrue(f.done()) + self.assertEqual(f.result(), 1) + + def test_multiple_set_exception(self): + f = create_future(state=PENDING) + e = ValueError() + f.set_exception(e) + + with self.assertRaisesRegex( + futures.InvalidStateError, + 'FINISHED: ' + ): + f.set_exception(Exception()) + + self.assertEqual(f.exception(), e) + + +def setUpModule(): + setup_module() + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_concurrent_futures/test_init.py b/Lib/test/test_concurrent_futures/test_init.py new file mode 100644 index 00000000000..df640929309 --- /dev/null +++ b/Lib/test/test_concurrent_futures/test_init.py @@ -0,0 +1,152 @@ +import contextlib +import logging +import queue +import time +import unittest +import sys +import io +from concurrent.futures._base import BrokenExecutor +from concurrent.futures.process import _check_system_limits + +from logging.handlers import QueueHandler + +from test import support + +from .util import ExecutorMixin, create_executor_tests, setup_module + + +INITIALIZER_STATUS = 'uninitialized' + +def init(x): + global INITIALIZER_STATUS + INITIALIZER_STATUS = x + +def get_init_status(): + return INITIALIZER_STATUS + +def init_fail(log_queue=None): + if log_queue is not None: + logger = logging.getLogger('concurrent.futures') + logger.addHandler(QueueHandler(log_queue)) + logger.setLevel('CRITICAL') + logger.propagate = False + time.sleep(0.1) # let some futures be scheduled + raise ValueError('error in initializer') + + +class InitializerMixin(ExecutorMixin): + worker_count = 2 + + def setUp(self): + global INITIALIZER_STATUS + INITIALIZER_STATUS = 'uninitialized' + self.executor_kwargs = dict(initializer=init, + initargs=('initialized',)) + super().setUp() + + def test_initializer(self): + futures = [self.executor.submit(get_init_status) + for _ in range(self.worker_count)] + + for f in futures: + self.assertEqual(f.result(), 'initialized') + + +class FailingInitializerMixin(ExecutorMixin): + worker_count = 2 + + def setUp(self): + if hasattr(self, "ctx"): + # Pass a queue to redirect the child's logging output + self.mp_context = self.get_context() + self.log_queue = self.mp_context.Queue() + self.executor_kwargs = dict(initializer=init_fail, + initargs=(self.log_queue,)) + else: + # In a thread pool, the child shares our logging setup + # (see _assert_logged()) + self.mp_context = None + self.log_queue = None + self.executor_kwargs = dict(initializer=init_fail) + super().setUp() + + def test_initializer(self): + with self._assert_logged('ValueError: error in initializer'): + try: + future = self.executor.submit(get_init_status) + except BrokenExecutor: + # Perhaps the executor is already broken + pass + else: + with self.assertRaises(BrokenExecutor): + future.result() + + # At some point, the executor should break + for _ in support.sleeping_retry(support.SHORT_TIMEOUT, + "executor not broken"): + if self.executor._broken: + break + + # ... and from this point submit() is guaranteed to fail + with self.assertRaises(BrokenExecutor): + self.executor.submit(get_init_status) + + @contextlib.contextmanager + def _assert_logged(self, msg): + if self.log_queue is not None: + yield + output = [] + try: + while True: + output.append(self.log_queue.get_nowait().getMessage()) + except queue.Empty: + pass + else: + with self.assertLogs('concurrent.futures', 'CRITICAL') as cm: + yield + output = cm.output + self.assertTrue(any(msg in line for line in output), + output) + + +create_executor_tests(globals(), InitializerMixin) +create_executor_tests(globals(), FailingInitializerMixin) + + +@unittest.skipIf(sys.platform == "win32", "Resource Tracker doesn't run on Windows") +class FailingInitializerResourcesTest(unittest.TestCase): + """ + Source: https://github.com/python/cpython/issues/104090 + """ + + def _test(self, test_class): + try: + _check_system_limits() + except NotImplementedError: + self.skipTest("ProcessPoolExecutor unavailable on this system") + + runner = unittest.TextTestRunner(stream=io.StringIO()) + runner.run(test_class('test_initializer')) + + # GH-104090: + # Stop resource tracker manually now, so we can verify there are not leaked resources by checking + # the process exit code + from multiprocessing.resource_tracker import _resource_tracker + _resource_tracker._stop() + + self.assertEqual(_resource_tracker._exitcode, 0) + + def test_spawn(self): + self._test(ProcessPoolSpawnFailingInitializerTest) + + @support.skip_if_sanitizer("TSAN doesn't support threads after fork", thread=True) + def test_forkserver(self): + self._test(ProcessPoolForkserverFailingInitializerTest) + + +def setUpModule(): + setup_module() + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_concurrent_futures/test_process_pool.py b/Lib/test/test_concurrent_futures/test_process_pool.py new file mode 100644 index 00000000000..17d311888a6 --- /dev/null +++ b/Lib/test/test_concurrent_futures/test_process_pool.py @@ -0,0 +1,234 @@ +import os +import sys +import threading +import time +import unittest +from concurrent import futures +from concurrent.futures.process import BrokenProcessPool + +from test import support +from test.support import hashlib_helper + +from .executor import ExecutorTest, mul +from .util import ( + ProcessPoolForkMixin, ProcessPoolForkserverMixin, ProcessPoolSpawnMixin, + create_executor_tests, setup_module) + + +class EventfulGCObj(): + def __init__(self, mgr): + self.event = mgr.Event() + + def __del__(self): + self.event.set() + + +class ProcessPoolExecutorTest(ExecutorTest): + + @unittest.skipUnless(sys.platform=='win32', 'Windows-only process limit') + def test_max_workers_too_large(self): + with self.assertRaisesRegex(ValueError, + "max_workers must be <= 61"): + futures.ProcessPoolExecutor(max_workers=62) + + def test_killed_child(self): + # When a child process is abruptly terminated, the whole pool gets + # "broken". + futures = [self.executor.submit(time.sleep, 3)] + # Get one of the processes, and terminate (kill) it + p = next(iter(self.executor._processes.values())) + p.terminate() + for fut in futures: + self.assertRaises(BrokenProcessPool, fut.result) + # Submitting other jobs fails as well. + self.assertRaises(BrokenProcessPool, self.executor.submit, pow, 2, 8) + + def test_map_chunksize(self): + def bad_map(): + list(self.executor.map(pow, range(40), range(40), chunksize=-1)) + + ref = list(map(pow, range(40), range(40))) + self.assertEqual( + list(self.executor.map(pow, range(40), range(40), chunksize=6)), + ref) + self.assertEqual( + list(self.executor.map(pow, range(40), range(40), chunksize=50)), + ref) + self.assertEqual( + list(self.executor.map(pow, range(40), range(40), chunksize=40)), + ref) + self.assertRaises(ValueError, bad_map) + + @classmethod + def _test_traceback(cls): + raise RuntimeError(123) # some comment + + def test_traceback(self): + # We want ensure that the traceback from the child process is + # contained in the traceback raised in the main process. + future = self.executor.submit(self._test_traceback) + with self.assertRaises(Exception) as cm: + future.result() + + exc = cm.exception + self.assertIs(type(exc), RuntimeError) + self.assertEqual(exc.args, (123,)) + cause = exc.__cause__ + self.assertIs(type(cause), futures.process._RemoteTraceback) + self.assertIn('raise RuntimeError(123) # some comment', cause.tb) + + with support.captured_stderr() as f1: + try: + raise exc + except RuntimeError: + sys.excepthook(*sys.exc_info()) + self.assertIn('raise RuntimeError(123) # some comment', + f1.getvalue()) + + @hashlib_helper.requires_hashdigest('md5') + def test_ressources_gced_in_workers(self): + # Ensure that argument for a job are correctly gc-ed after the job + # is finished + mgr = self.get_context().Manager() + obj = EventfulGCObj(mgr) + future = self.executor.submit(id, obj) + future.result() + + self.assertTrue(obj.event.wait(timeout=1)) + + # explicitly destroy the object to ensure that EventfulGCObj.__del__() + # is called while manager is still running. + support.gc_collect() + obj = None + support.gc_collect() + + mgr.shutdown() + mgr.join() + + def test_saturation(self): + executor = self.executor + mp_context = self.get_context() + sem = mp_context.Semaphore(0) + job_count = 15 * executor._max_workers + for _ in range(job_count): + executor.submit(sem.acquire) + self.assertEqual(len(executor._processes), executor._max_workers) + for _ in range(job_count): + sem.release() + + @support.requires_gil_enabled("gh-117344: test is flaky without the GIL") + def test_idle_process_reuse_one(self): + executor = self.executor + assert executor._max_workers >= 4 + if self.get_context().get_start_method(allow_none=False) == "fork": + raise unittest.SkipTest("Incompatible with the fork start method.") + executor.submit(mul, 21, 2).result() + executor.submit(mul, 6, 7).result() + executor.submit(mul, 3, 14).result() + self.assertEqual(len(executor._processes), 1) + + def test_idle_process_reuse_multiple(self): + executor = self.executor + assert executor._max_workers <= 5 + if self.get_context().get_start_method(allow_none=False) == "fork": + raise unittest.SkipTest("Incompatible with the fork start method.") + executor.submit(mul, 12, 7).result() + executor.submit(mul, 33, 25) + executor.submit(mul, 25, 26).result() + executor.submit(mul, 18, 29) + executor.submit(mul, 1, 2).result() + executor.submit(mul, 0, 9) + self.assertLessEqual(len(executor._processes), 3) + executor.shutdown() + + def test_max_tasks_per_child(self): + context = self.get_context() + if context.get_start_method(allow_none=False) == "fork": + with self.assertRaises(ValueError): + self.executor_type(1, mp_context=context, max_tasks_per_child=3) + return + # not using self.executor as we need to control construction. + # arguably this could go in another class w/o that mixin. + executor = self.executor_type( + 1, mp_context=context, max_tasks_per_child=3) + f1 = executor.submit(os.getpid) + original_pid = f1.result() + # The worker pid remains the same as the worker could be reused + f2 = executor.submit(os.getpid) + self.assertEqual(f2.result(), original_pid) + self.assertEqual(len(executor._processes), 1) + f3 = executor.submit(os.getpid) + self.assertEqual(f3.result(), original_pid) + + # A new worker is spawned, with a statistically different pid, + # while the previous was reaped. + f4 = executor.submit(os.getpid) + new_pid = f4.result() + self.assertNotEqual(original_pid, new_pid) + self.assertEqual(len(executor._processes), 1) + + executor.shutdown() + + def test_max_tasks_per_child_defaults_to_spawn_context(self): + # not using self.executor as we need to control construction. + # arguably this could go in another class w/o that mixin. + executor = self.executor_type(1, max_tasks_per_child=3) + self.assertEqual(executor._mp_context.get_start_method(), "spawn") + + def test_max_tasks_early_shutdown(self): + context = self.get_context() + if context.get_start_method(allow_none=False) == "fork": + raise unittest.SkipTest("Incompatible with the fork start method.") + # not using self.executor as we need to control construction. + # arguably this could go in another class w/o that mixin. + executor = self.executor_type( + 3, mp_context=context, max_tasks_per_child=1) + futures = [] + for i in range(6): + futures.append(executor.submit(mul, i, i)) + executor.shutdown() + for i, future in enumerate(futures): + self.assertEqual(future.result(), mul(i, i)) + + @unittest.expectedFailure # TODO: RUSTPYTHON AttributeError: module 'threading' has no attribute '_start_joinable_thread'. Did you mean: '_start_new_thread'? + def test_python_finalization_error(self): + # gh-109047: Catch RuntimeError on thread creation + # during Python finalization. + + context = self.get_context() + + # gh-109047: Mock the threading.start_joinable_thread() function to inject + # RuntimeError: simulate the error raised during Python finalization. + # Block the second creation: create _ExecutorManagerThread, but block + # QueueFeederThread. + orig_start_new_thread = threading._start_joinable_thread + nthread = 0 + def mock_start_new_thread(func, *args, **kwargs): + nonlocal nthread + if nthread >= 1: + raise RuntimeError("can't create new thread at " + "interpreter shutdown") + nthread += 1 + return orig_start_new_thread(func, *args, **kwargs) + + with support.swap_attr(threading, '_start_joinable_thread', + mock_start_new_thread): + executor = self.executor_type(max_workers=2, mp_context=context) + with executor: + with self.assertRaises(BrokenProcessPool): + list(executor.map(mul, [(2, 3)] * 10)) + executor.shutdown() + + +create_executor_tests(globals(), ProcessPoolExecutorTest, + executor_mixins=(ProcessPoolForkMixin, + ProcessPoolForkserverMixin, + ProcessPoolSpawnMixin)) + + +def setUpModule(): + setup_module() + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_concurrent_futures/test_shutdown.py b/Lib/test/test_concurrent_futures/test_shutdown.py new file mode 100644 index 00000000000..c879b6539b1 --- /dev/null +++ b/Lib/test/test_concurrent_futures/test_shutdown.py @@ -0,0 +1,406 @@ +import signal +import sys +import threading +import time +import unittest +from concurrent import futures + +from test import support +from test.support.script_helper import assert_python_ok + +from .util import ( + BaseTestCase, ThreadPoolMixin, ProcessPoolForkMixin, + ProcessPoolForkserverMixin, ProcessPoolSpawnMixin, + create_executor_tests, setup_module) + + +def sleep_and_print(t, msg): + time.sleep(t) + print(msg) + sys.stdout.flush() + + +class ExecutorShutdownTest: + def test_run_after_shutdown(self): + self.executor.shutdown() + self.assertRaises(RuntimeError, + self.executor.submit, + pow, 2, 5) + + @unittest.skip('TODO: RUSTPYTHON; hangs') + def test_interpreter_shutdown(self): + # Test the atexit hook for shutdown of worker threads and processes + rc, out, err = assert_python_ok('-c', """if 1: + from concurrent.futures import {executor_type} + from time import sleep + from test.test_concurrent_futures.test_shutdown import sleep_and_print + if __name__ == "__main__": + context = '{context}' + if context == "": + t = {executor_type}(5) + else: + from multiprocessing import get_context + context = get_context(context) + t = {executor_type}(5, mp_context=context) + t.submit(sleep_and_print, 1.0, "apple") + """.format(executor_type=self.executor_type.__name__, + context=getattr(self, "ctx", ""))) + # Errors in atexit hooks don't change the process exit code, check + # stderr manually. + self.assertFalse(err) + self.assertEqual(out.strip(), b"apple") + + @unittest.skip('TODO: RUSTPYTHON; Hangs') + def test_submit_after_interpreter_shutdown(self): + # Test the atexit hook for shutdown of worker threads and processes + rc, out, err = assert_python_ok('-c', """if 1: + import atexit + @atexit.register + def run_last(): + try: + t.submit(id, None) + except RuntimeError: + print("runtime-error") + raise + from concurrent.futures import {executor_type} + if __name__ == "__main__": + context = '{context}' + if not context: + t = {executor_type}(5) + else: + from multiprocessing import get_context + context = get_context(context) + t = {executor_type}(5, mp_context=context) + t.submit(id, 42).result() + """.format(executor_type=self.executor_type.__name__, + context=getattr(self, "ctx", ""))) + # Errors in atexit hooks don't change the process exit code, check + # stderr manually. + self.assertIn("RuntimeError: cannot schedule new futures", err.decode()) + self.assertEqual(out.strip(), b"runtime-error") + + def test_hang_issue12364(self): + fs = [self.executor.submit(time.sleep, 0.1) for _ in range(50)] + self.executor.shutdown() + for f in fs: + f.result() + + def test_cancel_futures(self): + assert self.worker_count <= 5, "test needs few workers" + fs = [self.executor.submit(time.sleep, .1) for _ in range(50)] + self.executor.shutdown(cancel_futures=True) + # We can't guarantee the exact number of cancellations, but we can + # guarantee that *some* were cancelled. With few workers, many of + # the submitted futures should have been cancelled. + cancelled = [fut for fut in fs if fut.cancelled()] + self.assertGreater(len(cancelled), 20) + + # Ensure the other futures were able to finish. + # Use "not fut.cancelled()" instead of "fut.done()" to include futures + # that may have been left in a pending state. + others = [fut for fut in fs if not fut.cancelled()] + for fut in others: + self.assertTrue(fut.done(), msg=f"{fut._state=}") + self.assertIsNone(fut.exception()) + + # Similar to the number of cancelled futures, we can't guarantee the + # exact number that completed. But, we can guarantee that at least + # one finished. + self.assertGreater(len(others), 0) + + @unittest.expectedFailure # TODO: RUSTPYTHON AssertionError: b'' != b'apple' + def test_hang_gh83386(self): + """shutdown(wait=False) doesn't hang at exit with running futures. + + See https://github.com/python/cpython/issues/83386. + """ + if self.executor_type == futures.ProcessPoolExecutor: + raise unittest.SkipTest( + "Hangs, see https://github.com/python/cpython/issues/83386") + + rc, out, err = assert_python_ok('-c', """if True: + from concurrent.futures import {executor_type} + from test.test_concurrent_futures.test_shutdown import sleep_and_print + if __name__ == "__main__": + if {context!r}: multiprocessing.set_start_method({context!r}) + t = {executor_type}(max_workers=3) + t.submit(sleep_and_print, 1.0, "apple") + t.shutdown(wait=False) + """.format(executor_type=self.executor_type.__name__, + context=getattr(self, 'ctx', None))) + self.assertFalse(err) + self.assertEqual(out.strip(), b"apple") + + def test_hang_gh94440(self): + """shutdown(wait=True) doesn't hang when a future was submitted and + quickly canceled right before shutdown. + + See https://github.com/python/cpython/issues/94440. + """ + if not hasattr(signal, 'alarm'): + raise unittest.SkipTest( + "Tested platform does not support the alarm signal") + + def timeout(_signum, _frame): + raise RuntimeError("timed out waiting for shutdown") + + kwargs = {} + if getattr(self, 'ctx', None): + kwargs['mp_context'] = self.get_context() + executor = self.executor_type(max_workers=1, **kwargs) + executor.submit(int).result() + old_handler = signal.signal(signal.SIGALRM, timeout) + try: + signal.alarm(5) + executor.submit(int).cancel() + executor.shutdown(wait=True) + finally: + signal.alarm(0) + signal.signal(signal.SIGALRM, old_handler) + + +class ThreadPoolShutdownTest(ThreadPoolMixin, ExecutorShutdownTest, BaseTestCase): + def test_threads_terminate(self): + def acquire_lock(lock): + lock.acquire() + + sem = threading.Semaphore(0) + for i in range(3): + self.executor.submit(acquire_lock, sem) + self.assertEqual(len(self.executor._threads), 3) + for i in range(3): + sem.release() + self.executor.shutdown() + for t in self.executor._threads: + t.join() + + def test_context_manager_shutdown(self): + with futures.ThreadPoolExecutor(max_workers=5) as e: + executor = e + self.assertEqual(list(e.map(abs, range(-5, 5))), + [5, 4, 3, 2, 1, 0, 1, 2, 3, 4]) + + for t in executor._threads: + t.join() + + def test_del_shutdown(self): + executor = futures.ThreadPoolExecutor(max_workers=5) + res = executor.map(abs, range(-5, 5)) + threads = executor._threads + del executor + + for t in threads: + t.join() + + # Make sure the results were all computed before the + # executor got shutdown. + assert all([r == abs(v) for r, v in zip(res, range(-5, 5))]) + + def test_shutdown_no_wait(self): + # Ensure that the executor cleans up the threads when calling + # shutdown with wait=False + executor = futures.ThreadPoolExecutor(max_workers=5) + res = executor.map(abs, range(-5, 5)) + threads = executor._threads + executor.shutdown(wait=False) + for t in threads: + t.join() + + # Make sure the results were all computed before the + # executor got shutdown. + assert all([r == abs(v) for r, v in zip(res, range(-5, 5))]) + + + def test_thread_names_assigned(self): + executor = futures.ThreadPoolExecutor( + max_workers=5, thread_name_prefix='SpecialPool') + executor.map(abs, range(-5, 5)) + threads = executor._threads + del executor + support.gc_collect() # For PyPy or other GCs. + + for t in threads: + self.assertRegex(t.name, r'^SpecialPool_[0-4]$') + t.join() + + def test_thread_names_default(self): + executor = futures.ThreadPoolExecutor(max_workers=5) + executor.map(abs, range(-5, 5)) + threads = executor._threads + del executor + support.gc_collect() # For PyPy or other GCs. + + for t in threads: + # Ensure that our default name is reasonably sane and unique when + # no thread_name_prefix was supplied. + self.assertRegex(t.name, r'ThreadPoolExecutor-\d+_[0-4]$') + t.join() + + def test_cancel_futures_wait_false(self): + # Can only be reliably tested for TPE, since PPE often hangs with + # `wait=False` (even without *cancel_futures*). + rc, out, err = assert_python_ok('-c', """if True: + from concurrent.futures import ThreadPoolExecutor + from test.test_concurrent_futures.test_shutdown import sleep_and_print + if __name__ == "__main__": + t = ThreadPoolExecutor() + t.submit(sleep_and_print, .1, "apple") + t.shutdown(wait=False, cancel_futures=True) + """) + # Errors in atexit hooks don't change the process exit code, check + # stderr manually. + self.assertFalse(err) + # gh-116682: stdout may be empty if shutdown happens before task + # starts executing. + self.assertIn(out.strip(), [b"apple", b""]) + + +class ProcessPoolShutdownTest(ExecutorShutdownTest): + def test_processes_terminate(self): + def acquire_lock(lock): + lock.acquire() + + mp_context = self.get_context() + if mp_context.get_start_method(allow_none=False) == "fork": + # fork pre-spawns, not on demand. + expected_num_processes = self.worker_count + else: + expected_num_processes = 3 + + sem = mp_context.Semaphore(0) + for _ in range(3): + self.executor.submit(acquire_lock, sem) + self.assertEqual(len(self.executor._processes), expected_num_processes) + for _ in range(3): + sem.release() + processes = self.executor._processes + self.executor.shutdown() + + for p in processes.values(): + p.join() + + def test_context_manager_shutdown(self): + with futures.ProcessPoolExecutor( + max_workers=5, mp_context=self.get_context()) as e: + processes = e._processes + self.assertEqual(list(e.map(abs, range(-5, 5))), + [5, 4, 3, 2, 1, 0, 1, 2, 3, 4]) + + for p in processes.values(): + p.join() + + def test_del_shutdown(self): + executor = futures.ProcessPoolExecutor( + max_workers=5, mp_context=self.get_context()) + res = executor.map(abs, range(-5, 5)) + executor_manager_thread = executor._executor_manager_thread + processes = executor._processes + call_queue = executor._call_queue + executor_manager_thread = executor._executor_manager_thread + del executor + support.gc_collect() # For PyPy or other GCs. + + # Make sure that all the executor resources were properly cleaned by + # the shutdown process + executor_manager_thread.join() + for p in processes.values(): + p.join() + call_queue.join_thread() + + # Make sure the results were all computed before the + # executor got shutdown. + assert all([r == abs(v) for r, v in zip(res, range(-5, 5))]) + + def test_shutdown_no_wait(self): + # Ensure that the executor cleans up the processes when calling + # shutdown with wait=False + executor = futures.ProcessPoolExecutor( + max_workers=5, mp_context=self.get_context()) + res = executor.map(abs, range(-5, 5)) + processes = executor._processes + call_queue = executor._call_queue + executor_manager_thread = executor._executor_manager_thread + executor.shutdown(wait=False) + + # Make sure that all the executor resources were properly cleaned by + # the shutdown process + executor_manager_thread.join() + for p in processes.values(): + p.join() + call_queue.join_thread() + + # Make sure the results were all computed before the executor got + # shutdown. + assert all([r == abs(v) for r, v in zip(res, range(-5, 5))]) + + @classmethod + def _failing_task_gh_132969(cls, n): + raise ValueError("failing task") + + @classmethod + def _good_task_gh_132969(cls, n): + time.sleep(0.1 * n) + return n + + def _run_test_issue_gh_132969(self, max_workers): + # max_workers=2 will repro exception + # max_workers=4 will repro exception and then hang + + # Repro conditions + # max_tasks_per_child=1 + # a task ends abnormally + # shutdown(wait=False) is called + start_method = self.get_context().get_start_method() + if (start_method == "fork" or + (start_method == "forkserver" and sys.platform.startswith("win"))): + self.skipTest(f"Skipping test for {start_method = }") + executor = futures.ProcessPoolExecutor( + max_workers=max_workers, + max_tasks_per_child=1, + mp_context=self.get_context()) + f1 = executor.submit(ProcessPoolShutdownTest._good_task_gh_132969, 1) + f2 = executor.submit(ProcessPoolShutdownTest._failing_task_gh_132969, 2) + f3 = executor.submit(ProcessPoolShutdownTest._good_task_gh_132969, 3) + result = 0 + try: + result += f1.result() + result += f2.result() + result += f3.result() + except ValueError: + # stop processing results upon first exception + pass + + # Ensure that the executor cleans up after called + # shutdown with wait=False + executor_manager_thread = executor._executor_manager_thread + executor.shutdown(wait=False) + time.sleep(0.2) + executor_manager_thread.join() + return result + + def test_shutdown_gh_132969_case_1(self): + # gh-132969: test that exception "object of type 'NoneType' has no len()" + # is not raised when shutdown(wait=False) is called. + result = self._run_test_issue_gh_132969(2) + self.assertEqual(result, 1) + + def test_shutdown_gh_132969_case_2(self): + # gh-132969: test that process does not hang and + # exception "object of type 'NoneType' has no len()" is not raised + # when shutdown(wait=False) is called. + result = self._run_test_issue_gh_132969(4) + self.assertEqual(result, 1) + + +create_executor_tests(globals(), ProcessPoolShutdownTest, + executor_mixins=(ProcessPoolForkMixin, + ProcessPoolForkserverMixin, + ProcessPoolSpawnMixin)) + + +def setUpModule(): + setup_module() + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_concurrent_futures/test_thread_pool.py b/Lib/test/test_concurrent_futures/test_thread_pool.py new file mode 100644 index 00000000000..4a60aff4239 --- /dev/null +++ b/Lib/test/test_concurrent_futures/test_thread_pool.py @@ -0,0 +1,122 @@ +import contextlib +import multiprocessing as mp +import multiprocessing.process +import multiprocessing.util +import os +import threading +import unittest +from concurrent import futures +from test import support + +from .executor import ExecutorTest, mul +from .util import BaseTestCase, ThreadPoolMixin, setup_module + + +class ThreadPoolExecutorTest(ThreadPoolMixin, ExecutorTest, BaseTestCase): + def test_map_submits_without_iteration(self): + """Tests verifying issue 11777.""" + finished = [] + def record_finished(n): + finished.append(n) + + self.executor.map(record_finished, range(10)) + self.executor.shutdown(wait=True) + self.assertCountEqual(finished, range(10)) + + def test_default_workers(self): + executor = self.executor_type() + expected = min(32, (os.process_cpu_count() or 1) + 4) + self.assertEqual(executor._max_workers, expected) + + def test_saturation(self): + executor = self.executor_type(4) + def acquire_lock(lock): + lock.acquire() + + sem = threading.Semaphore(0) + for i in range(15 * executor._max_workers): + executor.submit(acquire_lock, sem) + self.assertEqual(len(executor._threads), executor._max_workers) + for i in range(15 * executor._max_workers): + sem.release() + executor.shutdown(wait=True) + + @support.requires_gil_enabled("gh-117344: test is flaky without the GIL") + def test_idle_thread_reuse(self): + executor = self.executor_type() + executor.submit(mul, 21, 2).result() + executor.submit(mul, 6, 7).result() + executor.submit(mul, 3, 14).result() + self.assertEqual(len(executor._threads), 1) + executor.shutdown(wait=True) + + @support.requires_fork() + @unittest.skipUnless(hasattr(os, 'register_at_fork'), 'need os.register_at_fork') + @support.requires_resource('cpu') + def test_hang_global_shutdown_lock(self): + # bpo-45021: _global_shutdown_lock should be reinitialized in the child + # process, otherwise it will never exit + def submit(pool): + pool.submit(submit, pool) + + with futures.ThreadPoolExecutor(1) as pool: + pool.submit(submit, pool) + + for _ in range(50): + with futures.ProcessPoolExecutor(1, mp_context=mp.get_context('fork')) as workers: + workers.submit(tuple) + + @support.requires_fork() + @unittest.skipUnless(hasattr(os, 'register_at_fork'), 'need os.register_at_fork') + @unittest.expectedFailure # TODO: RUSTPYTHON AssertionError: DeprecationWarning not triggered + def test_process_fork_from_a_threadpool(self): + # bpo-43944: clear concurrent.futures.thread._threads_queues after fork, + # otherwise child process will try to join parent thread + def fork_process_and_return_exitcode(): + # Ignore the warning about fork with threads. + with self.assertWarnsRegex(DeprecationWarning, + r"use of fork\(\) may lead to deadlocks in the child"): + p = mp.get_context('fork').Process(target=lambda: 1) + p.start() + p.join() + return p.exitcode + + with futures.ThreadPoolExecutor(1) as pool: + process_exitcode = pool.submit(fork_process_and_return_exitcode).result() + + self.assertEqual(process_exitcode, 0) + + def test_executor_map_current_future_cancel(self): + stop_event = threading.Event() + log = [] + + def log_n_wait(ident): + log.append(f"{ident=} started") + try: + stop_event.wait() + finally: + log.append(f"{ident=} stopped") + + with self.executor_type(max_workers=1) as pool: + # submit work to saturate the pool + fut = pool.submit(log_n_wait, ident="first") + try: + with contextlib.closing( + pool.map(log_n_wait, ["second", "third"], timeout=0) + ) as gen: + with self.assertRaises(TimeoutError): + next(gen) + finally: + stop_event.set() + fut.result() + # ident='second' is cancelled as a result of raising a TimeoutError + # ident='third' is cancelled because it remained in the collection of futures + self.assertListEqual(log, ["ident='first' started", "ident='first' stopped"]) + + +def setUpModule(): + setup_module() + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_concurrent_futures/test_wait.py b/Lib/test/test_concurrent_futures/test_wait.py new file mode 100644 index 00000000000..cc387883141 --- /dev/null +++ b/Lib/test/test_concurrent_futures/test_wait.py @@ -0,0 +1,205 @@ +import sys +import threading +import unittest +from concurrent import futures +from test import support +from test.support import threading_helper + +from .util import ( + CANCELLED_FUTURE, CANCELLED_AND_NOTIFIED_FUTURE, EXCEPTION_FUTURE, + SUCCESSFUL_FUTURE, + create_executor_tests, setup_module, + BaseTestCase, ThreadPoolMixin, + ProcessPoolForkMixin, ProcessPoolForkserverMixin, ProcessPoolSpawnMixin) + + +def mul(x, y): + return x * y + +def wait_and_raise(e): + e.wait() + raise Exception('this is an exception') + + +class WaitTests: + def test_20369(self): + # See https://bugs.python.org/issue20369 + future = self.executor.submit(mul, 1, 2) + done, not_done = futures.wait([future, future], + return_when=futures.ALL_COMPLETED) + self.assertEqual({future}, done) + self.assertEqual(set(), not_done) + + + def test_first_completed(self): + event = self.create_event() + future1 = self.executor.submit(mul, 21, 2) + future2 = self.executor.submit(event.wait) + + try: + done, not_done = futures.wait( + [CANCELLED_FUTURE, future1, future2], + return_when=futures.FIRST_COMPLETED) + + self.assertEqual(set([future1]), done) + self.assertEqual(set([CANCELLED_FUTURE, future2]), not_done) + finally: + event.set() + future2.result() # wait for job to finish + + def test_first_completed_some_already_completed(self): + event = self.create_event() + future1 = self.executor.submit(event.wait) + + try: + finished, pending = futures.wait( + [CANCELLED_AND_NOTIFIED_FUTURE, SUCCESSFUL_FUTURE, future1], + return_when=futures.FIRST_COMPLETED) + + self.assertEqual( + set([CANCELLED_AND_NOTIFIED_FUTURE, SUCCESSFUL_FUTURE]), + finished) + self.assertEqual(set([future1]), pending) + finally: + event.set() + future1.result() # wait for job to finish + + def test_first_exception(self): + event1 = self.create_event() + event2 = self.create_event() + try: + future1 = self.executor.submit(mul, 2, 21) + future2 = self.executor.submit(wait_and_raise, event1) + future3 = self.executor.submit(event2.wait) + + # Ensure that future1 is completed before future2 finishes + def wait_for_future1(): + future1.result() + event1.set() + + t = threading.Thread(target=wait_for_future1) + t.start() + + finished, pending = futures.wait( + [future1, future2, future3], + return_when=futures.FIRST_EXCEPTION) + + self.assertEqual(set([future1, future2]), finished) + self.assertEqual(set([future3]), pending) + + threading_helper.join_thread(t) + finally: + event1.set() + event2.set() + future3.result() # wait for job to finish + + def test_first_exception_some_already_complete(self): + event = self.create_event() + future1 = self.executor.submit(divmod, 21, 0) + future2 = self.executor.submit(event.wait) + + try: + finished, pending = futures.wait( + [SUCCESSFUL_FUTURE, + CANCELLED_FUTURE, + CANCELLED_AND_NOTIFIED_FUTURE, + future1, future2], + return_when=futures.FIRST_EXCEPTION) + + self.assertEqual(set([SUCCESSFUL_FUTURE, + CANCELLED_AND_NOTIFIED_FUTURE, + future1]), finished) + self.assertEqual(set([CANCELLED_FUTURE, future2]), pending) + finally: + event.set() + future2.result() # wait for job to finish + + def test_first_exception_one_already_failed(self): + event = self.create_event() + future1 = self.executor.submit(event.wait) + + try: + finished, pending = futures.wait( + [EXCEPTION_FUTURE, future1], + return_when=futures.FIRST_EXCEPTION) + + self.assertEqual(set([EXCEPTION_FUTURE]), finished) + self.assertEqual(set([future1]), pending) + finally: + event.set() + future1.result() # wait for job to finish + + def test_all_completed(self): + future1 = self.executor.submit(divmod, 2, 0) + future2 = self.executor.submit(mul, 2, 21) + + finished, pending = futures.wait( + [SUCCESSFUL_FUTURE, + CANCELLED_AND_NOTIFIED_FUTURE, + EXCEPTION_FUTURE, + future1, + future2], + return_when=futures.ALL_COMPLETED) + + self.assertEqual(set([SUCCESSFUL_FUTURE, + CANCELLED_AND_NOTIFIED_FUTURE, + EXCEPTION_FUTURE, + future1, + future2]), finished) + self.assertEqual(set(), pending) + + def test_timeout(self): + short_timeout = 0.050 + + event = self.create_event() + future = self.executor.submit(event.wait) + + try: + finished, pending = futures.wait( + [CANCELLED_AND_NOTIFIED_FUTURE, + EXCEPTION_FUTURE, + SUCCESSFUL_FUTURE, + future], + timeout=short_timeout, + return_when=futures.ALL_COMPLETED) + + self.assertEqual(set([CANCELLED_AND_NOTIFIED_FUTURE, + EXCEPTION_FUTURE, + SUCCESSFUL_FUTURE]), + finished) + self.assertEqual(set([future]), pending) + finally: + event.set() + future.result() # wait for job to finish + + +class ThreadPoolWaitTests(ThreadPoolMixin, WaitTests, BaseTestCase): + + def test_pending_calls_race(self): + # Issue #14406: multi-threaded race condition when waiting on all + # futures. + event = threading.Event() + def future_func(): + event.wait() + oldswitchinterval = sys.getswitchinterval() + support.setswitchinterval(1e-6) + try: + fs = {self.executor.submit(future_func) for i in range(100)} + event.set() + futures.wait(fs, return_when=futures.ALL_COMPLETED) + finally: + sys.setswitchinterval(oldswitchinterval) + + +create_executor_tests(globals(), WaitTests, + executor_mixins=(ProcessPoolForkMixin, + ProcessPoolForkserverMixin, + ProcessPoolSpawnMixin)) + + +def setUpModule(): + setup_module() + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_concurrent_futures/util.py b/Lib/test/test_concurrent_futures/util.py new file mode 100644 index 00000000000..e85ef3b1c91 --- /dev/null +++ b/Lib/test/test_concurrent_futures/util.py @@ -0,0 +1,169 @@ +import multiprocessing +import sys +import threading +import time +import unittest +from concurrent import futures +from concurrent.futures._base import ( + PENDING, RUNNING, CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED, Future, + ) +from concurrent.futures.process import _check_system_limits + +from test import support +from test.support import threading_helper + + +def create_future(state=PENDING, exception=None, result=None): + f = Future() + f._state = state + f._exception = exception + f._result = result + return f + + +PENDING_FUTURE = create_future(state=PENDING) +RUNNING_FUTURE = create_future(state=RUNNING) +CANCELLED_FUTURE = create_future(state=CANCELLED) +CANCELLED_AND_NOTIFIED_FUTURE = create_future(state=CANCELLED_AND_NOTIFIED) +EXCEPTION_FUTURE = create_future(state=FINISHED, exception=OSError()) +SUCCESSFUL_FUTURE = create_future(state=FINISHED, result=42) + + +class BaseTestCase(unittest.TestCase): + def setUp(self): + self._thread_key = threading_helper.threading_setup() + + def tearDown(self): + support.reap_children() + threading_helper.threading_cleanup(*self._thread_key) + + +class ExecutorMixin: + worker_count = 5 + executor_kwargs = {} + + def setUp(self): + super().setUp() + + self.t1 = time.monotonic() + if hasattr(self, "ctx"): + self.executor = self.executor_type( + max_workers=self.worker_count, + mp_context=self.get_context(), + **self.executor_kwargs) + self.manager = self.get_context().Manager() + else: + self.executor = self.executor_type( + max_workers=self.worker_count, + **self.executor_kwargs) + self.manager = None + + def tearDown(self): + self.executor.shutdown(wait=True) + self.executor = None + if self.manager is not None: + self.manager.shutdown() + self.manager = None + + dt = time.monotonic() - self.t1 + if support.verbose: + print("%.2fs" % dt, end=' ') + self.assertLess(dt, 300, "synchronization issue: test lasted too long") + + super().tearDown() + + def get_context(self): + return multiprocessing.get_context(self.ctx) + + +class ThreadPoolMixin(ExecutorMixin): + executor_type = futures.ThreadPoolExecutor + + def create_event(self): + return threading.Event() + + +class ProcessPoolForkMixin(ExecutorMixin): + executor_type = futures.ProcessPoolExecutor + ctx = "fork" + + def get_context(self): + try: + _check_system_limits() + except NotImplementedError: + self.skipTest("ProcessPoolExecutor unavailable on this system") + if sys.platform == "win32": + self.skipTest("require unix system") + if support.check_sanitizer(thread=True): + self.skipTest("TSAN doesn't support threads after fork") + return super().get_context() + + def create_event(self): + return self.manager.Event() + + +class ProcessPoolSpawnMixin(ExecutorMixin): + executor_type = futures.ProcessPoolExecutor + ctx = "spawn" + + def get_context(self): + try: + _check_system_limits() + except NotImplementedError: + self.skipTest("ProcessPoolExecutor unavailable on this system") + return super().get_context() + + def create_event(self): + return self.manager.Event() + + +class ProcessPoolForkserverMixin(ExecutorMixin): + executor_type = futures.ProcessPoolExecutor + ctx = "forkserver" + + def get_context(self): + try: + _check_system_limits() + except NotImplementedError: + self.skipTest("ProcessPoolExecutor unavailable on this system") + if sys.platform == "win32": + self.skipTest("require unix system") + if support.check_sanitizer(thread=True): + self.skipTest("TSAN doesn't support threads after fork") + return super().get_context() + + def create_event(self): + return self.manager.Event() + + +def create_executor_tests(remote_globals, mixin, bases=(BaseTestCase,), + executor_mixins=(ThreadPoolMixin, + ProcessPoolForkMixin, + ProcessPoolForkserverMixin, + ProcessPoolSpawnMixin)): + def strip_mixin(name): + if name.endswith(('Mixin', 'Tests')): + return name[:-5] + elif name.endswith('Test'): + return name[:-4] + else: + return name + + module = remote_globals['__name__'] + for exe in executor_mixins: + name = ("%s%sTest" + % (strip_mixin(exe.__name__), strip_mixin(mixin.__name__))) + cls = type(name, (mixin,) + (exe,) + bases, {'__module__': module}) + remote_globals[name] = cls + + +def setup_module(): + try: + _check_system_limits() + except NotImplementedError: + pass + else: + unittest.addModuleCleanup(multiprocessing.util._cleanup_tests) + + thread_info = threading_helper.threading_setup() + unittest.addModuleCleanup(threading_helper.threading_cleanup, *thread_info) From 83a0deac25360cbce36b2bb2237deb687dee17ea Mon Sep 17 00:00:00 2001 From: HueCodes <197298026+HueCodes@users.noreply.github.com> Date: Fri, 9 Jan 2026 04:31:21 -0800 Subject: [PATCH 711/819] Fix set in-place operators with self argument (#6661) Co-authored-by: Hugh --- crates/vm/src/builtins/set.rs | 30 +++++++++++++++++++++++------ extra_tests/snippets/builtin_set.py | 12 ++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/crates/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs index 1c0268e2ed7..c3473809734 100644 --- a/crates/vm/src/builtins/set.rs +++ b/crates/vm/src/builtins/set.rs @@ -24,6 +24,7 @@ use crate::{ vm::VirtualMachine, }; use alloc::fmt; +use core::borrow::Borrow; use core::ops::Deref; use rustpython_common::{ atomic::{Ordering, PyAtomic, Radium}, @@ -719,8 +720,10 @@ impl PySet { } fn __iand__(zelf: PyRef, set: AnySet, vm: &VirtualMachine) -> PyResult> { - zelf.inner - .intersection_update(core::iter::once(set.into_iterable(vm)?), vm)?; + if !set.is(zelf.as_object()) { + zelf.inner + .intersection_update(core::iter::once(set.into_iterable(vm)?), vm)?; + } Ok(zelf) } @@ -731,8 +734,12 @@ impl PySet { } fn __isub__(zelf: PyRef, set: AnySet, vm: &VirtualMachine) -> PyResult> { - zelf.inner - .difference_update(set.into_iterable_iter(vm)?, vm)?; + if set.is(zelf.as_object()) { + zelf.inner.clear(); + } else { + zelf.inner + .difference_update(set.into_iterable_iter(vm)?, vm)?; + } Ok(zelf) } @@ -748,8 +755,12 @@ impl PySet { } fn __ixor__(zelf: PyRef, set: AnySet, vm: &VirtualMachine) -> PyResult> { - zelf.inner - .symmetric_difference_update(set.into_iterable_iter(vm)?, vm)?; + if set.is(zelf.as_object()) { + zelf.inner.clear(); + } else { + zelf.inner + .symmetric_difference_update(set.into_iterable_iter(vm)?, vm)?; + } Ok(zelf) } @@ -1297,6 +1308,13 @@ struct AnySet { object: PyObjectRef, } +impl Borrow for AnySet { + #[inline(always)] + fn borrow(&self) -> &PyObject { + &self.object + } +} + impl AnySet { /// Check if object is a set or frozenset (including subclasses) /// Equivalent to CPython's PyAnySet_Check diff --git a/extra_tests/snippets/builtin_set.py b/extra_tests/snippets/builtin_set.py index 1b2f6ff0968..950875ea09a 100644 --- a/extra_tests/snippets/builtin_set.py +++ b/extra_tests/snippets/builtin_set.py @@ -200,6 +200,18 @@ class S(set): with assert_raises(TypeError): a &= [1, 2, 3] +a = set([1, 2, 3]) +a &= a +assert a == set([1, 2, 3]) + +a = set([1, 2, 3]) +a -= a +assert a == set() + +a = set([1, 2, 3]) +a ^= a +assert a == set() + a = set([1, 2, 3]) a.difference_update([3, 4, 5]) assert a == set([1, 2]) From c2bfdf30bd32a093f12ac61b486aaf298514e4b2 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 9 Jan 2026 15:52:47 +0200 Subject: [PATCH 712/819] Assign opcode ids (#6637) --- Lib/_opcode_metadata.py | 47 +- Lib/test/test__opcode.py | 1 + Lib/test/test_dis.py | 1 + crates/codegen/src/compile.rs | 152 +++---- crates/compiler-core/src/bytecode.rs | 616 +++++++++------------------ crates/jit/src/instructions.rs | 4 +- crates/jit/tests/common.rs | 4 +- crates/stdlib/src/opcode.rs | 8 +- crates/vm/src/frame.rs | 72 +--- scripts/generate_opcode_metadata.py | 271 +++--------- 10 files changed, 384 insertions(+), 792 deletions(-) diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 0d6b7f1109e..529f5cbf656 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -10,7 +10,7 @@ 'CACHE': 0, 'BEFORE_ASYNC_WITH': 1, 'BEFORE_WITH': 2, - 'RESERVED_3': 3, + 'BINARY_OP_INPLACE_ADD_UNICODE': 3, 'BINARY_SLICE': 4, 'BINARY_SUBSCR': 5, 'CHECK_EG_MATCH': 6, @@ -24,7 +24,7 @@ 'FORMAT_SIMPLE': 14, 'FORMAT_WITH_SPEC': 15, 'GET_AITER': 16, - 'RESERVED_17': 17, + 'RESERVED': 17, 'GET_ANEXT': 18, 'GET_ITER': 19, 'GET_LEN': 20, @@ -127,36 +127,27 @@ 'UNPACK_SEQUENCE': 117, 'YIELD_VALUE': 118, 'BREAK': 119, - 'BUILD_LIST_UNPACK': 120, + 'BUILD_LIST_FROM_TUPLES': 120, 'BUILD_MAP_FOR_CALL': 121, - 'BUILD_SET_UNPACK': 122, - 'BUILD_TUPLE_ITER': 123, - 'BUILD_TUPLE_UNPACK': 124, - 'CALL_METHOD': 125, - 'CALL_METHOD_KW': 126, + 'BUILD_SET_FROM_TUPLES': 122, + 'BUILD_TUPLE_FROM_ITER': 123, + 'BUILD_TUPLE_FROM_TUPLES': 124, + 'CALL_METHOD_POSITIONAL': 125, + 'CALL_METHOD_KEYWORD': 126, 'CALL_METHOD_EX': 127, 'CONTINUE': 128, - 'JUMP': 129, - 'JUMP_IF_FALSE_OR_POP': 130, - 'JUMP_IF_TRUE_OR_POP': 131, - 'JUMP_IF_NOT_EXC_MATCH': 132, - 'LOAD_CLASSDEREF': 133, - 'LOAD_CLOSURE': 134, - 'LOAD_METHOD': 135, - 'POP_BLOCK': 136, - 'REVERSE': 137, - 'SET_EXC_INFO': 138, - 'SUBSCRIPT': 139, - 'UNARY_OP': 140, - 'RESERVED_141': 141, - 'RESERVED_142': 142, - 'RESERVED_143': 143, - 'RESERVED_144': 144, - 'RESERVED_145': 145, - 'RESERVED_146': 146, - 'RESERVED_147': 147, - 'RESERVED_148': 148, + 'JUMP_IF_FALSE_OR_POP': 129, + 'JUMP_IF_TRUE_OR_POP': 130, + 'JUMP_IF_NOT_EXC_MATCH': 131, + 'LOAD_CLASS_DEREF': 132, + 'REVERSE': 133, + 'SET_EXC_INFO': 134, + 'SUBSCRIPT': 135, 'RESUME': 149, + 'JUMP': 252, + 'LOAD_CLOSURE': 253, + 'LOAD_METHOD': 254, + 'POP_BLOCK': 255, } # CPython 3.13 compatible: opcodes < 44 have no argument diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py index 60dcdc6cd70..dabe95f7167 100644 --- a/Lib/test/test__opcode.py +++ b/Lib/test/test__opcode.py @@ -16,6 +16,7 @@ def check_bool_function_result(self, func, ops, expected): self.assertIsInstance(func(op), bool) self.assertEqual(func(op), expected) + @unittest.expectedFailure # TODO: RUSTPYTHON; Only supporting u8 ATM def test_invalid_opcodes(self): invalid = [-100, -1, 255, 512, 513, 1000] self.check_bool_function_result(_opcode.is_valid, invalid, False) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index eddfc155619..9526bdcbbaa 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -937,6 +937,7 @@ def test_opname(self): def test_boundaries(self): self.assertEqual(dis.opmap["EXTENDED_ARG"], dis.EXTENDED_ARG) + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 29 not less than or equal to 20 def test_widths(self): long_opcodes = set(['JUMP_BACKWARD_NO_INTERRUPT', 'INSTRUMENTED_CALL_FUNCTION_EX']) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 7a6fdc87b26..f61b01a43ec 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -494,7 +494,7 @@ impl Compiler { } ExprContext::Store => { emit!(self, Instruction::BuildSlice { argc }); - emit!(self, Instruction::StoreSubscript); + emit!(self, Instruction::StoreSubscr); } _ => unreachable!(), } @@ -505,8 +505,8 @@ impl Compiler { // Emit appropriate instruction based on context match ctx { ExprContext::Load => emit!(self, Instruction::Subscript), - ExprContext::Store => emit!(self, Instruction::StoreSubscript), - ExprContext::Del => emit!(self, Instruction::DeleteSubscript), + ExprContext::Store => emit!(self, Instruction::StoreSubscr), + ExprContext::Del => emit!(self, Instruction::DeleteSubscr), ExprContext::Invalid => { return Err(self.error(CodegenErrorType::SyntaxError( "Invalid expression context".to_owned(), @@ -1013,7 +1013,7 @@ impl Compiler { // Stack when in FinallyEnd: [..., prev_exc, exc] or // [..., prev_exc, exc, return_value] if preserve_tos // Note: No lasti here - it's only pushed for cleanup handler exceptions - // We need to pop: exc, prev_exc (via PopException) + // We need to pop: exc, prev_exc (via PopExcept) if preserve_tos { emit!(self, Instruction::Swap { index: 2 }); } @@ -1021,7 +1021,7 @@ impl Compiler { if preserve_tos { emit!(self, Instruction::Swap { index: 2 }); } - emit!(self, Instruction::PopException); // prev_exc is restored + emit!(self, Instruction::PopExcept); // prev_exc is restored } FBlockType::With | FBlockType::AsyncWith => { @@ -1040,7 +1040,7 @@ impl Compiler { self.emit_load_const(ConstantData::None); self.emit_load_const(ConstantData::None); self.emit_load_const(ConstantData::None); - emit!(self, Instruction::CallFunctionPositional { nargs: 3 }); + emit!(self, Instruction::Call { nargs: 3 }); // For async with, await the result if matches!(info.fb_type, FBlockType::AsyncWith) { @@ -1057,7 +1057,7 @@ impl Compiler { if preserve_tos { emit!(self, Instruction::Swap { index: 2 }); } - emit!(self, Instruction::PopException); + emit!(self, Instruction::PopExcept); // If there's an exception name, clean it up if let FBlockDatum::ExceptionName(ref name) = info.fb_datum { @@ -1342,7 +1342,7 @@ impl Compiler { } if Self::find_ann(statements) { - emit!(self, Instruction::SetupAnnotation); + emit!(self, Instruction::SetupAnnotations); } self.compile_statements(statements)?; @@ -1362,7 +1362,7 @@ impl Compiler { self.symbol_table_stack.push(symbol_table); if Self::find_ann(body) { - emit!(self, Instruction::SetupAnnotation); + emit!(self, Instruction::SetupAnnotations); } if let Some((last, body)) = body.split_last() { @@ -1594,9 +1594,9 @@ impl Compiler { NameOp::Name => { let idx = self.get_global_name_index(&name); let op = match usage { - NameUsage::Load => Instruction::LoadNameAny, - NameUsage::Store => Instruction::StoreLocal, - NameUsage::Delete => Instruction::DeleteLocal, + NameUsage::Load => Instruction::LoadName, + NameUsage::Store => Instruction::StoreName, + NameUsage::Delete => Instruction::DeleteName, }; self.emit_arg(idx, op); } @@ -1815,7 +1815,7 @@ impl Compiler { None => bytecode::RaiseKind::BareRaise, }; self.set_source_range(*range); - emit!(self, Instruction::Raise { kind }); + emit!(self, Instruction::RaiseVarargs { kind }); } Stmt::Try(StmtTry { body, @@ -1878,15 +1878,15 @@ impl Compiler { match msg { Some(e) => { self.compile_expression(e)?; - emit!(self, Instruction::CallFunctionPositional { nargs: 1 }); + emit!(self, Instruction::Call { nargs: 1 }); } None => { - emit!(self, Instruction::CallFunctionPositional { nargs: 0 }); + emit!(self, Instruction::Call { nargs: 0 }); } } emit!( self, - Instruction::Raise { + Instruction::RaiseVarargs { kind: bytecode::RaiseKind::Raise, } ); @@ -2102,7 +2102,7 @@ impl Compiler { fn apply_decorators(&mut self, decorator_list: &[Decorator]) { // Apply decorators: for _ in decorator_list { - emit!(self, Instruction::CallFunctionPositional { nargs: 1 }); + emit!(self, Instruction::Call { nargs: 1 }); } } @@ -2144,7 +2144,7 @@ impl Compiler { self.make_closure(code, bytecode::MakeFunctionFlags::empty())?; // Call the function immediately - emit!(self, Instruction::CallFunctionPositional { nargs: 0 }); + emit!(self, Instruction::Call { nargs: 0 }); Ok(()) } @@ -2379,7 +2379,7 @@ impl Compiler { // which then properly restores prev_exc before going to outer handler emit!( self, - Instruction::Raise { + Instruction::RaiseVarargs { kind: bytecode::RaiseKind::ReraiseFromStack } ); @@ -2391,10 +2391,10 @@ impl Compiler { if let Some(cleanup) = finally_cleanup_block { self.switch_to_block(cleanup); emit!(self, Instruction::CopyItem { index: 3_u32 }); - emit!(self, Instruction::PopException); + emit!(self, Instruction::PopExcept); emit!( self, - Instruction::Raise { + Instruction::RaiseVarargs { kind: bytecode::RaiseKind::ReraiseFromStack } ); @@ -2439,7 +2439,7 @@ impl Compiler { )?; // Exception is on top of stack now, pushed by unwind_blocks - // PUSH_EXC_INFO transforms [exc] -> [prev_exc, exc] for PopException + // PUSH_EXC_INFO transforms [exc] -> [prev_exc, exc] for PopExcept emit!(self, Instruction::PushExcInfo); for handler in handlers { let ExceptHandler::ExceptHandler(ExceptHandlerExceptHandler { @@ -2524,7 +2524,7 @@ impl Compiler { // which does COPY 3; POP_EXCEPT; RERAISE emit!( self, - Instruction::Raise { + Instruction::RaiseVarargs { kind: bytecode::RaiseKind::ReraiseFromStack, } ); @@ -2537,7 +2537,7 @@ impl Compiler { // POP_BLOCK (HandlerCleanup) then POP_BLOCK (SETUP_CLEANUP) // followed by POP_EXCEPT self.pop_fblock(FBlockType::ExceptionHandler); - emit!(self, Instruction::PopException); + emit!(self, Instruction::PopExcept); // Delete the exception variable if it was bound (normal path) if let Some(alias) = name { @@ -2577,7 +2577,7 @@ impl Compiler { // NOTE: We emit RERAISE 0 BEFORE popping fblock so it is within cleanup handler scope emit!( self, - Instruction::Raise { + Instruction::RaiseVarargs { kind: bytecode::RaiseKind::ReraiseFromStack, } ); @@ -2593,10 +2593,10 @@ impl Compiler { // RERAISE 1: reraise with lasti self.switch_to_block(cleanup_block); emit!(self, Instruction::CopyItem { index: 3_u32 }); - emit!(self, Instruction::PopException); + emit!(self, Instruction::PopExcept); emit!( self, - Instruction::Raise { + Instruction::RaiseVarargs { kind: bytecode::RaiseKind::ReraiseFromStack, } ); @@ -2671,7 +2671,7 @@ impl Compiler { // Stack: [lasti, prev_exc, exc] - exception is on top emit!( self, - Instruction::Raise { + Instruction::RaiseVarargs { kind: bytecode::RaiseKind::ReraiseFromStack, } ); @@ -2689,11 +2689,11 @@ impl Compiler { // COPY 3: copy the exception from position 3 emit!(self, Instruction::CopyItem { index: 3_u32 }); // POP_EXCEPT: restore prev_exc as current exception - emit!(self, Instruction::PopException); + emit!(self, Instruction::PopExcept); // RERAISE 1: reraise with lasti from stack emit!( self, - Instruction::Raise { + Instruction::RaiseVarargs { kind: bytecode::RaiseKind::ReraiseFromStack, } ); @@ -2962,7 +2962,7 @@ impl Compiler { // POP_BLOCK - no-op for us with exception tables (fblocks handle this) // POP_EXCEPT - restore previous exception context - emit!(self, Instruction::PopException); + emit!(self, Instruction::PopExcept); // Stack: [] if !finalbody.is_empty() { @@ -2981,7 +2981,7 @@ impl Compiler { // Stack: [result, prev_exc] // POP_EXCEPT - emit!(self, Instruction::PopException); + emit!(self, Instruction::PopExcept); // Stack: [result] // RERAISE 0 @@ -3317,13 +3317,13 @@ impl Compiler { ); emit!( self, - Instruction::CallFunctionPositional { + Instruction::Call { nargs: num_typeparam_args as u32 } ); } else { // No arguments, just call the closure - emit!(self, Instruction::CallFunctionPositional { nargs: 0 }); + emit!(self, Instruction::Call { nargs: 0 }); } } @@ -3561,20 +3561,20 @@ impl Compiler { let dunder_name = self.name("__name__"); emit!(self, Instruction::LoadGlobal(dunder_name)); let dunder_module = self.name("__module__"); - emit!(self, Instruction::StoreLocal(dunder_module)); + emit!(self, Instruction::StoreName(dunder_module)); // Store __qualname__ self.emit_load_const(ConstantData::Str { value: qualname.into(), }); let qualname_name = self.name("__qualname__"); - emit!(self, Instruction::StoreLocal(qualname_name)); + emit!(self, Instruction::StoreName(qualname_name)); // Store __doc__ only if there's an explicit docstring if let Some(doc) = doc_str { self.emit_load_const(ConstantData::Str { value: doc.into() }); let doc_name = self.name("__doc__"); - emit!(self, Instruction::StoreLocal(doc_name)); + emit!(self, Instruction::StoreName(doc_name)); } // Store __firstlineno__ (new in Python 3.12+) @@ -3582,22 +3582,22 @@ impl Compiler { value: BigInt::from(firstlineno), }); let firstlineno_name = self.name("__firstlineno__"); - emit!(self, Instruction::StoreLocal(firstlineno_name)); + emit!(self, Instruction::StoreName(firstlineno_name)); // Set __type_params__ if we have type parameters if type_params.is_some() { // Load .type_params from enclosing scope let dot_type_params = self.name(".type_params"); - emit!(self, Instruction::LoadNameAny(dot_type_params)); + emit!(self, Instruction::LoadName(dot_type_params)); // Store as __type_params__ let dunder_type_params = self.name("__type_params__"); - emit!(self, Instruction::StoreLocal(dunder_type_params)); + emit!(self, Instruction::StoreName(dunder_type_params)); } // Setup annotations if needed if Self::find_ann(body) { - emit!(self, Instruction::SetupAnnotation); + emit!(self, Instruction::SetupAnnotations); } // 3. Compile the class body @@ -3617,7 +3617,7 @@ impl Compiler { emit!(self, Instruction::LoadClosure(classcell_idx.to_u32())); emit!(self, Instruction::CopyItem { index: 1_u32 }); let classcell = self.name("__classcell__"); - emit!(self, Instruction::StoreLocal(classcell)); + emit!(self, Instruction::StoreName(classcell)); } else { self.emit_load_const(ConstantData::None); } @@ -3659,7 +3659,7 @@ impl Compiler { // Compile type parameters and store as .type_params self.compile_type_params(type_params.unwrap())?; let dot_type_params = self.name(".type_params"); - emit!(self, Instruction::StoreLocal(dot_type_params)); + emit!(self, Instruction::StoreName(dot_type_params)); } // Step 2: Compile class body (always done, whether generic or not) @@ -3680,21 +3680,21 @@ impl Compiler { let dot_generic_base = self.name(".generic_base"); // Create .generic_base - emit!(self, Instruction::LoadNameAny(dot_type_params)); + emit!(self, Instruction::LoadName(dot_type_params)); emit!( self, Instruction::CallIntrinsic1 { func: bytecode::IntrinsicFunction1::SubscriptGeneric } ); - emit!(self, Instruction::StoreLocal(dot_generic_base)); + emit!(self, Instruction::StoreName(dot_generic_base)); // Generate class creation code emit!(self, Instruction::LoadBuildClass); // Set up the class function with type params let mut func_flags = bytecode::MakeFunctionFlags::empty(); - emit!(self, Instruction::LoadNameAny(dot_type_params)); + emit!(self, Instruction::LoadName(dot_type_params)); func_flags |= bytecode::MakeFunctionFlags::TYPE_PARAMS; // Create class function with closure @@ -3712,7 +3712,7 @@ impl Compiler { }; // Load .generic_base as the last base - emit!(self, Instruction::LoadNameAny(dot_generic_base)); + emit!(self, Instruction::LoadName(dot_generic_base)); let nargs = 2 + u32::try_from(base_count).expect("too many base classes") + 1; // function, name, bases..., generic_base @@ -3730,14 +3730,14 @@ impl Compiler { } emit!( self, - Instruction::CallFunctionKeyword { + Instruction::CallKw { nargs: nargs + u32::try_from(arguments.keywords.len()) .expect("too many keyword arguments") } ); } else { - emit!(self, Instruction::CallFunctionPositional { nargs }); + emit!(self, Instruction::Call { nargs }); } // Return the created class @@ -3748,7 +3748,7 @@ impl Compiler { // Execute the type params function self.make_closure(type_params_code, bytecode::MakeFunctionFlags::empty())?; - emit!(self, Instruction::CallFunctionPositional { nargs: 0 }); + emit!(self, Instruction::Call { nargs: 0 }); } else { // Non-generic class: standard path emit!(self, Instruction::LoadBuildClass); @@ -3915,7 +3915,7 @@ impl Compiler { self.emit_load_const(ConstantData::None); self.emit_load_const(ConstantData::None); self.emit_load_const(ConstantData::None); - emit!(self, Instruction::CallFunctionPositional { nargs: 3 }); + emit!(self, Instruction::Call { nargs: 3 }); if is_async { emit!(self, Instruction::GetAwaitable); self.emit_load_const(ConstantData::None); @@ -3991,7 +3991,7 @@ impl Compiler { // Need to pop: True, exc, prev_exc, __exit__ self.switch_to_block(suppress_block); emit!(self, Instruction::PopTop); // pop True (TO_BOOL result) - emit!(self, Instruction::PopException); // pop exc and restore prev_exc + emit!(self, Instruction::PopExcept); // pop exc and restore prev_exc emit!(self, Instruction::PopTop); // pop __exit__ emit!(self, Instruction::PopTop); // pop lasti emit!( @@ -4013,7 +4013,7 @@ impl Compiler { // If we cleared fblock, exceptions here would propagate uncaught. self.switch_to_block(cleanup_block); emit!(self, Instruction::CopyItem { index: 3 }); - emit!(self, Instruction::PopException); + emit!(self, Instruction::PopExcept); emit!(self, Instruction::Reraise { depth: 1 }); // ===== After block ===== @@ -4335,7 +4335,7 @@ impl Compiler { ); } // Use BINARY_OP/NB_SUBSCR to extract the element. - emit!(self, Instruction::BinarySubscript); + emit!(self, Instruction::BinarySubscr); // Compile the subpattern in irrefutable mode. self.compile_pattern_subpattern(pattern, pc)?; } @@ -4588,7 +4588,7 @@ impl Compiler { // Stack: [subject, len, size] emit!( self, - Instruction::CompareOperation { + Instruction::CompareOp { op: ComparisonOperator::GreaterOrEqual } ); @@ -4714,7 +4714,7 @@ impl Compiler { // Stack: [rest_dict, k1, ..., kn, rest_dict] emit!(self, Instruction::Swap { index: 2 }); // Stack: [rest_dict, k1, ..., kn-1, rest_dict, kn] - emit!(self, Instruction::DeleteSubscript); + emit!(self, Instruction::DeleteSubscr); // Stack: [rest_dict, k1, ..., kn-1] (removed kn from rest_dict) remaining -= 1; } @@ -4892,7 +4892,7 @@ impl Compiler { self.emit_load_const(ConstantData::Integer { value: size.into() }); emit!( self, - Instruction::CompareOperation { + Instruction::CompareOp { op: ComparisonOperator::Equal } ); @@ -4905,7 +4905,7 @@ impl Compiler { }); emit!( self, - Instruction::CompareOperation { + Instruction::CompareOp { op: ComparisonOperator::GreaterOrEqual } ); @@ -4934,7 +4934,7 @@ impl Compiler { self.compile_expression(&p.value)?; emit!( self, - Instruction::CompareOperation { + Instruction::CompareOp { op: bytecode::ComparisonOperator::Equal } ); @@ -5078,13 +5078,13 @@ impl Compiler { fn compile_addcompare(&mut self, op: &CmpOp) { use bytecode::ComparisonOperator::*; match op { - CmpOp::Eq => emit!(self, Instruction::CompareOperation { op: Equal }), - CmpOp::NotEq => emit!(self, Instruction::CompareOperation { op: NotEqual }), - CmpOp::Lt => emit!(self, Instruction::CompareOperation { op: Less }), - CmpOp::LtE => emit!(self, Instruction::CompareOperation { op: LessOrEqual }), - CmpOp::Gt => emit!(self, Instruction::CompareOperation { op: Greater }), + CmpOp::Eq => emit!(self, Instruction::CompareOp { op: Equal }), + CmpOp::NotEq => emit!(self, Instruction::CompareOp { op: NotEqual }), + CmpOp::Lt => emit!(self, Instruction::CompareOp { op: Less }), + CmpOp::LtE => emit!(self, Instruction::CompareOp { op: LessOrEqual }), + CmpOp::Gt => emit!(self, Instruction::CompareOp { op: Greater }), CmpOp::GtE => { - emit!(self, Instruction::CompareOperation { op: GreaterOrEqual }) + emit!(self, Instruction::CompareOp { op: GreaterOrEqual }) } CmpOp::In => emit!(self, Instruction::ContainsOp(Invert::No)), CmpOp::NotIn => emit!(self, Instruction::ContainsOp(Invert::Yes)), @@ -5219,11 +5219,11 @@ impl Compiler { if let Expr::Name(ExprName { id, .. }) = &target { // Store as dict entry in __annotations__ dict: let annotations = self.name("__annotations__"); - emit!(self, Instruction::LoadNameAny(annotations)); + emit!(self, Instruction::LoadName(annotations)); self.emit_load_const(ConstantData::Str { value: self.mangle(id.as_str()).into_owned().into(), }); - emit!(self, Instruction::StoreSubscript); + emit!(self, Instruction::StoreSubscr); } else { // Drop annotation if not assigned to simple identifier. emit!(self, Instruction::PopTop); @@ -5360,7 +5360,7 @@ impl Compiler { // stack: CONTAINER SLICE RESULT emit!(self, Instruction::Swap { index: 3 }); emit!(self, Instruction::Swap { index: 2 }); - emit!(self, Instruction::StoreSubscript); + emit!(self, Instruction::StoreSubscr); } AugAssignKind::Attr { idx } => { // stack: CONTAINER RESULT @@ -6108,9 +6108,9 @@ impl Compiler { fn compile_normal_call(&mut self, ty: CallType) { match ty { CallType::Positional { nargs } => { - emit!(self, Instruction::CallFunctionPositional { nargs }) + emit!(self, Instruction::Call { nargs }) } - CallType::Keyword { nargs } => emit!(self, Instruction::CallFunctionKeyword { nargs }), + CallType::Keyword { nargs } => emit!(self, Instruction::CallKw { nargs }), CallType::Ex { has_kwargs } => emit!(self, Instruction::CallFunctionEx { has_kwargs }), } } @@ -6422,7 +6422,7 @@ impl Compiler { }; // Call just created function: - emit!(self, Instruction::CallFunctionPositional { nargs: 1 }); + emit!(self, Instruction::Call { nargs: 1 }); if is_async_list_set_dict_comprehension { emit!(self, Instruction::GetAwaitable); self.emit_load_const(ConstantData::None); @@ -6622,7 +6622,7 @@ impl Compiler { // Re-raise the exception emit!( self, - Instruction::Raise { + Instruction::RaiseVarargs { kind: bytecode::RaiseKind::ReraiseFromStack } ); @@ -6841,7 +6841,7 @@ impl Compiler { self.emit_load_const(ConstantData::None); self.emit_load_const(ConstantData::None); self.emit_load_const(ConstantData::None); - emit!(self, Instruction::CallFunctionPositional { nargs: 3 }); + emit!(self, Instruction::Call { nargs: 3 }); if is_async { emit!(self, Instruction::GetAwaitable); @@ -6852,7 +6852,7 @@ impl Compiler { emit!(self, Instruction::PopTop); } UnwindAction::HandlerCleanup => { - emit!(self, Instruction::PopException); + emit!(self, Instruction::PopExcept); } UnwindAction::FinallyTry { body, fblock_idx } => { // compile finally body inline @@ -6871,9 +6871,9 @@ impl Compiler { UnwindAction::FinallyEnd => { // Stack when in FinallyEnd: [..., prev_exc, exc] // Note: No lasti here - it's only pushed for cleanup handler exceptions - // We need to pop: exc, prev_exc (via PopException) + // We need to pop: exc, prev_exc (via PopExcept) emit!(self, Instruction::PopTop); // exc - emit!(self, Instruction::PopException); // prev_exc is restored + emit!(self, Instruction::PopExcept); // prev_exc is restored } UnwindAction::PopValue => { // Pop the return value - continue/break cancels the pending return diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index dc61e3b9b5a..19380be4713 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -676,441 +676,287 @@ pub type NameIdx = u32; #[repr(u8)] pub enum Instruction { // ==================== No-argument instructions (opcode < 44) ==================== - // 0: CACHE - placeholder for inline cache (not executed) - Cache, - // 1: BEFORE_ASYNC_WITH - BeforeAsyncWith, - // 2: BEFORE_WITH - BeforeWith, - // 3: Reserved (BINARY_OP_INPLACE_ADD_UNICODE in CPython) - Reserved3, - // 4: BINARY_SLICE - not implemented, placeholder - BinarySlice, - // 5: BINARY_SUBSCR - BinarySubscript, - // 6: CHECK_EG_MATCH - CheckEgMatch, - // 7: CHECK_EXC_MATCH - CheckExcMatch, - // 8: CLEANUP_THROW - CleanupThrow, - // 9: DELETE_SUBSCR - DeleteSubscript, - // 10: END_ASYNC_FOR - EndAsyncFor, - // 11: END_FOR - not implemented, placeholder - EndFor, - // 12: END_SEND - EndSend, - // 13: EXIT_INIT_CHECK - not implemented, placeholder - ExitInitCheck, - // 14: FORMAT_SIMPLE - FormatSimple, - // 15: FORMAT_WITH_SPEC - FormatWithSpec, - // 16: GET_AITER - GetAIter, - // 17: RESERVED - Reserved17, - // 18: GET_ANEXT - GetANext, - // 19: GET_ITER - GetIter, - // 20: GET_LEN - GetLen, - // 21: GET_YIELD_FROM_ITER - not implemented, placeholder - GetYieldFromIter, - // 22: INTERPRETER_EXIT - not implemented, placeholder - InterpreterExit, - // 23: LOAD_ASSERTION_ERROR - not implemented, placeholder - LoadAssertionError, - // 24: LOAD_BUILD_CLASS - LoadBuildClass, - // 25: LOAD_LOCALS - not implemented, placeholder - LoadLocals, - // 26: MAKE_FUNCTION - MakeFunction, - // 27: MATCH_KEYS - MatchKeys, - // 28: MATCH_MAPPING - MatchMapping, - // 29: MATCH_SEQUENCE - MatchSequence, - // 30: NOP - Nop, - // 31: POP_EXCEPT - PopException, - // 32: POP_TOP - PopTop, - // 33: PUSH_EXC_INFO - PushExcInfo, - // 34: PUSH_NULL - not implemented, placeholder - PushNull, - // 35: RETURN_GENERATOR - not implemented, placeholder - ReturnGenerator, - // 36: RETURN_VALUE - ReturnValue, - // 37: SETUP_ANNOTATIONS - SetupAnnotation, - // 38: STORE_SLICE - not implemented, placeholder - StoreSlice, - // 39: STORE_SUBSCR - StoreSubscript, - // 40: TO_BOOL - ToBool, - // 41: UNARY_INVERT - UnaryInvert, - // 42: UNARY_NEGATIVE - UnaryNegative, - // 43: UNARY_NOT - UnaryNot, - // ==================== With-argument instructions (opcode >= 44) ==================== - // 44: WITH_EXCEPT_START - WithExceptStart, - // 45: BINARY_OP + Cache = 0, // Placeholder + BeforeAsyncWith = 1, + BeforeWith = 2, + BinaryOpInplaceAddUnicode = 3, // Placeholder + BinarySlice = 4, // Placeholder + BinarySubscr = 5, + CheckEgMatch = 6, + CheckExcMatch = 7, + CleanupThrow = 8, + DeleteSubscr = 9, + EndAsyncFor = 10, + EndFor = 11, // Placeholder + EndSend = 12, + ExitInitCheck = 13, // Placeholder + FormatSimple = 14, + FormatWithSpec = 15, + GetAIter = 16, + Reserved = 17, + GetANext = 18, + GetIter = 19, + GetLen = 20, + GetYieldFromIter = 21, + InterpreterExit = 22, // Placeholder + LoadAssertionError = 23, // Placeholder + LoadBuildClass = 24, + LoadLocals = 25, // Placeholder + MakeFunction = 26, + MatchKeys = 27, + MatchMapping = 28, + MatchSequence = 29, + Nop = 30, + PopExcept = 31, + PopTop = 32, + PushExcInfo = 33, + PushNull = 34, // Placeholder + ReturnGenerator = 35, // Placeholder + ReturnValue = 36, + SetupAnnotations = 37, + StoreSlice = 38, // Placeholder + StoreSubscr = 39, + ToBool = 40, + UnaryInvert = 41, + UnaryNegative = 42, + UnaryNot = 43, + WithExceptStart = 44, + // ==================== With-argument instructions (opcode > 44) ==================== BinaryOp { op: Arg, - }, - // 46: BUILD_CONST_KEY_MAP - not implemented, placeholder + } = 45, BuildConstKeyMap { size: Arg, - }, - // 47: BUILD_LIST + } = 46, // Placeholder BuildList { size: Arg, - }, - // 48: BUILD_MAP + } = 47, BuildMap { size: Arg, - }, - // 49: BUILD_SET + } = 48, BuildSet { size: Arg, - }, - // 50: BUILD_SLICE + } = 49, BuildSlice { argc: Arg, - }, - // 51: BUILD_STRING + } = 50, BuildString { size: Arg, - }, - // 52: BUILD_TUPLE + } = 51, BuildTuple { size: Arg, - }, - // 53: CALL - CallFunctionPositional { + } = 52, + Call { nargs: Arg, - }, - // 54: CALL_FUNCTION_EX + } = 53, CallFunctionEx { has_kwargs: Arg, - }, - // 55: CALL_INTRINSIC_1 + } = 54, CallIntrinsic1 { func: Arg, - }, - // 56: CALL_INTRINSIC_2 + } = 55, CallIntrinsic2 { func: Arg, - }, - // 57: CALL_KW - CallFunctionKeyword { + } = 56, + CallKw { nargs: Arg, - }, - // 58: COMPARE_OP - CompareOperation { + } = 57, + CompareOp { op: Arg, - }, - // 59: CONTAINS_OP - ContainsOp(Arg), - // 60: CONVERT_VALUE + } = 58, + ContainsOp(Arg) = 59, ConvertValue { oparg: Arg, - }, - // 61: COPY + } = 60, CopyItem { index: Arg, - }, - // 62: COPY_FREE_VARS - not implemented, placeholder + } = 61, CopyFreeVars { count: Arg, - }, - // 63: DELETE_ATTR + } = 62, // Placeholder DeleteAttr { idx: Arg, - }, - // 64: DELETE_DEREF - DeleteDeref(Arg), - // 65: DELETE_FAST - DeleteFast(Arg), - // 66: DELETE_GLOBAL - DeleteGlobal(Arg), - // 67: DELETE_NAME - DeleteLocal(Arg), - // 68: DICT_MERGE - not implemented, placeholder + } = 63, + DeleteDeref(Arg) = 64, + DeleteFast(Arg) = 65, + DeleteGlobal(Arg) = 66, + DeleteName(Arg) = 67, DictMerge { index: Arg, - }, - // 69: DICT_UPDATE + } = 68, // Placeholder DictUpdate { index: Arg, - }, - // 70: ENTER_EXECUTOR - not implemented, placeholder - EnterExecutor { - index: Arg, - }, - // 71: EXTENDED_ARG - ExtendedArg, - // 72: FOR_ITER + } = 69, + EnterExecutor = 70, // Placeholder + ExtendedArg = 71, ForIter { target: Arg

) def test_fma_zero_result(self): nonnegative_finites = [0.0, 1e-300, 2.3, 1e300] diff --git a/crates/stdlib/src/cmath.rs b/crates/stdlib/src/cmath.rs index 6ce471195cc..e7abb3bcad3 100644 --- a/crates/stdlib/src/cmath.rs +++ b/crates/stdlib/src/cmath.rs @@ -1,6 +1,5 @@ -// TODO: Keep track of rust-num/num-complex/issues/2. A common trait could help with duplication -// that exists between cmath and math. pub(crate) use cmath::make_module; + #[pymodule] mod cmath { use crate::vm::{ @@ -9,137 +8,141 @@ mod cmath { }; use num_complex::Complex64; + use crate::math::pymath_exception; + // Constants - #[pyattr] - use core::f64::consts::{E as e, PI as pi, TAU as tau}; + #[pyattr(name = "e")] + const E: f64 = pymath::cmath::E; + #[pyattr(name = "pi")] + const PI: f64 = pymath::cmath::PI; + #[pyattr(name = "tau")] + const TAU: f64 = pymath::cmath::TAU; #[pyattr(name = "inf")] - const INF: f64 = f64::INFINITY; + const INF: f64 = pymath::cmath::INF; #[pyattr(name = "nan")] - const NAN: f64 = f64::NAN; + const NAN: f64 = pymath::cmath::NAN; #[pyattr(name = "infj")] - const INFJ: Complex64 = Complex64::new(0., f64::INFINITY); + const INFJ: Complex64 = pymath::cmath::INFJ; #[pyattr(name = "nanj")] - const NANJ: Complex64 = Complex64::new(0., f64::NAN); + const NANJ: Complex64 = pymath::cmath::NANJ; #[pyfunction] - fn phase(z: ArgIntoComplex) -> f64 { - z.into_complex().arg() + fn phase(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::phase(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn polar(x: ArgIntoComplex) -> (f64, f64) { - x.into_complex().to_polar() + fn polar(x: ArgIntoComplex, vm: &VirtualMachine) -> PyResult<(f64, f64)> { + pymath::cmath::polar(x.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn rect(r: ArgIntoFloat, phi: ArgIntoFloat) -> Complex64 { - Complex64::from_polar(r.into_float(), phi.into_float()) + fn rect(r: ArgIntoFloat, phi: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { + pymath::cmath::rect(r.into_float(), phi.into_float()) + .map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn isinf(z: ArgIntoComplex) -> bool { - let Complex64 { re, im } = z.into_complex(); - re.is_infinite() || im.is_infinite() + pymath::cmath::isinf(z.into_complex()) } #[pyfunction] fn isfinite(z: ArgIntoComplex) -> bool { - z.into_complex().is_finite() + pymath::cmath::isfinite(z.into_complex()) } #[pyfunction] fn isnan(z: ArgIntoComplex) -> bool { - z.into_complex().is_nan() + pymath::cmath::isnan(z.into_complex()) } #[pyfunction] fn exp(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { - let z = z.into_complex(); - result_or_overflow(z, z.exp(), vm) + pymath::cmath::exp(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn sqrt(z: ArgIntoComplex) -> Complex64 { - z.into_complex().sqrt() + fn sqrt(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::sqrt(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn sin(z: ArgIntoComplex) -> Complex64 { - z.into_complex().sin() + fn sin(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::sin(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn asin(z: ArgIntoComplex) -> Complex64 { - z.into_complex().asin() + fn asin(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::asin(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn cos(z: ArgIntoComplex) -> Complex64 { - z.into_complex().cos() + fn cos(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::cos(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn acos(z: ArgIntoComplex) -> Complex64 { - z.into_complex().acos() + fn acos(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::acos(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn log(z: ArgIntoComplex, base: OptionalArg) -> Complex64 { - // TODO: Complex64.log with a negative base yields wrong results. - // Issue is with num_complex::Complex64 implementation of log - // which returns NaN when base is negative. - // log10(z) / log10(base) yields correct results but division - // doesn't handle pos/neg zero nicely. (i.e log(1, 0.5)) - z.into_complex().log( - base.into_option() - .map(|base| base.into_complex().re) - .unwrap_or(core::f64::consts::E), + fn log( + z: ArgIntoComplex, + base: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + pymath::cmath::log( + z.into_complex(), + base.into_option().map(|b| b.into_complex()), ) + .map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn log10(z: ArgIntoComplex) -> Complex64 { - z.into_complex().log(10.0) + fn log10(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::log10(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn acosh(z: ArgIntoComplex) -> Complex64 { - z.into_complex().acosh() + fn acosh(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::acosh(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn atan(z: ArgIntoComplex) -> Complex64 { - z.into_complex().atan() + fn atan(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::atan(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn atanh(z: ArgIntoComplex) -> Complex64 { - z.into_complex().atanh() + fn atanh(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::atanh(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn tan(z: ArgIntoComplex) -> Complex64 { - z.into_complex().tan() + fn tan(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::tan(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn tanh(z: ArgIntoComplex) -> Complex64 { - z.into_complex().tanh() + fn tanh(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::tanh(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn sinh(z: ArgIntoComplex) -> Complex64 { - z.into_complex().sinh() + fn sinh(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::sinh(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn cosh(z: ArgIntoComplex) -> Complex64 { - z.into_complex().cosh() + fn cosh(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::cosh(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn asinh(z: ArgIntoComplex) -> Complex64 { - z.into_complex().asinh() + fn asinh(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult { + pymath::cmath::asinh(z.into_complex()).map_err(|err| pymath_exception(err, vm)) } #[derive(FromArgs)] @@ -158,52 +161,10 @@ mod cmath { fn isclose(args: IsCloseArgs, vm: &VirtualMachine) -> PyResult { let a = args.a.into_complex(); let b = args.b.into_complex(); - let rel_tol = args.rel_tol.map_or(1e-09, |v| v.into_float()); - let abs_tol = args.abs_tol.map_or(0.0, |v| v.into_float()); - - if rel_tol < 0.0 || abs_tol < 0.0 { - return Err(vm.new_value_error("tolerances must be non-negative")); - } - - if a == b { - /* short circuit exact equality -- needed to catch two infinities of - the same sign. And perhaps speeds things up a bit sometimes. - */ - return Ok(true); - } - - /* This catches the case of two infinities of opposite sign, or - one infinity and one finite number. Two infinities of opposite - sign would otherwise have an infinite relative tolerance. - Two infinities of the same sign are caught by the equality check - above. - */ - if a.is_infinite() || b.is_infinite() { - return Ok(false); - } + let rel_tol = args.rel_tol.into_option().map(|v| v.into_float()); + let abs_tol = args.abs_tol.into_option().map(|v| v.into_float()); - let diff = c_abs(b - a); - - Ok(diff <= (rel_tol * c_abs(b)) || (diff <= (rel_tol * c_abs(a))) || diff <= abs_tol) - } - - #[inline] - fn c_abs(Complex64 { re, im }: Complex64) -> f64 { - re.hypot(im) - } - - #[inline] - fn result_or_overflow( - value: Complex64, - result: Complex64, - vm: &VirtualMachine, - ) -> PyResult { - if !result.is_finite() && value.is_finite() { - // CPython doesn't return `inf` when called with finite - // values, it raises OverflowError instead. - Err(vm.new_overflow_error("math range error")) - } else { - Ok(result) - } + pymath::cmath::isclose(a, b, rel_tol, abs_tol) + .map_err(|_| vm.new_value_error("tolerances must be non-negative")) } } diff --git a/crates/stdlib/src/math.rs b/crates/stdlib/src/math.rs index fb8945c74f7..1e014e49e24 100644 --- a/crates/stdlib/src/math.rs +++ b/crates/stdlib/src/math.rs @@ -1,70 +1,48 @@ pub(crate) use math::make_module; -use crate::{builtins::PyBaseExceptionRef, vm::VirtualMachine}; +use crate::vm::{VirtualMachine, builtins::PyBaseExceptionRef}; #[pymodule] mod math { use crate::vm::{ - PyObject, PyObjectRef, PyRef, PyResult, VirtualMachine, + AsObject, PyObject, PyObjectRef, PyRef, PyResult, VirtualMachine, builtins::{PyFloat, PyInt, PyIntRef, PyStrInterned, try_bigint_to_f64, try_f64_to_bigint}, function::{ArgIndex, ArgIntoFloat, ArgIterable, Either, OptionalArg, PosArgs}, identifier, }; - use core::cmp::Ordering; - use itertools::Itertools; use malachite_bigint::BigInt; - use num_traits::{One, Signed, ToPrimitive, Zero}; - use rustpython_common::{float_ops, int::true_div}; + use num_traits::{Signed, ToPrimitive}; + + use super::{float_repr, pymath_exception}; // Constants #[pyattr] use core::f64::consts::{E as e, PI as pi, TAU as tau}; - use super::pymath_error_to_exception; #[pyattr(name = "inf")] const INF: f64 = f64::INFINITY; #[pyattr(name = "nan")] const NAN: f64 = f64::NAN; - // Helper macro: - macro_rules! call_math_func { - ( $fun:ident, $name:ident, $vm:ident ) => {{ - let value = $name.into_float(); - let result = value.$fun(); - result_or_overflow(value, result, $vm) - }}; - } - - #[inline] - fn result_or_overflow(value: f64, result: f64, vm: &VirtualMachine) -> PyResult { - if !result.is_finite() && value.is_finite() { - // CPython doesn't return `inf` when called with finite - // values, it raises OverflowError instead. - Err(vm.new_overflow_error("math range error")) - } else { - Ok(result) - } - } - // Number theory functions: #[pyfunction] fn fabs(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - call_math_func!(abs, x, vm) + pymath::math::fabs(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn isfinite(x: ArgIntoFloat) -> bool { - x.into_float().is_finite() + pymath::math::isfinite(x.into_float()) } #[pyfunction] fn isinf(x: ArgIntoFloat) -> bool { - x.into_float().is_infinite() + pymath::math::isinf(x.into_float()) } #[pyfunction] fn isnan(x: ArgIntoFloat) -> bool { - x.into_float().is_nan() + pymath::math::isnan(x.into_float()) } #[derive(FromArgs)] @@ -83,420 +61,286 @@ mod math { fn isclose(args: IsCloseArgs, vm: &VirtualMachine) -> PyResult { let a = args.a.into_float(); let b = args.b.into_float(); - let rel_tol = args.rel_tol.map_or(1e-09, |v| v.into_float()); - let abs_tol = args.abs_tol.map_or(0.0, |v| v.into_float()); - - if rel_tol < 0.0 || abs_tol < 0.0 { - return Err(vm.new_value_error("tolerances must be non-negative")); - } - - if a == b { - /* short circuit exact equality -- needed to catch two infinities of - the same sign. And perhaps speeds things up a bit sometimes. - */ - return Ok(true); - } + let rel_tol = args.rel_tol.into_option().map(|v| v.into_float()); + let abs_tol = args.abs_tol.into_option().map(|v| v.into_float()); - /* This catches the case of two infinities of opposite sign, or - one infinity and one finite number. Two infinities of opposite - sign would otherwise have an infinite relative tolerance. - Two infinities of the same sign are caught by the equality check - above. - */ - - if a.is_infinite() || b.is_infinite() { - return Ok(false); - } - - let diff = (b - a).abs(); - - Ok((diff <= (rel_tol * b).abs()) || (diff <= (rel_tol * a).abs()) || (diff <= abs_tol)) + pymath::math::isclose(a, b, rel_tol, abs_tol) + .map_err(|_| vm.new_value_error("tolerances must be non-negative")) } #[pyfunction] - fn copysign(x: ArgIntoFloat, y: ArgIntoFloat) -> f64 { - x.into_float().copysign(y.into_float()) + fn copysign(x: ArgIntoFloat, y: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { + pymath::math::copysign(x.into_float(), y.into_float()) + .map_err(|err| pymath_exception(err, vm)) } // Power and logarithmic functions: #[pyfunction] fn exp(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - call_math_func!(exp, x, vm) + pymath::math::exp(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn exp2(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - call_math_func!(exp2, x, vm) + pymath::math::exp2(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn expm1(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - call_math_func!(exp_m1, x, vm) + pymath::math::expm1(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn log(x: PyObjectRef, base: OptionalArg, vm: &VirtualMachine) -> PyResult { - let base: f64 = base.map(Into::into).unwrap_or(core::f64::consts::E); - if base.is_sign_negative() { - return Err(vm.new_value_error("math domain error")); + let base = base.into_option().map(|v| v.into_float()); + // Check base first for proper error messages + if let Some(b) = base { + if b <= 0.0 { + return Err(vm.new_value_error(format!( + "expected a positive input, got {}", + super::float_repr(b) + ))); + } + if b == 1.0 { + return Err(vm.new_value_error("math domain error".to_owned())); + } + } + // Handle BigInt specially for large values (only for actual int type, not float) + if let Some(i) = x.downcast_ref::() { + return pymath::math::log_bigint(i.as_bigint(), base).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error("expected a positive input".to_owned()), + _ => pymath_exception(err, vm), + }); } - log2(x, vm).map(|log_x| log_x / base.log2()) + let val = x.try_float(vm)?.to_f64(); + pymath::math::log(val, base).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error(format!( + "expected a positive input, got {}", + super::float_repr(val) + )), + _ => pymath_exception(err, vm), + }) } #[pyfunction] fn log1p(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let x = x.into_float(); - if x.is_nan() || x > -1.0_f64 { - Ok(x.ln_1p()) - } else { - Err(vm.new_value_error("math domain error")) - } - } - - /// Generates the base-2 logarithm of a BigInt `x` - fn int_log2(x: &BigInt) -> f64 { - // log2(x) = log2(2^n * 2^-n * x) = n + log2(x/2^n) - // If we set 2^n to be the greatest power of 2 below x, then x/2^n is in [1, 2), and can - // thus be converted into a float. - let n = x.bits() as u32 - 1; - let frac = true_div(x, &BigInt::from(2).pow(n)); - f64::from(n) + frac.log2() + pymath::math::log1p(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn log2(x: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match x.try_float(vm) { - Ok(x) => { - let x = x.to_f64(); - if x.is_nan() || x > 0.0_f64 { - Ok(x.log2()) - } else { - Err(vm.new_value_error("math domain error")) - } - } - Err(float_err) => { - if let Ok(x) = x.try_int(vm) { - let x = x.as_bigint(); - if x.is_positive() { - Ok(int_log2(x)) - } else { - Err(vm.new_value_error("math domain error")) - } - } else { - // Return the float error, as it will be more intuitive to users - Err(float_err) - } - } + // Handle BigInt specially for large values (only for actual int type, not float) + if let Some(i) = x.downcast_ref::() { + return pymath::math::log2_bigint(i.as_bigint()).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error("expected a positive input".to_owned()), + _ => pymath_exception(err, vm), + }); } + let val = x.try_float(vm)?.to_f64(); + pymath::math::log2(val).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error(format!( + "expected a positive input, got {}", + super::float_repr(val) + )), + _ => pymath_exception(err, vm), + }) } #[pyfunction] fn log10(x: PyObjectRef, vm: &VirtualMachine) -> PyResult { - log2(x, vm).map(|log_x| log_x / 10f64.log2()) - } - - #[pyfunction] - fn pow(x: ArgIntoFloat, y: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let x = x.into_float(); - let y = y.into_float(); - - if x < 0.0 && x.is_finite() && y.fract() != 0.0 && y.is_finite() - || x == 0.0 && y < 0.0 && y != f64::NEG_INFINITY - { - return Err(vm.new_value_error("math domain error")); - } - - let value = x.powf(y); - - if x.is_finite() && y.is_finite() && value.is_infinite() { - return Err(vm.new_overflow_error("math range error")); + // Handle BigInt specially for large values (only for actual int type, not float) + if let Some(i) = x.downcast_ref::() { + return pymath::math::log10_bigint(i.as_bigint()).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error("expected a positive input".to_owned()), + _ => pymath_exception(err, vm), + }); } - - Ok(value) + let val = x.try_float(vm)?.to_f64(); + pymath::math::log10(val).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error(format!( + "expected a positive input, got {}", + super::float_repr(val) + )), + _ => pymath_exception(err, vm), + }) } #[pyfunction] - fn sqrt(value: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let value = value.into_float(); - if value.is_nan() { - return Ok(value); - } - if value.is_sign_negative() { - if value.is_zero() { - return Ok(-0.0f64); - } - return Err(vm.new_value_error("math domain error")); - } - Ok(value.sqrt()) + fn pow(x: ArgIntoFloat, y: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { + pymath::math::pow(x.into_float(), y.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn isqrt(x: ArgIndex, vm: &VirtualMachine) -> PyResult { - let x = x.into_int_ref(); - let value = x.as_bigint(); - - if value.is_negative() { - return Err(vm.new_value_error("isqrt() argument must be nonnegative")); - } - Ok(value.sqrt()) + fn sqrt(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { + let val = x.into_float(); + pymath::math::sqrt(val).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error(format!( + "expected a nonnegative input, got {}", + super::float_repr(val) + )), + _ => pymath_exception(err, vm), + }) } // Trigonometric functions: #[pyfunction] fn acos(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let x = x.into_float(); - if x.is_nan() || (-1.0_f64..=1.0_f64).contains(&x) { - Ok(x.acos()) - } else { - Err(vm.new_value_error("math domain error")) - } + let val = x.into_float(); + pymath::math::acos(val).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error(format!( + "expected a number in range from -1 up to 1, got {}", + float_repr(val) + )), + _ => pymath_exception(err, vm), + }) } #[pyfunction] fn asin(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let x = x.into_float(); - if x.is_nan() || (-1.0_f64..=1.0_f64).contains(&x) { - Ok(x.asin()) - } else { - Err(vm.new_value_error("math domain error")) - } + let val = x.into_float(); + pymath::math::asin(val).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error(format!( + "expected a number in range from -1 up to 1, got {}", + float_repr(val) + )), + _ => pymath_exception(err, vm), + }) } #[pyfunction] fn atan(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - call_math_func!(atan, x, vm) + pymath::math::atan(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn atan2(y: ArgIntoFloat, x: ArgIntoFloat) -> f64 { - y.into_float().atan2(x.into()) + fn atan2(y: ArgIntoFloat, x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { + pymath::math::atan2(y.into_float(), x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn cos(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let x = x.into_float(); - if x.is_infinite() { - return Err(vm.new_value_error("math domain error")); - } - result_or_overflow(x, x.cos(), vm) + let val = x.into_float(); + pymath::math::cos(val).map_err(|err| match err { + pymath::Error::EDOM => { + vm.new_value_error(format!("expected a finite input, got {}", float_repr(val))) + } + _ => pymath_exception(err, vm), + }) } #[pyfunction] fn hypot(coordinates: PosArgs) -> f64 { - let mut coordinates = ArgIntoFloat::vec_into_f64(coordinates.into_vec()); - let mut max = 0.0; - let mut has_nan = false; - for f in &mut coordinates { - *f = f.abs(); - if f.is_nan() { - has_nan = true; - } else if *f > max { - max = *f - } - } - // inf takes precedence over nan - if max.is_infinite() { - return max; - } - if has_nan { - return f64::NAN; - } - coordinates.sort_unstable_by(|x, y| x.total_cmp(y).reverse()); - vector_norm(&coordinates) - } - - /// Implementation of accurate hypotenuse algorithm from Borges 2019. - /// See https://arxiv.org/abs/1904.09481. - /// This assumes that its arguments are positive finite and have been scaled to avoid overflow - /// and underflow. - fn accurate_hypot(max: f64, min: f64) -> f64 { - if min <= max * (f64::EPSILON / 2.0).sqrt() { - return max; - } - let hypot = max.mul_add(max, min * min).sqrt(); - let hypot_sq = hypot * hypot; - let max_sq = max * max; - let correction = (-min).mul_add(min, hypot_sq - max_sq) + hypot.mul_add(hypot, -hypot_sq) - - max.mul_add(max, -max_sq); - hypot - correction / (2.0 * hypot) - } - - /// Calculates the norm of the vector given by `v`. - /// `v` is assumed to be a list of non-negative finite floats, sorted in descending order. - fn vector_norm(v: &[f64]) -> f64 { - // Drop zeros from the vector. - let zero_count = v.iter().rev().cloned().take_while(|x| *x == 0.0).count(); - let v = &v[..v.len() - zero_count]; - if v.is_empty() { - return 0.0; - } - if v.len() == 1 { - return v[0]; - } - // Calculate scaling to avoid overflow / underflow. - let max = *v.first().unwrap(); - let min = *v.last().unwrap(); - let scale = if max > (f64::MAX / v.len() as f64).sqrt() { - max - } else if min < f64::MIN_POSITIVE.sqrt() { - // ^ This can be an `else if`, because if the max is near f64::MAX and the min is near - // f64::MIN_POSITIVE, then the min is relatively unimportant and will be effectively - // ignored. - min - } else { - 1.0 - }; - let mut norm = v - .iter() - .copied() - .map(|x| x / scale) - .reduce(accurate_hypot) - .unwrap_or_default(); - if v.len() > 2 { - // For larger lists of numbers, we can accumulate a rounding error, so a correction is - // needed, similar to that in `accurate_hypot()`. - // First, we estimate [sum of squares - norm^2], then we add the first-order - // approximation of the square root of that to `norm`. - let correction = v - .iter() - .copied() - .map(|x| (x / scale).powi(2)) - .chain(core::iter::once(-norm * norm)) - // Pairwise summation of floats gives less rounding error than a naive sum. - .tree_reduce(core::ops::Add::add) - .expect("expected at least 1 element"); - norm = norm + correction / (2.0 * norm); - } - norm * scale + let coords = ArgIntoFloat::vec_into_f64(coordinates.into_vec()); + pymath::math::hypot(&coords) } #[pyfunction] fn dist(p: Vec, q: Vec, vm: &VirtualMachine) -> PyResult { - let mut max = 0.0; - let mut has_nan = false; - let p = ArgIntoFloat::vec_into_f64(p); let q = ArgIntoFloat::vec_into_f64(q); - let mut diffs = vec![]; - if p.len() != q.len() { return Err(vm.new_value_error("both points must have the same number of dimensions")); } - - for i in 0..p.len() { - let px = p[i]; - let qx = q[i]; - - let x = (px - qx).abs(); - if x.is_nan() { - has_nan = true; - } - - diffs.push(x); - if x > max { - max = x; - } - } - - if max.is_infinite() { - return Ok(max); - } - if has_nan { - return Ok(f64::NAN); - } - diffs.sort_unstable_by(|x, y| x.total_cmp(y).reverse()); - Ok(vector_norm(&diffs)) + Ok(pymath::math::dist(&p, &q)) } #[pyfunction] fn sin(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let x = x.into_float(); - if x.is_infinite() { - return Err(vm.new_value_error("math domain error")); - } - result_or_overflow(x, x.sin(), vm) + let val = x.into_float(); + pymath::math::sin(val).map_err(|err| match err { + pymath::Error::EDOM => { + vm.new_value_error(format!("expected a finite input, got {}", float_repr(val))) + } + _ => pymath_exception(err, vm), + }) } #[pyfunction] fn tan(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let x = x.into_float(); - if x.is_infinite() { - return Err(vm.new_value_error("math domain error")); - } - result_or_overflow(x, x.tan(), vm) + let val = x.into_float(); + pymath::math::tan(val).map_err(|err| match err { + pymath::Error::EDOM => { + vm.new_value_error(format!("expected a finite input, got {}", float_repr(val))) + } + _ => pymath_exception(err, vm), + }) } #[pyfunction] fn degrees(x: ArgIntoFloat) -> f64 { - x.into_float() * (180.0 / core::f64::consts::PI) + pymath::math::degrees(x.into_float()) } #[pyfunction] fn radians(x: ArgIntoFloat) -> f64 { - x.into_float() * (core::f64::consts::PI / 180.0) + pymath::math::radians(x.into_float()) } // Hyperbolic functions: #[pyfunction] fn acosh(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let x = x.into_float(); - if x.is_sign_negative() || x.is_zero() { - Err(vm.new_value_error("math domain error")) - } else { - Ok(x.acosh()) - } + pymath::math::acosh(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn asinh(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - call_math_func!(asinh, x, vm) + pymath::math::asinh(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn atanh(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let x = x.into_float(); - if x >= 1.0_f64 || x <= -1.0_f64 { - Err(vm.new_value_error("math domain error")) - } else { - Ok(x.atanh()) - } + let val = x.into_float(); + pymath::math::atanh(val).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error(format!( + "expected a number between -1 and 1, got {}", + super::float_repr(val) + )), + _ => pymath_exception(err, vm), + }) } #[pyfunction] fn cosh(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - call_math_func!(cosh, x, vm) + pymath::math::cosh(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn sinh(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - call_math_func!(sinh, x, vm) + pymath::math::sinh(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn tanh(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - call_math_func!(tanh, x, vm) + pymath::math::tanh(x.into_float()).map_err(|err| pymath_exception(err, vm)) } // Special functions: #[pyfunction] - fn erf(x: ArgIntoFloat) -> f64 { - pymath::erf(x.into()) + fn erf(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { + pymath::math::erf(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn erfc(x: ArgIntoFloat) -> f64 { - pymath::erfc(x.into()) + fn erfc(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { + pymath::math::erfc(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn gamma(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - pymath::gamma(x.into()).map_err(|err| pymath_error_to_exception(err, vm)) + let val = x.into_float(); + pymath::math::gamma(val).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error(format!( + "expected a noninteger or positive integer, got {}", + super::float_repr(val) + )), + _ => pymath_exception(err, vm), + }) } #[pyfunction] fn lgamma(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - pymath::lgamma(x.into()).map_err(|err| pymath_error_to_exception(err, vm)) + pymath::math::lgamma(x.into_float()).map_err(|err| pymath_exception(err, vm)) } fn try_magic_method( @@ -521,37 +365,43 @@ mod math { #[pyfunction] fn ceil(x: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let result_or_err = try_magic_method(identifier!(vm, __ceil__), vm, &x); - if result_or_err.is_err() - && let Some(v) = x.try_float_opt(vm) - { + // Only call __ceil__ if the class defines it - if it exists but is not callable, + // the error should be propagated (not fall back to float conversion) + if x.class().has_attr(identifier!(vm, __ceil__)) { + return try_magic_method(identifier!(vm, __ceil__), vm, &x); + } + // __ceil__ not defined - fall back to float conversion + if let Some(v) = x.try_float_opt(vm) { let v = try_f64_to_bigint(v?.to_f64().ceil(), vm)?; return Ok(vm.ctx.new_int(v).into()); } - result_or_err + Err(vm.new_type_error(format!( + "type '{}' doesn't define '__ceil__' method", + x.class().name(), + ))) } #[pyfunction] fn floor(x: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let result_or_err = try_magic_method(identifier!(vm, __floor__), vm, &x); - if result_or_err.is_err() - && let Some(v) = x.try_float_opt(vm) - { + // Only call __floor__ if the class defines it - if it exists but is not callable, + // the error should be propagated (not fall back to float conversion) + if x.class().has_attr(identifier!(vm, __floor__)) { + return try_magic_method(identifier!(vm, __floor__), vm, &x); + } + // __floor__ not defined - fall back to float conversion + if let Some(v) = x.try_float_opt(vm) { let v = try_f64_to_bigint(v?.to_f64().floor(), vm)?; return Ok(vm.ctx.new_int(v).into()); } - result_or_err + Err(vm.new_type_error(format!( + "type '{}' doesn't define '__floor__' method", + x.class().name(), + ))) } #[pyfunction] fn frexp(x: ArgIntoFloat) -> (f64, i32) { - let value: f64 = x.into(); - if value.is_finite() { - let (m, exp) = float_ops::decompose_float(value); - (m * value.signum(), exp) - } else { - (value, 0) - } + pymath::math::frexp(x.into_float()) } #[pyfunction] @@ -564,315 +414,24 @@ mod math { Either::A(f) => f.to_f64(), Either::B(z) => try_bigint_to_f64(z.as_bigint(), vm)?, }; - - if value == 0_f64 || !value.is_finite() { - // NaNs, zeros and infinities are returned unchanged - return Ok(value); - } - - // Using IEEE 754 bit manipulation to handle large exponents correctly. - // Direct multiplication would overflow for large i values, especially when computing - // the largest finite float (i=1024, x<1.0). By directly modifying the exponent bits, - // we avoid intermediate overflow to infinity. - - // Scale subnormals to normal range first, then adjust exponent. - let (mant, exp0) = if value.abs() < f64::MIN_POSITIVE { - let scaled = value * (1u64 << 54) as f64; // multiply by 2^54 - let (mant_scaled, exp_scaled) = float_ops::decompose_float(scaled); - (mant_scaled, exp_scaled - 54) // adjust exponent back - } else { - float_ops::decompose_float(value) - }; - - let i_big = i.as_bigint(); - let overflow_bound = BigInt::from(1024_i32 - exp0); // i > 1024 - exp0 => overflow - if i_big > &overflow_bound { - return Err(vm.new_overflow_error("math range error")); - } - if i_big == &overflow_bound && mant == 1.0 { - return Err(vm.new_overflow_error("math range error")); - } - let underflow_bound = BigInt::from(-1074_i32 - exp0); // i < -1074 - exp0 => 0.0 with sign - if i_big < &underflow_bound { - return Ok(0.0f64.copysign(value)); - } - - let i_small: i32 = i_big - .to_i32() - .expect("exponent within [-1074-exp0, 1024-exp0] must fit in i32"); - let exp = exp0 + i_small; - - const SIGN_MASK: u64 = 0x8000_0000_0000_0000; - const FRAC_MASK: u64 = 0x000F_FFFF_FFFF_FFFF; - let sign_bit: u64 = if value.is_sign_negative() { - SIGN_MASK - } else { - 0 - }; - let mant_bits = mant.to_bits() & FRAC_MASK; - if exp >= -1021 { - let e_bits = (1022_i32 + exp) as u64; - let result_bits = sign_bit | (e_bits << 52) | mant_bits; - return Ok(f64::from_bits(result_bits)); - } - - let full_mant: u64 = (1u64 << 52) | mant_bits; - let shift: u32 = (-exp - 1021) as u32; - let frac_shifted = full_mant >> shift; - let lost_bits = full_mant & ((1u64 << shift) - 1); - - let half = 1u64 << (shift - 1); - let frac = if (lost_bits > half) || (lost_bits == half && (frac_shifted & 1) == 1) { - frac_shifted + 1 - } else { - frac_shifted - }; - - let result_bits = if frac >= (1u64 << 52) { - sign_bit | (1u64 << 52) - } else { - sign_bit | frac - }; - Ok(f64::from_bits(result_bits)) - } - - fn math_perf_arb_len_int_op(args: PosArgs, op: F, default: BigInt) -> BigInt - where - F: Fn(&BigInt, &PyInt) -> BigInt, - { - let arg_vec = args.into_vec(); - - if arg_vec.is_empty() { - return default; - } else if arg_vec.len() == 1 { - return op(arg_vec[0].as_ref().as_bigint(), arg_vec[0].as_ref()); - } - - let mut res = arg_vec[0].as_ref().as_bigint().clone(); - for num in &arg_vec[1..] { - res = op(&res, num.as_ref()) - } - res + pymath::math::ldexp_bigint(value, i.as_bigint()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] - fn gcd(args: PosArgs) -> BigInt { - use num_integer::Integer; - math_perf_arb_len_int_op(args, |x, y| x.gcd(y.as_bigint()), BigInt::zero()) - } - - #[pyfunction] - fn lcm(args: PosArgs) -> BigInt { - use num_integer::Integer; - math_perf_arb_len_int_op(args, |x, y| x.lcm(y.as_bigint()), BigInt::one()) - } - - #[pyfunction] - fn cbrt(x: ArgIntoFloat) -> f64 { - x.into_float().cbrt() + fn cbrt(x: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { + pymath::math::cbrt(x.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn fsum(seq: ArgIterable, vm: &VirtualMachine) -> PyResult { - let mut partials = Vec::with_capacity(32); - let mut special_sum = 0.0; - let mut inf_sum = 0.0; - - for obj in seq.iter(vm)? { - let mut x = obj?.into_float(); - - let xsave = x; - let mut i = 0; - // This inner loop applies `hi`/`lo` summation to each - // partial so that the list of partial sums remains exact. - for j in 0..partials.len() { - let mut y: f64 = partials[j]; - if x.abs() < y.abs() { - core::mem::swap(&mut x, &mut y); - } - // Rounded `x+y` is stored in `hi` with round-off stored in - // `lo`. Together `hi+lo` are exactly equal to `x+y`. - let hi = x + y; - let lo = y - (hi - x); - if lo != 0.0 { - partials[i] = lo; - i += 1; - } - x = hi; - } - - partials.truncate(i); - if x != 0.0 { - if !x.is_finite() { - // a non-finite x could arise either as - // a result of intermediate overflow, or - // as a result of a nan or inf in the - // summands - if xsave.is_finite() { - return Err(vm.new_overflow_error("intermediate overflow in fsum")); - } - if xsave.is_infinite() { - inf_sum += xsave; - } - special_sum += xsave; - // reset partials - partials.clear(); - } else { - partials.push(x); - } - } - } - if special_sum != 0.0 { - return if inf_sum.is_nan() { - Err(vm.new_value_error("-inf + inf in fsum")) - } else { - Ok(special_sum) - }; - } - - let mut n = partials.len(); - if n > 0 { - n -= 1; - let mut hi = partials[n]; - - let mut lo = 0.0; - while n > 0 { - let x = hi; - - n -= 1; - let y = partials[n]; - - hi = x + y; - lo = y - (hi - x); - if lo != 0.0 { - break; - } - } - if n > 0 && ((lo < 0.0 && partials[n - 1] < 0.0) || (lo > 0.0 && partials[n - 1] > 0.0)) - { - let y = lo + lo; - let x = hi + y; - - // Make half-even rounding work across multiple partials. - // Needed so that sum([1e-16, 1, 1e16]) will round-up the last - // digit to two instead of down to zero (the 1e-16 makes the 1 - // slightly closer to two). With a potential 1 ULP rounding - // error fixed-up, math.fsum() can guarantee commutativity. - if y == x - hi { - hi = x; - } - } - - Ok(hi) - } else { - Ok(0.0) - } - } - - #[pyfunction] - fn factorial(x: PyIntRef, vm: &VirtualMachine) -> PyResult { - let value = x.as_bigint(); - let one = BigInt::one(); - if value.is_negative() { - return Err(vm.new_value_error("factorial() not defined for negative values")); - } else if *value <= one { - return Ok(one); - } - // start from 2, since we know that value > 1 and 1*2=2 - let mut current = one + 1; - let mut product = BigInt::from(2u8); - while current < *value { - current += 1; - product *= ¤t; - } - Ok(product) - } - - #[pyfunction] - fn perm( - n: ArgIndex, - k: OptionalArg>, - vm: &VirtualMachine, - ) -> PyResult { - let n = n.into_int_ref(); - let n = n.as_bigint(); - let k_ref; - let v = match k.flatten() { - Some(k) => { - k_ref = k.into_int_ref(); - k_ref.as_bigint() - } - None => n, - }; - - if n.is_negative() || v.is_negative() { - return Err(vm.new_value_error("perm() not defined for negative values")); - } - if v > n { - return Ok(BigInt::zero()); - } - let mut result = BigInt::one(); - let mut current = n.clone(); - let tmp = n - v; - while current > tmp { - result *= ¤t; - current -= 1; - } - Ok(result) - } - - #[pyfunction] - fn comb(n: ArgIndex, k: ArgIndex, vm: &VirtualMachine) -> PyResult { - let k = k.into_int_ref(); - let mut k = k.as_bigint(); - let n = n.into_int_ref(); - let n = n.as_bigint(); - let one = BigInt::one(); - let zero = BigInt::zero(); - - if n.is_negative() || k.is_negative() { - return Err(vm.new_value_error("comb() not defined for negative values")); - } - - let temp = n - k; - if temp.is_negative() { - return Ok(zero); - } - - if temp < *k { - k = &temp - } - - if k.is_zero() { - return Ok(one); - } - - let mut result = n.clone(); - let mut factor = n.clone(); - let mut current = one; - while current < *k { - factor -= 1; - current += 1; - - result *= &factor; - result /= ¤t; - } - - Ok(result) + let values: Result, _> = + seq.iter(vm)?.map(|r| r.map(|v| v.into_float())).collect(); + pymath::math::fsum(values?).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn modf(x: ArgIntoFloat) -> (f64, f64) { - let x = x.into_float(); - if !x.is_finite() { - if x.is_infinite() { - return (0.0_f64.copysign(x), x); - } else if x.is_nan() { - return (x, x); - } - } - - (x.fract(), x.trunc()) + pymath::math::modf(x.into_float()) } #[derive(FromArgs)] @@ -887,85 +446,36 @@ mod math { #[pyfunction] fn nextafter(arg: NextAfterArgs, vm: &VirtualMachine) -> PyResult { - let steps: Option = arg - .steps - .map(|v| v.into_int_ref().try_to_primitive(vm)) - .transpose()? - .into_option(); - let x: f64 = arg.x.into(); - let y: f64 = arg.y.into(); - match steps { + let x = arg.x.into_float(); + let y = arg.y.into_float(); + + let steps = match arg.steps.into_option() { Some(steps) => { + let steps: i64 = steps.into_int_ref().try_to_primitive(vm)?; if steps < 0 { return Err(vm.new_value_error("steps must be a non-negative integer")); } - Ok(float_ops::nextafter_with_steps(x, y, steps as u64)) + Some(steps as u64) } - None => Ok(float_ops::nextafter(x, y)), - } + None => None, + }; + Ok(pymath::math::nextafter(x, y, steps)) } #[pyfunction] fn ulp(x: ArgIntoFloat) -> f64 { - float_ops::ulp(x.into()) - } - - fn fmod(x: f64, y: f64) -> f64 { - if y.is_infinite() && x.is_finite() { - return x; - } - - x % y + pymath::math::ulp(x.into_float()) } #[pyfunction(name = "fmod")] fn py_fmod(x: ArgIntoFloat, y: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let x = x.into_float(); - let y = y.into_float(); - - let r = fmod(x, y); - - if r.is_nan() && !x.is_nan() && !y.is_nan() { - return Err(vm.new_value_error("math domain error")); - } - - Ok(r) + pymath::math::fmod(x.into_float(), y.into_float()).map_err(|err| pymath_exception(err, vm)) } #[pyfunction] fn remainder(x: ArgIntoFloat, y: ArgIntoFloat, vm: &VirtualMachine) -> PyResult { - let x = x.into_float(); - let y = y.into_float(); - - if x.is_finite() && y.is_finite() { - if y == 0.0 { - return Err(vm.new_value_error("math domain error")); - } - - let abs_x = x.abs(); - let abs_y = y.abs(); - let modulus = abs_x % abs_y; - - let c = abs_y - modulus; - let r = match modulus.partial_cmp(&c) { - Some(Ordering::Less) => modulus, - Some(Ordering::Greater) => -c, - _ => modulus - 2.0 * fmod(0.5 * (abs_x - modulus), abs_y), - }; - - return Ok(1.0_f64.copysign(x) * r); - } - if x.is_infinite() && !y.is_nan() { - return Err(vm.new_value_error("math domain error")); - } - if x.is_nan() || y.is_nan() { - return Ok(f64::NAN); - } - if y.is_infinite() { - Ok(x) - } else { - Err(vm.new_value_error("math domain error")) - } + pymath::math::remainder(x.into_float(), y.into_float()) + .map_err(|err| pymath_exception(err, vm)) } #[derive(FromArgs)] @@ -978,15 +488,118 @@ mod math { #[pyfunction] fn prod(args: ProdArgs, vm: &VirtualMachine) -> PyResult { + use crate::vm::builtins::PyInt; + let iter = args.iterable; + let start = args.start; + + // Check if start is provided and what type it is (exact types only, not subclasses) + let (mut obj_result, start_is_int, start_is_float) = match &start { + OptionalArg::Present(s) => { + let is_int = s.class().is(vm.ctx.types.int_type); + let is_float = s.class().is(vm.ctx.types.float_type); + (Some(s.clone()), is_int, is_float) + } + OptionalArg::Missing => (None, true, false), // Default is int 1 + }; + + let mut item_iter = iter.iter(vm)?; + + // Integer fast path + if start_is_int && !start_is_float { + let mut int_result: i64 = match &start { + OptionalArg::Present(s) => { + if let Some(i) = s.downcast_ref::() { + match i.as_bigint().try_into() { + Ok(v) => v, + Err(_) => { + // Start overflows i64, fall through to generic path + obj_result = Some(s.clone()); + i64::MAX // Will be ignored + } + } + } else { + 1 + } + } + OptionalArg::Missing => 1, + }; - let mut result = args.start.unwrap_or_else(|| vm.new_pyobj(1)); + if obj_result.is_none() { + loop { + let item = match item_iter.next() { + Some(r) => r?, + None => return Ok(vm.ctx.new_int(int_result).into()), + }; + + // Only use fast path for exact int type (not subclasses) + if item.class().is(vm.ctx.types.int_type) + && let Some(int_item) = item.downcast_ref::() + && let Ok(b) = int_item.as_bigint().try_into() as Result + && let Some(product) = int_result.checked_mul(b) + { + int_result = product; + continue; + } - // TODO: CPython has optimized implementation for this - // refer: https://github.com/python/cpython/blob/main/Modules/mathmodule.c#L3093-L3193 - for obj in iter.iter(vm)? { - let obj = obj?; - result = vm._mul(&result, &obj)?; + // Overflow or non-int: restore to PyObject and continue + obj_result = Some(vm.ctx.new_int(int_result).into()); + let temp = vm._mul(obj_result.as_ref().unwrap(), &item)?; + obj_result = Some(temp); + break; + } + } + } + + // Float fast path + let obj_float = obj_result + .as_ref() + .and_then(|obj| obj.clone().downcast::().ok()); + if obj_float.is_some() || start_is_float { + let mut flt_result: f64 = if let Some(ref f) = obj_float { + f.to_f64() + } else if start_is_float && let OptionalArg::Present(s) = &start { + s.downcast_ref::() + .map(|f| f.to_f64()) + .unwrap_or(1.0) + } else { + 1.0 + }; + + loop { + let item = match item_iter.next() { + Some(r) => r?, + None => return Ok(vm.ctx.new_float(flt_result).into()), + }; + + // Only use fast path for exact float/int types (not subclasses) + if item.class().is(vm.ctx.types.float_type) + && let Some(f) = item.downcast_ref::() + { + flt_result *= f.to_f64(); + continue; + } + if item.class().is(vm.ctx.types.int_type) + && let Some(i) = item.downcast_ref::() + && let Ok(v) = i.as_bigint().try_into() as Result + { + flt_result *= v as f64; + continue; + } + + // Non-exact-float/int: restore and continue with generic path + obj_result = Some(vm.ctx.new_float(flt_result).into()); + let temp = vm._mul(obj_result.as_ref().unwrap(), &item)?; + obj_result = Some(temp); + break; + } + } + + // Generic path for remaining items + let mut result = obj_result.unwrap_or_else(|| vm.ctx.new_int(1).into()); + for item in item_iter { + let item = item?; + result = vm._mul(&result, &item)?; } Ok(result) @@ -998,29 +611,145 @@ mod math { q: ArgIterable, vm: &VirtualMachine, ) -> PyResult { + use crate::vm::builtins::PyInt; + let mut p_iter = p.iter(vm)?; let mut q_iter = q.iter(vm)?; - // We cannot just create a float because the iterator may contain - // anything as long as it supports __add__ and __mul__. - let mut result = vm.new_pyobj(0); + + // Fast path state + let mut int_path_enabled = true; + let mut int_total: i64 = 0; + let mut int_total_in_use = false; + let mut flt_p_values: Vec = Vec::new(); + let mut flt_q_values: Vec = Vec::new(); + + // Fallback accumulator for generic Python path + let mut obj_total: Option = None; + loop { let m_p = p_iter.next(); let m_q = q_iter.next(); - match (m_p, m_q) { - (Some(r_p), Some(r_q)) => { - let p = r_p?; - let q = r_q?; - let tmp = vm._mul(&p, &q)?; - result = vm._add(&result, &tmp)?; + + let (p_i, q_i, finished) = match (m_p, m_q) { + (Some(r_p), Some(r_q)) => (Some(r_p?), Some(r_q?), false), + (None, None) => (None, None, true), + _ => return Err(vm.new_value_error("Inputs are not the same length")), + }; + + // Integer fast path (only for exact int types, not subclasses) + if int_path_enabled { + if !finished { + let (p_i, q_i) = (p_i.as_ref().unwrap(), q_i.as_ref().unwrap()); + if p_i.class().is(vm.ctx.types.int_type) + && q_i.class().is(vm.ctx.types.int_type) + && let (Some(p_int), Some(q_int)) = + (p_i.downcast_ref::(), q_i.downcast_ref::()) + && let (Ok(p_val), Ok(q_val)) = ( + p_int.as_bigint().try_into() as Result, + q_int.as_bigint().try_into() as Result, + ) + && let Some(prod) = p_val.checked_mul(q_val) + && let Some(new_total) = int_total.checked_add(prod) + { + int_total = new_total; + int_total_in_use = true; + continue; + } } - (None, None) => break, - _ => { - return Err(vm.new_value_error("Inputs are not the same length")); + // Finalize int path + int_path_enabled = false; + if int_total_in_use { + let int_obj: PyObjectRef = vm.ctx.new_int(int_total).into(); + obj_total = Some(match obj_total { + Some(total) => vm._add(&total, &int_obj)?, + None => int_obj, + }); + int_total = 0; + int_total_in_use = false; } } + + // Float fast path - only when at least one value is exact float type + // (not subclasses, to preserve custom __mul__/__add__ behavior) + { + if !finished { + let (p_i, q_i) = (p_i.as_ref().unwrap(), q_i.as_ref().unwrap()); + + let p_is_exact_float = p_i.class().is(vm.ctx.types.float_type); + let q_is_exact_float = q_i.class().is(vm.ctx.types.float_type); + let p_is_exact_int = p_i.class().is(vm.ctx.types.int_type); + let q_is_exact_int = q_i.class().is(vm.ctx.types.int_type); + let p_is_exact_numeric = p_is_exact_float || p_is_exact_int; + let q_is_exact_numeric = q_is_exact_float || q_is_exact_int; + let has_exact_float = p_is_exact_float || q_is_exact_float; + + // Only use float path if at least one is exact float and both are exact int/float + if has_exact_float && p_is_exact_numeric && q_is_exact_numeric { + let p_flt = if let Some(f) = p_i.downcast_ref::() { + Some(f.to_f64()) + } else if let Some(i) = p_i.downcast_ref::() { + // PyLong_AsDouble fails for integers too large for f64 + try_bigint_to_f64(i.as_bigint(), vm).ok() + } else { + None + }; + + let q_flt = if let Some(f) = q_i.downcast_ref::() { + Some(f.to_f64()) + } else if let Some(i) = q_i.downcast_ref::() { + // PyLong_AsDouble fails for integers too large for f64 + try_bigint_to_f64(i.as_bigint(), vm).ok() + } else { + None + }; + + if let (Some(p_val), Some(q_val)) = (p_flt, q_flt) { + flt_p_values.push(p_val); + flt_q_values.push(q_val); + continue; + } + } + } + // Finalize float path + if !flt_p_values.is_empty() { + let flt_result = pymath::math::sumprod(&flt_p_values, &flt_q_values); + let flt_obj: PyObjectRef = vm.ctx.new_float(flt_result).into(); + obj_total = Some(match obj_total { + Some(total) => vm._add(&total, &flt_obj)?, + None => flt_obj, + }); + flt_p_values.clear(); + flt_q_values.clear(); + } + } + + if finished { + break; + } + + // Generic Python path + let (p_i, q_i) = (p_i.unwrap(), q_i.unwrap()); + + // Collect current + remaining elements + let p_remaining: Result, _> = + std::iter::once(Ok(p_i)).chain(p_iter).collect(); + let q_remaining: Result, _> = + std::iter::once(Ok(q_i)).chain(q_iter).collect(); + let (p_vec, q_vec) = (p_remaining?, q_remaining?); + + if p_vec.len() != q_vec.len() { + return Err(vm.new_value_error("Inputs are not the same length")); + } + + let mut total = obj_total.unwrap_or_else(|| vm.ctx.new_int(0).into()); + for (p_item, q_item) in p_vec.into_iter().zip(q_vec) { + let prod = vm._mul(&p_item, &q_item)?; + total = vm._add(&total, &prod)?; + } + return Ok(total); } - Ok(result) + Ok(obj_total.unwrap_or_else(|| vm.ctx.new_int(0).into())) } #[pyfunction] @@ -1030,30 +759,202 @@ mod math { z: ArgIntoFloat, vm: &VirtualMachine, ) -> PyResult { - let x = x.into_float(); - let y = y.into_float(); - let z = z.into_float(); - let result = x.mul_add(y, z); + pymath::math::fma(x.into_float(), y.into_float(), z.into_float()).map_err(|err| match err { + pymath::Error::EDOM => vm.new_value_error("invalid operation in fma"), + pymath::Error::ERANGE => vm.new_overflow_error("overflow in fma"), + }) + } + + // Integer functions: + + #[pyfunction] + fn isqrt(x: ArgIndex, vm: &VirtualMachine) -> PyResult { + let value = x.into_int_ref(); + pymath::math::integer::isqrt(value.as_bigint()) + .map_err(|_| vm.new_value_error("isqrt() argument must be nonnegative")) + } - if result.is_finite() { - return Ok(result); + #[pyfunction] + fn gcd(args: PosArgs) -> BigInt { + let ints: Vec<_> = args + .into_vec() + .into_iter() + .map(|x| x.into_int_ref()) + .collect(); + let refs: Vec<_> = ints.iter().map(|x| x.as_bigint()).collect(); + pymath::math::integer::gcd(&refs) + } + + #[pyfunction] + fn lcm(args: PosArgs) -> BigInt { + let ints: Vec<_> = args + .into_vec() + .into_iter() + .map(|x| x.into_int_ref()) + .collect(); + let refs: Vec<_> = ints.iter().map(|x| x.as_bigint()).collect(); + pymath::math::integer::lcm(&refs) + } + + #[pyfunction] + fn factorial(x: PyIntRef, vm: &VirtualMachine) -> PyResult { + // Check for negative before overflow - negative values are always invalid + if x.as_bigint().is_negative() { + return Err(vm.new_value_error("factorial() not defined for negative values")); } + let n: i64 = x.try_to_primitive(vm).map_err(|_| { + vm.new_overflow_error("factorial() argument should not exceed 9223372036854775807") + })?; + pymath::math::integer::factorial(n) + .map(|r| r.into()) + .map_err(|_| vm.new_value_error("factorial() not defined for negative values")) + } + + #[pyfunction] + fn perm( + n: ArgIndex, + k: OptionalArg>, + vm: &VirtualMachine, + ) -> PyResult { + let n_int = n.into_int_ref(); + let n_big = n_int.as_bigint(); - if result.is_nan() { - if !x.is_nan() && !y.is_nan() && !z.is_nan() { - return Err(vm.new_value_error("invalid operation in fma")); + if n_big.is_negative() { + return Err(vm.new_value_error("n must be a non-negative integer")); + } + + // k = None means k = n (factorial) + let k_int = k.flatten().map(|k| k.into_int_ref()); + let k_big: Option<&BigInt> = k_int.as_ref().map(|k| k.as_bigint()); + + if let Some(k_val) = k_big { + if k_val.is_negative() { + return Err(vm.new_value_error("k must be a non-negative integer")); + } + if k_val > n_big { + return Ok(BigInt::from(0u8)); } - } else if x.is_finite() && y.is_finite() && z.is_finite() { - return Err(vm.new_overflow_error("overflow in fma")); } - Ok(result) + // Convert k to u64 (required by pymath) + let ki: u64 = match k_big { + None => match n_big.to_u64() { + Some(n) => n, + None => { + return Err(vm.new_overflow_error(format!("n must not exceed {}", u64::MAX))); + } + }, + Some(k_val) => match k_val.to_u64() { + Some(k) => k, + None => { + return Err(vm.new_overflow_error(format!("k must not exceed {}", u64::MAX))); + } + }, + }; + + // Fast path: n fits in i64 + if let Some(ni) = n_big.to_i64() + && ni >= 0 + && ki > 1 + { + let result = pymath::math::integer::perm(ni, Some(ki as i64)) + .map_err(|_| vm.new_value_error("perm() error"))?; + return Ok(result.into()); + } + + // BigInt path: use perm_bigint + let result = pymath::math::perm_bigint(n_big, ki); + Ok(result.into()) + } + + #[pyfunction] + fn comb(n: ArgIndex, k: ArgIndex, vm: &VirtualMachine) -> PyResult { + let n_int = n.into_int_ref(); + let n_big = n_int.as_bigint(); + let k_int = k.into_int_ref(); + let k_big = k_int.as_bigint(); + + if n_big.is_negative() { + return Err(vm.new_value_error("n must be a non-negative integer")); + } + if k_big.is_negative() { + return Err(vm.new_value_error("k must be a non-negative integer")); + } + + // Fast path: n fits in i64 + if let Some(ni) = n_big.to_i64() + && ni >= 0 + { + // k overflow or k > n means result is 0 + let ki = match k_big.to_i64() { + Some(k) if k >= 0 && k <= ni => k, + _ => return Ok(BigInt::from(0u8)), + }; + // Apply symmetry: use min(k, n-k) + let ki = ki.min(ni - ki); + if ki > 1 { + let result = pymath::math::integer::comb(ni, ki) + .map_err(|_| vm.new_value_error("comb() error"))?; + return Ok(result.into()); + } + // ki <= 1 cases + if ki == 0 { + return Ok(BigInt::from(1u8)); + } + return Ok(n_big.clone()); // ki == 1 + } + + // BigInt path: n doesn't fit in i64 + // Apply symmetry: k = min(k, n - k) + let n_minus_k = n_big - k_big; + if n_minus_k.is_negative() { + return Ok(BigInt::from(0u8)); + } + let effective_k = if &n_minus_k < k_big { + &n_minus_k + } else { + k_big + }; + + // k must fit in u64 + let ki: u64 = match effective_k.to_u64() { + Some(k) => k, + None => { + return Err( + vm.new_overflow_error(format!("min(n - k, k) must not exceed {}", u64::MAX)) + ); + } + }; + + let result = pymath::math::comb_bigint(n_big, ki); + Ok(result.into()) } } -fn pymath_error_to_exception(err: pymath::Error, vm: &VirtualMachine) -> PyBaseExceptionRef { +pub(crate) fn pymath_exception(err: pymath::Error, vm: &VirtualMachine) -> PyBaseExceptionRef { match err { pymath::Error::EDOM => vm.new_value_error("math domain error"), pymath::Error::ERANGE => vm.new_overflow_error("math range error"), } } + +/// Format a float in Python style (ensures trailing .0 for integers). +fn float_repr(value: f64) -> String { + if value.is_nan() { + "nan".to_owned() + } else if value.is_infinite() { + if value.is_sign_positive() { + "inf".to_owned() + } else { + "-inf".to_owned() + } + } else { + let s = format!("{}", value); + // If no decimal point and not in scientific notation, add .0 + if !s.contains('.') && !s.contains('e') && !s.contains('E') { + format!("{}.0", s) + } else { + s + } + } +} diff --git a/crates/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs index f101a3aa8e3..9941aea9ba2 100644 --- a/crates/vm/src/builtins/float.rs +++ b/crates/vm/src/builtins/float.rs @@ -116,7 +116,7 @@ fn inner_divmod(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult<(f64, f64)> { pub fn float_pow(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult { if v1.is_zero() && v2.is_sign_negative() { - let msg = "0.0 cannot be raised to a negative power"; + let msg = "zero to a negative power"; Err(vm.new_zero_division_error(msg.to_owned())) } else if v1.is_sign_negative() && (v2.floor() - v2).abs() > f64::EPSILON { let v1 = Complex64::new(v1, 0.); From bb57724d3b61bc0d9814a102a62dce99e0cebe0c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 13 Jan 2026 22:05:49 +0900 Subject: [PATCH 796/819] flush on WantWrite (#6717) --- crates/stdlib/src/ssl/compat.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/stdlib/src/ssl/compat.rs b/crates/stdlib/src/ssl/compat.rs index 00e2c8d3c32..3c72ccf4e21 100644 --- a/crates/stdlib/src/ssl/compat.rs +++ b/crates/stdlib/src/ssl/compat.rs @@ -1237,8 +1237,7 @@ fn handle_handshake_complete( } } else if conn.wants_write() { // Send all pending data (e.g., TLS 1.3 NewSessionTicket) to socket - // Best-effort: WantWrite means socket buffer full, pending data will be - // sent in subsequent read/write calls. Don't fail handshake for this. + // Must drain ALL rustls buffer - don't break on WantWrite while conn.wants_write() { let tls_data = ssl_write_tls_records(conn)?; if tls_data.is_empty() { @@ -1246,7 +1245,13 @@ fn handle_handshake_complete( } match send_all_bytes(socket, tls_data, vm, None) { Ok(()) => {} - Err(SslError::WantWrite) => break, + Err(SslError::WantWrite) => { + // Socket buffer full, data saved to pending_tls_output + // Flush pending and continue draining rustls buffer + socket + .blocking_flush_all_pending(vm) + .map_err(SslError::Py)?; + } Err(e) => return Err(e), } } @@ -1256,6 +1261,7 @@ fn handle_handshake_complete( // TLS 1.3 Finished must reach server before handshake is considered complete // Without this, server may not process application data if !socket.is_bio_mode() { + // Flush pending_tls_output to ensure all TLS data reaches the server socket .blocking_flush_all_pending(vm) .map_err(SslError::Py)?; From 9eeea925c7072688146b9385f31248d55671ca34 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 14 Jan 2026 01:58:49 +0200 Subject: [PATCH 797/819] Move `OpArg` to its own file (#6703) * Move OpArg to its own file * Fix merge * Fix more merge --- crates/compiler-core/src/bytecode.rs | 661 +----------------- .../compiler-core/src/bytecode/instruction.rs | 83 ++- crates/compiler-core/src/bytecode/oparg.rs | 582 +++++++++++++++ 3 files changed, 669 insertions(+), 657 deletions(-) create mode 100644 crates/compiler-core/src/bytecode/oparg.rs diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 5270b89f3ad..3595e67c32e 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -8,15 +8,23 @@ use crate::{ }; use alloc::{collections::BTreeSet, fmt, vec::Vec}; use bitflags::bitflags; -use core::{hash, marker::PhantomData, mem, num::NonZeroU8, ops::Deref}; +use core::{hash, mem, ops::Deref}; use itertools::Itertools; use malachite_bigint::BigInt; use num_complex::Complex64; use rustpython_wtf8::{Wtf8, Wtf8Buf}; -pub use crate::bytecode::instruction::Instruction; +pub use crate::bytecode::{ + instruction::{Arg, Instruction, decode_load_super_attr_arg, encode_load_super_attr_arg}, + oparg::{ + BinaryOperator, BuildSliceArgCount, ComparisonOperator, ConvertValueOparg, + IntrinsicFunction1, IntrinsicFunction2, Invert, Label, MakeFunctionFlags, NameIdx, OpArg, + OpArgByte, OpArgState, OpArgType, RaiseKind, ResumeType, UnpackExArgs, + }, +}; mod instruction; +mod oparg; /// Exception table entry for zero-cost exception handling /// Format: (start, size, target, depth<<1|lasti) @@ -102,103 +110,6 @@ pub const fn decode_load_attr_arg(oparg: u32) -> (u32, bool) { (name_idx, is_method) } -/// Encode LOAD_SUPER_ATTR oparg: bit 0 = load_method, bit 1 = has_class, bits 2+ = name index. -#[inline] -pub const fn encode_load_super_attr_arg(name_idx: u32, load_method: bool, has_class: bool) -> u32 { - (name_idx << 2) | ((has_class as u32) << 1) | (load_method as u32) -} - -/// Decode LOAD_SUPER_ATTR oparg: returns (name_idx, load_method, has_class). -#[inline] -pub const fn decode_load_super_attr_arg(oparg: u32) -> (u32, bool, bool) { - let load_method = (oparg & 1) == 1; - let has_class = (oparg & 2) == 2; - let name_idx = oparg >> 2; - (name_idx, load_method, has_class) -} - -/// Oparg values for [`Instruction::ConvertValue`]. -/// -/// ## See also -/// -/// - [CPython FVC_* flags](https://github.com/python/cpython/blob/8183fa5e3f78ca6ab862de7fb8b14f3d929421e0/Include/ceval.h#L129-L132) -#[repr(u8)] -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -pub enum ConvertValueOparg { - /// No conversion. - /// - /// ```python - /// f"{x}" - /// f"{x:4}" - /// ``` - None = 0, - /// Converts by calling `str()`. - /// - /// ```python - /// f"{x!s}" - /// f"{x!s:2}" - /// ``` - Str = 1, - /// Converts by calling `repr()`. - /// - /// ```python - /// f"{x!r}" - /// f"{x!r:2}" - /// ``` - Repr = 2, - /// Converts by calling `ascii()`. - /// - /// ```python - /// f"{x!a}" - /// f"{x!a:2}" - /// ``` - Ascii = 3, -} - -impl fmt::Display for ConvertValueOparg { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let out = match self { - Self::Str => "1 (str)", - Self::Repr => "2 (repr)", - Self::Ascii => "3 (ascii)", - // We should never reach this. `FVC_NONE` are being handled by `Instruction::FormatSimple` - Self::None => "", - }; - - write!(f, "{out}") - } -} - -impl OpArgType for ConvertValueOparg { - #[inline] - fn from_op_arg(x: u32) -> Option { - Some(match x { - // Ruff `ConversionFlag::None` is `-1i8`, - // when its converted to `u8` its value is `u8::MAX` - 0 | 255 => Self::None, - 1 => Self::Str, - 2 => Self::Repr, - 3 => Self::Ascii, - _ => return None, - }) - } - - #[inline] - fn to_op_arg(self) -> u32 { - self as u32 - } -} - -/// Resume type for the RESUME instruction -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -#[repr(u32)] -pub enum ResumeType { - AtFuncStart = 0, - AfterYield = 1, - AfterYieldFrom = 2, - AfterAwait = 3, -} - /// CPython 3.11+ linetable location info codes #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] @@ -399,296 +310,6 @@ bitflags! { } } -/// an opcode argument that may be extended by a prior ExtendedArg -#[derive(Copy, Clone, PartialEq, Eq)] -#[repr(transparent)] -pub struct OpArgByte(pub u8); - -impl OpArgByte { - pub const fn null() -> Self { - Self(0) - } -} - -impl From for OpArgByte { - fn from(raw: u8) -> Self { - Self(raw) - } -} - -impl fmt::Debug for OpArgByte { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -/// a full 32-bit op_arg, including any possible ExtendedArg extension -#[derive(Copy, Clone, Debug)] -#[repr(transparent)] -pub struct OpArg(pub u32); - -impl OpArg { - pub const fn null() -> Self { - Self(0) - } - - /// Returns how many CodeUnits a instruction with this op_arg will be encoded as - #[inline] - pub const fn instr_size(self) -> usize { - (self.0 > 0xff) as usize + (self.0 > 0xff_ff) as usize + (self.0 > 0xff_ff_ff) as usize + 1 - } - - /// returns the arg split into any necessary ExtendedArg components (in big-endian order) and - /// the arg for the real opcode itself - #[inline(always)] - pub fn split(self) -> (impl ExactSizeIterator, OpArgByte) { - let mut it = self - .0 - .to_le_bytes() - .map(OpArgByte) - .into_iter() - .take(self.instr_size()); - let lo = it.next().unwrap(); - (it.rev(), lo) - } -} - -impl From for OpArg { - fn from(raw: u32) -> Self { - Self(raw) - } -} - -#[derive(Default, Copy, Clone)] -#[repr(transparent)] -pub struct OpArgState { - state: u32, -} - -impl OpArgState { - #[inline(always)] - pub fn get(&mut self, ins: CodeUnit) -> (Instruction, OpArg) { - let arg = self.extend(ins.arg); - if ins.op != Instruction::ExtendedArg { - self.reset(); - } - (ins.op, arg) - } - - #[inline(always)] - pub fn extend(&mut self, arg: OpArgByte) -> OpArg { - self.state = (self.state << 8) | u32::from(arg.0); - OpArg(self.state) - } - - #[inline(always)] - pub const fn reset(&mut self) { - self.state = 0 - } -} - -pub trait OpArgType: Copy { - fn from_op_arg(x: u32) -> Option; - - fn to_op_arg(self) -> u32; -} - -impl OpArgType for u32 { - #[inline(always)] - fn from_op_arg(x: u32) -> Option { - Some(x) - } - - #[inline(always)] - fn to_op_arg(self) -> u32 { - self - } -} - -impl OpArgType for bool { - #[inline(always)] - fn from_op_arg(x: u32) -> Option { - Some(x != 0) - } - - #[inline(always)] - fn to_op_arg(self) -> u32 { - self as u32 - } -} - -macro_rules! op_arg_enum_impl { - (enum $name:ident { $($(#[$var_attr:meta])* $var:ident = $value:literal,)* }) => { - impl OpArgType for $name { - fn to_op_arg(self) -> u32 { - self as u32 - } - - fn from_op_arg(x: u32) -> Option { - Some(match u8::try_from(x).ok()? { - $($value => Self::$var,)* - _ => return None, - }) - } - } - }; -} - -macro_rules! op_arg_enum { - ($(#[$attr:meta])* $vis:vis enum $name:ident { $($(#[$var_attr:meta])* $var:ident = $value:literal,)* }) => { - $(#[$attr])* - $vis enum $name { - $($(#[$var_attr])* $var = $value,)* - } - - op_arg_enum_impl!(enum $name { - $($(#[$var_attr])* $var = $value,)* - }); - }; -} - -#[derive(Copy, Clone)] -pub struct Arg(PhantomData); - -impl Arg { - #[inline] - pub const fn marker() -> Self { - Self(PhantomData) - } - - #[inline] - pub fn new(arg: T) -> (Self, OpArg) { - (Self(PhantomData), OpArg(arg.to_op_arg())) - } - - #[inline] - pub fn new_single(arg: T) -> (Self, OpArgByte) - where - T: Into, - { - (Self(PhantomData), OpArgByte(arg.into())) - } - - #[inline(always)] - pub fn get(self, arg: OpArg) -> T { - self.try_get(arg).unwrap() - } - - #[inline(always)] - pub fn try_get(self, arg: OpArg) -> Option { - T::from_op_arg(arg.0) - } - - /// # Safety - /// T::from_op_arg(self) must succeed - #[inline(always)] - pub unsafe fn get_unchecked(self, arg: OpArg) -> T { - // SAFETY: requirements forwarded from caller - unsafe { T::from_op_arg(arg.0).unwrap_unchecked() } - } -} - -impl PartialEq for Arg { - fn eq(&self, _: &Self) -> bool { - true - } -} - -impl Eq for Arg {} - -impl fmt::Debug for Arg { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Arg<{}>", core::any::type_name::()) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -#[repr(transparent)] -// XXX: if you add a new instruction that stores a Label, make sure to add it in -// Instruction::label_arg -pub struct Label(pub u32); - -impl OpArgType for Label { - #[inline(always)] - fn from_op_arg(x: u32) -> Option { - Some(Self(x)) - } - - #[inline(always)] - fn to_op_arg(self) -> u32 { - self.0 - } -} - -impl fmt::Display for Label { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -op_arg_enum!( - /// The kind of Raise that occurred. - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - #[repr(u8)] - pub enum RaiseKind { - /// Bare `raise` statement with no arguments. - /// Gets the current exception from VM state (topmost_exception). - /// Maps to RAISE_VARARGS with oparg=0. - BareRaise = 0, - /// `raise exc` - exception is on the stack. - /// Maps to RAISE_VARARGS with oparg=1. - Raise = 1, - /// `raise exc from cause` - exception and cause are on the stack. - /// Maps to RAISE_VARARGS with oparg=2. - RaiseCause = 2, - /// Reraise exception from the stack top. - /// Used in exception handler cleanup blocks (finally, except). - /// Gets exception from stack, not from VM state. - /// Maps to the RERAISE opcode. - ReraiseFromStack = 3, - } -); - -op_arg_enum!( - /// Intrinsic function for CALL_INTRINSIC_1 - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - #[repr(u8)] - pub enum IntrinsicFunction1 { - // Invalid = 0, - Print = 1, - /// Import * operation - ImportStar = 2, - // StopIterationError = 3, - // AsyncGenWrap = 4, - UnaryPositive = 5, - /// Convert list to tuple - ListToTuple = 6, - /// Type parameter related - TypeVar = 7, - ParamSpec = 8, - TypeVarTuple = 9, - /// Generic subscript for PEP 695 - SubscriptGeneric = 10, - TypeAlias = 11, - } -); - -op_arg_enum!( - /// Intrinsic function for CALL_INTRINSIC_2 - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - #[repr(u8)] - pub enum IntrinsicFunction2 { - PrepReraiseStar = 1, - TypeVarWithBound = 2, - TypeVarWithConstraint = 3, - SetFunctionTypeParams = 4, - /// Set default value for type parameter (PEP 695) - SetTypeparamDefault = 5, - } -); - -pub type NameIdx = u32; - #[derive(Copy, Clone)] #[repr(C)] pub struct CodeUnit { @@ -756,29 +377,6 @@ impl Deref for CodeUnits { } } -bitflags! { - #[derive(Copy, Clone, Debug, PartialEq)] - pub struct MakeFunctionFlags: u8 { - const CLOSURE = 0x01; - const ANNOTATIONS = 0x02; - const KW_ONLY_DEFAULTS = 0x04; - const DEFAULTS = 0x08; - const TYPE_PARAMS = 0x10; - } -} - -impl OpArgType for MakeFunctionFlags { - #[inline(always)] - fn from_op_arg(x: u32) -> Option { - Self::from_bits(x as u8) - } - - #[inline(always)] - fn to_op_arg(self) -> u32 { - self.bits().into() - } -} - /// A Constant (which usually encapsulates data within it) /// /// # Examples @@ -934,245 +532,6 @@ impl BorrowedConstant<'_, C> { } } -op_arg_enum!( - /// The possible comparison operators - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - #[repr(u8)] - pub enum ComparisonOperator { - // be intentional with bits so that we can do eval_ord with just a bitwise and - // bits: | Equal | Greater | Less | - Less = 0b001, - Greater = 0b010, - NotEqual = 0b011, - Equal = 0b100, - LessOrEqual = 0b101, - GreaterOrEqual = 0b110, - } -); - -op_arg_enum!( - /// The possible Binary operators - /// - /// # Examples - /// - /// ```rust - /// use rustpython_compiler_core::bytecode::{Arg, BinaryOperator, Instruction}; - /// let (op, _) = Arg::new(BinaryOperator::Add); - /// let instruction = Instruction::BinaryOp { op }; - /// ``` - /// - /// See also: - /// - [_PyEval_BinaryOps](https://github.com/python/cpython/blob/8183fa5e3f78ca6ab862de7fb8b14f3d929421e0/Python/ceval.c#L316-L343) - #[repr(u8)] - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub enum BinaryOperator { - /// `+` - Add = 0, - /// `&` - And = 1, - /// `//` - FloorDivide = 2, - /// `<<` - Lshift = 3, - /// `@` - MatrixMultiply = 4, - /// `*` - Multiply = 5, - /// `%` - Remainder = 6, - /// `|` - Or = 7, - /// `**` - Power = 8, - /// `>>` - Rshift = 9, - /// `-` - Subtract = 10, - /// `/` - TrueDivide = 11, - /// `^` - Xor = 12, - /// `+=` - InplaceAdd = 13, - /// `&=` - InplaceAnd = 14, - /// `//=` - InplaceFloorDivide = 15, - /// `<<=` - InplaceLshift = 16, - /// `@=` - InplaceMatrixMultiply = 17, - /// `*=` - InplaceMultiply = 18, - /// `%=` - InplaceRemainder = 19, - /// `|=` - InplaceOr = 20, - /// `**=` - InplacePower = 21, - /// `>>=` - InplaceRshift = 22, - /// `-=` - InplaceSubtract = 23, - /// `/=` - InplaceTrueDivide = 24, - /// `^=` - InplaceXor = 25, - } -); - -impl BinaryOperator { - /// Get the "inplace" version of the operator. - /// This has no effect if `self` is already an "inplace" operator. - /// - /// # Example - /// ```rust - /// use rustpython_compiler_core::bytecode::BinaryOperator; - /// - /// assert_eq!(BinaryOperator::Power.as_inplace(), BinaryOperator::InplacePower); - /// - /// assert_eq!(BinaryOperator::InplaceSubtract.as_inplace(), BinaryOperator::InplaceSubtract); - /// ``` - #[must_use] - pub const fn as_inplace(self) -> Self { - match self { - Self::Add => Self::InplaceAdd, - Self::And => Self::InplaceAnd, - Self::FloorDivide => Self::InplaceFloorDivide, - Self::Lshift => Self::InplaceLshift, - Self::MatrixMultiply => Self::InplaceMatrixMultiply, - Self::Multiply => Self::InplaceMultiply, - Self::Remainder => Self::InplaceRemainder, - Self::Or => Self::InplaceOr, - Self::Power => Self::InplacePower, - Self::Rshift => Self::InplaceRshift, - Self::Subtract => Self::InplaceSubtract, - Self::TrueDivide => Self::InplaceTrueDivide, - Self::Xor => Self::InplaceXor, - _ => self, - } - } -} - -impl fmt::Display for BinaryOperator { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let op = match self { - Self::Add => "+", - Self::And => "&", - Self::FloorDivide => "//", - Self::Lshift => "<<", - Self::MatrixMultiply => "@", - Self::Multiply => "*", - Self::Remainder => "%", - Self::Or => "|", - Self::Power => "**", - Self::Rshift => ">>", - Self::Subtract => "-", - Self::TrueDivide => "/", - Self::Xor => "^", - Self::InplaceAdd => "+=", - Self::InplaceAnd => "&=", - Self::InplaceFloorDivide => "//=", - Self::InplaceLshift => "<<=", - Self::InplaceMatrixMultiply => "@=", - Self::InplaceMultiply => "*=", - Self::InplaceRemainder => "%=", - Self::InplaceOr => "|=", - Self::InplacePower => "**=", - Self::InplaceRshift => ">>=", - Self::InplaceSubtract => "-=", - Self::InplaceTrueDivide => "/=", - Self::InplaceXor => "^=", - }; - write!(f, "{op}") - } -} - -op_arg_enum!( - /// Whether or not to invert the operation. - #[repr(u8)] - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - pub enum Invert { - /// ```py - /// foo is bar - /// x in lst - /// ``` - No = 0, - /// ```py - /// foo is not bar - /// x not in lst - /// ``` - Yes = 1, - } -); - -/// Specifies if a slice is built with either 2 or 3 arguments. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum BuildSliceArgCount { - /// ```py - /// x[5:10] - /// ``` - Two, - /// ```py - /// x[5:10:2] - /// ``` - Three, -} - -impl OpArgType for BuildSliceArgCount { - #[inline(always)] - fn from_op_arg(x: u32) -> Option { - Some(match x { - 2 => Self::Two, - 3 => Self::Three, - _ => return None, - }) - } - - #[inline(always)] - fn to_op_arg(self) -> u32 { - u32::from(self.argc().get()) - } -} - -impl BuildSliceArgCount { - /// Get the numeric value of `Self`. - #[must_use] - pub const fn argc(self) -> NonZeroU8 { - let inner = match self { - Self::Two => 2, - Self::Three => 3, - }; - // Safety: `inner` can be either 2 or 3. - unsafe { NonZeroU8::new_unchecked(inner) } - } -} - -#[derive(Copy, Clone)] -pub struct UnpackExArgs { - pub before: u8, - pub after: u8, -} - -impl OpArgType for UnpackExArgs { - #[inline(always)] - fn from_op_arg(x: u32) -> Option { - let [before, after, ..] = x.to_le_bytes(); - Some(Self { before, after }) - } - - #[inline(always)] - fn to_op_arg(self) -> u32 { - u32::from_le_bytes([self.before, self.after, 0, 0]) - } -} - -impl fmt::Display for UnpackExArgs { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "before: {}, after: {}", self.before, self.after) - } -} - /* Maintain a stack of blocks on the VM. pub enum BlockType { diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index c41da105a3d..07464f375cb 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -1,12 +1,13 @@ -use alloc::fmt; -use std::mem; +use core::{fmt, marker::PhantomData, mem}; use crate::{ bytecode::{ - Arg, BinaryOperator, BorrowedConstant, BuildSliceArgCount, ComparisonOperator, Constant, - ConvertValueOparg, InstrDisplayContext, IntrinsicFunction1, IntrinsicFunction2, Invert, - Label, MakeFunctionFlags, NameIdx, OpArg, RaiseKind, UnpackExArgs, decode_load_attr_arg, - decode_load_super_attr_arg, + BorrowedConstant, Constant, InstrDisplayContext, decode_load_attr_arg, + oparg::{ + BinaryOperator, BuildSliceArgCount, ComparisonOperator, ConvertValueOparg, + IntrinsicFunction1, IntrinsicFunction2, Invert, Label, MakeFunctionFlags, NameIdx, + OpArg, OpArgByte, OpArgType, RaiseKind, UnpackExArgs, + }, }, marshal::MarshalError, }; @@ -861,3 +862,73 @@ impl Instruction { } } } + +#[derive(Copy, Clone)] +pub struct Arg(PhantomData); + +impl Arg { + #[inline] + pub const fn marker() -> Self { + Self(PhantomData) + } + + #[inline] + pub fn new(arg: T) -> (Self, OpArg) { + (Self(PhantomData), OpArg(arg.to_op_arg())) + } + + #[inline] + pub fn new_single(arg: T) -> (Self, OpArgByte) + where + T: Into, + { + (Self(PhantomData), OpArgByte(arg.into())) + } + + #[inline(always)] + pub fn get(self, arg: OpArg) -> T { + self.try_get(arg).unwrap() + } + + #[inline(always)] + pub fn try_get(self, arg: OpArg) -> Option { + T::from_op_arg(arg.0) + } + + /// # Safety + /// T::from_op_arg(self) must succeed + #[inline(always)] + pub unsafe fn get_unchecked(self, arg: OpArg) -> T { + // SAFETY: requirements forwarded from caller + unsafe { T::from_op_arg(arg.0).unwrap_unchecked() } + } +} + +impl PartialEq for Arg { + fn eq(&self, _: &Self) -> bool { + true + } +} + +impl Eq for Arg {} + +impl fmt::Debug for Arg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Arg<{}>", core::any::type_name::()) + } +} + +/// Encode LOAD_SUPER_ATTR oparg: bit 0 = load_method, bit 1 = has_class, bits 2+ = name index. +#[inline] +pub const fn encode_load_super_attr_arg(name_idx: u32, load_method: bool, has_class: bool) -> u32 { + (name_idx << 2) | ((has_class as u32) << 1) | (load_method as u32) +} + +/// Decode LOAD_SUPER_ATTR oparg: returns (name_idx, load_method, has_class). +#[inline] +pub const fn decode_load_super_attr_arg(oparg: u32) -> (u32, bool, bool) { + let load_method = (oparg & 1) == 1; + let has_class = (oparg & 2) == 2; + let name_idx = oparg >> 2; + (name_idx, load_method, has_class) +} diff --git a/crates/compiler-core/src/bytecode/oparg.rs b/crates/compiler-core/src/bytecode/oparg.rs new file mode 100644 index 00000000000..ba8a8eb1f25 --- /dev/null +++ b/crates/compiler-core/src/bytecode/oparg.rs @@ -0,0 +1,582 @@ +use bitflags::bitflags; + +use core::{fmt, num::NonZeroU8}; + +use crate::bytecode::{CodeUnit, instruction::Instruction}; + +pub trait OpArgType: Copy { + fn from_op_arg(x: u32) -> Option; + + fn to_op_arg(self) -> u32; +} + +/// Opcode argument that may be extended by a prior ExtendedArg. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct OpArgByte(pub u8); + +impl OpArgByte { + pub const fn null() -> Self { + Self(0) + } +} + +impl From for OpArgByte { + fn from(raw: u8) -> Self { + Self(raw) + } +} + +impl fmt::Debug for OpArgByte { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Full 32-bit op_arg, including any possible ExtendedArg extension. +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] +pub struct OpArg(pub u32); + +impl OpArg { + pub const fn null() -> Self { + Self(0) + } + + /// Returns how many CodeUnits a instruction with this op_arg will be encoded as + #[inline] + pub const fn instr_size(self) -> usize { + (self.0 > 0xff) as usize + (self.0 > 0xff_ff) as usize + (self.0 > 0xff_ff_ff) as usize + 1 + } + + /// returns the arg split into any necessary ExtendedArg components (in big-endian order) and + /// the arg for the real opcode itself + #[inline(always)] + pub fn split(self) -> (impl ExactSizeIterator, OpArgByte) { + let mut it = self + .0 + .to_le_bytes() + .map(OpArgByte) + .into_iter() + .take(self.instr_size()); + let lo = it.next().unwrap(); + (it.rev(), lo) + } +} + +impl From for OpArg { + fn from(raw: u32) -> Self { + Self(raw) + } +} + +#[derive(Default, Copy, Clone)] +#[repr(transparent)] +pub struct OpArgState { + state: u32, +} + +impl OpArgState { + #[inline(always)] + pub fn get(&mut self, ins: CodeUnit) -> (Instruction, OpArg) { + let arg = self.extend(ins.arg); + if ins.op != Instruction::ExtendedArg { + self.reset(); + } + (ins.op, arg) + } + + #[inline(always)] + pub fn extend(&mut self, arg: OpArgByte) -> OpArg { + self.state = (self.state << 8) | u32::from(arg.0); + OpArg(self.state) + } + + #[inline(always)] + pub const fn reset(&mut self) { + self.state = 0 + } +} + +/// Oparg values for [`Instruction::ConvertValue`]. +/// +/// ## See also +/// +/// - [CPython FVC_* flags](https://github.com/python/cpython/blob/8183fa5e3f78ca6ab862de7fb8b14f3d929421e0/Include/ceval.h#L129-L132) +#[repr(u8)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum ConvertValueOparg { + /// No conversion. + /// + /// ```python + /// f"{x}" + /// f"{x:4}" + /// ``` + None = 0, + /// Converts by calling `str()`. + /// + /// ```python + /// f"{x!s}" + /// f"{x!s:2}" + /// ``` + Str = 1, + /// Converts by calling `repr()`. + /// + /// ```python + /// f"{x!r}" + /// f"{x!r:2}" + /// ``` + Repr = 2, + /// Converts by calling `ascii()`. + /// + /// ```python + /// f"{x!a}" + /// f"{x!a:2}" + /// ``` + Ascii = 3, +} + +impl fmt::Display for ConvertValueOparg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let out = match self { + Self::Str => "1 (str)", + Self::Repr => "2 (repr)", + Self::Ascii => "3 (ascii)", + // We should never reach this. `FVC_NONE` are being handled by `Instruction::FormatSimple` + Self::None => "", + }; + + write!(f, "{out}") + } +} + +impl OpArgType for ConvertValueOparg { + #[inline] + fn from_op_arg(x: u32) -> Option { + Some(match x { + // Ruff `ConversionFlag::None` is `-1i8`, + // when its converted to `u8` its value is `u8::MAX` + 0 | 255 => Self::None, + 1 => Self::Str, + 2 => Self::Repr, + 3 => Self::Ascii, + _ => return None, + }) + } + + #[inline] + fn to_op_arg(self) -> u32 { + self as u32 + } +} + +/// Resume type for the RESUME instruction +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +#[repr(u32)] +pub enum ResumeType { + AtFuncStart = 0, + AfterYield = 1, + AfterYieldFrom = 2, + AfterAwait = 3, +} + +impl OpArgType for u32 { + #[inline(always)] + fn from_op_arg(x: u32) -> Option { + Some(x) + } + + #[inline(always)] + fn to_op_arg(self) -> u32 { + self + } +} + +impl OpArgType for bool { + #[inline(always)] + fn from_op_arg(x: u32) -> Option { + Some(x != 0) + } + + #[inline(always)] + fn to_op_arg(self) -> u32 { + self as u32 + } +} + +macro_rules! op_arg_enum_impl { + (enum $name:ident { $($(#[$var_attr:meta])* $var:ident = $value:literal,)* }) => { + impl OpArgType for $name { + fn to_op_arg(self) -> u32 { + self as u32 + } + + fn from_op_arg(x: u32) -> Option { + Some(match u8::try_from(x).ok()? { + $($value => Self::$var,)* + _ => return None, + }) + } + } + }; +} + +macro_rules! op_arg_enum { + ($(#[$attr:meta])* $vis:vis enum $name:ident { $($(#[$var_attr:meta])* $var:ident = $value:literal,)* }) => { + $(#[$attr])* + $vis enum $name { + $($(#[$var_attr])* $var = $value,)* + } + + op_arg_enum_impl!(enum $name { + $($(#[$var_attr])* $var = $value,)* + }); + }; +} + +pub type NameIdx = u32; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +#[repr(transparent)] +pub struct Label(pub u32); + +impl OpArgType for Label { + #[inline(always)] + fn from_op_arg(x: u32) -> Option { + Some(Self(x)) + } + + #[inline(always)] + fn to_op_arg(self) -> u32 { + self.0 + } +} + +impl fmt::Display for Label { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +op_arg_enum!( + /// The kind of Raise that occurred. + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + #[repr(u8)] + pub enum RaiseKind { + /// Bare `raise` statement with no arguments. + /// Gets the current exception from VM state (topmost_exception). + /// Maps to RAISE_VARARGS with oparg=0. + BareRaise = 0, + /// `raise exc` - exception is on the stack. + /// Maps to RAISE_VARARGS with oparg=1. + Raise = 1, + /// `raise exc from cause` - exception and cause are on the stack. + /// Maps to RAISE_VARARGS with oparg=2. + RaiseCause = 2, + /// Reraise exception from the stack top. + /// Used in exception handler cleanup blocks (finally, except). + /// Gets exception from stack, not from VM state. + /// Maps to the RERAISE opcode. + ReraiseFromStack = 3, + } +); + +op_arg_enum!( + /// Intrinsic function for CALL_INTRINSIC_1 + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + #[repr(u8)] + pub enum IntrinsicFunction1 { + // Invalid = 0, + Print = 1, + /// Import * operation + ImportStar = 2, + // StopIterationError = 3, + // AsyncGenWrap = 4, + UnaryPositive = 5, + /// Convert list to tuple + ListToTuple = 6, + /// Type parameter related + TypeVar = 7, + ParamSpec = 8, + TypeVarTuple = 9, + /// Generic subscript for PEP 695 + SubscriptGeneric = 10, + TypeAlias = 11, + } +); + +op_arg_enum!( + /// Intrinsic function for CALL_INTRINSIC_2 + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + #[repr(u8)] + pub enum IntrinsicFunction2 { + PrepReraiseStar = 1, + TypeVarWithBound = 2, + TypeVarWithConstraint = 3, + SetFunctionTypeParams = 4, + /// Set default value for type parameter (PEP 695) + SetTypeparamDefault = 5, + } +); + +bitflags! { + #[derive(Copy, Clone, Debug, PartialEq)] + pub struct MakeFunctionFlags: u8 { + const CLOSURE = 0x01; + const ANNOTATIONS = 0x02; + const KW_ONLY_DEFAULTS = 0x04; + const DEFAULTS = 0x08; + const TYPE_PARAMS = 0x10; + } +} + +impl OpArgType for MakeFunctionFlags { + #[inline(always)] + fn from_op_arg(x: u32) -> Option { + Self::from_bits(x as u8) + } + + #[inline(always)] + fn to_op_arg(self) -> u32 { + self.bits().into() + } +} + +op_arg_enum!( + /// The possible comparison operators + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[repr(u8)] + pub enum ComparisonOperator { + // be intentional with bits so that we can do eval_ord with just a bitwise and + // bits: | Equal | Greater | Less | + Less = 0b001, + Greater = 0b010, + NotEqual = 0b011, + Equal = 0b100, + LessOrEqual = 0b101, + GreaterOrEqual = 0b110, + } +); + +op_arg_enum!( + /// The possible Binary operators + /// + /// # Examples + /// + /// ```rust + /// use rustpython_compiler_core::bytecode::{Arg, BinaryOperator, Instruction}; + /// let (op, _) = Arg::new(BinaryOperator::Add); + /// let instruction = Instruction::BinaryOp { op }; + /// ``` + /// + /// See also: + /// - [_PyEval_BinaryOps](https://github.com/python/cpython/blob/8183fa5e3f78ca6ab862de7fb8b14f3d929421e0/Python/ceval.c#L316-L343) + #[repr(u8)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub enum BinaryOperator { + /// `+` + Add = 0, + /// `&` + And = 1, + /// `//` + FloorDivide = 2, + /// `<<` + Lshift = 3, + /// `@` + MatrixMultiply = 4, + /// `*` + Multiply = 5, + /// `%` + Remainder = 6, + /// `|` + Or = 7, + /// `**` + Power = 8, + /// `>>` + Rshift = 9, + /// `-` + Subtract = 10, + /// `/` + TrueDivide = 11, + /// `^` + Xor = 12, + /// `+=` + InplaceAdd = 13, + /// `&=` + InplaceAnd = 14, + /// `//=` + InplaceFloorDivide = 15, + /// `<<=` + InplaceLshift = 16, + /// `@=` + InplaceMatrixMultiply = 17, + /// `*=` + InplaceMultiply = 18, + /// `%=` + InplaceRemainder = 19, + /// `|=` + InplaceOr = 20, + /// `**=` + InplacePower = 21, + /// `>>=` + InplaceRshift = 22, + /// `-=` + InplaceSubtract = 23, + /// `/=` + InplaceTrueDivide = 24, + /// `^=` + InplaceXor = 25, + } +); + +impl BinaryOperator { + /// Get the "inplace" version of the operator. + /// This has no effect if `self` is already an "inplace" operator. + /// + /// # Example + /// ```rust + /// use rustpython_compiler_core::bytecode::BinaryOperator; + /// + /// assert_eq!(BinaryOperator::Power.as_inplace(), BinaryOperator::InplacePower); + /// + /// assert_eq!(BinaryOperator::InplaceSubtract.as_inplace(), BinaryOperator::InplaceSubtract); + /// ``` + #[must_use] + pub const fn as_inplace(self) -> Self { + match self { + Self::Add => Self::InplaceAdd, + Self::And => Self::InplaceAnd, + Self::FloorDivide => Self::InplaceFloorDivide, + Self::Lshift => Self::InplaceLshift, + Self::MatrixMultiply => Self::InplaceMatrixMultiply, + Self::Multiply => Self::InplaceMultiply, + Self::Remainder => Self::InplaceRemainder, + Self::Or => Self::InplaceOr, + Self::Power => Self::InplacePower, + Self::Rshift => Self::InplaceRshift, + Self::Subtract => Self::InplaceSubtract, + Self::TrueDivide => Self::InplaceTrueDivide, + Self::Xor => Self::InplaceXor, + _ => self, + } + } +} + +impl fmt::Display for BinaryOperator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let op = match self { + Self::Add => "+", + Self::And => "&", + Self::FloorDivide => "//", + Self::Lshift => "<<", + Self::MatrixMultiply => "@", + Self::Multiply => "*", + Self::Remainder => "%", + Self::Or => "|", + Self::Power => "**", + Self::Rshift => ">>", + Self::Subtract => "-", + Self::TrueDivide => "/", + Self::Xor => "^", + Self::InplaceAdd => "+=", + Self::InplaceAnd => "&=", + Self::InplaceFloorDivide => "//=", + Self::InplaceLshift => "<<=", + Self::InplaceMatrixMultiply => "@=", + Self::InplaceMultiply => "*=", + Self::InplaceRemainder => "%=", + Self::InplaceOr => "|=", + Self::InplacePower => "**=", + Self::InplaceRshift => ">>=", + Self::InplaceSubtract => "-=", + Self::InplaceTrueDivide => "/=", + Self::InplaceXor => "^=", + }; + write!(f, "{op}") + } +} + +op_arg_enum!( + /// Whether or not to invert the operation. + #[repr(u8)] + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub enum Invert { + /// ```py + /// foo is bar + /// x in lst + /// ``` + No = 0, + /// ```py + /// foo is not bar + /// x not in lst + /// ``` + Yes = 1, + } +); + +/// Specifies if a slice is built with either 2 or 3 arguments. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum BuildSliceArgCount { + /// ```py + /// x[5:10] + /// ``` + Two, + /// ```py + /// x[5:10:2] + /// ``` + Three, +} + +impl OpArgType for BuildSliceArgCount { + #[inline(always)] + fn from_op_arg(x: u32) -> Option { + Some(match x { + 2 => Self::Two, + 3 => Self::Three, + _ => return None, + }) + } + + #[inline(always)] + fn to_op_arg(self) -> u32 { + u32::from(self.argc().get()) + } +} + +impl BuildSliceArgCount { + /// Get the numeric value of `Self`. + #[must_use] + pub const fn argc(self) -> NonZeroU8 { + let inner = match self { + Self::Two => 2, + Self::Three => 3, + }; + // Safety: `inner` can be either 2 or 3. + unsafe { NonZeroU8::new_unchecked(inner) } + } +} + +#[derive(Copy, Clone)] +pub struct UnpackExArgs { + pub before: u8, + pub after: u8, +} + +impl OpArgType for UnpackExArgs { + #[inline(always)] + fn from_op_arg(x: u32) -> Option { + let [before, after, ..] = x.to_le_bytes(); + Some(Self { before, after }) + } + + #[inline(always)] + fn to_op_arg(self) -> u32 { + u32::from_le_bytes([self.before, self.after, 0, 0]) + } +} + +impl fmt::Display for UnpackExArgs { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "before: {}, after: {}", self.before, self.after) + } +} From 49e6931c7edc71688b3d2c3cda43d307b682545a Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Wed, 14 Jan 2026 09:14:05 +0900 Subject: [PATCH 798/819] Introduce flame_guard in rustpython-stdlib too (#6722) --- Cargo.lock | 1 + Cargo.toml | 2 +- crates/stdlib/Cargo.toml | 2 ++ crates/stdlib/src/json.rs | 4 ++++ crates/stdlib/src/lib.rs | 3 +++ crates/stdlib/src/macros.rs | 7 +++++++ 6 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 crates/stdlib/src/macros.rs diff --git a/Cargo.lock b/Cargo.lock index 79681b880ed..b3ff4cf6656 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3160,6 +3160,7 @@ dependencies = [ "digest", "dns-lookup", "dyn-clone", + "flame", "flate2", "foreign-types-shared", "gethostname", diff --git a/Cargo.toml b/Cargo.toml index 96e821e977c..083358cf749 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ importlib = ["rustpython-vm/importlib"] encodings = ["rustpython-vm/encodings"] stdio = ["rustpython-vm/stdio"] stdlib = ["rustpython-stdlib", "rustpython-pylib", "encodings"] -flame-it = ["rustpython-vm/flame-it", "flame", "flamescope"] +flame-it = ["rustpython-vm/flame-it", "rustpython-stdlib/flame-it", "flame", "flamescope"] freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/freeze-stdlib"] jit = ["rustpython-vm/jit"] threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"] diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 232022452aa..e45a0eed15c 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -22,6 +22,7 @@ ssl-rustls-fips = ["ssl-rustls", "aws-lc-rs/fips"] ssl-openssl = ["ssl", "openssl", "openssl-sys", "foreign-types-shared", "openssl-probe"] ssl-vendor = ["ssl-openssl", "openssl/vendored"] tkinter = ["dep:tk-sys", "dep:tcl-sys", "dep:widestring"] +flame-it = ["flame"] [dependencies] # rustpython crates @@ -33,6 +34,7 @@ ahash = { workspace = true } ascii = { workspace = true } cfg-if = { workspace = true } crossbeam-utils = { workspace = true } +flame = { workspace = true, optional = true } hex = { workspace = true } itertools = { workspace = true } indexmap = { workspace = true } diff --git a/crates/stdlib/src/json.rs b/crates/stdlib/src/json.rs index a3fd7972126..cc98ad912cc 100644 --- a/crates/stdlib/src/json.rs +++ b/crates/stdlib/src/json.rs @@ -74,6 +74,7 @@ mod _json { scan_once: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { + flame_guard!("JsonScanner::parse"); let c = match s.chars().next() { Some(c) => c, None => { @@ -153,6 +154,7 @@ mod _json { } fn parse_number(&self, s: &str, vm: &VirtualMachine) -> Option<(PyResult, usize)> { + flame_guard!("JsonScanner::parse_number"); let mut has_neg = false; let mut has_decimal = false; let mut has_exponent = false; @@ -213,6 +215,7 @@ mod _json { } fn encode_string(s: &str, ascii_only: bool) -> String { + flame_guard!("_json::encode_string"); let mut buf = Vec::::with_capacity(s.len() + 2); machinery::write_json_string(s, ascii_only, &mut buf) // SAFETY: writing to a vec can't fail @@ -253,6 +256,7 @@ mod _json { strict: OptionalArg, vm: &VirtualMachine, ) -> PyResult<(Wtf8Buf, usize)> { + flame_guard!("_json::scanstring"); machinery::scanstring(s.as_wtf8(), end, strict.unwrap_or(true)) .map_err(|e| py_decode_error(e, s, vm)) } diff --git a/crates/stdlib/src/lib.rs b/crates/stdlib/src/lib.rs index 567c584d85b..dc9bad4bd2f 100644 --- a/crates/stdlib/src/lib.rs +++ b/crates/stdlib/src/lib.rs @@ -8,6 +8,9 @@ extern crate rustpython_derive; extern crate alloc; +#[macro_use] +pub(crate) mod macros; + pub mod array; mod binascii; mod bisect; diff --git a/crates/stdlib/src/macros.rs b/crates/stdlib/src/macros.rs new file mode 100644 index 00000000000..385f4b1c4ab --- /dev/null +++ b/crates/stdlib/src/macros.rs @@ -0,0 +1,7 @@ +#[macro_export] +macro_rules! flame_guard { + ($name:expr) => { + #[cfg(feature = "flame-it")] + let _guard = ::flame::start_guard($name); + }; +} From 6cb763a332c897ee39fc0f0052ce2c703d4026ad Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Wed, 14 Jan 2026 13:28:25 +0900 Subject: [PATCH 799/819] Fix some parts of cron-ci workflow (#6724) * Use shared PYTHON_VERSION in cron-ci benchmark job * Correct extra-tests/jsontests.py script When the `extra_tests/jsontests.py` script was added, it was presumably during the time when Python versions 3.5 to 3.8 were in use. At that time, `test.libregrtest.runtest` was a valid path, but from CPython version 3.11 onwards, the path changed to `test.libregrtest.findtests`. * Use ssl-rustls feature instead of ssl in cron-ci workflow Replace the ssl feature with ssl-rustls in both the CARGO_ARGS environment variable and the cargo-llvm-cov test command to fix the cron-ci workflow. Since 1a783fc9ec16bd824967449bc9899661a5ee5761, it is disallowed to use ssl manually. * Replace inspect.getargspec with inspect.getfullargspec in custom_text_test_runner inspect.getargspec() was removed in Python 3.11, causing jsontests.py to fail with "TypeError: 'NoneType' object is not iterable" when running on RustPython (which targets Python 3.13). The get_function_args() function was silently catching the AttributeError and returning None, which then caused the error in store_class_fields() when trying to iterate over None. > https://docs.python.org/3/whatsnew/3.11.html > The getargspec() function, deprecated since Python 3.0; use > inspect.signature() or inspect.getfullargspec() instead. Co-Authored-By: Claude Opus 4.5 * Exclude rustpython-venvlauncher in cron-ci workflow Since rustpython-venvlauncher is Windows-only, it disables the project in the cron-ci workflow. * Apply suggestion from @fanninpm Co-authored-by: fanninpm * Trigger cron-ci workflow in pull_request --------- Co-authored-by: Claude Opus 4.5 Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com> Co-authored-by: fanninpm --- .github/workflows/cron-ci.yaml | 13 ++++++++++--- extra_tests/custom_text_test_runner.py | 2 +- extra_tests/jsontests.py | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cron-ci.yaml b/.github/workflows/cron-ci.yaml index 59d664e0ea1..d48c5e4cfeb 100644 --- a/.github/workflows/cron-ci.yaml +++ b/.github/workflows/cron-ci.yaml @@ -5,11 +5,14 @@ on: push: paths: - .github/workflows/cron-ci.yaml + pull_request: + paths: + - .github/workflows/cron-ci.yaml name: Periodic checks/tasks env: - CARGO_ARGS: --no-default-features --features stdlib,importlib,encodings,ssl,jit + CARGO_ARGS: --no-default-features --features stdlib,importlib,encodings,ssl-rustls,jit PYTHON_VERSION: "3.13.1" jobs: @@ -29,7 +32,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - run: sudo apt-get update && sudo apt-get -y install lcov - name: Run cargo-llvm-cov with Rust tests. - run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --verbose --no-default-features --features stdlib,importlib,encodings,ssl,jit + run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher --verbose --no-default-features --features stdlib,importlib,encodings,ssl-rustls,jit - name: Run cargo-llvm-cov with Python snippets. run: python scripts/cargo-llvm-cov.py continue-on-error: true @@ -39,6 +42,7 @@ jobs: - name: Prepare code coverage data run: cargo llvm-cov report --lcov --output-path='codecov.lcov' - name: Upload to Codecov + if: ${{ github.event_name != 'pull_request' }} uses: codecov/codecov-action@v5 with: file: ./codecov.lcov @@ -58,6 +62,7 @@ jobs: env: RUSTPYTHONPATH: ${{ github.workspace }}/Lib - name: upload tests data to the website + if: ${{ github.event_name != 'pull_request' }} env: SSHKEY: ${{ secrets.ACTIONS_TESTS_DATA_DEPLOY_KEY }} GITHUB_ACTOR: ${{ github.actor }} @@ -94,6 +99,7 @@ jobs: env: RUSTPYTHONPATH: ${{ github.workspace }}/Lib - name: Upload data to the website + if: ${{ github.event_name != 'pull_request' }} env: SSHKEY: ${{ secrets.ACTIONS_TESTS_DATA_DEPLOY_KEY }} GITHUB_ACTOR: ${{ github.actor }} @@ -141,7 +147,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable - uses: actions/setup-python@v6.1.0 with: - python-version: 3.9 + python-version: ${{ env.PYTHON_VERSION }} - run: cargo install cargo-criterion - name: build benchmarks run: cargo build --release --benches @@ -162,6 +168,7 @@ jobs: mv reports/* . rmdir reports - name: upload benchmark data to the website + if: ${{ github.event_name != 'pull_request' }} env: SSHKEY: ${{ secrets.ACTIONS_TESTS_DATA_DEPLOY_KEY }} run: | diff --git a/extra_tests/custom_text_test_runner.py b/extra_tests/custom_text_test_runner.py index 018121f0da4..afec493a66c 100644 --- a/extra_tests/custom_text_test_runner.py +++ b/extra_tests/custom_text_test_runner.py @@ -112,7 +112,7 @@ def __call__(self, data_list, totals=None): def get_function_args(func_ref): try: - return [p for p in inspect.getargspec(func_ref).args if p != "self"] + return [p for p in inspect.getfullargspec(func_ref).args if p != "self"] except: return None diff --git a/extra_tests/jsontests.py b/extra_tests/jsontests.py index c1f92509fe7..f3213ac09d1 100644 --- a/extra_tests/jsontests.py +++ b/extra_tests/jsontests.py @@ -2,7 +2,7 @@ import unittest from custom_text_test_runner import CustomTextTestRunner as Runner -from test.libregrtest.runtest import findtests +from test.libregrtest.findtests import findtests testnames = findtests() # idk why this fixes the hanging, if it does From 2e5257d098fe4bd880aacd8331e6161cfe32550d Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Wed, 14 Jan 2026 14:44:34 +0900 Subject: [PATCH 800/819] Introduce benchmark for json.loads (#6723) --- benches/_data/pypi_org__simple__psutil.json | 1 + benches/benchmarks/json_loads.py | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 benches/_data/pypi_org__simple__psutil.json create mode 100644 benches/benchmarks/json_loads.py diff --git a/benches/_data/pypi_org__simple__psutil.json b/benches/_data/pypi_org__simple__psutil.json new file mode 100644 index 00000000000..91e2ff6b39e --- /dev/null +++ b/benches/_data/pypi_org__simple__psutil.json @@ -0,0 +1 @@ +{"alternate-locations":[],"files":[{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.1.1.tar.gz","hashes":{"sha256":"25c6caffbf00d8be77489391a784654e99fcbaf2a5278e80f748be4112ee0188"},"provenance":null,"requires-python":null,"size":44485,"upload-time":"2014-02-06T02:06:57.249874Z","url":"https://files.pythonhosted.org/packages/69/e4/7e36e3e6cbc83b76f1c93a63d4c053a03ca99f1c99b106835cb175b5932a/psutil-0.1.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.1.2.tar.gz","hashes":{"sha256":"4a13d7f760b043b263346e48823b1dfd4c202e97b23483e481e5ff696e74509e"},"provenance":null,"requires-python":null,"size":61640,"upload-time":"2014-02-06T02:06:51.674389Z","url":"https://files.pythonhosted.org/packages/6e/51/56198d83577106bf89cb23bffcb273f923aea8d5ffe03e3fce55f830c323/psutil-0.1.2.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.1.3.tar.gz","hashes":{"sha256":"43e327934b4a273da20d3a5797d6abcaab37914f61499d96fcf9e8e1ae75442b"},"provenance":null,"requires-python":null,"size":85749,"upload-time":"2014-02-06T02:06:45.294070Z","url":"https://files.pythonhosted.org/packages/1d/4f/dcfe500fd43e3d6b26d253cb0d7e6e3a7d80224b5059bd50c482aff62eef/psutil-0.1.3.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.0.tar.gz","hashes":{"sha256":"8de8efa92162c94f623297f522e440e818dc7b832f421f9f490324bc7d5d0d92"},"provenance":null,"requires-python":null,"size":129382,"upload-time":"2014-02-06T02:03:03.376283Z","url":"https://files.pythonhosted.org/packages/58/20/3457e441edc1625c6e1dbfcf780d2b22f2e9caa8606c3fd8ce6c48104e87/psutil-0.2.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.0.win-amd64-py2.7.exe","hashes":{"sha256":"4ce48cbbe3c915a9b720ce6465da34ce79465ffde63de8254cc9c8235fef824d"},"provenance":null,"requires-python":null,"size":291186,"upload-time":"2014-02-06T16:48:27.727869Z","url":"https://files.pythonhosted.org/packages/e6/1d/9a90eec0aec7e015d16f3922328336b4f8cd783f782e9ab81146b47ffee3/psutil-0.2.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.0.win-amd64-py3.3.exe","hashes":{"sha256":"eeba1fa1f2455219a05775044f7a76741252ea0bd4c762e7b652d281843c79ff"},"provenance":null,"requires-python":null,"size":289973,"upload-time":"2014-02-06T16:48:36.482056Z","url":"https://files.pythonhosted.org/packages/1d/75/7c67bc2c8304b137a8ff709d21cb2dd7f600bc5ee76ecc88f77ec008e69e/psutil-0.2.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.0.win-amd64-py3.4.exe","hashes":{"sha256":"919dabb5d47dc769a198a5d015c6765ef39b3b920c2ab6d04d05d9aa3ae6bb67"},"provenance":null,"requires-python":null,"size":289890,"upload-time":"2014-02-06T16:48:46.485205Z","url":"https://files.pythonhosted.org/packages/e0/e4/2ec24cecccf111a4dbda892b484bdfd8c00d3da5f99c1a0a79c469c62fb2/psutil-0.2.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.0.win32-py2.5.exe","hashes":{"sha256":"92f51445439f1082dab7733b9150b594b3797f4c96578457768c3e761f86c240"},"provenance":null,"requires-python":null,"size":129555,"upload-time":"2014-02-06T16:47:42.630117Z","url":"https://files.pythonhosted.org/packages/04/4f/478408899102af1d0d877be0b67e302fada1bdd8c8faaa4adc0e19550e53/psutil-0.2.0.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.0.win32-py2.6.exe","hashes":{"sha256":"c3e3dc863834d03485f6c48a735f76ab9a7cff2911d101c8cbd1e553c03f0fae"},"provenance":null,"requires-python":null,"size":262087,"upload-time":"2014-02-06T16:47:50.995531Z","url":"https://files.pythonhosted.org/packages/d7/f9/984326888f6c519cc813a96640cc9a2900dad85e15b9d368fe1f12a9bd11/psutil-0.2.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.0.win32-py2.7.exe","hashes":{"sha256":"08c6f16ac00babe2c54067dbc305c0587cb896a8eb64383290cdafdba74ea123"},"provenance":null,"requires-python":null,"size":261605,"upload-time":"2014-02-06T16:47:58.286157Z","url":"https://files.pythonhosted.org/packages/69/5c/be56c645a254ad83a9aa3055564aae543fdf23186aa5ec9c495a3937300b/psutil-0.2.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.0.win32-py3.3.exe","hashes":{"sha256":"e1f7cf0158a94e6b50f9a4b6f4258dec1bd89c9485c65d54b89ba2a09f0d51a3"},"provenance":null,"requires-python":null,"size":256763,"upload-time":"2014-02-06T16:48:06.663656Z","url":"https://files.pythonhosted.org/packages/76/42/d6813ce55af42553b2a3136e5159bd71e544bda5fdaed04c72759514e375/psutil-0.2.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.0.win32-py3.4.exe","hashes":{"sha256":"e49d818a4a49fd1ced40dac31076af622a40407234f717bc6a5c39b3f2335e81"},"provenance":null,"requires-python":null,"size":256718,"upload-time":"2014-02-06T16:48:16.971617Z","url":"https://files.pythonhosted.org/packages/f5/83/45b721a52001c1ba1f196e6b2ba3b12d99cbc03bf3f508f1fe56b4e25c66/psutil-0.2.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.1.tar.gz","hashes":{"sha256":"bd33f5f9e04b7677a932cc90d541ceec0050080d1b053ed39488ef39cb0fb4f4"},"provenance":null,"requires-python":null,"size":144657,"upload-time":"2014-02-06T02:06:37.338565Z","url":"https://files.pythonhosted.org/packages/9b/62/03133a1b4d1439227bc9b27389fc7d1137d111cbb15197094225967e21cf/psutil-0.2.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.1.win-amd64-py2.7.exe","hashes":{"sha256":"ab9d06c0c2a7baadebf66fbc74a2532520b3b6d4a58ba3c1f04311a175ed9e7f"},"provenance":null,"requires-python":null,"size":294651,"upload-time":"2014-02-06T16:46:46.406386Z","url":"https://files.pythonhosted.org/packages/55/3e/e49b7929b9daf9c3dcf31668a43ba669d22bb8606deca52e6f92cb67324d/psutil-0.2.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.1.win-amd64-py3.3.exe","hashes":{"sha256":"076c9914d8aadccd459e3efedc49c10f14d16918d0af5735c22a2bdcc3f90cac"},"provenance":null,"requires-python":null,"size":293431,"upload-time":"2014-02-06T16:46:54.986513Z","url":"https://files.pythonhosted.org/packages/34/f5/f471c56be91d10c9b83d22aa9f6f29006437f2196c89156f00f56f6e4661/psutil-0.2.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.1.win-amd64-py3.4.exe","hashes":{"sha256":"2679f8f9f20e61d85177f3fc6ee10bfbd6c4bd122b3f5774ba36d0035cac4f7a"},"provenance":null,"requires-python":null,"size":293352,"upload-time":"2014-02-06T16:47:04.453988Z","url":"https://files.pythonhosted.org/packages/68/ee/fc85a707394d1b374da61c071dee12299e779d87c09a86c727b6f30ad9a6/psutil-0.2.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.1.win32-py2.5.exe","hashes":{"sha256":"a8345934d7585c75d500098491f2a434dbba9e378fd908d76fbeb0bc20b8a3d3"},"provenance":null,"requires-python":null,"size":132806,"upload-time":"2014-02-06T16:46:02.328109Z","url":"https://files.pythonhosted.org/packages/ca/6e/5c0a87e2c1bac5e32471eb84b349f0808f6d6ed49b9118784af9d0e71baa/psutil-0.2.1.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.1.win32-py2.6.exe","hashes":{"sha256":"9b6294c3327625839fdbe92dca4093ab55e422fb348790b2ffc5c6cde163fe5b"},"provenance":null,"requires-python":null,"size":265263,"upload-time":"2014-02-06T16:46:11.006069Z","url":"https://files.pythonhosted.org/packages/e5/13/b358b509d4996df82ef77758c14c0999c443df537eefacc7bb24b6339758/psutil-0.2.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.1.win32-py2.7.exe","hashes":{"sha256":"ac4d86abc4c850141cd1e054a64f1fcb6977c4ccbbd65c5bac9bf9c8c5950cb1"},"provenance":null,"requires-python":null,"size":264780,"upload-time":"2014-02-06T16:46:19.079623Z","url":"https://files.pythonhosted.org/packages/a9/1e/8ad399f44f63de6de262fc503b1d3f091f6a9b22feaf3c273ee8bb7a7c38/psutil-0.2.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.1.win32-py3.3.exe","hashes":{"sha256":"60048c47698aa20936b7593636dd88454bae6c436852aef95fb9774b0551d06c"},"provenance":null,"requires-python":null,"size":259950,"upload-time":"2014-02-06T16:46:28.163437Z","url":"https://files.pythonhosted.org/packages/75/f3/296f43c033c0453ebfdf125dc9b8907193e31ffb92256e4b702d16ef8ac1/psutil-0.2.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.2.1.win32-py3.4.exe","hashes":{"sha256":"4aad401c1a45848107d395f97fc9cdb783acf120553ca1761dd77a7d52adb585"},"provenance":null,"requires-python":null,"size":259910,"upload-time":"2014-02-06T16:46:37.546918Z","url":"https://files.pythonhosted.org/packages/ae/8c/751f1e2fdcaa0ea817a8529468dca052b5de55033ee8539b996497b5be0e/psutil-0.2.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.3.0.tar.gz","hashes":{"sha256":"d70e16e60a575637f9d75a1005a8987d239700c0955134d4c8666a5aefbe16b8"},"provenance":null,"requires-python":null,"size":153990,"upload-time":"2014-02-06T02:06:31.022178Z","url":"https://files.pythonhosted.org/packages/f2/5c/4e74b08905dab9474a53534507e463fe8eec98b2fc0d29964b0c6a7f959d/psutil-0.3.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.3.0.win-amd64-py2.7.exe","hashes":{"sha256":"a554abcd10534ff32e3cbc4bd80b3c76a73d0bbb50739ee59482f0151703828b"},"provenance":null,"requires-python":null,"size":297305,"upload-time":"2014-02-06T16:45:05.336122Z","url":"https://files.pythonhosted.org/packages/78/91/21944fbddafa1bdf7c0c0ed7d9f4043cd8470e36a1f7a9f6e8b4406a8e39/psutil-0.3.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.3.0.win-amd64-py3.3.exe","hashes":{"sha256":"b33444731c63361ac316657bb305642e562097e870793bc57203cd9a4e4819a4"},"provenance":null,"requires-python":null,"size":296059,"upload-time":"2014-02-06T16:45:13.056233Z","url":"https://files.pythonhosted.org/packages/88/bd/5e9d566e941e165987b8c9bd61583c58242ef4da12b7d9fbb07494580aff/psutil-0.3.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.3.0.win-amd64-py3.4.exe","hashes":{"sha256":"89419f0cab4dd3ef4b32933237e34f58799069930fd25ed7f69deb9c64106c85"},"provenance":null,"requires-python":null,"size":296960,"upload-time":"2014-02-06T16:45:22.608830Z","url":"https://files.pythonhosted.org/packages/1f/06/c827177a80996d00f8f46ec99292e9d03fdf230a6e4fcea25ed9d19f3dcf/psutil-0.3.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.3.0.win32-py2.5.exe","hashes":{"sha256":"515b12e23581730c47e181fa163b457b90b6e848de0637d78b7fa8ca84ec2bc6"},"provenance":null,"requires-python":null,"size":135440,"upload-time":"2014-02-06T16:44:20.300778Z","url":"https://files.pythonhosted.org/packages/e5/55/6a353c646e6d15ee618348ed68cf79800f1275ade127f665779b6be0ed19/psutil-0.3.0.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.3.0.win32-py2.6.exe","hashes":{"sha256":"5bc91423ff25ad8dffb3c9979abdafb1ecd2e9a7ef4b9287bb731e76ea2c4b91"},"provenance":null,"requires-python":null,"size":267782,"upload-time":"2014-02-06T16:44:30.319267Z","url":"https://files.pythonhosted.org/packages/61/04/c447b4d468402bfb10d21995b0eae219c8397c00d9e8a2b209fb032ac1aa/psutil-0.3.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.3.0.win32-py2.7.exe","hashes":{"sha256":"4b7fbaca98b72f69561d593ef18b5254a8f98482b561db3d906aac4dd307bbcc"},"provenance":null,"requires-python":null,"size":267308,"upload-time":"2014-02-06T16:44:39.895580Z","url":"https://files.pythonhosted.org/packages/e4/2f/b060e631686e6455ff483ea5b6333382c6dddb5727edbfc9dcca1a8f024c/psutil-0.3.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.3.0.win32-py3.3.exe","hashes":{"sha256":"005217c21551618085e3323d653d75f9ad8f7418bff4fd5109850fba11950d6e"},"provenance":null,"requires-python":null,"size":262514,"upload-time":"2014-02-06T16:44:47.866655Z","url":"https://files.pythonhosted.org/packages/50/34/7bfc452cb2cd0069a81064db51b484ef135125cf1d246d0ff82c65653c81/psutil-0.3.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.3.0.win32-py3.4.exe","hashes":{"sha256":"331ac97b50def25e41bae637642d0131d5c4223b52ab55281d002549632ed5e0"},"provenance":null,"requires-python":null,"size":262479,"upload-time":"2014-02-06T16:44:56.381135Z","url":"https://files.pythonhosted.org/packages/b0/08/1944d2281986237cdaeabfa36195f44dc4c847d5c4fda3523b44c6823e19/psutil-0.3.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.0.tar.gz","hashes":{"sha256":"4b0ecc77d6c503449af3d2f0a41ad4cb8338e173f5d655a8239e41b1a49bc278"},"provenance":null,"requires-python":null,"size":167796,"upload-time":"2014-02-06T02:06:24.041544Z","url":"https://files.pythonhosted.org/packages/88/46/a933ab20c6d9b0ca5704b60307b9e80bdc119b759e89b74a2609b4c10eb6/psutil-0.4.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.0.win-amd64-py2.7.exe","hashes":{"sha256":"9689512e5a32508216bd6cc7cb2fbe938e4a47b483cd345b5bec6dda790a9210"},"provenance":null,"requires-python":null,"size":302844,"upload-time":"2014-02-06T16:43:18.853340Z","url":"https://files.pythonhosted.org/packages/a5/06/3ca1c85b733ceaced144cb211c1fc40a6b3b1c4f7a109bbb8e19fe09eac0/psutil-0.4.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.0.win-amd64-py3.3.exe","hashes":{"sha256":"9ee193dec84c2c91ac8712760eaaae0488dee92db37134dfcddc889ecd4f578d"},"provenance":null,"requires-python":null,"size":301654,"upload-time":"2014-02-06T16:43:28.863005Z","url":"https://files.pythonhosted.org/packages/ac/eb/f6923ea1b46803251f2bb42ad9b8ed90f4b8d04cdb2384c9ea535193e4f7/psutil-0.4.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.0.win-amd64-py3.4.exe","hashes":{"sha256":"94d2d0a24fa62f5655e9e21dd558592bf1d3d515d944eee70e9cd2e2bf2bf244"},"provenance":null,"requires-python":null,"size":302558,"upload-time":"2014-02-06T16:43:38.802980Z","url":"https://files.pythonhosted.org/packages/7c/5b/d9057a158b09c377eab80dae60928f8fe29bdd11635ca725098587981eeb/psutil-0.4.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.0.win32-py2.5.exe","hashes":{"sha256":"fab822fb8968b45695174a2c9b4d40e9ae81126343e24d38b627036579f1ee5e"},"provenance":null,"requires-python":null,"size":140768,"upload-time":"2014-02-06T16:42:35.663297Z","url":"https://files.pythonhosted.org/packages/07/1e/7827d1271248ddef694681747297aeb3f77dafe5c93ff707a625e3947082/psutil-0.4.0.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.0.win32-py2.6.exe","hashes":{"sha256":"ca511f56db8f1586612851a2eedbf80e38cf29f4a98f133410a74da284822747"},"provenance":null,"requires-python":null,"size":273280,"upload-time":"2014-02-06T16:42:44.697290Z","url":"https://files.pythonhosted.org/packages/b4/56/78f182bf9c81a978067ba5447eca2ea70ef2bc9ac94228adfae5428cd108/psutil-0.4.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.0.win32-py2.7.exe","hashes":{"sha256":"5b6cbf5aaeab55db1d9657fc75e4b54fa94d17d03ca97c94aa150e1bfdc8fb2e"},"provenance":null,"requires-python":null,"size":272807,"upload-time":"2014-02-06T16:42:52.281617Z","url":"https://files.pythonhosted.org/packages/7d/38/b719c8867699a71a98e92e61fd1b0f12bad997a9de60bf8f6215184692e8/psutil-0.4.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.0.win32-py3.3.exe","hashes":{"sha256":"dfe18b8eb9ee6dbbd49e28f245794c2fc22b56d770038668cb6e112935c49927"},"provenance":null,"requires-python":null,"size":268022,"upload-time":"2014-02-06T16:43:01.185161Z","url":"https://files.pythonhosted.org/packages/f4/2b/13be095a72c83fdbe11d519310cb34ff8e442d72ab52691218a83c216ac4/psutil-0.4.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.0.win32-py3.4.exe","hashes":{"sha256":"5b63e0e83dde30d62206497320c06b27c489ffb261e95ac1267b7a1b8d4ce72d"},"provenance":null,"requires-python":null,"size":267989,"upload-time":"2014-02-06T16:43:09.528893Z","url":"https://files.pythonhosted.org/packages/b4/8e/028b849600447c45c20582d7ddae1e39d31b83799289f54f1b79af664295/psutil-0.4.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.1.tar.gz","hashes":{"sha256":"33002c38f916835c949ae39a84f3f6d09ce01818ed805dfd61f8f3844c395c9d"},"provenance":null,"requires-python":null,"size":171549,"upload-time":"2014-02-06T02:06:17.394021Z","url":"https://files.pythonhosted.org/packages/a0/cd/00550e16a2a0357a9c24946160e60fc947bbe23bc276aff1b4d3e7b90345/psutil-0.4.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.1.win-amd64-py2.7.exe","hashes":{"sha256":"3e408028898b4ad09c207659c1b6cb8e6a74fb39ab3157b761e057a629118fec"},"provenance":null,"requires-python":null,"size":310414,"upload-time":"2014-02-06T16:41:41.640755Z","url":"https://files.pythonhosted.org/packages/06/75/3dc33773b1a1f7055b24245a0c8bec4d5776f1e1125329a13e82449007be/psutil-0.4.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.1.win-amd64-py3.3.exe","hashes":{"sha256":"457cfdecbf63f813bf86e2f736ab3b20447e8d8c2c516dd2a13a7463177f8bc6"},"provenance":null,"requires-python":null,"size":309169,"upload-time":"2014-02-06T16:41:50.926907Z","url":"https://files.pythonhosted.org/packages/6e/9e/6c7f5baf2529d9bba563cdff0b81413b5870b0681739318112adecd99ad0/psutil-0.4.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.1.win-amd64-py3.4.exe","hashes":{"sha256":"fc75e094cf573a7b9977be2a9d3a9d94671bc0ab098d52c391dd63227309cbea"},"provenance":null,"requires-python":null,"size":310087,"upload-time":"2014-02-06T16:42:00.068444Z","url":"https://files.pythonhosted.org/packages/cd/e6/9f963b32ab254b8c2bbedf5ce543cace8b08054924fd26fa233f99863ecc/psutil-0.4.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.1.win32-py2.5.exe","hashes":{"sha256":"ef4ed886b4d2d664fab5c369543690cda04e04a3b7496d72e369ef5240a8af6b"},"provenance":null,"requires-python":null,"size":148474,"upload-time":"2014-02-06T16:40:57.772154Z","url":"https://files.pythonhosted.org/packages/65/ad/e7828797558dd5f209694b02ce079cd3b6beacf6a5175f38c1973c688494/psutil-0.4.1.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.1.win32-py2.6.exe","hashes":{"sha256":"aafe9e328ffc173b26c78897c168858da335c85d4a02d666ad68fe2fe14601d1"},"provenance":null,"requires-python":null,"size":280897,"upload-time":"2014-02-06T16:41:07.265101Z","url":"https://files.pythonhosted.org/packages/1e/a1/594bf54e7c4056bc5284023be97f67c930175b3329e086a4ed8966cb067a/psutil-0.4.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.1.win32-py2.7.exe","hashes":{"sha256":"cefad502010c78425190498efc54541ae7bbe9eaa73ef4000cf11b032d32a8bb"},"provenance":null,"requires-python":null,"size":280445,"upload-time":"2014-02-06T16:41:15.492817Z","url":"https://files.pythonhosted.org/packages/c6/c4/14807de009a1beab2426b537379a8b05b1d69fef1fde7e23581cc332cdb3/psutil-0.4.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.1.win32-py3.3.exe","hashes":{"sha256":"a35ce124b2dec01d7632627c9370f5202978fce89ae59235cf4d5e05bbd0e02b"},"provenance":null,"requires-python":null,"size":275668,"upload-time":"2014-02-06T16:41:23.498162Z","url":"https://files.pythonhosted.org/packages/38/7d/407f7586bccdeb80f5da82d8ebb98712bfcc7217e3d7c3fc61b3bba893f2/psutil-0.4.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.4.1.win32-py3.4.exe","hashes":{"sha256":"860a039b67ee015a6304e8d05e6dc5c2a447eca331b7d6c9d04f41d7f2fc3a66"},"provenance":null,"requires-python":null,"size":275635,"upload-time":"2014-02-06T16:41:33.515203Z","url":"https://files.pythonhosted.org/packages/c5/b7/d2662ebf114961766c9eb3d9b737cb5541060d35e65b542b0acef470221a/psutil-0.4.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.0.tar.gz","hashes":{"sha256":"b10d1c19b9d334bea9f025f3b82b5664d642d26ba32ae0e71e7e20c5a6b4164f"},"provenance":null,"requires-python":null,"size":118411,"upload-time":"2014-02-06T02:06:09.471218Z","url":"https://files.pythonhosted.org/packages/3f/eb/e4115c3ecc189fd345b9a50d521e2ff76990bfb12a91921e93ea6398feef/psutil-0.5.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.0.win-amd64-py2.7.exe","hashes":{"sha256":"5bf7a91fd57be3a63c6b9fa78da63c185b9be909ca49032f3ceda662628be74d"},"provenance":null,"requires-python":null,"size":319335,"upload-time":"2014-02-06T15:53:45.976879Z","url":"https://files.pythonhosted.org/packages/c5/cc/9bb29fdd60e347617c3a09f51a595941ddb4777fc1608068d174ced5bfdd/psutil-0.5.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.0.win-amd64-py3.3.exe","hashes":{"sha256":"0cd9872d0d675b0314d2d15513e8ff3693a417e4ebaa158fc1f8780cc611574c"},"provenance":null,"requires-python":null,"size":317930,"upload-time":"2014-02-06T15:53:57.230392Z","url":"https://files.pythonhosted.org/packages/37/d8/276629523e0477639b33a606f93c2fc2e9c4cafba76b3a4cd1ddc42dd307/psutil-0.5.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.0.win-amd64-py3.4.exe","hashes":{"sha256":"13f71acf1d2fe08db072d890280e985e8414080bcd525c13c66e0e8fbc917710"},"provenance":null,"requires-python":null,"size":318790,"upload-time":"2014-02-06T15:54:08.039630Z","url":"https://files.pythonhosted.org/packages/5f/bc/a026bbb10a4df4cf458b5d6feabc72a0017e6a4f472898280442ff3f9393/psutil-0.5.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.0.win32-py2.5.exe","hashes":{"sha256":"d6839532038b97792d90341465430520e58ea3d0956a1df710b3fdd733dde587"},"provenance":null,"requires-python":null,"size":156923,"upload-time":"2014-02-06T16:39:37.158716Z","url":"https://files.pythonhosted.org/packages/26/83/d1aed38bad84576e48b9ca6501edcf829ca3b48631c823736c36fa2b24de/psutil-0.5.0.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.0.win32-py2.6.exe","hashes":{"sha256":"be79349ecf091470ea4920e45b78914b67f17bec9c23e4bea4ec0ca5fee1feab"},"provenance":null,"requires-python":null,"size":289457,"upload-time":"2014-02-06T15:53:07.782148Z","url":"https://files.pythonhosted.org/packages/4e/b2/bfdeae58283e0fb09e2c6725c01b9429acbd15917c7ead91c96f2df37d05/psutil-0.5.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.0.win32-py2.7.exe","hashes":{"sha256":"e4fa8e965eb58a885e65a360f1a7ffc7ed476e9227a2de3726091d7560c694a8"},"provenance":null,"requires-python":null,"size":289002,"upload-time":"2014-02-06T15:53:16.212429Z","url":"https://files.pythonhosted.org/packages/2d/62/ed3c23fb8648a916460e95306d58f5ba42b3b20c68f2a55d1fc6396190a5/psutil-0.5.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.0.win32-py3.3.exe","hashes":{"sha256":"0380ece3eeb6f170ba156c58eab2fa36508600b1c7e10bc9b7c651f721236f38"},"provenance":null,"requires-python":null,"size":284089,"upload-time":"2014-02-06T15:53:25.963338Z","url":"https://files.pythonhosted.org/packages/59/3d/25912eb67f01af306d8ec035041c07daa7f783aafa5f4e1dd90de91c4849/psutil-0.5.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.0.win32-py3.4.exe","hashes":{"sha256":"d6a9e67de81fc0fc58c790e1355ddfbb8a0760963868a538f217ac2325a81581"},"provenance":null,"requires-python":null,"size":284045,"upload-time":"2014-02-06T15:53:35.675392Z","url":"https://files.pythonhosted.org/packages/38/74/e6c5bfb482f011d19bb1366cecf7d4f0b5c9c9b7664ef31e951cc4d0757b/psutil-0.5.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.1.tar.gz","hashes":{"sha256":"f660d244a08373f5e89633650970819a59463b37af1c9d205699fb0d0608986d"},"provenance":null,"requires-python":null,"size":118781,"upload-time":"2014-02-06T02:06:02.078066Z","url":"https://files.pythonhosted.org/packages/aa/06/6ebc13a14c0961d7bb8184da530448d8fc198465eb5ecd1ad36c761807d2/psutil-0.5.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.1.win-amd64-py2.7.exe","hashes":{"sha256":"eff5ed677b6bdb5d75f1de925df774d8029582eea17abc3bbd31b9885d3c649d"},"provenance":null,"requires-python":null,"size":319512,"upload-time":"2014-02-06T16:36:10.664265Z","url":"https://files.pythonhosted.org/packages/8a/26/15751b500afdd0aea141905d5ba5319c38be41e0ca55a374c02827d1a79c/psutil-0.5.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.1.win-amd64-py3.3.exe","hashes":{"sha256":"8a12d8e368bfccfe2074616cae2472dfb1e136a2aaaea5b7dbafdb390166b202"},"provenance":null,"requires-python":null,"size":318245,"upload-time":"2014-02-06T16:36:21.268821Z","url":"https://files.pythonhosted.org/packages/13/9d/49f6fb5e1f75b3d6815bbdbbeb1630f7299d6ff96f85836dbd27989780ae/psutil-0.5.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.1.win-amd64-py3.4.exe","hashes":{"sha256":"9d991aae6b993ac58b540f27b2dff71137db62253f5d7acc5ede1ec845fd6b0b"},"provenance":null,"requires-python":null,"size":319083,"upload-time":"2014-02-06T16:36:31.287414Z","url":"https://files.pythonhosted.org/packages/2c/fa/a0469c75acedccc5006b655434e96a8a6a490725a9ae8208faaf39c043f6/psutil-0.5.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.1.win32-py2.5.exe","hashes":{"sha256":"ee32be6667bb69ebefc2fb5e23d115d98e307b5246c01ef50e45cfd8ad824d07"},"provenance":null,"requires-python":null,"size":157149,"upload-time":"2014-02-06T16:35:24.026975Z","url":"https://files.pythonhosted.org/packages/2e/30/9ed6283c7f1a716a3bf76a063b278ace2f89d1a8b2af9d35080dc2a0319b/psutil-0.5.1.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.1.win32-py2.6.exe","hashes":{"sha256":"f434f546d73366bff6283db907105b2607976ff9be67f2b5a7b35533e5f27219"},"provenance":null,"requires-python":null,"size":289555,"upload-time":"2014-02-06T16:35:32.000842Z","url":"https://files.pythonhosted.org/packages/eb/d9/55f5d02e42dc6c73f66c7cfc99bc3bf97cf10e9f259dbea69e123e0ef459/psutil-0.5.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.1.win32-py2.7.exe","hashes":{"sha256":"45acf090d65f2f47f3b1444b056cafb1b1ca70a8e266ddf10422734fb0ada897"},"provenance":null,"requires-python":null,"size":289095,"upload-time":"2014-02-06T16:35:40.182115Z","url":"https://files.pythonhosted.org/packages/81/25/cbbb80d1957c28dffb9f20c1eaf349d5642215d7ca15ec5b7015b6a510c8/psutil-0.5.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.1.win32-py3.3.exe","hashes":{"sha256":"9a20a99b6727334c8cc3a10ecc556d2f15a1e92efef97670ed022b86e09df36b"},"provenance":null,"requires-python":null,"size":284290,"upload-time":"2014-02-06T16:35:50.441013Z","url":"https://files.pythonhosted.org/packages/73/a2/ab4f2fa7ba6f8fc51d9cc9a0bb5ec269860a34574d391e39d20c2dca7b56/psutil-0.5.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.5.1.win32-py3.4.exe","hashes":{"sha256":"d547e164013a4d78909cbac0c176d65aa51c06de2fb888c63b738906f5ccc105"},"provenance":null,"requires-python":null,"size":284247,"upload-time":"2014-02-06T16:36:00.155604Z","url":"https://files.pythonhosted.org/packages/7f/8b/c9abd21cdc79b70744e169b98be06f0828687dfcac6468ed87455099f88d/psutil-0.5.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.0.tar.gz","hashes":{"sha256":"e918763243371f69bc98b601769df7337e196029dcdb797224f0ede474e17b94"},"provenance":null,"requires-python":null,"size":130803,"upload-time":"2014-02-06T02:05:55.032752Z","url":"https://files.pythonhosted.org/packages/3c/9f/0de622fc62e74f4c154656440c02c8a24a01a997d605a744272eb0d93742/psutil-0.6.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.0.win-amd64-py2.7.exe","hashes":{"sha256":"1792be8fca4b3f88553e1c5cc197e18932c3da9b969ce7f545b741913597d600"},"provenance":null,"requires-python":null,"size":322153,"upload-time":"2014-02-06T16:34:24.006415Z","url":"https://files.pythonhosted.org/packages/5f/fd/fc06aaf69ad438d1354312c728791a1f571b2c59a49b411128911abf647e/psutil-0.6.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.0.win-amd64-py3.3.exe","hashes":{"sha256":"084a1cbeca8e10f0cbba4b9d3f7d3136cb1d03997a868eea334401f82105207a"},"provenance":null,"requires-python":null,"size":320646,"upload-time":"2014-02-06T16:34:33.535927Z","url":"https://files.pythonhosted.org/packages/70/5e/b1d4a1d5238497d48112bc27e3448d05a0acb6c8fcd537565724d916e7c2/psutil-0.6.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.0.win-amd64-py3.4.exe","hashes":{"sha256":"e7233e45ed7505efe46309978edd3e8a1cb4160400c3535512be2e4114615bb9"},"provenance":null,"requires-python":null,"size":321547,"upload-time":"2014-02-06T16:34:44.543150Z","url":"https://files.pythonhosted.org/packages/47/ad/4139c3eaa2ee1da71d071c9b49e8a04ff23f2c71f6fdecc11850b0e7343e/psutil-0.6.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.0.win32-py2.5.exe","hashes":{"sha256":"469628dea02a7d91837e05777cb66bfcdc2e1639cc8607ed93f19679c4e42cef"},"provenance":null,"requires-python":null,"size":160485,"upload-time":"2014-02-06T16:33:39.286987Z","url":"https://files.pythonhosted.org/packages/10/3a/a0136c299b74224be3b0735812a1544e6962d7c61af41adfcc400791c684/psutil-0.6.0.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.0.win32-py2.6.exe","hashes":{"sha256":"a628ad11fefc16d6dd144a8d29a03ac379debcf6c2e4b576e6ee2b2677c15836"},"provenance":null,"requires-python":null,"size":292261,"upload-time":"2014-02-06T16:33:47.696744Z","url":"https://files.pythonhosted.org/packages/2b/79/eec1c63d3ea968cc9754871a1b9d17d50bfc24136ecac415f701b1d240ea/psutil-0.6.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.0.win32-py2.7.exe","hashes":{"sha256":"71197523bf8acb49e3e909ea945cc6e77a21693428db2f169e33348988c1bb11"},"provenance":null,"requires-python":null,"size":291811,"upload-time":"2014-02-06T16:33:56.338977Z","url":"https://files.pythonhosted.org/packages/e9/64/fe90d0f1ba2dff8ad2511423720aed3569cfe7b5dbb95ede64af25b77e84/psutil-0.6.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.0.win32-py3.3.exe","hashes":{"sha256":"1d7df98f4532e76685fabbd82e9502c91be9a60e4dfdfd5a19c2152936b7d198"},"provenance":null,"requires-python":null,"size":286963,"upload-time":"2014-02-06T16:34:04.390605Z","url":"https://files.pythonhosted.org/packages/d6/d7/91226f8635d3850917b64e94ca4903910a60739ee7c930ef9678d93638eb/psutil-0.6.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.0.win32-py3.4.exe","hashes":{"sha256":"da568edc3b8ec4be5b8cee54851289eb4c24cbaa8ea165858e9bfc279269bf22"},"provenance":null,"requires-python":null,"size":286905,"upload-time":"2014-02-06T16:34:14.322283Z","url":"https://files.pythonhosted.org/packages/56/67/533833832596e5f8c1fb51d3f94cd110d688579be7af46996cf96ad890e7/psutil-0.6.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.1.tar.gz","hashes":{"sha256":"52eba795281cdd1079f13ded6a851f6d029551ddf552eadc9a2ee3eb26fe994d"},"provenance":null,"requires-python":null,"size":131473,"upload-time":"2014-02-06T02:05:47.971617Z","url":"https://files.pythonhosted.org/packages/4f/e6/989cb0b2f7f0ebe3ab0e7144b78db17387810f1526e98498be96fb755fa9/psutil-0.6.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.1.win-amd64-py2.7.exe","hashes":{"sha256":"566bcec389a77279e10335eb5d0021eb968445720496b70b46d208dbbf8e49a1"},"provenance":null,"requires-python":null,"size":322415,"upload-time":"2014-02-06T16:32:41.593785Z","url":"https://files.pythonhosted.org/packages/9a/bb/cb19a41fa75a2205ac2d337be45183f879aa77e60bbb19387c872c81d451/psutil-0.6.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.1.win-amd64-py3.3.exe","hashes":{"sha256":"fde5e57f9b51057998cce5a030ad481f826832b26cc2a6490d60317c9024724c"},"provenance":null,"requires-python":null,"size":320911,"upload-time":"2014-02-06T16:32:50.930654Z","url":"https://files.pythonhosted.org/packages/7d/d1/9e75848da16a5d165e3918540418f3052e6409357bcce91a7342b22d674f/psutil-0.6.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.1.win-amd64-py3.4.exe","hashes":{"sha256":"402475d1a93306ac025070da9628a0ca0e0d253b0755bf13131318a39aaeea8e"},"provenance":null,"requires-python":null,"size":321810,"upload-time":"2014-02-06T16:33:01.995635Z","url":"https://files.pythonhosted.org/packages/95/82/1f52f8aaa37e07da54da4468c031f59717d3f40cd82b6faaf8f203d84bd7/psutil-0.6.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.1.win32-py2.5.exe","hashes":{"sha256":"3f5b8afde564c563cd6fa28ac14ed020d91d654ef1efd6c0e1672282fcde8e65"},"provenance":null,"requires-python":null,"size":160754,"upload-time":"2014-02-06T16:31:55.533884Z","url":"https://files.pythonhosted.org/packages/5b/3f/10449a8a3dfb809fbeae3d853453aafb36376d51f33c46e02b5f0723c620/psutil-0.6.1.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.1.win32-py2.6.exe","hashes":{"sha256":"1c1763ff214042e7be75fd6eb336322a47193342cddfc70b8547f086968e024c"},"provenance":null,"requires-python":null,"size":292530,"upload-time":"2014-02-06T16:32:04.859349Z","url":"https://files.pythonhosted.org/packages/3e/7a/5d53c99ec66ea86548d95acaf98b32f1cbb761cf2e1fc8ee62d0838f8e24/psutil-0.6.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.1.win32-py2.7.exe","hashes":{"sha256":"e411f2374994663ad35bc21afe4ff9003e3675e23b07a2a60b57c760da0c5f3f"},"provenance":null,"requires-python":null,"size":292082,"upload-time":"2014-02-06T16:32:13.267526Z","url":"https://files.pythonhosted.org/packages/4a/40/e43e412c55f66b57aee3a418f47b05e7bb92db83df93c0c93b00f11f1357/psutil-0.6.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.1.win32-py3.3.exe","hashes":{"sha256":"03ea1f851920d6ddc286c1940ebd167ed92c1cb7a870f1789510c21c6fa6b7bc"},"provenance":null,"requires-python":null,"size":287227,"upload-time":"2014-02-06T16:32:22.314264Z","url":"https://files.pythonhosted.org/packages/07/12/1ce6196592e01965a375b307a57f68e90488a49a56a647f17ba027382448/psutil-0.6.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.6.1.win32-py3.4.exe","hashes":{"sha256":"095107d63b176fed2f4a44de3a4d19e8aa6291de0c3ee5e398272c9b72bf2254"},"provenance":null,"requires-python":null,"size":287169,"upload-time":"2014-02-06T16:32:32.819904Z","url":"https://files.pythonhosted.org/packages/f4/88/d20f7eefa6b8cc59f9320c52bf561ba52baea2b9b36568b198bca0b3548d/psutil-0.6.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.0.tar.gz","hashes":{"sha256":"95089802017ee629b84332deeb367f7f775b42e827ee283d46b7e99d05120d71"},"provenance":null,"requires-python":null,"size":138681,"upload-time":"2014-02-06T02:05:40.485455Z","url":"https://files.pythonhosted.org/packages/0b/8c/aeb6acf5a4610f8d5bb29ade04081d8672c2294c3cefa1f0422c6398bc66/psutil-0.7.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.0.win-amd64-py2.7.exe","hashes":{"sha256":"add60c89c13443617c83116fab34a3c6e4f32ee8b102ff6ffb4cd20ef6f511c3"},"provenance":null,"requires-python":null,"size":325666,"upload-time":"2014-02-06T16:30:51.588803Z","url":"https://files.pythonhosted.org/packages/38/8e/f3023be1a2268b5ee3a20e05ec8997a95c987f6f2b7dadad837674d562e8/psutil-0.7.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.0.win-amd64-py3.3.exe","hashes":{"sha256":"520a43be7fcd21d63c02039cdd7ebfe959b9fa9a865ba9aab6b0194a85184250"},"provenance":null,"requires-python":null,"size":324143,"upload-time":"2014-02-06T16:31:01.226228Z","url":"https://files.pythonhosted.org/packages/7b/03/d6bff7ff8570f3521a1d7949f03741210f11ba5ba807789d37e185a0a780/psutil-0.7.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.0.win-amd64-py3.4.exe","hashes":{"sha256":"0e78c6b441e659d922c5fddd5d5a36a368a61192873a0600ba23171c14dc2a6d"},"provenance":null,"requires-python":null,"size":325001,"upload-time":"2014-02-06T16:31:12.826305Z","url":"https://files.pythonhosted.org/packages/ce/8a/95e7c8b343a92137d82e4635fcb3f5c69c29e2a84ce4d51f2d5d86020b1e/psutil-0.7.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.0.win32-py2.5.exe","hashes":{"sha256":"532b918d8d2df60c20c69c1f764b9b16373ea22c32595fe801e3cc9b1aa5adfb"},"provenance":null,"requires-python":null,"size":163763,"upload-time":"2014-02-06T16:30:02.815062Z","url":"https://files.pythonhosted.org/packages/9e/6f/a946eeb3c6b8848fcf6d21c52884e2639e7d4393b51f330d4555ec177e83/psutil-0.7.0.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.0.win32-py2.6.exe","hashes":{"sha256":"21e1d7880579ef7eea28547c737923eaeee53389d51fa971d9f70bf89e9759ee"},"provenance":null,"requires-python":null,"size":295425,"upload-time":"2014-02-06T16:30:11.405843Z","url":"https://files.pythonhosted.org/packages/14/e9/ac4865772c55a3c09c85d987ac2a0e679c31e897d536f389485019c29450/psutil-0.7.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.0.win32-py2.7.exe","hashes":{"sha256":"bc36a7350b89e2ada06691583f4c4c2f99988fe6fa710d4f8e4143bed9f9f8bf"},"provenance":null,"requires-python":null,"size":294933,"upload-time":"2014-02-06T16:30:20.866887Z","url":"https://files.pythonhosted.org/packages/12/1c/053e0d5485a39c055e92b845b6311ab54887a091836a3985369cfffa9e53/psutil-0.7.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.0.win32-py3.3.exe","hashes":{"sha256":"9ce0d7a8cedde996eaae1eda3db9b473d441bd6c797b36a166d7b7a70dd4650e"},"provenance":null,"requires-python":null,"size":290006,"upload-time":"2014-02-06T16:30:29.860768Z","url":"https://files.pythonhosted.org/packages/73/19/8009828f3cfcf529570822e52988b95ac1c25c90ba4fb96ae570b5c25e66/psutil-0.7.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.0.win32-py3.4.exe","hashes":{"sha256":"6fdeca414a470d156ebdf1b6e4e9ca0fd9eb1b53e8d9a0cbb7d4a69d31f68bdc"},"provenance":null,"requires-python":null,"size":289986,"upload-time":"2014-02-06T16:30:39.817575Z","url":"https://files.pythonhosted.org/packages/42/7d/8e159acbf98eed98b778c244287b295f06411ac9e1903d2d358ba48b1d36/psutil-0.7.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.1.tar.gz","hashes":{"sha256":"5236f649318a06dcff8b86947c888d4510abce1783923aa5455b2d62df7204c7"},"provenance":null,"requires-python":null,"size":138525,"upload-time":"2014-02-06T02:05:32.811951Z","url":"https://files.pythonhosted.org/packages/8c/75/1eeb93df943b70c2e18bf412b32d18af3577da831f2bfe8c7d29f8853a67/psutil-0.7.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.1.win-amd64-py2.7.exe","hashes":{"sha256":"47fc6887ec86bd7a422326dfc77e2b43fcbc71e498194f86088bb2268c2372b7"},"provenance":null,"requires-python":null,"size":325663,"upload-time":"2014-02-06T16:29:31.935519Z","url":"https://files.pythonhosted.org/packages/2d/ab/8fde8a7358a21bc2488a5d21a24879ce3cc162d2a99c56f9599c7d759c75/psutil-0.7.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.1.win-amd64-py3.3.exe","hashes":{"sha256":"c00ec6a0e061e31687851c06d542500fd47b0ed415818e0da61c294f2796572b"},"provenance":null,"requires-python":null,"size":324140,"upload-time":"2014-02-06T16:29:41.403650Z","url":"https://files.pythonhosted.org/packages/35/b1/8506f2e78974da833b20b162d70b7e38ac3ab722adf18c03a3e428c9d9c4/psutil-0.7.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.1.win-amd64-py3.4.exe","hashes":{"sha256":"a5238a16cd85d4f3240dfca8ad94cb0bfe8643acb5c21d839a53caa86d8a23ab"},"provenance":null,"requires-python":null,"size":324998,"upload-time":"2014-02-06T16:29:50.929853Z","url":"https://files.pythonhosted.org/packages/cc/29/d00e1011e6db097ffac31c2876fdba51fd1fc251ee472ad6219095739a86/psutil-0.7.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.1.win32-py2.5.exe","hashes":{"sha256":"facf21db78120afb79ec51685043a5f84e897a84a7678db4381c0e377a481364"},"provenance":null,"requires-python":null,"size":163764,"upload-time":"2014-02-06T16:28:44.337888Z","url":"https://files.pythonhosted.org/packages/06/d8/a34c090687fa4c09bee5b5a06a762eb1af134d847b907d3e63c58e5d2cba/psutil-0.7.1.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.1.win32-py2.6.exe","hashes":{"sha256":"709d2e851c161f1ffad6bd7ed925ee90229dbd2067fbc26126c11d473075d7b7"},"provenance":null,"requires-python":null,"size":295427,"upload-time":"2014-02-06T16:28:53.964081Z","url":"https://files.pythonhosted.org/packages/4d/8a/d51e550aa0928ab02e4f6b2531453b2e63c8877e30543e342c8a35c90d4d/psutil-0.7.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.1.win32-py2.7.exe","hashes":{"sha256":"ee218aa12ee3af563833cf0bf109b15382261a0760a1ce636bf9e26e630f833e"},"provenance":null,"requires-python":null,"size":294930,"upload-time":"2014-02-06T16:29:03.098211Z","url":"https://files.pythonhosted.org/packages/3c/4c/d46a8bc865e58b30dcb160772667abf42f854ec4910ee1100e79961035ce/psutil-0.7.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.1.win32-py3.3.exe","hashes":{"sha256":"fdf22a72f891a45b8a9ebca85b50a055a4ba7a8c05b98f93e1387b8ee88a4562"},"provenance":null,"requires-python":null,"size":290003,"upload-time":"2014-02-06T16:29:12.556491Z","url":"https://files.pythonhosted.org/packages/1a/cf/98fdaf08279cd754414ab6723bbb6bd55d890624b766d64b21c35379d865/psutil-0.7.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-0.7.1.win32-py3.4.exe","hashes":{"sha256":"f675a6346e5bd02469b043afff2db1b8e0b180e8a6d1ca94d44d23f26f60e68f"},"provenance":null,"requires-python":null,"size":289983,"upload-time":"2014-02-06T16:29:21.732021Z","url":"https://files.pythonhosted.org/packages/f4/f9/79ac18809795f53197fb5b6bd452d2b52f62e5bff6a66d9b813077a85eb9/psutil-0.7.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.0.tar.gz","hashes":{"sha256":"af776ebeaf2420709b1ab741f664c048dcfc2890e3e7d151ae868fbdc3d47920"},"provenance":null,"requires-python":null,"size":156516,"upload-time":"2014-02-06T02:05:25.754044Z","url":"https://files.pythonhosted.org/packages/ed/7e/dd4062bfbe9b735793a5e63a38d83ba37292c4ada3615fe54403c54e3260/psutil-1.0.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.0.win-amd64-py2.7.exe","hashes":{"sha256":"7868ef2ecc1d847620d99b40f8105b6d5303784abbde34da2388b2c18e73a3d2"},"provenance":null,"requires-python":null,"size":327529,"upload-time":"2014-02-06T16:25:06.308578Z","url":"https://files.pythonhosted.org/packages/11/46/8a6581c14d644f747ea66a12c60ffebebd4f8a77af62192aeb20cc16ae17/psutil-1.0.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.0.win-amd64-py3.3.exe","hashes":{"sha256":"cdced3409a241b9d357b82c72d00ece49602cdc3f694ed988a952761367df560"},"provenance":null,"requires-python":null,"size":326023,"upload-time":"2014-02-06T16:25:15.281576Z","url":"https://files.pythonhosted.org/packages/03/a4/aa6592e676f75705b77df8e319f6aa35cf08a662fb0cfaa34434d273d7c9/psutil-1.0.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.0.win-amd64-py3.4.exe","hashes":{"sha256":"ef6974ac18407fb15430cd7b81aecc2f3666e75ca83fe3a66848c4c5c086e0a3"},"provenance":null,"requires-python":null,"size":326915,"upload-time":"2014-02-06T16:25:27.390929Z","url":"https://files.pythonhosted.org/packages/5b/c1/003f0071d1e2a4dfe12ac2d66ac14f5a5a93049544c11ac9c78d83d85318/psutil-1.0.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.0.win32-py2.5.exe","hashes":{"sha256":"d8213feaa4c560c61514cea4e6a7eec83f6df83694bb97b06d009e919f60c48f"},"provenance":null,"requires-python":null,"size":165576,"upload-time":"2014-02-06T16:24:22.454259Z","url":"https://files.pythonhosted.org/packages/d6/28/5753ba13863c26d8a8fef685690b539a4652f1c4929d13d482eb4d691d99/psutil-1.0.0.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.0.win32-py2.6.exe","hashes":{"sha256":"7571d74d351bba723b28666a13da43c07bb3568543be3f9f7404af2b8c1ecf3a"},"provenance":null,"requires-python":null,"size":297205,"upload-time":"2014-02-06T16:24:31.483662Z","url":"https://files.pythonhosted.org/packages/ad/8c/37599876ea078c7c2db10d746f0e0aa805abcacbab96d1cf5dfb0465c40b/psutil-1.0.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.0.win32-py2.7.exe","hashes":{"sha256":"3f311d4f060b83d7cd0068dbad867ad17e699d3c335e494dc31db5380154fdf4"},"provenance":null,"requires-python":null,"size":296787,"upload-time":"2014-02-06T16:24:39.921427Z","url":"https://files.pythonhosted.org/packages/99/4e/8c1f5db3e90dbdbde891c34fd2496805a93aa279433bac716098efad1b4c/psutil-1.0.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.0.win32-py3.3.exe","hashes":{"sha256":"acf6cbc2408869b2843d406a802b6895969a358c762b5ab3e6f5afdd0c918faf"},"provenance":null,"requires-python":null,"size":291856,"upload-time":"2014-02-06T16:24:48.963293Z","url":"https://files.pythonhosted.org/packages/5b/f7/0bad3fc8ff5f226fa933a847b5fb3355562421f0456348747cf18f7137f5/psutil-1.0.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.0.win32-py3.4.exe","hashes":{"sha256":"191c2e6953426c861d7e100d48a5b522307ffc3de7d9e7bc8509aa45fb3de60c"},"provenance":null,"requires-python":null,"size":291810,"upload-time":"2014-02-06T16:24:57.589702Z","url":"https://files.pythonhosted.org/packages/0a/50/18ebd254b6662c98a8310983fff6795c7ac303d25188fd727888ca1c5211/psutil-1.0.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.1.tar.gz","hashes":{"sha256":"ba4c81622434836f6645e8d04e221ca5b22a9bd508c29989407f116b917be5b3"},"provenance":null,"requires-python":null,"size":156516,"upload-time":"2014-02-06T02:05:16.665325Z","url":"https://files.pythonhosted.org/packages/94/50/7c9e94cf6cdbf4e4e41d2e318094c2b0d58d3bb9196017fb6e4897adf277/psutil-1.0.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.1.win-amd64-py2.7.exe","hashes":{"sha256":"2d6705cf53a7474b992d0d5beac4697cdd37f2c24098d4e1b5bb35a83b70b27e"},"provenance":null,"requires-python":null,"size":327529,"upload-time":"2014-02-06T16:20:20.120751Z","url":"https://files.pythonhosted.org/packages/a9/28/ac3c9da11fbe1ae3ddefb2cb6b410c8ad701f39d86489931e87c871cc4d1/psutil-1.0.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.1.win-amd64-py3.3.exe","hashes":{"sha256":"6194670fcbddec715846fa9beca4450b4419423b2d466894da2fa1915fc7b712"},"provenance":null,"requires-python":null,"size":326023,"upload-time":"2014-02-06T16:20:43.401705Z","url":"https://files.pythonhosted.org/packages/97/30/094bb08295e15e05a266fcb15c116912f643fbb4dd8090ec64aca801ea72/psutil-1.0.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.1.win-amd64-py3.4.exe","hashes":{"sha256":"5cf4ddea8dd21b99b90656dafa2d28c32fa9c159ab30580782b65b3b615c62a7"},"provenance":null,"requires-python":null,"size":326915,"upload-time":"2014-02-06T16:20:52.673334Z","url":"https://files.pythonhosted.org/packages/e8/86/e72c22699491573508e85b226f1782eab840b713cd6b8d42655c5329324c/psutil-1.0.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.1.win32-py2.5.exe","hashes":{"sha256":"fadf7625c309bc5f6e6d3b9d42836491efabbfda1834db7166775d705db420bf"},"provenance":null,"requires-python":null,"size":165580,"upload-time":"2014-02-06T16:04:23.085098Z","url":"https://files.pythonhosted.org/packages/a5/40/d307abd2ee3015e38cbeeac18c6b0f091cdcac496688f4d2c286145fdc5f/psutil-1.0.1.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.1.win32-py2.6.exe","hashes":{"sha256":"84a257b4adea431473089ea4b798bea98ce64452587ba7b71856e150d88e9213"},"provenance":null,"requires-python":null,"size":297208,"upload-time":"2014-02-06T15:59:31.669293Z","url":"https://files.pythonhosted.org/packages/75/3f/c10ca801aff50ce6ea84f71ce69a32de1b4a1f0439ada94adeccd36e1afd/psutil-1.0.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.1.win32-py2.7.exe","hashes":{"sha256":"4b5047bb4055d48f718bbb5a457c9b1c1c06776ca6353cb310b471c38d51324d"},"provenance":null,"requires-python":null,"size":296787,"upload-time":"2014-02-06T15:59:41.621679Z","url":"https://files.pythonhosted.org/packages/15/6c/944089b8dd730314a8eec9faa4d7d383f31490130e9e0534c882173e2eb6/psutil-1.0.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.1.win32-py3.3.exe","hashes":{"sha256":"3553a27b4d0e7552d0d863c017e152a88cb689d2090b39e2bca68db7fc27e8b2"},"provenance":null,"requires-python":null,"size":291856,"upload-time":"2014-02-06T15:59:51.241604Z","url":"https://files.pythonhosted.org/packages/4c/51/eb8b12a0124050492faad7fe3e695cb44e4ec5495a64a8570abf6e7cddc3/psutil-1.0.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.0.1.win32-py3.4.exe","hashes":{"sha256":"d3ab1b882647d66099adefa8ff780c4144bf8df1bb97bc31c55cbd999807cae8"},"provenance":null,"requires-python":null,"size":291810,"upload-time":"2014-02-06T16:00:00.814707Z","url":"https://files.pythonhosted.org/packages/4d/2a/33063e4a674e3be38d77fbc6e5e988f0e0323cd000cb7eb60f9300493631/psutil-1.0.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.0.tar.gz","hashes":{"sha256":"31b4b411d3f6960d26dac1f075709ff6d36f60216bf1f2b47fc4dc62a2d0aa6f"},"provenance":null,"requires-python":null,"size":163785,"upload-time":"2013-09-28T09:48:01.746815Z","url":"https://files.pythonhosted.org/packages/f6/71/1f9049fc7936f7f48f0553abd8bf8394f4ad2d9fb63a881b3a653b67abd6/psutil-1.1.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.0.win-amd64-py2.7.exe","hashes":{"sha256":"ef250fbe783c0b31d9574d06324abd78ebf23c75981a8d0f9319d080435aa056"},"provenance":null,"requires-python":null,"size":305459,"upload-time":"2013-09-28T09:55:10.368138Z","url":"https://files.pythonhosted.org/packages/28/ff/e0dcc8e0194817191817ff02926a734a51d24a91e1cfc5c47be8c1e8508c/psutil-1.1.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.0.win-amd64-py3.2.exe","hashes":{"sha256":"a95bf68f2189b7cd3f22558b12a989f368335c905cbd3045ca315514538b3115"},"provenance":null,"requires-python":null,"size":306085,"upload-time":"2013-09-28T09:55:36.198407Z","url":"https://files.pythonhosted.org/packages/a5/be/6f3b8f8ff89eaf480f59bbcd3d18c10ee0206891aa5fb4a25280c031801c/psutil-1.1.0.win-amd64-py3.2.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.0.win32-py2.4.exe","hashes":{"sha256":"bc53be7f65ccb8a530e0c496a8db3ecd78b57b1faa443e43e228df327cf26899"},"provenance":null,"requires-python":null,"size":142269,"upload-time":"2013-09-28T09:51:43.685077Z","url":"https://files.pythonhosted.org/packages/ec/60/0a9a96611c77866a2c4140159a57c2702523ebc5371eaa24e0353d88ac2c/psutil-1.1.0.win32-py2.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.0.win32-py2.5.exe","hashes":{"sha256":"4a587008d6aae91fd1d3fee21141a27b4a2978fff75c80e57a37678a63a1cfcc"},"provenance":null,"requires-python":null,"size":142249,"upload-time":"2013-09-28T09:52:12.373832Z","url":"https://files.pythonhosted.org/packages/b5/16/dd5a768773cb017c4a66d69ec49abdd33df831215418e4c116639ed41afb/psutil-1.1.0.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.0.win32-py2.6.exe","hashes":{"sha256":"be853a268f0f3c3336c9bb648253db85ef2024213b6fc199f84b89643b74b130"},"provenance":null,"requires-python":null,"size":276570,"upload-time":"2013-09-28T09:53:51.099422Z","url":"https://files.pythonhosted.org/packages/cc/e0/adb41ece0bb2c27d75a41d98bd8f9802475d5d488df7d4905229596c0a70/psutil-1.1.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.0.win32-py3.2.exe","hashes":{"sha256":"dfdce701cfc7799dc1964debb486907b447fbdb361e06be786a9a5e11a1d5309"},"provenance":null,"requires-python":null,"size":275628,"upload-time":"2013-09-28T09:54:16.430842Z","url":"https://files.pythonhosted.org/packages/8f/c9/231df338b0cef518dbde2f98c1e0bb81142dc7dbec4a39267bb60dde1462/psutil-1.1.0.win32-py3.2.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.0.win32-py3.3.exe","hashes":{"sha256":"b4106508ea96ad24e1102de2a31a99c557ee608abab15d978bfe8d0f626e518c"},"provenance":null,"requires-python":null,"size":270259,"upload-time":"2013-09-28T09:54:43.666687Z","url":"https://files.pythonhosted.org/packages/a7/03/80f426e186dfdb202128c78b421b3ba34de461c32f6ed2c04041fc548d7b/psutil-1.1.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.1.tar.gz","hashes":{"sha256":"a5201e4c2a9b57e9e5d8de92b3a4006d093eedd9b56915b8279f365aaedd0f48"},"provenance":null,"requires-python":null,"size":165467,"upload-time":"2013-10-07T22:38:06.357263Z","url":"https://files.pythonhosted.org/packages/8d/0d/1a4bfc94f9cc783a510b3fc7efd5a2ef39858b3a2b6ab40a094a1ca8a54d/psutil-1.1.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.1.win-amd64-py2.7.exe","hashes":{"sha256":"b8ccaef0a96d7ef40eda7493ffcaa3c5e2d63f531e090c3567e4198b38ca8e33"},"provenance":null,"requires-python":null,"size":305937,"upload-time":"2013-10-07T22:43:47.570605Z","url":"https://files.pythonhosted.org/packages/5a/b8/66489689c8751acd67bdbc8f37534176a14b63f74e74a229c30b230c8a18/psutil-1.1.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.1.win-amd64-py3.2.exe","hashes":{"sha256":"fdabf11b316b0c406709aabb76f6c3cfdeb448c10ed38193786110f13e5c1248"},"provenance":null,"requires-python":null,"size":306563,"upload-time":"2013-10-07T22:44:09.209012Z","url":"https://files.pythonhosted.org/packages/e3/2d/cd5c620f9e5cb090eea573b2dc16574e746529347b6133b2f0b6e686d917/psutil-1.1.1.win-amd64-py3.2.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.1.win32-py2.4.exe","hashes":{"sha256":"74bb1ccf0b28a914bb9d303642c08450ea6c5876851eb71e17f24b3d8672ca7d"},"provenance":null,"requires-python":null,"size":142751,"upload-time":"2013-10-07T22:45:21.036212Z","url":"https://files.pythonhosted.org/packages/28/9a/b83f884add09296894a1223c9c404cb57155c1c4317d318abf8c170e07b5/psutil-1.1.1.win32-py2.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.1.win32-py2.5.exe","hashes":{"sha256":"8e52d5b6af64e3eefbda30e0b16c9f29434ef6f79ea10b6fbd4520a6fbdb2481"},"provenance":null,"requires-python":null,"size":142731,"upload-time":"2013-10-07T22:41:32.428090Z","url":"https://files.pythonhosted.org/packages/8b/80/c41382a4f650f47a37300411169b7b248acf0b0925eb92cb22286362c3df/psutil-1.1.1.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.1.win32-py2.6.exe","hashes":{"sha256":"1e3a17b2a2f2bc138774f9b4b5ff52767f65ffb6349f9e05eb24f243c550ce1b"},"provenance":null,"requires-python":null,"size":277048,"upload-time":"2013-10-07T22:41:54.149373Z","url":"https://files.pythonhosted.org/packages/16/cd/25a3b9af88d130dd1084acab467b30996884219afc0a1e989d2a015ea54b/psutil-1.1.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.1.win32-py2.7.exe","hashes":{"sha256":"2e7691ddbde94b1ec7bb5b4cd986b2f763efc6be5b122dd499e156c9802a195b"},"provenance":null,"requires-python":null,"size":276822,"upload-time":"2013-10-07T22:42:18.407145Z","url":"https://files.pythonhosted.org/packages/42/78/eeacb1210abbe15cf06b9810e84afeabae1f9362abe389e8d5ca2c19df43/psutil-1.1.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.1.win32-py3.2.exe","hashes":{"sha256":"01276bd695b5cebc7bea7c97814713b0f3030861e30be88a60d40c9daf3d529c"},"provenance":null,"requires-python":null,"size":276106,"upload-time":"2013-10-07T22:42:41.551135Z","url":"https://files.pythonhosted.org/packages/92/14/90b9a4690f04ef1aab89a97a7b5407708f56785ccc264d9f9ce372feaea4/psutil-1.1.1.win32-py3.2.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.1.win32-py3.3.exe","hashes":{"sha256":"86ce0cfa7a95c7c1bc3e2f5358e446b01fc2be5f0c879c8def21e7ede9dc77de"},"provenance":null,"requires-python":null,"size":270733,"upload-time":"2013-10-07T22:43:07.069809Z","url":"https://files.pythonhosted.org/packages/a7/64/ba4601de7df6130c27f42bcec9f11da4ea905eda26d2f5a41efdb481f377/psutil-1.1.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.2.tar.gz","hashes":{"sha256":"adeb1afcb46327bed6603aa8981dce863f052043a52f003e2742ec7c3739677a"},"provenance":null,"requires-python":null,"size":165709,"upload-time":"2013-10-22T18:13:09.038583Z","url":"https://files.pythonhosted.org/packages/e4/b1/34a4bd75027d08c8db4f6301d6562e333c8d9131dca08b7f76f05aeae00a/psutil-1.1.2.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.2.win-amd64-py2.7.exe","hashes":{"sha256":"4c9e952a7faf50f11fb8bcd6c12c952b063664d9327957c9f6abd498e6ef3bc8"},"provenance":null,"requires-python":null,"size":306157,"upload-time":"2013-10-22T18:16:38.181306Z","url":"https://files.pythonhosted.org/packages/f9/df/437db01296118d668cf654f097ad2b1c341291ba5dc4b5eb80f0a0a40c52/psutil-1.1.2.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.2.win-amd64-py3.2.exe","hashes":{"sha256":"6e8ed376b63b15b09a94eff7998dae1f8506bd174d795e509cc735f875fddabe"},"provenance":null,"requires-python":null,"size":306782,"upload-time":"2013-10-22T18:17:05.663884Z","url":"https://files.pythonhosted.org/packages/c9/d0/1e413f0258d02bf77bc1d94002d041b8853584369e4af039cd5cf89e3270/psutil-1.1.2.win-amd64-py3.2.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.2.win32-py2.4.exe","hashes":{"sha256":"ecc5ab4537259db7254c6f6dc7b7cb5a7f398c322a01612c272a8222696334a8"},"provenance":null,"requires-python":null,"size":142969,"upload-time":"2013-10-22T18:13:39.868851Z","url":"https://files.pythonhosted.org/packages/a7/63/fd5770ec4fe87d30bd836989d314b85662c775a52dbd017747fc69fe8f0e/psutil-1.1.2.win32-py2.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.2.win32-py2.5.exe","hashes":{"sha256":"e05d83d6b53ea24333c8b04d329e28ff11ecad75945f371ff5ce7f785df36aee"},"provenance":null,"requires-python":null,"size":142950,"upload-time":"2013-10-22T18:14:00.511107Z","url":"https://files.pythonhosted.org/packages/8d/e1/22650079452725e44ec790c3e75282f4d341f359b213b2afc7f2ada46930/psutil-1.1.2.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.2.win32-py2.6.exe","hashes":{"sha256":"f06ee6e1508a12afcfed04a4022ded9f872e2a964a62bd86617ece943d89ab01"},"provenance":null,"requires-python":null,"size":277266,"upload-time":"2013-10-22T18:14:33.903116Z","url":"https://files.pythonhosted.org/packages/3e/a4/5177488368f230acd4708a117c0820fb16843521e2a7a492078a2335bb9f/psutil-1.1.2.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.2.win32-py2.7.exe","hashes":{"sha256":"6c5be5538202ed7419911178ded41c65e118104fa634109f528a6d2d3e50a7d0"},"provenance":null,"requires-python":null,"size":277039,"upload-time":"2013-10-22T18:14:56.072979Z","url":"https://files.pythonhosted.org/packages/5e/d6/56f2891f6dd56f950866cc39892e5a56e85331d97c39e2634cfc4014f0df/psutil-1.1.2.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.2.win32-py3.2.exe","hashes":{"sha256":"5cfa97b52fb48dbb8255c120b567be4f06802afa7d2fe71b8fe7c7c4ee53ad88"},"provenance":null,"requires-python":null,"size":276327,"upload-time":"2013-10-22T18:15:25.393310Z","url":"https://files.pythonhosted.org/packages/dd/be/1aea1e7a1a3fb44f4c8d887a1d55e960de283d86875f15457a284268e197/psutil-1.1.2.win32-py3.2.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.2.win32-py3.3.exe","hashes":{"sha256":"f7dc7507fb9d4edb42709b356eb2e4b3da356efa54d83900e4cef59f3adebfbf"},"provenance":null,"requires-python":null,"size":270952,"upload-time":"2013-10-22T18:15:51.874969Z","url":"https://files.pythonhosted.org/packages/f5/7c/1a33b78a66a96e740e197ae55719496ba57bb9cee32f710a5a6affa68cc8/psutil-1.1.2.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.3.tar.gz","hashes":{"sha256":"5e1164086a7ed3b863ebd12315d35086e22252b328401fce901a0862050ef98c"},"provenance":null,"requires-python":null,"size":165550,"upload-time":"2013-11-07T20:47:17.039714Z","url":"https://files.pythonhosted.org/packages/93/fa/1f70b7fcdff77348f4e79d84cc9b568596874ca34940ede78c63d503a095/psutil-1.1.3.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.3.win-amd64-py2.7.exe","hashes":{"sha256":"43cbf089cbe160d193e1658fe4eec4a719430432e866334b25dc3acef73c3e61"},"provenance":null,"requires-python":null,"size":306145,"upload-time":"2013-11-07T21:05:00.986042Z","url":"https://files.pythonhosted.org/packages/03/ec/f05db404504d67a19397e17e64f0276cc610a9dc450eb51ed70436f37c43/psutil-1.1.3.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.3.win32-py2.4.exe","hashes":{"sha256":"ef7445e1b0449af34ff51a9e1d3f030b8794c9fcd2b99d072b2e815d00e8a783"},"provenance":null,"requires-python":null,"size":142958,"upload-time":"2013-11-07T21:16:11.539460Z","url":"https://files.pythonhosted.org/packages/37/c2/5d40dd0a36f0280c1dea0f651cfde79b5e99a0e6fab92273fa3ac41055f0/psutil-1.1.3.win32-py2.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.3.win32-py2.5.exe","hashes":{"sha256":"b7f4103b1058d2ee597f88902ff50f19421b95c18ac9b82ea9e8a00091786131"},"provenance":null,"requires-python":null,"size":142940,"upload-time":"2013-11-07T21:15:50.256574Z","url":"https://files.pythonhosted.org/packages/ee/cb/7d42052f4057c6233ba3f4b7afade92117f58c5d7544ee6ab16e82c515c7/psutil-1.1.3.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.3.win32-py2.6.exe","hashes":{"sha256":"e21b9c5a26ad3328b6f5b629e30944a9b7e56ea6ac0aa051455c8a352e67fbca"},"provenance":null,"requires-python":null,"size":277254,"upload-time":"2013-11-07T20:58:49.794773Z","url":"https://files.pythonhosted.org/packages/b2/7e/c42d752b333c5846d88a8b56bbab23325b247766c100dc6f68c6bc56019d/psutil-1.1.3.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.3.win32-py2.7.exe","hashes":{"sha256":"fc9732a4dea2a3f73f9320472aa54e1cfae45d324bea24bd207d88d3a0a281b0"},"provenance":null,"requires-python":null,"size":277028,"upload-time":"2013-11-07T21:16:56.712839Z","url":"https://files.pythonhosted.org/packages/2b/76/eec917b6f74ea9bd20a55dd8b4b01e69689278235335dbedc2b9be212815/psutil-1.1.3.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.3.win32-py3.2.exe","hashes":{"sha256":"a141d59c03a28bdd32dfce11a5d51b9a63a8e6c6c4245e416eeffcc1c43f1de9"},"provenance":null,"requires-python":null,"size":276315,"upload-time":"2013-11-07T21:02:04.504719Z","url":"https://files.pythonhosted.org/packages/3a/b9/748bbd0c53c74c682051137830048abd0126ae50e3bf4d5854a0188da143/psutil-1.1.3.win32-py3.2.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.1.3.win32-py3.3.exe","hashes":{"sha256":"36e855ea7c872beb04f22d968c55f1ef897df0261ea993ecf5ae6e8216939a55"},"provenance":null,"requires-python":null,"size":270942,"upload-time":"2013-11-07T21:04:24.025560Z","url":"https://files.pythonhosted.org/packages/e6/7b/655e08abdf19d06c8a3fd6fba35932867c80fcff05c97cf909b5d364603c/psutil-1.1.3.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.0.tar.gz","hashes":{"sha256":"c3a2b02e92363837499680760b674b3bf3bd03dd9528a5dc41392625a61b162a"},"provenance":null,"requires-python":null,"size":166747,"upload-time":"2013-11-20T20:02:56.854747Z","url":"https://files.pythonhosted.org/packages/77/89/8ee72ae2b5e3c749e43a9c1e95d61eceff022ab7c929cdde8a3b7539a707/psutil-1.2.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.0.win-amd64-py2.7.exe","hashes":{"sha256":"2bedcda55618f71bc98d6669bd190e2b51b940e3a3af2512dd77745cb1ebc101"},"provenance":null,"requires-python":null,"size":307021,"upload-time":"2013-11-20T20:08:29.733072Z","url":"https://files.pythonhosted.org/packages/9c/d1/598bcaa9701e06561cb8823a7a04ee44e702b0e327ef0a65bdd97b019613/psutil-1.2.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.0.win32-py2.4.exe","hashes":{"sha256":"417695f66562a22ac4f92bf7df1e9dda6264579eb308badd2c3e85df88ab9436"},"provenance":null,"requires-python":null,"size":143831,"upload-time":"2013-11-20T20:18:44.725428Z","url":"https://files.pythonhosted.org/packages/a4/ba/4b54baace7b49b73df74b54815337878137d25fda506e518f2d8dd2472fc/psutil-1.2.0.win32-py2.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.0.win32-py2.5.exe","hashes":{"sha256":"40747c59cf92cef26d595a9cefaac9c1a4bc0290abff6004bb092ee2ddec7d7b"},"provenance":null,"requires-python":null,"size":143812,"upload-time":"2013-11-20T20:18:35.170091Z","url":"https://files.pythonhosted.org/packages/88/17/3ba1b45ec63d666c2631f16679adb819564feb775cfd9e774d23e5774a44/psutil-1.2.0.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.0.win32-py2.6.exe","hashes":{"sha256":"fb0c3942a48fde9c52828dae4074381556eff8eb5651bdc9c4c107b40dc8318e"},"provenance":null,"requires-python":null,"size":278130,"upload-time":"2013-11-20T20:07:34.211552Z","url":"https://files.pythonhosted.org/packages/ae/4a/e62000dbe462270c30f1e2f2dcc913596f70e264d9a656f881bb9f487283/psutil-1.2.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.0.win32-py2.7.exe","hashes":{"sha256":"1024eff34ff14f2899186549ca8f8e461403e5700910e52eb013ffffb078cda3"},"provenance":null,"requires-python":null,"size":277905,"upload-time":"2013-11-20T20:18:07.310116Z","url":"https://files.pythonhosted.org/packages/9d/1a/b8e679a7e47229d07e41439a43fc1be48c0d34774e5e35ad6730398485bd/psutil-1.2.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.0.win32-py3.2.exe","hashes":{"sha256":"2b588673530d67f45287c31b6dfef02ee4e753aee7e7216448e6dbcb95b482e1"},"provenance":null,"requires-python":null,"size":277189,"upload-time":"2013-11-20T20:05:01.902941Z","url":"https://files.pythonhosted.org/packages/05/10/20a2364e4e69206d66da106288b65bf5bbde0d648aad5bb829f3eb08fabb/psutil-1.2.0.win32-py3.2.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.0.win32-py3.3.exe","hashes":{"sha256":"3e23d3b1ef5f20a458092887d1df32f55eaf4fc86f2ed49cc68cf164537128d6"},"provenance":null,"requires-python":null,"size":271857,"upload-time":"2013-11-20T20:05:28.007235Z","url":"https://files.pythonhosted.org/packages/24/39/4630dff3d0fa4896db905a118877fce5b72ced6629ca9958e207f7a0b198/psutil-1.2.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.1.tar.gz","hashes":{"sha256":"508e4a44c8253a386a0f86d9c9bd4a1b4cbb2f94e88d49a19c1513653ca66c45"},"provenance":null,"requires-python":null,"size":167397,"upload-time":"2013-11-25T20:06:20.566209Z","url":"https://files.pythonhosted.org/packages/8a/45/3b9dbd7a58482018927f756de098388ee252dd230143ddf486b3017117b1/psutil-1.2.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.1.win-amd64-py2.7.exe","hashes":{"sha256":"f3af7b44925554531dff038e0401976a6b92b089ecca51d50be903420d7a262d"},"provenance":null,"requires-python":null,"size":307074,"upload-time":"2013-11-25T20:09:25.387664Z","url":"https://files.pythonhosted.org/packages/40/47/665755b95ad75e6223af96f2d7c04667f663a53dede0315df9832c38b60d/psutil-1.2.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.1.win-amd64-py3.3.exe","hashes":{"sha256":"26fd84571ac026861d806a9f64e3bbfd38d619a8195c1edfd31c0a9ee2295b03"},"provenance":null,"requires-python":null,"size":329716,"upload-time":"2014-02-06T00:57:26.931878Z","url":"https://files.pythonhosted.org/packages/d8/65/2e9941492b3d001a87d87b5e5827b1f3cec42e30b7110fa82d24be8c4526/psutil-1.2.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.1.win32-py2.4.exe","hashes":{"sha256":"84a4e7e2de1ca6f45359cfd7fd60c807f494385a6d97804bd58759a94b9c5e2d"},"provenance":null,"requires-python":null,"size":143888,"upload-time":"2013-11-25T20:07:25.331363Z","url":"https://files.pythonhosted.org/packages/5c/99/d9147b76eea8c185b6cefbb46de73ae880e5ef0ff36d93cb3f6084e50d59/psutil-1.2.1.win32-py2.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.1.win32-py2.5.exe","hashes":{"sha256":"aad7d81607b3ad740fee47b27a9f1434a05fe35dc68abefb1f961f74bae3c3f9"},"provenance":null,"requires-python":null,"size":143868,"upload-time":"2013-11-25T20:07:40.595942Z","url":"https://files.pythonhosted.org/packages/bd/a0/5087d4a5145326a5a07a53ed4f9cd5c09bf5dad4f8d7b9850e6aaa13caa2/psutil-1.2.1.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.1.win32-py2.6.exe","hashes":{"sha256":"3ad3a40afd859cf0217a2d643c74be3a12ed2f54ebd4a91c40fa7b13084573c6"},"provenance":null,"requires-python":null,"size":278185,"upload-time":"2013-11-25T20:07:59.926444Z","url":"https://files.pythonhosted.org/packages/c6/e0/67810b602a598488d1f2982451655427effe7c7062184fe036c2b5bc928f/psutil-1.2.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.1.win32-py2.7.exe","hashes":{"sha256":"02fb79b9e5336ff179c44ce2017308cf46316e19bea70abb8855afd808db2a0f"},"provenance":null,"requires-python":null,"size":277955,"upload-time":"2013-11-25T20:08:20.641779Z","url":"https://files.pythonhosted.org/packages/5b/7f/9334b57597acabaaf2261c93bb9b1f9f02cdfef5c1b1aa808b262f770adb/psutil-1.2.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.1.win32-py3.2.exe","hashes":{"sha256":"7e64d065f12e8f941f2dbb2f3df0887b2677fee7b2b4c50ed91e490e094c7273"},"provenance":null,"requires-python":null,"size":277243,"upload-time":"2013-11-25T20:08:40.628203Z","url":"https://files.pythonhosted.org/packages/ae/c5/2842c69c67ae171f219efa8bb11bddc2fcec2cea059721e716fe4d48b50c/psutil-1.2.1.win32-py3.2.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-1.2.1.win32-py3.3.exe","hashes":{"sha256":"1a1c8e6635949a698b6ade3d5d2a8368daff916d8122cf13286c79a52ec8d7a1"},"provenance":null,"requires-python":null,"size":271900,"upload-time":"2013-11-25T20:09:05.464640Z","url":"https://files.pythonhosted.org/packages/86/df/007ca575da6ee7cbb015dc00122028ee0c97fc6a0c9e8bc02333753bfd2f/psutil-1.2.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.0.0.tar.gz","hashes":{"sha256":"38af34b0f40a4f50988a7401b7111ae4468beb5bcce0fbae409504dd3d5f2e8d"},"provenance":null,"requires-python":null,"size":207168,"upload-time":"2014-03-10T11:23:38.510743Z","url":"https://files.pythonhosted.org/packages/9c/2c/d4380234ddc21ecfb03691a982f5f26b03061e165658ac455b61886fe3ff/psutil-2.0.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.0.0.win-amd64-py2.7.exe","hashes":{"sha256":"32f27f9be7c03f60f61dc27af2464cfb1edd64525d05b81478e80dc12913fe3b"},"provenance":null,"requires-python":null,"size":310569,"upload-time":"2014-03-10T11:16:14.555076Z","url":"https://files.pythonhosted.org/packages/c3/10/c0e1b505d7d2b4a7f3294c1b4b2bc2644a4629462d777fe2cdcd57b1debe/psutil-2.0.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.0.0.win-amd64-py3.3.exe","hashes":{"sha256":"219cf2e5832cf68798e522e815d79a7307dea1f6b1d9b2372704f6e7fea085f3"},"provenance":null,"requires-python":null,"size":309024,"upload-time":"2014-03-10T11:16:29.122028Z","url":"https://files.pythonhosted.org/packages/eb/db/ab023b5ce09f314ee58ee4b9e73e85172dd06272501570a34a1afe6115c2/psutil-2.0.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.0.0.win-amd64-py3.4.exe","hashes":{"sha256":"12b9c02e108e887a43e0ed44e3a8e9968e65236d6d0b79c45891471ea2b9e14d"},"provenance":null,"requires-python":null,"size":310130,"upload-time":"2014-03-10T11:16:49.562585Z","url":"https://files.pythonhosted.org/packages/67/94/0dded4aab9c4992bddb311d2ae8fd9a638df5f6039d12a4fe66481f3ea1c/psutil-2.0.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.0.0.win32-py2.4.exe","hashes":{"sha256":"54d8636623a4f676a9a38b0afe3dfad5f1b90f710f2cb6c55e1a0803813d76a5"},"provenance":null,"requires-python":null,"size":143691,"upload-time":"2014-03-10T11:20:46.107824Z","url":"https://files.pythonhosted.org/packages/76/a3/48c0984c0b65be53a9e3e090df0cd5f3e6bddd767c3f8e62cf286be240e1/psutil-2.0.0.win32-py2.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.0.0.win32-py2.5.exe","hashes":{"sha256":"f669fe7e7cab107fb738362366cc9e0ecda532269ac3a9815a28930b474edf0b"},"provenance":null,"requires-python":null,"size":147735,"upload-time":"2014-03-10T11:15:02.487295Z","url":"https://files.pythonhosted.org/packages/f8/ba/346cc719249b9a5281dab059cb8796aff6faf487142f50966fc08330ad79/psutil-2.0.0.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.0.0.win32-py2.6.exe","hashes":{"sha256":"33b580008e0c65073198472da81d0d0c50d2a30e3e82c269713b1e3bdf14c2c6"},"provenance":null,"requires-python":null,"size":280920,"upload-time":"2014-03-10T11:15:17.132174Z","url":"https://files.pythonhosted.org/packages/2f/6f/7326e900c5333d59aa96a574d13321c94a9357ab56b0dd489b8f24ebab78/psutil-2.0.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.0.0.win32-py2.7.exe","hashes":{"sha256":"4f9c78cdd57e1c83242096f8343617ae038efd1c23af3864c25992335eabed3f"},"provenance":null,"requires-python":null,"size":280693,"upload-time":"2014-03-10T11:15:31.704968Z","url":"https://files.pythonhosted.org/packages/18/dd/c81485b54894c35fd8b62822563293db7c4dd17a05ea4eade169cf383266/psutil-2.0.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.0.0.win32-py3.3.exe","hashes":{"sha256":"380f8ff680ce3c8fdd7b31a3fe42d697b15a0c8677559691ed90b755051a5acf"},"provenance":null,"requires-python":null,"size":275631,"upload-time":"2014-03-10T11:15:45.735326Z","url":"https://files.pythonhosted.org/packages/77/5a/e87efff3e46862421a9c87847e63ebecd3bb332031305b476399918fea4f/psutil-2.0.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.0.0.win32-py3.4.exe","hashes":{"sha256":"4239b2431c825db2ad98e404a96320f0c78fb1c7d5bdf52a49a00d36bedfa0df"},"provenance":null,"requires-python":null,"size":275638,"upload-time":"2014-03-10T11:16:00.263121Z","url":"https://files.pythonhosted.org/packages/c4/80/35eb7f189482d25e3669871e7dcd295ec38f792dc4670b8635d72b4f949a/psutil-2.0.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.0.tar.gz","hashes":{"sha256":"d1e3ce46736d86164b6d72070f2cbaf86dcf9db03066b7f36a7b302e334a8d01"},"provenance":null,"requires-python":null,"size":211640,"upload-time":"2014-04-08T14:59:37.598513Z","url":"https://files.pythonhosted.org/packages/6c/d1/69431c4fab9b5cecaf28f2f2e0abee21805c5783c47143db5f0f7d42dbec/psutil-2.1.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.0.win-amd64-py2.7.exe","hashes":{"sha256":"9dc35f899946d58baf5b805ebed2f723f75d93f6b6bce212f79581d40f7276db"},"provenance":null,"requires-python":null,"size":312139,"upload-time":"2014-04-08T15:13:59.193442Z","url":"https://files.pythonhosted.org/packages/7a/3c/ce6a447030cdb50cf68a3988337df0c42e52abf45c3adfea3b225760eb70/psutil-2.1.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.0.win-amd64-py3.3.exe","hashes":{"sha256":"46232b7b0eb48c6ec1f12d17b2cc15ec3cc70ed952f2c98edb50aaa405dafa5d"},"provenance":null,"requires-python":null,"size":310591,"upload-time":"2014-04-08T15:14:05.019441Z","url":"https://files.pythonhosted.org/packages/cd/60/7134a7f812ef1eba9373c86b95ce6254f5f58928baba04af161f0066629f/psutil-2.1.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.0.win-amd64-py3.4.exe","hashes":{"sha256":"fa770a2c9e384924df021aed9aa5a6f92db8062a7f700113ba3943e262028454"},"provenance":null,"requires-python":null,"size":311682,"upload-time":"2014-04-08T15:14:24.701683Z","url":"https://files.pythonhosted.org/packages/45/56/3ac0a63799d54cb9b1914214944aed71e49297fb90de92b8d1fe20de3bd8/psutil-2.1.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.0.win32-py2.4.exe","hashes":{"sha256":"15d0d10bbd60e461690188cdd209d6688a1112648589bf291b17cc84e99cb6e7"},"provenance":null,"requires-python":null,"size":145275,"upload-time":"2014-04-08T15:12:04.809516Z","url":"https://files.pythonhosted.org/packages/09/e8/f6e4209b3d6373ea11fa340c2e6e4640a7ee53ef4697c49d610ffdf86674/psutil-2.1.0.win32-py2.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.0.win32-py2.5.exe","hashes":{"sha256":"381ac1027a270c04cf6decdc011a28a4270105d009f213d62daec4b3116b92b6"},"provenance":null,"requires-python":null,"size":149378,"upload-time":"2014-04-08T15:12:25.638187Z","url":"https://files.pythonhosted.org/packages/6d/05/0e7213e6f0dc71490a523a70e6ab5e7cd5140d87dc93a4447da58c440d6b/psutil-2.1.0.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.0.win32-py2.6.exe","hashes":{"sha256":"7eda949fbdf89548b0e52e7d666e096ea66b30b65bcbe04d305e036a24a76d11"},"provenance":null,"requires-python":null,"size":282509,"upload-time":"2014-04-08T15:12:57.055047Z","url":"https://files.pythonhosted.org/packages/f2/88/a856c5ed36d15b8ad74597f380baa29891ec284e7a1be4cb2f91f8453bd8/psutil-2.1.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.0.win32-py2.7.exe","hashes":{"sha256":"f1ec06db1b0d27681a1bea4c9f0b33705bc5a00035c32f168da0ea193883bb91"},"provenance":null,"requires-python":null,"size":282317,"upload-time":"2014-04-08T15:13:11.798772Z","url":"https://files.pythonhosted.org/packages/d0/17/ec99e9252dae834f4a012e13c804c86907fcb1cb474f7b1bc767562bfa7b/psutil-2.1.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.0.win32-py3.3.exe","hashes":{"sha256":"feccef3ccf09785c6f81e67223cf2d8736a90c8994623dd75f683dd2bf849235"},"provenance":null,"requires-python":null,"size":277268,"upload-time":"2014-04-08T15:13:26.711424Z","url":"https://files.pythonhosted.org/packages/f0/0a/32abfd9b965c9a433b5011574c904372b954330c170c6f92b637d661ecd2/psutil-2.1.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.0.win32-py3.4.exe","hashes":{"sha256":"4719134be984b0f2ba072ff761f334c2f3dbb8ca6af70ba43d8ca31b7e13c3db"},"provenance":null,"requires-python":null,"size":277279,"upload-time":"2014-04-08T15:13:41.834397Z","url":"https://files.pythonhosted.org/packages/f5/3b/eab6a8d832d805c7a00d0f2398c12a32bea7e8b6eb7d5fbdf869e4bcc9e0/psutil-2.1.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.1.tar.gz","hashes":{"sha256":"bf812a4aa6a41147d0e96e63d826eb7582fda6b54ad8f22534354b7f8ac45593"},"provenance":null,"requires-python":null,"size":216796,"upload-time":"2014-04-30T14:27:01.651394Z","url":"https://files.pythonhosted.org/packages/64/4b/70601d39b8e445265ed148affc49f7bfbd246940637785be5c80e007fa6e/psutil-2.1.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.1.win-amd64-py2.7.exe","hashes":{"sha256":"1b5ee64306535a625b77648e32f1b064c31bf58bfc7a37cde6c7bb3fa4abb6bd"},"provenance":null,"requires-python":null,"size":312763,"upload-time":"2014-04-30T14:31:09.411365Z","url":"https://files.pythonhosted.org/packages/e6/50/df05e0cbfcf20f022756d5e2da32b4f4f37d5bca6f5bd6965b4ef0460e8b/psutil-2.1.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.1.win-amd64-py3.3.exe","hashes":{"sha256":"9e28fe35e1185d59ba55313d90efc122d45b7a2e3fa304869024d26b0a809bc5"},"provenance":null,"requires-python":null,"size":311262,"upload-time":"2014-04-30T14:31:28.733500Z","url":"https://files.pythonhosted.org/packages/a3/a9/b3f114141a49244739321f221f35c300ac7f34ec9e3a352ea70c9fae41f8/psutil-2.1.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.1.win-amd64-py3.4.exe","hashes":{"sha256":"3afa327284a218d6a25b2f3520135337cfa4a47eaea030273ad8eb02894d60fe"},"provenance":null,"requires-python":null,"size":312365,"upload-time":"2014-04-30T14:31:47.981590Z","url":"https://files.pythonhosted.org/packages/c7/67/2aae3f66090c2e06fa60c29a2e554fd2a718949aca53bca78d640212cb34/psutil-2.1.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.1.win32-py2.4.exe","hashes":{"sha256":"7c76d99bfaeafbcf66096c69b8fca1f7269603d66cad1b14cd8dd93b14bceeb0"},"provenance":null,"requires-python":null,"size":145848,"upload-time":"2014-04-30T14:37:26.909225Z","url":"https://files.pythonhosted.org/packages/27/4e/c9b4802420d6b5d0a844208c9b8a4e25b3c37305428e40bc1da6f5076891/psutil-2.1.1.win32-py2.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.1.win32-py2.5.exe","hashes":{"sha256":"6aeb358b66cc4367378edadb928c77e82646f36858b2a516298d1917aa6aca25"},"provenance":null,"requires-python":null,"size":149998,"upload-time":"2014-04-30T14:28:49.630619Z","url":"https://files.pythonhosted.org/packages/5d/e9/69dee6454940bb102fbbcaa1e44b46bfefc4c0bf53e5e3835d3de3ebc9ae/psutil-2.1.1.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.1.win32-py2.6.exe","hashes":{"sha256":"a53546550067773920e1f7e6e3f1fad2f2befe45c6ff6c94e22b67f4b54c321a"},"provenance":null,"requires-python":null,"size":283143,"upload-time":"2014-04-30T14:29:06.869445Z","url":"https://files.pythonhosted.org/packages/9f/45/d2dfa6bdd9b64bfd46ad35af774c3819e0d5abf23a99d51adf11984ca658/psutil-2.1.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.1.win32-py2.7.exe","hashes":{"sha256":"37ce747f9042c375f62e861d627b5eb7bace24767303f5d0c4c03d17173a551c"},"provenance":null,"requires-python":null,"size":282941,"upload-time":"2014-04-30T14:29:27.059188Z","url":"https://files.pythonhosted.org/packages/78/47/14db8651f9863d301c0673d25fa22b87d13fde2974f94854502886a21fd1/psutil-2.1.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.1.win32-py3.3.exe","hashes":{"sha256":"8f0f88752e1e9bfeced78daf29d90e0028d17f39b805bb0acf70fefe77ba5ccb"},"provenance":null,"requires-python":null,"size":277944,"upload-time":"2014-04-30T14:29:49.405456Z","url":"https://files.pythonhosted.org/packages/f1/63/2fcaa58b101dce55a12f768508a7f0a0028ccc5a90633d86dd4cc0bcdb52/psutil-2.1.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.1.win32-py3.4.exe","hashes":{"sha256":"bc353df13a6ea40651cba82810a38590e2439c158cf6f130cd0876d0dda53118"},"provenance":null,"requires-python":null,"size":279092,"upload-time":"2014-04-30T14:30:08.449338Z","url":"https://files.pythonhosted.org/packages/5f/26/0be35c7f3dc9e78d405ed6be3aa76a9ce97b51e0076db98408a6f2c288fb/psutil-2.1.1.win32-py3.4.exe","yanked":false},{"core-metadata":{"sha256":"32ea7aa3898a3a77f4d45a211e8c96889ec6e5f933ea2ef9e4249d53fc41e4a1"},"data-dist-info-metadata":{"sha256":"32ea7aa3898a3a77f4d45a211e8c96889ec6e5f933ea2ef9e4249d53fc41e4a1"},"filename":"psutil-2.1.2-cp26-none-win32.whl","hashes":{"sha256":"ccb5e28357a4b6c572b97e710a070f349e9b45172315eaed6e0b72e86d333b68"},"provenance":null,"requires-python":null,"size":83869,"upload-time":"2014-09-21T13:47:09.164866Z","url":"https://files.pythonhosted.org/packages/c7/22/811ac7c641191e3b65c053c95eb34f6567bbc5155912630464271ab6f3df/psutil-2.1.2-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"32ea7aa3898a3a77f4d45a211e8c96889ec6e5f933ea2ef9e4249d53fc41e4a1"},"data-dist-info-metadata":{"sha256":"32ea7aa3898a3a77f4d45a211e8c96889ec6e5f933ea2ef9e4249d53fc41e4a1"},"filename":"psutil-2.1.2-cp27-none-win32.whl","hashes":{"sha256":"41992126c0281c2f5f279a0e8583382a3b840bd0d48262dfb7bc3cb67bdc6587"},"provenance":null,"requires-python":null,"size":83672,"upload-time":"2014-09-21T13:47:14.034296Z","url":"https://files.pythonhosted.org/packages/79/9d/154b179a73695ae605856c2d77ab5da2a66ef4819c3c4f97e4ab297a2902/psutil-2.1.2-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"32ea7aa3898a3a77f4d45a211e8c96889ec6e5f933ea2ef9e4249d53fc41e4a1"},"data-dist-info-metadata":{"sha256":"32ea7aa3898a3a77f4d45a211e8c96889ec6e5f933ea2ef9e4249d53fc41e4a1"},"filename":"psutil-2.1.2-cp27-none-win_amd64.whl","hashes":{"sha256":"79cb57bba4cbeebb7e445d19c531107493458a47d0f4c888ce49fc8dec670c32"},"provenance":null,"requires-python":null,"size":85848,"upload-time":"2014-09-21T13:47:30.044802Z","url":"https://files.pythonhosted.org/packages/00/ae/567c30ff44c263cc78dfe50197184e58b62ca9fcfebad144cd235f5d8d2d/psutil-2.1.2-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"e9eb9bc1a4851e5f154ad139504894781ffa9f0ee16ea77f8c17f4aee9d5aa50"},"data-dist-info-metadata":{"sha256":"e9eb9bc1a4851e5f154ad139504894781ffa9f0ee16ea77f8c17f4aee9d5aa50"},"filename":"psutil-2.1.2-cp33-none-win32.whl","hashes":{"sha256":"c5e6424833620f0d0a70ebc1305260bfad4b5c73989b8a2e8bd01bf39b4b600c"},"provenance":null,"requires-python":null,"size":83742,"upload-time":"2014-09-21T13:47:18.481401Z","url":"https://files.pythonhosted.org/packages/62/4d/be87c318274ade9d6589e8545dd5d5bb4bcc42e92334d88d948cf8daa36b/psutil-2.1.2-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"e9eb9bc1a4851e5f154ad139504894781ffa9f0ee16ea77f8c17f4aee9d5aa50"},"data-dist-info-metadata":{"sha256":"e9eb9bc1a4851e5f154ad139504894781ffa9f0ee16ea77f8c17f4aee9d5aa50"},"filename":"psutil-2.1.2-cp33-none-win_amd64.whl","hashes":{"sha256":"b4c69627c245025b9209acbffc066ff8ac1237d13e2eddd0461f3149969b7da3"},"provenance":null,"requires-python":null,"size":85822,"upload-time":"2014-09-21T13:47:33.681070Z","url":"https://files.pythonhosted.org/packages/bc/52/ab3c88a0574275ec33d9de0fe7dc9b1a32c573de0e468502876bc1df6f84/psutil-2.1.2-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"e9eb9bc1a4851e5f154ad139504894781ffa9f0ee16ea77f8c17f4aee9d5aa50"},"data-dist-info-metadata":{"sha256":"e9eb9bc1a4851e5f154ad139504894781ffa9f0ee16ea77f8c17f4aee9d5aa50"},"filename":"psutil-2.1.2-cp34-none-win32.whl","hashes":{"sha256":"97a1d0ddbb028a20feffaf7fa3d0c451004abc74ef7ea1e492d039f8b7278399"},"provenance":null,"requires-python":null,"size":83748,"upload-time":"2014-09-21T13:47:25.367914Z","url":"https://files.pythonhosted.org/packages/ef/4a/c956675314e4a50b319d9894c2ee2d48ce83df4d639d9c2fc06a99415dec/psutil-2.1.2-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"e9eb9bc1a4851e5f154ad139504894781ffa9f0ee16ea77f8c17f4aee9d5aa50"},"data-dist-info-metadata":{"sha256":"e9eb9bc1a4851e5f154ad139504894781ffa9f0ee16ea77f8c17f4aee9d5aa50"},"filename":"psutil-2.1.2-cp34-none-win_amd64.whl","hashes":{"sha256":"a78226f9236c674d43b206e7da06478cf2bcf10e5aee647f671878ea2434805f"},"provenance":null,"requires-python":null,"size":85789,"upload-time":"2014-09-21T13:47:38.760178Z","url":"https://files.pythonhosted.org/packages/b9/41/68b5fc38b97e037ef55e6475d618c079fe2b5d148f5e3fda795c21d888a7/psutil-2.1.2-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.2.tar.gz","hashes":{"sha256":"897e5163e0669001bf8bcb0557362f14703356336519082a93c38d54e5b392e4"},"provenance":null,"requires-python":null,"size":223595,"upload-time":"2014-09-21T13:50:56.650693Z","url":"https://files.pythonhosted.org/packages/53/6a/8051b913b2f94eb00fd045fe9e14a7182b6e7f088b12c308edd7616a559b/psutil-2.1.2.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.2.win-amd64-py2.7.exe","hashes":{"sha256":"3ed7ef4ab59894e6eb94adae0b656733c91906af169b55443329315322cd63b3"},"provenance":null,"requires-python":null,"size":319335,"upload-time":"2014-09-21T13:48:15.057062Z","url":"https://files.pythonhosted.org/packages/a6/88/cc912d38640ddf3441db1f85f8ff8a87f906056554239a4a211970cf6446/psutil-2.1.2.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.2.win-amd64-py3.3.exe","hashes":{"sha256":"e20f317187a7186e13dc6fdd6960083c227db86fffbb145fe000e8c167a854a6"},"provenance":null,"requires-python":null,"size":317817,"upload-time":"2014-09-21T13:48:20.665047Z","url":"https://files.pythonhosted.org/packages/d2/b4/887323eb2b0b5becb5a7b77ab04167741346fddffe27bc6ae5deeffcc3c1/psutil-2.1.2.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.2.win-amd64-py3.4.exe","hashes":{"sha256":"f7f2a61be7a828656dceffdf0d2fa304a144db6ab5ec4e2b033108f3822df6ff"},"provenance":null,"requires-python":null,"size":317804,"upload-time":"2014-09-21T13:48:33.226186Z","url":"https://files.pythonhosted.org/packages/57/e5/c041b08bea32246f53b7cf27b897e882a695f3ea95eb632b384dd35cf41f/psutil-2.1.2.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.2.win32-py2.4.exe","hashes":{"sha256":"77aac0179fe0e8b39c9b25ea732285d307bb941e715075366dc467b8609ebf09"},"provenance":null,"requires-python":null,"size":155426,"upload-time":"2014-09-21T13:54:03.017765Z","url":"https://files.pythonhosted.org/packages/ce/3f/7c434baec7ca47e65fb1e3bcbc8fe1c9f504d93c750a7fa4f98b16635920/psutil-2.1.2.win32-py2.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.2.win32-py2.5.exe","hashes":{"sha256":"982f4568876d10881f9c90fe992d1d9af353a59b5de7771cc4766d7432aee7ab"},"provenance":null,"requires-python":null,"size":155414,"upload-time":"2014-09-21T13:47:47.970150Z","url":"https://files.pythonhosted.org/packages/46/86/2971b31e2637ddc53adce2d997472cee8d4dec366d5a1e140945b95860d5/psutil-2.1.2.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.2.win32-py2.6.exe","hashes":{"sha256":"f35d91b5fc2b52472c0766fa411ac22f7a5ed8b1ececd96c1486380bdad0bb41"},"provenance":null,"requires-python":null,"size":289714,"upload-time":"2014-09-21T13:47:52.391726Z","url":"https://files.pythonhosted.org/packages/dd/3e/cdc3d4f343e6edc583d0686d1d20d98f9e11de35c51dc7990525ab2ca332/psutil-2.1.2.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.2.win32-py2.7.exe","hashes":{"sha256":"dbd9e4fbe08c0ff6d4a87a4d6dfae16fccd03346f980cffa105941de8801d00b"},"provenance":null,"requires-python":null,"size":289515,"upload-time":"2014-09-21T13:47:57.624310Z","url":"https://files.pythonhosted.org/packages/fb/01/73753147113f6db19734db6e7ac994cee5cce0f0935e12320d7aa1b56a14/psutil-2.1.2.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.2.win32-py3.3.exe","hashes":{"sha256":"73428e7b695e5889f07f1b37c5ec0cc0f49d3808dc40986050f1b13fd7c4c71e"},"provenance":null,"requires-python":null,"size":284506,"upload-time":"2014-09-21T13:48:02.428235Z","url":"https://files.pythonhosted.org/packages/b0/97/ba13c3915aba7776bb0d23819c04255230c46df1f8582752f7e0382c0b67/psutil-2.1.2.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.2.win32-py3.4.exe","hashes":{"sha256":"e2326c7b9f64a0f22891d3b362efcd92389e641023b07bc54567280c0c2e160d"},"provenance":null,"requires-python":null,"size":284538,"upload-time":"2014-09-21T13:48:08.358281Z","url":"https://files.pythonhosted.org/packages/88/bd/843fadc578d62f2888d5a7b4e2a418a28140ac239a5a5039c0d3678df647/psutil-2.1.2.win32-py3.4.exe","yanked":false},{"core-metadata":{"sha256":"69a75d24381600fdb642efa3827cf0c4e9c1c94273f2866e017a7017816167f8"},"data-dist-info-metadata":{"sha256":"69a75d24381600fdb642efa3827cf0c4e9c1c94273f2866e017a7017816167f8"},"filename":"psutil-2.1.3-cp26-none-win32.whl","hashes":{"sha256":"331a3b0b6688f95acebe87c02efca53003e613f1c7892a09063a9d3c0af33656"},"provenance":null,"requires-python":null,"size":83927,"upload-time":"2014-09-26T20:26:46.078909Z","url":"https://files.pythonhosted.org/packages/72/4c/f93448dfe2dec286b6eae91a00c32f1fbf65506e089354c51db76f4efdaf/psutil-2.1.3-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"69a75d24381600fdb642efa3827cf0c4e9c1c94273f2866e017a7017816167f8"},"data-dist-info-metadata":{"sha256":"69a75d24381600fdb642efa3827cf0c4e9c1c94273f2866e017a7017816167f8"},"filename":"psutil-2.1.3-cp27-none-win32.whl","hashes":{"sha256":"5ee9e17f6e92aaec7c53f6483c6708eec22124060a52ac339d104effcb4af37f"},"provenance":null,"requires-python":null,"size":83731,"upload-time":"2014-09-26T20:26:51.215134Z","url":"https://files.pythonhosted.org/packages/27/c5/a644b5df545c467569c7b8e768717fad6758eab8e2544b7d412d07c30ffe/psutil-2.1.3-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"69a75d24381600fdb642efa3827cf0c4e9c1c94273f2866e017a7017816167f8"},"data-dist-info-metadata":{"sha256":"69a75d24381600fdb642efa3827cf0c4e9c1c94273f2866e017a7017816167f8"},"filename":"psutil-2.1.3-cp27-none-win_amd64.whl","hashes":{"sha256":"47ea728de15c16a7cb476eb4f348be57e9daebfa730b9a4a104e9086da2cf6cd"},"provenance":null,"requires-python":null,"size":85906,"upload-time":"2014-09-26T20:27:07.470817Z","url":"https://files.pythonhosted.org/packages/89/82/1070ecb59d83967ddb2ea58b41ba2b9ffa8f3f686a3f49b8d33dc5684964/psutil-2.1.3-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"6ddf7c489626ecf2cd274a5d66e5785193f3eb6b2d596efff52afc15b251e25f"},"data-dist-info-metadata":{"sha256":"6ddf7c489626ecf2cd274a5d66e5785193f3eb6b2d596efff52afc15b251e25f"},"filename":"psutil-2.1.3-cp33-none-win32.whl","hashes":{"sha256":"ae0aac37af1d7d6821bb11dcffb12bb885b44773858bd40703e9dcb14325369c"},"provenance":null,"requires-python":null,"size":83804,"upload-time":"2014-09-26T20:26:56.233644Z","url":"https://files.pythonhosted.org/packages/b6/fc/7f2ceac523bb5ce9ea93bd85806ebb2d5327f0e430cbb6e70d32e4306e1d/psutil-2.1.3-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"6ddf7c489626ecf2cd274a5d66e5785193f3eb6b2d596efff52afc15b251e25f"},"data-dist-info-metadata":{"sha256":"6ddf7c489626ecf2cd274a5d66e5785193f3eb6b2d596efff52afc15b251e25f"},"filename":"psutil-2.1.3-cp33-none-win_amd64.whl","hashes":{"sha256":"45ebaf679050a6d64a32a91d901791868a29574f6961629997dcd4661edb4ece"},"provenance":null,"requires-python":null,"size":85884,"upload-time":"2014-09-26T20:27:12.800416Z","url":"https://files.pythonhosted.org/packages/45/33/a50ed3def836cd8fc53ce04d09e50df67a6816fd24a819e8e9c45b93fc74/psutil-2.1.3-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"6ddf7c489626ecf2cd274a5d66e5785193f3eb6b2d596efff52afc15b251e25f"},"data-dist-info-metadata":{"sha256":"6ddf7c489626ecf2cd274a5d66e5785193f3eb6b2d596efff52afc15b251e25f"},"filename":"psutil-2.1.3-cp34-none-win32.whl","hashes":{"sha256":"0ae7d503fc458181af7dd9a2bbf43c4b5fc7cd6e6797ef4a6d58bb8046026d76"},"provenance":null,"requires-python":null,"size":83804,"upload-time":"2014-09-26T20:27:02.155242Z","url":"https://files.pythonhosted.org/packages/55/e9/c122a9d2528cc36ff4f2824a714ee6d5da5462e4b6725e6b1c7098142c4a/psutil-2.1.3-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"6ddf7c489626ecf2cd274a5d66e5785193f3eb6b2d596efff52afc15b251e25f"},"data-dist-info-metadata":{"sha256":"6ddf7c489626ecf2cd274a5d66e5785193f3eb6b2d596efff52afc15b251e25f"},"filename":"psutil-2.1.3-cp34-none-win_amd64.whl","hashes":{"sha256":"da0961ba9a6d97c137381cd59b7d330894dd4f7deb5a8291bc3251375fd6d6ec"},"provenance":null,"requires-python":null,"size":85840,"upload-time":"2014-09-26T20:27:18.296315Z","url":"https://files.pythonhosted.org/packages/6f/97/5e01561cde882306c28e462b427f67d549add65f5fca324bf0bbdf831d21/psutil-2.1.3-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.3.tar.gz","hashes":{"sha256":"b434c75f01715777391f10f456002e33d0ca14633f96fdbd9ff9139b42d9452c"},"provenance":null,"requires-python":null,"size":224008,"upload-time":"2014-09-30T18:10:37.283557Z","url":"https://files.pythonhosted.org/packages/fe/a3/7cf43f28bbb52c4d680378f99e900ced201ade5073ee3a7b30e7f09e3c66/psutil-2.1.3.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.3.win-amd64-py2.7.exe","hashes":{"sha256":"449d07df8f8b9700cfae4ee67f0a73e4f96b55697428ae92cab29e33db4c3102"},"provenance":null,"requires-python":null,"size":319620,"upload-time":"2014-09-26T20:24:42.575161Z","url":"https://files.pythonhosted.org/packages/0e/66/bf4346c9ada08acee7e8f87d270fb4d4e6c86a477630adcfa3caa69aa5bb/psutil-2.1.3.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.3.win-amd64-py3.3.exe","hashes":{"sha256":"5b444e5c1f3d3ee7a19a5720c62d6462fe81dd1d1bbb8aa955546ead509b3c4a"},"provenance":null,"requires-python":null,"size":318101,"upload-time":"2014-09-26T20:24:47.973672Z","url":"https://files.pythonhosted.org/packages/bd/a7/888e9fa42c8d475de7b467b9635bb18ed360cb1c261bcf59f5f932d624ea/psutil-2.1.3.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.3.win-amd64-py3.4.exe","hashes":{"sha256":"3d25b4b20aabd360b7eda3dcbf4a14d2b256c2f61a8a569028e1c4b65b4d585a"},"provenance":null,"requires-python":null,"size":318089,"upload-time":"2014-09-26T20:24:55.536742Z","url":"https://files.pythonhosted.org/packages/b1/15/3f657d395213ad90efed423c88f1cb2f3b0a429d12bdea98a28c26de7ff4/psutil-2.1.3.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.3.win32-py2.4.exe","hashes":{"sha256":"36424b4e683d640291a1723f46a3949ebea37e43492f61027c168fb9dfe2055f"},"provenance":null,"requires-python":null,"size":155710,"upload-time":"2014-09-26T20:29:01.901009Z","url":"https://files.pythonhosted.org/packages/31/2e/d5448240fed09e88bbb59de194e1f2d3ba29f936a3231d718e5373736299/psutil-2.1.3.win32-py2.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.3.win32-py2.5.exe","hashes":{"sha256":"baa1803aaa4505fcf48bbd50d038d1f686d9e290defeecde41770d8fe876812b"},"provenance":null,"requires-python":null,"size":155699,"upload-time":"2014-09-26T20:24:13.810499Z","url":"https://files.pythonhosted.org/packages/2a/45/10d7b5057b3f941f803ca8ee690e651d2778e731f15ef3eee83c3f05f82f/psutil-2.1.3.win32-py2.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.3.win32-py2.6.exe","hashes":{"sha256":"55811a125d8a79bed0a65ed855fafc34f7d59cde162542a30cfa518da9e015bc"},"provenance":null,"requires-python":null,"size":289998,"upload-time":"2014-09-26T20:24:18.935804Z","url":"https://files.pythonhosted.org/packages/f3/4d/7f105269ece54ad7344c2a24b42ecb216b2746460f349c0ed1577b5ab8fa/psutil-2.1.3.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.3.win32-py2.7.exe","hashes":{"sha256":"6a1c8f1884f1983f8b74fda5dc89da07a69e3972fc022c3205f4964e1b01d235"},"provenance":null,"requires-python":null,"size":289802,"upload-time":"2014-09-26T20:24:24.112924Z","url":"https://files.pythonhosted.org/packages/93/64/1432ca27dfd7102a6726161b79b15a2997f461d3835867271a6c1e3353f7/psutil-2.1.3.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.3.win32-py3.3.exe","hashes":{"sha256":"8df72fcd30436e78bf8c5c6c796bb6815966511fa0bc8e3065e1aabbe4a2cf3d"},"provenance":null,"requires-python":null,"size":284794,"upload-time":"2014-09-26T20:24:30.110864Z","url":"https://files.pythonhosted.org/packages/0a/bb/d6cc7133624e3532e1d99f6cece35be7bd8d95ff7c82ca28cd502388c225/psutil-2.1.3.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.1.3.win32-py3.4.exe","hashes":{"sha256":"1f353ab0bbe0216e3a1636bfefb70ed366a2b1d95c95689f97e95d603626ef70"},"provenance":null,"requires-python":null,"size":284824,"upload-time":"2014-09-26T20:24:36.842776Z","url":"https://files.pythonhosted.org/packages/4a/66/9b1acf05dba9b9cb012ab3b8ed3d0fd7b01e1620808977fa6f1efe05dee2/psutil-2.1.3.win32-py3.4.exe","yanked":false},{"core-metadata":{"sha256":"30ed17dae0fd30cf429cfd022f7c9e5e8d675d87901927dda94ded45c224eb33"},"data-dist-info-metadata":{"sha256":"30ed17dae0fd30cf429cfd022f7c9e5e8d675d87901927dda94ded45c224eb33"},"filename":"psutil-2.2.0-cp26-none-win32.whl","hashes":{"sha256":"c92d2853ac52d5aaf8c2366435fb74892f7503daf2cf56b785f1ecadbf68e712"},"provenance":null,"requires-python":null,"size":82092,"upload-time":"2015-01-06T15:36:18.243917Z","url":"https://files.pythonhosted.org/packages/3c/65/f5bd2f8b54f14d950596c0c63d7b0f98b6dc779f7c61ac0dd2e9fc7e5e74/psutil-2.2.0-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"30ed17dae0fd30cf429cfd022f7c9e5e8d675d87901927dda94ded45c224eb33"},"data-dist-info-metadata":{"sha256":"30ed17dae0fd30cf429cfd022f7c9e5e8d675d87901927dda94ded45c224eb33"},"filename":"psutil-2.2.0-cp27-none-win32.whl","hashes":{"sha256":"6ad0e79b95f57a20f0cace08a063b0fc33fd83da1e5e501bc800bc69329a4501"},"provenance":null,"requires-python":null,"size":81891,"upload-time":"2015-01-06T15:36:28.466138Z","url":"https://files.pythonhosted.org/packages/6a/e6/96a4b4976f0eca169715d975b8c669b78b9afca58f9aadf261915694b73e/psutil-2.2.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"30ed17dae0fd30cf429cfd022f7c9e5e8d675d87901927dda94ded45c224eb33"},"data-dist-info-metadata":{"sha256":"30ed17dae0fd30cf429cfd022f7c9e5e8d675d87901927dda94ded45c224eb33"},"filename":"psutil-2.2.0-cp27-none-win_amd64.whl","hashes":{"sha256":"bbe719046986568ed3e84ce66ed617eebc46251ea8193566b3707abdf635afe6"},"provenance":null,"requires-python":null,"size":84072,"upload-time":"2015-01-06T15:36:58.206788Z","url":"https://files.pythonhosted.org/packages/28/28/4e4f94c9778ed16163f6c3dd696ce5e331121ac1600308830a3e98fa979a/psutil-2.2.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ac33f92c3e235fbeaf7e70b46e7b2d1503467bd420fac08e570cd9cfc3d3b738"},"data-dist-info-metadata":{"sha256":"ac33f92c3e235fbeaf7e70b46e7b2d1503467bd420fac08e570cd9cfc3d3b738"},"filename":"psutil-2.2.0-cp33-none-win32.whl","hashes":{"sha256":"73013822a953fc4e3fb269256ec01b261752c590e23851e666201d1bfd32a3a9"},"provenance":null,"requires-python":null,"size":81885,"upload-time":"2015-01-06T15:36:38.776238Z","url":"https://files.pythonhosted.org/packages/66/8d/3143623c2f5bc264197727854040fdc02e3175b4ad991490586db7a512ed/psutil-2.2.0-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"ac33f92c3e235fbeaf7e70b46e7b2d1503467bd420fac08e570cd9cfc3d3b738"},"data-dist-info-metadata":{"sha256":"ac33f92c3e235fbeaf7e70b46e7b2d1503467bd420fac08e570cd9cfc3d3b738"},"filename":"psutil-2.2.0-cp33-none-win_amd64.whl","hashes":{"sha256":"d664a896feb10ec5bf4c43f4df8b6c7fceeb94677004cc9cf8c9f35b52c0e4fc"},"provenance":null,"requires-python":null,"size":84002,"upload-time":"2015-01-06T15:37:08.775842Z","url":"https://files.pythonhosted.org/packages/bd/08/112807380b8e7b76de8c84f920234a0ebed35e6511271f93f900f691a10c/psutil-2.2.0-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ac33f92c3e235fbeaf7e70b46e7b2d1503467bd420fac08e570cd9cfc3d3b738"},"data-dist-info-metadata":{"sha256":"ac33f92c3e235fbeaf7e70b46e7b2d1503467bd420fac08e570cd9cfc3d3b738"},"filename":"psutil-2.2.0-cp34-none-win32.whl","hashes":{"sha256":"84299c41b251bef2a8b0812d651f4715209b3c4dfebe4a5df0f103bbdec78221"},"provenance":null,"requires-python":null,"size":81886,"upload-time":"2015-01-06T15:36:49.808417Z","url":"https://files.pythonhosted.org/packages/ef/0e/0cf2fea8f6e2e5ef84797eefc2c5ce561123e2417d7b931a6c54f9f8d413/psutil-2.2.0-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"ac33f92c3e235fbeaf7e70b46e7b2d1503467bd420fac08e570cd9cfc3d3b738"},"data-dist-info-metadata":{"sha256":"ac33f92c3e235fbeaf7e70b46e7b2d1503467bd420fac08e570cd9cfc3d3b738"},"filename":"psutil-2.2.0-cp34-none-win_amd64.whl","hashes":{"sha256":"3636879fcbde2b0b63db08abd0e3673c2cc72bb14075e46e15f98774b0c78236"},"provenance":null,"requires-python":null,"size":83954,"upload-time":"2015-01-06T15:37:18.558007Z","url":"https://files.pythonhosted.org/packages/82/ed/ce9c4281fd8a944a725c0f9a5d77f32295777ab22f54b4f706a51d59edd3/psutil-2.2.0-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.0.tar.gz","hashes":{"sha256":"b15cc9e7cad0991bd1cb806fa90ea85ba3a95d0f1226625ecef993294ad61521"},"provenance":null,"requires-python":null,"size":223676,"upload-time":"2015-01-06T15:38:45.979998Z","url":"https://files.pythonhosted.org/packages/ba/e4/760f64a8a5a5f1b95f3ce17c0d51134952f930caf1218e6ce21902f6c4ab/psutil-2.2.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.0.win-amd64-py2.7.exe","hashes":{"sha256":"623baf2a213adc99cbc067e1db04b9e578eb4c38d9535c903e04b6d1ded10cab"},"provenance":null,"requires-python":null,"size":317990,"upload-time":"2015-01-06T15:34:57.187611Z","url":"https://files.pythonhosted.org/packages/30/a8/d1754e9e5492717d1c1afb30970f0677056052859f1af935b48c72c6cd68/psutil-2.2.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.0.win-amd64-py3.3.exe","hashes":{"sha256":"fc6d348f603ae8465992a7edbc7c62cc16f0493bdd43aa609dcb7437992cac96"},"provenance":null,"requires-python":null,"size":316424,"upload-time":"2015-01-06T15:35:20.860940Z","url":"https://files.pythonhosted.org/packages/32/f1/e98caa1f6be7ba49dfafa0fbb63f50ffa191d316a1b2b3ec431d97ebf494/psutil-2.2.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.0.win-amd64-py3.4.exe","hashes":{"sha256":"5e4508ca6822839f25310bfc14c050e60362dc29ddea6f5eac91de2c7423a471"},"provenance":null,"requires-python":null,"size":316403,"upload-time":"2015-01-06T15:35:42.094623Z","url":"https://files.pythonhosted.org/packages/dd/46/31505418bd950b09d28c6c21be76a4a51d593ec06b412539797080c0aa6b/psutil-2.2.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.0.win32-py2.6.exe","hashes":{"sha256":"1f93ccdf415da40f15a84ab6d9d32ddda61bb1b20079dae602356e087f408d28"},"provenance":null,"requires-python":null,"size":288364,"upload-time":"2015-01-06T15:33:25.830661Z","url":"https://files.pythonhosted.org/packages/9c/0d/f360da29c906dafd825edccc219e70dab73370ad2c287fa5baa9f7fa370b/psutil-2.2.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.0.win32-py2.7.exe","hashes":{"sha256":"83eb1739b7c87a21a65224d77217325e582199bedd0141b29dac81b4e4144c62"},"provenance":null,"requires-python":null,"size":288166,"upload-time":"2015-01-06T15:33:46.994076Z","url":"https://files.pythonhosted.org/packages/7a/30/5bb6644318f2279caf5d334f32df19fe4b2bce11ef418af32124e16f8e98/psutil-2.2.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.0.win32-py3.3.exe","hashes":{"sha256":"9db585f37d56381c37738e03aa8beb0b501b26adc7c70660ff182e0473f2cb0a"},"provenance":null,"requires-python":null,"size":283089,"upload-time":"2015-01-06T15:34:11.431221Z","url":"https://files.pythonhosted.org/packages/12/c4/cb75d51edb425ff77e2af5bb32e405ed41beb124ad062fb927bbe135c709/psutil-2.2.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.0.win32-py3.4.exe","hashes":{"sha256":"4a0d8a192d1523a3f02a4028bf4ac296f7da92c935464d302618bd639c03a2c6"},"provenance":null,"requires-python":null,"size":283110,"upload-time":"2015-01-06T15:34:33.472476Z","url":"https://files.pythonhosted.org/packages/17/fb/ae589efdfd076d1961e1f858c969d234a63e26f648a79b3fac0409e95c2f/psutil-2.2.0.win32-py3.4.exe","yanked":false},{"core-metadata":{"sha256":"ff83c284ef44f26c82b6ebdf69d521137b95a89dc815e363bc49eedb63a04c98"},"data-dist-info-metadata":{"sha256":"ff83c284ef44f26c82b6ebdf69d521137b95a89dc815e363bc49eedb63a04c98"},"filename":"psutil-2.2.1-cp26-none-win32.whl","hashes":{"sha256":"610f08fc0df646e4997b52f2bf2e40118a560738422d540883297c6f4e38be8c"},"provenance":null,"requires-python":null,"size":82104,"upload-time":"2015-02-02T13:08:03.993201Z","url":"https://files.pythonhosted.org/packages/0a/a7/b6323d30a8dde3f6a139809898e4bdcabcae3129314621aa24eb5ca4468e/psutil-2.2.1-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"ff83c284ef44f26c82b6ebdf69d521137b95a89dc815e363bc49eedb63a04c98"},"data-dist-info-metadata":{"sha256":"ff83c284ef44f26c82b6ebdf69d521137b95a89dc815e363bc49eedb63a04c98"},"filename":"psutil-2.2.1-cp27-none-win32.whl","hashes":{"sha256":"84ec9e77cab1d9ecf2d93bf43eba255a11b26d480966ee542bc846be1e272ea5"},"provenance":null,"requires-python":null,"size":81907,"upload-time":"2015-02-02T13:08:14.963426Z","url":"https://files.pythonhosted.org/packages/40/b6/a94c6d00ac18f779c72973639b78519de0c488e8e8e8d00b65cf4287bec3/psutil-2.2.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"ff83c284ef44f26c82b6ebdf69d521137b95a89dc815e363bc49eedb63a04c98"},"data-dist-info-metadata":{"sha256":"ff83c284ef44f26c82b6ebdf69d521137b95a89dc815e363bc49eedb63a04c98"},"filename":"psutil-2.2.1-cp27-none-win_amd64.whl","hashes":{"sha256":"81a6ab277886f233f230c46073fa6fc97ca2a95a44a14b80c6f6054acb7a644b"},"provenance":null,"requires-python":null,"size":84086,"upload-time":"2015-02-02T13:08:42.917519Z","url":"https://files.pythonhosted.org/packages/1c/ff/6b00405e4eeb3c40d4fc07142ef7bdc3a7a7aa2a1640b65e654bdb7682d1/psutil-2.2.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"85bb3fab01138e16635be140adec7d7fd83c5150a93731deb5a4020ec9d569b1"},"data-dist-info-metadata":{"sha256":"85bb3fab01138e16635be140adec7d7fd83c5150a93731deb5a4020ec9d569b1"},"filename":"psutil-2.2.1-cp33-none-win32.whl","hashes":{"sha256":"f5e0d247b09c9460b896ff89098015b7687a2934d4ed6165a5c3a662fad7ab6b"},"provenance":null,"requires-python":null,"size":81896,"upload-time":"2015-02-02T13:08:24.437138Z","url":"https://files.pythonhosted.org/packages/a2/fe/40b7d42057c5e68ff4c733b5689e9ae9d8785f13576326ba44371689eff7/psutil-2.2.1-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"85bb3fab01138e16635be140adec7d7fd83c5150a93731deb5a4020ec9d569b1"},"data-dist-info-metadata":{"sha256":"85bb3fab01138e16635be140adec7d7fd83c5150a93731deb5a4020ec9d569b1"},"filename":"psutil-2.2.1-cp33-none-win_amd64.whl","hashes":{"sha256":"07f31cbe5fc1183c8efb52bab9a5382ebd001a6e11b5a5df827f69456219d514"},"provenance":null,"requires-python":null,"size":84011,"upload-time":"2015-02-02T13:08:53.420801Z","url":"https://files.pythonhosted.org/packages/fe/8d/ffc94f092a12fc3cb837b9bda93abd88fb12219bcc6684b9bffea5e1f385/psutil-2.2.1-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"85bb3fab01138e16635be140adec7d7fd83c5150a93731deb5a4020ec9d569b1"},"data-dist-info-metadata":{"sha256":"85bb3fab01138e16635be140adec7d7fd83c5150a93731deb5a4020ec9d569b1"},"filename":"psutil-2.2.1-cp34-none-win32.whl","hashes":{"sha256":"a98b350251df2658c9be6bf0b6ebbab10fe88ccb513079c2c56fb53330530ee6"},"provenance":null,"requires-python":null,"size":81896,"upload-time":"2015-02-02T13:08:34.116542Z","url":"https://files.pythonhosted.org/packages/7f/3a/4bb24092c3de0a44f2fc1afb7bcaf898f1daba67cba5b9188b0ae6527102/psutil-2.2.1-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"85bb3fab01138e16635be140adec7d7fd83c5150a93731deb5a4020ec9d569b1"},"data-dist-info-metadata":{"sha256":"85bb3fab01138e16635be140adec7d7fd83c5150a93731deb5a4020ec9d569b1"},"filename":"psutil-2.2.1-cp34-none-win_amd64.whl","hashes":{"sha256":"f49d3ca9150f20269eae79d6efac98f0bbe4e5ceac46fe418d881560a04b6d8e"},"provenance":null,"requires-python":null,"size":83963,"upload-time":"2015-02-02T13:09:03.990685Z","url":"https://files.pythonhosted.org/packages/64/f7/c385e9350abbbb082364596fd4b0066fcb0c51ebc103a7ebf6eb9bc837e9/psutil-2.2.1-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.1.tar.gz","hashes":{"sha256":"a0e9b96f1946975064724e242ac159f3260db24ffa591c3da0a355361a3a337f"},"provenance":null,"requires-python":null,"size":223688,"upload-time":"2015-02-02T13:10:14.673714Z","url":"https://files.pythonhosted.org/packages/df/47/ee54ef14dd40f8ce831a7581001a5096494dc99fe71586260ca6b531fe86/psutil-2.2.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.1.win-amd64-py2.7.exe","hashes":{"sha256":"552eaba2dbc9a49af2da64fc00cedf8847c7c9c2559cc620d9c8855583105764"},"provenance":null,"requires-python":null,"size":317995,"upload-time":"2015-02-02T13:06:42.422390Z","url":"https://files.pythonhosted.org/packages/97/27/e64014166df9efc3d00f987a251938d534d3897d62ef21486559a697a770/psutil-2.2.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.1.win-amd64-py3.3.exe","hashes":{"sha256":"d25746746f2680d6174c9eb0e2793cb73304ca200d5ec7334000fdb575cd6577"},"provenance":null,"requires-python":null,"size":316429,"upload-time":"2015-02-02T13:07:07.458895Z","url":"https://files.pythonhosted.org/packages/c2/dd/5fdd5fd6102ae546452a5f32a6f75ec44c11d6b84c188dd33cfe1e2809e1/psutil-2.2.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.1.win-amd64-py3.4.exe","hashes":{"sha256":"298ed760a365a846337750c5089adcec2a87014c61132ae39db671982750f35a"},"provenance":null,"requires-python":null,"size":316409,"upload-time":"2015-02-02T13:07:29.047577Z","url":"https://files.pythonhosted.org/packages/d9/59/0f14daf5797c61db2fec25bca49b49aa0a5d57dec57b4e58ae4652dadb95/psutil-2.2.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.1.win32-py2.6.exe","hashes":{"sha256":"6e49f72a1cd57918b275fdf7dd041618696fc39d5705ac722fdfccaf0b784a93"},"provenance":null,"requires-python":null,"size":288372,"upload-time":"2015-02-02T13:05:10.073479Z","url":"https://files.pythonhosted.org/packages/0c/a2/63967c61fbfc3f60c1b17e652a52d1afeb3c56403990c5a8f2fb801e2aa1/psutil-2.2.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.1.win32-py2.7.exe","hashes":{"sha256":"9227949290983df92bb3bb57c5b25605ebbc0e61a3f9baa394f752ed7abc914c"},"provenance":null,"requires-python":null,"size":288171,"upload-time":"2015-02-02T13:05:31.604222Z","url":"https://files.pythonhosted.org/packages/46/f3/d50f059d7d297db7335e340333124eac9441c5897bb29223c85ebaa64e7d/psutil-2.2.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.1.win32-py3.3.exe","hashes":{"sha256":"f8d94c8228011d1b658c39783596d95244e01950413bfc41fd44e78129ef7075"},"provenance":null,"requires-python":null,"size":283095,"upload-time":"2015-02-02T13:05:54.134208Z","url":"https://files.pythonhosted.org/packages/4a/d6/341767cdc6890adfb19ad60683e66dab3cdde605bc226427c8ec17dbd3f5/psutil-2.2.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-2.2.1.win32-py3.4.exe","hashes":{"sha256":"1e1df017bfa14c0edc87a124b11ca6e7fd8b6b5c9692eb06c1b72077bcca3845"},"provenance":null,"requires-python":null,"size":283115,"upload-time":"2015-02-02T13:06:12.827127Z","url":"https://files.pythonhosted.org/packages/8c/c1/24179e79ab74bde86ee766c4c0d2bb90b98e4e4d14017efeec024caba268/psutil-2.2.1.win32-py3.4.exe","yanked":false},{"core-metadata":{"sha256":"e61eb67de1755dd6d2917f81bb9ee7a0ae6dc5cda2948621b91794f7c5348033"},"data-dist-info-metadata":{"sha256":"e61eb67de1755dd6d2917f81bb9ee7a0ae6dc5cda2948621b91794f7c5348033"},"filename":"psutil-3.0.0-cp27-none-win32.whl","hashes":{"sha256":"9641677de91769127c82aa032b70a8d9aa75369d0a9965aba880ac335b02eccb"},"provenance":null,"requires-python":null,"size":85709,"upload-time":"2015-06-13T19:16:33.857911Z","url":"https://files.pythonhosted.org/packages/bb/cf/f893fa2fb0888384b5d668e22b6e3652c9ce187e749604027a64a6bd6646/psutil-3.0.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"e61eb67de1755dd6d2917f81bb9ee7a0ae6dc5cda2948621b91794f7c5348033"},"data-dist-info-metadata":{"sha256":"e61eb67de1755dd6d2917f81bb9ee7a0ae6dc5cda2948621b91794f7c5348033"},"filename":"psutil-3.0.0-cp27-none-win_amd64.whl","hashes":{"sha256":"11c6b17b37ab1ea04f6a1365bc4598637490819d6b744f3947fd3b8b425366b3"},"provenance":null,"requires-python":null,"size":88043,"upload-time":"2015-06-13T19:16:52.139563Z","url":"https://files.pythonhosted.org/packages/f1/26/a0904455b550f7fd5baa069ca10889825cc22440147813713998cd0676a0/psutil-3.0.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"adc9c5e259a9c63a9ebd7ef296c4a895baa01701f7d8127d1f989e968fa497aa"},"data-dist-info-metadata":{"sha256":"adc9c5e259a9c63a9ebd7ef296c4a895baa01701f7d8127d1f989e968fa497aa"},"filename":"psutil-3.0.0-cp33-none-win32.whl","hashes":{"sha256":"8333d2a496fe5d24171360ab1f78468db7a9691d61fd26bf15ca62336c699bac"},"provenance":null,"requires-python":null,"size":85737,"upload-time":"2015-06-13T19:16:39.871421Z","url":"https://files.pythonhosted.org/packages/ea/45/defb71d5009e178ef13e2474627a1070ae291b2dee64ae00e8f2b1a2aec2/psutil-3.0.0-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"adc9c5e259a9c63a9ebd7ef296c4a895baa01701f7d8127d1f989e968fa497aa"},"data-dist-info-metadata":{"sha256":"adc9c5e259a9c63a9ebd7ef296c4a895baa01701f7d8127d1f989e968fa497aa"},"filename":"psutil-3.0.0-cp33-none-win_amd64.whl","hashes":{"sha256":"44711a1a683369056d99fd08a05789d217205d72b6dbbe75c0b3743c5e1e3768"},"provenance":null,"requires-python":null,"size":87943,"upload-time":"2015-06-13T19:16:58.439865Z","url":"https://files.pythonhosted.org/packages/3c/0b/648fbaef2a037cc0c05388c0416ba3ca9244c22c4156dba73e4a900540a6/psutil-3.0.0-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"adc9c5e259a9c63a9ebd7ef296c4a895baa01701f7d8127d1f989e968fa497aa"},"data-dist-info-metadata":{"sha256":"adc9c5e259a9c63a9ebd7ef296c4a895baa01701f7d8127d1f989e968fa497aa"},"filename":"psutil-3.0.0-cp34-none-win32.whl","hashes":{"sha256":"9036f9c57a8c8a571f36c7d3b2c9a15a22dc9a95137fa60191e2eaa9527209d5"},"provenance":null,"requires-python":null,"size":85695,"upload-time":"2015-06-13T19:16:46.286646Z","url":"https://files.pythonhosted.org/packages/6d/ca/1d5dafae4d7396a825531296b6837b45129b63463b4eb221ac3429eb157d/psutil-3.0.0-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"adc9c5e259a9c63a9ebd7ef296c4a895baa01701f7d8127d1f989e968fa497aa"},"data-dist-info-metadata":{"sha256":"adc9c5e259a9c63a9ebd7ef296c4a895baa01701f7d8127d1f989e968fa497aa"},"filename":"psutil-3.0.0-cp34-none-win_amd64.whl","hashes":{"sha256":"18e53256390b299ff03bd58dc134d070bde4871d8c93baf08faea316e87544c8"},"provenance":null,"requires-python":null,"size":87893,"upload-time":"2015-06-13T19:17:05.116909Z","url":"https://files.pythonhosted.org/packages/59/82/51ab7cecaf03c8cbe7eff3b341f6564c3fa80baaa5a37643096cb526cae2/psutil-3.0.0-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.0.tar.gz","hashes":{"sha256":"a43cc84c6a2406e8f23785c68a52b792d95eb91e2b43be40f7c814bf80dc5979"},"provenance":null,"requires-python":null,"size":240872,"upload-time":"2015-06-13T19:14:46.063288Z","url":"https://files.pythonhosted.org/packages/ba/18/c5bb52abb67194aabadfd61286b538ee8856c7cb9c0e92dd64c1b132bf5e/psutil-3.0.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.0.win-amd64-py2.7.exe","hashes":{"sha256":"26b2cf4456e039f490f79457100564ff34718f4257b68dee53b42d17493f0187"},"provenance":null,"requires-python":null,"size":323299,"upload-time":"2015-06-13T19:15:53.089846Z","url":"https://files.pythonhosted.org/packages/0c/b1/fa44dd6ed19e8be6f9f05f776e824ce202822f5d6237093c54d6c5102888/psutil-3.0.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.0.win-amd64-py3.3.exe","hashes":{"sha256":"32808b29473e788487ee28a65caaab1c9fbd44c2d654ce6d563a89f20e106996"},"provenance":null,"requires-python":null,"size":321705,"upload-time":"2015-06-13T19:16:01.370127Z","url":"https://files.pythonhosted.org/packages/62/4f/89c6ac53e424109639f42033d0e87c4de4ddb610060007c6d5f6081a0fba/psutil-3.0.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.0.win-amd64-py3.4.exe","hashes":{"sha256":"37a9869ef17680e768bf32d7556252a04f426e5b1e6fa51d5af16305c8a701b6"},"provenance":null,"requires-python":null,"size":321688,"upload-time":"2015-06-13T19:16:11.540515Z","url":"https://files.pythonhosted.org/packages/d2/67/9c988331d40c445ebc9d29d95534ce3908f3949f6a84d640a6c125bc56e2/psutil-3.0.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.0.win32-py2.6.exe","hashes":{"sha256":"f29ee1aafa452cce77667e75f5e0345b678b8c091b288d0118840df30f303bed"},"provenance":null,"requires-python":null,"size":293522,"upload-time":"2015-06-13T19:15:19.130222Z","url":"https://files.pythonhosted.org/packages/10/19/9e2f8a305f5a6c01feee59dfeb38a73dc1ea4f4af0133cef9a36708f4111/psutil-3.0.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.0.win32-py2.7.exe","hashes":{"sha256":"7cbcf866faa23e5229ecfe44ff562f406b4806caae66cb255dbb327247e540aa"},"provenance":null,"requires-python":null,"size":293319,"upload-time":"2015-06-13T19:15:27.353152Z","url":"https://files.pythonhosted.org/packages/fc/0f/92e595cd2f2a80cead241d45b4ce4961e2515deff101644f3812c75e9bc7/psutil-3.0.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.0.win32-py3.3.exe","hashes":{"sha256":"b968771d3db5eef0c17ef6bed2d7756b8ac3cce40e11d5493824863d195dd8e3"},"provenance":null,"requires-python":null,"size":288278,"upload-time":"2015-06-13T19:15:35.880360Z","url":"https://files.pythonhosted.org/packages/fc/29/387c555f9dc38c6bb084a8df8936e607848de9e2e984dbf7eb2a298c1ceb/psutil-3.0.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.0.win32-py3.4.exe","hashes":{"sha256":"81ac6f54a27d091c75333f7c90082d028db45a1e78de59d28ca9f38cf6186395"},"provenance":null,"requires-python":null,"size":288253,"upload-time":"2015-06-13T19:15:44.871254Z","url":"https://files.pythonhosted.org/packages/fb/29/25ac80b589c0e56214ac64fdd8216992be162a2b2290f9b88b9a5c517cfd/psutil-3.0.0.win32-py3.4.exe","yanked":false},{"core-metadata":{"sha256":"986bdfc454536c2a940c955834882e01a25de324bf458054673b43d6b47a00ee"},"data-dist-info-metadata":{"sha256":"986bdfc454536c2a940c955834882e01a25de324bf458054673b43d6b47a00ee"},"filename":"psutil-3.0.1-cp27-none-win32.whl","hashes":{"sha256":"bcb8d23121848953ed295f7e3c0875b0164ee98d3245060beb3623c59ff2a1bc"},"provenance":null,"requires-python":null,"size":85793,"upload-time":"2015-06-18T02:36:50.974286Z","url":"https://files.pythonhosted.org/packages/17/05/b2c807b470464fbe3d357734b68199451574fcd75431fd3e5c77be24b6e0/psutil-3.0.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"986bdfc454536c2a940c955834882e01a25de324bf458054673b43d6b47a00ee"},"data-dist-info-metadata":{"sha256":"986bdfc454536c2a940c955834882e01a25de324bf458054673b43d6b47a00ee"},"filename":"psutil-3.0.1-cp27-none-win_amd64.whl","hashes":{"sha256":"ba56ec5c052489b7a7015c26ed3f917f2df4ffa9799266e86be41815bc358b80"},"provenance":null,"requires-python":null,"size":88128,"upload-time":"2015-06-18T02:37:08.778852Z","url":"https://files.pythonhosted.org/packages/38/bf/0b743c8a07265f2ecb203f8e60310571dcae33036aa6ba7aa16e2641ac7a/psutil-3.0.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"98502550423dd5cf7f5bcfaed4c1d92354cc0353b2b9bf673992e42fa246dce5"},"data-dist-info-metadata":{"sha256":"98502550423dd5cf7f5bcfaed4c1d92354cc0353b2b9bf673992e42fa246dce5"},"filename":"psutil-3.0.1-cp33-none-win32.whl","hashes":{"sha256":"23606e9b42760a8fdeada33281d0c3ce88df220949746e0ddf54d5db7974b4c6"},"provenance":null,"requires-python":null,"size":85819,"upload-time":"2015-06-18T02:36:56.905878Z","url":"https://files.pythonhosted.org/packages/b2/9c/b2c4373b9406eaf33654c4be828d9316ee780f4b3c19d0fa56f55eb64d61/psutil-3.0.1-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"98502550423dd5cf7f5bcfaed4c1d92354cc0353b2b9bf673992e42fa246dce5"},"data-dist-info-metadata":{"sha256":"98502550423dd5cf7f5bcfaed4c1d92354cc0353b2b9bf673992e42fa246dce5"},"filename":"psutil-3.0.1-cp33-none-win_amd64.whl","hashes":{"sha256":"ae4a7f51f40154d02ab1576e94377171a28cc83fc89c077c152f16ba3dae72f3"},"provenance":null,"requires-python":null,"size":88028,"upload-time":"2015-06-18T02:37:14.406512Z","url":"https://files.pythonhosted.org/packages/7a/59/1a9a10238226dbaed24b8d978e9cc743ac143504b3f4ad8f5e3a169e263a/psutil-3.0.1-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"98502550423dd5cf7f5bcfaed4c1d92354cc0353b2b9bf673992e42fa246dce5"},"data-dist-info-metadata":{"sha256":"98502550423dd5cf7f5bcfaed4c1d92354cc0353b2b9bf673992e42fa246dce5"},"filename":"psutil-3.0.1-cp34-none-win32.whl","hashes":{"sha256":"b7520db52c7c4e38cdd50bb11be02a826372d8002bf92bbe955823cac32c7f9d"},"provenance":null,"requires-python":null,"size":85783,"upload-time":"2015-06-18T02:37:03.210308Z","url":"https://files.pythonhosted.org/packages/ed/85/1d4d97ce4c9a8c6dd479f5f717694016651170d3731cdecf740db0e4eae3/psutil-3.0.1-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"98502550423dd5cf7f5bcfaed4c1d92354cc0353b2b9bf673992e42fa246dce5"},"data-dist-info-metadata":{"sha256":"98502550423dd5cf7f5bcfaed4c1d92354cc0353b2b9bf673992e42fa246dce5"},"filename":"psutil-3.0.1-cp34-none-win_amd64.whl","hashes":{"sha256":"1754d4118eaab16f299a37dafaf7d34111e9a8e5ac2a799e2bd9b1a5d9d1122b"},"provenance":null,"requires-python":null,"size":87982,"upload-time":"2015-06-18T02:37:20.490529Z","url":"https://files.pythonhosted.org/packages/1e/e6/0af7e190f74d6f959dfb87f2b56f4711271729952691f843fb91c5e06712/psutil-3.0.1-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.1.tar.gz","hashes":{"sha256":"3f213b9ceed3c3068a973e04d7a8b2a29d1076abcb5ef45382517bfc6b808801"},"provenance":null,"requires-python":null,"size":241539,"upload-time":"2015-06-18T02:33:52.119548Z","url":"https://files.pythonhosted.org/packages/aa/5d/cbd3b7227fe7a4c2c77e4031b6c43961563a3ecde2981190e5afe959be51/psutil-3.0.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.1.win-amd64-py2.7.exe","hashes":{"sha256":"b0a2ed567b31f71ae2e893768f0da0d51d51d12714471d4b7431e70ff5e36577"},"provenance":null,"requires-python":null,"size":323471,"upload-time":"2015-06-18T02:36:04.537383Z","url":"https://files.pythonhosted.org/packages/9d/a4/815181cacd33f0ce62a3c3aa188af65bd3945888aa2f6c16a925837ca517/psutil-3.0.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.1.win-amd64-py3.3.exe","hashes":{"sha256":"99c593cb459b54209cdb4aed4a607fa8b2920fbd4f3c5a9219a0ede114975758"},"provenance":null,"requires-python":null,"size":321879,"upload-time":"2015-06-18T02:36:14.693044Z","url":"https://files.pythonhosted.org/packages/08/b2/278ac09b03db15b9e51c3ae7f678b3fbf050e895a2eccab66d05017bdef9/psutil-3.0.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.1.win-amd64-py3.4.exe","hashes":{"sha256":"8f803bfe00a763254581bb6a3d788b3332492fbc67dbf46ad5068b663e44f309"},"provenance":null,"requires-python":null,"size":321861,"upload-time":"2015-06-18T02:36:23.687685Z","url":"https://files.pythonhosted.org/packages/d8/66/d21041db114938ae22d4994ea31f2d8f1353aa61e5f0d0c6c4e185b016a7/psutil-3.0.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.1.win32-py2.6.exe","hashes":{"sha256":"4a19475c1d6071c685b38f85837f6e6daa2c6ccd4d0132ab840123deb8ea2372"},"provenance":null,"requires-python":null,"size":293700,"upload-time":"2015-06-18T02:35:31.083110Z","url":"https://files.pythonhosted.org/packages/99/67/980b4a9257abaa3f53a60a3441f759301a0aca2922d60e18a05d23fb1b0e/psutil-3.0.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.1.win32-py2.7.exe","hashes":{"sha256":"363d3dbd610ce7bcf7f13b0a31133ee231d0990e99315142ed37f6ba2c1a84e6"},"provenance":null,"requires-python":null,"size":293492,"upload-time":"2015-06-18T02:35:39.030138Z","url":"https://files.pythonhosted.org/packages/5f/64/b994ed73ab49d5c847d97c47600b539603ebf0d6cfd8d3575b80db7aefb5/psutil-3.0.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.1.win32-py3.3.exe","hashes":{"sha256":"fc7cfe1d6919cb67f4144947acfcefc099d7d8299dd88bb4d863e62c44d041b4"},"provenance":null,"requires-python":null,"size":288451,"upload-time":"2015-06-18T02:35:47.145538Z","url":"https://files.pythonhosted.org/packages/1f/11/4871e823ff0d5a302a7f8ece60358d20ba7fc1620c4c9e2d94e826e1ff0e/psutil-3.0.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.0.1.win32-py3.4.exe","hashes":{"sha256":"73091a80ed295f990ab377dfd8cd4f7f00e3abfffb5e500192f1ea9fa58de158"},"provenance":null,"requires-python":null,"size":288427,"upload-time":"2015-06-18T02:35:55.768442Z","url":"https://files.pythonhosted.org/packages/08/af/c78ef8dc09ac61a479968b0c912677d0a0cb138c5ea7813b2a196fa32c53/psutil-3.0.1.win32-py3.4.exe","yanked":false},{"core-metadata":{"sha256":"74e797fa9b89ed58d856034a0b901f68c1d317dd41713af1d4e9bd93e2e19b2c"},"data-dist-info-metadata":{"sha256":"74e797fa9b89ed58d856034a0b901f68c1d317dd41713af1d4e9bd93e2e19b2c"},"filename":"psutil-3.1.0-cp26-none-win32.whl","hashes":{"sha256":"399916a016503c9ae99fd6aafbba4628b86ebdee52d67034e8c3f26e88a3504b"},"provenance":null,"requires-python":null,"size":87753,"upload-time":"2015-07-15T00:41:13.293562Z","url":"https://files.pythonhosted.org/packages/4e/0a/ab710541d02ff3ce4169a0922e4412952332991c4c39a0ea9df20e9279b0/psutil-3.1.0-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"74e797fa9b89ed58d856034a0b901f68c1d317dd41713af1d4e9bd93e2e19b2c"},"data-dist-info-metadata":{"sha256":"74e797fa9b89ed58d856034a0b901f68c1d317dd41713af1d4e9bd93e2e19b2c"},"filename":"psutil-3.1.0-cp27-none-win32.whl","hashes":{"sha256":"71af7a30eb6ed694a3624330334bd12da28bffdbe813f4b1080fadeb86f5970f"},"provenance":null,"requires-python":null,"size":87559,"upload-time":"2015-07-15T00:41:27.810047Z","url":"https://files.pythonhosted.org/packages/db/87/34b52811b755db94a7dd123e5c0f1d257885535f08f5f185c17810214d55/psutil-3.1.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"74e797fa9b89ed58d856034a0b901f68c1d317dd41713af1d4e9bd93e2e19b2c"},"data-dist-info-metadata":{"sha256":"74e797fa9b89ed58d856034a0b901f68c1d317dd41713af1d4e9bd93e2e19b2c"},"filename":"psutil-3.1.0-cp27-none-win_amd64.whl","hashes":{"sha256":"ada1cf324e6aba0affcb23c6fd959dae9f72de6ec135530788cbf17153d4fd3c"},"provenance":null,"requires-python":null,"size":90071,"upload-time":"2015-07-15T00:42:15.297263Z","url":"https://files.pythonhosted.org/packages/0b/7c/90869233a3e4056ddfdd1040d0e7722d3bb023c74b48bf10c09380c26eae/psutil-3.1.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"e5e4b772973543fffe4b2ffb66ad397284be77c3b6886db90b1fca8245b0d0b1"},"data-dist-info-metadata":{"sha256":"e5e4b772973543fffe4b2ffb66ad397284be77c3b6886db90b1fca8245b0d0b1"},"filename":"psutil-3.1.0-cp33-none-win32.whl","hashes":{"sha256":"0a313ebe14b9e277dfd151f4ad021012fb344dd51248e6de2aa1e7062d678541"},"provenance":null,"requires-python":null,"size":87569,"upload-time":"2015-07-15T00:41:43.765697Z","url":"https://files.pythonhosted.org/packages/f4/7c/56b718693e4c41b32af8bbe39160e8a3ea0ca12d3eece3dbbb8d4c046855/psutil-3.1.0-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"e5e4b772973543fffe4b2ffb66ad397284be77c3b6886db90b1fca8245b0d0b1"},"data-dist-info-metadata":{"sha256":"e5e4b772973543fffe4b2ffb66ad397284be77c3b6886db90b1fca8245b0d0b1"},"filename":"psutil-3.1.0-cp33-none-win_amd64.whl","hashes":{"sha256":"92a7f420bc97f899b5abab30392c23ba652304aec18415f2d1167da04dae9913"},"provenance":null,"requires-python":null,"size":89918,"upload-time":"2015-07-15T00:42:29.864336Z","url":"https://files.pythonhosted.org/packages/c7/5a/4046949e207b72b93540f3e19d699813fd35e290ccdf48080f332226b912/psutil-3.1.0-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"e5e4b772973543fffe4b2ffb66ad397284be77c3b6886db90b1fca8245b0d0b1"},"data-dist-info-metadata":{"sha256":"e5e4b772973543fffe4b2ffb66ad397284be77c3b6886db90b1fca8245b0d0b1"},"filename":"psutil-3.1.0-cp34-none-win32.whl","hashes":{"sha256":"8f5a0e859ae6dcc349914fb9ea0acc21cfd82a321d1c1b02d3d92c195f523ccd"},"provenance":null,"requires-python":null,"size":87592,"upload-time":"2015-07-15T00:42:00.678342Z","url":"https://files.pythonhosted.org/packages/09/34/09d53d29318a5fea88bd30d629595805064a0e3776e706eca2d79ceaebac/psutil-3.1.0-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"e5e4b772973543fffe4b2ffb66ad397284be77c3b6886db90b1fca8245b0d0b1"},"data-dist-info-metadata":{"sha256":"e5e4b772973543fffe4b2ffb66ad397284be77c3b6886db90b1fca8245b0d0b1"},"filename":"psutil-3.1.0-cp34-none-win_amd64.whl","hashes":{"sha256":"dcb4f208ec28fb72b35d1edf49aa51f2cc116b439aa40c4c415cbfe1fee54078"},"provenance":null,"requires-python":null,"size":89877,"upload-time":"2015-07-15T00:42:45.261251Z","url":"https://files.pythonhosted.org/packages/0b/f6/62592864eb064763989aa5830706034f9ad3c6ae2255fb7cae0c66b336a1/psutil-3.1.0-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.0.tar.gz","hashes":{"sha256":"4cdfeb2a328b6f8a2937f9b21f513c8aeda96dc076ecafda424f5c401dbad876"},"provenance":null,"requires-python":null,"size":246767,"upload-time":"2015-07-15T00:40:47.134419Z","url":"https://files.pythonhosted.org/packages/ce/d2/ab7f80718b4eafb2e474b8b410274d2c0d65341b963d730e653be9ed0ec8/psutil-3.1.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.0.win-amd64-py2.7.exe","hashes":{"sha256":"6570fb3ddde83597e11c062e20ab86210ff84a1fa97e54bc8bda05e4cd34670a"},"provenance":null,"requires-python":null,"size":326179,"upload-time":"2015-07-15T00:42:09.118162Z","url":"https://files.pythonhosted.org/packages/26/f5/c76bf7ef62736913146b0482879705d1d877c9334092a0739f2b3bbae162/psutil-3.1.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.0.win-amd64-py3.3.exe","hashes":{"sha256":"578a52f4b108857273d1e32de4d9bebf9b8f842f8c53ab3e242252cfc9bde295"},"provenance":null,"requires-python":null,"size":324540,"upload-time":"2015-07-15T00:42:24.223021Z","url":"https://files.pythonhosted.org/packages/00/51/2dc07e5618adb4a3676ab6c9c1759a3f268eda91808d58f45eb4dfd3d2c5/psutil-3.1.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.0.win-amd64-py3.4.exe","hashes":{"sha256":"2aea29ca2a5ea318155fb856f24e6c7563c8741ccfeec862f0fd9af0f2d8ae87"},"provenance":null,"requires-python":null,"size":324490,"upload-time":"2015-07-15T00:42:38.312454Z","url":"https://files.pythonhosted.org/packages/da/76/dcefdcf88fd51becaff6c1ec0ba7566ca654a207fe6d69662723c645e3b0/psutil-3.1.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.0.win32-py2.6.exe","hashes":{"sha256":"271f1b6fcb4861d1b0fc7f612b2abaacb36c0f878fcc2908f1cf673337c3472c"},"provenance":null,"requires-python":null,"size":296215,"upload-time":"2015-07-15T00:41:08.168047Z","url":"https://files.pythonhosted.org/packages/3f/c2/f7ec0a70bc58c1918f814001682cca30f4b168f5b46dce913220e625dee6/psutil-3.1.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.0.win32-py2.7.exe","hashes":{"sha256":"1bde1cea6b7f9bd66202feee289c282b076c00419dc6404db357b05125f4d692"},"provenance":null,"requires-python":null,"size":296024,"upload-time":"2015-07-15T00:41:21.733811Z","url":"https://files.pythonhosted.org/packages/56/2f/c97adcba8f119a23d3580a3a95939c1a37d37d514b304d90912585a85521/psutil-3.1.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.0.win32-py3.3.exe","hashes":{"sha256":"667fa795ca5ccde216b769fc8572398598f1e0e2619f78605df1a4c75a475174"},"provenance":null,"requires-python":null,"size":290958,"upload-time":"2015-07-15T00:41:37.038168Z","url":"https://files.pythonhosted.org/packages/37/89/a949b02d66d600c45230a3a622f0d5f491182dbbc96fff9ffbcd869431bd/psutil-3.1.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.0.win32-py3.4.exe","hashes":{"sha256":"8e637fe2a23fad4f4ea8ae11402b593e09fc587b86a6ced40b6bc1017be8d978"},"provenance":null,"requires-python":null,"size":290975,"upload-time":"2015-07-15T00:41:53.144204Z","url":"https://files.pythonhosted.org/packages/53/3f/249ff2e418313f30a6dcc4995966725f72b00493848ffddeb72b506e8e50/psutil-3.1.0.win32-py3.4.exe","yanked":false},{"core-metadata":{"sha256":"cb2a855afe0d2280f15e0904acf47929db7a22fcf8be740f65cecf46586d4ca5"},"data-dist-info-metadata":{"sha256":"cb2a855afe0d2280f15e0904acf47929db7a22fcf8be740f65cecf46586d4ca5"},"filename":"psutil-3.1.1-cp26-none-win32.whl","hashes":{"sha256":"13a6377cc8d2859f846058170830127822877e05229c4a43aea893cdcb504d65"},"provenance":null,"requires-python":null,"size":87749,"upload-time":"2015-07-15T12:34:56.810625Z","url":"https://files.pythonhosted.org/packages/e1/9e/721afc99b6fe467b47fa2cad6899acc19b45dee32d30b498dc731b6c09ef/psutil-3.1.1-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"cb2a855afe0d2280f15e0904acf47929db7a22fcf8be740f65cecf46586d4ca5"},"data-dist-info-metadata":{"sha256":"cb2a855afe0d2280f15e0904acf47929db7a22fcf8be740f65cecf46586d4ca5"},"filename":"psutil-3.1.1-cp27-none-win32.whl","hashes":{"sha256":"5b7228cb69fdaea5aeb901704f5ecd21b7846aa60c2c8d408f22573fcbaa7e6f"},"provenance":null,"requires-python":null,"size":87554,"upload-time":"2015-07-15T12:35:15.647007Z","url":"https://files.pythonhosted.org/packages/07/60/c88366202816ba42b3d8e93e793c14d1ac5e71be30dd53c2d0117c106eec/psutil-3.1.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"cb2a855afe0d2280f15e0904acf47929db7a22fcf8be740f65cecf46586d4ca5"},"data-dist-info-metadata":{"sha256":"cb2a855afe0d2280f15e0904acf47929db7a22fcf8be740f65cecf46586d4ca5"},"filename":"psutil-3.1.1-cp27-none-win_amd64.whl","hashes":{"sha256":"da7650e2f3fcf06419d5ad75123e6c68b9bf5ff2a6c91d4c77aaed8e6f444fc4"},"provenance":null,"requires-python":null,"size":90065,"upload-time":"2015-07-15T12:36:14.571710Z","url":"https://files.pythonhosted.org/packages/fa/5b/8834e22cc22b6b0e9c2c68e240ab69754bed7c4c5388fb65abfa716f4a67/psutil-3.1.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"19405a3f0d3e28d54384ce148334aeef185226341d9cc2a81c2d2933432a1de3"},"data-dist-info-metadata":{"sha256":"19405a3f0d3e28d54384ce148334aeef185226341d9cc2a81c2d2933432a1de3"},"filename":"psutil-3.1.1-cp33-none-win32.whl","hashes":{"sha256":"f3d68eb44ba49e24a18d6f7934463478294a49152f97fea2eefe1e1e1ee957f3"},"provenance":null,"requires-python":null,"size":87562,"upload-time":"2015-07-15T12:35:34.924344Z","url":"https://files.pythonhosted.org/packages/cb/96/0eb8eb289681364d2cda2a22a7d1abeb0196b321ab95694335dd178a5b35/psutil-3.1.1-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"19405a3f0d3e28d54384ce148334aeef185226341d9cc2a81c2d2933432a1de3"},"data-dist-info-metadata":{"sha256":"19405a3f0d3e28d54384ce148334aeef185226341d9cc2a81c2d2933432a1de3"},"filename":"psutil-3.1.1-cp33-none-win_amd64.whl","hashes":{"sha256":"f9be0ae975b55a3b5d5a8b769560096d76184b60a56c6e88ff6b7ebecf1bc684"},"provenance":null,"requires-python":null,"size":89916,"upload-time":"2015-07-15T12:36:34.374903Z","url":"https://files.pythonhosted.org/packages/eb/13/a38bc1e0ac6f7c42dddd9c17a206877befb822ba3af9c3b0dab9e85911a6/psutil-3.1.1-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"19405a3f0d3e28d54384ce148334aeef185226341d9cc2a81c2d2933432a1de3"},"data-dist-info-metadata":{"sha256":"19405a3f0d3e28d54384ce148334aeef185226341d9cc2a81c2d2933432a1de3"},"filename":"psutil-3.1.1-cp34-none-win32.whl","hashes":{"sha256":"8f25aad572bde88d5ee0b3a11a75ff2ae3c8b0a334c4128d6f8eb4fc95172734"},"provenance":null,"requires-python":null,"size":87574,"upload-time":"2015-07-15T12:35:55.507642Z","url":"https://files.pythonhosted.org/packages/02/10/439ec497e3e38a8c493d0c67c56e23d106b85c8b9f616e8df9ec6ce1e606/psutil-3.1.1-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"19405a3f0d3e28d54384ce148334aeef185226341d9cc2a81c2d2933432a1de3"},"data-dist-info-metadata":{"sha256":"19405a3f0d3e28d54384ce148334aeef185226341d9cc2a81c2d2933432a1de3"},"filename":"psutil-3.1.1-cp34-none-win_amd64.whl","hashes":{"sha256":"4be182c273758dcdbd30827fdeecd889e27cb6a30238798e91bddeebc29cdc4f"},"provenance":null,"requires-python":null,"size":89865,"upload-time":"2015-07-15T12:36:55.016343Z","url":"https://files.pythonhosted.org/packages/ec/2e/8d98579399bc1979904455df182a063dd584b285ee8c141f3c94e7814c47/psutil-3.1.1-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.1.tar.gz","hashes":{"sha256":"d3290bd4a027fa0b3a2e2ee87728056fe49d4112640e2b8c2ea4dd94ba0cf057"},"provenance":null,"requires-python":null,"size":247284,"upload-time":"2015-07-15T12:33:47.020532Z","url":"https://files.pythonhosted.org/packages/8d/b3/954de176aa8e3a7782bae52ce938f24726c2c68d0f4c60d159271b6b293d/psutil-3.1.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.1.win-amd64-py2.7.exe","hashes":{"sha256":"9c4fd3cc19bbc04eaa7ef3c61e3db26a41ac5e056f770977211d4569d0bf0086"},"provenance":null,"requires-python":null,"size":326261,"upload-time":"2015-07-15T12:36:07.064169Z","url":"https://files.pythonhosted.org/packages/e8/ac/7fb95ccc69ced76d0920e411b2fdfd3d38398c4bce53ec1dae92800df88a/psutil-3.1.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.1.win-amd64-py3.3.exe","hashes":{"sha256":"e7cc26f661c9eaa9b32d0543dd7838daea72aad6e9f02fe73715ffd0dcb65170"},"provenance":null,"requires-python":null,"size":324622,"upload-time":"2015-07-15T12:36:27.201153Z","url":"https://files.pythonhosted.org/packages/2f/84/ae41f6bb61d4a93399c621218f99b761171a69a0c9163b9a72db1d53d62a/psutil-3.1.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.1.win-amd64-py3.4.exe","hashes":{"sha256":"46cbfd86d6762e63c7df4ab0df889f6f2fffa9b5781ea3fc0431237f2a408382"},"provenance":null,"requires-python":null,"size":324571,"upload-time":"2015-07-15T12:36:47.165677Z","url":"https://files.pythonhosted.org/packages/88/d4/ca15a913ab43222e308774845317544e765718a9e56bd4efe5b3cedf1fbd/psutil-3.1.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.1.win32-py2.6.exe","hashes":{"sha256":"9efbd578d2f400dfe0ecab123b58d8af105854fdbb6222f841151e010e820b75"},"provenance":null,"requires-python":null,"size":296306,"upload-time":"2015-07-15T12:34:49.442980Z","url":"https://files.pythonhosted.org/packages/0c/f6/e81385c7ec989157eb68688a64a69c5a7477ff93d544893a9e1f251588b1/psutil-3.1.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.1.win32-py2.7.exe","hashes":{"sha256":"e0065e7cade4ac5ac70411674bc32326dee8d11c44469012a2b5164bf6dea97a"},"provenance":null,"requires-python":null,"size":296106,"upload-time":"2015-07-15T12:35:08.277861Z","url":"https://files.pythonhosted.org/packages/6b/fe/51596968f5a6a0970d9424021989f542d5aa715fe21d1a9c6bbbb0e377a9/psutil-3.1.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.1.win32-py3.3.exe","hashes":{"sha256":"c8ab17e07ea4907d2f9129254e82b6765ae08e61f0ce6dc8e2fc1faf145b166c"},"provenance":null,"requires-python":null,"size":291039,"upload-time":"2015-07-15T12:35:27.250487Z","url":"https://files.pythonhosted.org/packages/83/49/ff116fb9981ef04a5aed1c091ace117c214ed752d37be267ea4e2f28efad/psutil-3.1.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.1.1.win32-py3.4.exe","hashes":{"sha256":"3003d8be6e86eb6beb990863a88950f9b9fe53ccaae92edcd8efcd152d7451ea"},"provenance":null,"requires-python":null,"size":291056,"upload-time":"2015-07-15T12:35:47.023807Z","url":"https://files.pythonhosted.org/packages/7b/cd/accf3d7e37006bffe7a569e4fc587eb686d275a19a4e8a37a12930a1e2db/psutil-3.1.1.win32-py3.4.exe","yanked":false},{"core-metadata":{"sha256":"e8c3ed176a6ecb754b5289ef149667a6343dc0380cb33db608d4d556be0650f4"},"data-dist-info-metadata":{"sha256":"e8c3ed176a6ecb754b5289ef149667a6343dc0380cb33db608d4d556be0650f4"},"filename":"psutil-3.2.0-cp27-none-win32.whl","hashes":{"sha256":"1493041336a591f22c77bcb815a399faf9bdac32f79f4de354eda3507a0d6d6b"},"provenance":null,"requires-python":null,"size":88077,"upload-time":"2015-09-02T11:56:33.437905Z","url":"https://files.pythonhosted.org/packages/5f/fc/5f317fd548909b1bbb111d462c072faf8af3938268f3e7dd3ab2f9181461/psutil-3.2.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"e8c3ed176a6ecb754b5289ef149667a6343dc0380cb33db608d4d556be0650f4"},"data-dist-info-metadata":{"sha256":"e8c3ed176a6ecb754b5289ef149667a6343dc0380cb33db608d4d556be0650f4"},"filename":"psutil-3.2.0-cp27-none-win_amd64.whl","hashes":{"sha256":"3744ee760dff697f45731a71e7902514aa043c99800cc8fabeb6bebc9dad973d"},"provenance":null,"requires-python":null,"size":90526,"upload-time":"2015-09-02T11:57:23.745435Z","url":"https://files.pythonhosted.org/packages/84/6c/7efbe64b42748125e7113a90e48c0da9859b7f0363ac85ca5617decbafee/psutil-3.2.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"86ea3a30e605b64b3e8a93353520057387c9224bf456a532eb4f25799e27b050"},"data-dist-info-metadata":{"sha256":"86ea3a30e605b64b3e8a93353520057387c9224bf456a532eb4f25799e27b050"},"filename":"psutil-3.2.0-cp33-none-win32.whl","hashes":{"sha256":"8836f77d2c4ae2935431ca66e445435b87b53b4db637fcceb438b78843239210"},"provenance":null,"requires-python":null,"size":88080,"upload-time":"2015-09-02T11:56:48.988091Z","url":"https://files.pythonhosted.org/packages/64/8e/0a06028a1ac093402885febf2aeb18093f1d28ae2110c7eb10b43e7554c1/psutil-3.2.0-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"86ea3a30e605b64b3e8a93353520057387c9224bf456a532eb4f25799e27b050"},"data-dist-info-metadata":{"sha256":"86ea3a30e605b64b3e8a93353520057387c9224bf456a532eb4f25799e27b050"},"filename":"psutil-3.2.0-cp33-none-win_amd64.whl","hashes":{"sha256":"b16eb62d9c21efaa2c9ac8a9f8b23bb7a695cb799b597edf4b1289ce8e6973ac"},"provenance":null,"requires-python":null,"size":90372,"upload-time":"2015-09-02T12:03:01.254324Z","url":"https://files.pythonhosted.org/packages/cd/29/a8383040200a3ebe0e985f54f35691cc078a1deb632abb5340d3deb5b7b7/psutil-3.2.0-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"86ea3a30e605b64b3e8a93353520057387c9224bf456a532eb4f25799e27b050"},"data-dist-info-metadata":{"sha256":"86ea3a30e605b64b3e8a93353520057387c9224bf456a532eb4f25799e27b050"},"filename":"psutil-3.2.0-cp34-none-win32.whl","hashes":{"sha256":"0ac1d68ab3c5a65641cbbb23d19deda466f73226f9d967f91436851995281777"},"provenance":null,"requires-python":null,"size":88096,"upload-time":"2015-09-02T11:57:08.035697Z","url":"https://files.pythonhosted.org/packages/07/c3/76a50982a82c0e9d93d9614a0cd06644c1d3406c9bb80a43f95abdd4ab97/psutil-3.2.0-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"86ea3a30e605b64b3e8a93353520057387c9224bf456a532eb4f25799e27b050"},"data-dist-info-metadata":{"sha256":"86ea3a30e605b64b3e8a93353520057387c9224bf456a532eb4f25799e27b050"},"filename":"psutil-3.2.0-cp34-none-win_amd64.whl","hashes":{"sha256":"0b26ef262fe2d10185ab562cd0530af7f6d9a6744c631c44e64be94796f4ba2d"},"provenance":null,"requires-python":null,"size":90354,"upload-time":"2015-09-02T12:07:12.010031Z","url":"https://files.pythonhosted.org/packages/58/2d/8b7abb9b6f8956d9a6dfc3b1dffce27efab8c7c0497a6366e7fee444ae53/psutil-3.2.0-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.0.tar.gz","hashes":{"sha256":"06f9d255f8b12a6a04aa2b468ec453c539f54a464d110b3458c32b0152a5c943"},"provenance":null,"requires-python":null,"size":251988,"upload-time":"2015-09-02T11:59:23.849318Z","url":"https://files.pythonhosted.org/packages/1d/3a/d396274e6f086e342dd43401d4012973af98c00b3aabdb5cc4a432df660e/psutil-3.2.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.0.win-amd64-py2.7.exe","hashes":{"sha256":"71fd8712715f8e6acc5bee5719a83a61a396067cf2bfb15b4d8f1f2955648637"},"provenance":null,"requires-python":null,"size":326895,"upload-time":"2015-09-02T11:57:17.538160Z","url":"https://files.pythonhosted.org/packages/e1/49/990073ab7e010965a7d0df5e48181d07c212edd7fafb890feda664ea9b3c/psutil-3.2.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.0.win32-py2.7.exe","hashes":{"sha256":"2a8b5878d4e787d81a1eeddcc09ff28d501a3ceb320c7fffa7e207da5d61d01c"},"provenance":null,"requires-python":null,"size":296802,"upload-time":"2015-09-02T11:56:26.879142Z","url":"https://files.pythonhosted.org/packages/0b/c5/d6ad511c3c17afa9837d08fc26d76e85dc83ebc304c6c7bec3970e74f240/psutil-3.2.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.0.win32-py3.3.exe","hashes":{"sha256":"adfc63ceede4e8f6bf21e4bdf6fc91f70f9612ec2b1bf9ad306828909bb71c52"},"provenance":null,"requires-python":null,"size":291738,"upload-time":"2015-09-02T11:56:41.826609Z","url":"https://files.pythonhosted.org/packages/e3/35/81842c6c4366d19c87d1a1fb4ad4e4d22a18aa9facaea8b6f12ccd4c1212/psutil-3.2.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.0.win32-py3.4.exe","hashes":{"sha256":"4098e0ed7930003ef15feb852e64f73180c17a651c4170fb5573f8c44622d068"},"provenance":null,"requires-python":null,"size":291748,"upload-time":"2015-09-02T11:56:58.479934Z","url":"https://files.pythonhosted.org/packages/e9/e8/5b432a0490328cfff86605b574d2aa31b1fac4e61587dff5a7f76d4cb95e/psutil-3.2.0.win32-py3.4.exe","yanked":false},{"core-metadata":{"sha256":"c06dad4d23110956ffef0ab97d2845cb62aba9d194019b18cf8e24cbd88c0781"},"data-dist-info-metadata":{"sha256":"c06dad4d23110956ffef0ab97d2845cb62aba9d194019b18cf8e24cbd88c0781"},"filename":"psutil-3.2.1-cp26-none-win32.whl","hashes":{"sha256":"a77230ecd6f42d0b549f8eb6aa105f14e4bc5908c754d6e10ff979c900934481"},"provenance":null,"requires-python":null,"size":88306,"upload-time":"2015-09-03T15:37:28.878155Z","url":"https://files.pythonhosted.org/packages/37/46/f348f7728dea66436abdfc9fa14ef017e0148c6bca08a822ee4dd7cb6d75/psutil-3.2.1-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"c06dad4d23110956ffef0ab97d2845cb62aba9d194019b18cf8e24cbd88c0781"},"data-dist-info-metadata":{"sha256":"c06dad4d23110956ffef0ab97d2845cb62aba9d194019b18cf8e24cbd88c0781"},"filename":"psutil-3.2.1-cp27-none-win32.whl","hashes":{"sha256":"9453b8ceb249d4d9ddc69153729761be340dfef9c99509390e4fb0f1fcbb3853"},"provenance":null,"requires-python":null,"size":88111,"upload-time":"2015-09-03T15:30:11.278302Z","url":"https://files.pythonhosted.org/packages/ba/27/f55ca7d15af50e731e9bbbff9b22fc31a40b786c02f85d173568e5084152/psutil-3.2.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"c06dad4d23110956ffef0ab97d2845cb62aba9d194019b18cf8e24cbd88c0781"},"data-dist-info-metadata":{"sha256":"c06dad4d23110956ffef0ab97d2845cb62aba9d194019b18cf8e24cbd88c0781"},"filename":"psutil-3.2.1-cp27-none-win_amd64.whl","hashes":{"sha256":"014714beed46a66370834cebe0bbb53799bddc164f7f0149a4a70e2051f7bc1a"},"provenance":null,"requires-python":null,"size":90561,"upload-time":"2015-09-03T15:35:05.710499Z","url":"https://files.pythonhosted.org/packages/6b/ac/5da840018ce300a258925d4535a55a32b75236d5d777a8de6c3de18e71f3/psutil-3.2.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"51cd3e3b33e3b710f799f247a23955f942e6e11d4882ee5e3f3af539eee619c8"},"data-dist-info-metadata":{"sha256":"51cd3e3b33e3b710f799f247a23955f942e6e11d4882ee5e3f3af539eee619c8"},"filename":"psutil-3.2.1-cp33-none-win32.whl","hashes":{"sha256":"5c0daf045fd7d7f105863a5f9508d1698559ebbdfd70d7d8b6fe6fedde575735"},"provenance":null,"requires-python":null,"size":88117,"upload-time":"2015-09-03T15:32:19.746087Z","url":"https://files.pythonhosted.org/packages/c7/96/3ae14e4bf81f18f404bb5285fcda28e50ae6df87e91ad6bf45a9a4f51ac3/psutil-3.2.1-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"51cd3e3b33e3b710f799f247a23955f942e6e11d4882ee5e3f3af539eee619c8"},"data-dist-info-metadata":{"sha256":"51cd3e3b33e3b710f799f247a23955f942e6e11d4882ee5e3f3af539eee619c8"},"filename":"psutil-3.2.1-cp33-none-win_amd64.whl","hashes":{"sha256":"452622592564cd67f86808c8176720c1443d43e248cfd242d71cff559ed1424c"},"provenance":null,"requires-python":null,"size":90410,"upload-time":"2015-09-03T15:36:13.351055Z","url":"https://files.pythonhosted.org/packages/76/62/fe6f705cb331be5fcc97b268987527dcdb3f3aa104bf830b0ec8bf1e2ad4/psutil-3.2.1-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"51cd3e3b33e3b710f799f247a23955f942e6e11d4882ee5e3f3af539eee619c8"},"data-dist-info-metadata":{"sha256":"51cd3e3b33e3b710f799f247a23955f942e6e11d4882ee5e3f3af539eee619c8"},"filename":"psutil-3.2.1-cp34-none-win32.whl","hashes":{"sha256":"96379bee09d4c6b4d57d72cb7347dbc51b6847977f2fad01cdfefef3b53e44e3"},"provenance":null,"requires-python":null,"size":88131,"upload-time":"2015-09-03T15:33:36.571683Z","url":"https://files.pythonhosted.org/packages/31/ec/1a54f23c767e27dac09d2372f3522f88ef34f3a0ddd44c16122970259f6f/psutil-3.2.1-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"51cd3e3b33e3b710f799f247a23955f942e6e11d4882ee5e3f3af539eee619c8"},"data-dist-info-metadata":{"sha256":"51cd3e3b33e3b710f799f247a23955f942e6e11d4882ee5e3f3af539eee619c8"},"filename":"psutil-3.2.1-cp34-none-win_amd64.whl","hashes":{"sha256":"7a7f4f2ed6d2835c48c24a81b251ba4f9b21f6bba2323291f8205c9ecb6f659d"},"provenance":null,"requires-python":null,"size":90387,"upload-time":"2015-09-03T15:36:29.262691Z","url":"https://files.pythonhosted.org/packages/2e/10/f1590ae942a6b8dd2bdeef6088e30e89b30161a264881b14134f3c4a3a0e/psutil-3.2.1-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.1.tar.gz","hashes":{"sha256":"7f6bea8bfe2e5cfffd0f411aa316e837daadced1893b44254bb9a38a654340f7"},"provenance":null,"requires-python":null,"size":251653,"upload-time":"2015-09-03T15:30:34.118573Z","url":"https://files.pythonhosted.org/packages/cd/5f/4fae1036903c01929c48ded6800a8705106ee20f9e39e3f2ad5d1824e210/psutil-3.2.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.1.win-amd64-py2.7.exe","hashes":{"sha256":"e9a8a44f3847a0e20a54d321ed62de4e9cee5bc4e880e25fe88ae20cfa4e32b2"},"provenance":null,"requires-python":null,"size":327122,"upload-time":"2015-09-03T15:34:58.714500Z","url":"https://files.pythonhosted.org/packages/c5/af/4ab069ba93a037a4acf9bb84248daa44204a46687abc6a9f3a82ad8c5ee2/psutil-3.2.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.1.win-amd64-py3.3.exe","hashes":{"sha256":"5a62c38852de4513f1816b9c431a94f02531619c1edc60a2cc163c8754f51c50"},"provenance":null,"requires-python":null,"size":325481,"upload-time":"2015-09-03T15:36:01.424492Z","url":"https://files.pythonhosted.org/packages/32/87/82e449ff9573dde3c78685b64ac3d17b5d19db11e976eab27c4dc5dca942/psutil-3.2.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.1.win-amd64-py3.4.exe","hashes":{"sha256":"da898c0708b99b3892bfb7d5caebb447d14d03c7a655c55e484eb5fcc741c3ca"},"provenance":null,"requires-python":null,"size":325465,"upload-time":"2015-09-03T15:36:22.916532Z","url":"https://files.pythonhosted.org/packages/4b/11/f18b29033a0b383e67f664576eca59fbe8552a9fd97f9f22d6d0ff1c4951/psutil-3.2.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.1.win32-py2.6.exe","hashes":{"sha256":"0e5fe3d50f9f8d9a5216cfa23f56890aa0c6a6163869434001f4f2ba463dace5"},"provenance":null,"requires-python":null,"size":297224,"upload-time":"2015-09-03T15:37:22.791161Z","url":"https://files.pythonhosted.org/packages/c0/bb/ed28c191c4d9f27b60d9ea6bd7774b44d78778f0b1fb507ee1e789a490d7/psutil-3.2.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.1.win32-py2.7.exe","hashes":{"sha256":"9fb6f11bdd3fdbe1e611ae02b3ad3dff8f70ef6eaa694d13e8ad0906fd7a7261"},"provenance":null,"requires-python":null,"size":297025,"upload-time":"2015-09-03T15:30:14.327211Z","url":"https://files.pythonhosted.org/packages/9a/c1/075598067efefe25f6a2b0cf1b3eb896322597ee64ba097cc6611b89ada7/psutil-3.2.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.1.win32-py3.3.exe","hashes":{"sha256":"605cc7dbe2170e89f2f6709cf1577c8a02f89951fe4a0eb48e72530f605141ca"},"provenance":null,"requires-python":null,"size":291964,"upload-time":"2015-09-03T15:32:12.890526Z","url":"https://files.pythonhosted.org/packages/e0/ce/ff1db37cbdf6b3071e89afe4fced6f73d2cdf9c3a87696e7754d207e0ec5/psutil-3.2.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.1.win32-py3.4.exe","hashes":{"sha256":"0cd127239f527eae6c0f778dd41bb3ced84e6049b919713022c9b72e5f22a1c1"},"provenance":null,"requires-python":null,"size":291976,"upload-time":"2015-09-03T15:33:29.509014Z","url":"https://files.pythonhosted.org/packages/5d/a0/d624d4f5660a476821fe0d920a4c2e995a23151928b15cc3383379228f15/psutil-3.2.1.win32-py3.4.exe","yanked":false},{"core-metadata":{"sha256":"7115f58a06724cf8a31f365d5561fc59f6d68d8e55e20b3d75bf373a45007f7d"},"data-dist-info-metadata":{"sha256":"7115f58a06724cf8a31f365d5561fc59f6d68d8e55e20b3d75bf373a45007f7d"},"filename":"psutil-3.2.2-cp26-none-win32.whl","hashes":{"sha256":"5a8ce70327c0da578a31ebbf0042671ed9be6f4b6b022c02f03302b690074966"},"provenance":null,"requires-python":null,"size":88377,"upload-time":"2015-10-04T16:38:11.705751Z","url":"https://files.pythonhosted.org/packages/4d/af/5b8c2471ea942a4b6ee85706e9279284ae9dc86ee30b6f97db2d84a95433/psutil-3.2.2-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"7115f58a06724cf8a31f365d5561fc59f6d68d8e55e20b3d75bf373a45007f7d"},"data-dist-info-metadata":{"sha256":"7115f58a06724cf8a31f365d5561fc59f6d68d8e55e20b3d75bf373a45007f7d"},"filename":"psutil-3.2.2-cp27-none-win32.whl","hashes":{"sha256":"6c5809582d3d165511d2319401bd0f6c0e825d7853e49da59027c1fb8aa8f897"},"provenance":null,"requires-python":null,"size":88183,"upload-time":"2015-10-04T16:38:39.878475Z","url":"https://files.pythonhosted.org/packages/63/1e/a510f3f310b5f530336fbc708fb1456bf3e49e3b3d85c31d151b6e389c4f/psutil-3.2.2-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"7115f58a06724cf8a31f365d5561fc59f6d68d8e55e20b3d75bf373a45007f7d"},"data-dist-info-metadata":{"sha256":"7115f58a06724cf8a31f365d5561fc59f6d68d8e55e20b3d75bf373a45007f7d"},"filename":"psutil-3.2.2-cp27-none-win_amd64.whl","hashes":{"sha256":"37f1cc8fc7586cc930ea3737533d6d79c1f761d577fd1bb1bb5798ccd1543b53"},"provenance":null,"requires-python":null,"size":90637,"upload-time":"2015-10-04T16:40:05.379646Z","url":"https://files.pythonhosted.org/packages/e7/b7/f04d64a692159733ed383b4638abd9d3dc4538d4aacb5e193af02a3840a2/psutil-3.2.2-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"93db84a195bf6aa1de88b86ddb7fc98d3600b0ecf0ff4ab69e447ca200c3a4b0"},"data-dist-info-metadata":{"sha256":"93db84a195bf6aa1de88b86ddb7fc98d3600b0ecf0ff4ab69e447ca200c3a4b0"},"filename":"psutil-3.2.2-cp33-none-win32.whl","hashes":{"sha256":"f8fb145f8fa9e223696ff2f99924ea42538f3ad6b9738707292d840acbde528f"},"provenance":null,"requires-python":null,"size":88189,"upload-time":"2015-10-04T16:39:00.887284Z","url":"https://files.pythonhosted.org/packages/77/f3/6b3742040b634692393faf3a81e6c0e40366c22bc338ad3fc62ed21b157a/psutil-3.2.2-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"93db84a195bf6aa1de88b86ddb7fc98d3600b0ecf0ff4ab69e447ca200c3a4b0"},"data-dist-info-metadata":{"sha256":"93db84a195bf6aa1de88b86ddb7fc98d3600b0ecf0ff4ab69e447ca200c3a4b0"},"filename":"psutil-3.2.2-cp33-none-win_amd64.whl","hashes":{"sha256":"65c78ba625cf9761d5966603838cc959f396bd03536c480db69f8cf37bdf9994"},"provenance":null,"requires-python":null,"size":90471,"upload-time":"2015-10-04T16:40:27.009383Z","url":"https://files.pythonhosted.org/packages/59/33/3ccdbec4ef1452758ba80f711af46736717f63d73786744d6251afb68624/psutil-3.2.2-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"93db84a195bf6aa1de88b86ddb7fc98d3600b0ecf0ff4ab69e447ca200c3a4b0"},"data-dist-info-metadata":{"sha256":"93db84a195bf6aa1de88b86ddb7fc98d3600b0ecf0ff4ab69e447ca200c3a4b0"},"filename":"psutil-3.2.2-cp34-none-win32.whl","hashes":{"sha256":"3df8d3e32e2b4f7c2ea91014294844670eddb125ba76c24152c0a155a1f73b5b"},"provenance":null,"requires-python":null,"size":88202,"upload-time":"2015-10-04T16:39:39.989654Z","url":"https://files.pythonhosted.org/packages/62/6e/ee3597f32c650f744359e57fd18bcede773dd7465d392dabbb008bc79b48/psutil-3.2.2-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"93db84a195bf6aa1de88b86ddb7fc98d3600b0ecf0ff4ab69e447ca200c3a4b0"},"data-dist-info-metadata":{"sha256":"93db84a195bf6aa1de88b86ddb7fc98d3600b0ecf0ff4ab69e447ca200c3a4b0"},"filename":"psutil-3.2.2-cp34-none-win_amd64.whl","hashes":{"sha256":"e321d3f029268bc8442a7ff214da43fe91041924898f5e23d88bfda7ecb81acc"},"provenance":null,"requires-python":null,"size":90463,"upload-time":"2015-10-04T16:40:56.120875Z","url":"https://files.pythonhosted.org/packages/ed/fe/f31bb708dfdecfbc59b946ecb9ee3379fe7a8183c37ea6c43d6f4da5117d/psutil-3.2.2-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"826e3762fc7145422d64b4a51bb0f8ca6b3a05baed01f7ef3c10b391a0a6c865"},"data-dist-info-metadata":{"sha256":"826e3762fc7145422d64b4a51bb0f8ca6b3a05baed01f7ef3c10b391a0a6c865"},"filename":"psutil-3.2.2-cp35-none-win32.whl","hashes":{"sha256":"76c68c9005a2aa983fce440ef98b66e6f200f740f52064a90fdcc30d11771bc2"},"provenance":null,"requires-python":null,"size":90363,"upload-time":"2015-11-06T10:48:59.454097Z","url":"https://files.pythonhosted.org/packages/cb/79/fcedcf009ab9f8c605f2f345b1797b72134ecc6c9c9f786575e34b3471bc/psutil-3.2.2-cp35-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"826e3762fc7145422d64b4a51bb0f8ca6b3a05baed01f7ef3c10b391a0a6c865"},"data-dist-info-metadata":{"sha256":"826e3762fc7145422d64b4a51bb0f8ca6b3a05baed01f7ef3c10b391a0a6c865"},"filename":"psutil-3.2.2-cp35-none-win_amd64.whl","hashes":{"sha256":"d3ac8ad04a509819d7b5d58e453749a3ceb37253267dac6e8856ea7953e22ca0"},"provenance":null,"requires-python":null,"size":93288,"upload-time":"2015-11-06T10:50:14.699381Z","url":"https://files.pythonhosted.org/packages/03/c5/15e44d590afc788228e93cdacf55f98828f326de985242bbf03b3545b129/psutil-3.2.2-cp35-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.2.tar.gz","hashes":{"sha256":"f9d848e5bd475ffe7fa3ab1c20d249807e648568af64bb0058412296ec990a0c"},"provenance":null,"requires-python":null,"size":253502,"upload-time":"2015-10-04T16:39:43.138939Z","url":"https://files.pythonhosted.org/packages/dc/b2/ab65a2209b996c891209b8a7444a0c825125fba850efaec07b95bccb3ff5/psutil-3.2.2.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.2.win-amd64-py2.7.exe","hashes":{"sha256":"1fca15005063b401cbf94cebe3c01ef6ba3d86ba563730d5d5d6be962a637cf4"},"provenance":null,"requires-python":null,"size":327220,"upload-time":"2015-10-04T16:39:56.198669Z","url":"https://files.pythonhosted.org/packages/01/7c/47b7ac498c9dd6ac9f9b4489d3bd9cea8158e774592661a7c956a815dc78/psutil-3.2.2.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.2.win-amd64-py3.3.exe","hashes":{"sha256":"01a1f55819019ad13c288c41cb233e17a6ee648baf19591a70f6c2c2295dde6c"},"provenance":null,"requires-python":null,"size":325577,"upload-time":"2015-10-04T16:40:17.960471Z","url":"https://files.pythonhosted.org/packages/5d/89/b3bfca24d038b16af09055912e3a5bc35347ebeb0e6af322959b68dbf237/psutil-3.2.2.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.2.win-amd64-py3.5.exe","hashes":{"sha256":"9afc68c02717fb4416f91b3c2da4c407756f683804383d1499cdc9e1512e7942"},"provenance":null,"requires-python":null,"size":242452,"upload-time":"2015-11-06T10:49:50.288504Z","url":"https://files.pythonhosted.org/packages/1d/0b/9582cadcba005f4eb0207107baeae7eb2382431fea5e4bc0ea7ce683430f/psutil-3.2.2.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.2.win32-py2.6.exe","hashes":{"sha256":"c7c516a83d072d1a375ed3a0b5a1b1b9307ad839011e8d30aa16b0f932f6c481"},"provenance":null,"requires-python":null,"size":297317,"upload-time":"2015-10-04T16:38:04.476936Z","url":"https://files.pythonhosted.org/packages/66/e4/bb85391bb46b607be0578e0a091bc064daeb2d1c2e80aa2dab89260dff00/psutil-3.2.2.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.2.win32-py3.3.exe","hashes":{"sha256":"5168f99f065f6116ad1e8529bd5dd5309815198c6250c9180ff6058c6a3641d9"},"provenance":null,"requires-python":null,"size":292060,"upload-time":"2015-10-04T16:38:46.104177Z","url":"https://files.pythonhosted.org/packages/8e/86/8f1e1c0ffc0530dca5e71777f04fb90833c92d3ffc1d075b8b546874eae5/psutil-3.2.2.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.2.win32-py3.4.exe","hashes":{"sha256":"d5c96aa591ba711dfdadb1ab7a0adf08c5e637644a21437f3a39a9e427aa969b"},"provenance":null,"requires-python":null,"size":292515,"upload-time":"2015-11-06T01:39:51.263901Z","url":"https://files.pythonhosted.org/packages/28/fc/08f1098976de5416cd15967d88e03297569e9a34ca875e7dee38ff9150f0/psutil-3.2.2.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.2.2.win32-py3.5.exe","hashes":{"sha256":"f0e88f94f9822fd34bcd927aba4bb606bcb0ad0dda1543c9333e8175d5c05822"},"provenance":null,"requires-python":null,"size":232367,"upload-time":"2015-11-06T10:48:42.065794Z","url":"https://files.pythonhosted.org/packages/1d/b8/b725f9bd884f75ce141a9e871a79b2bdd1b5f31e814fc4e396d9ff7c98a2/psutil-3.2.2.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"5977c4a46aa367cb1f96ab1a6c70f8c803d8194d6a6440a31450ac202114431f"},"data-dist-info-metadata":{"sha256":"5977c4a46aa367cb1f96ab1a6c70f8c803d8194d6a6440a31450ac202114431f"},"filename":"psutil-3.3.0-cp26-none-win32.whl","hashes":{"sha256":"584f0b29fcc5d523b433cb8918b2fc74d67e30ee0b44a95baf031528f424619f"},"provenance":null,"requires-python":null,"size":90099,"upload-time":"2015-11-25T18:49:50.211423Z","url":"https://files.pythonhosted.org/packages/91/75/c20c3b9f4d3feb3436d607f498744e46dd28b265b8a72509812322198c7c/psutil-3.3.0-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"5977c4a46aa367cb1f96ab1a6c70f8c803d8194d6a6440a31450ac202114431f"},"data-dist-info-metadata":{"sha256":"5977c4a46aa367cb1f96ab1a6c70f8c803d8194d6a6440a31450ac202114431f"},"filename":"psutil-3.3.0-cp26-none-win_amd64.whl","hashes":{"sha256":"28ca0b6e9d99aa8dc286e8747a4471362b69812a25291de29b6a8d70a1545a0d"},"provenance":null,"requires-python":null,"size":92645,"upload-time":"2015-11-25T18:50:32.375134Z","url":"https://files.pythonhosted.org/packages/6a/d1/0ce316e4346bcae9dd23911366d894eda65875b88ff447ec8f0402ce556b/psutil-3.3.0-cp26-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"5977c4a46aa367cb1f96ab1a6c70f8c803d8194d6a6440a31450ac202114431f"},"data-dist-info-metadata":{"sha256":"5977c4a46aa367cb1f96ab1a6c70f8c803d8194d6a6440a31450ac202114431f"},"filename":"psutil-3.3.0-cp27-none-win32.whl","hashes":{"sha256":"167ad5fff52a672c4ddc1c1a0b25146d6813ebb08a9aab0a3ac45f8a5b669c3b"},"provenance":null,"requires-python":null,"size":90131,"upload-time":"2015-11-25T01:20:43.015358Z","url":"https://files.pythonhosted.org/packages/91/73/1f55b4a19db535759fec5fdbdd0653d7192336557078e3ac9085d7d77cd1/psutil-3.3.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"5977c4a46aa367cb1f96ab1a6c70f8c803d8194d6a6440a31450ac202114431f"},"data-dist-info-metadata":{"sha256":"5977c4a46aa367cb1f96ab1a6c70f8c803d8194d6a6440a31450ac202114431f"},"filename":"psutil-3.3.0-cp27-none-win_amd64.whl","hashes":{"sha256":"e6dea6173a988727bb223d3497349ad5cdef5c0b282eff2d83e5f9065c53f85f"},"provenance":null,"requires-python":null,"size":92586,"upload-time":"2015-11-25T01:22:19.444688Z","url":"https://files.pythonhosted.org/packages/a2/ab/d15a34c6b9090d58601541f8f5564f5b48d01e82f56e07593be969d529e7/psutil-3.3.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"data-dist-info-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"filename":"psutil-3.3.0-cp33-none-win32.whl","hashes":{"sha256":"2af5e0a4aad66049955d0734aa4e3dc8caa17a9eaf8b4c1a27a5f1ee6e40f6fc"},"provenance":null,"requires-python":null,"size":90141,"upload-time":"2015-11-25T01:21:10.374023Z","url":"https://files.pythonhosted.org/packages/c5/1f/5038a2567f5853ea1e0fb55f795c30b339a318717573c5b0c85b8814d733/psutil-3.3.0-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"data-dist-info-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"filename":"psutil-3.3.0-cp33-none-win_amd64.whl","hashes":{"sha256":"d9884dc0dc2e55e2448e495778dc9899c1c8bf37aeb2f434c1bea74af93c2683"},"provenance":null,"requires-python":null,"size":92432,"upload-time":"2015-11-25T01:22:39.035467Z","url":"https://files.pythonhosted.org/packages/b1/9c/a9cd75c8cfbac44397a2ca76430229c5496b21e0ab93cba5987d80e3f262/psutil-3.3.0-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"data-dist-info-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"filename":"psutil-3.3.0-cp34-none-win32.whl","hashes":{"sha256":"e27c2fe6dfcc8738be3d2c5a022f785eb72971057e1a9e1e34fba73bce8a71a6"},"provenance":null,"requires-python":null,"size":90151,"upload-time":"2015-11-25T01:21:33.616930Z","url":"https://files.pythonhosted.org/packages/23/57/6a7c3ab4d04d055cada3b5511c40e0e699d8dd5d8217cae6fb68ae61dff6/psutil-3.3.0-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"data-dist-info-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"filename":"psutil-3.3.0-cp34-none-win_amd64.whl","hashes":{"sha256":"65afd6fecc8f3aed09ee4be63583bc8eb472f06ceaa4fe24c4d1d5a1a3c0e13f"},"provenance":null,"requires-python":null,"size":92416,"upload-time":"2015-11-25T01:23:04.575877Z","url":"https://files.pythonhosted.org/packages/5d/a8/e62ec8105350c1e615ac84b084c7c8799d09e0d1b4530d3e68291dca8976/psutil-3.3.0-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"data-dist-info-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"filename":"psutil-3.3.0-cp35-none-win32.whl","hashes":{"sha256":"ba1c558fbfcdf94515c2394b1155c1dc56e2bc2a9c17d30349827c9ed8a67e46"},"provenance":null,"requires-python":null,"size":91965,"upload-time":"2015-11-25T01:21:52.452732Z","url":"https://files.pythonhosted.org/packages/6e/6d/cf51e672eef1f1fbf9efce429d5411d4a2f3aa239e079b82531389562cd2/psutil-3.3.0-cp35-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"data-dist-info-metadata":{"sha256":"60f0487057f863a2f4cee79fc120f02b60168ae677fac14ba9ccc63127b37514"},"filename":"psutil-3.3.0-cp35-none-win_amd64.whl","hashes":{"sha256":"ba95ea0022dcb64d36f0c1335c0605fae35bdf3e0fea8d92f5d0f6456a35e55b"},"provenance":null,"requires-python":null,"size":94891,"upload-time":"2015-11-25T01:23:25.107121Z","url":"https://files.pythonhosted.org/packages/90/49/3726db12f0fa7ff8f7e5493cc128ee6b40f5720f7397a4ef01db9e28dd7b/psutil-3.3.0-cp35-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.3.0.tar.gz","hashes":{"sha256":"421b6591d16b509aaa8d8c15821d66bb94cb4a8dc4385cad5c51b85d4a096d85"},"provenance":null,"requires-python":null,"size":261983,"upload-time":"2015-11-25T01:20:55.681846Z","url":"https://files.pythonhosted.org/packages/fe/69/c0d8e9b9f8a58cbf71aa4cf7f27c27ee0ab05abe32d9157ec22e223edef4/psutil-3.3.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.3.0.win-amd64-py2.6.exe","hashes":{"sha256":"326b305cbdb6f94dafbfe2c26b11da88b0ab07b8a07f8188ab9d75ff0c6e841a"},"provenance":null,"requires-python":null,"size":329214,"upload-time":"2015-11-25T18:50:19.881553Z","url":"https://files.pythonhosted.org/packages/a9/c1/7642d44312cffaa1b3efc6ac5252b7f1ab1c528903b55d56d7bd46805d92/psutil-3.3.0.win-amd64-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.3.0.win-amd64-py2.7.exe","hashes":{"sha256":"9aede5b2b6fe46b3748ea8e5214443890d1634027bef3d33b7dad16556830278"},"provenance":null,"requires-python":null,"size":329153,"upload-time":"2015-11-25T01:22:03.853435Z","url":"https://files.pythonhosted.org/packages/bb/e0/f8e4e286bf9c075f0e9fb3c0b17cecef04cda5e91f4c54982b91b3baf338/psutil-3.3.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.3.0.win-amd64-py3.3.exe","hashes":{"sha256":"73bed1db894d1aa9c3c7e611d302cdeab7ae8a0dc0eeaf76727878db1ac5cd87"},"provenance":null,"requires-python":null,"size":327512,"upload-time":"2015-11-25T01:22:31.545622Z","url":"https://files.pythonhosted.org/packages/fb/20/9438b78a3155b1eb480a4ea09dab6370f06e0a003cf43c3975743e0c9e8d/psutil-3.3.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.3.0.win-amd64-py3.4.exe","hashes":{"sha256":"935b5dd6d558af512f42501a7c08f41d7aff139af1bb3959daa3abb859234d6c"},"provenance":null,"requires-python":null,"size":327497,"upload-time":"2015-11-25T01:22:51.620885Z","url":"https://files.pythonhosted.org/packages/ae/71/c68af8e9b05144de969da58e1bf5ebfe0859b1c83b827e05ae3116178bb1/psutil-3.3.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.3.0.win-amd64-py3.5.exe","hashes":{"sha256":"4ca0111cf157dcc0f2f69a323c5b5478718d68d45fc9435d84be0ec0f186215b"},"provenance":null,"requires-python":null,"size":243943,"upload-time":"2015-11-25T01:23:16.182734Z","url":"https://files.pythonhosted.org/packages/bd/14/a67db75c827761bf55a50c6ce455cdf0fd7e75d1c7c395b7283359676288/psutil-3.3.0.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.3.0.win32-py2.6.exe","hashes":{"sha256":"b6f13c95398a3fcf0226c4dcfa448560ba5865259cd96ec2810658651e932189"},"provenance":null,"requires-python":null,"size":299025,"upload-time":"2015-11-25T18:49:29.675333Z","url":"https://files.pythonhosted.org/packages/ad/ea/d7c41ad9fab6e89263225c66971f9807a0396925dddf7c20901b637b99e2/psutil-3.3.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.3.0.win32-py2.7.exe","hashes":{"sha256":"ee6be30d1635bbdea4c4325d507dc8a0dbbde7e1c198bd62ddb9f43198b9e214"},"provenance":null,"requires-python":null,"size":299056,"upload-time":"2015-11-25T01:20:34.764583Z","url":"https://files.pythonhosted.org/packages/15/f7/a34370848c11d7d7933c0c107763ee470b54a7e48aa90a301919a3ad6757/psutil-3.3.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.3.0.win32-py3.3.exe","hashes":{"sha256":"dfa786858c268d7fbbe1b6175e001ec02738d7cfae0a7ce77bf9b651af676729"},"provenance":null,"requires-python":null,"size":293994,"upload-time":"2015-11-25T01:20:57.318743Z","url":"https://files.pythonhosted.org/packages/71/1e/af5675d52b426857441c29ad88d4fccfd55d300867ad02531d77991ab661/psutil-3.3.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.3.0.win32-py3.4.exe","hashes":{"sha256":"aa77f9de72af9c16cc288cd4a24cf58824388f57d7a81e400c4616457629870e"},"provenance":null,"requires-python":null,"size":294006,"upload-time":"2015-11-25T01:21:23.806352Z","url":"https://files.pythonhosted.org/packages/6d/41/cf5b54535ea052a32a76a8e8e56af817deb95f4ffde49277a52ded29763b/psutil-3.3.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.3.0.win32-py3.5.exe","hashes":{"sha256":"f500093357d04da8140d87932cac2e54ef592a54ca8a743abb2850f60c2c22eb"},"provenance":null,"requires-python":null,"size":233858,"upload-time":"2015-11-25T01:21:44.041656Z","url":"https://files.pythonhosted.org/packages/1e/1d/151535e51338efebe453a28d2f14d4d5b1e1f3ce54ccc63866c96dc7e1bd/psutil-3.3.0.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"5fd1ef4aa7b3f11f37a72bb6da5e9ae73e666b2a5580c3bf58fe259cac90c4e8"},"data-dist-info-metadata":{"sha256":"5fd1ef4aa7b3f11f37a72bb6da5e9ae73e666b2a5580c3bf58fe259cac90c4e8"},"filename":"psutil-3.4.1-cp26-none-win32.whl","hashes":{"sha256":"0b1382db1cf76d53fb1d6e5619b5f3c86126e11a933b200c21ed4fa7fe5037aa"},"provenance":null,"requires-python":null,"size":91763,"upload-time":"2016-01-15T12:34:15.511699Z","url":"https://files.pythonhosted.org/packages/8a/a4/6dfd46e45d06da1a4d42814dbbdcffe3a4fc7f9b655e1c2919ac960512c2/psutil-3.4.1-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"5fd1ef4aa7b3f11f37a72bb6da5e9ae73e666b2a5580c3bf58fe259cac90c4e8"},"data-dist-info-metadata":{"sha256":"5fd1ef4aa7b3f11f37a72bb6da5e9ae73e666b2a5580c3bf58fe259cac90c4e8"},"filename":"psutil-3.4.1-cp26-none-win_amd64.whl","hashes":{"sha256":"bcf212a926e8cffd3bec2acaeb584bf59a536e569d404bd8ea306f1752fbfc41"},"provenance":null,"requires-python":null,"size":94311,"upload-time":"2016-01-15T12:34:35.252561Z","url":"https://files.pythonhosted.org/packages/85/38/d9882d4e37f4b791bd949a1f45c620e0f2573bb4048eb16d59d469e97ec6/psutil-3.4.1-cp26-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"5fd1ef4aa7b3f11f37a72bb6da5e9ae73e666b2a5580c3bf58fe259cac90c4e8"},"data-dist-info-metadata":{"sha256":"5fd1ef4aa7b3f11f37a72bb6da5e9ae73e666b2a5580c3bf58fe259cac90c4e8"},"filename":"psutil-3.4.1-cp27-none-win32.whl","hashes":{"sha256":"1b8424eaa712fef7da41fc7f391b452e8991a641a54e49c4f46eb72ca2585577"},"provenance":null,"requires-python":null,"size":91570,"upload-time":"2016-01-15T12:25:47.323665Z","url":"https://files.pythonhosted.org/packages/cd/2d/760f774b1325037ea4ef85972f45fc9dee417da33ba225b21a0a8e512f5d/psutil-3.4.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"5fd1ef4aa7b3f11f37a72bb6da5e9ae73e666b2a5580c3bf58fe259cac90c4e8"},"data-dist-info-metadata":{"sha256":"5fd1ef4aa7b3f11f37a72bb6da5e9ae73e666b2a5580c3bf58fe259cac90c4e8"},"filename":"psutil-3.4.1-cp27-none-win_amd64.whl","hashes":{"sha256":"46d7429bae3703a0f2980c0299d4d49ada733c7ebd2cfa4e29fa3e31b5b16014"},"provenance":null,"requires-python":null,"size":94024,"upload-time":"2016-01-15T12:29:47.181880Z","url":"https://files.pythonhosted.org/packages/13/06/0104f224dd52bf9e3fb3ef14f6b6b93e9fac72f562842d54445af041f3f0/psutil-3.4.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"data-dist-info-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"filename":"psutil-3.4.1-cp33-none-win32.whl","hashes":{"sha256":"08f4ab9b720310890fa9337321a6e1e8aa525538636526be77e82653588df46b"},"provenance":null,"requires-python":null,"size":91580,"upload-time":"2016-01-15T12:26:59.466185Z","url":"https://files.pythonhosted.org/packages/65/22/f7121341bc75bff65000ecc0c5aad4f2a6d129506c26d5533ade2ca67349/psutil-3.4.1-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"data-dist-info-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"filename":"psutil-3.4.1-cp33-none-win_amd64.whl","hashes":{"sha256":"9e52230373076d0ecdb4aec373afd342c576ab52e11c382e058ed0188181a352"},"provenance":null,"requires-python":null,"size":93869,"upload-time":"2016-01-15T12:30:39.415707Z","url":"https://files.pythonhosted.org/packages/b0/84/a9edadc49ef3dbb89298855ae069b18ec534ca9f79a9294de417b8e46571/psutil-3.4.1-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"data-dist-info-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"filename":"psutil-3.4.1-cp34-none-win32.whl","hashes":{"sha256":"e658cd0e0ad7a2971b2eeb6ee4b1a0ad14245003ea47425846bc8c3e892fd567"},"provenance":null,"requires-python":null,"size":91584,"upload-time":"2016-01-15T12:28:02.007581Z","url":"https://files.pythonhosted.org/packages/b6/cd/59a87e4f10181ee228c4edc7d4927e3d62f652cff9f25f95a7e7e9ab3df0/psutil-3.4.1-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"data-dist-info-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"filename":"psutil-3.4.1-cp34-none-win_amd64.whl","hashes":{"sha256":"9c3e1146003df43aec9274be4741371a06896d70d7d590eb882ad59de2c06120"},"provenance":null,"requires-python":null,"size":93858,"upload-time":"2016-01-15T12:31:25.406165Z","url":"https://files.pythonhosted.org/packages/30/06/cf0559d12ca5ded37e6a32b1671be57fad3bade7f24536b943851aa6393e/psutil-3.4.1-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"data-dist-info-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"filename":"psutil-3.4.1-cp35-none-win32.whl","hashes":{"sha256":"3d3b2df184a31646a7e66cc48304f900a82c18ab3dc69d2d5f693ea97fca0572"},"provenance":null,"requires-python":null,"size":93406,"upload-time":"2016-01-15T12:28:48.357070Z","url":"https://files.pythonhosted.org/packages/c9/57/6e65ff27fa567cd9a7bfbc0a435e33293451b80865bc3a3a7b13c9bf7799/psutil-3.4.1-cp35-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"data-dist-info-metadata":{"sha256":"ea21e4b91d38833326191bf60eb600b6764ba491e5b44843eced5c91fcf75a03"},"filename":"psutil-3.4.1-cp35-none-win_amd64.whl","hashes":{"sha256":"820ed01d84ffcda1c613be80c09318d7560dd3505299c65bb99f101963bfc3dd"},"provenance":null,"requires-python":null,"size":96333,"upload-time":"2016-01-15T12:31:50.145506Z","url":"https://files.pythonhosted.org/packages/f0/f6/ccf16168a627d10ffbd80120cd2c521c4c9ecdb4545e402b7deca79f93ac/psutil-3.4.1-cp35-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.1.tar.gz","hashes":{"sha256":"c7443659674c87d1f9feecee0dfeea765da02181c58d532e0633337e42180c89"},"provenance":null,"requires-python":null,"size":271657,"upload-time":"2016-01-15T12:22:39.420430Z","url":"https://files.pythonhosted.org/packages/a5/56/c64187a9a6889e622f7ec687254cdb3cc3c706e11bba9244e6ac781ecf38/psutil-3.4.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.1.win-amd64-py2.6.exe","hashes":{"sha256":"e88e43423af984d7f2ecf8babf9d861ff59436794b0fdd2f85e9ea6bf7af6627"},"provenance":null,"requires-python":null,"size":331136,"upload-time":"2016-01-15T12:34:26.229804Z","url":"https://files.pythonhosted.org/packages/f7/90/53adfe2804c9cde062eb5014d88f0d067690fe1457ad2f49a2a553767689/psutil-3.4.1.win-amd64-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.1.win-amd64-py2.7.exe","hashes":{"sha256":"3c08e2c200b222a92a4ecaa8055a48e27e7cfe82d9bf6402b52dd82413a786ed"},"provenance":null,"requires-python":null,"size":330798,"upload-time":"2016-01-15T12:29:26.473954Z","url":"https://files.pythonhosted.org/packages/66/9b/2b58fdab300e5f2a20c3999c485692cfa73cc9d4e50770a19cc871f92743/psutil-3.4.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.1.win-amd64-py3.3.exe","hashes":{"sha256":"d03b8d081a281ebaa2122f259f7c0b3a464b2b98a3c221b9a54bfb0840355a9f"},"provenance":null,"requires-python":null,"size":329156,"upload-time":"2016-01-15T12:30:04.062459Z","url":"https://files.pythonhosted.org/packages/4c/26/695fa5b3578248f424d9a8e5bf2aafc6f706aeb7ec21ee31a5ebc2f79660/psutil-3.4.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.1.win-amd64-py3.4.exe","hashes":{"sha256":"66a243c4b9ad93059be391a18e3f75e015ad70b220df4f7f30f9f578b89f27ad"},"provenance":null,"requires-python":null,"size":329140,"upload-time":"2016-01-15T12:31:00.945171Z","url":"https://files.pythonhosted.org/packages/25/63/54f2ba7cf31bb936b9c2cd7a77fd40a698fb232cd7c95c1ce997295a5954/psutil-3.4.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.1.win-amd64-py3.5.exe","hashes":{"sha256":"4ee8641803d68a2e48952951336f9474a8914854da088fca673d67a91da7f9a4"},"provenance":null,"requires-python":null,"size":245586,"upload-time":"2016-01-15T12:31:39.398299Z","url":"https://files.pythonhosted.org/packages/43/54/f2d3b8845105fe5f55d5f0fde36773ab94fe1e35a0f4a5219adc818d586b/psutil-3.4.1.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.1.win32-py2.6.exe","hashes":{"sha256":"a4de0daf0dc7aeff6d45c6a1c782ef30d2b4fc6495196acabcb5cde2fb9b5a74"},"provenance":null,"requires-python":null,"size":300946,"upload-time":"2016-01-15T12:33:59.097771Z","url":"https://files.pythonhosted.org/packages/9e/34/53e1e85df97508df1b3eea711ab2809cc8f01b20e3b2341645673eb5d835/psutil-3.4.1.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.1.win32-py2.7.exe","hashes":{"sha256":"0efcecb6fcc21d83e9d4354754c6b8a8deb47a5fa06ec5d09fcf9799719eeac2"},"provenance":null,"requires-python":null,"size":300700,"upload-time":"2016-01-15T12:25:12.391548Z","url":"https://files.pythonhosted.org/packages/2f/4c/a07a53ff938e3bbc2ba73e4a484af8d1e02054b0bfcaf0f6d30117187d9f/psutil-3.4.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.1.win32-py3.3.exe","hashes":{"sha256":"62d0b529e40262293f39d7455db24f7ee297a1a3fe7f0e3e5923ae8168bd865c"},"provenance":null,"requires-python":null,"size":295638,"upload-time":"2016-01-15T12:26:24.412799Z","url":"https://files.pythonhosted.org/packages/39/6c/03ade7ba131b3952d916ed26c277418234bec0c9a5dfad513b9a5bb51046/psutil-3.4.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.1.win32-py3.4.exe","hashes":{"sha256":"6102294d6150f2c072dbc0166348389e8fa5d14a769ad118b697cda5b31c3381"},"provenance":null,"requires-python":null,"size":295651,"upload-time":"2016-01-15T12:27:36.577070Z","url":"https://files.pythonhosted.org/packages/52/d7/c2e9e0cb21482304e39a7681066c32c50e984f109bcda5929a84af926d70/psutil-3.4.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.1.win32-py3.5.exe","hashes":{"sha256":"0ffd99167272bb80c6ecf68f4c3d3176bef0f8c2a68f7e2787cab32413830023"},"provenance":null,"requires-python":null,"size":235502,"upload-time":"2016-01-15T12:28:24.463783Z","url":"https://files.pythonhosted.org/packages/1c/06/4d0ec9a6427db9c3b9885c4d724ca299746519b2ee61b724665d49e352c6/psutil-3.4.1.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"13402cacd1df223d4b15886666e72dc0e55f16091f011fc3f0c83e49b8034134"},"data-dist-info-metadata":{"sha256":"13402cacd1df223d4b15886666e72dc0e55f16091f011fc3f0c83e49b8034134"},"filename":"psutil-3.4.2-cp26-none-win32.whl","hashes":{"sha256":"2ac75c13657ab18eac0014e3f4c80def16978507b30e7719e46042ec93316bb0"},"provenance":null,"requires-python":null,"size":91920,"upload-time":"2016-01-20T16:25:20.303966Z","url":"https://files.pythonhosted.org/packages/be/b7/999dcbee8cc5ae32e64b2d0c9d588a3f5a441a07404772af83e86f3c8bc7/psutil-3.4.2-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"13402cacd1df223d4b15886666e72dc0e55f16091f011fc3f0c83e49b8034134"},"data-dist-info-metadata":{"sha256":"13402cacd1df223d4b15886666e72dc0e55f16091f011fc3f0c83e49b8034134"},"filename":"psutil-3.4.2-cp26-none-win_amd64.whl","hashes":{"sha256":"162f76140ca09490b9d218840bd641cbd1439245dcc2a9dd41f86224ed19490c"},"provenance":null,"requires-python":null,"size":94467,"upload-time":"2016-01-20T16:27:40.461506Z","url":"https://files.pythonhosted.org/packages/60/97/f9ea4fa7a4914350d15347a6a583c8a185643bb6bd5dc76d13e9d7dfc150/psutil-3.4.2-cp26-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"13402cacd1df223d4b15886666e72dc0e55f16091f011fc3f0c83e49b8034134"},"data-dist-info-metadata":{"sha256":"13402cacd1df223d4b15886666e72dc0e55f16091f011fc3f0c83e49b8034134"},"filename":"psutil-3.4.2-cp27-none-win32.whl","hashes":{"sha256":"b5f4bfdaa6389552501253b13b6022b7e3d715e4dca4b5cc1808f58cca181359"},"provenance":null,"requires-python":null,"size":91721,"upload-time":"2016-01-20T16:25:38.512050Z","url":"https://files.pythonhosted.org/packages/d4/19/4e5c376587076c969784762de8024bb30168a548b402e1b432221c5b97b1/psutil-3.4.2-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"13402cacd1df223d4b15886666e72dc0e55f16091f011fc3f0c83e49b8034134"},"data-dist-info-metadata":{"sha256":"13402cacd1df223d4b15886666e72dc0e55f16091f011fc3f0c83e49b8034134"},"filename":"psutil-3.4.2-cp27-none-win_amd64.whl","hashes":{"sha256":"9267e9bccb5c8b1c5ca872eb1caf88ba0ae47e336eb200be138f51d9f75e1113"},"provenance":null,"requires-python":null,"size":94177,"upload-time":"2016-01-20T16:27:58.779061Z","url":"https://files.pythonhosted.org/packages/d3/19/39f42cdfba58ab593d24f49ffc073c07b9b34ff7d5ba079b975018002e51/psutil-3.4.2-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"data-dist-info-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"filename":"psutil-3.4.2-cp33-none-win32.whl","hashes":{"sha256":"413800a94815e6bf3e3227823e4d46b06c63bd22ab9e5af112b9220af9a9c9d8"},"provenance":null,"requires-python":null,"size":91738,"upload-time":"2016-01-20T16:25:59.987045Z","url":"https://files.pythonhosted.org/packages/8e/7a/8ba0c9da039b5733edbe17321f0546f08a7066861ffcdb83c31eb061e8f1/psutil-3.4.2-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"data-dist-info-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"filename":"psutil-3.4.2-cp33-none-win_amd64.whl","hashes":{"sha256":"45535c18a0f261f90ff1ebb7d74c5e88d582cfb2006e4588498b9c0c9da5acb3"},"provenance":null,"requires-python":null,"size":94024,"upload-time":"2016-01-20T16:28:42.605657Z","url":"https://files.pythonhosted.org/packages/34/a7/94fc00a023ede02c3a7f525c63997a23c22434cbed65738ee3ef8939e084/psutil-3.4.2-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"data-dist-info-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"filename":"psutil-3.4.2-cp34-none-win32.whl","hashes":{"sha256":"c9ef9a08254c251858cf747703e6fd75fe6e9549b1e040bb4a501feaf44a5a75"},"provenance":null,"requires-python":null,"size":91749,"upload-time":"2016-01-20T16:26:31.788083Z","url":"https://files.pythonhosted.org/packages/dc/8b/df5d7dcfe8fc5db0c303e518ce12b7117cb70e1cbb29c0396ea6e36fc7a2/psutil-3.4.2-cp34-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"data-dist-info-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"filename":"psutil-3.4.2-cp34-none-win_amd64.whl","hashes":{"sha256":"0bf8925c3d252178c47bd8f29aff99c57a56f94513354b60069b457ca04bc25b"},"provenance":null,"requires-python":null,"size":94004,"upload-time":"2016-01-20T16:29:27.043903Z","url":"https://files.pythonhosted.org/packages/1c/cf/c9ce0014f43f74b1ce72c004c8f2eda68339cbc19117d9f090ee14afce3e/psutil-3.4.2-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"data-dist-info-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"filename":"psutil-3.4.2-cp35-none-win32.whl","hashes":{"sha256":"461d1431a14e4da5e687cfdc2a8576b1f0e3bc658694ab9c6ef2fa1e4c1a4871"},"provenance":null,"requires-python":null,"size":93563,"upload-time":"2016-01-20T16:27:20.576920Z","url":"https://files.pythonhosted.org/packages/de/51/d1ab564dfe98d5fcbccfcab0afdedb269f7266192721e94688ab3956b123/psutil-3.4.2-cp35-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"data-dist-info-metadata":{"sha256":"23ac4ea4417292cac0684989ffba547b75ac89f3a6ef7965183ec9e389edaa89"},"filename":"psutil-3.4.2-cp35-none-win_amd64.whl","hashes":{"sha256":"23d4ea79fea3de81daf9460662e49ff718555779b2f5e5e3610648c0a8cafecc"},"provenance":null,"requires-python":null,"size":96484,"upload-time":"2016-01-20T16:29:53.151921Z","url":"https://files.pythonhosted.org/packages/89/d8/dc9ce7b8862ab2d86975dd5199791b0ab2b2168fc2389223a216c2da1d45/psutil-3.4.2-cp35-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.2.tar.gz","hashes":{"sha256":"b17fa01aa766daa388362d0eda5c215d77e03a8d37676b68971f37bf3913b725"},"provenance":null,"requires-python":null,"size":274361,"upload-time":"2016-01-20T16:26:46.533423Z","url":"https://files.pythonhosted.org/packages/7b/58/2675697b6831e6ac4b7b7bc4e5dcdb24a2f39f8411186573eb0de16eb6d5/psutil-3.4.2.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.2.win-amd64-py2.6.exe","hashes":{"sha256":"3716cb36373ecfd033c148c8e8e22d815a9b682c87538c5bde2a3faca1a44705"},"provenance":null,"requires-python":null,"size":331279,"upload-time":"2016-01-20T16:27:30.129391Z","url":"https://files.pythonhosted.org/packages/6f/52/26cefb84d714ecdf39f6d4ea62af49af731cf55d8937a252226de41d3fb0/psutil-3.4.2.win-amd64-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.2.win-amd64-py2.7.exe","hashes":{"sha256":"9eba153441fabd6677f9dec95eedbfdcf4fe832a43b91c18f2c15bfd0a12b6c0"},"provenance":null,"requires-python":null,"size":330991,"upload-time":"2016-01-20T16:27:49.025596Z","url":"https://files.pythonhosted.org/packages/1f/85/817b298f6865d7a140897882015096ab25514e113c98ba3896b2e2c0425c/psutil-3.4.2.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.2.win-amd64-py3.3.exe","hashes":{"sha256":"2ffc77ec7452675db45174f77e65bfc9abd5780e696c4dd486fff89e18ef104a"},"provenance":null,"requires-python":null,"size":329349,"upload-time":"2016-01-20T16:28:23.729829Z","url":"https://files.pythonhosted.org/packages/2b/05/ccb8e8dc272a6aa126a6066583102b78df8939aeffd910a3ea28a51d48af/psutil-3.4.2.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.2.win-amd64-py3.4.exe","hashes":{"sha256":"6b576ea8faa312700953de30b92ff49dcd966dcdbf2e039c3655077826b59812"},"provenance":null,"requires-python":null,"size":329334,"upload-time":"2016-01-20T16:29:12.322779Z","url":"https://files.pythonhosted.org/packages/8f/0e/9b3eedad9ea2aa8e51c3ca6aa6485c0ec85bfc73925fbd4d82bbe03ead18/psutil-3.4.2.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.2.win-amd64-py3.5.exe","hashes":{"sha256":"2a987c57ddb06a1e67f75a4dd34d2962f8675c3d60b2104da2d60fdaa378b50f"},"provenance":null,"requires-python":null,"size":245779,"upload-time":"2016-01-20T16:29:45.537968Z","url":"https://files.pythonhosted.org/packages/2f/bb/5483a7a54dfaedcd5bc6d0f9f8beef21d96785589e10d09e246f7092cfe1/psutil-3.4.2.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.2.win32-py2.6.exe","hashes":{"sha256":"87b657f1021ab4155669f77baf9557657a015b0762854702d64ee7cfa19d5ae2"},"provenance":null,"requires-python":null,"size":301089,"upload-time":"2016-01-20T16:25:11.034218Z","url":"https://files.pythonhosted.org/packages/2e/9b/2bb0317a5113b4a3d597a9bcb94cafacb52f15a10631b1f0eb781ceb8e7a/psutil-3.4.2.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.2.win32-py2.7.exe","hashes":{"sha256":"d837d654a78fcc6cf7338fc3c3f025e5a43cf646d4d6cf180f0f3573ef255844"},"provenance":null,"requires-python":null,"size":300894,"upload-time":"2016-01-20T16:25:29.293012Z","url":"https://files.pythonhosted.org/packages/8c/3b/bf4d0698153784231768aa79255f1641efde680c4d178ba327546eba69df/psutil-3.4.2.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.2.win32-py3.3.exe","hashes":{"sha256":"ef756512c86cf24916f47b2209ff5dc69ef4d5ff8b3b0229863aab3537af58a1"},"provenance":null,"requires-python":null,"size":295833,"upload-time":"2016-01-20T16:25:50.417672Z","url":"https://files.pythonhosted.org/packages/50/be/c4911ae27c944e12183d9ba844ff0eee706b5208f92e4929d8120b79448e/psutil-3.4.2.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.2.win32-py3.4.exe","hashes":{"sha256":"13917c61de33518fcdca94a8f1005c4bb0be1f106af773de8c12d6a5a3f349ae"},"provenance":null,"requires-python":null,"size":295845,"upload-time":"2016-01-20T16:26:17.574774Z","url":"https://files.pythonhosted.org/packages/b5/d9/9a15af2703d8a0d7b685511df811fc4930b12d7b85b96573e843a0ba1067/psutil-3.4.2.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-3.4.2.win32-py3.5.exe","hashes":{"sha256":"75c4484f0e1038c1a37ae37fc80f5b59b456e363c696ed889027e79a831853ec"},"provenance":null,"requires-python":null,"size":235696,"upload-time":"2016-01-20T16:27:12.935221Z","url":"https://files.pythonhosted.org/packages/2c/74/46cf554a4a8d7d2367f058e6c90ba26a66e52e818812ec6de53a5013bd87/psutil-3.4.2.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"f4174370ba76a8fc971e342367fb6df5d587165b15b69cf652d5870ad0386329"},"data-dist-info-metadata":{"sha256":"f4174370ba76a8fc971e342367fb6df5d587165b15b69cf652d5870ad0386329"},"filename":"psutil-4.0.0-cp26-none-win32.whl","hashes":{"sha256":"0661261b634f01ec2568136fedf29382f5c94678c34f56b4137b1d019085ca6f"},"provenance":null,"requires-python":null,"size":154479,"upload-time":"2016-02-17T16:42:58.173240Z","url":"https://files.pythonhosted.org/packages/72/e2/24700d1a099dcd824ca7305cf439625a457221459494e677e7649a2f228b/psutil-4.0.0-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"f4174370ba76a8fc971e342367fb6df5d587165b15b69cf652d5870ad0386329"},"data-dist-info-metadata":{"sha256":"f4174370ba76a8fc971e342367fb6df5d587165b15b69cf652d5870ad0386329"},"filename":"psutil-4.0.0-cp26-none-win_amd64.whl","hashes":{"sha256":"b613a9fb5c3d2b16c65df3121aa369a28caed83391883bc24918cf16c5de495b"},"provenance":null,"requires-python":null,"size":156749,"upload-time":"2016-02-17T16:45:09.428253Z","url":"https://files.pythonhosted.org/packages/62/c0/1adb11a832d5aab8a984702a4f6fc08e5698fa4bbc8dc6eddac1c711c7ab/psutil-4.0.0-cp26-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"f4174370ba76a8fc971e342367fb6df5d587165b15b69cf652d5870ad0386329"},"data-dist-info-metadata":{"sha256":"f4174370ba76a8fc971e342367fb6df5d587165b15b69cf652d5870ad0386329"},"filename":"psutil-4.0.0-cp27-cp27m-win32.whl","hashes":{"sha256":"3eb6bc7e8d92777deb4288178c25455e21109033bb54ec475485b611e92d3b42"},"provenance":null,"requires-python":null,"size":154302,"upload-time":"2016-02-17T16:43:23.863441Z","url":"https://files.pythonhosted.org/packages/e8/c3/542bc833b743e952cbf99017ecb60add0ad3725a82e942b25aa5de523f8c/psutil-4.0.0-cp27-cp27m-win32.whl","yanked":false},{"core-metadata":{"sha256":"f4174370ba76a8fc971e342367fb6df5d587165b15b69cf652d5870ad0386329"},"data-dist-info-metadata":{"sha256":"f4174370ba76a8fc971e342367fb6df5d587165b15b69cf652d5870ad0386329"},"filename":"psutil-4.0.0-cp27-cp27m-win_amd64.whl","hashes":{"sha256":"c94193f38aa3bc35fd5dbcd24653d1f683c88ec8030997d1d56f92207ba7c523"},"provenance":null,"requires-python":null,"size":156487,"upload-time":"2016-02-24T17:23:02.172281Z","url":"https://files.pythonhosted.org/packages/32/1b/5f3cc96374c4eac441e96bb8698556c6c48eacfdcf843093bebcfd8ce56b/psutil-4.0.0-cp27-cp27m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"f4174370ba76a8fc971e342367fb6df5d587165b15b69cf652d5870ad0386329"},"data-dist-info-metadata":{"sha256":"f4174370ba76a8fc971e342367fb6df5d587165b15b69cf652d5870ad0386329"},"filename":"psutil-4.0.0-cp27-none-win_amd64.whl","hashes":{"sha256":"cb969d3c77db8810aba45e8a04e0b2851cd088e338be2430e1ff452f4e06007c"},"provenance":null,"requires-python":null,"size":156486,"upload-time":"2016-02-17T16:45:40.145586Z","url":"https://files.pythonhosted.org/packages/94/37/dc09e24aa80016ddeaff235d2f724d8aac9813b73cc3bf8a7fe3d1878315/psutil-4.0.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"data-dist-info-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"filename":"psutil-4.0.0-cp33-cp33m-win32.whl","hashes":{"sha256":"6a372681382b523bc837ee2eff6a84ded0f85b013b7c29ea6211bc928c7cc656"},"provenance":null,"requires-python":null,"size":154244,"upload-time":"2016-02-24T17:21:40.946801Z","url":"https://files.pythonhosted.org/packages/a0/8a/b9352e0daf69b501296715e0fca1b49d861130eb66156ce3b12aeeb039e4/psutil-4.0.0-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"data-dist-info-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"filename":"psutil-4.0.0-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"2bbb75fc2549965b457f313cbdfb98a00624f25fcb36e075322bb8b8912d83b5"},"provenance":null,"requires-python":null,"size":156393,"upload-time":"2016-02-24T17:23:25.807132Z","url":"https://files.pythonhosted.org/packages/4b/77/0fefa732947da69cba7f2580285eff553fe4a416314234f809901bede361/psutil-4.0.0-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"data-dist-info-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"filename":"psutil-4.0.0-cp33-none-win32.whl","hashes":{"sha256":"d6219c89940d745b614716be7c906660f2108a1d84b8ffc720922596b8306e23"},"provenance":null,"requires-python":null,"size":154245,"upload-time":"2016-02-17T16:43:48.027623Z","url":"https://files.pythonhosted.org/packages/1f/d9/34fb4fab5f1bbfeddc76675b1b5ca00b45ef490e63295af33542cedcd26b/psutil-4.0.0-cp33-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"data-dist-info-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"filename":"psutil-4.0.0-cp33-none-win_amd64.whl","hashes":{"sha256":"a64bb22e264f91a6d80cf8fdd813bd4fdd349dc367b363d517cf8ae1bc2c5db0"},"provenance":null,"requires-python":null,"size":156399,"upload-time":"2016-02-17T16:46:09.673427Z","url":"https://files.pythonhosted.org/packages/97/03/b9485635cb38dfad854754625422a49f434f53f214bff4885580f0fb21e6/psutil-4.0.0-cp33-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"data-dist-info-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"filename":"psutil-4.0.0-cp34-cp34m-win32.whl","hashes":{"sha256":"a521266ac13485772987f00342b53cb230cde98ce91d61154860ba4109fe2ebe"},"provenance":null,"requires-python":null,"size":154269,"upload-time":"2016-02-17T16:44:14.936699Z","url":"https://files.pythonhosted.org/packages/73/32/6399071b097f1251f6fa12770b30a67d5b3c9c0c76e81eacbb6139e1bf6d/psutil-4.0.0-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"data-dist-info-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"filename":"psutil-4.0.0-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"b559b8e8a85cde929e01e94e9635649e8641a88b2d077714933dc7723a967020"},"provenance":null,"requires-python":null,"size":156390,"upload-time":"2016-02-24T17:23:50.194021Z","url":"https://files.pythonhosted.org/packages/71/d7/878b77bad61bd94f4454536e823b6a48cd0af0f23b1506a2c8a49b2578cd/psutil-4.0.0-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"data-dist-info-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"filename":"psutil-4.0.0-cp34-none-win_amd64.whl","hashes":{"sha256":"02d7291f81e78c506ac2b5481aa9dc6d3888195484ac114ac984b37477f60929"},"provenance":null,"requires-python":null,"size":156397,"upload-time":"2016-02-17T16:46:44.626238Z","url":"https://files.pythonhosted.org/packages/ea/22/5f44e6eaa1e82f5a1497f3dfcf045e1998fca36d70de8a370ec96ce0f789/psutil-4.0.0-cp34-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"data-dist-info-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"filename":"psutil-4.0.0-cp35-cp35m-win32.whl","hashes":{"sha256":"7906302696960a6a788bb8fe1165b4ccd0156553b8a2f61640fd45a836d39024"},"provenance":null,"requires-python":null,"size":156177,"upload-time":"2016-02-24T17:22:25.142667Z","url":"https://files.pythonhosted.org/packages/e2/fe/a5ec73e62878cc2d0451b7029f4406647435dd8036ab15d6ed2fd42558bf/psutil-4.0.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"data-dist-info-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"filename":"psutil-4.0.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"f4214bdb2e96374b4c4a3a818bd8c7867f94571d33b91867b6dfd5f9b328c8ac"},"provenance":null,"requires-python":null,"size":158783,"upload-time":"2016-02-24T17:24:26.439061Z","url":"https://files.pythonhosted.org/packages/1d/a7/9300ad3d4071c191894073a94217ed5c0ca9604c782bdbf083bbedfa9cb1/psutil-4.0.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"data-dist-info-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"filename":"psutil-4.0.0-cp35-none-win32.whl","hashes":{"sha256":"4a1631cb8c4de2b6c9b4b16f8800d43de23c683805f7b6a5aec1c268a73df270"},"provenance":null,"requires-python":null,"size":156183,"upload-time":"2016-02-17T16:44:42.458015Z","url":"https://files.pythonhosted.org/packages/14/f0/a2436cb642ecfec0bfb6338e5fa26581d4dbcf1a00f7d9fe99380eb6779f/psutil-4.0.0-cp35-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"data-dist-info-metadata":{"sha256":"d2287fe3ce0e3872765b52193af0ec6f19279df57903b85d13961ca84449ba60"},"filename":"psutil-4.0.0-cp35-none-win_amd64.whl","hashes":{"sha256":"994839b6d99acbf90914fddf2e2817aaffb67ceca5d10134319267e3ffe97258"},"provenance":null,"requires-python":null,"size":158787,"upload-time":"2016-02-17T16:47:18.920483Z","url":"https://files.pythonhosted.org/packages/e8/2a/e215824c785d77119af61802bbb4d16dacc26ec0687709274afa3ac039fa/psutil-4.0.0-cp35-none-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.0.0.tar.gz","hashes":{"sha256":"1a7c672f9ee79c84ff16b8de6f6040080f0e25002ac47f115f4a54aa88e5cfcd"},"provenance":null,"requires-python":null,"size":293800,"upload-time":"2016-02-17T16:41:45.938066Z","url":"https://files.pythonhosted.org/packages/c4/3b/44bcae6c0fc53362bb7325fde25a73b7fd46541b57c89b7556ca81b08e7e/psutil-4.0.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.0.0.win-amd64-py2.6.exe","hashes":{"sha256":"61bf81cbe84e679a5b619e65775b0674b2c463885e49ddab73778198608198c5"},"provenance":null,"requires-python":null,"size":394157,"upload-time":"2016-02-17T16:45:00.666953Z","url":"https://files.pythonhosted.org/packages/86/50/6303a28a4ab5c9b6b9ef74eef70b141d5bd743ab096c58d225b6212fa057/psutil-4.0.0.win-amd64-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.0.0.win-amd64-py2.7.exe","hashes":{"sha256":"23618cbc04e2431d9c4d97f56ab8b4e2e35366c9a9a6e1ef89a3a7287d359864"},"provenance":null,"requires-python":null,"size":393901,"upload-time":"2016-02-17T16:45:21.822412Z","url":"https://files.pythonhosted.org/packages/59/82/93052d6359addea338c528ebd50254806d62bc2b2d1ad1303c49d85162f9/psutil-4.0.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.0.0.win-amd64-py3.3.exe","hashes":{"sha256":"b6f16c71be03495eeb4772c1f3f926213e3ea82ea7779bd1143229e6b419760b"},"provenance":null,"requires-python":null,"size":392318,"upload-time":"2016-02-17T16:45:57.189438Z","url":"https://files.pythonhosted.org/packages/e6/14/9ac37705e0753732c7707b000d1e076daac95ee02f35fd43ce906235ea1f/psutil-4.0.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.0.0.win-amd64-py3.4.exe","hashes":{"sha256":"24b41e436afbcecb07e485e58a52effdbd7b8065ad8a2e4d555b6d88907f19b7"},"provenance":null,"requires-python":null,"size":392315,"upload-time":"2016-02-17T16:46:29.967466Z","url":"https://files.pythonhosted.org/packages/77/a9/ff7c29d2e244f5bdc7654a626cbfcccd401e78df6e9388713f322c7aa7c7/psutil-4.0.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.0.0.win-amd64-py3.5.exe","hashes":{"sha256":"88edc0e7bfa672c245df74b1ac3b59db432cd75e5704beccc268e177ac2ffbbc"},"provenance":null,"requires-python":null,"size":308678,"upload-time":"2016-02-17T16:47:07.280714Z","url":"https://files.pythonhosted.org/packages/cc/1b/863bee07da70fe61cae804333d64242d9001b54288e8ff54e770225bbc0a/psutil-4.0.0.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.0.0.win32-py2.6.exe","hashes":{"sha256":"882a8ac29b63f256f76465d8fcd6e9eaeb9c929acdac26af102da97d66b2b619"},"provenance":null,"requires-python":null,"size":364245,"upload-time":"2016-02-17T16:42:49.267819Z","url":"https://files.pythonhosted.org/packages/41/39/6ea85cb0c748aae2943144118f1696004f5a99c54dab1fc635cffbb0d06c/psutil-4.0.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.0.0.win32-py2.7.exe","hashes":{"sha256":"bf3b2e305ca7408df40156c9aa6261c7baaff831441f0c018d0682bd820286f2"},"provenance":null,"requires-python":null,"size":364072,"upload-time":"2016-02-17T16:43:14.467612Z","url":"https://files.pythonhosted.org/packages/8f/0d/3e9cf8abb62d7241531019d78abaa87a19f3fcc017bfb9c2058ba61e8cf1/psutil-4.0.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.0.0.win32-py3.3.exe","hashes":{"sha256":"38d38211bba35c705a007e62b9dcc9be1d222acfcbee812612d4a48f9d8f0230"},"provenance":null,"requires-python":null,"size":358940,"upload-time":"2016-02-17T16:43:38.439349Z","url":"https://files.pythonhosted.org/packages/57/eb/514a71eab624b381473a3df9c3e3a02f5bb15707b12daf02c137271dfd26/psutil-4.0.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.0.0.win32-py3.4.exe","hashes":{"sha256":"bc868653a6502c3a01da32b3a598a8575674975f5586ac0bf9181349588925b9"},"provenance":null,"requires-python":null,"size":358966,"upload-time":"2016-02-17T16:44:02.873748Z","url":"https://files.pythonhosted.org/packages/25/f6/ef4b802658c21d5b79a0e038db941f4b08a7cc2de78df1949ad709542682/psutil-4.0.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.0.0.win32-py3.5.exe","hashes":{"sha256":"38244b0d07d3bece481a6f1c049e6101fdd26f2ee63dadcb63ce993283032fdc"},"provenance":null,"requires-python":null,"size":298915,"upload-time":"2016-02-17T16:44:31.772075Z","url":"https://files.pythonhosted.org/packages/b9/be/b8938c409231dab07d2954ff7b4d1129e725e0e8ab1b016d7f471f3285e9/psutil-4.0.0.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"9a907b7ac326dc42e1ba74d263d9a38c07d0929a2a9f91960d9961923d796b2d"},"data-dist-info-metadata":{"sha256":"9a907b7ac326dc42e1ba74d263d9a38c07d0929a2a9f91960d9961923d796b2d"},"filename":"psutil-4.1.0-cp26-none-win32.whl","hashes":{"sha256":"13aed96ad945db5c6b3d5fbe92be65330a3f2f757a300c7d1578a16efa0ece7f"},"provenance":null,"requires-python":null,"size":159498,"upload-time":"2016-03-12T17:13:58.069685Z","url":"https://files.pythonhosted.org/packages/77/04/d5a92cb5c0e79b84294f6c99b9725806921d1d88032e9d056ca8a7ba31c1/psutil-4.1.0-cp26-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"9a907b7ac326dc42e1ba74d263d9a38c07d0929a2a9f91960d9961923d796b2d"},"data-dist-info-metadata":{"sha256":"9a907b7ac326dc42e1ba74d263d9a38c07d0929a2a9f91960d9961923d796b2d"},"filename":"psutil-4.1.0-cp26-none-win_amd64.whl","hashes":{"sha256":"90b58cf88e80a4af52b79678df474679d231ed22200e6c25605a42ca71708a47"},"provenance":null,"requires-python":null,"size":161924,"upload-time":"2016-03-12T17:17:01.346400Z","url":"https://files.pythonhosted.org/packages/b5/a5/cf96f9f13f9e20bdb4cd2ca1af2ddd74f76fea4bbfb8505c31a5900b38d2/psutil-4.1.0-cp26-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9a907b7ac326dc42e1ba74d263d9a38c07d0929a2a9f91960d9961923d796b2d"},"data-dist-info-metadata":{"sha256":"9a907b7ac326dc42e1ba74d263d9a38c07d0929a2a9f91960d9961923d796b2d"},"filename":"psutil-4.1.0-cp27-cp27m-win32.whl","hashes":{"sha256":"ac141a44a5c145e9006bc7081c714b2c317077d158b65fe4624c9cbf2b8ac7bf"},"provenance":null,"requires-python":null,"size":159318,"upload-time":"2016-03-12T17:14:48.199436Z","url":"https://files.pythonhosted.org/packages/8a/31/439614cc2ccd6f3ce1d173c0d7c7a9e45be17cd2bf3ae1f8feaaf0a90cee/psutil-4.1.0-cp27-cp27m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9a907b7ac326dc42e1ba74d263d9a38c07d0929a2a9f91960d9961923d796b2d"},"data-dist-info-metadata":{"sha256":"9a907b7ac326dc42e1ba74d263d9a38c07d0929a2a9f91960d9961923d796b2d"},"filename":"psutil-4.1.0-cp27-cp27m-win_amd64.whl","hashes":{"sha256":"3605b6b9f23f3e186b157b03a95e0158559eb74bcef5d51920b8ddb48cc3a7e7"},"provenance":null,"requires-python":null,"size":161579,"upload-time":"2016-03-12T17:17:24.047978Z","url":"https://files.pythonhosted.org/packages/90/97/0a34c0e98bb794f0fc19f0eae13d26fbf39583c768a9a6c614c917135c00/psutil-4.1.0-cp27-cp27m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"data-dist-info-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"filename":"psutil-4.1.0-cp33-cp33m-win32.whl","hashes":{"sha256":"5568e21c8eb9de0e56c8a4a38982b725bf42117bca7ac75c7b079e5214aea5c4"},"provenance":null,"requires-python":null,"size":159241,"upload-time":"2016-03-12T17:15:16.355951Z","url":"https://files.pythonhosted.org/packages/bb/de/8e1f8c4ea6035d08e3c87a0cfc8af6f2862da21697c16d1d17311e095117/psutil-4.1.0-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"data-dist-info-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"filename":"psutil-4.1.0-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"3d3f8ae20b04b68e65b46dc2eedf15f32925655dacbb11cb7afe56ac562e112a"},"provenance":null,"requires-python":null,"size":161458,"upload-time":"2016-03-12T17:17:51.825112Z","url":"https://files.pythonhosted.org/packages/ac/cf/6241dd597ef4f995ab8e29746c54890c1acbb322484afed05aa8988118e1/psutil-4.1.0-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"data-dist-info-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"filename":"psutil-4.1.0-cp34-cp34m-win32.whl","hashes":{"sha256":"faafb81bf7717fa8c44bb0f2e826768f561c0311fd0568090c59c9b253b65238"},"provenance":null,"requires-python":null,"size":159266,"upload-time":"2016-03-12T17:15:48.577644Z","url":"https://files.pythonhosted.org/packages/48/2d/46ba91df965d4f0af5fd4252ac249ff408f6cb966fe1208396933275246f/psutil-4.1.0-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"data-dist-info-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"filename":"psutil-4.1.0-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"4ab1ee4152dbb790a37291149b73b1918ab4398c8edb3af7847fa6c884024c93"},"provenance":null,"requires-python":null,"size":161466,"upload-time":"2016-03-12T17:18:23.409579Z","url":"https://files.pythonhosted.org/packages/c0/b1/70868328ddf2cfcde201136bdaf4c9f7fabf868890bc91694fd5fa0fbc19/psutil-4.1.0-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"data-dist-info-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"filename":"psutil-4.1.0-cp35-cp35m-win32.whl","hashes":{"sha256":"2fa06b7ba58e870fdaa1427e82ed427a785493c7a998e059e0806b2c48bdbfaf"},"provenance":null,"requires-python":null,"size":161345,"upload-time":"2016-03-12T17:16:33.062364Z","url":"https://files.pythonhosted.org/packages/dc/f7/5d3f84507c057af85bc70da10b51a827766999f221b42cdf9621ca756e80/psutil-4.1.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"data-dist-info-metadata":{"sha256":"28328dafba00a0d87523ceca7f8bb23231019819caf14ec9eeddc3c4c8104338"},"filename":"psutil-4.1.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"1ca460fea3822d04f332f5dde144accc5ca4610e5bbce53f15d09cd985f30385"},"provenance":null,"requires-python":null,"size":164147,"upload-time":"2016-03-12T17:18:49.483687Z","url":"https://files.pythonhosted.org/packages/f1/65/040624aab6ca646af0c8b68ac54d08e0a33a672feb9405581cd509741367/psutil-4.1.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.1.0.tar.gz","hashes":{"sha256":"c6abebec9c8833baaf1c51dd1b0259246d1d50b9b50e9a4aa66f33b1e98b8d17"},"provenance":null,"requires-python":null,"size":301330,"upload-time":"2016-03-12T17:12:53.032151Z","url":"https://files.pythonhosted.org/packages/71/9b/6b6f630ad4262572839033b69905d415ef152d7701ef40aa98941ba75b38/psutil-4.1.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.1.0.win-amd64-py2.6.exe","hashes":{"sha256":"30a97a9c3ace92001e419a6bb039b2e899c5ab24cab6ad8bc249506475c84a0c"},"provenance":null,"requires-python":null,"size":399511,"upload-time":"2016-03-12T17:16:48.833953Z","url":"https://files.pythonhosted.org/packages/28/bd/c389af84b684d36010a634834f76932ff60f33505c54413c50eceb720a5d/psutil-4.1.0.win-amd64-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.1.0.win-amd64-py2.7.exe","hashes":{"sha256":"3ba9236c38fe85088b9d79bd5871e07f253d225b357ce82db9240e5807f147b6"},"provenance":null,"requires-python":null,"size":399168,"upload-time":"2016-03-12T17:17:14.518227Z","url":"https://files.pythonhosted.org/packages/6c/e8/49ff1b33e9fa6f7ea1232d40bbef9a453424fbf0421a7c53d9ecb9a896e7/psutil-4.1.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.1.0.win-amd64-py3.3.exe","hashes":{"sha256":"99a902d1bf5beb13cca4d7a3dc82efb6eaf40aafe916a5b632d47393313bfcfd"},"provenance":null,"requires-python":null,"size":397557,"upload-time":"2016-03-12T17:17:40.792655Z","url":"https://files.pythonhosted.org/packages/c5/3e/47adda552a79480e2a5d38a2f90144ab4e7ea34eba2b707523cdd1c65fc6/psutil-4.1.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.1.0.win-amd64-py3.4.exe","hashes":{"sha256":"ecf8b83e038acdda9102aebadb1525f89231463772cbe89e3e8a2cf5a5c6065d"},"provenance":null,"requires-python":null,"size":397564,"upload-time":"2016-03-12T17:18:11.514028Z","url":"https://files.pythonhosted.org/packages/52/9f/5874f391a300feead5519872395cbb4d4588eace24962150a03cd9b6ffdf/psutil-4.1.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.1.0.win-amd64-py3.5.exe","hashes":{"sha256":"448db9bf9db5d162f0282af70688e03d67d756b93d5bed94b0790a27a96af75b"},"provenance":null,"requires-python":null,"size":314216,"upload-time":"2016-03-12T17:18:36.745340Z","url":"https://files.pythonhosted.org/packages/32/34/6588580a1775a0741e14946003bf2722c493c7956c7dcc9a40c85dbe19f5/psutil-4.1.0.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.1.0.win32-py2.6.exe","hashes":{"sha256":"11b48d7ec00061960ffbef33a42e919f188e10a6a54c4161692d77a3ac37f1e2"},"provenance":null,"requires-python":null,"size":369441,"upload-time":"2016-03-12T17:13:47.639393Z","url":"https://files.pythonhosted.org/packages/5d/cc/7bf8593a60ed54b47def9a49a60b8bc3517d6fef6ee52a229583a5bc9046/psutil-4.1.0.win32-py2.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.1.0.win32-py2.7.exe","hashes":{"sha256":"c15ddba9f4c278f7d1bbbadc34df89d993171bb328fa1117cbecf68bcc1a01e5"},"provenance":null,"requires-python":null,"size":369263,"upload-time":"2016-03-12T17:14:25.482243Z","url":"https://files.pythonhosted.org/packages/bf/fa/7f0eda490dd5480bb8e6358f9e893fe7f824ec3a5b275b069708272d2260/psutil-4.1.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.1.0.win32-py3.3.exe","hashes":{"sha256":"8f97a836b89b09718b00aedbfd11d8468bbfcb821feadd65e338bd0b972dac54"},"provenance":null,"requires-python":null,"size":364112,"upload-time":"2016-03-12T17:15:06.252090Z","url":"https://files.pythonhosted.org/packages/e8/1c/e7d17500c0f5899f0fd3fd3b6d93c92254aff6de00cabc9c08de5a3803a2/psutil-4.1.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.1.0.win32-py3.4.exe","hashes":{"sha256":"fef37f4e2964043a58b6aa3b3be05ef9b3c7a58feb806d7e49dc91702bac52fa"},"provenance":null,"requires-python":null,"size":364136,"upload-time":"2016-03-12T17:15:29.468205Z","url":"https://files.pythonhosted.org/packages/6d/8e/7acd4079567fc64e1df12be6faeddf0ee19432445210293594444ea980a6/psutil-4.1.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.1.0.win32-py3.5.exe","hashes":{"sha256":"1a9409d2204d397b1ed10528065e45020e0b1c2ee5204bf5a1bc6fdff3f6ab91"},"provenance":null,"requires-python":null,"size":304255,"upload-time":"2016-03-12T17:16:06.024724Z","url":"https://files.pythonhosted.org/packages/5b/30/10f3bef7fa284167a3f9bd3862bcba5cc14fb8509f146a2f696ea5265b93/psutil-4.1.0.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"5eaefa5c21411ed0414953f28eef77176307e672d668daf95d91e18ad3dc36f7"},"data-dist-info-metadata":{"sha256":"5eaefa5c21411ed0414953f28eef77176307e672d668daf95d91e18ad3dc36f7"},"filename":"psutil-4.2.0-cp27-cp27m-win32.whl","hashes":{"sha256":"19f6c8bd30d7827ce4d4bbcfe23fe7158fea3d72f59505850c5afa12985184bb"},"provenance":null,"requires-python":null,"size":165248,"upload-time":"2016-05-15T06:36:58.089018Z","url":"https://files.pythonhosted.org/packages/58/a5/2ccc9f6180ea769005405381f6b0d01fe1268f20cc85877b02c04c27d306/psutil-4.2.0-cp27-cp27m-win32.whl","yanked":false},{"core-metadata":{"sha256":"5eaefa5c21411ed0414953f28eef77176307e672d668daf95d91e18ad3dc36f7"},"data-dist-info-metadata":{"sha256":"5eaefa5c21411ed0414953f28eef77176307e672d668daf95d91e18ad3dc36f7"},"filename":"psutil-4.2.0-cp27-cp27m-win_amd64.whl","hashes":{"sha256":"92bc2351bb4bc7672b3d0e251a449ac2234bbe4fac11f708614bdc0a8ebffe3b"},"provenance":null,"requires-python":null,"size":167782,"upload-time":"2016-05-15T06:37:06.221707Z","url":"https://files.pythonhosted.org/packages/c8/e5/5d0a1b2e182e41888fc4e9f4f657f37f126f9fdcd431b592442311c2db98/psutil-4.2.0-cp27-cp27m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"data-dist-info-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"filename":"psutil-4.2.0-cp33-cp33m-win32.whl","hashes":{"sha256":"2e16f792deceb1d33320981aaff7f139561cf6195ee3f1b21256d7f214162517"},"provenance":null,"requires-python":null,"size":165259,"upload-time":"2016-05-15T06:37:12.236981Z","url":"https://files.pythonhosted.org/packages/dd/94/8aeb332d07530b552099eaf207db13d859e09facfa8162892b4f9ef302dd/psutil-4.2.0-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"data-dist-info-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"filename":"psutil-4.2.0-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"3c57a6731b3bd4c4af834b0137493a388b76192f5adc2399825015b777e0b02b"},"provenance":null,"requires-python":null,"size":167667,"upload-time":"2016-05-15T06:37:17.736147Z","url":"https://files.pythonhosted.org/packages/d5/6b/c10a228ef2cdbc077171be3b273cd2f49e4f814bf7dc2deb3a464cc126de/psutil-4.2.0-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"data-dist-info-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"filename":"psutil-4.2.0-cp34-cp34m-win32.whl","hashes":{"sha256":"0cda72a1efacd2b028c9dbf0731111041e6cf9e7be938162811ab32ab3e88254"},"provenance":null,"requires-python":null,"size":165272,"upload-time":"2016-05-15T06:37:23.488252Z","url":"https://files.pythonhosted.org/packages/35/3e/3db756e014fe3e6e22e35c8394057dcf1eef58076d84fdf83ac00a053182/psutil-4.2.0-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"data-dist-info-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"filename":"psutil-4.2.0-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"ce208e1c416e143697a1ee9dd86ae9720c740c11764a1fda88eb28a2ecc0b510"},"provenance":null,"requires-python":null,"size":167627,"upload-time":"2016-05-15T06:37:28.596251Z","url":"https://files.pythonhosted.org/packages/c7/7d/cfb299960cf6923cca782f331b034c09239e9015dedf530dd206177dd6e4/psutil-4.2.0-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"data-dist-info-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"filename":"psutil-4.2.0-cp35-cp35m-win32.whl","hashes":{"sha256":"375b0acad448e49c8bc62e036f948af610b4e0cbe2a9a28eebc06357f20f67ea"},"provenance":null,"requires-python":null,"size":167370,"upload-time":"2016-05-15T06:37:34.104073Z","url":"https://files.pythonhosted.org/packages/4d/bc/f49882e8935f147b8922fc8bb0f430fe0e7b0d3231a601cd12e1c0272f77/psutil-4.2.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"data-dist-info-metadata":{"sha256":"467235151a7ca9506acf3ca52432d5a215f42e2fe44c043d0a0068e133a957dd"},"filename":"psutil-4.2.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"bd4b535996d06728b50bc7cd8777c402bf7294ad05229c843701bd1e63583c2c"},"provenance":null,"requires-python":null,"size":170689,"upload-time":"2016-05-15T06:37:39.395642Z","url":"https://files.pythonhosted.org/packages/7b/e2/2e1078a38189d51409f50af50b598309a2bd84ebe8ca71b79515da915c82/psutil-4.2.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.2.0.tar.gz","hashes":{"sha256":"544f013a0aea7199e07e3efe5627f5d4165179a04c66050b234cc3be2eca1ace"},"provenance":null,"requires-python":null,"size":311767,"upload-time":"2016-05-15T06:35:49.367304Z","url":"https://files.pythonhosted.org/packages/a6/bf/5ce23dc9f50de662af3b4bf54812438c298634224924c4e18b7c3b57a2aa/psutil-4.2.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.2.0.win-amd64-py2.7.exe","hashes":{"sha256":"1329160e09a86029ef4e07f47dbcc39d511c343257a53acf1af429c537caae57"},"provenance":null,"requires-python":null,"size":406095,"upload-time":"2016-05-15T06:37:44.994723Z","url":"https://files.pythonhosted.org/packages/6b/af/9e43a4a4976f1d1291de8be40c848c591c6e48d7e4053a7b26ad88ba750c/psutil-4.2.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.2.0.win-amd64-py3.3.exe","hashes":{"sha256":"12623a1e2e264eac8c899b89d78648e241c12eec754a879453b2e0a4a78b10dd"},"provenance":null,"requires-python":null,"size":404495,"upload-time":"2016-05-15T06:37:50.698273Z","url":"https://files.pythonhosted.org/packages/36/85/64244b1e930aa276205f079ba3e2996e8492bd173af019bbdaee47336a6a/psutil-4.2.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.2.0.win-amd64-py3.4.exe","hashes":{"sha256":"66a4a7793dc543a3c7413cda3187e3ced45acf302f95c4d596ebcfc663c01b40"},"provenance":null,"requires-python":null,"size":404456,"upload-time":"2016-05-15T06:37:56.103096Z","url":"https://files.pythonhosted.org/packages/30/fa/a734058699f351ef90b757e0fd8d67a6145c8272bbed85c498276acacd2e/psutil-4.2.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.2.0.win-amd64-py3.5.exe","hashes":{"sha256":"c2b7aa0a99b06967fb76e83e7e9c7153a2d9a5df073986a99a4e9656cfaabe28"},"provenance":null,"requires-python":null,"size":321489,"upload-time":"2016-05-15T06:38:02.113109Z","url":"https://files.pythonhosted.org/packages/a9/1b/c90c802a6db438aeebac412ac3ecf389022f4fd93abef9c0441358f46a71/psutil-4.2.0.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.2.0.win32-py2.7.exe","hashes":{"sha256":"856480ce003ecd1601bcb83d97e25bfe79f5b08c430ee9f139a5e768173b06ef"},"provenance":null,"requires-python":null,"size":375917,"upload-time":"2016-05-15T06:38:09.945181Z","url":"https://files.pythonhosted.org/packages/e4/63/267b0977027c8a4a2f98a1ffbc2ecc7c0689d12adabee591a1ac99b4c14e/psutil-4.2.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.2.0.win32-py3.3.exe","hashes":{"sha256":"ab83fefffa495813d36300cd3ad3f232cf7c86a5e5a02d8e8ea7ab7dba5a1a90"},"provenance":null,"requires-python":null,"size":370860,"upload-time":"2016-05-15T06:38:16.360218Z","url":"https://files.pythonhosted.org/packages/c0/96/8197557cbebb16be1cfd3c87f1d0972bd2e5b0733b21d0e5d890541634e8/psutil-4.2.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.2.0.win32-py3.4.exe","hashes":{"sha256":"44c9f0e26b93c2cc9437eb88c31df32bd4337c394a959e0c31bf006da6e0f073"},"provenance":null,"requires-python":null,"size":370872,"upload-time":"2016-05-15T06:38:22.312293Z","url":"https://files.pythonhosted.org/packages/22/c9/01646a50e3c52dda4b591aae411e85afc83952107f9906ec8a5806c9fcc0/psutil-4.2.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.2.0.win32-py3.5.exe","hashes":{"sha256":"c0013a6663b794fbe18284e06d4d553a9e2135b5489a2ac6982ad53641966a55"},"provenance":null,"requires-python":null,"size":311007,"upload-time":"2016-05-15T06:38:28.533757Z","url":"https://files.pythonhosted.org/packages/48/6f/2259000fa07a4efcdc843034810eb2f8675ef5b97845912f2265589483f4/psutil-4.2.0.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"e4d7d684395672838b7bb0e9173a41bda97a9a103de6473eecda8beed8de23f6"},"data-dist-info-metadata":{"sha256":"e4d7d684395672838b7bb0e9173a41bda97a9a103de6473eecda8beed8de23f6"},"filename":"psutil-4.3.0-cp27-none-win32.whl","hashes":{"sha256":"99c2ab6c8f0d60e0c86775f8e5844e266af48cc1d9ecd1be209cd407a3e9c9a1"},"provenance":null,"requires-python":null,"size":167400,"upload-time":"2016-06-18T17:56:45.275403Z","url":"https://files.pythonhosted.org/packages/7a/a5/002caeac2ff88526cf0788315bad93be61e66477acd54209fb01cc874745/psutil-4.3.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"e4d7d684395672838b7bb0e9173a41bda97a9a103de6473eecda8beed8de23f6"},"data-dist-info-metadata":{"sha256":"e4d7d684395672838b7bb0e9173a41bda97a9a103de6473eecda8beed8de23f6"},"filename":"psutil-4.3.0-cp27-none-win_amd64.whl","hashes":{"sha256":"a91474d34bf1bc86a0d95e2c198a70723208f9dc9e50258c2060a1bab3796f81"},"provenance":null,"requires-python":null,"size":169895,"upload-time":"2016-06-18T17:56:51.386191Z","url":"https://files.pythonhosted.org/packages/e1/77/fae92fff4ca7092555c7c8fc6e02c5dfc2a9af7e15762b7354d436adeb06/psutil-4.3.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"data-dist-info-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"filename":"psutil-4.3.0-cp33-cp33m-win32.whl","hashes":{"sha256":"c987f0691c01cbe81813b0c895208c474240c96e26f7d1e945e8dabee5c85437"},"provenance":null,"requires-python":null,"size":167414,"upload-time":"2016-06-18T17:56:57.419709Z","url":"https://files.pythonhosted.org/packages/73/46/224a6a3c05df4e282240826f6bef0bd51da5ca283ffe34ed53f5601fbca1/psutil-4.3.0-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"data-dist-info-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"filename":"psutil-4.3.0-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"233a943d3e6636d648f05515a4d21b4dda63499d8ca38d6890a57a3f78a9cceb"},"provenance":null,"requires-python":null,"size":169791,"upload-time":"2016-06-18T17:57:03.096187Z","url":"https://files.pythonhosted.org/packages/6b/4d/29b3d73c27cd8f348bb313376dd98a2e97cc8a075862cf483dea4c27e4bf/psutil-4.3.0-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"data-dist-info-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"filename":"psutil-4.3.0-cp34-cp34m-win32.whl","hashes":{"sha256":"1893fe42b0fb5f11bf84ffe770be5b2e27fb7ec959ba8bf620b704552b738c72"},"provenance":null,"requires-python":null,"size":167396,"upload-time":"2016-06-18T17:57:09.859528Z","url":"https://files.pythonhosted.org/packages/dc/f5/34070d328a578c38131d96c4fe539ebbabf1c31128012755e344c030bb34/psutil-4.3.0-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"data-dist-info-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"filename":"psutil-4.3.0-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"1345217075dd5bb4fecbf7cb0fe4c0c170e93ec57d48494756f4b617cd21b449"},"provenance":null,"requires-python":null,"size":169759,"upload-time":"2016-06-18T17:57:15.923623Z","url":"https://files.pythonhosted.org/packages/8d/91/7ae0835ae1a4bc1043b565dec9c6468d56c80a4f199472a7d005ddcd48e1/psutil-4.3.0-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"data-dist-info-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"filename":"psutil-4.3.0-cp35-cp35m-win32.whl","hashes":{"sha256":"c9c4274f5f95a171437c90f65c3e9b71a871753f0a827f930e1b14aa43041eab"},"provenance":null,"requires-python":null,"size":169508,"upload-time":"2016-06-18T17:57:21.072109Z","url":"https://files.pythonhosted.org/packages/e4/40/801cd906da337a5e7a0afaaa1ce5919d9834c50d804d31ee4a1d2120a51c/psutil-4.3.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"data-dist-info-metadata":{"sha256":"21e625db3c2ab1770665e4dd336de5c07e12cfa6636d671163b7e4021a1a4341"},"filename":"psutil-4.3.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"5984ee7b2880abcdaa0819315f69a5f37da963863495c2294392cb3e98141a95"},"provenance":null,"requires-python":null,"size":172803,"upload-time":"2016-06-18T17:57:26.746663Z","url":"https://files.pythonhosted.org/packages/35/54/ddb6e8e583abf2f5a2be52a2223ba4935b382214b696fe334af54fb03dad/psutil-4.3.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.0.tar.gz","hashes":{"sha256":"86197ae5978f216d33bfff4383d5cc0b80f079d09cf45a2a406d1abb5d0299f0"},"provenance":null,"requires-python":null,"size":316470,"upload-time":"2016-06-18T17:54:55.929749Z","url":"https://files.pythonhosted.org/packages/22/a8/6ab3f0b3b74a36104785808ec874d24203c6a511ffd2732dd215cf32d689/psutil-4.3.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.0.win-amd64-py2.7.exe","hashes":{"sha256":"1ad0075b6c86c0ea5076149ec39dcecf0c692711c34317d43d73a4d8c4d4ec30"},"provenance":null,"requires-python":null,"size":408495,"upload-time":"2016-06-18T17:57:33.655137Z","url":"https://files.pythonhosted.org/packages/d5/1f/638b17eab913d19203ebd721c4e5c726b57bc50def33ab1ec0070fe2ddc2/psutil-4.3.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.0.win-amd64-py3.3.exe","hashes":{"sha256":"02c042e1f6c68c807de5caf45547971cf02977abf4cd92c8961186af9c91c488"},"provenance":null,"requires-python":null,"size":406900,"upload-time":"2016-06-18T17:57:40.580278Z","url":"https://files.pythonhosted.org/packages/a6/9b/94598ec4041ee2834f7ca21c9f1ff67e8f8cef4376ee9d9b9e3ff6950d05/psutil-4.3.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.0.win-amd64-py3.4.exe","hashes":{"sha256":"2bcfeff969f3718bb31effea752d92511f375547d337687db1bd99ccd85b7ad7"},"provenance":null,"requires-python":null,"size":406871,"upload-time":"2016-06-18T17:57:47.827133Z","url":"https://files.pythonhosted.org/packages/79/2a/abe407e5b594b2a7c0432f5605579d33ce21e0049e3dc2e5c4f37402f760/psutil-4.3.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.0.win-amd64-py3.5.exe","hashes":{"sha256":"34736b91c9785ede9d859a79e28129390f609339014f904c276ad46d0a440730"},"provenance":null,"requires-python":null,"size":323883,"upload-time":"2016-06-18T17:57:54.043353Z","url":"https://files.pythonhosted.org/packages/14/5e/072b19d913b3fc1f86b871c2869d19db3b5fa50c2e9f4980ae8646e189e0/psutil-4.3.0.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.0.win32-py2.7.exe","hashes":{"sha256":"da6ee62fb5ffda188c39aacb0499d401c131046e922ba53fb4b908937e771f94"},"provenance":null,"requires-python":null,"size":378354,"upload-time":"2016-06-18T17:58:00.735354Z","url":"https://files.pythonhosted.org/packages/7e/85/a60111c14eb80aa7a74e1a0086c1a1bbc62282df0c913730e558d16e6a8c/psutil-4.3.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.0.win32-py3.3.exe","hashes":{"sha256":"6f36fa29aa9ca935405a3cebe03cfbc6dde27088f9ad3d9b2d3baa47d4b89914"},"provenance":null,"requires-python":null,"size":373297,"upload-time":"2016-06-18T17:58:08.504160Z","url":"https://files.pythonhosted.org/packages/8e/6e/744b98493947a11625a4c63adb9a148c69c2a6e0cf7ef2afd6212804df1d/psutil-4.3.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.0.win32-py3.4.exe","hashes":{"sha256":"63b75c5374fafdaf3f389229b592f19b88a8c7951d1b973b9113df649ade5cb9"},"provenance":null,"requires-python":null,"size":373279,"upload-time":"2016-06-18T17:58:15.187200Z","url":"https://files.pythonhosted.org/packages/87/ce/a3cfd0e1b7d34fccff488a6a2283e5b947841465f81960bd87eee5f78828/psutil-4.3.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.0.win32-py3.5.exe","hashes":{"sha256":"6d0e1e9bbdab5a7d40b57d8f0841eb9e11c2395ac7ec0ddd396116240d733f46"},"provenance":null,"requires-python":null,"size":313430,"upload-time":"2016-06-18T17:58:22.261628Z","url":"https://files.pythonhosted.org/packages/eb/23/d01e7eccd76a096b0675ab9d07c8fd2de9fd31bc8f4c92f1a150bc612ad4/psutil-4.3.0.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"f2eb91a4c96ace2af993ac8587ddb20d3ed535289e9c5a0fde7a8c3d67ae0e12"},"data-dist-info-metadata":{"sha256":"f2eb91a4c96ace2af993ac8587ddb20d3ed535289e9c5a0fde7a8c3d67ae0e12"},"filename":"psutil-4.3.1-cp27-none-win32.whl","hashes":{"sha256":"b0c5bf0d2a29a6f18ac22e2d24210730dca458c9f961914289c9e027ccb5ae43"},"provenance":null,"requires-python":null,"size":168476,"upload-time":"2016-09-02T13:58:43.429910Z","url":"https://files.pythonhosted.org/packages/2d/70/0b24c7272efbb1d8cac1be1768aabfb8ddb37bdc9ab8a176f6afc7e52b0d/psutil-4.3.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"f2eb91a4c96ace2af993ac8587ddb20d3ed535289e9c5a0fde7a8c3d67ae0e12"},"data-dist-info-metadata":{"sha256":"f2eb91a4c96ace2af993ac8587ddb20d3ed535289e9c5a0fde7a8c3d67ae0e12"},"filename":"psutil-4.3.1-cp27-none-win_amd64.whl","hashes":{"sha256":"fc78c29075e623b6ea1c4a1620a120a1534ee05370b76c0ec96f6d161d79e7a1"},"provenance":null,"requires-python":null,"size":170725,"upload-time":"2016-09-02T13:58:47.944701Z","url":"https://files.pythonhosted.org/packages/67/d4/0403e6bd1cf78bd597ac960a3a6ad36cea6c12e3b413c0a1d43361128fb5/psutil-4.3.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"data-dist-info-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"filename":"psutil-4.3.1-cp33-cp33m-win32.whl","hashes":{"sha256":"aa05f44a77ef83773af39446f99e461aa3b6edb7fdabeefdcf06e913d8884d3a"},"provenance":null,"requires-python":null,"size":168384,"upload-time":"2016-09-02T13:58:52.479199Z","url":"https://files.pythonhosted.org/packages/9c/ec/5f3f06012c54de3b4443a6948fc75fe1e348ca3de408b00815b5976b8877/psutil-4.3.1-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"data-dist-info-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"filename":"psutil-4.3.1-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"6b3882eb16f2f40f1da6208a051800abadb1f82a675d9ef6ca7386e1a208b1ad"},"provenance":null,"requires-python":null,"size":170566,"upload-time":"2016-09-02T13:58:56.870812Z","url":"https://files.pythonhosted.org/packages/de/c6/74f8b4d460d89811b4c8fd426b63530222456bad767fe372ebb4f5f207be/psutil-4.3.1-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"data-dist-info-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"filename":"psutil-4.3.1-cp34-cp34m-win32.whl","hashes":{"sha256":"cf1be0b16b38f0e2081ff0c81a1a4321c206a824ba6bd51903fdd440abb370b6"},"provenance":null,"requires-python":null,"size":168375,"upload-time":"2016-09-02T13:59:01.079445Z","url":"https://files.pythonhosted.org/packages/75/ff/d02c907869d5e4cc260ce72eb253f5007a7cdf0b47326d19693f8f937eb0/psutil-4.3.1-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"data-dist-info-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"filename":"psutil-4.3.1-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"afa94bed972722882264a4df06176f6b6e6acc6bcebcc3f1db5428c7271dacba"},"provenance":null,"requires-python":null,"size":170529,"upload-time":"2016-09-02T13:59:05.507453Z","url":"https://files.pythonhosted.org/packages/c9/72/07da416b1dcf258a0cb0587c823e3611392fe29b1fcae6e078b1c254dce5/psutil-4.3.1-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"data-dist-info-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"filename":"psutil-4.3.1-cp35-cp35m-win32.whl","hashes":{"sha256":"d2254f518624e6b2262f0f878931faa4bdbe8a77d1f8826564bc4576c6a4f85e"},"provenance":null,"requires-python":null,"size":170124,"upload-time":"2016-09-02T13:59:09.837050Z","url":"https://files.pythonhosted.org/packages/cd/b0/07b7083a134c43b58515d59f271734034f8ba06840b1f371eaa6b3ab85b2/psutil-4.3.1-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"data-dist-info-metadata":{"sha256":"2bf14b2dd8f24b0dedf17492f736d534043ed69d60a0cf1d5a8925a8a351061a"},"filename":"psutil-4.3.1-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"3b377bc8ba5e62adbc709a90ea07dce2d4addbd6e1cc7acede61ddfa1c66e00a"},"provenance":null,"requires-python":null,"size":173545,"upload-time":"2016-09-02T13:59:14.368908Z","url":"https://files.pythonhosted.org/packages/7e/7e/17c1467158ccac5dc54986a657420fc194686653cfb6feddb6717a60d17f/psutil-4.3.1-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.1.tar.gz","hashes":{"sha256":"38f74182fb9e15cafd0cdf0821098a95cc17301807aed25634a18b66537ba51b"},"provenance":null,"requires-python":null,"size":315878,"upload-time":"2016-09-01T20:56:06.777431Z","url":"https://files.pythonhosted.org/packages/78/cc/f267a1371f229bf16db6a4e604428c3b032b823b83155bd33cef45e49a53/psutil-4.3.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.1.win-amd64-py2.7.exe","hashes":{"sha256":"733210f39e95744da26f2256bc36035fc463b0ae88e91496e97486ba21c63cab"},"provenance":null,"requires-python":null,"size":408715,"upload-time":"2016-09-01T21:31:30.452539Z","url":"https://files.pythonhosted.org/packages/57/44/09ec7b6a3fc1216ecc2182a64f8b6c2eaf8dd0d983fb98ecbf9cecbf54b4/psutil-4.3.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.1.win-amd64-py3.3.exe","hashes":{"sha256":"4690f720054beff4fc66551a6a34512faff328588dca8e2dbed94398b6941112"},"provenance":null,"requires-python":null,"size":407063,"upload-time":"2016-09-01T21:31:37.405818Z","url":"https://files.pythonhosted.org/packages/f2/b5/2921abbf7779d2f7bb210aa819dd2d86ecd004430c59aef9cc52c2d4057b/psutil-4.3.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.1.win-amd64-py3.4.exe","hashes":{"sha256":"fd9b66edb9f8943eda6b39e7bb9bff8b14aa8d785f5b417d7a0bfa53d4781a7a"},"provenance":null,"requires-python":null,"size":407028,"upload-time":"2016-09-01T21:31:44.758391Z","url":"https://files.pythonhosted.org/packages/91/ea/d5fb1ef4615c0febab3a347ffbc98d50177878546c22b3291892f41de8f4/psutil-4.3.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.1.win-amd64-py3.5.exe","hashes":{"sha256":"9ab5b62c6571ce545b1c40b9740af81276bd5d94439fd54de07ed59be0ce3f4f"},"provenance":null,"requires-python":null,"size":777648,"upload-time":"2016-09-01T21:31:56.074229Z","url":"https://files.pythonhosted.org/packages/0d/d9/626030b223140d88176b5dda42ea081b843249af11fa71d9e36a465b4b18/psutil-4.3.1.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.1.win32-py2.7.exe","hashes":{"sha256":"ad8857923e9bc5802d5559ab5d70c1abc1a7be8e74e779adde883c5391e2061c"},"provenance":null,"requires-python":null,"size":378819,"upload-time":"2016-09-01T21:30:59.795815Z","url":"https://files.pythonhosted.org/packages/02/99/2cc2981b30b2b1fb5b393c921ad17e4baaa1f95d7de527fe54ee222e5663/psutil-4.3.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.1.win32-py3.3.exe","hashes":{"sha256":"ae20b76cddb3391ea37de5d2aaa1656d6373161bbc8fd868a0ca055194a46e45"},"provenance":null,"requires-python":null,"size":373653,"upload-time":"2016-09-01T21:31:06.076800Z","url":"https://files.pythonhosted.org/packages/06/2f/bbcae933425945d2c9ca3e30e2f35728827e87cedad113808d3f527fe2ea/psutil-4.3.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.1.win32-py3.4.exe","hashes":{"sha256":"0613437cc28b8721de92c582d5baf742dfa6dd824c84b578f8c49a60077e969a"},"provenance":null,"requires-python":null,"size":373650,"upload-time":"2016-09-01T21:31:12.717399Z","url":"https://files.pythonhosted.org/packages/fb/a6/28bca55c499426202341f8224f66fcd69e6577211201b02605be779f44b1/psutil-4.3.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.3.1.win32-py3.5.exe","hashes":{"sha256":"c2031732cd0fb7536af491bb8d8119c9263020a52450f9999c884fd49d346b26"},"provenance":null,"requires-python":null,"size":644701,"upload-time":"2016-09-01T21:31:22.237380Z","url":"https://files.pythonhosted.org/packages/f3/c7/f5da6e76f69c97ecfabe684f4d9e179ceaf9f7c1a74d6148480b1a6c41a8/psutil-4.3.1.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"73b13e96cdf0d35ce81d30eb0c2e6b9cbfea41ee45b898f54d9e832e8e3ddd92"},"data-dist-info-metadata":{"sha256":"73b13e96cdf0d35ce81d30eb0c2e6b9cbfea41ee45b898f54d9e832e8e3ddd92"},"filename":"psutil-4.4.0-cp27-none-win32.whl","hashes":{"sha256":"4b907a0bed62a76422eae4e1ed8c8eca25fc21e57a31fc080158b8a300e21dad"},"provenance":null,"requires-python":null,"size":172746,"upload-time":"2016-10-23T14:08:37.739179Z","url":"https://files.pythonhosted.org/packages/8b/00/f7203827a3b2576bf8162ea3ba41e39c3307651218ab08e22e1433d8dc36/psutil-4.4.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"73b13e96cdf0d35ce81d30eb0c2e6b9cbfea41ee45b898f54d9e832e8e3ddd92"},"data-dist-info-metadata":{"sha256":"73b13e96cdf0d35ce81d30eb0c2e6b9cbfea41ee45b898f54d9e832e8e3ddd92"},"filename":"psutil-4.4.0-cp27-none-win_amd64.whl","hashes":{"sha256":"f60ab95f5e65c420743d5dd5285bda2a6bba6712e9380fb9a5903ea539507326"},"provenance":null,"requires-python":null,"size":175102,"upload-time":"2016-10-23T14:08:40.758084Z","url":"https://files.pythonhosted.org/packages/61/5c/d1f89100973829813e485e7bfc2f203e0d11937f958109819b62e8995b51/psutil-4.4.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"data-dist-info-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"filename":"psutil-4.4.0-cp33-cp33m-win32.whl","hashes":{"sha256":"d45919d8b900a9ae03f3d43c489323842d4051cf7a728169f01a3889f50d24ad"},"provenance":null,"requires-python":null,"size":172612,"upload-time":"2016-10-23T14:08:44.422307Z","url":"https://files.pythonhosted.org/packages/34/16/ecc7366a7d023731b8b051cff64d3bb4bed2efa4947b125a3c9031e3d799/psutil-4.4.0-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"data-dist-info-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"filename":"psutil-4.4.0-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"df706e4a8533b43c1a083c2c94e816c7a605487db49e5d49fc64329e0147c0f5"},"provenance":null,"requires-python":null,"size":174980,"upload-time":"2016-10-23T14:08:48.077823Z","url":"https://files.pythonhosted.org/packages/f7/17/6fc596007c243a12cf8c9e81b150f1e7a49c7c85c0544771398f257d5608/psutil-4.4.0-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"data-dist-info-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"filename":"psutil-4.4.0-cp34-cp34m-win32.whl","hashes":{"sha256":"d119281d253bbcc44b491b7b7e5c38802f0933179af97ab228cfd8d072ad1503"},"provenance":null,"requires-python":null,"size":172623,"upload-time":"2016-10-23T14:08:50.867277Z","url":"https://files.pythonhosted.org/packages/50/8f/e8fcfb1bacd02c743545d58514004978318d8476c8bffeee9fecfc9f1f79/psutil-4.4.0-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"data-dist-info-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"filename":"psutil-4.4.0-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"ff8a22a40bf884f52cf8dd86872b5199e7ed58eef1575e837d1d9b668fb2416a"},"provenance":null,"requires-python":null,"size":174974,"upload-time":"2016-10-23T14:08:53.576968Z","url":"https://files.pythonhosted.org/packages/41/8f/31f1fb4bb639ccaff2192b0b2a760118a05fb108e156d4966c81a246071b/psutil-4.4.0-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"data-dist-info-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"filename":"psutil-4.4.0-cp35-cp35m-win32.whl","hashes":{"sha256":"7b7c850b3afe6c7895cfd3ee630492c4aabe644a6723a583cd56bac0222d7cd8"},"provenance":null,"requires-python":null,"size":174516,"upload-time":"2016-10-23T14:08:56.521588Z","url":"https://files.pythonhosted.org/packages/18/9e/702b1da450a13448f9d96f2d9e2f69eed901c56adb01e173d8307278c883/psutil-4.4.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"data-dist-info-metadata":{"sha256":"ee9b7c8ec564a66dc6543830d64d32a37387ea58517feba35aa4eefeb15ce5de"},"filename":"psutil-4.4.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"321d09e39bb4641c98544e51fba598f894d09355a18d14367468723632cb149e"},"provenance":null,"requires-python":null,"size":178046,"upload-time":"2016-10-23T14:08:59.346105Z","url":"https://files.pythonhosted.org/packages/8a/87/a22b95d91fb1a07591ebf373d1c286a16037bf2e321577a083156d7f6a13/psutil-4.4.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.0.tar.gz","hashes":{"sha256":"f4da111f473dbf7e813e6610aec1329000536aea5e7d7e73ed20bc42cfda7ecc"},"provenance":null,"requires-python":null,"size":1831734,"upload-time":"2016-10-23T14:09:04.179623Z","url":"https://files.pythonhosted.org/packages/fc/63/af9c6a4f2ab48293f60ec204cc1336f6f17c1cb782ffb0275982ac08d663/psutil-4.4.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.0.win-amd64-py2.7.exe","hashes":{"sha256":"184132916ceb845f12f7cced3a2cf5273097d314f44be8357bdc435a6dee49cd"},"provenance":null,"requires-python":null,"size":413868,"upload-time":"2016-10-23T14:09:10.236158Z","url":"https://files.pythonhosted.org/packages/98/d9/407ee7d17b4e78c45b92639bfbd9b376b6a6022b0c63965946bfd74af6f4/psutil-4.4.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.0.win-amd64-py3.3.exe","hashes":{"sha256":"7bb1c69a062aaff4a1773cca6bbe33b261293318d72246b9519357260b67be1e"},"provenance":null,"requires-python":null,"size":412259,"upload-time":"2016-10-23T14:09:15.072697Z","url":"https://files.pythonhosted.org/packages/3f/cc/b31fbc28247722ae7ece86f8911459e29a08f5075e3d4e794cef20f920b2/psutil-4.4.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.0.win-amd64-py3.4.exe","hashes":{"sha256":"887eca39665d3de362693d66db9c15bf93313cde261de93908452241ee439e56"},"provenance":null,"requires-python":null,"size":412252,"upload-time":"2016-10-23T14:09:19.006281Z","url":"https://files.pythonhosted.org/packages/32/72/88e9e57964144ac3868f817f77e269f987c74fa51e503eb0fe8040523d28/psutil-4.4.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.0.win-amd64-py3.5.exe","hashes":{"sha256":"a289ed4f7be6c535aa4c59c997baf327788d564881c8bf08fee502fab0cdc7cb"},"provenance":null,"requires-python":null,"size":782926,"upload-time":"2016-10-23T14:09:24.493051Z","url":"https://files.pythonhosted.org/packages/45/09/8908f5931a78c90bf2ef7f91a37a752a13d4017347ac2765d0cbb89e82f6/psutil-4.4.0.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.0.win32-py2.7.exe","hashes":{"sha256":"8a18d0527ac339f5501f0bd5471040d5fb83052453eedf13106feaf99ddef6cb"},"provenance":null,"requires-python":null,"size":383869,"upload-time":"2016-10-23T14:09:28.382095Z","url":"https://files.pythonhosted.org/packages/4a/eb/a131c438621822833e3131c4998741c3b68fb6ff67786d25e9398439a640/psutil-4.4.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.0.win32-py3.3.exe","hashes":{"sha256":"8700a0373fd0c95c79ad79bc08e0a7b76cabc0c463658cbe398600c69d57293d"},"provenance":null,"requires-python":null,"size":378663,"upload-time":"2016-10-23T14:09:32.358639Z","url":"https://files.pythonhosted.org/packages/be/c8/fd76b7ba3e1cc61d586c5779d9b0a45d1ad020e1de677eb344d2583bd0f1/psutil-4.4.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.0.win32-py3.4.exe","hashes":{"sha256":"ddd3f76486a366f1dc7adc8a9e8a285ec24b3b213912b8dce0cbbb629f954b8c"},"provenance":null,"requires-python":null,"size":378674,"upload-time":"2016-10-23T14:09:35.849351Z","url":"https://files.pythonhosted.org/packages/ce/0c/efbe5aefdd136bb3e2fc292b0506761a9b04c215d295be07e11263a90923/psutil-4.4.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.0.win32-py3.5.exe","hashes":{"sha256":"28963853709f6e0958edd8d6d7469152098680800deb98a759a67745d65b164b"},"provenance":null,"requires-python":null,"size":649869,"upload-time":"2016-10-23T14:09:40.045794Z","url":"https://files.pythonhosted.org/packages/fa/b6/b97b6d4dcbc87b91db9fd2753a0c3b999b734595a97155a38003be475a6d/psutil-4.4.0.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"b5aa70030cb34744d7b8b2639183dd13ebd01c60f592c3b0c5e6fd6d8086bcd3"},"data-dist-info-metadata":{"sha256":"b5aa70030cb34744d7b8b2639183dd13ebd01c60f592c3b0c5e6fd6d8086bcd3"},"filename":"psutil-4.4.1-cp27-none-win32.whl","hashes":{"sha256":"7e77ec1a9c75a858781c1fb46fe81c999e1ae0e711198b4aaf59e5f5bd373b11"},"provenance":null,"requires-python":null,"size":172150,"upload-time":"2016-10-25T15:16:10.656877Z","url":"https://files.pythonhosted.org/packages/a9/aa/ff4e5602420cda42e9ff12949eae95abf7d6838fc79d7892b29af416f4c2/psutil-4.4.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"b5aa70030cb34744d7b8b2639183dd13ebd01c60f592c3b0c5e6fd6d8086bcd3"},"data-dist-info-metadata":{"sha256":"b5aa70030cb34744d7b8b2639183dd13ebd01c60f592c3b0c5e6fd6d8086bcd3"},"filename":"psutil-4.4.1-cp27-none-win_amd64.whl","hashes":{"sha256":"0f7f830db35c1baeb0131b2bba458b77f7db98944b2fedafc34922168e467d09"},"provenance":null,"requires-python":null,"size":174507,"upload-time":"2016-10-25T15:16:16.530462Z","url":"https://files.pythonhosted.org/packages/62/e8/c3d3e4161e29bd86d6b06d16456defcb114fd74693b15e5692df9e2b611e/psutil-4.4.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"data-dist-info-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"filename":"psutil-4.4.1-cp33-cp33m-win32.whl","hashes":{"sha256":"fa4ad0533adef033bbcbac5e20d06e77f9aadf5d9c1596317d1b668f94b01b99"},"provenance":null,"requires-python":null,"size":172015,"upload-time":"2016-10-25T15:16:19.320410Z","url":"https://files.pythonhosted.org/packages/2c/da/0428f61f2c5eda7c5053ae35a2d57b097e4eb15e48f9d222319df7f4cbd3/psutil-4.4.1-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"data-dist-info-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"filename":"psutil-4.4.1-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"af337d186b07249b86f00a71b4cf6fcfa1964484fe5fb8a7b623f4559c2859c9"},"provenance":null,"requires-python":null,"size":174384,"upload-time":"2016-10-25T15:16:22.634310Z","url":"https://files.pythonhosted.org/packages/da/ce/d36c39da6d387fbfaea68f382319887315e9201e885ef93c418b89209b31/psutil-4.4.1-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"data-dist-info-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"filename":"psutil-4.4.1-cp34-cp34m-win32.whl","hashes":{"sha256":"4e5cb45c9616dd855c07e538f523c838705ce7c24e021e645cdce4c7894e7209"},"provenance":null,"requires-python":null,"size":172026,"upload-time":"2016-10-25T15:16:28.801885Z","url":"https://files.pythonhosted.org/packages/64/63/49aeca2d1a20f5c5203302a25c825be9262f81d8bf74d7d9e0bf0789b189/psutil-4.4.1-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"data-dist-info-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"filename":"psutil-4.4.1-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"0876646748f3db5e1678a94ae3f68dcef3bd51e82b34f06109e6a28bcddc266c"},"provenance":null,"requires-python":null,"size":174374,"upload-time":"2016-10-25T15:16:32.462229Z","url":"https://files.pythonhosted.org/packages/e0/b2/48a62d3204a714b6354105ed540d54d1e272d5b230bc35eddc14a1494dd1/psutil-4.4.1-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"data-dist-info-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"filename":"psutil-4.4.1-cp35-cp35m-win32.whl","hashes":{"sha256":"e4e9033ef5d775ef8a522750688161241e79c7d4669a05784a0a1a8d37dc8c3c"},"provenance":null,"requires-python":null,"size":173920,"upload-time":"2016-10-25T15:16:38.218083Z","url":"https://files.pythonhosted.org/packages/95/e6/d6bad966efeb674bb3357c94f3e89bbd3477a24f2635c3cadc1d0f2aea76/psutil-4.4.1-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"data-dist-info-metadata":{"sha256":"979b8a1231ad076b4475296c0795b1310188d8e8fda89e2785b4da3efbf4ad52"},"filename":"psutil-4.4.1-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"d8464100c62932eeeacce2be0a1041f68b3bfcc7be261cf9486c11ee98eaedd2"},"provenance":null,"requires-python":null,"size":177448,"upload-time":"2016-10-25T15:16:42.121191Z","url":"https://files.pythonhosted.org/packages/00/58/6845a2a2f6fbbc56a58f0605744368164bf68889299f134684cc1478baf6/psutil-4.4.1-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.1.tar.gz","hashes":{"sha256":"9da43dbf7c08f5c2a8e5e2c8792f5c438f52435677b1334e9653d23ea028f4f7"},"provenance":null,"requires-python":null,"size":1831794,"upload-time":"2016-10-25T15:16:47.508715Z","url":"https://files.pythonhosted.org/packages/e5/f3/b816daefa9a6757f867f81903f40849dcf0887f588793236b476e6a30ded/psutil-4.4.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.1.win-amd64-py2.7.exe","hashes":{"sha256":"23ac3e7b6784751ceeb4338d54b0e683c955cd22b86acbc089696aeb0717ab75"},"provenance":null,"requires-python":null,"size":408863,"upload-time":"2016-10-25T15:16:52.293269Z","url":"https://files.pythonhosted.org/packages/88/93/3c8434bd64a5f89f6b0cc58e1394e88dc3c272f83f1877afac9ede222c63/psutil-4.4.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.1.win-amd64-py3.3.exe","hashes":{"sha256":"8b97ece77d2dce49dd6adbfc0c9bfb7820be4460d00732bb8cf18b77b9ffb07f"},"provenance":null,"requires-python":null,"size":407253,"upload-time":"2016-10-25T15:16:57.006499Z","url":"https://files.pythonhosted.org/packages/50/21/6b462342ab2f091bed70898894322c107493d69e24a834b9f2ab93a1e876/psutil-4.4.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.1.win-amd64-py3.4.exe","hashes":{"sha256":"a0b5976a5b5a781754b6cebe89f4e21413b04b7f4005f5713f65257bee29be5f"},"provenance":null,"requires-python":null,"size":407244,"upload-time":"2016-10-25T15:17:01.612657Z","url":"https://files.pythonhosted.org/packages/09/94/a7c3c875884adef49f52c19faff68bfd2b04160a5eb0489dbffa60b5c2dc/psutil-4.4.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.1.win-amd64-py3.5.exe","hashes":{"sha256":"b0bfe16b9bd095e56b8ec481328ba64094ccd615ee1f789a12a13cfd7bc5e34a"},"provenance":null,"requires-python":null,"size":777920,"upload-time":"2016-10-25T15:17:05.774786Z","url":"https://files.pythonhosted.org/packages/09/bf/3d850244db8e6aa94116db6787edee352692bbb9647e31c056189e6742cc/psutil-4.4.1.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.1.win32-py2.7.exe","hashes":{"sha256":"392877b5f86977b4a747fb61968e3b44df62c1d6d87536f021c4979b601c68f5"},"provenance":null,"requires-python":null,"size":378863,"upload-time":"2016-10-25T15:17:10.258103Z","url":"https://files.pythonhosted.org/packages/ed/f9/ed4cc19c84086149067d03de7cfb9f3213cf02d58b6f75c2ac16f85e0baf/psutil-4.4.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.1.win32-py3.3.exe","hashes":{"sha256":"a571f179f29215f0aa710ec495573a3522f24e8a8ba0be67d094ad6f593fba05"},"provenance":null,"requires-python":null,"size":373657,"upload-time":"2016-10-25T15:17:17.117641Z","url":"https://files.pythonhosted.org/packages/e6/ad/d8ec058821191334cf17db08c32c0308252d7b3c7490417690061dd12096/psutil-4.4.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.1.win32-py3.4.exe","hashes":{"sha256":"c2614dc6194cafd74c147e4b95febe8778430c2ecb91197ad140bf45e3e3ada7"},"provenance":null,"requires-python":null,"size":373668,"upload-time":"2016-10-25T15:17:20.392896Z","url":"https://files.pythonhosted.org/packages/26/15/6d03a2171262a1be565d06c87d584c10daa51e787c7cdd2ff932a433419e/psutil-4.4.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.1.win32-py3.5.exe","hashes":{"sha256":"5915da802a1648d0baeeee06b0d1a87eb0e4e20654f65b8428a6f4cc8cd32caf"},"provenance":null,"requires-python":null,"size":644863,"upload-time":"2016-10-25T15:17:24.141533Z","url":"https://files.pythonhosted.org/packages/d1/91/89db11eaa91c1de34d77600c47d657fce7b60e847bcf6457dddfc23b387c/psutil-4.4.1.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"5a3e00c7b872d8d4c7c640b7cfec9ede44850d18f5d050b57037614e2ec90c72"},"data-dist-info-metadata":{"sha256":"5a3e00c7b872d8d4c7c640b7cfec9ede44850d18f5d050b57037614e2ec90c72"},"filename":"psutil-4.4.2-cp27-none-win32.whl","hashes":{"sha256":"15aba78f0262d7839702913f5d2ce1e97c89e31456bb26da1a5f9f7d7fe6d336"},"provenance":null,"requires-python":null,"size":172169,"upload-time":"2016-10-26T11:01:25.916580Z","url":"https://files.pythonhosted.org/packages/d7/da/b7895f01868977c9205bf1c8ff6a88290ec443535084e206b7db7f33918f/psutil-4.4.2-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"5a3e00c7b872d8d4c7c640b7cfec9ede44850d18f5d050b57037614e2ec90c72"},"data-dist-info-metadata":{"sha256":"5a3e00c7b872d8d4c7c640b7cfec9ede44850d18f5d050b57037614e2ec90c72"},"filename":"psutil-4.4.2-cp27-none-win_amd64.whl","hashes":{"sha256":"69e30d789c495b781f7cd47c13ee64452c58abfc7132d6dd1b389af312a78239"},"provenance":null,"requires-python":null,"size":174525,"upload-time":"2016-10-26T11:01:29.001553Z","url":"https://files.pythonhosted.org/packages/0c/76/f50742570195a9a13e26ee3e3e32575a9315df90408056c991af85d23792/psutil-4.4.2-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"data-dist-info-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"filename":"psutil-4.4.2-cp33-cp33m-win32.whl","hashes":{"sha256":"e44d6b758a96539e3e02336430d3f85263d43c470c5bad93572e9b6a86c67f76"},"provenance":null,"requires-python":null,"size":172034,"upload-time":"2016-10-26T11:01:31.931360Z","url":"https://files.pythonhosted.org/packages/f1/58/64db332e3a665d0d0260fcfe39ff8609871a1203ed11afcff0e5991db724/psutil-4.4.2-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"data-dist-info-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"filename":"psutil-4.4.2-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"c2b0d8d1d8b5669b9884d0dd49ccb4094d163858d672d3d13a3fa817bc8a3197"},"provenance":null,"requires-python":null,"size":174401,"upload-time":"2016-10-26T11:01:35.879864Z","url":"https://files.pythonhosted.org/packages/39/31/4b5a2ab9d20d14fa6c821b7fbbf69ae93708e1a4ba335d4e2ead549b2f59/psutil-4.4.2-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"data-dist-info-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"filename":"psutil-4.4.2-cp34-cp34m-win32.whl","hashes":{"sha256":"10fbb631142a3200623f4ab49f8bf82c32b79b8fe179f6056d01da3dfc589da1"},"provenance":null,"requires-python":null,"size":172044,"upload-time":"2016-10-26T11:01:38.950793Z","url":"https://files.pythonhosted.org/packages/67/f6/f034ad76faa7b9bd24d70a663b5dca8d71b6b7ddb4a3ceecd6a0a63afe92/psutil-4.4.2-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"data-dist-info-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"filename":"psutil-4.4.2-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"e423dd9cb12256c742d1d56ec38bc7d2a7fa09287c82c41e475e68b9f932c2af"},"provenance":null,"requires-python":null,"size":174393,"upload-time":"2016-10-26T11:01:41.933150Z","url":"https://files.pythonhosted.org/packages/f8/f2/df2d8de978236067d0fce847e3734c6e78bfd57df9db5a2786cc544ca85f/psutil-4.4.2-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"data-dist-info-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"filename":"psutil-4.4.2-cp35-cp35m-win32.whl","hashes":{"sha256":"7481f299ae0e966a10cb8dd93a327efd8f51995d9bdc8810dcc65d3b12d856ee"},"provenance":null,"requires-python":null,"size":173938,"upload-time":"2016-10-26T11:01:44.870925Z","url":"https://files.pythonhosted.org/packages/57/2d/0cdc3fdb9853ec1d815e0948104f1c51894f47c8ee2152c019ba754ce678/psutil-4.4.2-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"data-dist-info-metadata":{"sha256":"34c5de5faa22e5cdae426206704ec3a7d177ec1d34a20e3ad2d072136ac17b84"},"filename":"psutil-4.4.2-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"d96d31d83781c7f3d0df8ccb1cc50650ca84d4722c5070b71ce8f1cc112e02e0"},"provenance":null,"requires-python":null,"size":177465,"upload-time":"2016-10-26T11:01:47.925426Z","url":"https://files.pythonhosted.org/packages/cd/50/3c238d67e025701fa18cc37ed445327efcf14ea4d06e357eef92d1a250bf/psutil-4.4.2-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.2.tar.gz","hashes":{"sha256":"1c37e6428f7fe3aeea607f9249986d9bb933bb98133c7919837fd9aac4996b07"},"provenance":null,"requires-python":null,"size":1832052,"upload-time":"2016-10-26T11:01:53.029413Z","url":"https://files.pythonhosted.org/packages/6c/49/0f784a247868e167389f6ac76b8699b2f3d6f4e8e85685dfec43e58d1ed1/psutil-4.4.2.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.2.win-amd64-py2.7.exe","hashes":{"sha256":"11a20c0328206dce68f8da771461aeaef9c44811e639216fd935837e758632dc"},"provenance":null,"requires-python":null,"size":408884,"upload-time":"2016-10-26T11:01:57.830427Z","url":"https://files.pythonhosted.org/packages/58/bc/ac2a052035e945153f0bdc23d7c169e7e64e0729af6ff96f85d960962def/psutil-4.4.2.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.2.win-amd64-py3.3.exe","hashes":{"sha256":"642194ebefa573de62406883eb33868917bab2cc2e21b68d551248e194dd0b0a"},"provenance":null,"requires-python":null,"size":407275,"upload-time":"2016-10-26T11:02:00.968973Z","url":"https://files.pythonhosted.org/packages/38/b3/c8244a4858de5f9525f176d72cb06584433446a5b9d7272d5eec7a435f81/psutil-4.4.2.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.2.win-amd64-py3.4.exe","hashes":{"sha256":"c02b9fb5f1f3c857938b26a73b1ca92007e8b0b2fd64693b29300fae0ceaf679"},"provenance":null,"requires-python":null,"size":407267,"upload-time":"2016-10-26T11:02:04.291434Z","url":"https://files.pythonhosted.org/packages/cd/4d/8f447ecba1a3d5cae9f3bdd8395b45e2e6d07834a05fa3ce58c766ffcea6/psutil-4.4.2.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.2.win-amd64-py3.5.exe","hashes":{"sha256":"6c40dc16b579f645e1804341322364203d0b21045747e62e360fae843d945e20"},"provenance":null,"requires-python":null,"size":777940,"upload-time":"2016-10-26T11:02:10.124058Z","url":"https://files.pythonhosted.org/packages/73/6e/1c58cbdedcf4e2226f8ca182c947e29f29dba6965fdae0aa8e6a361365ce/psutil-4.4.2.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.2.win32-py2.7.exe","hashes":{"sha256":"c353ecc62e67bf7c7051c087670d49eae9472f1b30bb1623d667b0cd137e8934"},"provenance":null,"requires-python":null,"size":378885,"upload-time":"2016-10-26T11:02:16.586357Z","url":"https://files.pythonhosted.org/packages/37/27/9eba3e29f8291be103a8e5e44c06eefe5357217e9d8d294ff154dc3bff90/psutil-4.4.2.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.2.win32-py3.3.exe","hashes":{"sha256":"7106cb3722235ccb6fe4b18c51f60a548d4b111ec2d209abdcd3998661f4593a"},"provenance":null,"requires-python":null,"size":373678,"upload-time":"2016-10-26T11:02:20.035049Z","url":"https://files.pythonhosted.org/packages/62/25/189e73856fa3172d5e785404eb9cf4561b01094363cc39ec267399e401c4/psutil-4.4.2.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.2.win32-py3.4.exe","hashes":{"sha256":"de1f53fe955dfba562f7791f72517935010a2e88f9caad36917e8c5c03de9051"},"provenance":null,"requires-python":null,"size":373689,"upload-time":"2016-10-26T11:02:23.073976Z","url":"https://files.pythonhosted.org/packages/10/4a/e50ca35162538645c4d98816757763e4aaaac1ccaf055b39265acb8739e1/psutil-4.4.2.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-4.4.2.win32-py3.5.exe","hashes":{"sha256":"2eb123ca86057ed4f31cfc9880e098ee7a8e19c7ec02b068c45e7559ae7539a6"},"provenance":null,"requires-python":null,"size":644886,"upload-time":"2016-10-26T11:02:26.650470Z","url":"https://files.pythonhosted.org/packages/c5/97/018f0580bc3d3d85f1e084e683e904bc33cd58ca96e0ecb5ae6f346d6452/psutil-4.4.2.win32-py3.5.exe","yanked":false},{"core-metadata":{"sha256":"3db8fbdc9c0693e219a15e49b871dc421e85735214638b6e31d68aee5b78c550"},"data-dist-info-metadata":{"sha256":"3db8fbdc9c0693e219a15e49b871dc421e85735214638b6e31d68aee5b78c550"},"filename":"psutil-5.0.0-cp27-none-win32.whl","hashes":{"sha256":"cc2560b527cd88a9bc062ee4bd055c40b9fc107e37db01997422c75a3f94efe9"},"provenance":null,"requires-python":null,"size":175039,"upload-time":"2016-11-06T18:28:50.896397Z","url":"https://files.pythonhosted.org/packages/bb/82/efee0c83eab6ad3dd4a70b2b91013a50776ff6253f9b71d825914e693dfa/psutil-5.0.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"3db8fbdc9c0693e219a15e49b871dc421e85735214638b6e31d68aee5b78c550"},"data-dist-info-metadata":{"sha256":"3db8fbdc9c0693e219a15e49b871dc421e85735214638b6e31d68aee5b78c550"},"filename":"psutil-5.0.0-cp27-none-win_amd64.whl","hashes":{"sha256":"8a6cbc7165a476d08a89ee3078a74d111729cf515fd831db9f635012e56f9759"},"provenance":null,"requires-python":null,"size":177395,"upload-time":"2016-11-06T18:28:54.500906Z","url":"https://files.pythonhosted.org/packages/16/bf/09cb9f5286e5037eba1d5fe347c0f622b325e068757f1072c56f9b130cc3/psutil-5.0.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"data-dist-info-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"filename":"psutil-5.0.0-cp33-cp33m-win32.whl","hashes":{"sha256":"9b0f13e325f007a0fd04e9d44cfdb5187c0b3e144f89533324dd9f74c25bd9ec"},"provenance":null,"requires-python":null,"size":174954,"upload-time":"2016-11-06T18:28:57.539707Z","url":"https://files.pythonhosted.org/packages/a2/5d/82b85eb4c24c82064c5c216f52df92f7861ff0a02d3cbcfebfacadc2b28f/psutil-5.0.0-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"data-dist-info-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"filename":"psutil-5.0.0-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"ade8924028b2c23cc9ffe4a0737de38c668d50be5ce19495790154f530ce5389"},"provenance":null,"requires-python":null,"size":177229,"upload-time":"2016-11-06T18:29:00.558809Z","url":"https://files.pythonhosted.org/packages/14/5b/00b74d0ada625090dfaba6244860a9ec4df2e79a6c8440dd490e46c65543/psutil-5.0.0-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"data-dist-info-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"filename":"psutil-5.0.0-cp34-cp34m-win32.whl","hashes":{"sha256":"af01b73fd66f138e06f804508fd33118823fd2abb89f105ae2b99efa4c8fd1a3"},"provenance":null,"requires-python":null,"size":174957,"upload-time":"2016-11-06T18:29:04.527838Z","url":"https://files.pythonhosted.org/packages/fd/e6/5a08cd23cefadd22b8e74d0bd1c525897c9db4dc1d2f44716dee848b8fe3/psutil-5.0.0-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"data-dist-info-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"filename":"psutil-5.0.0-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"846925435e69cc7b802cd7d1a4bd640e180d0db15277c24e196d3a5799bf6760"},"provenance":null,"requires-python":null,"size":177189,"upload-time":"2016-11-06T18:29:08.003063Z","url":"https://files.pythonhosted.org/packages/b4/11/f68bad0d5fc08800006d2355c941e186315987456158a9b06f40e483d4e7/psutil-5.0.0-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"data-dist-info-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"filename":"psutil-5.0.0-cp35-cp35m-win32.whl","hashes":{"sha256":"d7885ff254425c64bcc2dbff256ec1367515c15218bfda0fd3d799092437d908"},"provenance":null,"requires-python":null,"size":176812,"upload-time":"2016-11-06T18:29:11.700563Z","url":"https://files.pythonhosted.org/packages/c4/16/84213b90c78e437eff09285138947d12105ea982cb6f8fda4ab2855014e6/psutil-5.0.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"data-dist-info-metadata":{"sha256":"48627687813fd477d371c4588c61dc9da3e42f22546da21c80f273e30c587f1b"},"filename":"psutil-5.0.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"e35cb38037973ff05bc52dac4f382d17028104d77f0bb51792de93f359046902"},"provenance":null,"requires-python":null,"size":180171,"upload-time":"2016-11-06T18:29:15.915700Z","url":"https://files.pythonhosted.org/packages/98/ef/40582d2d3e39fdcc202a33ba6aab15f6ccb36cdcd04f6756cc9afe30df30/psutil-5.0.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.0.win-amd64-py2.7.exe","hashes":{"sha256":"964e9db2641e3be6a80b5a3135f7a9425f87d8416342b4d967202e4854f3eeba"},"provenance":null,"requires-python":null,"size":411778,"upload-time":"2016-11-06T18:29:32.458135Z","url":"https://files.pythonhosted.org/packages/74/f0/e8964d58e12c7716775157821de2e758fece2581bc9f3b7c333a4de29b90/psutil-5.0.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.0.win-amd64-py3.3.exe","hashes":{"sha256":"d7b933193322523314b0b2d699a43e43a70f43016f73782dfd892bc7ee95ecd1"},"provenance":null,"requires-python":null,"size":410125,"upload-time":"2016-11-06T18:29:37.513499Z","url":"https://files.pythonhosted.org/packages/6e/a9/4438ec3289e3925a638cb079039b748ee4fb7e3ba18a93baed39f4fcd11e/psutil-5.0.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.0.win-amd64-py3.4.exe","hashes":{"sha256":"8709a5057d42d5e55f3940bb1e70370316defb3da03ad869342755b5a2c17c78"},"provenance":null,"requires-python":null,"size":410086,"upload-time":"2016-11-06T18:29:41.067409Z","url":"https://files.pythonhosted.org/packages/db/85/c2e27aab6db3c4d404f0e81762f5a962b7213caed0a789cb5df2990ac489/psutil-5.0.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.0.win-amd64-py3.5.exe","hashes":{"sha256":"c065eaf76a5423341f511e732f1a17e75a55dc4aceee9a321a619a5892aec18f"},"provenance":null,"requires-python":null,"size":780670,"upload-time":"2016-11-06T18:29:45.597507Z","url":"https://files.pythonhosted.org/packages/27/fd/12a4ef4a9940331a5521216e839444dcab0addd3a49fb90c940bdc40e46e/psutil-5.0.0.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.0.win32-py2.7.exe","hashes":{"sha256":"8eca28511d493209f59fe99cebfb8ecc65b8d6691f8a80fa3ab50dbb4994c81b"},"provenance":null,"requires-python":null,"size":381779,"upload-time":"2016-11-06T18:29:49.728094Z","url":"https://files.pythonhosted.org/packages/bc/77/457ddeac355fe88c8d4ee8062ce66ab5dd142f5c9423d1bfd10a568c7884/psutil-5.0.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.0.win32-py3.3.exe","hashes":{"sha256":"7a8e7654789c468d2a6c32508638563741046f138405fea2a4427a9228ac86f4"},"provenance":null,"requires-python":null,"size":376623,"upload-time":"2016-11-06T18:29:53.256127Z","url":"https://files.pythonhosted.org/packages/8d/1a/2f61de7f89602dfd975f961bf057522ff53ef3bdc48610dbdcf306b542df/psutil-5.0.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.0.win32-py3.4.exe","hashes":{"sha256":"4db1f1d8655a63832c9ab85d77c969ab95b740128ca9053b5c108a1e5efe6a7c"},"provenance":null,"requires-python":null,"size":376624,"upload-time":"2016-11-06T18:29:56.669027Z","url":"https://files.pythonhosted.org/packages/c1/3f/d8921d1a8672545a390fd874b77c9a30444b64ab5f8c48e2e9d971f4e98f/psutil-5.0.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.0.win32-py3.5.exe","hashes":{"sha256":"10f05841e3bf7b060b3779367b9cd953a6c97303c08a0e2630a9e461ce124412"},"provenance":null,"requires-python":null,"size":647782,"upload-time":"2016-11-06T18:30:00.475783Z","url":"https://files.pythonhosted.org/packages/ec/53/dc7d9e33e77efd26f7c4a9ba8d1e23ba9ce432077e16a55f1f755970e7f7/psutil-5.0.0.win32-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.0.zip","hashes":{"sha256":"5411e22c63168220f4b8cc42fd05ea96f5b5e65e08b93b675ca50653aea482f8"},"provenance":null,"requires-python":null,"size":374074,"upload-time":"2016-11-06T18:41:47.892123Z","url":"https://files.pythonhosted.org/packages/93/7f/347309562d30c688299727e65f4d76ef34180c406dfb6f2c7b6c8d746e13/psutil-5.0.0.zip","yanked":false},{"core-metadata":{"sha256":"3c16b5cea3c7592513b1491a7696e113d3ba33a4a94d74841ee2340fb4081106"},"data-dist-info-metadata":{"sha256":"3c16b5cea3c7592513b1491a7696e113d3ba33a4a94d74841ee2340fb4081106"},"filename":"psutil-5.0.1-cp27-none-win32.whl","hashes":{"sha256":"1f2379809f2182652fc740faefa511f78b5975e6471e5fa419882dd9e082f245"},"provenance":null,"requires-python":null,"size":176737,"upload-time":"2016-12-21T01:35:18.007609Z","url":"https://files.pythonhosted.org/packages/cd/2d/ecb32ce765c52780768e4078d4d2916bdaa790b313c454f52aebe27d559d/psutil-5.0.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"3c16b5cea3c7592513b1491a7696e113d3ba33a4a94d74841ee2340fb4081106"},"data-dist-info-metadata":{"sha256":"3c16b5cea3c7592513b1491a7696e113d3ba33a4a94d74841ee2340fb4081106"},"filename":"psutil-5.0.1-cp27-none-win_amd64.whl","hashes":{"sha256":"86d67da7bfed474b5b0d545b29211f16d622e87c4089de5ea41a3fbcfc4872c7"},"provenance":null,"requires-python":null,"size":179100,"upload-time":"2016-12-21T01:35:20.364405Z","url":"https://files.pythonhosted.org/packages/6b/7e/68835630dcc765452b76b61936f75c1ef71fceae8ebaa7ecb27e42bf3f28/psutil-5.0.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"data-dist-info-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"filename":"psutil-5.0.1-cp33-cp33m-win32.whl","hashes":{"sha256":"6a95283fd048810811cf971bec5cec3998e1e62e66237ef7a41a42dd0da29f8c"},"provenance":null,"requires-python":null,"size":176612,"upload-time":"2016-12-21T01:35:22.141278Z","url":"https://files.pythonhosted.org/packages/43/46/3fad73a8c581f63f08fb1f93ef5651e939caa5601a50baca7f2af3c54e2b/psutil-5.0.1-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"data-dist-info-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"filename":"psutil-5.0.1-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"8f492b531c4321c7c43ef82b60421f4bcf5ded4ba4e13f534c064ad6c2d910ed"},"provenance":null,"requires-python":null,"size":178930,"upload-time":"2016-12-21T01:35:23.896324Z","url":"https://files.pythonhosted.org/packages/b0/d0/6d9e39115aaab0d8d52422b909e365938a6dcebe5fa04fac3a6b70398aee/psutil-5.0.1-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"data-dist-info-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"filename":"psutil-5.0.1-cp34-cp34m-win32.whl","hashes":{"sha256":"5cc1b91d4848453b74ad8e63275a19e784ef3acd943c3627134a607b602bc31d"},"provenance":null,"requires-python":null,"size":176623,"upload-time":"2016-12-21T01:35:26.391755Z","url":"https://files.pythonhosted.org/packages/31/21/d836f37c8fde1760e889b0ace67e151c6e56f2ce819b227715a5f291452f/psutil-5.0.1-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"data-dist-info-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"filename":"psutil-5.0.1-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"55874a1814faceaa090b7fa7addbf350603fd7042562adaae13eb6e46d3ec907"},"provenance":null,"requires-python":null,"size":178917,"upload-time":"2016-12-21T01:35:28.280103Z","url":"https://files.pythonhosted.org/packages/a1/9c/cb3f68be56ab366dbf4c901002d281e30c50cf951377ca8155c0ad304394/psutil-5.0.1-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"data-dist-info-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"filename":"psutil-5.0.1-cp35-cp35m-win32.whl","hashes":{"sha256":"7f2d7b97faa524f75736dfde418680eff332f4be66d6217b67d09c630c90d02e"},"provenance":null,"requires-python":null,"size":178514,"upload-time":"2016-12-21T01:35:30.152277Z","url":"https://files.pythonhosted.org/packages/f7/f1/dd823eab436db1eac1ca2c8364918786584db996dc4d2bef5a3b0ebd7619/psutil-5.0.1-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"data-dist-info-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"filename":"psutil-5.0.1-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"bf4072d4d188802505b9229ec00e141083c127bb19a6c5636c62b0daabda4bd5"},"provenance":null,"requires-python":null,"size":181835,"upload-time":"2016-12-21T01:35:32.091445Z","url":"https://files.pythonhosted.org/packages/2a/3a/107a964dc66cb4f22af9e919dde846b73c0b4ff735b10ede680895ed296c/psutil-5.0.1-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"data-dist-info-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"filename":"psutil-5.0.1-cp36-cp36m-win32.whl","hashes":{"sha256":"786bbbeb3ea98d82ff5cedc86b640bad97bff435c819f26bddaa388da58d47da"},"provenance":null,"requires-python":null,"size":178650,"upload-time":"2017-01-15T18:27:34.549244Z","url":"https://files.pythonhosted.org/packages/c0/a1/c5d0aa766323b150dce5e51a5360542ccb75c109723d2d95e70d8cb248b8/psutil-5.0.1-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"data-dist-info-metadata":{"sha256":"b03798768de1ae02a3387726d3bb1c881c70cdee8bea158f236b08758379017e"},"filename":"psutil-5.0.1-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"544c0803760995fe42a2b7050cfd6ed32379b09ce6cd7a1eaf89c221d8669cc3"},"provenance":null,"requires-python":null,"size":181970,"upload-time":"2017-01-15T18:27:39.376642Z","url":"https://files.pythonhosted.org/packages/c0/e4/bf1059c5dd55abf65fe2ac92a0b24a92d09644a9bbc17f1ad2b497ae9d7d/psutil-5.0.1-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.1.tar.gz","hashes":{"sha256":"9d8b7f8353a2b2eb6eb7271d42ec99d0d264a9338a37be46424d56b4e473b39e"},"provenance":null,"requires-python":null,"size":326693,"upload-time":"2016-12-21T01:35:34.145966Z","url":"https://files.pythonhosted.org/packages/d9/c8/8c7a2ab8ec108ba9ab9a4762c5a0d67c283d41b13b5ce46be81fdcae3656/psutil-5.0.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.1.win-amd64-py2.7.exe","hashes":{"sha256":"0c54e3b7cdc0dbb8a19b58c3eb3845a5f9f48d3be2b06ed9aa1e553db8f9db74"},"provenance":null,"requires-python":null,"size":414521,"upload-time":"2016-12-21T01:35:36.513939Z","url":"https://files.pythonhosted.org/packages/3d/90/61f2860cd2c39a4381e415897b7ff94aab21c9aca38690d0c99c1c83b85f/psutil-5.0.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.1.win-amd64-py3.3.exe","hashes":{"sha256":"0fccb19631d555998fc8c98840c83678244f873486d20d5f24ebb0ac8e19d2f1"},"provenance":null,"requires-python":null,"size":412871,"upload-time":"2016-12-21T01:35:38.959549Z","url":"https://files.pythonhosted.org/packages/05/f7/6ba6efe79620bcaa9af41e0a8501e477815edccc0cad4bd379a8e8bea89c/psutil-5.0.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.1.win-amd64-py3.4.exe","hashes":{"sha256":"77fde4936f26080aa14b89d292b3ebefabb80be69ef407352cbad6d2ff6882d4"},"provenance":null,"requires-python":null,"size":412858,"upload-time":"2016-12-21T01:35:42.062765Z","url":"https://files.pythonhosted.org/packages/c7/b3/3b83e69b5d96ed052b45f1fe88882b3976a3a066c0497597d3ce1407f17f/psutil-5.0.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.1.win-amd64-py3.5.exe","hashes":{"sha256":"a9125b7bc12127174cf7974444ca2b39a3722f59ead1d985053e7358a3d29acd"},"provenance":null,"requires-python":null,"size":783380,"upload-time":"2016-12-21T01:35:44.660601Z","url":"https://files.pythonhosted.org/packages/4e/cf/d1004fa0617f6d6e39adf48d242f76d5d43ecd497f46add2abb513b6ffcd/psutil-5.0.1.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.1.win-amd64-py3.6.exe","hashes":{"sha256":"9a3f1413b24b6751e97e51284761e1778ec0cd0a456595edad6c2f7c115b3368"},"provenance":null,"requires-python":null,"size":783511,"upload-time":"2017-01-15T18:25:42.785800Z","url":"https://files.pythonhosted.org/packages/a0/94/1f29e03230504dafeb128a4c6e381606b038ce5d4dc9b8731632eb6b0928/psutil-5.0.1.win-amd64-py3.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.1.win32-py2.7.exe","hashes":{"sha256":"671c7b2d3fa8deffb879e9cb6cc00d83d1d990bc81f0c487576b70e811f102bf"},"provenance":null,"requires-python":null,"size":384515,"upload-time":"2016-12-21T01:35:47.208014Z","url":"https://files.pythonhosted.org/packages/2d/7c/85aa8efb36c4fcac4a845b9cfc0744c502620394cdb81db574dd20f7dda0/psutil-5.0.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.1.win32-py3.3.exe","hashes":{"sha256":"603bd19426b59762ad55ba5a0b8237282868addf2d0930e21b4dca79fc188787"},"provenance":null,"requires-python":null,"size":379325,"upload-time":"2016-12-21T01:35:49.642792Z","url":"https://files.pythonhosted.org/packages/1c/06/201f913389b920c24153fbfdc07c7a451653b00688d6c2ba92428b9a2c23/psutil-5.0.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.1.win32-py3.4.exe","hashes":{"sha256":"08e28b4dba54dd6b0d84718c988239c305e58c7f93160496661955de4f6cfe13"},"provenance":null,"requires-python":null,"size":379338,"upload-time":"2016-12-21T01:35:52.592934Z","url":"https://files.pythonhosted.org/packages/58/27/a42b1d12c201880822fd92588321e489c869f4b72eb906f8f79745bee6cb/psutil-5.0.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.1.win32-py3.5.exe","hashes":{"sha256":"1e71dd9aa041a7fa55f3bcc78b578e84f31004e6ce9df97229f05c60529fedb1"},"provenance":null,"requires-python":null,"size":650529,"upload-time":"2016-12-21T01:35:55.051616Z","url":"https://files.pythonhosted.org/packages/fb/c6/c60717f25c8ccfddabd57109d03580d24542eedde8d23a673328876b4137/psutil-5.0.1.win32-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.0.1.win32-py3.6.exe","hashes":{"sha256":"c879310328c0c248331dffeea4adbe691fad7b7095cf9c2ac0a4d78a09cd8a17"},"provenance":null,"requires-python":null,"size":650663,"upload-time":"2017-01-15T18:25:51.768008Z","url":"https://files.pythonhosted.org/packages/00/09/04898f2ce604af89d7ca6866632c05a1fb0fbd7b5ea0c7e84a62570c2678/psutil-5.0.1.win32-py3.6.exe","yanked":false},{"core-metadata":{"sha256":"45f08e36846b408dfbda38b60b36dd8b6430d6fbd955f5945dc985a85eac6068"},"data-dist-info-metadata":{"sha256":"45f08e36846b408dfbda38b60b36dd8b6430d6fbd955f5945dc985a85eac6068"},"filename":"psutil-5.1.0-cp27-cp27m-win32.whl","hashes":{"sha256":"85524e46a1c0c7f5274640809deb96c7cdb5133a0eb805bbed0a1f825c8de77e"},"provenance":null,"requires-python":null,"size":183880,"upload-time":"2017-02-01T18:27:27.314031Z","url":"https://files.pythonhosted.org/packages/d9/bf/eae1c0aa2a5a01456e78d5df4c7cefc512854a24f43103034180ff5ea92f/psutil-5.1.0-cp27-cp27m-win32.whl","yanked":false},{"core-metadata":{"sha256":"45f08e36846b408dfbda38b60b36dd8b6430d6fbd955f5945dc985a85eac6068"},"data-dist-info-metadata":{"sha256":"45f08e36846b408dfbda38b60b36dd8b6430d6fbd955f5945dc985a85eac6068"},"filename":"psutil-5.1.0-cp27-cp27m-win_amd64.whl","hashes":{"sha256":"0a64f395295aafe7c22d6cde81076151bf80f26e180e50c00c1cde5857cad224"},"provenance":null,"requires-python":null,"size":186335,"upload-time":"2017-02-01T18:27:35.043588Z","url":"https://files.pythonhosted.org/packages/f6/5d/67120bb4cea2432d333cdf0ab406336ef62122caacb2379e73a88a9cde82/psutil-5.1.0-cp27-cp27m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"data-dist-info-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"filename":"psutil-5.1.0-cp33-cp33m-win32.whl","hashes":{"sha256":"bd432606d09722220f8f492223e8b69343d30f21e7ed193712632e4fdcf87af2"},"provenance":null,"requires-python":null,"size":183793,"upload-time":"2017-02-01T18:27:43.056910Z","url":"https://files.pythonhosted.org/packages/26/35/ec223413c9301d8eeecf3d319d71a117dbd2847b628b079f3757d5ec693d/psutil-5.1.0-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"data-dist-info-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"filename":"psutil-5.1.0-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"f98c37506e5abfeb2c857794ccfbce86b376cf6344210ea55f41b0ced5fd8d98"},"provenance":null,"requires-python":null,"size":186185,"upload-time":"2017-02-01T18:27:49.896261Z","url":"https://files.pythonhosted.org/packages/9f/20/bb95f30c99837f3c41a8a595752d9cc1afe595f160d0612eea6e4a905de9/psutil-5.1.0-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"data-dist-info-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"filename":"psutil-5.1.0-cp34-cp34m-win32.whl","hashes":{"sha256":"8760214ce01f71cd6766527396607cf2b3b41e6f8a34aaa9f716090750fd9925"},"provenance":null,"requires-python":null,"size":183803,"upload-time":"2017-02-01T18:27:56.955786Z","url":"https://files.pythonhosted.org/packages/0b/f2/90a7288b36a18fb522e00116f7f5c657df9dba3a12636d2fc9bcef97f524/psutil-5.1.0-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"data-dist-info-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"filename":"psutil-5.1.0-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"2309cf46f5db0a78f52e56e06cbb258fe76d3d54cf8ab9fa067b4c26cd722541"},"provenance":null,"requires-python":null,"size":186156,"upload-time":"2017-02-01T18:28:03.790958Z","url":"https://files.pythonhosted.org/packages/51/c6/3757b2bea37edc9889fe85e2ca9b8a2c47b3b758d35f65704f2a5ac5c13e/psutil-5.1.0-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"data-dist-info-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"filename":"psutil-5.1.0-cp35-cp35m-win32.whl","hashes":{"sha256":"6d89d5f68433afe6755bd26ae3ea4587f395c7613d7be5bc4f0f97b1e299228a"},"provenance":null,"requires-python":null,"size":185696,"upload-time":"2017-02-01T18:28:10.369149Z","url":"https://files.pythonhosted.org/packages/30/9c/322df29c490ceb97d8cbbc663424fad94e900a1921d03cc9c61e36f17444/psutil-5.1.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"data-dist-info-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"filename":"psutil-5.1.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"b3c15965b2dfad34ea79494f82007e71b02e1c5352b551d00647fb2be9deabe1"},"provenance":null,"requires-python":null,"size":189130,"upload-time":"2017-02-01T18:28:17.197543Z","url":"https://files.pythonhosted.org/packages/b3/3a/37d6bf7dedf263e1119b03aaee8565182bf2577ed5ecd7975e253e242bd9/psutil-5.1.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"data-dist-info-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"filename":"psutil-5.1.0-cp36-cp36m-win32.whl","hashes":{"sha256":"d32551c242c041b0506d13cfd271db56ba57323283b1f952b858505824f3e82a"},"provenance":null,"requires-python":null,"size":185695,"upload-time":"2017-02-01T18:28:25.059542Z","url":"https://files.pythonhosted.org/packages/42/0b/5ee6feda28c2895b0e8cae4d828d356e13e29bf88bc614065e490bfc8b51/psutil-5.1.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"data-dist-info-metadata":{"sha256":"f1cc4b1bb4982cfc6e8259dbc0fd96789e31eff9b65d8e5a44401e11d9926c55"},"filename":"psutil-5.1.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"3b821bb59911afdba5c0c28b1e6e7511cfb0869dd8827c3ab6916ead508c9155"},"provenance":null,"requires-python":null,"size":189127,"upload-time":"2017-02-01T18:28:32.228516Z","url":"https://files.pythonhosted.org/packages/af/6f/ef0799221597f367171c0bb8b3f1b22865e6b7507ee26e1a6a16f408a058/psutil-5.1.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.0.tar.gz","hashes":{"sha256":"7570e1d82345fab3a0adce24baf993adbca4c87a1be2fa6ee79babdaafa817fb"},"provenance":null,"requires-python":null,"size":339603,"upload-time":"2017-02-01T18:29:04.029583Z","url":"https://files.pythonhosted.org/packages/ba/5f/87b151dd53f8790408adf5096fc81c3061313c36a089d9f7ec9e916da0c1/psutil-5.1.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.0.win-amd64-py2.7.exe","hashes":{"sha256":"07aab3a12c00315a144b2a61d384364e64077c08c0a79966f18b744d867f9727"},"provenance":null,"requires-python":null,"size":422485,"upload-time":"2017-02-01T18:24:47.444024Z","url":"https://files.pythonhosted.org/packages/8c/1c/8bf1c2f6f2b4f012449bd9781d55397e85e30487c2879ed35569c7f616de/psutil-5.1.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.0.win-amd64-py3.3.exe","hashes":{"sha256":"b78de627c157b4889e055fd00a635ad0fbe107ae4b3059f419738edbfb15b3f4"},"provenance":null,"requires-python":null,"size":420852,"upload-time":"2017-02-01T18:25:00.582537Z","url":"https://files.pythonhosted.org/packages/de/5a/3041f7d07d9699ef148291721ee167608761e223bc26bbe33483cbe90517/psutil-5.1.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.0.win-amd64-py3.4.exe","hashes":{"sha256":"48fc3945219f6577b61a76f81fbbaa3ce6bdccf0e87650597c88aac38e63904b"},"provenance":null,"requires-python":null,"size":420825,"upload-time":"2017-02-01T18:25:14.404878Z","url":"https://files.pythonhosted.org/packages/d0/83/4e03c76cd202d4a4ead9b468cf2afa611d689b1ae17e4f152b67c05d2a27/psutil-5.1.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.0.win-amd64-py3.5.exe","hashes":{"sha256":"98180376d2f0a3b0d24dbd8ef64da4aac46f98ccc84d2e3ebc3a486b97a75915"},"provenance":null,"requires-python":null,"size":791399,"upload-time":"2017-02-01T18:25:37.374511Z","url":"https://files.pythonhosted.org/packages/ff/86/328005e07308a87f4d86108a51f940fba1fb1c4f60d4676f80fc66a9dd7a/psutil-5.1.0.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.0.win-amd64-py3.6.exe","hashes":{"sha256":"a574d3fb56514ae98f2836a218b0dcf6daeb31b9945cec9b7daa9b619ad3005a"},"provenance":null,"requires-python":null,"size":791396,"upload-time":"2017-02-01T18:26:00.310591Z","url":"https://files.pythonhosted.org/packages/83/86/88c20e9ebb1565598401b23ece21367bd059d569cb0ce797509ebdc429be/psutil-5.1.0.win-amd64-py3.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.0.win32-py2.7.exe","hashes":{"sha256":"b014a8df14f00054762a2ef6f9a449ccde18d9c4de48dc8cb1809343f328f83d"},"provenance":null,"requires-python":null,"size":392388,"upload-time":"2017-02-01T18:26:13.878537Z","url":"https://files.pythonhosted.org/packages/a5/e8/1032177e5af2300b39aa50623e0df4d9a91a9b316803aab8f597e6c855e2/psutil-5.1.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.0.win32-py3.3.exe","hashes":{"sha256":"01e68d047f5023d4cb55b3a2653e296d86f53abc7e63e74c8abeefa627d9307f"},"provenance":null,"requires-python":null,"size":387232,"upload-time":"2017-02-01T18:26:25.910876Z","url":"https://files.pythonhosted.org/packages/cc/b8/24bf07e83d063f552132e7808d7029663466ebed945c532ba05068c65ae3/psutil-5.1.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.0.win32-py3.4.exe","hashes":{"sha256":"bfd279124512ee1b744eb7efa269be2096e0ce7075204daccd6f4b73c359316c"},"provenance":null,"requires-python":null,"size":387243,"upload-time":"2017-02-01T18:26:38.999172Z","url":"https://files.pythonhosted.org/packages/2a/ad/40d39c8911ca0783df9684649038241913fa3119c74030b9d1a02eb28da3/psutil-5.1.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.0.win32-py3.5.exe","hashes":{"sha256":"518356f5384a8996eb4056b6fd01a28bb502476e5d0ab9bd112d8e1dc9ce041a"},"provenance":null,"requires-python":null,"size":658437,"upload-time":"2017-02-01T18:26:58.991042Z","url":"https://files.pythonhosted.org/packages/db/b1/023fa6a995356c4a4ae68ba53e1269d693ad66783ee2c0c4ac1d6e556226/psutil-5.1.0.win32-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.0.win32-py3.6.exe","hashes":{"sha256":"23670901cfa4308cc6c442e08efcd93c6a2adb3bfdbbf51d4c120fc2a1595899"},"provenance":null,"requires-python":null,"size":658436,"upload-time":"2017-02-01T18:27:18.934060Z","url":"https://files.pythonhosted.org/packages/f8/ee/be644cdded3a5761f22cd0255534f04ad89a6fabdfb85f58100d25b6bf1a/psutil-5.1.0.win32-py3.6.exe","yanked":false},{"core-metadata":{"sha256":"b880ef4983d10a0a227170484a19bf585b91f8d2cf0238488b8a0e39337acdad"},"data-dist-info-metadata":{"sha256":"b880ef4983d10a0a227170484a19bf585b91f8d2cf0238488b8a0e39337acdad"},"filename":"psutil-5.1.1-cp27-none-win32.whl","hashes":{"sha256":"b9cc631c31794f8150a034a15448ecbde6c65ab078437eb01e0a71103bced297"},"provenance":null,"requires-python":null,"size":184444,"upload-time":"2017-02-03T12:00:02.925645Z","url":"https://files.pythonhosted.org/packages/e3/6f/3546783d70949aff150555cafb1cdd51cfddbb2fea042ed3d5b930232233/psutil-5.1.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"b880ef4983d10a0a227170484a19bf585b91f8d2cf0238488b8a0e39337acdad"},"data-dist-info-metadata":{"sha256":"b880ef4983d10a0a227170484a19bf585b91f8d2cf0238488b8a0e39337acdad"},"filename":"psutil-5.1.1-cp27-none-win_amd64.whl","hashes":{"sha256":"d723be81a8db76ad6d78b04effd33e73813567eb1b1b7669c6926da033cb9774"},"provenance":null,"requires-python":null,"size":186898,"upload-time":"2017-02-03T12:00:10.481992Z","url":"https://files.pythonhosted.org/packages/48/98/9f3277e1d4a6d6fbc501146b9f5e8547c793df038dfa7037816edbf90325/psutil-5.1.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"data-dist-info-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"filename":"psutil-5.1.1-cp33-cp33m-win32.whl","hashes":{"sha256":"5fc6d4fe04f014ea68392d1e7ab7103637b9dcbfd7bf3bc6d9d482177bf82777"},"provenance":null,"requires-python":null,"size":184356,"upload-time":"2017-02-03T12:00:18.015691Z","url":"https://files.pythonhosted.org/packages/98/fa/c43ce8992690d3b674259f21c897dab254b3fbbb2758254787e4b0f5aee8/psutil-5.1.1-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"data-dist-info-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"filename":"psutil-5.1.1-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"7d0c6f77ebeb248ee62383340a8bd5a9b067e64618c9056d701eefdccf27f9f4"},"provenance":null,"requires-python":null,"size":186735,"upload-time":"2017-02-03T12:00:25.248665Z","url":"https://files.pythonhosted.org/packages/e1/4f/7d16da2b82d615a7fc57f159e5e7c8776c7b794ca6330a9318ca93eb735b/psutil-5.1.1-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"data-dist-info-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"filename":"psutil-5.1.1-cp34-cp34m-win32.whl","hashes":{"sha256":"d31859dae480bc1a0be48f239bcf3caa26447fae177549a30c4b1a2a2776f299"},"provenance":null,"requires-python":null,"size":184362,"upload-time":"2017-02-03T12:00:31.866390Z","url":"https://files.pythonhosted.org/packages/bc/a8/3f3e69227217d1e7355e6de1980f357f6151ce1543bd520652b47823f93e/psutil-5.1.1-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"data-dist-info-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"filename":"psutil-5.1.1-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"857a3620b12a33ed4169aee959e1640e7323f18dd7726035cc057e1d39639df2"},"provenance":null,"requires-python":null,"size":186740,"upload-time":"2017-02-03T12:00:39.950524Z","url":"https://files.pythonhosted.org/packages/d3/2c/974ced45441b7adf476f1b32ac3f7840f0d7ae295dd4512de21997e2049b/psutil-5.1.1-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"data-dist-info-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"filename":"psutil-5.1.1-cp35-cp35m-win32.whl","hashes":{"sha256":"4ed11e8caff64c452a2eee8c1bea614b717a8e66e97fc88ce272d98a6499cb9a"},"provenance":null,"requires-python":null,"size":186257,"upload-time":"2017-02-03T12:00:46.799733Z","url":"https://files.pythonhosted.org/packages/2f/17/cb34ac2f50ff6499e6b64837a2d34b92696ff1dc14e57933363d94b7301c/psutil-5.1.1-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"data-dist-info-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"filename":"psutil-5.1.1-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"3115d7acbf3cc81345fd7252946a59c3730f7baba546361a535f0a8f00d862c9"},"provenance":null,"requires-python":null,"size":189715,"upload-time":"2017-02-03T12:00:55.165186Z","url":"https://files.pythonhosted.org/packages/09/da/f9f435d53711859d41ee8a0fb87177e8b30b5dbca5bd06dd05b5c4b46ef3/psutil-5.1.1-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"data-dist-info-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"filename":"psutil-5.1.1-cp36-cp36m-win32.whl","hashes":{"sha256":"9f6cb84d0f8e0c993c91d10ef86c637b7f1c1d4d4ca63ec0a73545cc13e9656a"},"provenance":null,"requires-python":null,"size":186256,"upload-time":"2017-02-03T12:01:01.728292Z","url":"https://files.pythonhosted.org/packages/24/19/20a4a33db5d005959458f68816fbb725791ee7843ba4f8a40dfd38e8e840/psutil-5.1.1-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"data-dist-info-metadata":{"sha256":"9de4440590e9d06325c73aa9a0f85d5e2beb77f570187d8aa12add76025de520"},"filename":"psutil-5.1.1-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"35311e26f7138276fa3e7af86bcb8ecbaee945c3549e690a481379075de386ba"},"provenance":null,"requires-python":null,"size":189711,"upload-time":"2017-02-03T12:01:08.476075Z","url":"https://files.pythonhosted.org/packages/6e/2b/44aea26dc3a304285e8eb91af628c9722ba4e1f44f7969cc24fded4aa744/psutil-5.1.1-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.1.tar.gz","hashes":{"sha256":"ece06401d719050a84cca97764ff5b0e41aafe6b6a2ec8a1d0bb89ca5e206d0f"},"provenance":null,"requires-python":null,"size":341006,"upload-time":"2017-02-03T12:01:19.347630Z","url":"https://files.pythonhosted.org/packages/49/ed/2a0b13f890e798b6f1f3625f0e87e5b712471d2c1c625bdcd396d36c56dc/psutil-5.1.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.1.win-amd64-py2.7.exe","hashes":{"sha256":"e75edc462005475da019f82c8a13b215e2e48db8f284d8be14308b611505d185"},"provenance":null,"requires-python":null,"size":423004,"upload-time":"2017-02-03T12:01:32.247678Z","url":"https://files.pythonhosted.org/packages/99/35/96acf109f463ce31cd29ec327d9b3b1c3eb13afbc5635e54c75e1e22c924/psutil-5.1.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.1.win-amd64-py3.3.exe","hashes":{"sha256":"e18274b77e61b3774bcf11a4aa368032fe3bbc21e4217ca903c77541fbe00eef"},"provenance":null,"requires-python":null,"size":421360,"upload-time":"2017-02-03T12:01:46.775449Z","url":"https://files.pythonhosted.org/packages/b7/56/70fc2173d34f0ee73215a0e88e2702933cf1549e3ff3d8f2d757b9e1351c/psutil-5.1.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.1.win-amd64-py3.4.exe","hashes":{"sha256":"76307f8ac94adc87509df43bba28706c27d6c5e4b7429fb658dd5905adae4dc3"},"provenance":null,"requires-python":null,"size":421366,"upload-time":"2017-02-03T12:02:01.021939Z","url":"https://files.pythonhosted.org/packages/f1/9b/961442950e039c65938665dcbe6e72bff1a2dbbd6c6c08c55e3e10db38ed/psutil-5.1.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.1.win-amd64-py3.5.exe","hashes":{"sha256":"8101a2b83fa3b93fbf5d5edca7169a4289f34ace2ee25d0f758cec5ae553190f"},"provenance":null,"requires-python":null,"size":791943,"upload-time":"2017-02-03T12:02:23.633303Z","url":"https://files.pythonhosted.org/packages/bb/69/95fdd45b2c1cc4735760b81d826a62433d6726ab6e8e6e2e982fb1264c20/psutil-5.1.1.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.1.win-amd64-py3.6.exe","hashes":{"sha256":"b16b48868a58322edd240cf55a0855e1b6fead3e5f02a41b73503d5c47acf330"},"provenance":null,"requires-python":null,"size":791938,"upload-time":"2017-02-03T12:02:48.386069Z","url":"https://files.pythonhosted.org/packages/15/b4/1ba8944aaae568f8f4acd7fd3ca46a95077f07b005774f212226c99a82d8/psutil-5.1.1.win-amd64-py3.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.1.win32-py2.7.exe","hashes":{"sha256":"12aeeaf5269bc75ed605ffc4979cb95c889989b40d307adf067f04f93f6d3365"},"provenance":null,"requires-python":null,"size":392908,"upload-time":"2017-02-03T12:03:01.259012Z","url":"https://files.pythonhosted.org/packages/d0/38/c274e67564aed7475fba4e0f9eaac3235797226df0b68abce7f3fc96bffa/psutil-5.1.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.1.win32-py3.3.exe","hashes":{"sha256":"2f51c669bd528982fc5397d9c84b8b389a56611c111159b1710570db33fc9750"},"provenance":null,"requires-python":null,"size":387752,"upload-time":"2017-02-03T12:03:13.897990Z","url":"https://files.pythonhosted.org/packages/d9/51/52e109c7884a6fa931882081e94dfd0e1b6adb054d4fc82943e1c9b691d2/psutil-5.1.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.1.win32-py3.4.exe","hashes":{"sha256":"f68ce5d80db909ee655ac8a322cda3abf47f41c134e7cf61b25565f116fce33f"},"provenance":null,"requires-python":null,"size":387758,"upload-time":"2017-02-03T12:03:26.769319Z","url":"https://files.pythonhosted.org/packages/5e/ee/ff6d626da64a799db055bab9f69ff9a50612a645781d14c24078bca418cb/psutil-5.1.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.1.win32-py3.5.exe","hashes":{"sha256":"380aa69aa529e4a4e35579d3e0320617e112473240c95b815d3b421c86e2ab6c"},"provenance":null,"requires-python":null,"size":658955,"upload-time":"2017-02-03T12:03:45.921701Z","url":"https://files.pythonhosted.org/packages/62/9b/847c9c6b1c053a15b44ccf809fce54bbd71e466fd8c1b8eecb0f8bbeb2ce/psutil-5.1.1.win32-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.1.win32-py3.6.exe","hashes":{"sha256":"bd3b881faa071a5f6f999d036cfc0d744eed223390fde05ae2a74f0f514f8bd0"},"provenance":null,"requires-python":null,"size":658953,"upload-time":"2017-02-03T12:04:04.770301Z","url":"https://files.pythonhosted.org/packages/80/ef/c23e653bbc1a523217d676fac3ac4050e9fe4d673c84680498c02ff89d23/psutil-5.1.1.win32-py3.6.exe","yanked":false},{"core-metadata":{"sha256":"40c22031c3c1f82a2758dbd44797eade645ac30207e45f42cb9da4fd1dd3808f"},"data-dist-info-metadata":{"sha256":"40c22031c3c1f82a2758dbd44797eade645ac30207e45f42cb9da4fd1dd3808f"},"filename":"psutil-5.1.2-cp27-none-win32.whl","hashes":{"sha256":"caa870244015bb547eeab7377d9fe41c44319fda6862aa56974108a224c04b1a"},"provenance":null,"requires-python":null,"size":185018,"upload-time":"2017-02-03T19:13:24.939112Z","url":"https://files.pythonhosted.org/packages/57/bc/e0a5e35a9b7f407e229db75b24c46bef012609450d7eac6b5e596a604acb/psutil-5.1.2-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"40c22031c3c1f82a2758dbd44797eade645ac30207e45f42cb9da4fd1dd3808f"},"data-dist-info-metadata":{"sha256":"40c22031c3c1f82a2758dbd44797eade645ac30207e45f42cb9da4fd1dd3808f"},"filename":"psutil-5.1.2-cp27-none-win_amd64.whl","hashes":{"sha256":"d60d978f5a9b4bc9bb22c5c4bbeb50043db6fb70e3270c08f51e81357f8ca556"},"provenance":null,"requires-python":null,"size":187473,"upload-time":"2017-02-03T19:13:32.487659Z","url":"https://files.pythonhosted.org/packages/84/bd/49681360ca11a1aeba5e48b80b6bd576695a67fe15baca144c6bfcbc9285/psutil-5.1.2-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"data-dist-info-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"filename":"psutil-5.1.2-cp33-cp33m-win32.whl","hashes":{"sha256":"61b9104157df03790fae3b138afb64ad3ecb663669ee3548e609306e0b35ca61"},"provenance":null,"requires-python":null,"size":184928,"upload-time":"2017-02-03T19:13:39.838419Z","url":"https://files.pythonhosted.org/packages/59/c7/7f31631d96ba58e81ad03b4f7cee234a35c67edc44b2fe5936e41e7d1e9e/psutil-5.1.2-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"data-dist-info-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"filename":"psutil-5.1.2-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"11bfc49cd680dec42c9a7200f8b0cc4d89a9d5dbad7f3b027cfac3a305343a2a"},"provenance":null,"requires-python":null,"size":187306,"upload-time":"2017-02-03T19:13:47.199966Z","url":"https://files.pythonhosted.org/packages/a3/dc/0c87a42dbff252ab68263f88e3bd8ffaa02d766e56f743cc9074bb28c0e1/psutil-5.1.2-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"data-dist-info-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"filename":"psutil-5.1.2-cp34-cp34m-win32.whl","hashes":{"sha256":"75b27bac9e91e9868723d8964e73a157799c26190978029b0ef294ad7727d91d"},"provenance":null,"requires-python":null,"size":184936,"upload-time":"2017-02-03T19:13:54.487932Z","url":"https://files.pythonhosted.org/packages/e6/2c/fa2d3770ea68f7f7577bfcf3fc00a2a68760e7d78b8ccb4a323fb44b27fa/psutil-5.1.2-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"data-dist-info-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"filename":"psutil-5.1.2-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"abcfbf43eb47372e961278ee7da9a4757d59999462225b358f49c8e69a393f32"},"provenance":null,"requires-python":null,"size":187315,"upload-time":"2017-02-03T19:14:02.683013Z","url":"https://files.pythonhosted.org/packages/ed/bf/cc8cdbfc7e10518e2e1141e0724623f52bc2f41e7bdd327f8ed63e0edc01/psutil-5.1.2-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"data-dist-info-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"filename":"psutil-5.1.2-cp35-cp35m-win32.whl","hashes":{"sha256":"3fd346acebeb84d9d351cabc02ea1bee1536fb7e165e7e5ead0e0912ce40cbb1"},"provenance":null,"requires-python":null,"size":186829,"upload-time":"2017-02-03T19:14:10.158362Z","url":"https://files.pythonhosted.org/packages/bf/60/e78e3455085393ab4902ef9b1c82e52d89f7b212582c58b76407a7078ad6/psutil-5.1.2-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"data-dist-info-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"filename":"psutil-5.1.2-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"69644ed20c08bd257039733c71a47d871f5bdd481d63f8408e28f03f491e2a03"},"provenance":null,"requires-python":null,"size":190288,"upload-time":"2017-02-03T19:14:17.352480Z","url":"https://files.pythonhosted.org/packages/55/0a/46eba44208248da5aaeadeeab57aac3ee172df8a7291ad5d62efbed566ee/psutil-5.1.2-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"data-dist-info-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"filename":"psutil-5.1.2-cp36-cp36m-win32.whl","hashes":{"sha256":"006ea083c66aa2a2be18bce84e35d0f3301d4ee3c869cb9d475fecce68470a71"},"provenance":null,"requires-python":null,"size":186826,"upload-time":"2017-02-03T19:14:24.235507Z","url":"https://files.pythonhosted.org/packages/26/e4/f5316795a709397e911eca478d547a3bb51b8dca447b7ed62d328cafa570/psutil-5.1.2-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"data-dist-info-metadata":{"sha256":"0f6bf0464a251593159b71879d6e05ca05190e53d209a32229b793365d8de056"},"filename":"psutil-5.1.2-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"b64fa3a1ec7e74689b093ee6b3a487979157f81915677b9145585a2babe1b9f5"},"provenance":null,"requires-python":null,"size":190282,"upload-time":"2017-02-03T19:14:31.621854Z","url":"https://files.pythonhosted.org/packages/c0/e2/2c758b825b48eb9ae30676499a862b8475efdf683952efbb63f531413489/psutil-5.1.2-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.2.tar.gz","hashes":{"sha256":"43f32b0a392c80cff0f480bd0792763333e46d7062285dd1226b70473c55e8ac"},"provenance":null,"requires-python":null,"size":341325,"upload-time":"2017-02-03T19:14:42.976980Z","url":"https://files.pythonhosted.org/packages/19/2c/41c601cdd5586f601663d6985ff2cf1c5322f1ffd32d67d3001035d9f81d/psutil-5.1.2.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.2.win-amd64-py2.7.exe","hashes":{"sha256":"a44f1735f8464b5cde862d76a78843869da02e1454278a38b1026c9cfa172daf"},"provenance":null,"requires-python":null,"size":423677,"upload-time":"2017-02-03T19:14:56.817144Z","url":"https://files.pythonhosted.org/packages/f2/0e/f26bd5b5e0293f715e18b1de0e46eb3378c8b2a2f54ce67bb48ee47dff44/psutil-5.1.2.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.2.win-amd64-py3.3.exe","hashes":{"sha256":"e8bb29ba0e1526de8932025e07c13f6e26ab43a4cc2861b849ab2daf83ef4c3a"},"provenance":null,"requires-python":null,"size":422033,"upload-time":"2017-02-03T19:15:12.278688Z","url":"https://files.pythonhosted.org/packages/82/d1/e86d5892bfeb5b8439a96b39b2acc892aa54b9df9feb75ef7384673fe883/psutil-5.1.2.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.2.win-amd64-py3.4.exe","hashes":{"sha256":"71cd26331eb0c26ba19d5acb67716741666a581f90bce35cc5cb733eb6bbb087"},"provenance":null,"requires-python":null,"size":422041,"upload-time":"2017-02-03T19:15:27.020838Z","url":"https://files.pythonhosted.org/packages/9f/75/f9232734ab6cdf3d656a4ef65fbe5440948c38db910002a4570b01e233d2/psutil-5.1.2.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.2.win-amd64-py3.5.exe","hashes":{"sha256":"e3ea19ac2c6e1d54cb3de5919040018af2b5a0d846f7f9dc0cc4e2a125725015"},"provenance":null,"requires-python":null,"size":792618,"upload-time":"2017-02-03T19:15:52.787383Z","url":"https://files.pythonhosted.org/packages/e9/3d/cb2444851956cc4c1fe62bc4b266881e07f16b4be3cdc7b5c1ead5d99b76/psutil-5.1.2.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.2.win-amd64-py3.6.exe","hashes":{"sha256":"18ad3e6d3b46dc56eda32674e9da77527bb4ac98503e45c89837fba641a2cc16"},"provenance":null,"requires-python":null,"size":792613,"upload-time":"2017-02-03T19:16:17.350579Z","url":"https://files.pythonhosted.org/packages/36/a5/803a7fdb45924ee69221259184bb2766a1a2819d91e806912f53c6dccc6d/psutil-5.1.2.win-amd64-py3.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.2.win32-py2.7.exe","hashes":{"sha256":"1f8e54923b5d80b880d0dbc2ec5bcf51cbf1db7d54e4d2acdeeb02f42a21735a"},"provenance":null,"requires-python":null,"size":393580,"upload-time":"2017-02-03T19:16:30.238524Z","url":"https://files.pythonhosted.org/packages/b6/89/191e13e1ba569fd143c66fe340e820b909fef8599f78237cc505dd59552b/psutil-5.1.2.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.2.win32-py3.3.exe","hashes":{"sha256":"99a77884670999cf6c589539f1af3af66d8f59d9b8a9697b60398434933f56a8"},"provenance":null,"requires-python":null,"size":388425,"upload-time":"2017-02-03T19:16:43.231178Z","url":"https://files.pythonhosted.org/packages/45/ba/764a926681d90302a54494d59665ea906676b974c2ae0af7b44d5d6d9f24/psutil-5.1.2.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.2.win32-py3.4.exe","hashes":{"sha256":"2a7874d2d2718a4648cfaa4ab731f7de4867230bdcd543bf1c1fda05bc34e068"},"provenance":null,"requires-python":null,"size":388433,"upload-time":"2017-02-03T19:16:58.697184Z","url":"https://files.pythonhosted.org/packages/eb/91/98584bf6f6934c5d02b116634d6e00eddb2ec6b057556d77fdda2fe72ab0/psutil-5.1.2.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.2.win32-py3.5.exe","hashes":{"sha256":"42cbf7db0ce76431676da30e792b80e1228857e50afe859518b999125b5da673"},"provenance":null,"requires-python":null,"size":659629,"upload-time":"2017-02-03T19:17:19.107384Z","url":"https://files.pythonhosted.org/packages/80/b9/b17a77e77be75640594fba23837490a1dc425c9d6a9fc0b4997f5bfce984/psutil-5.1.2.win32-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.2.win32-py3.6.exe","hashes":{"sha256":"cfd6bddd0db750b606454791432023a4260184204f4462b2a4206b7802546ead"},"provenance":null,"requires-python":null,"size":659626,"upload-time":"2017-02-03T19:17:39.500599Z","url":"https://files.pythonhosted.org/packages/8b/bd/af0e5d889818caa35fc8dc5157363c38bdb6fcb7fa1389c5daec24857a63/psutil-5.1.2.win32-py3.6.exe","yanked":false},{"core-metadata":{"sha256":"f3276ad95ad14e59ea78a4da9482d49f8096583e96beedc6b0261259c3d8e39e"},"data-dist-info-metadata":{"sha256":"f3276ad95ad14e59ea78a4da9482d49f8096583e96beedc6b0261259c3d8e39e"},"filename":"psutil-5.1.3-cp27-none-win32.whl","hashes":{"sha256":"359a66879068ce609f8c034b3c575e357a92c033357f398490fc77cf8af46bf7"},"provenance":null,"requires-python":null,"size":185714,"upload-time":"2017-02-07T21:33:17.748014Z","url":"https://files.pythonhosted.org/packages/6f/c0/82c15d73633fdee59b5bd064396038a63a8920359c86460cd01d6ddcedfc/psutil-5.1.3-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"f3276ad95ad14e59ea78a4da9482d49f8096583e96beedc6b0261259c3d8e39e"},"data-dist-info-metadata":{"sha256":"f3276ad95ad14e59ea78a4da9482d49f8096583e96beedc6b0261259c3d8e39e"},"filename":"psutil-5.1.3-cp27-none-win_amd64.whl","hashes":{"sha256":"a0becbbe09bed44f8f5dc3909c7eb383315f932faeb0029abe8d5c737e8dcc7e"},"provenance":null,"requires-python":null,"size":188166,"upload-time":"2017-02-07T21:33:23.089530Z","url":"https://files.pythonhosted.org/packages/80/92/c5136bbade8ba85d0aa2f5d5cbe8af80bac6ea1ef77c5445aa625a8caefb/psutil-5.1.3-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"data-dist-info-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"filename":"psutil-5.1.3-cp33-cp33m-win32.whl","hashes":{"sha256":"5b2cc379287ded7f9a22521318bf010429234c2864b4146fe518f11729821771"},"provenance":null,"requires-python":null,"size":185622,"upload-time":"2017-02-07T21:33:27.925426Z","url":"https://files.pythonhosted.org/packages/b2/fb/8ad6ff1a9169b35a001ea089ab8657156df1b9b9903e218f1839fba0aae3/psutil-5.1.3-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"data-dist-info-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"filename":"psutil-5.1.3-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"d0e88d2e8ac9ede745f589049a74ac1e3e614c4e5eed69e507d58bda8fa3c958"},"provenance":null,"requires-python":null,"size":188003,"upload-time":"2017-02-07T21:33:33.107156Z","url":"https://files.pythonhosted.org/packages/a7/8c/8a8bca010487f008ca026e69e76c53a025905a7bd1759567ec70d85d89a0/psutil-5.1.3-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"data-dist-info-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"filename":"psutil-5.1.3-cp34-cp34m-win32.whl","hashes":{"sha256":"72b67b988c0a42825a8ca76000fc385dde85652310278cca807db7dfbcba5e7e"},"provenance":null,"requires-python":null,"size":185627,"upload-time":"2017-02-07T21:33:37.199567Z","url":"https://files.pythonhosted.org/packages/81/69/0ea7d353a82df8d8251842c71131f24b8a5cfa6982f7b89e809c37819c0b/psutil-5.1.3-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"data-dist-info-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"filename":"psutil-5.1.3-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"474ab9a6abc05fcd7bb5c32cb828f3f9fc54a2cd349d63c94dff0af3b3ba7e64"},"provenance":null,"requires-python":null,"size":188011,"upload-time":"2017-02-07T21:33:41.528046Z","url":"https://files.pythonhosted.org/packages/93/82/2172e0a319e4c1387898b588e4dc9b8f2283fd9eda0a486ebe03ece2bff7/psutil-5.1.3-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"data-dist-info-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"filename":"psutil-5.1.3-cp35-cp35m-win32.whl","hashes":{"sha256":"92bfc1f1929593ab7793ddce512295336e3e788b86a1bbf32701aa67c5ce27f4"},"provenance":null,"requires-python":null,"size":187522,"upload-time":"2017-02-07T21:33:45.934959Z","url":"https://files.pythonhosted.org/packages/43/74/08fc07b34eeb8e357dbe6bca02b844cf76cef15b36098611a50720bf7786/psutil-5.1.3-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"data-dist-info-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"filename":"psutil-5.1.3-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"7be50561ed0060c86385c2ef4dd8a383298f29728eb6e30955ae2ebbd4554e1a"},"provenance":null,"requires-python":null,"size":190983,"upload-time":"2017-02-07T21:33:51.074513Z","url":"https://files.pythonhosted.org/packages/fa/42/42e547764bf65617077b696c4c49bed6109b00696882a196008de5f8917b/psutil-5.1.3-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"data-dist-info-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"filename":"psutil-5.1.3-cp36-cp36m-win32.whl","hashes":{"sha256":"4de5566d9d8c3695726f9ec3324cd56d3eb363365508ea39854d2ebe5d57b945"},"provenance":null,"requires-python":null,"size":187516,"upload-time":"2017-02-07T21:33:56.040728Z","url":"https://files.pythonhosted.org/packages/c5/5f/71b89c9dede1da356dbbe321ae410786b94e0b516791191c008864844733/psutil-5.1.3-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"data-dist-info-metadata":{"sha256":"ca962e552341e254e428a5583893983bb026161e7253c100c751493866519e08"},"filename":"psutil-5.1.3-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"678ef7b4e38281ff16dbdac98fc1d0679d46fed3fadd5d4648096fbb6d6b1b95"},"provenance":null,"requires-python":null,"size":190979,"upload-time":"2017-02-07T21:34:01.058372Z","url":"https://files.pythonhosted.org/packages/bc/21/1823e2349b1f6ec526a55d21497e5d627ac26a6c5a3d49a07b4afad45547/psutil-5.1.3-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.3.tar.gz","hashes":{"sha256":"959bd58bdc8152b0a143cb3bd822d4a1b8f7230617b0e3eb2ff6e63812120f2b"},"provenance":null,"requires-python":null,"size":341980,"upload-time":"2017-02-07T21:34:07.636785Z","url":"https://files.pythonhosted.org/packages/78/0a/aa90434c6337dd50d182a81fe4ae4822c953e166a163d1bf5f06abb1ac0b/psutil-5.1.3.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.3.win-amd64-py2.7.exe","hashes":{"sha256":"6c288b7a639f341391ba474d4c8fb495a19220015284b46e7b23f626afafc810"},"provenance":null,"requires-python":null,"size":424369,"upload-time":"2017-02-07T21:34:15.354723Z","url":"https://files.pythonhosted.org/packages/07/bb/aac12b9c56722cf8b6ed0c89eccf1e3db75795576b7e3575001248802c0d/psutil-5.1.3.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.3.win-amd64-py3.3.exe","hashes":{"sha256":"c4b53e0630b83c784f807170ae2d12f1cf1e45e3913f35f9784e5556ba4a0786"},"provenance":null,"requires-python":null,"size":422726,"upload-time":"2017-02-07T21:34:24.113585Z","url":"https://files.pythonhosted.org/packages/1e/5d/4804b1d23e0e8c442876c02438c4f89b3512806b034dea5807ca1ddd6535/psutil-5.1.3.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.3.win-amd64-py3.4.exe","hashes":{"sha256":"57a4e51a0f2fd8f361fcf545eeff54932a29b716ad01e60247d1abaffbc1b954"},"provenance":null,"requires-python":null,"size":422733,"upload-time":"2017-02-07T21:34:32.211085Z","url":"https://files.pythonhosted.org/packages/51/70/34ea430cf5c21540e30b805f0740d81f75c5766c2f68af9207ae18e147dc/psutil-5.1.3.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.3.win-amd64-py3.5.exe","hashes":{"sha256":"4b26f56f09ad206d9fb8b2fa29926a696419b26e2c5d461afe477481cec1105c"},"provenance":null,"requires-python":null,"size":793310,"upload-time":"2017-02-07T21:34:46.367507Z","url":"https://files.pythonhosted.org/packages/a2/8c/004fabd6406879fd1caa1ed4ea7ab850afad6e27e3c5b8e8a4d0d134de3e/psutil-5.1.3.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.3.win-amd64-py3.6.exe","hashes":{"sha256":"94ed102897b8c7103ff51e2b2953caf56bb80c3343523fd3013db3ec91bd8c4b"},"provenance":null,"requires-python":null,"size":793304,"upload-time":"2017-02-07T21:35:00.329688Z","url":"https://files.pythonhosted.org/packages/55/18/6a48a9b9dad56c54236260c1e0e2313497b3af176b053a110ea134b9bb9f/psutil-5.1.3.win-amd64-py3.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.3.win32-py2.7.exe","hashes":{"sha256":"9fcac25e01c0f9f1b6d86c860c6d4da627e458f277f24415f15b1b29cce35f60"},"provenance":null,"requires-python":null,"size":394272,"upload-time":"2017-02-07T21:35:07.791551Z","url":"https://files.pythonhosted.org/packages/33/97/442e6eefe2a12cd00d09721fb24ddf726dd62c1073579a860682919cc640/psutil-5.1.3.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.3.win32-py3.3.exe","hashes":{"sha256":"8349494ee9405a31f4f9d9d3564663c870fed5dd62efd2edfdf64c5841bb838f"},"provenance":null,"requires-python":null,"size":389117,"upload-time":"2017-02-07T21:35:16.679190Z","url":"https://files.pythonhosted.org/packages/ca/46/6d9a5c657298d1363cb37ab0f84eb1fd54639fa4b2729523a68cd6a1b043/psutil-5.1.3.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.3.win32-py3.4.exe","hashes":{"sha256":"c8dc71de8ba61604a5cae5dee5330229dc71538c82ef13458cee838b6c0f6435"},"provenance":null,"requires-python":null,"size":389124,"upload-time":"2017-02-07T21:35:24.590876Z","url":"https://files.pythonhosted.org/packages/5b/c4/7056b6c602ff5be0095fe403617cded940a75a80db49bb51846bc235a0bb/psutil-5.1.3.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.3.win32-py3.5.exe","hashes":{"sha256":"38c1e88f3a8a548d9caa7f56db1cc7d508eda48eb2c4aa484a908bc5d06f87bd"},"provenance":null,"requires-python":null,"size":660318,"upload-time":"2017-02-07T21:35:36.604103Z","url":"https://files.pythonhosted.org/packages/5f/b3/966c2979172a46f9fe42f34ce7321a59102054e26cdf9b26e3d604807953/psutil-5.1.3.win32-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.1.3.win32-py3.6.exe","hashes":{"sha256":"0961ebc2ba4b1c811ef164612d0d963532ad0a9af1755e022a99648a9027b065"},"provenance":null,"requires-python":null,"size":660315,"upload-time":"2017-02-07T21:35:48.440270Z","url":"https://files.pythonhosted.org/packages/be/43/8f0099425146c01c2d77e3ac90b28a7f42d69ccb2af6e161f059db132d99/psutil-5.1.3.win32-py3.6.exe","yanked":false},{"core-metadata":{"sha256":"1b02bec8364ab9ba69161e6031bf88699daaa9da96dcfa6527b452f562d8d646"},"data-dist-info-metadata":{"sha256":"1b02bec8364ab9ba69161e6031bf88699daaa9da96dcfa6527b452f562d8d646"},"filename":"psutil-5.2.0-cp27-none-win32.whl","hashes":{"sha256":"6eb2f6fb976152f320ee48a90ab732d694b2ae0c835260ce4f5af3907584448a"},"provenance":null,"requires-python":null,"size":187506,"upload-time":"2017-03-05T04:50:46.045903Z","url":"https://files.pythonhosted.org/packages/d2/56/56a15e285c7cf0104ed9fc569b2c75a24f97e7ab5c34567956b266d23ba3/psutil-5.2.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"1b02bec8364ab9ba69161e6031bf88699daaa9da96dcfa6527b452f562d8d646"},"data-dist-info-metadata":{"sha256":"1b02bec8364ab9ba69161e6031bf88699daaa9da96dcfa6527b452f562d8d646"},"filename":"psutil-5.2.0-cp27-none-win_amd64.whl","hashes":{"sha256":"35898b80a3f393a7ace8ad5da9a26800676b7fc40628a3a334902b9d0e444c8d"},"provenance":null,"requires-python":null,"size":189973,"upload-time":"2017-03-05T04:50:49.198757Z","url":"https://files.pythonhosted.org/packages/a5/a5/1039829542b856ca4d3d40bb4978fbb679b7f0bb684ece6340ce655aedc9/psutil-5.2.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"data-dist-info-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"filename":"psutil-5.2.0-cp33-cp33m-win32.whl","hashes":{"sha256":"5626533fc459ce1ac4bd017f7a38b99947c039d79175a10a2a6b6246e3a82fc8"},"provenance":null,"requires-python":null,"size":187442,"upload-time":"2017-03-05T04:50:52.517268Z","url":"https://files.pythonhosted.org/packages/8c/1e/7e6ac521b3c393b2f312f1c3795d702f3267dca23d603827d673b8170920/psutil-5.2.0-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"data-dist-info-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"filename":"psutil-5.2.0-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"d34cc4d48245873492e4befc5c58a146f0f6c98038ffa2430e191a6752717c61"},"provenance":null,"requires-python":null,"size":189849,"upload-time":"2017-03-05T04:50:55.188058Z","url":"https://files.pythonhosted.org/packages/b6/d0/6edd271e3ca150104c818ec0f4b2affc447fe79ec1504506cecb2900d391/psutil-5.2.0-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"data-dist-info-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"filename":"psutil-5.2.0-cp34-cp34m-win32.whl","hashes":{"sha256":"5834168071a92037736142616b33691ec4786f8806e28355e74b2e1a037cad4c"},"provenance":null,"requires-python":null,"size":187450,"upload-time":"2017-03-05T04:50:58.524346Z","url":"https://files.pythonhosted.org/packages/89/88/8fb4ce470a2022c33ab3cd16b3f2152f544e264c9db0f2f7159a93e0d2a3/psutil-5.2.0-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"data-dist-info-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"filename":"psutil-5.2.0-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"d29c24bc7c14ecb4e64b3b748814ebe0e3ac049802ea7f129edbfcb068e75c16"},"provenance":null,"requires-python":null,"size":189841,"upload-time":"2017-03-05T04:51:03.287652Z","url":"https://files.pythonhosted.org/packages/43/9b/35cae8c56d3ee2e9a02599fba6a2e1f3fcf3553fe55f70c0ea723f9a9522/psutil-5.2.0-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"data-dist-info-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"filename":"psutil-5.2.0-cp35-cp35m-win32.whl","hashes":{"sha256":"8353692da46bc6024b4001a9ed8849beb863fbb1d022553dd4ed8348745540bb"},"provenance":null,"requires-python":null,"size":189373,"upload-time":"2017-03-05T04:51:07.095988Z","url":"https://files.pythonhosted.org/packages/65/35/fff62f84dc6c165e8a9f7646e2c106bd223a3967a0a3f471979b38b5a5c0/psutil-5.2.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"data-dist-info-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"filename":"psutil-5.2.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"1e00f5684fb335dacfa750e5e01f83bb79d521eb5f0805b798de0a29a1fb25d4"},"provenance":null,"requires-python":null,"size":192813,"upload-time":"2017-03-05T04:51:10.595180Z","url":"https://files.pythonhosted.org/packages/94/d2/f78b5a0ded0993f4c5127bf17427e4bc10b183dc102a5e469d7f6725ecb9/psutil-5.2.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"data-dist-info-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"filename":"psutil-5.2.0-cp36-cp36m-win32.whl","hashes":{"sha256":"55d546333f1423ad219a0798867a9bbf9a90e1912c3336ad275476473624c071"},"provenance":null,"requires-python":null,"size":189376,"upload-time":"2017-03-05T04:51:15.088897Z","url":"https://files.pythonhosted.org/packages/91/8e/bd4f794b9f092d82a5b63b17da95ebd864f544ff62fb70bb1bce0687b013/psutil-5.2.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"data-dist-info-metadata":{"sha256":"49ff4351990c237dc58c60a958925eef7156ce86008b0f5cfef7af0e197da80e"},"filename":"psutil-5.2.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"548f14e3e21225884904e3ab228a769a73f886a3394399c591ec5f31fedc48ac"},"provenance":null,"requires-python":null,"size":192808,"upload-time":"2017-03-05T04:51:18.018177Z","url":"https://files.pythonhosted.org/packages/75/65/8499f256dc203b94f8a439f52b092742247668365dcb0997aee7349d530d/psutil-5.2.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.0.tar.gz","hashes":{"sha256":"2fc91d068faa5613c093335f0e758673ef8c722ad4bfa4aded64c13ae69089eb"},"provenance":null,"requires-python":null,"size":345519,"upload-time":"2017-03-05T04:51:23.230758Z","url":"https://files.pythonhosted.org/packages/3c/2f/f3ab91349c666f009077157b12057e613a3152a46a6c3be883777546b6de/psutil-5.2.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.0.win-amd64-py2.7.exe","hashes":{"sha256":"7cd5dd38e08d74112c68a8f4e9b9e12fac6c6f6270792604c79a9bbd574053fa"},"provenance":null,"requires-python":null,"size":426460,"upload-time":"2017-03-05T04:51:28.726871Z","url":"https://files.pythonhosted.org/packages/ca/6f/6289db524b6aae542fa36d539524e74f25d7f9296aabadb3b5a9f17746e8/psutil-5.2.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.0.win-amd64-py3.3.exe","hashes":{"sha256":"e5f1688d9bfd9e122edd35adcd8a0050430397094d08d95c380bd9c7dae48da3"},"provenance":null,"requires-python":null,"size":424854,"upload-time":"2017-03-05T04:51:34.189840Z","url":"https://files.pythonhosted.org/packages/93/7c/e92a80f5803be3febbcb40807d5d2bfe66dfe20b256c07616599c14ba2aa/psutil-5.2.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.0.win-amd64-py3.4.exe","hashes":{"sha256":"11e684bf163821bd73843ebf9a27b7cb6f8a8325b943954b49b1f622264b6e80"},"provenance":null,"requires-python":null,"size":424846,"upload-time":"2017-03-05T04:51:38.965015Z","url":"https://files.pythonhosted.org/packages/4a/82/5a78b9d40c17dc4d01a06345596ac1e3f7ba590f7329d83e80b817d47f9b/psutil-5.2.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.0.win-amd64-py3.5.exe","hashes":{"sha256":"9eeae9bef0875432b9aea9504ed4f7c72f8ee3e8d7ded63e484a453ee82d4a98"},"provenance":null,"requires-python":null,"size":793372,"upload-time":"2017-03-05T04:51:43.517651Z","url":"https://files.pythonhosted.org/packages/dd/f1/4bf05d2b34198954b5fd6455ebe06dd08cae5357354f6f142ef4321e41ab/psutil-5.2.0.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.0.win-amd64-py3.6.exe","hashes":{"sha256":"461445afc35f98d4b7781ea2f67d0ca4ae6cf36c065ee0f4be61ace59045a2a5"},"provenance":null,"requires-python":null,"size":795416,"upload-time":"2017-03-05T04:51:53.165366Z","url":"https://files.pythonhosted.org/packages/94/59/c54e7f853586561ae42c9bcf1aa0644e8c0298479e4654b4ca36fa9eafe6/psutil-5.2.0.win-amd64-py3.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.0.win32-py2.7.exe","hashes":{"sha256":"8bbbd02eb474045d201f6617d16dc8ee1d9903d5cec94f7f39cce610fc1e924b"},"provenance":null,"requires-python":null,"size":396349,"upload-time":"2017-03-05T04:51:59.453206Z","url":"https://files.pythonhosted.org/packages/21/79/40ea4e11ef6ca2b044d0aeb28d829a716a565b34c7786f779e99c005b80a/psutil-5.2.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.0.win32-py3.3.exe","hashes":{"sha256":"63d4320b0f3498da3551028a6ab9ee1c5aebabe0d23a7c38600c35333953ef6c"},"provenance":null,"requires-python":null,"size":391219,"upload-time":"2017-03-05T04:52:03.289639Z","url":"https://files.pythonhosted.org/packages/2e/9d/def6a3fb8150adfd71889f3e3d48160a1ba6210911baf12ce3ebd294307c/psutil-5.2.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.0.win32-py3.4.exe","hashes":{"sha256":"3c06b0162192db85e04846674a55915fca80f728cf626228a6b31684fc6930da"},"provenance":null,"requires-python":null,"size":391229,"upload-time":"2017-03-05T04:52:10.103932Z","url":"https://files.pythonhosted.org/packages/2c/ae/8616ac1eb00a7770d837b15ebb9ae759c43623c182f32fd43d2e6fed8649/psutil-5.2.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.0.win32-py3.5.exe","hashes":{"sha256":"0ba082468d6b45fb15cc1c4488aaf3ffcf0616a674c46393bf04eccc8d7c2196"},"provenance":null,"requires-python":null,"size":660405,"upload-time":"2017-03-05T04:52:18.130601Z","url":"https://files.pythonhosted.org/packages/f7/b6/c8cb94fd6696414a66021aa2229747d71612551eade262e9ab52eeb54ee2/psutil-5.2.0.win32-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.0.win32-py3.6.exe","hashes":{"sha256":"6ed1cb1c9339493e1f3c379de0155c543a4c8de18224bda894190843f9509cad"},"provenance":null,"requires-python":null,"size":662455,"upload-time":"2017-03-05T04:52:27.540851Z","url":"https://files.pythonhosted.org/packages/5d/9d/8b552e9d4c2a5c3baa00d1baa1468f2a8128acd3eba79ef39e59c182676a/psutil-5.2.0.win32-py3.6.exe","yanked":false},{"core-metadata":{"sha256":"84b8c9735984f276c69b5871ce5635af6381a960dd516077459ef1ded0e0580a"},"data-dist-info-metadata":{"sha256":"84b8c9735984f276c69b5871ce5635af6381a960dd516077459ef1ded0e0580a"},"filename":"psutil-5.2.1-cp27-none-win32.whl","hashes":{"sha256":"4e236c4ec6b0b20171c2477ded7a5b4402e4a877530640f814df839af0a40e30"},"provenance":null,"requires-python":null,"size":187855,"upload-time":"2017-03-24T15:42:20.835636Z","url":"https://files.pythonhosted.org/packages/3d/14/1242a70873873e92732dc35162317df448503a7a32e29c8bdbe30d4fa175/psutil-5.2.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"84b8c9735984f276c69b5871ce5635af6381a960dd516077459ef1ded0e0580a"},"data-dist-info-metadata":{"sha256":"84b8c9735984f276c69b5871ce5635af6381a960dd516077459ef1ded0e0580a"},"filename":"psutil-5.2.1-cp27-none-win_amd64.whl","hashes":{"sha256":"e88fe0d0ca5a9623f0d8d6be05a82e33984f27b067f08806bf8a548ba4361b40"},"provenance":null,"requires-python":null,"size":190301,"upload-time":"2017-03-24T15:42:26.623783Z","url":"https://files.pythonhosted.org/packages/4c/03/fffda9f6e1ca56ce989362969b709bf7a7ade16abf7d82661bbec96580f5/psutil-5.2.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"data-dist-info-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"filename":"psutil-5.2.1-cp33-cp33m-win32.whl","hashes":{"sha256":"54275bdbfbd20909d37ed7a2570cf9dd373ac702a89bac4814249cbc10503c03"},"provenance":null,"requires-python":null,"size":187792,"upload-time":"2017-03-24T15:42:30.952153Z","url":"https://files.pythonhosted.org/packages/d7/e0/4fde7667fad4271c06ed5e533a156bd600cdad1b69d8e6f278fe425452d2/psutil-5.2.1-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"data-dist-info-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"filename":"psutil-5.2.1-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"316c3e334b046dc12b4f0a3dafa1d1c394e38106ac519003694fc8aeb672eafd"},"provenance":null,"requires-python":null,"size":190190,"upload-time":"2017-03-24T15:42:35.956721Z","url":"https://files.pythonhosted.org/packages/a8/c5/63453c20ac576ccb58ee56f88388434380f5e2a729aa08885d2655eb83b7/psutil-5.2.1-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"data-dist-info-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"filename":"psutil-5.2.1-cp34-cp34m-win32.whl","hashes":{"sha256":"2249c687088145dcce87ecb90221258f9c0e7b7cea830886656cf07351e50e1b"},"provenance":null,"requires-python":null,"size":187785,"upload-time":"2017-03-24T15:42:40.746327Z","url":"https://files.pythonhosted.org/packages/59/8b/8ebb86ae5c0ba81e95bae8263de81038d3d7ee8a050f31b2b58f1a330198/psutil-5.2.1-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"data-dist-info-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"filename":"psutil-5.2.1-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"f74532c2037fac87b76737798c74102e17f8594ea9de07aa3cb19027a630bdb0"},"provenance":null,"requires-python":null,"size":190213,"upload-time":"2017-03-24T15:42:45.196850Z","url":"https://files.pythonhosted.org/packages/e7/81/c4dd47453864984d1bd5ad0c387efc11aa6791b5abb5b369ebe2e81f7ada/psutil-5.2.1-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"data-dist-info-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"filename":"psutil-5.2.1-cp35-cp35m-win32.whl","hashes":{"sha256":"c7c8ed864a9ef04d4736a998273e3ba0f95f22300f1e082c13a7c824b514f411"},"provenance":null,"requires-python":null,"size":189737,"upload-time":"2017-03-24T15:42:50.097781Z","url":"https://files.pythonhosted.org/packages/76/3b/2e6b3306dd2927fef9c81fdc29bc450beeb6f4bfe4cddec80260ab042900/psutil-5.2.1-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"data-dist-info-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"filename":"psutil-5.2.1-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"7a21b9d908a3bf381cc160c157a06bfcea3c6402362b26a2489566914cea9cc5"},"provenance":null,"requires-python":null,"size":193175,"upload-time":"2017-03-24T15:42:55.344665Z","url":"https://files.pythonhosted.org/packages/de/ee/cf9ecf7cea0a984a360bc889bb0bf11335755d5b7d2be9d8399fe5dc01fb/psutil-5.2.1-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"data-dist-info-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"filename":"psutil-5.2.1-cp36-cp36m-win32.whl","hashes":{"sha256":"d1efbfc743555e7fd366956d8fe39690a3ae87e8e9e9ac06cc80bd7e2ca3059b"},"provenance":null,"requires-python":null,"size":189738,"upload-time":"2017-03-24T15:43:00.448274Z","url":"https://files.pythonhosted.org/packages/15/18/e6b1b4288d885218c845f9a340e236f03352358fc83675b9b8ef96e26227/psutil-5.2.1-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"data-dist-info-metadata":{"sha256":"187f675b1b9bc62e2ffab1ba395e87ae25e1679d8867bd305462e360a04fd386"},"filename":"psutil-5.2.1-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"cf40e944f47000375320ce0e712585321ec624a0ef67e8259f522e51bcb35a35"},"provenance":null,"requires-python":null,"size":193175,"upload-time":"2017-03-24T15:43:06.043078Z","url":"https://files.pythonhosted.org/packages/e8/7c/240fd3dfcec8d839a9a48dd2f88ba5f6e687263adc8b2452ed973b66b862/psutil-5.2.1-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.1.tar.gz","hashes":{"sha256":"fe0ea53b302f68fca1c2a3bac289e11344456786141b73391ed4022b412d5455"},"provenance":null,"requires-python":null,"size":347241,"upload-time":"2017-03-24T15:43:12.551784Z","url":"https://files.pythonhosted.org/packages/b8/47/c85fbcd23f40892db6ecc88782beb6ee66d22008c2f9821d777cb1984240/psutil-5.2.1.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.1.win-amd64-py2.7.exe","hashes":{"sha256":"60e9bd558d640eaf9c7a4fbb0627b423b1e58fce95b41b8a24fda9145b753471"},"provenance":null,"requires-python":null,"size":426778,"upload-time":"2017-03-24T15:43:20.982480Z","url":"https://files.pythonhosted.org/packages/88/e8/40e20ea582157c81e55e1765139a5f6e969d8c01e47c016d90946b495531/psutil-5.2.1.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.1.win-amd64-py3.3.exe","hashes":{"sha256":"318cf7bf546a23564fe4f049eae0bf205895a0524120bd549de3e46599a7f265"},"provenance":null,"requires-python":null,"size":425186,"upload-time":"2017-03-24T15:43:29.554448Z","url":"https://files.pythonhosted.org/packages/dd/55/2a74e973eb217fa5006c910a24abbd720efb7720beae9659be14fe96a413/psutil-5.2.1.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.1.win-amd64-py3.4.exe","hashes":{"sha256":"3bcfbe8b8141c8145f1d54c3f9c2c86597508bb7cc2552e333de770a3c9b9368"},"provenance":null,"requires-python":null,"size":425211,"upload-time":"2017-03-24T15:43:39.017569Z","url":"https://files.pythonhosted.org/packages/9d/12/92575d652d33d28e6f8b0f858f3db326db5ffc4c8d55b09ac411b021d86d/psutil-5.2.1.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.1.win-amd64-py3.5.exe","hashes":{"sha256":"979a5804366b47acd0ebf28923ee645e9fc29f4c54cbc44c41d112a1cd36e9ba"},"provenance":null,"requires-python":null,"size":793725,"upload-time":"2017-03-24T15:43:54.421574Z","url":"https://files.pythonhosted.org/packages/7f/58/de0b10442e2f277de4de0ecfae277576a6bfac1a3137fe547a4085dafa32/psutil-5.2.1.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.1.win-amd64-py3.6.exe","hashes":{"sha256":"bf7d2cff21e3262d2b3e33a4b9dc27bbae81e851d694667d68dc7405c67ff31f"},"provenance":null,"requires-python":null,"size":795772,"upload-time":"2017-03-24T15:44:09.051326Z","url":"https://files.pythonhosted.org/packages/33/c0/7094de6644330b8dcdfefb0bae0a00379238588a6cf6cc9cd71c69e0cdce/psutil-5.2.1.win-amd64-py3.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.1.win32-py2.7.exe","hashes":{"sha256":"ad8b603d7cc6d070cf07d39276869683474dace4da51d8050f29893ac2e22baf"},"provenance":null,"requires-python":null,"size":396688,"upload-time":"2017-03-24T15:44:17.148742Z","url":"https://files.pythonhosted.org/packages/a9/7a/5d19102362c28b6a478f9a7f3262f3ca301f8c5fed12e8d0af9e9e82e6a2/psutil-5.2.1.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.1.win32-py3.3.exe","hashes":{"sha256":"71fbaa3649aa8fa92edb1ad2b45de1e9caa7ffc63f448be951d43d6b5c6263b1"},"provenance":null,"requires-python":null,"size":391560,"upload-time":"2017-03-24T15:44:25.069190Z","url":"https://files.pythonhosted.org/packages/0b/01/f2963d84b439b0802c2354d0f777b5ed4bd0c2c11161ba81e7057a0d0523/psutil-5.2.1.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.1.win32-py3.4.exe","hashes":{"sha256":"cab6e8cfab49511f34e7ae40885792d7e655bb107f6f3c89440d5061cb19ad2f"},"provenance":null,"requires-python":null,"size":391554,"upload-time":"2017-03-24T15:44:33.671369Z","url":"https://files.pythonhosted.org/packages/d5/46/b36ff70ba0ba3b92bb5088be595fdb5641ffd982bac8e206e7c4936b2dc5/psutil-5.2.1.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.1.win32-py3.5.exe","hashes":{"sha256":"0e9e3d74f6ee1a6cac503c0bba08563dc3954e723b8392a4c74ce36f46e119ea"},"provenance":null,"requires-python":null,"size":660759,"upload-time":"2017-03-24T15:44:45.335659Z","url":"https://files.pythonhosted.org/packages/62/81/e7431ad75f9d9ae1524ee886c1aff25ec3714058de6568d305de2e0c8373/psutil-5.2.1.win32-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.1.win32-py3.6.exe","hashes":{"sha256":"03e419618c3c715489ca5073cbdac6a0b12da41def69d3e4ee83f18fbb5798e5"},"provenance":null,"requires-python":null,"size":662807,"upload-time":"2017-03-24T15:44:56.446939Z","url":"https://files.pythonhosted.org/packages/77/c8/e256a28a63d06fe028f8837b860b7f6440c6ef9a475fb8c4490e1e08498b/psutil-5.2.1.win32-py3.6.exe","yanked":false},{"core-metadata":{"sha256":"0414f56fbdc05bdf6b0f2659c3f90672e666ae73ba46b3281e07291af1d58219"},"data-dist-info-metadata":{"sha256":"0414f56fbdc05bdf6b0f2659c3f90672e666ae73ba46b3281e07291af1d58219"},"filename":"psutil-5.2.2-cp27-none-win32.whl","hashes":{"sha256":"db473f0d45a56d422502043f3755385fcfd83f5bb0947bc807fcad689230f37f"},"provenance":null,"requires-python":null,"size":187988,"upload-time":"2017-04-10T17:19:35.132698Z","url":"https://files.pythonhosted.org/packages/9c/31/c651e4c475a4d0df9609024a86fcb358a21b7a01872f7c69c7cf501a2896/psutil-5.2.2-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"0414f56fbdc05bdf6b0f2659c3f90672e666ae73ba46b3281e07291af1d58219"},"data-dist-info-metadata":{"sha256":"0414f56fbdc05bdf6b0f2659c3f90672e666ae73ba46b3281e07291af1d58219"},"filename":"psutil-5.2.2-cp27-none-win_amd64.whl","hashes":{"sha256":"dcd9d3131f83480648da40d2c39403657c63a81e56e4e8d8e905bf65c133d59c"},"provenance":null,"requires-python":null,"size":190432,"upload-time":"2017-04-10T17:19:39.867178Z","url":"https://files.pythonhosted.org/packages/6e/5c/15f41041a321ffd4058ae67aade067924489acba6d277e10571b59b3127c/psutil-5.2.2-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"data-dist-info-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"filename":"psutil-5.2.2-cp33-cp33m-win32.whl","hashes":{"sha256":"3f79a044db0aae96592ef42be459e37095d0c2cebcae4fd7baf486d37a85a8cd"},"provenance":null,"requires-python":null,"size":187924,"upload-time":"2017-04-10T17:19:44.464637Z","url":"https://files.pythonhosted.org/packages/04/a5/a027a8584208fa2cb6a88e6337b06b11388edf7d39feb0a897c9c2024639/psutil-5.2.2-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"data-dist-info-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"filename":"psutil-5.2.2-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"838c66c123cb024bf8c8d2fec902b38c51f75b27988f4487d81383d1d3d8a8ce"},"provenance":null,"requires-python":null,"size":190325,"upload-time":"2017-04-10T17:19:49.563017Z","url":"https://files.pythonhosted.org/packages/bc/95/385e0f7e0299295401d41dd4cb6e568bf50c884af336b92a69d16981f71c/psutil-5.2.2-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"data-dist-info-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"filename":"psutil-5.2.2-cp34-cp34m-win32.whl","hashes":{"sha256":"a155875d2fedb614c2cd687fe47953d03a47f76eb39bd5756931b288b685655f"},"provenance":null,"requires-python":null,"size":187918,"upload-time":"2017-04-10T17:19:54.252736Z","url":"https://files.pythonhosted.org/packages/12/ad/aca0f4f146b25fb2b7e9e0735287ba3ebcc02eb2bf84d49916aef730d860/psutil-5.2.2-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"data-dist-info-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"filename":"psutil-5.2.2-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"a989876ac0cc7942ef9481b96d3bfc02777dc798d4a7a1b4e8f0f284228f3434"},"provenance":null,"requires-python":null,"size":190345,"upload-time":"2017-04-10T17:19:59.043821Z","url":"https://files.pythonhosted.org/packages/f4/45/6cbf2b7a55375f6aafc68f33581aa143f86ae1be9112546f04d8e9ee34da/psutil-5.2.2-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"data-dist-info-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"filename":"psutil-5.2.2-cp35-cp35m-win32.whl","hashes":{"sha256":"32616c5736f1de446e77865305e7f56905c718991f820c8286436adea8192f32"},"provenance":null,"requires-python":null,"size":189869,"upload-time":"2017-04-10T17:20:03.832890Z","url":"https://files.pythonhosted.org/packages/7b/1d/8cef4ee6c1a49b1204dcdca1231ac773e27f2ed0abbbf42deb14aaf2b5cc/psutil-5.2.2-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"data-dist-info-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"filename":"psutil-5.2.2-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"50c8ddc3a6d1cda1de6d7aaf1af10896832c6d686fc7d0fe3d01c1eb51e6f521"},"provenance":null,"requires-python":null,"size":193304,"upload-time":"2017-04-10T17:20:09.444679Z","url":"https://files.pythonhosted.org/packages/82/f4/9d4cb35c5e1c84f93718d1851adf0b4147b253111cb89f1996a08d14dba5/psutil-5.2.2-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"data-dist-info-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"filename":"psutil-5.2.2-cp36-cp36m-win32.whl","hashes":{"sha256":"e8b65a80e978af9bf10be423442155032c589b7042b4a26edc410dc36819d65e"},"provenance":null,"requires-python":null,"size":189868,"upload-time":"2017-04-10T17:20:16.876191Z","url":"https://files.pythonhosted.org/packages/72/9f/5ff6e45db392bc9dad642dffcca44eeee552289595c087a4f1d245fdb4f9/psutil-5.2.2-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"data-dist-info-metadata":{"sha256":"4c73be3d0927068fd96000b5591a3206eb03f1decaf76929c5e5cd06d004e214"},"filename":"psutil-5.2.2-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"7a5c0973bd4c1de98d9b225bd4303a0718d31e31d6e2342e825c3e656f7056df"},"provenance":null,"requires-python":null,"size":193305,"upload-time":"2017-04-10T17:20:21.983057Z","url":"https://files.pythonhosted.org/packages/eb/c6/29c695be774c52cca9bb68b94ae4dc866a42ddf29dcd19b7ab4c0d97bdda/psutil-5.2.2-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.2.tar.gz","hashes":{"sha256":"44746540c0fab5b95401520d29eb9ffe84b3b4a235bd1d1971cbe36e1f38dd13"},"provenance":null,"requires-python":null,"size":348413,"upload-time":"2017-04-10T17:20:29.132011Z","url":"https://files.pythonhosted.org/packages/57/93/47a2e3befaf194ccc3d05ffbcba2cdcdd22a231100ef7e4cf63f085c900b/psutil-5.2.2.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.2.win-amd64-py2.7.exe","hashes":{"sha256":"70732850abd11f4d9fa46f0e110af21030e0a6088204f332d335921b36e66305"},"provenance":null,"requires-python":null,"size":427159,"upload-time":"2017-04-10T17:20:36.896101Z","url":"https://files.pythonhosted.org/packages/56/bb/d03fa2260839abbcdd4d323e83c2e91ffaedcb1975b88d5d0bc71c95c1fb/psutil-5.2.2.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.2.win-amd64-py3.3.exe","hashes":{"sha256":"5d2f076788d71d2e1c7276f1e5a1bc255f29c2e80eb8879a9ffc633c5bf69481"},"provenance":null,"requires-python":null,"size":425568,"upload-time":"2017-04-10T17:20:45.676662Z","url":"https://files.pythonhosted.org/packages/79/fd/3d2626e6a9fd4d99859fd8eac7d52ff9850d5e4ea62611a1e3ffe6f3d257/psutil-5.2.2.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.2.win-amd64-py3.4.exe","hashes":{"sha256":"fecda42b274dc618278bd9139e8493c9459d2174376f82b65ba929557f10e880"},"provenance":null,"requires-python":null,"size":425592,"upload-time":"2017-04-10T17:20:54.334109Z","url":"https://files.pythonhosted.org/packages/24/56/637ef0dfac83cd3e51096436faf7ea030f780aff3da98a79b7e7ac98a8bb/psutil-5.2.2.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.2.win-amd64-py3.5.exe","hashes":{"sha256":"92e3500dfaf7a5502ebaf4a7472e2afb9ff0cb36b4e5dc1977b3c774f58332db"},"provenance":null,"requires-python":null,"size":794103,"upload-time":"2017-04-10T17:21:08.107023Z","url":"https://files.pythonhosted.org/packages/09/4d/9cf34797696c0a75fd76606c362ddfbbc0f87d2c19c95ae26e61120241ad/psutil-5.2.2.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.2.win-amd64-py3.6.exe","hashes":{"sha256":"ed09521d49ee177f1205ed9791ad62263feacd2fe1cc20d1d33cf37923f240ea"},"provenance":null,"requires-python":null,"size":796151,"upload-time":"2017-04-10T17:21:22.493104Z","url":"https://files.pythonhosted.org/packages/04/74/5ea4412f31c9652335c03537ff4608ff6a12895fb3a34a187578d693b865/psutil-5.2.2.win-amd64-py3.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.2.win32-py2.7.exe","hashes":{"sha256":"147093b75b8874e55e6b26c540544d40e98845bc4ee74dc6054c881fd2a3eed9"},"provenance":null,"requires-python":null,"size":397069,"upload-time":"2017-04-10T17:21:30.834225Z","url":"https://files.pythonhosted.org/packages/b9/aa/b779310ee8a120b5bb90880d22e7b3869f98f7a30381a71188c6fb7ff4a6/psutil-5.2.2.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.2.win32-py3.3.exe","hashes":{"sha256":"3d3c5c117e55c486a53ef796cc715035bf4f56419cc32dbd124fe26e9289ad1e"},"provenance":null,"requires-python":null,"size":391941,"upload-time":"2017-04-10T17:21:38.680258Z","url":"https://files.pythonhosted.org/packages/e0/3f/fd40d4edbfac1d7996b6ecb3bfa5f923662e21686727eb070490ab6dcca8/psutil-5.2.2.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.2.win32-py3.4.exe","hashes":{"sha256":"0c74c6a494b650966b88da256cab4e507f483c53e85b9b10d3ff9c38f059330b"},"provenance":null,"requires-python":null,"size":391936,"upload-time":"2017-04-10T17:21:46.576288Z","url":"https://files.pythonhosted.org/packages/c7/59/f7aa53e3f72d6dcfce7c60d80e74853a1444a8a4d7fef788441435c645bf/psutil-5.2.2.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.2.win32-py3.5.exe","hashes":{"sha256":"b5583d1c2c858056d39bd148ed25839c4f1b76fec8fb2cb9b564c82997a21266"},"provenance":null,"requires-python":null,"size":661141,"upload-time":"2017-04-10T17:21:57.551743Z","url":"https://files.pythonhosted.org/packages/f2/62/0fc0c5459bcc04171ea3bf5603622db6815ddd92a472db06bcd69612177d/psutil-5.2.2.win32-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.2.2.win32-py3.6.exe","hashes":{"sha256":"1da0aa70d66612588d77daed7784e623aac1fd038681c3acd0e1c76b2b2f0819"},"provenance":null,"requires-python":null,"size":663188,"upload-time":"2017-04-10T17:22:09.360293Z","url":"https://files.pythonhosted.org/packages/a0/7f/494b600e45a8a25a76658a45747f232d38538d1c177f5b80123902d2b8da/psutil-5.2.2.win32-py3.6.exe","yanked":false},{"core-metadata":{"sha256":"d331b301ac1684996cf5629df9922a8709a7bf9243a73151f9aa72cf620ac815"},"data-dist-info-metadata":{"sha256":"d331b301ac1684996cf5629df9922a8709a7bf9243a73151f9aa72cf620ac815"},"filename":"psutil-5.3.0-cp27-none-win32.whl","hashes":{"sha256":"6f8f858cdb79397509ee067ae9d25bee8f4b4902453ac8d155fa1629f03aa39d"},"provenance":null,"requires-python":null,"size":210071,"upload-time":"2017-09-01T10:50:37.295614Z","url":"https://files.pythonhosted.org/packages/db/36/71afb537f3718a00a9e63b75e8534bf14fee38b67fb7cb72eb60a3378162/psutil-5.3.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"d331b301ac1684996cf5629df9922a8709a7bf9243a73151f9aa72cf620ac815"},"data-dist-info-metadata":{"sha256":"d331b301ac1684996cf5629df9922a8709a7bf9243a73151f9aa72cf620ac815"},"filename":"psutil-5.3.0-cp27-none-win_amd64.whl","hashes":{"sha256":"b31d6d19e445b56559abaa21703a6bc4b162aaf9ab99867b6f2bbbdb2c7fce66"},"provenance":null,"requires-python":null,"size":212901,"upload-time":"2017-09-01T10:50:41.892260Z","url":"https://files.pythonhosted.org/packages/c8/2b/9bc89bb0d8a2ac49ab29954017e65a9c28b83b13453418c8166938281458/psutil-5.3.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"47a95c843a21d16ad147a795cf6dc0395740dd91d643d06e100a63f2bb9f4d08"},"data-dist-info-metadata":{"sha256":"47a95c843a21d16ad147a795cf6dc0395740dd91d643d06e100a63f2bb9f4d08"},"filename":"psutil-5.3.0-cp33-cp33m-win32.whl","hashes":{"sha256":"7f1ba5011095e39b3f543e9c87008409dd8a57a3e48ea1022c348244b5af77bf"},"provenance":null,"requires-python":null,"size":209934,"upload-time":"2017-09-01T10:50:46.901965Z","url":"https://files.pythonhosted.org/packages/3c/5b/b020c5f5b6fbe69bfe77072a6d35a6c6d68f7ec7fe8b148223d9365bf8b4/psutil-5.3.0-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"47a95c843a21d16ad147a795cf6dc0395740dd91d643d06e100a63f2bb9f4d08"},"data-dist-info-metadata":{"sha256":"47a95c843a21d16ad147a795cf6dc0395740dd91d643d06e100a63f2bb9f4d08"},"filename":"psutil-5.3.0-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"853f68a85cec0137acf0504d8ca6d40d899e48ecbe931130f593a072a35b812e"},"provenance":null,"requires-python":null,"size":212722,"upload-time":"2017-09-01T10:50:51.125180Z","url":"https://files.pythonhosted.org/packages/ad/f8/4d7f713241f1786097faf48afb51e6cb9f7966d2fc36656098e182056704/psutil-5.3.0-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"data-dist-info-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"filename":"psutil-5.3.0-cp34-cp34m-win32.whl","hashes":{"sha256":"01d9cb9473eee0e7e88319f9a5205a69e6e160b3ab2bd430a05b93bfae1528c2"},"provenance":null,"requires-python":null,"size":209879,"upload-time":"2017-09-01T10:50:55.651560Z","url":"https://files.pythonhosted.org/packages/d6/77/01a752e6d05061decf570acc800cd490766ea4534eccbe8c523b84fe5cc1/psutil-5.3.0-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"data-dist-info-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"filename":"psutil-5.3.0-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"91d37262095c1a0f97a78f5034e10e0108e3fa326c85baa17f8cdd63fa5f81b9"},"provenance":null,"requires-python":null,"size":212614,"upload-time":"2017-09-01T10:50:59.871475Z","url":"https://files.pythonhosted.org/packages/0f/cf/fd1d752d428c5845fed4904e7bcdbb89ea3327aa063247fddcdca319c615/psutil-5.3.0-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"data-dist-info-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"filename":"psutil-5.3.0-cp35-cp35m-win32.whl","hashes":{"sha256":"bd1776dc14b197388d728db72c103c0ebec834690ef1ce138035abf0123e2268"},"provenance":null,"requires-python":null,"size":212062,"upload-time":"2017-09-01T10:51:04.687879Z","url":"https://files.pythonhosted.org/packages/68/05/6d097706fd9cb43eda36a02a5feaee085aeac21f5bc6ea0b557109bc2eca/psutil-5.3.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"data-dist-info-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"filename":"psutil-5.3.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"7fadb1b1357ef58821b3f1fc2afb6e1601609b0daa3b55c2fabf765e0ea98901"},"provenance":null,"requires-python":null,"size":215734,"upload-time":"2017-09-01T10:51:09.110127Z","url":"https://files.pythonhosted.org/packages/6e/5a/7c688b472ff5b6cb5413acfa5a178b5e8140ffbdf0011b6d0469e97af3b1/psutil-5.3.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"data-dist-info-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"filename":"psutil-5.3.0-cp36-cp36m-win32.whl","hashes":{"sha256":"d5f4634a19e7d4692f37d8d67f8418f85f2bc1e2129914ec0e4208bf7838bf63"},"provenance":null,"requires-python":null,"size":212060,"upload-time":"2017-09-01T10:51:14.595884Z","url":"https://files.pythonhosted.org/packages/28/30/4ab277d7e37cd5ee1c47a89d21465c3eec3435b973ca86cd986efdd0aeac/psutil-5.3.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"data-dist-info-metadata":{"sha256":"70af37a6bd4674f8be60cec24e58ebdb4493432961ecc67d471a1739f1bf5d12"},"filename":"psutil-5.3.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"31505ee459913ef63fa4c1c0d9a11a4da60b5c5ec6a92d6d7f5d12b9653fc61b"},"provenance":null,"requires-python":null,"size":215731,"upload-time":"2017-09-01T10:51:18.736362Z","url":"https://files.pythonhosted.org/packages/c3/e6/98fd6259d8ed834d9a81567d4f41dd3645e12a9aea9d38563efaf245610a/psutil-5.3.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.0.tar.gz","hashes":{"sha256":"a3940e06e92c84ab6e82b95dad056241beea93c3c9b1d07ddf96485079855185"},"provenance":null,"requires-python":null,"size":397265,"upload-time":"2017-09-01T12:31:14.428985Z","url":"https://files.pythonhosted.org/packages/1c/da/555e3ad3cad30f30bcf0d539cdeae5c8e7ef9e2a6078af645c70aa81e418/psutil-5.3.0.tar.gz","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.0.win-amd64-py2.7.exe","hashes":{"sha256":"ba94f021942d6cc27e18dcdccd2c1a0976f0596765ef412316ecb887d4fd3db2"},"provenance":null,"requires-python":null,"size":450426,"upload-time":"2017-09-01T10:51:26.068723Z","url":"https://files.pythonhosted.org/packages/e3/d6/238a22e898d0a3703d5fd486487108bf57d8fab1137bf085d3602d04894a/psutil-5.3.0.win-amd64-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.0.win-amd64-py3.3.exe","hashes":{"sha256":"0f2fccf98bc25e8d6d61e24b2cc6350b8dfe8fa7f5251c817e977d8c61146e5d"},"provenance":null,"requires-python":null,"size":448781,"upload-time":"2017-09-01T10:51:33.193333Z","url":"https://files.pythonhosted.org/packages/91/d6/4ccff87a9f93f4837c97c3eb105c4cd1ccd544091658ba1938feca366b59/psutil-5.3.0.win-amd64-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.0.win-amd64-py3.4.exe","hashes":{"sha256":"d06f02c53260d16fb445e426410263b2d271cea19136b1bb715cf10b76960359"},"provenance":null,"requires-python":null,"size":448549,"upload-time":"2017-09-01T10:51:40.339200Z","url":"https://files.pythonhosted.org/packages/74/29/91f18e7798a150c922c7da5c2153edecd7af0292332ef4f46e8ebd7184d3/psutil-5.3.0.win-amd64-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.0.win-amd64-py3.5.exe","hashes":{"sha256":"724439fb20d083c943a2c62db1aa240fa15fe23644c4d4a1e9f573ffaf0bbddd"},"provenance":null,"requires-python":null,"size":817222,"upload-time":"2017-09-01T10:51:52.173626Z","url":"https://files.pythonhosted.org/packages/41/fe/5081186ce35c0def7db2f8531ccac908b83edf735c2b8b78241633853b34/psutil-5.3.0.win-amd64-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.0.win-amd64-py3.6.exe","hashes":{"sha256":"a58708f3f6f74897450babb012cd8067f8911e7c8a1f2991643ec9937a8f6c15"},"provenance":null,"requires-python":null,"size":817218,"upload-time":"2017-09-01T10:52:03.695382Z","url":"https://files.pythonhosted.org/packages/aa/49/2076044f5f8554232eb5f8fb69b4a59c8f96da6d107d6f6a35aea2fb0344/psutil-5.3.0.win-amd64-py3.6.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.0.win32-py2.7.exe","hashes":{"sha256":"108dae5ecb68f6e6212bf0553be055a2a0eec210227d8e14c3a26368b118624a"},"provenance":null,"requires-python":null,"size":419952,"upload-time":"2017-09-01T10:52:12.411324Z","url":"https://files.pythonhosted.org/packages/d1/18/7e1a418aff3f65c3072eb765b0e51445df6309be3da25c5a4cb9f1d1d18b/psutil-5.3.0.win32-py2.7.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.0.win32-py3.3.exe","hashes":{"sha256":"9832124af1e9ec0f298f17ab11c3bb91164f8068ec9429c39a7f7a0eae637a94"},"provenance":null,"requires-python":null,"size":414764,"upload-time":"2017-09-01T10:52:32.286415Z","url":"https://files.pythonhosted.org/packages/de/46/0ec33721564e235fab9112fecb836eb084d0475ab97d5a6d5c462656e715/psutil-5.3.0.win32-py3.3.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.0.win32-py3.4.exe","hashes":{"sha256":"7b8d10e7d72862d1e97caba546b60ce263b3fcecd6176e4c94efebef87ee68d3"},"provenance":null,"requires-python":null,"size":414582,"upload-time":"2017-09-01T10:52:41.243220Z","url":"https://files.pythonhosted.org/packages/86/82/254439b29eea5670633a947ec3f67c5ec33b88322a1a443f121d21dc2714/psutil-5.3.0.win32-py3.4.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.0.win32-py3.5.exe","hashes":{"sha256":"ed1f7cbbbf778a6ed98e25d48fdbdc098e66b360427661712610d72c1b4cf5f5"},"provenance":null,"requires-python":null,"size":684021,"upload-time":"2017-09-01T10:52:51.603711Z","url":"https://files.pythonhosted.org/packages/20/9c/2d84e2926e1a89c4d1ea8fc315e05145e6b10af79459727ebb688b22dab8/psutil-5.3.0.win32-py3.5.exe","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.0.win32-py3.6.exe","hashes":{"sha256":"3d8d62f3da0b38dbfaf4756a32e18c866530b9066c298da3fc293cfefae22f0a"},"provenance":null,"requires-python":null,"size":684018,"upload-time":"2017-09-01T10:53:02.878374Z","url":"https://files.pythonhosted.org/packages/61/c7/bbcc29ba03d59f1add008245edf0d56d45434b137dde0c0d6b8441ae3be6/psutil-5.3.0.win32-py3.6.exe","yanked":false},{"core-metadata":{"sha256":"efaa364a2342964849b527ba97bfad9c165b3524ee431b8e5ff9d3b6bd3b4047"},"data-dist-info-metadata":{"sha256":"efaa364a2342964849b527ba97bfad9c165b3524ee431b8e5ff9d3b6bd3b4047"},"filename":"psutil-5.3.1-cp27-none-win32.whl","hashes":{"sha256":"7a669b1897b8cdce1cea79defdf3a10fd6e4f0a8e42ac2a971dfe74bc1ce5679"},"provenance":null,"requires-python":null,"size":210168,"upload-time":"2017-09-10T05:26:42.273181Z","url":"https://files.pythonhosted.org/packages/34/90/145ff234428b4bd519c20d460d6d51db7820e8120879ca9cdc88602c57f4/psutil-5.3.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"efaa364a2342964849b527ba97bfad9c165b3524ee431b8e5ff9d3b6bd3b4047"},"data-dist-info-metadata":{"sha256":"efaa364a2342964849b527ba97bfad9c165b3524ee431b8e5ff9d3b6bd3b4047"},"filename":"psutil-5.3.1-cp27-none-win_amd64.whl","hashes":{"sha256":"57be53c045f2085e28d5371eedfce804f5e49e7b35fa79bcf63e271046058002"},"provenance":null,"requires-python":null,"size":212998,"upload-time":"2017-09-10T05:27:13.646528Z","url":"https://files.pythonhosted.org/packages/c8/c9/d8cbfc3844e1a3e8b648fcca317ad8589283a7cbbc232c2c5d29cae88352/psutil-5.3.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"411e80cfca140c76586e1f0d89c1c6d7b1ff79f31751e6780aee2632e60c3996"},"data-dist-info-metadata":{"sha256":"411e80cfca140c76586e1f0d89c1c6d7b1ff79f31751e6780aee2632e60c3996"},"filename":"psutil-5.3.1-cp33-cp33m-win32.whl","hashes":{"sha256":"27d4c5ff3ab97389a9372d246e1aa27e5f02e4709fede48a0599f89d2873ca88"},"provenance":null,"requires-python":null,"size":210025,"upload-time":"2017-09-10T05:27:19.363106Z","url":"https://files.pythonhosted.org/packages/c4/82/7e412884fcf9ae538b1d96e31688b804377803e34f4e3e86bba869eaa9f7/psutil-5.3.1-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"411e80cfca140c76586e1f0d89c1c6d7b1ff79f31751e6780aee2632e60c3996"},"data-dist-info-metadata":{"sha256":"411e80cfca140c76586e1f0d89c1c6d7b1ff79f31751e6780aee2632e60c3996"},"filename":"psutil-5.3.1-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"72ba7e4c82879b3781ccced1eeb901f07725a36fab66270e7555e484a460760d"},"provenance":null,"requires-python":null,"size":212814,"upload-time":"2017-09-10T05:27:24.067035Z","url":"https://files.pythonhosted.org/packages/8a/1b/e185f211c9a959739c6789872458b78f6424466819afc5c420af09b222af/psutil-5.3.1-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"data-dist-info-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"filename":"psutil-5.3.1-cp34-cp34m-win32.whl","hashes":{"sha256":"fc11c3a52990ec44064cbe026338dedcfff0e0027ca7516416eaa7d4f206c5af"},"provenance":null,"requires-python":null,"size":209976,"upload-time":"2017-09-10T05:27:29.519738Z","url":"https://files.pythonhosted.org/packages/ae/a9/ba609de04d2350878c6c3d641997dd37fa362775bf79aca3e6d542aae89e/psutil-5.3.1-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"data-dist-info-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"filename":"psutil-5.3.1-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"7b1f9856c2fc9503a8a687db85e4f419ad1a10bfcab92ba786a7d43a6aa8cea0"},"provenance":null,"requires-python":null,"size":212710,"upload-time":"2017-09-10T05:27:34.832459Z","url":"https://files.pythonhosted.org/packages/9e/4e/e35f4e9b3f5dfb8eb88be75ccbc6e6a6428443afa4d641ff5e9e29a8991f/psutil-5.3.1-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"data-dist-info-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"filename":"psutil-5.3.1-cp35-cp35m-win32.whl","hashes":{"sha256":"54781e463d9b9aa8c143033ee0d6a3149f9f143e6cc63099a95d4078f433dd56"},"provenance":null,"requires-python":null,"size":212159,"upload-time":"2017-09-10T05:27:39.270103Z","url":"https://files.pythonhosted.org/packages/79/4b/4531d21a7e428f3f25dc1b05be7e2024d9c2d45845ba005193dd9420e6b7/psutil-5.3.1-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"data-dist-info-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"filename":"psutil-5.3.1-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"e9ef8d265298268cad784dfece103ab06bd726512d57fc6ed9f94b55452e4571"},"provenance":null,"requires-python":null,"size":215831,"upload-time":"2017-09-10T05:27:43.869238Z","url":"https://files.pythonhosted.org/packages/aa/a7/1bc0baaea0798c1b29bfb6cec05f18f6a4cbaaf96646818429d998feb2f5/psutil-5.3.1-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"data-dist-info-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"filename":"psutil-5.3.1-cp36-cp36m-win32.whl","hashes":{"sha256":"f5d55618cd5b9270355fb52c0430ff30c4c84c5caf5b1254eec27f80d48e7a12"},"provenance":null,"requires-python":null,"size":212160,"upload-time":"2017-09-10T05:27:51.405015Z","url":"https://files.pythonhosted.org/packages/ba/03/1946dc720fec2083148b1d7b579e789bde85cebad67b22e05d5189871114/psutil-5.3.1-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"data-dist-info-metadata":{"sha256":"c340877ca68a4b72ce8f93ad0b6c48c15a2eeb72742c08998e478251efec5a92"},"filename":"psutil-5.3.1-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"773ba33fe365cb8b0998eedcbe494dc92ce7428998f07dca652a1360a9e2bce8"},"provenance":null,"requires-python":null,"size":215831,"upload-time":"2017-09-10T05:27:56.438641Z","url":"https://files.pythonhosted.org/packages/9b/de/5bd7038f8bb68516eedafc8bba9daf6740011db8afc1bb25cdcc8d654771/psutil-5.3.1-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.3.1.tar.gz","hashes":{"sha256":"12dd9c8abbad15f055e9579130035b38617020ce176f4a498b7870e6321ffa67"},"provenance":null,"requires-python":null,"size":397075,"upload-time":"2017-09-10T05:28:34.436466Z","url":"https://files.pythonhosted.org/packages/d3/0a/74dcbb162554909b208e5dbe9f4e7278d78cc27470993e05177005e627d0/psutil-5.3.1.tar.gz","yanked":false},{"core-metadata":{"sha256":"dc4ac37cc269cb56f6fd4292649312b61d28d4970d9dce30eebae6899076846a"},"data-dist-info-metadata":{"sha256":"dc4ac37cc269cb56f6fd4292649312b61d28d4970d9dce30eebae6899076846a"},"filename":"psutil-5.4.0-cp27-none-win32.whl","hashes":{"sha256":"8121039d2280275ac82f99a0a48110450cbf5b356a11c842c8f5cdeafdf105e1"},"provenance":null,"requires-python":null,"size":218177,"upload-time":"2017-10-12T07:26:59.429237Z","url":"https://files.pythonhosted.org/packages/4c/89/08a536124b4ee1fd850982f59ab7268a359e4160a6bcc1d473f6971fbdd4/psutil-5.4.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"dc4ac37cc269cb56f6fd4292649312b61d28d4970d9dce30eebae6899076846a"},"data-dist-info-metadata":{"sha256":"dc4ac37cc269cb56f6fd4292649312b61d28d4970d9dce30eebae6899076846a"},"filename":"psutil-5.4.0-cp27-none-win_amd64.whl","hashes":{"sha256":"fcd93acb2602d01b86e0cfa4c2db689a81badae98d9c572348c94f1b2ea4b30d"},"provenance":null,"requires-python":null,"size":220988,"upload-time":"2017-10-12T07:27:04.404602Z","url":"https://files.pythonhosted.org/packages/c9/3d/0cd95044d1245166ddf24144e1b3e7a6b6a81933de3ff48c1851664109fc/psutil-5.4.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b165c036e28aeff5aef41800231918509ab9366f5817be37e7123de4bd7ce3a2"},"data-dist-info-metadata":{"sha256":"b165c036e28aeff5aef41800231918509ab9366f5817be37e7123de4bd7ce3a2"},"filename":"psutil-5.4.0-cp33-cp33m-win32.whl","hashes":{"sha256":"60a58bfdda1fc6e86ecf95c6eef71252d9049694df9aa0a16c2841a425fc9deb"},"provenance":null,"requires-python":null,"size":218040,"upload-time":"2017-10-12T07:27:13.087638Z","url":"https://files.pythonhosted.org/packages/e5/1a/0f27898ad585d924e768b0c7029c7d7ac429994a3a032419c51f1c1f3e41/psutil-5.4.0-cp33-cp33m-win32.whl","yanked":false},{"core-metadata":{"sha256":"b165c036e28aeff5aef41800231918509ab9366f5817be37e7123de4bd7ce3a2"},"data-dist-info-metadata":{"sha256":"b165c036e28aeff5aef41800231918509ab9366f5817be37e7123de4bd7ce3a2"},"filename":"psutil-5.4.0-cp33-cp33m-win_amd64.whl","hashes":{"sha256":"9956b370243005d5561a94efa44b0cddb826d1f14d21958003925b008d3b9eb1"},"provenance":null,"requires-python":null,"size":220796,"upload-time":"2017-10-12T07:27:19.356245Z","url":"https://files.pythonhosted.org/packages/19/8e/b42236e04fbd03ffa2a08a393c8800fd7d51b11823c0e18723f0780d9b6a/psutil-5.4.0-cp33-cp33m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"data-dist-info-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"filename":"psutil-5.4.0-cp34-cp34m-win32.whl","hashes":{"sha256":"104fec73d9ed573351f3efbf1b7ee19eb3b4097e2b3d9ff26b1ac5bca52b6f9e"},"provenance":null,"requires-python":null,"size":217969,"upload-time":"2017-10-12T07:27:24.486998Z","url":"https://files.pythonhosted.org/packages/11/42/0711b59b7f2f2f7de7912d30bc599950011d25401bda8a3330f878ff1e56/psutil-5.4.0-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"data-dist-info-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"filename":"psutil-5.4.0-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"64b2814b30452d854d5f7a7c9c0d77423388b44eb2a8bcab3b84feeceaba8ffb"},"provenance":null,"requires-python":null,"size":220696,"upload-time":"2017-10-12T07:27:29.623184Z","url":"https://files.pythonhosted.org/packages/e6/67/e19ccc78646810cbc432c429e2993315cf50a126379b83b0f8b3eb9172b9/psutil-5.4.0-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"data-dist-info-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"filename":"psutil-5.4.0-cp35-cp35m-win32.whl","hashes":{"sha256":"1a89ba967d4b9a3d5f19ea2c63b09e5ffb3a81de3116ead7bfb67b9c308e8dba"},"provenance":null,"requires-python":null,"size":220150,"upload-time":"2017-10-12T07:27:34.351564Z","url":"https://files.pythonhosted.org/packages/cc/c1/47edb3fccbb1354499297702ca55aec41ff7aab67b4df69aba9db4e52a7c/psutil-5.4.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"data-dist-info-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"filename":"psutil-5.4.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"c8362902d4d94640c61960c18d8aa1af074822a549c3d4f137be3aa62c17f4b9"},"provenance":null,"requires-python":null,"size":223827,"upload-time":"2017-10-12T07:27:46.430105Z","url":"https://files.pythonhosted.org/packages/a9/e4/d399d060bbf40c1480ce2bfbc429edbd42769b048c4c7fdc51a49d74c9cf/psutil-5.4.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"data-dist-info-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"filename":"psutil-5.4.0-cp36-cp36m-win32.whl","hashes":{"sha256":"04ed7548dfe61ab2561a94ac848a5b79239bb23e9015596ffd0644efd22461ba"},"provenance":null,"requires-python":null,"size":220151,"upload-time":"2017-10-12T07:27:51.153048Z","url":"https://files.pythonhosted.org/packages/2b/46/88fdd6206f01b04547bb89a6d2ff7eec380e5c59bda56ee03a0759ed397c/psutil-5.4.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"data-dist-info-metadata":{"sha256":"eed08ae034f087bf660376ef37f75441a8f3d124c69a4be6089c1e755219a000"},"filename":"psutil-5.4.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"346555603ea903a5524bb37a49b0e0f90960c6c9973ebf794ae0802a4aa875eb"},"provenance":null,"requires-python":null,"size":223825,"upload-time":"2017-10-12T07:27:57.513929Z","url":"https://files.pythonhosted.org/packages/34/e2/8421ca5b99209fa43e48d2f76a21d5d86d7346838053abf7a4d2aa37255e/psutil-5.4.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.4.0.tar.gz","hashes":{"sha256":"8e6397ec24a2ec09751447d9f169486b68b37ac7a8d794dca003ace4efaafc6a"},"provenance":null,"requires-python":null,"size":406945,"upload-time":"2017-10-12T07:22:51.321681Z","url":"https://files.pythonhosted.org/packages/8d/96/1fc6468be91521192861966c40bd73fdf8b065eae6d82dd0f870b9825a65/psutil-5.4.0.tar.gz","yanked":false},{"core-metadata":{"sha256":"e0b722d181bbe7aba5016d4d14c510f34b0ceccccc534c1324af4b5bb619046f"},"data-dist-info-metadata":{"sha256":"e0b722d181bbe7aba5016d4d14c510f34b0ceccccc534c1324af4b5bb619046f"},"filename":"psutil-5.4.1-cp27-none-win32.whl","hashes":{"sha256":"7ef26ebe728ac821de17df23820e6ffcfd37c409fc865380e4d5ae1388f274a1"},"provenance":null,"requires-python":null,"size":218627,"upload-time":"2017-11-08T13:50:32.260924Z","url":"https://files.pythonhosted.org/packages/dd/dd/1811a99faefd2b5947b4e68bd70767b525fdbad65481a4bd2ee7e6408749/psutil-5.4.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"e0b722d181bbe7aba5016d4d14c510f34b0ceccccc534c1324af4b5bb619046f"},"data-dist-info-metadata":{"sha256":"e0b722d181bbe7aba5016d4d14c510f34b0ceccccc534c1324af4b5bb619046f"},"filename":"psutil-5.4.1-cp27-none-win_amd64.whl","hashes":{"sha256":"692dc72817d157aae522231dd334ea2524c6b07d844db0e7a2d6897820083427"},"provenance":null,"requires-python":null,"size":221450,"upload-time":"2017-11-08T13:50:37.542045Z","url":"https://files.pythonhosted.org/packages/51/c1/eec6a42a9f5fcd564c3fe3c6435c2c00e4e951a37f3ea3d324b04503ca6f/psutil-5.4.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"data-dist-info-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"filename":"psutil-5.4.1-cp34-cp34m-win32.whl","hashes":{"sha256":"92342777d46e4630cf17d437412dc7fce0a8561217e074d36a35eb911ffd570e"},"provenance":null,"requires-python":null,"size":218421,"upload-time":"2017-11-08T13:50:42.728570Z","url":"https://files.pythonhosted.org/packages/e1/bd/d34935cd39f893d6e9ed46df5245aa71b29d2408b98f23755f234d517f80/psutil-5.4.1-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"data-dist-info-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"filename":"psutil-5.4.1-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"f8f2f47a987c32ed3ca2068f3dfa9060dc9ff6cbed023d627d3f27060f4e59c4"},"provenance":null,"requires-python":null,"size":221144,"upload-time":"2017-11-08T13:50:47.928399Z","url":"https://files.pythonhosted.org/packages/24/ae/dcb7394e75b23b71f46742ffdab21d864a7ae74124b7930e5ea4f47b9049/psutil-5.4.1-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"data-dist-info-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"filename":"psutil-5.4.1-cp35-cp35m-win32.whl","hashes":{"sha256":"1fce45549618d1930afefe322834ba91758331725bfdaec73ba6abcc83f6dc11"},"provenance":null,"requires-python":null,"size":220611,"upload-time":"2017-11-08T13:50:52.754745Z","url":"https://files.pythonhosted.org/packages/e8/58/c60fbf66c58d1e4b18902d601a294cb6ee993f3d051b44fdf397b6166852/psutil-5.4.1-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"data-dist-info-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"filename":"psutil-5.4.1-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"f8a88553b2b5916f3bd814a91942215822a1dabae6db033cbb019095d6a24bc2"},"provenance":null,"requires-python":null,"size":224288,"upload-time":"2017-11-08T13:50:58.405900Z","url":"https://files.pythonhosted.org/packages/4f/f8/0e4e80114bc58267199c932f9227d09a00dea952b37400f76aa2a3bb9492/psutil-5.4.1-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"data-dist-info-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"filename":"psutil-5.4.1-cp36-cp36m-win32.whl","hashes":{"sha256":"4139f76baa59142b907dd581d7ff3506a5163cb8ef69e8e92060df330bbf5788"},"provenance":null,"requires-python":null,"size":220610,"upload-time":"2017-11-08T13:51:03.361290Z","url":"https://files.pythonhosted.org/packages/8e/16/02eb53ea087776d9f219973e7b52c7d729929a2727c15894842f9b3629e6/psutil-5.4.1-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"data-dist-info-metadata":{"sha256":"bea16c7d13fd463723ac0b38f421db313ae19443becf2ea91b0a77f6f3d8d8f2"},"filename":"psutil-5.4.1-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"d61bc04401ce938576e4c6ec201e812ed4114bfb9712202b87003619116c90c6"},"provenance":null,"requires-python":null,"size":224287,"upload-time":"2017-11-08T13:51:07.991086Z","url":"https://files.pythonhosted.org/packages/4a/89/0610a20ab3d1546fbe288001fb79ec4818ef6de29f89259c39daea85984f/psutil-5.4.1-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.4.1.tar.gz","hashes":{"sha256":"42e2de159e3c987435cb3b47d6f37035db190a1499f3af714ba7af5c379b6ba2"},"provenance":null,"requires-python":null,"size":408489,"upload-time":"2017-11-08T13:51:15.716367Z","url":"https://files.pythonhosted.org/packages/fe/17/0f0bf5792b2dfe6003efc5175c76225f7d3426f88e2bf8d360cfab870cd8/psutil-5.4.1.tar.gz","yanked":false},{"core-metadata":{"sha256":"3e4ef4cfed824c06d6acaff8f28af76203e0c1238133c71c6de75c654e9c89b1"},"data-dist-info-metadata":{"sha256":"3e4ef4cfed824c06d6acaff8f28af76203e0c1238133c71c6de75c654e9c89b1"},"filename":"psutil-5.4.2-cp27-none-win32.whl","hashes":{"sha256":"2fbbc7dce43c5240b9dc6d56302d57412f1c5a0d665d1f04eb05a6b7279f4e9b"},"provenance":null,"requires-python":null,"size":221114,"upload-time":"2017-12-07T12:03:17.419322Z","url":"https://files.pythonhosted.org/packages/e6/e2/09d55a6e899cf1a7b6a22d0cc2a75d45553df5b63d7c9f2eb2553c7207bc/psutil-5.4.2-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"3e4ef4cfed824c06d6acaff8f28af76203e0c1238133c71c6de75c654e9c89b1"},"data-dist-info-metadata":{"sha256":"3e4ef4cfed824c06d6acaff8f28af76203e0c1238133c71c6de75c654e9c89b1"},"filename":"psutil-5.4.2-cp27-none-win_amd64.whl","hashes":{"sha256":"259ec8578d19643179eb2377348c63b650b51ba40f58f2620a3d9732b8a0b557"},"provenance":null,"requires-python":null,"size":223996,"upload-time":"2017-12-07T12:03:22.758210Z","url":"https://files.pythonhosted.org/packages/47/fc/e2199322f422e4bb6e25808a335132235fb0f3fabb0ea71ec3442719fdaf/psutil-5.4.2-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"data-dist-info-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"filename":"psutil-5.4.2-cp34-cp34m-win32.whl","hashes":{"sha256":"d3808be8241433db17fa955566c3b8be61dac8ba8f221dcbb202a9daba918db5"},"provenance":null,"requires-python":null,"size":220935,"upload-time":"2017-12-07T12:03:27.058880Z","url":"https://files.pythonhosted.org/packages/d5/50/044ad4b47bf0e992a11ae7cb060e8c7dd52eb6983c2c4b6fdd5314fcc3b2/psutil-5.4.2-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"data-dist-info-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"filename":"psutil-5.4.2-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"449747f638c221f8ce6ca3548aefef13339aa05b453cc1f233f4d6c31c206198"},"provenance":null,"requires-python":null,"size":223749,"upload-time":"2017-12-07T12:03:31.671187Z","url":"https://files.pythonhosted.org/packages/b5/77/6f8b4c7c6e9e8fa60b0e0627853ba7182f1a2459ad0b557fe3257bbe014b/psutil-5.4.2-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"data-dist-info-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"filename":"psutil-5.4.2-cp35-cp35m-win32.whl","hashes":{"sha256":"f6c2d54abd59ed8691882de7fd6b248f5808a567885f20f50b3b4b9eedaebb1f"},"provenance":null,"requires-python":null,"size":223260,"upload-time":"2017-12-07T12:03:36.139696Z","url":"https://files.pythonhosted.org/packages/ad/fa/ac5b6bc2b25817509fc2e44f910f39b3385d1575262f7802b9c113ab786a/psutil-5.4.2-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"data-dist-info-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"filename":"psutil-5.4.2-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"e3d00d8fc3d4217f05d07af45390f072c04cb7c7dddd70b86b728e5fbe485c81"},"provenance":null,"requires-python":null,"size":226950,"upload-time":"2017-12-07T12:03:40.889125Z","url":"https://files.pythonhosted.org/packages/0e/5c/67b33328d4f307608006c3630838b272719125b36e5456a4dd8f4e76eca9/psutil-5.4.2-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"data-dist-info-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"filename":"psutil-5.4.2-cp36-cp36m-win32.whl","hashes":{"sha256":"3473d6abad9d6ec7b8a97f4dc55f0b3483ecf470d85f08f5e23c1c07592b914f"},"provenance":null,"requires-python":null,"size":223259,"upload-time":"2017-12-07T12:03:45.502272Z","url":"https://files.pythonhosted.org/packages/59/75/97862069d3fa20f1f2897fecd5b6822a3c06ee2af1161f1beb320cc4f5f8/psutil-5.4.2-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"data-dist-info-metadata":{"sha256":"aee7fd8368b69d2c7b666317dd0c8e73e7af18ef863dabadfb44e8f46ffef9d5"},"filename":"psutil-5.4.2-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"7dc6c3bbb5d28487f791f195d6abfdef295d34c44ce6cb5f2d178613fb3338ab"},"provenance":null,"requires-python":null,"size":226952,"upload-time":"2017-12-07T12:03:51.257182Z","url":"https://files.pythonhosted.org/packages/8f/9a/5dea138e49addd68d8e65d08103dd28428f1ea0b8d4e8beef9b24f069a16/psutil-5.4.2-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.4.2.tar.gz","hashes":{"sha256":"00a1f9ff8d1e035fba7bfdd6977fa8ea7937afdb4477339e5df3dba78194fe11"},"provenance":null,"requires-python":null,"size":411888,"upload-time":"2017-12-07T12:03:58.583601Z","url":"https://files.pythonhosted.org/packages/54/24/aa854703715fa161110daa001afce75d21d1840e9ab5eb28708d6a5058b0/psutil-5.4.2.tar.gz","yanked":false},{"core-metadata":{"sha256":"9de2e14fb34c7d6a6d1b3692c6e7830d8a950ce4740bd70dbc4f2900ad780b67"},"data-dist-info-metadata":{"sha256":"9de2e14fb34c7d6a6d1b3692c6e7830d8a950ce4740bd70dbc4f2900ad780b67"},"filename":"psutil-5.4.3-cp27-none-win32.whl","hashes":{"sha256":"82a06785db8eeb637b349006cc28a92e40cd190fefae9875246d18d0de7ccac8"},"provenance":null,"requires-python":null,"size":220984,"upload-time":"2018-01-01T20:33:16.515515Z","url":"https://files.pythonhosted.org/packages/e5/cc/6dd427e738a8db6d0b66525856da43d2ef12c4c19269863927f7cf0e2aaf/psutil-5.4.3-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"9de2e14fb34c7d6a6d1b3692c6e7830d8a950ce4740bd70dbc4f2900ad780b67"},"data-dist-info-metadata":{"sha256":"9de2e14fb34c7d6a6d1b3692c6e7830d8a950ce4740bd70dbc4f2900ad780b67"},"filename":"psutil-5.4.3-cp27-none-win_amd64.whl","hashes":{"sha256":"4152ae231709e3e8b80e26b6da20dc965a1a589959c48af1ed024eca6473f60d"},"provenance":null,"requires-python":null,"size":223871,"upload-time":"2018-01-01T20:33:24.887595Z","url":"https://files.pythonhosted.org/packages/b9/e4/6867765edcab8d12a52c84c9b0af492ecb99f8cc565ad552341bcf73ebd9/psutil-5.4.3-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"data-dist-info-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"filename":"psutil-5.4.3-cp34-cp34m-win32.whl","hashes":{"sha256":"230eeb3aeb077814f3a2cd036ddb6e0f571960d327298cc914c02385c3e02a63"},"provenance":null,"requires-python":null,"size":220823,"upload-time":"2018-01-01T20:33:31.019909Z","url":"https://files.pythonhosted.org/packages/5b/fc/745a864190a4221cdb984e666f4218e98d9a53a64b4dcf2eb7a71c1bf693/psutil-5.4.3-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"data-dist-info-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"filename":"psutil-5.4.3-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"a3286556d4d2f341108db65d8e20d0cd3fcb9a91741cb5eb496832d7daf2a97c"},"provenance":null,"requires-python":null,"size":223605,"upload-time":"2018-01-01T20:33:38.392793Z","url":"https://files.pythonhosted.org/packages/b0/25/414738d5e8e75418e560a36651d1e1b09c9df05440a2a808d999a5548b1e/psutil-5.4.3-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"data-dist-info-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"filename":"psutil-5.4.3-cp35-cp35m-win32.whl","hashes":{"sha256":"94d4e63189f2593960e73acaaf96be235dd8a455fe2bcb37d8ad6f0e87f61556"},"provenance":null,"requires-python":null,"size":223167,"upload-time":"2018-01-01T20:33:43.796884Z","url":"https://files.pythonhosted.org/packages/e9/80/8da216f42050220f37f7133d2accfcd001a1bd0f31d7cdb8660acb46b8fe/psutil-5.4.3-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"data-dist-info-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"filename":"psutil-5.4.3-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"c91eee73eea00df5e62c741b380b7e5b6fdd553891bee5669817a3a38d036f13"},"provenance":null,"requires-python":null,"size":226810,"upload-time":"2018-01-01T20:33:50.884798Z","url":"https://files.pythonhosted.org/packages/23/34/b3de39502c2c34899f9e7ae3c8d1050c9317997ab1fe6c647e7a789571a8/psutil-5.4.3-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"data-dist-info-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"filename":"psutil-5.4.3-cp36-cp36m-win32.whl","hashes":{"sha256":"779ec7e7621758ca11a8d99a1064996454b3570154277cc21342a01148a49c28"},"provenance":null,"requires-python":null,"size":223168,"upload-time":"2018-01-01T20:33:56.458784Z","url":"https://files.pythonhosted.org/packages/da/c1/caadba7c64f72118b02f019c60ad85a5668ddf0a32836230b71692b0cbfa/psutil-5.4.3-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"data-dist-info-metadata":{"sha256":"7a6c999b9c5b0661b0db22bb84f2ef4f1cf396a3f1ed6311aad005f3bee574e8"},"filename":"psutil-5.4.3-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"8a15d773203a1277e57b1d11a7ccdf70804744ef4a9518a87ab8436995c31a4b"},"provenance":null,"requires-python":null,"size":226804,"upload-time":"2018-01-01T20:34:03.130620Z","url":"https://files.pythonhosted.org/packages/71/80/90799d3dc6e33e650ee03f96fa18157faed885593eabea3a6560ebff7de0/psutil-5.4.3-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.4.3.tar.gz","hashes":{"sha256":"e2467e9312c2fa191687b89ff4bc2ad8843be4af6fb4dc95a7cc5f7d7a327b18"},"provenance":null,"requires-python":null,"size":412550,"upload-time":"2018-01-01T20:34:13.285899Z","url":"https://files.pythonhosted.org/packages/e2/e1/600326635f97fee89bf8426fef14c5c29f4849c79f68fd79f433d8c1bd96/psutil-5.4.3.tar.gz","yanked":false},{"core-metadata":{"sha256":"485b43e92c234e987a6b5a9be22665d46ab26940b08a2edad8583aa4743c0439"},"data-dist-info-metadata":{"sha256":"485b43e92c234e987a6b5a9be22665d46ab26940b08a2edad8583aa4743c0439"},"filename":"psutil-5.4.4-cp27-none-win32.whl","hashes":{"sha256":"8f208867d41eb3b6de416df098a9a28d08d40b432467d821b8ef5bb589a394ce"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":216647,"upload-time":"2018-04-13T09:10:24.220147Z","url":"https://files.pythonhosted.org/packages/e8/cd/dbf537e32de1c9f06a0069bf0ef13c8707f653e9af2b0ea0ed7040b73083/psutil-5.4.4-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"485b43e92c234e987a6b5a9be22665d46ab26940b08a2edad8583aa4743c0439"},"data-dist-info-metadata":{"sha256":"485b43e92c234e987a6b5a9be22665d46ab26940b08a2edad8583aa4743c0439"},"filename":"psutil-5.4.4-cp27-none-win_amd64.whl","hashes":{"sha256":"77b5e310de17085346ef2c4c21b64d5e39616ab4559b8ef6fea9f6f2ab0de66f"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219668,"upload-time":"2018-04-13T09:10:29.328807Z","url":"https://files.pythonhosted.org/packages/7f/37/538bb4275d8a26ec369944878a681000e827e72cab9b4f27f4b1b5932446/psutil-5.4.4-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"data-dist-info-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"filename":"psutil-5.4.4-cp34-cp34m-win32.whl","hashes":{"sha256":"fec0e59dacbe91db7e063f038301f49da7e9361732fc31d28338ecaa4719520e"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":216642,"upload-time":"2018-04-13T09:10:33.842941Z","url":"https://files.pythonhosted.org/packages/92/e9/c9c4ec1a0ac55ee1514c1a249d017dd2f7a89e727d236ed6862b493de154/psutil-5.4.4-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"data-dist-info-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"filename":"psutil-5.4.4-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"1268fb6959cd8d761c30e13e79908ae73ba5a69c3c3a5d09a7a27278446f9800"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219513,"upload-time":"2018-04-13T09:10:38.041575Z","url":"https://files.pythonhosted.org/packages/83/49/c903f446d28bfb6e92fa08b710f68cd5d17cc2ccfc4a13fe607f8b20f6dd/psutil-5.4.4-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"data-dist-info-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"filename":"psutil-5.4.4-cp35-cp35m-win32.whl","hashes":{"sha256":"7eb2d80ef79d90474a03eead13b32e541d1fdeb47468cf04c881f0a7392ddbc5"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219220,"upload-time":"2018-04-13T09:10:42.685382Z","url":"https://files.pythonhosted.org/packages/bb/63/c9b3e9ff8d23409896a7e9e7356a730fdfb5a45a1edc0e6d4ca5ce655f29/psutil-5.4.4-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"data-dist-info-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"filename":"psutil-5.4.4-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"69f1db4d13f362ce11a6246b20c752c31b87a6fd77452170fd03c26a8a20a4f2"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":222757,"upload-time":"2018-04-13T09:10:47.950981Z","url":"https://files.pythonhosted.org/packages/72/22/a5ce34af1285679e02d7fd701ff6389f579a17e623dd89236ea1873ce12b/psutil-5.4.4-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"data-dist-info-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"filename":"psutil-5.4.4-cp36-cp36m-win32.whl","hashes":{"sha256":"6eb59bcfd48eade8889bae67a16e0d8c7b18af0732ba64dead61206fd7cb4e45"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219224,"upload-time":"2018-04-13T09:10:52.291473Z","url":"https://files.pythonhosted.org/packages/3c/6d/5e9a3683d4532997525aa20d1d9ce0ca1201271d30aad9e5f18c34459478/psutil-5.4.4-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"data-dist-info-metadata":{"sha256":"893713cd1f34926704c2e5f844b554a79ad42ce1c20046651dad28613eb2d0bc"},"filename":"psutil-5.4.4-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"a4af5d4fcf6022886a30fb3b4fff71ff25f645865a68506680d43a3e634764af"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":222759,"upload-time":"2018-04-13T09:10:56.584621Z","url":"https://files.pythonhosted.org/packages/b6/61/eeeab30fa737b8b95b790d3eb8f49ebedeb783e43aef2d8d851687592d6c/psutil-5.4.4-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.4.4.tar.gz","hashes":{"sha256":"5959e33e0fc69742dd22e88bfc7789a1f2e1fc2297794b543119e10cdac8dfb1"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":417890,"upload-time":"2018-04-13T09:11:03.199870Z","url":"https://files.pythonhosted.org/packages/35/35/7da482448cd9ee3555faa9c5e541e37b18a849fb961e55d6fda6ca936ddb/psutil-5.4.4.tar.gz","yanked":false},{"core-metadata":{"sha256":"c8af77aa4cab9fad5f3310c13c633fb6bb0f5a44421d0e8662a9f16b38ca9aec"},"data-dist-info-metadata":{"sha256":"c8af77aa4cab9fad5f3310c13c633fb6bb0f5a44421d0e8662a9f16b38ca9aec"},"filename":"psutil-5.4.5-cp27-none-win32.whl","hashes":{"sha256":"33384065f0014351fa70187548e3e95952c4df4bc5c38648bd0e647d21eaaf01"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":216630,"upload-time":"2018-04-13T17:59:43.195295Z","url":"https://files.pythonhosted.org/packages/63/9f/529a599db3057602114a30aa5e3641e78bce6c6e195adb75309c9286cb88/psutil-5.4.5-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"c8af77aa4cab9fad5f3310c13c633fb6bb0f5a44421d0e8662a9f16b38ca9aec"},"data-dist-info-metadata":{"sha256":"c8af77aa4cab9fad5f3310c13c633fb6bb0f5a44421d0e8662a9f16b38ca9aec"},"filename":"psutil-5.4.5-cp27-none-win_amd64.whl","hashes":{"sha256":"f24cd52bafa06917935fe1b68c5a45593abe1f3097dc35b2dfc4718236795890"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219650,"upload-time":"2018-04-13T17:59:53.389911Z","url":"https://files.pythonhosted.org/packages/b6/ca/2d23b37e9b30908174d2cb596f60f06b3858856a2e595c931f7d4d640c03/psutil-5.4.5-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"data-dist-info-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"filename":"psutil-5.4.5-cp34-cp34m-win32.whl","hashes":{"sha256":"99029b6af386b22882f0b6d537ffed5a9c3d5ff31782974aeaa1d683262d8543"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":216589,"upload-time":"2018-04-13T18:00:04.166075Z","url":"https://files.pythonhosted.org/packages/bf/bc/f687dfa4679aad782fb78c43fed2626cb0157567a5b06790997e5aa0f166/psutil-5.4.5-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"data-dist-info-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"filename":"psutil-5.4.5-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"51e12aa74509832443862373a2655052b20c83cad7322f49d217452500b9a405"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219462,"upload-time":"2018-04-13T18:00:14.637120Z","url":"https://files.pythonhosted.org/packages/32/b5/545953316dd9cb053e4c7d4f70d88aba5362dbbe58422ca6bbec1bbf8956/psutil-5.4.5-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"data-dist-info-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"filename":"psutil-5.4.5-cp35-cp35m-win32.whl","hashes":{"sha256":"325c334596ad2d8a178d0e7b4eecc91748096a87489b3701ee16986173000aaa"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219172,"upload-time":"2018-04-13T18:00:28.230677Z","url":"https://files.pythonhosted.org/packages/00/ed/fdf2930c41e76e3a8bc59bf998062ee5ad0c393170a7d2c273dd3b259794/psutil-5.4.5-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"data-dist-info-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"filename":"psutil-5.4.5-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"52a91ba928a5e86e0249b4932d6e36972a72d1ad8dcc5b7f753a2ae14825a4ba"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":222706,"upload-time":"2018-04-13T18:00:38.180085Z","url":"https://files.pythonhosted.org/packages/c6/bf/09b13c17f54f0004ccb43cc1c2d36bab2eb75f471564b7856749dcaf62c3/psutil-5.4.5-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"data-dist-info-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"filename":"psutil-5.4.5-cp36-cp36m-win32.whl","hashes":{"sha256":"b10703a109cc9225cd588c207f7f93480a420ade35c13515ea8f20063b42a392"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219171,"upload-time":"2018-04-13T18:00:52.610707Z","url":"https://files.pythonhosted.org/packages/3c/ae/34952007b4d64f88a03510866b9cd90207e391f6b2b59b6301ad96fa0fb5/psutil-5.4.5-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"data-dist-info-metadata":{"sha256":"4fb804f1abb5eb53785449da8e1201c9af34b09face243251213fa79878f9142"},"filename":"psutil-5.4.5-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"ddba952ed256151844d82fb13c8fb1019fe11ecaeacbd659d67ba5661ae73d0d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":222710,"upload-time":"2018-04-13T18:01:02.457641Z","url":"https://files.pythonhosted.org/packages/4c/bb/303f15f4a47b96ff0ae5025d89b330e2be314085c418c0b726877476e937/psutil-5.4.5-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.4.5.tar.gz","hashes":{"sha256":"ebe293be36bb24b95cdefc5131635496e88b17fabbcf1e4bc9b5c01f5e489cfe"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":418003,"upload-time":"2018-04-13T18:01:19.381491Z","url":"https://files.pythonhosted.org/packages/14/a2/8ac7dda36eac03950ec2668ab1b466314403031c83a95c5efc81d2acf163/psutil-5.4.5.tar.gz","yanked":false},{"core-metadata":{"sha256":"aa807ba097a31687e416722680ba225449cb087946d4db7e62db8c1659a43a5d"},"data-dist-info-metadata":{"sha256":"aa807ba097a31687e416722680ba225449cb087946d4db7e62db8c1659a43a5d"},"filename":"psutil-5.4.6-cp27-none-win32.whl","hashes":{"sha256":"319e12f6bae4d4d988fbff3bed792953fa3b44c791f085b0a1a230f755671ef7"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":216468,"upload-time":"2018-06-07T15:39:23.967375Z","url":"https://files.pythonhosted.org/packages/b4/00/82c9fb4ffca22f2f6d0d883469584cd0cff71a604a19809015045b1fbab6/psutil-5.4.6-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"aa807ba097a31687e416722680ba225449cb087946d4db7e62db8c1659a43a5d"},"data-dist-info-metadata":{"sha256":"aa807ba097a31687e416722680ba225449cb087946d4db7e62db8c1659a43a5d"},"filename":"psutil-5.4.6-cp27-none-win_amd64.whl","hashes":{"sha256":"7789885a72aa3075d28d028236eb3f2b84d908f81d38ad41769a6ddc2fd81b7c"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219532,"upload-time":"2018-06-07T15:39:26.297791Z","url":"https://files.pythonhosted.org/packages/c2/23/22df1d36dc8ae002e9f646f9ed06b4f6bfbc7a22b67804c3a497be21d002/psutil-5.4.6-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"data-dist-info-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"filename":"psutil-5.4.6-cp34-cp34m-win32.whl","hashes":{"sha256":"0ff2b16e9045d01edb1dd10d7fbcc184012e37f6cd38029e959f2be9c6223f50"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":216464,"upload-time":"2018-06-07T15:39:28.563215Z","url":"https://files.pythonhosted.org/packages/f9/fa/966988e350306e1a1a9024e77ad5f118cbfe11318e8bdfc258c3d5a1c68b/psutil-5.4.6-cp34-cp34m-win32.whl","yanked":false},{"core-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"data-dist-info-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"filename":"psutil-5.4.6-cp34-cp34m-win_amd64.whl","hashes":{"sha256":"dc85fad15ef98103ecc047a0d81b55bbf5fe1b03313b96e883acc2e2fa87ed5c"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219331,"upload-time":"2018-06-07T15:39:31.250304Z","url":"https://files.pythonhosted.org/packages/a5/0d/40b552c2c089523df1f7ab5a0249fbf90cb2e80e89177b0189e41e367adc/psutil-5.4.6-cp34-cp34m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"data-dist-info-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"filename":"psutil-5.4.6-cp35-cp35m-win32.whl","hashes":{"sha256":"7f4616bcb44a6afda930cfc40215e5e9fa7c6896e683b287c771c937712fbe2f"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219052,"upload-time":"2018-06-07T15:39:33.732713Z","url":"https://files.pythonhosted.org/packages/00/4d/194ec701de80c704f679bf78495c054994cc403884ffec816787813c4fde/psutil-5.4.6-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"data-dist-info-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"filename":"psutil-5.4.6-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"529ae235896efb99a6f77653a7138273ab701ec9f0343a1f5030945108dee3c4"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":222533,"upload-time":"2018-06-07T15:39:36.224840Z","url":"https://files.pythonhosted.org/packages/e3/21/f82f270326e098f211bcc36cbb2ae7100732dcad03bd324e6af8c9d7e407/psutil-5.4.6-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"data-dist-info-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"filename":"psutil-5.4.6-cp36-cp36m-win32.whl","hashes":{"sha256":"254adb6a27c888f141d2a6032ae231d8ed4fc5f7583b4c825e5f7d7c78d26d2e"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219048,"upload-time":"2018-06-07T15:39:38.273913Z","url":"https://files.pythonhosted.org/packages/d6/e0/0f1b4f61246c4e2b540898b1ca0fa51ee2f52f0366956974f1039e00ed67/psutil-5.4.6-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"data-dist-info-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"filename":"psutil-5.4.6-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"a9b85b335b40a528a8e2a6b549592138de8429c6296e7361892958956e6a73cf"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":222534,"upload-time":"2018-06-07T15:39:40.675791Z","url":"https://files.pythonhosted.org/packages/36/4b/80d9eb5d39ec4b4d8aec8b098b5097a7291de20bbbe6c2ab233b9d8fe245/psutil-5.4.6-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"data-dist-info-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"filename":"psutil-5.4.6-cp37-cp37m-win32.whl","hashes":{"sha256":"6d981b4d863b20c8ceed98b8ac3d1ca7f96d28707a80845d360fa69c8fc2c44b"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":219860,"upload-time":"2018-06-28T22:47:57.979619Z","url":"https://files.pythonhosted.org/packages/7a/62/28923c44954b6cf8aee637f3a2f30e0e1ff39ec0f74a4f98069d37f00751/psutil-5.4.6-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"data-dist-info-metadata":{"sha256":"b7bbbf54e7767a4dcfada805607a533828d7e74794bf64ea1ae76700fc24d495"},"filename":"psutil-5.4.6-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"7fdb3d02bfd68f508e6745021311a4a4dbfec53fca03721474e985f310e249ba"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":224296,"upload-time":"2018-06-28T22:48:01.565801Z","url":"https://files.pythonhosted.org/packages/b1/be/78f9d786bddc190c4b394a01531741a11b95f1522cf2759958f13b46407f/psutil-5.4.6-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.4.6.tar.gz","hashes":{"sha256":"686e5a35fe4c0acc25f3466c32e716f2d498aaae7b7edc03e2305b682226bcf6"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":418059,"upload-time":"2018-06-07T15:39:42.364107Z","url":"https://files.pythonhosted.org/packages/51/9e/0f8f5423ce28c9109807024f7bdde776ed0b1161de20b408875de7e030c3/psutil-5.4.6.tar.gz","yanked":false},{"core-metadata":{"sha256":"94ef062f36609f64cc686f1930bf277790b85ced443769e242d41eb7a2588bec"},"data-dist-info-metadata":{"sha256":"94ef062f36609f64cc686f1930bf277790b85ced443769e242d41eb7a2588bec"},"filename":"psutil-5.4.7-cp27-none-win32.whl","hashes":{"sha256":"b34611280a2d0697f1c499e15e936d88109170194b390599c98bab8072a71f05"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":217777,"upload-time":"2018-08-14T21:01:10.586697Z","url":"https://files.pythonhosted.org/packages/b7/e9/bedbdfecef9d708489cfcd8b9aeada8d8f014fc14644c7129c7177e80d32/psutil-5.4.7-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"94ef062f36609f64cc686f1930bf277790b85ced443769e242d41eb7a2588bec"},"data-dist-info-metadata":{"sha256":"94ef062f36609f64cc686f1930bf277790b85ced443769e242d41eb7a2588bec"},"filename":"psutil-5.4.7-cp27-none-win_amd64.whl","hashes":{"sha256":"a890c3e490493f21da2817ffc92822693bc0d6bcac9999caa04ffce8dd4e7132"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":220989,"upload-time":"2018-08-14T21:01:13.273922Z","url":"https://files.pythonhosted.org/packages/50/6a/34525bc4e6e153bf6e849a4c4e936742b365f6819c0462cebfa4f082a3c4/psutil-5.4.7-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"data-dist-info-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"filename":"psutil-5.4.7-cp35-cp35m-win32.whl","hashes":{"sha256":"1914bacbd2fc2af8f795daa44b9d2e0649a147460cfd21b1a70a124472f66d40"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":220344,"upload-time":"2018-08-14T21:01:15.659359Z","url":"https://files.pythonhosted.org/packages/36/32/5b10d7c3940c64fe92620c368ede8a10016d51aa36079a5cd69944da5a74/psutil-5.4.7-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"data-dist-info-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"filename":"psutil-5.4.7-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"d081707ef0081920533db30200a2d30d5c0ea9cf6afa7cf8881ae4516cc69c48"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":224081,"upload-time":"2018-08-14T21:01:18.052521Z","url":"https://files.pythonhosted.org/packages/ef/fc/8dc7731df7de2f4c65378a7147ecc977221093eee90d9777ca501c2790c5/psutil-5.4.7-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"data-dist-info-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"filename":"psutil-5.4.7-cp36-cp36m-win32.whl","hashes":{"sha256":"0d8da7333549a998556c18eb2af3ce902c28d66ceb947505c008f91e9f988abd"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":220342,"upload-time":"2018-08-14T21:01:20.870660Z","url":"https://files.pythonhosted.org/packages/20/6e/a9a0f84bc3efe970b4c8688b7e7f14ee8342497de8a88cffd35bb485cdcc/psutil-5.4.7-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"data-dist-info-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"filename":"psutil-5.4.7-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"cea2557ee6a9faa2c100947637ded68414e12b851633c4ce26e0311b2a2ed539"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":224085,"upload-time":"2018-08-14T21:01:22.891149Z","url":"https://files.pythonhosted.org/packages/97/9e/c056abafcf0fc7ca5bddbc21ad1bb7c67889e16c088b9759f00b95fefcb4/psutil-5.4.7-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"data-dist-info-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"filename":"psutil-5.4.7-cp37-cp37m-win32.whl","hashes":{"sha256":"215d61a901e67b1a35e14c6aedef317f7fa7e6075a20c150fd11bd2c906d2c83"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":220347,"upload-time":"2018-08-14T21:01:25.157286Z","url":"https://files.pythonhosted.org/packages/2e/33/5cef36162d94cf0adce428729adeb18b8548ff060781854f3aca71e6b0f0/psutil-5.4.7-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"data-dist-info-metadata":{"sha256":"77861491100106f950300f1104d6fcb309d103d34e379a69c8780e9edefa6710"},"filename":"psutil-5.4.7-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"51057c03aea251ad6667c2bba259bc7ed3210222d3a74152c84e3ab06e1da0ba"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":224087,"upload-time":"2018-08-14T21:01:27.563142Z","url":"https://files.pythonhosted.org/packages/bb/15/aa3d11ae8bf04b7683224f7d3b8f2dd4d3f8a918dcce59bb1f987fca9c6e/psutil-5.4.7-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.4.7.tar.gz","hashes":{"sha256":"5b6322b167a5ba0c5463b4d30dfd379cd4ce245a1162ebf8fc7ab5c5ffae4f3b"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":420300,"upload-time":"2018-08-14T21:01:30.063470Z","url":"https://files.pythonhosted.org/packages/7d/9a/1e93d41708f8ed2b564395edfa3389f0fd6d567597401c2e5e2775118d8b/psutil-5.4.7.tar.gz","yanked":false},{"core-metadata":{"sha256":"000e5f1178f3b76020f7866397e41f07c1f4564da730a45c24894e3aebebb7cb"},"data-dist-info-metadata":{"sha256":"000e5f1178f3b76020f7866397e41f07c1f4564da730a45c24894e3aebebb7cb"},"filename":"psutil-5.4.8-cp27-none-win32.whl","hashes":{"sha256":"809c9cef0402e3e48b5a1dddc390a8a6ff58b15362ea5714494073fa46c3d293"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":220106,"upload-time":"2018-10-30T09:57:34.685390Z","url":"https://files.pythonhosted.org/packages/5a/3f/3f0920df352dae7f824e0e612ff02591378f78405d6c7663dcac023005c4/psutil-5.4.8-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"000e5f1178f3b76020f7866397e41f07c1f4564da730a45c24894e3aebebb7cb"},"data-dist-info-metadata":{"sha256":"000e5f1178f3b76020f7866397e41f07c1f4564da730a45c24894e3aebebb7cb"},"filename":"psutil-5.4.8-cp27-none-win_amd64.whl","hashes":{"sha256":"3b7a4daf4223dae171a67a89314ac5ca0738e94064a78d99cfd751c55d05f315"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":223347,"upload-time":"2018-10-30T09:57:37.065973Z","url":"https://files.pythonhosted.org/packages/0f/fb/6aecd2c8c9d0ac83d789eaf9f9ec052dd61dd5aea2b47ffa4704175d7a2a/psutil-5.4.8-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"data-dist-info-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"filename":"psutil-5.4.8-cp35-cp35m-win32.whl","hashes":{"sha256":"bbffac64cfd01c6bcf90eb1bedc6c80501c4dae8aef4ad6d6dd49f8f05f6fc5a"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":222713,"upload-time":"2018-10-30T09:57:39.214229Z","url":"https://files.pythonhosted.org/packages/46/2e/ce4ec4b60decc23e0e4d148b6f44c7ddd06ba0ab207dfaee21958bd669df/psutil-5.4.8-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"data-dist-info-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"filename":"psutil-5.4.8-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"b4d1b735bf5b120813f4c89db8ac22d89162c558cbd7fdd298866125fe906219"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226441,"upload-time":"2018-10-30T09:57:41.411069Z","url":"https://files.pythonhosted.org/packages/7f/28/5ccdb98eff12e7741cc2a6d9dcfdd5d9e06f6d363c2c019d5bfa0e0c1282/psutil-5.4.8-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"data-dist-info-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"filename":"psutil-5.4.8-cp36-cp36m-win32.whl","hashes":{"sha256":"3e19be3441134445347af3767fa7770137d472a484070840eee6653b94ac5576"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":222711,"upload-time":"2018-10-30T09:57:43.901154Z","url":"https://files.pythonhosted.org/packages/b5/31/8ac896ca77a6aa75ee900698f96ddce46e96bb2484a92457c359a4e4bae6/psutil-5.4.8-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"data-dist-info-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"filename":"psutil-5.4.8-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"1c19957883e0b93d081d41687089ad630e370e26dc49fd9df6951d6c891c4736"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226440,"upload-time":"2018-10-30T09:57:46.331527Z","url":"https://files.pythonhosted.org/packages/3b/15/62d1eeb4c015e20295e0197f7de0202bd9e5bcb5529b9503932decde2505/psutil-5.4.8-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"data-dist-info-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"filename":"psutil-5.4.8-cp37-cp37m-win32.whl","hashes":{"sha256":"bfcea4f189177b2d2ce4a34b03c4ac32c5b4c22e21f5b093d9d315e6e253cd81"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":222709,"upload-time":"2018-10-30T09:57:48.860424Z","url":"https://files.pythonhosted.org/packages/21/1e/fe6731e5f03ddf2e57d5b307f25bba294262bc88e27a0fbefdb3515d1727/psutil-5.4.8-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"data-dist-info-metadata":{"sha256":"edcd0deabcf9a4ae02acef42bf31c36db6ad085b36a3cc549cfded47de1f1e8f"},"filename":"psutil-5.4.8-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"1c71b9716790e202a00ab0931a6d1e25db1aa1198bcacaea2f5329f75d257fff"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226447,"upload-time":"2018-10-30T09:57:50.676641Z","url":"https://files.pythonhosted.org/packages/50/00/ae52663b879333aa5c65fc9a87ddc24169f8fdd1831762a1ba9c9be7740d/psutil-5.4.8-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.4.8.tar.gz","hashes":{"sha256":"6e265c8f3da00b015d24b842bfeb111f856b13d24f2c57036582568dc650d6c3"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":422742,"upload-time":"2018-10-30T09:57:52.770639Z","url":"https://files.pythonhosted.org/packages/e3/58/0eae6e4466e5abf779d7e2b71fac7fba5f59e00ea36ddb3ed690419ccb0f/psutil-5.4.8.tar.gz","yanked":false},{"core-metadata":{"sha256":"1d8c3b2819c1de19ccc23f8de068eab529322b6df33673da0ec45a41f785bdcc"},"data-dist-info-metadata":{"sha256":"1d8c3b2819c1de19ccc23f8de068eab529322b6df33673da0ec45a41f785bdcc"},"filename":"psutil-5.5.0-cp27-none-win32.whl","hashes":{"sha256":"96f3fdb4ef7467854d46ad5a7e28eb4c6dc6d455d751ddf9640cd6d52bdb03d7"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":221314,"upload-time":"2019-01-23T18:23:33.526811Z","url":"https://files.pythonhosted.org/packages/fa/53/53f8c4f1af6f81b169ce76bfd0f56698bc1705da498a47d2ce701f7d7fe3/psutil-5.5.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"1d8c3b2819c1de19ccc23f8de068eab529322b6df33673da0ec45a41f785bdcc"},"data-dist-info-metadata":{"sha256":"1d8c3b2819c1de19ccc23f8de068eab529322b6df33673da0ec45a41f785bdcc"},"filename":"psutil-5.5.0-cp27-none-win_amd64.whl","hashes":{"sha256":"d23f7025bac9b3e38adc6bd032cdaac648ac0074d18e36950a04af35458342e8"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":224546,"upload-time":"2019-01-23T18:23:36.443393Z","url":"https://files.pythonhosted.org/packages/86/96/a7bfcc3aebedd7112ff353204901db6a1a0c1f3555b2788c68842bb78005/psutil-5.5.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"data-dist-info-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"filename":"psutil-5.5.0-cp35-cp35m-win32.whl","hashes":{"sha256":"04d2071100aaad59f9bcbb801be2125d53b2e03b1517d9fed90b45eea51d297e"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":224084,"upload-time":"2019-01-23T18:23:39.132334Z","url":"https://files.pythonhosted.org/packages/c6/ca/f5d3841ca35e3e3607ed64fe61d2c392054692f05f35e807335299a7952b/psutil-5.5.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"data-dist-info-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"filename":"psutil-5.5.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"d0c4230d60376aee0757d934020b14899f6020cd70ef8d2cb4f228b6ffc43e8f"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":227536,"upload-time":"2019-01-23T18:23:42.027389Z","url":"https://files.pythonhosted.org/packages/86/0d/a13c15ddccd8e2ccabe63f6d4f139f5a94150b758026e030310e87dded80/psutil-5.5.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"data-dist-info-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"filename":"psutil-5.5.0-cp36-cp36m-win32.whl","hashes":{"sha256":"3ac48568f5b85fee44cd8002a15a7733deca056a191d313dbf24c11519c0c4a8"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":224085,"upload-time":"2019-01-23T18:23:44.450649Z","url":"https://files.pythonhosted.org/packages/45/00/7cdd50ded02e18e50667e2f76ceb645ecdfce59deb39422485f198c7be37/psutil-5.5.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"data-dist-info-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"filename":"psutil-5.5.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"f0fcb7d3006dd4d9ccf3ccd0595d44c6abbfd433ec31b6ca177300ee3f19e54e"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":227537,"upload-time":"2019-01-23T18:23:46.937101Z","url":"https://files.pythonhosted.org/packages/48/d1/c9105512328c7f9800c51992b912df6f945eac696dfcd850f719541f67f3/psutil-5.5.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"data-dist-info-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"filename":"psutil-5.5.0-cp37-cp37m-win32.whl","hashes":{"sha256":"c8ee08ad1b716911c86f12dc753eb1879006224fd51509f077987bb6493be615"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":224078,"upload-time":"2019-01-23T18:23:49.451606Z","url":"https://files.pythonhosted.org/packages/3f/14/5adcad73f22ae0c8fba8b054c0bb7c33c906121b588cc6cbdd686f098947/psutil-5.5.0-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"data-dist-info-metadata":{"sha256":"053bd08cfadac2a889dd4c4d2e10bd70c7d11e50fa66c23c145303639bb537b1"},"filename":"psutil-5.5.0-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"b755be689d6fc8ebc401e1d5ce5bac867e35788f10229e166338484eead51b12"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":227542,"upload-time":"2019-01-23T18:23:52.035332Z","url":"https://files.pythonhosted.org/packages/38/f1/a822d2b3d973c1ddd9d8a81d269e36987bab20e7bb28ecaa55aef66e8df5/psutil-5.5.0-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.5.0.tar.gz","hashes":{"sha256":"1aba93430050270750d046a179c5f3d6e1f5f8b96c20399ba38c596b28fc4d37"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":425058,"upload-time":"2019-01-23T18:23:54.951599Z","url":"https://files.pythonhosted.org/packages/6e/a0/833bcbcede5141cc5615e50c7cc5b960ce93d9c9b885fbe3b7d36e48a2d4/psutil-5.5.0.tar.gz","yanked":false},{"core-metadata":{"sha256":"0fa640ac67268845bdf0ad87325deaf6caae5372bf3703f282c5340f9af1f8c2"},"data-dist-info-metadata":{"sha256":"0fa640ac67268845bdf0ad87325deaf6caae5372bf3703f282c5340f9af1f8c2"},"filename":"psutil-5.5.1-cp27-none-win32.whl","hashes":{"sha256":"77c231b4dff8c1c329a4cd1c22b96c8976c597017ff5b09993cd148d6a94500c"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":221914,"upload-time":"2019-02-15T19:34:04.528250Z","url":"https://files.pythonhosted.org/packages/cc/cd/64aaf20c945662260026a128a08e46b93a49953224c0dccfdc6f37495d45/psutil-5.5.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"0fa640ac67268845bdf0ad87325deaf6caae5372bf3703f282c5340f9af1f8c2"},"data-dist-info-metadata":{"sha256":"0fa640ac67268845bdf0ad87325deaf6caae5372bf3703f282c5340f9af1f8c2"},"filename":"psutil-5.5.1-cp27-none-win_amd64.whl","hashes":{"sha256":"5ce6b5eb0267233459f4d3980c205828482f450999b8f5b684d9629fea98782a"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":225325,"upload-time":"2019-02-15T19:34:07.193210Z","url":"https://files.pythonhosted.org/packages/08/92/97b011d665ade1caf05dd02a3af4ede751c7b80f34812bc81479ec867d85/psutil-5.5.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"data-dist-info-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"filename":"psutil-5.5.1-cp35-cp35m-win32.whl","hashes":{"sha256":"a013b4250ccbddc9d22feca0f986a1afc71717ad026c0f2109bbffd007351191"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":224822,"upload-time":"2019-02-15T19:34:09.528347Z","url":"https://files.pythonhosted.org/packages/6f/05/70f033e35cd34bc23a08793eaf713347c615674585ccfc7628b40eac4094/psutil-5.5.1-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"data-dist-info-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"filename":"psutil-5.5.1-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"ef3e5e02b3c5d1df366abe7b4820400d5c427579668ad4465ff189d28ded5ebd"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":228328,"upload-time":"2019-02-15T19:34:12.035704Z","url":"https://files.pythonhosted.org/packages/8e/8d/1854ed30b9f69f4b2cc04ef4364ae8a52ad2988a3223bf6314d2d47f0f04/psutil-5.5.1-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"data-dist-info-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"filename":"psutil-5.5.1-cp36-cp36m-win32.whl","hashes":{"sha256":"ad43b83119eeea6d5751023298cd331637e542cbd332196464799e25a5519f8f"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":224831,"upload-time":"2019-02-15T19:34:15.031823Z","url":"https://files.pythonhosted.org/packages/e8/c1/32fe16cb90192a9413f3ba303021047c158cbdde5aabd7e26ace8b54f69e/psutil-5.5.1-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"data-dist-info-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"filename":"psutil-5.5.1-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"ec1ef313530a9457e48d25e3fdb1723dfa636008bf1b970027462d46f2555d59"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":228328,"upload-time":"2019-02-15T19:34:17.494390Z","url":"https://files.pythonhosted.org/packages/3e/6e/c0af4900f18811f09b93064588e53f3997abc051ae43f717d1ba610de3b7/psutil-5.5.1-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"data-dist-info-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"filename":"psutil-5.5.1-cp37-cp37m-win32.whl","hashes":{"sha256":"c177777c787d247d02dae6c855330f9ed3e1abf8ca1744c26dd5ff968949999a"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":224832,"upload-time":"2019-02-15T19:34:19.685175Z","url":"https://files.pythonhosted.org/packages/00/e6/561fed27453add44af41a52e13e1dfca4d1e35705d698769edea6292339a/psutil-5.5.1-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"data-dist-info-metadata":{"sha256":"2aea42bc7128a735bd3dd530e3776b5c46cfb57362fa41b02ed50e6275148b66"},"filename":"psutil-5.5.1-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"8846ab0be0cdccd6cc92ecd1246a16e2f2e49f53bd73e522c3a75ac291e1b51d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":228327,"upload-time":"2019-02-15T19:34:22.218113Z","url":"https://files.pythonhosted.org/packages/3d/22/ed4fa46c5bfd95b4dc57d6544c3fe6568abe398aef3990f6011777f1a3f3/psutil-5.5.1-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.5.1.tar.gz","hashes":{"sha256":"72cebfaa422b7978a1d3632b65ff734a34c6b34f4578b68a5c204d633756b810"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":426750,"upload-time":"2019-02-15T19:34:24.841527Z","url":"https://files.pythonhosted.org/packages/c7/01/7c30b247cdc5ba29623faa5c8cf1f1bbf7e041783c340414b0ed7e067c64/psutil-5.5.1.tar.gz","yanked":false},{"core-metadata":{"sha256":"254b7bff05e4525511dae40a6fde55a8c45855ad1f6f2e1bc7dc4a50d7c0caf6"},"data-dist-info-metadata":{"sha256":"254b7bff05e4525511dae40a6fde55a8c45855ad1f6f2e1bc7dc4a50d7c0caf6"},"filename":"psutil-5.6.0-cp27-none-win32.whl","hashes":{"sha256":"1020a37214c4138e34962881372b40f390582b5c8245680c04349c2afb785a25"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":222534,"upload-time":"2019-03-05T12:00:59.141291Z","url":"https://files.pythonhosted.org/packages/04/f5/11b1c93a8882615fdaf6222aaf9d3197f250ab3036d7ecf6b6c8594ddf61/psutil-5.6.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"254b7bff05e4525511dae40a6fde55a8c45855ad1f6f2e1bc7dc4a50d7c0caf6"},"data-dist-info-metadata":{"sha256":"254b7bff05e4525511dae40a6fde55a8c45855ad1f6f2e1bc7dc4a50d7c0caf6"},"filename":"psutil-5.6.0-cp27-none-win_amd64.whl","hashes":{"sha256":"d9cdc2e82aeb82200fff3640f375fac39d88b1bed27ce08377cd7fb0e3621cb7"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":225803,"upload-time":"2019-03-05T12:01:01.831664Z","url":"https://files.pythonhosted.org/packages/11/88/ed94a7c091fb6ad8fbf545f0f20e140c47286712d6d85dd8cfc40b34fe72/psutil-5.6.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"data-dist-info-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"filename":"psutil-5.6.0-cp35-cp35m-win32.whl","hashes":{"sha256":"c4a2f42abee709ed97b4498c21aa608ac31fc1f7cc8aa60ebdcd3c80757a038d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226153,"upload-time":"2019-03-05T12:01:04.695139Z","url":"https://files.pythonhosted.org/packages/ab/5c/dacbb4b6623dc390939e1785a818e6e816eb42c8c96c1f2d30a2fc22a773/psutil-5.6.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"data-dist-info-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"filename":"psutil-5.6.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"722dc0dcce5272f3c5c41609fdc2c8f0ee3f976550c2d2f2057e26ba760be9c0"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230078,"upload-time":"2019-03-05T12:01:07.429607Z","url":"https://files.pythonhosted.org/packages/41/27/9e6d39ae822387ced9a77da904e24e8796c1fa55c5f637cd35221b170980/psutil-5.6.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"data-dist-info-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"filename":"psutil-5.6.0-cp36-cp36m-win32.whl","hashes":{"sha256":"1c8e6444ca1cee9a60a1a35913b8409722f7474616e0e21004e4ffadba59964b"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226154,"upload-time":"2019-03-05T12:01:10.226359Z","url":"https://files.pythonhosted.org/packages/fb/d0/14b83939ed41c5a80dca6fa072b1a99cd576e33810f691e73a08a7c045b4/psutil-5.6.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"data-dist-info-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"filename":"psutil-5.6.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"151c9858c268a1523e16fab33e3bc3bae8a0e57b57cf7fcad85fb409cbac6baf"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230083,"upload-time":"2019-03-05T12:01:12.681476Z","url":"https://files.pythonhosted.org/packages/ce/3a/ff53c0ee59a864c3614fcaea45f4246e670934ea0d30b632c6e3905533c9/psutil-5.6.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"data-dist-info-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"filename":"psutil-5.6.0-cp37-cp37m-win32.whl","hashes":{"sha256":"86f61a1438c026c980a4c3e2dd88a5774a3a0f00d6d0954d6c5cf8d1921b804e"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226154,"upload-time":"2019-03-05T12:01:15.084497Z","url":"https://files.pythonhosted.org/packages/17/15/bf444a07aae2200f7b4e786c73bfe959201cb9ab63f737be12d21b5f252e/psutil-5.6.0-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"data-dist-info-metadata":{"sha256":"4587b1b69c3d6666cbea0b1ac5ab97e537ce98c5c4986439eec64421d1b2baeb"},"filename":"psutil-5.6.0-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"da6676a484adec2fdd3e1ce1b70799881ffcb958e40208dd4c5beba0011f3589"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230081,"upload-time":"2019-03-05T12:01:17.936041Z","url":"https://files.pythonhosted.org/packages/88/fd/a32491e77b37ffc00faf79c864975cfd720ae0ac867d93c90640d44adf43/psutil-5.6.0-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.6.0.tar.gz","hashes":{"sha256":"dca71c08335fbfc6929438fe3a502f169ba96dd20e50b3544053d6be5cb19d82"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":426596,"upload-time":"2019-03-05T12:01:20.833448Z","url":"https://files.pythonhosted.org/packages/79/e6/a4e3c92fe19d386dcc6149dbf0b76f1c93c5491ae9d9ecf866f6769b45a4/psutil-5.6.0.tar.gz","yanked":false},{"core-metadata":{"sha256":"36d6733a6cb331fb0a7acb6d901557574835b038d157bc2fc11404d520fe4b92"},"data-dist-info-metadata":{"sha256":"36d6733a6cb331fb0a7acb6d901557574835b038d157bc2fc11404d520fe4b92"},"filename":"psutil-5.6.1-cp27-none-win32.whl","hashes":{"sha256":"23e9cd90db94fbced5151eaaf9033ae9667c033dffe9e709da761c20138d25b6"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":223000,"upload-time":"2019-03-11T17:24:48.368476Z","url":"https://files.pythonhosted.org/packages/9d/f2/a84e650af7f04940709466384e94a0894cfe736e7cf43f48fb3bfb01be1b/psutil-5.6.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"36d6733a6cb331fb0a7acb6d901557574835b038d157bc2fc11404d520fe4b92"},"data-dist-info-metadata":{"sha256":"36d6733a6cb331fb0a7acb6d901557574835b038d157bc2fc11404d520fe4b92"},"filename":"psutil-5.6.1-cp27-none-win_amd64.whl","hashes":{"sha256":"e1494d20ffe7891d07d8cb9a8b306c1a38d48b13575265d090fc08910c56d474"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226302,"upload-time":"2019-03-11T17:24:51.027456Z","url":"https://files.pythonhosted.org/packages/46/51/4007e6188b6d6b68c8b4195d6755bd76585cffd4d286d9f1815ff9f1af01/psutil-5.6.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"data-dist-info-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"filename":"psutil-5.6.1-cp35-cp35m-win32.whl","hashes":{"sha256":"ec4b4b638b84d42fc48139f9352f6c6587ee1018d55253542ee28db7480cc653"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226675,"upload-time":"2019-03-11T17:24:53.625833Z","url":"https://files.pythonhosted.org/packages/55/f4/2dc147b66111116f5cb6a85fe42519b2cbfdbf6138562f8b0427bc754fc8/psutil-5.6.1-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"data-dist-info-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"filename":"psutil-5.6.1-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"c1fd45931889dc1812ba61a517630d126f6185f688eac1693171c6524901b7de"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230613,"upload-time":"2019-03-11T17:24:56.387303Z","url":"https://files.pythonhosted.org/packages/af/dd/f3bdafb37af7bf417f984db5381a5a27899f93fd2f87d5fd10fb6f3f4087/psutil-5.6.1-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"data-dist-info-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"filename":"psutil-5.6.1-cp36-cp36m-win32.whl","hashes":{"sha256":"d463a142298112426ebd57351b45c39adb41341b91f033aa903fa4c6f76abecc"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226672,"upload-time":"2019-03-11T17:24:58.629155Z","url":"https://files.pythonhosted.org/packages/d5/cc/e3fe388fe3c987973e929cb17cceb33bcd2ff7f8b754cd354826fbb7dfe7/psutil-5.6.1-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"data-dist-info-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"filename":"psutil-5.6.1-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"9c3a768486194b4592c7ae9374faa55b37b9877fd9746fb4028cb0ac38fd4c60"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230621,"upload-time":"2019-03-11T17:25:01.330880Z","url":"https://files.pythonhosted.org/packages/16/6a/cc5ba8d7e3ada0d4621d493dbdcb43ed38f3549642916a14c9e070add21a/psutil-5.6.1-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"data-dist-info-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"filename":"psutil-5.6.1-cp37-cp37m-win32.whl","hashes":{"sha256":"27858d688a58cbfdd4434e1c40f6c79eb5014b709e725c180488ccdf2f721729"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226673,"upload-time":"2019-03-11T17:25:03.865746Z","url":"https://files.pythonhosted.org/packages/a8/8e/3e04eefe955ed94f93c3cde5dc1f1ccd99e2b9a56697fa804ea9f54f7baa/psutil-5.6.1-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"data-dist-info-metadata":{"sha256":"ea349a2dffe50e05b14983a2cf8238d799954fa7f79adc264520315279fa3b54"},"filename":"psutil-5.6.1-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"354601a1d1a1322ae5920ba397c58d06c29728a15113598d1a8158647aaa5385"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230621,"upload-time":"2019-03-11T17:25:06.996226Z","url":"https://files.pythonhosted.org/packages/6a/48/dbcda6d136da319e8bee8196e6c52ff7febf56bd241435cf6a516341a4b1/psutil-5.6.1-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.6.1.tar.gz","hashes":{"sha256":"fa0a570e0a30b9dd618bffbece590ae15726b47f9f1eaf7518dfb35f4d7dcd21"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":427472,"upload-time":"2019-03-11T17:25:09.802408Z","url":"https://files.pythonhosted.org/packages/2f/b8/11ec5006d2ec2998cb68349b8d1317c24c284cf918ecd6729739388e4c56/psutil-5.6.1.tar.gz","yanked":false},{"core-metadata":{"sha256":"34922aac8bd27ca8268d8103c8b443cb013543dbfb0b3b7cf9f18d802b4fc760"},"data-dist-info-metadata":{"sha256":"34922aac8bd27ca8268d8103c8b443cb013543dbfb0b3b7cf9f18d802b4fc760"},"filename":"psutil-5.6.2-cp27-none-win32.whl","hashes":{"sha256":"76fb0956d6d50e68e3f22e7cc983acf4e243dc0fcc32fd693d398cb21c928802"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226692,"upload-time":"2019-04-26T02:43:03.614779Z","url":"https://files.pythonhosted.org/packages/56/8f/1bfb7e563e413f110ee06ac0cf12bb4b27f78e9cf277892d97ba08de4eac/psutil-5.6.2-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"34922aac8bd27ca8268d8103c8b443cb013543dbfb0b3b7cf9f18d802b4fc760"},"data-dist-info-metadata":{"sha256":"34922aac8bd27ca8268d8103c8b443cb013543dbfb0b3b7cf9f18d802b4fc760"},"filename":"psutil-5.6.2-cp27-none-win_amd64.whl","hashes":{"sha256":"753c5988edc07da00dafd6d3d279d41f98c62cd4d3a548c4d05741a023b0c2e7"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230225,"upload-time":"2019-04-26T02:43:08.318806Z","url":"https://files.pythonhosted.org/packages/91/3f/2ae9cd04b2ccc5340838383ba638839a498d2613936b7830079f77de2bf1/psutil-5.6.2-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"data-dist-info-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"filename":"psutil-5.6.2-cp35-cp35m-win32.whl","hashes":{"sha256":"a4c62319ec6bf2b3570487dd72d471307ae5495ce3802c1be81b8a22e438b4bc"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230751,"upload-time":"2019-04-26T02:43:12.014098Z","url":"https://files.pythonhosted.org/packages/ec/39/33a7d6c4e347ffff7784ec4967c04d6319886c5a77c208d29a0980045bc8/psutil-5.6.2-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"data-dist-info-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"filename":"psutil-5.6.2-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"ef342cb7d9b60e6100364f50c57fa3a77d02ff8665d5b956746ac01901247ac4"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":234724,"upload-time":"2019-04-26T02:43:15.850786Z","url":"https://files.pythonhosted.org/packages/a9/43/87e610adacc3f8e51d61c27a9d48db2c0e7fbac783764fefca4d4ec71dbe/psutil-5.6.2-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"data-dist-info-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"filename":"psutil-5.6.2-cp36-cp36m-win32.whl","hashes":{"sha256":"acba1df9da3983ec3c9c963adaaf530fcb4be0cd400a8294f1ecc2db56499ddd"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230749,"upload-time":"2019-04-26T02:43:19.804035Z","url":"https://files.pythonhosted.org/packages/23/2a/0f14e964507adc1732ade6eea3abd92afe34305614515ab856cbccca489a/psutil-5.6.2-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"data-dist-info-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"filename":"psutil-5.6.2-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"206eb909aa8878101d0eca07f4b31889c748f34ed6820a12eb3168c7aa17478e"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":234728,"upload-time":"2019-04-26T02:43:23.354778Z","url":"https://files.pythonhosted.org/packages/62/b0/54effe77128bdd8b62ca10edf38c22dbe5594ad8b34ce31836011949ac0a/psutil-5.6.2-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"data-dist-info-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"filename":"psutil-5.6.2-cp37-cp37m-win32.whl","hashes":{"sha256":"649f7ffc02114dced8fbd08afcd021af75f5f5b2311bc0e69e53e8f100fe296f"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230750,"upload-time":"2019-04-26T02:43:26.831308Z","url":"https://files.pythonhosted.org/packages/86/90/ea3d046bb26aebcbf25fba32cf9ec7d0954ea6b8e4e9d9c87a31633dd96b/psutil-5.6.2-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"data-dist-info-metadata":{"sha256":"11c017c8a57096625072309b7badf4a1f677690026dbfc728de5f3dfd4ae2936"},"filename":"psutil-5.6.2-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"6ebf2b9c996bb8c7198b385bade468ac8068ad8b78c54a58ff288cd5f61992c7"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":234731,"upload-time":"2019-04-26T02:43:30.698781Z","url":"https://files.pythonhosted.org/packages/98/3c/65f28f78848730c18dfa3ff3a107e3911b6d51d1442cfce8db53356179c3/psutil-5.6.2-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.6.2.tar.gz","hashes":{"sha256":"828e1c3ca6756c54ac00f1427fdac8b12e21b8a068c3bb9b631a1734cada25ed"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":432907,"upload-time":"2019-04-26T02:43:34.455685Z","url":"https://files.pythonhosted.org/packages/c6/c1/beed5e4eaa1345901b595048fab1c85aee647ea0fc02d9e8bf9aceb81078/psutil-5.6.2.tar.gz","yanked":false},{"core-metadata":{"sha256":"b5acb81452f4830e88ff6014732a59e7fc4839457f7950deab90c8eed0d4e6b8"},"data-dist-info-metadata":{"sha256":"b5acb81452f4830e88ff6014732a59e7fc4839457f7950deab90c8eed0d4e6b8"},"filename":"psutil-5.6.3-cp27-none-win32.whl","hashes":{"sha256":"d5350cb66690915d60f8b233180f1e49938756fb2d501c93c44f8fb5b970cc63"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":226847,"upload-time":"2019-06-11T04:24:07.412816Z","url":"https://files.pythonhosted.org/packages/da/76/7e445566f2c4363691a98f16df0072c6ba92c7c29b4410ef2df6514c9861/psutil-5.6.3-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"b5acb81452f4830e88ff6014732a59e7fc4839457f7950deab90c8eed0d4e6b8"},"data-dist-info-metadata":{"sha256":"b5acb81452f4830e88ff6014732a59e7fc4839457f7950deab90c8eed0d4e6b8"},"filename":"psutil-5.6.3-cp27-none-win_amd64.whl","hashes":{"sha256":"b6e08f965a305cd84c2d07409bc16fbef4417d67b70c53b299116c5b895e3f45"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230406,"upload-time":"2019-06-11T04:24:10.852056Z","url":"https://files.pythonhosted.org/packages/72/75/43047d7df3ea2af2bcd072e63420b8fa240b729c052295f8c4b964335d36/psutil-5.6.3-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"data-dist-info-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"filename":"psutil-5.6.3-cp35-cp35m-win32.whl","hashes":{"sha256":"cf49178021075d47c61c03c0229ac0c60d5e2830f8cab19e2d88e579b18cdb76"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230912,"upload-time":"2019-06-11T04:24:14.533883Z","url":"https://files.pythonhosted.org/packages/c2/f2/d99eaeefb2c0b1f7f9aca679db17f4072d4cc362f40f809157d4e2d273dd/psutil-5.6.3-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"data-dist-info-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"filename":"psutil-5.6.3-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"bc96d437dfbb8865fc8828cf363450001cb04056bbdcdd6fc152c436c8a74c61"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":234942,"upload-time":"2019-06-11T04:24:18.128893Z","url":"https://files.pythonhosted.org/packages/90/86/d5ae0eb79cab6acc00d3640a45243e3e0602dc2f7abca29fc2fe6b4819ca/psutil-5.6.3-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"data-dist-info-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"filename":"psutil-5.6.3-cp36-cp36m-win32.whl","hashes":{"sha256":"eba238cf1989dfff7d483c029acb0ac4fcbfc15de295d682901f0e2497e6781a"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230914,"upload-time":"2019-06-11T04:24:21.735106Z","url":"https://files.pythonhosted.org/packages/28/0c/e41fd3020662487cf92f0c47b09285d7e53c42ee56cdc3ddb03a147cfa5d/psutil-5.6.3-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"data-dist-info-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"filename":"psutil-5.6.3-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"954f782608bfef9ae9f78e660e065bd8ffcfaea780f9f2c8a133bb7cb9e826d7"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":234945,"upload-time":"2019-06-11T04:24:26.649579Z","url":"https://files.pythonhosted.org/packages/86/91/f15a3aae2af13f008ed95e02292d1a2e84615ff42b7203357c1c0bbe0651/psutil-5.6.3-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"data-dist-info-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"filename":"psutil-5.6.3-cp37-cp37m-win32.whl","hashes":{"sha256":"503e4b20fa9d3342bcf58191bbc20a4a5ef79ca7df8972e6197cc14c5513e73d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230914,"upload-time":"2019-06-11T04:24:30.881936Z","url":"https://files.pythonhosted.org/packages/3b/92/2a7fb18054ac12483fb72f281c285d21642ca3d29fc6a06f0e44d4b36d83/psutil-5.6.3-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"data-dist-info-metadata":{"sha256":"2df22107b0d2ddaa4d080521f6dd4f4139500bf928eeee6cbbfd64b2abb197fb"},"filename":"psutil-5.6.3-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"028a1ec3c6197eadd11e7b46e8cc2f0720dc18ac6d7aabdb8e8c0d6c9704f000"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":234949,"upload-time":"2019-06-11T04:24:34.722512Z","url":"https://files.pythonhosted.org/packages/7c/58/f5d68ddca37480d8557b8566a20bf6108d7e1c6c9b9208ee0786e0cd012b/psutil-5.6.3-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"991ea9c126c59fb12521c83a1676786ad9a7ea2af4f3985b1e8086af3eda4156"},"data-dist-info-metadata":{"sha256":"991ea9c126c59fb12521c83a1676786ad9a7ea2af4f3985b1e8086af3eda4156"},"filename":"psutil-5.6.3-cp38-cp38-win_amd64.whl","hashes":{"sha256":"12542c3642909f4cd1928a2fba59e16fa27e47cbeea60928ebb62a8cbd1ce123"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238041,"upload-time":"2019-10-24T10:11:31.818649Z","url":"https://files.pythonhosted.org/packages/a7/7f/0761489b5467af4e97ae3c5a25f24f8662d69a25692d85490c8ea5d52e45/psutil-5.6.3-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.6.3.tar.gz","hashes":{"sha256":"863a85c1c0a5103a12c05a35e59d336e1d665747e531256e061213e2e90f63f3"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":435374,"upload-time":"2019-06-11T04:24:39.293855Z","url":"https://files.pythonhosted.org/packages/1c/ca/5b8c1fe032a458c2c4bcbe509d1401dca9dda35c7fc46b36bb81c2834740/psutil-5.6.3.tar.gz","yanked":false},{"core-metadata":{"sha256":"2625785be98a15c8fa27587a95cae17a6673efa1e3bc2154856b9af05aaac32a"},"data-dist-info-metadata":{"sha256":"2625785be98a15c8fa27587a95cae17a6673efa1e3bc2154856b9af05aaac32a"},"filename":"psutil-5.6.4-cp27-none-win32.whl","hashes":{"sha256":"75d50d1138b2476a11dca33ab1ad2b78707d428418b581966ccedac768358f72"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":228290,"upload-time":"2019-11-04T08:38:28.711265Z","url":"https://files.pythonhosted.org/packages/8a/63/0273163f0197ff2d8f026ae0f9c7cac371f8819f6451e6cfd2e1e7132b90/psutil-5.6.4-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"2625785be98a15c8fa27587a95cae17a6673efa1e3bc2154856b9af05aaac32a"},"data-dist-info-metadata":{"sha256":"2625785be98a15c8fa27587a95cae17a6673efa1e3bc2154856b9af05aaac32a"},"filename":"psutil-5.6.4-cp27-none-win_amd64.whl","hashes":{"sha256":"0ff1f630ee0df7c048ef53e50196437d2c9cebab8ccca0e3078d9300c4b7da47"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":231797,"upload-time":"2019-11-04T08:38:33.155488Z","url":"https://files.pythonhosted.org/packages/db/d0/17a47a1876cf408d0dd679f78ba18a206228fdb95928137dd85538a51298/psutil-5.6.4-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"data-dist-info-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"filename":"psutil-5.6.4-cp35-cp35m-win32.whl","hashes":{"sha256":"10175ea15b7e4a1bf1a0863da7e17042862b3ea3e7d24285c96fa4cc65ab9788"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232325,"upload-time":"2019-11-04T08:38:37.308918Z","url":"https://files.pythonhosted.org/packages/cb/22/e2ce6539342fb0ebd65fb9e2a0a168585eb5cae9bd5dca7977735f8d430e/psutil-5.6.4-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"data-dist-info-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"filename":"psutil-5.6.4-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"f6b66a5663700b71bac3d8ecf6533a1550a679823e63b2c92dc4c3c8c244c52e"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236342,"upload-time":"2019-11-04T08:38:41.583480Z","url":"https://files.pythonhosted.org/packages/32/9a/beaf5df1f8b38d3ec7ec737f873515813586aa26926738896957c3af0a9d/psutil-5.6.4-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"data-dist-info-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"filename":"psutil-5.6.4-cp36-cp36m-win32.whl","hashes":{"sha256":"4f637dd25d3bce4879d0b4032d13f4120ba18ed2d028e85d911d429f447c251c"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232330,"upload-time":"2019-11-04T08:38:46.049782Z","url":"https://files.pythonhosted.org/packages/74/3c/61d46eccf9fca27fc11f30d27af11c3886609d5eb52475926a1ad12d6ebd/psutil-5.6.4-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"data-dist-info-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"filename":"psutil-5.6.4-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"43f0d7536a98c20a538242ce2bd8c64dbc1f6c396e97f2bdceb496d7583b9b80"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236347,"upload-time":"2019-11-04T08:38:50.265170Z","url":"https://files.pythonhosted.org/packages/2c/59/beae1392ad5188b419709d3e04641cbe93e36184b7b8686af825a2232b2b/psutil-5.6.4-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"data-dist-info-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"filename":"psutil-5.6.4-cp37-cp37m-win32.whl","hashes":{"sha256":"f0ec1a3ea56503f4facc1dca364cf3dd66dc39169c4603000d3d34270e05fbb3"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232327,"upload-time":"2019-11-04T08:38:54.615037Z","url":"https://files.pythonhosted.org/packages/e6/2e/e0c14d0fa46afb15b1467daa792f143e675034f8c544d03a2b7365d4926c/psutil-5.6.4-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"data-dist-info-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"filename":"psutil-5.6.4-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"512e77ac987105e2d7aa2386d9f260434ad8b71e41484f8d84bfecd4ae3764ca"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236347,"upload-time":"2019-11-04T08:38:58.188759Z","url":"https://files.pythonhosted.org/packages/a3/59/fe32a3ec677990a5ed6b1a44dc2e372c9ee40693890814e8b6d96c290c4d/psutil-5.6.4-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"data-dist-info-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"filename":"psutil-5.6.4-cp38-cp38-win32.whl","hashes":{"sha256":"41d645f100c6b4c995ff342ef7d79a936f3f48e9a816d7d655c69b352460341d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":234629,"upload-time":"2019-11-04T08:46:40.809419Z","url":"https://files.pythonhosted.org/packages/50/0c/7b2fa0baedd36147beb820f662844b1079e168c06e9fe62d78a4d2666375/psutil-5.6.4-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"data-dist-info-metadata":{"sha256":"f16d89990a0e49264d1393eb1ee021563470e55f511d8518008f85c41d8ff3b0"},"filename":"psutil-5.6.4-cp38-cp38-win_amd64.whl","hashes":{"sha256":"fb58e87c29ec0fb99937b95c5d473bb786d263aaa767d017a6bd4ad52d694e79"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239057,"upload-time":"2019-11-04T08:46:45.192939Z","url":"https://files.pythonhosted.org/packages/e3/28/fa3a597ed798a5a2cb9c9e53c2d8b6bc1310c4a147c2aaa65c5d3afdb6ed/psutil-5.6.4-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.6.4.tar.gz","hashes":{"sha256":"512e854d68f8b42f79b2c7864d997b39125baff9bcff00028ce43543867de7c4"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":447564,"upload-time":"2019-11-04T08:39:02.419203Z","url":"https://files.pythonhosted.org/packages/47/ea/d3b6d6fd0b4a6c12984df652525f394e68c8678d2b05075219144eb3a1cf/psutil-5.6.4.tar.gz","yanked":false},{"core-metadata":{"sha256":"3ccf04696d9a6c2c81c586788345860eb9fc45feada6313ab8d40865c7a9ec78"},"data-dist-info-metadata":{"sha256":"3ccf04696d9a6c2c81c586788345860eb9fc45feada6313ab8d40865c7a9ec78"},"filename":"psutil-5.6.5-cp27-none-win32.whl","hashes":{"sha256":"145e0f3ab9138165f9e156c307100905fd5d9b7227504b8a9d3417351052dc3d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":228289,"upload-time":"2019-11-06T10:07:27.672551Z","url":"https://files.pythonhosted.org/packages/97/52/9a44db00d4400814384d574f3a2c2b588259c212e457f542c2589dbdac58/psutil-5.6.5-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"3ccf04696d9a6c2c81c586788345860eb9fc45feada6313ab8d40865c7a9ec78"},"data-dist-info-metadata":{"sha256":"3ccf04696d9a6c2c81c586788345860eb9fc45feada6313ab8d40865c7a9ec78"},"filename":"psutil-5.6.5-cp27-none-win_amd64.whl","hashes":{"sha256":"3feea46fbd634a93437b718518d15b5dd49599dfb59a30c739e201cc79bb759d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":231796,"upload-time":"2019-11-06T10:07:31.973662Z","url":"https://files.pythonhosted.org/packages/c4/35/4422a966d304faa401d55dc45caf342722a14c0a9b56d57ecf208f9bb6a3/psutil-5.6.5-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"data-dist-info-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"filename":"psutil-5.6.5-cp35-cp35m-win32.whl","hashes":{"sha256":"348ad4179938c965a27d29cbda4a81a1b2c778ecd330a221aadc7bd33681afbd"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232325,"upload-time":"2019-11-06T10:07:36.463607Z","url":"https://files.pythonhosted.org/packages/4c/2f/93ac7f065f8c4c361e422dffff21dea1803cb40f2449e6ea52800caddf9f/psutil-5.6.5-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"data-dist-info-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"filename":"psutil-5.6.5-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"474e10a92eeb4100c276d4cc67687adeb9d280bbca01031a3e41fb35dfc1d131"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236340,"upload-time":"2019-11-06T10:07:41.723247Z","url":"https://files.pythonhosted.org/packages/9f/49/fddf3b6138a5d47668e2daf98ac9a6eeea4c28e3eb68b56580f18e8fe6af/psutil-5.6.5-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"data-dist-info-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"filename":"psutil-5.6.5-cp36-cp36m-win32.whl","hashes":{"sha256":"e3f5f9278867e95970854e92d0f5fe53af742a7fc4f2eba986943345bcaed05d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232327,"upload-time":"2019-11-06T10:07:45.647021Z","url":"https://files.pythonhosted.org/packages/ae/5d/11bb7fb7cc004bdf1325c0b40827e67e479ababe47c553ee871494353acf/psutil-5.6.5-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"data-dist-info-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"filename":"psutil-5.6.5-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"dfb8c5c78579c226841908b539c2374da54da648ee5a837a731aa6a105a54c00"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236344,"upload-time":"2019-11-06T10:07:49.683064Z","url":"https://files.pythonhosted.org/packages/c6/18/221e8a5084585c6a2550894fb0617dc4691b5216f4aa6bb82c330aa5d99c/psutil-5.6.5-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"data-dist-info-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"filename":"psutil-5.6.5-cp37-cp37m-win32.whl","hashes":{"sha256":"021d361439586a0fd8e64f8392eb7da27135db980f249329f1a347b9de99c695"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232327,"upload-time":"2019-11-06T10:07:53.427002Z","url":"https://files.pythonhosted.org/packages/31/f8/d612f18fed1422a016bb641d3ce1922904a2aa0cc3472ce4abf2716cf542/psutil-5.6.5-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"data-dist-info-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"filename":"psutil-5.6.5-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"e9649bb8fc5cea1f7723af53e4212056a6f984ee31784c10632607f472dec5ee"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236346,"upload-time":"2019-11-06T10:07:57.218779Z","url":"https://files.pythonhosted.org/packages/97/c7/9b18c3b429c987796d74647c61030c7029518ac223d1a664951417781fe4/psutil-5.6.5-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"data-dist-info-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"filename":"psutil-5.6.5-cp38-cp38-win32.whl","hashes":{"sha256":"47aeb4280e80f27878caae4b572b29f0ec7967554b701ba33cd3720b17ba1b07"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":234627,"upload-time":"2019-11-06T10:08:50.811061Z","url":"https://files.pythonhosted.org/packages/03/94/e4ee514cfbc4cca176fcc6b4b1118a724848b570941e90f0b98a9bd234e1/psutil-5.6.5-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"data-dist-info-metadata":{"sha256":"98b3da0b296c70a30e4e371a72ba5d3205dc455f09fe824464a824ad9b4fd6ad"},"filename":"psutil-5.6.5-cp38-cp38-win_amd64.whl","hashes":{"sha256":"73a7e002781bc42fd014dfebb3fc0e45f8d92a4fb9da18baea6fb279fbc1d966"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239056,"upload-time":"2019-11-06T10:08:55.334494Z","url":"https://files.pythonhosted.org/packages/cd/3b/de8a1f1692de2f16716a108a63366aa66692e5a087b6e0458eef9739e652/psutil-5.6.5-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.6.5.tar.gz","hashes":{"sha256":"d051532ac944f1be0179e0506f6889833cf96e466262523e57a871de65a15147"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":447489,"upload-time":"2019-11-06T10:08:01.639000Z","url":"https://files.pythonhosted.org/packages/03/9a/95c4b3d0424426e5fd94b5302ff74cea44d5d4f53466e1228ac8e73e14b4/psutil-5.6.5.tar.gz","yanked":false},{"core-metadata":{"sha256":"0ee5221d5d9af774f9490843a768567a29b9f7ace20754cf0822d80eb298fdd1"},"data-dist-info-metadata":{"sha256":"0ee5221d5d9af774f9490843a768567a29b9f7ace20754cf0822d80eb298fdd1"},"filename":"psutil-5.6.6-cp27-none-win32.whl","hashes":{"sha256":"06660136ab88762309775fd47290d7da14094422d915f0466e0adf8e4b22214e"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":228564,"upload-time":"2019-11-25T12:30:48.823010Z","url":"https://files.pythonhosted.org/packages/c9/c2/4d703fcb5aacd4cb8d472d3c1d0e7d8c21e17f37b515016a7ee34ff3f4a0/psutil-5.6.6-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"0ee5221d5d9af774f9490843a768567a29b9f7ace20754cf0822d80eb298fdd1"},"data-dist-info-metadata":{"sha256":"0ee5221d5d9af774f9490843a768567a29b9f7ace20754cf0822d80eb298fdd1"},"filename":"psutil-5.6.6-cp27-none-win_amd64.whl","hashes":{"sha256":"f21a7bb4b207e4e7c60b3c40ffa89d790997619f04bbecec9db8e3696122bc78"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232093,"upload-time":"2019-11-25T12:30:53.306595Z","url":"https://files.pythonhosted.org/packages/76/4d/6452c4791f9d95b48cca084b7cc6aa8a72b2f4c8d3d8bd38e7f3abfaf364/psutil-5.6.6-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"data-dist-info-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"filename":"psutil-5.6.6-cp35-cp35m-win32.whl","hashes":{"sha256":"5e8dbf31871b0072bcba8d1f2861c0ec6c84c78f13c723bb6e981bce51b58f12"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232818,"upload-time":"2019-11-25T12:30:58.414546Z","url":"https://files.pythonhosted.org/packages/f3/b3/e585b9b0c5a40e6a778e32e8d3040ab2363433990417202bf6cc0261ed77/psutil-5.6.6-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"data-dist-info-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"filename":"psutil-5.6.6-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"724390895cff80add7a1c4e7e0a04d9c94f3ee61423a2dcafd83784fabbd1ee9"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236751,"upload-time":"2019-11-25T12:31:03.059024Z","url":"https://files.pythonhosted.org/packages/73/38/a8ebf6dc6ada2257591284be45a52dcd19479163b8d3575186333a79a18e/psutil-5.6.6-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"data-dist-info-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"filename":"psutil-5.6.6-cp36-cp36m-win32.whl","hashes":{"sha256":"6d81b9714791ef9a3a00b2ca846ee547fc5e53d259e2a6258c3d2054928039ff"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232821,"upload-time":"2019-11-25T12:31:08.161481Z","url":"https://files.pythonhosted.org/packages/5b/04/2223b4fe61d3e5962c08ce5062b09633fcfdd8c3bb08c31b76306f748431/psutil-5.6.6-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"data-dist-info-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"filename":"psutil-5.6.6-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"3004361c6b93dbad71330d992c1ae409cb8314a6041a0b67507cc882357f583e"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236759,"upload-time":"2019-11-25T12:31:13.077581Z","url":"https://files.pythonhosted.org/packages/33/6c/6eb959bca82064d42e725dddd3aeeb39d9bed34eed7b513880bcfd8a3d59/psutil-5.6.6-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"data-dist-info-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"filename":"psutil-5.6.6-cp37-cp37m-win32.whl","hashes":{"sha256":"0fc7a5619b47f74331add476fbc6022d7ca801c22865c7069ec0867920858963"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232820,"upload-time":"2019-11-25T12:31:17.411233Z","url":"https://files.pythonhosted.org/packages/76/46/3a8dc20eb9d7d2c44178e71c2412dcc2c6476ab71c05a2cf2f77247c6e53/psutil-5.6.6-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"data-dist-info-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"filename":"psutil-5.6.6-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"f60042bef7dc50a78c06334ca8e25580455948ba2fa98f240d034a4fed9141a5"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236757,"upload-time":"2019-11-25T12:31:21.816790Z","url":"https://files.pythonhosted.org/packages/40/48/5debf9783077ac71f0d715c1171fde9ef287909f61672ed9a3e5fdca63cc/psutil-5.6.6-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"data-dist-info-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"filename":"psutil-5.6.6-cp38-cp38-win32.whl","hashes":{"sha256":"0c11adde31011a286197630ba2671e34651f004cc418d30ae06d2033a43c9e20"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":233147,"upload-time":"2019-11-25T12:31:26.294340Z","url":"https://files.pythonhosted.org/packages/c6/10/035c432a15bec90d51f3625d4b70b7d12f1b363062d0d8815213229f69ca/psutil-5.6.6-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"data-dist-info-metadata":{"sha256":"29b50729d8b20274ad25b9f22deb812c81cdc56e61cba32bbf7c5423b9c1ef5d"},"filename":"psutil-5.6.6-cp38-cp38-win_amd64.whl","hashes":{"sha256":"0c211eec4185725847cb6c28409646c7cfa56fdb531014b35f97b5dc7fe04ff9"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":237165,"upload-time":"2019-11-25T12:31:30.835876Z","url":"https://files.pythonhosted.org/packages/95/81/fb02ea8de73eca26cfa347f5ce81a7963b9dee6e038e0a7389ccbc971093/psutil-5.6.6-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.6.6.tar.gz","hashes":{"sha256":"ad21281f7bd6c57578dd53913d2d44218e9e29fd25128d10ff7819ef16fa46e7"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":447805,"upload-time":"2019-11-25T12:31:36.347718Z","url":"https://files.pythonhosted.org/packages/5f/dc/edf6758183afc7591a16bd4b8a44d8eea80aca1327ea60161dd3bad9ad22/psutil-5.6.6.tar.gz","yanked":false},{"core-metadata":{"sha256":"943b5895bc5955d1ab23e61bd0a4a6c4a4be1be625fd6bb204033b5d93574bf6"},"data-dist-info-metadata":{"sha256":"943b5895bc5955d1ab23e61bd0a4a6c4a4be1be625fd6bb204033b5d93574bf6"},"filename":"psutil-5.6.7-cp27-none-win32.whl","hashes":{"sha256":"1b1575240ca9a90b437e5a40db662acd87bbf181f6aa02f0204978737b913c6b"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":228449,"upload-time":"2019-11-26T07:25:43.864472Z","url":"https://files.pythonhosted.org/packages/13/a7/626f257d22168c954fd3ad69760c02bdec27c0648a62f6ea5060c4d40672/psutil-5.6.7-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"943b5895bc5955d1ab23e61bd0a4a6c4a4be1be625fd6bb204033b5d93574bf6"},"data-dist-info-metadata":{"sha256":"943b5895bc5955d1ab23e61bd0a4a6c4a4be1be625fd6bb204033b5d93574bf6"},"filename":"psutil-5.6.7-cp27-none-win_amd64.whl","hashes":{"sha256":"28f771129bfee9fc6b63d83a15d857663bbdcae3828e1cb926e91320a9b5b5cd"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":231969,"upload-time":"2019-11-26T07:25:50.426501Z","url":"https://files.pythonhosted.org/packages/52/44/e1e1954da522ea8640e035c8b101c116a9f8a0e94e04e108e56911064de5/psutil-5.6.7-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"data-dist-info-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"filename":"psutil-5.6.7-cp35-cp35m-win32.whl","hashes":{"sha256":"21231ef1c1a89728e29b98a885b8e0a8e00d09018f6da5cdc1f43f988471a995"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232644,"upload-time":"2019-11-26T07:25:56.230501Z","url":"https://files.pythonhosted.org/packages/d8/39/bf74da6282d9521fe3987b2d67f581b3464e635e6cb56660d0315d1bf1ed/psutil-5.6.7-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"data-dist-info-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"filename":"psutil-5.6.7-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"b74b43fecce384a57094a83d2778cdfc2e2d9a6afaadd1ebecb2e75e0d34e10d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236538,"upload-time":"2019-11-26T07:26:01.396655Z","url":"https://files.pythonhosted.org/packages/b3/84/0a899c6fac13aedfb4734413a2357a504c4f21cbf8acd7ad4caa6712d8cf/psutil-5.6.7-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"data-dist-info-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"filename":"psutil-5.6.7-cp36-cp36m-win32.whl","hashes":{"sha256":"e85f727ffb21539849e6012f47b12f6dd4c44965e56591d8dec6e8bc9ab96f4a"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232649,"upload-time":"2019-11-26T07:26:06.727306Z","url":"https://files.pythonhosted.org/packages/7c/d7/be2b607abfab4a98f04dd2155d6a7a40a666618d69c079897f09ce776a34/psutil-5.6.7-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"data-dist-info-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"filename":"psutil-5.6.7-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"b560f5cd86cf8df7bcd258a851ca1ad98f0d5b8b98748e877a0aec4e9032b465"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236541,"upload-time":"2019-11-26T07:26:11.229459Z","url":"https://files.pythonhosted.org/packages/78/e6/ce8a91afd605f254342f1294790f2a77c76202386d6927eb5ff0e36e4449/psutil-5.6.7-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"data-dist-info-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"filename":"psutil-5.6.7-cp37-cp37m-win32.whl","hashes":{"sha256":"094f899ac3ef72422b7e00411b4ed174e3c5a2e04c267db6643937ddba67a05b"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232645,"upload-time":"2019-11-26T07:26:15.814275Z","url":"https://files.pythonhosted.org/packages/c4/e1/80c7840db569ad5b1b60987893e066a5536779c0d2402363cbf1230613a2/psutil-5.6.7-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"data-dist-info-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"filename":"psutil-5.6.7-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"fd2e09bb593ad9bdd7429e779699d2d47c1268cbde4dda95fcd1bd17544a0217"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236539,"upload-time":"2019-11-26T07:26:20.217732Z","url":"https://files.pythonhosted.org/packages/9d/84/0a2006cc263e9f5b6dfbb2301fbcce5558f0d6d17d0c11c7c6749a45c79e/psutil-5.6.7-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"data-dist-info-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"filename":"psutil-5.6.7-cp38-cp38-win32.whl","hashes":{"sha256":"70387772f84fa5c3bb6a106915a2445e20ac8f9821c5914d7cbde148f4d7ff73"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232983,"upload-time":"2019-11-26T07:26:24.615122Z","url":"https://files.pythonhosted.org/packages/55/9d/9a6df5f730a1e2a3938fad0ccf541b30fad34706128b43ed3f965eaf7550/psutil-5.6.7-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"data-dist-info-metadata":{"sha256":"15f7352602bc6ef07ec1f8cc5816d4cee0171e750a5d38106a7277f703882211"},"filename":"psutil-5.6.7-cp38-cp38-win_amd64.whl","hashes":{"sha256":"10b7f75cc8bd676cfc6fa40cd7d5c25b3f45a0e06d43becd7c2d2871cbb5e806"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236965,"upload-time":"2019-11-26T07:26:28.827408Z","url":"https://files.pythonhosted.org/packages/8a/fa/b573850e912d6ffdad4aef3f5f705f94a64d098a83eec15d1cd3e1223f5e/psutil-5.6.7-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.6.7.tar.gz","hashes":{"sha256":"ffad8eb2ac614518bbe3c0b8eb9dffdb3a8d2e3a7d5da51c5b974fb723a5c5aa"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":448321,"upload-time":"2019-11-26T07:26:34.515073Z","url":"https://files.pythonhosted.org/packages/73/93/4f8213fbe66fc20cb904f35e6e04e20b47b85bee39845cc66a0bcf5ccdcb/psutil-5.6.7.tar.gz","yanked":false},{"core-metadata":{"sha256":"4665f6ffe5781ebc50d69efc5363ace07e50d1916f179a1cd2e51352df7eb11a"},"data-dist-info-metadata":{"sha256":"4665f6ffe5781ebc50d69efc5363ace07e50d1916f179a1cd2e51352df7eb11a"},"filename":"psutil-5.7.0-cp27-none-win32.whl","hashes":{"sha256":"298af2f14b635c3c7118fd9183843f4e73e681bb6f01e12284d4d70d48a60953"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":227641,"upload-time":"2020-02-18T18:02:31.085536Z","url":"https://files.pythonhosted.org/packages/0b/6b/f613593812c5f379c6d609bf5eca36a409812f508e13c704acd25712a73e/psutil-5.7.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"4665f6ffe5781ebc50d69efc5363ace07e50d1916f179a1cd2e51352df7eb11a"},"data-dist-info-metadata":{"sha256":"4665f6ffe5781ebc50d69efc5363ace07e50d1916f179a1cd2e51352df7eb11a"},"filename":"psutil-5.7.0-cp27-none-win_amd64.whl","hashes":{"sha256":"75e22717d4dbc7ca529ec5063000b2b294fc9a367f9c9ede1f65846c7955fd38"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":230741,"upload-time":"2020-02-18T18:02:35.453910Z","url":"https://files.pythonhosted.org/packages/79/b1/377fa0f28630d855cb6b5bfb2ee4c1bf0df3bc2603c691ceefce59a95181/psutil-5.7.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"data-dist-info-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"filename":"psutil-5.7.0-cp35-cp35m-win32.whl","hashes":{"sha256":"f344ca230dd8e8d5eee16827596f1c22ec0876127c28e800d7ae20ed44c4b310"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":231151,"upload-time":"2020-02-18T18:02:39.410543Z","url":"https://files.pythonhosted.org/packages/74/e6/4a0ef10b1a4ca43954cd8fd9eac02cc8606f9d2a5a66859a283f5f95452b/psutil-5.7.0-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"data-dist-info-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"filename":"psutil-5.7.0-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"e2d0c5b07c6fe5a87fa27b7855017edb0d52ee73b71e6ee368fae268605cc3f5"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":235435,"upload-time":"2020-02-18T18:02:43.014979Z","url":"https://files.pythonhosted.org/packages/65/c2/0aeb9f0cc7e4be2807aa052b3fd017e59439ed6d830b461f8ecb35b2f367/psutil-5.7.0-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"data-dist-info-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"filename":"psutil-5.7.0-cp36-cp36m-win32.whl","hashes":{"sha256":"a02f4ac50d4a23253b68233b07e7cdb567bd025b982d5cf0ee78296990c22d9e"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":231156,"upload-time":"2020-02-18T18:02:46.432233Z","url":"https://files.pythonhosted.org/packages/c9/37/b94930ae428b2d67d505aecc5ba84c53a0b75479a8a87cd35cc9a2c6eb7e/psutil-5.7.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"data-dist-info-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"filename":"psutil-5.7.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"1413f4158eb50e110777c4f15d7c759521703bd6beb58926f1d562da40180058"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":235439,"upload-time":"2020-02-18T18:02:49.890797Z","url":"https://files.pythonhosted.org/packages/4f/3c/205850b172a14a8b9fdc9b1e84a2c055d6b9aea226431da7685bea644f04/psutil-5.7.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"data-dist-info-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"filename":"psutil-5.7.0-cp37-cp37m-win32.whl","hashes":{"sha256":"d008ddc00c6906ec80040d26dc2d3e3962109e40ad07fd8a12d0284ce5e0e4f8"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":231153,"upload-time":"2020-02-18T18:02:53.653943Z","url":"https://files.pythonhosted.org/packages/54/25/7825fefd62635f7ca556c8e0d44369ce4674aa2ca0eca50b8ae4ff49954b/psutil-5.7.0-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"data-dist-info-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"filename":"psutil-5.7.0-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"73f35ab66c6c7a9ce82ba44b1e9b1050be2a80cd4dcc3352cc108656b115c74f"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":235439,"upload-time":"2020-02-18T18:02:57.139440Z","url":"https://files.pythonhosted.org/packages/86/f7/385040b90dd190edc28908c4a26af99b00ae37564ee5f5c4526dc1d80c27/psutil-5.7.0-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"data-dist-info-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"filename":"psutil-5.7.0-cp38-cp38-win32.whl","hashes":{"sha256":"60b86f327c198561f101a92be1995f9ae0399736b6eced8f24af41ec64fb88d4"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":231789,"upload-time":"2020-02-18T18:03:00.808478Z","url":"https://files.pythonhosted.org/packages/63/d5/f34a9433a0299d944605fb5a970306a89e076f5412164179dc59ebf70fa9/psutil-5.7.0-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"data-dist-info-metadata":{"sha256":"9743d37444bb906e26ad0d76922bd72314c8385e19a68cb8a763a3e70859b867"},"filename":"psutil-5.7.0-cp38-cp38-win_amd64.whl","hashes":{"sha256":"d84029b190c8a66a946e28b4d3934d2ca1528ec94764b180f7d6ea57b0e75e26"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":235952,"upload-time":"2020-02-18T18:03:04.130414Z","url":"https://files.pythonhosted.org/packages/86/fe/9f1d1f8c1c8138d42fc0e7c06ca5004e01f38e86e61342374d8e0fa919e4/psutil-5.7.0-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.7.0.tar.gz","hashes":{"sha256":"685ec16ca14d079455892f25bd124df26ff9137664af445563c1bd36629b5e0e"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":449628,"upload-time":"2020-02-18T18:03:07.566242Z","url":"https://files.pythonhosted.org/packages/c4/b8/3512f0e93e0db23a71d82485ba256071ebef99b227351f0f5540f744af41/psutil-5.7.0.tar.gz","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp27-cp27m-macosx_10_9_x86_64.whl","hashes":{"sha256":"2dfb7b5638ffaa33602a86b39cca60cded2324dabbe2617b1b5e65250e448769"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":233646,"upload-time":"2020-07-15T11:14:11.635539Z","url":"https://files.pythonhosted.org/packages/79/e4/cbaa3ecc458c2dd8da64073de983473543b8b6ef4ca21159cea9069d53dd/psutil-5.7.1-cp27-cp27m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp35-cp35m-macosx_10_9_x86_64.whl","hashes":{"sha256":"36c5e6882caf3d385c6c3a0d2f3b302b4cc337c808ea589d9a8c563b545beb8b"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":233862,"upload-time":"2020-07-15T11:14:27.607855Z","url":"https://files.pythonhosted.org/packages/4d/d9/48c3d16c1dfbbf528bd69254b5a604c9f6860f12169b1b73a5005723c6bf/psutil-5.7.1-cp35-cp35m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp35-cp35m-win32.whl","hashes":{"sha256":"3c5ffd00bc1ee809350dca97613985d387a7e13dff61d62fc1bdf4dc10892ddd"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238370,"upload-time":"2020-07-15T11:14:34.746888Z","url":"https://files.pythonhosted.org/packages/d0/e2/d4cdadda6a9fba79026ab628fc2b4da5e2e48dcfc6beada0a39363732ba1/psutil-5.7.1-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"4975c33aebe7de191d745ee3c545e907edd14d65c850a0b185c05024aa77cbcd"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242862,"upload-time":"2020-07-15T11:14:37.084757Z","url":"https://files.pythonhosted.org/packages/a0/6b/cdb41805a6bb62c051cfbb1b65a9cb40767e0144b3d40fdd7082d8271701/psutil-5.7.1-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp36-cp36m-macosx_10_9_x86_64.whl","hashes":{"sha256":"436a6e99098eba14b54a149f921c9d4e1df729f02645876af0c828396d36c46a"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":233861,"upload-time":"2020-07-15T11:14:39.242780Z","url":"https://files.pythonhosted.org/packages/1f/fb/097aeed40c361225cb69d6d04202421f2c172d7e42753130d1b619f68956/psutil-5.7.1-cp36-cp36m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp36-cp36m-win32.whl","hashes":{"sha256":"630ceda48c16b24ffd981fe06ae1a43684af1a3a837d6a3496a1be3dd3c7d332"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238378,"upload-time":"2020-07-15T11:14:45.606142Z","url":"https://files.pythonhosted.org/packages/ae/49/cba9353fd9946eac95031c85763daaf7904d3c3e8b0b4f2801199586b413/psutil-5.7.1-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"d3bb7f65199595a72a3ec53e4d05c159857ab832fadaae9d85e68db467d2d191"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242864,"upload-time":"2020-07-15T11:14:47.861971Z","url":"https://files.pythonhosted.org/packages/7b/38/f27fc6a30f81be1ee657bd4c355c2dc03a5fbb49f304a37c79c0bed05821/psutil-5.7.1-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp37-cp37m-macosx_10_9_x86_64.whl","hashes":{"sha256":"66d085317599684f70d995dd4a770894f518fb34d027d7f742b579bf47732858"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":233863,"upload-time":"2020-07-15T11:14:49.991128Z","url":"https://files.pythonhosted.org/packages/23/cb/410a516385c8cd69f090f98c8014636c51d124c96e4d6ab51e1bb2d04232/psutil-5.7.1-cp37-cp37m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp37-cp37m-win32.whl","hashes":{"sha256":"fb442b912fe28d80e0f966adcc3df4e394fbb7ef7575ae21fd171aeb06c8b0df"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238369,"upload-time":"2020-07-15T11:14:57.383010Z","url":"https://files.pythonhosted.org/packages/59/44/e9cfa470dd2790b5475ceb590949842a5f2feb52445e898576b721033f04/psutil-5.7.1-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"425d6c95ca3ece7ff4da7e67af2954b8eb56b0f15743b237dc84ad975f51c2a4"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242865,"upload-time":"2020-07-15T11:14:59.839217Z","url":"https://files.pythonhosted.org/packages/06/76/b4607e0eaf36369ad86f7ac73bde19aeaf32c82fb22675cb8f8dd975c692/psutil-5.7.1-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp38-cp38-macosx_10_9_x86_64.whl","hashes":{"sha256":"f2817a763c33c19fdefbb832c790bc85b3de90b51fb69dae43097a9885be0332"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":234129,"upload-time":"2020-07-15T11:15:01.982258Z","url":"https://files.pythonhosted.org/packages/ba/f7/64bf7fd7a12a40c50408b7d90cdf3addc28071e5463af1dbb7f3884a32d2/psutil-5.7.1-cp38-cp38-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp38-cp38-win32.whl","hashes":{"sha256":"3cf43d2265ee03fcf70f0f574487ed19435c92a330e15a3e773144811c1275f0"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239004,"upload-time":"2020-07-15T11:15:08.125783Z","url":"https://files.pythonhosted.org/packages/7e/64/c3bd24d53f6056ded095e8d147c0ca269bb6d858aea903561bd660d67035/psutil-5.7.1-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"data-dist-info-metadata":{"sha256":"5cfd93088b3a9182d5543595d138ec1f3a9f0fee62e4e1dcbc3332900a0871ea"},"filename":"psutil-5.7.1-cp38-cp38-win_amd64.whl","hashes":{"sha256":"006b720a67881037c8b02b1de012a39a2f007bd2b1b244b58fabef8eff0ad6d2"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243418,"upload-time":"2020-07-15T11:15:10.044651Z","url":"https://files.pythonhosted.org/packages/ff/5a/1e990cf86f47721225143ed4a903a226685fa1ba0b43500f52d71500b1be/psutil-5.7.1-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"621e2f3b573aa563717ee39c3f052b4cd3cda5e1f25e5973fa77e727433c6ce1"},"data-dist-info-metadata":{"sha256":"621e2f3b573aa563717ee39c3f052b4cd3cda5e1f25e5973fa77e727433c6ce1"},"filename":"psutil-5.7.1-pp27-pypy_73-macosx_10_9_x86_64.whl","hashes":{"sha256":"0c9187ec0c314a128362c3409afea2b80c6d6d2c2cb1d661fe20631a2ff8ad77"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":232076,"upload-time":"2020-07-15T11:15:12.408818Z","url":"https://files.pythonhosted.org/packages/80/fd/d91ff7582513d093097678eedd141a4879698da26e6163fbae16905aa75b/psutil-5.7.1-pp27-pypy_73-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.7.1.tar.gz","hashes":{"sha256":"4ef6845b35e152e6937d4f28388c2440ca89a0089ced0a30a116fa3ceefdfa3a"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":460148,"upload-time":"2020-07-15T11:15:21.985314Z","url":"https://files.pythonhosted.org/packages/2c/5c/cb95a715fb635e1ca858ffb8c50a523a16e2dc06aa3e207ab73cb93516af/psutil-5.7.1.tar.gz","yanked":false},{"core-metadata":{"sha256":"91f2a99bfe758ed3b22b8fc235d7bfa5b885bb5cdd7bd1a38cccff1e9756d183"},"data-dist-info-metadata":{"sha256":"91f2a99bfe758ed3b22b8fc235d7bfa5b885bb5cdd7bd1a38cccff1e9756d183"},"filename":"psutil-5.7.2-cp27-none-win32.whl","hashes":{"sha256":"f2018461733b23f308c298653c8903d32aaad7873d25e1d228765e91ae42c3f2"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":234782,"upload-time":"2020-07-15T13:19:05.321836Z","url":"https://files.pythonhosted.org/packages/d0/da/d7da0365f690e7555f6dda34bcb5bde10266379c9a23ee6a0735c3a7fdfd/psutil-5.7.2-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"91f2a99bfe758ed3b22b8fc235d7bfa5b885bb5cdd7bd1a38cccff1e9756d183"},"data-dist-info-metadata":{"sha256":"91f2a99bfe758ed3b22b8fc235d7bfa5b885bb5cdd7bd1a38cccff1e9756d183"},"filename":"psutil-5.7.2-cp27-none-win_amd64.whl","hashes":{"sha256":"66c18ca7680a31bf16ee22b1d21b6397869dda8059dbdb57d9f27efa6615f195"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238050,"upload-time":"2020-07-15T13:19:07.922423Z","url":"https://files.pythonhosted.org/packages/7d/9d/30a053a06d598cee4bdbc6ba69df44ced9e6d2ebb16e2de401a2a3bc6d63/psutil-5.7.2-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"data-dist-info-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"filename":"psutil-5.7.2-cp35-cp35m-win32.whl","hashes":{"sha256":"5e9d0f26d4194479a13d5f4b3798260c20cecf9ac9a461e718eb59ea520a360c"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238373,"upload-time":"2020-07-15T13:19:10.146779Z","url":"https://files.pythonhosted.org/packages/df/27/e5cf14b0894b4f06c23dc4f58288c60b17e71d8bef9af463f0b32ee46773/psutil-5.7.2-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"data-dist-info-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"filename":"psutil-5.7.2-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"4080869ed93cce662905b029a1770fe89c98787e543fa7347f075ade761b19d6"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242862,"upload-time":"2020-07-15T13:19:12.906781Z","url":"https://files.pythonhosted.org/packages/0a/4c/d31d58992314664e69bda6d575c1fd47b86ed5d67e00e300fc909040a9aa/psutil-5.7.2-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"data-dist-info-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"filename":"psutil-5.7.2-cp36-cp36m-win32.whl","hashes":{"sha256":"d8a82162f23c53b8525cf5f14a355f5d1eea86fa8edde27287dd3a98399e4fdf"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238380,"upload-time":"2020-07-15T13:19:15.247084Z","url":"https://files.pythonhosted.org/packages/57/c5/0aa3b1513b914a417db7ee149b60579a139111f81f79f5f1d38ae440cebf/psutil-5.7.2-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"data-dist-info-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"filename":"psutil-5.7.2-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"0ee3c36428f160d2d8fce3c583a0353e848abb7de9732c50cf3356dd49ad63f8"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242866,"upload-time":"2020-07-15T13:19:17.782781Z","url":"https://files.pythonhosted.org/packages/22/f8/7be159475303a508347efc82c0d5858b1786fe73fc2a6b21d82891791920/psutil-5.7.2-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"data-dist-info-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"filename":"psutil-5.7.2-cp37-cp37m-win32.whl","hashes":{"sha256":"ff1977ba1a5f71f89166d5145c3da1cea89a0fdb044075a12c720ee9123ec818"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238375,"upload-time":"2020-07-15T13:19:20.591814Z","url":"https://files.pythonhosted.org/packages/56/de/6f07749f275d0ba7f9b985cd6a4526e2fa47ad63b5179948c6650117f7d9/psutil-5.7.2-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"data-dist-info-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"filename":"psutil-5.7.2-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"a5b120bb3c0c71dfe27551f9da2f3209a8257a178ed6c628a819037a8df487f1"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242863,"upload-time":"2020-07-15T13:19:23.022907Z","url":"https://files.pythonhosted.org/packages/f8/9b/1d7df5e1747e047abef4ec877d895b642f3a796ab8bd2e0f682516740dfe/psutil-5.7.2-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"data-dist-info-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"filename":"psutil-5.7.2-cp38-cp38-win32.whl","hashes":{"sha256":"10512b46c95b02842c225f58fa00385c08fa00c68bac7da2d9a58ebe2c517498"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239003,"upload-time":"2020-07-15T13:19:25.770960Z","url":"https://files.pythonhosted.org/packages/da/d6/f66bbdbc8831a5cc78ba0e9bf69d924e68eac1a7b4191de93cf4e3643c54/psutil-5.7.2-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"data-dist-info-metadata":{"sha256":"4ece4267ea2940759bedb71b133199d6c5b33ccc17f4c3060d08d56f5697ca45"},"filename":"psutil-5.7.2-cp38-cp38-win_amd64.whl","hashes":{"sha256":"68d36986ded5dac7c2dcd42f2682af1db80d4bce3faa126a6145c1637e1b559f"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243419,"upload-time":"2020-07-15T13:19:28.240707Z","url":"https://files.pythonhosted.org/packages/6c/e6/f963547a36a96f74244cbe5e4046a02f140e3b7cbc5e5176035b38e2deb2/psutil-5.7.2-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.7.2.tar.gz","hashes":{"sha256":"90990af1c3c67195c44c9a889184f84f5b2320dce3ee3acbd054e3ba0b4a7beb"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":460198,"upload-time":"2020-07-15T13:19:30.438785Z","url":"https://files.pythonhosted.org/packages/aa/3e/d18f2c04cf2b528e18515999b0c8e698c136db78f62df34eee89cee205f1/psutil-5.7.2.tar.gz","yanked":false},{"core-metadata":{"sha256":"6032661358cee4f792940fdac32ebc65bab2fa2463828785b8a8f0fb6c2c210b"},"data-dist-info-metadata":{"sha256":"6032661358cee4f792940fdac32ebc65bab2fa2463828785b8a8f0fb6c2c210b"},"filename":"psutil-5.7.3-cp27-none-win32.whl","hashes":{"sha256":"1cd6a0c9fb35ece2ccf2d1dd733c1e165b342604c67454fd56a4c12e0a106787"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":235225,"upload-time":"2020-10-24T14:00:54.139761Z","url":"https://files.pythonhosted.org/packages/1d/a2/b732590561ef9d7dbc078ed0e2635e282115604a478911fef97ddaa3ad43/psutil-5.7.3-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"6032661358cee4f792940fdac32ebc65bab2fa2463828785b8a8f0fb6c2c210b"},"data-dist-info-metadata":{"sha256":"6032661358cee4f792940fdac32ebc65bab2fa2463828785b8a8f0fb6c2c210b"},"filename":"psutil-5.7.3-cp27-none-win_amd64.whl","hashes":{"sha256":"e02c31b2990dcd2431f4524b93491941df39f99619b0d312dfe1d4d530b08b4b"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238499,"upload-time":"2020-10-24T14:00:57.724543Z","url":"https://files.pythonhosted.org/packages/b4/4c/c14a9485957b00c20f70e208a03663e81ddc8dafdf5137fee2d50aa1ee5e/psutil-5.7.3-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"data-dist-info-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"filename":"psutil-5.7.3-cp35-cp35m-win32.whl","hashes":{"sha256":"56c85120fa173a5d2ad1d15a0c6e0ae62b388bfb956bb036ac231fbdaf9e4c22"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238813,"upload-time":"2020-10-24T15:02:21.678008Z","url":"https://files.pythonhosted.org/packages/e0/ba/a7c1096470e11c449019690ee9e7fd3adca1a4b9cfa6e5a13b60db3187b4/psutil-5.7.3-cp35-cp35m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"data-dist-info-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"filename":"psutil-5.7.3-cp35-cp35m-win_amd64.whl","hashes":{"sha256":"fa38ac15dbf161ab1e941ff4ce39abd64b53fec5ddf60c23290daed2bc7d1157"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243298,"upload-time":"2020-10-24T15:02:24.823245Z","url":"https://files.pythonhosted.org/packages/3f/7c/98aada1208462c841788712383f4288b3c31e45504570a818c0c303d78e7/psutil-5.7.3-cp35-cp35m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"data-dist-info-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"filename":"psutil-5.7.3-cp36-cp36m-win32.whl","hashes":{"sha256":"01bc82813fbc3ea304914581954979e637bcc7084e59ac904d870d6eb8bb2bc7"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238821,"upload-time":"2020-10-24T15:02:27.844759Z","url":"https://files.pythonhosted.org/packages/44/a8/ebfcbb4967e74a27049ea6e13b3027ae05c0cb73d1a2b71c2f0519c6d5f2/psutil-5.7.3-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"data-dist-info-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"filename":"psutil-5.7.3-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"6a3e1fd2800ca45083d976b5478a2402dd62afdfb719b30ca46cd28bb25a2eb4"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243300,"upload-time":"2020-10-24T15:02:31.552663Z","url":"https://files.pythonhosted.org/packages/9a/c3/3b0023b46fc038eff02fbb69a0e6e50d15a7dce25e717d8469e8eaa837a7/psutil-5.7.3-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"data-dist-info-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"filename":"psutil-5.7.3-cp37-cp37m-win32.whl","hashes":{"sha256":"fbcac492cb082fa38d88587d75feb90785d05d7e12d4565cbf1ecc727aff71b7"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238814,"upload-time":"2020-10-24T15:02:34.703606Z","url":"https://files.pythonhosted.org/packages/e1/f0/d4f58ddf077d970440b82b92e909e8e9b2f50e39a2dc2aa716b1e2fde5ef/psutil-5.7.3-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"data-dist-info-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"filename":"psutil-5.7.3-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"5d9106ff5ec2712e2f659ebbd112967f44e7d33f40ba40530c485cc5904360b8"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243302,"upload-time":"2020-10-24T15:02:38.016752Z","url":"https://files.pythonhosted.org/packages/f1/a0/094a6e32185bd1288a4681d91ebe362d5b41aa64413bbbd96ed547051f17/psutil-5.7.3-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"data-dist-info-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"filename":"psutil-5.7.3-cp38-cp38-win32.whl","hashes":{"sha256":"ade6af32eb80a536eff162d799e31b7ef92ddcda707c27bbd077238065018df4"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239426,"upload-time":"2020-10-24T15:02:41.250786Z","url":"https://files.pythonhosted.org/packages/bd/95/394485321a128f5ddb23f0a559f940309280f57bd6117580868fb2d5a246/psutil-5.7.3-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"data-dist-info-metadata":{"sha256":"9f3f41a734ced0a9775d53996e940b0d2411f0f5105b0cdcac5e8e3ffd8da0e9"},"filename":"psutil-5.7.3-cp38-cp38-win_amd64.whl","hashes":{"sha256":"2cb55ef9591b03ef0104bedf67cc4edb38a3edf015cf8cf24007b99cb8497542"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243849,"upload-time":"2020-10-24T15:02:44.162783Z","url":"https://files.pythonhosted.org/packages/df/64/8d7b55ac87e67398ffc260d43a5fb327f1e230b09758b7d8caaecf917dd6/psutil-5.7.3-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.7.3.tar.gz","hashes":{"sha256":"af73f7bcebdc538eda9cc81d19db1db7bf26f103f91081d780bbacfcb620dee2"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":465556,"upload-time":"2020-10-24T14:02:15.604830Z","url":"https://files.pythonhosted.org/packages/33/e0/82d459af36bda999f82c7ea86c67610591cf5556168f48fd6509e5fa154d/psutil-5.7.3.tar.gz","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl","hashes":{"sha256":"0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":235772,"upload-time":"2020-12-19T01:19:27.017908Z","url":"https://files.pythonhosted.org/packages/f5/7f/a2559a514bdeb2a33e4bf3dc3d2bb17d5acded718893869a82536130cfb3/psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":284461,"upload-time":"2020-12-19T01:19:30.158384Z","url":"https://files.pythonhosted.org/packages/19/2c/9f1bad783faee4e9704868f381913e68dbb69f0de3fcdc71ee7071c47847/psutil-5.8.0-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":287768,"upload-time":"2020-12-19T01:19:33.394809Z","url":"https://files.pythonhosted.org/packages/82/0a/eddb9a51ba5055cc7c242da07c1643a6b146070740c5eb5540277a0f01f4/psutil-5.8.0-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":284476,"upload-time":"2020-12-19T01:19:36.479406Z","url":"https://files.pythonhosted.org/packages/15/28/47c28171fd7eeb83df74f78ccac090211f4a49408f376eb8e78a7bb47dc0/psutil-5.8.0-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":287754,"upload-time":"2020-12-19T01:19:39.219693Z","url":"https://files.pythonhosted.org/packages/26/ef/461e9eec56fba7fa66692c4af00cbd6547b788a7ca818d9b8b5f1951f228/psutil-5.8.0-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"91c5c4b49dc8b3365ce65ca99e7cee1cf8d862d4379fcc384b1711d4600dd869"},"data-dist-info-metadata":{"sha256":"91c5c4b49dc8b3365ce65ca99e7cee1cf8d862d4379fcc384b1711d4600dd869"},"filename":"psutil-5.8.0-cp27-none-win32.whl","hashes":{"sha256":"ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236561,"upload-time":"2020-12-19T01:19:41.916204Z","url":"https://files.pythonhosted.org/packages/a8/b3/6a21c5b7e4f600bd6eaaecd4a5e76230fa34876e48cbc87b2cef0ab91c0a/psutil-5.8.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"91c5c4b49dc8b3365ce65ca99e7cee1cf8d862d4379fcc384b1711d4600dd869"},"data-dist-info-metadata":{"sha256":"91c5c4b49dc8b3365ce65ca99e7cee1cf8d862d4379fcc384b1711d4600dd869"},"filename":"psutil-5.8.0-cp27-none-win_amd64.whl","hashes":{"sha256":"5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239900,"upload-time":"2020-12-19T01:19:45.046259Z","url":"https://files.pythonhosted.org/packages/b2/3d/01ef1f4bf71413078bf2ce2aae04d47bc132cfede58738183a9de41aa122/psutil-5.8.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp36-cp36m-macosx_10_9_x86_64.whl","hashes":{"sha256":"74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236013,"upload-time":"2020-12-19T01:19:47.734642Z","url":"https://files.pythonhosted.org/packages/30/81/37ebe0ba2840b76681072e786bae3319cade8a6861029d0ae885c274fa0b/psutil-5.8.0-cp36-cp36m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp36-cp36m-manylinux2010_i686.whl","hashes":{"sha256":"74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":289247,"upload-time":"2020-12-19T01:19:50.429849Z","url":"https://files.pythonhosted.org/packages/2e/7c/13a6c3f068aa39ffafd99ae159c1a345521e7dd0074ccadb917e5670dbdc/psutil-5.8.0-cp36-cp36m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp36-cp36m-manylinux2010_x86_64.whl","hashes":{"sha256":"99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":291875,"upload-time":"2020-12-19T01:19:53.450222Z","url":"https://files.pythonhosted.org/packages/da/82/56cd16a4c5f53e3e5dd7b2c30d5c803e124f218ebb644ca9c30bc907eadd/psutil-5.8.0-cp36-cp36m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp36-cp36m-win32.whl","hashes":{"sha256":"36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":240337,"upload-time":"2020-12-19T01:19:56.748668Z","url":"https://files.pythonhosted.org/packages/19/29/f7a38ee30083f2caa14cc77a6d34c4d5cfd1a69641e87bf1b3d6ba90d0ba/psutil-5.8.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":244835,"upload-time":"2020-12-19T01:19:59.102544Z","url":"https://files.pythonhosted.org/packages/44/ed/49d75a29007727d44937ed4d233f116be346bc4657a83b5a9e2f423bca57/psutil-5.8.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp37-cp37m-macosx_10_9_x86_64.whl","hashes":{"sha256":"c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236013,"upload-time":"2020-12-19T01:20:02.260367Z","url":"https://files.pythonhosted.org/packages/fe/19/83ab423a7b69cafe4078dea751acdff7377e4b59c71e3718125ba3c341f9/psutil-5.8.0-cp37-cp37m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp37-cp37m-manylinux2010_i686.whl","hashes":{"sha256":"61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":290299,"upload-time":"2020-12-19T01:20:04.653817Z","url":"https://files.pythonhosted.org/packages/cc/5f/2a1967092086acc647962168d0e6fd1c22e14a973f03e3ffb1e2f0da5de9/psutil-5.8.0-cp37-cp37m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp37-cp37m-manylinux2010_x86_64.whl","hashes":{"sha256":"0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":296329,"upload-time":"2020-12-19T01:20:07.174230Z","url":"https://files.pythonhosted.org/packages/84/da/f7efdcf012b51506938553dbe302aecc22f3f43abd5cffa8320e8e0588d5/psutil-5.8.0-cp37-cp37m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp37-cp37m-win32.whl","hashes":{"sha256":"1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":240337,"upload-time":"2020-12-19T01:20:10.138302Z","url":"https://files.pythonhosted.org/packages/18/c9/1db6aa0d28831f60408a6aab9d108c2edbd5a9ed11e5957a91d9d8023898/psutil-5.8.0-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":244834,"upload-time":"2020-12-19T01:20:12.840200Z","url":"https://files.pythonhosted.org/packages/71/ce/35107e81e7eae55c847313f872d4258a71d2640fa04f57c5520fc81473ce/psutil-5.8.0-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp38-cp38-macosx_10_9_x86_64.whl","hashes":{"sha256":"6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236297,"upload-time":"2020-12-19T01:20:15.489742Z","url":"https://files.pythonhosted.org/packages/10/d6/c5c19e40bb05e2cb5f053f480dfe47e9543a8322f1a5985d7352bf689611/psutil-5.8.0-cp38-cp38-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp38-cp38-manylinux2010_i686.whl","hashes":{"sha256":"d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":293809,"upload-time":"2020-12-19T01:20:18.539919Z","url":"https://files.pythonhosted.org/packages/e9/d6/7d0bcf272923f6b3433e22effd31860b63ab580d65fb2d8f5cb443a9e6fc/psutil-5.8.0-cp38-cp38-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp38-cp38-manylinux2010_x86_64.whl","hashes":{"sha256":"28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":296040,"upload-time":"2020-12-19T01:20:21.423284Z","url":"https://files.pythonhosted.org/packages/3b/c2/78109a12da9febb2f965abf29da6f81b0a3f2b89a7b59d88b759e68dc6db/psutil-5.8.0-cp38-cp38-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp38-cp38-win32.whl","hashes":{"sha256":"ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":240950,"upload-time":"2020-12-19T01:20:24.359373Z","url":"https://files.pythonhosted.org/packages/87/be/6511e1341c203608fe2553249216c40b92cd8a72d8b35fa3c1decee9a616/psutil-5.8.0-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp38-cp38-win_amd64.whl","hashes":{"sha256":"90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":245386,"upload-time":"2020-12-19T01:20:26.799194Z","url":"https://files.pythonhosted.org/packages/8e/5c/c4b32c2024daeac35e126b90a1ff7a0209ef8b32675d1d50e55d58e78c81/psutil-5.8.0-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp39-cp39-macosx_10_9_x86_64.whl","hashes":{"sha256":"6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":236274,"upload-time":"2020-12-19T01:20:29.615503Z","url":"https://files.pythonhosted.org/packages/12/80/8d09c345f19af2b29a309f8f9284e3ba1ae1ebd9438419080c14630f743a/psutil-5.8.0-cp39-cp39-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp39-cp39-manylinux2010_i686.whl","hashes":{"sha256":"245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":291032,"upload-time":"2020-12-19T01:20:32.422979Z","url":"https://files.pythonhosted.org/packages/b6/2f/118e23a8f4e59d2c4ffe03a921cc72f364966e25548dc6c5a3011a334dc5/psutil-5.8.0-cp39-cp39-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp39-cp39-manylinux2010_x86_64.whl","hashes":{"sha256":"90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":293491,"upload-time":"2020-12-19T01:20:34.915141Z","url":"https://files.pythonhosted.org/packages/91/4d/033cc02ae3a47197d0ced818814e4bb8d9d29ebed4f1eb55badedec160f7/psutil-5.8.0-cp39-cp39-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp39-cp39-win32.whl","hashes":{"sha256":"ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":241508,"upload-time":"2020-12-19T01:20:37.527554Z","url":"https://files.pythonhosted.org/packages/a7/13/7285b74e061da21dfc4f15c8307eb2da1d2137367502d6598f03f4a5b5e7/psutil-5.8.0-cp39-cp39-win32.whl","yanked":false},{"core-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"data-dist-info-metadata":{"sha256":"069863fda59a3742371c9f60ad268b0b04c760a595c7d1a1de557d590bf205e6"},"filename":"psutil-5.8.0-cp39-cp39-win_amd64.whl","hashes":{"sha256":"f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":246139,"upload-time":"2020-12-19T01:20:39.890196Z","url":"https://files.pythonhosted.org/packages/21/71/33cb528381c443df1ee25cbb451da975421bddb5099b11e7f2eb3fc90d6d/psutil-5.8.0-cp39-cp39-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.8.0.tar.gz","hashes":{"sha256":"0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":470886,"upload-time":"2020-12-19T01:20:42.916847Z","url":"https://files.pythonhosted.org/packages/e1/b0/7276de53321c12981717490516b7e612364f2cb372ee8901bd4a66a000d7/psutil-5.8.0.tar.gz","yanked":false},{"core-metadata":{"sha256":"146a681ce7fa3f640f9204e5391309605d72b0f4ddaaf9927df9b73319a4490e"},"data-dist-info-metadata":{"sha256":"146a681ce7fa3f640f9204e5391309605d72b0f4ddaaf9927df9b73319a4490e"},"filename":"psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":285348,"upload-time":"2021-12-29T21:26:19.591062Z","url":"https://files.pythonhosted.org/packages/c9/62/5cfcb69c256d469236d4bddeb7ad4ee6a8b37d604dcfc82b7c938fd8ee37/psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"146a681ce7fa3f640f9204e5391309605d72b0f4ddaaf9927df9b73319a4490e"},"data-dist-info-metadata":{"sha256":"146a681ce7fa3f640f9204e5391309605d72b0f4ddaaf9927df9b73319a4490e"},"filename":"psutil-5.9.0-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":288125,"upload-time":"2021-12-29T21:26:24.644701Z","url":"https://files.pythonhosted.org/packages/eb/0d/c19872c9121208bbbb4335bb13a4a2f2b95661fd69d24f26e32f94e5a8a1/psutil-5.9.0-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"146a681ce7fa3f640f9204e5391309605d72b0f4ddaaf9927df9b73319a4490e"},"data-dist-info-metadata":{"sha256":"146a681ce7fa3f640f9204e5391309605d72b0f4ddaaf9927df9b73319a4490e"},"filename":"psutil-5.9.0-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":285364,"upload-time":"2021-12-29T21:26:28.242468Z","url":"https://files.pythonhosted.org/packages/1a/3e/ff287d01bca130b72cf53a9b20bbc31bf566d503ee63adf8c7dcfd9315e2/psutil-5.9.0-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"146a681ce7fa3f640f9204e5391309605d72b0f4ddaaf9927df9b73319a4490e"},"data-dist-info-metadata":{"sha256":"146a681ce7fa3f640f9204e5391309605d72b0f4ddaaf9927df9b73319a4490e"},"filename":"psutil-5.9.0-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":288110,"upload-time":"2021-12-29T21:26:31.963660Z","url":"https://files.pythonhosted.org/packages/d8/49/fbce284331d482703decdc8dec9bfd910fa00a3acd5b974e8efa8c30104a/psutil-5.9.0-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"e84069ba77055def96d865c3845efa434ef6204d61f576071bac882eb98e785c"},"data-dist-info-metadata":{"sha256":"e84069ba77055def96d865c3845efa434ef6204d61f576071bac882eb98e785c"},"filename":"psutil-5.9.0-cp27-none-win32.whl","hashes":{"sha256":"ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239056,"upload-time":"2021-12-29T21:26:35.738857Z","url":"https://files.pythonhosted.org/packages/ab/d7/a8b076603943ebce7872ca7d4e012f6dcdc33e86eabb117921a6fe6e1f8a/psutil-5.9.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"e84069ba77055def96d865c3845efa434ef6204d61f576071bac882eb98e785c"},"data-dist-info-metadata":{"sha256":"e84069ba77055def96d865c3845efa434ef6204d61f576071bac882eb98e785c"},"filename":"psutil-5.9.0-cp27-none-win_amd64.whl","hashes":{"sha256":"ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242517,"upload-time":"2021-12-29T21:26:39.603955Z","url":"https://files.pythonhosted.org/packages/2d/7a/ee32fa2c5712fa0bc6a9f376ffe9d2e1dc856e2e011d2bab4e12293dcd88/psutil-5.9.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl","hashes":{"sha256":"90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238624,"upload-time":"2021-12-29T21:26:42.964503Z","url":"https://files.pythonhosted.org/packages/89/48/2c6f566d35a38fb9f882e51d75425a6f1d097cb946e05b6aff98d450a151/psutil-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":279343,"upload-time":"2021-12-29T21:26:46.859457Z","url":"https://files.pythonhosted.org/packages/11/46/e790221e8281af5163517a17a20c88b10a75a5642d9c5106a868f2879edd/psutil-5.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":281400,"upload-time":"2021-12-29T21:26:51.801761Z","url":"https://files.pythonhosted.org/packages/6f/8a/d1810472a4950a31df385eafbc9bd20cde971814ff6533021dc565bf14ae/psutil-5.9.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp310-cp310-win32.whl","hashes":{"sha256":"8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":241383,"upload-time":"2021-12-29T21:26:55.364799Z","url":"https://files.pythonhosted.org/packages/61/93/4251cfa58e5bbd7f92e1bfb965a0c41376cbcbc83c524a8b60d2678f0edd/psutil-5.9.0-cp310-cp310-win32.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp310-cp310-win_amd64.whl","hashes":{"sha256":"9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":245540,"upload-time":"2021-12-29T21:26:59.088538Z","url":"https://files.pythonhosted.org/packages/9f/c9/7fb339d6a04db3b4ab94671536d11e03b23c056d1604e50e564075a96cd8/psutil-5.9.0-cp310-cp310-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp36-cp36m-macosx_10_9_x86_64.whl","hashes":{"sha256":"e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238331,"upload-time":"2022-01-07T14:28:07.566595Z","url":"https://files.pythonhosted.org/packages/48/cb/6841d4f39b5711652a93359748879f2977ede55c1020f69d038891073592/psutil-5.9.0-cp36-cp36m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":277267,"upload-time":"2022-01-07T14:28:12.318473Z","url":"https://files.pythonhosted.org/packages/1f/2d/e6640979580db1b51220d3165e256a1d0a31847944a3e2622800a737fe86/psutil-5.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":279702,"upload-time":"2022-01-07T14:28:16.706587Z","url":"https://files.pythonhosted.org/packages/64/87/461555057b080e1996427098a6c51c64a8a9025ec18571dabfe5be07eeec/psutil-5.9.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp36-cp36m-win32.whl","hashes":{"sha256":"e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243193,"upload-time":"2022-01-07T14:28:20.695929Z","url":"https://files.pythonhosted.org/packages/2b/a3/24d36239a7bfa30b0eb4302b045417796e9f2c7c21b296d2405735e8949e/psutil-5.9.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":247862,"upload-time":"2022-01-07T14:28:23.774803Z","url":"https://files.pythonhosted.org/packages/14/c9/f0bccd60a25197d63a02688ea8f5c5cd5a8b1baf1a7d6bf493d3291132d2/psutil-5.9.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp37-cp37m-macosx_10_9_x86_64.whl","hashes":{"sha256":"3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238334,"upload-time":"2021-12-29T21:27:03.461413Z","url":"https://files.pythonhosted.org/packages/70/40/0a6ca5641f7574b6ea38cdb561c30065659734755a1779db67b56e225f84/psutil-5.9.0-cp37-cp37m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":278077,"upload-time":"2021-12-29T21:27:07.864433Z","url":"https://files.pythonhosted.org/packages/6b/c0/0f233f87e816c20e5489bca749798255a464282cdd5911d62bb8344c4b5a/psutil-5.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":280736,"upload-time":"2021-12-29T21:27:11.252493Z","url":"https://files.pythonhosted.org/packages/60/f9/b78291ed21146ece2417bd1ba715564c6d3bdf2f1e9297ed67709bb36eeb/psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp37-cp37m-win32.whl","hashes":{"sha256":"df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":241810,"upload-time":"2021-12-29T21:27:14.020326Z","url":"https://files.pythonhosted.org/packages/47/3f/0475146306d02270243e55cad8167d5185c8918933953c90eda846d72ff3/psutil-5.9.0-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":246380,"upload-time":"2021-12-29T21:27:17.444889Z","url":"https://files.pythonhosted.org/packages/7c/d6/4ade7cebfe04710a89e2dc5638f712f09dc5e402a8fea95c3d16dc7f64bf/psutil-5.9.0-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl","hashes":{"sha256":"c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238620,"upload-time":"2021-12-29T21:27:21.799788Z","url":"https://files.pythonhosted.org/packages/89/8e/2a8814f903bc06471621f6e0cd3fc1a7085868656106f31aacf2f844eea2/psutil-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":281246,"upload-time":"2021-12-29T21:27:24.591382Z","url":"https://files.pythonhosted.org/packages/4c/95/3c0858c62ec02106cf5f3e79d74223264a6269a16996f31d5ab43abcec86/psutil-5.9.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":283823,"upload-time":"2021-12-29T21:27:27.809766Z","url":"https://files.pythonhosted.org/packages/0a/66/b2188d8e738ee52206a4ee804907f6eab5bcc9fc0e8486e7ab973a8323b7/psutil-5.9.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp38-cp38-win32.whl","hashes":{"sha256":"76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242160,"upload-time":"2021-12-29T21:27:31.512894Z","url":"https://files.pythonhosted.org/packages/d0/cf/7a86fc08f821d66c528939f155079df7d0945678fc474c6a6455c909f6eb/psutil-5.9.0-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp38-cp38-win_amd64.whl","hashes":{"sha256":"3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":246488,"upload-time":"2021-12-29T21:27:36.422809Z","url":"https://files.pythonhosted.org/packages/62/d4/72fc44dfd9939851bd672e94e43d12848a98b1d2c3f6f794d54a220fe4a7/psutil-5.9.0-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl","hashes":{"sha256":"539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238613,"upload-time":"2021-12-29T21:27:39.813718Z","url":"https://files.pythonhosted.org/packages/48/6a/c6e88a5584544033dbb8318c380e7e1e3796e5ac336577eb91dc75bdecd7/psutil-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":278536,"upload-time":"2021-12-29T21:27:43.204962Z","url":"https://files.pythonhosted.org/packages/f7/b1/82e95f6368dbde6b7e54ea6b18cf8ac3958223540d0bcbde23ba7be19478/psutil-5.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":280415,"upload-time":"2021-12-29T21:27:47.616041Z","url":"https://files.pythonhosted.org/packages/c4/35/7cec9647be077784d20913404f914fffd8fe6dfd0673e29f7bd822ac1331/psutil-5.9.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp39-cp39-win32.whl","hashes":{"sha256":"4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":241428,"upload-time":"2021-12-29T21:27:52.616449Z","url":"https://files.pythonhosted.org/packages/5a/c6/923aed22f6c9c5197998fa6907c983e884975a0ae3430ccd8514f5fd0d6a/psutil-5.9.0-cp39-cp39-win32.whl","yanked":false},{"core-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"data-dist-info-metadata":{"sha256":"ad269c4c58fadbf100c6116eb3ebed5d77878f0c43b9314d18addc011b2b2b80"},"filename":"psutil-5.9.0-cp39-cp39-win_amd64.whl","hashes":{"sha256":"7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":245606,"upload-time":"2021-12-29T21:27:56.202709Z","url":"https://files.pythonhosted.org/packages/9e/9e/3a48f15a1539505e2f3058a709eee56acfb379f2b0ff409d6291099e2a7e/psutil-5.9.0-cp39-cp39-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.9.0.tar.gz","hashes":{"sha256":"869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25"},"provenance":null,"requires-python":">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":478322,"upload-time":"2021-12-29T21:27:59.163343Z","url":"https://files.pythonhosted.org/packages/47/b6/ea8a7728f096a597f0032564e8013b705aa992a0990becd773dcc4d7b4a7/psutil-5.9.0.tar.gz","yanked":false},{"core-metadata":{"sha256":"ac1d788a80e2f59751f3eb37a186124618aa8bafbbe6cd2ad03985f508e49a67"},"data-dist-info-metadata":{"sha256":"ac1d788a80e2f59751f3eb37a186124618aa8bafbbe6cd2ad03985f508e49a67"},"filename":"psutil-5.9.1-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":286186,"upload-time":"2022-05-20T20:09:47.558524Z","url":"https://files.pythonhosted.org/packages/77/06/f9fd79449440d7217d6bf2c90998d540e125cfeffe39d214a328dadc46f4/psutil-5.9.1-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"ac1d788a80e2f59751f3eb37a186124618aa8bafbbe6cd2ad03985f508e49a67"},"data-dist-info-metadata":{"sha256":"ac1d788a80e2f59751f3eb37a186124618aa8bafbbe6cd2ad03985f508e49a67"},"filename":"psutil-5.9.1-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":288858,"upload-time":"2022-05-20T20:09:52.956913Z","url":"https://files.pythonhosted.org/packages/cf/29/ad704a45960bfb52ef8bf0beb9c41c09ce92d61c40333f03e9a03f246c22/psutil-5.9.1-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"e6c8d739b32cc31942a1726ba4e33d58fe5d46a1400a2526e858888d58324393"},"data-dist-info-metadata":{"sha256":"e6c8d739b32cc31942a1726ba4e33d58fe5d46a1400a2526e858888d58324393"},"filename":"psutil-5.9.1-cp27-cp27m-win32.whl","hashes":{"sha256":"0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239479,"upload-time":"2022-05-20T20:09:57.166474Z","url":"https://files.pythonhosted.org/packages/7e/8d/e0a66123fa98e309597815de518b47a7a6c571a8f886fc8d4db2331fd2ab/psutil-5.9.1-cp27-cp27m-win32.whl","yanked":false},{"core-metadata":{"sha256":"e6c8d739b32cc31942a1726ba4e33d58fe5d46a1400a2526e858888d58324393"},"data-dist-info-metadata":{"sha256":"e6c8d739b32cc31942a1726ba4e33d58fe5d46a1400a2526e858888d58324393"},"filename":"psutil-5.9.1-cp27-cp27m-win_amd64.whl","hashes":{"sha256":"e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242812,"upload-time":"2022-05-20T20:10:01.868774Z","url":"https://files.pythonhosted.org/packages/1b/53/8f0772df0a6d593bc2fcdf12f4f790bab5c4f6a77bb61a8ddaad2cbba7f8/psutil-5.9.1-cp27-cp27m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"ac1d788a80e2f59751f3eb37a186124618aa8bafbbe6cd2ad03985f508e49a67"},"data-dist-info-metadata":{"sha256":"ac1d788a80e2f59751f3eb37a186124618aa8bafbbe6cd2ad03985f508e49a67"},"filename":"psutil-5.9.1-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":286184,"upload-time":"2022-05-20T20:10:05.575313Z","url":"https://files.pythonhosted.org/packages/2d/56/54b4ed8102ce5a2f5367b4e766c1873c18f9c32cde321435d0e0ee2abcc5/psutil-5.9.1-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"ac1d788a80e2f59751f3eb37a186124618aa8bafbbe6cd2ad03985f508e49a67"},"data-dist-info-metadata":{"sha256":"ac1d788a80e2f59751f3eb37a186124618aa8bafbbe6cd2ad03985f508e49a67"},"filename":"psutil-5.9.1-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":288863,"upload-time":"2022-05-20T20:10:09.032899Z","url":"https://files.pythonhosted.org/packages/2c/9d/dc329b7da284677ea843f3ff4b35b8ab3b96b65a58a544b3c3f86d9d032f/psutil-5.9.1-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp310-cp310-macosx_10_9_x86_64.whl","hashes":{"sha256":"c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239266,"upload-time":"2022-05-20T20:10:12.541628Z","url":"https://files.pythonhosted.org/packages/d1/16/6239e76ab5d990dc7866bc22a80585f73421588d63b42884d607f5f815e2/psutil-5.9.1-cp310-cp310-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":280127,"upload-time":"2022-05-20T20:10:16.449769Z","url":"https://files.pythonhosted.org/packages/14/06/39d7e963a6a8bbf26519de208593cdb0ddfe22918b8989f4b2363d4ab49f/psutil-5.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":282049,"upload-time":"2022-05-20T20:10:19.914788Z","url":"https://files.pythonhosted.org/packages/6d/c6/6a4e46802e8690d50ba6a56c7f79ac283e703fcfa0fdae8e41909c8cef1f/psutil-5.9.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"data-dist-info-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"filename":"psutil-5.9.1-cp310-cp310-win32.whl","hashes":{"sha256":"20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":241699,"upload-time":"2022-05-20T20:10:23.278390Z","url":"https://files.pythonhosted.org/packages/26/b4/a58cf15ea649faa92c54f00c627aef1d50b9f1abf207485f10c967a50c95/psutil-5.9.1-cp310-cp310-win32.whl","yanked":false},{"core-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"data-dist-info-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"filename":"psutil-5.9.1-cp310-cp310-win_amd64.whl","hashes":{"sha256":"58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":245843,"upload-time":"2022-05-20T20:10:26.856840Z","url":"https://files.pythonhosted.org/packages/c0/5a/2ac88d5265b711c8aa4e786825b38d5d0b1e5ecbdd0ce78e9b04a820d247/psutil-5.9.1-cp310-cp310-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp36-cp36m-macosx_10_9_x86_64.whl","hashes":{"sha256":"3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239007,"upload-time":"2022-05-20T20:10:30.191299Z","url":"https://files.pythonhosted.org/packages/65/1d/6a112f146faee6292a6c3ee2a7f24a8e572697adb7e1c5de3d8508f647cc/psutil-5.9.1-cp36-cp36m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":278074,"upload-time":"2022-05-20T20:10:33.843171Z","url":"https://files.pythonhosted.org/packages/7e/52/a02dc53e26714a339c8b4972d8e3f268e4db8905f5d1a3a100f1e40b6fa7/psutil-5.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":280402,"upload-time":"2022-05-20T20:10:38.665061Z","url":"https://files.pythonhosted.org/packages/6b/76/a8cb69ed3566877dcbccf408f5f9d6055227ad4fed694e88809fa8506b0b/psutil-5.9.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp36-cp36m-win32.whl","hashes":{"sha256":"0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243501,"upload-time":"2022-05-20T20:10:42.164047Z","url":"https://files.pythonhosted.org/packages/85/4d/78173e3dffb74c5fa87914908f143473d0b8b9183f9d275333679a4e4649/psutil-5.9.1-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":248156,"upload-time":"2022-05-20T20:10:45.327961Z","url":"https://files.pythonhosted.org/packages/73/1a/d78f2f2de2aad6628415d2a48917cabc2c7fb0c3a31c7cdf187cffa4eb36/psutil-5.9.1-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp37-cp37m-macosx_10_9_x86_64.whl","hashes":{"sha256":"91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238960,"upload-time":"2022-05-20T20:10:48.678116Z","url":"https://files.pythonhosted.org/packages/d6/ef/fd4dc9085e3879c3af63fe60667dd3b71adf50d030b5549315f4a619271b/psutil-5.9.1-cp37-cp37m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":278553,"upload-time":"2022-05-20T20:10:52.019471Z","url":"https://files.pythonhosted.org/packages/97/f6/0180e58dd1359da7d6fbc27d04dac6fb500dc758b6f4b65407608bb13170/psutil-5.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":281356,"upload-time":"2022-05-20T20:10:55.963864Z","url":"https://files.pythonhosted.org/packages/13/71/c25adbd9b33a2e27edbe1fc84b3111a5ad97611885d7abcbdd8d1f2bb7ca/psutil-5.9.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"data-dist-info-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"filename":"psutil-5.9.1-cp37-cp37m-win32.whl","hashes":{"sha256":"d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242105,"upload-time":"2022-05-20T20:10:59.100543Z","url":"https://files.pythonhosted.org/packages/2a/32/136cd5bf55728ea64a22b1d817890e35fc17314c46a24ee3268b65f9076f/psutil-5.9.1-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"data-dist-info-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"filename":"psutil-5.9.1-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":246671,"upload-time":"2022-05-20T20:11:02.513602Z","url":"https://files.pythonhosted.org/packages/df/88/427f3959855fcb3ab04891e00c026a246892feb11b20433db814b7a24405/psutil-5.9.1-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp38-cp38-macosx_10_9_x86_64.whl","hashes":{"sha256":"19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239250,"upload-time":"2022-05-20T20:11:06.292977Z","url":"https://files.pythonhosted.org/packages/46/80/1de3a9bac336b5c8e4f7b0ff2e80c85ba237f18f2703be68884ee6798432/psutil-5.9.1-cp38-cp38-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":281678,"upload-time":"2022-05-20T20:11:09.990719Z","url":"https://files.pythonhosted.org/packages/fd/ba/c5a3f46f351ab609cc0be6a563e492900c57e3d5c9bda0b79b84d8c3eae9/psutil-5.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":284665,"upload-time":"2022-05-20T20:11:13.584309Z","url":"https://files.pythonhosted.org/packages/9d/41/d5f2db2ab7f5dff2fa795993a0cd6fa8a8f39ca197c3a86857875333ec10/psutil-5.9.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"data-dist-info-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"filename":"psutil-5.9.1-cp38-cp38-win32.whl","hashes":{"sha256":"a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242461,"upload-time":"2022-05-20T20:11:16.942263Z","url":"https://files.pythonhosted.org/packages/41/ec/5fd3e9388d0ed1edfdeae71799df374f4a117932646a63413fa95a121e9f/psutil-5.9.1-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"data-dist-info-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"filename":"psutil-5.9.1-cp38-cp38-win_amd64.whl","hashes":{"sha256":"79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":246804,"upload-time":"2022-05-20T20:11:20.322459Z","url":"https://files.pythonhosted.org/packages/b2/ad/65e2b2b97677f98d718388dc11b2a9d7f177ebbae5eef72547a32bc28911/psutil-5.9.1-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp39-cp39-macosx_10_9_x86_64.whl","hashes":{"sha256":"28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239248,"upload-time":"2022-05-20T20:11:23.352312Z","url":"https://files.pythonhosted.org/packages/9f/ca/84ce3e48b3ca2f0f74314d89929b3a523220f3f4a8dff395d6ef74dadef3/psutil-5.9.1-cp39-cp39-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":279320,"upload-time":"2022-05-20T20:11:27.310813Z","url":"https://files.pythonhosted.org/packages/a9/97/b7e3532d97d527349701d2143c3f868733b94e2db6f531b07811b698f549/psutil-5.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"data-dist-info-metadata":{"sha256":"1f39ef2570a850c4cbdb8430a13b995343050494e85d316030a206bd7da4c63b"},"filename":"psutil-5.9.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":281065,"upload-time":"2022-05-20T20:11:30.617653Z","url":"https://files.pythonhosted.org/packages/62/1f/f14225bda76417ab9bd808ff21d5cd59d5435a9796ca09b34d4cb0edcd88/psutil-5.9.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"data-dist-info-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"filename":"psutil-5.9.1-cp39-cp39-win32.whl","hashes":{"sha256":"32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":241748,"upload-time":"2022-05-20T20:11:34.374569Z","url":"https://files.pythonhosted.org/packages/b1/d2/c5374a784567c1e42ee8a589b1b42e2bd6e14c7be3c234d84360ab3a0a39/psutil-5.9.1-cp39-cp39-win32.whl","yanked":false},{"core-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"data-dist-info-metadata":{"sha256":"b48f9beb61d032244f4c239867a736641369484a967d3ac29eeab577ce127229"},"filename":"psutil-5.9.1-cp39-cp39-win_amd64.whl","hashes":{"sha256":"f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":245883,"upload-time":"2022-05-20T20:11:37.840887Z","url":"https://files.pythonhosted.org/packages/e0/ac/fd6f098969d49f046083ac032e6788d9f861903596fb9555a02bf50a1238/psutil-5.9.1-cp39-cp39-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.9.1.tar.gz","hashes":{"sha256":"57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":479090,"upload-time":"2022-05-20T20:11:41.043143Z","url":"https://files.pythonhosted.org/packages/d6/de/0999ea2562b96d7165812606b18f7169307b60cd378bc29cf3673322c7e9/psutil-5.9.1.tar.gz","yanked":false},{"core-metadata":{"sha256":"fcf26ce4e0d8482a82643f948df7370a2afab20a048f7563ee3fb420705fcf07"},"data-dist-info-metadata":{"sha256":"fcf26ce4e0d8482a82643f948df7370a2afab20a048f7563ee3fb420705fcf07"},"filename":"psutil-5.9.2-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":285739,"upload-time":"2022-09-04T20:15:28.668835Z","url":"https://files.pythonhosted.org/packages/37/a4/cb10e4c0faa3091de22eb78fa1c332566e60b9b59001bef326a4c1070417/psutil-5.9.2-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"fcf26ce4e0d8482a82643f948df7370a2afab20a048f7563ee3fb420705fcf07"},"data-dist-info-metadata":{"sha256":"fcf26ce4e0d8482a82643f948df7370a2afab20a048f7563ee3fb420705fcf07"},"filename":"psutil-5.9.2-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":289461,"upload-time":"2022-09-04T20:15:32.802519Z","url":"https://files.pythonhosted.org/packages/93/40/58dfcab15435b6fedf5385bc7e88a4c162cc6af0056f5d9d97f5ebfd7fa0/psutil-5.9.2-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"5f8d323cf6b24ee1e72553da21c2f9124f45da854ffd5befab575ceb9733c773"},"data-dist-info-metadata":{"sha256":"5f8d323cf6b24ee1e72553da21c2f9124f45da854ffd5befab575ceb9733c773"},"filename":"psutil-5.9.2-cp27-cp27m-win32.whl","hashes":{"sha256":"b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":240036,"upload-time":"2022-09-04T20:15:36.698093Z","url":"https://files.pythonhosted.org/packages/42/eb/83470960f2c13a026b07051456ad834f5fea0c80e8cb83fc65005f5f18d5/psutil-5.9.2-cp27-cp27m-win32.whl","yanked":false},{"core-metadata":{"sha256":"5f8d323cf6b24ee1e72553da21c2f9124f45da854ffd5befab575ceb9733c773"},"data-dist-info-metadata":{"sha256":"5f8d323cf6b24ee1e72553da21c2f9124f45da854ffd5befab575ceb9733c773"},"filename":"psutil-5.9.2-cp27-cp27m-win_amd64.whl","hashes":{"sha256":"404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243378,"upload-time":"2022-09-04T20:15:40.746927Z","url":"https://files.pythonhosted.org/packages/d1/5b/b9d6ac192d3108e1dc7875ab1579b7f65eb7bf0ef799dadd3f3798d0af2e/psutil-5.9.2-cp27-cp27m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"fcf26ce4e0d8482a82643f948df7370a2afab20a048f7563ee3fb420705fcf07"},"data-dist-info-metadata":{"sha256":"fcf26ce4e0d8482a82643f948df7370a2afab20a048f7563ee3fb420705fcf07"},"filename":"psutil-5.9.2-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":285740,"upload-time":"2022-09-04T20:15:44.420732Z","url":"https://files.pythonhosted.org/packages/b6/96/ddf877440f2686eb17933531507fe4822ff1ed76d85df4a093a605b91db8/psutil-5.9.2-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"fcf26ce4e0d8482a82643f948df7370a2afab20a048f7563ee3fb420705fcf07"},"data-dist-info-metadata":{"sha256":"fcf26ce4e0d8482a82643f948df7370a2afab20a048f7563ee3fb420705fcf07"},"filename":"psutil-5.9.2-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":289451,"upload-time":"2022-09-04T20:15:47.573981Z","url":"https://files.pythonhosted.org/packages/d7/df/ff5c766b50350f2a4555d5068127d372bb26201a2a5eeda9efc8dbf570b4/psutil-5.9.2-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp310-cp310-macosx_10_9_x86_64.whl","hashes":{"sha256":"614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239273,"upload-time":"2022-09-04T20:15:52.215984Z","url":"https://files.pythonhosted.org/packages/04/5d/d52473097582db5d3094bc34acf9874de726327a3166426e22ed0806de6a/psutil-5.9.2-cp310-cp310-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":280571,"upload-time":"2022-09-04T20:15:55.608887Z","url":"https://files.pythonhosted.org/packages/47/2b/bd12c4f2d1bd3024fe7c5d8388f8a5627cc02fbe11d62bd451aff356415d/psutil-5.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":282777,"upload-time":"2022-09-04T20:15:59.840541Z","url":"https://files.pythonhosted.org/packages/4c/85/7a112fb6a8c598a6f5d079228bbc03ae84c472397be79c075e7514b6ed36/psutil-5.9.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp310-cp310-win32.whl","hashes":{"sha256":"e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":241713,"upload-time":"2022-09-05T14:16:44.931542Z","url":"https://files.pythonhosted.org/packages/39/07/5cbcf3322031fcf8dcbfa431b1c145f193c96b18964ef374a88d6a83f2c9/psutil-5.9.2-cp310-cp310-win32.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp310-cp310-win_amd64.whl","hashes":{"sha256":"f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":245853,"upload-time":"2022-09-05T14:16:48.388734Z","url":"https://files.pythonhosted.org/packages/ae/9c/d29dd82d5fda2c6c6d959d57101c78ddbac8325defe94e1b9f983e7cfff3/psutil-5.9.2-cp310-cp310-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"c9979ffe6f0cae3b896ebf06f4e0fa63198d968c57d0df7bb3405936e7b9eb5f"},"data-dist-info-metadata":{"sha256":"c9979ffe6f0cae3b896ebf06f4e0fa63198d968c57d0df7bb3405936e7b9eb5f"},"filename":"psutil-5.9.2-cp36-cp36m-macosx_10_9_x86_64.whl","hashes":{"sha256":"dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238998,"upload-time":"2022-09-04T20:16:03.484887Z","url":"https://files.pythonhosted.org/packages/df/aa/8268eee572fb9bdf3486d384e3973ad9d635403841c6e7f2af7781e5525b/psutil-5.9.2-cp36-cp36m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"c9979ffe6f0cae3b896ebf06f4e0fa63198d968c57d0df7bb3405936e7b9eb5f"},"data-dist-info-metadata":{"sha256":"c9979ffe6f0cae3b896ebf06f4e0fa63198d968c57d0df7bb3405936e7b9eb5f"},"filename":"psutil-5.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":278226,"upload-time":"2022-09-04T20:16:07.134877Z","url":"https://files.pythonhosted.org/packages/f0/43/bcb92221f5dd45e155337aae37e412fe02a3e5d99e936156a4dcff89fa55/psutil-5.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"c9979ffe6f0cae3b896ebf06f4e0fa63198d968c57d0df7bb3405936e7b9eb5f"},"data-dist-info-metadata":{"sha256":"c9979ffe6f0cae3b896ebf06f4e0fa63198d968c57d0df7bb3405936e7b9eb5f"},"filename":"psutil-5.9.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":280489,"upload-time":"2022-09-04T20:16:10.463113Z","url":"https://files.pythonhosted.org/packages/a4/eb/d841d5bc526641aad65373b0a4850e98284580df967daff5288779090ea3/psutil-5.9.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"c9979ffe6f0cae3b896ebf06f4e0fa63198d968c57d0df7bb3405936e7b9eb5f"},"data-dist-info-metadata":{"sha256":"c9979ffe6f0cae3b896ebf06f4e0fa63198d968c57d0df7bb3405936e7b9eb5f"},"filename":"psutil-5.9.2-cp36-cp36m-win32.whl","hashes":{"sha256":"f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243511,"upload-time":"2022-09-05T14:16:51.624881Z","url":"https://files.pythonhosted.org/packages/54/5f/3619e7d22ded096fa6dbd329fc057bfcf53e998b1e2c1ecc07a4155175b1/psutil-5.9.2-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"c9979ffe6f0cae3b896ebf06f4e0fa63198d968c57d0df7bb3405936e7b9eb5f"},"data-dist-info-metadata":{"sha256":"c9979ffe6f0cae3b896ebf06f4e0fa63198d968c57d0df7bb3405936e7b9eb5f"},"filename":"psutil-5.9.2-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":248164,"upload-time":"2022-09-05T14:16:55.091982Z","url":"https://files.pythonhosted.org/packages/53/ac/7c4ff994b1ea7d46a84932f0c8d49e28e36a668173975876353f4ea38588/psutil-5.9.2-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp37-cp37m-macosx_10_9_x86_64.whl","hashes":{"sha256":"42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":238970,"upload-time":"2022-09-04T20:16:14.127339Z","url":"https://files.pythonhosted.org/packages/55/c5/fd2c45a0845e7bae07c8112ed67c21163742cc116732ac2702d9139a9a92/psutil-5.9.2-cp37-cp37m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":279452,"upload-time":"2022-09-04T20:16:18.609472Z","url":"https://files.pythonhosted.org/packages/89/cf/b228a7554eda5e72fd8c33b89c628a86336e5cdbd62fe8b8d2a61a099b2d/psutil-5.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":281281,"upload-time":"2022-09-04T20:16:22.476894Z","url":"https://files.pythonhosted.org/packages/3d/73/d8c87b5612c58d1e6c6d91997c1590771d34e4ee27d9c11eb1e64ecbf365/psutil-5.9.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp37-cp37m-win32.whl","hashes":{"sha256":"7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242115,"upload-time":"2022-09-05T14:16:58.770138Z","url":"https://files.pythonhosted.org/packages/98/42/62470fae4e1e9c0f4336acf74af9d4a6d5c6b5788c8435ec387e987a7ebe/psutil-5.9.2-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":246680,"upload-time":"2022-09-05T14:17:03.836879Z","url":"https://files.pythonhosted.org/packages/5e/a2/4025f29069010f118eba4bcd681167d547525d40d2c45029db2f64606f86/psutil-5.9.2-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp38-cp38-macosx_10_9_x86_64.whl","hashes":{"sha256":"fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239258,"upload-time":"2022-09-04T20:16:26.490949Z","url":"https://files.pythonhosted.org/packages/2b/52/c69f5d0acc4bbd3cf44178f025e498666d2eebc216f5f5725d9142244365/psutil-5.9.2-cp38-cp38-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":282623,"upload-time":"2022-09-04T20:16:29.707162Z","url":"https://files.pythonhosted.org/packages/c4/02/5fc4419f47f141ec0dd28db36fb8bcf1eb6e9df332690617b052c8bec76d/psutil-5.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":284813,"upload-time":"2022-09-04T20:16:33.456488Z","url":"https://files.pythonhosted.org/packages/79/61/a8d6d649996494672d8a86fe8be6c81b2880ee30881709d84435f2505b47/psutil-5.9.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp38-cp38-win32.whl","hashes":{"sha256":"561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242471,"upload-time":"2022-09-05T14:17:07.677347Z","url":"https://files.pythonhosted.org/packages/6f/8d/41c402ae33b1ce3f8e37a0dec691d753cbe66e6784e7fd26ed0cd16d99ab/psutil-5.9.2-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp38-cp38-win_amd64.whl","hashes":{"sha256":"67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":246816,"upload-time":"2022-09-05T14:17:11.540699Z","url":"https://files.pythonhosted.org/packages/29/07/a35c4127942cce6899d447cb54f9926d33cf1800a37c09192dd9b5a08744/psutil-5.9.2-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp39-cp39-macosx_10_9_x86_64.whl","hashes":{"sha256":"b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":239256,"upload-time":"2022-09-04T20:16:37.181485Z","url":"https://files.pythonhosted.org/packages/65/74/0ad485d753b2f0d00ee4ec933da1e169bc4c8f4f58db88132e886efed14b/psutil-5.9.2-cp39-cp39-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":279700,"upload-time":"2022-09-04T20:16:40.816901Z","url":"https://files.pythonhosted.org/packages/bb/df/0819b9aed416b0dedf668cc6b3f291899c276cb2b566c4aa0dc212a03d55/psutil-5.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":281896,"upload-time":"2022-09-04T20:16:45.773716Z","url":"https://files.pythonhosted.org/packages/b3/61/54822666fbbdd4ae1825f7a0b0cf8925a96fac1f778b4a0d5c9c066cf4b2/psutil-5.9.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp39-cp39-win32.whl","hashes":{"sha256":"ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":241759,"upload-time":"2022-09-05T14:17:15.034289Z","url":"https://files.pythonhosted.org/packages/67/cf/f620f740da5bb5895b441248e08b0cd167fb545ecaa3e74ea06f3551975e/psutil-5.9.2-cp39-cp39-win32.whl","yanked":false},{"core-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"data-dist-info-metadata":{"sha256":"9318f57df027edece05c45114c189721cfbe0a56aa2ada92fe2b21340be7af15"},"filename":"psutil-5.9.2-cp39-cp39-win_amd64.whl","hashes":{"sha256":"68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":245892,"upload-time":"2022-09-05T14:17:18.376384Z","url":"https://files.pythonhosted.org/packages/10/cf/7595896a7487937c171f53bae2eeb0adcc1690ebeef684ac180a77910639/psutil-5.9.2-cp39-cp39-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.9.2.tar.gz","hashes":{"sha256":"feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":479757,"upload-time":"2022-09-04T20:16:49.093359Z","url":"https://files.pythonhosted.org/packages/8f/57/828ac1f70badc691a716e77bfae258ef5db76bb7830109bf4bcf882de020/psutil-5.9.2.tar.gz","yanked":false},{"core-metadata":{"sha256":"eecee58b65701e8da473f7e6142a96da3fac24d305218d11c887ca28bcd6cd61"},"data-dist-info-metadata":{"sha256":"eecee58b65701e8da473f7e6142a96da3fac24d305218d11c887ca28bcd6cd61"},"filename":"psutil-5.9.3-cp27-cp27m-macosx_10_9_x86_64.whl","hashes":{"sha256":"b4a247cd3feaae39bb6085fcebf35b3b8ecd9b022db796d89c8f05067ca28e71"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":241919,"upload-time":"2022-10-18T20:12:47.255096Z","url":"https://files.pythonhosted.org/packages/74/42/6268344958236744962c711664de259598fe2005e5818c7d6bc77ae12690/psutil-5.9.3-cp27-cp27m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"eecee58b65701e8da473f7e6142a96da3fac24d305218d11c887ca28bcd6cd61"},"data-dist-info-metadata":{"sha256":"eecee58b65701e8da473f7e6142a96da3fac24d305218d11c887ca28bcd6cd61"},"filename":"psutil-5.9.3-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"5fa88e3d5d0b480602553d362c4b33a63e0c40bfea7312a7bf78799e01e0810b"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":293266,"upload-time":"2022-10-18T20:12:51.216028Z","url":"https://files.pythonhosted.org/packages/79/6a/7bb45dddeb348cdb9d91d7bc78e903026870ef7f257c35de250392719cf8/psutil-5.9.3-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"eecee58b65701e8da473f7e6142a96da3fac24d305218d11c887ca28bcd6cd61"},"data-dist-info-metadata":{"sha256":"eecee58b65701e8da473f7e6142a96da3fac24d305218d11c887ca28bcd6cd61"},"filename":"psutil-5.9.3-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"767ef4fa33acda16703725c0473a91e1832d296c37c63896c7153ba81698f1ab"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":297585,"upload-time":"2022-10-18T20:12:54.920684Z","url":"https://files.pythonhosted.org/packages/02/c7/d5a6106cf31cc58f4a8a9d88b1ab8405b645b02c482353dd59f5ef19926f/psutil-5.9.3-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"3a494c9ef6c9e0802bc0a2f0d176ca6b72ed9e8ba229b930c23d38f5f92be462"},"data-dist-info-metadata":{"sha256":"3a494c9ef6c9e0802bc0a2f0d176ca6b72ed9e8ba229b930c23d38f5f92be462"},"filename":"psutil-5.9.3-cp27-cp27m-win32.whl","hashes":{"sha256":"9a4af6ed1094f867834f5f07acd1250605a0874169a5fcadbcec864aec2496a6"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":240677,"upload-time":"2022-10-18T20:12:58.145019Z","url":"https://files.pythonhosted.org/packages/62/0a/27aa8d95995fe97a944939f8fff7183f151814a1052b76d125812bed4800/psutil-5.9.3-cp27-cp27m-win32.whl","yanked":false},{"core-metadata":{"sha256":"3a494c9ef6c9e0802bc0a2f0d176ca6b72ed9e8ba229b930c23d38f5f92be462"},"data-dist-info-metadata":{"sha256":"3a494c9ef6c9e0802bc0a2f0d176ca6b72ed9e8ba229b930c23d38f5f92be462"},"filename":"psutil-5.9.3-cp27-cp27m-win_amd64.whl","hashes":{"sha256":"fa5e32c7d9b60b2528108ade2929b115167fe98d59f89555574715054f50fa31"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":244019,"upload-time":"2022-10-18T20:13:01.999625Z","url":"https://files.pythonhosted.org/packages/2b/0a/36951d279e1d716ab264b04e8ddb12e0c08cc1c7cbd44f2d22c84dc61e33/psutil-5.9.3-cp27-cp27m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"eecee58b65701e8da473f7e6142a96da3fac24d305218d11c887ca28bcd6cd61"},"data-dist-info-metadata":{"sha256":"eecee58b65701e8da473f7e6142a96da3fac24d305218d11c887ca28bcd6cd61"},"filename":"psutil-5.9.3-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"fe79b4ad4836e3da6c4650cb85a663b3a51aef22e1a829c384e18fae87e5e727"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":293262,"upload-time":"2022-10-18T20:13:05.579750Z","url":"https://files.pythonhosted.org/packages/5b/9c/5412473100e3213d970c8b9291371816e57f1e4a74296b2e3b8a5c8ebb47/psutil-5.9.3-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"eecee58b65701e8da473f7e6142a96da3fac24d305218d11c887ca28bcd6cd61"},"data-dist-info-metadata":{"sha256":"eecee58b65701e8da473f7e6142a96da3fac24d305218d11c887ca28bcd6cd61"},"filename":"psutil-5.9.3-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"db8e62016add2235cc87fb7ea000ede9e4ca0aa1f221b40cef049d02d5d2593d"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":297584,"upload-time":"2022-10-18T20:13:09.701506Z","url":"https://files.pythonhosted.org/packages/8f/5a/e9e98bb3ade26bc7847d5722d0e4a6d437621fa8fc02269d9cba78f6f241/psutil-5.9.3-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp310-cp310-macosx_10_9_x86_64.whl","hashes":{"sha256":"941a6c2c591da455d760121b44097781bc970be40e0e43081b9139da485ad5b7"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242575,"upload-time":"2022-10-18T20:13:13.343655Z","url":"https://files.pythonhosted.org/packages/95/90/822c926e170e8a5769ff11edb92ac59dd523df505b5d56cad0ef3f15c325/psutil-5.9.3-cp310-cp310-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp310-cp310-macosx_11_0_arm64.whl","hashes":{"sha256":"71b1206e7909792d16933a0d2c1c7f04ae196186c51ba8567abae1d041f06dcb"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243240,"upload-time":"2022-10-18T20:13:17.345095Z","url":"https://files.pythonhosted.org/packages/42/9e/243aa51c3d71355913dafc27c5cb7ffdbe9a42c939a5aace526906bfc721/psutil-5.9.3-cp310-cp310-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"f57d63a2b5beaf797b87024d018772439f9d3103a395627b77d17a8d72009543"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":289285,"upload-time":"2022-10-18T20:13:20.535587Z","url":"https://files.pythonhosted.org/packages/f7/b0/6925fbfac4c342cb2f8bad1571b48e12802ac8031e1d4453a31e9a12b64d/psutil-5.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"e7507f6c7b0262d3e7b0eeda15045bf5881f4ada70473b87bc7b7c93b992a7d7"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":292339,"upload-time":"2022-10-18T20:13:26.635784Z","url":"https://files.pythonhosted.org/packages/ed/2c/483ed7332d74b3fef0f5ba13c192d33f21fe95df5468a7ca040f02bd7af9/psutil-5.9.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"data-dist-info-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"filename":"psutil-5.9.3-cp310-cp310-win32.whl","hashes":{"sha256":"1b540599481c73408f6b392cdffef5b01e8ff7a2ac8caae0a91b8222e88e8f1e"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242906,"upload-time":"2022-10-18T20:13:30.051727Z","url":"https://files.pythonhosted.org/packages/55/07/94730401200098b1119dc9f5d3a271e3bf865b31bfa64a2b58a0bbd9d222/psutil-5.9.3-cp310-cp310-win32.whl","yanked":false},{"core-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"data-dist-info-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"filename":"psutil-5.9.3-cp310-cp310-win_amd64.whl","hashes":{"sha256":"547ebb02031fdada635452250ff39942db8310b5c4a8102dfe9384ee5791e650"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":247045,"upload-time":"2022-10-18T20:13:34.024950Z","url":"https://files.pythonhosted.org/packages/37/c0/8a102d4ce45dbc5d04932b52327c4385b88023635e57af9d457ca5ea6bb3/psutil-5.9.3-cp310-cp310-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"0815ae21fa21e764d04c42f06a34a9f89e9b720a9d80f565b7943a08cf8e10ad"},"data-dist-info-metadata":{"sha256":"0815ae21fa21e764d04c42f06a34a9f89e9b720a9d80f565b7943a08cf8e10ad"},"filename":"psutil-5.9.3-cp36-cp36m-macosx_10_9_x86_64.whl","hashes":{"sha256":"d8c3cc6bb76492133474e130a12351a325336c01c96a24aae731abf5a47fe088"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242147,"upload-time":"2022-10-18T20:13:37.172733Z","url":"https://files.pythonhosted.org/packages/61/f2/74908ddbe57863007e3b3a76f39b509bbab9892d0949f1e9d5a888f8ec60/psutil-5.9.3-cp36-cp36m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"0815ae21fa21e764d04c42f06a34a9f89e9b720a9d80f565b7943a08cf8e10ad"},"data-dist-info-metadata":{"sha256":"0815ae21fa21e764d04c42f06a34a9f89e9b720a9d80f565b7943a08cf8e10ad"},"filename":"psutil-5.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"07d880053c6461c9b89cd5d4808f3b8336665fa3acdefd6777662c5ed73a851a"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":286758,"upload-time":"2022-10-18T20:13:40.932526Z","url":"https://files.pythonhosted.org/packages/30/2f/696c4459864385cc5c63a21f30584dfd99d2130c21c8b3084ffbaa0edd82/psutil-5.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"0815ae21fa21e764d04c42f06a34a9f89e9b720a9d80f565b7943a08cf8e10ad"},"data-dist-info-metadata":{"sha256":"0815ae21fa21e764d04c42f06a34a9f89e9b720a9d80f565b7943a08cf8e10ad"},"filename":"psutil-5.9.3-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"5e8b50241dd3c2ed498507f87a6602825073c07f3b7e9560c58411c14fe1e1c9"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":290363,"upload-time":"2022-10-18T20:13:43.847649Z","url":"https://files.pythonhosted.org/packages/2c/80/2f3072492a7f14faf4f4565dd26fe1baf4b3fd28557f1427b6708064a622/psutil-5.9.3-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"2ef63858bce55082c5fb7e0b7dabc2fd37fc333286a4d4e5b64df61afb5235de"},"data-dist-info-metadata":{"sha256":"2ef63858bce55082c5fb7e0b7dabc2fd37fc333286a4d4e5b64df61afb5235de"},"filename":"psutil-5.9.3-cp36-cp36m-win32.whl","hashes":{"sha256":"828c9dc9478b34ab96be75c81942d8df0c2bb49edbb481f597314d92b6441d89"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":244701,"upload-time":"2022-10-18T20:13:47.431806Z","url":"https://files.pythonhosted.org/packages/db/e3/10363d747d900f89f7920b8e4060b42cd862b580a69a2b9c9788c4de9035/psutil-5.9.3-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"2ef63858bce55082c5fb7e0b7dabc2fd37fc333286a4d4e5b64df61afb5235de"},"data-dist-info-metadata":{"sha256":"2ef63858bce55082c5fb7e0b7dabc2fd37fc333286a4d4e5b64df61afb5235de"},"filename":"psutil-5.9.3-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"ed15edb14f52925869250b1375f0ff58ca5c4fa8adefe4883cfb0737d32f5c02"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":249356,"upload-time":"2022-10-18T20:13:51.794050Z","url":"https://files.pythonhosted.org/packages/ac/cc/092ca7ae0c5f270bb14720cd8ac86a3fafda25fae31d08d2465eed4498b3/psutil-5.9.3-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp37-cp37m-macosx_10_9_x86_64.whl","hashes":{"sha256":"d266cd05bd4a95ca1c2b9b5aac50d249cf7c94a542f47e0b22928ddf8b80d1ef"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242154,"upload-time":"2022-10-18T20:14:12.425822Z","url":"https://files.pythonhosted.org/packages/16/51/d431f7db3a3a44d9c03ec1681835a5de52d2f0bb7e28f29ecd806ccc46ec/psutil-5.9.3-cp37-cp37m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"7e4939ff75149b67aef77980409f156f0082fa36accc475d45c705bb00c6c16a"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":287383,"upload-time":"2022-10-18T20:14:15.962130Z","url":"https://files.pythonhosted.org/packages/94/b0/cd3be14dc74a6f262b1de296841a5141a794cc485d4e3af5c1c0ffc9b886/psutil-5.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"68fa227c32240c52982cb931801c5707a7f96dd8927f9102d6c7771ea1ff5698"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":291396,"upload-time":"2022-10-18T20:14:19.855473Z","url":"https://files.pythonhosted.org/packages/5e/86/856aa554ec7eb843fb006ef125cf4543ee9058cb39ad09d131dd820c71f7/psutil-5.9.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"data-dist-info-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"filename":"psutil-5.9.3-cp37-cp37m-win32.whl","hashes":{"sha256":"beb57d8a1ca0ae0eb3d08ccaceb77e1a6d93606f0e1754f0d60a6ebd5c288837"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243306,"upload-time":"2022-10-18T20:14:23.302434Z","url":"https://files.pythonhosted.org/packages/ab/10/547feeec01275dd544a389ba05ecb3c316015d4b402cc7b440ca2d98ebcd/psutil-5.9.3-cp37-cp37m-win32.whl","yanked":false},{"core-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"data-dist-info-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"filename":"psutil-5.9.3-cp37-cp37m-win_amd64.whl","hashes":{"sha256":"12500d761ac091f2426567f19f95fd3f15a197d96befb44a5c1e3cbe6db5752c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":247870,"upload-time":"2022-10-18T20:14:26.971453Z","url":"https://files.pythonhosted.org/packages/0c/f1/50e71c11ef14c592686dfc60e2b42a381fe57af2d22713e66a72c07cf9d1/psutil-5.9.3-cp37-cp37m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp38-cp38-macosx_10_9_x86_64.whl","hashes":{"sha256":"ba38cf9984d5462b506e239cf4bc24e84ead4b1d71a3be35e66dad0d13ded7c1"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242611,"upload-time":"2022-10-18T20:14:30.842925Z","url":"https://files.pythonhosted.org/packages/01/d6/9ca99b416dddf4a49855a9ebf4af3a2db9526e94e9693da169fa5ed61788/psutil-5.9.3-cp38-cp38-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp38-cp38-macosx_11_0_arm64.whl","hashes":{"sha256":"46907fa62acaac364fff0b8a9da7b360265d217e4fdeaca0a2397a6883dffba2"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243235,"upload-time":"2022-10-18T20:14:34.363643Z","url":"https://files.pythonhosted.org/packages/2c/ce/daf28e50305fdbba0754ba58ab0346ec6cfa41293110412f4c6bf74738bb/psutil-5.9.3-cp38-cp38-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"a04a1836894c8279e5e0a0127c0db8e198ca133d28be8a2a72b4db16f6cf99c1"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":291620,"upload-time":"2022-10-18T20:14:38.179697Z","url":"https://files.pythonhosted.org/packages/b9/cf/56278ae450741b6390491aecaa5f6152ff491bf00544799830e98340ff48/psutil-5.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"8a4e07611997acf178ad13b842377e3d8e9d0a5bac43ece9bfc22a96735d9a4f"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":295598,"upload-time":"2022-10-18T20:14:41.691453Z","url":"https://files.pythonhosted.org/packages/af/5d/9c03a47af929fc12699fcf5174313744eef33a7b9e106e8111f57427b7d7/psutil-5.9.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"data-dist-info-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"filename":"psutil-5.9.3-cp38-cp38-win32.whl","hashes":{"sha256":"6ced1ad823ecfa7d3ce26fe8aa4996e2e53fb49b7fed8ad81c80958501ec0619"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243663,"upload-time":"2022-10-18T20:14:45.084651Z","url":"https://files.pythonhosted.org/packages/69/3d/e1a12f505eb0171912b94e4689453639bb0deeb70ab4eddbc7b9266f819e/psutil-5.9.3-cp38-cp38-win32.whl","yanked":false},{"core-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"data-dist-info-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"filename":"psutil-5.9.3-cp38-cp38-win_amd64.whl","hashes":{"sha256":"35feafe232d1aaf35d51bd42790cbccb882456f9f18cdc411532902370d660df"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":248005,"upload-time":"2022-10-18T20:14:48.240035Z","url":"https://files.pythonhosted.org/packages/69/cf/47a028bbb4589fdc0494bc60f134c73e319ec78c86c37e2dc66fd118e4db/psutil-5.9.3-cp38-cp38-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp39-cp39-macosx_10_9_x86_64.whl","hashes":{"sha256":"538fcf6ae856b5e12d13d7da25ad67f02113c96f5989e6ad44422cb5994ca7fc"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242570,"upload-time":"2022-10-18T20:14:51.709705Z","url":"https://files.pythonhosted.org/packages/e5/64/ced1461fd5ebc944d90f9e471149991893bd7ede05b5a88069c1953738dc/psutil-5.9.3-cp39-cp39-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp39-cp39-macosx_11_0_arm64.whl","hashes":{"sha256":"a3d81165b8474087bb90ec4f333a638ccfd1d69d34a9b4a1a7eaac06648f9fbe"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243242,"upload-time":"2022-10-18T20:14:54.641746Z","url":"https://files.pythonhosted.org/packages/ac/55/c108e74f22905382aeeef56110bd6c4b89b5fc64944d21cb83acb66faa4c/psutil-5.9.3-cp39-cp39-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"3a7826e68b0cf4ce2c1ee385d64eab7d70e3133171376cac53d7c1790357ec8f"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":288533,"upload-time":"2022-10-18T20:14:57.902650Z","url":"https://files.pythonhosted.org/packages/db/6f/2441388c48306f9b9d561080c6ba652b4ebd1199faac237069ec8983c8ef/psutil-5.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"data-dist-info-metadata":{"sha256":"235e00601e80ff0d0cb06ce872f02a457343187ed6fdfb79f6f9f1302b209409"},"filename":"psutil-5.9.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"9ec296f565191f89c48f33d9544d8d82b0d2af7dd7d2d4e6319f27a818f8d1cc"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":291401,"upload-time":"2022-10-18T20:15:01.503982Z","url":"https://files.pythonhosted.org/packages/03/47/15604dd812b1b860e81cabaf8c930474c549773389170cd03a093ecf54b6/psutil-5.9.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"data-dist-info-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"filename":"psutil-5.9.3-cp39-cp39-win32.whl","hashes":{"sha256":"9ec95df684583b5596c82bb380c53a603bb051cf019d5c849c47e117c5064395"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242954,"upload-time":"2022-10-18T20:15:04.992990Z","url":"https://files.pythonhosted.org/packages/2f/5e/c74dab9858ca67a68a543ad8fefac2aec107383c171019b45ba9ac5223c1/psutil-5.9.3-cp39-cp39-win32.whl","yanked":false},{"core-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"data-dist-info-metadata":{"sha256":"ddacf95a106563420486b913e49d19570b225ee95ad876d657e72d3df9c09536"},"filename":"psutil-5.9.3-cp39-cp39-win_amd64.whl","hashes":{"sha256":"4bd4854f0c83aa84a5a40d3b5d0eb1f3c128f4146371e03baed4589fe4f3c931"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":247081,"upload-time":"2022-10-18T20:15:08.131670Z","url":"https://files.pythonhosted.org/packages/34/31/9aa19bf0fb0cecae904c9e1ac400c5704d935252515da605aa08fca2be86/psutil-5.9.3-cp39-cp39-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.9.3.tar.gz","hashes":{"sha256":"7ccfcdfea4fc4b0a02ca2c31de7fcd186beb9cff8207800e14ab66f79c773af6"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":483579,"upload-time":"2022-10-18T20:15:11.635566Z","url":"https://files.pythonhosted.org/packages/de/eb/1c01a34c86ee3b058c556e407ce5b07cb7d186ebe47b3e69d6f152ca5cc5/psutil-5.9.3.tar.gz","yanked":false},{"core-metadata":{"sha256":"2123949a577021ace561405ef01228f83e4c6d7ccd4ec38ded6fd271e4243960"},"data-dist-info-metadata":{"sha256":"2123949a577021ace561405ef01228f83e4c6d7ccd4ec38ded6fd271e4243960"},"filename":"psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl","hashes":{"sha256":"c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242993,"upload-time":"2022-11-07T18:44:23.237667Z","url":"https://files.pythonhosted.org/packages/60/f8/b92fecd5297edcecda825a04dfde7cb0a2ecd178eb976cb5a7956e375c6a/psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"2123949a577021ace561405ef01228f83e4c6d7ccd4ec38ded6fd271e4243960"},"data-dist-info-metadata":{"sha256":"2123949a577021ace561405ef01228f83e4c6d7ccd4ec38ded6fd271e4243960"},"filename":"psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":294126,"upload-time":"2022-11-07T18:44:28.809923Z","url":"https://files.pythonhosted.org/packages/8e/6b/9a3a5471b74d92dc85bfd71a7f7a55e013b258d86b4c3826ace9d49f7b8c/psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"2123949a577021ace561405ef01228f83e4c6d7ccd4ec38ded6fd271e4243960"},"data-dist-info-metadata":{"sha256":"2123949a577021ace561405ef01228f83e4c6d7ccd4ec38ded6fd271e4243960"},"filename":"psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":298394,"upload-time":"2022-11-07T18:44:34.503099Z","url":"https://files.pythonhosted.org/packages/1d/80/e1502ba4ff65390bd17b4612010762075f64f5a0e7c28e889c4820bd95a9/psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"8fc2f52ee81f3c31e5cf1d7031e3122056d1ec9de55bbc714417091f2e0c5d34"},"data-dist-info-metadata":{"sha256":"8fc2f52ee81f3c31e5cf1d7031e3122056d1ec9de55bbc714417091f2e0c5d34"},"filename":"psutil-5.9.4-cp27-cp27m-win32.whl","hashes":{"sha256":"852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":242001,"upload-time":"2022-11-07T18:44:39.844844Z","url":"https://files.pythonhosted.org/packages/53/ae/536719016fe9399187dbf52cdc65aef942f82b75924495918a2f701bcb77/psutil-5.9.4-cp27-cp27m-win32.whl","yanked":false},{"core-metadata":{"sha256":"8fc2f52ee81f3c31e5cf1d7031e3122056d1ec9de55bbc714417091f2e0c5d34"},"data-dist-info-metadata":{"sha256":"8fc2f52ee81f3c31e5cf1d7031e3122056d1ec9de55bbc714417091f2e0c5d34"},"filename":"psutil-5.9.4-cp27-cp27m-win_amd64.whl","hashes":{"sha256":"9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":245334,"upload-time":"2022-11-07T18:44:44.877461Z","url":"https://files.pythonhosted.org/packages/99/9c/7a5761f9d2e79e6f781db5b25eeb9e74c2dc533bc52ee4749cb055a32ce9/psutil-5.9.4-cp27-cp27m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2123949a577021ace561405ef01228f83e4c6d7ccd4ec38ded6fd271e4243960"},"data-dist-info-metadata":{"sha256":"2123949a577021ace561405ef01228f83e4c6d7ccd4ec38ded6fd271e4243960"},"filename":"psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":294138,"upload-time":"2022-11-07T18:44:49.025560Z","url":"https://files.pythonhosted.org/packages/ec/be/b8df2071eda861e65a1b2cec35770bb1f4523737e84a10aa41c53e39e9bc/psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"2123949a577021ace561405ef01228f83e4c6d7ccd4ec38ded6fd271e4243960"},"data-dist-info-metadata":{"sha256":"2123949a577021ace561405ef01228f83e4c6d7ccd4ec38ded6fd271e4243960"},"filename":"psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":298409,"upload-time":"2022-11-07T18:44:56.505271Z","url":"https://files.pythonhosted.org/packages/89/a8/dd2f0866a7e87de751fb5f7c6eca99cbb953c81be76e1814ab3c8c3b0908/psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"fa7c88f96167219220d13bcc9fd94ae254e582adb547fdbb756d356aa9a18739"},"data-dist-info-metadata":{"sha256":"fa7c88f96167219220d13bcc9fd94ae254e582adb547fdbb756d356aa9a18739"},"filename":"psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243468,"upload-time":"2022-11-07T18:45:00.474371Z","url":"https://files.pythonhosted.org/packages/a5/73/35cea01aad1baf901c915dc95ea33a2f271c8ff8cf2f1c73b7f591f1bdf1/psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"fa7c88f96167219220d13bcc9fd94ae254e582adb547fdbb756d356aa9a18739"},"data-dist-info-metadata":{"sha256":"fa7c88f96167219220d13bcc9fd94ae254e582adb547fdbb756d356aa9a18739"},"filename":"psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":277515,"upload-time":"2022-11-07T18:45:05.428752Z","url":"https://files.pythonhosted.org/packages/5a/37/ef88eed265d93bc28c681316f68762c5e04167519e5627a0187c8878b409/psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"fa7c88f96167219220d13bcc9fd94ae254e582adb547fdbb756d356aa9a18739"},"data-dist-info-metadata":{"sha256":"fa7c88f96167219220d13bcc9fd94ae254e582adb547fdbb756d356aa9a18739"},"filename":"psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":280218,"upload-time":"2022-11-07T18:45:11.831087Z","url":"https://files.pythonhosted.org/packages/6e/c8/784968329c1c67c28cce91991ef9af8a8913aa5a3399a6a8954b1380572f/psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"bca9c896f97136a7e608898e18955f0413786b5391cd3140eec51917f648c864"},"data-dist-info-metadata":{"sha256":"bca9c896f97136a7e608898e18955f0413786b5391cd3140eec51917f648c864"},"filename":"psutil-5.9.4-cp36-abi3-win32.whl","hashes":{"sha256":"149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":247217,"upload-time":"2022-11-08T11:50:03.989781Z","url":"https://files.pythonhosted.org/packages/3e/af/fe14b984e8b0f778d502d387b789d846cb2fcc3989f63be942741266d8c8/psutil-5.9.4-cp36-abi3-win32.whl","yanked":false},{"core-metadata":{"sha256":"bca9c896f97136a7e608898e18955f0413786b5391cd3140eec51917f648c864"},"data-dist-info-metadata":{"sha256":"bca9c896f97136a7e608898e18955f0413786b5391cd3140eec51917f648c864"},"filename":"psutil-5.9.4-cp36-abi3-win_amd64.whl","hashes":{"sha256":"fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":252462,"upload-time":"2022-11-08T11:50:07.829565Z","url":"https://files.pythonhosted.org/packages/25/6e/ba97809175c90cbdcd33b470e466ebf0854d15d1506e605cc0ddd284d5b6/psutil-5.9.4-cp36-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"3c3c1d22a00986e9736611ca6121b0926aba1c13b75f0b3509aebf5f95b0d409"},"data-dist-info-metadata":{"sha256":"3c3c1d22a00986e9736611ca6121b0926aba1c13b75f0b3509aebf5f95b0d409"},"filename":"psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":244234,"upload-time":"2022-11-07T19:53:20.872251Z","url":"https://files.pythonhosted.org/packages/79/26/f026804298b933b11640cc2d15155a545805df732e5ead3a2ad7cf45a38b/psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.9.4.tar.gz","hashes":{"sha256":"3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":485825,"upload-time":"2022-11-07T19:53:36.245577Z","url":"https://files.pythonhosted.org/packages/3d/7d/d05864a69e452f003c0d77e728e155a89a2a26b09e64860ddd70ad64fb26/psutil-5.9.4.tar.gz","yanked":false},{"core-metadata":{"sha256":"950f52c16a7bc656305be0171e1d567927034839d8c5af80880070db4ba278be"},"data-dist-info-metadata":{"sha256":"950f52c16a7bc656305be0171e1d567927034839d8c5af80880070db4ba278be"},"filename":"psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl","hashes":{"sha256":"be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":244852,"upload-time":"2023-04-17T18:24:26.646150Z","url":"https://files.pythonhosted.org/packages/3b/e4/fee119c206545fd37be1e5fa4eeb0c729a52ec2ade4f728ae1fd1acb2a3a/psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"950f52c16a7bc656305be0171e1d567927034839d8c5af80880070db4ba278be"},"data-dist-info-metadata":{"sha256":"950f52c16a7bc656305be0171e1d567927034839d8c5af80880070db4ba278be"},"filename":"psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":296014,"upload-time":"2023-04-17T18:24:31.346064Z","url":"https://files.pythonhosted.org/packages/8d/24/ed6b6506f187def39887a91a68e58336eff4cf3e3d5a163ded58bee98624/psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"950f52c16a7bc656305be0171e1d567927034839d8c5af80880070db4ba278be"},"data-dist-info-metadata":{"sha256":"950f52c16a7bc656305be0171e1d567927034839d8c5af80880070db4ba278be"},"filename":"psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":300274,"upload-time":"2023-04-17T18:24:35.244779Z","url":"https://files.pythonhosted.org/packages/89/fa/ab117fa86195050802207639f5daee857791daaabe9a996935b5b77dbe10/psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"950f52c16a7bc656305be0171e1d567927034839d8c5af80880070db4ba278be"},"data-dist-info-metadata":{"sha256":"950f52c16a7bc656305be0171e1d567927034839d8c5af80880070db4ba278be"},"filename":"psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":295979,"upload-time":"2023-04-17T18:24:38.850555Z","url":"https://files.pythonhosted.org/packages/99/f5/ec768e107445f18baa907509aaa0562a4d148a602bd97e8114d79bd6c84d/psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"950f52c16a7bc656305be0171e1d567927034839d8c5af80880070db4ba278be"},"data-dist-info-metadata":{"sha256":"950f52c16a7bc656305be0171e1d567927034839d8c5af80880070db4ba278be"},"filename":"psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":300257,"upload-time":"2023-04-17T18:24:42.928863Z","url":"https://files.pythonhosted.org/packages/5f/da/de9d2342db0b7a96863ef84ab94ef1022eec78ece05aac253cddc494e1a7/psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"b8327a43c6d94aba442f903852217084687e619e3d853297d6743f1cd1a4fada"},"data-dist-info-metadata":{"sha256":"b8327a43c6d94aba442f903852217084687e619e3d853297d6743f1cd1a4fada"},"filename":"psutil-5.9.5-cp27-none-win32.whl","hashes":{"sha256":"5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":243956,"upload-time":"2023-04-17T18:24:46.410452Z","url":"https://files.pythonhosted.org/packages/cf/e3/6af6ec0cbe72f63e9a16d8b53590489e40ed0ff0c99b6a6f05d6af3bb80e/psutil-5.9.5-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"b8327a43c6d94aba442f903852217084687e619e3d853297d6743f1cd1a4fada"},"data-dist-info-metadata":{"sha256":"b8327a43c6d94aba442f903852217084687e619e3d853297d6743f1cd1a4fada"},"filename":"psutil-5.9.5-cp27-none-win_amd64.whl","hashes":{"sha256":"8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":247338,"upload-time":"2023-04-17T18:24:49.089552Z","url":"https://files.pythonhosted.org/packages/26/f2/dcd8a3cc9c9b1fcd7576a54e3603ce4d1f85672f2687a44050340f7d47b0/psutil-5.9.5-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"612932ce0d3ae556043e5e9a609d33777fe6fb88556dd92d0e07cb53270d1db6"},"data-dist-info-metadata":{"sha256":"612932ce0d3ae556043e5e9a609d33777fe6fb88556dd92d0e07cb53270d1db6"},"filename":"psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":245316,"upload-time":"2023-04-17T18:24:52.864585Z","url":"https://files.pythonhosted.org/packages/9a/76/c0195c3443a725c24b3a479f57636dec89efe53d19d435d1752c5188f7de/psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"612932ce0d3ae556043e5e9a609d33777fe6fb88556dd92d0e07cb53270d1db6"},"data-dist-info-metadata":{"sha256":"612932ce0d3ae556043e5e9a609d33777fe6fb88556dd92d0e07cb53270d1db6"},"filename":"psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":279398,"upload-time":"2023-04-17T18:24:56.977087Z","url":"https://files.pythonhosted.org/packages/e5/2e/56db2b45508ad484b3f22888b3e1adaaf09b8766eaa058ed0e4486c1abae/psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"612932ce0d3ae556043e5e9a609d33777fe6fb88556dd92d0e07cb53270d1db6"},"data-dist-info-metadata":{"sha256":"612932ce0d3ae556043e5e9a609d33777fe6fb88556dd92d0e07cb53270d1db6"},"filename":"psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":282082,"upload-time":"2023-04-17T18:25:00.863664Z","url":"https://files.pythonhosted.org/packages/af/4d/389441079ecef400e2551a3933224885a7bde6b8a4810091d628cdd75afe/psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"612932ce0d3ae556043e5e9a609d33777fe6fb88556dd92d0e07cb53270d1db6"},"data-dist-info-metadata":{"sha256":"612932ce0d3ae556043e5e9a609d33777fe6fb88556dd92d0e07cb53270d1db6"},"filename":"psutil-5.9.5-cp36-abi3-win32.whl","hashes":{"sha256":"104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":249834,"upload-time":"2023-04-17T18:25:05.571829Z","url":"https://files.pythonhosted.org/packages/fa/e0/e91277b1cabf5c3f2995c22314553f1be68b17444260101f365c5a5b6ba1/psutil-5.9.5-cp36-abi3-win32.whl","yanked":false},{"core-metadata":{"sha256":"612932ce0d3ae556043e5e9a609d33777fe6fb88556dd92d0e07cb53270d1db6"},"data-dist-info-metadata":{"sha256":"612932ce0d3ae556043e5e9a609d33777fe6fb88556dd92d0e07cb53270d1db6"},"filename":"psutil-5.9.5-cp36-abi3-win_amd64.whl","hashes":{"sha256":"b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":255148,"upload-time":"2023-04-17T18:25:09.779955Z","url":"https://files.pythonhosted.org/packages/86/f3/23e4e4e7ec7855d506ed928756b04735c246b14d9f778ed7ffaae18d8043/psutil-5.9.5-cp36-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2ae643bfca9fa3b942bf775226368a8ef859018ea312be94e137a8511ba0da07"},"data-dist-info-metadata":{"sha256":"2ae643bfca9fa3b942bf775226368a8ef859018ea312be94e137a8511ba0da07"},"filename":"psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":246094,"upload-time":"2023-04-17T18:25:14.584295Z","url":"https://files.pythonhosted.org/packages/ed/98/2624954f83489ab13fde2b544baa337d5578c07eee304d320d9ba56e1b1f/psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.9.5.tar.gz","hashes":{"sha256":"5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*","size":493489,"upload-time":"2023-04-17T18:25:18.787463Z","url":"https://files.pythonhosted.org/packages/d6/0f/96b7309212a926c1448366e9ce69b081ea79d63265bde33f11cc9cfc2c07/psutil-5.9.5.tar.gz","yanked":false},{"core-metadata":{"sha256":"03f47dbf7a696dbe30acaf142fc193530fe197d70ea954e60b2a6279946aca36"},"data-dist-info-metadata":{"sha256":"03f47dbf7a696dbe30acaf142fc193530fe197d70ea954e60b2a6279946aca36"},"filename":"psutil-5.9.6-cp27-cp27m-macosx_10_9_x86_64.whl","hashes":{"sha256":"fb8a697f11b0f5994550555fcfe3e69799e5b060c8ecf9e2f75c69302cc35c0d"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":245665,"upload-time":"2023-10-15T09:08:50.362285Z","url":"https://files.pythonhosted.org/packages/84/d6/7e23b2b208db3953f630934bc0e9c1736a0a831a781acf8c5891c27b29cf/psutil-5.9.6-cp27-cp27m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"03f47dbf7a696dbe30acaf142fc193530fe197d70ea954e60b2a6279946aca36"},"data-dist-info-metadata":{"sha256":"03f47dbf7a696dbe30acaf142fc193530fe197d70ea954e60b2a6279946aca36"},"filename":"psutil-5.9.6-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"91ecd2d9c00db9817a4b4192107cf6954addb5d9d67a969a4f436dbc9200f88c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":297167,"upload-time":"2023-10-15T09:08:52.370112Z","url":"https://files.pythonhosted.org/packages/d2/76/f154e5169756f3d18da160359a404f49f476756809ef21a79afdd0d5b552/psutil-5.9.6-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"03f47dbf7a696dbe30acaf142fc193530fe197d70ea954e60b2a6279946aca36"},"data-dist-info-metadata":{"sha256":"03f47dbf7a696dbe30acaf142fc193530fe197d70ea954e60b2a6279946aca36"},"filename":"psutil-5.9.6-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"10e8c17b4f898d64b121149afb136c53ea8b68c7531155147867b7b1ac9e7e28"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":301338,"upload-time":"2023-10-15T09:08:55.187829Z","url":"https://files.pythonhosted.org/packages/35/e8/5cc0e149ec32a91d459fbe51d0ce3c2dd7f8d67bc1400803ff810247d6dc/psutil-5.9.6-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"03f47dbf7a696dbe30acaf142fc193530fe197d70ea954e60b2a6279946aca36"},"data-dist-info-metadata":{"sha256":"03f47dbf7a696dbe30acaf142fc193530fe197d70ea954e60b2a6279946aca36"},"filename":"psutil-5.9.6-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"18cd22c5db486f33998f37e2bb054cc62fd06646995285e02a51b1e08da97017"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":297167,"upload-time":"2023-10-15T09:08:57.863175Z","url":"https://files.pythonhosted.org/packages/8d/f7/074071fa91dab747c8d1fe2eb74da439b3712248d6b254ba0136ada8694f/psutil-5.9.6-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"03f47dbf7a696dbe30acaf142fc193530fe197d70ea954e60b2a6279946aca36"},"data-dist-info-metadata":{"sha256":"03f47dbf7a696dbe30acaf142fc193530fe197d70ea954e60b2a6279946aca36"},"filename":"psutil-5.9.6-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"ca2780f5e038379e520281e4c032dddd086906ddff9ef0d1b9dcf00710e5071c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":301350,"upload-time":"2023-10-15T09:09:00.411072Z","url":"https://files.pythonhosted.org/packages/4a/65/557545149422a7845248641c1c35a0c8ea940c838896320f774072e16523/psutil-5.9.6-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"cbf967d735616b8f7384d1cb095719fdf508b6e794f91eda3c559b9eff000943"},"data-dist-info-metadata":{"sha256":"cbf967d735616b8f7384d1cb095719fdf508b6e794f91eda3c559b9eff000943"},"filename":"psutil-5.9.6-cp27-none-win32.whl","hashes":{"sha256":"70cb3beb98bc3fd5ac9ac617a327af7e7f826373ee64c80efd4eb2856e5051e9"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":244900,"upload-time":"2023-10-15T09:09:03.051464Z","url":"https://files.pythonhosted.org/packages/b8/23/d5d9e20c4ae7374abe1f826c69ecf2ab52f93827ca2b92c2c51f9aeb9226/psutil-5.9.6-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"cbf967d735616b8f7384d1cb095719fdf508b6e794f91eda3c559b9eff000943"},"data-dist-info-metadata":{"sha256":"cbf967d735616b8f7384d1cb095719fdf508b6e794f91eda3c559b9eff000943"},"filename":"psutil-5.9.6-cp27-none-win_amd64.whl","hashes":{"sha256":"51dc3d54607c73148f63732c727856f5febec1c7c336f8f41fcbd6315cce76ac"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":248205,"upload-time":"2023-10-15T09:09:05.456917Z","url":"https://files.pythonhosted.org/packages/7a/5e/db765b94cb620c04aaea0cb03d8b589905e50ec278130d25646eead8dff0/psutil-5.9.6-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"2e9d8110f5906489070a50d523dc29d42ccde7cff56677d3b49eae7ac8f23006"},"data-dist-info-metadata":{"sha256":"2e9d8110f5906489070a50d523dc29d42ccde7cff56677d3b49eae7ac8f23006"},"filename":"psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":246101,"upload-time":"2023-10-15T09:09:08.012635Z","url":"https://files.pythonhosted.org/packages/f8/36/35b12441ba1bc6684c9215191f955415196ca57ca85d88e313bec7f2cf8e/psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"2e9d8110f5906489070a50d523dc29d42ccde7cff56677d3b49eae7ac8f23006"},"data-dist-info-metadata":{"sha256":"2e9d8110f5906489070a50d523dc29d42ccde7cff56677d3b49eae7ac8f23006"},"filename":"psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":280854,"upload-time":"2023-10-15T09:09:09.832401Z","url":"https://files.pythonhosted.org/packages/61/c8/e684dea1912943347922ab5c05efc94b4ff3d7470038e8afbe3941ef9efe/psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"2e9d8110f5906489070a50d523dc29d42ccde7cff56677d3b49eae7ac8f23006"},"data-dist-info-metadata":{"sha256":"2e9d8110f5906489070a50d523dc29d42ccde7cff56677d3b49eae7ac8f23006"},"filename":"psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":283614,"upload-time":"2023-10-15T09:09:12.314910Z","url":"https://files.pythonhosted.org/packages/19/06/4e3fa3c1b79271e933c5ddbad3a48aa2c3d5f592a0fb7c037f3e0f619f4d/psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"2e9d8110f5906489070a50d523dc29d42ccde7cff56677d3b49eae7ac8f23006"},"data-dist-info-metadata":{"sha256":"2e9d8110f5906489070a50d523dc29d42ccde7cff56677d3b49eae7ac8f23006"},"filename":"psutil-5.9.6-cp36-cp36m-win32.whl","hashes":{"sha256":"3ebf2158c16cc69db777e3c7decb3c0f43a7af94a60d72e87b2823aebac3d602"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":250445,"upload-time":"2023-10-15T09:09:14.942143Z","url":"https://files.pythonhosted.org/packages/3f/63/d4a8dace1756b9c84b94683aa80ed0ba8fc7a4421904933b472d59268976/psutil-5.9.6-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"2e9d8110f5906489070a50d523dc29d42ccde7cff56677d3b49eae7ac8f23006"},"data-dist-info-metadata":{"sha256":"2e9d8110f5906489070a50d523dc29d42ccde7cff56677d3b49eae7ac8f23006"},"filename":"psutil-5.9.6-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"ff18b8d1a784b810df0b0fff3bcb50ab941c3b8e2c8de5726f9c71c601c611aa"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":255893,"upload-time":"2023-10-15T09:09:17.467925Z","url":"https://files.pythonhosted.org/packages/ad/00/c87d449746f8962eb9203554b46ab7dcf243be236dcf007372902791b374/psutil-5.9.6-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"3eb79e7ee359462d9b616150575b67510adf8a297f3b4c2b93aeb95daef17fb8"},"data-dist-info-metadata":{"sha256":"3eb79e7ee359462d9b616150575b67510adf8a297f3b4c2b93aeb95daef17fb8"},"filename":"psutil-5.9.6-cp37-abi3-win32.whl","hashes":{"sha256":"a6f01f03bf1843280f4ad16f4bde26b817847b4c1a0db59bf6419807bc5ce05c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":248489,"upload-time":"2023-10-15T09:09:19.912001Z","url":"https://files.pythonhosted.org/packages/06/ac/f31a0faf98267e63fc6ed046ad2aca68bd79521380026e92fd4921c869aa/psutil-5.9.6-cp37-abi3-win32.whl","yanked":false},{"core-metadata":{"sha256":"3eb79e7ee359462d9b616150575b67510adf8a297f3b4c2b93aeb95daef17fb8"},"data-dist-info-metadata":{"sha256":"3eb79e7ee359462d9b616150575b67510adf8a297f3b4c2b93aeb95daef17fb8"},"filename":"psutil-5.9.6-cp37-abi3-win_amd64.whl","hashes":{"sha256":"6e5fb8dc711a514da83098bc5234264e551ad980cec5f85dabf4d38ed6f15e9a"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":252327,"upload-time":"2023-10-15T09:09:32.052033Z","url":"https://files.pythonhosted.org/packages/c5/b2/699c50fe0b0402a1ccb64ad71313bcb740e735008dd3ab9abeddbe148e45/psutil-5.9.6-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"7298b90db12439da117e2817e35cbbb00312edeb4f885274cd1135a533903d6c"},"data-dist-info-metadata":{"sha256":"7298b90db12439da117e2817e35cbbb00312edeb4f885274cd1135a533903d6c"},"filename":"psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":246859,"upload-time":"2023-10-15T09:09:34.494297Z","url":"https://files.pythonhosted.org/packages/9e/cb/e4b83c27eea66bc255effc967053f6fce7c14906dd9b43a348ead9f0cfea/psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.9.6.tar.gz","hashes":{"sha256":"e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":496866,"upload-time":"2023-10-15T09:08:46.623978Z","url":"https://files.pythonhosted.org/packages/2d/01/beb7331fc6c8d1c49dd051e3611379bfe379e915c808e1301506027fce9d/psutil-5.9.6.tar.gz","yanked":false},{"core-metadata":{"sha256":"bee12356aebb79b07f3adec05e4a7e1cad1a4e76f3cbf8f7617b97d7598ed4e8"},"data-dist-info-metadata":{"sha256":"bee12356aebb79b07f3adec05e4a7e1cad1a4e76f3cbf8f7617b97d7598ed4e8"},"filename":"psutil-5.9.7-cp27-cp27m-macosx_10_9_x86_64.whl","hashes":{"sha256":"0bd41bf2d1463dfa535942b2a8f0e958acf6607ac0be52265ab31f7923bcd5e6"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":245542,"upload-time":"2023-12-17T11:25:25.875219Z","url":"https://files.pythonhosted.org/packages/3e/16/c86fcf73f02bd0a3d49b0dcabc8ebd4020647be2ea40ff668f717587af97/psutil-5.9.7-cp27-cp27m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"bee12356aebb79b07f3adec05e4a7e1cad1a4e76f3cbf8f7617b97d7598ed4e8"},"data-dist-info-metadata":{"sha256":"bee12356aebb79b07f3adec05e4a7e1cad1a4e76f3cbf8f7617b97d7598ed4e8"},"filename":"psutil-5.9.7-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"5794944462509e49d4d458f4dbfb92c47539e7d8d15c796f141f474010084056"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":312042,"upload-time":"2023-12-17T11:25:29.439835Z","url":"https://files.pythonhosted.org/packages/93/fc/e45a8e9b2acd54fe80ededa2f7b19de21e776f64e00437417c16c3e139d9/psutil-5.9.7-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"bee12356aebb79b07f3adec05e4a7e1cad1a4e76f3cbf8f7617b97d7598ed4e8"},"data-dist-info-metadata":{"sha256":"bee12356aebb79b07f3adec05e4a7e1cad1a4e76f3cbf8f7617b97d7598ed4e8"},"filename":"psutil-5.9.7-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"fe361f743cb3389b8efda21980d93eb55c1f1e3898269bc9a2a1d0bb7b1f6508"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":312681,"upload-time":"2023-12-17T11:25:33.024609Z","url":"https://files.pythonhosted.org/packages/d7/43/dd7034a3a3a900e95b9dcf47ee710680cfd11a224ab18b31c34370da36a8/psutil-5.9.7-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"bee12356aebb79b07f3adec05e4a7e1cad1a4e76f3cbf8f7617b97d7598ed4e8"},"data-dist-info-metadata":{"sha256":"bee12356aebb79b07f3adec05e4a7e1cad1a4e76f3cbf8f7617b97d7598ed4e8"},"filename":"psutil-5.9.7-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"e469990e28f1ad738f65a42dcfc17adaed9d0f325d55047593cb9033a0ab63df"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":312066,"upload-time":"2023-12-17T11:25:37.010306Z","url":"https://files.pythonhosted.org/packages/ff/ea/a47eecddcd97d65b496ac655c9f9ba8af270c203d5ea1630273cfc5ec740/psutil-5.9.7-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"bee12356aebb79b07f3adec05e4a7e1cad1a4e76f3cbf8f7617b97d7598ed4e8"},"data-dist-info-metadata":{"sha256":"bee12356aebb79b07f3adec05e4a7e1cad1a4e76f3cbf8f7617b97d7598ed4e8"},"filename":"psutil-5.9.7-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"3c4747a3e2ead1589e647e64aad601981f01b68f9398ddf94d01e3dc0d1e57c7"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":312684,"upload-time":"2023-12-17T11:25:39.891899Z","url":"https://files.pythonhosted.org/packages/cd/ee/d946d0b758120e724d9cdd9607c304ff1eedb9380bf60597c295dc7def6b/psutil-5.9.7-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"e17b0155d6125da908d40f364ab8665d24296d45f194128d7d4fee06adf24366"},"data-dist-info-metadata":{"sha256":"e17b0155d6125da908d40f364ab8665d24296d45f194128d7d4fee06adf24366"},"filename":"psutil-5.9.7-cp27-none-win32.whl","hashes":{"sha256":"1d4bc4a0148fdd7fd8f38e0498639ae128e64538faa507df25a20f8f7fb2341c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":244785,"upload-time":"2023-12-17T11:25:42.761256Z","url":"https://files.pythonhosted.org/packages/98/c5/6773a3f1c384ac4863665e167cd4da72433b3020580c0b7c6a7b497e11e2/psutil-5.9.7-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"e17b0155d6125da908d40f364ab8665d24296d45f194128d7d4fee06adf24366"},"data-dist-info-metadata":{"sha256":"e17b0155d6125da908d40f364ab8665d24296d45f194128d7d4fee06adf24366"},"filename":"psutil-5.9.7-cp27-none-win_amd64.whl","hashes":{"sha256":"4c03362e280d06bbbfcd52f29acd79c733e0af33d707c54255d21029b8b32ba6"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":248087,"upload-time":"2023-12-17T11:25:45.316036Z","url":"https://files.pythonhosted.org/packages/2d/91/40ac017db38c9f7f325385dd0dab1be3d4c65e3291100e74d5d7b6a213e8/psutil-5.9.7-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"75f8cc7702054674e6edcb7c6c070053f879e044c73942a152279c8d35fec61c"},"data-dist-info-metadata":{"sha256":"75f8cc7702054674e6edcb7c6c070053f879e044c73942a152279c8d35fec61c"},"filename":"psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":245972,"upload-time":"2023-12-17T11:25:48.202730Z","url":"https://files.pythonhosted.org/packages/6c/63/86a4ccc640b4ee1193800f57bbd20b766853c0cdbdbb248a27cdfafe6cbf/psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"75f8cc7702054674e6edcb7c6c070053f879e044c73942a152279c8d35fec61c"},"data-dist-info-metadata":{"sha256":"75f8cc7702054674e6edcb7c6c070053f879e044c73942a152279c8d35fec61c"},"filename":"psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":282514,"upload-time":"2023-12-17T11:25:51.371460Z","url":"https://files.pythonhosted.org/packages/58/80/cc6666b3968646f2d94de66bbc63d701d501f4aa04de43dd7d1f5dc477dd/psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"75f8cc7702054674e6edcb7c6c070053f879e044c73942a152279c8d35fec61c"},"data-dist-info-metadata":{"sha256":"75f8cc7702054674e6edcb7c6c070053f879e044c73942a152279c8d35fec61c"},"filename":"psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":285469,"upload-time":"2023-12-17T11:25:54.250669Z","url":"https://files.pythonhosted.org/packages/be/fa/f1f626620e3b47e6237dcc64cb8cc1472f139e99422e5b9fa5bbcf457f48/psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"75f8cc7702054674e6edcb7c6c070053f879e044c73942a152279c8d35fec61c"},"data-dist-info-metadata":{"sha256":"75f8cc7702054674e6edcb7c6c070053f879e044c73942a152279c8d35fec61c"},"filename":"psutil-5.9.7-cp36-cp36m-win32.whl","hashes":{"sha256":"b27f8fdb190c8c03914f908a4555159327d7481dac2f01008d483137ef3311a9"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":250357,"upload-time":"2023-12-17T12:38:23.681291Z","url":"https://files.pythonhosted.org/packages/63/16/11dfb52cdccd561da711ee2c127b4c0bd2baf4736d10828c707694f31b90/psutil-5.9.7-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"75f8cc7702054674e6edcb7c6c070053f879e044c73942a152279c8d35fec61c"},"data-dist-info-metadata":{"sha256":"75f8cc7702054674e6edcb7c6c070053f879e044c73942a152279c8d35fec61c"},"filename":"psutil-5.9.7-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"44969859757f4d8f2a9bd5b76eba8c3099a2c8cf3992ff62144061e39ba8568e"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":255808,"upload-time":"2023-12-17T12:38:34.170079Z","url":"https://files.pythonhosted.org/packages/0e/88/9b74b25c63b91ff0403a1b89e258238380b4a88e4116cbae4eaadbb4c17a/psutil-5.9.7-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"8eee389ea25ebe96edb28822645ab1709ede8c32315d59c0c91214e32d607b8e"},"data-dist-info-metadata":{"sha256":"8eee389ea25ebe96edb28822645ab1709ede8c32315d59c0c91214e32d607b8e"},"filename":"psutil-5.9.7-cp37-abi3-win32.whl","hashes":{"sha256":"c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":248406,"upload-time":"2023-12-17T12:38:50.326952Z","url":"https://files.pythonhosted.org/packages/7c/b8/dc6ebfc030b47cccc5f5229eeb15e64142b4782796c3ce169ccd60b4d511/psutil-5.9.7-cp37-abi3-win32.whl","yanked":false},{"core-metadata":{"sha256":"8eee389ea25ebe96edb28822645ab1709ede8c32315d59c0c91214e32d607b8e"},"data-dist-info-metadata":{"sha256":"8eee389ea25ebe96edb28822645ab1709ede8c32315d59c0c91214e32d607b8e"},"filename":"psutil-5.9.7-cp37-abi3-win_amd64.whl","hashes":{"sha256":"f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":252245,"upload-time":"2023-12-17T12:39:00.686632Z","url":"https://files.pythonhosted.org/packages/50/28/92b74d95dd991c837813ffac0c79a581a3d129eb0fa7c1dd616d9901e0f3/psutil-5.9.7-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"c6272489f71a5b9474dff03296e3f8e100ef6f8ed990f7e4ef444ac6e6a3d6fb"},"data-dist-info-metadata":{"sha256":"c6272489f71a5b9474dff03296e3f8e100ef6f8ed990f7e4ef444ac6e6a3d6fb"},"filename":"psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":246739,"upload-time":"2023-12-17T11:25:57.305436Z","url":"https://files.pythonhosted.org/packages/ba/8a/000d0e80156f0b96c55bda6c60f5ed6543d7b5e893ccab83117e50de1400/psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.9.7.tar.gz","hashes":{"sha256":"3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":498429,"upload-time":"2023-12-17T11:25:21.220127Z","url":"https://files.pythonhosted.org/packages/a0/d0/c9ae661a302931735237791f04cb7086ac244377f78692ba3b3eae3a9619/psutil-5.9.7.tar.gz","yanked":false},{"core-metadata":{"sha256":"8da6995843b9daa47e0867912881271ea684ac8d0bcb3aee77791084ee8c0be4"},"data-dist-info-metadata":{"sha256":"8da6995843b9daa47e0867912881271ea684ac8d0bcb3aee77791084ee8c0be4"},"filename":"psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl","hashes":{"sha256":"26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":248274,"upload-time":"2024-01-19T20:47:14.006890Z","url":"https://files.pythonhosted.org/packages/15/9a/c3e2922e2d672bafd37cf3b9681097c350463cdcf0e286e907ddd6cfb014/psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"8da6995843b9daa47e0867912881271ea684ac8d0bcb3aee77791084ee8c0be4"},"data-dist-info-metadata":{"sha256":"8da6995843b9daa47e0867912881271ea684ac8d0bcb3aee77791084ee8c0be4"},"filename":"psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":314796,"upload-time":"2024-01-19T20:47:17.872998Z","url":"https://files.pythonhosted.org/packages/62/e6/6d62285989d53a83def28ea49b46d3e00462d1273c7c47d9678ee28a0a39/psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"8da6995843b9daa47e0867912881271ea684ac8d0bcb3aee77791084ee8c0be4"},"data-dist-info-metadata":{"sha256":"8da6995843b9daa47e0867912881271ea684ac8d0bcb3aee77791084ee8c0be4"},"filename":"psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":315422,"upload-time":"2024-01-19T20:47:21.442877Z","url":"https://files.pythonhosted.org/packages/38/ba/41815f353f79374c1ad82aba998c666c7209793daf12f4799cfaa7302f29/psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"8da6995843b9daa47e0867912881271ea684ac8d0bcb3aee77791084ee8c0be4"},"data-dist-info-metadata":{"sha256":"8da6995843b9daa47e0867912881271ea684ac8d0bcb3aee77791084ee8c0be4"},"filename":"psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":314802,"upload-time":"2024-01-19T20:47:24.219052Z","url":"https://files.pythonhosted.org/packages/a8/2f/ad80cc502c452e1f207307a7d53533505ca47c503ec6e9f7e2c9fbb367e8/psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"8da6995843b9daa47e0867912881271ea684ac8d0bcb3aee77791084ee8c0be4"},"data-dist-info-metadata":{"sha256":"8da6995843b9daa47e0867912881271ea684ac8d0bcb3aee77791084ee8c0be4"},"filename":"psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":315420,"upload-time":"2024-01-19T20:47:26.828052Z","url":"https://files.pythonhosted.org/packages/e4/c3/357a292dee683282f7a46b752a76c5d56c78bf8f5d9def0ca0d39073344a/psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"bcd64b1f8b81a5a0ea3cff661abf5d6e3a937f442bb982b9bdcddbfd8608aa9c"},"data-dist-info-metadata":{"sha256":"bcd64b1f8b81a5a0ea3cff661abf5d6e3a937f442bb982b9bdcddbfd8608aa9c"},"filename":"psutil-5.9.8-cp27-none-win32.whl","hashes":{"sha256":"36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":248660,"upload-time":"2024-01-19T20:47:29.706532Z","url":"https://files.pythonhosted.org/packages/fe/5f/c26deb822fd3daf8fde4bdb658bf87d9ab1ffd3fca483816e89a9a9a9084/psutil-5.9.8-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"bcd64b1f8b81a5a0ea3cff661abf5d6e3a937f442bb982b9bdcddbfd8608aa9c"},"data-dist-info-metadata":{"sha256":"bcd64b1f8b81a5a0ea3cff661abf5d6e3a937f442bb982b9bdcddbfd8608aa9c"},"filename":"psutil-5.9.8-cp27-none-win_amd64.whl","hashes":{"sha256":"bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":251966,"upload-time":"2024-01-19T20:47:33.134054Z","url":"https://files.pythonhosted.org/packages/32/1d/cf66073d74d6146187e2d0081a7616df4437214afa294ee4f16f80a2f96a/psutil-5.9.8-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"19f679f9f89daead325ce0a0c8de1fba70a554793a47fcdf273fb714ac742a35"},"data-dist-info-metadata":{"sha256":"19f679f9f89daead325ce0a0c8de1fba70a554793a47fcdf273fb714ac742a35"},"filename":"psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":248702,"upload-time":"2024-01-19T20:47:36.303498Z","url":"https://files.pythonhosted.org/packages/e7/e3/07ae864a636d70a8a6f58da27cb1179192f1140d5d1da10886ade9405797/psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"19f679f9f89daead325ce0a0c8de1fba70a554793a47fcdf273fb714ac742a35"},"data-dist-info-metadata":{"sha256":"19f679f9f89daead325ce0a0c8de1fba70a554793a47fcdf273fb714ac742a35"},"filename":"psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":285242,"upload-time":"2024-01-19T20:47:39.650099Z","url":"https://files.pythonhosted.org/packages/b3/bd/28c5f553667116b2598b9cc55908ec435cb7f77a34f2bff3e3ca765b0f78/psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"19f679f9f89daead325ce0a0c8de1fba70a554793a47fcdf273fb714ac742a35"},"data-dist-info-metadata":{"sha256":"19f679f9f89daead325ce0a0c8de1fba70a554793a47fcdf273fb714ac742a35"},"filename":"psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":288191,"upload-time":"2024-01-19T20:47:43.078208Z","url":"https://files.pythonhosted.org/packages/c5/4f/0e22aaa246f96d6ac87fe5ebb9c5a693fbe8877f537a1022527c47ca43c5/psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"19f679f9f89daead325ce0a0c8de1fba70a554793a47fcdf273fb714ac742a35"},"data-dist-info-metadata":{"sha256":"19f679f9f89daead325ce0a0c8de1fba70a554793a47fcdf273fb714ac742a35"},"filename":"psutil-5.9.8-cp36-cp36m-win32.whl","hashes":{"sha256":"7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":253203,"upload-time":"2024-01-19T20:47:46.133427Z","url":"https://files.pythonhosted.org/packages/dd/9e/85c3bd5b466d96c091bbd6339881e99106adb43d5d60bde32ac181ab6fef/psutil-5.9.8-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"19f679f9f89daead325ce0a0c8de1fba70a554793a47fcdf273fb714ac742a35"},"data-dist-info-metadata":{"sha256":"19f679f9f89daead325ce0a0c8de1fba70a554793a47fcdf273fb714ac742a35"},"filename":"psutil-5.9.8-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":258655,"upload-time":"2024-01-19T20:47:48.804624Z","url":"https://files.pythonhosted.org/packages/0b/58/bcffb5ab03ec558e565d2871c01215dde74e11f583fb71e7d2b107200caa/psutil-5.9.8-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"949401d7571a13b3b43062a6c13b80a2a1361c3da4af20751f844e6ee3750021"},"data-dist-info-metadata":{"sha256":"949401d7571a13b3b43062a6c13b80a2a1361c3da4af20751f844e6ee3750021"},"filename":"psutil-5.9.8-cp37-abi3-win32.whl","hashes":{"sha256":"bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":251252,"upload-time":"2024-01-19T20:47:52.880124Z","url":"https://files.pythonhosted.org/packages/6e/f5/2aa3a4acdc1e5940b59d421742356f133185667dd190b166dbcfcf5d7b43/psutil-5.9.8-cp37-abi3-win32.whl","yanked":false},{"core-metadata":{"sha256":"949401d7571a13b3b43062a6c13b80a2a1361c3da4af20751f844e6ee3750021"},"data-dist-info-metadata":{"sha256":"949401d7571a13b3b43062a6c13b80a2a1361c3da4af20751f844e6ee3750021"},"filename":"psutil-5.9.8-cp37-abi3-win_amd64.whl","hashes":{"sha256":"8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":255090,"upload-time":"2024-01-19T20:47:56.019799Z","url":"https://files.pythonhosted.org/packages/93/52/3e39d26feae7df0aa0fd510b14012c3678b36ed068f7d78b8d8784d61f0e/psutil-5.9.8-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"1526b4912f0f04a9c6523b41c128f4d1c6c666fbbdccb62c6705d7e5747c95cc"},"data-dist-info-metadata":{"sha256":"1526b4912f0f04a9c6523b41c128f4d1c6c666fbbdccb62c6705d7e5747c95cc"},"filename":"psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":249898,"upload-time":"2024-01-19T20:47:59.238740Z","url":"https://files.pythonhosted.org/packages/05/33/2d74d588408caedd065c2497bdb5ef83ce6082db01289a1e1147f6639802/psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-5.9.8.tar.gz","hashes":{"sha256":"6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"},"provenance":null,"requires-python":">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*","size":503247,"upload-time":"2024-01-19T20:47:09.517227Z","url":"https://files.pythonhosted.org/packages/90/c7/6dc0a455d111f68ee43f27793971cf03fe29b6ef972042549db29eec39a2/psutil-5.9.8.tar.gz","yanked":false},{"core-metadata":{"sha256":"c8301b547ede1d12db293b91c2eda41064442de769d8466cfdb34279587226a9"},"data-dist-info-metadata":{"sha256":"c8301b547ede1d12db293b91c2eda41064442de769d8466cfdb34279587226a9"},"filename":"psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl","hashes":{"sha256":"a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":250527,"upload-time":"2024-06-18T21:40:17.061973Z","url":"https://files.pythonhosted.org/packages/13/e5/35ebd7169008752be5561cafdba3f1634be98193b85fe3d22e883f9fe2e1/psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"c8301b547ede1d12db293b91c2eda41064442de769d8466cfdb34279587226a9"},"data-dist-info-metadata":{"sha256":"c8301b547ede1d12db293b91c2eda41064442de769d8466cfdb34279587226a9"},"filename":"psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":316838,"upload-time":"2024-06-18T21:40:32.679396Z","url":"https://files.pythonhosted.org/packages/92/a7/083388ef0964a6d74df51c677b3d761e0866d823d37e3a8823551c0d375d/psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"c8301b547ede1d12db293b91c2eda41064442de769d8466cfdb34279587226a9"},"data-dist-info-metadata":{"sha256":"c8301b547ede1d12db293b91c2eda41064442de769d8466cfdb34279587226a9"},"filename":"psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":317493,"upload-time":"2024-06-18T21:40:41.710402Z","url":"https://files.pythonhosted.org/packages/52/2f/44b7005f306ea8bfd24aa662b5d0ba6ea1daf29dbd0b6c7bbcd3606373ad/psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"c8301b547ede1d12db293b91c2eda41064442de769d8466cfdb34279587226a9"},"data-dist-info-metadata":{"sha256":"c8301b547ede1d12db293b91c2eda41064442de769d8466cfdb34279587226a9"},"filename":"psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":316855,"upload-time":"2024-06-18T21:40:47.752750Z","url":"https://files.pythonhosted.org/packages/81/c9/8cb36769b6636d817be3414ebbb27a9ab3fbe6d13835d00f31e77e1fccce/psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"c8301b547ede1d12db293b91c2eda41064442de769d8466cfdb34279587226a9"},"data-dist-info-metadata":{"sha256":"c8301b547ede1d12db293b91c2eda41064442de769d8466cfdb34279587226a9"},"filename":"psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":317519,"upload-time":"2024-06-18T21:40:53.708954Z","url":"https://files.pythonhosted.org/packages/14/c0/024ac5369ca160e9ed45ed09247d9d779c460017fbd9aa801fd6eb0f060c/psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"30df484959758f1144d54b7788b34cc3dde563aa4d6853a6e549f2f022b23c5e"},"data-dist-info-metadata":{"sha256":"30df484959758f1144d54b7788b34cc3dde563aa4d6853a6e549f2f022b23c5e"},"filename":"psutil-6.0.0-cp27-none-win32.whl","hashes":{"sha256":"02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":249766,"upload-time":"2024-06-18T21:40:58.381272Z","url":"https://files.pythonhosted.org/packages/c5/66/78c9c3020f573c58101dc43a44f6855d01bbbd747e24da2f0c4491200ea3/psutil-6.0.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"30df484959758f1144d54b7788b34cc3dde563aa4d6853a6e549f2f022b23c5e"},"data-dist-info-metadata":{"sha256":"30df484959758f1144d54b7788b34cc3dde563aa4d6853a6e549f2f022b23c5e"},"filename":"psutil-6.0.0-cp27-none-win_amd64.whl","hashes":{"sha256":"21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":253024,"upload-time":"2024-06-18T21:41:04.548455Z","url":"https://files.pythonhosted.org/packages/e1/3f/2403aa9558bea4d3854b0e5e567bc3dd8e9fbc1fc4453c0aa9aafeb75467/psutil-6.0.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"data-dist-info-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"filename":"psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":250961,"upload-time":"2024-06-18T21:41:11.662513Z","url":"https://files.pythonhosted.org/packages/0b/37/f8da2fbd29690b3557cca414c1949f92162981920699cd62095a984983bf/psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"data-dist-info-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"filename":"psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":287478,"upload-time":"2024-06-18T21:41:16.180526Z","url":"https://files.pythonhosted.org/packages/35/56/72f86175e81c656a01c4401cd3b1c923f891b31fbcebe98985894176d7c9/psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"data-dist-info-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"filename":"psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":290455,"upload-time":"2024-06-18T21:41:29.048203Z","url":"https://files.pythonhosted.org/packages/19/74/f59e7e0d392bc1070e9a70e2f9190d652487ac115bb16e2eff6b22ad1d24/psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"data-dist-info-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"filename":"psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","hashes":{"sha256":"e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":292046,"upload-time":"2024-06-18T21:41:33.530555Z","url":"https://files.pythonhosted.org/packages/cd/5f/60038e277ff0a9cc8f0c9ea3d0c5eb6ee1d2470ea3f9389d776432888e47/psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"data-dist-info-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"filename":"psutil-6.0.0-cp36-cp36m-win32.whl","hashes":{"sha256":"fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":255537,"upload-time":"2024-06-18T21:41:38.034852Z","url":"https://files.pythonhosted.org/packages/cd/ff/39c38910cdb8f02fc9965afb520967a1e9307d53d14879dddd0a4f41f6f8/psutil-6.0.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"data-dist-info-metadata":{"sha256":"cce2895e87556b6152233398a38cd33b9b0712906393b6834b1448aec0a505ca"},"filename":"psutil-6.0.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":260973,"upload-time":"2024-06-18T21:41:41.566213Z","url":"https://files.pythonhosted.org/packages/08/88/16dd53af4a84e719e27a5ad7db040231415d8caeb48f019bacafbb4d0002/psutil-6.0.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"9e9a9bbc68eaa4d9f9716cef6b0d5f90d4d57007c2008e7301c292c7714a6abc"},"data-dist-info-metadata":{"sha256":"9e9a9bbc68eaa4d9f9716cef6b0d5f90d4d57007c2008e7301c292c7714a6abc"},"filename":"psutil-6.0.0-cp37-abi3-win32.whl","hashes":{"sha256":"a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":253560,"upload-time":"2024-06-18T21:41:46.067057Z","url":"https://files.pythonhosted.org/packages/8b/20/2ff69ad9c35c3df1858ac4e094f20bd2374d33c8643cf41da8fd7cdcb78b/psutil-6.0.0-cp37-abi3-win32.whl","yanked":false},{"core-metadata":{"sha256":"9e9a9bbc68eaa4d9f9716cef6b0d5f90d4d57007c2008e7301c292c7714a6abc"},"data-dist-info-metadata":{"sha256":"9e9a9bbc68eaa4d9f9716cef6b0d5f90d4d57007c2008e7301c292c7714a6abc"},"filename":"psutil-6.0.0-cp37-abi3-win_amd64.whl","hashes":{"sha256":"33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":257399,"upload-time":"2024-06-18T21:41:52.100137Z","url":"https://files.pythonhosted.org/packages/73/44/561092313ae925f3acfaace6f9ddc4f6a9c748704317bad9c8c8f8a36a79/psutil-6.0.0-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"c0710bf31d8e9160c90747acceb7692596b32d632efd784e22990fc01fad42dd"},"data-dist-info-metadata":{"sha256":"c0710bf31d8e9160c90747acceb7692596b32d632efd784e22990fc01fad42dd"},"filename":"psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":251988,"upload-time":"2024-06-18T21:41:57.337231Z","url":"https://files.pythonhosted.org/packages/7c/06/63872a64c312a24fb9b4af123ee7007a306617da63ff13bcc1432386ead7/psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-6.0.0.tar.gz","hashes":{"sha256":"8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":508067,"upload-time":"2024-06-18T21:40:10.559591Z","url":"https://files.pythonhosted.org/packages/18/c7/8c6872f7372eb6a6b2e4708b88419fb46b857f7a2e1892966b851cc79fc9/psutil-6.0.0.tar.gz","yanked":false},{"core-metadata":{"sha256":"c1b2df70b2192969ac8cec0847acb78d7ddd669a788780aad80d1133c89c4a43"},"data-dist-info-metadata":{"sha256":"c1b2df70b2192969ac8cec0847acb78d7ddd669a788780aad80d1133c89c4a43"},"filename":"psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl","hashes":{"sha256":"ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":247385,"upload-time":"2024-10-17T21:31:49.162372Z","url":"https://files.pythonhosted.org/packages/cd/8e/87b51bedb52f0fa02a6c9399702912a5059b24c7242fa8ea4fd027cb5238/psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"c1b2df70b2192969ac8cec0847acb78d7ddd669a788780aad80d1133c89c4a43"},"data-dist-info-metadata":{"sha256":"c1b2df70b2192969ac8cec0847acb78d7ddd669a788780aad80d1133c89c4a43"},"filename":"psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":312067,"upload-time":"2024-10-17T21:31:51.572046Z","url":"https://files.pythonhosted.org/packages/2c/56/99304ecbf1f25a2aa336c66e43a8f9462de70d089d3fbb487991dfd96b37/psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"c1b2df70b2192969ac8cec0847acb78d7ddd669a788780aad80d1133c89c4a43"},"data-dist-info-metadata":{"sha256":"c1b2df70b2192969ac8cec0847acb78d7ddd669a788780aad80d1133c89c4a43"},"filename":"psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":312345,"upload-time":"2024-10-17T21:31:53.950865Z","url":"https://files.pythonhosted.org/packages/24/87/7c1eeb2fd86a8eb792b15438a3d25eda05c970924df3457669b50e0c022b/psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"c1b2df70b2192969ac8cec0847acb78d7ddd669a788780aad80d1133c89c4a43"},"data-dist-info-metadata":{"sha256":"c1b2df70b2192969ac8cec0847acb78d7ddd669a788780aad80d1133c89c4a43"},"filename":"psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":312058,"upload-time":"2024-10-17T21:31:55.843636Z","url":"https://files.pythonhosted.org/packages/aa/fe/c94a914040c74b2bbe2ddb2c82b3f9a74d8a40401bb1239b0e949331c957/psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"c1b2df70b2192969ac8cec0847acb78d7ddd669a788780aad80d1133c89c4a43"},"data-dist-info-metadata":{"sha256":"c1b2df70b2192969ac8cec0847acb78d7ddd669a788780aad80d1133c89c4a43"},"filename":"psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":312330,"upload-time":"2024-10-17T21:31:57.551704Z","url":"https://files.pythonhosted.org/packages/44/8c/624823d5a5a9ec8635d63b273c3ab1554a4fcc3513f4d0236ff9706f1025/psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"6449e5b027e55d1dc345bb3b5089a60a968abd4f30577107504c292168ff86ea"},"data-dist-info-metadata":{"sha256":"6449e5b027e55d1dc345bb3b5089a60a968abd4f30577107504c292168ff86ea"},"filename":"psutil-6.1.0-cp27-none-win32.whl","hashes":{"sha256":"9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":246648,"upload-time":"2024-10-17T21:31:59.369185Z","url":"https://files.pythonhosted.org/packages/da/2b/f4dea5d993d9cd22ad958eea828a41d5d225556123d372f02547c29c4f97/psutil-6.1.0-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"6449e5b027e55d1dc345bb3b5089a60a968abd4f30577107504c292168ff86ea"},"data-dist-info-metadata":{"sha256":"6449e5b027e55d1dc345bb3b5089a60a968abd4f30577107504c292168ff86ea"},"filename":"psutil-6.1.0-cp27-none-win_amd64.whl","hashes":{"sha256":"a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":249905,"upload-time":"2024-10-17T21:32:01.974050Z","url":"https://files.pythonhosted.org/packages/9f/14/4aa97a7f2e0ac33a050d990ab31686d651ae4ef8c86661fef067f00437b9/psutil-6.1.0-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"f6ba356ca54ff4137597ca1c9c55cca6c6ccfd672a0c976cc370711d0a2652e8"},"data-dist-info-metadata":{"sha256":"f6ba356ca54ff4137597ca1c9c55cca6c6ccfd672a0c976cc370711d0a2652e8"},"filename":"psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":247762,"upload-time":"2024-10-17T21:32:05.991637Z","url":"https://files.pythonhosted.org/packages/01/9e/8be43078a171381953cfee33c07c0d628594b5dbfc5157847b85022c2c1b/psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"31aa42573bd48a390acf1e517f08ce9f05a4954542dc8afb9a8805655df7aa18"},"data-dist-info-metadata":{"sha256":"31aa42573bd48a390acf1e517f08ce9f05a4954542dc8afb9a8805655df7aa18"},"filename":"psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":248777,"upload-time":"2024-10-17T21:32:07.872442Z","url":"https://files.pythonhosted.org/packages/1d/cb/313e80644ea407f04f6602a9e23096540d9dc1878755f3952ea8d3d104be/psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"f6ba356ca54ff4137597ca1c9c55cca6c6ccfd672a0c976cc370711d0a2652e8"},"data-dist-info-metadata":{"sha256":"f6ba356ca54ff4137597ca1c9c55cca6c6ccfd672a0c976cc370711d0a2652e8"},"filename":"psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":284259,"upload-time":"2024-10-17T21:32:10.177301Z","url":"https://files.pythonhosted.org/packages/65/8e/bcbe2025c587b5d703369b6a75b65d41d1367553da6e3f788aff91eaf5bd/psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"f6ba356ca54ff4137597ca1c9c55cca6c6ccfd672a0c976cc370711d0a2652e8"},"data-dist-info-metadata":{"sha256":"f6ba356ca54ff4137597ca1c9c55cca6c6ccfd672a0c976cc370711d0a2652e8"},"filename":"psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":287255,"upload-time":"2024-10-17T21:32:11.964687Z","url":"https://files.pythonhosted.org/packages/58/4d/8245e6f76a93c98aab285a43ea71ff1b171bcd90c9d238bf81f7021fb233/psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"f6ba356ca54ff4137597ca1c9c55cca6c6ccfd672a0c976cc370711d0a2652e8"},"data-dist-info-metadata":{"sha256":"f6ba356ca54ff4137597ca1c9c55cca6c6ccfd672a0c976cc370711d0a2652e8"},"filename":"psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","hashes":{"sha256":"d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":288804,"upload-time":"2024-10-17T21:32:13.785068Z","url":"https://files.pythonhosted.org/packages/27/c2/d034856ac47e3b3cdfa9720d0e113902e615f4190d5d1bdb8df4b2015fb2/psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"4a33e7577c0dd3577291121b5beb777de2767238e7d66ad068c92fd37d3f3d6a"},"data-dist-info-metadata":{"sha256":"4a33e7577c0dd3577291121b5beb777de2767238e7d66ad068c92fd37d3f3d6a"},"filename":"psutil-6.1.0-cp36-cp36m-win32.whl","hashes":{"sha256":"6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":252360,"upload-time":"2024-10-17T21:32:16.434334Z","url":"https://files.pythonhosted.org/packages/43/39/414d7b67f4df35bb9c373d0fb9a75dd40b223d9bd6d02ebdc7658fd461a3/psutil-6.1.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4a33e7577c0dd3577291121b5beb777de2767238e7d66ad068c92fd37d3f3d6a"},"data-dist-info-metadata":{"sha256":"4a33e7577c0dd3577291121b5beb777de2767238e7d66ad068c92fd37d3f3d6a"},"filename":"psutil-6.1.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":257797,"upload-time":"2024-10-17T21:32:18.946615Z","url":"https://files.pythonhosted.org/packages/ca/da/ef86c99e33be4aa888570e79350caca8c4819b62f84a6d9274c88c40e331/psutil-6.1.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"7fdb0cc933f96b13edc5fc4b2255851b3a0222e29777763dd064aaeacaed9cb6"},"data-dist-info-metadata":{"sha256":"7fdb0cc933f96b13edc5fc4b2255851b3a0222e29777763dd064aaeacaed9cb6"},"filename":"psutil-6.1.0-cp37-abi3-win32.whl","hashes":{"sha256":"1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":250386,"upload-time":"2024-10-17T21:32:21.399329Z","url":"https://files.pythonhosted.org/packages/ea/55/5389ed243c878725feffc0d6a3bc5ef6764312b6fc7c081faaa2cfa7ef37/psutil-6.1.0-cp37-abi3-win32.whl","yanked":false},{"core-metadata":{"sha256":"7fdb0cc933f96b13edc5fc4b2255851b3a0222e29777763dd064aaeacaed9cb6"},"data-dist-info-metadata":{"sha256":"7fdb0cc933f96b13edc5fc4b2255851b3a0222e29777763dd064aaeacaed9cb6"},"filename":"psutil-6.1.0-cp37-abi3-win_amd64.whl","hashes":{"sha256":"a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":254228,"upload-time":"2024-10-17T21:32:23.880601Z","url":"https://files.pythonhosted.org/packages/11/91/87fa6f060e649b1e1a7b19a4f5869709fbf750b7c8c262ee776ec32f3028/psutil-6.1.0-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-6.1.0.tar.gz","hashes":{"sha256":"353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":508565,"upload-time":"2024-10-17T21:31:45.680545Z","url":"https://files.pythonhosted.org/packages/26/10/2a30b13c61e7cf937f4adf90710776b7918ed0a9c434e2c38224732af310/psutil-6.1.0.tar.gz","yanked":false},{"core-metadata":{"sha256":"b63844c7465e7a965b315aaeaab5c82b781f31476c21c62e1c9a135bceff2115"},"data-dist-info-metadata":{"sha256":"b63844c7465e7a965b315aaeaab5c82b781f31476c21c62e1c9a135bceff2115"},"filename":"psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl","hashes":{"sha256":"9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":247226,"upload-time":"2024-12-19T18:21:25.276122Z","url":"https://files.pythonhosted.org/packages/09/ea/f8844afff4c8c11d1d0586b737d8d579fd7cb13f1fa3eea599c71877b526/psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"b63844c7465e7a965b315aaeaab5c82b781f31476c21c62e1c9a135bceff2115"},"data-dist-info-metadata":{"sha256":"b63844c7465e7a965b315aaeaab5c82b781f31476c21c62e1c9a135bceff2115"},"filename":"psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl","hashes":{"sha256":"ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":312292,"upload-time":"2024-12-19T18:21:30.930117Z","url":"https://files.pythonhosted.org/packages/51/f8/e376f9410beb915bbf64cb4ae8ce5cf2d03e9a661a2519ebc6a63045a1ca/psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"b63844c7465e7a965b315aaeaab5c82b781f31476c21c62e1c9a135bceff2115"},"data-dist-info-metadata":{"sha256":"b63844c7465e7a965b315aaeaab5c82b781f31476c21c62e1c9a135bceff2115"},"filename":"psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl","hashes":{"sha256":"8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":312542,"upload-time":"2024-12-19T18:21:34.735400Z","url":"https://files.pythonhosted.org/packages/a7/3a/069d6c1e4a7af3cdb162c9ba0737ff9baed1d05cbab6f082f49e3b9ab0a5/psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"b63844c7465e7a965b315aaeaab5c82b781f31476c21c62e1c9a135bceff2115"},"data-dist-info-metadata":{"sha256":"b63844c7465e7a965b315aaeaab5c82b781f31476c21c62e1c9a135bceff2115"},"filename":"psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl","hashes":{"sha256":"1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":312279,"upload-time":"2024-12-19T18:21:37.897094Z","url":"https://files.pythonhosted.org/packages/81/d5/ee5de2cb8d0c938bb07dcccd4ff7e950359bd6ddbd2fe3118552f863bb52/psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl","yanked":false},{"core-metadata":{"sha256":"b63844c7465e7a965b315aaeaab5c82b781f31476c21c62e1c9a135bceff2115"},"data-dist-info-metadata":{"sha256":"b63844c7465e7a965b315aaeaab5c82b781f31476c21c62e1c9a135bceff2115"},"filename":"psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl","hashes":{"sha256":"018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":312521,"upload-time":"2024-12-19T18:21:40.651860Z","url":"https://files.pythonhosted.org/packages/37/98/443eff82762b3f2c6a4bd0cdf3bc5c9f62245376c5486b39ee194e920794/psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"55f120e7e066d74e328c2e0d559aa667c611e9e63f5a1bc5cae3d034db0be644"},"data-dist-info-metadata":{"sha256":"55f120e7e066d74e328c2e0d559aa667c611e9e63f5a1bc5cae3d034db0be644"},"filename":"psutil-6.1.1-cp27-none-win32.whl","hashes":{"sha256":"6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":246855,"upload-time":"2024-12-19T18:54:12.657947Z","url":"https://files.pythonhosted.org/packages/d2/d4/8095b53c4950f44dc99b8d983b796f405ae1f58d80978fcc0421491b4201/psutil-6.1.1-cp27-none-win32.whl","yanked":false},{"core-metadata":{"sha256":"55f120e7e066d74e328c2e0d559aa667c611e9e63f5a1bc5cae3d034db0be644"},"data-dist-info-metadata":{"sha256":"55f120e7e066d74e328c2e0d559aa667c611e9e63f5a1bc5cae3d034db0be644"},"filename":"psutil-6.1.1-cp27-none-win_amd64.whl","hashes":{"sha256":"c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":250110,"upload-time":"2024-12-19T18:54:16.635901Z","url":"https://files.pythonhosted.org/packages/b1/63/0b6425ea4f2375988209a9934c90d6079cc7537847ed58a28fbe30f4277e/psutil-6.1.1-cp27-none-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"06627f791c3970710891bde5c145df9bac4b643a1529e21d25a8c1c679aca404"},"data-dist-info-metadata":{"sha256":"06627f791c3970710891bde5c145df9bac4b643a1529e21d25a8c1c679aca404"},"filename":"psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":247511,"upload-time":"2024-12-19T18:21:45.163741Z","url":"https://files.pythonhosted.org/packages/61/99/ca79d302be46f7bdd8321089762dd4476ee725fce16fc2b2e1dbba8cac17/psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"f53aa914fe0e63af2de701b5d7b1c28a494b1852f4eddac6bec1c1d8eecc00d7"},"data-dist-info-metadata":{"sha256":"f53aa914fe0e63af2de701b5d7b1c28a494b1852f4eddac6bec1c1d8eecc00d7"},"filename":"psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":248985,"upload-time":"2024-12-19T18:21:49.254078Z","url":"https://files.pythonhosted.org/packages/0b/6b/73dbde0dd38f3782905d4587049b9be64d76671042fdcaf60e2430c6796d/psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"06627f791c3970710891bde5c145df9bac4b643a1529e21d25a8c1c679aca404"},"data-dist-info-metadata":{"sha256":"06627f791c3970710891bde5c145df9bac4b643a1529e21d25a8c1c679aca404"},"filename":"psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":284488,"upload-time":"2024-12-19T18:21:51.638630Z","url":"https://files.pythonhosted.org/packages/17/38/c319d31a1d3f88c5b79c68b3116c129e5133f1822157dd6da34043e32ed6/psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"06627f791c3970710891bde5c145df9bac4b643a1529e21d25a8c1c679aca404"},"data-dist-info-metadata":{"sha256":"06627f791c3970710891bde5c145df9bac4b643a1529e21d25a8c1c679aca404"},"filename":"psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":287477,"upload-time":"2024-12-19T18:21:55.306984Z","url":"https://files.pythonhosted.org/packages/9c/39/0f88a830a1c8a3aba27fededc642da37613c57cbff143412e3536f89784f/psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"06627f791c3970710891bde5c145df9bac4b643a1529e21d25a8c1c679aca404"},"data-dist-info-metadata":{"sha256":"06627f791c3970710891bde5c145df9bac4b643a1529e21d25a8c1c679aca404"},"filename":"psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","hashes":{"sha256":"33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":289017,"upload-time":"2024-12-19T18:21:57.875754Z","url":"https://files.pythonhosted.org/packages/47/da/99f4345d4ddf2845cb5b5bd0d93d554e84542d116934fde07a0c50bd4e9f/psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"4899522d2eb9cfd3ec5a1a377c43fb70f9e3963a8989dd7f84c20d6d00083470"},"data-dist-info-metadata":{"sha256":"4899522d2eb9cfd3ec5a1a377c43fb70f9e3963a8989dd7f84c20d6d00083470"},"filename":"psutil-6.1.1-cp36-cp36m-win32.whl","hashes":{"sha256":"384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":252576,"upload-time":"2024-12-19T18:22:01.852822Z","url":"https://files.pythonhosted.org/packages/8e/1f/1aebe4dd5914ccba6f7d6cc6d11fb79f6f23f95b858a7f631446bdc5d67f/psutil-6.1.1-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"4899522d2eb9cfd3ec5a1a377c43fb70f9e3963a8989dd7f84c20d6d00083470"},"data-dist-info-metadata":{"sha256":"4899522d2eb9cfd3ec5a1a377c43fb70f9e3963a8989dd7f84c20d6d00083470"},"filename":"psutil-6.1.1-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":258012,"upload-time":"2024-12-19T18:22:04.204308Z","url":"https://files.pythonhosted.org/packages/f4/de/fb4561e59611c19a2d7377c2b2534d11274b8a7df9bb7b7e7f1de5be3641/psutil-6.1.1-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"492e1282d630b4482b89e0c91cd388cea83f8efa851c6c2ffc46a6875c62b52d"},"data-dist-info-metadata":{"sha256":"492e1282d630b4482b89e0c91cd388cea83f8efa851c6c2ffc46a6875c62b52d"},"filename":"psutil-6.1.1-cp37-abi3-win32.whl","hashes":{"sha256":"eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":250602,"upload-time":"2024-12-19T18:22:08.808295Z","url":"https://files.pythonhosted.org/packages/38/53/bd755c2896f4461fd4f36fa6a6dcb66a88a9e4b9fd4e5b66a77cf9d4a584/psutil-6.1.1-cp37-abi3-win32.whl","yanked":false},{"core-metadata":{"sha256":"492e1282d630b4482b89e0c91cd388cea83f8efa851c6c2ffc46a6875c62b52d"},"data-dist-info-metadata":{"sha256":"492e1282d630b4482b89e0c91cd388cea83f8efa851c6c2ffc46a6875c62b52d"},"filename":"psutil-6.1.1-cp37-abi3-win_amd64.whl","hashes":{"sha256":"f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":254444,"upload-time":"2024-12-19T18:22:11.335598Z","url":"https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-6.1.1.tar.gz","hashes":{"sha256":"cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5"},"provenance":null,"requires-python":"!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7","size":508502,"upload-time":"2024-12-19T18:21:20.568966Z","url":"https://files.pythonhosted.org/packages/1f/5a/07871137bb752428aa4b659f910b399ba6f291156bdea939be3e96cae7cb/psutil-6.1.1.tar.gz","yanked":false},{"core-metadata":{"sha256":"0453e4553a618524d8001e2f64a83b1c24419826cab87bf6b734e6723d383c89"},"data-dist-info-metadata":{"sha256":"0453e4553a618524d8001e2f64a83b1c24419826cab87bf6b734e6723d383c89"},"filename":"psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"},"provenance":null,"requires-python":">=3.6","size":238051,"upload-time":"2025-02-13T21:54:12.360451Z","url":"https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"65f64bcd26d7284f4f07568c343ced6641bf8235c8c5a09a4986dd2ec5f68de7"},"data-dist-info-metadata":{"sha256":"65f64bcd26d7284f4f07568c343ced6641bf8235c8c5a09a4986dd2ec5f68de7"},"filename":"psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"},"provenance":null,"requires-python":">=3.6","size":239535,"upload-time":"2025-02-13T21:54:16.070769Z","url":"https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"0453e4553a618524d8001e2f64a83b1c24419826cab87bf6b734e6723d383c89"},"data-dist-info-metadata":{"sha256":"0453e4553a618524d8001e2f64a83b1c24419826cab87bf6b734e6723d383c89"},"filename":"psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"},"provenance":null,"requires-python":">=3.6","size":275004,"upload-time":"2025-02-13T21:54:18.662603Z","url":"https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"0453e4553a618524d8001e2f64a83b1c24419826cab87bf6b734e6723d383c89"},"data-dist-info-metadata":{"sha256":"0453e4553a618524d8001e2f64a83b1c24419826cab87bf6b734e6723d383c89"},"filename":"psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"},"provenance":null,"requires-python":">=3.6","size":277986,"upload-time":"2025-02-13T21:54:21.811145Z","url":"https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"0453e4553a618524d8001e2f64a83b1c24419826cab87bf6b734e6723d383c89"},"data-dist-info-metadata":{"sha256":"0453e4553a618524d8001e2f64a83b1c24419826cab87bf6b734e6723d383c89"},"filename":"psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","hashes":{"sha256":"a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"},"provenance":null,"requires-python":">=3.6","size":279544,"upload-time":"2025-02-13T21:54:24.680762Z","url":"https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"1326531887a042b91e9db0d6e74bd66dde0a4fd27151ed4b8baeca1fad635542"},"data-dist-info-metadata":{"sha256":"1326531887a042b91e9db0d6e74bd66dde0a4fd27151ed4b8baeca1fad635542"},"filename":"psutil-7.0.0-cp36-cp36m-win32.whl","hashes":{"sha256":"84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"},"provenance":null,"requires-python":">=3.6","size":243024,"upload-time":"2025-02-13T21:54:27.767214Z","url":"https://files.pythonhosted.org/packages/98/04/9e7b8afdad85824dec17de92c121d0fb1907ded624f486b86cd5e8189ebe/psutil-7.0.0-cp36-cp36m-win32.whl","yanked":false},{"core-metadata":{"sha256":"1326531887a042b91e9db0d6e74bd66dde0a4fd27151ed4b8baeca1fad635542"},"data-dist-info-metadata":{"sha256":"1326531887a042b91e9db0d6e74bd66dde0a4fd27151ed4b8baeca1fad635542"},"filename":"psutil-7.0.0-cp36-cp36m-win_amd64.whl","hashes":{"sha256":"1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"},"provenance":null,"requires-python":">=3.6","size":248462,"upload-time":"2025-02-13T21:54:31.148496Z","url":"https://files.pythonhosted.org/packages/25/9b/43f2c5f7794a3eba3fc0bb47020d1da44d43ff41c95637c5d760c3ef33eb/psutil-7.0.0-cp36-cp36m-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"8c4198dfca297dfee074ee46394207f317965c25dc1af8ad38862b954795a7c1"},"data-dist-info-metadata":{"sha256":"8c4198dfca297dfee074ee46394207f317965c25dc1af8ad38862b954795a7c1"},"filename":"psutil-7.0.0-cp37-abi3-win32.whl","hashes":{"sha256":"ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"},"provenance":null,"requires-python":">=3.6","size":241053,"upload-time":"2025-02-13T21:54:34.310916Z","url":"https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl","yanked":false},{"core-metadata":{"sha256":"8c4198dfca297dfee074ee46394207f317965c25dc1af8ad38862b954795a7c1"},"data-dist-info-metadata":{"sha256":"8c4198dfca297dfee074ee46394207f317965c25dc1af8ad38862b954795a7c1"},"filename":"psutil-7.0.0-cp37-abi3-win_amd64.whl","hashes":{"sha256":"4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"},"provenance":null,"requires-python":">=3.6","size":244885,"upload-time":"2025-02-13T21:54:37.486453Z","url":"https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-7.0.0.tar.gz","hashes":{"sha256":"7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"},"provenance":null,"requires-python":">=3.6","size":497003,"upload-time":"2025-02-13T21:54:07.946974Z","url":"https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz","yanked":false},{"core-metadata":{"sha256":"ac00a0bcd2510cbd355745e1742a41ef2eadd59546c28788d6122a57d329c6b6"},"data-dist-info-metadata":{"sha256":"ac00a0bcd2510cbd355745e1742a41ef2eadd59546c28788d6122a57d329c6b6"},"filename":"psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13"},"provenance":null,"requires-python":">=3.6","size":245242,"upload-time":"2025-09-17T20:14:56.126572Z","url":"https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ac00a0bcd2510cbd355745e1742a41ef2eadd59546c28788d6122a57d329c6b6"},"data-dist-info-metadata":{"sha256":"ac00a0bcd2510cbd355745e1742a41ef2eadd59546c28788d6122a57d329c6b6"},"filename":"psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5"},"provenance":null,"requires-python":">=3.6","size":246682,"upload-time":"2025-09-17T20:14:58.250040Z","url":"https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"ac00a0bcd2510cbd355745e1742a41ef2eadd59546c28788d6122a57d329c6b6"},"data-dist-info-metadata":{"sha256":"ac00a0bcd2510cbd355745e1742a41ef2eadd59546c28788d6122a57d329c6b6"},"filename":"psutil-7.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"22e4454970b32472ce7deaa45d045b34d3648ce478e26a04c7e858a0a6e75ff3"},"provenance":null,"requires-python":">=3.6","size":287994,"upload-time":"2025-09-17T20:14:59.901485Z","url":"https://files.pythonhosted.org/packages/88/7a/37c99d2e77ec30d63398ffa6a660450b8a62517cabe44b3e9bae97696e8d/psutil-7.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"ac00a0bcd2510cbd355745e1742a41ef2eadd59546c28788d6122a57d329c6b6"},"data-dist-info-metadata":{"sha256":"ac00a0bcd2510cbd355745e1742a41ef2eadd59546c28788d6122a57d329c6b6"},"filename":"psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3"},"provenance":null,"requires-python":">=3.6","size":291163,"upload-time":"2025-09-17T20:15:01.481447Z","url":"https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"ac00a0bcd2510cbd355745e1742a41ef2eadd59546c28788d6122a57d329c6b6"},"data-dist-info-metadata":{"sha256":"ac00a0bcd2510cbd355745e1742a41ef2eadd59546c28788d6122a57d329c6b6"},"filename":"psutil-7.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","hashes":{"sha256":"7d4a113425c037300de3ac8b331637293da9be9713855c4fc9d2d97436d7259d"},"provenance":null,"requires-python":">=3.6","size":293625,"upload-time":"2025-09-17T20:15:04.492789Z","url":"https://files.pythonhosted.org/packages/f4/58/c4f976234bf6d4737bc8c02a81192f045c307b72cf39c9e5c5a2d78927f6/psutil-7.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"24dfa33010cf11743d32bccef7c6bf186df383e24c75c8f904a21dc21061932a"},"data-dist-info-metadata":{"sha256":"24dfa33010cf11743d32bccef7c6bf186df383e24c75c8f904a21dc21061932a"},"filename":"psutil-7.1.0-cp37-abi3-win32.whl","hashes":{"sha256":"09ad740870c8d219ed8daae0ad3b726d3bf9a028a198e7f3080f6a1888b99bca"},"provenance":null,"requires-python":">=3.6","size":244812,"upload-time":"2025-09-17T20:15:07.462276Z","url":"https://files.pythonhosted.org/packages/79/87/157c8e7959ec39ced1b11cc93c730c4fb7f9d408569a6c59dbd92ceb35db/psutil-7.1.0-cp37-abi3-win32.whl","yanked":false},{"core-metadata":{"sha256":"24dfa33010cf11743d32bccef7c6bf186df383e24c75c8f904a21dc21061932a"},"data-dist-info-metadata":{"sha256":"24dfa33010cf11743d32bccef7c6bf186df383e24c75c8f904a21dc21061932a"},"filename":"psutil-7.1.0-cp37-abi3-win_amd64.whl","hashes":{"sha256":"57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d"},"provenance":null,"requires-python":">=3.6","size":247965,"upload-time":"2025-09-17T20:15:09.673366Z","url":"https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"24a51389a00af9d96fada7f4a79aed176d3a74f0ae399a5305d12ce9784f2635"},"data-dist-info-metadata":{"sha256":"24a51389a00af9d96fada7f4a79aed176d3a74f0ae399a5305d12ce9784f2635"},"filename":"psutil-7.1.0-cp37-abi3-win_arm64.whl","hashes":{"sha256":"6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07"},"provenance":null,"requires-python":">=3.6","size":244971,"upload-time":"2025-09-17T20:15:12.262753Z","url":"https://files.pythonhosted.org/packages/26/65/1070a6e3c036f39142c2820c4b52e9243246fcfc3f96239ac84472ba361e/psutil-7.1.0-cp37-abi3-win_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-7.1.0.tar.gz","hashes":{"sha256":"655708b3c069387c8b77b072fc429a57d0e214221d01c0a772df7dfedcb3bcd2"},"provenance":null,"requires-python":">=3.6","size":497660,"upload-time":"2025-09-17T20:14:52.902036Z","url":"https://files.pythonhosted.org/packages/b3/31/4723d756b59344b643542936e37a31d1d3204bcdc42a7daa8ee9eb06fb50/psutil-7.1.0.tar.gz","yanked":false},{"core-metadata":{"sha256":"d94db19e208334cc22bb81d2d0102f837a1101c98a0d350fa3d257181ae1ef09"},"data-dist-info-metadata":{"sha256":"d94db19e208334cc22bb81d2d0102f837a1101c98a0d350fa3d257181ae1ef09"},"filename":"psutil-7.1.1-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"8fa59d7b1f01f0337f12cd10dbd76e4312a4d3c730a4fedcbdd4e5447a8b8460"},"provenance":null,"requires-python":">=3.6","size":244221,"upload-time":"2025-10-19T15:44:03.145914Z","url":"https://files.pythonhosted.org/packages/51/30/f97f8fb1f9ecfbeae4b5ca738dcae66ab28323b5cfbc96cb5565f3754056/psutil-7.1.1-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"d94db19e208334cc22bb81d2d0102f837a1101c98a0d350fa3d257181ae1ef09"},"data-dist-info-metadata":{"sha256":"d94db19e208334cc22bb81d2d0102f837a1101c98a0d350fa3d257181ae1ef09"},"filename":"psutil-7.1.1-cp36-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"2a95104eae85d088891716db676f780c1404fc15d47fde48a46a5d61e8f5ad2c"},"provenance":null,"requires-python":">=3.6","size":245660,"upload-time":"2025-10-19T15:44:05.657308Z","url":"https://files.pythonhosted.org/packages/7b/98/b8d1f61ebf35f4dbdbaabadf9208282d8adc820562f0257e5e6e79e67bf2/psutil-7.1.1-cp36-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"d94db19e208334cc22bb81d2d0102f837a1101c98a0d350fa3d257181ae1ef09"},"data-dist-info-metadata":{"sha256":"d94db19e208334cc22bb81d2d0102f837a1101c98a0d350fa3d257181ae1ef09"},"filename":"psutil-7.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","hashes":{"sha256":"98629cd8567acefcc45afe2f4ba1e9290f579eacf490a917967decce4b74ee9b"},"provenance":null,"requires-python":">=3.6","size":286963,"upload-time":"2025-10-19T15:44:08.877299Z","url":"https://files.pythonhosted.org/packages/f0/4a/b8015d7357fefdfe34bc4a3db48a107bae4bad0b94fb6eb0613f09a08ada/psutil-7.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl","yanked":false},{"core-metadata":{"sha256":"d94db19e208334cc22bb81d2d0102f837a1101c98a0d350fa3d257181ae1ef09"},"data-dist-info-metadata":{"sha256":"d94db19e208334cc22bb81d2d0102f837a1101c98a0d350fa3d257181ae1ef09"},"filename":"psutil-7.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","hashes":{"sha256":"92ebc58030fb054fa0f26c3206ef01c31c29d67aee1367e3483c16665c25c8d2"},"provenance":null,"requires-python":">=3.6","size":290118,"upload-time":"2025-10-19T15:44:11.897889Z","url":"https://files.pythonhosted.org/packages/3d/3c/b56076bb35303d0733fc47b110a1c9cce081a05ae2e886575a3587c1ee76/psutil-7.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"d94db19e208334cc22bb81d2d0102f837a1101c98a0d350fa3d257181ae1ef09"},"data-dist-info-metadata":{"sha256":"d94db19e208334cc22bb81d2d0102f837a1101c98a0d350fa3d257181ae1ef09"},"filename":"psutil-7.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","hashes":{"sha256":"146a704f224fb2ded2be3da5ac67fc32b9ea90c45b51676f9114a6ac45616967"},"provenance":null,"requires-python":">=3.6","size":292587,"upload-time":"2025-10-19T15:44:14.670833Z","url":"https://files.pythonhosted.org/packages/dc/af/c13d360c0adc6f6218bf9e2873480393d0f729c8dd0507d171f53061c0d3/psutil-7.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"b46a0b2c74ca7acef22d45a6a1b88ac5c5a55793782e1b66e08e8ac56f33ddb1"},"data-dist-info-metadata":{"sha256":"b46a0b2c74ca7acef22d45a6a1b88ac5c5a55793782e1b66e08e8ac56f33ddb1"},"filename":"psutil-7.1.1-cp37-abi3-win32.whl","hashes":{"sha256":"295c4025b5cd880f7445e4379e6826f7307e3d488947bf9834e865e7847dc5f7"},"provenance":null,"requires-python":">=3.6","size":243772,"upload-time":"2025-10-19T15:44:16.938205Z","url":"https://files.pythonhosted.org/packages/90/2d/c933e7071ba60c7862813f2c7108ec4cf8304f1c79660efeefd0de982258/psutil-7.1.1-cp37-abi3-win32.whl","yanked":false},{"core-metadata":{"sha256":"b46a0b2c74ca7acef22d45a6a1b88ac5c5a55793782e1b66e08e8ac56f33ddb1"},"data-dist-info-metadata":{"sha256":"b46a0b2c74ca7acef22d45a6a1b88ac5c5a55793782e1b66e08e8ac56f33ddb1"},"filename":"psutil-7.1.1-cp37-abi3-win_amd64.whl","hashes":{"sha256":"9b4f17c5f65e44f69bd3a3406071a47b79df45cf2236d1f717970afcb526bcd3"},"provenance":null,"requires-python":">=3.6","size":246936,"upload-time":"2025-10-19T15:44:18.663465Z","url":"https://files.pythonhosted.org/packages/be/f3/11fd213fff15427bc2853552138760c720fd65032d99edfb161910d04127/psutil-7.1.1-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"41f2736f936aaaa81486aad04717acc54669b33ae5ad93df72d7a7b26aa50fa7"},"data-dist-info-metadata":{"sha256":"41f2736f936aaaa81486aad04717acc54669b33ae5ad93df72d7a7b26aa50fa7"},"filename":"psutil-7.1.1-cp37-abi3-win_arm64.whl","hashes":{"sha256":"5457cf741ca13da54624126cd5d333871b454ab133999a9a103fb097a7d7d21a"},"provenance":null,"requires-python":">=3.6","size":243944,"upload-time":"2025-10-19T15:44:20.666512Z","url":"https://files.pythonhosted.org/packages/0a/8d/8a9a45c8b655851f216c1d44f68e3533dc8d2c752ccd0f61f1aa73be4893/psutil-7.1.1-cp37-abi3-win_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-7.1.1.tar.gz","hashes":{"sha256":"092b6350145007389c1cfe5716050f02030a05219d90057ea867d18fe8d372fc"},"provenance":null,"requires-python":">=3.6","size":487067,"upload-time":"2025-10-19T15:43:59.373160Z","url":"https://files.pythonhosted.org/packages/89/fc/889242351a932d6183eec5df1fc6539b6f36b6a88444f1e63f18668253aa/psutil-7.1.1.tar.gz","yanked":false},{"core-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"data-dist-info-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"filename":"psutil-7.1.2-cp313-cp313t-macosx_10_13_x86_64.whl","hashes":{"sha256":"0cc5c6889b9871f231ed5455a9a02149e388fffcb30b607fb7a8896a6d95f22e"},"provenance":null,"requires-python":">=3.6","size":238575,"upload-time":"2025-10-25T10:46:38.728747Z","url":"https://files.pythonhosted.org/packages/b8/d9/b56cc9f883140ac10021a8c9b0f4e16eed1ba675c22513cdcbce3ba64014/psutil-7.1.2-cp313-cp313t-macosx_10_13_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"data-dist-info-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"filename":"psutil-7.1.2-cp313-cp313t-macosx_11_0_arm64.whl","hashes":{"sha256":"8e9e77a977208d84aa363a4a12e0f72189d58bbf4e46b49aae29a2c6e93ef206"},"provenance":null,"requires-python":">=3.6","size":239297,"upload-time":"2025-10-25T10:46:41.347184Z","url":"https://files.pythonhosted.org/packages/36/eb/28d22de383888deb252c818622196e709da98816e296ef95afda33f1c0a2/psutil-7.1.2-cp313-cp313t-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"data-dist-info-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"filename":"psutil-7.1.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"7d9623a5e4164d2220ecceb071f4b333b3c78866141e8887c072129185f41278"},"provenance":null,"requires-python":">=3.6","size":280420,"upload-time":"2025-10-25T10:46:44.122205Z","url":"https://files.pythonhosted.org/packages/89/5d/220039e2f28cc129626e54d63892ab05c0d56a29818bfe7268dcb5008932/psutil-7.1.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"data-dist-info-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"filename":"psutil-7.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"364b1c10fe4ed59c89ec49e5f1a70da353b27986fa8233b4b999df4742a5ee2f"},"provenance":null,"requires-python":">=3.6","size":283049,"upload-time":"2025-10-25T10:46:47.095973Z","url":"https://files.pythonhosted.org/packages/ba/7a/286f0e1c167445b2ef4a6cbdfc8c59fdb45a5a493788950cf8467201dc73/psutil-7.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"b27a63f31491a49efc9ff48050f5fdcca4606a487fac91e6cc46853031038b01"},"data-dist-info-metadata":{"sha256":"b27a63f31491a49efc9ff48050f5fdcca4606a487fac91e6cc46853031038b01"},"filename":"psutil-7.1.2-cp313-cp313t-win_amd64.whl","hashes":{"sha256":"f101ef84de7e05d41310e3ccbdd65a6dd1d9eed85e8aaf0758405d022308e204"},"provenance":null,"requires-python":">=3.6","size":248713,"upload-time":"2025-10-25T10:46:49.573269Z","url":"https://files.pythonhosted.org/packages/aa/cc/7eb93260794a42e39b976f3a4dde89725800b9f573b014fac142002a5c98/psutil-7.1.2-cp313-cp313t-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b27a63f31491a49efc9ff48050f5fdcca4606a487fac91e6cc46853031038b01"},"data-dist-info-metadata":{"sha256":"b27a63f31491a49efc9ff48050f5fdcca4606a487fac91e6cc46853031038b01"},"filename":"psutil-7.1.2-cp313-cp313t-win_arm64.whl","hashes":{"sha256":"20c00824048a95de67f00afedc7b08b282aa08638585b0206a9fb51f28f1a165"},"provenance":null,"requires-python":">=3.6","size":244644,"upload-time":"2025-10-25T10:46:51.924062Z","url":"https://files.pythonhosted.org/packages/ab/1a/0681a92b53366e01f0a099f5237d0c8a2f79d322ac589cccde5e30c8a4e2/psutil-7.1.2-cp313-cp313t-win_arm64.whl","yanked":false},{"core-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"data-dist-info-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"filename":"psutil-7.1.2-cp314-cp314t-macosx_10_15_x86_64.whl","hashes":{"sha256":"e09cfe92aa8e22b1ec5e2d394820cf86c5dff6367ac3242366485dfa874d43bc"},"provenance":null,"requires-python":">=3.6","size":238640,"upload-time":"2025-10-25T10:46:54.089898Z","url":"https://files.pythonhosted.org/packages/56/9e/f1c5c746b4ed5320952acd3002d3962fe36f30524c00ea79fdf954cc6779/psutil-7.1.2-cp314-cp314t-macosx_10_15_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"data-dist-info-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"filename":"psutil-7.1.2-cp314-cp314t-macosx_11_0_arm64.whl","hashes":{"sha256":"fa6342cf859c48b19df3e4aa170e4cfb64aadc50b11e06bb569c6c777b089c9e"},"provenance":null,"requires-python":">=3.6","size":239303,"upload-time":"2025-10-25T10:46:56.932071Z","url":"https://files.pythonhosted.org/packages/32/ee/fd26216a735395cc25c3899634e34aeb41fb1f3dbb44acc67d9e594be562/psutil-7.1.2-cp314-cp314t-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"data-dist-info-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"filename":"psutil-7.1.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"625977443498ee7d6c1e63e93bacca893fd759a66c5f635d05e05811d23fb5ee"},"provenance":null,"requires-python":">=3.6","size":281717,"upload-time":"2025-10-25T10:46:59.116890Z","url":"https://files.pythonhosted.org/packages/3c/cd/7d96eaec4ef7742b845a9ce2759a2769ecce4ab7a99133da24abacbc9e41/psutil-7.1.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"data-dist-info-metadata":{"sha256":"e7b235bb0ba92037ddba6bcb227d0db55f64a7d54b2302a64ee7d8ba78b81c44"},"filename":"psutil-7.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"4a24bcd7b7f2918d934af0fb91859f621b873d6aa81267575e3655cd387572a7"},"provenance":null,"requires-python":">=3.6","size":284575,"upload-time":"2025-10-25T10:47:00.944625Z","url":"https://files.pythonhosted.org/packages/bc/1a/7f0b84bdb067d35fe7fade5fff888408688caf989806ce2d6dae08c72dd5/psutil-7.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"b27a63f31491a49efc9ff48050f5fdcca4606a487fac91e6cc46853031038b01"},"data-dist-info-metadata":{"sha256":"b27a63f31491a49efc9ff48050f5fdcca4606a487fac91e6cc46853031038b01"},"filename":"psutil-7.1.2-cp314-cp314t-win_amd64.whl","hashes":{"sha256":"329f05610da6380982e6078b9d0881d9ab1e9a7eb7c02d833bfb7340aa634e31"},"provenance":null,"requires-python":">=3.6","size":249491,"upload-time":"2025-10-25T10:47:03.174087Z","url":"https://files.pythonhosted.org/packages/de/05/7820ef8f7b275268917e0c750eada5834581206d9024ca88edce93c4b762/psutil-7.1.2-cp314-cp314t-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b27a63f31491a49efc9ff48050f5fdcca4606a487fac91e6cc46853031038b01"},"data-dist-info-metadata":{"sha256":"b27a63f31491a49efc9ff48050f5fdcca4606a487fac91e6cc46853031038b01"},"filename":"psutil-7.1.2-cp314-cp314t-win_arm64.whl","hashes":{"sha256":"7b04c29e3c0c888e83ed4762b70f31e65c42673ea956cefa8ced0e31e185f582"},"provenance":null,"requires-python":">=3.6","size":244880,"upload-time":"2025-10-25T10:47:05.228789Z","url":"https://files.pythonhosted.org/packages/db/9a/58de399c7cb58489f08498459ff096cd76b3f1ddc4f224ec2c5ef729c7d0/psutil-7.1.2-cp314-cp314t-win_arm64.whl","yanked":false},{"core-metadata":{"sha256":"9060d8b9832fb047375c7eeac0d3ca41668a9f56cea96f12e30ce72cc4acea8b"},"data-dist-info-metadata":{"sha256":"9060d8b9832fb047375c7eeac0d3ca41668a9f56cea96f12e30ce72cc4acea8b"},"filename":"psutil-7.1.2-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"c9ba5c19f2d46203ee8c152c7b01df6eec87d883cfd8ee1af2ef2727f6b0f814"},"provenance":null,"requires-python":">=3.6","size":237244,"upload-time":"2025-10-25T10:47:07.086631Z","url":"https://files.pythonhosted.org/packages/ae/89/b9f8d47ddbc52d7301fc868e8224e5f44ed3c7f55e6d0f54ecaf5dd9ff5e/psutil-7.1.2-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"9060d8b9832fb047375c7eeac0d3ca41668a9f56cea96f12e30ce72cc4acea8b"},"data-dist-info-metadata":{"sha256":"9060d8b9832fb047375c7eeac0d3ca41668a9f56cea96f12e30ce72cc4acea8b"},"filename":"psutil-7.1.2-cp36-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"2a486030d2fe81bec023f703d3d155f4823a10a47c36784c84f1cc7f8d39bedb"},"provenance":null,"requires-python":">=3.6","size":238101,"upload-time":"2025-10-25T10:47:09.523680Z","url":"https://files.pythonhosted.org/packages/c8/7a/8628c2f6b240680a67d73d8742bb9ff39b1820a693740e43096d5dcb01e5/psutil-7.1.2-cp36-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"9060d8b9832fb047375c7eeac0d3ca41668a9f56cea96f12e30ce72cc4acea8b"},"data-dist-info-metadata":{"sha256":"9060d8b9832fb047375c7eeac0d3ca41668a9f56cea96f12e30ce72cc4acea8b"},"filename":"psutil-7.1.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"3efd8fc791492e7808a51cb2b94889db7578bfaea22df931424f874468e389e3"},"provenance":null,"requires-python":">=3.6","size":258675,"upload-time":"2025-10-25T10:47:11.082823Z","url":"https://files.pythonhosted.org/packages/30/28/5e27f4d5a0e347f8e3cc16cd7d35533dbce086c95807f1f0e9cd77e26c10/psutil-7.1.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"9060d8b9832fb047375c7eeac0d3ca41668a9f56cea96f12e30ce72cc4acea8b"},"data-dist-info-metadata":{"sha256":"9060d8b9832fb047375c7eeac0d3ca41668a9f56cea96f12e30ce72cc4acea8b"},"filename":"psutil-7.1.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"e2aeb9b64f481b8eabfc633bd39e0016d4d8bbcd590d984af764d80bf0851b8a"},"provenance":null,"requires-python":">=3.6","size":260203,"upload-time":"2025-10-25T10:47:13.226564Z","url":"https://files.pythonhosted.org/packages/e5/5c/79cf60c9acf36d087f0db0f82066fca4a780e97e5b3a2e4c38209c03d170/psutil-7.1.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"4e0958cd1551f981a3d0063faf8314b741cd97cb43d1e58934800cb4da537f64"},"data-dist-info-metadata":{"sha256":"4e0958cd1551f981a3d0063faf8314b741cd97cb43d1e58934800cb4da537f64"},"filename":"psutil-7.1.2-cp37-abi3-win_amd64.whl","hashes":{"sha256":"8e17852114c4e7996fe9da4745c2bdef001ebbf2f260dec406290e66628bdb91"},"provenance":null,"requires-python":">=3.6","size":246714,"upload-time":"2025-10-25T10:47:15.093571Z","url":"https://files.pythonhosted.org/packages/f7/03/0a464404c51685dcb9329fdd660b1721e076ccd7b3d97dee066bcc9ffb15/psutil-7.1.2-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"b27a63f31491a49efc9ff48050f5fdcca4606a487fac91e6cc46853031038b01"},"data-dist-info-metadata":{"sha256":"b27a63f31491a49efc9ff48050f5fdcca4606a487fac91e6cc46853031038b01"},"filename":"psutil-7.1.2-cp37-abi3-win_arm64.whl","hashes":{"sha256":"3e988455e61c240cc879cb62a008c2699231bf3e3d061d7fce4234463fd2abb4"},"provenance":null,"requires-python":">=3.6","size":243742,"upload-time":"2025-10-25T10:47:17.302139Z","url":"https://files.pythonhosted.org/packages/6a/32/97ca2090f2f1b45b01b6aa7ae161cfe50671de097311975ca6eea3e7aabc/psutil-7.1.2-cp37-abi3-win_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-7.1.2.tar.gz","hashes":{"sha256":"aa225cdde1335ff9684708ee8c72650f6598d5ed2114b9a7c5802030b1785018"},"provenance":null,"requires-python":">=3.6","size":487424,"upload-time":"2025-10-25T10:46:34.931002Z","url":"https://files.pythonhosted.org/packages/cd/ec/7b8e6b9b1d22708138630ef34c53ab2b61032c04f16adfdbb96791c8c70c/psutil-7.1.2.tar.gz","yanked":false},{"core-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"data-dist-info-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"filename":"psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl","hashes":{"sha256":"0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc"},"provenance":null,"requires-python":">=3.6","size":239751,"upload-time":"2025-11-02T12:25:58.161404Z","url":"https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"data-dist-info-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"filename":"psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl","hashes":{"sha256":"19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0"},"provenance":null,"requires-python":">=3.6","size":240368,"upload-time":"2025-11-02T12:26:00.491685Z","url":"https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"data-dist-info-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"filename":"psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7"},"provenance":null,"requires-python":">=3.6","size":287134,"upload-time":"2025-11-02T12:26:02.613574Z","url":"https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"data-dist-info-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"filename":"psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251"},"provenance":null,"requires-python":">=3.6","size":289904,"upload-time":"2025-11-02T12:26:05.207933Z","url":"https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"a690b24adf2968fc63d76d10c2e8052107d0af31da1588424d365c1ff08c9fd1"},"data-dist-info-metadata":{"sha256":"a690b24adf2968fc63d76d10c2e8052107d0af31da1588424d365c1ff08c9fd1"},"filename":"psutil-7.1.3-cp313-cp313t-win_amd64.whl","hashes":{"sha256":"18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa"},"provenance":null,"requires-python":">=3.6","size":249642,"upload-time":"2025-11-02T12:26:07.447774Z","url":"https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"a690b24adf2968fc63d76d10c2e8052107d0af31da1588424d365c1ff08c9fd1"},"data-dist-info-metadata":{"sha256":"a690b24adf2968fc63d76d10c2e8052107d0af31da1588424d365c1ff08c9fd1"},"filename":"psutil-7.1.3-cp313-cp313t-win_arm64.whl","hashes":{"sha256":"c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee"},"provenance":null,"requires-python":">=3.6","size":245518,"upload-time":"2025-11-02T12:26:09.719155Z","url":"https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl","yanked":false},{"core-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"data-dist-info-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"filename":"psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl","hashes":{"sha256":"b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353"},"provenance":null,"requires-python":">=3.6","size":239843,"upload-time":"2025-11-02T12:26:11.968073Z","url":"https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"data-dist-info-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"filename":"psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl","hashes":{"sha256":"ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b"},"provenance":null,"requires-python":">=3.6","size":240369,"upload-time":"2025-11-02T12:26:14.358801Z","url":"https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"data-dist-info-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"filename":"psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9"},"provenance":null,"requires-python":">=3.6","size":288210,"upload-time":"2025-11-02T12:26:16.699739Z","url":"https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"data-dist-info-metadata":{"sha256":"fee2408ed83d5d231935e659a072f72e41c592bc880496074a506e9d6b3eada7"},"filename":"psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f"},"provenance":null,"requires-python":">=3.6","size":291182,"upload-time":"2025-11-02T12:26:18.848963Z","url":"https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"a690b24adf2968fc63d76d10c2e8052107d0af31da1588424d365c1ff08c9fd1"},"data-dist-info-metadata":{"sha256":"a690b24adf2968fc63d76d10c2e8052107d0af31da1588424d365c1ff08c9fd1"},"filename":"psutil-7.1.3-cp314-cp314t-win_amd64.whl","hashes":{"sha256":"3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7"},"provenance":null,"requires-python":">=3.6","size":250466,"upload-time":"2025-11-02T12:26:21.183069Z","url":"https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"a690b24adf2968fc63d76d10c2e8052107d0af31da1588424d365c1ff08c9fd1"},"data-dist-info-metadata":{"sha256":"a690b24adf2968fc63d76d10c2e8052107d0af31da1588424d365c1ff08c9fd1"},"filename":"psutil-7.1.3-cp314-cp314t-win_arm64.whl","hashes":{"sha256":"31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264"},"provenance":null,"requires-python":">=3.6","size":245756,"upload-time":"2025-11-02T12:26:23.148427Z","url":"https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl","yanked":false},{"core-metadata":{"sha256":"3578dc4ee5f43e542d29839741b9e3d8760c3c0e5d8c388ec2b0b980bd8c66a1"},"data-dist-info-metadata":{"sha256":"3578dc4ee5f43e542d29839741b9e3d8760c3c0e5d8c388ec2b0b980bd8c66a1"},"filename":"psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab"},"provenance":null,"requires-python":">=3.6","size":238359,"upload-time":"2025-11-02T12:26:25.284599Z","url":"https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"3578dc4ee5f43e542d29839741b9e3d8760c3c0e5d8c388ec2b0b980bd8c66a1"},"data-dist-info-metadata":{"sha256":"3578dc4ee5f43e542d29839741b9e3d8760c3c0e5d8c388ec2b0b980bd8c66a1"},"filename":"psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880"},"provenance":null,"requires-python":">=3.6","size":239171,"upload-time":"2025-11-02T12:26:27.230751Z","url":"https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"3578dc4ee5f43e542d29839741b9e3d8760c3c0e5d8c388ec2b0b980bd8c66a1"},"data-dist-info-metadata":{"sha256":"3578dc4ee5f43e542d29839741b9e3d8760c3c0e5d8c388ec2b0b980bd8c66a1"},"filename":"psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3"},"provenance":null,"requires-python":">=3.6","size":263261,"upload-time":"2025-11-02T12:26:29.480632Z","url":"https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"3578dc4ee5f43e542d29839741b9e3d8760c3c0e5d8c388ec2b0b980bd8c66a1"},"data-dist-info-metadata":{"sha256":"3578dc4ee5f43e542d29839741b9e3d8760c3c0e5d8c388ec2b0b980bd8c66a1"},"filename":"psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b"},"provenance":null,"requires-python":">=3.6","size":264635,"upload-time":"2025-11-02T12:26:31.740762Z","url":"https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"89db7acafbe66cd8b6a060ae993afda9c8f50aa4342c34c92a284be2c8c8534f"},"data-dist-info-metadata":{"sha256":"89db7acafbe66cd8b6a060ae993afda9c8f50aa4342c34c92a284be2c8c8534f"},"filename":"psutil-7.1.3-cp37-abi3-win_amd64.whl","hashes":{"sha256":"f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd"},"provenance":null,"requires-python":">=3.6","size":247633,"upload-time":"2025-11-02T12:26:33.887174Z","url":"https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"a690b24adf2968fc63d76d10c2e8052107d0af31da1588424d365c1ff08c9fd1"},"data-dist-info-metadata":{"sha256":"a690b24adf2968fc63d76d10c2e8052107d0af31da1588424d365c1ff08c9fd1"},"filename":"psutil-7.1.3-cp37-abi3-win_arm64.whl","hashes":{"sha256":"bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1"},"provenance":null,"requires-python":">=3.6","size":244608,"upload-time":"2025-11-02T12:26:36.136434Z","url":"https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-7.1.3.tar.gz","hashes":{"sha256":"6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74"},"provenance":null,"requires-python":">=3.6","size":489059,"upload-time":"2025-11-02T12:25:54.619294Z","url":"https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz","yanked":false},{"core-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"data-dist-info-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"filename":"psutil-7.2.0-cp313-cp313t-macosx_10_13_x86_64.whl","hashes":{"sha256":"c31e927555539132a00380c971816ea43d089bf4bd5f3e918ed8c16776d68474"},"provenance":null,"requires-python":">=3.6","size":129593,"upload-time":"2025-12-23T20:26:28.019569Z","url":"https://files.pythonhosted.org/packages/a8/8e/b35aae6ed19bc4e2286cac4832e4d522fcf00571867b0a85a3f77ef96a80/psutil-7.2.0-cp313-cp313t-macosx_10_13_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"data-dist-info-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"filename":"psutil-7.2.0-cp313-cp313t-macosx_11_0_arm64.whl","hashes":{"sha256":"db8e44e766cef86dea47d9a1fa535d38dc76449e5878a92f33683b7dba5bfcb2"},"provenance":null,"requires-python":">=3.6","size":130104,"upload-time":"2025-12-23T20:26:30.270800Z","url":"https://files.pythonhosted.org/packages/61/a2/773d17d74e122bbffe08b97f73f2d4a01ef53fb03b98e61b8e4f64a9c6b9/psutil-7.2.0-cp313-cp313t-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"data-dist-info-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"filename":"psutil-7.2.0-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"85ef849ac92169dedc59a7ac2fb565f47b3468fbe1524bf748746bc21afb94c7"},"provenance":null,"requires-python":">=3.6","size":180579,"upload-time":"2025-12-23T20:26:32.628357Z","url":"https://files.pythonhosted.org/packages/0d/e3/d3a9b3f4bd231abbd70a988beb2e3edd15306051bccbfc4472bd34a56e01/psutil-7.2.0-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"data-dist-info-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"filename":"psutil-7.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"26782bdbae2f5c14ce9ebe8ad2411dc2ca870495e0cd90f8910ede7fa5e27117"},"provenance":null,"requires-python":">=3.6","size":183171,"upload-time":"2025-12-23T20:26:34.972726Z","url":"https://files.pythonhosted.org/packages/66/f8/6c73044424aabe1b7824d4d4504029d406648286d8fe7ba8c4682e0d3042/psutil-7.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"795be0019027992887b63578f61759602599118117e81710fb28c5b7fb5023d6"},"data-dist-info-metadata":{"sha256":"795be0019027992887b63578f61759602599118117e81710fb28c5b7fb5023d6"},"filename":"psutil-7.2.0-cp313-cp313t-win_amd64.whl","hashes":{"sha256":"b7665f612d3b38a583391b95969667a53aaf6c5706dc27a602c9a4874fbf09e4"},"provenance":null,"requires-python":">=3.6","size":139055,"upload-time":"2025-12-23T20:26:36.848832Z","url":"https://files.pythonhosted.org/packages/48/7d/76d7a863340885d41826562225a566683e653ee6c9ba03c9f3856afa7d80/psutil-7.2.0-cp313-cp313t-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"795be0019027992887b63578f61759602599118117e81710fb28c5b7fb5023d6"},"data-dist-info-metadata":{"sha256":"795be0019027992887b63578f61759602599118117e81710fb28c5b7fb5023d6"},"filename":"psutil-7.2.0-cp313-cp313t-win_arm64.whl","hashes":{"sha256":"4413373c174520ae28a24a8974ad8ce6b21f060d27dde94e25f8c73a7effe57a"},"provenance":null,"requires-python":">=3.6","size":134737,"upload-time":"2025-12-23T20:26:38.784066Z","url":"https://files.pythonhosted.org/packages/a0/48/200054ada0ae4872c8a71db54f3eb6a9af4101680ee6830d373b7fda526b/psutil-7.2.0-cp313-cp313t-win_arm64.whl","yanked":false},{"core-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"data-dist-info-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"filename":"psutil-7.2.0-cp314-cp314t-macosx_10_15_x86_64.whl","hashes":{"sha256":"2f2f53fd114e7946dfba3afb98c9b7c7f376009447360ca15bfb73f2066f84c7"},"provenance":null,"requires-python":">=3.6","size":129692,"upload-time":"2025-12-23T20:26:40.623176Z","url":"https://files.pythonhosted.org/packages/44/86/98da45dff471b93ef5ce5bcaefa00e3038295a7880a77cf74018243d37fb/psutil-7.2.0-cp314-cp314t-macosx_10_15_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"data-dist-info-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"filename":"psutil-7.2.0-cp314-cp314t-macosx_11_0_arm64.whl","hashes":{"sha256":"e65c41d7e60068f60ce43b31a3a7fc90deb0dfd34ffc824a2574c2e5279b377e"},"provenance":null,"requires-python":">=3.6","size":130110,"upload-time":"2025-12-23T20:26:42.569228Z","url":"https://files.pythonhosted.org/packages/50/ee/10eae91ba4ad071c92db3c178ba861f30406342de9f0ddbe6d51fd741236/psutil-7.2.0-cp314-cp314t-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"data-dist-info-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"filename":"psutil-7.2.0-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"cc66d21366850a4261412ce994ae9976bba9852dafb4f2fa60db68ed17ff5281"},"provenance":null,"requires-python":">=3.6","size":181487,"upload-time":"2025-12-23T20:26:44.633020Z","url":"https://files.pythonhosted.org/packages/87/3a/2b2897443d56fedbbc34ac68a0dc7d55faa05d555372a2f989109052f86d/psutil-7.2.0-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"data-dist-info-metadata":{"sha256":"2170dd4480c057291a293d121c10bf5228540dd58ab43945d29672ec02a0a23a"},"filename":"psutil-7.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"e025d67b42b8f22b096d5d20f5171de0e0fefb2f0ce983a13c5a1b5ed9872706"},"provenance":null,"requires-python":">=3.6","size":184320,"upload-time":"2025-12-23T20:26:46.830615Z","url":"https://files.pythonhosted.org/packages/11/66/44308428f7333db42c5ea7390c52af1b38f59b80b80c437291f58b5dfdad/psutil-7.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"795be0019027992887b63578f61759602599118117e81710fb28c5b7fb5023d6"},"data-dist-info-metadata":{"sha256":"795be0019027992887b63578f61759602599118117e81710fb28c5b7fb5023d6"},"filename":"psutil-7.2.0-cp314-cp314t-win_amd64.whl","hashes":{"sha256":"45f6b91f7ad63414d6454fd609e5e3556d0e1038d5d9c75a1368513bdf763f57"},"provenance":null,"requires-python":">=3.6","size":140372,"upload-time":"2025-12-23T20:26:49.334377Z","url":"https://files.pythonhosted.org/packages/18/28/d2feadc7f18e501c5ce687c377db7dca924585418fd694272b8e488ea99f/psutil-7.2.0-cp314-cp314t-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"795be0019027992887b63578f61759602599118117e81710fb28c5b7fb5023d6"},"data-dist-info-metadata":{"sha256":"795be0019027992887b63578f61759602599118117e81710fb28c5b7fb5023d6"},"filename":"psutil-7.2.0-cp314-cp314t-win_arm64.whl","hashes":{"sha256":"87b18a19574139d60a546e88b5f5b9cbad598e26cdc790d204ab95d7024f03ee"},"provenance":null,"requires-python":">=3.6","size":135400,"upload-time":"2025-12-23T20:26:51.585604Z","url":"https://files.pythonhosted.org/packages/b2/1d/48381f5fd0425aa054c4ee3de24f50de3d6c347019f3aec75f357377d447/psutil-7.2.0-cp314-cp314t-win_arm64.whl","yanked":false},{"core-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"data-dist-info-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"filename":"psutil-7.2.0-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"977a2fcd132d15cb05b32b2d85b98d087cad039b0ce435731670ba74da9e6133"},"provenance":null,"requires-python":">=3.6","size":128116,"upload-time":"2025-12-23T20:26:53.516520Z","url":"https://files.pythonhosted.org/packages/40/c5/a49160bf3e165b7b93a60579a353cf5d939d7f878fe5fd369110f1d18043/psutil-7.2.0-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"data-dist-info-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"filename":"psutil-7.2.0-cp36-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"24151011c21fadd94214d7139d7c6c54569290d7e553989bdf0eab73b13beb8c"},"provenance":null,"requires-python":">=3.6","size":128925,"upload-time":"2025-12-23T20:26:55.573324Z","url":"https://files.pythonhosted.org/packages/10/a1/c75feb480f60cd768fb6ed00ac362a16a33e5076ec8475a22d8162fb2659/psutil-7.2.0-cp36-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"data-dist-info-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"filename":"psutil-7.2.0-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"91f211ba9279e7c61d9d8f84b713cfc38fa161cb0597d5cb3f1ca742f6848254"},"provenance":null,"requires-python":">=3.6","size":154666,"upload-time":"2025-12-23T20:26:57.312776Z","url":"https://files.pythonhosted.org/packages/12/ff/e93136587c00a543f4bc768b157fac2c47cd77b180d4f4e5c6efb6ea53a2/psutil-7.2.0-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"data-dist-info-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"filename":"psutil-7.2.0-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"f37415188b7ea98faf90fed51131181646c59098b077550246e2e092e127418b"},"provenance":null,"requires-python":">=3.6","size":156109,"upload-time":"2025-12-23T20:26:58.851353Z","url":"https://files.pythonhosted.org/packages/b8/dd/4c2de9c3827c892599d277a69d2224136800870a8a88a80981de905de28d/psutil-7.2.0-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"data-dist-info-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"filename":"psutil-7.2.0-cp36-abi3-musllinux_1_2_aarch64.whl","hashes":{"sha256":"0d12c7ce6ed1128cd81fd54606afa054ac7dbb9773469ebb58cf2f171c49f2ac"},"provenance":null,"requires-python":">=3.6","size":148081,"upload-time":"2025-12-23T20:27:01.318270Z","url":"https://files.pythonhosted.org/packages/81/3f/090943c682d3629968dd0b04826ddcbc760ee1379021dbe316e2ddfcd01b/psutil-7.2.0-cp36-abi3-musllinux_1_2_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"data-dist-info-metadata":{"sha256":"78cbedc39a964b930e33a3c9bd2f10aaa72c827cfcce30c4074dffd544c24a58"},"filename":"psutil-7.2.0-cp36-abi3-musllinux_1_2_x86_64.whl","hashes":{"sha256":"ca0faef7976530940dcd39bc5382d0d0d5eb023b186a4901ca341bd8d8684151"},"provenance":null,"requires-python":">=3.6","size":147376,"upload-time":"2025-12-23T20:27:03.347816Z","url":"https://files.pythonhosted.org/packages/c4/88/c39648ebb8ec182d0364af53cdefe6eddb5f3872ba718b5855a8ff65d6d4/psutil-7.2.0-cp36-abi3-musllinux_1_2_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"758019d458979edff64acde45968217765d5828ba125593a164a985cdc0754bd"},"data-dist-info-metadata":{"sha256":"758019d458979edff64acde45968217765d5828ba125593a164a985cdc0754bd"},"filename":"psutil-7.2.0-cp37-abi3-win_amd64.whl","hashes":{"sha256":"abdb74137ca232d20250e9ad471f58d500e7743bc8253ba0bfbf26e570c0e437"},"provenance":null,"requires-python":">=3.6","size":136910,"upload-time":"2025-12-23T20:27:05.289344Z","url":"https://files.pythonhosted.org/packages/01/a2/5b39e08bd9b27476bc7cce7e21c71a481ad60b81ffac49baf02687a50d7f/psutil-7.2.0-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"795be0019027992887b63578f61759602599118117e81710fb28c5b7fb5023d6"},"data-dist-info-metadata":{"sha256":"795be0019027992887b63578f61759602599118117e81710fb28c5b7fb5023d6"},"filename":"psutil-7.2.0-cp37-abi3-win_arm64.whl","hashes":{"sha256":"284e71038b3139e7ab3834b63b3eb5aa5565fcd61a681ec746ef9a0a8c457fd2"},"provenance":null,"requires-python":">=3.6","size":133807,"upload-time":"2025-12-23T20:27:06.825130Z","url":"https://files.pythonhosted.org/packages/59/54/53839db1258c1eaeb4ded57ff202144ebc75b23facc05a74fd98d338b0c6/psutil-7.2.0-cp37-abi3-win_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-7.2.0.tar.gz","hashes":{"sha256":"2e4f8e1552f77d14dc96fb0f6240c5b34a37081c0889f0853b3b29a496e5ef64"},"provenance":null,"requires-python":">=3.6","size":489863,"upload-time":"2025-12-23T20:26:24.616214Z","url":"https://files.pythonhosted.org/packages/be/7c/31d1c3ceb1260301f87565f50689dc6da3db427ece1e1e012af22abca54e/psutil-7.2.0.tar.gz","yanked":false},{"core-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"data-dist-info-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"filename":"psutil-7.2.1-cp313-cp313t-macosx_10_13_x86_64.whl","hashes":{"sha256":"ba9f33bb525b14c3ea563b2fd521a84d2fa214ec59e3e6a2858f78d0844dd60d"},"provenance":null,"requires-python":">=3.6","size":129624,"upload-time":"2025-12-29T08:26:04.255517Z","url":"https://files.pythonhosted.org/packages/77/8e/f0c242053a368c2aa89584ecd1b054a18683f13d6e5a318fc9ec36582c94/psutil-7.2.1-cp313-cp313t-macosx_10_13_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"data-dist-info-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"filename":"psutil-7.2.1-cp313-cp313t-macosx_11_0_arm64.whl","hashes":{"sha256":"81442dac7abfc2f4f4385ea9e12ddf5a796721c0f6133260687fec5c3780fa49"},"provenance":null,"requires-python":">=3.6","size":130132,"upload-time":"2025-12-29T08:26:06.228150Z","url":"https://files.pythonhosted.org/packages/26/97/a58a4968f8990617decee234258a2b4fc7cd9e35668387646c1963e69f26/psutil-7.2.1-cp313-cp313t-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"data-dist-info-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"filename":"psutil-7.2.1-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"ea46c0d060491051d39f0d2cff4f98d5c72b288289f57a21556cc7d504db37fc"},"provenance":null,"requires-python":">=3.6","size":180612,"upload-time":"2025-12-29T08:26:08.276538Z","url":"https://files.pythonhosted.org/packages/db/6d/ed44901e830739af5f72a85fa7ec5ff1edea7f81bfbf4875e409007149bd/psutil-7.2.1-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"data-dist-info-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"filename":"psutil-7.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"35630d5af80d5d0d49cfc4d64c1c13838baf6717a13effb35869a5919b854cdf"},"provenance":null,"requires-python":">=3.6","size":183201,"upload-time":"2025-12-29T08:26:10.622093Z","url":"https://files.pythonhosted.org/packages/c7/65/b628f8459bca4efbfae50d4bf3feaab803de9a160b9d5f3bd9295a33f0c2/psutil-7.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"08f1da19e657e59de5e9a94e4295fa686697c5c4e76447bd048138c2227e8293"},"data-dist-info-metadata":{"sha256":"08f1da19e657e59de5e9a94e4295fa686697c5c4e76447bd048138c2227e8293"},"filename":"psutil-7.2.1-cp313-cp313t-win_amd64.whl","hashes":{"sha256":"923f8653416604e356073e6e0bccbe7c09990acef442def2f5640dd0faa9689f"},"provenance":null,"requires-python":">=3.6","size":139081,"upload-time":"2025-12-29T08:26:12.483257Z","url":"https://files.pythonhosted.org/packages/fb/23/851cadc9764edcc18f0effe7d0bf69f727d4cf2442deb4a9f78d4e4f30f2/psutil-7.2.1-cp313-cp313t-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"08f1da19e657e59de5e9a94e4295fa686697c5c4e76447bd048138c2227e8293"},"data-dist-info-metadata":{"sha256":"08f1da19e657e59de5e9a94e4295fa686697c5c4e76447bd048138c2227e8293"},"filename":"psutil-7.2.1-cp313-cp313t-win_arm64.whl","hashes":{"sha256":"cfbe6b40ca48019a51827f20d830887b3107a74a79b01ceb8cc8de4ccb17b672"},"provenance":null,"requires-python":">=3.6","size":134767,"upload-time":"2025-12-29T08:26:14.528248Z","url":"https://files.pythonhosted.org/packages/59/82/d63e8494ec5758029f31c6cb06d7d161175d8281e91d011a4a441c8a43b5/psutil-7.2.1-cp313-cp313t-win_arm64.whl","yanked":false},{"core-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"data-dist-info-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"filename":"psutil-7.2.1-cp314-cp314t-macosx_10_15_x86_64.whl","hashes":{"sha256":"494c513ccc53225ae23eec7fe6e1482f1b8a44674241b54561f755a898650679"},"provenance":null,"requires-python":">=3.6","size":129716,"upload-time":"2025-12-29T08:26:16.017500Z","url":"https://files.pythonhosted.org/packages/05/c2/5fb764bd61e40e1fe756a44bd4c21827228394c17414ade348e28f83cd79/psutil-7.2.1-cp314-cp314t-macosx_10_15_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"data-dist-info-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"filename":"psutil-7.2.1-cp314-cp314t-macosx_11_0_arm64.whl","hashes":{"sha256":"3fce5f92c22b00cdefd1645aa58ab4877a01679e901555067b1bd77039aa589f"},"provenance":null,"requires-python":">=3.6","size":130133,"upload-time":"2025-12-29T08:26:18.009017Z","url":"https://files.pythonhosted.org/packages/c9/d2/935039c20e06f615d9ca6ca0ab756cf8408a19d298ffaa08666bc18dc805/psutil-7.2.1-cp314-cp314t-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"data-dist-info-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"filename":"psutil-7.2.1-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"93f3f7b0bb07711b49626e7940d6fe52aa9940ad86e8f7e74842e73189712129"},"provenance":null,"requires-python":">=3.6","size":181518,"upload-time":"2025-12-29T08:26:20.241501Z","url":"https://files.pythonhosted.org/packages/77/69/19f1eb0e01d24c2b3eacbc2f78d3b5add8a89bf0bb69465bc8d563cc33de/psutil-7.2.1-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"data-dist-info-metadata":{"sha256":"23740d4f8fcd640f5d191b7b232f6a393e1df6bf023302f619c701f995dff02d"},"filename":"psutil-7.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"d34d2ca888208eea2b5c68186841336a7f5e0b990edec929be909353a202768a"},"provenance":null,"requires-python":">=3.6","size":184348,"upload-time":"2025-12-29T08:26:22.215886Z","url":"https://files.pythonhosted.org/packages/e1/6d/7e18b1b4fa13ad370787626c95887b027656ad4829c156bb6569d02f3262/psutil-7.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"08f1da19e657e59de5e9a94e4295fa686697c5c4e76447bd048138c2227e8293"},"data-dist-info-metadata":{"sha256":"08f1da19e657e59de5e9a94e4295fa686697c5c4e76447bd048138c2227e8293"},"filename":"psutil-7.2.1-cp314-cp314t-win_amd64.whl","hashes":{"sha256":"2ceae842a78d1603753561132d5ad1b2f8a7979cb0c283f5b52fb4e6e14b1a79"},"provenance":null,"requires-python":">=3.6","size":140400,"upload-time":"2025-12-29T08:26:23.993885Z","url":"https://files.pythonhosted.org/packages/98/60/1672114392dd879586d60dd97896325df47d9a130ac7401318005aab28ec/psutil-7.2.1-cp314-cp314t-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"08f1da19e657e59de5e9a94e4295fa686697c5c4e76447bd048138c2227e8293"},"data-dist-info-metadata":{"sha256":"08f1da19e657e59de5e9a94e4295fa686697c5c4e76447bd048138c2227e8293"},"filename":"psutil-7.2.1-cp314-cp314t-win_arm64.whl","hashes":{"sha256":"08a2f175e48a898c8eb8eace45ce01777f4785bc744c90aa2cc7f2fa5462a266"},"provenance":null,"requires-python":">=3.6","size":135430,"upload-time":"2025-12-29T08:26:25.999852Z","url":"https://files.pythonhosted.org/packages/fb/7b/d0e9d4513c46e46897b46bcfc410d51fc65735837ea57a25170f298326e6/psutil-7.2.1-cp314-cp314t-win_arm64.whl","yanked":false},{"core-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"data-dist-info-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"filename":"psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl","hashes":{"sha256":"b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42"},"provenance":null,"requires-python":">=3.6","size":128137,"upload-time":"2025-12-29T08:26:27.759659Z","url":"https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"data-dist-info-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"filename":"psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl","hashes":{"sha256":"05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1"},"provenance":null,"requires-python":">=3.6","size":128947,"upload-time":"2025-12-29T08:26:29.548034Z","url":"https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl","yanked":false},{"core-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"data-dist-info-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"filename":"psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","hashes":{"sha256":"5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8"},"provenance":null,"requires-python":">=3.6","size":154694,"upload-time":"2025-12-29T08:26:32.147774Z","url":"https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"data-dist-info-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"filename":"psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","hashes":{"sha256":"ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6"},"provenance":null,"requires-python":">=3.6","size":156136,"upload-time":"2025-12-29T08:26:34.079911Z","url":"https://files.pythonhosted.org/packages/06/e4/b751cdf839c011a9714a783f120e6a86b7494eb70044d7d81a25a5cd295f/psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"data-dist-info-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"filename":"psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl","hashes":{"sha256":"f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8"},"provenance":null,"requires-python":">=3.6","size":148108,"upload-time":"2025-12-29T08:26:36.225796Z","url":"https://files.pythonhosted.org/packages/44/ad/bbf6595a8134ee1e94a4487af3f132cef7fce43aef4a93b49912a48c3af7/psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl","yanked":false},{"core-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"data-dist-info-metadata":{"sha256":"c340b0dbe2145d45dac650326d0f34f3ee664206d3bcd32785393b3476525944"},"filename":"psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl","hashes":{"sha256":"99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67"},"provenance":null,"requires-python":">=3.6","size":147402,"upload-time":"2025-12-29T08:26:39.210698Z","url":"https://files.pythonhosted.org/packages/1c/15/dd6fd869753ce82ff64dcbc18356093471a5a5adf4f77ed1f805d473d859/psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl","yanked":false},{"core-metadata":{"sha256":"54955c981ab8a8b2b87efc900b6916c908a315660b0c3c5d205c910565bb3261"},"data-dist-info-metadata":{"sha256":"54955c981ab8a8b2b87efc900b6916c908a315660b0c3c5d205c910565bb3261"},"filename":"psutil-7.2.1-cp37-abi3-win_amd64.whl","hashes":{"sha256":"b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17"},"provenance":null,"requires-python":">=3.6","size":136938,"upload-time":"2025-12-29T08:26:41.036253Z","url":"https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl","yanked":false},{"core-metadata":{"sha256":"08f1da19e657e59de5e9a94e4295fa686697c5c4e76447bd048138c2227e8293"},"data-dist-info-metadata":{"sha256":"08f1da19e657e59de5e9a94e4295fa686697c5c4e76447bd048138c2227e8293"},"filename":"psutil-7.2.1-cp37-abi3-win_arm64.whl","hashes":{"sha256":"0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442"},"provenance":null,"requires-python":">=3.6","size":133836,"upload-time":"2025-12-29T08:26:43.086974Z","url":"https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl","yanked":false},{"core-metadata":false,"data-dist-info-metadata":false,"filename":"psutil-7.2.1.tar.gz","hashes":{"sha256":"f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3"},"provenance":null,"requires-python":">=3.6","size":490253,"upload-time":"2025-12-29T08:26:00.169622Z","url":"https://files.pythonhosted.org/packages/73/cb/09e5184fb5fc0358d110fc3ca7f6b1d033800734d34cac10f4136cfac10e/psutil-7.2.1.tar.gz","yanked":false}],"meta":{"_last-serial":33241970,"api-version":"1.4"},"name":"psutil","project-status":{"status":"active"},"versions":["0.1.1","0.1.2","0.1.3","0.2.0","0.2.1","0.3.0","0.4.0","0.4.1","0.5.0","0.5.1","0.6.0","0.6.1","0.7.0","0.7.1","1.0.0","1.0.1","1.1.0","1.1.1","1.1.2","1.1.3","1.2.0","1.2.1","2.0.0","2.1.0","2.1.1","2.1.2","2.1.3","2.2.0","2.2.1","3.0.0","3.0.1","3.1.0","3.1.1","3.2.0","3.2.1","3.2.2","3.3.0","3.4.1","3.4.2","4.0.0","4.1.0","4.2.0","4.3.0","4.3.1","4.4.0","4.4.1","4.4.2","5.0.0","5.0.1","5.1.0","5.1.1","5.1.2","5.1.3","5.2.0","5.2.1","5.2.2","5.3.0","5.3.1","5.4.0","5.4.1","5.4.2","5.4.3","5.4.4","5.4.5","5.4.6","5.4.7","5.4.8","5.5.0","5.5.1","5.6.0","5.6.1","5.6.2","5.6.3","5.6.4","5.6.5","5.6.6","5.6.7","5.7.0","5.7.1","5.7.2","5.7.3","5.8.0","5.9.0","5.9.1","5.9.2","5.9.3","5.9.4","5.9.5","5.9.6","5.9.7","5.9.8","6.0.0","6.1.0","6.1.1","7.0.0","7.1.0","7.1.1","7.1.2","7.1.3","7.2.0","7.2.1"]} diff --git a/benches/benchmarks/json_loads.py b/benches/benchmarks/json_loads.py new file mode 100644 index 00000000000..f67f14d2d9e --- /dev/null +++ b/benches/benchmarks/json_loads.py @@ -0,0 +1,7 @@ +import json + +with open('benches/_data/pypi_org__simple__psutil.json') as f: + data = f.read() + + +loaded = json.loads(data) From 81d89d1d90af56c208bc0ce0b304f4cc5f4c3119 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:59:10 +0900 Subject: [PATCH 801/819] Fix flaky multiprocessing tests (#6725) --- .github/workflows/ci.yaml | 2 -- Lib/test/_test_multiprocessing.py | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index cc7f745afaa..778890a9f3e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -108,14 +108,12 @@ env: ENV_POLLUTING_TESTS_COMMON: >- ENV_POLLUTING_TESTS_LINUX: >- test.test_multiprocessing_fork.test_processes - test.test_multiprocessing_fork.test_threads test.test_multiprocessing_forkserver.test_processes test.test_multiprocessing_forkserver.test_threads test.test_multiprocessing_spawn.test_processes test.test_multiprocessing_spawn.test_threads ENV_POLLUTING_TESTS_MACOS: >- test.test_multiprocessing_forkserver.test_processes - test.test_multiprocessing_forkserver.test_threads test.test_multiprocessing_spawn.test_processes test.test_multiprocessing_spawn.test_threads ENV_POLLUTING_TESTS_WINDOWS: >- diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 2fc206f7d53..80b663d32f4 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -6597,7 +6597,8 @@ def tearDownClass(cls): support.print_warning(f'Dangling processes: {processes}') processes = None - threads = set(threading._dangling) - set(cls.dangling[1]) + # TODO: RUSTPYTHON: Filter out stopped threads since gc.collect() is a no-op + threads = {t for t in threading._dangling if t.is_alive()} - {t for t in cls.dangling[1] if t.is_alive()} if threads: test.support.environment_altered = True support.print_warning(f'Dangling threads: {threads}') @@ -6794,7 +6795,8 @@ def tearDownModule(): support.print_warning(f'Dangling processes: {processes}') processes = None - threads = set(threading._dangling) - set(dangling[1]) + # TODO: RUSTPYTHON: Filter out stopped threads since gc.collect() is a no-op + threads = {t for t in threading._dangling if t.is_alive()} - {t for t in dangling[1] if t.is_alive()} if threads: need_sleep = True test.support.environment_altered = True From 3d4aaaa6c10c12681e66a5b6975d0854cc0230ac Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:20:52 +0900 Subject: [PATCH 802/819] thread.setname and context (#6726) --- crates/stdlib/src/contextvars.rs | 45 ++++++++++++++++++++++++------ crates/vm/src/stdlib/thread.rs | 48 +++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/crates/stdlib/src/contextvars.rs b/crates/stdlib/src/contextvars.rs index 329342fe6dc..5608b39d1e0 100644 --- a/crates/stdlib/src/contextvars.rs +++ b/crates/stdlib/src/contextvars.rs @@ -24,12 +24,12 @@ thread_local! { mod _contextvars { use crate::vm::{ AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, atomic_func, - builtins::{PyGenericAlias, PyStrRef, PyType, PyTypeRef}, + builtins::{PyGenericAlias, PyList, PyStrRef, PyType, PyTypeRef}, class::StaticType, common::hash::PyHash, function::{ArgCallable, FuncArgs, OptionalArg}, protocol::{PyMappingMethods, PySequenceMethods}, - types::{AsMapping, AsSequence, Constructor, Hashable, Representable}, + types::{AsMapping, AsSequence, Constructor, Hashable, Iterable, Representable}, }; use core::{ cell::{Cell, RefCell, UnsafeCell}, @@ -163,7 +163,7 @@ mod _contextvars { } } - #[pyclass(with(Constructor, AsMapping, AsSequence))] + #[pyclass(with(Constructor, AsMapping, AsSequence, Iterable))] impl PyContext { #[pymethod] fn run( @@ -205,11 +205,6 @@ mod _contextvars { self.borrow_vars().len() } - #[pymethod] - fn __iter__(&self) -> PyResult { - unimplemented!("Context.__iter__ is currently under construction") - } - #[pymethod] fn get( &self, @@ -238,6 +233,15 @@ mod _contextvars { let vars = zelf.borrow_vars(); vars.values().map(|value| value.to_owned()).collect() } + + // TODO: wrong return type + #[pymethod] + fn items(zelf: PyRef, vm: &VirtualMachine) -> Vec { + let vars = zelf.borrow_vars(); + vars.iter() + .map(|(k, v)| vm.ctx.new_tuple(vec![k.clone().into(), v.clone()]).into()) + .collect() + } } impl Constructor for PyContext { @@ -281,6 +285,15 @@ mod _contextvars { } } + impl Iterable for PyContext { + fn iter(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + let vars = zelf.borrow_vars(); + let keys: Vec = vars.keys().map(|k| k.clone().into()).collect(); + let list = vm.ctx.new_list(keys); + ::iter(list, vm) + } + } + #[pyattr] #[pyclass(name, traverse)] #[derive(PyPayload)] @@ -574,6 +587,22 @@ mod _contextvars { ) -> PyGenericAlias { PyGenericAlias::from_args(cls, args, vm) } + + #[pymethod] + fn __enter__(zelf: PyRef) -> PyRef { + zelf + } + + #[pymethod] + fn __exit__( + zelf: &Py, + _ty: PyObjectRef, + _val: PyObjectRef, + _tb: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + ContextVar::reset(&zelf.var, zelf.to_owned(), vm) + } } impl Constructor for ContextToken { diff --git a/crates/vm/src/stdlib/thread.rs b/crates/vm/src/stdlib/thread.rs index 0e14fe0e4d1..d51d78015d6 100644 --- a/crates/vm/src/stdlib/thread.rs +++ b/crates/vm/src/stdlib/thread.rs @@ -9,7 +9,7 @@ pub(crate) use _thread::{ pub(crate) mod _thread { use crate::{ AsObject, Py, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyDictRef, PyStr, PyTupleRef, PyType, PyTypeRef}, + builtins::{PyDictRef, PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef}, frame::FrameRef, function::{ArgCallable, Either, FuncArgs, KwArgs, OptionalArg, PySetterValue}, types::{Constructor, GetAttr, Representable, SetAttr}, @@ -260,6 +260,11 @@ pub(crate) mod _thread { Ok(()) } + #[pymethod] + fn locked(&self) -> bool { + self.mu.is_locked() + } + #[pymethod] fn _is_owned(&self) -> bool { self.mu.is_owned_by_current_thread() @@ -293,6 +298,47 @@ pub(crate) mod _thread { current_thread_id() } + /// Set the name of the current thread + #[pyfunction] + fn set_name(name: PyStrRef) { + #[cfg(target_os = "linux")] + { + use std::ffi::CString; + if let Ok(c_name) = CString::new(name.as_str()) { + // pthread_setname_np on Linux has a 16-byte limit including null terminator + // TODO: Potential UTF-8 boundary issue when truncating thread name on Linux. + // https://github.com/RustPython/RustPython/pull/6726/changes#r2689379171 + let truncated = if c_name.as_bytes().len() > 15 { + CString::new(&c_name.as_bytes()[..15]).unwrap_or(c_name) + } else { + c_name + }; + unsafe { + libc::pthread_setname_np(libc::pthread_self(), truncated.as_ptr()); + } + } + } + #[cfg(target_os = "macos")] + { + use std::ffi::CString; + if let Ok(c_name) = CString::new(name.as_str()) { + unsafe { + libc::pthread_setname_np(c_name.as_ptr()); + } + } + } + #[cfg(windows)] + { + // Windows doesn't have a simple pthread_setname_np equivalent + // SetThreadDescription requires Windows 10+ + let _ = name; + } + #[cfg(not(any(target_os = "linux", target_os = "macos", windows)))] + { + let _ = name; + } + } + /// Get OS-level thread ID (pthread_self on Unix) /// This is important for fork compatibility - the ID must remain stable after fork #[cfg(unix)] From 7a516c44c9e088f8c7a0764f3c27bcd78cc3cacd Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 14 Jan 2026 23:51:41 +0900 Subject: [PATCH 803/819] Update AI docs (#6728) * update copilot instructions * upgrade-pylib command --- .claude/commands/upgrade-pylib.md | 32 +++++++++++++++++++++++++++++++ .github/copilot-instructions.md | 15 +++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 .claude/commands/upgrade-pylib.md diff --git a/.claude/commands/upgrade-pylib.md b/.claude/commands/upgrade-pylib.md new file mode 100644 index 00000000000..520d5deb7ba --- /dev/null +++ b/.claude/commands/upgrade-pylib.md @@ -0,0 +1,32 @@ +# Upgrade Python Library from CPython + +Upgrade a Python standard library module from CPython to RustPython. + +## Arguments +- `$ARGUMENTS`: Library name to upgrade (e.g., `inspect`, `asyncio`, `json`) + +## Steps + +1. **Delete existing library in Lib/** + - If `Lib/$ARGUMENTS.py` exists, delete it + - If `Lib/$ARGUMENTS/` directory exists, delete it + +2. **Copy from cpython/Lib/** + - If `cpython/Lib/$ARGUMENTS.py` exists, copy it to `Lib/$ARGUMENTS.py` + - If `cpython/Lib/$ARGUMENTS/` directory exists, copy it to `Lib/$ARGUMENTS/` + +3. **Upgrade tests** + - Run: `python lib_updater.py --quick-upgrade cpython/Lib/test/test_$ARGUMENTS` + - This will update the test files with appropriate RustPython markers + +## Example Usage +``` +/upgrade-pylib inspect +/upgrade-pylib json +/upgrade-pylib asyncio +``` + +## Notes +- The cpython/ directory should contain the CPython source that we're syncing from +- lib_updater.py handles adding `# TODO: RUSTPYTHON` markers and `@unittest.expectedFailure` decorators +- After upgrading, you may need to run tests to verify: `cargo run --release -- -m test test_$ARGUMENTS` diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a2ab43a695c..4667f4ee17b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -87,11 +87,14 @@ rm -r target/debug/build/rustpython-* && find . | grep -E "\.pyc$" | xargs rm -r # Run Rust unit tests cargo test --workspace --exclude rustpython_wasm -# Run Python snippets tests +# Run Python snippets tests (debug mode recommended for faster compilation) +cargo run -- extra_tests/snippets/builtin_bytes.py + +# Run all Python snippets tests with pytest cd extra_tests pytest -v -# Run the Python test module +# Run the Python test module (release mode recommended for better performance) cargo run --release -- -m test ${TEST_MODULE} cargo run --release -- -m test test_unicode # to test test_unicode.py @@ -99,6 +102,8 @@ cargo run --release -- -m test test_unicode # to test test_unicode.py cargo run --release -- -m test test_unicode -k test_unicode_escape ``` +**Note**: For `extra_tests/snippets` tests, use debug mode (`cargo run`) as compilation is faster. For `unittest` (`-m test`), use release mode (`cargo run --release`) for better runtime performance. + ### Determining What to Implement Run `./whats_left.py` to get a list of unimplemented methods, which is helpful when looking for contribution opportunities. @@ -184,6 +189,12 @@ cargo build --target wasm32-wasip1 --no-default-features --features freeze-stdli cargo run --features jit ``` +### Building venvlauncher (Windows) + +See DEVELOPMENT.md "CPython Version Upgrade Checklist" section. + +**IMPORTANT**: All 4 venvlauncher binaries use the same source code. Do NOT add multiple `[[bin]]` entries to Cargo.toml. Build once and copy with different names. + ## Test Code Modification Rules **CRITICAL: Test code modification restrictions** From 13a875f609c5ecf77f0ec1ed17ce79653c6e8513 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Thu, 15 Jan 2026 00:01:14 +0900 Subject: [PATCH 804/819] fix backslash handling in fix_test.py (#6729) --- scripts/fix_test.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/fix_test.py b/scripts/fix_test.py index 0970531f079..1dfea12b8aa 100644 --- a/scripts/fix_test.py +++ b/scripts/fix_test.py @@ -150,11 +150,14 @@ def run_test(test_name): # Handle --quick-import: extract Lib/... path and copy if needed if args.quick_import is not None: - src_str = str(args.quick_import) + # Normalize path separators to forward slashes for cross-platform support + src_str = str(args.quick_import).replace("\\", "/") lib_marker = "/Lib/" if lib_marker not in src_str: - print(f"Error: --quick-import path must contain '/Lib/' (got: {src_str})") + print( + f"Error: --quick-import path must contain '/Lib/' or '\\Lib\\' (got: {args.quick_import})" + ) sys.exit(1) idx = src_str.index(lib_marker) From e3890f9b4a6fc1a641cb192e4ac73df887713e54 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 14 Jan 2026 19:52:25 +0200 Subject: [PATCH 805/819] Bytecode pseudo opcodes (#6715) --- Lib/_opcode_metadata.py | 18 +- Lib/test/test__opcode.py | 1 - crates/codegen/src/compile.rs | 160 ++++--- crates/codegen/src/ir.rs | 87 ++-- crates/compiler-core/src/bytecode.rs | 20 +- .../compiler-core/src/bytecode/instruction.rs | 397 +++++++++++++----- crates/compiler-core/src/bytecode/oparg.rs | 2 +- crates/jit/src/instructions.rs | 8 +- crates/stdlib/src/opcode.rs | 100 +++-- crates/vm/src/frame.rs | 264 ++++++------ scripts/generate_opcode_metadata.py | 11 +- 11 files changed, 652 insertions(+), 416 deletions(-) diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 560523bfab2..3e98489419f 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -138,14 +138,20 @@ 'JUMP_IF_NOT_EXC_MATCH': 131, 'SET_EXC_INFO': 134, 'SUBSCRIPT': 135, - 'LOAD_SUPER_METHOD': 136, - 'LOAD_ZERO_SUPER_ATTR': 137, - 'LOAD_ZERO_SUPER_METHOD': 138, 'RESUME': 149, - 'JUMP': 252, 'LOAD_CLOSURE': 253, - 'LOAD_ATTR_METHOD': 254, - 'POP_BLOCK': 255, + 'JUMP': 256, + 'JUMP_NO_INTERRUPT': 257, + 'RESERVED_258': 258, + 'LOAD_ATTR_METHOD': 259, + 'LOAD_SUPER_METHOD': 260, + 'LOAD_ZERO_SUPER_ATTR': 261, + 'LOAD_ZERO_SUPER_METHOD': 262, + 'POP_BLOCK': 263, + 'SETUP_CLEANUP': 264, + 'SETUP_FINALLY': 265, + 'SETUP_WITH': 266, + 'STORE_FAST_MAYBE_NULL': 267, } # CPython 3.13 compatible: opcodes < 44 have no argument diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py index dabe95f7167..60dcdc6cd70 100644 --- a/Lib/test/test__opcode.py +++ b/Lib/test/test__opcode.py @@ -16,7 +16,6 @@ def check_bool_function_result(self, func, ops, expected): self.assertIsInstance(func(op), bool) self.assertEqual(func(op), expected) - @unittest.expectedFailure # TODO: RUSTPYTHON; Only supporting u8 ATM def test_invalid_opcodes(self): invalid = [-100, -1, 255, 512, 513, 1000] self.check_bool_function_result(_opcode.is_valid, invalid, False) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index b7bc3f4d9c7..0f52a519f22 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -34,16 +34,17 @@ use ruff_python_ast::{ visitor::{Visitor, walk_expr}, }; use ruff_text_size::{Ranged, TextRange}; +use std::collections::HashSet; + use rustpython_compiler_core::{ Mode, OneIndexed, PositionEncoding, SourceFile, SourceLocation, bytecode::{ - self, Arg as OpArgMarker, BinaryOperator, BuildSliceArgCount, CodeObject, + self, AnyInstruction, Arg as OpArgMarker, BinaryOperator, BuildSliceArgCount, CodeObject, ComparisonOperator, ConstantData, ConvertValueOparg, Instruction, Invert, OpArg, OpArgType, - UnpackExArgs, + PseudoInstruction, UnpackExArgs, }, }; use rustpython_wtf8::Wtf8Buf; -use std::collections::HashSet; const MAXBLOCKS: usize = 20; @@ -274,17 +275,24 @@ pub fn compile_expression( } macro_rules! emit { - ($c:expr, Instruction::$op:ident { $arg:ident$(,)? }$(,)?) => { - $c.emit_arg($arg, |x| Instruction::$op { $arg: x }) + // Struct variant with single identifier (e.g., Foo::A { arg }) + ($c:expr, $enum:ident :: $op:ident { $arg:ident $(,)? } $(,)?) => { + $c.emit_arg($arg, |x| $enum::$op { $arg: x }) }; - ($c:expr, Instruction::$op:ident { $arg:ident : $arg_val:expr $(,)? }$(,)?) => { - $c.emit_arg($arg_val, |x| Instruction::$op { $arg: x }) + + // Struct variant with explicit value (e.g., Foo::A { arg: 42 }) + ($c:expr, $enum:ident :: $op:ident { $arg:ident : $arg_val:expr $(,)? } $(,)?) => { + $c.emit_arg($arg_val, |x| $enum::$op { $arg: x }) }; - ($c:expr, Instruction::$op:ident( $arg_val:expr $(,)? )$(,)?) => { - $c.emit_arg($arg_val, Instruction::$op) + + // Tuple variant (e.g., Foo::B(42)) + ($c:expr, $enum:ident :: $op:ident($arg_val:expr $(,)? ) $(,)?) => { + $c.emit_arg($arg_val, $enum::$op) }; - ($c:expr, Instruction::$op:ident$(,)?) => { - $c.emit_no_arg(Instruction::$op) + + // No-arg variant (e.g., Foo::C) + ($c:expr, $enum:ident :: $op:ident $(,)?) => { + $c.emit_no_arg($enum::$op) }; } @@ -1181,7 +1189,7 @@ impl Compiler { // Stack when entering: [..., __exit__, return_value (if preserve_tos)] // Need to call __exit__(None, None, None) - emit!(self, Instruction::PopBlock); + emit!(self, PseudoInstruction::PopBlock); // If preserving return value, swap it below __exit__ if preserve_tos { @@ -1906,7 +1914,7 @@ impl Compiler { self.compile_statements(body)?; emit!( self, - Instruction::Jump { + PseudoInstruction::Jump { target: after_block } ); @@ -1922,7 +1930,7 @@ impl Compiler { self.compile_statements(&clause.body)?; emit!( self, - Instruction::Jump { + PseudoInstruction::Jump { target: after_block } ); @@ -2510,7 +2518,7 @@ impl Compiler { } // Jump to end (skip exception path blocks) - emit!(self, Instruction::Jump { target: end_block }); + emit!(self, PseudoInstruction::Jump { target: end_block }); if let Some(finally_except) = finally_except_block { // Restore sub_tables for exception path compilation @@ -2583,7 +2591,7 @@ impl Compiler { self.compile_statements(body)?; self.pop_fblock(FBlockType::TryExcept); // No PopBlock emit - exception table handles this - emit!(self, Instruction::Jump { target: else_block }); + emit!(self, PseudoInstruction::Jump { target: else_block }); // except handlers: self.switch_to_block(handler_block); @@ -2666,7 +2674,7 @@ impl Compiler { let handler_normal_exit = self.new_block(); emit!( self, - Instruction::Jump { + PseudoInstruction::Jump { target: handler_normal_exit, } ); @@ -2715,7 +2723,7 @@ impl Compiler { // Jump to finally block emit!( self, - Instruction::Jump { + PseudoInstruction::Jump { target: finally_block, } ); @@ -2790,7 +2798,7 @@ impl Compiler { self.compile_statements(finalbody)?; // Jump to end_block to skip exception path blocks // This prevents fall-through to finally_except_block - emit!(self, Instruction::Jump { target: end_block }); + emit!(self, PseudoInstruction::Jump { target: end_block }); } // finally (exception path) @@ -2916,7 +2924,7 @@ impl Compiler { )?; self.compile_statements(body)?; self.pop_fblock(FBlockType::TryExcept); - emit!(self, Instruction::Jump { target: else_block }); + emit!(self, PseudoInstruction::Jump { target: else_block }); // Exception handler entry self.switch_to_block(handler_block); @@ -3029,7 +3037,7 @@ impl Compiler { } // Jump to next handler - emit!(self, Instruction::Jump { target: next_block }); + emit!(self, PseudoInstruction::Jump { target: next_block }); // Handler raised an exception (cleanup_end label) self.switch_to_block(handler_except_block); @@ -3057,7 +3065,7 @@ impl Compiler { // JUMP except_with_error // We directly JUMP to next_block since no_match_block falls through to it - emit!(self, Instruction::Jump { target: next_block }); + emit!(self, PseudoInstruction::Jump { target: next_block }); // No match - pop match (None) self.switch_to_block(no_match_block); @@ -3081,7 +3089,7 @@ impl Compiler { // Stack: [prev_exc, orig, list] emit!( self, - Instruction::Jump { + PseudoInstruction::Jump { target: reraise_star_block } ); @@ -3134,7 +3142,7 @@ impl Compiler { self.pop_fblock(FBlockType::FinallyTry); } - emit!(self, Instruction::Jump { target: end_block }); + emit!(self, PseudoInstruction::Jump { target: end_block }); // Reraise the result self.switch_to_block(reraise_block); @@ -3175,7 +3183,7 @@ impl Compiler { self.pop_fblock(FBlockType::FinallyTry); } - emit!(self, Instruction::Jump { target: end_block }); + emit!(self, PseudoInstruction::Jump { target: end_block }); self.switch_to_block(end_block); if !finalbody.is_empty() { @@ -4017,7 +4025,7 @@ impl Compiler { self.ctx.loop_data = was_in_loop; emit!( self, - Instruction::Jump { + PseudoInstruction::Jump { target: while_block, } ); @@ -4154,7 +4162,7 @@ impl Compiler { emit!(self, Instruction::PopTop); // Pop __exit__ result emit!( self, - Instruction::Jump { + PseudoInstruction::Jump { target: after_block } ); @@ -4226,7 +4234,7 @@ impl Compiler { emit!(self, Instruction::PopTop); // pop lasti emit!( self, - Instruction::Jump { + PseudoInstruction::Jump { target: after_block } ); @@ -4313,7 +4321,7 @@ impl Compiler { let was_in_loop = self.ctx.loop_data.replace((for_block, after_block)); self.compile_statements(body)?; self.ctx.loop_data = was_in_loop; - emit!(self, Instruction::Jump { target: for_block }); + emit!(self, PseudoInstruction::Jump { target: for_block }); self.switch_to_block(else_block); @@ -4372,7 +4380,7 @@ impl Compiler { JumpOp::Jump => { emit!( self, - Instruction::Jump { + PseudoInstruction::Jump { target: pc.fail_pop[pops] } ); @@ -5038,7 +5046,7 @@ impl Compiler { } } // Emit a jump to the common end label and reset any failure jump targets. - emit!(self, Instruction::Jump { target: end }); + emit!(self, PseudoInstruction::Jump { target: end }); self.emit_and_reset_fail_pop(pc)?; } @@ -5274,7 +5282,7 @@ impl Compiler { } self.compile_statements(&m.body)?; - emit!(self, Instruction::Jump { target: end }); + emit!(self, PseudoInstruction::Jump { target: end }); self.emit_and_reset_fail_pop(pattern_context)?; } @@ -5387,7 +5395,7 @@ impl Compiler { self.compile_addcompare(last_op); let end = self.new_block(); - emit!(self, Instruction::Jump { target: end }); + emit!(self, PseudoInstruction::Jump { target: end }); // early exit left us with stack: `rhs, comparison_result`. We need to clean up rhs. self.switch_to_block(cleanup); @@ -5829,7 +5837,7 @@ impl Compiler { ); // JUMP_NO_INTERRUPT send (regular JUMP in RustPython) - emit!(self, Instruction::Jump { target: send_block }); + emit!(self, PseudoInstruction::Jump { target: send_block }); // fail: CLEANUP_THROW // Stack when exception: [receiver, yielded_value, exc] @@ -5906,7 +5914,7 @@ impl Compiler { emit!(self, Instruction::LoadSuperAttr { arg: idx }); } SuperCallType::ZeroArg => { - emit!(self, Instruction::LoadZeroSuperAttr { idx }); + emit!(self, PseudoInstruction::LoadZeroSuperAttr { idx }); } } } else { @@ -6094,9 +6102,12 @@ impl Compiler { }) => { self.compile_comprehension( "", - Some(Instruction::BuildList { - size: OpArgMarker::marker(), - }), + Some( + Instruction::BuildList { + size: OpArgMarker::marker(), + } + .into(), + ), generators, &|compiler| { compiler.compile_comprehension_element(elt)?; @@ -6117,9 +6128,12 @@ impl Compiler { }) => { self.compile_comprehension( "", - Some(Instruction::BuildSet { - size: OpArgMarker::marker(), - }), + Some( + Instruction::BuildSet { + size: OpArgMarker::marker(), + } + .into(), + ), generators, &|compiler| { compiler.compile_comprehension_element(elt)?; @@ -6143,9 +6157,12 @@ impl Compiler { }) => { self.compile_comprehension( "", - Some(Instruction::BuildMap { - size: OpArgMarker::marker(), - }), + Some( + Instruction::BuildMap { + size: OpArgMarker::marker(), + } + .into(), + ), generators, &|compiler| { // changed evaluation order for Py38 named expression PEP 572 @@ -6222,7 +6239,7 @@ impl Compiler { self.compile_expression(body)?; emit!( self, - Instruction::Jump { + PseudoInstruction::Jump { target: after_block, } ); @@ -6347,10 +6364,10 @@ impl Compiler { let idx = self.name(attr.as_str()); match super_type { SuperCallType::TwoArg { .. } => { - emit!(self, Instruction::LoadSuperMethod { idx }); + emit!(self, PseudoInstruction::LoadSuperMethod { idx }); } SuperCallType::ZeroArg => { - emit!(self, Instruction::LoadZeroSuperMethod { idx }); + emit!(self, PseudoInstruction::LoadZeroSuperMethod { idx }); } } self.compile_call_helper(0, args)?; @@ -6359,7 +6376,7 @@ impl Compiler { // LOAD_ATTR_METHOD pushes [method, self_or_null] on stack self.compile_expression(value)?; let idx = self.name(attr.as_str()); - emit!(self, Instruction::LoadAttrMethod { idx }); + emit!(self, PseudoInstruction::LoadAttrMethod { idx }); self.compile_call_helper(0, args)?; } } else { @@ -6505,7 +6522,7 @@ impl Compiler { fn compile_comprehension( &mut self, name: &str, - init_collection: Option, + init_collection: Option, generators: &[Comprehension], compile_element: &dyn Fn(&mut Self) -> CompileResult<()>, comprehension_type: ComprehensionType, @@ -6651,7 +6668,7 @@ impl Compiler { compile_element(self)?; for (loop_block, after_block, is_async) in loop_labels.iter().rev().copied() { - emit!(self, Instruction::Jump { target: loop_block }); + emit!(self, PseudoInstruction::Jump { target: loop_block }); self.switch_to_block(after_block); if is_async { @@ -6728,7 +6745,7 @@ impl Compiler { /// This generates bytecode inline without creating a new code object fn compile_inlined_comprehension( &mut self, - init_collection: Option, + init_collection: Option, generators: &[Comprehension], compile_element: &dyn Fn(&mut Self) -> CompileResult<()>, _has_an_async_gen: bool, @@ -6849,7 +6866,7 @@ impl Compiler { // Step 7: Close all loops for (loop_block, after_block, is_async) in loop_labels.iter().rev().copied() { - emit!(self, Instruction::Jump { target: loop_block }); + emit!(self, PseudoInstruction::Jump { target: loop_block }); self.switch_to_block(after_block); if is_async { emit!(self, Instruction::EndAsyncFor); @@ -6863,7 +6880,7 @@ impl Compiler { self.pop_fblock(FBlockType::TryExcept); // Normal path: jump past cleanup - emit!(self, Instruction::Jump { target: end_block }); + emit!(self, PseudoInstruction::Jump { target: end_block }); // Exception cleanup path self.switch_to_block(cleanup_block); @@ -6936,14 +6953,14 @@ impl Compiler { } // Low level helper functions: - fn _emit(&mut self, instr: Instruction, arg: OpArg, target: BlockIdx) { + fn _emit>(&mut self, instr: I, arg: OpArg, target: BlockIdx) { let range = self.current_source_range; let source = self.source_file.to_source_code(); let location = source.source_location(range.start(), PositionEncoding::Utf8); let end_location = source.source_location(range.end(), PositionEncoding::Utf8); let except_handler = self.current_except_handler(); self.current_block().instructions.push(ir::InstructionInfo { - instr, + instr: instr.into(), arg, target, location, @@ -6952,14 +6969,14 @@ impl Compiler { }); } - fn emit_no_arg(&mut self, ins: Instruction) { + fn emit_no_arg>(&mut self, ins: I) { self._emit(ins, OpArg::null(), BlockIdx::NULL) } - fn emit_arg>( + fn emit_arg, I: Into>( &mut self, arg: T, - f: impl FnOnce(OpArgMarker) -> Instruction, + f: impl FnOnce(OpArgMarker) -> I, ) { let (op, arg, target) = arg.emit(f); self._emit(op, arg, target) @@ -6984,9 +7001,9 @@ impl Compiler { fn emit_return_value(&mut self) { if let Some(inst) = self.current_block().instructions.last_mut() - && let Instruction::LoadConst { idx } = inst.instr + && let AnyInstruction::Real(Instruction::LoadConst { idx }) = inst.instr { - inst.instr = Instruction::ReturnConst { idx }; + inst.instr = Instruction::ReturnConst { idx }.into(); return; } emit!(self, Instruction::ReturnValue) @@ -7435,23 +7452,28 @@ impl Compiler { } trait EmitArg { - fn emit( + fn emit>( self, - f: impl FnOnce(OpArgMarker) -> Instruction, - ) -> (Instruction, OpArg, BlockIdx); + f: impl FnOnce(OpArgMarker) -> I, + ) -> (AnyInstruction, OpArg, BlockIdx); } + impl EmitArg for T { - fn emit(self, f: impl FnOnce(OpArgMarker) -> Instruction) -> (Instruction, OpArg, BlockIdx) { + fn emit>( + self, + f: impl FnOnce(OpArgMarker) -> I, + ) -> (AnyInstruction, OpArg, BlockIdx) { let (marker, arg) = OpArgMarker::new(self); - (f(marker), arg, BlockIdx::NULL) + (f(marker).into(), arg, BlockIdx::NULL) } } + impl EmitArg for BlockIdx { - fn emit( + fn emit>( self, - f: impl FnOnce(OpArgMarker) -> Instruction, - ) -> (Instruction, OpArg, BlockIdx) { - (f(OpArgMarker::marker()), OpArg::null(), self) + f: impl FnOnce(OpArgMarker) -> I, + ) -> (AnyInstruction, OpArg, BlockIdx) { + (f(OpArgMarker::marker()).into(), OpArg::null(), self) } } diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 82b9921911c..8935f2063c3 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -1,12 +1,14 @@ use core::ops; use crate::{IndexMap, IndexSet, error::InternalError}; + use rustpython_compiler_core::{ OneIndexed, SourceLocation, bytecode::{ - Arg, CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, ExceptionTableEntry, - InstrDisplayContext, Instruction, Label, OpArg, PyCodeLocationInfoKind, - encode_exception_table, encode_load_attr_arg, encode_load_super_attr_arg, + AnyInstruction, Arg, CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, + ExceptionTableEntry, InstrDisplayContext, Instruction, InstructionMetadata, Label, OpArg, + PseudoInstruction, PyCodeLocationInfoKind, encode_exception_table, encode_load_attr_arg, + encode_load_super_attr_arg, }, varint::{write_signed_varint, write_varint}, }; @@ -85,7 +87,7 @@ impl ops::IndexMut for Vec { #[derive(Debug, Clone)] pub struct InstructionInfo { - pub instr: Instruction, + pub instr: AnyInstruction, pub arg: OpArg, pub target: BlockIdx, pub location: SourceLocation, @@ -195,48 +197,73 @@ impl CodeInfo { .filter(|b| b.next != BlockIdx::NULL || !b.instructions.is_empty()) { for info in &mut block.instructions { - match info.instr { + // Special case for: + // - `Instruction::LoadAttr` + // - `Instruction::LoadSuperAttr` + + if let Some(instr) = info.instr.real() { + match instr { + // LOAD_ATTR → encode with method flag=0 + Instruction::LoadAttr { idx } => { + let encoded = encode_load_attr_arg(idx.get(info.arg), false); + info.arg = OpArg(encoded); + info.instr = Instruction::LoadAttr { idx: Arg::marker() }.into(); + } + // LOAD_SUPER_ATTR → encode with flags=0b10 (method=0, class=1) + Instruction::LoadSuperAttr { arg: idx } => { + let encoded = + encode_load_super_attr_arg(idx.get(info.arg), false, true); + info.arg = OpArg(encoded); + info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into(); + } + _ => {} + } + + continue; + } + + let instr = info.instr.expect_pseudo(); + + match instr { // LOAD_ATTR_METHOD pseudo → LOAD_ATTR (with method flag=1) - Instruction::LoadAttrMethod { idx } => { + PseudoInstruction::LoadAttrMethod { idx } => { let encoded = encode_load_attr_arg(idx.get(info.arg), true); info.arg = OpArg(encoded); - info.instr = Instruction::LoadAttr { idx: Arg::marker() }; - } - // LOAD_ATTR → encode with method flag=0 - Instruction::LoadAttr { idx } => { - let encoded = encode_load_attr_arg(idx.get(info.arg), false); - info.arg = OpArg(encoded); - info.instr = Instruction::LoadAttr { idx: Arg::marker() }; + info.instr = Instruction::LoadAttr { idx: Arg::marker() }.into(); } // POP_BLOCK pseudo → NOP - Instruction::PopBlock => { - info.instr = Instruction::Nop; + PseudoInstruction::PopBlock => { + info.instr = Instruction::Nop.into(); } // LOAD_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b11: method=1, class=1) - Instruction::LoadSuperMethod { idx } => { + PseudoInstruction::LoadSuperMethod { idx } => { let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, true); info.arg = OpArg(encoded); - info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }; + info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into(); } // LOAD_ZERO_SUPER_ATTR pseudo → LOAD_SUPER_ATTR (flags=0b00: method=0, class=0) - Instruction::LoadZeroSuperAttr { idx } => { + PseudoInstruction::LoadZeroSuperAttr { idx } => { let encoded = encode_load_super_attr_arg(idx.get(info.arg), false, false); info.arg = OpArg(encoded); - info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }; + info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into(); } // LOAD_ZERO_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b01: method=1, class=0) - Instruction::LoadZeroSuperMethod { idx } => { + PseudoInstruction::LoadZeroSuperMethod { idx } => { let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, false); info.arg = OpArg(encoded); - info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }; + info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into(); } - // LOAD_SUPER_ATTR → encode with flags=0b10 (method=0, class=1) - Instruction::LoadSuperAttr { arg: idx } => { - let encoded = encode_load_super_attr_arg(idx.get(info.arg), false, true); - info.arg = OpArg(encoded); - info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }; + PseudoInstruction::Jump { .. } => { + // PseudoInstruction::Jump instructions are handled later + } + PseudoInstruction::JumpNoInterrupt { .. } + | PseudoInstruction::Reserved258 + | PseudoInstruction::SetupCleanup + | PseudoInstruction::SetupFinally + | PseudoInstruction::SetupWith + | PseudoInstruction::StoreFastMaybeNull => { + unimplemented!("Got a placeholder pseudo instruction ({instr:?})") } - _ => {} } } } @@ -277,7 +304,9 @@ impl CodeInfo { // Convert JUMP pseudo to real instructions (direction depends on offset) let op = match info.instr { - Instruction::Jump { .. } if target != BlockIdx::NULL => { + AnyInstruction::Pseudo(PseudoInstruction::Jump { .. }) + if target != BlockIdx::NULL => + { let target_offset = block_to_offset[target.idx()].0; if target_offset > current_offset { Instruction::JumpForward { @@ -289,7 +318,7 @@ impl CodeInfo { } } } - other => other, + other => other.expect_real(), }; let (extras, lo_arg) = info.arg.split(); diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 3595e67c32e..a0054b28877 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -15,7 +15,11 @@ use num_complex::Complex64; use rustpython_wtf8::{Wtf8, Wtf8Buf}; pub use crate::bytecode::{ - instruction::{Arg, Instruction, decode_load_super_attr_arg, encode_load_super_attr_arg}, + instruction::{ + AnyInstruction, Arg, Instruction, InstructionMetadata, PseudoInstruction, + decode_load_attr_arg, decode_load_super_attr_arg, encode_load_attr_arg, + encode_load_super_attr_arg, + }, oparg::{ BinaryOperator, BuildSliceArgCount, ComparisonOperator, ConvertValueOparg, IntrinsicFunction1, IntrinsicFunction2, Invert, Label, MakeFunctionFlags, NameIdx, OpArg, @@ -96,20 +100,6 @@ pub fn find_exception_handler(table: &[u8], offset: u32) -> Option u32 { - (name_idx << 1) | (is_method as u32) -} - -/// Decode LOAD_ATTR oparg: returns (name_idx, is_method). -#[inline] -pub const fn decode_load_attr_arg(oparg: u32) -> (u32, bool) { - let is_method = (oparg & 1) == 1; - let name_idx = oparg >> 1; - (name_idx, is_method) -} - /// CPython 3.11+ linetable location info codes #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index 07464f375cb..3ebb3666ae2 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -2,7 +2,7 @@ use core::{fmt, marker::PhantomData, mem}; use crate::{ bytecode::{ - BorrowedConstant, Constant, InstrDisplayContext, decode_load_attr_arg, + BorrowedConstant, Constant, InstrDisplayContext, oparg::{ BinaryOperator, BuildSliceArgCount, ComparisonOperator, ConvertValueOparg, IntrinsicFunction1, IntrinsicFunction2, Invert, Label, MakeFunctionFlags, NameIdx, @@ -12,10 +12,13 @@ use crate::{ marshal::MarshalError, }; -/// A Single bytecode instruction. -/// Instructions are ordered to match CPython 3.13 opcode numbers exactly. -/// HAVE_ARGUMENT = 44: opcodes 0-43 have no argument, 44+ have arguments. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +/// A Single bytecode instruction that are executed by the VM. +/// +/// Currently aligned with CPython 3.13. +/// +/// ## See also +/// - [CPython opcode IDs](https://github.com/python/cpython/blob/627894459a84be3488a1789919679c997056a03c/Include/opcode_ids.h) +#[derive(Clone, Copy, Debug)] #[repr(u8)] pub enum Instruction { // ==================== No-argument instructions (opcode < 44) ==================== @@ -245,20 +248,6 @@ pub enum Instruction { Resume { arg: Arg, } = 149, - // ===== LOAD_SUPER_* Pseudo Opcodes (136-138) ===== - // These are converted to LoadSuperAttr during bytecode finalization. - // "Zero" variants are for 0-arg super() calls (has_class=false). - // Non-"Zero" variants are for 2-arg super(cls, self) calls (has_class=true). - /// 2-arg super(cls, self).method() - has_class=true, load_method=true - LoadSuperMethod { - idx: Arg, - } = 136, // CPython uses pseudo-op 260 - LoadZeroSuperAttr { - idx: Arg, - } = 137, // CPython uses pseudo-op 261 - LoadZeroSuperMethod { - idx: Arg, - } = 138, // CPython uses pseudo-op 262 // ==================== RustPython-only instructions (119-135) ==================== // Ideally, we want to be fully aligned with CPython opcodes, but we still have some leftovers. // So we assign random IDs to these opcodes. @@ -290,15 +279,8 @@ pub enum Instruction { JumpIfNotExcMatch(Arg' b'http://google.com.'), response) + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response @make_request_and_skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") - @unittest.skip('TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response') def test_system_methods(self): """Test the presence of three consecutive system.* methods. @@ -190,7 +190,7 @@ def test_system_methods(self): b'
\nThis server does NOT support system' b'.methodSignature.'), response) - @unittest.skip('TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response') + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response def test_autolink_dotted_methods(self): """Test that selfdot values are made strong automatically in the documentation.""" @@ -200,7 +200,7 @@ def test_autolink_dotted_methods(self): self.assertIn(b"""Try self.add, too.""", response.read()) - @unittest.skip('TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response') + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response def test_annotations(self): """ Test that annotations works as expected """ self.client.request("GET", "/") @@ -214,7 +214,7 @@ def test_annotations(self): b'method_annotation(x: bytes)'), response.read()) - @unittest.skip('TODO: RUSTPYTHON; TypeError: HTMLDoc.heading() missing 2 required positional arguments: "fgcol" and "bgcol"') + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: HTMLDoc.heading() missing 2 required positional arguments: "fgcol" and "bgcol" def test_server_title_escape(self): # bpo-38243: Ensure that the server title and documentation # are escaped for HTML. diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index b9d9cf33e35..5a961711cce 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -445,7 +445,7 @@ def spam(cls): with self.assertRaises(AttributeError): del Season.SPRING.name - @unittest.skip('TODO: RUSTPYTHON') + @unittest.expectedFailure # TODO: RUSTPYTHON # RuntimeError: Error calling __set_name__ on '_proto_member' instance failed in 'BadSuper' def test_bad_new_super(self): with self.assertRaisesRegex( @@ -1923,7 +1923,7 @@ def test_wrong_inheritance_order(self): class Wrong(Enum, str): NotHere = 'error before this point' - @unittest.skip('TODO: RUSTPYTHON') + @unittest.expectedFailure # TODO: RUSTPYTHON # RuntimeError: Error calling __set_name__ on '_proto_member' instance INVALID in 'RgbColor' def test_raise_custom_error_on_creation(self): class InvalidRgbColorError(ValueError): @@ -2613,7 +2613,7 @@ class Test(Base2): self.assertEqual(Test.flash.flash, 'flashy dynamic') self.assertEqual(Test.flash.value, 1) - @unittest.skip('TODO: RUSTPYTHON') + @unittest.expectedFailure # TODO: RUSTPYTHON # RuntimeError: Error calling __set_name__ on '_proto_member' instance grene in 'Color' def test_no_duplicates(self): class UniqueEnum(Enum): @@ -3000,7 +3000,7 @@ def test_empty_globals(self): local_ls = {} exec(code, global_ns, local_ls) - @unittest.skip('TODO: RUSTPYTHON') + @unittest.expectedFailure # TODO: RUSTPYTHON # RuntimeError: Error calling __set_name__ on '_proto_member' instance one in 'FirstFailedStrEnum' def test_strenum(self): class GoodStrEnum(StrEnum): @@ -3126,7 +3126,7 @@ class ThirdFailedStrEnum(CustomStrEnum): one = '1' two = b'2', 'ascii', 9 - @unittest.skip('TODO: RUSTPYTHON') + @unittest.expectedFailure # TODO: RUSTPYTHON # RuntimeError: Error calling __set_name__ on '_proto_member' instance key_type in 'Combined' def test_missing_value_error(self): with self.assertRaisesRegex(TypeError, "_value_ not set in __new__"): @@ -3414,7 +3414,7 @@ def __new__(cls, c): self.assertEqual(FlagFromChar.a, 158456325028528675187087900672) self.assertEqual(FlagFromChar.a|1, 158456325028528675187087900673) - @unittest.skip('TODO: RUSTPYTHON') + @unittest.expectedFailure # TODO: RUSTPYTHON # RuntimeError: Error calling __set_name__ on '_proto_member' instance A in 'MyEnum' def test_init_exception(self): class Base: diff --git a/Lib/test/test_future_stmt/test_future.py b/Lib/test/test_future_stmt/test_future.py index a7d649b32a6..febd9fb095a 100644 --- a/Lib/test/test_future_stmt/test_future.py +++ b/Lib/test/test_future_stmt/test_future.py @@ -195,7 +195,7 @@ def test_syntactical_future_repl(self): out = kill_python(p) self.assertNotIn(b'SyntaxError: invalid syntax', out) - @unittest.skip('TODO: RUSTPYTHON') + @unittest.expectedFailure # TODO: RUSTPYTHON # SyntaxError: future feature spam is not defined def test_future_dotted_import(self): with self.assertRaises(ImportError): diff --git a/Lib/test/test_http_cookiejar.py b/Lib/test/test_http_cookiejar.py index f4b9dc6a282..68a693c78b3 100644 --- a/Lib/test/test_http_cookiejar.py +++ b/Lib/test/test_http_cookiejar.py @@ -125,7 +125,6 @@ def test_http2time_garbage(self): "http2time(%s) is not None\n" "http2time(test) %s" % (test, http2time(test))) - @unittest.skip("TODO: RUSTPYTHON, regressed to cubic complexity") def test_http2time_redos_regression_actually_completes(self): # LOOSE_HTTP_DATE_RE was vulnerable to malicious input which caused catastrophic backtracking (REDoS). # If we regress to cubic complexity, this test will take a very long time to succeed. diff --git a/Lib/test/test_importlib/test_threaded_import.py b/Lib/test/test_importlib/test_threaded_import.py index 3ceb86cbea3..f624e7d2c8d 100644 --- a/Lib/test/test_importlib/test_threaded_import.py +++ b/Lib/test/test_importlib/test_threaded_import.py @@ -249,14 +249,14 @@ def target(): __import__(TESTFN) del sys.modules[TESTFN] - @unittest.skip("TODO: RUSTPYTHON; hang") + @unittest.skip('TODO: RUSTPYTHON; hang; Suspected cause of crashes in Windows CI - PermissionError: [WinError 32] Permission denied: "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\test_python_0cdrhhs_\\test_python_6340æ"') def test_concurrent_futures_circular_import(self): # Regression test for bpo-43515 fn = os.path.join(os.path.dirname(__file__), 'partial', 'cfimport.py') script_helper.assert_python_ok(fn) - @unittest.skip("TODO: RUSTPYTHON - fails on Linux due to multiprocessing issues") + @unittest.skip('TODO: RUSTPYTHON; hang') def test_multiprocessing_pool_circular_import(self): # Regression test for bpo-41567 fn = os.path.join(os.path.dirname(__file__), diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 3c507c30492..741920a5864 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -1843,13 +1843,6 @@ def test_bad_readinto_type(self): def test_error_through_destructor(self): return super().test_error_through_destructor() - def test_truncate_on_read_only(self): - return super().test_truncate_on_read_only() - - @unittest.skip('TODO: RUSTPYTHON; fallible allocation') - def test_constructor(self): - return super().test_constructor() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickling_subclass(self): return super().test_pickling_subclass() @@ -2196,10 +2189,6 @@ def test_args_error(self): def test_error_through_destructor(self): return super().test_error_through_destructor() - @unittest.skip('TODO: RUSTPYTHON; fallible allocation') - def test_constructor(self): - return super().test_constructor() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickling_subclass(self): return super().test_pickling_subclass() @@ -2695,10 +2684,6 @@ def test_args_error(self): def test_error_through_destructor(self): return super().test_error_through_destructor() - @unittest.skip('TODO: RUSTPYTHON; fallible allocation') - def test_constructor(self): - return super().test_constructor() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickling_subclass(self): return super().test_pickling_subclass() @@ -4222,16 +4207,10 @@ def write(self, data): self.assertEqual([b"abcdef", b"middle", b"g"*chunk_size], buf._write_stack) - def test_basic_io(self): - return super().test_basic_io() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_constructor(self): return super().test_constructor() - def test_detach(self): - return super().test_detach() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_newlines(self): return super().test_newlines() @@ -4288,9 +4267,6 @@ def test_error_through_destructor(self): def test_repr(self): return super().test_repr() - def test_uninitialized(self): - return super().test_uninitialized() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_recursive_repr(self): return super().test_recursive_repr() diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 03dadb71f78..1c0c38ee042 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -1807,7 +1807,6 @@ def __next__(self): with self.assertRaisesRegex(RuntimeError, "tee"): next(a) - @unittest.skip("TODO: RUSTPYTHON; , hangs") @threading_helper.requires_working_threading() def test_tee_concurrent(self): start = threading.Event() diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 752054b7290..8ea77d186e4 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -1115,7 +1115,6 @@ class SMTPHandlerTest(BaseTest): # bpo-14314, bpo-19665, bpo-34092: don't wait forever TIMEOUT = support.LONG_TIMEOUT - @unittest.skip("TODO: RUSTPYTHON; hangs") def test_basic(self): sockmap = {} server = TestSMTPServer((socket_helper.HOST, 0), self.process_message, 0.001, @@ -2153,7 +2152,6 @@ def handle_request(self, request): request.end_headers() self.handled.set() - @unittest.skip('TODO: RUSTPYTHON; flaky test') def test_output(self): # The log message sent to the HTTPHandler is properly received. logger = logging.getLogger("http") diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 2161e06b2f2..142b45f6a46 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -35,7 +35,7 @@ def test_ints(self): self.helper(expected) n = n >> 1 - @unittest.skip("TODO: RUSTPYTHON; hang") + @unittest.expectedFailure # TODO: RUSTPYTHON def test_int64(self): # Simulate int marshaling with TYPE_INT64. maxint64 = (1 << 63) - 1 @@ -232,7 +232,7 @@ def check(s): self.assertRaises(ValueError, marshal.loads, s) run_tests(2**20, check) - @unittest.skip("TODO: RUSTPYTHON; segfault") + @unittest.expectedFailure # TODO: RUSTPYTHON; segfault def test_recursion_limit(self): # Create a deeply nested structure. head = last = [] diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 578bee3172a..2c5cec803d0 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -1351,7 +1351,6 @@ def handler(signum, frame): # Python handler self.assertEqual(len(sigs), N, "Some signals were lost") - @unittest.skip('TODO: RUSTPYTHON; hang') @unittest.skipIf(is_apple, "crashes due to system bug (FB13453490)") @unittest.skipUnless(hasattr(signal, "SIGUSR1"), "test needs SIGUSR1") diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 08d3dcb5792..57539a3862a 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -6842,7 +6842,6 @@ def test_errors(self): @unittest.skipUnless(hasattr(os, "sendfile"), 'os.sendfile() required for this test.') -@unittest.skip("TODO: RUSTPYTHON; os.sendfile count parameter not handled correctly") class SendfileUsingSendfileTest(SendfileUsingSendTest): """ Test the sendfile() implementation of socket.sendfile(). @@ -6850,6 +6849,13 @@ class SendfileUsingSendfileTest(SendfileUsingSendTest): def meth_from_sock(self, sock): return getattr(sock, "_sendfile_use_sendfile") + @unittest.skip("TODO: RUSTPYTHON; os.sendfile count parameter not handled correctly; flaky") + def testWithTimeout(self): + super().testWithTimeout() + + @unittest.skip("TODO: RUSTPYTHON; os.sendfile count parameter not handled correctly; flaky") + def testCount(self): + super().testCount() @unittest.skipUnless(HAVE_SOCKET_ALG, 'AF_ALG required') class LinuxKernelCryptoAPI(unittest.TestCase): @@ -6867,8 +6873,7 @@ def create_alg(self, typ, name): # bpo-31705: On kernel older than 4.5, sendto() failed with ENOKEY, # at least on ppc64le architecture - # TODO: RUSTPYTHON - AF_ALG not fully implemented - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON - AF_ALG not fully implemented @support.requires_linux_version(4, 5) def test_sha256(self): expected = bytes.fromhex("ba7816bf8f01cfea414140de5dae2223b00361a396" diff --git a/Lib/test/test_sort.py b/Lib/test/test_sort.py index be3d4a8461f..37ce489b79f 100644 --- a/Lib/test/test_sort.py +++ b/Lib/test/test_sort.py @@ -149,7 +149,7 @@ def __lt__(self, other): L = [C() for i in range(50)] self.assertRaises(ValueError, L.sort) - @unittest.skip("TODO: RUSTPYTHON; figure out how to detect sort mutation that doesn't change list length") + @unittest.expectedFailure # TODO: RUSTPYTHON; figure out how to detect sort mutation that doesn't change list length def test_undetected_mutation(self): # Python 2.4a1 did not always detect mutation memorywaster = [] diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index 9a95c489a31..a3ddd660ccb 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -545,9 +545,6 @@ def test_connection_init_good_isolation_levels(self): cx.isolation_level = level self.assertEqual(cx.isolation_level, level) - # TODO: RUSTPYTHON - # @unittest.expectedFailure - @unittest.skip("TODO: RUSTPYTHON deadlock") def test_connection_reinit(self): db = ":memory:" cx = sqlite.connect(db) @@ -587,11 +584,11 @@ def test_connection_bad_reinit(self): ((v,) for v in range(3))) -@unittest.skip("TODO: RUSTPYTHON") class UninitialisedConnectionTests(unittest.TestCase): def setUp(self): self.cx = sqlite.Connection.__new__(sqlite.Connection) + @unittest.skip('TODO: RUSTPYTHON') def test_uninit_operations(self): funcs = ( lambda: self.cx.isolation_level, diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index 9d43a33cd9e..6b766272a3f 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -565,7 +565,6 @@ def __str__(self): return self.sval self.checkraises(TypeError, ' ', 'join', [1, 2, 3]) self.checkraises(TypeError, ' ', 'join', ['1', '2', 3]) - @unittest.skip('TODO: RUSTPYTHON; oom handling') @unittest.skipIf(sys.maxsize > 2**32, 'needs too much memory on a 64-bit platform') def test_join_overflow(self): @@ -1460,7 +1459,6 @@ def __getitem__(self, key): self.assertRaises(TypeError, '{a}'.format_map, []) self.assertRaises(ZeroDivisionError, '{a}'.format_map, BadMapping()) - @unittest.skip('TODO: RUSTPYTHON; killed for chewing up RAM') def test_format_huge_precision(self): format_string = ".{}f".format(sys.maxsize + 1) with self.assertRaises(ValueError): @@ -2468,7 +2466,6 @@ def test_printable_repr(self): # This test only affects 32-bit platforms because expandtabs can only take # an int as the max value, not a 64-bit C long. If expandtabs is changed # to take a 64-bit long, this test should apply to all platforms. - @unittest.skip('TODO: RUSTPYTHON; oom handling') @unittest.skipIf(sys.maxsize > (1 << 32) or struct.calcsize('P') != 4, 'only applies to 32-bit platforms') def test_expandtabs_overflows_gracefully(self): @@ -2479,7 +2476,7 @@ def test_expandtabs_optimization(self): s = 'abc' self.assertIs(s.expandtabs(), s) - @unittest.skip('TODO: RUSTPYTHON; aborted: memory allocation of 9223372036854775759 bytes failed') + @unittest.expectedFailure # TODO: RUSTPYTHON def test_raiseMemError(self): asciifields = "nnb" compactfields = asciifields + "nP" diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index d29c6918234..92ce7c32a00 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -456,7 +456,6 @@ def test_premature_end_of_archive(self): with self.assertRaisesRegex(tarfile.ReadError, "unexpected end of data"): tar.extractfile(t).read() - @unittest.skip("TODO: RUSTPYTHON, infinite recursion") def test_length_zero_header(self): # bpo-39017 (CVE-2019-20907): reading a zero-length header should fail # with an exception diff --git a/Lib/test/test_thread.py b/Lib/test/test_thread.py index f55cf3656ea..4ae8a833b99 100644 --- a/Lib/test/test_thread.py +++ b/Lib/test/test_thread.py @@ -105,7 +105,6 @@ def test_nt_and_posix_stack_size(self): thread.stack_size(0) - @unittest.skip("TODO: RUSTPYTHON, weakref destructors") def test__count(self): # Test the _count() function. orig = thread._count() diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 910797afa8f..e635a6f9734 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -319,7 +319,7 @@ def f(mutex): # PyThreadState_SetAsyncExc() is a CPython-only gimmick, not (currently) # exposed at the Python level. This test relies on ctypes to get at it. - @unittest.skip('TODO: RUSTPYTHON; expects @cpython_only') + @cpython_only def test_PyThreadState_SetAsyncExc(self): ctypes = import_module("ctypes") @@ -424,7 +424,7 @@ def fail_new_thread(*args, **kwargs): finally: threading._start_joinable_thread = _start_joinable_thread - @unittest.skip('TODO: RUSTPYTHON; ctypes.pythonapi is not supported') + @unittest.expectedFailure # TODO: RUSTPYTHON; ctypes.pythonapi is not supported def test_finalize_running_thread(self): # Issue 1402: the PyGILState_Ensure / _Release functions may be called # very late on python exit: on deallocation of a running thread for diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 31a2a920d9e..df3a72e302a 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -622,7 +622,6 @@ def test_large_year(self): self.assertEqual(self.yearstr(12345), '12345') self.assertEqual(self.yearstr(123456789), '123456789') -@unittest.skip("TODO: RUSTPYTHON, ValueError: invalid struct_time parameter") class _TestStrftimeYear: # Issue 13305: For years < 1000, the value is not always diff --git a/Lib/test/test_timeout.py b/Lib/test/test_timeout.py index f40c7ee48b0..70a0175d771 100644 --- a/Lib/test/test_timeout.py +++ b/Lib/test/test_timeout.py @@ -71,7 +71,6 @@ def testTypeCheck(self): self.assertRaises(TypeError, self.sock.settimeout, {}) self.assertRaises(TypeError, self.sock.settimeout, 0j) - @unittest.skip("TODO: RUSTPYTHON; crash") def testRangeCheck(self): # Test range checking by settimeout() self.assertRaises(ValueError, self.sock.settimeout, -1) From a0ace054f32d7695f0e9bf5a773cda0b998b495e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 16 Jan 2026 09:12:16 +0900 Subject: [PATCH 813/819] Fix asyncio compile (#6739) --- crates/codegen/src/compile.rs | 45 ++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 0f52a519f22..0480f94b678 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -2547,18 +2547,28 @@ impl Compiler { )?; } self.compile_statements(finalbody)?; - // RERAISE 0 is emitted BEFORE pop_fblock - // This ensures RERAISE goes to cleanup block (FinallyEnd handler) - // which then properly restores prev_exc before going to outer handler + + // Pop FinallyEnd fblock BEFORE emitting RERAISE + // This ensures RERAISE routes to outer exception handler, not cleanup block + // Cleanup block is only for new exceptions raised during finally body execution + if finally_cleanup_block.is_some() { + self.pop_fblock(FBlockType::FinallyEnd); + } + + // Restore prev_exc as current exception before RERAISE + // Stack: [prev_exc, exc] -> COPY 2 -> [prev_exc, exc, prev_exc] + // POP_EXCEPT pops prev_exc and sets exc_info->exc_value = prev_exc + // Stack after POP_EXCEPT: [prev_exc, exc] + emit!(self, Instruction::Copy { index: 2_u32 }); + emit!(self, Instruction::PopExcept); + + // RERAISE 0: re-raise the original exception to outer handler emit!( self, Instruction::RaiseVarargs { kind: bytecode::RaiseKind::ReraiseFromStack } ); - if finally_cleanup_block.is_some() { - self.pop_fblock(FBlockType::FinallyEnd); - } } if let Some(cleanup) = finally_cleanup_block { @@ -2837,10 +2847,21 @@ impl Compiler { // Run finally body self.compile_statements(finalbody)?; - // RERAISE 0 is emitted BEFORE pop_fblock - // This ensures RERAISE goes to cleanup block (FinallyEnd handler) - // which then properly restores prev_exc before going to outer handler - // RERAISE 0: reraise the exception on TOS + // Pop FinallyEnd fblock BEFORE emitting RERAISE + // This ensures RERAISE routes to outer exception handler, not cleanup block + // Cleanup block is only for new exceptions raised during finally body execution + if finally_cleanup_block.is_some() { + self.pop_fblock(FBlockType::FinallyEnd); + } + + // Restore prev_exc as current exception before RERAISE + // Stack: [lasti, prev_exc, exc] -> COPY 2 -> [lasti, prev_exc, exc, prev_exc] + // POP_EXCEPT pops prev_exc and sets exc_info->exc_value = prev_exc + // Stack after POP_EXCEPT: [lasti, prev_exc, exc] + emit!(self, Instruction::Copy { index: 2_u32 }); + emit!(self, Instruction::PopExcept); + + // RERAISE 0: re-raise the original exception to outer handler // Stack: [lasti, prev_exc, exc] - exception is on top emit!( self, @@ -2848,10 +2869,6 @@ impl Compiler { kind: bytecode::RaiseKind::ReraiseFromStack, } ); - - if finally_cleanup_block.is_some() { - self.pop_fblock(FBlockType::FinallyEnd); - } } // finally cleanup block From 746e71af8796f1ec897978e7e8439e518c82cde5 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:18:31 +0900 Subject: [PATCH 814/819] reject SemLock reduce (#6738) --- Lib/test/test_concurrent_futures/test_process_pool.py | 8 -------- crates/stdlib/src/multiprocessing.rs | 7 +++++++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_concurrent_futures/test_process_pool.py b/Lib/test/test_concurrent_futures/test_process_pool.py index 9d3c771eaeb..5d4e9677f5c 100644 --- a/Lib/test/test_concurrent_futures/test_process_pool.py +++ b/Lib/test/test_concurrent_futures/test_process_pool.py @@ -228,13 +228,5 @@ def mock_start_new_thread(func, *args, **kwargs): def setUpModule(): setup_module() -class ProcessPoolSpawnProcessPoolExecutorTest(ProcessPoolSpawnProcessPoolExecutorTest): # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform == 'linux', "TODO: RUSTPYTHON flaky") - def test_saturation(self): super().test_saturation() # TODO: RUSTPYTHON - -class ProcessPoolForkProcessPoolExecutorTest(ProcessPoolForkProcessPoolExecutorTest): # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform == 'linux', "TODO: RUSTPYTHON flaky") - def test_ressources_gced_in_workers(self): super().test_ressources_gced_in_workers() # TODO: RUSTPYTHON - if __name__ == "__main__": unittest.main() diff --git a/crates/stdlib/src/multiprocessing.rs b/crates/stdlib/src/multiprocessing.rs index 21b7bfa85c7..bda13e6191a 100644 --- a/crates/stdlib/src/multiprocessing.rs +++ b/crates/stdlib/src/multiprocessing.rs @@ -547,6 +547,13 @@ mod _multiprocessing { self.last_tid.store(0, Ordering::Release); } + /// SemLock objects cannot be pickled directly. + /// Use multiprocessing.synchronize.SemLock wrapper which handles pickling. + #[pymethod] + fn __reduce__(&self, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("cannot pickle 'SemLock' object".to_owned())) + } + /// Num of `acquire()`s minus num of `release()`s for this process. // _multiprocessing_SemLock__count_impl #[pymethod] From ef871d227e3b61421fc2b5d731ced70b77cc23be Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Fri, 16 Jan 2026 21:38:15 +0900 Subject: [PATCH 815/819] Update json module to 3.13.11 (#6743) --- Lib/json/__init__.py | 52 +++++++++++-------- Lib/json/decoder.py | 8 +-- Lib/json/encoder.py | 20 +++---- .../test_json/test_encode_basestring_ascii.py | 3 +- Lib/test/test_json/test_scanstring.py | 3 +- Lib/test/test_json/test_unicode.py | 52 ++++++++++++++++++- 6 files changed, 98 insertions(+), 40 deletions(-) diff --git a/Lib/json/__init__.py b/Lib/json/__init__.py index ed2c74771ea..c7a6dcdf77e 100644 --- a/Lib/json/__init__.py +++ b/Lib/json/__init__.py @@ -128,8 +128,9 @@ def dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, instead of raising a ``TypeError``. If ``ensure_ascii`` is false, then the strings written to ``fp`` can - contain non-ASCII characters if they appear in strings contained in - ``obj``. Otherwise, all such characters are escaped in JSON strings. + contain non-ASCII and non-printable characters if they appear in strings + contained in ``obj``. Otherwise, all such characters are escaped in JSON + strings. If ``check_circular`` is false, then the circular reference check for container types will be skipped and a circular reference will @@ -145,10 +146,11 @@ def dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, level of 0 will only insert newlines. ``None`` is the most compact representation. - If specified, ``separators`` should be an ``(item_separator, key_separator)`` - tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and - ``(',', ': ')`` otherwise. To get the most compact JSON representation, - you should specify ``(',', ':')`` to eliminate whitespace. + If specified, ``separators`` should be an ``(item_separator, + key_separator)`` tuple. The default is ``(', ', ': ')`` if *indent* is + ``None`` and ``(',', ': ')`` otherwise. To get the most compact JSON + representation, you should specify ``(',', ':')`` to eliminate + whitespace. ``default(obj)`` is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. @@ -189,9 +191,10 @@ def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, (``str``, ``int``, ``float``, ``bool``, ``None``) will be skipped instead of raising a ``TypeError``. - If ``ensure_ascii`` is false, then the return value can contain non-ASCII - characters if they appear in strings contained in ``obj``. Otherwise, all - such characters are escaped in JSON strings. + If ``ensure_ascii`` is false, then the return value can contain + non-ASCII and non-printable characters if they appear in strings + contained in ``obj``. Otherwise, all such characters are escaped in + JSON strings. If ``check_circular`` is false, then the circular reference check for container types will be skipped and a circular reference will @@ -207,10 +210,11 @@ def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, level of 0 will only insert newlines. ``None`` is the most compact representation. - If specified, ``separators`` should be an ``(item_separator, key_separator)`` - tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and - ``(',', ': ')`` otherwise. To get the most compact JSON representation, - you should specify ``(',', ':')`` to eliminate whitespace. + If specified, ``separators`` should be an ``(item_separator, + key_separator)`` tuple. The default is ``(', ', ': ')`` if *indent* is + ``None`` and ``(',', ': ')`` otherwise. To get the most compact JSON + representation, you should specify ``(',', ':')`` to eliminate + whitespace. ``default(obj)`` is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. @@ -281,11 +285,12 @@ def load(fp, *, cls=None, object_hook=None, parse_float=None, ``object_hook`` will be used instead of the ``dict``. This feature can be used to implement custom decoders (e.g. JSON-RPC class hinting). - ``object_pairs_hook`` is an optional function that will be called with the - result of any object literal decoded with an ordered list of pairs. The - return value of ``object_pairs_hook`` will be used instead of the ``dict``. - This feature can be used to implement custom decoders. If ``object_hook`` - is also defined, the ``object_pairs_hook`` takes priority. + ``object_pairs_hook`` is an optional function that will be called with + the result of any object literal decoded with an ordered list of pairs. + The return value of ``object_pairs_hook`` will be used instead of the + ``dict``. This feature can be used to implement custom decoders. If + ``object_hook`` is also defined, the ``object_pairs_hook`` takes + priority. To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` kwarg; otherwise ``JSONDecoder`` is used. @@ -306,11 +311,12 @@ def loads(s, *, cls=None, object_hook=None, parse_float=None, ``object_hook`` will be used instead of the ``dict``. This feature can be used to implement custom decoders (e.g. JSON-RPC class hinting). - ``object_pairs_hook`` is an optional function that will be called with the - result of any object literal decoded with an ordered list of pairs. The - return value of ``object_pairs_hook`` will be used instead of the ``dict``. - This feature can be used to implement custom decoders. If ``object_hook`` - is also defined, the ``object_pairs_hook`` takes priority. + ``object_pairs_hook`` is an optional function that will be called with + the result of any object literal decoded with an ordered list of pairs. + The return value of ``object_pairs_hook`` will be used instead of the + ``dict``. This feature can be used to implement custom decoders. If + ``object_hook`` is also defined, the ``object_pairs_hook`` takes + priority. ``parse_float``, if specified, will be called with the string of every JSON float to be decoded. By default this is equivalent to diff --git a/Lib/json/decoder.py b/Lib/json/decoder.py index 9e6ca981d76..db87724a897 100644 --- a/Lib/json/decoder.py +++ b/Lib/json/decoder.py @@ -311,10 +311,10 @@ def __init__(self, *, object_hook=None, parse_float=None, place of the given ``dict``. This can be used to provide custom deserializations (e.g. to support JSON-RPC class hinting). - ``object_pairs_hook``, if specified will be called with the result of - every JSON object decoded with an ordered list of pairs. The return - value of ``object_pairs_hook`` will be used instead of the ``dict``. - This feature can be used to implement custom decoders. + ``object_pairs_hook``, if specified will be called with the result + of every JSON object decoded with an ordered list of pairs. The + return value of ``object_pairs_hook`` will be used instead of the + ``dict``. This feature can be used to implement custom decoders. If ``object_hook`` is also defined, the ``object_pairs_hook`` takes priority. diff --git a/Lib/json/encoder.py b/Lib/json/encoder.py index 08ef39d1592..0671500d106 100644 --- a/Lib/json/encoder.py +++ b/Lib/json/encoder.py @@ -111,9 +111,10 @@ def __init__(self, *, skipkeys=False, ensure_ascii=True, encoding of keys that are not str, int, float, bool or None. If skipkeys is True, such items are simply skipped. - If ensure_ascii is true, the output is guaranteed to be str - objects with all incoming non-ASCII characters escaped. If - ensure_ascii is false, the output can contain non-ASCII characters. + If ensure_ascii is true, the output is guaranteed to be str objects + with all incoming non-ASCII and non-printable characters escaped. + If ensure_ascii is false, the output can contain non-ASCII and + non-printable characters. If check_circular is true, then lists, dicts, and custom encoded objects will be checked for circular references during encoding to @@ -134,14 +135,15 @@ def __init__(self, *, skipkeys=False, ensure_ascii=True, indent level. An indent level of 0 will only insert newlines. None is the most compact representation. - If specified, separators should be an (item_separator, key_separator) - tuple. The default is (', ', ': ') if *indent* is ``None`` and - (',', ': ') otherwise. To get the most compact JSON representation, - you should specify (',', ':') to eliminate whitespace. + If specified, separators should be an (item_separator, + key_separator) tuple. The default is (', ', ': ') if *indent* is + ``None`` and (',', ': ') otherwise. To get the most compact JSON + representation, you should specify (',', ':') to eliminate + whitespace. If specified, default is a function that gets called for objects - that can't otherwise be serialized. It should return a JSON encodable - version of the object or raise a ``TypeError``. + that can't otherwise be serialized. It should return a JSON + encodable version of the object or raise a ``TypeError``. """ diff --git a/Lib/test/test_json/test_encode_basestring_ascii.py b/Lib/test/test_json/test_encode_basestring_ascii.py index 6a39b72a09d..c90d3e968e5 100644 --- a/Lib/test/test_json/test_encode_basestring_ascii.py +++ b/Lib/test/test_json/test_encode_basestring_ascii.py @@ -8,13 +8,12 @@ ('\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'), ('controls', '"controls"'), ('\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'), + ('\x00\x1f\x7f', '"\\u0000\\u001f\\u007f"'), ('{"object with 1 member":["array with 1 element"]}', '"{\\"object with 1 member\\":[\\"array with 1 element\\"]}"'), (' s p a c e d ', '" s p a c e d "'), ('\U0001d120', '"\\ud834\\udd20"'), ('\u03b1\u03a9', '"\\u03b1\\u03a9"'), ("`1~!@#$%^&*()_+-={':[,]}|;.?", '"`1~!@#$%^&*()_+-={\':[,]}|;.?"'), - ('\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'), - ('\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'), ] class TestEncodeBasestringAscii: diff --git a/Lib/test/test_json/test_scanstring.py b/Lib/test/test_json/test_scanstring.py index a5c46bb64b4..d6922c3b1b9 100644 --- a/Lib/test/test_json/test_scanstring.py +++ b/Lib/test/test_json/test_scanstring.py @@ -3,6 +3,7 @@ import unittest # XXX: RUSTPYTHON; importing to be able to skip tests + class TestScanstring: def test_scanstring(self): scanstring = self.json.decoder.scanstring @@ -147,7 +148,7 @@ def test_bad_escapes(self): @unittest.expectedFailure def test_overflow(self): with self.assertRaises(OverflowError): - self.json.decoder.scanstring(b"xxx", sys.maxsize+1) + self.json.decoder.scanstring("xxx", sys.maxsize+1) class TestPyScanstring(TestScanstring, PyTest): pass diff --git a/Lib/test/test_json/test_unicode.py b/Lib/test/test_json/test_unicode.py index 4bdb607e7da..be0ac8823d5 100644 --- a/Lib/test/test_json/test_unicode.py +++ b/Lib/test/test_json/test_unicode.py @@ -34,6 +34,29 @@ def test_encoding7(self): j = self.dumps(u + "\n", ensure_ascii=False) self.assertEqual(j, f'"{u}\\n"') + def test_ascii_non_printable_encode(self): + u = '\b\t\n\f\r\x00\x1f\x7f' + self.assertEqual(self.dumps(u), + '"\\b\\t\\n\\f\\r\\u0000\\u001f\\u007f"') + self.assertEqual(self.dumps(u, ensure_ascii=False), + '"\\b\\t\\n\\f\\r\\u0000\\u001f\x7f"') + + def test_ascii_non_printable_decode(self): + self.assertEqual(self.loads('"\\b\\t\\n\\f\\r"'), + '\b\t\n\f\r') + s = ''.join(map(chr, range(32))) + for c in s: + self.assertRaises(self.JSONDecodeError, self.loads, f'"{c}"') + self.assertEqual(self.loads(f'"{s}"', strict=False), s) + self.assertEqual(self.loads('"\x7f"'), '\x7f') + + def test_escaped_decode(self): + self.assertEqual(self.loads('"\\b\\t\\n\\f\\r"'), '\b\t\n\f\r') + self.assertEqual(self.loads('"\\"\\\\\\/"'), '"\\/') + for c in set(map(chr, range(0x100))) - set('"\\/bfnrt'): + self.assertRaises(self.JSONDecodeError, self.loads, f'"\\{c}"') + self.assertRaises(self.JSONDecodeError, self.loads, f'"\\{c}"', strict=False) + def test_big_unicode_encode(self): u = '\U0001d120' self.assertEqual(self.dumps(u), '"\\ud834\\udd20"') @@ -50,6 +73,18 @@ def test_unicode_decode(self): s = f'"\\u{i:04x}"' self.assertEqual(self.loads(s), u) + def test_single_surrogate_encode(self): + self.assertEqual(self.dumps('\uD83D'), '"\\ud83d"') + self.assertEqual(self.dumps('\uD83D', ensure_ascii=False), '"\ud83d"') + self.assertEqual(self.dumps('\uDC0D'), '"\\udc0d"') + self.assertEqual(self.dumps('\uDC0D', ensure_ascii=False), '"\udc0d"') + + def test_single_surrogate_decode(self): + self.assertEqual(self.loads('"\uD83D"'), '\ud83d') + self.assertEqual(self.loads('"\\uD83D"'), '\ud83d') + self.assertEqual(self.loads('"\udc0d"'), '\udc0d') + self.assertEqual(self.loads('"\\udc0d"'), '\udc0d') + def test_unicode_preservation(self): self.assertEqual(type(self.loads('""')), str) self.assertEqual(type(self.loads('"a"')), str) @@ -104,4 +139,19 @@ def test_object_pairs_hook_with_unicode(self): class TestPyUnicode(TestUnicode, PyTest): pass -class TestCUnicode(TestUnicode, CTest): pass + +class TestCUnicode(TestUnicode, CTest): + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_ascii_non_printable_encode(self): + return super().test_ascii_non_printable_encode() + + # TODO: RUSTPYTHON + @unittest.skip("TODO: RUSTPYTHON; panics with 'str has surrogates'") + def test_single_surrogate_decode(self): + return super().test_single_surrogate_decode() + + # TODO: RUSTPYTHON + @unittest.skip("TODO: RUSTPYTHON; panics with 'str has surrogates'") + def test_single_surrogate_encode(self): + return super().test_single_surrogate_encode() From 4cb3b9d8f02eb301e761ace2f7300d976b6cb529 Mon Sep 17 00:00:00 2001 From: Terry Tianlin Luan <44815845+terryluan12@users.noreply.github.com> Date: Fri, 16 Jan 2026 09:03:49 -0500 Subject: [PATCH 816/819] Updated the urllib + http libraries + test libraries (#6672) * Updated urllib + urllib tests * Updated http + test * Annotated failing tests * Fixed issues in httpservers, robotparser and urllib2net * Fixed windows only success * Annotated flaky test in test_logging --- Lib/http/__init__.py | 74 +++- Lib/http/client.py | 221 ++++++----- Lib/http/cookiejar.py | 64 ++-- Lib/http/server.py | 132 +++++-- Lib/test/test_http_cookiejar.py | 336 ++++++++++------ Lib/test/test_httplib.py | 615 ++++++++++++++++++++++++++---- Lib/test/test_httpservers.py | 446 +++++++++++++++++----- Lib/test/test_logging.py | 1 + Lib/test/test_robotparser.py | 4 + Lib/test/test_ssl.py | 3 +- Lib/test/test_urllib.py | 2 - Lib/test/test_urllib2.py | 185 ++++++--- Lib/test/test_urllib2_localnet.py | 92 ++--- Lib/test/test_urllib2net.py | 1 - Lib/test/test_urllib_response.py | 7 + Lib/test/test_wsgiref.py | 1 - Lib/urllib/error.py | 11 +- Lib/urllib/parse.py | 236 +++++++----- Lib/urllib/request.py | 250 ++++++------ Lib/urllib/robotparser.py | 35 +- 20 files changed, 1931 insertions(+), 785 deletions(-) diff --git a/Lib/http/__init__.py b/Lib/http/__init__.py index bf8d7d68868..17a47b180e5 100644 --- a/Lib/http/__init__.py +++ b/Lib/http/__init__.py @@ -1,14 +1,15 @@ -from enum import IntEnum +from enum import StrEnum, IntEnum, _simple_enum -__all__ = ['HTTPStatus'] +__all__ = ['HTTPStatus', 'HTTPMethod'] -class HTTPStatus(IntEnum): +@_simple_enum(IntEnum) +class HTTPStatus: """HTTP status codes and reason phrases Status codes from the following RFCs are all observed: - * RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616 + * RFC 9110: HTTP Semantics, obsoletes 7231, which obsoleted 2616 * RFC 6585: Additional HTTP Status Codes * RFC 3229: Delta encoding in HTTP * RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518 @@ -25,11 +26,30 @@ class HTTPStatus(IntEnum): def __new__(cls, value, phrase, description=''): obj = int.__new__(cls, value) obj._value_ = value - obj.phrase = phrase obj.description = description return obj + @property + def is_informational(self): + return 100 <= self <= 199 + + @property + def is_success(self): + return 200 <= self <= 299 + + @property + def is_redirection(self): + return 300 <= self <= 399 + + @property + def is_client_error(self): + return 400 <= self <= 499 + + @property + def is_server_error(self): + return 500 <= self <= 599 + # informational CONTINUE = 100, 'Continue', 'Request received, please continue' SWITCHING_PROTOCOLS = (101, 'Switching Protocols', @@ -94,22 +114,25 @@ def __new__(cls, value, phrase, description=''): 'Client must specify Content-Length') PRECONDITION_FAILED = (412, 'Precondition Failed', 'Precondition in headers is false') - REQUEST_ENTITY_TOO_LARGE = (413, 'Request Entity Too Large', - 'Entity is too large') - REQUEST_URI_TOO_LONG = (414, 'Request-URI Too Long', + CONTENT_TOO_LARGE = (413, 'Content Too Large', + 'Content is too large') + REQUEST_ENTITY_TOO_LARGE = CONTENT_TOO_LARGE + URI_TOO_LONG = (414, 'URI Too Long', 'URI is too long') + REQUEST_URI_TOO_LONG = URI_TOO_LONG UNSUPPORTED_MEDIA_TYPE = (415, 'Unsupported Media Type', 'Entity body in unsupported format') - REQUESTED_RANGE_NOT_SATISFIABLE = (416, - 'Requested Range Not Satisfiable', + RANGE_NOT_SATISFIABLE = (416, 'Range Not Satisfiable', 'Cannot satisfy request range') + REQUESTED_RANGE_NOT_SATISFIABLE = RANGE_NOT_SATISFIABLE EXPECTATION_FAILED = (417, 'Expectation Failed', 'Expect condition could not be satisfied') IM_A_TEAPOT = (418, 'I\'m a Teapot', 'Server refuses to brew coffee because it is a teapot.') MISDIRECTED_REQUEST = (421, 'Misdirected Request', 'Server is not able to produce a response') - UNPROCESSABLE_ENTITY = 422, 'Unprocessable Entity' + UNPROCESSABLE_CONTENT = 422, 'Unprocessable Content' + UNPROCESSABLE_ENTITY = UNPROCESSABLE_CONTENT LOCKED = 423, 'Locked' FAILED_DEPENDENCY = 424, 'Failed Dependency' TOO_EARLY = 425, 'Too Early' @@ -148,3 +171,32 @@ def __new__(cls, value, phrase, description=''): NETWORK_AUTHENTICATION_REQUIRED = (511, 'Network Authentication Required', 'The client needs to authenticate to gain network access') + + +@_simple_enum(StrEnum) +class HTTPMethod: + """HTTP methods and descriptions + + Methods from the following RFCs are all observed: + + * RFC 9110: HTTP Semantics, obsoletes 7231, which obsoleted 2616 + * RFC 5789: PATCH Method for HTTP + """ + def __new__(cls, value, description): + obj = str.__new__(cls, value) + obj._value_ = value + obj.description = description + return obj + + def __repr__(self): + return "<%s.%s>" % (self.__class__.__name__, self._name_) + + CONNECT = 'CONNECT', 'Establish a connection to the server.' + DELETE = 'DELETE', 'Remove the target.' + GET = 'GET', 'Retrieve the target.' + HEAD = 'HEAD', 'Same as GET, but only retrieve the status line and header section.' + OPTIONS = 'OPTIONS', 'Describe the communication options for the target.' + PATCH = 'PATCH', 'Apply partial modifications to a target.' + POST = 'POST', 'Perform target-specific processing with the request payload.' + PUT = 'PUT', 'Replace the target with the request payload.' + TRACE = 'TRACE', 'Perform a message loop-back test along the path to the target.' diff --git a/Lib/http/client.py b/Lib/http/client.py index a6ab135b2c3..dd5f4136e9e 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -111,6 +111,11 @@ _MAXLINE = 65536 _MAXHEADERS = 100 +# Data larger than this will be read in chunks, to prevent extreme +# overallocation. +_MIN_READ_BUF_SIZE = 1 << 20 + + # Header name/value ABNF (http://tools.ietf.org/html/rfc7230#section-3.2) # # VCHAR = %x21-7E @@ -172,6 +177,13 @@ def _encode(data, name='data'): "if you want to send it encoded in UTF-8." % (name.title(), data[err.start:err.end], name)) from None +def _strip_ipv6_iface(enc_name: bytes) -> bytes: + """Remove interface scope from IPv6 address.""" + enc_name, percent, _ = enc_name.partition(b"%") + if percent: + assert enc_name.startswith(b'['), enc_name + enc_name += b']' + return enc_name class HTTPMessage(email.message.Message): # XXX The only usage of this method is in @@ -221,8 +233,9 @@ def _read_headers(fp): break return headers -def parse_headers(fp, _class=HTTPMessage): - """Parses only RFC2822 headers from a file pointer. +def _parse_header_lines(header_lines, _class=HTTPMessage): + """ + Parses only RFC 5322 headers from header lines. email Parser wants to see strings rather than bytes. But a TextIOWrapper around self.rfile would buffer too many bytes @@ -231,10 +244,15 @@ def parse_headers(fp, _class=HTTPMessage): to parse. """ - headers = _read_headers(fp) - hstring = b''.join(headers).decode('iso-8859-1') + hstring = b''.join(header_lines).decode('iso-8859-1') return email.parser.Parser(_class=_class).parsestr(hstring) +def parse_headers(fp, _class=HTTPMessage): + """Parses only RFC 5322 headers from a file pointer.""" + + headers = _read_headers(fp) + return _parse_header_lines(headers, _class) + class HTTPResponse(io.BufferedIOBase): @@ -448,6 +466,7 @@ def isclosed(self): return self.fp is None def read(self, amt=None): + """Read and return the response body, or up to the next amt bytes.""" if self.fp is None: return b"" @@ -458,7 +477,7 @@ def read(self, amt=None): if self.chunked: return self._read_chunked(amt) - if amt is not None: + if amt is not None and amt >= 0: if self.length is not None and amt > self.length: # clip the read to the "end of response" amt = self.length @@ -576,13 +595,11 @@ def _get_chunk_left(self): def _read_chunked(self, amt=None): assert self.chunked != _UNKNOWN + if amt is not None and amt < 0: + amt = None value = [] try: - while True: - chunk_left = self._get_chunk_left() - if chunk_left is None: - break - + while (chunk_left := self._get_chunk_left()) is not None: if amt is not None and amt <= chunk_left: value.append(self._safe_read(amt)) self.chunk_left = chunk_left - amt @@ -593,8 +610,8 @@ def _read_chunked(self, amt=None): amt -= chunk_left self.chunk_left = 0 return b''.join(value) - except IncompleteRead: - raise IncompleteRead(b''.join(value)) + except IncompleteRead as exc: + raise IncompleteRead(b''.join(value)) from exc def _readinto_chunked(self, b): assert self.chunked != _UNKNOWN @@ -627,10 +644,25 @@ def _safe_read(self, amt): reading. If the bytes are truly not available (due to EOF), then the IncompleteRead exception can be used to detect the problem. """ - data = self.fp.read(amt) - if len(data) < amt: - raise IncompleteRead(data, amt-len(data)) - return data + cursize = min(amt, _MIN_READ_BUF_SIZE) + data = self.fp.read(cursize) + if len(data) >= amt: + return data + if len(data) < cursize: + raise IncompleteRead(data, amt - len(data)) + + data = io.BytesIO(data) + data.seek(0, 2) + while True: + # This is a geometric increase in read size (never more than + # doubling out the current length of data per loop iteration). + delta = min(cursize, amt - cursize) + data.write(self.fp.read(delta)) + if data.tell() >= amt: + return data.getvalue() + cursize += delta + if data.tell() < cursize: + raise IncompleteRead(data.getvalue(), amt - data.tell()) def _safe_readinto(self, b): """Same as _safe_read, but for reading into a buffer.""" @@ -655,6 +687,8 @@ def read1(self, n=-1): self._close_conn() elif self.length is not None: self.length -= len(result) + if not self.length: + self._close_conn() return result def peek(self, n=-1): @@ -679,6 +713,8 @@ def readline(self, limit=-1): self._close_conn() elif self.length is not None: self.length -= len(result) + if not self.length: + self._close_conn() return result def _read1_chunked(self, n): @@ -786,6 +822,20 @@ def getcode(self): ''' return self.status + +def _create_https_context(http_version): + # Function also used by urllib.request to be able to set the check_hostname + # attribute on a context object. + context = ssl._create_default_https_context() + # send ALPN extension to indicate HTTP/1.1 protocol + if http_version == 11: + context.set_alpn_protocols(['http/1.1']) + # enable PHA for TLS 1.3 connections if available + if context.post_handshake_auth is not None: + context.post_handshake_auth = True + return context + + class HTTPConnection: _http_vsn = 11 @@ -847,6 +897,7 @@ def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, self._tunnel_host = None self._tunnel_port = None self._tunnel_headers = {} + self._raw_proxy_headers = None (self.host, self.port) = self._get_hostport(host, port) @@ -859,9 +910,9 @@ def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, def set_tunnel(self, host, port=None, headers=None): """Set up host and port for HTTP CONNECT tunnelling. - In a connection that uses HTTP CONNECT tunneling, the host passed to the - constructor is used as a proxy server that relays all communication to - the endpoint passed to `set_tunnel`. This done by sending an HTTP + In a connection that uses HTTP CONNECT tunnelling, the host passed to + the constructor is used as a proxy server that relays all communication + to the endpoint passed to `set_tunnel`. This done by sending an HTTP CONNECT request to the proxy server when the connection is established. This method must be called before the HTTP connection has been @@ -869,6 +920,13 @@ def set_tunnel(self, host, port=None, headers=None): The headers argument should be a mapping of extra HTTP headers to send with the CONNECT request. + + As HTTP/1.1 is used for HTTP CONNECT tunnelling request, as per the RFC + (https://tools.ietf.org/html/rfc7231#section-4.3.6), a HTTP Host: + header must be provided, matching the authority-form of the request + target provided as the destination for the CONNECT request. If a + HTTP Host: header is not provided via the headers argument, one + is generated and transmitted automatically. """ if self.sock: @@ -876,10 +934,15 @@ def set_tunnel(self, host, port=None, headers=None): self._tunnel_host, self._tunnel_port = self._get_hostport(host, port) if headers: - self._tunnel_headers = headers + self._tunnel_headers = headers.copy() else: self._tunnel_headers.clear() + if not any(header.lower() == "host" for header in self._tunnel_headers): + encoded_host = self._tunnel_host.encode("idna").decode("ascii") + self._tunnel_headers["Host"] = "%s:%d" % ( + encoded_host, self._tunnel_port) + def _get_hostport(self, host, port): if port is None: i = host.rfind(':') @@ -895,17 +958,24 @@ def _get_hostport(self, host, port): host = host[:i] else: port = self.default_port - if host and host[0] == '[' and host[-1] == ']': - host = host[1:-1] + if host and host[0] == '[' and host[-1] == ']': + host = host[1:-1] return (host, port) def set_debuglevel(self, level): self.debuglevel = level + def _wrap_ipv6(self, ip): + if b':' in ip and ip[0] != b'['[0]: + return b"[" + ip + b"]" + return ip + def _tunnel(self): - connect = b"CONNECT %s:%d HTTP/1.0\r\n" % ( - self._tunnel_host.encode("ascii"), self._tunnel_port) + connect = b"CONNECT %s:%d %s\r\n" % ( + self._wrap_ipv6(self._tunnel_host.encode("idna")), + self._tunnel_port, + self._http_vsn_str.encode("ascii")) headers = [connect] for header, value in self._tunnel_headers.items(): headers.append(f"{header}: {value}\r\n".encode("latin-1")) @@ -917,23 +987,35 @@ def _tunnel(self): del headers response = self.response_class(self.sock, method=self._method) - (version, code, message) = response._read_status() + try: + (version, code, message) = response._read_status() - if code != http.HTTPStatus.OK: - self.close() - raise OSError(f"Tunnel connection failed: {code} {message.strip()}") - while True: - line = response.fp.readline(_MAXLINE + 1) - if len(line) > _MAXLINE: - raise LineTooLong("header line") - if not line: - # for sites which EOF without sending a trailer - break - if line in (b'\r\n', b'\n', b''): - break + self._raw_proxy_headers = _read_headers(response.fp) if self.debuglevel > 0: - print('header:', line.decode()) + for header in self._raw_proxy_headers: + print('header:', header.decode()) + + if code != http.HTTPStatus.OK: + self.close() + raise OSError(f"Tunnel connection failed: {code} {message.strip()}") + + finally: + response.close() + + def get_proxy_response_headers(self): + """ + Returns a dictionary with the headers of the response + received from the proxy server to the CONNECT request + sent to set the tunnel. + + If the CONNECT request was not sent, the method returns None. + """ + return ( + _parse_header_lines(self._raw_proxy_headers) + if self._raw_proxy_headers is not None + else None + ) def connect(self): """Connect to the host and port specified in __init__.""" @@ -942,7 +1024,7 @@ def connect(self): (self.host,self.port), self.timeout, self.source_address) # Might fail in OSs that don't implement TCP_NODELAY try: - self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) except OSError as e: if e.errno != errno.ENOPROTOOPT: raise @@ -980,14 +1062,11 @@ def send(self, data): print("send:", repr(data)) if hasattr(data, "read") : if self.debuglevel > 0: - print("sendIng a read()able") + print("sending a readable") encode = self._is_textIO(data) if encode and self.debuglevel > 0: print("encoding file using iso-8859-1") - while 1: - datablock = data.read(self.blocksize) - if not datablock: - break + while datablock := data.read(self.blocksize): if encode: datablock = datablock.encode("iso-8859-1") sys.audit("http.client.send", self, datablock) @@ -1013,14 +1092,11 @@ def _output(self, s): def _read_readable(self, readable): if self.debuglevel > 0: - print("sendIng a read()able") + print("reading a readable") encode = self._is_textIO(readable) if encode and self.debuglevel > 0: print("encoding file using iso-8859-1") - while True: - datablock = readable.read(self.blocksize) - if not datablock: - break + while datablock := readable.read(self.blocksize): if encode: datablock = datablock.encode("iso-8859-1") yield datablock @@ -1157,7 +1233,7 @@ def putrequest(self, method, url, skip_host=False, netloc_enc = netloc.encode("ascii") except UnicodeEncodeError: netloc_enc = netloc.encode("idna") - self.putheader('Host', netloc_enc) + self.putheader('Host', _strip_ipv6_iface(netloc_enc)) else: if self._tunnel_host: host = self._tunnel_host @@ -1173,9 +1249,9 @@ def putrequest(self, method, url, skip_host=False, # As per RFC 273, IPv6 address should be wrapped with [] # when used as Host header - - if host.find(':') >= 0: - host_enc = b'[' + host_enc + b']' + host_enc = self._wrap_ipv6(host_enc) + if ":" in host: + host_enc = _strip_ipv6_iface(host_enc) if port == self.default_port: self.putheader('Host', host_enc) @@ -1400,46 +1476,15 @@ class HTTPSConnection(HTTPConnection): default_port = HTTPS_PORT - # XXX Should key_file and cert_file be deprecated in favour of context? - - def __init__(self, host, port=None, key_file=None, cert_file=None, - timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - source_address=None, *, context=None, - check_hostname=None, blocksize=8192): + def __init__(self, host, port=None, + *, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, context=None, blocksize=8192): super(HTTPSConnection, self).__init__(host, port, timeout, source_address, blocksize=blocksize) - if (key_file is not None or cert_file is not None or - check_hostname is not None): - import warnings - warnings.warn("key_file, cert_file and check_hostname are " - "deprecated, use a custom context instead.", - DeprecationWarning, 2) - self.key_file = key_file - self.cert_file = cert_file if context is None: - context = ssl._create_default_https_context() - # send ALPN extension to indicate HTTP/1.1 protocol - if self._http_vsn == 11: - context.set_alpn_protocols(['http/1.1']) - # enable PHA for TLS 1.3 connections if available - if context.post_handshake_auth is not None: - context.post_handshake_auth = True - will_verify = context.verify_mode != ssl.CERT_NONE - if check_hostname is None: - check_hostname = context.check_hostname - if check_hostname and not will_verify: - raise ValueError("check_hostname needs a SSL context with " - "either CERT_OPTIONAL or CERT_REQUIRED") - if key_file or cert_file: - context.load_cert_chain(cert_file, key_file) - # cert and key file means the user wants to authenticate. - # enable TLS 1.3 PHA implicitly even for custom contexts. - if context.post_handshake_auth is not None: - context.post_handshake_auth = True + context = _create_https_context(self._http_vsn) self._context = context - if check_hostname is not None: - self._context.check_hostname = check_hostname def connect(self): "Connect to a host on a given (SSL) port." diff --git a/Lib/http/cookiejar.py b/Lib/http/cookiejar.py index 685f6a0b976..9a2f0fb851c 100644 --- a/Lib/http/cookiejar.py +++ b/Lib/http/cookiejar.py @@ -34,10 +34,7 @@ import re import time import urllib.parse, urllib.request -try: - import threading as _threading -except ImportError: - import dummy_threading as _threading +import threading as _threading import http.client # only for the default HTTP port from calendar import timegm @@ -92,8 +89,7 @@ def _timegm(tt): DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] -MONTHS_LOWER = [] -for month in MONTHS: MONTHS_LOWER.append(month.lower()) +MONTHS_LOWER = [month.lower() for month in MONTHS] def time2isoz(t=None): """Return a string representing time in seconds since epoch, t. @@ -108,9 +104,9 @@ def time2isoz(t=None): """ if t is None: - dt = datetime.datetime.utcnow() + dt = datetime.datetime.now(tz=datetime.UTC) else: - dt = datetime.datetime.utcfromtimestamp(t) + dt = datetime.datetime.fromtimestamp(t, tz=datetime.UTC) return "%04d-%02d-%02d %02d:%02d:%02dZ" % ( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) @@ -126,9 +122,9 @@ def time2netscape(t=None): """ if t is None: - dt = datetime.datetime.utcnow() + dt = datetime.datetime.now(tz=datetime.UTC) else: - dt = datetime.datetime.utcfromtimestamp(t) + dt = datetime.datetime.fromtimestamp(t, tz=datetime.UTC) return "%s, %02d-%s-%04d %02d:%02d:%02d GMT" % ( DAYS[dt.weekday()], dt.day, MONTHS[dt.month-1], dt.year, dt.hour, dt.minute, dt.second) @@ -434,6 +430,7 @@ def split_header_words(header_values): if pairs: result.append(pairs) return result +HEADER_JOIN_TOKEN_RE = re.compile(r"[!#$%&'*+\-.^_`|~0-9A-Za-z]+") HEADER_JOIN_ESCAPE_RE = re.compile(r"([\"\\])") def join_header_words(lists): """Do the inverse (almost) of the conversion done by split_header_words. @@ -441,10 +438,10 @@ def join_header_words(lists): Takes a list of lists of (key, value) pairs and produces a single header value. Attribute values are quoted if needed. - >>> join_header_words([[("text/plain", None), ("charset", "iso-8859-1")]]) - 'text/plain; charset="iso-8859-1"' - >>> join_header_words([[("text/plain", None)], [("charset", "iso-8859-1")]]) - 'text/plain, charset="iso-8859-1"' + >>> join_header_words([[("text/plain", None), ("charset", "iso-8859/1")]]) + 'text/plain; charset="iso-8859/1"' + >>> join_header_words([[("text/plain", None)], [("charset", "iso-8859/1")]]) + 'text/plain, charset="iso-8859/1"' """ headers = [] @@ -452,7 +449,7 @@ def join_header_words(lists): attr = [] for k, v in pairs: if v is not None: - if not re.search(r"^\w+$", v): + if not HEADER_JOIN_TOKEN_RE.fullmatch(v): v = HEADER_JOIN_ESCAPE_RE.sub(r"\\\1", v) # escape " and \ v = '"%s"' % v k = "%s=%s" % (k, v) @@ -644,7 +641,7 @@ def eff_request_host(request): """ erhn = req_host = request_host(request) - if req_host.find(".") == -1 and not IPV4_RE.search(req_host): + if "." not in req_host: erhn = req_host + ".local" return req_host, erhn @@ -1047,12 +1044,13 @@ def set_ok_domain(self, cookie, request): else: undotted_domain = domain embedded_dots = (undotted_domain.find(".") >= 0) - if not embedded_dots and domain != ".local": + if not embedded_dots and not erhn.endswith(".local"): _debug(" non-local domain %s contains no embedded dot", domain) return False if cookie.version == 0: - if (not erhn.endswith(domain) and + if (not (erhn.endswith(domain) or + erhn.endswith(f"{undotted_domain}.local")) and (not erhn.startswith(".") and not ("."+erhn).endswith(domain))): _debug(" effective request-host %s (even with added " @@ -1227,14 +1225,9 @@ def path_return_ok(self, path, request): _debug(" %s does not path-match %s", req_path, path) return False -def vals_sorted_by_key(adict): - keys = sorted(adict.keys()) - return map(adict.get, keys) - def deepvalues(mapping): - """Iterates over nested mapping, depth-first, in sorted order by key.""" - values = vals_sorted_by_key(mapping) - for obj in values: + """Iterates over nested mapping, depth-first""" + for obj in list(mapping.values()): mapping = False try: obj.items @@ -1898,7 +1891,10 @@ def save(self, filename=None, ignore_discard=False, ignore_expires=False): if self.filename is not None: filename = self.filename else: raise ValueError(MISSING_FILENAME_TEXT) - with open(filename, "w") as f: + with os.fdopen( + os.open(filename, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o600), + 'w', + ) as f: # There really isn't an LWP Cookies 2.0 format, but this indicates # that there is extra information in here (domain_dot and # port_spec) while still being compatible with libwww-perl, I hope. @@ -1923,9 +1919,7 @@ def _really_load(self, f, filename, ignore_discard, ignore_expires): "comment", "commenturl") try: - while 1: - line = f.readline() - if line == "": break + while (line := f.readline()) != "": if not line.startswith(header): continue line = line[len(header):].strip() @@ -1993,7 +1987,7 @@ class MozillaCookieJar(FileCookieJar): This class differs from CookieJar only in the format it uses to save and load cookies to and from a file. This class uses the Mozilla/Netscape - `cookies.txt' format. lynx uses this file format, too. + `cookies.txt' format. curl and lynx use this file format, too. Don't expect cookies saved while the browser is running to be noticed by the browser (in fact, Mozilla on unix will overwrite your saved cookies if @@ -2025,12 +2019,9 @@ def _really_load(self, f, filename, ignore_discard, ignore_expires): filename) try: - while 1: - line = f.readline() + while (line := f.readline()) != "": rest = {} - if line == "": break - # httponly is a cookie flag as defined in rfc6265 # when encoded in a netscape cookie file, # the line is prepended with "#HttpOnly_" @@ -2094,7 +2085,10 @@ def save(self, filename=None, ignore_discard=False, ignore_expires=False): if self.filename is not None: filename = self.filename else: raise ValueError(MISSING_FILENAME_TEXT) - with open(filename, "w") as f: + with os.fdopen( + os.open(filename, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o600), + 'w', + ) as f: f.write(NETSCAPE_HEADER_TEXT) now = time.time() for cookie in self: diff --git a/Lib/http/server.py b/Lib/http/server.py index 58abadf7377..0ec479003a4 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -2,18 +2,18 @@ Note: BaseHTTPRequestHandler doesn't implement any HTTP request; see SimpleHTTPRequestHandler for simple implementations of GET, HEAD and POST, -and CGIHTTPRequestHandler for CGI scripts. +and (deprecated) CGIHTTPRequestHandler for CGI scripts. -It does, however, optionally implement HTTP/1.1 persistent connections, -as of version 0.3. +It does, however, optionally implement HTTP/1.1 persistent connections. Notes on CGIHTTPRequestHandler ------------------------------ -This class implements GET and POST requests to cgi-bin scripts. +This class is deprecated. It implements GET and POST requests to cgi-bin scripts. -If the os.fork() function is not present (e.g. on Windows), -subprocess.Popen() is used as a fallback, with slightly altered semantics. +If the os.fork() function is not present (Windows), subprocess.Popen() is used, +with slightly altered but never documented semantics. Use from a threaded +process is likely to trigger a warning at os.fork() time. In all cases, the implementation is intentionally naive -- all requests are executed synchronously. @@ -93,6 +93,7 @@ import html import http.client import io +import itertools import mimetypes import os import posixpath @@ -109,11 +110,10 @@ # Default error message template DEFAULT_ERROR_MESSAGE = """\ - - + + - + Error response @@ -127,6 +127,10 @@ DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8" +# Data larger than this will be read in chunks, to prevent extreme +# overallocation. +_MIN_READ_BUF_SIZE = 1 << 20 + class HTTPServer(socketserver.TCPServer): allow_reuse_address = 1 # Seems to make sense in testing environment @@ -275,6 +279,7 @@ def parse_request(self): error response has already been sent back. """ + is_http_0_9 = False self.command = None # set in case of error on the first line self.request_version = version = self.default_request_version self.close_connection = True @@ -300,6 +305,10 @@ def parse_request(self): # - Leading zeros MUST be ignored by recipients. if len(version_number) != 2: raise ValueError + if any(not component.isdigit() for component in version_number): + raise ValueError("non digit in http version") + if any(len(component) > 10 for component in version_number): + raise ValueError("unreasonable length http version") version_number = int(version_number[0]), int(version_number[1]) except (ValueError, IndexError): self.send_error( @@ -328,8 +337,21 @@ def parse_request(self): HTTPStatus.BAD_REQUEST, "Bad HTTP/0.9 request type (%r)" % command) return False + is_http_0_9 = True self.command, self.path = command, path + # gh-87389: The purpose of replacing '//' with '/' is to protect + # against open redirect attacks possibly triggered if the path starts + # with '//' because http clients treat //path as an absolute URI + # without scheme (similar to http://path) rather than a path. + if self.path.startswith('//'): + self.path = '/' + self.path.lstrip('/') # Reduce to a single / + + # For HTTP/0.9, headers are not expected at all. + if is_http_0_9: + self.headers = {} + return True + # Examine the headers and look for a Connection directive. try: self.headers = http.client.parse_headers(self.rfile, @@ -556,6 +578,11 @@ def log_error(self, format, *args): self.log_message(format, *args) + # https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes + _control_char_table = str.maketrans( + {c: fr'\x{c:02x}' for c in itertools.chain(range(0x20), range(0x7f,0xa0))}) + _control_char_table[ord('\\')] = r'\\' + def log_message(self, format, *args): """Log an arbitrary message. @@ -571,12 +598,16 @@ def log_message(self, format, *args): The client ip and current date/time are prefixed to every message. + Unicode control characters are replaced with escaped hex + before writing the output to stderr. + """ + message = format % args sys.stderr.write("%s - - [%s] %s\n" % (self.address_string(), self.log_date_time_string(), - format%args)) + message.translate(self._control_char_table))) def version_string(self): """Return the server software version string.""" @@ -637,6 +668,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): """ server_version = "SimpleHTTP/" + __version__ + index_pages = ("index.html", "index.htm") extensions_map = _encodings_map_default = { '.gz': 'application/gzip', '.Z': 'application/octet-stream', @@ -680,7 +712,7 @@ def send_head(self): f = None if os.path.isdir(path): parts = urllib.parse.urlsplit(self.path) - if not parts.path.endswith('/'): + if not parts.path.endswith(('/', '%2f', '%2F')): # redirect browser - doing basically what apache does self.send_response(HTTPStatus.MOVED_PERMANENTLY) new_parts = (parts[0], parts[1], parts[2] + '/', @@ -690,9 +722,9 @@ def send_head(self): self.send_header("Content-Length", "0") self.end_headers() return None - for index in "index.html", "index.htm": + for index in self.index_pages: index = os.path.join(path, index) - if os.path.exists(index): + if os.path.isfile(index): path = index break else: @@ -702,7 +734,7 @@ def send_head(self): # The test for this was added in test_httpserver.py # However, some OS platforms accept a trailingSlash as a filename # See discussion on python-dev and Issue34711 regarding - # parseing and rejection of filenames with a trailing slash + # parsing and rejection of filenames with a trailing slash if path.endswith("/"): self.send_error(HTTPStatus.NOT_FOUND, "File not found") return None @@ -770,21 +802,23 @@ def list_directory(self, path): return None list.sort(key=lambda a: a.lower()) r = [] + displaypath = self.path + displaypath = displaypath.split('#', 1)[0] + displaypath = displaypath.split('?', 1)[0] try: - displaypath = urllib.parse.unquote(self.path, + displaypath = urllib.parse.unquote(displaypath, errors='surrogatepass') except UnicodeDecodeError: - displaypath = urllib.parse.unquote(path) + displaypath = urllib.parse.unquote(displaypath) displaypath = html.escape(displaypath, quote=False) enc = sys.getfilesystemencoding() - title = 'Directory listing for %s' % displaypath - r.append('') - r.append('\n') - r.append('' % enc) - r.append('%s\n' % title) - r.append('\n

%s

' % title) + title = f'Directory listing for {displaypath}' + r.append('') + r.append('') + r.append('') + r.append(f'') + r.append(f'{title}\n') + r.append(f'\n

{title}

') r.append('
\n
    ') for name in list: fullname = os.path.join(path, name) @@ -820,14 +854,14 @@ def translate_path(self, path): """ # abandon query parameters - path = path.split('?',1)[0] - path = path.split('#',1)[0] + path = path.split('#', 1)[0] + path = path.split('?', 1)[0] # Don't forget explicit trailing slash when normalizing. Issue17324 - trailing_slash = path.rstrip().endswith('/') try: path = urllib.parse.unquote(path, errors='surrogatepass') except UnicodeDecodeError: path = urllib.parse.unquote(path) + trailing_slash = path.endswith('/') path = posixpath.normpath(path) words = path.split('/') words = filter(None, words) @@ -877,7 +911,7 @@ def guess_type(self, path): ext = ext.lower() if ext in self.extensions_map: return self.extensions_map[ext] - guess, _ = mimetypes.guess_type(path) + guess, _ = mimetypes.guess_file_type(path) if guess: return guess return 'application/octet-stream' @@ -966,6 +1000,12 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): """ + def __init__(self, *args, **kwargs): + import warnings + warnings._deprecated("http.server.CGIHTTPRequestHandler", + remove=(3, 15)) + super().__init__(*args, **kwargs) + # Determine platform specifics have_fork = hasattr(os, 'fork') @@ -1078,7 +1118,7 @@ def run_cgi(self): "CGI script is not executable (%r)" % scriptname) return - # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html + # Reference: https://www6.uniovi.es/~antonio/ncsa_httpd/cgi/env.html # XXX Much of the following could be prepared ahead of time! env = copy.deepcopy(os.environ) env['SERVER_SOFTWARE'] = self.version_string() @@ -1198,7 +1238,18 @@ def run_cgi(self): env = env ) if self.command.lower() == "post" and nbytes > 0: - data = self.rfile.read(nbytes) + cursize = 0 + data = self.rfile.read(min(nbytes, _MIN_READ_BUF_SIZE)) + while len(data) < nbytes and len(data) != cursize: + cursize = len(data) + # This is a geometric increase in read size (never more + # than doubling out the current length of data per loop + # iteration). + delta = min(cursize, nbytes - cursize) + try: + data += self.rfile.read(delta) + except TimeoutError: + break else: data = None # throw away additional data [see bug #427345] @@ -1258,15 +1309,19 @@ def test(HandlerClass=BaseHTTPRequestHandler, parser = argparse.ArgumentParser() parser.add_argument('--cgi', action='store_true', help='run as CGI server') - parser.add_argument('--bind', '-b', metavar='ADDRESS', - help='specify alternate bind address ' + parser.add_argument('-b', '--bind', metavar='ADDRESS', + help='bind to this address ' '(default: all interfaces)') - parser.add_argument('--directory', '-d', default=os.getcwd(), - help='specify alternate directory ' + parser.add_argument('-d', '--directory', default=os.getcwd(), + help='serve this directory ' '(default: current directory)') - parser.add_argument('port', action='store', default=8000, type=int, - nargs='?', - help='specify alternate port (default: 8000)') + parser.add_argument('-p', '--protocol', metavar='VERSION', + default='HTTP/1.0', + help='conform to this HTTP version ' + '(default: %(default)s)') + parser.add_argument('port', default=8000, type=int, nargs='?', + help='bind to this port ' + '(default: %(default)s)') args = parser.parse_args() if args.cgi: handler_class = CGIHTTPRequestHandler @@ -1292,4 +1347,5 @@ def finish_request(self, request, client_address): ServerClass=DualStackServer, port=args.port, bind=args.bind, + protocol=args.protocol, ) diff --git a/Lib/test/test_http_cookiejar.py b/Lib/test/test_http_cookiejar.py index 68a693c78b3..51fa4a3d413 100644 --- a/Lib/test/test_http_cookiejar.py +++ b/Lib/test/test_http_cookiejar.py @@ -1,14 +1,16 @@ """Tests for http/cookiejar.py.""" import os +import stat +import sys import re -import test.support +from test import support from test.support import os_helper from test.support import warnings_helper +from test.support.testcase import ExtraAssertions import time import unittest import urllib.request -import pathlib from http.cookiejar import (time2isoz, http2time, iso2time, time2netscape, parse_ns_headers, join_header_words, split_header_words, Cookie, @@ -17,6 +19,7 @@ reach, is_HDN, domain_match, user_domain_match, request_path, request_port, request_host) +mswindows = (sys.platform == "win32") class DateTimeTests(unittest.TestCase): @@ -104,8 +107,7 @@ def test_http2time_formats(self): self.assertEqual(http2time(s.lower()), test_t, s.lower()) self.assertEqual(http2time(s.upper()), test_t, s.upper()) - def test_http2time_garbage(self): - for test in [ + @support.subTests('test', [ '', 'Garbage', 'Mandag 16. September 1996', @@ -120,10 +122,9 @@ def test_http2time_garbage(self): '08-01-3697739', '09 Feb 19942632 22:23:32 GMT', 'Wed, 09 Feb 1994834 22:23:32 GMT', - ]: - self.assertIsNone(http2time(test), - "http2time(%s) is not None\n" - "http2time(test) %s" % (test, http2time(test))) + ]) + def test_http2time_garbage(self, test): + self.assertIsNone(http2time(test)) def test_http2time_redos_regression_actually_completes(self): # LOOSE_HTTP_DATE_RE was vulnerable to malicious input which caused catastrophic backtracking (REDoS). @@ -148,9 +149,7 @@ def parse_date(text): self.assertEqual(parse_date("1994-02-03 19:45:29 +0530"), (1994, 2, 3, 14, 15, 29)) - def test_iso2time_formats(self): - # test iso2time for supported dates. - tests = [ + @support.subTests('s', [ '1994-02-03 00:00:00 -0000', # ISO 8601 format '1994-02-03 00:00:00 +0000', # ISO 8601 format '1994-02-03 00:00:00', # zone is optional @@ -163,16 +162,15 @@ def test_iso2time_formats(self): # A few tests with extra space at various places ' 1994-02-03 ', ' 1994-02-03T00:00:00 ', - ] - + ]) + def test_iso2time_formats(self, s): + # test iso2time for supported dates. test_t = 760233600 # assume broken POSIX counting of seconds - for s in tests: - self.assertEqual(iso2time(s), test_t, s) - self.assertEqual(iso2time(s.lower()), test_t, s.lower()) - self.assertEqual(iso2time(s.upper()), test_t, s.upper()) + self.assertEqual(iso2time(s), test_t, s) + self.assertEqual(iso2time(s.lower()), test_t, s.lower()) + self.assertEqual(iso2time(s.upper()), test_t, s.upper()) - def test_iso2time_garbage(self): - for test in [ + @support.subTests('test', [ '', 'Garbage', 'Thursday, 03-Feb-94 00:00:00 GMT', @@ -185,11 +183,10 @@ def test_iso2time_garbage(self): '01-01-1980 00:00:62', '01-01-1980T00:00:62', '19800101T250000Z', - ]: - self.assertIsNone(iso2time(test), - "iso2time(%r)" % test) + ]) + def test_iso2time_garbage(self, test): + self.assertIsNone(iso2time(test)) - @unittest.skip("TODO, RUSTPYTHON, regressed to quadratic complexity") def test_iso2time_performance_regression(self): # If ISO_DATE_RE regresses to quadratic complexity, this test will take a very long time to succeed. # If fixed, it should complete within a fraction of a second. @@ -199,24 +196,23 @@ def test_iso2time_performance_regression(self): class HeaderTests(unittest.TestCase): - def test_parse_ns_headers(self): - # quotes should be stripped - expected = [[('foo', 'bar'), ('expires', 2209069412), ('version', '0')]] - for hdr in [ + @support.subTests('hdr', [ 'foo=bar; expires=01 Jan 2040 22:23:32 GMT', 'foo=bar; expires="01 Jan 2040 22:23:32 GMT"', - ]: - self.assertEqual(parse_ns_headers([hdr]), expected) - - def test_parse_ns_headers_version(self): - + ]) + def test_parse_ns_headers(self, hdr): # quotes should be stripped - expected = [[('foo', 'bar'), ('version', '1')]] - for hdr in [ + expected = [[('foo', 'bar'), ('expires', 2209069412), ('version', '0')]] + self.assertEqual(parse_ns_headers([hdr]), expected) + + @support.subTests('hdr', [ 'foo=bar; version="1"', 'foo=bar; Version="1"', - ]: - self.assertEqual(parse_ns_headers([hdr]), expected) + ]) + def test_parse_ns_headers_version(self, hdr): + # quotes should be stripped + expected = [[('foo', 'bar'), ('version', '1')]] + self.assertEqual(parse_ns_headers([hdr]), expected) def test_parse_ns_headers_special_names(self): # names such as 'expires' are not special in first name=value pair @@ -232,8 +228,7 @@ def test_join_header_words(self): self.assertEqual(join_header_words([[]]), "") - def test_split_header_words(self): - tests = [ + @support.subTests('arg,expect', [ ("foo", [[("foo", None)]]), ("foo=bar", [[("foo", "bar")]]), (" foo ", [[("foo", None)]]), @@ -250,24 +245,22 @@ def test_split_header_words(self): (r'foo; bar=baz, spam=, foo="\,\;\"", bar= ', [[("foo", None), ("bar", "baz")], [("spam", "")], [("foo", ',;"')], [("bar", "")]]), - ] - - for arg, expect in tests: - try: - result = split_header_words([arg]) - except: - import traceback, io - f = io.StringIO() - traceback.print_exc(None, f) - result = "(error -- traceback follows)\n\n%s" % f.getvalue() - self.assertEqual(result, expect, """ + ]) + def test_split_header_words(self, arg, expect): + try: + result = split_header_words([arg]) + except: + import traceback, io + f = io.StringIO() + traceback.print_exc(None, f) + result = "(error -- traceback follows)\n\n%s" % f.getvalue() + self.assertEqual(result, expect, """ When parsing: '%s' Expected: '%s' Got: '%s' """ % (arg, expect, result)) - def test_roundtrip(self): - tests = [ + @support.subTests('arg,expect', [ ("foo", "foo"), ("foo=bar", "foo=bar"), (" foo ", "foo"), @@ -276,23 +269,35 @@ def test_roundtrip(self): ("foo=bar;bar=baz", "foo=bar; bar=baz"), ('foo bar baz', "foo; bar; baz"), (r'foo="\"" bar="\\"', r'foo="\""; bar="\\"'), + ("föo=bär", 'föo="bär"'), ('foo,,,bar', 'foo, bar'), ('foo=bar,bar=baz', 'foo=bar, bar=baz'), + ("foo=\n", 'foo=""'), + ('foo="\n"', 'foo="\n"'), + ('foo=bar\n', 'foo=bar'), + ('foo="bar\n"', 'foo="bar\n"'), + ('foo=bar\nbaz', 'foo=bar; baz'), + ('foo="bar\nbaz"', 'foo="bar\nbaz"'), ('text/html; charset=iso-8859-1', - 'text/html; charset="iso-8859-1"'), + 'text/html; charset=iso-8859-1'), + + ('text/html; charset="iso-8859/1"', + 'text/html; charset="iso-8859/1"'), ('foo="bar"; port="80,81"; discard, bar=baz', 'foo=bar; port="80,81"; discard, bar=baz'), (r'Basic realm="\"foo\\\\bar\""', - r'Basic; realm="\"foo\\\\bar\""') - ] - - for arg, expect in tests: - input = split_header_words([arg]) - res = join_header_words(input) - self.assertEqual(res, expect, """ + r'Basic; realm="\"foo\\\\bar\""'), + + ('n; foo="foo;_", bar="foo,_"', + 'n; foo="foo;_", bar="foo,_"'), + ]) + def test_roundtrip(self, arg, expect): + input = split_header_words([arg]) + res = join_header_words(input) + self.assertEqual(res, expect, """ When parsing: '%s' Expected: '%s' Got: '%s' @@ -336,9 +341,9 @@ def test_constructor_with_str(self): self.assertEqual(c.filename, filename) def test_constructor_with_path_like(self): - filename = pathlib.Path(os_helper.TESTFN) - c = LWPCookieJar(filename) - self.assertEqual(c.filename, os.fspath(filename)) + filename = os_helper.TESTFN + c = LWPCookieJar(os_helper.FakePath(filename)) + self.assertEqual(c.filename, filename) def test_constructor_with_none(self): c = LWPCookieJar(None) @@ -365,10 +370,63 @@ def test_lwp_valueless_cookie(self): c = LWPCookieJar() c.load(filename, ignore_discard=True) finally: - try: os.unlink(filename) - except OSError: pass + os_helper.unlink(filename) self.assertEqual(c._cookies["www.acme.com"]["/"]["boo"].value, None) + @unittest.skipIf(mswindows, "windows file permissions are incompatible with file modes") + @os_helper.skip_unless_working_chmod + def test_lwp_filepermissions(self): + # Cookie file should only be readable by the creator + filename = os_helper.TESTFN + c = LWPCookieJar() + interact_netscape(c, "http://www.acme.com/", 'boo') + try: + c.save(filename, ignore_discard=True) + st = os.stat(filename) + self.assertEqual(stat.S_IMODE(st.st_mode), 0o600) + finally: + os_helper.unlink(filename) + + @unittest.skipIf(mswindows, "windows file permissions are incompatible with file modes") + @os_helper.skip_unless_working_chmod + def test_mozilla_filepermissions(self): + # Cookie file should only be readable by the creator + filename = os_helper.TESTFN + c = MozillaCookieJar() + interact_netscape(c, "http://www.acme.com/", 'boo') + try: + c.save(filename, ignore_discard=True) + st = os.stat(filename) + self.assertEqual(stat.S_IMODE(st.st_mode), 0o600) + finally: + os_helper.unlink(filename) + + @unittest.skipIf(mswindows, "windows file permissions are incompatible with file modes") + @os_helper.skip_unless_working_chmod + def test_cookie_files_are_truncated(self): + filename = os_helper.TESTFN + for cookiejar_class in (LWPCookieJar, MozillaCookieJar): + c = cookiejar_class(filename) + + req = urllib.request.Request("http://www.acme.com/") + headers = ["Set-Cookie: pll_lang=en; Max-Age=31536000; path=/"] + res = FakeResponse(headers, "http://www.acme.com/") + c.extract_cookies(res, req) + self.assertEqual(len(c), 1) + + try: + # Save the first version with contents: + c.save() + # Now, clear cookies and re-save: + c.clear() + c.save() + # Check that file was truncated: + c.load() + finally: + os_helper.unlink(filename) + + self.assertEqual(len(c), 0) + def test_bad_magic(self): # OSErrors (eg. file doesn't exist) are allowed to propagate filename = os_helper.TESTFN @@ -392,8 +450,7 @@ def test_bad_magic(self): c = cookiejar_class() self.assertRaises(LoadError, c.load, filename) finally: - try: os.unlink(filename) - except OSError: pass + os_helper.unlink(filename) class CookieTests(unittest.TestCase): # XXX @@ -442,14 +499,7 @@ class CookieTests(unittest.TestCase): ## just the 7 special TLD's listed in their spec. And folks rely on ## that... - def test_domain_return_ok(self): - # test optimization: .domain_return_ok() should filter out most - # domains in the CookieJar before we try to access them (because that - # may require disk access -- in particular, with MSIECookieJar) - # This is only a rough check for performance reasons, so it's not too - # critical as long as it's sufficiently liberal. - pol = DefaultCookiePolicy() - for url, domain, ok in [ + @support.subTests('url,domain,ok', [ ("http://foo.bar.com/", "blah.com", False), ("http://foo.bar.com/", "rhubarb.blah.com", False), ("http://foo.bar.com/", "rhubarb.foo.bar.com", False), @@ -469,11 +519,18 @@ def test_domain_return_ok(self): ("http://foo/", ".local", True), ("http://barfoo.com", ".foo.com", False), ("http://barfoo.com", "foo.com", False), - ]: - request = urllib.request.Request(url) - r = pol.domain_return_ok(domain, request) - if ok: self.assertTrue(r) - else: self.assertFalse(r) + ]) + def test_domain_return_ok(self, url, domain, ok): + # test optimization: .domain_return_ok() should filter out most + # domains in the CookieJar before we try to access them (because that + # may require disk access -- in particular, with MSIECookieJar) + # This is only a rough check for performance reasons, so it's not too + # critical as long as it's sufficiently liberal. + pol = DefaultCookiePolicy() + request = urllib.request.Request(url) + r = pol.domain_return_ok(domain, request) + if ok: self.assertTrue(r) + else: self.assertFalse(r) def test_missing_value(self): # missing = sign in Cookie: header is regarded by Mozilla as a missing @@ -489,7 +546,7 @@ def test_missing_value(self): self.assertIsNone(cookie.value) self.assertEqual(cookie.name, '"spam"') self.assertEqual(lwp_cookie_str(cookie), ( - r'"spam"; path="/foo/"; domain="www.acme.com"; ' + r'"spam"; path="/foo/"; domain=www.acme.com; ' 'path_spec; discard; version=0')) old_str = repr(c) c.save(ignore_expires=True, ignore_discard=True) @@ -497,7 +554,7 @@ def test_missing_value(self): c = MozillaCookieJar(filename) c.revert(ignore_expires=True, ignore_discard=True) finally: - os.unlink(c.filename) + os_helper.unlink(c.filename) # cookies unchanged apart from lost info re. whether path was specified self.assertEqual( repr(c), @@ -507,10 +564,7 @@ def test_missing_value(self): self.assertEqual(interact_netscape(c, "http://www.acme.com/foo/"), '"spam"; eggs') - def test_rfc2109_handling(self): - # RFC 2109 cookies are handled as RFC 2965 or Netscape cookies, - # dependent on policy settings - for rfc2109_as_netscape, rfc2965, version in [ + @support.subTests('rfc2109_as_netscape,rfc2965,version', [ # default according to rfc2965 if not explicitly specified (None, False, 0), (None, True, 1), @@ -519,24 +573,27 @@ def test_rfc2109_handling(self): (False, True, 1), (True, False, 0), (True, True, 0), - ]: - policy = DefaultCookiePolicy( - rfc2109_as_netscape=rfc2109_as_netscape, - rfc2965=rfc2965) - c = CookieJar(policy) - interact_netscape(c, "http://www.example.com/", "ni=ni; Version=1") - try: - cookie = c._cookies["www.example.com"]["/"]["ni"] - except KeyError: - self.assertIsNone(version) # didn't expect a stored cookie - else: - self.assertEqual(cookie.version, version) - # 2965 cookies are unaffected - interact_2965(c, "http://www.example.com/", - "foo=bar; Version=1") - if rfc2965: - cookie2965 = c._cookies["www.example.com"]["/"]["foo"] - self.assertEqual(cookie2965.version, 1) + ]) + def test_rfc2109_handling(self, rfc2109_as_netscape, rfc2965, version): + # RFC 2109 cookies are handled as RFC 2965 or Netscape cookies, + # dependent on policy settings + policy = DefaultCookiePolicy( + rfc2109_as_netscape=rfc2109_as_netscape, + rfc2965=rfc2965) + c = CookieJar(policy) + interact_netscape(c, "http://www.example.com/", "ni=ni; Version=1") + try: + cookie = c._cookies["www.example.com"]["/"]["ni"] + except KeyError: + self.assertIsNone(version) # didn't expect a stored cookie + else: + self.assertEqual(cookie.version, version) + # 2965 cookies are unaffected + interact_2965(c, "http://www.example.com/", + "foo=bar; Version=1") + if rfc2965: + cookie2965 = c._cookies["www.example.com"]["/"]["foo"] + self.assertEqual(cookie2965.version, 1) def test_ns_parser(self): c = CookieJar() @@ -597,8 +654,6 @@ def test_ns_parser_special_names(self): self.assertIn('expires', cookies) self.assertIn('version', cookies) - # TODO: RUSTPYTHON; need to update http library to remove warnings - @unittest.expectedFailure def test_expires(self): # if expires is in future, keep cookie... c = CookieJar() @@ -706,8 +761,7 @@ def test_default_path_with_query(self): # Cookie is sent back to the same URI. self.assertEqual(interact_netscape(cj, uri), value) - def test_escape_path(self): - cases = [ + @support.subTests('arg,result', [ # quoted safe ("/foo%2f/bar", "/foo%2F/bar"), ("/foo%2F/bar", "/foo%2F/bar"), @@ -727,9 +781,9 @@ def test_escape_path(self): ("/foo/bar\u00fc", "/foo/bar%C3%BC"), # UTF-8 encoded # unicode ("/foo/bar\uabcd", "/foo/bar%EA%AF%8D"), # UTF-8 encoded - ] - for arg, result in cases: - self.assertEqual(escape_path(arg), result) + ]) + def test_escape_path(self, arg, result): + self.assertEqual(escape_path(arg), result) def test_request_path(self): # with parameters @@ -923,6 +977,48 @@ def test_two_component_domain_ns(self): ## self.assertEqual(len(c), 2) self.assertEqual(len(c), 4) + def test_localhost_domain(self): + c = CookieJar() + + interact_netscape(c, "http://localhost", "foo=bar; domain=localhost;") + + self.assertEqual(len(c), 1) + + def test_localhost_domain_contents(self): + c = CookieJar() + + interact_netscape(c, "http://localhost", "foo=bar; domain=localhost;") + + self.assertEqual(c._cookies[".localhost"]["/"]["foo"].value, "bar") + + def test_localhost_domain_contents_2(self): + c = CookieJar() + + interact_netscape(c, "http://localhost", "foo=bar;") + + self.assertEqual(c._cookies["localhost.local"]["/"]["foo"].value, "bar") + + def test_evil_nonlocal_domain(self): + c = CookieJar() + + interact_netscape(c, "http://evil.com", "foo=bar; domain=.localhost") + + self.assertEqual(len(c), 0) + + def test_evil_local_domain(self): + c = CookieJar() + + interact_netscape(c, "http://localhost", "foo=bar; domain=.evil.com") + + self.assertEqual(len(c), 0) + + def test_evil_local_domain_2(self): + c = CookieJar() + + interact_netscape(c, "http://localhost", "foo=bar; domain=.someother.local") + + self.assertEqual(len(c), 0) + def test_two_component_domain_rfc2965(self): pol = DefaultCookiePolicy(rfc2965=True) c = CookieJar(pol) @@ -1254,11 +1350,11 @@ def test_Cookie_iterator(self): r'port="90,100, 80,8080"; ' r'max-age=100; Comment = "Just kidding! (\"|\\\\) "') - versions = [1, 1, 1, 0, 1] - names = ["bang", "foo", "foo", "spam", "foo"] - domains = [".sol.no", "blah.spam.org", "www.acme.com", - "www.acme.com", "www.acme.com"] - paths = ["/", "/", "/", "/blah", "/blah/"] + versions = [1, 0, 1, 1, 1] + names = ["foo", "spam", "foo", "foo", "bang"] + domains = ["blah.spam.org", "www.acme.com", "www.acme.com", + "www.acme.com", ".sol.no"] + paths = ["/", "/blah", "/blah/", "/", "/"] for i in range(4): i = 0 @@ -1331,7 +1427,7 @@ def cookiejar_from_cookie_headers(headers): self.assertIsNone(cookie.expires) -class LWPCookieTests(unittest.TestCase): +class LWPCookieTests(unittest.TestCase, ExtraAssertions): # Tests taken from libwww-perl, with a few modifications and additions. def test_netscape_example_1(self): @@ -1423,7 +1519,7 @@ def test_netscape_example_1(self): h = req.get_header("Cookie") self.assertIn("PART_NUMBER=ROCKET_LAUNCHER_0001", h) self.assertIn("CUSTOMER=WILE_E_COYOTE", h) - self.assertTrue(h.startswith("SHIPPING=FEDEX;")) + self.assertStartsWith(h, "SHIPPING=FEDEX;") def test_netscape_example_2(self): # Second Example transaction sequence: @@ -1727,8 +1823,7 @@ def test_rejection(self): c = LWPCookieJar(policy=pol) c.load(filename, ignore_discard=True) finally: - try: os.unlink(filename) - except OSError: pass + os_helper.unlink(filename) self.assertEqual(old, repr(c)) @@ -1787,8 +1882,7 @@ def save_and_restore(cj, ignore_discard): DefaultCookiePolicy(rfc2965=True)) new_c.load(ignore_discard=ignore_discard) finally: - try: os.unlink(filename) - except OSError: pass + os_helper.unlink(filename) return new_c new_c = save_and_restore(c, True) diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index d4a6eefe322..275578d53cb 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -1,4 +1,4 @@ -import sys +import enum import errno from http import client, HTTPStatus import io @@ -8,7 +8,6 @@ import re import socket import threading -import warnings import unittest from unittest import mock @@ -17,16 +16,19 @@ from test import support from test.support import os_helper from test.support import socket_helper -from test.support import warnings_helper +from test.support.testcase import ExtraAssertions +support.requires_working_socket(module=True) here = os.path.dirname(__file__) # Self-signed cert file for 'localhost' -CERT_localhost = os.path.join(here, 'certdata/keycert.pem') +CERT_localhost = os.path.join(here, 'certdata', 'keycert.pem') # Self-signed cert file for 'fakehostname' -CERT_fakehostname = os.path.join(here, 'certdata/keycert2.pem') +CERT_fakehostname = os.path.join(here, 'certdata', 'keycert2.pem') # Self-signed cert file for self-signed.pythontest.net -CERT_selfsigned_pythontestdotnet = os.path.join(here, 'certdata/selfsigned_pythontestdotnet.pem') +CERT_selfsigned_pythontestdotnet = os.path.join( + here, 'certdata', 'selfsigned_pythontestdotnet.pem', +) # constants for testing chunked encoding chunked_start = ( @@ -133,7 +135,7 @@ def connect(self): def create_connection(self, *pos, **kw): return FakeSocket(*self.fake_socket_args) -class HeaderTests(TestCase): +class HeaderTests(TestCase, ExtraAssertions): def test_auto_headers(self): # Some headers are added automatically, but should not be added by # .request() if they are explicitly set. @@ -272,7 +274,7 @@ def test_ipv6host_header(self): sock = FakeSocket('') conn.sock = sock conn.request('GET', '/foo') - self.assertTrue(sock.data.startswith(expected)) + self.assertStartsWith(sock.data, expected) expected = b'GET /foo HTTP/1.1\r\nHost: [2001:102A::]\r\n' \ b'Accept-Encoding: identity\r\n\r\n' @@ -280,7 +282,23 @@ def test_ipv6host_header(self): sock = FakeSocket('') conn.sock = sock conn.request('GET', '/foo') - self.assertTrue(sock.data.startswith(expected)) + self.assertStartsWith(sock.data, expected) + + expected = b'GET /foo HTTP/1.1\r\nHost: [fe80::]\r\n' \ + b'Accept-Encoding: identity\r\n\r\n' + conn = client.HTTPConnection('[fe80::%2]') + sock = FakeSocket('') + conn.sock = sock + conn.request('GET', '/foo') + self.assertStartsWith(sock.data, expected) + + expected = b'GET /foo HTTP/1.1\r\nHost: [fe80::]:81\r\n' \ + b'Accept-Encoding: identity\r\n\r\n' + conn = client.HTTPConnection('[fe80::%2]:81') + sock = FakeSocket('') + conn.sock = sock + conn.request('GET', '/foo') + self.assertStartsWith(sock.data, expected) def test_malformed_headers_coped_with(self): # Issue 19996 @@ -318,9 +336,9 @@ def test_parse_all_octets(self): self.assertIsNotNone(resp.getheader('obs-text')) self.assertIn('obs-text', resp.msg) for folded in (resp.getheader('obs-fold'), resp.msg['obs-fold']): - self.assertTrue(folded.startswith('text')) + self.assertStartsWith(folded, 'text') self.assertIn(' folded with space', folded) - self.assertTrue(folded.endswith('folded with tab')) + self.assertEndsWith(folded, 'folded with tab') def test_invalid_headers(self): conn = client.HTTPConnection('example.com') @@ -520,11 +538,203 @@ def _parse_chunked(self, data): return b''.join(body) -class BasicTest(TestCase): +class BasicTest(TestCase, ExtraAssertions): def test_dir_with_added_behavior_on_status(self): # see issue40084 self.assertTrue({'description', 'name', 'phrase', 'value'} <= set(dir(HTTPStatus(404)))) + def test_simple_httpstatus(self): + class CheckedHTTPStatus(enum.IntEnum): + """HTTP status codes and reason phrases + + Status codes from the following RFCs are all observed: + + * RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616 + * RFC 6585: Additional HTTP Status Codes + * RFC 3229: Delta encoding in HTTP + * RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518 + * RFC 5842: Binding Extensions to WebDAV + * RFC 7238: Permanent Redirect + * RFC 2295: Transparent Content Negotiation in HTTP + * RFC 2774: An HTTP Extension Framework + * RFC 7725: An HTTP Status Code to Report Legal Obstacles + * RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2) + * RFC 2324: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0) + * RFC 8297: An HTTP Status Code for Indicating Hints + * RFC 8470: Using Early Data in HTTP + """ + def __new__(cls, value, phrase, description=''): + obj = int.__new__(cls, value) + obj._value_ = value + + obj.phrase = phrase + obj.description = description + return obj + + @property + def is_informational(self): + return 100 <= self <= 199 + + @property + def is_success(self): + return 200 <= self <= 299 + + @property + def is_redirection(self): + return 300 <= self <= 399 + + @property + def is_client_error(self): + return 400 <= self <= 499 + + @property + def is_server_error(self): + return 500 <= self <= 599 + + # informational + CONTINUE = 100, 'Continue', 'Request received, please continue' + SWITCHING_PROTOCOLS = (101, 'Switching Protocols', + 'Switching to new protocol; obey Upgrade header') + PROCESSING = 102, 'Processing' + EARLY_HINTS = 103, 'Early Hints' + # success + OK = 200, 'OK', 'Request fulfilled, document follows' + CREATED = 201, 'Created', 'Document created, URL follows' + ACCEPTED = (202, 'Accepted', + 'Request accepted, processing continues off-line') + NON_AUTHORITATIVE_INFORMATION = (203, + 'Non-Authoritative Information', 'Request fulfilled from cache') + NO_CONTENT = 204, 'No Content', 'Request fulfilled, nothing follows' + RESET_CONTENT = 205, 'Reset Content', 'Clear input form for further input' + PARTIAL_CONTENT = 206, 'Partial Content', 'Partial content follows' + MULTI_STATUS = 207, 'Multi-Status' + ALREADY_REPORTED = 208, 'Already Reported' + IM_USED = 226, 'IM Used' + # redirection + MULTIPLE_CHOICES = (300, 'Multiple Choices', + 'Object has several resources -- see URI list') + MOVED_PERMANENTLY = (301, 'Moved Permanently', + 'Object moved permanently -- see URI list') + FOUND = 302, 'Found', 'Object moved temporarily -- see URI list' + SEE_OTHER = 303, 'See Other', 'Object moved -- see Method and URL list' + NOT_MODIFIED = (304, 'Not Modified', + 'Document has not changed since given time') + USE_PROXY = (305, 'Use Proxy', + 'You must use proxy specified in Location to access this resource') + TEMPORARY_REDIRECT = (307, 'Temporary Redirect', + 'Object moved temporarily -- see URI list') + PERMANENT_REDIRECT = (308, 'Permanent Redirect', + 'Object moved permanently -- see URI list') + # client error + BAD_REQUEST = (400, 'Bad Request', + 'Bad request syntax or unsupported method') + UNAUTHORIZED = (401, 'Unauthorized', + 'No permission -- see authorization schemes') + PAYMENT_REQUIRED = (402, 'Payment Required', + 'No payment -- see charging schemes') + FORBIDDEN = (403, 'Forbidden', + 'Request forbidden -- authorization will not help') + NOT_FOUND = (404, 'Not Found', + 'Nothing matches the given URI') + METHOD_NOT_ALLOWED = (405, 'Method Not Allowed', + 'Specified method is invalid for this resource') + NOT_ACCEPTABLE = (406, 'Not Acceptable', + 'URI not available in preferred format') + PROXY_AUTHENTICATION_REQUIRED = (407, + 'Proxy Authentication Required', + 'You must authenticate with this proxy before proceeding') + REQUEST_TIMEOUT = (408, 'Request Timeout', + 'Request timed out; try again later') + CONFLICT = 409, 'Conflict', 'Request conflict' + GONE = (410, 'Gone', + 'URI no longer exists and has been permanently removed') + LENGTH_REQUIRED = (411, 'Length Required', + 'Client must specify Content-Length') + PRECONDITION_FAILED = (412, 'Precondition Failed', + 'Precondition in headers is false') + CONTENT_TOO_LARGE = (413, 'Content Too Large', + 'Content is too large') + REQUEST_ENTITY_TOO_LARGE = CONTENT_TOO_LARGE + URI_TOO_LONG = (414, 'URI Too Long', 'URI is too long') + REQUEST_URI_TOO_LONG = URI_TOO_LONG + UNSUPPORTED_MEDIA_TYPE = (415, 'Unsupported Media Type', + 'Entity body in unsupported format') + RANGE_NOT_SATISFIABLE = (416, + 'Range Not Satisfiable', + 'Cannot satisfy request range') + REQUESTED_RANGE_NOT_SATISFIABLE = RANGE_NOT_SATISFIABLE + EXPECTATION_FAILED = (417, 'Expectation Failed', + 'Expect condition could not be satisfied') + IM_A_TEAPOT = (418, 'I\'m a Teapot', + 'Server refuses to brew coffee because it is a teapot.') + MISDIRECTED_REQUEST = (421, 'Misdirected Request', + 'Server is not able to produce a response') + UNPROCESSABLE_CONTENT = 422, 'Unprocessable Content' + UNPROCESSABLE_ENTITY = UNPROCESSABLE_CONTENT + LOCKED = 423, 'Locked' + FAILED_DEPENDENCY = 424, 'Failed Dependency' + TOO_EARLY = 425, 'Too Early' + UPGRADE_REQUIRED = 426, 'Upgrade Required' + PRECONDITION_REQUIRED = (428, 'Precondition Required', + 'The origin server requires the request to be conditional') + TOO_MANY_REQUESTS = (429, 'Too Many Requests', + 'The user has sent too many requests in ' + 'a given amount of time ("rate limiting")') + REQUEST_HEADER_FIELDS_TOO_LARGE = (431, + 'Request Header Fields Too Large', + 'The server is unwilling to process the request because its header ' + 'fields are too large') + UNAVAILABLE_FOR_LEGAL_REASONS = (451, + 'Unavailable For Legal Reasons', + 'The server is denying access to the ' + 'resource as a consequence of a legal demand') + # server errors + INTERNAL_SERVER_ERROR = (500, 'Internal Server Error', + 'Server got itself in trouble') + NOT_IMPLEMENTED = (501, 'Not Implemented', + 'Server does not support this operation') + BAD_GATEWAY = (502, 'Bad Gateway', + 'Invalid responses from another server/proxy') + SERVICE_UNAVAILABLE = (503, 'Service Unavailable', + 'The server cannot process the request due to a high load') + GATEWAY_TIMEOUT = (504, 'Gateway Timeout', + 'The gateway server did not receive a timely response') + HTTP_VERSION_NOT_SUPPORTED = (505, 'HTTP Version Not Supported', + 'Cannot fulfill request') + VARIANT_ALSO_NEGOTIATES = 506, 'Variant Also Negotiates' + INSUFFICIENT_STORAGE = 507, 'Insufficient Storage' + LOOP_DETECTED = 508, 'Loop Detected' + NOT_EXTENDED = 510, 'Not Extended' + NETWORK_AUTHENTICATION_REQUIRED = (511, + 'Network Authentication Required', + 'The client needs to authenticate to gain network access') + enum._test_simple_enum(CheckedHTTPStatus, HTTPStatus) + + def test_httpstatus_range(self): + """Checks that the statuses are in the 100-599 range""" + + for member in HTTPStatus.__members__.values(): + self.assertGreaterEqual(member, 100) + self.assertLessEqual(member, 599) + + def test_httpstatus_category(self): + """Checks that the statuses belong to the standard categories""" + + categories = ( + ((100, 199), "is_informational"), + ((200, 299), "is_success"), + ((300, 399), "is_redirection"), + ((400, 499), "is_client_error"), + ((500, 599), "is_server_error"), + ) + for member in HTTPStatus.__members__.values(): + for (lower, upper), category in categories: + category_indicator = getattr(member, category) + if lower <= member <= upper: + self.assertTrue(category_indicator) + else: + self.assertFalse(category_indicator) + def test_status_lines(self): # Test HTTP status lines @@ -780,8 +990,7 @@ def test_send_file(self): sock = FakeSocket(body) conn.sock = sock conn.request('GET', '/foo', body) - self.assertTrue(sock.data.startswith(expected), '%r != %r' % - (sock.data[:len(expected)], expected)) + self.assertStartsWith(sock.data, expected) def test_send(self): expected = b'this is a test this is only a test' @@ -872,6 +1081,25 @@ def test_chunked(self): self.assertEqual(resp.read(), expected) resp.close() + # Explicit full read + for n in (-123, -1, None): + with self.subTest('full read', n=n): + sock = FakeSocket(chunked_start + last_chunk + chunked_end) + resp = client.HTTPResponse(sock, method="GET") + resp.begin() + self.assertTrue(resp.chunked) + self.assertEqual(resp.read(n), expected) + resp.close() + + # Read first chunk + with self.subTest('read1(-1)'): + sock = FakeSocket(chunked_start + last_chunk + chunked_end) + resp = client.HTTPResponse(sock, method="GET") + resp.begin() + self.assertTrue(resp.chunked) + self.assertEqual(resp.read1(-1), b"hello worl") + resp.close() + # Various read sizes for n in range(1, 12): sock = FakeSocket(chunked_start + last_chunk + chunked_end) @@ -1227,6 +1455,72 @@ def run_server(): thread.join() self.assertEqual(result, b"proxied data\n") + def test_large_content_length(self): + serv = socket.create_server((HOST, 0)) + self.addCleanup(serv.close) + + def run_server(): + [conn, address] = serv.accept() + with conn: + while conn.recv(1024): + conn.sendall( + b"HTTP/1.1 200 Ok\r\n" + b"Content-Length: %d\r\n" + b"\r\n" % size) + conn.sendall(b'A' * (size//3)) + conn.sendall(b'B' * (size - size//3)) + + thread = threading.Thread(target=run_server) + thread.start() + self.addCleanup(thread.join, 1.0) + + conn = client.HTTPConnection(*serv.getsockname()) + try: + for w in range(15, 27): + size = 1 << w + conn.request("GET", "/") + with conn.getresponse() as response: + self.assertEqual(len(response.read()), size) + finally: + conn.close() + thread.join(1.0) + + def test_large_content_length_truncated(self): + serv = socket.create_server((HOST, 0)) + self.addCleanup(serv.close) + + def run_server(): + while True: + [conn, address] = serv.accept() + with conn: + conn.recv(1024) + if not size: + break + conn.sendall( + b"HTTP/1.1 200 Ok\r\n" + b"Content-Length: %d\r\n" + b"\r\n" + b"Text" % size) + + thread = threading.Thread(target=run_server) + thread.start() + self.addCleanup(thread.join, 1.0) + + conn = client.HTTPConnection(*serv.getsockname()) + try: + for w in range(18, 65): + size = 1 << w + conn.request("GET", "/") + with conn.getresponse() as response: + self.assertRaises(client.IncompleteRead, response.read) + conn.close() + finally: + conn.close() + size = 0 + conn.request("GET", "/") + conn.close() + thread.join(1.0) + def test_putrequest_override_domain_validation(self): """ It should be possible to override the default validation @@ -1266,7 +1560,7 @@ def _encode_request(self, str_url): conn.putrequest('GET', '/☃') -class ExtendedReadTest(TestCase): +class ExtendedReadTest(TestCase, ExtraAssertions): """ Test peek(), read1(), readline() """ @@ -1325,7 +1619,7 @@ def mypeek(n=-1): # then unbounded peek p2 = resp.peek() self.assertGreaterEqual(len(p2), len(p)) - self.assertTrue(p2.startswith(p)) + self.assertStartsWith(p2, p) next = resp.read(len(p2)) self.assertEqual(next, p2) else: @@ -1340,18 +1634,22 @@ def test_readline(self): resp = self.resp self._verify_readline(self.resp.readline, self.lines_expected) - def _verify_readline(self, readline, expected): + def test_readline_without_limit(self): + self._verify_readline(self.resp.readline, self.lines_expected, limit=-1) + + def _verify_readline(self, readline, expected, limit=5): all = [] while True: # short readlines - line = readline(5) + line = readline(limit) if line and line != b"foo": if len(line) < 5: - self.assertTrue(line.endswith(b"\n")) + self.assertEndsWith(line, b"\n") all.append(line) if not line: break self.assertEqual(b"".join(all), expected) + self.assertTrue(self.resp.isclosed()) def test_read1(self): resp = self.resp @@ -1371,6 +1669,7 @@ def test_read1_unbounded(self): break all.append(data) self.assertEqual(b"".join(all), self.lines_expected) + self.assertTrue(resp.isclosed()) def test_read1_bounded(self): resp = self.resp @@ -1382,15 +1681,22 @@ def test_read1_bounded(self): self.assertLessEqual(len(data), 10) all.append(data) self.assertEqual(b"".join(all), self.lines_expected) + self.assertTrue(resp.isclosed()) def test_read1_0(self): self.assertEqual(self.resp.read1(0), b"") + self.assertFalse(self.resp.isclosed()) def test_peek_0(self): p = self.resp.peek(0) self.assertLessEqual(0, len(p)) +class ExtendedReadTestContentLengthKnown(ExtendedReadTest): + _header, _body = ExtendedReadTest.lines.split('\r\n\r\n', 1) + lines = _header + f'\r\nContent-Length: {len(_body)}\r\n\r\n' + _body + + class ExtendedReadTestChunked(ExtendedReadTest): """ Test peek(), read1(), readline() in chunked mode @@ -1447,7 +1753,7 @@ def readline(self, limit): raise -class OfflineTest(TestCase): +class OfflineTest(TestCase, ExtraAssertions): def test_all(self): # Documented objects defined in the module should be in __all__ expected = {"responses"} # Allowlist documented dict() object @@ -1500,13 +1806,17 @@ def test_client_constants(self): 'GONE', 'LENGTH_REQUIRED', 'PRECONDITION_FAILED', + 'CONTENT_TOO_LARGE', 'REQUEST_ENTITY_TOO_LARGE', + 'URI_TOO_LONG', 'REQUEST_URI_TOO_LONG', 'UNSUPPORTED_MEDIA_TYPE', + 'RANGE_NOT_SATISFIABLE', 'REQUESTED_RANGE_NOT_SATISFIABLE', 'EXPECTATION_FAILED', 'IM_A_TEAPOT', 'MISDIRECTED_REQUEST', + 'UNPROCESSABLE_CONTENT', 'UNPROCESSABLE_ENTITY', 'LOCKED', 'FAILED_DEPENDENCY', @@ -1529,7 +1839,7 @@ def test_client_constants(self): ] for const in expected: with self.subTest(constant=const): - self.assertTrue(hasattr(client, const)) + self.assertHasAttr(client, const) class SourceAddressTest(TestCase): @@ -1766,6 +2076,7 @@ def test_networked_good_cert(self): h.close() self.assertIn('nginx', server_string) + @support.requires_resource('walltime') def test_networked_bad_cert(self): # We feed a "CA" cert that is unrelated to the server's cert import ssl @@ -1778,7 +2089,6 @@ def test_networked_bad_cert(self): h.request('GET', '/') self.assertEqual(exc_info.exception.reason, 'CERTIFICATE_VERIFY_FAILED') - @unittest.skipIf(sys.platform == 'darwin', 'Occasionally success on macOS') def test_local_unknown_cert(self): # The custom cert isn't known to the default trust bundle import ssl @@ -1788,8 +2098,9 @@ def test_local_unknown_cert(self): h.request('GET', '/') self.assertEqual(exc_info.exception.reason, 'CERTIFICATE_VERIFY_FAILED') + @unittest.expectedFailure # TODO: RUSTPYTHON http.client.RemoteDisconnected: Remote end closed connection without response def test_local_good_hostname(self): - # The (valid) cert validates the HTTP hostname + # The (valid) cert validates the HTTPS hostname import ssl server = self.make_server(CERT_localhost) context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -1801,8 +2112,9 @@ def test_local_good_hostname(self): self.addCleanup(resp.close) self.assertEqual(resp.status, 404) + @unittest.expectedFailure # TODO: RUSTPYTHON http.client.RemoteDisconnected: Remote end closed connection without response def test_local_bad_hostname(self): - # The (valid) cert doesn't validate the HTTP hostname + # The (valid) cert doesn't validate the HTTPS hostname import ssl server = self.make_server(CERT_fakehostname) context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -1810,38 +2122,21 @@ def test_local_bad_hostname(self): h = client.HTTPSConnection('localhost', server.port, context=context) with self.assertRaises(ssl.CertificateError): h.request('GET', '/') - # Same with explicit check_hostname=True - with warnings_helper.check_warnings(('', DeprecationWarning)): - h = client.HTTPSConnection('localhost', server.port, - context=context, check_hostname=True) + + # Same with explicit context.check_hostname=True + context.check_hostname = True + h = client.HTTPSConnection('localhost', server.port, context=context) with self.assertRaises(ssl.CertificateError): h.request('GET', '/') - # With check_hostname=False, the mismatching is ignored - context.check_hostname = False - with warnings_helper.check_warnings(('', DeprecationWarning)): - h = client.HTTPSConnection('localhost', server.port, - context=context, check_hostname=False) - h.request('GET', '/nonexistent') - resp = h.getresponse() - resp.close() - h.close() - self.assertEqual(resp.status, 404) - # The context's check_hostname setting is used if one isn't passed to - # HTTPSConnection. + + # With context.check_hostname=False, the mismatching is ignored context.check_hostname = False h = client.HTTPSConnection('localhost', server.port, context=context) h.request('GET', '/nonexistent') resp = h.getresponse() - self.assertEqual(resp.status, 404) resp.close() h.close() - # Passing check_hostname to HTTPSConnection should override the - # context's setting. - with warnings_helper.check_warnings(('', DeprecationWarning)): - h = client.HTTPSConnection('localhost', server.port, - context=context, check_hostname=True) - with self.assertRaises(ssl.CertificateError): - h.request('GET', '/') + self.assertEqual(resp.status, 404) @unittest.skipIf(not hasattr(client, 'HTTPSConnection'), 'http.client.HTTPSConnection not available') @@ -1877,11 +2172,9 @@ def test_tls13_pha(self): self.assertIs(h._context, context) self.assertFalse(h._context.post_handshake_auth) - with warnings.catch_warnings(): - warnings.filterwarnings('ignore', 'key_file, cert_file and check_hostname are deprecated', - DeprecationWarning) - h = client.HTTPSConnection('localhost', 443, context=context, - cert_file=CERT_localhost) + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT, cert_file=CERT_localhost) + context.post_handshake_auth = True + h = client.HTTPSConnection('localhost', 443, context=context) self.assertTrue(h._context.post_handshake_auth) @@ -2016,14 +2309,15 @@ def test_getting_header_defaultint(self): header = self.resp.getheader('No-Such-Header',default=42) self.assertEqual(header, 42) -class TunnelTests(TestCase): +class TunnelTests(TestCase, ExtraAssertions): def setUp(self): response_text = ( - 'HTTP/1.0 200 OK\r\n\r\n' # Reply to CONNECT + 'HTTP/1.1 200 OK\r\n\r\n' # Reply to CONNECT 'HTTP/1.1 200 OK\r\n' # Reply to HEAD 'Content-Length: 42\r\n\r\n' ) self.host = 'proxy.com' + self.port = client.HTTP_PORT self.conn = client.HTTPConnection(self.host) self.conn._create_connection = self._create_connection(response_text) @@ -2035,15 +2329,45 @@ def create_connection(address, timeout=None, source_address=None): return FakeSocket(response_text, host=address[0], port=address[1]) return create_connection - def test_set_tunnel_host_port_headers(self): + def test_set_tunnel_host_port_headers_add_host_missing(self): tunnel_host = 'destination.com' tunnel_port = 8888 tunnel_headers = {'User-Agent': 'Mozilla/5.0 (compatible, MSIE 11)'} + tunnel_headers_after = tunnel_headers.copy() + tunnel_headers_after['Host'] = '%s:%d' % (tunnel_host, tunnel_port) self.conn.set_tunnel(tunnel_host, port=tunnel_port, headers=tunnel_headers) self.conn.request('HEAD', '/', '') self.assertEqual(self.conn.sock.host, self.host) - self.assertEqual(self.conn.sock.port, client.HTTP_PORT) + self.assertEqual(self.conn.sock.port, self.port) + self.assertEqual(self.conn._tunnel_host, tunnel_host) + self.assertEqual(self.conn._tunnel_port, tunnel_port) + self.assertEqual(self.conn._tunnel_headers, tunnel_headers_after) + + def test_set_tunnel_host_port_headers_set_host_identical(self): + tunnel_host = 'destination.com' + tunnel_port = 8888 + tunnel_headers = {'User-Agent': 'Mozilla/5.0 (compatible, MSIE 11)', + 'Host': '%s:%d' % (tunnel_host, tunnel_port)} + self.conn.set_tunnel(tunnel_host, port=tunnel_port, + headers=tunnel_headers) + self.conn.request('HEAD', '/', '') + self.assertEqual(self.conn.sock.host, self.host) + self.assertEqual(self.conn.sock.port, self.port) + self.assertEqual(self.conn._tunnel_host, tunnel_host) + self.assertEqual(self.conn._tunnel_port, tunnel_port) + self.assertEqual(self.conn._tunnel_headers, tunnel_headers) + + def test_set_tunnel_host_port_headers_set_host_different(self): + tunnel_host = 'destination.com' + tunnel_port = 8888 + tunnel_headers = {'User-Agent': 'Mozilla/5.0 (compatible, MSIE 11)', + 'Host': '%s:%d' % ('example.com', 4200)} + self.conn.set_tunnel(tunnel_host, port=tunnel_port, + headers=tunnel_headers) + self.conn.request('HEAD', '/', '') + self.assertEqual(self.conn.sock.host, self.host) + self.assertEqual(self.conn.sock.port, self.port) self.assertEqual(self.conn._tunnel_host, tunnel_host) self.assertEqual(self.conn._tunnel_port, tunnel_port) self.assertEqual(self.conn._tunnel_headers, tunnel_headers) @@ -2055,17 +2379,96 @@ def test_disallow_set_tunnel_after_connect(self): 'destination.com') def test_connect_with_tunnel(self): - self.conn.set_tunnel('destination.com') + d = { + b'host': b'destination.com', + b'port': client.HTTP_PORT, + } + self.conn.set_tunnel(d[b'host'].decode('ascii')) + self.conn.request('HEAD', '/', '') + self.assertEqual(self.conn.sock.host, self.host) + self.assertEqual(self.conn.sock.port, self.port) + self.assertIn(b'CONNECT %(host)s:%(port)d HTTP/1.1\r\n' + b'Host: %(host)s:%(port)d\r\n\r\n' % d, + self.conn.sock.data) + self.assertIn(b'HEAD / HTTP/1.1\r\nHost: %(host)s\r\n' % d, + self.conn.sock.data) + + def test_connect_with_tunnel_with_default_port(self): + d = { + b'host': b'destination.com', + b'port': client.HTTP_PORT, + } + self.conn.set_tunnel(d[b'host'].decode('ascii'), port=d[b'port']) + self.conn.request('HEAD', '/', '') + self.assertEqual(self.conn.sock.host, self.host) + self.assertEqual(self.conn.sock.port, self.port) + self.assertIn(b'CONNECT %(host)s:%(port)d HTTP/1.1\r\n' + b'Host: %(host)s:%(port)d\r\n\r\n' % d, + self.conn.sock.data) + self.assertIn(b'HEAD / HTTP/1.1\r\nHost: %(host)s\r\n' % d, + self.conn.sock.data) + + def test_connect_with_tunnel_with_nonstandard_port(self): + d = { + b'host': b'destination.com', + b'port': 8888, + } + self.conn.set_tunnel(d[b'host'].decode('ascii'), port=d[b'port']) + self.conn.request('HEAD', '/', '') + self.assertEqual(self.conn.sock.host, self.host) + self.assertEqual(self.conn.sock.port, self.port) + self.assertIn(b'CONNECT %(host)s:%(port)d HTTP/1.1\r\n' + b'Host: %(host)s:%(port)d\r\n\r\n' % d, + self.conn.sock.data) + self.assertIn(b'HEAD / HTTP/1.1\r\nHost: %(host)s:%(port)d\r\n' % d, + self.conn.sock.data) + + # This request is not RFC-valid, but it's been possible with the library + # for years, so don't break it unexpectedly... This also tests + # case-insensitivity when injecting Host: headers if they're missing. + def test_connect_with_tunnel_with_different_host_header(self): + d = { + b'host': b'destination.com', + b'tunnel_host_header': b'example.com:9876', + b'port': client.HTTP_PORT, + } + self.conn.set_tunnel( + d[b'host'].decode('ascii'), + headers={'HOST': d[b'tunnel_host_header'].decode('ascii')}) + self.conn.request('HEAD', '/', '') + self.assertEqual(self.conn.sock.host, self.host) + self.assertEqual(self.conn.sock.port, self.port) + self.assertIn(b'CONNECT %(host)s:%(port)d HTTP/1.1\r\n' + b'HOST: %(tunnel_host_header)s\r\n\r\n' % d, + self.conn.sock.data) + self.assertIn(b'HEAD / HTTP/1.1\r\nHost: %(host)s\r\n' % d, + self.conn.sock.data) + + def test_connect_with_tunnel_different_host(self): + d = { + b'host': b'destination.com', + b'port': client.HTTP_PORT, + } + self.conn.set_tunnel(d[b'host'].decode('ascii')) + self.conn.request('HEAD', '/', '') + self.assertEqual(self.conn.sock.host, self.host) + self.assertEqual(self.conn.sock.port, self.port) + self.assertIn(b'CONNECT %(host)s:%(port)d HTTP/1.1\r\n' + b'Host: %(host)s:%(port)d\r\n\r\n' % d, + self.conn.sock.data) + self.assertIn(b'HEAD / HTTP/1.1\r\nHost: %(host)s\r\n' % d, + self.conn.sock.data) + + def test_connect_with_tunnel_idna(self): + dest = '\u03b4\u03c0\u03b8.gr' + dest_port = b'%s:%d' % (dest.encode('idna'), client.HTTP_PORT) + expected = b'CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n' % ( + dest_port, dest_port) + self.conn.set_tunnel(dest) self.conn.request('HEAD', '/', '') self.assertEqual(self.conn.sock.host, self.host) self.assertEqual(self.conn.sock.port, client.HTTP_PORT) - self.assertIn(b'CONNECT destination.com', self.conn.sock.data) - # issue22095 - self.assertNotIn(b'Host: destination.com:None', self.conn.sock.data) - self.assertIn(b'Host: destination.com', self.conn.sock.data) - - # This test should be removed when CONNECT gets the HTTP/1.1 blessing - self.assertNotIn(b'Host: proxy.com', self.conn.sock.data) + self.assertIn(expected, self.conn.sock.data) def test_tunnel_connect_single_send_connection_setup(self): """Regresstion test for https://bugs.python.org/issue43332.""" @@ -2080,17 +2483,39 @@ def test_tunnel_connect_single_send_connection_setup(self): msg=f'unexpected number of send calls: {mock_send.mock_calls}') proxy_setup_data_sent = mock_send.mock_calls[0][1][0] self.assertIn(b'CONNECT destination.com', proxy_setup_data_sent) - self.assertTrue( - proxy_setup_data_sent.endswith(b'\r\n\r\n'), + self.assertEndsWith(proxy_setup_data_sent, b'\r\n\r\n', msg=f'unexpected proxy data sent {proxy_setup_data_sent!r}') def test_connect_put_request(self): - self.conn.set_tunnel('destination.com') + d = { + b'host': b'destination.com', + b'port': client.HTTP_PORT, + } + self.conn.set_tunnel(d[b'host'].decode('ascii')) + self.conn.request('PUT', '/', '') + self.assertEqual(self.conn.sock.host, self.host) + self.assertEqual(self.conn.sock.port, self.port) + self.assertIn(b'CONNECT %(host)s:%(port)d HTTP/1.1\r\n' + b'Host: %(host)s:%(port)d\r\n\r\n' % d, + self.conn.sock.data) + self.assertIn(b'PUT / HTTP/1.1\r\nHost: %(host)s\r\n' % d, + self.conn.sock.data) + + def test_connect_put_request_ipv6(self): + self.conn.set_tunnel('[1:2:3::4]', 1234) + self.conn.request('PUT', '/', '') + self.assertEqual(self.conn.sock.host, self.host) + self.assertEqual(self.conn.sock.port, client.HTTP_PORT) + self.assertIn(b'CONNECT [1:2:3::4]:1234', self.conn.sock.data) + self.assertIn(b'Host: [1:2:3::4]:1234', self.conn.sock.data) + + def test_connect_put_request_ipv6_port(self): + self.conn.set_tunnel('[1:2:3::4]:1234') self.conn.request('PUT', '/', '') self.assertEqual(self.conn.sock.host, self.host) self.assertEqual(self.conn.sock.port, client.HTTP_PORT) - self.assertIn(b'CONNECT destination.com', self.conn.sock.data) - self.assertIn(b'Host: destination.com', self.conn.sock.data) + self.assertIn(b'CONNECT [1:2:3::4]:1234', self.conn.sock.data) + self.assertIn(b'Host: [1:2:3::4]:1234', self.conn.sock.data) def test_tunnel_debuglog(self): expected_header = 'X-Dummy: 1' @@ -2105,6 +2530,56 @@ def test_tunnel_debuglog(self): lines = output.getvalue().splitlines() self.assertIn('header: {}'.format(expected_header), lines) + def test_proxy_response_headers(self): + expected_header = ('X-Dummy', '1') + response_text = ( + 'HTTP/1.0 200 OK\r\n' + '{0}\r\n\r\n'.format(':'.join(expected_header)) + ) + + self.conn._create_connection = self._create_connection(response_text) + self.conn.set_tunnel('destination.com') + + self.conn.request('PUT', '/', '') + headers = self.conn.get_proxy_response_headers() + self.assertIn(expected_header, headers.items()) + + def test_no_proxy_response_headers(self): + expected_header = ('X-Dummy', '1') + response_text = ( + 'HTTP/1.0 200 OK\r\n' + '{0}\r\n\r\n'.format(':'.join(expected_header)) + ) + + self.conn._create_connection = self._create_connection(response_text) + + self.conn.request('PUT', '/', '') + headers = self.conn.get_proxy_response_headers() + self.assertIsNone(headers) + + def test_tunnel_leak(self): + sock = None + + def _create_connection(address, timeout=None, source_address=None): + nonlocal sock + sock = FakeSocket( + 'HTTP/1.1 404 NOT FOUND\r\n\r\n', + host=address[0], + port=address[1], + ) + return sock + + self.conn._create_connection = _create_connection + self.conn.set_tunnel('destination.com') + exc = None + try: + self.conn.request('HEAD', '/', '') + except OSError as e: + # keeping a reference to exc keeps response alive in the traceback + exc = e + self.assertIsNotNone(exc) + self.assertTrue(sock.file_closed) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index cd689492ca3..63b778d8b97 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -8,6 +8,7 @@ SimpleHTTPRequestHandler, CGIHTTPRequestHandler from http import server, HTTPStatus +import contextlib import os import socket import sys @@ -26,13 +27,16 @@ import datetime import threading from unittest import mock -from io import BytesIO +from io import BytesIO, StringIO import unittest from test import support -from test.support import os_helper -from test.support import threading_helper +from test.support import ( + is_apple, os_helper, requires_subprocess, threading_helper +) +from test.support.testcase import ExtraAssertions +support.requires_working_socket(module=True) class NoLogRequestHandler: def log_message(self, *args): @@ -64,7 +68,7 @@ def stop(self): self.join() -class BaseTestCase(unittest.TestCase): +class BaseTestCase(unittest.TestCase, ExtraAssertions): def setUp(self): self._threads = threading_helper.threading_setup() os.environ = os_helper.EnvironmentVarGuard() @@ -163,6 +167,27 @@ def test_version_digits(self): res = self.con.getresponse() self.assertEqual(res.status, HTTPStatus.BAD_REQUEST) + def test_version_signs_and_underscores(self): + self.con._http_vsn_str = 'HTTP/-9_9_9.+9_9_9' + self.con.putrequest('GET', '/') + self.con.endheaders() + res = self.con.getresponse() + self.assertEqual(res.status, HTTPStatus.BAD_REQUEST) + + def test_major_version_number_too_long(self): + self.con._http_vsn_str = 'HTTP/909876543210.0' + self.con.putrequest('GET', '/') + self.con.endheaders() + res = self.con.getresponse() + self.assertEqual(res.status, HTTPStatus.BAD_REQUEST) + + def test_minor_version_number_too_long(self): + self.con._http_vsn_str = 'HTTP/1.909876543210' + self.con.putrequest('GET', '/') + self.con.endheaders() + res = self.con.getresponse() + self.assertEqual(res.status, HTTPStatus.BAD_REQUEST) + def test_version_none_get(self): self.con._http_vsn_str = '' self.con.putrequest('GET', '/') @@ -292,6 +317,44 @@ def test_head_via_send_error(self): self.assertEqual(b'', data) +class HTTP09ServerTestCase(BaseTestCase): + + class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler): + """Request handler for HTTP/0.9 server.""" + + def do_GET(self): + self.wfile.write(f'OK: here is {self.path}\r\n'.encode()) + + def setUp(self): + super().setUp() + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock = self.enterContext(self.sock) + self.sock.connect((self.HOST, self.PORT)) + + def test_simple_get(self): + self.sock.send(b'GET /index.html\r\n') + res = self.sock.recv(1024) + self.assertEqual(res, b"OK: here is /index.html\r\n") + + def test_invalid_request(self): + self.sock.send(b'POST /index.html\r\n') + res = self.sock.recv(1024) + self.assertIn(b"Bad HTTP/0.9 request type ('POST')", res) + + def test_single_request(self): + self.sock.send(b'GET /foo.html\r\n') + res = self.sock.recv(1024) + self.assertEqual(res, b"OK: here is /foo.html\r\n") + + # Ignore errors if the connection is already closed, + # as this is the expected behavior of HTTP/0.9. + with contextlib.suppress(OSError): + self.sock.send(b'GET /bar.html\r\n') + res = self.sock.recv(1024) + # The server should not process our request. + self.assertEqual(res, b'') + + class RequestHandlerLoggingTestCase(BaseTestCase): class request_handler(BaseHTTPRequestHandler): protocol_version = 'HTTP/1.1' @@ -312,8 +375,7 @@ def test_get(self): self.con.request('GET', '/') self.con.getresponse() - self.assertTrue( - err.getvalue().endswith('"GET / HTTP/1.1" 200 -\n')) + self.assertEndsWith(err.getvalue(), '"GET / HTTP/1.1" 200 -\n') def test_err(self): self.con = http.client.HTTPConnection(self.HOST, self.PORT) @@ -324,8 +386,8 @@ def test_err(self): self.con.getresponse() lines = err.getvalue().split('\n') - self.assertTrue(lines[0].endswith('code 404, message File not found')) - self.assertTrue(lines[1].endswith('"ERROR / HTTP/1.1" 404 -')) + self.assertEndsWith(lines[0], 'code 404, message File not found') + self.assertEndsWith(lines[1], '"ERROR / HTTP/1.1" 404 -') class SimpleHTTPServerTestCase(BaseTestCase): @@ -333,7 +395,7 @@ class request_handler(NoLogRequestHandler, SimpleHTTPRequestHandler): pass def setUp(self): - BaseTestCase.setUp(self) + super().setUp() self.cwd = os.getcwd() basetempdir = tempfile.gettempdir() os.chdir(basetempdir) @@ -361,7 +423,7 @@ def tearDown(self): except: pass finally: - BaseTestCase.tearDown(self) + super().tearDown() def check_status_and_reason(self, response, status, data=None): def close_conn(): @@ -388,35 +450,175 @@ def close_conn(): reader.close() return body - @unittest.skipIf(sys.platform == 'darwin', - 'undecodable name cannot always be decoded on macOS') - @unittest.skipIf(sys.platform == 'win32', - 'undecodable name cannot be decoded on win32') - @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE, - 'need os_helper.TESTFN_UNDECODABLE') - def test_undecodable_filename(self): + def check_list_dir_dirname(self, dirname, quotedname=None): + fullpath = os.path.join(self.tempdir, dirname) + try: + os.mkdir(os.path.join(self.tempdir, dirname)) + except (OSError, UnicodeEncodeError): + self.skipTest(f'Can not create directory {dirname!a} ' + f'on current file system') + + if quotedname is None: + quotedname = urllib.parse.quote(dirname, errors='surrogatepass') + response = self.request(self.base_url + '/' + quotedname + '/') + body = self.check_status_and_reason(response, HTTPStatus.OK) + displaypath = html.escape(f'{self.base_url}/{dirname}/', quote=False) enc = sys.getfilesystemencoding() - filename = os.fsdecode(os_helper.TESTFN_UNDECODABLE) + '.txt' - with open(os.path.join(self.tempdir, filename), 'wb') as f: - f.write(os_helper.TESTFN_UNDECODABLE) + prefix = f'listing for {displaypath}', body) + self.assertIn(prefix + b'h1>', body) + + def check_list_dir_filename(self, filename): + fullpath = os.path.join(self.tempdir, filename) + content = ascii(fullpath).encode() + (os_helper.TESTFN_UNDECODABLE or b'\xff') + try: + with open(fullpath, 'wb') as f: + f.write(content) + except OSError: + self.skipTest(f'Can not create file {filename!a} ' + f'on current file system') + response = self.request(self.base_url + '/') - if sys.platform == 'darwin': - # On Mac OS the HFS+ filesystem replaces bytes that aren't valid - # UTF-8 into a percent-encoded value. - for name in os.listdir(self.tempdir): - if name != 'test': # Ignore a filename created in setUp(). - filename = name - break body = self.check_status_and_reason(response, HTTPStatus.OK) quotedname = urllib.parse.quote(filename, errors='surrogatepass') - self.assertIn(('href="%s"' % quotedname) - .encode(enc, 'surrogateescape'), body) - self.assertIn(('>%s<' % html.escape(filename, quote=False)) - .encode(enc, 'surrogateescape'), body) + enc = response.headers.get_content_charset() + self.assertIsNotNone(enc) + self.assertIn((f'href="{quotedname}"').encode('ascii'), body) + displayname = html.escape(filename, quote=False) + self.assertIn(f'>{displayname}<'.encode(enc, 'surrogateescape'), body) + response = self.request(self.base_url + '/' + quotedname) - self.check_status_and_reason(response, HTTPStatus.OK, - data=os_helper.TESTFN_UNDECODABLE) + self.check_status_and_reason(response, HTTPStatus.OK, data=content) + + @unittest.skipUnless(os_helper.TESTFN_NONASCII, + 'need os_helper.TESTFN_NONASCII') + def test_list_dir_nonascii_dirname(self): + dirname = os_helper.TESTFN_NONASCII + '.dir' + self.check_list_dir_dirname(dirname) + + @unittest.skipUnless(os_helper.TESTFN_NONASCII, + 'need os_helper.TESTFN_NONASCII') + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response + def test_list_dir_nonascii_filename(self): + filename = os_helper.TESTFN_NONASCII + '.txt' + self.check_list_dir_filename(filename) + + @unittest.skipIf(is_apple, + 'undecodable name cannot always be decoded on Apple platforms') + @unittest.skipIf(sys.platform == 'win32', + 'undecodable name cannot be decoded on win32') + @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE, + 'need os_helper.TESTFN_UNDECODABLE') + def test_list_dir_undecodable_dirname(self): + dirname = os.fsdecode(os_helper.TESTFN_UNDECODABLE) + '.dir' + self.check_list_dir_dirname(dirname) + + @unittest.skipIf(is_apple, + 'undecodable name cannot always be decoded on Apple platforms') + @unittest.skipIf(sys.platform == 'win32', + 'undecodable name cannot be decoded on win32') + @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE, + 'need os_helper.TESTFN_UNDECODABLE') + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response + def test_list_dir_undecodable_filename(self): + filename = os.fsdecode(os_helper.TESTFN_UNDECODABLE) + '.txt' + self.check_list_dir_filename(filename) + + def test_list_dir_undecodable_dirname2(self): + dirname = '\ufffd.dir' + self.check_list_dir_dirname(dirname, quotedname='%ff.dir') + + @unittest.skipUnless(os_helper.TESTFN_UNENCODABLE, + 'need os_helper.TESTFN_UNENCODABLE') + def test_list_dir_unencodable_dirname(self): + dirname = os_helper.TESTFN_UNENCODABLE + '.dir' + self.check_list_dir_dirname(dirname) + + @unittest.skipUnless(os_helper.TESTFN_UNENCODABLE, + 'need os_helper.TESTFN_UNENCODABLE') + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response + def test_list_dir_unencodable_filename(self): + filename = os_helper.TESTFN_UNENCODABLE + '.txt' + self.check_list_dir_filename(filename) + + def test_list_dir_escape_dirname(self): + # Characters that need special treating in URL or HTML. + for name in ('q?', 'f#', '&', '&', '', '"dq"', "'sq'", + '%A4', '%E2%82%AC'): + with self.subTest(name=name): + dirname = name + '.dir' + self.check_list_dir_dirname(dirname, + quotedname=urllib.parse.quote(dirname, safe='&<>\'"')) + + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response + def test_list_dir_escape_filename(self): + # Characters that need special treating in URL or HTML. + for name in ('q?', 'f#', '&', '&', '', '"dq"', "'sq'", + '%A4', '%E2%82%AC'): + with self.subTest(name=name): + filename = name + '.txt' + self.check_list_dir_filename(filename) + os_helper.unlink(os.path.join(self.tempdir, filename)) + + def test_list_dir_with_query_and_fragment(self): + prefix = f'listing for {self.base_url}/', response) + self.assertIn(prefix + b'h1>', response) + response = self.request(self.base_url + '/?x=123').read() + self.assertIn(prefix + b'title>', response) + self.assertIn(prefix + b'h1>', response) + + def test_get_dir_redirect_location_domain_injection_bug(self): + """Ensure //evil.co/..%2f../../X does not put //evil.co/ in Location. + + //netloc/ in a Location header is a redirect to a new host. + https://github.com/python/cpython/issues/87389 + + This checks that a path resolving to a directory on our server cannot + resolve into a redirect to another server. + """ + os.mkdir(os.path.join(self.tempdir, 'existing_directory')) + url = f'/python.org/..%2f..%2f..%2f..%2f..%2f../%0a%0d/../{self.tempdir_name}/existing_directory' + expected_location = f'{url}/' # /python.org.../ single slash single prefix, trailing slash + # Canonicalizes to /tmp/tempdir_name/existing_directory which does + # exist and is a dir, triggering the 301 redirect logic. + response = self.request(url) + self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY) + location = response.getheader('Location') + self.assertEqual(location, expected_location, msg='non-attack failed!') + # //python.org... multi-slash prefix, no trailing slash + attack_url = f'/{url}' + response = self.request(attack_url) + self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY) + location = response.getheader('Location') + self.assertNotStartsWith(location, '//') + self.assertEqual(location, expected_location, + msg='Expected Location header to start with a single / and ' + 'end with a / as this is a directory redirect.') + + # ///python.org... triple-slash prefix, no trailing slash + attack3_url = f'//{url}' + response = self.request(attack3_url) + self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY) + self.assertEqual(response.getheader('Location'), expected_location) + + # If the second word in the http request (Request-URI for the http + # method) is a full URI, we don't worry about it, as that'll be parsed + # and reassembled as a full URI within BaseHTTPRequestHandler.send_head + # so no errant scheme-less //netloc//evil.co/ domain mixup can happen. + attack_scheme_netloc_2slash_url = f'https://pypi.org/{url}' + expected_scheme_netloc_location = f'{attack_scheme_netloc_2slash_url}/' + response = self.request(attack_scheme_netloc_2slash_url) + self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY) + location = response.getheader('Location') + # We're just ensuring that the scheme and domain make it through, if + # there are or aren't multiple slashes at the start of the path that + # follows that isn't important in this Location: header. + self.assertStartsWith(location, 'https://pypi.org/') + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_get(self): #constructs the path relative to the root directory of the HTTPServer response = self.request(self.base_url + '/test') @@ -424,10 +626,19 @@ def test_get(self): # check for trailing "/" which should return 404. See Issue17324 response = self.request(self.base_url + '/test/') self.check_status_and_reason(response, HTTPStatus.NOT_FOUND) + response = self.request(self.base_url + '/test%2f') + self.check_status_and_reason(response, HTTPStatus.NOT_FOUND) + response = self.request(self.base_url + '/test%2F') + self.check_status_and_reason(response, HTTPStatus.NOT_FOUND) response = self.request(self.base_url + '/') self.check_status_and_reason(response, HTTPStatus.OK) + response = self.request(self.base_url + '%2f') + self.check_status_and_reason(response, HTTPStatus.OK) + response = self.request(self.base_url + '%2F') + self.check_status_and_reason(response, HTTPStatus.OK) response = self.request(self.base_url) self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY) + self.assertEqual(response.getheader("Location"), self.base_url + "/") self.assertEqual(response.getheader("Content-Length"), "0") response = self.request(self.base_url + '/?hi=2') self.check_status_and_reason(response, HTTPStatus.OK) @@ -439,6 +650,9 @@ def test_get(self): self.check_status_and_reason(response, HTTPStatus.NOT_FOUND) response = self.request('/' + 'ThisDoesNotExist' + '/') self.check_status_and_reason(response, HTTPStatus.NOT_FOUND) + os.makedirs(os.path.join(self.tempdir, 'spam', 'index.html')) + response = self.request(self.base_url + '/spam/') + self.check_status_and_reason(response, HTTPStatus.OK) data = b"Dummy index file\r\n" with open(os.path.join(self.tempdir_name, 'index.html'), 'wb') as f: @@ -456,6 +670,7 @@ def test_get(self): finally: os.chmod(self.tempdir, 0o755) + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response def test_head(self): response = self.request( self.base_url + '/test', method='HEAD') @@ -465,6 +680,7 @@ def test_head(self): self.assertEqual(response.getheader('content-type'), 'application/octet-stream') + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response def test_browser_cache(self): """Check that when a request to /test is sent with the request header If-Modified-Since set to date of last modification, the server returns @@ -483,6 +699,7 @@ def test_browser_cache(self): response = self.request(self.base_url + '/test', headers=headers) self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED) + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response def test_browser_cache_file_changed(self): # with If-Modified-Since earlier than Last-Modified, must return 200 dt = self.last_modif_datetime @@ -494,6 +711,7 @@ def test_browser_cache_file_changed(self): response = self.request(self.base_url + '/test', headers=headers) self.check_status_and_reason(response, HTTPStatus.OK) + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response def test_browser_cache_with_If_None_Match_header(self): # if If-None-Match header is present, ignore If-Modified-Since @@ -512,6 +730,7 @@ def test_invalid_requests(self): response = self.request('/', method='GETs') self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED) + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response def test_last_modified(self): """Checks that the datetime returned in Last-Modified response header is the actual datetime of last modification, rounded to the second @@ -521,6 +740,7 @@ def test_last_modified(self): last_modif_header = response.headers['Last-modified'] self.assertEqual(last_modif_header, self.last_modif_header) + @unittest.expectedFailure # TODO: RUSTPYTHON; http.client.RemoteDisconnected: Remote end closed connection without response def test_path_without_leading_slash(self): response = self.request(self.tempdir_name + '/test') self.check_status_and_reason(response, HTTPStatus.OK, data=self.data) @@ -530,6 +750,8 @@ def test_path_without_leading_slash(self): self.check_status_and_reason(response, HTTPStatus.OK) response = self.request(self.tempdir_name) self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY) + self.assertEqual(response.getheader("Location"), + self.tempdir_name + "/") response = self.request(self.tempdir_name + '/?hi=2') self.check_status_and_reason(response, HTTPStatus.OK) response = self.request(self.tempdir_name + '?hi=1') @@ -537,27 +759,6 @@ def test_path_without_leading_slash(self): self.assertEqual(response.getheader("Location"), self.tempdir_name + "/?hi=1") - def test_html_escape_filename(self): - filename = '.txt' - fullpath = os.path.join(self.tempdir, filename) - - try: - open(fullpath, 'wb').close() - except OSError: - raise unittest.SkipTest('Can not create file %s on current file ' - 'system' % filename) - - try: - response = self.request(self.base_url + '/') - body = self.check_status_and_reason(response, HTTPStatus.OK) - enc = response.headers.get_content_charset() - finally: - os.unlink(fullpath) # avoid affecting test_undecodable_filename - - self.assertIsNotNone(enc) - html_text = '>%s<' % html.escape(filename, quote=False) - self.assertIn(html_text.encode(enc), body) - cgi_file1 = """\ #!%s @@ -569,14 +770,19 @@ def test_html_escape_filename(self): cgi_file2 = """\ #!%s -import cgi +import os +import sys +import urllib.parse print("Content-type: text/html") print() -form = cgi.FieldStorage() -print("%%s, %%s, %%s" %% (form.getfirst("spam"), form.getfirst("eggs"), - form.getfirst("bacon"))) +content_length = int(os.environ["CONTENT_LENGTH"]) +query_string = sys.stdin.buffer.read(content_length) +params = {key.decode("utf-8"): val.decode("utf-8") + for key, val in urllib.parse.parse_qsl(query_string)} + +print("%%s, %%s, %%s" %% (params["spam"], params["eggs"], params["bacon"])) """ cgi_file4 = """\ @@ -607,17 +813,40 @@ def test_html_escape_filename(self): print("") """ -@unittest.skipIf(not hasattr(os, '_exit'), - "TODO: RUSTPYTHON, run_cgi in http/server.py gets stuck as os._exit(127) doesn't currently kill forked processes") +cgi_file7 = """\ +#!%s +import os +import sys + +print("Content-type: text/plain") +print() + +content_length = int(os.environ["CONTENT_LENGTH"]) +body = sys.stdin.buffer.read(content_length) + +print(f"{content_length} {len(body)}") +""" + + @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, "This test can't be run reliably as root (issue #13308).") +@requires_subprocess() class CGIHTTPServerTestCase(BaseTestCase): class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler): - pass + _test_case_self = None # populated by each setUp() method call. + + def __init__(self, *args, **kwargs): + with self._test_case_self.assertWarnsRegex( + DeprecationWarning, + r'http\.server\.CGIHTTPRequestHandler'): + # This context also happens to catch and silence the + # threading DeprecationWarning from os.fork(). + super().__init__(*args, **kwargs) linesep = os.linesep.encode('ascii') def setUp(self): + self.request_handler._test_case_self = self # practical, but yuck. BaseTestCase.setUp(self) self.cwd = os.getcwd() self.parent_dir = tempfile.mkdtemp() @@ -637,12 +866,13 @@ def setUp(self): self.file3_path = None self.file4_path = None self.file5_path = None + self.file6_path = None + self.file7_path = None # The shebang line should be pure ASCII: use symlink if possible. # See issue #7668. self._pythonexe_symlink = None - # TODO: RUSTPYTHON; dl_nt not supported yet - if os_helper.can_symlink() and sys.platform != 'win32': + if os_helper.can_symlink(): self.pythonexe = os.path.join(self.parent_dir, 'python') self._pythonexe_symlink = support.PythonSymlink(self.pythonexe).__enter__() else: @@ -692,9 +922,15 @@ def setUp(self): file6.write(cgi_file6 % self.pythonexe) os.chmod(self.file6_path, 0o777) + self.file7_path = os.path.join(self.cgi_dir, 'file7.py') + with open(self.file7_path, 'w', encoding='utf-8') as file7: + file7.write(cgi_file7 % self.pythonexe) + os.chmod(self.file7_path, 0o777) + os.chdir(self.parent_dir) def tearDown(self): + self.request_handler._test_case_self = None try: os.chdir(self.cwd) if self._pythonexe_symlink: @@ -713,11 +949,16 @@ def tearDown(self): os.remove(self.file5_path) if self.file6_path: os.remove(self.file6_path) + if self.file7_path: + os.remove(self.file7_path) os.rmdir(self.cgi_child_dir) os.rmdir(self.cgi_dir) os.rmdir(self.cgi_dir_in_sub_dir) os.rmdir(self.sub_dir_2) os.rmdir(self.sub_dir_1) + # The 'gmon.out' file can be written in the current working + # directory if C-level code profiling with gprof is enabled. + os_helper.unlink(os.path.join(self.parent_dir, 'gmon.out')) os.rmdir(self.parent_dir) finally: BaseTestCase.tearDown(self) @@ -764,8 +1005,7 @@ def test_url_collapse_path(self): msg='path = %r\nGot: %r\nWanted: %r' % (path, actual, expected)) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON; works only on windows") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; AssertionError: Tuples differ: (b"", None, 200) != (b"Hello World\n", "text/html", )') def test_headers_and_content(self): res = self.request('/cgi-bin/file1.py') self.assertEqual( @@ -776,9 +1016,7 @@ def test_issue19435(self): res = self.request('///////////nocgi.py/../cgi-bin/nothere.sh') self.assertEqual(res.status, HTTPStatus.NOT_FOUND) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON; works only on windows") - @unittest.expectedFailure + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; b"" != b"1, python, 123456\n"') def test_post(self): params = urllib.parse.urlencode( {'spam' : 1, 'eggs' : 'python', 'bacon' : 123456}) @@ -787,13 +1025,30 @@ def test_post(self): self.assertEqual(res.read(), b'1, python, 123456' + self.linesep) + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; AssertionError: b"" != b"32768 32768\n"') + def test_large_content_length(self): + for w in range(15, 25): + size = 1 << w + body = b'X' * size + headers = {'Content-Length' : str(size)} + res = self.request('/cgi-bin/file7.py', 'POST', body, headers) + self.assertEqual(res.read(), b'%d %d' % (size, size) + self.linesep) + + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; AssertionError: b"" != b"Hello World\n"') + def test_large_content_length_truncated(self): + with support.swap_attr(self.request_handler, 'timeout', 0.001): + for w in range(18, 65): + size = 1 << w + headers = {'Content-Length' : str(size)} + res = self.request('/cgi-bin/file1.py', 'POST', b'x', headers) + self.assertEqual(res.read(), b'Hello World' + self.linesep) + def test_invaliduri(self): res = self.request('/cgi-bin/invalid') res.read() self.assertEqual(res.status, HTTPStatus.NOT_FOUND) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON; works only on windows") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; AssertionError: Tuples differ: (b"Hello World\n", "text/html", ) != (b"", None, 200)') def test_authorization(self): headers = {b'Authorization' : b'Basic ' + base64.b64encode(b'username:pass')} @@ -802,8 +1057,7 @@ def test_authorization(self): (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON; works only on windows") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; AssertionError: Tuples differ: (b"Hello World\n", "text/html", ) != (b"", None, 200)') def test_no_leading_slash(self): # http://bugs.python.org/issue2254 res = self.request('cgi-bin/file1.py') @@ -811,8 +1065,7 @@ def test_no_leading_slash(self): (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON; works only on windows") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; ValueError: signal only works in main thread') def test_os_environ_is_not_altered(self): signature = "Test CGI Server" os.environ['SERVER_SOFTWARE'] = signature @@ -822,32 +1075,28 @@ def test_os_environ_is_not_altered(self): (res.read(), res.getheader('Content-type'), res.status)) self.assertEqual(os.environ['SERVER_SOFTWARE'], signature) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON; works only on windows") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; ValueError: signal only works in main thread') def test_urlquote_decoding_in_cgi_check(self): res = self.request('/cgi-bin%2ffile1.py') self.assertEqual( (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON; works only on windows") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; AssertionError: Tuples differ: (b"Hello World\n", "text/html", ) != (b"", None, 200)') def test_nested_cgi_path_issue21323(self): res = self.request('/cgi-bin/child-dir/file3.py') self.assertEqual( (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON; works only on windows") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; ValueError: signal only works in main thread') def test_query_with_multiple_question_mark(self): res = self.request('/cgi-bin/file4.py?a=b?c=d') self.assertEqual( (b'a=b?c=d' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON; works only on windows") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; AssertionError: Tuples differ: (b"k=aa%2F%2Fbb&//q//p//=//a//b//\n", "text/html", ) != (b"", None, 200)') def test_query_with_continuous_slashes(self): res = self.request('/cgi-bin/file4.py?k=aa%2F%2Fbb&//q//p//=//a//b//') self.assertEqual( @@ -855,8 +1104,7 @@ def test_query_with_continuous_slashes(self): 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON; works only on windows") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; Tuples differ: (b"", None, 200) != (b"Hello World\n", "text/html", )') def test_cgi_path_in_sub_directories(self): try: CGIHTTPRequestHandler.cgi_directories.append('/sub/dir/cgi-bin') @@ -867,8 +1115,7 @@ def test_cgi_path_in_sub_directories(self): finally: CGIHTTPRequestHandler.cgi_directories.remove('/sub/dir/cgi-bin') - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON; works only on windows") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON; AssertionError: b"HTTP_ACCEPT=text/html,text/plain" not found in b""') def test_accept(self): browser_accept = \ 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' @@ -929,7 +1176,7 @@ def numWrites(self): return len(self.datas) -class BaseHTTPRequestHandlerTestCase(unittest.TestCase): +class BaseHTTPRequestHandlerTestCase(unittest.TestCase, ExtraAssertions): """Test the functionality of the BaseHTTPServer. Test the support for the Expect 100-continue header. @@ -960,6 +1207,27 @@ def verify_http_server_response(self, response): match = self.HTTPResponseMatch.search(response) self.assertIsNotNone(match) + def test_unprintable_not_logged(self): + # We call the method from the class directly as our Socketless + # Handler subclass overrode it... nice for everything BUT this test. + self.handler.client_address = ('127.0.0.1', 1337) + log_message = BaseHTTPRequestHandler.log_message + with mock.patch.object(sys, 'stderr', StringIO()) as fake_stderr: + log_message(self.handler, '/foo') + log_message(self.handler, '/\033bar\000\033') + log_message(self.handler, '/spam %s.', 'a') + log_message(self.handler, '/spam %s.', '\033\x7f\x9f\xa0beans') + log_message(self.handler, '"GET /foo\\b"ar\007 HTTP/1.0"') + stderr = fake_stderr.getvalue() + self.assertNotIn('\033', stderr) # non-printable chars are caught. + self.assertNotIn('\000', stderr) # non-printable chars are caught. + lines = stderr.splitlines() + self.assertIn('/foo', lines[0]) + self.assertIn(r'/\x1bbar\x00\x1b', lines[1]) + self.assertIn('/spam a.', lines[2]) + self.assertIn('/spam \\x1b\\x7f\\x9f\xa0beans.', lines[3]) + self.assertIn(r'"GET /foo\\b"ar\x07 HTTP/1.0"', lines[4]) + def test_http_1_1(self): result = self.send_typical_request(b'GET / HTTP/1.1\r\n\r\n') self.verify_http_server_response(result[0]) @@ -996,7 +1264,7 @@ def test_extra_space(self): b'Host: dummy\r\n' b'\r\n' ) - self.assertTrue(result[0].startswith(b'HTTP/1.1 400 ')) + self.assertStartsWith(result[0], b'HTTP/1.1 400 ') self.verify_expected_headers(result[1:result.index(b'\r\n')]) self.assertFalse(self.handler.get_called) @@ -1110,7 +1378,7 @@ def test_request_length(self): # Issue #10714: huge request lines are discarded, to avoid Denial # of Service attacks. result = self.send_typical_request(b'GET ' + b'x' * 65537) - self.assertEqual(result[0], b'HTTP/1.1 414 Request-URI Too Long\r\n') + self.assertEqual(result[0], b'HTTP/1.1 414 URI Too Long\r\n') self.assertFalse(self.handler.get_called) self.assertIsInstance(self.handler.requestline, str) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 8ea77d186e4..12b61e76423 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -736,6 +736,7 @@ def remove_loop(fname, tries): @threading_helper.requires_working_threading() @skip_if_asan_fork @skip_if_tsan_fork + @unittest.skip("TODO: RUSTPYTHON; Flaky") def test_post_fork_child_no_deadlock(self): """Ensure child logging locks are not held; bpo-6721 & bpo-36533.""" class _OurHandler(logging.Handler): diff --git a/Lib/test/test_robotparser.py b/Lib/test/test_robotparser.py index b0bed431d4b..89cabfe0083 100644 --- a/Lib/test/test_robotparser.py +++ b/Lib/test/test_robotparser.py @@ -259,6 +259,10 @@ class EmptyQueryStringTest(BaseRobotTest, unittest.TestCase): good = ['/some/path?'] bad = ['/another/path?'] + @unittest.expectedFailure # TODO: RUSTPYTHON; self.assertFalse(self.parser.can_fetch(agent, url))\nAssertionError: True is not false + def test_bad_urls(self): + super().test_bad_urls() + class DefaultEntryTest(BaseRequestRateTest, unittest.TestCase): robots_txt = """\ diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 5384e4caf69..9798a4f59c3 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -3525,6 +3525,7 @@ def test_starttls(self): else: s.close() + @unittest.expectedFailure # TODO: RUSTPYTHON def test_socketserver(self): """Using socketserver to create and manage SSL connections.""" server = make_https_server(self, certfile=SIGNED_CERTFILE) @@ -4596,7 +4597,7 @@ def server_callback(identity): with client_context.wrap_socket(socket.socket()) as s: s.connect((HOST, server.port)) - @unittest.skip("TODO: rustpython") + @unittest.skip("TODO: RUSTPYTHON; Hangs") def test_thread_recv_while_main_thread_sends(self): # GH-137583: Locking was added to calls to send() and recv() on SSL # socket objects. This seemed fine at the surface level because those diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py index aee9fb78017..7e3607842fd 100644 --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -1556,7 +1556,6 @@ def test_pathname2url_win(self): @unittest.skipIf(sys.platform == 'win32', 'test specific to POSIX pathnames') - @unittest.expectedFailure # AssertionError: '//a/b.c' != '////a/b.c' def test_pathname2url_posix(self): fn = urllib.request.pathname2url self.assertEqual(fn('/'), '/') @@ -1617,7 +1616,6 @@ def test_url2pathname_win(self): @unittest.skipIf(sys.platform == 'win32', 'test specific to POSIX pathnames') - @unittest.expectedFailure # AssertionError: '///foo/bar' != '/foo/bar' def test_url2pathname_posix(self): fn = urllib.request.url2pathname self.assertEqual(fn('/foo/bar'), '/foo/bar') diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index 399c94213a6..263472499d6 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -1,9 +1,11 @@ import unittest from test import support from test.support import os_helper -from test.support import socket_helper +from test.support import requires_subprocess from test.support import warnings_helper +from test.support.testcase import ExtraAssertions from test import test_urllib +from unittest import mock import os import io @@ -14,16 +16,19 @@ import subprocess import urllib.request -# The proxy bypass method imported below has logic specific to the OSX -# proxy config data structure but is testable on all platforms. +# The proxy bypass method imported below has logic specific to the +# corresponding system but is testable on all platforms. from urllib.request import (Request, OpenerDirector, HTTPBasicAuthHandler, HTTPPasswordMgrWithPriorAuth, _parse_proxy, + _proxy_bypass_winreg_override, _proxy_bypass_macosx_sysconf, AbstractDigestAuthHandler) from urllib.parse import urlparse import urllib.error import http.client +support.requires_working_socket(module=True) + # XXX # Request # CacheFTPHandler (hard to write) @@ -483,7 +488,18 @@ def build_test_opener(*handler_instances): return opener -class MockHTTPHandler(urllib.request.BaseHandler): +class MockHTTPHandler(urllib.request.HTTPHandler): + # Very simple mock HTTP handler with no special behavior other than using a mock HTTP connection + + def __init__(self, debuglevel=None): + super(MockHTTPHandler, self).__init__(debuglevel=debuglevel) + self.httpconn = MockHTTPClass() + + def http_open(self, req): + return self.do_open(self.httpconn, req) + + +class MockHTTPHandlerRedirect(urllib.request.BaseHandler): # useful for testing redirections and auth # sends supplied headers and code as first response # sends 200 OK as second response @@ -511,16 +527,17 @@ def http_open(self, req): return MockResponse(200, "OK", msg, "", req.get_full_url()) -class MockHTTPSHandler(urllib.request.AbstractHTTPHandler): - # Useful for testing the Proxy-Authorization request by verifying the - # properties of httpcon +if hasattr(http.client, 'HTTPSConnection'): + class MockHTTPSHandler(urllib.request.HTTPSHandler): + # Useful for testing the Proxy-Authorization request by verifying the + # properties of httpcon - def __init__(self, debuglevel=0): - urllib.request.AbstractHTTPHandler.__init__(self, debuglevel=debuglevel) - self.httpconn = MockHTTPClass() + def __init__(self, debuglevel=None, context=None, check_hostname=None): + super(MockHTTPSHandler, self).__init__(debuglevel, context, check_hostname) + self.httpconn = MockHTTPClass() - def https_open(self, req): - return self.do_open(self.httpconn, req) + def https_open(self, req): + return self.do_open(self.httpconn, req) class MockHTTPHandlerCheckAuth(urllib.request.BaseHandler): @@ -701,10 +718,6 @@ def test_processors(self): def sanepathname2url(path): - try: - path.encode("utf-8") - except UnicodeEncodeError: - raise unittest.SkipTest("path is not encodable to utf8") urlpath = urllib.request.pathname2url(path) if os.name == "nt" and urlpath.startswith("///"): urlpath = urlpath[2:] @@ -712,8 +725,9 @@ def sanepathname2url(path): return urlpath -class HandlerTests(unittest.TestCase): +class HandlerTests(unittest.TestCase, ExtraAssertions): + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: None != 'image/gif' def test_ftp(self): class MockFTPWrapper: def __init__(self, data): @@ -761,7 +775,7 @@ def connect_ftp(self, user, passwd, host, port, dirs, ["foo", "bar"], "", None), ("ftp://localhost/baz.gif;type=a", "localhost", ftplib.FTP_PORT, "", "", "A", - [], "baz.gif", None), # XXX really this should guess image/gif + [], "baz.gif", "image/gif"), ]: req = Request(url) req.timeout = None @@ -777,6 +791,7 @@ def connect_ftp(self, user, passwd, host, port, dirs, headers = r.info() self.assertEqual(headers.get("Content-type"), mimetype) self.assertEqual(int(headers["Content-length"]), len(data)) + r.close() def test_file(self): import email.utils @@ -984,6 +999,7 @@ def test_http_body_fileobj(self): file_obj.close() + @requires_subprocess() def test_http_body_pipe(self): # A file reading from a pipe. # A pipe cannot be seek'ed. There is no way to determine the @@ -1047,12 +1063,37 @@ def test_http_body_array(self): newreq = h.do_request_(req) self.assertEqual(int(newreq.get_header('Content-length')),16) - def test_http_handler_debuglevel(self): + def test_http_handler_global_debuglevel(self): + with mock.patch.object(http.client.HTTPConnection, 'debuglevel', 6): + o = OpenerDirector() + h = MockHTTPHandler() + o.add_handler(h) + o.open("http://www.example.com") + self.assertEqual(h._debuglevel, 6) + + def test_http_handler_local_debuglevel(self): o = OpenerDirector() - h = MockHTTPSHandler(debuglevel=1) + h = MockHTTPHandler(debuglevel=5) + o.add_handler(h) + o.open("http://www.example.com") + self.assertEqual(h._debuglevel, 5) + + @unittest.skipUnless(hasattr(http.client, 'HTTPSConnection'), 'HTTPSConnection required for HTTPS tests.') + def test_https_handler_global_debuglevel(self): + with mock.patch.object(http.client.HTTPSConnection, 'debuglevel', 7): + o = OpenerDirector() + h = MockHTTPSHandler() + o.add_handler(h) + o.open("https://www.example.com") + self.assertEqual(h._debuglevel, 7) + + @unittest.skipUnless(hasattr(http.client, 'HTTPSConnection'), 'HTTPSConnection required for HTTPS tests.') + def test_https_handler_local_debuglevel(self): + o = OpenerDirector() + h = MockHTTPSHandler(debuglevel=4) o.add_handler(h) o.open("https://www.example.com") - self.assertEqual(h._debuglevel, 1) + self.assertEqual(h._debuglevel, 4) def test_http_doubleslash(self): # Checks the presence of any unnecessary double slash in url does not @@ -1140,15 +1181,15 @@ def test_errors(self): r = MockResponse(200, "OK", {}, "", url) newr = h.http_response(req, r) self.assertIs(r, newr) - self.assertFalse(hasattr(o, "proto")) # o.error not called + self.assertNotHasAttr(o, "proto") # o.error not called r = MockResponse(202, "Accepted", {}, "", url) newr = h.http_response(req, r) self.assertIs(r, newr) - self.assertFalse(hasattr(o, "proto")) # o.error not called + self.assertNotHasAttr(o, "proto") # o.error not called r = MockResponse(206, "Partial content", {}, "", url) newr = h.http_response(req, r) self.assertIs(r, newr) - self.assertFalse(hasattr(o, "proto")) # o.error not called + self.assertNotHasAttr(o, "proto") # o.error not called # anything else calls o.error (and MockOpener returns None, here) r = MockResponse(502, "Bad gateway", {}, "", url) self.assertIsNone(h.http_response(req, r)) @@ -1179,7 +1220,7 @@ def test_redirect(self): o = h.parent = MockOpener() # ordinary redirect behaviour - for code in 301, 302, 303, 307: + for code in 301, 302, 303, 307, 308: for data in None, "blah\nblah\n": method = getattr(h, "http_error_%s" % code) req = Request(from_url, data) @@ -1191,10 +1232,11 @@ def test_redirect(self): try: method(req, MockFile(), code, "Blah", MockHeaders({"location": to_url})) - except urllib.error.HTTPError: - # 307 in response to POST requires user OK - self.assertEqual(code, 307) + except urllib.error.HTTPError as err: + # 307 and 308 in response to POST require user OK + self.assertIn(code, (307, 308)) self.assertIsNotNone(data) + err.close() self.assertEqual(o.req.get_full_url(), to_url) try: self.assertEqual(o.req.get_method(), "GET") @@ -1230,9 +1272,10 @@ def redirect(h, req, url=to_url): while 1: redirect(h, req, "http://example.com/") count = count + 1 - except urllib.error.HTTPError: + except urllib.error.HTTPError as err: # don't stop until max_repeats, because cookies may introduce state self.assertEqual(count, urllib.request.HTTPRedirectHandler.max_repeats) + err.close() # detect endless non-repeating chain of redirects req = Request(from_url, origin_req_host="example.com") @@ -1242,9 +1285,10 @@ def redirect(h, req, url=to_url): while 1: redirect(h, req, "http://example.com/%d" % count) count = count + 1 - except urllib.error.HTTPError: + except urllib.error.HTTPError as err: self.assertEqual(count, urllib.request.HTTPRedirectHandler.max_redirections) + err.close() def test_invalid_redirect(self): from_url = "http://example.com/a.html" @@ -1258,9 +1302,11 @@ def test_invalid_redirect(self): for scheme in invalid_schemes: invalid_url = scheme + '://' + schemeless_url - self.assertRaises(urllib.error.HTTPError, h.http_error_302, + with self.assertRaises(urllib.error.HTTPError) as cm: + h.http_error_302( req, MockFile(), 302, "Security Loophole", MockHeaders({"location": invalid_url})) + cm.exception.close() for scheme in valid_schemes: valid_url = scheme + '://' + schemeless_url @@ -1288,7 +1334,7 @@ def test_cookie_redirect(self): cj = CookieJar() interact_netscape(cj, "http://www.example.com/", "spam=eggs") - hh = MockHTTPHandler(302, "Location: http://www.cracker.com/\r\n\r\n") + hh = MockHTTPHandlerRedirect(302, "Location: http://www.cracker.com/\r\n\r\n") hdeh = urllib.request.HTTPDefaultErrorHandler() hrh = urllib.request.HTTPRedirectHandler() cp = urllib.request.HTTPCookieProcessor(cj) @@ -1298,7 +1344,7 @@ def test_cookie_redirect(self): def test_redirect_fragment(self): redirected_url = 'http://www.example.com/index.html#OK\r\n\r\n' - hh = MockHTTPHandler(302, 'Location: ' + redirected_url) + hh = MockHTTPHandlerRedirect(302, 'Location: ' + redirected_url) hdeh = urllib.request.HTTPDefaultErrorHandler() hrh = urllib.request.HTTPRedirectHandler() o = build_test_opener(hh, hdeh, hrh) @@ -1358,7 +1404,16 @@ def http_open(self, req): response = opener.open('http://example.com/') expected = b'GET ' + result + b' ' request = handler.last_buf - self.assertTrue(request.startswith(expected), repr(request)) + self.assertStartsWith(request, expected) + + def test_redirect_head_request(self): + from_url = "http://example.com/a.html" + to_url = "http://example.com/b.html" + h = urllib.request.HTTPRedirectHandler() + req = Request(from_url, method="HEAD") + fp = MockFile() + new_req = h.redirect_request(req, fp, 302, "Found", {}, to_url) + self.assertEqual(new_req.get_method(), "HEAD") def test_proxy(self): u = "proxy.example.com:3128" @@ -1379,7 +1434,8 @@ def test_proxy(self): [tup[0:2] for tup in o.calls]) def test_proxy_no_proxy(self): - os.environ['no_proxy'] = 'python.org' + env = self.enterContext(os_helper.EnvironmentVarGuard()) + env['no_proxy'] = 'python.org' o = OpenerDirector() ph = urllib.request.ProxyHandler(dict(http="proxy.example.com")) o.add_handler(ph) @@ -1391,10 +1447,10 @@ def test_proxy_no_proxy(self): self.assertEqual(req.host, "www.python.org") o.open(req) self.assertEqual(req.host, "www.python.org") - del os.environ['no_proxy'] def test_proxy_no_proxy_all(self): - os.environ['no_proxy'] = '*' + env = self.enterContext(os_helper.EnvironmentVarGuard()) + env['no_proxy'] = '*' o = OpenerDirector() ph = urllib.request.ProxyHandler(dict(http="proxy.example.com")) o.add_handler(ph) @@ -1402,7 +1458,6 @@ def test_proxy_no_proxy_all(self): self.assertEqual(req.host, "www.python.org") o.open(req) self.assertEqual(req.host, "www.python.org") - del os.environ['no_proxy'] def test_proxy_https(self): o = OpenerDirector() @@ -1420,6 +1475,7 @@ def test_proxy_https(self): self.assertEqual([(handlers[0], "https_open")], [tup[0:2] for tup in o.calls]) + @unittest.skipUnless(hasattr(http.client, 'HTTPSConnection'), 'HTTPSConnection required for HTTPS tests.') def test_proxy_https_proxy_authorization(self): o = OpenerDirector() ph = urllib.request.ProxyHandler(dict(https='proxy.example.com:3128')) @@ -1443,6 +1499,30 @@ def test_proxy_https_proxy_authorization(self): self.assertEqual(req.host, "proxy.example.com:3128") self.assertEqual(req.get_header("Proxy-authorization"), "FooBar") + @unittest.skipUnless(os.name == "nt", "only relevant for Windows") + def test_winreg_proxy_bypass(self): + proxy_override = "www.example.com;*.example.net; 192.168.0.1" + proxy_bypass = _proxy_bypass_winreg_override + for host in ("www.example.com", "www.example.net", "192.168.0.1"): + self.assertTrue(proxy_bypass(host, proxy_override), + "expected bypass of %s to be true" % host) + + for host in ("example.com", "www.example.org", "example.net", + "192.168.0.2"): + self.assertFalse(proxy_bypass(host, proxy_override), + "expected bypass of %s to be False" % host) + + # check intranet address bypass + proxy_override = "example.com; " + self.assertTrue(proxy_bypass("example.com", proxy_override), + "expected bypass of %s to be true" % host) + self.assertFalse(proxy_bypass("example.net", proxy_override), + "expected bypass of %s to be False" % host) + for host in ("test", "localhost"): + self.assertTrue(proxy_bypass(host, proxy_override), + "expect to bypass intranet address '%s'" + % host) + @unittest.skipUnless(sys.platform == 'darwin', "only relevant for OSX") def test_osx_proxy_bypass(self): bypass = { @@ -1483,7 +1563,7 @@ def check_basic_auth(self, headers, realm): password_manager = MockPasswordManager() auth_handler = urllib.request.HTTPBasicAuthHandler(password_manager) body = '\r\n'.join(headers) + '\r\n\r\n' - http_handler = MockHTTPHandler(401, body) + http_handler = MockHTTPHandlerRedirect(401, body) opener.add_handler(auth_handler) opener.add_handler(http_handler) self._test_basic_auth(opener, auth_handler, "Authorization", @@ -1543,7 +1623,7 @@ def test_proxy_basic_auth(self): password_manager = MockPasswordManager() auth_handler = urllib.request.ProxyBasicAuthHandler(password_manager) realm = "ACME Networks" - http_handler = MockHTTPHandler( + http_handler = MockHTTPHandlerRedirect( 407, 'Proxy-Authenticate: Basic realm="%s"\r\n\r\n' % realm) opener.add_handler(auth_handler) opener.add_handler(http_handler) @@ -1555,11 +1635,11 @@ def test_proxy_basic_auth(self): def test_basic_and_digest_auth_handlers(self): # HTTPDigestAuthHandler raised an exception if it couldn't handle a 40* - # response (http://python.org/sf/1479302), where it should instead + # response (https://bugs.python.org/issue1479302), where it should instead # return None to allow another handler (especially # HTTPBasicAuthHandler) to handle the response. - # Also (http://python.org/sf/14797027, RFC 2617 section 1.2), we must + # Also (https://bugs.python.org/issue14797027, RFC 2617 section 1.2), we must # try digest first (since it's the strongest auth scheme), so we record # order of calls here to check digest comes first: class RecordingOpenerDirector(OpenerDirector): @@ -1587,7 +1667,7 @@ def http_error_401(self, *args, **kwds): digest_handler = TestDigestAuthHandler(password_manager) basic_handler = TestBasicAuthHandler(password_manager) realm = "ACME Networks" - http_handler = MockHTTPHandler( + http_handler = MockHTTPHandlerRedirect( 401, 'WWW-Authenticate: Basic realm="%s"\r\n\r\n' % realm) opener.add_handler(basic_handler) opener.add_handler(digest_handler) @@ -1607,7 +1687,7 @@ def test_unsupported_auth_digest_handler(self): opener = OpenerDirector() # While using DigestAuthHandler digest_auth_handler = urllib.request.HTTPDigestAuthHandler(None) - http_handler = MockHTTPHandler( + http_handler = MockHTTPHandlerRedirect( 401, 'WWW-Authenticate: Kerberos\r\n\r\n') opener.add_handler(digest_auth_handler) opener.add_handler(http_handler) @@ -1617,7 +1697,7 @@ def test_unsupported_auth_basic_handler(self): # While using BasicAuthHandler opener = OpenerDirector() basic_auth_handler = urllib.request.HTTPBasicAuthHandler(None) - http_handler = MockHTTPHandler( + http_handler = MockHTTPHandlerRedirect( 401, 'WWW-Authenticate: NTLM\r\n\r\n') opener.add_handler(basic_auth_handler) opener.add_handler(http_handler) @@ -1704,7 +1784,7 @@ def test_basic_prior_auth_send_after_first_success(self): opener = OpenerDirector() opener.add_handler(auth_prior_handler) - http_handler = MockHTTPHandler( + http_handler = MockHTTPHandlerRedirect( 401, 'WWW-Authenticate: Basic realm="%s"\r\n\r\n' % None) opener.add_handler(http_handler) @@ -1755,7 +1835,7 @@ def test_invalid_closed(self): self.assertTrue(conn.fakesock.closed, "Connection not closed") -class MiscTests(unittest.TestCase): +class MiscTests(unittest.TestCase, ExtraAssertions): def opener_has_handler(self, opener, handler_class): self.assertTrue(any(h.__class__ == handler_class @@ -1814,14 +1894,21 @@ def test_HTTPError_interface(self): url = code = fp = None hdrs = 'Content-Length: 42' err = urllib.error.HTTPError(url, code, msg, hdrs, fp) - self.assertTrue(hasattr(err, 'reason')) + self.assertHasAttr(err, 'reason') self.assertEqual(err.reason, 'something bad happened') - self.assertTrue(hasattr(err, 'headers')) + self.assertHasAttr(err, 'headers') self.assertEqual(err.headers, 'Content-Length: 42') expected_errmsg = 'HTTP Error %s: %s' % (err.code, err.msg) self.assertEqual(str(err), expected_errmsg) expected_errmsg = '' % (err.code, err.msg) self.assertEqual(repr(err), expected_errmsg) + err.close() + + def test_gh_98778(self): + x = urllib.error.HTTPError("url", 405, "METHOD NOT ALLOWED", None, None) + self.assertEqual(getattr(x, "__notes__", ()), ()) + self.assertIsInstance(x.fp.read(), bytes) + x.close() def test_parse_proxy(self): parse_proxy_test_cases = [ diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py index 2c54ef85b4b..9a899785116 100644 --- a/Lib/test/test_urllib2_localnet.py +++ b/Lib/test/test_urllib2_localnet.py @@ -8,15 +8,18 @@ import unittest import hashlib +from test import support from test.support import hashlib_helper from test.support import threading_helper -from test.support import warnings_helper +from test.support.testcase import ExtraAssertions try: import ssl except ImportError: ssl = None +support.requires_working_socket(module=True) + here = os.path.dirname(__file__) # Self-signed cert file for 'localhost' CERT_localhost = os.path.join(here, 'certdata', 'keycert.pem') @@ -314,7 +317,9 @@ def test_basic_auth_httperror(self): ah = urllib.request.HTTPBasicAuthHandler() ah.add_password(self.REALM, self.server_url, self.USER, self.INCORRECT_PASSWD) urllib.request.install_opener(urllib.request.build_opener(ah)) - self.assertRaises(urllib.error.HTTPError, urllib.request.urlopen, self.server_url) + with self.assertRaises(urllib.error.HTTPError) as cm: + urllib.request.urlopen(self.server_url) + cm.exception.close() @hashlib_helper.requires_hashdigest("md5", openssl=True) @@ -356,23 +361,23 @@ def stop_server(self): self.server.stop() self.server = None - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_proxy_with_bad_password_raises_httperror(self): self.proxy_digest_handler.add_password(self.REALM, self.URL, self.USER, self.PASSWD+"bad") self.digest_auth_handler.set_qop("auth") - self.assertRaises(urllib.error.HTTPError, - self.opener.open, - self.URL) + with self.assertRaises(urllib.error.HTTPError) as cm: + self.opener.open(self.URL) + cm.exception.close() - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_proxy_with_no_password_raises_httperror(self): self.digest_auth_handler.set_qop("auth") - self.assertRaises(urllib.error.HTTPError, - self.opener.open, - self.URL) + with self.assertRaises(urllib.error.HTTPError) as cm: + self.opener.open(self.URL) + cm.exception.close() - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_proxy_qop_auth_works(self): self.proxy_digest_handler.add_password(self.REALM, self.URL, self.USER, self.PASSWD) @@ -381,7 +386,7 @@ def test_proxy_qop_auth_works(self): while result.read(): pass - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_proxy_qop_auth_int_works_or_throws_urlerror(self): self.proxy_digest_handler.add_password(self.REALM, self.URL, self.USER, self.PASSWD) @@ -442,7 +447,7 @@ def log_message(self, *args): return FakeHTTPRequestHandler -class TestUrlopen(unittest.TestCase): +class TestUrlopen(unittest.TestCase, ExtraAssertions): """Tests urllib.request.urlopen using the network. These tests are not exhaustive. Assuming that testing using files does a @@ -506,7 +511,7 @@ def start_https_server(self, responses=None, **kwargs): handler.port = server.port return handler - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_redirection(self): expected_response = b"We got here..." responses = [ @@ -520,7 +525,7 @@ def test_redirection(self): self.assertEqual(data, expected_response) self.assertEqual(handler.requests, ["/", "/somewhere_else"]) - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_chunked(self): expected_response = b"hello world" chunked_start = ( @@ -535,7 +540,7 @@ def test_chunked(self): data = self.urlopen("http://localhost:%s/" % handler.port) self.assertEqual(data, expected_response) - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_404(self): expected_response = b"Bad bad bad..." handler = self.start_server([(404, [], expected_response)]) @@ -551,7 +556,7 @@ def test_404(self): self.assertEqual(data, expected_response) self.assertEqual(handler.requests, ["/weeble"]) - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_200(self): expected_response = b"pycon 2008..." handler = self.start_server([(200, [], expected_response)]) @@ -559,7 +564,7 @@ def test_200(self): self.assertEqual(data, expected_response) self.assertEqual(handler.requests, ["/bizarre"]) - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_200_with_parameters(self): expected_response = b"pycon 2008..." handler = self.start_server([(200, [], expected_response)]) @@ -568,41 +573,14 @@ def test_200_with_parameters(self): self.assertEqual(data, expected_response) self.assertEqual(handler.requests, ["/bizarre", b"get=with_feeling"]) - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_https(self): handler = self.start_https_server() context = ssl.create_default_context(cafile=CERT_localhost) data = self.urlopen("https://localhost:%s/bizarre" % handler.port, context=context) self.assertEqual(data, b"we care a bit") - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") - def test_https_with_cafile(self): - handler = self.start_https_server(certfile=CERT_localhost) - with warnings_helper.check_warnings(('', DeprecationWarning)): - # Good cert - data = self.urlopen("https://localhost:%s/bizarre" % handler.port, - cafile=CERT_localhost) - self.assertEqual(data, b"we care a bit") - # Bad cert - with self.assertRaises(urllib.error.URLError) as cm: - self.urlopen("https://localhost:%s/bizarre" % handler.port, - cafile=CERT_fakehostname) - # Good cert, but mismatching hostname - handler = self.start_https_server(certfile=CERT_fakehostname) - with self.assertRaises(urllib.error.URLError) as cm: - self.urlopen("https://localhost:%s/bizarre" % handler.port, - cafile=CERT_fakehostname) - - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") - def test_https_with_cadefault(self): - handler = self.start_https_server(certfile=CERT_localhost) - # Self-signed cert should fail verification with system certificate store - with warnings_helper.check_warnings(('', DeprecationWarning)): - with self.assertRaises(urllib.error.URLError) as cm: - self.urlopen("https://localhost:%s/bizarre" % handler.port, - cadefault=True) - - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_https_sni(self): if ssl is None: self.skipTest("ssl module required") @@ -619,7 +597,7 @@ def cb_sni(ssl_sock, server_name, initial_context): self.urlopen("https://localhost:%s" % handler.port, context=context) self.assertEqual(sni_name, "localhost") - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_sending_headers(self): handler = self.start_server() req = urllib.request.Request("http://localhost:%s/" % handler.port, @@ -628,7 +606,7 @@ def test_sending_headers(self): pass self.assertEqual(handler.headers_received["Range"], "bytes=20-39") - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_sending_headers_camel(self): handler = self.start_server() req = urllib.request.Request("http://localhost:%s/" % handler.port, @@ -638,16 +616,15 @@ def test_sending_headers_camel(self): self.assertIn("X-Some-Header", handler.headers_received.keys()) self.assertNotIn("X-SoMe-hEader", handler.headers_received.keys()) - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_basic(self): handler = self.start_server() with urllib.request.urlopen("http://localhost:%s" % handler.port) as open_url: for attr in ("read", "close", "info", "geturl"): - self.assertTrue(hasattr(open_url, attr), "object returned from " - "urlopen lacks the %s attribute" % attr) + self.assertHasAttr(open_url, attr) self.assertTrue(open_url.read(), "calling 'read' failed") - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_info(self): handler = self.start_server() open_url = urllib.request.urlopen( @@ -659,7 +636,7 @@ def test_info(self): "instance of email.message.Message") self.assertEqual(info_obj.get_content_subtype(), "plain") - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_geturl(self): # Make sure same URL as opened is returned by geturl. handler = self.start_server() @@ -668,7 +645,7 @@ def test_geturl(self): url = open_url.geturl() self.assertEqual(url, "http://localhost:%s" % handler.port) - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_iteration(self): expected_response = b"pycon 2008..." handler = self.start_server([(200, [], expected_response)]) @@ -676,7 +653,7 @@ def test_iteration(self): for line in data: self.assertEqual(line, expected_response) - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_line_iteration(self): lines = [b"We\n", b"got\n", b"here\n", b"verylong " * 8192 + b"\n"] expected_response = b"".join(lines) @@ -689,7 +666,7 @@ def test_line_iteration(self): (index, len(lines[index]), len(line))) self.assertEqual(index + 1, len(lines)) - @unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, ValueError: illegal environment variable name") + @unittest.skipIf(os.name == 'nt', 'TODO: RUSTPYTHON; ValueError: illegal environment variable name') def test_issue16464(self): # See https://bugs.python.org/issue16464 # and https://bugs.python.org/issue46648 @@ -709,6 +686,7 @@ def test_issue16464(self): self.assertEqual(b"1234567890", request.data) self.assertEqual("10", request.get_header("Content-length")) + def setUpModule(): thread_info = threading_helper.threading_setup() unittest.addModuleCleanup(threading_helper.threading_cleanup, *thread_info) diff --git a/Lib/test/test_urllib2net.py b/Lib/test/test_urllib2net.py index c70b522d31d..41f170a6ad5 100644 --- a/Lib/test/test_urllib2net.py +++ b/Lib/test/test_urllib2net.py @@ -137,7 +137,6 @@ def setUp(self): # XXX The rest of these tests aren't very good -- they don't check much. # They do sometimes catch some major disasters, though. - @unittest.expectedFailure # TODO: RUSTPYTHON urllib.error.URLError: @support.requires_resource('walltime') def test_ftp(self): # Testing the same URL twice exercises the caching in CacheFTPHandler diff --git a/Lib/test/test_urllib_response.py b/Lib/test/test_urllib_response.py index 73d2ef0424f..d949fa38bfc 100644 --- a/Lib/test/test_urllib_response.py +++ b/Lib/test/test_urllib_response.py @@ -4,6 +4,11 @@ import tempfile import urllib.response import unittest +from test import support + +if support.is_wasi: + raise unittest.SkipTest("Cannot create socket on WASI") + class TestResponse(unittest.TestCase): @@ -43,6 +48,7 @@ def test_addinfo(self): info = urllib.response.addinfo(self.fp, self.test_headers) self.assertEqual(info.info(), self.test_headers) self.assertEqual(info.headers, self.test_headers) + info.close() def test_addinfourl(self): url = "http://www.python.org" @@ -55,6 +61,7 @@ def test_addinfourl(self): self.assertEqual(infourl.headers, self.test_headers) self.assertEqual(infourl.url, url) self.assertEqual(infourl.status, code) + infourl.close() def tearDown(self): self.sock.close() diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py index 1a3b4d4b721..d546e3ef219 100644 --- a/Lib/test/test_wsgiref.py +++ b/Lib/test/test_wsgiref.py @@ -134,7 +134,6 @@ def test_environ(self): b"Python test,Python test 2;query=test;/path/" ) - @unittest.expectedFailure # TODO: RUSTPYTHON; http library needs to be updated def test_request_length(self): out, err = run_amock(data=b"GET " + (b"x" * 65537) + b" HTTP/1.0\n\n") self.assertEqual(out.splitlines()[0], diff --git a/Lib/urllib/error.py b/Lib/urllib/error.py index 8cd901f13f8..a9cd1ecadd6 100644 --- a/Lib/urllib/error.py +++ b/Lib/urllib/error.py @@ -10,7 +10,7 @@ an application may want to handle an exception like a regular response. """ - +import io import urllib.response __all__ = ['URLError', 'HTTPError', 'ContentTooShortError'] @@ -42,12 +42,9 @@ def __init__(self, url, code, msg, hdrs, fp): self.hdrs = hdrs self.fp = fp self.filename = url - # The addinfourl classes depend on fp being a valid file - # object. In some cases, the HTTPError may not have a valid - # file object. If this happens, the simplest workaround is to - # not initialize the base classes. - if fp is not None: - self.__super_init(fp, hdrs, url, code) + if fp is None: + fp = io.BytesIO() + self.__super_init(fp, hdrs, url, code) def __str__(self): return 'HTTP Error %s: %s' % (self.code, self.msg) diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py index b35997bc00c..c72138a33ca 100644 --- a/Lib/urllib/parse.py +++ b/Lib/urllib/parse.py @@ -25,13 +25,19 @@ scenarios for parsing, and for backward compatibility purposes, some parsing quirks from older RFCs are retained. The testcases in test_urlparse.py provides a good indicator of parsing behavior. + +The WHATWG URL Parser spec should also be considered. We are not compliant with +it either due to existing user code API behavior expectations (Hyrum's Law). +It serves as a useful guide when making changes. """ +from collections import namedtuple +import functools +import math import re -import sys import types -import collections import warnings +import ipaddress __all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag", "urlsplit", "urlunsplit", "urlencode", "parse_qs", @@ -46,18 +52,18 @@ uses_relative = ['', 'ftp', 'http', 'gopher', 'nntp', 'imap', 'wais', 'file', 'https', 'shttp', 'mms', - 'prospero', 'rtsp', 'rtspu', 'sftp', + 'prospero', 'rtsp', 'rtsps', 'rtspu', 'sftp', 'svn', 'svn+ssh', 'ws', 'wss'] uses_netloc = ['', 'ftp', 'http', 'gopher', 'nntp', 'telnet', 'imap', 'wais', 'file', 'mms', 'https', 'shttp', - 'snews', 'prospero', 'rtsp', 'rtspu', 'rsync', + 'snews', 'prospero', 'rtsp', 'rtsps', 'rtspu', 'rsync', 'svn', 'svn+ssh', 'sftp', 'nfs', 'git', 'git+ssh', - 'ws', 'wss'] + 'ws', 'wss', 'itms-services'] uses_params = ['', 'ftp', 'hdl', 'prospero', 'http', 'imap', - 'https', 'shttp', 'rtsp', 'rtspu', 'sip', 'sips', - 'mms', 'sftp', 'tel'] + 'https', 'shttp', 'rtsp', 'rtsps', 'rtspu', 'sip', + 'sips', 'mms', 'sftp', 'tel'] # These are not actually used anymore, but should stay for backwards # compatibility. (They are undocumented, but have a public-looking name.) @@ -66,7 +72,7 @@ 'telnet', 'wais', 'imap', 'snews', 'sip', 'sips'] uses_query = ['', 'http', 'wais', 'imap', 'https', 'shttp', 'mms', - 'gopher', 'rtsp', 'rtspu', 'sip', 'sips'] + 'gopher', 'rtsp', 'rtsps', 'rtspu', 'sip', 'sips'] uses_fragment = ['', 'ftp', 'hdl', 'http', 'gopher', 'news', 'nntp', 'wais', 'https', 'shttp', 'snews', @@ -78,18 +84,17 @@ '0123456789' '+-.') +# Leading and trailing C0 control and space to be stripped per WHATWG spec. +# == "".join([chr(i) for i in range(0, 0x20 + 1)]) +_WHATWG_C0_CONTROL_OR_SPACE = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f ' + # Unsafe bytes to be removed per WHATWG spec _UNSAFE_URL_BYTES_TO_REMOVE = ['\t', '\r', '\n'] -# XXX: Consider replacing with functools.lru_cache -MAX_CACHE_SIZE = 20 -_parse_cache = {} - def clear_cache(): - """Clear the parse cache and the quoters cache.""" - _parse_cache.clear() - _safe_quoters.clear() - + """Clear internal performance caches. Undocumented; some tests want it.""" + urlsplit.cache_clear() + _byte_quoter_factory.cache_clear() # Helpers for bytes handling # For 3.2, we deliberately require applications that @@ -171,12 +176,11 @@ def hostname(self): def port(self): port = self._hostinfo[1] if port is not None: - try: - port = int(port, 10) - except ValueError: - message = f'Port could not be cast to integer value as {port!r}' - raise ValueError(message) from None - if not ( 0 <= port <= 65535): + if port.isdigit() and port.isascii(): + port = int(port) + else: + raise ValueError(f"Port could not be cast to integer value as {port!r}") + if not (0 <= port <= 65535): raise ValueError("Port out of range 0-65535") return port @@ -243,8 +247,6 @@ def _hostinfo(self): return hostname, port -from collections import namedtuple - _DefragResultBase = namedtuple('DefragResult', 'url fragment') _SplitResultBase = namedtuple( 'SplitResult', 'scheme netloc path query fragment') @@ -434,6 +436,37 @@ def _checknetloc(netloc): raise ValueError("netloc '" + netloc + "' contains invalid " + "characters under NFKC normalization") +def _check_bracketed_netloc(netloc): + # Note that this function must mirror the splitting + # done in NetlocResultMixins._hostinfo(). + hostname_and_port = netloc.rpartition('@')[2] + before_bracket, have_open_br, bracketed = hostname_and_port.partition('[') + if have_open_br: + # No data is allowed before a bracket. + if before_bracket: + raise ValueError("Invalid IPv6 URL") + hostname, _, port = bracketed.partition(']') + # No data is allowed after the bracket but before the port delimiter. + if port and not port.startswith(":"): + raise ValueError("Invalid IPv6 URL") + else: + hostname, _, port = hostname_and_port.partition(':') + _check_bracketed_host(hostname) + +# Valid bracketed hosts are defined in +# https://www.rfc-editor.org/rfc/rfc3986#page-49 and https://url.spec.whatwg.org/ +def _check_bracketed_host(hostname): + if hostname.startswith('v'): + if not re.match(r"\Av[a-fA-F0-9]+\..+\Z", hostname): + raise ValueError(f"IPvFuture address is invalid") + else: + ip = ipaddress.ip_address(hostname) # Throws Value Error if not IPv6 or IPv4 + if isinstance(ip, ipaddress.IPv4Address): + raise ValueError(f"An IPv4 address cannot be in brackets") + +# typed=True avoids BytesWarnings being emitted during cache key +# comparison since this API supports both bytes and str input. +@functools.lru_cache(typed=True) def urlsplit(url, scheme='', allow_fragments=True): """Parse a URL into 5 components: :///?# @@ -456,39 +489,37 @@ def urlsplit(url, scheme='', allow_fragments=True): """ url, scheme, _coerce_result = _coerce_args(url, scheme) + # Only lstrip url as some applications rely on preserving trailing space. + # (https://url.spec.whatwg.org/#concept-basic-url-parser would strip both) + url = url.lstrip(_WHATWG_C0_CONTROL_OR_SPACE) + scheme = scheme.strip(_WHATWG_C0_CONTROL_OR_SPACE) for b in _UNSAFE_URL_BYTES_TO_REMOVE: url = url.replace(b, "") scheme = scheme.replace(b, "") allow_fragments = bool(allow_fragments) - key = url, scheme, allow_fragments, type(url), type(scheme) - cached = _parse_cache.get(key, None) - if cached: - return _coerce_result(cached) - if len(_parse_cache) >= MAX_CACHE_SIZE: # avoid runaway growth - clear_cache() netloc = query = fragment = '' i = url.find(':') - if i > 0: + if i > 0 and url[0].isascii() and url[0].isalpha(): for c in url[:i]: if c not in scheme_chars: break else: scheme, url = url[:i].lower(), url[i+1:] - if url[:2] == '//': netloc, url = _splitnetloc(url, 2) if (('[' in netloc and ']' not in netloc) or (']' in netloc and '[' not in netloc)): raise ValueError("Invalid IPv6 URL") + if '[' in netloc and ']' in netloc: + _check_bracketed_netloc(netloc) if allow_fragments and '#' in url: url, fragment = url.split('#', 1) if '?' in url: url, query = url.split('?', 1) _checknetloc(netloc) v = SplitResult(scheme, netloc, url, query, fragment) - _parse_cache[key] = v return _coerce_result(v) def urlunparse(components): @@ -510,9 +541,13 @@ def urlunsplit(components): empty query; the RFC states that these are equivalent).""" scheme, netloc, url, query, fragment, _coerce_result = ( _coerce_args(*components)) - if netloc or (scheme and scheme in uses_netloc and url[:2] != '//'): + if netloc: if url and url[:1] != '/': url = '/' + url - url = '//' + (netloc or '') + url + url = '//' + netloc + url + elif url[:2] == '//': + url = '//' + url + elif scheme and scheme in uses_netloc and (not url or url[:1] == '/'): + url = '//' + url if scheme: url = scheme + ':' + url if query: @@ -611,6 +646,9 @@ def urldefrag(url): def unquote_to_bytes(string): """unquote_to_bytes('abc%20def') -> b'abc def'.""" + return bytes(_unquote_impl(string)) + +def _unquote_impl(string: bytes | bytearray | str) -> bytes | bytearray: # Note: strings are encoded as UTF-8. This is only an issue if it contains # unescaped non-ASCII characters, which URIs should not. if not string: @@ -622,8 +660,8 @@ def unquote_to_bytes(string): bits = string.split(b'%') if len(bits) == 1: return string - res = [bits[0]] - append = res.append + res = bytearray(bits[0]) + append = res.extend # Delay the initialization of the table to not waste memory # if the function is never called global _hextobyte @@ -637,10 +675,20 @@ def unquote_to_bytes(string): except KeyError: append(b'%') append(item) - return b''.join(res) + return res _asciire = re.compile('([\x00-\x7f]+)') +def _generate_unquoted_parts(string, encoding, errors): + previous_match_end = 0 + for ascii_match in _asciire.finditer(string): + start, end = ascii_match.span() + yield string[previous_match_end:start] # Non-ASCII + # The ascii_match[1] group == string[start:end]. + yield _unquote_impl(ascii_match[1]).decode(encoding, errors) + previous_match_end = end + yield string[previous_match_end:] # Non-ASCII tail + def unquote(string, encoding='utf-8', errors='replace'): """Replace %xx escapes by their single-character equivalent. The optional encoding and errors parameters specify how to decode percent-encoded @@ -652,21 +700,16 @@ def unquote(string, encoding='utf-8', errors='replace'): unquote('abc%20def') -> 'abc def'. """ if isinstance(string, bytes): - return unquote_to_bytes(string).decode(encoding, errors) + return _unquote_impl(string).decode(encoding, errors) if '%' not in string: + # Is it a string-like object? string.split return string if encoding is None: encoding = 'utf-8' if errors is None: errors = 'replace' - bits = _asciire.split(string) - res = [bits[0]] - append = res.append - for i in range(1, len(bits), 2): - append(unquote_to_bytes(bits[i]).decode(encoding, errors)) - append(bits[i + 1]) - return ''.join(res) + return ''.join(_generate_unquoted_parts(string, encoding, errors)) def parse_qs(qs, keep_blank_values=False, strict_parsing=False, @@ -740,11 +783,29 @@ def parse_qsl(qs, keep_blank_values=False, strict_parsing=False, Returns a list, as G-d intended. """ - qs, _coerce_result = _coerce_args(qs) - separator, _ = _coerce_args(separator) - if not separator or (not isinstance(separator, (str, bytes))): + if not separator or not isinstance(separator, (str, bytes)): raise ValueError("Separator must be of type string or bytes.") + if isinstance(qs, str): + if not isinstance(separator, str): + separator = str(separator, 'ascii') + eq = '=' + def _unquote(s): + return unquote_plus(s, encoding=encoding, errors=errors) + else: + if not qs: + return [] + # Use memoryview() to reject integers and iterables, + # acceptable by the bytes constructor. + qs = bytes(memoryview(qs)) + if isinstance(separator, str): + separator = bytes(separator, 'ascii') + eq = b'=' + def _unquote(s): + return unquote_to_bytes(s.replace(b'+', b' ')) + + if not qs: + return [] # If max_num_fields is defined then check that the number of fields # is less than max_num_fields. This prevents a memory exhaustion DOS @@ -756,25 +817,14 @@ def parse_qsl(qs, keep_blank_values=False, strict_parsing=False, r = [] for name_value in qs.split(separator): - if not name_value and not strict_parsing: - continue - nv = name_value.split('=', 1) - if len(nv) != 2: - if strict_parsing: + if name_value or strict_parsing: + name, has_eq, value = name_value.partition(eq) + if not has_eq and strict_parsing: raise ValueError("bad query field: %r" % (name_value,)) - # Handle case of a control-name with no equal sign - if keep_blank_values: - nv.append('') - else: - continue - if len(nv[1]) or keep_blank_values: - name = nv[0].replace('+', ' ') - name = unquote(name, encoding=encoding, errors=errors) - name = _coerce_result(name) - value = nv[1].replace('+', ' ') - value = unquote(value, encoding=encoding, errors=errors) - value = _coerce_result(value) - r.append((name, value)) + if value or keep_blank_values: + name = _unquote(name) + value = _unquote(value) + r.append((name, value)) return r def unquote_plus(string, encoding='utf-8', errors='replace'): @@ -791,23 +841,30 @@ def unquote_plus(string, encoding='utf-8', errors='replace'): b'0123456789' b'_.-~') _ALWAYS_SAFE_BYTES = bytes(_ALWAYS_SAFE) -_safe_quoters = {} -class Quoter(collections.defaultdict): - """A mapping from bytes (in range(0,256)) to strings. +def __getattr__(name): + if name == 'Quoter': + warnings.warn('Deprecated in 3.11. ' + 'urllib.parse.Quoter will be removed in Python 3.14. ' + 'It was not intended to be a public API.', + DeprecationWarning, stacklevel=2) + return _Quoter + raise AttributeError(f'module {__name__!r} has no attribute {name!r}') + +class _Quoter(dict): + """A mapping from bytes numbers (in range(0,256)) to strings. String values are percent-encoded byte values, unless the key < 128, and - in the "safe" set (either the specified safe set, or default set). + in either of the specified safe set, or the always safe set. """ - # Keeps a cache internally, using defaultdict, for efficiency (lookups + # Keeps a cache internally, via __missing__, for efficiency (lookups # of cached keys don't call Python code at all). def __init__(self, safe): """safe: bytes object.""" self.safe = _ALWAYS_SAFE.union(safe) def __repr__(self): - # Without this, will just display as a defaultdict - return "<%s %r>" % (self.__class__.__name__, dict(self)) + return f"" def __missing__(self, b): # Handle a cache miss. Store quoted string in cache and return. @@ -886,6 +943,11 @@ def quote_plus(string, safe='', encoding=None, errors=None): string = quote(string, safe + space, encoding, errors) return string.replace(' ', '+') +# Expectation: A typical program is unlikely to create more than 5 of these. +@functools.lru_cache +def _byte_quoter_factory(safe): + return _Quoter(safe).__getitem__ + def quote_from_bytes(bs, safe='/'): """Like quote(), but accepts a bytes object rather than a str, and does not perform string-to-bytes encoding. It always returns an ASCII string. @@ -899,14 +961,19 @@ def quote_from_bytes(bs, safe='/'): # Normalize 'safe' by converting to bytes and removing non-ASCII chars safe = safe.encode('ascii', 'ignore') else: + # List comprehensions are faster than generator expressions. safe = bytes([c for c in safe if c < 128]) if not bs.rstrip(_ALWAYS_SAFE_BYTES + safe): return bs.decode() - try: - quoter = _safe_quoters[safe] - except KeyError: - _safe_quoters[safe] = quoter = Quoter(safe).__getitem__ - return ''.join([quoter(char) for char in bs]) + quoter = _byte_quoter_factory(safe) + if (bs_len := len(bs)) < 200_000: + return ''.join(map(quoter, bs)) + else: + # This saves memory - https://github.com/python/cpython/issues/95865 + chunk_size = math.isqrt(bs_len) + chunks = [''.join(map(quoter, bs[i:i+chunk_size])) + for i in range(0, bs_len, chunk_size)] + return ''.join(chunks) def urlencode(query, doseq=False, safe='', encoding=None, errors=None, quote_via=quote_plus): @@ -939,10 +1006,9 @@ def urlencode(query, doseq=False, safe='', encoding=None, errors=None, # but that's a minor nit. Since the original implementation # allowed empty dicts that type of behavior probably should be # preserved for consistency - except TypeError: - ty, va, tb = sys.exc_info() + except TypeError as err: raise TypeError("not a valid non-string sequence " - "or mapping object").with_traceback(tb) + "or mapping object") from err l = [] if not doseq: @@ -1125,15 +1191,15 @@ def splitnport(host, defport=-1): def _splitnport(host, defport=-1): """Split host and port, returning numeric port. Return given default port if no ':' found; defaults to -1. - Return numerical port if a valid number are found after ':'. + Return numerical port if a valid number is found after ':'. Return None if ':' but not a valid number.""" host, delim, port = host.rpartition(':') if not delim: host = port elif port: - try: + if port.isdigit() and port.isascii(): nport = int(port) - except ValueError: + else: nport = None return host, nport return host, defport diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index a0ef60b30de..21d76913feb 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -11,8 +11,8 @@ Handlers needed to open the requested URL. For example, the HTTPHandler performs HTTP GET and POST requests and deals with non-error returns. The HTTPRedirectHandler automatically deals with -HTTP 301, 302, 303 and 307 redirect errors, and the HTTPDigestAuthHandler -deals with digest authentication. +HTTP 301, 302, 303, 307, and 308 redirect errors, and the +HTTPDigestAuthHandler deals with digest authentication. urlopen(url, data=None) -- Basic usage is the same as original urllib. pass the url and optionally data to post to an HTTP URL, and @@ -88,7 +88,6 @@ import http.client import io import os -import posixpath import re import socket import string @@ -137,7 +136,7 @@ _opener = None def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - *, cafile=None, capath=None, cadefault=False, context=None): + *, context=None): '''Open the URL url, which can be either a string or a Request object. *data* must be an object specifying additional data to be sent to @@ -155,14 +154,6 @@ def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, If *context* is specified, it must be a ssl.SSLContext instance describing the various SSL options. See HTTPSConnection for more details. - The optional *cafile* and *capath* parameters specify a set of trusted CA - certificates for HTTPS requests. cafile should point to a single file - containing a bundle of CA certificates, whereas capath should point to a - directory of hashed certificate files. More information can be found in - ssl.SSLContext.load_verify_locations(). - - The *cadefault* parameter is ignored. - This function always returns an object which can work as a context manager and has the properties url, headers, and status. @@ -188,25 +179,7 @@ def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, ''' global _opener - if cafile or capath or cadefault: - import warnings - warnings.warn("cafile, capath and cadefault are deprecated, use a " - "custom context instead.", DeprecationWarning, 2) - if context is not None: - raise ValueError( - "You can't pass both context and any of cafile, capath, and " - "cadefault" - ) - if not _have_ssl: - raise ValueError('SSL support not available') - context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, - cafile=cafile, - capath=capath) - # send ALPN extension to indicate HTTP/1.1 protocol - context.set_alpn_protocols(['http/1.1']) - https_handler = HTTPSHandler(context=context) - opener = build_opener(https_handler) - elif context: + if context: https_handler = HTTPSHandler(context=context) opener = build_opener(https_handler) elif _opener is None: @@ -266,10 +239,7 @@ def urlretrieve(url, filename=None, reporthook=None, data=None): if reporthook: reporthook(blocknum, bs, size) - while True: - block = fp.read(bs) - if not block: - break + while block := fp.read(bs): read += len(block) tfp.write(block) blocknum += 1 @@ -661,7 +631,7 @@ def redirect_request(self, req, fp, code, msg, headers, newurl): but another Handler might. """ m = req.get_method() - if (not (code in (301, 302, 303, 307) and m in ("GET", "HEAD") + if (not (code in (301, 302, 303, 307, 308) and m in ("GET", "HEAD") or code in (301, 302, 303) and m == "POST")): raise HTTPError(req.full_url, code, msg, headers, fp) @@ -680,6 +650,7 @@ def redirect_request(self, req, fp, code, msg, headers, newurl): newheaders = {k: v for k, v in req.headers.items() if k.lower() not in CONTENT_HEADERS} return Request(newurl, + method="HEAD" if m == "HEAD" else "GET", headers=newheaders, origin_req_host=req.origin_req_host, unverifiable=True) @@ -748,7 +719,7 @@ def http_error_302(self, req, fp, code, msg, headers): return self.parent.open(new, timeout=req.timeout) - http_error_301 = http_error_303 = http_error_307 = http_error_302 + http_error_301 = http_error_303 = http_error_307 = http_error_308 = http_error_302 inf_msg = "The HTTP server returned a redirect error that would " \ "lead to an infinite loop.\n" \ @@ -907,9 +878,9 @@ def find_user_password(self, realm, authuri): class HTTPPasswordMgrWithPriorAuth(HTTPPasswordMgrWithDefaultRealm): - def __init__(self, *args, **kwargs): + def __init__(self): self.authenticated = {} - super().__init__(*args, **kwargs) + super().__init__() def add_password(self, realm, uri, user, passwd, is_authenticated=False): self.update_authenticated(uri, is_authenticated) @@ -1255,8 +1226,8 @@ def http_error_407(self, req, fp, code, msg, headers): class AbstractHTTPHandler(BaseHandler): - def __init__(self, debuglevel=0): - self._debuglevel = debuglevel + def __init__(self, debuglevel=None): + self._debuglevel = debuglevel if debuglevel is not None else http.client.HTTPConnection.debuglevel def set_http_debuglevel(self, level): self._debuglevel = level @@ -1382,14 +1353,19 @@ def http_open(self, req): class HTTPSHandler(AbstractHTTPHandler): - def __init__(self, debuglevel=0, context=None, check_hostname=None): + def __init__(self, debuglevel=None, context=None, check_hostname=None): + debuglevel = debuglevel if debuglevel is not None else http.client.HTTPSConnection.debuglevel AbstractHTTPHandler.__init__(self, debuglevel) + if context is None: + http_version = http.client.HTTPSConnection._http_vsn + context = http.client._create_https_context(http_version) + if check_hostname is not None: + context.check_hostname = check_hostname self._context = context - self._check_hostname = check_hostname def https_open(self, req): return self.do_open(http.client.HTTPSConnection, req, - context=self._context, check_hostname=self._check_hostname) + context=self._context) https_request = AbstractHTTPHandler.do_request_ @@ -1561,6 +1537,7 @@ def ftp_open(self, req): dirs, file = dirs[:-1], dirs[-1] if dirs and not dirs[0]: dirs = dirs[1:] + fw = None try: fw = self.connect_ftp(user, passwd, host, port, dirs, req.timeout) type = file and 'I' or 'D' @@ -1578,9 +1555,12 @@ def ftp_open(self, req): headers += "Content-length: %d\n" % retrlen headers = email.message_from_string(headers) return addinfourl(fp, headers, req.full_url) - except ftplib.all_errors as exp: - exc = URLError('ftp error: %r' % exp) - raise exc.with_traceback(sys.exc_info()[2]) + except Exception as exp: + if fw is not None and not fw.keepalive: + fw.close() + if isinstance(exp, ftplib.all_errors): + raise URLError(exp) from exp + raise def connect_ftp(self, user, passwd, host, port, dirs, timeout): return ftpwrapper(user, passwd, host, port, dirs, timeout, @@ -1604,14 +1584,15 @@ def setMaxConns(self, m): def connect_ftp(self, user, passwd, host, port, dirs, timeout): key = user, host, port, '/'.join(dirs), timeout - if key in self.cache: - self.timeout[key] = time.time() + self.delay - else: - self.cache[key] = ftpwrapper(user, passwd, host, port, - dirs, timeout) - self.timeout[key] = time.time() + self.delay + conn = self.cache.get(key) + if conn is None or not conn.keepalive: + if conn is not None: + conn.close() + conn = self.cache[key] = ftpwrapper(user, passwd, host, port, + dirs, timeout) + self.timeout[key] = time.time() + self.delay self.check_cache() - return self.cache[key] + return conn def check_cache(self): # first check for old ones @@ -1681,12 +1662,27 @@ def data_open(self, req): def url2pathname(pathname): """OS-specific conversion from a relative URL of the 'file' scheme to a file system path; not recommended for general use.""" - return unquote(pathname) + if pathname[:3] == '///': + # URL has an empty authority section, so the path begins on the + # third character. + pathname = pathname[2:] + elif pathname[:12] == '//localhost/': + # Skip past 'localhost' authority. + pathname = pathname[11:] + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() + return unquote(pathname, encoding=encoding, errors=errors) def pathname2url(pathname): """OS-specific conversion from a file system path to a relative URL of the 'file' scheme; not recommended for general use.""" - return quote(pathname) + if pathname[:2] == '//': + # Add explicitly empty authority to avoid interpreting the path + # as authority. + pathname = '//' + pathname + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() + return quote(pathname, encoding=encoding, errors=errors) ftpcache = {} @@ -1791,7 +1787,7 @@ def open(self, fullurl, data=None): except (HTTPError, URLError): raise except OSError as msg: - raise OSError('socket error', msg).with_traceback(sys.exc_info()[2]) + raise OSError('socket error', msg) from msg def open_unknown(self, fullurl, data=None): """Overridable interface to open unknown URL type.""" @@ -1845,10 +1841,7 @@ def retrieve(self, url, filename=None, reporthook=None, data=None): size = int(headers["Content-Length"]) if reporthook: reporthook(blocknum, bs, size) - while 1: - block = fp.read(bs) - if not block: - break + while block := fp.read(bs): read += len(block) tfp.write(block) blocknum += 1 @@ -1988,9 +1981,17 @@ def http_error_default(self, url, fp, errcode, errmsg, headers): if _have_ssl: def _https_connection(self, host): - return http.client.HTTPSConnection(host, - key_file=self.key_file, - cert_file=self.cert_file) + if self.key_file or self.cert_file: + http_version = http.client.HTTPSConnection._http_vsn + context = http.client._create_https_context(http_version) + context.load_cert_chain(self.cert_file, self.key_file) + # cert and key file means the user wants to authenticate. + # enable TLS 1.3 PHA implicitly even for custom contexts. + if context.post_handshake_auth is not None: + context.post_handshake_auth = True + else: + context = None + return http.client.HTTPSConnection(host, context=context) def open_https(self, url, data=None): """Use HTTPS protocol.""" @@ -2093,7 +2094,7 @@ def open_ftp(self, url): headers = email.message_from_string(headers) return addinfourl(fp, headers, "ftp:" + url) except ftperrors() as exp: - raise URLError('ftp error %r' % exp).with_traceback(sys.exc_info()[2]) + raise URLError(f'ftp error: {exp}') from exp def open_data(self, url, data=None): """Use "data" URL.""" @@ -2211,6 +2212,13 @@ def http_error_307(self, url, fp, errcode, errmsg, headers, data=None): else: return self.http_error_default(url, fp, errcode, errmsg, headers) + def http_error_308(self, url, fp, errcode, errmsg, headers, data=None): + """Error 308 -- relocated, but turn POST into error.""" + if data is None: + return self.http_error_301(url, fp, errcode, errmsg, headers, data) + else: + return self.http_error_default(url, fp, errcode, errmsg, headers) + def http_error_401(self, url, fp, errcode, errmsg, headers, data=None, retry=False): """Error 401 -- authentication required. @@ -2436,8 +2444,7 @@ def retrfile(self, file, type): conn, retrlen = self.ftp.ntransfercmd(cmd) except ftplib.error_perm as reason: if str(reason)[:3] != '550': - raise URLError('ftp error: %r' % reason).with_traceback( - sys.exc_info()[2]) + raise URLError(f'ftp error: {reason}') from reason if not conn: # Set transfer mode to ASCII! self.ftp.voidcmd('TYPE A') @@ -2464,7 +2471,13 @@ def retrfile(self, file, type): return (ftpobj, retrlen) def endtransfer(self): + if not self.busy: + return self.busy = 0 + try: + self.ftp.voidresp() + except ftperrors(): + pass def close(self): self.keepalive = False @@ -2492,28 +2505,34 @@ def getproxies_environment(): this seems to be the standard convention. If you need a different way, you can pass a proxies dictionary to the [Fancy]URLopener constructor. - """ - proxies = {} # in order to prefer lowercase variables, process environment in # two passes: first matches any, second pass matches lowercase only - for name, value in os.environ.items(): - name = name.lower() - if value and name[-6:] == '_proxy': - proxies[name[:-6]] = value + + # select only environment variables which end in (after making lowercase) _proxy + proxies = {} + environment = [] + for name in os.environ: + # fast screen underscore position before more expensive case-folding + if len(name) > 5 and name[-6] == "_" and name[-5:].lower() == "proxy": + value = os.environ[name] + proxy_name = name[:-6].lower() + environment.append((name, value, proxy_name)) + if value: + proxies[proxy_name] = value # CVE-2016-1000110 - If we are running as CGI script, forget HTTP_PROXY # (non-all-lowercase) as it may be set from the web server by a "Proxy:" # header from the client # If "proxy" is lowercase, it will still be used thanks to the next block if 'REQUEST_METHOD' in os.environ: proxies.pop('http', None) - for name, value in os.environ.items(): + for name, value, proxy_name in environment: + # not case-folded, checking here for lower-case env vars only if name[-6:] == '_proxy': - name = name.lower() if value: - proxies[name[:-6]] = value + proxies[proxy_name] = value else: - proxies.pop(name[:-6], None) + proxies.pop(proxy_name, None) return proxies def proxy_bypass_environment(host, proxies=None): @@ -2566,6 +2585,7 @@ def _proxy_bypass_macosx_sysconf(host, proxy_settings): } """ from fnmatch import fnmatch + from ipaddress import AddressValueError, IPv4Address hostonly, port = _splitport(host) @@ -2582,20 +2602,17 @@ def ip2num(ipAddr): return True hostIP = None + try: + hostIP = int(IPv4Address(hostonly)) + except AddressValueError: + pass for value in proxy_settings.get('exceptions', ()): # Items in the list are strings like these: *.local, 169.254/16 if not value: continue m = re.match(r"(\d+(?:\.\d+)*)(/\d+)?", value) - if m is not None: - if hostIP is None: - try: - hostIP = socket.gethostbyname(hostonly) - hostIP = ip2num(hostIP) - except OSError: - continue - + if m is not None and hostIP is not None: base = ip2num(m.group(1)) mask = m.group(2) if mask is None: @@ -2618,6 +2635,31 @@ def ip2num(ipAddr): return False +# Same as _proxy_bypass_macosx_sysconf, testable on all platforms +def _proxy_bypass_winreg_override(host, override): + """Return True if the host should bypass the proxy server. + + The proxy override list is obtained from the Windows + Internet settings proxy override registry value. + + An example of a proxy override value is: + "www.example.com;*.example.net; 192.168.0.1" + """ + from fnmatch import fnmatch + + host, _ = _splitport(host) + proxy_override = override.split(';') + for test in proxy_override: + test = test.strip() + # "" should bypass the proxy server for all intranet addresses + if test == '': + if '.' not in host: + return True + elif fnmatch(host, test): + return True + return False + + if sys.platform == 'darwin': from _scproxy import _get_proxy_settings, _get_proxies @@ -2716,7 +2758,7 @@ def proxy_bypass_registry(host): import winreg except ImportError: # Std modules, so should be around - but you never know! - return 0 + return False try: internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') @@ -2726,40 +2768,10 @@ def proxy_bypass_registry(host): 'ProxyOverride')[0]) # ^^^^ Returned as Unicode but problems if not converted to ASCII except OSError: - return 0 + return False if not proxyEnable or not proxyOverride: - return 0 - # try to make a host list from name and IP address. - rawHost, port = _splitport(host) - host = [rawHost] - try: - addr = socket.gethostbyname(rawHost) - if addr != rawHost: - host.append(addr) - except OSError: - pass - try: - fqdn = socket.getfqdn(rawHost) - if fqdn != rawHost: - host.append(fqdn) - except OSError: - pass - # make a check value list from the registry entry: replace the - # '' string by the localhost entry and the corresponding - # canonical entry. - proxyOverride = proxyOverride.split(';') - # now check if we match one of the registry values. - for test in proxyOverride: - if test == '': - if '.' not in rawHost: - return 1 - test = test.replace(".", r"\.") # mask dots - test = test.replace("*", r".*") # change glob sequence - test = test.replace("?", r".") # change glob char - for val in host: - if re.match(test, val, re.I): - return 1 - return 0 + return False + return _proxy_bypass_winreg_override(host, proxyOverride) def proxy_bypass(host): """Return True, if host should be bypassed. diff --git a/Lib/urllib/robotparser.py b/Lib/urllib/robotparser.py index c58565e3945..63689816f30 100644 --- a/Lib/urllib/robotparser.py +++ b/Lib/urllib/robotparser.py @@ -11,6 +11,8 @@ """ import collections +import re +import urllib.error import urllib.parse import urllib.request @@ -19,6 +21,19 @@ RequestRate = collections.namedtuple("RequestRate", "requests seconds") +def normalize(path): + unquoted = urllib.parse.unquote(path, errors='surrogateescape') + return urllib.parse.quote(unquoted, errors='surrogateescape') + +def normalize_path(path): + path, sep, query = path.partition('?') + path = normalize(path) + if sep: + query = re.sub(r'[^=&]+', lambda m: normalize(m[0]), query) + path += '?' + query + return path + + class RobotFileParser: """ This class provides a set of methods to read, parse and answer questions about a single robots.txt file. @@ -54,7 +69,7 @@ def modified(self): def set_url(self, url): """Sets the URL referring to a robots.txt file.""" self.url = url - self.host, self.path = urllib.parse.urlparse(url)[1:3] + self.host, self.path = urllib.parse.urlsplit(url)[1:3] def read(self): """Reads the robots.txt URL and feeds it to the parser.""" @@ -65,9 +80,10 @@ def read(self): self.disallow_all = True elif err.code >= 400 and err.code < 500: self.allow_all = True + err.close() else: raw = f.read() - self.parse(raw.decode("utf-8").splitlines()) + self.parse(raw.decode("utf-8", "surrogateescape").splitlines()) def _add_entry(self, entry): if "*" in entry.useragents: @@ -111,7 +127,7 @@ def parse(self, lines): line = line.split(':', 1) if len(line) == 2: line[0] = line[0].strip().lower() - line[1] = urllib.parse.unquote(line[1].strip()) + line[1] = line[1].strip() if line[0] == "user-agent": if state == 2: self._add_entry(entry) @@ -165,10 +181,9 @@ def can_fetch(self, useragent, url): return False # search for given user agent matches # the first match counts - parsed_url = urllib.parse.urlparse(urllib.parse.unquote(url)) - url = urllib.parse.urlunparse(('','',parsed_url.path, - parsed_url.params,parsed_url.query, parsed_url.fragment)) - url = urllib.parse.quote(url) + parsed_url = urllib.parse.urlsplit(url) + url = urllib.parse.urlunsplit(('', '', *parsed_url[2:])) + url = normalize_path(url) if not url: url = "/" for entry in self.entries: @@ -211,7 +226,6 @@ def __str__(self): entries = entries + [self.default_entry] return '\n\n'.join(map(str, entries)) - class RuleLine: """A rule line is a single "Allow:" (allowance==True) or "Disallow:" (allowance==False) followed by a path.""" @@ -219,8 +233,7 @@ def __init__(self, path, allowance): if path == '' and not allowance: # an empty value means allow all allowance = True - path = urllib.parse.urlunparse(urllib.parse.urlparse(path)) - self.path = urllib.parse.quote(path) + self.path = normalize_path(path) self.allowance = allowance def applies_to(self, filename): @@ -266,7 +279,7 @@ def applies_to(self, useragent): def allowance(self, filename): """Preconditions: - our agent applies to this entry - - filename is URL decoded""" + - filename is URL encoded""" for line in self.rulelines: if line.applies_to(filename): return line.allowance From 380fa39eba2742e81ad4d9344b1a4d883a142abb Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:08:06 +0200 Subject: [PATCH 817/819] Bytecode instrumented placeholder (#6741) * Add all other bytecodes * Mark passing/failing tests --- Lib/_opcode_metadata.py | 98 ++++++++- Lib/test/test__opcode.py | 2 +- .../compiler-core/src/bytecode/instruction.rs | 207 +++++++++++++++++- 3 files changed, 296 insertions(+), 11 deletions(-) diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 3e98489419f..abb748519c3 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -136,10 +136,102 @@ 'JUMP_IF_FALSE_OR_POP': 129, 'JUMP_IF_TRUE_OR_POP': 130, 'JUMP_IF_NOT_EXC_MATCH': 131, - 'SET_EXC_INFO': 134, - 'SUBSCRIPT': 135, + 'SET_EXC_INFO': 132, + 'SUBSCRIPT': 133, 'RESUME': 149, - 'LOAD_CLOSURE': 253, + 'BINARY_OP_ADD_FLOAT': 150, + 'BINARY_OP_ADD_INT': 151, + 'BINARY_OP_ADD_UNICODE': 152, + 'BINARY_OP_MULTIPLY_FLOAT': 153, + 'BINARY_OP_MULTIPLY_INT': 154, + 'BINARY_OP_SUBTRACT_FLOAT': 155, + 'BINARY_OP_SUBTRACT_INT': 156, + 'BINARY_SUBSCR_DICT': 157, + 'BINARY_SUBSCR_GETITEM': 158, + 'BINARY_SUBSCR_LIST_INT': 159, + 'BINARY_SUBSCR_STR_INT': 160, + 'BINARY_SUBSCR_TUPLE_INT': 161, + 'CALL_ALLOC_AND_ENTER_INIT': 162, + 'CALL_BOUND_METHOD_EXACT_ARGS': 163, + 'CALL_BOUND_METHOD_GENERAL': 164, + 'CALL_BUILTIN_CLASS': 165, + 'CALL_BUILTIN_FAST': 166, + 'CALL_BUILTIN_FAST_WITH_KEYWORDS': 167, + 'CALL_BUILTIN_O': 168, + 'CALL_ISINSTANCE': 169, + 'CALL_LEN': 170, + 'CALL_LIST_APPEND': 171, + 'CALL_METHOD_DESCRIPTOR_FAST': 172, + 'CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS': 173, + 'CALL_METHOD_DESCRIPTOR_NOARGS': 174, + 'CALL_METHOD_DESCRIPTOR_O': 175, + 'CALL_NON_PY_GENERAL': 176, + 'CALL_PY_EXACT_ARGS': 177, + 'CALL_PY_GENERAL': 178, + 'CALL_STR_1': 179, + 'CALL_TUPLE_1': 180, + 'CALL_TYPE_1': 181, + 'COMPARE_OP_FLOAT': 182, + 'COMPARE_OP_INT': 183, + 'COMPARE_OP_STR': 184, + 'CONTAINS_OP_DICT': 185, + 'CONTAINS_OP_SET': 186, + 'FOR_ITER_GEN': 187, + 'FOR_ITER_LIST': 188, + 'FOR_ITER_RANGE': 189, + 'FOR_ITER_TUPLE': 190, + 'LOAD_ATTR_CLASS': 191, + 'LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN': 192, + 'LOAD_ATTR_INSTANCE_VALUE': 193, + 'LOAD_ATTR_METHOD_LAZY_DICT': 194, + 'LOAD_ATTR_METHOD_NO_DICT': 195, + 'LOAD_ATTR_METHOD_WITH_VALUES': 196, + 'LOAD_ATTR_MODULE': 197, + 'LOAD_ATTR_NONDESCRIPTOR_NO_DICT': 198, + 'LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES': 199, + 'LOAD_ATTR_PROPERTY': 200, + 'LOAD_ATTR_SLOT': 201, + 'LOAD_ATTR_WITH_HINT': 202, + 'LOAD_GLOBAL_BUILTIN': 203, + 'LOAD_GLOBAL_MODULE': 204, + 'LOAD_SUPER_ATTR_ATTR': 205, + 'LOAD_SUPER_ATTR_METHOD': 206, + 'RESUME_CHECK': 207, + 'SEND_GEN': 208, + 'STORE_ATTR_INSTANCE_VALUE': 209, + 'STORE_ATTR_SLOT': 210, + 'STORE_ATTR_WITH_HINT': 211, + 'STORE_SUBSCR_DICT': 212, + 'STORE_SUBSCR_LIST_INT': 213, + 'TO_BOOL_ALWAYS_TRUE': 214, + 'TO_BOOL_BOOL': 215, + 'TO_BOOL_INT': 216, + 'TO_BOOL_LIST': 217, + 'TO_BOOL_NONE': 218, + 'TO_BOOL_STR': 219, + 'UNPACK_SEQUENCE_LIST': 220, + 'UNPACK_SEQUENCE_TUPLE': 221, + 'UNPACK_SEQUENCE_TWO_TUPLE': 222, + 'INSTRUMENTED_RESUME': 236, + 'INSTRUMENTED_END_FOR': 237, + 'INSTRUMENTED_END_SEND': 238, + 'INSTRUMENTED_RETURN_VALUE': 239, + 'INSTRUMENTED_RETURN_CONST': 240, + 'INSTRUMENTED_YIELD_VALUE': 241, + 'INSTRUMENTED_LOAD_SUPER_ATTR': 242, + 'INSTRUMENTED_FOR_ITER': 243, + 'INSTRUMENTED_CALL': 244, + 'INSTRUMENTED_CALL_KW': 245, + 'INSTRUMENTED_CALL_FUNCTION_EX': 246, + 'INSTRUMENTED_INSTRUCTION': 247, + 'INSTRUMENTED_JUMP_FORWARD': 248, + 'INSTRUMENTED_JUMP_BACKWARD': 249, + 'INSTRUMENTED_POP_JUMP_IF_TRUE': 250, + 'INSTRUMENTED_POP_JUMP_IF_FALSE': 251, + 'INSTRUMENTED_POP_JUMP_IF_NONE': 252, + 'INSTRUMENTED_POP_JUMP_IF_NOT_NONE': 253, + 'INSTRUMENTED_LINE': 254, + 'LOAD_CLOSURE': 255, 'JUMP': 256, 'JUMP_NO_INTERRUPT': 257, 'RESERVED_258': 258, diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py index 60dcdc6cd70..045e010db4c 100644 --- a/Lib/test/test__opcode.py +++ b/Lib/test/test__opcode.py @@ -16,6 +16,7 @@ def check_bool_function_result(self, func, ops, expected): self.assertIsInstance(func(op), bool) self.assertEqual(func(op), expected) + @unittest.expectedFailure # TODO: RUSTPYTHON; Move LoadClosure to psudoes def test_invalid_opcodes(self): invalid = [-100, -1, 255, 512, 513, 1000] self.check_bool_function_result(_opcode.is_valid, invalid, False) @@ -27,7 +28,6 @@ def test_invalid_opcodes(self): self.check_bool_function_result(_opcode.has_local, invalid, False) self.check_bool_function_result(_opcode.has_exc, invalid, False) - @unittest.expectedFailure # TODO: RUSTPYTHON - no instrumented opcodes def test_is_valid(self): names = [ 'CACHE', diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index 3ebb3666ae2..44a57c44320 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -245,10 +245,7 @@ pub enum Instruction { YieldValue { arg: Arg, } = 118, - Resume { - arg: Arg, - } = 149, - // ==================== RustPython-only instructions (119-135) ==================== + // ==================== RustPython-only instructions (119-133) ==================== // Ideally, we want to be fully aligned with CPython opcodes, but we still have some leftovers. // So we assign random IDs to these opcodes. Break { @@ -277,10 +274,106 @@ pub enum Instruction { target: Arg